KV260 で USB デバイスが認識されない場合に考えられる原因と対策








# はじめに


Kria KV 260 Vision AI Starter Kit (以下 KV260) には USB TYPE-A コネクタが４つ備わっていて、USB メモリやキーボード、マウス、Webカメラ、USB-WiFi などの USB デバイスを接続して使うことが出来るようになっています。

Xilinx が提供している Petalinux 等では普通に使えるようになっていますが、なんらかの事情で Linux を再構築した際に USB デバイスが認識されなくことがあります。

この記事では、USB デバイスが認識されなくなった場合に考えられる理由の一つとその対策について説明します。

結果から先に簡単に述べると、USB HUB のデバイスドライバが適切に導入されていなくて USB HUB が機能していないため、USB デバイスが認識されないのが原因の場合があります。


# KV260 の USB 周りの構造 


次図に KV260 の USB 周りのブロック図を簡単に示します。


![Fig.1 KV260 の USB 周りのブロック図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/eab053a2-4e49-c9a5-3932-99fe5d1962c4.jpeg "Fig.1 KV260 の USB 周りのブロック図")

Fig.1 KV260 の USB 周りのブロック図

<br />

U43: USB5744 が USB HUB です。ZynqMP の USB0: USB Host Device Controller を上流ポートに接続しています。(その際 USB2.0 は U37:USB3220 という USB トランシーバー を介して、またUSB3 は ZynqMP の PHY を介しています。) 

USB5744 の各種設定は I2C で行います。I2C は ZynqMP の I2C1:I2C Controller に接続されています。ただし、この手の USB Hub は、特に I2C による設定を行わなくてもデフォルト状態で正常に動作するように設計されています。

USB5744 のリセット信号は U19:リセット制御LSI が生成しています。USB_HUB/USB_PHY のリセットは、電源が安定してから(PL_PGOOD が High になってから) 25ミリ秒後にリセット解除(リセット信号をHigh)可能になります。 そして、ここが肝心なところですが、KV260 ではリセット信号の制御は ZynqMP の MIO44 で行います。  __MIO44 に High を出力しないと、USB5744 のリセットは解除されません。__  


# Linux Kernel の USB5744 制御



## デバイスドライバのソースコード


KV260 では USB5744 のリセットを制御するには、MIO44 を制御しなければなりません。そこで Xilinx 社が提供している linux-xlnx では、USB5744 を制御するためのデバイスドライバを提供しています。
* https://github.com/Xilinx/linux-xlnx/blob/xilinx-v2021.2/drivers/usb/misc/usb5744.c



linux-xlnx 以外のLinux Kernel でUSB5744 デバイスドライバを使う場合は、このソースコードをdrivers/usb/misc に追加し、drivers/usb/misc/Kconfig とdrivers/usb/misc/Makefile に USB5744 の記述を追加しておきます。


```Kconfig:drivers/usb/misc/Kconfig
# SPDX-License-Identifier: GPL-2.0
#
# USB Miscellaneous driver configuration
#
comment "USB Miscellaneous drivers"
	:
	(中略)
	:
config USB_USB5744
	tristate "Microchip USB5744 Hub driver"
	depends on I2C
	depends on GPIOLIB
	help
	  This option enables support for Microchip USB5744 Hub. This driver
	  reset the hub using gpio pin and configure hub via i2c.
	:
	(後略)
```



```Makefile:drivers/usb/misc/Makefile
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the rest of the USB drivers
# (the ones that don't fit into any other categories)
#
	:
	(中略)
	:
obj-$(CONFIG_USB_USB5744)		+= usb5744.o
	:
	(後略)
```



## デバイスドライバの組み込み


defconfig や menuconfig でCONFIG_USB5744=y を指定しておく必要があります。  __理由は後述しますが、CONFIG_USB5744=m ではデバイスドライバが自動的にロードされず、リセット解除されないため、USB デバイスが認識されません。__  


## デバイスツリー


USB5744 デバイスドライバを Linux Kernel に組み込むためにはデバイスツリーにノードを追加する必要があります。次のように I2C1 が管理する I2C バスのところにノードを追加します。

compatible プロパティには "microchip,usb5744"、reg プロパティには I2C バス上のアドレス 0x2d、reset-gpio プロパティには gpio の 44番(MIO44) を指定します。


```devicetree:zynqmp-kv260-revB.dts
&i2c1 { /* I2C_SCK C23/C24 - MIO from SOM */
	#address-cells = <1>;
	#size-cells = <0>;
	pinctrl-names = "default", "gpio";
	pinctrl-0 = <&pinctrl_i2c1_default>;
	pinctrl-1 = <&pinctrl_i2c1_gpio>;
	scl-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>;
	sda-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>;
	usbhub: usb5744@2d { /* u43 */
		compatible = "microchip,usb5744";
		reg = <0x2d>;
		reset-gpios = <&gpio 44 GPIO_ACTIVE_HIGH>;
	};
};
```



# 余談１ CONFIG_USB5744=m ではダメな理由


CONFIG_USB5744=m を指定すると、USB5744 デバイスドライバは Linux Kernel 内部(built-in) に組み込まれるのではなく、カーネルモジュールとしてビルドされます。本来であれば、組み込みだろうとカーネルモジュールだろうとちゃんと動作するはずですが、USB5744 のデバイスドライバの問題でカーネルモジュールにすると、デバイスドライバがロードされません。

