随着内核的发展,linux驱动框架在不断的变化。很早很早以前,出现了gpio子系统,后来又出现了pinctrl子系统。对于一个驱动工程师来说,如何利用系统提供出来的api去进行控制gpio的状态,在这里总结几种方式。(希望总结的有点深度,但是实力不允许,继续努力!!!)
gpio是我们最常提及的,但对于gpio的介绍,我想在这里引用下大牛的总结,感谢蜗窝科技。对于不同的硬件芯片,它所呈现的IO Port是不同的。
1、和CPU的连接方式不同
对于ARM的嵌入式硬件平台,SOC本身可以提供大量的IO port,SOC上的GPIO controller是通过SOC的总线(AMBA)连接到CPU的。对于嵌入式系统而言,除了SOC的IO port,一些外设芯片也可能会提供IO port,例如:
(1)有些key controller芯片、codec或者PMU的芯片会提供I/O port
(2)有些专用的IO expander芯片可以扩展16个或者32个GPIO
当然这些IO Port的控制方式自然是不同的,比如有些IO expander芯片,是由i2c或者spi去和主cpu连接的。codec芯片有可能是PDM总线连接等。
2、访问方式不同
追根溯源,我们操控gpio口,都是去操控IO所对应的寄存器,只是不同的芯片对寄存器bit位的定义不同
3、配置方式不同
即便是使用了同样的硬件(例如都使用同样的某款SOC),不同硬件系统上GPIO的配置不同。在一个系统上配置为输入,在另外的系统上可能配置为输出。
4、GPIO特性不同。这些特性包括:
(1)是否能触发中断
(2)如果能够触发中断,那么该GPIO是否能够将CPU从sleep状态唤醒
(3)有些有软件可控的上拉或者下拉电阻的特性,有的GPIO不支持这种特性。在设定为输入的时候,有的GPIO可以设定debouce的算法,有的则不可以。
(4)多功能复用
截取一个高通sdm660的GPIO Configuration Spreadsheet
更正:MPM interrupt = Y,是指可配置可唤醒中断
上面也说了,操控SOC上的gpio,最终控制的就是IO对应的寄存器。
针对你所使用的平台,找到gpio 硬件寄存器手册,找到对应gpio的基地址。
如上图:660平台,gpio的基地址就是0x03100000。
比如讲gpio62的地址是多少,0x03100000 + (num)*1000=0x0313e000
接下来介绍第一种方法去操控gpio的状态(不建议使用)
方式1:
#define GPIO62CON 0x0313e000
#define GPIO62DAT 0x0313e004
volatile unsigned int * gpio62con;
volatile unsigned int * gpio62dat;
//在调用流程中执行
gpio62con = ioremap(GPIO62CON,0x4); //物理地址映射成虚拟地址
if(NULL == gpio62con)
{
printk("ioremap gpio62con fail \n");
goto err1;
}
gpio62dat = ioremap(GPIO62DAT,0x4);
if(NULL == gpi62dat)
{
printk("ioremap gpio62dat fail \n");
goto err2;
}
*gpio62con = 0x03;//DRV_2_MA,PULL_UP
*gpio62dat = 0x02;//输出1
方式2:
针对上述方式是直接操控寄存器,在高通Android的系统中,也提供了一种api去供用户debug。
读写寄存器的命令如下:
/system/bin/r <Address> <value>
编译读写gpio寄存器地址工具:/system/bin/r
Android9.0源码地址:
system/core/toolbox/r.c
system/core/toolbox/Android.bp
cc_binary {
name: "r",
defaults: ["toolbox_defaults"],
srcs: ["r.c"], }
方式3:
我们常用的gpio子系统提供的接口函数
简单介绍下使用方法:
例:
在设备树中添加资源配置:
qcom,msm-spk-ext-pa = <&tlmm 62 0>;//第三个参数是flag
pa_enable = of_get_named_gpio(pdev->dev.of_node,"qcom,msm-spk-ext-pa", 0);
if (gpio_is_valid(pa_enable )) {
pr_debug("%s: pa_enable request %d\n", __func__,
pa_enable );
ret = gpio_request(pa_enable , "msm-spk-ext-pa");
if (ret) {
pr_err("%s: pa_enable request failed, ret:%d\n",
__func__, ret);
goto err;
}
}
gpio_direction_output(pa_enable , 1);//EN
//请求一个/一组gpio
gpio_request/devm_gpio_request、gpio_request_one/devm_gpio_request_one、gpio_request_array ---------<1>
...
//设置gpio方向为输入/输出
gpio_direction_input或者gpio_direction_output ---------<2>
...
//将该gpio通过sys文件系统导出,应用层可以通过文件操作gpio
gpio_export ---------<3>
...
//如果gpio为输入,获取gpio值,如果gpio为输出,可以设置gpio高低电平
gpio_get_value、gpio_set_value ---------<4>
...
//将gpio转为对应的irq,然后注册该irq的中断handler
request_irq(gpio_to_irq(gpio_num)...) ---------<5>
...
//释放请求的一个或者一组gpio
gpio_free/devm_gpio_free、gpio_free_array ---------<6>
...
补充一点:qcom,msm-spk-ext-pa = <&tlmm 62 0>;//第三个参数是flag
对于第三个参数,很多情况下可能会忽略,我理解第三个参数是flag,在我们使用接口函数的时候,很多接口函数是可以利用这个flag的。
比如:
static inline int of_get_named_gpio_flags(struct device_node *np,
const char *list_name, int index, enum of_gpio_flags *flags)
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label)
{
struct gpio_desc *desc;
int err;
desc = gpio_to_desc(gpio);
/* Compatibility: assume unavailable "valid" GPIOs will appear later */
if (!desc && gpio_is_valid(gpio))
return -EPROBE_DEFER;
err = gpiod_request(desc, label);
if (err)
return err;
if (flags & GPIOF_OPEN_DRAIN)
set_bit(FLAG_OPEN_DRAIN, &desc->flags);
if (flags & GPIOF_OPEN_SOURCE)
set_bit(FLAG_OPEN_SOURCE, &desc->flags);
if (flags & GPIOF_ACTIVE_LOW)
set_bit(FLAG_ACTIVE_LOW, &desc->flags);
if (flags & GPIOF_DIR_IN)
err = gpiod_direction_input(desc);
else
err = gpiod_direction_output_raw(desc,
(flags & GPIOF_INIT_HIGH) ? 1 : 0);
if (err)
goto free_gpio;
if (flags & GPIOF_EXPORT) {
err = gpiod_export(desc, flags & GPIOF_EXPORT_CHANGEABLE);
if (err)
goto free_gpio;
}
return 0;
free_gpio:
gpiod_free(desc);
return err;
}
我们可以在配置的时候使用这个flag去做一些事情,比如配置中断触发方式,设置输入输出。(个人理解)
方式4:
pinctrl的配置方式
(1)默认在使用状态时不需要改变gpio状态
//sdm670-pinctrl.dtsi
&soc {
tlmm: pinctrl@03400000{
…..
….
..
Client1_pins {
Client1_default: client1_default{
mux {
pins = "gpio54";
function = "gpio";
};
config {
pins = "gpio54";
drive-strength = <2>; /* 2 mA */
bias-pull-down; /* pull down */
input-enable;
};
};
};
};
//sdm670.dtsi or sdm670-.dts
Soc {
…….
…
..
Client1 {
……
…
/*Note the default state is programmed by the kernel at the time of
kernel bootup. No driver changes are necessary since at probe time the
default state would already be programmed in TLMM */
pinctrl-states = “default”;
/* Use phandle reference to default configuration node */
pinctrl-0 = <&Client1_default>;
};
(2)配置gpio的active和sleep状态
&soc {
tlmm: pinctrl@03400000{
…..
….
/* I2C CONFIGURATION */
/* QUPv3 South SE mappings */
/* SE 0 pin mappings */
qupv3_se0_i2c_pins: qupv3_se0_i2c_pins {
qupv3_se0_i2c_active: qupv3_se0_i2c_active {
mux {
pins = "gpio0", "gpio1";
function = "qup0";
};
config {
pins = "gpio0", "gpio1";
drive-strength = <2>;
bias-disable;
};
};
qupv3_se0_i2c_sleep: qupv3_se0_i2c_sleep {
mux {
pins = "gpio0", "gpio1";
function = "gpio";
};
config {
pins = "gpio0", "gpio1";
drive-strength = <2>;
bias-pull-up;
};
};
};
Soc {
…….
…
Client2 {
……
pinctrl-states = “active”, “suspend”;
/* Use phandle reference to active and sleep configurations to
Define the active and sleep pin states */
pinctrl-0 = <& qupv3_se0_i2c_active>;
pinctrl-1 = <& qupv3_se0_i2c_sleep >;
};
//driver需要修改接口
Static int client2_probe (struct platform_device *pdev) {
Struct pinctrl_state *set_state;
Struct client2_data *client2_dd;
…..
…
..
/* Try to obtain pinctrl handle */
Pdev->dev->pins->p = devm_pinctrl_get(pdev);
/* Lookup the active configuration */ 34
set_state = pinctrl_lookup_state(pdev->dev->pins->p, “active”);
if (!set_state)
goto fail;
else
/*Actually write the active configuration to hardware */
ret = pinctrl_select_state(pdev->dev->pins->p, set_state);
/* Install sleep configuration in runtime suspend function */
Static int client2_suspend( struct platfom_device) {
/* Configure pins to sleep state state */
Struct pinctrl_state *set_state;
/* Actually write the sleep configuration to hardware */
set_state = pinctrl_lookup_state(pdev->dev->pins->p, “sleep”);
if (!set_state)
goto fail;
else
ret = pinctrl_select_state(pdev->dev->pins->p, set_state);
}
方式5:
gpio系统中会创建接口到文件系统中,以便用户使用。
/sys/class/gpio
export gpiochip1021 gpiochip378 gpiochip474 gpiochip570 gpiochip666 gpiochip762 gpiochip858 gpiochip954
gpiochip0 gpiochip314 gpiochip410 gpiochip506 gpiochip602 gpiochip698 gpiochip794 gpiochip890 gpiochip986
gpiochip1018 gpiochip346 gpiochip442 gpiochip538 gpiochip634 gpiochip730 gpiochip826 gpiochip922 unexport
1、gpio_operation 通过 /sys/ 文件接口操作 IO 端口 GPIO 到文件系统的映射。
2、控制 GPIO 的目录位于 /sys/class/gpio。
3、/sys/class/gpio/export 文件用于通知系统需要导出控制的 GPIO 引脚编号。
4、/sys/class/gpio/unexport 用于通知系统取消导出。
5、/sys/class/gpio/gpiochipX 目录保存系统中 GPIO 寄存器的信息,包括每个寄存器控制引脚的起始编号 base,寄存器名称,引脚总数。
解释下gpiochipX,我理解每个gpiochipX都对应一个gpio控制器,bsp工程师有可能会根据芯片对应的gpio可以自行写一个gpio控制器驱动,主要是通过gpiochip_add这个函数实现。有兴趣可看源码。
可参考大牛写的例子:Linux下的gpio控制器驱动
gpio引脚编号说明:
引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数
引脚编号是gpiochipxxx下的base + 第几个GPIO,也就是base加偏移,偏移的是位数。
例如gpiochip1028 下的第1个GPIO那么编号就是1018 + 1 = 1019
导出gpio号,比如gpio12
linux:/sys/class/gpio # echo 12 > export
linux:/sys/class/gpio # cd gpio12
linux:/sys/class/gpio/gpio12 # ls
active_low device direction edge power subsystem uevent value
之后通过echo和cat去设置查看相应的属性。
gpio_tlmm_config(uint32_t gpio, uint8_t func, uint8_t dir, uint8_t pull, uint8_t drvstr, uint32_t enable)
gpio = GPIO number
func = always '0'
dir = GPIO_OUTPUT or GPIO_INPUT
pull = Don't care if it is GPIO_OUTPUT
drvstr = GPIO_xMA
enable = GPIO_DISABLE is for output , GPIO_ENALE for input .
举个栗子:
**configure gpio as input**
gpio_tlmm_config(TLMM_VOL_UP_BTN_GPIO, 0, GPIO_INPUT, GPIO_PULL_UP, GPIO_2MA, GPIO_ENABLE);
gpio_status(TLMM_VOL_UP_BTN_GPIO); //get gpio value
**configure gpio as output**
gpio_tlmm_config(109, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA, GPIO_DISABLE);
gpio_set(109, 0x2); ////set gpio value
据高通solution介绍,目前在lk中并没有 GPIO_IRQ driver。
As you know, there’s no dedicate vector value for the every gpio IRQ. tlmm_summary_irq number need you check the msm**-pinctrl.dts, the
Interrupt num should be the num in
reg = <0x1000000 0x300000>;
interrupts = <0 208 0>;
Then interrup num should be : 208+32=240
注:刚开始有点疑惑中断号为什么是240,该中断为共享处理器中断,起始中断号为32.
先看几个缩写
IPI:inter-processer interrupt 中断号0~15
PPI:per processor interrupts 中断号16~31
SPI:shared processor interrupts 中断号 32~224
SGI:software generated interrupts (SGI)
设备树是用来描述硬件信息的,因此里面不涉及软件中断SGI,从设备树包含的头文件可以看出这一点:
在arm-gic.h文件中定义的只有SPI和PPI
#define GIC_SPI 0
#define GIC_PPI 1
一般设备树中的中断都是SPI,那么 interrupts = <0 208 1>;形式中的
都是什么呢
X:GIC_SPI或者GIC_PPI
Y:物理中断号-32
Z:触发方式
1 = low-to-high edge triggered
2 = high-to-low edge triggered (invalid for SPIs)
4 = active high level-sensitive
8 = active low level-sensitive (invalid for SPIs).
接上
It meant that any gpio irq are triggered, the tlmm_summary_irq() handler would be called. Then, needed to check about GPIO_INTR_STATUS_ADDR(10); if the GPIO 's IRQ is really triggered.
For gpio interrupt type (falling edge /rising edge /dual edge ) need you config the GPIO_INTR_CFGn register , for register information , need you refer chip SWI (software interface) document
Normally 0x9B for falling edge , 0x9f for dual edge .
+#define GPIO_INT_CONFIG_ADDR(x) (TLMM_BASE_ADDR + 0x00000008 + (x)*0x1000)
+#define GPIO_INT_STATUS_CONFIG_ADDR(x) (TLMM_BASE_ADDR + 0x0000000C + (x)*0x1000)
//gpio irq handler :
+enum handler_return summary_interrupt(void *arg)
+{
+ unsigned ret = INT_NO_RESCHEDULE;
+
+ dprintf(CRITICAL, "Home IRQ:0x%08x\n", *((volatile uint32_t *)GPIO_INT_STATUS_CONFIG_ADDR(109)));
/*
add your gpio irq handler here .
*/
+ return ret;
+}
//where you configure gpio as interrupt and register irq handler .
+ gpio_tlmm_config(109, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA, GPIO_ENABLE);
+ writel(0x9B, (unsigned int *)GPIO_INT_CONFIG_ADDR(109));
+ register_int_handler(240, summary_interrupt, (void *)0);
+ unmask_interrupt(240);
+ while(1)
+ {
+ if(target_volume_down())
+ break;
+ dprintf(CRITICAL, "HOME_KEY status:%d\n", gpio_status(109));
+ mdelay(1000);
+ }