当前位置: 首页 > news >正文

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+寄存器地址低字节+ACK+ReStart+设备地址+读数据;一定要注意那个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; }

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: 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 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 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 设备树文件

在板级设备树文件中配置I2C,需要注意从源码SDK中能看出设备地址是0x58,这里是7位,和I2C的协议有关,7位地址位第八位是读写位,也就是读的话是0xB1,写的话是0xB0,linux底层中I2C的接口都给处理好了。

&i2c3 { status = "okay"; gsv2001: gsv2001@58 { 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

http://www.jsqmd.com/news/1407227/

相关文章:

  • 2026厦门本地靠谱GEO优化公司推荐6家盘点:AI搜索服务商实力评估、选型标准与签约避坑FAQ详解 - 产业观察报
  • 手机智谱清言导出表格,别再忍受公式变乱码了!“AI 导出鸭”一键无损编译
  • 【OI】动态规划(入门)
  • 养虾热潮背后的技术本质与经济理性:从水质管理到生态平衡的深度解析
  • 拒绝“代码幻觉”与时区屎山:为什么 2026 年量化开发者都在从 AkShare/Tushare 转向 QuantDash?
  • 湖北专升本公办院校录取率真实推荐 - 新闻快传
  • 方向导数和梯度
  • 企业微信扫码一键登录OpenClaw:腾讯云身份中台实战指南
  • 银河麒麟V10服务器安装与初始化实战指南:从分区到Docker部署
  • 2026深圳GEO服务商推荐盘点:6家实力较强的优化公司解析,科技与外贸企业合作选择指南+签约避坑FAQ详解 - 商业大观
  • 二、AFSIM核心应用
  • SVN高效开发:配置Beyond Compare实现专业文件对比与三向合并
  • OpenClaw智能体框架实战:29个真实场景用例,从安装到生产力倍增
  • JavaWeb图书管理系统实战:从Servlet/JSP到MVC架构的完整开发指南
  • 2026年电话机器人品牌深度解析:谁更适合企业高转化获客 - 新闻快传
  • 【原】API安全学习总结
  • 湖北哪三家专升本机构实力最强 - 新闻快传
  • flutter等跨平台app上架appStore的13和6.5英寸图片和视频获取
  • 一图告诉你什么是物料编码 - 智慧园区
  • 锤子助手第099个开关:禁用设置页面个人信息与权限的位置、安全检查与隐私控制边界
  • 二叉树经典例题
  • Module Builder——Gem300之E40 ProcessJob模块
  • 《天道》23‑24集观后感
  • OpenClaw持久化记忆实现:从向量数据库到混合架构的实践指南
  • 18款吉利帝豪GL海拉透镜改装:郑州新郑阳迪车灯实测效果分享 - 阳迪小师傅
  • 微PE工具箱纯净安装Win11:从U盘制作到系统部署全攻略
  • 制造企业如何沉淀资深质量工程师经验,借助云上 AI Agent 搭建企业知识资产?:优先评估 Amazon Quick+Bedrock AgentCore
  • 2026化州免费量尺设计的瓷砖商家哪家靠谱?本地人避坑指南 - 新闻快传
  • VSCode Git提交插件:告别混乱提交历史,实现自动化规范
  • 复制一次,然后让它自己消失,我用蓝耘MaaS automodel模型两天搓出会阅后即焚的剪贴板