S5P6818_驱动篇(13)pinctrl与gpio子系统

Linux 是一个庞大而完善的系统,尤其是驱动框架,像 GPIO 这种最基本的驱动不可能采用“原始”的裸机驱动开发方式。 Linux 内核提供了 pinctrl 和 gpio 子系统用于GPIO 驱动,本章我们就来学习一下如何借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。

pinctrl 子系统

Linux 驱动讲究驱动分离与分层, pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架。Linux 内核针对 PIN 的配置推出了 pinctrl 子系统,对于 GPIO的配置推出了 gpio 子系统。大多数 SOC 的 pin 都是支持复用的,比如一个pin既可以作为普通的GPIO 使用,也可以作为 I2C使用 等等。此外我们还需要配置 pin 的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。 pinctrl 子系统就是为了解决这个问题而引入的, pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl。

对于不同soc厂商,pinctrl驱动不同,所以dts的配置格式也不同,根据使用soc定制。

示例:

        pinctrl@C0010000 {
			led1_gpio: leds-gpio {
				nexell,pins ="gpiob-12";
				nexell,pin-function = <2>;
				nexell,pin-pull = <2>;
				nexell,pin-strength = <0>;
			};
		};

gpio 子系统

pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。 gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。

示例:

	leds: gpio-leds {
		compatible = "gpio-leds";
		pinctrl-names = "default";
		pinctrl-0 =<&led1_gpio>;

		led@1 {
			gpios = <&gpio_b 12 GPIO_ACTIVE_LOW>;
			label = "status_led";
			linux,default-trigger = "heartbeat";
			linux,default-trigger-delay-ms = <0>;
		};
	};

属性“gpios”描述了LED引脚使用的哪个 IO。属性值一共有三个,我们来看一下这三个属性值的含义,“&gpio_b”表示引脚所使用的 IO 属于 GPIOB组,“12”表示 GPIOB 组的第 12 号 IO,通过这两个值LED驱动就知道引脚使用了 GPIOB_IO12这 GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。

gpio 子系统 API 函数

对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO, gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。 gpio 子系统提供的常用的 API 函数有下面几个:

gpio_request 函数

gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request进行申请,函数原型如下:

int gpio_request(unsigned gpio, const char *label)

函数参数和返回值含义如下:
gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。
label:给 gpio 设置个名字。
返回值: 0,申请成功;其他值,申请失败。

gpio_free 函数

如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:

void gpio_free(unsigned gpio)

函数参数和返回值含义如下:
gpio:要释放的 gpio 标号。
返回值: 无

gpio_direction_input 函数

此函数用于设置某个 GPIO 为输入,函数原型如下所示:

int gpio_direction_input(unsigned gpio)

函数参数和返回值含义如下:
gpio:要设置为输入的 GPIO 标号。
返回值: 0,设置成功;负值,设置失败。

gpio_direction_output 函数

此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:

int gpio_direction_output(unsigned gpio, int value)

函数参数和返回值含义如下:
gpio:要设置为输出的 GPIO 标号。
value: GPIO 默认输出值。
返回值: 0,设置成功;负值,设置失败。

gpio_get_value 函数

此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

gpio:要获取的 GPIO 标号。
返回值: 非负值,得到的 GPIO 值;负值,获取失败。

gpio_set_value 函数

此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下

#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

函数参数和返回值含义如下:
gpio:要设置的 GPIO 标号。
value: 要设置的值。
返回值: 无
关于 gpio 子系统常用的 API 函数就讲这些,这些是我们用的最多的。

gpio 相关 OF 函数

在驱动程序中需要读取 gpio 属性内容, Linux 内核提供了几个与 GPIO 有关的 OF 函数,常用的几个 OF 函数如下所示:

of_gpio_named_count 函数

of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息。

int of_gpio_named_count(struct device_node *np, const char *propname)

函数参数和返回值含义如下:
np:设备节点。
propname:要统计的 GPIO 属性。
返回值: 正值,统计到的 GPIO 数量;负值,失败。

of_gpio_count 函数

和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下
所示:

int of_gpio_count(struct device_node *np)

函数参数和返回值含义如下:
np:设备节点。
返回值: 正值,统计到的 GPIO 数量;负值,失败。

of_get_named_gpio 函数

此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号,此函数在驱动中使用很频繁!函数原型如下:

int of_get_named_gpio(struct device_node *np,const char *propname,int index)

函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取 GPIO 信息的属性名。
index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值: 正值,获取到的 GPIO 编号;负值,失败。

实验

DTS

/ {
	model = "FriendlyElec NanoPC-T3";
	compatible = "friendlyelec,nanopc-t3", "nexell,s5p6818";

	userio{
		compatible = "user-gpio";
		pinctrl-names = "default";
		pinctrl-0 =<&in_gpio &out_gpio>;
		in_gpios = <&gpio_c 4 GPIO_ACTIVE_LOW>;
		out_gpios = <&gpio_c 8 GPIO_ACTIVE_LOW>;
		status = "okay";
	};
};

