-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathslirc_rpi.c
373 lines (315 loc) · 11.6 KB
/
slirc_rpi.c
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/***********************************************************************************************************************
*
* Simple LIRC Kernel Module for Raspberry Pi
*
* Copyright (C) 2015 Gergely Budai
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
**********************************************************************************************************************/
#define DRIVER_NAME "slirc_rpi"
#define DESCRIPTION "Simple LIRC Kernel Module"
#define AUTHOR "Gergely Budai"
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/kfifo.h>
#include <asm/uaccess.h>
// Bits FIFO
#define FIFO_SIZE 32
static DEFINE_KFIFO(bit_fifo, unsigned int, FIFO_SIZE);
// Fifo usage statistics
static unsigned int max_bit_fifo_used = 0;
// For blocking read
static DECLARE_WAIT_QUEUE_HEAD(file_read);
// IRQ
static int irq_num = 0;
// Device variables
static struct class* device_class = NULL;
static struct device* device_device = NULL;
static int device_major = 0;
/***********************************************************************************************************************
* Module Parameters
**********************************************************************************************************************/
// GPIO to use
static unsigned int gpio = 25;
module_param(gpio, uint, S_IRUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(gpio, "GPIO to use (default=25)");
/***********************************************************************************************************************
* IRQ Handler
**********************************************************************************************************************/
static irqreturn_t irq_handler(int i, void *blah, struct pt_regs *regs)
{
// Last timestamp for bit length calculation
static unsigned int lastTimeStamp = 0;
// Local variables
unsigned int pulseLength, timeStamp, retval, fifo_used;
struct timeval tv;
// Get Timestamp
do_gettimeofday(&tv);
timeStamp = (tv.tv_sec * 1000000) + tv.tv_usec;
// Calculate bit length
pulseLength = timeStamp - lastTimeStamp;
lastTimeStamp = timeStamp;
// Put pulse into FIFO
retval = kfifo_put(&bit_fifo, pulseLength);
// Provide statistics over fifo usage
fifo_used = kfifo_len(&bit_fifo);
if(fifo_used > max_bit_fifo_used) {
max_bit_fifo_used = fifo_used;
}
// Check return value
switch(retval) {
// Everything OK, wake up reader
case 1: {
wake_up_interruptible(&file_read);
}
break;
// FIFO Full
case 0: {
printk(KERN_WARNING DRIVER_NAME": fifo full, pulse missed\n");
}
break;
// Should not happen
default: {
printk(KERN_ERR DRIVER_NAME": kfifo_put() returned %u\n", retval);
}
break;
}
return IRQ_HANDLED;
}
/***********************************************************************************************************************
* Check if we have the right chipset
**********************************************************************************************************************/
static int __init is_right_chip(struct gpio_chip *chip, void *data)
{
if (strcmp(data, chip->label) == 0) {
return 1;
}
return 0;
}
/***********************************************************************************************************************
* Init GPIO Port
**********************************************************************************************************************/
static int __init init_port(void)
{
struct gpio_chip *gpiochip;
int retval;
// Find Chipset
gpiochip = gpiochip_find("pinctrl-bcm2835", is_right_chip);
if(!gpiochip) {
printk(KERN_ERR DRIVER_NAME": bcm2835 chip not found!\n");
retval = -ENODEV;
goto exit;
}
// Request GPIO Port
if(gpio_request(gpio, DRIVER_NAME)) {
printk(KERN_ERR DRIVER_NAME": cant claim gpio pin %d\n", gpio);
retval = -ENODEV;
goto exit;
}
// Set GPIO direction
gpiochip->direction_input(gpiochip, gpio);
// Recort IRQ number for later
irq_num = gpiochip->to_irq(gpiochip, gpio);
retval = 0;
exit:
return retval;
}
/***********************************************************************************************************************
* Deinit GPIO Port
**********************************************************************************************************************/
static void __exit uninit_port(void)
{
gpio_free(gpio);
}
/***********************************************************************************************************************
* Device file open
**********************************************************************************************************************/
static int device_open(struct inode* inode, struct file* filp)
{
int result;
// Request GPIO IRQ
result = request_irq(irq_num, (irq_handler_t)irq_handler,
IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING, DRIVER_NAME, (void*) 0);
// Process result
switch (result) {
// IRQ Already used (probably device already opened)
case -EBUSY: {
printk(KERN_ERR DRIVER_NAME": IRQ %d is busy\n", irq_num);
goto exit;
}
break;
// Bad IRQ
case -EINVAL: {
printk(KERN_ERR DRIVER_NAME": Bad irq number or handler\n");
goto exit;
}
break;
}
// Increment usage counter;
try_module_get(THIS_MODULE);
exit:
return result;
}
/***********************************************************************************************************************
* Device file close
**********************************************************************************************************************/
static int device_close(struct inode* inode, struct file* filp)
{
// Disable and free the IRQ
irq_set_irq_type(irq_num, 0);
disable_irq(irq_num);
free_irq(irq_num, (void *) 0);
// Decrement usage counter;
module_put(THIS_MODULE);
return 0;
}
/***********************************************************************************************************************
* Device file read
**********************************************************************************************************************/
static ssize_t device_read(struct file* filp, char __user *buffer, size_t length, loff_t* offset)
{
int retval;
unsigned int copied;
// Wait for event from interrupt
wait_event_interruptible(file_read, !kfifo_is_empty(&bit_fifo));
// Get bit from fifo
retval = kfifo_to_user(&bit_fifo, buffer, length, &copied);
return retval ? retval : copied;
}
/***********************************************************************************************************************
* File operations structure
**********************************************************************************************************************/
static struct file_operations fops = {
.read = device_read,
.open = device_open,
.release = device_close
};
/***********************************************************************************************************************
* Sysfs file with Fifo Statistics
**********************************************************************************************************************/
static ssize_t show_fifo_stats(struct device *dev, struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "Size: %2u\nMax: %2u\nCurr: %2u\n",
FIFO_SIZE, max_bit_fifo_used, kfifo_len(&bit_fifo));
}
static DEVICE_ATTR(fifo, S_IRUSR | S_IRGRP | S_IROTH, show_fifo_stats, NULL);
/***********************************************************************************************************************
* Initialize device file
**********************************************************************************************************************/
static int __init init_device(void)
{
unsigned int retval;
// First, see if we can dynamically allocate a major for our device
device_major = register_chrdev(0, DRIVER_NAME, &fops);
if (device_major < 0) {
printk(KERN_ERR DRIVER_NAME": failed to register device\n");
retval = device_major;
goto register_chrdev_failed;
}
// Tie device to a virtual class
device_class = class_create(THIS_MODULE, DRIVER_NAME);
if (IS_ERR(device_class)) {
printk(KERN_ERR DRIVER_NAME": failed to register device class\n");
retval = PTR_ERR(device_class);
goto class_create_failed;
}
// Create device file
device_device = device_create(device_class, NULL, MKDEV(device_major, 0), NULL, DRIVER_NAME);
if (IS_ERR(device_device)) {
printk(KERN_ERR DRIVER_NAME": failed to create device\n");
retval = PTR_ERR(device_device);
goto device_create_failed;
}
// Sysfs file for fifo stats
if(device_create_file(device_device, &dev_attr_fifo) < 0) {
printk(KERN_WARNING DRIVER_NAME": could not create sysfs fifo stats file\n");
}
// Everything OK
retval = 0;
goto exit;
// Error unitialisation
device_create_failed:
class_unregister(device_class);
class_destroy(device_class);
class_create_failed:
unregister_chrdev(device_major, DRIVER_NAME);
register_chrdev_failed:
// Exit point
exit:
return retval;
}
/***********************************************************************************************************************
* Deinitialize device file
**********************************************************************************************************************/
static void __exit uninit_device(void)
{
device_remove_file(device_device, &dev_attr_fifo);
device_destroy(device_class, MKDEV(device_major, 0));
class_unregister(device_class);
class_destroy(device_class);
unregister_chrdev(device_major, DRIVER_NAME);
}
/***********************************************************************************************************************
* Module Entry
**********************************************************************************************************************/
static int __init init_main(void)
{
int result;
// Init port
result = init_port();
if(result) {
goto init_port_failed;
}
// Init device file
result = init_device();
if(result) {
goto init_device_failed;
}
// Init OK, print info message
printk(KERN_INFO DRIVER_NAME": driver installed on GPIO %u\n", gpio);
result = 0;
goto exit;
init_device_failed:
uninit_port();
init_port_failed:
exit:
return result;
}
/***********************************************************************************************************************
* Module Exit
**********************************************************************************************************************/
static void __exit exit_main(void)
{
// Deinit device file
uninit_device();
// Deinit GPIO Port
uninit_port();
printk(KERN_INFO DRIVER_NAME": driver uninstalled\n");
}
// Init and Exit functions
module_init(init_main);
module_exit(exit_main);
// Module informations
MODULE_LICENSE("GPL");
MODULE_AUTHOR(AUTHOR);
MODULE_DESCRIPTION(DESCRIPTION);