allwinnerのhdmi

FreeBSDにHDMIのコードがあり、動かなくなっているようなで調べてみている。

もともとA20(Banana Pi M1)を対象にコーディングされたコードで、A10では動いてなかったものと思われる。

allwinnerのクロックの実装がclkngに切り替わった時に、おそらく推測の上コードは書き換えたが実機ではまったくテストしていなかったものと思われる。これはdtsの設定項目とコードの定義が全くちぐはぐだからである。またdtsの一部はcommitされていない。

小言になるが、コードに対応するdtsファイルがないと、なんのこっちゃになる。FreeBSDではGNU由来のファイルがsys/gnu/dtsにあり、これは直接いじらず、sys/dtsに修正したファイルを置くのが流儀のようである。

AllwinnerのHDMIのサポートは以下のような仕組みになっている。

![ALL_DE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/ee8cf079-4dc8-ab17-8ce4-ad000e8ae059.png)

DEはDisplay Engineの略でFEはFrontEndでBEはBackEndである。機能が別で単独でも使えるし組み合わせても使えるようだ。

TCONはTiming Controllerの略のようです。LCDの描画やHDMIやTVへのデータの転送を行います。User ManualなどでLCDと呼んだりTCONと呼んだりしているところがあって混乱して良くないです。

このチップにはカメラ用のインターフェースもあり、複雑な画像処理を行うことも考えて、FE,BEの仕組みを作りこんだものと思われる。3Dな処理もできるようだ。

Display EngineについてA10のUsers Manualには全く記載がなくA20の方にはある。おそらくA10のリリースの時にはドキュメントが間に合わなかったのではないかと思われる。

A10のローコスト版のA13にはHDMIがなく、おそらくLCDコントローラーだけで、タブレット市場向けだったものと思われます。

H3などはDisplay Engine2になっているようです。

FE,BEをどう使うかはユーザーが自由に選択できる。FreeBSDでの実装は単純なframebufferとしてDE_BE0とLCD0とHDMIを使った実装になっていた。

AllwinnerのチップはHDMIの他にタブレットなどのLCD接続やTV接続をサポートしている。

u-bootのコードを眺めていて気がついたのだが、FreeBSDの現在のコードはA10ではそのままは使えないようだ。これはA10のDE_BE0のfifoが足りないためu-bootやLinuxではDE_FE0と組み合わせて使ってるようだ。

A20(sun7i)

![ALL_I7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/2a1baf3d-e509-8ccf-6336-d0dfb314ab7a.png)

A10(sun4i)

![ALL_I4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/c03f32fa-4ad7-3349-61f2-1b08cb6b31a1.png)



A10のクロックは以下のようになっている。

![ALL_CLK.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/fe419a45-05ce-abfb-7c95-4b90fe1c7d57.png)

HDMIとLCD0はPLL3を親として、DE_BE0はPLL5を親とするようになっていた。

PLL7を使うこともできて、PLL3がVideo0でPLL7がVideo1となっている。

~~PLL3はOSC24Mの8分周の3MHzの9倍から127倍すなわち27MHz~381MHzの設定ができる。これを使い、HDMIの148.5Hzにするには、PLL3を222MHzにしてこれの2倍を使い3分週すると148MHzが一番近い値になるようです。~~

u-bootの設定値を確認していて気がついたのですが、このロジックはバグってます。3MHzの99倍で297MHzを作ってその半分でちょうど148.5MHzになります。よくよく考えてみると、汎用のクロックならまだしもVideoとなってるクロックからHDMIで一番よく使うクロックが作れないわけないですよね。

これを計算しているロジック（バグあり）は古いコード(a10_clk.c)のここになります。

