在Linux-2.6.32.2下为ST16C554移植驱动的经历
2013-11-11 10:48
369 查看
文章转载于:http://www.linuxidc.com/Linux/2010-10/29379.htm
一、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 drive
4000
r;
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\n", 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_ST16C554
serial8250_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 menucofig
Device 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, \
}
一、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 drive
4000
r;
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\n", 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_ST16C554
serial8250_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 menucofig
Device 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, \
}
相关文章推荐
- Linux-2.6.32.2下为ST16C554移植驱动的经历
- pcDuino的linux移植五GPIO驱动开发
- Linux芯片级移植与底层驱动(基于3.7.4内核) --中断控制器
- Linux 驱动移植之Makefile&amp;Kconfig&amp;defconfig的关系
- ARM11、OK6410_Linux、系统移植 和 驱动加载
- I.mx6s上移植wm8960驱动(基于linux3.0.101版本)
- 基于linux-2.6.38.8内核的SDIO/wifi驱动分析&&android 平台USB wifi驱动移植及使用 SDIOwifi
- USB摄像头驱动的移植 linux
- linux2.6.32.2 mini2440平台移植--移植DM9000网卡驱动
- tiny4412学习(三)之移植linux-4.x驱动(1)支持网卡驱动【转】
- linux3.1.8内核移植到gt2440---CS8900A驱动移植
- S3C2440 Linux驱动移植——按键
- 基于S3C2440的Linux-3.6.6移植——按键驱动
- [Linux驱动开发] ramdisk文件系统制作及移植
- Linux-2.6.32.2内核在mini2440上的移植----LCD驱动移植
- Linux2.6.32移植到MINI2440(4)移植+分析DM9000网卡驱动
- Linux-2.6.32.2内核在mini2440上的移植(十六)---基于中断的按键驱动移植
- Davinci DM6446开发攻略——LINUX GPIO驱动源码移植 分类: DSP 2013-07-22 16:30 381人阅读 评论(0) 收藏
- Linux-2.6 16c550 串口驱动移植
- Linux2.6内核驱动移植参考