Linux 驱动开发之使用文件操作集函数控制GPIO口电平

在没有开发板的情况下,我们只能进行一些虚拟资源的驱动验证。为了更进一步进行驱动开发,必然要和硬件结合,在开发板上操作硬件资源。前面我们已经实现了在Ubuntu单机上进行简单字符设备框架的验证,具体可以参考:《没有开发板在Ubuntu下体验Linux驱动开发之字符设备框架实验》

此次实验将结合Linux驱动中的平台总线模型和设备树操作开发板上的GPIO,实验平台为荣品的RD-RK3588开发板+Debian11系统,且已通过了源码编译。

第一步,设备树修改

在/kernel/arch/arm64/boot/dts/rockchip 目录下找到名为 xxx.dts 的主设备树文件,在根节点下添加我们的测试节点,并在文件最后添加对pinctrl节点的引用,这里使用的是编号为155的gpio口。

gpio_test:gpio_test{

        compatible = "gpio_test";

        status = "okay";

        pinctrl-names = "default";

        pinctrl-0 = <&pin_test>;

        gpio_pin {

            gpio_num = <&gpio4 RK_PD3 GPIO_ACTIVE_LOW>;

            gpio_function = <0>;

        };

 };

&pinctrl {

        pin{

            pin_test: pin_test{

                rockchip,pins =

                  <4 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>;

            };

};

第二步,驱动程序编写

驱动程序采用平台总线框架,具体内容可以参考网上资料,这里给出关键部分代码:

1.文件操作集函数

static int gpio_open(struct inode *inode, struct file *filp)

{

    filp->private_data = &gpiotest; /* 设置私有数据 */

    return 0;

}

static ssize_t gpio_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)

{

    int retvalue;

    int databuf[1];

    int gpiostat;

    struct gpiotest_dev *dev = filp->private_data;

    gpiostat = gpio_get_value(dev->test_gpio); //该函数返回值为int型,所以采用int进行传递

    databuf[0] = gpiostat;

    retvalue = copy_to_user(buf,databuf,cnt);

    if(retvalue < 0) {

        printk("kernel read failed!\r\n");

        return -EFAULT;

    }

    return 0;

}

static ssize_t gpio_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)

{

    int retvalue;

    unsigned char databuf[1];

    unsigned char gpiostat;

    struct gpiotest_dev *dev = filp->private_data;

    retvalue = copy_from_user(databuf, buf, cnt);

    if(retvalue < 0) {

        printk("kernel write failed!\r\n");

        return -EFAULT;

    }

    gpiostat = databuf[0]; /* 获取写入值 */

    if(gpiostat == LOW) {

        gpio_set_value(dev->test_gpio, 0);

    } else if(gpiostat == HIGH) {

        gpio_set_value(dev->test_gpio, 1);

    }

    return 0;

}

static int gpio_release(struct inode *inode, struct file *filp)

{

    return 0;

}

2.probe函数

static int my_platform_probe(struct platform_device *pdev)

{

    int ret = 0;

    printk(KERN_INFO "my_platform_probe: Probing platform device\n");

    gpiotest.nd = of_find_node_by_path("/gpio_test/gpio_pin"); //查找对应节点

    if(gpiotest.nd == NULL) {

        printk("gpiotest node cant not found!\r\n");

        return -EINVAL;

    } else {

        printk("gpiotest node has been found!\r\n");

    }

    gpiotest.test_gpio = of_get_named_gpio(gpiotest.nd, "gpio_num", 0); //获取gpio号

    if(gpiotest.test_gpio < 0) {

        printk("can't get gpio");

        return -EINVAL;

    }

    printk("gpio num = %d\r\n", gpiotest.test_gpio);

    ret = gpio_direction_output(gpiotest.test_gpio, 1); //设置gpio输出1

    if(ret < 0) {

        printk("can't set gpio!\r\n");

    }

    /* 注册字符设备驱动 */

    if (gpiotest.major) {

        gpiotest.devid = MKDEV(gpiotest.major, 0);

        register_chrdev_region(gpiotest.devid, GPIO_CNT,GPIO_NAME);

    }else {

        alloc_chrdev_region(&gpiotest.devid, 0, GPIO_CNT, GPIO_NAME); /* 申请设备号 */

        gpiotest.major = MAJOR(gpiotest.devid); /* 获取分配号的主设备号 */

        gpiotest.minor = MINOR(gpiotest.devid); /* 获取分配号的次设备号 */

    }

    printk("gpio major=%d,minor=%d\r\n",gpiotest.major,gpiotest.minor);

    gpiotest.cdev.owner = THIS_MODULE;

    cdev_init(&gpiotest.cdev, &gpiotest_fops);

    cdev_add(&gpiotest.cdev, gpiotest.devid, GPIO_CNT);

    gpiotest.class = class_create(THIS_MODULE, GPIO_NAME);

    if (IS_ERR(gpiotest.class)) {

        return PTR_ERR(gpiotest.class);

    }

 gpiotest.device = device_create(gpiotest.class, NULL,gpiotest.devid, NULL, GPIO_NAME);

    if (IS_ERR(gpiotest.device)) {

        return PTR_ERR(gpiotest.device);

    }

    return 0;

}

第三步,测试程序编写

int main(int argc, char *argv[])

{

    int fd, retvalue;

    ssize_t ret;

    char *filename;

    unsigned char databuf[1];

    int readbuf[1];

    if(argc != 3){

        printf("Error Usage!\r\n");

        return -1;

    }

    filename = argv[1];

    /* 打开驱动 */

    fd = open(filename, O_RDWR);

    if(fd < 0){

        printf("file %s open failed!\r\n", argv[1]);

        return -1;

    }

    databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */

    /* 向文件写入数据 */

    retvalue = write(fd, databuf, sizeof(databuf));

    if(retvalue < 0){

        printf("GPIO Control Failed!\r\n");

        close(fd);

        return -1;

    }

    printf("write successed !!!\r\n");

    // 读取gpio值为int型,采用int进行传递

    ret = read(fd, readbuf, sizeof(readbuf));

    //printf("read ret is %ld\r\n",ret);

    printf("read successed !!!\r\n");

    printf("gpio155 value is %d\r\n",readbuf[0]);

    retvalue = close(fd); /* 关闭文件 */

    if(retvalue < 0){

        printf("file %s close failed!\r\n", argv[1]);

    return -1;

    }

    return 0;

}

第四步,运行测试

将测试驱动放在/kernel/drivers/char/test 目录下,并添加到上级Kconfig和Makefile中,使用图形化配置界面对内核进行配置,将test编译为模块,结果如下。

这里由于虚拟机上没有装交叉编译工具,所以将测试程序及驱动模块一起拷到开发板上,并在开发板上使用gcc编译测试程序,结果如下。

加载模块并手动查看gpio口的电平值

运行测试app,分别向gpio写入0和1,并验证结果

卸载模块

总结:可以看出当驱动与资源匹配后,成功进入probe函数完并成了相应的文件操作,并且成功实现了用户空间与内核空间的数据交互,直观地体现出了Linux下一切皆文件的思想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值