puppy居
puppy居士
posts - 41,comments - 27,trackbacks - 0
SD/MMC卡, 看了SANDISK的MMC规范文档, 对着看了MMCI host的驱动. 看多了发觉了一个有趣的内核编程"规则",当某个子系统有很多不同的模块, 但是模块之间又有很多相似的功能时,这个子系统就会抽象出一组与特定功能无关的核心代码,供各个模块共用. 

如内核(2.6.22.5)里的MMC驱动,在driver里分成三个部分, 分别是block, core和host, 其中这个core就是核心部分且与硬件无关的代码,   当要操作硬件是, core才会通过在host里注册的钩子函数去调用特定的host相关的操作函数.

block是块设备驱动代码, 在看过了ram disk或硬盘等块设备的驱动后这个也是比较容易理解的. 一个块设备所做的主要的工作就是, 初始化一个IO请求队列, 然后当有上层代码(如read/write)IO请求时, 这些请求就会被加入这个队列中. 块设备就是负责从这个队列中去读取请求, 然后处理这些请求. 就是说块设备就是通过这个队列与上层代码相连的.

MMC块设备驱动实现比较有趣, 它是通过一个内核线程去实现, 当队列为空时, 这个线程就进入睡眠状态, 当有请求加入队列时, 请求处理函数就会唤醒这个线程, 让它去处理这个请求.

我们知道, io请求是以request为单位的, 就是说传递到块设备驱动的每个请求为一个request, 这个request中包含了IO需要的所有信息. MMC块设备得到这个request后, 主要的工作就是将这个请求信息转换为MMC主机控制器可以识别的信息, 这就是mmc_command结构. 块设备驱动构造好这个结构后, 就会调用core里的函数, 将请求交由其去处理, 这就是说block驱动是看不到硬件的, 它只和core代码打交道, 之后core才会通过特定host 控制器注册的钩子函数, 将IO请求写入硬件, 发出真正的IO操作.

usb子系统工作过程也是差不多的, 如U盘,   当上层有一个IO请求request时, 她先通过scsi子系统生成一个scsi命令,因为U盘是模拟scsi盘工作的, 然后这个scsi命令会进一步被转换成urb命令, 再通过USB接口发个U盘的控制器.



mmci控制器驱动(2.6.22.5) -- (1) 驱动注册

R.wen

mmci为ARM的sd/mmc主控制器的驱动. 并且这个控制器是挂接在ARM的amba总线之下的, 所以驱动的注册会用到amba总线的一些函数.

1).驱动的注册.

static struct amba_driver mmci_driver = {
    .drv        = {
        .name    = DRIVER_NAME,
    },
    .probe        = mmci_probe,
    .remove        = mmci_remove,
    .suspend    = mmci_suspend,
    .resume        = mmci_resume,
    .id_table    = mmci_ids,
};

static int __init mmci_init(void)
{
    return amba_driver_register(&mmci_driver);
}

这个注册函数可以在前面的介绍中可以看到, 它定义了一个amba_driver结构,然后通过amba_driver_register(&mmci_driver)往amba总线上注

册这个驱动.
并且,如我们之前所说的, 这个注册函数最终会调用驱动中的probe函数完成驱动的初始化操作. 这就是mmc_probe():

