Linux的pinctrl和gpio子系统实验

声明:本文章来自正点原子学习记录,仅做个人网上学习笔记使用。

pinctrl系统

pinctrl 子系统重点是设置 PIN( 有的 SOC 叫做 PAD) 的复用
和电气属性

pin配置信息详解

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息
打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:
iomuxc : iomuxc@020e0000 {
         compatible = "fsl,imx6ul-iomuxc" ;
         reg = < 0x020e0000 0x4000 >;
};
打开 imx6ull-alientek-emmc.dts ,找到如下所示内容:
& iomuxc {
         pinctrl - names = "default" ;
         pinctrl - 0 = <& pinctrl_hog_1 >;
         imx6ul - evk {
                pinctrl_hog_1 : hoggrp - 1 {
                        fsl , pins = <
                                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19   0x17059
                                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
                                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
                                MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
                        >;
                };
......
                pinctrl_flexcan1 : flexcan1grp {
                        fsl , pins = <
                                MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
                                MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
                        >;
                };
......
                pinctrl_wdog : wdoggrp {
                        fsl , pins = <
                                MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
                        >;
                };
        };
};
就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不
同。
完整的 iomuxc 节点,如下所示
iomuxc : iomuxc@020e0000 {
        compatible = "fsl,imx6ul-iomuxc" ;
        reg = < 0x020e0000 0x4000 >;
        pinctrl - names = "default" ;
        pinctrl - 0 = <& pinctrl_hog_1 >;
        imx6ul - evk {
                pinctrl_hog_1 : hoggrp - 1 {
                        fsl , pins = <
                                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
                                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
                                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
                                MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
                        >;
......
                };
        };
};

设备树中添加pinctrl节点模块

创建对应节点

同一个外设的 PIN 都放到一个节点里面,打开 imx6ull-alientek-emmc.dts ,在 iomuxc 节点
中的“ imx6ul-evk ”子节点下添加“ pinctrl_test ”节点。添加完成以后如下所示
1 pinctrl_test : testgrp {
2          /* 具体的 PIN 信息 */
3 };

添加“fsl,pins”属性

1 pinctrl_test : testgrp {
2          fsl , pins = <
3         /* 设备所使用的 PIN 配置信息 */
4          >;
5 };

在“fsl,pins”属性中添加 PIN 配置信息

1 pinctrl_test : testgrp {
2         fsl , pins = <
3         MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值 */
4         >;
5 };

gpio子系统

I.MX6ULL gpio 子系统驱动

设备树中的 gpio 信息

打开 imx6ull-alientek-emmc.dtsUART1_RTS_B 这个 PIN pincrtl 设置如下:

316 pinctrl_hog_1 : hoggrp - 1 {
317         fsl , pins = <
318                 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
......
322          >;
SD 卡连接在
I.MX6ULL usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为“ usdhc1 ”的节点,这个
节点就是 SD 卡设备节点,如下所示:
760 & usdhc1 {
761         pinctrl - names = "default" , "state_100mhz" , "state_200mhz" ;
762         pinctrl - 0 = <& pinctrl_usdhc1 >;
763         pinctrl - 1 = <& pinctrl_usdhc1_100mhz >;
764         pinctrl - 2 = <& pinctrl_usdhc1_200mhz >;
765          /* pinctrl-3 = <&pinctrl_hog_1>; */
766         cd - gpios = <& gpio1 19 GPIO_ACTIVE_LOW >;
767         keep - power - in - suspend ;
768         enable - sdio - wakeup ;
769         vmmc - supply = <& reg_sd1_vmmc >;
770         status = "okay" ;
771 };

GPIO 驱动程序简介

gpio 子系统 API 函数

gpio_request 函数
使用一个 GPIO 之前一定要使用 gpio_request 进行申请。
int gpio_request(unsigned gpio, const char *label)
函数参数和返回值含义如下:
gpio :要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信
息,此函数会返回这个 GPIO 的标号。
label :给 gpio 设置个名字。
返回值: 0 ,申请成功;其他值,申请失败。
gpio_free 函数
如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:
void gpio_free(unsigned gpio)
函数参数和返回值含义如下:
gpio :要释放的 gpio 标号。
返回值: 无。
gpio_direction_input 函数
此函数用于设置某个 GPIO 为输入,函数原型如下所示:
int gpio_direction_input(unsigned gpio)
函数参数和返回值含义如下:
gpio :要设置为输入的 GPIO 标号。
返回值: 0 ,设置成功;负值,设置失败。
gpio_direction_output 函数
此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:
int gpio_direction_output(unsigned gpio, int value)
函数参数和返回值含义如下:
gpio :要设置为输出的 GPIO 标号。
value GPIO 默认输出值。
返回值: 0 ,设置成功;负值,设置失败。
gpio_get_value 函数
此函数用于获取某个 GPIO 的值 (0 1) ,此函数是个宏,定义所示:
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)
函数参数和返回值含义如下:
gpio :要获取的 GPIO 标号。
返回值: 非负值,得到的 GPIO 值;负值,获取失败。
gpio_set_value 函数
此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)
函数参数和返回值含义如下:
gpio :要设置的 GPIO 标号。
value 要设置的值。
返回值:

