我是靠谱客的博主 俊逸香水,这篇文章主要介绍Android下led控制(中)--Linux部分,现在分享给大家,希望可以做个参考。

首先声明一下我的实验平台,是全志CQA83T,成都启划信息的板子。上面一篇博客介绍了Android下led控制的Android部分。这一篇我想说说Linux下的部分,从上一篇我们可以知道,jni通过打开led设备/dev/led,进而使用ioctl函数来控制led的亮和灭和蜂鸣器的发声。那么在Linux下面,为什么会接受ioctl控制,ioctl函数是怎么控制led的?当然,其实到这个地步,已经和Android完全没有关系了,纯属于Linux驱动的事情了。

最初,我以为板子上的led驱动是动态驱动模块(*.ko),在系统启动后进行加载的,可是当我查看系统配置文件的时候才发现,完全不是这个样子的。我们看一下Android代码里初始化文件对led的配置,在CQA83TAndroid_v2.1.0_bv3/android/device/softwinner/octopus-f1/init.sun8i.rc里面,如下图


这里仅仅是更改设备的权限,这里也说明的当Android部分启动时,led的驱动已经加载到Linux内核。如果还不清楚,来看一下,初始化文件对其他设备的配置。如下图


到这个地方我们能说明,led驱动是在内核中加载完成的。那么它究竟是在何时加载的?这个问题我们先不去探究。下面我们先看一下led驱动的源文件。

我们知道,led驱动属于字符设备,那么其源码位置在Linux内核源码的drivers/char/led.c ,其源代码是:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#include <linux/types.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/init.h> #include <linux/input.h> #include <linux/irq.h> #include <linux/interrupt.h> #include <linux/jiffies.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/input/matrix_keypad.h> #include <linux/slab.h> #include <asm/io.h> #include <mach/irqs.h> #include <mach/hardware.h> #include <mach/sys_config.h> #include <linux/miscdevice.h> #include <linux/printk.h> #include <linux/kernel.h> #define LED_IOCTL_SET_ON 1 #define LED_IOCTL_SET_OFF 0 static script_item_u led_val[5]; static script_item_value_type_e led_type; static struct semaphore lock; //led_open static int led_open(struct inode *inode, struct file *file) { if (!down_trylock(&lock)) return 0; else return -EBUSY; } //led_close static int led_close(struct inode *inode, struct file *file) { up(&lock); return 0; } //led_ioctl static long led_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { unsigned int n; n = (unsigned int)arg; switch (cmd) { case LED_IOCTL_SET_ON: if (n < 1) return -EINVAL; if(led_val[n-1].gpio.gpio != -1) { __gpio_set_value(led_val[n-1].gpio.gpio, 1); printk("led%d on !n", n); } break; case LED_IOCTL_SET_OFF: default: if (n < 1) return -EINVAL; if(led_val[n-1].gpio.gpio != -1) { __gpio_set_value(led_val[n-1].gpio.gpio, 0); printk("led%d off !n", n); } break; } return 0; } //led_gpio static int __devinit led_gpio(void) { int i = 0; char gpio_num[10]; for(i =1 ; i < 6; i++) { sprintf(gpio_num, "led_gpio%d", i); led_type= script_get_item("led_para", gpio_num, &led_val[i-1]); if(SCIRPT_ITEM_VALUE_TYPE_PIO != led_type) { printk("led_gpio type fail !"); // gpio_free(led_val[i-1].gpio.gpio); led_val[i-1].gpio.gpio = -1; continue; } if(0 != gpio_request(led_val[i-1].gpio.gpio, NULL)) { printk("led_gpio gpio_request fail !"); led_val[i-1].gpio.gpio = -1; continue; } if (0 != gpio_direction_output(led_val[i-1].gpio.gpio, 0)) { printk("led_gpio gpio_direction_output fail !"); // gpio_free(led_val[i-1].gpio.gpio); led_val[i-1].gpio.gpio = -1; continue; } } return 0; } //file_operations static struct file_operations leds_ops = { .owner = THIS_MODULE, .open = led_open, .release = led_close, .unlocked_ioctl = led_ioctl, }; //miscdevice static struct miscdevice leds_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "led", .fops = &leds_ops, }; //led_remove static int __devexit led_remove(struct platform_device *pdev) { return 0; } //led_probe static int __devinit led_probe(struct platform_device *pdev) { int led_used; script_item_u val; script_item_value_type_e type; int err; printk("led_para!n"); type = script_get_item("led_para", "led_used", &val); if (SCIRPT_ITEM_VALUE_TYPE_INT != type) { printk("%s script_get_item "led_para" led_used = %dn", __FUNCTION__, val.val); return -1; } led_used = val.val; printk("%s script_get_item "led_para" led_used = %dn", __FUNCTION__, val.val); if(!led_used) { printk("%s led_used is not used in config, led_used=%dn", __FUNCTION__,led_used); return -1; } err = led_gpio(); if (err) return -1; sema_init(&lock, 1); err = misc_register(&leds_dev); printk("======= cqa83 led initialized ================n"); return err; } //platform_device struct platform_device led_device = { .name = "led", }; //platform_driver static struct platform_driver led_driver = { .probe = led_probe, .remove = __devexit_p(led_remove), .driver = { .name = "led", .owner = THIS_MODULE, }, }; //led_init static int __init led_init(void) { if (platform_device_register(&led_device)) { printk("%s: register gpio device failedn", __func__); } if (platform_driver_register(&led_driver)) { printk("%s: register gpio driver failedn", __func__); } return 0; } //led_exit static void __exit led_exit(void) { platform_driver_unregister(&led_driver); } module_init(led_init); module_exit(led_exit); MODULE_DESCRIPTION("Led Driver"); MODULE_LICENSE("GPL v2");

