OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 3680|回复: 1

关于imx6ul的GPIO驱动分离与分层的分享

[复制链接]

15

主题

38

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
325
金钱
325
注册时间
2022-4-12
在线时间
41 小时
发表于 2022-5-9 20:19:48 | 显示全部楼层 |阅读模式
因为我是做工业控制类产品,所以我的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个设备使用同一个驱动时,驱动中的设备信息结构体只会保存最后一个匹配的设备的信息,只有最后一个设备才能被驱动




正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

17

主题

43

帖子

0

精华

初级会员

Rank: 2

积分
116
金钱
116
注册时间
2020-10-19
在线时间
75 小时
发表于 2022-5-11 14:18:22 | 显示全部楼层
专业啊,有没有搞过串口扩展  比如ch438 16554
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-25 11:03

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表