]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/scsi/sata_mv.c
[PATCH] qla2xxx: Close window on race between rport removal and fcport transition.
[linux-2.6-omap-h63xx.git] / drivers / scsi / sata_mv.c
index 4ca4b35d00225873a277acaf9ede03cc2cac5f28..cd54244058b513d7d8c56b42c0da6ab1f8c66172 100644 (file)
@@ -2,6 +2,7 @@
  * sata_mv.c - Marvell SATA support
  *
  * Copyright 2005: EMC Corporation, all rights reserved.
+ * Copyright 2005 Red Hat, Inc.  All rights reserved.
  *
  * Please ALWAYS copy linux-ide@vger.kernel.org on emails.
  *
@@ -36,7 +37,7 @@
 #include <asm/io.h>
 
 #define DRV_NAME       "sata_mv"
-#define DRV_VERSION    "0.25"
+#define DRV_VERSION    "0.5"
 
 enum {
        /* BAR's are enumerated in terms of pci_resource_start() terms */
@@ -50,6 +51,7 @@ enum {
        MV_PCI_REG_BASE         = 0,
        MV_IRQ_COAL_REG_BASE    = 0x18000,      /* 6xxx part only */
        MV_SATAHC0_REG_BASE     = 0x20000,
+       MV_FLASH_CTL            = 0x1046c,
        MV_GPIO_PORT_CTL        = 0x104f0,
        MV_RESET_CFG            = 0x180d8,
 
@@ -84,15 +86,10 @@ enum {
        MV_FLAG_DUAL_HC         = (1 << 30),  /* two SATA Host Controllers */
        MV_FLAG_IRQ_COALESCE    = (1 << 29),  /* IRQ coalescing capability */
        MV_COMMON_FLAGS         = (ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY |
-                                  ATA_FLAG_SATA_RESET | ATA_FLAG_MMIO),
+                                  ATA_FLAG_SATA_RESET | ATA_FLAG_MMIO |
+                                  ATA_FLAG_NO_ATAPI),
        MV_6XXX_FLAGS           = MV_FLAG_IRQ_COALESCE,
 
-       chip_504x               = 0,
-       chip_508x               = 1,
-       chip_5080               = 2,
-       chip_604x               = 3,
-       chip_608x               = 4,
-
        CRQB_FLAG_READ          = (1 << 0),
        CRQB_TAG_SHIFT          = 1,
        CRQB_CMD_ADDR_SHIFT     = 8,
@@ -112,8 +109,19 @@ enum {
        PCI_MASTER_EMPTY        = (1 << 3),
        GLOB_SFT_RST            = (1 << 4),
 
-       PCI_IRQ_CAUSE_OFS       = 0x1d58,
-       PCI_IRQ_MASK_OFS        = 0x1d5c,
+       MV_PCI_MODE             = 0xd00,
+       MV_PCI_EXP_ROM_BAR_CTL  = 0xd2c,
+       MV_PCI_DISC_TIMER       = 0xd04,
+       MV_PCI_MSI_TRIGGER      = 0xc38,
+       MV_PCI_SERR_MASK        = 0xc28,
+       MV_PCI_XBAR_TMOUT       = 0x1d04,
+       MV_PCI_ERR_LOW_ADDRESS  = 0x1d40,
+       MV_PCI_ERR_HIGH_ADDRESS = 0x1d44,
+       MV_PCI_ERR_ATTRIBUTE    = 0x1d48,
+       MV_PCI_ERR_COMMAND      = 0x1d50,
+
+       PCI_IRQ_CAUSE_OFS               = 0x1d58,
+       PCI_IRQ_MASK_OFS                = 0x1d5c,
        PCI_UNMASK_ALL_IRQS     = 0x7fffff,     /* bits 22-0 */
 
        HC_MAIN_IRQ_CAUSE_OFS   = 0x1d60,
@@ -152,6 +160,9 @@ enum {
        PHY_MODE3               = 0x310,
        PHY_MODE4               = 0x314,
        PHY_MODE2               = 0x330,
+       MV5_PHY_MODE            = 0x74,
+       MV5_LT_MODE             = 0x30,
+       MV5_PHY_CTL             = 0x0C,
        SATA_INTERFACE_CTL      = 0x050,
 
        MV_M2_PREAMP_MASK       = 0x7e0,
@@ -208,6 +219,7 @@ enum {
        EDMA_DS                 = (1 << 1),
        ATA_RST                 = (1 << 2),
 
+       EDMA_IORDY_TMOUT        = 0x34,
        EDMA_ARB_CFG            = 0x38,
 
        /* Host private flags (hp_flags) */
@@ -223,6 +235,7 @@ enum {
        MV_PP_FLAG_EDMA_DS_ACT  = (1 << 1),
 };
 
+#define IS_50XX(hpriv) ((hpriv)->hp_flags & MV_HP_50XX)
 #define IS_60XX(hpriv) (((hpriv)->hp_flags & MV_HP_50XX) == 0)
 
 enum {
@@ -236,6 +249,14 @@ enum {
        EDMA_RSP_Q_BASE_LO_MASK = 0xffffff00U,
 };
 
+enum chip_type {
+       chip_504x,
+       chip_508x,
+       chip_5080,
+       chip_604x,
+       chip_608x,
+};
+
 /* Command ReQuest Block: 32B */
 struct mv_crqb {
        u32                     sg_addr;
@@ -279,11 +300,15 @@ struct mv_port_signal {
 
 struct mv_host_priv;
 struct mv_hw_ops {
-       void (*phy_errata)(struct ata_port *ap);
+       void (*phy_errata)(struct mv_host_priv *hpriv, void __iomem *mmio,
+                          unsigned int port);
        void (*enable_leds)(struct mv_host_priv *hpriv, void __iomem *mmio);
        void (*read_preamp)(struct mv_host_priv *hpriv, int idx,
                           void __iomem *mmio);
-       int (*reset_hc)(struct mv_host_priv *hpriv, void __iomem *mmio);
+       int (*reset_hc)(struct mv_host_priv *hpriv, void __iomem *mmio,
+                       unsigned int n_hc);
+       void (*reset_flash)(struct mv_host_priv *hpriv, void __iomem *mmio);
+       void (*reset_bus)(struct pci_dev *pdev, void __iomem *mmio);
 };
 
 struct mv_host_priv {
@@ -295,7 +320,10 @@ struct mv_host_priv {
 static void mv_irq_clear(struct ata_port *ap);
 static u32 mv_scr_read(struct ata_port *ap, unsigned int sc_reg_in);
 static void mv_scr_write(struct ata_port *ap, unsigned int sc_reg_in, u32 val);
+static u32 mv5_scr_read(struct ata_port *ap, unsigned int sc_reg_in);
+static void mv5_scr_write(struct ata_port *ap, unsigned int sc_reg_in, u32 val);
 static void mv_phy_reset(struct ata_port *ap);
+static void __mv_phy_reset(struct ata_port *ap, int can_sleep);
 static void mv_host_stop(struct ata_host_set *host_set);
 static int mv_port_start(struct ata_port *ap);
 static void mv_port_stop(struct ata_port *ap);
@@ -306,17 +334,28 @@ static irqreturn_t mv_interrupt(int irq, void *dev_instance,
 static void mv_eng_timeout(struct ata_port *ap);
 static int mv_init_one(struct pci_dev *pdev, const struct pci_device_id *ent);
 
-static void mv5_phy_errata(struct ata_port *ap);
+static void mv5_phy_errata(struct mv_host_priv *hpriv, void __iomem *mmio,
+                          unsigned int port);
 static void mv5_enable_leds(struct mv_host_priv *hpriv, void __iomem *mmio);
 static void mv5_read_preamp(struct mv_host_priv *hpriv, int idx,
                           void __iomem *mmio);
-static int mv5_reset_hc(struct mv_host_priv *hpriv, void __iomem *mmio);
+static int mv5_reset_hc(struct mv_host_priv *hpriv, void __iomem *mmio,
+                       unsigned int n_hc);
+static void mv5_reset_flash(struct mv_host_priv *hpriv, void __iomem *mmio);
+static void mv5_reset_bus(struct pci_dev *pdev, void __iomem *mmio);
 
-static void mv6_phy_errata(struct ata_port *ap);
+static void mv6_phy_errata(struct mv_host_priv *hpriv, void __iomem *mmio,
+                          unsigned int port);
 static void mv6_enable_leds(struct mv_host_priv *hpriv, void __iomem *mmio);
 static void mv6_read_preamp(struct mv_host_priv *hpriv, int idx,
                           void __iomem *mmio);
-static int mv6_reset_hc(struct mv_host_priv *hpriv, void __iomem *mmio);
+static int mv6_reset_hc(struct mv_host_priv *hpriv, void __iomem *mmio,
+                       unsigned int n_hc);
+static void mv6_reset_flash(struct mv_host_priv *hpriv, void __iomem *mmio);
+static void mv_reset_pci_bus(struct pci_dev *pdev, void __iomem *mmio);
+static void mv_channel_reset(struct mv_host_priv *hpriv, void __iomem *mmio,
+                            unsigned int port_no);
+static void mv_stop_and_reset(struct ata_port *ap);
 
 static struct scsi_host_template mv_sht = {
        .module                 = THIS_MODULE,
@@ -326,7 +365,7 @@ static struct scsi_host_template mv_sht = {
        .eh_strategy_handler    = ata_scsi_error,
        .can_queue              = MV_USE_Q_DEPTH,
        .this_id                = ATA_SHT_THIS_ID,
-       .sg_tablesize           = MV_MAX_SG_CT,
+       .sg_tablesize           = MV_MAX_SG_CT / 2,
        .max_sectors            = ATA_MAX_SECTORS,
        .cmd_per_lun            = ATA_SHT_CMD_PER_LUN,
        .emulated               = ATA_SHT_EMULATED,
@@ -335,10 +374,36 @@ static struct scsi_host_template mv_sht = {
        .dma_boundary           = MV_DMA_BOUNDARY,
        .slave_configure        = ata_scsi_slave_config,
        .bios_param             = ata_std_bios_param,
-       .ordered_flush          = 1,
 };
 
-static const struct ata_port_operations mv_ops = {
+static const struct ata_port_operations mv5_ops = {
+       .port_disable           = ata_port_disable,
+
+       .tf_load                = ata_tf_load,
+       .tf_read                = ata_tf_read,
+       .check_status           = ata_check_status,
+       .exec_command           = ata_exec_command,
+       .dev_select             = ata_std_dev_select,
+
+       .phy_reset              = mv_phy_reset,
+
+       .qc_prep                = mv_qc_prep,
+       .qc_issue               = mv_qc_issue,
+
+       .eng_timeout            = mv_eng_timeout,
+
+       .irq_handler            = mv_interrupt,
+       .irq_clear              = mv_irq_clear,
+
+       .scr_read               = mv5_scr_read,
+       .scr_write              = mv5_scr_write,
+
+       .port_start             = mv_port_start,
+       .port_stop              = mv_port_stop,
+       .host_stop              = mv_host_stop,
+};
+
+static const struct ata_port_operations mv6_ops = {
        .port_disable           = ata_port_disable,
 
        .tf_load                = ata_tf_load,
@@ -365,34 +430,34 @@ static const struct ata_port_operations mv_ops = {
        .host_stop              = mv_host_stop,
 };
 
-static struct ata_port_info mv_port_info[] = {
+static const struct ata_port_info mv_port_info[] = {
        {  /* chip_504x */
                .sht            = &mv_sht,
                .host_flags     = MV_COMMON_FLAGS,
                .pio_mask       = 0x1f, /* pio0-4 */
-               .udma_mask      = 0,    /* 0x7f (udma0-6 disabled for now) */
-               .port_ops       = &mv_ops,
+               .udma_mask      = 0x7f, /* udma0-6 */
+               .port_ops       = &mv5_ops,
        },
        {  /* chip_508x */
                .sht            = &mv_sht,
                .host_flags     = (MV_COMMON_FLAGS | MV_FLAG_DUAL_HC),
                .pio_mask       = 0x1f, /* pio0-4 */
-               .udma_mask      = 0,    /* 0x7f (udma0-6 disabled for now) */
-               .port_ops       = &mv_ops,
+               .udma_mask      = 0x7f, /* udma0-6 */
+               .port_ops       = &mv5_ops,
        },
        {  /* chip_5080 */
                .sht            = &mv_sht,
                .host_flags     = (MV_COMMON_FLAGS | MV_FLAG_DUAL_HC),
                .pio_mask       = 0x1f, /* pio0-4 */
-               .udma_mask      = 0,    /* 0x7f (udma0-6 disabled for now) */
-               .port_ops       = &mv_ops,
+               .udma_mask      = 0x7f, /* udma0-6 */
+               .port_ops       = &mv5_ops,
        },
        {  /* chip_604x */
                .sht            = &mv_sht,
                .host_flags     = (MV_COMMON_FLAGS | MV_6XXX_FLAGS),
                .pio_mask       = 0x1f, /* pio0-4 */
                .udma_mask      = 0x7f, /* udma0-6 */
-               .port_ops       = &mv_ops,
+               .port_ops       = &mv6_ops,
        },
        {  /* chip_608x */
                .sht            = &mv_sht,
@@ -400,17 +465,15 @@ static struct ata_port_info mv_port_info[] = {
                                   MV_FLAG_DUAL_HC),
                .pio_mask       = 0x1f, /* pio0-4 */
                .udma_mask      = 0x7f, /* udma0-6 */
-               .port_ops       = &mv_ops,
+               .port_ops       = &mv6_ops,
        },
 };
 
 static const struct pci_device_id mv_pci_tbl[] = {
-#if 0 /* unusably broken right now */
        {PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x5040), 0, 0, chip_504x},
        {PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x5041), 0, 0, chip_504x},
        {PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x5080), 0, 0, chip_5080},
        {PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x5081), 0, 0, chip_508x},
-#endif
 
        {PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x6040), 0, 0, chip_604x},
        {PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x6041), 0, 0, chip_604x},
@@ -433,6 +496,8 @@ static const struct mv_hw_ops mv5xxx_ops = {
        .enable_leds            = mv5_enable_leds,
        .read_preamp            = mv5_read_preamp,
        .reset_hc               = mv5_reset_hc,
+       .reset_flash            = mv5_reset_flash,
+       .reset_bus              = mv5_reset_bus,
 };
 
 static const struct mv_hw_ops mv6xxx_ops = {
@@ -440,6 +505,8 @@ static const struct mv_hw_ops mv6xxx_ops = {
        .enable_leds            = mv6_enable_leds,
        .read_preamp            = mv6_read_preamp,
        .reset_hc               = mv6_reset_hc,
+       .reset_flash            = mv6_reset_flash,
+       .reset_bus              = mv_reset_pci_bus,
 };
 
 /*
@@ -457,11 +524,27 @@ static inline void __iomem *mv_hc_base(void __iomem *base, unsigned int hc)
        return (base + MV_SATAHC0_REG_BASE + (hc * MV_SATAHC_REG_SZ));
 }
 
+static inline unsigned int mv_hc_from_port(unsigned int port)
+{
+       return port >> MV_PORT_HC_SHIFT;
+}
+
+static inline unsigned int mv_hardport_from_port(unsigned int port)
+{
+       return port & MV_PORT_MASK;
+}
+
+static inline void __iomem *mv_hc_base_from_port(void __iomem *base,
+                                                unsigned int port)
+{
+       return mv_hc_base(base, mv_hc_from_port(port));
+}
+
 static inline void __iomem *mv_port_base(void __iomem *base, unsigned int port)
 {
-       return (mv_hc_base(base, port >> MV_PORT_HC_SHIFT) +
+       return  mv_hc_base_from_port(base, port) +
                MV_SATAHC_ARBTR_REG_SZ +
-               ((port & MV_PORT_MASK) * MV_PORT_REG_SZ));
+               (mv_hardport_from_port(port) * MV_PORT_REG_SZ);
 }
 
 static inline void __iomem *mv_ap_base(struct ata_port *ap)
@@ -655,70 +738,6 @@ static void mv_scr_write(struct ata_port *ap, unsigned int sc_reg_in, u32 val)
        }
 }
 
-/**
- *      mv_global_soft_reset - Perform the 6xxx global soft reset
- *      @mmio_base: base address of the HBA
- *
- *      This routine only applies to 6xxx parts.
- *
- *      LOCKING:
- *      Inherited from caller.
- */
-static int mv6_reset_hc(struct mv_host_priv *hpriv, void __iomem *mmio)
-{
-       void __iomem *reg = mmio + PCI_MAIN_CMD_STS_OFS;
-       int i, rc = 0;
-       u32 t;
-
-       /* Following procedure defined in PCI "main command and status
-        * register" table.
-        */
-       t = readl(reg);
-       writel(t | STOP_PCI_MASTER, reg);
-
-       for (i = 0; i < 1000; i++) {
-               udelay(1);
-               t = readl(reg);
-               if (PCI_MASTER_EMPTY & t) {
-                       break;
-               }
-       }
-       if (!(PCI_MASTER_EMPTY & t)) {
-               printk(KERN_ERR DRV_NAME ": PCI master won't flush\n");
-               rc = 1;
-               goto done;
-       }
-
-       /* set reset */
-       i = 5;
-       do {
-               writel(t | GLOB_SFT_RST, reg);
-               t = readl(reg);
-               udelay(1);
-       } while (!(GLOB_SFT_RST & t) && (i-- > 0));
-
-       if (!(GLOB_SFT_RST & t)) {
-               printk(KERN_ERR DRV_NAME ": can't set global reset\n");
-               rc = 1;
-               goto done;
-       }
-
-       /* clear reset and *reenable the PCI master* (not mentioned in spec) */
-       i = 5;
-       do {
-               writel(t & ~(GLOB_SFT_RST | STOP_PCI_MASTER), reg);
-               t = readl(reg);
-               udelay(1);
-       } while ((GLOB_SFT_RST & t) && (i-- > 0));
-
-       if (GLOB_SFT_RST & t) {
-               printk(KERN_ERR DRV_NAME ": can't clear global reset\n");
-               rc = 1;
-       }
-done:
-       return rc;
-}
-
 /**
  *      mv_host_stop - Host specific cleanup/stop routine.
  *      @host_set: host data structure
@@ -876,20 +895,30 @@ static void mv_fill_sg(struct ata_queued_cmd *qc)
        struct scatterlist *sg;
 
        ata_for_each_sg(sg, qc) {
-               u32 sg_len;
                dma_addr_t addr;
+               u32 sg_len, len, offset;
 
                addr = sg_dma_address(sg);
                sg_len = sg_dma_len(sg);
 
-               pp->sg_tbl[i].addr = cpu_to_le32(addr & 0xffffffff);
-               pp->sg_tbl[i].addr_hi = cpu_to_le32((addr >> 16) >> 16);
-               assert(0 == (sg_len & ~MV_DMA_BOUNDARY));
-               pp->sg_tbl[i].flags_size = cpu_to_le32(sg_len);
-               if (ata_sg_is_last(sg, qc))
-                       pp->sg_tbl[i].flags_size |= cpu_to_le32(EPRD_FLAG_END_OF_TBL);
+               while (sg_len) {
+                       offset = addr & MV_DMA_BOUNDARY;
+                       len = sg_len;
+                       if ((offset + sg_len) > 0x10000)
+                               len = 0x10000 - offset;
 
-               i++;
+                       pp->sg_tbl[i].addr = cpu_to_le32(addr & 0xffffffff);
+                       pp->sg_tbl[i].addr_hi = cpu_to_le32((addr >> 16) >> 16);
+                       pp->sg_tbl[i].flags_size = cpu_to_le32(len);
+
+                       sg_len -= len;
+                       addr += len;
+
+                       if (!sg_len && ata_sg_is_last(sg, qc))
+                               pp->sg_tbl[i].flags_size |= cpu_to_le32(EPRD_FLAG_END_OF_TBL);
+
+                       i++;
+               }
        }
 }
 
@@ -1126,7 +1155,7 @@ static void mv_err_intr(struct ata_port *ap)
 
        /* check for fatal here and recover if needed */
        if (EDMA_ERR_FATAL & edma_err_cause) {
-               mv_phy_reset(ap);
+               mv_stop_and_reset(ap);
        }
 }
 
@@ -1191,6 +1220,10 @@ static void mv_host_intr(struct ata_host_set *host_set, u32 relevant,
                        handled++;
                }
 
+               if (ap &&
+                   (ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR)))
+                       continue;
+
                err_mask = ac_err_mask(ata_status);
 
                shift = port << 1;              /* (port * 2) */
@@ -1209,7 +1242,10 @@ static void mv_host_intr(struct ata_host_set *host_set, u32 relevant,
                                VPRINTK("port %u IRQ found for qc, "
                                        "ata_status 0x%x\n", port,ata_status);
                                /* mark qc status appropriately */
-                               ata_qc_complete(qc, err_mask);
+                               if (!(qc->tf.ctl & ATA_NIEN)) {
+                                       qc->err_mask |= err_mask;
+                                       ata_qc_complete(qc);
+                               }
                        }
                }
        }
@@ -1273,26 +1309,287 @@ static irqreturn_t mv_interrupt(int irq, void *dev_instance,
        return IRQ_RETVAL(handled);
 }
 
+static void __iomem *mv5_phy_base(void __iomem *mmio, unsigned int port)
+{
+       void __iomem *hc_mmio = mv_hc_base_from_port(mmio, port);
+       unsigned long ofs = (mv_hardport_from_port(port) + 1) * 0x100UL;
+
+       return hc_mmio + ofs;
+}
+
+static unsigned int mv5_scr_offset(unsigned int sc_reg_in)
+{
+       unsigned int ofs;
+
+       switch (sc_reg_in) {
+       case SCR_STATUS:
+       case SCR_ERROR:
+       case SCR_CONTROL:
+               ofs = sc_reg_in * sizeof(u32);
+               break;
+       default:
+               ofs = 0xffffffffU;
+               break;
+       }
+       return ofs;
+}
+
+static u32 mv5_scr_read(struct ata_port *ap, unsigned int sc_reg_in)
+{
+       void __iomem *mmio = mv5_phy_base(ap->host_set->mmio_base, ap->port_no);
+       unsigned int ofs = mv5_scr_offset(sc_reg_in);
+
+       if (ofs != 0xffffffffU)
+               return readl(mmio + ofs);
+       else
+               return (u32) ofs;
+}
+
+static void mv5_scr_write(struct ata_port *ap, unsigned int sc_reg_in, u32 val)
+{
+       void __iomem *mmio = mv5_phy_base(ap->host_set->mmio_base, ap->port_no);
+       unsigned int ofs = mv5_scr_offset(sc_reg_in);
+
+       if (ofs != 0xffffffffU)
+               writelfl(val, mmio + ofs);
+}
+
+static void mv5_reset_bus(struct pci_dev *pdev, void __iomem *mmio)
+{
+       u8 rev_id;
+       int early_5080;
+
+       pci_read_config_byte(pdev, PCI_REVISION_ID, &rev_id);
+
+       early_5080 = (pdev->device == 0x5080) && (rev_id == 0);
+
+       if (!early_5080) {
+               u32 tmp = readl(mmio + MV_PCI_EXP_ROM_BAR_CTL);
+               tmp |= (1 << 0);
+               writel(tmp, mmio + MV_PCI_EXP_ROM_BAR_CTL);
+       }
+
+       mv_reset_pci_bus(pdev, mmio);
+}
+
+static void mv5_reset_flash(struct mv_host_priv *hpriv, void __iomem *mmio)
+{
+       writel(0x0fcfffff, mmio + MV_FLASH_CTL);
+}
+
 static void mv5_read_preamp(struct mv_host_priv *hpriv, int idx,
                           void __iomem *mmio)
 {
-       /* FIXME */
+       void __iomem *phy_mmio = mv5_phy_base(mmio, idx);
+       u32 tmp;
+
+       tmp = readl(phy_mmio + MV5_PHY_MODE);
+
+       hpriv->signal[idx].pre = tmp & 0x1800;  /* bits 12:11 */
+       hpriv->signal[idx].amps = tmp & 0xe0;   /* bits 7:5 */
 }
 
 static void mv5_enable_leds(struct mv_host_priv *hpriv, void __iomem *mmio)
 {
-       /* FIXME */
+       u32 tmp;
+
+       writel(0, mmio + MV_GPIO_PORT_CTL);
+
+       /* FIXME: handle MV_HP_ERRATA_50XXB2 errata */
+
+       tmp = readl(mmio + MV_PCI_EXP_ROM_BAR_CTL);
+       tmp |= ~(1 << 0);
+       writel(tmp, mmio + MV_PCI_EXP_ROM_BAR_CTL);
+}
+
+static void mv5_phy_errata(struct mv_host_priv *hpriv, void __iomem *mmio,
+                          unsigned int port)
+{
+       void __iomem *phy_mmio = mv5_phy_base(mmio, port);
+       const u32 mask = (1<<12) | (1<<11) | (1<<7) | (1<<6) | (1<<5);
+       u32 tmp;
+       int fix_apm_sq = (hpriv->hp_flags & MV_HP_ERRATA_50XXB0);
+
+       if (fix_apm_sq) {
+               tmp = readl(phy_mmio + MV5_LT_MODE);
+               tmp |= (1 << 19);
+               writel(tmp, phy_mmio + MV5_LT_MODE);
+
+               tmp = readl(phy_mmio + MV5_PHY_CTL);
+               tmp &= ~0x3;
+               tmp |= 0x1;
+               writel(tmp, phy_mmio + MV5_PHY_CTL);
+       }
+
+       tmp = readl(phy_mmio + MV5_PHY_MODE);
+       tmp &= ~mask;
+       tmp |= hpriv->signal[port].pre;
+       tmp |= hpriv->signal[port].amps;
+       writel(tmp, phy_mmio + MV5_PHY_MODE);
+}
+
+
+#undef ZERO
+#define ZERO(reg) writel(0, port_mmio + (reg))
+static void mv5_reset_hc_port(struct mv_host_priv *hpriv, void __iomem *mmio,
+                            unsigned int port)
+{
+       void __iomem *port_mmio = mv_port_base(mmio, port);
+
+       writelfl(EDMA_DS, port_mmio + EDMA_CMD_OFS);
+
+       mv_channel_reset(hpriv, mmio, port);
+
+       ZERO(0x028);    /* command */
+       writel(0x11f, port_mmio + EDMA_CFG_OFS);
+       ZERO(0x004);    /* timer */
+       ZERO(0x008);    /* irq err cause */
+       ZERO(0x00c);    /* irq err mask */
+       ZERO(0x010);    /* rq bah */
+       ZERO(0x014);    /* rq inp */
+       ZERO(0x018);    /* rq outp */
+       ZERO(0x01c);    /* respq bah */
+       ZERO(0x024);    /* respq outp */
+       ZERO(0x020);    /* respq inp */
+       ZERO(0x02c);    /* test control */
+       writel(0xbc, port_mmio + EDMA_IORDY_TMOUT);
 }
+#undef ZERO
 
-static void mv5_phy_errata(struct ata_port *ap)
+#define ZERO(reg) writel(0, hc_mmio + (reg))
+static void mv5_reset_one_hc(struct mv_host_priv *hpriv, void __iomem *mmio,
+                       unsigned int hc)
 {
-       /* FIXME */
+       void __iomem *hc_mmio = mv_hc_base(mmio, hc);
+       u32 tmp;
+
+       ZERO(0x00c);
+       ZERO(0x010);
+       ZERO(0x014);
+       ZERO(0x018);
+
+       tmp = readl(hc_mmio + 0x20);
+       tmp &= 0x1c1c1c1c;
+       tmp |= 0x03030303;
+       writel(tmp, hc_mmio + 0x20);
 }
+#undef ZERO
 
-static int mv5_reset_hc(struct mv_host_priv *hpriv, void __iomem *mmio)
+static int mv5_reset_hc(struct mv_host_priv *hpriv, void __iomem *mmio,
+                       unsigned int n_hc)
 {
-       /* FIXME */
-       return 1;
+       unsigned int hc, port;
+
+       for (hc = 0; hc < n_hc; hc++) {
+               for (port = 0; port < MV_PORTS_PER_HC; port++)
+                       mv5_reset_hc_port(hpriv, mmio,
+                                         (hc * MV_PORTS_PER_HC) + port);
+
+               mv5_reset_one_hc(hpriv, mmio, hc);
+       }
+
+       return 0;
+}
+
+#undef ZERO
+#define ZERO(reg) writel(0, mmio + (reg))
+static void mv_reset_pci_bus(struct pci_dev *pdev, void __iomem *mmio)
+{
+       u32 tmp;
+
+       tmp = readl(mmio + MV_PCI_MODE);
+       tmp &= 0xff00ffff;
+       writel(tmp, mmio + MV_PCI_MODE);
+
+       ZERO(MV_PCI_DISC_TIMER);
+       ZERO(MV_PCI_MSI_TRIGGER);
+       writel(0x000100ff, mmio + MV_PCI_XBAR_TMOUT);
+       ZERO(HC_MAIN_IRQ_MASK_OFS);
+       ZERO(MV_PCI_SERR_MASK);
+       ZERO(PCI_IRQ_CAUSE_OFS);
+       ZERO(PCI_IRQ_MASK_OFS);
+       ZERO(MV_PCI_ERR_LOW_ADDRESS);
+       ZERO(MV_PCI_ERR_HIGH_ADDRESS);
+       ZERO(MV_PCI_ERR_ATTRIBUTE);
+       ZERO(MV_PCI_ERR_COMMAND);
+}
+#undef ZERO
+
+static void mv6_reset_flash(struct mv_host_priv *hpriv, void __iomem *mmio)
+{
+       u32 tmp;
+
+       mv5_reset_flash(hpriv, mmio);
+
+       tmp = readl(mmio + MV_GPIO_PORT_CTL);
+       tmp &= 0x3;
+       tmp |= (1 << 5) | (1 << 6);
+       writel(tmp, mmio + MV_GPIO_PORT_CTL);
+}
+
+/**
+ *      mv6_reset_hc - Perform the 6xxx global soft reset
+ *      @mmio: base address of the HBA
+ *
+ *      This routine only applies to 6xxx parts.
+ *
+ *      LOCKING:
+ *      Inherited from caller.
+ */
+static int mv6_reset_hc(struct mv_host_priv *hpriv, void __iomem *mmio,
+                       unsigned int n_hc)
+{
+       void __iomem *reg = mmio + PCI_MAIN_CMD_STS_OFS;
+       int i, rc = 0;
+       u32 t;
+
+       /* Following procedure defined in PCI "main command and status
+        * register" table.
+        */
+       t = readl(reg);
+       writel(t | STOP_PCI_MASTER, reg);
+
+       for (i = 0; i < 1000; i++) {
+               udelay(1);
+               t = readl(reg);
+               if (PCI_MASTER_EMPTY & t) {
+                       break;
+               }
+       }
+       if (!(PCI_MASTER_EMPTY & t)) {
+               printk(KERN_ERR DRV_NAME ": PCI master won't flush\n");
+               rc = 1;
+               goto done;
+       }
+
+       /* set reset */
+       i = 5;
+       do {
+               writel(t | GLOB_SFT_RST, reg);
+               t = readl(reg);
+               udelay(1);
+       } while (!(GLOB_SFT_RST & t) && (i-- > 0));
+
+       if (!(GLOB_SFT_RST & t)) {
+               printk(KERN_ERR DRV_NAME ": can't set global reset\n");
+               rc = 1;
+               goto done;
+       }
+
+       /* clear reset and *reenable the PCI master* (not mentioned in spec) */
+       i = 5;
+       do {
+               writel(t & ~(GLOB_SFT_RST | STOP_PCI_MASTER), reg);
+               t = readl(reg);
+               udelay(1);
+       } while ((GLOB_SFT_RST & t) && (i-- > 0));
+
+       if (GLOB_SFT_RST & t) {
+               printk(KERN_ERR DRV_NAME ": can't clear global reset\n");
+               rc = 1;
+       }
+done:
+       return rc;
 }
 
 static void mv6_read_preamp(struct mv_host_priv *hpriv, int idx,
@@ -1320,11 +1617,12 @@ static void mv6_enable_leds(struct mv_host_priv *hpriv, void __iomem *mmio)
        writel(0x00000060, mmio + MV_GPIO_PORT_CTL);
 }
 
-static void mv6_phy_errata(struct ata_port *ap)
+static void mv6_phy_errata(struct mv_host_priv *hpriv, void __iomem *mmio,
+                          unsigned int port)
 {
-       struct mv_host_priv *hpriv = ap->host_set->private_data;
+       void __iomem *port_mmio = mv_port_base(mmio, port);
+
        u32 hp_flags = hpriv->hp_flags;
-       void __iomem *port_mmio = mv_ap_base(ap);
        int fix_phy_mode2 =
                hp_flags & (MV_HP_ERRATA_60X1B2 | MV_HP_ERRATA_60X1C0);
        int fix_phy_mode4 =
@@ -1372,15 +1670,61 @@ static void mv6_phy_errata(struct ata_port *ap)
        m2 = readl(port_mmio + PHY_MODE2);
 
        m2 &= ~MV_M2_PREAMP_MASK;
-       m2 |= hpriv->signal[ap->port_no].amps;
-       m2 |= hpriv->signal[ap->port_no].pre;
+       m2 |= hpriv->signal[port].amps;
+       m2 |= hpriv->signal[port].pre;
        m2 &= ~(1 << 16);
 
        writel(m2, port_mmio + PHY_MODE2);
 }
 
+static void mv_channel_reset(struct mv_host_priv *hpriv, void __iomem *mmio,
+                            unsigned int port_no)
+{
+       void __iomem *port_mmio = mv_port_base(mmio, port_no);
+
+       writelfl(ATA_RST, port_mmio + EDMA_CMD_OFS);
+
+       if (IS_60XX(hpriv)) {
+               u32 ifctl = readl(port_mmio + SATA_INTERFACE_CTL);
+               ifctl |= (1 << 12) | (1 << 7);
+               writelfl(ifctl, port_mmio + SATA_INTERFACE_CTL);
+       }
+
+       udelay(25);             /* allow reset propagation */
+
+       /* Spec never mentions clearing the bit.  Marvell's driver does
+        * clear the bit, however.
+        */
+       writelfl(0, port_mmio + EDMA_CMD_OFS);
+
+       hpriv->ops->phy_errata(hpriv, mmio, port_no);
+
+       if (IS_50XX(hpriv))
+               mdelay(1);
+}
+
+static void mv_stop_and_reset(struct ata_port *ap)
+{
+       struct mv_host_priv *hpriv = ap->host_set->private_data;
+       void __iomem *mmio = ap->host_set->mmio_base;
+
+       mv_stop_dma(ap);
+
+       mv_channel_reset(hpriv, mmio, ap->port_no);
+
+       __mv_phy_reset(ap, 0);
+}
+
+static inline void __msleep(unsigned int msec, int can_sleep)
+{
+       if (can_sleep)
+               msleep(msec);
+       else
+               mdelay(msec);
+}
+
 /**
- *      mv_phy_reset - Perform eDMA reset followed by COMRESET
+ *      __mv_phy_reset - Perform eDMA reset followed by COMRESET
  *      @ap: ATA channel to manipulate
  *
  *      Part of this is taken from __sata_phy_reset and modified to
@@ -1390,7 +1734,7 @@ static void mv6_phy_errata(struct ata_port *ap)
  *      Inherited from caller.  This is coded to safe to call at
  *      interrupt level, i.e. it does not sleep.
  */
-static void mv_phy_reset(struct ata_port *ap)
+static void __mv_phy_reset(struct ata_port *ap, int can_sleep)
 {
        struct mv_port_priv *pp = ap->private_data;
        struct mv_host_priv *hpriv = ap->host_set->private_data;
@@ -1398,44 +1742,37 @@ static void mv_phy_reset(struct ata_port *ap)
        struct ata_taskfile tf;
        struct ata_device *dev = &ap->device[0];
        unsigned long timeout;
+       int retry = 5;
+       u32 sstatus;
 
        VPRINTK("ENTER, port %u, mmio 0x%p\n", ap->port_no, port_mmio);
 
-       mv_stop_dma(ap);
-
-       writelfl(ATA_RST, port_mmio + EDMA_CMD_OFS);
-
-       if (IS_60XX(hpriv)) {
-               u32 ifctl = readl(port_mmio + SATA_INTERFACE_CTL);
-               ifctl |= (1 << 12) | (1 << 7);
-               writelfl(ifctl, port_mmio + SATA_INTERFACE_CTL);
-       }
-
-       udelay(25);             /* allow reset propagation */
-
-       /* Spec never mentions clearing the bit.  Marvell's driver does
-        * clear the bit, however.
-        */
-       writelfl(0, port_mmio + EDMA_CMD_OFS);
-
-       hpriv->ops->phy_errata(ap);
-
        DPRINTK("S-regs after ATA_RST: SStat 0x%08x SErr 0x%08x "
                "SCtrl 0x%08x\n", mv_scr_read(ap, SCR_STATUS),
                mv_scr_read(ap, SCR_ERROR), mv_scr_read(ap, SCR_CONTROL));
 
-       /* proceed to init communications via the scr_control reg */
+       /* Issue COMRESET via SControl */
+comreset_retry:
        scr_write_flush(ap, SCR_CONTROL, 0x301);
-       mdelay(1);
+       __msleep(1, can_sleep);
+
        scr_write_flush(ap, SCR_CONTROL, 0x300);
-       timeout = jiffies + (HZ * 1);
+       __msleep(20, can_sleep);
+
+       timeout = jiffies + msecs_to_jiffies(200);
        do {
-               mdelay(10);
-               if ((scr_read(ap, SCR_STATUS) & 0xf) != 1)
+               sstatus = scr_read(ap, SCR_STATUS) & 0x3;
+               if ((sstatus == 3) || (sstatus == 0))
                        break;
+
+               __msleep(1, can_sleep);
        } while (time_before(jiffies, timeout));
 
-       mv_scr_write(ap, SCR_ERROR, mv_scr_read(ap, SCR_ERROR));
+       /* work around errata */
+       if (IS_60XX(hpriv) &&
+           (sstatus != 0x0) && (sstatus != 0x113) && (sstatus != 0x123) &&
+           (retry-- > 0))
+               goto comreset_retry;
 
        DPRINTK("S-regs after PHY wake: SStat 0x%08x SErr 0x%08x "
                "SCtrl 0x%08x\n", mv_scr_read(ap, SCR_STATUS),
@@ -1451,6 +1788,21 @@ static void mv_phy_reset(struct ata_port *ap)
        }
        ap->cbl = ATA_CBL_SATA;
 
+       /* even after SStatus reflects that device is ready,
+        * it seems to take a while for link to be fully
+        * established (and thus Status no longer 0x80/0x7F),
+        * so we poll a bit for that, here.
+        */
+       retry = 20;
+       while (1) {
+               u8 drv_stat = ata_check_status(ap);
+               if ((drv_stat != 0x80) && (drv_stat != 0x7f))
+                       break;
+               __msleep(500, can_sleep);
+               if (retry-- <= 0)
+                       break;
+       }
+
        tf.lbah = readb((void __iomem *) ap->ioaddr.lbah_addr);
        tf.lbam = readb((void __iomem *) ap->ioaddr.lbam_addr);
        tf.lbal = readb((void __iomem *) ap->ioaddr.lbal_addr);
@@ -1469,6 +1821,11 @@ static void mv_phy_reset(struct ata_port *ap)
        VPRINTK("EXIT\n");
 }
 
+static void mv_phy_reset(struct ata_port *ap)
+{
+       __mv_phy_reset(ap, 1);
+}
+
 /**
  *      mv_eng_timeout - Routine called by libata when SCSI times out I/O
  *      @ap: ATA channel to manipulate
@@ -1495,7 +1852,7 @@ static void mv_eng_timeout(struct ata_port *ap)
               &qc->scsicmd->cmnd);
 
        mv_err_intr(ap);
-       mv_phy_reset(ap);
+       mv_stop_and_reset(ap);
 
        if (!qc) {
                printk(KERN_ERR "ata%u: BUG: timeout without command\n",
@@ -1509,7 +1866,8 @@ static void mv_eng_timeout(struct ata_port *ap)
                 */
                spin_lock_irqsave(&ap->host_set->lock, flags);
                qc->scsidone = scsi_finish_command;
-               ata_qc_complete(qc, AC_ERR_OTHER);
+               qc->err_mask |= AC_ERR_OTHER;
+               ata_qc_complete(qc);
                spin_unlock_irqrestore(&ap->host_set->lock, flags);
        }
 }
@@ -1564,7 +1922,7 @@ static void mv_port_init(struct ata_ioports *port,  void __iomem *port_mmio)
 }
 
 static int mv_chip_id(struct pci_dev *pdev, struct mv_host_priv *hpriv,
-                        unsigned int board_idx)
+                     unsigned int board_idx)
 {
        u8 rev_id;
        u32 hp_flags = hpriv->hp_flags;
@@ -1656,7 +2014,6 @@ static int mv_init_host(struct pci_dev *pdev, struct ata_probe_ent *probe_ent,
 {
        int rc = 0, n_hc, port, hc;
        void __iomem *mmio = probe_ent->mmio_base;
-       void __iomem *port_mmio;
        struct mv_host_priv *hpriv = probe_ent->private_data;
 
        /* global interrupt mask */
@@ -1672,14 +2029,28 @@ static int mv_init_host(struct pci_dev *pdev, struct ata_probe_ent *probe_ent,
        for (port = 0; port < probe_ent->n_ports; port++)
                hpriv->ops->read_preamp(hpriv, port, mmio);
 
-       rc = hpriv->ops->reset_hc(hpriv, mmio);
+       rc = hpriv->ops->reset_hc(hpriv, mmio, n_hc);
        if (rc)
                goto done;
 
+       hpriv->ops->reset_flash(hpriv, mmio);
+       hpriv->ops->reset_bus(pdev, mmio);
        hpriv->ops->enable_leds(hpriv, mmio);
 
        for (port = 0; port < probe_ent->n_ports; port++) {
-               port_mmio = mv_port_base(mmio, port);
+               if (IS_60XX(hpriv)) {
+                       void __iomem *port_mmio = mv_port_base(mmio, port);
+
+                       u32 ifctl = readl(port_mmio + SATA_INTERFACE_CTL);
+                       ifctl |= (1 << 12);
+                       writelfl(ifctl, port_mmio + SATA_INTERFACE_CTL);
+               }
+
+               hpriv->ops->phy_errata(hpriv, mmio, port);
+       }
+
+       for (port = 0; port < probe_ent->n_ports; port++) {
+               void __iomem *port_mmio = mv_port_base(mmio, port);
                mv_port_init(&probe_ent->port[port], port_mmio);
        }