本文记录工作中基于rk3568安卓13的SDK适配板载外设GSV2001芯片驱动时具体的修改项和问题
目录
前言
本文记录了在RK3568平台、Android 13 SDK环境下,适配GSV2001 HDMI 2.0 分流芯片的完整过程。GSV2001由GSCoolink(基石酷联)推出,主要功能是将一路HDMI信号分配为两路输出,支持4K高清视频传输和音频处理。
官方提供了该芯片的应用程序SDK,但原始代码运行在STM32F030C8裸机环境下,需要将其完整移植到Linux内核驱动框架中,并适配RK3568的I2C总线与GPIO控制。本文将围绕工作原理梳理、驱动移植、设备树配置及调试踩坑四个方面展开,希望能为同类芯片的适配工作提供参考。
一、GSV2001工作原理梳理

从芯片规格书和硬件原理图可以归纳出GSV2001的核心工作逻辑:
-
通信方式:主控(RK3568)通过 I2C总线 对GSV2001的内部寄存器进行读写配置,使其进入正常工作状态。
-
控制时序:芯片上电后,需先拉低复位引脚(RESET)至少10ms以上完成硬件复位,随后通过I2C进行初始化寄存器配置。
-
功能模块:芯片内部集成了HDMI Rx/Tx、音频处理、视频缩放(Scaler)、色彩空间转换(Color)、CEC控制等多个功能模块,需要通过SDK提供的API依次初始化并建立路由连接。
官方SDK提供了完整的API层实现(av_api),包含设备注册、端口连接、状态轮询等机制。移植的核心任务是将底层的硬件抽象层(BSP)从STM32的HAL库替换为Linux内核的I2C/GPIO/延时接口,并将主循环从裸机的while(1)改造为Linux内核的工作队列(workqueue) 机制。
二、GSV2001软件适配
1.驱动移植
1.1 底层接口移植
官方SDK中的I2C源文件用普通IO模拟的,这里可以删除掉,只需要改写bsp.c文件中的接口。
官方在bsp.c文件中提供了四个核心接口,需要逐一替换为Linux内核对应的实现:
| 接口函数 | 功能 | Linux内核对应实现 |
|---|---|---|
BspI2cRead | I2C读操作(16位寄存器地址) | i2c_transfer + i2c_msg |
BspI2cWrite | I2C写操作(16位寄存器地址) | i2c_master_send |
BspUartSendByte | 调试日志输出 | dev_dbg / printk |
BspGetMilliSecond | 毫秒级时间戳 | ktime_get / jiffies |
I2C读操作实现(带RESTART)
这里一定要注意RESTART信号,芯片规格书中也有提到读协议时序如下:

