以RK3328为例,介绍设备树在GPIO方面的应用。
引脚图如下
一、首先在DTS文件中增加GPIO资源描述:
gpio_demo: gpio_demo {
status = "okay";
compatible = "rk3328,gpio_demo";
firefly-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>; /* GPIO0_B4 */
firefly-irq-gpio = <&gpio2 29 IRQ_TYPE_EDGE_RISING>; /* GPIO2_D5 */
};
- GPIO_ACTIVE_HIGH:引脚状态为常高
- IRQ_TYPE_EDGE_RISING:上升沿触发
- RK系列GPIO分为GPIO0,GPIO1,GPIO2…等组,每组GPIO下分为A,B,C,D等小组,而每个A下有0-7个引脚,B,C,D同A。于是A=0,B与A相差8个引脚,C与B相差8个引脚,以此类推。。。
GPIO0_B4
属于B组,基数为8,加4即为引脚序列号12。
二、在probe函数里添加解析
1.区别于无Device Tree驱动程序而言,首先要有一个device_id
结构体用于匹配我们在设备树资源里添加的属性:
static const struct of_device_id gpio_demo_dt_ids[] = {
{ .compatible = "rk3328,gpio_demo", },
{},
};
2.在平台设备驱动结构体里加上.of_match_table
属性,of_match_ptr(gpio_demo_dt_ids)
是匹配设备树的函数
static struct platform_driver int_demo_driver = { .
driver = {
.name = "gpio_demo",
.of_match_table = of_match_ptr(gpio_demo_dt_ids),
},
.probe = int_demo_probe,
.remove = int_demo_remove,
};
enum of_gpio_flags {
OF_GPIO_ACTIVE_LOW = 0x1,
};
static int firefly_gpio_probe(struct platform_device *pdev)
{
int ret;
int gpio;
enum of_gpio_flags flag;
struct firefly_gpio_info *gpio_info;
struct device_node *firefly_gpio_node = pdev->dev.of_node; //用于接收传入的设备树结点
printk("Firefly GPIO Test Program Probe\n");
//1、申请空间
gpio_info = devm_kzalloc(&pdev->dev,sizeof(struct firefly_gpio_info *), GFP_KERNEL);
if (!gpio_info) {
return -ENOMEM;
}
//2、获取名为 "firefly-gpio"的gpio信息
gpio = of_get_named_gpio_flags(firefly_gpio_node, "firefly-gpio", 0, &flag);
if (!gpio_is_valid(gpio)) {
printk("firefly-gpio: %d is invalid\n", gpio); return -ENODEV;
}
//请求控制获取的gpio
if (gpio_request(gpio, "firefly-gpio")) {
printk("gpio %d request failed!\n", gpio);
gpio_free(gpio);
return -ENODEV;
}
//设置操作的gpio
gpio_info->firefly_gpio = gpio;
//根据flag判断使能值
gpio_info->gpio_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1;
//设置为输出,首参为gpio的名字,设置gpio的电平值。
gpio_direction_output(gpio_info->firefly_gpio, gpio_info->gpio_enable_value);
printk("Firefly gpio putout\n");
}
#include <linux/gpio.h> //里面声明io口的操作函数
1. int gpio_request(unsigned gpio, const char *label);//每个io只能被请求一次,可防止多个驱动来控制同一个IO口
2. void gpio_free(unsigned gpio); //释放已请求的io口
3. int gpio_direction_input(unsigned gpio); //把指定的IO口作输入功能, gpio用于指定具体哪个io口
4. int gpio_direction_output(unsigned gpio, int value); //作输出功能,并根据value的值输出高低电平
5. int gpio_get_value(unsigned gpio); //获取指定IO口的电平
6. void gpio_set_value(unsigned gpio, int value); //设置IO口的电平为value(0/1)
7. int gpio_to_irq(unsigned gpio); //根据io口,获取到它对应的中断号(io口大都有外部中断功能)
获取设备树里设备节点的gpio口信息:
#include <linux/of_gpio.h>
//只需一个函数即可
int of_get_named_gpio_flags(struct device_node *np, const char *propname,
int index, enum of_gpio_flags *flags);
//返回值为int类型的gpio口.
//np为设备或设备子节点对象, propname为指定的属性名字, index表示获取属性里的第几个值
// 其中flags一定得注意,按文档里的说明应就是一个int类型的值,但根本就不能为int参数(会导致kernel panic),
// 通过阅读内核里的代码得出, flags的参数应为struct gpio_config类型. 定义在下面文件:
"include/linux/sys_config.h"
struct gpio_config {
u32 gpio; /* gpio global index, must be unique */
u32 mul_sel; /* multi sel val: 0 - input, 1 - output... */
u32 pull; /* pull val: 0 - pull up/down disable, 1 - pull up... */
u32 drv_level; /* driver level val: 0 - level 0, 1 - level 1... */
u32 data; /* data val: 0 - low, 1 - high, only vaild when mul_sel is input/output */
};
三、中断
firefly-irq-gpio = <&gpio2 29 IRQ_TYPE_EDGE_RISING>; /* GPIO2_D5 */
中断的类型
IRQ_TYPE_NONE //默认值,无定义中断触发类型
IRQ_TYPE_EDGE_RISING //上升沿触发
IRQ_TYPE_EDGE_FALLING //下降沿触发
IRQ_TYPE_EDGE_BOTH //上升沿和下降沿都触发
IRQ_TYPE_LEVEL_HIGH //高电平触发
IRQ_TYPE_LEVEL_LOW //低电平触发
static int firefly_gpio_probe(struct platform_device *pdev)
{
int ret;
int gpio;
enum of_gpio_flags flag;
struct firefly_gpio_info *gpio_info;
struct device_node *firefly_gpio_node = pdev->dev.of_node;
......
//1、获取名为 "firefly-irq-gpio"的gpio信息
gpio = of_get_named_gpio_flags(firefly_gpio_node, "firefly-irq-gpio", 0, &flag);
gpio_info->firefly_irq_gpio = gpio;
gpio_info->firefly_irq_mode = flag;
//把GPIO的PIN值转换为相应的IRQ值
gpio_info->firefly_irq = gpio_to_irq(gpio_info->firefly_irq_gpio);
if (gpio_info->firefly_irq) {
if (gpio_request(gpio, "firefly-irq-gpio")) {
printk("gpio %d request failed!\n", gpio); gpio_free(gpio); return IRQ_NONE;
}
//申请中断
ret = request_irq(gpio_info->firefly_irq, firefly_gpio_irq, flag, "firefly-irq-gpio", gpio_info);
if (ret != 0) free_irq(gpio_info->firefly_irq, gpio_info);
dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
}
return 0;
}
static irqreturn_t firefly_gpio_irq(int irq, void *dev_id) //中断函数
{
printk("Enter firefly gpio irq test program!\n");
return IRQ_HANDLED;
}
调用gpio_to_irq
把GPIO的PIN值转换为相应的IRQ值,调用gpio_request
申请占用该IO口,调用request_irq
申请中断,如果失败要调用free_irq释放,该函数中gpio_info->firefly_irq
是要申请的硬件中断号,firefly_gpio_irq
是中断函数,gpio_info->firefly_irq_mode
是中断处理的属性,”firefly-gpio”
是设备驱动程序名称,gpio_info是该设备的device结构,在注册共享中断时会用到。
四、复用
如何定义 GPIO 有哪些功能可以复用,在运行时又如何切换功能呢?以 I2C4 为例作简单的介绍。
查规格表可知,I2C4_SDA 与 I2C4_SCL 的功能定义如下:
Pad# func0 func1 I2C4_SDA/GPIO1_B3
gpio1b3 i2c4_sda I2C4_SCL/GPIO1_B4 gpio1b4 i2c4_scl
在 kernel/arch/arm64/boot/dts/rockchip/rk3328xx.dtsi 里有:
i2c4: i2c@ff3d0000{
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff3d0000 0x0 0x1000>;
clocks = <&pmucru SCLK_I2C4_PMU>, <&pmucru PCLK_I2C4_PMU>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default", "gpio";
pinctrl-0 = <&i2c4_xfer>;
pinctrl-1 = <&i2c4_gpio>; //此处源码未添加
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
此处,跟复用控制相关的是 pinctrl- 开头的属性:
pinctrl-names 定义了状态名称列表: default (i2c 功能) 和 gpio 两种状态。
pinctrl-0 定义了状态 0 (即 default)时需要设置的 pinctrl: &i2c4_xfer
pinctrl-1 定义了状态 1 (即 gpio)时需要设置的 pinctrl: &i2c4_gpio
这些 pinctrl 在kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi中这样定义:
pinctrl: pinctrl {
compatible = "rockchip,rk3399-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <0x2>;
#size-cells = <0x2>;
ranges;
i2c4{
i2c4_xfer: i2c4-xfer{
rockchip,pins = <1 12 RK_FUNC_1 &pcfg_pull_none>, <1 11 RK_FUNC_1 &pcfg_pull_none>;
};
i2c4_gpio: i2c4-gpio {
rockchip,pins = <1 12 RK_FUNC_GPIO &pcfg_pull_none>, <1 11 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
RK_FUNC_1,RK_FUNC_GPIO 的定义在 kernel/include/dt-bindings/pinctrl/rk.h 中:
#define RK_FUNC_GPIO 0
#define RK_FUNC_1 1
#define RK_FUNC_2 2
#define RK_FUNC_3 3
#define RK_FUNC_4 4
#define RK_FUNC_5 5
#define RK_FUNC_6 6
#define RK_FUNC_7 7
另外,像”1 11”,”1 12”这样的值是有编码规则的,编码方式与上一小节”输入输出”描述的一样,”1 11”代表GPIO1_B3,”1 12”代表GPIO1_B4。
在复用时,如果选择了 “default” (即 i2c 功能),系统会应用 i2c4_xfer 这个 pinctrl,最终将 GPIO1_B3 和 GPIO1_B4 两个针脚切换成对应的 i2c 功能;而如果选择了 “gpio” ,系统会应用 i2c4_gpio 这个 pinctrl,将 GPIO1_B3 和 GPIO1_B4 两个针脚还原为 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 = <&gpio1 GPIO_B3 GPIO_ACTIVE_LOW>, <&gpio1 GPIO_B4 GPIO_ACTIVE_LOW>;
然后是调用 devm_gpio_request 来申请 gpio,接着是调用 pinctrl_lookup_state 来查找 “gpio” 状态,而默认状态 “default” 已经由框架保存到 i2c->dev-pins->default_state 中了。
最后调用 pinctrl_select_state 来选择是 “default” 还是 “gpio” 功能。