Linux驱动——mmc host controller(九)
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的devicestruct 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_SLEEPstruct notifier_block pm_notify;// 用于支持power management有关的notify实现
#endifu32 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的标识,禁止搜索cardint 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绑定在一起的cardwait_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_REGULATORbool regulator_enabled; /* regulator state */ // 代表regulator(LDO)的状态
#endifstruct 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_REQUESTstruct fault_attr fail_mmc_request;
#endifunsigned 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 settingvoid (*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中的devicestruct 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 requeststruct mmc_request *manual_stop_mrq; // 需发送stop命令的mmc requestint 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: mmc@1c0f000 {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_hostmmc = 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_hosthost = 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-cachehost->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 contrllerret = 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 irqhost->sdio_imask = SDXC_SDIO_INTERRUPT;imask |= SDXC_SDIO_INTERRUPT;} else { // 关闭 sdio irqhost->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:busyreturn !!(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;elsewait_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 带有数据,则配置idmaif (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 doneif (finalize && ret == IRQ_HANDLED)mmc_request_done(host->mmc, mrq);// sdio irqif (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 mrqspin_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 cmdsunxi_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);// 组合cmdcmd_val = SDXC_START | SDXC_RESP_EXPIRE |SDXC_STOP_ABORT_CMD | SDXC_CHECK_RESPONSE_CRC;// sdio, cmd53if (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, cmd12cmd_val |= MMC_STOP_TRANSMISSION;arg = 0;}// 发送stop cmdmmc_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 controllerpm_runtime_force_suspend(&pdev->dev);disable_irq(host->irq); // disable irqsunxi_mmc_disable(host); // disable mmc host controllerdma_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;
}
#endifstatic const struct dev_pm_ops sunxi_mmc_pm_ops = {SET_RUNTIME_PM_OPS(sunxi_mmc_runtime_suspend,sunxi_mmc_runtime_resume,NULL)
};
Linux驱动——mmc host controller(九)相关推荐
- Linux驱动——mmc数据结构(二)
Linux驱动--mmc数据结构(二) 备注: 1. Kernel版本:5.4 2. 使用工具:Source Insight 4.0 3. 参考博客: 2. [mmc subsystem] ...
- Linux驱动——mmc概念与框架(一)
Linux驱动--mmc概念与框架(一) 备注: 1. Kernel版本:5.4 2. 使用工具:Source Insight 4.0 3. 参考博客: Linux MMC framewo ...
- Linux驱动——mmc card热插拔检测机制(十)
Linux驱动--mmc card热插拔检测机制(十) 备注: 1. Kernel版本:5.4 2. 使用工具:Source Insight 4.0 3. 参考博客: [sd card] ...
- Linux驱动——mmc sd card初始化流程(十一)
Linux驱动--mmc sd card初始化流程(十一) 备注: 1. Kernel版本:5.4 2. 使用工具:Source Insight 4.0 3. 参考博客: (1)[sd ...
- Linux驱动——mmc sd card 块设备读写流程(十三)
Linux驱动--mmc sd card 块设备读写流程(十三) 备注: 1. Kernel版本:5.4 2. 使用工具:Source Insight 4.0 3. 参考博客: (1) ...
- Linux 驱动学习笔记 - beep(九)
Linux 驱动学习笔记 - beep(九) 本系列均为正点原子 Linux 驱动的学习笔记, 以便加深笔者记忆.如读者想进一步学习,可以到正点原子官网中下载资料进行学习. 添加 pinctrl 节点 ...
- linux驱动学习笔记(九)SPI
前言 SPI协议是由摩托罗拉公司提出的串行通信协议,是一种高速全双工的通信总线.常见的SPI接口包含四根线,MOSI(主设备输出,从设备输入),MISO(主设备输入,从设备输出)SCLK(时钟,决定通 ...
- Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序
字符设备模拟pipe的驱动程序 让我们用一个"pipe"的设备驱动去结束简单字符设备吧(这里所说的pipe并非标准的pipe只是模拟了一个从一端写入从另一端写入的设备) 测试代码1 ...
- 从零开始之驱动发开、linux驱动(二十九、mmap原理)
一.概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系.实现这样的映射关系后,进程就可以采用指针的方式 ...
最新文章
- word2016提示mathtype文件未找到:MathPage.wll
- 活动推荐|20位大咖齐聚,“中国首届沉浸产业发展论坛”10月底将于南京召开...
- PAT_B_1074 宇宙无敌加法器
- 用javascript实现以下功能!_模电小实验:用三极管实现触摸开关功能
- SQL Server 编写自动增长的字符串型主键
- svg mysql_SVG 实例
- 95-10-080-启动-replicaManager副本管理器
- 40亿个手机号码如何去重?
- 企业微信怎么填写服务器,勤哲Excel服务器软件做企业微信管理系统
- 【托马斯微积分】(12版)阅读笔记2:极限
- win10安装ab测试工具
- 抢购为什么难,需要怎么做?
- 摄像头 - 双摄像头工作原理详解:RBG +RGB, RGB + Mono
- [Luogu P3975] [TJOI2015]弦论
- Let‘s Go Rust 系列之定时器 Ticker Timer
- 收藏:不错的数据中台建设方法论
- CSS实现水波纹效果
- 数据源大盘点 | 你们要的数据源都在这儿了84个!(保持更新,建议收藏)
- iTop-4412 裸机程序(十七)- 按键介绍
- Python发送邮件实例
热门文章
- 人工智能会统治人类吗?
- Ubuntu安装redis教程
- 定时任务(工作有用)
- 几种内网映射到公网的方法
- Python机器学习日记(八)
- 2021高考 无锡高中成绩查询,无锡2021高考成绩排名榜单,无锡各高中高考成绩喜报...
- 持续集成工具hudson
- cp: cannot create regular file '/media/lxl/wdd/boot/vxworks-cu2': Read-only file system
- 《网络空间欺骗:构筑欺骗防御的科学基石》一2.4.3 实施和集成欺骗
- Learning Visual Commonsense for Robust Scene Graph Generation论文笔记