Bootstrap

【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。

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函数的声明如下:

/*
 * @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);

示例: 

// 获取设备树节点
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 引脚为输入。

/*
 * @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);

示例: 

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 用于获取某个引脚的高低电平。

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

举例:

gpio_set_value(gpioNum, 0);        // 设置引脚输出低电平
gpio_set_value(gpioNum, 1);        // 设置引脚输出高电平

二、完整驱动代码

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

#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");
;