设备树中添加 gpio 节点模板

创建 test 设备节点

1 test {
2          /* 节点内容 */
3 };

添加 pinctrl 信息

1 test {
2         pinctrl - names = "default" ;
3         pinctrl - 0 = <& pinctrl_test >;
4          /* 其他节点内容 */
5 };

添加 GPIO 属性信息

1 test {
2         pinctrl - names = "default" ;
3         pinctrl - 0 = <& pinctrl_test >;
4         gpio = <& gpio1 0 GPIO_ACTIVE_LOW >;
5 };

gpio 相关的 OF 函数

of_gpio_named_count 函数
of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的
是空的 GPIO 信息也会被统计到,比如:
gpios = <0
        &gpio1 1 2
        0
        &gpio2 3 4>;
上述代码的“ gpios ”节点一共定义了 4 GPIO ,但是有 2 个是空的,没有实际的含义。
通过 of_gpio_named_count 函数统计出来的 GPIO 数量就是 4 个,此函数原型如下:
int of_gpio_named_count(struct device_node *np, const char *propname)
函数参数和返回值含义如下:
np :设备节点。
propname :要统计的 GPIO 属性。
返回值: 正值,统计到的 GPIO 数量;负值,失败。
of_gpio_count 函数
of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“ gpios ”这个属
性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下
所示:
int of_gpio_count(struct device_node *np)
函数参数和返回值含义如下:
np :设备节点。
of_get_named_gpio 函数
此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO API 函数都要使用 GPIO 编号,
此函数会将设备树中类似 <&gpio5 7 GPIO_ACTIVE_LOW> 的属性信息转换为对应的 GPIO
号,此函数在驱动中使用很频繁!函数原型如下:
int of_get_named_gpio(struct device_node  *np, const char *propname, int index)
函数参数和返回值含义如下:
np :设备节点。
propname :包含要获取 GPIO 信息的属性名。
index GPIO 索引,因为一个属性里面可能包含多个 GPIO ,此参数指定要获取哪个 GPIO
的编号,如果只有一个 GPIO 信息的话此参数为 0
返回值: 正值,获取到的 GPIO 编号;负值,失败。

pinctrl和gpio实验

修改设备树文件

添加 pinctrl 节点

打开 imx6ull-alientek
emmc.dts ,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“ pinctrl_led ”的子节点,节点
内容如下所示:
1 pinctrl_led : ledgrp {
2         fsl , pins = <
3                 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4          >;
5 };
GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03 ,电气属性值为 0X10B0

添加 LED 设备节点

在根节点“ / ”下创建 LED 灯节点,节点名为“ gpioled ”,节点内容如下:
1 gpioled {
2         #address - cells = < 1 >;
3         #size - cells = < 1 >;
4         compatible = "atkalpha-gpioled" ;
5         pinctrl - names = "default" ;
6         pinctrl - 0 = <& pinctrl_led >;
7         led - gpio = <& gpio1 3 GPIO_ACTIVE_LOW >;
8         status = "okay" ;
9 } ;
6 行, pinctrl-0 属性设置 LED 灯所使用的 PIN 对应的 pinctrl 节点。
7 行, led-gpio 属性指定了 LED 灯所使用的 GPIO ,在这里就是 GPIO1 IO03 ,低电平
有效。

