+/*
+ * 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);
+}
+
+static 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;
+}
+
+#ifdef CONFIG_PM
+static int piix_broken_suspend(void)
+{
+ static const struct dmi_system_id sysids[] = {
+ {
+ .ident = "TECRA M3",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TECRA M3"),
+ },
+ },
+ {
+ .ident = "TECRA M3",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Tecra M3"),
+ },
+ },
+ {
+ .ident = "TECRA M4",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Tecra M4"),
+ },
+ },
+ {
+ .ident = "TECRA M5",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TECRA M5"),
+ },
+ },
+ {
+ .ident = "TECRA M6",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TECRA M6"),
+ },
+ },
+ {
+ .ident = "TECRA M7",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TECRA M7"),
+ },
+ },
+ {
+ .ident = "TECRA A8",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TECRA A8"),
+ },
+ },
+ {
+ .ident = "Satellite R20",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Satellite R20"),
+ },
+ },
+ {
+ .ident = "Satellite R25",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Satellite R25"),
+ },
+ },
+ {
+ .ident = "Satellite U200",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Satellite U200"),
+ },
+ },
+ {
+ .ident = "Satellite U200",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SATELLITE U200"),
+ },
+ },
+ {
+ .ident = "Satellite Pro U200",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SATELLITE PRO U200"),
+ },
+ },
+ {
+ .ident = "Satellite U205",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Satellite U205"),
+ },
+ },
+ {
+ .ident = "SATELLITE U205",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SATELLITE U205"),
+ },
+ },
+ {
+ .ident = "Portege M500",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "PORTEGE M500"),
+ },
+ },
+
+ { } /* terminate list */
+ };
+ static const char *oemstrs[] = {
+ "Tecra M3,",
+ };
+ int i;
+
+ if (dmi_check_system(sysids))
+ return 1;
+
+ for (i = 0; i < ARRAY_SIZE(oemstrs); i++)
+ if (dmi_find_device(DMI_DEV_TYPE_OEM_STRING, oemstrs[i], NULL))
+ return 1;
+
+ return 0;
+}
+
+static int piix_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg)
+{
+ struct ata_host *host = dev_get_drvdata(&pdev->dev);
+ unsigned long flags;
+ int rc = 0;
+
+ rc = ata_host_suspend(host, mesg);
+ if (rc)
+ return rc;
+
+ /* Some braindamaged ACPI suspend implementations expect the
+ * controller to be awake on entry; otherwise, it burns cpu
+ * cycles and power trying to do something to the sleeping
+ * beauty.
+ */
+ if (piix_broken_suspend() && (mesg.event & PM_EVENT_SLEEP)) {
+ pci_save_state(pdev);
+
+ /* mark its power state as "unknown", since we don't
+ * know if e.g. the BIOS will change its device state
+ * when we suspend.
+ */
+ if (pdev->current_state == PCI_D0)
+ pdev->current_state = PCI_UNKNOWN;
+
+ /* tell resume that it's waking up from broken suspend */
+ spin_lock_irqsave(&host->lock, flags);
+ host->flags |= PIIX_HOST_BROKEN_SUSPEND;
+ spin_unlock_irqrestore(&host->lock, flags);
+ } else
+ ata_pci_device_do_suspend(pdev, mesg);
+
+ return 0;
+}
+
+static int piix_pci_device_resume(struct pci_dev *pdev)
+{
+ struct ata_host *host = dev_get_drvdata(&pdev->dev);
+ unsigned long flags;
+ int rc;
+
+ if (host->flags & PIIX_HOST_BROKEN_SUSPEND) {
+ spin_lock_irqsave(&host->lock, flags);
+ host->flags &= ~PIIX_HOST_BROKEN_SUSPEND;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ pci_set_power_state(pdev, PCI_D0);
+ pci_restore_state(pdev);
+
+ /* PCI device wasn't disabled during suspend. Use
+ * pci_reenable_device() to avoid affecting the enable
+ * count.
+ */
+ rc = pci_reenable_device(pdev);
+ if (rc)
+ dev_printk(KERN_ERR, &pdev->dev, "failed to enable "
+ "device after resume (%d)\n", rc);
+ } else
+ rc = ata_pci_device_do_resume(pdev);
+
+ if (rc == 0)
+ ata_host_resume(host);
+
+ return rc;
+}
+#endif
+
+static u8 piix_vmw_bmdma_status(struct ata_port *ap)
+{
+ return ata_bmdma_status(ap) & ~ATA_DMA_ERR;
+}
+