天天看點

linux sdio驅動

文章目錄

      • 架構
      • sdio控制器驅動

架構

MMC/SD裝置驅動在Linux中的結構層次

在Linux中MMC/SD卡的記憶體都當作塊裝置。MMC/SD裝置驅動代碼在drivers\mmc 分别有card、core和host三個檔案夾,

card層 存放閃存卡(塊裝置)的相關驅動,如MMC/SD卡裝置驅動

core層 MMC的核心層,抽象了mmc驅動的公共部分,完成不同協定和規範的實作,為host層和裝置驅動層提供接口函數

host層 mmc/sd/sdio主機控制器代碼

sdio接口用來連接配接主機和裝置,如wifi,gps等。主機中的wifi,gps驅動通過sdio接口和wifi晶片,gps晶片通信。

linux sdio驅動

wilc1000wifi晶片,提供sdio slave接口與 sdio host 接口相連。

linux sdio驅動

mmc core為host控制器驅動提供注冊接口,抽象同一API,提供給功能層驅動使用。

linux sdio驅動

先看sdio控制器層驅動

sdio控制器驅動

samsung,exynos5250-dw-mshc驅動為例

host控制器裝置是以platform_device類型注冊到platform總線,同時host控制器驅動以platform_driver注冊到到platform總線,無論是platform_device或platform_driver注冊到platform總線都進行probe。

[ /include/linux/mmc/host.h ]

struct mmc_host     用來描述卡控制器

struct mmc_card     用來描述卡

struct mmc_driver  用來描述 mmc 卡驅動

struct sdio_func      用來描述 功能裝置

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

dts中device描述:

dwmmc_3: dwmmc3@12230000 {
	compatible = "samsung,exynos5250-dw-mshc";
	reg = <0x12230000 0x1000>;
	interrupts = <0 78 0>;
	#address-cells = <1>;
	#size-cells = <0>;
	clocks = <&clock 283>, <&clock 142>;
	clock-names = "biu", "ciu";
};


/*
 * On Snow we've got SIP WiFi and so can keep drive strengths low to
 * reduce EMI.
 */
dwmmc3@12230000 {
	slot@0 {
		pinctrl-names = "default";
		pinctrl-0 = <&sd3_clk &sd3_cmd &sd3_bus4>;
	};
};
           

驅動注冊

static const struct dw_mci_drv_data exynos_drv_data = {
	.caps			= exynos_dwmmc_caps,
	.init			= dw_mci_exynos_priv_init,
	.setup_clock		= dw_mci_exynos_setup_clock,
	.prepare_command	= dw_mci_exynos_prepare_command,
	.set_ios		= dw_mci_exynos_set_ios,
	.parse_dt		= dw_mci_exynos_parse_dt,
};

static const struct of_device_id dw_mci_exynos_match[] = {
	{ .compatible = "samsung,exynos4412-dw-mshc",
			.data = &exynos_drv_data, },
	{ .compatible = "samsung,exynos5250-dw-mshc",
			.data = &exynos_drv_data, },
	{},
};
MODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
static struct platform_driver dw_mci_exynos_pltfm_driver = {
	.probe		= dw_mci_exynos_probe,
	.remove		= __exit_p(dw_mci_pltfm_remove),
	.driver		= {
		.name		= "dwmmc_exynos",
		.of_match_table	= dw_mci_exynos_match,
		.pm		= &dw_mci_pltfm_pmops,
	},
};

module_platform_driver(dw_mci_exynos_pltfm_driver); //驅動注冊
           

通過compatible = "samsung,exynos4412-dw-mshc ,在platform bus上比對成功後,調用dw_mci_exynos_probe;

static int dw_mci_exynos_probe(struct platform_device *pdev)
{
	const struct dw_mci_drv_data *drv_data;
	const struct of_device_id *match;

	match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
	drv_data = match->data; //driver data:exynos_drv_data
	return dw_mci_pltfm_register(pdev, drv_data);
}
           

struct dw_mci *host; 主機控制器驅動層定義的管理結構;而struct mmc_host 是core層抽象的主機控制器通用管理結構。

