ata_piix: save, use saved and restore IOCFG
authorTejun Heo <tj@kernel.org>
Fri, 2 Jan 2009 03:04:48 +0000 (12:04 +0900)
committerJeff Garzik <jgarzik@redhat.com>
Thu, 8 Jan 2009 21:09:43 +0000 (16:09 -0500)
Certain ACPI implementations mess up IOCFG on _STM making libata
detect cable type incorrectly after a suspend/resume cycle.  This
patch makes ata_piix save IOCFG on attach, use the saved value for
things which aren't dynamic and restore it on detach so that the next
driver also gets the BIOS initialized value.

This patch contains the following changes.

* makes ich_pata_cable_detect() use saved_iocfg.

* make piix_iocfg_bit18_quirk() take @host and use saved_iocfg.

* hpriv allocation moved upwards to save iocfg before doing anything
  else.

This fixes bz#11879.  Andreas Mohr reported and diagnosed the problem.

Signed-off-by: Tejun Heo <tj@kernel.org>
Cc: Andreas Mohr <andi@lisas.de>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
drivers/ata/ata_piix.c

index 5fdf167..7865954 100644 (file)
@@ -154,11 +154,13 @@ struct piix_map_db {
 
 struct piix_host_priv {
        const int *map;
+       u32 saved_iocfg;
        void __iomem *sidpr;
 };
 
 static int piix_init_one(struct pci_dev *pdev,
                         const struct pci_device_id *ent);
+static void piix_remove_one(struct pci_dev *pdev);
 static int piix_pata_prereset(struct ata_link *link, unsigned long deadline);
 static void piix_set_piomode(struct ata_port *ap, struct ata_device *adev);
 static void piix_set_dmamode(struct ata_port *ap, struct ata_device *adev);
@@ -296,7 +298,7 @@ static struct pci_driver piix_pci_driver = {
        .name                   = DRV_NAME,
        .id_table               = piix_pci_tbl,
        .probe                  = piix_init_one,
-       .remove                 = ata_pci_remove_one,
+       .remove                 = piix_remove_one,
 #ifdef CONFIG_PM
        .suspend                = piix_pci_device_suspend,
        .resume                 = piix_pci_device_resume,
@@ -610,8 +612,9 @@ static const struct ich_laptop ich_laptop[] = {
 static int ich_pata_cable_detect(struct ata_port *ap)
 {
        struct pci_dev *pdev = to_pci_dev(ap->host->dev);
+       struct piix_host_priv *hpriv = ap->host->private_data;
        const struct ich_laptop *lap = &ich_laptop[0];
-       u8 tmp, mask;
+       u8 mask;
 
        /* Check for specials - Acer Aspire 5602WLMi */
        while (lap->device) {
@@ -625,8 +628,7 @@ static int ich_pata_cable_detect(struct ata_port *ap)
 
        /* check BIOS cable detect results */
        mask = ap->port_no == 0 ? PIIX_80C_PRI : PIIX_80C_SEC;
-       pci_read_config_byte(pdev, PIIX_IOCFG, &tmp);
-       if ((tmp & mask) == 0)
+       if ((hpriv->saved_iocfg & mask) == 0)
                return ATA_CBL_PATA40;
        return ATA_CBL_PATA80;
 }
@@ -1350,7 +1352,7 @@ static int __devinit piix_init_sidpr(struct ata_host *host)
        return 0;
 }
 
-static void piix_iocfg_bit18_quirk(struct pci_dev *pdev)
+static void piix_iocfg_bit18_quirk(struct ata_host *host)
 {
        static const struct dmi_system_id sysids[] = {
                {
@@ -1367,7 +1369,8 @@ static void piix_iocfg_bit18_quirk(struct pci_dev *pdev)
 
                { }     /* terminate list */
        };
-       u32 iocfg;
+       struct pci_dev *pdev = to_pci_dev(host->dev);
+       struct piix_host_priv *hpriv = host->private_data;
 
        if (!dmi_check_system(sysids))
                return;
@@ -1376,12 +1379,11 @@ static void piix_iocfg_bit18_quirk(struct pci_dev *pdev)
         * seem to use it to disable a channel.  Clear the bit on the
         * affected systems.
         */
-       pci_read_config_dword(pdev, PIIX_IOCFG, &iocfg);
-       if (iocfg & (1 << 18)) {
+       if (hpriv->saved_iocfg & (1 << 18)) {
                dev_printk(KERN_INFO, &pdev->dev,
                           "applying IOCFG bit18 quirk\n");
-               iocfg &= ~(1 << 18);
-               pci_write_config_dword(pdev, PIIX_IOCFG, iocfg);
+               pci_write_config_dword(pdev, PIIX_IOCFG,
+                                      hpriv->saved_iocfg & ~(1 << 18));
        }
 }
 
@@ -1430,6 +1432,17 @@ static int __devinit piix_init_one(struct pci_dev *pdev,
        if (rc)
                return rc;
 
+       hpriv = devm_kzalloc(dev, sizeof(*hpriv), GFP_KERNEL);
+       if (!hpriv)
+               return -ENOMEM;
+
+       /* Save IOCFG, this will be used for cable detection, quirk
+        * detection and restoration on detach.  This is necessary
+        * because some ACPI implementations mess up cable related
+        * bits on _STM.  Reported on kernel bz#11879.
+        */
+       pci_read_config_dword(pdev, PIIX_IOCFG, &hpriv->saved_iocfg);
+
        /* ICH6R may be driven by either ata_piix or ahci driver
         * regardless of BIOS configuration.  Make sure AHCI mode is
         * off.
@@ -1441,10 +1454,6 @@ static int __devinit piix_init_one(struct pci_dev *pdev,
        }
 
        /* SATA map init can change port_info, do it before prepping host */
-       hpriv = devm_kzalloc(dev, sizeof(*hpriv), GFP_KERNEL);
-       if (!hpriv)
-               return -ENOMEM;
-
        if (port_flags & ATA_FLAG_SATA)
                hpriv->map = piix_init_sata_map(pdev, port_info,
                                        piix_map_db_table[ent->driver_data]);
@@ -1463,7 +1472,7 @@ static int __devinit piix_init_one(struct pci_dev *pdev,
        }
 
        /* apply IOCFG bit18 quirk */
-       piix_iocfg_bit18_quirk(pdev);
+       piix_iocfg_bit18_quirk(host);
 
        /* On ICH5, some BIOSen disable the interrupt using the
         * PCI_COMMAND_INTX_DISABLE bit added in PCI 2.3.
@@ -1488,6 +1497,16 @@ static int __devinit piix_init_one(struct pci_dev *pdev,
        return ata_pci_sff_activate_host(host, ata_sff_interrupt, &piix_sht);
 }
 
+static void piix_remove_one(struct pci_dev *pdev)
+{
+       struct ata_host *host = dev_get_drvdata(&pdev->dev);
+       struct piix_host_priv *hpriv = host->private_data;
+
+       pci_write_config_dword(pdev, PIIX_IOCFG, hpriv->saved_iocfg);
+
+       ata_pci_remove_one(pdev);
+}
+
 static int __init piix_init(void)
 {
        int rc;