检查 PIN 是否被其他外设使用

检查 PIN 有没有被其他外设使用包括两个方 面:
①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。
在本次实验中 LED 灯使用的 PIN GPIO1_IO03 ,因此先检查 GPIO_IO03 这个 PIN 有没
有被其他的 pinctrl 节点使用,在 imx6ull-alientek-emmc.dts 中搜索GPIO_IO03找到如下内容:
480 pinctrl_tsc : tscgrp {
481         fsl , pins = <
482                 MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
483                 MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
484                 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
485                 MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
486          >;
487 };
pinctrl_tsc 节点是 TSC( 电阻触摸屏接口 ) pinctrl 节点,从第 484 行可以看出,默认情况下
GPIO1_IO03 作为了 TSC 外设的 PIN 。所以我们需要将第 484 行屏蔽掉!和 C 语言一样,在要
屏蔽的内容前后加上“ /* ”和“ */ ”符号即可。修改如下:
480 pinctrl_tsc : tscgrp {
481         fsl , pins = <
482                 MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
483                 MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
484                 /* MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0 */
485                 MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
486          >;
487 };
继续搜索“ gpio1 3,选择大小写兼容 ”,看看除了本章的 LED 灯以外还有没有其他的
地方也使用了 GPIO1_IO03 ,找到一个屏蔽一个。
设备树编写完成以后使用“ make dtbs ”命令重新编译设备树,然后使用新编译出来的
imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。启动成功以后进入“ /proc/device-tree ”目录中
查看“ gpioled ”节点是否存在,如果存在的话就说明设备树基本修改成功。

LED 灯驱动程序编写

1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <asm/mach/map.h>
15 #include <asm/uaccess.h>
16 #include <asm/io.h>