デバイスツリー上は USB5744 のノードは I2C バスの配下に位置しています。I2C バスに接続されるデバイスドライバでも compatible プロパティを記述していればデバイスドライバがロードされる場合もあります。例えば同じUSB HUB のドライバである usb3503.c では次のようになっています。


```C:drivers/usb/misc/usb3503.c
	:
	(前略)
	:
#ifdef CONFIG_OF
static const struct of_device_id usb3503_of_match[] = {
	{ .compatible = "smsc,usb3503", },
	{ .compatible = "smsc,usb3503a", },
	{},
};
MODULE_DEVICE_TABLE(of, usb3503_of_match);
#endif
static struct i2c_driver usb3503_i2c_driver = {
	.driver = {
		.name = USB3503_I2C_NAME,
		.pm = pm_ptr(&usb3503_i2c_pm_ops),
		.of_match_table = of_match_ptr(usb3503_of_match),
	},
	.probe		= usb3503_i2c_probe,
	.remove		= usb3503_i2c_remove,
	.id_table	= usb3503_id,
};
	:
	(後略)
```


usb3503_i2c_driver (struct i2c_driver) の .driver の .of_match_table に注視してください。デバイスツリーのcompatible プロパティに対応した ID テーブルを指定しています。こうすることによって I2C バス配下のノードでもcompatible プロパティに適切な名前を指定していれば、Linux のブート時にカーネルモジュールライブラリから対応するデバイスドライバを見つけて自動的にロードします。



ところが USB5744 のデバイスドライバ usb5744.c では次のようになっています。 


```C:drivers/usb/misc/usb5744.c
	:
	(前略)
	:
static const struct i2c_device_id usb5744_id[] = {
	{ "usb5744", 0 },
	{}
};
MODULE_DEVICE_TABLE(i2c, usb5744_id);
static struct i2c_driver usb5744_i2c_driver = {
	.driver = {
		.name = "usb5744",
	},
	.probe = usb5744_i2c_probe,
	.id_table = usb5744_id,
};
static const struct of_device_id usb5744_platform_id[] = {
	{ .compatible = "microchip,usb5744", },
	{ }
};
static struct platform_driver usb5744_platform_driver = {
	.driver = {
		.name = "microchip,usb5744",
		.of_match_table	= usb5744_platform_id,
	},
	.probe = usb5744_platform_probe,
};
	:
	(後略)
```


ご覧の通り、usb5744_i2c_driver (struct i2c_driver) の .driver に .of_match_table が指定されていません。そのため、デバイスツリーで I2C バス配下のノードに compatible プロパティで "microchip,usb5744" を指定しても、Linux Kernel はデバイスドライバをロードしません。



仮に次のように usb5744.c の usb5744_i2c_driver (struct i2c_driver) の .driver に .of_match_tableをちゃんと設定すると、CONFIG_USB5744=m にしてもブート時にデバイスドライバがロードされてリセットシーケンスが起動して USB5744 が正常に動作するようになります。


```C:drivers/usb/misc/usb5744.c
	:
	(前略)
	:
static const struct i2c_device_id usb5744_id[] = {
	{ "usb5744", 0 },
	{}
};
MODULE_DEVICE_TABLE(i2c, usb5744_id);
static const struct of_device_id usb5744_platform_id[] = {
	{ .compatible = "microchip,usb5744", },
	{ }
};
MODULE_DEVICE_TABLE(of, usb5744_platform_id);
static struct i2c_driver usb5744_i2c_driver = {
	.driver = {
		.name = "usb5744",
		.of_match_table = of_match_ptr(usb5744_platform_id),
	},
	.probe = usb5744_i2c_probe,
	.id_table = usb5744_id,
};
static struct platform_driver usb5744_platform_driver = {
	.driver = {
		.name = "microchip,usb5744",
		.of_match_table	= usb5744_platform_id,
	},
	.probe = usb5744_platform_probe,
};
	:
	(後略)
```


なお、usb5744.c を修正しなくても、CONFIG_USB5744=m でもデバイスツリーにちゃんとノードを追加していれば、Linux Kernel 起動後にコンソールから  insmod コマンドによって usb5744 を手動でロードすることで USB HUB がリセット解除されて USB デバイスが認識されるようになります。




# 余談2 Ultra96-V2 の場合


実は Ultra96-V2 でも USB HUB として USB5744 が使われています。しかし USB5744 のリセット信号はボード上で作られていて ZynqMP から制御するようには設計されていません。そのため、あらためてデバイスドライバを用意してなくても、自動的に USB HUB のリセットが解除されて USB デバイスが認識できるようになっています。




# 参考

* [「Kria KV260 ビジョン AI スターターキット ユーザーガイド UG1089 (v1.1) 2022年3月16日」](https://japan.xilinx.com/content/dam/xilinx/support/documents/user_guides/som/1_1/j_ug1089-kv260-starter-kit.pdf)
* [Zynq UltraScale+ MPSoC Technical Reference Manual, UG1085(v1.7) December 22,2017](https://www.xilinx.com/support/documentation/user_guides/ug1085-zynq-ultrascale-trm.pdf)
* [「UltraZed/Ultra96/Ultra96-V2/KV260 向け Debian GNU/Linux (v2021.1版) ブートイメージの提供」@Qiita](https://qiita.com/ikwzm/items/a9adc5a7329b2eb36895)