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

Commit 0be0aa98 authored by Tejun Heo's avatar Tejun Heo Committed by Jeff Garzik
Browse files

[PATCH] libata: improve driver initialization and deinitialization



Implement ahci_[de]init_port() and use it during initialization and
de-initialization.  ahci_[de]init_port() are supersets of what used to
be done during driver [de-]initialization.  This patch makes the
following behavior changes.

* Per-port IRQ mask is cleared on driver load as done in other
  drivers.  The mask will be configured properly during probe.

* During init_one(), HOST_IRQ_STAT is cleared after masking port IRQs
  such that there is no race window.

* CMD_SPIN_UP is cleared during init_one() instead of being set.  It
  is set in port_start().  This is more consistent with overall
  structure of initialization.  Note that CMD_SPIN_UP simply controls
  PHY activation.

* Slumber and staggered spin-up are handled properly.

* All init/deinit operations are done in step-by-step manner as
  described in the spec instead of issued as single merged command.

Original implementation is from Zhao, Forrest <forrest.zhao@intel.com>

Signed-off-by: default avatarTejun Heo <htejun@gmail.com>
Signed-off-by: default avatarZhao, Forrest <forrest.zhao@intel.com>
Signed-off-by: default avatarJeff Garzik <jeff@garzik.org>
parent 9f592056
Loading
Loading
Loading
Loading
+151 −51
Original line number Diff line number Diff line
@@ -92,7 +92,9 @@ enum {
	HOST_AHCI_EN		= (1 << 31), /* AHCI enabled */

	/* HOST_CAP bits */
	HOST_CAP_SSC		= (1 << 14), /* Slumber capable */
	HOST_CAP_CLO		= (1 << 24), /* Command List Override support */
	HOST_CAP_SSS		= (1 << 27), /* Staggered Spin-up */
	HOST_CAP_NCQ		= (1 << 30), /* Native Command Queueing */
	HOST_CAP_64		= (1 << 31), /* PCI DAC (64-bit DMA) support */

@@ -155,6 +157,7 @@ enum {
	PORT_CMD_SPIN_UP	= (1 << 1), /* Spin up device */
	PORT_CMD_START		= (1 << 0), /* Enable port DMA engine */

	PORT_CMD_ICC_MASK	= (0xf << 28), /* i/f ICC state mask */
	PORT_CMD_ICC_ACTIVE	= (0x1 << 28), /* Put i/f in active state */
	PORT_CMD_ICC_PARTIAL	= (0x2 << 28), /* Put i/f in partial state */
	PORT_CMD_ICC_SLUMBER	= (0x6 << 28), /* Put i/f in slumber state */
@@ -440,6 +443,135 @@ static int ahci_stop_engine(void __iomem *port_mmio)
	return 0;
}

static void ahci_start_fis_rx(void __iomem *port_mmio, u32 cap,
			      dma_addr_t cmd_slot_dma, dma_addr_t rx_fis_dma)
{
	u32 tmp;

	/* set FIS registers */
	if (cap & HOST_CAP_64)
		writel((cmd_slot_dma >> 16) >> 16, port_mmio + PORT_LST_ADDR_HI);
	writel(cmd_slot_dma & 0xffffffff, port_mmio + PORT_LST_ADDR);

	if (cap & HOST_CAP_64)
		writel((rx_fis_dma >> 16) >> 16, port_mmio + PORT_FIS_ADDR_HI);
	writel(rx_fis_dma & 0xffffffff, port_mmio + PORT_FIS_ADDR);

	/* enable FIS reception */
	tmp = readl(port_mmio + PORT_CMD);
	tmp |= PORT_CMD_FIS_RX;
	writel(tmp, port_mmio + PORT_CMD);

	/* flush */
	readl(port_mmio + PORT_CMD);
}

static int ahci_stop_fis_rx(void __iomem *port_mmio)
{
	u32 tmp;

	/* disable FIS reception */
	tmp = readl(port_mmio + PORT_CMD);
	tmp &= ~PORT_CMD_FIS_RX;
	writel(tmp, port_mmio + PORT_CMD);

	/* wait for completion, spec says 500ms, give it 1000 */
	tmp = ata_wait_register(port_mmio + PORT_CMD, PORT_CMD_FIS_ON,
				PORT_CMD_FIS_ON, 10, 1000);
	if (tmp & PORT_CMD_FIS_ON)
		return -EBUSY;

	return 0;
}

static void ahci_power_up(void __iomem *port_mmio, u32 cap)
{
	u32 cmd;

	cmd = readl(port_mmio + PORT_CMD) & ~PORT_CMD_ICC_MASK;

	/* spin up device */
	if (cap & HOST_CAP_SSS) {
		cmd |= PORT_CMD_SPIN_UP;
		writel(cmd, port_mmio + PORT_CMD);
	}

	/* wake up link */
	writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD);
}

