Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bca1c4eb authored by Jeff Garzik's avatar Jeff Garzik
Browse files

[libata sata_mv] implement a bunch of errata workarounds

Based largely on the GPL'd Marvell vendor driver.
parent 9a68c1b9
Loading
Loading
Loading
Loading
+261 −7
Original line number Diff line number Diff line
@@ -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;
	}