Hatch Memorial Shell, Boston


Date/Time: 2006:07:30 10:33:19
Camera: FUJIFILM
Model: FinePix F401
Exporsure Time: 1/450
FNumber: 7.0
Aperture Value: 5.6
Focal Length: 5.7

Close

y2blog » Raspberry Pi 2 + Volumio + DAC でネットワークオーディオに挑戦(その6)

11

15

2015

Raspberry Pi 2 + Volumio + DAC でネットワークオーディオに挑戦(その6)

Raspberry Pi 2 を自作DACシステムに組み込んでみた



Raspberry Pi 2 Audio System
以前作成した自作DACシステムに無理矢理Raspberry Pi 2 を組み込んで実験している様子


これまでRaspberry Pi 2とVolumioの組み合わせでネットワークオーディオプレイヤーを実現する方法について簡単に説明してきたが、組み合わせるDACモジュールはTIのPCM5102やESS Technology のES9023などの比較的安価で簡易的なDACチップの組み合わせだけだったので、本格的なオーディオ用途としては少し物足りないという感じがする.


勿論、これらの組み合わせでも十分高品質なディジタルオーディオシステムと言えるのだが、やはりもう少し上位のDACシステムと組み合わせて見たいと思う.そこで、今回は以前製作したデジットのデジタルオーディオキットシステムに無理矢理Raspberry Pi 2を組み込んでみた.


Raspberry Pi 2とVolumioの組み合わせで問題となるのは、Raspberry Pi 2から出力されるI2S信号が、LRCK, BCK, DATA しかなく、MCK信号が出力されないことだ.PCM5102やES9023などはDACチップ内部でBCK信号などからMCK信号を内部で生成する機能を持っているので、MCK信号が無くても上記の3つの入力信号だけできちんと再生ができるので特に問題にはならない.


MCK信号が必須のDACチップをRaspberry Pi 2とVolumioの組み合わせで使う場合には、何らかの方法で MCK信号を作り出さなければならない.MCK信号を生成させる方法として、


・Volumioに組み込まれている、I2Sサウンドドライバーモジュールをに手を加えて、Raspberry Pi 2 のGPIOピンからMCKに相当する信号を出力させる.

 この方法は、Linuxのドライバーのソースコードを弄って再コンパイルするなどの高度な技能が必要になるので、一般の人では全く太刀打ちできないが、ほーりーさんがMCLK出力対応のVolumioのイメージを配布しているようなので、興味の在る人は下記のページを覗いてみて欲しい.

 『MCLK出力対応Volumio1.55』: ほーりーさんの日記 

・BCK信号からICS570等のマルチプライヤーICチップを使って、BCK信号の整数倍のMCK信号を生成する.

 この方法も、自分で回路を作成しなければならないので一般向けとは言えないが、電子工作が得意な人であればそれ程難しい事はないだろう.ICS570に関してはちょっと前にDigi-Keyから購入してあるので、時間ができたらICS570を用いたMCK信号生成回路を組み込んでみるつもりだ.

 ICS570のデータシート: https://www.idt.com/document/dst/570-datasheet

・ASRC(Asynchronous Sampling Rate Converter:非同期型サンプル レート コンバーター)を用いる方法

 今回は、このASRCを用いてMCK信号の無いRaspberry Pi 2とVolumioの組み合わせでもDACチップが正常に機能するようにシステムを組んでみることにする.とは言っても、既にASRCモジュール自体は自作DACシステムに組み込まれているので、今回は単純にRaspberry Pi 2のLRCK, BCK, DATA信号を既存のASRCモジュールに突っ込むだけの話だ.


ASRC & Clock Generator
今回はデジット(共立電子)のキットをカスタマイズしたモジュールを使用した


Raspberry Pi 2の上にはドーターカードを載せ、デジタルアイソレータ(TI ISO7640)によりI2S信号を絶縁して、Raspberry Pi側のノイズの影響がASRCやDAC側に伝わらないようにしてある.デジタルアイソレータを仲介してあるので、Raspberry Pi側の電源の質の悪が悪影響を及ぼす可能性は少ないとは思うが、Raspberry Pi B+ モデル以降で導入された内蔵のスイッチングレギュレータそのものを取り払って、Raspberry Piそのものをローノイズ化(実際の効果は不明? 多分気休め程度にしかなっていない???)した.勿論、Raspberry Pi の電源は専用の電源トランスと電源回路を使用し、ASRCやDACの電源系統とは完全にトランスレベルで分離している(この後の続編で紹介予定)



