四.驱动框架入门之LED

5.4.1.何谓驱动框架
5.4.1.1、驱动是谁写的
(1)驱动开发工程师
(2)内核维护者
//cope_to_user 等被写
5.4.1.2、驱动编程协作要求
(1)接口标准化
(2)尽量降低驱动开发者难度
5.4.1.3、到底什么是驱动框架
(1)驱动框架:内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,然后把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程师来实现,这就叫驱动框架。
//通用的、专用的;例如LED驱动实现,内核实现了哪一部分,实现的这一部分留出了哪些接口给我们驱动开发用,这些接口涉及哪些数据结构(这些接口和数据结构需要学),我们利用这些接口来写驱动代码。
(2)内核维护者在内核中设计了一些统一管控系统资源的体系,这些体系让内核能够对资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。譬如系统中所有的GPIO就属于系统资源,每个驱动模块如果要使用某个GPIO就要先调用特殊的接口先申请,申请到后使用,使用完后要释放。
又譬如中断号也是一种资源,驱动在使用前也必须去申请。这也是驱动框架的组成部分。
(3)学习一些特定的接口函数、一些特定的数据结构,这些是驱动框架的直接表现。
//纸上得来终觉浅,绝知此事要躬行!

5.4.2.内核驱动框架中LED的基本情况
5.4.2.1、相关文件
//三星拿了官方标准的linux-2.6.3内核,针对210 CPU 针对smdk v210芯片,对开发板进行了移植;九鼎科技再拿过来针对X210进行移植。
(1)drivers/leds目录,这个目录就是驱动框架规定的LED这种硬件的驱动应该待的地方。
(2)led-class.c和led-core.c,这两个文件加起来属于LED驱动框架的第一部分,这两个文件是内核开发者提供的,他们描述的是内核中所有厂家的不同LED硬件的相同部分的逻辑。
(3)leds-xxxx.c,这个文件是LED驱动框架的第2部分,是由不同厂商的驱动工程师编写添加的,厂商驱动工程师结合自己公司的硬件的不同情况来对LED进行操作,从而进行移植进来到该目录,使用第一部分提供的接口来和驱动框架进行交互,最终实现驱动的功能。
//
[root@localhost linux-3.5]# cd drivers/leds/ //
built-in.o leds-bd2802.c leds-locomo.c leds-pwm.c led-triggers.o
.built-in.o.cmd leds-clevo-mail.c leds-lp3944.c leds-rb532.c .led-triggers.o.cmd
dell-led.c leds-cobalt-qube.c leds-lp5521.c leds-regulator.c ledtrig-gpio.c
Kconfig leds-cobalt-raq.c leds-lp5523.c leds-renesas-tpu.c ledtrig-heartbeat.c
led-class.c leds-da903x.c leds-lt3593.c leds-s3c24xx.c ledtrig-ide-disk.c
led-class.o leds-da9052.c leds-max8997.c leds-ss4200.c ledtrig-sleep.c
.led-class.o.cmd leds-dac124s085.c leds-mc13783.c leds-sunfire.c ledtrig-timer.c
led-core.c leds-fsg.c leds-net48xx.c leds-tca6507.c ledtrig-transient.c
led-core.o leds-gpio.c leds-netxbig.c leds-wm831x-status.c Makefile
.led-core.o.cmd leds-gpio-register.c leds-ns2.c leds-wm8350.c modules.builtin
leds-88pm860x.c leds.h leds-ot200.c leds-wrap.c modules.order
leds-adp5520.c leds-hp6xx.c leds-pca9532.c ledtrig-backlight.c
leds-asic3.c leds-lm3530.c leds-pca955x.c ledtrig-default-on.c
leds-atmel-pwm.c leds-lm3533.c leds-pca9633.c led-triggers.c
[root@localhost linux-3.5]# cd drivers/leds/led-class.c^C
[root@localhost linux-3.5]# cd drivers/leds/led-core.c^C
[root@localhost linux-3.5]# cd drivers/char/
//[root@localhost linux-3.5]# vim drivers/leds/leds-s3c24xx.c //和我们的相近
//三星根据s3c24xx这个芯片写的驱动程序,其他厂家都是结合自己的芯片根据需要在该内核驱动框架下编写实现相应其驱动程序


