2012-05-11 12:08:27 +04:00
/*
* 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>
2013-03-19 12:48:13 +04:00
# include <linux/usb/hcd.h>
2012-05-11 12:08:27 +04:00
2014-05-21 05:08:17 +04:00
# include "hub.h"
2012-05-11 12:08:27 +04:00
2012-09-05 09:44:36 +04:00
/**
* 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 )
{
2014-05-21 05:08:17 +04:00
struct usb_hub * hub = usb_hub_to_struct_hub ( hdev ) ;
struct usb_port * port_dev ;
2012-09-05 09:44:36 +04:00
acpi_handle port_handle ;
unsigned char state ;
int port1 = index + 1 ;
int error = - EINVAL ;
2014-05-21 05:08:17 +04:00
if ( ! hub )
return - ENODEV ;
port_dev = hub - > ports [ port1 - 1 ] ;
port_handle = ( acpi_handle ) usb_get_hub_port_acpi_handle ( hdev , port1 ) ;
2012-09-05 09:44:36 +04:00
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 )
2014-05-21 05:08:17 +04:00
dev_dbg ( & port_dev - > dev , " acpi: power was set to %d \n " , enable ) ;
2012-09-05 09:44:36 +04:00
else
2014-05-21 05:08:17 +04:00
dev_dbg ( & port_dev - > dev , " acpi: power failed to be set \n " ) ;
2012-09-05 09:44:36 +04:00
return error ;
}
EXPORT_SYMBOL_GPL ( usb_acpi_set_power_state ) ;
2014-05-21 05:08:40 +04:00
static enum usb_port_connect_type usb_acpi_get_connect_type ( acpi_handle handle ,
struct acpi_pld_info * pld )
2012-05-11 12:08:28 +04:00
{
2014-05-21 05:08:17 +04:00
enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN ;
2012-05-11 12:08:28 +04:00
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
2014-05-21 05:08:17 +04:00
union acpi_object * upc ;
acpi_status status ;
2012-09-05 09:44:34 +04:00
/*
2014-01-04 09:54:41 +04:00
* According to ACPI Spec 9.13 . PLD indicates whether usb port is
2012-09-05 09:44:34 +04:00
* 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_evaluate_object ( handle , " _UPC " , NULL , & buffer ) ;
2012-05-11 12:08:28 +04:00
upc = buffer . pointer ;
if ( ! upc | | ( upc - > type ! = ACPI_TYPE_PACKAGE )
| | upc - > package . count ! = 4 ) {
goto out ;
}
if ( upc - > package . elements [ 0 ] . integer . value )
2012-10-08 02:14:06 +04:00
if ( pld - > user_visible )
2014-05-21 05:08:17 +04:00
connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG ;
2012-09-05 09:44:34 +04:00
else
2014-05-21 05:08:17 +04:00
connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED ;
2012-10-08 02:14:06 +04:00
else if ( ! pld - > user_visible )
2014-05-21 05:08:17 +04:00
connect_type = USB_PORT_NOT_USED ;
2012-05-11 12:08:28 +04:00
out :
kfree ( upc ) ;
2014-05-21 05:08:40 +04:00
return connect_type ;
2012-05-11 12:08:28 +04:00
}
2014-05-21 05:08:40 +04:00
/*
* Private to usb - acpi , all the core needs to know is that
* port_dev - > location is non - zero when it has been set by the firmware .
*/
# define USB_ACPI_LOCATION_VALID (1 << 31)
2017-06-02 16:36:26 +03:00
static struct acpi_device * usb_acpi_find_port ( struct acpi_device * parent ,
int raw )
{
struct acpi_device * adev ;
if ( ! parent )
return NULL ;
list_for_each_entry ( adev , & parent - > children , node ) {
if ( acpi_device_adr ( adev ) = = raw )
return adev ;
}
return acpi_find_child_device ( parent , raw , false ) ;
}
2013-11-29 19:27:34 +04:00
static struct acpi_device * usb_acpi_find_companion ( struct device * dev )
2012-05-11 12:08:27 +04:00
{
struct usb_device * udev ;
2014-05-21 05:08:22 +04:00
struct acpi_device * adev ;
2012-05-11 12:08:27 +04:00
acpi_handle * parent_handle ;
2012-09-05 09:44:33 +04:00
/*
* 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 ) ;
2014-05-21 05:08:22 +04:00
if ( udev - > parent )
2013-11-29 19:27:34 +04:00
return NULL ;
2012-09-05 09:44:34 +04:00
2014-05-21 05:08:22 +04:00
/* root hub is only child (_ADR=0) under its parent, the HC */
adev = ACPI_COMPANION ( dev - > parent ) ;
return acpi_find_child_device ( adev , 0 , false ) ;
2012-09-05 09:44:33 +04:00
} else if ( is_usb_port ( dev ) ) {
2014-05-21 05:08:17 +04:00
struct usb_port * port_dev = to_usb_port ( dev ) ;
2014-05-21 05:08:22 +04:00
int port1 = port_dev - > portnum ;
2014-05-21 05:08:40 +04:00
struct acpi_pld_info * pld ;
acpi_handle * handle ;
acpi_status status ;
2013-11-29 19:27:34 +04:00
2012-09-05 09:44:33 +04:00
/* 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 ) {
2013-03-19 12:48:13 +04:00
struct usb_hcd * hcd = bus_to_hcd ( udev - > bus ) ;
2014-05-21 05:08:17 +04:00
int raw ;
2013-03-19 12:48:13 +04:00
2014-05-21 05:08:17 +04:00
raw = usb_hcd_find_raw_port_number ( hcd , port1 ) ;
2017-06-02 16:36:26 +03:00
adev = usb_acpi_find_port ( ACPI_COMPANION ( & udev - > dev ) ,
raw ) ;
2013-11-29 19:27:34 +04:00
if ( ! adev )
return NULL ;
2012-09-05 09:44:33 +04:00
} else {
parent_handle =
usb_get_hub_port_acpi_handle ( udev - > parent ,
udev - > portnum ) ;
if ( ! parent_handle )
2013-11-29 19:27:34 +04:00
return NULL ;
2012-09-05 09:44:33 +04:00
2013-11-29 19:27:34 +04:00
acpi_bus_get_device ( parent_handle , & adev ) ;
2017-06-02 16:36:26 +03:00
adev = usb_acpi_find_port ( adev , port1 ) ;
2013-11-29 19:27:34 +04:00
if ( ! adev )
return NULL ;
2012-09-05 09:44:33 +04:00
}
2014-05-21 05:08:40 +04:00
handle = adev - > handle ;
status = acpi_get_physical_device_location ( handle , & pld ) ;
if ( ACPI_FAILURE ( status ) | | ! pld )
return adev ;
port_dev - > location = USB_ACPI_LOCATION_VALID
| pld - > group_token < < 8 | pld - > group_position ;
port_dev - > connect_type = usb_acpi_get_connect_type ( handle , pld ) ;
ACPI_FREE ( pld ) ;
2013-11-29 19:27:34 +04:00
return adev ;
}
2012-05-11 12:08:27 +04:00
2013-11-29 19:27:34 +04:00
return NULL ;
2012-05-11 12:08:27 +04:00
}
2013-03-04 01:35:20 +04:00
static bool usb_acpi_bus_match ( struct device * dev )
{
return is_usb_device ( dev ) | | is_usb_port ( dev ) ;
}
2012-05-11 12:08:27 +04:00
static struct acpi_bus_type usb_acpi_bus = {
2013-03-04 01:35:20 +04:00
. name = " USB " ,
. match = usb_acpi_bus_match ,
2013-11-29 19:27:34 +04:00
. find_companion = usb_acpi_find_companion ,
2012-05-11 12:08:27 +04:00
} ;
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 ) ;
}