ASRCについて


ASRC(非同期型サンプルレートコンバーター)と聞いてもディジタルオーディオに詳しい人でなければどのような物なのか想像がつかないかもしれないが、デジタルオーディオ信号を受け渡しする際に、サンプリング周波数や量子化ビット数などを変換するモジュールの事だ.本来はサンプリング周波数の変換機能の事だが、デジタルオーディオ信号を扱う場合には量子化レベルの変換も同時に行われる事が多いので、デジタルオーディオ信号用のASRCチップは両方の機能が備わっている.


今回用いたASRCチップは、TIのSRC4192という型番の物で、デジタルオーディオ入力信号のサンプリング周波数を最大16倍まで上げたり(アップサンプリング)、1/16まで下げたり(ダウンサンプリング)する事ができる.このチップ自体は最新の設計ではないので、扱えるサンプリング周波数が最大で212KHzまでという制限があり、最新の384KHzや768KHzまで扱う事のできるDACとの組み合わせではちょっと物足りないが、192KHz/24bit に対応しているのでPCM1792/1794などとの組合わせではジャストフィットだろう.



 TIのSRC4192の紹介ページ(一応日本語): http://www.tij.co.jp/product/jp/src4192

今回使用したASRCモジュールは、デジットの『サンプルレートコンバータ実験基板』にちょっと手を加えた物を利用した.SRC4192の機能を理解し使いこなすのは結構大変で、サンプルレートコンバータ実験基板の説明書を一通り読んだだけでは使い方を理解できなかったが、色々と実験を繰り返しているうちに何となく使い方や機能が理解できるようになった.


SRC4192を用いて、44.1KHz/16bitのCDレベルの信号を、192KHz/24bit の所謂ハイレゾ仕様に変換するためにはかなり面倒な設定が必要で、動作モード設定(スレーブモード、入力側マスタモード、出力側マスタモード)と入力するシステムクロック(マスタクロック)の周波数設定(FS x 128, 256, 512)、入力フォーマットの指定(右寄せRJ:16,18,20,24 左寄せ: 24 I2S:24 )出力フォーマット(左寄せ、右寄せ、I2S、TDM:ビット長 16/18/20/24 )などをきちんと設定しなければならない.特に、出力されるサンプリング周波数はシステムクロックの周波数が基準となって変換されるため、システムクロックを自由に変更できるような仕組みがなければ思い通りのサンプリング周波数変換ができないので、PLLを用いて任意のクロックを発生させることが可能なクロックジェネレータが必要だ.今回は同じくデジットの『PLLクロック基板CLK_1707D』を利用した.


Amanero Combo384やDIYINHKのXMOSなどのUSB DDCのデジタル出力信号(I2S)をSRC4192の入力として使用する場合は、MCK信号として 44.1KHz系(88.2/176.4/352.8KHz)の場合は22.5792MHz、48KHz系(32.0/48.0/64.0/96.0/192/384KHz)の場合は24.576MHzのMCK信号が出力される.この場合、選択可能な出力側のサンプリング周波数は、44.1KHz系の場合、22.5792MHz/128 = 176.4KHz、22.5792MHz/256 = 88.2KHz、22.5792MHz/512 = 44.1KHz の3通りの組み合わせしか選べない.同様に、48KHz系の場合は 192KHz、96KHz, 48KHz の3択という事になる.


44.1kHz系列と48kHz系列の間で相互に変換するためには、システムクロック側で出力する信号の系列に合わせた細かなクロック周波数の制御が必要になるので、SRC4192を使いこなすためには自由度の高いクロックジェネレータが必須と言えよう.


【ASRCを使う利点】


MCK信号が出力されないRaspberry Pi 2 + Volumio の組み合わせで、PCM1792のような高性能DACを用いる場合の方法について簡単に紹介してきたが、ASRCを使う最大の利点は何と言っても、ジッターの影響を少なくする事ができる事だろう.Raspberry Pi 2のハードウェアはかなり高性能なARMチップが使われており、コンピュータのクロックも高速化されているので、ハードウェア的にはジッターの影響は少なくなっていると思われる.


