5.1.5. 设计说明
5.1.5.1. 源码说明
源代码位于 drivers/mmc/host/:
zx-mmc.c,SDMC驱动实现
zx-mmc.h,SDMC的寄存器、数据结构定义
5.1.5.2. 模块架构
Linux中提供了MMC子系统,该子系统负责抽象一个块设备提供给通用块层使用,从整个软件的角度来看,架构如下:
其中:
对用户而言,MMC card层提供了一种块设备,和其他块设备使用方法类似
- MMC子系统的核心层的功能有:
对上层请求的处理,其中包括将请求转化为符合MMC协议的逻辑实现
对控制器驱动进行管理
将外部MMC设备抽象并进行管理
AIC SDMC控制器驱动:负责通过对寄存器的操作实现MMC子系统传来的请求
5.1.5.3. 关键流程设计
5.1.5.3.1. 初始化流程
MMC子系统的初始化包括MMC块设备、MMC子系统、MMC控制器驱动、card设备等几条线,初始化顺序:
最先进行的是MMC核心初始化
MC控制器驱动初始化完成后才会对card设备进行初始化
MMC块设备初始化没有严格的先后顺序
5.1.5.3.1.1. MMC 块设备驱动初始化
MMC在使用中,会将其抽象成一个块设备挂载到通用块层当中,通过module_init(mmc_blk_init)完成注册和初始化的操作,主要步骤如下:
注册总线(bus_register)
将块设备名”mmc”和主设备注册到块层中(register_blkdev)
将mmc_driver设备驱动注册到驱动模型中(mmc_register_driver)
块设备的初始化及磁盘分区的注册(mmc_blk_probe)
5.1.5.3.1.2. MMC 子系统核心初始化
MMC子系统的核心层负责处理block下达的请求,其中关于MMC协议的逻辑主要在此实现,通过subsys_initcall(mmc_init)完成初始化,其步骤如下:
MMC类型总线注册,(mmc_register_bus)
为控制器设备注册一个类,(mmc_register_host_class)
SDIO类型总线类型注册,(sdio_register_b)
5.1.5.3.1.3. card 设备注册与初始化
MMC驱动的访问对象为外设,在子系统中会将外设抽象成一个card设备,在每次探测外设的时候都会判断该设备是否需要被注册,所以card设备注册介绍分为探测时机和注册过程两部分:
- 探测时机:
mmc控制器启动时
热插拔时
mmc控制器从suspend转为resume时
上述三种情况均会进行一次探测,都会调用到函数mmc_detect_change
- 注册过程:
在探测时调用的函数mmc_detect_change,该函数会调用card设备的注册函数mmc_rescan,以SD卡为例,其注册和初始化过程如下:
判断当前卡是否被注册
若卡已经注册,则确认卡是否存在,存在则提前跳出,若不存在则释放相关资源
若卡未注册,则启动控制器进行卡的初始化步骤
为控制器绑定具体总线的操作函数(mmc_attach_bus(host, &mmc_sd_ops))
适配卡的工作电压(mmc_select_voltage)
根据MMC协议初始化卡,使卡进入传输模式化(mmc_sd_init_card)
注册卡设备(mmc_add_card)
5.1.5.3.1.4. 控制器驱动注册与初始化
MMC控制器驱动通过对控制器进行操作完成核心层的请求,控制器驱动也是实现和外设进行通信的软件最底层驱动,该层驱动根据厂商不同而不同,M4的SDMC模块的控制器驱动通过 module_platform_driver(zx_mmc_aic_pltfm_driver)实现,其主要步骤如下:
使能时钟(zx_mmc_clk_enable)
初始化计时机制,该机制实现发送命令和数据传输的timeout机制(timer_setup)
初始化保护锁(spin_lock_init)
初始化tasklet,在驱动中很多流程的处理会在tasklet中(tasklet_init)
初始化DMA(zx_mmc_init_dma)
中断初始化和注册(devm_request_irq)
注册具体的控制器(mmc_alloc_host + mmc_add_host)
初始化具体控制器,包括接口函数、工作电压、传输能力等
5.1.5.3.2. 请求处理流程
对于应用程序,通过读写的接口访问文件系统,文件系统访问块设备,MMC设备在内核中被注册为一个块设备,当读写的操作传入到MMC块设备后,通过MMC子系统处理相关操作,对于MMC子系统其处理皆以请求的方式实现。
5.1.5.3.2.1. 块层以上系统读写调用流程
在块层以上,通常是用户空间调用读写接口访问MMC设备,主要流程如下:
在用户空间,应用程序调用read/write接口
然后通过虚拟文件系统
调用通用块层的接口对块设备进行IO请求
IO调度层负责使用特定算法对这些请求进行调度
块设备驱动层调用具体的块设备接口访问设备
5.1.5.3.2.2. MMC 子系统请求处理流程
MMC子系统被抽象成一个块设备,通用块层将IO请求调用到具体的块设备驱动层,在MMC块设备驱动中的请求处理流程如下:
由于会有多个请求,在block中以队列的形式处理,在请求到达时,唤醒mmc_queue_thread
调用block的请求处理,发出request
block的request会由core来实现
core层会根据当前host驱动调用对应host的ops中的request接口去操作controller
函数调用关系:
mmc_wait_for_req
|--__mmc_start_req
|--init_completion
|--mmc_start_request
|--mmc_mrq_prep
|--__mmc_start_request
|--trace_mmc_request_start
|--host->ops->request (即zx_mmc_request)
5.1.5.3.2.3. Host 层驱动请求处理流程
在访问MMC外设时,都是通过发送CMD的方式,在host层驱动中需要通过操作controller去实现core层的request,主要流程如下:
检测卡设备,需要判断当前卡设备是否被拔出
判断传输状态,如果当前传输状态不是idle,那么将会将该请求放在请求队列里
处理data,如果当前请求需要处理数据,则将数据先行处理,如果不需要处理数据则跳过
发送CMD,解析请求中的CMD和参数,将其写入寄存器,然后触发CMD的发送
中断处理,在发送完CMD后,后续的工作需要等待中断的触发,在中断处理中会对外设返回的数据和状态进行处理
如果需要,发送stop命令,结束该次传输
Host层函数调用关系:
zx_mmc_request
|--zx_mmc_get_cd
|--zx_mmc_queue_request
|--zx_mmc_start_request
|--zx_mmc_prepare_command
|--zx_mmc_submit_data
|--zx_mmc_start_command
|--zx_mmc_prep_stop_abort
5.1.5.3.3. 中断处理流程
在触发中断后,需要根据目前的中断状态进行处理,其中主要为错误处理和传输处理,这些处理主要在tasklet中,并且基于一些状态变量来控制处理的流程。
- 状态变量。在流程的控制上,主要通过几个状态变量来控制:
host->state:表示当前的操作状态,例如发送数据,发送CMD等等
host->pending_events:当前中断发生的状态
host->completed_events:当前完成的状态,例如CMD完成,DATA完成等
host->cmd_status:发送CMD时中断的状态
host->data_status:传输数据时中断的状态
- 传输处理
当CMD发送完成中断触发后,会在tasklet中调用函数zx_mmc_command_complete,该函数中会读取外部SD设备返回给控制器的response数据,再根据当前的CMD状态对CMD的结果进行赋值
如果使用的是PIO的方式,当TX/RX FIFO请求中断响应后,会调用对应的函数对FIFO进行读写操作。
若是采用DMA的方式,则在中断函数中读取内部DMA状态,然后释放DMA传输的资源,再根据DMA的状态,在tsaklet中调用zx_mmc_data_complete函数,该函数会根据目前的数据传输情况对传输结果进行赋值
- 错误处理。目前SDMC支持的错误中断类型有
- CMD错误中断:
当出现CMD错误中断后,在中断处理函数中,会将当前中断寄存器的状态保存,然后设置cmd的状态为已经完成,最后在zx_mmc_command_complete函数中将CMD的结果进行赋值。
- DATA错误中断:
当出现DATA中断后,在中断处理函数中会将当前的中断状态保存,然后设置data的状态为DATA错误,然后切入到tasklet函数中,在该函数中,根据DATA错误的状态,停止dma,如果有需求,就发送stop命令
5.1.5.4. 数据结构设计
5.1.5.4.1. enum zx_mmc_state
定义了SDMC控制器的几个状态:
enum zx_mmc_state {
STATE_IDLE = 0,
STATE_SENDING_CMD,
STATE_SENDING_DATA,
STATE_DATA_BUSY,
STATE_SENDING_STOP,
STATE_DATA_ERROR,
STATE_SENDING_CMD11,
STATE_WAITING_CMD11_DONE,
};
5.1.5.4.2. zx_mmc
记录了SDMC控制器的设备信息:
struct zx_mmc {
spinlock_t lock;
spinlock_t irq_lock;
void __iomem *regs;
void __iomem *fifo_reg;
bool wm_aligned;
struct scatterlist *sg;
struct sg_mapping_iter sg_miter;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
struct mmc_command stop_abort;
unsigned int prev_blksz;
unsigned char timing;
/* DMA interface members*/
bool use_dma;
bool using_dma;
dma_addr_t sg_dma;
void *sg_cpu;
const struct zx_mmc_dma_ops *dma_ops;
/* For idmac */
unsigned int ring_size;
/* Registers's physical base address */
resource_size_t phy_regs;
u32 cmd_status;
u32 data_status;
u32 stop_cmdr;
u32 dir_status;
struct tasklet_struct tasklet;
unsigned long pending_events;
unsigned long completed_events;
enum zx_mmc_state state;
struct list_head queue;
u32 bus_hz;
u32 current_speed;
u32 fifoth_val;
u16 verid;
struct device *dev;
struct zx_mmc_board *pdata;
const struct zx_mmc_drv_data *drv_data;
void *priv;
struct clk *biu_clk;
struct clk *ciu_clk;
struct zx_mmc_slot *slot;
/* FIFO push and pull */
int fifo_depth;
int data_shift;
u8 part_buf_start;
u8 part_buf_count;
enum data_width data_width;
union {
u16 part_buf16;
u32 part_buf32;
u64 part_buf;
};
bool vqmmc_enabled;
unsigned long irq_flags; /* IRQ flags */
int irq;
struct timer_list cmd11_timer;
struct timer_list cto_timer;
struct timer_list dto_timer;
};
5.1.5.4.3. zx_mmc_board
记录了Board相关的配置信息:
struct zx_mmc_board {
unsigned int bus_hz; /* Clock speed at the cclk_in pad */
u32 caps; /* Capabilities */
u32 caps2; /* More capabilities */
u32 pm_caps; /* PM capabilities */
/*
* Override fifo depth. If 0, autodetect it from the FIFOTH register,
* but note that this may not be reliable after a bootloader has used
* it.
*/
unsigned int fifo_depth;
/* delay in mS before detecting cards after interrupt */
u32 detect_delay_ms;
struct reset_control *rstc;
struct zx_mmc_dma_ops *dma_ops;
struct dma_pdata *data;
};
5.1.5.4.4. zx_mmc_slot
记录了slot相关的配置信息:
struct zx_mmc_slot {
struct mmc_host *mmc;
struct zx_mmc *host;
u32 ctype;
struct mmc_request *mrq;
struct list_head queue_node;
unsigned int clock;
unsigned int __clk_old;
unsigned long flags;
#define ZX_MMC_CARD_PRESENT 0
#define ZX_MMC_CARD_NEED_INIT 1
#define ZX_MMC_CARD_NO_LOW_PWR 2
#define ZX_MMC_CARD_NO_USE_HOLD 3
#define ZX_MMC_CARD_NEEDS_POLL 4
};
5.1.5.4.5. zx_mmc_drv_data
记录了AIC SDMC驱动的特有数据:
struct zx_mmc_drv_data {
unsigned long *caps;
u32 num_caps;
int (*init)(struct zx_mmc *host);
int (*parse_dt)(struct zx_mmc *host);
int (*execute_tuning)(struct zx_mmc_slot *slot, u32 opcode);
int (*switch_voltage)(struct mmc_host *mmc,
struct mmc_ios *ios);
};
5.1.5.5. 接口设计
以下接口皆为MMC子系统所需要的标准接口,通过mmc_host_ops注册到MMC子系统。
static const struct mmc_host_ops zx_mmc_ops = {
.request = zx_mmc_request,
.pre_req = zx_mmc_pre_req,
.post_req = zx_mmc_post_req,
.set_ios = zx_mmc_set_ios,
.get_ro = zx_mmc_get_ro,
.get_cd = zx_mmc_get_cd,
.hw_reset = zx_mmc_hw_reset,
.enable_sdio_irq = zx_mmc_enable_sdio_irq,
.ack_sdio_irq = zx_mmc_ack_sdio_irq,
.execute_tuning = zx_mmc_execute_tuning,
.card_busy = zx_mmc_card_busy,
.start_signal_voltage_switch = zx_mmc_switch_voltage,
.init_card = zx_mmc_init_card,
};
5.1.5.5.1. zx_mmc_request
函数原型 |
static void zx_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) |
---|---|
功能说明 |
读取当前的RTC时间 |
功能说明 |
操作寄存器实现request |
参数定义 |
mmc: MMC设备指针
mrq:请求的参数和资源
|
返回值 |
无 |
注意事项 |
5.1.5.5.2. zx_mmc_pre_req
函数原型 |
static void zx_mmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq) |
---|---|
功能说明 |
准备下一个request |
参数定义 |
mmc: MMC设备指针
mrq:请求的参数和资源
|
返回值 |
无 |
注意事项 |
在准备下一个请求前,一般需要调用zx_mmc_post_request |
5.1.5.5.3. zx_mmc_post_req
函数原型 |
static void zx_mmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq, int err) |
---|---|
功能说明 |
送出一个request |
参数定义 |
mmc: MMC设备指针
mrq:请求的参数和资源
err:如果非零,需要清理掉pre_req()中申请的资源
|
返回值 |
无 |
注意事项 |
5.1.5.5.4. zx_mmc_set_ios
函数原型 |
static void zx_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) |
---|---|
功能说明 |
对设备的位宽、DDR模式、clock、power模式等进行配置 |
参数定义 |
mmc: MMC设备指针
ios:配置参数
|
返回值 |
无 |
注意事项 |
5.1.5.5.5. zx_mmc_get_cd
函数原型 |
static int zx_mmc_get_cd(struct mmc_host *mmc) |
---|---|
功能说明 |
探测外部SD设备 |
参数定义 |
mmc: MMC设备指针 |
返回值 |
执行成功则返回1 |
注意事项 |
5.1.5.5.6. zx_mmc_hw_reset
函数原型 |
static void zx_mmc_hw_reset(struct mmc_host *mmc) |
---|---|
功能说明 |
对MMC控制器、DMA等进行一次reset |
参数定义 |
mmc: MMC设备指针 |
返回值 |
无 |
注意事项 |
5.1.5.5.7. zx_mmc_enable_sdio_irq
函数原型 |
static void zx_mmc_enable_sdio_irq(struct mmc_host *mmc, int enb) |
---|---|
功能说明 |
使能或者关闭MMC控制器的中断 |
参数定义 |
mmc: MMC设备指针
enb:使能开关
|
返回值 |
无 |
注意事项 |
5.1.5.5.8. zx_mmc_ack_sdio_irq
函数原型 |
static void zx_mmc_ack_sdio_irq(struct mmc_host *mmc) |
---|---|
功能说明 |
打开MMC控制器的中断 |
参数定义 |
mmc: MMC设备指针 |
返回值 |
无 |
注意事项 |
5.1.5.5.9. zx_mmc_execute_tuning
函数原型 |
static int zx_mmc_execute_tuning(struct mmc_host *mmc, u32 opcode) |
---|---|
功能说明 |
MMC的tuning功能接口 |
参数定义 |
mmc: MMC设备指针
opcode:tuning命令码
|
返回值 |
无 |
注意事项 |
5.1.5.5.10. zx_mmc_card_busy
函数原型 |
static int zx_mmc_card_busy(struct mmc_host *mmc) |
---|---|
功能说明 |
查看MMC设备是否处于Busy状态 |
参数定义 |
mmc: MMC设备指针 |
返回值 |
若处于idle状态返回0,busy则返回1 |
注意事项 |
5.1.5.5.11. zx_mmc_switch_voltage
函数原型 |
static int zx_mmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios) |
---|---|
功能说明 |
设置MMC设备的工作电压 |
参数定义 |
mmc: MMC设备指针
ios:设置参数
|
返回值 |
0,成功; < 0,失败 |
注意事项 |
5.1.5.5.12. zx_mmc_init_card
函数原型 |
static void zx_mmc_init_card(struct mmc_host *mmc, struct mmc_card *card) |
---|---|
功能说明 |
初始化外部mmc设备 |
参数定义 |
mmc: MMC设备指针
card:card设备指针
|
返回值 |
无 |
注意事项 |