USB总线-Linux内核USB3.0主机控制器驱动初始化流程分析(十三)
1.概述
RK3588有2个USB3.0 DRD控制器,2个USB2.0 Host控制器。USB3.0 DRD控制器既可以做Host,也可以做Device,向下兼容USB2.0和USB1.0。USB3.0 DRD控制器的内部结构如下图所示;总线接口为AXI或AHB;USB3.0和USB2.0及USB1.0硬件上独立;USB3.0控制器数字逻辑部分对应SS MAC,PHY接口为PIPE,PHY为USBDP PHY,和DP使用同一个PHY;USB2.0和USB1.0控制器数字逻辑部分对应HS/FS/LS MAC,PHY接口为UTMI+,PHY为USB2.0 PHY。
USB3.0 DRD控制器作为Host的主要特性如下:
- 兼容USB3.0 Revision 1.0规范,兼容USB2.0规范,兼容xHCI Revision 1.1规范。
- 支持Control/Bulk (including stream)/Interrupt/Isochronous传输。
- USB3.0同时进行输入和输出传输,最大可达到8Gbps带宽。
- 具有描述符缓存和数据预取功能,可以改善高延迟系统的性能。
- USB2.0支持LPM低功耗协议,USB3.0支持U0、U1、U2、U3四种低功耗状态。
- 端点具有动态FIFO memory分配能力。
- LS模式具有Keep-Alive特性,HS/FS模式具有(micro-)SOFs特性。
- 支持AXI Master和AHB Slave接口。
- 最大支持64个设备。
- 支持1个中断。
- 支持USB2.0 port和USB3.0 port。
- 支持标准的和开源的xHCI和USB类驱动。
- USB2.0 PHY支持充电检测。
- 支持USB Type-C和DP Alt模式。
2.设备树
usbdrd3_1设备树定义如下,默认模式为Host。
[arch/arm64/boot/dts/rockchip/rk3588.dtsi]
/ {
......
usbdrd3_1: usbdrd3_1 {
compatible = "rockchip,rk3588-dwc3", "rockchip,rk3399-dwc3";
clocks = <&cru REF_CLK_USB3OTG1>, <&cru SUSPEND_CLK_USB3OTG1>,
<&cru ACLK_USB3OTG1>;
clock-names = "ref", "suspend", "bus";
#address-cells = <2>;
#size-cells = <2>;
ranges;
status = "disabled";
usbdrd_dwc3_1: usb@fc400000 {
compatible = "snps,dwc3";
reg = <0x0 0xfc400000 0x0 0x400000>;
interrupts = <GIC_SPI 221 IRQ_TYPE_LEVEL_HIGH>;
power-domains = <&power RK3588_PD_USB>;
resets = <&cru SRST_A_USB3OTG1>;
reset-names = "usb3-otg";
/* USB模式,host:主机,otg:otg模式,peripheral:设备 */
dr_mode = "host";
/* USB2.0和USB1.0 PHY USB3.0 PHY */
phys = <&u2phy1_otg>, <&usbdp_phy1_u3>;
phy-names = "usb2-phy", "usb3-phy";
phy_type = "utmi_wide"; /* USB2.0 PHY接口 */
/* When set clears the enblslpm in GUSB2PHYCFG,
* disabling the suspend signal to the PHY
*/
snps,dis_enblslpm_quirk;
/* When set, clear the u2_freeclk_exists in GUSB2PHYCFG,
* specify that USB2 PHY doesn't provide a free-running
* PHY clock.
*/
snps,dis-u2-freeclk-exists-quirk;
/* When set core will change PHY power from P0 to
* P1/P2/P3 without delay.
*/
snps,dis-del-phy-power-chg-quirk;
/* When set, disable u2mac linestate check during HS transmit */
snps,dis-tx-ipgap-linecheck-quirk;
status = "disabled";
};
};
};
[arch/arm64/boot/dts/rockchip/rk3588-evb.dtsi]
&usbdrd3_1 {
status = "okay";
};
3.DWC3驱动初始化
在xhci驱动初始化之前,需要dwc3驱动做一些底层的初始化,主要是非XHCI协议的硬件初始化,如模式、时钟、PHY等。dwc3驱动的主要工作如下:
-
首先初始化的是外围的rockchip驱动,内部的dwc3驱动由rockchip驱动进行匹配。
-
接着进入dwc3的驱动初始化流程,工作流程如下:解复位控制器。
-
获取控制器硬件配置的模式,并和软件配置的模式进行比较,起到校验作用
-
dwc3核心初始化,包括配置PHY接口、初始化PHY、给PHY上电、调整帧长度、使能Auto retry Feature等。
-
根据配置的模式,进入对应的模式。若是Host,则需要将USB2.0 PHY和USB3.0 PHY设置为PHY_MODE_USB_HOST,然后注册xhci的
platform_device
。
4.xHCI驱动初始化
dwc3驱动将设备树中的USB Host设备节点转换成platform_device,并将其name设置为"xhci-hcd",然后调用platform_device_add注册到系统中。USB Host驱动通过platform_driver实现,通过xhci_plat_init和xhci_plat_exit注册和注销。当USB Host设备或USB Host驱动注册的时候,都会调用platform_match
去匹配对方,若匹配成功,则会调用USB Host驱动中的probe函数,即usb_xhci_driver
中的xhci_plat_probe
函数。
[drivers/usb/host/xhci-plat.c]
static struct platform_driver usb_xhci_driver = {
.probe = xhci_plat_probe,
.remove = xhci_plat_remove,
.shutdown = usb_hcd_platform_shutdown,
.driver = {
.name = "xhci-hcd",
.pm = &xhci_plat_pm_ops,
.of_match_table = of_match_ptr(usb_xhci_of_match),
.acpi_match_table = ACPI_PTR(usb_xhci_acpi_match),
},
};
MODULE_ALIAS("platform:xhci-hcd");
static int __init xhci_plat_init(void)
{
xhci_init_driver(&xhci_plat_hc_driver, &xhci_plat_overrides);
return platform_driver_register(&usb_xhci_driver);
}
module_init(xhci_plat_init);
static void __exit xhci_plat_exit(void)
{
platform_driver_unregister(&usb_xhci_driver);
}
module_exit(xhci_plat_exit);
xhci_plat_probe
函数中会注册xHCI控制器的硬件操作函数集合,即xhci_hc_driver
数据结构,如下所示。
[drivers/usb/host/xhci.c]
static const struct hc_driver xhci_hc_driver = {
.description = "xhci-hcd",
.product_desc = "xHCI Host Controller",
.hcd_priv_size = sizeof(struct xhci_hcd),
/* generic hardware linkage */
.irq = xhci_irq,
.flags = HCD_MEMORY | HCD_DMA | HCD_USB3 | HCD_SHARED | HCD_BH,
/* basic lifecycle operations */
.reset = NULL, /* xhci_plat_setup */
.start = xhci_run, /* xhci_plat_start */
.stop = xhci_stop,
.shutdown = xhci_shutdown,
/* managing i/o requests and associated device resources */
.map_urb_for_dma = xhci_map_urb_for_dma,
.urb_enqueue = xhci_urb_enqueue,
.urb_dequeue = xhci_urb_dequeue,
.alloc_dev = xhci_alloc_dev,
.free_dev = xhci_free_dev,
.alloc_streams = xhci_alloc_streams,
.free_streams = xhci_free_streams,
.add_endpoint = xhci_add_endpoint,
.drop_endpoint = xhci_drop_endpoint,
.endpoint_disable = xhci_endpoint_disable,
.endpoint_reset = xhci_endpoint_reset,
.check_bandwidth = xhci_check_bandwidth,
.reset_bandwidth = xhci_reset_bandwidth,
.address_device = xhci_address_device,
.enable_device = xhci_enable_device,
.update_hub_device = xhci_update_hub_device,
.reset_device = xhci_discover_or_reset_device,
/* scheduling support */
.get_frame_number = xhci_get_frame,
/* root hub support */
.hub_control = xhci_hub_control,
.hub_status_data = xhci_hub_status_data,
.bus_suspend = xhci_bus_suspend,
.bus_resume = xhci_bus_resume,
.get_resuming_ports = xhci_get_resuming_ports,
/* call back when device connected and addressed */
.update_device = xhci_update_device,
.set_usb2_hw_lpm = xhci_set_usb2_hardware_lpm,
.enable_usb3_lpm_timeout = xhci_enable_usb3_lpm_timeout,
.disable_usb3_lpm_timeout = xhci_disable_usb3_lpm_timeout,
.find_raw_port_number = xhci_find_raw_port_number,
.clear_tt_buffer_complete = xhci_clear_tt_buffer_complete,
};
USB主机驱动的初始化过程如下图所示,具体的流程如下:
- USB主机的设备和驱动通过
platform_match
匹配,匹配成功后,驱动的xhci_plat_probe
函数被调用。 - 调用__usb_create_hcd函数 创建USB主机数据结构,即main_hcd(primary_hcd)和shared_hcd,前者为USB2.0,后者为USB3.0;创建USB主机数据结构的时候会初始化一个非常重要的定时器rh_timer,到期会调用rh_timer_func函数,该定时器的作用是轮询Hub的状态,检测USB设备连接、断开等情况。
- 调用
usb_add_hcd
注册USB2.0和USB3.0主机控制器,USB主机驱动的核心初始化工作在注册控制器时完成。- 注册USB总线。
- 分配root hub的数据结构
usb_device
(root hub不是一个真实的USB设备,无需调用xhci_alloc_dev
分配主机控制器资源);设置总线类型、设备类型和属性,总线类型usb_bus_type
中的usb_device_match
函数用于匹配USB设备和USB设备驱动,具体的匹配过程后面分析;使能使能root hub端点0,即将端点0的数据结构usb_host_endpoint
放到usb_device
输入输出端点的数组中,便于后续和USB设备通信。 - 调用
xhci_plat_setup
通过读取控制器的hcs_params寄存器获取寄存器地址、能力等信息,然后复位复位控制器,最后初始化xHCI需要的数据结构。 - 初始化root hub的tasklets,设置回调函数为
usb_giveback_urb_bh
,当root hub的port状态变化时,会调用usb_giveback_urb_bh
处理。 - 注册USB2.0控制器的中断处理函数
usb_hcd_irq
(中断只需要注册一次,2.0和3.0公用),usb_hcd_irq
内部会调用xhci_irq
,该中断用于处理控制器传输数据、事件。 - 调用
xhci_run
使能xHCI控制器。 - 调用
register_root_hub
注册root hub。root hub虽然不是真实的USB设备,但也需要获取描述符,走枚举的流程。- 获取设备描述符。使用控制传输,调用
usb_control_msg
。 - 初始化root hub。主要是获取配置描述符(获取配置描述符时,会一并获取该配置下的接口描述符、端点描述符等)。
- 枚举成功后,打印root hub的设备信息(需要开启对应的选项,否则不打印)。
- 注册USB设备。内部会调用
usb_device_match
匹配USB设备驱动,对于hub,则会匹配到hub_driver
,调用hub_probe
函数。
- 获取设备描述符。使用控制传输,调用
- 当设置
uses_new_polling
和HCD_POLL_RH(hcd)
标志时,内核会调用usb_hcd_poll_rh_status
函数轮询hub状态,以此探测设备的连接、断开等状态变化。详细的轮询过程在下一节分析。