USB总线-Linux内核USB3.0控制器初始化代码分析(三)

1.概述

RK33999使用synopsys dwc3的USB3.0控制器IP。早期的初始化需要在两个模块中进行,一个在rockchip官方提供的驱动中初始化,位于drivers/usb/dwc3/dwc3-rockchip.c文件中,主要初始化和CPU紧密相关的内容,如时钟、复位、电源、extcon(用于USB模式切换),另一个在synopsys提供的驱动中初始化,位于drivers/usb/dwc3/core.c文件中,这部分和USB3.0控制器密切相关,如USB3.0控制器内部寄存器地址、USB3.0的PHY、中断等。只有两个模块都初始化完毕,USB3.0控制器才能正常工作。本节只分析USB驱动早期初始化部分。

2.设备树

下面是USB3.0控制器的设备树节点。最外层的兼容属性为"rockchip,rk3399-dwc3",为rockchip定义的属性,有时钟、电源、复位、extcon等。extcon(external connectors)是USB用于状态通知的驱动,主要用于USB模式切换,当PHY收到中断及处理完USB状态后,通过extcon驱动广播到监听该事件的所有驱动。使用devm_extcon_register_notifier来注册监听usb状态变化的回调函数。内层的兼容属性为"snps,dwc3",为synopsys公司定义的属性,主要和USB控制器及PHY相关。dwc3设备节点具体属性信息可参考Documentation/devicetree/bindings/usb/dwc3.txt文档。
由于usbdrd3_0设备树节点是根节点的子节点,且有compatible属性,因此内核会自动将其转换为platform_device,然后和对应的驱动进行匹配,而其子节点usbdrd_dwc3_0内核则不会处理,由其父节点usbdrd3_0的驱动处理。

