一、简介
extcon 是External Connector的简称,用于抽象外部连接器,比如说Audio Jack、USB MicroB/TypeC接口等。它的原型是Android的switch-class驱动,经过修改后在kernel 3.4.0版本时被引入内核中。
高通(Qualcomm)平台的USB相关的EXTCON(External Connector)功能主要涉及到了对外部连接器状态的检测和响应机制,特别是当USB端口上有设备连接或断开时。EXTCON框架是Linux内核的一部分,用于统一管理所有类型的外部连接器,包括USB、耳机插孔、Docking接口等。在高通平台中,EXTCON常用于实现USB OTG(On-The-Go)模式的自动切换、USB设备接入的快速响应以及电源管理等功能。
二、功能介绍
extcon驱动的主要功能是识别外部连接器状态变化,并将状态变化通知到与外部连接器相关的其他驱动。
使用extcon驱动,有什么好处呢?之前的内核都没有extcon驱动,又是怎么处理这些外部连接器的?不妨以USB驱动为例,看看使用extcon驱动前后的变化。
USB常见的外部接口有TypeA/B/C三种,其中TypeA/B又有标准A/B、Mini A/B和Micro A/B三种,直接上图:
这三种不同的接口,TypeA/B只是物理信号上的连接,主控芯片内部没有针对TypeA/B的专用控制器,可通过VBUS和ID两个脚的状态来识别是否接入了USB主机或USB外设。接入主机前,VBUS脚上没有电压,接入主机后,主机端会在VBUS脚上提供5V电压;接入外设前,ID脚为高电平,接入外设后,ID脚被拉低。于是软件可以通过主动读取这两个脚的电平或者异步响应这两个脚的中断来获知状态的变化。
TypeC就有点特别,从TypeC规范可以看到,TypeC是有一个状态机的,从Unattached状态走到Attached Sink状态(做从设备)或者Attached Source状态(做主机),主控芯片内部是有相应的控制器的,控制器会通过寄存器汇报状态变化,并产生中断通知主控。TypeC控制器需要软件进行相应的编程来配置和使能它。
以上就是USB针对不同外部接口所面临的状况。在extcon驱动出现之前,同一份USB控制器驱动代码,比较常见的做法就是在设备树(dts)中指明是哪种接口,USB控制器驱动代码中会解析设备树中的定义,通过if...else...来走不同的代码逻辑。如果是MicroB接口,就注册VBUS和ID脚的中断、查询IO脚的电平状态;如果是TypeC接口,就注册TypeC的中断,查询TypeC的状态。假设后续又有新的接口出现,工作原理不同于已有的接口,那就又需要在USB控制器驱动中去增加相关代码。
在extcon驱动出现后,USB控制器驱动就能和外部接口驱动解耦。在USB控制器驱动看来,不管外部接口是什么,我只需知道外部接口状态的变化就好了,比如是否接入主机了、是否有设备接入了。使用extcon驱动提供的函数接口来注册notifier,当外部接口状态变化时,extcon驱动负责回调notifier,USB控制器驱动代码无需再针对不同的外部接口改来改去。不同的外部接口,都用extcon来抽象自己的行为。
三、高通QCM6490平台extcon通知机制实例解析
1、dts说明
qcom,pmic_glink { compatible = "qcom,pmic-glink"; qcom,pmic-glink-channel = "PMIC_RTR_ADSP_APPS"; qcom,subsys-name = "adsp"; qcom,protection-domain = "tms/servreg", "msm/adsp/charger_pd"; battery_charger: qcom,battery_charger { compatible = "qcom,battery-charger"; }; qcom,ucsi { compatible = "qcom,ucsi-glink"; port { usb_port0_connector: endpoint { remote-endpoint = <&usb_port0>; }; }; }; altmode: qcom,altmode { compatible = "qcom,altmode-glink"; #altmode-cells = <1>; }; }; //usb0的外部连接器为battery charger &usb0 { fibo,ss_switch_power = <&tlmm 106 GPIO_ACTIVE_HIGH>; extcon = <&battery_charger>; };
2、驱动的部分主要涉及两个部分
dwc3-msm.c和qti_battery_charger.c
(1)extcon设备的注册
- dwc3-msm.c中extcon注册的过程:
if (of_property_read_bool(node, "extcon") ) { //从dts中读取extcon,查看是否有extcon设备,如果有会进行注册extcon设备 ret = dwc3_msm_extcon_register(mdwc); ............. }
- dwc3_msm_extcon_register注册设备:
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); //读取具体的extcon设备,目前我们dts中的extcon设备为battery_charger 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; //注册id和vbus的通知 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; /* Update initial VBUS/ID state */ if (check_vbus_state && extcon_get_state(edev, EXTCON_USB))
- dwc3_msm_vbus_notifier通知函数:
static int dwc3_msm_vbus_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { struct dwc3 *dwc; struct extcon_dev *edev = ptr; struct extcon_nb *enb = container_of(nb, struct extcon_nb, vbus_nb); struct dwc3_msm *mdwc = enb->mdwc; char *eud_str; const char *edev_name; bool is_cdp; printk(KERN_ERR"***dwc3_msm_vbus_notifier*******"); if (!edev || !mdwc) return NOTIFY_DONE; dwc = platform_get_drvdata(mdwc->dwc3); printk(KERN_ERR"***dwc3_msm_vbus_notifier****extcon idx=%d********", enb->idx); dev_err(mdwc->dev, "vbus:%ld event received\n", event); edev_name = extcon_get_edev_name(edev); dbg_log_string("edev:%s\n", edev_name); /* detect USB spoof disconnect/connect notification with EUD device */ eud_str = strnstr(edev_name, "eud", strlen(edev_name)); printk(KERN_ERR"***dwc3_msm_vbus_notifier****eud_str=%s********",eud_str); if (eud_str) { if (mdwc->eud_active == event) return NOTIFY_DONE; mdwc->eud_active = event; mdwc->check_eud_state = true; } else { if (mdwc->vbus_active == event) return NOTIFY_DONE; mdwc->vbus_active = event; } /* * In case of ADSP based charger detection driving a pulse on * DP to ensure proper CDP detection will be taken care by * ADSP. */ is_cdp = ((mdwc->apsd_source == IIO) && (get_chg_type(mdwc) == POWER_SUPPLY_TYPE_USB_CDP)) || ((mdwc->apsd_source == PSY) && (get_chg_type(mdwc) == POWER_SUPPLY_USB_TYPE_CDP)); /* * Drive a pulse on DP to ensure proper CDP detection * and only when the vbus connect event is a valid one. */ if (is_cdp && mdwc->vbus_active && !mdwc->check_eud_state) { dev_err(mdwc->dev, "Connected to CDP, pull DP up\n"); mdwc->hs_phy->charger_detect(mdwc->hs_phy); } mdwc->ext_idx = enb->idx; if (dwc->dr_mode == USB_DR_MODE_OTG && !mdwc->in_restart) queue_work(mdwc->dwc3_wq, &mdwc->resume_work); return NOTIFY_DONE; }
- dwc3_msm_id_notifier通知函数
static int dwc3_msm_id_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { struct dwc3 *dwc; struct extcon_dev *edev = ptr; struct extcon_nb *enb = container_of(nb, struct extcon_nb, id_nb); struct dwc3_msm *mdwc = enb->mdwc; enum dwc3_id_state id; printk(KERN_ERR"\n*****dwc3_msm_id_notifier****\n"); if (!edev || !mdwc) return NOTIFY_DONE; dwc = platform_get_drvdata(mdwc->dwc3); dbg_event(0xFF, "extcon idx", enb->idx); id = event ? DWC3_ID_GROUND : DWC3_ID_FLOAT; if (mdwc->id_state == id) return NOTIFY_DONE; mdwc->ext_idx = enb->idx; dev_err(mdwc->dev, "host:%ld (id:%d) event received\n", event, id); mdwc->id_state = id; dbg_event(0xFF, "id_state", mdwc->id_state); queue_work(mdwc->dwc3_wq, &mdwc->resume_work); return NOTIFY_DONE; } static void check_for_sdp_connection(struct work_struct *w) { struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, sdp_check.work); struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); if (!mdwc->vbus_active) return; /* floating D+/D- lines detected */ if (dwc->gadget.state < USB_STATE_DEFAULT && dwc3_gadget_get_link_state(dwc) != DWC3_LINK_STATE_CMPLY) { mdwc->vbus_active = false; dbg_event(0xFF, "Q RW SPD CHK", mdwc->vbus_active); queue_work(mdwc->dwc3_wq, &mdwc->resume_work); } }
(2)qti_battery_charger.c中通过extcon_set_state_sync接口去发通知触发dwc3_msm_vbus_notifier和dwc3_msm_id_notifier
标签:dwc3,USB,vbus,extcon,驱动,mdwc,edev From: https://www.cnblogs.com/yuanqiangfei/p/18220743