前面我们已经知道,jni是通过ioctl来控制led和蜂鸣器的动作,那么源码里的led_ioctl函数就是与此相对应的。那我们重点来看一下led_ioctl函数:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//led_ioctl static long led_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { unsigned int n; n = (unsigned int)arg; switch (cmd) { case LED_IOCTL_SET_ON: if (n < 1) return -EINVAL; if(led_val[n-1].gpio.gpio != -1) { __gpio_set_value(led_val[n-1].gpio.gpio, 1); printk("led%d on !n", n); } break; case LED_IOCTL_SET_OFF: default: if (n < 1) return -EINVAL; if(led_val[n-1].gpio.gpio != -1) { __gpio_set_value(led_val[n-1].gpio.gpio, 0); printk("led%d off !n", n); } break; } return 0; }

函数内前两行是定义了变量n,并且把星灿arg赋值给n,这样n就代表led的标号。下面就是一个switch-case语句了,条件是形参cmd的值,用到LED_IOCTL_SET_ON和LED_IOCTL_SET_OFF两个宏,这两个宏是在源文件开头定义的,LED_IOCTL_SET_ON的值为1,LED_IOCTL_SET_OFF的值为0。很显然,这个cmd是用用来标示电路中的led是灭还亮的,如果cmd的值等于LED_IOCTL_SET_ON则使led亮,如果cmd的值等于LED_IOCTL_SET_OFF则使led灭。这里调用了,__gpio_set_value这个函数,我们先不分析这个函数,先说为什么写1 led亮,而写0 led灭。这个问题要从硬件电路来说明,我们来看一下,led和蜂鸣器的电路,如下图:


这样就一目了然,它们是共地的,只有IO口输出高电平时,led才能亮,蜂鸣器才能响。

下面我们看一下上面说的__gpio_set_value这个函数,这个函数的实现是在drivers/gpio/gpiolib.c这个文件里面。我们看一下这个函数的实现,

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/** * __gpio_set_value() - assign a gpio's value * @gpio: gpio whose value will be assigned * @value: value to assign * Context: any * * This is used directly or indirectly to implement gpio_set_value(). * It invokes the associated gpio_chip.set() method. */ void __gpio_set_value(unsigned gpio, int value) { struct gpio_chip *chip; chip = gpio_to_chip(gpio); /* Should be using gpio_set_value_cansleep() */ WARN_ON(chip->can_sleep); trace_gpio_value(gpio, 0, value); if (test_bit(FLAG_OPEN_DRAIN, &gpio_desc[gpio].flags)) _gpio_set_open_drain_value(gpio, chip, value); else if (test_bit(FLAG_OPEN_SOURCE, &gpio_desc[gpio].flags)) _gpio_set_open_source_value(gpio, chip, value); else chip->set(chip, gpio - chip->base, value); } EXPORT_SYMBOL_GPL(__gpio_set_value);

我们逐行分析这个函数:

struct gpio_chip *chip;

对于结构体gpio_chip牵涉到了Linux gpio驱动模型,这里简单说一下gpio驱动模型:

GPIO是嵌入式系统最简单、最常用的资源了,比如点亮LED,控制蜂鸣器,输出高低电平,检测按键,等等。GPIO分输入和输出,在davinci linux中,有关GPIO的最底层的寄存器驱动,archarmmach-davinci目录下的gpio.c,这个是寄存器级的驱动,搞过单片机MCU的朋友应该比较熟悉寄存器级的驱动。

