Jetsonのカメラドライバを書く[I2C編]

# はじめに

前の記事: [Jetsonカメラドライバを書く[準備編]](https://qiita.com/uzuna/items/d80418f910fe18e59645)
次の記事: [Jetsonカメラドライバを書く[RAW撮影編]](https://qiita.com/uzuna/items/05c169c91460e60fecea)

前の記事ではI2Cのスレーブアドレスが見えるところまでやりました。
この記事ではI2Cでデータを読んでデバイスを確認した後、ドライバでデバイスを検出するところまでやります。

# I2Cでデータを読んで見る

前回は期待通りのスレーブアドレスが見えました。ですがスレーブアドレスは7bitしかないですし、実は違うデバイスという可能性もあります。
念の為I2Cでデータを読んで期待するデバイスかどうか確認してみましょう。

## IMX708のI2Cプロトコル

IMX219のデータシートを見ると`3-1-1 Communication Protocol`にプロトコルについての情報が載っています。
IMX219では16bitのアドレスと8bitのデータとあり、RaspberryPiのIMX708のソースを見るとレジスタ設定用の構造体が定義されており、同じ仕様であると分かります。

```imx708.c
struct imx708_reg {
	u16 address;
	u8 val;
};
```

また、特定のアドレスに決まったCHIP_IDが書き込まれているようで、アドレスと期待される値について定義がありました。
実際にこの通りかを確認しましょう。

```c
/* Chip ID */
#define IMX708_REG_CHIP_ID		0x0016
#define IMX708_CHIP_ID			0x0708
```

IMX219のデータシートにはレジスタアドレス[15:8]と[7:0]を続けて書くと、対象のアドレスのデータでてくるとあります。続けてREADで次のアドレスのデータが読めるので、i2c-toolsのCLIで命令を再現すると以下のようになります。

```
$ i2cset -y 10 0x1a 0x00 0x16 ; i2cget -y 10 0x1a ; i2cget -y 10 0x1a
0x07
0x08
```

期待値である0x07, 0x08が読めました。IMX708で間違いなさそうですね。

# ドライバでカメラを検出する

ドライバには、ここまでやったような期待するデータが読めるかどうかを実装します。
スレーブアドレスが同じだけのデバイスを繋げて誤判定してしまったら困りますからね。
一連の流れをi2c_deviceのprobe functionに書きましょう。

ドライバと動作しているカーネルバージョンが違うと不具合に繋がるため、対象のJetsonと同じバージョンでビルドしなければなりません。
そのためにL4T35.3.1のソースやツールチェインを整えてビルドを通します。
この後、ドライバを構成する2要素、IMX708に合わせた**デバイスツリー**と**カーネルモジュール**を作ります。
以下のような流れとなります。

1. ビルド環境の準備
1. デバイスツリーの追加
1. カーネルモジュールの追加
1. Jetsonに適用してログを確認

## ビルド環境の準備

L4TはBSP(Board Support Package)のバイナリだけでなく、BSPのソースコード、そしてビルドのためのツールチェインも公開しています。
それらをセットアップする必要があるので、まずは[DeveloperGuideのKernelCustomization](https://docs.nvidia.com/jetson/archives/r35.3.1/DeveloperGuide/text/SD/Kernel/KernelCustomization.html)にあるガイドに従って用意をします。

しますが...ちょっと配置が大変なのでここにある出来合いのMakefileで時短をします。
作業はJetsonではなくお使いの(おそらくx86と思われる)Linux環境で問題ありません。
`make build`でビルドまで通るはずです。環境によりますが初回のビルドには10分以上はかかると思うのでビルドが出来るまでお茶して待ちましょう。

```Makefile
MAJOR:=35
MINOR:=3.1
L4T_SOURCE_TBZ:=public_sources.tbz2
KERNEL_TBZ:=kernel_src.tbz2
SOURCE_ADDR:=https://developer.download.nvidia.com/embedded/L4T/r${MAJOR}_Release_v${MINOR}/sources
TOOLCHAIN_TAR:=aarch64--glibc--stable-final.tar.gz

PWD:=$(shell pwd)
L4T_SOURCE_DIR:=Linux_for_Tegra/source/public
TOOLCHAIN_DIR:=${PWD}/l4t-gcc
KERNEL_DIR:=${PWD}/${L4T_SOURCE_DIR}/kernel/kernel-5.10
KERNEL_OUT:=${PWD}/kernel_out

export ARCH=arm64
export CROSS_COMPILE=${TOOLCHAIN_DIR}/bin/aarch64-buildroot-linux-gnu-

.PHONY: build extract toolchain

build: extract toolchain
	mkdir -p ${KERNEL_OUT}
	make -C ${KERNEL_DIR} O=${KERNEL_OUT} tegra_defconfig
	make -C ${KERNEL_DIR} O=${KERNEL_OUT} -j8 dtbs modules

# extract kernel source and toolchain
extract: ${KERNEL_DIR}

${KERNEL_DIR}: ${L4T_SOURCE_DIR}/${KERNEL_TBZ}
	cd ${L4T_SOURCE_DIR} && tar -xjf ${KERNEL_TBZ}
	touch ${KERNEL_DIR}

${L4T_SOURCE_DIR}/${KERNEL_TBZ}: ${L4T_SOURCE_TBZ}
	tar -xjf ${L4T_SOURCE_TBZ} ${L4T_SOURCE_DIR}/${KERNEL_TBZ}
	touch ${L4T_SOURCE_DIR}/${KERNEL_TBZ}

${L4T_SOURCE_TBZ}:
	wget -c ${SOURCE_ADDR}/${L4T_SOURCE_TBZ}

toolchain: ${CROSS_COMPILE}gcc
${CROSS_COMPILE}gcc: ${TOOLCHAIN_DIR}/${TOOLCHAIN_TAR}
	cd ${TOOLCHAIN_DIR} && tar xf ${TOOLCHAIN_TAR}
	touch ${CROSS_COMPILE}gcc

${TOOLCHAIN_DIR}/${TOOLCHAIN_TAR}:
	mkdir -p ${TOOLCHAIN_DIR}
	cd ${TOOLCHAIN_DIR} && wget https://developer.download.nvidia.com/embedded/L4T/bootlin/${TOOLCHAIN_TAR}
```

## デバイスツリーの追加

ビルドが出来たらまずはデバイスツリーを書き換えていきます。
デバイスツリーが初見の方は各自で調べていただくとして、ここでは**カーネルモジュールを正しく機能させるために動作環境のハードウェア構成を記述するもの**とご理解ください。

### Jetsonのデバイスツリーの構成

IMX708を動かすには以下の4つのデバイスツリーの追加が必要です。

1. IMX708の制御に関わるデバイスツリーの追加
1. 電源に関する設定の追加
1. Orin Nano開発者キットのデバイスツリーへの追加
1. IMX708オーバーレイの作成

ただ1つのデバイスのためにしては多い?という感じもしますね。
Jetsonのデバイスツリーの構成がどうなっているかを見てみましょう。

#### 開発者キットのデバイスツリー

まずはOrin Nano開発者キットをflashする時に使うデバイスツリー`tegra234-p3767-0003-p3768-0000-a0.dts`を見ます。
書いてあるのはファイル名、モデル名で、ほとんどの設定はincludeしています。

```hardware/nvidia/platform/t23x/p3768/kernel-dts/tegra234-p3767-0003-p3768-0000-a0.dts
/*
 * Top level DTS file for CVM:P3767-0003/P3767-0005 and CVB:P3768-0000.
 *
 * Copyright (c) 2021-2023, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 */

/dts-v1/;

#include "tegra234-p3767-0000-p3768-0000-a0.dts"
#include <t234-common-cvm/tegra234-cpufreq-pair-cooling.dtsi>
#include <t234-common-cvm/tegra234-p3767-pcie-max-speed-gen3.dtsi>

/ {
	nvidia,dtsfilename = __FILE__;
	nvidia,dtbbuildtime = __DATE__, __TIME__;
	compatible = "nvidia,p3768-0000+p3767-0003", "nvidia,p3768-0000+p3767-0005",
			"nvidia,p3767-0003", "nvidia,p3767-0005",
			"nvidia,tegra234", "nvidia,tegra23x";
	model = "NVIDIA Orin Nano Developer Kit";
};
```

一番上にあるincludeを見てみると非常によく似た名前の`tegra234-p3767-0000-p3768-0000-a0.dts"`というファイルをincludeしています。こちらも見てみましょう。

```hardware/nvidia/platform/t23x/p3768/kernel-dts/tegra234-p3767-0000-p3768-0000-a0.dts
/dts-v1/;
/*
 * Top level DTS file for CVM:P3767-0000 and CVB:P3768-0000.
 *
 * Copyright (c) 2021-2022, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 */

/dts-v1/;
#include "tegra234-dcb-p3767-0000-dp.dtsi"
#include <tegra234-soc/mods-simple-bus.dtsi>
#include <t234-common-cvm/tegra234-thermal.dtsi>
#include <t234-common-cvm/tegra234-cpuidle.dtsi>
#include <t234-common-cvm/tegra234-thermal-cooling.dtsi>
#include <t234-common-cvm/tegra234-thermal-userspace-alert.dtsi>
#include "cvm/tegra234-p3767-0000.dtsi"
#include "cvb/tegra234-p3768-0000-a0.dtsi"

/ {
	nvidia,dtsfilename = __FILE__;
	nvidia,dtbbuildtime = __DATE__, __TIME__;
	compatible = "nvidia,p3768-0000+p3767-0000", "nvidia,p3767-0000", "nvidia,tegra234", "nvidia,tegra23x";
	model = "NVIDIA Orin NX Developer Kit";
};
```

これも殆どがincludeです。
なんとなく名前からdpはDisplayPort、busはメディア関係のBus、cvmというディレクトリとcvbというのがあるというのがわかります。
更にコメントの先頭あたりを`CVM:P3767-0000 and CVB:P3768-0000` などと書いてありますね。
これがヒントになりそうです

#### CVBとCVM

Jetson Orin Nano開発者キットは2つのハードウェアで構成されています。
CVMとはOrin Nano 8GBのモジュールのことで、CVBとはリファレンスキャリアボードのことです。
つまりこのデバイスツリーは2つの個別に書かれたハードウェアの記述を組み合わせて構成されています。
PartNoと製品名の対応付は[ReleaseNote](https://docs.nvidia.com/jetson/archives/r35.3.1/ReleaseNotes/Jetson_Linux_Release_Notes_r35.3.1.pdf)などに書いてあり、抜粋すると以下のとおりです。
同じディレクトリ内に似たようなファイルがあるのは、これらの組み合わせ毎にデバイスツリーが必要だからですね。
そしてOrin Nano開発者キットは8GBのモジュール(P3767-0005)とOrinNanoのキャリアボード(P3768-0000)なので`tegra234-p3767-0003-p3768-0000-a0.dts`という名前になります(0005は0003と互換性があるので0003で良いようです)

|module|cvm|
| --- | --- |
| Jetson Orin NX 16GB-DRAM | P3767-0000 |
| Jetson Orin NX 8GB-DRAM | P3767-0001 |
| Jetson Orin Nano 8GB-DRAM | P3767-0003 |
| Jetson Orin Nano 4GB-DRAM | P3767-0004 |
| Jetson Orin Nano 8GB-DRAM developer kit module with SD Card | P3767-0005 |

|CarrierBord|cvb|
| --- | --- |
| Jetson Xavier NX reference carrier board | p3509-0000 |
| Jetson Orin Nano reference carrier board | P3768-0000 |

これらすべての組み合わせで個別に定義書くのは冗長ですから、カメラを繋ぐコネクタがあるキャリアボードに対して定義を書き、実際に使う組み合わせの時に`include`するのが良さそうです。
なのでIMX708に関するツリーは`CVB:P3768-0000`=キャリアボードに対して追加していきましょう。
既にIMX219とIMX477の定義があるので、これらをコピーして作ります。

#### デバイスツリーオーバーレイ

ところでキャリアボードにはカメラポートが2つしかありません。
どうやって異なるカメラを区別するのでしょうか?自動判定してくれるのでしょうか?
これらは私達が**明示的にデバイスツリーに有効か無効か**を書いて指定することができます。

現在の設定を見てみましょう。
起動中のJetsonの`/proc/device-tree`から現在の設定を見ることが出来ます。
デバイスツリーを変えていなければimx219とimx477のものがあり、imx477だけ`status=disabled`となっているはずです。
imx219は`status=enabled`かstatusの記述がないはずです。この場合imx219の設定が有効となっています。

```
$ ls /proc/device-tree/cam_i2cmux/i2c@0/rbpcv2_imx219_a@10/
compatible  mode0  mode2  mode4  phandle     physical_w  reg          sensor_model
devnode     mode1  mode3  name   physical_h  ports       reset-gpios  use_sensor_mode_id

$ cat /proc/device-tree/cam_i2cmux/i2c@0/rbpcv3_imx477_a@1a/status
disabled
```

確認のためにdmesgを見てみましょう。imxでgrepすると以下のようにimx219のセンサー設定に失敗というエラーが出ているはずです。

```
sudo dmesg | grep imx
[   13.397461] imx219 9-0010: tegracam sensor driver:imx219_v2.0.6
[   13.409396] imx219 9-0010: imx219_board_setup: error during i2c read probe (-121)
[   13.426280] imx219 9-0010: board setup failed
[   13.438194] imx219: probe of 9-0010 failed with error -121
```

開発者キットのカメラポートや40Pinは、ユーザーがそれぞれ必要なものを繋ぐためのポートなので、NVIDIAは何を繋ぐか知ることの出来ない場所です。
これらのために毎度ツリーをビルドするのは大変なので、後から書き換えるためのオーバーレイという仕組みがあります。
ツリーに基本的なことが書いてあるなら使わないツリーを`status="disabled"`にし、使うものを`status="okay"`で上書きすれば、ユーザーが自身の環境に合わせて容易に変更できます。
この仕組みをラップしてユーザーフレンドリーにしているのが`JetsonIO`になります。
ここにはIMX477のオーバーレイが用意してあるため、IMX708も同じように書きましょう。

### IMX708の制御に関わるデバイスツリーの追加

まずはIMX708自体に関するツリーからです。
`hardware/nvidia/platform/t23x/p3768/kernel-dts/cvb/tegra234-camera-rbpcv3-imx477.dtsi`を探し、これを`tegra234-camera-rbpcv3-imx708.dtsi`として別名保存します。
とりあえず`imx477`は`imx708`に全て置換しましょう。
現時点で注意すべきはI2CのスレーブアドレスがIMX708向けになっていること、`compatible`がこの後に書くドライバと対応していることです。
IMX477はIMX708と同じく`0x1a`がスレーブアドレスなので、前者の変更は不要です。
後者の`compatible`はRaspberryPiのドライバに倣い`sony,<モジュール名>`としましょう。

```diff
- compatible = "ridgerun,imx477";
+ compatible = "sony,imx708";
```

このツリーにはSensor-CSI-VIのルーティングに関する情報も書かれていますが。別途解説するので今はそういうのがあるということだけ知っておいてください。

### 電源に関する設定の追加

カメラ制御の次は電源に関する設定です。

`hardware/nvidia/platform/t23x/p3768/kernel-dts/cvb/tegra234-p3768-camera-rbpcv3-imx477.dtsi`も同じく**imx708**で別名保存します。

後々解説するので今は`imx477`を`imx708`に置換をしたらこちらは完成です。

### OrinNano向けのデバイスツリー修正の追加

最後はIMX708の制御と電源のデバイスツリーを、キャリアボード全体を構成するツリーに追加します。
`hardware/nvidia/platform/t23x/p3768/kernel-dts/cvb/tegra234-p3768-0000-a0.dtsi`がキャリアボードのツリーです。

先程作った電源に関する設定の追加`tegra234-p3768-camera-rbpcv3-imx477.dtsi`をincludeします。
デフォルトではIMX219が有効になって欲しいので、includeの順番はIMX219よりも先にします。

```diff
#include "tegra234-p3768-audio.dtsi"
+ #include "tegra234-p3768-camera-rbpcv3-imx708.dtsi"
#include "tegra234-p3768-camera-rbpcv3-imx477.dtsi"
#include "tegra234-p3768-camera-rbpcv2-imx219.dtsi"
```

### ビルドチェック

これでツリーへの追加は出来ました。期待どおりにビルドが出来るか確認しましょう。
`make build`をしたら、デバイスツリーがビルドされるはずです。

ビルドしたデバイスツリー(dtb)の中身を確認するため、`sudo apt install device-tree-compiler`でデバイスツリーコンパイラを入れます。
先の`Makefile`に逆コンパイルのために以下を追加して`make check_dt`をしましょう。
`tegra234-p3767-0003-p3768-0000-a0.dts`というファイルが生成されているはずです。`imx708`で検索して、設定が追加されているようであればOKです。
追加されていない場合、includeの設定ミスか置換忘れと思うので確認してください。

```Makefile
BASE_NAME:=tegra234-p3767-0003-p3768-0000-a0
BASE_DTB:=${KERNEL_OUT}/arch/arm64/boot/dts/nvidia/${BASE_NAME}.dtb

check_dt:
	dtc -I dtb -O dts -o ${BASE_NAME}.dts ${BASE_DTB}
```

### IMX708オーバーレイの作成

オーバーレイは`hardware/nvidia/platform/t23x/p3768/kernel-dts/tegra234-p3767-camera-p3768-imx477-dual.dts`を参考にして書きます。
名前のごとく、CAM0,CAM1にIMX477を設定するためのオーバーレイですが、今回はCAM1ポートのみなので少し変更を加えます。
CAM1のIMX708を有効にしてIMX219を無効にします。
また、Sensor-CSI間のルーティングは`remote-endpoint`で相互参照になっているため書き換えが必要です。
CSI-VI間は変更不要です。

`tegra234-p3767-camera-p3768-imx708.dts`を作って以下のようにします。

```tegra234-p3767-camera-p3768-imx477-dual.dts
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Jetson Device-tree overlay for IMX708 CAM1 port
 *
 * Copyright (c) 2021-2023 NVIDIA CORPORATION. All rights reserved.
 *
 */

/dts-v1/;
/plugin/;

#include <dt-common/jetson/tegra234-p3767-0000-common.h>

/ {
	overlay-name = "Camera IMX708-C";
	jetson-header-name = "Jetson 24pin CSI Connector";
        compatible = JETSON_COMPATIBLE_P3768;

	/* Disable IMX219-C */
	fragment@0 {
		target = <&imx219_cam1>;
		__overlay__ {
			status = "disabled";
		};
	};
    /* Enable IMX708-C */
	fragment@1 {
		target = <&imx708_cam1>;
		__overlay__ {
			status = "okay";
		};
	};
    /* route Sensor to NVCSI */
	fragment@2 {
		target = <&rbpcv3_imx708_csi_in1>;
		__overlay__ {
			status = "okay";
			port-index = <2>;
			bus-width = <2>;
			remote-endpoint = <&rbpcv3_imx708_out1>;
		};
	};
    /* route NVCSI TO VI */
	fragment@3 {
		target = <&rbpcv3_imx708_csi_out1>;
		__overlay__ {
			status = "okay";
			remote-endpoint = <&rbpcv3_imx708_vi_in1>;
		};
	};
};
```

ファイルをビルド対象にするため、同じディレクトリにあるMakefileにターゲットを追加します。
オーバーレイなのでDTB**O**ですね。
これでもう一度`make build`しましょう。

```diff
dtbo-$(BUILD_ENABLE) += tegra234-p3767-camera-p3768-imx477-dual-4lane.dtbo
+ dtbo-$(BUILD_ENABLE) += tegra234-p3767-camera-p3768-imx708.dtbo
dtbo-$(BUILD_ENABLE) += tegra234-p3767-overlay.dtbo
```

### デバイスツリーの変化確認

先のデバイスツリーの追加でIMX708のノードが増えているもの、まだ無効の状態です。
オーバーレイを適用して初めて有効になるため、期待通りに有効に出来るかオーバーレイの確認をしましょう
先程のMakefileの追加分を以下のように書き換えて`make check_overlay`でオーバーレイ前後の差分を見ます。

```Makefile
BASE_NAME:=tegra234-p3767-0003-p3768-0000-a0
CAMERA_NAME:=tegra234-p3767-camera-p3768-imx708
BASE_DTB:=${KERNEL_OUT}/arch/arm64/boot/dts/nvidia/${BASE_NAME}.dtb
CAMERA_DTBO:=${KERNEL_OUT}/arch/arm64/boot/dts/nvidia/${CAMERA_NAME}.dtbo

check_dt:
	dtc -I dtb -O dts -o ${BASE_NAME}.dts ${BASE_DTB}

check_overlay:
	fdtoverlay -i ${BASE_DTB} -o custom.dtb ${CAMERA_DTBO}
	dtc -I dtb -O dts -o custom.dts custom.dtb
	diff ${BASE_NAME}.dts custom.dts > diff.txt || true
```

デバイスツリーに問題なければ差分が`diff.txt`というファイルがあるはずです。
もしエラーが出る場合は、ファイルが生成できていないか、ツリーのシンボル名が間違っているなどが考えられます。
エラー文を見て対応してください。
すべてが期待通りなら以下のような差分が出力されます(ビルド日付はもちろん違うので省略してます)。

IMX708のツリーがokayになったほか`vi_in`の`remote-endpoint`がIMX708のものに変わっています。

```diff
11728a11729
> 							status = "okay";
11731c11732
< 							remote-endpoint = <0x287>;
---
> 							remote-endpoint = <0x492>;
11740a11742
> 							status = "okay";
14548c14550
< 				status = "disabled";
---
> 				status = "okay";
14754a14757
> 				status = "disable";
```

これでデバイスツリーの作業は完了です。

## カーネルモジュールの追加

デバイスツリーにIMX708が繋がっていることを書きました。
このデバイスツリーと対になるIMX708のカーネルモジュールを追加します。
こちらもデバイスツリーのときと同じく、IMX477をベースに書き換えていきましょう。

まずは以下の2つのファイルのコピーをimx708の名前でつくります。

`kernel/nvidia/drivers/media/i2c/imx477_mode_tbls.h`
`kernel/nvidia/drivers/media/i2c/nv_imx477.c`

今回もまた雑に`477`を`708`に置換しちゃいましょう。`IMX`と`imx`の表記揺れあるので数字だけ置換します。
ついでにログが出るようにデバッグフラグも立てておきます。
imx477は定数をヘッダファイルに書いて公開してましたが必要ではないので作りません。消します。

```diff
 */
+ #define DEBUG 1

#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>

#include <media/tegra_v4l2_camera.h>
#include <media/tegracam_core.h>
- #include <media/imx708.h>
```

ここからIMX708に無い実装を削ったり、設定を合わせていきます。

### デバイスツリーとの名前合わせ

モジュールロード時に適切なツリーの情報がアタッチされるようにモジュール名を上のデバイスツリーで設定した名前に変更します。

```diff
static const struct of_device_id imx708_of_match[] = {
- 	{.compatible = "ridgerun,imx708",},
+ 	{.compatible = "sony,imx708",},
	{},
};
```

### imx708_probeの書き換え

前述の通りI2Cデバイスはロード時にprobe関数を実行してデバイスの検知を行うため、この経路だけはエラー無く実行できるようにします。
制御は本来ならセンサーのデータシートを見ながら作っていくのですが、今回はないのでRaspberryPiのソースコード`imx708.c`を頼りに作業します。

置換して直後のprobe関数の流れを追うと以下の様になっています。

1. `imx708_probe`
    1. `imx708_board_setup`
        1. `imx708_power_on`
            1. IDを読んでモデルを確認
            1. `imx708_get_fine_integ_time`

一方RaspberryPiのIMX708の実装を見るとI2Cを触っているのは以下になっています

1. `imx708_probe`
    1. `imx708_power_on`
    1. `imx708_identify_module`

基本的には同じで`power_on`と`imx708_identify_module`の処理あれば良さそうです。

#### power_on

RaspberryPiの`power_on`では`reset_gpio`を上げてモジュールの初期化を`IMX708_XCLR_MIN_DELAY_US`ほど待つ処理になっています。

```imx708.c
/* Power/clock management functions */
static int imx708_power_on(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct imx708 *imx708 = to_imx708(sd);
	int ret;

	ret = regulator_bulk_enable(IMX708_NUM_SUPPLIES,
				    imx708->supplies);
	if (ret) {
		dev_err(&client->dev, "%s: failed to enable regulators\n",
			__func__);
		return ret;
	}

	ret = clk_prepare_enable(imx708->xclk);
	if (ret) {
		dev_err(&client->dev, "%s: failed to enable clock\n",
			__func__);
		goto reg_off;
	}

	gpiod_set_value_cansleep(imx708->reset_gpio, 1);
	usleep_range(IMX708_XCLR_MIN_DELAY_US,
		     IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US);

	return 0;

reg_off:
	regulator_bulk_disable(IMX708_NUM_SUPPLIES, imx708->supplies);
	return ret;
}
```

JetsonのIMX477の実装ではレギュレーターの取得とか色々やっていますが、このカメラモジュールには3.3Vの常時入力しかなく、デバイスツリーにもレギュレーターについては何も書かないのですべてスキップされます。無視していいです。
必要なのは`reset-gpio`を上げた後の待ち時間の変更だけです。RawpberryPiの実装に合わせます。

```diff
if (pw->reset_gpio) {
    if (gpio_cansleep(pw->reset_gpio))
        gpio_set_value_cansleep(pw->reset_gpio, 1);
    else
        gpio_set_value(pw->reset_gpio, 1);
}

- usleep_range(300000, 300100);
+ usleep_range(8000, 9000);

pw->state = SWITCH_ON;
```

#### imx708_identify_module

次にIDです。`nv_imx708.c`では特に関数は別れておらず`imx708_board_setup`の中でやってます。
置換直後は`IMX708_MODEL_ID_ADDR_*`という存在しない定数を読んでIDを確認するようなコードになっています。
IMX477の時にあった設定を置換しただけだからですね。
この記事の最初に確認したとおり`0x0016`にアクセスしたらCHIP_ID`0x0708`が得られます。
16bitアドレスに8bitデータなのでなので`0x0016`が`0x07`で、次のアドレスが`0x08`ですね。
この通りに書き換えます。

```diff
#include <media/tegra_v4l2_camera.h>
#include <media/tegracam_core.h>

#include "../platform/tegra/camera/camera_gpio.h"
#include "imx708_mode_tbls.h"

#define IMX708_SENSOR_INTERNAL_CLK_FREQ   840000000
+ #define IMX708_REG_CHIP_ID		0x0016
```

```diff
/* Probe sensor model id registers */
- err = imx708_read_reg(s_data, IMX708_MODEL_ID_ADDR_MSB, &reg_val[0]);
+ err = imx708_read_reg(s_data, IMX708_REG_CHIP_ID, &reg_val[0]);
if (err) {
    dev_err(dev, "%s: error during i2c read probe (%d)\n",
        __func__, err);
    goto err_reg_probe;
}
- err = imx708_read_reg(s_data, IMX708_MODEL_ID_ADDR_LSB, &reg_val[1]);
+ err = imx708_read_reg(s_data, IMX708_REG_CHIP_ID+1, &reg_val[1]);
if (err) {
    dev_err(dev, "%s: error during i2c read probe (%d)\n",
        __func__, err);
    goto err_reg_probe;
}

- if (!((reg_val[0] == 0x00) && reg_val[1] == 0x00))
+ if (!((reg_val[0] == 0x07) && reg_val[1] == 0x08))
    dev_err(dev, "%s: invalid sensor model id: %x%x\n",
        __func__, reg_val[0], reg_val[1]);
```

### 関係のないコードの削除

`imx708_get_fine_integ_time`はIMX477固有のようなのでまるごと削除します。
これでprobeの関数は通るはずです。

その他、IMX708では使わない定数を参照だったり、IMX477とは互換性がない制御があります。
ビルドを通すためにもprobeとは関係ないコードは削除をしましょう

現在値の取得関係は不要なので消します。

```diff
- static inline void imx708_get_frame_length_regs(imx708_reg * regs,
- 						u32 frame_length)
- {
- 	regs->addr = IMX708_FRAME_LENGTH_ADDR_MSB;
- 	regs->val = (frame_length >> 8) & 0xff;
- 	(regs + 1)->addr = IMX708_FRAME_LENGTH_ADDR_LSB;
- 	(regs + 1)->val = (frame_length) & 0xff;
- }
- 
- static inline void imx708_get_coarse_integ_time_regs(imx708_reg * regs,
- 						     u32 coarse_time)
- {
- 	regs->addr = IMX708_COARSE_INTEG_TIME_ADDR_MSB;
- 	regs->val = (coarse_time >> 8) & 0xff;
- 	(regs + 1)->addr = IMX708_COARSE_INTEG_TIME_ADDR_LSB;
- 	(regs + 1)->val = (coarse_time) & 0xff;
- }
- 
- static inline void imx708_get_gain_reg(imx708_reg * reg, u16 gain)
- {
- 	reg->addr = IMX708_ANALOG_GAIN_ADDR_MSB;
- 	reg->val = (gain >> IMX708_SHIFT_8_BITS) & IMX708_MASK_LSB_2_BITS;
- 
- 	(reg + 1)->addr = IMX708_ANALOG_GAIN_ADDR_LSB;
- 	(reg + 1)->val = (gain) & IMX708_MASK_LSB_8_BITS;
- }
```

`imx708_set_group_hold`, `imx708_set_gain`, `imx708_set_frame_rate`, `imx708_set_exposure`も今は映像取得しないので実装は消しましょう。もし呼ばれてもエラーが出ないように`return 0;`とします。

### ビルド対象に追加

これでビルドは通るはずですね。
`nv_imx708.c`をビルドの対象に追加してビルドします。

`kernel/nvidia/drivers/media/i2c/Kconfig`

```diff
config NV_VIDEO_IMX477
        tristate "IMX477 camera sensor support"
	depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
	help
	  This driver supports IMX477 camera sensor from Sony

	  To compile this driver as a module, choose M here: the module
	  will be called imx477.

+ config NV_VIDEO_IMX708
+         tristate "IMX708 camera sensor support"
+ 	depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ 	help
+ 	  This driver supports IMX708 camera sensor from Sony
+ 
+ 	  To compile this driver as a module, choose M here: the module
+ 	  will be called imx708.
+ 
config VIDEO_ECAM
        tristate "ECAM camera sensor support"
        depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
        help
          This driver supports ECAM camera sensor from ECON

          To compile this driver as a module, choose M here: the module
          will be called ar1335.

```

`kernel/nvidia/drivers/media/i2c/Makefile`

```diff
obj-$(CONFIG_NV_VIDEO_IMX477) += nv_imx477.o
+ obj-$(CONFIG_NV_VIDEO_IMX708) += nv_imx708.o
obj-$(CONFIG_NV_VIDEO_IMX219) += nv_imx219.o
```

`kernel_out/source/arch/arm64/configs/tegra_defconfig`

```diff
CONFIG_NV_VIDEO_IMX477=m
+ CONFIG_NV_VIDEO_IMX708=m
CONFIG_NV_VIDEO_IMX268=m
```

これでもう一度ビルドすると`nv_imx708.ko`が生成されているはずです。

```
LD [M]  drivers/media/i2c/nv_imx708.ko
```

## Jetsonに適用してログを確認

デバイスツリーとカーネルモジュールが揃いました。
Jetsonに持ち込んで期待通り動作するか確認しましょう。

### Jetsonへファイルコピー

sshのconfigが設定済みでorin-nanoという名前でアクセスできるものとして進めます。

設定例

```~/.ssh/config
Host orin-nano
    Hostname orin-nano.local
    User jetson
    StrictHostKeyChecking no
    UserKnownHostsFile=/dev/null
    ForwardX11 yes
    ForwardX11Trusted yes
```

ローカルのMakefileに、ビルドしたものをコピーするコマンドと設定を追加します。
orin-nanoに`imx708`のディレクトリを作っておき`make cp`で必要なファイルをコピーしましょう。

```Makefile
#
# remote copy to orin-nano
#
JETSON_TARGET:=orin-nano
WORKDIR:=~/imx708

cp:
	rsync -av ${BASE_DTB} ${JETSON_TARGET}:${WORKDIR}
	rsync -av ${CAMERA_DTBO} ${JETSON_TARGET}:${WORKDIR}
	rsync -av ${KERNEL_OUT}/drivers/media/i2c/nv_imx708.ko ${JETSON_TARGET}:${WORKDIR}
```

orin-nano上では以下のように見えるはずです。

```
jetson@orin-nano:~/imx708$ ls
nv_imx708.ko  tegra234-p3767-0003-p3768-0000-a0.dtb  tegra234-p3767-camera-p3768-imx708.dtbo
```

### DTB切り替えのための準備

ここからはorin-nanoで作業します。

まずはデバイスツリーを書き換えですが、この作業、よく失敗してL4Tが起動しなくなり、敢えなく再flashになることが本当に多いです(n敗)。
`extlinux.conf`を直接触って失敗すると非常に遠回りになるので、`JetsonIO`を使ってリスクを回避します。
適当なコンフィグを適用して `Save and exit without rebooting` してDTBの生成と`extlinux.conf`の書き換えを行ってください。

```sh
sudo /opt/nvidia/jetson-io/jetson-io.py 
```

以下は`IMX219 Dual`を適用した場合です。
`extlinux.conf`に`LABEL JetsonIO`が増えていたらOKです。
`/boot/kernel_tegra234-p3767-0003-p3768-0000-a0-user-custom.dtb`が生成されており、次の起動からこのデバイスツリーが使われるようになりました。
このファイルパスをそのまま使わせてもらいIMX708のデバイスツリーで上書きします。

```/boot/extlinux/extlinux.conf
LABEL JetsonIO
        MENU LABEL Custom Header Config: <CSI Camera IMX219 Dual>
        LINUX /boot/Image
        FDT /boot/kernel_tegra234-p3767-0003-p3768-0000-a0-user-custom.dtb
        INITRD /boot/initrd
        APPEND ${cbootargs} root=PARTUUID=a54531f3-405a-4a3c-a2b2-a12e72df425a rw rootwait rootfstype=ext4 mminit_loglevel=4 console=ttyTCU0,115200 console=ttyAMA0,115200 firmware_class.path=/etc/firmware fbcon=map:0 net.ifnames=0
```

### DTB書き換え

まずは以下のMakefileを追加します。
`OUT_DTB=/boot/kernel_tegra234-p3767-0003-p3768-0000-a0-user-custom.dtb`が`extlinux.conf`の値と同じであることを確認してください。

```Makefile
BASE_DTB:=tegra234-p3767-0003-p3768-0000-a0.dtb
CAMERA_DTB:=tegra234-p3767-camera-p3768-imx708.dtbo
OUT_DTB=/boot/kernel_tegra234-p3767-0003-p3768-0000-a0-user-custom.dtb

.PHONY: overlay
overlay:
	sudo fdtoverlay -i ${BASE_DTB} -o ${OUT_DTB} ${CAMERA_DTB}

PHONY: insmod
insmod:
	sudo insmod nv_imx708.ko
	v4l2-ctl --list-devices
	sudo media-ctl -p -d /dev/media0
	sudo systemctl restart nvargus-daemon

PHONY: rmmod
rmmod:
	sudo rmmod nv_imx708.ko
	sudo systemctl restart nvargus-daemon

.PHONY: setup
setup:
	sudo apt update
	sudo apt install -y nvidia-l4t-jetson-multimedia-api \
	cmake build-essential pkg-config libx11-dev libgtk-3-dev \
	libexpat1-dev libjpeg-dev libgstreamer1.0-dev v4l-utils tree
	cp -r /usr/src/jetson_multimedia_api .
	cd jetson_multimedia_api/argus/ && \
		mkdir -p build && \
		cd build && \
		cmake ..  && \
		make
	cp jetson_multimedia_api/argus/build/apps/camera/ui/camera/argus_camera .

.PHONY: check0
check0:
	v4l2-ctl -d /dev/video0 --set-ctrl bypass_mode=0 --stream-mmap --stream-count=1 --stream-to=test.raw

check_overlay:
	dtc -I dtb -O dts -o ref.dts /boot/dtb/kernel_tegra234-p3767-0003-p3768-0000-a0.dtb
	dtc -I dtb -O dts -o custom.dts ${OUT_DTB}
	diff ref.dts custom.dts > diff.txt || true

# need sudo
.PHONY: trace.enable
trace.enable:
	echo 1 > /sys/kernel/debug/tracing/tracing_on
	echo 30720 > /sys/kernel/debug/tracing/buffer_size_kb
	echo 1 > /sys/kernel/debug/tracing/events/tegra_rtcpu/enable
	echo 1 > /sys/kernel/debug/tracing/events/freertos/enable
	echo 2 > /sys/kernel/debug/camrtc/log-level
	echo > /sys/kernel/debug/tracing/trace

# need sudo
.PHONY: trace.disable
trace.disable:
	echo 0 > /sys/kernel/debug/tracing/events/tegra_rtcpu/enable	
	echo 0 > /sys/kernel/debug/tracing/events/freertos/enable
	echo 0 > /sys/kernel/debug/camrtc/log-level
	echo > /sys/kernel/debug/tracing/trace

.PHONY: trace.log
trace.log:
	sudo cat /sys/kernel/debug/tracing/trace > log.txt
```

`make overlay`をするとDTBが書き換わります。
FDTエラーなどが出る場合はツリーの設定が反映できてないか、名前変更のミスなどと思われるので修正してください。

### ドライバの読み込み確認

書き換えたデバイスツリーを適用するため再起動します。`sudo reboot`
今回は大丈夫だと思いますが、Linuxが起動しない場合は...デバイスと合わないツリーを持ってきてしまった可能性が高いです。シリアルコンソールから原因を探るしかありません。頑張りましょう。
NVMeの場合、書き換えられなくはないですがM.2を挿し直す手間が大きいので基本的には再flashすることになります。レスキュー用のUSBメモリを作っている場合はそちらから起動して`extlinux.conf`を修正することで復帰できたりします。

無事起動したらデバイスツリーの状態を確認します。
`imx708_c@1a`のツリーがありstatusがokayなら大丈夫です。

```
$ ls /proc/device-tree/cam_i2cmux/i2c@1/
'#address-cells'   name   phandle   rbpcv2_imx219_c@10   rbpcv3_imx477_c@1a   rbpcv3_imx708_c@1a   reg  '#size-cells'   status
$ cat /proc/device-tree/cam_i2cmux/i2c@1/rbpcv3_imx708_c@1a/status 
okay
```

`make insmod`でカーネルモジュールを読み込みます
これまでの設定に問題がなければエラー無く読み込まれるはずです。
dmesgでログを確認して`detected imx708 sensor`がでていたらOKです。
無事ドライバがIMX708を認識しました :tada::tada:

```
sudo dmesg

...

[ 2162.995124] imx708 10-001a: probing v4l2 sensor at addr 0x1a
[ 2162.995470] imx708 10-001a: mclk name not present, assume sensor driven externally
[ 2162.995472] imx708 10-001a: avdd, iovdd and/or dvdd reglrs. not present, assume sensor powered independently
[ 2162.995566] imx708 10-001a: tegracam sensor driver:imx708_v2.0.6
[ 2162.995568] imx708 10-001a: imx708_power_on: power on
[ 2163.005007] imx708 10-001a: imx708_power_off: power off
[ 2163.005072] tegra-camrtc-capture-vi tegra-capture-vi: subdev imx708 10-001a bound
[ 2163.005438] imx708 10-001a: detected imx708 sensor
[ 2163.009414] imx708 10-001a: imx708_open:
[ 2163.021777] imx708 10-001a: imx708_open:
[ 2181.407046] imx708 10-001a: imx708_open:
```

## デバイスツリーちょい解説

さて、無事に認識できたわけですが...前の記事では手でおこなったGPIOピンの番号やらスレーブアドレスやらを一体どこから得ているのでしょうか。
お気づきでしょうが、デバイスツリーに書かれています。

`電源に関する設定の追加`の作業で触った`tegra234-p3768-camera-rbpcv3-imx708.dtsi`は、IMX708の制御情報をインポートしつつ、GPIOピン設定を追加で設定をしているデバイスツリーです。

親となる`tegra234-p3768-0000-a0.dtsi`で`#include <dt-bindings/gpio/tegra234-gpio.h>`を読み込んでおり、ここでGPIOに関するポートのマッピングのマクロ?を読み出して設定をしています。`TEGRA234_MAIN_GPIO`がそれで、tegra234での名称からGPIO numberに変換され`486`が入ってきます。
カーネルモジュールではデバイスツリーから`reset-gpios`のフィールドを探して値を取得しています。gpioの向き、起動時のデフォルト値の指定も必要なので`gpio@6000d000`でoutput方向の初期値0で宣言しています。

```tegra234-p3768-camera-rbpcv3-imx708.dtsi
#include "tegra234-camera-rbpcv3-imx708.dtsi"

#define CAM0_PWDN	TEGRA234_MAIN_GPIO(H, 6)
#define CAM1_PWDN	TEGRA234_MAIN_GPIO(AC, 0)
#define CAM_I2C_MUX 	TEGRA234_AON_GPIO(CC, 3)

/ {
	cam_i2cmux {
		compatible = "i2c-mux-gpio";
		#address-cells = <1>;
		#size-cells = <0>;
		mux-gpios = <&tegra_aon_gpio CAM_I2C_MUX GPIO_ACTIVE_HIGH>;
		i2c-parent = <&cam_i2c>;
		i2c@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;
			rbpcv3_imx708_a@1a {
				status = "disabled";
				reset-gpios = <&tegra_main_gpio CAM0_PWDN GPIO_ACTIVE_HIGH>;
			};
		};
		i2c@1 {
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;
			rbpcv3_imx708_c@1a {
				status = "disabled";
				reset-gpios = <&tegra_main_gpio CAM1_PWDN GPIO_ACTIVE_HIGH>;
			};
		};
	};

	gpio@6000d000 {
		camera-control-output-low {
			gpio-hog;
			output-low;
			gpios = < CAM1_PWDN 0  CAM0_PWDN 0>;
			label = "cam1-pwdn", "cam0-pwdn";
		};
	};
};
```

スレーブアドレスの情報は`tegra234-camera-rbpcv3-imx708.dtsi`にあります。
Jetsonでは慣習的にカメラのノード名が`<カメラ識別子>_<csi_port>@<slave_address>`となっています。あコメントにもある通り`reg`にもI2Cのスレーブアドレス設定します。CSIカメラという規格がI2Cで指示を出すというのも含んでいるため、I2C設定を必須とする構成で作られています。

```tegra234-camera-rbpcv3-imx708.dtsi
i2c@1 {
    imx708_cam1: rbpcv3_imx708_c@1a {
        compatible = "sony,imx708";
        /* I2C device address */
        reg = <0x1a>;

        /* V4L2 device node location */
        devnode = "video1";

        /* Physical dimensions of sensor */
        physical_w = "3.680";
        physical_h = "2.760";

        sensor_model = "imx708";

        use_sensor_mode_id = "true";
```

`insmod`をした時に`sony,imx708`に対応するこのデバイスツリーの情報がカーネルモジュールに渡されて、いい感じに実行してくれてたのですね。

## まとめ

今回はデバイスドライバがデバイスを認識するまでにやっていることを手で実行し、それをデバイスドライバに書いて再現しました。
これでLinuxからIMX708のデバイスを見つけることが出来ました。
共通の処理をカーネルモジュールに書き、個別の設定値はデバイスツリーに書くことで、1つのカーネルモジュールがある程度柔軟に動くことが出来るようになっているようですね。

無事動いてめでたい。しかし道はまだ半ばです。I2C通信が出来ているだけでMIPI-CSI2関係はまだ何もしていません。
それに加え今の撮影設定もレジスタ設定もIMX477のものであり、まだイメージセンサとしては動作しないでしょう。
次はこれらを修正し、画像らしきものを撮りに行きます。

次の記事: [Jetsonカメラドライバを書く[RAW撮影編]](https://qiita.com/uzuna/items/05c169c91460e60fecea)