您的位置:首页 > 运维架构 > Linux

Android下led控制(中)--Linux部分

2016-06-14 18:00 633 查看
首先声明一下我的实验平台,是全志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 ,其源代码是:

#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 = %d\n",
__FUNCTION__, val.val);
return -1;
}
led_used = val.val;
printk("%s script_get_item \"led_para\" led_used = %d\n",
__FUNCTION__, val.val);

if(!led_used) {
printk("%s led_used is not used in config,  led_used=%d\n", __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 failed\n", __func__);
}
if (platform_driver_register(&led_driver)) {
printk("%s: register gpio driver failed\n", __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函数:

//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这个文件里面。我们看一下这个函数的实现,

/**
* __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的最底层的寄存器驱动,\arch\arm\mach-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文件中,具体内容是:

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);
在相同文件下的函数实现为:

/* 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,该结构体在同文件下,内容如下:

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)这个函数:

/*
*  _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 %d\n",
__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 %d\n",
__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
在此非常感谢大神们的分享。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息