static void ahci_power_down(void __iomem *port_mmio, u32 cap)
{
	u32 cmd, scontrol;

	cmd = readl(port_mmio + PORT_CMD) & ~PORT_CMD_ICC_MASK;

	if (cap & HOST_CAP_SSC) {
		/* enable transitions to slumber mode */
		scontrol = readl(port_mmio + PORT_SCR_CTL);
		if ((scontrol & 0x0f00) > 0x100) {
			scontrol &= ~0xf00;
			writel(scontrol, port_mmio + PORT_SCR_CTL);
		}

		/* put device into slumber mode */
		writel(cmd | PORT_CMD_ICC_SLUMBER, port_mmio + PORT_CMD);

		/* wait for the transition to complete */
		ata_wait_register(port_mmio + PORT_CMD, PORT_CMD_ICC_SLUMBER,
				  PORT_CMD_ICC_SLUMBER, 1, 50);
	}

	/* put device into listen mode */
	if (cap & HOST_CAP_SSS) {
		/* first set PxSCTL.DET to 0 */
		scontrol = readl(port_mmio + PORT_SCR_CTL);
		scontrol &= ~0xf;
		writel(scontrol, port_mmio + PORT_SCR_CTL);

		/* then set PxCMD.SUD to 0 */
		cmd &= ~PORT_CMD_SPIN_UP;
		writel(cmd, port_mmio + PORT_CMD);
	}
}

static void ahci_init_port(void __iomem *port_mmio, u32 cap,
			   dma_addr_t cmd_slot_dma, dma_addr_t rx_fis_dma)
{
	/* power up */
	ahci_power_up(port_mmio, cap);

	/* enable FIS reception */
	ahci_start_fis_rx(port_mmio, cap, cmd_slot_dma, rx_fis_dma);

	/* enable DMA */
	ahci_start_engine(port_mmio);
}

static int ahci_deinit_port(void __iomem *port_mmio, u32 cap, const char **emsg)
{
	int rc;

	/* disable DMA */
	rc = ahci_stop_engine(port_mmio);
	if (rc) {
		*emsg = "failed to stop engine";
		return rc;
	}

	/* disable FIS reception */
	rc = ahci_stop_fis_rx(port_mmio);
	if (rc) {
		*emsg = "failed stop FIS RX";
		return rc;
	}

	/* put device into slumber mode */
	ahci_power_down(port_mmio, cap);

	return 0;
}