GPIO的驱动主要就是读取GPIO口的状态,或者设置GPIO口的状态。就是这么简单,但是为了能够写好的这个驱动,在LINUX上作了一些软件上的分层。为了让其它驱动可以方便的操作到GPIO,在LINUX里实现了对GPIO操作的统一接口,这个接口实则上就是GPIO驱动的框架,具体的实现文件为gpiolib.c在配置内核的时候,我们必须使用CONFIG_GENERIC_GPIO这个宏来支持GPIO驱动。

    GPIO是与硬件体系密切相关的,linux提供一个模型来让驱动统一处理GPIO,即各个板卡都有实现自己的gpio_chip控制模块:request, free, input,output, get,set,irq...然后把控制模块注册到内核中,这时会改变全局gpio数组:gpio_desc[]. 当用户请求gpio时,就会到这个数组中找到,并调用这个GPIO对应的gpio_chip的处理函数。gpio实现为一组可用的 gpio_chip, 由驱动传入对应 gpio的全局序号去 request, dataout ,datain, free. 这时会调用gpio_chip中具体的实现。

    gpio是一组可控件的脚,由多个寄存器同时控制。通过设置对应的寄存器可以达到设置GPIO口对应状态与功能。数据状态,输入输出方向,清零,中断(那个边沿触发), 一般是一组(bank)一组的。寄存器读写函数: __raw_writel() __raw_writeb() __raw_readl() __raw_readb()

