天天看點

Linux mmc驅動架構(3)——host驅動初始化Host驅動裝置樹Host驅動

Linux mmc驅動架構

  • Host驅動裝置樹
  • Host驅動
    • platform_driver資料結構
    • 控制器驅動初始化函數
      • `sunxi_mmc_probe->mmc_add_host`
      • `sunxi_mmc_probe->mmc_add_host->mmc_start_host`
      • `sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change`
      • `sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change-> mmc_schedule_delayed_work`
      • `sunxi_mmc_probe->mmc_alloc_host`

host驅動的編寫主要步驟如下:

  1. 通過mmc_alloc_host配置設定一個mmc_host結構體
  2. 定義實作mmc_host_ops資料結構, 并指派給上面的mmc_host->mmc_host_ops成員變量
  3. 給mmc_host成員變量指派, 如ocr_avail、caps等成員變量
  4. 調用mmc_add_host注冊該Host

Host驅動裝置樹

  host驅動裝置樹用于比對host驅動,host驅動比對上裝置樹,初始化流程才能開始。本文舉例全志H3裝置樹以及mmc控制器驅動。

  下面的代碼段都是一些基本配置,compatible用于與驅動比對,reg為控制器IO記憶體位址。interrupt為中斷參數,GIC控制器,中斷号等。以及mmc控制器用到的時鐘參數。

mmc0: [email protected]01c0f000 {
	/* compatible and clocks are in per SoC .dtsi file */
	compatible = "allwinner,sun7i-a20-mmc";
	reg = <0x01c0f000 0x1000>;
	resets = <&ccu RST_BUS_MMC0>;
	reset-names = "ahb";
	interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
	status = "disabled";
	#address-cells = <1>;
	#size-cells = <0>;
	clocks = <&ccu CLK_BUS_MMC0>,
		 <&ccu CLK_MMC0>,
		 <&ccu CLK_MMC0_OUTPUT>,
		 <&ccu CLK_MMC0_SAMPLE>;
	clock-names = "ahb",
		      "mmc",
		      "output",
		      "sample";
};
           

Host驅動

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,
};

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 },		(1)
	{ .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 */ }
};
           

(1)這裡的compatible和裝置樹中對應,核心初始化時驅動比對上裝置樹,會調用platform_driver 的probe函數。

控制器驅動初始化函數

  首先來看控制器驅動的probe函數:

static int sunxi_mmc_probe(struct platform_device *pdev)
{
	struct sunxi_mmc_host *host;
	struct mmc_host *mmc;
	int ret;

	mmc = mmc_alloc_host(sizeof(struct sunxi_mmc_host), &pdev->dev);
	if (!mmc) {
		dev_err(&pdev->dev, "mmc alloc host failed\n");
		return -ENOMEM;
	}
	......
	mmc->ops		= &sunxi_mmc_ops;
	......
	ret = sunxi_mmc_init_host(host);
	if (ret)
		goto error_free_dma;
	......
	ret = mmc_add_host(mmc);
	......
	return ret;
}
           

  probe函數中首先會申請mmc_host資料結構,調用

mmc_alloc_host

函數申請資料結構記憶體,并設定部分mmc_host資料結構參數。

  随後設定

struct mmc_host

struct mmc_host_ops

資料結構。下面來看下

sunxi_mmc_ops

資料結構:

static const struct mmc_host_ops sunxi_mmc_ops = {
	.request	 = sunxi_mmc_request,
	.set_ios	 = sunxi_mmc_set_ios,
	.get_ro		 = mmc_gpio_get_ro,
	.get_cd		 = mmc_gpio_get_cd,
	.enable_sdio_irq = sunxi_mmc_enable_sdio_irq,
	.start_signal_voltage_switch = sunxi_mmc_volt_switch,
	.hw_reset	 = sunxi_mmc_hw_reset,
	.card_busy	 = sunxi_mmc_card_busy,
};
           

  

struct mmc_host_ops