しかしながら、Volumio自体はLinux(Debian)ベースのOSで動いているため、シビアな割り込み処理が要求される専用のRTOS(リアルタイムOS)とは比べ物にならないくらい、各プロセスのタイミング精度は悪い.Volumioで用いられるI2S信号ドライバは当然ソフトウェア的な処理による物なので、OSのカーネル上の他のプロセスの影響をもろに受けてしまい、出力されるI2S信号のクロック精度の劣化、すなわちデジタルオーディオ信号のクオリティーを劣化させるジッターが増加してしまう.


ASRCを用いることで、出力されるデジタルオーディオ信号はクロックジェネレータの高精度な水晶発振器によって生成された、安定したクロックに基づくジッターの少ないディジタルオーディオ信号をDACに引き渡す事が可能になる.つまり入力側のジッターの影響がASRCによって低減されるという事だ.



VolumioのI2S信号はちょっと厄介


これまで、デジットのデジタルオーディオキットの組み合わせと、Amanero Combo384やDIYINHKのXMOS USBなどのDDCとの組み合わせで何の問題も無く快調に動いていたので、今回もRaspberry Pi 2 + VolumioによるI2S信号も何の問題も無く音が出るものと思っていた.ところが実際にVolumioで音出ししてみると、音源の種類によっては正常に再生できない事が判明した.Volumioに登録されているネットワークラジオでも、再生可能な局と全く音が出ない局に分かれている.NAS等に置いた音源も再生できるものとそうでないものに分かれている.96KHz24bitの音源はどれも問題無く再生できているが、ライブラリの殆どを占めているCDからリッピングした44.1kHZ/16bit系の音源が全て再生できない.


何よりも問題なのは、iTunesなどからVolumioのAirPlay経由で再生する場合、44.1KHz/16bitモードでの再生となるので、この場合も音が全く出てこないことだ.私の場合はiTunesからのAirPlay再生のためだけに導入したので、正常に再生できないというのでは全く意味が無いのだ.


最初は再生できない原因が全く判らず、オシロスコープ等で再生できる楽曲やラジオ局との違いを調べて行くうちに、再生できない時のBCKの周波数が想定よりも低いことに気が付いた.正常に再生されている場合のBCKの周波数は約2.82MHz、44.1kHZ/16bit系の音源の場合は約1.41MHzとなっている.BCKの周波数はDACによる違いはあるが、通常LRCKの1周期(1/fs:サンプリング周波数)の間に各チャンネル32ビットのデータが埋め込まれている.つまりサンプリング周波数fsを64倍した値であり、44.1kHzの場合のBCKは 44.1KHz X 64 = 2.8224MHz となる.BCKが約1.41MHzということはLRCKの1周期中に32個のBCKが含まれているということになり、一般的なDACが想定している64個のBCK信号ではないことが音が出ない原因と思われる.



ライブラリのメイン音源である44.1KHz/16bit信号は全く音が出ない

44.1KHz-/16bit LRCK-BCK
44.1KHz/16bit信号のLRCK(上)とBCK(下)の波形


44.1KHz/16bit - DATA-BCK
44.1KHz/16bit信号のDATA(上)とBCK(下)の波形[ BCK: 44.1KHz X 32 = 1.4112MHz ]


96KHz/24bit信号は問題無く再生される

96KHz/24bit   LRCK-BCK
96KHz/24bit信号のLRCK(上)とBCK(下)の波形


96KHz/24bit DATA - BCK
96KHz/24bit信号のDATA(上)とBCK(下)の波形 [ BCK: 96KHz X 64 = 6.144MHz ]


PCM5102を用いて音出しをしていたときには44.1kHZ/16bit系の音源も正常に再生できているので、PCM5102のデータシートでDACの受け入れ可能なデータフォーマットを見たところ、44.1kHZの場合は、BCKの周波数としてfsX32(1.4112MHz)とfsx64(2.8224MHz)の両方に対応している事が確認できる.


