summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/ata/ata_piix.c251
1 files changed, 247 insertions, 4 deletions
diff --git a/drivers/ata/ata_piix.c b/drivers/ata/ata_piix.c
index 024e6d52eab2..a65c8ae5c461 100644
--- a/drivers/ata/ata_piix.c
+++ b/drivers/ata/ata_piix.c
@@ -101,9 +101,14 @@ enum {
ICH5_PMR = 0x90, /* port mapping register */
ICH5_PCS = 0x92, /* port control and status */
PIIX_SCC = 0x0A, /* sub-class code register */
+ PIIX_SIDPR_BAR = 5,
+ PIIX_SIDPR_LEN = 16,
+ PIIX_SIDPR_IDX = 0,
+ PIIX_SIDPR_DATA = 4,
PIIX_FLAG_AHCI = (1 << 27), /* AHCI possible */
PIIX_FLAG_CHECKINTR = (1 << 28), /* make sure PCI INTx enabled */
+ PIIX_FLAG_SIDPR = (1 << 29), /* SATA idx/data pair regs */
PIIX_PATA_FLAGS = ATA_FLAG_SLAVE_POSS,
PIIX_SATA_FLAGS = ATA_FLAG_SATA | PIIX_FLAG_CHECKINTR,
@@ -152,6 +157,7 @@ struct piix_map_db {
struct piix_host_priv {
const int *map;
+ void __iomem *sidpr;
};
static int piix_init_one(struct pci_dev *pdev,
@@ -162,6 +168,9 @@ static void piix_set_dmamode(struct ata_port *ap, struct ata_device *adev);
static void ich_set_dmamode(struct ata_port *ap, struct ata_device *adev);
static int ich_pata_cable_detect(struct ata_port *ap);
static u8 piix_vmw_bmdma_status(struct ata_port *ap);
+static int piix_sidpr_scr_read(struct ata_port *ap, unsigned int reg, u32 *val);
+static int piix_sidpr_scr_write(struct ata_port *ap, unsigned int reg, u32 val);
+static void piix_sidpr_error_handler(struct ata_port *ap);
#ifdef CONFIG_PM
static int piix_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg);
static int piix_pci_device_resume(struct pci_dev *pdev);
@@ -411,6 +420,35 @@ static const struct ata_port_operations piix_vmw_ops = {
.port_start = ata_port_start,
};
+static const struct ata_port_operations piix_sidpr_sata_ops = {
+ .tf_load = ata_tf_load,
+ .tf_read = ata_tf_read,
+ .check_status = ata_check_status,
+ .exec_command = ata_exec_command,
+ .dev_select = ata_std_dev_select,
+
+ .bmdma_setup = ata_bmdma_setup,
+ .bmdma_start = ata_bmdma_start,
+ .bmdma_stop = ata_bmdma_stop,
+ .bmdma_status = ata_bmdma_status,
+ .qc_prep = ata_qc_prep,
+ .qc_issue = ata_qc_issue_prot,
+ .data_xfer = ata_data_xfer,
+
+ .scr_read = piix_sidpr_scr_read,
+ .scr_write = piix_sidpr_scr_write,
+
+ .freeze = ata_bmdma_freeze,
+ .thaw = ata_bmdma_thaw,
+ .error_handler = piix_sidpr_error_handler,
+ .post_internal_cmd = ata_bmdma_post_internal_cmd,
+
+ .irq_clear = ata_bmdma_irq_clear,
+ .irq_on = ata_irq_on,
+
+ .port_start = ata_port_start,
+};
+
static const struct piix_map_db ich5_map_db = {
.mask = 0x7,
.port_enable = 0x3,
@@ -598,7 +636,8 @@ static struct ata_port_info piix_port_info[] = {
[ich8_sata_ahci] =
{
- .flags = PIIX_SATA_FLAGS | PIIX_FLAG_AHCI,
+ .flags = PIIX_SATA_FLAGS | PIIX_FLAG_AHCI |
+ PIIX_FLAG_SIDPR,
.pio_mask = 0x1f, /* pio0-4 */
.mwdma_mask = 0x07, /* mwdma0-2 */
.udma_mask = ATA_UDMA6,
@@ -607,7 +646,8 @@ static struct ata_port_info piix_port_info[] = {
[ich8_2port_sata] =
{
- .flags = PIIX_SATA_FLAGS | PIIX_FLAG_AHCI,
+ .flags = PIIX_SATA_FLAGS | PIIX_FLAG_AHCI |
+ PIIX_FLAG_SIDPR,
.pio_mask = 0x1f, /* pio0-4 */
.mwdma_mask = 0x07, /* mwdma0-2 */
.udma_mask = ATA_UDMA6,
@@ -625,7 +665,8 @@ static struct ata_port_info piix_port_info[] = {
[ich8m_apple_sata_ahci] =
{
- .flags = PIIX_SATA_FLAGS | PIIX_FLAG_AHCI,
+ .flags = PIIX_SATA_FLAGS | PIIX_FLAG_AHCI |
+ PIIX_FLAG_SIDPR,
.pio_mask = 0x1f, /* pio0-4 */
.mwdma_mask = 0x07, /* mwdma0-2 */
.udma_mask = ATA_UDMA6,
@@ -974,6 +1015,180 @@ static void ich_set_dmamode(struct ata_port *ap, struct ata_device *adev)
do_pata_set_dmamode(ap, adev, 1);
}
+/*
+ * Serial ATA Index/Data Pair Superset Registers access
+ *
+ * Beginning from ICH8, there's a sane way to access SCRs using index
+ * and data register pair located at BAR5. This creates an
+ * interesting problem of mapping two SCRs to one port.
+ *
+ * Although they have separate SCRs, the master and slave aren't
+ * independent enough to be treated as separate links - e.g. softreset
+ * resets both. Also, there's no protocol defined for hard resetting
+ * singled device sharing the virtual port (no defined way to acquire
+ * device signature). This is worked around by merging the SCR values
+ * into one sensible value and requesting follow-up SRST after
+ * hardreset.
+ *
+ * SCR merging is perfomed in nibbles which is the unit contents in
+ * SCRs are organized. If two values are equal, the value is used.
+ * When they differ, merge table which lists precedence of possible
+ * values is consulted and the first match or the last entry when
+ * nothing matches is used. When there's no merge table for the
+ * specific nibble, value from the first port is used.
+ */
+static const int piix_sidx_map[] = {
+ [SCR_STATUS] = 0,
+ [SCR_ERROR] = 2,
+ [SCR_CONTROL] = 1,
+};
+
+static void piix_sidpr_sel(struct ata_device *dev, unsigned int reg)
+{
+ struct ata_port *ap = dev->link->ap;
+ struct piix_host_priv *hpriv = ap->host->private_data;
+
+ iowrite32(((ap->port_no * 2 + dev->devno) << 8) | piix_sidx_map[reg],
+ hpriv->sidpr + PIIX_SIDPR_IDX);
+}
+
+static int piix_sidpr_read(struct ata_device *dev, unsigned int reg)
+{
+ struct piix_host_priv *hpriv = dev->link->ap->host->private_data;
+
+ piix_sidpr_sel(dev, reg);
+ return ioread32(hpriv->sidpr + PIIX_SIDPR_DATA);
+}
+
+static void piix_sidpr_write(struct ata_device *dev, unsigned int reg, u32 val)
+{
+ struct piix_host_priv *hpriv = dev->link->ap->host->private_data;
+
+ piix_sidpr_sel(dev, reg);
+ iowrite32(val, hpriv->sidpr + PIIX_SIDPR_DATA);
+}
+
+u32 piix_merge_scr(u32 val0, u32 val1, const int * const *merge_tbl)
+{
+ u32 val = 0;
+ int i, mi;
+
+ for (i = 0, mi = 0; i < 32 / 4; i++) {
+ u8 c0 = (val0 >> (i * 4)) & 0xf;
+ u8 c1 = (val1 >> (i * 4)) & 0xf;
+ u8 merged = c0;
+ const int *cur;
+
+ /* if no merge preference, assume the first value */
+ cur = merge_tbl[mi];
+ if (!cur)
+ goto done;
+ mi++;
+
+ /* if two values equal, use it */
+ if (c0 == c1)
+ goto done;
+
+ /* choose the first match or the last from the merge table */
+ while (*cur != -1) {
+ if (c0 == *cur || c1 == *cur)
+ break;
+ cur++;
+ }
+ if (*cur == -1)
+ cur--;
+ merged = *cur;
+ done:
+ val |= merged << (i * 4);
+ }
+
+ return val;
+}
+
+static int piix_sidpr_scr_read(struct ata_port *ap, unsigned int reg, u32 *val)
+{
+ const int * const sstatus_merge_tbl[] = {
+ /* DET */ (const int []){ 1, 3, 0, 4, 3, -1 },
+ /* SPD */ (const int []){ 2, 1, 0, -1 },
+ /* IPM */ (const int []){ 6, 2, 1, 0, -1 },
+ NULL,
+ };
+ const int * const scontrol_merge_tbl[] = {
+ /* DET */ (const int []){ 1, 0, 4, 0, -1 },
+ /* SPD */ (const int []){ 0, 2, 1, 0, -1 },
+ /* IPM */ (const int []){ 0, 1, 2, 3, 0, -1 },
+ NULL,
+ };
+ u32 v0, v1;
+
+ if (reg >= ARRAY_SIZE(piix_sidx_map))
+ return -EINVAL;
+
+ if (!(ap->flags & ATA_FLAG_SLAVE_POSS)) {
+ *val = piix_sidpr_read(&ap->link.device[0], reg);
+ return 0;
+ }
+
+ v0 = piix_sidpr_read(&ap->link.device[0], reg);
+ v1 = piix_sidpr_read(&ap->link.device[1], reg);
+
+ switch (reg) {
+ case SCR_STATUS:
+ *val = piix_merge_scr(v0, v1, sstatus_merge_tbl);
+ break;
+ case SCR_ERROR:
+ *val = v0 | v1;
+ break;
+ case SCR_CONTROL:
+ *val = piix_merge_scr(v0, v1, scontrol_merge_tbl);
+ break;
+ }
+
+ return 0;
+}
+
+static int piix_sidpr_scr_write(struct ata_port *ap, unsigned int reg, u32 val)
+{
+ if (reg >= ARRAY_SIZE(piix_sidx_map))
+ return -EINVAL;
+
+ piix_sidpr_write(&ap->link.device[0], reg, val);
+
+ if (ap->flags & ATA_FLAG_SLAVE_POSS)
+ piix_sidpr_write(&ap->link.device[1], reg, val);
+
+ return 0;
+}
+
+static int piix_sidpr_hardreset(struct ata_link *link, unsigned int *class,
+ unsigned long deadline)
+{
+ const unsigned long *timing = sata_ehc_deb_timing(&link->eh_context);
+ int rc;
+
+ /* do hardreset */
+ rc = sata_link_hardreset(link, timing, deadline);
+ if (rc) {
+ ata_link_printk(link, KERN_ERR,
+ "COMRESET failed (errno=%d)\n", rc);
+ return rc;
+ }
+
+ /* TODO: phy layer with polling, timeouts, etc. */
+ if (ata_link_offline(link)) {
+ *class = ATA_DEV_NONE;
+ return 0;
+ }
+
+ return -EAGAIN;
+}
+
+static void piix_sidpr_error_handler(struct ata_port *ap)
+{
+ ata_bmdma_drive_eh(ap, ata_std_prereset, ata_std_softreset,
+ piix_sidpr_hardreset, ata_std_postreset);
+}
+
#ifdef CONFIG_PM
static int piix_broken_suspend(void)
{
@@ -1304,6 +1519,32 @@ static const int *__devinit piix_init_sata_map(struct pci_dev *pdev,
return map;
}
+static void __devinit piix_init_sidpr(struct ata_host *host)
+{
+ struct pci_dev *pdev = to_pci_dev(host->dev);
+ struct piix_host_priv *hpriv = host->private_data;
+ int i;
+
+ /* check for availability */
+ for (i = 0; i < 4; i++)
+ if (hpriv->map[i] == IDE)
+ return;
+
+ if (!(host->ports[0]->flags & PIIX_FLAG_SIDPR))
+ return;
+
+ if (pci_resource_start(pdev, PIIX_SIDPR_BAR) == 0 ||
+ pci_resource_len(pdev, PIIX_SIDPR_BAR) != PIIX_SIDPR_LEN)
+ return;
+
+ if (pcim_iomap_regions(pdev, 1 << PIIX_SIDPR_BAR, DRV_NAME))
+ return;
+
+ hpriv->sidpr = pcim_iomap_table(pdev)[PIIX_SIDPR_BAR];
+ host->ports[0]->ops = &piix_sidpr_sata_ops;
+ host->ports[1]->ops = &piix_sidpr_sata_ops;
+}
+
static void piix_iocfg_bit18_quirk(struct pci_dev *pdev)
{
static const struct dmi_system_id sysids[] = {
@@ -1408,8 +1649,10 @@ static int piix_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
}
}
- if (port_flags & ATA_FLAG_SATA)
+ if (port_flags & ATA_FLAG_SATA) {
piix_init_pcs(host, piix_map_db_table[ent->driver_data]);
+ piix_init_sidpr(host);
+ }
/* apply IOCFG bit18 quirk */
piix_iocfg_bit18_quirk(pdev);