ARM Linux S3C2440 之UART分析

本文转载并加入自己的看法:2440uart

在分析ARM-Linux s3c2440中UART的时有必要先了解 s3c2440A中串口的硬件知识。

硬件篇:

S3c2440A串口提供三个独立的异步串行通信I/O端口(asynchronousserial I/O ports)。每一个串口均可以以普通中断方式或者DMA方式进行数据收发。采用系统时钟时,最大速率为115.2kbps.如果采用外部时钟(UEXTCLK),UART速度可以更快。每个串口包含有2个64-byte的FIFO缓存区用来发送或传输数据。S3c2440A 串口具有可编程波特率,红外(IR)收发数据,1或者2 位的停止位(stop),5/6/7/8 位数据宽度和奇偶校验功能(parity checking)。

每个串口由波特率产生单元,发送单元,接收单元和控制单元组成。如下图所示,波特产生单元的时钟可以是PCLK,FCLK/n,或者UEXTCLK(外部输入的时钟)。发送和接收单元包含有一个64-byte的FIFOs(先入先出队列)和数据移位器。

ARM Linux S3C2440 之UART分析_第1张图片

发送数据时,数据先被写进FIFO,然后拷贝到数据移位器后发送数据,最后数据被一位一位由数据发送脚(TxDn)送出。类似的,数据在接收时,数据一位一位的由数据接收脚(RxDn)接收,然后拷贝到FIFO缓存区。


寄存器:

串口的控制寄存器有两种,一种是UCONx,一种是ULCONx,它们各有三个:UCON0(ULCON0) ~ UCON2(ULCON2)分别对应于每一个串口,用于设置UART的工作模式,波特率,中断类型
ARM Linux S3C2440 之UART分析_第2张图片


状态寄存器:UTRSTAT0 ~UTRSTAT2, 用于串口工作时,接收/发送的状态指示:

ARM Linux S3C2440 之UART分析_第3张图片

FIFO控制寄存器: UFCON0 ~ UFCON2, 用于对FIFO的设置,假如使用的是中断触发,当缓冲区的字节达到FIFO设置的触发值,就会触发中断产生.


FIFO状态寄存器: UFSTAT0 ~ UFSTAT2, 用于表示FIFO缓存中的状态


对于Arm-linux s3c2440串口的使用,主要是对以上寄存器的操作。

下面将结合源码分析arm-linux s3c2440串口驱动的实现(软件篇)

软件篇(linux-2.6.22.6):

Linux系统的串口驱动与一般字符设备并一样,它采用层次化的架构,从而看做是一个串行系统来实现。

(1)      关注UART或其他底层串行硬件特征的底层驱动程序。

(2)      和底层驱动程序接口的TTY驱动程序。

(3)      加工用于和TTY驱动程序交换数据的线路规程。

下图描述了串行系统间的层次结构关系(s3c2440串口实现例),可以概括为:用户应用层 --> 线路规划层 --> TTY层 --> 底层驱动层 --> 物理硬件层


线路规程和TTY驱动程序是与硬件平台无关的,Linux源码中已经提供了实现,所以对于具体的平台,我们只需实现底层驱动程序即可,这也是我们最关心的。在s3c2440a中,主要由dirivers/serial/下的s3c2410.c和samsung.c实现。

Uart驱动程序主要围绕三个关键的数据结构展开(include/linux/serial_core.h中定义):

UART特定的驱动程序结构定义:struct uart_driver s3c24xx_uart_drv;

UART端口结构定义: struct uart_port s3c24xx_serial_ops;

UART相关操作函数结构定义: struct uart_ops s3c24xx_serial_ops;

基于以上三个结构体,来看看s3c2440是如何挂接到Linux中串口构架的:

S3c2440串口相关操作函数定义在s3c24xx_serial_ops(路径: drivers\serial\S3c2410.c)中,这个是一个struct uart_ops结构

static struct uart_ops s3c24xx_serial_ops = {
	.pm		= s3c24xx_serial_pm,//电源管理相关的函数
	.tx_empty	= s3c24xx_serial_tx_empty,//检查发送的fifo是为空
	.get_mctrl	= s3c24xx_serial_get_mctrl,//是否串口流控
	.set_mctrl	= s3c24xx_serial_set_mctrl,//是否设置串口流控cts
	.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,//串口检测
};
驱动程序结构定义:

static struct uart_driver s3c24xx_uart_drv = {
	.owner		= THIS_MODULE,
	.dev_name	= "s3c2410_serial",//具体设备的名称
	.nr		= 3,//定义几个端口
	.cons		= S3C24XX_SERIAL_CONSOLE,//console端口号
	.driver_name	= S3C24XX_SERIAL_NAME,//串口名
	.major		= S3C24XX_SERIAL_MAJOR,//主设备号
	.minor		= S3C24XX_SERIAL_MINOR,//次设备号
};
端口配置结构定义,其中包括了一个struct uart_ports结构(下面的赋值都是对uart_port的填充):

