fl2440內核linux-3.0移植-----添加led驅動

一、開發環境
    內核版本:linux-3.0
    開發板:FL2440(nandflash:K9F1G08 128m)
    編譯器:arm-linux-gcc 4.3.2
 
二、原理分析
    1.  硬件原理圖分析。由原理圖得知LED電路是共陽極的,LED0、LED1、LED2、LED3分別由2440的GPB5、GPB6、GPB8、GPB10口控制的,當GPB5、GPB6、GPB8、GPB10輸出低電平時,相應的LED便發亮,反之,LED則熄滅。


三、配置內核

make menuconfig來配置內核,這裏要加上對LED模塊的內容,即:

Device Drivers--->

       [*]LED Support--->

              <*>LED Class Support

              <*>LED Support for Samsung S3C24xx GPIO LEDs

編譯內核,並把編譯好的內核下載到開發板上,運行:

四、寫驅動和測試程序: 

編寫適合fl2440開發板的LED驅動,文件名稱:s3c_led.c(代碼在後面),編譯生成s3c_led.ko文件,並下載到開發板/目錄下面。

編寫適合自己驅動的LED測試程序,文件名稱:led-test.c(代碼在後面),並有gcc工具編譯生成可執行文件,並下載到開發板/usr/sbin目錄下。

五、加載和測試led驅動

>: ls
apps            etc             led_test        plat_button.ko  s3c_hello.ko    tmp
bin             info            lib             plat_led.ko     s3c_led.ko      usr
data            init            linuxrc         proc            sbin            var
dev             jbs.mp3         mnt             root            sys             yw.mp3
>: insmod s3c_led.ko 
>: cat proc/devices

136 pts
180 usb
188 ttyUSB
189 usb_device
204 ttyS
252 button
253 led(可見主設備號爲253)
254 rtc

因爲板子上有4個led燈,所以要依次創建設備節點;

>:mknod dev/led0 c 253 0

>:mknod dev/led1 c 253 1

>:mknod dev/led2 c 253 2

>:mknod dev/led3 c 253 3

>: ls dev/led*
dev/led0  dev/led1  dev/led2  dev/led3

已經加載好了下面運行測試程序:

>: led_test --help
Usage:led_test  on|off 0|1|2|3
>: led_test on 0
>: led_test on 1
>: led_test on 2
>: led_test on 3
>: led_test  off 0
>: led_test  off 1
>: led_test  off 2
>: led_test  off 3

>:rmmod  s3c_led(卸載驅動)

可以看到板子上的四個led依次點亮又依次熄滅,當然這裏編寫的測試程序程序不同,現象也不同。

到這裏led移植就算成功,一下是我借鑑別人的驅動程序和我的測試代碼:

/***************************s3c_led.c***********************************************/


#include <linux/module.h>     /*  Every Linux kernel module must include this head */
#include <linux/init.h>     /*  Every Linux kernel module must include this head */
#include <linux/kernel.h>   /*  printk() */
#include <linux/fs.h>       /*  struct fops */
#include <linux/errno.h>    /*  error codes */
#include <linux/cdev.h>     /*  cdev_alloc()  */
#include <asm/io.h>         /*  ioremap()  */
#include <linux/ioport.h>   /*  request_mem_region() */
#include <linux/slab.h>

#include <asm/ioctl.h>      /*  Linux kernel space head file for macro _IO() to generate ioctl command  */
#ifndef __KERNEL__
#include <sys/ioctl.h>      /*  User space head file for macro _IO() to generate ioctl command */
#endif

#define DEV_AUTHOR          "liuchengdeng<1037398771@qq.com>"
#define DEV_DESC            "fl2440 led driver"
#define DEV_NAME            "led"
#define DEV_NUM             4
#define NUM                 4
#define ERR                 -1

#define S3C_GPB_BASE        0x56000010  // 定義led的控制寄存器的基地址和偏移
#define S3C_GPB_LEN         0x10 
#define GPBCON_OFFSET       0
#define GPBDAT_OFFSET       4
#define GPBUP_OFFSET        8

#define PLATDRV_MAGIC       0x1C   

#define LED_OFF             _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON              _IO (PLATDRV_MAGIC, 0x19)

 /*定義一個魔數。魔數有着特殊的功能。我們定義了led_on和led_off,但是這個宏定義可能和系統的別的重複,因此我們採用魔數機制,定義一個系統未用的魔數,然後讓魔數生成我們定義的led_on和led_off,這樣,我們的定義就不會和系統的相同了。*/

#ifndef LED_MAJOR
#define LED_MAJOR           0      

