中级会员
- 积分
- 325
- 金钱
- 325
- 注册时间
- 2022-4-12
- 在线时间
- 41 小时
|
因为我是做工业控制类产品,所以我的GPIO应用场景主要是作开关量控制,如开有源开关量输出,干接点输出,开关量输入等,同时也会有一些简单的LED控制。
但是在实际的应用中,根据硬件工程师的电路设计,有时候GPIO输出高电平表示“有效”,有时候输出低电平表示“有效”。
而教程里的大部分驱动的写法好像都是单一的应用——“一个驱动对应一个设备”,并没有体现“Linux下的驱动分离与分层”。
像我这样的GPIO控制,N种应用就要写N个对应的驱动?
但是实际上对于MPU而言所有的GPIO控制又都是一样的,无非就是引脚出输出高低电平,或者读取电平。
所以我想跟大家分享下我摸索的GPIO驱动,有不足的地方欢迎指出,(我只测试了写输出,还没有测试读输入)
PS:代码里所有的“fa”只是我的公司名称缩写,并没有特殊含义
1、首先是设备树部分
大体上跟教程差不多
在根节点下添加自己的设备节点,
增加了一个fa-polarity = <0>;用于指明该引脚的有效极性,我并没有用GPIO_ACTIVE_LOW这个值(主要是懒,没有深度摸索Linux设备树方面的代码,所以直接新建了一个自己的属性)
增加了一个fa-name = "fa_gpio_active_output1";用于给这个GPIO设备取个名字,(实际上没什么用,完全可以用节点的名字代替。。。)
fa_gpio_output1 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "fa,iWaterX-fa_gpio";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_fa_gpio_output1>;
fa-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
fa-polarity = <0>;
fa-name = "fa_gpio_active_output1";
status = "okay";
};
fa_gpio_output2 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "fa,iWaterX-fa_gpio";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_fa_gpio_output2>;
fa-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>;
fa-polarity = <1>;
fa-name = "fa_gpio_passive_output1";
status = "okay";
};
引脚复用部分根据教程和自己实际情况修改即可
pinctrl_fa_gpio_output1:gpio_output1_grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0
>;
};
pinctrl_fa_gpio_output2:gpio_output2_grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x10b0
>;
};
2、驱动.c文件
大体上也是跟教程差不多,主要是增加了设备信息结构体数组,和区别到底是哪个设备被使用的查找函数,先直接贴代码了,重点部分我加粗显示
#include "include.h"
/*
linux gpio demo
各个有返回值的函数一定要写返回值,否则可能出现段错误
头文件根据教程自己添加就好了
*/
#define DEVICE_NAME "fa_gpio"
#define DEVICE_CNT 1
#define DEVICE_MAX 5 //我是静态分配的设备信息结构体数组,所以宏定义了允许的最大设备数量为5
/* */
/* 设备结构体 */
typedef struct
{
dev_t dev_id; /* 设备号 */
struct cdev dev; /* cdev */
struct class *pclass; /* 类 */
struct device *pdevice; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *node; /* 设备节点 */
int gpio_cnt; /* gpio引脚数量 */
int gpio[10]; /* GPIO编号 */
int polarity; /* GPIO极性 */
const char *pname; /* 设备名称 */
}fa_gpio_t;
static fa_gpio_t fa_gpio[DEVICE_MAX]; //设备信息结构体数组,用于保存各个设备的信息
static int fa_gpio_index = 0; //当前设备总数
/*
搜索符合要求的设备信息结构体,并返回序号
pname是目标设备名称
*/
static int driver_fa_gpio_find(const char *pname)
{
int i;
int ret = -1;
for(i=0;i<fa_gpio_index;i++)
{
if(strcmp(pname,fa_gpio.pname) == 0)
{
ret = i;
break;
}
}
return ret;
}
/* 字符设备框架 */
static int driver_fa_gpio_open(struct inode *pinode, struct file *pfile)
{
int index;
int ret = 0;
index = driver_fa_gpio_find(pfile->f_path.dentry->d_iname); //pfile->f_path.dentry->d_iname是被打开的设备文件的名称,它与你设备是同名的
if(index != -1)
{
ret = 0;
pfile->private_data = &fa_gpio[index];
printk("%s open !! \r\n",pfile->f_path.dentry->d_iname);
}
else
{
ret = -1;
printk("%s open failed !! \r\n",pfile->f_path.dentry->d_iname);
}
return ret;
}
static int driver_fa_gpio_release(struct inode *pinode, struct file *pfile)
{
printk("%s release !! \r\n",pfile->f_path.dentry->d_iname);
return 0;
}
ssize_t driver_fa_gpio_read(struct file *pfile, char __user *p__user, size_t size, loff_t *ploff)
{
char databuf[size];
uint8_t value = 0;
int i;
int ret;
fa_gpio_t *pdev = (fa_gpio_t *)pfile->private_data;
for(i=0;i<pdev->gpio_cnt;i++)
{
value |= (((gpio_direction_input(pdev->gpio) == pdev->polarity) ? 1 : 0) << i);
}
databuf[0] = value;
ret = copy_to_user(p__user,databuf,size);
printk("%s read !! \r\n",pdev->pname);
return 0;
}
ssize_t driver_fa_gpio_write(struct file *pfile, const char __user *p__user, size_t size, loff_t *ploff)
{
char databuf[size];
uint8_t value;
int i;
int ret;
fa_gpio_t *pdev = (fa_gpio_t *)pfile->private_data;
ret = copy_from_user(databuf,p__user,size);
value = databuf[0];
for(i=0;i<pdev->gpio_cnt;i++)
{
if(value & 0x01)
{
gpio_direction_output(pdev->gpio,pdev->polarity);
}
else
{
gpio_direction_output(pdev->gpio,!pdev->polarity);
}
value >>= 1;
}
printk("%s write !! \r\n",pdev->pname);
return 0;
}
static const struct file_operations driver_fa_gpio_fops =
{
.owner = THIS_MODULE,
.open = driver_fa_gpio_open,
.release = driver_fa_gpio_release,
.read = driver_fa_gpio_read,
.write = driver_fa_gpio_write,
};
/* platform 框架 */
static int driver_fa_gpio_probe(struct platform_device *pdev)
{
int i;
if(fa_gpio_index == DEVICE_MAX)
{
return -1;
}
//get device_node
fa_gpio[fa_gpio_index].node = pdev->dev.of_node; //教程里一般都是用of_find_node_by_path("xxxxx/xxxxx/xxxxx");这样的话驱动只能指定某一个节点表示的设备了,我这样的写法是参考内核的自带的驱动的写法
//get gpio_cnt读取fa-gpio属性中的引脚个数
fa_gpio[fa_gpio_index].gpio_cnt = of_gpio_named_count(fa_gpio[fa_gpio_index].node,"fa-gpio");
printk("fa_gpio gpio cnt %d !!\r\n",fa_gpio[fa_gpio_index].gpio_cnt);
//get gpio polarity读取引脚极性
of_property_read_u32_index(fa_gpio[fa_gpio_index].node,"fa-polarity",0,&fa_gpio[fa_gpio_index].polarity);
printk("fa_gpio gpio polarity %d !!\r\n",fa_gpio[fa_gpio_index].polarity);
//get device name
if(of_property_read_string_index(fa_gpio[fa_gpio_index].node,"fa-name",0,&fa_gpio[fa_gpio_index].pname) != 0)
{
fa_gpio[fa_gpio_index].pname = fa_gpio[fa_gpio_index].node->name;
}
printk("fa_gpio name %s !!\r\n",fa_gpio[fa_gpio_index].pname);
//读取引脚编号,并初始化引脚为非极性电平输出
for(i=0;i<fa_gpio[fa_gpio_index].gpio_cnt;i++)
{
fa_gpio[fa_gpio_index].gpio = of_get_named_gpio(fa_gpio[fa_gpio_index].node,"fa-gpio",i);
gpio_direction_output(fa_gpio[fa_gpio_index].gpio,!fa_gpio[fa_gpio_index].polarity);
printk("fa_gpio gpio %d = %d !!\r\n",i,fa_gpio[fa_gpio_index].gpio);
}
//alloc chrdev dev_id
alloc_chrdev_region(&fa_gpio[fa_gpio_index].dev_id,0,DEVICE_CNT,fa_gpio[fa_gpio_index].pname);
fa_gpio[fa_gpio_index].major = MAJOR(fa_gpio[fa_gpio_index].dev_id);
fa_gpio[fa_gpio_index].minor = MINOR(fa_gpio[fa_gpio_index].dev_id);
//printk chrdev dev_id
printk("fa_gpio dev_id = %d major = %d minor = %d \r\n",fa_gpio[fa_gpio_index].dev_id,fa_gpio[fa_gpio_index].major,fa_gpio[fa_gpio_index].minor);
//init cdev
cdev_init(&fa_gpio[fa_gpio_index].dev,&driver_fa_gpio_fops);
//add cdev
cdev_add(&fa_gpio[fa_gpio_index].dev,fa_gpio[fa_gpio_index].dev_id,DEVICE_CNT);
//create class
fa_gpio[fa_gpio_index].pclass = class_create(THIS_MODULE,fa_gpio[fa_gpio_index].pname);
//create device
fa_gpio[fa_gpio_index].pdevice = device_create(fa_gpio[fa_gpio_index].pclass,NULL,fa_gpio[fa_gpio_index].dev_id,NULL,fa_gpio[fa_gpio_index].pname);
printk("%s probe !!!\r\n\r\n",fa_gpio[fa_gpio_index].pname);
fa_gpio_index++;
return 0;
}
static int driver_fa_gpio_remove(struct platform_device *pdev)
{
int i,j;
for(j=0;j<fa_gpio_index;j++)
{
for(i=0;i<fa_gpio[j].gpio_cnt;i++)
{
gpio_direction_output(fa_gpio[j].gpio,!fa_gpio[j].polarity);
}
//destroy device
device_destroy(fa_gpio[j].pclass,fa_gpio[j].dev_id);
//destroy class
class_destroy(fa_gpio[j].pclass);
//del cdev
cdev_del(&fa_gpio[j].dev);
//del dev_id
unregister_chrdev_region(fa_gpio[j].dev_id,DEVICE_CNT);
printk("%s remove !!!\r\n\r\n",fa_gpio[j].pname);
}
fa_gpio_index = 0;
return 0;
}
static const struct of_device_id driver_fa_gpio_of_mach[] = {
{.compatible = "fa,iWaterX-fa_gpio"},
{}
};
static const struct platform_device_id driver_fa_gpio_id_mach[] = {
{"fa,iWaterX-fa_gpio",0},
{}
};
static struct platform_driver platform_driver_fa_gpio = {
.driver = {
.owner = THIS_MODULE,
.name = DEVICE_NAME,
.of_match_table = driver_fa_gpio_of_mach,
},
.probe = driver_fa_gpio_probe,
.remove = driver_fa_gpio_remove,
.id_table = driver_fa_gpio_id_mach,
};
int __init driver_fa_gpio_init(void)
{
printk("%s init ok !!\r\n\r\n",DEVICE_NAME);
return platform_driver_register(&platform_driver_fa_gpio);
}
void __exit driver_fa_gpio_exit(void)
{
printk("%s exit ok !!\r\n\r\n",DEVICE_NAME);
return platform_driver_unregister(&platform_driver_fa_gpio);
}
module_init(driver_fa_gpio_init);
module_exit(driver_fa_gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JHW");
3、应用
应用软件参考教程即可
根据上述的驱动代码,编译设备树,并加载驱动后,
ls /dev就可以看到fa_gpio_active_output1和fa_gpio_passive_output1这2个设备了,设备名是我的“fa-name”属性决定的,所以不是现实的节点的名称
运行测试app,参数使用/dev/fa_gpio_active_output1或者/dev/fa_gpio_passive_output1就可以分别驱动2种GPIO设备了
如果就改变教程的GPIO驱动代码,当有2个设备使用同一个驱动时,驱动中的设备信息结构体只会保存最后一个匹配的设备的信息,只有最后一个设备才能被驱动
|
|