由于之后的触摸屏驱动分析中使用到了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子系统

最新文章

  1. 6.Linux的文件权限与目录配置
  2. php高级
  3. WPF学习之路(十一)布局(续)
  4. BZOJ3979 : [WF2012]infiltration
  5. JavaScript密码复杂度
  6. 锋利的jQuery-1--jQuery对象和DOM对象以及相互转化
  7. Python strip、lstrip和rstrip的用法
  8. FZU 2105 Digits Count(线段树)
  9. busybox reboot 无效
  10. 【转】linux Centos 6.5 安装桌面环境GNOME
  11. ViewPager实现启动引导页面(个人认为很详细)
  12. nmap -- write a nmap script
  13. DNA比对算法:BWT
  14. ALTER TABLE SWITCH&#39; statement failed. The table x&#39; is partitioned while index &#39;x&#39; is not partitioned.
  15. left join 连表时,on后多条件无效问题
  16. Winform DevExpress控件库(三) 使用NavBarControl控件定制导航栏
  17. SSM连接数据库自动生成问题
  18. Java面向对象程序设计的六大基本原则
  19. zoj3471 状态压缩dp基础
  20. s2第二章深入c#类型

热门文章

  1. [TJOI2019]甲苯先生的字符串——矩阵乘法+递推
  2. 深入理解JVM虚拟机5:虚拟机字节码执行引擎
  3. docker-compose EFK查看docker及容器的日志
  4. Ionic4.x Javascript 扩展 ActionSheet Alert Toast Loading 以及 ionic 手势相 关事件
  5. ISO/IEC 9899:2011 条款6.2.4——对象的存储持久性
  6. webdriver报不可见元素异常方法总结
  7. 03--STL算法(常用算法)
  8. (五)AJAX技术
  9. EasyNetQ使用(八)【对延迟消息插件的支持,自动订阅者】
  10. CentOS 7启动与切换图形界面