最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【转】mmci控制器驱动(2.6.22.5) — (1) 驱动注册

SD/MMC crifan 1831浏览 0评论

觉得这位对Linux的SD/MMC的驱动注册过程解释的比较清晰,所以转载于此共享。

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

http://hi.baidu.com/rwen2012/blog/item/539efda1c4ff8f89461064eb.html

原作者: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,%dn",
        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);

** 最后一步就是我们一开始所说的定时器设置了.

转载请注明:在路上 » 【转】mmci控制器驱动(2.6.22.5) — (1) 驱动注册

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
83 queries in 0.162 seconds, using 22.02MB memory