嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。

一、开发环境

主  机:VMWare--Fedora 9

开发板:Mini2440--64MB Nand, Kernel:2.6.30.4

编译器:arm-linux-gcc-4.3.2

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);

}

嵌入式linux sd卡读写,嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)...相关推荐

  1. S3C2440上MMC/SD卡驱动实例开发讲解(二)

    嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤.一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便.如有错误之处,谢请指正. 共享资源,欢迎转载:http:/ ...

  2. S3C2440上MMC/SD卡驱动分析(二)

    下面的文章主要是转载的,先记录下自己的经验. MMC/SD驱动有两种模式:FIFO和DMA.在代码中两种方式都予以了实现,在make menuconfig时候,可以选择是使用fifo方式还是DMA方式 ...

  3. linux sd卡读写出错,linux系统SD卡读写问题

    请教有过linux系统SD卡读写经验的前辈. 我的项目是对FPGA上的SD卡部分做测试,在测试过程中发现在对SD卡所有领域进行读写操作时 1.bus width 选择1-bit的速度4-bit是差不多 ...

  4. 【正点原子FPGA连载】第四十六章SD卡读写测试实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  5. linux内核如何读写ddr,linux内核解压详解.doc

    linux内核解压详解 Description: /*?OldLinux论坛 -- 有关早期Linux内核代码发展的论坛. 9!%83gY* ?linux/arch/arm/boot/compress ...

  6. linux sftp没有读写权限,Linux下SFTP用户权限设置条件及实现命令

    Linux下SFTP用户权限设置条件及实现命令 众所周知SFTP账号是基于SSH账号的,所以在默认情况下访问服务器的权限是非常大的,今天的教程就是教大家进行SFTP用户权限设置. 必要条件: 你的op ...

  7. linux中i2c读写函数,Linux下的eeprom读写操作(IIC)

    /* *************************************************************************** * File name: eeprom_i ...

  8. m1卡读写c语言,Android NFC(一) M1卡读写

    原料:Android 带NFC功能手机.M1卡 怕你们没耐心先上demo 1.在AndroidManifest中添加权限控制 activity中需要添加 android:resource=" ...

  9. linux内核培训广州,嵌入式Linux驱动开发高级培训班-华清远见嵌入式培训中心

    课程目标 本课程以案例教学为主,系统地介绍Linux下有关FrameBuffer.MMC卡.USB设备的驱动程序开发.参加本课程学习的学员,因为具备了Linux设备驱动开发基础,所以本课程针对性较强, ...

最新文章

  1. cad里面f命令用不了,CAD出现命令无效、失灵等问题?不用慌,两招帮你快速解决...
  2. PNAS “深度学习的科学”论文合集导读
  3. C#基础全接触分类:asp.net技术
  4. ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full comm
  5. python request-python3的request用法实例
  6. JVM启动的时候,默认加入的属性
  7. C++ Public, Protected, Private
  8. hibernate继承关系映射方法(三)--每个具体类一张表TPC
  9. 乡村要振兴,快递先进村?
  10. 音视频技术开发周刊(第123期)
  11. Exp3 免杀原理与实践
  12. mysql 关闭in自动排序_为什么MySQL的in查询会自动排序
  13. java 栈的用法_让Java程序员再次生机勃勃,还是技术的力量
  14. ebc是什么意思_什么是亚马逊EBC,EBC有什么用?
  15. KMP算法 C#实现 字符串查找简单实现
  16. php 按行入读文件 返回数组
  17. 杭州/北京内推 | 蚂蚁集团数字身份及安全生态团队招聘学术实习生
  18. springboot个人博客项目面试准备
  19. What is pessimistic locking in Hibernate
  20. 行走的励志君——选择和努力哪个更重要

热门文章

  1. 一文读懂数据分析必备思维:框架思维
  2. 第二天一早的飞鸽传书
  3. 程序员们记得还是八五年PC登陆我国时候的事?
  4. H618B刷了tomato0530版本 呵呵,测试一下
  5. PXE 网络化安装linux系统
  6. 哪些技能面试经常被问,但实际开发很少用上?
  7. 竖流式沉淀池集水槽设计计算_竖流式沉淀池的设计
  8. 数学rect什么意思_想要孩子数学成绩好,这些坏习惯必须改掉:一位伯克利学霸的忠告...
  9. leetcode:剑指offer----二维数组中查找
  10. Linux 迎来 29 岁:从个人爱好到统治世界的操作系统内核