平台:rk3576
方案:原生bt1120 tx接nvp6021芯片 转为ahd信号 接ahd显示屏幕
1、硬件确认
1.1 rk3576 bt1120硬件确认
查阅硬件设计指南可以确认 rk3576是支持 16bit bt1120模式 ,最高支持1080p60Hz。
对应引脚可以查阅引脚设计指南或者设备树pinctrl ,bt1120只有这些引脚,如下图所示:
1.2、nvp6021硬件确认
电源,有供电引脚控制需要开启,并测了一下是否正常。
复位:这里是active low ,因为使用硬件原理图上加了三极管,需要取反处理
I2C地址及电平,使用i2c的电平必须为3v3或者转为3v3的。
2、驱动及设备树配置
2.1、nvp6021驱动实现
nvp6021厂家是没有提供Linux版本驱动的,只提供一个1080P_720P5060_74.25MHz_16bit_20191213.txt这种寄存器配置,根据这个配置结合设备树简单实现对应分辨率及帧数选择。
通过设备树nextchip,vmode 属性来选择对应寄存器写入。
nvp6021: nvp6021@30 {
pinctrl-names = "default";
pinctrl-0 = <&nvp6021_rst_pins>;
compatible = "nextchip,nvp6021";
reg = <0x30>;
reset-gpios = <&gpio3 RK_PD4 GPIO_ACTIVE_HIGH>;
nextchip,vmode = <2>; // 0:1080p30, 1:1080p25, 2:720p60, 3:720p50
};
实现的驱动源码,复位引脚电平会导致寄存器是否正常写入,以实际硬件为准。
// SPDX-License-Identifier: GPL-2.0
/*
* NVP6021 AHD TX initialization driver (I2C only)
*
* Supports video modes:
* 0 = 1080p30
* 1 = 1080p25
* 2 = 720p60
* 3 = 720p50
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/property.h>
/* Video mode defines (matching original DecoderInitial) */
#define NVP6021_VMODE_1080P30 0
#define NVP6021_VMODE_1080P25 1
#define NVP6021_VMODE_720P60 2
#define NVP6021_VMODE_720P50 3
struct nvp6021_priv {
struct i2c_client *client;
struct gpio_desc *reset_gpio;
u8 vmode;
};
/* Write one byte to register address */
static int nvp6021_write(struct i2c_client *client, u8 reg, u8 val)
{
return i2c_smbus_write_byte_data(client, reg, val);
}
/* Hardware reset sequence (exactly as original) */
static void nvp6021_hw_reset(struct nvp6021_priv *priv)
{
if (!priv->reset_gpio) {
dev_warn(&priv->client->dev, "No reset GPIO, skip reset\n");
return;
}
gpiod_set_value_cansleep(priv->reset_gpio, 0);
usleep_range(2000, 3000);
gpiod_set_value_cansleep(priv->reset_gpio, 1);
usleep_range(20000, 21000);
gpiod_set_value_cansleep(priv->reset_gpio, 0);
usleep_range(5000, 6000);
}
/* Full initialization sequence based on the given code */
static int nvp6021_initialize(struct nvp6021_priv *priv)
{
struct i2c_client *client = priv->client;
u8 vmode = priv->vmode;
int ret;
dev_info(&client->dev, "Initializing NVP6021 (vmode=%d)\n", vmode);
/* Hardware reset */
nvp6021_hw_reset(priv);
/* ---- BANK0 ---- */
ret = nvp6021_write(client, 0xFF, 0x00); // select BANK0
if (ret) goto err;
ret = nvp6021_write(client, 0x00, 0x01);
if (ret) goto err;
switch (vmode) {
case NVP6021_VMODE_1080P30:
ret = nvp6021_write(client, 0x01, 0xF0);
if (ret) goto err;
ret = nvp6021_write(client, 0x04, 0x00);
//ret = nvp6021_write(client, 0x04, 0x85);
break;
case NVP6021_VMODE_1080P25:
ret = nvp6021_write(client, 0x01, 0xF1);
if (ret) goto err;
ret = nvp6021_write(client, 0x04, 0x00);
break;
case NVP6021_VMODE_720P60:
ret = nvp6021_write(client, 0x01, 0xE2);
if (ret) goto err;
ret = nvp6021_write(client, 0x04, 0x00);
break;
case NVP6021_VMODE_720P50:
ret = nvp6021_write(client, 0x01, 0xE3);
if (ret) goto err;
ret = nvp6021_write(client, 0x04, 0x00);
break;
default:
dev_err(&client->dev, "Invalid vmode %d\n", vmode);
return -EINVAL;
}
if (ret) goto err;
/* ---- BANK1 ---- */
ret = nvp6021_write(client, 0xFF, 0x01); // select BANK1
if (ret) goto err;
ret = nvp6021_write(client, 0x0E, 0x00);
if (ret) goto err;
/* ---- BANK2 (main configuration) ---- */
ret = nvp6021_write(client, 0xFF, 0x02); // select BANK2
if (ret) goto err;
/* Write the sequence exactly as provided */
u8 init_vals[][2] = {
{0x00, 0xFE}, {0x02, 0x00}, {0x04, 0x80}, {0x05, 0x00},
{0x06, 0x10}, {0x0C, 0x04}, {0x0D, 0x3F}, {0x0E, 0x00},
{0x10, 0xEB}, {0x11, 0x10}, {0x12, 0xF0}, {0x13, 0x10},
{0x14, 0x01}, {0x15, 0x00}, {0x16, 0x00}, {0x17, 0x00},
{0x18, 0x00}, {0x19, 0x00}, {0x1C, 0x80}, {0x1D, 0x80},
{0x1E, 0x80}, {0x36, 0x01}, {0x37, 0x80}, {0x39, 0x00},
{0x3C, 0x00}, {0x3D, 0x00}, {0x3E, 0x00}, {0x40, 0x01},
{0x41, 0xFF}, {0x42, 0x80}, {0x60, 0x80}, {0x61, 0x80},
{0x63, 0xE0}, {0x64, 0x19}, {0x65, 0x04}, {0x66, 0xEB},
{0x67, 0x60}, {0x68, 0x00}, {0x69, 0x00}, {0x6A, 0x00},
{0x6B, 0x00},
};
for (int i = 0; i < ARRAY_SIZE(init_vals); i++) {
ret = nvp6021_write(client, init_vals[i][0], init_vals[i][1]);
if (ret) goto err;
}
/* Clear 0x20..0x34 */
for (u8 r = 0x20; r <= 0x34; r++) {
ret = nvp6021_write(client, r, 0x00);
if (ret) goto err;
}
/* Clear 0x48..0x57 */
for (u8 r = 0x48; r <= 0x57; r++) {
ret = nvp6021_write(client, r, 0x00);
if (ret) goto err;
}
/* Extra clears */
ret = nvp6021_write(client, 0x59, 0x00); if (ret) goto err;
ret = nvp6021_write(client, 0x5A, 0x00); if (ret) goto err;
for (u8 r = 0x5C; r <= 0x5F; r++) {
ret = nvp6021_write(client, r, 0x00);
if (ret) goto err;
}
/* Mode-dependent BANK2 writes (0x3A, 0x01) */
switch (vmode) {
case NVP6021_VMODE_1080P30:
ret = nvp6021_write(client, 0x3A, 0x11); if (ret) goto err;
ret = nvp6021_write(client, 0x01, 0xF0); break;
case NVP6021_VMODE_1080P25:
ret = nvp6021_write(client, 0x3A, 0x11); if (ret) goto err;
ret = nvp6021_write(client, 0x01, 0xF1); break;
case NVP6021_VMODE_720P60:
ret = nvp6021_write(client, 0x3A, 0x11); if (ret) goto err;
ret = nvp6021_write(client, 0x01, 0xE2); break;
case NVP6021_VMODE_720P50:
ret = nvp6021_write(client, 0x3A, 0x11); if (ret) goto err;
ret = nvp6021_write(client, 0x01, 0xE3); break;
}
if (ret) goto err;
/* Second BANK2 access -- mode‑specific fine‑tuning */
ret = nvp6021_write(client, 0xFF, 0x02); // ensure BANK2
if (ret) goto err;
switch (vmode) {
case NVP6021_VMODE_1080P30:
ret = nvp6021_write(client, 0x3D, 0x80); if (ret) goto err;
ret = nvp6021_write(client, 0x5C, 0x52); if (ret) goto err;
ret = nvp6021_write(client, 0x5D, 0xCA); if (ret) goto err;
ret = nvp6021_write(client, 0x5E, 0xF0); if (ret) goto err;
ret = nvp6021_write(client, 0x5F, 0x2C); if (ret) goto err;
ret = nvp6021_write(client, 0x1D, 0xB0); if (ret) goto err;
ret = nvp6021_write(client, 0x1E, 0xB0); if (ret) goto err;
ret = nvp6021_write(client, 0x37, 0xB0); break;
case NVP6021_VMODE_1080P25:
ret = nvp6021_write(client, 0x3D, 0x80); if (ret) goto err;
ret = nvp6021_write(client, 0x5C, 0x52); if (ret) goto err;
ret = nvp6021_write(client, 0x5D, 0xC3); if (ret) goto err;
ret = nvp6021_write(client, 0x5E, 0x7D); if (ret) goto err;
ret = nvp6021_write(client, 0x5F, 0xC8); if (ret) goto err;
ret = nvp6021_write(client, 0x1D, 0xB0); if (ret) goto err;
ret = nvp6021_write(client, 0x1E, 0xB0); if (ret) goto err;
ret = nvp6021_write(client, 0x37, 0xB0); break;
case NVP6021_VMODE_720P60:
ret = nvp6021_write(client, 0x3D, 0x80); if (ret) goto err;
ret = nvp6021_write(client, 0x5C, 0x52); if (ret) goto err;
ret = nvp6021_write(client, 0x5D, 0xC5); if (ret) goto err;
ret = nvp6021_write(client, 0x5E, 0xF9); if (ret) goto err;
ret = nvp6021_write(client, 0x5F, 0x2C); if (ret) goto err;
ret = nvp6021_write(client, 0x1D, 0xB0); if (ret) goto err;
ret = nvp6021_write(client, 0x1E, 0xB0); if (ret) goto err;
ret = nvp6021_write(client, 0x37, 0xB0); break;
case NVP6021_VMODE_720P50:
ret = nvp6021_write(client, 0x3D, 0x80); if (ret) goto err;
ret = nvp6021_write(client, 0x5C, 0x52); if (ret) goto err;
ret = nvp6021_write(client, 0x5D, 0xCF); if (ret) goto err;
ret = nvp6021_write(client, 0x5E, 0xE7); if (ret) goto err;
ret = nvp6021_write(client, 0x5F, 0x2C); if (ret) goto err;
ret = nvp6021_write(client, 0x1D, 0xB0); if (ret) goto err;
ret = nvp6021_write(client, 0x1E, 0xB0); if (ret) goto err;
ret = nvp6021_write(client, 0x37, 0xB0); break;
}
if (ret) goto err;
dev_info(&client->dev, "NVP6021 initialization completed successfully\n");
//return 0;
//ret = nvp6021_write(client, 0xFF, 0x02); // 确保选择 BANK2
//if (ret) goto err;
//ret = nvp6021_write(client, 0x04, 0x81); // 0x04 的 bit7 置 1 使能彩条输出
//if (ret) goto err;
dev_info(&client->dev, "NVP6021 initialization done (color bar test enabled)\n");
return 0;
err:
dev_err(&client->dev, "I2C write failed\n");
return ret;
}
/* Probe - called when device is found on I2C bus */
static int nvp6021_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct nvp6021_priv *priv;
u32 vmode = NVP6021_VMODE_1080P30;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->client = client;
i2c_set_clientdata(client, priv);
/* Obtain reset GPIO (active high as in original) */
priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(priv->reset_gpio))
return dev_err_probe(dev, PTR_ERR(priv->reset_gpio),
"Failed to get reset GPIO\n");
/* Read video mode from device tree property */
device_property_read_u32(dev, "nextchip,vmode", &vmode);
if (vmode > NVP6021_VMODE_720P50) {
dev_warn(dev, "Invalid vmode %u, fallback to 1080p30\n", vmode);
vmode = NVP6021_VMODE_1080P30;
}
priv->vmode = (u8)vmode;
/* Run initialization sequence */
ret = nvp6021_initialize(priv);
if (ret)
return ret;
dev_info(dev, "NVP6021 driver probe successful\n");
return 0;
}
static const struct of_device_id nvp6021_of_match[] = {
{ .compatible = "nextchip,nvp6021" },
{ }
};
MODULE_DEVICE_TABLE(of, nvp6021_of_match);
static const struct i2c_device_id nvp6021_id[] = {
{ "nvp6021", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, nvp6021_id);
static struct i2c_driver nvp6021_driver = {
.driver = {
.name = "nvp6021",
.of_match_table = nvp6021_of_match,
},
.probe = nvp6021_probe,
.id_table = nvp6021_id,
};
module_i2c_driver(nvp6021_driver);
MODULE_DESCRIPTION("Nextchip NVP6021 AHD TX initialization driver");
MODULE_AUTHOR("lhx");
MODULE_LICENSE("GPL");
使用i2c-tools功能查看寄存器是正常写入,驱动里面有三个bank的寄存器配置,这里以切换到bank0为例子
2.2 设备树配置
#include <dt-bindings/display/media-bus-format.h>
#include <dt-bindings/display/rockchip_vop.h>
/{
panel: panel {
compatible = "simple-panel";
clock-frequency = <74250000>;
bus-format = <MEDIA_BUS_FMT_YUYV8_1X16>;
status = "okay";
display-timings {
native-mode = <&timing_1080p30>;
timing_1080p30: timing {
clock-frequency = <74250000>; // 74.25MHz时钟
hactive = <1920>; // 有效水平像素
vactive = <1080>; // 有效垂直行数
hfront-porch = <88>; // 水平前沿
hback-porch = <148>; // 水平后沿
hsync-len = <44>; // 水平同步脉冲
vfront-porch = <4>; // 垂直前沿
vback-porch = <36>; // 垂直后沿
vsync-len = <5>; // 垂直同步脉冲
hsync-active = <1>; // 高电平有效
vsync-active = <1>; // 高电平有效
de-active = <0>; // DE信号极性
pixelclk-active = <0>; // 像素时钟下降沿采样
};
timing_720p60: timing1 {
clock-frequency = <74250000>;
hactive = <1280>;
vactive = <720>;
hfront-porch = <110>;
hback-porch = <220>;
hsync-len = <40>;
vfront-porch = <5>;
vback-porch = <20>;
vsync-len = <5>;
hsync-active = <1>;
vsync-active = <1>;
de-active = <0>;
pixelclk-active = <0>;
};
};
port {
panel_in_rgb: endpoint {
remote-endpoint = <&rgb_out_panel>;
};
};
};
};
&display_subsystem {
status = "okay";
};
&vp2 {
xmirror-enable;
};
&i2c8 {
clock-frequency = <400000>;
pinctrl-0 = <&i2c8m1_xfer>;
status = "okay";
nvp6021: nvp6021@30 {
pinctrl-names = "default";
pinctrl-0 = <&nvp6021_rst_pins>;
compatible = "nextchip,nvp6021";
reg = <0x30>;
reset-gpios = <&gpio3 RK_PD4 GPIO_ACTIVE_HIGH>;
nextchip,vmode = <2>; // 0:1080p30, 1:1080p25, 2:720p60, 3:720p50
};
};
&rgb {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&bt1120_pins>;
ports {
port@1 {
reg = <1>;
rgb_out_panel: endpoint {
remote-endpoint = <&panel_in_rgb>;
};
};
};
};
&rgb_in_vp2 {
status = "okay";
};
&route_rgb {
status = "okay";
connect = <&vp2_out_rgb>;
};
&pinctrl {
nvp6021_rst {
nvp6021_rst_pins: nvp6021_rst_pins {
rockchip,pins = <3 RK_PD4 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
};
3、调试与测试
查看当前屏幕的显示状态:
cat /sys/kernel/debug/dri/0/summary
使用modetest测试输出彩条
modetest -M rockchip
modestet -M rockchip -s 168@72:1920x1080
能正常输出彩条,屏幕是调通了的。
如果输出不太正常 可以尝试抓ahd_out输出波形或者时钟是否正确,这里原厂说波形算下来和1080p30的行时序差不多,具体如果计算并未说明。
暂时还有一个问题未解决,从寄存器配置输出彩条或者从modetest测试都偏暗,测试调整过datasheet 里面, bnak2 0x1d/1e/0x37 没有效果,也测试过调整色域,参考rk色域说明也是没有什么效果。