Linux extcon驱动学习

最近在chg驱动和usb驱动中经常看见extcon的踪影,打算整理汇总一下extcon相关的知识。
extcon:External Connector framework
从名字看主要表征外部连接器的,通过gpio状态去识别外部连接器的类型,并通知关心外部连接器状态的驱动。
主要驱动代码路径:
kernel/msm-4.19/include/linux/extcon.h
kernel/msm-4.19/drivers/extcon

从下图中可以看到常见的usb 充电 显示接口 耳机等等都可以使用extcon来做状态通知和同步
include/linux/extcon.h

主要的API接口如下:

//获取id当前的连接状态
extern int extcon_get_state(struct extcon_dev *edev, unsigned int id);

/*
 * Following APIs get the property of each external connector.
 * The 'id' argument indicates the defined external connector
 * and the 'prop' indicates the extcon property.
 *
 * And extcon_get_property_capability() get the capability of the property
 * for each external connector. They are used to get the capability of the
 * property of each external connector based on the id and property.
 */
extern int extcon_get_property(struct extcon_dev *edev, unsigned int id,
				unsigned int prop,
				union extcon_property_value *prop_val);
extern int extcon_get_property_capability(struct extcon_dev *edev,
				unsigned int id, unsigned int prop);

/*
 * Following APIs set array of mutually exclusive.
 * The 'exclusive' argument indicates the array of mutually exclusive set
 * of cables that cannot be attached simultaneously.
 */
extern int extcon_set_mutually_exclusive(struct extcon_dev *edev,
				const u32 *exclusive);

/*
 * Following APIs register the notifier block in order to detect
 * the change of both state and property value for each external connector.
 *
 * extcon_register_notifier(*edev, id, *nb) : Register a notifier block
 *			for specific external connector of the extcon.
 * extcon_register_notifier_all(*edev, *nb) : Register a notifier block
 *			for all supported external connectors of the extcon.
 */
extern int extcon_register_notifier(struct extcon_dev *edev, unsigned int id, struct notifier_block *nb);
extern int extcon_unregister_notifier(struct extcon_dev *edev, unsigned int id, struct notifier_block *nb);
extern int extcon_register_blocking_notifier(struct extcon_dev *edev, unsigned int id, struct notifier_block *nb);
extern int extcon_unregister_blocking_notifier(struct extcon_dev *edev, unsigned int id, struct notifier_block *nb);
extern int devm_extcon_register_notifier(struct device *dev,
				struct extcon_dev *edev, unsigned int id,
				struct notifier_block *nb);
extern void devm_extcon_unregister_notifier(struct device *dev,
				struct extcon_dev *edev, unsigned int id,
				struct notifier_block *nb);

extern int extcon_register_notifier_all(struct extcon_dev *edev, struct notifier_block *nb);
extern int extcon_unregister_notifier_all(struct extcon_dev *edev, struct notifier_block *nb);
extern int devm_extcon_register_notifier_all(struct device *dev, struct extcon_dev *edev, struct notifier_block *nb);
extern void devm_extcon_unregister_notifier_all(struct device *dev, struct extcon_dev *edev, struct notifier_block *nb);

/*
 * Following APIs get the extcon_dev from devicetree or by through extcon name.
 */
extern struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name);
extern struct extcon_dev *extcon_find_edev_by_node(struct device_node *node);
extern struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index);

/* Following API get the name of extcon device. */
extern const char *extcon_get_edev_name(struct extcon_dev *edev);

extern int extcon_blocking_sync(struct extcon_dev *edev, unsigned int id, u8 val);

用法实例:

static const unsigned int smblib_extcon_cable[] = {
	EXTCON_USB,
	EXTCON_USB_HOST,
	EXTCON_NONE,
};

/* extcon alloc*/
chg->extcon = devm_extcon_dev_allocate(chg->dev, smblib_extcon_cable);

/* extcon registration */
rc = devm_extcon_dev_register(chg->dev, chg->extcon);

