Linux 驱动学习笔记 - gpio 子系统 (八)

Linux 驱动学习笔记 - gpio 子系统 (八)

本系列均为正点原子 Linux 驱动的学习笔记, 以便加深笔者记忆。如读者想进一步学习,可以到正点原子官网中下载资料进行学习。

pinctrl 子系统和 gpio 子系统的联系

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

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 标号
valueGPIO 默认输出值
返回值含义
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 节点模板

在 pinctrl 中我们已经讲解了如何创建 test 设备的 pinctrl 节点。接下来就创建一个 test 设备的 GPIO 节点。

创建 test 设备节点

在根节点 / 下创建 test 设备子节点,如下

test {
    /* 节点内容 */
}

添加 pinctrl 信息

在 pinctrl 章节中我们创建了 pinctrl_test 节点,此节点描述了 test 设备所使用的 GPIO1_IO00 这个 PIN 的信息,所以要将这个节点添加到 test 设备节点中,如下所示

test {
    pinctrl-names = "default";
    pinctrl-0 = <@pinctrl_test>;
    /* 其它节点内容 */
};

添加 GPIO 属性信息

我们最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,添加完成以后如下所示:

test {
    pinctrl-names = "default";
    pinctrl-0 = <@pinctrl_test>;
    gpio = <@gpio1 0 GPIO_ACTIVE_LOW>;
};

与 gpio 相关的 OF 函数

上面定义了一个 gpio 属性,gpio 属性描述了 test 这个设备所使用的 GPIO。在驱动程序中需要读取 gpio 属性内容,Linux 内核提供了几个与 GPIO 有关的 OF 函数,常用的几个 OF 函数如下所示:

of_gpio_named_count

of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的是空的 GPIO 信息也会被统计到。函数原型如下

int of_gpio_named_count(struct device_node *np, const char *propname)
参数含义
np设备节点
propname要统计的 GPIO 属性
返回值含义
正值统计到的 GPIO 数量
负值失败

例如

gpios = <0
        &gpio1 1 2
        0
        &gpio2 3 4>

上述代码的 “gpios” 节点一共定义了 4 个 GPIO,但是有 2 个是空的,没有实际的含义。通过 of_gpio_named_count 函数统计出来的 GPIO 数量就是 4 个

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 信息的属性名
indexGPIO 索引 因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值含义
正值获取到的 GPIO 编号
负值失败

程序编写

添加 pinctrl 节点

在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led 子节点,节点内容如下所示:

pinctrl_led: ledgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
    >;
};

添加 LED 设备节点

在根节点 / 下创建 LED 灯节点,节点名为 gpioled ,节点内容如下:

gpioled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "atkalpha-gpioled";
    pinctrl-names = "default"; 
    pinctrl-0 = <&pinctrl_led>;
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay"; 
 }

检查 PIN 是否被其他外设使用

  • 检查 pinctrl 设置。

  • 如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用

实验程序

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT 1          /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0               /* 关灯 */
#define LEDON 1                /* 开灯 */

/* gpioled设备结构体 */
struct gpioled_dev
{
    dev_t devid;            /* 设备号 	 */
    struct cdev cdev;       /* cdev 	*/
    struct class *class;    /* 类 		*/
    struct device *device;  /* 设备 	 */
    int major;              /* 主设备号	  */
    int minor;              /* 次设备号   */
    struct device_node *nd; /* 设备节点 */
    int led_gpio;           /* led所使用的GPIO编号		*/
};

struct gpioled_dev gpioled; /* led设备 */

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled; /* 设置私有数据 */
    return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0]; /* 获取状态值 */

    if (ledstat == LEDON)
    {
        gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
    }
    else if (ledstat == LEDOFF)
    {
        gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
    }
    return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
    int ret = 0;

    /* 设置LED所使用的GPIO */
    /* 1、获取设备节点:gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL)
    {
        printk("gpioled node not find!\r\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled node find!\r\n");
    }

    /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if (gpioled.led_gpio < 0)
    {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);

    /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret < 0)
    {
        printk("can't set gpio!\r\n");
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major)
    { /*  定义了设备号 */
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    }
    else
    {                                                                      /* 没有定义设备号 */
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid);                              /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid);                              /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d,minor=%d\r\n", gpioled.major, gpioled.minor);

    /* 2、初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    /* 3、添加一个cdev */
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class))
    {
        return PTR_ERR(gpioled.class);
    }

    /* 5、创建设备 */
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device))
    {
        return PTR_ERR(gpioled.device);
    }
    return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
    /* 释放 gpio */
    gpio_free(gpioled.gpioled);

    /* 注销字符设备驱动 */
    cdev_del(&gpioled.cdev);                              /*  删除cdev */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("tyustli");
  • 10
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
PWM-GPIO驱动程序是Linux内核中的一个驱动模块,用于控制嵌入式系统中的GPIO引脚产生PWM信号。该驱动程序允许开发人员通过编程的方式来控制GPIO引脚的电平变化,从而产生不同占空比的PWM信号。 在Linux内核中,PWM-GPIO驱动程序通过向用户空间提供了相应的接口来实现PWM信号的控制。开发人员可以通过打开相应的设备节点,并使用相应的系统调用函数来设置PWM的频率、占空比等参数,从而实现对GPIO引脚的PWM信号的控制。 驱动程序的核心部分是一个PWM子系统,它与GPIO子系统紧密集成。PWM子系统负责管理PWM信号的生成和控制,而GPIO子系统负责管理GPIO引脚的配置和操作。PWM-GPIO驱动程序在这两个子系统之间起着桥梁的作用。 PWM-GPIO驱动程序的实现方式与硬件平台相关,每个平台可能有不同的具体实现。在驱动程序的初始化过程中,必须先配置GPIO引脚的功能为PWM模式,并将相应的寄存器映射到内核中,以便能够通过对寄存器的操作来控制GPIO引脚。驱动程序还需要初始化PWM子系统,为每个GPIO引脚分配相应的PWM通道,并根据需求设置PWM的频率、占空比等参数。 通过PWM-GPIO驱动程序,开发人员可以方便地利用Linux内核的功能来实现对嵌入式系统中GPIO引脚产生PWM信号的控制。这为开发PWM驱动、控制舵机、LED等应用提供了便捷的方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值