dw_mci_pltfm_register中先配置設定struct dw_mci *host; 擷取dts中定義各種控制器裝置資源:io位址,中斷号等。

然後調用dw_mci_probe,dw_mci_probe先完成控制器初始化配置,時鐘,電壓。dw_mci_init_slot初始化slot

int dw_mci_probe(struct dw_mci *host)
{
	const struct dw_mci_drv_data *drv_data = host->drv_data;
	int width, i, ret = 0;
	u32 fifo_size;
	int init_slots = 0;
	u32 msize;

......
	tasklet_init(&host->tasklet, dw_mci_tasklet_func, (unsigned long)host);//注冊tasklet中斷上半部,處理各種中斷
	host->card_workqueue = alloc_workqueue("dw-mci-card",
			WQ_MEM_RECLAIM | WQ_NON_REENTRANT, 1);
	if (!host->card_workqueue)
		goto err_dmaunmap;
	INIT_WORK(&host->card_work, dw_mci_work_routine_card); //注冊workqueue中斷上半部,主要處理卡檢測
	ret = devm_request_irq(host->dev, host->irq, dw_mci_interrupt, //注冊硬體中斷
			       host->irq_flags, "dw-mci", host);
........
	/* We need at least one slot to succeed */
	for (i = 0; i < host->num_slots; i++) {
		ret = dw_mci_init_slot(host, i); //初始化slot
		if (ret)
			dev_dbg(host->dev, "slot %d init failed\n", i);
		else
			init_slots++;
	}

。。。。
}
           

dw_mci_init_slot:

主要 mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev);配置設定 struct mmc_host *mmc;

mmc_add_host-》mmc_start_host 控制器初始完畢,開始工作;

其中,mmc_alloc_host中會 INIT_DELAYED_WORK(&host->detect, mmc_rescan); 建立卡檢測work;

void mmc_start_host(struct mmc_host *host)
{
	host->f_init = max(freqs[0], host->f_min);
	host->rescan_disable = 0;
	if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)
		mmc_power_off(host);
	else
		mmc_power_up(host);
	mmc_detect_change(host, 0); //控制器開始工作後,主動探測卡是否存在
}
mmc_detect_change--》mmc_schedule_delayed_work(&host->detect, delay); 喚醒mmc_rescan卡掃描,後面再描述

           

中斷處理

發生中斷後,會回調dw_mci_interrupt,讀取中斷狀态寄存器,檢視中斷源,進行不同處理。例如:指令發送完成,資料發送完成,卡連接配接,錯誤處理等。

