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/config.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/pci.h>
# include <linux/device.h>
# include <asm/system.h>
# include <asm/irq.h>
# define IN_CARD_SERVICES
# include <pcmcia/cs_types.h>
# include <pcmcia/ss.h>
# include <pcmcia/cs.h>
# include <pcmcia/bulkmem.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 */
# ifdef DEBUG
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
/**
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 ) {
if ( socket - > dev . dev ! = dev )
continue ;
down ( & socket - > skt_sem ) ;
socket_suspend ( socket ) ;
up ( & socket - > skt_sem ) ;
}
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 ) {
if ( socket - > dev . dev ! = dev )
continue ;
down ( & socket - > skt_sem ) ;
socket_resume ( socket ) ;
up ( & socket - > skt_sem ) ;
}
up_read ( & pcmcia_socket_list_rwsem ) ;
return 0 ;
}
EXPORT_SYMBOL ( pcmcia_socket_dev_resume ) ;
struct pcmcia_socket * pcmcia_get_socket ( struct pcmcia_socket * skt )
{
struct class_device * cl_dev = class_device_get ( & skt - > dev ) ;
if ( ! cl_dev )
return NULL ;
skt = class_get_devdata ( cl_dev ) ;
if ( ! try_module_get ( skt - > owner ) ) {
class_device_put ( & skt - > dev ) ;
return NULL ;
}
return ( skt ) ;
}
EXPORT_SYMBOL ( pcmcia_get_socket ) ;
void pcmcia_put_socket ( struct pcmcia_socket * skt )
{
module_put ( skt - > owner ) ;
class_device_put ( & skt - > dev ) ;
}
EXPORT_SYMBOL ( pcmcia_put_socket ) ;
static void pcmcia_release_socket ( struct class_device * class_dev )
{
struct pcmcia_socket * socket = class_get_devdata ( class_dev ) ;
complete ( & socket - > socket_released ) ;
}
static int pccardd ( void * __skt ) ;
/**
* pcmcia_register_socket - add a new pcmcia socket device
*/
int pcmcia_register_socket ( struct pcmcia_socket * socket )
{
int ret ;
if ( ! socket | | ! socket - > ops | | ! socket - > dev . dev | | ! socket - > resource_ops )
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 */
socket - > dev . class_data = socket ;
socket - > dev . class = & pcmcia_socket_class ;
snprintf ( socket - > dev . class_id , BUS_ID_SIZE , " pcmcia_socket%u " , socket - > sock ) ;
/* 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 ) ;
init_waitqueue_head ( & socket - > thread_wait ) ;
init_MUTEX ( & socket - > skt_sem ) ;
spin_lock_init ( & socket - > thread_lock ) ;
ret = kernel_thread ( pccardd , socket , CLONE_KERNEL ) ;
if ( ret < 0 )
goto err ;
wait_for_completion ( & socket - > thread_done ) ;
if ( ! socket - > thread ) {
printk ( KERN_WARNING " PCMCIA: warning: socket thread for socket %p did not start \n " , socket ) ;
return - EIO ;
}
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
*/
void pcmcia_unregister_socket ( struct pcmcia_socket * socket )
{
if ( ! socket )
return ;
cs_dbg ( socket , 0 , " pcmcia_unregister_socket(0x%p) \n " , socket - > ops ) ;
if ( socket - > thread ) {
init_completion ( & socket - > thread_done ) ;
socket - > thread = NULL ;
wake_up ( & socket - > thread_wait ) ;
wait_for_completion ( & socket - > thread_done ) ;
}
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 ) ;
2005-06-28 03:28:53 +04:00
/**
* socket_setup ( ) and shutdown_socket ( ) 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 .
* shutdown_socket ( ) unconfigures a socket and turns off socket power .
*/
2005-04-17 02:20:36 +04:00
static void shutdown_socket ( struct pcmcia_socket * s )
{
2005-06-28 03:28:53 +04:00
cs_dbg ( s , 1 , " shutdown_socket \n " ) ;
/* 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 ) ;
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_CARDBUS
2005-06-28 03:28:53 +04:00
cb_free ( s ) ;
2005-04-17 02:20:36 +04:00
# endif
2005-06-28 03:28:53 +04:00
s - > functions = 0 ;
if ( s - > config ) {
kfree ( s - > config ) ;
s - > config = NULL ;
2005-04-17 02:20:36 +04:00
}
2005-06-28 03:28:53 +04:00
{
int status ;
s - > ops - > get_status ( s , & status ) ;
if ( status & SS_POWERON ) {
printk ( KERN_ERR " PCMCIA: socket %p: *** DANGER *** unable to remove socket power \n " , s ) ;
}
}
} /* shutdown_socket */
2005-04-17 02:20:36 +04: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 void socket_shutdown ( struct pcmcia_socket * skt )
{
cs_dbg ( skt , 4 , " shutdown \n " ) ;
socket_remove_drivers ( skt ) ;
skt - > state & = SOCKET_INUSE | SOCKET_PRESENT ;
msleep ( shutdown_delay * 10 ) ;
skt - > state & = SOCKET_INUSE ;
shutdown_socket ( skt ) ;
}
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 ) )
return CS_NO_CARD ;
if ( status & SS_READY )
return CS_SUCCESS ;
msleep ( unreset_check * 10 ) ;
}
cs_err ( skt , " time out after reset. \n " ) ;
return CS_GENERAL_FAILURE ;
}
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 ) )
return CS_NO_CARD ;
msleep ( initial_delay * 10 ) ;
for ( i = 0 ; i < 100 ; i + + ) {
skt - > ops - > get_status ( skt , & status ) ;
if ( ! ( status & SS_DETECT ) )
return CS_NO_CARD ;
if ( ! ( status & SS_PENDING ) )
break ;
msleep ( 100 ) ;
}
if ( status & SS_PENDING ) {
cs_err ( skt , " voltage interrogation timed out. \n " ) ;
return CS_GENERAL_FAILURE ;
}
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 ) )
return CS_NO_CARD ;
ret = socket_setup ( skt , setup_delay ) ;
if ( ret = = CS_SUCCESS ) {
skt - > state | = SOCKET_PRESENT ;
# 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 ) ;
cs_socket_put ( skt ) ;
}
return ret ;
}
static int socket_suspend ( struct pcmcia_socket * skt )
{
if ( skt - > state & SOCKET_SUSPEND )
return CS_IN_USE ;
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 ;
return CS_SUCCESS ;
}
/*
* 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 ) )
return CS_IN_USE ;
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 ) ;
if ( ret = = CS_SUCCESS ) {
/*
* 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 ) ;
cs_socket_put ( skt ) ;
}
skt - > state & = ~ SOCKET_SUSPEND ;
return CS_SUCCESS ;
}
static void socket_remove ( struct pcmcia_socket * skt )
{
socket_shutdown ( skt ) ;
cs_socket_put ( 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 ;
DECLARE_WAITQUEUE ( wait , current ) ;
int ret ;
daemonize ( " pccardd " ) ;
skt - > thread = current ;
skt - > socket = dead_socket ;
skt - > ops - > init ( skt ) ;
skt - > ops - > set_socket ( skt , & skt - > socket ) ;
/* register with the device core */
ret = class_device_register ( & skt - > dev ) ;
if ( ret ) {
printk ( KERN_WARNING " PCMCIA: unable to register socket 0x%p \n " ,
skt ) ;
skt - > thread = NULL ;
complete_and_exit ( & skt - > thread_done , 0 ) ;
}
add_wait_queue ( & skt - > thread_wait , & wait ) ;
2005-09-10 00:03:23 +04:00
complete ( & skt - > thread_done ) ;
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 ) {
down ( & skt - > skt_sem ) ;
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 ) ;
up ( & skt - > skt_sem ) ;
continue ;
}
if ( ! skt - > thread )
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_wait_queue ( & skt - > thread_wait , & wait ) ;
/* remove from the device core */
class_device_unregister ( & skt - > dev ) ;
complete_and_exit ( & skt - > thread_done , 0 ) ;
}
/*
* 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 )
{
cs_dbg ( s , 4 , " parse_events: events %08x \n " , events ) ;
if ( s - > thread ) {
spin_lock ( & s - > thread_lock ) ;
s - > thread_events | = events ;
spin_unlock ( & s - > thread_lock ) ;
wake_up ( & s - > thread_wait ) ;
}
} /* 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 ;
/* s->skt_sem also protects s->callback */
down ( & s - > skt_sem ) ;
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 :
up ( & s - > skt_sem ) ;
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 " ) ;
down ( & skt - > skt_sem ) ;
do {
if ( ! ( skt - > state & SOCKET_PRESENT ) ) {
ret = CS_NO_CARD ;
break ;
}
if ( skt - > state & SOCKET_SUSPEND ) {
ret = CS_IN_USE ;
break ;
}
if ( skt - > state & SOCKET_CARDBUS ) {
ret = CS_UNSUPPORTED_FUNCTION ;
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 ) ;
if ( socket_reset ( skt ) = = CS_SUCCESS )
send_event ( skt , CS_EVENT_CARD_RESET , CS_EVENT_PRI_LOW ) ;
}
ret = CS_SUCCESS ;
} while ( 0 ) ;
up ( & skt - > skt_sem ) ;
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 " ) ;
down ( & skt - > skt_sem ) ;
do {
if ( ! ( skt - > state & SOCKET_PRESENT ) ) {
ret = CS_NO_CARD ;
break ;
}
if ( skt - > state & SOCKET_CARDBUS ) {
ret = CS_UNSUPPORTED_FUNCTION ;
break ;
}
ret = socket_suspend ( skt ) ;
} while ( 0 ) ;
up ( & skt - > skt_sem ) ;
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 " ) ;
down ( & skt - > skt_sem ) ;
do {
if ( ! ( skt - > state & SOCKET_PRESENT ) ) {
ret = CS_NO_CARD ;
break ;
}
if ( skt - > state & SOCKET_CARDBUS ) {
ret = CS_UNSUPPORTED_FUNCTION ;
break ;
}
ret = socket_resume ( skt ) ;
} while ( 0 ) ;
up ( & skt - > skt_sem ) ;
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 " ) ;
down ( & skt - > skt_sem ) ;
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 ) ;
up ( & skt - > skt_sem ) ;
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 " ) ;
down ( & skt - > skt_sem ) ;
do {
if ( skt - > state & SOCKET_PRESENT ) {
ret = - EBUSY ;
break ;
}
if ( socket_insert ( skt ) = = CS_NO_CARD ) {
ret = - ENODEV ;
break ;
}
ret = 0 ;
} while ( 0 ) ;
up ( & skt - > skt_sem ) ;
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
2005-06-28 03:28:05 +04:00
static int pcmcia_socket_hotplug ( struct class_device * dev , char * * envp ,
int num_envp , char * buffer , int buffer_size )
{
struct pcmcia_socket * s = container_of ( dev , struct pcmcia_socket , dev ) ;
int i = 0 , length = 0 ;
if ( add_hotplug_env_var ( envp , num_envp , & i , buffer , buffer_size ,
& length , " SOCKET_NO=%u " , s - > sock ) )
return - ENOMEM ;
envp [ i ] = NULL ;
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 " ,
2005-06-28 03:28:05 +04:00
. hotplug = pcmcia_socket_hotplug ,
2005-04-17 02:20:36 +04:00
. 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
int ret ;
init_completion ( & pcmcia_unload ) ;
ret = class_register ( & pcmcia_socket_class ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
return ( ret ) ;
return class_interface_register ( & pccard_sysfs_interface ) ;
}
static void __exit exit_pcmcia_cs ( void )
{
2005-06-28 03:28:53 +04:00
class_interface_unregister ( & pccard_sysfs_interface ) ;
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 ) ;