使用内核LED框架搭建驱动 ——led_classdev_register
#include <linux/init.h> // __init __exit #include <linux/module.h> // module_init module_exit #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> #include <asm/io.h> //writel #include <linux/ioport.h> //request_mem_region #include <asm/string.h> #include <linux/leds.h> #define GPJ0_REGBASE 0xE0200240 typedef struct GPJ0REG { volatile unsigned int gpj0Con; volatile unsigned int gpj0Dat; }gpj0_reg_t; gpj0_reg_t *pgpj0_reg =NULL; struct led_classdev cdev1; struct led_classdev cdev2; struct led_classdev cdev3; void s5pv210_led1_set(struct led_classdev *led_cdev,enum led_brightness brightness); void s5pv210_led2_set(struct led_classdev *led_cdev,enum led_brightness brightness); void s5pv210_led3_set(struct led_classdev *led_cdev,enum led_brightness brightness); void s5pv210_led1_set(struct led_classdev *led_cdev,enum led_brightness brightness) { printk(KERN_INFO "s5pv210_led1_set successful %d\n",brightness); if(brightness == LED_OFF) { writel((readl(&pgpj0_reg->gpj0Con)&0xff0fffff),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Con)|0x00100000),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<5)),&pgpj0_reg->gpj0Dat); } else { writel((readl(&pgpj0_reg->gpj0Con)&0xff0fffff),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Con)|0x00100000),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<5))),&pgpj0_reg->gpj0Dat); } } void s5pv210_led2_set(struct led_classdev *led_cdev,enum led_brightness brightness) { printk(KERN_INFO "s5pv210_led2_set successful %d\n",brightness); if(brightness == LED_OFF) { writel((readl(&pgpj0_reg->gpj0Con)&0xfff0ffff),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Con)|0x00010000),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<4)),&pgpj0_reg->gpj0Dat); } else { writel((readl(&pgpj0_reg->gpj0Con)&0xfff0ffff),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Con)|0x00010000),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<4))),&pgpj0_reg->gpj0Dat); } } void s5pv210_led3_set(struct led_classdev *led_cdev,enum led_brightness brightness) { printk(KERN_INFO "s5pv210_led3_set successful %d\n",brightness); if(brightness == LED_OFF) { writel((readl(&pgpj0_reg->gpj0Con)&0xffff0fff),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Con)|0x00001000),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Dat)|(0x01<<3)),&pgpj0_reg->gpj0Dat); } else { writel((readl(&pgpj0_reg->gpj0Con)&0xffff0fff),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Con)|0x00001000),&pgpj0_reg->gpj0Con); writel((readl(&pgpj0_reg->gpj0Dat)&(~(0x01<<3))),&pgpj0_reg->gpj0Dat); } } static int __init s5pv210_led_init(void) { int ret = -1; printk(KERN_INFO "s5pv210_led_init successful \n"); cdev1.brightness_set = s5pv210_led1_set; cdev1.name = "led1"; ret = led_classdev_register(NULL, &cdev1); if (ret < 0) { printk(KERN_WARNING "led_classdev_register fail \n"); goto reg_err1; } cdev2.brightness_set = s5pv210_led2_set; cdev2.name = "led2"; ret = led_classdev_register(NULL, &cdev2); if (ret < 0) { printk(KERN_WARNING "led_classdev_register fail \n"); goto reg_err2; } cdev3.brightness_set = s5pv210_led3_set; cdev3.name = "led3"; ret = led_classdev_register(NULL, &cdev3); if (ret < 0) { printk(KERN_WARNING "led_classdev_register fail \n"); goto reg_err3; } if (!request_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t), "gpj0_reg")) { ret = -EBUSY; goto mem_err; } pgpj0_reg = ioremap(GPJ0_REGBASE, sizeof(gpj0_reg_t)); return 0; mem_err: led_classdev_unregister(&cdev3); reg_err3: led_classdev_unregister(&cdev2); reg_err2: led_classdev_unregister(&cdev1); reg_err1: return ret; } static void __exit s5pv210_led_exit(void) { printk(KERN_INFO "s5pv210_led_exit successful \n"); iounmap(pgpj0_reg); release_mem_region(GPJ0_REGBASE,sizeof(gpj0_reg_t)); led_classdev_unregister(&cdev1); led_classdev_unregister(&cdev2); led_classdev_unregister(&cdev3); } module_init(s5pv210_led_init); module_exit(s5pv210_led_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("musk"); // 描述模块的作者 MODULE_DESCRIPTION("x210 LED driver"); // 描述模块的介绍信息 MODULE_ALIAS("led_driver"); // 描述模块的别名信息
一. 内核LED框架介绍:
1.1. 在内核中相关文件
1.1.1. 驱动框架规定的LED这种硬件的驱动框架在:drivers/leds目录下
1.1.2. led-class.c和led-core.c,这两个文件是内核提供的,他们统一描述了内核中所有厂家的不同LED硬件的相同部分的逻辑。
1.1.3. leds-xxxx.c,是由不同厂商的驱动工程师编写添加的,厂商驱动工程师结合自己公司的硬件的不同情况来对LED进行操作,使用内核提供的接口来和驱动框架进行交互,最终实现驱动的功能。
1.2. 使用LED框架和之前使用写的LED驱动区别(register_chrdev)
1.2.1. LED框架中相关最终去创建一个属于/sys/class/leds这个类的一个设备。如何在这个类下有brightness max_brightness power subsystem uevent等文件来操作硬件
1.2.2. 之前写的LED驱动通过file_operations结构体绑定相关函数来操作硬件
1.2.3. 这两中方式是并列的。驱动开发者可以选择其中任意一种方式来开发驱动。
二. 分析led-class.c文件
2.1. subsys_initcall & module_init函数
2.1.1. subsys_initcall是一个宏,定义在linux/init.h中。经过对这个宏进行展开,发现这个宏的功能是:将其声明的函数放到一个特定的段:.initcall4.init。
subsys_initcall
__define_initcall("4",fn,4)
2.1.2. 分析module_init宏,可以看出它将函数放到了.initcall6.init段中。
module_init
__initcall
device_initcall
__define_initcall("6",fn,6)
2.1.3. 内核在启动过程中需要顺序的做很多事,内核如何实现按照先后顺序去做很多初始化操作。内核的解决方案就是给内核启动时要调用的所有函数归类,然后每个类按照一定的次序去调用执行。这些分类名就叫.initcalln.init。n的值从1到8。内核开发者在编写内核代码时只要将函数设置合适的级别,这些函数就会被链接的时候放入特定的段,内核启动时再按照段顺序去依次执行各个段即可。
2.2. 经过分析,可以看出,subsys_initcall和module_init的作用是一样的,只不过前者所声明的函数要比后者在内核启动时的执行顺序更早。
2.3. led_class_attrs数组
2.3.1. 什么是attribute,对应将来/sys/class/leds/目录里的内容,一般是文件和文件夹。这些文件其实就是sysfs开放给应用层的一些操作接口(非常类似于/dev/目录下的那些设备文件)
2.3.2. attribute有什么用,作用就是让应用程序可以通过/sys/class/leds/目录下面的属性文件来操作驱动进而操作硬件设备。
2.3.3. attribute其实是另一条驱动实现的路线。有区别于之前讲的file_operations那条线。
2.4. leds_init函数
2.4.1. 函数内部调用
leds_init
class_create
2.4.1. 此函数在/sys/class目录下创建leds类文件
static int __init leds_init(void) { leds_class = class_create(THIS_MODULE, "leds"); if (IS_ERR(leds_class)) return PTR_ERR(leds_class); leds_class->suspend = led_suspend; leds_class->resume = led_resume; leds_class->dev_attrs = led_class_attrs; return 0; }
2.5. led_classdev结构体
2.5.1. LED驱动框架中最终是通过该结构体与硬件关联起来
struct led_classdev { const char *name; int brightness; int max_brightness; int flags; /* Lower 16 bits reflect status */ #define LED_SUSPENDED (1 << 0) /* Upper 16 bits reflect control information */ #define LED_CORE_SUSPENDRESUME (1 << 16) /* Set LED brightness level */ /* Must not sleep, use a workqueue if needed */ void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); /* Get LED brightness level */ enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); /* Activate hardware accelerated blink, delays are in * miliseconds and if none is provided then a sensible default * should be chosen. The call can adjust the timings if it can't * match the values specified exactly. */ int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off); struct device *dev; struct list_head node; /* LED Device list */ const char *default_trigger; /* Trigger to use */ #ifdef CONFIG_LEDS_TRIGGERS /* Protects the trigger data below */ struct rw_semaphore trigger_lock; struct led_trigger *trigger; struct list_head trig_list; void *trigger_data; #endif };
三.相关代码分析led_classdev_register
3.1. 函数内部关系
led_classdev_register
device_create
3.2. led_classdev_register函数分析
3.2.1. led_classdev_register这个函数其实就是去创建一个属于leds这个类的一个设备。其实就是去注册一个设备。所以这个函数其实就是led驱动框架中内核开发者提供给SoC厂家驱动开发者的一个注册驱动的接口。
3.2.2. 当我们使用led驱动框架去编写驱动的时候,这个led_classdev_register函数的作用类似于我们之前使用file_operations方式去注册字符设备驱动时的register_chrdev函数
/** * led_classdev_register - register a new object of led_classdev class. * @parent: The device to register. * @led_cdev: the led_classdev structure for this device. */ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) { led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name); if (IS_ERR(led_cdev->dev)) return PTR_ERR(led_cdev->dev); #ifdef CONFIG_LEDS_TRIGGERS init_rwsem(&led_cdev->trigger_lock); #endif /* add to the list of leds */ down_write(&leds_list_lock); list_add_tail(&led_cdev->node, &leds_list); up_write(&leds_list_lock); if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; led_update_brightness(led_cdev); #ifdef CONFIG_LEDS_TRIGGERS led_trigger_set_default(led_cdev); #endif printk(KERN_DEBUG "Registered led device: %s\n", led_cdev->name); return 0; }
3.3. led_classdev_unregister函数分析
/** * led_classdev_unregister - unregisters a object of led_properties class. * @led_cdev: the led device to unregister * * Unregisters a previously registered via led_classdev_register object. */ void led_classdev_unregister(struct led_classdev *led_cdev) { #ifdef CONFIG_LEDS_TRIGGERS down_write(&led_cdev->trigger_lock); if (led_cdev->trigger) led_trigger_set(led_cdev, NULL); up_write(&led_cdev->trigger_lock); #endif device_unregister(led_cdev->dev); down_write(&leds_list_lock); list_del(&led_cdev->node); up_write(&leds_list_lock); }
四. 实验效果
[root@musk210 driver_test]# cd /sys/class/leds/led1/ [root@musk210 led1]# ls brightness max_brightness power subsystem uevent [root@musk210 led1]# echo 1 > brightness [ 3193.332848] s5pv210_led1_set successful 1 [root@musk210 led1]# ls brightness max_brightness power subsystem uevent [root@musk210 led1]# echo 0 > brightness [ 6081.310443] s5pv210_led1_set successful 0 [root@musk210 led1]# cd ../ [root@musk210 leds]# ls led1 led2 led3 mmc0:: mmc1:: mmc2:: mmc3:: [root@musk210 leds]#
4.1. 我们写的驱动确实工作了,被加载了,/sys/class/leds/目录下确实多出来了一个表示设备的文件夹。文件夹里面有相应的操控led硬件的2个属性brightness和max_brightness
4.2. led-class.c中brightness方法有一个show方法和store方法,这两个方法对应用户在/sys/class/leds/led1/brightness目录下直接去读写这个文件时实际执行的代码。
4.2.1. 当我们show brightness时,实际就会执行led_brightness_show函数
4.2.1. 当我们echo 1 > brightness时,实际就会执行led_brightness_store函数
4.3. show方法实际要做的就是读取LED硬件信息,然后把硬件信息返回给我们即可。所以show方法和store方法必要要会去操控硬件。但是led-class.c文件又属于驱动框架中的文件,它本身无法直接读取具体硬件,因此在show和store方法中使用函数指针的方式调用了struct led_classdev结构体中的相应的读取/写入硬件信息的方法。
参考《朱老师.课件_5.4.驱动框架入门之LED》