[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: Tejun Heo <htejun@gmail.com>
Signed-off-by: Zhao, Forrest <forrest.zhao@intel.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
This commit is contained in:
Tejun Heo 2006-07-26 15:59:26 +09:00 committed by Jeff Garzik
parent 9f5920567b
commit 0be0aa9898

View file

@ -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;
const char *emsg = NULL;
int rc;
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 */
/* 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);