【Linux驱动】pinctrl 和 gpio子系统(二)—— 通过 pinctrl 和 gpio 子系统驱动LED

pinctrl 子系统一般用于快速配置引脚的复用,而gpio子系统一般用于配置引脚的初始状态,只有将一个引脚复用为普通的 GPIO 功能,而并非作为 USART 的 TXD、SPI 的 CS 或者是 I2C 的SDA等其他功能时,才会需要用到 gpio 子系统。

一、相关API介绍

Linux内核提供了获取 gpio 子系统相关属性的API,这些 API 的声明在 linux/of_gpio.h 文件

1、of_get_named_gpio

该函数用于获取 gpio 编号,gpio 编号可以看做是标识gpio引脚的一种方式,大部分gpio相关的API都要通过gpio编号来操作对应的引脚。上一篇文章已经在设备树中创建了一个 gpio-led 的节点,of_get_named_gpio 获取到的便是 led-gpio 属性的内容,返回的是一个引脚编号,用来代表引脚 GPIO1_IO03。

javascript 复制代码
gpio-led {
	pinctrl-names = "default";                // pinctrl 子系统
	pinctrl-0 = <&pinctrl_gpio_leds>;
	led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;    // gpio 子系统
	status = "okay";
};

of_get_named_gpio函数的声明如下:

javascript 复制代码
/*
 * @description          获取gpio引脚编号
 * @param np             设备树节点
 * @param propname       gpio属性名(gpio子系统的属性名是自己拟定的)
 * @param index          gpio属性包含的引脚可以不止一个,对此需要通过索引来获取到指定引脚
 * @return               返回gpio引脚编号(成功返回值大于0,失败返回值小于0)
 */
static inline int of_get_named_gpio(struct device_node *np,
                                    const char *propname, 
                                    int index);

示例:

javascript 复制代码
// 获取设备树节点
struct device_node* gpioNode = of_find_node_by_path("/gpio-led"); 
// 获取gpio属性
int gpioNum = of_get_named_gpio(gpioNode, "led-gpio", 0);
if (gpioNum < 0)
{
	printk("gpio property fetch failed!\n");
	return -1;
}

**注意:**gpio属性包含的引脚可以不止一个,若gpio属性中包含了多个引脚

2、gpio_direction_output/input

此函数用于设置某个 GPIO 引脚为输出,并且设置默认输出值。与之相对应的,gpio_direction_input 用于设置某个 GPIO 引脚为输入。

cpp 复制代码
/*
 * @description          设置引脚用于输入
 * @param gpio           gpio 引脚编号
 * @return               成功返回 0,失败返回负值
 */
int gpio_direction_input(unsigned gpio);
/*
 * @description          设置引脚用于输出
 * @param gpio           gpio 引脚编号
 * @param value          引脚初始值
 * @return               成功返回 0,失败返回负值
 */
int gpio_direction_output(unsigned gpio, int value);

示例:

cpp 复制代码
int ret = gpio_direction_output(chrdev_led.gpioNum, 1);
if (ret < 0)
{
	printk("gpio set failed!\n");
	return -1;
}

3、gpio_set_value

gpio_set_value 用于设置gpio引脚的高低电平,与之对应的 gpio_get_value 用于获取某个引脚的高低电平。

cpp 复制代码
/*
 * @description          设置gpio引脚的输出值
 * @param gpio           gpio 引脚编号
 * @param value          gpio 引脚输出值
 */
void gpio_set_value(unsigned gpio, int value);

举例:

cpp 复制代码
gpio_set_value(gpioNum, 0);        // 设置引脚输出低电平
gpio_set_value(gpioNum, 1);        // 设置引脚输出高电平

二、完整驱动代码

与之前驱动代码的主要不同之处在于: 入口函数 chrdevbase_init 、写操作函数 write

cpp 复制代码
#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/uaccess.h>
#include <linux/cdev.h>			// cdev_init
#include <linux/device.h>		// device_create
#include <linux/err.h>			// IS_ERR
#include <asm/io.h>				// ioremap、iounmap
#include <linux/of.h>			// 获取设备树属性 API
#include <linux/of_address.h>	// of_ioremap
#include <linux/of_gpio.h>		// of_get_named_gpio

#define CHRDEVBASE_NAME "chrdevbase" 	/* 设备名 */

/* 寄存器虚拟地址 */
static void __iomem* CCM_CCGR1;
static void __iomem* IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
static void __iomem* IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03;
static void __iomem* GPIO1_GDIR;
static void __iomem* GPIO1_DR;

static u32 val;

enum LED_STAT {
	LED_ON,
	LED_OFF
};

struct chrdev_led_t{
	struct class* class;		/* 设备节点所属类 */
	struct device* driver_node;	/* 驱动文件节点 */

	struct cdev dev;		/* 字符设备 */
	dev_t devid;			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */

	struct device_node* gpioNode;	/* 设备树节点 */
	int gpioNum;					/* gpio 引脚编号 */
};	
static struct chrdev_led_t chrdev_led;

/*
 * @description 	: 打开设备
 * @param -- pinode 	: 传递给驱动的 inode
 * @param - pfile 	: 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *pinode, struct file *pfile)
{
    /* 用户实现具体功能 */
	printk("open chrdevbase\n");
	pfile->private_data = &chrdev_led;
    return 0;
}

