嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)
(转自:http://www.cublog.cn/u3/101649/showart_2232724.html)
嵌入式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); } |
//数据请求处理设置,主要是数据控制寄存器的配置 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)/n", __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./n"); //如果正在传输中则立刻停止传输 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; } //FIFO传输的大小使用字传输类型 dcon |= S3C2440_SDIDCON_DS_WORD; //数据传输开始 dcon |= S3C2440_SDIDCON_DATSTART; //将以上配置的值写入SDI数据控制寄存器生效 writel(dcon, host->base + S3C2410_SDIDCON); //配置模块大小寄存器的块大小值 writel(data->blksz, host->base + S3C2410_SDIBSIZE); //出现FIFO失败SDI中断使能;数据接收CRC错误SDI中断使能;数据接收超时SDI中断使能;数据计时器为0SDI中断使能 imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC | S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH; enable_imask(host, imsk);//使能中断 //将配置的值写入SDI中断屏蔽寄存器,使之生效 writel(0x007FFFFF, host->base + S3C2410_SDITIMER); return 0; } //复位整个MMC/SD时钟 static void s3cmci_reset(struct s3cmci_host *host) { u32 con = readl(host->base + S3C2410_SDICON); con |= S3C2440_SDICON_SDRESET; writel(con, host->base + S3C2410_SDICON); } //使能中断 static inline u32 enable_imask(struct s3cmci_host *host, u32 imask) { u32 newmask; newmask = readl(host->base + host->sdiimsk); newmask |= imask; writel(newmask, host->base + host->sdiimsk); return newmask; } //屏蔽中断 static inline u32 disable_imask(struct s3cmci_host *host, u32 imask) { u32 newmask; newmask = readl(host->base + host->sdiimsk); newmask &= ~imask; writel(newmask, host->base + host->sdiimsk); return newmask; } //清空中断屏蔽寄存器 static inline void clear_imask(struct s3cmci_host *host) { writel(0, host->base + host->sdiimsk); } |
//使用DMA传输数据方式,注意:这里就不讲如何使用DMA的具体细节了,以后再讲。 //对于驱动中相关DMA操作的方法都在plat-s3c24xx/dma.c中定义了。 static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data) { int dma_len, i; //判断DMA传输的方向是读还是写 int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0; //根据传输的方向来配置DMA相关寄存器 s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW); //s3c2410_dma_ctrl函数将根据标志flag来控制DMA传输的开始、停止等操作 s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); //合并data->sg上相邻的段,映射一个发散/汇聚DMA操作 //返回值是传送的DMA缓冲区数,可能会小于sg_len,也就是说sg_len与dma_len可能是不同。 dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); if (dma_len == 0) return -ENOMEM; host->dma_complete = 0;//初始DMA操作的状态 host->dmatogo = dma_len;//保存合并后的段数 for (i = 0; i < dma_len; i++) { int res; //分配一个数据段管理结构体,并将各数据段穿成单向链表,以及加载一个数据段到DMA通道 //sg_dma_address返回的是总线(DMA)的地址,sg_dma_len返回的是缓存区的长度 res = s3c2410_dma_enqueue(host->dma, (void *) host, sg_dma_address(&data->sg[i]), sg_dma_len(&data->sg[i])); if (res) { s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); return -EBUSY; } } //开始DMA数据传输,数据传输会在接收到请求后真正开始 s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START); return 0; } //根据传输的方向来配置DMA相关寄存器,详细描述请查看数据手册DMA章节 static void s3cmci_dma_setup(struct s3cmci_host *host, enum s3c2410_dmasrc source) { static enum s3c2410_dmasrc last_source = -1; static int setup_ok; if (last_source == source) return; last_source = source; //配置DMA源或者目标硬件类型和地址,这里DMA使用的是物理地址,不是虚拟地址。 s3c2410_dma_devconfig(host->dma, source, 3, host->mem->start + host->sdidata); //这个判断的作用是让下面的代码只执行一次,以后不在被执行 if (!setup_ok) { //配置DMA控制寄存器中的传输数据大小单位 s3c2410_dma_config(host->dma, 4, 0); //设置DMA回调函数为s3cmci_dma_done_callback,当一段数据传输完后该函数被调用 s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback); s3c2410_dma_setflags(host->dma, S3C2410_DMAF_AUTOSTART); setup_ok = 1; } } //DMA回调函数, 当一段数据传输完后该函数被调用 static void s3cmci_dma_done_callback(struct s3c2410_dma_chan *dma_ch, void *buf_id, int size, enum s3c2410_dma_buffresult result) { struct s3cmci_host *host = buf_id;//这个s3cmci_host类型的参数是在s3c2410_dma_enqueue的时候传递进来的 unsigned long iflags; u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt; mci_csta = readl(host->base + S3C2410_SDICMDSTAT);//命令状态寄存器的值 mci_dsta = readl(host->base + S3C2410_SDIDSTA);//数据状态寄存器的值 mci_fsta = readl(host->base + S3C2410_SDIFSTA);//FIFO状态寄存器的值 mci_dcnt = readl(host->base + S3C2410_SDIDCNT);//数据保留计数器寄存器的值 spin_lock_irqsave(&host->complete_lock, iflags); //如果DMA返回错误,则调到错误处理处进行错误处理 if (result != S3C2410_RES_OK) { goto fail_request; } host->dmatogo--; //合并data->sg上相邻后的段数递减 //如果合并的段数不为0,即所有的段还没有处理完 if (host->dmatogo) { goto out; } //否则,标识这次DMA操作真正完成了 host->complete_what = COMPLETION_FINALIZE; out: //切换到中断底半部执行 tasklet_schedule(&host->pio_tasklet); spin_unlock_irqrestore(&host->complete_lock, iflags); return; fail_request: host->mrq->data->error = -EINVAL; host->complete_what = COMPLETION_FINALIZE; //如果DMA请求失败,则屏蔽SDI中断 writel(0, host->base + host->sdiimsk); goto out; } |
//使用FIFO传输数据方式。具体操作就是调用do_pio_write往FIFO中填充数据,当64字节的FIFO少于33字节时就会产生中断; //或者是从SD读数据,则先使能中断,当FIFO多于31字节时时,则会调用中断服务程序,中断服务程序中将会调用do_pio_read读出FIFO的数据。 static int s3cmci_prepare_pio(struct s3cmci_host *host, struct mmc_data *data) { //跟DMA类似,这里同样要判断FIFO传输的方向是读还是写 int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0; host->pio_sgptr = 0; host->pio_bytes = 0; host->pio_count = 0; host->pio_active = rw ? XFER_WRITE : XFER_READ;//记录FIFO操作状态共三种:读、写和无操作,定义在驱动头文件中 if (rw) //写 { //FIFO写操作 do_pio_write(host); //使能中断。根据数据手册SDI中断屏蔽寄存器的描述,当发送FIFO半填满就产生SDI中断 enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); } else //读 { //使能中断。根据数据手册SDI中断屏蔽寄存器的描述,当接收FIFO半填满或者接收FIFO有最后数据就产生SDI中断 enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST); } return 0; } //FIFO写操作(即填充FIFO) static void do_pio_write(struct s3cmci_host *host) { void __iomem *to_ptr; int res; u32 fifo; u32 *ptr; //SDI数据寄存器的虚拟地址 to_ptr = host->base + host->sdidata; //检查FIFO中当前的剩余空间 while ((fifo = fifo_free(host)) > 3) { if (!host->pio_bytes) { //从分散聚集列表中获取要写的数据缓存,这里主要是获取缓存的长度和开始地址 res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr); if (res) { host->pio_active = XFER_NONE; return; } } //如果FIFO剩余空间比这一次要写入的数据段长度要大 if (fifo >= host->pio_bytes) fifo = host->pio_bytes; else fifo -= fifo & 3; host->pio_bytes -= fifo;//更新还剩下没写完的缓存长度 host->pio_count += fifo; fifo = (fifo + 3) >> 2;//将字节数转化为字数 ptr = host->pio_ptr; while (fifo--)//写入FIFO writel(*ptr++, to_ptr); host->pio_ptr = ptr;//更新当前地址指针的位置 } //FIFO半填满时发生MMC/SD中断 enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); } //FIFO读操作 static void do_pio_read(struct s3cmci_host *host) { int res; u32 fifo; u32 *ptr; u32 fifo_words; void __iomem *from_ptr; //设置SDI波特率预定标器寄存器的值 writel(host->prescaler, host->base + S3C2410_SDIPRE); //SDI数据寄存器的虚拟地址 from_ptr = host->base + host->sdidata; //检测FIFO中当前的数据个数 while ((fifo = fifo_count(host))) { if (!host->pio_bytes) { //从分散聚集列表中获取要读数据缓存,这里主要是获取缓存的长度和开始地址的指针位置 res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr); if (res) { host->pio_active = XFER_NONE; host->complete_what = COMPLETION_FINALIZE; return; } } //如果FIFO中当前的数据个数比这一次要读出的数据段长度要大 if (fifo >= host->pio_bytes) fifo = host->pio_bytes; else fifo -= fifo & 3; host->pio_bytes -= fifo;//更新还剩下没读完的缓存长度 host->pio_count += fifo; fifo_words = fifo >> 2;//将字节数转化为字数 ptr = host->pio_ptr; while (fifo_words--)//从FIFO中读出数据 *ptr++ = readl(from_ptr); host->pio_ptr = ptr;//更新当前地址指针的位置 //如果fifo中的数据非字对齐则读取非对齐部分 if (fifo & 3) { u32 n = fifo & 3; u32 data = readl(from_ptr); u8 *p = (u8 *)host->pio_ptr; while (n--) { *p++ = data; data >>= 8; } } } //请求的数据已读完 if (!host->pio_bytes) { res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr); if (res) { host->pio_active = XFER_NONE; host->complete_what = COMPLETION_FINALIZE; return; } } //接收FIFO半满或者接收FIFO有最后数据时发生MMC/SD中断 enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST); } //检测FIFO中当前的数据个数 static inline u32 fifo_count(struct s3cmci_host *host) { //读取SDI FIFO状态寄存器 u32 fifostat = readl(host->base + S3C2410_SDIFSTA); //FIFO中的数据个数是保存在寄存器的0-6位,所以与上S3C2410_SDIFSTA_COUNTMASK得出数据个数值 //S3C2410_SDIFSTA_COUNTMASK定义在regs-sdi.h中为:0x7f,即:1111111 fifostat &= S3C2410_SDIFSTA_COUNTMASK; return fifostat; } //检查FIFO中当前的剩余空间 static inline u32 fifo_free(struct s3cmci_host *host) { //这里跟检测FIFO中当前的数据个数是一样的 u32 fifostat = readl(host->base + S3C2410_SDIFSTA); fifostat &= S3C2410_SDIFSTA_COUNTMASK; return 63 - fifostat;//用FIFO的总容量-FIFO中当前的数据个数=剩余空间 } //MMC/SD核心为mrq->data成员分配了一个struct scatterlist的表,用来支持分散聚集, //使用这种方法,使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题 static inline int get_data_buffer(struct s3cmci_host *host, u32 *bytes, u32 **pointer) { struct scatterlist *sg; //FIFO当前的操作状态验证 if (host->pio_active == XFER_NONE) return -EINVAL; //MMC/SD请求及数据有效性验证 if ((!host->mrq) || (!host->mrq->data)) return -EINVAL; //数据缓存的入口有没有超过分散列表的范围 if (host->pio_sgptr >= host->mrq->data->sg_len) return -EBUSY; //从分散聚集列表中获取一段数据缓存 sg = &host->mrq->data->sg[host->pio_sgptr]; *bytes = sg->length;//该段数据缓存的长度 *pointer = sg_virt(sg);//该段数据缓存的入口地址(为虚拟地址),相当于一个游标的意思 host->pio_sgptr++;//准备下一段数据缓存的入口 return 0; } |
//以上三段代码是对发送数据请求处理的,下面是发送命令请求 static void s3cmci_send_command(struct s3cmci_host *host, struct mmc_command *cmd) { u32 ccon, imsk; //出现CRC状态错误|命令响应超时|接收命令响应|命令发出|响应CRC校验失败时,将产生SDI中断 imsk = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT | S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT | S3C2410_SDIIMSK_RESPONSECRC; //将值写入SDI中断屏蔽寄存器中 enable_imask(host, imsk); //判断请求所处在何种状态 if (cmd->data) //如果有数据传输,则设当前任务为完成数据传输且接收命令响应状态 host->complete_what = COMPLETION_XFERFINISH_RSPFIN; else if (cmd->flags & MMC_RSP_PRESENT) host->complete_what = COMPLETION_RSPFIN; else //命令发送状态 host->complete_what = COMPLETION_CMDSENT; //设置命令参数寄存器 writel(cmd->arg, host->base + S3C2410_SDICMDARG); ccon = cmd->opcode & S3C2410_SDICMDCON_INDEX; ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART;//命令操作开始 if (cmd->flags & MMC_RSP_PRESENT) ccon |= S3C2410_SDICMDCON_WAITRSP;//主设备等待响应 if (cmd->flags & MMC_RSP_136) ccon |= S3C2410_SDICMDCON_LONGRSP;//主设备接收一个136位长的响应 //设置命令控制寄存器,开始命令的传输 writel(ccon, host->base + S3C2410_SDICMDCON); } |
7. s3cmci_irq_cd SDI的卡检测中断服务功能
//当MMC/SD卡插入卡槽时引发的中断 static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id) { //这个dev_id参数是申请中断时传递过来的 struct s3cmci_host *host = (struct s3cmci_host *)dev_id; //调用核心层中的方法将将struct delayed_work detect加入共享工作队列, //其处理函数为核心层中的mmc_rescan方法,用于卡的识别并初始化。 mmc_detect_change(host->mmc, msecs_to_jiffies(500)); return IRQ_HANDLED; } |
8. s3cmci_irq SDI的中断服务功能。我们从第6小节中对MMC/SD各种请求处理的代码中和(一)中“命令、数据发送流程图”中可以看出,在这个中断服务中将要处理很多请求相关的事情。但对于中断服务来说,这样会严重影响系统的性能,所以这正是为什么要在驱动中实现中断的底半部机制。下面看代码进行分析。
//MMC/SD卡中断服务程序 static irqreturn_t s3cmci_irq(int irq, void *dev_id) { //dev_id参数是申请中断的时候传递过来的s3cmci_host结构体,void类型的指针可以存放任何的数据类型 struct s3cmci_host *host = dev_id; struct mmc_command *cmd; u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk; u32 mci_cclear, mci_dclear; unsigned long iflags; //关中断并保持状态字 spin_lock_irqsave(&host->complete_lock, iflags); //分别读命令状态、数据状态、数据保留计数器、FIFO状态、中断屏蔽寄存器的值 mci_csta = readl(host->base + S3C2410_SDICMDSTAT); mci_dsta = readl(host->base + S3C2410_SDIDSTA); mci_dcnt = readl(host->base + S3C2410_SDIDCNT); mci_fsta = readl(host->base + S3C2410_SDIFSTA); mci_imsk = readl(host->base + host->sdiimsk); mci_cclear = 0; mci_dclear = 0; //如果当前没有请求状态或者请求已经完成了,则恢复中断什么都不做 if ((host->complete_what == COMPLETION_NONE) || (host->complete_what == COMPLETION_FINALIZE)) { host->status = "nothing to complete"; clear_imask(host); goto irq_out; } //如果核心层无MMC/SD请求,则恢复中断什么都不做 if (!host->mrq) { host->status = "no active mrq"; clear_imask(host); goto irq_out; } //获取当前发送命令有无完成 cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd; //如果发送命令完成了,则恢复中断什么都不做 if (!cmd) { host->status = "no active cmd"; clear_imask(host); goto irq_out; } //判断在数据传输状态时使用的传输方式 if (!host->dodma) { //不是DMA传输。如果是FIFO写,则切换到底半部去进行FIFO的写操作 if ((host->pio_active == XFER_WRITE) && (mci_fsta & S3C2410_SDIFSTA_TFDET)) { disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); tasklet_schedule(&host->pio_tasklet); host->status = "pio tx"; } //如果是FIFO读,则切换到底半部去进行FIFO的读操作 if ((host->pio_active == XFER_READ) && (mci_fsta & S3C2410_SDIFSTA_RFDET)) { disable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST); tasklet_schedule(&host->pio_tasklet); host->status = "pio rx"; } } //命令响应超时 if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) { dbg(host, dbg_err, "CMDSTAT: error CMDTIMEOUT/n"); cmd->error = -ETIMEDOUT; host->status = "error: command timeout"; goto fail_transfer; } //命令发送结束 if (mci_csta & S3C2410_SDICMDSTAT_CMDSENT) { if (host->complete_what == COMPLETION_CMDSENT) { host->status = "ok: command sent"; goto close_transfer; } mci_cclear |= S3C2410_SDICMDSTAT_CMDSENT; } //收到命令响应,CRC校验失败 if (mci_csta & S3C2410_SDICMDSTAT_CRCFAIL) { if (cmd->flags & MMC_RSP_CRC) { if (host->mrq->cmd->flags & MMC_RSP_136) { dbg(host, dbg_irq, "fixup: ignore CRC fail with long rsp/n"); } else { /* note, we used to fail the transfer * here, but it seems that this is just * the hardware getting it wrong. * * cmd->error = -EILSEQ; * host->status = "error: bad command crc"; * goto fail_transfer; */ } } mci_cclear |= S3C2410_SDICMDSTAT_CRCFAIL; } //收到命令响应,响应结束 if (mci_csta & S3C2410_SDICMDSTAT_RSPFIN) { //如果当前任务是完成,接收命令响应 if (host->complete_what == COMPLETION_RSPFIN) { host->status = "ok: command response received"; goto close_transfer;//停止传输 } //当前任务是完成数据传输和接收命令响应 if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN) //标记当前任务为完成数据传输 host->complete_what = COMPLETION_XFERFINISH; //清除收到命令响应标志 mci_cclear |= S3C2410_SDICMDSTAT_RSPFIN; } if (!cmd->data) goto clear_status_bits; //FIFO失败 if (mci_fsta & S3C2440_SDIFSTA_FIFOFAIL) { dbg(host, dbg_err, "FIFO failure/n"); host->mrq->data->error = -EILSEQ; host->status = "error: 2440 fifo failure"; goto fail_transfer; } //接收CRC错误 if (mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL) { dbg(host, dbg_err, "bad data crc (outgoing)/n"); cmd->data->error = -EILSEQ; host->status = "error: bad data crc (outgoing)"; goto fail_transfer; } //发送数据后,CRC状态错误 if (mci_dsta & S3C2410_SDIDSTA_CRCFAIL) { dbg(host, dbg_err, "bad data crc (incoming)/n"); cmd->data->error = -EILSEQ; host->status = "error: bad data crc (incoming)"; goto fail_transfer; } //数据/忙接收超时 if (mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT) { dbg(host, dbg_err, "data timeout/n"); cmd->data->error = -ETIMEDOUT; host->status = "error: data timeout"; goto fail_transfer; } //数据计数器为0,和本次请求的全部数据传输结束 if (mci_dsta & S3C2410_SDIDSTA_XFERFINISH) { //如果当前任务是完成数据传输则结束数据传输 if (host->complete_what == COMPLETION_XFERFINISH) { host->status = "ok: data transfer completed"; goto close_transfer; } //如果当前任务是完成数据传输和接收命令响应 if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN) //标记当前任务为完成 接收命令响应 host->complete_what = COMPLETION_RSPFIN; //清除数据传输完标志 mci_dclear |= S3C2410_SDIDSTA_XFERFINISH; } //清除状态字 clear_status_bits: writel(mci_cclear, host->base + S3C2410_SDICMDSTAT); writel(mci_dclear, host->base + S3C2410_SDIDSTA); goto irq_out; //传输失败 fail_transfer: host->pio_active = XFER_NONE; //传输结束 close_transfer: host->complete_what = COMPLETION_FINALIZE; clear_imask(host); tasklet_schedule(&host->pio_tasklet); goto irq_out; irq_out: dbg(host, dbg_irq, "csta:0x%08x dsta:0x%08x fsta:0x%08x dcnt:0x%08x status:%s./n", mci_csta, mci_dsta, mci_fsta, mci_dcnt, host->status); //开中断并恢复状态字 spin_unlock_irqrestore(&host->complete_lock, iflags); return IRQ_HANDLED; } //MMC/SD卡中断底半部程序 static void pio_tasklet(unsigned long data) { //data参数是在s3cmci_probe中的tasklet_init的时候传递过来的 struct s3cmci_host *host = (struct s3cmci_host *) data; //在执行底半部程序的时候屏蔽中断 disable_irq(host->irq); //判断如果当前存在FIFO的写状态,则进行FIFO的写操作 if (host->pio_active == XFER_WRITE) do_pio_write(host); //判断如果当前存在FIFO的读状态,则进行FIFO的读操作 if (host->pio_active == XFER_READ) do_pio_read(host); //判断如果当前的请求状态为完成状态,则准备进行完成请求处理 if (host->complete_what == COMPLETION_FINALIZE) { //清空中断屏蔽寄存器 clear_imask(host); //FIFO状态验证 if (host->pio_active != XFER_NONE) { if (host->mrq->data) host->mrq->data->error = -EINVAL; } //完成请求处理 finalize_request(host); } else //当前请求状态为其他,则使能中断继续请求处理 enable_irq(host->irq); } //完成请求处理 static void finalize_request(struct s3cmci_host *host) { struct mmc_request *mrq = host->mrq; struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd; int debug_as_failure = 0; //如果当前请求状态不为完成状态,则为错误 if (host->complete_what != COMPLETION_FINALIZE) return; if (!mrq) return; if (cmd->data && (cmd->error == 0) && (cmd->data->error == 0)) { if (host->dodma && (!host->dma_complete)) { dbg(host, dbg_dma, "DMA Missing!/n"); return; } } //读响应寄存器 cmd->resp[0] = readl(host->base + S3C2410_SDIRSP0); cmd->resp[1] = readl(host->base + S3C2410_SDIRSP1); cmd->resp[2] = readl(host->base + S3C2410_SDIRSP2); cmd->resp[3] = readl(host->base + S3C2410_SDIRSP3); writel(host->prescaler, host->base + S3C2410_SDIPRE); if (cmd->error) debug_as_failure = 1; if (cmd->data && cmd->data->error) debug_as_failure = 1; dbg_dumpcmd(host, cmd, debug_as_failure); //清空命令参数、数据配置、命令配置、中断屏蔽寄存器 writel(0, host->base + S3C2410_SDICMDARG); writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON); writel(0, host->base + S3C2410_SDICMDCON); writel(0, host->base + host->sdiimsk); if (cmd->data && cmd->error) cmd->data->error = cmd->error; //有数据请求,有传输停止命令,数据传输命令已发送 if (cmd->data && cmd->data->stop && (!host->cmd_is_stop)) { host->cmd_is_stop = 1; s3cmci_send_request(host->mmc);//传输停止命令 return; } if (!mrq->data) goto request_done; //计算已传输的数据量 if (mrq->data->error == 0) { mrq->data->bytes_xfered = (mrq->data->blocks * mrq->data->blksz); } else { mrq->data->bytes_xfered = 0; } if (mrq->data->error != 0) { if (host->dodma) s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); //清除和复位FIFO状态寄存器 writel(S3C2440_SDIFSTA_FIFORESET | S3C2440_SDIFSTA_FIFOFAIL, host->base + S3C2410_SDIFSTA); } //完成请求 request_done: host->complete_what = COMPLETION_NONE; host->mrq = NULL; mmc_request_done(host->mmc, mrq); } |