2007-06-16 10:04:16 +04:00
/*
* linux / drivers / mmc / core / sdio_cis . c
*
* Author : Nicolas Pitre
* Created : June 11 , 2007
* Copyright : MontaVista Software Inc .
*
2007-06-16 10:06:47 +04:00
* Copyright 2007 Pierre Ossman
*
2007-06-16 10:04:16 +04:00
* 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 .
*/
# include <linux/kernel.h>
# include <linux/mmc/host.h>
2007-07-30 17:15:30 +04:00
# include <linux/mmc/card.h>
2007-06-16 10:04:16 +04:00
# include <linux/mmc/sdio.h>
# include <linux/mmc/sdio_func.h>
# include "sdio_cis.h"
# include "sdio_ops.h"
2007-09-19 20:42:16 +04:00
static int cistpl_vers_1 ( struct mmc_card * card , struct sdio_func * func ,
const unsigned char * buf , unsigned size )
{
unsigned i , nr_strings ;
char * * buffer , * string ;
2009-10-08 03:32:33 +04:00
/* Find all null-terminated (including zero length) strings in
the TPLLV1_INFO field . Trailing garbage is ignored . */
2007-09-19 20:42:16 +04:00
buf + = 2 ;
size - = 2 ;
nr_strings = 0 ;
for ( i = 0 ; i < size ; i + + ) {
if ( buf [ i ] = = 0xff )
break ;
if ( buf [ i ] = = 0 )
nr_strings + + ;
}
2009-10-08 03:32:33 +04:00
if ( nr_strings = = 0 )
2007-09-19 20:42:16 +04:00
return 0 ;
size = i ;
buffer = kzalloc ( sizeof ( char * ) * nr_strings + size , GFP_KERNEL ) ;
if ( ! buffer )
return - ENOMEM ;
string = ( char * ) ( buffer + nr_strings ) ;
for ( i = 0 ; i < nr_strings ; i + + ) {
buffer [ i ] = string ;
strcpy ( string , buf ) ;
string + = strlen ( string ) + 1 ;
buf + = strlen ( buf ) + 1 ;
}
if ( func ) {
func - > num_info = nr_strings ;
func - > info = ( const char * * ) buffer ;
} else {
card - > num_info = nr_strings ;
card - > info = ( const char * * ) buffer ;
}
return 0 ;
}
2007-07-30 17:15:30 +04:00
static int cistpl_manfid ( struct mmc_card * card , struct sdio_func * func ,
const unsigned char * buf , unsigned size )
2007-06-16 10:04:16 +04:00
{
2007-07-30 17:15:30 +04:00
unsigned int vendor , device ;
2007-06-16 10:04:16 +04:00
/* TPLMID_MANF */
2007-07-30 17:15:30 +04:00
vendor = buf [ 0 ] | ( buf [ 1 ] < < 8 ) ;
2007-06-16 10:04:16 +04:00
/* TPLMID_CARD */
2007-07-30 17:15:30 +04:00
device = buf [ 2 ] | ( buf [ 3 ] < < 8 ) ;
if ( func ) {
func - > vendor = vendor ;
func - > device = device ;
} else {
card - > cis . vendor = vendor ;
card - > cis . device = device ;
}
return 0 ;
}
static const unsigned char speed_val [ 16 ] =
{ 0 , 10 , 12 , 13 , 15 , 20 , 25 , 30 , 35 , 40 , 45 , 50 , 55 , 60 , 70 , 80 } ;
static const unsigned int speed_unit [ 8 ] =
{ 10000 , 100000 , 1000000 , 10000000 , 0 , 0 , 0 , 0 } ;
2009-12-15 05:01:19 +03:00
typedef int ( tpl_parse_t ) ( struct mmc_card * , struct sdio_func * ,
const unsigned char * , unsigned ) ;
struct cis_tpl {
unsigned char code ;
unsigned char min_size ;
tpl_parse_t * parse ;
2009-10-02 02:44:05 +04:00
} ;
2009-12-15 05:01:19 +03:00
static int cis_tpl_parse ( struct mmc_card * card , struct sdio_func * func ,
const char * tpl_descr ,
const struct cis_tpl * tpl , int tpl_count ,
unsigned char code ,
const unsigned char * buf , unsigned size )
2009-10-02 02:44:05 +04:00
{
2009-12-15 05:01:19 +03:00
int i , ret ;
2009-10-02 02:44:05 +04:00
2009-12-15 05:01:19 +03:00
/* look for a matching code in the table */
for ( i = 0 ; i < tpl_count ; i + + , tpl + + ) {
if ( tpl - > code = = code )
break ;
2009-10-02 02:44:05 +04:00
}
2009-12-15 05:01:19 +03:00
if ( i < tpl_count ) {
if ( size > = tpl - > min_size ) {
if ( tpl - > parse )
ret = tpl - > parse ( card , func , buf , size ) ;
else
ret = - EILSEQ ; /* known tuple, not parsed */
} else {
/* invalid tuple */
ret = - EINVAL ;
}
if ( ret & & ret ! = - EILSEQ & & ret ! = - ENOENT ) {
printk ( KERN_ERR " %s: bad %s tuple 0x%02x (%u bytes) \n " ,
mmc_hostname ( card - > host ) , tpl_descr , code , size ) ;
}
} else {
/* unknown tuple */
ret = - ENOENT ;
}
return ret ;
2009-10-02 02:44:05 +04:00
}
2009-12-15 05:01:19 +03:00
static int cistpl_funce_common ( struct mmc_card * card , struct sdio_func * func ,
2007-07-30 17:15:30 +04:00
const unsigned char * buf , unsigned size )
{
2009-12-15 05:01:19 +03:00
/* Only valid for the common CIS (function 0) */
if ( func )
2007-07-30 17:15:30 +04:00
return - EINVAL ;
/* TPLFE_FN0_BLK_SIZE */
card - > cis . blksize = buf [ 1 ] | ( buf [ 2 ] < < 8 ) ;
/* TPLFE_MAX_TRAN_SPEED */
card - > cis . max_dtr = speed_val [ ( buf [ 3 ] > > 3 ) & 15 ] *
speed_unit [ buf [ 3 ] & 7 ] ;
return 0 ;
}
2009-12-15 05:01:19 +03:00
static int cistpl_funce_func ( struct mmc_card * card , struct sdio_func * func ,
2007-07-30 17:15:30 +04:00
const unsigned char * buf , unsigned size )
{
unsigned vsn ;
unsigned min_size ;
2009-12-15 05:01:19 +03:00
/* Only valid for the individual function's CIS (1-7) */
if ( ! func )
return - EINVAL ;
2009-10-02 02:44:05 +04:00
2009-12-15 05:01:19 +03:00
/*
* This tuple has a different length depending on the SDIO spec
* version .
*/
2007-07-30 17:15:30 +04:00
vsn = func - > card - > cccr . sdio_vsn ;
min_size = ( vsn = = SDIO_SDIO_REV_1_00 ) ? 28 : 42 ;
2009-12-15 05:01:19 +03:00
if ( size < min_size )
2007-07-30 17:15:30 +04:00
return - EINVAL ;
/* TPLFE_MAX_BLK_SIZE */
2007-08-08 17:23:48 +04:00
func - > max_blksize = buf [ 12 ] | ( buf [ 13 ] < < 8 ) ;
2007-06-16 10:04:16 +04:00
2008-07-10 03:41:43 +04:00
/* TPLFE_ENABLE_TIMEOUT_VAL, present in ver 1.1 and above */
if ( vsn > SDIO_SDIO_REV_1_00 )
func - > enable_timeout = ( buf [ 28 ] | ( buf [ 29 ] < < 8 ) ) * 10 ;
else
func - > enable_timeout = jiffies_to_msecs ( HZ ) ;
2007-06-16 10:04:16 +04:00
return 0 ;
}
2009-12-15 05:01:19 +03:00
/*
* Known TPLFE_TYPEs table for CISTPL_FUNCE tuples .
*
* Note that , unlike PCMCIA , CISTPL_FUNCE tuples are not parsed depending
* on the TPLFID_FUNCTION value of the previous CISTPL_FUNCID as on SDIO
* TPLFID_FUNCTION is always hardcoded to 0x0C .
*/
static const struct cis_tpl cis_tpl_funce_list [ ] = {
{ 0x00 , 4 , cistpl_funce_common } ,
{ 0x01 , 0 , cistpl_funce_func } ,
{ 0x04 , 1 + 1 + 6 , /* CISTPL_FUNCE_LAN_NODE_ID */ } ,
} ;
2007-07-30 17:15:30 +04:00
static int cistpl_funce ( struct mmc_card * card , struct sdio_func * func ,
const unsigned char * buf , unsigned size )
{
2009-12-15 05:01:19 +03:00
if ( size < 1 )
return - EINVAL ;
2007-07-30 17:15:30 +04:00
2009-12-15 05:01:19 +03:00
return cis_tpl_parse ( card , func , " CISTPL_FUNCE " ,
cis_tpl_funce_list ,
ARRAY_SIZE ( cis_tpl_funce_list ) ,
buf [ 0 ] , buf , size ) ;
2007-07-30 17:15:30 +04:00
}
2009-12-15 05:01:19 +03:00
/* Known TPL_CODEs table for CIS tuples */
2007-06-16 10:04:16 +04:00
static const struct cis_tpl cis_tpl_list [ ] = {
2007-09-19 20:42:16 +04:00
{ 0x15 , 3 , cistpl_vers_1 } ,
2007-06-16 10:04:16 +04:00
{ 0x20 , 4 , cistpl_manfid } ,
{ 0x21 , 2 , /* cistpl_funcid */ } ,
2007-07-30 17:15:30 +04:00
{ 0x22 , 0 , cistpl_funce } ,
2007-06-16 10:04:16 +04:00
} ;
2007-07-30 17:15:30 +04:00
static int sdio_read_cis ( struct mmc_card * card , struct sdio_func * func )
2007-06-16 10:04:16 +04:00
{
int ret ;
2007-06-16 10:06:47 +04:00
struct sdio_func_tuple * this , * * prev ;
2007-06-16 10:04:16 +04:00
unsigned i , ptr = 0 ;
2007-07-30 17:15:30 +04:00
/*
* Note that this works for the common CIS ( function number 0 ) as
* well as a function ' s CIS * since SDIO_CCCR_CIS and SDIO_FBR_CIS
* have the same offset .
*/
2007-06-16 10:04:16 +04:00
for ( i = 0 ; i < 3 ; i + + ) {
2007-07-30 17:15:30 +04:00
unsigned char x , fn ;
if ( func )
fn = func - > num ;
else
fn = 0 ;
ret = mmc_io_rw_direct ( card , 0 , 0 ,
2007-08-08 17:23:05 +04:00
SDIO_FBR_BASE ( fn ) + SDIO_FBR_CIS + i , 0 , & x ) ;
2007-06-16 10:04:16 +04:00
if ( ret )
return ret ;
ptr | = x < < ( i * 8 ) ;
}
2007-07-30 17:15:30 +04:00
if ( func )
prev = & func - > tuples ;
else
prev = & card - > tuples ;
BUG_ON ( * prev ) ;
2007-06-16 10:04:16 +04:00
do {
unsigned char tpl_code , tpl_link ;
2007-07-30 17:15:30 +04:00
ret = mmc_io_rw_direct ( card , 0 , 0 , ptr + + , 0 , & tpl_code ) ;
2007-06-16 10:04:16 +04:00
if ( ret )
break ;
/* 0xff means we're done */
if ( tpl_code = = 0xff )
break ;
2009-03-05 21:38:38 +03:00
/* null entries have no link field or data */
if ( tpl_code = = 0x00 )
continue ;
2007-07-30 17:15:30 +04:00
ret = mmc_io_rw_direct ( card , 0 , 0 , ptr + + , 0 , & tpl_link ) ;
2007-06-16 10:04:16 +04:00
if ( ret )
break ;
2009-03-05 21:37:28 +03:00
/* a size of 0xff also means we're done */
if ( tpl_link = = 0xff )
break ;
2007-06-16 10:06:47 +04:00
this = kmalloc ( sizeof ( * this ) + tpl_link , GFP_KERNEL ) ;
if ( ! this )
return - ENOMEM ;
for ( i = 0 ; i < tpl_link ; i + + ) {
2007-07-30 17:15:30 +04:00
ret = mmc_io_rw_direct ( card , 0 , 0 ,
2007-06-16 10:06:47 +04:00
ptr + i , 0 , & this - > data [ i ] ) ;
if ( ret )
2007-06-16 10:04:16 +04:00
break ;
}
2007-06-16 10:06:47 +04:00
if ( ret ) {
kfree ( this ) ;
2007-06-16 10:04:16 +04:00
break ;
}
2009-12-15 05:01:19 +03:00
/* Try to parse the CIS tuple */
ret = cis_tpl_parse ( card , func , " CIS " ,
cis_tpl_list , ARRAY_SIZE ( cis_tpl_list ) ,
tpl_code , this - > data , tpl_link ) ;
if ( ret = = - EILSEQ | | ret = = - ENOENT ) {
2009-10-02 02:44:05 +04:00
/*
2009-12-15 05:01:19 +03:00
* The tuple is unknown or known but not parsed .
* Queue the tuple for the function driver .
2009-10-02 02:44:05 +04:00
*/
this - > next = NULL ;
this - > code = tpl_code ;
this - > size = tpl_link ;
* prev = this ;
prev = & this - > next ;
2009-12-15 05:01:19 +03:00
if ( ret = = - ENOENT ) {
/* warn about unknown tuples */
printk ( KERN_WARNING " %s: queuing unknown "
" CIS tuple 0x%02x (%u bytes) \n " ,
mmc_hostname ( card - > host ) ,
tpl_code , tpl_link ) ;
}
2009-10-02 02:44:05 +04:00
/* keep on analyzing tuples */
ret = 0 ;
2009-12-15 05:01:19 +03:00
} else {
/*
* We don ' t need the tuple anymore if it was
* successfully parsed by the SDIO core or if it is
* not going to be queued for a driver .
*/
kfree ( this ) ;
2007-06-16 10:04:16 +04:00
}
2007-06-16 10:06:47 +04:00
ptr + = tpl_link ;
2007-06-16 10:04:16 +04:00
} while ( ! ret ) ;
2007-07-30 17:15:30 +04:00
/*
* Link in all unknown tuples found in the common CIS so that
* drivers don ' t have to go digging in two places .
*/
if ( func )
* prev = card - > tuples ;
2007-06-16 10:04:16 +04:00
return ret ;
}
2007-06-16 10:06:47 +04:00
2007-07-30 17:15:30 +04:00
int sdio_read_common_cis ( struct mmc_card * card )
{
return sdio_read_cis ( card , NULL ) ;
}
void sdio_free_common_cis ( struct mmc_card * card )
2007-06-16 10:06:47 +04:00
{
struct sdio_func_tuple * tuple , * victim ;
2007-07-30 17:15:30 +04:00
tuple = card - > tuples ;
2007-06-16 10:06:47 +04:00
while ( tuple ) {
victim = tuple ;
tuple = tuple - > next ;
kfree ( victim ) ;
}
2007-07-30 17:15:30 +04:00
card - > tuples = NULL ;
}
int sdio_read_func_cis ( struct sdio_func * func )
{
int ret ;
ret = sdio_read_cis ( func - > card , func ) ;
if ( ret )
return ret ;
/*
* Since we ' ve linked to tuples in the card structure ,
* we must make sure we have a reference to it .
*/
get_device ( & func - > card - > dev ) ;
/*
* Vendor / device id is optional for function CIS , so
* copy it from the card structure as needed .
*/
if ( func - > vendor = = 0 ) {
func - > vendor = func - > card - > cis . vendor ;
func - > device = func - > card - > cis . device ;
}
return 0 ;
}
void sdio_free_func_cis ( struct sdio_func * func )
{
struct sdio_func_tuple * tuple , * victim ;
tuple = func - > tuples ;
while ( tuple & & tuple ! = func - > card - > tuples ) {
victim = tuple ;
tuple = tuple - > next ;
kfree ( victim ) ;
}
2007-06-16 10:06:47 +04:00
func - > tuples = NULL ;
2007-07-30 17:15:30 +04:00
/*
* We have now removed the link to the tuples in the
* card structure , so remove the reference .
*/
put_device ( & func - > card - > dev ) ;
2007-06-16 10:06:47 +04:00
}