usb-acpi.c 5.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * USB-ACPI glue code
 *
 * Copyright 2012 Red Hat <mjg@redhat.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 2.
 *
 */
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/acpi.h>
#include <linux/pci.h>
18
#include <linux/usb/hcd.h>
19 20 21 22
#include <acpi/acpi_bus.h>

#include "usb.h"

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
/**
 * 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);

85 86
static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
	acpi_handle handle, int port1)
87 88 89 90
{
	acpi_status status;
	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
	union acpi_object *upc;
91
	struct acpi_pld_info *pld;
92 93
	int ret = 0;

94 95 96 97 98 99 100 101 102
	/*
	 * Accoding to ACPI Spec 9.13. PLD indicates whether usb port is
	 * user visible and _UPC indicates whether it is connectable. If
	 * the port was visible and connectable, it could be freely connected
	 * and disconnected with USB devices. If no visible and connectable,
	 * a usb device is directly hard-wired to the port. If no visible and
	 * no connectable, the port would be not used.
	 */
	status = acpi_get_physical_device_location(handle, &pld);
103 104 105
	if (ACPI_FAILURE(status))
		return -ENODEV;

106
	status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
107 108 109 110 111 112 113 114
	upc = buffer.pointer;
	if (!upc || (upc->type != ACPI_TYPE_PACKAGE)
		|| upc->package.count != 4) {
		ret = -EINVAL;
		goto out;
	}

	if (upc->package.elements[0].integer.value)
115
		if (pld->user_visible)
116 117 118 119 120
			usb_set_hub_port_connect_type(hdev, port1,
				USB_PORT_CONNECT_TYPE_HOT_PLUG);
		else
			usb_set_hub_port_connect_type(hdev, port1,
				USB_PORT_CONNECT_TYPE_HARD_WIRED);
121
	else if (!pld->user_visible)
122
		usb_set_hub_port_connect_type(hdev, port1, USB_PORT_NOT_USED);
123 124

out:
125
	ACPI_FREE(pld);
126 127 128 129
	kfree(upc);
	return ret;
}

130 131 132 133
static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)
{
	struct usb_device *udev;
	acpi_handle *parent_handle;
134
	int port_num;
135

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
	/*
	 * In the ACPI DSDT table, only usb root hub and usb ports are
	 * acpi device nodes. The hierarchy like following.
	 * Device (EHC1)
	 *	Device (HUBN)
	 *		Device (PR01)
	 *			Device (PR11)
	 *			Device (PR12)
	 *			Device (PR13)
	 *			...
	 * So all binding process is divided into two parts. binding
	 * root hub and usb ports.
	 */
	if (is_usb_device(dev)) {
		udev = to_usb_device(dev);
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
		if (udev->parent) {
			enum usb_port_connect_type type;

			/*
			 * According usb port's connect type to set usb device's
			 * removability.
			 */
			type = usb_get_hub_port_connect_type(udev->parent,
				udev->portnum);
			switch (type) {
			case USB_PORT_CONNECT_TYPE_HOT_PLUG:
				udev->removable = USB_DEVICE_REMOVABLE;
				break;
			case USB_PORT_CONNECT_TYPE_HARD_WIRED:
				udev->removable = USB_DEVICE_FIXED;
				break;
			default:
				udev->removable = USB_DEVICE_REMOVABLE_UNKNOWN;
				break;
			}

172
			return -ENODEV;
173 174
		}

175
		/* root hub's parent is the usb hcd. */
176
		parent_handle = ACPI_HANDLE(dev->parent);
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
		*handle = acpi_get_child(parent_handle, udev->portnum);
		if (!*handle)
			return -ENODEV;
		return 0;
	} else if (is_usb_port(dev)) {
		sscanf(dev_name(dev), "port%d", &port_num);
		/* Get the struct usb_device point of port's hub */
		udev = to_usb_device(dev->parent->parent);

		/*
		 * The root hub ports' parent is the root hub. The non-root-hub
		 * ports' parent is the parent hub port which the hub is
		 * connected to.
		 */
		if (!udev->parent) {
192 193 194 195
			struct usb_hcd *hcd = bus_to_hcd(udev->bus);
			int raw_port_num;

			raw_port_num = usb_hcd_find_raw_port_number(hcd,
196
				port_num);
197
			*handle = acpi_get_child(ACPI_HANDLE(&udev->dev),
198
				raw_port_num);
199 200 201 202 203 204 205 206 207 208 209 210 211
			if (!*handle)
				return -ENODEV;
		} else {
			parent_handle =
				usb_get_hub_port_acpi_handle(udev->parent,
				udev->portnum);
			if (!parent_handle)
				return -ENODEV;

			*handle = acpi_get_child(parent_handle,	port_num);
			if (!*handle)
				return -ENODEV;
		}
212
		usb_acpi_check_port_connect_type(udev, *handle, port_num);
213
	} else
214 215 216 217 218
		return -ENODEV;

	return 0;
}

219 220 221 222 223
static bool usb_acpi_bus_match(struct device *dev)
{
	return is_usb_device(dev) || is_usb_port(dev);
}

224
static struct acpi_bus_type usb_acpi_bus = {
225 226
	.name = "USB",
	.match = usb_acpi_bus_match,
227 228 229 230 231 232 233 234 235 236 237 238
	.find_device = usb_acpi_find_device,
};

int usb_acpi_register(void)
{
	return register_acpi_bus_type(&usb_acpi_bus);
}

void usb_acpi_unregister(void)
{
	unregister_acpi_bus_type(&usb_acpi_bus);
}