1.2.4 sysfs中的访问方法
在sysfs中访问GPIO,实际上用的就是引脚号,老的方法。
a. 先确定某个GPIO Controller的基准引脚号(base number),再计算出某个引脚的号码。
方法如下:
① 先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录:
② 然后进入某个gpiochip目录,查看文件label的内容
③ 根据label的内容对比设备树
label内容来自设备树,比如它的寄存器基地址。用来跟设备树(dtsi文件)比较,就可以知道这对应哪一个GPIO Controller。
下图是在100asK_imx6ull上运行的结果,通过对比设备树可知gpiochip96对应gpio4:
所以gpio4这组引脚的基准引脚号就是96,这也可以“cat base”来再次确认。
b. 基于sysfs操作引脚:
以100ask_imx6ull为例,它有一个按键,原理图如下:
那么GPIO4_14的号码是96+14=110,可以如下操作读取按键值:
echo 110 > /sys/class/gpio/export echo in > /sys/class/gpio/gpio110/direction cat /sys/class/gpio/gpio110/value echo 110 > /sys/class/gpio/unexport
注意:如果驱动程序已经使用了该引脚,那么将会export失败,会提示下面的错误:
对于输出引脚,假设引脚号为N,可以用下面的方法设置它的值为1:
echo N > /sys/class/gpio/export echo out > /sys/class/gpio/gpioN/direction echo 1 > /sys/class/gpio/gpioN/value echo N > /sys/class/gpio/unexport
1.3 基于GPIO子系统的LED驱动程序
1.3.1 编写思路
GPIO的地位跟其他模块,比如I2C、UART的地方是一样的,要使用某个引脚,需要先把引脚配置为GPIO功能,这要使用Pinctrl子系统,只需要在设备树里指定就可以。在驱动代码上不需要我们做任何事情。
GPIO本身需要确定引脚,这也需要在设备树里指定。 设备树节点会被内核转换为platform_device。
对应的,驱动代码中要注册一个platform_driver,在probe函数中:获得引脚、注册file_operations。
在file_operations中:设置方向、读值/写值。
下图就是一个设备树的例子:
1.3.2 在设备树中添加Pinctrl信息
有些芯片提供了设备树生成工具,在GUI界面中选择引脚功能和配置信息,就可以自动生成Pinctrl子结点。把它复制到你的设备树文件中,再在client device结点中引用就可以。
有些芯片只提供文档,那就去阅读文档,一般在内核源码目录Documentation\devicetree\bindings\pinctrl下面,保存有该厂家的文档。
如果连文档都没有,那只能参考内核源码中的设备树文件,在内核源码目录arch/arm/boot/dts目录下。 最后一步,网络搜索。
Pinctrl子节点的样式如下:
1.3.3 在设备树中添加GPIO信息
先查看电路原理图确定所用引脚,再在设备树中指定:添加”[name]-gpios”属性,指定使用的是哪一个GPIO Controller里的哪一个引脚,还有其他Flag信息,比如GPIO_ACTIVE_LOW等。具体需要多少个cell来描述一个引脚,需要查看设备树中这个GPIO Controller节点里的“#gpio-cells”属性值,也可以查看内核文档。 示例如下:
1.3.4 编程示例
在实际操作过程中也许会碰到意外的问题,现场演示如何解决。
a. 定义、注册一个platform_driver
b. 在它的probe函数里:
b.1 根据platform_device的设备树信息确定GPIO:gpiod_get
b.2 定义、注册一个file_operations结构体
b.3 在file_operarions中使用GPIO子系统的函数操作GPIO: gpiod_direction_output、gpiod_set_value
好处:这些代码对所有的板子都是完全一样的!
使用GIT命令载后,源码leddrv.c位于这个目录下:
01_all_series_quickstart\ 05_嵌入式Linux驱动开发基础知识\source\ 05_gpio_and_pinctrl\ 01_led
摘录重点内容:
a. 注册platform_driver
注意下面第122行的"100ask,leddrv",它会跟设备树中节点的compatible对应:
121 static const struct of_device_id ask100_leds[] = { 122 { .compatible = "100ask,leddrv" }, 123 { }, 124 }; 125 126 /* 1. 定义platform_driver */ 127 static struct platform_driver chip_demo_gpio_driver = { 128 .probe = chip_demo_gpio_probe, 129 .remove = chip_demo_gpio_remove, 130 .driver = { 131 .name = "100ask_led", 132 .of_match_table = ask100_leds, 133 }, 134 }; 135 136 /* 2. 在入口函数注册platform_driver */ 137 static int __init led_init(void) 138 { 139 int err; 140 141 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 142 143 err = platform_driver_register(&chip_demo_gpio_driver); 144 145 return err; 146 }
b. 在probe函数中获得GPIO
核心代码是第87行,它从该设备(对应设备树中的设备节点)获取名为“led”的引脚。在设备树中,必定有一属性名为“led-gpios”或“led-gpio”。
77 /* 4. 从platform_device获得GPIO 78 * 把file_operations结构体告诉内核:注册驱动程序 79 */ 80 static int chip_demo_gpio_probe(struct platform_device *pdev) 81 { 82 //int err; 83 84 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 85 86 /* 4.1 设备树中定义有: led-gpios=<...>; */ 87 led_gpio = gpiod_get(&pdev->dev, "led", 0); 88 if (IS_ERR(led_gpio)) { 89 dev_err(&pdev->dev, "Failed to get GPIO for led\n"); 90 return PTR_ERR(led_gpio); 91 } 92
c. 注册file_operations结构体:
这是老套路了:
93 /* 4.2 注册file_operations */ 94 major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */ 95 96 led_class = class_create(THIS_MODULE, "100ask_led_class"); 97 if (IS_ERR(led_class)) { 98 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 99 unregister_chrdev(major, "led"); 100 gpiod_put(led_gpio); 101 return PTR_ERR(led_class); 102 } 103 104 device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */ 105
d. 在open函数中调用GPIO函数设置引脚方向:
51 static int led_drv_open (struct inode *node, struct file *file) 52 { 53 //int minor = iminor(node); 54 55 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 56 /* 根据次设备号初始化LED */ 57 gpiod_direction_output(led_gpio, 0); 58 59 return 0; 60 }
e. 在write函数中调用GPIO函数设置引脚值:
34 /* write(fd, &val, 1); */ 35 static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) 36 { 37 int err; 38 char status; 39 //struct inode *inode = file_inode(file); 40 //int minor = iminor(inode); 41 42 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 43 err = copy_from_user(&status, buf, 1); 44 45 /* 根据次设备号和status控制LED */ 46 gpiod_set_value(led_gpio, status); 47 48 return 1; 49 }
f. 释放GPIO:
gpiod_put(led_gpio);
1.4 在100ASK_IMX6ULL上机实验
1.4.1 确定引脚并生成设备树节点
NXP公司对于IMX6ULL芯片,有设备树生成工具。我们也把它上传到GIT去了,使用GIT命令载后,在这个目录下:
01_all_series_quickstart\ 05_嵌入式Linux驱动开发基础知识\source\ 05_gpio_and_pinctrl\ tools\ imx\
安装“Pins_Tool_for_i.MX_Processors_v6_x64.exe”后运行,打开IMX6ULL的配置文件“MCIMX6Y2xxx08.mex”,就可以在GUI界面中选择引脚,配置它的功能,这就可以自动生成Pinctrl的子节点信息。
100ASK_IMX6ULL使用的LED原理图如下,可知引脚是GPIO5_3:
在设备树工具中,如下图操作:
把自动生成的设备树信息,放到内核源码arch/arm/boot/dts/100ask_imx6ull-14x14.dts中,代码如下:
a. Pinctrl信息:
&iomuxc_snvs { …… myled_for_gpio_subsys: myled_for_gpio_subsys{ fsl,pins = < MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0 >; };
b. 设备节点信息(放在根节点下):
myled { compatible = "100ask,leddrv"; pinctrl-names = "default"; pinctrl-0 = <&myled_for_gpio_subsys>; led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>; };
1.4.2 编译程序
编译设备树后,要更新设备树。
编译驱动程序时,“leddrv_未测试的原始版本.c”是有错误信息的,“leddrv.c”是修改过的。
测试方法,在板子上执行命令:
# insmod leddrv.ko # ls /dev/100ask_led0 # ./ledtest /dev/100ask_led0 on # ./ledtest /dev/100ask_led0 off