基于Linux-2.6.28的 EELiod平台UART驱动分析(一)

0、前言

深圳亿道电子技术有限公司自主研发的EELiod高校教学开发平台采用Intel PXA架构的PXA270,在Linux内核中,有比较成熟的基于Intel PXA架构的UART驱动,现对该驱动作一点分析,这对于理解、掌握以及编写UART驱动有一定的借鉴作用。

基于Linux-2.6.28的PXA架构UART硬件相关的驱动代码位于:/drivers/serial/pxa.c。与UART硬件无关的驱动代码位于/drivers/Serial/Serial_core.c,另外与整个PXA架构相关的代码位于arch/arm/Mach-pxa/Devices.c。从这三个文件入手,基本上可以开始分析基于PXA架构的UART驱动。

一、 PXA UART串口驱动初始化

linux-2.6.28/drivers/serial/pxa.c

int __init serial_pxa_init(void)

{

int ret;

ret = uart_register_driver(&serial_pxa_reg);

if (ret != 0)

return ret;

ret = platform_driver_register(&serial_pxa_driver);

if (ret != 0)

uart_unregister_driver(&serial_pxa_reg);

return ret;

}

在UART初始化函数中,首先调用uart_register_driver(struct uart_driver *drv)函数来注册UART驱动,在这函数里它会分配一个tty_driver对象,并初始化tty_operations为serial_pxa_pops, 这是serial-core.c提供的统一的UART操作函数。下面为serial_pxa_reg结构体的定义。

static struct uart_driver serial_pxa_reg = {

.owner = THIS_MODULE,

.driver_name = “PXA serial”,

.dev_name = “ttyS”,

.major = TTY_MAJOR, //major=4

.minor = 64,

.nr = 4,

.cons = PXA_CONSOLE,

};

从serial_pxa_reg结构体中,UART使用的设备名为ttySn(n=0~3),主设备号major为4,次设备号minor为64,串口数nr为4,当然PXA270只有三个串口(FFUART、BTUART、STUART),但是PXA255/26x 微处理器还附加了一个HWUART;当用户编译内核选择SERIAL_PXA_CONSOLE时,UART驱动中将增加终端控制台输出结构体PXA_CONSOLE

#ifdef CONFIG_SERIAL_PXA_CONSOLE

…….

static struct console serial_pxa_console = {

.name = “ttyS”,

.write = serial_pxa_console_write,

.device = uart_console_device,

.setup = serial_pxa_console_setup,

.flags = CON_PRINTBUFFER,

.index = -1,

.data = &serial_pxa_reg,

};

#define PXA_CONSOLE &serial_pxa_console

#else

#define PXA_CONSOLE NULL

#endif

初始化函数然后调用平台驱动注册函数platform_driver_register(struct platform_driver *drv)注册UART驱动,该函数实际上定义有关“伪总线”(pseudo-bus)驱动的相关操作,Linux-2.6.x内核为简化一些片上系统(SoC)系统的集成外设和传统PC接插件的驱动接口,提出了“伪总线”的概念。

linux-2.6.28/drivers/Base/ Platform.c

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;

if (drv->pm)

drv->driver.pm = &drv->pm->base;

return driver_register(&drv->driver);

}

platform_driver_register函数中的serial_pxa_driver变量定义如下:该结构体实际上定义驱动探测(probe)、移除(remove)、挂起(suspend)、重启(resume)等相关函数操作:/drivers/serial/pxa.c文件本质上完成对所有这些函数的实现。

static struct platform_driver serial_pxa_driver = {

.probe = serial_pxa_probe,

.remove = serial_pxa_remove,

.suspend = serial_pxa_suspend,

.resume = serial_pxa_resume,

.driver = {

.name = “pxa2xx-uart”,

.owner = THIS_MODULE,

},

};

1、UART驱动侦测函数(probe)

static int serial_pxa_probe(struct platform_device *dev)

{

struct uart_pxa_port *sport;

struct resource *mmres, *irqres;

int ret;

fficeffice” />

mmres = platform_get_resource(dev, IORESOURCE_MEM, 0);

irqres = platform_get_resource(dev, IORESOURCE_IRQ, 0);

if (!mmres || !irqres)

return -ENODEV;

sport = kzalloc(sizeof(struct uart_pxa_port), GFP_KERNEL);

if (!sport)

return -ENOMEM;

sport->clk = clk_get(&dev->dev, “UARTCLK”);

if (IS_ERR(sport->clk)) {

ret = PTR_ERR(sport->clk);

goto err_free;

}

sport->port.type = PORT_PXA;

sport->port.iotype = UPIO_MEM;

sport->port.mapbase = mmres->start;

sport->port.irq = irqres->start;

sport->port.fifosize = 64;

sport->port.ops = &serial_pxa_pops;

sport->port.line = dev->id;

sport->port.dev = &dev->dev;

sport->port.flags = UPF_IOREMAP | UPF_BOOT_AUTOCONF;

sport->port.uartclk = clk_get_rate(sport->clk);

/*

* Is it worth keeping this?

*/

if (mmres->start == __PREG(FFUART))

sport->name = “FFUART”;

else if (mmres->start == __PREG(BTUART))

sport->name = “BTUART”;

else if (mmres->start == __PREG(STUART))

sport->name = “STUART”;

else if (mmres->start == __PREG(HWUART))

sport->name = “HWUART”;

else

sport->name = “???”;

sport->port.membase = ioremap(mmres->start, mmres->end – mmres->start + 1);

if (!sport->port.membase) {

ret = -ENOMEM;

goto err_clk;

}

serial_pxa_ports[dev->id] = sport;

uart_add_one_port(&serial_pxa_reg, &sport->port);

platform_set_drvdata(dev, sport);

return 0;

err_clk:

clk_put(sport->clk);

err_free:

kfree(sport);

return ret;

}

初始化函数然后调用平台驱动注册函数platform_driver_register注册UART驱动时会调用UART驱动侦测函数serial_pxa_probe,并且传入serial_pxa_driver参数,从下面调试信息可以看出,linux操作系统中进行平台初始化时便调用了注册UART的platform_driver_register函数。而且三次调用该函数,也就是说,操作系统至少有三处跟串口初始化有关。

基于Linux-2.6.28的 EELiod平台UART驱动分析(2)” src=”http://cdn.verydemo.com/upload/2013_05_01/13673638012221.png” alt=”[原创]基于Linux-2.6.28的 EELiod平台UART驱动分析(2)” width=”549″ height=”358″ />

2009-2-3 01:01 上传下载附件 (56.87 KB) [原创]基于Linux-2.6.28的 EELiod平台UART驱动分析(2)

根据“伪总线”(pseudo-bus)驱动,PXA架构的微处理器属于SoC类别,而EELiod开发平台采用的微处理器为PXA270,在Linux-ffice:smarttags” />2.6.28操作系统中,该处理器属于PXA27x系列。在linux-2.6.28/arch/arm/Mach-pxa/Pxa27x.c文件中有如下定义,其中涉及到PXA27x系列的三个串口,在该文件中pxa27x_init(void)函数中通过调用platform_add_devices函数将平台相关的所有外设添加到操作系统中,在内核初始化所调用的postcore_initcall(pxa27x_init),因为系统部分系统需要利用串口作为控制台。

static struct platform_device *devices[] __initdata = {

&pxa27x_device_udc,

&pxa_device_ffuart,

&pxa_device_btuart,

&pxa_device_stuart,

&pxa_device_i2s,

&pxa_device_rtc,

&pxa27x_device_i2c_power,

&pxa27x_device_ssp1,

&pxa27x_device_ssp2,

&pxa27x_device_ssp3,

&pxa27x_device_pwm0,

&pxa27x_device_pwm1,

};

static int __init pxa27x_init(void)

{

int i, ret = 0;

if (cpu_is_pxa27x()) {

reset_status = RCSR;

clks_register(pxa27x_clks, ARRAY_SIZE(pxa27x_clks));

if ((ret = pxa_init_dma(32)))

return ret;

pxa27x_init_pm();

for (i = 0; i < ARRAY_SIZE(pxa27x_sysdev); i++) {

ret = sysdev_register(&pxa27x_sysdev);

if (ret)

pr_err(“failed to register sysdev[%d]/n”, i);

}

ret = platform_add_devices(devices, ARRAY_SIZE(devices));

}

return ret;

}

postcore_initcall(pxa27x_init);

PXA27x系统的三个串口都使用“pxa2xx-uart”作为驱动名(见前面的serial_pxa_driver变量),UART驱动侦测函数调用platform_get_resource函数来获取“伪总线”(pseudo-bus)驱动相关资源(如中断),对应PXA27x的三个串口的相关资源定义位于arch/arm/Mach-pxa/Devices.c文件中。

