Device Tree

dts/binding/gpio/gpio-controller.yaml描述了设备树中支持的属性。

gpio-controller节点:
其中gpio-controllergpio-cells两个属性是必须的。
前者表示gpio controller节点,后者表示其他节点使用gpio specifier需要几个item来描述。

gpio: gpio@0x400ff000 {
	compatible = "nxp, kinetis-gpio";
	status = "disabled";
	reg = <0x400ff000 0x40>;
	interrupts = <59 2>;
	gpio-controller;
	#gpio-cells = <2>;
	ngpios = <32>; // optional, default 32
	gpio-reserved-ranges = <3 2>, <10, 1>; // optional, <index size>表示第<index>起<size>个gpio不能使用。
};

consumer节点:

xxx-gpios/gpios = <&<gpio-controller节点> <gpio_number> <配置属性>>

xxx-gpios = <&gpio 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>,
		<&gpio 6 (GPIO_PULL_DOWN | GPIO_ACTIVE_LOW)>;

GPIO_ACTIVE_LOW表示逻辑电平和实际电平相反,逻辑1代表低电平。

配置属性可在include/zephyr/dt-bindings/gpio/gpio.h中查找。

Interrupt相关flags查阅gpio.h,是多个bit的组合,上图没有完全列出。

Consumer

方法一: 设备树API

从设备树获取的gpio信息会保存到gpio_dt_spec结构体。包括gpio controller, pin number, config flag。对应上面device tree中consumer节点的node。

struct gpio_dt_spec {
	/** GPIO device controlling the pin */
	const struct device *port;
	/** The pin's number on the device */
	gpio_pin_t pin;
	/** The pin's configuration flags as specified in devicetree */
	gpio_dt_flags_t dt_flags;
};

可以通过GPIO_DT_SPEC_GET_BY_IDX()或其他一系列变种函数解析dts获取gpio_dt_spec结构体。
node_id对应consumer的node,propxxx-gpios, idxxxx-gpios下的第x条属性。

#define GPIO_DT_SPEC_GET_BY_IDX(node_id, prop, idx)			       \
	{								       \
		.port = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(node_id, prop, idx)),\
		.pin = DT_GPIO_PIN_BY_IDX(node_id, prop, idx),		       \
		.dt_flags = DT_GPIO_FLAGS_BY_IDX(node_id, prop, idx),	       \
	}

得到gpio_dt_spec结构体后,就可以调用一系列设备树函数来进行GPIO操作了。

// 判断gpio controller是否ready
static inline bool gpio_is_ready_dt(const struct gpio_dt_spec *spec);
/** 设置GPIO中断, 可传入的gpio_flags_t有, 具体含义参考drivers/gpio.h:
 * GPIO_INT_EDGE_RISING
 * GPIO_INT_EDGE_FALLING
 * GPIO_INT_EDGE_BOTH
 * GPIO_INT_LEVEL_LOW
 * GPIO_INT_LEVEL_HIGH
 * ...
 **/
static inline int gpio_pin_interrupt_configure_dt(const struct gpio_dt_spec *spec,
						  gpio_flags_t flags);
// 配置一个pin的config, 结果为spec->dt_flags | extra_flags
static inline int gpio_pin_configure_dt(const struct gpio_dt_spec *spec,
					gpio_flags_t extra_flags)
// 判断pin是否为input
static inline int gpio_pin_is_input_dt(const struct gpio_dt_spec *spec)
// 判断pin是否为output
static inline int gpio_pin_is_output_dt(const struct gpio_dt_spec *spec)
// 获取pin的config保存到flags中
static inline int gpio_pin_get_config_dt(const struct gpio_dt_spec *spec,
					gpio_flags_t *flags)
// 获取input pin value
static inline int gpio_pin_get_dt(const struct gpio_dt_spec *spec)
// 设置ouput pin value
static inline int gpio_pin_set_dt(const struct gpio_dt_spec *spec, int value)
// toggle gpio
static inline int gpio_pin_toggle_dt(const struct gpio_dt_spec *spec)
// 增加gpio中断的callback函数
static inline int gpio_add_callback_dt(const struct gpio_dt_spec *spec,
					struct gpio_callback *callback)

方法二: 直接传入device结构体,不需要从设备树获取数据。

可以通过DEVICE_DT_GET宏得到device结构体。
include/zephyr/devicetree/gpio.h中封装了许多gpio操作devicetree的宏,用来获取gpio pin flag等属性。

和上面的device tree APIs的底层实现是相同的,不过这些APIs需要指定pin, flags等参数传入。

// 配置gpio中断相关config, interrupt edge/level...
__syscall int gpio_pin_interrupt_configure(const struct device *port,
					   gpio_pin_t pin,
					   gpio_flags_t flags);
// 配置gpio相关config, output/input, pull up/down...
__syscall int gpio_pin_configure(const struct device *port,
				 gpio_pin_t pin,
				 gpio_flags_t flags);
__syscall int gpio_port_get_direction(const struct device *port, gpio_port_pins_t map,
				      gpio_port_pins_t *inputs, gpio_port_pins_t *outputs);
__syscall int gpio_pin_get_config(const struct device *port, gpio_pin_t pin,
				  gpio_flags_t *flags);
// 获取input value
__syscall int gpio_port_get_raw(const struct device *port,
				gpio_port_value_t *value);
// 设置output value
__syscall int gpio_port_set_masked_raw(const struct device *port,
				       gpio_port_pins_t mask,
				       gpio_port_value_t value);
// 把gpio_port_pins_t(U32) pins中对应置1的output high
__syscall int gpio_port_set_bits_raw(const struct device *port,
				     gpio_port_pins_t pins);
// 与上面相反,output low
__syscall int gpio_port_clear_bits_raw(const struct device *port,
				       gpio_port_pins_t pins);
__syscall int gpio_port_toggle_bits(const struct device *port,
				    gpio_port_pins_t pins);
__syscall int gpio_get_pending_int(const struct device *dev);

注意设备树的API获取的都是单个pin的信息,而方法二的API例如gpio_port_get_raw等都是获取一整个gpio port的信息。

Driver Example

i2c_ite_enhance.cdev->config中保存了两个gpio_dt_spec结构体:

struct i2c_enhance_config {
	//...
	/* SCL GPIO cells */
	struct gpio_dt_spec scl_gpios;
	/* SDA GPIO cells */
	struct gpio_dt_spec sda_gpios;
	//...
};

初始化时通过GPIO_DT_SPEC_INST_GET宏从设备树获取数据填充gpio_dt_spec

i2c0: i2c@f04300 {
	compatible = "ite,enhance-i2c";
	scl-gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>;
	sda-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>;
};
static const struct i2c_enhance_config i2c_enhance_cfg_##inst = {       \
	//...
	.scl_gpios = GPIO_DT_SPEC_INST_GET(inst, scl_gpios),            \
	.sda_gpios = GPIO_DT_SPEC_INST_GET(inst, sda_gpios),            \
	//...
};

i2c_enhance_recover_bus函数中获取dev->config, 就可以调用gpio相关api了。

const struct i2c_enhance_config *config = dev->config;
/* Set SCL of I2C as GPIO pin */
gpio_pin_configure_dt(&config->scl_gpios, GPIO_OUTPUT);
/* Set SDA of I2C as GPIO pin */
gpio_pin_configure_dt(&config->sda_gpios, GPIO_OUTPUT);

/* Pull SCL and SDA pin to high */
gpio_pin_set_dt(&config->scl_gpios, 1);
gpio_pin_set_dt(&config->sda_gpios, 1);

GPIO Interrupt

增加gpio中断的callback处理函数:

static inline void gpio_init_callback(struct gpio_callback *callback,
				      gpio_callback_handler_t handler,
				      gpio_port_pins_t pin_mask)

static inline int gpio_add_callback(const struct device *port,
				    struct gpio_callback *callback)

Test Example

tests/drivers/gpio/目录下有许多gpio相关的测试程序。

Provider

可以实现的API:

