当前位置: 首页 > news >正文

Linux驱动学习day8(按键驱动读取方式、GPIO、pinctrl子系统)

按键驱动程序的几种读取方式

非阻塞

阻塞 

 

select/poll 

 异步通知

GPIO和Pinctrl子系统

 Pinctrl 子系统主要用于配置 多功能引脚的功能复用 和 电气属性(如上拉/下拉、驱动能力),这是很多 SoC 芯片(如 Rockchip、Allwinner)必需的步骤。

一个引脚可能有多个功能(如 UART TX/RX、SPI、GPIO)

Pinctrl 子系统负责告诉内核“当前要把这个引脚用作 GPIO(或 SPI/UART)

RK板子的pins为rockchip,pins = <bank号 引脚号 功能编号 引脚配置宏>下面代码的意思是第一组GPIO中第15个引脚,引脚复用为GPIO模式,没有上拉或者下拉电阻。

led {compatible = "my,led";pinctrl-names = "default";pinctrl-0 = <&led_pins>;gpio-names = "led";led-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
};pinctrl {led_pins: led-pins {rockchip,pins = <1 15 RK_FUNC_GPIO &pcfg_pull_none>;};
};

 

GPIO 子系统提供了一个统一的接口,用于在 Linux 驱动中 控制 GPIO 引脚的输入输出电平,支持配置方向、读写状态等。 设备树中GPIO写法示例:

led {compatible = "my,led";gpio-names = "led";led-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
};

上机实验

按道理来讲,将一个 PIN 用作 GPIO 功能的时候也需要创建对应的 pinctrl 节点,并
且将所用的 PIN 复用为 GPIO 功能,但是!对于 RK3568 而言,如果一个 PIN 用作 GPIO 功能
的时候不需要创建对应的 pinctrl 节点!

所以在LED点灯的实验中,只需要向设备树中添加LED灯的结点即可。

myled{compatible = "my,myled";gpio-names = "led";led-gpios  = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;status     = "okey";
};

这里的gpio-names是因为我在驱动代码中使用了gpiod-get函数,所以需要加上这个属性,led-gpio中的属性意思是 使用gpio0 0C这个引脚,GPIO_ACTIVE_HIGH表示该 GPIO 引脚的逻辑电平有效方式:高电平有效(1 表示开启,0 表示关闭)。这里之所以使用这个宏,是因为我们在驱动代码中使用了gpiod_set_value(led_gpio , status),status系统默认会是一个逻辑值。驱动只处理逻辑信号,硬件差异通过设备树隐藏起来,换板子只改设备树,不用动驱动,极大方便了维护和移植。

程序代码

代码流程分析:

1、首先还是最主要的驱动部分file_operations结构体的构造,先写出最一般的file_operations结构体,写好驱动程序对应的open,read,write,release函数。

2、由于要利用设备树来操作硬件,所以需要创建platform_driver结构体,写出基本的probe函数和remove函数,并且在.driver中设置.name 属性和.of_match_table(创建of_device_id 结构体数组,设置属性{.compatible = "和设备树中的结点compatible属性相同"}) 。

3、在入口函数注册platform_driver,出口函数释放unregister。并且使用module_init() ,  module_exit()修饰入口出口函数。

4、当设备树中结点和驱动程序相匹配时,会调用驱动程序的probe函数,在probe函数中,我们需要注册file_operation结构体和加载设备节点(class_create , device_create),并且获得struct gpio_desc *led_gpio。

5、当APP调用open函数的时候,进入驱动程序的open函数。在open函数中使用gpiod_direction_output函数设置其为输出引脚,默认高电平。

6、当APP调用write函数的时候,进入到驱动程序的write函数,在函数中调用gpiod_set_value,确定引脚输出高低电平。

7、在remove中注销之前创建的class,device,led_gpio , file_operation结构体。

