十三、GPIO子系统
由于之后的触摸屏驱动分析中使用到了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子系统的更多相关文章
- Linux内核驱动之GPIO子系统API接口概述
1.前言 在嵌入式Linux开发中,对嵌入式SoC中的GPIO进行控制非常重要,Linux内核中提供了GPIO子系统,驱动开发者在驱动代码中使用GPIO子系统提供的API函数,便可以达到对GPIO控制 ...
- 【linux】gpio子系统
目录 前言 linux子系统 gpio子系统 gpio子系统实战-系统调用 前言 目前不涉及驱动源码 参考链接 linux子系统 在 Linux 系统中 绝大多数硬件设备都有非常成熟的驱动框架 驱动工 ...
- Linux内核驱动之GPIO子系统(一)GPIO的使用
转自:http://blog.csdn.net/mirkerson/article/details/8464290 一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt , ...
- Linux内核驱动之GPIO子系统(一)GPIO的使用【转】
转自:http://blog.csdn.net/tommy_wxie/article/details/9427047 一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt ...
- Linux GPIO子系统
一 概述 Linux内核中gpio是最简单,最常用的资源(和 interrupt ,dma,timer一样)驱动程序,应用程序都能够通过相应的接口使用gpio,gpio使用0-MAX_INT之间的整数 ...
- gpio子系统和pinctrl子系统(下)
情景分析 打算从两个角度来情景分析,先从bsp驱动工程师的角度,然后是驱动工程师的角度,下面以三星s3c6410 Pinctrl-samsung.c为例看看pinctrl输入参数的初始化过程(最开始的 ...
- gpio子系统和pinctrl子系统(中)
pinctrl子系统核心实现分析 pinctrl子系统的内容在drivers/pinctrl文件夹下,主要文件有(建议先看看pinctrl内核文档Documentation/pinctrl.txt): ...
- gpio子系统和pinctrl子系统(上)
前言 随着内核的发展,linux驱动框架在不断的变化.很早很早以前,出现了gpio子系统,后来又出现了pinctrl子系统.在网上很难看到一篇讲解这类子系统的文章.就拿gpio操作来说吧,很多时候都是 ...
- Linux驱动之GPIO子系统和pinctrl子系统
前期知识 1.如何编写一个简单的Linux驱动(一)--驱动的基本框架 2.如何编写一个简单的Linux驱动(二)--设备操作集file_operations 3.如何编写一个简单的Lin ...
随机推荐
- python3删除mysql上月分区数据(脚本)
import datetime import pymysql import pymysql.cursors tables_schdule=["talbe1","table ...
- VMware Workstation虚拟机打开系统时,提示“无法打开内核设备“\\.\Global\vmx86”: 系统找不到指定的文件。是否在安装 VMware Workstation 后重新引导?”
VMware Workstation虚拟机打开系统时,提示“无法打开内核设备“\\.\Global\vmx86”: 系统找不到指定的文件.是否在安装 VMware Workstation 后重新引导? ...
- Python Log Viewer
https://pythonhosted.org/logview/
- C# 将文本写入到文件
将字符串数组写入到文件,每个元素为一行 string[] lines = { "First line", "Second line", "Third ...
- ISO/IEC 9899:2011 条款6.4.8——预处理数字
6.4.8 预处理数字 语法 1.pp-number: digit . digit pp-number digit pp-number identifier-nondigit pp- ...
- Unix/Linux系统下获得时间戳函数
在Unix/Linux系统下,使用gettimeofday函数来获得当前系统的时间戳,精度可达到微秒(microsecond,即μs)级别. 通过结构体timeval来存放当前时间戳的信息: #ifn ...
- PHP连接MySQL数据库的三种方式(mysql、mysqli、pdo)--续
2.PHP与Mysqli扩展,面向过程.对象 <?php $mysql_conf = array( 'host' => '127.0.0.1:3306', 'db' => 'test ...
- 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_14-认证接口开发-需求分析
4 认证接口开发 4.1 需求分析 用户登录的流程图如下: 执行流程: 1.用户登录,请求认证服务 2.认证服务认证通过,生成jwt令牌,将jwt令牌及相关信息写入Redis,并且将身份令牌写入coo ...
- Qt pri文件
pri文件就是一个简单的文件夹包含或者动态库调用路径等说明,在pro文件里include了pri文件,相当于把pri文件的内容直接复制到pro文件中
- Spring事务的隔离级别和传播机制
七个传播机制:(红色字体的代表如果不设置传播机制时候默认的)PROPAGATION_REQUIRED-支持当前事务;如果不存在,创建一个新的. PROPAGATION_SUPPORTS-支持当前事务; ...