static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
{
	struct dw_mci *host = dev_id;
	u32 pending;
	int i;

	pending = mci_readl(host, MINTSTS); /* read-only mask reg */

	if (pending) {

		/*
		 * DTO fix - version 2.10a and below, and only if internal DMA
		 * is configured.
		 */
		if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO) {
			if (!pending &&
				((mci_readl(host, STATUS) >> 17) & 0x1fff))
				pending |= SDMMC_INT_DATA_OVER;
		}

		if (pending & SDMMC_INT_CMD_DONE) {
			u32 cmd = mci_readl(host, CMD) & 0x3f;
			if (cmd == SD_SWITCH_VOLTAGE &&
				!(mci_readl(host, STATUS) & SDMMC_DATA_BUSY)) {
				pending |= SDMMC_INT_RTO;
			}
		}

		if (pending & SDMMC_INT_HLE) {
			mci_writel(host, RINTSTS, SDMMC_INT_HLE);
			host->cmd_status = pending;
			tasklet_schedule(&host->tasklet);
		}

		if (pending & DW_MCI_CMD_ERROR_FLAGS) {
			mci_writel(host, RINTSTS, DW_MCI_CMD_ERROR_FLAGS);
			host->cmd_status = pending;
			smp_wmb();
			set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
		}

		if (pending & SDMMC_INT_VOLT_SW) {
			u32 cmd = mci_readl(host, CMD) & 0x3f;
			if (cmd == SD_SWITCH_VOLTAGE) {
				mci_writel(host, RINTSTS, SDMMC_INT_VOLT_SW);
				dw_mci_cmd_interrupt(host, pending);
			}
		}

		if (pending & DW_MCI_DATA_ERROR_FLAGS) {
			/* if there is an error report DATA_ERROR */
			mci_writel(host, RINTSTS, DW_MCI_DATA_ERROR_FLAGS);
			host->data_status = pending;
			smp_wmb();
			set_bit(EVENT_DATA_ERROR, &host->pending_events);
			tasklet_schedule(&host->tasklet);
		}

		if (pending & SDMMC_INT_DATA_OVER) {
			if (host->quirks & DW_MCI_QUIRK_BROKEN_DTO)
				del_timer(&host->dto_timer);

			mci_writel(host, RINTSTS, SDMMC_INT_DATA_OVER);
			if (!host->data_status)
				host->data_status = pending;
			smp_wmb();
			if (host->dir_status == DW_MCI_RECV_STATUS) {
				if (host->sg != NULL)
					dw_mci_read_data_pio(host, true);
			}
			set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
			tasklet_schedule(&host->tasklet);
		}

		if (pending & SDMMC_INT_RXDR) {
			mci_writel(host, RINTSTS, SDMMC_INT_RXDR);
			if (host->dir_status == DW_MCI_RECV_STATUS && host->sg) {
				dw_mci_read_data_pio(host, false);
			} else {
				if (host->hw_mmc_id == DWMMC_SD_ID && !host->sg) {
					printk(KERN_DEBUG"mmc%d:debug error:host.sg=%p.cmd%d\n",
							host->hw_mmc_id, host->sg,mci_readl(host, CMD) & 0x3F);
					dw_mci_fifo_reset(host->dev, host);
					dw_mci_ciu_reset(host->dev, host);
				}
			}
		}

		if (pending & SDMMC_INT_TXDR) {
			mci_writel(host, RINTSTS, SDMMC_INT_TXDR);
			if (host->dir_status == DW_MCI_SEND_STATUS && host->sg)
				dw_mci_write_data_pio(host);
		}

		if (pending & SDMMC_INT_CMD_DONE) {
			mci_writel(host, RINTSTS, SDMMC_INT_CMD_DONE);
			dw_mci_cmd_interrupt(host, pending);
		}

		if (pending & SDMMC_INT_CD) { //卡檢測中
			mci_writel(host, RINTSTS, SDMMC_INT_CD);
			queue_work(host->card_workqueue, &host->card_work);//喚醒card_workqueue隊列工作
		}

		/* Handle SDIO Interrupts */
		for (i = 0; i < host->num_slots; i++) {
			struct dw_mci_slot *slot = host->slot[i];
			if (pending & SDMMC_INT_SDIO(i)) {
				mci_writel(host, RINTSTS, SDMMC_INT_SDIO(i));
				mmc_signal_sdio_irq(slot->mmc);
			}
		}

	}

#ifdef CONFIG_MMC_DW_IDMAC
	/* Handle DMA interrupts */
	pending = mci_readl(host, IDSTS);
	if (pending & (SDMMC_IDMAC_INT_TI | SDMMC_IDMAC_INT_RI)) {
		mci_writel(host, IDSTS, SDMMC_IDMAC_INT_TI | SDMMC_IDMAC_INT_RI);
		mci_writel(host, IDSTS, SDMMC_IDMAC_INT_NI);
		host->dma_ops->complete(host);
	}
#endif

	return IRQ_HANDLED;
}
           

bit 15:結束位錯誤/CRC 錯誤

bit 14:自動指令完成(ACD)

bit 13:起始位錯誤(SBE) /忙退出

中斷 0(BCI0)。

bit 12:硬體鎖定寫錯誤(HLE)

bit 11: FIFO 下溢出/上溢出錯誤

(FRUN)

bit 10:主機逾時引起的資料缺乏

(HTO) /電壓切換中斷

bit 9:資料讀逾時(DRTO) /Boot 數

據開始(BDS)

bit 8:響應逾時(RTO) /Boot 接收

響應(BAR)

bit 7:資料 CRC 錯誤(DCRC)

bit 6:響應 CRC 錯誤(RCRC)

bit 5:接收 FIFO 資料請求(RXDR)

