前面介绍SDIO寄存器空间时看到CIA(Function0)空间中有一部分是CIS,这一篇就来分享CIS这部分空间。从CIS寻址到结构解析两步来进行。
参考:[REF1] SD Specifications Part E1 SDIO Simplified Specification Version 3.00 July 25, 2018
[REF1]的章节6.7 SDIO Fixed Internal Map的如下图中可以看到, 128KB的CIA(Function0)寄存器区域(即Common I/O Area),划分了0x001000~0x017FFF,92KB给CIS。同时CCCR和FBR中出发有一些箭头指向了CIS区域,这里的箭头的意思是CCCR和FBR中的寄存器值确定CIS的位置。
CIS即Card Information Structure卡信息结构,提供关于卡和各个Function的更完整的信息。
所有Function共享一个common的CIS,同时每个Function1-7还有自己独立的CIS。如上图所示,所有CIS的位置都在0x001000~0x017FFF内,但是每个CIS的基地址则由CCCR和FBRn中的寄存器来确定。CCCR中的寄存器确定common的CIS的位置,FBRn中的寄存器来确定Function1-7对应的CIS的位置。
[REF1]的Table 6-1 : Card Common Control Registers (CCCR)可以看到common CIS的位置由0x09-0x0B位置的寄存器的值确定
[REF1]的Table 6-3 : Function Basic Information Registers (FBR)可以看到,Function1-7对应的CIS的位置由FBR偏移0x09~0x0B位置的寄存器的值确定,FBR本身的偏移地址是n*0x100(n为Function号)。
所以可以统一为Function n对应的CIS位置,由寄存器n*0x100+0x09~n*0x100+0x0B的值决定(3个寄存器,24位地址,小端)。
其中Function 0对应的CIS即Common CIS。
所以需要实现一个根据Function号,查找CIS位置的函数。
其中sdio_io_rw_direct是CMD52单寄存器读写的实现。
int sdio_get_cis_addr(sdio_host_common_st* host, uint8_t fn, uint32_t* addr)
{
uint32_t tmp;
uint32_t regaddr;
uint32_t getaddr = 0;
regaddr = ((uint32_t)fn<<8)+0x09;
tmp = 0;
if(0 == sdio_io_rw_direct(host,0, 0, 0, regaddr, &tmp))
{
getaddr = (uint32_t)(tmp & 0xFF);
regaddr += 1;
if(0 == sdio_io_rw_direct(host,0, 0, 0, regaddr, &tmp))
{
getaddr |= (uint32_t)(tmp & 0xFF)<<8;
regaddr += 1;
if(0 == sdio_io_rw_direct(host,0, 0, 0, regaddr, &tmp))
{
getaddr |= (uint32_t)(tmp & 0xFF)<<16;
*addr = getaddr;
return 0;
}
else
{
return -3;
}
}
else
{
return -2;
}
}
else
{
return -1;
}
}
测试获取CIS地址
for(int i=0; i<=host->fn; i++)
{
uint32_t cisaddr;
if(0 == (res = sdio_get_cis_addr(host,i,&cisaddr)))
{
SDIO_WIFI_INFO_LOG(("[CIS:%01d]0x%05x\r\n", i, cisaddr));
}
else
{
SDIO_WIFI_ERR_LOG(("get function %d cis addr err\n", i, res));
}
}
打印如下
[CIS:0]0x01000
[CIS:1]0x01100
都在范围0x001000~0x017FFF内,且common CIS一般都是从偏移0开始,后面接着其他Function的CIS。
参考[REF1]的6.11 Card Information Structure (CIS)章节。
CIS结构基于 PCMCIA的标准,见《P C C A R D S TA N D A R D Volume 4 Metaformat Specification》。
参考[REF1]的16. CIS Formats章节,详细介绍了SDIO中支持的结构。
CIS以块(或者元组Tuple)为单位存储信息,元组依次存放。
每个元组都以以下形式组织,
一个字节的TPL_CODE,用于表示该元组的类型,如果TPL_CODE=CISTPL_END 0xFF表示结束,后续没有元组了,且本元组也只有CODE0xFF,无需后面的TPL_LINK和数据,TPL_CODE为CISTPL_NULL=0x00时表示本元组为空,元组无后面的TPL_LINK和数据,但是后面还有其他元组。
一个字节的TPL_LINK,用于表示下一个元组的偏移,TPL_LINK可以为0表示没有数据,如果为0xFF也表示本元组是最后一个元组。所以有两种方式表示元组结束,一个是TPL_CODE=0xFF,一个是TPL_LINK=0xFF,建议使用前者。元组最大长度就是1+1+255=257字节,即255字节数据的元组也表示元组的结束。
n个字节的数据, 理论上TPL_LINK可以大于n,这样n个数据之后有间隙,再继续放下一个元组,但是一般TPL_LINK就等于n,即每个元组依次放不留间隙。当然TPL_LINK不能小于n。
多字节数据按照小端模式,固定长字符串以NULL填充。
SDIO支持以下CODE,O表示可选实现,R表示建议实现,M表示强制实现,n/a表示无需实现。
可以看到对于common只有MANFID和END是强制要实现的,
对于其他Function1-7只有FUNCID,FUNCE,END是强制要实现的。
对于申明支持SDIO接口的强制要实现SDIO_STD。
MANFID是common必须实现,function可选实现的。
4字节数据,
低2字节为Card manufacturer code,对于有JEDEC ID的可以使用JEDEC厂商编码的低8位,高8位为0.
高2字节为manufacturer information,厂商自定义可以保存标志和修订信息等。
对于Function是强制的,common不是强制的。
为了识别SDIO卡,CISTPL_FUNCID元组应存在于所有CIS区域中。Common和每个function中都应有一个。格式如下
TPLFID_FUNCTION Card function code (0Ch)用于表示是SDIO卡,
该元组后面接其他的Function Extension Tuple
Function Extension Tuple对于common和function不一样,function又分两种格式。
通过02h位置来决定是哪一种,02h处为0x00则为common,为0x01则为function的格式1,为0x02则为function的格式2.
支持SDIO的才有
保留供SDIO卡将来使用,V3.00暂时未定义具体格式。
static int sdio_scan_cis(sdio_host_common_st* host, uint32_t addr)
{
uint32_t regaddr = addr;
uint32_t tmp;
uint8_t code;
uint8_t link;
while(1)
{
tmp = 0;
if(0 == sdio_io_rw_drirect(host,0, 0, 0, regaddr, &tmp))
{
/* 读CODE */
code = (tmp&0xFF);
if(code == 0xFF)
{
return 0; /* CODE=0xFF表示结束 */
}
if(code == 0x00)
{
regaddr++;
continue; /* CODE=0x00表示NULL,空元组,继续 */
}
SDIO_WIFI_INFO_LOG(("[CODE]:0x%02x\n",code));
/* 读LINK */
tmp = 0;
regaddr++;
if(0 == sdio_io_rw_drirect(host,0, 0, 0, regaddr, &tmp))
{
link = (tmp&0xFF);
SDIO_WIFI_INFO_LOG(("[LINK]:0x%02x\n",link));
if(link == 0x00)
{
regaddr++;
continue; /* LINK=0 无数据 */
}
}
else
{
return -2;
}
/* 读DATA */
SDIO_WIFI_INFO_LOG(("[DATA]:\r\n"));
for(uint8_t i=0;i
{
tmp = 0;
regaddr++;
if((i%16==0) && (i!=0))
{
SDIO_WIFI_INFO_LOG(("\r\n"));
}
if(0 == sdio_io_rw_drirect(host,0, 0, 0, regaddr, &tmp))
{
SDIO_WIFI_INFO_LOG(("%02x ",tmp&0xFF));
}
else
{
return -3;
}
}
regaddr++;
SDIO_WIFI_INFO_LOG(("\r\n"));
}
else
{
return -1;
}
}
}
/* 解析CIS信息 */
for(int i=0; i<=host->fn; i++)
{
uint32_t cisaddr;
if(0 == (res = sdio_get_cis_addr(host,i,&cisaddr)))
{
SDIO_WIFI_INFO_LOG(("[CIS:%01d]0x%05x\r\n", i, cisaddr));
sdio_scan_cis(host, cisaddr);
}
else
{
SDIO_WIFI_ERR_LOG(("get function %d cis addr err\n", i, res));
}
}
打印如下
[CIS:0]0x01000
[CODE]:0x20
[LINK]:0x04
[DATA]:
4c 02 79 f1
[CODE]:0x21
[LINK]:0x02
[DATA]:
0c 00
[CODE]:0x22
[LINK]:0x04
[DATA]:
00 08 00 32
[CIS:1]0x01100
[CODE]:0x21
[LINK]:0x02
[DATA]:
0c 00
[CODE]:0x22
[LINK]:0x2a
[DATA]:
01 01 00 00 00 00 00 00 00 00 00 00 00 02 00 ff
ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 eb 00 6e 01 00 00 00 00
其中
Function0的CIS有三个元组
[CODE]:0x20
[LINK]:0x04
[DATA]:
4c 02 79 f1
Card manufacturer code和manufacturer information
分别是0x024C,0xF179
从官方linux驱动代码可以看到,对应的是RTL8188F,实际我这里是RTL8189FTV,属于一个系列。
[CODE]:0x21
[LINK]:0x02
[DATA]:
0c 00
CISTPL_FUNCID为0x000C表示是SDIO卡
0c 00
[CODE]:0x22
[LINK]:0x04
[DATA]:
00 08 00 32
CISTPL_FUNCE Function0
Blocksize为0x0008
Speed为0x32
Function1的CIS有两个元组
[CODE]:0x21
[LINK]:0x02
[DATA]:
0c 00
CISTPL_FUNCID为0x000C表示是SDIO卡
0c 00
[CODE]:0x22
[LINK]:0x2a
[DATA]:
01 01 00 00 00 00 00 00 00 00 00 00 00 02 00 ff
ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 eb 00 6e 01 00 00 00 00
CISTPL_FUNCE,Function1 01表示是格式1的CISTPL_FUNCE
Blocksize为0x0200
以上介绍了CIS的寻址与结构解析,并实现获取地址和解析的函数,以RTL8189FTV为例获取了其CIS信息,具体含义可以根据规格书去看。