static struct resource pxa_resource_ffuart[] = {

{

.start = __PREG(FFUART),

.end = __PREG(FFUART) + 35,

.flags = IORESOURCE_MEM,

}, {

.start = IRQ_FFUART,

.end = IRQ_FFUART,

.flags = IORESOURCE_IRQ,

}

};

struct platform_device pxa_device_ffuart= {

.name = “pxa2xx-uart”,

.id = 0,

.resource = pxa_resource_ffuart,

.num_resources = ARRAY_SIZE(pxa_resource_ffuart),

};

static struct resource pxa_resource_btuart[] = {

{

.start = __PREG(BTUART),

.end = __PREG(BTUART) + 35,

.flags = IORESOURCE_MEM,

}, {

.start = IRQ_BTUART,

.end = IRQ_BTUART,

.flags = IORESOURCE_IRQ,

}

};

struct platform_device pxa_device_btuart = {

.name = “pxa2xx-uart”,

.id = 1,

.resource = pxa_resource_btuart,

.num_resources = ARRAY_SIZE(pxa_resource_btuart),

};

static struct resource pxa_resource_stuart[] = {

{

.start = __PREG(STUART),

.end = __PREG(STUART) + 35,

.flags = IORESOURCE_MEM,

}, {

.start = IRQ_STUART,

.end = IRQ_STUART,

.flags = IORESOURCE_IRQ,

}

};

struct platform_device pxa_device_stuart = {

.name = “pxa2xx-uart”,

.id = 2,

.resource = pxa_resource_stuart,

.num_resources = ARRAY_SIZE(pxa_resource_stuart),

};

static struct resource pxa_resource_hwuart[] = {

{

.start = __PREG(HWUART),

.end = __PREG(HWUART) + 47,

.flags = IORESOURCE_MEM,

}, {

.start = IRQ_HWUART,

.end = IRQ_HWUART,

.flags = IORESOURCE_IRQ,

}

};

struct platform_device pxa_device_hwuart = {

.name = “pxa2xx-uart”,

.id = 3,

.resource = pxa_resource_hwuart,

.num_resources = ARRAY_SIZE(pxa_resource_hwuart),

};

然后初始化uart_pxa_port结构体,在该结构体中定义了有个uart_port结构体成员变量port,实际上又回到了硬件无关的Serial_core.c文件中。

struct uart_pxa_port {

struct uart_port port;

unsigned char ier;

unsigned char lcr;

unsigned char mcr;

unsigned int lsr_break_flag;

struct clk *clk;

char *name;

};

在UART的侦测函数最后,调用Serial_core.c中的uart_add_one_port函数将驱动和一个实际的端口结构题相关联。

2、PXA架构的UART驱动操作

在PXA架构的UART驱动侦测函数(probe)中定义了struct uart_pxa_port *sport;该结构体中封装了uart_port结构体,并在侦测函数中对uart_port结构体进行了初始化。主要包括端口类型(type)、内存映射起始地址(mapbase)、中断(irq)、串口操作(ops)等,其中ops为uart_ops结构体变量,它定义了串口硬件能完成的所有操作(如数据的收发等)。下面为uart_ops结构体实例,该结构体实例过程实际上将一系列serial_pxa_ 函数赋值给了uart_ops结构体的成员。

struct uart_ops serial_pxa_pops = {

.tx_empty = serial_pxa_tx_empty,//发送缓冲区空

.set_mctrl = serial_pxa_set_mctrl,//设置modem控制(MCR)

.get_mctrl = serial_pxa_get_mctrl,//获取modem控制设置

.stop_tx = serial_pxa_stop_tx,//停止接收字符

.start_tx = serial_pxa_start_tx,//开始传输字符

.stop_rx = serial_pxa_stop_rx,//停止接收字符

.enable_ms = serial_pxa_enable_ms,// modem状态中断使能

.break_ctl = serial_pxa_break_ctl,// 控制break信号的传输

.startup = serial_pxa_startup,//启动串口

.shutdown = serial_pxa_shutdown, //禁用串口

.set_termios = serial_pxa_set_termios,//改变串口参数

.pm = serial_pxa_pm,//端口电源管理

.type = serial_pxa_type,//返回描述特定端口的常量字符串指针

.release_port = serial_pxa_release_port,//释放串口占用的内存及IO资源

.request_port = serial_pxa_request_port,//申请串口所需的内存和IO资源

.config_port = serial_pxa_config_port,//执行端口所需的自动配置步骤

.verify_port = serial_pxa_verify_port,//验证新的串行端口信息

};