bit 4:發送 FIFO 資料請求(TXDR)

bit 3:資料傳輸結束(DTO)

bit 2:指令完成(CD)

bit 1:響應錯誤(RE)

bit 0:卡檢測(CDT)

卡檢測

host->card_workqueue–》

dw_mci_work_routine_card:讀取控制器寄存器 present = dw_mci_get_cd(mmc);擷取卡狀态,插入或拔出;

卡存在:

mmc_detect_change–>mmc_schedule_delayed_work(&host->detect, delay); 喚醒mmc_alloc_host中建立的 INIT_DELAYED_WORK(&host->detect, mmc_rescan);

mmc_rescan()
{
....
  //以不同頻率掃描卡static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };
  //MMC标準預設為400KHZ
	for (i = 0; i < ARRAY_SIZE(freqs); i++) {
		if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) {
			extend_wakelock = true;
			break;
		}
		if (freqs[i] <= host->f_min)
			break;
	}
.....
}
           
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
	host->f_init = freq;

	//host上電
	mmc_power_up(host);

	/*
	 * Some eMMCs (with VCCQ always on) may not be reset after power up, so
	 * do a hardware reset if possible.
	 */
	mmc_hw_reset_for_init(host); //複位硬體

	/*
	 * sdio_reset sends CMD52 to reset card.  Since we do not know
	 * if the card is being re-initialized, just send it.  CMD52
	 * should be ignored by SD/eMMC cards.
	 */
//如果目标卡是純SD卡則目标卡不會應答,一般主機host的寄存器會報錯,但是這個無關緊要,可以不理它。如果目标卡是純SDIO卡,那麼這裡就是複位SDIO卡,通過指令CMD52來實作的。③如果目标卡是SD卡和SDIO卡的組合卡,則需要先發送CMD52來複位SDIO卡,再複位SD卡,因為CMD52要先于CMD0發送。
	sdio_reset(host);
	mmc_go_idle(host);//發送CMD0,讓裝置進入IDLE模式

	mmc_send_if_cond(host, host->ocr_avail);//發送CMD8,擷取該卡所支援的電壓值

	/* Order's important: probe SDIO, then SD, then MMC */
	識别卡類型
   首先發送 CMD5。如果收到一個響應,那麼該卡是 SDIO。否則發送 ACMD41;如果收到一個響應,那麼該卡是 SD。 否則,該卡是 MMC。
	if (!mmc_attach_sdio(host))
		return 0;
	if (!mmc_attach_sd(host))
		return 0;
	if (!mmc_attach_mmc(host))
		return 0;

	mmc_power_off(host);
	return -EIO;
}
           

SDIO類型初始化

/*
 * Starting point for SDIO card init.
 */
int mmc_attach_sdio(struct mmc_host *host)
{
	int err, i, funcs;
	u32 ocr;
	struct mmc_card *card;


	//發送cmd5,如果收到響應就是sdio卡,否則不是直接傳回; 收到響應為R4訓示sdio卡使用的電壓
	err = mmc_send_io_op_cond(host, 0, &ocr);
	if (err)
		return err;
    //設定mmc host中bus_ops為sdio ops
	mmc_attach_bus(host, &mmc_sdio_ops);
	if (host->ocr_avail_sdio)
		host->ocr_avail = host->ocr_avail_sdio;

	/*
	 * Sanity check the voltages that the card claims to
	 * support.
	 */
	 cmd5時傳回電壓
	if (ocr & 0x7F) {
		pr_warning("%s: card claims to support voltages "
		       "below the defined range. These will be ignored.\n",
		       mmc_hostname(host));
		ocr &= ~0x7F;
	}
    //設定電壓值,需要sdio控制器支援的電壓和sdio卡比對
	host->ocr = mmc_select_voltage(host, ocr);




    //初始化卡,裡面先配置設定card類型結構,struct mmc_card card = mmc_alloc_card(host, NULL);并判斷sdio對應device是否為存儲還是其它功能(wifi,gps等),設定card->type = MMC_TYPE_SD_COMBO;或card->type = MMC_TYPE_SDIO;并且擷取mmc_send_relative_addr(host, &card->rca);裝置位址儲存到card->rca
	err = mmc_sdio_init_card(host, host->ocr, NULL, 0);

	card = host->card;



	/*
	 * The number of functions on the card is encoded inside
	 * the ocr.
	 */
	funcs = (ocr & 0x70000000) >> 28; //sdio device上具備的功能個數,例如wifi裝置
	card->sdio_funcs = 0;



	/*
	 * Initialize (but don't add) all present functions.
	 */
	for (i = 0; i < funcs; i++, card->sdio_funcs++) {
			//初始化化sdio 對應卡裝置上功能,配置設定了struct sdio_func,sdio_alloc_func(card);
			err = sdio_init_func(host->card, i + 1);

	}

	/*
	 * First add the card to the driver model...
	 */
	mmc_release_host(host);
	err = mmc_add_card(host->card); //把card裝置注冊到系統中


	/*
	 * ...then the SDIO functions.
	 */
	for (i = 0;i < funcs;i++) {
		err = sdio_add_func(host->card->sdio_func[i]); //把func功能注冊到裝置上
		if (err)
			goto remove_added;
	}

	mmc_claim_host(host);
	return 0;
}
           

