关键词:linux、驱动、usb、phy
同以太网类似,USB芯片也分为Host Controller部分(主机控制器/设备控制器)和PHY部分(收发器) 两大部分组成。
USB 的 PHY 与以太网的 PHY 类似,用于数字信号和电气信号的转换。
主机控制器Controller部分主要实现USB的协议和控制,内部逻辑主要有 MAC层,CSR层,FIFO层等。
- MAC层实现安装USB协议进行数据包打包和解包,并把数据按照 UTMI/ULPI总线格式发送给PHY。
- FIFO控制层主要是和DDR进行数据交互,控制USB从DDR搬运数据的通道
USB PHY负责最底层的信号转换,作用类似于网口的PHY。主要实现 并转串的功能,把控制器通过 UTMI或ULPI总线传递过来的并行数据 转换为串行数据,再通过差分数据线输出到USB接口。或反之。
总之USB芯片内部实现的功能就是接受软件的控制,进而从内存搬运数据并按照USB协议进行数据打包,并串转换后输出到芯片外部。或者从芯片外部接收差分数据信号,串并转换后进行数据打包并写到内存中。
以Controller 和 PHY 都被封装到SOC为例,如图
- VBUS :电压线,主机利用VBUS给USB设备提供工作电压。
- D+ : 正向传送数据 数据线
- D-: 反向传送数据 数据线
一般来说如果芯片的usb phy封装在芯片内,基本采用UTMI+的接口。不封装到芯片内的一般采用ULPI接口,这样可以降低pin的数量。
如下图是嵌入式设备上的USB系统,其中SOC内嵌了USB控制器,USB 收发器(PHY)没有封装到芯片内。该控制器支持4条总线和3中操作模式。
- 总线1工作在主机模式下,通过USB收发器(PHY) 和 A型接口( USB Type A)连接。USB Type A常用于个人电脑PC及消费类电子产品中,用于连接键盘,鼠标等外设
- 总线2 也工作在主机模式下,只不过它的USB收发器(PHY)连接的是内嵌USB设备,如打印机等
- 总线3工作在设备模式下,通过USB收发器(PHY) 和 B型接口(USB Type B)连接。B型接口通过一条 B-A线和主机连接。在这种模式下,该嵌入式设备可以当做USB从设备使用。同PC机相比,嵌入式设备(如MP3,手机等)作为USB的设备端,所以 大部分嵌入式设备 除了包含主机控制器之外,还包含USB设备控制器。
- 总线4接的是 OTG(On-The-Go)控制器,既可以做主机,也可以作从机,与前三种总线不同,总线4的USB收发器是智能的,能够通过I2C 和处理器交换控制信息,USB OTG 收发器(PHY)的另一端和Mini-AB OTG接口相连。如果两个设备都支持OTG,他们不需要作为主机的计算机介入就可以直接通信。
USB 主机控制器分为以下几种:
- UHCI(Universal Host Controller Interface 通用主机控制器接口) 该标准是英特尔提出。
- OHCI(Open Host Controller Interface 开放主机控制器接口)该接口是康柏和微软等公司提出,兼容OHCI的控制器硬件智能程度比UHCI高。
- EHCI(Enhanced Host Controller Interface 增强型主机控制器接口) 该主机控制器支持高速的USB2.0S设备。为支持低速的USB设备,该控制器通常同时包含UHCI 和 OHCI控制器。
- USB OTG控制器,这类控制器在嵌入式微控制器领域越来越受欢迎,由于采用了OTG控制器,每个通信终端 即能作主机也能作从机,设备可以根据功能需要在主机模式和设备模式之间任意切换。
主机控制器内嵌了一个叫 根集线器 的硬件。很重要!!!!
USB集线器又称为USB Hub,用于拓展计算机USB接口。计算机主板上对外往往提供多个USB接口,这些接口往往都是通过主板上的USB集线器芯片来拓展出来的。在USB总线通信协议中,通过设备描述符和接口描述符来判断该USB是否为USB集线器。
USB 根集线器(USB Root Hub)指的是直接连接到USB主控制器上的USB Hub。USB根集线器供电与USB主控器供电来源相同。USB总线中只有USB主机和USB集线器可以向外部供电。
USB PHY驱动
以 RK3128为例,其USB PHY 使用的是 USB2.0 PHY ,使用的是 Innosilicon 的IP ,对应的驱动文件为linux-4.4 \drivers\phy\rockchip\phy-rockchip-inno-usb2.c。
Innosilicon USB2.0 PHY 的硬件框架如下图 2-1 所示,主要包括五个子模块:Transceiver block,PLL clock multiplier,digital UTMI+ core,automatic test functionality,OTG Circuitry(optional)。
rk3128 USB PHY 设备树配置如下:
grf: syscon@20008000 { //System Controller Registers R/W driver,syscon参见内核文档:Documentation\devicetree\bindings\mfd\syscon.txt
compatible = "rockchip,rk3128-grf", "syscon", "simple-mfd";
reg = <0x20008000 0x1000>;
#address-cells = <1>;
#size-cells = <1>;
u2phy: usb2-phy@17c {
compatible = "rockchip,rk3128-usb2phy";
reg = <0x017c 0x0c>;
clocks = <&cru SCLK_OTGPHY0>;
clock-names = "phyclk";
#clock-cells = <0>;
clock-output-names = "usb480m_phy";
assigned-clocks = <&cru SCLK_USB480M>;
assigned-clock-parents = <&u2phy>;
status = "okay";
u2phy_otg: otg-port { #usb otg 的设备树配置
#phy-cells = <0>;
interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "otg-bvalid", "otg-id",
"linestate";
status = "okay";
};
u2phy_host: host-port { #usb host 的设备树配置
#phy-cells = <0>;
interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "linestate";
status = "okay";
};
};
};
设备树 u2phy 对应 /sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c :
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c# ll
total 0
drwxr-xr-x 5 root root 0 Jan 1 00:53 ./
drwxr-xr-x 4 root root 0 Jan 1 00:53 ../
lrwxrwxrwx 1 root root 0 Jan 1 01:35 driver -> ../../../../bus/platform/drivers/rockchip-usb2phy/
-rw-r--r-- 1 root root 4096 Jan 1 01:35 driver_override
drwxr-xr-x 3 root root 0 Jan 1 00:53 extcon/
-r--r--r-- 1 root root 4096 Jan 1 01:35 modalias
lrwxrwxrwx 1 root root 0 Jan 1 01:35 of_node -> ../../../../firmware/devicetree/base/syscon@20008000/usb2-phy@17c/
-rw-r--r-- 1 root root 4096 Jan 1 01:35 otg_mode
drwxr-xr-x 4 root root 0 Jan 1 00:53 phy/
drwxr-xr-x 2 root root 0 Jan 1 01:35 power/
lrwxrwxrwx 1 root root 0 Jan 1 01:35 subsystem -> ../../../../bus/platform/
-rw-r--r-- 1 root root 4096 Jan 1 00:53 uevent
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c# cat modalias
of:Nusb2-phyT<NULL>Crockchip,rk3128-usb2phy
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c# cat otg_mode
otg
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c# cat uevent
DRIVER=rockchip-usb2phy
OF_NAME=usb2-phy
OF_FULLNAME=/syscon@20008000/usb2-phy@17c
OF_COMPATIBLE_0=rockchip,rk3128-usb2phy
OF_COMPATIBLE_N=1
MODALIAS=of:Nusb2-phyT<NULL>Crockchip,rk3128-usb2phy
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c# cd extcon/
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/extcon# ls
extcon0
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/extcon# cd extcon0/
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/extcon/extcon0# ls
cable.0 cable.1 cable.2 cable.3 cable.4 cable.5 cable.6 device name power state subsystem uevent
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/extcon/extcon0# cd ../..
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c# ls
driver driver_override extcon modalias of_node otg_mode phy power subsystem uevent
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c# cd phy/
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy# ls
phy-20008000.syscon:usb2-phy@17c.0 phy-20008000.syscon:usb2-phy@17c.1
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy# cd phy-20008000.syscon\:usb2-phy@17c.0/
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy/phy-20008000.syscon:usb2-phy@17c.0# ls
device of_node power subsystem uevent
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy/phy-20008000.syscon:usb2-phy@17c.0# cat uevent
OF_NAME=otg-port
OF_FULLNAME=/syscon@20008000/usb2-phy@17c/otg-port
OF_COMPATIBLE_N=0
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy/phy-20008000.syscon:usb2-phy@17c.0# cd ..
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy# cd phy-20008000.syscon\:usb2-phy@17c.1/
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy/phy-20008000.syscon:usb2-phy@17c.1# ls
device of_node power subsystem uevent
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy/phy-20008000.syscon:usb2-phy@17c.1# cat uevent
OF_NAME=host-port
OF_FULLNAME=/syscon@20008000/usb2-phy@17c/host-port
OF_COMPATIBLE_N=0
root@mxlos:/sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c/phy/phy-20008000.syscon:usb2-phy@17c.1#
以 RK3128为例,其USB PHY 使用的是 USB2.0 PHY ,使用的是 Innosilicon 的IP ,对应的驱动文件为linux-4.4 \drivers\phy\rockchip\phy-rockchip-inno-usb2.c,对应的平台驱动为 struct platform_driver rockchip_usb2phy_driver:
static struct platform_driver rockchip_usb2phy_driver = {
.probe = rockchip_usb2phy_probe,
.driver = {
.name = "rockchip-usb2phy",
.pm = ROCKCHIP_USB2PHY_DEV_PM,
.of_match_table = rockchip_usb2phy_dt_match,
/*
static const struct of_device_id rockchip_usb2phy_dt_match[] = {
{ .compatible = "rockchip,rk3128-usb2phy", .data = &rk312x_phy_cfgs },
{ .compatible = "rockchip,rk3308-usb2phy", .data = &rk3308_phy_cfgs },
{ .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs },
{}
};
*/
},
};
module_platform_driver(rockchip_usb2phy_driver);
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
RK3128 USB PHY 驱动 struct platform_driver rockchip_usb2phy_driver 与设备 /sys/devices/platform/20008000.syscon/20008000.syscon:usb2-phy@17c 匹配时调用 probe = struct platform_driver rockchip_usb2phy_driverb.probe= rockchip_usb2phy_probe,其执行过程如下:
static int rockchip_usb2phy_probe(struct platform_device *pdev)->
struct rockchip_usb2phy *rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL);
const struct of_device_id *match = of_match_device(dev->driver->of_match_table, dev);
rphy->grf = syscon_node_to_regmap(dev->parent->of_node);
of_property_read_u32(np, "reg", ®);
rphy->dev = dev;
phy_cfgs = match->data;
rphy->chg_state = USB_CHG_STATE_UNDEFINED;
rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
rphy->edev_self = false;
platform_set_drvdata(pdev, rphy);
ret = rockchip_usb2phy_extcon_register(rphy)->
struct extcon_dev *edev = devm_extcon_dev_allocate(rphy->dev, rockchip_usb2phy_extcon_cable);
ret = devm_extcon_dev_register(rphy->dev, edev);
rphy->edev = edev;
struct rockchip_usb2phy *rphy->clk = of_clk_get_by_name(np, "phyclk");
clk_prepare_enable(rphy->clk); //使用能 PHY 时钟
ret = rphy->phy_cfg->phy_tuning(rphy);
index = 0;
for_each_available_child_of_node(np, child_np) {
struct rockchip_usb2phy_port *rport = &rphy->ports[index];
struct phy *phy;
/* This driver aims to support both otg-port and host-port */
if (of_node_cmp(child_np->name, "host-port") && of_node_cmp(child_np->name, "otg-port"))
goto next_child;
/*
static const struct phy_ops rockchip_usb2phy_ops = {
.init = rockchip_usb2phy_init,
.exit = rockchip_usb2phy_exit,
.power_on = rockchip_usb2phy_power_on,
.power_off = rockchip_usb2phy_power_off,
.set_mode = rockchip_usb2phy_set_mode,
.owner = THIS_MODULE,
};
*/
phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops); //创建 struct phy *phy
ptr = devres_alloc(devm_phy_consume, sizeof(*ptr), GFP_KERNEL);
struct phy *phy = phy_create(dev, node, ops)->
struct phy *phy = kzalloc(sizeof(*phy), GFP_KERNEL);
id = ida_simple_get(&phy_ida, 0, 0, GFP_KERNEL);
device_initialize(&phy->dev);
//phy_class = class_create(THIS_MODULE, "phy"); //对应目录 /sys/class/phy
phy->dev.class = phy_class;//对应目录 /sys/class/phy
phy->dev.parent = dev;
phy->dev.of_node = node ?: dev->of_node;
phy->id = id;
phy->ops = ops= struct phy_ops rockchip_usb2phy_ops
//对应 /sys/class/phy/phy-20008000.syscon:usb2-phy@17c.0 和 /sys/class/phy/phy-20008000.syscon:usb2-phy@17c.1
ret = dev_set_name(&phy->dev, "phy-%s.%d", dev_name(dev), id);
phy->pwr = regulator_get_optional(&phy->dev, "phy");
ret = device_add(&phy->dev);
return phy;
rport->phy = phy;
phy_set_drvdata(rport->phy, rport);
/* initialize otg/host port separately : 对 usb otg 和 usb host 分开处理 */
if (!of_node_cmp(child_np->name, "host-port"))
{
ret = rockchip_usb2phy_host_port_init(rphy, rport,child_np)->
struct rockchip_usb2phy_port *rport->port_id = USB2PHY_PORT_HOST = 1;
rport->low_power_en = of_property_read_bool(child_np, "rockchip,low-power-mode");
INIT_DELAYED_WORK(&rport->sm_work, rockchip_usb2phy_sm_work);
rport->ls_irq = of_irq_get_byname(child_np, "linestate"); //中断
ret = devm_request_threaded_irq(rphy->dev, rport->ls_irq, NULL,
rockchip_usb2phy_linestate_irq,
IRQF_ONESHOT,
"rockchip_usb2phy", rport);
/*
root@mxlos:~# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
187: 0 0 0 0 GIC 67 Level rockchip_usb2phy_bvalid
188: 0 0 0 0 GIC 84 Level rockchip_usb2phy
189: 0 0 0 0 GIC 83 Level rockchip_usb2phy_id
190: 1 0 0 0 GIC 85 Level rockchip_usb2phy
root@mxlos:~#
*/
ret = property_enable(base, &rport->port_cfg->phy_sus, true);
}
else
{
ret = rockchip_usb2phy_otg_port_init(rphy, rport,child_np)->
struct rockchip_usb2phy_port *rport->port_id = USB2PHY_PORT_OTG = 0;
rport->state = OTG_STATE_UNDEFINED;
rport->vbus = devm_regulator_get_optional(&rport->phy->dev, "vbus");
rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1);
wake_lock_init(&rport->wakelock, WAKE_LOCK_SUSPEND, "rockchip_otg");
INIT_DELAYED_WORK(&rport->bypass_uart_work, rockchip_usb_bypass_uart_work);
INIT_DELAYED_WORK(&rport->chg_work, rockchip_chg_detect_work);
INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work);
rport->bvalid_irq = of_irq_get_byname(child_np, "otg-bvalid");
ret = devm_request_threaded_irq(rphy->dev, rport->bvalid_irq,
NULL,
rockchip_usb2phy_bvalid_irq,
IRQF_ONESHOT,
"rockchip_usb2phy_bvalid",
rport);
rport->ls_irq = of_irq_get_byname(child_np, "linestate");
ret = devm_request_threaded_irq(rphy->dev, rport->ls_irq, NULL,
rockchip_usb2phy_linestate_irq,
IRQF_ONESHOT,
"rockchip_usb2phy", rport);
rport->id_irq = of_irq_get_byname(child_np, "otg-id");
ret = extcon_register_notifier(rphy->edev, EXTCON_USB_HOST, &rport->event_nb);
}
next_child:
/* to prevent out of boundary */
if (++index >= rphy->phy_cfg->num_ports)
break;
}
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate)->
__devm_of_phy_provider_register((dev), THIS_MODULE, (xlate))
struct phy_provider *phy_provider = __of_phy_provider_register(dev, owner, of_xlate)->
struct phy_provider *phy_provider = kzalloc(sizeof(*phy_provider), GFP_KERNEL);
phy_provider->dev = dev;
phy_provider->owner = owner;
phy_provider->of_xlate = of_xlate;
list_add_tail(&phy_provider->list, &phy_provider_list);
/*
USB 主机控制器使用函数 usb_create_hcd() 和 usb_add_hcd() 创建并注册,
在函数usb_add_hcd() 中会调用函数phy_get() 遍历链表phy_provider_list 获取 USB PHY驱动中注册的 struct phy *phy
err = usb_add_hcd(hcd, irq, IRQF_SHARED)->
struct phy *phy = phy_get(hcd->self.controller, "usb");
index = of_property_match_string(dev->of_node, "phy-names", string);
struct phy *phy = _of_phy_get(dev->of_node, index)-> //获取 usb phy 驱动中注册的 struct phy
ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", index, &args);
struct phy_provider *phy_provider = of_phy_provider_lookup(args.np)->
list_for_each_entry(phy_provider, &phy_provider_list, list)
*/
/* Attributes */
ret = sysfs_create_group(&dev->kobj, &usb2_phy_attr_group);
ret = rockchip_usb2phy_clk480m_register(rphy);
if (of_property_read_bool(np, "wakeup-source"))
device_init_wakeup(rphy->dev, true);
else
device_init_wakeup(rphy->dev, false);