首页 / 操作系统 / Linux / 在Linux-2.6.32.2下为ST16C554移植驱动的经历
一、Linux 驱动的基本理论 理解linux驱动,最重要的是要区分device和driver这两个概念,要搞清device和driver之间的联系。 device 描述了某个设备所占用的硬件资源(地址、中断),可以理解为硬件方面描述。而driver则是描述了使用和操作该设备的方法、流程、逻辑,可以理解为软件方面的描述。这二者之间的对应联系是一个设备名。我们来看一下两个结构体的定义:struct platform_device { const char* name; int id; struct device dev; u32 num_resources; struct resource * resource; struct platform_device_id * id_entry; struct pdev_archdata archdata;};在arch/arm/mach-s 3c2440/mach-mini2440.c中初始化struct device { struct device *parent; struct device_private *p; struct kobject kobj; const char *init_name; struct device_type *type; struct semaphore sem; struct bus_type *bus; struct device_driver *driver; void *platform_data; struct dev_pm_info power;#ifdef CONFIG_NUMA int numa_node; #endif u64 *dma_mask; u64 coherent_dma_mask; struct device_dma_parameters *dma_parms; struct list_head dma_pools; struct dma_coherent_mem *dma_mem; struct dev_archdata archdata; dev_t devt; spinlock_t devres_lock; struct list_head devres_head; struct klist_node knode_class; struct class *class; const struct attribute_group **groups; void (*release)(struct device *dev);}; struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; struct platform_device_id *id_table;};在drivers/serial/8250.c中初始化struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; bool suppress_bind_attrs; int (*probe) (struct device *dev); int (*remove) (struct device *dev); void (*shutdown) (struct device *dev); int (*suspend) (struct device *dev, pm_message_t state); int (*resume) (struct device *dev); const struct attribute_group **groups; const struct dev_pm_ops *pm; struct driver_private *p;}; 二、ST16C554 st16c554是一款集成了4路标准异步串行收发器的串口扩展芯片,也就是通常所说的16c550(也同8250)适应串口。可以将其看成是简单封装了4个16c550的芯片。它的操作方法和寄存器用法与8250完全相同,因此我们可以用linux内经典的8250驱动来驱动st16c554。 三、移植过程 硬件平台:mini2440 系统:linux-2.6.32.2 红色部分为添加的语句。1. 修改arch/arm/mach-s3c2440/mach-mini2440.c添加头文件#ifdef CONFIG_SERIAL_8250_MINI2440_ST16C554#include <linux/serial_8250.h>#endif初始化st16c554的platform_device数据结构 #define PORT(_base,_irq) { //ARM体系结构中将IO和MEMORY统一编址, //因此这里使用的是Memory地址 .mapbase = (unsigned long)_base, .irq = _irq, .uartclk = 1843200, .iotype = UPIO_MEM32, .flags = (UPF_BOOT_AUTOCONF | UPF_IOREMAP), .regshift = 0, }static struct plat_serial8250_port mini2440_st16c554_8250_data[] = { PORT(S3C2410_CS1 + 0x0, IRQ_EINT0), PORT(S3C2410_CS2 + 0x0, IRQ_EINT1), PORT(S3C2410_CS3 + 0x0, IRQ_EINT2), PORT(S3C2410_CS5 + 0x0, IRQ_EINT3), { },};static struct platform_device mini2440_device_st16c554 = { .name = "serial8250", .id = PLAT8250_DEV_EXAR_ST16C554, .dev = { .platform_data = &mini2440_st16c554_8250_data, },};将st16c554对应的platform_device数据结构体添加到mini2440对应的platform_device数据结构体中。 static struct platform_device *mini2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_rtc, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &mini2440_device_eth,//#ifdef CONFIG_SERIAL_8250_MINI2440_ST16C554 &mini2440_device_st16c554,//#endif &s3c24xx_uda134x, &s3c_device_nand, &s3c_device_sdi, &s3c_device_usbgadget,};2.修改 drivers/serial/8250.c 添加头文件#ifdef CONFIG_SERIAL_8250_MINI2440_ST16C554#include <mach/regs-mem.h>#endif 修改S3C2440 四个Bank使用的的 bus width为8位,以及设定这四个Bank的总线 timing。static int __init serial8250_init(void){ int ret; if (nr_uarts > UART_NR) nr_uarts = UART_NR; printk(KERN_INFO "Serial: 8250/16550 driver, " "%d ports, IRQ sharing %sabled
", nr_uarts, share_irqs ? "en" : "dis");#ifdef CONFIG_SERIAL_8250_MINI2440_ST16C554 *((volatile unsigned int *)S3C2410_BWSCON) =((*((volatile unsigned int *)S3C2410_BWSCON)) & ~(0x30333<<4)) | S3C2410_BWSCON_DW1_8 | S3C2410_BWSCON_DW2_8 | S3C2410_BWSCON_DW3_8 | S3C2410_BWSCON_DW5_8; // *((volatile unsigned int *)S3C2410_BANKCON1) = 0x1f7c; // *((volatile unsigned int *)S3C2410_BANKCON2) = 0x1f7c; // *((volatile unsigned int *)S3C2410_BANKCON3) = 0x1f7c; // *((volatile unsigned int *)S3C2410_BANKCON5) = 0x1f7c;#endif#ifdef CONFIG_SPARC ret = sunserial_register_minors(&serial8250_reg, UART_NR);#else serial8250_reg.nr = UART_NR; ret = uart_register_driver(&serial8250_reg);#endif if (ret) goto out;#ifdef CONFIG_8250_MINI2440_ST16C554serial8250_isa_devs =platform_device_alloc("serial8250", PLAT8250_DEV_EXAR_ST16C554);#else serial8250_isa_devs = platform_device_alloc("serial8250", PLAT8250_DEV_LEGACY);#endif修改中断信号的类型为下降沿触发 if (i->head) { list_add(&up->list, i->head); spin_unlock_irq(&i->lock); ret = 0; } else { INIT_LIST_HEAD(&up->list); i->head = &up->list; spin_unlock_irq(&i->lock);#ifdef CONFIG_SERIAL_8250_MINI2440_ST16C554 irq_flags |= IRQF_TRIGGER_RISING;#else irq_flags |= up->port.irqflags;#endif ret = request_irq(up->port.irq, serial8250_interrupt, irq_flags, "serial", i); if (ret < 0) serial_do_unlink(i, up); }3.修改 drivers/serial/Kconfig添加一个编译配置选项config SERIAL_8250_MINI2440_ST16C554 bool "Support MINI2440 Extend ST16C554/554D Quad UART" depends on SERIAL_8250 help A chip of ST16C554 is uesed to extend Quad UART on the MINI2440 Board. If you are tinkering with ST16C554, or have a machine with this UART, say Y here.To compile this driver as a module, choose M here: the module will be called 8250_mini2440_st16c554. 4.重新编译内核> make menucofigDevice Drivers à Character Devices à Serial Drivers à <*> 8250/16c550 and compatible serial support [*] Support MINI2440 Extend ST16C554/554D Quad UART保存.config文件> make zImage 这样驱动就添加好了,如果你的根文件系统使用了mdev,那么不用做任何修改,mdev会自动地将四个新串口添加在 /dev/serial/tty目录下面,分别为 ttyS0, ttyS1, ttyS2, ttyS3。查看更详细的信息> cat /proc/tty/driver/serial 将显示四个串口的物理地址和虚拟地址 四.碰到的问题内核启动过程中,报错Unable to handle kernel NULL pointer dereference at virtual address 000000c0产生这个错误有两种可能:(1) 地址指针错误,比如在初始化平台设备结构体时丢掉了&符号。static struct platform_device mini2440_device_st16c554 = { .name = "serial8250", .id = PLAT8250_DEV_EXAR_ST16C554, .dev = { .platform_data = &mini2440_st16c554_8250_data, },};(2) 在为ST16C554的四个端口指定系统地址时,没有使用Memory地址,而使用了IO地址。#define PORT(_base,_irq) { .mapbase = (unsigned long)_base, .irq = _irq, .uartclk = 1843200, .iotype = UPIO_MEM32, .flags = (UPF_BOOT_AUTOCONF | UPF_IOREMAP), .regshift = 0, }在 driver/serial/8250_exar_st16c554.c中使用的是IO地址#define PORT(_base,_irq) { .iobase = _base, .irq = _irq, .uartclk = 1843200, .iotype = UPIO_PORT, .flags = UPF_BOOT_AUTOCONF, }