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 处理接口