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控制器驅動程序的基礎,它解釋了這種設備主要用到的結構。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM