基于WK2204在RK3588上的SPI 扩展多串口方案
基于WK2204在RK3588上扩展多路串口的方案。
1、概述
1.1 WK2204芯片简介
WK2204是一款多功能的串口扩展芯片。支持SPI、IIC、UART扩展4串口。
每个子通道UART的波特率、字长、校验格式可以独立设置,最高可以提供2Mbps的通信速率。
每个子通道具备收/发独立的256 级FIFO,FIFO的中断可按用户需求进行编程触发点且具备超时中断功能。
出了WK2204,也可以用WK2124\WK2168等芯片实现如下功能。
1.2 RK3588简介
基于 Rockchip 全新一代旗舰 AIoT 芯片 – RK3588,采用了 8nm LP 制程;搭载八核(Cortex-A76 x 4 + Cortex-A55 x 4)64位 CPU,主频高达2.4 GHz。集成 ARM Mali-G610 MP4 四核 GPU,内置 AI 加速器 NPU,可提供6 Tops 算力,支持主流的深度学习框架;最大支持32 GB 大内存;支持 8K 视频编解码和多种格式的视频输入输出;支持多 种操作系统;可适用于 ARM PC、边缘计算、云服务器、智能NVR 等领域。
本文采用的硬件设备是ROC-RK3588S-PC + WK2204(WK2168)扩展板。
2、硬件方案介绍
本文用的开发板是 ROC-RK3588S-PC ,开发板自带一路SPI。
硬件连接示意图:

ROC-RK3588S-PC 提供了一组SPI和GPIO,但是这些IO都是1.8V.不能和WK2204或者WK2168直接连接。所以中间做了一个1.8V-3.3V的电平转换。 WK2204用的是3.3V供电。
SPI接口电平转换如下:

IRQ和RESET 也类似。
3、驱动设计
3.1 驱动基本框架
如下图为WK驱动和系统之间关系示意图。

- WK驱动工作在linux 内核层,向上提供4个串口设备节点供应用层用户调用。也就是说WK驱动注册成功以后,在/dev/ 目录下会生成 ttysWK0、ttysWK1、ttysWK2、ttysWK3 共4个串口设备节点,应用层就可以按照操作普通串口节点的方式操作。
- WK驱动需要和WK芯片进行数据交互,数据交互是通过SPI总线进行的,所以WK驱动会调用SPI总线驱动接口进行数据收发。
4、驱动源码分析
驱动源代码放在wk2xxx_spi.c
1、 串口驱动描述
鉴于芯片的相关特性和驱动编写的需要,定义了结构体 wk2xxx_port用于对WK的SPI转串口驱动进行描述。程序清单入下所示
struct wk2xxx_port
{
const struct wk2xxx_devtype *devtype;
struct uart_driver uart;
struct spi_device *spi_wk;
struct workqueue_struct *workqueue;
struct work_struct work;
unsigned char buf[256];
struct kthread_worker kworker;
struct task_struct *kworker_task;
struct kthread_work irq_work;
//int cs_gpio_num;
uint8_t gena_reg;
uint8_t gier_reg;
int irq_gpio_num;
int rst_gpio_num;
int irq_gpio;
int minor; /* minor number */
int tx_empty;
struct wk2xxx_one p[NR_PORTS];
};
2、串口端口描述
定义一个结构体wk2xxx_one来描述WK2xxx芯片的串口端口进行描述,实际上是对uart_port的进一步封装,增加了两个内核队列和芯片子串口一些寄存器。程序如下:
struct wk2xxx_one
{
struct uart_port port; //[NR_PORTS];
struct kthread_work startup_work;
struct kthread_work termios_work;
struct kthread_work start_tx_work;
struct kthread_work stop_tx_work;
struct kthread_work stop_rx_work;
struct kthread_work tx_empty_work;
uint8_t line;
int tx_empty;
uint8_t new_lcr_reg;
uint8_t new_fwcr_reg;
uint8_t new_scr_reg;
/*baud register*/
uint8_t new_baud1_reg;
uint8_t new_baud0_reg;
uint8_t new_pres_reg;
};
4.1 读全局寄存器函数
该函数调用SPI接口,实现读WK2XXX芯片的全局寄存器,全局寄存器通常包括GENA 、GRST、GIER、GIFR、GMUT、GPDIR、GPDAT等
如下是读寄存器的操作协议
具体实现源码如下:
/*
* This function read wk2xxx of Global register:
*/
static int wk2xxx_read_global_reg(struct spi_device *spi,uint8_t reg,uint8_t *dat)
{
struct spi_message msg;
uint8_t buf_wdat[2];
uint8_t buf_rdat[2];
int status;
struct spi_transfer index_xfer = {
.len = 2,
.speed_hz = wk2xxx_spi_speed,
};
mutex_lock(&wk2xxxs_reg_lock);
status =0;
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 0);
#endif
spi_message_init(&msg);
buf_wdat[0] = 0x40|reg;
buf_wdat[1] = 0x00;
buf_rdat[0] = 0x00;
buf_rdat[1] = 0x00;
index_xfer.tx_buf = buf_wdat;
index_xfer.rx_buf =(void *) buf_rdat;
spi_message_add_tail(&index_xfer, &msg);
status = spi_sync(spi, &msg);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 1);
#endif
mutex_unlock(&wk2xxxs_reg_lock);
if(status){
return status;
}
*dat = buf_rdat[1];
return 0;
}
4.2 写全局寄存器函数
该函数调用SPI接口,实现对WK2XXX芯片的全局寄存器写操作,全局寄存器通常包括GENA 、GRST、GIER、GIFR、GMUT、GPDIR、GPDAT等。具体需要结合数据手册操作时序分析:
如下写寄存器操作协议:

