usb/acpi: Use ACPI methods to power off ports.
authorLan Tianyu <tianyu.lan@intel.com>
Wed, 5 Sep 2012 05:44:36 +0000 (13:44 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 10 Sep 2012 20:04:01 +0000 (13:04 -0700)
Upcoming Intel systems will have an ACPI method to control whether a USB
port can be completely powered off.  The implication of powering off a
USB port is that the device and host sees a physical disconnect, and
subsequent port connections and remote wakeups will be lost.

Add a new function, usb_acpi_power_manageable(), that can be used to
find whether the usb port has ACPI power resources that can be used to
power on and off the port on these machines. Also add a new function
called usb_acpi_set_power_state() that controls the port power via these
ACPI methods.

When the USB core calls into the xHCI hub driver to power off a port,
check whether the port can be completely powered off via this new ACPI
mechanism.  If so, call into these new ACPI methods.  Also use the ACPI
methods when the USB core asks to power on a port.

Signed-off-by: Lan Tianyu <tianyu.lan@intel.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/usb-acpi.c
drivers/usb/host/xhci-hub.c
include/linux/usb.h

index 404d86a..0ef7d42 100644 (file)
 
 #include "usb.h"
 
+/**
+ * usb_acpi_power_manageable - check whether usb port has
+ * acpi power resource.
+ * @hdev: USB device belonging to the usb hub
+ * @index: port index based zero
+ *
+ * Return true if the port has acpi power resource and false if no.
+ */
+bool usb_acpi_power_manageable(struct usb_device *hdev, int index)
+{
+       acpi_handle port_handle;
+       int port1 = index + 1;
+
+       port_handle = usb_get_hub_port_acpi_handle(hdev,
+               port1);
+       if (port_handle)
+               return acpi_bus_power_manageable(port_handle);
+       else
+               return false;
+}
+EXPORT_SYMBOL_GPL(usb_acpi_power_manageable);
+
+/**
+ * usb_acpi_set_power_state - control usb port's power via acpi power
+ * resource
+ * @hdev: USB device belonging to the usb hub
+ * @index: port index based zero
+ * @enable: power state expected to be set
+ *
+ * Notice to use usb_acpi_power_manageable() to check whether the usb port
+ * has acpi power resource before invoking this function.
+ *
+ * Returns 0 on success, else negative errno.
+ */
+int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable)
+{
+       acpi_handle port_handle;
+       unsigned char state;
+       int port1 = index + 1;
+       int error = -EINVAL;
+
+       port_handle = (acpi_handle)usb_get_hub_port_acpi_handle(hdev,
+               port1);
+       if (!port_handle)
+               return error;
+
+       if (enable)
+               state = ACPI_STATE_D0;
+       else
+               state = ACPI_STATE_D3_COLD;
+
+       error = acpi_bus_set_power(port_handle, state);
+       if (!error)
+               dev_dbg(&hdev->dev, "The power of hub port %d was set to %d\n",
+                       port1, enable);
+       else
+               dev_dbg(&hdev->dev, "The power of hub port failed to be set\n");
+
+       return error;
+}
+EXPORT_SYMBOL_GPL(usb_acpi_set_power_state);
+
 static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
        acpi_handle handle, int port1)
 {
index 03032b3..630e9e6 100644 (file)
@@ -766,6 +766,12 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
 
                        temp = xhci_readl(xhci, port_array[wIndex]);
                        xhci_dbg(xhci, "set port power, actual port %d status  = 0x%x\n", wIndex, temp);
+
+                       temp = usb_acpi_power_manageable(hcd->self.root_hub,
+                                       wIndex);
+                       if (temp)
+                               usb_acpi_set_power_state(hcd->self.root_hub,
+                                               wIndex, true);
                        break;
                case USB_PORT_FEAT_RESET:
                        temp = (temp | PORT_RESET);
@@ -868,6 +874,12 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
                case USB_PORT_FEAT_POWER:
                        xhci_writel(xhci, temp & ~PORT_POWER,
                                port_array[wIndex]);
+
+                       temp = usb_acpi_power_manageable(hcd->self.root_hub,
+                                       wIndex);
+                       if (temp)
+                               usb_acpi_set_power_state(hcd->self.root_hub,
+                                               wIndex, false);
                        break;
                default:
                        goto error;
index e0084a1..07915a3 100644 (file)
@@ -602,6 +602,16 @@ extern int usb_lock_device_for_reset(struct usb_device *udev,
 extern int usb_reset_device(struct usb_device *dev);
 extern void usb_queue_reset_device(struct usb_interface *dev);
 
+#ifdef CONFIG_ACPI
+extern int usb_acpi_set_power_state(struct usb_device *hdev, int index,
+       bool enable);
+extern bool usb_acpi_power_manageable(struct usb_device *hdev, int index);
+#else
+static inline int usb_acpi_set_power_state(struct usb_device *hdev, int index,
+       bool enable) { return 0; }
+static inline bool usb_acpi_power_manageable(struct usb_device *hdev, int index)
+       { return true; }
+#endif
 
 /* USB autosuspend and autoresume */
 #ifdef CONFIG_USB_SUSPEND