以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
前言
之前写到,九鼎没有使用内核推荐的LED驱动框架。因此,我们打算基于LED驱动框架来编写x210开发板的LED驱动。编写前先要去除九鼎移植的LED驱动,然后配置内核支持LED驱动框架,最后根据LED驱动框架来编写LED驱动。
一、去除九鼎移植的LED驱动
九鼎移植的驱动在应用层的接口,位于/sys/devices/platform/x210-led/目录下,有led1、led2、led3、led4四个设备文件,各自管理一个led。(比如“echo 1 > led1”可以点亮其中的led1。)
[root@xjh ~]# cd /sys [root@xjh sys]# ls //sys目录内容 block class devices fs module bus dev firmware kernel power [root@xjh sys]# cd devices/ [root@xjh devices]# ls //sys/devices目录内容 platform system virtual [root@xjh devices]# cd platform/ [root@xjh platform]# ls //sys/devices/platform目录内容 alarm s3c-adc s3c-sdhci.2 s5p-cec android_pmem.1 s3c-button s3c-sdhci.3 s5p-ehci arm-pmu.0 s3c-csis s3c-ts s5p-hpd dm9000.0 s3c-fimc.0 s3c2410-wdt s5p-tvout pm-wifi s3c-fimc.1 s3c2440-i2c.0 s5pv210-nand power s3c-fimc.2 s3c2440-i2c.1 s5pv210-uart.0 power.0 s3c-g2d s3c2440-i2c.2 s5pv210-uart.1 pvrsrvkm s3c-jpg s3c24xx-pwm.0 s5pv210-uart.2 reg-s5pv210-pd.0 s3c-keypad s3c24xx-pwm.1 s5pv210-uart.3 reg-s5pv210-pd.1 s3c-mfc s3c24xx-pwm.2 sec-fake-battery reg-s5pv210-pd.2 s3c-pl330.0 s3c24xx-pwm.3 smdkc110-rtc reg-s5pv210-pd.3 s3c-pl330.1 s3c64xx-iis.0 soc-audio.1 reg-s5pv210-pd.4 s3c-pl330.2 s3c64xx-iis.1 switch-gpio reg-s5pv210-pd.5 s3c-sdhci.0 s3c_lcd uevent regulatory.0 s3c-sdhci.1 s3cfb x210-led //这里 [root@xjh platform]# cd x210-led/ [root@xjh x210-led]# ls // sys/devices/platform/x210-led目录内容 driver led2 led4 power uevent led1 led3 modalias subsystem [root@xjh x210-led]#
要去除九鼎移植的LED驱动,需要在内核配置阶段去掉相应的选择项,然后重新编译得到zImage并烧录至开发板。新内核启动后,如果/sys/devices/platform/x210-led目录不存在,则说明成功去掉该驱动。
步骤如下(内核编译初体验_天糊土的博客-CSDN博客)
步骤1:在 /home/xjh/iot/embedded_basic/kernel/x210_kernel 执行make distclean、make x210ii_qt_defconfig、make menuconfig。
步骤2:在弹出的配置窗口中去除九鼎移植的LED驱动选项。
|………Device Drivers --->
|…………Character devices --->
|………………[*] x210 led driver //将 * 换成 n。
步骤3:在配置窗口中选择支持NFS功能的选项。
见以NFS方式挂载rootfs的设置方法_天糊土的博客-CSDN博客。
可以修改配置文件,使得默认设置都是想要的配置,这样就不用再进行步骤2与3了。
比如将drivers/char/led目录下的Kconfig文件中的内容修改成如下(实测好像不行?)
config X210_LED_DRIVERbool "x210 led driver" # default y 将默认值y改为ndefault nhelpcompile for leddriver,y for kernel,m for module.
步骤4:执行make得到zImage,它在arch/arm/boot目录中。
步骤5:将新生成的zImage复制到tftp服务器的目录/tftp,开发板启动时uboot会自动从这里下载内核镜像。
步骤6:测试,/sys/devices/platform/x210-led目录不再存在。
[root@xjh platform]# pwd /sys/devices/platform [root@xjh platform]# ls alarm s3c-adc s3c-sdhci.2 s5p-cec android_pmem.1 s3c-button s3c-sdhci.3 s5p-ehci arm-pmu.0 s3c-csis s3c-ts s5p-hpd dm9000.0 s3c-fimc.0 s3c2410-wdt s5p-tvout pm-wifi s3c-fimc.1 s3c2440-i2c.0 s5pv210-nand power s3c-fimc.2 s3c2440-i2c.1 s5pv210-uart.0 power.0 s3c-g2d s3c2440-i2c.2 s5pv210-uart.1 pvrsrvkm s3c-jpg s3c24xx-pwm.0 s5pv210-uart.2 reg-s5pv210-pd.0 s3c-keypad s3c24xx-pwm.1 s5pv210-uart.3 reg-s5pv210-pd.1 s3c-mfc s3c24xx-pwm.2 sec-fake-battery reg-s5pv210-pd.2 s3c-pl330.0 s3c24xx-pwm.3 smdkc110-rtc reg-s5pv210-pd.3 s3c-pl330.1 s3c64xx-iis.0 soc-audio.1 reg-s5pv210-pd.4 s3c-pl330.2 s3c64xx-iis.1 switch-gpio reg-s5pv210-pd.5 s3c-sdhci.0 s3c_lcd uevent regulatory.0 s3c-sdhci.1 s3cfb //不存在x210-led [root@xjh platform]#
二、添加LED驱动框架支持
当前内核中没有LED驱动框架,表现为/sys/class目录下没有此类,因此要添加LED驱动框架支持,这主要涉及menuconfig的操作。
1、添加LED驱动框架的步骤
在make menuconfig阶段,进行如下配置。
|…………Device Drivers --->
|………………[*] LED Support --->
|…………………… <*> LED Class Support
然后重新编译与下载,在开发板的/sys/class/目录下将出现leds目录。
[root@xjh ~]# cd /sys/class [root@xjh class]# ls backlight i2c-adapter misc regulator scsi_host video4linux bdi i2c-dev mmc_host rfkill sound vtconsole block ieee80211 mtd rtc spi_master firmware input net s3c_bc switch gpio lcd power_supply scsi_device timed_output graphics leds //这里 ppp scsi_disk tty hidraw mem pvr scsi_generic vc [root@xjh class]#
2、sysfs中的内容分析
三、基于驱动框架写LED驱动(具体操作层)
1、代码编写前的分析
(1)可参考的驱动程序在哪里?
drivers/leds/leds-s3c24xx.c文件,drivers/leds/led-class.c文件
(2)代码编写的关键点是什么?
led_classdev_register函数。
注意,在驱动框架核心层drivers/leds/led-class.c文件中,已经注册了一个类leds,会创建/sys/class/leds这个目录。我们现在要利用驱动框架提供的led_classdev_register函数,在/sys/class/leds目录下会创建一个设备。
(经过测试,不会在/dev下创建设备文件,因为这是两条不同的路线。现在的路线是在/sys/class/leds目录下创建一个设备目录,目录里有属性文件,应用层通过操作这些属性文件进而操作硬件。)
2、动手编写代码
这里没有像leds-s3c24xx.c那样使用驱动模型,而是参考其中部分内容,然后自己编写。
#include <linux/module.h> // module_init module_exit #include <linux/init.h> // __init __exit #include <linux/fs.h> #include <linux/leds.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> #include <linux/io.h> #include <linux/ioport.h>#define GPJ0CON S5PV210_GPJ0CON #define GPJ0DAT S5PV210_GPJ0DATstatic struct led_classdev mydev; // 定义结构体变量// 这个函数就是要去完成具体的硬件读写任务的 static void s5pv210_led_set(struct led_classdev *led_cdev,\enum led_brightness value) {printk(KERN_INFO "s5pv210_led_set\n");// 在这里根据用户设置的值来操作硬件// 用户设置的值就是valueif (value == LED_OFF){// 用户给了个0,希望LED灭writel(0x11111111, GPJ0CON);writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT);}else{// 用户给的是非0,希望LED亮writel(0x11111111, GPJ0CON);writel(((0<<3) | (0<<4) | (0<<5)), GPJ0DAT);} }static int __init s5pv210_led_init(void) {// 用户insmod安装驱动模块时会调用该函数// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备int ret = -1;//填充mydevmydev.name = "myled";//设备的名字mydev.brightness = 255; mydev.brightness_set = s5pv210_led_set;ret = led_classdev_register(NULL, &mydev);if (ret < 0) {printk(KERN_ERR "led_classdev_register failed\n");return ret;}return 0; }static void __exit s5pv210_led_exit(void) {led_classdev_unregister(&mydev); }module_init(s5pv210_led_init); module_exit(s5pv210_led_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模块的作者 MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息 MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
3、测试与分析
将上面的驱动程序进行编译,然后对比模块安装前后的情况。
[root@xjh ~]# cd /sys/class [root@xjh class]# ls backlight i2c-adapter misc regulator scsi_host video4linux bdi i2c-dev mmc_host rfkill sound vtconsole block ieee80211 mtd rtc spi_master firmware input net s3c_bc switch gpio lcd power_supply scsi_device timed_output graphics leds ppp scsi_disk tty hidraw mem pvr scsi_generic vc [root@xjh class]# cd leds [root@xjh leds]# ls mmc0:: mmc1:: mmc2:: mmc3:: [root@xjh leds]# insmod /mnt/module_test.ko [root@xjh leds]# ls mmc0:: mmc1:: mmc2:: mmc3:: myled [root@xjh leds]# cd myled/ [root@xjh myled]# ls brightness max_brightness power subsystem uevent [root@xjh myled]#
这测试证明我们写的驱动确实被加载了,表现为/sys/class/leds/目录下出现了一个表示设备的文件夹myled(这个名字是我们写驱动的时候指定的)。
myled文件夹里面有LED硬件的2个属性brightness和max_brightness。
led-class.c文件中,有一个led_brightness_show函数和led_brightness_store函数,分别对应着用户读与写/sys/class/leds/myled/brightness这个文件时实际执行的代码。当我们在命令行执行“cat brightness”时(表示读),实际就会执行led_brightness_show函数;当我们执行“echo 1 > brightness”时(表示写),实际就会执行led_brightness_store函数。
led_brightness_show函数其实就是读取LED硬件信息,然后把硬件信息返回,因此该函数会去操控硬件。但是led-class.c文件属于驱动框架中核心层的文件,它本身无法直接读取具体硬件,因此该函数使用函数指针的方式,指向struct led_classdev结构体中相应的读取硬件信息的函数。而struct led_classdev结构体中实际用来读取硬件信息的函数,就是我们自己写的驱动文件leds-s5pv210.c中要提供的。同理,store函数也是如此。
4、在驱动中将4个LED分开
(1)机制与策略的含义
驱动只应该提供机制(具体实现)而不是策略(方法、主意、解决方案),策略由应用程序来做。
驱动设计时,不要对最终需求功能进行假定(不能假定用户进行什么操作,比如是几个led一起操作还是一个操作而已),而应该只是直接的对硬件的操作。
(2)在驱动中将4个LED分开
驱动层实现对各个LED设备的独立访问,并向应用层展示出4个操作接口led1、led2、led3、led4,这样应用层可以完全按照自己的需要对LED进行控制。
(3)代码示例
#include <linux/module.h> // module_init module_exit #include <linux/init.h> // __init __exit #include <linux/fs.h> #include <linux/leds.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> #include <linux/io.h> #include <linux/ioport.h>#define GPJ0CON S5PV210_GPJ0CON #define GPJ0DAT S5PV210_GPJ0DATstatic struct led_classdev mydev1; // 定义结构体变量 static struct led_classdev mydev2; // 定义结构体变量 static struct led_classdev mydev3; // 定义结构体变量// 这个函数就是要去完成具体的硬件读写任务的 static void s5pv210_led1_set(struct led_classdev *led_cdev, \enum led_brightness value) {printk(KERN_INFO "s5pv210_led1_set\n");writel(0x11111111, GPJ0CON);// 在这里根据用户设置的值来操作硬件// 用户设置的值就是valueif (value == LED_OFF){// 用户给了个0,希望LED灭//writel(0x11111111, GPJ0CON);// 读改写三部曲writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);}else{// 用户给的是非0,希望LED亮//writel(0x11111111, GPJ0CON);writel((readl(GPJ0DAT) & ~(1<<3)), GPJ0DAT);} }static void s5pv210_led2_set(struct led_classdev *led_cdev, \enum led_brightness value) {printk(KERN_INFO "s5pv2102_led_set\n");writel(0x11111111, GPJ0CON);// 在这里根据用户设置的值来操作硬件// 用户设置的值就是valueif (value == LED_OFF){// 用户给了个0,希望LED灭//writel(0x11111111, GPJ0CON);// 读改写三部曲writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT);}else{// 用户给的是非0,希望LED亮//writel(0x11111111, GPJ0CON);writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);} }static void s5pv210_led3_set(struct led_classdev *led_cdev,enum led_brightness value) {printk(KERN_INFO "s5pv210_led3_set\n");writel(0x11111111, GPJ0CON);// 在这里根据用户设置的值来操作硬件// 用户设置的值就是valueif (value == LED_OFF){// 用户给了个0,希望LED灭//writel(0x11111111, GPJ0CON);// 读改写三部曲writel((readl(GPJ0DAT) | (1<<5)), GPJ0DAT);}else{// 用户给的是非0,希望LED亮//writel(0x11111111, GPJ0CON);writel((readl(GPJ0DAT) & ~(1<<5)), GPJ0DAT);} }static int __init s5pv210_led_init(void) {// 用户insmod安装驱动模块时会调用该函数// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备int ret = -1;// led1mydev1.name = "led1";mydev1.brightness = 255; mydev1.brightness_set = s5pv210_led1_set;ret = led_classdev_register(NULL, &mydev1);if (ret < 0) {printk(KERN_ERR "led_classdev_register failed\n");return ret;}// led2mydev2.name = "led2";mydev2.brightness = 255; //这句话就把驱动框架第一部分,和第二部分关联起来了!!!!!mydev2.brightness_set = s5pv210_led2_set;ret = led_classdev_register(NULL, &mydev2);if (ret < 0) {printk(KERN_ERR "led_classdev_register failed\n");return ret;}// led3mydev3.name = "led3";mydev3.brightness = 255; mydev3.brightness_set = s5pv210_led3_set;ret = led_classdev_register(NULL, &mydev3);if (ret < 0) {printk(KERN_ERR "led_classdev_register failed\n");return ret;}return 0; }static void __exit s5pv210_led_exit(void) {led_classdev_unregister(&mydev1);led_classdev_unregister(&mydev2);led_classdev_unregister(&mydev3); }module_init(s5pv210_led_init); module_exit(s5pv210_led_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模块的作者 MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息 MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息