嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二) 嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。一、开发环境- 主 机:VMWare--Fedora 9
- 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4
- 编译器:arm-linux-gcc-4.3.2
上接:S3C2440上MMC/SD卡驱动实例开发讲解(一)6. s3cmci_ops SDI主机控制器操作接口函数功能分析: static struct mmc_host_ops s3cmci_ops =
{
.request = s3cmci_request,//实现host的请求处理(即:命令和数据的发送和接收)
.set_ios = s3cmci_set_ios,//通过核心层传递过来的ios,配置host寄存器(使能时钟、总线带宽等)
.get_ro = s3cmci_get_ro,//通过读取GPIO端口来判断卡是否写有保护
.get_cd = s3cmci_card_present,//通过读取GPIO端口来判断卡是否存在
};
mmc_host_ops结构体定义了对host主机进行操作的各种方法,其定义在Core核心层的host.h中,也就是Core核心层对Host主机层提供的接口函数。这里各种方法的函数原型如下:void (*request)(struct mmc_host *host, struct mmc_request *req);
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
int (*get_ro)(struct mmc_host *host);
int (*get_cd)(struct mmc_host *host);
从各函数原型上看,他们都将mmc_host结构体作为参数,所以我在刚开始的时候就说过mmc_host结构体是MMC/SD卡驱动中比较重要的数据结构。 可以这样说,他是Core层与Host层进行数据交换的载体。那么,这些接口函数何时会被调用呢?答案可以在Core层的core.c和sd.c中找到,我们可以看到如下部分代码: static void mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
......
host->ops->request(host, mrq);//导致s3cmci_request被调用
}
static inline void mmc_set_ios(struct mmc_host *host)
{
......
host->ops->set_ios(host, ios);//导致s3cmci_set_ios被调用
}
void mmc_rescan(struct work_struct *work)
{
......//导致s3cmci_card_present被调用
if (host->ops->get_cd && host->ops->get_cd(host) == 0)
goto out;
......
}
static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
......
/* Check if read-only switch is active.*/
if (!oldcard)
{ //导致s3cmci_get_ro被调用
if (!host->ops->get_ro || host->ops->get_ro(host) < 0)
{
printk(KERN_WARNING "%s: host does not "
"support reading read-only "
"switch. assuming write-enable.
",
mmc_hostname(host));
}
else
{
if (host->ops->get_ro(host) > 0)
mmc_card_set_readonly(card);
}
}
......
}
好了,我们开始分析每个接口函数的具体实现吧,从简单的开始吧。 判断卡是否存在,如下代码:
static int s3cmci_card_present(struct mmc_host *mmc)
{
//从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
struct s3cmci_host *host = mmc_priv(mmc);
struct s3c24xx_mci_pdata *pdata = host->pdata;
int ret;
//判断有无设置卡检测引脚端口,引脚在s3cmci_probe函数中已设置
if (pdata->gpio_detect == 0)
return -ENOSYS;
//从设置的卡检测引脚中读出当前的电平值,来判断卡是插入存在的还是被拔出不存在的
ret = s3c2410_gpio_getpin(pdata->gpio_detect) ? 0 : 1;
return ret ^ pdata->detect_invert;
}
获取卡是否写有保护,其实实现跟卡检查类似,代码如下:
static int s3cmci_get_ro(struct mmc_host *mmc)
{
//从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
struct s3cmci_host *host = mmc_priv(mmc);
struct s3c24xx_mci_pdata *pdata = host->pdata;
int ret;
//判断有无设置卡写保护引脚端口,引脚在s3cmci_probe函数中已设置
if (pdata->gpio_wprotect == 0)
return 0;
//从设置的卡写保护引脚中读出当前的电平值,来判断卡是否写有保护
ret = s3c2410_gpio_getpin(pdata->gpio_wprotect);
if (pdata->wprotect_invert)
ret = !ret;
return ret;
}
配置host寄存器的时钟和总线宽度,代码如下: static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
//从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
struct s3cmci_host *host = mmc_priv(mmc);
u32 mci_con;
//读取SDI控制寄存器的值
mci_con = readl(host->base + S3C2410_SDICON);
//ios结构体参数从Core层传递过来,根据不同的电源状态来配置SDI各寄存器
switch (ios->power_mode)
{
case MMC_POWER_ON:
case MMC_POWER_UP:
//根据开发板引脚连接情况配置SDI控制器的各信号线,包括:时钟线、命令线和四条数据线
s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD);
s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0);
s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1);
s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);
s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);
if (host->pdata->set_power)
host->pdata->set_power(ios->power_mode, ios->vdd);
break;
case MMC_POWER_OFF:
default:
//如果电源状态为关闭或者默认情况下,关闭SDI的时钟信号
s3c2410_gpio_setpin(S3C2410_GPE5, 0);
s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP);
//根据数据手册的SDICON寄存器位的介绍,此处是将整个sdmmc时钟复位
mci_con |= S3C2440_SDICON_SDRESET;
if (host->pdata->set_power)
host->pdata->set_power(ios->power_mode, ios->vdd);
break;
}
//设置SDI波特率预定标器寄存器以确定时钟,看其定义部分
s3cmci_set_clk(host, ios);
//根据SDI当前的时钟频率来设置寄存器的使能时钟位
if (ios->clock)
mci_con |= S3C2410_SDICON_CLOCKTYPE;
else
mci_con &= ~S3C2410_SDICON_CLOCKTYPE;
//将计算好的值写回SDI控制寄存器
writel(mci_con, host->base + S3C2410_SDICON);
//下面只是一些调试信息,可以不要
if ((ios->power_mode == MMC_POWER_ON) || (ios->power_mode == MMC_POWER_UP))
{
dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).
",
host->real_rate/1000, ios->clock/1000);
}
else
{
dbg(host, dbg_conf, "powered down.
");
}
//设置总线宽度
host->bus_width = ios->bus_width;
}
//设置SDI波特率预定标器寄存器以确定时钟
static void s3cmci_set_clk(struct s3cmci_host *host, struct mmc_ios *ios)
{
u32 mci_psc;
//根据SDI工作时钟频率范围来确定时钟预分频器值
for (mci_psc = 0; mci_psc < 255; mci_psc++)
{
host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1));
if (host->real_rate <= ios->clock)
break;
}
//根据数据手册描述,SDI波特率预定标器寄存器只有8个位,所以最大值为255
if (mci_psc > 255)
mci_psc = 255;
host->prescaler = mci_psc;//确定的预分频器值
//将预分频器值写于SDI波特率预定标器寄存器中
writel(host->prescaler, host->base + S3C2410_SDIPRE);
if (ios->clock == 0)
host->real_rate = 0;
}
MMC/SD请求处理,这是Host驱动中比较重要的一部分。请求处理的整个流程请参考(一)中的流程图,他很好的描述了一个请求是怎样从Host层发出,通过Core层提交到Card层被块设备处理的。下面看代码: static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
//从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
struct s3cmci_host *host = mmc_priv(mmc);
//s3cmci_host结构体定义的status主要是记录请求过程所处的阶段及状态,方便调试时使用
host->status = "mmc request";
//请求处理主要包括MMC/SD命令和数据处理,所以定义cmd_is_stop来区分是哪种请求
host->cmd_is_stop = 0;
//将Core层的mmc_request对象保存到Host层中以备使用
host->mrq = mrq;
//在开始发出一个请求前先要检测一下卡是否还存在,否则提交到了块设备层而没有请求处理的对象发生错误
if (s3cmci_card_present(mmc) == 0)
{
dbg(host, dbg_err, "%s: no medium present
", __func__);
host->mrq->cmd->error = -ENOMEDIUM;
mmc_request_done(mmc, mrq);//如果卡不存在则马上结束这次请求
}
else
{
s3cmci_send_request(mmc);//如果卡还存在则发出请求
}
}
//发送请求
static void s3cmci_send_request(struct mmc_host *mmc)
{
//从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
struct s3cmci_host *host = mmc_priv(mmc);
//取出在s3cmci_request函数中保存的mmc_request对象以使用
struct mmc_request *mrq = host->mrq;
//在s3cmci_request函数中设置的cmd_is_stop初始值为0,表示当前是命令请求
struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
//清空SDI命令状态寄存器、数据状态寄存器和FIFO状态寄存器
writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT);
writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA);
writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA);
//如果当前这次的请求是数据请求
if (cmd->data)
{
//进入数据请求处理设置,主要是数据控制寄存器的配置
int res = s3cmci_setup_data(host, cmd->data);
if (res)
{
//如果在数据请求设置中出现异常,则马上结束这次请求
dbg(host, dbg_err, "setup data error %d
", res);
cmd->error = res;
cmd->data->error = res;
mmc_request_done(mmc, mrq);
return;
}
//判断数据处理的方式是DAM还是FIFO,在s3cmci_probe函数中初始的是0,所以没有使用DMA的方式
if (host->dodma)
res = s3cmci_prepare_dma(host, cmd->data);
else
res = s3cmci_prepare_pio(host, cmd->data);
if (res)
{
//如果请求处理数据失败则也要马上结束这次请求
dbg(host, dbg_err, "data prepare error %d
", res);
cmd->error = res;
cmd->data->error = res;
mmc_request_done(mmc, mrq);
return;
}
}
//否则这次请求是命令请求
s3cmci_send_command(host, cmd);
//还记得在s3cmci_probe中SDI未准备好是屏蔽了SD中断,所以这里就使能中断
enable_irq(host->irq);
}
//数据请求处理设置,主要是数据控制寄存器的配置
static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
{
u32 dcon, imsk, stoptries = 3;
/*如果不是数据处理请求则清零SDI数据控制寄存器*/
if (!data)
{
writel(0, host->base + S3C2410_SDIDCON);
return 0;
}
//根据SDI模块大小寄存器描述,如果在多模块下BlkSize必须分配字大小即:BlkSize[1:0]=00
//所以这里与上3(即:二进制的11)来判断的是单模块
if ((data->blksz & 3) != 0)
{
//如果在单模块处理的情况下,模块数大于1了,就出现异常
if (data->blocks > 1)
{
pr_warning("%s: can't do non-word sized block transfers (blksz %d)
", __func__, data->blksz);
return -EINVAL;
}
}
//循环判断数据是否正在传输中(发送或者接收)
while (readl(host->base + S3C2410_SDIDSTA) & (S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON))
{
dbg(host, dbg_err, "mci_setup_data() transfer stillin progress.
");
//如果正在传输中则立刻停止传输
writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
//接着立刻复位整个MMC/SD时钟
s3cmci_reset(host);
//这里应该是起到一个延迟的效果。因为硬件停止传输到复位MMC/SD需要一点时间,而循环判断非常快。
//如果在这个时间内硬件还处在数据传输中而没有复位好,则异常
if ((stoptries--) == 0)
{
return -EINVAL;
}
}
dcon = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;
//如果使用DMA传输,则使能SDI数据控制寄存器的DMA
if (host->dodma)
dcon |= S3C2410_SDIDCON_DMAEN;
//如果设置总线宽度为4线,则使能SDI数据控制寄存器的总线宽度模式为宽总线模式(即:4线模式)
if (host->bus_width == MMC_BUS_WIDTH_4)
dcon |= S3C2410_SDIDCON_WIDEBUS;
//配置SDI数据控制寄存器的数据传输模式为模块数据传输
if (!(data->flags & MMC_DATA_STREAM))
dcon |= S3C2410_SDIDCON_BLOCKMODE;
if (data->flags & MMC_DATA_WRITE)
{
//数据发送命令响应收到后开始数据传输
dcon |= S3C2410_SDIDCON_TXAFTERRESP;
//数据发送模式
dcon |= S3C2410_SDIDCON_XFER_TXSTART;
}
if (data->flags & MMC_DATA_READ)
{
//数据发送命令响应收到后开始数据接收
dcon |= S3C2410_SDIDCON_RXAFTERCMD;
//数据接收模式
dcon |= S3C2410_SDIDCON_XFER_RXSTART;