一,GPIO介绍
GPIO(Generic Purpose Input/Output port),即通用输入输出端口。gpio提供了通用的引脚输入输出和特定的功能,可以实现引脚级别的输入输出控制,多个引脚组合在一起,也可以实现byte/word等并行传输接口。一般在嵌入式系统中使用的比较多,用来接入一些比较简单的外部设备和外部电路到CPU。因为gpio芯片寄存器直接映射到CPU IO端口或IO内存,所以直接写入/读取CPU IO端口或IO内存,就能控制/读取gpio引脚的状态。相较于I2C设备(主从设计结构,主控设备需要通过总线跟从设备通讯,具有速率限制),gpio基本没有延迟,效率要高很多。同时,GPIO还可以通过特殊功能,接入其他总线,如I2C等。
二,s3c24xx GPIO芯片寄存器
根据平台的不同,gpio主控芯片的数量也不同,主控芯片的寄存器端口映射方式和映射的地址也都不同,这些细节是平台相关的,需要平台去适配。当然,不同gpio芯片的寄存器数量和功能也不尽相同。一般情况下,gpio芯片都要提供两个寄存器,一个是配置(控制)寄存器,一个是数据寄存器。
s3c24xx平台提供了12个gpio芯片,分别为GPIOA,GPIOB,GPIOC,GPIOD,GPIOE,GPIOF,GPIOG,GPIOH,GPIOJ,GPIOK,GPIOL,GPIOM。其中。每个gpio芯片至少提供了2个32位的寄存器:配置寄存器GPXCON,数据寄存器GPXDAT。除了GPIOA,其他gpio芯片还提供了上拉电阻寄存器GPXUP。
配置寄存器的作用是,用来配置该芯片的每根引脚的输入输出状态或者特殊功能等。数据寄存器用于数据的读写。上拉电阻寄存器一般用于缺省/空闲状态的预设值。
GPIOA的配置寄存器GPACON不同于其他gpio芯片的配置寄存器:使用1bit控制芯片的相应引脚状态,32bit寄存器使用低23bit控制23个引脚。当控制位值为0的时候,表示该引脚是通用输出状态;控制位为1的时候,引脚是特殊功能状态,用于控制其他外接器件(此时,数据寄存器GPADAT无效)。因而,可以看出,GPIOA芯片不能实现通用输入功能。一般来说,GPIOA控制寄存器配置成1,用于控制其他外接器件,如I2C等。
GPIOB~GPIOM的配置寄存器GPBCON~GPMCON:使用2bit控制芯片的相应引脚状态(bit0,1控制引脚0,bit2,3控制引脚1,以此类推),每个芯片提供的引脚个数不尽相同,但最多只能提供32/2=16个引脚。当控制位为00的时候,表示相应引脚处于通用输入状态;控制位为01时,表示相应引脚为通用输出状态;控制位为10时,表示相应引脚为特殊功能状态;控制位为11预留。
当配置寄存器配置为输入/输出状态时,可以通过读取/写入相应的数据寄存器来实现数据的输入/输出。
三,Kernel中的gpiolib架构
文件:drivers/gpio/gpiolib.c
Linux kernel提供了一套gpio操作架构:gpiolib,用来屏蔽底层各平台gpio硬件细节上的差异。lib实现平台无关的的管理,并向上提供一套接口供用户使用。各平台不同的硬件细节在系统初始化的过程中由各平台厂家具体实现。
我们知道,每个平台配置多个gpio芯片,而每个gpio芯片又管理着多个引脚,因此gpiolib的管理结构也是清晰明了。使用一个双向全局链表管理平台中所有的gpio芯片实例gpio_chip,每个gpio_chip中又含有引脚描述符数组gpio_desc[]。并提供了增加/移除gpio芯片,request/free引脚,get/set配置寄存器,get/set引脚值等接口,供系统初始化和驱动层调用。
1,gpio芯片全局链表定义
LIST_HEAD(gpio_chips);
2,gpio_chip定义
struct gpio_chip {
const char *label; // chip标签
struct device *dev; // 设备框架相关
struct device *cdev; // 关联的字符设备
struct module *owner;
struct list_head list; // 链接到全局gpio_chips
// 以下是芯片硬件相关的操作函数,需要平台适配
int (*request)(struct gpio_chip *chip,
unsigned offset);
void (*free)(struct gpio_chip *chip,
unsigned offset);
int (*get_direction)(struct gpio_chip *chip,
unsigned offset);
int (*direction_input)(struct gpio_chip *chip,
unsigned offset);
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value);
int (*get)(struct gpio_chip *chip,
unsigned offset);
void (*set)(struct gpio_chip *chip,
unsigned offset, int value);
void (*set_multiple)(struct gpio_chip *chip,
unsigned long *mask,
unsigned long *bits);
int (*set_debounce)(struct gpio_chip *chip,
unsigned offset,
unsigned debounce);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
int base; // 该gpio芯片起始引脚在整个平台上的编号
u16 ngpio; // 该gpio芯片的引脚个数
struct gpio_desc *desc; // 该gpio芯片所有引脚描述符数组
const char *const *names;
bool can_sleep;
bool irq_not_threaded;
#ifdef CONFIG_GPIOLIB_IRQCHIP
/*
* With CONFIG_GPIOLIB_IRQCHIP we get an irqchip inside the gpiolib
* to handle IRQs for most practical cases.
*/
struct irq_chip *irqchip;
struct irq_domain *irqdomain;
unsigned int irq_base;
irq_flow_handler_t irq_handler;
unsigned int irq_default_type;
int irq_parent;
struct lock_class_key *lock_key;
#endif
#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
};
说明:gpio_chip中封装的重要信息如下:芯片引脚起始编号,芯片引脚个数,芯片下挂的所有引脚的描述符,以及各个引脚请求/释放/配置/值读写等操作。
3,gpio_desc定义
struct gpio_desc {
struct gpio_chip *chip; // 所属的gpio_chip
unsigned long flags; // 标识,方向/低电平激活/是否已经被请求/中断请求等等
/* flag symbols are bit numbers */
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_EXPORT 2 /* protected by sysfs_lock */
#define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */
#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 FLAG_IS_HOGGED 11 /* GPIO is hogged */
/* Connection label */
const char *label; // 标签
/* Name of the GPIO */
const char *name; // 引脚名称
};
4,管理/操作接口
(1)gpiochip_add(struct gpio_chip *chip)
平台设备初始化的过程中,调用该函数在全局链表gpio_chips中注册一个gpio_chip。平台在初始化过程中,需要填写gpio_chip中的base,ngpio字段,注册接口动态分配所有的引脚描述符数组并简单初始化,然后将该gpio_chip按照 base从小到大的顺序注册进全局链表gpio_chips中。注册完成之后,每个引脚描述符此时都还处于未初始化的状态(比如,方向未初始化,但是初始都是未被请求状态),直到某个驱动在请求该引脚时,主动设置该引脚状态。
(2)gpiochip_remove(struct gpio_chip *chip)
从设备平台全局链表移除gpio芯片。
(3)struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *chip, u16 hwnum, const char *label)
请求指定的gpio_chip下编号未hwnum的引脚,并在请求成功时,设置该引脚标签。该接口首先检查请求的引脚描述符chip_desc->flag是否已经被requested,如果没有设置chip_desc->flag为requested,并设置标签。如果该gpio芯片定义了request操作方法,则调用该方法,执行硬件相关的操作请求。
(4)void gpiochip_free_own_desc(struct gpio_desc *desc)
释放指定的gpio引脚。
(5)int gpiod_direction_input(struct gpio_desc *desc)
设置引脚方向为输入。调用gpio_chip芯片实际硬件相关的direction_input方法,设置配置寄存器,配置相应引脚方向为输入。
(6)int gpiod_direction_output_raw(struct gpio_desc *desc, int value)
设置引脚方向为输出,并输出值value(高电平/低电平)。调用gpio_chip芯片实际硬件相关的direction_output方法,设置配置寄存器,配置相应引脚方向为输出,并输出value到数据寄存器。当然,还会处理 FLAG_ACTIVE_LOW, FLAG_OPEN_DRAIN, FLAG_OPEN_SOURCE等标识。
(7)void gpiod_set_value(struct gpio_desc *desc, int value)
设置引脚输出值为value。调用gpio_chip芯片实际硬件相关的set方法,写value到数据寄存器。
(8)int gpiod_get_value(const struct gpio_desc *desc)
获取引脚输入值。调用gpio_chip芯片实际硬件相关的get方法,从数据寄存器获取value。
四,s3c24xx gpio平台初始化及操作接口
文件路径:drivers/gpio/gpio-samsung.c
以s3c24xx平台为例,分析gpio的设备初始化和操作接口。
(1)平台硬件相关gpio芯片结构定义
struct samsung_gpio_chip {
struct gpio_chip chip; // 内嵌平台无关gpio_chip结构
struct samsung_gpio_cfg *config; // 配置方法,主要是set_config和get_config方法
struct samsung_gpio_pm *pm; // 电源管理模块相关操作方法
void __iomem *base; // gpio芯片寄存器地址
int irq_base; // 中断号
int group;
spinlock_t lock;
#ifdef CONFIG_PM
u32 pm_save[4];
#endif
u32 bitmap_gpio_int;
};
说明:该结构体中最重要的字段是gpio_chip,config和base。
(2)平台gpio芯片配置
struct samsung_gpio_chip s3c24xx_gpios[] = {
#ifdef CONFIG_PLAT_S3C24XX
// 对GPIOA,配置寄存器1bit对应一个引脚。因此其配置寄存器的操作方法需要特殊配置。
{
.config = &s3c24xx_gpiocfg_banka, // 配置banka的set_config和get_config,1bit操作
.chip = {
.base = S3C2410_GPA(0), // 相对于平台的引脚编号,从0开始
.owner = THIS_MODULE,
.label = "GPIOA",
.ngpio = 27,
.direction_input = s3c24xx_gpiolib_banka_input, // 配置banka的set_direction_input,1bit操作
.direction_output = s3c24xx_gpiolib_banka_output, // 配置banka的set_direction_output,1bit操作
},
}, {
// 未设置配置寄存器操作方法 .config,在后续初始过程中会设置默认2bit操作方法。
.chip = {
.base = S3C2410_GPB(0), //
.owner = THIS_MODULE,
.label = "GPIOB",
.ngpio = 11,
},
}, {
.chip = {
.base = S3C2410_GPC(0),
.owner = THIS_MODULE,
.label = "GPIOC",
.ngpio = 16,
},
}, {
.chip = {
.base = S3C2410_GPD(0),
.owner = THIS_MODULE,
.label = "GPIOD",
.ngpio = 16,
},
}, {
.chip = {
.base = S3C2410_GPE(0),
.label = "GPIOE",
.owner = THIS_MODULE,
.ngpio = 16,
},
}, {
.chip = {
.base = S3C2410_GPF(0),
.owner = THIS_MODULE,
.label = "GPIOF",
.ngpio = 8,
.to_irq = s3c24xx_gpiolib_fbank_to_irq,
},
}, {
.irq_base = IRQ_EINT8,
.chip = {
.base = S3C2410_GPG(0),
.owner = THIS_MODULE,
.label = "GPIOG",
.ngpio = 16,
.to_irq = samsung_gpiolib_to_irq,
},
}, {
.chip = {
.base = S3C2410_GPH(0),
.owner = THIS_MODULE,
.label = "GPIOH",
.ngpio = 15,
},
},
/* GPIOS for the S3C2443 and later devices. */
{
.base = S3C2440_GPJCON,
.chip = {
.base = S3C2410_GPJ(0),
.owner = THIS_MODULE,
.label = "GPIOJ",
.ngpio = 16,
},
}, {
.base = S3C2443_GPKCON,
.chip = {
.base = S3C2410_GPK(0),
.owner = THIS_MODULE,
.label = "GPIOK",
.ngpio = 16,
},
}, {
.base = S3C2443_GPLCON,
.chip = {
.base = S3C2410_GPL(0),
.owner = THIS_MODULE,
.label = "GPIOL",
.ngpio = 15,
},
}, {
.base = S3C2443_GPMCON,
.chip = {
.base = S3C2410_GPM(0),
.owner = THIS_MODULE,
.label = "GPIOM",
.ngpio = 2,
},
},
#endif
};
(3)平台gpio设备初始化过程
static __init int samsung_gpiolib_init(void)
{
/*
* Currently there are two drivers that can provide GPIO support for
* Samsung SoCs. For device tree enabled platforms, the new
* pinctrl-samsung driver is used, providing both GPIO and pin control
* interfaces. For legacy (non-DT) platforms this driver is used.
*/
if (of_have_populated_dt())
return -ENODEV;
samsung_gpiolib_set_cfg(samsung_gpio_cfgs, ARRAY_SIZE(samsung_gpio_cfgs));
if (soc_is_s3c24xx()) {
s3c24xx_gpiolib_add_chips(s3c24xx_gpios,
ARRAY_SIZE(s3c24xx_gpios), S3C24XX_VA_GPIO); // 注册平台下所有gpio芯片,S3C24XX_VA_GPIO是GPIO芯片寄存器的虚拟地址基址。
} else if (soc_is_s3c64xx()) {
samsung_gpiolib_add_2bit_chips(s3c64xx_gpios_2bit,
ARRAY_SIZE(s3c64xx_gpios_2bit),
S3C64XX_VA_GPIO + 0xE0, 0x20);
samsung_gpiolib_add_4bit_chips(s3c64xx_gpios_4bit,
ARRAY_SIZE(s3c64xx_gpios_4bit),
S3C64XX_VA_GPIO);
samsung_gpiolib_add_4bit2_chips(s3c64xx_gpios_4bit2,
ARRAY_SIZE(s3c64xx_gpios_4bit2));
} else {
WARN(1, "Unknown SoC in gpio-samsung, no GPIOs added\n");
return -ENODEV;
}
return 0;
}
core_initcall(samsung_gpiolib_init);
说明:在系统启动的core_initcall阶段,调用samsung_gpiolib_init。然后注册平台下所有gpio芯片。
(4)s3c24xx_gpiolib_add_chips
static void __init s3c24xx_gpiolib_add_chips(struct samsung_gpio_chip *chip,
int nr_chips, void __iomem *base)
{
int i;
struct gpio_chip *gc = &chip->chip;
for (i = 0 ; i < nr_chips; i++, chip++) {
/* skip banks not present on SoC */
if (chip->chip.base >= S3C_GPIO_END)
continue;
if (!chip->config)
chip->config = &s3c24xx_gpiocfg_default; /* 若果没有设置config,默认是2bit配置操作。对GPIOA,已经显示配置成s3c24xx_gpiocfg_banka */
if (!chip->pm)
chip->pm = __gpio_pm(&samsung_gpio_pm_2bit);
if ((base != NULL) && (chip->base == NULL))
chip->base = base + ((i) * 0x10); // GPIO芯片寄存器基址,虚拟地址
if (!gc->direction_input)
gc->direction_input = samsung_gpiolib_2bit_input; // 平台相关配置函数,默认2bit配置操作
if (!gc->direction_output)
gc->direction_output = samsung_gpiolib_2bit_output; // 平台相关配置函数,默认2bit配置操作
samsung_gpiolib_add(chip); // 平台gpio芯片注册包装函数
}
}
(5)平台gpio芯片注册
static void __init samsung_gpiolib_add(struct samsung_gpio_chip *chip)
{
struct gpio_chip *gc = &chip->chip;
int ret;
BUG_ON(!chip->base);
BUG_ON(!gc->label);
BUG_ON(!gc->ngpio);
spin_lock_init(&chip->lock);
if (!gc->direction_input)
gc->direction_input = samsung_gpiolib_2bit_input;
if (!gc->direction_output)
gc->direction_output = samsung_gpiolib_2bit_output;
if (!gc->set)
gc->set = samsung_gpiolib_set; // 设置引脚值设置方法
if (!gc->get)
gc->get = samsung_gpiolib_get; // 设置引脚值获取方法
#ifdef CONFIG_PM
if (chip->pm != NULL) {
if (!chip->pm->save || !chip->pm->resume)
pr_err("gpio: %s has missing PM functions\n",
gc->label);
} else
pr_err("gpio: %s has no PM function\n", gc->label);
#endif
/* gpiochip_add() prints own failure message on error. */
ret = gpiochip_add(gc); // 注册到gpiolib中
if (ret >= 0)
s3c_gpiolib_track(chip);
}
(6)平台配置/操作方法:
struct samsung_gpio_cfg s3c24xx_gpiocfg_default = {
.set_config = samsung_gpio_setcfg_2bit, // GPIOB~GPIOM使用2bit配置方法
.get_config = samsung_gpio_getcfg_2bit, // GPIOB~GPIOM使用2bit配置方法
};
#ifdef CONFIG_PLAT_S3C24XX
static struct samsung_gpio_cfg s3c24xx_gpiocfg_banka = {
.set_config = s3c24xx_gpio_setcfg_abank, // GPIOA使用1bit配置方法
.get_config = s3c24xx_gpio_getcfg_abank, // GPIOA使用1bit配置方法
};
#endif
samsung_gpiolib_set // 数据寄存器写
samsung_gpiolib_get // 数据寄存器读
(7)配置/数据寄存器读写实现
简单看一下samsung_gpiolib_2bit_input配置寄存器读取实现:
static int samsung_gpiolib_2bit_input(struct gpio_chip *chip, unsigned offset)
{
struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip);
void __iomem *base = ourchip->base; // 寄存器基址,虚拟地址
unsigned long flags;
unsigned long con;
samsung_gpio_lock(ourchip, flags);
con = __raw_readl(base + 0x00); // base+0x00 表示gpio配置寄存器
con &= ~(3 << (offset * 2)); // 配置寄存器对应2bit清零,表示相应的引脚是输入方向
__raw_writel(con, base + 0x00);
samsung_gpio_unlock(ourchip, flags);
return 0;
}
再看一下数据寄存器写入实现实现:
static void samsung_gpiolib_set(struct gpio_chip *chip,
unsigned offset, int value)
{
struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip);
void __iomem *base = ourchip->base;
unsigned long flags;
unsigned long dat;
samsung_gpio_lock(ourchip, flags);
dat = __raw_readl(base + 0x04); // base + 0x04是数据寄存器地址
dat &= ~(1 << offset); // 数据寄存器1位对应一个引脚
if (value)
dat |= 1 << offset;
__raw_writel(dat, base + 0x04);
samsung_gpio_unlock(ourchip, flags);
}
五,gpio mouse驱动实例分析:
文件路径:driver/input/mouse/gpio_mouse.c
kernel提供了一个基于gpio的鼠标驱动,下面简单分析一下该驱动的实现。
1,数据结构
#define GPIO_MOUSE_PIN_UP 0
#define GPIO_MOUSE_PIN_DOWN 1
#define GPIO_MOUSE_PIN_LEFT 2
#define GPIO_MOUSE_PIN_RIGHT 3
#define GPIO_MOUSE_PIN_BLEFT 4
#define GPIO_MOUSE_PIN_BMIDDLE 5
#define GPIO_MOUSE_PIN_BRIGHT 6
#define GPIO_MOUSE_PIN_MAX 7
struct gpio_mouse_platform_data {
int scan_ms;
int polarity;
union {
struct {
int up;
int down;
int left;
int right;
int bleft;
int bmiddle;
int bright;
};
int pins[GPIO_MOUSE_PIN_MAX]; // 平台鼠标使用的gpio引脚
};
};
说明:鼠标的方向(上下左右)和击键(左中右)7个事件通过gpio的7个引脚输入。
2,源码分析:
(1)驱动probe函数:
static int gpio_mouse_probe(struct platform_device *pdev)
{
struct gpio_mouse_platform_data *pdata = dev_get_platdata(&pdev->dev); /* 系统初始化,注册设备时,确定好各引脚连线 */
struct input_polled_dev *input_poll;
struct input_dev *input;
int pin, i;
int error;
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
error = -ENXIO;
goto out;
}
if (pdata->scan_ms < 0) {
dev_err(&pdev->dev, "invalid scan time\n");
error = -EINVAL;
goto out;
}
for (i = 0; i < GPIO_MOUSE_PIN_MAX; i++) {
pin = pdata->pins[i];
if (pin < 0) {
if (i <= GPIO_MOUSE_PIN_RIGHT) {
/* Mouse direction is required. */
dev_err(&pdev->dev,
"missing GPIO for directions\n");
error = -EINVAL;
goto out_free_gpios;
}
if (i == GPIO_MOUSE_PIN_BLEFT)
dev_dbg(&pdev->dev, "no left button defined\n");
} else {
error = gpio_request(pin, "gpio_mouse"); // 请求配置好的gpio引脚
if (error) {
dev_err(&pdev->dev, "fail %d pin (%d idx)\n",
pin, i);
goto out_free_gpios;
}
gpio_direction_input(pin); // 设置gpio引脚方向为输入
}
}
input_poll = input_allocate_polled_device();
if (!input_poll) {
dev_err(&pdev->dev, "not enough memory for input device\n");
error = -ENOMEM;
goto out_free_gpios;
}
platform_set_drvdata(pdev, input_poll);
/* set input-polldev handlers */
input_poll->private = pdata;
input_poll->poll = gpio_mouse_scan;
input_poll->poll_interval = pdata->scan_ms;
input = input_poll->input;
input->name = pdev->name;
input->id.bustype = BUS_HOST;
input->dev.parent = &pdev->dev;
input_set_capability(input, EV_REL, REL_X);
input_set_capability(input, EV_REL, REL_Y);
if (pdata->bleft >= 0)
input_set_capability(input, EV_KEY, BTN_LEFT);
if (pdata->bmiddle >= 0)
input_set_capability(input, EV_KEY, BTN_MIDDLE);
if (pdata->bright >= 0)
input_set_capability(input, EV_KEY, BTN_RIGHT);
error = input_register_polled_device(input_poll); // 注册进input子系统
if (error) {
dev_err(&pdev->dev, "could not register input device\n");
goto out_free_polldev;
}
dev_dbg(&pdev->dev, "%d ms scan time, buttons: %s%s%s\n",
pdata->scan_ms,
pdata->bleft < 0 ? "" : "left ",
pdata->bmiddle < 0 ? "" : "middle ",
pdata->bright < 0 ? "" : "right");
return 0;
out_free_polldev:
input_free_polled_device(input_poll);
out_free_gpios:
while (--i >= 0) {
pin = pdata->pins[i];
if (pin)
gpio_free(pin);
}
out:
return error;
}
(2)引脚扫描处理
static void gpio_mouse_scan(struct input_polled_dev *dev)
{
struct gpio_mouse_platform_data *gpio = dev->private;
struct input_dev *input = dev->input;
int x, y;
if (gpio->bleft >= 0)
input_report_key(input, BTN_LEFT,
gpio_get_value(gpio->bleft) ^ gpio->polarity); // 读取左按键值,并上报input子系统
if (gpio->bmiddle >= 0)
input_report_key(input, BTN_MIDDLE,
gpio_get_value(gpio->bmiddle) ^ gpio->polarity); // 读取中按键值,并上报input子系统
if (gpio->bright >= 0)
input_report_key(input, BTN_RIGHT,
gpio_get_value(gpio->bright) ^ gpio->polarity); // 读取右按键值,并上报input子系统
// 下面读取上下左右方向键
x = (gpio_get_value(gpio->right) ^ gpio->polarity)
- (gpio_get_value(gpio->left) ^ gpio->polarity);
y = (gpio_get_value(gpio->down) ^ gpio->polarity)
- (gpio_get_value(gpio->up) ^ gpio->polarity);
input_report_rel(input, REL_X, x);
input_report_rel(input, REL_Y, y);
input_sync(input);
}
六,总结
GPIO框架还是比较简单的(当然,这里只分析了最核心的功能,对于上/下拉电阻,gpio引脚中断等没有作深入分析),其核心思想永远都是:gpiolib框架抽象出一套平台无关的架构和接口,具体的实现由各平台去实现。如此,对上层驱动设备驱动来说,只需要使用gpiolib提供的平台无关接口实现外设本身驱动功能,而无需关系平台硬件细节。