GPIO, 全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。
RK3399有5组GPIO bank:GPIO0~GPIO4,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分(不是所有 bank 都有全部编号,例如 GPIO4 就只有 C0~C7, D0~D2)。
所有的GPIO在上电后的初始状态都是输入模式,可以通过软件设为上拉或下拉,也可以设置为中断脚,驱动强度都是可编程的。 每个 GPIO 口除了通用输入输出功能外,还可能有其它复用功能,例如 GPIO2_B2,可以利用成以下功能:
每个 GPIO 口的驱动电流、上下拉和重置后的初始状态都不尽相同,详细情况请参考RK3399TRM! 的 “Chapter 10 GPIO” 一章。 RK3399 的 GPIO 驱动是在以下 pinctrl 文件中实现的:
drivers/pinctrl/pinctrl-rockchip.c
其核心是填充 GPIO bank 的方法和参数,并调用 gpiochip_add 注册到内核中
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 = ARCH_GPIO_BASE + bank->pin_base;
gc->ngpio = bank->nr_pins;
gc->dev = &pdev->dev;
gc->of_node = bank->of_node;
gc->label = bank->name;
ret = gpiochip_add(gc);
....
}
...
}
在DTSI或者DTS文件中增加GPIO的资源描述:
ak7755: ak7755@18 {
#sound-dai-cells = <0>;
compatible = "akm,ak7755";
reg = <0x18>;
clocks = <&cru SCLK_I2S_8CH_OUT>;
clock-names = "mclk";
pinctrl-names = "default";
pinctrl-0 = <&i2s_8ch_mclk>;
status = "okay";
ak7755,pdn-gpio = <&gpio1 1 GPIO_ACTIVE_HIGH>;
tpa-sdz-gpio=<&gpio2 9 GPIO_ACTIVE_LOW>;
};
RK3399 有7组GPIO 每组 4个BANK ,每个BANK有8个,
上面个的 gpio1 1 对应的GPIO1 A1 1 A1
GPIO2 9 对应GPIO2 B1 8+1 =9
这两种表示方式 kernel都允许。
在probe函数中 对DTS所添加的资源进行解析
......
ak7755->pdn_gpio = of_get_named_gpio(np, "ak7755,pdn-gpio", 0);
if (ak7755->pdn_gpio < 0) {
┊ ak7755->pdn_gpio = -1;
┊ return -1;
}
if( !gpio_is_valid(ak7755->pdn_gpio) ) {
┊ printk(KERN_ERR "ak7755 pdn pin(%u) is invalid\n", ak7755->pdn_gpio);
┊ return -1;
}
tpa_sdz = of_get_named_gpio(np,"tpa-sdz-gpio",0);
......
ret = gpio_request(ak7755->pdn_gpio, "ak7755 pdn");
gpio_direction_output(ak7755->pdn_gpio, 0);
devm_gpio_request(&(ak7755->i2c->dev),tpa_sdz,"tpa-sdz-gpio");
gpio_direction_output(tpa_sdz, 1);
......
of_get_named_gpio_flags 从设备树中读取 gpio 的 GPIO 配置编号和标志,gpio_is_valid 判断该 GPIO 编号是否有效,gpio_request 则申请占用该 GPIO。
如果初始化过程出错,需要调用 gpio_free 来释放之前申请过且成功的 GPIO 。
在驱动中调用 gpio_direction_output 就可以设置输出高还是低电平,这里默认输出从DTS获取得到的有效电平GPIO_ACTIVE_HIGH,即为高电平,
如果驱动正常工作,可以用万用表测得对应的引脚应该为高电平。
实际中如果要读出 GPIO,需要先设置成输入模式,然后再读取值:
int val;
gpio_direction_input(your_gpio);
val = gpio_get_value(your_gpio);
#include
#include
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);
中断类型
IRQ_TYPE_NONE //默认值,无定义中断触发类型
IRQ_TYPE_EDGE_RISING //上升沿触发
IRQ_TYPE_EDGE_FALLING //下降沿触发
IRQ_TYPE_EDGE_BOTH //上升沿和下降沿都触发
IRQ_TYPE_LEVEL_HIGH //高电平触发
IRQ_TYPE_LEVEL_LOW //低电平触发
与上述过程基本一致,配置DTS,在probe函数中进行DTS解析,再中断注册申请
......
if (gpio_is_valid(button->gpio)) {
error = devm_gpio_request_one(&pdev->dev, button->gpio,GPIOF_IN, desc);
if (error < 0) {
dev_err(dev, "Failed to request GPIO %d, error %d\n",
button->gpio, error);
return error;
}
if (button->debounce_interval) {
error = gpio_set_debounce(button->gpio,
button->debounce_interval * 1000);
/* use timer if gpiolib doesn't provide debounce */
if (error < 0)
bdata->software_debounce =
button->debounce_interval;
}
if (button->irq) {
bdata->irq = button->irq;
} else {
irq = gpio_to_irq(button->gpio);
.....
INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
isr = gpio_keys_gpio_isr;
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
......
/*
┊* Install custom action to cancel release timer and
┊* workqueue item.
┊*/
error = devm_add_action(&pdev->dev, gpio_keys_quiesce_key, bdata);
if (error) {
dev_err(&pdev->dev,
"failed to register quiesce action, error: %d\n",
error);
return error;
}
/*
┊* If platform has specified that the button can be disabled,
┊* we don't want it to share the interrupt line.
┊*/
if (!button->can_disable)
irqflags |= IRQF_SHARED;
error = devm_request_any_context_irq(&pdev->dev, bdata->irq,isr, irqflags, desc, bdata);
调用gpio_to_irq把GPIO的PIN值转换为相应的IRQ值
调用gpio_request申请占用该IO口
irq 号 绑定 ISR
在TRM!中获取复用相关信息 IOMUX在 General Register Files (GRF) 中
GRF_GPIO4C_IOMUX:
3:2 RW 0x0
gpio4c1_sel
GPIO4C[1] iomux select
2’b00: gpio
2’b01: i2c3hdmi_scl
2’b10: uart2dbgb_sout
2’b11: hdmii2c_scl
1:0 RW 0x0
gpio4c0_sel
GPIO4C[0] iomux select
2’b00: gpio
2’b01: i2c3hdmi_sda
2’b10: uart2dbgb_sin
2’b11: hdmii2c_sda
寄存器不同值,对应复用不一致
DTS资源
pinctrl: pinctrl {
compatible = "rockchip,rk3399-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <0x2>;
#size-cells = <0x2>;
ranges;
i2c3 {
i2c3_xfer: i2c3-xfer {
rockchip,pins =
<4 17 RK_FUNC_1 &pcfg_pull_none>,
<4 16 RK_FUNC_1 &pcfg_pull_none>;
};
i2c3_gpio: i2c3_gpio {
rockchip,pins =
<4 17 RK_FUNC_GPIO &pcfg_pull_none>,
<4 16 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
...
i2c3: i2c@ff130000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff130000 0x0 0x1000>;
clocks = <&cru SCLK_I2C3>, <&cru PCLK_I2C3>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c3_xfer>;
pinctrl-1 = <&i2c3_gpio>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
复用控制相关的是 pinctrl- 开头的属性
在复用时,如果选择了 “default” ,系统会应用 otp_gpio 这个 pinctrl;
而如果选择了 pinctrl-1 ,系统会应用 pinctrl: &otp_out 功能。
我们看看 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 = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_i2c), GFP_KERNEL);
...
strlcpy(i2c->adap.name, "rockchip_i2c", sizeof(i2c->adap.name));
i2c->dev = &pdev->dev;
i2c->adap.owner = THIS_MODULE;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->adap.retries = 2;
i2c->adap.timeout = msecs_to_jiffies(100);
i2c->adap.algo = &rockchip_i2c_algorithm;
i2c_set_adapdata(&i2c->adap, i2c);
i2c->adap.dev.parent = &pdev->dev;
i2c->adap.dev.of_node = np;
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->regs = devm_ioremap_resource(&pdev->dev, res);
...
i2c->check_idle = true;
of_property_read_u32(np, "rockchip,check-idle", &i2c->check_idle);
if (i2c->check_idle) {
i2c->sda_gpio = of_get_gpio(np, 0);/*step 1*/
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));/*step 2*/
if (ret) {
dev_err(&pdev->dev, "failed to request sda gpio\n");
return ret;
}
i2c->scl_gpio = of_get_gpio(np, 1);/*step 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));/*step 2*/
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");/*step 3*/
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);/*step 4*/
}
/* setup info block for the i2c core */
ret = i2c_add_adapter(&i2c->adap);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add adapter\n");
return ret;
}
platform_set_drvdata(pdev, i2c);
/* find the clock and enable it */
i2c->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
return PTR_ERR(i2c->clk);
}
/* find the IRQ for this unit (note, this relies on the init call to
┊* ensure no current IRQs pending
┊*/
i2c->irq = ret = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, i2c->irq, rockchip_i2c_irq, 0,
dev_name(&i2c->adap.dev), i2c);
ret = clk_prepare(i2c->clk);
i2c->i2c_rate = clk_get_rate(i2c->clk);
rockchip_i2c_init_hw(i2c, 100 * 1000);
dev_info(&pdev->dev, "%s: Rockchip I2C adapter\n", dev_name(&i2c->adap.dev));
of_i2c_register_devices(&i2c->adap);
mutex_init(&i2c->suspend_lock);
return 0;
}
#include
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);
读取寄存器GPIO调试有一个很好用的工具,那就是IO指令,RK3399的Android系统默认已经内置了IO指令,使用IO指令可以实时读取或写入每个IO口的状态,这里简单介绍IO指令的使用。 首先查看 io 指令的帮助:
1|rk3399_mid:/ $ io -help
Raw memory i/o utility - $Revision: 1.5 $
io -v -1|2|4 -r|w [-l <len>] [-f <file>] <addr> [<value>]
-v Verbose, asks for confirmation
-1|2|4 Sets memory access size in bytes (default byte)
-l <len> Length in bytes of area to access (defaults to
one access, or whole file length)
-r|w Read from or Write to memory (default read)
-f <file> File to write on memory read, or
to read on memory write
<addr> The memory address to access
<val> The value to write (implies -w)
Examples:
io 0x1000 Reads one byte from 0x1000
io 0x1000 0x12 Writes 0x12 to location 0x1000
io -2 -l 8 0x1000 Reads 8 words from 0x1000
io -r -f dmp -l 100 200 Reads 100 bytes from addr 200 to file
io -w -f img 0x10000 Writes the whole of file to memory
Note access size (-1|2|4) does not apply to file based accesses.
读写一个寄存器
io -4 -r addr //读从addr起的4位寄存器的值
io -4 -w addr //写从addr起的4位寄存器的值
i2c3 SDA/SCL对应的 GPIO为 4 16 / 4 17 几位GPIO4 BANK C
GPIO4—C 查询datasheet对应的寄存器为:
GRF_GPIO4C_IOMUX
Address: Operational Base + offset (0x0e028)
GPIO4C iomux control
GRF基地址为:FF77_0000
GPIO4C iomux control地址为:0xff7700+0x0e028 0xff77e028
rk3399_mid:/ # io -4 -r 0xff77e028
ff77e028: 0000855f
bit0-bit3 11 11
2’b11: hdmii2c_scl
2’b11: hdmii2c_sda
Debugfs文件系统目的是为开发人员提供更多内核数据,方便调试。 这里GPIO的调试也可以用Debugfs文件系统,获得更多的内核信息。
GPIO在Debugfs文件系统中的接口为 /sys/kernel/debug/gpio,可以这样读取该接口的信息:
rk3399_mid:/ # cat /sys/kernel/debug/gpio
GPIOs 1000-1031, platform/pinctrl, gpio0:
gpio-1004 ( |bt_default_wake_host) in lo
gpio-1005 ( |power ) in hi
gpio-1009 ( |bt_default_reset ) out hi
gpio-1010 ( |reset ) out hi
GPIOs 1032-1063, platform/pinctrl, gpio1:
gpio-1034 ( |int-n ) in hi
gpio-1045 ( |enable ) out hi
gpio-1046 ( |vsel ) out lo
gpio-1049 ( |vsel ) out lo
gpio-1054 ( |mpu6500 ) in lo
GPIOs 1064-1095, platform/pinctrl, gpio2:
gpio-1064 ( |vbus-5v ) out lo
gpio-1069 ( |power33 ) out hi
gpio-1070 ( |power ) out hi
gpio-1071 ( |reset ) out hi
gpio-1072 ( |stanby ) out hi
gpio-1073 ( |power18 ) out hi
gpio-1074 ( |csi-ctl ) out lo
gpio-1076 ( |int ) in hi
gpio-1083 ( |bt_default_rts ) out lo
gpio-1090 ( |bt_default_wake ) out hi
GPIOs 1096-1127, platform/pinctrl, gpio3:
gpio-1111 ( |mdio-reset ) out hi
GPIOs 1128-1159, platform/pinctrl, gpio4:
gpio-1150 ( |? ) out hi
gpio-1153 ( |vcc5v0_host ) out hi
gpio-1157 ( |enable ) out hi
gpio-1158 ( |vcc_lcd ) out hi
从读取到的信息中可以知道,内核把GPIO当前的状态都列出来了,以GPIO0组为例,gpio-1004(GPIO0_A4)作为BT模块的唤醒脚(bt_default_wake_host),输入低电平(in lo)。
当IO指令读出来的值都是0x00000000时,请确认该GPIO的CLK是不是被关了,GPIO的CLK是由CRU控制,
可以通过读取datasheet下面CRU_CLKGATE_CON* 寄存器来查到CLK是否开启,
如果没有开启可以用io命令设置对应的寄存器,从而打开对应的CLK,打开CLK之后应该就可以读到正确的寄存器值了。