今回はASRCがDACの前に入るので、SRC4192のデータシートでBCKのオペレーション条件を調べてみたところ、やはりマスターモードでの入力側のBCKは64fsに固定されていた.SRC4192では入力側のBCKが32fsでは動作しないという事だ.うーん困った...



Volumioのリサンプリング機能を試してみたが...


Volumioにはソフトウェア的にリサンプリングを行う機能が備わっており、”MENU” → “Playback” 中に “Resampling” に関する設定をONにすると、Volumioのメディアプレイヤーがリサンプリングを行い、任意のサンプリング周波数とビット数でデジタル出力する機能がある.機能的にはASRCと同じものだが、こちらはソフトウェア処理なのでRaspberry PiのCPUに余計な負荷が掛かり、余計にジッターが悪化しそうなのでできれば使用したくない.とりあえずこの機能を”ON”にする事でBCKの32fs問題は解決できそうだ.




Volumio SRC
Volumioのリサンプリング機能を設定してBCKの32fs問題を回避してみる


出力モードの設定項目として、

    ”disbaled”
    ”16bit/44.1KHz”
    ”16bit/96.0KHz”
    ”24bit/44.1KHz”
    ”24bit/96.0KHz”
    ”24bit/192KHz”
    ”32bit/44.1KHz”
    ”32bit/96.0KHz”
    ”32bit/192KHz”
    ”32bit/384KHz”

が選択可能だ.サンプリング周波数の変換は不要なので、単純に量子化ビット数だけを24bit以上にすれば BCKが64fsになるので都合が良いのだが、残念な事に量子化ビット数だけの変換は行えないようだ.常に設定したサンプリング周波数に変換されてしまうので、折角のハイレゾ音源も設定によってはダウングレードされた状態で再生されてしまう.




Volumio Player
Volumioのメディアプレイヤー機能を使って44.1KHz/16bit音源を再生してみる

44.1Khz/16bit mode
44.1KHz/16bit音源が BCK 64fsできちんと再生されるようになった

I2S Signal
オシロスコープの同期を調整して、LRCK信号(下)とDATA信号(上)の関係を分かり易く表示してみた

実際に”24bit/96.0KHz”に設定してみると、一応44.1KHz/16bit音源のデータもBCKが64fs(2.8224MHz)となりきちんと再生されるようになったが、補間方法として “Best Sinc Interpolator” を選ぶとCPUの負荷が高過ぎるのか再生が不安定になってしまった.やはりVolumioのリサンプリング機能は使いものにならないようだ.


とりあえずリサンプリング機能でBCKの32fs問題は一応解決したかに思えたが、私にとってVolumioを使う最大の動機であった”AirPlay”による再生機能に関しては、このリサンプリング機能は全く働かない事が判明した.”AirPlay”経由でiTunes等から再生しても、常に44.1KHz/16bitモードで再生されてしまい、BCKが32fsのままなので、結局”AirPlay”では再生できないという事のようだ.これを回避するには、VolumioのI2Sドライバそのものを書き換えて常にBCKが64fsになるように変更するか、何らかのハードウェアによるフォーマット変換装置を導入するしか方法は無さそうだ.


Raspberry Pi + Volumioの組み合わせでは、満足なネットワークオーディオプレイヤーを作るのは難しそうだ.何よりも問題なのは、Raspberry PiのシステムがLinuxベースで電源スイッチのON/OFFだけで簡単に操作できない事だろう.一々シャットダウン処理しなければ電源を切ることすらできないというのはオーディオ機器としては致命的な欠陥で、残念だが当面はごく一部のニッチなユーザのためだけのマニアックなシステムのままだろう.Raspberry Pi はコンピュータ屋さんのおもちゃとしては最高の遊び道具だが、オーディオ屋さんにとっては敷居が高過ぎるようだ.




Volumio のI2Sサウンドドライバー廻りを少しだけ探ってみた


Raspberry Pi + Volumio + 自作DAC の組合わせでは問題が生じてしまったが、このまま諦めるのは癪なので問題解決の糸口を探ってみることにした.解決の糸口としては2つ有ると述べたが、先ずはそのうちの一つ、ハードウェアによる解決方法を探ってみる.


