上一篇介绍了platform misc驱动的写法。因为misc设备是字符设备的一种,也不需要很多的函数来注册,因此作为练手。这一篇写一个led的字符设备驱动。你会发现platform驱动只是在字符设备驱动上又封装了一下而已(个人理解)。
led设备
和上一篇的led misc驱动设备差不多,但是多了一些自己的注释。方便以后学习。makefile文件基本一致,这里就不发了。
//my2416PlatformLedDev2.c
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <linux/gpio.h> //2.6.32.2内核版本要求
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/gpio.h> //2.6.32.2内核版本要求
/* 参考arch/arm/plat-s3c24xx/devs.c */
/*1. 根据芯片手册来获取资源*/
static struct resource led_resource[] = {
[0] = {
.start = S3C2410_GPBCON,//使用2416开发板的GPB1,
.end = S3C2410_GPBUP,//资源的起始地址,结束地址
.flags = IORESOURCE_MEM,//flags可以为IORESOURCE_IO, IORESOURCE_MEM, IORESOURCE_IRQ, IORESOURCE_DMA等如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。
},
//[1] = {
// .start = 5,
// .end = 5,
// .flags = IORESOURCE_IRQ,
//},
};
void led_release(struct device *dev)
{
}
/*1.构建平台设备结构体,将平台资源加入进来,需要注意的是platform_device 实质上是经过处理过的设备,在platform_device结构体中存在一个设备结构体,与之前的设备存在差别的是引入了设备资源。这些设备资源就能实现对设备寄存器,中断等资源的访问。*/
struct platform_device led_device = {
.name = "myplatformled2", /* 设备名,使用名为"myplatformled2"的平台驱动 ,注册后,会在/sys/device/platform目录下创建一个以name命名的目录,并且创建软连接到/sys/bus/platform/device下。*/
.id = -1,/*设备id,一般为-1,如果是-1,表示同样名字的设备只有一个举个简单的例子,name/id是“serial/1”则它的bus_id就是serial.1 如果name/id是“serial/0”则它的bus_id就是serial.0 ,如果它的name/id是“serial/-1”则它的bus_id就是serial。 */
.dev = {//结构体中内嵌的device结构体。
.release = led_release,
},
.num_resources = ARRAY_SIZE(led_resource),/* 设备所使用各类资源数量 */
.resource = led_resource,//定义平台设备的资源
};
/*2。把我们的设备资源挂在到虚拟总线的设备连表中去,
如果没有定义上面的struct platform_device led_device,那么需要下面的init函数*/
int led_dev_init(void)
{
platform_device_register(&led_device); //platform设备的初注册
return 0;
}
/*如果没有定义上面的struct platform_device led_device,那么需要使用platform_device_alloc()函数分配一个platform_device结构体,然后使用platform_device_add_resources函数添加资源,最后使用platform_device_add函数*/
/*
struct platform_device *my_buttons_dev;
static int __init platform_dev_init(void)
{
int ret;
my_buttons_dev = platform_device_alloc("my_buttons", -1);
platform_device_add_resources(my_buttons_dev,key_resource,6);//添加资源
ret = platform_device_add(my_buttons_dev); //platform设备的注册
if(ret)
platform_device_put(my_buttons_dev);
return ret;
}
*/
void led_dev_exit(void)
{
platform_device_unregister(&led_device);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
led驱动
//my2416PlatformLedDriver2.c
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <linux/gpio.h> //2.6.32.2内核版本要求
#include <linux/platform_device.h>
#include <linux/cdev.h>
#define DEVICE_NAME "myleds"
#define DRIVER_NAME "myleds2"//加载驱动之后会在/dev/目录下发现myleds2,应用程序可以使用
#define MYLED_SIZE 0x1000//全局内存最大4K
#define MEM_CLEAR 0x1//清零全局内存
#define MYLED_MAJOR 250 //预设的myled的主设备号
static int myled_major = MYLED_MAJOR;
struct class *myled_class;
static struct device *myledDevice=NULL;
struct myled_dev
{
struct cdev cdev;//cdev结构体
//unsigned char mem[MYLED_SIZE];//全局内存
};
struct myled_dev *myled_devp;//设备结构体指针
#define LED_ON 0 //根据原理图,0点亮led,1熄灭led
#define LED_OFF 1
//定义GPIO管脚
static unsigned long led_table [] =
{
S3C2410_GPB(1), //不能是S3C2410_GPB5; 因为没有这样定义,可以通过#define S3C2410_GPB5 S3C2410_GPB(5)
//S3C2410_GPF(1),
//S3C2410_GPF(2),
//S3C2410_GPF(3),
};
//设置管脚模式
static unsigned int led_cfg_table [] =
{
S3C2410_GPIO_OUTPUT, //随内核版本中定义类型的变化,在arch/arm/mach-sc2410/include/mach/Regs-gpio.h文件中定义
//S3C2410_GPIO_OUTPUT,
//S3C2410_GPIO_OUTPUT,
//S3C2410_GPIO_OUTPUT,
};
static int my2416_leds_ioctl(struct file* filp, unsigned int cmd,unsigned long arg)
{
switch(cmd)
{
case LED_ON:
s3c2410_gpio_setpin(S3C2410_GPB(1), LED_ON);
break;
case LED_OFF:
//s3c2410_gpio_setpin(led_table[arg], !cmd);
s3c2410_gpio_setpin(S3C2410_GPB(1), LED_OFF);
break;
default:
printk("LED control:no cmd\n");
printk("LED control are LED_ON or LED_OFF\n");
return(-EINVAL);
}
return 0;
}
//打开函数
static int my2416_led_open(struct inode *inode, struct file *file)
{
int i;
这里只定义了一个io口GPB1配置GPIO
for (i = 0; i < 1; i++)
{
s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
s3c2410_gpio_setpin(led_table[i], 0);
}
return 0;
}
//dev_fops操作指令集
static struct file_operations my2416Led_fops =
{
.owner =THIS_MODULE,
.open = my2416_led_open,
//.release = s3c24xx_buttons_close,
//.read = s3c24xx_buttons_read,
//.poll = s3c24xx_buttons_poll,
.unlocked_ioctl = my2416_leds_ioctl,//这里必须是unlocked_ioctl而不是ioctl。
};
/*//第三步:混杂设备定义
static struct miscdevice my2416Ledmisc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,//加载驱动之后会在/dev/目录下发现myleds,应用程序可以使用
.fops = &my2416Led_fops,
};
*/
static void Led_setup_cdev(struct myled_dev *dev,int index)
{
int err, devno = MKDEV(myled_major,index);
/*初始化cdev,并将相关的文件操作添加进来*/
cdev_init(&dev->cdev, &my2416Led_fops);
dev->cdev.owner = THIS_MODULE;
//dev->cdev.ops = &my2416Led_fops;
/*注册字符设备*/
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk("Error %d\n", err);
else
printk("have finish add\n");
}
/*3。实现probe函数*/
static int led_probe(struct platform_device *pdev)
{
int result;
printk("led_probe\n");
/*创建一个设备号*/
dev_t devno=MKDEV(myled_major,0);
/*注册一个设备号*/
/*如果定义了主设备号采用静态申请的方式*/
if(myled_major)
{
result=register_chrdev_region(devno,1,DEVICE_NAME);
}
else//动态申请设备号
{
result= alloc_chrdev_region(&devno,0,1,DEVICE_NAME);
myled_major=MAJOR(devno);
}
if(result<0)
{
printk (DEVICE_NAME " can't register\n");
return result;
}
printk("led devno\n");
//动态申请设备结构体内存
myled_devp=kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
if(!myled_devp)//申请失败
{
printk("kmalloc faile\n");
result=-ENOMEM;
goto fail_malloc;
}
printk("kmalloc succeed\n");
/*清除空间*/
memset(myled_devp,0,sizeof(struct myled_dev));
/*创建一个设备*/
Led_setup_cdev(myled_devp,0);
//class_create和device_create函数是为了自动在/dev下创建DRIVER_NAME设备文件。
//创建一个类,这个类存放于sysfs下面
myled_class=class_create(THIS_MODULE,DRIVER_NAME);
if(IS_ERR(myled_class))
{
result = PTR_ERR(myled_class);
printk("class create failed\n");
goto class_create_fail;
}
//在/dev目录下创建相应的设备节点
//加载驱动之后会在/dev/目录下发现myleds2,应用程序可以使用
myledDevice = device_create(myled_class,NULL,devno,NULL,DRIVER_NAME);
if(IS_ERR(myledDevice))
{
result = PTR_ERR(myledDevice);
printk("device_create faile\n");
goto device_create_faile;
}
/*
cdev_init(&(myled_dev.cdev),&my2416Led_fops);
myled_dev.cdev.owner = THIS_MODULE;
ret = cdev_add(&(myled_dev.cdev),devno,1);
if(ret)
{
printk("Add device error\n");
return ret;
}
printk (DEVICE_NAME " Initialized \n");
return 0;
*/
fail_malloc:
unregister_chrdev_region(devno,1);//释放设备号
class_create_fail:
unregister_chrdev_region(MKDEV(myled_major, 0), 1);//释放设备号
device_create_faile:
class_destroy(myled_class);/*注销创建的设备类*/
return result;
}
int led_remove(struct platform_device *dev)
{
/*注销设备*/
device_destroy(myled_class,MKDEV(myled_major, 0));
/*注销创建的设备类*/
class_destroy(myled_class);
/*字符设备注销*/
cdev_del(&myled_devp->cdev);//注销cdev
kfree(myled_devp);/*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(myled_major, 0), 1);//释放设备号
printk(DEVICE_NAME " exit\n");
return 0;
}
/*1。平台驱动定义*/
static struct platform_driver led_driver = {
.probe = led_probe, /* 平台总线下增加一个平台设备时,调用枚举函数 */
.remove = led_remove, /* 平台总线下去掉一个平台设备时,调用remove函数 */
.driver = {
.name = "myplatformled2", /* 能支持名为"myplatformled2"的平台设备 */
.owner = THIS_MODULE,
},
};
/*2。注册,把我们的驱动加入到平台设备驱动连表中去*/
static int led_drv_init(void)
{
int ret;
/*平台驱动注册*/
ret=platform_driver_register(&led_driver);
return ret;
}
static void __exit led_drv_exit(void)
{
/*平台驱动注销*/
platform_driver_unregister(&led_driver);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
说明
具体不多说了,就是驱动这里调用了class_create为该设备创建一个class,再为每个设备调用 class_device_create创建对应的设备,这样就可以在/dev目录下看到自己的设备了。这里是myleds2,而不是myleds。为什么呢?后面再说吧。