2005-04-17 02:20:36 +04:00
/*
* cs . c - - Kernel Card Services - core services
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* The initial developer of the original code is David A . Hinds
* < dahinds @ users . sourceforge . net > . Portions created by David A . Hinds
* are Copyright ( C ) 1999 David A . Hinds . All Rights Reserved .
*
* ( C ) 1999 David A . Hinds
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/major.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/mm.h>
# include <linux/interrupt.h>
# include <linux/timer.h>
# include <linux/ioport.h>
# include <linux/delay.h>
# include <linux/pm.h>
# include <linux/device.h>
2006-06-14 17:01:26 +04:00
# include <linux/kthread.h>
2006-12-07 07:34:23 +03:00
# include <linux/freezer.h>
2005-04-17 02:20:36 +04:00
# include <asm/system.h>
# include <asm/irq.h>
# include <pcmcia/cs_types.h>
# include <pcmcia/ss.h>
# include <pcmcia/cs.h>
# include <pcmcia/cistpl.h>
# include <pcmcia/cisreg.h>
# include <pcmcia/ds.h>
# include "cs_internal.h"
/* Module parameters */
MODULE_AUTHOR ( " David Hinds <dahinds@users.sourceforge.net> " ) ;
2005-06-28 03:28:53 +04:00
MODULE_DESCRIPTION ( " Linux Kernel Card Services " ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
# define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444)
INT_MODULE_PARM ( setup_delay , 10 ) ; /* centiseconds */
INT_MODULE_PARM ( resume_delay , 20 ) ; /* centiseconds */
INT_MODULE_PARM ( shutdown_delay , 3 ) ; /* centiseconds */
INT_MODULE_PARM ( vcc_settle , 40 ) ; /* centiseconds */
INT_MODULE_PARM ( reset_time , 10 ) ; /* usecs */
INT_MODULE_PARM ( unreset_delay , 10 ) ; /* centiseconds */
INT_MODULE_PARM ( unreset_check , 10 ) ; /* centiseconds */
INT_MODULE_PARM ( unreset_limit , 30 ) ; /* unreset_check's */
/* Access speed for attribute memory windows */
INT_MODULE_PARM ( cis_speed , 300 ) ; /* ns */
2008-08-02 23:02:01 +04:00
# ifdef CONFIG_PCMCIA_DEBUG
2005-04-17 02:20:36 +04:00
static int pc_debug ;
module_param ( pc_debug , int , 0644 ) ;
int cs_debug_level ( int level )
{
return pc_debug > level ;
}
# endif
socket_state_t dead_socket = {
. csc_mask = SS_DETECT ,
} ;
2005-06-28 03:28:53 +04:00
EXPORT_SYMBOL ( dead_socket ) ;
2005-04-17 02:20:36 +04:00
/* List of all sockets, protected by a rwsem */
LIST_HEAD ( pcmcia_socket_list ) ;
EXPORT_SYMBOL ( pcmcia_socket_list ) ;
2005-06-28 03:28:53 +04:00
DECLARE_RWSEM ( pcmcia_socket_list_rwsem ) ;
EXPORT_SYMBOL ( pcmcia_socket_list_rwsem ) ;
2005-04-17 02:20:36 +04:00
2007-12-11 02:49:22 +03:00
/*
2005-06-28 03:28:53 +04:00
* Low - level PCMCIA socket drivers need to register with the PCCard
* core using pcmcia_register_socket .
*
* socket drivers are expected to use the following callbacks in their
2005-04-17 02:20:36 +04:00
* . drv struct :
* - pcmcia_socket_dev_suspend
* - pcmcia_socket_dev_resume
* These functions check for the appropriate struct pcmcia_soket arrays ,
* and pass them to the low - level functions pcmcia_ { suspend , resume } _socket
*/
static int socket_resume ( struct pcmcia_socket * skt ) ;
static int socket_suspend ( struct pcmcia_socket * skt ) ;
int pcmcia_socket_dev_suspend ( struct device * dev , pm_message_t state )
{
struct pcmcia_socket * socket ;
down_read ( & pcmcia_socket_list_rwsem ) ;
list_for_each_entry ( socket , & pcmcia_socket_list , socket_list ) {
2006-09-12 19:00:10 +04:00
if ( socket - > dev . parent ! = dev )
2005-04-17 02:20:36 +04:00
continue ;
2006-01-10 23:20:36 +03:00
mutex_lock ( & socket - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
socket_suspend ( socket ) ;
2006-01-10 23:20:36 +03:00
mutex_unlock ( & socket - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
}
up_read ( & pcmcia_socket_list_rwsem ) ;
return 0 ;
}
EXPORT_SYMBOL ( pcmcia_socket_dev_suspend ) ;
int pcmcia_socket_dev_resume ( struct device * dev )
{
struct pcmcia_socket * socket ;
down_read ( & pcmcia_socket_list_rwsem ) ;
list_for_each_entry ( socket , & pcmcia_socket_list , socket_list ) {
2006-09-12 19:00:10 +04:00
if ( socket - > dev . parent ! = dev )
2005-04-17 02:20:36 +04:00
continue ;
2006-01-10 23:20:36 +03:00
mutex_lock ( & socket - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
socket_resume ( socket ) ;
2006-01-10 23:20:36 +03:00
mutex_unlock ( & socket - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
}
up_read ( & pcmcia_socket_list_rwsem ) ;
return 0 ;
}
EXPORT_SYMBOL ( pcmcia_socket_dev_resume ) ;
struct pcmcia_socket * pcmcia_get_socket ( struct pcmcia_socket * skt )
{
2006-09-12 19:00:10 +04:00
struct device * dev = get_device ( & skt - > dev ) ;
if ( ! dev )
2005-04-17 02:20:36 +04:00
return NULL ;
2006-09-12 19:00:10 +04:00
skt = dev_get_drvdata ( dev ) ;
2005-04-17 02:20:36 +04:00
if ( ! try_module_get ( skt - > owner ) ) {
2006-09-12 19:00:10 +04:00
put_device ( & skt - > dev ) ;
2005-04-17 02:20:36 +04:00
return NULL ;
}
return ( skt ) ;
}
EXPORT_SYMBOL ( pcmcia_get_socket ) ;
void pcmcia_put_socket ( struct pcmcia_socket * skt )
{
module_put ( skt - > owner ) ;
2006-09-12 19:00:10 +04:00
put_device ( & skt - > dev ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( pcmcia_put_socket ) ;
2006-09-12 19:00:10 +04:00
static void pcmcia_release_socket ( struct device * dev )
2005-04-17 02:20:36 +04:00
{
2006-09-12 19:00:10 +04:00
struct pcmcia_socket * socket = dev_get_drvdata ( dev ) ;
2005-04-17 02:20:36 +04:00
complete ( & socket - > socket_released ) ;
}
static int pccardd ( void * __skt ) ;
/**
* pcmcia_register_socket - add a new pcmcia socket device
2007-12-11 02:49:22 +03:00
* @ socket : the & socket to register
2005-04-17 02:20:36 +04:00
*/
int pcmcia_register_socket ( struct pcmcia_socket * socket )
{
2006-06-14 17:01:26 +04:00
struct task_struct * tsk ;
2005-04-17 02:20:36 +04:00
int ret ;
2006-09-12 19:00:10 +04:00
if ( ! socket | | ! socket - > ops | | ! socket - > dev . parent | | ! socket - > resource_ops )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
cs_dbg ( socket , 0 , " pcmcia_register_socket(0x%p) \n " , socket - > ops ) ;
spin_lock_init ( & socket - > lock ) ;
if ( socket - > resource_ops - > init ) {
ret = socket - > resource_ops - > init ( socket ) ;
if ( ret )
return ( ret ) ;
}
/* try to obtain a socket number [yes, it gets ugly if we
2005-06-28 03:28:53 +04:00
* register more than 2 ^ sizeof ( unsigned int ) pcmcia
* sockets . . . but the socket number is deprecated
2005-04-17 02:20:36 +04:00
* anyways , so I don ' t care ] */
down_write ( & pcmcia_socket_list_rwsem ) ;
if ( list_empty ( & pcmcia_socket_list ) )
socket - > sock = 0 ;
else {
unsigned int found , i = 1 ;
struct pcmcia_socket * tmp ;
do {
found = 1 ;
list_for_each_entry ( tmp , & pcmcia_socket_list , socket_list ) {
if ( tmp - > sock = = i )
found = 0 ;
}
i + + ;
} while ( ! found ) ;
socket - > sock = i - 1 ;
}
list_add_tail ( & socket - > socket_list , & pcmcia_socket_list ) ;
up_write ( & pcmcia_socket_list_rwsem ) ;
2005-07-08 04:59:07 +04:00
# ifndef CONFIG_CARDBUS
/*
* If we do not support Cardbus , ensure that
* the Cardbus socket capability is disabled .
*/
socket - > features & = ~ SS_CAP_CARDBUS ;
# endif
2005-04-17 02:20:36 +04:00
/* set proper values in socket->dev */
2006-09-12 19:00:10 +04:00
dev_set_drvdata ( & socket - > dev , socket ) ;
2005-04-17 02:20:36 +04:00
socket - > dev . class = & pcmcia_socket_class ;
2006-09-12 19:00:10 +04:00
snprintf ( socket - > dev . bus_id , BUS_ID_SIZE , " pcmcia_socket%u " , socket - > sock ) ;
2005-04-17 02:20:36 +04:00
/* base address = 0, map = 0 */
socket - > cis_mem . flags = 0 ;
socket - > cis_mem . speed = cis_speed ;
INIT_LIST_HEAD ( & socket - > cis_cache ) ;
init_completion ( & socket - > socket_released ) ;
init_completion ( & socket - > thread_done ) ;
2006-01-10 23:20:36 +03:00
mutex_init ( & socket - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
spin_lock_init ( & socket - > thread_lock ) ;
2006-06-14 17:01:26 +04:00
tsk = kthread_run ( pccardd , socket , " pccardd " ) ;
if ( IS_ERR ( tsk ) ) {
ret = PTR_ERR ( tsk ) ;
2005-04-17 02:20:36 +04:00
goto err ;
2006-06-14 17:01:26 +04:00
}
2005-04-17 02:20:36 +04:00
wait_for_completion ( & socket - > thread_done ) ;
2006-06-14 17:01:26 +04:00
if ( ! socket - > thread ) {
2008-08-02 20:08:38 +04:00
dev_printk ( KERN_WARNING , & socket - > dev ,
" PCMCIA: warning: socket thread did not start \n " ) ;
2005-04-17 02:20:36 +04:00
return - EIO ;
}
2006-06-14 17:01:26 +04:00
2005-04-17 02:20:36 +04:00
pcmcia_parse_events ( socket , SS_DETECT ) ;
return 0 ;
err :
down_write ( & pcmcia_socket_list_rwsem ) ;
list_del ( & socket - > socket_list ) ;
up_write ( & pcmcia_socket_list_rwsem ) ;
return ret ;
} /* pcmcia_register_socket */
EXPORT_SYMBOL ( pcmcia_register_socket ) ;
/**
* pcmcia_unregister_socket - remove a pcmcia socket device
2007-12-11 02:49:22 +03:00
* @ socket : the & socket to unregister
2005-04-17 02:20:36 +04:00
*/
void pcmcia_unregister_socket ( struct pcmcia_socket * socket )
{
if ( ! socket )
return ;
cs_dbg ( socket , 0 , " pcmcia_unregister_socket(0x%p) \n " , socket - > ops ) ;
2007-04-22 16:55:36 +04:00
if ( socket - > thread )
2006-06-14 17:01:26 +04:00
kthread_stop ( socket - > thread ) ;
2007-04-22 16:55:36 +04:00
2005-04-17 02:20:36 +04:00
release_cis_mem ( socket ) ;
/* remove from our own list */
down_write ( & pcmcia_socket_list_rwsem ) ;
list_del ( & socket - > socket_list ) ;
up_write ( & pcmcia_socket_list_rwsem ) ;
/* wait for sysfs to drop all references */
release_resource_db ( socket ) ;
wait_for_completion ( & socket - > socket_released ) ;
} /* pcmcia_unregister_socket */
EXPORT_SYMBOL ( pcmcia_unregister_socket ) ;
struct pcmcia_socket * pcmcia_get_socket_by_nr ( unsigned int nr )
{
struct pcmcia_socket * s ;
down_read ( & pcmcia_socket_list_rwsem ) ;
list_for_each_entry ( s , & pcmcia_socket_list , socket_list )
if ( s - > sock = = nr ) {
up_read ( & pcmcia_socket_list_rwsem ) ;
return s ;
}
up_read ( & pcmcia_socket_list_rwsem ) ;
return NULL ;
}
EXPORT_SYMBOL ( pcmcia_get_socket_by_nr ) ;
2007-12-11 02:49:22 +03:00
/*
2005-06-28 03:28:53 +04:00
* The central event handler . Send_event ( ) sends an event to the
* 16 - bit subsystem , which then calls the relevant device drivers .
* Parse_events ( ) interprets the event bits from
* a card status change report . Do_shutdown ( ) handles the high
* priority stuff associated with a card removal .
*/
2005-04-17 02:20:36 +04:00
/* NOTE: send_event needs to be called with skt->sem held. */
static int send_event ( struct pcmcia_socket * s , event_t event , int priority )
{
int ret ;
if ( s - > state & SOCKET_CARDBUS )
return 0 ;
cs_dbg ( s , 1 , " send_event(event %d, pri %d, callback 0x%p) \n " ,
event , priority , s - > callback ) ;
if ( ! s - > callback )
return 0 ;
if ( ! try_module_get ( s - > callback - > owner ) )
return 0 ;
ret = s - > callback - > event ( s , event , priority ) ;
module_put ( s - > callback - > owner ) ;
return ret ;
}
static void socket_remove_drivers ( struct pcmcia_socket * skt )
{
cs_dbg ( skt , 4 , " remove_drivers \n " ) ;
send_event ( skt , CS_EVENT_CARD_REMOVAL , CS_EVENT_PRI_HIGH ) ;
}
static int socket_reset ( struct pcmcia_socket * skt )
{
int status , i ;
cs_dbg ( skt , 4 , " reset \n " ) ;
skt - > socket . flags | = SS_OUTPUT_ENA | SS_RESET ;
skt - > ops - > set_socket ( skt , & skt - > socket ) ;
udelay ( ( long ) reset_time ) ;
skt - > socket . flags & = ~ SS_RESET ;
skt - > ops - > set_socket ( skt , & skt - > socket ) ;
msleep ( unreset_delay * 10 ) ;
for ( i = 0 ; i < unreset_limit ; i + + ) {
skt - > ops - > get_status ( skt , & status ) ;
if ( ! ( status & SS_DETECT ) )
2008-08-03 13:10:56 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
if ( status & SS_READY )
2008-08-03 12:07:45 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
msleep ( unreset_check * 10 ) ;
}
cs_err ( skt , " time out after reset. \n " ) ;
2008-08-03 13:15:45 +04:00
return - ETIMEDOUT ;
2005-04-17 02:20:36 +04:00
}
2007-12-11 02:49:22 +03:00
/*
2005-12-30 17:12:35 +03:00
* socket_setup ( ) and socket_shutdown ( ) are called by the main event handler
* when card insertion and removal events are received .
* socket_setup ( ) turns on socket power and resets the socket , in two stages .
* socket_shutdown ( ) unconfigures a socket and turns off socket power .
*/
static void socket_shutdown ( struct pcmcia_socket * s )
{
int status ;
cs_dbg ( s , 4 , " shutdown \n " ) ;
socket_remove_drivers ( s ) ;
s - > state & = SOCKET_INUSE | SOCKET_PRESENT ;
msleep ( shutdown_delay * 10 ) ;
s - > state & = SOCKET_INUSE ;
/* Blank out the socket state */
s - > socket = dead_socket ;
s - > ops - > init ( s ) ;
s - > ops - > set_socket ( s , & s - > socket ) ;
s - > irq . AssignedIRQ = s - > irq . Config = 0 ;
s - > lock_count = 0 ;
destroy_cis_cache ( s ) ;
# ifdef CONFIG_CARDBUS
cb_free ( s ) ;
# endif
s - > functions = 0 ;
2007-07-31 11:38:08 +04:00
/* give socket some time to power down */
msleep ( 100 ) ;
2005-12-30 17:12:35 +03:00
s - > ops - > get_status ( s , & status ) ;
if ( status & SS_POWERON ) {
2008-08-02 20:08:38 +04:00
dev_printk ( KERN_ERR , & s - > dev ,
" *** DANGER *** unable to remove socket power \n " ) ;
2005-12-30 17:12:35 +03:00
}
cs_socket_put ( s ) ;
}
2005-04-17 02:20:36 +04:00
static int socket_setup ( struct pcmcia_socket * skt , int initial_delay )
{
int status , i ;
cs_dbg ( skt , 4 , " setup \n " ) ;
skt - > ops - > get_status ( skt , & status ) ;
if ( ! ( status & SS_DETECT ) )
2008-08-03 13:10:56 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
msleep ( initial_delay * 10 ) ;
for ( i = 0 ; i < 100 ; i + + ) {
skt - > ops - > get_status ( skt , & status ) ;
if ( ! ( status & SS_DETECT ) )
2008-08-03 13:10:56 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
if ( ! ( status & SS_PENDING ) )
break ;
msleep ( 100 ) ;
}
if ( status & SS_PENDING ) {
cs_err ( skt , " voltage interrogation timed out. \n " ) ;
2008-08-03 13:15:45 +04:00
return - ETIMEDOUT ;
2005-04-17 02:20:36 +04:00
}
if ( status & SS_CARDBUS ) {
2005-07-08 04:59:07 +04:00
if ( ! ( skt - > features & SS_CAP_CARDBUS ) ) {
cs_err ( skt , " cardbus cards are not supported. \n " ) ;
return CS_BAD_TYPE ;
}
2005-04-17 02:20:36 +04:00
skt - > state | = SOCKET_CARDBUS ;
}
/*
* Decode the card voltage requirements , and apply power to the card .
*/
if ( status & SS_3VCARD )
skt - > socket . Vcc = skt - > socket . Vpp = 33 ;
else if ( ! ( status & SS_XVCARD ) )
skt - > socket . Vcc = skt - > socket . Vpp = 50 ;
else {
cs_err ( skt , " unsupported voltage key. \n " ) ;
return CS_BAD_TYPE ;
}
2005-06-23 11:10:12 +04:00
if ( skt - > power_hook )
skt - > power_hook ( skt , HOOK_POWER_PRE ) ;
2005-04-17 02:20:36 +04:00
skt - > socket . flags = 0 ;
skt - > ops - > set_socket ( skt , & skt - > socket ) ;
/*
* Wait " vcc_settle " for the supply to stabilise .
*/
msleep ( vcc_settle * 10 ) ;
skt - > ops - > get_status ( skt , & status ) ;
if ( ! ( status & SS_POWERON ) ) {
cs_err ( skt , " unable to apply power. \n " ) ;
return CS_BAD_TYPE ;
}
2005-06-23 11:10:12 +04:00
status = socket_reset ( skt ) ;
if ( skt - > power_hook )
skt - > power_hook ( skt , HOOK_POWER_POST ) ;
return status ;
2005-04-17 02:20:36 +04:00
}
/*
* Handle card insertion . Setup the socket , reset the card ,
* and then tell the rest of PCMCIA that a card is present .
*/
static int socket_insert ( struct pcmcia_socket * skt )
{
int ret ;
cs_dbg ( skt , 4 , " insert \n " ) ;
if ( ! cs_socket_get ( skt ) )
2008-08-03 13:10:56 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
ret = socket_setup ( skt , setup_delay ) ;
2008-08-03 12:07:45 +04:00
if ( ret = = 0 ) {
2005-04-17 02:20:36 +04:00
skt - > state | = SOCKET_PRESENT ;
2005-11-13 01:34:06 +03:00
2008-08-02 20:08:38 +04:00
dev_printk ( KERN_NOTICE , & skt - > dev ,
" pccard: %s card inserted into slot %d \n " ,
( skt - > state & SOCKET_CARDBUS ) ? " CardBus " : " PCMCIA " ,
skt - > sock ) ;
2005-11-13 01:34:06 +03:00
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_CARDBUS
if ( skt - > state & SOCKET_CARDBUS ) {
cb_alloc ( skt ) ;
skt - > state | = SOCKET_CARDBUS_CONFIG ;
}
# endif
cs_dbg ( skt , 4 , " insert done \n " ) ;
send_event ( skt , CS_EVENT_CARD_INSERTION , CS_EVENT_PRI_LOW ) ;
} else {
socket_shutdown ( skt ) ;
}
return ret ;
}
static int socket_suspend ( struct pcmcia_socket * skt )
{
if ( skt - > state & SOCKET_SUSPEND )
2008-08-03 13:40:19 +04:00
return - EBUSY ;
2005-04-17 02:20:36 +04:00
send_event ( skt , CS_EVENT_PM_SUSPEND , CS_EVENT_PRI_LOW ) ;
skt - > socket = dead_socket ;
skt - > ops - > set_socket ( skt , & skt - > socket ) ;
if ( skt - > ops - > suspend )
skt - > ops - > suspend ( skt ) ;
skt - > state | = SOCKET_SUSPEND ;
2008-08-03 12:07:45 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
/*
* Resume a socket . If a card is present , verify its CIS against
* our cached copy . If they are different , the card has been
* replaced , and we need to tell the drivers .
*/
static int socket_resume ( struct pcmcia_socket * skt )
{
int ret ;
if ( ! ( skt - > state & SOCKET_SUSPEND ) )
2008-08-03 13:40:19 +04:00
return - EBUSY ;
2005-04-17 02:20:36 +04:00
skt - > socket = dead_socket ;
skt - > ops - > init ( skt ) ;
skt - > ops - > set_socket ( skt , & skt - > socket ) ;
if ( ! ( skt - > state & SOCKET_PRESENT ) ) {
skt - > state & = ~ SOCKET_SUSPEND ;
return socket_insert ( skt ) ;
}
ret = socket_setup ( skt , resume_delay ) ;
2008-08-03 12:07:45 +04:00
if ( ret = = 0 ) {
2005-04-17 02:20:36 +04:00
/*
* FIXME : need a better check here for cardbus cards .
*/
if ( verify_cis_cache ( skt ) ! = 0 ) {
cs_dbg ( skt , 4 , " cis mismatch - different card \n " ) ;
socket_remove_drivers ( skt ) ;
destroy_cis_cache ( skt ) ;
/*
* Workaround : give DS time to schedule removal .
* Remove me once the 100 ms delay is eliminated
* in ds . c
*/
msleep ( 200 ) ;
send_event ( skt , CS_EVENT_CARD_INSERTION , CS_EVENT_PRI_LOW ) ;
} else {
cs_dbg ( skt , 4 , " cis matches cache \n " ) ;
send_event ( skt , CS_EVENT_PM_RESUME , CS_EVENT_PRI_LOW ) ;
}
} else {
socket_shutdown ( skt ) ;
}
skt - > state & = ~ SOCKET_SUSPEND ;
2008-08-03 12:07:45 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
static void socket_remove ( struct pcmcia_socket * skt )
{
2008-08-02 20:08:38 +04:00
dev_printk ( KERN_NOTICE , & skt - > dev ,
" pccard: card ejected from slot %d \n " , skt - > sock ) ;
2005-04-17 02:20:36 +04:00
socket_shutdown ( skt ) ;
}
/*
* Process a socket card detect status change .
*
* If we don ' t have a card already present , delay the detect event for
* about 20 ms ( to be on the safe side ) before reading the socket status .
*
* Some i82365 - based systems send multiple SS_DETECT events during card
* insertion , and the " card present " status bit seems to bounce . This
* will probably be true with GPIO - based card detection systems after
* the product has aged .
*/
static void socket_detect_change ( struct pcmcia_socket * skt )
{
if ( ! ( skt - > state & SOCKET_SUSPEND ) ) {
int status ;
if ( ! ( skt - > state & SOCKET_PRESENT ) )
msleep ( 20 ) ;
skt - > ops - > get_status ( skt , & status ) ;
if ( ( skt - > state & SOCKET_PRESENT ) & &
! ( status & SS_DETECT ) )
socket_remove ( skt ) ;
if ( ! ( skt - > state & SOCKET_PRESENT ) & &
( status & SS_DETECT ) )
socket_insert ( skt ) ;
}
}
static int pccardd ( void * __skt )
{
struct pcmcia_socket * skt = __skt ;
int ret ;
skt - > thread = current ;
skt - > socket = dead_socket ;
skt - > ops - > init ( skt ) ;
skt - > ops - > set_socket ( skt , & skt - > socket ) ;
/* register with the device core */
2006-09-12 19:00:10 +04:00
ret = device_register ( & skt - > dev ) ;
2005-04-17 02:20:36 +04:00
if ( ret ) {
2008-08-02 20:08:38 +04:00
dev_printk ( KERN_WARNING , & skt - > dev ,
" PCMCIA: unable to register socket \n " ) ;
2005-04-17 02:20:36 +04:00
skt - > thread = NULL ;
2006-06-14 17:01:26 +04:00
complete ( & skt - > thread_done ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2008-04-28 12:03:20 +04:00
ret = pccard_sysfs_add_socket ( & skt - > dev ) ;
if ( ret )
dev_warn ( & skt - > dev , " err %d adding socket attributes \n " , ret ) ;
2005-04-17 02:20:36 +04:00
2005-09-10 00:03:23 +04:00
complete ( & skt - > thread_done ) ;
2007-07-17 15:03:35 +04:00
set_freezable ( ) ;
2005-04-17 02:20:36 +04:00
for ( ; ; ) {
unsigned long flags ;
unsigned int events ;
set_current_state ( TASK_INTERRUPTIBLE ) ;
spin_lock_irqsave ( & skt - > thread_lock , flags ) ;
events = skt - > thread_events ;
skt - > thread_events = 0 ;
spin_unlock_irqrestore ( & skt - > thread_lock , flags ) ;
if ( events ) {
2006-01-10 23:20:36 +03:00
mutex_lock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( events & SS_DETECT )
socket_detect_change ( skt ) ;
if ( events & SS_BATDEAD )
send_event ( skt , CS_EVENT_BATTERY_DEAD , CS_EVENT_PRI_LOW ) ;
if ( events & SS_BATWARN )
send_event ( skt , CS_EVENT_BATTERY_LOW , CS_EVENT_PRI_LOW ) ;
if ( events & SS_READY )
send_event ( skt , CS_EVENT_READY_CHANGE , CS_EVENT_PRI_LOW ) ;
2006-01-10 23:20:36 +03:00
mutex_unlock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
continue ;
}
2006-06-14 17:01:26 +04:00
if ( kthread_should_stop ( ) )
2005-04-17 02:20:36 +04:00
break ;
2005-09-10 00:03:23 +04:00
schedule ( ) ;
try_to_freeze ( ) ;
2005-04-17 02:20:36 +04:00
}
2005-10-10 19:13:17 +04:00
/* make sure we are running before we exit */
set_current_state ( TASK_RUNNING ) ;
2005-04-17 02:20:36 +04:00
/* remove from the device core */
2008-04-28 12:03:20 +04:00
pccard_sysfs_remove_socket ( & skt - > dev ) ;
2006-09-12 19:00:10 +04:00
device_unregister ( & skt - > dev ) ;
2005-04-17 02:20:36 +04:00
2006-06-14 17:01:26 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
/*
* Yenta ( at least ) probes interrupts before registering the socket and
* starting the handler thread .
*/
void pcmcia_parse_events ( struct pcmcia_socket * s , u_int events )
{
2006-06-30 12:31:13 +04:00
unsigned long flags ;
2005-04-17 02:20:36 +04:00
cs_dbg ( s , 4 , " parse_events: events %08x \n " , events ) ;
if ( s - > thread ) {
2006-06-30 12:31:13 +04:00
spin_lock_irqsave ( & s - > thread_lock , flags ) ;
2005-04-17 02:20:36 +04:00
s - > thread_events | = events ;
2006-06-30 12:31:13 +04:00
spin_unlock_irqrestore ( & s - > thread_lock , flags ) ;
2005-04-17 02:20:36 +04:00
2007-04-22 16:55:36 +04:00
wake_up_process ( s - > thread ) ;
2005-04-17 02:20:36 +04:00
}
} /* pcmcia_parse_events */
2005-06-28 03:28:53 +04:00
EXPORT_SYMBOL ( pcmcia_parse_events ) ;
2005-04-17 02:20:36 +04:00
/* register pcmcia_callback */
int pccard_register_pcmcia ( struct pcmcia_socket * s , struct pcmcia_callback * c )
{
int ret = 0 ;
2006-01-10 23:20:36 +03:00
/* s->skt_mutex also protects s->callback */
mutex_lock ( & s - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( c ) {
/* registration */
if ( s - > callback ) {
ret = - EBUSY ;
goto err ;
}
s - > callback = c ;
if ( ( s - > state & ( SOCKET_PRESENT | SOCKET_CARDBUS ) ) = = SOCKET_PRESENT )
send_event ( s , CS_EVENT_CARD_INSERTION , CS_EVENT_PRI_LOW ) ;
} else
s - > callback = NULL ;
err :
2006-01-10 23:20:36 +03:00
mutex_unlock ( & s - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
EXPORT_SYMBOL ( pccard_register_pcmcia ) ;
2005-06-28 03:28:53 +04:00
/* I'm not sure which "reset" function this is supposed to use,
* but for now , it uses the low - level interface ' s reset , not the
* CIS register .
*/
2005-04-17 02:20:36 +04:00
int pccard_reset_card ( struct pcmcia_socket * skt )
{
int ret ;
2005-06-28 03:28:53 +04:00
2005-04-17 02:20:36 +04:00
cs_dbg ( skt , 1 , " resetting socket \n " ) ;
2006-01-10 23:20:36 +03:00
mutex_lock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
do {
if ( ! ( skt - > state & SOCKET_PRESENT ) ) {
2008-08-03 13:10:56 +04:00
ret = - ENODEV ;
2005-04-17 02:20:36 +04:00
break ;
}
if ( skt - > state & SOCKET_SUSPEND ) {
2008-08-03 13:40:19 +04:00
ret = - EBUSY ;
2005-04-17 02:20:36 +04:00
break ;
}
if ( skt - > state & SOCKET_CARDBUS ) {
2008-08-03 12:47:59 +04:00
ret = - EPERM ;
2005-04-17 02:20:36 +04:00
break ;
}
ret = send_event ( skt , CS_EVENT_RESET_REQUEST , CS_EVENT_PRI_LOW ) ;
if ( ret = = 0 ) {
send_event ( skt , CS_EVENT_RESET_PHYSICAL , CS_EVENT_PRI_LOW ) ;
2006-01-06 02:02:03 +03:00
if ( skt - > callback )
skt - > callback - > suspend ( skt ) ;
2008-08-03 12:07:45 +04:00
if ( socket_reset ( skt ) = = 0 ) {
2005-04-17 02:20:36 +04:00
send_event ( skt , CS_EVENT_CARD_RESET , CS_EVENT_PRI_LOW ) ;
2006-01-06 02:02:03 +03:00
if ( skt - > callback )
skt - > callback - > resume ( skt ) ;
}
2005-04-17 02:20:36 +04:00
}
2008-08-03 12:07:45 +04:00
ret = 0 ;
2005-04-17 02:20:36 +04:00
} while ( 0 ) ;
2006-01-10 23:20:36 +03:00
mutex_unlock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
} /* reset_card */
EXPORT_SYMBOL ( pccard_reset_card ) ;
2005-06-28 03:28:53 +04:00
/* These shut down or wake up a socket. They are sort of user
* initiated versions of the APM suspend and resume actions .
*/
2005-04-17 02:20:36 +04:00
int pcmcia_suspend_card ( struct pcmcia_socket * skt )
{
int ret ;
2005-06-28 03:28:53 +04:00
2005-04-17 02:20:36 +04:00
cs_dbg ( skt , 1 , " suspending socket \n " ) ;
2006-01-10 23:20:36 +03:00
mutex_lock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
do {
if ( ! ( skt - > state & SOCKET_PRESENT ) ) {
2008-08-03 13:10:56 +04:00
ret = - ENODEV ;
2005-04-17 02:20:36 +04:00
break ;
}
if ( skt - > state & SOCKET_CARDBUS ) {
2008-08-03 12:47:59 +04:00
ret = - EPERM ;
2005-04-17 02:20:36 +04:00
break ;
}
2006-01-06 02:02:03 +03:00
if ( skt - > callback ) {
ret = skt - > callback - > suspend ( skt ) ;
if ( ret )
break ;
}
2005-04-17 02:20:36 +04:00
ret = socket_suspend ( skt ) ;
} while ( 0 ) ;
2006-01-10 23:20:36 +03:00
mutex_unlock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
} /* suspend_card */
2005-06-28 03:28:53 +04:00
EXPORT_SYMBOL ( pcmcia_suspend_card ) ;
2005-04-17 02:20:36 +04:00
int pcmcia_resume_card ( struct pcmcia_socket * skt )
{
int ret ;
cs_dbg ( skt , 1 , " waking up socket \n " ) ;
2006-01-10 23:20:36 +03:00
mutex_lock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
do {
if ( ! ( skt - > state & SOCKET_PRESENT ) ) {
2008-08-03 13:10:56 +04:00
ret = - ENODEV ;
2005-04-17 02:20:36 +04:00
break ;
}
if ( skt - > state & SOCKET_CARDBUS ) {
2008-08-03 12:47:59 +04:00
ret = - EPERM ;
2005-04-17 02:20:36 +04:00
break ;
}
ret = socket_resume ( skt ) ;
2006-01-06 02:02:03 +03:00
if ( ! ret & & skt - > callback )
skt - > callback - > resume ( skt ) ;
2005-04-17 02:20:36 +04:00
} while ( 0 ) ;
2006-01-10 23:20:36 +03:00
mutex_unlock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
} /* resume_card */
2005-06-28 03:28:53 +04:00
EXPORT_SYMBOL ( pcmcia_resume_card ) ;
2005-04-17 02:20:36 +04:00
2005-06-28 03:28:53 +04:00
/* These handle user requests to eject or insert a card. */
2005-04-17 02:20:36 +04:00
int pcmcia_eject_card ( struct pcmcia_socket * skt )
{
int ret ;
cs_dbg ( skt , 1 , " user eject request \n " ) ;
2006-01-10 23:20:36 +03:00
mutex_lock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
do {
if ( ! ( skt - > state & SOCKET_PRESENT ) ) {
ret = - ENODEV ;
break ;
}
ret = send_event ( skt , CS_EVENT_EJECTION_REQUEST , CS_EVENT_PRI_LOW ) ;
if ( ret ! = 0 ) {
ret = - EINVAL ;
break ;
}
socket_remove ( skt ) ;
ret = 0 ;
} while ( 0 ) ;
2006-01-10 23:20:36 +03:00
mutex_unlock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
} /* eject_card */
2005-06-28 03:28:53 +04:00
EXPORT_SYMBOL ( pcmcia_eject_card ) ;
2005-04-17 02:20:36 +04:00
int pcmcia_insert_card ( struct pcmcia_socket * skt )
{
int ret ;
cs_dbg ( skt , 1 , " user insert request \n " ) ;
2006-01-10 23:20:36 +03:00
mutex_lock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
do {
if ( skt - > state & SOCKET_PRESENT ) {
ret = - EBUSY ;
break ;
}
2008-08-03 13:10:56 +04:00
if ( socket_insert ( skt ) = = - ENODEV ) {
2005-04-17 02:20:36 +04:00
ret = - ENODEV ;
break ;
}
ret = 0 ;
} while ( 0 ) ;
2006-01-10 23:20:36 +03:00
mutex_unlock ( & skt - > skt_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
} /* insert_card */
2005-06-28 03:28:53 +04:00
EXPORT_SYMBOL ( pcmcia_insert_card ) ;
2005-04-17 02:20:36 +04:00
2007-08-14 17:15:12 +04:00
static int pcmcia_socket_uevent ( struct device * dev ,
struct kobj_uevent_env * env )
2005-06-28 03:28:05 +04:00
{
struct pcmcia_socket * s = container_of ( dev , struct pcmcia_socket , dev ) ;
2007-08-14 17:15:12 +04:00
if ( add_uevent_var ( env , " SOCKET_NO=%u " , s - > sock ) )
2005-06-28 03:28:05 +04:00
return - ENOMEM ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
2005-06-28 03:28:54 +04:00
static struct completion pcmcia_unload ;
static void pcmcia_release_socket_class ( struct class * data )
{
complete ( & pcmcia_unload ) ;
}
2005-04-17 02:20:36 +04:00
struct class pcmcia_socket_class = {
. name = " pcmcia_socket " ,
2006-09-12 19:00:10 +04:00
. dev_uevent = pcmcia_socket_uevent ,
. dev_release = pcmcia_release_socket ,
2005-06-28 03:28:54 +04:00
. class_release = pcmcia_release_socket_class ,
2005-04-17 02:20:36 +04:00
} ;
EXPORT_SYMBOL ( pcmcia_socket_class ) ;
static int __init init_pcmcia_cs ( void )
{
2005-06-28 03:28:54 +04:00
init_completion ( & pcmcia_unload ) ;
2008-04-28 12:03:20 +04:00
return class_register ( & pcmcia_socket_class ) ;
2005-04-17 02:20:36 +04:00
}
static void __exit exit_pcmcia_cs ( void )
{
2005-06-28 03:28:53 +04:00
class_unregister ( & pcmcia_socket_class ) ;
2005-06-28 03:28:54 +04:00
wait_for_completion ( & pcmcia_unload ) ;
2005-04-17 02:20:36 +04:00
}
subsys_initcall ( init_pcmcia_cs ) ;
module_exit ( exit_pcmcia_cs ) ;