資料結構用來描述卡控制器操作接口函數功能,用于從主機控制器層向 core 層注冊操作函數,進而将core 層與具體的主機控制器隔離。

  core 要操作主機控制器,就用這個 ops 當中給的函數指針操作,不能直接調用具體主要制器的函數,進而實作邏輯層和硬體驅動層的分離。

  再繼續看

sunxi_mmc_probe

函數,

ret = sunxi_mmc_init_host(host);

這部分代碼,主要是涉及到硬體相關的mmc控制器初始化代碼,這裡就不展開了。

  最後調用

mmc_add_host

,顧名思義,添加一個

mmc_host

。下面來看一下

mmc_add_host

代碼。

sunxi_mmc_probe->mmc_add_host

int mmc_add_host(struct mmc_host *host)
{
	int err;

	WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
		!host->ops->enable_sdio_irq);

	err = device_add(&host->class_dev);						(1)
	if (err)
		return err;

	led_trigger_register_simple(dev_name(&host->class_dev), &host->led);

#ifdef CONFIG_DEBUG_FS
	mmc_add_host_debugfs(host);
#endif

	mmc_start_host(host);									(2)
	mmc_register_pm_notifier(host);

	return 0;
}
           

  很簡單,就是增加了一個 device ,然後就調用 mmc_start_host 了,下面來看

mmc_start_host

sunxi_mmc_probe->mmc_add_host->mmc_start_host

void mmc_start_host(struct mmc_host *host)
{
	host->f_init = max(freqs[0], host->f_min);
	host->rescan_disable = 0;
	host->ios.power_mode = MMC_POWER_UNDEFINED;

	if (!(host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)) {
		mmc_claim_host(host);
		mmc_power_up(host, host->ocr_avail);
		mmc_release_host(host);
	}

	mmc_gpiod_request_cd_irq(host);
	_mmc_detect_change(host, 0, false);
}
           

sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change

static void _mmc_detect_change(struct mmc_host *host, unsigned long delay,
				bool cd_irq)
{
	if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL) &&
		device_can_wakeup(mmc_dev(host)))
		pm_wakeup_event(mmc_dev(host), 5000);

	host->detect_change = 1;
	mmc_schedule_delayed_work(&host->detect, delay);
}
           

sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change-> mmc_schedule_delayed_work

static int mmc_schedule_delayed_work(struct delayed_work *work,
				     unsigned long delay)
{
	return queue_delayed_work(system_freezable_wq, work, delay);
}
           

  

mmc_start_host

代碼很簡單,主要代碼為調用了

_mmc_detect_change

函數。

  

_mmc_detect_change

函數調用

mmc_schedule_delayed_work

  

mmc_schedule_delayed_work

函數最終調用了

queue_delayed_work

  

queue_delayed_work

具體是幹嘛的可以百度,這裡

  這幾個代碼告訴我們在 workqueue 這個工作隊列當中添加一個延遲的工作任務,而這個工作任務就是由 host->detect 來描述的,在随後的 delay 個 jiffies 後會有一個記錄在 host->detect 裡面的函數被執行,上面可以看到

_mmc_detect_change

傳遞的delay參數為0,意思是不延遲?大概是為了預留需要延遲檢測卡的情況。

  代碼執行到這裡

sunxi_mmc_probe

這個函數基本上執行完成了,host(控制器)的初始化完成了。

  但事情還沒有完, workqueue 這個工作隊列還在忙,不一會兒它就會調用 host->detect 裡面那個函數,這個函數到底是哪個函數,到底是用來幹什麼的呢?好像沒有看到,detect 包含在 host 裡面,那估計是在剛才那個申請的地方設定的那個函數,回過頭來看一下

mmc_alloc_host

sunxi_mmc_probe->mmc_alloc_host

struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
	struct mmc_host *host;

	host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
	if (!host)
		return NULL;
	......
	INIT_DELAYED_WORK(&host->detect, mmc_rescan);
	......
	return host;
}
           

  

INIT_DELAYED_WORK

初始化延遲工作隊列,延時結束後調用工作隊列函數,這裡是

mmc_rescan

,這個函數是用來檢測卡的。

繼續閱讀