嵌入式Linux的串口驱动
2019-07-12 20:10 发布
生成海报
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]是同一套,具体实现不再多言,这些对于写串口驱动来说貌似已经够用了。
下面开始修改源码(注意:红 {MOD}部分是我添加或修改的 )
/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 // added by B.Zhou for the serial ports #include // 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 //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_3/n"); 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_4/n"); 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_5/n"); 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_6/n"); 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 %d/n", 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 %d/n", ourport->tx_irq); goto err; } ourport->tx_claimed = 1; dbg("s3c24xx_serial_startup ok/n"); /* 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_3/n"); 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_4/n"); 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_5/n"); 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_6/n"); 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 driver/n"); 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), %s/n", 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 %d/n", 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
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