MMC/SD卡驱动实例开发讲解(二)

2019-07-12 15:33发布

ccommandioscmdcallback任务 嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。 一、开发环境
  • 主  机:VMWare--Fedora 9
  • 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4
  • 编译器:arm-linux-gcc-4.3.2
上接:S3C2440上MMC/SD卡驱动实例开发讲解(一) 6. s3cmci_ops SDI主机控制器操作接口函数功能分析: static struct mmc_host_ops s3cmci_ops = 
{
    .request = s3cmci_request,//实现host的请求处理(即:命令和数据的发送和接收)
    .set_ios = s3cmci_set_ios,//过核心层传递过来的ios配置host寄存器(使能时钟、总线带宽等)
    .get_ro  = s3cmci_get_ro,//通过读取GPIO端口来判断卡是否写有保护
    .get_cd  = s3cmci_card_present,//通过读取GPIO端口来判断卡是否存在
};
mmc_host_ops结构体定义了对host主机进行操作的各种方法,其定义在Core核心层的host.h中,也就是Core核心层对Host主机层提供的接口函数。这里各种方法的函数原型如下:
void  (*request)(struct mmc_host *host, struct mmc_request *req);
void  (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
int   (*get_ro)(struct mmc_host *host);
int   (*get_cd)(struct mmc_host *host);

从各函数原型上看,他们都将mmc_host结构体作为参数,所以我在刚开始的时候就说过mmc_host结构体是MMC/SD卡驱动中比较重要的数据结构。 可以这样说,他是Core层与Host层进行数据交换的载体。那么,这些接口函数何时会被调用呢?答案可以在Core层的core.c和sd.c中找到,我们可以看到如下部分代码: static void mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
    ......
    host->ops->request(host, mrq);//导致s3cmci_request被调用
}

static inline void mmc_set_ios(struct mmc_host *host)
{
    ......
    host->ops->set_ios(host, ios);//导致s3cmci_set_ios被调用
}

void mmc_rescan(struct work_struct *work)
{
    ......//导致s3cmci_card_present被调用
    if (host->ops->get_cd && host->ops->get_cd(host) == 0)
            goto out;
    ......
}

static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
    struct mmc_card *oldcard)
{
    ......
    /* Check if read-only switch is active.*/
    if (!oldcard) 
    {   //导致s3cmci_get_ro被调用
        if (!host->ops->get_ro || host->ops->get_ro(host) < 0) 
        {
            printk(KERN_WARNING "%s: host does not "
                "support reading read-only "
                "switch. assuming write-enable. ",
                mmc_hostname(host));
        } 
        else 
        {
            if (host->ops->get_ro(host) > 0)
                mmc_card_set_readonly(card);
        }
    }
    ......
}

好了,我们开始分析每个接口函数的具体实现吧,从简单的开始吧。 判断卡是否存在,如下代码: static int s3cmci_card_present(struct mmc_host *mmc)
{
    
//从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
    struct s3cmci_host *host = mmc_priv(mmc);
    struct s3c24xx_mci_pdata *pdata = host->pdata;
    int ret;

    
//判断有无设置卡检测引脚端口,引脚在s3cmci_probe函数中已设置
    if (pdata->gpio_detect == 0)
        return -ENOSYS;

    
//从设置的卡检测引脚中读出当前的电平值,来判断卡是插入存在的还是被拔出不存在的
    ret = s3c2410_gpio_getpin(pdata->gpio_detect) ? 0 : 1;
    return ret ^ pdata->detect_invert;
}
获取卡是否写有保护,其实实现跟卡检查类似,代码如下: static int s3cmci_get_ro(struct mmc_host *mmc)
{
    //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
    struct s3cmci_host *host = mmc_priv(mmc);
    struct s3c24xx_mci_pdata *pdata = host->pdata;
    int ret;

    //判断有无设置卡写保护引脚端口,引脚在s3cmci_probe函数中已设置
    if (pdata->gpio_wprotect == 0)
        return 0;

    //从设置的卡写保护引脚中读出当前的电平值,来判断卡是否写有保护
    ret = s3c2410_gpio_getpin(pdata->gpio_wprotect);

    if (pdata->wprotect_invert)
        ret = !ret;

    return ret;
}
配置host寄存器的时钟和总线宽度,代码如下: static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
    //从mmc_host的对象中获取出s3cmci_host结构体的数据,在s3cmci_probe函数中进行关联的
    struct s3cmci_host *host = mmc_priv(mmc);
    u32 mci_con;

    //读取SDI控制寄存器的值
    mci_con = readl(host->base + S3C2410_SDICON);

    //ios结构体参数从Core层传递过来,根据不同的电源状态来配置SDI各寄存器
    switch (ios->power_mode) 
    {
        case MMC_POWER_ON:
        case MMC_POWER_UP:
            //根据开发板引脚连接情况配置SDI控制器的各信号线,包括:时钟线、命令线和四条数据线
            s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK);
            s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD);
            s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0);
            s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1);
            s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);
            s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);
    
            if (host->pdata->set_power)
                host->pdata->set_power(ios->power_mode, ios->vdd);
    
            break;
    
        case MMC_POWER_OFF:
        default:
            //如果电源状态为关闭或者默认情况下,关闭SDI的时钟信号
            s3c2410_gpio_setpin(S3C2410_GPE5, 0);
            s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP);
    
            //根据数据手册的SDICON寄存器位的介绍,此处是将整个sdmmc时钟复位
            mci_con |= S3C2440_SDICON_SDRESET;
    
            if (host->pdata->set_power)
                host->pdata->set_power(ios->power_mode, ios->vdd);
    
            break;
    }

    //设置SDI波特率预定标器寄存器以确定时钟,看其定义部分
    s3cmci_set_clk(host, ios);

    //根据SDI当前的时钟频率来设置寄存器的使能时钟位
    if (ios->clock)
        mci_con |= S3C2410_SDICON_CLOCKTYPE;
    else
        mci_con &= ~S3C2410_SDICON_CLOCKTYPE;

    //将计算好的值写回SDI控制寄存器
    writel(mci_con, host->base + S3C2410_SDICON);

    //下面只是一些调试信息,可以不要
    if ((ios->power_mode == MMC_POWER_ON) || (ios->power_mode == MMC_POWER_UP)) 
    {
        dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz). ",
            host->real_rate/1000, ios->clock/1000);
    } 
    else