linux-3.6.8 s3c6410 GPIO 驱动 简要分析

时间:2022-02-10 07:00:09

Linux的GPIO驱动模型框架为structgpio_chip(include/asm-generic/gpio.h)。依据此框架,Linux自身提供了一套操控函数(drivers/gpio/gpiolib.c)。而SAMSUNG根据自身SoC的GPIO寄存器特性,将该框架封装,并定义了一套自己的GPIO操控函数(drivers/gpio/gpio-samsung.c)。

Linux对GPIO以数组的思想进行管理,每个GPIO管脚对应一个序号(这里并非指数组元素),连续的若干个序号组成一个GPIO组(例如GPA、GPB等等),而这每一个组就对应一个struct gpio_chip。此种管理方式相对于s3c6410体现于arch/arm/mach-s3c64xx/include/mach/gpio.h文件中,其中形如S3C64XX_GPIO_*_NR的宏定义了每组GPIO有多少个管脚;形如S3C64XX_GPIO_*_START的宏定义了每组GPIO第一个管脚的序号;形如S3C64XX_GP*(_nr)的宏隐藏了序号,方便了程序调用(例如S3C64xx_GPI(7)就代表了第I组GPIO的第7个管脚的序号)。

下面来看看Linux的GPIO驱动模型框架,并简要叙述其中部分成员:

/**
* struct gpio_chip - abstract a GPIO controller
* @label: for diagnostics
* @dev: optional device providing the GPIOs
* @owner: helps prevent removal of modules exporting active GPIOs
* @request: optional hook for chip-specific activation, such as
*enabling module power and clock; may sleep
* @free: optional hook for chip-specific deactivation, such as
*disabling module power and clock; may sleep
* @direction_input: configures signal "offset" as input, or returns error
* @get: returns value for signal "offset"; for output signals this
*returns either the value actually sensed, or zero
* @direction_output: configures signal "offset" as output, or returns error
* @set: assigns output value for signal "offset"
* @to_irq: optional hook supporting non-static gpio_to_irq() mappings;
*implementation may not sleep
* @dbg_show: optional routine to show contents in debugfs; default code
*will be used when this is omitted, but custom code can show extra
*state (such as pullup/pulldown configuration).
* @base: identifies the first GPIO number handled by this chip; or, if
*negative during registration, requests dynamic ID allocation.
* @ngpio: the number of GPIOs handled by this controller; the last GPIO
*handled is (base + ngpio - 1).
* @can_sleep: flag must be set iff get()/set() methods sleep, as they
*must while accessing GPIO expander chips over I2C or SPI
* @names: if set, must be an array of strings to use as alternative
* names for the GPIOs in this chip. Any entry in the array
* may be NULL if there is no alias for the GPIO, however the
* array must be @ngpio entries long. A name can include a single printk
* format specifier for an unsigned int. It is substituted by the actual
* number of the gpio.
*
* A gpio_chip can help platforms abstract various sources of GPIOs so
* they can all be accessed through a common programing interface.
* Example sources would be SOC controllers, FPGAs, multifunction
* chips, dedicated GPIO expanders, and so on.
*
* Each chip controls a number of signals, identified in method calls
* by "offset" values in the range 0..(@ngpio - 1). When those signals
* are referenced through calls like gpio_get_value(gpio), the offset
* is calculated by subtracting @base from the gpio number.
*/
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);
intbase;
u16ngpio;
const char*const *names;
unsignedcan_sleep:1;
unsignedexported: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
};

base存储的就是该组GPIO第一个管脚的序号。ngpio存储该组GPIO共有多少个管脚。direction_input和direction_output函数指针分别指向将GPIO管脚配置成输入和输出的函数。set和get函数指针分别指向设置和获取GPIO管脚值的函数。request和free函数指针分别指向申请和释放GPIO管脚使用权的函数。

我们首先来看看Linux基于该框架为我们提供了什么(由于知识面有限,在此仅分析其中部分函数):

