本次说一下tty子系统的驱动编程,因为UART相关的寄存器比较多,同时,应用比较广泛,所以本次的驱动程序量也不少,而且只是完成和特定CPU相关的一部分,通用的部分本次都没有涉及到.在写驱动之前,我们先来了解一下硬件资源和tty子系统框架.
1、串口通信简介
串口通信指串口按位(bit)发送和接收字节,串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线 接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2 米;而对于串口而言,长度可达1200米。
串口通信所采用的通信协议为RS-232,RS-232通信方式允许简单连接三线:Tx、Rx和地线。但是对于数据传输,双方必须对数据定时采用使用相同的波特率。RS-232(ANSI/EIA-232标准)是IBM-PC及其兼容机上的串行连接标准。可用于许多用途,比如连接鼠标、打印机或者Modem,同时也 可以接工业仪器仪表。用于驱动和连线的改进,实际应用中RS-232的传输长度或者速度常常超过标准的值。RS-232只限于PC串口和设备间点对点的通信。
2、串口的通信基本模型如下图所示:
3,硬件资源;
如上图,tiny4412的板子上引出了3个UART接口,分别是UART0 , 2, 3,它有5组UART,另外一组UART1用在了WIFI + BT模块,UART4没有引出来,这几组UART的区别主要是FIFO的大小,UART0的FIFO为256byte,UART1, 4的FIFO大小为64byte,UART2, 3的FIFO大小为16 byte,这5组都支持DMA操作,中断等,最大传输速率是4Mbps(512k byte/s),在这里,我们会使用UART3来测试串口功能.
4,UART寄存器及地址
如上图是官方给的UART寄存器map,这个跟STM32是很类似的,需要了解的小伙伴也可以直接找STM32的UART资料了解.UART0上集成了红外功能,上一次,我们通过GPIO口中断的方式,开发了红外驱动,这次也可以使用UART0来做红外驱动,相对简单一些,但是,我们的UART0已经用作了调试终端,所以,就先不这样做了.
下面是UART的一些寄存器:
网上有很多对于寄存器进行讲解如何使用的一些博文和视频,这方面的基础知识,请自行学习,这里就不多说了,接下来说一下tty子系统的一下知识.
上图是TTY子系统的层次架构,我们需要实现的就是底层驱动程序,因为这一部分是和特定CPU相关的,其它几层都是通用的.
上图是UART驱动程序的映射图,也就是tty的框架.了解了这些之后,我们看一看代码实现的具体细节实现(图用的是网上的):
有了具体的实现框图之后,我们来实现一下驱动程序(驱动暂时没这么顺利,没调好输入部分,先贴一个三星官方的):
#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/sysrq.h>
#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
#include <linux/spinlock.h>
#endif
#include <linux/of.h>
#include <asm/irq.h>
#include <mach/hardware.h>
#include <mach/map.h>
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
#include <mach/regs-clock.h>
#include <mach/dma.h>
#endif
#include <plat/regs-serial.h>
#include <plat/clock.h>
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
#include <linux/dma-mapping.h>
#endif
#include "samsung.h"
/* UART name and device definitions */
#define S3C24XX_SERIAL_NAME "ttySAC"
#define S3C24XX_SERIAL_MAJOR 204
#define S3C24XX_SERIAL_MINOR 64
/* Baudrate definition*/
#define MAX_BAUD 3000000
#define MIN_BAUD 0
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
/* definitions for dma mode */
#define ENABLE_UART_DMA_MODE true
#define DMA_TRANS_LIMIT 64
#define RX_BUFFER_SIZE 256
#define BURST_1BYTE 0
#endif
/* macros to change one thing to another */
#define tx_enabled(port) ((port)->unused[0])
#define rx_enabled(port) ((port)->unused[1])
/* flag to ignore all characters coming in */
#define RXSTAT_DUMMY_READ (0x10000000)
/* ? - where has parity gone?? */
#define S3C2410_UERSTAT_PARITY (0x1000)
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
static struct s3c2410_dma_client samsung_uart_dma_client = {
.name = "samsung-uart-dma",
};
static void prepare_dma(struct uart_dma_data *dma,
unsigned len, dma_addr_t buf);
#endif
static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port)
{
return container_of(port, struct s3c24xx_uart_port, port);
}
/* translate a port to the device name */
static inline const char *s3c24xx_serial_portname(struct uart_port *port)
{
return to_platform_device(port->dev)->name;
}
static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
{
return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);
}
/*
* s3c64xx and later SoC's include the interrupt mask and status registers in
* the controller itself, unlike the s3c24xx SoC's which have these registers
* in the interrupt controller. Check if the port type is s3c64xx or higher.
*/
static int s3c24xx_serial_has_interrupt_mask(struct uart_port *port)
{
return to_ourport(port)->info->type == PORT_S3C6400;
}
static void s3c24xx_serial_rx_enable(struct uart_port *port)
{
unsigned long flags;
unsigned int ucon, ufcon;
int count = 10000;
spin_lock_irqsave(&port->lock, flags);
while (--count && !s3c24xx_serial_txempty_nofifo(port))
udelay(100);
ufcon = rd_regl(port, S3C2410_UFCON);
ufcon |= S3C2410_UFCON_RESETRX;
wr_regl(port, S3C2410_UFCON, ufcon);
ucon = rd_regl(port, S3C2410_UCON);
ucon |= S3C2410_UCON_RXIRQMODE;
wr_regl(port, S3C2410_UCON, ucon);
rx_enabled(port) = 1;
spin_unlock_irqrestore(&port->lock, flags);
}
static void s3c24xx_serial_rx_disable(struct uart_port *port)
{
unsigned long flags;
unsigned int ucon;
spin_lock_irqsave(&port->lock, flags);
ucon = rd_regl(port, S3C2410_UCON);
ucon &= ~S3C2410_UCON_RXIRQMODE;
wr_regl(port, S3C2410_UCON, ucon);
rx_enabled(port) = 0;
spin_unlock_irqrestore(&port->lock, flags);
}
static void s3c24xx_serial_stop_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
struct exynos_uart_dma *uart_dma = &ourport->uart_dma;
#endif
if (tx_enabled(port)) {
if (s3c24xx_serial_has_interrupt_mask(port))
__set_bit(S3C64XX_UINTM_TXD,
portaddrl(port, S3C64XX_UINTM));
else
disable_irq_nosync(ourport->tx_irq);
tx_enabled(port) = 0;
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_enable(port);
}
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
uart_dma->ops->stop(uart_dma->tx.ch);
#endif
}
static void s3c24xx_serial_start_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
if (!tx_enabled(port)) {
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_disable(port);
if (s3c24xx_serial_has_interrupt_mask(port))
__clear_bit(S3C64XX_UINTM_TXD,
portaddrl(port, S3C64XX_UINTM));
else
enable_irq(ourport->tx_irq);
tx_enabled(port) = 1;
}
}
static void s3c24xx_serial_stop_rx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
struct exynos_uart_dma *uart_dma = &ourport->uart_dma;
#endif
if (rx_enabled(port)) {
dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
if (s3c24xx_serial_has_interrupt_mask(port))
__set_bit(S3C64XX_UINTM_RXD,
portaddrl(port, S3C64XX_UINTM));
else
disable_irq_nosync(ourport->rx_irq);
rx_enabled(port) = 0;
}
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
uart_dma->ops->stop(uart_dma->rx.ch);
#endif
}
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
static void uart_rx_dma_request(struct s3c24xx_uart_port *ourport)
{
struct uart_port *port = &ourport->port;
struct exynos_uart_dma *uart_dma = &ourport->uart_dma;
uart_dma->rx_dst_addr = dma_map_single(port->dev,
uart_dma->rx_buff,
uart_dma->rx.req_size,
DMA_FROM_DEVICE);
if (dma_mapping_error(port->dev, uart_dma->rx_dst_addr))
pr_err("Rx DMA mapping error!!!\n");
/* prepare rx dma mode */
prepare_dma(&uart_dma->rx,
uart_dma->rx.req_size, uart_dma->rx_dst_addr);
}
static void callback_uart_rx_dma(void *data)
{
struct exynos_uart_dma *uart_dma = container_of(data,
struct exynos_uart_dma, rx);
struct s3c24xx_uart_port *ourport = container_of(uart_dma,
struct s3c24xx_uart_port, uart_dma);
struct uart_port *port = &ourport->port;
struct tty_struct *tty = port->state->port.tty;
unsigned int uerstat = ourport->err_occurred;
unsigned int received_size = 0;
dma_addr_t src_addr = 0;
dma_addr_t dst_addr = 0;
uart_dma->ops->getposition(uart_dma->rx.ch, &src_addr, &dst_addr);
received_size = dst_addr - (unsigned int)uart_dma->rx_dst_addr;
dma_unmap_single(port->dev, uart_dma->rx_dst_addr,
received_size, DMA_FROM_DEVICE);
/* Error check after DMA transfer */
if (uerstat != 0) {
pr_err("UART Rx DMA Error(0x%x)!!!\n", uerstat);
if (uerstat & S3C2410_UERSTAT_BREAK) {
dbg("break!\n");
port->icount.brk += received_size;
}
if (uerstat & S3C2410_UERSTAT_FRAME)
port->icount.frame += received_size;
if (uerstat & S3C2410_UERSTAT_OVERRUN)
port->icount.overrun += received_size;
uerstat &= port->read_status_mask;
ourport->err_occurred = 0;
}
tty_insert_flip_string(tty, uart_dma->rx_buff, received_size);
tty_flip_buffer_push(tty);
if (uart_dma->rx.busy == 1)
uart_rx_dma_request(ourport);
}
static void enable_rx_dma(struct uart_port *port)
{
unsigned long flags;
unsigned int ufcon, ucon;
spin_lock_irqsave(&port->lock, flags);
ufcon = rd_regl(port, S3C2410_UFCON);
ufcon &= ~(0x7 << 4);
ufcon |= (0x1 << 2) | (0x1 << 1) | S5PV210_UFCON_RXTRIG64;
wr_regl(port, S3C2410_UFCON, ufcon);
/* set Rx mode to DMA mode */
ucon = rd_regl(port, S3C2410_UCON);
ucon &= ~((0x7 << UCON_RXBURST_SZ) |
(0x1 << UCON_TIMEOUT_VAL) |
(0x1 << UCON_EMPTYINT_EN) |
(0x1 << UCON_DMASUS_EN) |
UCON_RXMODE_CL);
ucon |= (BURST_1BYTE << UCON_RXBURST_SZ) |
(0xf << UCON_TIMEOUT_VAL) |
(0x1 << UCON_EMPTYINT_EN) |
(0x1 << UCON_DMASUS_EN) |
(0x1 << UCON_TIMEOUT_EN) |
UCON_RXDMA_MODE;
wr_regl(port, S3C2410_UCON, ucon);
spin_unlock_irqrestore(&port->lock, flags);
}
static void callback_uart_tx_dma(void *data)
{
struct exynos_uart_dma *uart_dma;
struct s3c24xx_uart_port *ourport;
struct uart_port *port;
struct circ_buf *xmit;
unsigned long ucon, uintm;
uart_dma = container_of(data, struct exynos_uart_dma, tx);
ourport = container_of(uart_dma, struct s3c24xx_uart_port, uart_dma);
port = &ourport->port;
dbg("callback_uart_dma\n");
dma_unmap_single(port->dev, uart_dma->tx_src_addr,
uart_dma->tx.req_size, DMA_TO_DEVICE);
xmit = &port->state->xmit;
xmit->tail = (xmit->tail + uart_dma->tx.req_size)&(UART_XMIT_SIZE - 1);
port->icount.tx += uart_dma->tx.req_size;
/* Set Tx mode to interrupt mode */
ucon = rd_regl(port, S3C2410_UCON);
ucon &= ~((0x7 << UCON_TXBURST_SZ) | UCON_TXMODE_CL);
ucon |= UCON_TXCPU_MODE;
wr_regl(port, S3C2410_UCON, ucon);
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(port);
if (uart_circ_empty(xmit)) {
s3c24xx_serial_stop_tx(port);
} else {
/* Unmask tx DMA */
uintm = rd_regl(port, S3C64XX_UINTM);
uintm &= ~(0x1 << 2);
wr_regl(port, S3C64XX_UINTM, uintm);
}
}
static void prepare_dma(struct uart_dma_data *dma,
unsigned len, dma_addr_t buf)
{
struct exynos_uart_dma *uart_dma;
struct samsung_dma_prep info;
struct samsung_dma_config config;
info.cap = DMA_SLAVE;
info.len = len;
info.fp = callback_uart_tx_dma;
info.fp_param = dma;
info.direction = dma->direction;
info.buf = buf;
config.direction = dma->direction;
config.fifo = dma->fifo_base;
/* burst size = 1byte (1, 4, 8bytes) */
config.width = DMA_SLAVE_BUSWIDTH_1_BYTE;
if (dma->direction == DMA_MEM_TO_DEV) {
uart_dma = container_of((void *)dma,
struct exynos_uart_dma, tx);
info.fp = callback_uart_tx_dma;
} else {
uart_dma = container_of((void *)dma,
struct exynos_uart_dma, rx);
info.fp = callback_uart_rx_dma;
}
uart_dma->ops->config(dma->ch, &co