源码如下:
/*
* This function write wk2xxx of Global register:
*/
static int wk2xxx_write_global_reg(struct spi_device *spi,uint8_t reg,uint8_t dat)
{
struct spi_message msg;
uint8_t buf_reg[2];
int status;
struct spi_transfer index_xfer = {
.len = 2,
.speed_hz = wk2xxx_spi_speed,
};
mutex_lock(&wk2xxxs_reg_lock);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 0);
#endif
spi_message_init(&msg);
/* register index */
buf_reg[0] = 0x00|reg;
buf_reg[1] = dat;
index_xfer.tx_buf = buf_reg;
spi_message_add_tail(&index_xfer, &msg);
status = spi_sync(spi, &msg);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 1);
#endif
mutex_unlock(&wk2xxxs_reg_lock);
return status;
}
4.3 读子串口寄存器函数
读芯片子串口的操作函数,具体可以结合数据手册解读。
读寄存器协议如下:

源码如下:
/*
* This function read wk2xxx of slave register:
*/
static int wk2xxx_read_slave_reg(struct spi_device *spi,uint8_t port,uint8_t reg,uint8_t *dat)
{
struct spi_message msg;
uint8_t buf_wdat[2];
uint8_t buf_rdat[2];
int status;
struct spi_transfer index_xfer = {
.len = 2,
.speed_hz = wk2xxx_spi_speed,
};
mutex_lock(&wk2xxxs_reg_lock);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 0);
#endif
status =0;
spi_message_init(&msg);
buf_wdat[0] = 0x40|(((port-1)<<4)|reg);
buf_wdat[1] = 0x00;
buf_rdat[0] = 0x00;
buf_rdat[1] = 0x00;
index_xfer.tx_buf = buf_wdat;
index_xfer.rx_buf =(void *) buf_rdat;
spi_message_add_tail(&index_xfer, &msg);
status = spi_sync(spi, &msg);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 1);
#endif
mutex_unlock(&wk2xxxs_reg_lock);
if(status){
return status;
}
*dat = buf_rdat[1];
return 0;
}
4.4 写子串口寄存器函数
写寄存器协议如下:

源码如下:
/*
* This function write wk2xxx of Slave register:
*/
static int wk2xxx_write_slave_reg(struct spi_device *spi,uint8_t port,uint8_t reg,uint8_t dat)
{
struct spi_message msg;
uint8_t buf_reg[2];
int status;
struct spi_transfer index_xfer = {
.len = 2,
.speed_hz = wk2xxx_spi_speed,
};
mutex_lock(&wk2xxxs_reg_lock);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 0);
#endif
spi_message_init(&msg);
/* register index */
buf_reg[0] = ((port-1)<<4)|reg;
buf_reg[1] = dat;
index_xfer.tx_buf = buf_reg;
spi_message_add_tail(&index_xfer, &msg);
status = spi_sync(spi, &msg);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 1);
#endif
mutex_unlock(&wk2xxxs_reg_lock);
return status;
}
4.5 写FIFO函数
写fifo操作,通过该协议能直接快速的把需要传输的串口数据,直接写入芯片的子串口发送FIFO.
写fifo操作协议:

