]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/ata/libata-acpi.c
libata-acpi: fix up for acpi_pm_device_sleep_state API
[karo-tx-linux.git] / drivers / ata / libata-acpi.c
index bb7c5f1085ccdfa997671520afc87c4f887eed39..902b5a457170958f4d805ae30185cee170e6b6e9 100644 (file)
@@ -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)
@@ -228,56 +220,6 @@ static const struct acpi_dock_ops ata_acpi_ap_dock_ops = {
        .uevent = ata_acpi_ap_uevent,
 };
 
-/**
- * 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);
+}