安全的合肥网站建设,普通网站一年要多少钱,怎么添加网站 多少钱,网站建设运营知识昨天跟同事调试GPIO#xff0c;用的就是Rockchip的板子#xff0c;我们想把一个gpio口设置成阻塞状态#xff0c;刚开始一直没有成功#xff0c;然后我问了连总#xff0c;连总是我原来的同事#xff0c;是一个大牛#xff0c;他跟我说#xff0c;只要把gpio口设置成输… 昨天跟同事调试GPIO用的就是Rockchip的板子我们想把一个gpio口设置成阻塞状态刚开始一直没有成功然后我问了连总连总是我原来的同事是一个大牛他跟我说只要把gpio口设置成输入状态不要上拉不要下拉就可以设置成阻塞态了。我确实这样设置了但是没有效果我们是直接在代码里面设置的后来我们的同事在dts里面尝试修改了下结果成功了。看这个宏的意思也就是没有上拉没有下拉的意思应该是代码上做了判断。所以我想到代码里面去看看这个宏有什么作用可惜的是这个也是在dts里面定义的。定义如下然后我料定bias-disable 这个一定是在代码里面定义的于是我搜索了代码里面的这个字符串。所以上面的设置实际上是跑到了这里去执行感兴趣的同学可以自己去piconf-generic.c里面去一探究竟。 然后我网上搜到了一个文章是说明rockchip gpio的虽然简单但是我觉得对于开发有参考意义推荐给各位义士。 使用的开发板~ AIO-3399J采用RK3399六核A72x2A53x464位处理器主频高达2.0GHz集成四核Mali-T864 GPU性能优异。 简介¶GPIO, 全称 General-Purpose Input/Output通用输入输出是一种软件运行期间能够动态配置和控制的通用引脚。RK3399有5组GPIO bankGPIO0~GPIO4每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分不是所有 bank 都有全部编号例如 GPIO4 就只有 C0~C7, D0~D2)。所有的GPIO在上电后的初始状态都是输入模式可以通过软件设为上拉或下拉也可以设置为中断脚驱动强度都是可编程的。每个 GPIO 口除了通用输入输出功能外还可能有其它复用功能例如 GPIO2_A2可以利用成以下功能GPIO2_A2CIF_D2每个 GPIO 口的驱动电流、上下拉和重置后的初始状态都不尽相同详细情况请参考《RK3399 规格书》中的 “Chapter 10 GPIO” 一章。RK3399 的 GPIO 驱动是在以下 pinctrl 文件中实现的kernel/drivers/pinctrl/pinctrl-rockchip.c
其核心是填充 GPIO bank 的方法和参数并调用 gpiochip_add 注册到内核中。AIO-3399J开发板为方便用户开发使用并没有引出了通用的GPIO口但是可以将其他接口用作gpio例如LED扩展接口在不用的情况可以将其当作一般的gpio口使用其对应引脚如下图本文以TP_RST(GPIO0_B4)和LCD_RST(GPIO4_D5)这两个通用GPIO口为例写了一份简单操作GPIO口的驱动在SDK的路径为kernel/drivers/gpio/gpio-firefly.c
以下就以该驱动为例介绍GPIO的操作。输入输出¶首先在DTS文件中增加驱动的资源描述kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly-demo.dtsi
gpio_demo: gpio_demo {status okay;compatible firefly,rk3399-gpio;firefly-gpio gpio0 12 GPIO_ACTIVE_HIGH; /* GPIO0_B4 */firefly-irq-gpio gpio4 29 IRQ_TYPE_EDGE_RISING; /* GPIO4_D5 */
};
这里定义了一个脚作为一般的输出输入口AIO-3399J的dts对引脚的描述与Firefly-RK3288有所区别GPIO0_B4被描述为gpio0 12 GPIO_ACTIVE_HIGH这里的12来源于8412其中8是因为GPIO0_B4是属于GPIO0的B组如果是A组的话则为0如果是C组则为16如果是D组则为24以此递推而4是因为B4后面的4。GPIO_ACTIVE_HIGH表示高电平有效如果想要低电平有效可以改为GPIO_ACTIVE_LOW这个属性将被驱动所读取。然后在probe函数中对DTS所添加的资源进行解析代码如下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);gpio_info devm_kzalloc(pdev-dev,sizeof(struct firefly_gpio_info *), GFP_KERNEL);if (!gpio_info) {return -ENOMEM;}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;}if (gpio_request(gpio, firefly-gpio)) {printk(gpio %d request failed!\n, gpio);gpio_free(gpio);return -ENODEV;}gpio_info-firefly_gpio gpio;gpio_info-gpio_enable_value (flag OF_GPIO_ACTIVE_LOW) ? 0:1;gpio_direction_output(gpio_info-firefly_gpio, gpio_info-gpio_enable_value);printk(Firefly gpio putout\n);......
}
of_get_named_gpio_flags 从设备树中读取 firefly-gpio 和 firefly-irq-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);
下面是常用的 GPIO API 定义#include linux/gpio.h
#include linux/of_gpio.henum 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);
中断¶在Firefly的例子程序中还包含了一个中断引脚GPIO口的中断使用与GPIO的输入输出类似首先在DTS文件中增加驱动的资源描述kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly-port.dtsi
gpio {compatible firefly-gpio;firefly-irq-gpio gpio4 29 IRQ_TYPE_EDGE_RISING; /* GPIO4_D5 */
};
IRQ_TYPE_EDGE_RISING表示中断由上升沿触发当该引脚接收到上升沿信号时可以触发中断函数。这里还可以配置成如下IRQ_TYPE_NONE //默认值无定义中断触发类型
IRQ_TYPE_EDGE_RISING //上升沿触发
IRQ_TYPE_EDGE_FALLING //下降沿触发
IRQ_TYPE_EDGE_BOTH //上升沿和下降沿都触发
IRQ_TYPE_LEVEL_HIGH //高电平触发
IRQ_TYPE_LEVEL_LOW //低电平触发
然后在probe函数中对DTS所添加的资源进行解析再做中断的注册申请代码如下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;......gpio_info-firefly_irq_gpio gpio;gpio_info-firefly_irq_mode flag;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-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/rk3399.dtsi 里有i2c4: i2cff3d0000 {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_xferpinctrl-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” 功能。下面是常用的复用 API 定义#include linux/pinctrl/consumer.h
struct device {//...#ifdef CONFIG_PINCTRLstruct dev_pin_info *pins;#endif//...
};
struct dev_pin_info {struct pinctrl *p;struct pinctrl_state *default_state;#ifdef CONFIG_PMstruct 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);
IO-Domain¶在复杂的片上系统SOC中设计者一般会将系统的供电分为多个独立的block这称作电源域Power Domain这样做有很多好处例如在IO-Domain的DTS节点统一配置电压域不需要每个驱动都去配置一次便于管理依照的是Upstream的做法以后如果需要Upstream比较方便IO-Domain的驱动支持运行过程中动态调整电压域例如PMIC的某个Regulator可以1.8v和3.3v的动态切换一旦Regulator电压发生改变会通知IO-Domain驱动去重新设置电压域。AIO-3399J原理图上的 Power Domain Map 表以及配置如下表所示通过RK3399 SDK的原理图可以看到bt656-supply 的电压域连接的是vcc18_dvp, vcc_io是从PMIC RK808的VLDO1出来的在DTS里面可以找到vcc1v8_dvp 将bt656-supply vcc18_dvp。其他路的配置也类似需要注意的是如果这里是其他PMIC所用的Regulator也不一样,具体以实际电路情况为标准。调试方法¶IO指令¶GPIO调试有一个很好用的工具那就是IO指令AIO-3399J的Android系统默认已经内置了IO指令使用IO指令可以实时读取或写入每个IO口的状态这里简单介绍IO指令的使用。首先查看 io 指令的帮助#io --help
Unknown option: ?
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 toone access, or whole file length)-r|w Read from or Write to memory (default read)-f file File to write on memory read, orto read on memory writeaddr The memory address to accessval The value to write (implies -w)Examples:io 0x1000 Reads one byte from 0x1000io 0x1000 0x12 Writes 0x12 to location 0x1000io -2 -l 8 0x1000 Reads 8 words from 0x1000io -r -f dmp -l 100 200 Reads 100 bytes from addr 200 to fileio -w -f img 0x10000 Writes the whole of file to memoryNote access size (-1|2|4) does not apply to file based accesses.
从帮助上可以看出如果要读或者写一个寄存器可以用io -4 -r 0x1000 //读从0x1000起的4位寄存器的值
io -4 -w 0x1000 //写从0x1000起的4位寄存器的值
使用示例查看GPIO1_B3引脚的复用情况从主控的datasheet查到GPIO1对应寄存器基地址为0xff320000从主控的datasheet查到GPIO1B_IOMUX的偏移量为0x00014GPIO1_B3的iomux寄存器地址为基址(Operational Base) 偏移量(offset)0xff3200000x000140xff320014用以下指令查看GPIO1_B3的复用情况# io -4 -r 0xff320014
ff320014: 0000816a
从datasheet查到[7:6]gpio1b3_sel
GPIO1B[3] iomux select
2b00: gpio
2b01: i2c4sensor_sda
2b10: reserved
2b11: reserved
因此可以确定该GPIO被复用为 i2c4sensor_sda。如果想复用为GPIO,可以使用以下指令设置# io -4 -w 0xff320014 0x0000812a
GPIO调试接口¶Debugfs文件系统目的是为开发人员提供更多内核数据方便调试。这里GPIO的调试也可以用Debugfs文件系统获得更多的内核信息。GPIO在Debugfs文件系统中的接口为 /sys/kernel/debug/gpio可以这样读取该接口的信息# cat /sys/kernel/debug/gpio
GPIOs 0-31, platform/pinctrl, gpio0:gpio-2 ( |vcc3v3_3g ) out hi gpio-4 ( |bt_default_wake_host) in lo gpio-5 ( |power ) in hi gpio-9 ( |bt_default_reset ) out lo gpio-10 ( |reset ) out lo gpio-13 ( |? ) out lo GPIOs 32-63, platform/pinctrl, gpio1:gpio-32 ( |vcc5v0_host ) out hi gpio-34 ( |int-n ) in hi gpio-35 ( |vbus-5v ) out lo gpio-45 ( |pmic-hold-gpio ) out hi gpio-49 ( |vcc3v3_pcie ) out hi gpio-54 ( |mpu6500 ) out hi gpio-56 ( |pmic-stby-gpio ) out hi GPIOs 64-95, platform/pinctrl, gpio2:gpio-83 ( |bt_default_rts ) in hi gpio-90 ( |bt_default_wake ) in lo gpio-91 ( |? ) out hi GPIOs 96-127, platform/pinctrl, gpio3:gpio-111 ( |mdio-reset ) out hi GPIOs 128-159, platform/pinctrl, gpio4:gpio-149 ( |hp-con-gpio ) out lo
从读取到的信息中可以知道内核把GPIO当前的状态都列出来了以GPIO0组为例gpio-2(GPIO0_A2)作为3G模块的电源控制脚(vcc3v3_3g)输出高电平(out hi)。FAQs¶Q1: 如何将PIN的MUX值切换为一般的GPIO¶A1: 当使用GPIO request时候会将该PIN的MUX值强制切换为GPIO所以使用该pin脚为GPIO功能的时候确保该pin脚没有被其他模块所使用。Q2: 为什么我用IO指令读出来的值都是0x00000000¶A2: 如果用IO命令读某个GPIO的寄存器读出来的值异常,如 0x00000000或0xffffffff等请确认该GPIO的CLK是不是被关了GPIO的CLK是由CRU控制可以通过读取datasheet下面CRU_CLKGATE_CON* 寄存器来查到CLK是否开启如果没有开启可以用io命令设置对应的寄存器从而打开对应的CLK打开CLK之后应该就可以读到正确的寄存器值了。Q3: 测量到PIN脚的电压不对应该怎么查¶A3: 测量该PIN脚的电压不对时如果排除了外部因素可以确认下该pin所在的io电压源是否正确以及IO-Domain配置是否正确。Q4: gpio_set_value()与gpio_direction_output()有什么区别¶A4: 如果使用该GPIO时不会动态的切换输入输出建议在开始时就设置好GPIO 输出方向后面拉高拉低时使用gpio_set_value()接口而不建议使用gpio_direction_output(), 因为gpio_direction_output接口里面有mutex锁对中断上下文调用会有错误异常且相比 gpio_set_valuegpio_direction_output 所做事情更多浪费。 扫码或长按关注回复「 篮球的大肚子」进入技术群聊关注公众号回复「1024」获取学习资料