入式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.\n", 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).\n", host->real_rate/1000, ios->clock/1000); } else { dbg(host, dbg_conf, "powered down.\n"); }
//设置总线宽度 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\n", __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\n", 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\n", 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); }
|
阅读(1154) | 评论(0) | 转发(0) |