はじめに
M5Stack core MP135でNervesをポーティングする取り組みを行っています。
この製品にはHDMIポートがあるのですが、このHDMIポートのトランスミッターのLT8618SXBを適切に設定しないとHDMI出力を有効にすることができません。
このチップについて調査して、HDMI出力を得られるようにした経緯を記事にしておきます。
Linuxのドライバーについては、あまり詳しくなかったんですが、ChatGPTを使って、作成する事ができました。
チップの制御方法
M5Stackの公式のbuildroot では、lt8618sxb_mcu_configというバイナリーファイルが含まれていて、これを実行することで、LT8618SXBの設定を行い、HDMI出力を有効にしていました。
LT8618SXBこのチップの内部情報などソフトウエア開発に必要な情報は公開されていないようです。
データーシートも見つけられませんでした。
情報がなく、詰んでしまいました。
M5社の提供しているbuildrootに含まれているlt8618sx.cがあります。このプログラムはuboot用のものです。
調査したところ、ubootが起動時にこのドライバーは実行しておらず、働いていない事がわかりました。
もしかして、このプログラムと同じ動作をすればHDMI出力が有効になるのでは?
詳細は不明ですが、この方針を試してみる事にしました。
下調べ
まずユーザ空間のプログラムで試して見ました。
Nervesが動作する環境はできているので、Elixirのプログラムで実行してみました。
defmodule Test2Project do
import Bitwise
def open() do
{:ok, bus} = Circuits.I2C.open("i2c-0")
{bus,0x39}
end
def write({bus, addr}, reg, data) do
Circuits.I2C.write(bus, addr, [reg, data])
end
def write4({bus, addr}, reg, data) do
Circuits.I2C.write(bus, addr, <<reg::8, data::32-little>>)
end
def read({bus, addr}, reg) do
{:ok, <<x>>} = Circuits.I2C.write_read(bus, addr, [reg], 1)
x
end
def read4({bus, addr}, reg) do
{:ok, <<x::32-little>>} = Circuits.I2C.write_read(bus, addr, [reg], 4)
x
end
def write_register_set(handle, list) do
list
|> Enum.chunk_every(2)
|> Enum.map(fn [reg,value] -> write(handle, reg, value) end)
end
def loop(0) do
end
def loop(h, cnt) do
hdmi_init7 = [0xff, 0x80, 0x16, 0xf1, 0x18, 0xdc, 0x18, 0xfc,
0x16, 0xf3, 0x16, 0xe3, 0x16, 0xf3, 0xff, 0x82]
hdmi_init8 = [
0xb9, 0x00, 0xff, 0x84, 0x43, 0x31, 0x44, 0x10, 0x45, 0x2a, 0x47, 0x04,
0x10, 0x2c, 0x12, 0x64, 0x3d, 0x0a, 0xff, 0x80, 0x11, 0x00, 0x13, 0xf1,
0x13, 0xf9, 0xff, 0x81, 0x30, 0xea, 0x31, 0x44, 0x32, 0x4a, 0x33, 0x0b,
0x34, 0x00, 0x35, 0x00, 0x36, 0x00, 0x37, 0x44, 0x3f, 0x0f, 0x40, 0xa0,
0x41, 0xa0, 0x42, 0xa0, 0x43, 0xa0, 0x44, 0x0a]
Process.sleep(10)
write_register_set(h, hdmi_init7)
a = read(h,0x15)
a = a &&& 0x80
b = read(h,0xea)
c = read(h,0xeb)
c = c &&& 0x80
if a != 0 and c != 0 and b != 0xff do
write_register_set(h,hdmi_init8)
else
loop(cnt-1)
end
end
def init_htmi_chip() do
hdmi_init5 = [
0xff, 0x81, 0x30, 0x00, 0x02, 0x66, 0x0a, 0x06, 0x15, 0x06, 0x4e, 0xa8,
0xff, 0x82, 0x1b, 0x77, 0x1c, 0xec, 0xff, 0x80, 0xee, 0x01, 0x11, 0x00,
0x13, 0xf1, 0x13, 0xf9, 0x0a, 0x80, 0xff, 0x82, 0x45, 0x70, 0x4f, 0x40,
0x50, 0x00, 0x47, 0x07, 0xff, 0x82, 0xd6, 0x8e, 0xd7, 0x04, 0xff, 0x84,
0x06, 0x08, 0x07, 0x10, 0x09, 0x00, 0x0f, 0x2b, 0x34, 0xd5, 0x35, 0x00,
0x36, 0x18, 0x37, 0x00, 0x3c, 0x21, 0xff, 0x82, 0xde, 0x00, 0xde, 0xc0,
0xff, 0x81, 0x23, 0x40, 0x24, 0x64, 0x26, 0x55, 0x29, 0x04, 0x4d, 0x00,
0x27, 0x60, 0x28, 0x00, 0x25, 0x01, 0x2c, 0x94, 0x2d, 0x99]
hdmi_init6 = [0x25, 0x00, 0x2c, 0x9e, 0x2d, 0x99, 0x28, 0x88,
0x4d, 0x09, 0x27, 0x66, 0x2a, 0x00, 0x2a, 0x20]
h = open()
write(h,0xff,0x80)
write(h,0xee,0x01)
a = read(h,0x00)
b = read(h,0x01)
c = read(h,0x02)
{23, 2, 226} = {a,b,c}
write_register_set(h, hdmi_init5)
a = read(h, 0x2b)
write(h, 0x2b, a &&& 0xfd)
a = read(h, 0x2e)
write(h, 0x2e, a &&& 0xfe)
write_register_set(h, hdmi_init6)
loop(h, 5)
end
end
このプログラムを実行することで、HDMI出力が有効になりました。
この処理で問題ないようです。
Linuxのドライバー作成
Linuxのドライバーを1から作った事はなかったんですが、ChatGPTに教えてもらって以下の通りに作ってみました。
デバイスツリーの修正
LT8618SXBは、i2cで接続されています。i2cのコントローラは複数搭載されていて(4つあるっぽい)、どのコントローラの配下なのか知る必要があります。
ドキュメントによるとRTCと同じコントローラなので、デバイスツリーの&i2c3
と判明しました。
&i2c3
の中に、次の記述を追加します。
hdmi: lt8618sx@39 {
compatible = "lt8618sx";
reg = <0x39>;
status = "okay";
};
ドライバー作成
ChatGPTとのやり取り
「次のデバイスツリーのhdmi: lt8618sx@39 のi2cを読み書きするカーネルドライバーのプログラムを作って」
↓
いろいろ説明してくれた。
「一般的なLinuxのドライバーの作法にしたがう場合、ファイル名やフォルダーはどうすればよい?」
↓
いろいろ説明してくれた。Makefile, Kconfig等について理解した。
「lt8618sx.c をつくってみて」
↓
lt8618sx.c
lt8618sx.cは、そのままコンパイルが通る状態でした。
デバイスIDの確認のコードが含まれていましたが、今回の処理には必要なさそうだったので、削除してます。
デバイスツリーについて、よく理解できてなかったんですが、このプログラムをみて、働きがよくわかりました。
- i2cのコントローラが初期化された後に、lt8618sx_probeが呼び出される
- この時、デバイスツリーで指定しているレジスターの値0x39が、client引数に含まれている
- ドライバーはclientを使ってデバイスにアクセスするだけでよい
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/regmap.h>
#define LT8618SX_I2C_ADDR 0x39 /* I2Cアドレス */
#define DRIVER_NAME "lt8618sx"
/* LT8618SX HDMI Controller レジスタ */
#define LT8618SX_REG_DEVICE_ID 0x00 /* デバイスIDレジスタのアドレス(仮定) */
/* 読み書きヘルパー関数 */
static int lt8618sx_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
dev_err(&client->dev, "Failed to read register 0x%02x\n", reg);
return ret;
}
*val = ret;
return 0;
}
static int lt8618sx_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
int ret;
ret = i2c_smbus_write_byte_data(client, reg, val);
if (ret < 0)
dev_err(&client->dev, "Failed to write 0x%02x to register 0x%02x\n", val, reg);
return ret;
}
/* I2C probe 関数 */
static int lt8618sx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
u8 device_id;
dev_info(&client->dev, "Probing LT8618SX HDMI controller at I2C address 0x%02x\n", client->addr);
/* デバイスIDの確認(仮定のレジスタ 0x00 から読み取る) */
ret = lt8618sx_read_reg(client, LT8618SX_REG_DEVICE_ID, &device_id);
if (ret) {
dev_err(&client->dev, "Failed to read device ID\n");
return ret;
}
dev_info(&client->dev, "LT8618SX device ID: 0x%02x\n", device_id);
/* 必要な初期化やレジスタ設定をここに追加する */
return 0;
}
/* I2C remove 関数 */
static int lt8618sx_remove(struct i2c_client *client)
{
dev_info(&client->dev, "Removing LT8618SX driver\n");
/* 必要なクリーンアップ処理をここに追加 */
return 0;
}
/* I2CデバイスIDテーブル */
static const struct i2c_device_id lt8618sx_id[] = {
{ "lt8618sx", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lt8618sx_id);
/* デバイスツリーマッチングテーブル */
static const struct of_device_id lt8618sx_of_match[] = {
{ .compatible = "lt8618sx", },
{ }
};
MODULE_DEVICE_TABLE(of, lt8618sx_of_match);
/* I2Cドライバ構造体 */
static struct i2c_driver lt8618sx_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = lt8618sx_of_match,
},
.probe = lt8618sx_probe,
.remove = lt8618sx_remove,
.id_table = lt8618sx_id,
};
/* モジュール初期化とクリーンアップ */
module_i2c_driver(lt8618sx_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("LT8618SX HDMI Controller I2C Driver");
MODULE_LICENSE("GPL");
obj-$(CONFIG_I2C_LT8618SX) += lt8618sx.o
config I2C_LT8618SX
tristate "LT8618SX HDMI Controller I2C driver"
depends on I2C
help
Support for the LT8618SX HDMI controller over I2C.
CONFIG_I2C_LT8618SX=y
実行結果
組み込んで、カーネルを起動してみました。
起動時のカーネルメッセージにも、lt8618sxが表示され、HDMI出力が有効になりました。
[ 1.217025] stm32f7-i2c 40012000.i2c: STM32F7 I2C-2 bus adapter
[ 1.248938] stm32f7-i2c 40013000.i2c: STM32F7 I2C-3 bus adapter
[ 1.279757] edt_ft5x06 0-0038: supply vcc not found, using dummy regulator
[ 1.280077] edt_ft5x06 0-0038: supply iovcc not found, using dummy regulator
[ 1.291634] rtc-pcf8563 0-0051: registered as rtc0
[ 1.293079] rtc-pcf8563 0-0051: setting system clock to 2024-10-14T05:19:52 UTC (1728883192)
[ 1.297514] lt8618sx 0-0039: Probing LT8618SX HDMI controller at I2C address 0x39
[ 1.299431] input: EP0110M09 as /devices/platform/soc/4c004000.i2c/i2c-0/0-0038/input/input0
[ 1.357761] stm32f7-i2c 4c004000.i2c: STM32F7 I2C-0 bus adapter
[ 1.385432] stm32f7-i2c 4c006000.i2c: STM32F7 I2C-1 bus adapter
まとめ
- core MP135のHDMI出力を有効にできた
- ChatGPTの助けによりドライバーとして実装することができた
- デバイスツリーの理解が深まった
Linuxカーネルの作成は、いろいろ前提となる知識も必要で、調査するのも大変だなぁとおもっていました。
ChatGTPに質問する事で、サンプルになるコードを短時間で得る事ができ、30分程度で作成する事ができて助かりました。