嵌入式Linux的串口驱动

ARM 150浏览

CPU : s3c2410

PLAT : 由smdk2410修改来的一块板子。(资料依旧非常有限)

这次的项目,板子上面串口部分非常怪异。我们知道,samsung s3c2410这款芯片的UART控制器一共可以支持3个串行端口,如果不够用,还可以通过8250之类的芯片来扩展。而我手上的板子却不是这样实现的,它一共设计了7个端口,但是使用的方式比较特殊,port[1]和port[3]~port[6]这5个端口在任意时刻,只有一个有可能会被使用,考虑到这种特殊的使用方式,我们没有必要再承担一块8250芯片的成本,于是我们用一个映射到bank3(0x18000000)的寄存器来作为开关,实现虚拟的端口的切换,实际上port[3]~port[5]所用的硬件部分和port[1]是同一套,具体实现不再多言,这些对于写串口驱动来说貌似已经够用了。

下面开始修改源码(注意:红色部分是我添加或修改的 )

/linux-2.6.30.4/arch/arm/plat-s3c24xx/devs.c

添加UART设备和资源描述结构体。

#if 0 //modified by B.Zhou
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,
},
};
#else
static struct resource s3c2410_uart3_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_uart4_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_uart5_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_uart6_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,
}
};
#endif

#if 1 //added by B.Zhou
static struct platform_device s3c24xx_uart_device4 = {
.id = 4,
};

static struct platform_device s3c24xx_uart_device5 = {
.id = 5,
};

static struct platform_device s3c24xx_uart_device6 = {
.id = 6,
};
#endif

struct platform_device *s3c24xx_uart_src[7 ] = {
&s3c24xx_uart_device0,
&s3c24xx_uart_device1,
&s3c24xx_uart_device2,
&s3c24xx_uart_device3,
#if 1 //added by B.Zhou
&s3c24xx_uart_device4,
&s3c24xx_uart_device5,
&s3c24xx_uart_device6,
#endif
};

/linux-2.6.30.4/include/linux/autoconf.h

这个文件中的常量来自“make menuconfig”中的内核配置信息。

#define CONFIG_SERIAL_SAMSUNG_UARTS 7 //modified by B.Zhou
(注意,这个常量每次通过“make menuconfig”修改内核配置信息后,都会还原为3)

/linux-2.6.30.4/include/linux/serial_reg.h

在这里面添加bank3(0x18000000)寄存器相关的物理地址和虚拟地址的定义,具体如下:

#if 1 //added by B.Zhou for the driver of serial ports
#define pA_UART_Reg 0x18000000 //PA
#define vA_UART_Reg 0xe2000000 //VA
#define len_UART_Reg 0x00010000
#endif

/linux-2.6.30.4/arch/arm/mach-s3c2410/mach-smdk2410.c

在这里面要实现的是对于上面地址的映射。

先添加两个头文件:

#include <linux/serial_reg.h> // added by B.Zhou for the serial ports
#include <asm/memory.h> // added by B.Zhou for the __phys_to_pfn()

static struct map_desc smdk2410_iodesc[] __initdata = {
/* nothing here yet */
/* added by B.Zhou for the serial ports */
{vA_UART_Reg, __phys_to_pfn(pA_UART_Reg), len_UART_Reg, MT_DEVICE_NONSHARED},
};

linux-2.6.30.4/arch/arm/include/asm/io.h

添加静态变量,用于定义虚拟地址

volatile static unsigned long REG_SERIAL; // added by B.Zhou


/linux-2.6.30.4/drivers/serial/samsung.c

首先添加一个头文件,用于寄存器操作。

#include <asm/io.h> //added by B.Zhou for the "iowrite8()" and REG_SERIAL


我们在使用端口port[3]~port[5]之前需要通过0x18000000的寄存器来完成端口选择,完成使用后,还要还原状态。这个工作应该在端口启动和关闭时完成。那么我们就需要为port[3]~port[5],修改“static int s3c24xx_serial_startup(struct uart_port *port)”和“static int s3c24xx_serial_shutdown(struct uart_port *port)”两个函数。具体做法为添加以下两部分代码:

#if 1 //added by B.Zhou


static void s3c24xx_serial_shutdown_3(struct uart_port *port)
{
printk(KERN_INFO "ncalling s3c24xx_serial_shutdown_3n");
s3c24xx_serial_shutdown(port);
iowrite8(0x08,REG_SERIAL);
}

static void s3c24xx_serial_shutdown_4(struct uart_port *port)
{
printk(KERN_INFO "ncalling s3c24xx_serial_shutdown_4n");
s3c24xx_serial_shutdown(port);
iowrite8(0x08,REG_SERIAL);
}

static void s3c24xx_serial_shutdown_5(struct uart_port *port)
{
printk(KERN_INFO "ncalling s3c24xx_serial_shutdown_5n");
s3c24xx_serial_shutdown(port);
iowrite8(0x08,REG_SERIAL);
}

