[libata sata_mv] implement a bunch of errata workarounds
authorJeff Garzik <jgarzik@pobox.com>
Sat, 12 Nov 2005 17:48:15 +0000 (12:48 -0500)
committerJeff Garzik <jgarzik@pobox.com>
Sat, 12 Nov 2005 17:48:15 +0000 (12:48 -0500)
Based largely on the GPL'd Marvell vendor driver.

drivers/scsi/sata_mv.c

index 26e9d51e6caf01fe01e60a1c9f11b6c5e214ff2c..b1696e353dd417475680758c91724b6210079b1f 100644 (file)
@@ -50,6 +50,8 @@ enum {
        MV_PCI_REG_BASE         = 0,
        MV_IRQ_COAL_REG_BASE    = 0x18000,      /* 6xxx part only */
        MV_SATAHC0_REG_BASE     = 0x20000,
+       MV_GPIO_PORT_CTL        = 0x104f0,
+       MV_RESET_CFG            = 0x180d8,
 
        MV_PCI_REG_SZ           = MV_MAJOR_REG_AREA_SZ,
        MV_SATAHC_REG_SZ        = MV_MAJOR_REG_AREA_SZ,
@@ -148,6 +150,11 @@ enum {
        /* SATA registers */
        SATA_STATUS_OFS         = 0x300,  /* ctrl, err regs follow status */
        SATA_ACTIVE_OFS         = 0x350,
+       PHY_MODE4               = 0x314,
+       PHY_MODE2               = 0x330,
+       SATA_INTERFACE_CTL      = 0x050,
+
+       MV_M2_PREAMP_MASK       = 0x7e0,
 
        /* Port registers */
        EDMA_CFG_OFS            = 0,
@@ -201,14 +208,26 @@ enum {
        EDMA_DS                 = (1 << 1),
        ATA_RST                 = (1 << 2),
 
+       EDMA_ARB_CFG            = 0x38,
+       EDMA_NO_SNOOP           = (1 << 6),
+
        /* Host private flags (hp_flags) */
        MV_HP_FLAG_MSI          = (1 << 0),
+       MV_HP_ERRATA_60X1A1     = (1 << 1),
+       MV_HP_ERRATA_60X1B0     = (1 << 2),
+       MV_HP_ERRATA_50XXB0     = (1 << 3),
+       MV_HP_ERRATA_50XXB1     = (1 << 4),
+       MV_HP_ERRATA_50XXB2     = (1 << 5),
+       MV_HP_50XX              = (1 << 6),
 
        /* Port private flags (pp_flags) */
        MV_PP_FLAG_EDMA_EN      = (1 << 0),
        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 {
        /* Our DMA boundary is determined by an ePRD being unable to handle
         * anything larger than 64KB
@@ -256,8 +275,14 @@ struct mv_port_priv {
        u32                     pp_flags;
 };
 
+struct mv_port_signal {
+       u32                     amps;
+       u32                     pre;
+};
+
 struct mv_host_priv {
        u32                     hp_flags;
+       struct mv_port_signal   signal[8];
 };
 
 static void mv_irq_clear(struct ata_port *ap);
@@ -354,10 +379,12 @@ static struct ata_port_info mv_port_info[] = {
 };
 
 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_508x},
        {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},
@@ -402,9 +429,9 @@ static inline void __iomem *mv_ap_base(struct ata_port *ap)
        return mv_port_base(ap->host_set->mmio_base, ap->port_no);
 }
 
-static inline int mv_get_hc_count(unsigned long hp_flags)
+static inline int mv_get_hc_count(unsigned long host_flags)
 {
-       return ((hp_flags & MV_FLAG_DUAL_HC) ? 2 : 1);
+       return ((host_flags & MV_FLAG_DUAL_HC) ? 2 : 1);
 }
 
 static void mv_irq_clear(struct ata_port *ap)
@@ -694,6 +721,7 @@ static inline void mv_priv_free(struct mv_port_priv *pp, struct device *dev)
 static int mv_port_start(struct ata_port *ap)
 {
        struct device *dev = ap->host_set->dev;
+       struct mv_host_priv *hpriv = ap->host_set->private_data;
        struct mv_port_priv *pp;
        void __iomem *port_mmio = mv_ap_base(ap);
        void *mem;
@@ -751,6 +779,15 @@ static int mv_port_start(struct ata_port *ap)
        writelfl(pp->crpb_dma & EDMA_RSP_Q_BASE_LO_MASK,
                 port_mmio + EDMA_RSP_Q_OUT_PTR_OFS);
 
+       if (hpriv->hp_flags & MV_HP_ERRATA_60X1A1) {
+               u32 new_tmp, tmp;
+
+               new_tmp = tmp = readl(port_mmio + EDMA_ARB_CFG);
+               new_tmp &= ~EDMA_NO_SNOOP;
+               if (new_tmp != tmp)
+                       writel(new_tmp, port_mmio + EDMA_ARB_CFG);
+       }
+
        pp->req_producer = pp->rsp_consumer = 0;
 
        /* Don't turn on EDMA here...do it before DMA commands only.  Else
@@ -1206,6 +1243,52 @@ static irqreturn_t mv_interrupt(int irq, void *dev_instance,
        return IRQ_RETVAL(handled);
 }
 
+static void mv_phy_errata5(struct ata_port *ap)
+{
+       /* FIXME */
+}
+
+static void mv_phy_errata6(struct ata_port *ap)
+{
+       struct mv_host_priv *hpriv = ap->host_set->private_data;
+       u32 hp_flags = hpriv->hp_flags;
+       void __iomem *port_mmio = mv_ap_base(ap);
+       int fix_phy_mode4 =
+               hp_flags & (MV_HP_ERRATA_60X1A1 | MV_HP_ERRATA_60X1B0);
+       u32 m2;
+
+       if (fix_phy_mode4) {
+               u32 tmp, m4;
+
+               m4 = readl(port_mmio + PHY_MODE4);
+               tmp = readl(port_mmio + 0x310);
+
+               m4 = (m4 & ~(1 << 1)) | (1 << 0);
+
+               writel(m4, port_mmio + PHY_MODE4);
+               writel(tmp, port_mmio + 0x310);
+       }
+
+       /* Revert values of pre-emphasis and signal amps to the saved ones */
+       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;
+
+       writel(m2, port_mmio + PHY_MODE2);
+}
+
+static void mv_phy_errata(struct ata_port *ap)
+{
+       struct mv_host_priv *hpriv = ap->host_set->private_data;
+
+       if (IS_50XX(hpriv))
+               mv_phy_errata5(ap);
+       else
+               mv_phy_errata6(ap);
+}
+
 /**
  *      mv_phy_reset - Perform eDMA reset followed by COMRESET
  *      @ap: ATA channel to manipulate
@@ -1220,6 +1303,7 @@ static irqreturn_t mv_interrupt(int irq, void *dev_instance,
 static void mv_phy_reset(struct ata_port *ap)
 {
        struct mv_port_priv *pp = ap->private_data;
+       struct mv_host_priv *hpriv = ap->host_set->private_data;
        void __iomem *port_mmio = mv_ap_base(ap);
        struct ata_taskfile tf;
        struct ata_device *dev = &ap->device[0];
@@ -1230,6 +1314,13 @@ static void mv_phy_reset(struct ata_port *ap)
        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
@@ -1237,6 +1328,8 @@ static void mv_phy_reset(struct ata_port *ap)
         */
        writelfl(0, port_mmio + EDMA_CMD_OFS);
 
+       mv_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));
@@ -1283,7 +1376,7 @@ static void mv_phy_reset(struct ata_port *ap)
 
        pp->pp_flags &= ~MV_PP_FLAG_EDMA_EN;
 
-       printk("EXIT\n");
+       VPRINTK("EXIT\n");
 }
 
 /**
@@ -1380,8 +1473,152 @@ static void mv_port_init(struct ata_ioports *port,  void __iomem *port_mmio)
                readl(port_mmio + EDMA_ERR_IRQ_MASK_OFS));
 }
 
+static void mv_enable_leds5(struct mv_host_priv *hpriv, void __iomem *mmio)
+{
+       /* FIXME */
+}
+
+static void mv_enable_leds6(struct mv_host_priv *hpriv, void __iomem *mmio)
+{
+       if (hpriv->hp_flags & MV_HP_ERRATA_60X1A1)
+               writel(0x00020060, mmio + MV_GPIO_PORT_CTL);
+
+       else if (hpriv->hp_flags & MV_HP_ERRATA_60X1B0)
+               writel(0x00000060, mmio + MV_GPIO_PORT_CTL);
+}
+
+static void mv_enable_leds(struct mv_host_priv *hpriv, void __iomem *mmio)
+{
+       if (IS_50XX(hpriv))
+               mv_enable_leds5(hpriv, mmio);
+       else
+               mv_enable_leds6(hpriv, mmio);
+}
+
+static void mv_cfg_signal5(struct mv_host_priv *hpriv, int idx,
+                          void __iomem *mmio)
+{
+       /* FIXME */
+}
+
+static void mv_cfg_signal6(struct mv_host_priv *hpriv, int idx,
+                          void __iomem *mmio)
+{
+       void __iomem *port_mmio;
+       u32 tmp;
+
+       if (hpriv->hp_flags & MV_HP_ERRATA_60X1A1) {
+               hpriv->signal[idx].amps = 0x5 << 8;
+               hpriv->signal[idx].pre = 0x3 << 5;
+               return;
+       }
+
+       assert (hpriv->hp_flags & MV_HP_ERRATA_60X1B0);
+
+       tmp = readl(mmio + MV_RESET_CFG);
+       if ((tmp & (1 << 0)) == 0) {
+               hpriv->signal[idx].amps = 0x4 << 8;
+               hpriv->signal[idx].pre = 0x1 << 5;
+               return;
+       }
+
+       port_mmio = mv_port_base(mmio, idx);
+       tmp = readl(port_mmio + PHY_MODE2);
+
+       hpriv->signal[idx].amps = tmp & 0x700;  /* bits 10:8 */
+       hpriv->signal[idx].pre = tmp & 0xe0;    /* bits 7:5 */
+}
+
+static int mv_cfg_errata(struct pci_dev *pdev, struct mv_host_priv *hpriv,
+                        unsigned int board_idx)
+{
+       u8 rev_id;
+       u32 hp_flags = hpriv->hp_flags;
+
+       pci_read_config_byte(pdev, PCI_REVISION_ID, &rev_id);
+
+       switch(board_idx) {
+       case chip_504x:
+       case chip_508x:
+               hp_flags |= MV_HP_50XX;
+
+               if (pdev->device == 0x5080) {
+                       switch (rev_id) {
+                       case 0x0:
+                               dev_printk(KERN_WARNING, &pdev->dev,
+                                          "Applying B0 workarounds to unknown rev 0\n");
+                               /* fall through */
+                       case 0x1:
+                               hp_flags |= MV_HP_ERRATA_50XXB0;
+                               break;
+                       case 0x2:
+                               hp_flags |= MV_HP_ERRATA_50XXB1;
+                               break;
+                       case 0x3:
+                               hp_flags |= MV_HP_ERRATA_50XXB2;
+                               break;
+                       default:
+                               dev_printk(KERN_WARNING, &pdev->dev,
+                                          "Applying B2 workarounds to future rev\n");
+                               hp_flags |= MV_HP_ERRATA_50XXB2;
+                               break;
+                       }
+               } else {
+                       switch (rev_id) {
+                       case 0x0:
+                               hp_flags |= MV_HP_ERRATA_50XXB0;
+                               break;
+                       case 0x1:
+                               dev_printk(KERN_WARNING, &pdev->dev,
+                                 "Applying B1 workarounds to unknown rev 1\n");
+                               /* fall through */
+                       case 0x2:
+                               hp_flags |= MV_HP_ERRATA_50XXB1;
+                               break;
+                       default:
+                               dev_printk(KERN_WARNING, &pdev->dev,
+                                  "Applying B2 workarounds to future rev\n");
+                               /* fall through */
+                       case 0x3:
+                               hp_flags |= MV_HP_ERRATA_50XXB2;
+                               break;
+                       }
+               }
+               break;
+
+       case chip_604x:
+       case chip_608x:
+               switch (rev_id) {
+               case 0x0:
+                       dev_printk(KERN_WARNING, &pdev->dev,
+                                 "Applying A1 workarounds to unknown rev 0\n");
+                       /* fall through */
+               case 0x1:
+                       hp_flags |= MV_HP_ERRATA_60X1A1;
+                       break;
+               default:
+                       dev_printk(KERN_WARNING, &pdev->dev,
+                                  "Applying B0 workarounds to future rev\n");
+                       /* fall through */
+               case 0x2:
+                       hp_flags |= MV_HP_ERRATA_60X1B0;
+                       break;
+               }
+               break;
+
+       default:
+               printk(KERN_ERR DRV_NAME ": BUG: invalid board index %u\n", board_idx);
+               return 1;
+       }
+
+       hpriv->hp_flags = hp_flags;
+
+       return 0;
+}
+
 /**
  *      mv_host_init - Perform some early initialization of the host.
+ *     @pdev: host PCI device
  *      @probe_ent: early data struct representing the host
  *
  *      If possible, do an early global reset of the host.  Then do
@@ -1390,11 +1627,28 @@ static void mv_port_init(struct ata_ioports *port,  void __iomem *port_mmio)
  *      LOCKING:
  *      Inherited from caller.
  */
-static int mv_host_init(struct ata_probe_ent *probe_ent)
+static int mv_host_init(struct pci_dev *pdev, struct ata_probe_ent *probe_ent,
+                       unsigned int board_idx)
 {
        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;
+
+       rc = mv_cfg_errata(pdev, hpriv, board_idx);
+       if (rc)
+               goto done;
+
+       n_hc = mv_get_hc_count(probe_ent->host_flags);
+       probe_ent->n_ports = MV_PORTS_PER_HC * n_hc;
+
+       if (IS_50XX(hpriv)) {
+               for (port = 0; port < probe_ent->n_ports; port++)
+                       mv_cfg_signal5(hpriv, port, mmio);
+       } else {
+               for (port = 0; port < probe_ent->n_ports; port++)
+                       mv_cfg_signal6(hpriv, port, mmio);
+       }
 
        if ((MV_FLAG_GLBL_SFT_RST & probe_ent->host_flags) &&
            mv_global_soft_reset(probe_ent->mmio_base)) {
@@ -1402,8 +1656,7 @@ static int mv_host_init(struct ata_probe_ent *probe_ent)
                goto done;
        }
 
-       n_hc = mv_get_hc_count(probe_ent->host_flags);
-       probe_ent->n_ports = MV_PORTS_PER_HC * n_hc;
+       mv_enable_leds(hpriv, mmio);
 
        for (port = 0; port < probe_ent->n_ports; port++) {
                port_mmio = mv_port_base(mmio, port);
@@ -1435,6 +1688,7 @@ static int mv_host_init(struct ata_probe_ent *probe_ent)
                readl(mmio + HC_MAIN_IRQ_MASK_OFS),
                readl(mmio + PCI_IRQ_CAUSE_OFS),
                readl(mmio + PCI_IRQ_MASK_OFS));
+
 done:
        return rc;
 }
@@ -1540,7 +1794,7 @@ static int mv_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
        probe_ent->private_data = hpriv;
 
        /* initialize adapter */
-       rc = mv_host_init(probe_ent);
+       rc = mv_host_init(pdev, probe_ent, board_idx);
        if (rc) {
                goto err_out_hpriv;
        }