TDA4VM-LINUX-CSI-9296-9295-camera架构驱动分析和详细使用

前言

TI在LINUX-SDK-8.01版本后才开始支持Linux V4L2接入CSI2,所以在使用前尽量先用RTOS-SDK接入CSI2的接口camera,正常工作后开始进行Linux V4L2的开发。
LINUX-SDK的安装使用,参考另一篇文档《优雅的玩转TDA4VM》
在TDA4VM中,一个CSI接口是默认启动16个虚拟通道的。

硬件架构

系统 硬件架构
Linux CSI2RX子系统由3个IP组成:Cadence DPHY,Cadence CSI2RX桥接, TI CSI2RX DMA 封装器
RTOS CSI2RX子系统由2个IP组成:Cadence DPHY,CSI2RX_drive

image.png
CSI2RX 子系统支持以下功能:

  • 符合 MIPI CSI v1.3 标准
  • 每个输入最多支持 16 个虚拟通道(部分 MIPI CSI v2.0 功能)。
  • 每通道数据速率高达 2.5 Gbps(线速)。
  • 支持 1、2、3 或 4 条数据通道连接到DPHY_RX。
  • 可编程格式包括 YUV420、YUV422、RGB、Raw 等。

驱动程序

该驱动程序基于 Video 4 Linux 2 (V4L2) API。它已实现 根据捕获设备的 V4L2 标准。司机只是 负责对用于捕获的SoC组件进行编程,例如DPHY,CSI-Bridge,DMA。对于相机传感器等外部设备,单独的 V4L2 子设备需要驱动程序。

link_freq计算

V4L2_CID_LINK_FREQ

数据总线频率。数据总线频率与媒体总线像素代码、总线类型(每个样本的时钟周期)一起定义了像素阵列中的像素速率(V4L2_CID_PIXEL_RATE),如果设备不是图像传感器,则可能定义其他像素速率()。帧速率可以通过像素时钟、图像宽度和高度以及水平和垂直消隐来计算。虽然像素速率控制可以在包含像素数组的子开发中以外的其他位置定义,但无法从该信息中获取帧速率。这是因为只有在像素阵列上,才能假设垂直和水平消隐信息是精确的:像素数组中不允许其他消隐。通过选择所需的水平和垂直消隐来选择帧速率。此控件的单位是 Hz。

V4L2_CID_PIXEL_RATE

子开发源垫中的像素率。此控件是只读的,其单位为像素/秒。Ex mipi 总线:pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample位深
mipi_link_freq = wh * fps * bits_per_sample / nr_of_lanes / 2
pixel_clk_rate = w
h * fps

CSI2RX寄存器调试

使用devmem2工具依次查看对应CSI2RX的寄存器,从而判断硬件是否有数据输入。

DPHY正常状态

0x04590B00(DPHY_RX_VBUS2APB_PCS_TX_DIG_TBIT0) = 0x000001ef
0x04504040(CSI_RX_IF_VBUS2APB_DPHY_LANE_CONTROL) = 0x0001F01F
0x04504048(CSI_RX_IF_VBUS2APB_DPHY_STATUS) = 0x00333306/0x00222206
0x04504060(CSI_RX_IF_VBUS2APB_INTEGRATION_DEBUG) = 0x10000000 // WAIT_FOR_PACKET
0x04504100(CSI_RX_IF_VBUS2APB_STREAM0_CTRL) = 0x00000001
0x04504104(CSI_RX_IF_VBUS2APB_STREAM0_STATUS) = 0x80000111
0x04580C10 ( DPHY_RX_VBUS2APB_ISO_PHY_ISO_CL_CNTRL_L ) = 0x00000029 //normal

寄存器配置

max9296

//使能
max9296_write(priv, 0x0313, 0x02);
//关闭
max9296_write(priv, 0x0313, 0x00);
    max9296_write(priv, 0x0010, 0x31);
    msleep(100);
    max9296_write(priv, 0x0313, 0x00);
    msleep(1);
    max9296_write(priv, 0x048B, 0x07);
    msleep(1);
    max9296_write(priv, 0x048D, 0x1E);
    msleep(1);
    max9296_write(priv, 0x048E, 0x1E);
    msleep(1);
    max9296_write(priv, 0x0490, 0x00);
    msleep(1);
    max9296_write(priv, 0x0491, 0x01);
    msleep(1);
    max9296_write(priv, 0x0492, 0x01);
    msleep(1);
    // max9296_write(priv, 0x04AD, 0x2A);
    max9296_write(priv, 0x04AD, 0x15);//206
    msleep(1);
    max9296_write(priv, 0x0320, 0x2C);
    msleep(1);
    max9296_write(priv, 0x0051, 0x02);
    msleep(1);
    max9296_write(priv, 0x0052, 0x01);
    msleep(1);