__subsystem struct gpio_driver_api {
	// 配置output/input, pull up/down,
	int (*pin_configure)(const struct device *port, gpio_pin_t pin,
			     gpio_flags_t flags);
#ifdef CONFIG_GPIO_GET_CONFIG
	int (*pin_get_config)(const struct device *port, gpio_pin_t pin,
			      gpio_flags_t *flags);
#endif
	int (*port_get_raw)(const struct device *port,
			    gpio_port_value_t *value);
	int (*port_set_masked_raw)(const struct device *port,
				   gpio_port_pins_t mask,
				   gpio_port_value_t value);
	int (*port_set_bits_raw)(const struct device *port,
				 gpio_port_pins_t pins);
	int (*port_clear_bits_raw)(const struct device *port,
				   gpio_port_pins_t pins);
	int (*port_toggle_bits)(const struct device *port,
				gpio_port_pins_t pins);
	int (*pin_interrupt_configure)(const struct device *port,
				       gpio_pin_t pin,
				       enum gpio_int_mode, enum gpio_int_trig);
	int (*manage_callback)(const struct device *port,
			       struct gpio_callback *cb,
			       bool set);
	uint32_t (*get_pending_int)(const struct device *dev);
#ifdef CONFIG_GPIO_GET_DIRECTION
	int (*port_get_direction)(const struct device *port, gpio_port_pins_t map,
				  gpio_port_pins_t *inputs, gpio_port_pins_t *outputs);
#endif /* CONFIG_GPIO_GET_DIRECTION */
};

Driver需要定义一个device实例,其中dev->configdev->data两个自定义结构体,需要首先包含struct gpio_driver_configstruct gpio_driver_data两个通用结构体,以gpio_dw.h为例:

struct gpio_dw_config {
	/* gpio_driver_config needs to be first */
	struct gpio_driver_config common;
	uint32_t ngpios;
	uint32_t irq_num; /* set to 0 if GPIO port cannot interrupt */
	gpio_config_irq_t config_func;
};

struct gpio_dw_runtime {
	/* gpio_driver_data needs to be first */
	struct gpio_driver_data common;
	uint32_t base_addr;
	sys_slist_t callbacks;
};
struct gpio_driver_config {
	/** Mask identifying pins supported by the controller.
	 *
	 * Initialization of this mask is the responsibility of device
	 * instance generation in the driver.
	 */
	gpio_port_pins_t port_pin_mask;
};

struct gpio_driver_data {
	/** Mask identifying pins that are configured as active low.
	 *
	 * Management of this mask is the responsibility of the
	 * wrapper functions in this header.
	 */
	gpio_port_pins_t invert;
};

// port_pin_mask需要在gpio controller driver中从设备树gpio-reserved-ranges获取,表示需要跳过的gpios。以gpio_dw.c为例:
static const struct gpio_dw_config gpio_dw_config_##n = {				\
	.common = {									\
		.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n),			\
	},										\
	.irq_num = COND_CODE_1(DT_INST_IRQ_HAS_IDX(n, 0), (DT_INST_IRQN(n)), (0)),	\
	.ngpios = DT_INST_PROP(n, ngpios),						\
	.config_func = gpio_config_##n##_irq,						\
};

// invert会在抽象层gpio.h定义,不需要driver来实现,从设备树GPIO_ACTIVE_HIGH/LOW获取。

GPIO SHELL

Zephyr 在shell中提供了一个gpio命令来操作pins, 具体是现在gpio_shell.c中。

uart:~$ gpio
gpio - GPIO commands
Subcommands:
  conf   : Configure GPIO pin
          Usage: gpio conf <device> <pin> <configuration <i|o>[u|d][h|l][0|1]>
          [vendor specific]
          <i|o> - input|output
          [u|d] - pull up|pull down, otherwise open
          [h|l] - active high|active low, otherwise defaults to active high
          [0|1] - initialise to logic 0|logic 1, otherwise defaults to logic 0
          [vendor specific] - configuration flags within the mask 0xFF00
          see include/zephyr/dt-bindings/gpio/
  get    : Get GPIO pin value
          Usage: gpio get <device> <pin>
  set    : Set GPIO pin value
          Usage: gpio set <device> <pin> <level 0|1>
  blink  : Blink GPIO pin
          Usage: gpio blink <device> <pin>
  info   : GPIO Information
          Usage: gpio info [device]

uart:~$ gpio info
  Line         Reserved Device           Pin
                         gpio@40110180     0
                *        gpio@40110180     1
                *        gpio@40110180     2
                *        gpio@40110180     3
                         gpio@40110180     4
                         gpio@40110180     5
                         gpio@40110180     6
                         gpio@40110180     7
                         gpio@40110180     8
                         gpio@40110180     9

uart:~$ gpio conf gpio@40110180 20 o1 # 配置pin
uart:~$ gpio get gpio@40110180 20 # 获取pin input value