```c
#define TCON_PLL_WORST          1000000
#define TCON_PLL_N_MIN          1
#define TCON_PLL_N_MAX          15
#define TCON_PLL_M_MIN          9
#define TCON_PLL_M_MAX          127
#define TCON_PLLREF_SINGLE      3000    /* kHz */
#define TCON_PLLREF_DOUBLE      6000    /* kHz */
#define TCON_RATE_KHZ(rate_hz)  ((rate_hz) / 1000)
#define TCON_RATE_HZ(rate_khz)  ((rate_khz) * 1000)
#define HDMI_DEFAULT_RATE       297000000
#define DEBE_DEFAULT_RATE       300000000

static void
calc_tcon_pll(int f_ref, int f_out, int *pm, int *pn)
{
        int best, m, n, f_cur, diff;

        best = TCON_PLL_WORST;
        for (n = TCON_PLL_N_MIN; n <= TCON_PLL_N_MAX; n++) {
                for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) {
                        f_cur = (m * f_ref) / n;
                        diff = f_out - f_cur;
                        if (diff > 0 && diff < best) {
                                best = diff;
                                *pm = m;
                                *pn = n;
                        }
                }
        }
}

int
a10_clk_tcon_activate(unsigned int freq)
{
        struct a10_ccm_softc *sc;
        int m, n, m2, n2, f_single, f_double, dbl, src_sel;

        sc = a10_ccm_sc;
        if (sc == NULL)
                return (ENXIO);

        m = n = m2 = n2 = 0;
        dbl = 0;

        calc_tcon_pll(TCON_PLLREF_SINGLE, TCON_RATE_KHZ(freq), &m, &n);
        calc_tcon_pll(TCON_PLLREF_DOUBLE, TCON_RATE_KHZ(freq), &m2, &n2);

        f_single = n ? (m * TCON_PLLREF_SINGLE) / n : 0;
        f_double = n2 ? (m2 * TCON_PLLREF_DOUBLE) / n2 : 0;

        if (f_double > f_single) {
                dbl = 1;
                m = m2;
                n = n2;
        }
        src_sel = dbl ? CCM_LCD_CH1_SRC_SEL_PLL3_2X : CCM_LCD_CH1_SRC_SEL_PLL3;

        if (n == 0 || m == 0)
                return (EINVAL);

        /* Set PLL3 to the closest possible rate */
        a10_clk_pll3_set_rate(TCON_RATE_HZ(m * TCON_PLLREF_SINGLE));

        /* Enable LCD0 CH1 clock */
        ccm_write_4(sc, CCM_LCD0_CH1_CLK,
            CCM_LCD_CH1_SCLK2_GATING | CCM_LCD_CH1_SCLK1_GATING |
            (src_sel << CCM_LCD_CH1_SRC_SEL_SHIFT) | (n - 1));

        return (0);
}
```

バグは

```
if (diff > 0 && diff < best) {
```

が

```
if (diff >= 0 && diff < best) {
```

のようです。

どうもこの仕組みはclkngには無いような気がします。

FreeBSDでの実装はまずa10_html.cが設定を行い、次にa10_fb.cでhdmiのイベントを拾えるようにして、起動時に飛んでくるEDIDイベントで解像度を選択しBE,LCD,HDMIの設定をする。FrameBufferのメモリはallocして物理アドレスを取得してBEに設定している。

```
a10hdmi0: <Allwinner HDMI TX> mem 0x1c16000-0x1c16fff irq 17 on simplebus0
fb0: <Allwinner Framebuffer> mem 0x1e60000-0x1e6ffff,0x1c0c000-0x1c0cfff on ofwbus0
fb0: using 1920x1080
```

u-bootのHDMIのコードは

drivers/video/sunxi

にあり、Linuxでも同じパスにファイルがあります。

u-bootのヘッダーは

arch/arm/include/asm/arch-sunxi

にあります。

u-bootでA10で使われているファイルはこれらです。

lcdc.c
simplefb_common.c
sunxi_display.c
tve_common.c

他にもファイルがありますが、他のチップの場合に使われるものと思われます。

ccmの処理は以下のようになっています。

pll3を300000000Hzに
hdmi_clk_cfgの親をCCM_HDMI_CTRL_PLL3にset
ahb_gate1のAHB_GATE_OFFSET_HDMIをset
hdmi_clk_cfgのCCM_HDMI_CTRL_GATEをset

hdmi_clk_cfgのCCM_HDMI_CTRL_DDC_GATEをset
EDIDを受け取ったら
hdmi_clk_cfgのCCM_HDMI_CTRL_DDC_GATEをclear

lcd0_ch0_clk_cfgのCCM_LCD_CH0_CTRL_RSTをset
ahb_gate1のAHB_GATE_OFFSET_LCD0をset

ahb_gate1のAHB_GATE_OFFSET_DE_BE0をset
sun4i以外の場合
dram_clk_gateのCCM_DRAM_GATE_OFFSET_DE_BE0をset
be0_clk_cfgを300000000Hzに

sun4iの場合
ahb_gate1のAHB_GATE_OFFSET_DE_FE0をset
dram_clk_gateのCCM_DRAM_GATE_OFFSET_DE_FE0をset
fe0_clk_cfgを300000000Hzに

dotclockの近似値を得るためにpll3を設定
lcd0_ch1_clk_cfgをdotclockに設定

sun4iのA10とsun7iのA20はBE/FE以外はさほど違わないがsun6iのA31はちょっと違っているみたいだ。

allwinnerのコードにはチップごとのifdefなどが少なく好感がもてます。

 2週間くらいトライ＆エラーをやって、u-bootが初期化してそのままOSをブートしていたのでFEに設定されているframebufferのアドレスを変える方法で、A10で表示できるようになりました。

![写真（2020-03-10 13.18）.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/9f2cd3ed-fd75-7410-3e68-15bd433a43ef.jpeg)

u-bootからHDMIの初期化を抜いてみて、完全な初期化を作ることも考えたのですが、clkngがバグってるので、これを直さないといけないので、ちゃんと直してレビュー出しても、いろいろ難癖つけられそうなので、安易にu-bootの初期化を流用しました。