U-Boot から Zynq の PLクロックとリセット信号を制御する

# はじめに

[「FPGA+SoC+Linuxのブートシーケンス(ZYNQ+U-Boot-SPL編)」](http://qiita.com/ikwzm/items/efacec8d8e0744e17d45)で、U-Boot-spl(U-Boot Second Program Loader)をステージ１ブートローダーとして Linux をブートする方法を説明しました。
その際、ちょっと問題になったのが PL 周りの設定です。

PL周りの設定をPLのデザイン時に設定した値を使う場合、PL側のデザインを変更する度に boot.bin(U-Boot-SPL) を再ビルドする必要があります(PL周りの設定が変わらない場合は除く)。

また、PL周りの設定を U-Boot がすでに定義済みのものをそのまま使う場合は、boot.bin(U-Boot-SPL)を再ビルドする必要はありませんが、PL周りの設定を変更することができません。

そこで、U-Boot の mw (memory write) コマンドを使って直接 Zynq のレジスタに書き込むことによって PL周りの設定を変える方法を説明します。

なお、Zynq のレジスタを直接操作するので、けっこう危険です。ハードウェア や PLL に詳しい人向けです。
レジスタ等の詳細は「Zynq-7000 Technical Reference Manual」を参照してください。これは Xilinx 社から UG585(User Guide 585)として公開されています。

# PL周りの設定レジスタ

Zynq で PL 周りの設定が必要なレジスタは、おそらく、クロックの設定とリセット出力制御だと思います。

これらを設定するレジスタは System Level Control Registers (slcr) のグループに属しています。このうち FPGA のクロックの設定とリセット出力の制御を行うレジスタは次の通りです。

| Register Name |  Address | Width | Type | Description                 |
|:-------------:|:---------|:-----:|:----:|:----------------------------|
|FPGA0_CLK_CTRL |0xF8000170|   32  |  rw  |PL Clock 0 Output control    |
|FPGA1_CLK_CTRL |0xF8000180|   32  |  rw  |PL Clock 1 Output control    |
|FPGA2_CLK_CTRL |0xF8000190|   32  |  rw  |PL Clock 2 Output control    |
|FPGA3_CLK_CTRL |0xF80001A0|   32  |  rw  |PL Clock 3 Output control    |
|FPGA_RST_CTRL  |0xF8000240|   32  |  rw  |FPGA Software Reset Control  |

## FPGA\[0-3\]_CLK_CTRL

 - Name        : FPGA\[0-3\]_CLK_CTRL
 - Address     : 0xF8000170, 0xF8000180, 0xF8000190, 0xF80001A0
 - Width       : 32
 - Access Type : rw(Read and Write)
 - Description : PL Clock \[0-3\] Output control

| Field Name | Bits|Type|Description|
|:-----------|:---:|:--:|:----------|
| DIVISOR1   |25:20| rw |Provides the divisor used to divide the source clock to generate the required generated clock frequency. Second cascade divide |
| DIVISOR0   |13:08| rw |Provides the divisor used to divide the source clock to generate the required generated clock frequency. First cascade divide  |
| SRCSEL     |05:04| rw | Select the source used to generate the clock:<br>0x: Source for generated clock is IO PLL.<br>10: Source for generated clock is ARM PLL.<br>11: Source for generated clock is DDR PLL. |

## FPGA_RST_CTRL

 - Name        : FPGA_RST_CTRL
 - Address     : 0xF8000240
 - Width       : 32
 - Access Type : rw(Read and Write)
 - Description : FPGA Software Reset Control
 
|  Field Name | Bits|Type|Description|
|:------------|:---:|:--:|:----------|
|FPGA3_OUT_RST|  3  | rw |PL Reset 3 (FCLKRESETN3 output signal). Refer to the PS7 wrapper in EDK for possible signal inversion. Logic level on the FCLKRESETN3 signal:<br>0: De-assert reset (High logic level).<br>1: Assert Reset (Low logic state) |
|FPGA2_OUT_RST|  2  | rw |PL Reset 2 (FCLKRESETN2 output signal). Refer to the PS7 wrapper in EDK for possible signal inversion. Logic level on the FCLKRESETN2 signal:<br>0: De-assert reset (High logic level).<br>1: Assert Reset (Low logic state) |
|FPGA1_OUT_RST|  1  | rw |PL Reset 1 (FCLKRESETN1 output signal). Refer to the PS7 wrapper in EDK for possible signal inversion. Logic level on the FCLKRESETN1 signal:<br>0: De-assert reset (High logic level).<br>1: Assert Reset (Low logic state) |
|FPGA0_OUT_RST|  0  | rw |PL Reset 0 (FCLKRESETN0 output signal). Refer to the PS7 wrapper in EDK for possible signal inversion. Logic level on the FCLKRESETN0 signal:<br>0: De-assert reset (High logic level).<br>1: Assert Reset (Low logic state) |

# どのような値を設定すれば良いか

## U-Boot が定義済みの値(ZYBOの場合)

最新の U-Boot を Zynq 用にビルドした場合、設定されているクロックの周波数を U-Boot の clk dump コマンドで見ることができます。

````
Zynq> clk dump
clk             frequency
    armpll          1300000000
    ddrpll          1050000000
     iopll          1000000000
 cpu_6or4x           650000000
 cpu_3or2x           325000000
    cpu_2x           216666666
    cpu_1x           108333333
    ddr_2x           350000000
    ddr_3x           525000000
       dci            10096154
     lqspi           200000000
       smc            21666667
      pcap           200000000
      gem0           125000000
      gem1            16666667
     fclk0           100000000
     fclk1           175000000
     fclk2            12264151
     fclk3           100000000
     sdio0            50000000
     sdio1            50000000
     uart0            50000000
     uart1            50000000
      spi0            15873016
      spi1            15873016
 usb0_aper           108333333
 usb1_aper           108333333
 gem0_aper           108333333
 gem1_aper           108333333
sdio0_aper           108333333
sdio1_aper           108333333
 spi0_aper           108333333
 spi1_aper           108333333
 can0_aper           108333333
 can1_aper           108333333
 i2c0_aper           108333333
 i2c1_aper           108333333
uart0_aper           108333333
uart1_aper           108333333
 gpio_aper           108333333
lqspi_aper           108333333
  smc_aper           108333333
   dbg_trc            66666667
   dbg_apb            66666667
````

これによるとクロックの周波数は次の様になっているようです。

|clk   | frequency |
|:----:|----------:|
|ARMPLL|   1300MHz |
|DDRPLL|   1050MHz |
|IOPLL |   1000MHz |
|FPGA0 |    100MHz |
|FPGA1 |    175MHz |
|FPGA2 |    123MHz |
|FPGA3 |    100MHz |

次に U-Boot がレジスタにどのような値を設定しているか見ると、次の様に設定しています。

| Register Name |  Address | U-Boot Def Value | Description                 |
|:-------------:|:---------|:-----------------|:----------------------------|
|FPGA0_CLK_CTRL |0xF8000170| 0x00100A00       |PL Clock 0 Output control    |
|FPGA1_CLK_CTRL |0xF8000180| 0x00100630       |PL Clock 1 Output control    |
|FPGA2_CLK_CTRL |0xF8000190| 0x00203520       |PL Clock 2 Output control    |
|FPGA3_CLK_CTRL |0xF80001A0| 0x00100A00       |PL Clock 3 Output control    |
|FPGA_RST_CTRL  |0xF8000240| 0x00000000       |FPGA Software Reset Control  |

FPGA0_CLK_CTRL では、ソースの PLL に IOPLL、DIVISOR0 に 10、DIVISOR1 に 1 を設定しています。
つまり、PL Clock 0 の周波数は (1GHz(=IOPLLの周波数)÷10(=DIVISOR0の値))÷(1(=DIVISOR1の値))で100MHzになります。


## 周波数に対応した設定値

例えば PL Clock 0 に 125MHz を設定したければ、FPGA0_CLK_CTRL に 0x00100800(SRCSEL=IOPLL、DIVISOR0=8、DIVISOR1=1)を書き込みます。

残念ながら、Zynq のこの PLL の仕様上、ソースの周波数の整数分の１の周波数しか設定できません。
例えば ZYBO の場合、PL CLock 0 に 133MHz を設定しようと思っても、無理です。一番近いのは、131.25MHz=((1050MHz(=DDRPLLの周波数)÷8)÷1)で、FPGA0_CLK_CTRL に 0x00100803(SRCSEL=DDRPLL、DIVISOR0=8、DIVISOR1=1)を書き込むことでしょう。


## その他のボードの場合

ここでは ZYBO の例をあげましたが、ARMPLL、DDRPLL、IOPLLの周波数はボードによって異なります。
参考までに、判る範囲で調べて見たところ次の様になりました。

| Board Name | Input Freq | ARMPLL Mult | ARMPLL Freq | DDRPLL Mult | DDRPLL Freq | IOPLL Mult  | IOPLL Freq  |
|:----------:|:-----------|:------------|:------------|:------------|:------------|:------------|:------------|
| ZC706      | 33.3333MHz | 40          | 1333.333MHz | 32          | 1066.667MHz | 30          | 1000MHz     |
| ZC702      | 33.3333MHz | 40          | 1333.333MHz | 32          | 1066.667MHz | 30          | 1000MHz     |
| ZedBoard   | 33.3333MHz | 40          | 1333.333MHz | 32          | 1066.667MHz | 30          | 1000MHz     |
| ZYBO       | 50MHz      | 26          | 1300.000MHz | 21          | 1050.000MHz | 20          | 1000MHz     |


# レジスタへの書き込み

素直に mw(memory write) コマンドにアドレスとデータを指定して書き込みたいところですが、残念ながら Zynq の場合はそういうわけにはいきません。というのも、Zynq の System Level Control Registers(slcr) は、万が一プログラムが暴走してもレジスタに変な値が書き込まれないようにライトプロテクションがかけられているからです。
ライトプロテクションを制御するレジスタは次の通りです。

| Register Name |  Address | Width | Type | Description                 |
|:-------------:|:---------|:-----:|:----:|:----------------------------|
|SLCR_LOCK      |0xF8000004|   32  |  wo  |SLCR Write Protection Lock   |
|SLCR_UNLOCK    |0xF8000008|   32  |  wo  |SLCR Write Protection UnLock |

## SLCR_LOCK

 - Name        : SLCR_LOCK
 - Address     : 0xF8000004
 - Width       : 32
 - Access Type : wo(Write Only)
 - Description : SLCR Write Protection Lock

| Field Name | Bits|Type|Description|
|:-----------|:---:|:--:|:----------|
| LOCK_KEY   | 15:0| wo | Write the lock key, 0x767B, to write protect the slcr registers: <br> all slcr registers, 0xF800_0000 to 0xF800_0B74, <br> are write protected until the unlock key is written to the SLCR_UNLOCK register. <br>A read of this register returns zero.|

## SLCR_UNLOCK

 - Name        : SLCR_UNLOCK
 - Address     : 0xF8000008
 - Width       : 32
 - Access Type : wo(Write Only)
 - Description : SLCR Write Protection Unlock

| Field Name | Bits|Type|Description|
|:-----------|:---:|:--:|:----------|
| UNLOCK_KEY | 15:0| wo | Write the unlock key, 0xDF0D, to enable writes to the slcr registers.<br> All slcr registers, 0xF800_0000 to 0xF800_0B74, <br> are writeable until locked using the SLCR_LOCK register. <br>A read of this register returns zero.|

## 書き込みの手順

つまり、FPGA0_CLK_CTRL や FPGA_RST_CTRL に値を書き込む前に SLCR_UNLOCK(0xF8000008) に 0xDF0D を書いてライトプロテクションを無効にする必要があります。またレジスタへの書き込みが終わったら SLCR_LOCK(0xF8000004) に 0x767B を書いてライトプロテクションを有効にします。

## uEnv.txt の例

U-Boot がブート時に実行するスクリプトを記述したファイルを用意します。ここでは uEnv.txt を使いますが、U-Boot には他にも専用のスクリプトファイルがあるので、そちらを使ってもかまいません。

````u-boot:uEnv.txt
slcr_unlock_cmd=mw.l 0xF8000008 0xDF0D
slcr_lock_cmd=mw.l 0xF8000004 0x767B
fpga_set_cmd=run slcr_unlock_cmd && mw.l 0xF8000170 0x00101400 && run slcr_lock_cmd
fpga_load_cmd=fatload mmc 0 0x03000000 design.bit && fpga loadb 0 0x03000000 $filesize
linux_load_cmd=fatload mmc 0 0x03000000 zImage && fatload mmc 0 0x02A00000 devicetree.dtb
linux_boot_cmd=bootz 0x03000000 - 0x02A00000
uenvcmd=run fpga_load_cmd && run fpga_set_cmd && run linux_load_cmd && run linux_boot_cmd
````

- slcr_unlock_cmd=mw.l 0xF8000008 0xDF0D      
  System Levec Control Registers のライトプロテクションを解除するコマンドを定義。
- slcr_lock_cmd=mw.l 0xF8000004 0x767B   
  System Levec Control Registers のライトプロテクションをセットするコマンドを定義。
- fpga_set_cmd=run slcr_unlock_cmd && mw.l 0xF8000170 0x00101400 && run slcr_lock_cmd   
  PL Clock をセットするコマンドを定義。   
  ここでは FPGA0_CLK_CTRL に 50MHz=(SRCSEL=IOPLL(1GHz),DIVISOR0=20,DIVISOR1=1)をセットしています。
- fpga_load_cmd=fatload mmc 0 0x03000000 design.bit && fpga loadb 0 0x03000000 $filesize   
  FPGAビットストリームファイル(ここではdesign.bit)を FPGA にロードするコマンドを定義。
- linux_load_cmd=fatload mmc 0 0x03000000 zImage && fatload mmc 0 0x02A00000 devicetree.dtb   
  Linux Kernelのイメージファイル(zImage)とDevice Tree Blob(devicetree.dtb)をメモリにロードするコマンドを定義。
- linux_boot_cmd=bootz 0x03000000 - 0x02A00000   
  メモリにロードした Linux Kernel と Device Tree Blob を使ってブートするコマンドを定義。
- uenvcmd=run fpga_load_cmd && run fpga_set_cmd && run linux_load_cmd && run linux_boot_cmd   
  FPGAのロード、FPGAの設定、Linux のロードとブートを行うコマンドを定義。   
  ブートの際、uEnv.txt に uenvcmd があると自動的にこのコマンドが実行されます。   
  なお、uenvcmd がある程度の文字数(256?)以上だとブートに失敗することがあります。


## 参考


* [FPGA+SoC+Linuxブートシーケンス(概要)](http://qiita.com/ikwzm/items/b4ccae34cf554c80d1b4)
* [FPGA+SoC+Linuxのブートシーケンス(ZYNQ+Vivado編)](http://qiita.com/ikwzm/items/1614c35233e1836c7a26)
* [FPGA+SoC+Linuxのブートシーケンス(ZYNQ+U-Boot-SPL編)](http://qiita.com/ikwzm/items/efacec8d8e0744e17d45)
* [Zynq-7000 Technical Reference Manual](http://www.xilinx.com/support/documentation/user_guides/ug585-Zynq-7000-TRM.pdf)