structgpio_desc {

       structgpio_chip    *chip;

       unsignedlong             flags;

/* flag symbols are bit numbers */

#define FLAG_REQUESTED  0

#define FLAG_IS_OUT  1

#define FLAG_RESERVED    2

#define FLAG_EXPORT 3     /* protected bysysfs_lock */

#define FLAG_SYSFS    4     /* exported via/sys/class/gpio/control */

#define FLAG_TRIG_FALL    5     /* trigger on fallingedge */

#define FLAG_TRIG_RISE     6     /* trigger on risingedge */

#define FLAG_ACTIVE_LOW       7     /* sysfs value hasactive low */

#define FLAG_OPEN_DRAIN       8     /* Gpio is opendrain type */

#define FLAG_OPEN_SOURCE 9  /* Gpio is open source type */

 

#define ID_SHIFT  16    /* add new flags beforethis one */

 

#define GPIO_FLAGS_MASK              ((1 << ID_SHIFT) - 1)

#define GPIO_TRIGGER_MASK    (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE))

 

#ifdef CONFIG_DEBUG_FS

       constchar            *label;

#endif

};

static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

该全局数组主要用于描述每个GPIO脚当前的一些特性,用unsigned long flags的各bit指明。ARCH_NR_GPIOS表示总GPIO管脚数。结构体中的部分标识位(FLAG_*)会在接下来的某些函数说明中提到。

int gpiochip_add(struct gpio_chip *chip)

int gpiochip_remove(struct gpio_chip *chip)

每个struct gpio_chip表明一个GPIO组,该组中包含着几个GPIO管脚。那么gpiochip_add()就是将这几个管脚在gpio_desc全局数组中的位置的chip成员指向这个struct gpio_chip。这样在使用该管脚时,就能找到该管脚所在GPIO组所对应的struct gpio_chip。该函数最后调用of_gpiochip_add()向内核注册该struct gpio_chip。

有注册就有注销,gpiochip_remove()做相反的操作。调用of_gpiochip_remove()从内核中注销该struct gpio_chip,并将该struct gpio_chip所包含的几个管脚在全局数组gpio_desc中所对应的chip分别赋为NULL。

int gpio_request(unsigned gpio, const char*label)

int gpio_request_one(unsigned gpio, unsignedlong flags, const char *label)

int gpio_request_array(const struct gpio*array, size_t num)

static int gpio_ensure_requested(structgpio_desc *desc, unsigned offset)

const char *gpiochip_is_requested(structgpio_chip *chip, unsigned offset)

void gpio_free(unsigned gpio)

void gpio_free_array(const struct gpio *array,size_t num)

       gpio_request()申请某个GPIO管脚的使用权。

       struct gpio_desc    *desc;

       struct gpio_chip     *chip;

      

       desc = &gpio_desc[gpio];

       chip = desc->chip;

这段代码便体现了gpio_desc全局数组的作用,只要简单传入GPIO的序号(gpio),就可以方便的找到该GPIO管脚对应的struct gpio_chip。该函数还置位了gpio_desc中的FLAG_REQUESTED位,表明该GPIO管脚已被分配使用权。最后函数调用struct gpio_chip中的request函数指针,执行具体的申请使用权函数,该函数应由板卡驱动实现。

gpio_request_one()首先调用gpio_request()申请使用权,接着根据传入的flags对该GPIO管脚进行一些初始化配置。

gpio_request_array()通过多次调用gpio_request_one()申请多个GPIO(并不一定是GPIO组)的使用权以及初始化这些GPIO。

gpio_ensure_requested()一般在配置GPIO管脚前调用,它查看你是否request过该管脚。如果没有,则打印警告(该函数却并没有实际调用gpio_request作为弥补)。

gpiochip_is_requested()类似于gpio_ensure_requested(),它一般用在GPIO调试信息输出函数中。传入的GPIO序号非法、未注册该GPIO以及未申请该GPIO的使用权,该函数都会返回NULL,否则返回一个无意义的字符串。