ハードウェアによる解決といってもそれ程大掛かりな物では無く、単に今使っているTIのSRC4192の使用を止めて別なASRCチップを使う事を検討するだけの事だ.別なASRCチップの候補としては、旭化成エレクトロニクス社のAK4137という最新のASRCチップが真っ先に浮かぶだろう.このチップは先月くらいに市場に出まわるようになった最新のチップで、実は既にDigi-Key経由で2つ程購入してある.時間が無くてまだこのチップを組み込んだシステムは製作していないが、データシートを見る限りPCM/DSDの相互変換ができるなど、何でもござれの機能満載チップのようだ.


 ・AK4137EQ 768kHz 2ch Asynchronous 32-bit Premium SRC

AK4137EQのデータシートを見ると、42 page の”入力ポートのシステムクロックとオーディオインタフェースフォーマット”の記述に、”Mode 3: 32/16bit, I2S Compatible” という項目が記載されており、”16bit, I2S Compatible: IBICK 32fs” となっているので、このモードを使えばVolumioの BCK 32fs 問題は解決できそうだ.


AK4137を使ったASRCの製作記事については、ある程度実装が終わった段階で公開するつもりだ.(でも何時になるかは不明)



解決方法の残りの一つが、VolumioのI2Sサウンドドライバー廻りをソースコードレベルで書き換えてしまうという方法だが、これを実現するにはLinuxのカーネルレベルでのソースコードからの再構築作業が必要だ.先に紹介した『ほーりーさんの日記』にこの辺りの改造について紹介されているので、幾つか関係の深そうなリンクを載せておく.



 ・Volumioの構造

 ・Raspberry PI のカーネルクロスコンパイル環境を作る

 ・Volumio でジッターを無理やりなくしてみる

 ・Volumio1.55 ここまでのまとめ

 ・Volumio に 24bbit R-2R 上野 DAC を接続する

先ずは、Volumio1.55(GitHub上には開発中のVolumio2.0が有るようですが、とりあえず今は現行のV1.55のLinuxソースコードを入手しておきましょう)のソースコードを取得しなければなりませんが、Volumioのホームページを漁ってもイマイチソースコードが何処に置かれているのか判らなかった.『ほーりーさんの日記』の “Raspberry PI のカーネルクロスコンパイル環境を作る” で紹介されている URLを参考に取得してみた.

   git clone https://github.com/raspberrypi/linux.git
   git clone https://github.com/raspberrypi/tools.git


上記コマンドで取得したソースコードを探って行くと、

Linux Kernel Source Tree
取得したlinuxディレクトリ配下を覗いてみる


I2Sサウンドドライバー廻りのソースコードは、”./linux/sound/soc/bcm/” ディレクトリ配下に置かれているようだ .”bcm2708-i2s.c”, “bcm2835-i2s.c”, “hifiberry_dac.c” が今回のI2Sサウンド廻りのソースコードだろう.まだ中身をきちんと調べていないので、詳しい事は判らないがBCKやオーディオフォーマットなどの変更は、これらのソースコードを書き換えれば何とかなりそうだ.


Raspberry Pi 2 では BCM2836 というARMV7 系列のCPUコアが使われているが、”bcm2835-i2s.c”を使うのか、それとも”bcm2708-i2s.c”なのかは現時点では不明だ.ひょっとするとRaspberry Pi 2では”bcm2836-i2s.c”のようなファイルが別に用意されているのかもしれないが、多分BCKやオーディオフォーマットなどの変更しなければならない部分は共通だろう.


“bcm2708-i2s.c”, “bcm2835-i2s.c”と”hifiberry_dac.c”の関係がまだ判らないが、名前から察するに、”hifiberry_dac.c”がDACモジュール(PCM5102系列を前提に作られている)に関する固有の情報が記述されており、ALSAオーディオドライバーである、”bcm2708-i2s.c”, “bcm2835-i2s.c”にhifiberry_dacがリンクされるようだ.”hifiberry_dac.c”の中身を覗いてみたら、’ .cpu_dai_name = “bcm2708-i2s.0” ‘ という項目があるので、実際に使われているのは”bcm2708-i2s.c”の方かもしれない.この辺の仕組みはまだ何も判っていないので、また後日改めて検証してみようと思う.