static void s3c24xx_serial_shutdown_6(struct uart_port *port)
{
printk(KERN_INFO "ncalling s3c24xx_serial_shutdown_6n");
s3c24xx_serial_shutdown(port);
iowrite8(0x08,REG_SERIAL);
}

#endif

static int s3c24xx_serial_startup(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
int ret;

dbg("s3c24xx_serial_startup: port(line:%d)=%p (%08lx,%p)n",
port->line,port->mapbase, port->membase);
rx_enabled(port) = 1;
#if 0 //modified by B.Zhou
ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
s3c24xx_serial_portname(port), ourport);
#else
ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, IRQF_SHARED,
s3c24xx_serial_portname(port), ourport);
#endif
if (ret != 0) {
printk(KERN_ERR "cannot get irq %dn", ourport->rx_irq);
return ret;
}

ourport->rx_claimed = 1;

dbg("requesting tx irq...n");

tx_enabled(port) = 1;
#if 0 //modified by B.Zhou
ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
s3c24xx_serial_portname(port), ourport);
#else
ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, IRQF_SHARED,
s3c24xx_serial_portname(port), ourport);
#endif

if (ret) {
printk(KERN_ERR "cannot get irq %dn", ourport->tx_irq);
goto err;
}

ourport->tx_claimed = 1;

dbg("s3c24xx_serial_startup okn");

/* the port reset code should have done the correct
* register setup for the port controls */

return ret;

err:
s3c24xx_serial_shutdown(port);
return ret;
}


#if 1 //added by B.Zhou

static int s3c24xx_serial_startup_3(struct uart_port *port)
{
printk(KERN_INFO "ncall s3c24xx_serial_startup_3n");
iowrite8(0x01,REG_SERIAL);
return s3c24xx_serial_startup(port);
}

static int s3c24xx_serial_startup_4(struct uart_port *port)
{
printk(KERN_INFO "ncall s3c24xx_serial_startup_4n");
iowrite8(0x02,REG_SERIAL);
return s3c24xx_serial_startup(port);
}

static int s3c24xx_serial_startup_5(struct uart_port *port)
{
printk(KERN_INFO "ncall s3c24xx_serial_startup_5n");
iowrite8(0x04,REG_SERIAL);
return s3c24xx_serial_startup(port);
}

static int s3c24xx_serial_startup_6(struct uart_port *port)
{
printk(KERN_INFO "ncall s3c24xx_serial_startup_6n");
iowrite8(0x08,REG_SERIAL);
return s3c24xx_serial_startup(port);
}
#endif

添加用于port[3]~port[6]的操作函数结构体,实际上只是修改了端口启动和关闭的函数,具体如下:

#if 1 //added by B.Zhou
static struct uart_ops s3c24xx_serial_ops3 = {
.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,
.startup = s3c24xx_serial_startup_3,
.shutdown = s3c24xx_serial_shutdown_3,
.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_ops s3c24xx_serial_ops4 = {
.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,
.startup = s3c24xx_serial_startup_4,
.shutdown = s3c24xx_serial_shutdown_4,
.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_ops s3c24xx_serial_ops5 = {
.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,
.startup = s3c24xx_serial_startup_5,
.shutdown = s3c24xx_serial_shutdown_5,
.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_ops s3c24xx_serial_ops6 = {
.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,
.startup = s3c24xx_serial_startup_6,
.shutdown = s3c24xx_serial_shutdown_6,
.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,
};
#endif

在 “static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS]” 数组中添加port[3]~port[5]的端口描述结构体:

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
[0] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
.iotype = UPIO_PORT,
.iobase = S3C2410_PA_UART0,
.irq = IRQ_S3CUART_RX0,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 0,
}
},
[1] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
.iotype = UPIO_PORT,
.iobase = S3C2410_PA_UART1,
.irq = IRQ_S3CUART_RX1,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 1,
}
},
#if CONFIG_SERIAL_SAMSUNG_UARTS > 2

[2] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
.iotype = UPIO_PORT,
.iobase = S3C2410_PA_UART2,
.irq = IRQ_S3CUART_RX2,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 2,
}
},
#endif
#if 1 //added by B.Zhou
#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
[3] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
.iotype = UPIO_PORT,
.iobase = S3C2410_PA_UART1 ,
.irq = IRQ_S3CUART_RX1 ,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops3,
.flags = UPF_BOOT_AUTOCONF,
.line = 3,
}
},

[4] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[4].port.lock),
.iotype = UPIO_PORT,
.iobase = S3C2410_PA_UART1,
.irq = IRQ_S3CUART_RX1,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops4,
.flags = UPF_BOOT_AUTOCONF,
.line = 4,
}
},

[5] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[5].port.lock),
.iotype = UPIO_PORT,
.iobase = S3C2410_PA_UART1,
.irq = IRQ_S3CUART_RX1,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops5,
.flags = UPF_BOOT_AUTOCONF,
.line = 5,
}
},

[6] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[6].port.lock),
.iotype = UPIO_PORT,
.iobase = S3C2410_PA_UART1,
.irq = IRQ_S3CUART_RX1,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops6,
.flags = UPF_BOOT_AUTOCONF,
.line = 6,
}
}
#endif

