最近在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来做状态通知和同步
主要的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:
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;
}