前言
车载项目中串行/解串器是十分常见的外设,目前常用的有两种标准:GMSL(美信家的)、FPD-Link(TI家的)。本次基于美信的一对Ser/Deser实现:串行端将HDMI源视频信号转为GMSL2串行信号,解串端解串后转为LVDS格式连接显示屏,并在屏幕上显示。
一、环境介绍
SOC:Amlogic A311D2 SDK:Amlogic Android 11 Kernel:5.4 Serializer:MAX96763 Deserializer:MAX96752F
系统框图:
二、硬件配置
1. MAX96763
GPIO17/I2CSEL:low(Main Uart mode) GPIO16/CXTP:low(TP双绞线) ADD0~ADD2:000(Dev Addr=0x80) 确保: HPD:high(即SOC的HDMI TX与MAX96763连接上) PWDNB:high(MAX96763 ready)
2. MAX96752F
GPIO01/I2CSEL:low(Main Uart mode) GPIO09/CXTP:low(TP双绞线) ADD0~ADD2:010(Dev Addr=0x98) 确保: PWDNB:high(MAX96752F ready)
三、串口通信协议
1. 帧格式
START(1bit) : 固定填0 DATA(8bit) : 8位数据 EVEN PARITY(1bit) :1位偶校验,1表示DATA的8位中1的个数为奇数;0表示DATA的8位中1的个数为偶数。 STOP(至少1bit,最多4bit): 固定填1
2. 同步帧
START D0 D1 D2 D3 D4 D5 D6 D7 PARITY STOP
0 1 0 0 1 1 1 1 0 1 1 // 0x79
3. 应答帧
START D0 D1 D2 D3 D4 D5 D6 D7 PARITY STOP
0 1 1 0 0 0 0 1 1 0 1 // 0xC3
4. 包格式(包由帧组成)
写操作(地址帧的bit0置0):
SYNC帧 | DEV ADDR帧(LSB=0) | REG ADDR(MSB) | REG ADDR(LSB) | BYTE COUNT | DATA1 | ········ | DATA N
读操作(地址帧的bit0置1):
SYNC帧 | DEV ADDR帧(LSB=1) | REG ADDR(MSB) | REG ADDR(LSB) | BYTE COUNT
示例: 读MAX96763的DEV ID,即读0x000D寄存器(应返回:C3 B2):
79 81 00 0D 01
读MAX96752F的DEV ID,即读0x000D寄存器(应返回:C3 82):
79 99 00 0D 01
四、内核模块实现
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/console.h>
#include <linux/clk.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/sysrq.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/serial_bcm63xx.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/kthread.h>
#define MAX96763_DEV_ADDR 0x80
#define MAX96752_DEV_ADDR 0x98
#define MAX96763_DEV_ID 0xB2
#define MAX96752_DEV_ID 0x82
#define TTY_NAME "/dev/ttyS3"
#define TTY_SPEED 921600
#define TRY_TIMES 10
#define SCHE_TIMEOUT msecs_to_jiffies(1000)
static struct task_struct *server_thread;
static int max96763_main_uart_set_termios(struct file *filp)
{
struct tty_file_private *priv = NULL;
struct tty_struct *tty = NULL;
priv = filp->private_data;
if(IS_ERR(priv)) {
pr_err("%s %d priv is NULL\n", __func__, __LINE__);
return -1;
}
else {
tty = priv->tty;
}
if(IS_ERR(tty)) {
pr_err("%s %d tty is NULL\n", __func__, __LINE__);
return -1;
}
else {
struct ktermios ktermios = tty->termios;
ktermios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
ktermios.c_oflag &= ~OPOST;
ktermios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
ktermios.c_cflag |= CLOCAL;
ktermios.c_cflag &= ~CBAUD;
tty_termios_encode_baud_rate(&ktermios, TTY_SPEED, TTY_SPEED);
ktermios.c_cflag &= ~CSIZE;
ktermios.c_cflag |= CS8;
ktermios.c_cflag &= ~(PARENB | PARODD | CMSPAR);
ktermios.c_cflag |= PARENB;
ktermios.c_cflag &= ~CSTOPB;
ktermios.c_cflag &= ~CRTSCTS;
tty_set_termios(tty, &ktermios);
}
pr_info("%s %d ok\n", __func__, __LINE__);
return 0;
}
static void max96763_main_uart_config_reg(struct file *filp)
{
unsigned char cmd_1[6]={ 0x79, MAX96752_DEV_ADDR, 0x00, 0x01, 0x01, 0x02 };
unsigned char cmd_2[6]={ 0x79, MAX96752_DEV_ADDR, 0x01, 0xCE, 0x01, 0x5E };
unsigned char cmd_3[6]={ 0x79, MAX96752_DEV_ADDR, 0x00, 0x02, 0x01, 0x43 };
unsigned char cmd_4[6]={ 0x79, MAX96752_DEV_ADDR, 0x02, 0x06, 0x01, 0x83 };
unsigned char cmd_5[6]={ 0x79, MAX96752_DEV_ADDR, 0x02, 0x07, 0x01, 0x27 };
unsigned char cmd_6[6]={ 0x79, MAX96763_DEV_ADDR, 0x00, 0x01, 0x01, 0x88 };
unsigned char cmd_206[6]={ 0x79, MAX96763_DEV_ADDR, 0x02, 0x06, 0x01, 0x84 };
unsigned char cmd_207[6]={ 0x79, MAX96763_DEV_ADDR, 0x02, 0x07, 0x01, 0x20 };
unsigned char cmd_208[6]={ 0x79, MAX96763_DEV_ADDR, 0x02, 0x08, 0x01, 0x87 };
unsigned char cmd_7[6]={ 0x79, MAX96763_DEV_ADDR, 0x00, 0x10, 0x01, 0x31 };
unsigned char cmd_a[6]={ 0x79, MAX96763_DEV_ADDR|0x01, 0x00, 0x0D, 0x01 };
unsigned char cmd_b[6]={ 0x79, MAX96752_DEV_ADDR|0x01, 0x00, 0x0D, 0x01 };
unsigned char cmd_c[13]={ 0x79, 0x6c, 0x00, 0x02, 0x08, 0x09, 0x00, 0x01, 0x01, 0x53, 0xE1, 0x70, 0x43 };
unsigned char cmd_e[13]={ 0x79, 0x6c, 0x00, 0x00, 0x08, 0x09, 0x00, 0x03, 0x01, 0x2C, 0x1F, 0xB3, 0xCA };
unsigned char cmd_f[13]={ 0x79, 0x6c, 0x00, 0x00, 0x08, 0x01, 0x00, 0x01, 0x00, 0xAC, 0x9A, 0xC9, 0x31 };
loff_t pos = 0;
unsigned int size = 0;
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
size = vfs_write(filp, cmd_a, sizeof(cmd_a), &pos);
pr_info("[%s] write cmd_a %d bytes to file %s\n", __func__, size, TTY_NAME);
#if 0
size = vfs_read(filp, buf, buf_len, &pos);
pr_info("[%s] read %d bytes from file %s, buf = %X %x\n", __func__, size, TTY_NAME, buf[0], buf[1]);
if(buf[0]==0xC3 && buf[1]==MAX96763_DEV_ID)
{
pr_info("[%s] MAX96763 found!\n", __func__);
}else {
pr_err("[%s] MAX96763 not found!\n", __func__);
goto PROCESS_END;
}
memset(buf, 0, buf_len);
#endif
size = vfs_write(filp, cmd_b, sizeof(cmd_b), &pos);
pr_info("[%s] write cmd_b %d bytes to file %s\n", __func__, size, TTY_NAME);
#if 0
size = vfs_read(filp, buf, buf_len, &pos);
pr_info("[%s] read %d bytes from file %s, buf = %X %x\n", __func__, size, TTY_NAME, buf[0], buf[1]);
if(buf[0]==0xC3 && buf[1]==MAX96752_DEV_ID)
{
pr_info("[%s] MAX96752 found!\n", __func__);
}else {
pr_err("[%s] MAX96752 not found!\n", __func__);
goto PROCESS_END;
}
#endif
size = vfs_write(filp, cmd_1, sizeof(cmd_1), &pos);
pr_info("[%s] write cmd_1 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_2, sizeof(cmd_2), &pos);
pr_info("[%s] write cmd_2 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_3, sizeof(cmd_3), &pos);
pr_info("[%s] write cmd_3 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_4, sizeof(cmd_4), &pos);
pr_info("[%s] write cmd_4 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_5, sizeof(cmd_5), &pos);
pr_info("[%s] write cmd_5 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_6, sizeof(cmd_6), &pos);
pr_info("[%s] write cmd_6 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_206, sizeof(cmd_206), &pos);
pr_info("[%s] write cmd_206 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_207, sizeof(cmd_207), &pos);
pr_info("[%s] write cmd_207 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_208, sizeof(cmd_208), &pos);
pr_info("[%s] write cmd_208 %d bytes to file %s\n", __func__, size, TTY_NAME);
size = vfs_write(filp, cmd_7, sizeof(cmd_7), &pos);
pr_info("[%s] write cmd_7 %d bytes to file %s\n", __func__, size, TTY_NAME);
msleep(10);
size = vfs_write(filp, cmd_c, sizeof(cmd_c), &pos);
pr_info("[%s] write cmd_c %d bytes to file %s\n", __func__, size, TTY_NAME);
msleep(10);
size = vfs_write(filp, cmd_e, sizeof(cmd_e), &pos);
pr_info("[%s] write cmd_e %d bytes to file %s\n", __func__, size, TTY_NAME);
msleep(10);
size = vfs_write(filp, cmd_f, sizeof(cmd_f), &pos);
pr_info("[%s] write cmd_f %d bytes to file %s\n", __func__, size, TTY_NAME);
pr_info("[%s] -------------------- OK!\n", __func__);
set_fs(old_fs);
return;
}
static int max96763_main_uart_config_thread(void *unused)
{
struct file *filp = NULL;
int try = TRY_TIMES;
while (!kthread_should_stop() && try--) {
filp = filp_open(TTY_NAME, O_RDWR|O_NOCTTY, 0);
if (IS_ERR(filp)) {
pr_err("%s: cannot open %s\n", __func__, TTY_NAME);
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(SCHE_TIMEOUT);
}else {
pr_info("%s: open %s success!\n", __func__, TTY_NAME);
if(max96763_main_uart_set_termios(filp)==0) {
max96763_main_uart_config_reg(filp);
} else {
pr_err("%s: set %s termios failed!\n", __func__, TTY_NAME);
}
filp_close(filp, NULL);
break;
}
}
pr_info("%s %d exit, try %d\n", __func__, __LINE__, try);
return 0;
}
static int __init max96763_main_uart_init(void)
{
int rc;
server_thread = kthread_run(max96763_main_uart_config_thread, NULL, "max96763_config");
if (IS_ERR(server_thread)) {
rc = PTR_ERR(server_thread);
pr_err("%s %d kthread_run rc %d\n", __func__, __LINE__, rc);
return -1;
}
pr_info("%s %d ok\n", __func__, __LINE__);
return 0;
}
static void __exit max96763_main_uart_exit(void)
{
pr_info("%s %d\n", __func__, __LINE__);
}
module_init(max96763_main_uart_init);
module_exit(max96763_main_uart_exit);
MODULE_AUTHOR("rentong <rentong@skyworth.com>");
MODULE_DESCRIPTION("This module is used to config MAXIN chips through serial port");
MODULE_LICENSE("GPL");
|