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

📅 2026/8/16 21:55:59
rk3568安卓13驱动适配-HDMI分流(GSV2001)
本文记录工作中基于rk3568安卓13的SDK适配板载外设GSV2001芯片驱动时具体的修改项和问题目录前言一、GSV2001工作原理梳理二、GSV2001软件适配1.驱动移植1.1 底层接口移植1.2 Main函数移植1.3 设备树文件2.问题记录2.1 I2C读协议错误2.2 edid写入失败总结前言GSV2001 是一款由 GSCoolink基石酷联生产的 HDMI 2.0 芯片‌主要功能是将 1 路 HDMI 信号分配为 2 路输出支持 4K 高清视频传输与音频处理 。‌‌‌本文介绍如何在rk3568安卓13的SDK中适配GSV2001让其能正常工作。一、GSV2001工作原理梳理通过芯片规格书和硬件设计原理可以看出主控通过I2C对GSV芯片的寄存器进行配置使其正常工作。官方有提供应用SDK不过是跑在STM32F030C8裸机上的需要移植到RK3568上当作驱动去运行。从硬件上可以看出工作很简单匹配多个电源后控制复位引脚拉低10ms以上即可复位然后通过I2C对GSV芯片进行轮询配置。二、GSV2001软件适配1.驱动移植1.1 底层接口移植首先需要把底层I2C读写接口还有串口打印移植到linux驱动上去都有现成的接口需要注意I2C读时是起始位设备地址写位ACK寄存器地址高字节ACK寄存器地址低字节ACKReStart设备地址读数据一定要注意那个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: dev0x%02x reg0x%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: dev0x%02x reg0x%04x ret%d\n, dev_addr, reg_addr, ret); return -EIO; } return len; }SDK中的I2C源文件用普通IO模拟的这里可以删除掉只需要改写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: dev0x%02x reg0x%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: dev0x%02x reg0x%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 Main函数移植将官方提供的av_main.c文件中的main函数改写成linux驱动中的工作队列形式定时执行。修改后的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 users 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 设备树文件在板级设备树文件中配置I2C需要注意从源码SDK中能看出设备地址是0x58这里是7位和I2C的协议有关7位地址位第八位是读写位也就是读的话是0xB1,写的话是0xB0,linux底层中I2C的接口都给处理好了。i2c3 { status okay; gsv2001: gsv200158 { compatible gsv,gsv2001; reg 0x58; reset-gpios gpio0 RK_PB6 GPIO_ACTIVE_LOW; status okay; }; };2.问题记录这里记录我调试过程中遇到的问题2.1 I2C读协议错误一开始用以下接口实现的I2C读导致restart信号变成了stop信号gsv芯片不响应。是通过示波器抓I2C总线上的波形对比gsv正常I2C通信才发现的这个错误gsv规格书中关于I2C时序也有列出这点。/* 从 16位 寄存器读一个字节 */ int gsv_i2c_read_reg(u32 dev_addr, u16 reg, u8 *val) { u8 buf[2]; buf[0] (reg 8) 0xFF; buf[1] reg 0xFF; if (i2c_master_send(g_client, buf, 2) 0) return -EIO; return i2c_master_recv(g_client, val, 1); }2.2 edid写入失败编写写接口时会申请tx_buf数组一般我们都喜欢申请256但GSV芯片驱动程序中会写edid加上寄存器地址长度正好257刚好超界导致写入失败。这里需要申请大一些。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); }总结本文介绍了本人在工作过程中适配GSV2001芯片驱动时具体的修改项和遇到的问题为gsv同系列芯片在linux下适配提供了参考。程序包下载链接如下已测试可用。https://download.csdn.net/download/x150061/93278461