link もっと前
   2019年 1月 7日 -
      2018年 12月 29日  
link もっと後

link 未来から過去へ表示(*)
link 過去から未来へ表示

日々

link permalink

linux-next で ROCK64 のオーディオが動かない、その 7

  • 2018年 10月 14日 - I2S と クロックレジスタ
  • 2018年 11月 10日 - I2S の DMA 問題と、残りの 3つの問題
  • 2018年 11月 11日 - I2S2 を認識しない問題解決(DMA チャネル数の問題だった)
  • 2018年 11月 28日 - I2S1 が動かない問題解決(クロックゲート定義のバグを直した)
  • 2018年 11月 29日 - アナログオーディオが動かない問題解決(ACODEC ドライバがなかったから移植)
  • 2018年 12月 23日 - 44.1kHz の PCM を再生できない問題解決(GPLL クロック定義のバグを直した)
  • 2019年 1月 5日 - 32kHz → 44.1kHz の順で PCM を再生できない問題解決(fractional divider の実装を変えた)

ROCK64 で 32kHz → 44.1kHz の順に再生するとエラーになる

年末に喰らった A 型インフルエンザのダメージも回復したので、RK3328 の I2S1 の MCLK(マスタークロック)の設定がおかしくなる問題の追跡を再開しました。

思っていた以上に難しくて理解できず、かれこれ 1週間以上費やしてしまいましたが、やっと見えてきました。

ざっくり言えば Rockchip の SoC が持つクロック分周器の構成がかなり変わっていることが関係している、と言えるんじゃないでしょうか。良し悪しはさておいて、問題の原因になっています。

現象と再現方法

再現方法はとても簡単です。48kHz → 44kHz の順で再生すると正常に再生できますが、32kHz → 44kHz の順で再生するとエラーになって再生できません。これだけです。

Rockchip のクロックと分周器

早速、問題の原因を説明したいところですが、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_ は省きます。

Rockchip の I2S クロック

I2S 系のクロックは Integer Divider と Fractional Divider の両方を利用可能です。他のハードウェアブロックは Integer Divider しか使えませんが、UART と I2S と S/PDIF は両方使えるようです。

PLL と分周器と I2S1 の接続は下記のようになっています。

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 が選ばれると言いましたが、実はこのときクロックドライバの裏側では、

  • i2s1_div: CPLL 1.2GHz / 107 = 11.214954MHz
  • i2s1_frac: i2s1_div 491.52MHz * 147/6400 = 11.2896MHz

この 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 より低いと、設定を諦めてしまう実装になっています。

コードで言うとこの部分です。

Fractional Divider のドライバ(一部)

//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 しか設定できません」という結果を返すことと等しいです。

この結果を受けたクロックドライバの選択肢は下記のようになります。

  • i2s1_div: CPLL 1.2GHz / 107 = 11.214954MHz
  • i2s1_frac: i2s1_div 8.192MHz

いずれも目標の 11.2896MHz と合いませんが、目標に近いのは i2s1_div と言えます。このためクロックドライバは i2s1_div が最適と判断し、I2S1 のマスタークロックが 11.214954MHz というおかしな値に設定されてしまいます。

このマスタークロック周波数は、サンプリング周波数 44.1kHz の整数倍ではないため、サウンドドライバがエラーと判断して PCM 再生を止めてしまいます。

48kHz → 44.1kHz の再生で問題が起こらない理由

48kHz → 44.1kHz の再生時は rate = 11.2896MHz, *parent_rate = 12.288MHz となるため、★の if 文を突破して fd->approximation の呼び出しに到達します。Rockchip の Fractional divider の場合、この関数ポインタは rockchip_fractional_approximation() 関数を指しています。

この関数は変わった処理で、ある条件を満たすと、親のクロックを使うのを諦めて、親の親のクロックを使う処理になっています。

Rockchip の Fractional Divider ドライバ(一部)

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ブロックだけで解決できないとき、親クロックに対して再帰呼び出しを行い、親の設定を変更しようとする仕組みになっています。

良くできている素晴らしい仕組みだとは思うのですが…、関数ポインタを使って高度に抽象化されているため、コードを見てもどの関数が呼ばれるのか、もう全く全然わかりません。動作もかなり追いづらいし、デバッグが辛いです……。

[編集者: すずき]
[更新: 2019年 2月 13日 00:27]
link 編集する

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

git プロトコルの社内プロキシ越え

Yocto を使ったプロジェクトをビルドする際に、Git プロトコルを使わないとアクセスできないリポジトリがあります。

Git プロトコル(git:// から始まる URL のリポジトリ)は無害なんですけど、大抵の社内プロキシを越えられなくて苦労するので、越え方をメモしておきます。

まず Git のプロキシ設定を行います。この例では git-proxy というコマンドを使ってくれと指示しています。

Git 側の設定

git config --global --add core.gitProxy git-proxy

そんなコマンドはないので、自分で作ります。パスの通った場所、例えば ~/bin/git-proxy のようなファイルを作成して、下記のシェルスクリプトを書いておきます。

自作 Git プロキシコマンド

#!/bin/sh

exec socat STDIO PROXY:192.168.x.x:$1:$2,proxyport=8080

もちろん別途 socat コマンドのインストールが必要です。Debian なら apt-get install socat でインストールできます。

最初は netcat でやってみたんですがダメでした。何でだろ?

[編集者: すずき]
[更新: 2019年 1月 6日 20:12]
link 編集する

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
   2019年 1月 7日 -
      2018年 12月 29日  
link もっと後

管理用メニュー

link 記事を新規作成

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDF ファイル RSS 1.0
QR コード QR コード

最終更新: 5/22 01:47

カレンダー

<2019>
<<<01>>>
--12345
6789101112
13141516171819
20212223242526
2728293031--

最近のコメント 5件

  • link 19年04月01日
    すずき 「どの CPU というかシステムでも同じ傾...」
    (更新:04/05 11:03)
  • link 19年04月01日
    hdk 「去年Ryzen 7 1700で測りました...」
    (更新:04/02 22:48)
  • link 19年03月05日
    すずき 「> オシロの波形見てて気がつかなか...」
    (更新:03/21 17:45)
  • link 19年03月05日
    kml 「> 自分が持っている RockPr...」
    (更新:03/20 21:30)
  • link 19年03月10日
    すずき 「ありがとー!!」
    (更新:03/13 09:46)

最近の記事 3件

link もっとみる
  • link 19年05月17日
    すずき 「[GCC を調べる - その 2 - デバッグ環境] GCC をデ...」
    (更新:05/22 01:47)
  • link 19年05月13日
    すずき 「[GCC を調べる - その 1 - ビルド] 最近 GCC のコ...」
    (更新:05/22 01:38)
  • link 19年05月09日
    すずき 「[RockPro64 の PCIe] RockPro64 の PC...」
    (更新:05/11 15:12)

こんてんつ

open/close wiki
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報