在前一章中,我们讨论了 GPIO 线。这些线路通过一种称为 GPIO 控制器的特殊设备暴露在系统中。本章将逐步解释如何为这类设备编写驱动,从而涵盖以下主题:
- GPIO 控制器驱动架构和数据结构
- 用于 GPIO 控制器的 Sysfs 接口
- 数据传输中的通用输入输出控制器表示
此类设备的驱动应提供:
- 建立 GPIO 方向(输入和输出)的方法。
- 用于访问 GPIO 值的方法(获取和设置)。
- 方法将给定的 GPIO 映射到 IRQ 并返回关联的数字。
- 表示对其方法的调用是否可以休眠的标志,这非常重要。
- 可选的
debugfs dump
方法(显示额外的状态,如上拉配置)。 - 称为基数的可选数字,GPIO 编号应从该数字开始。如果省略,它将被自动分配。
在内核中,GPIO 控制器被表示为struct gpio_chip
的一个实例,在linux/gpio/driver.h
中定义:
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 之前执行。- 每当需要知道 GPIO
offset
的方向时,就会执行get_direction
。返回值应为 0 表示出,1 表示入,(与GPIOF_DIR_XXX
相同),或者为负误差。 direction_input
将信号offset
配置为输入,否则返回错误。get
返回 GPIOoffset
的值;对于输出信号,这将返回实际检测到的值或零。set
将输出值分配给 GPIOoffset
。- 当需要为
mask
定义的多个信号分配输出值时,调用set_multiple
。如果没有提供,内核将安装一个通用钩子,遍历mask
位并在每个位集上执行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));
}
}
set_debounce
如果控制器支持,这个钩子是一个可选的回调,用于为指定的 GPIO 设置去抖时间。to_irq
是提供 GPIO 到 IRQ 映射的可选钩子。每当要执行gpio_to_irq()
或gpiod_to_irq()
功能时,都会调用这个函数。此实现可能不会休眠。base
标识该芯片处理的第一个 GPIO 号;或者,如果在注册期间为负,内核将自动(动态)分配一个。ngpio
是该控制器提供的 GPIOs 数量,从base
开始,到(base + ngpio - 1)
结束。names
,如果设置,必须是字符串数组,用作该芯片中 GPIOs 的替代名称。数组必须是ngpio
大小,任何不需要别名的 GPIO 都可以将其条目设置为数组中的NULL
。can_sleep
是一个布尔标志,如果get()
/set()
方法可能休眠,则设置该标志。GPIO 控制器(也称为扩展器)位于总线上就是这种情况,例如 I2C 或 SPI,其访问可能会导致睡眠。这意味着,如果芯片支持 IRQ,这些 IRQ 需要线程化,因为芯片访问可能会休眠,例如在读取 IRQ 状态寄存器时。对于映射到内存(SoC 的一部分)的 GPIO 控制器,这可以设置为 false。irq_not_threaded
是布尔标志,如果设置了can_sleep
,则必须设置,但是 IRQs 不需要线程化。
Each chip exposes a number of signals, identified in method calls by offset values in the range 0 (ngpio - 1
). When those signals are referenced through calls like gpio_get_value(gpio)
, the offset is calculated by subtracting base from the GPIO number.
在定义了每个回调并设置了其他字段之后,应该在配置的struct gpio_chip
结构上调用gpiochip_add()
,以便向内核注册控制器。说到注销,使用gpiochip_remove()
。仅此而已。您可以看到编写自己的 GPIO 控制器驱动是多么容易。在图书资源库中,您将找到一个工作正常的 GPIO 控制器驱动,用于微芯片的 MCP23016 I2C 输入/输出扩展器,其数据表可在http://ww1.microchip.com/downloads/en/DeviceDoc/20090C.pdf获得。
要编写这样的驱动,您应该包括:
#include <linux/gpio.h>
以下是我们为控制器编写的驱动的摘录,只是为了向您展示编写 GPIO 控制器驱动的任务有多简单:
#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 actomic 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()
发布。
根据为其编写驱动的控制器,您可能需要实现一些引脚控制操作来处理引脚多路复用、配置等:
- 对于一个只能做简单 GPIO 的管脚控制器,一个简单的
struct gpio_chip
就足够处理了。不需要设置struct pinctrl_desc
结构,只需要把 GPIO 控制器驱动写成它就可以了。 - 如果控制器能够在 GPIO 功能的基础上产生中断,必须设置一个
struct irq_chip
并注册到 IRQ 子系统。 - 对于具有引脚多路复用、高级引脚驱动强度、复杂偏置的控制器,应设置以下三个接口:
struct gpio_chip
,本章前面讨论过struct irq_chip
,下一章讨论(第 16 章、高级 IRQ 管理struct pinctrl_desc
,书中没有讨论,但在Documentation/pinctrl . txt的内核文档中有很好的解释
成功gpiochip_add()
后,将创建一个路径类似/sys/class/gpio/gpiochipX/
的目录条目,其中X
是 GPIO 控制器库(从#X
开始提供 GPIO 的控制器),具有以下属性:
base
,其值与X
相同,对应gpio_chip.base
(如果静态赋值),是该芯片管理的第一个 GPIO。label
,用于诊断(不总是唯一的)。ngpio
,告知该控制器提供多少个 GPIOs】)。这与gpio_chip.ngpios
中的定义相同。
所有上述属性都是只读的。
DT 中声明的每个 GPIO 控制器都必须设置布尔属性gpio-controller
。一些控制器提供映射到 GPIO 的 IRQ。在这种情况下,属性interrupt-cells
也应该设置,通常使用2
,但这取决于需要。第一个单元是引脚号,第二个单元代表中断标志。
gpio-cells
应设置为标识有多少个单元用于描述一个 GPIO 说明符。一个通常使用<2>
,第一个单元格标识 GPIO 号,第二个用于标志。实际上,大多数非内存映射的 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>;
#interrupt-cells=<2>;
};
前面的例子是我们的 GPIO-controller 设备的节点,完整的设备驱动提供了这本书的源代码。
本章不仅仅是为您可能遇到的 GPIO 控制器编写驱动的基础。它解释了描述这种设备的主要结构。下一章涉及高级 IRQ 管理,我们将在其中看到如何管理中断控制器,从而在微芯片的 MCP23016 扩展器的驱动中添加此类功能。