fclkcfg は FPGA Clock Configuration Device Driver で、ユーザー空間から Zynq/Zynq UltraScale+ の PL(Plogrammable Logic) のクロックの周波数を変更および出力を制御するデバイスドライバです。
fclkcfg はLinux Kernel が本来持っている clk ドライバを、ユーザー空間から制御できるようにします。
Fig.1 fclkcfg の位置づけ
- OS: Linux Kernel Version 4.4.4 以降
- CPU: ARM(Zynq-7000), ARM64(Zynq UltraScale+)
現在(2016年4月8日)、Altera-SoC で動作確認中ですがまだ動いていません。
Makefile を用意しています。環境にあわせて適当に修正してください。
insmod で fclkcfg のカーネルドライバをロードします。その際 Device Tree の設定に従いデバイスドライバができます。Device Tree に関しては後述します。
zynq# insmod fclkcfg.ko
[ 102.044387] fclkcfg amba:fclk0: driver installed.
[ 102.049016] fclkcfg amba:fclk0: device name : fclk0
[ 102.053949] fclkcfg amba:fclk0: clock name : fclk0
[ 102.058748] fclkcfg amba:fclk0: clock rate : 100000000
[ 102.058748] fclkcfg amba:fclk0: clock enable : 1
アンインストールするには rmmod を使います。
zynq# rmmod fclkcfg
[ 261.514039] fclkcfg amba:fclk0: driver unloaded
詳しくは以下の URL を参照してください。
fclkcfg はデバイスツリーでクロックの設定をします。具体的には次のようなデバイスツリーを用意します。
fclk0 {
compatible = "ikwzm,fclkcfg";
device-name = "fpga-clk0";
clocks = <&clkc 15>, <&clkc 2>;
insert-rate = "100000000";
insert-enable = <1>;
remove-rate = "1000000";
remove-enable = <0>;
};
以下にデバイスツリーのプロパティの説明をします。
compatible プロパティはカーネルモジュールの中から対応するデバイスドライバを探すためのキーワードを示します。fclkcfg では "ikwzm,fclkcfg-0.10.a" または "ikwzm,fclkcfg" を指定します。compatible プロパティは必須です。(v1.6.0より前は "ikwzm、fclkcfg-0.10.a" のみ使えました。 v1.6.0以降では "ikwzm、fclkcfg" も使えます。)
device-name プロパティはデバイス名を文字列で指定します。device-name プロパティはオプションです。device-name プロパティが省略された場合は、devicetree のノード名(例では fclk0)がデバイス名になります。
clocks プロパティの第一引数で制御する PL のクロックを指定します。
clocks プロパティの第二引数以降で PL の リソースのクロックを指定します。
clocks プロパティは必須です。ただし第一引数は必須ですが第二引数以降はオプションです。
clocks プロパティで指定するクロックは、<クロックのハンドル クロックのインデックス> で指定します。例えば Zynq の場合、次のようにデバイスツリーでクロックが指定されています。
/dts-v1/;
:
(中略)
:
slcr: slcr@f8000000 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "xlnx,zynq-slcr", "syscon", "simple-bus";
reg = <0xF8000000 0x1000>;
ranges;
clkc: clkc@100 {
#clock-cells = <1>;
compatible = "xlnx,ps7-clkc";
fclk-enable = <0>;
clock-output-names = "armpll", "ddrpll", "iopll", "cpu_6or4x",
"cpu_3or2x", "cpu_2x", "cpu_1x", "ddr2x", "ddr3x",
"dci", "lqspi", "smc", "pcap", "gem0", "gem1",
"fclk0", "fclk1", "fclk2", "fclk3", "can0", "can1",
"sdio0", "sdio1", "uart0", "uart1", "spi0", "spi1",
"dma", "usb0_aper", "usb1_aper", "gem0_aper",
"gem1_aper", "sdio0_aper", "sdio1_aper",
"spi0_aper", "spi1_aper", "can0_aper", "can1_aper",
"i2c0_aper", "i2c1_aper", "uart0_aper", "uart1_aper",
"gpio_aper", "lqspi_aper", "smc_aper", "swdt",
"dbg_trc", "dbg_apb";
reg = <0x100 0x100>;
};
:
(中略)
:
このデバイスツリーではクロックの設定は slcr(System Level Control Register) の clkc で行うことを示しています。
clocks = <&clkc 15>; と記述することにより、 clkc が管理しているクロックの15番目のクロック(これが PL Clock 0を指す)を制御することを指定します。
clocks の第二引数以降で PL の リソースのクロックを指定しています。PL のクロックは、リソースのクロックを分周することにより必要な周波数のクロックを出力しています。clocks の第二引数以降で、PL のリソースのクロックを "armpll"、"ddrpll"、 "iopll" の何れかから選択することができます。"armpll" は <&clkc 0>、"ddrpll" は <&clkc 1>、"iopll" は <&clkc 2> です。
clocks = <&clkc 16>, <&clkc 2>; と記述することにより、clkc が管理している16番目のクロック(これが PL Clock 1を指す)を制御することを指定し、かつ clkc の管理している2番目のクロック(これが "iopll" を指す)をリソースクロックとして選択することを指定します。
第二引数が省略された場合は、Linux Kernel の起動時に設定されていたリソースクロックが選択されます。
&clkc の代わりに phandle を使って指定することもできます。phandle は デバイスツリーを dtc (Device Tree Compiler) で dtb に変換するときに dtc によって割り当てられる整数値です。例えば dtc によって clkc の phandle が 5 に設定された場合、clocks = <5 15> と指定する事で PL Clock 0 を制御することが出来ます。
Linux を起動する時に読み込むデバイスツリーがシンボル情報を含んでいない場合があります。このようなデバイスツリーで起動した Linux の場合、デバイスツリーオーバーレイで使うデバイスツリーに &clkc のようなシンボルは使えません。この場合は次のように clkc の phandle の値を明示的に指定する必要があります。
/dts-v1/;
/ {
fragment@0 {
target-path = "/amba";
__overlay__ {
fclk0 {
compatible = "ikwzm,fclkcfg";
clocks = <5 15>;
};
};
};
};
Zynq の場合 clocks に指定できるプロパティは次の通りです。
Table.1 Zynq のクロック
Clock Name | Index | Property Value | Description |
---|---|---|---|
armpll | 0 | <&clkc 0> | ARMPLL 第二引数でのみ指定可 省略可 |
ddrpll | 1 | <&clkc 1> | DDRPLL 第二引数でのみ指定可 省略可 |
iopll | 2 | <&clkc 2> | IOPLL 第二引数でのみ指定可 省略可 |
fclk0 | 15 | <&clkc 15> | PL Clock 0 第一引数でのみ指定可 |
fclk1 | 16 | <&clkc 16> | PL Clock 1 第一引数でのみ指定可 |
fclk2 | 17 | <&clkc 17> | PL Clock 2 第一引数でのみ指定可 |
fclk3 | 18 | <&clkc 18> | PL Clock 3 第一引数でのみ指定可 |
ZynqMP の場合 clocks に指定できるプロパティは次の通りです。
Table.2 ZynqMP のクロック (linux-xlnx v2018.2)
Clock Name | Index | Property Value | Description |
---|---|---|---|
iopll | 0 | <&clkc 0> | IOPLL. 第二引数でのみ指定可 省略可 |
rpll | 1 | <&clkc 1> | RPLL. 第二引数でのみ指定可 省略可 |
dpll_to_lpd | 8 | <&clkc 8> | DPLL. 第二引数でのみ指定可 省略可 |
pl0_ref | 71 | <&clkc 71> | PL Clock 0. 第一引数でのみ指定可 |
pl1_ref | 72 | <&clkc 72> | PL Clock 1. 第一引数でのみ指定可 |
pl2_ref | 73 | <&clkc 73> | PL Clock 2. 第一引数でのみ指定可 |
pl3_ref | 74 | <&clkc 74> | PL Clock 3. 第一引数でのみ指定可 |
Table.3 ZynqMP のクロック (linux-xlnx v2019.1)
Clock Name | Index | Property Value | Description |
---|---|---|---|
iopll | 0 | <&zynqmp_clk 0> | IOPLL. 第二引数でのみ指定可 省略可 |
rpll | 1 | <&zynqmp_clk 1> | RPLL. 第二引数でのみ指定可 省略可 |
dpll_to_lpd | 8 | <&zynqmp_clk 8> | DPLL. 第二引数でのみ指定可 省略可 |
pl0_ref | 71 | <&zynqmp_clk 71> | PL Clock 0. 第一引数でのみ指定可 |
pl1_ref | 72 | <&zynqmp_clk 72> | PL Clock 1. 第一引数でのみ指定可 |
pl2_ref | 73 | <&zynqmp_clk 73> | PL Clock 2. 第一引数でのみ指定可 |
pl3_ref | 74 | <&zynqmp_clk 74> | PL Clock 3. 第一引数でのみ指定可 |
insert-rate プロパティは、このデバイスがインストールされた時に設定する周波数を指定します。設定する周波数は、10進数の文字列で指定します。例えば次のようにデバイスツリーに記述することで、インストール時に周波数を 100MHz に設定します。
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba";
__overlay__ {
fclk0 {
compatible = "ikwzm,fclkcfg";
clocks = <&clkc 15>;
insert-rate = "100000000";
};
};
};
};
insert-rate プロパティはオプションです。insert-rate プロパティが指定されていない場合、デバイスのインストール時に周波数は変更されません。
insert-enable プロパティは、このデバイスがインストールされた時にクロックを出力するかしないかを指定します。設定する値は、出力する時は<1>を、出力しない時は<0>を指定します。例えば次のようにデバイスツリーに記述することで、インストール時にクロックを出力するようにします。
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba";
__overlay__ {
fclk0 {
compatible = "ikwzm,fclkcfg";
clocks = <&clkc 15>;
insert-enable = <1>;
};
};
};
};
insert-enable プロパティはオプションです。insert-enable プロパティが指定されていない場合、デバイスのインストール時に出力の制御をしません。
insert-resource プロパティは、このデバイスがインストールされた時に設定されるリソースクロックを指定します。insert-resource プロパティは clocks プロパティに第二引数以降がある場合にのみ効果があります。clocks プロパティが第一引数のみがある場合、リソースクロックは変更されません。
insert-resource プロパティは整数です。 clocks プロパティの2番目の引数のクロックを指定する時は <0> を、3番目の引数のクロックを指定する場合は <1> を、4番目の引数のクロックを指定する場合は <2> を指定します。
例えば次のようにデバイスツリーに記述することで、インストール時にリソースクロックを <&clk 1> に設定します。
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba";
__overlay__ {
fclk0 {
compatible = "ikwzm,fclkcfg";
clocks = <&clkc 15>, <&clkc 0>, <&clkc 1>, <&clkc 2>;
insert-resource = <1>; // <0>: <&clkc 0>, <1>: <&clkc 1>, <2>: <&clkc 2>,
insert-rate = "100000000";
};
};
};
};
insert-resouce プロパティはオプションで、デフォルトは <0> です。この場合、 clocksプロパティが2番目の引数を超える場合、2番目の引数で指定されたクロックがリソースクロックとして設定されます。
remove-rate プロパティは、このデバイスがリムーブされた時に設定する周波数を指定します。設定する周波数は、10進数の文字列で指定します。例えば次のようにデバイスツリーに記述することで、リムーブ時に周波数を 1MHz に設定します。
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba";
__overlay__ {
fclk0 {
compatible = "ikwzm,fclkcfg";
clocks = <&clkc 15>;
remove-rate = "1000000";
};
};
};
};
remove-rate プロパティはオプションです。remove-rate プロパティが指定されていない場合、デバイスがリムーブされた時に周波数は変更されません。
remove-enable プロパティは、このデバイスがリムーブされた時にクロックを出力するかしないかを指定します。設定する値は、出力する時は<1>を、出力しない時は<0>を指定します。例えば次のようにデバイスツリーに記述することで、リムーブ時にクロックの出力を停止します。
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba";
__overlay__ {
fclk0 {
compatible = "ikwzm,fclkcfg";
clocks = <&clkc 15>;
remove-enable = <0>;
};
};
};
};
remove-enable プロパティはオプションです。remove-enable プロパティが指定されていない場合、デバイスのリムーブ時に出力の制御をしません。
remove-resource プロパティは、このデバイスがリムーブされた時のリソースクロックを指定します。remove-resource プロパティは clocks プロパティに第二引数以降がある場合にのみ効果があります。clocks プロパティが第一引数のみがある場合、リソースクロックは変更されません。
remove-resource プロパティは整数です。 clocks プロパティの2番目の引数のクロックを指定する時は <0> を、3番目の引数のクロックを指定する場合は <1> を、4番目の引数のクロックを指定する場合は <2> を指定します。
例えば次のようにデバイスツリーに記述することで、のデバイスがリムーブされた時にリソースクロックを <&clk 1> に設定します。
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba";
__overlay__ {
fclk0 {
compatible = "ikwzm,fclkcfg";
clocks = <&clkc 15>, <&clkc 0>, <&clkc 1>, <&clkc 2>;
remove-resource = <1>; // <0>: <&clkc 0>, <1>: <&clkc 1>, <2>: <&clkc 2>,
remove-rate = "100000000";
};
};
};
};
remove-resouce プロパティはオプションです。省略された場合、このデバイスがリムーブされてもリソースクロックは変更されません。
fclkcfg をインストールしてデバイスツリーを追加すると、各デバイス毎に次のようなデバイスファイルが作成されます。<device-name> には、前節で説明したデバイス名が入ります。
- /sys/class/fclkcfg/<device-name>/enable
- /sys/class/fclkcfg/<device-name>/rate
- /sys/class/fclkcfg/<device-name>/round_rate
- /sys/class/fclkcfg/<device-name>/resource
- /sys/class/fclkcfg/<device-name>/resource_clks
- /sys/class/fclkcfg/<device-name>/remove_rate
- /sys/class/fclkcfg/<device-name>/remove_enable
- /sys/class/fclkcfg/<device-name>/remove_resource
このファイルに 1 を書くことで、クロックを出力するようにします。 このファイルに 0 を書くことで、クロックを出力しないようにします。このファイルを読むことで、現在クロックを出力しているか否かが判ります。 1 なら出力中を示します。
zynq# echo 1 > /sys/class/fclkcfg/fclk0/enable
zynq# cat /sys/class/fclkcfg/fclk0/enable
1
zynq# echo 0 > /sys/class/fclkcfg/fclk0/enable
zynq# cat /sys/class/fclkcfg/fclk0/enable
0
このファイルに周波数を書くことで、クロックの周波数を変更することができます。例えば次のようにすることで周波数を 100MHz に変更します。
zynq# echo 100000000 > /sys/class/fclkcfg/fclk0/rate
zynq# cat /sys/class/fclkcfg/fclk0/rate
100000000
ただし、クロックによっては PLL の制限により希望した周波数に設定することが出来ない場合があります。例えば 「U-Boot から Zynq の PLクロックとリセット信号を制御する」でも例をあげましたが ZYBO では 133 MHz を設定することが出来ません。強引に設定しようとすると次の様に125MHzに設定されます。
zynq# echo 133333333 > /sys/class/fclkcfg/fclk0/rate
zynq# cat /sys/class/fclkcfg/fclk0/rate
125000000
前節で説明した通り、クロックによっては PLL の制限により希望した周波数に設定することが出来ない場合があります。このファイルに周波数を書いて読むことにより、実際にどのような周波数になるかを知ることが出来ます。
zynq# echo 133333333 > /sys/class/fclkcfg/fclk0/round_rate
zynq# cat /sys/class/fclkcfg/fclk0/round_rate
133333333 => 125000000
zynq# echo 75000000 > /sys/class/fclkcfg/fclk0/round_rate
zynq# cat /sys/class/fclkcfg/fclk0/round_rate
75000000 => 71428572
このファイルはリソースクロックを変更するために使用します。次の例ではリソースクロックを1に変更しています。
zynq# echo 1 > /sys/class/fclkcfg/fclk0/resource
zynq# cat /sys/class/fclkcfg/fclk0/resource
1
このファイルを読むことで、リソースクロックとして指定できるクロックの名前を得ることが出来ます。
zynq# cat /sys/class/fclkcfg/fclk0/resource_clks
armpll, ddrpll, iopll
このファイルは、クロックデバイスがリムーブされた時に設定する周波数を指定します。
zynq# echo 100000000 > /sys/class/fclkcfg/fclk0/remove_rate
zynq# cat /sys/class/fclkcfg/fclk0/remove_rate
100000000
このファイルに負の値を書き込んだ場合、クロックデバイスのリムーブ時に周波数を変更しません。
このファイルは、クロックデバイスがリムーブされた時にクロックを出力するか停止するかを指定します。
1を書き込むとクロックデバイスのリムーブ時にクロックを出力します。
0を書き込むとクロックデバイスのリムーブ時にクロックを出力しません。
−1を書き込むとクロックデバイスのリムーブ時にクロックを制御しません。
このファイルは、クロックデバイスがリムーブされた時のリソースクロックを指定します。このファイルは clocks プロパティに第二引数以降がある場合にのみ効果があります。clocks プロパティが第一引数のみがある場合、リソースクロックは変更されません。
0以上の値を書き込むと、クロックデバイスのリムーブ時にリソースクロックを指定されたリソースクロックに変更します。
負の値を書き込むと、クロックデバイスのリムーブ時にリソースクロックを変更しません。
PLL (Phase-Locked Loop) 周波数シンセサイザは、入力クロック信号に対して加減すべき周波数の倍率をデジタル的に設定することで正確な周波数の出力クロック信号を作ります。
PLL はその出力クロック信号をフィードバックして入力クロック信号との位相差を調節することで所望の周波数のクロックを生成します。一般的に位相の調節には多少の時間を要し、その調整期間中、出力されるクロックの周波数は不安定になります。このクロックをそのまま出力してしまうと、そのクロックで動いている回路が誤動作する可能性があります。
Fig.2 ADPLL(All Digital Phase-Locked Loop)
ZynqMP の FPGA クロック生成回路は次のような構造になっています。二つの独立した Divider が連なっているのが特徴です。
Fig.3 ZynqMPのクロック生成回路の構造
しかしながら、個々の Divider を設定する時間にタイムラグが存在すると、意図しない周波数のクロックが出力される可能性があります。
例えば Primary PLL に1500MHz、pl0_division0 に15、Pl0_division1 に 1 が設定されているとします。この場合、出力クロックの周波数は100MHz(=(1500MHz÷15)÷1) です。ここで周波数を250MHz に変更するとします。もし、次の図で示すように、pl0_division0 に1を設定してから pl0_division1 に 6 を設定したとしたら、pl0_divison0 の設定から pl1_division1 の設定までの間、1500MHz のクロックが出力されてしまいます。そして、そのクロックで動いている回路が誤動作する可能性があります。
Fig.4 ZynqMP で意図しない周波数のクロックが出力される例
fclkcfg は周波数を変更する際に、変更する前に一旦クロック出力を停止します。そして、周波数を変更した後にクロックを出力します。このように意図しない周波数のクロックが出力されないようすることで、そのクロックで動いている回路が誤動作するのを防いでいます。
Fig.5 fclkcfg による安全な周波数変更
- FPGA Clock Configuration Device Driver(https://github.com/ikwzm/fclkcfg)
- fclkcfg kernel module debian package(https://github.com/ikwzm/fclkcfg-kmod-dpkg)
- 「FPGA+SoC+Linuxのブートシーケンス(ZYNQ+U-Boot-SPL編)」 @Qiita
- 「U-Boot から Zynq の PLクロックとリセット信号を制御する」 @Qiita
- 「FPGA+SoC+LinuxでDevice Tree Overlayを試してみた」 @Qiita