static unsigned int ahci_dev_classify(struct ata_port *ap)
{
	void __iomem *port_mmio = (void __iomem *) ap->ioaddr.cmd_addr;
@@ -1037,20 +1169,8 @@ static int ahci_port_start(struct ata_port *ap)

	ap->private_data = pp;

	if (hpriv->cap & HOST_CAP_64)
		writel((pp->cmd_slot_dma >> 16) >> 16, port_mmio + PORT_LST_ADDR_HI);
	writel(pp->cmd_slot_dma & 0xffffffff, port_mmio + PORT_LST_ADDR);
	readl(port_mmio + PORT_LST_ADDR); /* flush */

	if (hpriv->cap & HOST_CAP_64)
		writel((pp->rx_fis_dma >> 16) >> 16, port_mmio + PORT_FIS_ADDR_HI);
	writel(pp->rx_fis_dma & 0xffffffff, port_mmio + PORT_FIS_ADDR);
	readl(port_mmio + PORT_FIS_ADDR); /* flush */

	writel(PORT_CMD_ICC_ACTIVE | PORT_CMD_FIS_RX |
	       PORT_CMD_POWER_ON | PORT_CMD_SPIN_UP |
	       PORT_CMD_START, port_mmio + PORT_CMD);
	readl(port_mmio + PORT_CMD); /* flush */
	/* initialize port */
	ahci_init_port(port_mmio, hpriv->cap, pp->cmd_slot_dma, pp->rx_fis_dma);

	return 0;
}
@@ -1058,20 +1178,17 @@ static int ahci_port_start(struct ata_port *ap)
static void ahci_port_stop(struct ata_port *ap)
{
	struct device *dev = ap->host_set->dev;
	struct ahci_host_priv *hpriv = ap->host_set->private_data;
	struct ahci_port_priv *pp = ap->private_data;
	void __iomem *mmio = ap->host_set->mmio_base;
	void __iomem *port_mmio = ahci_port_base(mmio, ap->port_no);
	u32 tmp;

	tmp = readl(port_mmio + PORT_CMD);
	tmp &= ~(PORT_CMD_START | PORT_CMD_FIS_RX);
	writel(tmp, port_mmio + PORT_CMD);
	readl(port_mmio + PORT_CMD); /* flush */
	const char *emsg = NULL;
	int rc;

	/* spec says 500 msecs for each PORT_CMD_{START,FIS_RX} bit, so
	 * this is slightly incorrect.
	 */
	msleep(500);
	/* de-initialize port */
	rc = ahci_deinit_port(port_mmio, hpriv->cap, &emsg);
	if (rc)
		ata_port_printk(ap, KERN_WARNING, "%s (%d)\n", emsg, rc);

	ap->private_data = NULL;
	dma_free_coherent(dev, AHCI_PORT_PRIV_DMA_SZ,
@@ -1099,7 +1216,7 @@ static int ahci_host_init(struct ata_probe_ent *probe_ent)
	struct pci_dev *pdev = to_pci_dev(probe_ent->dev);
	void __iomem *mmio = probe_ent->mmio_base;
	u32 tmp, cap_save;
	unsigned int i, j, using_dac;
	unsigned int i, using_dac;
	int rc;
	void __iomem *port_mmio;

@@ -1175,6 +1292,8 @@ static int ahci_host_init(struct ata_probe_ent *probe_ent)
	}

	for (i = 0; i < probe_ent->n_ports; i++) {
		const char *emsg = NULL;

#if 0 /* BIOSen initialize this incorrectly */
		if (!(hpriv->port_map & (1 << i)))
			continue;
@@ -1187,43 +1306,24 @@ static int ahci_host_init(struct ata_probe_ent *probe_ent)
				(unsigned long) mmio, i);

		/* make sure port is not active */
		tmp = readl(port_mmio + PORT_CMD);
		VPRINTK("PORT_CMD 0x%x\n", tmp);
		if (tmp & (PORT_CMD_LIST_ON | PORT_CMD_FIS_ON |
			   PORT_CMD_FIS_RX | PORT_CMD_START)) {
			tmp &= ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON |
				 PORT_CMD_FIS_RX | PORT_CMD_START);
			writel(tmp, port_mmio + PORT_CMD);
			readl(port_mmio + PORT_CMD); /* flush */

			/* spec says 500 msecs for each bit, so
			 * this is slightly incorrect.
			 */
			msleep(500);
		}

		writel(PORT_CMD_SPIN_UP, port_mmio + PORT_CMD);

		j = 0;
		while (j < 100) {
			msleep(10);
			tmp = readl(port_mmio + PORT_SCR_STAT);
			if ((tmp & 0xf) == 0x3)
				break;
			j++;
		}
		rc = ahci_deinit_port(port_mmio, hpriv->cap, &emsg);
		if (rc)
			dev_printk(KERN_WARNING, &pdev->dev,
				   "%s (%d)\n", emsg, rc);

		/* clear SError */
		tmp = readl(port_mmio + PORT_SCR_ERR);
		VPRINTK("PORT_SCR_ERR 0x%x\n", tmp);
		writel(tmp, port_mmio + PORT_SCR_ERR);

		/* ack any pending irq events for this port */
		/* clear & turn off port IRQ */
		tmp = readl(port_mmio + PORT_IRQ_STAT);
		VPRINTK("PORT_IRQ_STAT 0x%x\n", tmp);
		if (tmp)
			writel(tmp, port_mmio + PORT_IRQ_STAT);

		writel(1 << i, mmio + HOST_IRQ_STAT);
		writel(0, port_mmio + PORT_IRQ_MASK);
	}

	tmp = readl(mmio + HOST_CTL);