Raspberry Pi 用のLinux ディストリビューションである Raspbian のカーネルの再構築作業については、Raspberry Piご本家の HELP に説明があるので、こちらを参照しておいた方が良さそう.


  KERNEL BUILDING : https://www.raspberrypi.org/documentation/linux/kernel/building.md

“hifiberry_dac.c”の中に、



static int snd_rpi_hifiberry_dac_hw_params(struct snd_pcm_substream *substream,
				       struct snd_pcm_hw_params *params)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;

	unsigned int sample_bits =
		snd_pcm_format_physical_width(params_format(params));

	return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2);
}


という関数があり、DACチップのサンプルビット長の2倍をBCKレシオの値として設定しているというような部分が伺えるが、検出されるsample_bitsの値が16なのか、それとも32なのかは現時点では不明だが、多分デフォルトではsample_bitsの値として16が採られているのかもしれない.



もう一方の”bcm2708-i2s.c”の中身は”hifiberry_dac.c”よりも複雑で、ざっと眺めただけでは何が何だか良く分からないが、BCKの周波数やオーディオデータフォーマットに関係しそうな部分が数カ所見つかる.




static int bcm2708_i2s_hw_params(struct snd_pcm_substream *substream,
				 struct snd_pcm_hw_params *params,
				 struct snd_soc_dai *dai)
{
	struct bcm2708_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);

	unsigned int sampling_rate = params_rate(params);
	unsigned int data_length, data_delay, bclk_ratio;
	unsigned int ch1pos, ch2pos, mode, format;
	unsigned int mash = BCM2708_CLK_MASH_1;
	unsigned int divi, divf, target_frequency;
	int clk_src = -1;
	unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
	bool bit_master =	(master == SND_SOC_DAIFMT_CBS_CFS
					|| master == SND_SOC_DAIFMT_CBS_CFM);

	bool frame_master =	(master == SND_SOC_DAIFMT_CBS_CFS
					|| master == SND_SOC_DAIFMT_CBM_CFS);
	uint32_t csreg;

	/*
	 * If a stream is already enabled,
	 * the registers are already set properly.
	 */
	regmap_read(dev->i2s_regmap, BCM2708_I2S_CS_A_REG, &csreg);

	if (csreg & (BCM2708_I2S_TXON | BCM2708_I2S_RXON))
		return 0;

	if (!dev->dev->of_node)
		bcm2708_i2s_setup_gpio();

	/*
	 * Adjust the data length according to the format.
	 * We prefill the half frame length with an integer
	 * divider of 2400 as explained at the clock settings.
	 * Maybe it is overwritten there, if the Integer mode
	 * does not apply.
	 */
	switch (params_format(params)) {
	case SNDRV_PCM_FORMAT_S16_LE:
		data_length = 16;
		bclk_ratio = 50;
		break;
	case SNDRV_PCM_FORMAT_S24_LE:
		data_length = 24;
		bclk_ratio = 50;
		break;
	case SNDRV_PCM_FORMAT_S32_LE:
		data_length = 32;
		bclk_ratio = 100;
		break;
	default:
		return -EINVAL;
	}

	/* If bclk_ratio already set, use that one. */
	if (dev->bclk_ratio)
		bclk_ratio = dev->bclk_ratio;

	/*
	 * Clock Settings
	 *
	 * The target frequency of the bit clock is
	 *	sampling rate * frame length
	 *
	 * Integer mode:
	 * Sampling rates that are multiples of 8000 kHz
	 * can be driven by the oscillator of 19.2 MHz
	 * with an integer divider as long as the frame length
	 * is an integer divider of 19200000/8000=2400 as set up above.
	 * This is no longer possible if the sampling rate
	 * is too high (e.g. 192 kHz), because the oscillator is too slow.
	 *
	 * MASH mode:
	 * For all other sampling rates, it is not possible to
	 * have an integer divider. Approximate the clock
	 * with the MASH module that induces a slight frequency
	 * variance. To minimize that it is best to have the fastest
	 * clock here. That is PLLD with 500 MHz.
	 */
	target_frequency = sampling_rate * bclk_ratio;
	clk_src = BCM2708_CLK_SRC_OSC;
	mash = BCM2708_CLK_MASH_0;

	if (bcm2708_clk_freq[clk_src] % target_frequency == 0
			&& bit_master && frame_master) {
		divi = bcm2708_clk_freq[clk_src] / target_frequency;
		divf = 0;
	} else {
		uint64_t dividend;

		if (!dev->bclk_ratio) {
			/*
			 * Overwrite bclk_ratio, because the
			 * above trick is not needed or can
			 * not be used.
			 */
			bclk_ratio = 2 * data_length;
		}

		target_frequency = sampling_rate * bclk_ratio;

		clk_src = BCM2708_CLK_SRC_PLLD;
		mash = BCM2708_CLK_MASH_1;

		dividend = bcm2708_clk_freq[clk_src];
		dividend <<= BCM2708_CLK_SHIFT;
		do_div(dividend, target_frequency);
		divi = dividend >> BCM2708_CLK_SHIFT;
		divf = dividend & BCM2708_CLK_DIVF_MASK;
	}

	/* Clock should only be set up here if CPU is clock master */
	if (((dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBS_CFS) ||
	    ((dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBS_CFM)) {
		/* Set clock divider */
		regmap_write(dev->clk_regmap, BCM2708_CLK_PCMDIV_REG, BCM2708_CLK_PASSWD
				| BCM2708_CLK_DIVI(divi)
				| BCM2708_CLK_DIVF(divf));

		/* Setup clock, but don't start it yet */
		regmap_write(dev->clk_regmap, BCM2708_CLK_PCMCTL_REG, BCM2708_CLK_PASSWD
				| BCM2708_CLK_MASH(mash)
				| BCM2708_CLK_SRC(clk_src));
	}

	/* Setup the frame format */
	format = BCM2708_I2S_CHEN;

	if (data_length >= 24)
		format |= BCM2708_I2S_CHWEX;

	format |= BCM2708_I2S_CHWID((data_length-8)&0xf);

	switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_I2S:
		data_delay = 1;
		break;
	default:
		/*
		 * TODO
		 * Others are possible but are not implemented at the moment.
		 */
		dev_err(dev->dev, "%s:bad format\n", __func__);
		return -EINVAL;
	}

	ch1pos = data_delay;
	ch2pos = bclk_ratio / 2 + data_delay;

	switch (params_channels(params)) {
	case 2:
		format = BCM2708_I2S_CH1(format) | BCM2708_I2S_CH2(format);
		format |= BCM2708_I2S_CH1(BCM2708_I2S_CHPOS(ch1pos));
		format |= BCM2708_I2S_CH2(BCM2708_I2S_CHPOS(ch2pos));
		break;
	default:
		return -EINVAL;
	}

	/*
	 * Set format for both streams.
	 * We cannot set another frame length
	 * (and therefore word length) anyway,
	 * so the format will be the same.
	 */
	regmap_write(dev->i2s_regmap, BCM2708_I2S_RXC_A_REG, format);
	regmap_write(dev->i2s_regmap, BCM2708_I2S_TXC_A_REG, format);

	/* Setup the I2S mode */
	mode = 0;

	if (data_length <= 16) {
		/*
		 * Use frame packed mode (2 channels per 32 bit word)
		 * We cannot set another frame length in the second stream
		 * (and therefore word length) anyway,
		 * so the format will be the same.
		 */
		mode |= BCM2708_I2S_FTXP | BCM2708_I2S_FRXP;
	}

	mode |= BCM2708_I2S_FLEN(bclk_ratio - 1);
	mode |= BCM2708_I2S_FSLEN(bclk_ratio / 2);

	/* Master or slave? */
	switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
	case SND_SOC_DAIFMT_CBS_CFS:
		/* CPU is master */
		break;
	case SND_SOC_DAIFMT_CBM_CFS:
		/*
		 * CODEC is bit clock master
		 * CPU is frame master
		 */
		mode |= BCM2708_I2S_CLKM;
		break;
	case SND_SOC_DAIFMT_CBS_CFM:
		/*
		 * CODEC is frame master
		 * CPU is bit clock master
		 */
		mode |= BCM2708_I2S_FSM;
		break;
	case SND_SOC_DAIFMT_CBM_CFM:
		/* CODEC is master */
		mode |= BCM2708_I2S_CLKM;
		mode |= BCM2708_I2S_FSM;
		break;
	default:
		dev_err(dev->dev, "%s:bad master\n", __func__);
		return -EINVAL;
	}

	/*
	 * Invert clocks?
	 *
	 * The BCM approach seems to be inverted to the classical I2S approach.
	 */
	switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) {
	case SND_SOC_DAIFMT_NB_NF:
		/* None. Therefore, both for BCM */
		mode |= BCM2708_I2S_CLKI;
		mode |= BCM2708_I2S_FSI;
		break;
	case SND_SOC_DAIFMT_IB_IF:
		/* Both. Therefore, none for BCM */
		break;
	case SND_SOC_DAIFMT_NB_IF:
		/*
		 * Invert only frame sync. Therefore,
		 * invert only bit clock for BCM
		 */
		mode |= BCM2708_I2S_CLKI;
		break;
	case SND_SOC_DAIFMT_IB_NF:
		/*
		 * Invert only bit clock. Therefore,
		 * invert only frame sync for BCM
		 */
		mode |= BCM2708_I2S_FSI;
		break;
	default:
		return -EINVAL;
	}

	regmap_write(dev->i2s_regmap, BCM2708_I2S_MODE_A_REG, mode);

	/* Setup the DMA parameters */
	regmap_update_bits(dev->i2s_regmap, BCM2708_I2S_CS_A_REG,
			BCM2708_I2S_RXTHR(1)
			| BCM2708_I2S_TXTHR(1)
			| BCM2708_I2S_DMAEN, 0xffffffff);

	regmap_update_bits(dev->i2s_regmap, BCM2708_I2S_DREQ_A_REG,
			  BCM2708_I2S_TX_PANIC(0x10)
			| BCM2708_I2S_RX_PANIC(0x30)
			| BCM2708_I2S_TX(0x30)
			| BCM2708_I2S_RX(0x20), 0xffffffff);

	/* Clear FIFOs */
	bcm2708_i2s_clear_fifos(dev, true, true);

	return 0;
}