GSV2001采用16位寄存器地址(高字节在前),读取时需要先写入寄存器地址,再发送RESTART信号切换为读操作。具体实现如下:
int ManI2cRead(uint8_t dev_addr, uint16_t reg_addr, uint8_t *buf, int len)
{
struct i2c_msg msgs[2];
uint8_t reg_buf[2];
int ret;
if (!g_i2c_client) return -EINVAL;
if (!buf || len <= 0) {
return -EINVAL;
}
/* 准备寄存器地址(大端:高字节在前) */
reg_buf[0] = (reg_addr >> 8) & 0xFF;
reg_buf[1] = reg_addr & 0xFF;
/* 消息1: 写寄存器地址(不发送 STOP) */
msgs[0].addr = g_i2c_client->addr;
msgs[0].flags = 0; /* 写 */
msgs[0].len = 2;
msgs[0].buf = reg_buf;
/* 消息2: 读取数据(发送 RESTART + STOP) */
msgs[1].addr = g_i2c_client->addr;
msgs[1].flags = I2C_M_RD; /* 读 */
msgs[1].len = len;
msgs[1].buf = buf;
/* 执行传输: 两个消息之间会自动产生 RESTART */
ret = i2c_transfer(g_i2c_client->adapter, msgs, 2);
if (ret < 0) {
dev_err(&g_i2c_client->dev,
"GSV: I2C read failed: dev=0x%02x reg=0x%04x len=%d ret=%d\n",
dev_addr, reg_addr, len, ret);
return ret;
}
if (ret != 2) {
dev_err(&g_i2c_client->dev,
"GSV: I2C read partial: dev=0x%02x reg=0x%04x ret=%d\n",
dev_addr, reg_addr, ret);
return -EIO;
}
return len;
}
I2C写操作实现:
int ManI2cWrite(uint8_t dev_addr, uint16_t reg_addr, uint8_t *buf, int len)
{
uint8_t tx_buf[512];
int i;
if (!g_i2c_client) return -EINVAL;
if (len > 509){
dev_info(&g_i2c_client->dev, "ManI2cWrite fail overSize\n");
return -EINVAL;
}
tx_buf[0] = (reg_addr >> 8) & 0xFF;
tx_buf[1] = reg_addr & 0xFF;
for (i = 0; i < len; i++) {
tx_buf[2 + i] = buf[i];
}
return i2c_master_send(g_i2c_client, tx_buf, len + 2);
}
完整的移植后的bsp.c文件如下:
/**
* @file bsp.c
*
* @brief sample bsp support
*/
#include "bsp.h"
#include <linux/i2c.h>
#include <linux/delay.h>
static struct i2c_client *g_i2c_client = NULL;
/* 在驱动probe中调用此函数设置client */
void i2c_set_client(struct i2c_client *client)
{
g_i2c_client = client;
}
int ManI2cRead(uint8_t dev_addr, uint16_t reg_addr, uint8_t *buf, int len)
{
struct i2c_msg msgs[2];
uint8_t reg_buf[2];
int ret;
if (!g_i2c_client) return -EINVAL;
if (!buf || len <= 0) {
return -EINVAL;
}
/* 准备寄存器地址(大端:高字节在前) */
reg_buf[0] = (reg_addr >> 8) & 0xFF;
reg_buf[1] = reg_addr & 0xFF;
/* 消息1: 写寄存器地址(不发送 STOP) */
msgs[0].addr = g_i2c_client->addr;
msgs[0].flags = 0; /* 写 */
msgs[0].len = 2;
msgs[0].buf = reg_buf;
/* 消息2: 读取数据(发送 RESTART + STOP) */
msgs[1].addr = g_i2c_client->addr;
msgs[1].flags = I2C_M_RD; /* 读 */
msgs[1].len = len;
msgs[1].buf = buf;
/* 执行传输: 两个消息之间会自动产生 RESTART */
ret = i2c_transfer(g_i2c_client->adapter, msgs, 2);
if (ret < 0) {
dev_err(&g_i2c_client->dev,
"GSV: I2C read failed: dev=0x%02x reg=0x%04x len=%d ret=%d\n",
dev_addr, reg_addr, len, ret);
return ret;
}
if (ret != 2) {
dev_err(&g_i2c_client->dev,
"GSV: I2C read partial: dev=0x%02x reg=0x%04x ret=%d\n",
dev_addr, reg_addr, ret);
return -EIO;
}
return len;
}
int ManI2cWrite(uint8_t dev_addr, uint16_t reg_addr, uint8_t *buf, int len)
{
uint8_t tx_buf[512];
int i;
if (!g_i2c_client) return -EINVAL;
if (len > 509){
dev_info(&g_i2c_client->dev, "ManI2cWrite fail overSize\n");
return -EINVAL;
}
tx_buf[0] = (reg_addr >> 8) & 0xFF;
tx_buf[1] = reg_addr & 0xFF;
for (i = 0; i < len; i++) {
tx_buf[2 + i] = buf[i];
}
return i2c_master_send(g_i2c_client, tx_buf, len + 2);
}
/**
* @brief bsp i2c read function, support bus/dev address, 8/16 register address
* @return AvOk - success
*/
AvRet BspI2cRead(uint32 devAddress, uint32 regAddress, uint8 *data, uint16 count)
{
AvRet ret = AvOk;
int rst = 0;
uint8 deviceAddress = (uint8)AvGetI2cDeviceAddress(devAddress);
uint16 regAdress = (uint32)((AvGetRegAddress(devAddress)<<8) | AvGetRegAddress(regAddress));
rst = ManI2cRead(deviceAddress, regAdress, data, count);
if(rst < 0){
ret = rst;
}
else if(rst == 0){
ret = AvError;
}
return ret;
}
/**
* @brief bsp i2c write function, support bus/dev address, 8/16 register address
* @return AvOk - success
*/
AvRet BspI2cWrite(uint32 devAddress, uint32 regAddress, uint8 *data, uint16 count)
{
AvRet ret = AvOk;
int rst = 0;
char buf[300];
char print_buf[512] = {0};
int offset = 0;
int i = 0;
uint8 deviceAddress = (uint8)AvGetI2cDeviceAddress(devAddress);
uint16 regAdress = (uint32)((AvGetRegAddress(devAddress)<<8) | AvGetRegAddress(regAddress));
rst = ManI2cWrite(deviceAddress, regAdress, data, count);
if(rst < 0 ){
ret = rst;
}
else if(rst == 0){
ret = AvError;
}
return ret;
}
/**
* @brief send one byte from uart
* @return AvOk - success
*/
AvRet BspUartSendByte(uint8 *data, uint16 size)
{
char buf[256];
int i, len;
// 1. 检查 g_i2c_client 是否有效
if (!g_i2c_client) {
printk(KERN_INFO "GSV: UART send %d bytes (client not ready)\n", size);
return AvOk;
}
// 2. 检查数据有效性
if (!data || size == 0) {
dev_dbg(&g_i2c_client->dev, "GSV: UART send empty\n");
return AvOk;
}
// 3. 只打印可见字符
len = min((int)size, 255);
for (i = 0; i < len; i++) {
uint8 c = data[i];
if (c >= 0x20 && c < 0x7F) {
buf[i] = c;
} else if (c == '\n' || c == '\r') {
buf[i] = c;
} else {
buf[i] = '.';
}
}
buf[i] = '\0';
dev_info(&g_i2c_client->dev, "GSV: %s\n", buf);
return AvOk;
}
/**
* @brief get one byte from uart
* @return AvOk - success
*/
AvRet BspUartGetByte(uint8 *data)
{
AvRet ret = AvOk;
return ret;
}
/**
* @brief get current time in ms
* @return AvOk - success
*/
AvRet BspGetMilliSecond(uint32 *ms)
{
*ms = (uint32)(ktime_get_real_ns() / 1000000);
return AvOk;
}
AvRet BspGetKey(uint8 *data)
{
AvRet ret = AvNotAvailable;
return ret;
}
AvRet BspIrdaGetByte(uint8 *data)
{
AvRet ret = AvOk;
return ret;
}
1.2 主函数移植
官方SDK的主循环在裸机中是一个无限循环,持续调用AvApiUpdate()进行状态更新和事件处理。在Linux内核驱动中,需要将其改造为延迟工作队列(delayed_work) 方式,以50ms为周期轮询执行:
static struct delayed_work gsv_work;
static bool gsv_running = false;
static void gsv_work_handler(struct work_struct *work)
{
if (!gsv_running)
return;
/* 调用官方SDK主循环 */
AvApiUpdate();
AvPortConnectUpdate(&devices[0]);
/* 重新调度 - 50ms轮询 */
schedule_delayed_work(&gsv_work, msecs_to_jiffies(50));
}
在probe函数中完成设备注册、BSP接口钩挂、端口配置和路由连接后,启动工作队列:
static int gsv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
/* 保存I2C client供BSP层使用 */
gsv_client = client;
i2c_set_client(client);
/* 1. 初始化SDK并钩挂BSP函数 */
AvApiInit();
AvApiHookBspFunctions(BspI2cRead, BspI2cWrite,
BspUartSendByte, BspUartGetByte,
BspGetMilliSecond, BspGetKey, BspIrdGetByte);
/* 2. 声明设备与端口(HDMI Rx、Tx、音频、Scaler等) */
devices[0].DeviceAddress = AvGenerateDeviceAddress(0x00, 0x01, 0x80, 0x00);
AvApiAddDevice(&devices[0], Gsv2k1, 0,
(void *)&gsv2k1_0, (void *)&gsv2k1Ports[0], NULL);
/* 3. 添加各功能端口 */
AvApiAddPort(&devices[0], &gsv2k1Ports[0], 0, HdmiRx);
AvApiAddPort(&devices[0], &gsv2k1Ports[1], 4, HdmiTx);
AvApiAddPort(&devices[0], &gsv2k1Ports[2], 5, HdmiTx);
/* ... 省略音频、Scaler、Color等端口配置 */
/* 4. 配置视频路由:RxA -> TxA / TxB */
AvApiConnectPort(&gsv2k1Ports[0], &gsv2k1Ports[1], AvConnectAV);
AvApiConnectPort(&gsv2k1Ports[0], &gsv2k1Ports[2], AvConnectAV);
/* 5. 初始化设备并启动工作队列 */
AvApiInitDevice(&devices[0]);
AvApiPortStart();
gsv_running = true;
INIT_DELAYED_WORK(&gsv_work, gsv_work_handler);
schedule_delayed_work(&gsv_work, msecs_to_jiffies(100));
dev_info(dev, "GSV2001 driver loaded\n");
return 0;
}
完整移植后的av_main.c文件如下:
/**
* @file av_main.c
*
* @brief sample main entry for audio/video based software
*/
#include "av_main.h"
#include "global_var.h"
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
struct gsv_device_data {
struct gpio_desc *reset_gpio;
struct i2c_client *client;
};
static struct i2c_client *gsv_client;
static struct delayed_work gsv_work;
static bool gsv_running = false;
/* 2. Device Level Declaration */
/* 2.1 total devices */
/* it must be declared in AvDevice */
static AvDevice devices[1] = {0};
static AvPort gsv2k1Ports[7] = {0};
static Gsv2k1Device gsv2k1_0 = {0};
extern void i2c_set_client(struct i2c_client *client);
static void gsv_work_handler(struct work_struct *work)
{
if (!gsv_running)
return;
// 调用官方SDK主循环
AvApiUpdate();
AvPortConnectUpdate(&devices[0]);
// 重新调度 - 50ms轮询
schedule_delayed_work(&gsv_work, msecs_to_jiffies(50));
}
static int gsv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
/* 2.2 specific devices and ports */
/* they must be able to be linked to the device in 1. */
dev_info(dev, "GSV2001 probe start\n");
gsv_client = client;
i2c_set_client(client);
/* 1. Low Level Hardware Level Initialization */
/* 1.1 init bsp support (user speficic) */
// BspInit();
/* 1.2 init software package and hookup user's bsp functions */
AvApiInit();
AvApiHookBspFunctions(&BspI2cRead, &BspI2cWrite, &BspUartSendByte,
&BspUartGetByte, &BspGetMilliSecond, &BspGetKey, &BspIrdaGetByte);
AvApiHookUserFunctions(&ListenToKeyCommand, &ListenToUartCommand, &ListenToIrdaCommand);
/* 2.3 init device address in 2.2 */
gsv2k1_0.DeviceAddress = AvGenerateDeviceAddress(0x00,0x01,0xB0,0x00);
/* 2.4 connect devices to device declaration */
AvApiAddDevice(&devices[0], Gsv2k1, 0, (void *)&gsv2k1_0, (void *)&gsv2k1Ports[0], NULL);
/* 3. Port Level Declaration */
/* 3.1 init devices and port structure, must declare in number order */
/* 0-3 HdmiRx, 4-7 HdmiTx, 8-9 TTLTx, 10-11 TTLRx,
20-23 Scaler, 24-27 Color, 28 VideoGen, 30 VideoIn, 32 VideoOut,
34 AudioGen, 36 ClockGen */
AvApiAddPort(&devices[0],&gsv2k1Ports[0] ,0 ,HdmiRx);
AvApiAddPort(&devices[0],&gsv2k1Ports[1] ,4 ,HdmiTx);
AvApiAddPort(&devices[0],&gsv2k1Ports[2] ,5 ,HdmiTx);
AvApiAddPort(&devices[0],&gsv2k1Ports[3] ,8 ,LogicAudioTx);
AvApiAddPort(&devices[0],&gsv2k1Ports[4] ,20,VideoScaler);
AvApiAddPort(&devices[0],&gsv2k1Ports[5] ,24,VideoColor);
AvApiAddPort(&devices[0],&gsv2k1Ports[6] ,10,LogicAudioRx);
// AvApiAddPort(&devices[0],&gsv2k1Ports[6], 28,VideoGen);
// AvApiAddPort(&devices[0],&gsv2k1Ports[8] ,34,AudioGen);
// AvApiAddPort(&devices[0],&gsv2k1Ports[9] ,36,ClockGen);
/* 3.2 initialize port content */
#if AvEnableCecFeature
gsv2k1Ports[1].content.cec->CecEnable = 1;
if(AudioStatus == 0)
gsv2k1Ports[1].content.cec->EnableAudioAmplifier = AV_CEC_AMP_TO_DISABLE;
else
{
gsv2k1Ports[1].content.cec->EnableAudioAmplifier = AV_CEC_AMP_TO_ENABLE;
gsv2k1Ports[1].content.cec->EnableARC = AV_CEC_ARC_TO_INITIATE;
}
Cec_Tx_Audio_Status.Volume = 30;
Cec_Tx_Audio_Status.Mute = 0; /* */
Cec_Tx_Audio_Status.AudioMode = 1; /* Audio Mode is ON to meet ARC */
Cec_Tx_Audio_Status.AudioRate = 1; /* 100% rate */
Cec_Tx_Audio_Status.AudioFormatCode = AV_AUD_FORMAT_LINEAR_PCM; /* Follow Spec */
Cec_Tx_Audio_Status.MaxNumberOfChannels = 2; /* Max Channels */
Cec_Tx_Audio_Status.AudioSampleRate = 0x07; /* 32KHz/44.1KHz/48KHz */
Cec_Tx_Audio_Status.AudioBitLen = 0x01; /* 16-bit only */
Cec_Tx_Audio_Status.MaxBitRate = 0; /* default */
Cec_Tx_Audio_Status.ActiveSource = 0; /* default */
#endif
//-------------------------
/* 3.3 init fsms */
AvApiInitDevice(&devices[0]);
AvApiPortStart();
/* 3.4 routing */
/* connect the port by video using AvConnectVideo */
/* connect the port by audio using AvConnectAudio */
/* connect the port by video and audio using AvConnectAV */
/* 3.4.1 video routing */
/* case 1: default routing RxA->TxA/TxB */
AvApiConnectPort(&gsv2k1Ports[0], &gsv2k1Ports[1], AvConnectAV);
// AvApiConnectPort(&gsv2k1Ports[0], &gsv2k1Ports[2], AvConnectAV);
AvApiConnectPort(&gsv2k1Ports[0], &gsv2k1Ports[3], AvConnectAudio);
///* case 2: audio insertion */
//AvApiConnectPort(&gsv2k1Ports[0], &gsv2k1Ports[1], AvConnectVideo);
//AvApiConnectPort(&gsv2k1Ports[0], &gsv2k1Ports[2], AvConnectVideo);
//AvApiConnectPort(&gsv2k1Ports[9], &gsv2k1Ports[1], AvConnectAudio);
//AvApiConnectPort(&gsv2k1Ports[9], &gsv2k1Ports[2], AvConnectAudio);
/* case 3: videogen and audiogen */
//AvApiConnectPort(&gsv2k1Ports[6], &gsv2k1Ports[1], AvConnectVideo);
//AvApiConnectPort(&gsv2k1Ports[6], &gsv2k1Ports[2], AvConnectVideo);
//AvApiConnectPort(&gsv2k1Ports[7], &gsv2k1Ports[1], AvConnectAudio);
//AvApiConnectPort(&gsv2k1Ports[7], &gsv2k1Ports[2], AvConnectAudio);
/* 3.4.2 ARC Connection, set after rx port connection to avoid conflict */
#if AvEnableCecFeature
if(AudioStatus == 1)
{
AvApiConnectPort(&gsv2k1Ports[1], &gsv2k1Ports[3], AvConnectAudio);
}
#endif
/* 3.4.3 Internal Video Generator*/
#if AvEnableInternalVideoGen
//gsv2k1Ports[6].content.video->timing.Vic = 0x61; /* 4K60 */
gsv2k1Ports[6].content.video->timing.Vic = 0x60;
gsv2k1Ports[6].content.video->AvailableVideoPackets = AV_BIT_AV_INFO_FRAME;
gsv2k1Ports[6].content.video->Cd = AV_CD_24;
gsv2k1Ports[6].content.video->Y = AV_Y2Y1Y0_RGB;
gsv2k1Ports[6].content.vg->Pattern = AV_PT_COLOR_BAR;
#endif
/* 3.4.4 Audio Insertion */
#if AvEnableAudioTTLInput
gsv2k1Ports[3].content.audio->AudioMute = 0;
gsv2k1Ports[3].content.audio->AudFormat = AV_AUD_I2S;
gsv2k1Ports[3].content.audio->AudType = AV_AUD_TYPE_ASP;
gsv2k1Ports[3].content.audio->AudCoding = AV_AUD_FORMAT_LINEAR_PCM;
gsv2k1Ports[3].content.audio->AudMclkRatio = AV_MCLK_256FS;
gsv2k1Ports[3].content.audio->Layout = 0; /* 2 channel Layout = 0 */
gsv2k1Ports[3].content.audio->Consumer = 0; /* Consumer */
gsv2k1Ports[3].content.audio->Copyright = 0; /* Copyright asserted */
gsv2k1Ports[3].content.audio->Emphasis = 0; /* No Emphasis */
gsv2k1Ports[3].content.audio->CatCode = 0; /* Default */
gsv2k1Ports[3].content.audio->SrcNum = 0; /* Refer to Audio InfoFrame */
gsv2k1Ports[3].content.audio->ChanNum = 2; /* Audio Channel Count */
gsv2k1Ports[3].content.audio->SampFreq = AV_AUD_FS_48KHZ; /* Sample Frequency */
gsv2k1Ports[3].content.audio->ClkAccur = 0; /* Level 2 */
gsv2k1Ports[3].content.audio->WordLen = 0x0B; /* 24-bit word length */
#endif
// 8. 启动工作队列
gsv_running = true;
INIT_DELAYED_WORK(&gsv_work, gsv_work_handler);
schedule_delayed_work(&gsv_work, msecs_to_jiffies(100));
dev_info(dev, "GSV2001 driver loaded\n");
return 0;
}
static int gsv_remove(struct i2c_client *client)
{
gsv_running = false;
cancel_delayed_work_sync(&gsv_work);
dev_info(&client->dev, "GSV2001 removed\n");
return 0;
}
static const struct of_device_id gsv_of_match[] = {
{ .compatible = "gsv,gsv2001" },
{}
};
MODULE_DEVICE_TABLE(of, gsv_of_match);
static struct i2c_driver gsv_driver = {
.driver = {
.name = "gsv2001",
.of_match_table = gsv_of_match,
},
.probe = gsv_probe,
.remove = gsv_remove,
};
module_i2c_driver(gsv_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("board");
MODULE_DESCRIPTION("GSV2001 HDMI Splitter Driver for RK3568");
1.3 设备树文件
GSV2001挂接在RK3568的I2C3总线上,设备地址为0x58(7位地址格式)。在板级设备树中添加如下节点:
&i2c3 {
status = "okay";
gsv2001: gsv2001@58 {
compatible = "gsv,gsv2001";
reg = <0x58>;
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
status = "okay";
};
};
关于I2C地址的说明:设备树中的reg = <0x58>是7位地址。I2C协议中,7位地址加上第8位读写标志位后,实际总线上的地址为0xB0(写)和0xB1(读)。Linux内核的I2C框架会自动处理读写位的组装,驱动层无需额外转换。
2.问题记录
2.1 I2C读协议错误
现象:驱动加载后,I2C通信失败,无法正确读取GSV2001的寄存器。
原因分析:通过示波器抓取I2C总线波形对比发现,问题出在I2C读操作的时序上。最初的实现中,写寄存器地址和读数据之间产生了STOP + START信号,而GSV2001要求的是RESTART。对比GSV2001规格书中的I2C时序要求,确认了正确的读操作应遵循以下格式:
START -> 写设备地址(0xB0) -> 写寄存器地址(16bit) -> RESTART -> 读设备地址(0xB1) -> 读数据 -> STOP

解决方案:使用i2c_transfer配合两个i2c_msg消息(写地址 + 读数据),由内核I2C框架自动产生RESTART信号。
2.2 EDID 写入失败
现象:I2C写操作在写入EDID数据时失败,但写小数据块正常。
原因分析:代码中的写缓冲区tx_buf申请了256字节,但EDID数据为256字节,加上2字节的寄存器地址前缀,实际需要发送258字节。数组越界导致内存踩踏,写入失败。
解决方案:将tx_buf的分配大小调整大一些,动态分配确保空间充足。
总结
本文基于RK3568平台和Android 13 SDK,完成了GSV2001 HDMI分流芯片的驱动移植工作。希望本文能为在Linux/Android平台上适配GSV2001系列芯片或其他I2C外设的开发者提供有价值的参考
215

被折叠的 条评论
为什么被折叠?



