7.11.5. 源码说明

7.11.5.1. 串口芯片

在驱动代码说明开始前先介绍几种通用的串口芯片。

7.11.5.1.1. 8250

8250是 IBM PC 及兼容机使用的第一种串口芯片。这是一种相对来说很慢的芯片,有时候装载到它的寄存器速度太快,它来不及处理,就会出现数据丢失现象。8250有7个寄存器,支持的最大波特率为56kb。

7.11.5.1.2. 16450

16450 是 8250A 的快速版。加快了处理器存取它的速度,但最大速度还是 56kb。有些人实际用得比这高也可以。

7.11.5.1.3. 16550

16550/16550A 是第一种带先进先出(FIFO)功能的8250系列串口芯片。

AIC UART 模块是一 16550A UART,属于标准的8250系,而 Linux 对8250系串口有通用的驱动,我们采取在该驱动上添加 AIC 私有的代码的方式实现我们的驱动。

7.11.5.2. 驱动配置宏

因为8250驱动兼容很多后续开发的串口芯片,因此有非常多的编译功能配置项。

menuconfig 中位置:device drivers/Character devices/8250 16550 and compatible serial support

关键配置项说明:

  • SERIAL_8250_DEPRECATED_OPTIONS: Support 8250_core.* kernel options (DEPRECATED)

    3.7 版本的错误,不应该打开

  • SERIAL_8250_PNP: 8250/16550 PNP device support

    不打开

  • SERIAL_8250_16550A_VARIANTS: 16550A serial port

    打开

  • SERIAL_8250_FINTEK: Fintek F81216A LPC to 4 UART RS485 API

    不打开

  • SERIAL_8250_CONSOLE: 8250/16550 and compatible serial port

    打开

  • SERIAL_8250_DMA: DMA support for 16550 compatible UART controllers

    打开

  • SERIAL_8250_NR_UARTS: Maximum number of 8250/16550 serial ports

    设置为8

  • SERIAL_8250_RUNTIME_UARTS: Number of 8250/16550 serial ports to register at runtime

    设置为8

  • SERIAL_8250_EXTENDED: Extended 8250/16550 serial driver options

    没有使用,无关系

  • SERIAL_8250_MANY_PORTS

    没有使用,无关系

  • SERIAL_8250_SHARE_IRQ: Support for sharing serial interrupts

    不打开

  • SERIAL_8250_DETECT_IRQ: Autodetect IRQ on standard ports (unsafe)

    不打开

  • SERIAL_8250_RSA: Support RSA serial ports

    不打开,没有使用

  • SERIAL_8250_DW: Support for Synopsys DesignWare 8250 quirks

    不打开,不需要兼容

  • SERIAL_OF_PLATFORM: Device tree based probing for 8250 ports

    不打开,使用AIC定义的OF

7.11.5.3. 寄存器

8250驱动使用索引来定义寄存器,地址的计算方式为索引 * 位宽。 AIC UART 的寄存器大致可分为两种:

7.11.5.3.1. 标准寄存器

0x7C 之前,为8250标准寄存器,在include/uapi/linux/serial_reg.h 中定义,AIC 对如下3个寄存器值有修改

#define UART_IER   1    /*Interrupt Enable: Shifter_Reg_Empty_EN*/
#define UART_IIR   2    /*Interrupt Identity:Shifter_Reg_Empty_INT*/
#define UART_MCR   4    /*Modem Control:UART_FUNCTION*/

7.11.5.3.2. 扩展寄存器

0x7C 之后,为 AIC 新扩展,需要额外的逻辑代码,譬如 RS485,在 8250_zx.h 中定义

#define AIC_REG_UART_USR   0x1f        /* UART Status Register */
#define AIC_REG_UART_TFL   0x20        /* transmit FIFO level */
#define AIC_REG_UART_RFL   0x21        /* Receive FIFO Level */
#define AIC_REG_UART_HSK   0x22        /* DMA Handshake Configuration */
#define AIC_REG_UART_HALT  0x29        /* Halt tx register */
#define AIC_REG_UART_DLL   0x2C        /* DBG DLL */
#define AIC_REG_UART_DLH   0x2D        /* DBG DLH */
#define AIC_REG_UART_RS485 0x30        /* RS485 control status register */

