From f084de5efc3fba428e409fbd55c0e7fac9ab2b82 Mon Sep 17 00:00:00 2001 From: Peter Schwenke Date: Fri, 10 Aug 2007 13:36:03 +1000 Subject: [PATCH] UBUNTU: OriginalAuthor: Tejun Heo OriginalLocation: http://bugzilla.kernel.org/show_bug.cgi?id=7780 Bug: 90771 Signed-off-by: Peter Schwenke I have ported the Tejun Heo's patch to the Ubuntu 2.6.20 kernel. I have tested on my Toshiba Portege M500 and suspend now works in a few seconds rather than 5 minutes. That has been the behaviour from Dapper until now. --- drivers/ata/ata_piix.c | 122 +++++++++++++++++++++++++++++++++++++++++++++- drivers/pci/pci-driver.c | 3 +- drivers/pci/pci.c | 16 ++++++ include/linux/libata.h | 4 +- include/linux/pci.h | 1 + 5 files changed, 141 insertions(+), 5 deletions(-) diff --git a/drivers/ata/ata_piix.c b/drivers/ata/ata_piix.c index 88a5b8e..2b5af5d 100644 --- a/drivers/ata/ata_piix.c +++ b/drivers/ata/ata_piix.c @@ -91,6 +91,7 @@ #include #include #include +#include #define DRV_NAME "ata_piix" #define DRV_VERSION "2.10ac1" @@ -140,6 +141,9 @@ enum { RV = -3, /* reserved */ PIIX_AHCI_DEVICE = 6, + + /* host->flags bits */ + PIIX_HOST_BROKEN_SUSPEND = (1 << 24), }; struct piix_map_db { @@ -160,6 +164,10 @@ static void piix_sata_error_handler(struct ata_port *ap); 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); static void ich_set_dmamode (struct ata_port *ap, struct ata_device *adev); +#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); +#endif static unsigned int in_module_init = 1; @@ -256,8 +264,8 @@ static struct pci_driver piix_pci_driver = { .probe = piix_init_one, .remove = ata_pci_remove_one, #ifdef CONFIG_PM - .suspend = ata_pci_device_suspend, - .resume = ata_pci_device_resume, + .suspend = piix_pci_device_suspend, + .resume = piix_pci_device_resume, #endif }; @@ -914,6 +922,116 @@ static void ich_set_dmamode (struct ata_port *ap, struct ata_device *adev) do_pata_set_dmamode(ap, adev, 1); } +#ifdef CONFIG_PM + +static int piix_broken_suspend(void) +{ + static struct dmi_system_id sysids[] = { + { + .ident = "TECRA M5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "TECRA M5"), + }, + }, + { + .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"), + }, + }, + { } + }; + 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_SUSPEND) { + 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 + #define AHCI_PCI_BAR 5 #define AHCI_GLOBAL_CTL 0x04 #define AHCI_ENABLE (1 << 31) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 92d5e8d..add8683 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -324,8 +324,7 @@ static int pci_default_resume(struct pci_dev *pci_dev) /* restore the PCI config space */ pci_restore_state(pci_dev); /* if the device was enabled before suspend, reenable */ - if (atomic_read(&pci_dev->enable_cnt)) - retval = __pci_enable_device(pci_dev); + retval = pci_reenable_device(pci_dev); /* if the device was busmaster before the suspend, make it busmaster again */ if (pci_dev->is_busmaster) pci_set_master(pci_dev); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 9036d15..a850ea6 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -723,6 +723,21 @@ __pci_enable_device(struct pci_dev *dev) } /** + * pci_reenable_device - Resume abandoned device + * @dev: PCI device to be resumed + * + * Note this function is a backend of pci_default_resume and is not supposed + * to be called by normal code, write proper resume handler and use it instead. + */ +int +pci_reenable_device(struct pci_dev *dev) +{ + if (atomic_read(&dev->enable_cnt)) + return __pci_enable_device(dev); + return 0; +} + +/** * pci_enable_device - Initialize device before it's used by a driver. * @dev: PCI device to be initialized * @@ -1342,6 +1357,7 @@ EXPORT_SYMBOL(isa_bridge); EXPORT_SYMBOL_GPL(pci_restore_bars); EXPORT_SYMBOL(pci_enable_device_bars); EXPORT_SYMBOL(pci_enable_device); +EXPORT_SYMBOL(pci_reenable_device); EXPORT_SYMBOL(pcim_enable_device); EXPORT_SYMBOL(pcim_pin_device); EXPORT_SYMBOL(pci_disable_device); diff --git a/include/linux/libata.h b/include/linux/libata.h index 067d12f..4b99dc0 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -208,8 +208,10 @@ enum { ATA_QCFLAG_SENSE_VALID = (1 << 17), /* sense data valid */ ATA_QCFLAG_EH_SCHEDULED = (1 << 18), /* EH scheduled (obsolete) */ - /* host set flags */ + /* host set flags */ ATA_HOST_SIMPLEX = (1 << 0), /* Host is simplex, one DMA channel per host only */ + + /* bits 24:31 of host->flags are reserved for LLD specific flags */ /* various lengths of time */ ATA_TMOUT_BOOT = 30 * HZ, /* heuristic */ diff --git a/include/linux/pci.h b/include/linux/pci.h index b9bbda9..2e6121e 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -521,6 +521,7 @@ static inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val } int __must_check pci_enable_device(struct pci_dev *dev); +int __must_check pci_reenable_device(struct pci_dev *dev); int __must_check pci_enable_device_bars(struct pci_dev *dev, int mask); int __must_check pcim_enable_device(struct pci_dev *pdev); void pcim_pin_device(struct pci_dev *pdev); -- 1.4.4.2