linux sd卡驱动结构_linux sd卡驱动 - CSDN
精华内容
参与话题
  • Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇

    万次阅读 多人点赞 2016-04-02 19:51:44
    回顾一下前面的知识,MMC 子系统范围三个部分: HOST 部分是针对不同主机的驱动程序,...CARD 部分:因为这些记忆都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD 如何实现为块设备的。 它们

          回顾一下前面的知识,MMC 子系统范围三个部分:

    HOST 部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。

    CORE 部分: 这是整个MMC 的核心存,这部分完成了不同协议和规范的实现,并为HOST 层的驱动提供了接口函数。

    CARD 部分:因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD 卡如何实现为块设备的。

    它们分布于下面的文件夹中 Linux/drivers/mmc中


         其中,card(区块层) 与core(核心层)是linux系统封装好了部分,我们不需要修改,host(主控制器层)中提供与各芯片构架相关的文件,这才是我们所要开发的部分

         核心层根据需要构造各种MMC/SD命令,这些命令怎么发送给MMC/SD卡呢?这通过主机控制器层来实现。这层是架构相关的,里面针对各款CPU提供一个文件,目前支持的CPU还很少。

          以本节即将移植的s3cmci.c为例,它首先进行一些低层设置,比如设置MMC/SD/SDIO控制器使用到的CPIO引脚使能控制器注册中断处理函数等,然后向上面的核心层增加一个主机(Host),这样核心层就能调用s3cmci.c提供的函数来识别、使用具体存储卡了。

          在向核心层增加主机之前,s3cmci.c 设置了一个mmc_host_ops结构体,它实现两个函数:

    a -- 发起访问请求的request函数

    b -- 进行一些属性设置(时钟频率、数据线位宽等)的set_ios函数

          下面列出识别存储卡、区块层发起操作请求两种情况下函数的主要调用关系:

    1)识别存储卡


    2)区块层发起操作请求


          以后上次对存储卡的操作都通过调用这两个函数来完成。下面对HOST层进行分析(Linux内核版本:Linux-3.14)。


    一、struct mmc_host 结构体

            主要用来描述卡控制器位 结构体mmc_host定义于/include/linux/mmc/host.c,可以认为是linux为SD卡控制器专门准备的一个类,该类里面的成员是所有SD卡控制器都需要的,放之四海而皆准的数据结构,在本例芯片控制器的驱动程序s3cmci.c中,则为该类具体化了一个对象struct mmc_host *mmc,此mmc指针即指代着该ARM芯片SD卡控制器的一个具体化对象(可以看出虽然C是面向过程的语言,但还是用到了一些面向对象思想的)。

    linux/driver/mmc/host/s3cmci.h下定义

    struct s3cmci_host {
    	struct platform_device	*pdev;
    	struct s3c24xx_mci_pdata *pdata;
    	struct mmc_host		*mmc;
    	struct resource		*mem;
    	struct clk		*clk;
    	void __iomem		*base;
    	int			irq;
    	int			irq_cd;
    	int			dma;
    
    	unsigned long		clk_rate;
    	unsigned long		clk_div;
    	unsigned long		real_rate;
    	u8			prescaler;
    
    	int			is2440;
    	unsigned		sdiimsk;
    	unsigned		sdidata;
    	int			dodma;
    	int			dmatogo;
    
    	bool			irq_disabled;
    	bool			irq_enabled;
    	bool			irq_state;
    	int			sdio_irqen;
    
    	struct mmc_request	*mrq;
    	int			cmd_is_stop;
    
    	spinlock_t		complete_lock;
    	enum s3cmci_waitfor	complete_what;
    
    	int			dma_complete;
    
    	u32			pio_sgptr;
    	u32			pio_bytes;
    	u32			pio_count;
    	u32			*pio_ptr;
    #define XFER_NONE 0
    #define XFER_READ 1
    #define XFER_WRITE 2
    	u32			pio_active;
    
    	int			bus_width;
    
    	char 			dbgmsg_cmd[301];
    	char 			dbgmsg_dat[301];
    	char			*status;
    
    	unsigned int		ccnt, dcnt;
    	struct tasklet_struct	pio_tasklet;
    
    #ifdef CONFIG_DEBUG_FS
    	struct dentry		*debug_root;
    	struct dentry		*debug_state;
    	struct dentry		*debug_regs;
    #endif
    
    #ifdef CONFIG_CPU_FREQ
    	struct notifier_block	freq_transition;
    #endif
    };

       其中struct mmc_host(linux/include/linux/mmc/host.h)用于与core层的命令请求,数据 传输等信息,这里代码较长,展示部分

    struct mmc_host 
    {
    	const struct mmc_host_ops *ops;     // SD卡主控制器的操作函数,即该控制器所具备的驱动能力
    	const struct mmc_bus_ops *bus_ops; // SD总线驱动的操作函数,即SD总线所具备的驱动能力
    	struct mmc_ios  ios;  // 配置时钟、总线、电源、片选、时序等
    	struct mmc_card  *card;  // 连接到此主控制器的SD卡设备
    	... ...
    };
    

          本文中struct mmc_host_ops *ops定义

    static struct mmc_host_ops s3cmci_ops = {
    	.request	= s3cmci_request,
    	.set_ios	= s3cmci_set_ios,
    	.get_ro		= s3cmci_get_ro,
    	.get_cd		= s3cmci_card_present,
    	.enable_sdio_irq = s3cmci_enable_sdio_irq,
    };


    二、SD控制器之初始化(linux/driver/mmc/host)

            这一层讲述硬件与硬件之间将要发生的故事,也是最底层驱动的核心。通常所谓的驱动程序设计的任务将落实到这一层上,所以关注host故事的发展也将成为移植整个SD类设备驱动的核心。在host 目录中有各种平台下SD 卡主机驱动器的实例,这里我们选择s3c2440平台作为分析的重点。参看Kconfig和Makefile即可获得相应信息,这里对应的文件即是s3cmci.c。

    1、设备的注册

          旧瓶装新酒,还是那个module_init,不一样的是其中的入口函数。在s3cmci.c中对应的是module_init(s3cmci_init);

    module_platform_driver(s3cmci_driver);

     这里是不是很奇怪,不应该是module_init 与 module_exit 吗?module_platform_driver其实是一个宏定义,定义在include/linux/platform_device.h文件中:

    /* module_platform_driver() - Helper macro for drivers that don't do
     * anything special in module init/exit.  This eliminates a lot of
     * boilerplate.  Each module may only use this macro once, and
     * calling it replaces module_init() and module_exit()
     */
    #define module_platform_driver(__platform_driver) \
    	module_driver(__platform_driver, platform_driver_register, \
    			platform_driver_unregister)

    宏module_driver定义在include/linux/device.h文件中,其内容如下:

    /**
     * module_driver() - Helper macro for drivers that don't do anything
     * special in module init/exit. This eliminates a lot of boilerplate.
     * Each module may only use this macro once, and calling it replaces
     * module_init() and module_exit().
     *
     * @__driver: driver name
     * @__register: register function for this driver type
     * @__unregister: unregister function for this driver type
     * @...: Additional arguments to be passed to __register and __unregister.
     *
     * Use this macro to construct bus specific macros for registering
     * drivers, and do not use it on its own.
     */
    #define module_driver(__driver, __register, __unregister, ...) \
    static int __init __driver##_init(void) \
    { \
    	return __register(&(__driver) , ##__VA_ARGS__); \
    } \
    module_init(__driver##_init); \
    static void __exit __driver##_exit(void) \
    { \
    	__unregister(&(__driver) , ##__VA_ARGS__); \
    } \
    module_exit(__driver##_exit);
        可以看出,最终还是调用module_init 与 module_exit。这里注册了一个平台驱动,这个前面分析的串口驱动等是一样的原理。这是新版内核中引入的一个虚拟的平台总线。对应的平台设备早在内核启动时通过platform_add_devices加入到了内核,相关的具体内容前面已经分析的挺多了,这里就不在详细说明。该句调用的结果会导致s3cmci_driver中的probe方法得以调用,由此也就把我们引入了host的世界。


    2、probe函数

         在驱动的接口函数中s3cmci_probe() 函数,用于分配mmc_host ,s3cmci_host结构体,并对结构体进行设置在SDI主机控制器操作接口函数mmc_host_ops中会调用s3cmci_host结构体,申请中断并设置中断服务函数,将结构体mmc_host添加到主机。

       SD主控制器驱动程序的初始化函数probe(struct platform_device *pdev),概括地讲,主要完成五大任务,

    • 初始化设备的数据结构,并将数据挂载到pdev->dev.driver_data下
    • 实现设备驱动的功能函数,如mmc->ops = &s3cmci_ops;
    • 申请中断函数request_irq()
    • 注册设备,即注册kobject,建立sys文件,发送uevent等
    • 其他需求,如在/proc/driver下建立用户交互文件等

    首先还是来大致看一下s3cmci_driver中所对应的具体内容

    static struct platform_driver s3cmci_driver = {
    	.driver	= {
    		.name	= "s3c-sdi",
    		.owner	= THIS_MODULE,
    	},
    	.id_table	= s3cmci_driver_ids,
    	.probe		= s3cmci_probe,
    	.remove		= s3cmci_remove,
    	.shutdown	= s3cmci_shutdown,
    };
         其中probe是我们关注的核心,由于host与硬件是直接相关的,probe接下来将做部分关于硬件初始化的工作,因此在分析下面一部分代码之前,最好能对s3c2440的sdi相关的内容有所了解。下面进入到probe的相关内容,整个函数洋洋洒洒三百多行,就分段说明吧。

    先看这几行

    static int s3cmci_probe(struct platform_device *pdev)
    {
    	struct s3cmci_host *host;
    	struct mmc_host	*mmc;
    	int ret;
    	int is2440;
    	int i;
    
    	is2440 = platform_get_device_id(pdev)->driver_data;
    
    	mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
    	if (!mmc) {
    		ret = -ENOMEM;
    		goto probe_out;
    	}

           这里,mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); 分配一个mmc的控制器同时struct s3cmci_host 结构作为一个私有数据类型将添加到struct mmc_host的private域

    a --  mmc_alloc_host

            mmc_alloc_host相应的代码如下:linux/drivers/mmc/core/host.c

    struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
    {
    	int err;
    	struct mmc_host *host;
    
    	host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
    	if (!host)
    		return NULL;
    
    	/* scanning will be enabled when we're ready */
    	host->rescan_disable = 1;
    	idr_preload(GFP_KERNEL);
    	spin_lock(&mmc_host_lock);
    	err = idr_alloc(&mmc_host_idr, host, 0, 0, GFP_NOWAIT);
    	if (err >= 0)
    		host->index = err;
    	spin_unlock(&mmc_host_lock);
    	idr_preload_end();
    	if (err < 0)
    		goto free;
    
    	dev_set_name(&host->class_dev, "mmc%d", host->index);
    
    	host->parent = dev;
    	host->class_dev.parent = dev;
    	host->class_dev.class = &mmc_host_class;
    	device_initialize(&host->class_dev);
    
    	mmc_host_clk_init(host);
    
    	mutex_init(&host->slot.lock);
    	host->slot.cd_irq = -EINVAL;
    
    	spin_lock_init(&host->lock);
    	init_waitqueue_head(&host->wq);
    	INIT_DELAYED_WORK(&host->detect, mmc_rescan);
    #ifdef CONFIG_PM
    	host->pm_notify.notifier_call = mmc_pm_notify;
    #endif
    
    	/*
    	 * By default, hosts do not support SGIO or large requests.
    	 * They have to set these according to their abilities.
    	 */
    	host->max_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;
    
    free:
    	kfree(host);
    	return NULL;
    }
    14行是内核的高效搜索树,将host->index与host结构相关联,方便以后查找。
    24-27行主要是初始化host->class_dev,这个日后会通过device_register注册进系统。
    35行前面已经在这个等待队列上花了不少笔墨,主要是同步对host资源的竞争的。
    45-50行这些个都是设置host的一些属性的,是与block.c中请求队列的设置相对应的。

    重新回到s3cmci_probe....
    for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) {
    		ret = gpio_request(i, dev_name(&pdev->dev));
    		if (ret) {
    			dev_err(&pdev->dev, "failed to get gpio %d\n", i);
    
    			for (i--; i >= S3C2410_GPE(5); i--)
    				gpio_free(i);
    
    			goto probe_free_host;
    		}
    	}

    上面这一段申请SD卡驱动器所需的GPIO资源

    继续分析...

    	host = mmc_priv(mmc);
    	host->mmc 	= mmc;
    	host->pdev	= pdev;
    	host->is2440	= is2440;

        上面这段代码就是对struct s3cmci_host *host这个私有结构 的配置对于core或block层见到的只有struct mmc_host从另外的一个角度可以理解struct mmc_host实际上是struct s3cmci_host的基类,有着所有控制器所必须具有的属性。struct s3cmci_host还包含了与host硬件平台相关的特征
    	host->pdata = pdev->dev.platform_data;
    	if (!host->pdata) {
    		pdev->dev.platform_data = &s3cmci_def_pdata;
    		host->pdata = &s3cmci_def_pdata;
    	}

    这是平台设备注册时platform_device所具有的属性.

            spin_lock_init(&host->complete_lock);
    	tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);

          tasklet就象一个内核定时器, 在一个"软中断"的上下文中执行(以原子模式),常用在硬件中断处理中,使得可以使得复杂的任务安全地延后到以后的时间处理。task_init 建立一个tasklet,然后调用函数 tasklet_schedule将这个tasklet放在 tasklet_vec链表的头部,并唤醒后台线程 ksoftirqd。当后台线程ksoftirqd 运行调用__do_softirq 时,会执行在中断向量表softirq_vec里中断号TASKLET_SOFTIRQ对应的 tasklet_action函数,然后 tasklet_action遍历 tasklet_vec链表,调用每个 tasklet的函数完成软中断操作,上面例子中即是pio_tasklet函数,另外软中断处理函数只能传递一个long型变量。这里是直接使用host的地址,作为传递参数。关于这个pio_tasklet现在说他还为时过早,等时辰一到自然会对他大书特书。

    	if (is2440) {
    		host->sdiimsk	= S3C2440_SDIIMSK;
    		host->sdidata	= S3C2440_SDIDATA;
    		host->clk_div	= 1;
    	} else {
    		host->sdiimsk	= S3C2410_SDIIMSK;
    		host->sdidata	= S3C2410_SDIDATA;
    		host->clk_div	= 2;
    	}

    host->sdiimsk 、host->sdidata分别用来存放host控制器SDI中断屏蔽寄存器SDI数据寄存器相对SDI 寄存器的偏移地址。对于s3c2440 根据芯片手册SDIIntMsk 偏移地址为0x3C,SDIDAT偏移地址为0x40。最后host->clk_div就是指的SDI使用的时钟分频系数了。

            host->complete_what 	= COMPLETION_NONE;
    	host->pio_active 	= XFER_NONE;

    host->complete_what 是一个枚举类型变量,实际上用以标示传输完成的状态;host->pio_active标示数据传输的方向,所以在这里一起初始化为空。关于传输完成的标示为了后面分析方便还是一起列举出来:

    enum s3cmci_waitfor {
    	COMPLETION_NONE,
    	COMPLETION_FINALIZE,
    	COMPLETION_CMDSENT,
    	COMPLETION_RSPFIN,
    	COMPLETION_XFERFINISH,
    	COMPLETION_XFERFINISH_RSPFIN,
    };

    继续分析

    #ifdef CONFIG_MMC_S3C_PIODMA
    	host->dodma		= host->pdata->use_dma;
    #endif

    上面是同时使能了PIO和DMA模式的情况,这里我们对两种传输方式都做相应的分析,所以host->dodma默认为1。
    	host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	if (!host->mem) {
    		dev_err(&pdev->dev,
    			"failed to get io memory region resource.\n");
    
    		ret = -ENOENT;
    		goto probe_free_gpio;
    	}
    
    	host->mem = request_mem_region(host->mem->start,
    				       resource_size(host->mem), pdev->name);
    
    	if (!host->mem) {
    		dev_err(&pdev->dev, "failed to request io memory region.\n");
    		ret = -ENOENT;
    		goto probe_free_gpio;
    	}
    
    	host->base = ioremap(host->mem->start, resource_size(host->mem));
    	if (!host->base) {
    		dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");
    		ret = -EINVAL;
    		goto probe_free_mem_region;
    	}
          上面的一段代码相对比较简单,都是平台驱动设计过程中常用的几个处理函数,就不一一展开了。首先是获取IO 资源, 这当然即使mach-mini2440.c 中所注册的IORESOURCE_MEM,

          这段代码会申请这个资源并检查是否可用,当然只要之前没有使用过SDI寄存器空间,这里都会申请成功。最后就是IO映射,将实地址映射为内核虚拟地址使用。最后,host->base将保持SDI寄存器基地址所对应的内核虚拟地址。

            host->irq = platform_get_irq(pdev, 0);
    	if (host->irq == 0) {
    		dev_err(&pdev->dev, "failed to get interrupt resource.\n");
    		ret = -EINVAL;
    		goto probe_iounmap;
    	}
    
    	if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) {
    		dev_err(&pdev->dev, "failed to request mci interrupt.\n");
    		ret = -ENOENT;
    		goto probe_iounmap;
    	}

    上面一段是对中断资源的申请,并通过request_irq安装了中断处理函数,使能了SDI中断。在上段最为关心的是s3cmci_irq 中断处理函数及其传入的dev_id。关于这个处理函数的分析后面讲述数据传输的时候会进行细致分析。接着向下....

    /* We get spurious interrupts even when we have set the IMSK
    	 * register to ignore everything, so use disable_irq() to make
    	 * ensure we don't lock the system with un-serviceable requests. */
    
    	disable_irq(host->irq);
    	host->irq_state = false;
    前面我们强调了request_irq调用的结果会使能host->irq,但此时系统初始化尚未完成这时候出现的中断可能将处理器带入一个异常状态,所以5行屏蔽中断1650行将中断状态置位无效都是有必要的。
    if (!host->pdata->no_detect) {
    		ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
    		if (ret) {
    			dev_err(&pdev->dev, "failed to get detect gpio\n");
    			goto probe_free_irq;
    		}

    如果SD卡存在的检查一般是通过读取专用引脚状态来实现的,这里如果需要做detect相关的工作的话就必须重新分配一个管脚,在当前系统中前面定义了以GPG8作为检测引脚

    host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);

    s3c2410_gpio_getirq是获取这个GPIO的外部中断向量号,可见后面的SD卡的检测可能会用到这个引脚的外部中断。

    if (host->irq_cd >= 0) {
    			if (request_irq(host->irq_cd, s3cmci_irq_cd,
    					IRQF_TRIGGER_RISING |
    					IRQF_TRIGGER_FALLING,
    					DRIVER_NAME, host)) {
    				dev_err(&pdev->dev,
    					"can't get card detect irq.\n");
    				ret = -ENOENT;
    				goto probe_free_gpio_cd;
    			}
    		} else {
    			dev_warn(&pdev->dev,
    				 "host detect has no irq available\n");
    			gpio_direction_input(host->pdata->gpio_detect);
    		}
    	} else
    		host->irq_cd = -1;

    上面的这段代码意图很明显就是要申请这个中断了,s3cmci_irq_cd是其处理函数。对像SD卡这种可移出设备来作为块设备存储介质的话,大多会涉及到媒体切换,具体这方面的内容后面用到的时候也会有个详细分析。

    if (!host->pdata->no_wprotect) {
    		ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp");
    		if (ret) {
    			dev_err(&pdev->dev, "failed to get writeprotect\n");
    			goto probe_free_irq_cd;
    		}
    
    		gpio_direction_input(host->pdata->gpio_wprotect);
    	}
    
    	/* depending on the dma state, get a dma channel to use. */
    
    	if (s3cmci_host_usedma(host)) {
    		host->dma = s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client,
    						host);
    		if (host->dma < 0) {
    			dev_err(&pdev->dev, "cannot get DMA channel.\n");
    			if (!s3cmci_host_canpio()) {
    				ret = -EBUSY;
    				goto probe_free_gpio_wp;
    			} else {
    				dev_warn(&pdev->dev, "falling back to PIO.\n");
    				host->dodma = 0;
    			}
    		}
    	}

    1行这个我们之前就默认为TURE了,所以接下来的几行代码是免不了。

    14 行s3c2410_dma_request 申请DMA 通道,对s3c2440 平台有4 通道的DMA。而DMACH_SDI 是一个虚拟的通道号,由于这部分代码是和硬件紧密相关的,而且整个DMA的管理相对来讲比较复杂,所以这里只是粗略了解一下。

    host->clk = clk_get(&pdev->dev, "sdi");
    	if (IS_ERR(host->clk)) {
    		dev_err(&pdev->dev, "failed to find clock source.\n");
    		ret = PTR_ERR(host->clk);
    		host->clk = NULL;
    		goto probe_free_dma;
    	}
    
    	ret = clk_enable(host->clk);
    	if (ret) {
    		dev_err(&pdev->dev, "failed to enable clock source.\n");
    		goto clk_free;
    	}
    
    	host->clk_rate = clk_get_rate(host->clk);

    以上是关于sdi时钟和波特率的有关设置,都是内核提供的一些简单的函数调用,相关的内容不是现在研究的重点就不再详细分析了。

    	mmc->ops 	= &s3cmci_ops;
    	mmc->ocr_avail	= MMC_VDD_32_33 | MMC_VDD_33_34;
    #ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
    	mmc->caps	= MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
    #else
    	mmc->caps	= MMC_CAP_4_BIT_DATA;
    #endif
    	mmc->f_min 	= host->clk_rate / (host->clk_div * 256);
    	mmc->f_max 	= host->clk_rate / host->clk_div;
    
    	if (host->pdata->ocr_avail)
    		mmc->ocr_avail = host->pdata->ocr_avail;
    
    	mmc->max_blk_count	= 4095;
    	mmc->max_blk_size	= 4095;
    	mmc->max_req_size	= 4095 * 512;
    	mmc->max_seg_size	= mmc->max_req_size;
    
    	mmc->max_segs		= 128;
    
    	dbg(host, dbg_debug,
    	    "probe: mode:%s mapped mci_base:%p irq:%u irq_cd:%u dma:%u.\n",
    	    (host->is2440?"2440":""),
    	    host->base, host->irq, host->irq_cd, host->dma);
    

    上面就是对这个将要出嫁的mmc_host进行最后的设置mmc->ops = &s3cmci_ops; 就是一直以来向core层提供的接口函数集。后面的分析可能部分是围绕它其中的函数展开的。

            ret = s3cmci_cpufreq_register(host);
    	if (ret) {
    		dev_err(&pdev->dev, "failed to register cpufreq\n");
    		goto free_dmabuf;
    	}

    这里使用的是Linux的通告机制,s3cmci_cpufreq_register相对应的代码如下:

    static inline int s3cmci_cpufreq_register(struct s3cmci_host *host) {

    host->freq_transition.notifier_call = s3cmci_cpufreq_transition;

    return cpufreq_register_notifier(&host->freq_transition,

    CPUFREQ_TRANSITION_NOTIFIER);
    }

    cpufreq_register_notifier cpu是将host->freq_transition注册到CPU频率通告链上,这个是由内核维护的,当cpu频率改变时将会调用上面注册的s3cmci_cpufreq_transition的内容。

    ret = mmc_add_host(mmc);
    	if (ret) {
    		dev_err(&pdev->dev, "failed to add mmc host.\n");
    		goto free_cpufreq;
    	}

    b -- mmc_add_host

         这是core层的函数,他保存了所有平台通用的代码。同时看上去简简单单的一个mmc_add_host,可能蕴藏着天大的玄机。为此我们将为mmc_add_host另分一章,作为core层的续集,专门讲述mmc_add_host过程中发生的点点滴滴....在开始分析mmc_add_host之前,让我们还是结束SD主机控制器的probe函数,接下来

            s3cmci_debugfs_attach(host);
    
    	platform_set_drvdata(pdev, mmc);
    	dev_info(&pdev->dev, "%s - using %s, %s SDIO IRQ\n", mmc_hostname(mmc),
    		 s3cmci_host_usedma(host) ? "dma" : "pio",
    		 mmc->caps & MMC_CAP_SDIO_IRQ ? "hw" : "sw");
    
    	return 0;
    
     free_cpufreq:
    	s3cmci_cpufreq_deregister(host);
    
     free_dmabuf:
    	clk_disable(host->clk);
    
     clk_free:
    	clk_put(host->clk);
    
     probe_free_dma:
    	if (s3cmci_host_usedma(host))
    		s3c2410_dma_free(host->dma, &s3cmci_dma_client);
    
     probe_free_gpio_wp:
    	if (!host->pdata->no_wprotect)
    		gpio_free(host->pdata->gpio_wprotect);
    
     probe_free_gpio_cd:
    	if (!host->pdata->no_detect)
    		gpio_free(host->pdata->gpio_detect);
    
     probe_free_irq_cd:
    	if (host->irq_cd >= 0)
    		free_irq(host->irq_cd, host);
    
     probe_free_irq:
    	free_irq(host->irq, host);
    
     probe_iounmap:
    	iounmap(host->base);
    
     probe_free_mem_region:
    	release_mem_region(host->mem->start, resource_size(host->mem));
    
     probe_free_gpio:
    	for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++)
    		gpio_free(i);
    
     probe_free_host:
    	mmc_free_host(mmc);
    
     probe_out:
    	return ret;
    }
    
    static void s3cmci_shutdown(struct platform_device *pdev)
    {
    	struct mmc_host	*mmc = platform_get_drvdata(pdev);
    	struct s3cmci_host *host = mmc_priv(mmc);
    
    	if (host->irq_cd >= 0)
    		free_irq(host->irq_cd, host);
    
    	s3cmci_debugfs_remove(host);
    	s3cmci_cpufreq_deregister(host);
    	mmc_remove_host(mmc);
    	clk_disable(host->clk);
    }

    上面的基本都是出错的处理了,那么也就没有必要纠结于此了,赶快开启新的生活吧....

    再多说一句,可以看到probe函数里面,大量的工作是对 host 结构体 mmc结构体的填充,类似于USB probe函数中对 usb_skel 结构体的填充,向上提供接口函数


    三、 设备驱动的功能函数

       一般情况下,设备驱动里都有一个行为函数结构体,比如字符设备驱动里的struct file_operations *fops,该结构描述了设备所具备的工作能力,比如open, read, write等,

    struct file_operations {
     struct module *owner;
     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
     int (*open) (struct inode *, struct file *);
     ... ...
    };

      同理,SD主控制器驱动程序里也有一个类似的结构struct mmc_host_ops *ops,它描述了该控制器所具备驱动的能力。

    static struct mmc_host_ops s3cmci_ops = {
    	.request	= s3cmci_request,
    	.set_ios	= s3cmci_set_ios,
    	.get_ro		= s3cmci_get_ro,
    	.get_cd		= s3cmci_card_present,
    	.enable_sdio_irq = s3cmci_enable_sdio_irq,
    };
         其中,(*set_ios)为主控制器设置总线和时钟等配置(*get_ro)得到只读属性(*enable_sdio_irq)开启sdio中断,本文重点讨论(*request)这个回调函数,它是整个SD主控制器驱动的核心,实现了SD主控制器能与SD卡进行通信的能力。
    static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
    {
    	struct s3cmci_host *host = mmc_priv(mmc);
    
    	host->status = "mmc request";
    	host->cmd_is_stop = 0;
    	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);
    }

    可以看到这里调用了s3cmci_send_request(mmc) 函数,定义如下:

    static void s3cmci_send_request(struct mmc_host *mmc)
    {
    	struct s3cmci_host *host = mmc_priv(mmc);
    	struct mmc_request *mrq = host->mrq;
    	struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
    
    	host->ccnt++;
    	prepare_dbgmsg(host, cmd, host->cmd_is_stop);
    
    	/* Clear command, data and fifo status registers
    	   Fifo clear only necessary on 2440, but doesn't hurt on 2410
    	*/
    	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);
    
    		host->dcnt++;
    
    		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;
    		}
    
    		if (s3cmci_host_usedma(host))
    			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;
    		}
    	}
    
    	/* Send command */
    	s3cmci_send_command(host, cmd);
    
    	/* Enable Interrupt */
    	s3cmci_enable_irq(host, true);
    }

           其中有两个重要的结构体 s3cmci_setup_data(host, cmd->data)实现数据传输s3cmci_send_command(host, cmd)实现指令传输,至此,我们需要接触SD主控制器的芯片手册了。

         首先,SD主控制器由一系列32位寄存器组成。通过软件的方式,即对寄存器赋值,来控制SD主控制器,进而扮演SD主控制器的角色与SD卡取得通信。

    1) cmdat

        根据主控制器的芯片手册,寄存器MMC_CMDAT控制命令和数据的传输,具体内容如下

    结合对寄存器MMC_CMDAT的描述,分析代码

    host->cmdat &= ~CMDAT_INIT;               // 非初始化状态
    if (mrq->data) {                                       // 如果存在数据需要传输
      pxamci_setup_data(host, mrq->data);  // 实现主控制器与SD卡之间数据的传输
      cmdat &= ~CMDAT_BUSY;                      // 没有忙碌busy信号
      cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;   // 有数据传输,使用DMA
      if (mrq->data->flags & MMC_DATA_WRITE)    
       cmdat |= CMDAT_WRITE;                               // 设置为写数据
      if (mrq->data->flags & MMC_DATA_STREAM)
       cmdat |= CMDAT_STREAM;                             // 设置为数据流stream模式
    }


    2) s3cmci_setup_data  

        通过DMA实现主控制器与SD卡之间数据的传输

    static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
    {
    	u32 dcon, imsk, stoptries = 3;
    
    	/* write DCON register */
    
    	if (!data) {
    		writel(0, host->base + S3C2410_SDIDCON);
    		return 0;
    	}
    
    	if ((data->blksz & 3) != 0) {
    		/* We cannot deal with unaligned blocks with more than
    		 * one block being transferred. */
    
    		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);
    		s3cmci_reset(host);
    
    		if ((stoptries--) == 0) {
    			dbg_dumpregs(host, "DRF");
    			return -EINVAL;
    		}
    	}
    
    	dcon  = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;
    
    	if (s3cmci_host_usedma(host))
    		dcon |= S3C2410_SDIDCON_DMAEN;
    
    	if (host->bus_width == MMC_BUS_WIDTH_4)
    		dcon |= S3C2410_SDIDCON_WIDEBUS;
    
    	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;
    	}
    
    	if (host->is2440) {
    		dcon |= S3C2440_SDIDCON_DS_WORD;
    		dcon |= S3C2440_SDIDCON_DATSTART;
    	}
    
    	writel(dcon, host->base + S3C2410_SDIDCON);
    
    	/* write BSIZE register */
    
    	writel(data->blksz, host->base + S3C2410_SDIBSIZE);
    
    	/* add to IMASK register */
    	imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
    	       S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
    
    	enable_imask(host, imsk);
    
    	/* write TIMER register */
    
    	if (host->is2440) {
    		writel(0x007FFFFF, host->base + S3C2410_SDITIMER);
    	} else {
    		writel(0x0000FFFF, host->base + S3C2410_SDITIMER);
    
    		/* FIX: set slow clock to prevent timeouts on read */
    		if (data->flags & MMC_DATA_READ)
    			writel(0xFF, host->base + S3C2410_SDIPRE);
    	}
    
    	return 0;
    }
     for()循环里的内容是整个SD卡主控制器设备驱动的实质,通过DMA的方式实现主控制器与SD卡之间数据的读写操作。

    3) m3cmci_start_cmd  

         实现主控制器与SD卡之间指令的传输

    static void s3cmci_send_command(struct s3cmci_host *host,
    					struct mmc_command *cmd)
    {
    	u32 ccon, imsk;
    
    	imsk  = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT |
    		S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT |
    		S3C2410_SDIIMSK_RESPONSECRC;
    
    	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;
    
    	writel(ccon, host->base + S3C2410_SDICMDCON);
    }

    > response类型

      根据SD卡的协议,当SD卡收到从控制器发来的cmd指令后,SD卡会发出response相应,而response的类型分为R1,R1b,R2,R3,R6,R7,这些类型分别对应不同的指令,各自的数据包结构也不同(具体内容参考SD卡协议)。这里,通过RSP_TYPE对指令cmd的opcode的解析得到相对应的reponse类型,再通过swich赋给寄存器MMC_CMDAT对应的[1:0]位。

    -> 将指令和参数写入寄存器

      4行writel()是整个SD卡主控制器设备驱动的实质,通过对主控制器芯片寄存器MMC_CMD,MMC_ARGH,MMC_ARGL,MMC_CMDAT的设置,实现主控制器发送指令到SD卡的功能。


    四、申请中断

            这里对前面的中断函数进行详细描述:

            s3cmci_probe(struct platform_device *pdev)中有两个中断,一个为SD主控制器芯片内电路固有的内部中断另一个为探测引脚的探测到外部有SD卡插拔引起的中断

    1) 由主控芯片内部电路引起的中断

        (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)

    回顾一下,host->irq就是刚才从platform_device里得到的中断号,

      irq = platform_get_irq(pdev, 0);

      host->irq = irq;

    接下来,s3cmci_irq便是为该中断host->irq申请的中断函数

    /*
     * ISR for SDI Interface IRQ
     * Communication between driver and ISR works as follows:
     *   host->mrq 			points to current request
     *   host->complete_what	Indicates when the request is considered done
     *     COMPLETION_CMDSENT	  when the command was sent
     *     COMPLETION_RSPFIN          when a response was received
     *     COMPLETION_XFERFINISH	  when the data transfer is finished
     *     COMPLETION_XFERFINISH_RSPFIN both of the above.
     *   host->complete_request	is the completion-object the driver waits for
     *
     * 1) Driver sets up host->mrq and host->complete_what
     * 2) Driver prepares the transfer
     * 3) Driver enables interrupts
     * 4) Driver starts transfer
     * 5) Driver waits for host->complete_rquest
     * 6) ISR checks for request status (errors and success)
     * 6) ISR sets host->mrq->cmd->error and host->mrq->data->error
     * 7) ISR completes host->complete_request
     * 8) ISR disables interrupts
     * 9) Driver wakes up and takes care of the request
     *
     * Note: "->error"-fields are expected to be set to 0 before the request
     *       was issued by mmc.c - therefore they are only set, when an error
     *       contition comes up
     */
    
    static irqreturn_t s3cmci_irq(int irq, void *dev_id)
    {
    	struct s3cmci_host *host = dev_id;
    	struct mmc_command *cmd;
    	u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk;
    	u32 mci_cclear = 0, mci_dclear;
    	unsigned long iflags;
    
    	mci_dsta = readl(host->base + S3C2410_SDIDSTA);
    	mci_imsk = readl(host->base + host->sdiimsk);
    
    	if (mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) {
    		if (mci_imsk & S3C2410_SDIIMSK_SDIOIRQ) {
    			mci_dclear = S3C2410_SDIDSTA_SDIOIRQDETECT;
    			writel(mci_dclear, host->base + S3C2410_SDIDSTA);
    
    			mmc_signal_sdio_irq(host->mmc);
    			return IRQ_HANDLED;
    		}
    	}
    
    	spin_lock_irqsave(&host->complete_lock, iflags);
    
    	mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
    	mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
    	mci_fsta = readl(host->base + S3C2410_SDIFSTA);
    	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;
    	}
    
    	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 (!s3cmci_host_usedma(host)) {
    		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";
    		}
    
    		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;
    	}
    
    	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;
    	}
    
    	/* errors handled after this point are only relevant
    	   when a data transfer is in progress */
    
    	if (!cmd->data)
    		goto clear_status_bits;
    
    	/* Check for FIFO failure */
    	if (host->is2440) {
    		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;
    		}
    	} else {
    		if (mci_dsta & S3C2410_SDIDSTA_FIFOFAIL) {
    			dbg(host, dbg_err, "FIFO failure\n");
    			cmd->data->error = -EILSEQ;
    			host->status = "error:  fifo failure";
    			goto fail_transfer;
    		}
    	}
    
    	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;
    	}
    
    	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;
    	}
    
    	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;
    
    }
    

        当调用(*request),即host->ops->request(host, mrq),即上文中的s3cmci_request()后,控制器与SD卡之间开始进行一次指令或数据传输,通信完毕后,主控芯片将产生一个内部中断,以告知此次指令或数据传输完毕。这个中断的具体值将保存在一个名为MMC_I_REG的中断寄存器中以供读取,中断寄存器MMC_I_REG中相关描述如下



    如果中断寄存器MMC_I_REG中的第0位有值,则意味着数据传输完成,执行s3cmci_cmd_done(host, stat);

    如果中断寄存器MMC_I_REG中的第2位有值,则意味着指令传输完成,执行s3cmci_data_done(host, stat);

    其中stat是从状态寄存器MMC_STAT中读取的值,在代码里主要起到处理错误状态的作用。

    -> s3cmci_cmd_done 收到结束指令的内部中断信号,主控制器从SD卡那里得到response,结束这次指令传输

    这里需要注意,寄存器MMC_RES里已经存放了来自SD卡发送过来的response,以供读取。


    2、探测引脚引起的中断


    		host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);
    
    		if (host->irq_cd >= 0) {
    			if (request_irq(host->irq_cd, s3cmci_irq_cd,
    					IRQF_TRIGGER_RISING |
    					IRQF_TRIGGER_FALLING,
    					DRIVER_NAME, host)) {
    				dev_err(&pdev->dev,
    					"can't get card detect irq.\n");
    				ret = -ENOENT;
    				goto probe_free_gpio_cd;
    			}
    		} else {
    			dev_warn(&pdev->dev,
    				 "host detect has no irq available\n");
    			gpio_direction_input(host->pdata->gpio_detect);
    		}
    	
     

         其中,irq_cd是通过GPIO转换得到的中断号,s3cmci_irq_cd便是该中断实现的函数

    static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
    {
    	struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
    
    	dbg(host, dbg_irq, "card detect\n");
    
    	mmc_detect_change(host->mmc, msecs_to_jiffies(500));
    
    	return IRQ_HANDLED;
    }

         之前已经提到过mmc_detect_change,它将最终调用queue_delayed_work执行工作队列里的mmc_rescan函数

        当有SD卡插入或拔出时,硬件主控制器芯片的探测pin脚产生外部中断,进入中断处理函数,执行工作队列里的mmc_rescan,扫描SD总线,对插入或拔出SD卡作相应的处理。下文协议层将讨论mmc_rescan()。



    展开全文
  • Linux SD卡驱动开发

    2020-05-24 16:17:07
    Linux SD卡驱动开发(一) —— SD 相关基础概念 Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST...Linux SD卡驱动开发(六) —— SD卡启动过程总体分析 一.SD/MMC卡基础概念 1.1.什么是MMC卡 MMC:MMC就是MultiM...

    Linux SD卡驱动开发(一) —— SD 相关基础概念

    Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇

    Linux SD卡驱动开发(三) —— SD 卡驱动分析CORE篇

    Linux SD卡驱动开发(四) —— SD 控制器之真正的硬件操作

    Linux SD卡驱动开发(五) —— SD 卡驱动分析Core补充篇

    Linux SD卡驱动开发(六) —— SD卡启动过程总体分析

    一.SD/MMC卡基础概念

    1.1.什么是MMC卡

          MMC:MMC就是MultiMediaCard的缩写,即多媒体卡。它是一种非易失性存储器件,体积小巧(24mm*32mm*1.4mm),容量大,耗电量低,传输速度快,广泛应用于消费类电子产品中。

    1.2.什么是SD卡

         SD:SD卡为Secure Digital Memory Card, 即安全数码卡。它在MMC的基础上发展而来,增加了两个主要特色:SD卡强调数据的安全安全,可以设定所储存的使用权限,防止数据被他人复制;另外一个特色就是传输速度比2.11版的MMC卡快。在数据传输和物理规范上,SD卡(24mm*32mm*2.1mm,比 MMC卡更厚一点),向前兼容了MMC卡.所有支持SD卡的设备也支持MMC卡。SD卡和2.11版的MMC卡完全兼容。

    1.3.什么是SDIO

       SDIO:SDIO是在SD标准上定义了一种外设接口,它和SD卡规范间的一个重要区别是增加了低速标准。在SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开销支持低速IO能力。

    1.4.什么是MCI 

       MCI:MCI是Multimedia Card Interface的简称,即多媒体卡接口。上述的MMC,SD,SDI卡定义的接口都属于MCI接口。MCI这个术语在驱动程序中经常使用,很多文件,函数名字都包括”mci”.

    1.5.MMC/SD/SDIO卡的区别

         SDIO 是目前我们比较关心的技术,SDIO 故名思义,就是 SD 的 I/O 接口(interface )的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO 。

         所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 接脚来连接外部外围,并且透过 SD 上的I/O 数据接位与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得SDIO 外围(我们称为 SDIO 卡)的开发与应用变得相当热门。

        现在已经有非常多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device而制定),而且许多 SDIO 外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO 外围(SDIO 卡)有:

    ·Wi-Fi card (无线网络卡)
    ·CMOS sensor card (照相模块)
    ·GPS card
    ·GSM/GPRS modem card
    ·Bluetooth card
    ·Radio/TV card (很好玩)

    SDIO 的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO 式的 SPI 接口。

    二、开发板SD资源

            以Exynos4412开发板为例,其SD卡硬件原理图如下:

          图中可以看到,SD卡设备的连接方式就是SDIO总线的驱动方式,这里使用EINT7作为NCD的控制器,当SD卡设备插入/取出时均会中断响应。

    三、 SD协议概要

    1、 总线接口  

    按照SD卡的协议的描述可分为2种总线的接口 

    SD BUS   

    物理层定义:  

    D0-D3 数据传送  

    CMD 进行CMD 和Respons   

    CLK 大家最熟悉的HOST时钟信号线了 

    VDD VSS 电源和地   

    SPI BUS 

    一般用SPI协议的接口来做 

    物理层定义:  

    CLK HOST时钟信号线了  

    DATAIN HOST-àSD Card数据信号线 

    DATAOUT SD Card àHOST数据信号线

    2、请求处理流程

         根据协议,MMC/SD卡的驱动被分为:卡识别阶段和数据传输阶段。

        在卡识别阶段通过命令使MMC/SD处于:空闲(idle)、准备(ready)、识别(ident)、等待(stby)、不活动(ina)几种不同的状态;

        而在数据传输阶段通过命令使MMC/SD处于:发送(data)、传输(tran)、接收(rcv)、程序(prg)、断开连接(dis)几种不同的状态。

    所以可以总结MMC/SD在工作的整个过程中分为两个阶段和十种状态。下面使用图形来描述一下在两个阶段中这十种状态之间的转换关系。

    a -- 卡识别阶段

    b -- 数据传输阶段

    四、 MMC/SD设备驱动在Linux中的结构层次  

     在Linux中MMC/SD卡的记忆体都当作块设备。MMC/SD设备驱动代码在linux-2.6.38.2\drivers\mmc  分别有card、core和host三个文件夹,    

    card层    要把操作的数据以块设备的处理方式写到记忆体上或从记忆体上读取; 

    core层    则是将数据以何种格式,何种方式在 MMC/SD主机控制器与MMC/SD卡的记 忆体(即块设备)之间进行传递,这种格式、方式被称之为规范或协议, 

    host层   下的代码就是你要动手实现的具体MMC/SD设备驱动了,包括RAM芯片中的 SDI控制器(支持对MMC/SD卡的控制,俗称MMC/SD主机控制器)和SDI控制器与MMC/SD卡的硬件接口电路。       

    那么,card、core和host这三层的关系,我们用一幅图来进行描述,图如下:

          从这幅图中的关系可以看出,整个MMC/SD模块中最重要的部分是Core核心层,他提供了一系列的接口函数,对上提供了将主机驱动注册到系统,给应用程序提供设备访问接口,对下提供了对主机控制器控制的方法及块设备请求的支持。对于主机控制器的操作就是对相关寄存器进行读写,而对于MMC/SD设备的请求处理则比较复杂。

    展开全文
  •  Linux在内核源码的drivers/mmc/core文件夹下为我们的提供了一系列SD卡的接口服务函数。可以查看Makefile如下 可见,core文件夹下有针对总线的服务bus.c,针对主控制器的服务host.c,针对SD卡的服务sd.c, sd_ops...

           废话不多说,直接切进主题:

           Linux在内核源码的drivers/mmc/core文件夹下为我们的提供了一系列SD卡的接口服务函数。可以查看Makefile如下


    可见,core文件夹下有针对总线的服务bus.c,针对主控制器的服务host.c,针对SD卡的服务sd.c, sd_ops.c等等。

    其中,最为核心的一个函数便是之前提到的位于core.c的mmc_rescan,概括来讲,主要完成两项任务,即

    扫描SD总线,插入SD卡
    扫描SD总线,拔出SD卡


    一、 插入SD卡

            前面HOST篇最后的中断篇中讲到,插入SD卡,主控制器产生中断,进入中断处理函数s3cmci_irq_cd,其中调用的函数 mmc_detect_change,它将最终调用queue_delayed_work执行工作队列里的mmc_rescan函数

    下面来看看 mmc_rescan

    void mmc_rescan(struct work_struct *work)
    {
    	struct mmc_host *host =
    		container_of(work, struct mmc_host, detect.work);
    	int i;
    
    	if (host->rescan_disable)
    		return;
    
    	/* If there is a non-removable card registered, only scan once */
    	if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)
    		return;
    	host->rescan_entered = 1;
    
    	mmc_bus_get(host);
    
    	/*
    	 * if there is a _removable_ card registered, check whether it is
    	 * still present
    	 */
    	if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
    	    && !(host->caps & MMC_CAP_NONREMOVABLE))
    		host->bus_ops->detect(host);
    
    	host->detect_change = 0;
    
    	/*
    	 * Let mmc_bus_put() free the bus/bus_ops if we've found that
    	 * the card is no longer present.
    	 */
    	mmc_bus_put(host);
    	mmc_bus_get(host);
    
    	/* if there still is a card present, stop here */
    	if (host->bus_ops != NULL) {
    		mmc_bus_put(host);
    		goto out;
    	}
    
    	/*
    	 * Only we can add a new handler, so it's safe to
    	 * release the lock here.
    	 */
    	mmc_bus_put(host);
    
    	if (!(host->caps & MMC_CAP_NONREMOVABLE) && host->ops->get_cd &&
    			host->ops->get_cd(host) == 0) {
    		mmc_claim_host(host);
    		mmc_power_off(host);
    		mmc_release_host(host);
    		goto out;
    	}
    
    	mmc_claim_host(host);
    	for (i = 0; i < ARRAY_SIZE(freqs); i++) {
    		if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
    			break;
    		if (freqs[i] <= host->f_min)
    			break;
    	}
    	mmc_release_host(host);
    
     out:
    	if (host->caps & MMC_CAP_NEEDS_POLL)
    		mmc_schedule_delayed_work(&host->detect, HZ);
    }
    

          插入SD卡,mmc_rescan扫描SD总线上是否存在SD卡,具体的实现方法就是通过向SD卡上电,看是否能成功,以普通SD卡为例,为普通SD卡上电的函数mmc_send_app_op_cond(host, 0, &ocr);

    如果上电成功,则返回0,即执行if()里的mmc_attach_sd()进行总线与SD卡的绑定

    如果上电失败,则返回非0值,跳过if(),尝试其他上电的方法。

         那么,上电方法究竟有何不同呢?具体看看mmc_send_app_op_cond()的实现过程

    int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
    {
    	struct mmc_command cmd;
    	cmd.opcode = SD_APP_OP_COND;    /* #define SD_APP_OP_COND   41   */
    	mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES);
    
    	... ...
    
    }
    int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, struct mmc_command *cmd, int retries)
    {
    
      mmc_app_cmd(host, card);   /* #define MMC_APP_CMD   55   */
      mrq.cmd = cmd;
      cmd->data = NULL;
    
      mmc_wait_for_req(host, &mrq);
    
      ... ...
    
    }

    这里的指令SD_APP_OP_COND只有SD2.0的协议支持,也就是说,只有普通SD卡支持,所以也只有普通SD卡能够成功上电。 

                 


    如果上电成功,就开始进行总线与SD卡的绑定,通过mmc_attach_sd(),绑定过程可分为四步,

    注册SD总线上的操作函数 - struct mmc_bus_ops mmc_sd_ops

    设置主控制器的时钟和总线方式 - 通过回调函数host->ops->set_ios();

    启动SD卡 - 根据协议,完成SD卡启动的各个步骤

    注册SD卡设备驱动


    二、注册总线上的操作函数

    int mmc_attach_sd(struct mmc_host *host, u32 ocr)
    {
    	mmc_sd_attach_bus_ops(host);
    
     	... ...
    
    }
    

    static void mmc_sd_attach_bus_ops(struct mmc_host *host)
    {
    	const struct mmc_bus_ops *bus_ops;
    
    	bus_ops = &mmc_sd_ops;
    	mmc_attach_bus(host, bus_ops);
    }
    void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
    {
    	host->bus_ops = ops;
    	host->bus_refs = 1;
    	host->bus_dead = 0;
    }
    static const struct mmc_bus_ops mmc_sd_ops = {
    	.remove = mmc_sd_remove,  // 拔出SD卡的操作函数
    	.detect = mmc_sd_detect,      // 探测SD卡的操作函数
    	.suspend = NULL,
    	.resume = NULL,
    	.power_restore = mmc_sd_power_restore,  // 重新启动SD卡的操作函数
    };

         这里的mmc_sd_detect和mmc_sd_remove就是拔出SD卡所需要用到的函数,下文将详细讨论。这里需要注意的是,插入SD卡的时候,并不执行mmc_sd_detect和mmc_sd_remove这两个函数,但是会注册它们,也就是说,这两个函数的功能已经实现,将来可以使用。


    三、设置时钟和总线

    int mmc_attach_sd(struct mmc_host *host, u32 ocr)
    {
    	host->ocr = mmc_select_voltage(host, ocr);
    
    	... ...
    
    }
    
    u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
    {
    	mmc_set_ios(host);
    
    	... ...
    }
    
    static inline void mmc_set_ios(struct mmc_host *host)
    {
    	struct mmc_ios *ios = &host->ios;
    
    	host->ops->set_ios(host, ios);  // 设置主控制器时钟和总线的回调函数,具体实现由主控制器驱动完成
    }

        从这里可以体会到回调函数的精髓:协议层里利用回调函数为所有满足该协议的设备提供统一的接口,而具体实现由底层不同的设备驱动各自完成。注意到,之所以要定义一些放之四海而皆准的公用的类,比如struct mmc_host,就是需要通过struct mmc_host *host指针作为形参传到协议层所提供的接口函数中,从而得以调用。


    四、启动SD卡

    int mmc_attach_sd(struct mmc_host *host, u32 ocr)
    {
    
    	mmc_sd_init_card(host, host->ocr, NULL);
    
    	... ...
    
    }

     mmc_sd_init_card主要完成以下任务,

    SD卡的启动过程
    得到寄存器CID, CSD, SCR, RCA的数据
    其他操作比如切换到高速模式,初始化card

    static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard)
    {
    	
    	/* SD卡的启动过程 */
    	mmc_go_idle(host);
    	mmc_send_if_cond(host, ocr);
    	mmc_send_app_op_cond(host, ocr, NULL);
    	mmc_all_send_cid(host, cid);
    	mmc_send_relative_addr(host, &card->rca);
    	
    	/* 得到寄存器CID, CSD, SCR的数据 */
    	mmc_send_csd(card, card->raw_csd);
    	mmc_decode_csd(card);
    	mmc_decode_cid(card);
    	mmc_app_send_scr(card, card->raw_scr);
    	mmc_decode_scr(card);
    
    	/* 其它操作 */
    	mmc_alloc_card(host, &sd_type);
    	mmc_select_card(card); 
    	mmc_read_switch(card);
    	mmc_switch_hs(card);
    	... ...
    
    }


    1) SD卡的启动过程

        根据SD2.0协议,SD卡的状态可分为两种模式:卡识别模式(card-identification mode)和数据传输模式(data-transfer mode)。这里,我们关注启动SD卡的卡识别模式。


    综合代码:

      mmc_go_idle(host);                     CMD0
      Idle State
      mmc_send_if_cond(host, ocr);     CMD8
      mmc_send_app_op_cond(host, ocr, NULL);       ACMD41
      Ready State
      mmc_all_send_cid(host, cid);       CMD2
      Identification State
      mmc_send_relative_addr(host, &card->rca);     CMD3
      Stand-by State

    2) 寄存器CID, CSD, SCR, RCA

    -> 发送指令并得到寄存器的值

       当主控制器向SD卡发送cmd指令,比如mmc_send_cid(card, card->raw_cid),请求得到SD卡CID寄存器的值,当主控制器发送cmd完成后,芯片产生一个内部中断,处理结束cmd的中断函数,之后得到来自SD卡的response,即CID寄存器的值,存放于host->cmd->resp[i]中。关于内部中断处理,参看上文的中断一节里的 mmc_wait_for_cmd()

       mmc_send_cid(card, card->raw_cid);这个函数发送了接收CSD寄存器的请求,并且得到了来自SD卡的CSD寄存器的值。

    int mmc_send_cid(struct mmc_card *card, u32 *cid)
    {
       return mmc_send_cxd_native(card->host, card->rca << 16, cid, MMC_SEND_CID);
    
    }
    
    static int mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode)
    {
     cmd.opcode = opcode;
     cmd.arg = arg;
     cmd.flags = MMC_RSP_R2 | MMC_CMD_AC;
    
     mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
    
     memcpy(cxd, cmd.resp, sizeof(u32) * 4);  // 得到response赋给cxd,即card->raw_cid
    
     ... ...
    }

    -> 解析寄存器的值

        为什么要解析?先来看看寄存器CID在SD卡协议里的定义,它是一个128位的寄存器,存放了关于这块SD卡的基本信息,就像自己的身份证。通过mmc_send_cid()将这个寄存器的数值赋给了card->raw_cid (定义 u32 raw_cid[4];) ,为了方便得到具体某一个信息,协议层为我们解析了寄存器里的域,并赋给card->cid,比如厂商名称,就可以通过card->cid.manfid直接读取到。

    static int mmc_decode_cid(struct mmc_card *card)
    {
      u32 *resp = card->raw_cid;
    
      card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
      card->cid.oemid  = UNSTUFF_BITS(resp, 104, 16);
      card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
      card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
      card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
      card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
      card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
      card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
      card->cid.serial = UNSTUFF_BITS(resp, 16, 32);
      card->cid.month  = UNSTUFF_BITS(resp, 12, 4);
      card->cid.year  = UNSTUFF_BITS(resp, 8, 4) + 1997;
      return 0;
    }

    五、 注册SD卡设备驱动

     int mmc_attach_sd(struct mmc_host *host, u32 ocr)
    {
    
      /* mmc_alloc_card(host, &sd_type); 在mmc_sd_init_card()已完成 */
    
      mmc_add_card(host->card);
    
      ... ...
    
    }

    上文已经提到,设备驱动程序都会通过alloc_xxx()和add_xxx()两步来注册驱动,其实质是调用/drivers/base/core.c里的device_initialize()和device_add(),device_add()完成建立kobject,sys文件,发送uevent,等工作。


    六、拔出SD卡

     void mmc_rescan(struct work_struct *work)
    {
     struct mmc_host *host = container_of(work, struct mmc_host, detect.work);
     mmc_bus_get(host);
    
     /* if there is a card registered, check whether it is still present */
     if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead)
      host->bus_ops->detect(host);
    
     mmc_bus_put(host);
    
     ... ...
    
    }

    这里的mmc_bus_get/put(),为SD总线加上一个自旋锁,规定同时只能有一个线程在SD总线上操作。


    1、 bus_ops->detect()

           mmc_rescan()扫描SD总线,如果发现host->ops上赋了值,即之前已有SD卡注册过,就执行bus_ops->detect()操作去探测SD总线上是否还存在SD卡,如果不存在了,就执行bus_ops->remove()拔出SD卡。之前已经提到,这个bus_ops->detect()已在mmc_attach_sd()注册完成了。

    static void mmc_sd_detect(struct mmc_host *host)
    {
     mmc_claim_host(host);
    
     /*
      * Just check if our card has been removed.
      */
     err = mmc_send_status(host->card, NULL);
    
     mmc_release_host(host);
    
     if (err) {
      mmc_sd_remove(host);
    
      mmc_claim_host(host);
      mmc_detach_bus(host);
      mmc_release_host(host);
     }
    }

    这里的mmc_claim_host(host)通过set_current_state(TASK_RUNNING);将当前进程设置为正在运行进程。

    mmc_send_status()发送得到SD卡状态的请求,如果未能得到状态数据,则执行mmc_sd_remove(host)拔出SD卡。

    int mmc_send_status(struct mmc_card *card, u32 *status)
    {
     struct mmc_command cmd;
    
     cmd.opcode = MMC_SEND_STATUS;    /* #define MMC_SEND_STATUS   13 */
     cmd.arg = card->rca << 16;
     cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
    
     err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
    
     if (err)               
      return err;           // 接收来自SD卡的response失败,即没有发现SD卡
     if (status)
      *status = cmd.resp[0];
    
     return 0;
    
    }


    2、bus_ops->remove()

          拔出SD卡,其实就是注册SD卡驱动的反操作,实质就是执行device_del()和device_put()

    static void mmc_sd_remove(struct mmc_host *host)
    {
     mmc_remove_card(host->card);
     host->card = NULL;
    }
    void mmc_remove_card(struct mmc_card *card)
    {
     if (mmc_card_present(card))
      device_del(&card->dev);
    
     put_device(&card->dev);
    }




    展开全文
  • 一.SD/MMC卡基础概念 1.1.什么是MMC卡  MMC:MMC就是MultiMediaCard的缩写,即多媒体卡。它是一种非易失性存储器件,体积小巧(24mm*32mm*1.4... SD:SD卡为Secure Digital Memory Card, 即安全数码卡。它在MMC的基础

    一.SD/MMC卡基础概念

    1.1.什么是MMC卡

          MMC:MMC就是MultiMediaCard的缩写,即多媒体卡。它是一种非易失性存储器件,体积小巧(24mm*32mm*1.4mm),容量大,耗电量低,传输速度快,广泛应用于消费类电子产品中。

    1.2.什么是SD卡

         SD:SD卡为Secure Digital Memory Card, 即安全数码卡。它在MMC的基础上发展而来,增加了两个主要特色:SD卡强调数据的安全安全,可以设定所储存的使用权限,防止数据被他人复制;另外一个特色就是传输速度比2.11版的MMC卡快。在数据传输和物理规范上,SD卡(24mm*32mm*2.1mm,比 MMC卡更厚一点),向前兼容了MMC卡.所有支持SD卡的设备也支持MMC卡。SD卡和2.11版的MMC卡完全兼容。

    1.3.什么是SDIO

       SDIO:SDIO是在SD标准上定义了一种外设接口,它和SD卡规范间的一个重要区别是增加了低速标准。在SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开销支持低速IO能力。

    1.4.什么是MCI 

       MCI:MCI是Multimedia Card Interface的简称,即多媒体卡接口。上述的MMC,SD,SDI卡定义的接口都属于MCI接口。MCI这个术语在驱动程序中经常使用,很多文件,函数名字都包括”mci”.

    1.5.MMC/SD/SDIO卡的区别


         SDIO 是目前我们比较关心的技术,SDIO 故名思义,就是 SD 的 I/O 接口(interface )的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO 。

         所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 接脚来连接外部外围,并且透过 SD 上的I/O 数据接位与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得SDIO 外围(我们称为 SDIO 卡)的开发与应用变得相当热门。

        现在已经有非常多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device而制定),而且许多 SDIO 外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO 外围(SDIO 卡)有:

    ·Wi-Fi card (无线网络卡)
    ·CMOS sensor card (照相模块)
    ·GPS card
    ·GSM/GPRS modem card
    ·Bluetooth card
    ·Radio/TV card (很好玩)

    SDIO 的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO 式的 SPI 接口。


    二、开发板SD资源

            以Exynos4412开发板为例,其SD卡硬件原理图如下:


          图中可以看到,SD卡设备的连接方式就是SDIO总线的驱动方式,这里使用EINT7作为NCD的控制器,当SD卡设备插入/取出时均会中断响应。


    三、 SD协议概要

    1、 总线接口  

    按照SD卡的协议的描述可分为2种总线的接口 

    SD BUS   

    物理层定义:  

    D0-D3 数据传送  

    CMD 进行CMD 和Respons   

    CLK 大家最熟悉的HOST时钟信号线了 

    VDD VSS 电源和地   


    SPI BUS 

    一般用SPI协议的接口来做 

    物理层定义:  

    CLK HOST时钟信号线了  

    DATAIN HOST-àSD Card数据信号线 

    DATAOUT SD Card àHOST数据信号线


    2、请求处理流程

         根据协议,MMC/SD卡的驱动被分为:卡识别阶段数据传输阶段

        在卡识别阶段通过命令使MMC/SD处于:空闲(idle)、准备(ready)、识别(ident)、等待(stby)、不活动(ina)几种不同的状态;

        而在数据传输阶段通过命令使MMC/SD处于:发送(data)、传输(tran)、接收(rcv)、程序(prg)、断开连接(dis)几种不同的状态。

    所以可以总结MMC/SD在工作的整个过程中分为两个阶段和十种状态。下面使用图形来描述一下在两个阶段中这十种状态之间的转换关系。

    a -- 卡识别阶段


    b -- 数据传输阶段




    四、 MMC/SD设备驱动在Linux中的结构层次  

     在Linux中MMC/SD卡的记忆体都当作块设备。MMC/SD设备驱动代码在linux-2.6.38.2\drivers\mmc  分别有card、core和host三个文件夹,    

    card层    要把操作的数据以块设备的处理方式写到记忆体上或从记忆体上读取; 

    core层    则是将数据以何种格式,何种方式在 MMC/SD主机控制器与MMC/SD卡的记 忆体(即块设备)之间进行传递,这种格式、方式被称之为规范或协议, 

    host层   下的代码就是你要动手实现的具体MMC/SD设备驱动了,包括RAM芯片中的 SDI控制器(支持对MMC/SD卡的控制,俗称MMC/SD主机控制器)和SDI控制器与MMC/SD卡的硬件接口电路。       

    那么,card、core和host这三层的关系,我们用一幅图来进行描述,图如下:

          从这幅图中的关系可以看出,整个MMC/SD模块中最重要的部分是Core核心层,他提供了一系列的接口函数,对上提供了将主机驱动注册到系统,给应用程序提供设备访问接口,对下提供了对主机控制器控制的方法及块设备请求的支持。对于主机控制器的操作就是对相关寄存器进行读写,而对于MMC/SD设备的请求处理则比较复杂。



    展开全文
  • linux 驱动编写(sd卡驱动

    千次阅读 2018-04-08 08:59:02
    那下面我们可以看一下,linux驱动框架上是怎么处理sd卡驱动的?1、代码目录地址drivers/mmc2、基本结构从mmc的代码结构可以看得出,主要分为两个部分,其中core为协议部分,host为各个soc的适配部分host是我...
  • 一.SD/MMC卡介绍1.1.什么是MMC卡 MMC:MMC就是MultiMediaCard的缩写,即多媒体卡。...1.2.什么是SD卡 SD:SD卡为Secure Digital Memory Card, 即安全数码卡。它在MMC的基础上发展而来,增加了两...
  • mmc驱动主要文件包括 drivers/mmc/card/block.c drivers/mmc/card/queue.c drivers/mmc/core/core.c drivers/mmc/core/host.c drivers/mmc/core/ 内核启动时,首先执行core/core.c的mmc_init,注册mmc、sd总线,以及...
  • LinuxSD卡驱动移植

    千次阅读 2011-08-13 20:30:50
    Linux中对SD卡的支持非常完善了,我们只需要按照以下修改即可完成SD卡的初始化 1、arch/arm/mach-s3c2440/mach-smdk2440.c platform结构中增加&s3c_device_sdi语句。 static struct platform_d
  • linux sd卡驱动分析

    千次阅读 2014-04-17 21:37:27
    基于S3C2410的SD卡linux驱动工作原理  Linux MMC/SD/SDIO体系结构 linux sd卡驱动分析  基于S3C2410的SD卡linux驱动工作原理  我在讲嵌入式Linux驱动开发班的时候...
  • 其实SD控制器层更过的意义是为core层提供一种操作SD卡硬件的一种方法,当然不同的控制器对硬件控制的方法不尽相同,但是他们最终都能像core层提交一个统一的封装有操作方法的数据结构,那便是即将闪亮登场的struct ...
  • linuxSD卡驱动分析(一)

    千次阅读 2016-07-26 17:08:59
    #include #include "sdhci-pltfm.h" #define SDHCI_ARASAN_CLK_CTRL_OFFSET 0x2c #define CLK_CTRL_TIMEOUT_SHIFT 16 #define CLK_CTRL_TIMEOUT_MASK (0xf ) #define CLK_CTRL_TIMEOU
  • Linux PCI网卡驱动的详细分析

    千次阅读 2019-05-07 16:38:19
    Linux网卡驱动分析  学习应该是一个先把问题简单化,在把问题复杂化的过程。一开始就着手处理复杂的问题,难免让人有心惊胆颤,捉襟见肘的感觉。读Linux网卡驱动也是一样。那长长的源码夹杂着那些我们陌生的变量和...
  • spi模式sd卡linux设备驱动移植

    千次阅读 2016-12-04 17:05:26
    1、spi模式的sd卡linux设备驱动涉及两个驱动,一个spi总线的驱动,另一个是sd卡驱动sd卡设备挂载在spi总线上并不像设备挂载在i2c总线上,i2c总线提供设备挂载需要的接口函数attach,只需要设备主动去调用attach就...
  • linux sd卡驱动分析,基于mini2440,sdio mmc sd卡驱动 1. 硬件基础: SD/MMC/SDIO 概念区分概要 SD (Secure Digital )与 MMC (Multimedia Card ) SD 是一种 flash memory card 的标准,也...
  • linux虚拟网卡驱动

    千次阅读 2017-11-14 09:13:29
    之前的前两篇写w5500网卡驱动是我的思路有偏差,用的是一种取巧的方法,在linux的用户空间利用spidev直接进行w5500的设置与tcp连接,这只能叫做是一个w5500的应用程序驱动,虽然能达到相应的目的,但是感觉是一种...
  • linux SD卡驱动分析

    2012-10-09 20:31:01
    1. 硬件基础: SD/MMC/SDIO 概念区分概要 SD (Secure Digital )与 MMC (Multimedia ...SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆,而 MMC 则是较早的一种记忆标准,目前已经被 SD
1 2 3 4 5 ... 20
收藏数 30,030
精华内容 12,012
关键字:

linux sd卡驱动结构