5.4.2.2、九鼎移植的内核中led驱动
(1)九鼎实际未使用内核推荐的led驱动框架
//内核驱动框架可以使用(使用的话:更容易被人理解,比较符合linux内核使用习惯,容易在不同版本间取得兼容性,做法比较标准和典型);
// 也可以不使用,自己开辟一条线路来实现,(不够典型;例如九鼎这个,可能是和工程师的习惯有关)不走寻常路。
(2)drivers/char/led/x210-led.c
//[root@localhost linux-3.5]# ls drivers/char/led
//ls: 无法访问drivers/char/led: 没有那个文件或目录

5.4.2.3、案例分析驱动框架的使用
(1)以leds-s3c24xx.c为例。leds-s3c24xx.c中通过调用led_classdev_register来完成我们的LED驱动的注册,
而led_classdev_register是在drivers/leds/led-class.c中定义的。所以其实SoC厂商的驱动工程师是调用内核开发者在驱动框架中提供的接口来实现自己的驱动的。
(2)驱动框架的关键点就是:分清楚内核开发者提供了什么,驱动开发者自己要提供什么
5.4.2.4、典型的驱动开发行业现状:
(1)内核开发者对驱动框架进行开发和维护、升级,对应led-class.c和led-core.c
(2)SoC厂商的驱动工程师对设备驱动源码进行编写、调试,提供参考版本,例如三星芯片:对应leds-s3c24xx.c
//注意:他们是针对自己的开发板移植、调试哦! 我们使用他们的芯片和他们开发板,但是会将该开发板修改以达到我们的板子要求(比如有些芯片不用要舍弃,用的要添加)。
//eg:公司是做电容式触摸屏的则需要对硬件等了解,操作其寄存器,难的是芯片本身的东西,开发出相应驱动程序后需要对买家提供你们公司的这个芯片的驱动程序,//纯做驱动的少;狭隘、孤岛(每种设备驱动框架不同,换了设备需重新研究内核驱动框架。例如触摸屏换成wifi等),专,工作机会少,工资高;
(3)做产品的厂商的驱动工程师以SoC厂商提供的驱动源码为基础,来做移植和调试
//eg:比如平板电脑产品厂商,用三星的芯片,利用他们的demo板子,得做一些修改,叫移植;调试来达到最优化(只是基本功能,还须各项功能最优化)!产品公司一般不会从头写代码,无需对硬件熟悉,拿过来驱动做移植和调试优化来让自己产品能工作即可。
//这个社会是团结协作的!


5.4.3_4.初步分析led驱动框架源码1_2
5.4.3.1、涉及到的文件
(1)led-core.c
(2)led-class.c
//
216 static int __init leds_init(void)
217 {
218 leds_class = class_create(THIS_MODULE, "leds");//1、创建类
219 if (IS_ERR(leds_class))
220 return PTR_ERR(leds_class);
221 leds_class->suspend = led_suspend;
222 leds_class->resume = led_resume;
223 leds_class->dev_attrs = led_class_attrs;//
224 return 0;
225 }
227 static void __exit leds_exit(void)
228 {
229 class_destroy(leds_class);
230 }
//[root@localhost linux-3.5]# grep "subsys_initcall" * -nR //在特定目录里查找某个文件的 字段

5.4.3.2、subsys_initcall
(1)经过基本分析,发现LED驱动框架中内核开发者实现的部分主要是led-class.c
(2)我们发现led-class.c就是一个内核模块,对led-class.c分析应该从下往上,遵从对模块的基本分析方法。
(3)为什么LED驱动框架中内核开发者实现的部分要实现成一个模块?因为内核开发者希望这个驱动框架是可以被装载/卸载的。这样当我们内核使用者不需要这个驱动框架时可以完全去掉,需要时可以随时加上。
(4)subsys_initcall是一个宏,定义在linux/init.h中。经过对这个宏进行展开,发现这个宏的功能是:将其声明的函数放到一个特定的段:.initcall4.init。
subsys_initcall
__define_initcall("4",fn,4)
(5)分析module_init宏,可以看出它将函数放到了.initcall6.init段中。
module_init
__initcall
device_initcall
__define_initcall("6",fn,6)
(6)内核在启动过程中需要顺序的做很多事,内核如何实现按照先后顺序去做很多初始化操作。内核的解决方案就是给内核启动时要调用的所有函数归类,然后每个类按照一定的次序去调用执行。这些分类名就叫.initcalln.init。n的值从1到8。内核开发者在编写内核代码时只要将函数设置合适的级别,这些函数就会被链接的时候放入特定的段,内核启动时再按照段顺序去依次执行各个段即可。
(7)经过分析,可以看出,subsys_initcall和module_init的作用是一样的,只不过前者所声明的函数要比后者在内核启动时的执行顺序更早。
//[root@localhost linux-3.5]# vim arch/arm/kernel/vmlinux.lds
527 .init.data : {
528 *(.init.data) *(.meminit.data) *(.init.rodata) . = ALIGN(8); __start_ftrace_events = .; *(_ftrace_events) _ _stop_ftrace_events = .; *(.meminit.rodata) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = . ;
529 . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
530 __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.in itcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initca ll4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_ start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.init call6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
531 __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
532 __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
533 . = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)
534 }


