04-Linux裝置樹系列-GPIO驅動實踐
1. 前言
GPIO驅動開發可能算是Linux核心裝置驅動開發中最為簡單、最常見的一個方向,對於開發板的按鍵、LED、蜂鳴器、電源控制等模組,可能都是使用GPIO實現的。Linux核心的GPIO子系統在核心不斷的演進過程中進行了多次的重構,本文的第二章所舉的案例依照大家比較熟悉的GPIO開發模式展開,第3章會介紹GPIO架構最新的程式設計模式(基於4.18核心)。Linux核心GPIO子系統與pinctrl子系統存在的很大的關係,關於pinctrl子系統可以參見文章,關於GPIO子系統的討論在後續文章中會總結。下面討論GPIO基本的程式設計模式。
2. GPIO程式設計模式
本章所介紹的GPIO驅動demo基於Linux kernel 3.10
2.1 程式設計介面
Linux核心關於GPIO的控制一般通過GPIOLIB框架,核心配置時需要啟用該選項* CONFIG_GPIOLIB*。下面所有介面都是基於GPIOLIB實現的。
本demo中所涉及的GPIO程式設計介面如下:
(linux/asm-generic/gpio.h): extern int gpio_request(unsigned gpio, const char *label); extern void gpio_free(unsigned gpio); extern int gpio_direction_input(unsigned gpio); extern int gpio_direction_output(unsigned gpio, int value); extern int gpio_set_debounce(unsigned gpio, unsigned debounce); extern int gpio_get_value_cansleep(unsigned gpio); extern void gpio_set_value_cansleep(unsigned gpio, int value); (linux/gpio.h) static inline int gpio_get_value(unsigned int gpio); static inline void gpio_set_value(unsigned int gpio, int value);
2.2 DTS配置
Demo通過DTS中關於GPIO的配置資訊完成GPIO各個埠的初始化工作,下面舉一個DTS配置例子(關於DTS基本語法可以參考文章):
gpios { compatible = "gpio-user"; status = "okay"; /*input*/ gpio0 { label = "in0"; gpios = <&gpio1 1 0>; default-direction = "in"; }; ... ... /*output*/ gpio17 { label = "out1"; gpios = <&gpio3 3 0>; default-direction = "out"; }; };
上面表示系統配置了兩個GPIO,一個作為輸入gpio,一個作為輸出gpio,其中:
- label:標識該GPIO埠;
- gpios屬性的定義格式一般依賴於GPIO controler的#gpio-cells屬性欄位。對於本開發板來說gpios的格式為:
2.3 GPIO驅動程式
該dmeo基於核心的misc裝置開發框架實現,而後通過以platform_driver的形式註冊到系統中。platform_driver的資料結構如下:
static struct platform_driver gpio_user_driver = {
.probe = gpio_user_probe,
.remove = gpio_user_remove,
.driver = {
.owner = THIS_MODULE,
.name = "gpio-user",
.of_match_table = of_gpio_user_id_table,
},
};
其中,of_gpio_user_id_table定義了該驅動的裝置相容性,其與2.2節中的compatible = “gpio-user”,”gpio-user”欄位相對應。下面是of_gpio_user_id_table的定義:
static const struct of_device_id of_gpio_user_id_table[] = {
{ .compatible = "gpio-user",},
{},
};
如果DTS中所定義的裝置與驅動匹配上的話,那麼gpio_user_probe將會被執行,下面著重分析一下該函式的實現方式。在講解之前需要分析一下驅動定義的私有資料結構,其定義如下:
struct gpio_user_data{
const char *label;//DTS中的label欄位
bool input;//是否為輸入模式
unsigned gpio;//gpio編號
unsigned dft;//gpio輸出模式下的預設輸出值
};
static struct gpio_misc{
struct miscdevice misc;//misc裝置模型
struct gpio_user_data *data;//gpio配置資料
int gpio_count;//gpio配置資料個數
} *gpio_misc;
gpio_user_probe的實現如下:
static int gpio_user_probe(struct platform_device *pdev)
{
int index;
struct device_node *node = pdev->dev.of_node, *child;
gpio_misc = devm_kzalloc(&pdev->dev,sizeof(*gpio_misc),GFP_KERNEL);------------------>(1)
if(!gpio_misc){
return -ENOMEM;
}
gpio_misc->gpio_count = of_get_available_child_count(node);------------------>(2)
if(!gpio_misc->gpio_count){
return -ENODEV;
}
if(gpio_misc->gpio_count > MAX_GPIO_NR){
gpio_misc->gpio_count = MAX_GPIO_NR;
}
gpio_misc->data = devm_kzalloc(&pdev->dev,sizeof(struct gpio_user_data) * gpio_misc->gpio_count,GFP_KERNEL);
if(!gpio_misc->data){
return -ENOMEM;
}
index = 0;
for_each_available_child_of_node(node,child){------------------>(3)
const char *input;
struct gpio_user_data *data = &gpio_misc->data[index++];
data->label = of_get_property(child,"label",NULL) ? : child->name;
input = of_get_property(child,"default-direction",NULL) ? : "in";
if(strcmp(input,"in") == 0)
data->input = true;
data->gpio = of_get_gpio_flags(child,0,&data->dft);
}
gpio_user_init_default();------------------>(4)
gpio_misc->misc.name = "gpio";------------------>(5)
gpio_misc->misc.minor = MISC_DYNAMIC_MINOR;
gpio_misc->misc.fops = &gpio_user_fops;
return misc_register(&gpio_misc->misc);
}
下面分步驟對其進行解析:
- (1)使用具有記憶體回收功能的devm_kzalloc為gpio_misc分配記憶體空間。
- (2)通過of_get_available_child_count獲取DTS的gpio子節點的個數。判斷該值的有效性,然後根據gpio子節點個數建立gpio_misc->data陣列。
- (3)使用for_each_available_child_of_node變臉DTS中gpio子節點,初始化gpio_misc->data陣列。
- (4)初始化gpio配置資訊,下面會詳細介紹gpio_user_init_default函式。
- (5)初始化misc裝置資訊,註冊gpio_misc->misc裝置到系統中。
gpio_user_init_default函式實現方式如下:
static void gpio_user_init_default(void)
{
int i,ret;
struct gpio_user_data *data;
data = gpio_misc->data;
for(i = 0;i < gpio_misc->gpio_count;i++) {
if(!gpio_is_valid(data[i].gpio)) {
continue;
}
ret = gpio_request(data[i].gpio,data[i].label);------------------>(1)
if(ret < 0) {
continue;
}
if(data[i].input) {
gpio_direction_input(data[i].gpio);---------------------->(2)
}
else {
gpio_direction_output(data[i].gpio,data[i].dft);
}
}
}
下面簡單的介紹一下實現步驟:
(1)檢測gpio_num的有效性,並通過gpio_request向系統申請gpio的使用權(如果申請成功,該gpio會被標記為已佔用,並且GPIO的功能屬性為GPIO);
(2)配置GPIO的輸入、輸出模式。
通過上面一系列的初始化工作,DTS中配置的gpio基本都配置完成了。下面分析一下ioctl介面,講解一下如何控制GPIO的行為。
#define GPIO_U_IOCTL_BASE 'x'
#define GPIOC_OPS _IOWR(GPIO_U_IOCTL_BASE,0,int)
static const struct file_operations gpio_user_fops = {
.owner = THIS_MODULE,
.open = gpio_user_open,
.release = gpio_user_release,
.unlocked_ioctl = gpio_user_ioctl,
};
static long gpio_user_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
{
int no, offset;
unsigned long val;
unsigned long __user *p = (void __user *)arg;
struct gpio_user_data *data;
unsigned long get_value;
if(!gpio_misc)
return -ENODEV;
data = gpio_misc->data;
if(_IOC_TYPE(cmd) != GPIO_U_IOCTL_BASE)
return -EINVAL;
switch(_IOC_NR(cmd)) {
case 0:
if(get_user(val,p))
return -EFAULT;
no = val & (~(1u << 31));------------------>(1)
if(data[no].input) {
get_value = gpio_get_value(data[no].gpio);------------------>(2)
printk("get_value is %d\n", get_value);
offset = data[no].gpio % 32;
val = get_value >> offset;
printk("val is %d\n",val);
put_user(val,p);
} else {
gpio_set_value(data[no].gpio,val >> 31);------------------>(3)
}
break;
default:
return -ENOTTY;
}
return 0;
}
首先,通過_IOWR定義了一個ioctl操作命令:GPIOC_OPS,關於_IOWR的具體使用方式可以參考文章。
下面簡單的介紹一下實現步驟:
- (1)檢測ioctl控制命令有效性,獲取gpio編號;
- (2)如果為input模式下的gpio,讀取GPIO當前value,並將該狀態返回給使用者程式;
- (3)如果為output模式下的gpio, 設定GPIO輸出value;
上面既是GPIO驅動端的程式碼實現,完整的程式碼可以在這裡下載。
2.3 GPIO測試程式
GPIO的測試程式通過裝置檔案(/dev/gpio)完成GPIO的輸出狀態配置和GPIO輸入狀態的讀取功能。程式碼可以在這裡下載。
3. GPIO子系統的變化
關於GPIO子系統的變化,參考後續的文章,未完待續… …