内核 GPIO 子系统
GPIO 子系统框架
func call
/**
* struct gpio_chip - abstract a GPIO controller
* @label: a functional name for the GPIO device, such as a part
* number or the name of the SoC IP-block implementing it.
* @gpiodev: the internal state holder, opaque struct
* @parent: optional parent device providing the GPIOs
* @owner: helps prevent removal of modules exporting active GPIOs
* @request: optional hook for chip-specific activation, such as
* enabling module power and clock; may sleep
* @free: optional hook for chip-specific deactivation, such as
* disabling module power and clock; may sleep
* @get_direction: returns direction for signal "offset", 0=out, 1=in,
* (same as GPIOF_DIR_XXX), or negative error
* @direction_input: configures signal "offset" as input, or returns error
* @direction_output: configures signal "offset" as output, or returns error
* @get: returns value for signal "offset", 0=low, 1=high, or negative error
* @get_multiple: reads values for multiple signals defined by "mask" and
* stores them in "bits", returns 0 on success or negative error
* @set: assigns output value for signal "offset"
* @set_multiple: assigns output values for multiple signals defined by "mask"
* @set_config: optional hook for all kinds of settings. Uses the same
* packed config format as generic pinconf.
* @to_irq: optional hook supporting non-static gpio_to_irq() mappings;
* implementation may not sleep
* @dbg_show: optional routine to show contents in debugfs; default code
* will be used when this is omitted, but custom code can show extra
* state (such as pullup/pulldown configuration).
* @base: identifies the first GPIO number handled by this chip;
* or, if negative during registration, requests dynamic ID allocation.
* DEPRECATION: providing anything non-negative and nailing the base
* offset of GPIO chips is deprecated. Please pass -1 as base to
* let gpiolib select the chip base in all possible cases. We want to
* get rid of the static GPIO number space in the long run.
* @ngpio: the number of GPIOs handled by this controller; the last GPIO
* handled is (base + ngpio - 1).
* @names: if set, must be an array of strings to use as alternative
* names for the GPIOs in this chip. Any entry in the array
* may be NULL if there is no alias for the GPIO, however the
* array must be @ngpio entries long. A name can include a single printk
* format specifier for an unsigned int. It is substituted by the actual
* number of the gpio.
* @can_sleep: flag must be set iff get()/set() methods sleep, as they
* must while accessing GPIO expander chips over I2C or SPI. This
* implies that if the chip supports IRQs, these IRQs need to be threaded
* as the chip access may sleep when e.g. reading out the IRQ status
* registers.
* @read_reg: reader function for generic GPIO
* @write_reg: writer function for generic GPIO
* @be_bits: if the generic GPIO has big endian bit order (bit 31 is representing
* line 0, bit 30 is line 1 ... bit 0 is line 31) this is set to true by the
* generic GPIO core. It is for internal housekeeping only.
* @reg_dat: data (in) register for generic GPIO
* @reg_set: output set register (out=high) for generic GPIO
* @reg_clr: output clear register (out=low) for generic GPIO
* @reg_dir: direction setting register for generic GPIO
* @bgpio_bits: number of register bits used for a generic GPIO i.e.
* <register width> * 8
* @bgpio_lock: used to lock chip->bgpio_data. Also, this is needed to keep
* shadowed and real data registers writes together.
* @bgpio_data: shadowed data register for generic GPIO to clear/set bits
* safely.
* @bgpio_dir: shadowed direction register for generic GPIO to clear/set
* direction safely.
*
* A gpio_chip can help platforms abstract various sources of GPIOs so
* they can all be accessed through a common programing interface.
* Example sources would be SOC controllers, FPGAs, multifunction
* chips, dedicated GPIO expanders, and so on.
*
* Each chip controls a number of signals, identified in method calls
* by "offset" values in the range 0..(@ngpio - 1). When those signals
* are referenced through calls like gpio_get_value(gpio), the offset
* is calculated by subtracting @base from the gpio number.
*/
struct gpio_chip {
const char *label;
struct gpio_device *gpiodev;
struct device *parent;
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);
int (*get_multiple)(struct gpio_chip *chip,
unsigned long *mask,
unsigned long *bits);
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_config)(struct gpio_chip *chip,
unsigned offset,
unsigned long config);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
int base;
u16 ngpio;
const char *const *names;
bool can_sleep;
...
};
gpio_chip 这个数据结构包含很多函数指针,抽象了 GPIO 的所有操作。这样就可以通过一个通用的接口来访问它们。这些函数指针指向具体的函数实现,不同的芯片有不同的实现。如下,是一个 rockchip 芯片的具体实现挂接。
static const struct gpio_chip rockchip_gpiolib_chip = {
.request = gpiochip_generic_request,
.free = gpiochip_generic_free,
.set = rockchip_gpio_set,
.get = rockchip_gpio_get, // 下面以这个举例
.get_direction = rockchip_gpio_get_direction,
.direction_input = rockchip_gpio_direction_input,
.direction_output = rockchip_gpio_direction_output,
.set_config = rockchip_gpio_set_config,
.to_irq = rockchip_gpio_to_irq,
.owner = THIS_MODULE,
};
/*
* Returns the level of the pin for input direction and setting of the DR
* register for output gpios.
*/
static int rockchip_gpio_get(struct gpio_chip *gc, unsigned offset)
{
struct rockchip_pin_bank *bank = gpiochip_get_data(gc);
u32 data;
clk_enable(bank->clk);
data = readl(bank->reg_base + GPIO_EXT_PORT); // 从内存映射的 I/O 空间读取数据,读取 32 位 (4 字节)
clk_disable(bank->clk);
data >>= offset;
data &= 1;
return data;
}
GPIO_EXT_PORT 这个就是寄存器地址了
/* GPIO control registers */
#define GPIO_SWPORT_DR 0x00
#define GPIO_SWPORT_DDR 0x04
#define GPIO_INTEN 0x30
#define GPIO_INTMASK 0x34
#define GPIO_INTTYPE_LEVEL 0x38
#define GPIO_INT_POLARITY 0x3c
#define GPIO_INT_STATUS 0x40
#define GPIO_INT_RAWSTATUS 0x44
#define GPIO_DEBOUNCE 0x48
#define GPIO_PORTS_EOI 0x4c
#define GPIO_EXT_PORT 0x50
#define GPIO_LS_SYNC 0x60
static inline u32 readl(const volatile void __iomem *addr)
{
u32 val;
asm volatile(
"%0 = memw(%1);"
: "=&r" (val)
: "r" (addr)
);
return val;
}
gpiochip_add(chip)
gpiochip_add_data(chip, NULL) // NULL 可以为 data,传递用户私有数据
gpiochip_add_data_with_key(chip, data, NULL, NULL)
Rockchip GPIO 实际分析流程
以 gpiochip_add() 为切入点
static int gb_gpio_probe(struct gbphy_device *gbphy_dev,
const struct gbphy_device_id *id)
{
struct gpio_chip *gpio;
...
ret = gpiochip_add(gpio);
...
return ret;
}
查看 struct gpio_chip 结构
上文中有贴出来,结构里有函数指针,这些函数指针肯定要指向 rockchip 的具体实现函数。全局搜索 .direction_output = r*
找到
static const struct gpio_chip rockchip_gpiolib_chip = {
.request = gpiochip_generic_request,
.free = gpiochip_generic_free,
.set = rockchip_gpio_set,
.get = rockchip_gpio_get,
.get_direction = rockchip_gpio_get_direction,
.direction_input = rockchip_gpio_direction_input,
.direction_output = rockchip_gpio_direction_output,
.set_config = rockchip_gpio_set_config,
.to_irq = rockchip_gpio_to_irq,
.owner = THIS_MODULE,
};
rockchip_xxx() 就是 rockchip 芯片的实际实现。对应到图一的『具体GPIO驱动(实现 gpio_chip)』。这些具体函数最终调用汇编,操作硬件寄存器,上文中也有提到。
开始向上追,查看哪里调用了 rockchip_gpiolib_chip
static int rockchip_gpiolib_register(struct platform_device *pdev,
struct rockchip_pinctrl *info)
{
struct rockchip_pin_ctrl *ctrl = info->ctrl;
struct rockchip_pin_bank *bank = ctrl->pin_banks;
struct gpio_chip *gc;
int ret;
int i;
for (i = 0; i < ctrl->nr_banks; ++i, ++bank) {
if (!bank->valid) {
dev_warn(&pdev->dev, "bank %s is not valid\n",
bank->name);
continue;
}
bank->gpio_chip = rockchip_gpiolib_chip;
gc = &bank->gpio_chip;
gc->base = bank->pin_base;
gc->ngpio = bank->nr_pins;
gc->parent = &pdev->dev;
gc->of_node = bank->of_node;
gc->label = bank->name;
ret = gpiochip_add_data(gc, bank);
if (ret) {
dev_err(&pdev->dev, "failed to register gpio_chip %s, error code: %d\n",
gc->label, ret);
goto fail;
}
}
rockchip_interrupts_register(pdev, info);
return 0;
fail:
for (--i, --bank; i >= 0; --i, --bank) {
if (!bank->valid)
continue;
gpiochip_remove(&bank->gpio_chip);
}
return ret;
}
按 bank 循环调用 gpiochip_add_data() ,将具体的实现函数注册到 GPIO 驱动框架。
继续向上追
static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
...
ret = rockchip_gpiolib_register(pdev, info);
...
}
继续
static struct platform_driver rockchip_pinctrl_driver = {
.probe = rockchip_pinctrl_probe,
.driver = {
.name = "rockchip-pinctrl",
.pm = &rockchip_pinctrl_dev_pm_ops,
.of_match_table = rockchip_pinctrl_dt_match,
},
};
static int __init rockchip_pinctrl_drv_register(void)
{
return platform_driver_register(&rockchip_pinctrl_driver);
}
postcore_initcall(rockchip_pinctrl_drv_register);
各种各样的 xxx_initcall() 包括我们用的最多的 module_init() 最终都沦陷成 __define_initcall()
,只不过使用的 level 不同
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
#define __initcall(fn) device_initcall(fn)
#define module_init(x) __initcall(x);
level 小的初始化函数先于 level 大的初始化函数被调用。
pinctrl 子系统和 gpio 子系统的关系
pinctrl 子系统的作用是 pin config(引脚配置)和 pin mux(引脚复用),而如果 pin 脚被复用为了 GPIO(注意:GPIO 功能只是 pin 脚功能的一种),就需要再借助 GPIO 子系统对 pin 脚进行控制了。
参考
RK3399平台开发系列讲解(内核子系统篇)2.1、GPIO子系统框架图解
Linux驱动之GPIO子系统和pinctrl子系统 狐狸教程 (freeaihub.com) 言简意赅。pincrtl 子系统和 gpio 子系统的关系