#endif
};

找到模块初始化函数,为“REG_SERIAL” 赋值,也可以直接添加上面提到的“serial_reg.h” 头文件。

static int __init s3c24xx_serial_modinit(void)
{
int ret;

#if 1 //added by B.Zhou
REG_SERIAL = 0xe2000000;

#endif
ret = uart_register_driver(&s3c24xx_uart_drv);
if (ret < 0) {
printk(KERN_ERR "failed to register UART drivern");
return -1;
}
return 0;
}

找到端口初始化函数,做如下修改:

static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info *info)
{
struct s3c24xx_uart_port *ptr = s3c24xx_serial_ports;
struct platform_device **platdev_ptr;
int i;

dbg("s3c24xx_serial_init_ports: initialising ports...n");

platdev_ptr = s3c24xx_uart_devs;
#if 1 //modified by B.Zhou
for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++, ptr++) {
if(i < 3)
s3c24xx_serial_init_port(ptr, info, *(platdev_ptr+i));
else
s3c24xx_serial_init_port(ptr, info, *(platdev_ptr+1));
}
#else
for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++, ptr++, platdev_ptr++) {
s3c24xx_serial_init_port(ptr, info, *platdev_ptr);
}
#endif

return 0;
}

下面修改控制台初始化函数中的工作频率:

static int __init
s3c24xx_serial_console_setup(struct console *co, char *options)
{
struct uart_port *port;
int baud = 115200; //modified by B.Zhou 9600
int bits = 8;
int parity = 'n';
int flow = 'n';

dbg("s3c24xx_serial_console_setup: co=%p (%d), %sn",
co, co->index, options);

/* is this a valid port */

if (co->index == -1 || co->index >= CONFIG_SERIAL_SAMSUNG_UARTS)
co->index = 0;


port = &s3c24xx_serial_ports[co->index].port;

/* is the port configured? */

if (port->mapbase == 0x0) {
co->index = 0;
port = &s3c24xx_serial_ports[co->index].port;
}

cons_uart = port;

dbg("s3c24xx_serial_console_setup: port=%p (%d)n", port, co->index);

/*
* Check whether an invalid uart number has been specified, and
* if so, search for the first available port that does have
* console support.
*/
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
else
s3c24xx_serial_get_options(port, &baud, &parity, &bits);

dbg("s3c24xx_serial_console_setup: baud %dn", baud);

return uart_set_options(port, co, baud, parity, bits, flow);
}

/linux-2.6.30.4/arch/arm/plat-s3c/init.c

在这个文件中修改UART设备初始化函数:

void __init s3c24xx_init_uartdevs(char *name,
struct s3c24xx_uart_resources *res,
struct s3c2410_uartcfg *cfg, int no)
{
struct platform_device *platdev;
struct s3c2410_uartcfg *cfgptr = uart_cfgs;
struct s3c24xx_uart_resources *resp;
int uart;
int uart_cur;

memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);

for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {

platdev = s3c24xx_uart_src[cfgptr->hwport];

resp = res + cfgptr->hwport;

s3c24xx_uart_devs[uart] = platdev;

platdev->name = name;
platdev->resource = resp->resources;
platdev->num_resources = resp->nr_resources;

platdev->dev.platform_data = cfgptr;
}
#if 1 //added by B.Zhou
for(uart_cur = 3; uart_cur < 7; uart_cur++, cfgptr++) {
memcpy(cfgptr, cfg-2, sizeof(struct s3c2410_uartcfg));

platdev = s3c24xx_uart_src[uart_cur];

resp = res + cfgptr->hwport;

s3c24xx_uart_devs[uart_cur] = platdev;

platdev->name = name;
platdev->resource = resp->resources;
platdev->num_resources = resp->nr_resources;

platdev->dev.platform_data = cfgptr;

}
nr_uarts = 7;
#else
nr_uarts = no;
#endif
}

至此,这个板子上的串口已经可以工作了,启动后会看到在“/dev” 文件夹下,生成了7个串口设备文件,分别是"s3c2410_serial0~ s3c2410_serial6 "。 但是还有一个问题,就是串口的缺省状态,比如波特率默认为9600,而我们更希望它是115200。

/linux-2.6.30.4/drivers/serial/serial_core.c

找到UART驱动注册函数:

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;

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;

#if 0 //modified by B.Zhou
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
#else
normal->init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOE);
normal->init_termios.c_oflag &= ~OPOST;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 115200;
#endif

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

tty_port_init(&state->info.port);
init_waitqueue_head(&state->info.delta_msr_wait);
tasklet_init(&state->info.tlet, uart_tasklet_action,
(unsigned long)state);
}

retval = tty_register_driver(normal);
out:
if (retval < 0) {
put_tty_driver(normal);
kfree(drv->state);
}
return retval;
}

终于,串口驱动全部完成了。

转至:http://blog.csdn.net/BZhouCHN/archive/2010/04/16/5494812.aspx