由于之后的触摸屏驱动分析中使用到了GPIO子系统和i2c子系统,因此在分析触摸屏驱动之前我准备把这两个子系统进行简单分析。

之前我们使用GPIO引脚的方式并不是推荐的方式,当我们更改某一bit时,很有可能导致另外的bit值发生更改。而GPIO子系统进行了封装,确保每次只对一个GPIO引脚操作,而不会影响到别的GPIO引脚。

下面这段代码是我从驱动程序中摘出来的,它首先获取GPIO引脚,之后设置为输出模式。

 ret = gpio_request(EXYNOS4_GPL0(), "TP1_EN");
if (ret) {
printk(KERN_ERR "failed to request TP1_EN for I2C control\n");
//return err;
} gpio_direction_output(EXYNOS4_GPL0(), ); s3c_gpio_cfgpin(EXYNOS4_GPL0(), S3C_GPIO_OUTPUT);
gpio_free(EXYNOS4_GPL0()); mdelay();

一、gpio_direction_output()分析

gpio_direction_output()方便我们操作GPIO引脚,我们来看一下gpio_direction_output()函数会做些什么。

 int gpio_direction_output(unsigned gpio, int value)
{
unsigned long flags;
struct gpio_chip *chip; /* 不同单板对应不同chip,由芯片厂实现 */
struct gpio_desc *desc = &gpio_desc[gpio];
int status = -EINVAL; spin_lock_irqsave(&gpio_lock, flags);
/* 判断gpio是否可用 */
if (!gpio_is_valid(gpio))
goto fail;
chip = desc->chip;
if (!chip || !chip->set || !chip->direction_output)
goto fail;
gpio -= chip->base;
if (gpio >= chip->ngpio)
goto fail;
status = gpio_ensure_requested(desc, gpio);
if (status < )
goto fail; /* now we know the gpio is valid and chip won't vanish */ spin_unlock_irqrestore(&gpio_lock, flags); might_sleep_if(chip->can_sleep); if (status) {
status = chip->request(chip, gpio);
if (status < ) {
pr_debug("GPIO-%d: chip request fail, %d\n",
chip->base + gpio, status);
/* and it's not available to anyone else ...
* gpio_request() is the fully clean solution.
*/
goto lose;
}
}
/* 调用chip的direction_output()函数 */
status = chip->direction_output(chip, gpio, value);
if (status == )
set_bit(FLAG_IS_OUT, &desc->flags);
trace_gpio_value(chip->base + gpio, , value);
trace_gpio_direction(chip->base + gpio, , status);
lose:
return status;
fail:
spin_unlock_irqrestore(&gpio_lock, flags);
if (status)
pr_debug("%s: gpio-%d status %d\n",
__func__, gpio, status);
return status;
}
EXPORT_SYMBOL_GPL(gpio_direction_output);

通过代码第4行struct gpio_chip,我们可以推断出此结构体是内核提供给各个板商的,用于其实现不同GPIO引脚操作。

在drivers/gpio/目录下,有跟平台相关的文件,这些文件就是由厂商提供的GPIO引脚操作函数。在此我以gpio-omap.c为例进行讨论

我们先来查看probe()函数:

 static int __devinit omap_gpio_probe(struct platform_device *pdev)
{
static int gpio_init_done;
struct omap_gpio_platform_data *pdata;
struct resource *res;
int id;
struct gpio_bank *bank;
...
pdata = pdev->dev.platform_data;
...
ret = init_gpio_info(pdev);
...
id = pdev->id;
bank = &gpio_bank[id]; res = platform_get_resource(pdev, IORESOURCE_IRQ, );
...
bank->irq = res->start;
bank->virtual_irq_start = pdata->virtual_irq_start;
bank->method = pdata->bank_type;
bank->dev = &pdev->dev;
bank->dbck_flag = pdata->dbck_flag;
bank->stride = pdata->bank_stride;
bank_width = pdata->bank_width; spin_lock_init(&bank->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, ); bank->base = ioremap(res->start, resource_size(res));
...
omap_gpio_mod_init(bank, id);
omap_gpio_chip_init(bank);
omap_gpio_show_rev(bank); if (!gpio_init_done)
gpio_init_done = ; return ;
}

此函数首先填充了struct gpio_bank,之后使用ioremap()映射,最后调用omap_gpio_chip_init()初始化并注册chip。

 static void __devinit omap_gpio_chip_init(struct gpio_bank *bank)
{
int j;
static int gpio;
/* 填充chip成员函数 */
bank->mod_usage = ;
bank->chip.request = omap_gpio_request;
bank->chip.free = omap_gpio_free;
bank->chip.direction_input = gpio_input;
bank->chip.get = gpio_get;
bank->chip.direction_output = gpio_output;
bank->chip.set_debounce = gpio_debounce;
bank->chip.set = gpio_set;
bank->chip.to_irq = gpio_2irq;
if (bank_is_mpuio(bank)) {
bank->chip.label = "mpuio";
#ifdef CONFIG_ARCH_OMAP16XX
bank->chip.dev = &omap_mpuio_device.dev;
#endif
bank->chip.base = OMAP_MPUIO();
} else {
bank->chip.label = "gpio";
bank->chip.base = gpio;
gpio += bank_width;
}
bank->chip.ngpio = bank_width; gpiochip_add(&bank->chip);
...
irq_set_chained_handler(bank->irq, gpio_irq_handler);
irq_set_handler_data(bank->irq, bank);
}

接下来我们来查看chip是如何注册到内核中的:

 int gpiochip_add(struct gpio_chip *chip)
{
unsigned long flags;
int status = ;
unsigned id;
int base = chip->base;
/* 判断gpio是否可用 */
if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - )) && base >= ) {
...
}
...
if (status == ) {
for (id = base; id < base + chip->ngpio; id++) {
gpio_desc[id].chip = chip; /* 放入全局变量中 */ gpio_desc[id].flags = !chip->direction_input
? ( << FLAG_IS_OUT)
: ;
}
}
...
unlock:
spin_unlock_irqrestore(&gpio_lock, flags); status = gpiochip_export(chip);
...
return status;
}
EXPORT_SYMBOL_GPL(gpiochip_add);

重新回到gpio_direction_output()函数,我们继续向下分析。代码第40行:chip->direction_output(chip, gpio, value);最终会调用chip的direction_output()函数。我们来看一下gpio-omap.c中的direction_output()函数是怎么实现的:

 static void _set_gpio_direction(struct gpio_bank *bank, int gpio, int is_input)
{
void __iomem *reg = bank->base;
u32 l; switch (bank->method) {
#ifdef CONFIG_ARCH_OMAP1
case METHOD_MPUIO:
reg += OMAP_MPUIO_IO_CNTL / bank->stride;
break;
#endif
#ifdef CONFIG_ARCH_OMAP15XX
case METHOD_GPIO_1510:
reg += OMAP1510_GPIO_DIR_CONTROL;
break;
#endif
#ifdef CONFIG_ARCH_OMAP16XX
case METHOD_GPIO_1610:
reg += OMAP1610_GPIO_DIRECTION;
break;
#endif
#if defined(CONFIG_ARCH_OMAP730) || defined(CONFIG_ARCH_OMAP850)
case METHOD_GPIO_7XX:
reg += OMAP7XX_GPIO_DIR_CONTROL;
break;
#endif
#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3)
case METHOD_GPIO_24XX:
reg += OMAP24XX_GPIO_OE;
break;
#endif
#if defined(CONFIG_ARCH_OMAP4)
case METHOD_GPIO_44XX:
reg += OMAP4_GPIO_OE;
break;
#endif
default:
WARN_ON();
return;
}
l = __raw_readl(reg);
if (is_input)
l |= << gpio;
else
l &= ~( << gpio);
__raw_writel(l, reg);
}

最终它会通过readl()和writel()等操作实现对寄存器的读写操作,对于这两个函数,读者可以参考:Linux驱动函数解读第四节。

二、内核中GPIO的使用函数

1. 检测GPIO引脚是否有效

bool gpio_is_valid(int number)

2. 获取或释放GPIO引脚。如果GPIO引脚正在使用,则无法获取。原理类似于原子操作

int gpio_request(unsigned gpio, const char *label)

void gpio_free(unsigned gpio)

3. 设置GPIO引脚为输入或输出

int gpio_direction_input(unsigned gpio)    /* 输入 */

int gpio_direction_output(unsigned gpio, int value)    /* 输出 */

4. 获取或设置GPIO引脚的值

int gpio_get_value(unsigned gpio)

void gpio_set_value(unsigned gpio, int value)

5. 设置GPIO引脚为中断引脚

int gpio_to_irq(unsigned gpio)    /* 返回值为request_irq()使用的中断号 */

6. 导出或撤销GPIO引脚到用户空间

int gpio_export(unsigned gpio, bool direction_may_change)
/* 参数direction_may_change表示用户程序是否可以修改GPIO的方向,
如果可以,则参数direction_may_change为真 */ void gpio_unexport(unsigned gpio) /* 撤销GPIO */

下面我们可以使用GPIO子系统优化第二章的LED程序。

三、LED驱动优化

led源代码:

 #include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/io.h> #include <asm/uaccess.h>
#include <asm/io.h> #include <plat/gpio-cfg.h> /* 定义文件内私有结构体 */
struct led_device {
struct cdev cdev;
int stat; /* 用于保存LED状态,0为灭,1为亮 */
}; static int g_major;
module_param(g_major, int, S_IRUGO); static struct led_device* dev;
static struct class* scls;
static struct device* sdev; /* LED write()函数 */
static ssize_t led_write(struct file *filep, const char __user * buf, size_t len, loff_t *ppos)
{
struct led_device *dev = filep->private_data; if (copy_from_user(&(dev->stat), buf, ))
return -EFAULT; if (dev->stat == )
gpio_set_value(EXYNOS4_GPK1(), );
else
gpio_set_value(EXYNOS4_GPK1(), ); return ;
} /* LED open()函数 */
static int led_open(struct inode *inodep, struct file *filep)
{
struct led_device *dev;
int ret = -; dev = container_of(inodep->i_cdev, struct led_device, cdev);
// 放入私有数据中
filep->private_data = dev; // 设为输出引脚,灭灯
ret = gpio_request(EXYNOS4_GPK1(), "LED3");
if (ret)
printk(KERN_ERR "failed to request LED3\n"); gpio_direction_output(EXYNOS4_GPK1(), );
s3c_gpio_cfgpin(EXYNOS4_GPK1(), S3C_GPIO_OUTPUT); gpio_set_value(EXYNOS4_GPK1(), ); return ;
} static int led_close(struct inode *inodep, struct file *filep)
{
gpio_free(EXYNOS4_GPK1()); return ;
} /* 把定义的函数接口集合起来,方便系统调用 */
static const struct file_operations led_fops = {
.write = led_write,
.open = led_open,
.release = led_close,
}; static int __init led_init(void)
{
int ret;
dev_t devt; /* 1. 申请设备号 */
if (g_major) {
devt = MKDEV(g_major, );
ret = register_chrdev_region(devt, , "led");
}
else {
ret = alloc_chrdev_region(&devt, , , "led");
g_major = MAJOR(devt);
}
if (ret)
return ret; /* 2. 申请文件内私有结构体 */
dev = kzalloc(sizeof(struct led_device), GFP_KERNEL);
if (dev == NULL) {
ret = -ENOMEM;
goto fail_malloc;
} /* 3. 注册字符设备驱动 */
cdev_init(&dev->cdev, &led_fops); /* 初始化cdev并链接file_operations和cdev */
ret = cdev_add(&dev->cdev, devt, ); /* 注册cdev */
if (ret)
return ret; /* 4. 创建类设备,insmod后会生成/dev/led设备文件 */
scls = class_create(THIS_MODULE, "led");
sdev = device_create(scls, NULL, devt, NULL, "led"); return ; fail_malloc:
unregister_chrdev_region(devt, ); return ret;
} static void __exit led_exit(void)
{
/* 镜像注销 */
dev_t devt = MKDEV(g_major, ); device_destroy(scls, devt);
class_destroy(scls); cdev_del(&(dev->cdev));
kfree(dev); unregister_chrdev_region(devt, );
} /* 声明段属性 */
module_init(led_init);
module_exit(led_exit); MODULE_LICENSE("GPL");

Makefile:

 KERN_DIR = /work/itop4412/tools/linux-3.5

 all:
make -C $(KERN_DIR) M=`pwd` modules clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order obj-m += led.o

测试文件:

 #include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h> int main(int argc, char** argv)
{
if (argc != ) {
printf("Usage: \n");
printf("%s <on|off>\n", argv[]);
return -;
} int fd;
fd = open("/dev/led", O_RDWR);
if (fd < ) {
printf("can't open /dev/led\n");
return -;
} char stat;
if ( == strcmp(argv[], "off")) {
stat = ;
write(fd, &stat, );
} else {
stat = ;
write(fd, &stat, );
}
close(fd); return ;
}

下一章  十四、i2c子系统

十三、GPIO子系统的更多相关文章

  1. Linux内核驱动之GPIO子系统API接口概述

    1.前言 在嵌入式Linux开发中,对嵌入式SoC中的GPIO进行控制非常重要,Linux内核中提供了GPIO子系统,驱动开发者在驱动代码中使用GPIO子系统提供的API函数,便可以达到对GPIO控制 ...

  2. 【linux】gpio子系统

    目录 前言 linux子系统 gpio子系统 gpio子系统实战-系统调用 前言 目前不涉及驱动源码 参考链接 linux子系统 在 Linux 系统中 绝大多数硬件设备都有非常成熟的驱动框架 驱动工 ...

  3. Linux内核驱动之GPIO子系统(一)GPIO的使用

    转自:http://blog.csdn.net/mirkerson/article/details/8464290 一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt , ...

  4. Linux内核驱动之GPIO子系统(一)GPIO的使用【转】

    转自:http://blog.csdn.net/tommy_wxie/article/details/9427047 一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt  ...

  5. Linux GPIO子系统

    一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt ,dma,timer一样)驱动程序,应用程序都能够通过相应的接口使用gpio,gpio使用0-MAX_INT之间的整数 ...

  6. gpio子系统和pinctrl子系统(下)

    情景分析 打算从两个角度来情景分析,先从bsp驱动工程师的角度,然后是驱动工程师的角度,下面以三星s3c6410 Pinctrl-samsung.c为例看看pinctrl输入参数的初始化过程(最开始的 ...

  7. gpio子系统和pinctrl子系统(中)

    pinctrl子系统核心实现分析 pinctrl子系统的内容在drivers/pinctrl文件夹下,主要文件有(建议先看看pinctrl内核文档Documentation/pinctrl.txt): ...

  8. gpio子系统和pinctrl子系统(上)

    前言 随着内核的发展,linux驱动框架在不断的变化.很早很早以前,出现了gpio子系统,后来又出现了pinctrl子系统.在网上很难看到一篇讲解这类子系统的文章.就拿gpio操作来说吧,很多时候都是 ...

  9. Linux驱动之GPIO子系统和pinctrl子系统

    前期知识   1.如何编写一个简单的Linux驱动(一)--驱动的基本框架   2.如何编写一个简单的Linux驱动(二)--设备操作集file_operations   3.如何编写一个简单的Lin ...

随机推荐

  1. python3删除mysql上月分区数据(脚本)

    import datetime import pymysql import pymysql.cursors tables_schdule=["talbe1","table ...

  2. VMware Workstation虚拟机打开系统时,提示“无法打开内核设备“\\.\Global\vmx86”: 系统找不到指定的文件。是否在安装 VMware Workstation 后重新引导?”

    VMware Workstation虚拟机打开系统时,提示“无法打开内核设备“\\.\Global\vmx86”: 系统找不到指定的文件.是否在安装 VMware Workstation 后重新引导? ...

  3. Python Log Viewer

    https://pythonhosted.org/logview/

  4. C# 将文本写入到文件

    将字符串数组写入到文件,每个元素为一行 string[] lines = { "First line", "Second line", "Third ...

  5. ISO/IEC 9899:2011 条款6.4.8——预处理数字

    6.4.8 预处理数字 语法 1.pp-number: digit .    digit pp-number    digit pp-number    identifier-nondigit pp- ...

  6. Unix/Linux系统下获得时间戳函数

    在Unix/Linux系统下,使用gettimeofday函数来获得当前系统的时间戳,精度可达到微秒(microsecond,即μs)级别. 通过结构体timeval来存放当前时间戳的信息: #ifn ...

  7. PHP连接MySQL数据库的三种方式(mysql、mysqli、pdo)--续

    2.PHP与Mysqli扩展,面向过程.对象 <?php $mysql_conf = array( 'host' => '127.0.0.1:3306', 'db' => 'test ...

  8. 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_14-认证接口开发-需求分析

    4 认证接口开发 4.1 需求分析 用户登录的流程图如下: 执行流程: 1.用户登录,请求认证服务 2.认证服务认证通过,生成jwt令牌,将jwt令牌及相关信息写入Redis,并且将身份令牌写入coo ...

  9. Qt pri文件

    pri文件就是一个简单的文件夹包含或者动态库调用路径等说明,在pro文件里include了pri文件,相当于把pri文件的内容直接复制到pro文件中

  10. Spring事务的隔离级别和传播机制

    七个传播机制:(红色字体的代表如果不设置传播机制时候默认的)PROPAGATION_REQUIRED-支持当前事务;如果不存在,创建一个新的. PROPAGATION_SUPPORTS-支持当前事务; ...