开发板:FL2440(nandflash:K9F1G08 128m)
编译器:arm-linux-gcc 4.3.2
三、配置内核
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;
}