2005-04-17 02:20:36 +04:00
/*
* Block driver for media ( i . e . , flash cards )
*
* Copyright 2002 Hewlett - Packard Company
*
* Use consistent with the GNU GPL is permitted ,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works .
*
* HEWLETT - PACKARD COMPANY MAKES NO WARRANTIES , EXPRESSED OR IMPLIED ,
* AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
* FITNESS FOR ANY PARTICULAR PURPOSE .
*
* Many thanks to Alessandro Rubini and Jonathan Corbet !
*
* Author : Andrew Christian
* 28 May 2002
*/
# include <linux/moduleparam.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/sched.h>
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/errno.h>
# include <linux/hdreg.h>
# include <linux/kdev_t.h>
# include <linux/blkdev.h>
2006-01-12 21:43:35 +03:00
# include <linux/mutex.h>
2006-10-06 11:44:03 +04:00
# include <linux/scatterlist.h>
2005-04-17 02:20:36 +04:00
# include <linux/mmc/card.h>
2006-06-18 16:34:37 +04:00
# include <linux/mmc/host.h>
2005-04-17 02:20:36 +04:00
# include <linux/mmc/protocol.h>
2006-08-30 18:14:56 +04:00
# include <linux/mmc/host.h>
2005-04-17 02:20:36 +04:00
# include <asm/system.h>
# include <asm/uaccess.h>
# include "mmc_queue.h"
/*
* max 8 partitions per card
*/
# define MMC_SHIFT 3
static int major ;
/*
* There is one mmc_blk_data per slot .
*/
struct mmc_blk_data {
spinlock_t lock ;
struct gendisk * disk ;
struct mmc_queue queue ;
unsigned int usage ;
unsigned int block_bits ;
2006-01-04 01:38:44 +03:00
unsigned int read_only ;
2005-04-17 02:20:36 +04:00
} ;
2006-01-12 21:43:35 +03:00
static DEFINE_MUTEX ( open_lock ) ;
2005-04-17 02:20:36 +04:00
static struct mmc_blk_data * mmc_blk_get ( struct gendisk * disk )
{
struct mmc_blk_data * md ;
2006-01-12 21:43:35 +03:00
mutex_lock ( & open_lock ) ;
2005-04-17 02:20:36 +04:00
md = disk - > private_data ;
if ( md & & md - > usage = = 0 )
md = NULL ;
if ( md )
md - > usage + + ;
2006-01-12 21:43:35 +03:00
mutex_unlock ( & open_lock ) ;
2005-04-17 02:20:36 +04:00
return md ;
}
static void mmc_blk_put ( struct mmc_blk_data * md )
{
2006-01-12 21:43:35 +03:00
mutex_lock ( & open_lock ) ;
2005-04-17 02:20:36 +04:00
md - > usage - - ;
if ( md - > usage = = 0 ) {
put_disk ( md - > disk ) ;
kfree ( md ) ;
}
2006-01-12 21:43:35 +03:00
mutex_unlock ( & open_lock ) ;
2005-04-17 02:20:36 +04:00
}
static int mmc_blk_open ( struct inode * inode , struct file * filp )
{
struct mmc_blk_data * md ;
int ret = - ENXIO ;
md = mmc_blk_get ( inode - > i_bdev - > bd_disk ) ;
if ( md ) {
if ( md - > usage = = 2 )
check_disk_change ( inode - > i_bdev ) ;
ret = 0 ;
2005-09-07 02:18:52 +04:00
2006-01-04 01:38:44 +03:00
if ( ( filp - > f_mode & FMODE_WRITE ) & & md - > read_only )
2005-09-07 02:18:52 +04:00
ret = - EROFS ;
2005-04-17 02:20:36 +04:00
}
return ret ;
}
static int mmc_blk_release ( struct inode * inode , struct file * filp )
{
struct mmc_blk_data * md = inode - > i_bdev - > bd_disk - > private_data ;
mmc_blk_put ( md ) ;
return 0 ;
}
static int
2006-01-08 12:02:50 +03:00
mmc_blk_getgeo ( struct block_device * bdev , struct hd_geometry * geo )
2005-04-17 02:20:36 +04:00
{
2006-01-08 12:02:50 +03:00
geo - > cylinders = get_capacity ( bdev - > bd_disk ) / ( 4 * 16 ) ;
geo - > heads = 4 ;
geo - > sectors = 16 ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
static struct block_device_operations mmc_bdops = {
. open = mmc_blk_open ,
. release = mmc_blk_release ,
2006-01-08 12:02:50 +03:00
. getgeo = mmc_blk_getgeo ,
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
} ;
struct mmc_blk_request {
struct mmc_request mrq ;
struct mmc_command cmd ;
struct mmc_command stop ;
struct mmc_data data ;
} ;
static int mmc_blk_prep_rq ( struct mmc_queue * mq , struct request * req )
{
struct mmc_blk_data * md = mq - > data ;
int stat = BLKPREP_OK ;
/*
* If we have no device , we haven ' t finished initialising .
*/
if ( ! md | | ! mq - > card ) {
printk ( KERN_ERR " %s: killing request - no device/host \n " ,
req - > rq_disk - > disk_name ) ;
stat = BLKPREP_KILL ;
}
return stat ;
}
2006-10-06 11:44:03 +04:00
static u32 mmc_sd_num_wr_blocks ( struct mmc_card * card )
{
int err ;
u32 blocks ;
struct mmc_request mrq ;
struct mmc_command cmd ;
struct mmc_data data ;
unsigned int timeout_us ;
struct scatterlist sg ;
memset ( & cmd , 0 , sizeof ( struct mmc_command ) ) ;
cmd . opcode = MMC_APP_CMD ;
cmd . arg = card - > rca < < 16 ;
cmd . flags = MMC_RSP_R1 | MMC_CMD_AC ;
err = mmc_wait_for_cmd ( card - > host , & cmd , 0 ) ;
if ( ( err ! = MMC_ERR_NONE ) | | ! ( cmd . resp [ 0 ] & R1_APP_CMD ) )
return ( u32 ) - 1 ;
memset ( & cmd , 0 , sizeof ( struct mmc_command ) ) ;
cmd . opcode = SD_APP_SEND_NUM_WR_BLKS ;
cmd . arg = 0 ;
cmd . flags = MMC_RSP_R1 | MMC_CMD_ADTC ;
memset ( & data , 0 , sizeof ( struct mmc_data ) ) ;
data . timeout_ns = card - > csd . tacc_ns * 100 ;
data . timeout_clks = card - > csd . tacc_clks * 100 ;
timeout_us = data . timeout_ns / 1000 ;
timeout_us + = data . timeout_clks * 1000 /
( card - > host - > ios . clock / 1000 ) ;
if ( timeout_us > 100000 ) {
data . timeout_ns = 100000000 ;
data . timeout_clks = 0 ;
}
data . blksz = 4 ;
data . blocks = 1 ;
data . flags = MMC_DATA_READ ;
data . sg = & sg ;
data . sg_len = 1 ;
memset ( & mrq , 0 , sizeof ( struct mmc_request ) ) ;
mrq . cmd = & cmd ;
mrq . data = & data ;
sg_init_one ( & sg , & blocks , 4 ) ;
mmc_wait_for_req ( card - > host , & mrq ) ;
if ( cmd . error ! = MMC_ERR_NONE | | data . error ! = MMC_ERR_NONE )
return ( u32 ) - 1 ;
blocks = ntohl ( blocks ) ;
return blocks ;
}
2005-04-17 02:20:36 +04:00
static int mmc_blk_issue_rq ( struct mmc_queue * mq , struct request * req )
{
struct mmc_blk_data * md = mq - > data ;
struct mmc_card * card = md - > queue . card ;
2006-10-04 13:15:41 +04:00
struct mmc_blk_request brq ;
2006-11-15 00:13:13 +03:00
int ret = 1 ;
2005-04-17 02:20:36 +04:00
if ( mmc_card_claim_host ( card ) )
2006-11-15 00:13:13 +03:00
goto flush_queue ;
2005-04-17 02:20:36 +04:00
do {
struct mmc_command cmd ;
2006-08-30 18:14:56 +04:00
u32 readcmd , writecmd ;
2005-04-17 02:20:36 +04:00
memset ( & brq , 0 , sizeof ( struct mmc_blk_request ) ) ;
brq . mrq . cmd = & brq . cmd ;
brq . mrq . data = & brq . data ;
brq . cmd . arg = req - > sector < < 9 ;
2006-02-02 15:23:12 +03:00
brq . cmd . flags = MMC_RSP_R1 | MMC_CMD_ADTC ;
2006-05-20 00:48:03 +04:00
brq . data . blksz = 1 < < md - > block_bits ;
2005-04-17 02:20:36 +04:00
brq . data . blocks = req - > nr_sectors > > ( md - > block_bits - 9 ) ;
brq . stop . opcode = MMC_STOP_TRANSMISSION ;
brq . stop . arg = 0 ;
2006-02-02 15:23:12 +03:00
brq . stop . flags = MMC_RSP_R1B | MMC_CMD_AC ;
2005-04-17 02:20:36 +04:00
2006-09-07 18:57:12 +04:00
mmc_set_data_timeout ( & brq . data , card , rq_data_dir ( req ) ! = READ ) ;
2006-06-18 16:34:37 +04:00
2006-08-30 18:14:56 +04:00
/*
* If the host doesn ' t support multiple block writes , force
2006-10-06 11:44:03 +04:00
* block writes to single block . SD cards are excepted from
* this rule as they support querying the number of
* successfully written sectors .
2006-08-30 18:14:56 +04:00
*/
if ( rq_data_dir ( req ) ! = READ & &
2006-10-06 11:44:03 +04:00
! ( card - > host - > caps & MMC_CAP_MULTIWRITE ) & &
! mmc_card_sd ( card ) )
2005-04-17 02:20:36 +04:00
brq . data . blocks = 1 ;
2006-01-10 00:12:17 +03:00
if ( brq . data . blocks > 1 ) {
brq . data . flags | = MMC_DATA_MULTI ;
brq . mrq . stop = & brq . stop ;
2006-08-30 18:14:56 +04:00
readcmd = MMC_READ_MULTIPLE_BLOCK ;
writecmd = MMC_WRITE_MULTIPLE_BLOCK ;
2006-01-10 00:12:17 +03:00
} else {
brq . mrq . stop = NULL ;
2006-08-30 18:14:56 +04:00
readcmd = MMC_READ_SINGLE_BLOCK ;
writecmd = MMC_WRITE_BLOCK ;
}
if ( rq_data_dir ( req ) = = READ ) {
brq . cmd . opcode = readcmd ;
brq . data . flags | = MMC_DATA_READ ;
} else {
brq . cmd . opcode = writecmd ;
brq . data . flags | = MMC_DATA_WRITE ;
2006-01-10 00:12:17 +03:00
}
2005-04-17 02:20:36 +04:00
brq . data . sg = mq - > sg ;
brq . data . sg_len = blk_rq_map_sg ( req - > q , req , brq . data . sg ) ;
mmc_wait_for_req ( card - > host , & brq . mrq ) ;
if ( brq . cmd . error ) {
printk ( KERN_ERR " %s: error %d sending read/write command \n " ,
req - > rq_disk - > disk_name , brq . cmd . error ) ;
goto cmd_err ;
}
if ( brq . data . error ) {
printk ( KERN_ERR " %s: error %d transferring data \n " ,
req - > rq_disk - > disk_name , brq . data . error ) ;
goto cmd_err ;
}
if ( brq . stop . error ) {
printk ( KERN_ERR " %s: error %d sending stop command \n " ,
req - > rq_disk - > disk_name , brq . stop . error ) ;
goto cmd_err ;
}
2006-09-24 13:46:43 +04:00
if ( rq_data_dir ( req ) ! = READ ) {
do {
int err ;
cmd . opcode = MMC_SEND_STATUS ;
cmd . arg = card - > rca < < 16 ;
cmd . flags = MMC_RSP_R1 | MMC_CMD_AC ;
err = mmc_wait_for_cmd ( card - > host , & cmd , 5 ) ;
if ( err ) {
printk ( KERN_ERR " %s: error %d requesting status \n " ,
req - > rq_disk - > disk_name , err ) ;
goto cmd_err ;
}
} while ( ! ( cmd . resp [ 0 ] & R1_READY_FOR_DATA ) ) ;
2005-04-17 02:20:36 +04:00
#if 0
2006-09-24 13:46:43 +04:00
if ( cmd . resp [ 0 ] & ~ 0x00000900 )
printk ( KERN_ERR " %s: status = %08x \n " ,
req - > rq_disk - > disk_name , cmd . resp [ 0 ] ) ;
if ( mmc_decode_status ( cmd . resp ) )
goto cmd_err ;
2005-04-17 02:20:36 +04:00
# endif
2006-09-24 13:46:43 +04:00
}
2005-04-17 02:20:36 +04:00
/*
* A block was successfully transferred .
*/
spin_lock_irq ( & md - > lock ) ;
ret = end_that_request_chunk ( req , 1 , brq . data . bytes_xfered ) ;
if ( ! ret ) {
/*
* The whole request completed successfully .
*/
add_disk_randomness ( req - > rq_disk ) ;
blkdev_dequeue_request ( req ) ;
2006-01-06 11:49:03 +03:00
end_that_request_last ( req , 1 ) ;
2005-04-17 02:20:36 +04:00
}
spin_unlock_irq ( & md - > lock ) ;
} while ( ret ) ;
mmc_card_release_host ( card ) ;
return 1 ;
cmd_err :
2006-10-06 11:44:03 +04:00
/*
* If this is an SD card and we ' re writing , we can first
* mark the known good sectors as ok .
*
* If the card is not SD , we can still ok written sectors
* if the controller can do proper error reporting .
2006-10-04 13:15:41 +04:00
*
* For reads we just fail the entire chunk as that should
* be safe in all cases .
2005-04-17 02:20:36 +04:00
*/
2006-10-06 11:44:03 +04:00
if ( rq_data_dir ( req ) ! = READ & & mmc_card_sd ( card ) ) {
u32 blocks ;
unsigned int bytes ;
blocks = mmc_sd_num_wr_blocks ( card ) ;
if ( blocks ! = ( u32 ) - 1 ) {
if ( card - > csd . write_partial )
bytes = blocks < < md - > block_bits ;
else
bytes = blocks < < 9 ;
spin_lock_irq ( & md - > lock ) ;
ret = end_that_request_chunk ( req , 1 , bytes ) ;
spin_unlock_irq ( & md - > lock ) ;
}
} else if ( rq_data_dir ( req ) ! = READ & &
( card - > host - > caps & MMC_CAP_MULTIWRITE ) ) {
2006-10-04 13:15:41 +04:00
spin_lock_irq ( & md - > lock ) ;
ret = end_that_request_chunk ( req , 1 , brq . data . bytes_xfered ) ;
spin_unlock_irq ( & md - > lock ) ;
}
2006-10-06 11:44:03 +04:00
mmc_card_release_host ( card ) ;
2006-11-15 00:13:13 +03:00
flush_queue :
2005-04-17 02:20:36 +04:00
spin_lock_irq ( & md - > lock ) ;
2006-10-04 13:15:41 +04:00
while ( ret ) {
2005-04-17 02:20:36 +04:00
ret = end_that_request_chunk ( req , 0 ,
req - > current_nr_sectors < < 9 ) ;
2006-10-04 13:15:41 +04:00
}
2005-04-17 02:20:36 +04:00
add_disk_randomness ( req - > rq_disk ) ;
blkdev_dequeue_request ( req ) ;
2006-01-06 11:49:03 +03:00
end_that_request_last ( req , 0 ) ;
2005-04-17 02:20:36 +04:00
spin_unlock_irq ( & md - > lock ) ;
return 0 ;
}
# define MMC_NUM_MINORS (256 >> MMC_SHIFT)
static unsigned long dev_use [ MMC_NUM_MINORS / ( 8 * sizeof ( unsigned long ) ) ] ;
2006-01-04 01:38:44 +03:00
static inline int mmc_blk_readonly ( struct mmc_card * card )
{
return mmc_card_readonly ( card ) | |
! ( card - > csd . cmdclass & CCC_BLOCK_WRITE ) ;
}
2005-04-17 02:20:36 +04:00
static struct mmc_blk_data * mmc_blk_alloc ( struct mmc_card * card )
{
struct mmc_blk_data * md ;
int devidx , ret ;
devidx = find_first_zero_bit ( dev_use , MMC_NUM_MINORS ) ;
if ( devidx > = MMC_NUM_MINORS )
return ERR_PTR ( - ENOSPC ) ;
__set_bit ( devidx , dev_use ) ;
md = kmalloc ( sizeof ( struct mmc_blk_data ) , GFP_KERNEL ) ;
2006-01-04 01:38:44 +03:00
if ( ! md ) {
ret = - ENOMEM ;
goto out ;
}
2005-04-17 02:20:36 +04:00
2006-01-04 01:38:44 +03:00
memset ( md , 0 , sizeof ( struct mmc_blk_data ) ) ;
2005-04-17 02:20:36 +04:00
2006-01-04 01:38:44 +03:00
/*
* Set the read - only status based on the supported commands
* and the write protect switch .
*/
md - > read_only = mmc_blk_readonly ( card ) ;
2005-04-17 02:20:36 +04:00
2006-01-04 01:38:44 +03:00
/*
2006-09-07 19:01:30 +04:00
* Both SD and MMC specifications state ( although a bit
* unclearly in the MMC case ) that a block size of 512
* bytes must always be supported by the card .
2006-01-04 01:38:44 +03:00
*/
2006-09-07 19:01:30 +04:00
md - > block_bits = 9 ;
2005-04-17 02:20:36 +04:00
2006-01-04 01:38:44 +03:00
md - > disk = alloc_disk ( 1 < < MMC_SHIFT ) ;
if ( md - > disk = = NULL ) {
ret = - ENOMEM ;
goto err_kfree ;
}
2005-04-17 02:20:36 +04:00
2006-01-04 01:38:44 +03:00
spin_lock_init ( & md - > lock ) ;
md - > usage = 1 ;
2005-04-17 02:20:36 +04:00
2006-01-04 01:38:44 +03:00
ret = mmc_init_queue ( & md - > queue , card , & md - > lock ) ;
if ( ret )
goto err_putdisk ;
2005-04-17 02:20:36 +04:00
2006-01-04 01:38:44 +03:00
md - > queue . prep_fn = mmc_blk_prep_rq ;
md - > queue . issue_fn = mmc_blk_issue_rq ;
md - > queue . data = md ;
2005-12-23 02:21:38 +03:00
2006-01-04 01:38:44 +03:00
md - > disk - > major = major ;
md - > disk - > first_minor = devidx < < MMC_SHIFT ;
md - > disk - > fops = & mmc_bdops ;
md - > disk - > private_data = md ;
md - > disk - > queue = md - > queue . queue ;
md - > disk - > driverfs_dev = & card - > dev ;
/*
* As discussed on lkml , GENHD_FL_REMOVABLE should :
*
* - be set for removable media with permanent block devices
* - be unset for removable block devices with permanent media
*
* Since MMC block devices clearly fall under the second
* case , we do not set GENHD_FL_REMOVABLE . Userspace
* should use the block device creation / destruction hotplug
* messages to tell when the card is present .
*/
sprintf ( md - > disk - > disk_name , " mmcblk%d " , devidx ) ;
blk_queue_hardsect_size ( md - > queue . queue , 1 < < md - > block_bits ) ;
/*
* The CSD capacity field is in units of read_blkbits .
* set_capacity takes units of 512 bytes .
*/
set_capacity ( md - > disk , card - > csd . capacity < < ( card - > csd . read_blkbits - 9 ) ) ;
2005-04-17 02:20:36 +04:00
return md ;
2006-01-04 01:38:44 +03:00
err_putdisk :
put_disk ( md - > disk ) ;
err_kfree :
kfree ( md ) ;
out :
return ERR_PTR ( ret ) ;
2005-04-17 02:20:36 +04:00
}
static int
mmc_blk_set_blksize ( struct mmc_blk_data * md , struct mmc_card * card )
{
struct mmc_command cmd ;
int err ;
mmc_card_claim_host ( card ) ;
cmd . opcode = MMC_SET_BLOCKLEN ;
2005-12-23 02:21:38 +03:00
cmd . arg = 1 < < md - > block_bits ;
2006-02-02 15:23:12 +03:00
cmd . flags = MMC_RSP_R1 | MMC_CMD_AC ;
2005-04-17 02:20:36 +04:00
err = mmc_wait_for_cmd ( card - > host , & cmd , 5 ) ;
mmc_card_release_host ( card ) ;
if ( err ) {
printk ( KERN_ERR " %s: unable to set block size to %d: %d \n " ,
md - > disk - > disk_name , cmd . arg , err ) ;
return - EINVAL ;
}
return 0 ;
}
static int mmc_blk_probe ( struct mmc_card * card )
{
struct mmc_blk_data * md ;
int err ;
2005-05-21 13:27:02 +04:00
/*
* Check that the card supports the command class ( es ) we need .
*/
if ( ! ( card - > csd . cmdclass & CCC_BLOCK_READ ) )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
md = mmc_blk_alloc ( card ) ;
if ( IS_ERR ( md ) )
return PTR_ERR ( md ) ;
err = mmc_blk_set_blksize ( md , card ) ;
if ( err )
goto out ;
2006-01-14 00:44:18 +03:00
printk ( KERN_INFO " %s: %s %s %lluKiB %s \n " ,
2005-04-17 02:20:36 +04:00
md - > disk - > disk_name , mmc_card_id ( card ) , mmc_card_name ( card ) ,
2006-01-14 00:44:18 +03:00
( unsigned long long ) ( get_capacity ( md - > disk ) > > 1 ) ,
md - > read_only ? " (ro) " : " " ) ;
2005-04-17 02:20:36 +04:00
mmc_set_drvdata ( card , md ) ;
add_disk ( md - > disk ) ;
return 0 ;
out :
mmc_blk_put ( md ) ;
return err ;
}
static void mmc_blk_remove ( struct mmc_card * card )
{
struct mmc_blk_data * md = mmc_get_drvdata ( card ) ;
if ( md ) {
int devidx ;
2006-11-15 00:08:16 +03:00
/* Stop new requests from getting into the queue */
2005-04-17 02:20:36 +04:00
del_gendisk ( md - > disk ) ;
2006-11-15 00:08:16 +03:00
/* Then flush out any already in there */
mmc_cleanup_queue ( & md - > queue ) ;
2005-04-17 02:20:36 +04:00
devidx = md - > disk - > first_minor > > MMC_SHIFT ;
__clear_bit ( devidx , dev_use ) ;
mmc_blk_put ( md ) ;
}
mmc_set_drvdata ( card , NULL ) ;
}
# ifdef CONFIG_PM
static int mmc_blk_suspend ( struct mmc_card * card , pm_message_t state )
{
struct mmc_blk_data * md = mmc_get_drvdata ( card ) ;
if ( md ) {
mmc_queue_suspend ( & md - > queue ) ;
}
return 0 ;
}
static int mmc_blk_resume ( struct mmc_card * card )
{
struct mmc_blk_data * md = mmc_get_drvdata ( card ) ;
if ( md ) {
mmc_blk_set_blksize ( md , card ) ;
mmc_queue_resume ( & md - > queue ) ;
}
return 0 ;
}
# else
# define mmc_blk_suspend NULL
# define mmc_blk_resume NULL
# endif
static struct mmc_driver mmc_driver = {
. drv = {
. name = " mmcblk " ,
} ,
. probe = mmc_blk_probe ,
. remove = mmc_blk_remove ,
. suspend = mmc_blk_suspend ,
. resume = mmc_blk_resume ,
} ;
static int __init mmc_blk_init ( void )
{
int res = - ENOMEM ;
res = register_blkdev ( major , " mmc " ) ;
if ( res < 0 ) {
printk ( KERN_WARNING " Unable to get major %d for MMC media: %d \n " ,
major , res ) ;
goto out ;
}
if ( major = = 0 )
major = res ;
return mmc_register_driver ( & mmc_driver ) ;
out :
return res ;
}
static void __exit mmc_blk_exit ( void )
{
mmc_unregister_driver ( & mmc_driver ) ;
unregister_blkdev ( major , " mmc " ) ;
}
module_init ( mmc_blk_init ) ;
module_exit ( mmc_blk_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Multimedia Card (MMC) block device driver " ) ;
module_param ( major , int , 0444 ) ;
MODULE_PARM_DESC ( major , " specify the major device number for MMC block driver " ) ;