大多数嵌入式Linux驱动程序和内核工程师使用gpio编写或使用pin多路复用。所谓引脚,我指的是组件的引出线。SoC作多引脚复用,这意味着一个引脚可能有几个功能; 例如,arch/arm/boot/dts/imx6dl- pinfunction .h中的MX6QDL_PAD_SD3_DAT1能够是SD3数据线1,UART1的cts/rts, Flexcan2的Rx,或者普通的GPIO。node
你选择一个引脚应该工做的模式的机制被称为引脚muxing。负责这个的系统被称为引脚控制器。在本章的第二部分,咱们将讨论通用输入输出(GPIO),它是一种特殊的功能(模式),一个引脚能够在其中工做。linux
在本章中,咱们将:ios
pin control (pinctrl)子系统容许管理pin muxing。在DT中,须要以某种方式多路复用引脚的设备必须声明它们须要的引脚控制配置。app
pinctrl子系统提供:ide
这篇文章的目的仅限于使用由引脚控制器驱动程序导出的函数,而不包括如何编写引脚控制器驱动程序。函数
pinctrl子系统只是一种收集引脚(不只仅是GPIO)的方式,并将它们传递给驱动程序。引脚控制器驱动程序负责解析DT中的引脚描述,并将其配置应用到芯片中。驱动程序一般须要一组两个嵌套节点来描述一组引脚配置。第一个节点描述组的功能(组将用于什么目的),第二个节点保存引脚的配置。this
在DT中如何分配引脚组很大程度上取决于平台,所以也取决于引脚控制器驱动程序。每一个引脚控制状态都有一个从0开始的连续整数ID。
您可使用name属性,它将映射到ID的顶部,这样相同的名称老是指向相同的ID。spa
每一个客户机设备本身的绑定决定了必须在其DT节点中定义的一组状态,以及是否认义必须提供的一组状态id,仍是定义必须提供的一组状态名称。在任何状况下,一个pin配置节点能够经过两个属性分配到一个设备:线程
如下是DT的摘录,显示了一些设备节点及其引脚控制节点:debug
1 usdhc@0219c000 { /* uSDHC4 */ 2 non-removable; 3 vmmc-supply = <®_3p3v>; 4 status = "okay"; 5 pinctrl-names = "default"; 6 pinctrl-0 = <&pinctrl_usdhc4_1>; 7 }; 8 gpio-keys { 9 compatible = "gpio-keys"; 10 pinctrl-names = "default"; 11 pinctrl-0 = <&pinctrl_io_foo &pinctrl_io_bar>; 12 }; 13 iomuxc@020e0000 { 14 compatible = "fsl,imx6q-iomuxc"; 15 reg = <0x020e0000 0x4000>; 16 /* shared pinctrl settings */ 17 usdhc4 { /* first node describing the function */ 18 pinctrl_usdhc4_1: usdhc4grp-1 { /* second node */ 19 fsl,pins = < 20 MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059 21 MX6QDL_PAD_SD4_CLK__SD4_CLK 0x10059 22 MX6QDL_PAD_SD4_DAT0__SD4_DATA0 0x17059 23 MX6QDL_PAD_SD4_DAT1__SD4_DATA1 0x17059 24 MX6QDL_PAD_SD4_DAT2__SD4_DATA2 0x17059 25 MX6QDL_PAD_SD4_DAT3__SD4_DATA3 0x17059 26 MX6QDL_PAD_SD4_DAT4__SD4_DATA4 0x17059 27 MX6QDL_PAD_SD4_DAT5__SD4_DATA5 0x17059 28 MX6QDL_PAD_SD4_DAT6__SD4_DATA6 0x17059 29 MX6QDL_PAD_SD4_DAT7__SD4_DATA7 0x17059 30 >; 31 }; 32 }; 33 [...] 34 uart3 { 35 pinctrl_uart3_1: uart3grp-1 { 36 fsl,pins = < 37 MX6QDL_PAD_EIM_D24__UART3_TX_DATA 0x1b0b1 38 MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1 39 >; 40 }; 41 }; 42 // GPIOs (Inputs) 43 44 45 gpios { 46 pinctrl_io_foo: pinctrl_io_foo { 47 fsl,pins = < 48 MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 0x1f059 49 MX6QDL_PAD_DISP0_DAT13__GPIO5_IO07 0x1f059 50 >; 51 }; 52 pinctrl_io_bar: pinctrl_io_bar { 53 fsl,pins = < 54 MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05 0x1f059 55 MX6QDL_PAD_DISP0_DAT9__GPIO4_IO30 0x1f059 56 MX6QDL_PAD_DISP0_DAT7__GPIO4_IO28 0x1f059 57 MX6QDL_PAD_DISP0_DAT5__GPIO4_IO26 0x1f059 58 >; 59 }; 60 }; 61 };
在上面的例子中,pin配置以<PIN_FUNCTION> < PIN_SETTING > 的形式给出,例如:
MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 0x1f059
MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09表明pin function,在本例中是GPIO, 0x1f059表示引脚设置。
如下行:
MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1
MX6QDL_PAD_EIM_D25__UART3_RX_DATA表示pin function,即UART3的RX线,0x1b0b1表示设置。
pin function是一个宏,它的值只对pin控制器驱动有意义。它们一般定义在位于arch/<arch>/boot/dts/中的头文件中。若是你使用UDOO四轴,例如,它有一个i.MX6四轴核心(ARM), pin函数头将是arch/ ARM /boot/dts/imx6q- pinfunct .h。下面是GPIO5控制器第5行对应的宏:
/*
* The pin function ID is a tuple of <mux_reg conf_reg input_reg mux_mode input_val>
*/
#define MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05 0x19c 0x4b0 0x000 0x5 0x0
<PIN_SETTING>能够用来设置诸如上拉,下拉,保持器,驱动强度,等等。它应该如何指定取决于pin控制器绑定,它的值的含义取决于SoC数据表,一般在IOMUX部分。在i.MX6 IOMUXC上,少于17位专用于此目的。
前面的这些节点是从相应的特定于驱动程序的节点调用的。此外,这些引脚是在相应的驱动初始化过程当中配置的。在选择一个pin组状态以前,您必须首先使用pinctrl_get()函数得到pin控制,调用pinctrl_lookup_state()来检查请求的状态是否存在,最后使用pinctrl_select_state()来应用状态。
下面的示例展现了如何获取pin control并应用它的默认配置:
1 struct pinctrl *p; 2 struct pinctrl_state *s; 3 int ret; 4 p = pinctrl_get(dev); 5 if (IS_ERR(p)) 6 return p; 7 s = pinctrl_lookup_state(p, name); 8 if (IS_ERR(s)) { 9 devm_pinctrl_put(p); 10 return ERR_PTR(PTR_ERR(s)); 11 } 12 ret = pinctrl_select_state(p, s); 13 if (ret < 0) { 14 devm_pinctrl_put(p); 15 return ERR_PTR(ret); 16 }
一般在驱动程序初始化期间执行这些步骤。这段代码的合适位置多是probe()函数内部。
pinctrl_select_state() 在内部调用 pinmux_enable_setting() ,而 pinmux_enable_setting() 依次调用pin控制节点中的每一个pin上的 pin_request() 。
pin控件能够经过 pinctrl_put() 函数释放。您可使用该API的resource-managed版本。也就是说,您可使用 pinctrl_get_select() ,给定要选择的状态名,来配置pinmux。该函数在 include/linux/pinctrl/consumer.h 中定义以下:
static struct pinctrl *pinctrl_get_select(struct device *dev, const char *name)
这里,*name是在pinctrl-name属性中写入的状态名。若是状态的名称是default,你能够调用pinctr_get_select_default()函数,它是对pinctl_get_select()的包装:
1 static struct pinctrl * pinctrl_get_select_default(struct device *dev) 2 { 3 return pinctrl_get_select(dev, PINCTRL_STATE_DEFAULT); 4 }
让咱们看一个板子特有的DTS文件中的实际示例(am335x-evm.dts):
1 dcan1: d_can@481d0000 { 2 status = "okay"; 3 pinctrl-names = "default"; 4 pinctrl-0 = <&d_can1_pins>; 5 };
对应的驱动程序示例以下:
1 pinctrl = devm_pinctrl_get_select_default(&pdev->dev); 2 if (IS_ERR(pinctrl)) 3 dev_warn(&pdev->dev,"pins are not configured from the driver\n");
当设备被探测(probe)时,pin 控制核心将自动为咱们声明默认的 pinctrl 状态。若是您定义了一个 init 状态,那么 pinctrl 核心会在 probe() 函数以前自动将 pinctrl 设置为这个状态,而后在 probe() 以后切换到默认状态(除非驱动程序已经显式地改变了状态)。
从硬件的角度来看,GPIO 是一种功能,一种引脚能够操做的模式。从软件的角度来看,GPIO 只是一条数字线,它能够做为输入或输出,而且只能有两个值(1表示高,0表示低)。内核 GPIO 子系统提供了您能够想象的设置和处理的全部函数从你的驱动程序内部的 GPIO 行:
实际上,在内核中有两种不一样的方式来处理 GPIO,以下:
基于整数的接口是最著名的。GPIO 由一个整数标识,这个整数用于须要在 GPIO 上执行的每个操做。下面是包含传统 GPIO 访问函数的头文件:
#include <linux/gpio.h>
在内核中有一些处理 GPIO 的知名函数。
你可使用 gpio_request() 函数来分配和得到 GPIO 的全部权:
static int gpio_request(unsigned gpio, const char *label)
gpio 表示咱们感兴趣的gpio号, label 是内核为sysfs中的gpio所使用的标签,咱们能够在 /sys/kernel/debug/gpio 中看到。您必须检查返回的值,其中0表示成功,一个错误产生一个负的错误代码。一旦你用 GPIO 完成了这些,应该用 gpio_free() 函数释放它:
void gpio_free(unsigned int gpio)
若是有疑问,可使用 gpio_is_valid() 函数在分配这个 GPIO 号以前检查它在系统上是否有效:
static bool gpio_is_valid(int number)
一旦咱们拥有了GPIO,咱们能够根据须要改变它的方向,以及它应该是输入仍是输出,使用 gpio_direction_input() 或 gpio_direction_output() 函数:
static int gpio_direction_input(unsigned gpio) static int gpio_direction_output(unsigned gpio, int value)
gpio是咱们须要设定方向的 GPIO 号。第二个参数是配置 GPIO 做为output, value,即输出方向有效时GPIO的状态。一样,返回值是零或负的错误数。这些函数在内部映射到底层回调函数之上,这些回调函数由提供咱们使用的GPIO的GPIO控制器的驱动程序导出。
在处理GPIO控制器驱动时,咱们将看到GPIO控制器,经过它的结构gpio_chip结构,必须导出一组通用的回调函数来使用它的GPIO。
一些 GPIO 控制器提供了改变GPIO 防抖(debounc-interval) 的可能性(这只在 GPIO 被配置为输入时有用)。该特性与平台相关。你可使用 int gpio_set_debounce() 来实现:
static int gpio_set_debounce(unsigned gpio, unsigned debounce)
在这里,debounce的值是ms。
前面全部的函数都应该在可能休眠的上下文中调用。从驱动程序的探测函数(probe)中声明和配置gpio是一个很好的实践。
在访问 GPIO 时要注意。在原子上下文中,特别是在中断处理程序中,您必须确保 GPIO 控制器回调函数不会休眠。一个设计良好的控制器驱动程序应该可以通知其余驱动程序(其实是客户端)对其方法的调用是否会休眠。这能够经过 gpio_cansleep() 函数来检查。
全部用于访问 GPIO 的函数都没有返回错误代码。这就是为何在 GPIO 分配和配置过程当中要注意并检查返回值。
有一些 GPIO 控制器能够经过简单的内存读写操做来访问和管理。它们一般嵌入在 SoC 中,不须要休眠。对于这些控制器, gpio_cansleep() 老是返回false。对于这样的 GPIO,你可使用众所周知的 gpio_get_value() 或 gpio_set_value() 从IRQ处理程序中获取/设置它们的值,这取决于 GPIO 被配置为输入仍是输出:
1 static int gpio_get_value(unsigned gpio) 2 void gpio_set_value(unsigned int gpio, int value);
当 GPIO 被配置为 input (使用 gpio_direction_input() )时,应该使用 gpio_get_value() ,并返回 GPIO 的实际值(state)。另外一方面, gpio_set_value() 将影响 GPIO 的值,GPIO 应该使用 gpio_direction_output() 将其配置为输出。对于这两个函数,value 均可以被认为是布尔值,其中零表示低,非零值表示高。
另外一方面,有 GPIO 控制器链接在总线上,如SPI和I2C。由于访问这些总线的函数可能会致使睡眠, gpio_cansleep() 函数应该老是返回true(这取决于GPIO控制器返回true)。在这种状况下,您不该该从IRQ中断处理内部访问这些gpio,至少不能在其上半部分访问(硬IRQ)。此外,你必须使用的访问器做为你的通用访问应该以_cansleep为后缀:
static int gpio_get_value_cansleep(unsigned gpio); void gpio_set_value_cansleep(unsigned gpio, int value);
它们的行为与不带 _cansleep() 后缀的访问器彻底同样,惟一的区别是它们防止内核在 gpio 被访问时打印警告。
输入 gpio 常常被用做 IRQ 信号。这种 irq 能够是边缘触发或水平触发。配置取决于您的须要。GPIO 控制器负责提供 GPIO 和它的 IRQ 之间的映射。你可使用 gpio_to_irq() 将给定的 GPIO 值映射到它的 IRQ 值:
int gpio_to_irq(unsigned gpio);
返回值是 IRQ 编号,你能够调用 request_irq() (或者线程版本 request_threaded_irq() )来为这个 IRQ 注册一个处理程序:
1 static irqreturn_t my_interrupt_handler(int irq, void *dev_id) 2 { 3 [...] 4 return IRQ_HANDLED; 5 } 6 [...] 7 int gpio_int = of_get_gpio(np, 0); 8 int irq_num = gpio_to_irq(gpio_int); 9 int error = devm_request_threaded_irq(&client->dev, irq_num, 10 NULL, my_interrupt_handler, 11 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 12 input_dev->name, my_data_struct); 13 if (error) { 14 dev_err(&client->dev, "irq %d requested failed, %d\n", 15 client->irq, error); 16 return error; 17 }
下面的代码是一个总结,将讨论的全部关于基于整数的接口的概念付诸实践。这个驱动程序管理四个gpio:两个按钮(btn1和btn2)和两个led(绿色和红色)。btn1被映射到IRQ,当它的状态变为低,btn2的状态应用于led。例如,若是btn1的状态是低的,而btn2的状态是高的,绿色和红色led将被驱动到高:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/kernel.h> 4 #include <linux/gpio.h> /* For Legacy integer based GPIO */ 5 #include <linux/interrupt.h> /* For IRQ */ 6 static unsigned int GPIO_LED_RED = 49; 7 static unsigned int GPIO_BTN1 = 115; 8 static unsigned int GPIO_BTN2 = 116; 9 static unsigned int GPIO_LED_GREEN = 120; 10 static int irq; 11 12 static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id) 13 { 14 int state; 15 16 /* read BTN2 value and change the led state */ 17 state = gpio_get_value(GPIO_BTN2); 18 gpio_set_value(GPIO_LED_RED, state); 19 gpio_set_value(GPIO_LED_GREEN, state); 20 pr_info("GPIO_BTN1 interrupt: Interrupt! GPIO_BTN2 state is %d)\n", state); 21 22 return IRQ_HANDLED; 23 } 24 25 static int __init helloworld_init(void) 26 { 27 int retval; 28 /* 29 * One could have checked whether the GPIO is valid on the controller 30 or not, 31 * using gpio_is_valid() function. 32 * Ex: 33 * if (!gpio_is_valid(GPIO_LED_RED)) { 34 * pr_infor("Invalid Red LED\n"); 35 * return -ENODEV; 36 * } 37 */ 38 gpio_request(GPIO_LED_GREEN, "green-led"); 39 gpio_request(GPIO_LED_RED, "red-led"); 40 gpio_request(GPIO_BTN1, "button-1"); 41 gpio_request(GPIO_BTN2, "button-2"); 42 /* 43 * Configure Button GPIOs as input 44 * 45 * After this, one can call gpio_set_debounce() 46 * only if the controller has the feature 47 * 48 * For example, to debounce a button with a delay of 200ms 49 * gpio_set_debounce(GPIO_BTN1, 200); 50 */ 51 gpio_direction_input(GPIO_BTN1); 52 gpio_direction_input(GPIO_BTN2); 53 /* 54 * Set LED GPIOs as output, with their initial values set to 0 55 */ 56 gpio_direction_output(GPIO_LED_RED, 0); 57 gpio_direction_output(GPIO_LED_GREEN, 0); 58 irq = gpio_to_irq(GPIO_BTN1); 59 60 retval = request_threaded_irq(irq, NULL,\ 61 btn1_pushed_irq_handler, \ 62 IRQF_TRIGGER_LOW | IRQF_ONESHOT, \ 63 "device-name", NULL); 64 pr_info("Hello world!\n"); 65 return 0; 66 } 67 68 static void __exit hellowolrd_exit(void) 69 { 70 free_irq(irq, NULL); 71 gpio_free(GPIO_LED_RED); 72 gpio_free(GPIO_LED_GREEN); 73 gpio_free(GPIO_BTN1); 74 gpio_free(GPIO_BTN2); 75 pr_info("End of the world\n"); 76 } 77 78 module_init(hellowolrd_init); 79 module_exit(hellowolrd_exit); 80 MODULE_AUTHOR("aw aw@email.com"); 81 MODULE_LICENSE("GPL");
使用新的基于描述符的GPIO接口,GPIO的特征是一个一致的结构gpio_desc结构:
1 struct gpio_desc { 2 struct gpio_chip *chip; 3 unsigned long flags; 4 const char *label; 5 };
你应该使用如下头文件来使用新的接口:
#include <linux/gpio/consumer.h>
对于基于描述符的接口,在分配和得到GPIOs全部权以前,这些GPIOs必须已经映射到某个地方。经过映射,个人意思是他们应该被分配给你的设备,然而,与传统的基于整数的接口,你只须要在任何地方获取一个数字,并请求它做为GPIO。实际上,在内核中有三种映射:
GPIO描述符映射在消费设备的节点中定义。包含GPIO描述符映射的属性必须命名为<name>-gpios 或 <name>-gpio,其中<name>足够有意义来描述这些GPIO将要使用的功能。
你应该老是在属性名后面加上 -gpio 或 -gpios,由于每一个基于描述符的接口函数都依赖于 gpio_suffix[] 变量,该变量在 drivers/gpio/gpiolib.h 中定义,以下所示:
/* gpio suffixes used for ACPI and device tree lookup */ static const char * const gpio_suffixes[] = { "gpios", "gpio" };
让咱们来看看如何在DT的设备中查找GPIO描述符映射:
static struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id, unsigned int idx, enum gpio_lookup_flags *flags) { char prop_name[32]; /* 32 is max size of property name */ enum of_gpio_flags of_flags; struct gpio_desc *desc; unsigned int i; for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { if (con_id) snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id, gpio_suffixes[i]); else snprintf(prop_name, sizeof(prop_name), "%s", gpio_suffixes[i]); desc = of_get_named_gpiod_flags(dev->of_node, prop_name, idx, &of_flags); if (!IS_ERR(desc) || (PTR_ERR(desc) == -EPROBE_DEFER)) break; } if (IS_ERR(desc)) return desc; if (of_flags & OF_GPIO_ACTIVE_LOW) *flags |= GPIO_ACTIVE_LOW; return desc; }
如今,让咱们考虑如下节点,它是从 Documentation/gpio/board.txt 节选的:
foo_device { compatible = "acme,foo"; [...] led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */ <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */ <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */ power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; };
映射应该是这样的,具备一个有意义的名称。
你可使用gpiog_get()或者gpiod_get_index()来分配一个GPIO描述符:
struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags)
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags)
在出现错误时,若是给定函数没有指定GPIO,或者出现可使用IS_ERR()宏处理的错误,这些函数将返回-ENOENT。第一个函数返回与给定索引处的GPIO对应的GPIO描述符结构,而第二个函数返回索引为0处的GPIO(对于单GPIO映射颇有用)。
dev是GPIO描述符所属的设备。这是你的设备。con_id 对应于DT中属性名的<name>前缀。idx是须要一个描述符的GPIO的索引(从0开始)。flags是一个可选参数,它决定了GPIO初始化标志,用来配置方向和/或输出值。它是enum gpiod_flags的一个实例,定义在include/linux/gpio/consumer.h中:
enum gpiod_flags { GPIOD_ASIS = 0, GPIOD_IN = GPIOD_FLAGS_BIT_DIR_SET, GPIOD_OUT_LOW = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT, GPIOD_OUT_HIGH = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT | GPIOD_FLAGS_BIT_DIR_VAL, };
如今,让咱们为以前DT中定义的映射分配GPIO描述符:
struct gpio_desc *red, *green, *blue, *power;
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH); green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH); blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_LOW);
LED GPIO将为active-high,而power GPIO将为active-low(即gpiod_is_active_low(power)将为true)。分配的反向操做是经过gpiod_put()函数完成的:
gpiod_put(struct gpio_desc *desc);
让咱们看看如何释放红色和蓝色GPIO led:
gpiod_put(blue);
gpiod_put(red);
在进一步讨论以前,请记住,除了gpiod_get()/gpiod_get_index() 和 gpio_put() 函数(它们与 gpio_request() 和 gpio_free() 彻底不一样)以外,您还能够经过将gpio_前缀更改成gpiod_来执行从基于整数的接口到基于描述符的API转换。
要改变方向,应该使用 gpiod_direction_input() 和 gpiod_direction_output() 函数:
int gpiod_direction_input(struct gpio_desc *desc); int gpiod_direction_output(struct gpio_desc *desc, int value);
value是当方向设置为输出时应用到GPIO的状态。若是GPIO控制器有这个特性,你可使用GPIO的描述符来设置它的超时:
int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);
为了访问给定描述符的GPIO,必须像访问基于整数的接口同样注意它。换句话说,你应该注意你是在一个原子上下文(不能睡眠)仍是非原子上下文中,而后使用适当的函数:
int gpiod_cansleep(const struct gpio_desc *desc); /* Value get/set from sleeping context */ int gpiod_get_value_cansleep(const struct gpio_desc *desc); void gpiod_set_value_cansleep(struct gpio_desc *desc, int value); /* Value get/set from non-sleeping context */ int gpiod_get_value(const struct gpio_desc *desc); void gpiod_set_value(struct gpio_desc *desc, int value);
对于映射到IRQ的GPIO描述符,您可使用gpiod_to_irq()来获取对应于给定GPIO描述符的IRQ编号,能够与request_irq()函数一块儿使用:
int gpiod_to_irq(const struct gpio_desc *desc);
在代码中的任何给定点,均可以使用 desc_to_gpio() 或 gpio_to_desc() 函数,从基于描述符的接口切换到基于整数的传统接口,反之亦然:
/* Convert between the old gpio_ and new gpiod_ interfaces */ struct gpio_desc *gpio_to_desc(unsigned gpio);
int desc_to_gpio(const struct gpio_desc *desc);
这里的驱动程序总结了基于描述符的接口中引入的概念。和基于整数的gpio接口原理是同样的:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> /* For platform devices */ #include <linux/gpio/consumer.h> /* For GPIO Descriptor */ #include <linux/interrupt.h> /* For IRQ */ #include <linux/of.h> /* For DT*/ /* * Let us consider the below mapping in device tree: * * foo_device { * compatible = "packt,gpio-descriptor-sample"; * led-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>, // red * <&gpio2 16 GPIO_ACTIVE_HIGH>, // green * * btn1-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; * btn2-gpios = <&gpio2 31 GPIO_ACTIVE_LOW>; * }; */ static struct gpio_desc *red, *green, *btn1, *btn2; static int irq;
static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id) { int state; /* read the button value and change the led state */ state = gpiod_get_value(btn2); gpiod_set_value(red, state); gpiod_set_value(green, state); pr_info("btn1 interrupt: Interrupt! btn2 state is %d)\n", state);
return IRQ_HANDLED; }
static const struct of_device_id gpiod_dt_ids[] = { { .compatible = "packt,gpio-descriptor-sample", }, { /* sentinel */ } };
static int my_pdrv_probe (struct platform_device *pdev) { int retval; struct device *dev = &pdev->dev;
/* * We use gpiod_get/gpiod_get_index() along with the flags * in order to configure the GPIO direction and an initial * value in a single function call. * * One could have used: * red = gpiod_get_index(dev, "led", 0); * gpiod_direction_output(red, 0); */
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW); green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW); /* * Configure GPIO Buttons as input * * After this, one can call gpiod_set_debounce() * only if the controller has the feature * For example, to debounce a button with a delay of 200ms * gpiod_set_debounce(btn1, 200); */ btn1 = gpiod_get(dev, "led", 0, GPIOD_IN); btn2 = gpiod_get(dev, "led", 1, GPIOD_IN); irq = gpiod_to_irq(btn1); retval = request_threaded_irq(irq, NULL,\ btn1_pushed_irq_handler, \ IRQF_TRIGGER_LOW | IRQF_ONESHOT, \ "gpio-descriptor-sample", NULL); pr_info("Hello! device probed!\n");
return 0; }
static void my_pdrv_remove(struct platform_device *pdev) { free_irq(irq, NULL); gpiod_put(red); gpiod_put(green); gpiod_put(btn1); gpiod_put(btn2); pr_info("good bye reader!\n"); } static struct platform_driver mypdrv = {
.probe = my_pdrv_probe, .remove = my_pdrv_remove, .driver = { .name = "gpio_descriptor_sample", .of_match_table = of_match_ptr(gpiod_dt_ids), .owner = THIS_MODULE, }, };
module_platform_driver(mypdrv); MODULE_AUTHOR("A W <aw@email.com>"); MODULE_LICENSE("GPL");
不管您须要为哪一个接口使用GPIO,如何指定GPIO取决于提供它们的控制器,特别是关于它的 #gpio-cells 属性,它决定了GPIO说明符使用的单元数。一个GPIO说明符至少包含控制器句柄和一个或多个参数,其中包含提供GPIO的控制器的 #gpio-cells 属性上的参数数量。第一个单元一般是控制器上的GPIO偏移数,第二个表明GPIO标志。
GPIO属性应该命名为[<name>-]gpios], <name>是设备的GPIO的目的。请记住,这个规则对于基于描述符的接口是必须的,而且变成了<name>-gpios(注意没有方括号,意味着<name>前缀是必须的):
gpio1: gpio1 { gpio-controller; #gpio-cells = <2>; }; gpio2: gpio2 { gpio-controller; #gpio-cells = <1>; }; [...]
cs-gpios = <&gpio1 17 0>, <&gpio2 2>; <0>, /* holes are permitted, means no GPIO 2 */ <&gpio1 17 0>;
reset-gpios = <&gpio1 30 0>; cd-gpios = <&gpio2 10>;
在上述示例中,CS GPIOs同时包含controller-1和controller-2 GPIOs。若是你不须要在列表中给定的索引处指定GPIO,你可使用<0>。reset GPIO有两个cell(控制器phandle后的两个参数),而CD GPIO只有一个cell。您能够看到我给GPIO说明符的名称是多么有意义。
该接口依赖于如下头文件:
#include <linux/of_gpio.h>
当你须要在驱动中使用基于整数的接口来支持DT时,你应该记住两个函数; 它们是 of_get_named_gpio() 和 of_get_named_gpio_count():
int of_get_named_gpio(struct device_node *np, const char *propname, int index) int of_get_named_gpio_count(struct device_node *np, const char* propname)
int n_gpios = of_get_named_gpio_count(dev.of_node, "cs-gpios"); /* return 4 */ int second_gpio = of_get_named_gpio(dev.of_node, "cs-gpio", 1); int rst_gpio = of_get_named_gpio("reset-gpio", 0); gpio_request(second_gpio, "my-gpio);
仍然有驱动程序支持旧的指定符,其中GPIO属性被命名[<name>-gpio] 或 gpios。在这种状况下,你应该使用未命名的API版本,经过 of_get_gpio() 和 of_gpio_count():
int of_gpio_count(struct device_node *np) int of_get_gpio(struct device_node *np, int index)
my_node@addr { compatible = "[...]"; gpios = <&gpio1 2 0>, /* INT */ <&gpio1 5 0>; /* RST */ [...] };
struct device_node *np = dev->of_node;
if (!np) return ERR_PTR(-ENOENT);
int n_gpios = of_gpio_count(); /* Will return 2 */ int gpio_int = of_get_gpio(np, 0); if (!gpio_is_valid(gpio_int)) { dev_err(dev, "failed to get interrupt gpio\n"); return ERR_PTR(-EINVAL); }
gpio_rst = of_get_gpio(np, 1); if (!gpio_is_valid(pdata->gpio_rst)) { dev_err(dev, "failed to get reset gpio\n"); return ERR_PTR(-EINVAL); }
你能够经过重写第一个驱动(一个基于整数的接口)来总结这一点,为了符合平台驱动结构,并使用DT API:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> /* For platform devices */ #include <linux/interrupt.h> /* For IRQ */ #include <linux/gpio.h> /* For Legacy integer based GPIO */ #include <linux/of_gpio.h> /* For of_gpio* functions */ #include <linux/of.h> /* For DT*/
/* * Let us consider the following node * * foo_device { * compatible = "packt,gpio-legacy-sample"; * led-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>, // red * <&gpio2 16 GPIO_ACTIVE_HIGH>, // green * * btn1-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; * btn2-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; * }; */ static unsigned int gpio_red, gpio_green, gpio_btn1, gpio_btn2; static int irq; static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id) { /* The content of this function remains unchanged */ [...] }
static const struct of_device_id gpio_dt_ids[] = { { .compatible = "packt,gpio-legacy-sample", }, { /* sentinel */ } };
static int my_pdrv_probe (struct platform_device *pdev) { int retval; struct device_node *np = &pdev->dev.of_node;
if (!np) return ERR_PTR(-ENOENT);
gpio_red = of_get_named_gpio(np, "led", 0); gpio_green = of_get_named_gpio(np, "led", 1); gpio_btn1 = of_get_named_gpio(np, "btn1", 0); gpio_btn2 = of_get_named_gpio(np, "btn2", 0);
gpio_request(gpio_green, "green-led"); gpio_request(gpio_red, "red-led"); gpio_request(gpio_btn1, "button-1"); gpio_request(gpio_btn2, "button-2");
/* Code to configure GPIO and request IRQ remains unchanged */ [...] return 0; }
static void my_pdrv_remove(struct platform_device *pdev) { /* The content of this function remains unchanged */ [...] } static struct platform_driver mypdrv = { .probe = my_pdrv_probe, .remove = my_pdrv_remove, .driver = { .name = "gpio_legacy_sample", .of_match_table = of_match_ptr(gpio_dt_ids), .owner = THIS_MODULE, }, };
module_platform_driver(mypdrv); MODULE_AUTHOR("A W <aw@email.com>"); MODULE_LICENSE("GPL");
您能够很容易地将GPIO映射到设备树中的IRQ。有两个属性用于指定中断:
这些应用于遗留的和基于描述符的接口。IRQ指示符取决于 #interrupt-cel l的GPIO控制器属性。#interrupt-cell 肯定指定中断时使用的单元数。一般,第一个单元表明映射到IRQ的GPIO号,第二个单元表明电平/边缘触发中断机制。在任何状况下,中断指示符老是依赖于它的父(拥有中断控制器集的父),因此请参考内核源代码中的绑定文档:
gpio4: gpio4 { gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; };
my_label: node@0 { reg = <0>; spi-max-frequency = <1000000>; interrupt-parent = <&gpio4>; interrupts = <29 IRQ_TYPE_LEVEL_LOW>; };
有两种方法能够得到相应的IRQ:
int platform_get_irq(struct platform_device *dev, unsigned int num);
sysfs GPIO接口容许人们经过设置或文件来管理和控制GPIO。它位于 /sys/class/gpio 目录下。设备模型在这里被大量使用,有三种类型的入口:
除了使用/sys/class/gpio/export文件导出gpio到用户空间外,您能够从内核代码中使用诸如gpio_export(用于遗留接口)或gpioD_export(新接口)这样的函数,以便显式管理使用gpio_request()或gpiod_get()已经请求的gpio的导出:
int gpio_export(unsigned gpio, bool direction_may_change); int gpiod_export(struct gpio_desc *desc, bool direction_may_change);
direction_may_change 参数决定是否能够将信号方向从输入改成输出,反之亦然。内核的反向操做是gpio_unexport()或gpiod_unexport():
void gpio_unexport(unsigned gpio); /* Integer-based interface */ void gpiod_unexport(struct gpio_desc *desc) /* Descriptor-based */
导出后,您可使用gpio_export_link()(或gpiod_export_link()用于基于描述符的接口)从sysfs中的其余地方建立符号连接,它将指向GPIO sysfs节点。在sysfs中,驱动程序能够用一个描述性的名称在本身的设备下提供接口:
int gpio_export_link(struct device *dev, const char *name, unsigned gpio) int gpiod_export_link(struct device *dev, const char *name, struct gpio_desc *desc)
你能够在probe()函数中使用它来实现基于描述符的接口,以下所示:
static struct gpio_desc *red, *green, *btn1, *btn2;
static int my_pdrv_probe (struct platform_device *pdev) { [...] red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW); green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW); gpiod_export(&pdev->dev, "Green_LED", green); gpiod_export(&pdev->dev, "Red_LED", red);
[...] return 0; }
对于基于整数的接口,代码以下所示:
static int my_pdrv_probe (struct platform_device *pdev) { [...]
gpio_red = of_get_named_gpio(np, "led", 0); gpio_green = of_get_named_gpio(np, "led", 1); [...]
int gpio_export_link(&pdev->dev, "Green_LED", gpio_green) int gpio_export_link(&pdev->dev, "Red_LED", gpio_red) return 0; }
在内核中处理GPIO是一项简单的任务,如本文所示。咱们讨论了遗留的和新的接口,从而能够选择适合您须要的接口,以便编写加强的GPIO驱动程序。您如今应该可以处理IRQs映射到gpio。下一篇文章将讨论提供和公开的芯片GPIO lines,称为GPIO控制器。