参考文章:【正点原子】I.MX6U嵌入式Linux驱动开发——Linux USB驱动
由于 USB 协议太过庞大和复杂,所以本节只对 STM32MP157 自带的 USB 驱动进行使能和测试。详细的 USB 接口和协议的介绍,可以参考原子哥的资料《USB2.0 协议中文版.pdf》和《USB3.0 协议中文版.pdf》。
USB 全称为 Universal Serial Bus,翻译过来就是通用串行总线,用于规范电脑与外部设备的连接与通讯。接口部分,最新的智能手机均采用 USB Typec 取代了传统的 3.5mm 耳机接口,苹果最新的 MacBook 只有 USB Typec 接口,至于其他的 HDMI、网口等均可以通过 USB Typec 扩展坞来扩展。协议部分,USB 目前可以划分为 USB1.0、USB2.0、USB3.0 以及正在即将到来的 USB4.0。
USB A 插头一般有四个触点,也就是 4 根线,从左到右线序依次为 1,2,3,4,第 1 根线为 VBUS,电压为 5V,第 2 根线为 D-,第 3 根线为 D+,第 4 根线为 GND。USB 采用差分信号来传输数据,因此有 D- 和 D+两根差分信号线。
Mini USB 插头一般有五个触点,第 1 根线为 VCC(5V),第 2 根线为 D-,第 3 根线为 D+,第 4 根线为 ID,第 5 根线为 GND。这个 ID 线用于实现 OTG 功能,通过 ID 线来判断当前连接的是主设备(HOST)还是从设备(SLAVE)。
USB 分为 HOST(主机)和从机(或 DEVICE),有些设备可能有时候需要做 HOST,有时候又需要做 DEVICE,USB OTG 应运而生,OTG 是 On-The-Go 的缩写,支持 USB OTG 功能的 USB 接口既可以做 HOST,也可以做 DEVICE。
ID=1:OTG 设备工作在从机模式。
ID=0:OTG 设备工作在主机模式。
支持 OTG 模式的 USB 接口一般都是 Mini USB、 Micro USB 等这些带有 ID 线的接口。正点原子的 STM32MP1 开发板 OTG 模式是使用 USB Type-C 接口,没有 ID 线。 USB Type C 有自己的识别方法。
STM32MP157 提供了两个 USB2.0 接口,这两个 USB 接口都支持高速模式,也就是480Mbit / 秒,都内置了高速 PHY。其中 USB2 支持 OTG 功能, 正点原子 STM32MP157 开发板上的 USB OTG 接口就是连接到 USB2 接口上的。USB1 接口连接了一个 HUB 芯片,可以实现 USB Host 接口扩展。
根据 USB 协议,USB TypeC 接口也叫做 USB3.1 接口。由于 TypeC 功能比较复杂,比如支持 PD 充电、显示、音频等,这里只关注 TypeC 的数据通信部分。母口的引脚定义如下:
标准的Type-C接口是有24个引脚的,也有简化的16P引脚,去掉了 8 根 USB3.1 高速差分收发数据线。也有6P封装的接口,与Micro USB的引脚相同,只是换了个接口。
正点原子的 STM32MP1 开发板 USB 部分原理图可以分为两部分:USB HUB 以及 USB OTG。由于 STM32MP157 芯片只提供了 USB2.0 的接口,所以虽然是 Type-C 接口,本质上还是 USB2.0 协议。另外,使用 TypeC 接口实现 OTG 功能需要外接 TypeC 芯片,通过专用的 TypeC 芯片来控制 CC 引脚实现 USB 的主从切换。
所以具体的外接芯片需要根据实际情况选用。
USB HUB原理图:
使用 FE2.1 这个 HUB 芯片将 STM32MP1 的 USB2 扩展成了 7 路 HOST 接口,其中一路供 4G 模块使用(HUB_DP7/DM7)。
注:使用 FE2.1 扩展出来的 7 路 USB 接口只能用作 HOST
USB OTG 原理图:
V1.5版本使用 FUSB302MPX 做控制芯片,在 V1.4 及之前的版本里使用的是 STUSB1600 控制芯片。此控制器主要的功能就是负责切换主机和从机模式。
I2C1_SCL 和 I2C1_SDA 的引脚分别为:PF14 和 PF15。USB 属于热插拔设备。USB_INT 这个中断引脚主要是检测有没有设备接入,有设备接入电平就会被拉低,驱动进入中断函数,使用 I2C 去读取寄存器配置开发板的主从模式,USB_INT 的引脚为 PG2。
FUSB302PMX 版本中,还有一个MT9700HT5,是负载开关,用来控制 VBUS 输出。当 OTG_PWR_CTRL 输出高电平时 OUT 引脚就输出 5V 电压,也就是 VBUS 变为 5V。当 OTG_PWR_CTRL 输出低电平的时候 OUT 输出 0V,相当于 VBUS 关闭。
所以当开发板上的 TypeC 接口作为主设备时,OTG_PWR_CTRL 要输出高电平,VBUS 输出 5V,为外部 USB 设备供电。当 TypeC 接口作为从设备时,OTG_PWR_CTRL 输出低电平。OTG_PWR_CTRL 对应的 GPIO 引脚为 PZ6。
USB 子系统是一个标准和复杂的接口,不过好在 Linux 内核已经提供了相关的驱动,只需要在设备树中提供相应的节点即可。
ST 官方的 STM3MP157C-DK2 开发板已经配置好了 USBH 的节点信息,可以参考此节点进行修改。
在 stm32mp151.dtsi 文件中,可以找到“usbh_ohci”和“usbh_ehci”节点:
从代码中可以知道 USBH 是支持 USB2.0 和 USB1.1。使用 USB2.0 就要配置 usbh_ehci 节点,使用 USB1.1 就要配置 usbh_ohci 节点。根据 compitable 属性值,可以查找到驱动文件为 drivers/usb/host/ohci-platform.c 和 drivers/usb/host/ehciplatform.c。
同样在 stm32mp151.dtsi 文件中,可以找到 phy 相关设备节点:
usbphyc 节点就是 STM32MP1 的 USB PHY,PHY 控制器有两个端口,对应两个子节点分别为:usbphyc_port0 和 usbphyc_port1,其中 usbphyc_port0 只能分配给 USB Host。“#phy-cells”属性值为 0 表示做 OTG 的 PHY 端口,1 表示做 USBH 的 PHY 端口。
将 “stm32mp15xx-dkx.dtsi” 中的 usb_phy_tuning 节点拷贝到自己的设备树中,usb_phy_tuning 此节点负责调整 PHY 的配置,相关的参考文档为 Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.yaml。
usb_phy_tuning: usb-phy-tuning {
st,hs-dc-level = <2>;
st,fs-rftime-tuning;
st,hs-rftime-reduction;
st,hs-current-trim = <15>;
st,hs-impedance-trim = <1>;
st,squelch-level = <3>;
st,hs-rx-offset = <2>;
st,no-lsfs-sc;
};
接着在自己的设备树文件中继续追加内容,用来设置 usbphyc_port0 节点。
&usbphyc {
status = "okay";
};
&usbphyc_port0 {
phy-supply = <&v3v3>;
st,phy-tuning = <&usb_phy_tuning>;
};
①将 usbphyc 的 status 属性修改为“okay”,使能 usbphyc
②给 usbphyc_port0 节点追加 phy-supply 属性,添加一个电源管理属性
③将要修改的 PHY 配置,添加到 usbphyc_port0 节点里
在自己的设备树中,使能 usbh_ehci 和指定 PHY 端口:
&usbh_ehci {
phys = <&usbphyc_port0>;
status = "okay";
};
修改完成后编译出新的设备树,用于启动开发板。终端会输出以下信息:
在 Linux 内核的 menuconfig 中选中以下选项,使能 HID 驱动:
然后使能 USB 键盘和鼠标驱动:
此选项对应配置项就是 CONFIG_USB_HID,也就是 USB 接口的 HID 设备。但是要注意,此驱动和 HIDBP(Boot Protocol)键盘、鼠标的驱动不能一起使用。
然后编译出新的内核镜像,启动开发板。启动以后插入 USB 鼠标,会出现以下信息:
并且会在/dev/input 目录下生成一个名为 eventX(X=0,1,2,3…)的文件,这个就是前面讲的输入子系统,鼠标和键盘都是作为输入子系统设备。
如果要测试 USB 键盘,可以将 LCD 作为默认终端控制台,在开发板的 /etc/inittab 中添加以下语句:
tty1::askfirst:-/bin/sh
然后就可以使用开发板屏幕作为终端控制,使用键盘进行输入。
U 盘使用 SCSI 协议,所以要先使能 Linux 内核中的 SCSI 协议:
然后使能 USB Mass Storage,也就是 USB 接口的大容量存储设备:
然后就可以编译出内核镜像,启动开发板。注:U盘需要格式化为 Fat32 格式,NTFS 和 exFAT 由于版权问题所以在 Linux 下支持的不完善,可能会出现问题。
例如我的U盘格式化为 NTFS 格式就无法写入。
成功挂载后,/dev 目录下会出现 sda 和 sda1 两个文件。/dev/sda 是整个 U 盘,/dev/sda1 是 U 盘的第一个分区,一般使用 U 盘的时候都是只有一个分区。要想访问 U 盘需要先对 U 盘进行挂载:
mkdir /mnt/usb_disk -p
mount /dev/sda1 /mnt/usb_disk/ -t vfat -o iocharset=utf8
-t 指定挂载所使用的文件系统类型,这里设置为 vfat,也就是 FAT 文件系统,“-o iocharset”设置硬盘编码格式为 utf8,否则 U 盘里的中文会显示乱码。然后就可以对U盘中的数据进行读写操作了。
卸载U盘:
sync #同步
cd / #退出U盘目录,否则可能会出现设备忙状态,卸载失败
umount /mnt/usb_disk #卸载
在 arch/arm/boot/dts/stm32mp151.dtsi 设备树文件中,有一个 usbotg_hs 节点,此节点就是 USB OTG 控制器节点:
根据 compatible 属性可以找到驱动文件为 drivers/usb/dwc2/params.c。
首先在自己的设备树中,配置 PHY 接口,追加 usbphyc_port1 节点:
注:要先使能 usbphyc 节点
然后追加 usbotg_hs 的相关属性信息:
&usbotg_hs {
phys = <&usbphyc_port1 0>;
phy-names = "usb2-phy";
usb-role-switch;
status = "okay";
port {
usbotg_hs_ep: endpoint {
remote-endpoint = <&con_usbotg_hs_ep>;
};
};
};
①配置 usbotg_hs 的 PHY 接口,这里 0 表示为 OTG USB 的 PHY 端口
②把 status 属性改为 okay,使能 usbotg_hs
③添加了一个 port 节点,指定 usbotg_hs 节点使用 con_usbotg_hs_ep 做控制器, con_usbotg_hs_ep 会在 USB 控制芯片对应的节点里创建
电源节点:
vdd_usb: regulator-vdd-usb {
compatible = "regulator-fixed";
regulator-name = "vdd_usb";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
regulator-boot-on;
};
控制 Typec 的芯片是 FUSB302MPX (或 STUSB1600),此芯片是使用 I2C 协议和 CPU 进行通讯,所以要使能 I2C1:
&i2c1 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c1_pins_b>;
pinctrl-1 = <&i2c1_pins_sleep_b>;
status = "okay";
};
配置 FUSB302 的中断电气属性,在 stm32mp15-pinctrl.dtsi 中添加以下内容:
fusb302_pins_a: fusb302-0 {
pins {
pinmux = <STM32_PINMUX('G', 2, ANALOG)>;
bias-pull-up;
};
};
然后在 stm32mp167d-atk.dts 文件中添加头文件“dt-bindings/usb/pd.h”。
接着配置 FUSB302 节点,此节点为 i2c1 节点的子节点:
fusb302@22 {
compatible = "fcs,fusb302","fairchild,fusb302";
reg = <0x22>;
pinctrl-names = "default";
pinctrl-0 = <&fusb302_pins_a>;
int-n-gpios = <&gpiog 2 GPIO_ACTIVE_HIGH>;
vbus-5v-gpios = <&gpioz 6 GPIO_ACTIVE_HIGH>;
status = "okay";
connector {
compatible = "usb-c-connector";
label = "USB-C";
power-role = "dual";
power-opmode = "default";
try-power-role = "sink";
source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)
PDO_VAR(3000, 12000, 3000)
PDO_PPS_APDO(3000, 11000, 3000)>;
op-sink-microwatt = <10000000>;
port {
con_usbotg_hs_ep: endpoint {
remote-endpoint = <&usbotg_hs_ep>;
};
};
};
};
①reg 属性值为“0x22”,表示该芯片的 i2c 通讯地址为0x22
②定义一个 con_usbotg_hs_ep 端口,并指定此端口连接到 usbotg_hs 节点的 usbotg_hs_ep 端口
fusb302 的设备树没有使用电源管理,而是用 PZ6 引脚去控制电压。此外,Linux 内核提供的 fusb302 驱动不读取 connector 相关的属性,所以不能使用(旧版本代码,不知道新的有没有优化)。在 github 上有可用的驱动,或者使用正点原子提供的 fusb302.c 和 fusb302.h,覆盖掉 drivers/usb/typec/tcpm 目录下的原驱动文件。然后再使能内核的menuconfig中的相关配置。
然后编译出新的内核镜像和设备树,启动开发板。
首先配置Linux内核,开发板当做一个 U 盘。(选择编译成模块,手动挂载)
然后使用以下命令,编译内核、模块:
make uImage LOADADDR=0XC2000040 -j32
make modules -j32
编译完成之后会得到 3 个.ko 内核模块文件:
drivers/usb/gadget/libcomposite.ko
drivers/usb/gadget/function/usb_f_mass_storage.ko
drivers/usb/gadget/legacy/g_mass_storage.ko
将三个文件拷贝到开发板 /lib/modules/5.4.31 目录,拷贝完成以后使用新编译出来的 uImage 启动开发板,在开发板上插入一个 U 盘。记下U盘挂载后的设备文件名。
使用 TypeC 数据线将开发板 OTG 口和电脑连接,连接好以后依次加载 libcomposite.ko、usb_f_mass_storage.ko 和 g_mass_storage.ko 这三个驱动文件,顺序不能错。
命令如下:
depmod
modprobe libcomposite.ko
modprobe usb_f_mass_storage.ko
modprobe g_mass_storage.ko file=/dev/sda1(设备文件描述符) removable=1
然后就可以在电脑上对U盘进行读写操作。
操作完成后使用以下命令退出:
rmmod g_mass_storage.ko
rmmod usb_f_mass_storage.ko
rmmod libcomposite.ko
STM32MP1 开发板板载了音频解码芯片,因此可以将 STM32MP1 开发板作为一个外置 USB 声卡,配置 Linux 内核:
编译后会得到以下三个模块:
drivers/usb/gadget/libcomposite.ko
drivers/usb/gadget/function/usb_f_uac1_legacy.ko
drivers/usb/gadget/legacy/g_audio.ko
然后启动开发板,按照之前的教程配置声卡,保证声卡正常播音。然后连接 OTG 口和电脑。最后依次加载 libcomposite.ko、usb_f_uac1_legacy.ko 和 g_audio.ko 这三个驱动模块:
depmod
modprobe libcomposite.ko
modprobe usb_f_uac1_legacy.ko
modprobe g_audio.ko
加载完成以后稍等一会虚拟出一个 USB 声卡,打开电脑的设备管理器,选择“声音、视频和游戏控制器”,会发现有一个名为“AC Interface”设备:
将该声卡作为电脑的扬声器,就可以使用开发板放音了:
做主机的话测试方法和上面的 USB HOST 测试一模一样,直接在正点原子的 USB_OTG 接口上连接一个拓展坞,然后接入鼠标键盘、U 盘等设备。