量产的项目遇到一个问题,10%的充电线不充电。将这些不良的充电线接到另一个项目或者其他手机上,能正常充电。问题跟着线走,但为什么这种线接到其他手机能充电呢。
拿到有问题的usb线后,通过WARN_ON/dump_stack函数查看充电流程的堆栈,发现检测充电线插入的方式不一样。一种是通过vbus来检测充电(这种比较容易理解),另一种通过typec中断(typec线的cc脚会进行握手,标准协议)来检测充电线的插拔。由于typec线不良(内部的电阻阻值不对),导致握手失败,进而不能触发typec中断,所以手机不能充电。
故事的背景讲完了,讲下本文故事的主角extcon(USB external connector)。先看两个项目的dts配置,
project1 config
extcon_gpio: extcon-gpio {
compatible = "linux,extcon-usb-gpio";
vbus-gpio = <&pmic_eic 0 GPIO_ACTIVE_HIGH>;
};
&hsphy {
extcon = <&extcon_gpio>;
};
project2 config
pmic_typec: typec@380 {
compatible = "sprd,sc27xx-typec", "sprd,sc2730-typec";
interrupt-parent = <&sc2730_pmic>;
};
&hsphy {
extcon = <&pmic_typec>;
};
先看project1 config是如何检测充电线的插入的。
drivers/extcon/extcon-usb-gpio.c
先分配edev,初始化内核通知链
static const unsigned int usb_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_NONE,
};
static int usb_extcon_probe(struct platform_device *pdev)
{
...
info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
ret = devm_extcon_dev_register(dev, info->edev);
info->vbus_irq = gpiod_to_irq(info->vbus_gpiod);
devm_request_threaded_irq(dev, info->vbus_irq, NULL,usb_irq_handler,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING | IRQF_ONESHOT,pdev->name, info);
...
}
int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev)
{
...
info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);//注册对应的edev
ret = extcon_dev_register(edev);
...
}
int extcon_dev_register(struct extcon_dev *edev)
{
...
edev->nh = devm_kcalloc(&edev->dev, edev->max_supported,sizeof(*edev->nh), GFP_KERNEL);
-
for (index = 0; index < edev->max_supported; index++)
RAW_INIT_NOTIFIER_HEAD(&edev->nh[index]);
RAW_INIT_NOTIFIER_HEAD(&edev->nh_all);
...
}
drivers/usb/phy/phy-sprd-sharkl5.c
根据dts找到edev,并注册到其内核通知链中
static int sprd_hsphy_vbus_notify(struct notifier_block *nb,unsigned long event, void *data)
{
//触发充电流程
}
static int sprd_hsphy_probe(struct platform_device *pdev)
{
...
phy->phy.vbus_nb.notifier_call = sprd_hsphy_vbus_notify;
ret = usb_add_phy_dev(&phy->phy);
...
}
int usb_add_phy_dev(struct usb_phy *x)
{
...
ret = usb_add_extcon(x);
...
}
static int usb_add_extcon(struct usb_phy *x)
{
...
if (of_property_read_bool(x->dev->of_node, "extcon")) {
x->edev = extcon_get_edev_by_phandle(x->dev, 0);//找对对应的edev
if (x->vbus_nb.notifier_call) {
ret = devm_extcon_register_notifier(x->dev, x->edev,EXTCON_USB,&x->vbus_nb);
}
}
...
}
int devm_extcon_register_notifier(struct device *dev, struct extcon_dev *edev,unsigned int id, struct notifier_block *nb)
{
...
ret = extcon_register_notifier(edev, id, nb);
...
}
int extcon_register_notifier(struct extcon_dev *edev, unsigned int id,struct notifier_block *nb)
{
...
idx = find_cable_index_by_id(edev, id);
ret = raw_notifier_chain_register(&edev->nh[idx], nb);
...
}
vbus中断过来后,回调内核通知链函数
INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable);
static irqreturn_t usb_irq_handler(int irq, void *dev_id)
{
...
queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,info->debounce_jiffies);
...
}
static void usb_extcon_detect_cable(struct work_struct *work)
{
...
extcon_set_state_sync(info->edev, EXTCON_USB, true);
...
}
int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, bool state)
{
...
return extcon_sync(edev, id);
...
}
int extcon_sync(struct extcon_dev *edev, unsigned int id)
{
...
index = find_cable_index_by_id(edev, id);
raw_notifier_call_chain(&edev->nh[index], state, edev);
...
}
再看下这个配置
extcon_gpio: extcon-gpio {
compatible = "linux,extcon-usb-gpio";
vbus-gpio = <&pmic_eic 0 GPIO_ACTIVE_HIGH>;//事件触发者--->回调函数
};//注册edev,初始化内核通知链
&hsphy {
extcon = <&extcon_gpio>;//edev为exton_gpio里注册的
};//注册到edev的内核通知链
看到了没,extcon的内核基础就是内核通知链。
extcon还定义了如下设备
/*
* Define the unique id of supported external connectors
*/
#define EXTCON_NONE 0
/* USB external connector */
#define EXTCON_USB 1
#define EXTCON_USB_HOST 2
/*
* Charging external connector
*
* When one SDP charger connector was reported, we should also report
* the USB connector, which means EXTCON_CHG_USB_SDP should always
* appear together with EXTCON_USB. The same as ACA charger connector,
* EXTCON_CHG_USB_ACA would normally appear with EXTCON_USB_HOST.
*
* The EXTCON_CHG_USB_SLOW connector can provide at least 500mA of
* current at 5V. The EXTCON_CHG_USB_FAST connector can provide at
* least 1A of current at 5V.
*/
#define EXTCON_CHG_USB_SDP 5 /* Standard Downstream Port */
#define EXTCON_CHG_USB_DCP 6 /* Dedicated Charging Port */
#define EXTCON_CHG_USB_CDP 7 /* Charging Downstream Port */
#define EXTCON_CHG_USB_ACA 8 /* Accessory Charger Adapter */
#define EXTCON_CHG_USB_FAST 9
#define EXTCON_CHG_USB_SLOW 10
#define EXTCON_CHG_WPT 11 /* Wireless Power Transfer */
#define EXTCON_CHG_USB_PD 12 /* USB Power Delivery */
/* Jack external connector */
#define EXTCON_JACK_MICROPHONE 20
#define EXTCON_JACK_HEADPHONE 21
#define EXTCON_JACK_LINE_IN 22
#define EXTCON_JACK_LINE_OUT 23
#define EXTCON_JACK_VIDEO_IN 24
#define EXTCON_JACK_VIDEO_OUT 25
#define EXTCON_JACK_SPDIF_IN 26 /* Sony Philips Digital InterFace */
#define EXTCON_JACK_SPDIF_OUT 27
/* Display external connector */
#define EXTCON_DISP_HDMI 40 /* High-Definition Multimedia Interface */
#define EXTCON_DISP_MHL 41 /* Mobile High-Definition Link */
#define EXTCON_DISP_DVI 42 /* Digital Visual Interface */
#define EXTCON_DISP_VGA 43 /* Video Graphics Array */
#define EXTCON_DISP_DP 44 /* Display Port */
#define EXTCON_DISP_HMD 45 /* Head-Mounted Display */
/* Miscellaneous external connector */
#define EXTCON_DOCK 60
#define EXTCON_JIG 61
#define EXTCON_MECHANICAL 62
#define EXTCON_NUM 63