参考:https://zhuanlan.zhihu.com/p/112708449
上一篇:荔枝派zero驱动开发04:GPIO操作(寄存器方式)
下一篇:荔枝派zero驱动开发06:GPIO操作(platform框架)
设备树修改
由于默认设备树默认设备树配置了LED,需要确保在设备树中禁用默认的LED配置,参考上篇操作(注释/leds节点或将/leds节点状态设为disabled)
在根节点下创建gpioled节点
gpioled {
compatible = "user,led";
status = "okay";
gpios = <&pio 6 0 GPIO_ACTIVE_LOW>; /* PG0 green */
// gpios = <&pio 6 0 GPIO_ACTIVE_LOW>,<&pio 6 1 GPIO_ACTIVE_LOW>;
};
其中compatible与status为常见属性,compatible可以自定义,与驱动匹配即可(注意不要与其他节点的兼容属性一样,防止出现冲突)
gpios属性即描述引脚的属性,厂家的写法会有区别,一般来说参考模板的写法即可
这里&pio 6代表GPIOG(GPIOA对应0,GPIOB对应1,以此类推),0代表第0个端口 即PG0,GPIO_ACTIVE_LOW代表低电平有效
- 基础概念参考Documentation/devicetree/bindings/gpio/gpio.txt和Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
- 也可以根据pinctrl的兼容属性"allwinner,sun8i-v3s-pinctrl",查到Documentation/devicetree/bindings/pinctrl/allwinner,sunxi-pinctrl.txt,包括全志芯片pinctrl写法的解释,对应的源码为drivers/pinctrl/sunxi/pinctrl-sun8i-v3s.c
简要分析
主要接口
-
static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index)
从设备节点中获取 GPIO 编号,返回值为GPIO编号,支持设备树中配置多个引脚的信息,通过index区分(设为0即为第一个引脚的信息)
-
static inline int gpio_request(unsigned gpio, const char *label)
通过GPIO编号申请使用对应的引脚,建议申请,可以预防引脚冲突;label为系统中显示的标签名
-
static inline int gpio_direction_output(unsigned gpio, int value)
用于设置某个 GPIO 为输出,并且设置默认输出值(一般仅需调用一次)
-
#define gpio_set_value __gpio_set_value
static inline void __gpio_set_value(unsigned gpio, int value)
用于设置某个 GPIO 的输出值
流程
参考 荔枝派zero驱动开发03:设备树基础 ,从设备树中获取gpioled设备节点,并检查compatible和status属性
然后调用of_get_named_gpio获取GPIO编号,使用gpio_request申请引脚,将引脚配置为输出
后续注册设备节点等操作无变化
最后需要实现fops的write函数,在led_gpio_write中判断传入的值并使用gpio_set_value配置相应亮灭
测试
源码gpioled.c附在文后,参考前述章节修改Makefile并编译,编译出的gpioled.ko传到开发板
测试可直接使用使用上一章的ledcharApp进行
源码
gpioled.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/printk.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
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 = {
.major = 0,
};
#define PIN_N 0 // 第0个引脚,PG0,绿色
#define DEV_NAME "gpioled"
#define LED_ON 0 // 上拉,低电平亮
#define LED_OFF 1
static int led_gpio_open(struct inode *inode, struct file *file)
{
file->private_data = &gpioled;
return 0;
}
static int led_gpio_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
static int led_gpio_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t led_gpio_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
int ret = 0;
unsigned char databuf;
struct gpioled_dev *dev = file->private_data;
ret = copy_from_user(&databuf, user_buf, sizeof(databuf));
if (ret < 0)
{
pr_err("copy_from_user failed\r\n");
return -EFAULT;
}
if (databuf == 0 || databuf == '0') // LED_OFF
gpio_set_value(dev->led_gpio, 1);
if (databuf == 1 || databuf == '1') // LED_ON
gpio_set_value(dev->led_gpio, 0);
return 1;
}
static const struct file_operations gpioled_fops = {
.open = led_gpio_open,
.read = led_gpio_read,
.release = led_gpio_release,
.write = led_gpio_write,
};
static int __init led_driver_init(void)
{
int ret;
const char *str;
// 1.获取设备节点
gpioled.nd = of_find_node_by_path("/gpioled");
if (gpioled.nd == NULL)
{
pr_err("cannot find node /gpioled\r\n");
return -EINVAL;
}
// 2.检查设备节点属性,确保为okay
ret = of_property_read_string(gpioled.nd, "status", &str);
if (ret < 0)
{
return -EINVAL;
}
if (strcmp(str, "okay"))
{
pr_err("status is not okay\r\n");
return -EINVAL;
}
// 3.检查设备节点兼容属性compatible,确保与设备树一致
if(of_device_is_compatible(gpioled.nd, "user,led"))
if (ret < 0)
{
pr_err("compatible is not \"user,led\"\r\n");
return -EINVAL;
}
// 4.获取 LED 灯的 GPIO 号
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "gpios", 0);
if (gpioled.led_gpio < 0)
{
printk("can't get gpios");
return -EINVAL;
}
printk("gpio num = %d\r\n", gpioled.led_gpio);
// 5.向 gpio 子系统申请使用 GPIO
ret = gpio_request(gpioled.led_gpio, "green");
if (ret)
{
printk(KERN_ERR "Failed to request gpio\n");
return ret;
}
// 6、设置 PI0 为输出,并且输出低电平,默认打开 LED 灯
ret = gpio_direction_output(gpioled.led_gpio, 0);
if (ret < 0)
{
printk("can't set gpio!\r\n");
}
// 注册字符设备
if (gpioled.major) // 定义了设备号,静态设备号
{
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.major, 1, DEV_NAME);
if (ret < 0)
{
pr_err("cannot register %s char driver.ret:%d\r\n", DEV_NAME, ret);
goto exit;
}
}
else // 没有定义设备号,动态申请设备号
{
ret = alloc_chrdev_region(&gpioled.devid, 0, 1, DEV_NAME);
if (ret < 0)
{
pr_err("cannot alloc_chrdev_region,ret:%d\r\n", ret);
goto exit;
}
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
printk("led major=%d,minor=%d\r\n", gpioled.major, gpioled.minor);
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
ret = cdev_add(&gpioled.cdev, gpioled.devid, 1);
if (ret < 0)
goto del_unregister;
gpioled.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(gpioled.class))
goto del_cdev;
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, DEV_NAME);
if (IS_ERR(gpioled.device))
goto destroy_class;
return 0;
// 注意 goto后的标签没有return操作,将顺序执行多个label直至return,这里反向写
destroy_class:
class_destroy(gpioled.class);
del_cdev:
cdev_del(&gpioled.cdev);
del_unregister:
unregister_chrdev_region(gpioled.devid, 1);
exit:
printk("init failed\r\n");
return -EIO;
}
static void __exit led_driver_exit(void)
{
if (gpioled.led_gpio)
{
gpio_set_value(gpioled.led_gpio, 1);
gpio_free(gpioled.led_gpio);
}
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, 1);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("USER");
MODULE_INFO(intree, "Y");