在前一篇文章中,我们处理了GPIO lines。这些lines通过一个叫做GPIO控制器的特殊设备向系统开放。本章将逐步解释如何为这些设备编写驱动程序,因此包括以下主题:
- GPIO控制器驱动结构和数据结构
- GPIO控制器的Sysfs接口
- GPIO控制器在DT中的表示
驱动架构和数据结构
此类设备的驱动程序应提供以下内容:
- 建立GPIO方向(输入输出)的方法。
- 用于访问GPIO值的方法(get和set)。
- 将给定的GPIO映射到IRQ并返回相关的编号的方法。
- 一个表示对其方法的调用是否可以休眠的标志。这一点非常重要。
- 一个可选的debugfs转储方法(显示额外的状态,如pullup config)。
- 一个叫做base number的可选的编号,GPIO编号应该从它开始。如果省略,它将被自动分配。
在内核中,GPIO控制器被表示为在linux/ GPIO /driver.h中定义的结构体gpio_chip的实例:
struct gpio_chip { const char *label; struct device *dev; struct module *owner; int (*request)(struct gpio_chip *chip, unsigned offset); void (*free)(struct gpio_chip *chip, unsigned offset); int (*get_direction)(struct gpio_chip *chip, unsigned offset); int (*direction_input)(struct gpio_chip *chip, unsigned offset); int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value); int (*get)(struct gpio_chip *chip,unsigned offset); void (*set)(struct gpio_chip *chip, unsigned offset, int value); void (*set_multiple)(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits); int (*set_debounce)(struct gpio_chip *chip, unsigned offset, unsigned debounce); int (*to_irq)(struct gpio_chip *chip, unsigned offset); int base; u16 ngpio; const char *const *names; bool can_sleep; bool irq_not_threaded; bool exported; #ifdef CONFIG_GPIOLIB_IRQCHIP /* * With CONFIG_GPIOLIB_IRQCHIP we get an irqchip * inside the gpiolib to handle IRQs for most practical cases. */ struct irq_chip *irqchip; struct irq_domain *irqdomain; unsigned int irq_base; irq_flow_handler_t irq_handler; unsigned int irq_default_type; #endif #if defined(CONFIG_OF_GPIO) /* * If CONFIG_OF is enabled, then all GPIO controllers described in the * device tree automatically may have an OF translation */ struct device_node *of_node; int of_gpio_n_cells; int (*of_xlate)(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, u32 *flags); };
下面是结构中每个元素的含义:
- request 是特定芯片激活的可选回调函数。如果提供了,在调用gpio_request()或gpiod_get()时,它会在分配GPIO之前执行。
- free 是一个可选的回调函数,用于特定芯片的释放。如果提供了,那么在调用gpiod_put()或gpio_free()时,它会在GPIO被释放之前执行。
- get_direction 在您需要知道方向的时候执行GPIO偏移量。返回值应为0表示out, 1表示in(与GPIOF_DIR_XXX相同),或负错误。
- direction_input 将信号偏移量offset配置为输入,否则返回错误。
- get 返回GPIO offset 的值;对于输出信号,这将返回实际感知到的值或0。
- set 指定一个输出值给GPIO offset。
- 当需要为 mask 定义的多个信号分配输出值时,调用 set_multiple。如果没有提供,内核将安装一个通用回调函数,它将遍历掩码位并在每个位执行chip->set(i)。
请看下面的代码,它展示了如何实现这个函数:
static void gpio_chip_set_multiple(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits) { if (chip->set_multiple) { chip->set_multiple(chip, mask, bits); } else { unsigned int i; /* set outputs if the corresponding mask bit is set */ for_each_set_bit(i, mask, chip->ngpio) chip->set(chip, i, test_bit(i, bits)); } }
- 如果控制器支持,这个钩子是一个可选的回调函数,用于为指定的GPIO设置防抖时间(GPIO设置为输入时可以设置防抖时间)。
- to_irq 是一个可选钩子,用于提供GPIO到IRQ的映射。当您想要执行gpio_to_irq()或gpiod_to_irq()函数时,就会调用这个函数。这个实现可能不会休眠。
- base 标识该芯片处理的第一个GPIO号;或者,如果注册时为负数,内核将自动(动态)分配一个。
- ngpio 是这个控制器提供的gpio数;它从 base 开始到 (base + ngpio - 1)。
- names,如果设置的话,对于这个芯片上的GPIOs,必须是一个字符串数组作为一个替代名称来使用。
- can_sleep 是一个布尔标志,如果get()/set()方法可以休眠,则设置它。对于位于总线上的GPIO控制器(也称为expander),例如I2C或SPI,它的访问可能导致睡眠。这意味着,如果芯片支持IRQ,这些IRQ需要被线程化,因为芯片访问可能会休眠,例如,读取IRQ状态寄存器。对于映射到内存(SoC的一部分)的GPIO控制器,这可以设置为false。
- irq_not_threads 是一个布尔值标志,如果设置了can_sleep,则必须设置irq_not_threads,但是IRQs不需要被线程化。
每个芯片导出了一些信号,在方法调用中通过0 (ngpio - 1)范围内的偏移值来识别。当这些信号通过诸如gpio_get_value(gpio)之类的调用被引用时,偏移量通过gpio数减去基数(base)来计算。
在定义了每个回调函数并设置了其他字段之后,您应该在配置的结构gpio_chip结构上调用gpiochip_add(),以便将控制器注册到内核。当需要注销时,请使用gpiochip_remove()。你可以发现,编写自己的GPIO控制器驱动程序是多么容易。
一个适用于MCP23016 I2C的GPIO控制器驱动程序来自microchip的I/O扩展器,其数据手册可在 http://ww1.microchip.com/downloads/en/DeviceDoc/20090C.pdf 上获得。
要编写GPIO controller驱动程序,你需要包含有以下的头文件:
#include <linux/gpio.h>
下面是控制器驱动程序的部分摘录:
#define GPIO_NUM 16 struct mcp23016 { struct i2c_client *client; struct gpio_chip chip; };
static int mcp23016_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct mcp23016 *mcp; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -EIO;
mcp = devm_kzalloc(&client->dev, sizeof(*mcp), GFP_KERNEL); if (!mcp) return -ENOMEM;
mcp->chip.label = client->name; mcp->chip.base = -1; mcp->chip.dev = &client->dev; mcp->chip.owner = THIS_MODULE; mcp->chip.ngpio = GPIO_NUM; /* 16 */ mcp->chip.can_sleep = 1; /* may not be accessed from atomic context */ mcp->chip.get = mcp23016_get_value; mcp->chip.set = mcp23016_set_value; mcp->chip.direction_output = mcp23016_direction_output; mcp->chip.direction_input = mcp23016_direction_input; mcp->client = client; i2c_set_clientdata(client, mcp);
return gpiochip_add(&mcp->chip); }
要从控制器驱动程序中请求一个自有的GPIO,你不应该使用gpio_request()。GPIO驱动程序可以使用以下函数来请求和释放描述符,而不必永远被固定在内核上:
struct gpio_desc *gpiochip_request_own_desc(struct gpio_desc *desc, const char *label) void gpiochip_free_own_desc(struct gpio_desc *desc)
使用gpiochip_request_own_desc()请求的描述符必须使用gpiochip_free_own_desc()释放。
Pin controller指南
取决于你写驱动程序的控制器,你可能需要实现一个引脚控制操作来处理引脚复用,配置,等等:
- 对于只能做简单GPIO的引脚控制器,一个简单的结构gpio_chip就足够处理它了。没有必要建立一个struct pinctrl_desc结构,只需写个GPIO控制器驱动程序。
- 如果控制器可以在GPIO功能之上产生中断,必须建立一个irq_chip结构并注册到IRQ子系统。
- 对于一个具有引脚复用、高级引脚驱动强度和复杂偏置的控制器,您应该设置以下三个接口:
- struct gpio_chip
- struct irq_chip
- struct pinctrl_desc,内核文档中有很好的解释Documentation/pinctrl.txt
GPIO控制器的Sysfs接口
gpiochip_add()成功后,将创建一个路径为/sys/class/gpio/gpiochipX/的目录条目,其中X是gpio控制器base(提供以#X开始的gpio的控制器),具有以下属性:
- base,其值与X相同,对应于gpio_chip.base(如果静态分配)并且是这个芯片管理的第一个GPIO。
- label,它是为诊断提供的(并不总是唯一的)。
- ngpio,它告诉了这个控制器提供了多少gpio (N 到 N + ngpio - 1).这与 gpio_chip.ngpios 中定义的相同。
以上所有属性都是只读的。
GPIO控制器和DT
在DT中声明的每个GPIO控制器都必须具有 gpio-controller 的布尔属性集。一些控制器提供映射到GPIO的IRQs。在这种情况下,也应该设置interrupt-cells属性;通常使用2,但这取决于需要。第一个 cell 是引脚号码,第二个 cell 代表中断标志。
应该设置gpio-cells,以确定使用多少个cell来描述GPIO指示符。通常使用<2>,第一个cell 用来标识GPIO号,第二个cell 用来标识标志。实际上,大多数非内存映射的GPIO控制器不使用标记:
expander_1: mcp23016@27 { compatible = "microchip,mcp23016"; interrupt-controller; gpio-controller; #gpio-cells = <2>; interrupt-parent = <&gpio6>; interrupts = <31 IRQ_TYPE_LEVEL_LOW>; reg = <0x27>; /* i2c slave address */ #interrupt-cells=<2>; };
上面的示例是我们的一个 gpio-controller设备(mcp23016)的设备树节点。
本文是编写GPIO控制器驱动程序的基础,它解释了这种设备主要用到的结构。