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 ,其源代码是:

#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


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







你可能感兴趣的:(android,LED驱动,LED驱动,CQA83T)