前言
GPIO驅動是Linux驅動開發中最基礎、但卻是很常用、很重要的驅動。比如你要點亮一個LED燈、鍵盤掃描、輸出高低電平等等。而Linux內核的強大之處在于對最底層的GPIO硬件操作層的基礎上封裝了一些統一的GPIO操作接口,也就是所謂的GPIO驅動框架。這樣開發人員可以調用這些接口去操作設備的IO口,不需要擔心硬件平臺的不同導致IO口的不同。
今天,我主要講的就是如何使用Linux內核封裝好的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()。
1. Linux內核中GPIO模型的結構
1.1 struct gpio_desc
//表示一個gpio口,含對應的gpio_chip.
//對于每一個gpio,都有一個gpio描述符,這個描述符包含了這個gpio所屬的控制器即chip和一些標志,label等。
/* flag symbols are bit numbers */ #define FLAG_EXPORT 2 /* protected by sysfs_lock */ #define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */ #define FLAG_TRIG_FALL 4 /* trigger on falling edge */ #define FLAG_TRIG_RISE 5 /* trigger on rising edge */ #define FLAG_ACTIVE_LOW 6 /* value has active low */ #define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */ #define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */ #define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */ #define ID_SHIFT 16 /* add new flags before this one */ #define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1) #define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE)) //采用了一個具有ARCH_NR_GPIOS大小的gpio描述符數組。這個描述符數組便代表了系統所有的gpio。
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
1.2 struct davinci_gpio_controller
//一組GPIO控制器結構,例如GPIO0和GPIO1是一組(共32個GPIO口),共用一組寄存器,所以GPIO0和GPIO1荷載一起用chips[0]來控制
//假如有144個GPIO,分為4組(GPIO0~GPIO8),每組有2個banks(即GPIO0和GPIO1為1組),每組最多可以有32個GPIO,每組的控制寄存器空間有10個
struct davinci_gpio_controller { struct gpio_chip chip;//每組對應的gpio_chip void __iomem *regs;//每組的寄存器地址 void __iomem *set_data;//設置數據寄存器地址 void __iomem *clr_data;//清除數據寄存器地址 void __iomem *in_data;//輸入數據寄存器地址
1.3 struct gpio_chip
//每一個davinci_gpio_controller結構都對應于一個gpio_chip結構,gpio_chip既可看成是davinci_gpio_controller結構的補充
//表示一個gpio controller.通過這個結構抽象化所有的GPIO源,而讓板上其它的模塊可以用相同的接口調用使用這些GPIO。
int (*request)(struct gpio_chip *chip,unsigned offset);//請求gpio void *free)(struct gpio_chip *chip,unsigned offset);//釋放gpio int (*get_direction)(struct gpio_chip *chip,unsigned offset); int (*direction_input)(struct gpio_chip *chip,unsigned offset);//配置gpio為輸入,返回當前gpio狀態 int (*get)(struct gpio_chip *chip,unsigned offset);//獲取gpio的狀態 int (*direction_output)(struct gpio_chip *chip,unsigned offset, int value);//配置gpio為輸出,并設置為value int (*set_debounce)(struct gpio_chip *chip,unsigned offset, unsigned debounce);//設置消抖動時間,尤其是gpio按鍵時有用 void (*set)(struct gpio_chip *chip,unsigned offset, int value);//設置gpio為value值 int (*to_irq)(struct gpio_chip *chip,unsigned offset);//把gpio號轉換為中斷號 void (*dbg_show)(struct seq_file *s,struct gpio_chip *chip); int base;// 這個gpio控制器的gpio開始編號 u16 ngpio;//這個gpio控制器說控制的gpio數 const char *const *names; #if defined(CONFIG_OF_GPIO) struct device_node *of_node; int (*of_xlate)(struct gpio_chip *gc,const struct of_phandle_args *gpiospec, u32 *flags); struct list_head pin_ranges;
1.4 struct davinci_gpio_regs
//GPIO寄存器結構
struct davinci_gpio_regs { u32 out_data; // gpio設置為輸出時,表示輸出狀態(0或1) u32 set_data; // gpio設置為輸出時,用于輸出高電平 u32 clr_data; // gpio設置為輸出時,用于輸出低電平 u32 in_data; // gpio設置為輸入時,用于讀取輸入值 u32 set_rising; // gpio中斷上升沿觸發設置 u32 clr_rising; // gpio中斷上升沿觸發清除 u32 set_falling; // gpio中斷下降沿觸發設置 u32 clr_falling; // gpio中斷下降沿觸發清除 u32 intstat; // gpio中斷狀態位,由硬件設置,可讀取,寫1時清除。
1.5 struct gpio
struct gpio {
unsigned gpio;//gpio號
unsigned long flags;//gpio標志
const char *label;//gpio名
};
2. 驅動開發中GPIO初始化操作
在實際的驅動開發中,根據板級資源和CPU手冊,GPIO初始化一般需要以下三個步驟:
1.設置IO口的復用模式,如果某個IO當作GPIO使用,那么就需要根據CPU手冊去配置iomux(IO復用寄存器)為GPIO模式;
2.設置IO口的輸入輸出方向,根據實際開發需求,將相應的GPIO配置為相應的輸入輸出方向;
3.GPIO初始化賦值(輸出高低電平)、拉高拉低操作;
2.1 GPIO申請
#########################################
#description:申請一個GPIO資源
#unsigned gpio:要申請的GPIO管腳號,為一個正整數
# const char *label:為申請的GPIO管腳取個名字
#########################################
int gpio_request(unsigned gpio, const char *label);
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
2.2 GPIO輸入輸出設置
#########################################
#description:設置某個GPIO的輸入輸出方向
#unsigned gpio:要設置的GPIO管腳號,為一個正整數
# int value:設置的值
#########################################
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
2.3 獲取GPIO管腳的值和設置GPIO管腳的值
#########################################
#description:獲取、設置某個GPIO的值
#unsigned gpio:要獲取、設置的GPIO管腳號
# int value:設置的值
#########################################
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);
2.4 GPIO當作中斷口使用
#########################################
#description:設置某個GPIO為中斷口
#unsigned gpio:要設置中斷的GPIO管腳號
#########################################
int gpio_to_irq(unsigned gpio);
返回的值即中斷編號可以傳給request_irq()和free_irq(),內核通過調用該函數將gpio端口轉換為中斷,在用戶空間也有類似方法。
3. GPIO驅動實例
3.1 以下GPIO驅動例子為矩陣鍵盤中對GPIO的操作
//設置某個管腳為輸入
int set_key_input(unsigned int gpio) sprintf(name, "GPIO%d", gpio); if(gpio_request(gpio,NULL) != 0) printk("gpio request error!\n"); gpio_direction_input(gpio);
//設置某個管腳為輸出
int set_key_output(unsigned int gpio,int value) sprintf(name, "GPIO%d", gpio); if(gpio_request(gpio,NULL) != 0) printk("gpio request error!\n"); gpio_direction_output(gpio,value);
//獲取某個GPIO管腳的值
int get_key_value(unsigned int gpio) if (gpio_request(gpio, NULL) != 0) printk("get_key_value err\n"); value = gpio_get_value(gpio);
//設置某個GPIO輸出為低電平
int set_key_low(unsigned int gpio) if (gpio_request(gpio, NULL) != 0) { //printk("set_key_low request err\n"); gpio_direction_output(gpio, 0); __gpio_set_value(gpio, 0);
//拉高、拉低某個GPIO
//GPIO的拉高拉低操作內核沒有提供通用的接口函數,這個需要驅動開發人員根據CPU手冊的寄存器配置去封裝拉高拉低函數,以下給出一個偽代碼的例子:
//假設拉高GPIO1_IO01這個IO:
<span style="font-size:12px;">#define SET_PULL_UP 0x01 #define SET_PULL_DOWN 0x00 #define REG_GPIO_BASE 0x8e000000 #define GPIO1_IO01_OFFSET 0x400 int set_pull_up(unsigned int reg_base,unsigned int offset,int up) gpio_base = ioreamap(reg_base,SIZE_4K);//調用ioreamap映射GPIO空間到內存,映射大小根據實際需求而定 gpio = gpio_base + offset; gpio |= up; //將某個GPIO拉高,根據具體的寄存器操作而定 __raw_writel(gpio,gpio_base + offset); set_pull_up(REG_GPIO_BASE,GPIO1_IO01_OFFSET,SET_PULL_UP);</span>
//拉低某個GPIO
#define SET_PULL_DOWN 0x00 #define REG_GPIO_BASE 0x8e000000 #define GPIO1_IO01_OFFSET 0x400 int set_pull_down(unsigned int reg_base,unsigned int offset,int down) gpio_base = ioreamap(reg_base,SIZE_4K);//調用ioreamap映射GPIO空間到內存,映射大小根據實際需求而定 gpio = gpio_base + offset; gpio &= down; //將某個GPIO拉低,根據具體的寄存器操作而定 __raw_writel(gpio,gpio_base + offset); set_pull_up(REG_GPIO_BASE,GPIO1_IO01_OFFSET,SET_PULL_DOWN);
3.2 總結
3.1中展示了基本的GPIO操作函數的編寫,在實際的驅動開發中,比如對某個連接到CPU的GPIO管腳的外設模塊需要初始化的時候,一般都是調用GPIO接口函數進行輸入輸出、拉高、拉低設置,讀者可以參考3.1的例子根據實際開發需求進行修改。
|