实现源代码如下:
/*
* This function write wk2xxx of fifo:
*/
static int wk2xxx_write_fifo(struct spi_device *spi,uint8_t port,uint8_t fifolen,uint8_t *dat)
{
struct spi_message msg;
int status,i;
uint8_t recive_fifo_data[MAX_RFCOUNT_SIZE+1]={0};
uint8_t transmit_fifo_data[MAX_RFCOUNT_SIZE+1]={0};
struct spi_transfer index_xfer = {
.len = fifolen+1,
.speed_hz = wk2xxx_spi_speed,
};
if(!(fifolen>0)){
printk(KERN_ERR "%s,fifolen error,fifolen:%d!!\n", __func__,fifolen);
return 1;
}
mutex_lock(&wk2xxxs_reg_lock);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 0);
#endif
spi_message_init(&msg);
/* register index */
transmit_fifo_data[0] = ((port-1)<<4)|0x80;
for(i=0;i<fifolen;i++){
transmit_fifo_data[i+1]=*(dat+i);
}
index_xfer.tx_buf = transmit_fifo_data;
index_xfer.rx_buf =(void *) recive_fifo_data;
spi_message_add_tail(&index_xfer, &msg);
status = spi_sync(spi, &msg);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 1);
#endif
mutex_unlock(&wk2xxxs_reg_lock);
return status;
}
4.6 读FIFO函数
读FIFO的操作是可以直接把接收FIFO中的数据,连续的读出来。
读FIFO操作的协议如下:

