08、GPIO 控制器驱动
控制器需要实现以下功能:
- 设置GPIO方向
提供设置GPIO引脚为输入或输出模式的方法。 - 读写GPIO值
包含两种方法:
- 读取指定GPIO引脚的当前电平状态
- 设置指定GPIO引脚的输出电平状态
- GPIO到中断映射
能够将指定的GPIO引脚与中断请求(IRQ)关联,并返回对应的中断编号。 - 睡眠状态标识
每个方法必须明确标注:在调用过程中是否会进入睡眠状态(等待其他资源/操作完成)。这非常重要! - 可选调试功能
可选实现debugfs接口的dump方法,用于输出额外状态信息(如上拉电阻配置等调试数据)
每个功能点都使用简单直白的表述,避免技术术语堆砌,同时保持逻辑顺序合理。关键点(如第4项)通过加粗和强调标注来突出重要性。
一、数据结构
C
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
/*
* 通过CONFIG_GPIOLIB_IRQCHIP,我们在gpiolib中获得了一个irqchip
* 用于处理大多数实际情况下的IRQ
*/
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)
/*
* 如果启用了CONFIG_OF,那么设备树中描述的所有GPIO控制器都将
* 自动具有一个转换
*/
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
二、RK GPIO控制器
- request:该参数为选项,它是芯片特有的激活钩子函数,如果提供,每当调用gpio_request()或gpiod_get()分配GPIO之前执行它。
- free:该参数为选项,它是芯片特有的停用钩子函数,如果提供,每当调用gpiod_put()或gpio_free()时在释放GPIO之前执行它。
- get_direction:当需要了解GPIO offset的方向时执行它。返回0表示输出,返回1表示输入(与GPIOF_DIR_XXX相同),出错时返回负值。
- direction_input:将信号offset配置为输入,否则返回错误。
- get:返回GPIO offset的值。对于输出信号,返回实际检测到的值或零。
- set:将输出值赋给GPIO offset。
C
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,
};
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
C
static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank)
{
struct gpio_chip *gc;
int ret;
bank->gpio_chip = rockchip_gpiolib_chip;
gc = &bank->gpio_chip;
gc->base = bank->pin_base;
gc->ngpio = bank->nr_pins;
gc->label = bank->name;
gc->parent = bank->dev;
#ifdef CONFIG_OF_GPIO
gc->of_node = of_node_get(bank->of_node);
#endif
ret = gpiochip_add_data(gc, bank);
if (ret) {
dev_err(bank->dev, "failed to add gpiochip %s, %d\n", gc->label, ret);
return ret;
}
if (!of_property_read_bool(bank->of_node, "gpio-ranges")) {
struct device_node *pctlnp = of_get_parent(bank->of_node);
struct pinctrl_dev *pctldev = NULL;
if (!pctlnp)
return -ENODATA;
pctldev = of_pinctrl_get(pctlnp);
if (!pctldev)
return -ENODEV;
ret = gpiochip_add_pin_range(gc, dev_name(pctldev->dev), 0, gc->base, gc->ngpio);
if (ret) {
dev_err(bank->dev, "Failed to add pin range\n");
goto fail;
}
}
ret = rockchip_interrupts_register(bank);
if (ret) {
dev_err(bank->dev, "failed to register interrupt, %d\n", ret);
goto fail;
}
fail:
gpiochip_remove(&bank->gpio_chip);
return ret;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
gpiolib.c
gpiochip_add_data_with_key
三、GPIO控制器的sysfs接口
当成功调用gpiochip_add()函数后,系统会在/sys/class/gpio/目录下创建一个类似gpiochipX的新目录(X代表该GPIO控制器提供的第一个GPIO编号)。这个目录包含以下只读属性:
- base:显示X的数值,表示该芯片管理的GPIO起始编号。例如,若路径是gpiochip20,这里的base值就是20,说明该芯片从GPIO 20号开始提供引脚。
- label:显示芯片名称(可能不唯一),用于识别或调试时参考(例如"LED Controller")。
- ngpio:显示该芯片能管理的GPIO总数。例如如果ngpio是32,那么该芯片实际提供GPIO编号为X到X+31(比如20-51)。
所有上述信息都是系统自动生成且不可修改的,只能查看不能编辑。
四、GPIO控制器和设备树
在设备树中声明GPIO控制器时,需要遵循以下规则:
必须设置标识属性
每个GPIO控制器节点都必须添加gpio-controller
属性(布尔值,表示这是一个GPIO控制器)。支持中断时添加参数
如果该控制器能处理中断信号:需要添加
interrupt-cells
属性,通常设为<2>
。这两个参数分别表示:
- 第一个单元:GPIO引脚编号
- 第二个单元:中断触发方式(如上升沿/下降沿等)
GPIO参数的定义
必须设置gpio-cells
属性,通常设为<2>
:- 第一个单元:指定GPIO的物理编号
- 第二个单元:标识GPIO功能(如方向、电平状态等)
实际使用中的简化情况
对于大多数非内存映射的GPIO控制器:- 第二个参数(标识/触发方式)通常不使用或设为默认值
- 可直接通过第一个参数(GPIO编号)快速配置
这样设置后,系统就能通过这两个参数正确识别和控制GPIO引脚的功能。: