*/
/*
- sata_mv TODO list:
-
- 1) Needs a full errata audit for all chipsets. I implemented most
- of the errata workarounds found in the Marvell vendor driver, but
- I distinctly remember a couple workarounds (one related to PCI-X)
- are still needed.
-
- 2) Improve/fix IRQ and error handling sequences.
-
- 3) ATAPI support (Marvell claims the 60xx/70xx chips can do it).
-
- 4) Think about TCQ support here, and for libata in general
- with controllers that suppport it via host-queuing hardware
- (a software-only implementation could be a nightmare).
-
- 5) Investigate problems with PCI Message Signalled Interrupts (MSI).
-
- 6) Cache frequently-accessed registers in mv_port_priv to reduce overhead.
-
- 7) Fix/reenable hot plug/unplug (should happen as a side-effect of (2) above).
-
- 8) Develop a low-power-consumption strategy, and implement it.
-
- 9) [Experiment, low priority] See if ATAPI can be supported using
- "unknown FIS" or "vendor-specific FIS" support, or something creative
- like that.
-
- 10) [Experiment, low priority] Investigate interrupt coalescing.
- Quite often, especially with PCI Message Signalled Interrupts (MSI),
- the overhead reduced by interrupt mitigation is quite often not
- worth the latency cost.
-
- 11) [Experiment, Marvell value added] Is it possible to use target
- mode to cross-connect two Linux boxes with Marvell cards? If so,
- creating LibATA target mode support would be very interesting.
-
- Target mode, for those without docs, is the ability to directly
- connect two SATA controllers.
-
-*/
+ * sata_mv TODO list:
+ *
+ * --> Errata workaround for NCQ device errors.
+ *
+ * --> More errata workarounds for PCI-X.
+ *
+ * --> Complete a full errata audit for all chipsets to identify others.
+ *
+ * --> ATAPI support (Marvell claims the 60xx/70xx chips can do it).
+ *
+ * --> Investigate problems with PCI Message Signalled Interrupts (MSI).
+ *
+ * --> Cache frequently-accessed registers in mv_port_priv to reduce overhead.
+ *
+ * --> Develop a low-power-consumption strategy, and implement it.
+ *
+ * --> [Experiment, low priority] Investigate interrupt coalescing.
+ * Quite often, especially with PCI Message Signalled Interrupts (MSI),
+ * the overhead reduced by interrupt mitigation is quite often not
+ * worth the latency cost.
+ *
+ * --> [Experiment, Marvell value added] Is it possible to use target
+ * mode to cross-connect two Linux boxes with Marvell cards? If so,
+ * creating LibATA target mode support would be very interesting.
+ *
+ * Target mode, for those without docs, is the ability to directly
+ * connect two SATA ports.
+ */
#include <linux/kernel.h>
#include <linux/module.h>
EDMA_ERR_IRQ_TRANSIENT = EDMA_ERR_LNK_CTRL_RX_0 |
EDMA_ERR_LNK_CTRL_RX_1 |
EDMA_ERR_LNK_CTRL_RX_3 |
- EDMA_ERR_LNK_CTRL_TX |
- /* temporary, until we fix hotplug: */
- (EDMA_ERR_DEV_DCON | EDMA_ERR_DEV_CON),
+ EDMA_ERR_LNK_CTRL_TX,
EDMA_EH_FREEZE = EDMA_ERR_D_PAR |
EDMA_ERR_PRD_PAR |
/*
* initialize request queue
*/
- index = (pp->req_idx & MV_MAX_Q_DEPTH_MASK) << EDMA_REQ_Q_PTR_SHIFT;
+ pp->req_idx &= MV_MAX_Q_DEPTH_MASK; /* paranoia */
+ index = pp->req_idx << EDMA_REQ_Q_PTR_SHIFT;
WARN_ON(pp->crqb_dma & 0x3ff);
writel((pp->crqb_dma >> 16) >> 16, port_mmio + EDMA_REQ_Q_BASE_HI_OFS);
/*
* initialize response queue
*/
- index = (pp->resp_idx & MV_MAX_Q_DEPTH_MASK) << EDMA_RSP_Q_PTR_SHIFT;
+ pp->resp_idx &= MV_MAX_Q_DEPTH_MASK; /* paranoia */
+ index = pp->resp_idx << EDMA_RSP_Q_PTR_SHIFT;
WARN_ON(pp->crpb_dma & 0xff);
writel((pp->crpb_dma >> 16) >> 16, port_mmio + EDMA_RSP_Q_BASE_HI_OFS);
flags |= (qc->dev->link->pmp & 0xf) << CRQB_PMP_SHIFT;
/* get current queue index from software */
- in_index = pp->req_idx & MV_MAX_Q_DEPTH_MASK;
+ in_index = pp->req_idx;
pp->crqb[in_index].sg_addr =
cpu_to_le32(pp->sg_tbl_dma[qc->tag] & 0xffffffff);
flags |= (qc->dev->link->pmp & 0xf) << CRQB_PMP_SHIFT;
/* get current queue index from software */
- in_index = pp->req_idx & MV_MAX_Q_DEPTH_MASK;
+ in_index = pp->req_idx;
crqb = (struct mv_crqb_iie *) &pp->crqb[in_index];
crqb->addr = cpu_to_le32(pp->sg_tbl_dma[qc->tag] & 0xffffffff);
mv_start_dma(ap, port_mmio, pp, qc->tf.protocol);
- pp->req_idx++;
-
- in_index = (pp->req_idx & MV_MAX_Q_DEPTH_MASK) << EDMA_REQ_Q_PTR_SHIFT;
+ pp->req_idx = (pp->req_idx + 1) & MV_MAX_Q_DEPTH_MASK;
+ in_index = pp->req_idx << EDMA_REQ_Q_PTR_SHIFT;
/* and write the request in pointer to kick the EDMA to life */
writelfl((pp->crqb_dma & EDMA_REQ_Q_BASE_LO_MASK) | in_index,
return 0;
}
+static struct ata_queued_cmd *mv_get_active_qc(struct ata_port *ap)
+{
+ struct mv_port_priv *pp = ap->private_data;
+ struct ata_queued_cmd *qc;
+
+ if (pp->pp_flags & MV_PP_FLAG_NCQ_EN)
+ return NULL;
+ qc = ata_qc_from_tag(ap, ap->link.active_tag);
+ if (qc && (qc->tf.flags & ATA_TFLAG_POLLING))
+ qc = NULL;
+ return qc;
+}
+
+static void mv_unexpected_intr(struct ata_port *ap)
+{
+ struct mv_port_priv *pp = ap->private_data;
+ struct ata_eh_info *ehi = &ap->link.eh_info;
+ char *when = "";
+
+ /*
+ * We got a device interrupt from something that
+ * was supposed to be using EDMA or polling.
+ */
+ ata_ehi_clear_desc(ehi);
+ if (pp->pp_flags & MV_PP_FLAG_EDMA_EN) {
+ when = " while EDMA enabled";
+ } else {
+ struct ata_queued_cmd *qc = ata_qc_from_tag(ap, ap->link.active_tag);
+ if (qc && (qc->tf.flags & ATA_TFLAG_POLLING))
+ when = " while polling";
+ }
+ ata_ehi_push_desc(ehi, "unexpected device interrupt%s", when);
+ ehi->err_mask |= AC_ERR_OTHER;
+ ehi->action |= ATA_EH_RESET;
+ ata_port_freeze(ap);
+}
+
/**
* mv_err_intr - Handle error interrupts on the port
* @ap: ATA channel to manipulate
- * @reset_allowed: bool: 0 == don't trigger from reset here
+ * @qc: affected command (non-NCQ), or NULL
*
- * In most cases, just clear the interrupt and move on. However,
- * some cases require an eDMA reset, which also performs a COMRESET.
- * The SERR case requires a clear of pending errors in the SATA
- * SERROR register. Finally, if the port disabled DMA,
- * update our cached copy to match.
+ * Most cases require a full reset of the chip's state machine,
+ * which also performs a COMRESET.
+ * Also, if the port disabled DMA, update our cached copy to match.
*
* LOCKING:
* Inherited from caller.
u32 edma_err_cause, eh_freeze_mask, serr = 0;
struct mv_port_priv *pp = ap->private_data;
struct mv_host_priv *hpriv = ap->host->private_data;
- unsigned int edma_enabled = (pp->pp_flags & MV_PP_FLAG_EDMA_EN);
unsigned int action = 0, err_mask = 0;
struct ata_eh_info *ehi = &ap->link.eh_info;
ata_ehi_clear_desc(ehi);
- if (!edma_enabled) {
- /* just a guess: do we need to do this? should we
- * expand this, and do it in all cases?
- */
- sata_scr_read(&ap->link, SCR_ERROR, &serr);
- sata_scr_write_flush(&ap->link, SCR_ERROR, serr);
- }
-
+ /*
+ * Read and clear the err_cause bits. This won't actually
+ * clear for some errors (eg. SError), but we will be doing
+ * a hard reset in those cases regardless, which *will* clear it.
+ */
edma_err_cause = readl(port_mmio + EDMA_ERR_IRQ_CAUSE_OFS);
+ writelfl(~edma_err_cause, port_mmio + EDMA_ERR_IRQ_CAUSE_OFS);
ata_ehi_push_desc(ehi, "edma_err_cause=%08x", edma_err_cause);
ata_ehi_push_desc(ehi, "EDMA self-disable");
}
if (edma_err_cause & EDMA_ERR_SERR) {
- sata_scr_read(&ap->link, SCR_ERROR, &serr);
- sata_scr_write_flush(&ap->link, SCR_ERROR, serr);
- err_mask = AC_ERR_ATA_BUS;
+ /*
+ * Ensure that we read our own SCR, not a pmp link SCR:
+ */
+ ap->ops->scr_read(ap, SCR_ERROR, &serr);
+ /*
+ * Don't clear SError here; leave it for libata-eh:
+ */
+ ata_ehi_push_desc(ehi, "SError=%08x", serr);
+ err_mask |= AC_ERR_ATA_BUS;
action |= ATA_EH_RESET;
}
}
- /* Clear EDMA now that SERR cleanup done */
- writelfl(~edma_err_cause, port_mmio + EDMA_ERR_IRQ_CAUSE_OFS);
-
if (!err_mask) {
err_mask = AC_ERR_OTHER;
action |= ATA_EH_RESET;
ata_port_abort(ap);
}
-static void mv_intr_pio(struct ata_port *ap)
+static void mv_process_crpb_response(struct ata_port *ap,
+ struct mv_crpb *response, unsigned int tag, int ncq_enabled)
{
- struct ata_queued_cmd *qc;
- u8 ata_status;
-
- /* ignore spurious intr if drive still BUSY */
- ata_status = readb(ap->ioaddr.status_addr);
- if (unlikely(ata_status & ATA_BUSY))
- return;
+ struct ata_queued_cmd *qc = ata_qc_from_tag(ap, tag);
- /* get active ATA command */
- qc = ata_qc_from_tag(ap, ap->link.active_tag);
- if (unlikely(!qc)) /* no active tag */
- return;
- if (qc->tf.flags & ATA_TFLAG_POLLING) /* polling; we don't own qc */
- return;
-
- /* and finally, complete the ATA command */
- qc->err_mask |= ac_err_mask(ata_status);
- ata_qc_complete(qc);
+ if (qc) {
+ u8 ata_status;
+ u16 edma_status = le16_to_cpu(response->flags);
+ /*
+ * edma_status from a response queue entry:
+ * LSB is from EDMA_ERR_IRQ_CAUSE_OFS (non-NCQ only).
+ * MSB is saved ATA status from command completion.
+ */
+ if (!ncq_enabled) {
+ u8 err_cause = edma_status & 0xff & ~EDMA_ERR_DEV;
+ if (err_cause) {
+ /*
+ * Error will be seen/handled by mv_err_intr().
+ * So do nothing at all here.
+ */
+ return;
+ }
+ }
+ ata_status = edma_status >> CRPB_FLAG_STATUS_SHIFT;
+ qc->err_mask |= ac_err_mask(ata_status);
+ ata_qc_complete(qc);
+ } else {
+ ata_port_printk(ap, KERN_ERR, "%s: no qc for tag=%d\n",
+ __func__, tag);
+ }
}
-static void mv_intr_edma(struct ata_port *ap)
+static void mv_process_crpb_entries(struct ata_port *ap, struct mv_port_priv *pp)
{
void __iomem *port_mmio = mv_ap_base(ap);
struct mv_host_priv *hpriv = ap->host->private_data;
- struct mv_port_priv *pp = ap->private_data;
- struct ata_queued_cmd *qc;
- u32 out_index, in_index;
+ u32 in_index;
bool work_done = false;
+ int ncq_enabled = (pp->pp_flags & MV_PP_FLAG_NCQ_EN);
- /* get h/w response queue pointer */
+ /* Get the hardware queue position index */
in_index = (readl(port_mmio + EDMA_RSP_Q_IN_PTR_OFS)
>> EDMA_RSP_Q_PTR_SHIFT) & MV_MAX_Q_DEPTH_MASK;
- while (1) {
- u16 status;
+ /* Process new responses from since the last time we looked */
+ while (in_index != pp->resp_idx) {
unsigned int tag;
+ struct mv_crpb *response = &pp->crpb[pp->resp_idx];
- /* get s/w response queue last-read pointer, and compare */
- out_index = pp->resp_idx & MV_MAX_Q_DEPTH_MASK;
- if (in_index == out_index)
- break;
+ pp->resp_idx = (pp->resp_idx + 1) & MV_MAX_Q_DEPTH_MASK;
- /* 50xx: get active ATA command */
- if (IS_GEN_I(hpriv))
+ if (IS_GEN_I(hpriv)) {
+ /* 50xx: no NCQ, only one command active at a time */
tag = ap->link.active_tag;
-
- /* Gen II/IIE: get active ATA command via tag, to enable
- * support for queueing. this works transparently for
- * queued and non-queued modes.
- */
- else
- tag = le16_to_cpu(pp->crpb[out_index].id) & 0x1f;
-
- qc = ata_qc_from_tag(ap, tag);
-
- /* For non-NCQ mode, the lower 8 bits of status
- * are from EDMA_ERR_IRQ_CAUSE_OFS,
- * which should be zero if all went well.
- */
- status = le16_to_cpu(pp->crpb[out_index].flags);
- if ((status & 0xff) && !(pp->pp_flags & MV_PP_FLAG_NCQ_EN)) {
- mv_err_intr(ap, qc);
- return;
- }
-
- /* and finally, complete the ATA command */
- if (qc) {
- qc->err_mask |=
- ac_err_mask(status >> CRPB_FLAG_STATUS_SHIFT);
- ata_qc_complete(qc);
+ } else {
+ /* Gen II/IIE: get command tag from CRPB entry */
+ tag = le16_to_cpu(response->id) & 0x1f;
}
-
- /* advance software response queue pointer, to
- * indicate (after the loop completes) to hardware
- * that we have consumed a response queue entry.
- */
+ mv_process_crpb_response(ap, response, tag, ncq_enabled);
work_done = true;
- pp->resp_idx++;
}
/* Update the software queue position index in hardware */
if (work_done)
writelfl((pp->crpb_dma & EDMA_RSP_Q_BASE_LO_MASK) |
- (out_index << EDMA_RSP_Q_PTR_SHIFT),
+ (pp->resp_idx << EDMA_RSP_Q_PTR_SHIFT),
port_mmio + EDMA_RSP_Q_OUT_PTR_OFS);
}
/**
* mv_host_intr - Handle all interrupts on the given host controller
* @host: host specific structure
- * @relevant: port error bits relevant to this host controller
- * @hc: which host controller we're to look at
- *
- * Read then write clear the HC interrupt status then walk each
- * port connected to the HC and see if it needs servicing. Port
- * success ints are reported in the HC interrupt status reg, the
- * port error ints are reported in the higher level main
- * interrupt status register and thus are passed in via the
- * 'relevant' argument.
+ * @main_cause: Main interrupt cause register for the chip.
*
* LOCKING:
* Inherited from caller.
*/
-static void mv_host_intr(struct ata_host *host, u32 relevant, unsigned int hc)
+static int mv_host_intr(struct ata_host *host, u32 main_cause)
{
struct mv_host_priv *hpriv = host->private_data;
- void __iomem *mmio = hpriv->base;
- void __iomem *hc_mmio = mv_hc_base(mmio, hc);
- u32 hc_irq_cause;
- int port, port0, last_port;
-
- if (hc == 0)
- port0 = 0;
- else
- port0 = MV_PORTS_PER_HC;
-
- if (HAS_PCI(host))
- last_port = port0 + MV_PORTS_PER_HC;
- else
- last_port = port0 + hpriv->n_ports;
- /* we'll need the HC success int register in most cases */
- hc_irq_cause = readl(hc_mmio + HC_IRQ_CAUSE_OFS);
- if (!hc_irq_cause)
- return;
-
- writelfl(~hc_irq_cause, hc_mmio + HC_IRQ_CAUSE_OFS);
-
- VPRINTK("ENTER, hc%u relevant=0x%08x HC IRQ cause=0x%08x\n",
- hc, relevant, hc_irq_cause);
+ void __iomem *mmio = hpriv->base, *hc_mmio = NULL;
+ u32 hc_irq_cause = 0;
+ unsigned int handled = 0, port;
- for (port = port0; port < last_port; port++) {
+ for (port = 0; port < hpriv->n_ports; port++) {
struct ata_port *ap = host->ports[port];
struct mv_port_priv *pp;
- int have_err_bits, hardport, shift;
-
- if ((!ap) || (ap->flags & ATA_FLAG_DISABLED))
+ unsigned int shift, hardport, port_cause;
+ /*
+ * When we move to the second hc, flag our cached
+ * copies of hc_mmio (and hc_irq_cause) as invalid again.
+ */
+ if (port == MV_PORTS_PER_HC)
+ hc_mmio = NULL;
+ /*
+ * Do nothing if port is not interrupting or is disabled:
+ */
+ MV_PORT_TO_SHIFT_AND_HARDPORT(port, shift, hardport);
+ port_cause = (main_cause >> shift) & (DONE_IRQ | ERR_IRQ);
+ if (!port_cause || !ap || (ap->flags & ATA_FLAG_DISABLED))
continue;
-
+ /*
+ * Each hc within the host has its own hc_irq_cause register.
+ * We defer reading it until we know we need it, right now:
+ *
+ * FIXME later: we don't really need to read this register
+ * (some logic changes required below if we go that way),
+ * because it doesn't tell us anything new. But we do need
+ * to write to it, outside the top of this loop,
+ * to reset the interrupt triggers for next time.
+ */
+ if (!hc_mmio) {
+ hc_mmio = mv_hc_base_from_port(mmio, port);
+ hc_irq_cause = readl(hc_mmio + HC_IRQ_CAUSE_OFS);
+ writelfl(~hc_irq_cause, hc_mmio + HC_IRQ_CAUSE_OFS);
+ handled = 1;
+ }
+ /*
+ * Process completed CRPB response(s) before other events.
+ */
pp = ap->private_data;
-
- shift = port << 1; /* (port * 2) */
- if (port >= MV_PORTS_PER_HC)
- shift++; /* skip bit 8 in the HC Main IRQ reg */
-
- have_err_bits = ((ERR_IRQ << shift) & relevant);
-
- if (unlikely(have_err_bits)) {
- struct ata_queued_cmd *qc;
-
- qc = ata_qc_from_tag(ap, ap->link.active_tag);
- if (qc && (qc->tf.flags & ATA_TFLAG_POLLING))
- continue;
-
- mv_err_intr(ap, qc);
- continue;
+ if (hc_irq_cause & (DMA_IRQ << hardport)) {
+ if (pp->pp_flags & MV_PP_FLAG_EDMA_EN)
+ mv_process_crpb_entries(ap, pp);
}
-
- hardport = mv_hardport_from_port(port); /* range 0..3 */
-
- if (pp->pp_flags & MV_PP_FLAG_EDMA_EN) {
- if ((DMA_IRQ << hardport) & hc_irq_cause)
- mv_intr_edma(ap);
- } else {
- if ((DEV_IRQ << hardport) & hc_irq_cause)
- mv_intr_pio(ap);
+ /*
+ * Handle chip-reported errors, or continue on to handle PIO.
+ */
+ if (unlikely(port_cause & ERR_IRQ)) {
+ mv_err_intr(ap, mv_get_active_qc(ap));
+ } else if (hc_irq_cause & (DEV_IRQ << hardport)) {
+ if (!(pp->pp_flags & MV_PP_FLAG_EDMA_EN)) {
+ struct ata_queued_cmd *qc = mv_get_active_qc(ap);
+ if (qc) {
+ ata_sff_host_intr(ap, qc);
+ continue;
+ }
+ }
+ mv_unexpected_intr(ap);
}
}
- VPRINTK("EXIT\n");
+ return handled;
}
-static void mv_pci_error(struct ata_host *host, void __iomem *mmio)
+static int mv_pci_error(struct ata_host *host, void __iomem *mmio)
{
struct mv_host_priv *hpriv = host->private_data;
struct ata_port *ap;
ata_port_freeze(ap);
}
}
+ return 1; /* handled */
}
/**
{
struct ata_host *host = dev_instance;
struct mv_host_priv *hpriv = host->private_data;
- unsigned int hc, handled = 0, n_hcs;
- void __iomem *mmio = hpriv->base;
+ unsigned int handled = 0;
u32 main_cause, main_mask;
spin_lock(&host->lock);
* Deal with cases where we either have nothing pending, or have read
* a bogus register value which can indicate HW removal or PCI fault.
*/
- if (!(main_cause & main_mask) || (main_cause == 0xffffffffU))
- goto out_unlock;
-
- n_hcs = mv_get_hc_count(host->ports[0]->flags);
-
- if (unlikely((main_cause & PCI_ERR) && HAS_PCI(host))) {
- mv_pci_error(host, mmio);
- handled = 1;
- goto out_unlock; /* skip all other HC irq handling */
- }
-
- for (hc = 0; hc < n_hcs; hc++) {
- u32 relevant = main_cause & (HC0_IRQ_PEND << (hc * HC_SHIFT));
- if (relevant) {
- mv_host_intr(host, relevant, hc);
- handled = 1;
- }
+ if ((main_cause & main_mask) && (main_cause != 0xffffffffU)) {
+ if (unlikely((main_cause & PCI_ERR) && HAS_PCI(host)))
+ handled = mv_pci_error(host, hpriv->base);
+ else
+ handled = mv_host_intr(host, main_cause);
}
-
-out_unlock:
spin_unlock(&host->lock);
return IRQ_RETVAL(handled);
}
printk(KERN_ERR DRV_NAME ": can't clear global reset\n");
rc = 1;
}
- /*
- * Temporary: wait 3 seconds before port-probing can happen,
- * so that we don't miss finding sleepy SilXXXX port-multipliers.
- * This can go away once hotplug is fully/correctly implemented.
- */
- if (rc == 0)
- msleep(3000);
done:
return rc;
}