diff options
Diffstat (limited to 'drivers/usb/core/usb-acpi.c')
| -rw-r--r-- | drivers/usb/core/usb-acpi.c | 199 | 
1 files changed, 154 insertions, 45 deletions
diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 8947b203d5a..0ef7d42d8ab 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -19,20 +19,91 @@  #include "usb.h" -static int usb_acpi_check_upc(struct usb_device *udev, acpi_handle handle) +/** + * 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)  {  	acpi_status status;  	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };  	union acpi_object *upc; +	struct acpi_pld pld;  	int ret = 0; -	status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer); - +	/* +	 * 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);  	if (ACPI_FAILURE(status))  		return -ENODEV; +	status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);  	upc = buffer.pointer; -  	if (!upc || (upc->type != ACPI_TYPE_PACKAGE)  		|| upc->package.count != 4) {  		ret = -EINVAL; @@ -40,69 +111,107 @@ static int usb_acpi_check_upc(struct usb_device *udev, acpi_handle handle)  	}  	if (upc->package.elements[0].integer.value) -		udev->removable = USB_DEVICE_REMOVABLE; -	else -		udev->removable = USB_DEVICE_FIXED; +		if (pld.user_visible) +			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); +	else if (!pld.user_visible) +		usb_set_hub_port_connect_type(hdev, port1, USB_PORT_NOT_USED);  out:  	kfree(upc);  	return ret;  } -static int usb_acpi_check_pld(struct usb_device *udev, acpi_handle handle) -{ -	acpi_status status; -	struct acpi_pld pld; - -	status = acpi_get_physical_device_location(handle, &pld); - -	if (ACPI_FAILURE(status)) -		return -ENODEV; - -	if (pld.user_visible) -		udev->removable = USB_DEVICE_REMOVABLE; -	else -		udev->removable = USB_DEVICE_FIXED; - -	return 0; -} -  static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)  {  	struct usb_device *udev; -	struct device *parent;  	acpi_handle *parent_handle; +	int port_num; -	if (!is_usb_device(dev)) -		return -ENODEV; +	/* +	 * 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); +		if (udev->parent) { +			enum usb_port_connect_type type; -	udev = to_usb_device(dev); -	parent = dev->parent; -	parent_handle = DEVICE_ACPI_HANDLE(parent); +			/* +			 * 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; +			} -	if (!parent_handle) -		return -ENODEV; +			return -ENODEV; +		} -	*handle = acpi_get_child(parent_handle, udev->portnum); +		/* root hub's parent is the usb hcd. */ +		parent_handle = DEVICE_ACPI_HANDLE(dev->parent); +		*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); -	if (!*handle) -		return -ENODEV; +		/* +		 * 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) { +			*handle = acpi_get_child(DEVICE_ACPI_HANDLE(&udev->dev), +				port_num); +			if (!*handle) +				return -ENODEV; +		} else { +			parent_handle = +				usb_get_hub_port_acpi_handle(udev->parent, +				udev->portnum); +			if (!parent_handle) +				return -ENODEV; -	/* -	 * PLD will tell us whether a port is removable to the user or -	 * not. If we don't get an answer from PLD (it's not present -	 * or it's malformed) then try to infer it from UPC. If a -	 * device isn't connectable then it's probably not removable. -	 */ -	if (usb_acpi_check_pld(udev, *handle) != 0) -		usb_acpi_check_upc(udev, *handle); +			*handle = acpi_get_child(parent_handle,	port_num); +			if (!*handle) +				return -ENODEV; +		} +		usb_acpi_check_port_connect_type(udev, *handle, port_num); +	} else +		return -ENODEV;  	return 0;  }  static struct acpi_bus_type usb_acpi_bus = {  	.bus = &usb_bus_type, -	.find_bridge = NULL, +	.find_bridge = usb_acpi_find_device,  	.find_device = usb_acpi_find_device,  };  |