linux 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芯片通信。

在这里插入图片描述wilc1000wifi芯片,提供sdio slave接口与 sdio host 接口相连。
在这里插入图片描述

mmc core为host控制器驱动提供注册接口,抽象同一API,提供给功能层驱动使用。
在这里插入图片描述

先看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。

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SDIO (Secure Digital Input/Output) 是一种用于将外部设备连接到计算机系统的接口标准。Linux提供了SDIO设备驱动程序,用于管理和控制与系统连接的SDIO设备SDIO设备驱动Linux内核中的实现基于统一的设备模型,以提供设备发现、配置和控制功能。驱动程序负责与硬件通信,并且为用户空间程序提供与SDIO设备交互的接口。 SDIO设备驱动程序的核心功能包括: 1. 设备识别和初始化:驱动程序通过检测SDIO设备的插入和拔出事件来进行设备识别。在插入新的SDIO设备时,驱动程序会通过与设备通信来获取设备的识别信息,并进行必要的初始化设置。 2. 数据传输:驱动程序负责管理和控制从SDIO设备读取数据或向设备写入数据。它处理数据传输的请求和缓冲区管理,以确保数据的正确传输和处理。驱动程序还为用户空间程序提供接口,以便它们可以通过系统调用与SDIO设备进行数据交互。 3. 中断处理:SDIO设备可能会产生中断信号,以通知系统有新的数据可用或发生了特定事件。驱动程序负责处理这些中断,并触发适当的操作或通知用户空间程序。 4. 功耗管理:SDIO设备驱动程序还可以实现功耗管理功能,以控制和优化设备的功耗消耗。它可以根据系统的需求来控制设备的电源状态,例如睡眠模式和唤醒模式之间的切换。 5. 错误处理和调试:驱动程序应具备可靠的错误处理和调试功能,以便在发生错误或异常情况时能够及时识别和处理问题。例如,驱动程序可能需要处理数据传输错误、设备通信失败或设备故障等情况。 总之,LinuxSDIO设备驱动程序提供了一个统一的接口和框架,以便方便地管理和控制与系统连接的SDIO设备。它允许用户空间程序与SDIO设备进行数据交互,并负责处理设备的初始化、数据传输、中断处理、功耗管理和错误处理等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值