#endif

//定義默認的主設備號爲0,一般這個定義的設備號是固定不可用的,但是這爲自動分配主設備號的邏輯提供了方便。
int    led_num = 4;                        //定義設備個數
static int    LED[NUM] = {5,6,8,10};        //定義設備在datasheet的位置
int    led_major = LED_MAJOR;
int    led_minor = 0;

static volatile unsigned long  gpb_con, gpb_dat;
#define s3c_gpio_write(value, reg) __raw_writel((value), (reg)+fl2440_gpb_membase)   
#define s3c_gpio_read(reg)       __raw_readl((reg)+fl2440_gpb_membase)

/*定義宏,這裏用到了__raw_writel和__raw_readl兩個宏,這裏對這兩個宏的功能不做詳細介紹,簡單的講就是讀操作和寫操作寄存器*/

static volatile unsigned long   gpb_con, gpb_dat;
static void __iomem             *fl2440_gpb_membase;
static struct cdev              *led_cdev;

static int fl2440_hw_led_init(void)
{
    if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN,DEV_NAME)) /* 申請內存。注意:這裏的內存是FL2440中實際的物理內存,他對應了與LED的相關的寄存器*/
    {
        return ERR;
    }

    if( !(fl2440_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )

/* 建立物理內存到虛擬內存的映射。注意:在內核啓動之後,所有對內存的操作都是虛擬的,如果要操作實際的物理內存,那麼就要使用ioremap建立映射關係*/
    {
       release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);/*申請內存有可能失敗,如別的程序佔用此資源等原因。這時候就必須要停止申請*/
      return ERR;
    }

    gpb_con = s3c_gpio_read(GPBCON_OFFSET);
    gpb_con &= ~((3<<20)|(3<<16)|(3<<12)|(3<<10));   /*  清零相關位*/
    gpb_con |= ((1<<20)|(1<<16)|(1<<12)|(1<<10)); /*  設置爲OUTPUT模式*/
    s3c_gpio_write(gpb_con, GPBCON_OFFSET);

    gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
    gpb_dat |= ((1<<10)|(1<<8)|(1<<6)|(1<<5));  /* default: This port set to low level, then turn LED on */
    s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);

    return 0;
}

static void fl2440_hw_exit(void)
{
    {
        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
        gpb_dat |= ((1<<10)|(1<<8)|(1<<6)|(1<<5));  /*turn off the all led */
        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
    }

    release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);/* 程序退出的時候,要釋放申請的內存 */
    iounmap(fl2440_gpb_membase);     /* 解除映射關係*/
}