static int mmci_probe(struct amba_device *dev, void *id)
{
    struct mmc_platform_data *plat = dev->dev.platform_data;
    struct mmci_host *host;
    struct mmc_host *mmc;
    int ret;

    /* must have platform data */
    if (!plat) {
        ret = -EINVAL;
        goto out;
    }

    //申请一个内存区
    ret = amba_request_regions(dev, DRIVER_NAME);
    if (ret)
        goto out;

    分配一个mmc_host结构, 还有一个特殊的mmci_host结构.
    mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
    if (!mmc) {
        ret = -ENOMEM;
        goto rel_regions;
    }

    host = mmc_priv(mmc); 两个结构关联起来


    //以下这段代码获取时钟的频率

    //得到一个clk结构
    host->clk = clk_get(&dev->dev, "MCLK");
    if (IS_ERR(host->clk)) {
        ret = PTR_ERR(host->clk);
        host->clk = NULL;
        goto host_free;
    }

    ret = clk_enable(host->clk);
    if (ret)
        goto clk_free;

    host->plat = plat;
    host->mclk = clk_get_rate(host->clk); //频率
    host->mmc = mmc;

    //得到设备寄存器的MMU后的基址
    host->base = ioremap(dev->res.start, SZ_4K);
    if (!host->base) {
        ret = -ENOMEM;
        goto clk_disable;
    }


    //这是请求操作函数结构,最终的对MMC卡的操作都是通过它来完成的.
    mmc->ops = &mmci_ops;
    mmc->f_min = (host->mclk + 511) / 512;
    mmc->f_max = min(host->mclk, fmax);
    mmc->ocr_avail = plat->ocr_mask;
    mmc->caps = MMC_CAP_MULTIWRITE;

    /*
    * We can do SGIO
    */
    mmc->max_hw_segs = 16;
    mmc->max_phys_segs = NR_SG;

    /*
    * Since we only have a 16-bit data length register, we must
    * ensure that we don't exceed 2^16-1 bytes in a single request.
    */
    mmc->max_req_size = 65535;

    /*
    * Set the maximum segment size. Since we aren't doing DMA
    * (yet) we are only limited by the data length register.
    */
    mmc->max_seg_size = mmc->max_req_size;

    /*
    * Block size can be up to 2048 bytes, but must be a power of two.
    */
    mmc->max_blk_size = 2048;

    /*
    * No limit on the number of blocks transferred.
    */
    mmc->max_blk_count = mmc->max_req_size;

    spin_lock_init(&host->lock);

    //先关中断
    writel(0, host->base + MMCIMASK0);
    writel(0, host->base + MMCIMASK1);
    writel(0xfff, host->base + MMCICLEAR);


    //申请两个中断
    ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
    if (ret)
        goto unmap;

    ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, DRIVER_NAME " (pio)", host);
    if (ret)
        goto irq0_free;

    //再开中断
    writel(MCI_IRQENABLE, host->base + MMCIMASK0);

    //关联结构
    amba_set_drvdata(dev, mmc);
    mmc_add_host(mmc);

    printk(KERN_INFO "%s: MMCI rev %x cfg %02x at 0x%016llx irq %d,%d\n",
        mmc_hostname(mmc), amba_rev(dev), amba_config(dev),
        (unsigned long long)dev->res.start, dev->irq[0], dev->irq[1]);

    //定时扫描控制器
    init_timer(&host->timer);
    host->timer.data = (unsigned long)host;
    host->timer.function = mmci_check_status;
    host->timer.expires = jiffies + HZ;
    add_timer(&host->timer);

    return 0;

irq0_free:
    free_irq(dev->irq[0], host);
unmap:
    iounmap(host->base);
clk_disable:
    clk_disable(host->clk);
clk_free:
    clk_put(host->clk);
host_free:
    mmc_free_host(mmc);
rel_regions:
    amba_release_regions(dev);
out:
    return ret;
}


先来看这个函数的最后一部分:


    init_timer(&host->timer);
    host->timer.data = (unsigned long)host;
    host->timer.function = mmci_check_status;
    host->timer.expires = jiffies + HZ;
    add_timer(&host->timer);

这是建立一个定时器, 时间为1秒, 超时后,它就会执行mmci_check_status函数:

static void mmci_check_status(unsigned long data)
{
    struct mmci_host *host = (struct mmci_host *)data;
    unsigned int status;

    status = host->plat->status(mmc_dev(host->mmc));
    if (status ^ host->oldstat)
        mmc_detect_change(host->mmc, 0);

    host->oldstat = status;
    mod_timer(&host->timer, jiffies + HZ);
}

我们可以看到, 这个函数通过mod_timer(), 修改这个定时器, 使之1秒之后再次执行, 这个就是说, MMC控制器是通过轮询的方式,每隔1秒调

