pinctrl子系统和gpio子系统试验
6ULL的GPIO的使用
调用pinctrl子系统完成gpio复用功能和电气属性的设置。
1、设置PIN的复用和电气属性
2、配置GPIO
pinctrl子系统
使用GPIO子系统来使用GPIO子系统。
打开imx6ull.dts查看复用设备树。
和参考手册对应上了
根据设备的类型,创造对应的子节点,然后设备所用的pin放到子节点。
如何添加一个pin 的信息。
imx6ul-evk {
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058 /* USB_OTG1_ID */
>;
};
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
>;
};
我们在imx6ul-pinfunc.h中找到:
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
<mux_reg conf_reg input_reg mux_mode input_val>
0x0090 0x031C 0x0000 0x5 0x0
IOMUXC父节点首地址0x020e0000,因此UART1_RTS_B这个PIN的mux寄存器地址就是:0x020e0000+0x0090=0x020e 0090。
conf_reg:0x020e0000+0x031C=0x020e 031C,这个寄存器就是UART1_RTS_B的电气属性配置寄存器。
input_reg,偏移为0,表示UART1_RTS_B这个PIN没有input功能。
mux_mode:5表示复用为GPIO1_IO19,将其写入0x020e 0090
input_val:就是写入input_reg寄存器的值。
0x17059:为PIN的电气属性配置寄存器值。
pinctrl驱动
如何找到IMX6ULL对应的pinctrl子系统驱动。
设备树的节点和驱动通过compatible
属性匹配起来。此属性是描述一段字符串列表。驱动文件里面有一个描述驱动兼容性的东西。当设备树节点的compatible
属性和驱动里面的兼容性匹配,就表示设备和驱动匹配了。
找到pinctrl-mx6ul.c文件就是找到了。
当驱动和设备匹配好了之后,执行probe函数。也就是
imx6ul_pinctrl_probe
引脚复用的时候需要用到pinctrl子系统。
gpio子系统
引脚作为gpio使用的时候,要用到gpio子系统。
设备树中添加 pinctrl 节点模板
我们已经对 pinctrl 有了比较深入的了解,接下来我们学习一下如何在设备树中添加某个外
设 的 PIN 信息。关于 I.MX 系 列 SOC 的 pinctrl 设备树绑定信息可以参考文档
Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。这里我们虚拟一个名为“test”的设
备,test 使用了 GPIO1_IO00 这个 PIN 的 GPIO 功能,pinctrl 节点添加过程如下:
1、创建对应的节点
同一个外设的 PIN 都放到一个节点里面,打开 imx6ull-alientek-emmc.dts,在 iomuxc 节点
中的“imx6ul-evk”子节点下添加“pinctrl_test”节点,注意!节点前缀一定要为“pinctrl_”。添
加完成以后如下所示:
示例代码 45.1.2.10 test 设备 pinctrl 节点
1 pinctrl_test: testgrp {
2 /* 具体的 PIN 信息 */
3 };
2、添加“fsl,pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,
因为对于 I.MX 系列 SOC 而言,pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配
置信息,完成以后如下所示:
示例代码 45.1.2.11 添加"fsl,pins"属性
1 pinctrl_test: testgrp {
2 fsl,pins = <
3 /* 设备所使用的 PIN 配置信息 */
4 >;
5 };
3、在“fsl,pins”属性中添加 PIN 配置信息
最后在“fsl,pins”属性中添加具体的 PIN 配置信息,完成以后如下所示:
示例代码 45.1.2.13 完整的 test 设备 pinctrl 子节点
1 pinctrl_test: testgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
4 >;
5 };
至此,我们已经在 imx6ull-alientek-emmc.dts 文件中添加好了 test 设备所使用的 PIN 配置信
息。
驱动中对gpio的操作函数
1、首先,获取到GPIO所处的设备节点,比如of_find_node_by_path。
2、获取GPIO编号, of_get_named_gpio函数,返回值就是GPIO编号。
3、请求此编号的GPIO,gpio_request函数
4、设置GPIO,输入或输出,gpio_direction_input或gpio_direction_output。
5、如果是输入,那么通过gpio_get_value函数读取GPIO值,如果是输出,通过gpio_set_value设置GPIO值。
led驱动程序编写:
编写驱动:
修改设备树
1、添加 pinctrl 节点
I.MX6U-ALPHA 开发板上的 LED 灯使用了 GPIO1_IO03 这个 PIN,打开 imx6ull-alientekemmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点,节点
内容如下所示:
示例代码 45.4.1.1 GPIO1_IO03 pinctrl 节点
1 pinctrl_led: ledgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4 >;
5 };
第 3 行,将 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03,电气属性值为 0X10B0。
2、添加 LED 设备节点
在根节点“/”下创建 LED 灯节点,节点名为“gpioled”,节点内容如下:
示例代码 45.4.1.2 创建 LED 灯节点
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,低电平
有效。稍后编写驱动程序的时候会获取 led-gpio 属性的内容来得到 GPIO 编号,因为 gpio 子系
统的 API 操作函数需要 GPIO 编号。
3、检查 PIN 是否被其他外设使用
修改结果如下:
找到需要使用的引脚:
GPIO复用操作:
重新加载设备树:
新的节点已经生成了。
P30 左忠凯最后一次手撕代码。
加载驱动之后申请失败了
注意:申请IO失败很大部分都是这个IO被其他外设占用了,检查设备树。查找有哪些使用同一个IO的。
1、检查复用设置。
2、查看设备树。
加载驱动成功:
小结:
1、添加pinctrl信息
2、检查设备树要使用IO有没有被占用
3、添加设备节点信息。在设备上创建一个属性,此属性藐视所使用的GPIO
4、编写驱动、获取GPIO的编号,并申请IO,申请成功可以使用此IO。
驱动源码
/**
*my first driver
*
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#define LED_NAME "gpioled"
#define LED_COUNT 1
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
struct gpioled_dev {
struct cdev cdev;
struct class *class;/*类:为了自动创建节点*/
struct device *device;/*设备:为了自动创建节点*/
dev_t devid; //设备号
int major; //主设备号
int minor; //次设备号
struct device_node *nd; //设备节点
int led_gpio;
};
struct gpioled_dev gpioled; //led设备
static int gpioled_open(struct inode *inode,struct file *filp)
{
filp->private_data = &gpioled;
return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
struct gpioled_dev *dev = (struct gpioled_dev *)filp->private_data;
int retvalue = 0;
unsigned char databuf[1];
retvalue = copy_from_user(databuf,buf,cnt);
if (retvalue < 0)
{
printk("kernel write failed!\r\n");
return -EFAULT;
}
if (databuf[0] == LEDON)
{
gpio_set_value(dev->led_gpio,0);
}
else if(databuf[0] == LEDOFF){
gpio_set_value(dev->led_gpio,1);
}
return 0;
}
static int gpioled_release(struct inode *inode,struct file *filp)
{
//struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = gpioled_write,
.open = gpioled_open,
.release = gpioled_release,
};
static int __init led_init(void)
{
int ret = 0;
/*注册字符设备*/
gpioled.major = 0;
if (gpioled.major){
gpioled.devid = MKDEV(gpioled.major,0);//设备号
ret = register_chrdev_region(gpioled.devid,LED_COUNT,LED_NAME);
} else{
ret = alloc_chrdev_region(&gpioled.devid,0,LED_COUNT,LED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}printk("led major = %d led minor = %d \r\n",gpioled.major,gpioled.minor);
if (ret < 0){
goto faile_devid;
}
/*2、添加字符设备*/
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev,&led_fops);
ret = cdev_add(&gpioled.cdev,gpioled.devid,LED_COUNT);
if(ret<0){
goto fail_cdev;
}
/*3、创建设备类*/
gpioled.class = class_create(THIS_MODULE,LED_NAME);
if(IS_ERR(gpioled.class)){
ret = PTR_ERR(gpioled.class);
printk("can't class_create \r\n");
goto fail_class;
}
/*创建设备节点*/
gpioled.device = device_create(gpioled.class,NULL,gpioled.devid,NULL,LED_NAME);
if(IS_ERR(gpioled.device)){
ret = PTR_ERR(gpioled.device);
printk("can't device_create \r\n");
goto fail_device;
}
/*4、获取设备属性内容*/
gpioled.nd = of_find_node_by_path("/gpioled");
if (gpioled.nd == NULL )
{
ret = -EINVAL;
goto fail_findnd;
}
/*获取LED所对应的GPIO*/
gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-goios",0);
if (gpioled.led_gpio < 0)
{
printk("can't find gpio \r\n");
goto fail_findnode;
}
printk("led gpio number = %d\r\n",gpioled.led_gpio);
/*申请的GPIO*/
ret = gpio_request(gpioled.led_gpio,"led-gpio");
if (ret)
{
printk("gpio_request errno \r\n");
ret = -EINVAL;
}
/*使用IO*/
ret = gpio_direction_output(gpioled.led_gpio,1);
if (ret)
{
goto fail_setoutput;
}
gpio_set_value(gpioled.led_gpio,0);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
// fail_findu32arr:
// fail_findrs:
fail_findnd:
device_destroy(gpioled.class,gpioled.devid);
fail_device:
class_destroy(gpioled.class);
fail_class:
cdev_del(&gpioled.cdev);
fail_cdev:
unregister_chrdev_region(gpioled.devid,LED_COUNT);
faile_devid:
return ret;
}
static void __exit led_exit(void)
{
/* 关灯 */
gpio_set_value(gpioled.led_gpio,1);
/*删除设备*/
cdev_del(&gpioled.cdev);
/*释放设备号*/
unregister_chrdev_region(gpioled.devid,LED_COUNT);
/*摧毁设备*/
device_destroy(gpioled.class,gpioled.devid);
/*释放类*/
class_destroy(gpioled.class);
/*释放IO*/
gpio_free(gpioled.led_gpio);
}
//模块加载函数
module_init(led_init);
//模块卸载
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qhy");
蜂鸣器驱动:
1、添加蜂鸣器的节点
注意:6UL和6ULL的SNVS组IO的地址是不同的!
注意区别:
例程驱动源码:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define BEEPNAME "beep"
#define BEEPCOUNT 1
#define BEEPON 1
#define BEEPOFF 0
struct beep_dev
{
dev_t devid; /*设备ID*/
struct cdev cdev; /*字符设备结构体*/
struct device *device; /*设备*/
struct class *class; /* 类 */
struct device_node *nd; /*设备节点*/
int beep_num; /*beep所使用的GPIO编号*/
int major; /*主设备号*/
int minor; /*次设备号*/
};
struct beep_dev beep_gpio;
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int beep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &beep_gpio; /* 设置私有数据 */
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t beep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
unsigned char databuf[1];
unsigned char beepstat;
int ret;
ret = copy_from_user(databuf,buf,cnt);
if(ret < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
beepstat = databuf[0];
if (beepstat == BEEPON)
{
gpio_direction_output(beep_gpio.beep_num,0); /*打开蜂鸣器*/
}
else if (beepstat == BEEPOFF)
{
gpio_direction_output(beep_gpio.beep_num,1); /*关闭蜂鸣器*/
}
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int beep_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*设备操作函数*/
static struct file_operations beep_fops =
{
.owner = THIS_MODULE,
.open = beep_open,
.release = beep_release,
.write = beep_write,
};
static int __init beep_init(void){
int ret = 0;
/*设置GPIO所使用的GPIO*/
/*1、获取设备的节点:beep*/
beep_gpio.nd = of_find_node_by_path("/beep");
if (beep_gpio.nd == NULL)
{
printk("beep node not find!\r\n");
return -EINVAL;
}
/*2、获取设备树的GPIO的属性,得到所使用的Beep的编号*/
beep_gpio.beep_num = of_get_named_gpio(beep_gpio.nd,"beep-gpio",0);
if (beep_gpio.beep_num < 0)
{
printk("can't get beep-gpio");
return -EINVAL;
}
ret = gpio_requset(beep_gpio.num,"beep-gpio");
if(ret)
{
printk("can't gpio requset!\r\n");
}
/*3、设置GPIO5_IO01为输出,并且输出为高电平,默认关闭GPIO*/
ret = gpio_direction_output(beep_gpio.beep_num,1);
if (ret < 0)
{
printk("can't set gpio!\r\n");
}
/*注册字符设备驱动*/
/*1、创建设备号*/
if (beep_gpio.major)
{
beep_gpio.devid = MKDEV(beep_gpio.major,0);
register_chrdev_region(beep_gpio.devid,BEEPCOUNT,BEEPNAME);
}else{
alloc_chrdev_region(&beep_gpio.devid,0,BEEPCOUNT,BEEPNAME);
beep_gpio.major = MAJOR(beep_gpio.devid);
beep_gpio.minor = MINOR(beep_gpio.devid);
}
/*2、初始化字符设备*/
beep_gpio.cdev.owner = THIS_MODULE;
cdev_init(&beep_gpio.cdev,&beep_fops);
/*3、添加字符设备*/
cdev_add(&beep_gpio.cdev,beep_gpio.devid,BEEPCOUNT);
/*4、创建类:为了自动生成设备节点*/
beep_gpio.class = class_create(THIS_MODULE, BEEPNAME);
if(IS_ERR(beep_gpio.class)){
return PTR_ERR(beep_gpio.class);
}
/*5、创建设备*/
beep_gpio.device = device_create(beep_gpio.class,NULL,beep_gpio.devid,NULL,BEEPNAME);
if(IS_ERR(beep_gpio.device)){
return PTR_ERR(beep_gpio.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit beep_exit(void){
/*关闭蜂鸣器*/
gpio_direction_output(beep_gpio.beep_num,0);
/*销毁设备*/
device_destroy(beep_gpio.class,beep_gpio.devid);
/*销毁类*/
class_destroy(beep_gpio.class);
/*注销设备号*/
unregister_chrdev_region(beep_gpio.devid,BEEPCOUNT);
/*删除cdev*/
cdev_del(&beep_gpio.cdev);
gpio_free(beep_gpio.beep_num);
}
module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qhy");