linux下的extcon驱动小析

wuchangjian2021-11-07 01:30:52编程学习

      量产的项目遇到一个问题,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

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。