static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = {
	[0] = {//串口0
		.port = {
			.lock		= __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
			.iotype		= UPIO_MEM,
			.irq		= IRQ_S3CUART_RX0,//接收中断号
			.uartclk	= 0,
			.fifosize	= 16,//fifo缓冲的大小
			.ops		= &s3c24xx_serial_ops,//串口的操作函数
			.flags		= UPF_BOOT_AUTOCONF,
			.line		= 0,
		}
	},
	[1] = {
		.port = {
			.lock		= __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
			.iotype		= UPIO_MEM,
			.irq		= IRQ_S3CUART_RX1,
			.uartclk	= 0,
			.fifosize	= 16,
			.ops		= &s3c24xx_serial_ops,
			.flags		= UPF_BOOT_AUTOCONF,
			.line		= 1,
		}
	},
#if NR_PORTS > 2
	[2] = {
		.port = {
			.lock		= __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
			.iotype		= UPIO_MEM,
			.irq		= IRQ_S3CUART_RX2,
			.uartclk	= 0,
			.fifosize	= 16,
			.ops		= &s3c24xx_serial_ops,
			.flags		= UPF_BOOT_AUTOCONF,
			.line		= 2,
		}
	}
#endif
};

综上所述,s3c2440主要是实现这三个数据结构: 

s3c24xx_serial_ops,  s3c24xx_uart_drv, s3c24xx_serial_ports


下面将进一步结合源码探讨ARM-Linuxs3c2440 的实现。

回顾一下上文,s3c2440串口底层驱动围绕三个数据结构展开:

UART特定的驱动程序结构定义:struct uart_driver s3c24xx_uart_drv;

UART端口结构定义: struct uart_port s3c24xx_serial_ops;

UART相关操作函数结构定义: struct uart_ops s3c24xx_serial_ops;

实现了这三个数据结构体,基本完成了驱动操作函数的实现,紧接着需要对串口设备及设备驱动进行初始化,首先是模块初始化
module_init(s3c24xx_serial_modinit);

static int __init s3c24xx_serial_modinit(void)
{
	int ret;

	ret = uart_register_driver(&s3c24xx_uart_drv);
	if (ret < 0) {
		printk(KERN_ERR "failed to register UART driver\n");
		return -1;
	}

	s3c2400_serial_init();
	s3c2410_serial_init();
	s3c2412_serial_init();
	s3c2440_serial_init();

	return 0;
}
以上代码支持了2400,2410,2412,2440的串口初始化。

uart_register_driver在串口核心及TTY层间进行相关注册(路径:drivers\serial\Serial_core.c):

int uart_register_driver(struct uart_driver *drv)
{
	struct tty_driver *normal = NULL;
	int i, retval;

	BUG_ON(drv->state);

	/*
	 * Maybe we should be using a slab cache for this, especially if
	 * we have a large number of ports to handle.
	 */
	drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
	retval = -ENOMEM;
	if (!drv->state)
		goto out;

	normal  = alloc_tty_driver(drv->nr);
	if (!normal)
		goto out;

	drv->tty_driver = normal;//将tty驱动付接到uart driver上

	normal->owner		= drv->owner;
	normal->driver_name	= drv->driver_name;
	normal->name		= drv->dev_name;
	normal->major		= drv->major;
	normal->minor_start	= drv->minor;
	normal->type		= TTY_DRIVER_TYPE_SERIAL;
	normal->subtype		= SERIAL_TYPE_NORMAL;
	normal->init_termios	= tty_std_termios;
	normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
	normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
	normal->flags		= TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
	normal->driver_state    = drv;
	tty_set_operations(normal, &uart_ops);

	/*
	 * Initialise the UART state(s).
	 */
	for (i = 0; i < drv->nr; i++) {
		struct uart_state *state = drv->state + i;

		state->close_delay     = 500;	/* .5 seconds */
		state->closing_wait    = 30000;	/* 30 seconds */

		mutex_init(&state->mutex);
	}

	retval = tty_register_driver(normal);//注册tty驱动
 out:
	if (retval < 0) {
		put_tty_driver(normal);
		kfree(drv->state);
	}
	return retval;
}
tty驱动注册完毕后,注册 接着进行总线驱动platform_driver_register:
看下函数s3c2440_serial_init的实现:

static inline int s3c2440_serial_init(void)
{
	return s3c24xx_serial_init(&s3c2440_serial_drv, &s3c2440_uart_inf);
}
static int s3c24xx_serial_init(struct platform_driver *drv,
			       struct s3c24xx_uart_info *info)
{
	dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);
	return platform_driver_register(drv);
}
int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;
	if (drv->suspend)
		drv->driver.suspend = platform_drv_suspend;
	if (drv->resume)
		drv->driver.resume = platform_drv_resume;
	return driver_register(&drv->driver);
}
platfrom_driver_register()中调用driver_register(),因为串口设备在系统是一种platform_device所以是一种总线驱动类型,总线设备驱动注册platform_driver_register()之后,串口设备即可和相应的驱动关联起来了,这样就完成了串口设备与串口驱动的注册过程。
下面看看platform_driver的定义:

static struct platform_driver s3c2440_serial_drv = {
	.probe		= s3c2440_serial_probe,//在平台驱动和设备匹配后就会调用到
	.remove		= s3c24xx_serial_remove,
	.suspend	= s3c24xx_serial_suspend,
	.resume		= s3c24xx_serial_resume,
	.driver		= {
		.name	= "s3c2440-uart",
		.owner	= THIS_MODULE,
	},
};
platform_device 的定义:









你可能感兴趣的:(UART)