(上面引用自:http://blog.csdn.net/bytxl/article/details/50337091)大家看这篇博文了解更详细的GPIO驱动模型。

结构体gpio_chip的定义在include/asm-generic/gpio.h文件中,具体内容是:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
struct gpio_chip { const char *label; struct device *dev; struct module *owner; int (*request)(struct gpio_chip *chip, unsigned offset); void (*free)(struct gpio_chip *chip, unsigned offset); int (*direction_input)(struct gpio_chip *chip, unsigned offset); int (*get)(struct gpio_chip *chip, unsigned offset); int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value); int (*set_debounce)(struct gpio_chip *chip, unsigned offset, unsigned debounce); void (*set)(struct gpio_chip *chip, unsigned offset, int value); int (*to_irq)(struct gpio_chip *chip, unsigned offset); void (*dbg_show)(struct seq_file *s, struct gpio_chip *chip); int base; u16 ngpio; const char *const *names; unsigned can_sleep:1; unsigned exported:1; #if defined(CONFIG_OF_GPIO) /* * If CONFIG_OF is enabled, then all GPIO controllers described in the * device tree automatically may have an OF translation */ struct device_node *of_node; int of_gpio_n_cells; int (*of_xlate)(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, u32 *flags); #endif #ifdef CONFIG_PINCTRL /* * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally * describe the actual pin range which they serve in an SoC. This * information would be used by pinctrl subsystem to configure * corresponding pins for gpio usage. */ struct list_head pin_ranges; #endif };

下面来看:

chip = gpio_to_chip(gpio);

在相同文件下的函数实现为:

复制代码
1
2
3
4
5
/* caller holds gpio_lock *OR* gpio is marked as requested */ struct gpio_chip *gpio_to_chip(unsigned gpio) { return gpio_desc[gpio].chip; }
这个函数很简单,我们来看gpio描述结构体gpio_desc,该结构体在同文件下,内容如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct gpio_desc { struct gpio_chip *chip; unsigned long flags; <span style="white-space:pre"> </span>/* flag symbols are bit numbers */ <span style="white-space:pre"> </span>#define FLAG_REQUESTED 0 <span style="white-space:pre"> </span>#define FLAG_IS_OUT 1 <span style="white-space:pre"> </span>#define FLAG_RESERVED 2 <span style="white-space:pre"> </span>#define FLAG_EXPORT 3 /* protected by sysfs_lock */ <span style="white-space:pre"> </span>#define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */ <span style="white-space:pre"> </span>#define FLAG_TRIG_FALL 5 /* trigger on falling edge */ <span style="white-space:pre"> </span>#define FLAG_TRIG_RISE 6 /* trigger on rising edge */ <span style="white-space:pre"> </span>#define FLAG_ACTIVE_LOW 7 /* sysfs value has active low */ <span style="white-space:pre"> </span>#define FLAG_OPEN_DRAIN 8 /* Gpio is open drain type */ <span style="white-space:pre"> </span>#define FLAG_OPEN_SOURCE 9 /* Gpio is open source type */ <span style="white-space:pre"> </span>#define ID_SHIFT 16 /* add new flags before this one */ <span style="white-space:pre"> </span>#define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1) <span style="white-space:pre"> </span>#define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE)) <span style="white-space:pre"> </span>#ifdef CONFIG_DEBUG_FS const char *label; <span style="white-space:pre"> </span>#endif }; static struct gpio_desc <span style="color:#ff0000;">gpio_desc</span>[ARCH_NR_GPIOS];

继续看:

WARN_ON(chip->can_sleep); 

这句是设置gpio值,gpio可休眠,同gpio_set_value_cansleep()函数。

trace_gpio_value(gpio, 0, value);

根据查资料,对gpio的值添加追踪事件,我推测是应该是获取你要操作的gpio的当前状态。(没有查到确切资料,如果哪位知道,请共享一下)


if (test_bit(FLAG_OPEN_DRAIN,  &gpio_desc[gpio].flags))
_gpio_set_open_drain_value(gpio, chip, value);
else if (test_bit(FLAG_OPEN_SOURCE,  &gpio_desc[gpio].flags))
_gpio_set_open_source_value(gpio, chip, value);
else
chip->set(chip, gpio - chip->base, value);

这三句就是给IO口写值了,这里牵涉到了硬件上GPIO控制器的GPIO的控制模式,test_bit函数是用来做位测试,test_bit(FLAG_OPEN_DRAIN,  &gpio_desc[gpio].flags),这里就是要测试gpio_desc[gpio].flags)的第FLAG_OPEN_DRAIN位是否为1。意思就是,该GPIO控制器是否支持开漏控制方式。

我们再进入到_gpio_set_open_drain_value(gpio, chip, value)和_gpio_set_open_source_value(gpio, chip, value)这个函数:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* * _gpio_set_open_drain_value() - Set the open drain gpio's value. * @gpio: Gpio whose state need to be set. * @chip: Gpio chip. * @value: Non-zero for setting it HIGH otherise it will set to LOW. */ static void _gpio_set_open_drain_value(unsigned gpio, struct gpio_chip *chip, int value) { int err = 0; if (value) { err = chip->direction_input(chip, gpio - chip->base); if (!err) clear_bit(FLAG_IS_OUT, &gpio_desc[gpio].flags); } else { err = chip->direction_output(chip, gpio - chip->base, 0); if (!err) set_bit(FLAG_IS_OUT, &gpio_desc[gpio].flags); } trace_gpio_direction(gpio, value, err); if (err < 0) pr_err("%s: Error in set_value for open drain gpio%d err %dn", __func__, gpio, err); } /* * _gpio_set_open_source() - Set the open source gpio's value. * @gpio: Gpio whose state need to be set. * @chip: Gpio chip. * @value: Non-zero for setting it HIGH otherise it will set to LOW. */ static void _gpio_set_open_source_value(unsigned gpio, struct gpio_chip *chip, int value) { int err = 0; if (value) { err = chip->direction_output(chip, gpio - chip->base, 1); if (!err) set_bit(FLAG_IS_OUT, &gpio_desc[gpio].flags); } else { err = chip->direction_input(chip, gpio - chip->base); if (!err) clear_bit(FLAG_IS_OUT, &gpio_desc[gpio].flags); } trace_gpio_direction(gpio, !value, err); if (err < 0) pr_err("%s: Error in set_value for open source gpio%d err %dn", __func__, gpio, err); }

从这两个函数可以看出,到这里基本上都是直接对GPIO的直接操作了,包括输入输出控制。细心点可以发现,如果我们假设,value等于1,也就是我们打算让GPIO口输出高,两个函数里使用的函数是不一样的,_gpio_set_open_drain_value(gpio, chip, value)里面使用的是chip->direction_input(chip, gpio - chip->base),而_gpio_set_open_source_value(gpio, chip, value)里面使用的是chip->direction_output(chip, gpio - chip->base, 1),这 里不怎么看的懂,我的直观感觉是和开漏电路有关系,希望知道的朋友能够共享。

到这里,基本上是把Linux下GPIO驱动模型马马虎虎的了解了一点点,下载就一个感觉Linux好复杂。下面还是回到我们的驱动函数led.c里面,从里面不难发现,这个驱动使用了Linux的platform机制。

关于plantform先不在这里分析,专门写文章来分析。先给大家推荐几篇博文:

http://blog.csdn.net/yuanlulu/article/details/6184266

http://blog.csdn.net/weiqing1981127/article/details/8245665

http://blog.csdn.net/ufo714/article/details/8595021
http://blog.csdn.net/liuhaoyutz/article/details/15504127

http://blog.csdn.net/yaozhenguo2006/article/details/6784895


在此非常感谢大神们的分享。







最后

以上就是俊逸香水最近收集整理的关于Android下led控制(中)--Linux部分的全部内容,更多相关Android下led控制(中)--Linux部分内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(80)

评论列表共有 0 条评论

立即
投稿
返回
顶部