荔枝派zero驱动开发05:GPIO操作(使用GPIO子系统)

参考:https://zhuanlan.zhihu.com/p/112708449

上一篇:荔枝派zero驱动开发04:GPIO操作(寄存器方式)
下一篇:更新中…

设备树修改

由于默认设备树默认设备树配置了LED,需要确保在设备树中禁用默认的LED配置,参考上篇操作(注释/leds节点或将/leds节点状态设为disabled)

在根节点下创建gpioled节点

	gpioled {
		compatible = "user,led";
		status = "okay";
		gpios = <&pio 6 0 GPIO_ACTIVE_LOW>; /* PG0 green */
		// gpios = <&pio 6 0 GPIO_ACTIVE_LOW>,<&pio 6 1 GPIO_ACTIVE_LOW>; 
	};

其中compatible与status为常见属性,compatible可以自定义,与驱动匹配即可(注意不要与其他节点的兼容属性一样,防止出现冲突)

gpios属性即描述引脚的属性,厂家的写法会有区别,一般来说参考模板的写法即可

这里&pio 6代表GPIOG(GPIOA对应0,GPIOB对应1,以此类推),0代表第0个端口 即PG0,GPIO_ACTIVE_LOW代表低电平有效

  • 基础概念参考Documentation/devicetree/bindings/gpio/gpio.txt和Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
  • 也可以根据pinctrl的兼容属性"allwinner,sun8i-v3s-pinctrl",查到Documentation/devicetree/bindings/pinctrl/allwinner,sunxi-pinctrl.txt,包括全志芯片pinctrl写法的解释,对应的源码为drivers/pinctrl/sunxi/pinctrl-sun8i-v3s.c

简要分析

主要接口
  • static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index)

    从设备节点中获取 GPIO 编号,返回值为GPIO编号,支持设备树中配置多个引脚的信息,通过index区分(设为0即为第一个引脚的信息)

  • static inline int gpio_request(unsigned gpio, const char *label)

    通过GPIO编号申请使用对应的引脚,建议申请,可以预防引脚冲突;label为系统中显示的标签名

  • static inline int gpio_direction_output(unsigned gpio, int value)

    用于设置某个 GPIO 为输出,并且设置默认输出值(一般仅需调用一次)

  • #define gpio_set_value __gpio_set_value

    static inline void __gpio_set_value(unsigned gpio, int value)

    用于设置某个 GPIO 的输出值

流程

参考 荔枝派zero驱动开发03:设备树基础 ,从设备树中获取gpioled设备节点,并检查compatible和status属性

然后调用of_get_named_gpio获取GPIO编号,使用gpio_request申请引脚,将引脚配置为输出

后续注册设备节点等操作无变化

最后需要实现fops的write函数,在led_gpio_write中判断传入的值并使用gpio_set_value配置相应亮灭

测试

源码gpioled.c附在文后,参考前述章节修改Makefile并编译,编译出的gpioled.ko传到开发板

测试可直接使用使用上一章的ledcharApp进行

在这里插入图片描述

源码

gpioled.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/printk.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

struct gpioled_dev
{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int led_gpio;
};
struct gpioled_dev gpioled = {
    .major = 0,
};

#define PIN_N 0 // 第0个引脚,PG0,绿色
#define DEV_NAME "gpioled"
#define LED_ON 0 // 上拉,低电平亮
#define LED_OFF 1

static int led_gpio_open(struct inode *inode, struct file *file)
{
    file->private_data = &gpioled;
    return 0;
}

static int led_gpio_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    return 0;
}

static int led_gpio_release(struct inode *inode, struct file *file)
{
    return 0;
}

static ssize_t led_gpio_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
    int ret = 0;
    unsigned char databuf;
    struct gpioled_dev *dev = file->private_data;

    ret = copy_from_user(&databuf, user_buf, sizeof(databuf));
    if (ret < 0)
    {
        pr_err("copy_from_user failed
");
        return -EFAULT;
    }
    if (databuf == 0 || databuf == '0') // LED_OFF
        gpio_set_value(dev->led_gpio, 1);
    if (databuf == 1 || databuf == '1') // LED_ON
        gpio_set_value(dev->led_gpio, 0);

    return 1;
}

static const struct file_operations gpioled_fops = {
    .open = led_gpio_open,
    .read = led_gpio_read,
    .release = led_gpio_release,
    .write = led_gpio_write,
};

static int __init led_driver_init(void)
{
    int ret;
    const char *str;

    // 1.获取设备节点
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL)
    {
        pr_err("cannot find node /gpioled
");
        return -EINVAL;
    }

    // 2.检查设备节点属性,确保为okay
    ret = of_property_read_string(gpioled.nd, "status", &str);
    if (ret < 0)
    {
        return -EINVAL;
    }
    if (strcmp(str, "okay"))
    {
        pr_err("status is not okay
");
        return -EINVAL;
    }

    // 3.检查设备节点兼容属性compatible,确保与设备树一致
    if(of_device_is_compatible(gpioled.nd, "user,led")) 
    if (ret < 0)
    {
        pr_err("compatible is not "user,led"
");
        return -EINVAL;
    }

    // 4.获取 LED 灯的 GPIO 号
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "gpios", 0);
    if (gpioled.led_gpio < 0)
    {
        printk("can't get gpios");
        return -EINVAL;
    }
    printk("gpio num = %d
", gpioled.led_gpio);

    // 5.向 gpio 子系统申请使用 GPIO
    ret = gpio_request(gpioled.led_gpio, "green");
    if (ret)
    {
        printk(KERN_ERR "Failed to request gpio
");
        return ret;
    }

    // 6、设置 PI0 为输出,并且输出低电平,默认打开 LED 灯
    ret = gpio_direction_output(gpioled.led_gpio, 0);
    if (ret < 0)
    {
        printk("can't set gpio!
");
    }

    // 注册字符设备
    if (gpioled.major) // 定义了设备号,静态设备号
    {
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.major, 1, DEV_NAME);
        if (ret < 0)
        {
            pr_err("cannot register %s char driver.ret:%d
", DEV_NAME, ret);
            goto exit;
        }
    }
    else // 没有定义设备号,动态申请设备号
    {
        ret = alloc_chrdev_region(&gpioled.devid, 0, 1, DEV_NAME);
        if (ret < 0)
        {
            pr_err("cannot alloc_chrdev_region,ret:%d
", ret);
            goto exit;
        }
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    printk("led major=%d,minor=%d
", gpioled.major, gpioled.minor);

    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    ret = cdev_add(&gpioled.cdev, gpioled.devid, 1);
    if (ret < 0)
        goto del_unregister;

    gpioled.class = class_create(THIS_MODULE, DEV_NAME);
    if (IS_ERR(gpioled.class))
        goto del_cdev;

    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, DEV_NAME);
    if (IS_ERR(gpioled.device))
        goto destroy_class;

    return 0;

    // 注意  goto后的标签没有return操作,将顺序执行多个label直至return,这里反向写
destroy_class:
    class_destroy(gpioled.class);
del_cdev:
    cdev_del(&gpioled.cdev);
del_unregister:
    unregister_chrdev_region(gpioled.devid, 1);
exit:
    printk("init failed
");
    return -EIO;
}

static void __exit led_driver_exit(void)
{
    if (gpioled.led_gpio)
    {
        gpio_set_value(gpioled.led_gpio, 1);
        gpio_free(gpioled.led_gpio);
    }
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, 1);
    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
}

module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("USER");
MODULE_INFO(intree, "Y");