1,从主机发来的设置包,被设备控制器接收完成。产生一个UDC中断(完成中断)
2,完成处理函数将会去检查这个中断是哪个端点中断产生的。(这个在端点完成寄存器中)
从而调用相应的端点处理函数(譬如控制端点0会去调用udc_control_in_isr或者udc_control_out_isr,而普通的端点将会调用udc_data_out_isr或者
udc_data_in_isr),在处理这些端点的事务完成后就会产生一个complete的调度,它会将请求从端点的队列中移除。
3,如果DCD的setup处理函数不支持这个包,它将会把它移交给安卓gadget驱动层处理。
4,在安卓gadget驱动层中调用android_setup()将这个设置包交给composite gadget驱动层的composite_setup()来处理。
5,在这个处理中会去调用usb_ep_queue()将这个设置请求包所请求的描述符提交给相应的端点。这个调用将会进入DCD层。
对于厂商特定的请求,一开始会和标准的SETUP请求的流程相同
1,从主机发来的设置包,被设备控制器接收完成。产生一个UDC中断(完成中断)
2,完成处理函数将会去检查这个中断是哪个端点中断产生的。(这个在端点完成寄存器中)
从而调用相应的端点处理函数(譬如控制端点0会去调用udc_control_in_isr或者udc_control_out_isr,而普通的端点将会调用udc_data_out_isr或者
udc_data_in_isr),在处理这些端点的事务完成后就会产生一个complete的调度,它会将请求从端点的队列中移除。
3,如果DCD的setup处理函数不支持这个包,它将会把它移交给安卓gadget驱动层处理。
4,在安卓gadget驱动层中调用android_setup()将这个设置包交给composite gadget驱动层的composite_setup()来处理。
5,每个功能驱动可能注册了一个setup()回调函数,这个回调函数将会被使用setup包中特定的这个接口(譬如接口1)的composite驱动调用。
也就是这个setup包需要在我们特定的功能驱动层中所对应的setup()函数中处理,
6,最后也是通过调用usb_ep_queue()将这个设置请求包所请求的描述符提交给相应的端点。这个调用将会进入DCD层。
2.9 DWC3驱动
DWC3 OTG驱动和之前存在的legacyOTG驱动非常接近。然而,也有一些改变,譬如所有的驱动在状态机中是如何连接和改变的。这部分描述如下:
1,初始化
2,DWC3驱动之间的交流
3,DWC3 OTG状态机
4,USB vbus 事件流程
5,USB ID 事件流程
2.9.1 DWC3 OTG 初始化
两个文件与初始化相关 dwc3下的core.c 和dwc3-msm.c
这两个驱动文件都有自己的probe函数,因此在设备树文件中需要两个不同的平台设备。
usb3: qcom,ssusb@f9200000 {
compatible = "qcom,dwc-usb3-msm";
reg = <0xf9200000 0xfc000>,
<0xfd4ab000 0x4>;
#address-cells = <1>;
#size-cells = <1>;
ranges;
interrupt-parent = <&usb3>;
interrupts = <0 1>;
#interrupt-cells = <1>;
interrupt-map-mask = <0x0 0xffffffff>;
interrupt-map = <0x0 0 &intc 0 133 0
0x0 1 &spmi_bus 0x0 0x0 0x9 0x0>;
interrupt-names = "hs_phy_irq", "pmic_id_irq";
ssusb_vdd_dig-supply = <&pm8841_s2_corner>;
SSUSB_1p8-supply = <&pm8941_l6>;
hsusb_vdd_dig-supply = <&pm8841_s2_corner>;
HSUSB_1p8-supply = <&pm8941_l6>;
HSUSB_3p3-supply = <&pm8941_l24>;
vbus_dwc3-supply = <&pm8941_mvs1>;
qcom,dwc-usb3-msm-dbm-eps = <4>;
qcom,vdd-voltage-level = <1 5 7>;
qcom,dwc-hsphy-init = <0x00D191A4>;
qcom,misc-ref = <&pm8941_misc>;
dwc_usb3-adc_tm = <&pm8941_adc_tm>;
qcom,dwc-usb3-msm-tx-fifo-size = <29696>;
qcom,dwc-usb3-msm-qdss-tx-fifo-size = <16384>;
qcom,msm-bus,name = "usb3";
qcom,msm-bus,num-cases = <2>;
qcom,msm-bus,num-paths = <1>;
qcom,msm-bus,vectors-KBps =
<61 512 0 0>,
<61 512 240000 960000>;
dwc3@f9200000 {
compatible = "synopsys,dwc3";
reg = <0xf9200000 0xfc000>;
interrupt-parent = <&intc>;
interrupts = <0 131 0>, <0 179 0>;
interrupt-names = "irq", "otg_irq";
tx-fifo-resize;
};
};
根节点标记为qcom,ssusb,子节点定义为dwc3.这个结构体描述的方式就像内核中的设备相关的方式一样,例如dwc3有一个父节点qcom,ssusb。
dwc3-msm.c对应qcom,dwc-usb3-msm,core.c对应的是synopsys,dwc3。
2.9.1.1 DWC3 MSM驱动初始化
由于根节点和dwc3-msm驱动对应,所以先初始化dwc3-msm.c。这个probe()负责完成下面的工作:
1,初始化驱动相关的资源
work queues ---- chg_work, id_work, resume_work, etc. (platform-specific)
2,初始化并且使能所需要的时钟,用于USBcore的
Xo_clk, iface_clk, sleep_clk, etc. (platform-specific)
3,初始化regulator用于控制器,HSPHY 和 SSPHY
4,初始化并且注册驱动相关的中断处理函数
5,注册设备到PM运行时层
6,注册并且设置bus scaling for SNOC
7,读出设备树接口的参数并且填充各自的变量
DWC3 MSM 驱动只用来负责处理一些MSM特定的设置和资源
2.9.1.1.1 regulator 初始化
在初始化完时钟后,USBregulator需要被开启用来给控制器和 PHY 供电。每个PHY 有自己的regulator源,但是他们都来自于相同的PMIC regulator
HSPHY regulators
3.3 V rail 1.8 V rail 1.1 V rail
SSPHY regulators
1.8 V rail 1.1 V rail
SSPHY 和 HSPHY 共享来自于PMIC的相同的1.8V和1.1V的regulator源。这些regulator被定义在如下面的设备树文件中:
msm8974.dtsi
ssusb_vdd_dig-supply = <&pm8841_s2_corner>;
SSUSB_1p8-supply = <&pm8941_l6>;
hsusb_vdd_dig-supply = <&pm8841_s2_corner>;
HSUSB_1p8-supply = <&pm8941_l6>;
HSUSB_3p3-supply = <&pm8941_l24>;
VDDCX使用 角电压voting。角电压voting允许RPM使用预置的电压值。在平台间,电压值的正常可能有些不同,因此通过允许RPM来处理这些
电压的不同来允许对USB驱动更少的改动。
2.9.1.1.2 中断(s)
DWC3 MSM 驱动只有两个中断处理函数,他们服务:
1,USB异步中断 --hs_phy_irq ; 上升沿触发
2,PMIC ID 中断 --- pmic_id_irq ; 双边沿触发
hs_phy_irq()主要用来唤醒控制器。(控制器是在一个HSPHY异步中断发生后被挂载了的)
pmic_id_irq()处理函数被用来通知ID 接地或者悬空事件,并且将这个事件传给DWC3 OTG驱动。
看3.2.12来获得详细的描述。
2.9.1.1.3 USB电源
USB电源有多重用途。他被用来接收PMIC VBUS 和 DWC3 OTG 主机模式事件,充电状态,等。它使用电源框架,因此一些驱动能通过注册的回调函数执行
一些命令。
初始化
dwc3_msm_probe()中
if (mdwc->ext_xceiv.otg_capability ||
!mdwc->charger.charging_disabled) {
mdwc->usb_psy.name = "usb";
mdwc->usb_psy.type = POWER_SUPPLY_TYPE_USB;
mdwc->usb_psy.supplied_to = dwc3_msm_pm_power_supplied_to;
mdwc->usb_psy.num_supplicants = ARRAY_SIZE(
dwc3_msm_pm_power_supplied_to);
mdwc->usb_psy.properties = dwc3_msm_pm_power_props_usb;
mdwc->usb_psy.num_properties =
ARRAY_SIZE(dwc3_msm_pm_power_props_usb);
mdwc->usb_psy.get_property = dwc3_msm_power_get_property_usb;
mdwc->usb_psy.set_property = dwc3_msm_power_set_property_usb;
mdwc->usb_psy.external_power_changed =
dwc3_msm_external_power_changed;
mdwc->usb_psy.property_is_writeable =
dwc3_msm_property_is_writeable;
ret = power_supply_register(&pdev->dev, &mdwc->usb_psy);
if (ret < 0) {
dev_err(&pdev->dev,
"%s:power_supply_register usb failed\n",
__func__);
goto disable_hs_ldo;
}
}
有一些回调函数被注册到了电源层。这些回调函数根据用户选择使用的API来执行。
此处省略 一张表
可能实际上不是每个属性都和充电器设置有联系,例如电源类型和电流下拉。DWC3中有一些对电源框架的使用情况,下面会讲到。
充电器设置
在充电器检测被完成以后,充电器的类型被传递给DWC3 OTG层的状态机,在这个状态机中,设置可以充电器可以提供的最大的电流。
static void dwc3_otg_sm_work(struct work_struct *w)
{
…
if (charger) {
/* Has charger been detected? If no detect it */
switch (charger->chg_type) {
case DWC3_DCP_CHARGER:
case DWC3_PROPRIETARY_CHARGER:
dev_dbg(phy->dev, "lpm, DCP charger\n");
dwc3_otg_set_power(phy,DWC3_IDEV_CHG_MAX);
pm_runtime_put_sync(phy->dev);
break;
case DWC3_CDP_CHARGER:
dwc3_otg_set_power(phy,DWC3_IDEV_CHG_MAX);
dwc3_otg_start_peripheral(&dotg->otg,1);
phy->state = OTG_STATE_B_PERIPHERAL;
work = 1;
break;
对于SDP的情况,充电设置在DWC3 gadget 层中就被做了,使用的是set_power() 回调函数映射到dwc3_otg_set_power()。在调用dwc3_otg_set_power()
以后,电源的类型被保存下来了,并且电源框架通知监听器电源发生了改变(power_supply_changed())。
static void power_supply_changed_work(struct work_struct *work)
{
…
if (psy->changed) {
psy->changed = false;
spin_unlock_irqrestore(&psy->changed_lock, flags);
class_for_each_device(power_supply_class, NULL, psy,
__power_supply_changed_work);
power_supply_update_leds(psy);
kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
spin_lock_irqsave(&psy->changed_lock, flags);
}
static int __power_supply_changed_work(struct device *dev, void *data)
{
…
(i = 0; i < psy->num_supplicants; i++)
if (!strcmp(psy->supplied_to[i], pst->name)) {
if (pst->external_power_changed)
pst->external_power_changed(pst);
}
return 0;
监听USB电源节点的充电器驱动调用它的 external_power_changed() 。例如,在MSM8974中,qpnp-charger驱动被用作主要充电器驱动,并且它
注册了一个回调函数 qpnp_batt_external_power_changed()。这个注册的回调函数负责读出从总线上可以得到的最大的电流,并且在PMIC中设置
充电电流。
static void
qpnp_batt_external_power_changed(struct power_supply *psy)
{
…
chip->usb_psy->get_property(chip->usb_psy,
POWER_SUPPLY_PROP_ONLINE, &ret);
/* Only honour requests while USB is present */
if (qpnp_chg_is_usb_chg_plugged_in(chip)) {
chip->usb_psy->get_property(chip->usb_psy,
POWER_SUPPLY_PROP_CURRENT_MAX, &ret);
if (chip->prev_usb_max_ma == ret.intval)
goto skip_set_iusb_max;
在电流被设置以后,power_supply_changed_work() 还会发送一个UEVENT事件到用户空间,告知电源的改变。这要求
一些用户空间的进程监听这些事件,并做相应的操作。
3.2.9.1.14 OTG 和外部的 hooks(联系)
有两个结构体包含到DWC3 OTG驱动的通知回调函数:
1,ext_xceiv
2,otg_xceiv
由于DWC3 MSM probe最先被调用,有一些初始化序列的部分依赖这些结构体来填充。在dwc3_msm_probe()函数里,
函数会强制probeDWC3 core 驱动来注册DWC3 OTG,这允许DWC3 OTG MSM驱动能填充必要的结构体。
if (node) {
ret = of_platform_populate(node, NULL, NULL, &pdev->dev);
if (ret) {
dev_err(&pdev->dev,
"failed to add create dwc3 core\n");
goto put_psupply;
}
}
...
mdwc->otg_xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
/* Register with OTG if present, ignore USB2 OTG using other PHY */
if (!IS_ERR_OR_NULL(mdwc->otg_xceiv) &&
!(mdwc->otg_xceiv->flags & ENABLE_SECONDARY_PHY)) {
/* Skip charger detection for simulator targets */
if (!mdwc->charger.skip_chg_detect) {
mdwc->charger.start_detection = dwc3_start_chg_det;
ret = dwc3_set_charger(mdwc->otg_xceiv->otg,
&mdwc->charger);
if (ret || !mdwc->charger.notify_detection_complete) {
dev_err(&pdev->dev,
"failed to register charger: %d\n",
ret);
goto put_xcvr;
}
}
if (mdwc->ext_xceiv.otg_capability)
mdwc->ext_xceiv.ext_block_reset = dwc3_msm_block_reset;
ret = dwc3_set_ext_xceiv(mdwc->otg_xceiv->otg,
&mdwc->ext_xceiv);
if (ret || !mdwc->ext_xceiv.notify_ext_events) {
dev_err(&pdev->dev, "failed to register xceiver: %d\n",
ret);
goto put_xcvr;
}
} else {
调用of_platform_populate()同步执行DWC3 core 驱动的probe回调函数(dwc3_probe()).为了填充otg_xceiv结构体,需要OTG驱动
已经被注册了,并且初始化了。而且,为了注册外部的事件通知器(ext_xceiv),驱动需要满足otg_xceiv已经被填充了。
dwc3_probe()函数负责注册必须的OTG数据结构体,并且在继续DWC3 MSM初始化之前初始化DWC3 gadget驱动。
3.2.9.1.2 DWC3 core 驱动初始化
DWC3 core 驱动在DWC3 MSM 开始它之后被probe。DWC3 core驱动初始化下面的东西:
1,发起一个到USB core的软重置
2,设置一些全局的寄存器和配置所需要的USB管道寄存器
3,分配并且设置事件buffers
4,初始化DCD 和/或者 HCD 驱动
取决于控制器所支持的能力,驱动选择哪一种DWC3 驱动必须被设置。有三种可能的模式。
DWC3_MODE_DEVICE -- 只设备模式
DWC3_MODE_HOST -- 只主机模式
DWC3_MODE_DRD ---- 双角色模式(主机和从机)
DRD角色是一个用来描述支持主机和从机模式的特定的术语。设备模式和主机模式基于USB ID的值动态的转换。这个需要DCD和HCD协议都被初始化了。
并且,一个中间媒介驱动(OTG驱动)处理这两个协议之间的转换。
3.2.9.1.2.1 OTG驱动初始化
dwc3_otg_init()是设备和主机驱动之间的连接层,因此驱动应该有引用主机和gadget设备两边的结构体。OTG驱动必须初始化下面的变量:
1,dotg->irq -- 单独的irq中断线专门负责OTG相关的事件(ID/BSV).(如果有依赖控制器 BSV/ID中断的话)
2,dotg->regs --- DWC3 控制器的寄存器的基地址
3,dotg->otg.set_peripheral 这个回调函数被用来填充在注册的usb_gadget设备中的usb_otg结构体。usb_gadget结构体在外设驱动开始的时候
被需要。
4,dotg->set_host ---在主机驱动被probed和初始化后,这个回调函数被用来设置端口电压。
5,填充 usb_phy 结构体
6,dotg->otg.phy->state ---初始化OTG状态为未定义状态 (OTG_STATE_UNDEFINED)
中断
DWC3 OTG驱动有一个专门的中断线来处理OTG事件。在OTG驱动中,dwc3_otg_interrupt()被注册为中断处理函数。在大多数平台,我们不依赖
控制器来处理BSV/ID中断,因此如果支持一个外部的通知的话,我们就在OTG控制器状态被重置的时候禁止 ID/BSV事件。(dwc3_otg_reset()).
static void dwc3_otg_reset(struct dwc3_otg *dotg)
{
…
/* Enable ID/BSV StsChngEn event*/
if (ext_xceiv && !ext_xceiv->otg_capability)
dwc3_writel(dotg->regs, DWC3_OEVTEN,
DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT |
DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT);
注册OTG驱动
OTG驱动必须注册它的usb_phy结构体到通用OTG层。这样就允许DWC3 MSM能够获得一个引用,并且通过通用OTG层来访问OTG的回调函数。usb_phy结构体
通过使用usb_add_phy 来添加。
ret = usb_add_phy(dotg->otg.phy, USB_PHY_TYPE_USB2);
if (ret) {
dev_err(dotg->otg.phy->dev,
"%s: failed to set transceiver, already exists\n",
__func__);
goto err2;
}
在OTG驱动将usb_phy添加到通用OTG层的phy_list里后,客户(DWC3 MSM)能够通过使用usb_get_phy()来保留一份usb_phy的引用。
函数:dwc3_msm_probe()
mdwc->otg_xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
/* Register with OTG if present, ignore USB2 OTG using other PHY */
if (!IS_ERR_OR_NULL(mdwc->otg_xceiv) &&
!(mdwc->otg_xceiv->flags & ENABLE_SECONDARY_PHY)) {
/* Skip charger detection for simulator targets */
if (!mdwc->charger.skip_chg_detect) {
mdwc->charger.start_detection = dwc3_start_chg_det;
ret = dwc3_set_charger(mdwc->otg_xceiv->otg,
&mdwc->charger);
if (ret || !mdwc->charger.notify_detection_complete) {
dev_err(&pdev->dev,
"failed to register charger: %d\n",
ret);
goto put_xcvr;
}
}
由于驱动已经访问到一个有效的USB PHY结构体,它能够完成剩下的probe调用(在DWC3 core驱动完成它的probe()后,
可能会包含一些主机和设备协议的初始化)
3.2.9.1.2.2 HCD 驱动初始化
dwc3_host_init()负责设置必须的平台资源以用来在主机模式下运行
1,dwc->xhci ---包含平台设备分配的XHCI HCD。dwc3_host_init()调用platform_device_add()来probe各自的XHCI HCD驱动。
2,pdata->vendor --- 存储厂商ID
3,pdata->revision ----存储DWC 硬件版本
4,dwc ->xhci_resource ---- 当添加平台设备的时候,它装载着IO资源和控制器的基地址
添加XHCI平台设备
为了xhci控制器能够正确的初始化,要求最小的平台数据域被正确的设置。至少资源必须添加到平台设备结构体,并且DWC3 XHCI 相关的参数,例如
vendor和revision都是可选的,尽管他们是平台设备的一部分。
在调用dwc3_probe()期间,XHCI资源用中断资源以及控制器内存地址和范围来填充。这个将会在之后添加平台设备的时候使用。
static int __devinit dwc3_probe(struct platform_device *pdev)
{
…
dwc->notify_event = notify_event;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(dev, "missing IRQ\n");
return -ENODEV;
}
dwc->xhci_resources[1].start = res->start;
dwc->xhci_resources[1].end = res->end;
dwc->xhci_resources[1].flags = res->flags;
dwc->xhci_resources[1].name = res->name;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "missing memory resource\n");
return -ENODEV;
}
dwc->xhci_resources[0].start = res->start;
dwc->xhci_resources[0].end = dwc->xhci_resources[0].start +
DWC3_XHCI_REGS_END;
dwc->xhci_resources[0].flags = res->flags;
dwc->xhci_resources[0].name = res->name;
如果设备不支持OTG,那么在填充完结构体后,驱动能够添加pdev,这将会调用XHCI HCD probe。下面一行代码保存XHCI结构体到dwc3层:
dwc->xhci = xhci;
int dwc3_host_init(struct dwc3 *dwc)
{
…
/* Add XHCI device if !OTG, otherwise OTG takes care of this */
if (!dwc->dotg) {
ret = platform_device_add(xhci);
if (ret) {
dev_err(dwc->dev, "failed to register xHCI device\n");
goto err1;
}
}
然而,如果OTG模式被使能了,当一个ID pin 接地事件被检测到时,平台设备添加函数将会被处理
static int dwc3_otg_start_host(struct usb_otg *otg, int on)
{
…
if (on) {
…
pm_runtime_init(&dwc->xhci->dev);
ret = platform_device_add(dwc->xhci);
if (ret) {
dev_err(otg->phy->dev,
"%s: failed to add XHCI pdev ret=%d\n",
__func__, ret);
return ret;
}
XHCI-Plat probe
在Linux设备驱动发现一个有效的设备和驱动的匹配后,调用probe()。匹配的驱动在usb/host/xhci-plat.c中
xhci_plat_probe()是设置所需要的资源用来工作在主机模式并且开始主机协议的主要函数。它执行的步骤如下:
1,当创建一个HCD时,获得必须的值:
irq 号
内存地址和大小
一些额外的标识符 flags
2,添加USB3.0 roothub设备并且添加与USB3.0部分联系的primary HCD。
3,添加USB2.0 roothub并且添加一个shared HCD设备
XHCI协议假设在USB3.0控制其中有两个不同的 roothubs 。第一个roothub(primary)将是为USB3.0准备的,并且第二个roothub(shared)
将会是为USB2.0准备的。这是支持所有的类型的USB设备所必要的。
创建一个HCD
static int xhci_plat_probe(struct platform_device *pdev)
{
…
driver = &xhci_plat_xhci_driver;
…
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
if (!hcd)
return -ENOMEM;
hcd_to_bus(hcd)->skip_resume = true;
hcd->rsrc_start = res->start;
hcd->rsrc_len = resource_size(res);
当创建HCD结构体的时候,注册处理函数来处理明确的I/O 和 总线 事件。HCD也应该设置正确的地址范围。
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,
driver->description)) {
dev_dbg(&pdev->dev, "controller already in use\n");
ret = -EBUSY;
goto put_hcd;
}
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (!hcd->regs) {
dev_dbg(&pdev->dev, "error mapping memory\n");
ret = -EFAULT;
goto release_mem_region;
}
添加primary HCD
ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
if (ret)
goto unmap_registers;
primary HCD 将会开启USB3.0的roothub,它枚举roothub并且创建roothub设备。这个过程的详细的大纲在5.2.2.1中
创建共享的HCD
/* USB 2.0 roothub is stored in the platform_device now. */
hcd = dev_get_drvdata(&pdev->dev);
xhci = hcd_to_xhci(hcd);
xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev,
dev_name(&pdev->dev), hcd);
if (!xhci->shared_hcd) {
ret = -ENOMEM;
goto dealloc_usb2_hcd;
}
创建shared HCD 和创建primary HCD非常相似。它实际上就是使用一些来自于primary HCD的值来填充自己的参数。
struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
struct device *dev, const char *bus_name,
struct usb_hcd *primary_hcd)
{
…
} else {
hcd->bandwidth_mutex = primary_hcd->bandwidth_mutex;
hcd->primary_hcd = primary_hcd;
primary_hcd->primary_hcd = primary_hcd;
hcd->shared_hcd = primary_hcd;
primary_hcd->shared_hcd = hcd;
}
添加共享的 HCD
ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
if (ret)
goto put_usb3_hcd;
添加共享HCD和primary HCD部分大致是相同的。
中断设置
static int xhci_plat_probe(struct platform_device *pdev)
{
…
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return -ENODEV;
probe()必须先获得在dwc3_probe()中填充在xhci_resources中的中断号。这个中断号之后会被作为usb_add_hcd()的一个参数。
ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
irq的中断处理函数早就已经在创建HCD的时候就已经被注册了。
static const struct hc_driver xhci_plat_xhci_driver = {
.description = "xhci-hcd",
.product_desc = "xHCI Host Controller",
.hcd_priv_size = sizeof(struct xhci_hcd *),
/*
* generic hardware linkage
*/
.irq = xhci_irq,
使能端口电压
phy = usb_get_phy(USB_PHY_TYPE_USB2);
/* Register with OTG if present, ignore USB2 OTG using other PHY */
if (!IS_ERR_OR_NULL(phy)
&& phy->otg && !(phy->flags & ENABLE_SECONDARY_PHY)) {
dev_dbg(&pdev->dev, "%s otg support available\n", __func__);
ret = otg_set_host(phy->otg, &hcd->self);
if (ret) {
dev_err(&pdev->dev, "%s otg_set_host failed\n",
__func__);
usb_put_phy(phy);
goto put_usb3_hcd;
}
} else {
在主机模式时,在DWC3 OTG 的set_host()回调函数负责使能端口的供能。
3.2.9.1.2.3 DCD驱动初始化
dwc3_gadget_init()负责设置必要的资源来让平台运行在设备模式。它必须设置下面的变量
1,EP0传输初始化
2,dwc→gadget.ops -- 被DWC3gadget 驱动暴露在通用USB gadget 层的回调操作集。这个允许和上层的安卓USB层交互。
3,dwc->gadget。max_speed ---设置设备支持的最快的速度。当一个设备将它自己作为一个超速设备时,PC主机协议对它的初始化是不同的,
但是只支持HSUSB。
4,dwc->gadget.speed --- 包含当前的USB速度设置,例如超速,高速等。
5,端点初始化
设置OTG连接层
为了让DWC3 OTG驱动能在设备协议下操作,UDC驱动需要填充在usb_otg结构体中的“gadget”参数。
struct usb_otg {
u8 default_a;
struct usb_phy *phy;
struct usb_bus *host;
struct usb_gadget *gadget;
由于OTG驱动在gadget驱动前被设置,所以能够注册set_peripheral()回调函数到通用OTG层。通用OTG层暴露
otg_set_peripheral() API来调用set_peripheral()回调函数来设置外设。
static int dwc3_otg_set_peripheral(struct usb_otg *otg,
struct usb_gadget *gadget)
{
struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg);
if (gadget) {
dev_dbg(otg->phy->dev, "%s: set gadget %s\n",
__func__, gadget->name);
otg->gadget = gadget;
schedule_work(&dotg->sm_work);
并且,由于DCD是最后的被初始化的驱动,所以dwc3_otg_set_peripheral()将会开启OTG状态机。
3.2.9.2.1 DWC3 OTG 状态机
DWC3 OTG状态机是介于主机和设备之间的连接层,它能根据条件动态的打开任意一边。
3.2.9.2.1
DWC3 OTG 状态机概要
OTG的状态机被简化为5个状态。他们是:
1,未定义状态 --这个状态只在第一次启动时有效。在设备进入B_idle状态后,这个状态不再有效。
2,B_idle ---这个状态是默认的OTG状态机的状态。它有处理 ID 和 BSV 事件的逻辑。
3,B_peripheral --- 这个状态发生在当设备处在设备模式时。
4,A_idle --- 这是一个在USB ID接地后进入主机模式的暂时状态。
5,A_host --- 这个状态发生在当主机处于主机模式的时候
它也服务下面的状态机位:
B_SESS_VLD ---B 会话有效
ID ---- ID 状态
3.2.9.2.1.1 DWC3 OTG 状态机初始化
在DWC3 core驱动开始初始化设备 和/或者 主机协议时,设备边的初始化作为最后一步发生的初始化。因此在gadget probe程序的最后就开启了状态机。
这个要求主机和设备结构体在USB线插入时都已经被填充了。
初始化序列:
3.2.9.1.2.3对DWC3 gadget的probe程序进行了重点标识,以及重点标识了它的代码流程。在probe调用的结尾,set_periperhal()回调函数被执行。
相应的回调函数是dwc3_otg_set_peripheral()。在这个函数中,工作队列被进队来开始状态机。
当初始化开始后,一切就都应该处在默认的状态:
otg→phy→state 未定义的状态
BSV clear 默认的
ID clear 默认的
状态机从默认的状态开始。
static void dwc3_otg_sm_work(struct work_struct *w)
{
…
switch (phy->state) {
case OTG_STATE_UNDEFINED:
dwc3_otg_init_sm(dotg);
在开始之前,驱动需要检查总线的状态。这个需要包括在启动的时候插入USB线的情景。
void dwc3_otg_init_sm(struct dwc3_otg *dotg)
{
…
ret = wait_for_completion_timeout(&dotg->dwc3_xcvr_vbus_init, HZ * 5);
if (!ret) {
dev_err(phy->dev, "%s: completion timeout\n", __func__);
/* We can safely assume no cable connected */
set_bit(ID, &dotg->inputs);
}
USB驱动假设在VBUS初始的状态时,PMIC充电器驱动应该通知VBUS事件的产生。这个通知被作为一个触发来告诉状态机有状态改变需要被服务。
dwc3_xcvr_vbus_init 完成函数只有在PMIC发送一个通知的时候才去调用。
static void dwc3_ext_event_notify(struct usb_otg *otg, enum dwc3_ext_events event)
{
…
if (!init) {
init = true;
if (!work_busy(&dotg->sm_work))
schedule_work(&dotg->sm_work);
complete(&dotg->dwc3_xcvr_vbus_init);
dev_dbg(phy->dev, "XCVR: BSV init complete\n");
return;
}
如果PMIC通知事件发生,状态机的初始化会继续。然而,有一些情况下,从来自于PMIC的ID和VBUS通知没有被使用。在这种情况下,
驱动将会等待完成事件超时,并且使用USB控制器寄存器来设置正确的状态。
if (ext_xceiv && !ext_xceiv->otg_capability) {
if (osts & DWC3_OTG_OSTS_CONIDSTS)
set_bit(ID, &dotg->inputs);
else
clear_bit(ID, &dotg->inputs);
if (osts & DWC3_OTG_OSTS_BSESVALID)
set_bit(B_SESS_VLD, &dotg->inputs);
else
clear_bit(B_SESS_VLD, &dotg->inputs);
}
otg_capacility标识表示平台是否使用PMIC ID/VBUS 中断。这个标识被定义在设备树中。在这种状况下,OTG寄存器也将重置为正确的值
OTG寄存器使用dwc3_otg_reset()来重置寄存器值。
然后驱动保证中断被正确的初始化了。它首先清除OTG的各种事件,并且然后决定是否需要OTG控制器事件。
/* Clear all otg events (interrupts) indications */
dwc3_writel(dotg->regs, DWC3_OEVT, 0xFFFF);
/* Enable ID/BSV StsChngEn event*/
if (ext_xceiv && !ext_xceiv->otg_capability)
dwc3_writel(dotg->regs, DWC3_OEVTEN,DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT | DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT);
在OTG状态被初始化后,状态机获得一个USB电源结构体的句柄来支持报告当前的耗能等给DWC3 MSM。
dwc3_otg_init_sm(dotg);
if (!dotg->psy) {
dotg->psy = power_supply_get_by_name("usb");
if (!dotg->psy)
dev_err(phy->dev,"couldn't get usb power supply\n");
}
然后状态机决定是否需要一些更进一步的状态转变。如果不是的话,将会进入B_idle状态,这个也是默认的状态。它也会减小PM runtime计数器的值,
来让USB core进入LPM模式。在这个完成以后,USB状态机被初始化,一些随后的插入事件将会被相应的处理。
3.2.9.2.1.2 DWC3 OTG状态机---BSV 处理
最普遍的事件是处理设备插入电脑或者主机的情况。在这种情况下,设备处于外设模式,因此gadget协议被使能。
基于PMIC的VBUS的连接检测
在QTI参考设计中,系统移植使用基于PMIC的 ID/VBUS 中断。在这种体系中,PMIC被用来作为一个外部的VBUS探测器,因此
不需要 USB PHY 来执行任何的传感器检测动作(sensing)。这样的话,就使得我们可以关闭PHY相关的电源(关闭1.8V和
3.3V的rails),这可以省很多电能。
最近的平台不再有从PMIC路由到APQ的 VBUS 信号了。之前需要这个VBUS信号通过芯片上的
实际的PHY VBUS引脚来保持会话有效状态为真。 VBUS 感应允许软件能够控制控制器里面的会话有效状态。
USB线在插入后,产生一个中断,调用qpnp_chg_usb_usbin_valid_irq_handler()函数,这个函数调用charger 驱动里面的
power_supply_set_present()---->dwc3_msm_power_set_property_usb()(POWER_SUPPLY_PROP_PRESENT),在dwc3 MSM
里会去调用一个延时工作队列,在20秒后会去调用dwc3_resume_work()--->dwc3_ext_event_notify(DWC3_EVENT_XCEIV_STATE)
,进入DWC3 OTG层,设置B_SESS_VLD,然后调度状态机,dwc3_otg_sm()设置PHY的状态为OTG_STATE_B_IDLE,输入域的B_SESS_VLD
为1,充电的类型为无效的充电器。然后再回到DWC3 MSM层开始charger的检测 dwc3_start_chg_det(),进入charger驱动层
调度延时工作队列 dwc3_chg_detect_work(),设置充电的状态为未定义状态,然后charger 检测开始到结束,这期间所做的事
就是二次检测,先检测是否是SDP,否则的话进入第二次检测,第二次检测区别是CDP还是DCP。这个时候我们设置充电器的状态为
已检测到。然后我们调用dwc3 msm层的dwc3_ext_chg_det_done(),然后进入otg 驱动层,调度OTG的状态机,设置phy的状态为
OTG_STATE_B_IDLE,输入域的B_SESS_VLD为1,充电器的类型为SDP,然后进入DWC3 MSM层调用 dwc3_otg_start_peripheral()。
最后执行usb_gadget_vbus_connect()进入到gadget层。 (-----基于PMIC的VBUS的连接检测 )
1,在启动的时候,充电器驱动通过power_supply_get_by_name()来获得一个USB电源的引用。
2,在充电器驱动有一个USB电源的引用后,charger 驱动就可以通过qpnp_chg_usb_usbin_valid_irq_handler()通知USB驱动
产生了一个VBUS事件。
3,USBIN有效中断使用power_set_present(),这个函数有一个注册到USB驱动的回调函数dwc3_msm_power_set_property_usb()。
4,USB驱动设置mdwc->ext_xceiv.bsv为传过来的值,这个值告知我们是否VBUS为高或者为低。
5,dwc3_msm_power_set_property_usb()让一个延时工作队列进入到唤醒工作队列(延时是20ms)。
6,dwc3_resume_work()通过对mdwc->dev使用pm_runtime_get_sync()唤醒控制器(如果需要的话)。
7,dwc3_resume_work()执行hook来通知DWC OTG驱动,一个外部的事件发生了(dwc3_ext_event_notify())
8,dwc3_ext_event_notify()处理 设置或者清除 BSV状态机位,并且调度OTG状态机工作队列。(从resume_work传递过去的参数
是DWC3_EVENT_XCEIV_STATE)。
9,运行dwc3_otg_sm_work()来服务BSV改变事件(开始或者停止设备协议)
基于USB控制器的
基于USB控制器的VBUS检测机制和基于PMIC的顺序相似。只是检测有点不一样并且在DWC OTG驱动中被处理。它不包括任何与DWC
MSM 驱动的交流。
下面的过程描述了基于USB控制器的序列流程:
1,DWC3控制器有一个单独的用来处理 ID/VBUS 事件的中断线。对应的中断处理函数是dwc3_otg_interrupt()。
2,dwc3_otg_interrupt()处理函数将会负责设置或者清除BSV位,这代替了基于PMIC检测中的dwc3_ext_event_notify().
3,在正确的状态位被设置后,中断处理函数调度dwc3_msm_sm_work()来运行。下一步和基于PMIC的序列的相同。
3.2.10 功能驱动
功能驱动实际上代表的是USB接口驱动,用户空间访问功能驱动来执行不同的设备相关的操作。驱动使用一个gadget驱动框架,
例如,一个安卓gadget驱动,来和设备控制器驱以及OTG驱动通信。
在LinuxUSB外设驱动中,默认的支持composite驱动,这个驱动提供一套功能驱动,这些驱动被枚举为不同的设备并且
代表不同的USB设备。
3.2.10.1 实现一个功能驱动
使用gadget框架来实现一个功能驱动,基本的对象和方法必须被定义。
1,由于HS-USB OTG核 支持高速和全速(包括低速)USB设备,功能驱动能够定义USB描述符用来描述全速和高速作为
usb_descriptor_header表的一部分:
接口描述符
端点描述符
2,实例化USB设备的android_usb_function,并且在function 调用函数的init()函数里,android_register_function
用来注册这个function到Android gadget 驱动中。定义包括:
功能驱动的名字
功能驱动绑定定义 例如 fun_bind_config()
3,定义功能绑定配置。它典型的是实例化USB设备数据结构体,这包含usb_function结构体。usb_function 结构体成员在这里
被填充。
1,功能驱动的名字
2,描述符头(HS或者FS)
3,典型的注册的回调函数 ---bind,unbind,set_alt,以及disable。必须一直被定义。
4,如果功能驱动不是默认的composition usb_function结构体成员的一部分,禁止标识必须被设置为1,因此function的描述符的信息
不会通过composite驱动通过config_buf()传递给主机。
5,调用usb_add_function()来添加这个功能驱动到USB配置驱动链表(定义在3.2.6中)。如果set_alt和禁止回调函数没有被注册,那么
功能函数不会被添加到链表。
6,除了功能函数,驱动可能定义内核级别的数据结构体来执行基本的打开,关闭,读写操作。
4,定义bind功能:
1,分配并且声明一个端点号通过调用usb_ep_autoconfig(),并且配置这个端点号所对应的这个设备的 IN 和OUT端点,并且
更新这个USB设备的端点描述符的端点地址(bEndpointAdress)位。此时端点还没有被使能。
2,实例化usb_request,这个请求代表了一个单独的设备I/O请求,通过调用usb_ep_alloc_request()来读和写。分配驱动Rx和Tx buffers,
并且注册完成处理函数程序。unbind 函数释放在功能绑定配置里面创建的buffers,usb_request,和设备实例。
5,定义set_alt 函数:
1,通过调用usb_ep_enable()函数来使能端点,,这些端点被定义在功能驱动端点描述符中。
2,当主机发送一个SET_CONFIG来请求一个存在的配置的时候,这个函数被调用。由于USB线被插入到主机中,所以usb_configuration不应该为
NULL, 结果,从这点来看,驱动已经准备好执行设备操作。
6,定义disable函数:
1,禁止并且刷新所有的端点通过调用usb_ep_disable(),通过功能驱动端点描述符被定义。
2,这个函数将会在USB线被拔出或者一个新的配置被选中时被调用。结果,驱动不能执行设备操作。
7,添加这个功能驱动名字,就像定义在usb_function结构体中的一样,到usb_function_all表中,对于索引号,
将会成为功能驱动接口号在枚举过程中被分配。
8,添加这个function到一个新的或者已经存在的function表中。
9,定义一些函数,这些函数可能包括驱动操作相关的实现,例如:
1,典型的文件处理操作,例如,打开,关闭,读,写
2,注册到一些子系统中,或者作为一个字符驱动接口或者TTY接口暴露到内核中。
3.2.10.2数据传输
数据传输(非ep0 Rx或者Tx)请求并且实现如下表所示
分为 function 驱动层, gadget驱动层 设备控制器驱动层
在function驱动层调用usb_ep_queue()后进入到gadget驱动层,然后调用usb_ept_queue_xfer(),然后进入设备控制器驱动层执行
usb_ept_start() ,这个函数用来准备事务描述符的与硬件相关的元素并且连接硬件队列头和首端点。
然后在事务执行完成以后,会产生一个事务完成中断usb_interrupt()。然后进入gadget驱动层执行handle_endpoint(),然后调用function驱动层的
完成处理函数。(------功能驱动数据传输)
3.2.11 PHY 驱动
在基于3.10内核版本的产品,引进了USB PHY层,这使得USB驱动可以轻易的将多个PHY单独区分开来。例如,DWC3驱动使用USB PHY层来重新获得
一些SSPHY相关的 操作,这些操作与HSPHY相关的操作是独立的。目前有多个USB PHY驱动存在。然而,使用明确的驱动取决于chipset(如 msm8996)
驱动名 连接的驱动 注释
phy-msm-husb.c dwc3-msm.c 处理一些HSPHY相关的操作 for dwc3驱动 chipset: MSM8974 APQ8084
phy-msm-ssusb.c dwc3_msm.c 处理一些SSPHY相关的操作 for DWC3驱动 chipset: MSM8974 APQ8084
phy-msm-ssusb-qmp.c dwc3-msm.c 处理一些SSPHY(QMP PHY)相关的操作 for DWC3 驱动 chipset MSM8994
phy-msm-qusb.c ehci-msm2.c 处理HSPHY(QUSB PHY)在只主机端口被使用(secondary 端口) chipset :MSM8994
phy-msm-usb.c phy-msm-usb.c 处理legacy HSPHY,这个原先是在msm_otg驱动中被处理的:chipset :MSM8x10 MSM8916 MSM8939
许多配置都是基于上面的列表信息的,下面的部分提供了这些驱动如何被使用和初始化的信息。
3.2.11.1 USB PHY 驱动定义
在一个chipset中被使用的USB PHY(s)被定义在设备树中。对应一个USBPHY 的一个单独的设备树节点是可以得到的,并且被USB核的设备树节点引用。
USB PHY 设备节点
hsphy0: hsphy@f92f8800 {
compatible = "qcom,usb-hsphy";
reg = <0xf92f8800 0x3ff>,
<0xfd4ab204 0x4>;
qcom,hsphy-init = <0x00D191A4>;
vdd-supply = <&pma8084_s1>;
vdda18-supply = <&pma8084_l6>;
vdda33-supply = <&pma8084_l24>;
qcom,vdd-voltage-level = <0 900000 1050000>;
qcom,ext-vbus-id;
qcom,vbus-valid-override;
};
由于USB PHY 驱动处理所有的PHY所要求的资源或者序列,因此他们包含所有使用的资源。USBPHY设备节点接着被添加到USB核的设备节点:
USB core 设备节点
usb3: ssusb@f9200000 {
…
dwc3@f9200000 {
compatible = "synopsys,dwc3";
reg = <0xf9200000 0xfc000>;
interrupt-parent = <&intc>;
interrupts = <0 131 0>, <0 179 0>;
interrupt-names = "irq", "otg_irq";
tx-fifo-resize;
usb-phy = <&hsphy0>, <&ssphy0>;
};
上面的USBPHY是USB3.0的核,它需要两个USBphy (SSPHY 和 HSPHY)
例外
使用legacy USB phy的chipset(phy-msm-usb.c)没有这种类型的结构体。phy-msm-usb驱动包含所有的PHY功能特性在一个文件中。由于这个驱动
是对应USB2.0控制器的,所以只有一个HSPHY和它相关。因此,不需要有单独的phy驱动。
3.2.11.2 USB PHY驱动初始化
每个USBPHY 设备节点调用与该驱动相关的probe()代码
3.2.12 USB 中断和唤醒中断
USB 控制器有一些中断路径和处理函数在下面的部分将会被提及到。
不同的中断处理函数服务特定的USB控制器事件。例如,OTG驱动只处理OTG寄存器中所明确表示的事件,而UDC只处理与设备寄存器相关的事件。
DCD中断处理函数 OTG中断处理函数 HCD 中断处理函数
Main OTG USB 2.0控制器 msm_udc_irq() msm_otg_irq() ehci_irq()
Main OTG DWC3 USB3.0 控制器 dwc3_interrrupt() dwc3_otg_interrupt() xhci_irq()
次 主机USB2.0控制器 -------- ---------- msm_ehci_irq()
HSIC USB 2.0控制器 msm_udc_hsic_irq() ---------- msm_hsic_irq()
上面表中提到的处理函数服务主(main)中断,来自于USB控制器。可能,除了主irq,还有中断源。
3.2.12.1 USB2.0控制器中的中断类型
3.2.12.1.1 异步中断
自从legacy产品以来,异步中断就是一个存在于USB2.0模块中的模块。异步中断的主要的目的在于能够允许控制器进入低功率模式,并且能够检测
明确的唤醒事件。一些能够触发异步中断的事件如下:
VBUS改变事件
ID改变事件
数据线改变
这些事件从USBphy 传给USB控制器,如果使能了的话,USB控制器将会产生一个中断给中断控制器。要配置异步中断,软件必须使能USBCMD寄存器里面
的 ASYNC_INTR_CTRL 位。这个位(29)默认的是0.设置为1允许从USB core输出异步中断.
当这个位被设置为1时,控制器连续不断的生成一个中断到全局中断控制器(GIC),当异步中断被处理的时候,USB控制器的中断线必须被禁止。例如:
static irqreturn_t msm_otg_irq(int irq, void *data)
{
struct msm_otg *motg = data;
struct usb_otg *otg = motg->phy.otg;
u32 otgsc = 0, usbsts, pc;
bool work = 0;
irqreturn_t ret = IRQ_HANDLED;
if (atomic_read(&motg->in_lpm)) {
pr_debug("OTG IRQ: %d in LPM\n", irq);
disable_irq_nosync(irq);
motg->async_int = irq;
if (!atomic_read(&motg->pm_suspended))
pm_request_resume(otg->phy->dev);
return IRQ_HANDLED;
}
…
msm_otg_irq()是控制器默认的中断处理函数。如果控制器处于低功率模式,任何发生的中断都是一个唤醒事件。立即禁止IRQ直到
ASYNC_INTR_CTRL位被清除了。异步中断没有一个对应的中断使能位。因此直到ASYNC_INTR_CTRL被清除前,控制器都能够继续传输异步事件。
通用的USB控制器唤醒功能描述如下。它负责将USB控制器带出低功率模式。确保这个USB控制器的中断是禁止的直到异步中断位已经被清除了。
static int msm_otg_resume(struct msm_otg *motg)
{
...
temp = readl(USB_USBCMD);
temp &= ~ASYNC_INTR_CTRL;
temp &= ~ULPI_STP_CTRL;
writel(temp, USB_USBCMD);
...
if (motg->async_int) {
/* Match the disable_irq call from ISR */
enable_irq(motg->async_int);
motg->async_int = 0;
}
...
在B协议族中对异步中断的改变
B家族产品的异步中断被做了一些改变。更早些时候,异步中断由主 USB控制器IRQ线生成。然而,在B家族chipset,有一个专门的
用来处理异步事件的中断线。每个驱动已经都采用这种新的异步IRQ线了。
3.2.12.2 USB 2.0控制器中断处理函数
USB2.0控制器有一些处理函数,对应不同的使用情况。这一部分将会描述公共的体系结构/设计,并且解释中断的流程。
3.2.12.2.1 USB2.0控制器 主(main)OTG核
驱动文件的所在位置
drivers/usb/otg/msm_otg.c
usb/gadget/ci13xxx_msm.c
usb/host/ehci-msm.c
所有的安卓版本都支持。
中断处理函数 中断服务 注释
msm_otg_irq() IDIS- ID PIN 状态 BSV和ID中断仅仅只有当对应的中断使能位也被设置了才会去服务中断
BSVIS VBUS状态 这个中断处理函数处理几乎所有的OTG相关的中断(otgsc寄存器)
PHY_ALT_INT 非USB中断,例如电池
相关的中断。ASYNC_INT 异步中断
msm_udc_irq() 设备模式 :
URI USB reset
PCI 端口改变
UEI USB事务错误
UI USB TD 完成
SLI USB挂载
ehci_irq() STS_ERR – USB completion
error
? STS_INT – USB completion
? STS_IAA – Async advance
doorbell
? STS_PCD – Port change
detect
? STS_FATAL – USB fatal error
msm_otg_set_vbus_state() BSV – VBUS status 这个是注册的VBUS有效处理函数for PMIC BSV 中断
msm_pmic_id_irq() ID – ID pin status 这个是注册的ID GPIO 中断处理函数forPMIC ID状态
GPIO反映了实际的ID引脚的状态。
中断处理函数服务每个中断是不同的。例如,OTG相关的中断,由msm_otg_irq处理的只会设置OTG状态机位域然后开启OTG状态机工作队列。然而,
UDC irq处理函数服务直接在一些中断的中断上下文中。
除了通用的控制器中断,在明确的使用情况和设计中,USB2.0控制器被当做主(main)OTG核使用,驱动可能使用一些中断源,例如GPIOs。
下表显示了独立的MDM设计,在这个设计中,一个外部的GPIO被用来作为一个唤醒源。对于明确的MDM产品,有一个已知的丢失硬件连接到MPM的情况,这
使得USB总线resume来唤醒处于休眠状态的设备,例如,XO关闭,或者VDD最小。
注意: 这个是