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 cpu

	if (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_devices
    platform_device_add(pdev);
        if (pdev->id != -1)
            dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
        else
            dev_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位
在这里插入图片描述
Linux uart底层设备驱动详解_第1张图片
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]获得。
    Linux uart底层设备驱动详解_第2张图片

现在看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个数据位
Linux uart底层设备驱动详解_第3张图片

⑨给 UFCON0寄存器写入0x51(1010001)
01: Tx FIFO Trigger Level = 16-byte
01: Rx FIFO Trigger Level = 8-byte
0:Reserved
0:
0:
1:使能FIFO
Linux uart底层设备驱动详解_第4张图片

  • 调用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]设置为0

  • s3c24xx_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);

Linux uart底层设备驱动详解_第5张图片

④获取到时钟源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;
    else
        ulcon |= 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内核驱动)