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

举报
小麦大叔 发表于 2021/12/29 23:18:28 2021/12/29
【摘要】 文章目录 前言设备树两个结构体gpio_platform_datagpio_demo_device 两种方式轮询外部中断 总结附录 前言 前面是如何操作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		
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 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\n");

	ret = of_property_read_u32(np, "mode", &pdata->mode);
	if (ret < 0) {
		dev_err(dev, "can't get mode property\n");
	}
	ret = of_property_read_u32(np, "poll_time", &pdata->poll_ms);
	if (ret < 0) {
		dev_err(dev, "can't get poll_ms property\n");		
	}

	pdata->gpio_index = of_get_named_gpio(np,"input-gpio", 0);
	if (pdata->gpio_index < 0) {
		dev_err(dev, "can't get input gpio\n");
	}
	// debug parse device tree data
	dev_info(dev, "Success:mode is %d\n", pdata->mode);
	dev_info(dev, "Success:gpio index is %d\n", pdata->gpio_index);
	return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

两个结构体

gpio_platform_data

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

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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;
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

两种方式

在驱动的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\n");
	}
	...
	if (priv->pdata->mode == 0){
		gpio_demo_init_poll(priv); //轮询
	} else {
		gpio_demo_init_interrupt(priv);//中断
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

轮询

在轮询工作模式下,已经通过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\n");
		goto end;
	}
	if ( (value = gpio_get_value(gpio_index) ) == 0) {
		dev_info(di->dev,"get value is %d\n",value);
	}else{
		dev_info(di->dev,"get value is %d\n",value);
	}
	end:
	queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
			   msecs_to_jiffies(di->pdata->poll_ms));
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

外部中断

中断的申请和初始化在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);
	...
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

其中,每次外部发送一个下降沿信号,就会触发中断并进入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\n", __func__);
	return IRQ_HANDLED;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

总结

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

  • 通过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\n");

	ret = of_property_read_u32(np, "mode", &pdata->mode);
	if (ret < 0) {
		dev_err(dev, "can't get mode property\n");
	}
	ret = of_property_read_u32(np, "poll_time", &pdata->poll_ms);
	if (ret < 0) {
		dev_err(dev, "can't get poll_ms property\n");		
	}

	pdata->gpio_index = of_get_named_gpio(np,"input-gpio", 0);
	if (pdata->gpio_index < 0) {
		dev_err(dev, "can't get input gpio\n");
	}
	// debug parse device tree data
	dev_info(dev, "Success:mode is %d\n", pdata->mode);
	dev_info(dev, "Success:gpio index is %d\n", 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\n");
		goto end;
	}
	if ( (value = gpio_get_value(gpio_index) ) == 0) {
		dev_info(di->dev,"get value is %d\n",value);
	}else{
		dev_info(di->dev,"get value is %d\n",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\n", __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\n", __func__);
	//printk("%s\n",__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\n", __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\n",
				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\n",
				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\n");
	}

	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");

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233

文章来源: great.blog.csdn.net,作者:小麦大叔,版权归原作者所有,如需转载,请联系作者。

原文链接:great.blog.csdn.net/article/details/90042503

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200