Firefly-RK3288/GPIO

来自Firefly wiki
跳转至: 导航搜索

Intro

GPIO, short for General-Purpose Input/Output, is a flexible software-controlled digital signal.

RK3288 has 9 GPIO banks: GPIO0,GPIO1, ..., GPIO8. Each bit of the bank is numbered with A0~A7, B0~B7, C0~C7, D0~D7 (Not all banks have full numbers, for example, GPIO5 only has B0~B7, C0~C3).

Besides the general-purpose input/output, GPIO may be multiplexed with other functions. For example, GPIO5_B4, has the following extra functions:

  • spi0_clk
  • ts0_data4
  • uart4exp_ctsn

The drive current, pull up/down and reset state of all the gpios are not neccessary the same. Please refer section titled "RK3288 function IO description" in RK3288 datasheet.

RK3288's GPIO driver is implemented in the following pinctrl file:

kernel/drivers/pinctrl/pinctrl-rockchip.c

The core logic is filling up methods and parameters of each GPIO bank before calling gpiochip_add to register into the kernel.

Usage

The development board has two power leds controlled by GPIO, which are:

Rk3288 leds schematic.png

From the schematic, the led will be on if outputing low voltage level from GPIO, and off if high voltage level otherwse.

Additionally, the extension slots have exported serveral GPIOs not used by system, which are:

Rk3288 gpios in extension.png

There GPIOs can be customized for input or output use.

Input/Output

We will use the power led driver as an example, to describe how to control GPIO in the kernel driver.

To begin with, you need to add resource description to the dts (Device Tree) file firefly-rk3288.dts (0930) or firefly-rk3288_beta.dts (0809):

  1. 	firefly-led{
  2. 		compatible = "firefly,led";
  3. 		led-work = <&gpio8 GPIO_A2 GPIO_ACTIVE_LOW>;
  4. 		led-power = <&gpio8 GPIO_A1 GPIO_ACTIVE_LOW>;
  5. 		status = "okay";
  6. 	};

Two led lights are declared here:

led-work  GPIO8_A2  GPIO_ACTIVE_LOW
led-power GPIO8_A1  GPIO_ACTIVE_LOW

GPIO_ACTIVE_LOW means low level voltage is effective for light on. If high level voltage is effective, replace it with GPIO_ACTIVE_HIGH.

Next, add requesting and controlling of GPIO to the device driver:

#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);
}

First call of_get_named_gpio_flags to read number and flag of the gpio named "led-power”. Use gpio_is_valid to check whether this gpio number is valid, then request this gpio from kernel with gpio_request. If errors occur, call gpio_free to free the gpio which was previously requested successfully.

Call gpio_direction_output to set gpio in output direction, with high or low output. Here the flag is GPIO_ACTIVE_LOW, in order to turn on the led, write 0 to it.

If you want to read from gpio, you need to set it in input direction before reading the value:

int val;
gpio_direction_input(your_gpio);
val = gpio_get_value(your_gpio);

Here are the definition of some commonly used GPIO APIs:

#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)

Multiplexing

The gpio has multiplexing functions. How to declare it, and how to switch it at the runtime? We take I2C4 for a brief descrition.

Here is what we find in the data sheet:

Pad# func0 func1
I2C4_SDA/GPIO7_C1 gpio7c1 i2c4tp_sda
I2C4_SCL/GPIO7_C2 gpio7c2 i2c4tp_scl

In /kernel/arch/arm/boot/dts/rk3288.dtsi, i2c4 is declared as below:

	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";
	};

Properties prefixed with "pinctrl-" are relative with mutiplexing:

  • pinctrl-names defines a state name list: default (i2c) and gpio.
  • pinctrl-0 defines the pinctrls which needs to apply in state 0: i2c4_sda 和 i2c4_scl
  • pinctrl-1 defines the pinctrls which needs to apply in state 1: 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 are defined in /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 is defined in /kernel/arch/arm/boot/dts/include/dt-bindings/pinctrl/rockchip.h:

#define FUNC_TO_GPIO(m)		((m) & 0xfff0)

That is to say: FUNC_TO_GPIO(I2C4TP_SDA) == GPIO7_C1, FUNC_TO_GPIO(I2C4TP_SCL) == GPIO7_C2 .

The encoding rule of value 0x7c11 is:

7 c1 1
| |  `- func
| `---- offset
`------ bank
0x7c11 represents GPIO7_C1 func1, i.e. i2c4tp_sda.

Therefore, when using multiplexing, if you select "default" (I2C function here) state, the kernel will apply pinctrls of i2c4_sda and i2c4_scl, which results in switching GPIO7_C1 pin and GPIO7_C2 pin to I2C functions i2c4_sda and i2c4_scl respectively; if you select "gpio" state, the kernel will apply the i2c4_gpio pinctrl, restoring the two pins back to general-purpose input/output function.

We'll check the i2c driver file /kernel/drivers/i2c/busses/i2c-rockchip.c, to find out how to switch the multiplexing functions:

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);
// ...
}

First call of_get_gpio to get the gpio pins from the "gpios" list property in the i2c4 node in device tree:

gpios = <&gpio7 GPIO_C1 GPIO_ACTIVE_LOW>, <&gpio7 GPIO_C2 GPIO_ACTIVE_LOW>;

Then call devm_gpio_request request gpio, followed by calling pinctrl_lookup_state to look up the “gpio” state. The "default" state is already saved in i2c->dev-pins->default_state by the kernel device tree parsing.

Finally, call pinctrl_select_state to select between "default" or "gpio" state, which results in the correspoding multiplexing function.

Here are some commonly used multiplexing APIs:

#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);