1. 程式人生 > >Android下led控制(中)--Linux部分

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

在此非常感謝大神們的分享。