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来做状态通知和同步
Linux extcon驱动学习_第1张图片

主要的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配置:
Linux extcon驱动学习_第2张图片
driver:
Linux extcon驱动学习_第3张图片
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;
}

你可能感兴趣的:(Linux,设备驱动,linux)