- 为什么要引入pinctrl和gpio子系统?
我认为有这么一个根本出发点——让你的程序更有生命力,不要为了目标板的一点小小的变更而忙得焦头烂额。所以,
1)当你的按键驱动程序要去适配不同的SOC时,要做到不改变驱动程序,就要把驱动程序分为上层的按键驱动程序“空壳”(即没有实际对硬件操作的框架)和下层的具体SOC的驱动程序(实现实际所需硬件驱动,并注册给上层以便于其调用)的上下两层,这样一来,当目标SOC变更时换掉下层的SOC驱动程序即可,这就是分层;但,更进一步地,
2)当目标SOC一致而外围电路发生变化时,如果按照上面第1)条更改下层的驱动程序,你仍然会为写一些重复的代码而觉得焦头烂额。这就引入了分离的思想——即将下层的驱动程序分为目标板资源程序文件和对应的SOC驱动程序文件,这样,只要目标板的SOC不变更,只需要修改目标板资源文件即可,这就是分离;但,更进一步地,
3)既然要将资源文件与驱动文件分离,就会存在驱动对于资源文件的依懒或资源文件对驱动文件的依懒(要看工程师的编程习惯),依懒总会带来麻烦——不要依懒我,我也不要依懒你,我们都依懒“接口”吧。于是就有了上层的platform平台设备总线接口,当需要修改驱动程序或设备(资源)文件时修改就好了,platform平台设备总线bus会自动匹配对两者,即当注册platform_device时会自动搜索平台总线下匹配的platform_driver,当注册platform_driver时自动搜索平台总线下匹配的platform_device,然后将资源用于驱动的对象,这就是platform平台设备驱动总线接口;但更进一步地,
4)因为linux使用了宏内核,随着arm处理器的迅猛发展,使用arm处理器的产品千变万化层出不穷,而这又造成了大量的资源文件(被Linus称为“垃圾”的东西)使得内核变得雍肿不堪。为了不将这些大量的重复的资源文件放入内核,于是就有了基于open firmware的设备树,资源文件以设备树文件形式存在而不再放入内核,而在内核启动时就将其转换为platform_device文件,内核雍肿的问题得以解决;但更进一步地,
5)设备树的属性定义自由度非常大——为了匹配platform_driver驱动程序可以设置各种属性,驱动开发人员认识水平和经验各不相同,编写出来的驱动程序也多种多样,这些变化往往影响驱动程序的复用。于是,pinctrl子系统、gpio子系统、input子系统等设备树的标准化属性便应运而生。简单而言,pinctrl子系统用于选择某芯片引脚用于什么作用,比如将imx6ull的GPIO1.IO18用作IO功能,则可以这样定义设备树:
pinctrl_key:keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
>;
};
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "glen-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>; /* 属性设置按键所使用的PIN对应的pinctrl节点 */
...
};
在内核启动后,imx6ull的GPIO1.IO18就用作IO功能了,这个过程不需要普通驱动开发人员来处理,而需要芯片厂商的驱动开发人员设计对应的pinctrl底层驱动程序,在此按住不表。说回gpio子系统,其用于设置GPIO引脚方向(输入还是输出)、读值──获得电平状态,写值──输出高低电平。有了gpio子系统,普通驱动开发人员只需要调用其接口即可,同样地,具体到如何操作寄存器、读写什么值这些工作由芯片厂商的驱动开发人员设计——因为芯片原厂对这些最熟悉。这里面内核上层提供了pinctrl、gpio接口函数,也就是一堆“空壳”(框架),具体SOC的pinctrl、gpio函数的实现要芯片厂商的驱动开发工程师设计好后注册给这些“空壳”(框架)调用。这就是pinctrl、gpio子系统。但更进一步地,
5)基于RISC-V指令集的SOC发展迅猛,可以猜测在未来不远的时间会得到广泛地应用,那么发展一种跨SOC的程序框架成为非常大的可能,拭目以待吧。
扯远了,本文基于pinctrl和gpio子系统的按键驱动仍保留了button_drv.c、imx6_io_drv.c、button_drv.h文件。
2. 按键设备驱动(框架)文件 与上文无差异
button_drv.c文件实现与硬件有关的按键驱动,主要实现struct file_operations类型相关函数及其实例的注册/解除注册。其中的.read、.open、.release成员要调用下层驱动:
/**
* 文件 : button_drv.c
* 作者 : glen
* 描述 : button 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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "button_drv.h"
struct button_drv btn_drv = {
.hw_ops = NULL,
.name = "gbtn",
.major = 0,
};
ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
int ret;
int minor = iminor(filp->f_inode);
struct btn_hw_ops *ops = (struct btn_hw_ops *)filp->private_data;
//struct btn_hw_ops *ops = btn_drv.hw_ops;
char kval;
size = (size >= 1) ? 1 : 0;
if (ops == NULL) {
printk("Please register button hardware operations instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->num) {
printk("Reading button value error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
if (ops->read(minor, &kval) == 0) {
ret = copy_to_user(buf, &kval, size);
printk("Read button value successfully:");
return size;
}
return 0;
}
int button_drv_open (struct inode *nd, struct file *filp)
{
int minor = iminor(nd);
struct btn_hw_ops *ops = btn_drv.hw_ops;
filp->private_data = btn_drv.hw_ops;
if (ops == NULL) {
printk("Please register button hardware operations instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->num) {
printk("Openning button driver error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
if (ops->open(minor) == 0)
printk("Open button driver successfully:");
return 0;
}
int button_drv_release (struct inode *nd, struct file *filp)
{
int minor = iminor(nd);
struct btn_hw_ops *ops = (struct btn_hw_ops *)filp->private_data;
if (ops == NULL) {
printk("Please register button hardware operations instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->num) {
printk("Closing button driver error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
if (ops->close(minor) == 0) {
printk("Close button driver successfully:");
filp->private_data = NULL;
}
return 0;
}
unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
{
return 0;
}
static struct file_operations button_drv_ops = {
.owner = THIS_MODULE,
.read = button_drv_read,
.open = button_drv_open,
.release = button_drv_release,
.poll = button_drv_poll,
};
static int __init button_drv_init(void)
{
btn_drv.major = register_chrdev(btn_drv.major, btn_drv.name, &button_drv_ops);
btn_drv.class = class_create(THIS_MODULE, btn_drv.name);
if (IS_ERR(btn_drv.class)) {
printk("Class create error!\n");
unregister_chrdev(btn_drv.major, btn_drv.name);
}
printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
module_init(button_drv_init);
static void __exit button_drv_exit(void)
{
/* device_destroy */
class_destroy(btn_drv.class);
unregister_chrdev(btn_drv.major, btn_drv.name);
printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_exit(button_drv_exit);
int button_hw_ops_register(struct btn_hw_ops *ops)
{
if (btn_drv.hw_ops)
return -EIO;
btn_drv.hw_ops = ops;
printk("Register button hardware operations instance successfully\n");
return 0;
}
EXPORT_SYMBOL(button_hw_ops_register);
void button_hw_ops_unregister(struct btn_hw_ops *ops)
{
if (btn_drv.hw_ops)
btn_drv.hw_ops = NULL;
printk("Unregister button hardware operations instance successfully\n");
}
EXPORT_SYMBOL(button_hw_ops_unregister);
void button_device_create(int idx)
{
device_create(btn_drv.class, NULL, MKDEV(btn_drv.major, idx), NULL, "gbtn%d", idx);
}
EXPORT_SYMBOL(button_device_create);
void button_device_destroy(int idx)
{
device_destroy(btn_drv.class, MKDEV(btn_drv.major, idx));
}
EXPORT_SYMBOL(button_device_destroy);
/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
其中包含的button_drv.h文件提供了与下层soc驱动共用的结构体,注册/销毁soc驱动等接口函数,以便在button_drv.c文件中调用——驱动上层和下层都依懒于接口。
/**
* 文件 : button_drv.h
* 作者 : glen
* 描述 : button driver头文件
*/
#ifndef __BUTTON_DRV_H__
#define __BUTTON_DRV_H__
struct btn_hw_ops {
u32 num;
int (* read) (int idx, char *kval);
int (* open) (int idx);
int (* close) (int idx);
};
struct button_drv {
struct btn_hw_ops *hw_ops;
struct class *class;
char *name;
int major;
};
int button_hw_ops_register(struct btn_hw_ops *ops);
void button_hw_ops_unregister(struct btn_hw_ops *ops);
void button_device_create(int idx);
void button_device_destroy(int idx);
#endif // !__BUTTON_DRV_H__
button_hw_ops_register/button_hw_ops_unregister函数用于注册、解注册实际SOC驱动,button_device_create/button_device_destroy函数用于创建、销毁次设备号。由于这些用于SOC的驱动工作已由芯片厂商驱动工程师完成,这里看起来有些多余,不过我还是想过渡一下。
3. SOC相关的IO驱动文件
imx6_io_drv.c获取IO资源(这里获取的是按键的IO信息),并将资源用于实现按键的open、read、close等操作:
/**
* 文件 : imx6_io_drv.c
* 作者 : glen
* 描述 : imx6ull io 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/platform_device.h>
#include <linux/of.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "button_drv.h"
// #include "imx6_io_drv.h"
// static struct btn_dev_res __btn_dev_res[16];
static struct gpio_descs *p_gbtn = NULL;
static int imx6_io_read (int idx, char *kval);
static int imx6_io_open (int idx);
static int imx6_io_close (int idx);
static struct btn_hw_ops btn_hw_oper = {
.open = imx6_io_open,
.read = imx6_io_read,
.close = imx6_io_close,
};
static int imx6_io_read (int idx, char *kval)
{
if (idx >= btn_hw_oper.num)
return -EINVAL;
*kval = gpiod_get_value(p_gbtn->desc[idx]);
return 0;
}
static int imx6_io_open (int idx)
{
int ret;
if (idx >= btn_hw_oper.num)
return -EINVAL;
ret = gpiod_direction_input(p_gbtn->desc[idx]);
if (ret)
printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
else
printk("Set the button%d pin as input successfully!\n", idx);
return ret;
}
static int imx6_io_close (int idx)
{
if (idx >= btn_hw_oper.num)
return -EINVAL;
return 0;
}
int btn_hw_drv_probe (struct platform_device *pdev)
{
int i;
p_gbtn = gpiod_get_array_optional(&pdev->dev, "gbtn", GPIOD_ASIS);
if (p_gbtn == NULL) {
printk("Get \"gbtn-gpios\" property failed! \n");
return -EIO;
}
printk("Get %d gpio members from the \"gbtn gpios\" property!\n", p_gbtn->ndescs);
btn_hw_oper.num = p_gbtn->ndescs;
for (i = 0; i < p_gbtn->ndescs; i++)
button_device_create(i);
return 0;
}
int btn_hw_drv_remove(struct platform_device *pdev)
{
button_hw_ops_unregister(NULL);
gpiod_put_array(p_gbtn);
button_device_destroy(btn_hw_oper.num);
btn_hw_oper.num = 0;
return 0;
}
static const struct of_device_id gbtns_id[] = {
{.compatible = "glen,gbtn"},
{ },
};
static struct platform_driver btn_hw_drv = {
.driver = {
.name = "gbtn",
.of_match_table = gbtns_id,
},
.probe = btn_hw_drv_probe,
.remove = btn_hw_drv_remove,
};
static int __init imx6_io_drv_init(void)
{
int ret;
ret = platform_driver_register(&btn_hw_drv);
if (ret)
pr_err("Unable to initialize imx6 io driver\n");
else
pr_info("The imx6 io driver is registered.\n");
button_hw_ops_register(&btn_hw_oper);
return ret;
}
module_init(imx6_io_drv_init);
static void __exit imx6_io_drv_exit(void)
{
platform_driver_unregister(&btn_hw_drv);
pr_info("The imx6 io driver is unregistered.\n");
}
module_exit(imx6_io_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("glen");
可以看出,用于实现按键的open、read、close等操作已经非常简单。
4. 更改设备树文件按键相关资源
pinctrl_key:keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xF080 /* KEY1 此按键不存在 */
>;
};
/* 添加基于pinctrl的gbtns设备节点 */
gbtns {
compatible = "glen,gbtn";
#address-cells = <1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
gpio-controller;
#gpio-cells = <2>;
gbtn-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>, /* button0 */
<&gpio1 3 GPIO_ACTIVE_LOW>; /* button1 */
};
与上一篇文章相比,这里用了pinctrl和gpio,使得设备树变得更简单。
5. 应用程序(不作修改)
/*
* 文件名 : button_drv_test.c
* 作者 : glen
* 描述 : button_drv应用程序
*/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/**
* @brief : main函数
* @par : argc argv数组元素的个数
* argv 参数数组
* @retval : 0 成功 其它 失败
*/
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
char kval;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &kval, 1);
if (ret <= 0) {
printf("Read glen button value failed!\r\n");
close(fd);
return -1;
} else {
printf("The glen button value is: %d!\r\n", kval);
}
sleep(1);
}
/* 关闭文件 */
ret = close(fd);
if (ret < 0) {
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
- 在alientek_linux_alpha开发板实测验证如下
/ # cd drv_module/
/drv_module # ls
btn_drv_test button_drv.ko imx6_io_drv.ko
/drv_module # insmod button_drv.ko
/home/glen/linux/imx6ull/glen/3_3_button_pinctrl/button_drv.c button_drv_init line 120
/drv_module # insmod imx6_io_drv.ko
Get 2 gpio members from the "gbtn gpios" property!
The imx6 io driver is registered.
Register button hardware operations instance successfully
/drv_module # ./random: nonblocking pool is initialized
/drv_module # ./btn_drv_test /dev/gbtn0
Set the button0 pin as input successfully!
Open button driver successfully:Read button value successfully:The glen button value is: 0!
Read button value successfully:The glen button value is: 0!
Read button value successfully:The glen button value is: 0!
Read button value successfully:The glen button value is: 0!
Read button value successfully:The glen button value is: 0!
Read button value successfully:The glen button value is: 0!
Read button value successfully:The glen button value is: 1!
Read button value successfully:The glen button value is: 1!
Read button value successfully:The glen button value is: 1!
Read button value successfully:The glen button value is: 1!
Read button value successfully:The glen button value is: 1!
Read button value successfully:The glen button value is: 0!
Read button value successfully:The glen button value is: 0!
^CClose button driver successfully:
/drv_module # rmmod imx6_io_drv.ko
Register button hardware operations instance successfully
The imx6 io driver is unregistered.
/drv_module # rmmod button_drv.ko
/home/glen/linux/imx6ull/glen/3_3_button_pinctrl/button_drv.c button_drv_exit line 130
- 下面将imx6_io_drv.c当中的实现并入到button_drv.c文件,与上面验证的效果相同
/**
* 文件 : button_drv.c
* 作者 : glen
* 描述 : button 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/of.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "button_drv.h"
struct button_drv btn_drv = {
.p_gbtn = NULL,
.name = "gbtn",
.major = 0,
};
ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
int minor = iminor(filp->f_inode);
struct gpio_descs *ops = (struct gpio_descs *)filp->private_data;
char kval;
size = (size >= 1) ? 1 : 0;
if (ops == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->ndescs) {
printk("Reading button value error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
kval = gpiod_get_value(ops->desc[minor]);
if (copy_to_user(buf, &kval, size))
return -EFAULT;
printk("Read button%d value successfully:", minor);
return size;
}
int button_drv_open (struct inode *nd, struct file *filp)
{
int ret;
int minor = iminor(nd);
struct gpio_descs *ops = btn_drv.p_gbtn;
if (ops == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->ndescs) {
printk("Openning button driver error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
ret = gpiod_direction_input(ops->desc[minor]);
if (ret)
printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
else
printk("Set the button%d pin as input successfully!\n", minor);
filp->private_data = btn_drv.p_gbtn;
return 0;
}
int button_drv_release (struct inode *nd, struct file *filp)
{
int minor = iminor(nd);
struct gpio_descs *ops = (struct gpio_descs *)filp->private_data;
if (ops == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (minor >= ops->ndescs) {
printk("Closing button driver error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EINVAL;
}
filp->private_data = NULL;
return 0;
}
unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
{
return 0;
}
static struct file_operations button_drv_ops = {
.owner = THIS_MODULE,
.read = button_drv_read,
.open = button_drv_open,
.release = button_drv_release,
.poll = button_drv_poll,
};
int btn_hw_drv_probe (struct platform_device *pdev)
{
int i;
btn_drv.p_gbtn = gpiod_get_array_optional(&pdev->dev, "gbtn", GPIOD_ASIS);
if (btn_drv.p_gbtn == NULL) {
printk("Get \"gbtn-gpios\" property failed! \n");
return -EIO;
}
printk("Get %d gpio members from the \"gbtn gpios\" property!\n", btn_drv.p_gbtn->ndescs);
btn_drv.major = register_chrdev(btn_drv.major, btn_drv.name, &button_drv_ops);
btn_drv.class = class_create(THIS_MODULE, btn_drv.name);
if (IS_ERR(btn_drv.class)) {
printk("Class create error!\n");
unregister_chrdev(btn_drv.major, btn_drv.name);
}
printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
for (i = 0; i < btn_drv.p_gbtn->ndescs; i++)
device_create(btn_drv.class, NULL, MKDEV(btn_drv.major, i), NULL, "gbtn%d", i);
return 0;
}
int btn_hw_drv_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < btn_drv.p_gbtn->ndescs; i++)
device_destroy(btn_drv.class, MKDEV(btn_drv.major, i));
gpiod_put_array(btn_drv.p_gbtn);
return 0;
}
static const struct of_device_id gbtns_id[] = {
{.compatible = "glen,gbtn"},
{ },
};
static struct platform_driver btn_hw_drv = {
.driver = {
.name = "gbtn",
.of_match_table = gbtns_id,
},
.probe = btn_hw_drv_probe,
.remove = btn_hw_drv_remove,
};
static int __init button_drv_init(void)
{
int ret;
ret = platform_driver_register(&btn_hw_drv);
if (ret)
pr_err("Unable to initialize button driver\n");
else
pr_info("The button driver is registered.\n");
return 0;
}
module_init(button_drv_init);
static void __exit button_drv_exit(void)
{
platform_driver_unregister(&btn_hw_drv);
class_destroy(btn_drv.class);
unregister_chrdev(btn_drv.major, btn_drv.name);
printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_exit(button_drv_exit);
/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");