static int led_open(struct inode *inode, struct file *file)          //如果在應用空間調用open函數,就會調用led_open,打開相應的設備節點
{
    int minor = iminor(inode); /* iminor函數能夠將設備節點的次設備號獲取出來,根據次設備號,就可以操作該設備*/
    file->private_data =(void *)minor;
    printk("/dev/led%d opened.\n",minor);
    return 0;
}
static int led_release(struct inode *inode,struct file *file)   //如果在應用程序空間調用close函數,就會調用led_release,關閉節點
{
    printk("/dev/led%d closed.\n",iminor(inode));
    return 0;
}
static void led_on(int which_led)
{
    gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
    gpb_dat &= ~(0x01<<LED[which_led]);  /*turn on the all led */
    s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
static void led_off(int which_led)
{
    gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
    gpb_dat |= (0x01<<LED[which_led]);  /*turn off the all led */
    s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}

 

static long led_ioctl(struct file *file,unsigned int cmd, unsigned long args)
{
    int which = (int)file->private_data;

    switch(cmd)
    {
        case LED_ON:
            led_on(which);
            break;
        case LED_OFF:
            led_off(which);
             break;
        default:
             printk("%s is not support ioctl command=%d!\n", DEV_NAME, cmd);
             break;
    }
    return 0;
}
static struct file_operations led_fops =
{
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .unlocked_ioctl = led_ioctl,
};

static int fl2440_sw_led_init(void)
{
   int result;

   dev_t dev_m;

   if(led_major != 0)
   {//典型的手動分配主設備號
       dev_m = MKDEV(led_major,0); /* 將主設備號和次設備號轉換成dev_t類型 */
       result = register_chrdev_region(dev_m,led_num,DEV_NAME);/*to appoint minor number to a device*/
   }//爲一個字符驅動獲取一個或多個設備編號來使用,dev_m是起始的設備編號,通常爲零,led_num是有多少個設備
   else
   {
       result = alloc_chrdev_region(&dev_m,led_minor,led_num,"led");/* dynamic alloc the device number */
       led_major = MAJOR(dev_m);
   }//動態分配設備編號,第一個參數是設備,第二個是參數是第一次設備號,一般爲零,第三個參數是設備個數,

   if(result < 0)
   {
       printk("led major allocation failure!\n");
       printk("fl2440 %s driver use major number%d.\n", DEV_NAME, led_major);
       return ERR;    //動態分配失敗

   }
   printk("fl2440 %s driver use major number %d.\n", DEV_NAME, led_major);

   if(NULL == (led_cdev=cdev_alloc()) ) /* Register independent character device */
   {
       printk(KERN_ERR "fl2440  can't register device for led!\n");
       unregister_chrdev_region(dev_m,led_num); /*release the dev*/
       return ERR;//分配失敗的時候,要取消分配
   }

   led_cdev->owner = THIS_MODULE;
   cdev_init(led_cdev, &led_fops);     /* 初始化設備*/ 
   if(0!=(result = cdev_add(led_cdev, dev_m, led_num))) 
/* add a character device to the system */
   {
       printk(KERN_INFO "led driver can't reigster cdev! ");
       goto error;
   }
   return 0;

error:    //如果在註冊的過程中出現失敗,那麼程序轉到此處,反向註銷
   cdev_del(led_cdev); /* delete a character device  */
   unregister_chrdev_region(dev_m,led_num);
   return result;
}

 


static int __init fl2440_led_init(void)

/*驅動進來的第一個函數,此函數有module_init指定。在這裏,我喜歡放兩個函數,一個函數是硬件的初始化,包括內存申請,物理地址和虛擬地址的映射,寄存器的初始化等工作;另一個函數是軟件的初始化,包括主次設備號的申請,設備的註冊和添加,設備的初始化等工作。*/
{
   int result;
   printk("led module install in your system!\n");

   result=fl2440_hw_led_init();
   if(result != 0)
   {
       printk("led hardware init failure!\n");
       return ERR;
   }
   printk("led hardware init succeed!\n");

   result=fl2440_sw_led_init();
   if(result != 0)
   {
       printk("led software init failure!\n");
       return ERR;
   }
   printk("led software init succeed!\n");
   return 0;
}

static void __exit fl2440_led_exit(void)  

  /*驅動退出時候執行的函數,由module_exit指定,完成一些掃尾工作,比如釋放cdev佔用的內存,釋放原先申請的設備號等。它像你忠實的僕人,爲你做清潔,不過記得給小費哦。*/
{
        dev_t dev_m = MKDEV(led_major, led_minor);

        fl2440_hw_exit();
        cdev_del(led_cdev);
        unregister_chrdev_region(dev_m,led_num);
        printk("led module removed from your system!\n");
        return ;
}

module_init(fl2440_led_init);
module_exit(fl2440_led_exit);

MODULE_AUTHOR(DEV_AUTHOR);
MODULE_DESCRIPTION(DEV_DESC);
MODULE_LICENSE("GPL");

/********************************end*****************************************/

/***************************led_test.c***********************************************/

#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>

#define LED_CNT        4

#define DEVNAME_LEN    10


#define PLATDRV_MAGIC             0x60
#define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)

 
int main (int argc, char **argv)
{
    int           i, index;
    int           fd[LED_CNT];
    char          dev_name[DEVNAME_LEN]={0,0,0,0};

 for(i=0; i<LED_CNT; i++)
    {
        snprintf(dev_name, sizeof(dev_name), "/dev/led%d", i);
        fd[i] = open(dev_name, O_RDWR, 0);
        if(fd[i] < 0)
            goto err;
    }

 if(argc != 3 || sscanf(argv[2], "%d", &index) != 1 || index < 0|| index > 3)
        {
            printf("Usage:led_test  on|off 0|1|2|3\n");
            exit(1);
        }
 i=index;
 if(strcmp(argv[1], "on") == 0)

{
            ioctl(fd[i], LED_ON,index);
        }
     else if(strcmp(argv[1], "off") == 0)
        {
            ioctl(fd[i], LED_OFF,index);
        }
else
        printf("Usage:led_test  on|off 0|1|2|3\n");

for(i=0; i<LED_CNT; i++)
        {
            close(fd[i]);
        }
 return 0;
err:
    for(i=0; i<LED_CNT; i++)
        {
            if(fd[i] >= 0)
                {
                    close(fd[i]);

               }
        }
    printf("open the dev failed \n");
    return -1;
}

/********************************end*****************************************/




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