summaryrefslogtreecommitdiffstats
path: root/drivers/ata/sata_sil24.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ata/sata_sil24.c')
-rw-r--r--drivers/ata/sata_sil24.c227
1 files changed, 206 insertions, 21 deletions
diff --git a/drivers/ata/sata_sil24.c b/drivers/ata/sata_sil24.c
index b4f81eb8bbbe..03bfbb65c533 100644
--- a/drivers/ata/sata_sil24.c
+++ b/drivers/ata/sata_sil24.c
@@ -30,7 +30,7 @@
#include <linux/libata.h>
#define DRV_NAME "sata_sil24"
-#define DRV_VERSION "1.0"
+#define DRV_VERSION "1.1"
/*
* Port request block (PRB) 32 bytes
@@ -238,7 +238,7 @@ enum {
SIL24_COMMON_FLAGS = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY |
ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA |
ATA_FLAG_NCQ | ATA_FLAG_ACPI_SATA |
- ATA_FLAG_AN,
+ ATA_FLAG_AN | ATA_FLAG_PMP,
SIL24_COMMON_LFLAGS = ATA_LFLAG_SKIP_D2H_BSY,
SIL24_FLAG_PCIX_IRQ_WOC = (1 << 24), /* IRQ loss errata on PCI-X */
@@ -330,9 +330,14 @@ static u8 sil24_check_status(struct ata_port *ap);
static int sil24_scr_read(struct ata_port *ap, unsigned sc_reg, u32 *val);
static int sil24_scr_write(struct ata_port *ap, unsigned sc_reg, u32 val);
static void sil24_tf_read(struct ata_port *ap, struct ata_taskfile *tf);
+static int sil24_qc_defer(struct ata_queued_cmd *qc);
static void sil24_qc_prep(struct ata_queued_cmd *qc);
static unsigned int sil24_qc_issue(struct ata_queued_cmd *qc);
static void sil24_irq_clear(struct ata_port *ap);
+static void sil24_pmp_attach(struct ata_port *ap);
+static void sil24_pmp_detach(struct ata_port *ap);
+static int sil24_pmp_read(struct ata_device *dev, int pmp, int reg, u32 *r_val);
+static int sil24_pmp_write(struct ata_device *dev, int pmp, int reg, u32 val);
static void sil24_freeze(struct ata_port *ap);
static void sil24_thaw(struct ata_port *ap);
static void sil24_error_handler(struct ata_port *ap);
@@ -341,6 +346,7 @@ static int sil24_port_start(struct ata_port *ap);
static int sil24_init_one(struct pci_dev *pdev, const struct pci_device_id *ent);
#ifdef CONFIG_PM
static int sil24_pci_device_resume(struct pci_dev *pdev);
+static int sil24_port_resume(struct ata_port *ap);
#endif
static const struct pci_device_id sil24_pci_tbl[] = {
@@ -393,7 +399,7 @@ static const struct ata_port_operations sil24_ops = {
.tf_read = sil24_tf_read,
- .qc_defer = ata_std_qc_defer,
+ .qc_defer = sil24_qc_defer,
.qc_prep = sil24_qc_prep,
.qc_issue = sil24_qc_issue,
@@ -402,12 +408,21 @@ static const struct ata_port_operations sil24_ops = {
.scr_read = sil24_scr_read,
.scr_write = sil24_scr_write,
+ .pmp_attach = sil24_pmp_attach,
+ .pmp_detach = sil24_pmp_detach,
+ .pmp_read = sil24_pmp_read,
+ .pmp_write = sil24_pmp_write,
+
.freeze = sil24_freeze,
.thaw = sil24_thaw,
.error_handler = sil24_error_handler,
.post_internal_cmd = sil24_post_internal_cmd,
.port_start = sil24_port_start,
+
+#ifdef CONFIG_PM
+ .port_resume = sil24_port_resume,
+#endif
};
/*
@@ -521,11 +536,40 @@ static void sil24_tf_read(struct ata_port *ap, struct ata_taskfile *tf)
*tf = pp->tf;
}
+static void sil24_config_pmp(struct ata_port *ap, int attached)
+{
+ void __iomem *port = ap->ioaddr.cmd_addr;
+
+ if (attached)
+ writel(PORT_CS_PMP_EN, port + PORT_CTRL_STAT);
+ else
+ writel(PORT_CS_PMP_EN, port + PORT_CTRL_CLR);
+}
+
+static void sil24_clear_pmp(struct ata_port *ap)
+{
+ void __iomem *port = ap->ioaddr.cmd_addr;
+ int i;
+
+ writel(PORT_CS_PMP_RESUME, port + PORT_CTRL_CLR);
+
+ for (i = 0; i < SATA_PMP_MAX_PORTS; i++) {
+ void __iomem *pmp_base = port + PORT_PMP + i * PORT_PMP_SIZE;
+
+ writel(0, pmp_base + PORT_PMP_STATUS);
+ writel(0, pmp_base + PORT_PMP_QACTIVE);
+ }
+}
+
static int sil24_init_port(struct ata_port *ap)
{
void __iomem *port = ap->ioaddr.cmd_addr;
u32 tmp;
+ /* clear PMP error status */
+ if (ap->nr_pmp_links)
+ sil24_clear_pmp(ap);
+
writel(PORT_CS_INIT, port + PORT_CTRL_STAT);
ata_wait_register(port + PORT_CTRL_STAT,
PORT_CS_INIT, PORT_CS_INIT, 10, 100);
@@ -640,7 +684,7 @@ static int sil24_do_softreset(struct ata_link *link, unsigned int *class,
static int sil24_softreset(struct ata_link *link, unsigned int *class,
unsigned long deadline)
{
- return sil24_do_softreset(link, class, 0, deadline);
+ return sil24_do_softreset(link, class, SATA_PMP_CTRL_PORT, deadline);
}
static int sil24_hardreset(struct ata_link *link, unsigned int *class,
@@ -708,6 +752,38 @@ static inline void sil24_fill_sg(struct ata_queued_cmd *qc,
}
}
+static int sil24_qc_defer(struct ata_queued_cmd *qc)
+{
+ struct ata_link *link = qc->dev->link;
+ struct ata_port *ap = link->ap;
+ u8 prot = qc->tf.protocol;
+ int is_atapi = (prot == ATA_PROT_ATAPI ||
+ prot == ATA_PROT_ATAPI_NODATA ||
+ prot == ATA_PROT_ATAPI_DMA);
+
+ /* ATAPI commands completing with CHECK_SENSE cause various
+ * weird problems if other commands are active. PMP DMA CS
+ * errata doesn't cover all and HSM violation occurs even with
+ * only one other device active. Always run an ATAPI command
+ * by itself.
+ */
+ if (unlikely(ap->excl_link)) {
+ if (link == ap->excl_link) {
+ if (ap->nr_active_links)
+ return ATA_DEFER_PORT;
+ qc->flags |= ATA_QCFLAG_CLEAR_EXCL;
+ } else
+ return ATA_DEFER_PORT;
+ } else if (unlikely(is_atapi)) {
+ ap->excl_link = link;
+ if (ap->nr_active_links)
+ return ATA_DEFER_PORT;
+ qc->flags |= ATA_QCFLAG_CLEAR_EXCL;
+ }
+
+ return ata_std_qc_defer(qc);
+}
+
static void sil24_qc_prep(struct ata_queued_cmd *qc)
{
struct ata_port *ap = qc->ap;
@@ -751,7 +827,7 @@ static void sil24_qc_prep(struct ata_queued_cmd *qc)
}
prb->ctrl = cpu_to_le16(ctrl);
- ata_tf_to_fis(&qc->tf, 0, 1, prb->fis);
+ ata_tf_to_fis(&qc->tf, qc->dev->link->pmp, 1, prb->fis);
if (qc->flags & ATA_QCFLAG_DMAMAP)
sil24_fill_sg(qc, sge);
@@ -780,6 +856,65 @@ static void sil24_irq_clear(struct ata_port *ap)
/* unused */
}
+static void sil24_pmp_attach(struct ata_port *ap)
+{
+ sil24_config_pmp(ap, 1);
+ sil24_init_port(ap);
+}
+
+static void sil24_pmp_detach(struct ata_port *ap)
+{
+ sil24_init_port(ap);
+ sil24_config_pmp(ap, 0);
+}
+
+static int sil24_pmp_read(struct ata_device *dev, int pmp, int reg, u32 *r_val)
+{
+ struct ata_port *ap = dev->link->ap;
+ struct ata_taskfile tf;
+ int rc;
+
+ sata_pmp_read_init_tf(&tf, dev, pmp, reg);
+ rc = sil24_exec_polled_cmd(ap, SATA_PMP_CTRL_PORT, &tf, 1, 0,
+ SATA_PMP_SCR_TIMEOUT);
+ if (rc == 0) {
+ sil24_read_tf(ap, 0, &tf);
+ *r_val = sata_pmp_read_val(&tf);
+ }
+ return rc;
+}
+
+static int sil24_pmp_write(struct ata_device *dev, int pmp, int reg, u32 val)
+{
+ struct ata_port *ap = dev->link->ap;
+ struct ata_taskfile tf;
+
+ sata_pmp_write_init_tf(&tf, dev, pmp, reg, val);
+ return sil24_exec_polled_cmd(ap, SATA_PMP_CTRL_PORT, &tf, 1, 0,
+ SATA_PMP_SCR_TIMEOUT);
+}
+
+static int sil24_pmp_softreset(struct ata_link *link, unsigned int *class,
+ unsigned long deadline)
+{
+ return sil24_do_softreset(link, class, link->pmp, deadline);
+}
+
+static int sil24_pmp_hardreset(struct ata_link *link, unsigned int *class,
+ unsigned long deadline)
+{
+ int rc;
+
+ rc = sil24_init_port(link->ap);
+ if (rc) {
+ ata_link_printk(link, KERN_ERR,
+ "hardreset failed (port not ready)\n");
+ return rc;
+ }
+
+ return sata_pmp_std_hardreset(link, class, deadline);
+}
+
static void sil24_freeze(struct ata_port *ap)
{
void __iomem *port = ap->ioaddr.cmd_addr;
@@ -807,8 +942,10 @@ static void sil24_error_intr(struct ata_port *ap)
{
void __iomem *port = ap->ioaddr.cmd_addr;
struct sil24_port_priv *pp = ap->private_data;
- struct ata_eh_info *ehi = &ap->link.eh_info;
- int freeze = 0;
+ struct ata_queued_cmd *qc = NULL;
+ struct ata_link *link;
+ struct ata_eh_info *ehi;
+ int abort = 0, freeze = 0;
u32 irq_stat;
/* on error, we need to clear IRQ explicitly */
@@ -816,6 +953,8 @@ static void sil24_error_intr(struct ata_port *ap)
writel(irq_stat, port + PORT_IRQ_STAT);
/* first, analyze and record host port events */
+ link = &ap->link;
+ ehi = &link->eh_info;
ata_ehi_clear_desc(ehi);
ata_ehi_push_desc(ehi, "irq_stat 0x%08x", irq_stat);
@@ -844,8 +983,43 @@ static void sil24_error_intr(struct ata_port *ap)
if (irq_stat & PORT_IRQ_ERROR) {
struct sil24_cerr_info *ci = NULL;
unsigned int err_mask = 0, action = 0;
- struct ata_queued_cmd *qc;
- u32 cerr;
+ u32 context, cerr;
+ int pmp;
+
+ abort = 1;
+
+ /* DMA Context Switch Failure in Port Multiplier Mode
+ * errata. If we have active commands to 3 or more
+ * devices, any error condition on active devices can
+ * corrupt DMA context switching.
+ */
+ if (ap->nr_active_links >= 3) {
+ ehi->err_mask |= AC_ERR_OTHER;
+ ehi->action |= ATA_EH_HARDRESET;
+ ata_ehi_push_desc(ehi, "PMP DMA CS errata");
+ freeze = 1;
+ }
+
+ /* find out the offending link and qc */
+ if (ap->nr_pmp_links) {
+ context = readl(port + PORT_CONTEXT);
+ pmp = (context >> 5) & 0xf;
+
+ if (pmp < ap->nr_pmp_links) {
+ link = &ap->pmp_link[pmp];
+ ehi = &link->eh_info;
+ qc = ata_qc_from_tag(ap, link->active_tag);
+
+ ata_ehi_clear_desc(ehi);
+ ata_ehi_push_desc(ehi, "irq_stat 0x%08x",
+ irq_stat);
+ } else {
+ err_mask |= AC_ERR_HSM;
+ action |= ATA_EH_HARDRESET;
+ freeze = 1;
+ }
+ } else
+ qc = ata_qc_from_tag(ap, link->active_tag);
/* analyze CMD_ERR */
cerr = readl(port + PORT_CMD_ERR);
@@ -864,7 +1038,6 @@ static void sil24_error_intr(struct ata_port *ap)
}
/* record error info */
- qc = ata_qc_from_tag(ap, ap->link.active_tag);
if (qc) {
sil24_read_tf(ap, qc->tag, &pp->tf);
qc->err_mask |= err_mask;
@@ -872,13 +1045,21 @@ static void sil24_error_intr(struct ata_port *ap)
ehi->err_mask |= err_mask;
ehi->action |= action;
+
+ /* if PMP, resume */
+ if (ap->nr_pmp_links)
+ writel(PORT_CS_PMP_RESUME, port + PORT_CTRL_STAT);
}
/* freeze or abort */
if (freeze)
ata_port_freeze(ap);
- else
- ata_port_abort(ap);
+ else if (abort) {
+ if (qc)
+ ata_link_abort(qc->dev->link);
+ else
+ ata_port_abort(ap);
+ }
}
static void sil24_finish_qc(struct ata_queued_cmd *qc)
@@ -971,16 +1152,14 @@ static irqreturn_t sil24_interrupt(int irq, void *dev_instance)
static void sil24_error_handler(struct ata_port *ap)
{
- struct ata_eh_context *ehc = &ap->link.eh_context;
-
- if (sil24_init_port(ap)) {
+ if (sil24_init_port(ap))
ata_eh_freeze_port(ap);
- ehc->i.action |= ATA_EH_HARDRESET;
- }
/* perform recovery */
- ata_do_eh(ap, ata_std_prereset, sil24_softreset, sil24_hardreset,
- ata_std_postreset);
+ sata_pmp_do_eh(ap, ata_std_prereset, sil24_softreset, sil24_hardreset,
+ ata_std_postreset, sata_pmp_std_prereset,
+ sil24_pmp_softreset, sil24_pmp_hardreset,
+ sata_pmp_std_postreset);
}
static void sil24_post_internal_cmd(struct ata_queued_cmd *qc)
@@ -988,8 +1167,8 @@ static void sil24_post_internal_cmd(struct ata_queued_cmd *qc)
struct ata_port *ap = qc->ap;
/* make DMA engine forget about the failed command */
- if (qc->flags & ATA_QCFLAG_FAILED)
- sil24_init_port(ap);
+ if ((qc->flags & ATA_QCFLAG_FAILED) && sil24_init_port(ap))
+ ata_eh_freeze_port(ap);
}
static int sil24_port_start(struct ata_port *ap)
@@ -1190,6 +1369,12 @@ static int sil24_pci_device_resume(struct pci_dev *pdev)
return 0;
}
+
+static int sil24_port_resume(struct ata_port *ap)
+{
+ sil24_config_pmp(ap, ap->nr_pmp_links);
+ return 0;
+}
#endif
static int __init sil24_init(void)