声明:本文章来自正点原子学习记录,仅做个人网上学习笔记使用。
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.dts, UART1_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