参考:https://zhuanlan.zhihu.com/p/112708449
上一篇:荔枝派zero驱动开发04:GPIO操作(寄存器方式)
下一篇:更新中…
设备树修改
由于默认设备树默认设备树配置了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 "); 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 "); 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 "); return -EINVAL; } // 3.检查设备节点兼容属性compatible,确保与设备树一致 if(of_device_is_compatible(gpioled.nd, "user,led")) if (ret < 0) { pr_err("compatible is not "user,led" "); 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 ", gpioled.led_gpio); // 5.向 gpio 子系统申请使用 GPIO ret = gpio_request(gpioled.led_gpio, "green"); if (ret) { printk(KERN_ERR "Failed to request gpio "); return ret; } // 6、设置 PI0 为输出,并且输出低电平,默认打开 LED 灯 ret = gpio_direction_output(gpioled.led_gpio, 0); if (ret < 0) { printk("can't set gpio! "); } // 注册字符设备 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 ", 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 ", ret); goto exit; } gpioled.major = MAJOR(gpioled.devid); gpioled.minor = MINOR(gpioled.devid); } printk("led major=%d,minor=%d ", 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 "); 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");