用mmci_check_status去查询这个设备的状态有没有改变. 状态的查询是通过 host->plat->status(mmc_dev(host->mmc)去完成的:

static unsigned int realview_mmc_status(struct device *dev)
{
    struct amba_device *adev = container_of(dev, struct amba_device, dev);
    u32 mask;

    if (adev->res.start == REALVIEW_MMCI0_BASE)
        mask = 1;
    else
        mask = 2;

    return readl(REALVIEW_SYSMCI) & mask;
}

struct mmc_platform_data realview_mmc0_plat_data = {
    .ocr_mask    = MMC_VDD_32_33|MMC_VDD_33_34,
    .status        = realview_mmc_status,
};


它是通过读取相应的状态寄存器位,然后跟原来的状态比较(status ^ host->oldstat), 如果发生了改变, 则调用mmc_detect_change去处理这

个变化.

/**
*    mmc_detect_change - process change of state on a MMC socket
*    @host: host which changed state.
*    @delay: optional delay to wait before detection (jiffies)
*
*    All we know is that card(s) have been inserted or removed
*    from the socket(s). We don't know which socket or cards.
*/
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
    unsigned long flags;
    spin_lock_irqsave(&host->lock, flags);
    BUG_ON(host->removed);
    spin_unlock_irqrestore(&host->lock, flags);
#endif

    mmc_schedule_delayed_work(&host->detect, delay);
}


显然,他是通过工作队列来做的. 这个host->detect是一个delayed_work结构. 代表一个任务. 这个任务是在probe中通过mmc_alloc_host已经

初始化了.mmc_alloc_host我们后面在看, 这里我们只要知道它有这个这个操作INIT_DELAYED_WORK(&host->detect, mmc_rescan), 就是说每当

发现状态有所改变, MMCI最终会执行这个mmc_rescan().

static void mmc_rescan(struct work_struct *work)
{
    struct mmc_host *host =
        container_of(work, struct mmc_host, detect.work);
    u32 ocr;
    int err;

    mmc_bus_get(host);

    if (host->bus_ops == NULL) { //检测SD卡
        /*
        * Only we can add a new handler, so it's safe to
        * release the lock here.
        */
        mmc_bus_put(host);

        mmc_claim_host(host);

        mmc_power_up(host);
        mmc_go_idle(host);

        mmc_send_if_cond(host, host->ocr_avail);

        err = mmc_send_app_op_cond(host, 0, &ocr);
        if (err == MMC_ERR_NONE) {
            if (mmc_attach_sd(host, ocr))
                mmc_power_off(host);

        } else { //检测MMC卡

            /*
            * If we fail to detect any SD cards then try
            * searching for MMC cards.
            */
            err = mmc_send_op_cond(host, 0, &ocr);
            if (err == MMC_ERR_NONE) { //如果没有出错
                if (mmc_attach_mmc(host, ocr))
                    mmc_power_off(host);
            } else {
                mmc_power_off(host);
                mmc_release_host(host);
            }
        }
    } else {
        if (host->bus_ops->detect && !host->bus_dead)
            host->bus_ops->detect(host);

        mmc_bus_put(host);
    }
}


看MMC卡的情况, 如果没出错, 则会调用mmc_attach_mmc,这个函数将会为一张MMC卡分配一个mmc_card结构, 初始化她并将她加入队列 host-

>cards中去.

**回头看mmci_probe:
她先通过以下方式申请一个mmc_host和mmci_host结构,

struct mmci_host *host;
struct mmc_host *mmc;
mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);

这两个结构是一般和特殊的关系, mmc_host是通用的mmc host控制器的结构, 而特殊的控制器有好多种,这里是arm的MMCI, 还有at91的,

intel的等等. 可以想到, 都是MMC控制器, 每个特殊的控制器肯定有很多相同的属性, 所以这里就抽象出一个父结构,相当于面向对象里的父类

, 这里就是mmc_host了.


