2006-07-10 01:22:28 +04:00
/*
* dock . c - ACPI dock station driver
*
* Copyright ( C ) 2006 Kristen Carlson Accardi < kristen . c . accardi @ intel . 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 ; either version 2 of the License , or ( at
* your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA .
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/notifier.h>
2006-12-05 01:49:43 +03:00
# include <linux/platform_device.h>
2006-10-18 21:55:46 +04:00
# include <linux/jiffies.h>
2007-03-27 09:38:49 +04:00
# include <linux/stddef.h>
2006-07-10 01:22:28 +04:00
# include <acpi/acpi_bus.h>
# include <acpi/acpi_drivers.h>
2007-02-13 07:50:02 +03:00
# define ACPI_DOCK_DRIVER_DESCRIPTION "ACPI Dock Station Driver"
2006-07-10 01:22:28 +04:00
2007-02-13 06:42:12 +03:00
ACPI_MODULE_NAME ( " dock " ) ;
2006-07-10 01:22:28 +04:00
MODULE_AUTHOR ( " Kristen Carlson Accardi " ) ;
2007-02-13 07:50:02 +03:00
MODULE_DESCRIPTION ( ACPI_DOCK_DRIVER_DESCRIPTION ) ;
2006-07-10 01:22:28 +04:00
MODULE_LICENSE ( " GPL " ) ;
2007-05-10 02:08:15 +04:00
static int immediate_undock = 1 ;
module_param ( immediate_undock , bool , 0644 ) ;
MODULE_PARM_DESC ( immediate_undock , " 1 (default) will cause the driver to "
" undock immediately when the undock button is pressed, 0 will cause "
" the driver to wait for userspace to write the undock sysfs file "
" before undocking " ) ;
2006-07-10 01:22:28 +04:00
static struct atomic_notifier_head dock_notifier_list ;
2007-05-10 02:07:04 +04:00
static struct platform_device * dock_device ;
2006-12-05 01:49:43 +03:00
static char dock_device_name [ ] = " dock " ;
2006-07-10 01:22:28 +04:00
struct dock_station {
acpi_handle handle ;
unsigned long last_dock_time ;
u32 flags ;
spinlock_t dd_lock ;
2006-10-30 22:18:45 +03:00
struct mutex hp_lock ;
2006-07-10 01:22:28 +04:00
struct list_head dependent_devices ;
struct list_head hotplug_devices ;
} ;
struct dock_dependent_device {
struct list_head list ;
struct list_head hotplug_list ;
acpi_handle handle ;
acpi_notify_handler handler ;
void * context ;
} ;
# define DOCK_DOCKING 0x00000001
2007-05-10 02:08:15 +04:00
# define DOCK_UNDOCKING 0x00000002
2006-08-02 01:59:19 +04:00
# define DOCK_EVENT 3
# define UNDOCK_EVENT 2
2006-07-10 01:22:28 +04:00
static struct dock_station * dock_station ;
/*****************************************************************************
* Dock Dependent device functions *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* alloc_dock_dependent_device - allocate and init a dependent device
* @ handle : the acpi_handle of the dependent device
*
* Allocate memory for a dependent device structure for a device referenced
* by the acpi handle
*/
static struct dock_dependent_device *
alloc_dock_dependent_device ( acpi_handle handle )
{
struct dock_dependent_device * dd ;
dd = kzalloc ( sizeof ( * dd ) , GFP_KERNEL ) ;
if ( dd ) {
dd - > handle = handle ;
INIT_LIST_HEAD ( & dd - > list ) ;
INIT_LIST_HEAD ( & dd - > hotplug_list ) ;
}
return dd ;
}
/**
* add_dock_dependent_device - associate a device with the dock station
* @ ds : The dock station
* @ dd : The dependent device
*
* Add the dependent device to the dock ' s dependent device list .
*/
static void
add_dock_dependent_device ( struct dock_station * ds ,
struct dock_dependent_device * dd )
{
spin_lock ( & ds - > dd_lock ) ;
list_add_tail ( & dd - > list , & ds - > dependent_devices ) ;
spin_unlock ( & ds - > dd_lock ) ;
}
/**
* dock_add_hotplug_device - associate a hotplug handler with the dock station
* @ ds : The dock station
* @ dd : The dependent device struct
*
* Add the dependent device to the dock ' s hotplug device list
*/
static void
dock_add_hotplug_device ( struct dock_station * ds ,
struct dock_dependent_device * dd )
{
2006-10-30 22:18:45 +03:00
mutex_lock ( & ds - > hp_lock ) ;
2006-07-10 01:22:28 +04:00
list_add_tail ( & dd - > hotplug_list , & ds - > hotplug_devices ) ;
2006-10-30 22:18:45 +03:00
mutex_unlock ( & ds - > hp_lock ) ;
2006-07-10 01:22:28 +04:00
}
/**
* dock_del_hotplug_device - remove a hotplug handler from the dock station
* @ ds : The dock station
* @ dd : the dependent device struct
*
* Delete the dependent device from the dock ' s hotplug device list
*/
static void
dock_del_hotplug_device ( struct dock_station * ds ,
struct dock_dependent_device * dd )
{
2006-10-30 22:18:45 +03:00
mutex_lock ( & ds - > hp_lock ) ;
2006-07-10 01:22:28 +04:00
list_del ( & dd - > hotplug_list ) ;
2006-10-30 22:18:45 +03:00
mutex_unlock ( & ds - > hp_lock ) ;
2006-07-10 01:22:28 +04:00
}
/**
* find_dock_dependent_device - get a device dependent on this dock
* @ ds : the dock station
* @ handle : the acpi_handle of the device we want
*
* iterate over the dependent device list for this dock . If the
* dependent device matches the handle , return .
*/
static struct dock_dependent_device *
find_dock_dependent_device ( struct dock_station * ds , acpi_handle handle )
{
struct dock_dependent_device * dd ;
spin_lock ( & ds - > dd_lock ) ;
list_for_each_entry ( dd , & ds - > dependent_devices , list ) {
if ( handle = = dd - > handle ) {
spin_unlock ( & ds - > dd_lock ) ;
return dd ;
}
}
spin_unlock ( & ds - > dd_lock ) ;
return NULL ;
}
/*****************************************************************************
* Dock functions *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* is_dock - see if a device is a dock station
* @ handle : acpi handle of the device
*
* If an acpi object has a _DCK method , then it is by definition a dock
* station , so return true .
*/
static int is_dock ( acpi_handle handle )
{
acpi_status status ;
acpi_handle tmp ;
status = acpi_get_handle ( handle , " _DCK " , & tmp ) ;
if ( ACPI_FAILURE ( status ) )
return 0 ;
return 1 ;
}
/**
* is_dock_device - see if a device is on a dock station
* @ handle : acpi handle of the device
*
* If this device is either the dock station itself ,
* or is a device dependent on the dock station , then it
* is a dock device
*/
int is_dock_device ( acpi_handle handle )
{
if ( ! dock_station )
return 0 ;
if ( is_dock ( handle ) | | find_dock_dependent_device ( dock_station , handle ) )
return 1 ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( is_dock_device ) ;
/**
* dock_present - see if the dock station is present .
* @ ds : the dock station
*
* execute the _STA method . note that present does not
* imply that we are docked .
*/
static int dock_present ( struct dock_station * ds )
{
unsigned long sta ;
acpi_status status ;
if ( ds ) {
status = acpi_evaluate_integer ( ds - > handle , " _STA " , NULL , & sta ) ;
if ( ACPI_SUCCESS ( status ) & & sta )
return 1 ;
}
return 0 ;
}
/**
* dock_create_acpi_device - add new devices to acpi
* @ handle - handle of the device to add
*
* This function will create a new acpi_device for the given
* handle if one does not exist already . This should cause
* acpi to scan for drivers for the given devices , and call
* matching driver ' s add routine .
*
* Returns a pointer to the acpi_device corresponding to the handle .
*/
static struct acpi_device * dock_create_acpi_device ( acpi_handle handle )
{
struct acpi_device * device = NULL ;
struct acpi_device * parent_device ;
acpi_handle parent ;
int ret ;
if ( acpi_bus_get_device ( handle , & device ) ) {
/*
* no device created for this object ,
* so we should create one .
*/
acpi_get_parent ( handle , & parent ) ;
if ( acpi_bus_get_device ( parent , & parent_device ) )
parent_device = NULL ;
ret = acpi_bus_add ( & device , parent_device , handle ,
ACPI_BUS_TYPE_DEVICE ) ;
if ( ret ) {
pr_debug ( " error adding bus, %x \n " ,
- ret ) ;
return NULL ;
}
}
return device ;
}
/**
* dock_remove_acpi_device - remove the acpi_device struct from acpi
* @ handle - the handle of the device to remove
*
* Tell acpi to remove the acpi_device . This should cause any loaded
* driver to have it ' s remove routine called .
*/
static void dock_remove_acpi_device ( acpi_handle handle )
{
struct acpi_device * device ;
int ret ;
if ( ! acpi_bus_get_device ( handle , & device ) ) {
ret = acpi_bus_trim ( device , 1 ) ;
if ( ret )
pr_debug ( " error removing bus, %x \n " , - ret ) ;
}
}
/**
* hotplug_dock_devices - insert or remove devices on the dock station
* @ ds : the dock station
* @ event : either bus check or eject request
*
* Some devices on the dock station need to have drivers called
* to perform hotplug operations after a dock event has occurred .
* Traverse the list of dock devices that have registered a
* hotplug handler , and call the handler .
*/
static void hotplug_dock_devices ( struct dock_station * ds , u32 event )
{
struct dock_dependent_device * dd ;
2006-10-30 22:18:45 +03:00
mutex_lock ( & ds - > hp_lock ) ;
2006-07-10 01:22:28 +04:00
/*
* First call driver specific hotplug functions
*/
list_for_each_entry ( dd , & ds - > hotplug_devices , hotplug_list ) {
if ( dd - > handler )
dd - > handler ( dd - > handle , event , dd - > context ) ;
}
/*
* Now make sure that an acpi_device is created for each
* dependent device , or removed if this is an eject request .
* This will cause acpi_drivers to be stopped / started if they
* exist
*/
list_for_each_entry ( dd , & ds - > dependent_devices , list ) {
if ( event = = ACPI_NOTIFY_EJECT_REQUEST )
dock_remove_acpi_device ( dd - > handle ) ;
else
dock_create_acpi_device ( dd - > handle ) ;
}
2006-10-30 22:18:45 +03:00
mutex_unlock ( & ds - > hp_lock ) ;
2006-07-10 01:22:28 +04:00
}
static void dock_event ( struct dock_station * ds , u32 event , int num )
{
2007-05-10 02:07:04 +04:00
struct device * dev = & dock_device - > dev ;
2007-08-11 00:10:32 +04:00
char event_string [ 13 ] ;
2007-05-10 02:10:22 +04:00
char * envp [ ] = { event_string , NULL } ;
if ( num = = UNDOCK_EVENT )
2007-08-11 00:10:32 +04:00
sprintf ( event_string , " EVENT=undock " ) ;
2007-05-10 02:10:22 +04:00
else
2007-08-11 00:10:32 +04:00
sprintf ( event_string , " EVENT=dock " ) ;
2007-05-10 02:10:22 +04:00
2006-08-02 01:59:19 +04:00
/*
2006-12-11 23:05:08 +03:00
* Indicate that the status of the dock station has
* changed .
2006-08-02 01:59:19 +04:00
*/
2007-05-10 02:10:22 +04:00
kobject_uevent_env ( & dev - > kobj , KOBJ_CHANGE , envp ) ;
2006-07-10 01:22:28 +04:00
}
/**
* eject_dock - respond to a dock eject request
* @ ds : the dock station
*
* This is called after _DCK is called , to execute the dock station ' s
* _EJ0 method .
*/
static void eject_dock ( struct dock_station * ds )
{
struct acpi_object_list arg_list ;
union acpi_object arg ;
acpi_status status ;
acpi_handle tmp ;
/* all dock devices should have _EJ0, but check anyway */
status = acpi_get_handle ( ds - > handle , " _EJ0 " , & tmp ) ;
if ( ACPI_FAILURE ( status ) ) {
pr_debug ( " No _EJ0 support for dock device \n " ) ;
return ;
}
arg_list . count = 1 ;
arg_list . pointer = & arg ;
arg . type = ACPI_TYPE_INTEGER ;
arg . integer . value = 1 ;
if ( ACPI_FAILURE ( acpi_evaluate_object ( ds - > handle , " _EJ0 " ,
& arg_list , NULL ) ) )
pr_debug ( " Failed to evaluate _EJ0! \n " ) ;
}
/**
* handle_dock - handle a dock event
* @ ds : the dock station
* @ dock : to dock , or undock - that is the question
*
* Execute the _DCK method in response to an acpi event
*/
static void handle_dock ( struct dock_station * ds , int dock )
{
acpi_status status ;
struct acpi_object_list arg_list ;
union acpi_object arg ;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
struct acpi_buffer name_buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
acpi_get_name ( ds - > handle , ACPI_FULL_PATHNAME , & name_buffer ) ;
2007-07-18 09:10:24 +04:00
printk ( KERN_INFO PREFIX " %s - %s \n " ,
( char * ) name_buffer . pointer , dock ? " docking " : " undocking " ) ;
2006-07-10 01:22:28 +04:00
/* _DCK method has one argument */
arg_list . count = 1 ;
arg_list . pointer = & arg ;
arg . type = ACPI_TYPE_INTEGER ;
arg . integer . value = dock ;
status = acpi_evaluate_object ( ds - > handle , " _DCK " , & arg_list , & buffer ) ;
if ( ACPI_FAILURE ( status ) )
2007-07-18 09:10:24 +04:00
printk ( KERN_ERR PREFIX " %s - failed to execute _DCK \n " ,
( char * ) name_buffer . pointer ) ;
2006-07-10 01:22:28 +04:00
kfree ( buffer . pointer ) ;
kfree ( name_buffer . pointer ) ;
}
static inline void dock ( struct dock_station * ds )
{
handle_dock ( ds , 1 ) ;
}
static inline void undock ( struct dock_station * ds )
{
handle_dock ( ds , 0 ) ;
}
static inline void begin_dock ( struct dock_station * ds )
{
ds - > flags | = DOCK_DOCKING ;
}
static inline void complete_dock ( struct dock_station * ds )
{
ds - > flags & = ~ ( DOCK_DOCKING ) ;
ds - > last_dock_time = jiffies ;
}
2007-05-10 02:08:15 +04:00
static inline void begin_undock ( struct dock_station * ds )
{
ds - > flags | = DOCK_UNDOCKING ;
}
static inline void complete_undock ( struct dock_station * ds )
{
ds - > flags & = ~ ( DOCK_UNDOCKING ) ;
}
2006-07-10 01:22:28 +04:00
/**
* dock_in_progress - see if we are in the middle of handling a dock event
* @ ds : the dock station
*
* Sometimes while docking , false dock events can be sent to the driver
* because good connections aren ' t made or some other reason . Ignore these
* if we are in the middle of doing something .
*/
static int dock_in_progress ( struct dock_station * ds )
{
if ( ( ds - > flags & DOCK_DOCKING ) | |
time_before ( jiffies , ( ds - > last_dock_time + HZ ) ) )
return 1 ;
return 0 ;
}
/**
* register_dock_notifier - add yourself to the dock notifier list
* @ nb : the callers notifier block
*
* If a driver wishes to be notified about dock events , they can
* use this function to put a notifier block on the dock notifier list .
* this notifier call chain will be called after a dock event , but
* before hotplugging any new devices .
*/
int register_dock_notifier ( struct notifier_block * nb )
{
2006-12-05 01:50:17 +03:00
if ( ! dock_station )
return - ENODEV ;
2006-07-10 01:22:28 +04:00
return atomic_notifier_chain_register ( & dock_notifier_list , nb ) ;
}
EXPORT_SYMBOL_GPL ( register_dock_notifier ) ;
/**
* unregister_dock_notifier - remove yourself from the dock notifier list
* @ nb : the callers notifier block
*/
void unregister_dock_notifier ( struct notifier_block * nb )
{
2006-12-05 01:50:17 +03:00
if ( ! dock_station )
return ;
2006-07-10 01:22:28 +04:00
atomic_notifier_chain_unregister ( & dock_notifier_list , nb ) ;
}
EXPORT_SYMBOL_GPL ( unregister_dock_notifier ) ;
/**
* register_hotplug_dock_device - register a hotplug function
* @ handle : the handle of the device
* @ handler : the acpi_notifier_handler to call after docking
* @ context : device specific data
*
* If a driver would like to perform a hotplug operation after a dock
* event , they can register an acpi_notifiy_handler to be called by
* the dock driver after _DCK is executed .
*/
int
register_hotplug_dock_device ( acpi_handle handle , acpi_notify_handler handler ,
void * context )
{
struct dock_dependent_device * dd ;
if ( ! dock_station )
return - ENODEV ;
/*
* make sure this handle is for a device dependent on the dock ,
* this would include the dock station itself
*/
dd = find_dock_dependent_device ( dock_station , handle ) ;
if ( dd ) {
dd - > handler = handler ;
dd - > context = context ;
dock_add_hotplug_device ( dock_station , dd ) ;
return 0 ;
}
return - EINVAL ;
}
EXPORT_SYMBOL_GPL ( register_hotplug_dock_device ) ;
/**
* unregister_hotplug_dock_device - remove yourself from the hotplug list
* @ handle : the acpi handle of the device
*/
void unregister_hotplug_dock_device ( acpi_handle handle )
{
struct dock_dependent_device * dd ;
if ( ! dock_station )
return ;
dd = find_dock_dependent_device ( dock_station , handle ) ;
if ( dd )
dock_del_hotplug_device ( dock_station , dd ) ;
}
EXPORT_SYMBOL_GPL ( unregister_hotplug_dock_device ) ;
2006-12-05 01:49:58 +03:00
/**
* handle_eject_request - handle an undock request checking for error conditions
*
* Check to make sure the dock device is still present , then undock and
* hotremove all the devices that may need removing .
*/
static int handle_eject_request ( struct dock_station * ds , u32 event )
{
if ( ! dock_present ( ds ) )
return - ENODEV ;
if ( dock_in_progress ( ds ) )
return - EBUSY ;
/*
* here we need to generate the undock
* event prior to actually doing the undock
* so that the device struct still exists .
*/
dock_event ( ds , event , UNDOCK_EVENT ) ;
hotplug_dock_devices ( ds , ACPI_NOTIFY_EJECT_REQUEST ) ;
undock ( ds ) ;
eject_dock ( ds ) ;
if ( dock_present ( ds ) ) {
printk ( KERN_ERR PREFIX " Unable to undock! \n " ) ;
return - EBUSY ;
}
2007-05-10 02:08:15 +04:00
complete_undock ( ds ) ;
2006-12-05 01:49:58 +03:00
return 0 ;
}
2006-07-10 01:22:28 +04:00
/**
* dock_notify - act upon an acpi dock notification
* @ handle : the dock station handle
* @ event : the acpi event
* @ data : our driver data struct
*
* If we are notified to dock , then check to see if the dock is
* present and then dock . Notify all drivers of the dock event ,
2006-12-05 01:49:58 +03:00
* and then hotplug and devices that may need hotplugging .
2006-07-10 01:22:28 +04:00
*/
static void dock_notify ( acpi_handle handle , u32 event , void * data )
{
2006-10-01 02:28:50 +04:00
struct dock_station * ds = data ;
2006-07-10 01:22:28 +04:00
switch ( event ) {
case ACPI_NOTIFY_BUS_CHECK :
if ( ! dock_in_progress ( ds ) & & dock_present ( ds ) ) {
begin_dock ( ds ) ;
dock ( ds ) ;
if ( ! dock_present ( ds ) ) {
printk ( KERN_ERR PREFIX " Unable to dock! \n " ) ;
break ;
}
atomic_notifier_call_chain ( & dock_notifier_list ,
event , NULL ) ;
hotplug_dock_devices ( ds , event ) ;
complete_dock ( ds ) ;
dock_event ( ds , event , DOCK_EVENT ) ;
}
break ;
case ACPI_NOTIFY_DEVICE_CHECK :
/*
* According to acpi spec 3.0 a , if a DEVICE_CHECK notification
* is sent and _DCK is present , it is assumed to mean an
* undock request . This notify routine will only be called
* for objects defining _DCK , so we will fall through to eject
* request here . However , we will pass an eject request through
* to the driver who wish to hotplug .
*/
case ACPI_NOTIFY_EJECT_REQUEST :
2007-05-10 02:08:15 +04:00
begin_undock ( ds ) ;
if ( immediate_undock )
handle_eject_request ( ds , event ) ;
else
dock_event ( ds , event , UNDOCK_EVENT ) ;
2006-07-10 01:22:28 +04:00
break ;
default :
printk ( KERN_ERR PREFIX " Unknown dock event %d \n " , event ) ;
}
}
/**
* find_dock_devices - find devices on the dock station
* @ handle : the handle of the device we are examining
* @ lvl : unused
* @ context : the dock station private data
* @ rv : unused
*
* This function is called by acpi_walk_namespace . It will
* check to see if an object has an _EJD method . If it does , then it
* will see if it is dependent on the dock station .
*/
static acpi_status
find_dock_devices ( acpi_handle handle , u32 lvl , void * context , void * * rv )
{
acpi_status status ;
2007-02-03 06:33:00 +03:00
acpi_handle tmp , parent ;
2006-10-01 02:28:50 +04:00
struct dock_station * ds = context ;
2006-07-10 01:22:28 +04:00
struct dock_dependent_device * dd ;
status = acpi_bus_get_ejd ( handle , & tmp ) ;
2007-02-03 06:33:00 +03:00
if ( ACPI_FAILURE ( status ) ) {
/* try the parent device as well */
status = acpi_get_parent ( handle , & parent ) ;
if ( ACPI_FAILURE ( status ) )
goto fdd_out ;
/* see if parent is dependent on dock */
status = acpi_bus_get_ejd ( parent , & tmp ) ;
if ( ACPI_FAILURE ( status ) )
goto fdd_out ;
}
2006-07-10 01:22:28 +04:00
if ( tmp = = ds - > handle ) {
dd = alloc_dock_dependent_device ( handle ) ;
if ( dd )
add_dock_dependent_device ( ds , dd ) ;
}
2007-02-03 06:33:00 +03:00
fdd_out :
2006-07-10 01:22:28 +04:00
return AE_OK ;
}
2006-12-05 01:49:58 +03:00
/*
* show_docked - read method for " docked " file in sysfs
*/
static ssize_t show_docked ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return snprintf ( buf , PAGE_SIZE , " %d \n " , dock_present ( dock_station ) ) ;
}
DEVICE_ATTR ( docked , S_IRUGO , show_docked , NULL ) ;
2007-05-10 02:08:15 +04:00
/*
* show_flags - read method for flags file in sysfs
*/
static ssize_t show_flags ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return snprintf ( buf , PAGE_SIZE , " %d \n " , dock_station - > flags ) ;
}
DEVICE_ATTR ( flags , S_IRUGO , show_flags , NULL ) ;
2006-12-05 01:49:58 +03:00
/*
* write_undock - write method for " undock " file in sysfs
*/
static ssize_t write_undock ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
int ret ;
if ( ! count )
return - EINVAL ;
ret = handle_eject_request ( dock_station , ACPI_NOTIFY_EJECT_REQUEST ) ;
return ret ? ret : count ;
}
DEVICE_ATTR ( undock , S_IWUSR , NULL , write_undock ) ;
2007-02-20 02:19:31 +03:00
/*
* show_dock_uid - read method for " uid " file in sysfs
*/
static ssize_t show_dock_uid ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
unsigned long lbuf ;
2007-05-10 02:04:24 +04:00
acpi_status status = acpi_evaluate_integer ( dock_station - > handle ,
" _UID " , NULL , & lbuf ) ;
if ( ACPI_FAILURE ( status ) )
2007-02-20 02:19:31 +03:00
return 0 ;
2007-05-10 02:04:24 +04:00
2007-02-20 02:19:31 +03:00
return snprintf ( buf , PAGE_SIZE , " %lx \n " , lbuf ) ;
}
DEVICE_ATTR ( uid , S_IRUGO , show_dock_uid , NULL ) ;
2006-07-10 01:22:28 +04:00
/**
* dock_add - add a new dock station
* @ handle : the dock station handle
*
* allocated and initialize a new dock station device . Find all devices
* that are on the dock station , and register for dock event notifications .
*/
static int dock_add ( acpi_handle handle )
{
int ret ;
acpi_status status ;
struct dock_dependent_device * dd ;
/* allocate & initialize the dock_station private data */
dock_station = kzalloc ( sizeof ( * dock_station ) , GFP_KERNEL ) ;
if ( ! dock_station )
return - ENOMEM ;
dock_station - > handle = handle ;
dock_station - > last_dock_time = jiffies - HZ ;
INIT_LIST_HEAD ( & dock_station - > dependent_devices ) ;
INIT_LIST_HEAD ( & dock_station - > hotplug_devices ) ;
spin_lock_init ( & dock_station - > dd_lock ) ;
2006-10-30 22:18:45 +03:00
mutex_init ( & dock_station - > hp_lock ) ;
2006-07-10 22:19:15 +04:00
ATOMIC_INIT_NOTIFIER_HEAD ( & dock_notifier_list ) ;
2006-07-10 01:22:28 +04:00
2006-12-05 01:49:43 +03:00
/* initialize platform device stuff */
2007-05-10 02:07:04 +04:00
dock_device =
platform_device_register_simple ( dock_device_name , 0 , NULL , 0 ) ;
if ( IS_ERR ( dock_device ) ) {
2006-12-05 01:49:43 +03:00
kfree ( dock_station ) ;
2007-05-10 02:05:48 +04:00
dock_station = NULL ;
2007-05-10 02:07:04 +04:00
return PTR_ERR ( dock_device ) ;
2006-12-05 01:49:43 +03:00
}
2007-05-10 02:08:15 +04:00
2007-05-10 02:09:12 +04:00
/* we want the dock device to send uevents */
dock_device - > dev . uevent_suppress = 0 ;
2007-05-10 02:07:04 +04:00
ret = device_create_file ( & dock_device - > dev , & dev_attr_docked ) ;
2006-12-05 01:49:58 +03:00
if ( ret ) {
printk ( " Error %d adding sysfs file \n " , ret ) ;
2007-05-10 02:07:04 +04:00
platform_device_unregister ( dock_device ) ;
2006-12-05 01:49:58 +03:00
kfree ( dock_station ) ;
2007-05-10 02:05:48 +04:00
dock_station = NULL ;
2006-12-05 01:49:58 +03:00
return ret ;
}
2007-05-10 02:07:04 +04:00
ret = device_create_file ( & dock_device - > dev , & dev_attr_undock ) ;
2006-12-05 01:49:58 +03:00
if ( ret ) {
printk ( " Error %d adding sysfs file \n " , ret ) ;
2007-05-10 02:07:04 +04:00
device_remove_file ( & dock_device - > dev , & dev_attr_docked ) ;
platform_device_unregister ( dock_device ) ;
2006-12-05 01:49:58 +03:00
kfree ( dock_station ) ;
2007-05-10 02:05:48 +04:00
dock_station = NULL ;
2006-12-05 01:49:58 +03:00
return ret ;
}
2007-05-10 02:07:04 +04:00
ret = device_create_file ( & dock_device - > dev , & dev_attr_uid ) ;
2007-02-20 02:19:31 +03:00
if ( ret ) {
printk ( " Error %d adding sysfs file \n " , ret ) ;
2007-05-10 02:07:04 +04:00
device_remove_file ( & dock_device - > dev , & dev_attr_docked ) ;
device_remove_file ( & dock_device - > dev , & dev_attr_undock ) ;
platform_device_unregister ( dock_device ) ;
2007-02-20 02:19:31 +03:00
kfree ( dock_station ) ;
2007-05-10 02:05:48 +04:00
dock_station = NULL ;
2007-02-20 02:19:31 +03:00
return ret ;
}
2007-05-10 02:08:15 +04:00
ret = device_create_file ( & dock_device - > dev , & dev_attr_flags ) ;
if ( ret ) {
printk ( " Error %d adding sysfs file \n " , ret ) ;
device_remove_file ( & dock_device - > dev , & dev_attr_docked ) ;
device_remove_file ( & dock_device - > dev , & dev_attr_undock ) ;
device_remove_file ( & dock_device - > dev , & dev_attr_uid ) ;
platform_device_unregister ( dock_device ) ;
kfree ( dock_station ) ;
dock_station = NULL ;
return ret ;
}
2006-12-05 01:49:43 +03:00
2006-07-10 01:22:28 +04:00
/* Find dependent devices */
acpi_walk_namespace ( ACPI_TYPE_DEVICE , ACPI_ROOT_OBJECT ,
ACPI_UINT32_MAX , find_dock_devices , dock_station ,
NULL ) ;
/* add the dock station as a device dependent on itself */
dd = alloc_dock_dependent_device ( handle ) ;
if ( ! dd ) {
kfree ( dock_station ) ;
2007-05-10 02:05:48 +04:00
dock_station = NULL ;
2006-12-05 01:49:43 +03:00
ret = - ENOMEM ;
goto dock_add_err_unregister ;
2006-07-10 01:22:28 +04:00
}
add_dock_dependent_device ( dock_station , dd ) ;
/* register for dock events */
status = acpi_install_notify_handler ( dock_station - > handle ,
ACPI_SYSTEM_NOTIFY ,
dock_notify , dock_station ) ;
if ( ACPI_FAILURE ( status ) ) {
printk ( KERN_ERR PREFIX " Error installing notify handler \n " ) ;
ret = - ENODEV ;
goto dock_add_err ;
}
2007-02-13 07:50:02 +03:00
printk ( KERN_INFO PREFIX " %s \n " , ACPI_DOCK_DRIVER_DESCRIPTION ) ;
2006-07-10 01:22:28 +04:00
return 0 ;
dock_add_err :
kfree ( dd ) ;
2006-12-05 01:49:43 +03:00
dock_add_err_unregister :
2007-05-10 02:07:04 +04:00
device_remove_file ( & dock_device - > dev , & dev_attr_docked ) ;
device_remove_file ( & dock_device - > dev , & dev_attr_undock ) ;
device_remove_file ( & dock_device - > dev , & dev_attr_uid ) ;
2007-05-10 02:08:15 +04:00
device_remove_file ( & dock_device - > dev , & dev_attr_flags ) ;
2007-05-10 02:07:04 +04:00
platform_device_unregister ( dock_device ) ;
2006-12-05 01:49:43 +03:00
kfree ( dock_station ) ;
2007-05-10 02:05:48 +04:00
dock_station = NULL ;
2006-07-10 01:22:28 +04:00
return ret ;
}
/**
* dock_remove - free up resources related to the dock station
*/
static int dock_remove ( void )
{
struct dock_dependent_device * dd , * tmp ;
acpi_status status ;
if ( ! dock_station )
return 0 ;
/* remove dependent devices */
list_for_each_entry_safe ( dd , tmp , & dock_station - > dependent_devices ,
list )
kfree ( dd ) ;
/* remove dock notify handler */
status = acpi_remove_notify_handler ( dock_station - > handle ,
ACPI_SYSTEM_NOTIFY ,
dock_notify ) ;
if ( ACPI_FAILURE ( status ) )
printk ( KERN_ERR " Error removing notify handler \n " ) ;
2006-12-05 01:49:43 +03:00
/* cleanup sysfs */
2007-05-10 02:07:04 +04:00
device_remove_file ( & dock_device - > dev , & dev_attr_docked ) ;
device_remove_file ( & dock_device - > dev , & dev_attr_undock ) ;
device_remove_file ( & dock_device - > dev , & dev_attr_uid ) ;
2007-05-10 02:08:15 +04:00
device_remove_file ( & dock_device - > dev , & dev_attr_flags ) ;
2007-05-10 02:07:04 +04:00
platform_device_unregister ( dock_device ) ;
2006-12-05 01:49:43 +03:00
2006-07-10 01:22:28 +04:00
/* free dock station memory */
kfree ( dock_station ) ;
2007-05-10 02:05:48 +04:00
dock_station = NULL ;
2006-07-10 01:22:28 +04:00
return 0 ;
}
/**
* find_dock - look for a dock station
* @ handle : acpi handle of a device
* @ lvl : unused
* @ context : counter of dock stations found
* @ rv : unused
*
* This is called by acpi_walk_namespace to look for dock stations .
*/
static acpi_status
find_dock ( acpi_handle handle , u32 lvl , void * context , void * * rv )
{
2006-10-01 02:28:50 +04:00
int * count = context ;
2006-07-10 01:22:28 +04:00
acpi_status status = AE_OK ;
if ( is_dock ( handle ) ) {
if ( dock_add ( handle ) > = 0 ) {
( * count ) + + ;
status = AE_CTRL_TERMINATE ;
}
}
return status ;
}
static int __init dock_init ( void )
{
int num = 0 ;
dock_station = NULL ;
/* look for a dock station */
acpi_walk_namespace ( ACPI_TYPE_DEVICE , ACPI_ROOT_OBJECT ,
ACPI_UINT32_MAX , find_dock , & num , NULL ) ;
if ( ! num )
2006-12-05 01:50:17 +03:00
printk ( KERN_INFO " No dock devices found. \n " ) ;
2006-07-10 01:22:28 +04:00
return 0 ;
}
static void __exit dock_exit ( void )
{
dock_remove ( ) ;
}
postcore_initcall ( dock_init ) ;
module_exit ( dock_exit ) ;