Tinker Board上でlinux-nextを実行するとreboot時にハングしてしまい、その後ウンともスンとも言わなくなるので、外部からボードの電源ON/OFFする方法を探していました。考え付いた方法は3つです。
1番目は下手なハンダ付けをして、失敗するとボードが壊れることと、制御用にもう1つボードが要るのは何だかダサいのでやりたくありません。
2番目の線で探していたのですが、USBの給電をON/OFFできる製品って、意外とないですね……。
仕方なく3番目の線で、TP-LinkのスマートプラグHS105を購入しました(メーカーの製品サイト)。Amazonで2,600円です。Wi-Fiが内蔵されている割に、ずいぶん値段が安いですね。
メモ: 技術系の話はFacebookから転記しておくことにした。少し加筆した。
目次: ROCK64/ROCKPro64
年末に喰らったA型インフルエンザのダメージも回復したので、RK3328のI2S1のMCLK(マスタークロック)の設定がおかしくなる問題の追跡を再開しました。
思っていた以上に難しくて理解できず、かれこれ1週間以上費やしてしまいましたが、やっと見えてきました。
ざっくり言えばRockchipのSoCが持つクロック分周器の構成がかなり変わっていることが関係している、と言えるんじゃないでしょうか。良し悪しはさておいて、問題の原因になっています。
再現方法はとても簡単です。48kHz → 44kHzの順で再生すると正常に再生できますが、32kHz → 44kHzの順で再生するとエラーになって再生できません。これだけです。
早速、問題の原因を説明したいところですが、Rockchipのクロックがどのように繋がっているか説明しないと、全く何も意味が分からないと思いますので、軽く説明します。
RK3328やRockchipの 他のSoCは、1〜128分周まで可能なInteger Dividerと、有理数で分周(例えば7/500など、分子、分母は16ビット精度の整数で指定可能)が可能なFractional Dividerの2つを持っています。
RK3328のTRM(Technical Reference Manual)では、前者にi2s1_pll_div、後者にi2s1_frac_divという名前を付けており、Linuxのクロックドライバでは前者にclk_i2s1_div、後者にclk_i2s1_fracという名前を付けています。
媒体 | Integer Divider | Fractional Divider |
---|---|---|
TRM(Technical Reference Manual) | i2s1_pll_div | i2s1_frac_div |
Linux Clockドライバ | clk_i2s1_div | clk_i2s1_frac |
今後はLinuxの名前で表記しますが、接頭辞のclk_ は省きます。
I2S系のクロックはInteger DividerとFractional Dividerの両方を利用可能です。他のハードウェアブロックはInteger Dividerしか使えませんが、UARTとI2SとS/PDIFは両方使えるようです。
PLLと分周器とI2S1の接続は下記のようになっています。
CPLL --> | selector |-----> i2s1_div --+--> | selector |--> I2S1 MCLK GPLL --> | | ,---------------' | | `--> i2s1_frac ----> | |
図からもわかる通り、必要に応じてi2s1_fracは使ったり、使わなかったりします。
例えば48kHz, 32kHzで再生する場合は、GPLL -> i2s1_div -> I2S1の経路が使われます。i2s1_fracは使いません。44kHzで再生する場合は、GPLL -> i2s1_div -> i2s1_frac -> I2S1の経路が使われます。
サンプリング周波数Fsと、正しく再生できているときの各分周器の出力周波数、分周比は下記のとおりです。
Fs | i2s1_div出力 | i2s1_div分周比 | i2s1_frac出力 | i2s1_frac分周比 |
---|---|---|---|---|
32kHz | 8.192MHz | 1/60 | 未使用 | 未使用 |
44.1kHz | 491.52MHz | 1/1 | 11.2896MHz | 147/6400 |
48kHz | 12.288MHz | 1/40 | 未使用 | 未使用 |
ちなみにi2s1_divの入力はGPLLが選択されることが多く、周波数は491.52MHzで固定のようです。CPLL/GPLLの周波数は可変のはずですが、切り替わったところを見たことがありません。まあ、GPLLは今回の話に関係ないから、どうでも良いですけど……。
Linuxのクロックドライバは、セレクタで選択可能なクロック系統(この場合だとi2s1_divとi2s1_frac)全てに対して、同じ目標周波数で設定して、目標に一番近い周波数を出力できるクロック系統を選択する仕組みになっています。
例えば先ほど44.1kHzの再生のときはi2s1_fracが選ばれると言いましたが、実はこのときクロックドライバの裏側では、
この2つの選択肢(※)が提示されており、i2s1_fracの方が目標値に近い(誤差0)ため、i2s1_fracが選択されています。
(※)正確に言うと12MHz clkinもあるので3つの選択肢から選びますが、ここでは説明を省いています。12MHz clkinが選ばれることはほぼありません。
先ほど示した表のとおり、32kHzの再生後はi2s1_divの出力が8.192MHzになります。この状態で44.1kHzを再生しようとすると、クロックドライバはi2s1_fracの出力を11.2896MHzにしようと試みます、しかし…。
今のクロックドライバはi2s1_fracの親にあたるクロック、つまりi2s1_divの周波数が目標の出力周波数11.2896MHzより低いと、設定を諦めてしまう実装になっています。
コードで言うとこの部分です。
//drivers/clk/clk-fractional-divider.c
static long clk_fd_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct clk_fractional_divider *fd = to_clk_fd(hw);
unsigned long m, n;
u64 ret;
if (!rate || rate >= *parent_rate) //★★この部分★★
return *parent_rate;
if (fd->approximation)
fd->approximation(hw, rate, parent_rate, &m, &n);
else
clk_fd_general_approximation(hw, rate, parent_rate, &m, &n);
//...
32kHz → 44.1kHzの再生時はrate = 11.2896MHz, *parent_rate = 8.192MHzとなり、★の部分のif文が発動します。これは「i2s1_fracは8.192MHzしか設定できません」という結果を返すことと等しいです。
この結果を受けたクロックドライバの選択肢は下記のようになります。
いずれも目標の11.2896MHzと合いませんが、目標に近いのはi2s1_divと言えます。このためクロックドライバはi2s1_divが最適と判断し、I2S1のマスタークロックが11.214954MHzというおかしな値に設定されてしまいます。
このマスタークロック周波数は、サンプリング周波数44.1kHzの整数倍ではないため、サウンドドライバがエラーと判断してPCM再生を止めてしまいます。
48kHz → 44.1kHzの再生時はrate = 11.2896MHz, *parent_rate = 12.288MHzとなるため、★のif文を突破してfd->approximationの呼び出しに到達します。RockchipのFractional dividerの場合、この関数ポインタはrockchip_fractional_approximation() 関数を指しています。
この関数は変わった処理で、ある条件を満たすと、親のクロックを使うのを諦めて、親の親のクロックを使う処理になっています。
static void rockchip_fractional_approximation(struct clk_hw *hw,
unsigned long rate, unsigned long *parent_rate,
unsigned long *m, unsigned long *n)
{
struct clk_fractional_divider *fd = to_clk_fd(hw);
unsigned long p_rate, p_parent_rate;
struct clk_hw *p_parent;
unsigned long scale;
p_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
if ((rate * 20 > p_rate) && (p_rate % rate != 0)) { //★★目標値が親クロックの20倍より大きく、割り切れないとき★★
p_parent = clk_hw_get_parent(clk_hw_get_parent(hw)); //★★親の親(CPLLかGPLL)を使う★★
p_parent_rate = clk_hw_get_rate(p_parent);
*parent_rate = p_parent_rate;
}
通常ならば、存在するかどうかわからない「親の親のクロック」の存在を仮定しており、Rockchipのクロックトポロジー(PLL -> i2s1_div -> i2s1_frac)と、ハードウェア制約に強く依存した特殊な処理になっていることが伺えます。
しかし、この特殊処理のおかげでi2s1_divの周波数がi2s1_fracにとって扱いづらい変な値に設定されていたとしても、親の親(CPLLかGPLL)の周波数に戻すことができます。
48kHz → 44.1kHzの再生時にi2s1_fracだけでなく、i2s1_divの周波数まで変わってしまっているのは、なんだか不思議だなあと思った方も居るかもしれません。その理由は、この関数が親クロックの周波数目標値を書き換えてしまうから、だったんです。
ここまで読んでいただいている方はほぼゼロだと思いますが……、分周器i2s1_divとi2s1_fracの設定が可能かどうか?どちらを選ぶべきか?を判定する部分は、こんな感じの呼び出し経路になっています。
rockchip_i2s_set_sysclk
clk_set_rate
clk_core_set_rate_nolock
clk_core_req_round_rate_nolock
clk_core_get_boundaries
clk_core_round_rate_nolock // I2S1クロックの設定
clk_core_determine_round_nolock
clk_mux_determine_rate
clk_mux_determine_rate_flags // I2S1手前のセレクタの設定
__clk_determine_rate
clk_core_round_rate_nolock // i2s1_divの設定
clk_core_determine_round_nolock
clk_composite_determine_rate // i2s1_divのセレクタの設定
clk_divider_round_rate // Integer dividerの設定
__clk_determine_rate
clk_core_round_rate_nolock // i2s1_fracの設定
clk_core_determine_round_nolock
clk_composite_round_rate // i2s1_fracのセレクタの設定
clk_fd_round_rate // Fractional dividerの設定
rockchip_fractional_approximation // RockchipのFractional divider固有の設定
何度も同じ関数名が出てきて奇妙に見えると思いますが、目標となるクロック周波数の設定を1ブロックだけで解決できないとき、親クロックに対して再帰呼び出しを行い、親の設定を変更しようとする仕組みになっています。
良くできている素晴らしい仕組みだとは思うのですが…、関数ポインタを使って高度に抽象化されているため、コードを見てもどの関数が呼ばれるのか、もう全く全然わかりません。動作もかなり追いづらいし、デバッグが辛いです……。
Yoctoを使ったプロジェクトをビルドする際に、Gitプロトコルを使わないとアクセスできないリポジトリがあります。
Gitプロトコル(git:// から始まるURLのリポジトリ)は無害なんですけど、大抵の社内プロキシを越えられなくて苦労するので、越え方をメモしておきます。
まずGitのプロキシ設定を行います。この例ではgit-proxyというコマンドを使ってくれと指示しています。
git config --global --add core.gitProxy git-proxy
そんなコマンドはないので、自分で作ります。パスの通った場所、例えば ~/bin/git-proxyのようなファイルを作成して、下記のシェルスクリプトを書いておきます。
#!/bin/sh
exec socat STDIO PROXY:192.168.x.x:$1:$2,proxyport=8080
もちろん別途socatコマンドのインストールが必要です。Debianならapt-get install socatでインストールできます。
最初はnetcatでやってみたんですがダメでした。何でだろ?
< | 2019 | > | ||||
<< | < | 01 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | - | - |
合計:
本日: