5.1.5. 设计说明

5.1.5.1. 源码说明

源代码位于 drivers/mmc/host/:

  • zx-mmc.c,SDMC驱动实现

  • zx-mmc.h,SDMC的寄存器、数据结构定义

5.1.5.2. 模块架构

Linux中提供了MMC子系统,该子系统负责抽象一个块设备提供给通用块层使用,从整个软件的角度来看,架构如下:

../../../_images/sw_system2.png

图 5.23 Linux MMC子系统架构图

其中:

  1. 对用户而言,MMC card层提供了一种块设备,和其他块设备使用方法类似

  2. MMC子系统的核心层的功能有:
    • 对上层请求的处理,其中包括将请求转化为符合MMC协议的逻辑实现

    • 对控制器驱动进行管理

    • 将外部MMC设备抽象并进行管理

  3. 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)完成注册和初始化的操作,主要步骤如下:

  1. 注册总线(bus_register)

  2. 将块设备名”mmc”和主设备注册到块层中(register_blkdev)

  3. 将mmc_driver设备驱动注册到驱动模型中(mmc_register_driver)

  4. 块设备的初始化及磁盘分区的注册(mmc_blk_probe)

5.1.5.3.1.2. MMC 子系统核心初始化

MMC子系统的核心层负责处理block下达的请求,其中关于MMC协议的逻辑主要在此实现,通过subsys_initcall(mmc_init)完成初始化,其步骤如下:

  1. MMC类型总线注册,(mmc_register_bus)

  2. 为控制器设备注册一个类,(mmc_register_host_class)

  3. SDIO类型总线类型注册,(sdio_register_b)

5.1.5.3.1.3. card 设备注册与初始化

MMC驱动的访问对象为外设,在子系统中会将外设抽象成一个card设备,在每次探测外设的时候都会判断该设备是否需要被注册,所以card设备注册介绍分为探测时机和注册过程两部分:

  1. 探测时机:
    • mmc控制器启动时

    • 热插拔时

    • mmc控制器从suspend转为resume时

    • 上述三种情况均会进行一次探测,都会调用到函数mmc_detect_change

  2. 注册过程:

    在探测时调用的函数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)实现,其主要步骤如下:

  1. 使能时钟(zx_mmc_clk_enable)

  2. 初始化计时机制,该机制实现发送命令和数据传输的timeout机制(timer_setup)

  3. 初始化保护锁(spin_lock_init)

  4. 初始化tasklet,在驱动中很多流程的处理会在tasklet中(tasklet_init)

  5. 初始化DMA(zx_mmc_init_dma)

  6. 中断初始化和注册(devm_request_irq)

  7. 注册具体的控制器(mmc_alloc_host + mmc_add_host)

  8. 初始化具体控制器,包括接口函数、工作电压、传输能力等

5.1.5.3.2. 请求处理流程

对于应用程序,通过读写的接口访问文件系统,文件系统访问块设备,MMC设备在内核中被注册为一个块设备,当读写的操作传入到MMC块设备后,通过MMC子系统处理相关操作,对于MMC子系统其处理皆以请求的方式实现。

5.1.5.3.2.1. 块层以上系统读写调用流程

在块层以上,通常是用户空间调用读写接口访问MMC设备,主要流程如下:

../../../_images/blkdev_access_flow.png

图 5.24 通用块设备的访问流程

  1. 在用户空间,应用程序调用read/write接口

  2. 然后通过虚拟文件系统

  3. 调用通用块层的接口对块设备进行IO请求

  4. IO调度层负责使用特定算法对这些请求进行调度

  5. 块设备驱动层调用具体的块设备接口访问设备

5.1.5.3.2.2. MMC 子系统请求处理流程

MMC子系统被抽象成一个块设备,通用块层将IO请求调用到具体的块设备驱动层,在MMC块设备驱动中的请求处理流程如下:

../../../_images/mmc_request_flow.png

图 5.25 MMC数据请求的处理流程

  1. 由于会有多个请求,在block中以队列的形式处理,在请求到达时,唤醒mmc_queue_thread

  2. 调用block的请求处理,发出request

  3. block的request会由core来实现

  4. 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,主要流程如下:

../../../_images/host_request_flow.png

图 5.26 Host 层驱动的请求处理流程

  1. 检测卡设备,需要判断当前卡设备是否被拔出

  2. 判断传输状态,如果当前传输状态不是idle,那么将会将该请求放在请求队列里

  3. 处理data,如果当前请求需要处理数据,则将数据先行处理,如果不需要处理数据则跳过

  4. 发送CMD,解析请求中的CMD和参数,将其写入寄存器,然后触发CMD的发送

  5. 中断处理,在发送完CMD后,后续的工作需要等待中断的触发,在中断处理中会对外设返回的数据和状态进行处理

  6. 如果需要,发送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中,并且基于一些状态变量来控制处理的流程。

  1. 状态变量。在流程的控制上,主要通过几个状态变量来控制:
    • host->state:表示当前的操作状态,例如发送数据,发送CMD等等

    • host->pending_events:当前中断发生的状态

    • host->completed_events:当前完成的状态,例如CMD完成,DATA完成等

    • host->cmd_status:发送CMD时中断的状态

    • host->data_status:传输数据时中断的状态

  2. 传输处理
    • 当CMD发送完成中断触发后,会在tasklet中调用函数zx_mmc_command_complete,该函数中会读取外部SD设备返回给控制器的response数据,再根据当前的CMD状态对CMD的结果进行赋值

    • 如果使用的是PIO的方式,当TX/RX FIFO请求中断响应后,会调用对应的函数对FIFO进行读写操作。

    • 若是采用DMA的方式,则在中断函数中读取内部DMA状态,然后释放DMA传输的资源,再根据DMA的状态,在tsaklet中调用zx_mmc_data_complete函数,该函数会根据目前的数据传输情况对传输结果进行赋值

  3. 错误处理。目前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设备指针

返回值

注意事项