PYNQ-Z1+Linux で USB-HOST が動かない時の対処方法







# はじめに


[『FPGA+SoC+Linux+Device Tree Overlay+FPGA Manager(PYNQ-Z1対応)』](https://qiita.com/ikwzm/items/d6fdaa859906252fd8b0)で紹介した https://github.com/ikwzm/FPGA-SoC-Linux で、USB-HOST が動かないという報告がありました( https://github.com/ikwzm/FPGA-SoC-Linux/issues/3 )。この記事では、このトラブルに関しての対処方法を示します。




# トラブルが発生する環境


このトラブルが発生する環境は次の通りです。

  * 対象となるボードは PYNQ-Z1
  * OS に Linux Kernel のメインライン版を使用

OS に Xilinx が提供する Linux Kernel (Linux-xlnx) を使った場合、この問題はトラブルは発生しません。ただし、後述しますが、古い環境で作った boot.bin (fsbl または u-boot-spl)では、MIO46のピン設定が間違っていることがあり、同様のトラブルが発生することがあります。




# 対処方法


OS に関しては、Xilinx が提供する Linux Kernel (Linux-xlnx) を使うのが一番簡単でしょう。どうしてもメインラインのLinux Kernel を使いたい時は、USB デバイスドライバを修正する必要があります。

これでも未だ治らないようであれば、MIO46の設定が間違っていることがあるので、boot.bin(fsbl または u-boot-spl)を新しい環境で再構築する事で治るかもしれません。




# Linux-xlnx ではどうなっているか



## デバイスツリー


Xilinx が提供する Linux Kernel の PYNQ-Z1 のデバイスツリーでは、USB-PHY に関しては次のようになっています。


```devicetree:devicetree.dts
/dts-v1/;
	phy0 {
		compatible = "ulpi-phy";
		#phy-cells = <0x0>;
		reg = <0xe0002000 0x1000>;
		view-port = <0x170>;
		drv-vbus;
	};

```


ところが、Linux のメインラインには、"ulpi-phy" というデバイスドライバはありません。どうやら、これは Xilinx が独自に追加しているデバイスドライバのようです。




## ULPI-PHY デバイスドライバ


"ulpi-phy" のデバイスドライバのソースコードは、drivers/usb/phy/phy-ulpi.c です。ただ、この名前のファイルはメインラインにもあります。しかしメインラインでは、このファイルにはカーネル内部から呼ばれることを想定した関数が定義されているだけです。Linux-xlnx ではこのファイルに次のような記述を追加して、デバイスツリーから呼べるデバイスドライバとして使えるようにしていました。


```C:drivers/usb/phy/phy-ulpi.c
static int ulpi_phy_probe(struct platform_device *pdev)
{
	(中略)
}
static int ulpi_phy_remove(struct platform_device *pdev)
{
	(中略)
}
static const struct of_device_id ulpi_phy_table[] = {
	{ .compatible = "ulpi-phy" },
	{ },
};
MODULE_DEVICE_TABLE(of, ulpi_phy_table);
static struct platform_driver ulpi_phy_driver = {
	.probe		= ulpi_phy_probe,
	.remove		= ulpi_phy_remove,
	.driver		= {
		.name	= "ulpi-phy",
		.of_match_table = ulpi_phy_table,
	},
};
module_platform_driver(ulpi_phy_driver);
MODULE_DESCRIPTION("ULPI PHY driver");
MODULE_LICENSE("GPL v2");

```

このデバイスドライバを有効にするために、CONFIG_USB_ULPI=y、および CONFIG_USB_ULPI_VIEWPORT=y にしています。



## USBコントローラーのデバイスドライバ


USB コントローラーのデバイスドライバにいくつか変更が加えられています。具体的には次の通りです。変更点のみ抜粋しました。



* CI_HDRC_PHY_VBUS_CONTROLというフラグが include/linux/usb/chipidea.h に追加されています。


```C:include/linux/usb/chipidea.h
#define CI_HDRC_PHY_VBUS_CONTROL	BIT(12)
```




* zynq用のフラグの初期化変数に、CI_HDRC_PHY_VBUS_CONTROLが追加されています。


```C:drivers/usb/chipidea/ci_hdrc_usb2.c
static struct ci_hdrc_platform_data ci_zynq_pdata = {
	.capoffset	= DEF_CAPOFFSET,
	.flags 		= CI_HDRC_PHY_VBUS_CONTROL,
};

```




* drivers/usb/chipidea/host.c と drivers/usb/chipidea/otg_fsm.c にVBUS を制御する記述が追加されています。


```C:drivers/usb/chipidea/host.c
static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
{
	(中略)
		if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL &&
			ci->usb_phy && ci->usb_phy->set_vbus) {
		if (enable)
			ci->usb_phy->set_vbus(ci->usb_phy, 1);
		else
			ci->usb_phy->set_vbus(ci->usb_phy, 0);
	}
	(中略)
};
```



```C:drivers/usb/chipidea/otg_fsm.c
static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
{
	int ret;
	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm);
	if (on) {
	(中略)
		if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL &&
				ci->usb_phy && ci->usb_phy->set_vbus)
			ci->usb_phy->set_vbus(ci->usb_phy, 1);
	(中略)
	} else {
	(中略)
		if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL &&
				ci->usb_phy && ci->usb_phy->set_vbus)
			ci->usb_phy->set_vbus(ci->usb_phy, 0);
	(中略)
	}
}
```




どうやら、USB-PHY に対して VBUS を供給するように指示しているようです。



余談ですが、このような重要な修正はメインラインにも反映して欲しいものです。




# FPGA-SoC-Linux での対処


[『FPGA+SoC+Linux+Device Tree Overlay+FPGA Manager(PYNQ-Z1対応)』](https://qiita.com/ikwzm/items/d6fdaa859906252fd8b0)で紹介した https://github.com/ikwzm/FPGA-SoC-Linux では、v0.5.0 に Linux-xlnx と同様のパッチをあてています。このパッチにより、Linux Kernel 4.12.14 でも USB-HOST として使えるようになっています。




# MIO46(USB_RESET)ピンの設定


Linux Kernel でこれらの対処をしてもなお、USB-HOST が動作しないことがあります。その時は ZYNQ の MIO46 の設定を確認してみましょう。USB-PHY へのリセット信号は、PYNQ-Z1(他ZYBOなどでも)では MIO46 pinに繋がっています。MIO46の設定がうまくいっていないと、USB-PHY にリセットがかからないため、USB-PHY の認識に失敗してデバイスドライバが動きません。

実は、古い環境やボード設定ファイルを利用した場合、MIO46 の設定が出来ていないことがあります(経験済み)。Linux でUSB-HOST を使わない場合は発覚しないので、そのまま使用していることがあるので注意が必要です。




## Vivado で確認


Vivado の IP Integrator で ZYNQ のカスタマイズ UI から、MIO Configuration　> I/O Peripherals > GPIO > USB Reset > USB0 Reset を見て MIO 46 になっていれば OK です。

![Vivado でMIO46(USB_RESET)を確認](https://qiita-image-store.s3.amazonaws.com/0/24981/10477f40-c15c-6ceb-d1f2-8d5adb15f128.jpeg)



## ps7_init.html で確認


Vivado でハードウェア情報を出力した時、hdf ファイルが出来ます。hdf ファイルには ps7_init.html が含まれていて、このファイルはhdfファイルを開くか(hdf ファイル自体はzipでアーカイブされているだけなので拡張子を変えれば展開できます)、Vivado SDK で取り込むことによって見ることが出来ます。



そのとき、MIO46 の設定が次のように USB Reset になっていれば OK です。

![MIO46(USB_RESET)が設定されている時のps7_init.html](https://qiita-image-store.s3.amazonaws.com/0/24981/b80cadda-3b62-0917-1b42-85ea87b7a40b.jpeg)




次のように空白(何も設定されていない)場合は、USB-PHY にリセットがかからず失敗することがあります。

![MIO46(USB_RESET)が設定されていない時のps7_init.html](https://qiita-image-store.s3.amazonaws.com/0/24981/77a76cd0-127a-89fb-3841-0f3a6ee7264e.jpeg)





## u-boot で確認


u-boot をインタラクティブモードにして、コマンドを叩くことで確認することができます。u-boot でアドレス0xF80007B8 の内容を見ます。アドレス0xF80007B8 はZYNQ の MIO46 のピン設定用レジスタです。このレジスタの詳細は「Zynq-7000 Technical Reference Manual」を参照してください。これは Xilinx 社から UG585(User Guide 585)として公開されています。 

MIO46 が USB_RESET になっていた場合、次のように0x1200になっています。0x1201 になっていた場合、USB_RESET が設定されていない可能性があります。


```shell:u-boot
Zynq> md.l 0xF80007B8 1
f80007b8: 00001200                               ....
```





## Vivado で MIO46 を USB_RESET に設定する方法


PYNQボードの最新の設定用 tcl ファイルを入手して Vivado に取り込みます。FPGAの部屋の[「PYNQボード用PSの設定方法」](http://marsee101.blog19.fc2.com/blog-entry-3599.html)に詳しい方法が書かれているので参考にしてください。Vivado の IP Integrator で ZYNQ のカスタマイズ UI から、MIO Configuration > I/O Peripherals > GPIO > USB Reset > USB0 Reset が MIO46 になっていれば OK です。




## u-boot から USB-PHY のピン設定をしてリセットする


どうしても boot.bin(fsblまたはu-boot.spl)を再ビルド出来ない事情がある場合は、u-boot で USB-PHY をリセットします。ただし、u-boot のコマンドを叩くか、uEnv.txt 等でスクリプトを書く必要があります。




```shell:u-boot
Zynq> mw.l 0xF8000008 0xDF0D
Zynq> mw.l 0xF80007B8 0x1200
Zynq> md.l 0xF80007B8 1
f80007b8: 00001200                               ....
Zynq> gpio clear 46
gpio: pin 46 (gpio 46) value is 0
Zynq> gpio set 46
gpio: pin 46 (gpio 46) value is 1
Zynq> usb start
starting USB...
USB0:   USB EHCI 1.00
scanning bus 0 for devices... 1 USB Device(s) found
USB1:   usb1 wrong num MIO: 0, Index 1
lowlevel init failed
       scanning usb for storage devices... 0 Storage Device(s) found
Zynq> boot
```


* SLCR_UNLOCK(0xF8000008) に 0xDF0D を書いてライトプロテクションを無効にします
* SLCR_MIO_PIN_46(0xF80007B8) に 0x1200 を書いて MIO46 を出力モードにします
* SLCR_MIO_PIN_46(0xF80007B8) に 0x1200 が設定されていることを確認します
* gpio clear 46 コマンドで、MIO46 を Low にして USB-PHY をリセットします
* gpio set 46 コマンドで、MIO46 を High にしてリセットを解除します
* usb start コマンドを実行して、USB-PHY が動作していることを確認します




# 参考


* [https://github.com/ikwzm/FPGA-SoC-Linux] (https://github.com/ikwzm/FPGA-SoC-Linux)
* [「PYNQのUSB HOSTが死んでいる」(https://github.com/ikwzm/FPGA-SoC-Linux/issues/3)](https://github.com/ikwzm/FPGA-SoC-Linux/issues/3)
* [「FPGA+SoC+Linux+Device Tree Overlay+FPGA Manager(PYNQ-Z1対応)」](https://qiita.com/ikwzm/items/d6fdaa859906252fd8b0)
* [「FPGAの部屋  PYNQボード用PSの設定方法」](http://marsee101.blog19.fc2.com/blog-entry-3599.html)
* https://reference.digilentinc.com/reference/programmable-logic/pynq-z1/start
* [Zynq-7000 Technical Reference Manual](http://www.xilinx.com/support/documentation/user_guides/ug585-Zynq-7000-TRM.pdf)