读FIFO操作的源码如下:
/*
* This function read wk2xxx of fifo:
*/
static int wk2xxx_read_fifo(struct spi_device *spi,uint8_t port,uint8_t fifolen,uint8_t *dat)
{
struct spi_message msg;
int status,i;
uint8_t recive_fifo_data[MAX_RFCOUNT_SIZE+1]={0};
uint8_t transmit_fifo_data[MAX_RFCOUNT_SIZE+1]={0};
struct spi_transfer index_xfer = {
.len = fifolen+1,
.speed_hz = wk2xxx_spi_speed,
};
if(!(fifolen>0)){
printk(KERN_ERR "%s,fifolen error!!\n", __func__);
return 1;
}
mutex_lock(&wk2xxxs_reg_lock);
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 0);
#endif
spi_message_init(&msg);
/* register index */
transmit_fifo_data[0] = ((port-1)<<4)|0xc0;
index_xfer.tx_buf = transmit_fifo_data;
index_xfer.rx_buf =(void *) recive_fifo_data;
spi_message_add_tail(&index_xfer, &msg);
status = spi_sync(spi, &msg);
for(i=0;i<fifolen;i++)
*(dat+i)=recive_fifo_data[i+1];
#ifdef WK_CSGPIO_FUNCTION
gpio_set_value(cs_gpio_num, 1);
#endif
mutex_unlock(&wk2xxxs_reg_lock);
return status;
}
4.7 打开串口
用户空调用open()函数的时候,驱动层的wk2xxx_startup()函数会被调用,该函数中会加载thread。具体的实现的函数是static void wk2xxx_startup_work_proc(struct kthread_work *ws)。
static int wk2xxx_startup(struct uart_port *port)//i
{
//uint8_t gena,grst,gier,sier,scr,dat[1];
struct wk2xxx_port *s = dev_get_drvdata(port->dev);
struct wk2xxx_one *one = to_wk2xxx_one(port, port);
bool ret;
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--in--\n", __func__,one->port.iobase);
#endif
#ifdef WK_WORK_KTHREAD
ret=kthread_queue_work(&s->kworker, &one->startup_work);
#else
ret=queue_kthread_work(&s->kworker, &one->startup_work);
#endif
uart_circ_clear(&one->port.state->xmit);
wk2xxx_enable_ms(&one->port);
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--exit--\n", __func__,one->port.iobase);
#endif
return 0;
}
static void wk2xxx_startup_work_proc(struct kthread_work *ws)
4.8 关闭串口
当用户空间调用close的时候,驱动的wk2xxx_shut()函数会被调用。
static void wk2xxx_shutdown(struct uart_port *port)
{
uint8_t gena,grst,gier;
//struct wk2xxx_port *s = container_of(port,struct wk2xxx_port,port);
struct wk2xxx_port *s = dev_get_drvdata(port->dev);
struct wk2xxx_one *one = to_wk2xxx_one(port, port);
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--in--\n", __func__,one->port.iobase);
#endif
mutex_lock(&wk2xxxs_global_lock);
//wk2xxx_read_global_reg(s->spi_wk,WK2XXX_GIER_REG,&gier);
gier=s->gier_reg;
switch (one->port.iobase){
case 1:
gier&=~WK2XXX_GIER_UT1IE_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GIER_REG,gier);
break;
case 2:
gier&=~WK2XXX_GIER_UT2IE_BIT;;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GIER_REG,gier);
break;
case 3:
gier&=~WK2XXX_GIER_UT3IE_BIT;;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GIER_REG,gier);
break;
case 4:
gier&=~WK2XXX_GIER_UT4IE_BIT;;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GIER_REG,gier);
break;
default:
printk(KERN_ALERT "%s!! (GIER)bad iobase %d\n",__func__, (uint8_t)one->port.iobase);;
break;
}
s->gier_reg=gier;
wk2xxx_write_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_SIER_REG,0x0);
mutex_unlock(&wk2xxxs_global_lock);
#ifdef WK_WORK_KTHREAD
kthread_flush_work(&one->startup_work);
kthread_flush_work(&one->termios_work);
kthread_flush_work(&one->start_tx_work);
kthread_flush_work(&one->stop_tx_work);
kthread_flush_work(&one->tx_empty_work);
kthread_flush_work(&one->stop_rx_work);
//kthread_flush_work(&s->irq_work);
//kthread_flush_worker(&s->kworker);
#else
flush_kthread_work(&one->startup_work);
flush_kthread_work(&one->termios_work);
flush_kthread_work(&one->start_tx_work);
flush_kthread_work(&one->stop_tx_work);
flush_kthread_work(&one->tx_empty_work);
flush_kthread_work(&one->stop_rx_work);
//flush_kthread_work(&s->irq_work);
//flush_kthread_worker(&s->kworker);
#endif
mutex_lock(&wk2xxxs_global_lock);
grst=0;
switch (one->port.iobase){
case 1:
grst|=WK2XXX_GRST_UT1RST_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GRST_REG,grst);
break;
case 2:
grst|=WK2XXX_GRST_UT2RST_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GRST_REG,grst);
break;
case 3:
grst|=WK2XXX_GRST_UT3RST_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GRST_REG,grst);
break;
case 4:
grst|=WK2XXX_GRST_UT4RST_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GRST_REG,grst);
break;
default:
printk(KERN_ALERT "%s!! bad iobase %d\n",__func__, (uint8_t)one->port.iobase);
break;
}
//wk2xxx_read_global_reg(s->spi_wk,WK2XXX_GENA_REG,dat);
//gena=dat[0];
gena=s->gena_reg;
switch (one->port.iobase){
case 1:
gena&=~WK2XXX_GENA_UT1EN_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GENA_REG,gena);
break;
case 2:
gena&=~WK2XXX_GENA_UT2EN_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GENA_REG,gena);
break;
case 3:
gena&=~WK2XXX_GENA_UT3EN_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GENA_REG,gena);
break;
case 4:
gena&=~WK2XXX_GENA_UT4EN_BIT;
wk2xxx_write_global_reg(s->spi_wk,WK2XXX_GENA_REG,gena);
break;
default:
printk(KERN_ALERT "%s!! bad iobase %d\n",__func__, (uint8_t)one->port.iobase);;
break;
}
s->gena_reg=gena;
mutex_unlock(&wk2xxxs_global_lock);
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--exit--\n", __func__,one->port.iobase);
#endif
}
4.9 设置波特率与数据格式
设置波特率被和数据格式被调用的函数。
static void wk2xxx_termios( struct uart_port *port, struct ktermios *termios,const struct ktermios *old)
{
struct wk2xxx_port *s = dev_get_drvdata(port->dev);
struct wk2xxx_one *one = to_wk2xxx_one(port, port);
int baud = 0;
uint32_t temp=0,freq=0;
uint8_t lcr=0,fwcr=0,baud1=0,baud0=0,pres=0,bParityType=0;
bool ret;
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--in--\n", __func__,one->port.iobase);
printk(KERN_ALERT "%s!!---c_cflag:0x%x,c_iflag:0x%x.\n",__func__,termios->c_cflag,termios->c_iflag);
#endif
baud1=0;
baud0=0;
pres=0;
baud = tty_termios_baud_rate(termios);
freq=one->port.uartclk;
if(freq>=(baud*16)){
temp=(freq)/(baud*16);
temp=temp-1;
baud1=(uint8_t)((temp>>8)&0xff);
baud0=(uint8_t)(temp&0xff);
temp=(((freq%(baud*16))*100)/(baud));
pres=(temp+100/2)/100;
#ifdef _DEBUG_WK_TEST
printk(KERN_ALERT "%s!!---freq:%d,baudrate:%d\n",__func__,freq,baud);
printk(KERN_ALERT "%s!!---baud1:%x,baud0:%x,pres:%x\n",__func__,baud1,baud0,pres);
#endif
}else{
printk(KERN_ALERT "the baud rate:%d is too high! \n",baud);
}
tty_termios_encode_baud_rate(termios, baud, baud);
lcr =0;
if (termios->c_cflag & CSTOPB)
lcr|=WK2XXX_LCR_STPL_BIT;//two stop_bits
else
lcr&=~WK2XXX_LCR_STPL_BIT;//one stop_bits
bParityType = termios->c_cflag & PARENB ?(termios->c_cflag & PARODD ? 1 : 2) +(termios->c_cflag & CMSPAR ? 2 : 0) : 0;
if (termios->c_cflag & PARENB) {
lcr|=WK2XXX_LCR_PAEN_BIT;//enbale spa
switch (bParityType) {
case 0x01: //ODD
lcr |= WK2XXX_LCR_PAM0_BIT;
lcr &= ~WK2XXX_LCR_PAM1_BIT;
break;
case 0x02: //EVEN
lcr |= WK2XXX_LCR_PAM1_BIT;
lcr &= ~WK2XXX_LCR_PAM0_BIT;
break;
case 0x03: //MARK--1
lcr |= WK2XXX_LCR_PAM1_BIT|WK2XXX_LCR_PAM0_BIT;
break;
case 0x04: //SPACE--0
lcr &= ~WK2XXX_LCR_PAM1_BIT;
lcr &= ~WK2XXX_LCR_PAM0_BIT;
break;
default:
lcr &= ~WK2XXX_LCR_PAEN_BIT;
break;
}
}
/* Set read status mask */
port->read_status_mask = WK2XXX_LSR_OE_BIT;
if (termios->c_iflag & INPCK)
port->read_status_mask |= WK2XXX_LSR_PE_BIT |
WK2XXX_LSR_FE_BIT;
if (termios->c_iflag & (BRKINT | PARMRK))
port->read_status_mask |= WK2XXX_LSR_BI_BIT;
/* Set status ignore mask */
port->ignore_status_mask = 0;
if (termios->c_iflag & IGNBRK)
port->ignore_status_mask |= WK2XXX_LSR_BI_BIT;
if (!(termios->c_cflag & CREAD))
port->ignore_status_mask |= WK2XXX_LSR_BRK_ERROR_MASK;
#ifdef WK_FlowControl_FUNCTION
/* Configure flow control */
if (termios->c_cflag & CRTSCTS){
fwcr=0X30;
printk(KERN_ALERT "wk2xxx_termios(2)----port:%lx;lcr:0x%x;fwcr:0x%x---\n",one->port.iobase,lcr,fwcr);
}
if (termios->c_iflag & IXON){
printk(KERN_ALERT "%s!!---c_cflag:0x%x,IXON:0x%x.\n",__func__,termios->c_cflag,IXON);
}
if (termios->c_iflag & IXOFF){
printk(KERN_ALERT "%s!!---c_cflag:0x%x,IXOFF:0x%x.\n",__func__,termios->c_cflag,IXOFF);
}
#endif
one->new_baud1_reg=baud1;
one->new_baud0_reg=baud0;
one->new_pres_reg=pres;
one->new_lcr_reg = lcr;
one->new_fwcr_reg = fwcr;
#ifdef _DEBUG_WK_VALUE
printk(KERN_ALERT "wk2xxx_termios()----port:%lx;lcr:0x%x;fwcr:0x%x---\n",one->port.iobase,lcr,fwcr);
#endif
#ifdef WK_WORK_KTHREAD
ret=kthread_queue_work(&s->kworker, &one->termios_work);
#else
ret=queue_kthread_work(&s->kworker, &one->termios_work);
#endif
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--exit--\n", __func__,one->port.iobase);
#endif
}
4.10 发送数据
发送数据时,该函数具体实现和芯片的交互。
static void wk2xxx_tx_chars(struct uart_port *port)
{
struct wk2xxx_port *s = dev_get_drvdata(port->dev);
//struct wk2xxx_port *s = container_of(port,struct wk2xxx_port,port);
struct wk2xxx_one *one = to_wk2xxx_one(port, port);
uint8_t fsr,tfcnt,dat[1],txbuf[256]={0};
int count,tx_count,i;
int len_tfcnt,len_limit,len_p=0;
len_limit=SPI_LEN_LIMIT;
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--in--\n", __func__,one->port.iobase);
#endif
if (one->port.x_char) {
#ifdef _DEBUG_WK_TX
printk(KERN_ALERT "wk2xxx_tx_chars one->port.x_char:%x,port = %ld\n",one->port.x_char,one->port.iobase);
#endif
wk2xxx_write_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_FDAT_REG,one->port.x_char);
one->port.icount.tx++;
one->port.x_char = 0;
goto out;
}
if(uart_circ_empty(&one->port.state->xmit) || uart_tx_stopped(&one->port)){
goto out;
}
wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_FSR_REG,&fsr);
wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_TFCNT_REG,&tfcnt);
#ifdef _DEBUG_WK_TX
printk(KERN_ALERT "wk2xxx_tx_chars fsr:0x%x,tfcnt:0x%x,port = %ld\n",fsr,tfcnt,one->port.iobase);
#endif
if(tfcnt==0){
tx_count=(fsr & WK2XXX_FSR_TFULL_BIT)?0:256;
#ifdef _DEBUG_WK_TX
printk(KERN_ALERT "wk2xxx_tx_chars2 tx_count:%x,port = %ld\n",tx_count,one->port.iobase);
#endif
}else{
tx_count=256-tfcnt;
#ifdef _DEBUG_WK_TX
printk(KERN_ALERT "wk2xxx_tx_chars2 tx_count:%x,port = %ld\n",tx_count,one->port.iobase);
#endif
}
if(tx_count>200){
tx_count=200;
}
count = tx_count;
i=0;
while(count){
if(uart_circ_empty(&one->port.state->xmit))
break;
txbuf[i]=one->port.state->xmit.buf[one->port.state->xmit.tail];
one->port.state->xmit.tail = (one->port.state->xmit.tail + 1) & (UART_XMIT_SIZE - 1);
one->port.icount.tx++;
i++;
count=count-1;
#ifdef _DEBUG_WK_TX
printk(KERN_ALERT "tx_chars:0x%x--\n",txbuf[i-1]);
#endif
};
#ifdef WK_FIFO_FUNCTION
len_tfcnt=i;
while(len_tfcnt){
if(len_tfcnt>len_limit){
wk2xxx_write_fifo(s->spi_wk,one->port.iobase,len_limit,txbuf+len_p);
len_p=len_p+len_limit;
len_tfcnt=len_tfcnt-len_limit;
}else{
wk2xxx_write_fifo(s->spi_wk,one->port.iobase,len_tfcnt,txbuf+len_p);
len_p=len_p+len_tfcnt;
len_tfcnt=0;
}
}
#else
for(count=0;count<i;count++){
wk2xxx_write_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_FDAT_REG,txbuf[count]);
}
#endif
out:wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_FSR_REG,dat);
fsr = dat[0];
#ifdef _DEBUG_WK_VALUE
printk(KERN_ALERT "%s!!-port:%ld;--FSR:0X%X--\n", __func__,one->port.iobase,fsr);
#endif
if(((fsr&WK2XXX_FSR_TDAT_BIT)==0)&&((fsr&WK2XXX_FSR_TBUSY_BIT)==0)){
if (uart_circ_chars_pending(&one->port.state->xmit) < WAKEUP_CHARS){
uart_write_wakeup(&one->port);
}
if (uart_circ_empty(&one->port.state->xmit)){
wk2xxx_write_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_SIER_REG,0x03);
}
}
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--exit--\n", __func__,one->port.iobase);
#endif
}
4.11 接收数据
接收数据是,该函数具体实现。
static void wk2xxx_rx_chars(struct uart_port *port)
{
//struct wk2xxx_port *s = container_of(port,struct wk2xxx_port,port);
struct wk2xxx_port *s = dev_get_drvdata(port->dev);
struct wk2xxx_one *one = to_wk2xxx_one(port, port);
uint8_t fsr,rx_dat[256]={0};
uint8_t rfcnt=0,rfcnt2=0;
unsigned int flg, status = 0,rx_count=0;
int rx_num=0,rxlen=0;
int len_rfcnt,len_limit,len_p=0;
len_limit=SPI_LEN_LIMIT;
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--in--\n", __func__,one->port.iobase);
#endif
wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_FSR_REG,&fsr);
if (fsr& WK2XXX_FSR_RDAT_BIT){
wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_RFCNT_REG,&rfcnt);
if(rfcnt==0){
wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_RFCNT_REG,&rfcnt);
}
wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_RFCNT_REG,&rfcnt2);
if(rfcnt2==0){
wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_RFCNT_REG,&rfcnt2);
}
rfcnt=(rfcnt2>=rfcnt)?rfcnt:rfcnt2;
rxlen=(rfcnt==0)?256:rfcnt;
}
#ifdef _DEBUG_WK_RX
printk(KERN_ALERT "rx_chars()-port:%lx--fsr:0x%x--rxlen:%d--\n",one->port.iobase,fsr,rxlen);
#endif
flg = TTY_NORMAL;
#ifdef WK_FIFO_FUNCTION
len_rfcnt=rxlen;
while(len_rfcnt){
if(len_rfcnt>len_limit){
wk2xxx_read_fifo(s->spi_wk,one->port.iobase,len_limit,rx_dat+len_p);
len_rfcnt=len_rfcnt-len_limit;
len_p=len_p+len_limit;
}else{
wk2xxx_read_fifo(s->spi_wk,one->port.iobase,len_rfcnt,rx_dat+len_p);//
len_rfcnt=0;
}
}
#else
for(rx_num=0;rx_num<rxlen;rx_num++){
wk2xxx_read_slave_reg(s->spi_wk,one->port.iobase,WK2XXX_FDAT_REG,&rx_dat[rx_num]);
}
#endif
one->port.icount.rx+=rxlen;
for(rx_num=0;rx_num<rxlen;rx_num++){
if(fsr&WK2XXX_FSR_ERR_MASK){
fsr &= WK2XXX_FSR_ERR_MASK ;
if (fsr&(WK2XXX_FSR_RFOE_BIT |WK2XXX_FSR_RFBI_BIT|WK2XXX_FSR_RFFE_BIT|WK2XXX_FSR_RFPE_BIT)){
if(fsr & WK2XXX_FSR_RFPE_BIT){
one->port.icount.parity++;
status |= WK2XXX_STATUS_PE;
flg = TTY_PARITY;
}
if (fsr & WK2XXX_FSR_RFFE_BIT){
one->port.icount.frame++;
status |= WK2XXX_STATUS_FE;
flg = TTY_FRAME;
}
if(fsr & WK2XXX_FSR_RFOE_BIT){
one->port.icount.overrun++;
status |= WK2XXX_STATUS_OE;
flg = TTY_OVERRUN;
}
if(fsr & WK2XXX_FSR_RFBI_BIT){
one->port.icount.brk++;
status |= WK2XXX_STATUS_BRK;
flg = TTY_BREAK;
}
}
}
if (uart_handle_sysrq_char(port,rx_dat[rx_num]))
continue;//
#ifdef _DEBUG_WK_RX
printk(KERN_ALERT "rx_chars:0x%x----\n",rx_dat[rx_num]);
#endif
uart_insert_char(port, status, WK2XXX_STATUS_OE, rx_dat[rx_num], flg);
rx_count++;
}
if(rx_count > 0){
#ifdef _DEBUG_WK_RX
printk(KERN_ALERT "push buffer tty flip port = :%lx count =:%d\n",one->port.iobase,rx_count);
#endif
tty_flip_buffer_push(&port->state->port);
rx_count = 0;
}
#ifdef _DEBUG_WK_FUNCTION
printk(KERN_ALERT "%s!!-port:%ld;--exit--\n", __func__,one->port.iobase);
#endif
}
4.12 用户空间和内核空间读写的实现
通常应用层通过write() /read()函数来实现串口的数据传输。那么驱动层是怎么来实现的?
用户空间和驱动层之间的数据是怎么交互的。如下图所示
用户空间和驱动层之间在数据传递上并不是直接传递的。当write()写数据时,用户空间仅仅是把数据传递给tty缓冲区,然后驱动程序收到发送数据的指令,然后按照一定的流程去发送数据;当接收数据的时候,驱动层首先把接收的数据放入tty缓冲区,用户空间read()去读数据,那么就能从tty缓冲区读出子串口接收的数据。
4.12 接收与发送处理逻辑
串口数据的接收和发送都依赖于驱动中的中断系统来实现的,具体怎么实现的,如下。
1、首先来分析数据发送:
a、在应用层会调用write()函数,把数据写入到tty缓冲区,其次会调用wk2xxx_start_tx()函数,使能芯片的发送中断,WK芯片产生发送中断信号。
b、中断信号,触发驱动中断函数,进入数据发送处理流程。
c、 数据发送函数wk2xxx_tx_chars()会把tty缓冲区中需要发送的数据,写入芯片的发送缓冲区,芯片会自动发送写入的数据。
d、数据传输完成,驱动会关闭发送,等待新的发送任务
2、数据接收流程
a、串口接收数据传输进入WK芯片的RX引脚,并被写入接收FIFO,这些事芯片自动完成的。
b、芯片产生接收中断信号,触发驱动中断函数,进入数据接收处理流程
c、数据接收函数wk2xxx_rx_chars() 会把串口接收fifo中已经收到的数据读出来,然后推送tty接收缓存区。
d、用户空间调用read()函数,就会从tty缓存区读出串口接收的数据。

