Linux uart底层设备驱动详解
本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记
一. uart 基本信息
- S3C2440A提供了3个独立的串口。
- 串口的操作使用中断或DMA模式。
- 串口的接受和发送缓存为 64-byte。
二. uart硬件初始化
- uart初始化调用的函数是:s3c24xx_init_uarts,这个函数被mini2440_map_io调用,mini2440_map_io函数赋值给了machine_desc.map_io函数。machine_desc.map_io函数在start_kernel函数里面调用。
- s3c24xx_init_uarts函数传参(mini2440_uartcfgs,3),如下所示:
static struct s3c2410_uartcfg mini2440_uartcfgs[] __initdata =
{[0] = {.hwport = 0,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,},[1] = {.hwport = 1,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,},[2] = {.hwport = 2,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,}
};
.hwport是硬件端口号,其他的结构体成员暂时往后看。
- s3c24xx_init_uarts函数调用cpu->init_uarts函数。
void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{if (cpu == NULL)return;if (cpu->init_uarts == NULL){printk(KERN_ERR "s3c24xx_init_uarts: cpu has no uart init\n");}else(cpu->init_uarts)(cfg, no);}
.init_uarts = s3c244x_init_uarts,
最终调用的是s3c244x_init_uarts函数。
- s3c244x_init_uarts函数,传入了参数s3c2410_uart_resources
void __init s3c244x_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{s3c24xx_init_uartdevs("s3c2440-uart", s3c2410_uart_resources, cfg, no);
}
static struct resource s3c2410_uart0_resource[] =
{[0] = {.start = S3C2410_PA_UART0,.end = S3C2410_PA_UART0 + 0x3fff,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_S3CUART_RX0,.end = IRQ_S3CUART_ERR0,.flags = IORESOURCE_IRQ,}
};static struct resource s3c2410_uart1_resource[] =
{[0] = {.start = S3C2410_PA_UART1,.end = S3C2410_PA_UART1 + 0x3fff,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_S3CUART_RX1,.end = IRQ_S3CUART_ERR1,.flags = IORESOURCE_IRQ,}
};static struct resource s3c2410_uart2_resource[] =
{[0] = {.start = S3C2410_PA_UART2,.end = S3C2410_PA_UART2 + 0x3fff,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_S3CUART_RX2,.end = IRQ_S3CUART_ERR2,.flags = IORESOURCE_IRQ,}
};static struct resource s3c2410_uart3_resource[] =
{[0] = {.start = S3C2443_PA_UART3,.end = S3C2443_PA_UART3 + 0x3fff,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_S3CUART_RX3,.end = IRQ_S3CUART_ERR3,.flags = IORESOURCE_IRQ,},
};struct s3c24xx_uart_resources s3c2410_uart_resources[] __initdata =
{[0] = {.resources = s3c2410_uart0_resource,.nr_resources = ARRAY_SIZE(s3c2410_uart0_resource),},[1] = {.resources = s3c2410_uart1_resource,.nr_resources = ARRAY_SIZE(s3c2410_uart1_resource),},[2] = {.resources = s3c2410_uart2_resource,.nr_resources = ARRAY_SIZE(s3c2410_uart2_resource),},[3] = {.resources = s3c2410_uart3_resource,.nr_resources = ARRAY_SIZE(s3c2410_uart3_resource),},
};
nr_resources = 2
s3c24xx_init_uartdevs函数分析
对platdev填充name,resource,num_resources,dev.platform_data等结构体成员,然后把platdev赋值给了s3c24xx_uart_devs全局变量,最终结果如下:
s3c24xx_uart_devs[0] =
{.name = "s3c2440-uart",.id = 0,.resource ={[0] = {.start = S3C2410_PA_UART0,.end = S3C2410_PA_UART0 + 0x3fff,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_S3CUART_RX0,.end = IRQ_S3CUART_ERR0,.flags = IORESOURCE_IRQ,}},.num_resources = 2,.dev.platform_data = {[0] = {.hwport = 0,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,},[1] = {.hwport = 1,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,},[2] = {.hwport = 2,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,}},
};
三. uart device注册
从上一步看,uart device 设备为s3c24xx_uart_devs,device个数为3.
uart设备在哪里注册呢,可以看到在s3c_arch_init函数中注册了uart设备。
static int __init s3c_arch_init(void)
{int ret;// do the correct init for cpuif (cpu == NULL)panic("s3c_arch_init: NULL cpu\n");ret = (cpu->init)();if (ret != 0)return ret;ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);return ret;
}
s3c_arch_init函数又是什么时候调用呢?通过arch_initcall(s3c_arch_init)可知,s3c_arch_init函数在内核启动时被调用。
platform_add_devicesplatform_device_add(pdev);if (pdev->id != -1)dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);elsedev_set_name(&pdev->dev, "%s", pdev->name);
pdev->id = 0,1,2, 最终注册的设备为s3c2440-uart.0,s3c2440-uart.1,s3c2440-uart.2
设备路径:/sys/devices/platform/s3c2440-uart.0
/sys/devices/platform/s3c2440-uart.1
/sys/devices/platform/s3c2440-uart.2
四. uart driver的注册
搜索设备名s3c2440-uart,找到uart driver的注册文件/drivers/serial/s3c2440.c文件
在s3c2440.c文件中platform_driver的定义如下:
static struct platform_driver s3c2440_serial_driver =
{.probe = s3c2440_serial_probe,.remove = __devexit_p(s3c24xx_serial_remove),.driver = {.name = "s3c2440-uart",.owner = THIS_MODULE,},
};
最后通过调用platform_driver_register函数注册uart driver。
- driver在/sys/bus/platform/drivers/s3c2440-uart目录下。
五. uart driver的probe函数
uart driver的probe函数为s3c2440_serial_probe,但是s3c2440_serial_probe函数又调用了s3c24xx_serial_probe函数,s3c24xx_serial_probe函数传参s3c2440_uart_inf,如下所示:
#define S3C2440_UFSTAT_TXFULL (1<<14)
#define S3C2440_UFSTAT_RXFULL (1<<6)
#define S3C2440_UFSTAT_TXSHIFT (8)
#define S3C2440_UFSTAT_RXSHIFT (0)
#define S3C2440_UFSTAT_TXMASK (63<<8)
#define S3C2440_UFSTAT_RXMASK (63)static struct s3c24xx_uart_info s3c2440_uart_inf =
{.name = "Samsung S3C2440 UART",.type = PORT_S3C2440,.fifosize = 64,.rx_fifomask = S3C2440_UFSTAT_RXMASK,.rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,.rx_fifofull = S3C2440_UFSTAT_RXFULL,.tx_fifofull = S3C2440_UFSTAT_TXFULL,.tx_fifomask = S3C2440_UFSTAT_TXMASK,.tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,.get_clksrc = s3c2440_serial_getsource,.set_clksrc = s3c2440_serial_setsource,.reset_port = s3c2440_serial_resetport,
};
.fifosize = 64:表示缓冲buffer为64byte。
.rx_fifomask = 63:读缓冲buffer count正常取值为0 ~ 63,否则就是读缓冲buffer满了。
.rx_fifoshift = 0:因为 Rx FIFO Count为【5:0】,不需要移动
rx_fifofull = (1<<6):表示读缓冲buffer满了
.tx_fifofull = (1<<14):写缓冲buffer满了
.tx_fifomask = (63<<8):写缓冲buffer count正常取值为0 ~ 63,但是要右移8位
.tx_fifoshift = (8):写缓冲buffer count需要右移8位
s3c2440_serial_getsource,,s3c2440_serial_resetport几个函数放在后面看。
- s3c2440_serial_setsource
设置时钟源,总共有3种类型时钟给串口使用: PCLK, UEXTCLK or FCLK/n。
当选择FCLK/n时钟时,要通过 UCON寄存器的[15:12]设置n值。
uart clock = FCLK / (divider+6)
n = divider+6
当设置UCON0时, 7<= n <= 21; UCON1,UCON2要设置为0
当设置UCON1时, 22 <= n <= 36 UCON0,UCON2要设置为0
当设置UCON2时, 37 <= n <= 43 UCON0,UCON1要设置为0
设置PCLK, UCON的[11:10]设置为0,2
设置UCLK, UCON的[11:10]设置为1
设置FCLK/n, UCON的[11:10]设置为3
static int s3c2440_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *clk)
{unsigned long ucon = rd_regl(port, S3C2410_UCON);/* todo - proper fclk<>nonfclk switch. */ucon &= ~S3C2440_UCON_CLKMASK;if (strcmp(clk->name, "uclk") == 0)ucon |= S3C2440_UCON_UCLK;else if (strcmp(clk->name, "pclk") == 0)ucon |= S3C2440_UCON_PCLK;else if (strcmp(clk->name, "fclk") == 0)ucon |= S3C2440_UCON_FCLK;else {printk(KERN_ERR "unknown clock source %s\n", clk->name);return -EINVAL;}wr_regl(port, S3C2410_UCON, ucon);return 0;
}
- s3c2440_serial_getsource
读UCON寄存器的[11:10],判断时钟源是PCLK, UEXTCLK or FCLK/n。
如果是PCLK,UEXTCLK时钟源,clk->divisor = 1,如果是FCLK/n时钟,clk->divisor需要根据UCON寄存器的[15:12]获得。
现在看s3c24xx_serial_probe函数。
- 端口初始化,端口是全局变量s3c24xx_serial_ports。调用函数s3c24xx_serial_init_port。s3c24xx_serial_ports变量里面有个uart_port类型的变量,主要是给该变量赋值。
①保存info结构体
ourport->info = info;
②ourport->port.fifosize = info->fifosize = 64,之前给的默认值是16
③获取mem资源,保存在port->mapbase和port->membase变量中
res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
if (res == NULL) {printk(KERN_ERR "failed to find memory resource for uart\n");return -EINVAL;
}
dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);port->mapbase = res->start;
port->membase = S3C_VA_UART + res->start - (S3C_PA_UART & 0xfff00000);
④获取终端号,保存在ourport->rx_irq和ourport->tx_irq变量中
ret = platform_get_irq(platdev, 0);
if (ret < 0)port->irq = 0;
else {port->irq = ret;ourport->rx_irq = ret;ourport->tx_irq = ret + 1;
⑤获取uart时钟,保存在ourport->clk中
ourport->clk = clk_get(&platdev->dev, "uart");
⑥reset fifo,设置串口,调用函数s3c24xx_serial_resetport,实际是调用s3c2440_uart_inf的reset_port函数。
就是s3c2440_serial_resetport函数。s3c2440_serial_resetport函数设置了下面的几步的寄存器。
static struct s3c2410_uartcfg mini2440_uartcfgs[] __initdata = {[0] = {.hwport = 0,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,},
};
⑦给UCON0寄存器追加写入0x3c5(1111000101)
1:发送时产生电平中断(而不是脉冲中断)
1:接收时产生电平中断
1:允许接收时产生超时中断
1:允许接收时产生错误中断
0:一般模式,不用回环模式,就是一个串口的RX接TX
0:不发送break信号
01:中断或者轮询方式发送数据
01:中断或者轮询方式接收数据
⑧给 ULCON0寄存器写入0x03(00000011)
0: 普通模式(而不是红外模式)
000: 无校验位
0: 1个停止位
11: 8个数据位
⑨给 UFCON0寄存器写入0x51(1010001)
01: Tx FIFO Trigger Level = 16-byte
01: Rx FIFO Trigger Level = 8-byte
0:Reserved
0:
0:
1:使能FIFO
- 调用uart_add_one_port函数,uart_add_one_port函数里面主要是注册tty设备。
注册后的设备为s3c2410_serial0,在目录:/sys/devices/platform/s3c2440-uart.0/tty下面。
六. ktermios结构体
ktermios结构体结构体定义如下:
struct ktermios
{tcflag_t c_iflag; /* input mode flags */tcflag_t c_oflag; /* output mode flags */tcflag_t c_cflag; /* control mode flags */tcflag_t c_lflag; /* local mode flags */cc_t c_line; /* line discipline */cc_t c_cc[NCCS]; /* control characters */speed_t c_ispeed; /* input speed */speed_t c_ospeed; /* output speed */
};
下面参考大牛的博客
c_iflag常用的如下:
IGNBRK:忽略输入中的 BREAK 状态
BRKINT
IGNPAR:忽略桢错误和奇偶校验错
PARMRK:如果没有设置 IGNPAR,在有奇偶校验错或桢错误的字符前插入 /377 /0。如果既没有设置 IGNPAR 也没有设置PARMRK,将有奇偶校验错或桢错误的字符视为 /0
INPCK:启用输入奇偶检测
ISTRIP:去掉第八位
INLCR:将输入中的 NL 翻译为 CR
IGNCR:忽略输入中的回车
ICRNL:将输入中的回车翻译为新行 (除非设置了 IGNCR)
IUCLC:将输入中的大写字母映射为小写字母
IXON:启用输入的XON流控制
IXANY:允许任何字符来重新开始输出
IXOFF:启用输入的XFF流控制
c_oflag常用的如下:
OPOST:启用具体实现自行定义的输出处理。
OLCUC:将输出中的小写字母映射为大写字母。
ONLCR:将输出中的新行符映射为回车-换行。
OCRNL:将输出中的回车映射为新行符
ONOCR:不在第 0 列输出回车。
ONLRET:不输出回车。
OFILL:发送填充字符作为延时,而不是使用定时来延时。
OFDEL:填充字符是 ASCII DEL (0177)。如果不设置,填充字符则是 ASCII NUL。
NLDLY:新行延时掩码。取值为 NL0 和 NL1。
CRDLY:回车延时掩码。取值为 CR0, CR1, CR2, 或 CR3。
TABDLY:水平跳格延时掩码。取值为 TAB0, TAB1, TAB2, TAB3 (或 XTABS)。取值为 TAB3,即 XTABS,将扩展跳格为空格 (每个跳格符填充 8 个空格)。
BSDLY:回退延时掩码。取值为 BS0 或 BS1。(从来没有被实现过)
VTDLY:竖直跳格延时掩码。取值为 VT0 或 VT1。
FFDLY:进表延时掩码。取值为 FF0 或 FF1。
c_cflag 标志常量:
CBAUD:波特率掩码 (4+1 位)。
CBAUDEX:扩展的波特率掩码 (1 位),包含在 CBAUD 中。
CSIZE:字符长度掩码。取值为 CS5, CS6, CS7, 或 CS8。
CSTOPB:设置两个停止位,而不是一个。
CREAD:打开接受者。
PARENB:允许输出产生奇偶信息以及输入的奇偶校验。
PARODD:输入和输出是奇校验。
HUPCL:在最后一个进程关闭设备后,降低 modem 控制线 (挂断)。
CLOCAL:忽略 modem 控制线。
LOBLK:从非当前 shell 层阻塞输出(用于 shl )。(?)
CIBAUD:输入速度的掩码。CIBAUD 各位的值与 CBAUD 各位相同,左移了 IBSHIFT 位。
CRTSCTS:启用 RTS/CTS (硬件) 流控制。
七. uart_ops结构体
在上面的probe函数中,有个重要的结构体uart_ops,这个结构体定义了uart驱动层的操作。
static struct uart_ops s3c24xx_serial_ops =
{.pm = s3c24xx_serial_pm, //电源管理.tx_empty = s3c24xx_serial_tx_empty, //检测发送缓冲区是否为空.get_mctrl = s3c24xx_serial_get_mctrl, //获取串口是否流控.set_mctrl = s3c24xx_serial_set_mctrl, //设置串口流控.stop_tx = s3c24xx_serial_stop_tx, //停止发送.start_tx = s3c24xx_serial_start_tx, //启动发送.stop_rx = s3c24xx_serial_stop_rx, //停止接收.enable_ms = s3c24xx_serial_enable_ms,.break_ctl = s3c24xx_serial_break_ctl, //发送break信号.startup = s3c24xx_serial_startup, //启动串口的发送接收.shutdown = s3c24xx_serial_shutdown, //关闭串口.set_termios = s3c24xx_serial_set_termios, //串口时钟,波特率等参数设置.type = s3c24xx_serial_type,.release_port = s3c24xx_serial_release_port, //释放串口.request_port = s3c24xx_serial_request_port, //申请串口.config_port = s3c24xx_serial_config_port, //串口配置.verify_port = s3c24xx_serial_verify_port, //串口校验
};
下面一一分析里面的函数。
- s3c24xx_serial_tx_empty函数分析
该函数是判断发送端是否为空
①读 UFCON0寄存器,判断第0位是否为1,为1表示FIFO模式,为0表示非FIFO模式
②FIFO模式下,判断 UFSTAT0寄存器的 Tx FIFO Count是否为0,或者判断 Tx FIFO Full标志位,为空返回1,否则返回0.
if (ufcon & S3C2410_UFCON_FIFOMODE)
{if ((ufstat & info->tx_fifomask) != 0 ||(ufstat & info->tx_fifofull))return 0;return 1;
}
③非FIFO模式下,判断 UTRSTAT0寄存器的第2位,为1表示空了。
static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
{return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);
}
s3c24xx_serial_get_mctrl和s3c24xx_serial_set_mctrl
硬件流控函数s3c24xx_serial_stop_tx
TX disable,就是把port->unused[0]设置为0,如果是流控的话,还要调用s3c24xx_serial_rx_enable函数打开串口的流控制接收。s3c24xx_serial_start_tx
TX enable,就是把port->unused[0]设置为1,如果是流控的话,还要调用s3c24xx_serial_rx_disable函数停止串口的流控制接收。s3c24xx_serial_stop_rx
RX disable, 就是把port->unused[1]设置为0s3c24xx_serial_break_ctl
设置 UCON寄存器的第4位,写1表示发送break信号
#define S3C2410_UCON_SBREAK (1<<4)
ucon = rd_regl(port, S3C2410_UCON);
ucon |= S3C2410_UCON_SBREAK;
wr_regl(port, S3C2410_UCON, ucon);
- s3c24xx_serial_startup
RX enable, TX enable, 申请串口接收和发送中断
rx_enabled(port) = 1;
ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0, s3c24xx_serial_portname(port), ourport);tx_enabled(port) = 1;
ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0, s3c24xx_serial_portname(port), ourport);
- s3c24xx_serial_shutdown
TX disable,释放TX中断
RX disable,释放RX中断
free_irq(ourport->tx_irq, ourport);
tx_enabled(port) = 0;free_irq(ourport->rx_irq, ourport);
rx_enabled(port) = 0;
- s3c24xx_serial_set_termios
在注册uart driver时,init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;串口core层调用uart_ops的set_termios函数时,会传入这个init_termios.c_cflag。
①不支持流控制
termios->c_cflag &= ~(HUPCL | CMSPAR);
termios->c_cflag |= CLOCAL;
HUPCL:在最后一个进程关闭设备后,降低 modem 控制线 (挂断)。
CLOCAL:忽略 modem 控制线。
②根据termios找到对用的波特率,init_termios.c_cflag |= B9600,返回波特率baud = 9600
baud = uart_get_baud_rate(port, termios, old, 0, 115200*8);
③计算分频系数,分频系数的计算公式为: UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud);
最后将分频系数写入 UBRDIV寄存器
wr_regl(port, S3C2410_UBRDIV, quot);
④获取到时钟源clksrc,clk
⑤刚开始的时候ourport->clksrc = NULL, ourport->baudclk = NULL
需要将时钟源写入 UCON[11:10]
s3c24xx_serial_setsource(port, clksrc);
使能时钟
clk_enable(clk);
将时钟源保存起来,下次时钟源没有更新的时候不用重新设置上面的步骤
ourport->clksrc = clksrc;
ourport->baudclk = clk;
ourport->baudclk_rate = clk ? clk_get_rate(clk) : 0;
⑥ULCON寄存器的设置
设置数据位数,init_termios.c_cflag |= CS8,数据是8位
#define S3C2410_LCON_CS8 (0x3)
ulcon = S3C2410_LCON_CS8;
是否设置2个停止位,我们这里不用设置
#define S3C2410_LCON_STOPB (1<<2)
if (termios->c_cflag & CSTOPB)ulcon |= S3C2410_LCON_STOPB;
是否设置奇偶校验位,我们这里没有奇偶校验
if (termios->c_cflag & PARENB)
{if (termios->c_cflag & PARODD)ulcon |= S3C2410_LCON_PODD;elseulcon |= S3C2410_LCON_PEVEN;
}
else
{ulcon |= S3C2410_LCON_PNONE;
}
⑦UMCON寄存器的设置,主要是设置流控制,我们这里不用流控制
#define S3C2410_UMCOM_AFC (1<<4)
umcon = (termios->c_cflag & CRTSCTS) ? S3C2410_UMCOM_AFC : 0;
wr_regl(port, S3C2410_UMCON, umcon);
⑧给port->read_status_mask赋值
接收溢出错误中断:port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
如果启用检验:
port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY;
我们这里是没有启用校验的。
⑨给port->ignore_status_mask赋值
if (termios->c_iflag & IGNPAR)port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;
一定要init_termios.c_cflag |= CREAD,不然会忽略所有的输入字符
if ((termios->c_cflag & CREAD) == 0)port->ignore_status_mask |= RXSTAT_DUMMY_READ;
八. 串口中断模式下接收数据
Rx FIFO Trigger Level = 8-byte,即缓存中超过了8个字节数据,就会产生中断。
- 读 UFSTAT寄存器,获取缓存中有多少个数据。如果没有数据,退出。
if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)break;
- 从URXH寄存器中读取一个字符,并且port->icount.rx计数加1。
ch = rd_regb(port, S3C2410_URXH);
port->icount.rx++;
- 读错误状态寄存器 UERSTAT
如果收到break信号,port->icount.brk加1;
如果产生 Frame Error,port->icount.frame加1;
if (uerstat & S3C2410_UERSTAT_FRAME)port->icount.frame++;
如果产生溢出,port->icount.overrun加1
if (uerstat & S3C2410_UERSTAT_OVERRUN)port->icount.overrun++;
- 将读到的字符放入tty->buffer中。调用函数uart_insert_char。
uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
- 缓存中的数据读完了,退出while循环,调用tty_flip_buffer_push函数,将数据push到上层。
tty_flip_buffer_push(tty);
九. 串口中断模式下发送数据
- 如果是软件流控字符(xon/xoff char),将字符写入UTXH寄存器后退出。
if (port->x_char)
{wr_regb(port, S3C2410_UTXH, port->x_char);port->icount.tx++;port->x_char = 0;goto out;
}
- 如果xmit这个环形buffer是空的,或者串口被停止了,TX disable
if (uart_circ_empty(xmit) || uart_tx_stopped(port))
{s3c24xx_serial_stop_tx(port);goto out;
}
- 把xmit这个环形buffer的数据一个一个写入到UTXH寄存器,直到TX缓存满了,或者xmit buffer空了,
同时port->icount.tx加1.
while (!uart_circ_empty(xmit) && count-- > 0)
{if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)break;wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);port->icount.tx++;
}
- 退出上面的写这个步骤,判断一下xmit buffer剩余的字符,如果小于一半,唤醒tty,请求注入更多的数据。
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)uart_write_wakeup(port);tasklet_schedule(&state->tlet);tasklet_init(&state->tlet, uart_tasklet_action, (unsigned long)state);tty_wakeup(state->port.tty);
- 如果xmit buffer空了,TX disable掉。
if (uart_circ_empty(xmit))s3c24xx_serial_stop_tx(port);
十. 串口计算分频系数
计算分频系数调用的函数是:
static int s3c24xx_serial_calcbaud(struct baud_calc *calc, struct uart_port *port,struct s3c24xx_uart_clksrc *clksrc, unsigned int baud)
{struct s3c24xx_uart_port *ourport = to_ourport(port);unsigned long rate;calc->src = clk_get(port->dev, clksrc->name);if (calc->src == NULL || IS_ERR(calc->src))return 0;rate = clk_get_rate(calc->src);rate /= clksrc->divisor;calc->clksrc = clksrc;if (ourport->info->has_divslot){unsigned long div = rate / baud;/* The UDIVSLOT register on the newer UARTs allows us to* get a divisor adjustment of 1/16th on the baud clock.** We don't keep the UDIVSLOT value (the 16ths we calculated* by not multiplying the baud by 16) as it is easy enough* to recalculate.*/calc->quot = div / 16;calc->calc = rate / div;} else {calc->quot = (rate + (8 * baud)) / (16 * baud);calc->calc = (rate / (calc->quot * 16));}calc->quot--;return 1;
}
分频系数的计算公式为: UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
获取时钟源,这里的时钟源应该是PCLK,假设为100M
rate = 100000000Hz。
因为是PCLK,clksrc->divisor = 1,因此rate还是100000000Hz
ourport->info->has_divslot这个变量没有定义,因此:
calc->quot = (rate + (8 * baud)) / (16 * baud);
calc->calc = (rate / (calc->quot * 16));
按照计算公式,calc->quot = (rate ) / (16 * baud),可是为什么这里
calc->quot = (rate + (8 * baud)) / (16 * baud)?
原因是:calc->quot值要进行四舍五入。
最后,calc->quot要减1才是实际的值。
十一. s3c24xx_serial_getclk函数
- 获取s3c2410_uartcfg时钟的个数,s3c2410_uartcfg在mach-mini2440.c定义如下:
static struct s3c2410_uartcfg mini2440_uartcfgs[] __initdata =
{[0] = {.hwport = 0,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,},[1] = {.hwport = 1,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,},[2] = {.hwport = 2,.flags = 0,.ucon = 0x3c5,.ulcon = 0x03,.ufcon = 0x51,}
};
没有定义cfg->clocks_size,那么cfg->clocks_size = 0
当cfg->clocks_size = 0时,采用默认的时钟pclk
static struct s3c24xx_uart_clksrc tmp_clksrc =
{.name = "pclk",.min_baud = 0,.max_baud = 0,.divisor = 1,
};
最终函数输出:*clksrc = tmp_clksrc
*clk = pclk
但是如果有多个时钟呢,最终采用哪一个时钟?
每个时钟都计算出一个波特率的值,这几个值都比较接近传入的baud的值。并且让resptr指针指向所有时钟源的数组的末尾。
for (i = 0; i < cfg->clocks_size; i++, clkp++)
{if (s3c24xx_serial_calcbaud(resptr, port, clkp, baud))resptr++;
}
把每一个时钟源计算出来的波特率值sptr->calc与原来的baud比较,哪一个值最接近baud,就把哪一个作为uart的时钟源。
for (sptr = res; sptr < resptr; sptr++)
{calc_deviation = baud - sptr->calc;if (calc_deviation < 0)calc_deviation = -calc_deviation;if (calc_deviation < deviation) {best = sptr;deviation = calc_deviation;}
}
参考:
https://www.cnblogs.com/colife/p/5531093.html
Linux uart底层设备驱动详解相关推荐
- Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)
文章目录 系列文章目录 前言 正文 Device Tree Overlays:"插件"设备树 传统设备树 "插件"设备树 使用前提 案例说明 设备树:foo.d ...
- Linux字符设备驱动详解四(使用自属的xbus驱动总线)
文章目录 系列文章目录 前言 驱动目录 正文 驱动总线 总线管理 总线注册 设备注册 驱动注册 代码示例 总结 系列文章目录 Linux字符设备驱动详解 Linux字符设备驱动详解二(使用设备驱动模型 ...
- Linux 内核console设备实现详解
本文基于Linux-4.14 1.earlycon early console,顾名思义,他表示的就是早期的console设备,主要用于在系统启动阶段的内核打印的输出,由于linux内核实际设备驱动模 ...
- Linux usb设备驱动详解
1.Linux usb设备驱动框架 USB是通用串行总线的总称,Linux内核几乎支持所有的usb设备,包括键盘,鼠标,打印机,modem,扫描仪.Linux的usb驱动分为主机驱动与gadget驱动 ...
- Linux设备驱动之usb设备驱动详解
原文地址:http://blog.csdn.net/chenjin_zhong/article/details/6329316 1.Linux usb设备驱动框架 USB是通用串行总线的总称,Linu ...
- 【转】草根老师的 linux字符设备驱动详解
Linux 驱动 之 模块化编程 Linux 驱动之模块参数和符号导出 Linux 设备驱动之字符设备(一) Linux 设备驱动之字符设备(二) Linux 设备驱动之字符设备(三) 转载于:htt ...
- itop4412 LCD设备驱动详解(四)之PROBE再深入
LCD的工作,在kernel中有device和driver两个描述,这也是必然 在第二节中我们详解介绍了 s3cfb_main.c ---probe函数的框架. 回顾一下probe函数的作用: 1. ...
- itop4412 LCD设备驱动详解(三)之PROBE
lcd的工作,在kernel中有device和driver两个描述,这也是必然 在上一节中我们详解介绍了 s3cfb_main.c ---probe函数的框架. 回顾一下probe函数的作用: 1. ...
- 设备、设备节点和设备驱动详解
Linux下的设备通常分为三类,字符设备,块设备和网络设备. 设备驱动程序也分为对应的三类:字符设备驱动程序.块设备驱动程序和网络设备驱动程序. 常见的字符设备有鼠标.键盘.串口.控制台等. 常见的块 ...
- Linux LCD设备驱动详解
本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记 一. LCD device硬件信息 1.LCD控制器的寄存器地址从 0X4D000000开始 2.lcd de ...
最新文章
- ubuntu设置securecrt串口权限
- 《Nature》发文:好导师的16个标准
- 深度学习平台你知道多少?
- java应用架构设计_java应用架构设计
- 输出torch构建的模型结构
- 为EasyUI 的Tab 标签添加右键菜单
- 紧跟月影大佬的步伐,一起来学习如何写好JS(下)
- Android精准计步器
- 为什么python不需要编译_为什么我用Go写机器学习部署平台,而偏偏不用Python?...
- 里程碑式突破!我国量子计算原型机“九章”问世
- 【Java】Java之Collections.emptyList()、emptySet()、emptyMap()的作用和好处以及要注意的地方
- python 容器类型存放_Python学习笔记三(容器)
- oppo9s刷机教程_OPPO R9s R9sk刷机教程 OPPO R9s R9sk卡刷升级教程
- python如何修改图片透明度_如何改变图像的不透明度
- CorelDRAW插件-CPG插件开发-环境搭建-VS2017-TLB文件-CDR插件
- 高性能零售IT系统的建设03-监控体系化的重要不亚于开发的投入
- 区块链是什么?区块链能做什么?区块链学习路线分享
- 上海计算机二级python_上海市高等学校计算机等级考试(二级)《Python程序设计
- 物金妆团是长存于她心中金妆团影响她一生的两金妆团位挚友和恩师金妆团莫妮卡说她要用
- Lepus(天兔)数据库监控系统