1.介绍
为了满足框架开发,linux系统中使用pinctrl和GPIO子系统来简化开发。
2.pinctrl子系统
pinctrl 子系统主要工作内容如下:
- 获取设备树中 pin 信息。
- 根据获取到的 pin 信息来设置 pin 的复用功能
- 根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成,pinctrl 子系统源码目录为drivers/pinctrl。
3.gpio子系统
上一小节讲解了 pinctrl 子系统,pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO,Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。
4.代码
4.1 修改设备树的代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* gpioled设备结构体 */
struct gpioled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 注设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* 设备树里面的编号 */
};
struct gpioled_dev gpioled; /* 定义LED结构体 */
void led_switch(struct gpioled_dev *dev, u8 sta)
{
if(sta == LEDON) {
gpio_set_value(dev->led_gpio, 0);
}else if(sta == LEDOFF) {
gpio_set_value(dev->led_gpio, 1);
} else {
printk("Error Number! \r\n");
}
}
static int gpioled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled;
return 0;
}
static int gpioled_release(struct inode *inode, struct file *filp)
{
//struct gpioled_dev *dev = (struct gpioled_dev *)filp->private_data;
return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int retvalue;
unsigned char databuf[1];
// unsigned char ledstat;
struct gpioled_dev *dev = (struct gpioled_dev *)filp->private_data;
retvalue = copy_from_user(databuf, buf, count);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
led_switch(dev, databuf[0]);
return 0;
}
static ssize_t gpioled_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/* 字符设备操作集 */
static const struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.write = gpioled_write,
.open = gpioled_open,
.read = gpioled_read,
.release = gpioled_release,
};
/* 模块入口函数 */
static int __init gpioled_init(void)
{
/* 定义一些所需变量 */
int ret = 0;
/* 1. 注册字符设备驱动 */
gpioled.major = 0;
if(gpioled.major) {
gpioled.devid = MKDEV(gpioled.major, 0);
ret = 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);
}
if(ret < 0){
goto fail_devid;
}
printk("Make devid success! \r\n");
printk("major = %d, minor = %d \r\n", gpioled.major, gpioled.minor);
/* 2. 初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
if (ret < 0){
goto fail_cdev;
} else {
printk("Cdev add sucess! \r\n");
}
/* 3. 自动创建设备节点 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)) {
ret = PTR_ERR(gpioled.class);
goto fail_class;
} else {
printk("Class create sucess! \r\n");
}
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)) {
ret = PTR_ERR(gpioled.device);
goto fail_device;
} else {
printk("Device create sucess! \r\n");
}
/* 1. 获取设备节点 */
gpioled.nd = of_find_node_by_path("/gpioled");
if( gpioled.nd == NULL){
ret = -EINVAL;
printk("gpioled node can not found!\r\n");
goto fail_findnd;
}
/* 2. 获取设备所对应的gpio */
/* 注意第三个参数,因为只有一个索引,所以是0 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if( gpioled.led_gpio < 0) {
printk("Can't find led gpio \r\n");
ret = -EINVAL;
goto fail_findnd;
}
printk("Led gpio num = %d. \r\n", gpioled.led_gpio);
/* 3. 获取到IO之后,申请IO */
/* 作用是申请之后,就能知道该IO是否被其他函数占用 */
/* 如果申请失败,说明被战用啦 */
/* 解决方法:1.搜索所需管脚 2.在搜索复用管脚,例如 &gpio1 3 */
ret = gpio_request(gpioled.led_gpio, "led-gpio");
if(ret) {
printk("Failed to request the led gpio! \r\n");
ret = -EINVAL;
goto fail_findnd;
}
/* 4. 使用IO */
ret = gpio_direction_output(gpioled.led_gpio, 1); /* 设置输出,默认高电平不点亮 */
if(ret < 0){
printk("Failed to set input or output! \r\n");
goto fail_set_output;
}
/* 5. 输出低电平,点亮LED */
gpio_set_value(gpioled.led_gpio, 0);
printk("gpioled init! \r\n");
return 0;
/* 错误处理 */
fail_set_output:
gpio_free(gpioled.led_gpio);
fail_findnd:
device_destroy(gpioled.class, gpioled.devid);
fail_device:
class_destroy(gpioled.class);
fail_class:
cdev_del(&gpioled.cdev);
fail_cdev:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
return ret;
}
/* 模块出口函数 */
static void __exit gpioled_exit(void)
{
/* 关灯 */
gpio_set_value(gpioled.led_gpio, 1);
/* 1. 释放设备号 */
cdev_del(&gpioled.cdev);
/* 2. 注销设备号 */
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
/* 3. 摧毁设备 */
device_destroy(gpioled.class, gpioled.devid);
/* 4.摧毁类 */
class_destroy(gpioled.class);
/* 释放IO, 和上面的申请IO对应 */
gpio_free(gpioled.led_gpio);
printk("gpioled exit! \r\n");
}
/* 模块入口和出口注册 */
module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shao Zheming");