设计说明
串口芯片
- 8250:IBM PC 及兼容机使用的第一种串口芯片。这是一种相对来说很慢的芯片,有时候装载到它的寄存器速度太快,它来不及处理,就会出现数据丢失现象。8250 有 7 个寄存器,支持的最大波特率为 56kb。
- 16450:8250A 的快速版。加快了处理器存取它的速度,但最大速度还是 56kb。有些人实际用得比这高也可以。
- 16550/16550A:第一种带先进先出(FIFO)功能的 8250 系列串口芯片。
AIC UART 模块是一 16550A UART,属于标准的 8250 系,而 Linux 对 8250 系串口有通用的驱动,我们采取在该驱动上添加 AIC 私有的代码的方式实现我们的驱动。
驱动配置宏
因为 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_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 |
寄存器
- 标准寄存器: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*/
- 扩展寄存器:0x7C 之后,为 AIC 新扩展,需要额外的逻辑代码,譬如 RS485,在 8250_artinchip.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 */
程序入口
- 8250_core:8250 标准寄存器/逻辑的初始化入口,三方驱动大都借助 8250_core
完成其工作,文件:8250_core.c。
module_init(serial8250_init); module_exit(serial8250_exit);
- aic-uart:aic8250 是 artinchip 的 uart
驱动的入口,声明驱动和初始化私有寄存器/逻辑,文件:8250_artinchip.c
static const struct of_device_id aic8250_of_match[] = { { .compatible = "artinchip,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);
数据结构
- uart_8250_port声明在 include/linux/serial_8250.h 中,驱动中以变量名:up 变量存在,artinchip 驱动中未进行任何设置,只 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;
- 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); ... ...
- 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 };
- Operations:uart_port 各操作接口设置和操作的寄存器。
蓝色接口为可以在用户的驱动中定制,否则使用 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,
关键流程
- 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
- 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
- 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
RS485
ArtInChip 的 RS485 有两种工作模式:
-
标准模式:标准 RS485 接口,有 B+/B-两根数据线和发送使能,共三线
通过在 board.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; }
-
精简模式:为 ArtInChip 定制版,使用单数据线进行数据传输,加发送使能共二线
精简模式 RS485 首先是 RS485,因此需要在开启 RS485 的基础上进行额外的配置,通过在 board.dts 中添加 aic,rs485-compact-io-mode 完成配置,代码流为:drivers/tty/serial/serial_artinchip.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; }
DMA
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;
}
DMA 寄存器
p->serial_out(p, AIC_REG_UART_HSK, AIC_HSK_HAND_SHAKE);
修改总结
- 驱动接口
-
添加 AIC 私有接口,8250_artinchip.c,8250_artinchip.h
-
从 board.dst 中读取并配置 uartclk
-
配合 SOC 功能简化代码
-
移除 autoconfig 功能,因为有些 try 会导致输出乱码
-
DMA 策略添加和私有逻辑接口
-
RS485 支持
RS485 的支持需要设置 Modem_Control 和 RS485 Control and Status 两个寄存器,添加了 aic8250_rs485_config 处理接口