所有函数的原型定义中linux-2.6.28/include/Linux /Serial_core.h文件中struct uart_ops结构体中:

set_mctrl()函数的原型为:void (*set_mctrl)(struct uart_port *, unsigned int mctrl);它将参数port所对应的调制解调器控制线的值设为参数mctrl的值。

get_mctrl()函数的原型为:unsigned int (*get_mctrl)(struct uart_port *);该函数返回调制解调器控制输入的现有状态,这些状态信息包括:TIOCM_CD(CD 信号状态)、TIOCM_CTS(CTS信号状态)、TIOCM_DSR(DSR信号状态)、TIOCM_RI(RI信号状态)等。如果信号被置为有效,则对应位将被置位。

端口启动函数startup()的原型为:int (*startup)(struct uart_port *);该函数申请所有中断资源,初始化底层驱动状态,并开启端口为可接收数据的状态。

shutdown()函数完成与startup()函数的作用相反,其原型:void (*shutdown)(struct uart_port *);这个函数禁用端口,释放所有的中断资源。

具体可参考linux-2.6.28/include/Linux /Serial_core.h文件中struct uart_ops中的定义

2.1串口的启动与停止

串口驱动uart_ops结构体的startup ()成员函数serial_pxa_startup用于启动端口,申请端口的发送、接收中断,使能端口的发送和接收,其实现如代码:

static int serial_pxa_startup(struct uart_port *port)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

unsigned long flags;

int retval;

if (port->line == 3) /* HWUART */

up->mcr |= UART_MCR_AFE;

else

up->mcr = 0;

up->port.uartclk = clk_get_rate(up->clk);

/*

* Allocate the IRQ

*/

retval = request_irq(up->port.irq, serial_pxa_irq, 0, up->name, up);

if (retval)

return retval;

/*

* Clear the FIFO buffers and disable them.

* (they will be reenabled in set_termios())

*/

serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO);

serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO |

UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);

serial_out(up, UART_FCR, 0);

/*

* Clear the interrupt registers.

*/

(void) serial_in(up, UART_LSR);

(void) serial_in(up, UART_RX);

(void) serial_in(up, UART_IIR);

(void) serial_in(up, UART_MSR);

/*

* Now, initialize the UART

*/

serial_out(up, UART_LCR, UART_LCR_WLEN8);

spin_lock_irqsave(&up->port.lock, flags);

up->port.mctrl |= TIOCM_OUT2;

serial_pxa_set_mctrl(&up->port, up->port.mctrl);

spin_unlock_irqrestore(&up->port.lock, flags);

/*

* Finally, enable interrupts. Note: Modem status interrupts

* are set via set_termios(), which will be occurring imminently

* anyway, so we don’t enable them here.

*/

up->ier = UART_IER_RLSI | UART_IER_RDI | UART_IER_RTOIE | UART_IER_UUE;

serial_out(up, UART_IER, up->ier);

/*

* And clear the interrupt registers again for luck.

*/

(void) serial_in(up, UART_LSR);

(void) serial_in(up, UART_RX);

(void) serial_in(up, UART_IIR);

(void) serial_in(up, UART_MSR);

return 0;

}

中断服务程序中,首先判断所产生的中断,如果为接收中断,则响应receive_chars(up, &lsr)函数,如果为发送中断,则响应transmit_chars(up)函数。

static inline irqreturn_t serial_pxa_irq(int irq, void *dev_id)

{

struct uart_pxa_port *up = dev_id;

unsigned int iir, lsr;

iir = serial_in(up, UART_IIR);

if (iir & UART_IIR_NO_INT)

return IRQ_NONE;

lsr = serial_in(up, UART_LSR);

if (lsr & UART_LSR_DR)

receive_chars(up, &lsr);//接收字符函数

check_modem_status(up);

if (lsr & UART_LSR_THRE)

transmit_chars(up);//发送字符函数

return IRQ_HANDLED;

}

接收字符函数receive_chars调用ch = serial_in(up, UART_RX)函数以获得接收到的字符,并调用uart_insert_char()将该字符添加了tty设备的flip缓冲区中,当接收到256个字符或者不再能接受到字符后,调用tty_flip_buffer_push()函数向上层“压栈推”tty设备的flip缓冲。uart_insert_char函数是串口核心层对tty_insert_flip_char()的封装,它作为内联函数被定义于linux-2.6.28/include/Linux /serial_core.h文件文件中。

