在前一篇文章中,我們處理了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控制器驅動程序的基礎,它解釋了這種設備主要用到的結構。