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>
2009-07-29 00:45:54 +04:00
# define PREFIX "ACPI: "
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-12-07 15:20:42 +03:00
static const struct acpi_device_id dock_device_ids [ ] = {
{ " LNXDOCK " , 0 } ,
{ " " , 0 } ,
} ;
MODULE_DEVICE_TABLE ( acpi , dock_device_ids ) ;
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 ;
2008-08-28 06:03:58 +04:00
2009-10-01 21:59:23 +04:00
struct list_head sibling ;
2008-08-28 06:03:58 +04:00
struct platform_device * dock_device ;
2006-07-10 01:22:28 +04:00
} ;
2008-08-28 06:03:58 +04:00
static LIST_HEAD ( dock_stations ) ;
static int dock_station_count ;
2006-07-10 01:22:28 +04:00
struct dock_dependent_device {
struct list_head list ;
struct list_head hotplug_list ;
acpi_handle handle ;
2008-08-28 06:06:16 +04:00
struct acpi_dock_ops * ops ;
2006-07-10 01:22:28 +04:00
void * context ;
} ;
# define DOCK_DOCKING 0x00000001
2007-05-10 02:08:15 +04:00
# define DOCK_UNDOCKING 0x00000002
2008-08-28 06:03:58 +04:00
# define DOCK_IS_DOCK 0x00000010
# define DOCK_IS_ATA 0x00000020
# define DOCK_IS_BAT 0x00000040
2006-08-02 01:59:19 +04:00
# define DOCK_EVENT 3
# define UNDOCK_EVENT 2
2006-07-10 01:22:28 +04:00
/*****************************************************************************
* Dock Dependent device functions *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
2009-10-20 01:14:29 +04:00
* add_dock_dependent_device - associate a device with the dock station
* @ ds : The dock station
* @ handle : handle of the dependent device
2006-07-10 01:22:28 +04:00
*
2009-10-20 01:14:29 +04:00
* Add the dependent device to the dock ' s dependent device list .
2006-07-10 01:22:28 +04:00
*/
2009-10-20 01:14:29 +04:00
static int
add_dock_dependent_device ( struct dock_station * ds , acpi_handle handle )
2006-07-10 01:22:28 +04:00
{
struct dock_dependent_device * dd ;
dd = kzalloc ( sizeof ( * dd ) , GFP_KERNEL ) ;
2009-10-20 01:14:29 +04:00
if ( ! dd )
return - ENOMEM ;
dd - > handle = handle ;
INIT_LIST_HEAD ( & dd - > list ) ;
INIT_LIST_HEAD ( & dd - > hotplug_list ) ;
2006-07-10 01:22:28 +04:00
spin_lock ( & ds - > dd_lock ) ;
list_add_tail ( & dd - > list , & ds - > dependent_devices ) ;
spin_unlock ( & ds - > dd_lock ) ;
2009-10-20 01:14:29 +04:00
return 0 ;
2006-07-10 01:22:28 +04:00
}
/**
* 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 ;
}
2008-08-28 06:03:58 +04:00
static int is_ejectable ( acpi_handle handle )
{
acpi_status status ;
acpi_handle tmp ;
status = acpi_get_handle ( handle , " _EJ0 " , & tmp ) ;
if ( ACPI_FAILURE ( status ) )
return 0 ;
return 1 ;
}
static int is_ata ( acpi_handle handle )
{
acpi_handle tmp ;
if ( ( ACPI_SUCCESS ( acpi_get_handle ( handle , " _GTF " , & tmp ) ) ) | |
( ACPI_SUCCESS ( acpi_get_handle ( handle , " _GTM " , & tmp ) ) ) | |
( ACPI_SUCCESS ( acpi_get_handle ( handle , " _STM " , & tmp ) ) ) | |
( ACPI_SUCCESS ( acpi_get_handle ( handle , " _SDD " , & tmp ) ) ) )
return 1 ;
return 0 ;
}
static int is_battery ( acpi_handle handle )
{
struct acpi_device_info * info ;
int ret = 1 ;
2009-06-29 09:39:29 +04:00
if ( ! ACPI_SUCCESS ( acpi_get_object_info ( handle , & info ) ) )
2008-08-28 06:03:58 +04:00
return 0 ;
if ( ! ( info - > valid & ACPI_VALID_HID ) )
ret = 0 ;
else
2009-06-29 09:39:29 +04:00
ret = ! strcmp ( " PNP0C0A " , info - > hardware_id . string ) ;
2008-08-28 06:03:58 +04:00
2009-06-29 09:39:29 +04:00
kfree ( info ) ;
2008-08-28 06:03:58 +04:00
return ret ;
}
static int is_ejectable_bay ( acpi_handle handle )
{
acpi_handle phandle ;
if ( ! is_ejectable ( handle ) )
return 0 ;
if ( is_battery ( handle ) | | is_ata ( handle ) )
return 1 ;
if ( ! acpi_get_parent ( handle , & phandle ) & & is_ata ( phandle ) )
return 1 ;
return 0 ;
}
2006-07-10 01:22:28 +04:00
/**
* 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 )
{
2008-08-28 06:03:58 +04:00
struct dock_station * dock_station ;
if ( ! dock_station_count )
2006-07-10 01:22:28 +04:00
return 0 ;
2008-08-28 06:03:58 +04:00
if ( is_dock ( handle ) )
2006-07-10 01:22:28 +04:00
return 1 ;
2009-10-01 21:59:23 +04:00
list_for_each_entry ( dock_station , & dock_stations , sibling ) {
2008-08-28 06:03:58 +04:00
if ( find_dock_dependent_device ( dock_station , handle ) )
return 1 ;
}
2006-07-10 01:22:28 +04:00
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 )
{
2008-10-10 10:22:59 +04:00
unsigned long long sta ;
2006-07-10 01:22:28 +04:00
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 ) {
2008-08-28 06:06:16 +04:00
if ( dd - > ops & & dd - > ops - > handler )
dd - > ops - > handler ( dd - > handle , event , dd - > context ) ;
2006-07-10 01:22:28 +04:00
}
/*
* 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 )
{
2008-08-28 06:03:58 +04:00
struct device * dev = & ds - > 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 } ;
2008-08-28 06:06:16 +04:00
struct dock_dependent_device * dd ;
2007-05-10 02:10:22 +04:00
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
*/
2008-08-28 06:06:16 +04:00
if ( num = = DOCK_EVENT )
kobject_uevent_env ( & dev - > kobj , KOBJ_CHANGE , envp ) ;
list_for_each_entry ( dd , & ds - > hotplug_devices , hotplug_list )
if ( dd - > ops & & dd - > ops - > uevent )
dd - > ops - > uevent ( dd - > handle , event , dd - > context ) ;
if ( num ! = DOCK_EVENT )
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 ) ;
2008-08-28 06:03:58 +04:00
if ( ACPI_FAILURE ( status ) & & status ! = AE_NOT_FOUND )
2008-10-11 08:15:04 +04:00
ACPI_EXCEPTION ( ( AE_INFO , status , " %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 ) ;
}
2008-08-28 06:03:26 +04:00
static void dock_lock ( struct dock_station * ds , int lock )
{
struct acpi_object_list arg_list ;
union acpi_object arg ;
acpi_status status ;
arg_list . count = 1 ;
arg_list . pointer = & arg ;
arg . type = ACPI_TYPE_INTEGER ;
arg . integer . value = ! ! lock ;
status = acpi_evaluate_object ( ds - > handle , " _LCK " , & arg_list , NULL ) ;
if ( ACPI_FAILURE ( status ) & & status ! = AE_NOT_FOUND ) {
if ( lock )
printk ( KERN_WARNING PREFIX " Locking device failed \n " ) ;
else
printk ( KERN_WARNING PREFIX " Unlocking device failed \n " ) ;
}
}
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 )
{
2008-08-28 06:03:58 +04:00
if ( ! dock_station_count )
2006-12-05 01:50:17 +03:00
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 )
{
2008-08-28 06:03:58 +04:00
if ( ! dock_station_count )
2006-12-05 01:50:17 +03:00
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
2008-08-28 06:06:16 +04:00
* @ ops : handlers to call after docking
2006-07-10 01:22:28 +04:00
* @ 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
2008-08-28 06:06:16 +04:00
register_hotplug_dock_device ( acpi_handle handle , struct acpi_dock_ops * ops ,
2006-07-10 01:22:28 +04:00
void * context )
{
struct dock_dependent_device * dd ;
2008-08-28 06:03:58 +04:00
struct dock_station * dock_station ;
2008-08-28 06:07:14 +04:00
int ret = - EINVAL ;
2006-07-10 01:22:28 +04:00
2008-08-28 06:03:58 +04:00
if ( ! dock_station_count )
2006-07-10 01:22:28 +04:00
return - ENODEV ;
/*
* make sure this handle is for a device dependent on the dock ,
* this would include the dock station itself
*/
2009-10-01 21:59:23 +04:00
list_for_each_entry ( dock_station , & dock_stations , sibling ) {
2008-08-28 06:07:14 +04:00
/*
* An ATA bay can be in a dock and itself can be ejected
* seperately , so there are two ' dock stations ' which need the
* ops
*/
2008-08-28 06:03:58 +04:00
dd = find_dock_dependent_device ( dock_station , handle ) ;
if ( dd ) {
2008-08-28 06:06:16 +04:00
dd - > ops = ops ;
2008-08-28 06:03:58 +04:00
dd - > context = context ;
dock_add_hotplug_device ( dock_station , dd ) ;
2008-08-28 06:07:14 +04:00
ret = 0 ;
2008-08-28 06:03:58 +04:00
}
2006-07-10 01:22:28 +04:00
}
2008-08-28 06:07:14 +04:00
return ret ;
2006-07-10 01:22:28 +04:00
}
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 ;
2008-08-28 06:03:58 +04:00
struct dock_station * dock_station ;
2006-07-10 01:22:28 +04:00
2008-08-28 06:03:58 +04:00
if ( ! dock_station_count )
2006-07-10 01:22:28 +04:00
return ;
2009-10-01 21:59:23 +04:00
list_for_each_entry ( dock_station , & dock_stations , sibling ) {
2008-08-28 06:03:58 +04:00
dd = find_dock_dependent_device ( dock_station , handle ) ;
if ( dd )
dock_del_hotplug_device ( dock_station , dd ) ;
}
2006-07-10 01:22:28 +04:00
}
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_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 .
2008-08-06 19:56:01 +04:00
* Also , even send the dock event if the
* device is not present anymore
2006-12-05 01:49:58 +03:00
*/
dock_event ( ds , event , UNDOCK_EVENT ) ;
2008-08-06 19:56:01 +04:00
2006-12-05 01:49:58 +03:00
hotplug_dock_devices ( ds , ACPI_NOTIFY_EJECT_REQUEST ) ;
undock ( ds ) ;
2008-08-28 06:03:26 +04:00
dock_lock ( ds , 0 ) ;
2006-12-05 01:49:58 +03:00
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 ;
2008-08-28 06:02:03 +04:00
struct acpi_device * tmp ;
2008-08-28 06:03:58 +04:00
int surprise_removal = 0 ;
2006-07-10 01:22:28 +04:00
2008-08-28 06:03:58 +04:00
/*
* 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 .
*/
if ( ( ds - > flags & DOCK_IS_DOCK ) & & event = = ACPI_NOTIFY_DEVICE_CHECK )
event = ACPI_NOTIFY_EJECT_REQUEST ;
/*
* dock station : BUS_CHECK - docked or surprise removal
* DEVICE_CHECK - undocked
* other device : BUS_CHECK / DEVICE_CHECK - added or surprise removal
*
* To simplify event handling , dock dependent device handler always
* get ACPI_NOTIFY_BUS_CHECK / ACPI_NOTIFY_DEVICE_CHECK for add and
* ACPI_NOTIFY_EJECT_REQUEST for removal
*/
2006-07-10 01:22:28 +04:00
switch ( event ) {
case ACPI_NOTIFY_BUS_CHECK :
2008-08-28 06:03:58 +04:00
case ACPI_NOTIFY_DEVICE_CHECK :
2008-08-28 06:02:03 +04:00
if ( ! dock_in_progress ( ds ) & & acpi_bus_get_device ( ds - > handle ,
& tmp ) ) {
2006-07-10 01:22:28 +04:00
begin_dock ( ds ) ;
dock ( ds ) ;
if ( ! dock_present ( ds ) ) {
printk ( KERN_ERR PREFIX " Unable to dock! \n " ) ;
2008-08-28 06:02:03 +04:00
complete_dock ( ds ) ;
2006-07-10 01:22:28 +04:00
break ;
}
atomic_notifier_call_chain ( & dock_notifier_list ,
event , NULL ) ;
hotplug_dock_devices ( ds , event ) ;
complete_dock ( ds ) ;
dock_event ( ds , event , DOCK_EVENT ) ;
2008-08-28 06:03:26 +04:00
dock_lock ( ds , 1 ) ;
2008-08-28 06:03:58 +04:00
break ;
2006-07-10 01:22:28 +04:00
}
2008-08-28 06:03:58 +04:00
if ( dock_present ( ds ) | | dock_in_progress ( ds ) )
break ;
/* This is a surprise removal */
surprise_removal = 1 ;
event = ACPI_NOTIFY_EJECT_REQUEST ;
/* Fall back */
2006-07-10 01:22:28 +04:00
case ACPI_NOTIFY_EJECT_REQUEST :
2007-05-10 02:08:15 +04:00
begin_undock ( ds ) ;
2008-08-28 06:05:45 +04:00
if ( ( immediate_undock & & ! ( ds - > flags & DOCK_IS_ATA ) )
| | surprise_removal )
2007-05-10 02:08:15 +04:00
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 ) ;
}
}
2008-08-28 06:05:06 +04:00
struct dock_data {
acpi_handle handle ;
unsigned long event ;
struct dock_station * ds ;
} ;
static void acpi_dock_deferred_cb ( void * context )
{
struct dock_data * data = ( struct dock_data * ) context ;
dock_notify ( data - > handle , data - > event , data - > ds ) ;
kfree ( data ) ;
}
2008-08-28 06:04:29 +04:00
static int acpi_dock_notifier_call ( struct notifier_block * this ,
unsigned long event , void * data )
{
struct dock_station * dock_station ;
acpi_handle handle = ( acpi_handle ) data ;
if ( event ! = ACPI_NOTIFY_BUS_CHECK & & event ! = ACPI_NOTIFY_DEVICE_CHECK
& & event ! = ACPI_NOTIFY_EJECT_REQUEST )
return 0 ;
2009-10-01 21:59:23 +04:00
list_for_each_entry ( dock_station , & dock_stations , sibling ) {
2008-08-28 06:04:29 +04:00
if ( dock_station - > handle = = handle ) {
2008-08-28 06:05:06 +04:00
struct dock_data * dock_data ;
dock_data = kmalloc ( sizeof ( * dock_data ) , GFP_KERNEL ) ;
if ( ! dock_data )
return 0 ;
dock_data - > handle = handle ;
dock_data - > event = event ;
dock_data - > ds = dock_station ;
acpi_os_hotplug_execute ( acpi_dock_deferred_cb ,
dock_data ) ;
2008-08-28 06:04:29 +04:00
return 0 ;
}
}
return 0 ;
}
static struct notifier_block dock_acpi_notifier = {
. notifier_call = acpi_dock_notifier_call ,
} ;
2006-07-10 01:22:28 +04:00
/**
* 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
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
2009-10-20 01:14:29 +04:00
if ( tmp = = ds - > handle )
add_dock_dependent_device ( ds , handle ) ;
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 )
{
2009-01-20 14:18:24 +03:00
struct acpi_device * tmp ;
2009-10-20 01:14:45 +04:00
struct dock_station * dock_station = dev - > platform_data ;
2006-12-05 01:49:58 +03:00
2009-01-20 14:18:24 +03:00
if ( ACPI_SUCCESS ( acpi_bus_get_device ( dock_station - > handle , & tmp ) ) )
return snprintf ( buf , PAGE_SIZE , " 1 \n " ) ;
return snprintf ( buf , PAGE_SIZE , " 0 \n " ) ;
2006-12-05 01:49:58 +03:00
}
2007-10-24 20:24:42 +04:00
static DEVICE_ATTR ( docked , S_IRUGO , show_docked , NULL ) ;
2006-12-05 01:49:58 +03:00
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 )
{
2009-10-20 01:14:45 +04:00
struct dock_station * dock_station = dev - > platform_data ;
2007-05-10 02:08:15 +04:00
return snprintf ( buf , PAGE_SIZE , " %d \n " , dock_station - > flags ) ;
}
2007-10-24 20:24:42 +04:00
static DEVICE_ATTR ( flags , S_IRUGO , show_flags , NULL ) ;
2007-05-10 02:08:15 +04:00
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 ;
2009-10-20 01:14:45 +04:00
struct dock_station * dock_station = dev - > platform_data ;
2006-12-05 01:49:58 +03:00
if ( ! count )
return - EINVAL ;
2008-03-12 03:07:27 +03:00
begin_undock ( dock_station ) ;
2006-12-05 01:49:58 +03:00
ret = handle_eject_request ( dock_station , ACPI_NOTIFY_EJECT_REQUEST ) ;
return ret ? ret : count ;
}
2007-10-24 20:24:42 +04:00
static DEVICE_ATTR ( undock , S_IWUSR , NULL , write_undock ) ;
2006-12-05 01:49:58 +03:00
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 )
{
2008-10-10 10:22:59 +04:00
unsigned long long lbuf ;
2009-10-20 01:14:45 +04:00
struct dock_station * dock_station = dev - > platform_data ;
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
2008-10-10 10:22:59 +04:00
return snprintf ( buf , PAGE_SIZE , " %llx \n " , lbuf ) ;
2007-02-20 02:19:31 +03:00
}
2007-10-24 20:24:42 +04:00
static DEVICE_ATTR ( uid , S_IRUGO , show_dock_uid , NULL ) ;
2007-02-20 02:19:31 +03:00
2008-08-28 06:07:45 +04:00
static ssize_t show_dock_type ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
2009-10-20 01:14:45 +04:00
struct dock_station * dock_station = dev - > platform_data ;
2008-08-28 06:07:45 +04:00
char * type ;
if ( dock_station - > flags & DOCK_IS_DOCK )
type = " dock_station " ;
else if ( dock_station - > flags & DOCK_IS_ATA )
type = " ata_bay " ;
else if ( dock_station - > flags & DOCK_IS_BAT )
type = " battery_bay " ;
else
type = " unknown " ;
return snprintf ( buf , PAGE_SIZE , " %s \n " , type ) ;
}
static DEVICE_ATTR ( type , S_IRUGO , show_dock_type , NULL ) ;
2009-10-20 01:14:24 +04:00
static struct attribute * dock_attributes [ ] = {
& dev_attr_docked . attr ,
& dev_attr_flags . attr ,
& dev_attr_undock . attr ,
& dev_attr_uid . attr ,
& dev_attr_type . attr ,
NULL
} ;
static struct attribute_group dock_attribute_group = {
. attrs = dock_attributes
} ;
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 )
{
2009-10-20 01:14:45 +04:00
int ret , id ;
struct dock_station ds , * dock_station ;
2008-08-28 06:03:58 +04:00
struct platform_device * dock_device ;
2006-07-10 01:22:28 +04:00
2009-10-20 01:14:45 +04:00
id = dock_station_count ;
2009-10-20 01:14:40 +04:00
dock_device =
2009-10-20 01:14:45 +04:00
platform_device_register_data ( NULL , " dock " ,
id , & ds , sizeof ( ds ) ) ;
2009-10-20 01:14:40 +04:00
if ( IS_ERR ( dock_device ) )
return PTR_ERR ( dock_device ) ;
2009-10-20 01:14:45 +04:00
dock_station = dock_device - > dev . platform_data ;
2006-07-10 01:22:28 +04:00
dock_station - > handle = handle ;
2009-10-20 01:14:40 +04:00
dock_station - > dock_device = dock_device ;
2006-07-10 01:22:28 +04:00
dock_station - > last_dock_time = jiffies - HZ ;
INIT_LIST_HEAD ( & dock_station - > dependent_devices ) ;
INIT_LIST_HEAD ( & dock_station - > hotplug_devices ) ;
2009-10-01 21:59:23 +04:00
INIT_LIST_HEAD ( & dock_station - > sibling ) ;
2006-07-10 01:22:28 +04:00
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
2007-05-10 02:09:12 +04:00
/* we want the dock device to send uevents */
2009-03-01 16:10:49 +03:00
dev_set_uevent_suppress ( & dock_device - > dev , 0 ) ;
2007-05-10 02:09:12 +04:00
2008-08-28 06:03:58 +04:00
if ( is_dock ( handle ) )
dock_station - > flags | = DOCK_IS_DOCK ;
if ( is_ata ( handle ) )
dock_station - > flags | = DOCK_IS_ATA ;
if ( is_battery ( handle ) )
dock_station - > flags | = DOCK_IS_BAT ;
2009-10-20 01:14:24 +04:00
ret = sysfs_create_group ( & dock_device - > dev . kobj , & dock_attribute_group ) ;
2008-08-28 06:07:45 +04:00
if ( ret )
2009-10-20 01:14:24 +04:00
goto err_unregister ;
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 */
2009-10-20 01:14:29 +04:00
ret = add_dock_dependent_device ( dock_station , handle ) ;
if ( ret )
2009-10-20 01:14:24 +04:00
goto err_rmgroup ;
2006-07-10 01:22:28 +04:00
2008-08-28 06:03:58 +04:00
dock_station_count + + ;
2009-10-01 21:59:23 +04:00
list_add ( & dock_station - > sibling , & dock_stations ) ;
2006-07-10 01:22:28 +04:00
return 0 ;
2009-10-20 01:14:24 +04:00
err_rmgroup :
sysfs_remove_group ( & dock_device - > dev . kobj , & dock_attribute_group ) ;
err_unregister :
2007-05-10 02:07:04 +04:00
platform_device_unregister ( dock_device ) ;
2009-10-20 01:14:24 +04:00
printk ( KERN_ERR " %s encountered error %d \n " , __func__ , ret ) ;
2006-07-10 01:22:28 +04:00
return ret ;
}
/**
* dock_remove - free up resources related to the dock station
*/
2008-08-28 06:03:58 +04:00
static int dock_remove ( struct dock_station * dock_station )
2006-07-10 01:22:28 +04:00
{
struct dock_dependent_device * dd , * tmp ;
2008-08-28 06:03:58 +04:00
struct platform_device * dock_device = dock_station - > dock_device ;
2006-07-10 01:22:28 +04:00
2008-08-28 06:03:58 +04:00
if ( ! dock_station_count )
2006-07-10 01:22:28 +04:00
return 0 ;
/* remove dependent devices */
list_for_each_entry_safe ( dd , tmp , & dock_station - > dependent_devices ,
list )
kfree ( dd ) ;
2009-10-20 01:14:45 +04:00
list_del ( & dock_station - > sibling ) ;
2006-12-05 01:49:43 +03:00
/* cleanup sysfs */
2009-10-20 01:14:24 +04:00
sysfs_remove_group ( & dock_device - > dev . kobj , & dock_attribute_group ) ;
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
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 )
{
acpi_status status = AE_OK ;
if ( is_dock ( handle ) ) {
if ( dock_add ( handle ) > = 0 ) {
status = AE_CTRL_TERMINATE ;
}
}
return status ;
}
2008-08-28 06:03:58 +04:00
static acpi_status
find_bay ( acpi_handle handle , u32 lvl , void * context , void * * rv )
2006-07-10 01:22:28 +04:00
{
2008-08-28 06:07:14 +04:00
/* If bay is a dock, it's already handled */
if ( is_ejectable_bay ( handle ) & & ! is_dock ( handle ) )
2008-08-28 06:03:58 +04:00
dock_add ( handle ) ;
return AE_OK ;
}
2006-07-10 01:22:28 +04:00
2008-08-28 06:03:58 +04:00
static int __init dock_init ( void )
{
2008-06-25 06:57:12 +04:00
if ( acpi_disabled )
return 0 ;
2006-07-10 01:22:28 +04:00
/* look for a dock station */
acpi_walk_namespace ( ACPI_TYPE_DEVICE , ACPI_ROOT_OBJECT ,
2008-08-28 06:03:58 +04:00
ACPI_UINT32_MAX , find_dock , NULL , NULL ) ;
2006-07-10 01:22:28 +04:00
2008-08-28 06:03:58 +04:00
/* look for bay */
acpi_walk_namespace ( ACPI_TYPE_DEVICE , ACPI_ROOT_OBJECT ,
ACPI_UINT32_MAX , find_bay , NULL , NULL ) ;
if ( ! dock_station_count ) {
printk ( KERN_INFO PREFIX " No dock devices found. \n " ) ;
return 0 ;
}
2006-07-10 01:22:28 +04:00
2008-08-28 06:04:29 +04:00
register_acpi_bus_notifier ( & dock_acpi_notifier ) ;
2008-08-28 06:03:58 +04:00
printk ( KERN_INFO PREFIX " %s: %d docks/bays found \n " ,
ACPI_DOCK_DRIVER_DESCRIPTION , dock_station_count ) ;
2006-07-10 01:22:28 +04:00
return 0 ;
}
static void __exit dock_exit ( void )
{
2008-08-28 06:03:58 +04:00
struct dock_station * dock_station ;
2009-04-02 09:29:56 +04:00
struct dock_station * tmp ;
2008-08-28 06:03:58 +04:00
2008-08-28 06:04:29 +04:00
unregister_acpi_bus_notifier ( & dock_acpi_notifier ) ;
2009-10-01 21:59:23 +04:00
list_for_each_entry_safe ( dock_station , tmp , & dock_stations , sibling )
2008-08-28 06:03:58 +04:00
dock_remove ( dock_station ) ;
2006-07-10 01:22:28 +04:00
}
2008-08-28 06:03:58 +04:00
/*
* Must be called before drivers of devices in dock , otherwise we can ' t know
* which devices are in a dock
*/
subsys_initcall ( dock_init ) ;
2006-07-10 01:22:28 +04:00
module_exit ( dock_exit ) ;