sdio_init_func(host->card, i + 1);

初始化功能并注冊到系統中,在/sys/bus/sdio/devices,

mmc1:0001:1 表示host控制器1:連接配接的裝置1:裝置1上功能1

mmc1:0001:2 表示host控制器1:連接配接的裝置1:裝置1上功能2

配置設定struct mmc_card

/*
 * Allocate and initialise a new MMC card structure.
 */
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{
	struct mmc_card *card;

	card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);


	card->host = host;

	device_initialize(&card->dev);

	card->dev.parent = mmc_classdev(host);
	card->dev.bus = &mmc_bus_type;//設定卡裝置bus為mmc_bus_type
	card->dev.release = mmc_release_card;
	card->dev.type = type;

	return card;
}


static struct bus_type mmc_bus_type = {
	.name		= "mmc",
	.dev_attrs	= mmc_dev_attrs,
	.match		= mmc_bus_match,
	.uevent		= mmc_bus_uevent,
	.probe		= mmc_bus_probe,
	.remove		= mmc_bus_remove,
	.pm		= &mmc_bus_pm_ops,
};
/*
 * This currently matches any MMC driver to any MMC card - drivers
 * themselves make the decision whether to drive this card in their
 * probe method.
 */
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
	return 1; //預設傳回1,任何mmc card和mmc驅動都能夠比對
}

           

mmc_blk_init–>mmc_register_driver(&mmc_driver);–>drv->drv.bus = &mmc_bus_type;–>driver_register(&drv->drv);

注冊了mmc_bus_type 類型mmc card驅動會和 sdio card裝置 比對到。

func配置設定struct sdio_func

/*
 * Allocate and initialise a new SDIO function structure.
 */
struct sdio_func *sdio_alloc_func(struct mmc_card *card)
{
	struct sdio_func *func;

	func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL);

	func->card = card;

	device_initialize(&func->dev);

	func->dev.parent = &card->dev;//父節點為card裝置
	func->dev.bus = &sdio_bus_type; //bus類型為sdio_bus_type
	func->dev.release = sdio_release_func;

	return func;
}

static struct bus_type sdio_bus_type = {
	.name		= "sdio",
	.dev_attrs	= sdio_dev_attrs,
	.match		= sdio_bus_match,
	.uevent		= sdio_bus_uevent,
	.probe		= sdio_bus_probe,
	.remove		= sdio_bus_remove,
	.pm		= SDIO_PM_OPS_PTR,
};

static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
	struct sdio_driver *sdrv)
{
	const struct sdio_device_id *ids;

	ids = sdrv->id_table;

	if (ids) {
		while (ids->class || ids->vendor || ids->device) { 讀取card func中id和driver中id比對
			if (sdio_match_one(func, ids))
				return ids;
			ids++;
		}
	}

	return NULL;
}

           

func為sdio_bus_type 注冊到sdio bus中,和系統中注冊的driver進行比對。然後調用驅動的probe。