2015-12-17 09:29:22 +00:00
|
|
|
From 04646f6a5ffec4a5cd55999e85df839278860cb1 Mon Sep 17 00:00:00 2001
|
2015-07-17 12:48:39 +00:00
|
|
|
From: Aron Szabo <aron@aron.ws>
|
|
|
|
Date: Sat, 16 Jun 2012 12:15:55 +0200
|
2015-12-17 09:29:22 +00:00
|
|
|
Subject: [PATCH 016/222] lirc: added support for RaspberryPi GPIO
|
2015-07-17 12:48:39 +00:00
|
|
|
|
|
|
|
lirc_rpi: Use read_current_timer to determine transmitter delay. Thanks to jjmz and others
|
|
|
|
See: https://github.com/raspberrypi/linux/issues/525
|
|
|
|
|
|
|
|
lirc: Remove restriction on gpio pins that can be used with lirc
|
|
|
|
|
|
|
|
Compute Module, for example could use different pins
|
|
|
|
|
|
|
|
lirc_rpi: Add parameter to specify input pin pull
|
|
|
|
|
|
|
|
Depending on the connected IR circuitry it might be desirable to change the
|
|
|
|
gpios internal pull from it pull-down default behaviour. Add a module
|
|
|
|
parameter to allow the user to set it explicitly.
|
|
|
|
|
|
|
|
Signed-off-by: Julian Scheel <julian@jusst.de>
|
|
|
|
|
|
|
|
lirc-rpi: Use the higher-level irq control functions
|
|
|
|
|
|
|
|
This module used to access the irq_chip methods of the
|
|
|
|
gpio controller directly, rather than going through the
|
|
|
|
standard enable_irq/irq_set_irq_type functions. This
|
|
|
|
caused problems on pinctrl-bcm2835 which only implements
|
|
|
|
the irq_enable/disable methods and not irq_unmask/mask.
|
|
|
|
|
|
|
|
lirc-rpi: Correct the interrupt usage
|
|
|
|
|
|
|
|
1) Correct the use of enable_irq (i.e. don't call it so often)
|
|
|
|
2) Correct the shutdown sequence.
|
|
|
|
3) Avoid a bcm2708_gpio driver quirk by setting the irq flags earlier
|
|
|
|
|
|
|
|
lirc-rpi: use getnstimeofday instead of read_current_timer
|
|
|
|
|
|
|
|
read_current_timer isn't guaranteed to return values in
|
|
|
|
microseconds, and indeed it doesn't on a Pi2.
|
|
|
|
|
|
|
|
Issue: linux#827
|
|
|
|
|
|
|
|
lirc-rpi: Add device tree support, and a suitable overlay
|
|
|
|
|
|
|
|
The overlay supports DT parameters that match the old module
|
|
|
|
parameters, except that gpio_in_pull should be set using the
|
|
|
|
strings "up", "down" or "off".
|
|
|
|
|
|
|
|
lirc-rpi: Also support pinctrl-bcm2835 in non-DT mode
|
|
|
|
---
|
|
|
|
drivers/staging/media/lirc/Kconfig | 6 +
|
|
|
|
drivers/staging/media/lirc/Makefile | 1 +
|
|
|
|
drivers/staging/media/lirc/lirc_rpi.c | 765 ++++++++++++++++++++++++++++++++++
|
|
|
|
3 files changed, 772 insertions(+)
|
|
|
|
create mode 100644 drivers/staging/media/lirc/lirc_rpi.c
|
|
|
|
|
|
|
|
--- a/drivers/staging/media/lirc/Kconfig
|
|
|
|
+++ b/drivers/staging/media/lirc/Kconfig
|
|
|
|
@@ -32,6 +32,12 @@ config LIRC_PARALLEL
|
|
|
|
help
|
|
|
|
Driver for Homebrew Parallel Port Receivers
|
|
|
|
|
|
|
|
+config LIRC_RPI
|
|
|
|
+ tristate "Homebrew GPIO Port Receiver/Transmitter for the RaspberryPi"
|
|
|
|
+ depends on LIRC
|
|
|
|
+ help
|
|
|
|
+ Driver for Homebrew GPIO Port Receiver/Transmitter for the RaspberryPi
|
|
|
|
+
|
|
|
|
config LIRC_SASEM
|
|
|
|
tristate "Sasem USB IR Remote"
|
|
|
|
depends on LIRC && USB
|
|
|
|
--- a/drivers/staging/media/lirc/Makefile
|
|
|
|
+++ b/drivers/staging/media/lirc/Makefile
|
|
|
|
@@ -6,6 +6,7 @@
|
|
|
|
obj-$(CONFIG_LIRC_BT829) += lirc_bt829.o
|
|
|
|
obj-$(CONFIG_LIRC_IMON) += lirc_imon.o
|
|
|
|
obj-$(CONFIG_LIRC_PARALLEL) += lirc_parallel.o
|
|
|
|
+obj-$(CONFIG_LIRC_RPI) += lirc_rpi.o
|
|
|
|
obj-$(CONFIG_LIRC_SASEM) += lirc_sasem.o
|
|
|
|
obj-$(CONFIG_LIRC_SERIAL) += lirc_serial.o
|
|
|
|
obj-$(CONFIG_LIRC_SIR) += lirc_sir.o
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/staging/media/lirc/lirc_rpi.c
|
|
|
|
@@ -0,0 +1,765 @@
|
|
|
|
+/*
|
|
|
|
+ * lirc_rpi.c
|
|
|
|
+ *
|
|
|
|
+ * lirc_rpi - Device driver that records pulse- and pause-lengths
|
|
|
|
+ * (space-lengths) (just like the lirc_serial driver does)
|
|
|
|
+ * between GPIO interrupt events on the Raspberry Pi.
|
|
|
|
+ * Lots of code has been taken from the lirc_serial module,
|
|
|
|
+ * so I would like say thanks to the authors.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2012 Aron Robert Szabo <aron@reon.hu>,
|
|
|
|
+ * Michael Bishop <cleverca22@gmail.com>
|
|
|
|
+ * 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 2 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, write to the Free Software
|
|
|
|
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/errno.h>
|
|
|
|
+#include <linux/interrupt.h>
|
|
|
|
+#include <linux/sched.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/time.h>
|
|
|
|
+#include <linux/timex.h>
|
|
|
|
+#include <linux/timekeeping.h>
|
|
|
|
+#include <linux/string.h>
|
|
|
|
+#include <linux/delay.h>
|
|
|
|
+#include <linux/platform_device.h>
|
|
|
|
+#include <linux/irq.h>
|
|
|
|
+#include <linux/spinlock.h>
|
|
|
|
+#include <media/lirc.h>
|
|
|
|
+#include <media/lirc_dev.h>
|
|
|
|
+#include <mach/gpio.h>
|
|
|
|
+#include <linux/gpio.h>
|
|
|
|
+#include <linux/of_platform.h>
|
|
|
|
+
|
|
|
|
+#include <linux/platform_data/bcm2708.h>
|
|
|
|
+
|
|
|
|
+#define LIRC_DRIVER_NAME "lirc_rpi"
|
|
|
|
+#define RBUF_LEN 256
|
|
|
|
+#define LIRC_TRANSMITTER_LATENCY 50
|
|
|
|
+
|
|
|
|
+#ifndef MAX_UDELAY_MS
|
|
|
|
+#define MAX_UDELAY_US 5000
|
|
|
|
+#else
|
|
|
|
+#define MAX_UDELAY_US (MAX_UDELAY_MS*1000)
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+#define dprintk(fmt, args...) \
|
|
|
|
+ do { \
|
|
|
|
+ if (debug) \
|
|
|
|
+ printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \
|
|
|
|
+ fmt, ## args); \
|
|
|
|
+ } while (0)
|
|
|
|
+
|
|
|
|
+/* module parameters */
|
|
|
|
+
|
|
|
|
+/* set the default GPIO input pin */
|
|
|
|
+static int gpio_in_pin = 18;
|
|
|
|
+/* set the default pull behaviour for input pin */
|
|
|
|
+static int gpio_in_pull = BCM2708_PULL_DOWN;
|
|
|
|
+/* set the default GPIO output pin */
|
|
|
|
+static int gpio_out_pin = 17;
|
|
|
|
+/* enable debugging messages */
|
|
|
|
+static bool debug;
|
|
|
|
+/* -1 = auto, 0 = active high, 1 = active low */
|
|
|
|
+static int sense = -1;
|
|
|
|
+/* use softcarrier by default */
|
|
|
|
+static bool softcarrier = 1;
|
|
|
|
+/* 0 = do not invert output, 1 = invert output */
|
|
|
|
+static bool invert = 0;
|
|
|
|
+
|
|
|
|
+struct gpio_chip *gpiochip;
|
|
|
|
+static int irq_num;
|
|
|
|
+
|
|
|
|
+/* forward declarations */
|
|
|
|
+static long send_pulse(unsigned long length);
|
|
|
|
+static void send_space(long length);
|
|
|
|
+static void lirc_rpi_exit(void);
|
|
|
|
+
|
|
|
|
+static struct platform_device *lirc_rpi_dev;
|
|
|
|
+static struct timeval lasttv = { 0, 0 };
|
|
|
|
+static struct lirc_buffer rbuf;
|
|
|
|
+static spinlock_t lock;
|
|
|
|
+
|
|
|
|
+/* initialized/set in init_timing_params() */
|
|
|
|
+static unsigned int freq = 38000;
|
|
|
|
+static unsigned int duty_cycle = 50;
|
|
|
|
+static unsigned long period;
|
|
|
|
+static unsigned long pulse_width;
|
|
|
|
+static unsigned long space_width;
|
|
|
|
+
|
|
|
|
+static void safe_udelay(unsigned long usecs)
|
|
|
|
+{
|
|
|
|
+ while (usecs > MAX_UDELAY_US) {
|
|
|
|
+ udelay(MAX_UDELAY_US);
|
|
|
|
+ usecs -= MAX_UDELAY_US;
|
|
|
|
+ }
|
|
|
|
+ udelay(usecs);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static unsigned long read_current_us(void)
|
|
|
|
+{
|
|
|
|
+ struct timespec now;
|
|
|
|
+ getnstimeofday(&now);
|
|
|
|
+ return (now.tv_sec * 1000000) + (now.tv_nsec/1000);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int init_timing_params(unsigned int new_duty_cycle,
|
|
|
|
+ unsigned int new_freq)
|
|
|
|
+{
|
|
|
|
+ if (1000 * 1000000L / new_freq * new_duty_cycle / 100 <=
|
|
|
|
+ LIRC_TRANSMITTER_LATENCY)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ if (1000 * 1000000L / new_freq * (100 - new_duty_cycle) / 100 <=
|
|
|
|
+ LIRC_TRANSMITTER_LATENCY)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ duty_cycle = new_duty_cycle;
|
|
|
|
+ freq = new_freq;
|
|
|
|
+ period = 1000 * 1000000L / freq;
|
|
|
|
+ pulse_width = period * duty_cycle / 100;
|
|
|
|
+ space_width = period - pulse_width;
|
|
|
|
+ dprintk("in init_timing_params, freq=%d pulse=%ld, "
|
|
|
|
+ "space=%ld\n", freq, pulse_width, space_width);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static long send_pulse_softcarrier(unsigned long length)
|
|
|
|
+{
|
|
|
|
+ int flag;
|
|
|
|
+ unsigned long actual, target;
|
|
|
|
+ unsigned long actual_us, initial_us, target_us;
|
|
|
|
+
|
|
|
|
+ length *= 1000;
|
|
|
|
+
|
|
|
|
+ actual = 0; target = 0; flag = 0;
|
|
|
|
+ actual_us = read_current_us();
|
|
|
|
+
|
|
|
|
+ while (actual < length) {
|
|
|
|
+ if (flag) {
|
|
|
|
+ gpiochip->set(gpiochip, gpio_out_pin, invert);
|
|
|
|
+ target += space_width;
|
|
|
|
+ } else {
|
|
|
|
+ gpiochip->set(gpiochip, gpio_out_pin, !invert);
|
|
|
|
+ target += pulse_width;
|
|
|
|
+ }
|
|
|
|
+ initial_us = actual_us;
|
|
|
|
+ target_us = actual_us + (target - actual) / 1000;
|
|
|
|
+ /*
|
|
|
|
+ * Note - we've checked in ioctl that the pulse/space
|
|
|
|
+ * widths are big enough so that d is > 0
|
|
|
|
+ */
|
|
|
|
+ if ((int)(target_us - actual_us) > 0)
|
|
|
|
+ udelay(target_us - actual_us);
|
|
|
|
+ actual_us = read_current_us();
|
|
|
|
+ actual += (actual_us - initial_us) * 1000;
|
|
|
|
+ flag = !flag;
|
|
|
|
+ }
|
|
|
|
+ return (actual-length) / 1000;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static long send_pulse(unsigned long length)
|
|
|
|
+{
|
|
|
|
+ if (length <= 0)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ if (softcarrier) {
|
|
|
|
+ return send_pulse_softcarrier(length);
|
|
|
|
+ } else {
|
|
|
|
+ gpiochip->set(gpiochip, gpio_out_pin, !invert);
|
|
|
|
+ safe_udelay(length);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void send_space(long length)
|
|
|
|
+{
|
|
|
|
+ gpiochip->set(gpiochip, gpio_out_pin, invert);
|
|
|
|
+ if (length <= 0)
|
|
|
|
+ return;
|
|
|
|
+ safe_udelay(length);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void rbwrite(int l)
|
|
|
|
+{
|
|
|
|
+ if (lirc_buffer_full(&rbuf)) {
|
|
|
|
+ /* no new signals will be accepted */
|
|
|
|
+ dprintk("Buffer overrun\n");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ lirc_buffer_write(&rbuf, (void *)&l);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void frbwrite(int l)
|
|
|
|
+{
|
|
|
|
+ /* simple noise filter */
|
|
|
|
+ static int pulse, space;
|
|
|
|
+ static unsigned int ptr;
|
|
|
|
+
|
|
|
|
+ if (ptr > 0 && (l & PULSE_BIT)) {
|
|
|
|
+ pulse += l & PULSE_MASK;
|
|
|
|
+ if (pulse > 250) {
|
|
|
|
+ rbwrite(space);
|
|
|
|
+ rbwrite(pulse | PULSE_BIT);
|
|
|
|
+ ptr = 0;
|
|
|
|
+ pulse = 0;
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (!(l & PULSE_BIT)) {
|
|
|
|
+ if (ptr == 0) {
|
|
|
|
+ if (l > 20000) {
|
|
|
|
+ space = l;
|
|
|
|
+ ptr++;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if (l > 20000) {
|
|
|
|
+ space += pulse;
|
|
|
|
+ if (space > PULSE_MASK)
|
|
|
|
+ space = PULSE_MASK;
|
|
|
|
+ space += l;
|
|
|
|
+ if (space > PULSE_MASK)
|
|
|
|
+ space = PULSE_MASK;
|
|
|
|
+ pulse = 0;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ rbwrite(space);
|
|
|
|
+ rbwrite(pulse | PULSE_BIT);
|
|
|
|
+ ptr = 0;
|
|
|
|
+ pulse = 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ rbwrite(l);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static irqreturn_t irq_handler(int i, void *blah, struct pt_regs *regs)
|
|
|
|
+{
|
|
|
|
+ struct timeval tv;
|
|
|
|
+ long deltv;
|
|
|
|
+ int data;
|
|
|
|
+ int signal;
|
|
|
|
+
|
|
|
|
+ /* use the GPIO signal level */
|
|
|
|
+ signal = gpiochip->get(gpiochip, gpio_in_pin);
|
|
|
|
+
|
|
|
|
+ if (sense != -1) {
|
|
|
|
+ /* get current time */
|
|
|
|
+ do_gettimeofday(&tv);
|
|
|
|
+
|
|
|
|
+ /* calc time since last interrupt in microseconds */
|
|
|
|
+ deltv = tv.tv_sec-lasttv.tv_sec;
|
|
|
|
+ if (tv.tv_sec < lasttv.tv_sec ||
|
|
|
|
+ (tv.tv_sec == lasttv.tv_sec &&
|
|
|
|
+ tv.tv_usec < lasttv.tv_usec)) {
|
|
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME
|
|
|
|
+ ": AIEEEE: your clock just jumped backwards\n");
|
|
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME
|
|
|
|
+ ": %d %d %lx %lx %lx %lx\n", signal, sense,
|
|
|
|
+ tv.tv_sec, lasttv.tv_sec,
|
|
|
|
+ tv.tv_usec, lasttv.tv_usec);
|
|
|
|
+ data = PULSE_MASK;
|
|
|
|
+ } else if (deltv > 15) {
|
|
|
|
+ data = PULSE_MASK; /* really long time */
|
|
|
|
+ if (!(signal^sense)) {
|
|
|
|
+ /* sanity check */
|
|
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME
|
|
|
|
+ ": AIEEEE: %d %d %lx %lx %lx %lx\n",
|
|
|
|
+ signal, sense, tv.tv_sec, lasttv.tv_sec,
|
|
|
|
+ tv.tv_usec, lasttv.tv_usec);
|
|
|
|
+ /*
|
|
|
|
+ * detecting pulse while this
|
|
|
|
+ * MUST be a space!
|
|
|
|
+ */
|
|
|
|
+ sense = sense ? 0 : 1;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ data = (int) (deltv*1000000 +
|
|
|
|
+ (tv.tv_usec - lasttv.tv_usec));
|
|
|
|
+ }
|
|
|
|
+ frbwrite(signal^sense ? data : (data|PULSE_BIT));
|
|
|
|
+ lasttv = tv;
|
|
|
|
+ wake_up_interruptible(&rbuf.wait_poll);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return IRQ_HANDLED;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int is_right_chip(struct gpio_chip *chip, void *data)
|
|
|
|
+{
|
|
|
|
+ dprintk("is_right_chip %s %d\n", chip->label, strcmp(data, chip->label));
|
|
|
|
+
|
|
|
|
+ if (strcmp(data, chip->label) == 0)
|
|
|
|
+ return 1;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline int read_bool_property(const struct device_node *np,
|
|
|
|
+ const char *propname,
|
|
|
|
+ bool *out_value)
|
|
|
|
+{
|
|
|
|
+ u32 value = 0;
|
|
|
|
+ int err = of_property_read_u32(np, propname, &value);
|
|
|
|
+ if (err == 0)
|
|
|
|
+ *out_value = (value != 0);
|
|
|
|
+ return err;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void read_pin_settings(struct device_node *node)
|
|
|
|
+{
|
|
|
|
+ u32 pin;
|
|
|
|
+ int index;
|
|
|
|
+
|
|
|
|
+ for (index = 0;
|
|
|
|
+ of_property_read_u32_index(
|
|
|
|
+ node,
|
|
|
|
+ "brcm,pins",
|
|
|
|
+ index,
|
|
|
|
+ &pin) == 0;
|
|
|
|
+ index++) {
|
|
|
|
+ u32 function;
|
|
|
|
+ int err;
|
|
|
|
+ err = of_property_read_u32_index(
|
|
|
|
+ node,
|
|
|
|
+ "brcm,function",
|
|
|
|
+ index,
|
|
|
|
+ &function);
|
|
|
|
+ if (err == 0) {
|
|
|
|
+ if (function == 1) /* Output */
|
|
|
|
+ gpio_out_pin = pin;
|
|
|
|
+ else if (function == 0) /* Input */
|
|
|
|
+ gpio_in_pin = pin;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int init_port(void)
|
|
|
|
+{
|
|
|
|
+ int i, nlow, nhigh, ret;
|
|
|
|
+ struct device_node *node;
|
|
|
|
+
|
|
|
|
+ node = lirc_rpi_dev->dev.of_node;
|
|
|
|
+
|
|
|
|
+ gpiochip = gpiochip_find("bcm2708_gpio", is_right_chip);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Because of the lack of a setpull function, only support
|
|
|
|
+ * pinctrl-bcm2835 if using device tree.
|
|
|
|
+ */
|
|
|
|
+ if (!gpiochip && node)
|
|
|
|
+ gpiochip = gpiochip_find("pinctrl-bcm2835", is_right_chip);
|
|
|
|
+
|
|
|
|
+ if (!gpiochip) {
|
|
|
|
+ pr_err(LIRC_DRIVER_NAME ": gpio chip not found!\n");
|
|
|
|
+ return -ENODEV;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (node) {
|
|
|
|
+ struct device_node *pins_node;
|
|
|
|
+
|
|
|
|
+ pins_node = of_parse_phandle(node, "pinctrl-0", 0);
|
|
|
|
+ if (!pins_node) {
|
|
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
|
|
+ ": pinctrl settings not found!\n");
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto exit_init_port;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ read_pin_settings(pins_node);
|
|
|
|
+
|
|
|
|
+ of_property_read_u32(node, "rpi,sense", &sense);
|
|
|
|
+
|
|
|
|
+ read_bool_property(node, "rpi,softcarrier", &softcarrier);
|
|
|
|
+
|
|
|
|
+ read_bool_property(node, "rpi,invert", &invert);
|
|
|
|
+
|
|
|
|
+ read_bool_property(node, "rpi,debug", &debug);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ if (gpio_in_pin >= BCM2708_NR_GPIOS ||
|
|
|
|
+ gpio_out_pin >= BCM2708_NR_GPIOS) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
|
|
+ ": invalid GPIO pin(s) specified!\n");
|
|
|
|
+ goto exit_init_port;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (gpio_request(gpio_out_pin, LIRC_DRIVER_NAME " ir/out")) {
|
|
|
|
+ printk(KERN_ALERT LIRC_DRIVER_NAME
|
|
|
|
+ ": cant claim gpio pin %d\n", gpio_out_pin);
|
|
|
|
+ ret = -ENODEV;
|
|
|
|
+ goto exit_init_port;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (gpio_request(gpio_in_pin, LIRC_DRIVER_NAME " ir/in")) {
|
|
|
|
+ printk(KERN_ALERT LIRC_DRIVER_NAME
|
|
|
|
+ ": cant claim gpio pin %d\n", gpio_in_pin);
|
|
|
|
+ ret = -ENODEV;
|
|
|
|
+ goto exit_gpio_free_out_pin;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bcm2708_gpio_setpull(gpiochip, gpio_in_pin, gpio_in_pull);
|
|
|
|
+ gpiochip->direction_input(gpiochip, gpio_in_pin);
|
|
|
|
+ gpiochip->direction_output(gpiochip, gpio_out_pin, 1);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ gpiochip->set(gpiochip, gpio_out_pin, invert);
|
|
|
|
+
|
|
|
|
+ irq_num = gpiochip->to_irq(gpiochip, gpio_in_pin);
|
|
|
|
+ dprintk("to_irq %d\n", irq_num);
|
|
|
|
+
|
|
|
|
+ /* if pin is high, then this must be an active low receiver. */
|
|
|
|
+ if (sense == -1) {
|
|
|
|
+ /* wait 1/2 sec for the power supply */
|
|
|
|
+ msleep(500);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * probe 9 times every 0.04s, collect "votes" for
|
|
|
|
+ * active high/low
|
|
|
|
+ */
|
|
|
|
+ nlow = 0;
|
|
|
|
+ nhigh = 0;
|
|
|
|
+ for (i = 0; i < 9; i++) {
|
|
|
|
+ if (gpiochip->get(gpiochip, gpio_in_pin))
|
|
|
|
+ nlow++;
|
|
|
|
+ else
|
|
|
|
+ nhigh++;
|
|
|
|
+ msleep(40);
|
|
|
|
+ }
|
|
|
|
+ sense = (nlow >= nhigh ? 1 : 0);
|
|
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
|
|
+ ": auto-detected active %s receiver on GPIO pin %d\n",
|
|
|
|
+ sense ? "low" : "high", gpio_in_pin);
|
|
|
|
+ } else {
|
|
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
|
|
+ ": manually using active %s receiver on GPIO pin %d\n",
|
|
|
|
+ sense ? "low" : "high", gpio_in_pin);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ exit_gpio_free_out_pin:
|
|
|
|
+ gpio_free(gpio_out_pin);
|
|
|
|
+
|
|
|
|
+ exit_init_port:
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// called when the character device is opened
|
|
|
|
+static int set_use_inc(void *data)
|
|
|
|
+{
|
|
|
|
+ int result;
|
|
|
|
+
|
|
|
|
+ /* initialize timestamp */
|
|
|
|
+ do_gettimeofday(&lasttv);
|
|
|
|
+
|
|
|
|
+ result = request_irq(irq_num,
|
|
|
|
+ (irq_handler_t) irq_handler,
|
|
|
|
+ IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING,
|
|
|
|
+ LIRC_DRIVER_NAME, (void*) 0);
|
|
|
|
+
|
|
|
|
+ switch (result) {
|
|
|
|
+ case -EBUSY:
|
|
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
|
|
+ ": IRQ %d is busy\n",
|
|
|
|
+ irq_num);
|
|
|
|
+ return -EBUSY;
|
|
|
|
+ case -EINVAL:
|
|
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
|
|
+ ": Bad irq number or handler\n");
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ default:
|
|
|
|
+ dprintk("Interrupt %d obtained\n",
|
|
|
|
+ irq_num);
|
|
|
|
+ break;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ /* initialize pulse/space widths */
|
|
|
|
+ init_timing_params(duty_cycle, freq);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void set_use_dec(void *data)
|
|
|
|
+{
|
|
|
|
+ /* GPIO Pin Falling/Rising Edge Detect Disable */
|
|
|
|
+ irq_set_irq_type(irq_num, 0);
|
|
|
|
+ disable_irq(irq_num);
|
|
|
|
+
|
|
|
|
+ free_irq(irq_num, (void *) 0);
|
|
|
|
+
|
|
|
|
+ dprintk(KERN_INFO LIRC_DRIVER_NAME
|
|
|
|
+ ": freed IRQ %d\n", irq_num);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static ssize_t lirc_write(struct file *file, const char *buf,
|
|
|
|
+ size_t n, loff_t *ppos)
|
|
|
|
+{
|
|
|
|
+ int i, count;
|
|
|
|
+ unsigned long flags;
|
|
|
|
+ long delta = 0;
|
|
|
|
+ int *wbuf;
|
|
|
|
+
|
|
|
|
+ count = n / sizeof(int);
|
|
|
|
+ if (n % sizeof(int) || count % 2 == 0)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ wbuf = memdup_user(buf, n);
|
|
|
|
+ if (IS_ERR(wbuf))
|
|
|
|
+ return PTR_ERR(wbuf);
|
|
|
|
+ spin_lock_irqsave(&lock, flags);
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < count; i++) {
|
|
|
|
+ if (i%2)
|
|
|
|
+ send_space(wbuf[i] - delta);
|
|
|
|
+ else
|
|
|
|
+ delta = send_pulse(wbuf[i]);
|
|
|
|
+ }
|
|
|
|
+ gpiochip->set(gpiochip, gpio_out_pin, invert);
|
|
|
|
+
|
|
|
|
+ spin_unlock_irqrestore(&lock, flags);
|
|
|
|
+ kfree(wbuf);
|
|
|
|
+ return n;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
|
|
|
|
+{
|
|
|
|
+ int result;
|
|
|
|
+ __u32 value;
|
|
|
|
+
|
|
|
|
+ switch (cmd) {
|
|
|
|
+ case LIRC_GET_SEND_MODE:
|
|
|
|
+ return -ENOIOCTLCMD;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LIRC_SET_SEND_MODE:
|
|
|
|
+ result = get_user(value, (__u32 *) arg);
|
|
|
|
+ if (result)
|
|
|
|
+ return result;
|
|
|
|
+ /* only LIRC_MODE_PULSE supported */
|
|
|
|
+ if (value != LIRC_MODE_PULSE)
|
|
|
|
+ return -ENOSYS;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LIRC_GET_LENGTH:
|
|
|
|
+ return -ENOSYS;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LIRC_SET_SEND_DUTY_CYCLE:
|
|
|
|
+ dprintk("SET_SEND_DUTY_CYCLE\n");
|
|
|
|
+ result = get_user(value, (__u32 *) arg);
|
|
|
|
+ if (result)
|
|
|
|
+ return result;
|
|
|
|
+ if (value <= 0 || value > 100)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ return init_timing_params(value, freq);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LIRC_SET_SEND_CARRIER:
|
|
|
|
+ dprintk("SET_SEND_CARRIER\n");
|
|
|
|
+ result = get_user(value, (__u32 *) arg);
|
|
|
|
+ if (result)
|
|
|
|
+ return result;
|
|
|
|
+ if (value > 500000 || value < 20000)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ return init_timing_params(duty_cycle, value);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ return lirc_dev_fop_ioctl(filep, cmd, arg);
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct file_operations lirc_fops = {
|
|
|
|
+ .owner = THIS_MODULE,
|
|
|
|
+ .write = lirc_write,
|
|
|
|
+ .unlocked_ioctl = lirc_ioctl,
|
|
|
|
+ .read = lirc_dev_fop_read,
|
|
|
|
+ .poll = lirc_dev_fop_poll,
|
|
|
|
+ .open = lirc_dev_fop_open,
|
|
|
|
+ .release = lirc_dev_fop_close,
|
|
|
|
+ .llseek = no_llseek,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static struct lirc_driver driver = {
|
|
|
|
+ .name = LIRC_DRIVER_NAME,
|
|
|
|
+ .minor = -1,
|
|
|
|
+ .code_length = 1,
|
|
|
|
+ .sample_rate = 0,
|
|
|
|
+ .data = NULL,
|
|
|
|
+ .add_to_buf = NULL,
|
|
|
|
+ .rbuf = &rbuf,
|
|
|
|
+ .set_use_inc = set_use_inc,
|
|
|
|
+ .set_use_dec = set_use_dec,
|
|
|
|
+ .fops = &lirc_fops,
|
|
|
|
+ .dev = NULL,
|
|
|
|
+ .owner = THIS_MODULE,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct of_device_id lirc_rpi_of_match[] = {
|
|
|
|
+ { .compatible = "rpi,lirc-rpi", },
|
|
|
|
+ {},
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(of, lirc_rpi_of_match);
|
|
|
|
+
|
|
|
|
+static struct platform_driver lirc_rpi_driver = {
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = LIRC_DRIVER_NAME,
|
|
|
|
+ .owner = THIS_MODULE,
|
|
|
|
+ .of_match_table = of_match_ptr(lirc_rpi_of_match),
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int __init lirc_rpi_init(void)
|
|
|
|
+{
|
|
|
|
+ struct device_node *node;
|
|
|
|
+ int result;
|
|
|
|
+
|
|
|
|
+ /* Init read buffer. */
|
|
|
|
+ result = lirc_buffer_init(&rbuf, sizeof(int), RBUF_LEN);
|
|
|
|
+ if (result < 0)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ result = platform_driver_register(&lirc_rpi_driver);
|
|
|
|
+ if (result) {
|
|
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
|
|
+ ": lirc register returned %d\n", result);
|
|
|
|
+ goto exit_buffer_free;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ node = of_find_compatible_node(NULL, NULL,
|
|
|
|
+ lirc_rpi_of_match[0].compatible);
|
|
|
|
+
|
|
|
|
+ if (node) {
|
|
|
|
+ /* DT-enabled */
|
|
|
|
+ lirc_rpi_dev = of_find_device_by_node(node);
|
|
|
|
+ WARN_ON(lirc_rpi_dev->dev.of_node != node);
|
|
|
|
+ of_node_put(node);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ lirc_rpi_dev = platform_device_alloc(LIRC_DRIVER_NAME, 0);
|
|
|
|
+ if (!lirc_rpi_dev) {
|
|
|
|
+ result = -ENOMEM;
|
|
|
|
+ goto exit_driver_unregister;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ result = platform_device_add(lirc_rpi_dev);
|
|
|
|
+ if (result)
|
|
|
|
+ goto exit_device_put;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ exit_device_put:
|
|
|
|
+ platform_device_put(lirc_rpi_dev);
|
|
|
|
+
|
|
|
|
+ exit_driver_unregister:
|
|
|
|
+ platform_driver_unregister(&lirc_rpi_driver);
|
|
|
|
+
|
|
|
|
+ exit_buffer_free:
|
|
|
|
+ lirc_buffer_free(&rbuf);
|
|
|
|
+
|
|
|
|
+ return result;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void lirc_rpi_exit(void)
|
|
|
|
+{
|
|
|
|
+ if (!lirc_rpi_dev->dev.of_node)
|
|
|
|
+ platform_device_unregister(lirc_rpi_dev);
|
|
|
|
+ platform_driver_unregister(&lirc_rpi_driver);
|
|
|
|
+ lirc_buffer_free(&rbuf);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int __init lirc_rpi_init_module(void)
|
|
|
|
+{
|
|
|
|
+ int result;
|
|
|
|
+
|
|
|
|
+ result = lirc_rpi_init();
|
|
|
|
+ if (result)
|
|
|
|
+ return result;
|
|
|
|
+
|
|
|
|
+ result = init_port();
|
|
|
|
+ if (result < 0)
|
|
|
|
+ goto exit_rpi;
|
|
|
|
+
|
|
|
|
+ driver.features = LIRC_CAN_SET_SEND_DUTY_CYCLE |
|
|
|
|
+ LIRC_CAN_SET_SEND_CARRIER |
|
|
|
|
+ LIRC_CAN_SEND_PULSE |
|
|
|
|
+ LIRC_CAN_REC_MODE2;
|
|
|
|
+
|
|
|
|
+ driver.dev = &lirc_rpi_dev->dev;
|
|
|
|
+ driver.minor = lirc_register_driver(&driver);
|
|
|
|
+
|
|
|
|
+ if (driver.minor < 0) {
|
|
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
|
|
+ ": device registration failed with %d\n", result);
|
|
|
|
+ result = -EIO;
|
|
|
|
+ goto exit_rpi;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": driver registered!\n");
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ exit_rpi:
|
|
|
|
+ lirc_rpi_exit();
|
|
|
|
+
|
|
|
|
+ return result;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __exit lirc_rpi_exit_module(void)
|
|
|
|
+{
|
|
|
|
+ lirc_unregister_driver(driver.minor);
|
|
|
|
+
|
|
|
|
+ gpio_free(gpio_out_pin);
|
|
|
|
+ gpio_free(gpio_in_pin);
|
|
|
|
+
|
|
|
|
+ lirc_rpi_exit();
|
|
|
|
+
|
|
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": cleaned up module\n");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+module_init(lirc_rpi_init_module);
|
|
|
|
+module_exit(lirc_rpi_exit_module);
|
|
|
|
+
|
|
|
|
+MODULE_DESCRIPTION("Infra-red receiver and blaster driver for Raspberry Pi GPIO.");
|
|
|
|
+MODULE_AUTHOR("Aron Robert Szabo <aron@reon.hu>");
|
|
|
|
+MODULE_AUTHOR("Michael Bishop <cleverca22@gmail.com>");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
+
|
|
|
|
+module_param(gpio_out_pin, int, S_IRUGO);
|
|
|
|
+MODULE_PARM_DESC(gpio_out_pin, "GPIO output/transmitter pin number of the BCM"
|
|
|
|
+ " processor. (default 17");
|
|
|
|
+
|
|
|
|
+module_param(gpio_in_pin, int, S_IRUGO);
|
|
|
|
+MODULE_PARM_DESC(gpio_in_pin, "GPIO input pin number of the BCM processor."
|
|
|
|
+ " (default 18");
|
|
|
|
+
|
|
|
|
+module_param(gpio_in_pull, int, S_IRUGO);
|
|
|
|
+MODULE_PARM_DESC(gpio_in_pull, "GPIO input pin pull configuration."
|
|
|
|
+ " (0 = off, 1 = up, 2 = down, default down)");
|
|
|
|
+
|
|
|
|
+module_param(sense, int, S_IRUGO);
|
|
|
|
+MODULE_PARM_DESC(sense, "Override autodetection of IR receiver circuit"
|
|
|
|
+ " (0 = active high, 1 = active low )");
|
|
|
|
+
|
|
|
|
+module_param(softcarrier, bool, S_IRUGO);
|
|
|
|
+MODULE_PARM_DESC(softcarrier, "Software carrier (0 = off, 1 = on, default on)");
|
|
|
|
+
|
|
|
|
+module_param(invert, bool, S_IRUGO);
|
|
|
|
+MODULE_PARM_DESC(invert, "Invert output (0 = off, 1 = on, default off");
|
|
|
|
+
|
|
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|