天天看點

Linux驅動——mmc host controller(九)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的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驅動。

Linux驅動——mmc host controller(九)Linux驅動——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中斷服務函數中具體實作的功能及步驟如下:

    1. 擷取idma和中斷狀态寄存的值;
    1. 判斷是否有cmd done和 sdio irq的标志;
    1. 清楚idma和中斷狀态;
    1. 處理cmd的resp和data,并傳回是否需要啟動中斷下半部線程的值;
    1. 若有cmd done标志,則mmc_requset_done;
    1. 若有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)
};
           

繼續閱讀