diff options
Diffstat (limited to 'drivers/ata/libata-acpi.c')
-rw-r--r-- | drivers/ata/libata-acpi.c | 408 |
1 files changed, 292 insertions, 116 deletions
diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index bb7c5f1085cc..902b5a457170 100644 --- a/drivers/ata/libata-acpi.c +++ b/drivers/ata/libata-acpi.c @@ -16,6 +16,7 @@ #include <linux/libata.h> #include <linux/pci.h> #include <linux/slab.h> +#include <linux/pm_runtime.h> #include <scsi/scsi_device.h> #include "libata.h" @@ -48,62 +49,53 @@ static void ata_acpi_clear_gtf(struct ata_device *dev) } /** - * ata_acpi_associate_sata_port - associate SATA port with ACPI objects - * @ap: target SATA port + * ata_ap_acpi_handle - provide the acpi_handle for an ata_port + * @ap: the acpi_handle returned will correspond to this port * - * Look up ACPI objects associated with @ap and initialize acpi_handle - * fields of @ap, the port and devices accordingly. - * - * LOCKING: - * EH context. - * - * RETURNS: - * 0 on success, -errno on failure. + * Returns the acpi_handle for the ACPI namespace object corresponding to + * the ata_port passed into the function, or NULL if no such object exists */ -void ata_acpi_associate_sata_port(struct ata_port *ap) +acpi_handle ata_ap_acpi_handle(struct ata_port *ap) { - WARN_ON(!(ap->flags & ATA_FLAG_ACPI_SATA)); - - if (!sata_pmp_attached(ap)) { - u64 adr = SATA_ADR(ap->port_no, NO_PORT_MULT); - - ap->link.device->acpi_handle = - acpi_get_child(ap->host->acpi_handle, adr); - } else { - struct ata_link *link; - - ap->link.device->acpi_handle = NULL; - - ata_for_each_link(link, ap, EDGE) { - u64 adr = SATA_ADR(ap->port_no, link->pmp); + if (ap->flags & ATA_FLAG_ACPI_SATA) + return NULL; - link->device->acpi_handle = - acpi_get_child(ap->host->acpi_handle, adr); - } - } + /* + * If acpi bind operation has already happened, we can get the handle + * for the port by checking the corresponding scsi_host device's + * firmware node, otherwise we will need to find out the handle from + * its parent's acpi node. + */ + if (ap->scsi_host) + return DEVICE_ACPI_HANDLE(&ap->scsi_host->shost_gendev); + else + return acpi_get_child(DEVICE_ACPI_HANDLE(ap->host->dev), + ap->port_no); } +EXPORT_SYMBOL(ata_ap_acpi_handle); -static void ata_acpi_associate_ide_port(struct ata_port *ap) +/** + * ata_dev_acpi_handle - provide the acpi_handle for an ata_device + * @dev: the acpi_device returned will correspond to this port + * + * Returns the acpi_handle for the ACPI namespace object corresponding to + * the ata_device passed into the function, or NULL if no such object exists + */ +acpi_handle ata_dev_acpi_handle(struct ata_device *dev) { - int max_devices, i; - - ap->acpi_handle = acpi_get_child(ap->host->acpi_handle, ap->port_no); - if (!ap->acpi_handle) - return; - - max_devices = 1; - if (ap->flags & ATA_FLAG_SLAVE_POSS) - max_devices++; - - for (i = 0; i < max_devices; i++) { - struct ata_device *dev = &ap->link.device[i]; - - dev->acpi_handle = acpi_get_child(ap->acpi_handle, i); - } + acpi_integer adr; + struct ata_port *ap = dev->link->ap; - if (ata_acpi_gtm(ap, &ap->__acpi_init_gtm) == 0) - ap->pflags |= ATA_PFLAG_INIT_GTM_VALID; + if (ap->flags & ATA_FLAG_ACPI_SATA) { + if (!sata_pmp_attached(ap)) + adr = SATA_ADR(ap->port_no, NO_PORT_MULT); + else + adr = SATA_ADR(ap->port_no, dev->link->pmp); + return acpi_get_child(DEVICE_ACPI_HANDLE(ap->host->dev), adr); + } else + return acpi_get_child(ata_ap_acpi_handle(ap), dev->devno); } +EXPORT_SYMBOL(ata_dev_acpi_handle); /* @ap and @dev are the same as ata_acpi_handle_hotplug() */ static void ata_acpi_detach_device(struct ata_port *ap, struct ata_device *dev) @@ -229,56 +221,6 @@ static const struct acpi_dock_ops ata_acpi_ap_dock_ops = { }; /** - * ata_acpi_associate - associate ATA host with ACPI objects - * @host: target ATA host - * - * Look up ACPI objects associated with @host and initialize - * acpi_handle fields of @host, its ports and devices accordingly. - * - * LOCKING: - * EH context. - * - * RETURNS: - * 0 on success, -errno on failure. - */ -void ata_acpi_associate(struct ata_host *host) -{ - int i, j; - - if (!is_pci_dev(host->dev) || libata_noacpi) - return; - - host->acpi_handle = DEVICE_ACPI_HANDLE(host->dev); - if (!host->acpi_handle) - return; - - for (i = 0; i < host->n_ports; i++) { - struct ata_port *ap = host->ports[i]; - - if (host->ports[0]->flags & ATA_FLAG_ACPI_SATA) - ata_acpi_associate_sata_port(ap); - else - ata_acpi_associate_ide_port(ap); - - if (ap->acpi_handle) { - /* we might be on a docking station */ - register_hotplug_dock_device(ap->acpi_handle, - &ata_acpi_ap_dock_ops, ap); - } - - for (j = 0; j < ata_link_max_devices(&ap->link); j++) { - struct ata_device *dev = &ap->link.device[j]; - - if (dev->acpi_handle) { - /* we might be on a docking station */ - register_hotplug_dock_device(dev->acpi_handle, - &ata_acpi_dev_dock_ops, dev); - } - } - } -} - -/** * ata_acpi_dissociate - dissociate ATA host from ACPI objects * @host: target ATA host * @@ -299,7 +241,7 @@ void ata_acpi_dissociate(struct ata_host *host) struct ata_port *ap = host->ports[i]; const struct ata_acpi_gtm *gtm = ata_acpi_init_gtm(ap); - if (ap->acpi_handle && gtm) + if (ata_ap_acpi_handle(ap) && gtm) ata_acpi_stm(ap, gtm); } } @@ -324,7 +266,8 @@ int ata_acpi_gtm(struct ata_port *ap, struct ata_acpi_gtm *gtm) acpi_status status; int rc = 0; - status = acpi_evaluate_object(ap->acpi_handle, "_GTM", NULL, &output); + status = acpi_evaluate_object(ata_ap_acpi_handle(ap), "_GTM", NULL, + &output); rc = -ENOENT; if (status == AE_NOT_FOUND) @@ -394,7 +337,8 @@ int ata_acpi_stm(struct ata_port *ap, const struct ata_acpi_gtm *stm) input.count = 3; input.pointer = in_params; - status = acpi_evaluate_object(ap->acpi_handle, "_STM", &input, NULL); + status = acpi_evaluate_object(ata_ap_acpi_handle(ap), "_STM", &input, + NULL); if (status == AE_NOT_FOUND) return -ENOENT; @@ -451,7 +395,8 @@ static int ata_dev_get_GTF(struct ata_device *dev, struct ata_acpi_gtf **gtf) __func__, ap->port_no); /* _GTF has no input parameters */ - status = acpi_evaluate_object(dev->acpi_handle, "_GTF", NULL, &output); + status = acpi_evaluate_object(ata_dev_acpi_handle(dev), "_GTF", NULL, + &output); out_obj = dev->gtf_cache = output.pointer; if (ACPI_FAILURE(status)) { @@ -817,7 +762,8 @@ static int ata_acpi_push_id(struct ata_device *dev) /* It's OK for _SDD to be missing too. */ swap_buf_le16(dev->id, ATA_ID_WORDS); - status = acpi_evaluate_object(dev->acpi_handle, "_SDD", &input, NULL); + status = acpi_evaluate_object(ata_dev_acpi_handle(dev), "_SDD", &input, + NULL); swap_buf_le16(dev->id, ATA_ID_WORDS); if (status == AE_NOT_FOUND) @@ -867,7 +813,7 @@ void ata_acpi_on_resume(struct ata_port *ap) const struct ata_acpi_gtm *gtm = ata_acpi_init_gtm(ap); struct ata_device *dev; - if (ap->acpi_handle && gtm) { + if (ata_ap_acpi_handle(ap) && gtm) { /* _GTM valid */ /* restore timing parameters */ @@ -907,23 +853,39 @@ void ata_acpi_on_resume(struct ata_port *ap) void ata_acpi_set_state(struct ata_port *ap, pm_message_t state) { struct ata_device *dev; - - if (!ap->acpi_handle || (ap->flags & ATA_FLAG_ACPI_SATA)) - return; + acpi_handle handle; + int acpi_state; /* channel first and then drives for power on and vica versa for power off */ - if (state.event == PM_EVENT_ON) - acpi_bus_set_power(ap->acpi_handle, ACPI_STATE_D0); + handle = ata_ap_acpi_handle(ap); + if (handle && state.event == PM_EVENT_ON) + acpi_bus_set_power(handle, ACPI_STATE_D0); ata_for_each_dev(dev, &ap->link, ENABLED) { - if (dev->acpi_handle) - acpi_bus_set_power(dev->acpi_handle, - state.event == PM_EVENT_ON ? - ACPI_STATE_D0 : ACPI_STATE_D3); + handle = ata_dev_acpi_handle(dev); + if (!handle) + continue; + + if (state.event != PM_EVENT_ON) { + acpi_state = acpi_pm_device_sleep_state( + &dev->sdev->sdev_gendev, NULL, ACPI_STATE_D3); + if (acpi_state > 0) + acpi_bus_set_power(handle, acpi_state); + /* TBD: need to check if it's runtime pm request */ + acpi_pm_device_run_wake( + &dev->sdev->sdev_gendev, true); + } else { + /* Ditto */ + acpi_pm_device_run_wake( + &dev->sdev->sdev_gendev, false); + acpi_bus_set_power(handle, ACPI_STATE_D0); + } } - if (state.event != PM_EVENT_ON) - acpi_bus_set_power(ap->acpi_handle, ACPI_STATE_D3); + + handle = ata_ap_acpi_handle(ap); + if (handle && state.event != PM_EVENT_ON) + acpi_bus_set_power(handle, ACPI_STATE_D3); } /** @@ -948,7 +910,7 @@ int ata_acpi_on_devcfg(struct ata_device *dev) int nr_executed = 0; int rc; - if (!dev->acpi_handle) + if (!ata_dev_acpi_handle(dev)) return 0; /* do we need to do _GTF? */ @@ -994,7 +956,6 @@ int ata_acpi_on_devcfg(struct ata_device *dev) } ata_dev_warn(dev, "ACPI: failed the second time, disabled\n"); - dev->acpi_handle = NULL; /* We can safely continue if no _GTF command has been executed * and port is not frozen. @@ -1018,3 +979,218 @@ void ata_acpi_on_disable(struct ata_device *dev) { ata_acpi_clear_gtf(dev); } + +static void ata_acpi_wake_dev(acpi_handle handle, u32 event, void *context) +{ + struct ata_device *ata_dev = context; + + if (event == ACPI_NOTIFY_DEVICE_WAKE && ata_dev && + pm_runtime_suspended(&ata_dev->sdev->sdev_gendev)) + scsi_autopm_get_device(ata_dev->sdev); +} + +static void ata_acpi_add_pm_notifier(struct ata_device *dev) +{ + struct acpi_device *acpi_dev; + acpi_handle handle; + acpi_status status; + + handle = ata_dev_acpi_handle(dev); + if (!handle) + return; + + status = acpi_bus_get_device(handle, &acpi_dev); + if (ACPI_FAILURE(status)) + return; + + if (dev->sdev->can_power_off) { + acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + ata_acpi_wake_dev, dev); + device_set_run_wake(&dev->sdev->sdev_gendev, true); + } +} + +static void ata_acpi_remove_pm_notifier(struct ata_device *dev) +{ + struct acpi_device *acpi_dev; + acpi_handle handle; + acpi_status status; + + handle = ata_dev_acpi_handle(dev); + if (!handle) + return; + + status = acpi_bus_get_device(handle, &acpi_dev); + if (ACPI_FAILURE(status)) + return; + + if (dev->sdev->can_power_off) { + device_set_run_wake(&dev->sdev->sdev_gendev, false); + acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + ata_acpi_wake_dev); + } +} + +static void ata_acpi_register_power_resource(struct ata_device *dev) +{ + struct scsi_device *sdev = dev->sdev; + acpi_handle handle; + struct device *device; + + handle = ata_dev_acpi_handle(dev); + if (!handle) + return; + + device = &sdev->sdev_gendev; + + acpi_power_resource_register_device(device, handle); +} + +static void ata_acpi_unregister_power_resource(struct ata_device *dev) +{ + struct scsi_device *sdev = dev->sdev; + acpi_handle handle; + struct device *device; + + handle = ata_dev_acpi_handle(dev); + if (!handle) + return; + + device = &sdev->sdev_gendev; + + acpi_power_resource_unregister_device(device, handle); +} + +void ata_acpi_bind(struct ata_device *dev) +{ + ata_acpi_add_pm_notifier(dev); + ata_acpi_register_power_resource(dev); +} + +void ata_acpi_unbind(struct ata_device *dev) +{ + ata_acpi_remove_pm_notifier(dev); + ata_acpi_unregister_power_resource(dev); +} + +static int compat_pci_ata(struct ata_port *ap) +{ + struct device *dev = ap->tdev.parent; + struct pci_dev *pdev; + + if (!is_pci_dev(dev)) + return 0; + + pdev = to_pci_dev(dev); + + if ((pdev->class >> 8) != PCI_CLASS_STORAGE_SATA && + (pdev->class >> 8) != PCI_CLASS_STORAGE_IDE) + return 0; + + return 1; +} + +static int ata_acpi_bind_host(struct ata_port *ap, acpi_handle *handle) +{ + if (ap->flags & ATA_FLAG_ACPI_SATA) + return -ENODEV; + + *handle = acpi_get_child(DEVICE_ACPI_HANDLE(ap->tdev.parent), + ap->port_no); + + if (!*handle) + return -ENODEV; + + return 0; +} + +static int ata_acpi_bind_device(struct ata_port *ap, struct scsi_device *sdev, + acpi_handle *handle) +{ + struct ata_device *ata_dev; + acpi_status status; + struct acpi_device *acpi_dev; + struct acpi_device_power_state *states; + + if (ap->flags & ATA_FLAG_ACPI_SATA) + ata_dev = &ap->link.device[sdev->channel]; + else + ata_dev = &ap->link.device[sdev->id]; + + *handle = ata_dev_acpi_handle(ata_dev); + + if (!*handle) + return -ENODEV; + + status = acpi_bus_get_device(*handle, &acpi_dev); + if (ACPI_FAILURE(status)) + return 0; + + /* + * If firmware has _PS3 or _PR3 for this device, + * and this ata ODD device support device attention, + * it means this device can be powered off + */ + states = acpi_dev->power.states; + if ((states[ACPI_STATE_D3_HOT].flags.valid || + states[ACPI_STATE_D3_COLD].flags.explicit_set) && + ata_dev->flags & ATA_DFLAG_DA) + sdev->can_power_off = 1; + + return 0; +} + +static int is_ata_port(const struct device *dev) +{ + return dev->type == &ata_port_type; +} + +static struct ata_port *dev_to_ata_port(struct device *dev) +{ + while (!is_ata_port(dev)) { + if (!dev->parent) + return NULL; + dev = dev->parent; + } + return to_ata_port(dev); +} + +static int ata_acpi_find_device(struct device *dev, acpi_handle *handle) +{ + struct ata_port *ap = dev_to_ata_port(dev); + + if (!ap) + return -ENODEV; + + if (!compat_pci_ata(ap)) + return -ENODEV; + + if (scsi_is_host_device(dev)) + return ata_acpi_bind_host(ap, handle); + else if (scsi_is_sdev_device(dev)) { + struct scsi_device *sdev = to_scsi_device(dev); + + return ata_acpi_bind_device(ap, sdev, handle); + } else + return -ENODEV; +} + +static int ata_acpi_find_dummy(struct device *dev, acpi_handle *handle) +{ + return -ENODEV; +} + +static struct acpi_bus_type ata_acpi_bus = { + .find_bridge = ata_acpi_find_dummy, + .find_device = ata_acpi_find_device, +}; + +int ata_acpi_register(void) +{ + return scsi_register_acpi_bus_type(&ata_acpi_bus); +} + +void ata_acpi_unregister(void) +{ + scsi_unregister_acpi_bus_type(&ata_acpi_bus); +} |