5.1.5. 设计说明¶
5.1.5.2. 模块架构¶
Linux中提供了MMC子系统,该子系统负责抽象一个块设备提供给通用块层使用,从整个软件的角度来看,架构如下:
 
图 5.18 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控制器驱动通过对控制器进行操作完成核心层的请求,控制器驱动也是实现和外设进行通信的软件最底层驱动,该层驱动根据厂商不同而不同,D211的SDMC模块的控制器驱动通过 module_platform_driver(artinchip_mmc_aic_pltfm_driver)实现,其主要步骤如下:
- 使能时钟(artinchip_mmc_clk_enable) 
- 初始化计时机制,该机制实现发送命令和数据传输的timeout机制(timer_setup) 
- 初始化保护锁(spin_lock_init) 
- 初始化tasklet,在驱动中很多流程的处理会在tasklet中(tasklet_init) 
- 初始化DMA(artinchip_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设备,主要流程如下:
 
图 5.19 通用块设备的访问流程¶
- 在用户空间,应用程序调用read/write接口 
- 然后通过虚拟文件系统 
- 调用通用块层的接口对块设备进行IO请求 
- IO调度层负责使用特定算法对这些请求进行调度 
- 块设备驱动层调用具体的块设备接口访问设备 
5.1.5.3.2.2. MMC 子系统请求处理流程¶
MMC子系统被抽象成一个块设备,通用块层将IO请求调用到具体的块设备驱动层,在MMC块设备驱动中的请求处理流程如下:
 
图 5.20 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 (即artinchip_mmc_request)
5.1.5.3.2.3. Host 层驱动请求处理流程¶
在访问MMC外设时,都是通过发送CMD的方式,在host层驱动中需要通过操作controller去实现core层的request,主要流程如下:
 
图 5.21 Host 层驱动的请求处理流程¶
- 检测卡设备,需要判断当前卡设备是否被拔出 
- 判断传输状态,如果当前传输状态不是idle,那么将会将该请求放在请求队列里 
- 处理data,如果当前请求需要处理数据,则将数据先行处理,如果不需要处理数据则跳过 
- 发送CMD,解析请求中的CMD和参数,将其写入寄存器,然后触发CMD的发送 
- 中断处理,在发送完CMD后,后续的工作需要等待中断的触发,在中断处理中会对外设返回的数据和状态进行处理 
- 如果需要,发送stop命令,结束该次传输 
Host层函数调用关系:
artinchip_mmc_request
|--artinchip_mmc_get_cd
|--artinchip_mmc_queue_request
|--artinchip_mmc_start_request
        |--artinchip_mmc_prepare_command
        |--artinchip_mmc_submit_data
        |--artinchip_mmc_start_command
        |--artinchip_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中调用函数artinchip_mmc_command_complete,该函数中会读取外部SD设备返回给控制器的response数据,再根据当前的CMD状态对CMD的结果进行赋值 
- 如果使用的是PIO的方式,当TX/RX FIFO请求中断响应后,会调用对应的函数对FIFO进行读写操作。 
- 若是采用DMA的方式,则在中断函数中读取内部DMA状态,然后释放DMA传输的资源,再根据DMA的状态,在tsaklet中调用artinchip_mmc_data_complete函数,该函数会根据目前的数据传输情况对传输结果进行赋值 
 
 
- 错误处理。目前SDMC支持的错误中断类型有
- CMD错误中断:
- 当出现CMD错误中断后,在中断处理函数中,会将当前中断寄存器的状态保存,然后设置cmd的状态为已经完成,最后在artinchip_mmc_command_complete函数中将CMD的结果进行赋值。 
 
- DATA错误中断:
- 当出现DATA中断后,在中断处理函数中会将当前的中断状态保存,然后设置data的状态为DATA错误,然后切入到tasklet函数中,在该函数中,根据DATA错误的状态,停止dma,如果有需求,就发送stop命令 
 
 
 
5.1.5.4. 数据结构设计¶
5.1.5.4.1. enum artinchip_mmc_state¶
定义了SDMC控制器的几个状态:
enum artinchip_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. artinchip_mmc¶
记录了SDMC控制器的设备信息:
struct artinchip_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 artinchip_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 artinchip_mmc_state    state;
    struct list_head    queue;
    u32         bus_hz;
    u32         current_speed;
    u32         fifoth_val;
    u16         verid;
    struct device       *dev;
    struct artinchip_mmc_board  *pdata;
    const struct artinchip_mmc_drv_data *drv_data;
    void            *priv;
    struct clk      *biu_clk;
    struct clk      *ciu_clk;
    struct artinchip_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. artinchip_mmc_board¶
记录了Board相关的配置信息:
struct artinchip_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 artinchip_mmc_dma_ops *dma_ops;
    struct dma_pdata *data;
};
5.1.5.4.4. artinchip_mmc_slot¶
记录了slot相关的配置信息:
struct artinchip_mmc_slot {
    struct mmc_host     *mmc;
    struct artinchip_mmc        *host;
    u32         ctype;
    struct mmc_request  *mrq;
    struct list_head    queue_node;
    unsigned int        clock;
    unsigned int        __clk_old;
    unsigned long       flags;
#define ARTINCHIP_MMC_CARD_PRESENT  0
#define ARTINCHIP_MMC_CARD_NEED_INIT    1
#define ARTINCHIP_MMC_CARD_NO_LOW_PWR   2
#define ARTINCHIP_MMC_CARD_NO_USE_HOLD 3
#define ARTINCHIP_MMC_CARD_NEEDS_POLL   4
};
5.1.5.4.5. artinchip_mmc_drv_data¶
记录了AIC SDMC驱动的特有数据:
struct artinchip_mmc_drv_data {
    unsigned long   *caps;
    u32     num_caps;
    int     (*init)(struct artinchip_mmc *host);
    int     (*parse_dt)(struct artinchip_mmc *host);
    int     (*execute_tuning)(struct artinchip_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 artinchip_mmc_ops = {
    .request        = artinchip_mmc_request,
    .pre_req        = artinchip_mmc_pre_req,
    .post_req       = artinchip_mmc_post_req,
    .set_ios        = artinchip_mmc_set_ios,
    .get_ro         = artinchip_mmc_get_ro,
    .get_cd         = artinchip_mmc_get_cd,
    .hw_reset               = artinchip_mmc_hw_reset,
    .enable_sdio_irq    = artinchip_mmc_enable_sdio_irq,
    .ack_sdio_irq       = artinchip_mmc_ack_sdio_irq,
    .execute_tuning     = artinchip_mmc_execute_tuning,
    .card_busy      = artinchip_mmc_card_busy,
    .start_signal_voltage_switch = artinchip_mmc_switch_voltage,
    .init_card      = artinchip_mmc_init_card,
};
5.1.5.5.1. artinchip_mmc_request¶
| 函数原型 | static void artinchip_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) | 
|---|---|
| 功能说明 | 读取当前的RTC时间 | 
| 功能说明 | 操作寄存器实现request | 
| 参数定义 | mmc: MMC设备指针 mrq:请求的参数和资源 | 
| 返回值 | 无 | 
| 注意事项 | 
5.1.5.5.2. artinchip_mmc_pre_req¶
| 函数原型 | static void artinchip_mmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq) | 
|---|---|
| 功能说明 | 准备下一个request | 
| 参数定义 | mmc: MMC设备指针 mrq:请求的参数和资源 | 
| 返回值 | 无 | 
| 注意事项 | 在准备下一个请求前,一般需要调用artinchip_mmc_post_request | 
5.1.5.5.3. artinchip_mmc_post_req¶
| 函数原型 | static void artinchip_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. artinchip_mmc_set_ios¶
| 函数原型 | static void artinchip_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) | 
|---|---|
| 功能说明 | 对设备的位宽、DDR模式、clock、power模式等进行配置 | 
| 参数定义 | mmc: MMC设备指针 ios:配置参数 | 
| 返回值 | 无 | 
| 注意事项 | 
5.1.5.5.5. artinchip_mmc_get_cd¶
| 函数原型 | static int artinchip_mmc_get_cd(struct mmc_host *mmc) | 
|---|---|
| 功能说明 | 探测外部SD设备 | 
| 参数定义 | mmc: MMC设备指针 | 
| 返回值 | 执行成功则返回1 | 
| 注意事项 | 
5.1.5.5.6. artinchip_mmc_hw_reset¶
| 函数原型 | static void artinchip_mmc_hw_reset(struct mmc_host *mmc) | 
|---|---|
| 功能说明 | 对MMC控制器、DMA等进行一次reset | 
| 参数定义 | mmc: MMC设备指针 | 
| 返回值 | 无 | 
| 注意事项 | 
5.1.5.5.7. artinchip_mmc_enable_sdio_irq¶
| 函数原型 | static void artinchip_mmc_enable_sdio_irq(struct mmc_host *mmc, int enb) | 
|---|---|
| 功能说明 | 使能或者关闭MMC控制器的中断 | 
| 参数定义 | mmc: MMC设备指针 enb:使能开关 | 
| 返回值 | 无 | 
| 注意事项 | 
5.1.5.5.8. artinchip_mmc_ack_sdio_irq¶
| 函数原型 | static void artinchip_mmc_ack_sdio_irq(struct mmc_host *mmc) | 
|---|---|
| 功能说明 | 打开MMC控制器的中断 | 
| 参数定义 | mmc: MMC设备指针 | 
| 返回值 | 无 | 
| 注意事项 | 
5.1.5.5.9. artinchip_mmc_execute_tuning¶
| 函数原型 | static int artinchip_mmc_execute_tuning(struct mmc_host *mmc, u32 opcode) | 
|---|---|
| 功能说明 | MMC的tuning功能接口 | 
| 参数定义 | mmc: MMC设备指针 opcode:tuning命令码 | 
| 返回值 | 无 | 
| 注意事项 | 
5.1.5.5.10. artinchip_mmc_card_busy¶
| 函数原型 | static int artinchip_mmc_card_busy(struct mmc_host *mmc) | 
|---|---|
| 功能说明 | 查看MMC设备是否处于Busy状态 | 
| 参数定义 | mmc: MMC设备指针 | 
| 返回值 | 若处于idle状态返回0,busy则返回1 | 
| 注意事项 | 
5.1.5.5.11. artinchip_mmc_switch_voltage¶
| 函数原型 | static int artinchip_mmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios) | 
|---|---|
| 功能说明 | 设置MMC设备的工作电压 | 
| 参数定义 | mmc: MMC设备指针 ios:设置参数 | 
| 返回值 | 0,成功; < 0,失败 | 
| 注意事项 | 
5.1.5.5.12. artinchip_mmc_init_card¶
| 函数原型 | static void artinchip_mmc_init_card(struct mmc_host *mmc, struct mmc_card *card) | 
|---|---|
| 功能说明 | 初始化外部mmc设备 | 
| 参数定义 | mmc: MMC设备指针 card:card设备指针 | 
| 返回值 | 无 | 
| 注意事项 |