目錄
[隐藏]
- 1 簡介
- 2 使用
- 2.1 輸入輸出
- 2.2 複用
簡介
GPIO, 全稱 General-Purpose Input/Output(通用輸入輸出),是一種軟體運作期間能夠動态配置和控制的通用引腳。
RK3288 有 9 組 GPIO bank: GPIO0,GPIO1, ..., GPIO8。每組又以 A0~A7, B0~B7, C0~C7, D0~D7 作為編号區分(不是所有 bank 都有全部編号,例如 GPIO5 就隻有 B0~B7, C0~C3)。
每個 GPIO 口除了通用輸入輸出功能外,還可能有其它複用功能,例如 GPIO5_B4,可以複用成以下功能之一:
- spi0_clk
- ts0_data4
- uart4exp_ctsn
每個 GPIO 口的驅動電流、上下拉和重置後的初始狀态都不盡相同,詳細情況請參考《RK3288 規格書》中的 "RK3288 function IO description" 一章。
RK3288 的 GPIO 驅動是在以下 pinctrl 檔案中實作的:
kernel/drivers/pinctrl/pinctrl-rockchip.c
其核心是填充 GPIO bank 的方法和參數,并調用 gpiochip_add 注冊到核心中。
使用
開發闆有兩個電源 LED 燈是 GPIO 口控制的,分别是:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SN2UDO4IWYzUjMmRGO5ADMzYzX5MDOxYTMwIzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
從電路圖上看,GPIO 口輸出低電平時燈亮,高電平時燈滅。
另外,擴充槽上引出了幾個空閑的 GPIO 口,分别是:
這幾個 GPIO 口可以自定義作輸入、輸出使用。
輸入輸出
下面以電源 LED 燈的驅動為例,講述如何在核心編寫代碼控制 GPIO 口的輸出。
首先需要在 dts (Device Tree) 檔案 firefly-rk3288.dts (0930版) 或 firefly-rk3288_beta.dts (0809版) 中增加驅動的資源描述:
firefly-led{compatible = "firefly,led";led-work = <&gpio8 GPIO_A2 GPIO_ACTIVE_LOW>;led-power = <&gpio8 GPIO_A1 GPIO_ACTIVE_LOW>;status = "okay";};
這裡定義了兩顆 LED 燈的 GPIO 設定:
led-work GPIO8_A2 GPIO_ACTIVE_LOW
led-power GPIO8_A1 GPIO_ACTIVE_LOW
GPIO_ACTIVE_LOW 表示低電平有效(燈亮),如果是高電平有效,需要替換為 GPIO_ACTIVE_HIGH 。
之後在驅動程式中加入對 GPIO 口的申請和控制則可:
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_gpio.h>
#endif
static int firefly_led_probe(struct platform_device *pdev)
{
int ret = -1;
int gpio, flag;
struct device_node *led_node = pdev->dev.of_node;
gpio = of_get_named_gpio_flags(led_node, "led-power", 0, &flag);
if (!gpio_is_valid(gpio)){
printk("invalid led-power: %d\n",gpio);
return -1;
}
if (gpio_request(gpio, "led_power")) {
printk("gpio %d request failed!\n",gpio);
return ret;
}
led_info.power_gpio = gpio;
led_info.power_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0 : 1;
gpio_direction_output(led_info.power_gpio, !(led_info.power_enable_value));
...
on_error:
gpio_free(gpio);
}
of_get_named_gpio_flags 從裝置樹中讀取 led-power 的 GPIO 配置編号和标志,gpio_is_valid 判斷該 GPIO 編号是否有效,gpio_request 則申請占用該 GPIO。如果初始化過程出錯,需要調用 gpio_free 來釋放之前申請過且成功的 GPIO 。
調用 gpio_direction_output 就可以設定輸出高還是低電平,因為是 GPIO_ACTIVE_LOW ,如果要燈亮,需要寫入 0 。
實際中如果要讀出 GPIO,需要先設定成輸入模式,然後再讀取值:
int val;
gpio_direction_input(your_gpio);
val = gpio_get_value(your_gpio);
下面是常用的 GPIO API 定義:
#include <linux/gpio.h>
#include <linux/of_gpio.h>
enum of_gpio_flags {
OF_GPIO_ACTIVE_LOW = 0x1,
};
int of_get_named_gpio_flags(struct device_node *np, const char *propname,
int index, enum of_gpio_flags *flags);
int gpio_is_valid(int gpio);
int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);
int gpio_direction_input(int gpio);
int gpio_direction_output(int gpio, int v)
複用
如何定義 GPIO 有哪些功能可以複用,在運作時又如何切換功能呢?以 I2C4 為例作簡單的介紹。
查規格表可知,I2C4_SDA 與 GPIO7C1 的功能定義如下:
Pad# | func0 | func1 |
I2C4_SDA/GPIO7_C1 | gpio7c1 | i2c4tp_sda |
I2C4_SCL/GPIO7_C2 | gpio7c2 | i2c4tp_scl |
在 /kernel/arch/arm/boot/dts/rk3288.dtsi 裡有:
i2c4: i2c@ff160000 {
compatible = "rockchip,rk30-i2c";
reg = <0xff160000 0x1000>;
interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default", "gpio";
pinctrl-0 = <&i2c4_sda &i2c4_scl>;
pinctrl-1 = <&i2c4_gpio>;
gpios = <&gpio7 GPIO_C1 GPIO_ACTIVE_LOW>, <&gpio7 GPIO_C2 GPIO_ACTIVE_LOW>;
clocks = <&clk_gates6 15>;
rockchip,check-idle = <1>;
status = "disabled";
};
此處,跟複用控制相關的是 pinctrl- 開頭的屬性:
- pinctrl-names 定義了狀态名稱清單: default (i2c 功能) 和 gpio 兩種狀态。
- pinctrl-0 定義了狀态 0 (即 default)時需要設定的 pinctrl: i2c4_sda 和 i2c4_scl
- pinctrl-1 定義了狀态 1 (即 gpio)時需要設定的 pinctrl: i2c4_gpio
這些 pinctrl 在 /kernel/arch/arm/boot/dts/rk3288-pinctrl.dtsi 中定義:
/ {
pinctrl: pinctrl@ff770000 {
compatible = "rockchip,rk3288-pinctrl";
...
gpio7_i2c4 {
i2c4_sda:i2c4-sda {
rockchip,pins = <I2C4TP_SDA>;
rockchip,pull = <VALUE_PULL_DISABLE>;
rockchip,drive = <VALUE_DRV_DEFAULT>;
//rockchip,tristate = <VALUE_TRI_DEFAULT>;
};
i2c4_scl:i2c4-scl {
rockchip,pins = <I2C4TP_SCL>;
rockchip,pull = <VALUE_PULL_DISABLE>;
rockchip,drive = <VALUE_DRV_DEFAULT>;
//rockchip,tristate = <VALUE_TRI_DEFAULT>;
};
i2c4_gpio: i2c4-gpio {
rockchip,pins = <FUNC_TO_GPIO(I2C4TP_SDA)>, <FUNC_TO_GPIO(I2C4TP_SCL)>;
rockchip,drive = <VALUE_DRV_DEFAULT>;
};
};
...
}
}
I2C4TP_SDA, I2C4TP_SCL 的定義在 /kernel/arch/arm/boot/dts/include/dt-bindings/pinctrl/rockchip-rk3288.h 中:
#define GPIO7_C1 0x7c10
#define I2C4TP_SDA 0x7c11
#define GPIO7_C2 0x7c20
#define I2C4TP_SCL 0x7c21
FUN_TO_GPIO 的定義在 /kernel/arch/arm/boot/dts/include/dt-bindings/pinctrl/rockchip.h 中:
#define FUNC_TO_GPIO(m) ((m) & 0xfff0)
也就是說 FUNC_TO_GPIO(I2C4TP_SDA) == GPIO7_C1, FUNC_TO_GPIO(I2C4TP_SCL) == GPIO7_C2 。
像 0x7c11 這樣的值是有編碼規則的:
7 c1 1
| | `- func
| `---- offset
`------ bank
0x7c11 就表示 GPIO7_C1 func1, 即 i2c4tp_sda 。
在複用時,如果選擇了 "default" (即 i2c 功能),系統會應用 i2c4_sda 和 i2c4_scl 這兩個 pinctrl,最終得将 GPIO7_C1 和 GPIO7_C2 兩個針腳切換成對應的 i2c 功能;而如果選擇了 "gpio" ,系統會應用 i2c4_gpio 這個 pinctrl,将 GPIO7_C1 和 GPIO7_C2 兩個針腳還原為 GPIO 功能。
我們看看 i2c 的驅動程式 /kernel/drivers/i2c/busses/i2c-rockchip.c 是如何切換複用功能的:
static int rockchip_i2c_probe(struct platform_device *pdev)
{
struct rockchip_i2c *i2c = NULL;
struct resource *res;
struct device_node *np = pdev->dev.of_node;
int ret;
// ...
i2c->sda_gpio = of_get_gpio(np, 0);
if (!gpio_is_valid(i2c->sda_gpio)) {
dev_err(&pdev->dev, "sda gpio is invalid\n");
return -EINVAL;
}
ret = devm_gpio_request(&pdev->dev, i2c->sda_gpio, dev_name(&i2c->adap.dev));
if (ret) {
dev_err(&pdev->dev, "failed to request sda gpio\n");
return ret;
}
i2c->scl_gpio = of_get_gpio(np, 1);
if (!gpio_is_valid(i2c->scl_gpio)) {
dev_err(&pdev->dev, "scl gpio is invalid\n");
return -EINVAL;
}
ret = devm_gpio_request(&pdev->dev, i2c->scl_gpio, dev_name(&i2c->adap.dev));
if (ret) {
dev_err(&pdev->dev, "failed to request scl gpio\n");
return ret;
}
i2c->gpio_state = pinctrl_lookup_state(i2c->dev->pins->p, "gpio");
if (IS_ERR(i2c->gpio_state)) {
dev_err(&pdev->dev, "no gpio pinctrl state\n");
return PTR_ERR(i2c->gpio_state);
}
pinctrl_select_state(i2c->dev->pins->p, i2c->gpio_state);
gpio_direction_input(i2c->sda_gpio);
gpio_direction_input(i2c->scl_gpio);
pinctrl_select_state(i2c->dev->pins->p, i2c->dev->pins->default_state);
// ...
}
首先是調用 of_get_gpio 取出裝置樹中 i2c4 結點的 gpios 屬于所定義的兩個 gpio:
gpios = <&gpio7 GPIO_C1 GPIO_ACTIVE_LOW>, <&gpio7 GPIO_C2 GPIO_ACTIVE_LOW>;
然後是調用 devm_gpio_request 來申請 gpio,接着是調用 pinctrl_lookup_state 來查找 “gpio” 狀态,而預設狀态 "default" 已經由架構儲存到 i2c->dev-pins->default_state 中了。
最後調用 pinctrl_select_state 來選擇是 "default" 還是 "gpio" 功能。
#include <linux/pinctrl/consumer.h>
struct device {
//...
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
//...
};
struct dev_pin_info {
struct pinctrl *p;
struct pinctrl_state *default_state;
#ifdef CONFIG_PM
struct pinctrl_state *sleep_state;
struct pinctrl_state *idle_state;
#endif
};
struct pinctrl_state * pinctrl_lookup_state(struct pinctrl *p, const char *name);
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);