【Linux基础系列之】gpio系统
来源:互联网 发布:恐怖黎明物品数据官网 编辑:程序博客网 时间:2024/05/17 04:10
上一章介绍了pinctrl系统管理所有的物理pin脚,gpio也是pin脚的一种,所以需要某个gpio的时候就需要通过pinctrl把某个pin脚设置为gpio功能就即可;在pinctrl系统提供了操作pin脚的统一API接口,同时也可以用gpio的API来操作,在request gpio表明该pin没有被挪为他用之后,就可以设置该GPIO的输入输出,驱动能力,或者debounce功能等;
(一) GPIO 初始化
pinctrl有讲到一个pinctrl可以管理着个gpio range链表,每个node管理着一定范围内的gpio,可以叫作gpio bank,我们把它实例话成一个设备,用gpio_chip来表示,下面为各个结构关系图:
通过gpiochip_add()将gpio chip添加到gpio子系统中:
299 int gpiochip_add(struct gpio_chip *chip) 300 { 301 unsigned long flags; 302 int status = 0; 303 unsigned id; 304 int base = chip->base; //这个chip处理的第一个gpio number; 305 struct gpio_desc *descs; 306 307 descs = kcalloc(chip->ngpio, sizeof(descs[0]), GFP_KERNEL); 308 if (!descs) 309 return -ENOMEM; 310 311 spin_lock_irqsave(&gpio_lock, flags); 312 313 if (base < 0) { //如果没有指定base,那么需要基于该chip的gpio数量在系统支持的gpio范围里找一段区间给该chip; //一般平台默认为512个gpio,通过扫描gpio_chips全局chip链表,分配一段free的gpio空间; 314 base = gpiochip_find_base(chip->ngpio); //ngpio为该chip处理的gpio个数; 315 if (base < 0) { 316 status = base; 317 spin_unlock_irqrestore(&gpio_lock, flags); 318 goto err_free_descs; 319 } 320 chip->base = base; 321 } 322 //到这里的时候,说明一切正常,把它加入到全局的gpiochip链表中去,注意,加入的时候会基于base排序; 323 status = gpiochip_add_to_list(chip); 324 if (status) { 325 spin_unlock_irqrestore(&gpio_lock, flags); 326 goto err_free_descs; 327 } 328 //如果加入成功,最后一步就是初始化该chip对应的那些gpio了; 329 for (id = 0; id < chip->ngpio; id++) { 330 struct gpio_desc *desc = &descs[id]; //每个gpio分配一个gpio_desc来描述; 331 332 desc->chip = chip; 333 //默认指定为输入为好,考虑到大多数情况下推挽式输出,防止漏电; 340 desc->flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0; 341 } 342 343 chip->desc = descs; 344 345 spin_unlock_irqrestore(&gpio_lock, flags); 346 347 #ifdef CONFIG_PINCTRL 348 INIT_LIST_HEAD(&chip->pin_ranges); 349 #endif 350 351 if (!chip->owner && chip->dev && chip->dev->driver) 352 chip->owner = chip->dev->driver->owner; 353 354 status = gpiochip_set_desc_names(chip); 355 if (status) 356 goto err_remove_from_list; 357 358 status = of_gpiochip_add(chip); // 初始化设备树相关的信息; 359 if (status) 360 goto err_remove_chip; 361 //添加chip到ACPI(Advanced Configuration & Power Interface.) 362 acpi_gpiochip_add(chip); 363 364 status = gpiochip_sysfs_register(chip);// 将该gpiochip导出到sys,用于调试和应用层直接操作; 372 return 0; 373 391 } 392 EXPORT_SYMBOL_GPL(gpiochip_add);
422 int of_gpiochip_add(struct gpio_chip *chip)423 { 424 int status;425 426 if ((!chip->of_node) && (chip->dev))427 chip->of_node = chip->dev->of_node;428 429 if (!chip->of_node)430 return 0;431 432 if (!chip->of_xlate) {433 chip->of_gpio_n_cells = 2;434 chip->of_xlate = of_gpio_simple_xlate; //of_xlate用于解析设备树里gpio属性,不同的soc可能需要不同的解析方法;435 }436 437 status = of_gpiochip_add_pin_range(chip);//从dts解析pin range,一般没有定义gpio-ranges和gpio-ranges-group-names;都是用gpiochip_add_pin_range()来添加,下面在介绍;438 if (status)439 return status;440 441 of_node_get(chip->of_node);//增加该节点引用计数442 443 of_gpiochip_scan_gpios(chip);444 445 return 0;446 }
总结gpiochip_add()函数的作用:
- 将chip添加到全局的gpio chip链表中,用于gpio chip冲突处理和gpio管理 ;
- 将该gpio chip对应的那段gpio都初始化 ;
- 初始化设备树相关的信息,用于后面的属性解析及向pinctrl子系统同步下;
- 导入sys目录;
gpiochip_add_pin_range():
697 int gpiochip_add_pin_range(struct gpio_chip *chip, const char *pinctl_name, 698 unsigned int gpio_offset, unsigned int pin_offset, 699 unsigned int npins) 700 { 701 struct gpio_pin_range *pin_range; 702 int ret; 703 704 pin_range = kzalloc(sizeof(*pin_range), GFP_KERNEL); 709 710 /* Use local offset as range ID */ 711 pin_range->range.id = gpio_offset;//GPIO chip ID 712 pin_range->range.gc = chip;//每个GPIO bank都是一个gpio chip,对应一个GPIO range 713 pin_range->range.name = chip->label; 714 pin_range->range.base = chip->base + gpio_offset; 715 pin_range->range.pin_base = pin_offset;//在线性映射的情况下,这是起始的pin base; 716 pin_range->range.npins = npins;//这个range有多少个GPIO引脚; 717 pin_range->pctldev = pinctrl_find_and_add_gpio_range(pinctl_name, 718 &pin_range->range); 719 if (IS_ERR(pin_range->pctldev)) { 720 ret = PTR_ERR(pin_range->pctldev); 721 chip_err(chip, "could not create pin range\n"); 722 kfree(pin_range); 723 return ret; 724 } 725 chip_dbg(chip, "created GPIO range %d->%d ==> %s PIN %d->%d\n", 726 gpio_offset, gpio_offset + npins - 1, 727 pinctl_name, 728 pin_offset, pin_offset + npins - 1); 729 730 list_add_tail(&pin_range->node, &chip->pin_ranges); 731 732 return 0; 733 } 734 EXPORT_SYMBOL_GPL(gpiochip_add_pin_range);
一个pin control device要管理的GPIO ID是分成区域的,每个区域用struct pinctrl_gpio_range来抽象,在pin controller初始化的时候,会调用pinctrl_add_gpio_range将每个GPIO bank表示的gpio range挂入到pin control device的range list中(gpio_ranges成员);
当管脚控制子系统中GPIO特定函数被调用后,这些范围将用于查找合适的管脚控制器,通过检查且匹配管脚到所有控制器的管脚范围。当匹配到一个管脚控制器的处理范围时,GPIO-specific函数在此管脚控制器上被调用。
(二) GPIO API
linux内核中的gpio接口目前有新旧两个版本,新的版本的接口是descriptor-based的,而旧的是integer-based的。旧的接口已出于兼容性的考虑仍被支持,但已不再建议使用。
legacy gpio API接口:
//请求一个/一组gpiogpio_request/devm_gpio_request、gpio_request_one/devm_gpio_request_one、gpio_request_array//设置gpio方向为输入/输出gpio_direction_input或者gpio_direction_output//如果gpio为输入,获取gpio值,如果gpio为输出,可以设置gpio高低电平gpio_get_value、gpio_set_value //将gpio转为对应的irq,然后注册该irq的中断handlerrequest_irq(gpio_to_irq(gpio_num)...) //释放请求的一个或者一组gpiogpio_free/devm_gpio_free、gpio_free_array
descriptor-based gpio API 操作借口:
#include <linux/gpio/consumer.h>//需要include consumer.h;//请求第一个/指定某一个gpio desc,该返回值用于后面的操作;//gpiod_get和gpiod_get_index则是通过解析设备树信息,提取里面的gpio号,然后再转换;gpiod_get/devm_gpiod_get、gpiod_get_index/devm_gpiod_get_index//设置gpio方向为输入/输出;gpiod_direction_input或者gpiod_get_direction//将该gpio通过sys文件系统导出,应用层可以通过文件操作gpio;gpiod_export//如果gpio为输入,获取gpio值,如果gpio为输出,可以设置gpio高低电平;gpiod_get_value或者gpiod_set_value //将gpio转为对应的irq,然后注册该irq的中断handler;request_irq(gpiod_to_irq(gpio_desc)...) //释放请求的一个或者一组gpio;gpiod_put/devm_gpiod_put
(1) gpiod_request
下面分析gpiod_request():
934 int gpiod_request(struct gpio_desc *desc, const char *label) 935 { 936 int status = -EPROBE_DEFER; 937 struct gpio_chip *chip; 938 //检查desc是否有效,gpio_request会根据传入的gpio号在全局的desc里定位到desc;可以通过gpiod_find()获取desc; 939 if (!desc) { 940 pr_warn("%s: invalid GPIO\n", __func__); 941 return -EINVAL; 942 } 943 944 chip = desc->chip;//获取该desc的chip; 945 if (!chip) 946 goto done; 947 948 if (try_module_get(chip->owner)) {//该module是否有效; 949 status = __gpiod_request(desc, label); 950 if (status < 0) 951 module_put(chip->owner); 952 } 958 return status; 959 }
891 static int __gpiod_request(struct gpio_desc *desc, const char *label) 892 { 910 911 if (chip->request) { 912 /* chip->request may sleep */ 913 spin_unlock_irqrestore(&gpio_lock, flags); 914 status = chip->request(chip, gpio_chip_hwgpio(desc)); 915 spin_lock_irqsave(&gpio_lock, flags); 922 } 923 if (chip->get_direction) { 924 /* chip->get_direction may sleep */ 925 spin_unlock_irqrestore(&gpio_lock, flags); 926 gpiod_get_direction(desc);//调用chip的get_direction; 927 spin_lock_irqsave(&gpio_lock, flags); 928 } 931 return status; 932 }
request通常是直接调用gpiochip_generic_request() -> pinctrl_request_gpio()传入chip和gpio偏移:
570 int pinctrl_request_gpio(unsigned gpio) 571 { 572 struct pinctrl_dev *pctldev; 573 struct pinctrl_gpio_range *range; 574 int ret; 575 int pin; 576 //两个链表轮询先在全局pinctrldev_list列表获取一个pinctrl_dev然后根据gpio是否在这个range内,返回该pinctrl_dev和range; 577 ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range); 578 if (ret) { 579 if (pinctrl_ready_for_gpio_range(gpio)) 580 ret = 0; 581 return ret; 582 } 583 584 mutex_lock(&pctldev->mutex); 585 586 /* Convert to the pin controllers number space */ 587 pin = gpio_to_pin(range, gpio); 588 589 ret = pinmux_request_gpio(pctldev, range, pin, gpio);//作用主要有两个,第一个是在core driver中标记该pin已经用作GPIO了,这样,如果有模块后续request该资源,那么core driver可以拒绝不合理的要求。//第二个就是调用底层pin controller driver的callback函数,进行底层寄存器相关的操作。 590 591 mutex_unlock(&pctldev->mutex); 592 593 return ret; 594 }
pinmux_request_gpio() -> pin_request(pctldev, pin, owner, range) -> ops->gpio_request_enable()最终调用到上一章pinctrl所定义的pmxops; (ops = pctldev->desc->pmxops)
(2) gpiod_set_value()
通过gpiod_set_value()可以来设置gpio的状态,传入一个参数gpio_desc,这在前面gpiochip_add()的时候会为每个GPIO创建一个描述符,并添加到全局变量gpio_desc数组中,其中flag表是该gpio的类型:
58 static struct gpio_desc gpio_desc[ARCH_NR_GPIOS]; 65 struct gpio_desc { 66 struct gpio_chip *chip; 67 unsigned long flags; 68 /* flag symbols are bit numbers */ 69 #define FLAG_REQUESTED 0 70 #define FLAG_IS_OUT 1 71 #define FLAG_EXPORT 2 /* protected by sysfs_lock */ 72 #define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */ 73 #define FLAG_TRIG_FALL 4 /* trigger on falling edge */ 74 #define FLAG_TRIG_RISE 5 /* trigger on rising edge */ 75 #define FLAG_ACTIVE_LOW 6 /* value has active low */ 76 #define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */ 77 #define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */ 78 #define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */ 79 80 #define ID_SHIFT 16 /* add new flags before this one */ 81 82 #define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1) 83 #define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE)) 84 85 const char *label; 86 };
当我们需要设置一个gpio的状态的时候,就需要通过gpio号找到它对应的gpio_desc,可以通过gpio_to_desc()接口来实现;现在来看看gpiod_set_value()的实现:
1291 void gpiod_set_value(struct gpio_desc *desc, int value)1292 {1293 if (!desc)1294 return;1295 /* Should be using gpio_set_value_cansleep() */ //函数说明的很清楚,该函数调用的时候是不能睡眠的;1296 WARN_ON(desc->chip->can_sleep); 1297 if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))1298 value = !value; 1299 _gpiod_set_raw_value(desc, value);1300 } 1301 EXPORT_SYMBOL_GPL(gpiod_set_value);
1245 static void _gpiod_set_raw_value(struct gpio_desc *desc, bool value)1246 {1247 struct gpio_chip *chip;1248 1249 chip = desc->chip;1250 trace_gpio_value(desc_to_gpio(desc), 0, value); //开漏模式1251 if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))1252 _gpio_set_open_drain_value(desc, value);1253 else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))1254 _gpio_set_open_source_value(desc, value);1255 else1256 chip->set(chip, gpio_chip_hwgpio(desc), value);1257 }
最终调用注册在gpio_chip的函数接口来实现,普通输出设置为高还是低通过set来设置,关于open drain和open source模式抽出时间在研究研究;
(三) GPIO 与中断
GPIO系统用作普通io的同时也可以当作interrupt controller,是一个GPIO type的中断控制器,通过访问GPIO type的中断控制器的寄存器,通过访问GPIO type的中断控制器的寄存器,可以软件设置中断的disable和enable(mask和unmask),中断的触发方式等,但是并非所有的IO port都支持中断功能,可能某些处理器只有一两组GPIO有中断功能。
gpio系统也作为一个中断控制器,所以也通过IRQCHIP_DECLARE来申明一个irqchipchip,并保存在全局的系统初始化的时候通过名字匹配,匹配之后完成相关的初始化:
IRQCHIP_DECLARE(8937_pinctrl, "qcom,msm8937-pinctrl", pinctrl_irq_dummy);
通过gpiochip_irqchip_add()来添加irqchip,通过注册的接口来操作该gpio中断控制器:
791 static struct irq_chip msm_gpio_irq_chip = { 792 .name = "msmgpio", 793 .irq_mask = msm_gpio_irq_mask, 794 .irq_unmask = msm_gpio_irq_unmask, 795 .irq_ack = msm_gpio_irq_ack, 796 .irq_set_type = msm_gpio_irq_set_type, 797 .irq_set_wake = msm_gpio_irq_set_wake, 798 };
572 int gpiochip_irqchip_add(struct gpio_chip *gpiochip, 573 struct irq_chip *irqchip, 574 unsigned int first_irq, 575 irq_flow_handler_t handler, 576 unsigned int type) 577 { 578 struct device_node *of_node; 579 unsigned int offset; 580 unsigned irq_base = 0; 581 589 of_node = gpiochip->dev->of_node; 590 #ifdef CONFIG_OF_GPIO 595 if (gpiochip->of_node) 596 of_node = gpiochip->of_node; 597 #endif 598 gpiochip->irqchip = irqchip; 599 gpiochip->irq_handler = handler;//注册flow interrupt 处理函数:handle_edge_irq,边沿触发类型; 600 gpiochip->irq_default_type = type; 601 gpiochip->to_irq = gpiochip_to_irq;//gpio num和irq number 转换函数; 602 gpiochip->irqdomain = irq_domain_add_simple(of_node, 603 gpiochip->ngpio, first_irq, 604 &gpiochip_domain_ops, gpiochip);//irq domain模块来管理IRQ number与HW interrupt ID的映射;并提供解析函数接口:gpiochip_domain_ops; //first_irq为0,只通过__irq_domain_add来初始化并添加irq_domain链表; 609 irqchip->irq_request_resources = gpiochip_irq_reqres; 610 irqchip->irq_release_resources = gpiochip_irq_relres; 611 617 for (offset = 0; offset < gpiochip->ngpio; offset++) { 618 irq_base = irq_create_mapping(gpiochip->irqdomain, offset);//创建HW interrupt ID和IRQ number的映射关系,并为每个gpio number分配中断描述符; 619 if (offset == 0) 624 gpiochip->irq_base = irq_base; 625 } 626 627 acpi_gpiochip_request_interrupts(gpiochip);//for ACPI; 628 629 return 0; 630 } 631 EXPORT_SYMBOL_GPL(gpiochip_irqchip_add);
关于具体中断系统的创建可以参考之前的文章 【Linux基础系列之】中断系统(1)-框架
在具体驱动里面,我们需要某个gpio作为中断号,注册中断函数接口还是一样,通过request_threaded_irq来实现,需要的是用gpiod_to_irq来把gpio num转化成irq number,举个例子如下:
1323 int gpiod_to_irq(const struct gpio_desc *desc)1324 {1325 struct gpio_chip *chip;1326 int offset; 1327 1328 if (!desc) 1329 return -EINVAL; 1330 chip = desc->chip;1331 offset = gpio_chip_hwgpio(desc);//获取该gpio号对应于该chip的offset1332 return chip->to_irq ? chip->to_irq(chip, offset) : -ENXIO; //gpio号与中断号的对应关系是由chip driver处理,调用前面注册的to_irq函数;最终通过irq_domain的注册的映射关系来查找;1333 }1334 EXPORT_SYMBOL_GPL(gpiod_to_irq);
- 【Linux基础系列之】gpio系统
- 【Linux基础系列之】pinctrl系统
- cheng@Linux之基础--GPIO实验
- ARM系列之GPIO
- 【Linux基础系列之】中断系统(1)-框架
- 【Linux基础系列之】中断系统(2)-下半部
- linux内核中的GPIO系统之(1):软件框架
- linux内核中的GPIO系统之(1):软件框架
- linux内核中的GPIO系统之(1):软件框架
- linux内核中的GPIO系统之(1):软件框架
- linux内核中的GPIO系统
- Android基础系列-----------GUI系统之WindowManagerService
- Linux基础之系统安装
- Linux 内核设备驱动之GPIO驱动之GPIO GPIO描述符到GPIO号
- Linux 内核设备驱动之GPIO驱动之GPIO GPIO描述符到GPIO CHIP
- 学习linux之GPIO口
- linux之GPIO的使用
- linux之GPIO的使用
- JAVA SE基础总结(可以作为有一定基础的参考)
- 爬虫——使用BeautifulSoup4的爬虫
- ASP.NET MVC入门到精通——搭建项目框架
- 第16章 数组总结
- POJ
- 【Linux基础系列之】gpio系统
- leetcode__Longest Substring Without Repeating Characters
- 扫雷游戏
- 位运算
- 子串查找
- Google Gson的使用方法,实现Json结构的相互转换
- maven项目名称出现红叉但是project下又看不到错误在哪儿
- 常用的几个正则表达式式
- 如何优化cocos2d/x程序的内存使用和程序大小