5.4.3.3、led_class_attrs
(1)什么是attribute,对应将来/sys/class/leds/目录里的内容,一般是文件和文件夹。这些文件其实就是sysfs开放给应用层的一些操作接口(非常类似于/dev/目录下的那些设备文件)
(2)attribute有什么用,作用就是让应用程序可以通过/sys/class/leds/目录下面的属性文件来操作驱动进而操作硬件设备。
(3)attribute其实是另一条驱动实现的路线。有区别于之前讲的file_operations那条线。//多种方式实现!!!
//
69 static struct device_attribute led_class_attrs[] = { //利用一下这些宏来生成/sys/class/leds/目录等
70 __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),//写方法
71 __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),//读方法
72 #ifdef CONFIG_LEDS_TRIGGERS
73 __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
74 #endif
75 __ATTR_NULL,
76 };

5.4.3.4、led_classdev_register
led_classdev_register
device_create
(1)分析可知,led_classdev_register这个函数其实就是去创建一个属于leds这个类的一个设备。其实就是去注册一个设备。所以这个函数其实就是led驱动框架中内核开发者提供给SoC厂家驱动开发者的一个注册驱动的接口。
(2)当我们使用led驱动框架去编写驱动的时候,这个led_classdev_register函数的作用类似于我们之前使用file_operations方式去注册字符设备驱动时的register_chrdev函数。
//
150 /**
151 * led_classdev_register - register a new object of led_classdev class.
152 * @parent: The device to register.
153 * @led_cdev: the led_classdev structure for this device.
154 */
155 int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)//2、创建属于该类的一个设备
156 {
157 led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
158 "%s", led_cdev->name);
159 if (IS_ERR(led_cdev->dev))
160 return PTR_ERR(led_cdev->dev);

 

5.4.5.在内核中添加或去除某个驱动
5.4.5.1、去除九鼎移植的LED驱动
(1)九鼎移植的驱动在应用层的接口在/sys/devices/platform/x210-led/目录下,有led1、led2、led3、led4四个设备文件,各自管理一个led。
(2)要去掉九鼎自己移植的led驱动,要在make menucofig中去掉选择项,然后重新make得到zImage,然后重启时启动这个新的zImage即可。
(3)新的内核启动后,如果/sys/devices/platform/目录下已经没有了x210-led这个目录,就说明我们去掉这个驱动成功了。
(4)为什么make menuconfig就能去掉这个驱动?
//九鼎没有利用内核的LED驱动框架,这里自己利用平台总线管理4个LED灯
cat led1 //灯灭
0
echo 1 > led1 //灯亮;也可写应用程序去操作。

5.4.5.2、添加led驱动框架支持
(1)当前内核中是没有LED驱动框架的,我们要去添加它。
//[root@localhost linux-3.5]# make menuconfig
Device Drivers --->
[*] LED Support --->
<*> LED Class Support //没有我们需要的,则需要我们移植。
[root@localhost linux-3.5]# ls /sys/class/leds/
[root@localhost linux-3.5]#

5.4.5.3、sysfs中的内容分析
5.4.5.4、后续展望:完成leds-x210.c

5.4.6.基于驱动框架写led驱动1
5.4.6.1、分析
(1)参考哪里? drivers/leds/leds-s3c24xx.c
(2)关键点:led_classdev_register
5.4.6.2、动手写led驱动模块
//毕竟都是三星出的,所以和朱老师的X210、或者我的tiny4412相似性较大,可供参考
[root@localhost leds]# vim leds-s3c24xx.c //
[root@localhost leds]# pwd
/linux-3.5/drivers/leds

