GPIO控制器驱动- gpio_chip

前一篇文章中,我们处理了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控制器驱动程序的基础,它解释了这种设备主要用到的结构。

posted @ 2021-03-04 14:58  王楼小子  阅读(1351)  评论(0编辑  收藏  举报