/**
*    mmc_alloc_host - initialise the per-host structure.
*    @extra: sizeof private data structure
*    @dev: pointer to host device model structure
*
*    Initialise the per-host structure.
*/
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
    struct mmc_host *host;

    host = mmc_alloc_host_sysfs(extra, dev);
    if (host) {
        spin_lock_init(&host->lock);
        init_waitqueue_head(&host->wq);
        INIT_DELAYED_WORK(&host->detect, mmc_rescan);

        /*
        * By default, hosts do not support SGIO or large requests.
        * They have to set these according to their abilities.
        */
        host->max_hw_segs = 1;
        host->max_phys_segs = 1;
        host->max_seg_size = PAGE_CACHE_SIZE;

        host->max_req_size = PAGE_CACHE_SIZE;
        host->max_blk_size = 512;
        host->max_blk_count = PAGE_CACHE_SIZE / 512;
    }

    return host;
}

我们看到,首先是分配一个空间的函数, 有个extra参数,就是用于为特殊设备分配额外空间的大小.
如果分配成功, 则初始化一个等待队列和一个工作队列, 而这个工作队列就是我们先前说提到的.
在接下来就是一个控制器的默认参数的设置了.

分配完空间后, 接着就是host = mmc_priv(mmc),

static inline void *mmc_priv(struct mmc_host *host)
{
    return (void *)host->private;
}

就是将他们关联起来, 这样通过通用的mmc_host,就可以找到特殊的mmci_host了.



**下一步就是时钟频率的设置, MMC卡的传输速率跟这个频率有直接的关系. 比如, 这个频率是20MHz, 而且MMC卡工作在4位模式, 则它的传输

速率就是20M * 4 = 80M/sec, 就是每秒10M多字节的速度.

    host->clk = clk_get(&dev->dev, "MCLK");
    if (IS_ERR(host->clk)) {
        ret = PTR_ERR(host->clk);
        host->clk = NULL;
        goto host_free;
    }

    ret = clk_enable(host->clk);
    if (ret)
        goto clk_free;

    host->mclk = clk_get_rate(host->clk);


先取出一个时钟结构, 这个是根据MCLK这个名字,然后取出这个相应的结构的. 这个是在系统初始化的时候设置好的.
如何通过clk_get_rate取出一个整型的速率值.


**接着就是

    struct mmc_platform_data {
        unsigned int ocr_mask;            /* available voltages */
        u32 (*translate_vdd)(struct device *, unsigned int);
        unsigned int (*status)(struct device *);
    };

    struct mmc_platform_data *plat = dev->dev.platform_data;
    host->plat = plat;

这个也是通过dev传过来的参数, 我们在上面已经看到了它的使用, 就是用来检测控制器状态的变化.


mmc控制器操作函数的设置,

    mmc->ops = &mmci_ops;

假设一个一个进程要读一个MMC卡上的一个文件, 它就要将这个请求(request)发给mmc块设备(mmc_block), mmc块设备最终调用这个结构中的

函数处理这个请求.

static const struct mmc_host_ops mmci_ops = {
    .request    = mmci_request,
    .set_ios    = mmci_set_ios,
};

mmci_request()函数就是直接操作控制器的的寄存器了.


** 还有一些SGIO的设置等等,和如下的一些值的初始化:

    mmc->f_min = (host->mclk + 511) / 512;
    mmc->f_max = min(host->mclk, fmax);
    mmc->ocr_avail = plat->ocr_mask;
    mmc->caps = MMC_CAP_MULTIWRITE;

**下一步就是中断的处理了, 做法很简单,就是--关中断, 设置中断处理例程, 开中断.
    writel(0, host->base + MMCIMASK0);
    writel(0, host->base + MMCIMASK1);
    writel(0xfff, host->base + MMCICLEAR);

    ## 两个IRQ
    ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
    if (ret)
        goto unmap;

    ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, DRIVER_NAME " (pio)", host);
    if (ret)
        goto irq0_free;

    writel(MCI_IRQENABLE, host->base + MMCIMASK0);


** 将MMC写会到dev的drvdata中, 关联起来
    amba_set_drvdata(dev, mmc);


** 最后一步就是我们一开始所说的定时器设置了.
posted on 2008-08-20 11:24 puppy 阅读(1615) 评论(1)  编辑 收藏 引用

FeedBack:
# re: sd/mmc主控制器的驱动
2009-12-01 16:06 | lanmanck
能讲一下mmc驱动架构就好了。  回复  更多评论
  
只有注册用户登录后才能发表评论。