2005-03-19 02:45:35 +03:00
/*
* Link physical devices with ACPI devices support
*
* Copyright ( c ) 2005 David Shaohua Li < shaohua . li @ intel . com >
* Copyright ( c ) 2005 Intel Corp .
*
* This file is released under the GPLv2 .
*/
# include <linux/init.h>
# include <linux/list.h>
# include <linux/device.h>
# include <linux/rwsem.h>
# include <linux/acpi.h>
# define ACPI_GLUE_DEBUG 0
# if ACPI_GLUE_DEBUG
# define DBG(x...) printk(PREFIX x)
# else
2007-07-09 22:33:14 +04:00
# define DBG(x...) do { } while(0)
2005-03-19 02:45:35 +03:00
# endif
static LIST_HEAD ( bus_type_list ) ;
static DECLARE_RWSEM ( bus_type_sem ) ;
int register_acpi_bus_type ( struct acpi_bus_type * type )
{
if ( acpi_disabled )
return - ENODEV ;
if ( type & & type - > bus & & type - > find_device ) {
down_write ( & bus_type_sem ) ;
list_add_tail ( & type - > list , & bus_type_list ) ;
up_write ( & bus_type_sem ) ;
2005-08-05 08:44:28 +04:00
printk ( KERN_INFO PREFIX " bus type %s registered \n " ,
type - > bus - > name ) ;
2005-03-19 02:45:35 +03:00
return 0 ;
}
return - ENODEV ;
}
int unregister_acpi_bus_type ( struct acpi_bus_type * type )
{
if ( acpi_disabled )
return 0 ;
if ( type ) {
down_write ( & bus_type_sem ) ;
list_del_init ( & type - > list ) ;
up_write ( & bus_type_sem ) ;
2005-08-05 08:44:28 +04:00
printk ( KERN_INFO PREFIX " ACPI bus type %s unregistered \n " ,
type - > bus - > name ) ;
2005-03-19 02:45:35 +03:00
return 0 ;
}
return - ENODEV ;
}
static struct acpi_bus_type * acpi_get_bus_type ( struct bus_type * type )
{
struct acpi_bus_type * tmp , * ret = NULL ;
down_read ( & bus_type_sem ) ;
list_for_each_entry ( tmp , & bus_type_list , list ) {
if ( tmp - > bus = = type ) {
ret = tmp ;
break ;
}
}
up_read ( & bus_type_sem ) ;
return ret ;
}
static int acpi_find_bridge_device ( struct device * dev , acpi_handle * handle )
{
struct acpi_bus_type * tmp ;
int ret = - ENODEV ;
down_read ( & bus_type_sem ) ;
list_for_each_entry ( tmp , & bus_type_list , list ) {
if ( tmp - > find_bridge & & ! tmp - > find_bridge ( dev , handle ) ) {
ret = 0 ;
break ;
}
}
up_read ( & bus_type_sem ) ;
return ret ;
}
/* Get device's handler per its address under its parent */
struct acpi_find_child {
acpi_handle handle ;
acpi_integer address ;
} ;
static acpi_status
do_acpi_find_child ( acpi_handle handle , u32 lvl , void * context , void * * rv )
{
acpi_status status ;
struct acpi_device_info * info ;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
2006-10-01 02:28:50 +04:00
struct acpi_find_child * find = context ;
2005-03-19 02:45:35 +03:00
status = acpi_get_object_info ( handle , & buffer ) ;
if ( ACPI_SUCCESS ( status ) ) {
info = buffer . pointer ;
if ( info - > address = = find - > address )
find - > handle = handle ;
2006-06-30 11:19:10 +04:00
kfree ( buffer . pointer ) ;
2005-03-19 02:45:35 +03:00
}
return AE_OK ;
}
acpi_handle acpi_get_child ( acpi_handle parent , acpi_integer address )
{
struct acpi_find_child find = { NULL , address } ;
if ( ! parent )
return NULL ;
acpi_walk_namespace ( ACPI_TYPE_DEVICE , parent ,
1 , do_acpi_find_child , & find , NULL ) ;
return find . handle ;
}
EXPORT_SYMBOL ( acpi_get_child ) ;
/* Link ACPI devices with physical devices */
static void acpi_glue_data_handler ( acpi_handle handle ,
u32 function , void * context )
{
/* we provide an empty handler */
}
/* Note: a success call will increase reference count by one */
struct device * acpi_get_physical_device ( acpi_handle handle )
{
acpi_status status ;
struct device * dev ;
status = acpi_get_data ( handle , acpi_glue_data_handler , ( void * * ) & dev ) ;
if ( ACPI_SUCCESS ( status ) )
return get_device ( dev ) ;
return NULL ;
}
EXPORT_SYMBOL ( acpi_get_physical_device ) ;
2008-08-01 19:37:54 +04:00
/* ToDo: When a PCI bridge is found, return the PCI device behind the bridge
* This should work in general , but did not on a Lenovo T61 for the
* graphics card . But this must be fixed when the PCI device is
* bound and the kernel device struct is attached to the acpi device
* Note : A success call will increase reference count by one
* Do call put_device ( dev ) on the returned device then
*/
struct device * acpi_get_physical_pci_device ( acpi_handle handle )
{
struct device * dev ;
long long device_id ;
acpi_status status ;
status =
acpi_evaluate_integer ( handle , " _ADR " , NULL , & device_id ) ;
if ( ACPI_FAILURE ( status ) )
return NULL ;
/* We need to attempt to determine whether the _ADR refers to a
PCI device or not . There ' s no terribly good way to do this ,
so the best we can hope for is to assume that there ' ll never
be a device in the host bridge */
if ( device_id > = 0x10000 ) {
/* It looks like a PCI device. Does it exist? */
dev = acpi_get_physical_device ( handle ) ;
} else {
/* It doesn't look like a PCI device. Does its parent
exist ? */
acpi_handle phandle ;
if ( acpi_get_parent ( handle , & phandle ) )
return NULL ;
dev = acpi_get_physical_device ( phandle ) ;
}
if ( ! dev )
return NULL ;
return dev ;
}
EXPORT_SYMBOL ( acpi_get_physical_pci_device ) ;
2005-03-19 02:45:35 +03:00
static int acpi_bind_one ( struct device * dev , acpi_handle handle )
{
2008-02-23 08:54:24 +03:00
struct acpi_device * acpi_dev ;
2005-03-19 02:45:35 +03:00
acpi_status status ;
2006-11-11 09:18:42 +03:00
if ( dev - > archdata . acpi_handle ) {
2008-05-02 08:02:41 +04:00
dev_warn ( dev , " Drivers changed 'acpi_handle' \n " ) ;
2005-03-19 02:45:35 +03:00
return - EINVAL ;
}
get_device ( dev ) ;
status = acpi_attach_data ( handle , acpi_glue_data_handler , dev ) ;
if ( ACPI_FAILURE ( status ) ) {
put_device ( dev ) ;
return - EINVAL ;
}
2006-11-11 09:18:42 +03:00
dev - > archdata . acpi_handle = handle ;
2005-03-19 02:45:35 +03:00
2008-02-23 08:54:24 +03:00
status = acpi_bus_get_device ( handle , & acpi_dev ) ;
if ( ! ACPI_FAILURE ( status ) ) {
int ret ;
ret = sysfs_create_link ( & dev - > kobj , & acpi_dev - > dev . kobj ,
" firmware_node " ) ;
ret = sysfs_create_link ( & acpi_dev - > dev . kobj , & dev - > kobj ,
" physical_node " ) ;
2008-10-04 02:23:49 +04:00
if ( acpi_dev - > wakeup . flags . valid ) {
2008-07-07 05:34:48 +04:00
device_set_wakeup_capable ( dev , true ) ;
2008-10-04 02:23:49 +04:00
device_set_wakeup_enable ( dev ,
acpi_dev - > wakeup . state . enabled ) ;
}
2008-02-23 08:54:24 +03:00
}
2005-03-19 02:45:35 +03:00
return 0 ;
}
static int acpi_unbind_one ( struct device * dev )
{
2006-11-11 09:18:42 +03:00
if ( ! dev - > archdata . acpi_handle )
2005-03-19 02:45:35 +03:00
return 0 ;
2006-11-11 09:18:42 +03:00
if ( dev = = acpi_get_physical_device ( dev - > archdata . acpi_handle ) ) {
2008-02-23 08:54:24 +03:00
struct acpi_device * acpi_dev ;
2005-03-19 02:45:35 +03:00
/* acpi_get_physical_device increase refcnt by one */
put_device ( dev ) ;
2008-02-23 08:54:24 +03:00
if ( ! acpi_bus_get_device ( dev - > archdata . acpi_handle ,
& acpi_dev ) ) {
sysfs_remove_link ( & dev - > kobj , " firmware_node " ) ;
sysfs_remove_link ( & acpi_dev - > dev . kobj , " physical_node " ) ;
}
2006-11-11 09:18:42 +03:00
acpi_detach_data ( dev - > archdata . acpi_handle ,
acpi_glue_data_handler ) ;
dev - > archdata . acpi_handle = NULL ;
2005-03-19 02:45:35 +03:00
/* acpi_bind_one increase refcnt by one */
put_device ( dev ) ;
} else {
2008-05-02 08:02:41 +04:00
dev_err ( dev , " Oops, 'acpi_handle' corrupt \n " ) ;
2005-03-19 02:45:35 +03:00
}
return 0 ;
}
static int acpi_platform_notify ( struct device * dev )
{
struct acpi_bus_type * type ;
acpi_handle handle ;
int ret = - EINVAL ;
if ( ! dev - > bus | | ! dev - > parent ) {
/* bridge devices genernally haven't bus or parent */
ret = acpi_find_bridge_device ( dev , & handle ) ;
goto end ;
}
type = acpi_get_bus_type ( dev - > bus ) ;
if ( ! type ) {
2009-01-26 01:40:56 +03:00
DBG ( " No ACPI bus support for %s \n " , dev_name ( dev ) ) ;
2005-03-19 02:45:35 +03:00
ret = - EINVAL ;
goto end ;
}
if ( ( ret = type - > find_device ( dev , & handle ) ) ! = 0 )
2009-01-26 01:40:56 +03:00
DBG ( " Can't get handler for %s \n " , dev_name ( dev ) ) ;
2005-03-19 02:45:35 +03:00
end :
if ( ! ret )
acpi_bind_one ( dev , handle ) ;
# if ACPI_GLUE_DEBUG
if ( ! ret ) {
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
2006-11-11 09:18:42 +03:00
acpi_get_name ( dev - > archdata . acpi_handle ,
ACPI_FULL_PATHNAME , & buffer ) ;
2009-01-26 01:40:56 +03:00
DBG ( " Device %s -> %s \n " , dev_name ( dev ) , ( char * ) buffer . pointer ) ;
2006-06-30 11:19:10 +04:00
kfree ( buffer . pointer ) ;
2005-03-19 02:45:35 +03:00
} else
2009-01-26 01:40:56 +03:00
DBG ( " Device %s -> No ACPI support \n " , dev_name ( dev ) ) ;
2005-03-19 02:45:35 +03:00
# endif
return ret ;
}
static int acpi_platform_notify_remove ( struct device * dev )
{
acpi_unbind_one ( dev ) ;
return 0 ;
}
static int __init init_acpi_device_notify ( void )
{
if ( acpi_disabled )
return 0 ;
if ( platform_notify | | platform_notify_remove ) {
printk ( KERN_ERR PREFIX " Can't use platform_notify \n " ) ;
return 0 ;
}
platform_notify = acpi_platform_notify ;
platform_notify_remove = acpi_platform_notify_remove ;
return 0 ;
}
arch_initcall ( init_acpi_device_notify ) ;