max9295

    max9295_write(priv, 0x0010, 0x21);
    msleep(100);
    max9295_write(priv, 0x02be, 0x10);
    msleep(500);
    max9295_write(priv, 0x0318, 0x5E);
    msleep(1);
    max9295_write(priv, 0x02D3, 0x00);
    msleep(500);
    max9295_write(priv, 0x02D3, 0x10);
    msleep(100);
    max9295_write(priv, 0x02D6, 0x00);
    msleep(500);
    max9295_write(priv, 0x02D6, 0x10);
    msleep(100);

驱动源码

...
static int max9295_probe(struct i2c_client *client)
{
	struct max9295_priv *priv;
	struct device *dev = &client->dev;
	int ret;

	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;
	
	priv->client = client;
	i2c_set_clientdata(client, priv);
	
	priv->regmap = devm_regmap_init_i2c(client, &max9295_i2c_regmap);
	if (IS_ERR(priv->regmap))
		return PTR_ERR(priv->regmap);
	
	if (max9295_read(priv, 0x0000) != MAX9295_ID)
		return -EFAULT;
	
	max9295_mipi_configure(priv);
	
	// max9295_v4l2_register(priv);
	dev_info(dev, "Successfully probed 9295 (rev/mask)n");
	return 0;

}
...
...
static int max9296_probe(struct i2c_client *client)
{
	struct max9296_priv *priv;
	int ret;
	

	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;
	
	mutex_init(&priv->mutex);
	
	priv->client = client;
	i2c_set_clientdata(client, priv);
	
	priv->regmap = devm_regmap_init_i2c(client, &max9296_i2c_regmap);
	if (IS_ERR(priv->regmap))
		return PTR_ERR(priv->regmap);
	
	/*parse_dt*/
	ret = max9296_parse_dt(priv);
	if (ret)
		goto err_powerdown;
	
	ret = max9296_init(&client->dev);
	if (ret < 0)
		goto err_cleanup_dt;
	dev_err(&client->dev, "Successfully probed 9296 okn");
	return 0;

err_cleanup_dt:
	max9296_cleanup_dt(priv);
err_powerdown:
	gpiod_set_value_cansleep(priv->gpiod_pwdn, 0);
	return ret;
}
...

创建设备树节点

确定主设备树

使用dmesg工具,查看启动日志,找到启动所用的主设备树。
记录到内核环形缓冲区的每个消息都有一个附加的级别。 级别表示信息的重要性。 包含的级别有:

  • emerg : 系统无法使用;
  • alert : 必须立即采取措施;
  • crit : 严重的情况;
  • err : 错误;
  • warn : 警告;
  • notice : 正常但是重要的情况;
  • info : 消息;
  • debug : 调试信息;

通过使用 -l(level)参数并将级别的名称作为命令行参数传递,可以提取与特定级别匹配的消息。 如果查看“informational”级别的消息,请使用如下命令:

$ dmesg -l info  //列出的所有消息都是"info"消息。 它们不包含错误或警告,仅包含有用的通知。

从启动日志确认设备树为k3-j721e-common-proc-board.dts。

修改主设备树

&main_i2c6 {
	pinctrl-names = "default";
	pinctrl-0 = <&main_i2c6_pins_default>;
	clock-frequency = <400000>;

	exp5: gpio@20 {
		compatible = "ti,tca6408";
		reg = <0x20>;
		gpio-controller;
		#gpio-cells = <2>;
	};
	
	des@48 {
		compatible = "maxim,max9296";
		reg       = <0x48>;
		ports {
			#address-cells = <1>;
			#size-cells = <0>;
	
			/* CSI-2 */
			port@4 {
				reg = <4>;
				max9296_0_csi_out: endpoint {
					clock-lanes = <0>;
					data-lanes = <1 2 3 4>;
					remote-endpoint = <&csi2_phy0>;
				};
			};
		};
	};
	
	ser@40 {
		compatible = "maxim,max9295";
		reg = <0x40>;
	
	};

};

加载驱动

lsmod max9295.ko
lsmod max9296.ko
$ ls /dev  //查看一个CSI接口,video节点是否正常创建16

成像显示

gst-launch-1.0 v4l2src device="/dev/video2" ! video/x-raw ,width=1920, height=1080, framerate=30/1 ! waylandsink
sleep 1
gst-launch-1.0 v4l2src device="/dev/video2" ! video/x-raw, width=1920, height=1080, format=UYVY ! autovideosink