struct xgene_ahci_context { struct ahci_host_priv *hpriv; struct device *dev;
u8 last_cmd[MAX_AHCI_CHN_PERCTR]; /* tracking the last command issued*/
u32 class[MAX_AHCI_CHN_PERCTR]; /* tracking the class of device */ void __iomem *csr_core; /* Core CSR address of IP */ void __iomem *csr_diag; /* Diag CSR address of IP */ void __iomem *csr_axi; /* AXI CSR address of IP */ void __iomem *csr_mux; /* MUX CSR address of IP */
};
staticint xgene_ahci_init_memram(struct xgene_ahci_context *ctx)
{
dev_dbg(ctx->dev, "Release memory from shutdown\n");
writel(0x0, ctx->csr_diag + CFG_MEM_RAM_SHUTDOWN);
readl(ctx->csr_diag + CFG_MEM_RAM_SHUTDOWN); /* Force a barrier */
msleep(1); /* reset may take up to 1ms */ if (readl(ctx->csr_diag + BLOCK_MEM_RDY) != 0xFFFFFFFF) {
dev_err(ctx->dev, "failed to release memory from shutdown\n"); return -ENODEV;
} return 0;
}
/** * xgene_ahci_poll_reg_val- Poll a register on a specific value. * @ap : ATA port of interest. * @reg : Register of interest. * @val : Value to be attained. * @interval : waiting interval for polling. * @timeout : timeout for achieving the value.
*/ staticint xgene_ahci_poll_reg_val(struct ata_port *ap, void __iomem *reg, unsignedint val, unsignedint interval, unsignedint timeout)
{ unsignedlong deadline; unsignedint tmp;
while (tmp != val && time_before(jiffies, deadline)) {
ata_msleep(ap, interval);
tmp = ioread32(reg);
}
return tmp;
}
/** * xgene_ahci_restart_engine - Restart the dma engine. * @ap : ATA port of interest * * Waits for completion of multiple commands and restarts * the DMA engine inside the controller.
*/ staticint xgene_ahci_restart_engine(struct ata_port *ap)
{ struct ahci_host_priv *hpriv = ap->host->private_data; struct ahci_port_priv *pp = ap->private_data; void __iomem *port_mmio = ahci_port_base(ap);
u32 fbs;
/* * In case of PMP multiple IDENTIFY DEVICE commands can be * issued inside PxCI. So need to poll PxCI for the * completion of outstanding IDENTIFY DEVICE commands before * we restart the DMA engine.
*/ if (xgene_ahci_poll_reg_val(ap, port_mmio +
PORT_CMD_ISSUE, 0x0, 1, 100)) return -EBUSY;
hpriv->stop_engine(ap);
ahci_start_fis_rx(ap);
/* * Enable the PxFBS.FBS_EN bit as it * gets cleared due to stopping the engine.
*/ if (pp->fbs_supported) {
fbs = readl(port_mmio + PORT_FBS);
writel(fbs | PORT_FBS_EN, port_mmio + PORT_FBS);
fbs = readl(port_mmio + PORT_FBS);
}
hpriv->start_engine(ap);
return 0;
}
/** * xgene_ahci_qc_issue - Issue commands to the device * @qc: Command to issue * * Due to Hardware errata for IDENTIFY DEVICE command, the controller cannot * clear the BSY bit after receiving the PIO setup FIS. This results in the dma * state machine goes into the CMFatalErrorUpdate state and locks up. By * restarting the dma engine, it removes the controller out of lock up state. * * Due to H/W errata, the controller is unable to save the PMP * field fetched from command header before sending the H2D FIS. * When the device returns the PMP port field in the D2H FIS, there is * a mismatch and results in command completion failure. The * workaround is to write the pmp value to PxFBS.DEV field before issuing * any command to PMP.
*/ staticunsignedint xgene_ahci_qc_issue(struct ata_queued_cmd *qc)
{ struct ata_port *ap = qc->ap; struct ahci_host_priv *hpriv = ap->host->private_data; struct xgene_ahci_context *ctx = hpriv->plat_data; int rc = 0;
u32 port_fbs; void __iomem *port_mmio = ahci_port_base(ap);
/* * Write the pmp value to PxFBS.DEV * for case of Port Mulitplier.
*/ if (ctx->class[ap->port_no] == ATA_DEV_PMP) {
port_fbs = readl(port_mmio + PORT_FBS);
port_fbs &= ~PORT_FBS_DEV_MASK;
port_fbs |= qc->dev->link->pmp << PORT_FBS_DEV_OFFSET;
writel(port_fbs, port_mmio + PORT_FBS);
}
/** * xgene_ahci_read_id - Read ID data from the specified device * @dev: device * @tf: proposed taskfile * @id: data buffer * * This custom read ID function is required due to the fact that the HW * does not support DEVSLP.
*/ staticunsignedint xgene_ahci_read_id(struct ata_device *dev, struct ata_taskfile *tf, __le16 *id)
{
u32 err_mask;
err_mask = ata_do_dev_read_id(dev, tf, id); if (err_mask) return err_mask;
/* * Mask reserved area. Word78 spec of Link Power Management * bit15-8: reserved * bit7: NCQ autosence * bit6: Software settings preservation supported * bit5: reserved * bit4: In-order sata delivery supported * bit3: DIPM requests supported * bit2: DMA Setup FIS Auto-Activate optimization supported * bit1: DMA Setup FIX non-Zero buffer offsets supported * bit0: Reserved * * Clear reserved bit 8 (DEVSLP bit) as we don't support DEVSLP
*/
id[ATA_ID_FEATURE_SUPP] &= cpu_to_le16(~(1 << 8));
dev_dbg(ctx->dev, "port configure mmio 0x%p channel %d\n",
mmio, channel);
val = readl(mmio + PORTCFG);
val = PORTADDR_SET(val, channel == 0 ? 2 : 3);
writel(val, mmio + PORTCFG);
readl(mmio + PORTCFG); /* Force a barrier */ /* Disable fix rate */
writel(0x0001fffe, mmio + PORTPHY1CFG);
readl(mmio + PORTPHY1CFG); /* Force a barrier */
writel(0x28183219, mmio + PORTPHY2CFG);
readl(mmio + PORTPHY2CFG); /* Force a barrier */
writel(0x13081008, mmio + PORTPHY3CFG);
readl(mmio + PORTPHY3CFG); /* Force a barrier */
writel(0x00480815, mmio + PORTPHY4CFG);
readl(mmio + PORTPHY4CFG); /* Force a barrier */ /* Set window negotiation */
val = readl(mmio + PORTPHY5CFG);
val = PORTPHY5CFG_RTCHG_SET(val, 0x300);
writel(val, mmio + PORTPHY5CFG);
readl(mmio + PORTPHY5CFG); /* Force a barrier */
val = readl(mmio + PORTAXICFG);
val = PORTAXICFG_EN_CONTEXT_SET(val, 0x1); /* Enable context mgmt */
val = PORTAXICFG_OUTTRANS_SET(val, 0xe); /* Set outstanding */
writel(val, mmio + PORTAXICFG);
readl(mmio + PORTAXICFG); /* Force a barrier */ /* Set the watermark threshold of the receive FIFO */
val = readl(mmio + PORTRANSCFG);
val = PORTRANSCFG_RXWM_SET(val, 0x30);
writel(val, mmio + PORTRANSCFG);
}
/** * xgene_ahci_do_hardreset - Issue the actual COMRESET * @link: link to reset * @deadline: deadline jiffies for the operation * @online: Return value to indicate if device online * * Due to the limitation of the hardware PHY, a difference set of setting is * required for each supported disk speed - Gen3 (6.0Gbps), Gen2 (3.0Gbps), * and Gen1 (1.5Gbps). Otherwise during long IO stress test, the PHY will * report disparity error and etc. In addition, during COMRESET, there can * be error reported in the register PORT_SCR_ERR. For SERR_DISPARITY and * SERR_10B_8B_ERR, the PHY receiver line must be reseted. Also during long * reboot cycle regression, sometimes the PHY reports link down even if the * device is present because of speed negotiation failure. so need to retry * the COMRESET to get the link up. The following algorithm is followed to * proper configure the hardware PHY during COMRESET: * * Alg Part 1: * 1. Start the PHY at Gen3 speed (default setting) * 2. Issue the COMRESET * 3. If no link, go to Alg Part 3 * 4. If link up, determine if the negotiated speed matches the PHY * configured speed * 5. If they matched, go to Alg Part 2 * 6. If they do not matched and first time, configure the PHY for the linked * up disk speed and repeat step 2 * 7. Go to Alg Part 2 * * Alg Part 2: * 1. On link up, if there are any SERR_DISPARITY and SERR_10B_8B_ERR error * reported in the register PORT_SCR_ERR, then reset the PHY receiver line * 2. Go to Alg Part 4 * * Alg Part 3: * 1. Check the PORT_SCR_STAT to see whether device presence detected but PHY * communication establishment failed and maximum link down attempts are * less than Max attempts 3 then goto Alg Part 1. * 2. Go to Alg Part 4. * * Alg Part 4: * 1. Clear any pending from register PORT_SCR_ERR. * * NOTE: For the initial version, we will NOT support Gen1/Gen2. In addition * and until the underlying PHY supports an method to reset the receiver * line, on detection of SERR_DISPARITY or SERR_10B_8B_ERR errors, * an warning message will be printed.
*/ staticint xgene_ahci_do_hardreset(struct ata_link *link, unsignedlong deadline, bool *online)
{ constunsignedint *timing = sata_ehc_deb_timing(&link->eh_context); struct ata_port *ap = link->ap; struct ahci_host_priv *hpriv = ap->host->private_data; struct xgene_ahci_context *ctx = hpriv->plat_data; struct ahci_port_priv *pp = ap->private_data;
u8 *d2h_fis = pp->rx_fis + RX_FIS_D2H_REG; void __iomem *port_mmio = ahci_port_base(ap); struct ata_taskfile tf; int link_down_retry = 0; int rc;
u32 val, sstatus;
do { /* clear D2H reception area to properly wait for D2H FIS */
ata_tf_init(link->device, &tf);
tf.status = ATA_BUSY;
ata_tf_to_fis(&tf, 0, 0, d2h_fis);
rc = sata_link_hardreset(link, timing, deadline, online,
ahci_check_ready); if (*online) {
val = readl(port_mmio + PORT_SCR_ERR); if (val & (SERR_DISPARITY | SERR_10B_8B_ERR))
dev_warn(ctx->dev, "link has error\n"); break;
}
/** * xgene_ahci_pmp_softreset - Issue the softreset to the drives connected * to Port Multiplier. * @link: link to reset * @class: Return value to indicate class of device * @deadline: deadline jiffies for the operation * * Due to H/W errata, the controller is unable to save the PMP * field fetched from command header before sending the H2D FIS. * When the device returns the PMP port field in the D2H FIS, there is * a mismatch and results in command completion failure. The workaround * is to write the pmp value to PxFBS.DEV field before issuing any command * to PMP.
*/ staticint xgene_ahci_pmp_softreset(struct ata_link *link, unsignedint *class, unsignedlong deadline)
{ int pmp = sata_srst_pmp(link); struct ata_port *ap = link->ap; void __iomem *port_mmio = ahci_port_base(ap);
u32 port_fbs;
/* * Set PxFBS.DEV field with pmp * value.
*/
port_fbs = readl(port_mmio + PORT_FBS);
port_fbs &= ~PORT_FBS_DEV_MASK;
port_fbs |= pmp << PORT_FBS_DEV_OFFSET;
writel(port_fbs, port_mmio + PORT_FBS);
/** * xgene_ahci_softreset - Issue the softreset to the drive. * @link: link to reset * @class: Return value to indicate class of device * @deadline: deadline jiffies for the operation * * Due to H/W errata, the controller is unable to save the PMP * field fetched from command header before sending the H2D FIS. * When the device returns the PMP port field in the D2H FIS, there is * a mismatch and results in command completion failure. The workaround * is to write the pmp value to PxFBS.DEV field before issuing any command * to PMP. Here is the algorithm to detect PMP : * * 1. Save the PxFBS value * 2. Program PxFBS.DEV with pmp value send by framework. Framework sends * 0xF for both PMP/NON-PMP initially * 3. Issue softreset * 4. If signature class is PMP goto 6 * 5. restore the original PxFBS and goto 3 * 6. return
*/ staticint xgene_ahci_softreset(struct ata_link *link, unsignedint *class, unsignedlong deadline)
{ int pmp = sata_srst_pmp(link); struct ata_port *ap = link->ap; struct ahci_host_priv *hpriv = ap->host->private_data; struct xgene_ahci_context *ctx = hpriv->plat_data; void __iomem *port_mmio = ahci_port_base(ap);
u32 port_fbs;
u32 port_fbs_save;
u32 retry = 1; int rc;
port_fbs_save = readl(port_mmio + PORT_FBS);
/* * Set PxFBS.DEV field with pmp * value.
*/
port_fbs = readl(port_mmio + PORT_FBS);
port_fbs &= ~PORT_FBS_DEV_MASK;
port_fbs |= pmp << PORT_FBS_DEV_OFFSET;
writel(port_fbs, port_mmio + PORT_FBS);
ctx->class[ap->port_no] = *class; if (*class != ATA_DEV_PMP) { /* * Retry for normal drives without * setting PxFBS.DEV field with pmp value.
*/ if (retry--) {
writel(port_fbs_save, port_mmio + PORT_FBS); goto softreset_retry;
}
}
return rc;
}
/** * xgene_ahci_handle_broken_edge_irq - Handle the broken irq. * @host: Host that received the irq * @irq_masked: HOST_IRQ_STAT value * * For hardware with broken edge trigger latch * the HOST_IRQ_STAT register misses the edge interrupt * when clearing of HOST_IRQ_STAT register and hardware * reporting the PORT_IRQ_STAT register at the * same clock cycle. * As such, the algorithm below outlines the workaround. * * 1. Read HOST_IRQ_STAT register and save the state. * 2. Clear the HOST_IRQ_STAT register. * 3. Read back the HOST_IRQ_STAT register. * 4. If HOST_IRQ_STAT register equals to zero, then * traverse the rest of port's PORT_IRQ_STAT register * to check if an interrupt is triggered at that point else * go to step 6. * 5. If PORT_IRQ_STAT register of rest ports is not equal to zero * then update the state of HOST_IRQ_STAT saved in step 1. * 6. Handle port interrupts. * 7. Exit
*/ staticint xgene_ahci_handle_broken_edge_irq(struct ata_host *host,
u32 irq_masked)
{ struct ahci_host_priv *hpriv = host->private_data; void __iomem *port_mmio; int i;
if (!readl(hpriv->mmio + HOST_IRQ_STAT)) { for (i = 0; i < host->n_ports; i++) { if (irq_masked & (1 << i)) continue;
/* sigh. 0xffffffff is a valid return from h/w */
irq_stat = readl(mmio + HOST_IRQ_STAT); if (!irq_stat) return IRQ_NONE;
irq_masked = irq_stat & hpriv->port_map;
spin_lock(&host->lock);
/* * HOST_IRQ_STAT behaves as edge triggered latch meaning that * it should be cleared before all the port events are cleared.
*/
writel(irq_stat, mmio + HOST_IRQ_STAT);
/* Retrieve the IP core resource */
ctx->csr_core = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(ctx->csr_core)) return PTR_ERR(ctx->csr_core);
/* Retrieve the IP diagnostic resource */
ctx->csr_diag = devm_platform_ioremap_resource(pdev, 2); if (IS_ERR(ctx->csr_diag)) return PTR_ERR(ctx->csr_diag);
/* Retrieve the IP AXI resource */
ctx->csr_axi = devm_platform_ioremap_resource(pdev, 3); if (IS_ERR(ctx->csr_axi)) return PTR_ERR(ctx->csr_axi);
/* Retrieve the optional IP mux resource */
res = platform_get_resource(pdev, IORESOURCE_MEM, 4); if (res) { void __iomem *csr = devm_ioremap_resource(dev, res); if (IS_ERR(csr)) return PTR_ERR(csr);
ctx->csr_mux = csr;
}
if (dev->of_node) {
version = (enum xgene_ahci_version)of_device_get_match_data(dev);
} #ifdef CONFIG_ACPI else { conststruct acpi_device_id *acpi_id; struct acpi_device_info *info;
acpi_status status;
acpi_id = acpi_match_device(xgene_ahci_acpi_match, &pdev->dev); if (!acpi_id) {
dev_warn(&pdev->dev, "No node entry in ACPI table. Assume version1\n");
version = XGENE_AHCI_V1;
} elseif (acpi_id->driver_data) {
version = (enum xgene_ahci_version) acpi_id->driver_data;
status = acpi_get_object_info(ACPI_HANDLE(&pdev->dev), &info); if (ACPI_FAILURE(status)) {
dev_warn(&pdev->dev, "%s: Error reading device info. Assume version1\n",
__func__);
version = XGENE_AHCI_V1;
} else { if (info->valid & ACPI_VALID_CID)
version = XGENE_AHCI_V2;
kfree(info);
}
}
} #endif
/* Select ATA */ if ((rc = xgene_ahci_mux_select(ctx))) {
dev_err(dev, "SATA mux selection failed error %d\n", rc); return -ENODEV;
}
if (xgene_ahci_is_memram_inited(ctx)) {
dev_info(dev, "skip clock and PHY initialization\n"); goto skip_clk_phy;
}
/* Due to errata, HW requires full toggle transition */
rc = ahci_platform_enable_clks(hpriv); if (rc) goto disable_resources;
ahci_platform_disable_clks(hpriv);
rc = ahci_platform_enable_resources(hpriv); if (rc) goto disable_resources;
/* Configure the host controller */
xgene_ahci_hw_init(hpriv);
skip_clk_phy:
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.