#include <linux/module.h>     // 最基本模块宏
#include <linux/kernel.h>     // printk
#include <linux/init.h>       // __init/__exit
#include <linux/fs.h>         // register_chrdev 等
#include <linux/uaccess.h>    // copy_to_user, copy_from_user
#include <linux/types.h>      // dev_t, bool 等类型
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of_device.h>static int major = 0;static struct class *myled_class;
static struct gpio_desc *led_gpio;/* funciton */static ssize_t led_drv_read (struct file *file, char __user *buf , size_t size, loff_t * offset)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);printk("read from drivers:%s %s\n" , __FILE__ , __FUNCTION__ );return 0;
}
/* write(fd , &val , 1) */
static ssize_t led_drv_write (struct file *file, const char __user *buf , size_t size, loff_t * offset)
{unsigned char status;unsigned int ret;printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);ret = copy_from_user(&status, buf, 1);/* minior and status  */gpiod_set_value(led_gpio, status); /* logic value */return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);/* init LED gpio output*/gpiod_direction_output(led_gpio, 0);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{// printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);return 0;
}/*  create struct file_operations */
static struct file_operations myled_drv = 
{.owner          = THIS_MODULE,.open           = led_drv_open,.read           = led_drv_read,.write          = led_drv_write,.release        = led_drv_close,
};static int chip_demo_gpio_probe(struct platform_device *pdev)
{printk("%s %s %d \n", __FILE__ , __FUNCTION__ , __LINE__);led_gpio = gpiod_get(&pdev->dev, "led", 0);if(IS_ERR(led_gpio)){printk("%s %s line %d\n" , __FILE__ , __FUNCTION__ , __LINE__);dev_err(&pdev->dev , "failed to get GPIO for led\n");return PTR_ERR(led_gpio);}major = register_chrdev(0 , "led_drv", &myled_drv);/* do not need mknod */if(!myled_class){myled_class = class_create(THIS_MODULE, "led_class");if (IS_ERR(myled_class)) {printk("failed to allocate class\n");unregister_chrdev(major, "led_class");return PTR_ERR(myled_class);}}device_create(myled_class , NULL , MKDEV(major, 0) , NULL , "myled%d" , 0);return 0;
}static int chip_demo_gpio_remove(struct platform_device *pdev)
{device_destroy(myled_class , MKDEV(major, 0));class_destroy(myled_class);unregister_chrdev(major, "led_drv");gpiod_put(led_gpio);return 0;
}static const struct of_device_id myleds[] = {{ .compatible = "my,myled" },{},
};static struct platform_driver chip_demo_gpio_driver={.probe  = chip_demo_gpio_probe,.remove = chip_demo_gpio_remove,.driver = {.name = "myled",.of_match_table = myleds,}
};/* register_chrdev */
static int __init led_init(void)
{/* struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...) *///p_led_opr = get_board_led_opr();int err;err = platform_driver_register(&chip_demo_gpio_driver);return 0;
}/* entry function */
static void __exit led_exit(void)
{/* distroy void device_destroy(struct class *class, dev_t devt)*/platform_driver_unregister(&chip_demo_gpio_driver);return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");//#warning "MODULE_LICENSE included in led_template_drv.c"

上述代码中使用到了几个GPIO的API,记录一下:

int gpiod_direction_output(struct gpio_desc *desc, int value);/* 设置GPIO为输出,默认低电平 */
void gpiod_set_value(struct gpio_desc *desc, int value);/* 设置GPIO的值,value使用status 为逻辑值 */
struct gpio_desc *__must_check gpiod_get(struct device *dev, const char *con_id,enum gpiod_flags flags)
/* dev设备对象(一般是 pdev->dev),用于查找该设备节点绑定的 GPIO con_id和设备树中的 gpio-names 对应,用于匹配指定获取 GPIO 的意图__must_check:必须检查返回值
*/
void gpiod_put(struct gpio_desc *desc);/* 释放指针 */

这里要记住,我之前习惯将{ .compatible = "my,myled" }写成{ .compatible = "my , myled" },每个符号之后都有一个空格,并且将设备树中的compatible 也写成 "my , myled",但是这样写就会出现问题,加载驱动之后不会调用.probe函数,也就是说之后的都无法执行,只需要将空格去掉即可,一开始还以为逻辑出现了错误,一直找不到问题所在。

测试程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>/* ./ledtest /dev/led_drv on ./ledtest /dev/led_drv off 
*/
int main(int argc , char *argv[])
{int fd;char status;if(argc != 3){printf("Usage: %s <dev> < off | on >\n" , argv[0]);printf("eg: %s /dev/led_drv on\n" , argv[0]);printf("eg: %s /dev/led_drv off\n" , argv[0]);return -1;}/* open */fd = open(argv[1] , O_RDWR);if(fd < 0){printf("can not open %s\n" , argv[1]);return -1;}/* write */if(strcmp(argv[2] , "on") == 0){status = 1;write(fd , &status , 1);}else if(strcmp(argv[2] , "off") == 0){status = 0;write(fd , &status , 1);}close(fd);return 0;
}

测试程序和Makefile都和之前一样,不用修改。

还有一点就是,需要查看这个引脚是否有作为其他功能使用,如果有作为其他功能使用,我们加载这个驱动的时候就会出现错误,因此需要在设备树中查看一下这个引脚的其他作用,并将其禁止。RK3568上的LED灯被作为心跳灯使用,我们要禁止它,打开k3568-evb.dtsi设备树文件,搜索gpio0 RK_PC0,我们可以看到下图,这里添加status = "disabled"。

http://www.lryc.cn/news/575745.html

相关文章:

  • FFmpeg进行简单的视频编辑与代码写法实例
  • 推荐系统的视频特征-视频关键帧特征提取与向量生成
  • 【Unity】如何制作翻页UI
  • 圆周期性显示和消失——瞬态实现(CAD c#二次开发、插件定制)
  • 算法打卡 day4
  • Vue样式绑定与条件渲染详
  • MySQL多表关系
  • ASIO 避坑指南:高效、安全与稳健的异步网络编程
  • 游戏App前端安全加固:利用AI云防护技术抵御恶意攻击
  • vue3 json 转 实体
  • 临床开发计划:从实验室到市场的战略蓝图
  • day48-硬件学习之GPT定时器、UART及I2C
  • 面试150 判断子序列
  • 【已解决】Android Studio gradle遇到unresolved reference错误
  • 鸿蒙 SplitLayout 组件解析:折叠屏分割布局与多端适配指南
  • 视频关键帧提取
  • 跟着AI学习C#之项目实战-电商平台 Day1
  • Python打卡:Day36
  • mac电脑安装vscode的力扣插件报错解决办法
  • 板凳-------Mysql cookbook学习 (十--11)
  • Spring Boot高并发 锁的使用方法
  • Flutter 多平台项目开发指南
  • 使用java语言,计算202503291434距离当前时间,是否大于三天
  • SQL SERVER存储过程
  • 赋能 Java 工程,飞算科技重新定义智能开发
  • 自动化测试--app自动化测试之给手机设置锁屏图案
  • 桌面小屏幕实战课程:DesktopScreen 11 SPI 水墨屏
  • httpClient连接配置超时时间该设置多长才合适?
  • 从提示工程(Prompt Engineering)到上下文工程(Context Engineering)
  • 【RabbitMQ】多系统下的安装配置与编码使用(python)