最近要深一步用到GPIO口控制,寫個博客記錄下Kernel層的GPIO學習過程!
一、概念
General Purpose Input Output (通用輸入/輸出)簡稱為GPIO,或 總線擴展器。也就是芯片的引腳,當微控制器或芯片組沒有足夠的I/O端口,或當系統需要采用遠端串行通信或控制時,GPIO產品能夠提供額外的控制和監視功能。通常在ARM里,所有I/O都是通用的,
每個GPIO端口包含8個管腳,如PA端口是PA0~PA7,而且GPIO口至少有兩個寄存器,即"通用IO控制寄存器"與"通用IO數據寄存器"。數據寄存器的各位都直接引到芯片外部,而對這種寄存器中每一位的作用,即每一位的信號流通方向,則可以通過控制寄存器中對應位獨立地加以設置。比如,可以設置某個引腳的屬性為輸入、輸出或其他特殊功能。
常用的應該是:高阻輸入、推挽輸出、開漏輸出。
通俗理解為 :
高阻輸入—— 保持高阻抗狀態,徹底斷開輸出,避免干擾,對總線狀態不起作用,此時總線可由其他器件占用。
推挽輸出——可以輸出高,低電平,連接數字器件。
開漏輸出——輸出端相當於三極管的集電極. 要得到高電平狀態需要上拉電阻才行。
軟件上就是通過設置IO口的模式,然后控制IO的上拉下拉,寫入對應寄存器,通過寄存器控制電路:
上拉寄存器是控制對應端口上拉使能(DE)的。當對應位為0時,設置對應引腳上拉使能,為1時,禁止對應引腳上拉使能。如果上拉寄存器使能,無論引腳功能寄存器如何設置(輸入,輸出,數據,中斷等),對應引腳輸出高電平。
上拉是一個電阻接到一個電壓,其實就是增強IO的驅動能力,IO口是高電平。
下拉是一個電阻接到地,保證IO口是低電平。
二、kernel層調用接口實現
GPIO操作,在Kernel 2.6.32版本以上提供了gpio口管理的庫文件/kernel/drivers/gpio/gpiolib.c,里面就是我們需要的接口函數實現!
幾個主要的方法:
申請一個pin腳作為gpio口,命名為 * label,如果經過判斷空閑的 申請成功了做一些初始的bit位設置。
int gpio_request(unsigned gpio, const char *label)
{
struct gpio_desc *desc;
struct gpio_chip *chip;
int status = -EINVAL;
unsigned long flags;
spin_lock_irqsave(&gpio_lock, flags);
...
if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0)
...
}
釋放這個gpio口,還原bit。
void gpio_free(unsigned gpio)
{
unsigned long flags;
struct gpio_desc *desc;
struct gpio_chip *chip;
might_sleep();
...
clear_bit(FLAG_ACTIVE_LOW, &desc->flags);
clear_bit(FLAG_REQUESTED, &desc->flags);
clear_bit(FLAG_OPEN_DRAIN, &desc->flags);
clear_bit(FLAG_OPEN_SOURCE, &desc->flags);
...
}
設置gpio口為輸入模式:
int gpio_direction_input(unsigned gpio)
{
unsigned long flags;
struct gpio_chip *chip;
struct gpio_desc *desc = &gpio_desc[gpio];
int status = -EINVAL;
spin_lock_irqsave(&gpio_lock, flags);
if (!gpio_is_valid(gpio))
goto fail;
chip = desc->chip;
...
status = chip->direction_input(chip, gpio);
...
}
其中的chip->direction_input(chip, gpio)為實現!
設置gpio口為輸出模式 value為初始值 0為高電平/1為低電平:
int gpio_direction_output(unsigned gpio, int value)
{
unsigned long flags;
struct gpio_chip *chip;
struct gpio_desc *desc = &gpio_desc[gpio];
int status = -EINVAL;
/* Open drain pin should not be driven to 1 */
if (value && test_bit(FLAG_OPEN_DRAIN, &desc->flags))
return gpio_direction_input(gpio);
...
status = chip->direction_output(chip, gpio, value);
...
}
其中的chip->direction_output(chip, gpio, value)為實現!
設置gpio口的值:
void __gpio_set_value(unsigned gpio, int value)
{
struct gpio_chip *chip;
chip = gpio_to_chip(gpio);
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);
}
獲取gpio口的值:
int __gpio_get_value(unsigned gpio)
{
struct gpio_chip *chip;
int value;
chip = gpio_to_chip(gpio);
WARN_ON(chip->can_sleep);
value = chip->get ? chip->get(chip, gpio - chip->base) : 0;
trace_gpio_value(gpio, 1, value);
return value;
}
對於有些掛載在I2C,SPI總線上的擴展GPIO,讀寫操作可能會導致睡眠,因此不能在中斷函數中
使用。使用下面的函數以區別於正常的GPIO
int gpio_get_value_cansleep(unsigned gpio);//讀GPIO void gpio_set_value_cansleep(unsigned gpio, int value);//寫GPIO
三、gpiolib.c關聯芯片接口
以上為gpiolib.c的基本方法都是向下調用到對應芯片的gpio實現!
所以每個方法里面的實現都是通過 struct gpio_chip*chip 這個指針調用結構體中關聯的相關接口!
下面是gplolib怎么關聯到特定的芯片(mach-at91)的幾個主要方法:
mach-at91的/kernel/arch/arm/mach-at91/gpio.c中的
static struct at91_gpio_chip gpio_chip[] = {
AT91_GPIO_CHIP("A", 0x00 + PIN_BASE, 32),
AT91_GPIO_CHIP("B", 0x20 + PIN_BASE, 32),
AT91_GPIO_CHIP("C", 0x40 + PIN_BASE, 32),
AT91_GPIO_CHIP("D", 0x60 + PIN_BASE, 32),
AT91_GPIO_CHIP("E", 0x80 + PIN_BASE, 32),
};
gpiolib.c中的接口與mach-at91函數接口對應關系如下:
#define AT91_GPIO_CHIP(name, base_gpio, nr_gpio) \
{ \
.chip = { \
.label = name, \
.direction_input = at91_gpiolib_direction_input, \
.direction_output = at91_gpiolib_direction_output, \
.get = at91_gpiolib_get, \
.set = at91_gpiolib_set, \
.dbg_show = at91_gpiolib_dbg_show, \
.base = base_gpio, \
.ngpio = nr_gpio, \
}, \
}
從上面可以看到 在gpiolib.c中的方法調用芯片(mach-at91)的映射關系:
比如:gpio_direction_output() 設置gpio口為輸出模式中最終的實現調用chip->direction_output(chip, gpio, value)。
從上面可以看出來,也就是調用mach-at91的at91_gpiolib_direction_output()!
其它的類似。
gpiolib.c中的
int gpiochip_add(struct gpio_chip *chip)
{
unsigned long flags;
int status = 0;
unsigned id;
int base = chip->base;
if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))
&& base >= 0) {
status = -EINVAL;
goto fail;
}
spin_lock_irqsave(&gpio_lock, flags);
....
}
添加到結構體:
struct gpio_desc {
struct gpio_chip *chip;
unsigned long flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_RESERVED 2
...
}
這就保存到了 chip 指針。
這一部分在Kernel初始化的時候調用執行!
四、芯片(mach-at91)gpio口的接口
由gpiolib.c根據映射調用對應接口
static int at91_gpiolib_direction_output(struct gpio_chip *chip,
unsigned offset, int val)
{
struct at91_gpio_chip *at91_gpio = to_at91_gpio_chip(chip);
void __iomem *pio = at91_gpio->regbase;
unsigned mask = 1 << offset;
__raw_writel(mask, pio + (val ? PIO_SODR : PIO_CODR));
__raw_writel(mask, pio + PIO_OER);
return 0;
}
定義為輸出模式,初始設置val 上拉還是下拉 , 寫入數據寄存器。
#define PIO_SODR 0x30 /* Set Output Data Register */ #define PIO_CODR 0x34 /* Clear Output Data Register */ #define PIO_ODR 0x14 /* Output Disable Register */ #define PIO_PDSR 0x3c /* Pin Data Status Register */
同樣設置為輸入:
static int at91_gpiolib_direction_input(struct gpio_chip *chip,
unsigned offset)
{
struct at91_gpio_chip *at91_gpio = to_at91_gpio_chip(chip);
void __iomem *pio = at91_gpio->regbase;
unsigned mask = 1 << offset;
__raw_writel(mask, pio + PIO_ODR);
return 0;
}
設置GPIO口的value 值 0或者1,已經設置為輸出模式下:
static void at91_gpiolib_set(struct gpio_chip *chip, unsigned offset, int val)
{
struct at91_gpio_chip *at91_gpio = to_at91_gpio_chip(chip);
void __iomem *pio = at91_gpio->regbase;
unsigned mask = 1 << offset;
__raw_writel(mask, pio + (val ? PIO_SODR : PIO_CODR));
}
讀寄存器獲取該GPIO口的值:
static int at91_gpiolib_get(struct gpio_chip *chip, unsigned offset)
{
struct at91_gpio_chip *at91_gpio = to_at91_gpio_chip(chip);
void __iomem *pio = at91_gpio->regbase;
unsigned mask = 1 << offset;
u32 pdsr;
pdsr = __raw_readl(pio + PIO_PDSR);
return (pdsr & mask) != 0;
}
大體流程就這樣了。
撰寫不易,轉載請注明出處http://blog.csdn.net/jscese/article/details/16823519