7.11.5.4. 程序入口

7.11.5.4.1. 8250_core

8250标准寄存器/逻辑的初始化入口,三方驱动大都借助 8250_core 完成其工作,文件:8250_core.c。

module_init(serial8250_init);
module_exit(serial8250_exit);

7.11.5.4.2. aic-uart

aic8250 是 zx 的 uart 驱动的入口,声明驱动和初始化私有寄存器/逻辑,文件:8250_zx.c

static const struct of_device_id aic8250_of_match[] = {
   { .compatible = "zx,aic-uart-v1.0" },
   { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, aic8250_of_match);

static struct platform_driver aic8250_platform_driver = {
   .driver = {
      .name          = AICUART_DRIVER_NAME,
      .pm            = &aic8250_pm_ops,
      .of_match_table        = aic8250_of_match,
   },
   .probe                    = aic8250_probe,
   .remove           = aic8250_remove,
};
module_platform_driver(aic8250_platform_driver);

7.11.5.5. 数据结构

7.11.5.5.1. uart_8250_port

声明在 include/linux/serial_8250.h 中,驱动中以变量名:up 变量存在,zx 驱动中未进行任何设置,只 overlay 了 AIC 平台的 rs485 设置函数, 其他完全使用 8250_core.c 中参数。uart_8250_port的初始化在 8250_core.c:serial8250_register_8250_port中完成。

struct uart_8250_port {
   struct uart_port          port;
   …
   struct uart_8250_dma      *dma;
   const struct uart_8250_ops        *ops;
   /* 8250 specific callbacks */
   int                       (*dl_read)(struct uart_8250_port *);
   void                      (*dl_write)(struct uart_8250_port *, int);
   struct uart_8250_em485 *em485;
   void                      (*rs485_start_tx)(struct uart_8250_port *);
   void                      (*rs485_stop_tx)(struct uart_8250_port *);

   .dl_read          = default_serial_dl_read
   .dl_write         = default_serial_dl_write
   .rs485_config = aic8250_rs485_config;
   .rs485            = NULL;
   .rs485_start_tx = NULL;
   .rs485_stop_tx = NULL;

7.11.5.5.2. uart_port

uart_port 是UART驱动的关键数据结构,驱动中以 变量名:p存在,声明include/linux/serial_core.h,包含变量和关键操作函数的overlay入口。

struct uart_port {
   spinlock_t     lock;                      /* port lock */
   unsigned long  iobase;            /* in/out */
   unsigned char __iomem     *membase;               /* read/write */
   unsigned int   (*serial_in)(struct uart_port *, int);
   void           (*serial_out)(struct uart_port *, int, int);
   ... ...

7.11.5.5.3. aic8250_data

struct aic8250_data {
        struct aic8250_port_data     data;

        int                  msr_mask_on;
        int                  msr_mask_off;
        struct clk           *clk;
        struct reset_control *rst;

        unsigned int         uart_16550_compatible:1;
        unsigned int         tx_empty;
        unsigned int         rs485simple;      //compact-io mode
};

7.11.5.5.4. Operations

uart_port各操作接口设置和操作的寄存器。

蓝色接口为可以在客户的驱动中定制,否则使用uart8250_core中标准接口。

.handle_irq  = aic8250_handle_irq;
.pm          = aic8250_do_pm;
.serial_in   = aic8250_serial_in32;
.serial_out  = aic8250_serial_out32;
.set_ldisc   = aic8250_set_ldisc;
.set_termios = aic8250_set_termios;
.set_mctrl   = serial8250_set_mctrl, //set modem control, 包括termios 和 modem的转换,Modem Control Reg: MCR
.get_mctrl   = serial8250_get_mctrl, //get modem control
.startup     = serial8250_startup,   //寄存器初始化
.shutdown    = serial8250_shutdown,  //
.get_divisor   = serial8250_do_get_divisor
.set_divisor    = serial8250_do_set_divisor
.handle_break =
.tx_empty       = serial8250_tx_empty    //Line Status: Transmitter Empty & TX Holding Register Empty
.stop_tx     = serial8250_stop_tx,   //serial8250_clear_THRI,Interrupt Enable Reg,Enable Transmitter:10,Enable Receive:01
.start_tx    = serial8250_start_tx,  //serial8250_set_THRI,Interrupt Enable Reg,Enable Transmitter:10,Enable Receive:01
.throttle    = serial8250_throttle,  //synclink_gt.c,
.unthrottle  = serial8250_unthrottle,
.stop_rx     = serial8250_stop_rx,   //Interrupt Enable Reg,Enable Receive:01,Enable receiver line status interrupt: 04
.enable_ms   = serial8250_enable_ms, //Interrupt Enable Reg,Enable Modem status interrupt:08
.break_ctl   = serial8250_break_ctl, //Line Control Reg,Break Control Bit:
.type                = serial8250_type,              //8250 or 16550 or TI16750
.release_port        = serial8250_release_port,      //release_mem_region
.request_port= serial8250_request_port,//request_mem_region
.config_port = serial8250_config_port,       //
.verify_port = serial8250_verify_port,

7.11.5.6. 关键流程

7.11.5.6.1. serial8250_init

serial8250_init
   |-->serial8250_isa_init_ports
     |-->UART_NR                        //8个
     |-->serial8250_init_port
         |-->port->ops = &serial8250_pops;    //标准ops
     |-->serial8250_set_defaults     //io,dma,fifosize,标准DMA接口
     |-->serial8250_isa_config
   |-->uart_register_driver
   |-->serial8250_pnp_init
   |-->platform_device_add
   |-->serial8250_register_ports
   |-->platform_driver_register

7.11.5.6.2. aic8250_probe

aic8250_probe
   |-->regs = platform_get_resource
   |-->platform_get_irq
   |-->p->handle_irq = aic8250_handle_irq;
   |-->p->pm         = aic8250_do_pm;
   |-->p->type               = PORT_16550
   |-->p->serial_in  = aic8250_serial_in32;
   |-->p->serial_out         = aic8250_serial_out32;
   |-->p->set_ldisc            = aic8250_set_ldisc;
   |-->p->set_termios        = aic8250_set_termios;
   |-->p->regshift            = dts:reg-shift, reg 32bit
   |-->Prepare clk
   |-->prepare reset
   |-->aic8250_apply_quirks  : 处理aic 私有逻辑,dcd-override,dsr-override,cts-override,ri-override
      |-->aic8250_init_special_reg
   |-->data->data.line = serial8250_register_8250_port: return port number
   |-->platform_set_drvdata

7.11.5.6.3. serial8250_register_8250_port

serial8250_register_8250_port
   |-->return port number
   |-->serial8250_find_match_or_unused
   |-->copy aic uart_8250_port to local uart_8250_port
   |-->uart_get_rs485_mode:
     |-->rs485-rts-delay,rs485-rx-during-tx,linux,rs485-enabled-at-boot-time,rs485-rts-active-low,rs485-term
   |-->mctrl_gpio_init:    //null
   |-->serial8250_set_defaults //io,dma,fifosize setting
   |-->serial8250_apply_quirks
     |-->uart_add_one_port

7.11.5.7. RS485

ZX 的 RS485 有两种工作模式:

  • 标准模式:标准 RS485 接口,有B+/B-两根数据线和发送使能,共三线

  • 精简模式:为 ZX 定制版,使用单数据线进行数据传输,加发送使能共二线

7.11.5.7.1. 标准模式

通过在dts中添加linux,rs485-enabled-at-boot-time配置为RS485模式,代码流为:

static void aic8250_apply_quirks(struct device *dev, struct uart_port *p,
      struct aic8250_data *data)
{
        struct device_node *np = p->dev->of_node;
        int id;

        uart_get_rs485_mode(p);
}

drivers/tty/serial/serial_core.c

int uart_get_rs485_mode(struct uart_port *port)
{
   if (device_property_read_bool(dev, "linux,rs485-enabled-at-boot-time"))
      rs485conf->flags |= SER_RS485_ENABLED;
}

7.11.5.7.2. 精简模式

精简模式 RS485 首先是 RS485,因此需要在开启 RS485 的基础上进行额外的配置,通过在 xxx-board.dts 中添加 aic,rs485-compact-io-mode 完成配置,代码流为:

drivers/tty/serial/serial_zx.c
aic8250_apply_quirks:
if (device_property_read_bool(dev, "aic,rs485-compact-io-mode"))
   data->rs485simple = 1;

static int aic8250_rs485_config(struct uart_port *p,
      struct serial_rs485 *prs485)
{
   struct aic8250_data *d = to_aic8250_data(p->private_data);
     unsigned int mcr = p->serial_in(p, UART_MCR);
     unsigned int rs485 = p->serial_in(p, AIC_REG_UART_RS485);

     mcr &= AIC_UART_MCR_FUNC_MASK;
     if (prs485->flags & SER_RS485_ENABLED) {
             if (d->rs485simple)
                     mcr |= AIC_UART_MCR_RS485S;
             else
                     mcr |= AIC_UART_MCR_RS485;

             rs485 |= AIC_UART_RS485_RXBFA;
             rs485 &= ~AIC_UART_RS485_CTL_MODE;
     } else {
             mcr = AIC_UART_MCR_UART;
             rs485 &= ~AIC_UART_RS485_RXBFA;
     }

     p->serial_out(p, UART_MCR, mcr);
     p->serial_out(p, AIC_REG_UART_RS485, rs485);

     return 0;
}

7.11.5.8. DMA

7.11.5.8.1. 特殊性

AIC 的 DMA 和标准的 DMA有 一点使用不同,限制了我们只能使用自己私有的 DMA 接口,主要差别点在于我们必须设置 UART FIFO中 的数据长度进行 DMA 搬运才不会出错,测试的逻辑为:

  • 如果设置了 FIFO 的中断触发为1/2,则在 FIFO 中数据达到 128byte 后会收到 data ready 中断

  • 如果这个时候 UAR T在继续接收,则FIFO中的数据会按 4bytes/次 的速度增加

  • 如果设置了 DMA 接收长度为64,则128-64 = 64会丢失

  • 如果设置了 DM A接收长度为1024,则因为fifo中的数据最多为256,则永远收不满

因此AIC的DMA处理逻辑为:

  • 收到 data ready 中断后,先停掉 UART 的接收,防止 FIFO 中数据增加

  • 读取当时 FIFO 中数据的长度,作为 DMA 的参数进行数据搬移

  • DMA 搬移成功后重启 UART 数据接收

int aic8250_dma_rx(struct uart_8250_port *p)
{
   struct uart_8250_dma              *dma = p->dma;
   struct dma_async_tx_descriptor    *desc;

   if (dma->rx_running)
      return 0;

   aic8250_set_ier(p, false);
   dma->rx_size = p->port.serial_in(&p->port, AIC_REG_UART_RFL);

   desc = dmaengine_prep_slave_single(dma->rxchan, dma->rx_addr,
                  dma->rx_size, DMA_DEV_TO_MEM,
                  DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
   if (!desc)
      return -EBUSY;

   dma->rx_running = 1;
   desc->callback = aic8250_dma_rx_complete;
   desc->callback_param = p;

   dma->rx_cookie = dmaengine_submit(desc);

   dma_async_issue_pending(dma->rxchan);

   return 0;
}

7.11.5.8.2. DMA寄存器

DMA 的所有寄存器不建议进行参数调整,使用默认值即可,但 DMA 需要工作在 HSK 模式。

p->serial_out(p, AIC_REG_UART_HSK, AIC_HSK_HAND_SHAKE);

7.11.5.9. 修改总结

AIC 的 UART 驱动附着于8250标准驱动,但又有不一样的地方,总结一下修改列表

7.11.5.9.1. 驱动接口

  • 添加 AIC 私有接口,8250_zx.c,8250_zx.h

  • 从 board.dst 中读取并配置 uartclk

  • 配合 SOC 功能简化代码

  • 移除 autoconfig 功能,因为有些 try 会导致输出乱码

  • DMA 策略添加和私有逻辑接口

7.11.5.9.2. RS485支持

RS485 的支持需要设置 Modem_Control 和 RS485 Control and Status 两个寄存器,添加了 aic8250_rs485_config 处理接口