static inline void receive_chars(struct uart_pxa_port *up, int *status)

{

struct tty_struct *tty = up->port.info->port.tty;

unsigned int ch, flag;

int max_count = 256;

do {

ch = serial_in(up, UART_RX); //读出字符

flag = TTY_NORMAL;

up->port.icount.rx++;

if (unlikely(*status & (UART_LSR_BI | UART_LSR_PE |

UART_LSR_FE | UART_LSR_OE))) {

/*

* For statistics only

*/

if (*status & UART_LSR_BI) {

*status &= ~(UART_LSR_FE | UART_LSR_PE);

up->port.icount.brk++;

if (uart_handle_break(&up->port))

goto ignore_char;

} else if (*status & UART_LSR_PE)

up->port.icount.parity++;

else if (*status & UART_LSR_FE)

up->port.icount.frame++;

if (*status & UART_LSR_OE)

up->port.icount.overrun++;

/*

* Mask off conditions which should be ignored.

*/

*status &= up->port.read_status_mask;

#ifdef CONFIG_SERIAL_PXA_CONSOLE

if (up->port.line == up->port.cons->index) {

/* Recover the break flag from console xmit */

*status |= up->lsr_break_flag;

up->lsr_break_flag = 0;

}

#endif

if (*status & UART_LSR_BI) {

flag = TTY_BREAK;

} else if (*status & UART_LSR_PE)

flag = TTY_PARITY;

else if (*status & UART_LSR_FE)

flag = TTY_FRAME;

}

if (uart_handle_sysrq_char(&up->port, ch))

goto ignore_char;

//插入字符到tty设备的flip 缓冲

uart_insert_char(&up->port, *status, UART_LSR_OE, ch, flag);

ignore_char:

*status = serial_in(up, UART_LSR);

} while ((*status & UART_LSR_DR) && (max_count– > 0));

tty_flip_buffer_push(tty); //刷新tty设备的flip 缓冲

}

发送字符函数transmit_chars读取环形缓冲区中的字符,并调用serial_out(up, UART_TX, up->port.x_char)函数将数据写入发送缓冲寄存器UART_TX。当发送环形缓冲区中的字符数小于WAKEUP_CHARS(256),驱动将通过uart_write_wakeup函数请求上层向下传递更多的数据,uart_circ_chars_pending、uart_circ_empty是定义于serial_core.h中的宏,分别返回环形缓冲区剩余的字符数以及判断缓冲区是否为空。

static void transmit_chars(struct uart_pxa_port *up)

{

struct circ_buf *xmit = &up->port.info->xmit; //得到环形缓冲区

int count;

//如果定义了xchar,发送

if (up->port.x_char) {

serial_out(up, UART_TX, up->port.x_char);//发送字符

up->port.icount.tx++;

up->port.x_char = 0;

return;

}

/* 如果没有更多的字符需要发送,或者uart Tx停止,则停止uart并退出 */

if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) {

serial_pxa_stop_tx(&up->port);

return;

}

count = up->port.fifosize / 2; //每次最多发送字符的字符数(32个字符)

do {

serial_out(up, UART_TX, xmit->buf[xmit->tail]);// 发送环形缓冲中的数据

xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE – 1);

up->port.icount.tx++;

if (uart_circ_empty(xmit)) //发送环形缓冲中的数据已发空

break;

} while (–count > 0);

// 如果环形缓冲区中剩余的字符少于WAKEUP_CHARS,唤醒上层

if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)

uart_write_wakeup(&up->port);

if (uart_circ_empty(xmit)) //如果发送环形buffer为空

serial_pxa_stop_tx(&up->port); //停止发送

}

serial_pxa_startup的“反函数”为serial_pxa_shutdown,其释放和屏蔽中断,禁止发送和接收,

static void serial_pxa_shutdown(struct uart_port *port)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

unsigned long flags;

free_irq(up->port.irq, up);

/*

* Disable interrupts from this port

*/

up->ier = 0;

serial_out(up, UART_IER, 0);

spin_lock_irqsave(&up->port.lock, flags);

up->port.mctrl &= ~TIOCM_OUT2;

serial_pxa_set_mctrl(&up->port, up->port.mctrl);

spin_unlock_irqrestore(&up->port.lock, flags);

/*

* Disable break condition and FIFOs

*/

