• Linux内核驱动学习(九)GPIO外部输入的处理


    前言

    前面是如何操作GPIO进行输出,这里我重新实现了一个gpio的驱动,可以获取外部信号的输入。gpio-demo.c中已经包括检测一个gpio的信号,并且包含了中断和轮询两种方式,可以通过设备树里的mode属性进行选择。

    设备树

    本文检测的输入引脚是GPIO3_D0,具体的设备树如下所示;

    gpio-demo {
    		compatible = "gpio-demo";
    		input-gpio = <&gpio3 RK_PD0 GPIO_ACTIVE_LOW>;
    		mode = <1>; // 0:poll 1:interrupt
    		poll_time = <1000>; //ms		
    };
    
    • compatible:设备兼容属性为gpio-demo,与后面的驱动代码中的
      gpio_demo_of_match[] = { { .compatible = "gpio-demo"}, {}, } 需要相同;
    • input-gpio:这个属性值通过of_get_named_gpio来获取;
    • mode:用于判断当前的工作模式是轮询还是中断;
    • poll_time:轮询模式下的周期,间隔多少毫秒会读取一次gpio的状态;

    对于设备树的解析,单独封装了一个接口;

    static int gpio_parse_data(struct gpio_demo_device *di){
    
    	int ret;
    	struct gpio_platform_data *pdata;
    	struct device *dev = di->dev;
    	struct device_node *np = di->dev->of_node;
    
    	pdata = devm_kzalloc(di->dev, sizeof(*pdata), GFP_KERNEL);
    	if (!pdata) {
    		return -ENOMEM;
    	}
    	di->pdata = pdata;
    	// set default value for platform data
    	pdata->mode = DEFAULT_MODE;
    	pdata->poll_ms = DEFAULT_POLL_TIME * 1000;
    
    	dev_info(dev,"parse platform data
    ");
    
    	ret = of_property_read_u32(np, "mode", &pdata->mode);
    	if (ret < 0) {
    		dev_err(dev, "can't get mode property
    ");
    	}
    	ret = of_property_read_u32(np, "poll_time", &pdata->poll_ms);
    	if (ret < 0) {
    		dev_err(dev, "can't get poll_ms property
    ");		
    	}
    
    	pdata->gpio_index = of_get_named_gpio(np,"input-gpio", 0);
    	if (pdata->gpio_index < 0) {
    		dev_err(dev, "can't get input gpio
    ");
    	}
    	// debug parse device tree data
    	dev_info(dev, "Success:mode is %d
    ", pdata->mode);
    	dev_info(dev, "Success:gpio index is %d
    ", pdata->gpio_index);
    	return 0;
    }
    

    两个结构体

    gpio_platform_data

    gpio_platform_data主要是对设备树中众多属性的封装;

    struct gpio_platform_data {
    	int mode;
    	int count;
    	int gpio_index; 
    	struct mutex mtx;
    	int poll_ms;
    };
    

    gpio_demo_device

    gpio_demo_device是与设备驱动中相关资源的封装,包括工作队列等等;

    struct gpio_demo_device {
    	struct platform_device *pdev;
    	struct device *dev;
    	struct gpio_platform_data 	*pdata;
    	struct workqueue_struct		*gpio_monitor_wq;
    	struct delayed_work gpio_delay_work ;
    	int gpio_irq;
    };
    

    两种方式

    在驱动的probe函数中,先通过gpio_parse_data解析设备树文件,从而获取mode属性的值:

    • 0gpio_demo_init_poll初始化进入轮询工作模式;
    • 1gpio_demo_init_interrupt初始化进入中断工作模式;
    static int gpio_demo_probe(struct platform_device *pdev){
    	...
    	ret = gpio_parse_data(priv);
    	if (ret){
    		dev_err(dev,"parse data failed
    ");
    	}
    	...
    	if (priv->pdata->mode == 0){
    		gpio_demo_init_poll(priv); //轮询
    	} else {
    		gpio_demo_init_interrupt(priv);//中断
    	}
    }
    

    轮询

    在轮询工作模式下,已经通过gpio_demo_init_poll对工作队列进行初始化,之后,后启动运行gpio_demo_work任务,并在规定的调度时间内,重复检测运行这个任务。
    通过gpio_get_value(gpio_index)读取GPIO3_D0上的电平状态,如果需要对边沿信号进行处理还需要做改动,本文只能对电平信号进行处理。

    static void gpio_demo_work(struct work_struct *work) {
    
    	struct gpio_demo_device *di = container_of(work,
    			     struct gpio_demo_device,
    			     gpio_delay_work.work);
    
    	struct gpio_platform_data *padta = di->pdata;
    	int gpio_index,value;
    	//获取gpio索引号
    	gpio_index = padta->gpio_index;
    	if (!gpio_is_valid(gpio_index) ) {
    		dev_err(di->dev, "gpio is not valid
    ");
    		goto end;
    	}
    	if ( (value = gpio_get_value(gpio_index) ) == 0) {
    		dev_info(di->dev,"get value is %d
    ",value);
    	}else{
    		dev_info(di->dev,"get value is %d
    ",value);
    	}
    	end:
    	queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
    			   msecs_to_jiffies(di->pdata->poll_ms));
    }
    

    外部中断

    中断的申请和初始化在gpio_demo_init_interrupt函数中已经实现,如下所示;
    通过gpio_to_irq接口获取相应GPIO上的软件中断号,然后通过devm_request_irq申请中断;

    static int gpio_demo_init_interrupt(struct gpio_demo_device *di) {
    	...
    	// 获取gpio上的中断号
    	irq = gpio_to_irq(gpio_index);
    	...
    	//申请中断
    	ret = devm_request_irq(di->dev, irq, gpio_demo_isr, 
    					IRQF_TRIGGER_FALLING, //下降沿
    					"gpio-demo-isr", //中断名称
    					di);
    	...
    }
    

    其中,每次外部发送一个下降沿信号,就会触发中断并进入gpio_demo_isr这个中断服务程序;下面来看一下这个gpio_demo_isr,在这里可以做一些我们想做的事情;

    static irqreturn_t gpio_demo_isr(int irq, void *dev_id)
    {
    	struct gpio_demo_device *di = (struct gpio_demo_device *)dev_id;
    	struct gpio_platform_data *pdata = di->pdata;
    
    	BUG_ON(irq != gpio_to_irq(pdata->gpio_index));
    	//TODO 
    	dev_info(di->dev, "%s
    ", __func__);
    	return IRQ_HANDLED;
    }
    

    最终,我只在中断服务程序中打印了一下串口信息,方便验证。

    总结

    通过这次学习和总结,总体了解了以下几点;

    • 通过delayed_workGPIO进行轮询操作,后面会再深入学习一下;
    • 学习了对于GPIO上的中断申请,目前对于中断还是刚好够用的阶段,中断的篇幅较长,可以对其原理做一下学习,还有内核中中断的机制;
    • 学习了内核中读取设备树的几个接口;
    • 学习了platform设备驱动模型的框架;

    附录

    #include <linux/module.h> 
    #include <linux/init.h>
    
    #include <linux/platform_device.h>
    //API for libgpio
    #include <linux/gpio.h>
    //API for malloc
    #include <linux/slab.h>
    //API for device tree
    #include <linux/of_platform.h>
    #include <linux/of_gpio.h>
    #include <linux/of_device.h>
    //API for thread 
    #include <linux/kthread.h>
    
    #include <linux/delay.h>
    #include <linux/mutex.h>
    //API for delaywork
    #include <linux/workqueue.h>
    
    #include <linux/interrupt.h>
    #include <linux/irq.h>
    
    #define TIMER_MS_COUNTS		1000
    
    // default value of dts 
    #define DEFAULT_POLL_TIME	5
    #define DEFAULT_MODE		1
    
    
    struct gpio_platform_data {
    	int mode;
    	int count;
    	int gpio_index; 
    	struct mutex mtx;
    	int poll_ms;
    };
    
    
    struct gpio_demo_device {
    
    	struct platform_device *pdev;
    	struct device *dev;
    	struct gpio_platform_data 	*pdata;
    	struct workqueue_struct		*gpio_monitor_wq;
    	struct delayed_work gpio_delay_work ;
    	int gpio_irq;
    };
    
    static int gpio_parse_data(struct gpio_demo_device *di){
    
    	int ret;
    	struct gpio_platform_data *pdata;
    	struct device *dev = di->dev;
    	struct device_node *np = di->dev->of_node;
    
    	pdata = devm_kzalloc(di->dev, sizeof(*pdata), GFP_KERNEL);
    	if (!pdata) {
    		return -ENOMEM;
    	}
    	di->pdata = pdata;
    	// set default value for platform data
    	pdata->mode = DEFAULT_MODE;
    	pdata->poll_ms = DEFAULT_POLL_TIME * 1000;
    
    	dev_info(dev,"parse platform data
    ");
    
    	ret = of_property_read_u32(np, "mode", &pdata->mode);
    	if (ret < 0) {
    		dev_err(dev, "can't get mode property
    ");
    	}
    	ret = of_property_read_u32(np, "poll_time", &pdata->poll_ms);
    	if (ret < 0) {
    		dev_err(dev, "can't get poll_ms property
    ");		
    	}
    
    	pdata->gpio_index = of_get_named_gpio(np,"input-gpio", 0);
    	if (pdata->gpio_index < 0) {
    		dev_err(dev, "can't get input gpio
    ");
    	}
    	// debug parse device tree data
    	dev_info(dev, "Success:mode is %d
    ", pdata->mode);
    	dev_info(dev, "Success:gpio index is %d
    ", pdata->gpio_index);
    	return 0;
    }
    
    static void gpio_demo_work(struct work_struct *work) {
    
    	struct gpio_demo_device *di = container_of(work,
    			     struct gpio_demo_device,
    			     gpio_delay_work.work);
    
    	struct gpio_platform_data *padta = di->pdata;
    	int gpio_index,value;
    	gpio_index = padta->gpio_index;
    	if (!gpio_is_valid(gpio_index) ) {
    		dev_err(di->dev, "gpio is not valid
    ");
    		goto end;
    	}
    	if ( (value = gpio_get_value(gpio_index) ) == 0) {
    		dev_info(di->dev,"get value is %d
    ",value);
    	}else{
    		dev_info(di->dev,"get value is %d
    ",value);
    	}
    	end:
    	queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
    			   msecs_to_jiffies(di->pdata->poll_ms));
    }
    
    static int gpio_demo_init_poll(struct gpio_demo_device *di) {
    
    	dev_info(di->dev,"%s
    ", __func__);
    
    	di->gpio_monitor_wq = alloc_ordered_workqueue("%s",
    			WQ_MEM_RECLAIM | WQ_FREEZABLE, "gpio-demo-wq");
    
    
    	INIT_DELAYED_WORK(&di->gpio_delay_work, gpio_demo_work);
    	queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
    			   msecs_to_jiffies(TIMER_MS_COUNTS * 5));
    
    
    	return 0;
    }
    
    static irqreturn_t gpio_demo_isr(int irq, void *dev_id)
    {
    	struct gpio_demo_device *di = (struct gpio_demo_device *)dev_id;
    	struct gpio_platform_data *pdata = di->pdata;
    
    	BUG_ON(irq != gpio_to_irq(pdata->gpio_index));
    
    	dev_info(di->dev, "%s
    ", __func__);
    	//printk("%s
    ",__func__);
    	return IRQ_HANDLED;
    }
    
    static int gpio_demo_init_interrupt(struct gpio_demo_device *di) {
    	
    	int irq, ret;
    	int gpio_index = di->pdata->gpio_index;
    	dev_info(di->dev,"%s
    ", __func__);
    
    	if (!gpio_is_valid(gpio_index)){
    		return -1;
    	}
    
    	irq = gpio_to_irq(gpio_index);
    
    	if (irq < 0) {
    		dev_err(di->dev, "Unable to get irq number for GPIO %d, error %d
    ",
    				gpio_index, irq);
    		gpio_free(gpio_index);
    		return -1;
    	}
    	ret = devm_request_irq(di->dev, irq, gpio_demo_isr,
    					IRQF_TRIGGER_FALLING,
    					"gpio-demo-isr",
    					di);
    	if (ret) {
    		dev_err(di->dev, "Unable to claim irq %d; error %d
    ",
    				irq, ret);
    		gpio_free(gpio_index);
    		return -1;
    	}
    
    	return 0;
    }
    
    
    static int gpio_demo_probe(struct platform_device *pdev){
    
    	int ret;
    	struct gpio_demo_device *priv;
    	struct device *dev = &pdev->dev;
    
    	priv = devm_kzalloc(dev, sizeof(*priv) , GFP_KERNEL);
    
    	if (!priv) {
    		return -ENOMEM;
    	}
    	priv->dev = dev; //important 
    
    	ret = gpio_parse_data(priv);
    	if (ret){
    		dev_err(dev,"parse data failed
    ");
    	}
    
    	platform_set_drvdata(pdev,priv);
    
    	if (priv->pdata->mode == 0){
    		gpio_demo_init_poll(priv);
    	} else {
    		gpio_demo_init_interrupt(priv);
    	}
    	return 0;
    }
    #ifdef CONFIG_OF
    static struct of_device_id gpio_demo_of_match[] = {
    	{ .compatible = "gpio-demo"},
    	{},
    }
    
    MODULE_DEVICE_TABLE(of,gpio_demo_of_match);
    #else
    static struct of_device_id gpio_demo_of_match[] = {
    	{ },
    }
    #endif
    
    static struct platform_driver gpio_demo_driver = {
    	.probe = gpio_demo_probe,
    	.driver = {
    		.name = "gpio-demo-device",
    		.owner = THIS_MODULE,
    		.of_match_table = of_match_ptr(gpio_demo_of_match),
    	}
    };
    
    static int __init gpio_demo_init(void){
    	return  platform_driver_register(&gpio_demo_driver);
    }
    
    static void __exit gpio_demo_exit(void){
    	platform_driver_unregister(&gpio_demo_driver);
    }
    
    late_initcall(gpio_demo_init);
    module_exit(gpio_demo_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("Gpio demo Driver");
    MODULE_ALIAS("platform:gpio-demo");
    
  • 相关阅读:
    MLlib--FPGrowth算法
    MLlib--SVD算法
    算法--访问单个节点的删除
    算法--环形链表插值
    算法--数组变树
    算法--滑动窗口
    RMAN备份失败之:mount: block device /dev/emcpowerc1 is write-protected, mounting read-only
    OPatch failed with error code 73
    Sybase ASE报错:server Error: 8242, Severity: 16, State: 1
    ORA-00257: archiver error. Connect internal only, until freed
  • 原文地址:https://www.cnblogs.com/unclemac/p/12783390.html
Copyright © 2020-2023  润新知