usbdrd3_0: usb0 {
    compatible = "rockchip,rk3399-dwc3";
    clocks = <&cru SCLK_USB3OTG0_REF>, <&cru SCLK_USB3OTG0_SUSPEND>,
            <&cru ACLK_USB3OTG0>, <&cru ACLK_USB3_GRF>;
    clock-names = "ref_clk", "suspend_clk", "bus_clk", "grf_clk";
    power-domains = <&power RK3399_PD_USB3>;  // 电源
    resets = <&cru SRST_A_USB3_OTG0>;  // 用于复位
    reset-names = "usb3-otg";
    #address-cells = <2>;
    #size-cells = <2>;
    ranges;
    status = "disabled";

    usbdrd_dwc3_0: dwc3@fe800000 {
        compatible = "snps,dwc3";
        reg = <0x0 0xfe800000 0x0 0x100000>;
        interrupts = ;  // 中断属性
        dr_mode = "otg";  // 模式,默认模式为OTG
        phys = <&u2phy0_otg>, <&tcphy0_usb3>;
        phy-names = "usb2-phy", "usb3-phy";  // USB PHY
        phy_type = "utmi_wide";
        /* 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 disable USB2 suspend phy */
        snps,dis_u2_susphy_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,tx-ipgap-linecheck-dis-quirk;
        /* when set, need an extraordinary delay to wait for xHC enter the 
           Halted state (i.e. HCH in the USBSTS register is '1') */
        snps,xhci-slow-suspend-quirk;
        /* when set, the xHC use the Evaluate Next TRB(ENT) flag to force
           the xHC to pre-fetch the next TRB of a TD */
        snps,xhci-trb-ent-quirk;
        /* when set, need warm reset on resume */
        snps,usb3-warm-reset-on-resume-quirk;
        status = "disabled";
    };
};
&usbdrd3_0 {
    status = "okay";
    extcon = <&fusb0>;
};
&i2c4 {
    status = "okay";
    i2c-scl-rising-time-ns = <160>;
    i2c-scl-falling-time-ns = <30>;
    clock-frequency = <400000>;

    fusb0: fusb30x@22 {
        compatible = "fairchild,fusb302";
        reg = <0x22>;
        pinctrl-names = "default";
        pinctrl-0 = <&fusb0_int>;
        int-n-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
        vbus-5v-gpios = <&gpio4 26 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
    ......
};

3.初始化驱动分析

初始化驱动分为两部分,一部分是和CPU相关的初始化,如时钟、电源等,由rockchip提供的驱动完成,另一部分是USB控制器相关的初始化,如USB控制器寄存器地址、中断、PHY等,由synopsys官方的驱动完成。这两部分分开来分析,首先分析rockchip提供的驱动,最后分析synopsys官方的驱动。

3.1.rockchip USB初始化驱动分析

rockchip提供的USB初始化驱动是一个platform_driver,设备树匹配的属性为"rockchip,rk3399-dwc3",入口函数为dwc3_rockchip_probe。下面分析一下入口函数的执行流程。

    [drivers/usb/dwc3/dwc3-rockchip.c]
    static const struct of_device_id rockchip_dwc3_match[] = {
        { .compatible = "rockchip,rk3399-dwc3" },
        { /* Sentinel */ }
    };
    MODULE_DEVICE_TABLE(of, rockchip_dwc3_match);
    static struct platform_driver dwc3_rockchip_driver = {
        .probe		= dwc3_rockchip_probe,
        .remove		= dwc3_rockchip_remove,
        .driver		= {
            .name	= "rockchip-dwc3",
            .of_match_table = rockchip_dwc3_match,
            .pm	= DEV_PM_OPS,
        },
    };
    module_platform_driver(dwc3_rockchip_driver);

dwc3_rockchip_probe函数主要的工作如下:
(1)获取时钟和使能时钟
(2)将子节点usbdrd_dwc3_0转换为platform_device,并保存子节点对应设备驱动程序的私有数据结构dwc3结构体指针,若获取不到dwc3结构体指针,则返回EPROBE_DEFER,内核稍后会再次执行dwc3_rockchip_probe函数。
(3)处理extcon属性,设置通知回调函数,设备的回调函数为dwc3_rockchip_device_notifier,主机的回调函数为dwc3_rockchip_host_notifier,回调函数通过otg_work工作队列执行
(4)异步执行dwc3_rockchip_async_probe函数,主要注册通知回调、设置电源等工作

    dwc3_rockchip_probe
        devm_kzalloc  // 分配struct dwc3_rockchip结构体内存
        of_clk_get_parent_count       // 获取设备树引用时钟源的数量
        rockchip->num_clocks = count  // 保存时钟源的数量
        devm_kcalloc             // 分配num_clocks个struct clk*指针
        platform_set_drvdata     // 保存dwc3_rockchip指针
        clk = of_clk_get(np, i)  // 获取时钟
        clk_prepare_enable       // 使能时钟
        rockchip->clks[i] = clk  // 保存时钟结构体指针
        pm_runtime_set_active    // 电源管理相关
        pm_runtime_enable
        pm_runtime_get_sync 
        devm_reset_control_get(dev, "usb3-otg")  // 获取用于复位的reset_control
        of_get_child_by_name(np, "dwc3")         // 获取子节点dwc3的device_node
        /* 遍历指定节点的所有子节点,将复合要求的转换为platform_device,即将usbdrd3_0的子节点
           usbdrd_dwc3_0转换为platform_device */
        of_platform_populate  
        // 初始化工作队列,工作队列的入口函数为dwc3_rockchip_otg_extcon_evt_work,用于USB模式切换
        INIT_WORK(&rockchip->otg_work, dwc3_rockchip_otg_extcon_evt_work)
        of_find_device_by_node  // 获取子节点的platform_device指针,即usbdrd_dwc3_0节点
        /* 获取子节点驱动的私有数据指针,即dwc3结构体指针,若获取不成功,则返回EPROBE_DEFER,
           内核会后续再次执行dwc3_rockchip_probe */
        rockchip->dwc = platform_get_drvdata  
        // 若是Host和OTG模式,则获取主机控制器的struct usb_hcd指针
        rockchip->hcd = dev_get_drvdata(&rockchip->dwc->xhci->dev)
        // 处理extcon属性
        dwc3_rockchip_get_extcon_dev
            // 判断usbdrd3_0节点中是否有extcon属性
            device_property_read_bool(dev, "extcon")
            extcon_get_edev_by_phandle  // 通过引用的设备树节点获取struct extcon_dev
            // 设置通知的回调函数
            rockchip->device_nb.notifier_call = dwc3_rockchip_device_notifier;
            rockchip->host_nb.notifier_call = dwc3_rockchip_host_notifier;
            rockchip->edev = edev;
        // 异步执行dwc3_rockchip_async_probe函数,实质上是通过system_unbound_wq工作队列执行
        async_schedule(dwc3_rockchip_async_probe, rockchip)

extcon的回调函数如下,设备和主机的回调函数除了参数不一样,其他都一样,都是通过schedule_work调度otg_work工作队列处理工作任务,工作任务函数为dwc3_rockchip_otg_extcon_evt_work,主要和USB模式切换有关,后面分析extcon驱动时详细分析。

    dwc3_rockchip_device_notifier
        // 获取dwc3_rockchip结构体
        rockchip = container_of(nb, struct dwc3_rockchip, device_nb)
        // usb不处于suspended状态时,调度otg_work工作队列
        schedule_work(&rockchip->otg_work)  

    dwc3_rockchip_host_notifier
        // 获取dwc3_rockchip结构体
        rockchip = container_of(nb, struct dwc3_rockchip, host_nb)
        // usb不处于suspended状态时,调度otg_work工作队列
        schedule_work(&rockchip->otg_work)

dwc3_rockchip_async_probe是异步执行的函数,实质上是通过system_unbound_wq工作队列执行。主要工作是注册extcon的通知回调函数、给USB PHY上电及创建调试属性文件组。

    dwc3_rockchip_async_probe
        // 获取设备是否有"needs-reset-on-resume"属性,有返回true
        device_property_read_bool(dev, "needs-reset-on-resume")
        devm_extcon_register_notifier(..., &rockchip->device_nb)  // 注册设备的extcon通知回调函数
        devm_extcon_register_notifier(..., &rockchip->host_nb)    // 注册主机的extcon通知回调函数

        // 若存在extcon或dr_mode为USB_DR_MODE_OTG,则进行电源相关设置
        pm_runtime_set_autosuspend_delay  // 设置autosuspend的延迟时间为500毫秒
        pm_runtime_allow  // 开启动态电源管理
        pm_runtime_suspend  // 进入suspend状态
        // 调度otg_work工作队列,执行函数为dwc3_rockchip_otg_extcon_evt_work
        schedule_work(&rockchip->otg_work)  

        // 若extcon不存在且dr_mode不为USB_DR_MODE_OTG,说明USB控制器不进行模式切换
        // 只能是主机模式或设备模式,
        // 设置connected为true,防止USB从PM suspend状态转换为resume时复位DWC3控制器,
        // 主机模式复位时会导致设备重新枚举
        rockchip->connected = true
        // 若设备没有设置"needs-reset-on-resume"属性且CPU为"rockchip,rk3399"且
        // dr_mode为USB_DR_MODE_HOST
        // RK3399的USB3.0的PHY为Type-C PHY,除了在dwc3_core_init()中上电,还需要在
        // 这里上电,以防止USB设备连接到DWC3主机控制器后状态切换为suspend时关闭PHY的电源
        phy_power_on(dwc->usb2_generic_phy)
        phy_power_on(dwc->usb3_generic_phy)
        rockchip->is_phy_on = true

        // 创建调试属性文件组
        sysfs_create_group(&dev->kobj, &dwc3_rockchip_attr_group)

3.2.synopsys USB初始化驱动分析

3.2.1.数据结构体分析

struct dwc3是USB3.0 OTG控制器的核心数据结构,所有工作都围绕此数据结构展开。该数据结构的意义如下面的代码所示,省略了一些不太重要的内容。

    [drivers/usb/dwc3/core.h]
    struct dwc3 {
    	// 端点0的USB控制请求, 即ep0out接收到的setup请求,如Get Descriptor, Set Interface等命令
        struct usb_ctrlrequest	*ctrl_req;
        struct dwc3_trb		*ep0_trb;       // 端点0控制传输的trb
        // 使用kzalloc函数分配的, 不是dma_alloc_coherent函数
        // Get Status和Set Sel等标准请求需要用到该预分配的buffer作为usb传输的负载
        void			*ep0_bounce;        // 端点0的bounce buffer
        void			*zlp_buf;           // request->zero设置时使用
        // 主要用于休眠唤醒(Hibernation)功能, 即休眠时保存控制器的寄存器信息到内存中
        void			*scratchbuf;        // RK3399 dwc3控制器没有使用
        u8			*setup_buf;             // 处理标准USB请求时使用
        dma_addr_t		ctrl_req_addr;      // ctrl_req的dma地址
        dma_addr_t		ep0_trb_addr;       // ep0_trb的dma地址
        dma_addr_t		ep0_bounce_addr;    // ep0_bounce的dma的地址
        dma_addr_t		scratch_addr;       // scratchbuf的dma地址
        struct dwc3_request	ep0_usb_req;    // dummy req used while handling STD USB requests
        struct device		*dev;
        struct platform_device	*xhci;      // USB主机控制器数据结构
        struct resource xhci_resources[DWC3_XHCI_RESOURCES_NUM];  // USB主机控制器资源
        // 事件buffer,控制器会将传输的事件信息保存到该缓冲区中,由软件统一处理
        struct dwc3_event_buffer *ev_buf;              
        struct dwc3_ep		*eps[DWC3_ENDPOINTS_NUM];  // 端点数据结构的指针数组,长度为32
        struct usb_gadget	gadget;  // USB控制器处于设备模式时使用的数据结构
        struct usb_gadget_driver *gadget_driver;  // 设备模式时使用的驱动,由具体匹配的设备决定
        void __iomem		*regs;    // 寄存器基地址
        size_t			regs_size;    // 寄存器地址长度
        enum usb_dr_mode	dr_mode;  // USB控制器模式枚举类型
        u32			fladj;  // frame length adjustment
        u32			irq_gadget;  // USB处于设备模式时的中断号
        u32			nr_scratch;  // scratch buffers的数量,没有使用
        u32			u1u2;        // only used on revisions <1.83a for workaround
        u32			maximum_speed;  // 最大的速度
        u32			revision;       // USB控制器寄存器中的版本号
        enum dwc3_ep0_next	ep0_next_event;  // hold the next expected event
        enum dwc3_ep0_state	ep0state;        // 端点0的状态
        enum dwc3_link_state	link_state;  // 链接状态
        u16			isoch_delay;  // wValue from Set Isochronous Delay request
        u16			u2sel;  // parameter from Set SEL request
        u16			u2pel;  // parameter from Set SEL request
        u8			u1sel;  // parameter from Set SEL request
        u8			u1pel;  // parameter from Set SEL request
        u8			speed;  // device speed (super, high, full, low)
        u8			num_out_eps;  // 输出端点的数量
        u8			num_in_eps;   // 输入端点的数量
        void			*mem;  // 指向该结构体的起始内存地址,dwc3有对齐要求,可能开始的多个字节内存没有使用
        struct dwc3_hwparams	hwparams;  // 寄存器缓存
        struct dentry		*root;
        struct debugfs_regset32	*regset;
        u8			test_mode;
        u8			test_mode_nr;
        u8			lpm_nyet_threshold;
        u8			hird_threshold;
        u32			grxthrcfg[2];
        u32			gtxthrcfg[2];
    };

struct dwc3结构体很复杂,内部嵌入了很多重要的数据结构,它们之间的关系可以简略用下面的图说明。xhci指向了usb作为主机模式时的数据结构,使用platform_device表示,主机模式的驱动是platform_driver,两个会通过设备名称xhci-hcd匹配,具体在分析主机驱动时说明。usb作为设备模式时的数据结构是usb_gadgetgadget_driverusb_gadget内部包含了usb_udcusb_gadget_ops数据结构,usb_gadget_ops是usb控制器硬件操作函数集合,指向了dwc3_gadget_opsgadget_driver是具体设备的驱动,需要根据匹配设备的类型决定,在设备匹配成功时设置。eps[32]是一个指针数组,保存了设备模式时所有端点结构体dwc3_ep的指针,每一个端点都对应一个dwc3_ep数据结构,端点0的操作函数集合指向了dwc3_gadget_ep0_ops,其他端点的操作函数集合指向了dwc3_gadget_ep_ops

USB总线-Linux内核USB3.0控制器初始化代码分析(三)_第1张图片

3.2.2.驱动分析

dwc3_rockchip_probe会将设备树节点usbdrd_dwc3_0转换为platform_device,随后会和dwc3_driver匹配,匹配成功后dwc3_probe函数将会被执行。

    [drivers/usb/dwc3/core.c]
    static const struct of_device_id of_dwc3_match[] = {
        {
            .compatible = "snps,dwc3"
        },
        {
            .compatible = "synopsys,dwc3"
        },
        { },
    };
    static struct platform_driver dwc3_driver = {
        .probe		= dwc3_probe,
        .remove		= dwc3_remove,
        .driver		= {
            .name	= "dwc3",
            .of_match_table	= of_match_ptr(of_dwc3_match),
            .acpi_match_table = ACPI_PTR(dwc3_acpi_match),
            .pm	= &dwc3_dev_pm_ops,
        },
    };
    module_platform_driver(dwc3_driver);

dwc3_probe完成dwc3 USB3.0控制器初始化的工作。主要内容如下:
(1)分配驱动的数据结构dwc3,并且按16字节对齐
(2)获取并处理资源,如寄存器资源、参数、最大速度、dr_mode等其他属性
(3)分配一致性DMA缓冲区dwc3_event_buffer,DMA将USB控制器事件传输到dwc3_event_buffer后由CPU处理
(4)核心初始化和USB模式初始化,后面详细分析
(5)初始化调试文件,具体如下图所示,用户可以在用户空间获取USB控制器信息和控制USB控制器

dwc3调试文件

    dwc3_probe
        mem = devm_kzalloc  // 分配dwc3结构体 
        dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1)  // 按16字节对齐
        dwc->mem = mem  // 保存分配的内存地址
        // 设置64位DMA掩码,设备并不一定能在所有内存地址上执行DMA操作,在这种情况下,应该设置
        // DMA地址掩码,dma_mask是设备DMA可以寻址的范围,coherent_dma_mask用于一致性映射的DMA掩码
        dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))
        platform_get_resource  // 获取寄存器地址资源
        devm_ioremap_resource  // 映射寄存器地址,但不包括xHCI的寄存器,xHCI的寄存器由其驱动处理
        lpm_nyet_threshold = 0xff /* default to highest possible threshold */
        tx_de_emphasis = 1  /* default to -3.5dB de-emphasis */
        /* default to assert utmi_sleep_n and use maximum allowed HIRD
	       threshold value of 0b1100 */
        hird_threshold = 12
        usb_get_maximum_speed  // 获取最大速度
        usb_get_dr_mode        // 获取dr_mode
        ......                 // 获取dwc3的一些属性,可参考设备树节点和dwc3.txt文档
        // 若maximum_speed为USB_SPEED_UNKNOWN,则设置为USB_SPEED_SUPER
        dwc->maximum_speed = USB_SPEED_SUPER;
        platform_set_drvdata  // 设置platform_device的私有数据
        dwc3_cache_hwparams   // 读取dwc3控制器内部寄存器保存的参数,保存到dwc3的hwparams
        dwc3_core_get_phy     // 获取dwc3 usb控制器的phy
        dwc3_alloc_event_buffers  // 分配事件缓冲区,长度为DWC3_EVENT_BUFFERS_SIZE=4096
            dwc3_alloc_one_event_buffer
                devm_kzalloc  // 首先分配管理事件缓冲区的dwc3_event_buffer结构体
                evt->dwc = dwc        // 保存dwc3结构体指针
                evt->length	= length  // 保存缓冲区长度
                // 分配一致性DMA缓冲区,buf保存虚拟地址,&evt->dma保存物理地址
                evt->buf = dma_alloc_coherent(dwc->dev, length, &evt->dma, GFP_KERNEL)
        dwc3_alloc_scratch_buffers  // 分配暂存缓冲区,没有使用
            dwc->scratchbuf = kmalloc_array  // 分配nr_scratch * DWC3_SCRATCHBUF_SIZE内存
        dwc3_core_init  // 核心初始化
        dwc3_core_init_mode  // 根据dr_mode初始化对应的模式,有device、host和otg模式
        dwc3_debugfs_init    // dwc3调试属性相关初始化
            debugfs_create_dir  // 创建调试目录/sys/kernel/debug/fe800000.dwc3(fe900000.dwc3)
            kzalloc  // 分配debugfs_regset32结构体
            dwc->regset->regs = dwc3_regs  // 保存需要dump的寄存器
            debugfs_create_regset32("regdump", ...)  // 创建可以dump寄存器值的调试文件
            // 创建可以切换模式的调试文件,需要开启CONFIG_USB_DWC3_DUAL_ROLE选项
            debugfs_create_file("mode", ...., &dwc3_mode_fops)

            // 创建调试模式的调试文件,需要开启CONFIG_USB_DWC3_DUAL_ROLE或CONFIG_USB_DWC3_GADGET选项
            debugfs_create_file("testmode", ..., &dwc3_testmode_fops)
            // 创建link_state调试文件,需要开启CONFIG_USB_DWC3_DUAL_ROLE或CONFIG_USB_DWC3_GADGET选项
            debugfs_create_file("link_state", ..., &dwc3_link_state_fops)
            // 创建端点调试文件,需要开启CONFIG_USB_DWC3_DUAL_ROLE或CONFIG_USB_DWC3_GADGET选项
            dwc3_debugfs_create_endpoint_dirs(dwc, root)
                dwc3_debugfs_create_endpoint_dir  // 循环创建输入端点调试文件
                    debugfs_create_dir  // 创建目录
                    dwc3_debugfs_create_endpoint_files  // 创建文件
                dwc3_debugfs_create_endpoint_dir  // 循环创建输出端点调试文件
                    debugfs_create_dir  // 创建目录
                    dwc3_debugfs_create_endpoint_files  // 创建文件