serial_out(up, UART_LCR, serial_in(up, UART_LCR) & ~UART_LCR_SBC);

serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO |

UART_FCR_CLEAR_RCVR |

UART_FCR_CLEAR_XMIT);

serial_out(up, UART_FCR, 0);

}

2.2串口的数据收发

串口数据的接收由启动函数所注册的中断服务程序中所调用的接收字符函数receive_chars函数完成,发送数据则由serial_pxa_start_tx函数通过置位发送中断寄存器UART_IER再响应中断服务程序,然后由中断服务程序发送字符函数transmit_chars完成数据的发送。

static void serial_pxa_start_tx(struct uart_port *port)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

if (!(up->ier & UART_IER_THRI)) {

up->ier |= UART_IER_THRI;

serial_out(up, UART_IER, up->ier);//置位发送中断

}

2.3 串口参数设置

串口驱动uart_ops结构体的set_termios()成员函数用于改变端口的波特率、字长、停止位、奇偶校验等参数设置,它会根据传递给它的port、termios参数成员的值设置PXA架构的UART的线路控制寄存器(LCR)、波特率分频锁存寄存器(DLL、DLH)、接收FIFO控制寄存器(FCR)等。

static void serial_pxa_set_termios(struct uart_port *port, struct ktermios *termios,

struct ktermios *old)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

unsigned char cval, fcr = 0;

unsigned long flags;

unsigned int baud, quot;

//设置数据位

switch (termios->c_cflag & CSIZE) {

case CS5:

cval = UART_LCR_WLEN5;

break;

case CS6:

cval = UART_LCR_WLEN6;

break;

case CS7:

cval = UART_LCR_WLEN7;

break;

default:

case CS8:

cval = UART_LCR_WLEN8;

break;

}

if (termios->c_cflag & CSTOPB)

cval |= UART_LCR_STOP; //设置停止位

if (termios->c_cflag & PARENB)

cval |= UART_LCR_PARITY; //奇偶校验使能选择

if (!(termios->c_cflag & PARODD))

cval |= UART_LCR_EPAR; //奇偶校验设置

//获取波特率

baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);

quot = uart_get_divisor(port, baud); //UART端口时钟分频系数

//UART端口时钟分频系数设置接收FIFO的大小阈值

if ((up->port.uartclk / quot) < (2400 * 16))

fcr = UART_FCR_ENABLE_FIFO | UART_FCR_PXAR1;

else if ((up->port.uartclk / quot) < (230400 * 16))

fcr = UART_FCR_ENABLE_FIFO | UART_FCR_PXAR8;

else

fcr = UART_FCR_ENABLE_FIFO | UART_FCR_PXAR32;

spin_lock_irqsave(&up->port.lock, flags);//屏蔽中断

up->ier |= IER_UUE;

//更新UART端口的超时设置

uart_update_timeout(port, termios->c_cflag, baud);

up->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;

if (termios->c_iflag & INPCK)

up->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE;

if (termios->c_iflag & (BRKINT | PARMRK))

up->port.read_status_mask |= UART_LSR_BI;

up->port.ignore_status_mask = 0;

if (termios->c_iflag & IGNPAR)

up->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;

if (termios->c_iflag & IGNBRK) {

up->port.ignore_status_mask |= UART_LSR_BI;

if (termios->c_iflag & IGNPAR)

up->port.ignore_status_mask |= UART_LSR_OE;

}

if ((termios->c_cflag & CREAD) == 0)

up->port.ignore_status_mask |= UART_LSR_DR;

up->ier &= ~UART_IER_MSI;

if (UART_ENABLE_MS(&up->port, termios->c_cflag))

up->ier |= UART_IER_MSI;

serial_out(up, UART_IER, up->ier);//重新开启中断

if (termios->c_cflag & CRTSCTS)

up->mcr |= UART_MCR_AFE;

else

up->mcr &= ~UART_MCR_AFE;

serial_out(up, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */

serial_out(up, UART_DLL, quot & 0xff); /* LS of divisor */

serial_out(up, UART_DLM, quot >> 8); /* MS of divisor */

serial_out(up, UART_LCR, cval); /* reset DLAB */

up->lcr = cval; /* Save LCR */

serial_pxa_set_mctrl(&up->port, up->port.mctrl);

serial_out(up, UART_FCR, fcr);//设置接收FIFO控制寄存器

spin_unlock_irqrestore(&up->port.lock, flags);//恢复中断

}

