一
本mmc子系统主要讨论了下述内容:
关于mmc子系统,各硬件平台、各Linux版本之间的差异;
阅读mmc子系统需要的知识准备;
啥是是mmc,啥又是SD,啥又是SDIO;
一些LInux设备模型的重现;
虚拟总线;
mmc子系统的结构组织;
关于platform总线,研究的主线一;
关于mmc总线,研究的主线二;
关于sdio总线
相关结构体;比如 platform_device描述一个platform设备,platform driver描述一个msmsdcc_driver设备驱动,描述mmc card的结构等等;
host控制的结构体;
mmc子系统的代码组织;
BSP相关代码;
驱动相关代码;
主要代码分析;
mmc card初始设备;
相关中断,及上下半步各完成什么工作;
定时器相关的一些介绍;
二
mmc子系统差异说明:
本mmc子系统学习笔记一以htc g5(android)手机的linux内核为例,除设备资源外的差别之外,和其他linux内核无异。
内核版本:2.6.x
linux设备驱动多分为多层次管理,只有最底层一般会和设备资源相关,故mmc子系统的最底层才与不同厂商打交道,如s3c,msm,omap等,但是处理流程一样,无多少变化。
关于mmc
MMC:Multi Media Card的缩写,即多媒体卡。24mm*32mm*1.mm。以前的MMC规范的数据传输宽度只有1位,最新的1.0版MMC中拓宽了4位和8位带宽,时钟频率也达到了52MHZ,从而理所当然的支持50MHZ的传输速率。值得一提的是,对于SD时代提倡的“数据安全”特性,MMC协会也终于接纳了具有竞争性的安全卡协议——Secure MMC1.1规范。
关于SD,SDIO
SD(Secure Digital Memory Card),即安全数码存储卡,建议读法是SD存储卡。它在MMC的基础上发展而来,并且增加了相对于MMC的两个新特色:传输速率比2.11版本的MMC快了将近4倍,另外SD存储卡强调数据的安全,可以设定数据的使用权限,防止他人复制。
在数据和物理规范上,SD存储卡向前兼容MMC卡,在外观上,SD存储卡只是在厚度上比MMC卡厚了0.7mm。
这段时间在研究SDIO接口,所以要求对MMC、SD、SDIO都要有所了解,网上找过这些概念,不过总是流传的是Jollen 笔记里面的一份,许多地方未能清楚交待,所以我在其基础上加上一些自己的了解来加深对SDIO的学习。并一并学习下SDIO总线。
三
这些寄存器的详细分区已经其对应的功能,在开发过程中都是需要仔细研读的,这些都在协议的SPEC中都有详细说明,这里就不在啰嗦了。
四
Linux设备模型:
这个walfred曾经做好了一个系列,地址是:Linux设备模型学习笔记阅读
进程上下文和中断上下文:
处理器总处于以下状态中的一种:
1、内核态,运行于进程上下文,内核代表进程运行于内核空间;
2、内核态,运行于中断上下文,内核代表硬件运行于内核空间;
3、用户态,运行于用户空间。
用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。
硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的“中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。
五
在Linux中,系统中mmc子系统本身没有任何README文档,所以此算一个"帮助文档"。其将涉及到3条总线,2块设备,2个驱动。
三条总线
总线名 | platform | mmc | sdio |
类型 | struct bus_type | ||
变量名 | platform_bus | mmc_bus_type | sdio_bus_type |
这个我们可以在/sys/bus中查看。
两块设备
设备名 | msm_sdcc | mmc_card |
类型 | struct platform_device | struct mmc_card |
备注 | msm_sdcc(soc) | 由前者sdcc的驱动,对mmc/sd/sdio的统一抽象 |
msm_sdcc(全称应该是sdcardcontroller集成到soc,所以属platform_device,优点具有较好的移植性和安全性),mmc_card(由前者sdcc的驱动,对mmc/sd/sdio的统一抽象);
两组驱动
驱动名 | msmsdcc_driver | mmc_driver |
类型 | struct platform_driver | struct mmc_driver |
备注 |
|
|
对应与上面的2块设备,分别是msmsdcc_driver,mmc_driver;
它们之间的关系
关于platform总线,下文研究的主线一
驱动msmsdcc_driver,以及设备msm_sdcc是通过platform_bus关联。
关于mmc总线,下文研究的主线二
驱动mmc_driver,以及设备mmc_card是通过该总线关联
关于sdio总线
尚未过多研究,此处暂时并考虑。以后会陆续补充。
相关结构体
这是描述一个platform设备的
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
这是msm_device_sdc设备
struct platform_device msm_device_sdc1 = {
.name = "msm_sdcc",//platform_device名
.id = 1,
.num_resources = ARRAY_SIZE(resources_sdc1),//资源个数
.resource = resources_sdc1,//指向资源的指针
.dev = {
.coherent_dma_mask = 0xffffffff,
},
};
platform driver这是描述一个msmsdcc_driver设备驱动的
static struct platform_driver msmsdcc_driver = {
.probe = msmsdcc_probe,
.suspend = msmsdcc_suspend,
.resume = msmsdcc_resume,
.driver = {
.name = "msm_sdcc",
},
};
描述mmc card的结构体
/*
* MMC device
*/
struct mmc_card {
struct mmc_host *host; /* the host this device belongs to */
struct device dev; /* the device */
unsigned int rca; /* relative card address of device */
unsigned int type; /* card type */
#define MMC_TYPE_MMC 0 /* MMC card */
#define MMC_TYPE_SD 1 /* SD card */
#define MMC_TYPE_SDIO 2 /* SDIO card */
unsigned int state; /* (our) card state */
#define MMC_STATE_PRESENT (1<<0) /* present in sysfs */
#define MMC_STATE_READONLY (1<<1) /* card is read-only */
#define MMC_STATE_HIGHSPEED (1<<2) /* card is in high speed mode */
#define MMC_STATE_BLOCKADDR (1<<3) /* card uses block-addressing */
unsigned int quirks; /* card quirks */
#define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */
#define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1) /* use func->cur_blksize */
/* for byte mode */
u32 raw_cid[4]; /* raw card CID */
u32 raw_csd[4]; /* raw card CSD */
u32 raw_scr[2]; /* raw card SCR */
struct mmc_cid cid; /* card identification */
struct mmc_csd csd; /* card specific */
struct mmc_ext_csd ext_csd; /* mmc v4 extended card specific */
struct sd_scr scr; /* extra SD information */
struct sd_switch_caps sw_caps; /* switch (CMD6) caps */
unsigned int sdio_funcs; /* number of SDIO functions */
struct sdio_cccr cccr; /* common card info */
struct sdio_cis cis; /* common tuple info */
struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */
unsigned num_info; /* number of info strings */
const char **info; /* info strings */
struct sdio_func_tuple *tuples; /* unknown common tuples */
struct dentry *debugfs_root;
};
描述mmc card驱动的结构体
static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.suspend = mmc_blk_suspend,
.resume = mmc_blk_resume,
};
控制的结构体
struct msmsdcc_host {
struct resource *cmd_irqres;
struct resource *pio_irqres;
struct resource *memres;
struct resource *dmares;
void __iomem *base;
int pdev_id;
unsigned int stat_irq;
struct msmsdcc_curr_req curr;
struct mmc_host *mmc;
struct clk *clk; /* main MMC bus clock */
struct clk *pclk; /* SDCC peripheral bus clock */
unsigned int clks_on; /* set if clocks are enabled */
struct timer_list busclk_timer;
unsigned int eject; /* eject state */
spinlock_t lock;
unsigned int clk_rate; /* Current clock rate */
unsigned int pclk_rate;
u32 pwr;
u32 saved_irq0mask; /* MMCIMASK0 reg value */
struct mmc_platform_data *plat;
struct timer_list timer;
unsigned int oldstat;
struct msmsdcc_dma_data dma;
struct msmsdcc_pio_data pio;
int cmdpoll;
struct msmsdcc_stats stats;
#ifdef CONFIG_MMC_MSM7X00A_RESUME_IN_WQ
struct work_struct resume_task;
#endif
/* Command parameters */
unsigned int cmd_timeout;
unsigned int cmd_pio_irqmask;
unsigned int cmd_datactrl;
struct mmc_command *cmd_cmd;
u32 cmd_c;
};
六
BSP相关代码
在soc系统中,sdcc不可能单独使用cpu的接口连接mmc总线,或者sdio总线,也不会使用spi,sci等,故直接集成到cpu中,充作一个platform_device。
arch/arm/
mach-msm/
board-mahimahi-mmc.c /*初始化platform_device,并将其添加到platform_bus上*/
board-qsd8x50.c /*platform_device的资源定义*/
mmc驱动相关代码
driver/
mmc/
card/
block.c /*描述了一个块设备驱动;*/
queue.c /*上述块设备驱动的请求队列等的实现;*/
care/ /*mmc子系统的核心层次*/
bus.c /*mmc_bus相关,包括驱动mmc_driver注册,及设备mmc_card设备及驱动注册*/
core.c /*核心,调用sd,mmc,sdio操作函数*/
mmc_ops.c /*cmd0 cmd8*/
mmc.c /*主要是将mmc设备初始化成mmc_card添加到mmc总线,在添加之前需要完成的初始化,获取寄存器信息等;*/
sdio_ops.c /*cmd5和cmd52*/
sdio.c 主要是将sdio设备初始化成mmc_card添加到mmc总线,在添加之前需要完成的初始化,获取寄存器信息等;
sd_ops.c /*cmd41相关*/
sd.c 主要是将sd设备初始化成mmc_card添加到mmc总线,在添加之前需要完成的初始化,获取寄存器信息等;
host/
host.c /*和host相关的一些函数*/
msm_sdcc.c /*Qualcomm MSM 7X00A SDCC Driver*/
其他平台暂时忽略
七
在mmc子系统学习笔记四 mmc子系统的结构组织中,谈到了mmc子系统中,我们只要看这两条主线就几乎可以吃透整个mmc子系统了,下面我们先看第一条主线。
主线一,关于platform总线
设备msm_sdcc,以及驱动msmsdcc_driver是通过platform_bus关联。
platform总线,暂且理解成系统就已经存在的。
platform_device msm_sdcc
系统通过调用mahimahi_init_mmc初始化设备,并通过调用msm_add_sdcc将设备注册进platform bus。
代码清单:
int __init mahimahi_init_mmc(unsigned int sys_rev, unsigned debug_uart) //系统在初始化完成的,对与参数sys_rev需要考虑其值
{
uint32_t id;
printk("%s()+\n", __func__);
/* initial WIFI_SHUTDOWN# */
id = PCOM_GPIO_CFG(MAHIMAHI_GPIO_WIFI_SHUTDOWN_N, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA),
msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0);
msm_add_sdcc(1, &mahimahi_wifi_data, 0, 0);/*第一次调用,整型量1表示数组下表,
mahimahi_wifi_data是除却mem,irq,dma其他的相关资源集合,可以看出这是关于sdio相关的资源*/
if (debug_uart) {
pr_info("%s: sdcard disabled due to debug uart\n", __func__);
goto done;
}
if (opt_disable_sdcard) {
pr_info("%s: sdcard disabled on cmdline\n", __func__);
goto done;
}
sdslot_vreg_enabled = 0;
if (is_cdma_version(sys_rev)) {
/* In the CDMA version, sdslot is supplied by a gpio. */
int rc = gpio_request(MAHIMAHI_CDMA_SD_2V85_EN, "sdslot_en");
if (rc < 0) {
pr_err("%s: gpio_request(%d) failed: %d\n", __func__,
MAHIMAHI_CDMA_SD_2V85_EN, rc);
return rc;
}
mahimahi_sdslot_data.translate_vdd = mahimahi_cdma_sdslot_switchvdd;
} else {
/* in UMTS version, sdslot is supplied by pmic */
sdslot_vreg = vreg_get(0, "gp6");
if (IS_ERR(sdslot_vreg))
return PTR_ERR(sdslot_vreg);
}
if (system_rev > 0)
msm_add_sdcc(2, &mahimahi_sdslot_data, 0, 0); /*这边同上,是第二次添加platform device到platform bus上,注意这次是传的2,
加上额外的资源mahimahi_sdslot_data,*/
else {
mahimahi_sdslot_data.status = mahimahi_sdslot_status_rev0;
mahimahi_sdslot_data.register_status_notify = NULL;
set_irq_wake(MSM_GPIO_TO_INT(MAHIMAHI_GPIO_SDMC_CD_REV0_N), 1);
msm_add_sdcc(2, &mahimahi_sdslot_data,
MSM_GPIO_TO_INT(MAHIMAHI_GPIO_SDMC_CD_REV0_N),
IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHEDGE);
}
done:
printk("%s()-\n", __func__);
return 0;
}
int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat,
unsigned int stat_irq, unsigned long stat_irq_flags)
{
struct platform_device *pdev;
struct resource *res;
if (controller < 1 || controller > 4)
return -EINVAL;
pdev = msm_sdcc_devices[controller-1];//根据传过来的1和2,可知道pdev将是msm_sdcc_devices[0],msm_sdcc_devices[1]
pdev->dev.platform_data = plat;
res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "status_irq");
if (!res)
return -EINVAL;
else if (stat_irq) {
res->start = res->end = stat_irq;
res->flags &= ~IORESOURCE_DISABLED;
res->flags |= stat_irq_flags;
}
return platform_device_register(pdev);//最后挂到iplatform bus上
}
//msm_sdcc_devices定义了4个元素,可以看下每一个元素
static struct platform_device *msm_sdcc_devices[] __initdata = {
&msm_device_sdc1,
&msm_device_sdc2,
&msm_device_sdc3,
&msm_device_sdc4,
};
struct platform_device msm_device_sdc1 = {
.name = "msm_sdcc",//platform_device名
.id = 1,
.num_resources = ARRAY_SIZE(resources_sdc1),//资源个数
.resource = resources_sdc1,//指向资源的指针
.dev = {
.coherent_dma_mask = 0xffffffff,
},
};
struct platform_device msm_device_sdc2 = {
.name = "msm_sdcc",
.id = 2,
.num_resources = ARRAY_SIZE(resources_sdc2),
.resource = resources_sdc2,
.dev = {
.coherent_dma_mask = 0xffffffff,
},
};
资源可以看下
static struct resource resources_sdc1[] = {
{
.start = MSM_SDC1_PHYS,
.end = MSM_SDC1_PHYS + MSM_SDC1_SIZE - 1,
.flags = IORESOURCE_MEM,
},
{
.start = INT_SDC1_0,
.end = INT_SDC1_0,
.flags = IORESOURCE_IRQ,
.name = "cmd_irq",
},
{
.start = INT_SDC1_1,
.end = INT_SDC1_1,
.flags = IORESOURCE_IRQ,
.name = "pio_irq",
},
{
.flags = IORESOURCE_IRQ | IORESOURCE_DISABLED,
.name = "status_irq"
},
{
.start = 8,
.end = 8,
.flags = IORESOURCE_DMA,
},
};
另:一共定义了四个元素大小的数组msm_sdcc_devices,最终只用了下表为0和1的两个元素msm_sdcc_devices[0]和msm_sdcc_devices[1]。对比这两个,可以看得出他们的名字都是msm_sdcc,只不过他们分配的资源不一样,比如中断号(是共享中断号26,27...)和内存地址。当然额外的mahimahi_wifi_data和mahimahi_sdslot_data更是不一样的。这样这个msm_sdd就注册到platform bus上了。
platform_driver msmsdcc_driver
代码位置
driver/
host/
msm_sdcc.c /*Qualcomm MSM 7X00A SDCC Driver*/
其他平台暂时忽略
代码清单:
static int __init msmsdcc_init(void)
{
return platform_driver_register(&msmsdcc_driver);
}
然后看下,msmsdcc_driver这个结构体
static struct platform_driver msmsdcc_driver = {
.probe = msmsdcc_probe,
.suspend = msmsdcc_suspend,
.resume = msmsdcc_resume,
.driver = {
.name = "msm_sdcc",
},
};
看看这个msmsdcc_driver
主要完成上述结构体的任务,这里主要分析msmsdcc_probe函数,这个很重要,他的任务是巨大的,下面小节会很详细的分析,这个知道大概先,需要完成:
(1)构造msmsdcc_host这个结构体,并和mmc_host这个结构体关联起来,mmc_host是主host(真正的)是和device通信的,而msmsdcc_host只不过是结合msm的平台虚构出来的,他们关联之后,mmc_host就能找到msmsdcc_host了。这样msmsdcc_sdcc就可以和接到这个平台的mmc系列卡通信了。
(2)定时/延时函数的
(3)读写中断处理程序的注册
(4)抽象出mmc_card结构的设备
下面是代码的分析:
static int msmsdcc_probe(struct platform_device *pdev)
{
struct mmc_platform_data *plat = pdev->dev.platform_data;
struct msmsdcc_host *host;
struct mmc_host *mmc;
struct resource *cmd_irqres = NULL;
struct resource *pio_irqres = NULL;
struct resource *stat_irqres = NULL;
struct resource *memres = NULL;
struct resource *dmares = NULL;
int ret;
/* must have platform data */
if (!plat) {
pr_err("%s: Platform data not available\n", __func__);
ret = -EINVAL;
goto out;
}
if (pdev->id < 1 || pdev->id > 4)
return -EINVAL;
if (pdev->resource == NULL || pdev->num_resources < 2) {
pr_err("%s: Invalid resource\n", __func__);
return -ENXIO;
}
memres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
cmd_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
"cmd_irq");
pio_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
"pio_irq");
stat_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
"status_irq");
if (!cmd_irqres || !pio_irqres || !memres) {
pr_err("%s: Invalid resource\n", __func__);
return -ENXIO;
}
/*
* Setup our host structure
*/
mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto out;
}/*完成上述任务一,具体的可以进去看看,里面定义了一个延时工作*/
host = mmc_priv(mmc);/*关联*/
host->pdev_id = pdev->id;
host->plat = plat;
host->mmc = mmc;
host->curr.cmd = NULL;
host->cmdpoll = 1;
host->base = ioremap(memres->start, PAGE_SIZE);
if (!host->base) {
ret = -ENOMEM;
goto out;
}
host->cmd_irqres = cmd_irqres;
host->pio_irqres = pio_irqres;
host->memres = memres;
host->dmares = dmares;
spin_lock_init(&host->lock);
#ifdef CONFIG_MMC_EMBEDDED_SDIO
if (plat->embedded_sdio)
mmc_set_embedded_sdio_data(mmc,
&plat->embedded_sdio->cis,
&plat->embedded_sdio->cccr,
plat->embedded_sdio->funcs,
plat->embedded_sdio->num_funcs);
#endif
/*
* Setup DMA
*/
msmsdcc_init_dma(host);
/* Get our clocks */
host->pclk = clk_get(&pdev->dev, "sdc_pclk");
if (IS_ERR(host->pclk)) {
ret = PTR_ERR(host->pclk);
goto host_free;
}
host->clk = clk_get(&pdev->dev, "sdc_clk");
if (IS_ERR(host->clk)) {
ret = PTR_ERR(host->clk);
goto pclk_put;
}
/* Enable clocks */
ret = msmsdcc_enable_clocks(host);
if (ret)
goto clk_put;
ret = clk_set_rate(host->clk, msmsdcc_fmin);
if (ret) {
pr_err("%s: Clock rate set failed (%d)\n", __func__, ret);
goto clk_disable;
}
host->pclk_rate = clk_get_rate(host->pclk);
host->clk_rate = clk_get_rate(host->clk);
/*
* Setup MMC host structure
*/
mmc->ops = &msmsdcc_ops; /*操作函数,由最上层的card调用的操作*/
mmc->f_min = msmsdcc_fmin;
mmc->f_max = msmsdcc_fmax;
mmc->ocr_avail = plat->ocr_mask;
if (msmsdcc_4bit)
mmc->caps |= MMC_CAP_4_BIT_DATA;
if (msmsdcc_sdioirq)
mmc->caps |= MMC_CAP_SDIO_IRQ;
mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
mmc->max_phys_segs = NR_SG;
mmc->max_hw_segs = NR_SG;
mmc->max_blk_size = 4096; /* MCI_DATA_CTL BLOCKSIZE up to 4096 */
mmc->max_blk_count = 65536;
mmc->max_req_size = 33554432; /* MCI_DATA_LENGTH is 25 bits */
mmc->max_seg_size = mmc->max_req_size;
msmsdcc_writel(host, 0, MMCIMASK0);
msmsdcc_writel(host, 0x5e007ff, MMCICLEAR);
msmsdcc_writel(host, MCI_IRQENABLE, MMCIMASK0);
host->saved_irq0mask = MCI_IRQENABLE;
mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY;
if (plat->built_in)
mmc->pm_flags = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY;
/*
* Setup card detect change
*/
memset(&host->timer, 0, sizeof(host->timer));
if (stat_irqres && !(stat_irqres->flags & IORESOURCE_DISABLED)) {
unsigned long irqflags = IRQF_SHARED |
(stat_irqres->flags & IRQF_TRIGGER_MASK);
host->stat_irq = stat_irqres->start;
ret = request_irq(host->stat_irq,
msmsdcc_platform_status_irq,
irqflags,
DRIVER_NAME " (slot)",
host);/**中断处理程序的注册一,卡状态*/
if (ret) {
pr_err("%s: Unable to get slot IRQ %d (%d)\n",
mmc_hostname(mmc), host->stat_irq, ret);
goto clk_disable;
}
} else if (plat->register_status_notify) {
plat->register_status_notify(msmsdcc_status_notify_cb, host);
} else if (!plat->status)
pr_err("%s: No card detect facilities available\n",
mmc_hostname(mmc));
else {
init_timer(&host->timer);
host->timer.data = (unsigned long)host;
host->timer.function = msmsdcc_check_status;
host->timer.expires = jiffies + HZ;
add_timer(&host->timer);
}/*定时器相关*/
if (plat->status) {
host->oldstat = host->plat->status(mmc_dev(host->mmc));
host->eject = !host->oldstat;
}
init_timer(&host->busclk_timer);
host->busclk_timer.data = (unsigned long) host;
host->busclk_timer.function = msmsdcc_busclk_expired;
ret = request_irq(cmd_irqres->start, msmsdcc_irq, IRQF_SHARED,
DRIVER_NAME " (cmd)", host);/*中断处理程序的注册二,msmsdcc_irq为中断处理函数,下面会分析的,命令状态*/
if (ret)
goto stat_irq_free;
ret = request_irq(pio_irqres->start, msmsdcc_pio_irq, IRQF_SHARED,
DRIVER_NAME " (pio)", host);/*中断处理程序的注册三,msmsdcc_pio_irq为中断处理函数,下面会分析的,读写卡*/
if (ret)
goto cmd_irq_free;
mmc_set_drvdata(pdev, mmc);
mmc_add_host(mmc);/*完成任务四,这个在下文还需要具体分析*/
pr_info("%s: Qualcomm MSM SDCC at 0x%016llx irq %d,%d dma %d\n",
mmc_hostname(mmc), (unsigned long long)memres->start,
(unsigned int) cmd_irqres->start,
(unsigned int) host->stat_irq, host->dma.channel);
pr_info("%s: 4 bit data mode %s\n", mmc_hostname(mmc),
(mmc->caps & MMC_CAP_4_BIT_DATA ? "enabled" : "disabled"));
pr_info("%s: MMC clock %u -> %u Hz, PCLK %u Hz\n",
mmc_hostname(mmc), msmsdcc_fmin, msmsdcc_fmax, host->pclk_rate);
pr_info("%s: Slot eject status = %d\n", mmc_hostname(mmc), host->eject);
pr_info("%s: Power save feature enable = %d\n",
mmc_hostname(mmc), msmsdcc_pwrsave);
if (host->dma.channel != -1) {
pr_info("%s: DM non-cached buffer at %p, dma_addr 0x%.8x\n",
mmc_hostname(mmc), host->dma.nc, host->dma.nc_busaddr);
pr_info("%s: DM cmd busaddr 0x%.8x, cmdptr busaddr 0x%.8x\n",
mmc_hostname(mmc), host->dma.cmd_busaddr,
host->dma.cmdptr_busaddr);
} else
pr_info("%s: PIO transfer enabled\n", mmc_hostname(mmc));
if (host->timer.function)
pr_info("%s: Polling status mode enabled\n", mmc_hostname(mmc));
#if BUSCLK_PWRSAVE
msmsdcc_disable_clocks(host, 1);
#endif
return 0;
cmd_irq_free:
free_irq(cmd_irqres->start, host);
stat_irq_free:
if (host->stat_irq)
free_irq(host->stat_irq, host);
clk_disable:
msmsdcc_disable_clocks(host, 0);
clk_put:
clk_put(host->clk);
pclk_put:
clk_put(host->pclk);
host_free:
mmc_free_host(mmc);
out:
return ret;
}
看完,那我们就着看主线二,再提醒下mmc子系统学习笔记框架在mmc子系统学习笔记一 序。
关于mmc总线,这是研究的主线二
驱动mmc_driver,以及设备mmc_card是通过该总线关联。
我们知道上面的mmc子系统学习笔记六 基于platform总线的驱动代码研究已经生产出来了mmc_card,这个mmc_card添加到哪儿呢?
答案是添加到mmc总线上了,那么mmc总线肯定是在mmc_card生产出来之前就已经向系统注册了吗?因为关于mmc总线的我们的device已经有了。下面不说了。
mmc_bus相关代码
这个是上述驱动代码总最先注册的。看下代码:
位置:
care/ /*mmc子系统的核心层次*/
bus.c /*mmc_bus相关,包括驱动mmc_driver注册,及设备mmc_card设备及驱动注册*/
core.c /*核心,调用sd,mmc,sdio操作函数*/
这是在core.c调用bus.c和sdio_bus.c中的mmc 和 sdio总线注册函数
static int __init mmc_init(void)
{
int ret;
wake_lock_init(&mmc_delayed_work_wake_lock, WAKE_LOCK_SUSPEND, "mmc_delayed_work");
workqueue = create_singlethread_workqueue("kmmcd");
if (!workqueue)
return -ENOMEM;
ret = mmc_register_bus();
if (ret)
goto destroy_workqueue;
ret = mmc_register_host_class();
if (ret)
goto unregister_bus;
ret = sdio_register_bus();
if (ret)
goto unregister_host_class;
return 0;
unregister_host_class:
mmc_unregister_host_class();
unregister_bus:
mmc_unregister_bus();
destroy_workqueue:
destroy_workqueue(workqueue);
return ret;
}
而调用上述函数的是subsys_initcall(mmc_init);什么是subsys_initcall()呢?系统初始化某些子系统的时候的入口函数,所以在系统起来的时候就已经地用了。
关于mmc_card
上面5.1.4已经生产出来了mmc card这边bu就赘述了。
关于mmc_driver
mmc driver是mmc card的驱动,这个驱动和一般的块设备驱动没什么两样。
代码位置:
driver/
mmc/
card/
block.c /*描述了一个块设备驱动;*/
queue.c /*上述块设备驱动的请求队列等的实现;*/
这边主要分析块设备层中的block.c 和queue.c
module_init(mmc_blk_init);
mmc_blk_init(void);
(1)register_blkdev 注册mmc驱动
(2)mmc_register_driver(&mmc_driver) 向总线注册为mmc_bus_driver;
调用 core/bus.c brv->drv.bus = &mmc_bus_type;
driver/base/driver.c
注册设备驱动程序,搜寻drv对应的设备并关联;
详述下struct mmc_driver mmc_driver
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.suspend = mmc_blk_suspend,
.resume = mmc_blk_resume,
static int mmc_blk_probe(struct mmc_card *card);这个函数主要完成的任务
A,检查命令集
B,mmc_blk_alloc 分配card
a,find_first_zero_bit 寻找第一个值为0的区域
b,kzalloc 分配数据包
c,判读卡是否为只读卡 mmc_blk_readonly
d,分配gendisk
e,自旋锁初始化spin_lock_init(&md->lock)
f,mmc_init_queue 请求队列初始化 下文中重点介绍
g,md->queue.issue_fn = mmc_blk_issue_rq下文中重点介绍
h,初始化md->disk结构体 其中fops = &mmc_bops
其中f:
是在queue.c中实现的
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock);
完成:
1.mq->queue = blk_init_queue(mmc_request, lock);初始化将request函数与队列绑定
2.blk_queue_prep_rq(mq->queue, mmc_prep_request);
命令预处理,为驱动程序在返回evl_next_request之前,提供检查和预处理请求的机制
3.blk_queue_ordered(mq->queue, QUEUE_ORDERED_DRAIN, NULL);/barrier request屏障请求,防止重新组合产生的错误,设置标准后,保证请求的数据及时写入到介质。
4.mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd");最后设置了一个内核线程,线程关联的函数是mmc_queue_thread,这个很重要,待会分析。
C,接下来调用 mmc_blk_set_blksize来设置block的长度为512。
D,一切都准备好了以后激活磁 盘:add_disk(md->disk);
IO请求
mq->queue = blk_init_queue(mmc_request, lock)
详细分析
1.req = blk_fetch_request(q)
2.wake_up_process(mq->thread);
通过什么来进行数据块的传输呢这里就是通过唤醒
mq->thread线程来实现的这个线程实际上就是mmc_queue_thread函数
a,获取请求
b,mq->issue_fn(mq, req);
看看
mq->issue_fn(mq, req);干了什么
md->queue.issue_fn = mmc_blk_issue_rq;
还回到block.c
static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req);
scatterlist 数据缓冲区
mmc_wait_for_req(card->host, &mrq);
位于core/core.c
/**
* mmc_wait_for_req - start a request and wait for completion
* @host: MMC host to start command
* @mrq: MMC request to start
*
* Start a new MMC custom command request for a host, and wait
* for the command to complete. Does not attempt to parse the
* response.
*/
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
{
DECLARE_COMPLETION_ONSTACK(complete);
mrq->done_data = &complete;
mrq->done = mmc_wait_done;
mmc_start_request(host, mrq);
wait_for_completion(&complete);
}
作用就是:向host发送请求,mmc_start_request(host, mrq);的最后一句就是 host->ops->request(host, mrq),这样就和我们msmsdcc的驱动程序的定义的操作集合中的request联系起来了,由于这次cmd—>data成员不再为空,所以启动的是数据传输。
在mmc子系统学习笔记七 基于mmc总线的驱动代码研究中我们看到了最后添加mmc_card设备到mmc总线上,那么在添加之前总线控制器发现了有卡插进了卡槽,做了什么操作,构造mmc card这个设备呢?
还回到host下的msmsdcc-host.c文件中。
当然还是msmsdcc_probe函数,在上面5.2中我已经简要的分析了,但之前分析的时候,故意把一些重要的问题留到后面,也就是这儿。
在这个函数的最后,调用了mmc_add_host(mmc);
那这个mmc_add_host(mmc);干了什么么?
发现了mmc_start_host(host)函数,然后又调用了mmc_detect_change(host, 0);
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
mmc_schedule_delayed_work(&host->detect, delay);
}
mmc_schedule_delayed_work(&host->detect, delay);/*延时函数,在probe最后的定时器负责polling,一旦host寄存器发生变化就调用延时的工作函数mmc_rescan,那现在假设polling的时候发现了host那块的寄存器状态改变,延时函数mmc_rescan做了什么?*/
初始化插入的设备
无论什么卡设备统一发送cmd0和cmd8
在mmc_go_idle(host);发送cmd0
cmd 0复位。
在mmc_send_if_cond(host, host->ocr_avail);发送cmd8
cmd 8 是否是sd 2.0即高容量
首先判断是不是sdio设备
err = mmc_send_io_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sdio(host, ocr))
mmc_power_off(host);
extend_wakelock = 1;
goto out;
}
mmc_send_io_op_cond(),这个函数是cmd5,ocr为0,即arg=0,回应是r4,正确的response则继续mmc_attach_sdio(host, ocr))需要完成以下工作:
(1)host->ocr = mmc_select_voltage(host, ocr);选择ocr中主机支持,且为最低的电压
(2)告诉卡,主机的的工作电压,这一步仍发送cmd5,但是arg有参数了,即是主机的选中的工作电压
(3)分配mmc card,并开始初始化
(4)发布rca,cmd3命令
(5)读取cia中通用寄存器cccr,使用地址0x00来完成操作function0,注CIA包含 CCCR+FBR+CIS
(6)最后err = mmc_add_card(host->card);
是不是sd设备
和上述差不多,可自行分析,注意获取ocr的命令此时变成acmd41.
十
那么我们看下这个中断上个怎么工作的?
中断处理这边就不再赘述了。稍微提下的是,mmc这块中断处理的下半部采用的是工作队列(workqueue)。
mmc中断上半部
注册中断处理程序
代码:仍然在msmsdcc_probe函数中
ret = request_irq(host->stat_irq,
msmsdcc_platform_status_irq,
irqflags,
DRIVER_NAME " (slot)",
host);
参数分别是:irq号,中断处理程序(上半部),中断标识号这个是在前面定义过了IRQF_SHARED即共享中断号,后面两个参数是因为是共享中断而指定的该中断相关标记
看看实际的中断处理程序(ISR)msmsdcc_platform_status_irq()
static irqreturn_t
msmsdcc_platform_status_irq(int irq, void *dev_id)
{
struct msmsdcc_host *host = dev_id;
printk(KERN_DEBUG "%s: %d\n", __func__, irq);
msmsdcc_check_status((unsigned long) host);
return IRQ_HANDLED;
}主要是完成msmsdcc_check_status,然后在这个函数中调用mmc_detect_change
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
mmc_schedule_delayed_work(&host->detect, delay);
}最后这个是下半部的处理函数了
mmc中断下半部
工作者线程是在自己定义,未用默认的envents,而是自己定义的工作者线程,在core.c文件中,定义的:
static struct workqueue_struct *workqueue; 定义工作者线程
workqueue = create_singlethread_workqueue("kmmcd");创建所有的工作者线程,每一个cpu一个
对应于7.1的状态中断的请求的上半部,其下半部任务为
mmc_schedule_delayed_work(&host->detect, delay);
等到延时delay个节拍就执行mmc_rescan,关于detect是怎么和mmc_rescan请看mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev);函数
关于另两个irq
cmd_irq,数据pio_irq,请自行分析。
十一
if (stat_irqres && !(stat_irqres->flags & IORESOURCE_DISABLED)) {
.....
else {
init_timer(&host->timer); //初始化定时器
host->timer.data = (unsigned long)host; //超时处理函数的参数
host->timer.function = msmsdcc_check_status;//超时处理函数
host->timer.expires = jiffies + HZ;//定义超时
add_timer(&host->timer);//激活该定时器
}
这边定义了一个定时器,名字为timer;
系统现在的时间为jiffies,当到了这个时间jiffies + HZ(即刚刚的时间加1s,超时1s)就执行msmsdcc_check_status((unsigned long)host);
这个过程是和笔记九之后是一样的。