rk3568安卓13驱动适配-HDMI分流(GSV2001)

本文记录工作中基于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内核对应实现
BspI2cReadI2C读操作(16位寄存器地址)i2c_transfer + i2c_msg
BspI2cWriteI2C写操作(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外设的开发者提供有价值的参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

又见情义

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值