网站logo做黑页,wordpress有赞云,商城网站制作公司,国外域名服务商目录
Pinctrl子系统的概念
GPIO子系统的概念
定义自己的GPIO节点
GPIO子系统的函数
引脚号的确定
基于GPIO子系统的驱动程序
驱动程序
设备树修改 之前我们进行驱动开发的时候#xff0c;对于硬件的操作是依赖于ioremap对寄存器的物理地址进行映射#xff0c;以此来达…目录
Pinctrl子系统的概念
GPIO子系统的概念
定义自己的GPIO节点
GPIO子系统的函数
引脚号的确定
基于GPIO子系统的驱动程序
驱动程序
设备树修改 之前我们进行驱动开发的时候对于硬件的操作是依赖于ioremap对寄存器的物理地址进行映射以此来达到对操控硬件的目的但是在实际的开发中如果对引脚一个个的进行物理地址的映射并不现实我们在这里使用Pinctrl子系统来完成对引脚的配置以及复用等。pinctrl子系统由BSP工程师来完成驱动工程师使用这个子系统
Pinctrl子系统的概念 pinctrl中引入两个概念一个是client device 一个是 controllerclient device表示客户也就是子系统的使用者在这里主要指定状态也就是引脚在不同的状态下使用的不同配置而controller则主要是对应状态的具体详细配置
假如此时引脚的状态为“default”那么就是对应pinctrl-0这个节点那么引脚的功能function设置为uart0groups设置为“u0rxtx”“u0rtscts”。
client device的格式一般是一样的但是controller这里的格式不同的开发板有不同的格式格式虽然不同但是概念是一样的 GPIO子系统的概念
当我们使用pinctrl子系统设置了引脚为GPIO模式的时候我们就可以使用GPIO子系统来设置引脚方向(输入还是输出)、读值──获得电平状态写 值──输出高低电平。
在之前我们一般是使用寄存器操控来控制引脚而在这里我们使用GPIO子系统完全是使用API来控制引脚而不是设计寄存器操作这样不管是使用什么板都是兼容的
我们可以在设备树中指定GPIO引脚然后在驱动程序中直接使用对应的函数来获得 GPIO、设置 GPIO 方向、读 取/设置 GPIO 值。 几乎所有芯片的GPIO都是分组的我们需要在自己所需要操控的GPIO分组中找到自己所需要的。如下就是一个GPIO Controller也就是一个GPIO分组为GPIO分组1.一般来说GPIO Controller是由厂家来实现的我们只负责使用 我们暂时关注这两个属性 。“gpio-controller”表示这个节点是一个 GPIO Controller它下面有很多引脚。 “#gpio-cells ”表示这个控制器下每一个引脚要用 2 个 32 位的数 (cell)来描述。至于为什么需要使用两个cell来表示一般第一个cell第一个32位数表示哪个引脚第二个表示有效电平。 GPIO_ACTIVE_HIGH 高电平有效 GPIO_ACTIVE_LOW : 低电平有效 定义自己的GPIO节点
定义 GPIO Controller 是芯片厂家的事那么我们该如何定义自己的节点呢。
如下图所示我在的这个节点中通过gpios 多少来设置我在这个节点当中使用的是哪个GPIO节点一般的使用格式为GPIO组 引脚 什么电平激活 下图中的表示为此节点使用的是GPIO5组当中的3引脚当电平为低电平的时候激活
如下的设置为设备树当中的例子只需要看 gpios 这个属性即可 一般来说如果我们要定义自己的节点那么格式如下
compatible属性作用是与驱动代码中的of_match_table进行匹配当匹配成功之后驱动代码才会调用probe获取到引脚硬件信息。
pinctrl-name和pinctrl-0都是pinctrl的信息用于设置引脚在不同状态下pinctrl-name决定的引脚复用和状态设置开漏、上拉等pinctrl-0.....对应的状态决定
led-gpio用来决定引脚信息这个属性的节点为name-gpio如下代码中可知name为led则在驱动代码中可以通过name来获得引脚信息
mynode{compatible ;pinctrl-name ;pinctrl-0 ;led-gpio ;};
GPIO子系统的函数
当我们通过pinctrl子系统定义完了引脚信息后我们应该如何在驱动代码中获取设备树中对应的引脚信息呢我们使用如下这些GPIO 子系统的接口函数。
GPIO 子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。通常情况下我们使用前者也就是基于描述符的。前者使用起来更为简单后者还要基于引脚号。前者的函数都有前缀“gpiod_”后者的函数都有前缀“gpio_”。
使用这些函数需要包含如下头文件
#include linux/gpio/consumer.h // descriptor-based
#include linux/gpio.h // legacy
常用函数如下 descriptor-based legacy 获得 GPIO gpiod_getgpio_requestgpiod_get_indexgpiod_get_arraygpio_request_arraydevm_gpiod_getdevm_gpiod_get_indexdevm_gpiod_get_array设置方向 gpiod_direction_inputgpio_direction_inputgpiod_direction_outputgpio_direction_output读值、写值 gpiod_get_valuegpio_get_valuegpiod_set_valuegpio_set_value释放 GPIO gpio_free gpio_free gpiod_putgpio_free_arraygpiod_put_arraydevm_gpiod_putdevm_gpiod_put_array我们假设在设备树中存在如下节点
foo_device {
compatible acme,foo;
...
led-gpios gpio 15 GPIO_ACTIVE_HIGH, /* red */gpio 16 GPIO_ACTIVE_HIGH, /* green */gpio 17 GPIO_ACTIVE_HIGH; /* blue */power-gpios gpio 1 GPIO_ACTIVE_LOW; }; 那我们我们该如何在驱动程序中获得如下的节点信息呢使用如下方法
我们看red第二个参数“led”也就是nameled-gpios格式为name-gpio那么led就是name0表示获得第一个name为led的参数信息GPIOD_OUT_HIGH表示设置为激活状态但是并不一定是高电平它的意思是逻辑输出是一个逻辑值。如果引脚低电平点亮则为0反之则为1.
struct gpio_desc *red, *green, *blue, *power;red gpiod_get_index(dev, led, 0, GPIOD_OUT_HIGH);
green gpiod_get_index(dev, led, 1, GPIOD_OUT_HIGH);
blue gpiod_get_index(dev, led, 2, GPIOD_OUT_HIGH);
power gpiod_get(dev, power, GPIOD_OUT_HIGH);
引脚号的确定
如果我们需要使用旧的GPIO函数才操控引脚那么我们就需要知道引脚号为多少才能够操控对应的引脚那么引脚号一般是怎么计算的呢。
一般来说假如GPIO1的基引脚号为base那么GPIO0的第n号引脚号为basen 进入开发板目录/sys/class/gpio 可以看到如下GPIO分组 它表示GPIO的分组我们进入gpioip128得到如下 查看label属性 通过如上的地址“20ac000”这个地址通过查阅芯片手册或者设备树即可知道引脚号为128的是哪个GPIO组的基引脚号 如下为imx6ull.dtsi中的信息可知20ac000这个地址表示为gpio5组的基引脚号那么gpioip128就表示gpio5组了那么可知如果我们需要使用GPIO5的引脚3那么此引脚的引脚号则为 1283 131 在这里我们可以做一个测试已知按键的电路图如下 可知它使用的是GPIO4的引脚14我们可推算GPIO4组的基引脚号为96那么引脚14就是9614110引脚号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 echo 110 /sys/class/gpio/export 这个命令将编号为110的GPIO引脚导出为用户空间的设备使其可以在用户空间进行操作。 echo in /sys/class/gpio/gpio110/direction 这个命令设置编号为110的GPIO引脚的方向为输入in。 cat /sys/class/gpio/gpio110/value 这个命令读取编号为110的GPIO引脚的当前值。如果引脚连接的是一个按钮或传感器它会输出0或1表示引脚的状态是低电平或高电平。 echo 110 /sys/class/gpio/unexport 这个命令将编号为110的GPIO引脚取消导出使其不再在用户空间可用。 通过如上命令可以在用户空间读取按键值但是如果此引脚被使用则会出现如下提醒 基于GPIO子系统的驱动程序
驱动程序 如下函数为probe函数只有当设备树与驱动成功配对后才会调用此函数主要的目的是在驱动设备中获取设备树当中的引脚等信息 如上两个函数主要为驱动和设备树节点对应成功后以及卸载时所需要设置的步骤往下实现file_operation结构体中的相关提供给应用层的函数这里实现open和write举例 gpiod_set_value第二个参数传入的是逻辑值只要传入的是1那么它一定是激活引脚的电平这个在dts的属性中可以设置引脚是低电平有效还是高电平有效子系统会达到控制效果。 #include linux/module.h
#include linux/platform_device.h#include linux/fs.h
#include linux/errno.h
#include linux/miscdevice.h
#include linux/kernel.h
#include linux/major.h
#include linux/mutex.h
#include linux/proc_fs.h
#include linux/seq_file.h
#include linux/stat.h
#include linux/init.h
#include linux/device.h
#include linux/tty.h
#include linux/kmod.h
#include linux/gfp.h
#include linux/gpio/consumer.h
#include linux/of.h/* 1. 确定主设备号 */
static int major 0;
static struct class *led_class;
static struct gpio_desc *led_gpio;/* 3. 实现对应的open/read/write等函数填入file_operations结构? */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);return 0;
}/* write(fd, val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;//struct inode *inode file_inode(file);//int minor iminor(inode);printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);err copy_from_user(status, buf, 1);/* 根据次设备号和status控制LED */gpiod_set_value(led_gpio, status);return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{//int minor iminor(node);printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);/* 根据次设备号初始化LED */gpiod_direction_output(led_gpio, 0);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 定义自己的file_operations结构? */
static struct file_operations led_drv {.owner THIS_MODULE,.open led_drv_open,.read led_drv_read,.write led_drv_write,.release led_drv_close,
};/* 4. 从platform_device获得GPIO* 把file_operations结构体告诉内核注册驱动程序*/
static int chip_demo_gpio_probe(struct platform_device *pdev)
{//int err;printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);/* 4.1 设备树中定义? led-gpios...; */led_gpio gpiod_get(pdev-dev, led, 0);if (IS_ERR(led_gpio)) {dev_err(pdev-dev, Failed to get GPIO for led\n);return PTR_ERR(led_gpio);}/* 4.2 注册file_operations */major register_chrdev(0, 100ask_led, led_drv); led_class class_create(THIS_MODULE, 100ask_led_class);if (IS_ERR(led_class)) {printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, led);gpiod_put(led_gpio);return PTR_ERR(led_class);}device_create(led_class, NULL, MKDEV(major, 0), NULL, 100ask_led%d, 0); /* /dev/100ask_led0 */return 0;}static int chip_demo_gpio_remove(struct platform_device *pdev)
{device_destroy(led_class, MKDEV(major, 0));class_destroy(led_class);unregister_chrdev(major, 100ask_led);gpiod_put(led_gpio);return 0;
}static const struct of_device_id ask100_leds[] {{ .compatible 100ask,leddrv },{ },
};/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver {.probe chip_demo_gpio_probe,.remove chip_demo_gpio_remove,.driver {.name 100ask_led,.of_match_table ask100_leds,},
};/* 2. 在入口函数注册platform_driver */
static int __init led_init(void)
{int err;printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);err platform_driver_register(chip_demo_gpio_driver); return err;
}/* 3. 有入口函数就应该有出口函数卸载驱动程序时就会去调用这个出口函? * 卸载platform_driver*/
static void __exit led_exit(void)
{printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);platform_driver_unregister(chip_demo_gpio_driver);
}/* 7. 其他完善提供设备信息自动创建设备节点 */module_init(led_init);
module_exit(led_exit);MODULE_LICENSE(GPL);
设备树修改
imx6ull芯片有对应的设备树生成的工具使用 “Pins_Tool_for_i.MX_Processors_v6_x64.exe”可以生成对应的设备树文件。 把自动生成的信息放在设备树 arch/arm/boot/dts/100ask_imx6ull-14x14.dts 下 然后在此设备树文件中定义自己的新节点添加在根节点下 由于GPIO5 3 已经被用来做CPU指示灯需要再设备树中关闭它 由于都是使用pinctrl来控制我们在dts中搜索 MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 在搜索框中的节点名既可以反推找到使用该节点的节点 设置完后在内核目录下使用 make dtbs 编译设备树文件拷贝到网络文件系统中更新设备树安装编译后的驱动程序