dwc3_core_init主要的工作是初始化USB控制器硬件,主要流程如下:
(1)获取USB控制器IP的版本,便于后续进行不同的配置,USB控制器不同IP版本之间有差别,将Linux内核版本号写入USB控制器寄存器,以便发现某些版本下的bug
(2)根据dr_mode,选择是否复位USB控制器
(3)从USB控制器寄存器缓存中获取USB控制器端点数量,此处端点表示的是一组资源
(4)建立scratch_buffers,采用流式DMA映射,RK3399的USB控制器没有使用该特性

    dwc3_core_init
        dwc3_readl(dwc->regs, DWC3_GSNPSID)  // 获取Global SNPS ID Register中的内容
        /* Write Linux Version Code to our GUID register so it's easy to figure
           out which kernel version a bug was found. */
        dwc3_writel(dwc->regs, DWC3_GUID, LINUX_VERSION_CODE)
        dwc3_writel(dwc->regs, DWC3_GUID, LINUX_VERSION_CODE)
        dwc3_soft_reset  // 软件复位USB控制器
            // 只有USB控制器处于设备模式才会复位,处于主机模式或dr_mode为OTG模式
            // 且寄存器被配置为主机模式时不复位,由后续的驱动复位
            dwc3_readl(dwc->regs, DWC3_GCTL)  // 读取USB的全局控制寄存器
            timeout = jiffies + msecs_to_jiffies(500)  // 设置复位等待超时时间为500毫秒
            dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST)  // 软件复位,复位成功该位被硬件清除

            // 循环读取DWC3_DCTL寄存器,判断复位是否成功
            dwc3_readl(dwc->regs, DWC3_DCTL)
            time_after(jiffies, timeout)  // 判断超时时间是否到
            cpu_relax()  // 内存屏障 asm volatile("yield" ::: "memory")
                // yield:表示当前执行的线程不重要,可以被切换出去,memory:内存屏障
                asm volatile("yield" ::: "memory")  // aarch64
            dwc3_core_soft_reset  // 初始化PHY
            dwc3_phy_setup  // Configure USB PHY Interface of DWC3 Core
            dwc3_core_num_eps  // 获取端点数量
                dwc->num_in_eps = DWC3_NUM_IN_EPS(parms)  // 输入端点数量
                dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps  // 输出端点数量
            dwc3_setup_scratch_buffers  // 没有使用
                // 流式DMA映射,数据可以双向移动,即可以传输到设备也可以从设备传出,返回物理地址
                scratch_addr = dma_map_single(..., DMA_BIDIRECTIONAL);
                dwc->scratch_addr = scratch_addr  // 保存物理地址
                param = lower_32_bits(scratch_addr)  // 获取低32位物理地址
                dwc3_send_gadget_generic_command(..., DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO, ...)
                    // 向寄存器写入低32位地址,作为命令的参数,命令执行的时候会使用
                    dwc3_writel(dwc->regs, DWC3_DGCMDPAR, param)  
                    // 写入命令,DWC3_DGCMD_CMDACT:激活命令,设置后开始执行命令
                    // DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO:Set Scratchpad Buffer Array Address Lo
                    dwc3_writel(dwc->regs, DWC3_DGCMD, cmd | DWC3_DGCMD_CMDACT)

USB控制器host模式驱动和device模式驱动是两套驱动框架,不能通用。因此,dwc3驱动初始化的时候,会根据dr_mode初始化对应的USB模式驱动,dr_mode在设备树中指定。设置USB控制器模式驱动的是dwc3_core_init_mode函数,该函数的主要工作如下:
(1)当dwc->dr_mode == USB_DR_MODE_PERIPHERAL,调用dwc3_gadget_init函数初始化device模式驱动
(2)当dwc->dr_mode == USB_DR_MODE_HOST,调用dwc3_host_init函数初始化host模式驱动
(3)当dwc->dr_mode == USB_DR_MODE_OTG,调用dwc3_gadget_initdwc3_host_init函数,同时初始化device和host模式驱动

    dwc3_core_init_mode  // 根据dr_mode初始化对应的驱动
        // dwc->dr_mode == USB_DR_MODE_PERIPHERAL
        dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE)  // 设置USB控制器为设备模式
        dwc3_gadget_init  // 初始化设备驱动

        // dwc->dr_mode == USB_DR_MODE_HOST
        dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST)  // 设置USB控制器为主机模式
        dwc3_host_init  // 初始化主机驱动

        // dwc->dr_mode == USB_DR_MODE_OTG
        // 设置USB控制器为OTG模式,处于OTG模式的USB控制器可以为主机也可以为设备,由extcon负责切换
        dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG)
        dwc3_host_init    // 初始化主机驱动
        dwc3_gadget_init  // 初始化设备驱动

主机驱动和设备驱动具体的初始化过程在后面讲到主机和设备的时候具体分析。

参考资料

  1. Linux内核4.4版本源码(RK官方代码)

你可能感兴趣的:(Linux设备驱动,USB,dwc3,Linux内核,USB驱动,RK3399)