Linux驱动——mmc host controller(九)
备注:
1. Kernel版本:5.4
2. 使用工具:Source Insight 4.0
文章目录
- Linux驱动——mmc host controller(九)
-
- 前言
- 概述
-
- host说明
- host驱动说明
- host contrller相关结构体
-
- mmc_host
- mmc_host_ops
- sunxi_mmc_host
- mmc host controller驱动实现
-
- mmc host platform driver的添加
- sunxi_mmc_probe的实现
- sunxi_mmc_ops的实现
-
- sunxi_mmc_set_ios
- sunxi_mmc_enable_sdio_irq
- sunxi_mmc_card_busy
- sunxi_mmc_request
- sunxi mmc irq中断服务函数的实现
-
- 中断申请及注册
- mmc irq 上半部——sunxi_mmc_irq
- mmc irq下半部——sunxi_mmc_handle_manual_stop
- sunxi_mmc_remove的实现
- sunxi_mmc_pm_ops的实现
前言
本文从bsp驱动工程师的角度,从mmc 控制器的概念/数据结构、mmc控制器驱动、mmc控制器驱动的实现步骤等分析如何实现一个mmc controller驱动。以allwinner的mmc host controller——sunxi-mmc.c为例,进行详解。
概述
host说明
host,也可以理解为host controller,是指mmc总线上的主机端,mmc总线的控制器,每个host controller对应一条mmc总线。
host controller会控制命令线、数据线和时钟线,从而实现mmc总线上的通讯。
上层发送mmc请求时,就是通过host controller产生对应的mmc通讯时序,下发至mmc设备,与mmc设备通讯。
注意,host的部分主要是实现card的通讯和检测,不去负责card的具体功能。
host驱动说明
1. host driver路径
平台实现mmc驱动,核心内容就是要实现host controller的驱动。
在mmc subsystem中,把host controller的驱动都放在了/drivers/mmc/host目录下。
2. host controller要做的事情
通过《mmc core浅析》一系列的说明,可以知道一个host driver要做的事情如下:
- 申请mmc_host
- 设置mmc_host的成员,包括操作集等等
- 完成host controller的初始化(哪些方面的初始化)
- 注册mmc_host,注册之后会去搜索card
补充说明:应实际的card设备(emmc card、mmc card、sd card),mmc core部分已经实现了其协议中初始化的部分,而其card设备具体功能的实现则是在card模块中进行实现。host驱动只负责card的通讯和检测等等,并不会去实现card的具体功能。!!!
host contrller相关结构体
mmc_host
struct mmc_host是mmc core由host controller抽象出来的结构体,用于代表一个mmc host控制器。
struct mmc_host {
struct device *parent; //对应的host controller的device
struct device class_dev; // mmc_host的device结构体,会挂在class/mmc_host下
int index; // 该host的索引号
const struct mmc_host_ops *ops; // 该host的操作集,由host controller设置
struct mmc_pwrseq *pwrseq; // 该host电源管理有关的操作函数集
unsigned int f_min; // 该host支持的最低频率
unsigned int f_max; // 该host支持的最大频率
unsigned int f_init; // 该host使用的初始化频率
/*
* OCR(Operating Conditions Register)
* 是MMC/SD/SDIO卡的一个32-bit的寄存器,
* 其中有些bit指明了该卡的操作电压。
* MMC host在驱动这些卡的时候,
* 需要和Host自身所支持的电压范围匹配之后,
* 才能正常操作,这就是ocr_avail的存在意义
*/
u32 ocr_avail; // 该host可支持的操作电压范围
/*
* 如果MMC host针对SDIO、SD、MMC等不同类型的卡,
* 所支持的电压范围不同的话,
* 需要通过这几个字段特别指定。
* 否则,不需要赋值(初始化为0)
*/
u32 ocr_avail_sdio; /* SDIO-specific OCR */
u32 ocr_avail_sd; /* SD-specific OCR */
u32 ocr_avail_mmc; /* MMC-specific OCR */
#ifdef CONFIG_PM_SLEEP
struct notifier_block pm_notify;// 用于支持power management有关的notify实现
#endif
u32 max_current_330; // 3.3V时的最大电流
u32 max_current_300; // 3.0V时的最大电流
u32 max_current_180; // 1.8V时的最大电流
#define MMC_VDD_165_195 0x00000080 /* VDD voltage 1.65 - 1.95 */
#define MMC_VDD_20_21 0x00000100 /* VDD voltage 2.0 ~ 2.1 */
#define MMC_VDD_21_22 0x00000200 /* VDD voltage 2.1 ~ 2.2 */
#define MMC_VDD_22_23 0x00000400 /* VDD voltage 2.2 ~ 2.3 */
#define MMC_VDD_23_24 0x00000800 /* VDD voltage 2.3 ~ 2.4 */
#define MMC_VDD_24_25 0x00001000 /* VDD voltage 2.4 ~ 2.5 */
#define MMC_VDD_25_26 0x00002000 /* VDD voltage 2.5 ~ 2.6 */
#define MMC_VDD_26_27 0x00004000 /* VDD voltage 2.6 ~ 2.7 */
#define MMC_VDD_27_28 0x00008000 /* VDD voltage 2.7 ~ 2.8 */
#define MMC_VDD_28_29 0x00010000 /* VDD voltage 2.8 ~ 2.9 */
#define MMC_VDD_29_30 0x00020000 /* VDD voltage 2.9 ~ 3.0 */
#define MMC_VDD_30_31 0x00040000 /* VDD voltage 3.0 ~ 3.1 */
#define MMC_VDD_31_32 0x00080000 /* VDD voltage 3.1 ~ 3.2 */
#define MMC_VDD_32_33 0x00100000 /* VDD voltage 3.2 ~ 3.3 */
#define MMC_VDD_33_34 0x00200000 /* VDD voltage 3.3 ~ 3.4 */
#define MMC_VDD_34_35 0x00400000 /* VDD voltage 3.4 ~ 3.5 */
#define MMC_VDD_35_36 0x00800000 /* VDD voltage 3.5 ~ 3.6 */
// 指示该MMC host所支持的功能特性
u32 caps; /* Host capabilities */
#define MMC_CAP_4_BIT_DATA (1 << 0) /* Can the host do 4 bit transfers */
#define MMC_CAP_MMC_HIGHSPEED (1 << 1) /* Can do MMC high-speed timing */
#define MMC_CAP_SD_HIGHSPEED (1 << 2) /* Can do SD high-speed timing */
#define MMC_CAP_SDIO_IRQ (1 << 3) /* Can signal pending SDIO IRQs */
#define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */
#define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */
#define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */
#define MMC_CAP_AGGRESSIVE_PM (1 << 7) /* Suspend (e)MMC/SD at idle */
#define MMC_CAP_NONREMOVABLE (1 << 8) /* Nonremovable e.g. eMMC */
#define MMC_CAP_WAIT_WHILE_BUSY (1 << 9) /* Waits while card is busy */
#define MMC_CAP_ERASE (1 << 10) /* Allow erase/trim commands */
#define MMC_CAP_3_3V_DDR (1 << 11) /* Host supports eMMC DDR 3.3V */
#define MMC_CAP_1_8V_DDR (1 << 12) /* Host supports eMMC DDR 1.8V */
#define MMC_CAP_1_2V_DDR (1 << 13) /* Host supports eMMC DDR 1.2V */
#define MMC_CAP_POWER_OFF_CARD (1 << 14) /* Can power off after boot */
#define MMC_CAP_BUS_WIDTH_TEST (1 << 15) /* CMD14/CMD19 bus width ok */
#define MMC_CAP_UHS_SDR12 (1 << 16) /* Host supports UHS SDR12 mode */
#define MMC_CAP_UHS_SDR25 (1 << 17) /* Host supports UHS SDR25 mode */
#define MMC_CAP_UHS_SDR50 (1 << 18) /* Host supports UHS SDR50 mode */
#define MMC_CAP_UHS_SDR104 (1 << 19) /* Host supports UHS SDR104 mode */
#define MMC_CAP_UHS_DDR50 (1 << 20) /* Host supports UHS DDR50 mode */
#define MMC_CAP_UHS (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | \
MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | \
MMC_CAP_UHS_DDR50)
#define MMC_CAP_SYNC_RUNTIME_PM (1 << 21) /* Synced runtime PM suspends. */
#define MMC_CAP_NEED_RSP_BUSY (1 << 22) /* Commands with R1B can't use R1. */
#define MMC_CAP_DRIVER_TYPE_A (1 << 23) /* Host supports Driver Type A */
#define MMC_CAP_DRIVER_TYPE_C (1 << 24) /* Host supports Driver Type C */
#define MMC_CAP_DRIVER_TYPE_D (1 << 25) /* Host supports Driver Type D */
#define MMC_CAP_DONE_COMPLETE (1 << 27) /* RW reqs can be completed within mmc_request_done() */
#define MMC_CAP_CD_WAKE (1 << 28) /* Enable card detect wake */
#define MMC_CAP_CMD_DURING_TFR (1 << 29) /* Commands during data transfer */
#define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */
#define MMC_CAP_HW_RESET (1 << 31) /* Hardware reset */
// 指示该MMC host所支持的功能特性
u32 caps2; /* More host capabilities */
#define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */
#define MMC_CAP2_FULL_PWR_CYCLE (1 << 2) /* Can do full power cycle */
#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */
#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */
#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \
MMC_CAP2_HS200_1_2V_SDR)
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */
#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) /* Don't power up before scan */
#define MMC_CAP2_HS400_1_8V (1 << 15) /* Can support HS400 1.8V */
#define MMC_CAP2_HS400_1_2V (1 << 16) /* Can support HS400 1.2V */
#define MMC_CAP2_HS400 (MMC_CAP2_HS400_1_8V | \
MMC_CAP2_HS400_1_2V)
#define MMC_CAP2_HSX00_1_8V (MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS400_1_8V)
#define MMC_CAP2_HSX00_1_2V (MMC_CAP2_HS200_1_2V_SDR | MMC_CAP2_HS400_1_2V)
#define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)
#define MMC_CAP2_NO_WRITE_PROTECT (1 << 18) /* No physical write protect pin, assume that card is always read-write */
#define MMC_CAP2_NO_SDIO (1 << 19) /* Do not send SDIO commands during initialization */
#define MMC_CAP2_HS400_ES (1 << 20) /* Host supports enhanced strobe */
#define MMC_CAP2_NO_SD (1 << 21) /* Do not send SD commands during initialization */
#define MMC_CAP2_NO_MMC (1 << 22) /* Do not send (e)MMC commands during initialization */
#define MMC_CAP2_CQE (1 << 23) /* Has eMMC command queue engine */
#define MMC_CAP2_CQE_DCMD (1 << 24) /* CQE can issue a direct command */
#define MMC_CAP2_AVOID_3_3V (1 << 25) /* Host must negotiate down from 3.3V */
#define MMC_CAP2_MERGE_CAPABLE (1 << 26) /* Host can merge a segment over the segment size */
int fixed_drv_type; /* fixed driver type for non-removable media */
// 该host所支持的电源管理特性
mmc_pm_flag_t pm_caps; /* supported pm features */
/* host specific block data */
unsigned int max_seg_size; /* see blk_queue_max_segment_size */
unsigned short max_segs; /* see blk_queue_max_segments */
unsigned short unused;
unsigned int max_req_size; /* maximum number of bytes in one req */
unsigned int max_blk_size; /* maximum size of one mmc block */
unsigned int max_blk_count; /* maximum number of blocks in one req */
unsigned int max_busy_timeout; /* max busy timeout in ms */
/* private data */
// 该host的bus使用的锁
spinlock_t lock; /* lock for claim and bus ops */
// 用于保存MMC bus的当前配置
struct mmc_ios ios; /* current io bus settings */
/* group bitfields together to minimize padding */
unsigned int use_spi_crc:1;
unsigned int claimed:1; /* host exclusively claimed */ // host是否已经被占用
unsigned int bus_dead:1; /* bus has been released */ // host的bus是否处于激活状态
unsigned int can_retune:1; /* re-tuning can be used */
unsigned int doing_retune:1; /* re-tuning in progress */
unsigned int retune_now:1; /* do re-tuning at next req */
unsigned int retune_paused:1; /* re-tuning is temporarily disabled */
unsigned int use_blk_mq:1; /* use blk-mq */
unsigned int retune_crc_disable:1; /* don't trigger retune upon crc */
unsigned int can_dma_map_merge:1; /* merging can be used */
int rescan_disable; /* disable card detection */ // 禁止rescan的标识,禁止搜索card
int rescan_entered; /* used with nonremovable devices */// 是否已经rescan过的标识,对应不可移除的设备只能rescan一次
int need_retune; /* re-tuning is needed */
int hold_retune; /* hold off re-tuning */
unsigned int retune_period; /* re-tuning period in secs */
struct timer_list retune_timer; /* for periodic re-tuning */
bool trigger_card_event; /* card_event necessary */
struct mmc_card *card; /* device attached to this host */// 和该host绑定在一起的card
wait_queue_head_t wq;
struct mmc_ctx *claimer; /* context that has host claimed */// 该host的占有者进程
int claim_cnt; /* "claim" nesting count */// 占有者进程对该host的占用计数
struct mmc_ctx default_ctx; /* default context */
struct delayed_work detect; // 检测卡槽变化的工作
int detect_change; /* card detect flag */// 需要检测卡槽变化的标识
struct mmc_slot slot; // 卡槽的结构体
const struct mmc_bus_ops *bus_ops; /* current bus driver */ // host的mmc总线的操作集
unsigned int bus_refs; /* reference counter */ // host的mmc总线的使用计数
unsigned int sdio_irqs;
struct task_struct *sdio_irq_thread;
struct delayed_work sdio_irq_work;
bool sdio_irq_pending;
atomic_t sdio_irq_thread_abort;
mmc_pm_flag_t pm_flags; /* requested pm features */
struct led_trigger *led; /* activity led */
#ifdef CONFIG_REGULATOR
bool regulator_enabled; /* regulator state */ // 代表regulator(LDO)的状态
#endif
struct mmc_supply supply;
struct dentry *debugfs_root; // 对应的debug目录结构体
/* Ongoing data transfer that allows commands during transfer */
struct mmc_request *ongoing_mrq;
#ifdef CONFIG_FAIL_MMC_REQUEST
struct fault_attr fail_mmc_request;
#endif
unsigned int actual_clock; /* Actual HC clock rate */
unsigned int slotno; /* used for sdio acpi binding */
int dsr_req; /* DSR value is valid */
u32 dsr; /* optional driver stage (DSR) value */
/* Command Queue Engine (CQE) support */
const struct mmc_cqe_ops *cqe_ops;
void *cqe_private;
int cqe_qdepth;
bool cqe_enabled;
bool cqe_on;
unsigned long private[0] ____cacheline_aligned;
};
mmc_host_ops
mmc core将host需要提供的一些操作方法封装成struct mmc_host_ops。
mmc core主模块的很多接口都是基于这里面的操作方法来实现的,通过这些方法来操作host硬件达到对应的目的。
所以struct mmc_host_ops也是host controller driver需要实现的核心部分。
struct mmc_host_ops {
/*
* It is optional for the host to implement pre_req and post_req in
* order to support double buffering of requests (prepare one
* request while another request is active).
* pre_req() must always be followed by a post_req().
* To undo a call made to pre_req(), call post_req() with
* a nonzero err condition.
*/
// post_req和pre_req是为了实现异步请求处理而设置的,是非必需的,
// 异步请求处理就是指,当另外一个异步请求还没有处理完成的时候,
// 可以先准备另外一个异步请求而不必等待
void (*post_req)(struct mmc_host *host, struct mmc_request *req,
int err);
void (*pre_req)(struct mmc_host *host, struct mmc_request *req);
// host处理mmc请求的方法,在mmc_start_request中会调用
void (*request)(struct mmc_host *host, struct mmc_request *req);
/*
* Avoid calling the next three functions too often or in a "fast
* path", since underlaying controller might implement them in an
* expensive and/or slow way. Also note that these functions might
* sleep, so don't call them in the atomic contexts!
*/
/*
* Notes to the set_ios callback:
* ios->clock might be 0. For some controllers, setting 0Hz
* as any other frequency works. However, some controllers
* explicitly need to disable the clock. Otherwise e.g. voltage
* switching might fail because the SDCLK is not really quiet.
*/
// 设置host的总线的io setting
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
/*
* Return values for the get_ro callback should be:
* 0 for a read/write card
* 1 for a read-only card
* -ENOSYS when not supported (equal to NULL callback)
* or a negative errno value when something bad happened
*/
int (*get_ro)(struct mmc_host *host); // 获取host上的card的读写属性
/*
* Return values for the get_cd callback should be:
* 0 for a absent card
* 1 for a present card
* -ENOSYS when not supported (equal to NULL callback)
* or a negative errno value when something bad happened
*/
int (*get_cd)(struct mmc_host *host); // 检测host的卡槽中card的插入状态
void (*enable_sdio_irq)(struct mmc_host *host, int enable);
/* Mandatory callback when using MMC_CAP2_SDIO_IRQ_NOTHREAD. */
void (*ack_sdio_irq)(struct mmc_host *host);
/* optional callback for HC quirks */ // 初始化card的方法
void (*init_card)(struct mmc_host *host, struct mmc_card *card);
int (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios);
/* Check if the card is pulling dat[0:3] low */
int (*card_busy)(struct mmc_host *host); // 用于检测card是否处于busy状态
/* The tuning command opcode value is different for SD and eMMC cards */
// 执行tuning操作,为card选择一个合适的采样点
int (*execute_tuning)(struct mmc_host *host, u32 opcode);
/* Prepare HS400 target operating frequency depending host driver */
int (*prepare_hs400_tuning)(struct mmc_host *host, struct mmc_ios *ios);
/* Prepare switch to DDR during the HS400 init sequence */
int (*hs400_prepare_ddr)(struct mmc_host *host);
/* Prepare for switching from HS400 to HS200 */
void (*hs400_downgrade)(struct mmc_host *host);
/* Complete selection of HS400 */
void (*hs400_complete)(struct mmc_host *host);
/* Prepare enhanced strobe depending host driver */
void (*hs400_enhanced_strobe)(struct mmc_host *host,
struct mmc_ios *ios);
int (*select_drive_strength)(struct mmc_card *card,
unsigned int max_dtr, int host_drv,
int card_drv, int *drv_type);
void (*hw_reset)(struct mmc_host *host); // 硬件复位
void (*card_event)(struct mmc_host *host); // 硬件复位
/*
* Optional callback to support controllers with HW issues for multiple
* I/O. Returns the number of supported blocks for the request.
*/
int (*multi_io_quirk)(struct mmc_card *card,
unsigned int direction, int blk_size);
};
sunxi_mmc_host
每个soc厂家一般都会讲mmc_host进行封装成一个更友好与自己mmc控制器的结构体,本结构体为allwinner所封装的sunxi_mmc_host。
struct sunxi_mmc_host {
struct device *dev; // 当前driver中的device
struct mmc_host *mmc; // mmc host实体
struct reset_control *reset; // reset实体
const struct sunxi_mmc_cfg *cfg;// 兼容多系列IC的配置信息
/* IO mapping base */
void __iomem *reg_base; // remap后mm base
/* clock management */
struct clk *clk_ahb; // clk信息
struct clk *clk_mmc;
struct clk *clk_sample;
struct clk *clk_output;
/* irq */
spinlock_t lock;
int irq; // mmc控制器虚拟中断号
u32 int_sum; // 记录当前中断状态寄存器的值
u32 sdio_imask; // 记录当前中断使能寄存器的值
/* dma */
dma_addr_t sg_dma; // 链式dma所对应内存的物理地址
void *sg_cpu; // 链式dma所对应内存的句柄
bool wait_dma; // 等待dma完成的标志
struct mmc_request *mrq; // 当前传输的mmc request
struct mmc_request *manual_stop_mrq; // 需发送stop命令的mmc request
int ferror;
/* vqmmc */
bool vqmmc_enabled;
/* timings */
bool use_new_timings;
};
mmc host controller驱动实现
本章节将从mmc host controller driver的添加,mmc_host的申请/添加,mmc_request处理流程等角度分析如何实现一个mmc host controller驱动。
mmc host platform driver的添加
platfrom_device是通过device-tree添加的,我们只需在驱动中实现platform_driver,具体方法如下:
static struct platform_driver sunxi_mmc_driver = {
.driver = {
.name = "sunxi-mmc",
.of_match_table = of_match_ptr(sunxi_mmc_of_match),
.pm = &sunxi_mmc_pm_ops,
},
.probe = sunxi_mmc_probe,
.remove = sunxi_mmc_remove,
};
module_platform_driver(sunxi_mmc_driver);
sunxi_mmc_of_match的实现:
static const struct of_device_id sunxi_mmc_of_match[] = {
{ .compatible = "allwinner,sun4i-a10-mmc", .data = &sun4i_a10_cfg },
{ .compatible = "allwinner,sun5i-a13-mmc", .data = &sun5i_a13_cfg },
{ .compatible = "allwinner,sun7i-a20-mmc", .data = &sun7i_a20_cfg },
{ .compatible = "allwinner,sun8i-a83t-emmc", .data = &sun8i_a83t_emmc_cfg },
{ .compatible = "allwinner,sun9i-a80-mmc", .data = &sun9i_a80_cfg },
{ .compatible = "allwinner,sun50i-a64-mmc", .data = &sun50i_a64_cfg },
{ .compatible = "allwinner,sun50i-a64-emmc", .data = &sun50i_a64_emmc_cfg },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sunxi_mmc_of_match);
mmc device-tree的实现:
mmc0: [email protected] {
compatible = "allwinner,sun7i-a20-mmc";
reg = <0x01c0f000 0x1000>;
clocks = <&ccu CLK_BUS_MMC0>,
<&ccu CLK_MMC0>,
<&ccu CLK_MMC0_OUTPUT>,
<&ccu CLK_MMC0_SAMPLE>;
clock-names = "ahb",
"mmc",
"output",
"sample";
resets = <&ccu RST_BUS_MMC0>;
reset-names = "ahb";
interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&mmc0_pins>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
sunxi_mmc_probe的实现
sunxi_mmc_probe的主要功能有以下几个:
- 申请mmc host;
- 申请及初始化mmc host controller资源(内存/中断);
- mmc host controller硬件信息配置(如:max_blk_size、sdio irq);
-
注册mmc host到host bus中;
具体代码实现如下:
static int sunxi_mmc_probe(struct platform_device *pdev)
{
struct sunxi_mmc_host *host;
struct mmc_host *mmc;
int ret;
//申请mmc_host,private将为sunxi_mmc_host
mmc = mmc_alloc_host(sizeof(struct sunxi_mmc_host), &pdev->dev);
if (!mmc) {
dev_err(&pdev->dev, "mmc alloc host failed\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, mmc); //device是有数据为mmc_host
// 初始化sunxi_mmc_host
host = mmc_priv(mmc);
host->dev = &pdev->dev;
host->mmc = mmc;
spin_lock_init(&host->lock);
// 申请reg_mm、clk、irq、reset等
ret = sunxi_mmc_resource_request(host, pdev);
if (ret)
goto error_free_host;
// 申请链式dma管理的内存池,该部分内存数据为no-cache
host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
&host->sg_dma, GFP_KERNEL);
if (!host->sg_cpu) {
dev_err(&pdev->dev, "Failed to allocate DMA descriptor mem\n");
ret = -ENOMEM;
goto error_free_host;
}
if (host->cfg->ccu_has_timings_switch) {
/*
* Supports both old and new timing modes.
* Try setting the clk to new timing mode.
*/
sunxi_ccu_set_mmc_timing_mode(host->clk_mmc, true);
/* And check the result */
ret = sunxi_ccu_get_mmc_timing_mode(host->clk_mmc);
if (ret < 0) {
/*
* For whatever reason we were not able to get
* the current active mode. Default to old mode.
*/
dev_warn(&pdev->dev, "MMC clk timing mode unknown\n");
host->use_new_timings = false;
} else {
host->use_new_timings = !!ret;
}
} else if (host->cfg->needs_new_timings) {
/* Supports new timing mode only */
host->use_new_timings = true;
}
// mmc_host_ops的实现
mmc->ops = &sunxi_mmc_ops;
// mmc_host controller特性blk、req、seq等max_size配置
mmc->max_blk_count = 8192;
mmc->max_blk_size = 4096;
mmc->max_segs = PAGE_SIZE / sizeof(struct sunxi_idma_des);
mmc->max_seg_size = (1 << host->cfg->idma_des_size_bits);
mmc->max_req_size = mmc->max_seg_size * mmc->max_segs;
// mmc_host controller时钟速率配置
/* 400kHz ~ 52MHz */
mmc->f_min = 400000;
mmc->f_max = 52000000;
mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED |
MMC_CAP_ERASE | MMC_CAP_SDIO_IRQ;
/*
* Some H5 devices do not have signal traces precise enough to
* use HS DDR mode for their eMMC chips.
*
* We still enable HS DDR modes for all the other controller
* variants that support them.
*/
if ((host->cfg->clk_delays || host->use_new_timings) &&
!of_device_is_compatible(pdev->dev.of_node,
"allwinner,sun50i-h5-emmc"))
mmc->caps |= MMC_CAP_1_8V_DDR | MMC_CAP_3_3V_DDR;
// 解析设备树信息
ret = mmc_of_parse(mmc);
if (ret)
goto error_free_dma;
/*
* If we don't support delay chains in the SoC, we can't use any
* of the higher speed modes. Mask them out in case the device
* tree specifies the properties for them, which gets added to
* the caps by mmc_of_parse() above.
*/
if (!(host->cfg->clk_delays || host->use_new_timings)) {
mmc->caps &= ~(MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR |
MMC_CAP_1_2V_DDR | MMC_CAP_UHS);
mmc->caps2 &= ~MMC_CAP2_HS200;
}
/* TODO: This driver doesn't support HS400 mode yet */
mmc->caps2 &= ~MMC_CAP2_HS400;
// 初始化 mmc_host contrller
ret = sunxi_mmc_init_host(host);
if (ret)
goto error_free_dma;
pm_runtime_set_active(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
// 将本mmc_host添加到host bus中,并予以启动,详见《mmc host》的解析
ret = mmc_add_host(mmc);
if (ret)
goto error_free_dma;
dev_info(&pdev->dev, "initialized, max. request size: %u KB%s\n",
mmc->max_req_size >> 10,
host->use_new_timings ? ", uses new timings mode" : "");
return 0;
error_free_dma:
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
error_free_host:
mmc_free_host(mmc);
return ret;
}
sunxi_mmc_ops的实现
sunxi_mmc_ops为mmc host controller的行为提供具体的实现方法,定义代码如下:
static const struct mmc_host_ops sunxi_mmc_ops = {
.request = sunxi_mmc_request, // 实现 mmc request的处理
.set_ios = sunxi_mmc_set_ios, // 实现 mmc io 相关的处理
.get_ro = mmc_gpio_get_ro, // 实现 mmc card可读写
.get_cd = mmc_gpio_get_cd, // 实现 mmc card热拔插
.enable_sdio_irq = sunxi_mmc_enable_sdio_irq, // 实现 mmc sdio irq的控制
.start_signal_voltage_switch = sunxi_mmc_volt_switch, // 实现 mmc voltage的切换
.hw_reset = sunxi_mmc_hw_reset, // 实现 mmc hw_reset的功能
.card_busy = sunxi_mmc_card_busy, // 实现 mmc card busy检测功能
};
sunxi_mmc_set_ios
static void sunxi_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct sunxi_mmc_host *host = mmc_priv(mmc);
sunxi_mmc_card_power(host, ios); // MMC_POWER_ON/MMC_POWER_OFF等的处理
sunxi_mmc_set_bus_width(host, ios->bus_width); // mmc 数据宽度的配置
sunxi_mmc_set_clk(host, ios); // mmc clk速率的配置
}
sunxi_mmc_enable_sdio_irq
static void sunxi_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct sunxi_mmc_host *host = mmc_priv(mmc);
unsigned long flags;
u32 imask;
if (enable)
pm_runtime_get_noresume(host->dev);
spin_lock_irqsave(&host->lock, flags);
imask = mmc_readl(host, REG_IMASK);
if (enable) { // 使能 sdio irq
host->sdio_imask = SDXC_SDIO_INTERRUPT;
imask |= SDXC_SDIO_INTERRUPT;
} else { // 关闭 sdio irq
host->sdio_imask = 0;
imask &= ~SDXC_SDIO_INTERRUPT;
}
mmc_writel(host, REG_IMASK, imask);
spin_unlock_irqrestore(&host->lock, flags);
if (!enable)
pm_runtime_put_noidle(host->mmc->parent);
}
sunxi_mmc_card_busy
static int sunxi_mmc_card_busy(struct mmc_host *mmc)
{
struct sunxi_mmc_host *host = mmc_priv(mmc);
// 获取DATA1的数据线的状态,HIGH:free,LOW:busy
return !!(mmc_readl(host, REG_STAS) & SDXC_CARD_DATA_BUSY);
}
sunxi_mmc_request
该函数为核心函数,主要实现每个mrq的启动收发动作的处理,结束部分则由中断服务函数处理,因此mrq的处理流程需结合中断服务函数一起进行。
static void sunxi_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sunxi_mmc_host *host = mmc_priv(mmc);
struct mmc_command *cmd = mrq->cmd;
struct mmc_data *data = mrq->data;
unsigned long iflags;
u32 imask = SDXC_INTERRUPT_ERROR_BIT;
u32 cmd_val = SDXC_START | (cmd->opcode & 0x3f);
bool wait_dma = host->wait_dma;
int ret;
/* Check for set_ios errors (should never happen) */
if (host->ferror) {
mrq->cmd->error = host->ferror;
mmc_request_done(mmc, mrq);
return;
}
// mrq 需发送数据,则流式dma处理数据
if (data) {
ret = sunxi_mmc_map_dma(host, data);
if (ret < 0) {
dev_err(mmc_dev(mmc), "map DMA failed\n");
cmd->error = ret;
data->error = ret;
mmc_request_done(mmc, mrq);
return;
}
}
if (cmd->opcode == MMC_GO_IDLE_STATE) {
cmd_val |= SDXC_SEND_INIT_SEQUENCE;
imask |= SDXC_COMMAND_DONE;
}
// 不同cmd 模式的配置
if (cmd->flags & MMC_RSP_PRESENT) {
cmd_val |= SDXC_RESP_EXPIRE;
if (cmd->flags & MMC_RSP_136)
cmd_val |= SDXC_LONG_RESPONSE;
if (cmd->flags & MMC_RSP_CRC)
cmd_val |= SDXC_CHECK_RESPONSE_CRC;
if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC) {
cmd_val |= SDXC_DATA_EXPIRE | SDXC_WAIT_PRE_OVER;
if (cmd->data->stop) {
imask |= SDXC_AUTO_COMMAND_DONE;
cmd_val |= SDXC_SEND_AUTO_STOP;
} else {
imask |= SDXC_DATA_OVER;
}
if (cmd->data->flags & MMC_DATA_WRITE)
cmd_val |= SDXC_WRITE;
else
wait_dma = true;
} else {
imask |= SDXC_COMMAND_DONE;
}
} else {
imask |= SDXC_COMMAND_DONE;
}
dev_dbg(mmc_dev(mmc), "cmd %d(%08x) arg %x ie 0x%08x len %d\n",
cmd_val & 0x3f, cmd_val, cmd->arg, imask,
mrq->data ? mrq->data->blksz * mrq->data->blocks : 0);
spin_lock_irqsave(&host->lock, iflags);
if (host->mrq || host->manual_stop_mrq) {
spin_unlock_irqrestore(&host->lock, iflags);
if (data)
dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len,
mmc_get_dma_dir(data));
dev_err(mmc_dev(mmc), "request already pending\n");
mrq->cmd->error = -EBUSY;
mmc_request_done(mmc, mrq);
return;
}
// mrq 带有数据,则配置idma
if (data) {
mmc_writel(host, REG_BLKSZ, data->blksz);
mmc_writel(host, REG_BCNTR, data->blksz * data->blocks);
sunxi_mmc_start_dma(host, data);
}
// 配置cmd,arg,int等寄存器
host->mrq = mrq;
host->wait_dma = wait_dma;
mmc_writel(host, REG_IMASK, host->sdio_imask | imask);
mmc_writel(host, REG_CARG, cmd->arg);
mmc_writel(host, REG_CMDR, cmd_val);
spin_unlock_irqrestore(&host->lock, iflags);
}
sunxi mmc irq中断服务函数的实现
中断申请及注册
static int sunxi_mmc_resource_request(struct sunxi_mmc_host *host,
struct platform_device *pdev)
{
int ret;
......
// 获取中断号
host->irq = platform_get_irq(pdev, 0);
if (host->irq <= 0) {
ret = -EINVAL;
goto error_disable_mmc;
}
//注册中断函数,使用线程化的方法实现中断的上下部
return devm_request_threaded_irq(&pdev->dev, host->irq, sunxi_mmc_irq,
sunxi_mmc_handle_manual_stop, 0, "sunxi-mmc", host);
error_disable_mmc:
sunxi_mmc_disable(host);
return ret;
}
mmc irq 上半部——sunxi_mmc_irq
mmc irq中断服务函数中具体实现的功能及步骤如下:
-
- 获取idma和中断状态寄存的值;
-
- 判断是否有cmd done和 sdio irq的标志;
-
- 清楚idma和中断状态;
-
- 处理cmd的resp和data,并返回是否需要启动中断下半部线程的值;
-
- 若有cmd done标志,则mmc_requset_done;
-
- 若有sdio irq标志,则disable sdio irq和唤醒sdio_irq_thread;
static irqreturn_t sunxi_mmc_irq(int irq, void *dev_id)
{
struct sunxi_mmc_host *host = dev_id;
struct mmc_request *mrq;
u32 msk_int, idma_int;
bool finalize = false;
bool sdio_int = false;
irqreturn_t ret = IRQ_HANDLED;
spin_lock(&host->lock);
idma_int = mmc_readl(host, REG_IDST); // 读取idma的状态
msk_int = mmc_readl(host, REG_MISTA);// 读取int的状态
dev_dbg(mmc_dev(host->mmc), "irq: rq %p mi %08x idi %08x\n",
host->mrq, msk_int, idma_int);
// 获取cmd done的标志
mrq = host->mrq;
if (mrq) {
if (idma_int & SDXC_IDMAC_RECEIVE_INTERRUPT)
host->wait_dma = false;
host->int_sum |= msk_int;
/* Wait for COMMAND_DONE on RESPONSE_TIMEOUT before finalize */
if ((host->int_sum & SDXC_RESP_TIMEOUT) &&
!(host->int_sum & SDXC_COMMAND_DONE))
mmc_writel(host, REG_IMASK,
host->sdio_imask | SDXC_COMMAND_DONE);
/* Don't wait for dma on error */
else if (host->int_sum & SDXC_INTERRUPT_ERROR_BIT)
finalize = true;
else if ((host->int_sum & SDXC_INTERRUPT_DONE_BIT) &&
!host->wait_dma)
finalize = true;
}
// 获取 sdio irq标志
if (msk_int & SDXC_SDIO_INTERRUPT)
sdio_int = true;
// 清楚 idma和int的状态
mmc_writel(host, REG_RINTR, msk_int);
mmc_writel(host, REG_IDST, idma_int);
// cmd done/error/timerout,则处理resp、data等
if (finalize)
ret = sunxi_mmc_finalize_request(host);
spin_unlock(&host->lock);
// cmd done
if (finalize && ret == IRQ_HANDLED)
mmc_request_done(host->mmc, mrq);
// sdio irq
if (sdio_int)
mmc_signal_sdio_irq(host->mmc);
return ret;
}
/* Called in interrupt context! */
static irqreturn_t sunxi_mmc_finalize_request(struct sunxi_mmc_host *host)
{
struct mmc_request *mrq = host->mrq;
struct mmc_data *data = mrq->data;
u32 rval;
mmc_writel(host, REG_IMASK, host->sdio_imask);
mmc_writel(host, REG_IDIE, 0);
// 解析错误flag,并打印
if (host->int_sum & SDXC_INTERRUPT_ERROR_BIT) {
sunxi_mmc_dump_errinfo(host);
mrq->cmd->error = -ETIMEDOUT;
if (data) {
data->error = -ETIMEDOUT;
host->manual_stop_mrq = mrq;
}
if (mrq->stop)
mrq->stop->error = -ETIMEDOUT;
} else {
// 获取resp值
if (mrq->cmd->flags & MMC_RSP_136) {
mrq->cmd->resp[0] = mmc_readl(host, REG_RESP3);
mrq->cmd->resp[1] = mmc_readl(host, REG_RESP2);
mrq->cmd->resp[2] = mmc_readl(host, REG_RESP1);
mrq->cmd->resp[3] = mmc_readl(host, REG_RESP0);
} else {
mrq->cmd->resp[0] = mmc_readl(host, REG_RESP0);
}
if (data)
data->bytes_xfered = data->blocks * data->blksz;
}
// 清除/关闭 idma,unmap sg等
if (data) {
mmc_writel(host, REG_IDST, 0x337);
mmc_writel(host, REG_DMAC, 0);
rval = mmc_readl(host, REG_GCTRL);
rval |= SDXC_DMA_RESET;
mmc_writel(host, REG_GCTRL, rval);
rval &= ~SDXC_DMA_ENABLE_BIT;
mmc_writel(host, REG_GCTRL, rval);
rval |= SDXC_FIFO_RESET;
mmc_writel(host, REG_GCTRL, rval);
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
mmc_get_dma_dir(data));
}
mmc_writel(host, REG_RINTR, 0xffff);
host->mrq = NULL;
host->int_sum = 0;
host->wait_dma = false;
return host->manual_stop_mrq ? IRQ_WAKE_THREAD : IRQ_HANDLED;
}
mmc irq下半部——sunxi_mmc_handle_manual_stop
static irqreturn_t sunxi_mmc_handle_manual_stop(int irq, void *dev_id)
{
struct sunxi_mmc_host *host = dev_id;
struct mmc_request *mrq;
unsigned long iflags;
// 获取 stop mrq
spin_lock_irqsave(&host->lock, iflags);
mrq = host->manual_stop_mrq;
spin_unlock_irqrestore(&host->lock, iflags);
if (!mrq) {
dev_err(mmc_dev(host->mmc), "no request for manual stop\n");
return IRQ_HANDLED;
}
dev_err(mmc_dev(host->mmc), "data error, sending stop command\n");
/*
* We will never have more than one outstanding request,
* and we do not complete the request until after
* we've cleared host->manual_stop_mrq so we do not need to
* spin lock this function.
* Additionally we have wait states within this function
* so having it in a lock is a very bad idea.
*/
// 发送 stop cmd
sunxi_mmc_send_manual_stop(host, mrq);
spin_lock_irqsave(&host->lock, iflags);
host->manual_stop_mrq = NULL;
spin_unlock_irqrestore(&host->lock, iflags);
mmc_request_done(host->mmc, mrq);
return IRQ_HANDLED;
}
static void sunxi_mmc_send_manual_stop(struct sunxi_mmc_host *host,
struct mmc_request *req)
{
u32 arg, cmd_val, ri;
unsigned long expire = jiffies + msecs_to_jiffies(1000);
// 组合cmd
cmd_val = SDXC_START | SDXC_RESP_EXPIRE |
SDXC_STOP_ABORT_CMD | SDXC_CHECK_RESPONSE_CRC;
// sdio, cmd53
if (req->cmd->opcode == SD_IO_RW_EXTENDED) {
cmd_val |= SD_IO_RW_DIRECT;
arg = (1 << 31) | (0 << 28) | (SDIO_CCCR_ABORT << 9) |
((req->cmd->arg >> 28) & 0x7);
} else { // other, cmd12
cmd_val |= MMC_STOP_TRANSMISSION;
arg = 0;
}
// 发送stop cmd
mmc_writel(host, REG_CARG, arg);
mmc_writel(host, REG_CMDR, cmd_val);
// 等待stop cmd完成
do {
ri = mmc_readl(host, REG_RINTR);
} while (!(ri & (SDXC_COMMAND_DONE | SDXC_INTERRUPT_ERROR_BIT)) &&
time_before(jiffies, expire));
if (!(ri & SDXC_COMMAND_DONE) || (ri & SDXC_INTERRUPT_ERROR_BIT)) {
dev_err(mmc_dev(host->mmc), "send stop command failed\n");
if (req->stop)
req->stop->resp[0] = -ETIMEDOUT;
} else {
if (req->stop)
req->stop->resp[0] = mmc_readl(host, REG_RESP0);
}
mmc_writel(host, REG_RINTR, 0xffff);
}
sunxi_mmc_remove的实现
static int sunxi_mmc_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
struct sunxi_mmc_host *host = mmc_priv(mmc);
mmc_remove_host(mmc); //从 host bus上移除mmc host controller
pm_runtime_force_suspend(&pdev->dev);
disable_irq(host->irq); // disable irq
sunxi_mmc_disable(host); // disable mmc host controller
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); //释放sg_cpu内存
mmc_free_host(mmc); // 释放mmc_host 内存
return 0;
}
sunxi_mmc_pm_ops的实现
#ifdef CONFIG_PM
static int sunxi_mmc_runtime_resume(struct device *dev)
{
struct mmc_host *mmc = dev_get_drvdata(dev);
struct sunxi_mmc_host *host = mmc_priv(mmc);
int ret;
ret = sunxi_mmc_enable(host);
if (ret)
return ret;
sunxi_mmc_init_host(host);
sunxi_mmc_set_bus_width(host, mmc->ios.bus_width);
sunxi_mmc_set_clk(host, &mmc->ios);
enable_irq(host->irq);
return 0;
}
static int sunxi_mmc_runtime_suspend(struct device *dev)
{
struct mmc_host *mmc = dev_get_drvdata(dev);
struct sunxi_mmc_host *host = mmc_priv(mmc);
/*
* When clocks are off, it's possible receiving
* fake interrupts, which will stall the system.
* Disabling the irq will prevent this.
*/
disable_irq(host->irq);
sunxi_mmc_reset_host(host);
sunxi_mmc_disable(host);
return 0;
}
#endif
static const struct dev_pm_ops sunxi_mmc_pm_ops = {
SET_RUNTIME_PM_OPS(sunxi_mmc_runtime_suspend,
sunxi_mmc_runtime_resume,
NULL)
};