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驱动和系统之间关系示意图。

  1. WK驱动工作在linux 内核层,向上提供4个串口设备节点供应用层用户调用。也就是说WK驱动注册成功以后,在/dev/ 目录下会生成 ttysWK0、ttysWK1、ttysWK2、ttysWK3  共4个串口设备节点,应用层就可以按照操作普通串口节点的方式操作。
  2. 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 编译驱动并加载驱动

把驱动编译成模块,然后加载和测试。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