155 int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)//2、创建属于该类的一个设备
31 struct led_classdev { //类似于cdev
32 const char *name;
33 int brightness;
34 int max_brightness;
35 int flags;
36
37 /* Lower 16 bits reflect status */
38 #define LED_SUSPENDED (1 << 0)
39 /* Upper 16 bits reflect control information */
40 #define LED_CORE_SUSPENDRESUME (1 << 16)
41
42 /* Set LED brightness level */
43 /* Must not sleep, use a workqueue if needed */
44 void (*brightness_set)(struct led_classdev *led_cdev,
45 enum led_brightness brightness);//
46 /* Get LED brightness level */
47 enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

参考三星的:
43 static void s3c24xx_led_set(struct led_classdev *led_cdev,
44 enum led_brightness value) //
45 {
46 struct s3c24xx_gpio_led *led = to_gpio(led_cdev);
47 struct s3c24xx_led_platdata *pd = led->pdata;
48
49 /* there will be a short delay between setting the output and
50 * going from output to input when using tristate. */
51
52 s3c2410_gpio_setpin(pd->gpio, (value ? 1 : 0) ^
53 (pd->flags & S3C24XX_LEDF_ACTLOW));
54
55 if (pd->flags & S3C24XX_LEDF_TRISTATE)
56 s3c2410_gpio_cfgpin(pd->gpio,
57 value ? S3C2410_GPIO_OUTPUT : S3C2410_GPIO_INPUT);
58
59 }

32 static ssize_t led_brightness_show(struct device *dev,
33 struct device_attribute *attr, char *buf)
34 {
35 struct led_classdev *led_cdev = dev_get_drvdata(dev);
36
37 /* no lock needed for this */
38 led_update_brightness(led_cdev);
39
40 return sprintf(buf, "%u\n", led_cdev->brightness);
41 }
42
43 static ssize_t led_brightness_store(struct device *dev,
44 struct device_attribute *attr, const char *buf, size_t size)
45 {
46 struct led_classdev *led_cdev = dev_get_drvdata(dev);
47 unsigned long state;
48 ssize_t ret = -EINVAL;
49
50 ret = kstrtoul(buf, 10, &state);
51 if (ret)
52 return ret;
53
54 if (state == LED_OFF)
55 led_trigger_remove(led_cdev);
56 led_set_brightness(led_cdev, state);
57
58 return size;
59 }

61 static ssize_t led_max_brightness_show(struct device *dev,
62 struct device_attribute *attr, char *buf)//
63 {
64 struct led_classdev *led_cdev = dev_get_drvdata(dev);
65
66 return sprintf(buf, "%u\n", led_cdev->max_brightness);
67 }
68
69 static struct device_attribute led_class_attrs[] = { //属性等
70 __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
71 __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
72 #ifdef CONFIG_LEDS_TRIGGERS
73 __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
74 #endif
75 __ATTR_NULL,
76 };


5.4.7.基于驱动框架写led驱动2
5.4.7.1、代码实践
(1)调试
(2)分析
通过分析看出:
第1:我们写的驱动确实工作了,被加载了,/sys/class/leds/目录下确实多出来了一个表示设备的文件夹。文件夹里面有相应的操控led硬件的2个属性brightness和max_brightness
第2:led-class.c中brightness方法有一个show方法和store方法,这两个方法对应用户在/sys/class/leds/myled/brightness目录下直接去读写这个文件时实际执行的代码。
当我们show brightness时,实际就会执行led_brightness_show函数
当我们echo 1 > brightness时,实际就会执行led_brightness_store函数
(3)show方法实际要做的就是读取LED硬件信息,然后把硬件信息返回给我们即可。所以show方法和store方法必要要会去操控硬件。但是led-class.c文件又属于驱动框架中的文件,它本身无法直接读取具体硬件,因此在show和store方法中使用函数指针的方式调用了struct led_classdev结构体中的相应的读取/写入硬件信息的方法。
(4)struct led_classdev结构体中的实际用来读写硬件信息的函数,就是我们自己写的驱动文件leds-s5pv210.c中要提供的。

//承接上面的函数顺序分析
20 static inline void led_set_brightness(struct led_classdev *led_cdev,
21 enum led_brightness value)
22 {
23 if (value > led_cdev->max_brightness)
24 value = led_cdev->max_brightness;
25 led_cdev->brightness = value;
26 if (!(led_cdev->flags & LED_SUSPENDED))
27 led_cdev->brightness_set(led_cdev, value);//操控硬件
28 }
5.4.7.2、添加硬件操作进行操作!

5.4.8.基于驱动框架写led驱动3
5.4.8.1、在驱动中将4个LED分开
(1)好处。驱动层实现对各个LED设备的独立访问,并向应用层展示出4个操作接口led1、led2、led3、led4,这样应用层可以完全按照自己的需要对LED进行控制。
驱动的设计理念:不要对最终需求功能进行假定,而应该只是直接的对硬件的操作。
有一个概念就是:机制和策略的问题。在硬件操作上驱动只应该提供机制而不是策略。策略由应用程序来做。
(2)如何实现 //我们写的是简化版本的!
5.4.8.2、和leds-s3c24xx.c的不同
5.4.8.3、gpiolib引入
(1)一个事实:很多硬件都要用到GPIO、GPIO会复用
//LED 按键 蜂鸣器 LCD SD卡等使用GPIO引脚;其他缩在SOC内部的都直接使用寄存器(定时器、RTC等)
(2)如果同一个GPIO被2个驱动同时控制了,就会出现bug
(3)内核提供gpiolib来统一管理系统中所有GPIO
//申请、使用、释放
(4)gpiolib本身属于驱动框架的一部分
//
[root@localhost linux-3.5]# ls drivers/gpio/
built-in.o gpio-ge.c gpio-max730x.c gpio-mxs.c gpio-sodaville.c gpio-vr41xx.c
devres.c gpio-generic.c gpio-max732x.c gpio-omap.c gpio-sta2x11.c gpio-vx855.c
devres.o gpio-ich.c gpio-mc33880.c gpio-pca953x.c gpio-stmpe.c gpio-wm831x.c
gpio-74x164.c gpio-it8761e.c gpio-mc9s08dz60.c gpio-pcf857x.c gpio-stp-xway.c gpio-wm8350.c
gpio-ab8500.c gpio-janz-ttl.c gpio-mcp23s08.c gpio-pch.c gpio-sx150x.c gpio-wm8994.c
gpio-adp5520.c gpio-ks8695.c gpio-ml-ioh.c gpio-pl061.c gpio-tc3589x.c gpio-xilinx.c
gpio-adp5588.c gpio-langwell.c gpio-mm-lantiq.c gpio-pxa.c gpio-tegra.c Kconfig
gpio-bt8xx.c gpiolib.c gpio-mpc5200.c gpio-rc5t583.c gpio-timberdale.c Makefile
gpio-cs5535.c gpiolib.o gpio-mpc8xxx.c gpio-rdc321x.c gpio-tnetv107x.c modules.builtin
gpio-da9052.c gpiolib-of.c gpio-msic.c gpio-sa1100.c gpio-tps65910.c modules.order
gpio-davinci.c gpio-lpc32xx.c gpio-msm-v1.c gpio-samsung.c gpio-tps65912.c
gpio-em.c gpio-max7300.c gpio-msm-v2.c gpio-samsung.o gpio-twl4030.c
gpio-ep93xx.c gpio-max7301.c gpio-mxc.c gpio-sch.c gpio-ucb1400.c
[root@localhost linux-3.5]# vim drivers/gpio/gpiolib.c


5.4.9.linux内核的gpiolib学习1
5.4.9.1、gpiolib学习重点
(1)gpiolib的建立过程
(2)gpiolib的使用方法:申请、使用、释放
(3)gpiolib的架构:涉及哪些目录的哪些文件
5.4.9.2、gpiolib的学习方法
(1)以一条主线进去,坚持主线
(2)中途遇到杂碎知识,彻底搞定之,然后继续主线
(3)随时做笔记以加深理解和记忆
(4)学习途中注意架构思想,提升自己大脑的空间复杂度
5.4.9.3、主线1:gpiolib的建立
(1)找到目标函数
smdkc110_map_io
s5pv210_gpiolib_init 这个函数就是我们gpiolib初始化的函数

 

http://blog.csdn.net/u013511330/article/details/47126793

posted @ 2017-08-09 16:31  bkycrmn  阅读(1040)  评论(0)    收藏  举报