同样,有申请就有释放。gpio_free()调用struct gpio_chip中的free函数指针,执行具体的释放使用权函数,该函数同样应由板卡驱动实现。

gpio_free_array()通过多次调用gpio_free()释放多个GPIO的使用权(并不一定是GPIO组)。

int __init gpiochip_reserve(int start, intngpio)

该函数通过设置全局数组gpio_desc对应序号的FLAG_RESERVED标志位预留一段连续的序号(start ~ start+ngpio-1)。如果其中有任意序号被注册或是预留过,则返回错误,成功返回0。该函数可以保护一段GPIO序号不被自动分配(关于GPIO序号的自动分配会在接下来的gpiochip_find_base()中提及),注释中写的很明白,例如GPx的驱动由于某些原因需要延时加载,则属于该GPx组的序号可以先进行预留。

struct gpio_chip *gpiochip_find(void *data, int(*match)(struct gpio_chip *chip, void *data))

static int gpiochip_find_base(int ngpio)

       gpiochip_find()根据提供的data找寻匹配的struct gpio_chip,match函数指针指向具体的找寻函数,该函数应由板卡驱动实现(一般采用如下方法:strcmp(chip->label, data))。

       gpiochip_find_base()为若干个GPIO管脚自动分配一段连续未被注册或未被预留的序号。现在回头来看看arch/arm/mach-s3c64xx/include/mach/gpio.h文件尾部,ARCH_NR_GPIOS代表该板卡支持的GPIO总数量,而其中不仅包含实际的GPIO序号(GPIO_BOARD_START),还包含了一些额外的序号(BOARD_NR_GPIOS)。例如使用热插拔设备时,这些额外的序号就可以被用来自动分配。实际的序号在前,额外的序号在后,这也就解释了为何函数中从最后一个序号开始寻找(for (i = ARCH_NR_GPIOS - 1; i >= 0 ; i--))。

int gpio_direction_input(unsigned gpio)

int gpio_direction_output(unsigned gpio, intvalue)

       gpio_direction_input()设置GPIO为输入模式,它通过调用struct gpio_chip中的direction_input函数指针调用实际的设置函数。在函数中可以看到,全局数组gpio_desc对应序号的FLAG_IS_OUT标志位为0表示该GPIO管脚为输入模式,那么标志位为1则代表输出模式。

       gpio_direction_output()将GPIO设置为输出模式,依旧是通过调用struct gpio_chip中的direction_output函数指针的方式调用实际的设置函数,最后置位FLAG_IS_OUT。不过输出有两种特殊的模式,全局数组gpio_desc中的FLAG_OPEN_DRAIN和FLAG_OPEN_SOURCE标志位分别指定这两种模式(第一个是开漏模式,第二个不太清楚 +_+)。它们分别不能被设置为1和0,如果强行这么设置,函数中会调用gpio_direction_input()将该GPIO管脚设置为输入模式。

int gpio_set_debounce(unsigned gpio, unsigneddebounce)

       gpio_set_debounce()设定抖动判定时间(微秒)。struct gpio_chip中有该函数指针。

void __gpio_set_value(unsigned gpio, intvalue)

static void _gpio_set_open_drain_value(unsignedgpio, struct gpio_chip *chip, int value)

static void_gpio_set_open_source_value(unsigned gpio, struct gpio_chip *chip, int value)

int __gpio_get_value(unsigned gpio)

       这些函数分别调用struct gpio_chip中的set和get函数指针去调用具体的函数。在设置输出模式的GPIO管脚的值时,既可以使用不同模式的设置函数(_gpio_set_open_drain_value()和_gpio_set_open_source_value()),也可以使用通用模式的设置函数__gpio_set_value()(该函数会根据全局数组gpio_desc中的标志位调用对应的设置函数)。

 

未完待续……