/* Support reporting polarity and speed via properties */
/*EXTCON_USB/EXTCON_USB_HOST 支持设置typec的方向还有usb的速度 */
rc = extcon_set_property_capability(chg->extcon, EXTCON_USB, EXTCON_PROP_USB_TYPEC_POLARITY);
rc |= extcon_set_property_capability(chg->extcon, EXTCON_USB, EXTCON_PROP_USB_SS);
rc |= extcon_set_property_capability(chg->extcon, EXTCON_USB_HOST,  EXTCON_PROP_USB_TYPEC_POLARITY);
rc |= extcon_set_property_capability(chg->extcon, EXTCON_USB_HOST, EXTCON_PROP_USB_SS);

/*设置EXTCON_USB 的EXTCON_PROP_USB_TYPEC_POLARITY 属性为 val*/
extcon_set_property(chg->extcon, EXTCON_USB, EXTCON_PROP_USB_TYPEC_POLARITY, val);
/*读取EXTCON_USB 的EXTCON_PROP_USB_TYPEC_POLARITY 属性*/
ret = extcon_get_property(edev, EXTCON_USB, EXTCON_PROP_USB_SS, &val);

下面完整看下手机里面的extcon部分驱动:
dts配置:
在这里插入图片描述
driver:
extcon-usb-gpio.c
usb_extcon_probe关键函数如下:解析dts的gpio配置并注册中断usb_irq_handler

static const unsigned int usb_extcon_cable[] = {
	EXTCON_USB,
	EXTCON_USB_HOST,
	EXTCON_NONE,
};

static int usb_extcon_probe(struct platform_device *pdev)
{
	......
	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);

	info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN);
	info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus", GPIOD_IN);
	info->vbus_out_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus-out", GPIOD_OUT_HIGH);
	.....
	info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
	ret = devm_extcon_dev_register(dev, info->edev);

	INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable);

	if (info->id_gpiod) {
		info->id_irq = gpiod_to_irq(info->id_gpiod);
		ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
						usb_irq_handler,
						IRQF_TRIGGER_RISING |
						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
						pdev->name, info);
	}
	if (info->vbus_gpiod) {
		info->vbus_irq = gpiod_to_irq(info->vbus_gpiod);
		ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
						usb_irq_handler,
						IRQF_TRIGGER_RISING |
						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
						pdev->name, info);
	}

	/* Perform initial detection */
	usb_extcon_detect_cable(&info->wq_detcable.work);
	return 0;
}

在中断函数usb_irq_handler里面根据id pin和vbus状态区分设置usb DRD 模式的切换(作为host或者device)

static irqreturn_t usb_irq_handler(int irq, void *dev_id)
{
	.......
	queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, info->debounce_jiffies);
	return IRQ_HANDLED;
}

static void usb_extcon_detect_cable(struct work_struct *work)
{

	/* check ID and VBUS and update cable state */
	id = info->id_gpiod ? gpiod_get_value_cansleep(info->id_gpiod) : 1;
	vbus = info->vbus_gpiod ? gpiod_get_value_cansleep(info->vbus_gpiod) : id;

	/* at first we clean states which are no longer active */
	if (id) {
		if (info->vbus_out_gpiod)
			gpiod_set_value_cansleep(info->vbus_out_gpiod, 0);
		extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false);
	}
	if (!vbus)
		extcon_set_state_sync(info->edev, EXTCON_USB, false);

	if (!id) {
		if (info->vbus_out_gpiod)
			gpiod_set_value_cansleep(info->vbus_out_gpiod, 1);
		extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true);
	} else {
		if (vbus)
			extcon_set_state_sync(info->edev, EXTCON_USB, true);
	}
}

再其他驱动(dwc3-msm.c)里面也可以注册特定extcon的notifier回调函数:


static int dwc3_msm_extcon_register(struct dwc3_msm *mdwc)
{
	struct device_node *node = mdwc->dev->of_node;
	struct extcon_dev *edev;
	int idx, extcon_cnt, ret = 0;
	bool check_vbus_state, check_id_state, phandle_found = false;

	extcon_cnt = of_count_phandle_with_args(node, "extcon", NULL);
	if (extcon_cnt < 0) {
		dev_err(mdwc->dev, "of_count_phandle_with_args failed\n");
		return -ENODEV;
	}

	mdwc->extcon = devm_kcalloc(mdwc->dev, extcon_cnt, sizeof(*mdwc->extcon), GFP_KERNEL);
	if (!mdwc->extcon)
		return -ENOMEM;

	for (idx = 0; idx < extcon_cnt; idx++) {
		edev = extcon_get_edev_by_phandle(mdwc->dev, idx);
		if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV)
			return PTR_ERR(edev);

		if (IS_ERR_OR_NULL(edev))
			continue;

		check_vbus_state = check_id_state = true;
		phandle_found = true;

		mdwc->extcon[idx].mdwc = mdwc;
		mdwc->extcon[idx].edev = edev;
		mdwc->extcon[idx].idx = idx;

		mdwc->extcon[idx].vbus_nb.notifier_call = dwc3_msm_vbus_notifier;
		ret = extcon_register_notifier(edev, EXTCON_USB, &mdwc->extcon[idx].vbus_nb);
		if (ret < 0)
			check_vbus_state = false;

		mdwc->extcon[idx].id_nb.notifier_call = dwc3_msm_id_notifier;
		ret = extcon_register_notifier(edev, EXTCON_USB_HOST, &mdwc->extcon[idx].id_nb);
		if (ret < 0)
			check_id_state = false;

		mdwc->extcon[idx].blocking_sync_nb.notifier_call = dwc3_usb_blocking_sync;
		extcon_register_blocking_notifier(edev, EXTCON_USB_HOST, &mdwc->extcon[idx].blocking_sync_nb);

		/* Update initial VBUS/ID state */
		if (check_vbus_state && extcon_get_state(edev, EXTCON_USB))
			dwc3_msm_vbus_notifier(&mdwc->extcon[idx].vbus_nb, true, edev);
		else  if (check_id_state &&
				extcon_get_state(edev, EXTCON_USB_HOST))
			dwc3_msm_id_notifier(&mdwc->extcon[idx].id_nb, true, edev);
	}

	if (!phandle_found) {
		dev_err(mdwc->dev, "no extcon device found\n");
		return -ENODEV;
	}

	return 0;
}
  • 4
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux设备驱动学习涉及到理解Linux内核中的设备模型以及设备驱动的开发流程Linux设备模型的目的是为内核建立起一个统一的设备模型,通过对硬件设备的归纳、分类和抽象,简化设备驱动的开发。设备模型主要支持电源管理、系统关机、与用户空间的通讯、热插拔设备、设备类型以及对象生命周期等任务。 在学习Linux设备驱动时,需要了解设备驱动程序的注册和注销过程。对于PCI设备驱动,注册过程包括将驱动程序的总线指向pci_bus_type,并将probe和remove函数指向PCI核心内的相关函数,同时设置驱动程序的属性文件。然后使用driver_register函数注册驱动程序到内核中。 设备驱动的开发还需要了解设备驱动的删除过程。对于PCI驱动,删除设备驱动程序需要调用pci_unregister_driver函数,该函数使用传递给它的struct pci_driver指针调用驱动核心函数driver_unregister。在driver_unregister函数中,会清理与驱动相关的sysfs属性,并为连接到该驱动的设备调用release函数进行清理。 此外,学习Linux设备驱动还需要了解设备的探测过程。在PCI总线中,当一个PCI设备被发现时,PCI核心会在内存中创建一个pci_dev类型的结构变量。该结构变量包含设备的各种信息,如设备号、厂商ID、设备ID、子系统厂商ID、子系统设备ID、设备类别等。 总结来说,学习Linux设备驱动需要理解Linux设备模型、设备驱动的注册和注销过程,以及设备的探测过程。通过深入学习这些内容,可以掌握设备驱动开发的基本原理和技巧。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Linux设备驱动程序学习(十三)——Linux设备驱动模型](https://blog.csdn.net/baidu_38661691/article/details/95642000)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值