2008-02-09 21:20:54 +03:00
/*
* Sony MemoryStick support
*
* Copyright ( C ) 2007 Alex Dubov < oakad @ yahoo . com >
*
* 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 .
*
* Special thanks to Carlos Corbacho for providing various MemoryStick cards
* that made this driver possible .
*
*/
# include <linux/memstick.h>
# include <linux/idr.h>
# include <linux/fs.h>
# include <linux/delay.h>
# define DRIVER_NAME "memstick"
static unsigned int cmd_retries = 3 ;
module_param ( cmd_retries , uint , 0644 ) ;
static struct workqueue_struct * workqueue ;
static DEFINE_IDR ( memstick_host_idr ) ;
static DEFINE_SPINLOCK ( memstick_host_lock ) ;
static int memstick_dev_match ( struct memstick_dev * card ,
struct memstick_device_id * id )
{
if ( id - > match_flags & MEMSTICK_MATCH_ALL ) {
if ( ( id - > type = = card - > id . type )
& & ( id - > category = = card - > id . category )
& & ( id - > class = = card - > id . class ) )
return 1 ;
}
return 0 ;
}
static int memstick_bus_match ( struct device * dev , struct device_driver * drv )
{
struct memstick_dev * card = container_of ( dev , struct memstick_dev ,
dev ) ;
struct memstick_driver * ms_drv = container_of ( drv ,
struct memstick_driver ,
driver ) ;
struct memstick_device_id * ids = ms_drv - > id_table ;
if ( ids ) {
while ( ids - > match_flags ) {
if ( memstick_dev_match ( card , ids ) )
return 1 ;
+ + ids ;
}
}
return 0 ;
}
static int memstick_uevent ( struct device * dev , struct kobj_uevent_env * env )
{
struct memstick_dev * card = container_of ( dev , struct memstick_dev ,
dev ) ;
if ( add_uevent_var ( env , " MEMSTICK_TYPE=%02X " , card - > id . type ) )
return - ENOMEM ;
if ( add_uevent_var ( env , " MEMSTICK_CATEGORY=%02X " , card - > id . category ) )
return - ENOMEM ;
if ( add_uevent_var ( env , " MEMSTICK_CLASS=%02X " , card - > id . class ) )
return - ENOMEM ;
return 0 ;
}
static int memstick_device_probe ( struct device * dev )
{
struct memstick_dev * card = container_of ( dev , struct memstick_dev ,
dev ) ;
struct memstick_driver * drv = container_of ( dev - > driver ,
struct memstick_driver ,
driver ) ;
int rc = - ENODEV ;
if ( dev - > driver & & drv - > probe ) {
rc = drv - > probe ( card ) ;
if ( ! rc )
get_device ( dev ) ;
}
return rc ;
}
static int memstick_device_remove ( struct device * dev )
{
struct memstick_dev * card = container_of ( dev , struct memstick_dev ,
dev ) ;
struct memstick_driver * drv = container_of ( dev - > driver ,
struct memstick_driver ,
driver ) ;
if ( dev - > driver & & drv - > remove ) {
drv - > remove ( card ) ;
card - > dev . driver = NULL ;
}
put_device ( dev ) ;
return 0 ;
}
# ifdef CONFIG_PM
static int memstick_device_suspend ( struct device * dev , pm_message_t state )
{
struct memstick_dev * card = container_of ( dev , struct memstick_dev ,
dev ) ;
struct memstick_driver * drv = container_of ( dev - > driver ,
struct memstick_driver ,
driver ) ;
if ( dev - > driver & & drv - > suspend )
return drv - > suspend ( card , state ) ;
return 0 ;
}
static int memstick_device_resume ( struct device * dev )
{
struct memstick_dev * card = container_of ( dev , struct memstick_dev ,
dev ) ;
struct memstick_driver * drv = container_of ( dev - > driver ,
struct memstick_driver ,
driver ) ;
if ( dev - > driver & & drv - > resume )
return drv - > resume ( card ) ;
return 0 ;
}
# else
# define memstick_device_suspend NULL
# define memstick_device_resume NULL
# endif /* CONFIG_PM */
# define MEMSTICK_ATTR(name, format) \
static ssize_t name # # _show ( struct device * dev , struct device_attribute * attr , \
char * buf ) \
{ \
struct memstick_dev * card = container_of ( dev , struct memstick_dev , \
dev ) ; \
return sprintf ( buf , format , card - > id . name ) ; \
}
MEMSTICK_ATTR ( type , " %02X " ) ;
MEMSTICK_ATTR ( category , " %02X " ) ;
MEMSTICK_ATTR ( class , " %02X " ) ;
# define MEMSTICK_ATTR_RO(name) __ATTR(name, S_IRUGO, name##_show, NULL)
static struct device_attribute memstick_dev_attrs [ ] = {
MEMSTICK_ATTR_RO ( type ) ,
MEMSTICK_ATTR_RO ( category ) ,
MEMSTICK_ATTR_RO ( class ) ,
__ATTR_NULL
} ;
static struct bus_type memstick_bus_type = {
. name = " memstick " ,
. dev_attrs = memstick_dev_attrs ,
. match = memstick_bus_match ,
. uevent = memstick_uevent ,
. probe = memstick_device_probe ,
. remove = memstick_device_remove ,
. suspend = memstick_device_suspend ,
. resume = memstick_device_resume
} ;
2008-03-04 02:13:36 +03:00
static void memstick_free ( struct device * dev )
2008-02-09 21:20:54 +03:00
{
2008-03-04 02:13:36 +03:00
struct memstick_host * host = container_of ( dev , struct memstick_host ,
dev ) ;
2008-02-09 21:20:54 +03:00
kfree ( host ) ;
}
static struct class memstick_host_class = {
2008-09-13 13:33:26 +04:00
. name = " memstick_host " ,
2008-03-04 02:13:36 +03:00
. dev_release = memstick_free
2008-02-09 21:20:54 +03:00
} ;
static void memstick_free_card ( struct device * dev )
{
struct memstick_dev * card = container_of ( dev , struct memstick_dev ,
dev ) ;
kfree ( card ) ;
}
static int memstick_dummy_check ( struct memstick_dev * card )
{
return 0 ;
}
/**
* memstick_detect_change - schedule media detection on memstick host
* @ host - host to use
*/
void memstick_detect_change ( struct memstick_host * host )
{
queue_work ( workqueue , & host - > media_checker ) ;
}
EXPORT_SYMBOL ( memstick_detect_change ) ;
/**
* memstick_next_req - called by host driver to obtain next request to process
* @ host - host to use
* @ mrq - pointer to stick the request to
*
* Host calls this function from idle state ( * mrq = = NULL ) or after finishing
* previous request ( * mrq should point to it ) . If previous request was
* unsuccessful , it is retried for predetermined number of times . Return value
* of 0 means that new request was assigned to the host .
*/
int memstick_next_req ( struct memstick_host * host , struct memstick_request * * mrq )
{
int rc = - ENXIO ;
if ( ( * mrq ) & & ( * mrq ) - > error & & host - > retries ) {
( * mrq ) - > error = rc ;
host - > retries - - ;
return 0 ;
}
if ( host - > card & & host - > card - > next_request )
rc = host - > card - > next_request ( host - > card , mrq ) ;
if ( ! rc )
2008-03-10 21:43:38 +03:00
host - > retries = cmd_retries > 1 ? cmd_retries - 1 : 1 ;
2008-02-09 21:20:54 +03:00
else
* mrq = NULL ;
return rc ;
}
EXPORT_SYMBOL ( memstick_next_req ) ;
/**
* memstick_new_req - notify the host that some requests are pending
* @ host - host to use
*/
void memstick_new_req ( struct memstick_host * host )
{
2008-07-26 06:45:02 +04:00
if ( host - > card ) {
host - > retries = cmd_retries ;
INIT_COMPLETION ( host - > card - > mrq_complete ) ;
host - > request ( host ) ;
}
2008-02-09 21:20:54 +03:00
}
EXPORT_SYMBOL ( memstick_new_req ) ;
/**
* memstick_init_req_sg - set request fields needed for bulk data transfer
* @ mrq - request to use
* @ tpc - memstick Transport Protocol Command
* @ sg - TPC argument
*/
void memstick_init_req_sg ( struct memstick_request * mrq , unsigned char tpc ,
2008-09-13 13:33:26 +04:00
const struct scatterlist * sg )
2008-02-09 21:20:54 +03:00
{
mrq - > tpc = tpc ;
if ( tpc & 8 )
mrq - > data_dir = WRITE ;
else
mrq - > data_dir = READ ;
mrq - > sg = * sg ;
2008-03-10 21:43:37 +03:00
mrq - > long_data = 1 ;
2008-02-09 21:20:54 +03:00
if ( tpc = = MS_TPC_SET_CMD | | tpc = = MS_TPC_EX_SET_CMD )
mrq - > need_card_int = 1 ;
else
mrq - > need_card_int = 0 ;
}
EXPORT_SYMBOL ( memstick_init_req_sg ) ;
/**
* memstick_init_req - set request fields needed for short data transfer
* @ mrq - request to use
* @ tpc - memstick Transport Protocol Command
* @ buf - TPC argument buffer
* @ length - TPC argument size
*
* The intended use of this function ( transfer of data items several bytes
* in size ) allows us to just copy the value between request structure and
* user supplied buffer .
*/
void memstick_init_req ( struct memstick_request * mrq , unsigned char tpc ,
2008-09-13 13:33:26 +04:00
const void * buf , size_t length )
2008-02-09 21:20:54 +03:00
{
mrq - > tpc = tpc ;
if ( tpc & 8 )
mrq - > data_dir = WRITE ;
else
mrq - > data_dir = READ ;
mrq - > data_len = length > sizeof ( mrq - > data ) ? sizeof ( mrq - > data ) : length ;
if ( mrq - > data_dir = = WRITE )
memcpy ( mrq - > data , buf , mrq - > data_len ) ;
2008-03-10 21:43:37 +03:00
mrq - > long_data = 0 ;
2008-02-09 21:20:54 +03:00
if ( tpc = = MS_TPC_SET_CMD | | tpc = = MS_TPC_EX_SET_CMD )
mrq - > need_card_int = 1 ;
else
mrq - > need_card_int = 0 ;
}
EXPORT_SYMBOL ( memstick_init_req ) ;
/*
* Functions prefixed with " h_ " are protocol callbacks . They can be called from
* interrupt context . Return value of 0 means that request processing is still
* ongoing , while special error value of - EAGAIN means that current request is
* finished ( and request processor should come back some time later ) .
*/
static int h_memstick_read_dev_id ( struct memstick_dev * card ,
struct memstick_request * * mrq )
{
struct ms_id_register id_reg ;
if ( ! ( * mrq ) ) {
memstick_init_req ( & card - > current_mrq , MS_TPC_READ_REG , NULL ,
sizeof ( struct ms_id_register ) ) ;
* mrq = & card - > current_mrq ;
return 0 ;
} else {
if ( ! ( * mrq ) - > error ) {
memcpy ( & id_reg , ( * mrq ) - > data , sizeof ( id_reg ) ) ;
card - > id . match_flags = MEMSTICK_MATCH_ALL ;
card - > id . type = id_reg . type ;
card - > id . category = id_reg . category ;
card - > id . class = id_reg . class ;
}
complete ( & card - > mrq_complete ) ;
2008-03-20 03:01:06 +03:00
dev_dbg ( & card - > dev , " if_mode = %02x \n " , id_reg . if_mode ) ;
2008-02-09 21:20:54 +03:00
return - EAGAIN ;
}
}
static int h_memstick_set_rw_addr ( struct memstick_dev * card ,
struct memstick_request * * mrq )
{
if ( ! ( * mrq ) ) {
memstick_init_req ( & card - > current_mrq , MS_TPC_SET_RW_REG_ADRS ,
( char * ) & card - > reg_addr ,
sizeof ( card - > reg_addr ) ) ;
* mrq = & card - > current_mrq ;
return 0 ;
} else {
complete ( & card - > mrq_complete ) ;
return - EAGAIN ;
}
}
/**
* memstick_set_rw_addr - issue SET_RW_REG_ADDR request and wait for it to
* complete
* @ card - media device to use
*/
int memstick_set_rw_addr ( struct memstick_dev * card )
{
card - > next_request = h_memstick_set_rw_addr ;
memstick_new_req ( card - > host ) ;
wait_for_completion ( & card - > mrq_complete ) ;
return card - > current_mrq . error ;
}
EXPORT_SYMBOL ( memstick_set_rw_addr ) ;
static struct memstick_dev * memstick_alloc_card ( struct memstick_host * host )
{
struct memstick_dev * card = kzalloc ( sizeof ( struct memstick_dev ) ,
GFP_KERNEL ) ;
struct memstick_dev * old_card = host - > card ;
struct ms_id_register id_reg ;
if ( card ) {
card - > host = host ;
2009-01-06 21:44:38 +03:00
dev_set_name ( & card - > dev , " %s " , dev_name ( & host - > dev ) ) ;
2008-03-04 02:13:36 +03:00
card - > dev . parent = & host - > dev ;
2008-02-09 21:20:54 +03:00
card - > dev . bus = & memstick_bus_type ;
card - > dev . release = memstick_free_card ;
card - > check = memstick_dummy_check ;
card - > reg_addr . r_offset = offsetof ( struct ms_register , id ) ;
card - > reg_addr . r_length = sizeof ( id_reg ) ;
card - > reg_addr . w_offset = offsetof ( struct ms_register , id ) ;
card - > reg_addr . w_length = sizeof ( id_reg ) ;
init_completion ( & card - > mrq_complete ) ;
host - > card = card ;
if ( memstick_set_rw_addr ( card ) )
goto err_out ;
card - > next_request = h_memstick_read_dev_id ;
memstick_new_req ( host ) ;
wait_for_completion ( & card - > mrq_complete ) ;
if ( card - > current_mrq . error )
goto err_out ;
}
host - > card = old_card ;
return card ;
err_out :
host - > card = old_card ;
kfree ( card ) ;
return NULL ;
}
2008-07-26 06:45:00 +04:00
static int memstick_power_on ( struct memstick_host * host )
2008-02-09 21:20:54 +03:00
{
2008-07-26 06:45:00 +04:00
int rc = host - > set_param ( host , MEMSTICK_POWER , MEMSTICK_POWER_ON ) ;
if ( ! rc )
rc = host - > set_param ( host , MEMSTICK_INTERFACE , MEMSTICK_SERIAL ) ;
return rc ;
2008-02-09 21:20:54 +03:00
}
static void memstick_check ( struct work_struct * work )
{
struct memstick_host * host = container_of ( work , struct memstick_host ,
media_checker ) ;
struct memstick_dev * card ;
2008-03-04 02:13:36 +03:00
dev_dbg ( & host - > dev , " memstick_check started \n " ) ;
2008-02-09 21:20:54 +03:00
mutex_lock ( & host - > lock ) ;
2008-07-26 06:45:01 +04:00
if ( ! host - > card ) {
if ( memstick_power_on ( host ) )
goto out_power_off ;
2008-09-13 13:33:26 +04:00
} else if ( host - > card - > stop )
2008-07-26 06:45:01 +04:00
host - > card - > stop ( host - > card ) ;
2008-02-09 21:20:54 +03:00
card = memstick_alloc_card ( host ) ;
if ( ! card ) {
if ( host - > card ) {
device_unregister ( & host - > card - > dev ) ;
host - > card = NULL ;
}
} else {
2008-03-04 02:13:36 +03:00
dev_dbg ( & host - > dev , " new card %02x, %02x, %02x \n " ,
2008-02-09 21:20:54 +03:00
card - > id . type , card - > id . category , card - > id . class ) ;
if ( host - > card ) {
if ( memstick_set_rw_addr ( host - > card )
| | ! memstick_dev_match ( host - > card , & card - > id )
| | ! ( host - > card - > check ( host - > card ) ) ) {
device_unregister ( & host - > card - > dev ) ;
host - > card = NULL ;
2008-09-13 13:33:26 +04:00
} else if ( host - > card - > start )
2008-07-26 06:45:01 +04:00
host - > card - > start ( host - > card ) ;
2008-02-09 21:20:54 +03:00
}
if ( ! host - > card ) {
host - > card = card ;
if ( device_register ( & card - > dev ) ) {
kfree ( host - > card ) ;
host - > card = NULL ;
}
} else
kfree ( card ) ;
}
2008-07-26 06:45:01 +04:00
out_power_off :
2008-02-09 21:20:54 +03:00
if ( ! host - > card )
host - > set_param ( host , MEMSTICK_POWER , MEMSTICK_POWER_OFF ) ;
mutex_unlock ( & host - > lock ) ;
2008-03-04 02:13:36 +03:00
dev_dbg ( & host - > dev , " memstick_check finished \n " ) ;
2008-02-09 21:20:54 +03:00
}
/**
* memstick_alloc_host - allocate a memstick_host structure
* @ extra : size of the user private data to allocate
* @ dev : parent device of the host
*/
struct memstick_host * memstick_alloc_host ( unsigned int extra ,
struct device * dev )
{
struct memstick_host * host ;
host = kzalloc ( sizeof ( struct memstick_host ) + extra , GFP_KERNEL ) ;
if ( host ) {
mutex_init ( & host - > lock ) ;
INIT_WORK ( & host - > media_checker , memstick_check ) ;
2008-03-04 02:13:36 +03:00
host - > dev . class = & memstick_host_class ;
host - > dev . parent = dev ;
device_initialize ( & host - > dev ) ;
2008-02-09 21:20:54 +03:00
}
return host ;
}
EXPORT_SYMBOL ( memstick_alloc_host ) ;
/**
* memstick_add_host - start request processing on memstick host
* @ host - host to use
*/
int memstick_add_host ( struct memstick_host * host )
{
int rc ;
if ( ! idr_pre_get ( & memstick_host_idr , GFP_KERNEL ) )
return - ENOMEM ;
spin_lock ( & memstick_host_lock ) ;
rc = idr_get_new ( & memstick_host_idr , host , & host - > id ) ;
spin_unlock ( & memstick_host_lock ) ;
if ( rc )
return rc ;
2009-01-06 21:44:38 +03:00
dev_set_name ( & host - > dev , " memstick%u " , host - > id ) ;
2008-02-09 21:20:54 +03:00
2008-03-04 02:13:36 +03:00
rc = device_add ( & host - > dev ) ;
2008-02-09 21:20:54 +03:00
if ( rc ) {
spin_lock ( & memstick_host_lock ) ;
idr_remove ( & memstick_host_idr , host - > id ) ;
spin_unlock ( & memstick_host_lock ) ;
return rc ;
}
host - > set_param ( host , MEMSTICK_POWER , MEMSTICK_POWER_OFF ) ;
memstick_detect_change ( host ) ;
return 0 ;
}
EXPORT_SYMBOL ( memstick_add_host ) ;
/**
* memstick_remove_host - stop request processing on memstick host
* @ host - host to use
*/
void memstick_remove_host ( struct memstick_host * host )
{
flush_workqueue ( workqueue ) ;
mutex_lock ( & host - > lock ) ;
if ( host - > card )
device_unregister ( & host - > card - > dev ) ;
host - > card = NULL ;
host - > set_param ( host , MEMSTICK_POWER , MEMSTICK_POWER_OFF ) ;
mutex_unlock ( & host - > lock ) ;
spin_lock ( & memstick_host_lock ) ;
idr_remove ( & memstick_host_idr , host - > id ) ;
spin_unlock ( & memstick_host_lock ) ;
2008-03-04 02:13:36 +03:00
device_del ( & host - > dev ) ;
2008-02-09 21:20:54 +03:00
}
EXPORT_SYMBOL ( memstick_remove_host ) ;
/**
* memstick_free_host - free memstick host
* @ host - host to use
*/
void memstick_free_host ( struct memstick_host * host )
{
mutex_destroy ( & host - > lock ) ;
2008-03-04 02:13:36 +03:00
put_device ( & host - > dev ) ;
2008-02-09 21:20:54 +03:00
}
EXPORT_SYMBOL ( memstick_free_host ) ;
2008-03-10 21:43:38 +03:00
/**
* memstick_suspend_host - notify bus driver of host suspension
* @ host - host to use
*/
void memstick_suspend_host ( struct memstick_host * host )
{
mutex_lock ( & host - > lock ) ;
host - > set_param ( host , MEMSTICK_POWER , MEMSTICK_POWER_OFF ) ;
mutex_unlock ( & host - > lock ) ;
}
EXPORT_SYMBOL ( memstick_suspend_host ) ;
/**
* memstick_resume_host - notify bus driver of host resumption
* @ host - host to use
*/
void memstick_resume_host ( struct memstick_host * host )
{
2008-07-26 06:45:00 +04:00
int rc = 0 ;
2008-03-10 21:43:38 +03:00
mutex_lock ( & host - > lock ) ;
2008-03-20 03:01:06 +03:00
if ( host - > card )
2008-07-26 06:45:00 +04:00
rc = memstick_power_on ( host ) ;
2008-03-10 21:43:38 +03:00
mutex_unlock ( & host - > lock ) ;
2008-07-26 06:45:00 +04:00
if ( ! rc )
memstick_detect_change ( host ) ;
2008-03-10 21:43:38 +03:00
}
EXPORT_SYMBOL ( memstick_resume_host ) ;
2008-02-09 21:20:54 +03:00
int memstick_register_driver ( struct memstick_driver * drv )
{
drv - > driver . bus = & memstick_bus_type ;
return driver_register ( & drv - > driver ) ;
}
EXPORT_SYMBOL ( memstick_register_driver ) ;
void memstick_unregister_driver ( struct memstick_driver * drv )
{
driver_unregister ( & drv - > driver ) ;
}
EXPORT_SYMBOL ( memstick_unregister_driver ) ;
static int __init memstick_init ( void )
{
int rc ;
workqueue = create_freezeable_workqueue ( " kmemstick " ) ;
if ( ! workqueue )
return - ENOMEM ;
rc = bus_register ( & memstick_bus_type ) ;
if ( ! rc )
rc = class_register ( & memstick_host_class ) ;
if ( ! rc )
return 0 ;
bus_unregister ( & memstick_bus_type ) ;
destroy_workqueue ( workqueue ) ;
return rc ;
}
static void __exit memstick_exit ( void )
{
class_unregister ( & memstick_host_class ) ;
bus_unregister ( & memstick_bus_type ) ;
destroy_workqueue ( workqueue ) ;
idr_destroy ( & memstick_host_idr ) ;
}
module_init ( memstick_init ) ;
module_exit ( memstick_exit ) ;
MODULE_AUTHOR ( " Alex Dubov " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Sony MemoryStick core driver " ) ;