IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> USB总线-Linux内核USB3.0控制器初始化代码分析(三) -> 正文阅读

[数据结构与算法]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 = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH 0>;  // 中断属性
        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 {
        struct usb_ctrlrequest	*ctrl_req;  // 用于端点0的USB控制请求
        struct dwc3_trb		*ep0_trb;       // 端点0控制传输的trb
        void			*ep0_bounce;        // 端点0的bounce buffer
        void			*zlp_buf;           // request->zero设置时使用
        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主机控制器资源
        struct dwc3_event_buffer *ev_buf;              // 事件buffer
        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

数据结构关系

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  // 初始化设备驱动
3.2.2.1.USB设备模式初始化分析

dwc3_gadget_init函数初始化device模式的驱动,主要的工作如下:
(1)获取和保存USB控制器中断,device模式和host模式的中断处理函数不一样
(2)分配dma一致性缓冲区,这里有多个缓冲区,后面在分析device模式驱动时说明
(3)设置usb_gadget结构体,该结构体是设备驱动的核心结构体,内嵌的usb_udc结构体描述了一个usb device控制器,内嵌的usb_gadget_ops是usb devcie控制器的硬件操作函数,指向dwc3_gadget_ops
(4)初始化输出和输入端点,端点的数量从usb控制器的寄存器缓存中获取,每一个端点分配一个dwc3_ep结构体,保存到eps[32]的数组中,使用端点编号进行索引,端点0的描述符和操作函数分别为dwc3_gadget_ep0_descdwc3_gadget_ep0_ops,其他端点的操作函数为dwc3_gadget_ep_ops
(5)添加新的gadget到udc class driver,分配usb_udc结构体,保存usb_gadget结构体指针,并挂入到udc_list链表中

    [drivers\usb\dwc3\gadget.c]
    // gadget操作函数集合,用于控制USB控制器硬件
    static const struct usb_gadget_ops dwc3_gadget_ops = {
        .get_frame		= dwc3_gadget_get_frame,
        .wakeup			= dwc3_gadget_wakeup,
        .set_selfpowered	= dwc3_gadget_set_selfpowered,
        .pullup			= dwc3_gadget_pullup,
        .udc_start		= dwc3_gadget_start,
        .udc_stop		= dwc3_gadget_stop,
    };
    // 端点0的描述符
    static struct usb_endpoint_descriptor dwc3_gadget_ep0_desc = {
        .bLength	= USB_DT_ENDPOINT_SIZE,
        .bDescriptorType = USB_DT_ENDPOINT,
        .bmAttributes	= USB_ENDPOINT_XFER_CONTROL,
    };
    // 端点0的操作函数
    static const struct usb_ep_ops dwc3_gadget_ep0_ops = {
        .enable		= dwc3_gadget_ep0_enable,
        .disable	= dwc3_gadget_ep0_disable,
        .alloc_request	= dwc3_gadget_ep_alloc_request,
        .free_request	= dwc3_gadget_ep_free_request,
        .queue		= dwc3_gadget_ep0_queue,
        .dequeue	= dwc3_gadget_ep_dequeue,
        .set_halt	= dwc3_gadget_ep0_set_halt,
        .set_wedge	= dwc3_gadget_ep_set_wedge,
    };
    // 其他端点的操作函数
    static const struct usb_ep_ops dwc3_gadget_ep_ops = {
        .enable		= dwc3_gadget_ep_enable,
        .disable	= dwc3_gadget_ep_disable,
        .alloc_request	= dwc3_gadget_ep_alloc_request,
        .free_request	= dwc3_gadget_ep_free_request,
        .queue		= dwc3_gadget_ep_queue,
        .dequeue	= dwc3_gadget_ep_dequeue,
        .set_halt	= dwc3_gadget_ep_set_halt,
        .set_wedge	= dwc3_gadget_ep_set_wedge,
    };

    dwc3_gadget_init
        // 获取USB控制器中断号,只要其中一个成功即可
        platform_get_irq_byname(dwc3_pdev, "peripheral")
        platform_get_irq_byname(dwc3_pdev, "dwc_usb3")
        platform_get_irq(dwc3_pdev, 0)
        dwc->irq_gadget = irq  // 保存软件中断号
        // 分配USB设备控制请求一致性缓冲区
        dwc->ctrl_req = dma_alloc_coherent(..., &dwc->ctrl_req_addr, ...)
        // 分配端点0的控制请求一致性缓冲区
        dwc->ep0_trb = dma_alloc_coherent(...,sizeof(*dwc->ep0_trb)*2, &dwc->ep0_trb_addr,...)
        dwc->setup_buf = kzalloc(DWC3_EP0_BOUNCE_SIZE, GFP_KERNEL)  // 512字节
        // 端点0的bounce一致性缓冲区,长度为512字节
        dwc->ep0_bounce = dma_alloc_coherent
        dwc->zlp_buf = kzalloc  // 长度1024,used when request->zero is set

        dwc->gadget.ops = &dwc3_gadget_ops  // gadget操作函数集合
        dwc->gadget.speed		= USB_SPEED_UNKNOWN;
        dwc->gadget.sg_supported	= true;
        dwc->gadget.name		= "dwc3-gadget";
        dwc->gadget.is_otg		= dwc->dr_mode == USB_DR_MODE_OTG;

        dwc3_gadget_init_endpoints         // 初始化端点
            dwc3_gadget_init_hw_endpoints  // 初始化输出端点
                kzalloc  // 分配struct dwc3_ep结构体
                dep->number = epnum  // 设置端点编号
                dep->direction = !!direction  // 设置方向
                dep->regs = dwc->regs + DWC3_DEP_BASE(epnum)  // 设置端点寄存器基地址
                dwc->eps[epnum] = dep  // 保存端点数据结构地址
                // epnum == 0 || epnum == 1表示端点0
                usb_ep_set_maxpacket_limit(&dep->endpoint, 512)  // 端点0的maxpacket为512
                dep->endpoint.maxburst = 1  // 端点0的burst为1
                dep->endpoint.ops = &dwc3_gadget_ep0_ops  // 端点0的操作函数集合
                // 其他端点
                usb_ep_set_maxpacket_limit(&dep->endpoint, 1024)  // maxpacket为1024
                dep->endpoint.max_streams = 15  // 流数量最大为15
                dep->endpoint.ops = &dwc3_gadget_ep_ops  // 操作函数集合
                // 非端点0的挂入gadget.ep_list链表
                ist_add_tail(&dep->endpoint.ep_list, &dwc->gadget.ep_list)
            // 输入端点初始化和输出端点初始化相似,只是端点方向不一样
            dwc3_gadget_init_hw_endpoints  // 初始化输入端点
            ...

        usb_add_gadget_udc  // 添加新的gadget到udc class driver
            usb_add_gadget_udc_release
                kzalloc  // 分配struct usb_udc结构体
                // 工作队列执行函数为usb_gadget_state_work
                INIT_WORK(&gadget->work, usb_gadget_state_work)
                device_register  // 注册gadget设备
                udc->gadget = gadget;  // 保存gadget指针
	            gadget->udc = udc;
                list_add_tail(&udc->list, &udc_list)  // 挂入udc_list链表
                device_add  // 注册udc设备
                usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED)
                    gadget->state = state  // 设置状态为USB_STATE_NOTATTACHED
                    schedule_work(&gadget->work)  // 调度工作队列
                udc->vbus = true
