在Linux-2.6.32.2下爲ST16C554移植驅動的經歷

一、Linux 驅動的基本理論

    理解linux驅動,最重要的是要區分devicedriver這兩個概念,要搞清devicedriver之間的聯繫。 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)適應串口。可以將其看成是簡單封裝了416c550的芯片。它的操作方法和寄存器用法與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

初始化st16c554platform_device數據結構

 

#define PORT(_base,_irq)                           \

  { //ARM體系結構中將IOMEMORY統一編址,

     //因此這裏使用的是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 width8位,以及設定這四個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, \

       }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章