&pinctrl{
	in_gpio: in_gpio {
		nexell,pins ="gpioc-4";
		nexell,pin-function = <1>;
		nexell,pin-pull = <NX_PIN_PULL_NONE>;
		nexell,pin-strength = <NX_PIN_STR0>;
	};

	out_gpio: out_gpio {
		nexell,pins ="gpioc-8";
		nexell,pin-function = <1>;
		nexell,pin-pull = <NX_PIN_PULL_NONE>;
		nexell,pin-strength = <NX_PIN_STR0>;
	};
};

驱动

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/device.h>

#define DRIVER_NAME "user_gpio"
#define CLASS_NAME "user-gpio"

struct gpio_device {
    struct cdev cdev;
    dev_t devno;
    struct class *cls;
    struct device *dev;
    int in_gpio;
    int out_gpio;
};

static struct gpio_device gpio_dev;

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

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

static ssize_t gpio_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    int value;
    
    // 读取输入GPIO状态
    value = gpio_get_value(gpio_dev.in_gpio);
    
    // 将结果拷贝到用户空间
    if (copy_to_user(buf, &value, sizeof(value)))
        return -EFAULT;

    return sizeof(value);
}

static ssize_t gpio_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    int value;

    // 从用户空间获取数据
    if (copy_from_user(&value, buf, sizeof(value)))
        return -EFAULT;

    // 设置输出GPIO状态
    gpio_set_value(gpio_dev.out_gpio, value);

    return sizeof(value);
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = gpio_open,
    .release = gpio_release,
    .read = gpio_read,
    .write = gpio_write,
};

static int __init gpio_init(void)
{
    struct device_node *node;
    int ret;

    // 查找设备树节点
    node = of_find_compatible_node(NULL, NULL, "user-gpio");
    if (!node) {
        printk(KERN_ERR "Failed to find device tree node\n");
        return -ENODEV;
    }

    // 获取GPIO编号
    gpio_dev.in_gpio = of_get_named_gpio(node, "in_gpios", 0);
    gpio_dev.out_gpio = of_get_named_gpio(node, "out_gpios", 0);

    if (gpio_dev.in_gpio < 0 || gpio_dev.out_gpio < 0) {
        printk(KERN_ERR "Failed to get GPIO numbers\n");
        return -ENODEV;
    }

    // 申请GPIO
    if (gpio_request(gpio_dev.in_gpio, "in_gpio")) {
        printk(KERN_ERR "Failed to request input GPIO\n");
        return -EBUSY;
    }

    if (gpio_request(gpio_dev.out_gpio, "out_gpio")) {
        printk(KERN_ERR "Failed to request output GPIO\n");
        gpio_free(gpio_dev.in_gpio);
        return -EBUSY;
    }

    // 配置GPIO方向
    gpio_direction_input(gpio_dev.in_gpio);
    gpio_direction_output(gpio_dev.out_gpio, 0);

    // 动态分配设备号
    ret = alloc_chrdev_region(&gpio_dev.devno, 0, 1, DRIVER_NAME);
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device number\n");
        goto free_gpio;
    }

    // 初始化字符设备
    cdev_init(&gpio_dev.cdev, &fops);
    gpio_dev.cdev.owner = THIS_MODULE;

    // 添加字符设备到系统
    ret = cdev_add(&gpio_dev.cdev, gpio_dev.devno, 1);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add character device\n");
        goto unregister_chrdev;
    }

    // 创建设备类
    gpio_dev.cls = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(gpio_dev.cls)) {
        printk(KERN_ERR "Failed to create device class\n");
        ret = PTR_ERR(gpio_dev.cls);
        goto del_cdev;
    }

    // 创建设备节点
    gpio_dev.dev = device_create(gpio_dev.cls, NULL, gpio_dev.devno, NULL, DRIVER_NAME);
    if (IS_ERR(gpio_dev.dev)) {
        printk(KERN_ERR "Failed to create device node\n");
        ret = PTR_ERR(gpio_dev.dev);
        goto destroy_class;
    }

    printk(KERN_INFO "User GPIO driver loaded successfully\n");
    return 0;

destroy_class:
    class_destroy(gpio_dev.cls);
del_cdev:
    cdev_del(&gpio_dev.cdev);
unregister_chrdev:
    unregister_chrdev_region(gpio_dev.devno, 1);
free_gpio:
    gpio_free(gpio_dev.in_gpio);
    gpio_free(gpio_dev.out_gpio);
    return ret;
}

static void __exit gpio_exit(void)
{
    device_destroy(gpio_dev.cls, gpio_dev.devno);
    class_destroy(gpio_dev.cls);
    cdev_del(&gpio_dev.cdev);
    unregister_chrdev_region(gpio_dev.devno, 1);
    gpio_free(gpio_dev.in_gpio);
    gpio_free(gpio_dev.out_gpio);
    printk(KERN_INFO "User GPIO driver unloaded\n");
}

module_init(gpio_init);
module_exit(gpio_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Character device driver for user GPIO");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Absorbed_w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值