Skip to content

Latest commit

 

History

History
212 lines (166 loc) · 10.3 KB

File metadata and controls

212 lines (166 loc) · 10.3 KB

十五、通用输入输出控制器驱动——通用输入输出芯片

在前一章中,我们讨论了 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返回 GPIO offset的值;对于输出信号,这将返回实际检测到的值或零。
  • set将输出值分配给 GPIO offset
  • 当需要为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的内核文档中有很好的解释

GPIO 控制器的 Sysfs 接口

成功gpiochip_add()后,将创建一个路径类似/sys/class/gpio/gpiochipX/的目录条目,其中X是 GPIO 控制器库(从#X开始提供 GPIO 的控制器),具有以下属性:

  • base,其值与X相同,对应gpio_chip.base(如果静态赋值),是该芯片管理的第一个 GPIO。
  • label,用于诊断(不总是唯一的)。
  • ngpio,告知该控制器提供多少个 GPIOs】)。这与gpio_chip.ngpios中的定义相同。

所有上述属性都是只读的。

GPIO 控制器和 DT

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 扩展器的驱动中添加此类功能。