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" },
{ }
};
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
of_clk_get_parent_count
rockchip->num_clocks = count
devm_kcalloc
platform_set_drvdata
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")
of_get_child_by_name(np, "dwc3")
of_platform_populate
INIT_WORK(&rockchip->otg_work, dwc3_rockchip_otg_extcon_evt_work)
of_find_device_by_node
rockchip->dwc = platform_get_drvdata
rockchip->hcd = dev_get_drvdata(&rockchip->dwc->xhci->dev)
dwc3_rockchip_get_extcon_dev
device_property_read_bool(dev, "extcon")
extcon_get_edev_by_phandle
rockchip->device_nb.notifier_call = dwc3_rockchip_device_notifier;
rockchip->host_nb.notifier_call = dwc3_rockchip_host_notifier;
rockchip->edev = edev;
async_schedule(dwc3_rockchip_async_probe, rockchip)
extcon的回调函数如下,设备和主机的回调函数除了参数不一样,其他都一样,都是通过schedule_work 调度otg_work 工作队列处理工作任务,工作任务函数为dwc3_rockchip_otg_extcon_evt_work ,主要和USB模式切换有关,后面分析extcon驱动时详细分析。
dwc3_rockchip_device_notifier
rockchip = container_of(nb, struct dwc3_rockchip, device_nb)
schedule_work(&rockchip->otg_work)
dwc3_rockchip_host_notifier
rockchip = container_of(nb, struct dwc3_rockchip, host_nb)
schedule_work(&rockchip->otg_work)
dwc3_rockchip_async_probe 是异步执行的函数,实质上是通过system_unbound_wq 工作队列执行。主要工作是注册extcon的通知回调函数、给USB PHY上电及创建调试属性文件组。
dwc3_rockchip_async_probe
device_property_read_bool(dev, "needs-reset-on-resume")
devm_extcon_register_notifier(..., &rockchip->device_nb)
devm_extcon_register_notifier(..., &rockchip->host_nb)
pm_runtime_set_autosuspend_delay
pm_runtime_allow
pm_runtime_suspend
schedule_work(&rockchip->otg_work)
rockchip->connected = true
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;
struct dwc3_trb *ep0_trb;
void *ep0_bounce;
void *zlp_buf;
void *scratchbuf;
u8 *setup_buf;
dma_addr_t ctrl_req_addr;
dma_addr_t ep0_trb_addr;
dma_addr_t ep0_bounce_addr;
dma_addr_t scratch_addr;
struct dwc3_request ep0_usb_req;
struct device *dev;
struct platform_device *xhci;
struct resource xhci_resources[DWC3_XHCI_RESOURCES_NUM];
struct dwc3_event_buffer *ev_buf;
struct dwc3_ep *eps[DWC3_ENDPOINTS_NUM];
struct usb_gadget gadget;
struct usb_gadget_driver *gadget_driver;
void __iomem *regs;
size_t regs_size;
enum usb_dr_mode dr_mode;
u32 fladj;
u32 irq_gadget;
u32 nr_scratch;
u32 u1u2;
u32 maximum_speed;
u32 revision;
enum dwc3_ep0_next ep0_next_event;
enum dwc3_ep0_state ep0state;
enum dwc3_link_state link_state;
u16 isoch_delay;
u16 u2sel;
u16 u2pel;
u8 u1sel;
u8 u1pel;
u8 speed;
u8 num_out_eps;
u8 num_in_eps;
void *mem;
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_gadget 和gadget_driver ;usb_gadget 内部包含了usb_udc 和usb_gadget_ops 数据结构,usb_gadget_ops 是usb控制器硬件操作函数集合,指向了dwc3_gadget_ops ;gadget_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_probe
mem = devm_kzalloc
dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1)
dwc->mem = mem
dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))
platform_get_resource
devm_ioremap_resource
lpm_nyet_threshold = 0xff
tx_de_emphasis = 1
hird_threshold = 12
usb_get_maximum_speed
usb_get_dr_mode
......
dwc->maximum_speed = USB_SPEED_SUPER;
platform_set_drvdata
dwc3_cache_hwparams
dwc3_core_get_phy
dwc3_alloc_event_buffers
dwc3_alloc_one_event_buffer
devm_kzalloc
evt->dwc = dwc
evt->length = length
evt->buf = dma_alloc_coherent(dwc->dev, length, &evt->dma, GFP_KERNEL)
dwc3_alloc_scratch_buffers
dwc->scratchbuf = kmalloc_array
dwc3_core_init
dwc3_core_init_mode
dwc3_debugfs_init
debugfs_create_dir
kzalloc
dwc->regset->regs = dwc3_regs
debugfs_create_regset32("regdump", ...)
debugfs_create_file("mode", ...., &dwc3_mode_fops)
debugfs_create_file("testmode", ..., &dwc3_testmode_fops)
debugfs_create_file("link_state", ..., &dwc3_link_state_fops)
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)
dwc3_writel(dwc->regs, DWC3_GUID, LINUX_VERSION_CODE)
dwc3_writel(dwc->regs, DWC3_GUID, LINUX_VERSION_CODE)
dwc3_soft_reset
dwc3_readl(dwc->regs, DWC3_GCTL)
timeout = jiffies + msecs_to_jiffies(500)
dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST)
dwc3_readl(dwc->regs, DWC3_DCTL)
time_after(jiffies, timeout)
cpu_relax()
asm volatile("yield" ::: "memory")
dwc3_core_soft_reset
dwc3_phy_setup
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
scratch_addr = dma_map_single(..., DMA_BIDIRECTIONAL);
dwc->scratch_addr = scratch_addr
param = lower_32_bits(scratch_addr)
dwc3_send_gadget_generic_command(..., DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO, ...)
dwc3_writel(dwc->regs, DWC3_DGCMDPAR, param)
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_init 和dwc3_host_init 函数,同时初始化device和host模式驱动
dwc3_core_init_mode
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE)
dwc3_gadget_init
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST)
dwc3_host_init
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_desc 和dwc3_gadget_ep0_ops ,其他端点的操作函数为dwc3_gadget_ep_ops (5)添加新的gadget到udc class driver,分配usb_udc 结构体,保存usb_gadget 结构体指针,并挂入到udc_list 链表中
[drivers\usb\dwc3\gadget.c]
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,
};
static struct usb_endpoint_descriptor dwc3_gadget_ep0_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_CONTROL,
};
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
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
dwc->ctrl_req = dma_alloc_coherent(..., &dwc->ctrl_req_addr, ...)
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)
dwc->ep0_bounce = dma_alloc_coherent
dwc->zlp_buf = kzalloc
dwc->gadget.ops = &dwc3_gadget_ops
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
dep->number = epnum
dep->direction = !!direction
dep->regs = dwc->regs + DWC3_DEP_BASE(epnum)
dwc->eps[epnum] = dep
usb_ep_set_maxpacket_limit(&dep->endpoint, 512)
dep->endpoint.maxburst = 1
dep->endpoint.ops = &dwc3_gadget_ep0_ops
usb_ep_set_maxpacket_limit(&dep->endpoint, 1024)
dep->endpoint.max_streams = 15
dep->endpoint.ops = &dwc3_gadget_ep_ops
ist_add_tail(&dep->endpoint.ep_list, &dwc->gadget.ep_list)
dwc3_gadget_init_hw_endpoints
...
usb_add_gadget_udc
usb_add_gadget_udc_release
kzalloc
INIT_WORK(&gadget->work, usb_gadget_state_work)
device_register
udc->gadget = gadget;
gadget->udc = udc;
list_add_tail(&udc->list, &udc_list)
device_add
usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED)
gadget->state = state
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;
xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO)
dma_set_coherent_mask(&xhci->dev, dwc->dev->coherent_dma_mask)
dwc->xhci = xhci
phy_create_lookup(..., "usb2-phy", ...)
phy_create_lookup(..., "usb3-phy", ...)
platform_device_add(xhci)
|