/*
 * @description 	: 从设备读取数据
 * @param - pfile	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 缓冲区长度
 * @param - offset	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *pfile, char __user *buf, size_t cnt, loff_t *offset)
{
    /* 用户实现具体功能 */
	struct chrdev_led_t* pdev = pfile->private_data;
	
	const char* msg = "hello, user";
	int ret = copy_to_user(buf, msg, cnt);
	if(ret == 0)
	{
		printk("kernel send data ok!\n");
	}
	else
	{
		printk("kernel send data failed!\n");
	}

    return 0;
}

/*
 * @description 	: 向设备写数据
 * @param - pfile	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 要给设备写入的数据(用户缓冲区)
 * @param - cnt 	: 要写入的数据长度
 * @param - offset	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *pfile, const char __user *buf, size_t cnt, loff_t *offset)
{
    // 获取模块数据
	struct chrdev_led_t* pdev = pfile->private_data;

	printk("write chrdevbase\n");
	u8 databuf[1];
	u8 ledstat;
	u32 ret = 0;
	
	// 将数据从用户缓冲区拷贝到内核缓冲区
	ret = copy_from_user(databuf, buf, cnt);
	if(ret != 0)
		return 0;

	ledstat = buf[0] - '0';
	printk("led state: %d\n", ledstat);
	gpio_set_value();
	if (ledstat == LED_ON)
	{
		gpio_set_value(pdev->gpioNum, 0);
	}
	else if(ledstat == LED_OFF)
	{
		gpio_set_value(pdev->gpioNum, 1);
	}
    return cnt;
}

/*
 * @description 	: 关闭/释放设备
 * @param - pfile	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release (struct inode *pinode, struct file * pfile)
{
    /* 用户实现具体功能 */
	printk("close chrdevbase\n");

    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	u32 ret = 0;
	const char* outstr;
	u32 regData[10];

	/* 通过设备树获取到寄存器地址 */
	// 获取节点
	chrdev_led.gpioNode = of_find_node_by_path("/gpio-led"); 
	if(chrdev_led.gpioNode == NULL)
	{	
		printk("node cannot be found!\n");
		return -1;
	}
	// 读取gpio编号
	chrdev_led.gpioNum = of_get_named_gpio(chrdev_led.gpioNode, "led-gpio", 0);
	if (chrdev_led.gpioNum < 0)
	{
		printk("gpio property fetch failed!\n");
		return -1;
	}
	printk("led-gpio num = %d\r\n", chrdev_led.gpioNum);

	// 配置 GPIO1_IO03 为输出且高电平,默认关闭LED
	ret = gpio_direction_output(chrdev_led.gpioNum, 1);
	if (ret < 0)
	{
		printk("gpio set failed!\n");
		return -1;
	}

	/* 1. 注册设备号 */
	if (chrdev_led.major)
	{
		chrdev_led.devid = MKDEV(chrdev_led.major, 0);
		ret = register_chrdev_region(chrdev_led.devid, 1, CHRDEVBASE_NAME);
	}
	else
	{
		ret = alloc_chrdev_region(&chrdev_led.devid, 0, 1, CHRDEVBASE_NAME);
		chrdev_led.major = MAJOR(chrdev_led.devid);
		chrdev_led.minor = MINOR(chrdev_led.devid);
	}

	/* 2. 初始化字符设备 */
	chrdev_led.dev.owner = THIS_MODULE;
	cdev_init(&chrdev_led.dev, &chrdevbase_fops);					// 初始化字符设备
	/* 3. 将字符设备添加到内核 */
	cdev_add(&chrdev_led.dev, chrdev_led.devid, 1);			// 将字符设备添加到内核

	/* 自动创建设备节点 */
	// 设备节点所属类
 	chrdev_led.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
	if (IS_ERR(chrdev_led.class))
	{
		return PTR_ERR(chrdev_led.class);
	}
	// 创建驱动文件节点
	chrdev_led.driver_node = device_create(chrdev_led.class, NULL, chrdev_led.devid, NULL, CHRDEVBASE_NAME);
	if (IS_ERR(chrdev_led.driver_node))
	{
		return PTR_ERR(chrdev_led.driver_node);
	}
	
	printk("chrdevbase init!\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 取消虚拟地址和物理地址的映射 */
	iounmap(CCM_CCGR1);
	iounmap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03);
	iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);
	iounmap(GPIO1_GDIR);
	iounmap(GPIO1_DR);
	
	/* 注销字符设备 */
	unregister_chrdev_region(chrdev_led.devid, 1);		// 注销设备号
	cdev_del(&chrdev_led.dev);							// 卸载字符设备
	
	device_destroy(chrdev_led.class, chrdev_led.devid);	// 删除节点
	class_destroy(chrdev_led.class);					// 删除类
	printk("chrdevbase exit!\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");
相关推荐
fangzhanpeng1684 天前
快速掌握C#语言基础知识点(1.基础数据类型)
开发语言·c#·1024程序员节
无己心6 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u01030552712 天前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u01030552713 天前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝14 天前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u01030552714 天前
Java图像处理实战指南
人工智能·1024程序员节
u01030552715 天前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u01030552717 天前
Java实用类应用精讲
人工智能·1024程序员节
u01030552717 天前
Java I/O核心操作实战
人工智能·1024程序员节
u01030552719 天前
ArrayList操作详解与实战应用
人工智能·1024程序员节