5、驱动的移植
5.1 驱动移植前的准备工作
ROC-RK3588S-PC 预装的是Ubuntu系统,系统内核linux 6.1.
5.2 驱动源文件和makefile
源文件 wk2xxx_spi.c
由于源代码比较多,就暂时不放在这里面了。
makefile文件如下:
ARCH:= arm64
CROSS_COMPILE := /home/xxw/proj/rk3588_6.1/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
export ARCH CROSS_COMPILE
KDIR := /home/xxw/proj/rk3588_6.1/kernel
TARGET =wk2xxx_spi
EXEC = $(TARGET)
obj-m :=$(TARGET).o
PWD :=$(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *~core.depend.*.cmd *.ko *.mod.c .tmp_versions $(TARGET)
5.3 配置DTS

注释说明:
本驱动使用的是SPI1,
status:如果要启用SPI,那么设置为okay,如不启用,设置为disable
wk2xxx_spi@00:由于硬件使用的是SPI1的cs0引脚,所以设置为00.如果使用cs1,则设置为01
compatible:这里的属性必须与驱动中的结构体:of_device_id 中的成员 compatible 保持一致。这个是SPI驱动匹配的关键。
reg:此处与wk2xxx_spi@00:保持一致。此处设置为:00
reset_gpio:该选项在SPI驱动当中不是必须的。该gpio和WK2xxx芯片的复位引脚相连,用于控制芯片的复位。根据实际使用的gpio去修改。
irq_gpio: 该gpio和wk2xxx芯片的IRQ引脚相连,用于接收wk2xxx芯片传递来的中断信号。根据具体使用的GPIO去修改。
5.4 编译驱动并加载驱动
把驱动编译成模块,然后加载和测试。
更多推荐




所有评论(0)