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
在此非常感謝大神們的分享。