この “bcm2708_i2s_hw_params()” 関数の中身を書き換えることで、I2S出力のBCKの周波数や出力されるオーディオデータフォーマットも変更する事が可能だろう.因みにI2SフォーマットではLRCKの立ち下がりから1BCK分遅れてDATA信号が出力されるが、”data_delay = 1;” がこの遅れを調整する役目のようだ.”data_delay = 0;” にすると、Left Justified(左詰め)形式で出力できる筈だ.同様にちょっと工夫すればPCM1792等のデフォルトフォーマット(標準フォーマット)である、Right justified(右詰め)にも対応可能だろう.


Volumioと自作のDACシステムの組み合わせでもう一つ問題となるのが、Volumioではメディアプレイヤーの再生が止まると、LRCK,BCK,DATA の信号も一緒に停止してしまうことだ.PCM5102のような廉価版のDACではあまり問題にならないのかもしれないが、PCM1792の様なちょっと高級なDACチップではMCKやBCK, LRCKなどは曲が再生されていない間も供給され続けている方が望ましい.曲の再生が終わる度にこれらのクロックが停止すると、DACチップによってはノイズが発生したり、場合によってはDACチップそのものがハングアップしてしまう可能性が高い.”bcm2708-i2s.c”には、クロックを起動したり止めたりする関数も有るようなので、DACに常にクロック信号が供給されるように改造する事も可能かもしれない.


Linuxカーネルの再構築環境を準備して、I2Sサウンドドライバーを書き換えるのは結構大変そうなので、今度時間の有るときに挑戦してみようと思う.




【追記】インターフェース誌 2015年12月号 『自分専用Linux X ラズパイ・オーディオ』


何とタイムリーな事にインターフェース誌の12月号がラズベリーパイのオーディオをカスタマイズするという主旨の企画を特集していたのでとりあえず購入してみた.まだ、ちゃんと読んではいないが、デバイスドライバなどの扱いなどにも触れているようだが、デバイスドライバ廻りの開発の参考にするには、少し掘り下げ方が足りないかな...