2.4 串口驱动Modem控制的获取与设置

串口驱动Modem控制的获取主要通过获取PXA架构微处理器的Modem状态寄存器(MSR)中的位信息。

static unsigned int serial_pxa_get_mctrl(struct uart_port *port)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

unsigned char status;

unsigned int ret;

status = serial_in(up, UART_MSR);//读取Modem状态寄存器

ret = 0;

if (status & UART_MSR_DCD)//数据载波检测状态(DCD)

ret |= TIOCM_CAR;

if (status & UART_MSR_RI) //振铃状态(RI)

ret |= TIOCM_RNG;

if (status & UART_MSR_DSR) //数据设置准备状态(DSR)

ret |= TIOCM_DSR;

if (status & UART_MSR_CTS) //清除发送状态(CTS)

ret |= TIOCM_CTS;

return ret;

}

串口驱动Modem控制器的设置主要设置PXA架构微处理器的Modem状态控制寄存器(MCR)中的位信息。

static void serial_pxa_set_mctrl(struct uart_port *port, unsigned int mctrl)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

unsigned char mcr = 0;

if (mctrl & TIOCM_RTS)

mcr |= UART_MCR_RTS;

if (mctrl & TIOCM_DTR)

mcr |= UART_MCR_DTR;

if (mctrl & TIOCM_OUT1)

mcr |= UART_MCR_OUT1;

if (mctrl & TIOCM_OUT2)

mcr |= UART_MCR_OUT2;

if (mctrl & TIOCM_LOOP)

mcr |= UART_MCR_LOOP;

mcr |= up->mcr;

serial_out(up, UART_MCR, mcr); //设置Modem控制寄存器(MCR)

}

2.5串口的数据停止收发

串口驱动uart_ops结构体stop_rx()成员函数serial_pxa_stop_tx ()用于停止发送串口的数据,主要通过屏蔽发送中断的方式完成。

static void serial_pxa_stop_tx(struct uart_port *port)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

if (up->ier & UART_IER_THRI) {//如果端口发送已使能

up->ier &= ~UART_IER_THRI;//屏蔽发送中断

serial_out(up, UART_IER, up->ier);//设置中断使能寄存器(IER)

}

}

串口驱动uart_ops结构体的stop_rx ()成员函数serial_pxa_stop_rx用于停止接收串口的数据,通过屏蔽接收中断的方式完成。

static void serial_pxa_stop_rx(struct uart_port *port)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

up->ier &= ~UART_IER_RLSI;

up->port.read_status_mask &= ~UART_LSR_DR; //接收中断复位

serial_out(up, UART_IER, up->ier); //设置中断使能寄存器(IER)

}

2.6串口发送缓冲是否为空的判断

串口驱动uart_ops结构体的tx_empty()成员函数serial_pxa_tx_empty用于判断发送缓冲区是否为空,通过获取UART的线路状态寄存器(LSR)的TEMT位来确定发送缓冲是否为空,代码如下:

static unsigned int serial_pxa_tx_empty(struct uart_port *port)

{

struct uart_pxa_port *up = (struct uart_pxa_port *)port;

unsigned long flags;

unsigned int ret;

spin_lock_irqsave(&up->port.lock, flags);

ret = serial_in(up, UART_LSR) & UART_LSR_TEMT ? TIOCSER_TEMT : 0;//判断TEMT位

spin_unlock_irqrestore(&up->port.lock, flags);

return ret;

}

2.7串口驱动的其他操作函数。

串口驱动uart_ops结构体其他操作函数如type()成员函数serial_pxa_type(struct uart_port *port)、config_port成员函数static void serial_pxa_config_port(struct uart_port *port, int flags)等,相对比较简单,大家可参考linux-2.6.28/drivers/serial/ pxa.c文件。

3.心得总结

Linux-2.6.28下的所有驱动相当linux-2.4.x来讲,条理更清楚,这对从事linux开发和研究的工程师来讲,提供了更大的方便。通过对这些代码的分析,弄清程序的来龙去脉对自己来讲也是一种提高,尤其当你进行内核和驱动移植时,你要到巨人的肩上站一站,这样可以大大提供您的工作效率。

都成为命途中美丽的点缀,看天,看雪,安安静静,不言不语都是好风景。

基于Linux-2.6.28的 EELiod平台UART驱动分析(一)

相关文章:

你感兴趣的文章:

标签云: