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