一、开发环境
linux内核版本: 2.6.22.6
硬件环境:jz2440 V2开发板 ,使用的是S3C2440
ARM9处理器
,LED使用的GPIO引脚为GPF4,GPF5,GPF6,当输出低电平时,LED亮,高电平LED灭
二、目的
采用分离分层的思想编写本驱动程序,主要分为两个部分,与硬件操作相关的led_dev和相对稳定的led_drv;led_dev主要负责分配,设置,注册一个平台设备结构体(platform_device),本程序的目的是想点亮一个led灯,如果控制另外一个led灯,只需要修改此驱动程序即可实现需要的功能;led_drv主要负责分配,设置,注册一个平台驱动结构体(platform_drvier),主要负责注册一个字符设备驱动程序,获取平台设备传来的资源信息
三、驱动程序
驱动程序分为两个部分,分别进行编程
①.
led_dev.c
//
参考文件arch\arm\plat-s3c24xx\devs.c
//由于在新浪博客里尖括号符号显示不了,下面引用头文件的尖括号用“”双引号替代了
#include "linux/platform_device.h"
#include "linux/kernel.h"
#include "linux/module.h"
//该结构体存入了最为重要的设备资源信息,驱动程序通过匹配后可以获取硬件的资源
static struct resource led_resources[] = {
[0] = {
.start
=0x56000050,//GPFCON的起始地址
.end
=0x56000050 +8-1,//GPDCON的结束地址
.flags= IORESOURCE_MEM,//描述的是内存类型的资源信息
},
[1] = {
.start
=6,//点亮GPF6灯
.end
=6,
.flags= IORESOURCE_IRQ,//描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。
},
};
//该函数必须要添加,可以什么都不做
static void led_dev_release(struct device *
dev)
{
}
static struct platform_device led_dev = {
.name = "led_bus",//设备name,通过此来匹配platform_driver
.id= -1,
.dev = {
.release
= led_dev_release,
},
.num_resources =
ARRAY_SIZE(led_resources),
.resource = led_resources,
};
static int led_dev_init(void)
{
return platform_device_register(&led_dev);
}
static void led_drv_exit(void)
{
return platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
②.
led_drv.c
//由于在新浪博客里尖括号符号显示不了,下面引用头文件的尖括号用“”双引号替代了
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/platform_device.h"
#include "linux/fs.h"
#include "asm/uaccess.h"
#include "asm/io.h"
#include "linux/cdev.h"
#define DEV_NAME "led"
static int major=0; //主设备号初始值设为0
static struct cdev led_drv_cdev;
static struct class *led_drv_class;//定义一个类结构体
volatile unsigned long *gpfcon;//定义变量代表GPFCON寄存器
volatile unsigned long *gpfdat;//定义变量代表GPFDAT寄存器
static int pin;//
static int led_drv_open(struct inode *inode, struct file
*file)
{
*gpfcon
&=~((0x3<
*gpfcon
|=((0x1<
return 0;
}
//用于读取LED灯的控制信息
static ssize_t led_drv_write(struct file *file, const char
__user *buf, size_t size, loff_t *ppos)
{
int val;
int err;
err=copy_from_user(&val, buf,1);//从用户空间buf传值到内核空间val
if(err)//如果没有数据
{
printk("copy_from_user error\n");//打印错误信息
}
if(val==1)//若传进来的值为1,点灯
{
*gpfdat
&=~(1<
}
else//否则灭灯
{
*gpfdat |=(1<
}
return 0;
}
//构造一个file_operations结构体
static struct file_operations led_drv_fops={
.owner=THIS_MODULE,
.open =led_drv_open,
.write =led_drv_write,
};
static int led_drv_probe(struct
platform_device *pdev)
{
//printk(" led_drv_probe\n ");
// 注册一个字符设备
dev_t devid;
int result;
struct resource *res;//定义一个源结构体,用于获取平台信息
res = platform_get_resource(pdev, IORESOURCE_MEM,
0);//获取平台资源
gpfcon=ioremap(res->start,res->end-res->start+1);//内存地址映射
gpfdat=gpfcon+1;//这里的加1表示地址值加4
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
pin=res->start;
if(major)//如果主设备号为非0,注册字符设备时就不需要动态分配一个主设备号
{
devid=MKDEV(major,0);
result=register_chrdev_region(devid,1,DEV_NAME);
}
else
{
result=alloc_chrdev_region(&devid, 0,
1,DEV_NAME);
major=MAJOR(devid);
}
if(result<0)
{
printk(" register error \n");
return -EIO;
}
cdev_init(&led_drv_cdev,&led_drv_fops);
cdev_add(&led_drv_cdev,devid,1);
//
创建类和设备节点
led_drv_class=class_create(THIS_MODULE,DEV_NAME);
class_device_create(led_drv_class,NULL,
devid,NULL,DEV_NAME);
return 0;
}
static int led_drv__remove(struct platform_device
*pdev)
{
iounmap(gpfcon);
class_device_destroy(led_drv_class,MKDEV(major,0));
class_destroy(led_drv_class);
cdev_del(&led_drv_cdev);
unregister_chrdev_region(MKDEV(major,0),1);
printk(" led_drv__remove\n ");
return 0;
}
//定义,设置一个platform_driver
结构体
struct platform_driver led_platform_driver = {
.probe= led_drv_probe,
.remove= led_drv__remove,
.driver= {
.name= "led_bus",
.owner= THIS_MODULE,
}
};
static int led_drv_init(void)
{
return
platform_driver_register(&led_platform_driver);
static void
led_drv_exit(void)//出口函数
{
return
platform_driver_unregister(&led_platform_driver);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
③.Makefile文件
KERN_DIR = /work/system/linux-2.6.22.6 //编译驱动程序时,所用到的linux内核所在的目录
all:
make -C $(KERN_DIR) M=`pwd`
modules
clean:
make -C $(KERN_DIR) M=`pwd` modules
clean
rm -rf modules.order
obj-m +=led_dev.o
obj-m +=led_drv.o
四、应用程序(led_drv_dev_test.c)
//由于在新浪博客里尖括号符号显示不了,下面引用头文件的尖括号用“”双引号替代了
#include
"sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "string.h"
#include "unistd.h"
// ./led_drv_dev_test on
点灯
// ./led_dev_dev_test
off 灭灯
void print_usage(char *file)//打印用法
{
printf(" %s \n",file);
}
int main(int argc,char **argv)
{
int fd;
int val;
if(argc !=2)//如果输入的参数的个数不为2,打印用法
{
print_usage(argv[0]);
return -1;
}
fd=open("/dev/led",O_RDWR);
if(fd<0)
{
printf("can't open\n");
return -1;
}
if(strcmp(argv[1],"on")==0)//若第二个参数为on,表示开灯
{
val=1;
}
else if(strcmp(argv[1],"off")==0)//若第二个参数为off,表示关灯
{
val=0;
}
else
{
print_usage(argv[0]);
return -1;
}
write(fd,&val,1);//将值传入内核空间,用于控制LED灯
return 0;
}
五、测试
①编译驱动程序和应用程序
编译驱动程序:输入命令
make
编译应用程序:输入命令
arm-linux-gcc -o led_drv_dev_test
led_drv_dev_test.c,生成二进制可执行文件
②装载驱动程序
输入命令:insmod
led_dev.c
输入命令: insmod
led_drv.c
③执行应用程序
输入命令:./led_drv_dev_test
on 开灯
输入命令 :./led_drv_dev_test
off 灭灯