您的位置:首页 > 运维架构 > Linux

嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)

2012-10-08 21:43 661 查看
嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。
共享资源,欢迎转载:http://hbhuanggang.cublog.cn
一、开发环境

主  机: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(<
4000
span style="color:#0000ff;">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->pd
20000
ata->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);
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