27 #define GPIOLED_CNT 1 /* 设备号个数 */
28 #define GPIOLED_NAME "gpioled" /* 名字 */
29 #define LEDOFF 0 /* 关灯 */
30 #define LEDON 1 /* 开灯 */
31 
32 /* gpioled 设备结构体 */
33 struct gpioled_dev{
34     dev_t devid; /* 设备号 */
35     struct cdev cdev; /* cdev */
36     struct class *class; /* 类 */
37     struct device *device; /* 设备 */
38     int major; /* 主设备号 */
39     int minor; /* 次设备号 */
40     struct device_node *nd; /* 设备节点 */
41     int led_gpio; /* led 所使用的 GPIO 编号 */
42 };
43 
44 struct gpioled_dev gpioled; /* led 设备 */
45 
46 /*
47 * @description : 打开设备
48 * @param – inode : 传递给驱动的 inode
49 * @param – filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
50 * 一般在 open 的时候将 private_data 指向设备结构体。
51 * @return : 0 成功;其他 失败
52 */
53 static int led_open(struct inode *inode, struct file *filp)
54 {
55     filp->private_data = &gpioled; /* 设置私有数据 */
56     return 0;
57 }
58 
59 /*
60 * @description : 从设备读取数据
61 * @param – filp : 要打开的设备文件(文件描述符)
62 * @param - buf : 返回给用户空间的数据缓冲区
63 * @param - cnt : 要读取的数据长度
64 * @param – offt : 相对于文件首地址的偏移
65 * @return : 读取的字节数,如果为负值,表示读取失败
66 */
67 static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
68 {
69     return 0;
70 }
71 
72 /*
73 * @description : 向设备写数据
74 * @param - filp : 设备文件,表示打开的文件描述符
75 * @param - buf : 要写给设备写入的数据
76 * @param - cnt : 要写入的数据长度
77 * @param – offt : 相对于文件首地址的偏移
78 * @return : 写入的字节数,如果为负值,表示写入失败
79 */
80 static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
81 {
82     int retvalue;
83     unsigned char databuf[1];
84     unsigned char ledstat;
85     struct gpioled_dev *dev = filp->private_data;
86 
87     retvalue = copy_from_user(databuf, buf, cnt);
88     if(retvalue < 0) {
89         printk("kernel write failed!\r\n");
90         return -EFAULT;
91     }
92 
93     ledstat = databuf[0]; /* 获取状态值 */
94 
95     if(ledstat == LEDON) { 
96         gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
97     } else if(ledstat == LEDOFF) {
98         gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
99     }
100     return 0;
101 }
102
103 /*
104 * @description : 关闭/释放设备
105 * @param – filp : 要关闭的设备文件(文件描述符)
106 * @return : 0 成功;其他 失败
107 */
108 static int led_release(struct inode *inode, struct file *filp)
109 {
110     return 0;
111 }
112
113 /* 设备操作函数 */
114 static struct file_operations gpioled_fops = {
115     .owner = THIS_MODULE,
116     .open = led_open,
117     .read = led_read,
118     .write = led_write,
119     .release = led_release,
120 };
121
122 /*
123 * @description : 驱动入口函数
124 * @param : 无
125 * @return : 无
126 */
127 static int __init led_init(void)
128 {
129     int ret = 0;
130
131     /* 设置 LED 所使用的 GPIO */
132     /* 1、获取设备节点:gpioled */
133     gpioled.nd = of_find_node_by_path("/gpioled");
134     if(gpioled.nd == NULL) {
135         printk("gpioled node cant not found!\r\n");
136         return -EINVAL;
137     } else {
138         printk("gpioled node has been found!\r\n");
139     }
140
141     /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
142     gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
143     if(gpioled.led_gpio < 0) {
144         printk("can't get led-gpio");
145         return -EINVAL;
146     }
147     printk("led-gpio num = %d\r\n", gpioled.led_gpio);
148 
149     /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
150     ret = gpio_direction_output(gpioled.led_gpio, 1);
151     if(ret < 0) {
152         printk("can't set gpio!\r\n");
153     }
154
155     /* 注册字符设备驱动 */
156     /* 1、创建设备号 */
157     if (gpioled.major) { /* 定义了设备号 */
158         gpioled.devid = MKDEV(gpioled.major, 0);
159         register_chrdev_region(gpioled.devid, GPIOLED_CNT,GPIOLED_NAME);
160     } else { /* 没有定义设备号 */
161         alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号*/
162         gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
163         gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
164     }
165     printk("gpioled major=%d,minor=%d\r\n",gpioled.major,gpioled.minor); 
166 
167     /* 2、初始化 cdev */
168     gpioled.cdev.owner = THIS_MODULE;
169     cdev_init(&gpioled.cdev, &gpioled_fops);
170 
171     /* 3、添加一个 cdev */
172     cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
173
174     /* 4、创建类 */
175     gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
176     if (IS_ERR(gpioled.class)) {
177         return PTR_ERR(gpioled.class);
178     }
179
180     /* 5、创建设备 */
181     gpioled.device = device_create(gpioled.class, NULL,gpioled.devid, NULL, GPIOLED_NAME);
182     if (IS_ERR(gpioled.device)) {
183         return PTR_ERR(gpioled.device);
184     }
185     return 0;
186 }
187
188 /*
189 * @description : 驱动出口函数
190 * @param : 无
191 * @return : 无
192 */
193 static void __exit led_exit(void)
194 {
195     /* 注销字符设备驱动 */
196     cdev_del(&gpioled.cdev); /* 删除 cdev */
197     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */
198
199     device_destroy(gpioled.class, gpioled.devid);
200     class_destroy(gpioled.class);
201 }
202
203 module_init(led_init);
204 module_exit(led_exit);
205 MODULE_LICENSE("GPL");
206 MODULE_AUTHOR("zipeng");

编写测试 APP

运行测试

编译驱动程序

1 KERNELDIR := /home/zipeng/linux/myKernel/temp/linux-imx
rel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := gpioled.o
......
11 clean :
12 $(MAKE) -C $(KERNELDIR) M = $(CURRENT_PATH) clean

make -j32

编译测试 APP

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

运行测试

depmod //第一次加载驱动的时候需要运行此命令
  modprobe gpioled.ko   //加载驱动
./ledApp /dev/gpioled 1 //打开 LED
./ledApp /dev/gpioled 0 //熄灭  LED
卸载驱动
rmmod gpioled.ko
  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值