3.2.2.2.USB主机模式初始化分析

dwc3_host_init初始化usb host模式驱动,该函数的工作较为简单,首先获取并保存usb控制器中断号,分配并注册host模式的platform_device结构体,注册时会匹配host模式的驱动,根据设备名称"xhci-hcd"匹配,若匹配成功,则对用的probe函数会被调用。这个在分析host模式驱动时具体说明。

    dwc3_host_init
        // 获取中断,只要其中之一获取成功即刻
        platform_get_irq_byname(dwc3_pdev, "host")
        platform_get_irq_byname(dwc3_pdev, "dwc_usb3")
        platform_get_irq(dwc3_pdev, 0)
        // 保存中断号
        dwc->xhci_resources[1].start = irq;
	    dwc->xhci_resources[1].end = irq;

        // 创建platform_device数据结构,设备名称为xhci-hcd
        xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO)
        // 设置DMA一致性内存的掩码
        dma_set_coherent_mask(&xhci->dev, dwc->dev->coherent_dma_mask)
        dwc->xhci = xhci  // 保存xhci数据结构 
        phy_create_lookup(..., "usb2-phy", ...)  // 创建USB2.0的PHY
        phy_create_lookup(..., "usb3-phy", ...)  // 创建USB3.0的PHY
        // 注册USB主机控制器设备,会和主机控制器驱动匹配,匹配成功则调用主机控制器驱动的probe函数
        platform_device_add(xhci)  
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-07-26 12:18:24  更:2021-07-26 12:18:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 9:46:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计