はじめに
前の記事: Jetsonカメラドライバを書く[準備編]
次の記事: Jetsonカメラドライバを書く[RAW撮影編]
前の記事ではI2Cのスレーブアドレスが見えるところまでやりました。
この記事ではI2Cでデータを読んでデバイスを確認した後、ドライバでデバイスを検出するところまでやります。
I2Cでデータを読んで見る
前回は期待通りのスレーブアドレスが見えました。ですがスレーブアドレスは7bitしかないですし、実は違うデバイスという可能性もあります。
念の為I2Cでデータを読んで期待するデバイスかどうか確認してみましょう。
IMX708のI2Cプロトコル
IMX219のデータシートを見ると3-1-1 Communication Protocol
にプロトコルについての情報が載っています。
IMX219では16bitのアドレスと8bitのデータとあり、RaspberryPiのIMX708のソースを見るとレジスタ設定用の構造体が定義されており、同じ仕様であると分かります。
struct imx708_reg {
u16 address;
u8 val;
};
また、特定のアドレスに決まったCHIP_IDが書き込まれているようで、アドレスと期待される値について定義がありました。
実際にこの通りかを確認しましょう。
/* 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に合わせたデバイスツリーとカーネルモジュールを作ります。
以下のような流れとなります。
- ビルド環境の準備
- デバイスツリーの追加
- カーネルモジュールの追加
- Jetsonに適用してログを確認
ビルド環境の準備
L4TはBSP(Board Support Package)のバイナリだけでなく、BSPのソースコード、そしてビルドのためのツールチェインも公開しています。
それらをセットアップする必要があるので、まずはDeveloperGuideのKernelCustomizationにあるガイドに従って用意をします。
しますが...ちょっと配置が大変なのでここにある出来合いのMakefileで時短をします。
作業はJetsonではなくお使いの(おそらくx86と思われる)Linux環境で問題ありません。
make build
でビルドまで通るはずです。環境によりますが初回のビルドには10分以上はかかると思うのでビルドが出来るまでお茶して待ちましょう。
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つのデバイスツリーの追加が必要です。
- IMX708の制御に関わるデバイスツリーの追加
- 電源に関する設定の追加
- Orin Nano開発者キットのデバイスツリーへの追加
- IMX708オーバーレイの作成
ただ1つのデバイスのためにしては多い?という感じもしますね。
Jetsonのデバイスツリーの構成がどうなっているかを見てみましょう。
開発者キットのデバイスツリー
まずはOrin Nano開発者キットをflashする時に使うデバイスツリーtegra234-p3767-0003-p3768-0000-a0.dts
を見ます。
書いてあるのはファイル名、モデル名で、ほとんどの設定はincludeしています。
/*
* 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しています。こちらも見てみましょう。
/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などに書いてあり、抜粋すると以下のとおりです。
同じディレクトリ内に似たようなファイルがあるのは、これらの組み合わせ毎にデバイスツリーが必要だからですね。
そして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,<モジュール名>
としましょう。
- 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よりも先にします。
#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の設定ミスか置換忘れと思うので確認してください。
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
を作って以下のようにします。
// 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にターゲットを追加します。
オーバーレイなのでDTBOですね。
これでもう一度make build
しましょう。
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
でオーバーレイ前後の差分を見ます。
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のものに変わっています。
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は定数をヘッダファイルに書いて公開してましたが必要ではないので作りません。消します。
*/
+ #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に無い実装を削ったり、設定を合わせていきます。
デバイスツリーとの名前合わせ
モジュールロード時に適切なツリーの情報がアタッチされるようにモジュール名を上のデバイスツリーで設定した名前に変更します。
static const struct of_device_id imx708_of_match[] = {
- {.compatible = "ridgerun,imx708",},
+ {.compatible = "sony,imx708",},
{},
};
imx708_probeの書き換え
前述の通りI2Cデバイスはロード時にprobe関数を実行してデバイスの検知を行うため、この経路だけはエラー無く実行できるようにします。
制御は本来ならセンサーのデータシートを見ながら作っていくのですが、今回はないのでRaspberryPiのソースコードimx708.c
を頼りに作業します。
置換して直後のprobe関数の流れを追うと以下の様になっています。
-
imx708_probe
-
imx708_board_setup
-
imx708_power_on
- IDを読んでモデルを確認
imx708_get_fine_integ_time
-
-
一方RaspberryPiのIMX708の実装を見るとI2Cを触っているのは以下になっています
-
imx708_probe
imx708_power_on
imx708_identify_module
基本的には同じでpower_on
とimx708_identify_module
の処理あれば良さそうです。
power_on
RaspberryPiのpower_on
ではreset_gpio
を上げてモジュールの初期化をIMX708_XCLR_MIN_DELAY_US
ほど待つ処理になっています。
/* 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の実装に合わせます。
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_ID0x0708
が得られます。
16bitアドレスに8bitデータなのでなので0x0016
が0x07
で、次のアドレスが0x08
ですね。
この通りに書き換えます。
#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
/* Probe sensor model id registers */
- err = imx708_read_reg(s_data, IMX708_MODEL_ID_ADDR_MSB, ®_val[0]);
+ err = imx708_read_reg(s_data, IMX708_REG_CHIP_ID, ®_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, ®_val[1]);
+ err = imx708_read_reg(s_data, IMX708_REG_CHIP_ID+1, ®_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とは関係ないコードは削除をしましょう
現在値の取得関係は不要なので消します。
- 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
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
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
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という名前でアクセスできるものとして進めます。
設定例
Host orin-nano
Hostname orin-nano.local
User jetson
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null
ForwardX11 yes
ForwardX11Trusted yes
ローカルのMakefileに、ビルドしたものをコピーするコマンドと設定を追加します。
orin-nanoにimx708
のディレクトリを作っておきmake cp
で必要なファイルをコピーしましょう。
#
# 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
の書き換えを行ってください。
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のデバイスツリーで上書きします。
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
の値と同じであることを確認してください。
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を認識しました
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で宣言しています。
#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設定を必須とする構成で作られています。
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撮影編]