最近lede-project/sourceにマージされたPR #1542 に関連して、dtsのGPIO関連について初めて理解できた点が多かったので、備忘録的に。 前置きが長いので、不要な人は 本題 まですっ飛ばしてください。
仕様
何はともあれ、まずWT1520の仕様。
- SoC: Ralink RT5350 (MIPS32, 1C1T, 384MHz)
- RAM: 32MB
- Flash: 4MB
- WAN/LAN: 100Mbps x1 / 100Mbps x1
- USB: USB 2.0 Type-A x1
ざっくり言ってしまうと、少し古めの所謂ポケットルータ。
問題
OpenWrtから長らく、リセットボタンがdts(Device Tree Source)で定義されておらず使用できなかった。
対処法
Windows使いのOpenWrt(ビルド編) | もくのすけのスペース
で紹介されている通り、dtsに付け足してビルドする。
その後
上記対処法でビルドした結果、LEDEになりramipsのKernelが4.4から4.9に引き上げられる前後までは正常に機能していた。しかし、どこからか(気づいたのがだいぶ遅かったので、どこからNGになったのか正確には不明)この対処法でビルドした場合リセットボタンは機能するものの、シリアルのコンソールが入力を受け付けない状態になってしまった。
個人的にリセットボタンよりもシリアルコンソールが重要なので、泣く泣くリセットボタンを有効化するためのコミットをrevert。
以上が長いけど前置き。
修正してみる
手持ちのルータは基本シリアルを取るようにしているので、リセットボタンが機能しなくても通常は firstboot コマンドで初期化が可能。しかしながら、WT1520に関しては2台所有しているうち片方はシリアルのケーブルをハンダ付けする際に誤って基板上のパターンを剥がしてしまったために、シリアル通信が不可能になってしまった。そのため、どうしてもリセットボタンを使えるようにする必要があった。
修正内容
何度か試行錯誤を重ねた結果、WT1520のFlash 4MB版と8MB版で共通部を定義する WT1520.dtsi を以下のように修正。
修正前
[sourcecode]
include "rt5350.dtsi"
/ { compatible = "nexx,wt1520", "ralink,rt5350-soc";
memory@0 {
device_type = "memory";
reg = <0x0 0x2000000>;
};
chosen {
bootargs = "console=ttyS1,57600";
};
};
&uart { pinctrl-names = "default"; pinctrl-0 = <&uartf_pins>; status = "okay"; };
&pinctrl { state_default: pinctrl0 { gpio { ralink,group = "jtag"; ralink,function = "gpio"; }; }; };
ðernet { mtd-mac-address = <&factory 0x4>; };
&wmac { ralink,mtd-eeprom = <&factory 0>; };
&ehci { status = "okay"; };
&ohci { status = "okay"; }; [/sourcecode]
修正後
ハイライト部分が追加/修正/削除した箇所。 [sourcecode highlight="3-4,18-29,33,39"]
include "rt5350.dtsi"
include <dt-bindings/gpio/gpio.h>
include <dt-bindings/input/input.h>
/ { compatible = "nexx,wt1520", "ralink,rt5350-soc";
memory@0 {
device_type = "memory";
reg = <0x0 0x2000000>;
};
chosen {
bootargs = "console=ttyS1,57600";
};
gpio-keys-polled {
compatible = "gpio-keys-polled";
#address-cells = <1>;
#size-cells = <0>;
poll-interval = <20>;
reset {
label = "reset";
gpios = <&gpio0 10 GPIO_ACTIVE_LOW>;
linux,code = <KEY_RESTART>;
};
};
};
&uart { status = "okay"; };
&pinctrl { state_default: pinctrl0 { gpio { ralink,group = "jtag", "uartf"; ralink,function = "gpio"; }; }; };
ðernet { mtd-mac-address = <&factory 0x4>; };
&wmac { ralink,mtd-eeprom = <&factory 0>; };
&ehci { status = "okay"; };
&ohci { status = "okay"; }; [/sourcecode]
変更点
[sourcecode firstline="3"]
include <dt-bindings/gpio/gpio.h>
include <dt-bindings/input/input.h>
[/sourcecode] ボタンの定義に関して、GPIOの0/1をLOW/HIGHと記述したり、ボタンの割り当てコードを数字ではなく文字で記述できるようにするためのincludeを追加
[sourcecode firstline="18"] gpio-keys-polled { compatible = "gpio-keys-polled"; #address-cells = <1>; #size-cells = <0>; poll-interval = <20>;
reset {
label = "reset";
gpios = <&gpio0 10 GPIO_ACTIVE_LOW>;
linux,code = <KEY_RESTART>;
};
};
[/sourcecode]
ボタンの定義を追加。"gpio-keys-polled" はボタン(key)の種類で、その後の#address-cellsや#size-cells, poll-intervalは正直不明。その下に、個々のボタンの定義が並ぶ。
resetボタンはGPIOチップ0のGPIO10がLOW、つまり0である場合にACTIVE(押されている状態)。codeは他の機種と同様に "KEY_RESTART" を割当て。
[sourcecode firstline="32"] &uart { status = "okay"; }; [/sourcecode] pinctrl-names = "default"; と pinctrl-0 = ; の削除。リセットボタンに関して参考にした前述のサイトを読んだ際、"シリアルコンソールとリセットボタンでGPIOピンを共有している" という盛大な勘違いをしてしまい、この箇所を削ると上手く両立できるようになるのでは?と考えた(間違い)。
[sourcecode firstline="36"] &pinctrl { state_default: pinctrl0 { gpio { ralink,group = "jtag", "uartf"; ralink,function = "gpio"; }; }; }; [/sourcecode]
39行目の ralink,group に "uartf" を追加。これも上記同様、シリアルとリセットボタンを両立させるために必要なのでは、と考えた(間違い)。
以上が自分で修正してみた点。かなり色々と思い込みが多く、認識を間違えたままやってしまったものの、これでリセットボタンが機能するようになってしまった。
PR
できればUpstreamにも反映できないかな、と考えた結果、この状態でPRを出した。正直ブン殴りたい。
予想していなかったわけではないものの指摘が入ったが、どちらかというと予想外な方向だった。
この指摘とそれに関連する議論については、lede-project/source - PR #1542 を参照。恥ずかしい。詳細は記事では割愛します。
メンテナのmkresin氏の指摘やアドバイスに従って確認作業をしていくと、どうも元々 WT1520.dtsi 自体に問題があったことが判明。どうやら、基板上に出ているシリアルはGPIO10ではなく別のピンの "UART Lite" に接続されているらしい。
WT1520の搭載するRT5350にはUART LiteとUART Fullが存在し(これも初めて知った)、UART Liteは送受信各1本のみの信号線を有する文字通りLiteなインターフェースで、UART FullはLiteの信号線以外にも各種制御用などの信号線を有するフル機能版ということになる。
このうち、UART Liteは既に WT1520.dtsi でincludeしている rt5350.dtsi で有効化(status = "disabled" が無い)されており、WT1520.dtsi でも有効扱いとなっている。これだけでUART Liteは使用可能になっているが、何故か WT1520.dtsi ではUART Fullまで有効化していた。
このため、ttyS0はUART Fullに、ttyS1はUART Lite割り当てられてしまい、そのままでは(デフォルト設定はconsole=ttyS0のため)シリアルコンソールを接続しても通信ができなくなってしまう。そこで、WT1520.dtsi では chosen {}; ノードにより console をttyS1 に設定されていた。
ざっくりまとめると以下の表のとおり。
本来(18cc8d5 以降)
UART | status | tty | bootargs |
---|---|---|---|
Lite (Console) |
Enable (rt5350.dtsi) |
ttyS0 | (None) |
Full | Disable | (None) |
WT1520(18cc8d5 よりも前)
UART | status | tty | bootargs |
---|---|---|---|
Lite (Console) |
Enable (rt5350.dtsi) |
ttyS1 | console=ttyS1,57600 |
Full | Enable (WT1520.dtsi) |
ttyS0 |
上記の通りttyS0が入れ替わってしまい、bootargsを設定してUART LiteであるttyS1を指定し直している。では、UART Fullを有効にして何に使っていたかというと、何もしていない。RT5350のdatasheetを見るとUART Fullのピンの中にリセットボタンが接続されているGPIO10があるが、リセットボタンを機能させるのにUART Fullの有効化は必要ない。
要するに、不要なUART Fullを有効にしてそのためにttyS0が変更され、わざわざUART LiteのttyS1を指定し直すという二度手間ということだった。どうしてこうなってしまったのかは謎。
最終的に "UART Full の有効化は必要ないので消してしまおう" ということになり、具体的な修正方法まで示されたので、それに従ってコミットを修正し、それがLEDE-Project masterブランチへマージされた。(後から気付いたが、lede-17.01ブランチにもbackportされていた。)
WT1520.dtsi の最終的なコードは、lede-project/source を参照。
気づいたこと
[sourcecode] &pinctrl { state_default: pinctrl0 { gpio { ralink,group = "jtag", "uartf"; ralink,function = "gpio"; }; }; }; [/sourcecode] は、ピンのグループ(上記だと "jtag" や "uartf")に対する機能の割り当ての設定ということ。上記ではグループ "jtag" と "uartf" がそれぞれ持っているピンに対して、一括でGPIOとして機能するように割り当てを行っている。この場合、あくまでGPIOとしてのみ動作し、元々のJTAGやUART Fullとして動作することはできなくなる。
もし本来の機能通りに動作させる場合、gpio {}; ノードの中ではなく下記のように設定する。 [sourcecode] &uart { status = "ok"; }
&pinctrl {
state_default: pinctrl0 {
gpio {
ralink,group = "jtag";
ralink,function = "gpio";
};
uartf {
ralink,group = "uartf";
ralink,function = "uartf";
};
};
};
[/sourcecode]
1 - 3行目でUART(≠ UART Lite)を有効化し、pinctrl内で uartf {}; を設定して "uartf" グループに対して "uartf" 機能の割り当てを行う。
このうち、pinctrl内の uartf {}; は rt5350.dtsi でも定義されており、それを利用して以下の通りに書くことも可能な模様(commit 18cc8d5 よりも前と同じ状態)。
[sourcecode]
&uart {
pinctrl-names = "default";
pinctrl-0 = <&uartf_pins>;
status = "okay";
};
&pinctrl { state_default: pinctrl0 { gpio { ralink,group = "jtag"; ralink,function = "gpio"; }; }; }; [/sourcecode] &uart {}; ノード内に pinctrl-names と pinctrl-0 を追加。
このピンの割り当て方法については、"rt5350 datasheet" などと検索すると見つかるデータシートに詳しく書かれている。
(1 PIN DESCRIPTION -> 1.3 PIN SHARING SCHEME -> Table 1-5 UARTF Pin Sharing Scheme)
これを見ると、UART Fullの持つ8本のピンのうち、全てをUART Fullとして利用するものから半分で分割するもの、全てGPIOとして利用するものなど複数あり、なるほどと思える。
今回、commit 18cc8d5 の前までは全てのピンがUART Fullとして割り当てられ、commit 18cc8d5 以降は逆に全てGPIOとしての割り当てになった。
ちなみに、GPIOとUART Fullでピンを半々に割り当てる場合、
[sourcecode]
&pinctrl {
state_default: pinctrl0 {
gpio {
ralink,group = "jtag";
ralink,function = "gpio";
};
uartf_gpio {
ralink,group = "uartf";
ralink,function = "gpio uartf";
};
};
};
[/sourcecode]
というように、ralink,function に並べて指定する模様。
まとめ
mkresin氏、本当に丁寧に教えて頂いてありがとうございました。