3. sd卡驱动
3.1 引入
经过第2章我们知道,要想实现读写sd卡,需要按照sd协议规定的基本传输单位(命令、响应、数据)以及流程(初始化、读、写),向sd卡发送信号或者从sd卡接收信号。
为了简化cpu的操作,人们在soc内部设计了一个sd控制器,这个sd控制器专门按照sd规定的协议进行收发信号,并把我们上述提到的基本传输单位命令、响应以及数据的内容简化到一个个寄存器,比如s3c2440内部的sd控制器中包含有:
- SDICmdArg寄存器:用来保存命令的参数,当我们需要发送命令到sd卡时,对于命令的参数,只需要写入该寄存器即可;
- SDICmdCon寄存器:低八位用来保存命令的编号,当我们需要发送命令到sd卡时,对于命令的编号,只需要写入该寄存器即可;
- SDIRSP0——SDIRSP3寄存器:用来保存响应的内容,当我们需要读取sd卡发来的响应值时,便可以读取该寄存器;
- SDIDAT寄存器:用来保存数据的内容,当我们需要读取从sd卡发来的数据或者向sd卡写入数据时,便可以操作该寄存器;
等等还有很多寄存器,上面只是简单举例说明。
现在我们来回答问题2:cpu如何控制sd控制器来发出各种信号?
答案便是:通过读写其内部的寄存器,这也是sd驱动的主要内容。
现在,我想大家应该明白了,所谓sd卡驱动即是:
通过sd控制器提供的简化功能,按照sd协议的规定通过cpu读写其相应的寄存器,以此来达到向sd卡发出或者从sd卡接收一系列信号的目的,最终完成读写sd卡的功能。
下面从裸机、u-boot层面对sd卡驱动进行分析,并着重比较其实现的异同。
3.2 裸机中的sd卡驱动
注:本裸机驱动来自我之前写的移植u-boot 2020文章中的spl工程。
现在我们已经知道,sd卡驱动的内容主要便是按照sd协议的规定读写sd控制器中的寄存器,这在裸机驱动中表现得最为明显。
对于裸机中的sd卡驱动,主要围绕如下三个功能的实现而展开:
- 初始化sd卡
- 读sd卡
- 写sd卡
3.2.1 初始化sd卡
初始化的过程就是按照sd协议中规定的流程进行的。
过程如下:
- 初始化连接sd卡的gpio引脚以及sd控制器;
- 等待74个CLK
- 发送CMD0复位卡
- 发送CMD1检测是否为sd卡或者mmc卡
- 发送CMD2获取sd卡的CID
- 发送CMD3设置sd卡或者mmc卡的RCA
- 发送CMD9获取sd卡的CSD
- 发送CMD7选中sd卡
- 发送CMD13查询是否为传输状态
- 发送ACMD6设置总线带宽
代码如下:
u8 sd_init()
{
int i;
debug("\r\nSDI初始化开始!");
GPEUP = 0xf83f; // The pull up 1111 1000 0011 1111 必须上拉
GPECON = 0xaaaaaaaa; // 1010 1010 1010 1010 1010 1010 1010 1010
SDICSTA = 0xffff; //SDI指令状态
SDIDSTA = 0xffff; //SDI数据状态
SDIPRE = 124; // 400KHz 波特率设置 频率 PCLK/400K -1
SDICON = 1; // Type A, clk enable SDI控制
SDIFSTA |= 1 << 16; //FIFO reset
SDIBSIZE = 0x200; // 512byte(128word) SDI块大小
SDIDTIMER = 0x7fffff; // Set timeout count 数据传输超时时间
//等待74个CLK
for(i=0;i<0x1000;i++);
//先执行CMD0,复位
CMD0();
//判断卡的类型
if(SDI_MMC_OCR())
SDCard.sdiType = 1; //卡为MMC
else
SDCard.sdiType = 0; //卡为SD
//检测SD卡
if (SDI_SD_OCR()) {
debug("SD is ready\r\n");
} else{
debug("Initialize fail\r\nNo Card assertion\r\n");
return 0;
}
//读CID
if (CMD2(SDCard.cCardCID)) {
debug("CID\r\n");
debug("MID = %d\r\n",SDCard.cCardCID[0]);
debug("OLD = %d\r\n",(SDCard.cCardCID[1]*0X100)+SDCard.cCardCID[2]);
debug("生产厂家:%s\r\n",(SDCard.cCardCID+3));
debug("生产日期:20%d,%d\r\n",((SDCard.cCardCID[13]&0x0f)<<4)+((SDCard.cCardCID[14]&0xf0)>>4), (SDCard.cCardCID[14]&0x0f));
} else {
debug("Read Card CID is fail!\r\n");
return 0;
}
//设置RCA MMC的RCA=1 SD的RCA=0
//MMC
if (SDCard.sdiType==1) {
if (CMD3(1,&SDCard.sdiRCA)) {
SDCard.sdiRCA = 1;
SDIPRE = 2; //16MHZ
debug("MMC Card RCA = 0x%x\r\n",SDCard.sdiRCA);
debug("MMC Frequency is %dHz\r\n",(PCLK/(SDIPRE+1)));
} else {
debug("Read MMC RCA is fail!\r\n");
return 0;
}
//SD
} else {
if (CMD3(0,&SDCard.sdiRCA)) {
SDIPRE = 1; // Normal clock=25MHz
debug("SD Card RCA = 0x%x\r\n",SDCard.sdiRCA);
debug("SD Frequency is %dHz\r\n",(PCLK/(SDIPRE+1)));
} else {
debug("Read SD RCA is fail!\r\n");
return 0;
}
}
//读CSD
if (CMD9(SDCard.sdiRCA,SDCard.lCardCSD)) {
SDCard.lCardSize = (((SDCard.lCardCSD[1]&0x0000003f)<<16)+((SDCard.lCardCSD[2]&0xffff0000)>>16)+1)*512;
SDCard.lSectorSize = ((SDCard.lCardCSD[2]>>6)&0x0000007f)+1;
debug("Read Card CSD OK!\r\n");
debug("0x%08x\r\n",SDCard.lCardCSD[0]);
debug("0x%08x\r\n",SDCard.lCardCSD[1]);
debug("0x%08x\r\n",SDCard.lCardCSD[2]);
debug("0x%08x\r\n",SDCard.lCardCSD[3]);
debug("卡容量为:%dKB,%dMB\r\n",SDCard.lCardSize,SDCard.lCardSize/1024);
} else {
debug("Read Card CSD Fail!\r\n");
return 0;
}
//选中卡 CMD7 进入传输状态
//1表示选中卡
if (select_card(1,SDCard.sdiRCA)) {
debug("Card sel desel OK!\r\n");
} else {
debug("Card sel desel fail!\r\n");
return 0;
}
//CMD13 查询是否为传输状态
while ((CMD13(SDCard.sdiRCA) & 0x1e00) != 0x800);
//设置总线带宽 ACMD6
if (Set_bus_Width(SDCard.sdiType,1,SDCard.sdiRCA)) {
SDCard.sdiWide = 1;
debug("Bus Width is 4bit\r\n");
} else {
SDCard.sdiWide = 0;
debug("Bus Width is 1bit\r\n");
}
return 1;
}
3.2.2 读sd卡
读sd卡的过程也是按照sd协议中规定的流程进行的。
过程如下:
- 设置sd控制器;
- 发送CMD18多块读sd卡
- 通过sd控制器中的SDIDAT寄存器获取读到的数据到指定内存地址
- 清除sd控制器中的状态寄存器
- 发送CMD12停止多块读传输
代码如下:
/*
* sd_read_sector - 从SD卡中读出指定块起始地址的单个或多个数据块
*
* @addr 被读块的起始地址
* @buffer 用于接收读出数据的缓冲区
* @block_num 读的块数
*
* @return
* 0 读块操作不成功
* 1 读块操作成功
*
*/
u8 sd_read_sector(u32 *buf, u32 addr, u32 block_num)
{
u32 i=0;
u32 status=0;
SDIDTIMER = 0x7fffff; // Set timeout count
SDIBSIZE = 0x200; // 512byte(128word)
SDIFSTA = SDIFSTA | (1 << 16); // FIFO reset
SDIDCON = (block_num << 0) | (2 << 12) | (1 << 14) | (SDCard.sdiWide << 16) | (1 << 17) | (1 << 19) | (2 << 22);
// debug("enter sd_read_sector(): src_block = %d, block_num = %d\r\n", addr, block_num);
//发送读多个块指令
while (CMD18(addr) != 1)
SDICSTA = 0xF << 9;
//开始接收数据到缓冲区
while (i < block_num * 128) {
//检查是否超时和CRC校验是否出错
if (SDIDSTA & 0x60) {
//清除超时标志和CRC错误标志
SDIDSTA = (0x3 << 0x5);
return -1;
}
status = SDIFSTA;
//如果接收FIFO中有数据
if ((status & 0x1000) == 0x1000) {
*buf = SDIDAT;
buf++;
i++;
}
}
SDIDCON = SDIDCON & ~(7 << 12);
SDIFSTA = SDIFSTA & 0x200; //Clear Rx FIFO Last data Ready
SDIDSTA = 0x10; //Clear data Tx/Rx end detect
//发送结束指令
while (CMD12() != 1)
SDICSTA = 0xF << 9;
debug("leave sd_read_sector(): src_block = %d, block_num = %d\r\n", addr, block_num);
return 0;
}
3.2.3 写sd卡
与读sd卡过程相似,写sd卡的过程也是按照sd协议中规定的流程进行的。
过程如下:
- 设置sd控制器;
- 发送CMD25多块写sd卡
- 将指定内存地址处的要写入sd卡的数据写到sd控制器中的SDIDAT寄存器
- 设置sd控制器;
- 发送CMD12停止多块写传输
代码如下:
/*
* sd_write_sector - 向SD卡的一个或多个数据块写入数据
*
* @addr 被写块的起始地址
* @buffer 用于发送数据的缓冲区
* @block_num 块数
*
* @return
* 0 数据写入操作失败
* 1 数据写入操作成功
*
*/
u8 sd_write_sector(u32 *buf, u32 addr, u32 block_num)
{
u16 i = 0;
u32 status = 0;
SDIDTIMER = 0x7fffff; // Set timeout count
SDIBSIZE = 0x200; // 512byte(128word)
SDIFSTA = SDIFSTA | (1 << 16); // FIFO reset
SDIDCON = (block_num << 0) | (3 << 12) | (1 << 14) | (1 << 16) | (1 << 17) | (1 << 20) | (2 << 22);
//发送写多个块指令
while (CMD25(addr) != 1)
SDICSTA = 0xF << 9;
//开始传递数据到缓冲区
while (i < block_num * 128) {
status = SDIFSTA;
//如果发送FIFO可用,即FIFO未满
if ((status & 0x2000) == 0x2000) {
SDIDAT = *buf;
buf++;
i++;
}
}
SDIDCON = SDIDCON & ~(7 << 12);
//发送结束指令
while (CMD12() != 1)
SDICSTA=0xF<<9;
//等待数据发送结束
do {
status = SDIDSTA;
} while ((status & 0x2) == 0x2);
SDIDSTA = status;
SDIDSTA = 0xf4;
return 0;
}
3.2.4 具体命令实现举例
在前面的sd卡初始化、读、写函数中,主要的内容均是按照sd协议的规定发送一系列的命令,那具体每个命令是怎么实现的呢?下面举例说明:
- CMD9:获取卡的CSD寄存器的值
过程如下:
- 指定使用哪个sd卡,将卡的RCA作为命令的参数写入SDICARG寄存器
- 将命令的编号写入SDICCON寄存器,同时指定响应类型为长响应,并启动向sd卡发送该命令
- 读取sd控制器中的状态寄存器,检查命令是否发送完成
- 待命令发送完成后,读取SDIRSP0—SDIRSP3寄存器获取响应值,也即sd卡的CSD寄存器的值
代码如下:
//获取卡的CSD寄存器的值
u8 CMD9(u16 iRCA,u32 *lCSD)
{
SDICARG = iRCA<<16; // (RCA,stuff bit)
SDICCON = (0x1<<10)|(0x1<<9)|(0x1<<8)|0x49; // long_resp, wait_resp, start
if(!SDI_Check_CMD_End(9, 1))
return 0;
*(lCSD+0) = SDIRSP0;
*(lCSD+1) = SDIRSP1;
*(lCSD+2) = SDIRSP2;
*(lCSD+3) = SDIRSP3;
return 1;
}
- CMD18:读取多个数据块
过程如下:
- 指定读取sd卡的地址,将地址命令的参数写入SDICARG寄存器
- 将命令的编号写入SDICCON寄存器,并启动向sd卡发送该命令
- 读取sd控制器中的状态寄存器,检查命令是否发送完成
代码如下:
//读取多个数据块
u8 CMD18(u32 addr)
{
//STEP1:发送指令
SDICARG = addr; //设定指令参数
SDICCON = (1<<9)|(1<<8)|0x52; //发送CMD18指令
if(SDI_Check_CMD_End(18,1))
return 1;
else
return 0;
}
- CMD25:写入多个数据块
过程如下:
- 指定写入sd卡的地址,将地址命令的参数写入SDICARG寄存器
- 将命令的编号写入SDICCON寄存器,并启动向sd卡发送该命令
- 读取sd控制器中的状态寄存器,检查命令是否发送完成
代码如下:
//写入多个数据块
u8 CMD25(u32 addr)
{
//STEP1:发送指令
SDICARG = addr; //设定指令参数
SDICCON = (1<<9)|(1<<8)|0x59; //发送CMD25指令
if(SDI_Check_CMD_End(25,1))
return 1;
else
return 0;
}
3.2.5 优缺点总结
- 优点:
代码简单,容易理解,函数功能清晰明确,代码流程基本按照sd协议的规定走下去,便于初学者学习寄存器的操作与sd协议 - 缺点:
复用性、扩展性差,当需要同时支持多个sd控制器的时候,改动会非常大
3.3 u-boot中的sd卡驱动
相比于裸机,u-boot中的sd卡驱动在对sd控制器中的各个寄存器的操作以及对sd协议的遵循两方面基本相同,主要区别在于拥有良好的扩展性。所以,相比于寄存器与协议两方面,这里会更着重分析其组织框架,探究其支持良好扩展性的原因。
3.3.1 框架概述
下图是我总结的sd驱动的整体框架图:
其中,
- sd协议层
sd协议相关的内容,由于sd协议硬件无关,相比于3.2 裸机中的sd卡驱动,u-boot中将该内容单独作为一层,并且已经作为公共部分实现好,任何的sd卡驱动均可以复用该层的内容,也就是说在sd卡驱动中不必涉及协议相关的内容,只需关注于对sd控制器中的寄存器操作,这样,便实现了硬件操作与sd协议相分离 - sd核心层
由于不同的sd控制器针对sd协议中发送命令、接收响应等操作需要不同的硬件操作,所以sd核心层的作用在于向下屏蔽不同sd控制器所带来的不同的硬件操作,向上提供统一的接口给sd协议层 - sd驱动层
sd卡驱动在上述框架中的位置为sd driver,不具有通用性,需要针对具体的sd控制器进行编写,这也是移植sd卡驱动时需要做的部分
3.3.2 协议层
如3.2.1所述,u-boot将sd协议单独设为一层,与sd卡驱动进行了分离,那么本节我们就开始探寻u-boot中对于sd协议是如何实现的。
3.3.2.1 初始化
初始化主要有两个函数,分别用于不同的阶段,下面进行说明:
- mmc_initialize函数
该函数在u-boot的board_r阶段初始化时被调用
#ifdef CONFIG_MMC
static int initr_mmc(void)
{
puts("MMC: ");
mmc_initialize(gd->bd);
return 0;
}
#endif
主要完成sd卡早期的初始化工作,
- 调用sd卡驱动中的probe函数,进行sd控制器层面的初始化
- 按照sd协议发送一些命令,以探测是否有卡插入以及是sd卡还是mmc卡等
代码如下:
int mmc_initialize(bd_t *bis)
{
static int initialized = 0;
int ret;
if (initialized) /* Avoid initializing mmc multiple times */
return 0;
initialized = 1;
#if !CONFIG_IS_ENABLED(BLK)
#if !CONFIG_IS_ENABLED(MMC_TINY)
mmc_list_init();
#endif
#endif
ret = mmc_probe(bis);
if (ret)
return ret;
#ifndef CONFIG_SPL_BUILD
print_mmc_devices(',');
#endif
mmc_do_preinit();
return 0;
}
- mmc_init函数
该函数在u-boot启动之后的多个地方被调用,比如mmc命令、块设备驱动、dfu驱动等等,相比于mmc_initialize函数的早期初始化功能,该函数按照sd协议进行完全的初始化,具体代码如下:
int mmc_init(struct mmc *mmc)
{
int err = 0;
__maybe_unused ulong start;
#if CONFIG_IS_ENABLED(DM_MMC)
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(mmc->dev);
upriv->mmc = mmc;
#endif
if (mmc->has_init)
return 0;
start = get_timer(0);
if (!mmc->init_in_progress)
err = mmc_start_init(mmc);
if (!err)
err = mmc_complete_init(mmc);
if (err)
pr_info("%s: %d, time %lu\n", __func__, err, get_timer(start));
return err;
}
- mmc_start_init函数
这里我们选择两个初始化函数都会调用的mmc_start_init函数进行说明,一路追踪下去发现,最终会调用如下核心内容:
......
/* Reset the Card */
err = mmc_go_idle(mmc);
if (err)
return err;
/* The internal partition reset to user partition(0) at every CMD0*/
mmc_get_blk_desc(mmc)->hwpart = 0;
/* Test for SD version 2 */
err = mmc_send_if_cond(mmc);
/* Now try to get the SD card's operating condition */
err = sd_send_op_cond(mmc, uhs_en);
if (err && uhs_en) {
uhs_en = false;
mmc_power_cycle(mmc);
goto retry;
}
/* If the command timed out, we check for an MMC card */
if (err == -ETIMEDOUT) {
err = mmc_send_op_cond(mmc);
if (err) {
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
pr_err("Card did not respond to voltage select!\n");
#endif
return -EOPNOTSUPP;
}
}
......
这里我们重点关注如下函数:
- mmc_go_idle
- mmc_send_if_cond
- sd_send_op_cond
- mmc_send_op_cond
至于函数的具体实现,请参考3.2.2.4
3.3.2.2 读
代码如下:
#if CONFIG_IS_ENABLED(BLK)
ulong mmc_bread(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, void *dst)
#else
ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,
void *dst)
#endif
{
#if CONFIG_IS_ENABLED(BLK)
struct blk_desc *block_dev = dev_get_uclass_platdata(dev);
#endif
int dev_num = block_dev->devnum;
int err;
lbaint_t cur, blocks_todo = blkcnt;
uint b_max;
if (blkcnt == 0)
return 0;
struct mmc *mmc = find_mmc_device(dev_num);
if (!mmc)
return 0;
if (CONFIG_IS_ENABLED(MMC_TINY))
err = mmc_switch_part(mmc, block_dev->hwpart);
else
err = blk_dselect_hwpart(block_dev, block_dev->hwpart);
if (err < 0)
return 0;
if ((start + blkcnt) > block_dev->lba) {
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
pr_err("MMC: block number 0x" LBAF " exceeds max(0x" LBAF ")\n",
start + blkcnt, block_dev->lba);
#endif
return 0;
}
if (mmc_set_blocklen(mmc, mmc->read_bl_len)) {
pr_debug("%s: Failed to set blocklen\n", __func__);
return 0;
}
b_max = mmc_get_b_max(mmc, dst, blkcnt);
do {
cur = (blocks_todo > b_max) ? b_max : blocks_todo;
if (mmc_read_blocks(mmc, dst, start, cur) != cur) {
pr_debug("%s: Failed to read blocks\n", __func__);
return 0;
}
blocks_todo -= cur;
start += cur;
dst += cur * mmc->read_bl_len;
} while (blocks_todo > 0);
return blkcnt;
}
这里我们重点关注如下函数:
- mmc_set_blocklen
- mmc_read_blocks
至于函数的具体实现,请参考3.2.2.4
3.3.2.3 写
代码如下:
#if CONFIG_IS_ENABLED(BLK)
ulong mmc_bwrite(struct udevice *dev, lbaint_t start, lbaint_t blkcnt,
const void *src)
#else
ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,
const void *src)
#endif
{
#if CONFIG_IS_ENABLED(BLK)
struct blk_desc *block_dev = dev_get_uclass_platdata(dev);
#endif
int dev_num = block_dev->devnum;
lbaint_t cur, blocks_todo = blkcnt;
int err;
struct mmc *mmc = find_mmc_device(dev_num);
if (!mmc)
return 0;
err = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_num, block_dev->hwpart);
if (err < 0)
return 0;
if (mmc_set_blocklen(mmc, mmc->write_bl_len))
return 0;
do {
cur = (blocks_todo > mmc->cfg->b_max) ?
mmc->cfg->b_max : blocks_todo;
if (mmc_write_blocks(mmc, start, cur, src) != cur)
return 0;
blocks_todo -= cur;
start += cur;
src += cur * mmc->write_bl_len;
} while (blocks_todo > 0);
return blkcnt;
}
这里我们重点关注如下函数:
- mmc_set_blocklen
- mmc_write_blocks
至于函数的具体实现,请参考3.2.2.4
3.3.2.4 具体命令实现举例
这里用几个命令作为例子来讲解下其具体的实现,其他命令的实现原理均是类似的。
- mmc_go_idle函数
static int mmc_go_idle(struct mmc *mmc)
{
struct mmc_cmd cmd;
int err;
udelay(1000);
cmd.cmdidx = MMC_CMD_GO_IDLE_STATE;
cmd.cmdarg = 0;
cmd.resp_type = MMC_RSP_NONE;
err = mmc_send_cmd(mmc, &cmd, NULL);
if (err)
return err;
udelay(2000);
return 0;
}
u-boot中将命令抽象成了一个结构体struct mmc_cmd,具体定义及变量解释如下:
struct mmc_cmd {
ushort cmdidx; /* 命令编号 */
uint resp_type; /* 命令返回的相应类型 */
uint cmdarg; /* 命令参数 */
uint response[4]; /* 命令返回的响应值 */
};
我们看到mmc_go_idle中对应的命令编号为MMC_CMD_GO_IDLE_STATE,这个宏定义对应的值为0,然后命令参数为0,响应类型为无响应,最后调用了mmc_send_cmd函数。
mmc_send_cmd函数的作用便是负责发送一个命令到sd卡,其具体的实现请参看后面的3.2.3一节。
- mmc_send_if_cond函数
static int mmc_send_if_cond(struct mmc *mmc)
{
struct mmc_cmd cmd;
int err;
cmd.cmdidx = SD_CMD_SEND_IF_COND;
/* We set the bit if the host supports voltages between 2.7 and 3.6 V */
cmd.cmdarg = ((mmc->cfg->voltages & 0xff8000) != 0) << 8 | 0xaa;
cmd.resp_type = MMC_RSP_R7;
err = mmc_send_cmd(mmc, &cmd, NULL);
if (err)
return err;
if ((cmd.response[0] & 0xff) != 0xaa)
return -EOPNOTSUPP;
else
mmc->version = SD_VERSION_2;
return 0;
}
我们看到,mmc_send_if_cond函数与mmc_go_idle函数的结构非常相似。
通过查看宏定义SD_CMD_SEND_IF_COND可知,该函数对应的命令为CMD8,然后也是调用mmc_send_cmd函数将该命令发送出去,最后又读取了一下响应值。
- mmc_read_blocks函数
static int mmc_read_blocks(struct mmc *mmc, void *dst, lbaint_t start,
lbaint_t blkcnt)
{
struct mmc_cmd cmd;
struct mmc_data data;
if (blkcnt > 1)
cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK;
else
cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK;
if (mmc->high_capacity)
cmd.cmdarg = start;
else
cmd.cmdarg = start * mmc->read_bl_len;
cmd.resp_type = MMC_RSP_R1;
data.dest = dst;
data.blocks = blkcnt;
data.blocksize = mmc->read_bl_len;
data.flags = MMC_DATA_READ;
if (mmc_send_cmd(mmc, &cmd, &data))
return 0;
if (blkcnt > 1) {
cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
cmd.cmdarg = 0;
cmd.resp_type = MMC_RSP_R1b;
if (mmc_send_cmd(mmc, &cmd, NULL)) {
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
pr_err("mmc fail to send stop cmd\n");
#endif
return 0;
}
}
return blkcnt;
}
通过参数MMC_CMD_READ_SINGLE_BLOCK以及MMC_CMD_READ_MULTIPLE_BLOCK,可知该函数对应的命令为CMD17或者CMD18,功能为读取sd卡的一个或多个块数据,因为这两个命令是带有数据要返回的,针对sd协议中的数据,u-boot定义了结构体struct mmc_data,具体定义及变量解释如下:
struct mmc_data {
union {
char *dest; /* 针对读,数据目的地址 */
const char *src; /* 针对写,数据源地址 */
};
uint flags; /* 读或者写标志 */
uint blocks; /* 读或者写的块数量 */
uint blocksize; /* 读或者写的块大小 */
};
然后对struct mmc_data结构体赋值后,连同struct mmc_cmd结构体一并作为参数调用mmc_send_cmd函数将命令发送出去。因为是读,当mmc_send_cmd函数执行完成返回后,读取到的数据便写到了struct mmc_data结构体中赋好值的目的地址上了。
最后,如果是多块读,又调用了MMC_CMD_STOP_TRANSMISSION,也就是CMD12,功能为停止多块读或者写操作。
3.3.3 核心层
在上文的协议层中,我们发现每个命令函数最终都会调用到mmc_send_cmd函数将命令发送出去,那本节就来讲解该函数的实现以及其他一些初始化过程中会用到的函数,最后探究相比于裸机驱动为什么会多出这个核心层。
3.3.3.1 mmc_send_cmd函数
代码如下:
int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
{
return dm_mmc_send_cmd(mmc->dev, cmd, data);
}
其中又调用了dm_mmc_send_cmd函数,代码如下:
int dm_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
struct mmc_data *data)
{
struct mmc *mmc = mmc_get_mmc_dev(dev);
struct dm_mmc_ops *ops = mmc_get_ops(dev);
int ret;
mmmc_trace_before_send(mmc, cmd);
if (ops->send_cmd)
ret = ops->send_cmd(dev, cmd, data);
else
ret = -ENOSYS;
mmmc_trace_after_send(mmc, cmd, ret);
return ret;
}
我们发现,该函数最终又调用了struct dm_mmc_ops *ops的send_cmd函数指针,关于struct dm_mmc_ops定义如下:
struct dm_mmc_ops {
/**
* deferred_probe() - Some configurations that need to be deferred
* to just before enumerating the device
*
* @dev: Device to init
* @return 0 if Ok, -ve if error
*/
int (*deferred_probe)(struct udevice *dev);
/**
* send_cmd() - Send a command to the MMC device
*
* @dev: Device to receive the command
* @cmd: Command to send
* @data: Additional data to send/receive
* @return 0 if OK, -ve on error
*/
int (*send_cmd)(struct udevice *dev, struct mmc_cmd *cmd,
struct mmc_data *data);
/**
* set_ios() - Set the I/O speed/width for an MMC device
*
* @dev: Device to update
* @return 0 if OK, -ve on error
*/
int (*set_ios)(struct udevice *dev);
/**
* get_cd() - See whether a card is present
*
* @dev: Device to check
* @return 0 if not present, 1 if present, -ve on error
*/
int (*get_cd)(struct udevice *dev);
/**
* get_wp() - See whether a card has write-protect enabled
*
* @dev: Device to check
* @return 0 if write-enabled, 1 if write-protected, -ve on error
*/
int (*get_wp)(struct udevice *dev);
#ifdef MMC_SUPPORTS_TUNING
/**
* execute_tuning() - Start the tuning process
*
* @dev: Device to start the tuning
* @opcode: Command opcode to send
* @return 0 if OK, -ve on error
*/
int (*execute_tuning)(struct udevice *dev, uint opcode);
#endif
/**
* wait_dat0() - wait until dat0 is in the target state
* (CLK must be running during the wait)
*
* @dev: Device to check
* @state: target state
* @timeout_us: timeout in us
* @return 0 if dat0 is in the target state, -ve on error
*/
int (*wait_dat0)(struct udevice *dev, int state, int timeout_us);
#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT)
/* set_enhanced_strobe() - set HS400 enhanced strobe */
int (*set_enhanced_strobe)(struct udevice *dev);
#endif
/**
* host_power_cycle - host specific tasks in power cycle sequence
* Called between mmc_power_off() and
* mmc_power_on()
*
* @dev: Device to check
* @return 0 if not present, 1 if present, -ve on error
*/
int (*host_power_cycle)(struct udevice *dev);
/**
* get_b_max - get maximum length of single transfer
* Called before reading blocks from the card,
* useful for system which have e.g. DMA limits
* on various memory ranges.
*
* @dev: Device to check
* @dst: Destination buffer in memory
* @blkcnt: Total number of blocks in this transfer
* @return maximum number of blocks for this transfer
*/
int (*get_b_max)(struct udevice *dev, void *dst, lbaint_t blkcnt);
};
其实,这种调用函数指针的方式同样用在了其他一些用于初始化时的函数,比如下列函数:
3.3.3.2 mmc_set_ios函数
该函数的功能为设置sd控制器的时钟以及数据线宽度。
int mmc_set_ios(struct mmc *mmc)
{
return dm_mmc_set_ios(mmc->dev);
}
int dm_mmc_set_ios(struct udevice *dev)
{
struct dm_mmc_ops *ops = mmc_get_ops(dev);
if (!ops->set_ios)
return -ENOSYS;
return ops->set_ios(dev);
}
3.3.3.3 mmc_getwp函数
该函数的功能为获取sd卡的写保护功能是否使能。
int mmc_getwp(struct mmc *mmc)
{
return dm_mmc_get_wp(mmc->dev);
}
int dm_mmc_get_wp(struct udevice *dev)
{
struct dm_mmc_ops *ops = mmc_get_ops(dev);
if (!ops->get_wp)
return -ENOSYS;
return ops->get_wp(dev);
}
3.3.3.4 mmc_getcd函数
该函数的功能为探测sd卡是否插入。
int mmc_getcd(struct mmc *mmc)
{
return dm_mmc_get_cd(mmc->dev);
}
int dm_mmc_get_cd(struct udevice *dev)
{
struct dm_mmc_ops *ops = mmc_get_ops(dev);
if (!ops->get_cd)
return -ENOSYS;
return ops->get_cd(dev);
}
3.3.3.5 增加核心层的原因
上述一系列函数都只是调用了struct dm_mmc_ops结构体中的函数指针,我们可以想一下,为什么要这样做呢?
我们要知道,u-boot需要支持不同的sd控制器,而每种sd控制器针对上述的一系列函数的实现均不同,这样,我们就需要在上文提到的协议层与对sd控制器的控制(也就是我们后面的驱动层的内容)之间增加一层核心层,作用便是解除协议层与驱动层之间的耦合。
当需要支持不同的sd控制器时,可以针对每个sd控制器分别实现一套上述的一系列函数,然后将每一套函数均赋值到自己的struct dm_mmc_ops结构体的各个函数指针中。当需要调用时,只需找到针对每个sd控制器的struct dm_mmc_ops结构体,然后调用其中的函数指针即可。
这里我们可以猜想到,驱动层的主要工作应该就是针对每个sd控制器,去实现具体的struct dm_mmc_ops结构体中的函数指针对应的函数,事实的确如此,接下来我们便开始进入驱动层的世界。
3.3.4 驱动层
注:该驱动主要内容来自u-boot 2016版本中的sd驱动,在此基础上我做了如下工作:
- 由于u-boot 2016版本中的sd驱动并未支持dm驱动模型,故增加了适配dm驱动模型的代码
- 利用pinctrl子系统配置sd控制器接入到sd卡的引脚为sd模式
- 利用clock子系统使能时钟,获得时钟的频率
- 利用gpio子系统获得sd卡探测引脚的电平,以探测sd卡是否插入
如3.3.3.5一节中分析的那样,驱动层的工作主要便是实现struct dm_mmc_ops结构体中的需要的函数指针对应的函数,此外还需要实现一个probe函数,用于初始化sd控制器,而这一切又包含于驱动模型之中,对于驱动模型不熟悉的朋友请参考3.3.1一节。
3.3.4.1 驱动入口
下面是sd卡驱动符合驱动模型的入口:
static const struct udevice_id s3c2440_mmc_ids[] = {
{ .compatible = "samsung,s3c2440-mmc"},
{ /* sentinel */ }
};
U_BOOT_DRIVER(s3c2440_mmc_drv) = {
.name = "s3c2440_mmc",
.id = UCLASS_MMC,
.of_match = s3c2440_mmc_ids,
.bind = s3c2440_mmc_bind,
.probe = s3c2440_mmc_probe,
.ops = &s3c2440_mmc_ops,
.platdata_auto_alloc_size = sizeof(struct s3c2440_mmc_plat),
.priv_auto_alloc_size = sizeof(struct s3c2440_mmc_priv),
};
3.3.4.2 probe函数
probe函数的作用主要是初始化该驱动,下面我们分功能来了解该函数。
- 从设备树中获取sd控制器中寄存器的基地址、sd控制器支持的数据线宽度、sd控制器的时钟:
代码如下:
priv->reg = (void *)dev_read_addr(dev);
bus_width = dev_read_u32_default(dev, "bus-width", 1);
ret = clk_get_by_name(dev, "sdmmc_clk", &priv->clk);
if (ret) {
printf("%s(): get sdmmc clk failed!\n", __func__);
return -ENODEV;
}
设备树节点如下:
sdmmc: mmc@5a000000 {
compatible = "samsung,s3c2440-mmc";
reg = <0x5a000000 0x1000>;
clock-names = "sdmmc_clk";
clocks = <&clk PCLK_SDI>;
pinctrl-names = "default";
pinctrl-0 = <&sdmmc_pin>,<&cd_pin>;
cd-gpio = <&gpiog 8 GPIO_ACTIVE_LOW>;
bus-width = <4>;
};
- 使能sd控制器的时钟,该时钟从PCLK而来,并且获得时钟具体的频率,以供后面使用:
ret = clk_enable(&priv->clk);
if (ret) {
printf("%s(): enable sdmmc clk failed!\n", __func__);
return -EAGAIN;
}
sdmmc_clk = clk_get_rate(&priv->clk);
printf("%s(): sdmmc_clk = %d\n", __func__, sdmmc_clk);
- 提供sd控制器的能力给核心层驱动:
cfg->name = dev->name;
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS;
cfg->f_min = 400000;
cfg->f_max = sdmmc_clk / 2;
cfg->b_max = 0x80;
upriv->mmc = &plat->mmc;
- 初始化sd控制器:
writel(S3C2440_SDICON_SDRESET, &priv->reg->sdicon);
mdelay(10);
writel(0x7fffff, &priv->reg->sdidtimer);
writel(MMC_MAX_BLOCK_LEN, &priv->reg->sdibsize);
writel(0x0, &priv->reg->sdiimsk);
writel(S3C2410_SDICON_FIFORESET | S3C2410_SDICON_CLOCKTYPE,
&priv->reg->sdicon);
mdelay(125);
完整代码如下:
static int s3c2440_mmc_probe(struct udevice *dev)
{
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
struct s3c2440_mmc_plat *plat = dev_get_platdata(dev);
struct s3c2440_mmc_priv *priv = dev_get_priv(dev);
struct mmc_config *cfg = &plat->cfg;
int bus_width, ret;
u32 sdmmc_clk;
priv->reg = (void *)dev_read_addr(dev);
bus_width = dev_read_u32_default(dev, "bus-width", 1);
ret = clk_get_by_name(dev, "sdmmc_clk", &priv->clk);
if (ret) {
printf("%s(): get sdmmc clk failed!\n", __func__);
return -ENODEV;
}
ret = clk_enable(&priv->clk);
if (ret) {
printf("%s(): enable sdmmc clk failed!\n", __func__);
return -EAGAIN;
}
sdmmc_clk = clk_get_rate(&priv->clk);
printf("%s(): sdmmc_clk = %d\n", __func__, sdmmc_clk);
cfg->name = dev->name;
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS;
cfg->f_min = 400000;
cfg->f_max = sdmmc_clk / 2;
cfg->b_max = 0x80;
upriv->mmc = &plat->mmc;
writel(S3C2440_SDICON_SDRESET, &priv->reg->sdicon);
mdelay(10);
writel(0x7fffff, &priv->reg->sdidtimer);
writel(MMC_MAX_BLOCK_LEN, &priv->reg->sdibsize);
writel(0x0, &priv->reg->sdiimsk);
writel(S3C2410_SDICON_FIFORESET | S3C2410_SDICON_CLOCKTYPE,
&priv->reg->sdicon);
mdelay(125);
return 0;
}
3.3.4.3 ops操作函数集
ops操作函数集中的每个函数均完成一个单独的功能,由核心层进行调用,定义如下:
static const struct dm_mmc_ops s3c2440_mmc_ops = {
.send_cmd = s3c2440_mmc_send_cmd,
.set_ios = s3c2440_mmc_set_ios,
.get_cd = s3c2440_mmc_getcd,
.get_wp = s3c2440_mmc_getwp,
};
下面我们逐个分析每个函数。
- s3c2440_mmc_send_cmd函数
该函数是整个sd驱动的核心,其通过读写sd控制器中的寄存器,提供发送命令到sd卡的功能,如果命令伴有响应返回则会读取响应值,如果命令伴有数据传输则会进行收发数据。
/*
* WARNING: We only support one SD IP block.
* NOTE: It's not likely there will ever exist an S3C24xx with two,
* at least not in this universe all right.
*/
static int s3c2440_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
struct mmc_data *data)
{
struct s3c2440_mmc_plat *plat = dev_get_platdata(dev);
struct s3c2440_mmc_priv *priv = dev_get_priv(dev);
struct mmc *mmc = &plat->mmc;
unsigned int timeout = 100000;
int ret = 0, xfer_len, data_offset = 0;
uint32_t sdiccon, sdicsta, sdidcon, sdidsta, sdidat, sdifsta;
uint32_t sdicsta_wait_bit = S3C2410_SDICMDSTAT_CMDSENT;
const uint32_t sdidsta_err_mask = S3C2410_SDIDSTA_FIFOFAIL |
S3C2410_SDIDSTA_CRCFAIL | S3C2410_SDIDSTA_RXCRCFAIL |
S3C2410_SDIDSTA_DATATIMEOUT;
writel(0xffffffff, &priv->reg->sdicsta);
writel(0xffffffff, &priv->reg->sdidsta);
writel(0xffffffff, &priv->reg->sdifsta);
/* Set up data transfer (if applicable). */
if (data) {
writel(data->blocksize, &priv->reg->sdibsize);
sdidcon = data->blocks & S3C2410_SDIDCON_BLKNUM;
sdidcon |= S3C2410_SDIDCON_BLOCKMODE;
sdidcon |= S3C2440_SDIDCON_DS_WORD | S3C2440_SDIDCON_DATSTART;
if (mmc->bus_width == 4)
sdidcon |= S3C2410_SDIDCON_WIDEBUS;
if (data->flags & MMC_DATA_READ) {
sdidcon |= S3C2410_SDIDCON_RXAFTERCMD;
sdidcon |= S3C2410_SDIDCON_XFER_RXSTART;
} else {
sdidcon |= S3C2410_SDIDCON_TXAFTERRESP;
sdidcon |= S3C2410_SDIDCON_XFER_TXSTART;
}
writel(sdidcon, &priv->reg->sdidcon);
}
/* Write CMD arg. */
writel(cmd->cmdarg, &priv->reg->sdicarg);
/* Write CMD index. */
sdiccon = cmd->cmdidx & S3C2410_SDICMDCON_INDEX;
sdiccon |= S3C2410_SDICMDCON_SENDERHOST;
sdiccon |= S3C2410_SDICMDCON_CMDSTART;
/* Command with short response. */
if (cmd->resp_type & MMC_RSP_PRESENT) {
sdiccon |= S3C2410_SDICMDCON_WAITRSP;
sdicsta_wait_bit = S3C2410_SDICMDSTAT_RSPFIN;
}
/* Command with long response. */
if (cmd->resp_type & MMC_RSP_136)
sdiccon |= S3C2410_SDICMDCON_LONGRSP;
/* Start the command. */
writel(sdiccon, &priv->reg->sdiccon);
/* Wait for the command to complete or for response. */
for (timeout = 100000; timeout; timeout--) {
sdicsta = readl(&priv->reg->sdicsta);
if (sdicsta & sdicsta_wait_bit)
break;
if (sdicsta & S3C2410_SDICMDSTAT_CMDTIMEOUT)
timeout = 1;
}
/* Clean the status bits. */
writel(readl(&priv->reg->sdicsta) | (0xf << 9), &priv->reg->sdicsta);
// setbits_le32(&priv->reg->sdicsta, 0xf << 9);
if (!timeout) {
printf("S3C SDI: Command timed out!\n");
ret = -ETIMEDOUT;
goto error;
}
/* Read out the response. */
if (cmd->resp_type & MMC_RSP_136) {
cmd->response[0] = readl(&priv->reg->sdirsp0);
cmd->response[1] = readl(&priv->reg->sdirsp1);
cmd->response[2] = readl(&priv->reg->sdirsp2);
cmd->response[3] = readl(&priv->reg->sdirsp3);
} else {
cmd->response[0] = readl(&priv->reg->sdirsp0);
}
/* If there are no data, we're done. */
if (!data)
return 0;
xfer_len = data->blocksize * data->blocks;
while (xfer_len > 0) {
sdidsta = readl(&priv->reg->sdidsta);
sdifsta = readl(&priv->reg->sdifsta);
if (sdidsta & sdidsta_err_mask) {
printf("S3C SDI: Data error (sdta=0x%08x)\n", sdidsta);
ret = -EIO;
goto error;
}
if (data->flags & MMC_DATA_READ) {
if ((sdifsta & S3C2410_SDIFSTA_COUNTMASK) < 4)
continue;
sdidat = readl(&priv->reg->sdidat);
put_unaligned_le32(sdidat, data->dest + data_offset);
} else { /* Write */
/* TX FIFO half full. */
if (!(sdifsta & S3C2410_SDIFSTA_TFHALF))
continue;
/* TX FIFO is below 32b full, write. */
sdidat = get_unaligned_le32(data->src + data_offset);
writel(sdidat, &priv->reg->sdidat);
}
data_offset += 4;
xfer_len -= 4;
}
/* Wait for the command to complete or for response. */
for (timeout = 100000; timeout; timeout--) {
sdidsta = readl(&priv->reg->sdidsta);
if (sdidsta & S3C2410_SDIDSTA_XFERFINISH)
break;
if (sdidsta & S3C2410_SDIDSTA_DATATIMEOUT)
timeout = 1;
}
/* Clear status bits. */
writel(0x6f8, &priv->reg->sdidsta);
if (!timeout) {
printf("S3C SDI: Command timed out!\n");
ret = -ETIMEDOUT;
goto error;
}
writel(0, &priv->reg->sdidcon);
return 0;
error:
return ret;
}
- s3c2440_mmc_set_ios函数
该函数获得核心层传来的需要设置的时钟值,然后进行设置。
static int s3c2440_mmc_set_ios(struct udevice *dev)
{
struct s3c2440_mmc_plat *plat = dev_get_platdata(dev);
struct s3c2440_mmc_priv *priv = dev_get_priv(dev);
struct mmc *mmc = &plat->mmc;
uint32_t divider = 0;
u32 sdmmc_clk;
printf("set ios: bus_width: %x, clock: %d\n",
mmc->bus_width, mmc->clock);
if (!mmc->clock)
return -1;
sdmmc_clk = clk_get_rate(&priv->clk);
printf("%s(): sdmmc_clk = %d\n", __func__, sdmmc_clk);
divider = DIV_ROUND_UP(sdmmc_clk, mmc->clock);
if (divider)
divider--;
writel(divider, &priv->reg->sdipre);
mdelay(125);
return 0;
}
- s3c2440_mmc_getcd函数
该函数通过读取sd卡的探测引脚的电平值,进而供核心层判断sd卡是否插入。
static int s3c2440_mmc_getcd(struct udevice *dev)
{
struct s3c2440_mmc_priv *priv = dev_get_priv(dev);
int value, ret;
ret = gpio_request_by_name(dev, "cd-gpio", 0, &priv->cd_gpio, GPIOD_IS_IN);
if (ret) {
printf("%s(): gpio_request_by_name failed!\n", __func__);
return ret;
}
if (!dm_gpio_is_valid(&priv->cd_gpio)) {
printf("%s(): dm_gpio_is_valid failed!\n", __func__);
return ret;
}
value = dm_gpio_get_value(&priv->cd_gpio);
printf("%s(): value = %d\n", __func__, value);
dm_gpio_free(dev, &priv->cd_gpio);
return value;
}
- s3c2440_mmc_getwp函数
该函数的作用是获取sd卡写保护引脚的电平值,由于micro sd卡没有写保护功能,故该函数设置为固定返回0,以告知核心层sd卡没有写保护,可以直接写入。
static int s3c2440_mmc_getwp(struct udevice *dev)
{
/* There is allways no write-protected */
return 0;
}
4. 关于文件系统
文件系统,纯软件的概念,是一种数据在存储介质上的组织格式,目的是把对扇区的读写转换为对目录下的文件的读写。
sd卡之上的文件系统,最终也会调用sd卡的读写函数来完成对某个文件的读写。
由于这里不是本篇文章的重点,所以就不详细说明了。