16919 lines
437 KiB
Diff
16919 lines
437 KiB
Diff
include/linux/lirc.h | 94 ++
|
|
drivers/input/Kconfig | 2 +
|
|
drivers/input/Makefile | 2 +
|
|
drivers/input/lirc/Kconfig | 116 ++
|
|
drivers/input/lirc/Makefile | 21 +
|
|
drivers/input/lirc/lirc_bt829.c | 383 ++++++
|
|
drivers/input/lirc/lirc_dev.c | 736 ++++++++++
|
|
drivers/input/lirc/lirc_dev.h | 225 +++
|
|
drivers/input/lirc/lirc_ene0100.c | 646 +++++++++
|
|
drivers/input/lirc/lirc_ene0100.h | 169 +++
|
|
drivers/input/lirc/lirc_i2c.c | 536 ++++++++
|
|
drivers/input/lirc/lirc_igorplugusb.c | 556 ++++++++
|
|
drivers/input/lirc/lirc_imon.c | 1054 ++++++++++++++
|
|
drivers/input/lirc/lirc_it87.c | 991 ++++++++++++++
|
|
drivers/input/lirc/lirc_it87.h | 116 ++
|
|
drivers/input/lirc/lirc_ite8709.c | 540 ++++++++
|
|
drivers/input/lirc/lirc_mceusb.c | 1222 +++++++++++++++++
|
|
drivers/input/lirc/lirc_parallel.c | 709 ++++++++++
|
|
drivers/input/lirc/lirc_parallel.h | 26 +
|
|
drivers/input/lirc/lirc_sasem.c | 931 +++++++++++++
|
|
drivers/input/lirc/lirc_serial.c | 1317 ++++++++++++++++++
|
|
drivers/input/lirc/lirc_sir.c | 1283 +++++++++++++++++
|
|
drivers/input/lirc/lirc_streamzap.c | 794 +++++++++++
|
|
drivers/input/lirc/lirc_ttusbir.c | 397 ++++++
|
|
drivers/input/lirc/lirc_zilog.c | 1396 +++++++++++++++++++
|
|
drivers/input/misc/Kconfig | 12 +
|
|
drivers/input/misc/Makefile | 1 +
|
|
drivers/input/misc/imon.c | 2430 +++++++++++++++++++++++++++++++++
|
|
28 files changed, 16705 insertions(+), 0 deletions(-)
|
|
|
|
diff --git a/include/linux/lirc.h b/include/linux/lirc.h
|
|
new file mode 100644
|
|
index 0000000..8ae64fa
|
|
--- /dev/null
|
|
+++ b/include/linux/lirc.h
|
|
@@ -0,0 +1,94 @@
|
|
+/*
|
|
+ * lirc.h - linux infrared remote control header file
|
|
+ * last modified 2007/09/27
|
|
+ */
|
|
+
|
|
+#ifndef _LINUX_LIRC_H
|
|
+#define _LINUX_LIRC_H
|
|
+
|
|
+#include <linux/types.h>
|
|
+#include <linux/ioctl.h>
|
|
+
|
|
+#define PULSE_BIT 0x01000000
|
|
+#define PULSE_MASK 0x00FFFFFF
|
|
+
|
|
+/*** lirc compatible hardware features ***/
|
|
+
|
|
+#define LIRC_MODE2SEND(x) (x)
|
|
+#define LIRC_SEND2MODE(x) (x)
|
|
+#define LIRC_MODE2REC(x) ((x) << 16)
|
|
+#define LIRC_REC2MODE(x) ((x) >> 16)
|
|
+
|
|
+#define LIRC_MODE_RAW 0x00000001
|
|
+#define LIRC_MODE_PULSE 0x00000002
|
|
+#define LIRC_MODE_MODE2 0x00000004
|
|
+#define LIRC_MODE_LIRCCODE 0x00000010
|
|
+
|
|
+
|
|
+#define LIRC_CAN_SEND_RAW LIRC_MODE2SEND(LIRC_MODE_RAW)
|
|
+#define LIRC_CAN_SEND_PULSE LIRC_MODE2SEND(LIRC_MODE_PULSE)
|
|
+#define LIRC_CAN_SEND_MODE2 LIRC_MODE2SEND(LIRC_MODE_MODE2)
|
|
+#define LIRC_CAN_SEND_LIRCCODE LIRC_MODE2SEND(LIRC_MODE_LIRCCODE)
|
|
+
|
|
+#define LIRC_CAN_SEND_MASK 0x0000003f
|
|
+
|
|
+#define LIRC_CAN_SET_SEND_CARRIER 0x00000100
|
|
+#define LIRC_CAN_SET_SEND_DUTY_CYCLE 0x00000200
|
|
+#define LIRC_CAN_SET_TRANSMITTER_MASK 0x00000400
|
|
+
|
|
+#define LIRC_CAN_REC_RAW LIRC_MODE2REC(LIRC_MODE_RAW)
|
|
+#define LIRC_CAN_REC_PULSE LIRC_MODE2REC(LIRC_MODE_PULSE)
|
|
+#define LIRC_CAN_REC_MODE2 LIRC_MODE2REC(LIRC_MODE_MODE2)
|
|
+#define LIRC_CAN_REC_LIRCCODE LIRC_MODE2REC(LIRC_MODE_LIRCCODE)
|
|
+
|
|
+#define LIRC_CAN_REC_MASK LIRC_MODE2REC(LIRC_CAN_SEND_MASK)
|
|
+
|
|
+#define LIRC_CAN_SET_REC_CARRIER (LIRC_CAN_SET_SEND_CARRIER << 16)
|
|
+#define LIRC_CAN_SET_REC_DUTY_CYCLE (LIRC_CAN_SET_SEND_DUTY_CYCLE << 16)
|
|
+
|
|
+#define LIRC_CAN_SET_REC_DUTY_CYCLE_RANGE 0x40000000
|
|
+#define LIRC_CAN_SET_REC_CARRIER_RANGE 0x80000000
|
|
+#define LIRC_CAN_GET_REC_RESOLUTION 0x20000000
|
|
+
|
|
+#define LIRC_CAN_SEND(x) ((x)&LIRC_CAN_SEND_MASK)
|
|
+#define LIRC_CAN_REC(x) ((x)&LIRC_CAN_REC_MASK)
|
|
+
|
|
+#define LIRC_CAN_NOTIFY_DECODE 0x01000000
|
|
+
|
|
+/*** IOCTL commands for lirc driver ***/
|
|
+
|
|
+#define LIRC_GET_FEATURES _IOR('i', 0x00000000, uint64_t)
|
|
+
|
|
+#define LIRC_GET_SEND_MODE _IOR('i', 0x00000001, uint64_t)
|
|
+#define LIRC_GET_REC_MODE _IOR('i', 0x00000002, uint64_t)
|
|
+#define LIRC_GET_SEND_CARRIER _IOR('i', 0x00000003, uint32_t)
|
|
+#define LIRC_GET_REC_CARRIER _IOR('i', 0x00000004, uint32_t)
|
|
+#define LIRC_GET_SEND_DUTY_CYCLE _IOR('i', 0x00000005, uint32_t)
|
|
+#define LIRC_GET_REC_DUTY_CYCLE _IOR('i', 0x00000006, uint32_t)
|
|
+#define LIRC_GET_REC_RESOLUTION _IOR('i', 0x00000007, uint32_t)
|
|
+
|
|
+/* code length in bits, currently only for LIRC_MODE_LIRCCODE */
|
|
+#define LIRC_GET_LENGTH _IOR('i', 0x0000000f, uint64_t)
|
|
+
|
|
+#define LIRC_SET_SEND_MODE _IOW('i', 0x00000011, uint64_t)
|
|
+#define LIRC_SET_REC_MODE _IOW('i', 0x00000012, uint64_t)
|
|
+/* Note: these can reset the according pulse_width */
|
|
+#define LIRC_SET_SEND_CARRIER _IOW('i', 0x00000013, uint32_t)
|
|
+#define LIRC_SET_REC_CARRIER _IOW('i', 0x00000014, uint32_t)
|
|
+#define LIRC_SET_SEND_DUTY_CYCLE _IOW('i', 0x00000015, uint32_t)
|
|
+#define LIRC_SET_REC_DUTY_CYCLE _IOW('i', 0x00000016, uint32_t)
|
|
+#define LIRC_SET_TRANSMITTER_MASK _IOW('i', 0x00000017, uint32_t)
|
|
+
|
|
+/*
|
|
+ * to set a range use
|
|
+ * LIRC_SET_REC_DUTY_CYCLE_RANGE/LIRC_SET_REC_CARRIER_RANGE with the
|
|
+ * lower bound first and later
|
|
+ * LIRC_SET_REC_DUTY_CYCLE/LIRC_SET_REC_CARRIER with the upper bound
|
|
+ */
|
|
+
|
|
+#define LIRC_SET_REC_DUTY_CYCLE_RANGE _IOW('i', 0x0000001e, uint32_t)
|
|
+#define LIRC_SET_REC_CARRIER_RANGE _IOW('i', 0x0000001f, uint32_t)
|
|
+
|
|
+#define LIRC_NOTIFY_DECODE _IO('i', 0x00000020)
|
|
+
|
|
+#endif
|
|
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
|
|
index 07c2cd4..ebc8743 100644
|
|
--- a/drivers/input/Kconfig
|
|
+++ b/drivers/input/Kconfig
|
|
@@ -183,6 +183,8 @@ source "drivers/input/tablet/Kconfig"
|
|
|
|
source "drivers/input/touchscreen/Kconfig"
|
|
|
|
+source "drivers/input/lirc/Kconfig"
|
|
+
|
|
source "drivers/input/misc/Kconfig"
|
|
|
|
endif
|
|
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
|
|
index 7ad212d..cb119e7 100644
|
|
--- a/drivers/input/Makefile
|
|
+++ b/drivers/input/Makefile
|
|
@@ -26,3 +26,5 @@ obj-$(CONFIG_INPUT_MISC) += misc/
|
|
obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o
|
|
|
|
obj-$(CONFIG_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
|
|
+
|
|
+obj-$(CONFIG_INPUT_LIRC) += lirc/
|
|
diff --git a/drivers/input/lirc/Kconfig b/drivers/input/lirc/Kconfig
|
|
new file mode 100644
|
|
index 0000000..86fc063
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/Kconfig
|
|
@@ -0,0 +1,116 @@
|
|
+#
|
|
+# LIRC driver(s) configuration
|
|
+#
|
|
+menuconfig INPUT_LIRC
|
|
+ tristate "Linux Infrared Remote Control IR receiver/transmitter drivers"
|
|
+ help
|
|
+ Say Y here, and all supported Linux Infrared Remote Control IR and
|
|
+ RF receiver and transmitter drivers will be displayed. When paired
|
|
+ with a remote control and the lirc daemon, the receiver drivers
|
|
+ allow control of your Linux system via remote control.
|
|
+
|
|
+if INPUT_LIRC
|
|
+
|
|
+config LIRC_BT829
|
|
+ tristate "BT829 based hardware"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for the IR interface on BT829-based hardware
|
|
+
|
|
+config LIRC_ENE0100
|
|
+ tristate "ENE KB3924/ENE0100 CIR Port Reciever"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ This is a driver for CIR port handled by ENE KB3924 embedded
|
|
+ controller found on some notebooks.
|
|
+ It appears on PNP list as ENE0100.
|
|
+
|
|
+config LIRC_I2C
|
|
+ tristate "I2C Based IR Receivers"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for I2C-based IR receivers, such as those commonly
|
|
+ found onboard Hauppauge PVR-150/250/350 video capture cards
|
|
+
|
|
+config LIRC_IGORPLUGUSB
|
|
+ tristate "Igor Cesko's USB IR Receiver"
|
|
+ depends on INPUT_LIRC && USB
|
|
+ help
|
|
+ Driver for Igor Cesko's USB IR Receiver
|
|
+
|
|
+config LIRC_IMON
|
|
+ tristate "Legacy SoundGraph iMON Receiver and Display"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for the original SoundGraph iMON IR Receiver and Display
|
|
+
|
|
+ Current generation iMON devices use the input layer imon driver.
|
|
+
|
|
+config LIRC_IT87
|
|
+ tristate "ITE IT87XX CIR Port Receiver"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for the ITE IT87xx IR Receiver
|
|
+
|
|
+config LIRC_ITE8709
|
|
+ tristate "ITE8709 CIR Port Receiver"
|
|
+ depends on INPUT_LIRC && PNP
|
|
+ help
|
|
+ Driver for the ITE8709 IR Receiver
|
|
+
|
|
+config LIRC_MCEUSB
|
|
+ tristate "Windows Media Center Ed. USB IR Transceiver"
|
|
+ depends on INPUT_LIRC && USB
|
|
+ help
|
|
+ Driver for Windows Media Center Ed. USB IR Transceivers
|
|
+
|
|
+config LIRC_PARALLEL
|
|
+ tristate "Homebrew Parallel Port Receiver"
|
|
+ depends on INPUT_LIRC && !SMP
|
|
+ help
|
|
+ Driver for Homebrew Parallel Port Receivers
|
|
+
|
|
+config LIRC_SASEM
|
|
+ tristate "Sasem USB IR Remote"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for the Sasem OnAir Remocon-V or Dign HV5 HTPC IR/VFD Module
|
|
+
|
|
+config LIRC_SERIAL
|
|
+ tristate "Homebrew Serial Port Receiver"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for Homebrew Serial Port Receivers
|
|
+
|
|
+config LIRC_SERIAL_TRANSMITTER
|
|
+ bool "Serial Port Transmitter"
|
|
+ default y
|
|
+ depends on LIRC_SERIAL
|
|
+ help
|
|
+ Serial Port Transmitter support
|
|
+
|
|
+config LIRC_SIR
|
|
+ tristate "Built-in SIR IrDA port"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for the SIR IrDA port
|
|
+
|
|
+config LIRC_STREAMZAP
|
|
+ tristate "Streamzap PC Receiver"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for the Streamzap PC Receiver
|
|
+
|
|
+config LIRC_TTUSBIR
|
|
+ tristate "Technotrend USB IR Receiver"
|
|
+ depends on INPUT_LIRC && USB
|
|
+ help
|
|
+ Driver for the Technotrend USB IR Receiver
|
|
+
|
|
+config LIRC_ZILOG
|
|
+ tristate "Zilog/Hauppauge IR Transmitter"
|
|
+ depends on INPUT_LIRC
|
|
+ help
|
|
+ Driver for the Zilog/Hauppauge IR Transmitter, found on
|
|
+ PVR-150/500, HVR-1200/1250/1700/1800, HD-PVR and other cards
|
|
+endif
|
|
diff --git a/drivers/input/lirc/Makefile b/drivers/input/lirc/Makefile
|
|
new file mode 100644
|
|
index 0000000..9122e87
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/Makefile
|
|
@@ -0,0 +1,21 @@
|
|
+# Makefile for the lirc drivers.
|
|
+#
|
|
+
|
|
+# Each configuration option enables a list of files.
|
|
+
|
|
+obj-$(CONFIG_INPUT_LIRC) += lirc_dev.o
|
|
+obj-$(CONFIG_LIRC_BT829) += lirc_bt829.o
|
|
+obj-$(CONFIG_LIRC_ENE0100) += lirc_ene0100.o
|
|
+obj-$(CONFIG_LIRC_I2C) += lirc_i2c.o
|
|
+obj-$(CONFIG_LIRC_IGORPLUGUSB) += lirc_igorplugusb.o
|
|
+obj-$(CONFIG_LIRC_IMON) += lirc_imon.o
|
|
+obj-$(CONFIG_LIRC_IT87) += lirc_it87.o
|
|
+obj-$(CONFIG_LIRC_ITE8709) += lirc_ite8709.o
|
|
+obj-$(CONFIG_LIRC_MCEUSB) += lirc_mceusb.o
|
|
+obj-$(CONFIG_LIRC_PARALLEL) += lirc_parallel.o
|
|
+obj-$(CONFIG_LIRC_SASEM) += lirc_sasem.o
|
|
+obj-$(CONFIG_LIRC_SERIAL) += lirc_serial.o
|
|
+obj-$(CONFIG_LIRC_SIR) += lirc_sir.o
|
|
+obj-$(CONFIG_LIRC_STREAMZAP) += lirc_streamzap.o
|
|
+obj-$(CONFIG_LIRC_TTUSBIR) += lirc_ttusbir.o
|
|
+obj-$(CONFIG_LIRC_ZILOG) += lirc_zilog.o
|
|
diff --git a/drivers/input/lirc/lirc_bt829.c b/drivers/input/lirc/lirc_bt829.c
|
|
new file mode 100644
|
|
index 0000000..0485884
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_bt829.c
|
|
@@ -0,0 +1,383 @@
|
|
+/*
|
|
+ * Remote control driver for the TV-card based on bt829
|
|
+ *
|
|
+ * by Leonid Froenchenko <lfroen@galileo.co.il>
|
|
+ *
|
|
+ * 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/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/threads.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+static int poll_main(void);
|
|
+static int atir_init_start(void);
|
|
+
|
|
+static void write_index(unsigned char index, unsigned int value);
|
|
+static unsigned int read_index(unsigned char index);
|
|
+
|
|
+static void do_i2c_start(void);
|
|
+static void do_i2c_stop(void);
|
|
+
|
|
+static void seems_wr_byte(unsigned char al);
|
|
+static unsigned char seems_rd_byte(void);
|
|
+
|
|
+static unsigned int read_index(unsigned char al);
|
|
+static void write_index(unsigned char ah, unsigned int edx);
|
|
+
|
|
+static void cycle_delay(int cycle);
|
|
+
|
|
+static void do_set_bits(unsigned char bl);
|
|
+static unsigned char do_get_bits(void);
|
|
+
|
|
+#define DATA_PCI_OFF 0x7FFC00
|
|
+#define WAIT_CYCLE 20
|
|
+
|
|
+#define DRIVER_NAME "lirc_bt829"
|
|
+
|
|
+static int debug;
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG DRIVER_NAME ": "fmt, ## args); \
|
|
+ } while (0)
|
|
+
|
|
+static int atir_minor;
|
|
+static unsigned long pci_addr_phys;
|
|
+static unsigned char *pci_addr_lin;
|
|
+
|
|
+static struct lirc_driver atir_driver;
|
|
+
|
|
+static struct pci_dev *do_pci_probe(void)
|
|
+{
|
|
+ struct pci_dev *my_dev;
|
|
+ my_dev = pci_get_device(PCI_VENDOR_ID_ATI,
|
|
+ PCI_DEVICE_ID_ATI_264VT, NULL);
|
|
+ if (my_dev) {
|
|
+ printk(KERN_ERR DRIVER_NAME ": Using device: %s\n",
|
|
+ pci_name(my_dev));
|
|
+ pci_addr_phys = 0;
|
|
+ if (my_dev->resource[0].flags & IORESOURCE_MEM) {
|
|
+ pci_addr_phys = my_dev->resource[0].start;
|
|
+ printk(KERN_INFO DRIVER_NAME ": memory at 0x%08X \n",
|
|
+ (unsigned int)pci_addr_phys);
|
|
+ }
|
|
+ if (pci_addr_phys == 0) {
|
|
+ printk(KERN_ERR DRIVER_NAME ": no memory resource ?\n");
|
|
+ return NULL;
|
|
+ }
|
|
+ } else {
|
|
+ printk(KERN_ERR DRIVER_NAME ": pci_probe failed\n");
|
|
+ return NULL;
|
|
+ }
|
|
+ return my_dev;
|
|
+}
|
|
+
|
|
+static int atir_add_to_buf(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ unsigned char key;
|
|
+ int status;
|
|
+ status = poll_main();
|
|
+ key = (status >> 8) & 0xFF;
|
|
+ if (status & 0xFF) {
|
|
+ dprintk("reading key %02X\n", key);
|
|
+ lirc_buffer_write(buf, &key);
|
|
+ return 0;
|
|
+ }
|
|
+ return -ENODATA;
|
|
+}
|
|
+
|
|
+static int atir_set_use_inc(void *data)
|
|
+{
|
|
+ dprintk("driver is opened\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void atir_set_use_dec(void *data)
|
|
+{
|
|
+ dprintk("driver is closed\n");
|
|
+}
|
|
+
|
|
+int init_module(void)
|
|
+{
|
|
+ struct pci_dev *pdev;
|
|
+
|
|
+ pdev = do_pci_probe();
|
|
+ if (pdev == NULL)
|
|
+ return 1;
|
|
+
|
|
+ if (!atir_init_start())
|
|
+ return 1;
|
|
+
|
|
+ strcpy(atir_driver.name, "ATIR");
|
|
+ atir_driver.minor = -1;
|
|
+ atir_driver.code_length = 8;
|
|
+ atir_driver.sample_rate = 10;
|
|
+ atir_driver.data = 0;
|
|
+ atir_driver.add_to_buf = atir_add_to_buf;
|
|
+ atir_driver.set_use_inc = atir_set_use_inc;
|
|
+ atir_driver.set_use_dec = atir_set_use_dec;
|
|
+ atir_driver.dev = &pdev->dev;
|
|
+ atir_driver.owner = THIS_MODULE;
|
|
+
|
|
+ atir_minor = lirc_register_driver(&atir_driver);
|
|
+ if (atir_minor < 0) {
|
|
+ printk(KERN_ERR DRIVER_NAME ": failed to register driver!\n");
|
|
+ return atir_minor;
|
|
+ }
|
|
+ dprintk("driver is registered on minor %d\n", atir_minor);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+void cleanup_module(void)
|
|
+{
|
|
+ lirc_unregister_driver(atir_minor);
|
|
+}
|
|
+
|
|
+
|
|
+static int atir_init_start(void)
|
|
+{
|
|
+ pci_addr_lin = ioremap(pci_addr_phys + DATA_PCI_OFF, 0x400);
|
|
+ if (pci_addr_lin == 0) {
|
|
+ printk(KERN_INFO DRIVER_NAME ": pci mem must be mapped\n");
|
|
+ return 0;
|
|
+ }
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static void cycle_delay(int cycle)
|
|
+{
|
|
+ udelay(WAIT_CYCLE*cycle);
|
|
+}
|
|
+
|
|
+
|
|
+static int poll_main()
|
|
+{
|
|
+ unsigned char status_high, status_low;
|
|
+
|
|
+ do_i2c_start();
|
|
+
|
|
+ seems_wr_byte(0xAA);
|
|
+ seems_wr_byte(0x01);
|
|
+
|
|
+ do_i2c_start();
|
|
+
|
|
+ seems_wr_byte(0xAB);
|
|
+
|
|
+ status_low = seems_rd_byte();
|
|
+ status_high = seems_rd_byte();
|
|
+
|
|
+ do_i2c_stop();
|
|
+
|
|
+ return (status_high << 8) | status_low;
|
|
+}
|
|
+
|
|
+static void do_i2c_start(void)
|
|
+{
|
|
+ do_set_bits(3);
|
|
+ cycle_delay(4);
|
|
+
|
|
+ do_set_bits(1);
|
|
+ cycle_delay(7);
|
|
+
|
|
+ do_set_bits(0);
|
|
+ cycle_delay(2);
|
|
+}
|
|
+
|
|
+static void do_i2c_stop(void)
|
|
+{
|
|
+ unsigned char bits;
|
|
+ bits = do_get_bits() & 0xFD;
|
|
+ do_set_bits(bits);
|
|
+ cycle_delay(1);
|
|
+
|
|
+ bits |= 1;
|
|
+ do_set_bits(bits);
|
|
+ cycle_delay(2);
|
|
+
|
|
+ bits |= 2;
|
|
+ do_set_bits(bits);
|
|
+ bits = 3;
|
|
+ do_set_bits(bits);
|
|
+ cycle_delay(2);
|
|
+}
|
|
+
|
|
+static void seems_wr_byte(unsigned char value)
|
|
+{
|
|
+ int i;
|
|
+ unsigned char reg;
|
|
+
|
|
+ reg = do_get_bits();
|
|
+ for (i = 0; i < 8; i++) {
|
|
+ if (value & 0x80)
|
|
+ reg |= 0x02;
|
|
+ else
|
|
+ reg &= 0xFD;
|
|
+
|
|
+ do_set_bits(reg);
|
|
+ cycle_delay(1);
|
|
+
|
|
+ reg |= 1;
|
|
+ do_set_bits(reg);
|
|
+ cycle_delay(1);
|
|
+
|
|
+ reg &= 0xFE;
|
|
+ do_set_bits(reg);
|
|
+ cycle_delay(1);
|
|
+ value <<= 1;
|
|
+ }
|
|
+ cycle_delay(2);
|
|
+
|
|
+ reg |= 2;
|
|
+ do_set_bits(reg);
|
|
+
|
|
+ reg |= 1;
|
|
+ do_set_bits(reg);
|
|
+
|
|
+ cycle_delay(1);
|
|
+ do_get_bits();
|
|
+
|
|
+ reg &= 0xFE;
|
|
+ do_set_bits(reg);
|
|
+ cycle_delay(3);
|
|
+}
|
|
+
|
|
+static unsigned char seems_rd_byte(void)
|
|
+{
|
|
+ int i;
|
|
+ int rd_byte;
|
|
+ unsigned char bits_2, bits_1;
|
|
+
|
|
+ bits_1 = do_get_bits() | 2;
|
|
+ do_set_bits(bits_1);
|
|
+
|
|
+ rd_byte = 0;
|
|
+ for (i = 0; i < 8; i++) {
|
|
+ bits_1 &= 0xFE;
|
|
+ do_set_bits(bits_1);
|
|
+ cycle_delay(2);
|
|
+
|
|
+ bits_1 |= 1;
|
|
+ do_set_bits(bits_1);
|
|
+ cycle_delay(1);
|
|
+
|
|
+ bits_2 = do_get_bits();
|
|
+ if (bits_2 & 2)
|
|
+ rd_byte |= 1;
|
|
+
|
|
+ rd_byte <<= 1;
|
|
+ }
|
|
+
|
|
+ bits_1 = 0;
|
|
+ if (bits_2 == 0)
|
|
+ bits_1 |= 2;
|
|
+
|
|
+ do_set_bits(bits_1);
|
|
+ cycle_delay(2);
|
|
+
|
|
+ bits_1 |= 1;
|
|
+ do_set_bits(bits_1);
|
|
+ cycle_delay(3);
|
|
+
|
|
+ bits_1 &= 0xFE;
|
|
+ do_set_bits(bits_1);
|
|
+ cycle_delay(2);
|
|
+
|
|
+ rd_byte >>= 1;
|
|
+ rd_byte &= 0xFF;
|
|
+ return rd_byte;
|
|
+}
|
|
+
|
|
+static void do_set_bits(unsigned char new_bits)
|
|
+{
|
|
+ int reg_val;
|
|
+ reg_val = read_index(0x34);
|
|
+ if (new_bits & 2) {
|
|
+ reg_val &= 0xFFFFFFDF;
|
|
+ reg_val |= 1;
|
|
+ } else {
|
|
+ reg_val &= 0xFFFFFFFE;
|
|
+ reg_val |= 0x20;
|
|
+ }
|
|
+ reg_val |= 0x10;
|
|
+ write_index(0x34, reg_val);
|
|
+
|
|
+ reg_val = read_index(0x31);
|
|
+ if (new_bits & 1)
|
|
+ reg_val |= 0x1000000;
|
|
+ else
|
|
+ reg_val &= 0xFEFFFFFF;
|
|
+
|
|
+ reg_val |= 0x8000000;
|
|
+ write_index(0x31, reg_val);
|
|
+}
|
|
+
|
|
+static unsigned char do_get_bits(void)
|
|
+{
|
|
+ unsigned char bits;
|
|
+ int reg_val;
|
|
+
|
|
+ reg_val = read_index(0x34);
|
|
+ reg_val |= 0x10;
|
|
+ reg_val &= 0xFFFFFFDF;
|
|
+ write_index(0x34, reg_val);
|
|
+
|
|
+ reg_val = read_index(0x34);
|
|
+ bits = 0;
|
|
+ if (reg_val & 8)
|
|
+ bits |= 2;
|
|
+ else
|
|
+ bits &= 0xFD;
|
|
+
|
|
+ reg_val = read_index(0x31);
|
|
+ if (reg_val & 0x1000000)
|
|
+ bits |= 1;
|
|
+ else
|
|
+ bits &= 0xFE;
|
|
+
|
|
+ return bits;
|
|
+}
|
|
+
|
|
+static unsigned int read_index(unsigned char index)
|
|
+{
|
|
+ unsigned char *addr;
|
|
+ unsigned int value;
|
|
+ /* addr = pci_addr_lin + DATA_PCI_OFF + ((index & 0xFF) << 2); */
|
|
+ addr = pci_addr_lin + ((index & 0xFF) << 2);
|
|
+ value = readl(addr);
|
|
+ return value;
|
|
+}
|
|
+
|
|
+static void write_index(unsigned char index, unsigned int reg_val)
|
|
+{
|
|
+ unsigned char *addr;
|
|
+ addr = pci_addr_lin + ((index & 0xFF) << 2);
|
|
+ writel(reg_val, addr);
|
|
+}
|
|
+
|
|
+MODULE_AUTHOR("Froenchenko Leonid");
|
|
+MODULE_DESCRIPTION("IR remote driver for bt829 based TV cards");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Debug enabled or not");
|
|
diff --git a/drivers/input/lirc/lirc_dev.c b/drivers/input/lirc/lirc_dev.c
|
|
new file mode 100644
|
|
index 0000000..504e122
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_dev.c
|
|
@@ -0,0 +1,736 @@
|
|
+/*
|
|
+ * LIRC base driver
|
|
+ *
|
|
+ * by Artur Lipowski <alipowski@interia.pl>
|
|
+ *
|
|
+ * 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/kernel.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/ioctl.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/wait.h>
|
|
+#include <linux/unistd.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/bitops.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/cdev.h>
|
|
+#include <linux/smp_lock.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+static int debug;
|
|
+
|
|
+#define IRCTL_DEV_NAME "BaseRemoteCtl"
|
|
+#define NOPLUG -1
|
|
+#define LOGHEAD "lirc_dev (%s[%d]): "
|
|
+
|
|
+static dev_t lirc_base_dev;
|
|
+
|
|
+struct irctl {
|
|
+ struct lirc_driver d;
|
|
+ int attached;
|
|
+ int open;
|
|
+
|
|
+ struct mutex irctl_lock;
|
|
+ struct lirc_buffer *buf;
|
|
+ unsigned int chunk_size;
|
|
+
|
|
+ struct task_struct *task;
|
|
+ long jiffies_to_wait;
|
|
+
|
|
+ struct cdev cdev;
|
|
+};
|
|
+
|
|
+static DEFINE_MUTEX(lirc_dev_lock);
|
|
+
|
|
+static struct irctl *irctls[MAX_IRCTL_DEVICES];
|
|
+
|
|
+/* Only used for sysfs but defined to void otherwise */
|
|
+static struct class *lirc_class;
|
|
+
|
|
+/* helper function
|
|
+ * initializes the irctl structure
|
|
+ */
|
|
+static void init_irctl(struct irctl *ir)
|
|
+{
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "initializing irctl\n",
|
|
+ ir->d.name, ir->d.minor);
|
|
+ mutex_init(&ir->irctl_lock);
|
|
+ ir->d.minor = NOPLUG;
|
|
+}
|
|
+
|
|
+static void cleanup(struct irctl *ir)
|
|
+{
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "cleaning up\n", ir->d.name, ir->d.minor);
|
|
+
|
|
+ device_destroy(lirc_class, MKDEV(MAJOR(lirc_base_dev), ir->d.minor));
|
|
+
|
|
+ if (ir->buf != ir->d.rbuf) {
|
|
+ lirc_buffer_free(ir->buf);
|
|
+ kfree(ir->buf);
|
|
+ }
|
|
+ ir->buf = NULL;
|
|
+}
|
|
+
|
|
+/* helper function
|
|
+ * reads key codes from driver and puts them into buffer
|
|
+ * returns 0 on success
|
|
+ */
|
|
+static int add_to_buf(struct irctl *ir)
|
|
+{
|
|
+ if (ir->d.add_to_buf) {
|
|
+ int res = -ENODATA;
|
|
+ int got_data = 0;
|
|
+
|
|
+ /*
|
|
+ * service the device as long as it is returning
|
|
+ * data and we have space
|
|
+ */
|
|
+get_data:
|
|
+ res = ir->d.add_to_buf(ir->d.data, ir->buf);
|
|
+ if (res == 0) {
|
|
+ got_data++;
|
|
+ goto get_data;
|
|
+ }
|
|
+
|
|
+ if (res == -ENODEV)
|
|
+ kthread_stop(ir->task);
|
|
+
|
|
+ return got_data ? 0 : res;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* main function of the polling thread
|
|
+ */
|
|
+static int lirc_thread(void *irctl)
|
|
+{
|
|
+ struct irctl *ir = irctl;
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "poll thread started\n",
|
|
+ ir->d.name, ir->d.minor);
|
|
+
|
|
+ do {
|
|
+ if (ir->open) {
|
|
+ if (ir->jiffies_to_wait) {
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ schedule_timeout(ir->jiffies_to_wait);
|
|
+ }
|
|
+ if (kthread_should_stop())
|
|
+ break;
|
|
+ if (!add_to_buf(ir))
|
|
+ wake_up_interruptible(&ir->buf->wait_poll);
|
|
+ } else {
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ schedule();
|
|
+ }
|
|
+ } while (!kthread_should_stop());
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "poll thread ended\n",
|
|
+ ir->d.name, ir->d.minor);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static struct file_operations fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .read = lirc_dev_fop_read,
|
|
+ .write = lirc_dev_fop_write,
|
|
+ .poll = lirc_dev_fop_poll,
|
|
+ .ioctl = lirc_dev_fop_ioctl,
|
|
+ .open = lirc_dev_fop_open,
|
|
+ .release = lirc_dev_fop_close,
|
|
+};
|
|
+
|
|
+static int lirc_cdev_add(struct irctl *ir)
|
|
+{
|
|
+ int retval;
|
|
+ struct lirc_driver *d = &ir->d;
|
|
+
|
|
+ if (d->fops) {
|
|
+ cdev_init(&ir->cdev, d->fops);
|
|
+ ir->cdev.owner = d->owner;
|
|
+ } else {
|
|
+ cdev_init(&ir->cdev, &fops);
|
|
+ ir->cdev.owner = THIS_MODULE;
|
|
+ }
|
|
+ kobject_set_name(&ir->cdev.kobj, "lirc%d", d->minor);
|
|
+
|
|
+ retval = cdev_add(&ir->cdev, MKDEV(MAJOR(lirc_base_dev), d->minor), 1);
|
|
+ if (retval)
|
|
+ kobject_put(&ir->cdev.kobj);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+int lirc_register_driver(struct lirc_driver *d)
|
|
+{
|
|
+ struct irctl *ir;
|
|
+ int minor;
|
|
+ int bytes_in_key;
|
|
+ unsigned int chunk_size;
|
|
+ unsigned int buffer_size;
|
|
+ int err;
|
|
+
|
|
+ if (!d) {
|
|
+ printk(KERN_ERR "lirc_dev: lirc_register_driver: "
|
|
+ "driver pointer must be not NULL!\n");
|
|
+ err = -EBADRQC;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (MAX_IRCTL_DEVICES <= d->minor) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "\"minor\" must be between 0 and %d (%d)!\n",
|
|
+ MAX_IRCTL_DEVICES-1, d->minor);
|
|
+ err = -EBADRQC;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (1 > d->code_length || (BUFLEN * 8) < d->code_length) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "code length in bits for minor (%d) "
|
|
+ "must be less than %d!\n",
|
|
+ d->minor, BUFLEN * 8);
|
|
+ err = -EBADRQC;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ dev_dbg(d->dev, "lirc_dev: lirc_register_driver: sample_rate: %d\n",
|
|
+ d->sample_rate);
|
|
+ if (d->sample_rate) {
|
|
+ if (2 > d->sample_rate || HZ < d->sample_rate) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "sample_rate must be between 2 and %d!\n", HZ);
|
|
+ err = -EBADRQC;
|
|
+ goto out;
|
|
+ }
|
|
+ if (!d->add_to_buf) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "add_to_buf cannot be NULL when "
|
|
+ "sample_rate is set\n");
|
|
+ err = -EBADRQC;
|
|
+ goto out;
|
|
+ }
|
|
+ } else if (!(d->fops && d->fops->read) && !d->rbuf) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "fops->read and rbuf cannot all be NULL!\n");
|
|
+ err = -EBADRQC;
|
|
+ goto out;
|
|
+ } else if (!d->rbuf) {
|
|
+ if (!(d->fops && d->fops->read && d->fops->poll &&
|
|
+ d->fops->ioctl)) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "neither read, poll nor ioctl can be NULL!\n");
|
|
+ err = -EBADRQC;
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mutex_lock(&lirc_dev_lock);
|
|
+
|
|
+ minor = d->minor;
|
|
+
|
|
+ if (minor < 0) {
|
|
+ /* find first free slot for driver */
|
|
+ for (minor = 0; minor < MAX_IRCTL_DEVICES; minor++)
|
|
+ if (!irctls[minor])
|
|
+ break;
|
|
+ if (MAX_IRCTL_DEVICES == minor) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "no free slots for drivers!\n");
|
|
+ err = -ENOMEM;
|
|
+ goto out_lock;
|
|
+ }
|
|
+ } else if (irctls[minor]) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "minor (%d) just registered!\n", minor);
|
|
+ err = -EBUSY;
|
|
+ goto out_lock;
|
|
+ }
|
|
+
|
|
+ ir = kzalloc(sizeof(struct irctl), GFP_KERNEL);
|
|
+ if (!ir) {
|
|
+ err = -ENOMEM;
|
|
+ goto out_lock;
|
|
+ }
|
|
+ init_irctl(ir);
|
|
+ irctls[minor] = ir;
|
|
+ d->minor = minor;
|
|
+
|
|
+ if (d->sample_rate) {
|
|
+ ir->jiffies_to_wait = HZ / d->sample_rate;
|
|
+ } else {
|
|
+ /* it means - wait for external event in task queue */
|
|
+ ir->jiffies_to_wait = 0;
|
|
+ }
|
|
+
|
|
+ /* some safety check 8-) */
|
|
+ d->name[sizeof(d->name)-1] = '\0';
|
|
+
|
|
+ bytes_in_key = BITS_TO_LONGS(d->code_length) +
|
|
+ (d->code_length % 8 ? 1 : 0);
|
|
+ buffer_size = d->buffer_size ? d->buffer_size : BUFLEN / bytes_in_key;
|
|
+ chunk_size = d->chunk_size ? d->chunk_size : bytes_in_key;
|
|
+
|
|
+ if (d->rbuf) {
|
|
+ ir->buf = d->rbuf;
|
|
+ } else {
|
|
+ ir->buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
|
|
+ if (!ir->buf) {
|
|
+ err = -ENOMEM;
|
|
+ goto out_lock;
|
|
+ }
|
|
+ err = lirc_buffer_init(ir->buf, chunk_size, buffer_size);
|
|
+ if (err) {
|
|
+ kfree(ir->buf);
|
|
+ goto out_lock;
|
|
+ }
|
|
+ }
|
|
+ ir->chunk_size = ir->buf->chunk_size;
|
|
+
|
|
+ if (d->features == 0)
|
|
+ d->features = LIRC_CAN_REC_LIRCCODE;
|
|
+
|
|
+ ir->d = *d;
|
|
+ ir->d.minor = minor;
|
|
+
|
|
+ device_create(lirc_class, ir->d.dev,
|
|
+ MKDEV(MAJOR(lirc_base_dev), ir->d.minor), NULL,
|
|
+ "lirc%u", ir->d.minor);
|
|
+
|
|
+ if (d->sample_rate) {
|
|
+ /* try to fire up polling thread */
|
|
+ ir->task = kthread_run(lirc_thread, (void *)ir, "lirc_dev");
|
|
+ if (IS_ERR(ir->task)) {
|
|
+ dev_err(d->dev, "lirc_dev: lirc_register_driver: "
|
|
+ "cannot run poll thread for minor = %d\n",
|
|
+ d->minor);
|
|
+ err = -ECHILD;
|
|
+ goto out_sysfs;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = lirc_cdev_add(ir);
|
|
+ if (err)
|
|
+ goto out_sysfs;
|
|
+
|
|
+ ir->attached = 1;
|
|
+ mutex_unlock(&lirc_dev_lock);
|
|
+
|
|
+ dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n",
|
|
+ ir->d.name, ir->d.minor);
|
|
+ return minor;
|
|
+
|
|
+out_sysfs:
|
|
+ device_destroy(lirc_class, MKDEV(MAJOR(lirc_base_dev), ir->d.minor));
|
|
+out_lock:
|
|
+ mutex_unlock(&lirc_dev_lock);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_register_driver);
|
|
+
|
|
+int lirc_unregister_driver(int minor)
|
|
+{
|
|
+ struct irctl *ir;
|
|
+
|
|
+ if (minor < 0 || minor >= MAX_IRCTL_DEVICES) {
|
|
+ printk(KERN_ERR "lirc_dev: lirc_unregister_driver: "
|
|
+ "\"minor (%d)\" must be between 0 and %d!\n",
|
|
+ minor, MAX_IRCTL_DEVICES-1);
|
|
+ return -EBADRQC;
|
|
+ }
|
|
+
|
|
+ ir = irctls[minor];
|
|
+
|
|
+ mutex_lock(&lirc_dev_lock);
|
|
+
|
|
+ if (ir->d.minor != minor) {
|
|
+ printk(KERN_ERR "lirc_dev: lirc_unregister_driver: "
|
|
+ "minor (%d) device not registered!", minor);
|
|
+ mutex_unlock(&lirc_dev_lock);
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ /* end up polling thread */
|
|
+ if (ir->task)
|
|
+ kthread_stop(ir->task);
|
|
+
|
|
+ dev_dbg(ir->d.dev, "lirc_dev: driver %s unregistered from minor = %d\n",
|
|
+ ir->d.name, ir->d.minor);
|
|
+
|
|
+ ir->attached = 0;
|
|
+ if (ir->open) {
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "releasing opened driver\n",
|
|
+ ir->d.name, ir->d.minor);
|
|
+ wake_up_interruptible(&ir->buf->wait_poll);
|
|
+ mutex_lock(&ir->irctl_lock);
|
|
+ ir->d.set_use_dec(ir->d.data);
|
|
+ module_put(ir->d.owner);
|
|
+ mutex_unlock(&ir->irctl_lock);
|
|
+ cdev_del(&ir->cdev);
|
|
+ } else {
|
|
+ cleanup(ir);
|
|
+ cdev_del(&ir->cdev);
|
|
+ kfree(ir);
|
|
+ irctls[minor] = NULL;
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&lirc_dev_lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_unregister_driver);
|
|
+
|
|
+int lirc_dev_fop_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct irctl *ir;
|
|
+ int retval = 0;
|
|
+
|
|
+ if (iminor(inode) >= MAX_IRCTL_DEVICES) {
|
|
+ printk(KERN_WARNING "lirc_dev [%d]: open result = -ENODEV\n",
|
|
+ iminor(inode));
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (mutex_lock_interruptible(&lirc_dev_lock))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ ir = irctls[iminor(inode)];
|
|
+ if (!ir) {
|
|
+ retval = -ENODEV;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "open called\n", ir->d.name, ir->d.minor);
|
|
+
|
|
+ if (ir->d.minor == NOPLUG) {
|
|
+ retval = -ENODEV;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ if (ir->open) {
|
|
+ retval = -EBUSY;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ if (try_module_get(ir->d.owner)) {
|
|
+ ++ir->open;
|
|
+ retval = ir->d.set_use_inc(ir->d.data);
|
|
+
|
|
+ if (retval) {
|
|
+ module_put(ir->d.owner);
|
|
+ --ir->open;
|
|
+ } else {
|
|
+ lirc_buffer_clear(ir->buf);
|
|
+ }
|
|
+ if (ir->task)
|
|
+ wake_up_process(ir->task);
|
|
+ }
|
|
+
|
|
+error:
|
|
+ if (ir)
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "open result = %d\n",
|
|
+ ir->d.name, ir->d.minor, retval);
|
|
+
|
|
+ mutex_unlock(&lirc_dev_lock);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_dev_fop_open);
|
|
+
|
|
+int lirc_dev_fop_close(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct irctl *ir = irctls[iminor(inode)];
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "close called\n", ir->d.name, ir->d.minor);
|
|
+
|
|
+ WARN_ON(mutex_lock_killable(&lirc_dev_lock));
|
|
+
|
|
+ --ir->open;
|
|
+ if (ir->attached) {
|
|
+ ir->d.set_use_dec(ir->d.data);
|
|
+ module_put(ir->d.owner);
|
|
+ } else {
|
|
+ cleanup(ir);
|
|
+ irctls[ir->d.minor] = NULL;
|
|
+ kfree(ir);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&lirc_dev_lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_dev_fop_close);
|
|
+
|
|
+unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait)
|
|
+{
|
|
+ struct irctl *ir = irctls[iminor(file->f_dentry->d_inode)];
|
|
+ unsigned int ret;
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "poll called\n", ir->d.name, ir->d.minor);
|
|
+
|
|
+ if (!ir->attached) {
|
|
+ mutex_unlock(&ir->irctl_lock);
|
|
+ return POLLERR;
|
|
+ }
|
|
+
|
|
+ poll_wait(file, &ir->buf->wait_poll, wait);
|
|
+
|
|
+ if (ir->buf)
|
|
+ if (lirc_buffer_empty(ir->buf))
|
|
+ ret = 0;
|
|
+ else
|
|
+ ret = POLLIN | POLLRDNORM;
|
|
+ else
|
|
+ ret = POLLERR;
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "poll result = %d\n",
|
|
+ ir->d.name, ir->d.minor, ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_dev_fop_poll);
|
|
+
|
|
+int lirc_dev_fop_ioctl(struct inode *inode, struct file *file,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ unsigned long mode;
|
|
+ int result = 0;
|
|
+ struct irctl *ir = irctls[iminor(inode)];
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "ioctl called (0x%x)\n",
|
|
+ ir->d.name, ir->d.minor, cmd);
|
|
+
|
|
+ if (ir->d.minor == NOPLUG || !ir->attached) {
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "ioctl result = -ENODEV\n",
|
|
+ ir->d.name, ir->d.minor);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ switch (cmd) {
|
|
+ case LIRC_GET_FEATURES:
|
|
+ result = put_user(ir->d.features, (unsigned long *)arg);
|
|
+ break;
|
|
+ case LIRC_GET_REC_MODE:
|
|
+ if (!(ir->d.features & LIRC_CAN_REC_MASK))
|
|
+ return -ENOSYS;
|
|
+
|
|
+ result = put_user(LIRC_REC2MODE
|
|
+ (ir->d.features & LIRC_CAN_REC_MASK),
|
|
+ (unsigned long *)arg);
|
|
+ break;
|
|
+ case LIRC_SET_REC_MODE:
|
|
+ if (!(ir->d.features & LIRC_CAN_REC_MASK))
|
|
+ return -ENOSYS;
|
|
+
|
|
+ result = get_user(mode, (unsigned long *)arg);
|
|
+ if (!result && !(LIRC_MODE2REC(mode) & ir->d.features))
|
|
+ result = -EINVAL;
|
|
+ /*
|
|
+ * FIXME: We should actually set the mode somehow but
|
|
+ * for now, lirc_serial doesn't support mode changing either
|
|
+ */
|
|
+ break;
|
|
+ case LIRC_GET_LENGTH:
|
|
+ result = put_user(ir->d.code_length, (unsigned long *)arg);
|
|
+ break;
|
|
+ default:
|
|
+ result = -EINVAL;
|
|
+ }
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "ioctl result = %d\n",
|
|
+ ir->d.name, ir->d.minor, result);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_dev_fop_ioctl);
|
|
+
|
|
+ssize_t lirc_dev_fop_read(struct file *file,
|
|
+ char *buffer,
|
|
+ size_t length,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ struct irctl *ir = irctls[iminor(file->f_dentry->d_inode)];
|
|
+ unsigned char buf[ir->chunk_size];
|
|
+ int ret = 0, written = 0;
|
|
+ DECLARE_WAITQUEUE(wait, current);
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "read called\n", ir->d.name, ir->d.minor);
|
|
+
|
|
+ if (mutex_lock_interruptible(&ir->irctl_lock))
|
|
+ return -ERESTARTSYS;
|
|
+ if (!ir->attached) {
|
|
+ mutex_unlock(&ir->irctl_lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (length % ir->chunk_size) {
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "read result = -EINVAL\n",
|
|
+ ir->d.name, ir->d.minor);
|
|
+ mutex_unlock(&ir->irctl_lock);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * we add ourselves to the task queue before buffer check
|
|
+ * to avoid losing scan code (in case when queue is awaken somewhere
|
|
+ * between while condition checking and scheduling)
|
|
+ */
|
|
+ add_wait_queue(&ir->buf->wait_poll, &wait);
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+
|
|
+ /*
|
|
+ * while we didn't provide 'length' bytes, device is opened in blocking
|
|
+ * mode and 'copy_to_user' is happy, wait for data.
|
|
+ */
|
|
+ while (written < length && ret == 0) {
|
|
+ if (lirc_buffer_empty(ir->buf)) {
|
|
+ /* According to the read(2) man page, 'written' can be
|
|
+ * returned as less than 'length', instead of blocking
|
|
+ * again, returning -EWOULDBLOCK, or returning
|
|
+ * -ERESTARTSYS */
|
|
+ if (written)
|
|
+ break;
|
|
+ if (file->f_flags & O_NONBLOCK) {
|
|
+ ret = -EWOULDBLOCK;
|
|
+ break;
|
|
+ }
|
|
+ if (signal_pending(current)) {
|
|
+ ret = -ERESTARTSYS;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&ir->irctl_lock);
|
|
+ schedule();
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+
|
|
+ if (mutex_lock_interruptible(&ir->irctl_lock)) {
|
|
+ ret = -ERESTARTSYS;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!ir->attached) {
|
|
+ ret = -ENODEV;
|
|
+ break;
|
|
+ }
|
|
+ } else {
|
|
+ lirc_buffer_read(ir->buf, buf);
|
|
+ ret = copy_to_user((void *)buffer+written, buf,
|
|
+ ir->buf->chunk_size);
|
|
+ written += ir->buf->chunk_size;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ remove_wait_queue(&ir->buf->wait_poll, &wait);
|
|
+ set_current_state(TASK_RUNNING);
|
|
+ mutex_unlock(&ir->irctl_lock);
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "read result = %s (%d)\n",
|
|
+ ir->d.name, ir->d.minor, ret ? "-EFAULT" : "OK", ret);
|
|
+
|
|
+ return ret ? ret : written;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_dev_fop_read);
|
|
+
|
|
+void *lirc_get_pdata(struct file *file)
|
|
+{
|
|
+ void *data = NULL;
|
|
+
|
|
+ if (file && file->f_dentry && file->f_dentry->d_inode &&
|
|
+ file->f_dentry->d_inode->i_rdev) {
|
|
+ struct irctl *ir;
|
|
+ ir = irctls[iminor(file->f_dentry->d_inode)];
|
|
+ data = ir->d.data;
|
|
+ }
|
|
+
|
|
+ return data;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_get_pdata);
|
|
+
|
|
+
|
|
+ssize_t lirc_dev_fop_write(struct file *file, const char *buffer,
|
|
+ size_t length, loff_t *ppos)
|
|
+{
|
|
+ struct irctl *ir = irctls[iminor(file->f_dentry->d_inode)];
|
|
+
|
|
+ dev_dbg(ir->d.dev, LOGHEAD "write called\n", ir->d.name, ir->d.minor);
|
|
+
|
|
+ if (!ir->attached)
|
|
+ return -ENODEV;
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+EXPORT_SYMBOL(lirc_dev_fop_write);
|
|
+
|
|
+
|
|
+static int __init lirc_dev_init(void)
|
|
+{
|
|
+ int retval;
|
|
+
|
|
+ lirc_class = class_create(THIS_MODULE, "lirc");
|
|
+ if (IS_ERR(lirc_class)) {
|
|
+ retval = PTR_ERR(lirc_class);
|
|
+ printk(KERN_ERR "lirc_dev: class_create failed\n");
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ retval = alloc_chrdev_region(&lirc_base_dev, 0, MAX_IRCTL_DEVICES,
|
|
+ IRCTL_DEV_NAME);
|
|
+ if (retval) {
|
|
+ class_destroy(lirc_class);
|
|
+ printk(KERN_ERR "lirc_dev: alloc_chrdev_region failed\n");
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+
|
|
+ printk(KERN_INFO "lirc_dev: IR Remote Control driver registered, "
|
|
+ "major %d \n", MAJOR(lirc_base_dev));
|
|
+
|
|
+error:
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+static void __exit lirc_dev_exit(void)
|
|
+{
|
|
+ class_destroy(lirc_class);
|
|
+ unregister_chrdev_region(lirc_base_dev, MAX_IRCTL_DEVICES);
|
|
+ printk(KERN_INFO "lirc_dev: module unloaded\n");
|
|
+}
|
|
+
|
|
+module_init(lirc_dev_init);
|
|
+module_exit(lirc_dev_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("LIRC base driver module");
|
|
+MODULE_AUTHOR("Artur Lipowski");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
diff --git a/drivers/input/lirc/lirc_dev.h b/drivers/input/lirc/lirc_dev.h
|
|
new file mode 100644
|
|
index 0000000..99d0442
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_dev.h
|
|
@@ -0,0 +1,225 @@
|
|
+/*
|
|
+ * LIRC base driver
|
|
+ *
|
|
+ * by Artur Lipowski <alipowski@interia.pl>
|
|
+ * This code is licensed under GNU GPL
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef _LINUX_LIRC_DEV_H
|
|
+#define _LINUX_LIRC_DEV_H
|
|
+
|
|
+#define MAX_IRCTL_DEVICES 4
|
|
+#define BUFLEN 16
|
|
+
|
|
+#define mod(n, div) ((n) % (div))
|
|
+
|
|
+#include <linux/slab.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/ioctl.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/kfifo.h>
|
|
+
|
|
+struct lirc_buffer {
|
|
+ wait_queue_head_t wait_poll;
|
|
+ spinlock_t fifo_lock;
|
|
+ unsigned int chunk_size;
|
|
+ unsigned int size; /* in chunks */
|
|
+ /* Using chunks instead of bytes pretends to simplify boundary checking
|
|
+ * And should allow for some performance fine tunning later */
|
|
+ struct kfifo fifo;
|
|
+ u8 fifo_initialized;
|
|
+};
|
|
+
|
|
+static inline void lirc_buffer_clear(struct lirc_buffer *buf)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (buf->fifo_initialized) {
|
|
+ spin_lock_irqsave(&buf->fifo_lock, flags);
|
|
+ kfifo_reset(&buf->fifo);
|
|
+ spin_unlock_irqrestore(&buf->fifo_lock, flags);
|
|
+ } else
|
|
+ WARN(1, "calling %s on an uninitialized lirc_buffer\n",
|
|
+ __func__);
|
|
+}
|
|
+
|
|
+static inline int lirc_buffer_init(struct lirc_buffer *buf,
|
|
+ unsigned int chunk_size,
|
|
+ unsigned int size)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ init_waitqueue_head(&buf->wait_poll);
|
|
+ spin_lock_init(&buf->fifo_lock);
|
|
+ buf->chunk_size = chunk_size;
|
|
+ buf->size = size;
|
|
+ ret = kfifo_alloc(&buf->fifo, size * chunk_size, GFP_KERNEL);
|
|
+ if (ret == 0)
|
|
+ buf->fifo_initialized = 1;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static inline void lirc_buffer_free(struct lirc_buffer *buf)
|
|
+{
|
|
+ if (buf->fifo_initialized) {
|
|
+ kfifo_free(&buf->fifo);
|
|
+ buf->fifo_initialized = 0;
|
|
+ } else
|
|
+ WARN(1, "calling %s on an uninitialized lirc_buffer\n",
|
|
+ __func__);
|
|
+}
|
|
+
|
|
+static inline int lirc_buffer_len(struct lirc_buffer *buf)
|
|
+{
|
|
+ int len;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&buf->fifo_lock, flags);
|
|
+ len = kfifo_len(&buf->fifo);
|
|
+ spin_unlock_irqrestore(&buf->fifo_lock, flags);
|
|
+
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static inline int lirc_buffer_full(struct lirc_buffer *buf)
|
|
+{
|
|
+ return lirc_buffer_len(buf) == buf->size * buf->chunk_size;
|
|
+}
|
|
+
|
|
+static inline int lirc_buffer_empty(struct lirc_buffer *buf)
|
|
+{
|
|
+ return !lirc_buffer_len(buf);
|
|
+}
|
|
+
|
|
+static inline int lirc_buffer_available(struct lirc_buffer *buf)
|
|
+{
|
|
+ return buf->size - (lirc_buffer_len(buf) / buf->chunk_size);
|
|
+}
|
|
+
|
|
+static inline unsigned int lirc_buffer_read(struct lirc_buffer *buf,
|
|
+ unsigned char *dest)
|
|
+{
|
|
+ unsigned int ret = 0;
|
|
+
|
|
+ if (lirc_buffer_len(buf) >= buf->chunk_size)
|
|
+ ret = kfifo_out_locked(&buf->fifo, dest, buf->chunk_size,
|
|
+ &buf->fifo_lock);
|
|
+ return ret;
|
|
+
|
|
+}
|
|
+
|
|
+static inline unsigned int lirc_buffer_write(struct lirc_buffer *buf,
|
|
+ unsigned char *orig)
|
|
+{
|
|
+ unsigned int ret;
|
|
+
|
|
+ ret = kfifo_in_locked(&buf->fifo, orig, buf->chunk_size,
|
|
+ &buf->fifo_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+struct lirc_driver {
|
|
+ char name[40];
|
|
+ int minor;
|
|
+ unsigned long code_length;
|
|
+ unsigned int buffer_size; /* in chunks holding one code each */
|
|
+ int sample_rate;
|
|
+ unsigned long features;
|
|
+
|
|
+ unsigned int chunk_size;
|
|
+
|
|
+ void *data;
|
|
+ int (*add_to_buf) (void *data, struct lirc_buffer *buf);
|
|
+ struct lirc_buffer *rbuf;
|
|
+ int (*set_use_inc) (void *data);
|
|
+ void (*set_use_dec) (void *data);
|
|
+ struct file_operations *fops;
|
|
+ struct device *dev;
|
|
+ struct module *owner;
|
|
+};
|
|
+
|
|
+/* name:
|
|
+ * this string will be used for logs
|
|
+ *
|
|
+ * minor:
|
|
+ * indicates minor device (/dev/lirc) number for registered driver
|
|
+ * if caller fills it with negative value, then the first free minor
|
|
+ * number will be used (if available)
|
|
+ *
|
|
+ * code_length:
|
|
+ * length of the remote control key code expressed in bits
|
|
+ *
|
|
+ * sample_rate:
|
|
+ *
|
|
+ * data:
|
|
+ * it may point to any driver data and this pointer will be passed to
|
|
+ * all callback functions
|
|
+ *
|
|
+ * add_to_buf:
|
|
+ * add_to_buf will be called after specified period of the time or
|
|
+ * triggered by the external event, this behavior depends on value of
|
|
+ * the sample_rate this function will be called in user context. This
|
|
+ * routine should return 0 if data was added to the buffer and
|
|
+ * -ENODATA if none was available. This should add some number of bits
|
|
+ * evenly divisible by code_length to the buffer
|
|
+ *
|
|
+ * rbuf:
|
|
+ * if not NULL, it will be used as a read buffer, you will have to
|
|
+ * write to the buffer by other means, like irq's (see also
|
|
+ * lirc_serial.c).
|
|
+ *
|
|
+ * set_use_inc:
|
|
+ * set_use_inc will be called after device is opened
|
|
+ *
|
|
+ * set_use_dec:
|
|
+ * set_use_dec will be called after device is closed
|
|
+ *
|
|
+ * fops:
|
|
+ * file_operations for drivers which don't fit the current driver model.
|
|
+ *
|
|
+ * Some ioctl's can be directly handled by lirc_dev if the driver's
|
|
+ * ioctl function is NULL or if it returns -ENOIOCTLCMD (see also
|
|
+ * lirc_serial.c).
|
|
+ *
|
|
+ * owner:
|
|
+ * the module owning this struct
|
|
+ *
|
|
+ */
|
|
+
|
|
+
|
|
+/* following functions can be called ONLY from user context
|
|
+ *
|
|
+ * returns negative value on error or minor number
|
|
+ * of the registered device if success
|
|
+ * contents of the structure pointed by p is copied
|
|
+ */
|
|
+extern int lirc_register_driver(struct lirc_driver *d);
|
|
+
|
|
+/* returns negative value on error or 0 if success
|
|
+*/
|
|
+extern int lirc_unregister_driver(int minor);
|
|
+
|
|
+/* Returns the private data stored in the lirc_driver
|
|
+ * associated with the given device file pointer.
|
|
+ */
|
|
+void *lirc_get_pdata(struct file *file);
|
|
+
|
|
+/* default file operations
|
|
+ * used by drivers if they override only some operations
|
|
+ */
|
|
+int lirc_dev_fop_open(struct inode *inode, struct file *file);
|
|
+int lirc_dev_fop_close(struct inode *inode, struct file *file);
|
|
+unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait);
|
|
+int lirc_dev_fop_ioctl(struct inode *inode, struct file *file,
|
|
+ unsigned int cmd, unsigned long arg);
|
|
+ssize_t lirc_dev_fop_read(struct file *file, char *buffer, size_t length,
|
|
+ loff_t *ppos);
|
|
+ssize_t lirc_dev_fop_write(struct file *file, const char *buffer, size_t length,
|
|
+ loff_t *ppos);
|
|
+long lirc_dev_fop_compat_ioctl(struct file *file, unsigned int cmd32,
|
|
+ unsigned long arg);
|
|
+
|
|
+#endif
|
|
diff --git a/drivers/input/lirc/lirc_ene0100.c b/drivers/input/lirc/lirc_ene0100.c
|
|
new file mode 100644
|
|
index 0000000..a152c52
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_ene0100.c
|
|
@@ -0,0 +1,646 @@
|
|
+/*
|
|
+ * driver for ENE KB3926 B/C/D CIR (also known as ENE0100)
|
|
+ *
|
|
+ * Copyright (C) 2009 Maxim Levitsky <maximlevitsky@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/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/pnp.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/sched.h>
|
|
+#include "lirc_ene0100.h"
|
|
+
|
|
+static int sample_period = 75;
|
|
+static int enable_idle = 1;
|
|
+static int enable_learning;
|
|
+
|
|
+static void ene_set_idle(struct ene_device *dev, int idle);
|
|
+static void ene_set_inputs(struct ene_device *dev, int enable);
|
|
+
|
|
+/* read a hardware register */
|
|
+static u8 ene_hw_read_reg(struct ene_device *dev, u16 reg)
|
|
+{
|
|
+ outb(reg >> 8, dev->hw_io + ENE_ADDR_HI);
|
|
+ outb(reg & 0xFF, dev->hw_io + ENE_ADDR_LO);
|
|
+ return inb(dev->hw_io + ENE_IO);
|
|
+}
|
|
+
|
|
+/* write a hardware register */
|
|
+static void ene_hw_write_reg(struct ene_device *dev, u16 reg, u8 value)
|
|
+{
|
|
+ outb(reg >> 8, dev->hw_io + ENE_ADDR_HI);
|
|
+ outb(reg & 0xFF, dev->hw_io + ENE_ADDR_LO);
|
|
+ outb(value, dev->hw_io + ENE_IO);
|
|
+}
|
|
+
|
|
+/* change specific bits in hardware register */
|
|
+static void ene_hw_write_reg_mask(struct ene_device *dev,
|
|
+ u16 reg, u8 value, u8 mask)
|
|
+{
|
|
+ u8 regvalue;
|
|
+
|
|
+ outb(reg >> 8, dev->hw_io + ENE_ADDR_HI);
|
|
+ outb(reg & 0xFF, dev->hw_io + ENE_ADDR_LO);
|
|
+
|
|
+ regvalue = inb(dev->hw_io + ENE_IO) & ~mask;
|
|
+ regvalue |= (value & mask);
|
|
+ outb(regvalue, dev->hw_io + ENE_IO);
|
|
+}
|
|
+
|
|
+/* read irq status and ack it */
|
|
+static int ene_hw_irq_status(struct ene_device *dev, int *buffer_pointer)
|
|
+{
|
|
+ u8 irq_status;
|
|
+ u8 fw_flags1, fw_flags2;
|
|
+
|
|
+ fw_flags2 = ene_hw_read_reg(dev, ENE_FW2);
|
|
+
|
|
+ if (buffer_pointer)
|
|
+ *buffer_pointer = 4 * (fw_flags2 & ENE_FW2_BUF_HIGH);
|
|
+
|
|
+ if (dev->hw_revision < ENE_HW_C) {
|
|
+ irq_status = ene_hw_read_reg(dev, ENEB_IRQ_STATUS);
|
|
+
|
|
+ if (!(irq_status & ENEB_IRQ_STATUS_IR))
|
|
+ return 0;
|
|
+ ene_hw_write_reg(dev, ENEB_IRQ_STATUS,
|
|
+ irq_status & ~ENEB_IRQ_STATUS_IR);
|
|
+
|
|
+ /* rev B support only recieving */
|
|
+ return ENE_IRQ_RX;
|
|
+ }
|
|
+
|
|
+ irq_status = ene_hw_read_reg(dev, ENEC_IRQ);
|
|
+
|
|
+ if (!(irq_status & ENEC_IRQ_STATUS))
|
|
+ return 0;
|
|
+
|
|
+ /* original driver does that twice - a workaround ? */
|
|
+ ene_hw_write_reg(dev, ENEC_IRQ, irq_status & ~ENEC_IRQ_STATUS);
|
|
+ ene_hw_write_reg(dev, ENEC_IRQ, irq_status & ~ENEC_IRQ_STATUS);
|
|
+
|
|
+ /* clear unknown flag in F8F9 */
|
|
+ if (fw_flags2 & ENE_FW2_IRQ_CLR)
|
|
+ ene_hw_write_reg(dev, ENE_FW2, fw_flags2 & ~ENE_FW2_IRQ_CLR);
|
|
+
|
|
+ /* check if this is a TX interrupt */
|
|
+ fw_flags1 = ene_hw_read_reg(dev, ENE_FW1);
|
|
+
|
|
+ if (fw_flags1 & ENE_FW1_TXIRQ) {
|
|
+ ene_hw_write_reg(dev, ENE_FW1, fw_flags1 & ~ENE_FW1_TXIRQ);
|
|
+ return ENE_IRQ_TX;
|
|
+ } else
|
|
+ return ENE_IRQ_RX;
|
|
+}
|
|
+
|
|
+static int ene_hw_detect(struct ene_device *dev)
|
|
+{
|
|
+ u8 chip_major, chip_minor;
|
|
+ u8 hw_revision, old_ver;
|
|
+ u8 tmp;
|
|
+ u8 fw_capabilities;
|
|
+
|
|
+ tmp = ene_hw_read_reg(dev, ENE_HW_UNK);
|
|
+ ene_hw_write_reg(dev, ENE_HW_UNK, tmp & ~ENE_HW_UNK_CLR);
|
|
+
|
|
+ chip_major = ene_hw_read_reg(dev, ENE_HW_VER_MAJOR);
|
|
+ chip_minor = ene_hw_read_reg(dev, ENE_HW_VER_MINOR);
|
|
+
|
|
+ ene_hw_write_reg(dev, ENE_HW_UNK, tmp);
|
|
+ hw_revision = ene_hw_read_reg(dev, ENE_HW_VERSION);
|
|
+ old_ver = ene_hw_read_reg(dev, ENE_HW_VER_OLD);
|
|
+
|
|
+ if (hw_revision == 0xFF) {
|
|
+
|
|
+ ene_printk(KERN_WARNING, "device seems to be disabled\n");
|
|
+ ene_printk(KERN_WARNING,
|
|
+ "send a mail to lirc-list@lists.sourceforge.net\n");
|
|
+ ene_printk(KERN_WARNING, "please attach output of acpidump\n");
|
|
+
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (chip_major == 0x33) {
|
|
+ ene_printk(KERN_WARNING, "chips 0x33xx aren't supported yet\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (chip_major == 0x39 && chip_minor == 0x26 && hw_revision == 0xC0) {
|
|
+ dev->hw_revision = ENE_HW_C;
|
|
+ ene_printk(KERN_WARNING,
|
|
+ "KB3926C detected, driver support is not complete!\n");
|
|
+
|
|
+ } else if (old_ver == 0x24 && hw_revision == 0xC0) {
|
|
+ dev->hw_revision = ENE_HW_B;
|
|
+ ene_printk(KERN_NOTICE, "KB3926B detected\n");
|
|
+ } else {
|
|
+ dev->hw_revision = ENE_HW_D;
|
|
+ ene_printk(KERN_WARNING,
|
|
+ "unknown ENE chip detected, assuming KB3926D\n");
|
|
+ ene_printk(KERN_WARNING, "driver support incomplete");
|
|
+
|
|
+ }
|
|
+
|
|
+ ene_printk(KERN_DEBUG, "chip is 0x%02x%02x - 0x%02x, 0x%02x\n",
|
|
+ chip_major, chip_minor, old_ver, hw_revision);
|
|
+
|
|
+
|
|
+ /* detect features hardware supports */
|
|
+
|
|
+ if (dev->hw_revision < ENE_HW_C)
|
|
+ return 0;
|
|
+
|
|
+ fw_capabilities = ene_hw_read_reg(dev, ENE_FW2);
|
|
+
|
|
+ dev->hw_gpio40_learning = fw_capabilities & ENE_FW2_GP40_AS_LEARN;
|
|
+ dev->hw_learning_and_tx_capable = fw_capabilities & ENE_FW2_LEARNING;
|
|
+
|
|
+ dev->hw_fan_as_normal_input = dev->hw_learning_and_tx_capable &&
|
|
+ fw_capabilities & ENE_FW2_FAN_AS_NRML_IN;
|
|
+
|
|
+ ene_printk(KERN_NOTICE, "hardware features:\n");
|
|
+ ene_printk(KERN_NOTICE,
|
|
+ "learning and tx %s, gpio40_learn %s, fan_in %s\n",
|
|
+ dev->hw_learning_and_tx_capable ? "on" : "off",
|
|
+ dev->hw_gpio40_learning ? "on" : "off",
|
|
+ dev->hw_fan_as_normal_input ? "on" : "off");
|
|
+
|
|
+ if (!dev->hw_learning_and_tx_capable && enable_learning)
|
|
+ enable_learning = 0;
|
|
+
|
|
+ if (dev->hw_learning_and_tx_capable) {
|
|
+ ene_printk(KERN_WARNING,
|
|
+ "Device supports transmitting, but the driver doesn't\n");
|
|
+ ene_printk(KERN_WARNING,
|
|
+ "due to lack of hardware to test against.\n");
|
|
+ ene_printk(KERN_WARNING,
|
|
+ "Send a mail to: lirc-list@lists.sourceforge.net\n");
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* hardware initialization */
|
|
+static int ene_hw_init(void *data)
|
|
+{
|
|
+ u8 reg_value;
|
|
+ struct ene_device *dev = (struct ene_device *)data;
|
|
+ dev->in_use = 1;
|
|
+
|
|
+ if (dev->hw_revision < ENE_HW_C) {
|
|
+ ene_hw_write_reg(dev, ENEB_IRQ, dev->irq << 1);
|
|
+ ene_hw_write_reg(dev, ENEB_IRQ_UNK1, 0x01);
|
|
+ } else {
|
|
+ reg_value = ene_hw_read_reg(dev, ENEC_IRQ) & 0xF0;
|
|
+ reg_value |= ENEC_IRQ_UNK_EN;
|
|
+ reg_value &= ~ENEC_IRQ_STATUS;
|
|
+ reg_value |= (dev->irq & ENEC_IRQ_MASK);
|
|
+ ene_hw_write_reg(dev, ENEC_IRQ, reg_value);
|
|
+ ene_hw_write_reg(dev, ENE_TX_UNK1, 0x63);
|
|
+ }
|
|
+
|
|
+ ene_hw_write_reg(dev, ENE_CIR_CONF2, 0x00);
|
|
+ ene_set_inputs(dev, enable_learning);
|
|
+
|
|
+ /* set sampling period */
|
|
+ ene_hw_write_reg(dev, ENE_CIR_SAMPLE_PERIOD, sample_period);
|
|
+
|
|
+ /* ack any pending irqs - just in case */
|
|
+ ene_hw_irq_status(dev, NULL);
|
|
+
|
|
+ /* enter idle mode */
|
|
+ ene_set_idle(dev, 1);
|
|
+
|
|
+ /* enable firmware bits */
|
|
+ ene_hw_write_reg_mask(dev, ENE_FW1,
|
|
+ ENE_FW1_ENABLE | ENE_FW1_IRQ,
|
|
+ ENE_FW1_ENABLE | ENE_FW1_IRQ);
|
|
+ /* clear stats */
|
|
+ dev->sample = 0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* this enables gpio40 signal, used if connected to wide band input*/
|
|
+static void ene_enable_gpio40(struct ene_device *dev, int enable)
|
|
+{
|
|
+ ene_hw_write_reg_mask(dev, ENE_CIR_CONF1, enable ?
|
|
+ 0 : ENE_CIR_CONF2_GPIO40DIS,
|
|
+ ENE_CIR_CONF2_GPIO40DIS);
|
|
+}
|
|
+
|
|
+/* this enables the classic sampler */
|
|
+static void ene_enable_normal_recieve(struct ene_device *dev, int enable)
|
|
+{
|
|
+ ene_hw_write_reg(dev, ENE_CIR_CONF1, enable ? ENE_CIR_CONF1_ADC_ON : 0);
|
|
+}
|
|
+
|
|
+/* this enables recieve via fan input */
|
|
+static void ene_enable_fan_recieve(struct ene_device *dev, int enable)
|
|
+{
|
|
+ if (!enable)
|
|
+ ene_hw_write_reg(dev, ENE_FAN_AS_IN1, 0);
|
|
+ else {
|
|
+ ene_hw_write_reg(dev, ENE_FAN_AS_IN1, ENE_FAN_AS_IN1_EN);
|
|
+ ene_hw_write_reg(dev, ENE_FAN_AS_IN2, ENE_FAN_AS_IN2_EN);
|
|
+ }
|
|
+ dev->fan_input_inuse = enable;
|
|
+}
|
|
+
|
|
+/* determine which input to use*/
|
|
+static void ene_set_inputs(struct ene_device *dev, int learning_enable)
|
|
+{
|
|
+ ene_enable_normal_recieve(dev, 1);
|
|
+
|
|
+ /* old hardware doesn't support learning mode for sure */
|
|
+ if (dev->hw_revision <= ENE_HW_B)
|
|
+ return;
|
|
+
|
|
+ /* reciever not learning capable, still set gpio40 correctly */
|
|
+ if (!dev->hw_learning_and_tx_capable) {
|
|
+ ene_enable_gpio40(dev, !dev->hw_gpio40_learning);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* enable learning mode */
|
|
+ if (learning_enable) {
|
|
+ ene_enable_gpio40(dev, dev->hw_gpio40_learning);
|
|
+
|
|
+ /* fan input is not used for learning */
|
|
+ if (dev->hw_fan_as_normal_input)
|
|
+ ene_enable_fan_recieve(dev, 0);
|
|
+
|
|
+ /* disable learning mode */
|
|
+ } else {
|
|
+ if (dev->hw_fan_as_normal_input) {
|
|
+ ene_enable_fan_recieve(dev, 1);
|
|
+ ene_enable_normal_recieve(dev, 0);
|
|
+ } else
|
|
+ ene_enable_gpio40(dev, !dev->hw_gpio40_learning);
|
|
+ }
|
|
+
|
|
+ /* set few additional settings for this mode */
|
|
+ ene_hw_write_reg_mask(dev, ENE_CIR_CONF1, learning_enable ?
|
|
+ ENE_CIR_CONF1_LEARN1 : 0, ENE_CIR_CONF1_LEARN1);
|
|
+
|
|
+ ene_hw_write_reg_mask(dev, ENE_CIR_CONF2, learning_enable ?
|
|
+ ENE_CIR_CONF2_LEARN2 : 0, ENE_CIR_CONF2_LEARN2);
|
|
+}
|
|
+
|
|
+/* deinitialization */
|
|
+static void ene_hw_deinit(void *data)
|
|
+{
|
|
+ struct ene_device *dev = (struct ene_device *)data;
|
|
+
|
|
+ /* disable samplers */
|
|
+ ene_enable_normal_recieve(dev, 0);
|
|
+
|
|
+ if (dev->hw_fan_as_normal_input)
|
|
+ ene_enable_fan_recieve(dev, 0);
|
|
+
|
|
+ /* disable hardware IRQ and firmware flag */
|
|
+ ene_hw_write_reg_mask(dev, ENE_FW1, 0, ENE_FW1_ENABLE | ENE_FW1_IRQ);
|
|
+
|
|
+ ene_set_idle(dev, 1);
|
|
+ dev->in_use = 0;
|
|
+}
|
|
+
|
|
+/* sends current sample to userspace */
|
|
+static void send_sample(struct ene_device *dev)
|
|
+{
|
|
+ int value = abs(dev->sample) & PULSE_MASK;
|
|
+
|
|
+ if (dev->sample > 0)
|
|
+ value |= PULSE_BIT;
|
|
+
|
|
+ if (!lirc_buffer_full(dev->lirc_driver->rbuf)) {
|
|
+ lirc_buffer_write(dev->lirc_driver->rbuf, (void *)&value);
|
|
+ wake_up(&dev->lirc_driver->rbuf->wait_poll);
|
|
+ }
|
|
+ dev->sample = 0;
|
|
+}
|
|
+
|
|
+/* this updates current sample */
|
|
+static void update_sample(struct ene_device *dev, int sample)
|
|
+{
|
|
+ if (!dev->sample)
|
|
+ dev->sample = sample;
|
|
+ else if (same_sign(dev->sample, sample))
|
|
+ dev->sample += sample;
|
|
+ else {
|
|
+ send_sample(dev);
|
|
+ dev->sample = sample;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* enable or disable idle mode */
|
|
+static void ene_set_idle(struct ene_device *dev, int idle)
|
|
+{
|
|
+ struct timeval now;
|
|
+ int disable = idle && enable_idle && (dev->hw_revision < ENE_HW_C);
|
|
+
|
|
+ ene_hw_write_reg_mask(dev, ENE_CIR_SAMPLE_PERIOD,
|
|
+ disable ? 0 : ENE_CIR_SAMPLE_OVERFLOW,
|
|
+ ENE_CIR_SAMPLE_OVERFLOW);
|
|
+ dev->idle = idle;
|
|
+
|
|
+ /* remember when we have entered the idle mode */
|
|
+ if (idle) {
|
|
+ do_gettimeofday(&dev->gap_start);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* send the gap between keypresses now */
|
|
+ do_gettimeofday(&now);
|
|
+
|
|
+ if (now.tv_sec - dev->gap_start.tv_sec > 16)
|
|
+ dev->sample = space(PULSE_MASK);
|
|
+ else
|
|
+ dev->sample = dev->sample +
|
|
+ space(1000000ull * (now.tv_sec - dev->gap_start.tv_sec))
|
|
+ + space(now.tv_usec - dev->gap_start.tv_usec);
|
|
+
|
|
+ if (abs(dev->sample) > PULSE_MASK)
|
|
+ dev->sample = space(PULSE_MASK);
|
|
+ send_sample(dev);
|
|
+}
|
|
+
|
|
+/* interrupt handler */
|
|
+static irqreturn_t ene_hw_irq(int irq, void *data)
|
|
+{
|
|
+ u16 hw_value;
|
|
+ int i, hw_sample;
|
|
+ int space;
|
|
+ int buffer_pointer;
|
|
+ int irq_status;
|
|
+
|
|
+ struct ene_device *dev = (struct ene_device *)data;
|
|
+ irq_status = ene_hw_irq_status(dev, &buffer_pointer);
|
|
+
|
|
+ if (!irq_status)
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ /* TODO: only RX for now */
|
|
+ if (irq_status == ENE_IRQ_TX)
|
|
+ return IRQ_HANDLED;
|
|
+
|
|
+ for (i = 0; i < ENE_SAMPLES_SIZE; i++) {
|
|
+
|
|
+ hw_value = ene_hw_read_reg(dev,
|
|
+ ENE_SAMPLE_BUFFER + buffer_pointer + i);
|
|
+
|
|
+ if (dev->fan_input_inuse) {
|
|
+ /* read high part of the sample */
|
|
+ hw_value |= ene_hw_read_reg(dev,
|
|
+ ENE_SAMPLE_BUFFER_FAN + buffer_pointer + i) << 8;
|
|
+
|
|
+ /* test for _space_ bit */
|
|
+ space = !(hw_value & ENE_FAN_SMPL_PULS_MSK);
|
|
+
|
|
+ /* clear space bit, and other unused bits */
|
|
+ hw_value &= ENE_FAN_VALUE_MASK;
|
|
+ hw_sample = hw_value * ENE_SAMPLE_PERIOD_FAN;
|
|
+
|
|
+ } else {
|
|
+ space = hw_value & ENE_SAMPLE_SPC_MASK;
|
|
+ hw_value &= ENE_SAMPLE_VALUE_MASK;
|
|
+ hw_sample = hw_value * sample_period;
|
|
+ }
|
|
+
|
|
+ /* no more data */
|
|
+ if (!(hw_value))
|
|
+ break;
|
|
+
|
|
+ if (space)
|
|
+ hw_sample *= -1;
|
|
+
|
|
+ /* overflow sample recieved, handle it */
|
|
+
|
|
+ if (!dev->fan_input_inuse && hw_value == ENE_SAMPLE_OVERFLOW) {
|
|
+
|
|
+ if (dev->idle)
|
|
+ continue;
|
|
+
|
|
+ if (dev->sample > 0 || abs(dev->sample) <= ENE_MAXGAP)
|
|
+ update_sample(dev, hw_sample);
|
|
+ else
|
|
+ ene_set_idle(dev, 1);
|
|
+
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* normal first sample recieved */
|
|
+ if (!dev->fan_input_inuse && dev->idle) {
|
|
+ ene_set_idle(dev, 0);
|
|
+
|
|
+ /* discard first recieved value, its random
|
|
+ since its the time signal was off before
|
|
+ first pulse if idle mode is enabled, HW
|
|
+ does that for us */
|
|
+
|
|
+ if (!enable_idle)
|
|
+ continue;
|
|
+ }
|
|
+ update_sample(dev, hw_sample);
|
|
+ send_sample(dev);
|
|
+ }
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int ene_probe(struct pnp_dev *pnp_dev,
|
|
+ const struct pnp_device_id *dev_id)
|
|
+{
|
|
+ struct ene_device *dev;
|
|
+ struct lirc_driver *lirc_driver;
|
|
+ int error = -ENOMEM;
|
|
+
|
|
+ dev = kzalloc(sizeof(struct ene_device), GFP_KERNEL);
|
|
+
|
|
+ if (!dev)
|
|
+ goto err1;
|
|
+
|
|
+ dev->pnp_dev = pnp_dev;
|
|
+ pnp_set_drvdata(pnp_dev, dev);
|
|
+
|
|
+
|
|
+ /* prepare lirc interface */
|
|
+ error = -ENOMEM;
|
|
+ lirc_driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
|
|
+
|
|
+ if (!lirc_driver)
|
|
+ goto err2;
|
|
+
|
|
+ dev->lirc_driver = lirc_driver;
|
|
+
|
|
+ strcpy(lirc_driver->name, ENE_DRIVER_NAME);
|
|
+ lirc_driver->minor = -1;
|
|
+ lirc_driver->code_length = sizeof(int) * 8;
|
|
+ lirc_driver->features = LIRC_CAN_REC_MODE2;
|
|
+ lirc_driver->data = dev;
|
|
+ lirc_driver->set_use_inc = ene_hw_init;
|
|
+ lirc_driver->set_use_dec = ene_hw_deinit;
|
|
+ lirc_driver->dev = &pnp_dev->dev;
|
|
+ lirc_driver->owner = THIS_MODULE;
|
|
+
|
|
+ lirc_driver->rbuf = kzalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
|
|
+
|
|
+ if (!lirc_driver->rbuf)
|
|
+ goto err3;
|
|
+
|
|
+ if (lirc_buffer_init(lirc_driver->rbuf, sizeof(int), sizeof(int) * 256))
|
|
+ goto err4;
|
|
+
|
|
+ error = -ENODEV;
|
|
+ if (lirc_register_driver(lirc_driver))
|
|
+ goto err5;
|
|
+
|
|
+ /* validate resources */
|
|
+ if (!pnp_port_valid(pnp_dev, 0) ||
|
|
+ pnp_port_len(pnp_dev, 0) < ENE_MAX_IO)
|
|
+ goto err6;
|
|
+
|
|
+ if (!pnp_irq_valid(pnp_dev, 0))
|
|
+ goto err6;
|
|
+
|
|
+ dev->hw_io = pnp_port_start(pnp_dev, 0);
|
|
+ dev->irq = pnp_irq(pnp_dev, 0);
|
|
+
|
|
+ /* claim the resources */
|
|
+ error = -EBUSY;
|
|
+ if (!request_region(dev->hw_io, ENE_MAX_IO, ENE_DRIVER_NAME))
|
|
+ goto err6;
|
|
+
|
|
+ if (request_irq(dev->irq, ene_hw_irq,
|
|
+ IRQF_SHARED, ENE_DRIVER_NAME, (void *)dev))
|
|
+ goto err7;
|
|
+
|
|
+ /* detect hardware version and features */
|
|
+ error = ene_hw_detect(dev);
|
|
+ if (error)
|
|
+ goto err8;
|
|
+
|
|
+ ene_printk(KERN_NOTICE, "driver has been succesfully loaded\n");
|
|
+ return 0;
|
|
+
|
|
+err8:
|
|
+ free_irq(dev->irq, dev);
|
|
+err7:
|
|
+ release_region(dev->hw_io, ENE_MAX_IO);
|
|
+err6:
|
|
+ lirc_unregister_driver(lirc_driver->minor);
|
|
+err5:
|
|
+ lirc_buffer_free(lirc_driver->rbuf);
|
|
+err4:
|
|
+ kfree(lirc_driver->rbuf);
|
|
+err3:
|
|
+ kfree(lirc_driver);
|
|
+err2:
|
|
+ kfree(dev);
|
|
+err1:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static void ene_remove(struct pnp_dev *pnp_dev)
|
|
+{
|
|
+ struct ene_device *dev = pnp_get_drvdata(pnp_dev);
|
|
+ ene_hw_deinit(dev);
|
|
+ free_irq(dev->irq, dev);
|
|
+ release_region(dev->hw_io, ENE_MAX_IO);
|
|
+ lirc_unregister_driver(dev->lirc_driver->minor);
|
|
+ lirc_buffer_free(dev->lirc_driver->rbuf);
|
|
+ kfree(dev->lirc_driver);
|
|
+ kfree(dev);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+
|
|
+/* TODO: make 'wake on IR' configurable and add .shutdown */
|
|
+/* currently impossible due to lack of kernel support */
|
|
+
|
|
+static int ene_suspend(struct pnp_dev *pnp_dev, pm_message_t state)
|
|
+{
|
|
+ struct ene_device *dev = pnp_get_drvdata(pnp_dev);
|
|
+ ene_hw_write_reg_mask(dev, ENE_FW1, ENE_FW1_WAKE, ENE_FW1_WAKE);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ene_resume(struct pnp_dev *pnp_dev)
|
|
+{
|
|
+ struct ene_device *dev = pnp_get_drvdata(pnp_dev);
|
|
+ if (dev->in_use)
|
|
+ ene_hw_init(dev);
|
|
+
|
|
+ ene_hw_write_reg_mask(dev, ENE_FW1, 0, ENE_FW1_WAKE);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#endif
|
|
+
|
|
+static const struct pnp_device_id ene_ids[] = {
|
|
+ {.id = "ENE0100",},
|
|
+ {},
|
|
+};
|
|
+
|
|
+static struct pnp_driver ene_driver = {
|
|
+ .name = ENE_DRIVER_NAME,
|
|
+ .id_table = ene_ids,
|
|
+ .flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
|
|
+
|
|
+ .probe = ene_probe,
|
|
+ .remove = __devexit_p(ene_remove),
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+ .suspend = ene_suspend,
|
|
+ .resume = ene_resume,
|
|
+#endif
|
|
+};
|
|
+
|
|
+static int __init ene_init(void)
|
|
+{
|
|
+ if (sample_period < 5) {
|
|
+ ene_printk(KERN_ERR, "sample period must be at\n");
|
|
+ ene_printk(KERN_ERR, "least 5 us, (at least 30 recommended)\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ return pnp_register_driver(&ene_driver);
|
|
+}
|
|
+
|
|
+static void ene_exit(void)
|
|
+{
|
|
+ pnp_unregister_driver(&ene_driver);
|
|
+}
|
|
+
|
|
+module_param(sample_period, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(sample_period, "Hardware sample period (75 us default)");
|
|
+
|
|
+module_param(enable_idle, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(enable_idle,
|
|
+ "Enables turning off signal sampling after long inactivity time; "
|
|
+ "if disabled might help detecting input signal (default: enabled)");
|
|
+
|
|
+module_param(enable_learning, bool, S_IRUGO);
|
|
+MODULE_PARM_DESC(enable_learning, "Use wide band (learning) reciever");
|
|
+
|
|
+MODULE_DEVICE_TABLE(pnp, ene_ids);
|
|
+MODULE_DESCRIPTION
|
|
+ ("LIRC driver for KB3926B/KB3926C/KB3926D (aka ENE0100) CIR port");
|
|
+MODULE_AUTHOR("Maxim Levitsky");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_init(ene_init);
|
|
+module_exit(ene_exit);
|
|
diff --git a/drivers/input/lirc/lirc_ene0100.h b/drivers/input/lirc/lirc_ene0100.h
|
|
new file mode 100644
|
|
index 0000000..953e7e4
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_ene0100.h
|
|
@@ -0,0 +1,169 @@
|
|
+/*
|
|
+ * driver for ENE KB3926 B/C/D CIR (also known as ENE0100)
|
|
+ *
|
|
+ * Copyright (C) 2009 Maxim Levitsky <maximlevitsky@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/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+/* hardware address */
|
|
+#define ENE_STATUS 0 /* hardware status - unused */
|
|
+#define ENE_ADDR_HI 1 /* hi byte of register address */
|
|
+#define ENE_ADDR_LO 2 /* low byte of register address */
|
|
+#define ENE_IO 3 /* read/write window */
|
|
+#define ENE_MAX_IO 4
|
|
+
|
|
+/* 8 bytes of samples, divided in 2 halfs*/
|
|
+#define ENE_SAMPLE_BUFFER 0xF8F0 /* regular sample buffer */
|
|
+#define ENE_SAMPLE_SPC_MASK (1 << 7) /* sample is space */
|
|
+#define ENE_SAMPLE_VALUE_MASK 0x7F
|
|
+#define ENE_SAMPLE_OVERFLOW 0x7F
|
|
+#define ENE_SAMPLES_SIZE 4
|
|
+
|
|
+/* fan input sample buffer */
|
|
+#define ENE_SAMPLE_BUFFER_FAN 0xF8FB /* this buffer holds high byte of */
|
|
+ /* each sample of normal buffer */
|
|
+
|
|
+#define ENE_FAN_SMPL_PULS_MSK 0x8000 /* this bit of combined sample */
|
|
+ /* if set, says that sample is pulse */
|
|
+#define ENE_FAN_VALUE_MASK 0x0FFF /* mask for valid bits of the value */
|
|
+
|
|
+/* first firmware register */
|
|
+#define ENE_FW1 0xF8F8
|
|
+#define ENE_FW1_ENABLE (1 << 0) /* enable fw processing */
|
|
+#define ENE_FW1_TXIRQ (1 << 1) /* TX interrupt pending */
|
|
+#define ENE_FW1_WAKE (1 << 6) /* enable wake from S3 */
|
|
+#define ENE_FW1_IRQ (1 << 7) /* enable interrupt */
|
|
+
|
|
+/* second firmware register */
|
|
+#define ENE_FW2 0xF8F9
|
|
+#define ENE_FW2_BUF_HIGH (1 << 0) /* which half of the buffer to read */
|
|
+#define ENE_FW2_IRQ_CLR (1 << 2) /* clear this on IRQ */
|
|
+#define ENE_FW2_GP40_AS_LEARN (1 << 4) /* normal input is used as */
|
|
+ /* learning input */
|
|
+#define ENE_FW2_FAN_AS_NRML_IN (1 << 6) /* fan is used as normal input */
|
|
+#define ENE_FW2_LEARNING (1 << 7) /* hardware supports learning and TX */
|
|
+
|
|
+/* fan as input settings - only if learning capable */
|
|
+#define ENE_FAN_AS_IN1 0xFE30 /* fan init reg 1 */
|
|
+#define ENE_FAN_AS_IN1_EN 0xCD
|
|
+#define ENE_FAN_AS_IN2 0xFE31 /* fan init reg 2 */
|
|
+#define ENE_FAN_AS_IN2_EN 0x03
|
|
+#define ENE_SAMPLE_PERIOD_FAN 61 /* fan input has fixed sample period */
|
|
+
|
|
+/* IRQ registers block (for revision B) */
|
|
+#define ENEB_IRQ 0xFD09 /* IRQ number */
|
|
+#define ENEB_IRQ_UNK1 0xFD17 /* unknown setting = 1 */
|
|
+#define ENEB_IRQ_STATUS 0xFD80 /* irq status */
|
|
+#define ENEB_IRQ_STATUS_IR (1 << 5) /* IR irq */
|
|
+
|
|
+/* IRQ registers block (for revision C,D) */
|
|
+#define ENEC_IRQ 0xFE9B /* new irq settings register */
|
|
+#define ENEC_IRQ_MASK 0x0F /* irq number mask */
|
|
+#define ENEC_IRQ_UNK_EN (1 << 4) /* always enabled */
|
|
+#define ENEC_IRQ_STATUS (1 << 5) /* irq status and ACK */
|
|
+
|
|
+/* CIR block settings */
|
|
+#define ENE_CIR_CONF1 0xFEC0
|
|
+#define ENE_CIR_CONF1_ADC_ON 0x7 /* reciever on gpio40 enabled */
|
|
+#define ENE_CIR_CONF1_LEARN1 (1 << 3) /* enabled on learning mode */
|
|
+#define ENE_CIR_CONF1_TX_ON 0x30 /* enabled on transmit */
|
|
+#define ENE_CIR_CONF1_TX_CARR (1 << 7) /* send TX carrier or not */
|
|
+
|
|
+#define ENE_CIR_CONF2 0xFEC1 /* unknown setting = 0 */
|
|
+#define ENE_CIR_CONF2_LEARN2 (1 << 4) /* set on enable learning */
|
|
+#define ENE_CIR_CONF2_GPIO40DIS (1 << 5) /* disable normal input via gpio40 */
|
|
+
|
|
+#define ENE_CIR_SAMPLE_PERIOD 0xFEC8 /* sample period in us */
|
|
+#define ENE_CIR_SAMPLE_OVERFLOW (1 << 7) /* interrupt on overflows if set */
|
|
+
|
|
+
|
|
+/* transmitter - not implemented yet */
|
|
+/* KB3926C and higher */
|
|
+/* transmission is very similiar to recieving, a byte is written to */
|
|
+/* ENE_TX_INPUT, in same manner as it is read from sample buffer */
|
|
+/* sample period is fixed*/
|
|
+
|
|
+
|
|
+/* transmitter ports */
|
|
+#define ENE_TX_PORT1 0xFC01 /* this enables one or both */
|
|
+#define ENE_TX_PORT1_EN (1 << 5) /* TX ports */
|
|
+#define ENE_TX_PORT2 0xFC08
|
|
+#define ENE_TX_PORT2_EN (1 << 1)
|
|
+
|
|
+#define ENE_TX_INPUT 0xFEC9 /* next byte to transmit */
|
|
+#define ENE_TX_SPC_MASK (1 << 7) /* Transmitted sample is space */
|
|
+#define ENE_TX_UNK1 0xFECB /* set to 0x63 */
|
|
+#define ENE_TX_SMPL_PERIOD 50 /* transmit sample period */
|
|
+
|
|
+
|
|
+#define ENE_TX_CARRIER 0xFECE /* TX carrier * 2 (khz) */
|
|
+#define ENE_TX_CARRIER_UNKBIT 0x80 /* This bit set on transmit */
|
|
+#define ENE_TX_CARRIER_LOW 0xFECF /* TX carrier / 2 */
|
|
+
|
|
+/* Hardware versions */
|
|
+#define ENE_HW_VERSION 0xFF00 /* hardware revision */
|
|
+#define ENE_HW_UNK 0xFF1D
|
|
+#define ENE_HW_UNK_CLR (1 << 2)
|
|
+#define ENE_HW_VER_MAJOR 0xFF1E /* chip version */
|
|
+#define ENE_HW_VER_MINOR 0xFF1F
|
|
+#define ENE_HW_VER_OLD 0xFD00
|
|
+
|
|
+#define same_sign(a, b) ((((a) > 0) && (b) > 0) || ((a) < 0 && (b) < 0))
|
|
+
|
|
+#define ENE_DRIVER_NAME "enecir"
|
|
+#define ENE_MAXGAP 250000 /* this is amount of time we wait
|
|
+ before turning the sampler, chosen
|
|
+ arbitry */
|
|
+
|
|
+#define space(len) (-(len)) /* add a space */
|
|
+
|
|
+/* software defines */
|
|
+#define ENE_IRQ_RX 1
|
|
+#define ENE_IRQ_TX 2
|
|
+
|
|
+#define ENE_HW_B 1 /* 3926B */
|
|
+#define ENE_HW_C 2 /* 3926C */
|
|
+#define ENE_HW_D 3 /* 3926D */
|
|
+
|
|
+#define ene_printk(level, text, ...) \
|
|
+ printk(level ENE_DRIVER_NAME ": " text, ## __VA_ARGS__)
|
|
+
|
|
+struct ene_device {
|
|
+ struct pnp_dev *pnp_dev;
|
|
+ struct lirc_driver *lirc_driver;
|
|
+
|
|
+ /* hw settings */
|
|
+ unsigned long hw_io;
|
|
+ int irq;
|
|
+
|
|
+ int hw_revision; /* hardware revision */
|
|
+ int hw_learning_and_tx_capable; /* learning capable */
|
|
+ int hw_gpio40_learning; /* gpio40 is learning */
|
|
+ int hw_fan_as_normal_input; /* fan input is used as regular input */
|
|
+
|
|
+ /* device data */
|
|
+ int idle;
|
|
+ int fan_input_inuse;
|
|
+
|
|
+ int sample;
|
|
+ int in_use;
|
|
+
|
|
+ struct timeval gap_start;
|
|
+};
|
|
diff --git a/drivers/input/lirc/lirc_i2c.c b/drivers/input/lirc/lirc_i2c.c
|
|
new file mode 100644
|
|
index 0000000..f3f8c2e
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_i2c.c
|
|
@@ -0,0 +1,536 @@
|
|
+/*
|
|
+ * lirc_i2c.c
|
|
+ *
|
|
+ * i2c IR driver for the onboard IR port on many TV tuner cards, including:
|
|
+ * -Flavors of the Hauppauge PVR-150/250/350
|
|
+ * -Hauppauge HVR-1300
|
|
+ * -PixelView (BT878P+W/FM)
|
|
+ * -KNC ONE TV Station/Anubis Typhoon TView Tuner
|
|
+ * -Asus TV-Box and Creative/VisionTek BreakOut-Box
|
|
+ * -Leadtek Winfast PVR2000
|
|
+ *
|
|
+ * Copyright (c) 2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
|
|
+ * modified for PixelView (BT878P+W/FM) by
|
|
+ * Michal Kochanowicz <mkochano@pld.org.pl>
|
|
+ * Christoph Bartelmus <lirc@bartelmus.de>
|
|
+ * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by
|
|
+ * Ulrich Mueller <ulrich.mueller42@web.de>
|
|
+ * modified for Asus TV-Box and Creative/VisionTek BreakOut-Box by
|
|
+ * Stefan Jahn <stefan@lkcc.org>
|
|
+ * modified for inclusion into kernel sources by
|
|
+ * Jerome Brock <jbrock@users.sourceforge.net>
|
|
+ * modified for Leadtek Winfast PVR2000 by
|
|
+ * Thomas Reitmayr (treitmayr@yahoo.com)
|
|
+ * modified for Hauppauge HVR-1300 by
|
|
+ * Jan Frey (jfrey@gmx.de)
|
|
+ *
|
|
+ * parts are cut&pasted from the old lirc_haup.c driver
|
|
+ *
|
|
+ * 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/version.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/kmod.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/timer.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/i2c-algo-bit.h>
|
|
+
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+struct IR {
|
|
+ struct lirc_driver l;
|
|
+ struct i2c_client c;
|
|
+ int nextkey;
|
|
+ unsigned char b[3];
|
|
+ unsigned char bits;
|
|
+ unsigned char flag;
|
|
+};
|
|
+
|
|
+#define DEVICE_NAME "lirc_i2c"
|
|
+
|
|
+/* module parameters */
|
|
+static int debug; /* debug output */
|
|
+static int minor = -1; /* minor number */
|
|
+
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG DEVICE_NAME ": " fmt, \
|
|
+ ## args); \
|
|
+ } while (0)
|
|
+
|
|
+static int reverse(int data, int bits)
|
|
+{
|
|
+ int i;
|
|
+ int c;
|
|
+
|
|
+ for (c = 0, i = 0; i < bits; i++)
|
|
+ c |= ((data & (1<<i)) ? 1 : 0) << (bits-1-i);
|
|
+
|
|
+ return c;
|
|
+}
|
|
+
|
|
+static int add_to_buf_adap(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+ unsigned char keybuf[4];
|
|
+
|
|
+ keybuf[0] = 0x00;
|
|
+ i2c_master_send(&ir->c, keybuf, 1);
|
|
+ /* poll IR chip */
|
|
+ if (i2c_master_recv(&ir->c, keybuf, sizeof(keybuf)) != sizeof(keybuf)) {
|
|
+ dprintk("read error\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ dprintk("key (0x%02x%02x%02x%02x)\n",
|
|
+ keybuf[0], keybuf[1], keybuf[2], keybuf[3]);
|
|
+
|
|
+ /* key pressed ? */
|
|
+ if (keybuf[2] == 0xff)
|
|
+ return -ENODATA;
|
|
+
|
|
+ /* remove repeat bit */
|
|
+ keybuf[2] &= 0x7f;
|
|
+ keybuf[3] |= 0x80;
|
|
+
|
|
+ lirc_buffer_write(buf, keybuf);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int add_to_buf_pcf8574(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+ int rc;
|
|
+ unsigned char all, mask;
|
|
+ unsigned char key;
|
|
+
|
|
+ /* compute all valid bits (key code + pressed/release flag) */
|
|
+ all = ir->bits | ir->flag;
|
|
+
|
|
+ /* save IR writable mask bits */
|
|
+ mask = i2c_smbus_read_byte(&ir->c) & ~all;
|
|
+
|
|
+ /* send bit mask */
|
|
+ rc = i2c_smbus_write_byte(&ir->c, (0xff & all) | mask);
|
|
+
|
|
+ /* receive scan code */
|
|
+ rc = i2c_smbus_read_byte(&ir->c);
|
|
+
|
|
+ if (rc == -1) {
|
|
+ dprintk("%s read error\n", ir->c.name);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ /* drop duplicate polls */
|
|
+ if (ir->b[0] == (rc & all))
|
|
+ return -ENODATA;
|
|
+
|
|
+ ir->b[0] = rc & all;
|
|
+
|
|
+ dprintk("%s key 0x%02X %s\n", ir->c.name, rc & ir->bits,
|
|
+ (rc & ir->flag) ? "released" : "pressed");
|
|
+
|
|
+ /* ignore released buttons */
|
|
+ if (rc & ir->flag)
|
|
+ return -ENODATA;
|
|
+
|
|
+ /* set valid key code */
|
|
+ key = rc & ir->bits;
|
|
+ lirc_buffer_write(buf, &key);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* common for Hauppauge IR receivers */
|
|
+static int add_to_buf_haup_common(void *data, struct lirc_buffer *buf,
|
|
+ unsigned char *keybuf, int size, int offset)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+ __u16 code;
|
|
+ unsigned char codes[2];
|
|
+ int ret;
|
|
+
|
|
+ /* poll IR chip */
|
|
+ ret = i2c_master_recv(&ir->c, keybuf, size);
|
|
+ if (ret == size) {
|
|
+ ir->b[0] = keybuf[offset];
|
|
+ ir->b[1] = keybuf[offset+1];
|
|
+ ir->b[2] = keybuf[offset+2];
|
|
+ if (ir->b[0] != 0x00 && ir->b[1] != 0x00)
|
|
+ dprintk("key (0x%02x/0x%02x)\n", ir->b[0], ir->b[1]);
|
|
+ } else {
|
|
+ dprintk("read error (ret=%d)\n", ret);
|
|
+ /* keep last successful read buffer */
|
|
+ }
|
|
+
|
|
+ /* key pressed ? */
|
|
+ if ((ir->b[0] & 0x80) == 0)
|
|
+ return -ENODATA;
|
|
+
|
|
+ /* look what we have */
|
|
+ code = (((__u16)ir->b[0]&0x7f)<<6) | (ir->b[1]>>2);
|
|
+
|
|
+ codes[0] = (code >> 8) & 0xff;
|
|
+ codes[1] = code & 0xff;
|
|
+
|
|
+ /* return it */
|
|
+ dprintk("sending code 0x%02x%02x to lirc\n", codes[0], codes[1]);
|
|
+ lirc_buffer_write(buf, codes);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* specific for the Hauppauge PVR150 IR receiver */
|
|
+static int add_to_buf_haup_pvr150(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ unsigned char keybuf[6];
|
|
+ /* fetch 6 bytes, first relevant is at offset 3 */
|
|
+ return add_to_buf_haup_common(data, buf, keybuf, 6, 3);
|
|
+}
|
|
+
|
|
+/* used for all Hauppauge IR receivers but the PVR150 */
|
|
+static int add_to_buf_haup(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ unsigned char keybuf[3];
|
|
+ /* fetch 3 bytes, first relevant is at offset 0 */
|
|
+ return add_to_buf_haup_common(data, buf, keybuf, 3, 0);
|
|
+}
|
|
+
|
|
+
|
|
+static int add_to_buf_pvr2000(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+ unsigned char key;
|
|
+ s32 flags;
|
|
+ s32 code;
|
|
+
|
|
+ /* poll IR chip */
|
|
+ flags = i2c_smbus_read_byte_data(&ir->c, 0x10);
|
|
+ if (-1 == flags) {
|
|
+ dprintk("read error\n");
|
|
+ return -ENODATA;
|
|
+ }
|
|
+ /* key pressed ? */
|
|
+ if (0 == (flags & 0x80))
|
|
+ return -ENODATA;
|
|
+
|
|
+ /* read actual key code */
|
|
+ code = i2c_smbus_read_byte_data(&ir->c, 0x00);
|
|
+ if (-1 == code) {
|
|
+ dprintk("read error\n");
|
|
+ return -ENODATA;
|
|
+ }
|
|
+
|
|
+ key = code & 0xFF;
|
|
+
|
|
+ dprintk("IR Key/Flags: (0x%02x/0x%02x)\n", key, flags & 0xFF);
|
|
+
|
|
+ /* return it */
|
|
+ lirc_buffer_write(buf, &key);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int add_to_buf_pixelview(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+ unsigned char key;
|
|
+
|
|
+ /* poll IR chip */
|
|
+ if (1 != i2c_master_recv(&ir->c, &key, 1)) {
|
|
+ dprintk("read error\n");
|
|
+ return -1;
|
|
+ }
|
|
+ dprintk("key %02x\n", key);
|
|
+
|
|
+ /* return it */
|
|
+ lirc_buffer_write(buf, &key);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int add_to_buf_pv951(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+ unsigned char key;
|
|
+ unsigned char codes[4];
|
|
+
|
|
+ /* poll IR chip */
|
|
+ if (1 != i2c_master_recv(&ir->c, &key, 1)) {
|
|
+ dprintk("read error\n");
|
|
+ return -ENODATA;
|
|
+ }
|
|
+ /* ignore 0xaa */
|
|
+ if (key == 0xaa)
|
|
+ return -ENODATA;
|
|
+ dprintk("key %02x\n", key);
|
|
+
|
|
+ codes[0] = 0x61;
|
|
+ codes[1] = 0xD6;
|
|
+ codes[2] = reverse(key, 8);
|
|
+ codes[3] = (~codes[2])&0xff;
|
|
+
|
|
+ lirc_buffer_write(buf, codes);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int add_to_buf_knc1(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ static unsigned char last_key = 0xFF;
|
|
+ struct IR *ir = data;
|
|
+ unsigned char key;
|
|
+
|
|
+ /* poll IR chip */
|
|
+ if (1 != i2c_master_recv(&ir->c, &key, 1)) {
|
|
+ dprintk("read error\n");
|
|
+ return -ENODATA;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * it seems that 0xFE indicates that a button is still held
|
|
+ * down, while 0xFF indicates that no button is held
|
|
+ * down. 0xFE sequences are sometimes interrupted by 0xFF
|
|
+ */
|
|
+
|
|
+ dprintk("key %02x\n", key);
|
|
+
|
|
+ if (key == 0xFF)
|
|
+ return -ENODATA;
|
|
+
|
|
+ if (key == 0xFE)
|
|
+ key = last_key;
|
|
+
|
|
+ last_key = key;
|
|
+ lirc_buffer_write(buf, &key);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int set_use_inc(void *data)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+
|
|
+ dprintk("%s called\n", __func__);
|
|
+
|
|
+ /* lock bttv in memory while /dev/lirc is in use */
|
|
+ i2c_use_client(&ir->c);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void set_use_dec(void *data)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+
|
|
+ dprintk("%s called\n", __func__);
|
|
+
|
|
+ i2c_release_client(&ir->c);
|
|
+}
|
|
+
|
|
+static struct lirc_driver lirc_template = {
|
|
+ .name = "lirc_i2c",
|
|
+ .set_use_inc = set_use_inc,
|
|
+ .set_use_dec = set_use_dec,
|
|
+ .dev = NULL,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id);
|
|
+static int ir_remove(struct i2c_client *client);
|
|
+static int ir_command(struct i2c_client *client, unsigned int cmd, void *arg);
|
|
+
|
|
+static const struct i2c_device_id ir_receiver_id[] = {
|
|
+ /* Generic entry for any IR receiver */
|
|
+ { "ir_video", 0 },
|
|
+ /* IR device specific entries could be added here */
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct i2c_driver driver = {
|
|
+ .driver = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .name = "i2c ir driver",
|
|
+ },
|
|
+ .probe = ir_probe,
|
|
+ .remove = ir_remove,
|
|
+ .id_table = ir_receiver_id,
|
|
+ .command = ir_command,
|
|
+};
|
|
+
|
|
+static void pcf_probe(struct i2c_client *client, struct IR *ir)
|
|
+{
|
|
+ int ret1, ret2, ret3, ret4;
|
|
+
|
|
+ ret1 = i2c_smbus_write_byte(client, 0xff);
|
|
+ ret2 = i2c_smbus_read_byte(client);
|
|
+ ret3 = i2c_smbus_write_byte(client, 0x00);
|
|
+ ret4 = i2c_smbus_read_byte(client);
|
|
+
|
|
+ /* in the Asus TV-Box: bit 1-0 */
|
|
+ if (((ret2 & 0x03) == 0x03) && ((ret4 & 0x03) == 0x00)) {
|
|
+ ir->bits = (unsigned char) ~0x07;
|
|
+ ir->flag = 0x04;
|
|
+ /* in the Creative/VisionTek BreakOut-Box: bit 7-6 */
|
|
+ } else if (((ret2 & 0xc0) == 0xc0) && ((ret4 & 0xc0) == 0x00)) {
|
|
+ ir->bits = (unsigned char) ~0xe0;
|
|
+ ir->flag = 0x20;
|
|
+ }
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|
+{
|
|
+ struct IR *ir;
|
|
+ struct i2c_adapter *adap = client->adapter;
|
|
+ unsigned short addr = client->addr;
|
|
+ int retval;
|
|
+
|
|
+ ir = kzalloc(sizeof(struct IR), GFP_KERNEL);
|
|
+ if (!ir)
|
|
+ return -ENOMEM;
|
|
+ memcpy(&ir->l, &lirc_template, sizeof(struct lirc_driver));
|
|
+ memcpy(&ir->c, client, sizeof(struct i2c_client));
|
|
+
|
|
+ i2c_set_clientdata(client, ir);
|
|
+ ir->l.data = ir;
|
|
+ ir->l.minor = minor;
|
|
+ ir->l.sample_rate = 10;
|
|
+ ir->l.dev = &ir->c.dev;
|
|
+ ir->nextkey = -1;
|
|
+
|
|
+ switch (addr) {
|
|
+ case 0x64:
|
|
+ strlcpy(ir->c.name, "Pixelview IR", I2C_NAME_SIZE);
|
|
+ ir->l.code_length = 8;
|
|
+ ir->l.add_to_buf = add_to_buf_pixelview;
|
|
+ break;
|
|
+ case 0x4b:
|
|
+ strlcpy(ir->c.name, "PV951 IR", I2C_NAME_SIZE);
|
|
+ ir->l.code_length = 32;
|
|
+ ir->l.add_to_buf = add_to_buf_pv951;
|
|
+ break;
|
|
+ case 0x71:
|
|
+ if (adap->id == I2C_HW_B_CX2388x)
|
|
+ strlcpy(ir->c.name, "Hauppauge HVR1300", I2C_NAME_SIZE);
|
|
+ else /* bt8xx or cx2341x */
|
|
+ /*
|
|
+ * The PVR150 IR receiver uses the same protocol as
|
|
+ * other Hauppauge cards, but the data flow is
|
|
+ * different, so we need to deal with it by its own.
|
|
+ */
|
|
+ strlcpy(ir->c.name, "Hauppauge PVR150", I2C_NAME_SIZE);
|
|
+ ir->l.code_length = 13;
|
|
+ ir->l.add_to_buf = add_to_buf_haup_pvr150;
|
|
+ break;
|
|
+ case 0x6b:
|
|
+ strlcpy(ir->c.name, "Adaptec IR", I2C_NAME_SIZE);
|
|
+ ir->l.code_length = 32;
|
|
+ ir->l.add_to_buf = add_to_buf_adap;
|
|
+ break;
|
|
+ case 0x18:
|
|
+ case 0x1a:
|
|
+ if (adap->id == I2C_HW_B_CX2388x) {
|
|
+ strlcpy(ir->c.name, "Leadtek IR", I2C_NAME_SIZE);
|
|
+ ir->l.code_length = 8;
|
|
+ ir->l.add_to_buf = add_to_buf_pvr2000;
|
|
+ } else { /* bt8xx or cx2341x */
|
|
+ strlcpy(ir->c.name, "Hauppauge IR", I2C_NAME_SIZE);
|
|
+ ir->l.code_length = 13;
|
|
+ ir->l.add_to_buf = add_to_buf_haup;
|
|
+ }
|
|
+ break;
|
|
+ case 0x30:
|
|
+ strlcpy(ir->c.name, "KNC ONE IR", I2C_NAME_SIZE);
|
|
+ ir->l.code_length = 8;
|
|
+ ir->l.add_to_buf = add_to_buf_knc1;
|
|
+ break;
|
|
+ case 0x21:
|
|
+ case 0x23:
|
|
+ pcf_probe(client, ir);
|
|
+ strlcpy(ir->c.name, "TV-Box IR", I2C_NAME_SIZE);
|
|
+ ir->l.code_length = 8;
|
|
+ ir->l.add_to_buf = add_to_buf_pcf8574;
|
|
+ break;
|
|
+ default:
|
|
+ /* shouldn't happen */
|
|
+ printk("lirc_i2c: Huh? unknown i2c address (0x%02x)?\n", addr);
|
|
+ kfree(ir);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ printk(KERN_INFO "lirc_i2c: chip 0x%x found @ 0x%02x (%s)\n",
|
|
+ adap->id, addr, ir->c.name);
|
|
+
|
|
+ retval = lirc_register_driver(&ir->l);
|
|
+
|
|
+ if (retval < 0) {
|
|
+ printk(KERN_ERR "lirc_i2c: failed to register driver!\n");
|
|
+ kfree(ir);
|
|
+ return retval;
|
|
+ }
|
|
+
|
|
+ ir->l.minor = retval;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ir_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct IR *ir = i2c_get_clientdata(client);
|
|
+
|
|
+ /* unregister device */
|
|
+ lirc_unregister_driver(ir->l.minor);
|
|
+
|
|
+ /* free memory */
|
|
+ kfree(ir);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ir_command(struct i2c_client *client, unsigned int cmd, void *arg)
|
|
+{
|
|
+ /* nothing */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __init lirc_i2c_init(void)
|
|
+{
|
|
+ i2c_add_driver(&driver);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit lirc_i2c_exit(void)
|
|
+{
|
|
+ i2c_del_driver(&driver);
|
|
+}
|
|
+
|
|
+MODULE_DESCRIPTION("Infrared receiver driver for Hauppauge and "
|
|
+ "Pixelview cards (i2c stack)");
|
|
+MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, "
|
|
+ "Ulrich Mueller, Stefan Jahn, Jerome Brock");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_param(minor, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(minor, "Preferred minor device number");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
+
|
|
+module_init(lirc_i2c_init);
|
|
+module_exit(lirc_i2c_exit);
|
|
diff --git a/drivers/input/lirc/lirc_igorplugusb.c b/drivers/input/lirc/lirc_igorplugusb.c
|
|
new file mode 100644
|
|
index 0000000..599037d
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_igorplugusb.c
|
|
@@ -0,0 +1,556 @@
|
|
+/*
|
|
+ * lirc_igorplugusb - USB remote support for LIRC
|
|
+ *
|
|
+ * Supports the standard homebrew IgorPlugUSB receiver with Igor's firmware.
|
|
+ * See http://www.cesko.host.sk/IgorPlugUSB/IgorPlug-USB%20(AVR)_eng.htm
|
|
+ *
|
|
+ * The device can only record bursts of up to 36 pulses/spaces.
|
|
+ * Works fine with RC5. Longer commands lead to device buffer overrun.
|
|
+ * (Maybe a better firmware or a microcontroller with more ram can help?)
|
|
+ *
|
|
+ * Version 0.1 [beta status]
|
|
+ *
|
|
+ * Copyright (C) 2004 Jan M. Hochstein
|
|
+ * <hochstein@algo.informatik.tu-darmstadt.de>
|
|
+ *
|
|
+ * This driver was derived from:
|
|
+ * Paul Miller <pmiller9@users.sourceforge.net>
|
|
+ * "lirc_atiusb" module
|
|
+ * Vladimir Dergachev <volodya@minspring.com>'s 2002
|
|
+ * "USB ATI Remote support" (input device)
|
|
+ * Adrian Dewhurst <sailor-lk@sailorfrag.net>'s 2002
|
|
+ * "USB StreamZap remote driver" (LIRC)
|
|
+ * Artur Lipowski <alipowski@kki.net.pl>'s 2002
|
|
+ * "lirc_dev" and "lirc_gpio" LIRC modules
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * 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/kernel.h>
|
|
+#include <linux/kmod.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/usb.h>
|
|
+#include <linux/smp_lock.h>
|
|
+#include <linux/time.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+
|
|
+/* module identification */
|
|
+#define DRIVER_VERSION "0.1"
|
|
+#define DRIVER_AUTHOR \
|
|
+ "Jan M. Hochstein <hochstein@algo.informatik.tu-darmstadt.de>"
|
|
+#define DRIVER_DESC "USB remote driver for LIRC"
|
|
+#define DRIVER_NAME "lirc_igorplugusb"
|
|
+
|
|
+/* debugging support */
|
|
+#ifdef CONFIG_USB_DEBUG
|
|
+static int debug = 1;
|
|
+#else
|
|
+static int debug;
|
|
+#endif
|
|
+
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG fmt, ## args); \
|
|
+ } while (0)
|
|
+
|
|
+/* One mode2 pulse/space has 4 bytes. */
|
|
+#define CODE_LENGTH sizeof(int)
|
|
+
|
|
+/* Igor's firmware cannot record bursts longer than 36. */
|
|
+#define DEVICE_BUFLEN 36
|
|
+
|
|
+/*
|
|
+ * Header at the beginning of the device's buffer:
|
|
+ * unsigned char data_length
|
|
+ * unsigned char data_start (!=0 means ring-buffer overrun)
|
|
+ * unsigned char counter (incremented by each burst)
|
|
+ */
|
|
+#define DEVICE_HEADERLEN 3
|
|
+
|
|
+/* This is for the gap */
|
|
+#define ADDITIONAL_LIRC_BYTES 2
|
|
+
|
|
+/* times to poll per second */
|
|
+#define SAMPLE_RATE 100
|
|
+static int sample_rate = SAMPLE_RATE;
|
|
+
|
|
+
|
|
+/**** Igor's USB Request Codes */
|
|
+
|
|
+#define SET_INFRABUFFER_EMPTY 1
|
|
+/**
|
|
+ * Params: none
|
|
+ * Answer: empty
|
|
+ */
|
|
+
|
|
+#define GET_INFRACODE 2
|
|
+/**
|
|
+ * Params:
|
|
+ * wValue: offset to begin reading infra buffer
|
|
+ *
|
|
+ * Answer: infra data
|
|
+ */
|
|
+
|
|
+#define SET_DATAPORT_DIRECTION 3
|
|
+/**
|
|
+ * Params:
|
|
+ * wValue: (byte) 1 bit for each data port pin (0=in, 1=out)
|
|
+ *
|
|
+ * Answer: empty
|
|
+ */
|
|
+
|
|
+#define GET_DATAPORT_DIRECTION 4
|
|
+/**
|
|
+ * Params: none
|
|
+ *
|
|
+ * Answer: (byte) 1 bit for each data port pin (0=in, 1=out)
|
|
+ */
|
|
+
|
|
+#define SET_OUT_DATAPORT 5
|
|
+/**
|
|
+ * Params:
|
|
+ * wValue: byte to write to output data port
|
|
+ *
|
|
+ * Answer: empty
|
|
+ */
|
|
+
|
|
+#define GET_OUT_DATAPORT 6
|
|
+/**
|
|
+ * Params: none
|
|
+ *
|
|
+ * Answer: least significant 3 bits read from output data port
|
|
+ */
|
|
+
|
|
+#define GET_IN_DATAPORT 7
|
|
+/**
|
|
+ * Params: none
|
|
+ *
|
|
+ * Answer: least significant 3 bits read from input data port
|
|
+ */
|
|
+
|
|
+#define READ_EEPROM 8
|
|
+/**
|
|
+ * Params:
|
|
+ * wValue: offset to begin reading EEPROM
|
|
+ *
|
|
+ * Answer: EEPROM bytes
|
|
+ */
|
|
+
|
|
+#define WRITE_EEPROM 9
|
|
+/**
|
|
+ * Params:
|
|
+ * wValue: offset to EEPROM byte
|
|
+ * wIndex: byte to write
|
|
+ *
|
|
+ * Answer: empty
|
|
+ */
|
|
+
|
|
+#define SEND_RS232 10
|
|
+/**
|
|
+ * Params:
|
|
+ * wValue: byte to send
|
|
+ *
|
|
+ * Answer: empty
|
|
+ */
|
|
+
|
|
+#define RECV_RS232 11
|
|
+/**
|
|
+ * Params: none
|
|
+ *
|
|
+ * Answer: byte received
|
|
+ */
|
|
+
|
|
+#define SET_RS232_BAUD 12
|
|
+/**
|
|
+ * Params:
|
|
+ * wValue: byte to write to UART bit rate register (UBRR)
|
|
+ *
|
|
+ * Answer: empty
|
|
+ */
|
|
+
|
|
+#define GET_RS232_BAUD 13
|
|
+/**
|
|
+ * Params: none
|
|
+ *
|
|
+ * Answer: byte read from UART bit rate register (UBRR)
|
|
+ */
|
|
+
|
|
+
|
|
+/* data structure for each usb remote */
|
|
+struct igorplug {
|
|
+
|
|
+ /* usb */
|
|
+ struct usb_device *usbdev;
|
|
+ struct urb *urb_in;
|
|
+ int devnum;
|
|
+
|
|
+ unsigned char *buf_in;
|
|
+ unsigned int len_in;
|
|
+ int in_space;
|
|
+ struct timeval last_time;
|
|
+
|
|
+ dma_addr_t dma_in;
|
|
+
|
|
+ /* lirc */
|
|
+ struct lirc_driver *d;
|
|
+
|
|
+ /* handle sending (init strings) */
|
|
+ int send_flags;
|
|
+ wait_queue_head_t wait_out;
|
|
+};
|
|
+
|
|
+static int unregister_from_lirc(struct igorplug *ir)
|
|
+{
|
|
+ struct lirc_driver *d = ir->d;
|
|
+ int devnum;
|
|
+
|
|
+ if (!ir->d)
|
|
+ return -EINVAL;
|
|
+
|
|
+ devnum = ir->devnum;
|
|
+ dprintk(DRIVER_NAME "[%d]: unregister from lirc called\n", devnum);
|
|
+
|
|
+ lirc_unregister_driver(d->minor);
|
|
+
|
|
+ printk(DRIVER_NAME "[%d]: usb remote disconnected\n", devnum);
|
|
+
|
|
+ kfree(d);
|
|
+ ir->d = NULL;
|
|
+ kfree(ir);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int set_use_inc(void *data)
|
|
+{
|
|
+ struct igorplug *ir = data;
|
|
+
|
|
+ if (!ir) {
|
|
+ printk(DRIVER_NAME "[?]: set_use_inc called with no context\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+ dprintk(DRIVER_NAME "[%d]: set use inc\n", ir->devnum);
|
|
+
|
|
+ if (!ir->usbdev)
|
|
+ return -ENODEV;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void set_use_dec(void *data)
|
|
+{
|
|
+ struct igorplug *ir = data;
|
|
+
|
|
+ if (!ir) {
|
|
+ printk(DRIVER_NAME "[?]: set_use_dec called with no context\n");
|
|
+ return;
|
|
+ }
|
|
+ dprintk(DRIVER_NAME "[%d]: set use dec\n", ir->devnum);
|
|
+}
|
|
+
|
|
+
|
|
+/**
|
|
+ * Called in user context.
|
|
+ * return 0 if data was added to the buffer and
|
|
+ * -ENODATA if none was available. This should add some number of bits
|
|
+ * evenly divisible by code_length to the buffer
|
|
+ */
|
|
+static int usb_remote_poll(void *data, struct lirc_buffer *buf)
|
|
+{
|
|
+ int ret;
|
|
+ struct igorplug *ir = (struct igorplug *)data;
|
|
+
|
|
+ if (!ir->usbdev) /* Has the device been removed? */
|
|
+ return -ENODEV;
|
|
+
|
|
+ memset(ir->buf_in, 0, ir->len_in);
|
|
+
|
|
+ ret = usb_control_msg(
|
|
+ ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
|
|
+ GET_INFRACODE, USB_TYPE_VENDOR|USB_DIR_IN,
|
|
+ 0/* offset */, /*unused*/0,
|
|
+ ir->buf_in, ir->len_in,
|
|
+ /*timeout*/HZ * USB_CTRL_GET_TIMEOUT);
|
|
+ if (ret > 0) {
|
|
+ int i = DEVICE_HEADERLEN;
|
|
+ int code, timediff;
|
|
+ struct timeval now;
|
|
+
|
|
+ if (ret <= 1) /* ACK packet has 1 byte --> ignore */
|
|
+ return -ENODATA;
|
|
+
|
|
+ dprintk(DRIVER_NAME ": Got %d bytes. Header: %02x %02x %02x\n",
|
|
+ ret, ir->buf_in[0], ir->buf_in[1], ir->buf_in[2]);
|
|
+
|
|
+ if (ir->buf_in[2] != 0) {
|
|
+ printk(DRIVER_NAME "[%d]: Device buffer overrun.\n",
|
|
+ ir->devnum);
|
|
+ /* start at earliest byte */
|
|
+ i = DEVICE_HEADERLEN + ir->buf_in[2];
|
|
+ /* where are we now? space, gap or pulse? */
|
|
+ }
|
|
+
|
|
+ do_gettimeofday(&now);
|
|
+ timediff = now.tv_sec - ir->last_time.tv_sec;
|
|
+ if (timediff + 1 > PULSE_MASK / 1000000)
|
|
+ timediff = PULSE_MASK;
|
|
+ else {
|
|
+ timediff *= 1000000;
|
|
+ timediff += now.tv_usec - ir->last_time.tv_usec;
|
|
+ }
|
|
+ ir->last_time.tv_sec = now.tv_sec;
|
|
+ ir->last_time.tv_usec = now.tv_usec;
|
|
+
|
|
+ /* create leading gap */
|
|
+ code = timediff;
|
|
+ lirc_buffer_write(buf, (unsigned char *)&code);
|
|
+ ir->in_space = 1; /* next comes a pulse */
|
|
+
|
|
+ /* MODE2: pulse/space (PULSE_BIT) in 1us units */
|
|
+
|
|
+ while (i < ret) {
|
|
+ /* 1 Igor-tick = 85.333333 us */
|
|
+ code = (unsigned int)ir->buf_in[i] * 85
|
|
+ + (unsigned int)ir->buf_in[i] / 3;
|
|
+ if (ir->in_space)
|
|
+ code |= PULSE_BIT;
|
|
+ lirc_buffer_write(buf, (unsigned char *)&code);
|
|
+ /* 1 chunk = CODE_LENGTH bytes */
|
|
+ ir->in_space ^= 1;
|
|
+ ++i;
|
|
+ }
|
|
+
|
|
+ ret = usb_control_msg(
|
|
+ ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
|
|
+ SET_INFRABUFFER_EMPTY, USB_TYPE_VENDOR|USB_DIR_IN,
|
|
+ /*unused*/0, /*unused*/0,
|
|
+ /*dummy*/ir->buf_in, /*dummy*/ir->len_in,
|
|
+ /*timeout*/HZ * USB_CTRL_GET_TIMEOUT);
|
|
+ if (ret < 0)
|
|
+ printk(DRIVER_NAME "[%d]: SET_INFRABUFFER_EMPTY: "
|
|
+ "error %d\n", ir->devnum, ret);
|
|
+ return 0;
|
|
+ } else if (ret < 0)
|
|
+ printk(DRIVER_NAME "[%d]: GET_INFRACODE: error %d\n",
|
|
+ ir->devnum, ret);
|
|
+
|
|
+ return -ENODATA;
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+static int usb_remote_probe(struct usb_interface *intf,
|
|
+ const struct usb_device_id *id)
|
|
+{
|
|
+ struct usb_device *dev = NULL;
|
|
+ struct usb_host_interface *idesc = NULL;
|
|
+ struct usb_host_endpoint *ep_ctl2;
|
|
+ struct igorplug *ir = NULL;
|
|
+ struct lirc_driver *driver = NULL;
|
|
+ int devnum, pipe, maxp;
|
|
+ int minor = 0;
|
|
+ char buf[63], name[128] = "";
|
|
+ int mem_failure = 0;
|
|
+ int ret;
|
|
+
|
|
+ dprintk(DRIVER_NAME ": usb probe called.\n");
|
|
+
|
|
+ dev = interface_to_usbdev(intf);
|
|
+
|
|
+ idesc = intf->cur_altsetting;
|
|
+
|
|
+ if (idesc->desc.bNumEndpoints != 1)
|
|
+ return -ENODEV;
|
|
+ ep_ctl2 = idesc->endpoint;
|
|
+ if (((ep_ctl2->desc.bEndpointAddress & USB_ENDPOINT_DIR_MASK)
|
|
+ != USB_DIR_IN)
|
|
+ || (ep_ctl2->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
|
|
+ != USB_ENDPOINT_XFER_CONTROL)
|
|
+ return -ENODEV;
|
|
+ pipe = usb_rcvctrlpipe(dev, ep_ctl2->desc.bEndpointAddress);
|
|
+ devnum = dev->devnum;
|
|
+ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
|
|
+
|
|
+ dprintk(DRIVER_NAME "[%d]: bytes_in_key=%lu maxp=%d\n",
|
|
+ devnum, CODE_LENGTH, maxp);
|
|
+
|
|
+
|
|
+ mem_failure = 0;
|
|
+ ir = kzalloc(sizeof(struct igorplug), GFP_KERNEL);
|
|
+ if (!ir) {
|
|
+ mem_failure = 1;
|
|
+ goto mem_failure_switch;
|
|
+ }
|
|
+ driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
|
|
+ if (!driver) {
|
|
+ mem_failure = 2;
|
|
+ goto mem_failure_switch;
|
|
+ }
|
|
+
|
|
+ ir->buf_in = usb_buffer_alloc(dev,
|
|
+ DEVICE_BUFLEN+DEVICE_HEADERLEN,
|
|
+ GFP_ATOMIC, &ir->dma_in);
|
|
+ if (!ir->buf_in) {
|
|
+ mem_failure = 3;
|
|
+ goto mem_failure_switch;
|
|
+ }
|
|
+
|
|
+ strcpy(driver->name, DRIVER_NAME " ");
|
|
+ driver->minor = -1;
|
|
+ driver->code_length = CODE_LENGTH * 8; /* in bits */
|
|
+ driver->features = LIRC_CAN_REC_MODE2;
|
|
+ driver->data = ir;
|
|
+ driver->chunk_size = CODE_LENGTH;
|
|
+ driver->buffer_size = DEVICE_BUFLEN + ADDITIONAL_LIRC_BYTES;
|
|
+ driver->set_use_inc = &set_use_inc;
|
|
+ driver->set_use_dec = &set_use_dec;
|
|
+ driver->sample_rate = sample_rate; /* per second */
|
|
+ driver->add_to_buf = &usb_remote_poll;
|
|
+ driver->dev = &intf->dev;
|
|
+ driver->owner = THIS_MODULE;
|
|
+
|
|
+ init_waitqueue_head(&ir->wait_out);
|
|
+
|
|
+ minor = lirc_register_driver(driver);
|
|
+ if (minor < 0)
|
|
+ mem_failure = 9;
|
|
+
|
|
+mem_failure_switch:
|
|
+
|
|
+ switch (mem_failure) {
|
|
+ case 9:
|
|
+ usb_buffer_free(dev, DEVICE_BUFLEN+DEVICE_HEADERLEN,
|
|
+ ir->buf_in, ir->dma_in);
|
|
+ case 3:
|
|
+ kfree(driver);
|
|
+ case 2:
|
|
+ kfree(ir);
|
|
+ case 1:
|
|
+ printk(DRIVER_NAME "[%d]: out of memory (code=%d)\n",
|
|
+ devnum, mem_failure);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ driver->minor = minor;
|
|
+ ir->d = driver;
|
|
+ ir->devnum = devnum;
|
|
+ ir->usbdev = dev;
|
|
+ ir->len_in = DEVICE_BUFLEN+DEVICE_HEADERLEN;
|
|
+ ir->in_space = 1; /* First mode2 event is a space. */
|
|
+ do_gettimeofday(&ir->last_time);
|
|
+
|
|
+ if (dev->descriptor.iManufacturer
|
|
+ && usb_string(dev, dev->descriptor.iManufacturer,
|
|
+ buf, sizeof(buf)) > 0)
|
|
+ strlcpy(name, buf, sizeof(name));
|
|
+ if (dev->descriptor.iProduct
|
|
+ && usb_string(dev, dev->descriptor.iProduct, buf, sizeof(buf)) > 0)
|
|
+ snprintf(name + strlen(name), sizeof(name) - strlen(name),
|
|
+ " %s", buf);
|
|
+ printk(DRIVER_NAME "[%d]: %s on usb%d:%d\n", devnum, name,
|
|
+ dev->bus->busnum, devnum);
|
|
+
|
|
+ /* clear device buffer */
|
|
+ ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
|
|
+ SET_INFRABUFFER_EMPTY, USB_TYPE_VENDOR|USB_DIR_IN,
|
|
+ /*unused*/0, /*unused*/0,
|
|
+ /*dummy*/ir->buf_in, /*dummy*/ir->len_in,
|
|
+ /*timeout*/HZ * USB_CTRL_GET_TIMEOUT);
|
|
+ if (ret < 0)
|
|
+ printk(DRIVER_NAME "[%d]: SET_INFRABUFFER_EMPTY: error %d\n",
|
|
+ devnum, ret);
|
|
+
|
|
+ usb_set_intfdata(intf, ir);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static void usb_remote_disconnect(struct usb_interface *intf)
|
|
+{
|
|
+ struct usb_device *dev = interface_to_usbdev(intf);
|
|
+ struct igorplug *ir = usb_get_intfdata(intf);
|
|
+ usb_set_intfdata(intf, NULL);
|
|
+
|
|
+ if (!ir || !ir->d)
|
|
+ return;
|
|
+
|
|
+ ir->usbdev = NULL;
|
|
+ wake_up_all(&ir->wait_out);
|
|
+
|
|
+ usb_buffer_free(dev, ir->len_in, ir->buf_in, ir->dma_in);
|
|
+
|
|
+ unregister_from_lirc(ir);
|
|
+}
|
|
+
|
|
+static struct usb_device_id usb_remote_id_table[] = {
|
|
+ /* Igor Plug USB (Atmel's Manufact. ID) */
|
|
+ { USB_DEVICE(0x03eb, 0x0002) },
|
|
+
|
|
+ /* Terminating entry */
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct usb_driver usb_remote_driver = {
|
|
+ .name = DRIVER_NAME,
|
|
+ .probe = usb_remote_probe,
|
|
+ .disconnect = usb_remote_disconnect,
|
|
+ .id_table = usb_remote_id_table
|
|
+};
|
|
+
|
|
+static int __init usb_remote_init(void)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ printk(KERN_INFO "\n"
|
|
+ DRIVER_NAME ": " DRIVER_DESC " v" DRIVER_VERSION "\n");
|
|
+ printk(DRIVER_NAME ": " DRIVER_AUTHOR "\n");
|
|
+ dprintk(DRIVER_NAME ": debug mode enabled\n");
|
|
+
|
|
+ i = usb_register(&usb_remote_driver);
|
|
+ if (i < 0) {
|
|
+ printk(DRIVER_NAME ": usb register failed, result = %d\n", i);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit usb_remote_exit(void)
|
|
+{
|
|
+ usb_deregister(&usb_remote_driver);
|
|
+}
|
|
+
|
|
+module_init(usb_remote_init);
|
|
+module_exit(usb_remote_exit);
|
|
+
|
|
+#include <linux/vermagic.h>
|
|
+MODULE_INFO(vermagic, VERMAGIC_STRING);
|
|
+
|
|
+MODULE_DESCRIPTION(DRIVER_DESC);
|
|
+MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DEVICE_TABLE(usb, usb_remote_id_table);
|
|
+
|
|
+module_param(sample_rate, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(sample_rate, "Sampling rate in Hz (default: 100)");
|
|
+
|
|
diff --git a/drivers/input/lirc/lirc_imon.c b/drivers/input/lirc/lirc_imon.c
|
|
new file mode 100644
|
|
index 0000000..5bea43b
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_imon.c
|
|
@@ -0,0 +1,1054 @@
|
|
+/*
|
|
+ * lirc_imon.c: LIRC/VFD/LCD driver for SoundGraph iMON IR/VFD/LCD
|
|
+ * including the iMON PAD model
|
|
+ *
|
|
+ * Copyright(C) 2004 Venky Raju(dev@venky.ws)
|
|
+ * Copyright(C) 2009 Jarod Wilson <jarod@wilsonet.com>
|
|
+ *
|
|
+ * lirc_imon 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
+ */
|
|
+
|
|
+#include <linux/errno.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/usb.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+
|
|
+#define MOD_AUTHOR "Venky Raju <dev@venky.ws>"
|
|
+#define MOD_DESC "Driver for SoundGraph iMON MultiMedia IR/Display"
|
|
+#define MOD_NAME "lirc_imon"
|
|
+#define MOD_VERSION "0.8"
|
|
+
|
|
+#define DISPLAY_MINOR_BASE 144
|
|
+#define DEVICE_NAME "lcd%d"
|
|
+
|
|
+#define BUF_CHUNK_SIZE 4
|
|
+#define BUF_SIZE 128
|
|
+
|
|
+#define BIT_DURATION 250 /* each bit received is 250us */
|
|
+
|
|
+/*** P R O T O T Y P E S ***/
|
|
+
|
|
+/* USB Callback prototypes */
|
|
+static int imon_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id);
|
|
+static void imon_disconnect(struct usb_interface *interface);
|
|
+static void usb_rx_callback(struct urb *urb);
|
|
+static void usb_tx_callback(struct urb *urb);
|
|
+
|
|
+/* suspend/resume support */
|
|
+static int imon_resume(struct usb_interface *intf);
|
|
+static int imon_suspend(struct usb_interface *intf, pm_message_t message);
|
|
+
|
|
+/* Display file_operations function prototypes */
|
|
+static int display_open(struct inode *inode, struct file *file);
|
|
+static int display_close(struct inode *inode, struct file *file);
|
|
+
|
|
+/* VFD write operation */
|
|
+static ssize_t vfd_write(struct file *file, const char *buf,
|
|
+ size_t n_bytes, loff_t *pos);
|
|
+
|
|
+/* LIRC driver function prototypes */
|
|
+static int ir_open(void *data);
|
|
+static void ir_close(void *data);
|
|
+
|
|
+/* Driver init/exit prototypes */
|
|
+static int __init imon_init(void);
|
|
+static void __exit imon_exit(void);
|
|
+
|
|
+/*** G L O B A L S ***/
|
|
+
|
|
+struct imon_context {
|
|
+ struct usb_device *usbdev;
|
|
+ /* Newer devices have two interfaces */
|
|
+ int display; /* not all controllers do */
|
|
+ int display_isopen; /* display port has been opened */
|
|
+ int ir_isopen; /* IR port open */
|
|
+ int dev_present; /* USB device presence */
|
|
+ struct mutex ctx_lock; /* to lock this object */
|
|
+ wait_queue_head_t remove_ok; /* For unexpected USB disconnects */
|
|
+
|
|
+ int vfd_proto_6p; /* some VFD require a 6th packet */
|
|
+
|
|
+ struct lirc_driver *driver;
|
|
+ struct usb_endpoint_descriptor *rx_endpoint;
|
|
+ struct usb_endpoint_descriptor *tx_endpoint;
|
|
+ struct urb *rx_urb;
|
|
+ struct urb *tx_urb;
|
|
+ unsigned char usb_rx_buf[8];
|
|
+ unsigned char usb_tx_buf[8];
|
|
+
|
|
+ struct rx_data {
|
|
+ int count; /* length of 0 or 1 sequence */
|
|
+ int prev_bit; /* logic level of sequence */
|
|
+ int initial_space; /* initial space flag */
|
|
+ } rx;
|
|
+
|
|
+ struct tx_t {
|
|
+ unsigned char data_buf[35]; /* user data buffer */
|
|
+ struct completion finished; /* wait for write to finish */
|
|
+ atomic_t busy; /* write in progress */
|
|
+ int status; /* status of tx completion */
|
|
+ } tx;
|
|
+};
|
|
+
|
|
+static struct file_operations display_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = &display_open,
|
|
+ .write = &vfd_write,
|
|
+ .release = &display_close
|
|
+};
|
|
+
|
|
+/*
|
|
+ * USB Device ID for iMON USB Control Boards
|
|
+ *
|
|
+ * The Windows drivers contain 6 different inf files, more or less one for
|
|
+ * each new device until the 0x0034-0x0046 devices, which all use the same
|
|
+ * driver. Some of the devices in the 34-46 range haven't been definitively
|
|
+ * identified yet. Early devices have either a TriGem Computer, Inc. or a
|
|
+ * Samsung vendor ID (0x0aa8 and 0x04e8 respectively), while all later
|
|
+ * devices use the SoundGraph vendor ID (0x15c2).
|
|
+ */
|
|
+static struct usb_device_id imon_usb_id_table[] = {
|
|
+ /* TriGem iMON (IR only) -- TG_iMON.inf */
|
|
+ { USB_DEVICE(0x0aa8, 0x8001) },
|
|
+
|
|
+ /* SoundGraph iMON (IR only) -- sg_imon.inf */
|
|
+ { USB_DEVICE(0x04e8, 0xff30) },
|
|
+
|
|
+ /* SoundGraph iMON VFD (IR & VFD) -- iMON_VFD.inf */
|
|
+ { USB_DEVICE(0x0aa8, 0xffda) },
|
|
+
|
|
+ /* SoundGraph iMON SS (IR & VFD) -- iMON_SS.inf */
|
|
+ { USB_DEVICE(0x15c2, 0xffda) },
|
|
+
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* Some iMON VFD models requires a 6th packet for VFD writes */
|
|
+static struct usb_device_id vfd_proto_6p_list[] = {
|
|
+ { USB_DEVICE(0x15c2, 0xffda) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* Some iMON devices have no lcd/vfd, don't set one up */
|
|
+static struct usb_device_id ir_only_list[] = {
|
|
+ { USB_DEVICE(0x0aa8, 0x8001) },
|
|
+ { USB_DEVICE(0x04e8, 0xff30) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* USB Device data */
|
|
+static struct usb_driver imon_driver = {
|
|
+ .name = MOD_NAME,
|
|
+ .probe = imon_probe,
|
|
+ .disconnect = imon_disconnect,
|
|
+ .suspend = imon_suspend,
|
|
+ .resume = imon_resume,
|
|
+ .id_table = imon_usb_id_table,
|
|
+};
|
|
+
|
|
+static struct usb_class_driver imon_class = {
|
|
+ .name = DEVICE_NAME,
|
|
+ .fops = &display_fops,
|
|
+ .minor_base = DISPLAY_MINOR_BASE,
|
|
+};
|
|
+
|
|
+/* to prevent races between open() and disconnect(), probing, etc */
|
|
+static DEFINE_MUTEX(driver_lock);
|
|
+
|
|
+static int debug;
|
|
+
|
|
+/*** M O D U L E C O D E ***/
|
|
+
|
|
+MODULE_AUTHOR(MOD_AUTHOR);
|
|
+MODULE_DESCRIPTION(MOD_DESC);
|
|
+MODULE_VERSION(MOD_VERSION);
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DEVICE_TABLE(usb, imon_usb_id_table);
|
|
+module_param(debug, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes(default: no)");
|
|
+
|
|
+static void free_imon_context(struct imon_context *context)
|
|
+{
|
|
+ usb_free_urb(context->tx_urb);
|
|
+ usb_free_urb(context->rx_urb);
|
|
+ lirc_buffer_free(context->driver->rbuf);
|
|
+ kfree(context->driver->rbuf);
|
|
+ kfree(context->driver);
|
|
+ kfree(context);
|
|
+
|
|
+ dev_dbg(context->driver->dev, "%s: iMON context freed\n", __func__);
|
|
+}
|
|
+
|
|
+static void deregister_from_lirc(struct imon_context *context)
|
|
+{
|
|
+ int retval;
|
|
+ int minor = context->driver->minor;
|
|
+
|
|
+ retval = lirc_unregister_driver(minor);
|
|
+ if (retval)
|
|
+ err("%s: unable to deregister from lirc(%d)",
|
|
+ __func__, retval);
|
|
+ else
|
|
+ printk(KERN_INFO MOD_NAME ": Deregistered iMON driver "
|
|
+ "(minor:%d)\n", minor);
|
|
+
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called when the Display device (e.g. /dev/lcd0)
|
|
+ * is opened by the application.
|
|
+ */
|
|
+static int display_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct usb_interface *interface;
|
|
+ struct imon_context *context = NULL;
|
|
+ int subminor;
|
|
+ int retval = 0;
|
|
+
|
|
+ /* prevent races with disconnect */
|
|
+ mutex_lock(&driver_lock);
|
|
+
|
|
+ subminor = iminor(inode);
|
|
+ interface = usb_find_interface(&imon_driver, subminor);
|
|
+ if (!interface) {
|
|
+ err("%s: could not find interface for minor %d",
|
|
+ __func__, subminor);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+ context = usb_get_intfdata(interface);
|
|
+
|
|
+ if (!context) {
|
|
+ err("%s: no context found for minor %d",
|
|
+ __func__, subminor);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ if (!context->display) {
|
|
+ err("%s: display not supported by device", __func__);
|
|
+ retval = -ENODEV;
|
|
+ } else if (context->display_isopen) {
|
|
+ err("%s: display port is already open", __func__);
|
|
+ retval = -EBUSY;
|
|
+ } else {
|
|
+ context->display_isopen = 1;
|
|
+ file->private_data = context;
|
|
+ dev_info(context->driver->dev, "display port opened\n");
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+
|
|
+exit:
|
|
+ mutex_unlock(&driver_lock);
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called when the display device (e.g. /dev/lcd0)
|
|
+ * is closed by the application.
|
|
+ */
|
|
+static int display_close(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct imon_context *context = NULL;
|
|
+ int retval = 0;
|
|
+
|
|
+ context = (struct imon_context *)file->private_data;
|
|
+
|
|
+ if (!context) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ if (!context->display) {
|
|
+ err("%s: display not supported by device", __func__);
|
|
+ retval = -ENODEV;
|
|
+ } else if (!context->display_isopen) {
|
|
+ err("%s: display is not open", __func__);
|
|
+ retval = -EIO;
|
|
+ } else {
|
|
+ context->display_isopen = 0;
|
|
+ dev_info(context->driver->dev, "display port closed\n");
|
|
+ if (!context->dev_present && !context->ir_isopen) {
|
|
+ /*
|
|
+ * Device disconnected before close and IR port is not
|
|
+ * open. If IR port is open, context will be deleted by
|
|
+ * ir_close.
|
|
+ */
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ free_imon_context(context);
|
|
+ return retval;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Sends a packet to the device -- this function must be called
|
|
+ * with context->ctx_lock held.
|
|
+ */
|
|
+static int send_packet(struct imon_context *context)
|
|
+{
|
|
+ unsigned int pipe;
|
|
+ int interval = 0;
|
|
+ int retval = 0;
|
|
+ struct usb_ctrlrequest *control_req = NULL;
|
|
+
|
|
+ /* Check if we need to use control or interrupt urb */
|
|
+ pipe = usb_sndintpipe(context->usbdev,
|
|
+ context->tx_endpoint->bEndpointAddress);
|
|
+ interval = context->tx_endpoint->bInterval;
|
|
+
|
|
+ usb_fill_int_urb(context->tx_urb, context->usbdev, pipe,
|
|
+ context->usb_tx_buf,
|
|
+ sizeof(context->usb_tx_buf),
|
|
+ usb_tx_callback, context, interval);
|
|
+
|
|
+ context->tx_urb->actual_length = 0;
|
|
+
|
|
+ init_completion(&context->tx.finished);
|
|
+ atomic_set(&(context->tx.busy), 1);
|
|
+
|
|
+ retval = usb_submit_urb(context->tx_urb, GFP_KERNEL);
|
|
+ if (retval) {
|
|
+ atomic_set(&(context->tx.busy), 0);
|
|
+ err("%s: error submitting urb(%d)", __func__, retval);
|
|
+ } else {
|
|
+ /* Wait for transmission to complete (or abort) */
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ retval = wait_for_completion_interruptible(
|
|
+ &context->tx.finished);
|
|
+ if (retval)
|
|
+ err("%s: task interrupted", __func__);
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ retval = context->tx.status;
|
|
+ if (retval)
|
|
+ err("%s: packet tx failed (%d)", __func__, retval);
|
|
+ }
|
|
+
|
|
+ kfree(control_req);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Writes data to the VFD. The iMON VFD is 2x16 characters
|
|
+ * and requires data in 5 consecutive USB interrupt packets,
|
|
+ * each packet but the last carrying 7 bytes.
|
|
+ *
|
|
+ * I don't know if the VFD board supports features such as
|
|
+ * scrolling, clearing rows, blanking, etc. so at
|
|
+ * the caller must provide a full screen of data. If fewer
|
|
+ * than 32 bytes are provided spaces will be appended to
|
|
+ * generate a full screen.
|
|
+ */
|
|
+static ssize_t vfd_write(struct file *file, const char *buf,
|
|
+ size_t n_bytes, loff_t *pos)
|
|
+{
|
|
+ int i;
|
|
+ int offset;
|
|
+ int seq;
|
|
+ int retval = 0;
|
|
+ struct imon_context *context;
|
|
+ const unsigned char vfd_packet6[] = {
|
|
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
|
|
+
|
|
+ context = (struct imon_context *)file->private_data;
|
|
+ if (!context) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ if (!context->dev_present) {
|
|
+ err("%s: no iMON device present", __func__);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (n_bytes <= 0 || n_bytes > 32) {
|
|
+ err("%s: invalid payload size", __func__);
|
|
+ retval = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (copy_from_user(context->tx.data_buf, buf, n_bytes)) {
|
|
+ retval = -EFAULT;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ /* Pad with spaces */
|
|
+ for (i = n_bytes; i < 32; ++i)
|
|
+ context->tx.data_buf[i] = ' ';
|
|
+
|
|
+ for (i = 32; i < 35; ++i)
|
|
+ context->tx.data_buf[i] = 0xFF;
|
|
+
|
|
+ offset = 0;
|
|
+ seq = 0;
|
|
+
|
|
+ do {
|
|
+ memcpy(context->usb_tx_buf, context->tx.data_buf + offset, 7);
|
|
+ context->usb_tx_buf[7] = (unsigned char) seq;
|
|
+
|
|
+ retval = send_packet(context);
|
|
+ if (retval) {
|
|
+ err("%s: send packet failed for packet #%d",
|
|
+ __func__, seq/2);
|
|
+ goto exit;
|
|
+ } else {
|
|
+ seq += 2;
|
|
+ offset += 7;
|
|
+ }
|
|
+
|
|
+ } while (offset < 35);
|
|
+
|
|
+ if (context->vfd_proto_6p) {
|
|
+ /* Send packet #6 */
|
|
+ memcpy(context->usb_tx_buf, &vfd_packet6, sizeof(vfd_packet6));
|
|
+ context->usb_tx_buf[7] = (unsigned char) seq;
|
|
+ retval = send_packet(context);
|
|
+ if (retval)
|
|
+ err("%s: send packet failed for packet #%d",
|
|
+ __func__, seq/2);
|
|
+ }
|
|
+
|
|
+exit:
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+
|
|
+ return (!retval) ? n_bytes : retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: transmit data
|
|
+ */
|
|
+static void usb_tx_callback(struct urb *urb)
|
|
+{
|
|
+ struct imon_context *context;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+ context = (struct imon_context *)urb->context;
|
|
+ if (!context)
|
|
+ return;
|
|
+
|
|
+ context->tx.status = urb->status;
|
|
+
|
|
+ /* notify waiters that write has finished */
|
|
+ atomic_set(&context->tx.busy, 0);
|
|
+ complete(&context->tx.finished);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called by lirc_dev when the application opens /dev/lirc
|
|
+ */
|
|
+static int ir_open(void *data)
|
|
+{
|
|
+ int retval = 0;
|
|
+ struct imon_context *context;
|
|
+
|
|
+ /* prevent races with disconnect */
|
|
+ mutex_lock(&driver_lock);
|
|
+
|
|
+ context = (struct imon_context *)data;
|
|
+
|
|
+ /* initial IR protocol decode variables */
|
|
+ context->rx.count = 0;
|
|
+ context->rx.initial_space = 1;
|
|
+ context->rx.prev_bit = 0;
|
|
+
|
|
+ context->ir_isopen = 1;
|
|
+ dev_info(context->driver->dev, "IR port opened\n");
|
|
+
|
|
+ mutex_unlock(&driver_lock);
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called by lirc_dev when the application closes /dev/lirc
|
|
+ */
|
|
+static void ir_close(void *data)
|
|
+{
|
|
+ struct imon_context *context;
|
|
+
|
|
+ context = (struct imon_context *)data;
|
|
+ if (!context) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ context->ir_isopen = 0;
|
|
+ dev_info(context->driver->dev, "IR port closed\n");
|
|
+
|
|
+ if (!context->dev_present) {
|
|
+ /*
|
|
+ * Device disconnected while IR port was still open. Driver
|
|
+ * was not deregistered at disconnect time, so do it now.
|
|
+ */
|
|
+ deregister_from_lirc(context);
|
|
+
|
|
+ if (!context->display_isopen) {
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ free_imon_context(context);
|
|
+ return;
|
|
+ }
|
|
+ /*
|
|
+ * If display port is open, context will be deleted by
|
|
+ * display_close
|
|
+ */
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ return;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Convert bit count to time duration (in us) and submit
|
|
+ * the value to lirc_dev.
|
|
+ */
|
|
+static void submit_data(struct imon_context *context)
|
|
+{
|
|
+ unsigned char buf[4];
|
|
+ int value = context->rx.count;
|
|
+ int i;
|
|
+
|
|
+ dev_dbg(context->driver->dev, "submitting data to LIRC\n");
|
|
+
|
|
+ value *= BIT_DURATION;
|
|
+ value &= PULSE_MASK;
|
|
+ if (context->rx.prev_bit)
|
|
+ value |= PULSE_BIT;
|
|
+
|
|
+ for (i = 0; i < 4; ++i)
|
|
+ buf[i] = value>>(i*8);
|
|
+
|
|
+ lirc_buffer_write(context->driver->rbuf, buf);
|
|
+ wake_up(&context->driver->rbuf->wait_poll);
|
|
+ return;
|
|
+}
|
|
+
|
|
+static inline int tv2int(const struct timeval *a, const struct timeval *b)
|
|
+{
|
|
+ int usecs = 0;
|
|
+ int sec = 0;
|
|
+
|
|
+ if (b->tv_usec > a->tv_usec) {
|
|
+ usecs = 1000000;
|
|
+ sec--;
|
|
+ }
|
|
+
|
|
+ usecs += a->tv_usec - b->tv_usec;
|
|
+
|
|
+ sec += a->tv_sec - b->tv_sec;
|
|
+ sec *= 1000;
|
|
+ usecs /= 1000;
|
|
+ sec += usecs;
|
|
+
|
|
+ if (sec < 0)
|
|
+ sec = 1000;
|
|
+
|
|
+ return sec;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Process the incoming packet
|
|
+ */
|
|
+static void imon_incoming_packet(struct imon_context *context,
|
|
+ struct urb *urb, int intf)
|
|
+{
|
|
+ int len = urb->actual_length;
|
|
+ unsigned char *buf = urb->transfer_buffer;
|
|
+ struct device *dev = context->driver->dev;
|
|
+ int octet, bit;
|
|
+ unsigned char mask;
|
|
+ int i, chunk_num;
|
|
+
|
|
+ /*
|
|
+ * just bail out if no listening IR client
|
|
+ */
|
|
+ if (!context->ir_isopen)
|
|
+ return;
|
|
+
|
|
+ if (len != 8) {
|
|
+ dev_warn(dev, "imon %s: invalid incoming packet "
|
|
+ "size (len = %d, intf%d)\n", __func__, len, intf);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (debug) {
|
|
+ printk(KERN_INFO "raw packet: ");
|
|
+ for (i = 0; i < len; ++i)
|
|
+ printk("%02x ", buf[i]);
|
|
+ printk("\n");
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Translate received data to pulse and space lengths.
|
|
+ * Received data is active low, i.e. pulses are 0 and
|
|
+ * spaces are 1.
|
|
+ *
|
|
+ * My original algorithm was essentially similar to
|
|
+ * Changwoo Ryu's with the exception that he switched
|
|
+ * the incoming bits to active high and also fed an
|
|
+ * initial space to LIRC at the start of a new sequence
|
|
+ * if the previous bit was a pulse.
|
|
+ *
|
|
+ * I've decided to adopt his algorithm.
|
|
+ */
|
|
+
|
|
+ if (buf[7] == 1 && context->rx.initial_space) {
|
|
+ /* LIRC requires a leading space */
|
|
+ context->rx.prev_bit = 0;
|
|
+ context->rx.count = 4;
|
|
+ submit_data(context);
|
|
+ context->rx.count = 0;
|
|
+ }
|
|
+
|
|
+ for (octet = 0; octet < 5; ++octet) {
|
|
+ mask = 0x80;
|
|
+ for (bit = 0; bit < 8; ++bit) {
|
|
+ int curr_bit = !(buf[octet] & mask);
|
|
+ if (curr_bit != context->rx.prev_bit) {
|
|
+ if (context->rx.count) {
|
|
+ submit_data(context);
|
|
+ context->rx.count = 0;
|
|
+ }
|
|
+ context->rx.prev_bit = curr_bit;
|
|
+ }
|
|
+ ++context->rx.count;
|
|
+ mask >>= 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (chunk_num == 10) {
|
|
+ if (context->rx.count) {
|
|
+ submit_data(context);
|
|
+ context->rx.count = 0;
|
|
+ }
|
|
+ context->rx.initial_space = context->rx.prev_bit;
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: receive data
|
|
+ */
|
|
+static void usb_rx_callback(struct urb *urb)
|
|
+{
|
|
+ struct imon_context *context;
|
|
+ unsigned char *buf;
|
|
+ int len;
|
|
+ int intfnum = 0;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+
|
|
+ context = (struct imon_context *)urb->context;
|
|
+ if (!context)
|
|
+ return;
|
|
+
|
|
+ buf = urb->transfer_buffer;
|
|
+ len = urb->actual_length;
|
|
+
|
|
+ switch (urb->status) {
|
|
+ case -ENOENT: /* usbcore unlink successful! */
|
|
+ return;
|
|
+
|
|
+ case 0:
|
|
+ imon_incoming_packet(context, urb, intfnum);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ dev_warn(context->driver->dev, "imon %s: status(%d): ignored\n",
|
|
+ __func__, urb->status);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ usb_submit_urb(context->rx_urb, GFP_ATOMIC);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: Probe
|
|
+ */
|
|
+static int imon_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id)
|
|
+{
|
|
+ struct usb_device *usbdev = NULL;
|
|
+ struct usb_host_interface *iface_desc = NULL;
|
|
+ struct usb_endpoint_descriptor *rx_endpoint = NULL;
|
|
+ struct usb_endpoint_descriptor *tx_endpoint = NULL;
|
|
+ struct urb *rx_urb = NULL;
|
|
+ struct urb *tx_urb = NULL;
|
|
+ struct lirc_driver *driver = NULL;
|
|
+ struct lirc_buffer *rbuf = NULL;
|
|
+ struct usb_interface *first_if;
|
|
+ struct device *dev = &interface->dev;
|
|
+ int ifnum;
|
|
+ int lirc_minor = 0;
|
|
+ int num_endpts;
|
|
+ int retval = 0;
|
|
+ int display_ep_found = 0;
|
|
+ int ir_ep_found = 0;
|
|
+ int alloc_status = 0;
|
|
+ int vfd_proto_6p = 0;
|
|
+ int code_length;
|
|
+ struct imon_context *context = NULL;
|
|
+ struct imon_context *first_if_context = NULL;
|
|
+ int i;
|
|
+ u16 vendor, product;
|
|
+
|
|
+ /*
|
|
+ * Try to auto-detect the type of display if the user hasn't set
|
|
+ * it by hand via the display_type modparam. Default is VFD.
|
|
+ */
|
|
+ if (usb_match_id(interface, ir_only_list))
|
|
+ context->display = 0;
|
|
+ else
|
|
+ context->display = 1;
|
|
+
|
|
+ code_length = BUF_CHUNK_SIZE * 8;
|
|
+
|
|
+ usbdev = usb_get_dev(interface_to_usbdev(interface));
|
|
+ iface_desc = interface->cur_altsetting;
|
|
+ num_endpts = iface_desc->desc.bNumEndpoints;
|
|
+ ifnum = iface_desc->desc.bInterfaceNumber;
|
|
+ vendor = le16_to_cpu(usbdev->descriptor.idVendor);
|
|
+ product = le16_to_cpu(usbdev->descriptor.idProduct);
|
|
+
|
|
+ dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n",
|
|
+ __func__, vendor, product, ifnum);
|
|
+
|
|
+ /* prevent races probing devices w/multiple interfaces */
|
|
+ mutex_lock(&driver_lock);
|
|
+
|
|
+ first_if = usb_ifnum_to_if(usbdev, 0);
|
|
+ first_if_context = (struct imon_context *)usb_get_intfdata(first_if);
|
|
+
|
|
+ /*
|
|
+ * Scan the endpoint list and set:
|
|
+ * first input endpoint = IR endpoint
|
|
+ * first output endpoint = display endpoint
|
|
+ */
|
|
+ for (i = 0; i < num_endpts && !(ir_ep_found && display_ep_found); ++i) {
|
|
+ struct usb_endpoint_descriptor *ep;
|
|
+ int ep_dir;
|
|
+ int ep_type;
|
|
+ ep = &iface_desc->endpoint[i].desc;
|
|
+ ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
|
|
+ ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
|
|
+
|
|
+ if (!ir_ep_found &&
|
|
+ ep_dir == USB_DIR_IN &&
|
|
+ ep_type == USB_ENDPOINT_XFER_INT) {
|
|
+
|
|
+ rx_endpoint = ep;
|
|
+ ir_ep_found = 1;
|
|
+ dev_dbg(dev, "%s: found IR endpoint\n", __func__);
|
|
+
|
|
+ } else if (!display_ep_found && ep_dir == USB_DIR_OUT &&
|
|
+ ep_type == USB_ENDPOINT_XFER_INT) {
|
|
+ tx_endpoint = ep;
|
|
+ display_ep_found = 1;
|
|
+ dev_dbg(dev, "%s: found display endpoint\n", __func__);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Some iMON receivers have no display. Unfortunately, it seems
|
|
+ * that SoundGraph recycles device IDs between devices both with
|
|
+ * and without... :\
|
|
+ */
|
|
+ if (context->display == 0) {
|
|
+ display_ep_found = 0;
|
|
+ dev_dbg(dev, "%s: device has no display\n", __func__);
|
|
+ }
|
|
+
|
|
+ /* Input endpoint is mandatory */
|
|
+ if (!ir_ep_found) {
|
|
+ err("%s: no valid input (IR) endpoint found.", __func__);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ /* Determine if display requires 6 packets */
|
|
+ if (display_ep_found) {
|
|
+ if (usb_match_id(interface, vfd_proto_6p_list))
|
|
+ vfd_proto_6p = 1;
|
|
+
|
|
+ dev_dbg(dev, "%s: vfd_proto_6p: %d\n",
|
|
+ __func__, vfd_proto_6p);
|
|
+ }
|
|
+
|
|
+ context = kzalloc(sizeof(struct imon_context), GFP_KERNEL);
|
|
+ if (!context) {
|
|
+ err("%s: kzalloc failed for context", __func__);
|
|
+ alloc_status = 1;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
|
|
+ if (!driver) {
|
|
+ err("%s: kzalloc failed for lirc_driver", __func__);
|
|
+ alloc_status = 2;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
|
|
+ if (!rbuf) {
|
|
+ err("%s: kmalloc failed for lirc_buffer", __func__);
|
|
+ alloc_status = 3;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) {
|
|
+ err("%s: lirc_buffer_init failed", __func__);
|
|
+ alloc_status = 4;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ rx_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!rx_urb) {
|
|
+ err("%s: usb_alloc_urb failed for IR urb", __func__);
|
|
+ alloc_status = 5;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ tx_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!tx_urb) {
|
|
+ err("%s: usb_alloc_urb failed for display urb",
|
|
+ __func__);
|
|
+ alloc_status = 6;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+
|
|
+ mutex_init(&context->ctx_lock);
|
|
+ context->vfd_proto_6p = vfd_proto_6p;
|
|
+
|
|
+ strcpy(driver->name, MOD_NAME);
|
|
+ driver->minor = -1;
|
|
+ driver->code_length = sizeof(int) * 8;
|
|
+ driver->sample_rate = 0;
|
|
+ driver->features = LIRC_CAN_REC_MODE2;
|
|
+ driver->data = context;
|
|
+ driver->rbuf = rbuf;
|
|
+ driver->set_use_inc = ir_open;
|
|
+ driver->set_use_dec = ir_close;
|
|
+ driver->dev = &interface->dev;
|
|
+ driver->owner = THIS_MODULE;
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ context->driver = driver;
|
|
+ /* start out in keyboard mode */
|
|
+
|
|
+ lirc_minor = lirc_register_driver(driver);
|
|
+ if (lirc_minor < 0) {
|
|
+ err("%s: lirc_register_driver failed", __func__);
|
|
+ alloc_status = 7;
|
|
+ goto alloc_status_switch;
|
|
+ } else
|
|
+ dev_info(dev, "Registered iMON driver "
|
|
+ "(lirc minor: %d)\n", lirc_minor);
|
|
+
|
|
+ /* Needed while unregistering! */
|
|
+ driver->minor = lirc_minor;
|
|
+
|
|
+ context->usbdev = usbdev;
|
|
+ context->dev_present = 1;
|
|
+ context->rx_endpoint = rx_endpoint;
|
|
+ context->rx_urb = rx_urb;
|
|
+
|
|
+ /*
|
|
+ * tx is used to send characters to lcd/vfd, associate RF
|
|
+ * remotes, set IR protocol, and maybe more...
|
|
+ */
|
|
+ context->tx_endpoint = tx_endpoint;
|
|
+ context->tx_urb = tx_urb;
|
|
+
|
|
+ if (display_ep_found)
|
|
+ context->display = 1;
|
|
+
|
|
+ usb_fill_int_urb(context->rx_urb, context->usbdev,
|
|
+ usb_rcvintpipe(context->usbdev,
|
|
+ context->rx_endpoint->bEndpointAddress),
|
|
+ context->usb_rx_buf, sizeof(context->usb_rx_buf),
|
|
+ usb_rx_callback, context,
|
|
+ context->rx_endpoint->bInterval);
|
|
+
|
|
+ retval = usb_submit_urb(context->rx_urb, GFP_KERNEL);
|
|
+
|
|
+ if (retval) {
|
|
+ err("%s: usb_submit_urb failed for intf0 (%d)",
|
|
+ __func__, retval);
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ usb_set_intfdata(interface, context);
|
|
+
|
|
+ if (context->display && ifnum == 0) {
|
|
+ dev_dbg(dev, "%s: Registering iMON display with sysfs\n",
|
|
+ __func__);
|
|
+
|
|
+ if (usb_register_dev(interface, &imon_class)) {
|
|
+ /* Not a fatal error, so ignore */
|
|
+ dev_info(dev, "%s: could not get a minor number for "
|
|
+ "display\n", __func__);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dev_info(dev, "iMON device (%04x:%04x, intf%d) on "
|
|
+ "usb<%d:%d> initialized\n", vendor, product, ifnum,
|
|
+ usbdev->bus->busnum, usbdev->devnum);
|
|
+
|
|
+alloc_status_switch:
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+
|
|
+ switch (alloc_status) {
|
|
+ case 7:
|
|
+ usb_free_urb(tx_urb);
|
|
+ case 6:
|
|
+ usb_free_urb(rx_urb);
|
|
+ case 5:
|
|
+ if (rbuf)
|
|
+ lirc_buffer_free(rbuf);
|
|
+ case 4:
|
|
+ kfree(rbuf);
|
|
+ case 3:
|
|
+ kfree(driver);
|
|
+ case 2:
|
|
+ kfree(context);
|
|
+ context = NULL;
|
|
+ case 1:
|
|
+ retval = -ENOMEM;
|
|
+ break;
|
|
+ case 0:
|
|
+ retval = 0;
|
|
+ }
|
|
+
|
|
+exit:
|
|
+ mutex_unlock(&driver_lock);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: disconnect
|
|
+ */
|
|
+static void imon_disconnect(struct usb_interface *interface)
|
|
+{
|
|
+ struct imon_context *context;
|
|
+ int ifnum;
|
|
+
|
|
+ /* prevent races with ir_open()/display_open() */
|
|
+ mutex_lock(&driver_lock);
|
|
+
|
|
+ context = usb_get_intfdata(interface);
|
|
+ ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ usb_set_intfdata(interface, NULL);
|
|
+
|
|
+ /* Abort ongoing write */
|
|
+ if (atomic_read(&context->tx.busy)) {
|
|
+ usb_kill_urb(context->tx_urb);
|
|
+ complete_all(&context->tx.finished);
|
|
+ }
|
|
+
|
|
+ context->dev_present = 0;
|
|
+ usb_kill_urb(context->rx_urb);
|
|
+ if (context->display)
|
|
+ usb_deregister_dev(interface, &imon_class);
|
|
+
|
|
+ if (!context->ir_isopen && !context->dev_present) {
|
|
+ deregister_from_lirc(context);
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ if (!context->display_isopen)
|
|
+ free_imon_context(context);
|
|
+ } else
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+
|
|
+ mutex_unlock(&driver_lock);
|
|
+
|
|
+ printk(KERN_INFO "%s: iMON device (intf%d) disconnected\n",
|
|
+ __func__, ifnum);
|
|
+}
|
|
+
|
|
+static int imon_suspend(struct usb_interface *intf, pm_message_t message)
|
|
+{
|
|
+ struct imon_context *context = usb_get_intfdata(intf);
|
|
+
|
|
+ usb_kill_urb(context->rx_urb);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int imon_resume(struct usb_interface *intf)
|
|
+{
|
|
+ int rc = 0;
|
|
+ struct imon_context *context = usb_get_intfdata(intf);
|
|
+
|
|
+ usb_fill_int_urb(context->rx_urb, context->usbdev,
|
|
+ usb_rcvintpipe(context->usbdev,
|
|
+ context->rx_endpoint->bEndpointAddress),
|
|
+ context->usb_rx_buf, sizeof(context->usb_rx_buf),
|
|
+ usb_rx_callback, context,
|
|
+ context->rx_endpoint->bInterval);
|
|
+
|
|
+ rc = usb_submit_urb(context->rx_urb, GFP_ATOMIC);
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int __init imon_init(void)
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ printk(KERN_INFO MOD_NAME ": " MOD_DESC ", v" MOD_VERSION "\n");
|
|
+
|
|
+ rc = usb_register(&imon_driver);
|
|
+ if (rc) {
|
|
+ err("%s: usb register failed(%d)", __func__, rc);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit imon_exit(void)
|
|
+{
|
|
+ usb_deregister(&imon_driver);
|
|
+ printk(KERN_INFO MOD_NAME ": module removed. Goodbye!\n");
|
|
+}
|
|
+
|
|
+module_init(imon_init);
|
|
+module_exit(imon_exit);
|
|
diff --git a/drivers/input/lirc/lirc_it87.c b/drivers/input/lirc/lirc_it87.c
|
|
new file mode 100644
|
|
index 0000000..c69662d
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_it87.c
|
|
@@ -0,0 +1,991 @@
|
|
+/*
|
|
+ * LIRC driver for ITE IT8712/IT8705 CIR port
|
|
+ *
|
|
+ * Copyright (C) 2001 Hans-Gunter Lutke Uphues <hg_lu@web.de>
|
|
+ *
|
|
+ * 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
|
|
+ *
|
|
+ * ITE IT8705 and IT8712(not tested) and IT8720 CIR-port support for lirc based
|
|
+ * via cut and paste from lirc_sir.c (C) 2000 Milan Pikula
|
|
+ *
|
|
+ * Attention: Sendmode only tested with debugging logs
|
|
+ *
|
|
+ * 2001/02/27 Christoph Bartelmus <lirc@bartelmus.de> :
|
|
+ * reimplemented read function
|
|
+ * 2005/06/05 Andrew Calkin implemented support for Asus Digimatrix,
|
|
+ * based on work of the following member of the Outertrack Digimatrix
|
|
+ * Forum: Art103 <r_tay@hotmail.com>
|
|
+ * 2009/12/24 James Edwards <jimbo-lirc@edwardsclan.net> implemeted support
|
|
+ * for ITE8704/ITE8718, on my machine, the DSDT reports 8704, but the
|
|
+ * chip identifies as 18.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/signal.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/serial_reg.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/wait.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/poll.h>
|
|
+#include <asm/system.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/fcntl.h>
|
|
+
|
|
+#include <linux/timer.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+#include "lirc_it87.h"
|
|
+
|
|
+#ifdef LIRC_IT87_DIGIMATRIX
|
|
+static int digimatrix = 1;
|
|
+static int it87_freq = 36; /* kHz */
|
|
+static int irq = 9;
|
|
+#else
|
|
+static int digimatrix;
|
|
+static int it87_freq = 38; /* kHz */
|
|
+static int irq = IT87_CIR_DEFAULT_IRQ;
|
|
+#endif
|
|
+
|
|
+static unsigned long it87_bits_in_byte_out;
|
|
+static unsigned long it87_send_counter;
|
|
+static unsigned char it87_RXEN_mask = IT87_CIR_RCR_RXEN;
|
|
+
|
|
+#define RBUF_LEN 1024
|
|
+#define WBUF_LEN 1024
|
|
+
|
|
+#define LIRC_DRIVER_NAME "lirc_it87"
|
|
+
|
|
+/* timeout for sequences in jiffies (=5/100s) */
|
|
+/* must be longer than TIME_CONST */
|
|
+#define IT87_TIMEOUT (HZ*5/100)
|
|
+
|
|
+/* module parameters */
|
|
+static int debug;
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \
|
|
+ fmt, ## args); \
|
|
+ } while (0)
|
|
+
|
|
+static int io = IT87_CIR_DEFAULT_IOBASE;
|
|
+/* receiver demodulator default: off */
|
|
+static int it87_enable_demodulator;
|
|
+
|
|
+static int timer_enabled;
|
|
+static DEFINE_SPINLOCK(timer_lock);
|
|
+static struct timer_list timerlist;
|
|
+/* time of last signal change detected */
|
|
+static struct timeval last_tv = {0, 0};
|
|
+/* time of last UART data ready interrupt */
|
|
+static struct timeval last_intr_tv = {0, 0};
|
|
+static int last_value;
|
|
+
|
|
+static DECLARE_WAIT_QUEUE_HEAD(lirc_read_queue);
|
|
+
|
|
+static DEFINE_SPINLOCK(hardware_lock);
|
|
+static DEFINE_SPINLOCK(dev_lock);
|
|
+
|
|
+static int rx_buf[RBUF_LEN];
|
|
+unsigned int rx_tail, rx_head;
|
|
+static int tx_buf[WBUF_LEN];
|
|
+
|
|
+/* SECTION: Prototypes */
|
|
+
|
|
+/* Communication with user-space */
|
|
+static int lirc_open(struct inode *inode, struct file *file);
|
|
+static int lirc_close(struct inode *inode, struct file *file);
|
|
+static unsigned int lirc_poll(struct file *file, poll_table *wait);
|
|
+static ssize_t lirc_read(struct file *file, char *buf,
|
|
+ size_t count, loff_t *ppos);
|
|
+static ssize_t lirc_write(struct file *file, const char *buf,
|
|
+ size_t n, loff_t *pos);
|
|
+static int lirc_ioctl(struct inode *node, struct file *filep,
|
|
+ unsigned int cmd, unsigned long arg);
|
|
+static void add_read_queue(int flag, unsigned long val);
|
|
+static int init_chrdev(void);
|
|
+static void drop_chrdev(void);
|
|
+/* Hardware */
|
|
+static irqreturn_t it87_interrupt(int irq, void *dev_id);
|
|
+static void send_space(unsigned long len);
|
|
+static void send_pulse(unsigned long len);
|
|
+static void init_send(void);
|
|
+static void terminate_send(unsigned long len);
|
|
+static int init_hardware(void);
|
|
+static void drop_hardware(void);
|
|
+/* Initialisation */
|
|
+static int init_port(void);
|
|
+static void drop_port(void);
|
|
+
|
|
+
|
|
+/* SECTION: Communication with user-space */
|
|
+
|
|
+static int lirc_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ spin_lock(&dev_lock);
|
|
+ if (module_refcount(THIS_MODULE)) {
|
|
+ spin_unlock(&dev_lock);
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ spin_unlock(&dev_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static int lirc_close(struct inode *inode, struct file *file)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static unsigned int lirc_poll(struct file *file, poll_table *wait)
|
|
+{
|
|
+ poll_wait(file, &lirc_read_queue, wait);
|
|
+ if (rx_head != rx_tail)
|
|
+ return POLLIN | POLLRDNORM;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static ssize_t lirc_read(struct file *file, char *buf,
|
|
+ size_t count, loff_t *ppos)
|
|
+{
|
|
+ int n = 0;
|
|
+ int retval = 0;
|
|
+
|
|
+ while (n < count) {
|
|
+ if (file->f_flags & O_NONBLOCK && rx_head == rx_tail) {
|
|
+ retval = -EAGAIN;
|
|
+ break;
|
|
+ }
|
|
+ retval = wait_event_interruptible(lirc_read_queue,
|
|
+ rx_head != rx_tail);
|
|
+ if (retval)
|
|
+ break;
|
|
+
|
|
+ if (copy_to_user((void *) buf + n, (void *) (rx_buf + rx_head),
|
|
+ sizeof(int))) {
|
|
+ retval = -EFAULT;
|
|
+ break;
|
|
+ }
|
|
+ rx_head = (rx_head + 1) & (RBUF_LEN - 1);
|
|
+ n += sizeof(int);
|
|
+ }
|
|
+ if (n)
|
|
+ return n;
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+
|
|
+static ssize_t lirc_write(struct file *file, const char *buf,
|
|
+ size_t n, loff_t *pos)
|
|
+{
|
|
+ int i = 0;
|
|
+
|
|
+ if (n % sizeof(int) || (n / sizeof(int)) > WBUF_LEN)
|
|
+ return -EINVAL;
|
|
+ if (copy_from_user(tx_buf, buf, n))
|
|
+ return -EFAULT;
|
|
+ n /= sizeof(int);
|
|
+ init_send();
|
|
+ while (1) {
|
|
+ if (i >= n)
|
|
+ break;
|
|
+ if (tx_buf[i])
|
|
+ send_pulse(tx_buf[i]);
|
|
+ i++;
|
|
+ if (i >= n)
|
|
+ break;
|
|
+ if (tx_buf[i])
|
|
+ send_space(tx_buf[i]);
|
|
+ i++;
|
|
+ }
|
|
+ terminate_send(tx_buf[i - 1]);
|
|
+ return n;
|
|
+}
|
|
+
|
|
+
|
|
+static int lirc_ioctl(struct inode *node, struct file *filep,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ int retval = 0;
|
|
+ unsigned long value = 0;
|
|
+ unsigned int ivalue;
|
|
+ unsigned long hw_flags;
|
|
+
|
|
+ if (cmd == LIRC_GET_FEATURES)
|
|
+ value = LIRC_CAN_SEND_PULSE |
|
|
+ LIRC_CAN_SET_SEND_CARRIER |
|
|
+ LIRC_CAN_REC_MODE2;
|
|
+ else if (cmd == LIRC_GET_SEND_MODE)
|
|
+ value = LIRC_MODE_PULSE;
|
|
+ else if (cmd == LIRC_GET_REC_MODE)
|
|
+ value = LIRC_MODE_MODE2;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case LIRC_GET_FEATURES:
|
|
+ case LIRC_GET_SEND_MODE:
|
|
+ case LIRC_GET_REC_MODE:
|
|
+ retval = put_user(value, (unsigned long *) arg);
|
|
+ break;
|
|
+
|
|
+ case LIRC_SET_SEND_MODE:
|
|
+ case LIRC_SET_REC_MODE:
|
|
+ retval = get_user(value, (unsigned long *) arg);
|
|
+ break;
|
|
+
|
|
+ case LIRC_SET_SEND_CARRIER:
|
|
+ retval = get_user(ivalue, (unsigned int *) arg);
|
|
+ if (retval)
|
|
+ return retval;
|
|
+ ivalue /= 1000;
|
|
+ if (ivalue > IT87_CIR_FREQ_MAX ||
|
|
+ ivalue < IT87_CIR_FREQ_MIN)
|
|
+ return -EINVAL;
|
|
+
|
|
+ it87_freq = ivalue;
|
|
+
|
|
+ spin_lock_irqsave(&hardware_lock, hw_flags);
|
|
+ outb(((inb(io + IT87_CIR_TCR2) & IT87_CIR_TCR2_TXMPW) |
|
|
+ (it87_freq - IT87_CIR_FREQ_MIN) << 3),
|
|
+ io + IT87_CIR_TCR2);
|
|
+ spin_unlock_irqrestore(&hardware_lock, hw_flags);
|
|
+ dprintk("demodulation frequency: %d kHz\n", it87_freq);
|
|
+
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ retval = -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (retval)
|
|
+ return retval;
|
|
+
|
|
+ if (cmd == LIRC_SET_REC_MODE) {
|
|
+ if (value != LIRC_MODE_MODE2)
|
|
+ retval = -ENOSYS;
|
|
+ } else if (cmd == LIRC_SET_SEND_MODE) {
|
|
+ if (value != LIRC_MODE_PULSE)
|
|
+ retval = -ENOSYS;
|
|
+ }
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static void add_read_queue(int flag, unsigned long val)
|
|
+{
|
|
+ unsigned int new_rx_tail;
|
|
+ int newval;
|
|
+
|
|
+ dprintk("add flag %d with val %lu\n", flag, val);
|
|
+
|
|
+ newval = val & PULSE_MASK;
|
|
+
|
|
+ /*
|
|
+ * statistically, pulses are ~TIME_CONST/2 too long. we could
|
|
+ * maybe make this more exact, but this is good enough
|
|
+ */
|
|
+ if (flag) {
|
|
+ /* pulse */
|
|
+ if (newval > TIME_CONST / 2)
|
|
+ newval -= TIME_CONST / 2;
|
|
+ else /* should not ever happen */
|
|
+ newval = 1;
|
|
+ newval |= PULSE_BIT;
|
|
+ } else
|
|
+ newval += TIME_CONST / 2;
|
|
+ new_rx_tail = (rx_tail + 1) & (RBUF_LEN - 1);
|
|
+ if (new_rx_tail == rx_head) {
|
|
+ dprintk("Buffer overrun.\n");
|
|
+ return;
|
|
+ }
|
|
+ rx_buf[rx_tail] = newval;
|
|
+ rx_tail = new_rx_tail;
|
|
+ wake_up_interruptible(&lirc_read_queue);
|
|
+}
|
|
+
|
|
+
|
|
+static struct file_operations lirc_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .read = lirc_read,
|
|
+ .write = lirc_write,
|
|
+ .poll = lirc_poll,
|
|
+ .ioctl = lirc_ioctl,
|
|
+ .open = lirc_open,
|
|
+ .release = lirc_close,
|
|
+};
|
|
+
|
|
+static int set_use_inc(void *data)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void set_use_dec(void *data)
|
|
+{
|
|
+}
|
|
+
|
|
+static struct lirc_driver driver = {
|
|
+ .name = LIRC_DRIVER_NAME,
|
|
+ .minor = -1,
|
|
+ .code_length = 1,
|
|
+ .sample_rate = 0,
|
|
+ .data = NULL,
|
|
+ .add_to_buf = NULL,
|
|
+ .set_use_inc = set_use_inc,
|
|
+ .set_use_dec = set_use_dec,
|
|
+ .fops = &lirc_fops,
|
|
+ .dev = NULL,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+
|
|
+#ifdef MODULE
|
|
+static int init_chrdev(void)
|
|
+{
|
|
+ driver.minor = lirc_register_driver(&driver);
|
|
+
|
|
+ if (driver.minor < 0) {
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME ": init_chrdev() failed.\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static void drop_chrdev(void)
|
|
+{
|
|
+ lirc_unregister_driver(driver.minor);
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/* SECTION: Hardware */
|
|
+static long delta(struct timeval *tv1, struct timeval *tv2)
|
|
+{
|
|
+ unsigned long deltv;
|
|
+
|
|
+ deltv = tv2->tv_sec - tv1->tv_sec;
|
|
+ if (deltv > 15)
|
|
+ deltv = 0xFFFFFF;
|
|
+ else
|
|
+ deltv = deltv*1000000 + tv2->tv_usec - tv1->tv_usec;
|
|
+ return deltv;
|
|
+}
|
|
+
|
|
+static void it87_timeout(unsigned long data)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ /* avoid interference with interrupt */
|
|
+ spin_lock_irqsave(&timer_lock, flags);
|
|
+
|
|
+ if (digimatrix) {
|
|
+ /* We have timed out. Disable the RX mechanism. */
|
|
+
|
|
+ outb((inb(io + IT87_CIR_RCR) & ~IT87_CIR_RCR_RXEN) |
|
|
+ IT87_CIR_RCR_RXACT, io + IT87_CIR_RCR);
|
|
+ if (it87_RXEN_mask)
|
|
+ outb(inb(io + IT87_CIR_RCR) | IT87_CIR_RCR_RXEN,
|
|
+ io + IT87_CIR_RCR);
|
|
+ dprintk(" TIMEOUT\n");
|
|
+ timer_enabled = 0;
|
|
+
|
|
+ /* fifo clear */
|
|
+ outb(inb(io + IT87_CIR_TCR1) | IT87_CIR_TCR1_FIFOCLR,
|
|
+ io+IT87_CIR_TCR1);
|
|
+
|
|
+ } else {
|
|
+ /*
|
|
+ * if last received signal was a pulse, but receiving stopped
|
|
+ * within the 9 bit frame, we need to finish this pulse and
|
|
+ * simulate a signal change to from pulse to space. Otherwise
|
|
+ * upper layers will receive two sequences next time.
|
|
+ */
|
|
+
|
|
+ if (last_value) {
|
|
+ unsigned long pulse_end;
|
|
+
|
|
+ /* determine 'virtual' pulse end: */
|
|
+ pulse_end = delta(&last_tv, &last_intr_tv);
|
|
+ dprintk("timeout add %d for %lu usec\n",
|
|
+ last_value, pulse_end);
|
|
+ add_read_queue(last_value, pulse_end);
|
|
+ last_value = 0;
|
|
+ last_tv = last_intr_tv;
|
|
+ }
|
|
+ }
|
|
+ spin_unlock_irqrestore(&timer_lock, flags);
|
|
+}
|
|
+
|
|
+static irqreturn_t it87_interrupt(int irq, void *dev_id)
|
|
+{
|
|
+ unsigned char data;
|
|
+ struct timeval curr_tv;
|
|
+ static unsigned long deltv;
|
|
+ unsigned long deltintrtv;
|
|
+ unsigned long flags, hw_flags;
|
|
+ int iir, lsr;
|
|
+ int fifo = 0;
|
|
+ static char lastbit;
|
|
+ char bit;
|
|
+
|
|
+ /* Bit duration in microseconds */
|
|
+ const unsigned long bit_duration = 1000000ul /
|
|
+ (115200 / IT87_CIR_BAUDRATE_DIVISOR);
|
|
+
|
|
+
|
|
+ iir = inb(io + IT87_CIR_IIR);
|
|
+
|
|
+ switch (iir & IT87_CIR_IIR_IID) {
|
|
+ case 0x4:
|
|
+ case 0x6:
|
|
+ lsr = inb(io + IT87_CIR_RSR) & (IT87_CIR_RSR_RXFTO |
|
|
+ IT87_CIR_RSR_RXFBC);
|
|
+ fifo = lsr & IT87_CIR_RSR_RXFBC;
|
|
+ dprintk("iir: 0x%x fifo: 0x%x\n", iir, lsr);
|
|
+
|
|
+ /* avoid interference with timer */
|
|
+ spin_lock_irqsave(&timer_lock, flags);
|
|
+ spin_lock_irqsave(&hardware_lock, hw_flags);
|
|
+ if (digimatrix) {
|
|
+ static unsigned long acc_pulse;
|
|
+ static unsigned long acc_space;
|
|
+
|
|
+ do {
|
|
+ data = inb(io + IT87_CIR_DR);
|
|
+ data = ~data;
|
|
+ fifo--;
|
|
+ if (data != 0x00) {
|
|
+ if (timer_enabled)
|
|
+ del_timer(&timerlist);
|
|
+ /*
|
|
+ * start timer for end of
|
|
+ * sequence detection
|
|
+ */
|
|
+ timerlist.expires = jiffies +
|
|
+ IT87_TIMEOUT;
|
|
+ add_timer(&timerlist);
|
|
+ timer_enabled = 1;
|
|
+ }
|
|
+ /* Loop through */
|
|
+ for (bit = 0; bit < 8; ++bit) {
|
|
+ if ((data >> bit) & 1) {
|
|
+ ++acc_pulse;
|
|
+ if (lastbit == 0) {
|
|
+ add_read_queue(0,
|
|
+ acc_space *
|
|
+ bit_duration);
|
|
+ acc_space = 0;
|
|
+ }
|
|
+ } else {
|
|
+ ++acc_space;
|
|
+ if (lastbit == 1) {
|
|
+ add_read_queue(1,
|
|
+ acc_pulse *
|
|
+ bit_duration);
|
|
+ acc_pulse = 0;
|
|
+ }
|
|
+ }
|
|
+ lastbit = (data >> bit) & 1;
|
|
+ }
|
|
+
|
|
+ } while (fifo != 0);
|
|
+ } else { /* Normal Operation */
|
|
+ do {
|
|
+ del_timer(&timerlist);
|
|
+ data = inb(io + IT87_CIR_DR);
|
|
+
|
|
+ dprintk("data=%02x\n", data);
|
|
+ do_gettimeofday(&curr_tv);
|
|
+ deltv = delta(&last_tv, &curr_tv);
|
|
+ deltintrtv = delta(&last_intr_tv, &curr_tv);
|
|
+
|
|
+ dprintk("t %lu , d %d\n",
|
|
+ deltintrtv, (int)data);
|
|
+
|
|
+ /*
|
|
+ * if nothing came in last 2 cycles,
|
|
+ * it was gap
|
|
+ */
|
|
+ if (deltintrtv > TIME_CONST * 2) {
|
|
+ if (last_value) {
|
|
+ dprintk("GAP\n");
|
|
+
|
|
+ /* simulate signal change */
|
|
+ add_read_queue(last_value,
|
|
+ deltv -
|
|
+ deltintrtv);
|
|
+ last_value = 0;
|
|
+ last_tv.tv_sec =
|
|
+ last_intr_tv.tv_sec;
|
|
+ last_tv.tv_usec =
|
|
+ last_intr_tv.tv_usec;
|
|
+ deltv = deltintrtv;
|
|
+ }
|
|
+ }
|
|
+ data = 1;
|
|
+ if (data ^ last_value) {
|
|
+ /*
|
|
+ * deltintrtv > 2*TIME_CONST,
|
|
+ * remember ? the other case is
|
|
+ * timeout
|
|
+ */
|
|
+ add_read_queue(last_value,
|
|
+ deltv-TIME_CONST);
|
|
+ last_value = data;
|
|
+ last_tv = curr_tv;
|
|
+ if (last_tv.tv_usec >= TIME_CONST)
|
|
+ last_tv.tv_usec -= TIME_CONST;
|
|
+ else {
|
|
+ last_tv.tv_sec--;
|
|
+ last_tv.tv_usec += 1000000 -
|
|
+ TIME_CONST;
|
|
+ }
|
|
+ }
|
|
+ last_intr_tv = curr_tv;
|
|
+ if (data) {
|
|
+ /*
|
|
+ * start timer for end of
|
|
+ * sequence detection
|
|
+ */
|
|
+ timerlist.expires =
|
|
+ jiffies + IT87_TIMEOUT;
|
|
+ add_timer(&timerlist);
|
|
+ }
|
|
+ outb((inb(io + IT87_CIR_RCR) &
|
|
+ ~IT87_CIR_RCR_RXEN) |
|
|
+ IT87_CIR_RCR_RXACT,
|
|
+ io + IT87_CIR_RCR);
|
|
+ if (it87_RXEN_mask)
|
|
+ outb(inb(io + IT87_CIR_RCR) |
|
|
+ IT87_CIR_RCR_RXEN,
|
|
+ io + IT87_CIR_RCR);
|
|
+ fifo--;
|
|
+ } while (fifo != 0);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&hardware_lock, hw_flags);
|
|
+ spin_unlock_irqrestore(&timer_lock, flags);
|
|
+
|
|
+ return IRQ_RETVAL(IRQ_HANDLED);
|
|
+
|
|
+ default:
|
|
+ /* not our irq */
|
|
+ dprintk("unknown IRQ (shouldn't happen) !!\n");
|
|
+ return IRQ_RETVAL(IRQ_NONE);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static void send_it87(unsigned long len, unsigned long stime,
|
|
+ unsigned char send_byte, unsigned int count_bits)
|
|
+{
|
|
+ long count = len / stime;
|
|
+ long time_left = 0;
|
|
+ static unsigned char byte_out;
|
|
+ unsigned long hw_flags;
|
|
+
|
|
+ dprintk("%s: len=%ld, sb=%d\n", __func__, len, send_byte);
|
|
+
|
|
+ time_left = (long)len - (long)count * (long)stime;
|
|
+ count += ((2 * time_left) / stime);
|
|
+ while (count) {
|
|
+ long i = 0;
|
|
+ for (i = 0; i < count_bits; i++) {
|
|
+ byte_out = (byte_out << 1) | (send_byte & 1);
|
|
+ it87_bits_in_byte_out++;
|
|
+ }
|
|
+ if (it87_bits_in_byte_out == 8) {
|
|
+ dprintk("out=0x%x, tsr_txfbc: 0x%x\n",
|
|
+ byte_out,
|
|
+ inb(io + IT87_CIR_TSR) &
|
|
+ IT87_CIR_TSR_TXFBC);
|
|
+
|
|
+ while ((inb(io + IT87_CIR_TSR) &
|
|
+ IT87_CIR_TSR_TXFBC) >= IT87_CIR_FIFO_SIZE)
|
|
+ ;
|
|
+
|
|
+ spin_lock_irqsave(&hardware_lock, hw_flags);
|
|
+ outb(byte_out, io + IT87_CIR_DR);
|
|
+ spin_unlock_irqrestore(&hardware_lock, hw_flags);
|
|
+
|
|
+ it87_bits_in_byte_out = 0;
|
|
+ it87_send_counter++;
|
|
+ byte_out = 0;
|
|
+ }
|
|
+ count--;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/*TODO: maybe exchange space and pulse because it8705 only modulates 0-bits */
|
|
+
|
|
+static void send_space(unsigned long len)
|
|
+{
|
|
+ send_it87(len, TIME_CONST, IT87_CIR_SPACE, IT87_CIR_BAUDRATE_DIVISOR);
|
|
+}
|
|
+
|
|
+static void send_pulse(unsigned long len)
|
|
+{
|
|
+ send_it87(len, TIME_CONST, IT87_CIR_PULSE, IT87_CIR_BAUDRATE_DIVISOR);
|
|
+}
|
|
+
|
|
+
|
|
+static void init_send()
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&hardware_lock, flags);
|
|
+ /* RXEN=0: receiver disable */
|
|
+ it87_RXEN_mask = 0;
|
|
+ outb(inb(io + IT87_CIR_RCR) & ~IT87_CIR_RCR_RXEN,
|
|
+ io + IT87_CIR_RCR);
|
|
+ spin_unlock_irqrestore(&hardware_lock, flags);
|
|
+ it87_bits_in_byte_out = 0;
|
|
+ it87_send_counter = 0;
|
|
+}
|
|
+
|
|
+
|
|
+static void terminate_send(unsigned long len)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ unsigned long last = 0;
|
|
+
|
|
+ last = it87_send_counter;
|
|
+ /* make sure all necessary data has been sent */
|
|
+ while (last == it87_send_counter)
|
|
+ send_space(len);
|
|
+ /* wait until all data sent */
|
|
+ while ((inb(io + IT87_CIR_TSR) & IT87_CIR_TSR_TXFBC) != 0)
|
|
+ ;
|
|
+ /* then re-enable receiver */
|
|
+ spin_lock_irqsave(&hardware_lock, flags);
|
|
+ it87_RXEN_mask = IT87_CIR_RCR_RXEN;
|
|
+ outb(inb(io + IT87_CIR_RCR) | IT87_CIR_RCR_RXEN,
|
|
+ io + IT87_CIR_RCR);
|
|
+ spin_unlock_irqrestore(&hardware_lock, flags);
|
|
+}
|
|
+
|
|
+
|
|
+static int init_hardware(void)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ unsigned char it87_rcr = 0;
|
|
+
|
|
+ spin_lock_irqsave(&hardware_lock, flags);
|
|
+ /* init cir-port */
|
|
+ /* enable r/w-access to Baudrate-Register */
|
|
+ outb(IT87_CIR_IER_BR, io + IT87_CIR_IER);
|
|
+ outb(IT87_CIR_BAUDRATE_DIVISOR % 0x100, io+IT87_CIR_BDLR);
|
|
+ outb(IT87_CIR_BAUDRATE_DIVISOR / 0x100, io+IT87_CIR_BDHR);
|
|
+ /* Baudrate Register off, define IRQs: Input only */
|
|
+ if (digimatrix) {
|
|
+ outb(IT87_CIR_IER_IEC | IT87_CIR_IER_RFOIE, io + IT87_CIR_IER);
|
|
+ /* RX: HCFS=0, RXDCR = 001b (33,75..38,25 kHz), RXEN=1 */
|
|
+ } else {
|
|
+ outb(IT87_CIR_IER_IEC | IT87_CIR_IER_RDAIE, io + IT87_CIR_IER);
|
|
+ /* RX: HCFS=0, RXDCR = 001b (35,6..40,3 kHz), RXEN=1 */
|
|
+ }
|
|
+ it87_rcr = (IT87_CIR_RCR_RXEN & it87_RXEN_mask) | 0x1;
|
|
+ if (it87_enable_demodulator)
|
|
+ it87_rcr |= IT87_CIR_RCR_RXEND;
|
|
+ outb(it87_rcr, io + IT87_CIR_RCR);
|
|
+ if (digimatrix) {
|
|
+ /* Set FIFO depth to 1 byte, and disable TX */
|
|
+ outb(inb(io + IT87_CIR_TCR1) | 0x00,
|
|
+ io + IT87_CIR_TCR1);
|
|
+
|
|
+ /*
|
|
+ * TX: it87_freq (36kHz), 'reserved' sensitivity
|
|
+ * setting (0x00)
|
|
+ */
|
|
+ outb(((it87_freq - IT87_CIR_FREQ_MIN) << 3) | 0x00,
|
|
+ io + IT87_CIR_TCR2);
|
|
+ } else {
|
|
+ /* TX: 38kHz, 13,3us (pulse-width) */
|
|
+ outb(((it87_freq - IT87_CIR_FREQ_MIN) << 3) | 0x06,
|
|
+ io + IT87_CIR_TCR2);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&hardware_lock, flags);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static void drop_hardware(void)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&hardware_lock, flags);
|
|
+ disable_irq(irq);
|
|
+ /* receiver disable */
|
|
+ it87_RXEN_mask = 0;
|
|
+ outb(0x1, io + IT87_CIR_RCR);
|
|
+ /* turn off irqs */
|
|
+ outb(0, io + IT87_CIR_IER);
|
|
+ /* fifo clear */
|
|
+ outb(IT87_CIR_TCR1_FIFOCLR, io+IT87_CIR_TCR1);
|
|
+ /* reset */
|
|
+ outb(IT87_CIR_IER_RESET, io+IT87_CIR_IER);
|
|
+ enable_irq(irq);
|
|
+ spin_unlock_irqrestore(&hardware_lock, flags);
|
|
+}
|
|
+
|
|
+
|
|
+static unsigned char it87_read(unsigned char port)
|
|
+{
|
|
+ outb(port, IT87_ADRPORT);
|
|
+ return inb(IT87_DATAPORT);
|
|
+}
|
|
+
|
|
+
|
|
+static void it87_write(unsigned char port, unsigned char data)
|
|
+{
|
|
+ outb(port, IT87_ADRPORT);
|
|
+ outb(data, IT87_DATAPORT);
|
|
+}
|
|
+
|
|
+
|
|
+/* SECTION: Initialisation */
|
|
+
|
|
+static int init_port(void)
|
|
+{
|
|
+ unsigned long hw_flags;
|
|
+ int retval = 0;
|
|
+
|
|
+ unsigned char init_bytes[4] = IT87_INIT;
|
|
+ unsigned char it87_chipid = 0;
|
|
+ unsigned char ldn = 0;
|
|
+ unsigned int it87_io = 0;
|
|
+ unsigned int it87_irq = 0;
|
|
+
|
|
+ /* Enter MB PnP Mode */
|
|
+ outb(init_bytes[0], IT87_ADRPORT);
|
|
+ outb(init_bytes[1], IT87_ADRPORT);
|
|
+ outb(init_bytes[2], IT87_ADRPORT);
|
|
+ outb(init_bytes[3], IT87_ADRPORT);
|
|
+
|
|
+ /* 8712 or 8705 ? */
|
|
+ it87_chipid = it87_read(IT87_CHIP_ID1);
|
|
+ if (it87_chipid != 0x87) {
|
|
+ retval = -ENXIO;
|
|
+ return retval;
|
|
+ }
|
|
+ it87_chipid = it87_read(IT87_CHIP_ID2);
|
|
+ if ((it87_chipid != 0x05) &&
|
|
+ (it87_chipid != 0x12) &&
|
|
+ (it87_chipid != 0x18) &&
|
|
+ (it87_chipid != 0x20)) {
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
+ ": no IT8704/05/12/18/20 found (claimed IT87%02x), "
|
|
+ "exiting..\n", it87_chipid);
|
|
+ retval = -ENXIO;
|
|
+ return retval;
|
|
+ }
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
+ ": found IT87%02x.\n",
|
|
+ it87_chipid);
|
|
+
|
|
+ /* get I/O-Port and IRQ */
|
|
+ if (it87_chipid == 0x12 || it87_chipid == 0x18)
|
|
+ ldn = IT8712_CIR_LDN;
|
|
+ else
|
|
+ ldn = IT8705_CIR_LDN;
|
|
+ it87_write(IT87_LDN, ldn);
|
|
+
|
|
+ it87_io = it87_read(IT87_CIR_BASE_MSB) * 256 +
|
|
+ it87_read(IT87_CIR_BASE_LSB);
|
|
+ if (it87_io == 0) {
|
|
+ if (io == 0)
|
|
+ io = IT87_CIR_DEFAULT_IOBASE;
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
+ ": set default io 0x%x\n",
|
|
+ io);
|
|
+ it87_write(IT87_CIR_BASE_MSB, io / 0x100);
|
|
+ it87_write(IT87_CIR_BASE_LSB, io % 0x100);
|
|
+ } else
|
|
+ io = it87_io;
|
|
+
|
|
+ it87_irq = it87_read(IT87_CIR_IRQ);
|
|
+ if (digimatrix || it87_irq == 0) {
|
|
+ if (irq == 0)
|
|
+ irq = IT87_CIR_DEFAULT_IRQ;
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
+ ": set default irq 0x%x\n",
|
|
+ irq);
|
|
+ it87_write(IT87_CIR_IRQ, irq);
|
|
+ } else
|
|
+ irq = it87_irq;
|
|
+
|
|
+ spin_lock_irqsave(&hardware_lock, hw_flags);
|
|
+ /* reset */
|
|
+ outb(IT87_CIR_IER_RESET, io+IT87_CIR_IER);
|
|
+ /* fifo clear */
|
|
+ outb(IT87_CIR_TCR1_FIFOCLR |
|
|
+ /* IT87_CIR_TCR1_ILE | */
|
|
+ IT87_CIR_TCR1_TXRLE |
|
|
+ IT87_CIR_TCR1_TXENDF, io+IT87_CIR_TCR1);
|
|
+ spin_unlock_irqrestore(&hardware_lock, hw_flags);
|
|
+
|
|
+ /* get I/O port access and IRQ line */
|
|
+ if (request_region(io, 8, LIRC_DRIVER_NAME) == NULL) {
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
+ ": i/o port 0x%.4x already in use.\n", io);
|
|
+ /* Leaving MB PnP Mode */
|
|
+ it87_write(IT87_CFGCTRL, 0x2);
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ /* activate CIR-Device */
|
|
+ it87_write(IT87_CIR_ACT, 0x1);
|
|
+
|
|
+ /* Leaving MB PnP Mode */
|
|
+ it87_write(IT87_CFGCTRL, 0x2);
|
|
+
|
|
+ retval = request_irq(irq, it87_interrupt, 0 /*IRQF_DISABLED*/,
|
|
+ LIRC_DRIVER_NAME, NULL);
|
|
+ if (retval < 0) {
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
+ ": IRQ %d already in use.\n",
|
|
+ irq);
|
|
+ release_region(io, 8);
|
|
+ return retval;
|
|
+ }
|
|
+
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
+ ": I/O port 0x%.4x, IRQ %d.\n", io, irq);
|
|
+
|
|
+ init_timer(&timerlist);
|
|
+ timerlist.function = it87_timeout;
|
|
+ timerlist.data = 0xabadcafe;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static void drop_port(void)
|
|
+{
|
|
+#if 0
|
|
+ unsigned char init_bytes[4] = IT87_INIT;
|
|
+
|
|
+ /* Enter MB PnP Mode */
|
|
+ outb(init_bytes[0], IT87_ADRPORT);
|
|
+ outb(init_bytes[1], IT87_ADRPORT);
|
|
+ outb(init_bytes[2], IT87_ADRPORT);
|
|
+ outb(init_bytes[3], IT87_ADRPORT);
|
|
+
|
|
+ /* deactivate CIR-Device */
|
|
+ it87_write(IT87_CIR_ACT, 0x0);
|
|
+
|
|
+ /* Leaving MB PnP Mode */
|
|
+ it87_write(IT87_CFGCTRL, 0x2);
|
|
+#endif
|
|
+
|
|
+ del_timer_sync(&timerlist);
|
|
+ free_irq(irq, NULL);
|
|
+ release_region(io, 8);
|
|
+}
|
|
+
|
|
+
|
|
+static int init_lirc_it87(void)
|
|
+{
|
|
+ int retval;
|
|
+
|
|
+ init_waitqueue_head(&lirc_read_queue);
|
|
+ retval = init_port();
|
|
+ if (retval < 0)
|
|
+ return retval;
|
|
+ init_hardware();
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": Installed.\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static int __init lirc_it87_init(void)
|
|
+{
|
|
+ int retval;
|
|
+
|
|
+ retval = init_chrdev();
|
|
+ if (retval < 0)
|
|
+ return retval;
|
|
+ retval = init_lirc_it87();
|
|
+ if (retval) {
|
|
+ drop_chrdev();
|
|
+ return retval;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static void __exit lirc_it87_exit(void)
|
|
+{
|
|
+ drop_hardware();
|
|
+ drop_chrdev();
|
|
+ drop_port();
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": Uninstalled.\n");
|
|
+}
|
|
+
|
|
+module_init(lirc_it87_init);
|
|
+module_exit(lirc_it87_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("LIRC driver for ITE IT8704/05/12/18/20 CIR port");
|
|
+MODULE_AUTHOR("Hans-Gunter Lutke Uphues");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_param(io, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(io, "I/O base address (default: 0x310)");
|
|
+
|
|
+module_param(irq, int, S_IRUGO);
|
|
+#ifdef LIRC_IT87_DIGIMATRIX
|
|
+MODULE_PARM_DESC(irq, "Interrupt (1,3-12) (default: 9)");
|
|
+#else
|
|
+MODULE_PARM_DESC(irq, "Interrupt (1,3-12) (default: 7)");
|
|
+#endif
|
|
+
|
|
+module_param(it87_enable_demodulator, bool, S_IRUGO);
|
|
+MODULE_PARM_DESC(it87_enable_demodulator,
|
|
+ "Receiver demodulator enable/disable (1/0), default: 0");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
+
|
|
+module_param(digimatrix, bool, S_IRUGO | S_IWUSR);
|
|
+#ifdef LIRC_IT87_DIGIMATRIX
|
|
+MODULE_PARM_DESC(digimatrix,
|
|
+ "Asus Digimatrix it87 compat. enable/disable (1/0), default: 1");
|
|
+#else
|
|
+MODULE_PARM_DESC(digimatrix,
|
|
+ "Asus Digimatrix it87 compat. enable/disable (1/0), default: 0");
|
|
+#endif
|
|
+
|
|
+
|
|
+module_param(it87_freq, int, S_IRUGO);
|
|
+#ifdef LIRC_IT87_DIGIMATRIX
|
|
+MODULE_PARM_DESC(it87_freq,
|
|
+ "Carrier demodulator frequency (kHz), (default: 36)");
|
|
+#else
|
|
+MODULE_PARM_DESC(it87_freq,
|
|
+ "Carrier demodulator frequency (kHz), (default: 38)");
|
|
+#endif
|
|
diff --git a/drivers/input/lirc/lirc_it87.h b/drivers/input/lirc/lirc_it87.h
|
|
new file mode 100644
|
|
index 0000000..cf021c8
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_it87.h
|
|
@@ -0,0 +1,116 @@
|
|
+/* lirc_it87.h */
|
|
+/* SECTION: Definitions */
|
|
+
|
|
+/********************************* ITE IT87xx ************************/
|
|
+
|
|
+/* based on the following documentation from ITE:
|
|
+ a) IT8712F Preliminary CIR Programming Guide V0.1
|
|
+ b) IT8705F Simple LPC I/O Preliminary Specification V0.3
|
|
+ c) IT8712F EC-LPC I/O Preliminary Specification V0.5
|
|
+*/
|
|
+
|
|
+/* IT8712/05 Ports: */
|
|
+#define IT87_ADRPORT 0x2e
|
|
+#define IT87_DATAPORT 0x2f
|
|
+#define IT87_INIT {0x87, 0x01, 0x55, 0x55}
|
|
+
|
|
+/* alternate Ports: */
|
|
+/*
|
|
+#define IT87_ADRPORT 0x4e
|
|
+#define IT87_DATAPORT 0x4f
|
|
+#define IT87_INIT {0x87, 0x01, 0x55, 0xaa}
|
|
+ */
|
|
+
|
|
+/* IT8712/05 Registers */
|
|
+#define IT87_CFGCTRL 0x2
|
|
+#define IT87_LDN 0x7
|
|
+#define IT87_CHIP_ID1 0x20
|
|
+#define IT87_CHIP_ID2 0x21
|
|
+#define IT87_CFG_VERSION 0x22
|
|
+#define IT87_SWSUSPEND 0x23
|
|
+
|
|
+#define IT8712_CIR_LDN 0xa
|
|
+#define IT8705_CIR_LDN 0x7
|
|
+
|
|
+/* CIR Configuration Registers: */
|
|
+#define IT87_CIR_ACT 0x30
|
|
+#define IT87_CIR_BASE_MSB 0x60
|
|
+#define IT87_CIR_BASE_LSB 0x61
|
|
+#define IT87_CIR_IRQ 0x70
|
|
+#define IT87_CIR_CONFIG 0xf0
|
|
+
|
|
+/* List of IT87_CIR registers: offset to BaseAddr */
|
|
+#define IT87_CIR_DR 0
|
|
+#define IT87_CIR_IER 1
|
|
+#define IT87_CIR_RCR 2
|
|
+#define IT87_CIR_TCR1 3
|
|
+#define IT87_CIR_TCR2 4
|
|
+#define IT87_CIR_TSR 5
|
|
+#define IT87_CIR_RSR 6
|
|
+#define IT87_CIR_BDLR 5
|
|
+#define IT87_CIR_BDHR 6
|
|
+#define IT87_CIR_IIR 7
|
|
+
|
|
+/* Bit Definition */
|
|
+/* IER: */
|
|
+#define IT87_CIR_IER_TM_EN 0x80
|
|
+#define IT87_CIR_IER_RESEVED 0x40
|
|
+#define IT87_CIR_IER_RESET 0x20
|
|
+#define IT87_CIR_IER_BR 0x10
|
|
+#define IT87_CIR_IER_IEC 0x8
|
|
+#define IT87_CIR_IER_RFOIE 0x4
|
|
+#define IT87_CIR_IER_RDAIE 0x2
|
|
+#define IT87_CIR_IER_TLDLIE 0x1
|
|
+
|
|
+/* RCR: */
|
|
+#define IT87_CIR_RCR_RDWOS 0x80
|
|
+#define IT87_CIR_RCR_HCFS 0x40
|
|
+#define IT87_CIR_RCR_RXEN 0x20
|
|
+#define IT87_CIR_RCR_RXEND 0x10
|
|
+#define IT87_CIR_RCR_RXACT 0x8
|
|
+#define IT87_CIR_RCR_RXDCR 0x7
|
|
+
|
|
+/* TCR1: */
|
|
+#define IT87_CIR_TCR1_FIFOCLR 0x80
|
|
+#define IT87_CIR_TCR1_ILE 0x40
|
|
+#define IT87_CIR_TCR1_FIFOTL 0x30
|
|
+#define IT87_CIR_TCR1_TXRLE 0x8
|
|
+#define IT87_CIR_TCR1_TXENDF 0x4
|
|
+#define IT87_CIR_TCR1_TXMPM 0x3
|
|
+
|
|
+/* TCR2: */
|
|
+#define IT87_CIR_TCR2_CFQ 0xf8
|
|
+#define IT87_CIR_TCR2_TXMPW 0x7
|
|
+
|
|
+/* TSR: */
|
|
+#define IT87_CIR_TSR_RESERVED 0xc0
|
|
+#define IT87_CIR_TSR_TXFBC 0x3f
|
|
+
|
|
+/* RSR: */
|
|
+#define IT87_CIR_RSR_RXFTO 0x80
|
|
+#define IT87_CIR_RSR_RESERVED 0x40
|
|
+#define IT87_CIR_RSR_RXFBC 0x3f
|
|
+
|
|
+/* IIR: */
|
|
+#define IT87_CIR_IIR_RESERVED 0xf8
|
|
+#define IT87_CIR_IIR_IID 0x6
|
|
+#define IT87_CIR_IIR_IIP 0x1
|
|
+
|
|
+/* TM: */
|
|
+#define IT87_CIR_TM_IL_SEL 0x80
|
|
+#define IT87_CIR_TM_RESERVED 0x40
|
|
+#define IT87_CIR_TM_TM_REG 0x3f
|
|
+
|
|
+#define IT87_CIR_FIFO_SIZE 32
|
|
+
|
|
+/* Baudratedivisor for IT87: power of 2: only 1,2,4 or 8) */
|
|
+#define IT87_CIR_BAUDRATE_DIVISOR 0x1
|
|
+#define IT87_CIR_DEFAULT_IOBASE 0x310
|
|
+#define IT87_CIR_DEFAULT_IRQ 0x7
|
|
+#define IT87_CIR_SPACE 0x00
|
|
+#define IT87_CIR_PULSE 0xff
|
|
+#define IT87_CIR_FREQ_MIN 27
|
|
+#define IT87_CIR_FREQ_MAX 58
|
|
+#define TIME_CONST (IT87_CIR_BAUDRATE_DIVISOR * 8000000ul / 115200ul)
|
|
+
|
|
+/********************************* ITE IT87xx ************************/
|
|
diff --git a/drivers/input/lirc/lirc_ite8709.c b/drivers/input/lirc/lirc_ite8709.c
|
|
new file mode 100644
|
|
index 0000000..6210847
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_ite8709.c
|
|
@@ -0,0 +1,540 @@
|
|
+/*
|
|
+ * LIRC driver for ITE8709 CIR port
|
|
+ *
|
|
+ * Copyright (C) 2008 Grégory Lardière <spmf2004-lirc@yahoo.fr>
|
|
+ *
|
|
+ * 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/interrupt.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/pnp.h>
|
|
+#include <linux/io.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+#define LIRC_DRIVER_NAME "lirc_ite8709"
|
|
+
|
|
+#define BUF_CHUNK_SIZE sizeof(int)
|
|
+#define BUF_SIZE (128*BUF_CHUNK_SIZE)
|
|
+
|
|
+/*
|
|
+ * The ITE8709 device seems to be the combination of IT8512 superIO chip and
|
|
+ * a specific firmware running on the IT8512's embedded micro-controller.
|
|
+ * In addition of the embedded micro-controller, the IT8512 chip contains a
|
|
+ * CIR module and several other modules. A few modules are directly accessible
|
|
+ * by the host CPU, but most of them are only accessible by the
|
|
+ * micro-controller. The CIR module is only accessible by the micro-controller.
|
|
+ * The battery-backed SRAM module is accessible by the host CPU and the
|
|
+ * micro-controller. So one of the MC's firmware role is to act as a bridge
|
|
+ * between the host CPU and the CIR module. The firmware implements a kind of
|
|
+ * communication protocol using the SRAM module as a shared memory. The IT8512
|
|
+ * specification is publicly available on ITE's web site, but the communication
|
|
+ * protocol is not, so it was reverse-engineered.
|
|
+ */
|
|
+
|
|
+/* ITE8709 Registers addresses and values (reverse-engineered) */
|
|
+#define ITE8709_MODE 0x1a
|
|
+#define ITE8709_REG_ADR 0x1b
|
|
+#define ITE8709_REG_VAL 0x1c
|
|
+#define ITE8709_IIR 0x1e /* Interrupt identification register */
|
|
+#define ITE8709_RFSR 0x1f /* Receiver FIFO status register */
|
|
+#define ITE8709_FIFO_START 0x20
|
|
+
|
|
+#define ITE8709_MODE_READY 0X00
|
|
+#define ITE8709_MODE_WRITE 0X01
|
|
+#define ITE8709_MODE_READ 0X02
|
|
+#define ITE8709_IIR_RDAI 0x02 /* Receiver data available interrupt */
|
|
+#define ITE8709_IIR_RFOI 0x04 /* Receiver FIFO overrun interrupt */
|
|
+#define ITE8709_RFSR_MASK 0x3f /* FIFO byte count mask */
|
|
+
|
|
+/*
|
|
+ * IT8512 CIR-module registers addresses and values
|
|
+ * (from IT8512 E/F specification v0.4.1)
|
|
+ */
|
|
+#define IT8512_REG_MSTCR 0x01 /* Master control register */
|
|
+#define IT8512_REG_IER 0x02 /* Interrupt enable register */
|
|
+#define IT8512_REG_CFR 0x04 /* Carrier frequency register */
|
|
+#define IT8512_REG_RCR 0x05 /* Receive control register */
|
|
+#define IT8512_REG_BDLR 0x08 /* Baud rate divisor low byte register */
|
|
+#define IT8512_REG_BDHR 0x09 /* Baud rate divisor high byte register */
|
|
+
|
|
+#define IT8512_MSTCR_RESET 0x01 /* Reset registers to default value */
|
|
+#define IT8512_MSTCR_FIFOCLR 0x02 /* Clear FIFO */
|
|
+#define IT8512_MSTCR_FIFOTL_7 0x04 /* FIFO threshold level : 7 */
|
|
+#define IT8512_MSTCR_FIFOTL_25 0x0c /* FIFO threshold level : 25 */
|
|
+#define IT8512_IER_RDAIE 0x02 /* Enable data interrupt request */
|
|
+#define IT8512_IER_RFOIE 0x04 /* Enable FIFO overrun interrupt req */
|
|
+#define IT8512_IER_IEC 0x80 /* Enable interrupt request */
|
|
+#define IT8512_CFR_CF_36KHZ 0x09 /* Carrier freq : low speed, 36kHz */
|
|
+#define IT8512_RCR_RXDCR_1 0x01 /* Demodulation carrier range : 1 */
|
|
+#define IT8512_RCR_RXACT 0x08 /* Receiver active */
|
|
+#define IT8512_RCR_RXEN 0x80 /* Receiver enable */
|
|
+#define IT8512_BDR_6 6 /* Baud rate divisor : 6 */
|
|
+
|
|
+/* Actual values used by this driver */
|
|
+#define CFG_FIFOTL IT8512_MSTCR_FIFOTL_25
|
|
+#define CFG_CR_FREQ IT8512_CFR_CF_36KHZ
|
|
+#define CFG_DCR IT8512_RCR_RXDCR_1
|
|
+#define CFG_BDR IT8512_BDR_6
|
|
+#define CFG_TIMEOUT 100000 /* Rearm interrupt when a space is > 100 ms */
|
|
+
|
|
+static int debug;
|
|
+
|
|
+struct ite8709_device {
|
|
+ int use_count;
|
|
+ int io;
|
|
+ int irq;
|
|
+ spinlock_t hardware_lock;
|
|
+ unsigned long long acc_pulse;
|
|
+ unsigned long long acc_space;
|
|
+ char lastbit;
|
|
+ struct timeval last_tv;
|
|
+ struct lirc_driver driver;
|
|
+ struct tasklet_struct tasklet;
|
|
+ char force_rearm;
|
|
+ char rearmed;
|
|
+ char device_busy;
|
|
+};
|
|
+
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \
|
|
+ fmt, ## args); \
|
|
+ } while (0)
|
|
+
|
|
+
|
|
+static unsigned char ite8709_read(struct ite8709_device *dev,
|
|
+ unsigned char port)
|
|
+{
|
|
+ outb(port, dev->io);
|
|
+ return inb(dev->io+1);
|
|
+}
|
|
+
|
|
+static void ite8709_write(struct ite8709_device *dev, unsigned char port,
|
|
+ unsigned char data)
|
|
+{
|
|
+ outb(port, dev->io);
|
|
+ outb(data, dev->io+1);
|
|
+}
|
|
+
|
|
+static void ite8709_wait_device(struct ite8709_device *dev)
|
|
+{
|
|
+ int i = 0;
|
|
+ /*
|
|
+ * loop until device tells it's ready to continue
|
|
+ * iterations count is usually ~750 but can sometimes achieve 13000
|
|
+ */
|
|
+ for (i = 0; i < 15000; i++) {
|
|
+ udelay(2);
|
|
+ if (ite8709_read(dev, ITE8709_MODE) == ITE8709_MODE_READY)
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void ite8709_write_register(struct ite8709_device *dev,
|
|
+ unsigned char reg_adr, unsigned char reg_value)
|
|
+{
|
|
+ ite8709_wait_device(dev);
|
|
+
|
|
+ ite8709_write(dev, ITE8709_REG_VAL, reg_value);
|
|
+ ite8709_write(dev, ITE8709_REG_ADR, reg_adr);
|
|
+ ite8709_write(dev, ITE8709_MODE, ITE8709_MODE_WRITE);
|
|
+}
|
|
+
|
|
+static void ite8709_init_hardware(struct ite8709_device *dev)
|
|
+{
|
|
+ spin_lock_irq(&dev->hardware_lock);
|
|
+ dev->device_busy = 1;
|
|
+ spin_unlock_irq(&dev->hardware_lock);
|
|
+
|
|
+ ite8709_write_register(dev, IT8512_REG_BDHR, (CFG_BDR >> 8) & 0xff);
|
|
+ ite8709_write_register(dev, IT8512_REG_BDLR, CFG_BDR & 0xff);
|
|
+ ite8709_write_register(dev, IT8512_REG_CFR, CFG_CR_FREQ);
|
|
+ ite8709_write_register(dev, IT8512_REG_IER,
|
|
+ IT8512_IER_IEC | IT8512_IER_RFOIE | IT8512_IER_RDAIE);
|
|
+ ite8709_write_register(dev, IT8512_REG_RCR, CFG_DCR);
|
|
+ ite8709_write_register(dev, IT8512_REG_MSTCR,
|
|
+ CFG_FIFOTL | IT8512_MSTCR_FIFOCLR);
|
|
+ ite8709_write_register(dev, IT8512_REG_RCR,
|
|
+ IT8512_RCR_RXEN | IT8512_RCR_RXACT | CFG_DCR);
|
|
+
|
|
+ spin_lock_irq(&dev->hardware_lock);
|
|
+ dev->device_busy = 0;
|
|
+ spin_unlock_irq(&dev->hardware_lock);
|
|
+
|
|
+ tasklet_enable(&dev->tasklet);
|
|
+}
|
|
+
|
|
+static void ite8709_drop_hardware(struct ite8709_device *dev)
|
|
+{
|
|
+ tasklet_disable(&dev->tasklet);
|
|
+
|
|
+ spin_lock_irq(&dev->hardware_lock);
|
|
+ dev->device_busy = 1;
|
|
+ spin_unlock_irq(&dev->hardware_lock);
|
|
+
|
|
+ ite8709_write_register(dev, IT8512_REG_RCR, 0);
|
|
+ ite8709_write_register(dev, IT8512_REG_MSTCR,
|
|
+ IT8512_MSTCR_RESET | IT8512_MSTCR_FIFOCLR);
|
|
+
|
|
+ spin_lock_irq(&dev->hardware_lock);
|
|
+ dev->device_busy = 0;
|
|
+ spin_unlock_irq(&dev->hardware_lock);
|
|
+}
|
|
+
|
|
+static int ite8709_set_use_inc(void *data)
|
|
+{
|
|
+ struct ite8709_device *dev;
|
|
+ dev = data;
|
|
+ if (dev->use_count == 0)
|
|
+ ite8709_init_hardware(dev);
|
|
+ dev->use_count++;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ite8709_set_use_dec(void *data)
|
|
+{
|
|
+ struct ite8709_device *dev;
|
|
+ dev = data;
|
|
+ dev->use_count--;
|
|
+ if (dev->use_count == 0)
|
|
+ ite8709_drop_hardware(dev);
|
|
+}
|
|
+
|
|
+static void ite8709_add_read_queue(struct ite8709_device *dev, int flag,
|
|
+ unsigned long long val)
|
|
+{
|
|
+ int value;
|
|
+
|
|
+ dprintk("add a %llu usec %s\n", val, flag ? "pulse" : "space");
|
|
+
|
|
+ value = (val > PULSE_MASK) ? PULSE_MASK : val;
|
|
+ if (flag)
|
|
+ value |= PULSE_BIT;
|
|
+
|
|
+ if (!lirc_buffer_full(dev->driver.rbuf)) {
|
|
+ lirc_buffer_write(dev->driver.rbuf, (void *) &value);
|
|
+ wake_up(&dev->driver.rbuf->wait_poll);
|
|
+ }
|
|
+}
|
|
+
|
|
+static irqreturn_t ite8709_interrupt(int irq, void *dev_id)
|
|
+{
|
|
+ unsigned char data;
|
|
+ int iir, rfsr, i;
|
|
+ int fifo = 0;
|
|
+ char bit;
|
|
+ struct timeval curr_tv;
|
|
+
|
|
+ /* Bit duration in microseconds */
|
|
+ const unsigned long bit_duration = 1000000ul / (115200 / CFG_BDR);
|
|
+
|
|
+ struct ite8709_device *dev;
|
|
+ dev = dev_id;
|
|
+
|
|
+ /*
|
|
+ * If device is busy, we simply discard data because we are in one of
|
|
+ * these two cases : shutting down or rearming the device, so this
|
|
+ * doesn't really matter and this avoids waiting too long in IRQ ctx
|
|
+ */
|
|
+ spin_lock(&dev->hardware_lock);
|
|
+ if (dev->device_busy) {
|
|
+ spin_unlock(&dev->hardware_lock);
|
|
+ return IRQ_RETVAL(IRQ_HANDLED);
|
|
+ }
|
|
+
|
|
+ iir = ite8709_read(dev, ITE8709_IIR);
|
|
+
|
|
+ switch (iir) {
|
|
+ case ITE8709_IIR_RFOI:
|
|
+ dprintk("fifo overrun, scheduling forced rearm just in case\n");
|
|
+ dev->force_rearm = 1;
|
|
+ tasklet_schedule(&dev->tasklet);
|
|
+ spin_unlock(&dev->hardware_lock);
|
|
+ return IRQ_RETVAL(IRQ_HANDLED);
|
|
+
|
|
+ case ITE8709_IIR_RDAI:
|
|
+ rfsr = ite8709_read(dev, ITE8709_RFSR);
|
|
+ fifo = rfsr & ITE8709_RFSR_MASK;
|
|
+ if (fifo > 32)
|
|
+ fifo = 32;
|
|
+ dprintk("iir: 0x%x rfsr: 0x%x fifo: %d\n", iir, rfsr, fifo);
|
|
+
|
|
+ if (dev->rearmed) {
|
|
+ do_gettimeofday(&curr_tv);
|
|
+ dev->acc_space += 1000000ull
|
|
+ * (curr_tv.tv_sec - dev->last_tv.tv_sec)
|
|
+ + (curr_tv.tv_usec - dev->last_tv.tv_usec);
|
|
+ dev->rearmed = 0;
|
|
+ }
|
|
+ for (i = 0; i < fifo; i++) {
|
|
+ data = ite8709_read(dev, i+ITE8709_FIFO_START);
|
|
+ data = ~data;
|
|
+ /* Loop through */
|
|
+ for (bit = 0; bit < 8; ++bit) {
|
|
+ if ((data >> bit) & 1) {
|
|
+ dev->acc_pulse += bit_duration;
|
|
+ if (dev->lastbit == 0) {
|
|
+ ite8709_add_read_queue(dev, 0,
|
|
+ dev->acc_space);
|
|
+ dev->acc_space = 0;
|
|
+ }
|
|
+ } else {
|
|
+ dev->acc_space += bit_duration;
|
|
+ if (dev->lastbit == 1) {
|
|
+ ite8709_add_read_queue(dev, 1,
|
|
+ dev->acc_pulse);
|
|
+ dev->acc_pulse = 0;
|
|
+ }
|
|
+ }
|
|
+ dev->lastbit = (data >> bit) & 1;
|
|
+ }
|
|
+ }
|
|
+ ite8709_write(dev, ITE8709_RFSR, 0);
|
|
+
|
|
+ if (dev->acc_space > CFG_TIMEOUT) {
|
|
+ dprintk("scheduling rearm IRQ\n");
|
|
+ do_gettimeofday(&dev->last_tv);
|
|
+ dev->force_rearm = 0;
|
|
+ tasklet_schedule(&dev->tasklet);
|
|
+ }
|
|
+
|
|
+ spin_unlock(&dev->hardware_lock);
|
|
+ return IRQ_RETVAL(IRQ_HANDLED);
|
|
+
|
|
+ default:
|
|
+ /* not our irq */
|
|
+ dprintk("unknown IRQ (shouldn't happen) !!\n");
|
|
+ spin_unlock(&dev->hardware_lock);
|
|
+ return IRQ_RETVAL(IRQ_NONE);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void ite8709_rearm_irq(unsigned long data)
|
|
+{
|
|
+ struct ite8709_device *dev;
|
|
+ unsigned long flags;
|
|
+ dev = (struct ite8709_device *) data;
|
|
+
|
|
+ spin_lock_irqsave(&dev->hardware_lock, flags);
|
|
+ dev->device_busy = 1;
|
|
+ spin_unlock_irqrestore(&dev->hardware_lock, flags);
|
|
+
|
|
+ if (dev->force_rearm || dev->acc_space > CFG_TIMEOUT) {
|
|
+ dprintk("rearming IRQ\n");
|
|
+ ite8709_write_register(dev, IT8512_REG_RCR,
|
|
+ IT8512_RCR_RXACT | CFG_DCR);
|
|
+ ite8709_write_register(dev, IT8512_REG_MSTCR,
|
|
+ CFG_FIFOTL | IT8512_MSTCR_FIFOCLR);
|
|
+ ite8709_write_register(dev, IT8512_REG_RCR,
|
|
+ IT8512_RCR_RXEN | IT8512_RCR_RXACT | CFG_DCR);
|
|
+ if (!dev->force_rearm)
|
|
+ dev->rearmed = 1;
|
|
+ dev->force_rearm = 0;
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&dev->hardware_lock, flags);
|
|
+ dev->device_busy = 0;
|
|
+ spin_unlock_irqrestore(&dev->hardware_lock, flags);
|
|
+}
|
|
+
|
|
+static int ite8709_cleanup(struct ite8709_device *dev, int stage, int errno,
|
|
+ char *msg)
|
|
+{
|
|
+ if (msg != NULL)
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME ": %s\n", msg);
|
|
+
|
|
+ switch (stage) {
|
|
+ case 6:
|
|
+ if (dev->use_count > 0)
|
|
+ ite8709_drop_hardware(dev);
|
|
+ case 5:
|
|
+ free_irq(dev->irq, dev);
|
|
+ case 4:
|
|
+ release_region(dev->io, 2);
|
|
+ case 3:
|
|
+ lirc_unregister_driver(dev->driver.minor);
|
|
+ case 2:
|
|
+ lirc_buffer_free(dev->driver.rbuf);
|
|
+ kfree(dev->driver.rbuf);
|
|
+ case 1:
|
|
+ kfree(dev);
|
|
+ case 0:
|
|
+ ;
|
|
+ }
|
|
+
|
|
+ return errno;
|
|
+}
|
|
+
|
|
+static int __devinit ite8709_pnp_probe(struct pnp_dev *dev,
|
|
+ const struct pnp_device_id *dev_id)
|
|
+{
|
|
+ struct lirc_driver *driver;
|
|
+ struct ite8709_device *ite8709_dev;
|
|
+ int ret;
|
|
+
|
|
+ /* Check resources validity */
|
|
+ if (!pnp_irq_valid(dev, 0))
|
|
+ return ite8709_cleanup(NULL, 0, -ENODEV, "invalid IRQ");
|
|
+ if (!pnp_port_valid(dev, 2))
|
|
+ return ite8709_cleanup(NULL, 0, -ENODEV, "invalid IO port");
|
|
+
|
|
+ /* Allocate memory for device struct */
|
|
+ ite8709_dev = kzalloc(sizeof(struct ite8709_device), GFP_KERNEL);
|
|
+ if (ite8709_dev == NULL)
|
|
+ return ite8709_cleanup(NULL, 0, -ENOMEM, "kzalloc failed");
|
|
+ pnp_set_drvdata(dev, ite8709_dev);
|
|
+
|
|
+ /* Initialize device struct */
|
|
+ ite8709_dev->use_count = 0;
|
|
+ ite8709_dev->irq = pnp_irq(dev, 0);
|
|
+ ite8709_dev->io = pnp_port_start(dev, 2);
|
|
+ ite8709_dev->hardware_lock = __SPIN_LOCK_UNLOCKED(
|
|
+ ite8709_dev->hardware_lock);
|
|
+ ite8709_dev->acc_pulse = 0;
|
|
+ ite8709_dev->acc_space = 0;
|
|
+ ite8709_dev->lastbit = 0;
|
|
+ do_gettimeofday(&ite8709_dev->last_tv);
|
|
+ tasklet_init(&ite8709_dev->tasklet, ite8709_rearm_irq,
|
|
+ (long) ite8709_dev);
|
|
+ ite8709_dev->force_rearm = 0;
|
|
+ ite8709_dev->rearmed = 0;
|
|
+ ite8709_dev->device_busy = 0;
|
|
+
|
|
+ /* Initialize driver struct */
|
|
+ driver = &ite8709_dev->driver;
|
|
+ strcpy(driver->name, LIRC_DRIVER_NAME);
|
|
+ driver->minor = -1;
|
|
+ driver->code_length = sizeof(int) * 8;
|
|
+ driver->sample_rate = 0;
|
|
+ driver->features = LIRC_CAN_REC_MODE2;
|
|
+ driver->data = ite8709_dev;
|
|
+ driver->add_to_buf = NULL;
|
|
+ driver->set_use_inc = ite8709_set_use_inc;
|
|
+ driver->set_use_dec = ite8709_set_use_dec;
|
|
+ driver->dev = &dev->dev;
|
|
+ driver->owner = THIS_MODULE;
|
|
+
|
|
+ /* Initialize LIRC buffer */
|
|
+ driver->rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
|
|
+ if (!driver->rbuf)
|
|
+ return ite8709_cleanup(ite8709_dev, 1, -ENOMEM,
|
|
+ "can't allocate lirc_buffer");
|
|
+ if (lirc_buffer_init(driver->rbuf, BUF_CHUNK_SIZE, BUF_SIZE))
|
|
+ return ite8709_cleanup(ite8709_dev, 1, -ENOMEM,
|
|
+ "lirc_buffer_init() failed");
|
|
+
|
|
+ /* Register LIRC driver */
|
|
+ ret = lirc_register_driver(driver);
|
|
+ if (ret < 0)
|
|
+ return ite8709_cleanup(ite8709_dev, 2, ret,
|
|
+ "lirc_register_driver() failed");
|
|
+
|
|
+ /* Reserve I/O port access */
|
|
+ if (!request_region(ite8709_dev->io, 2, LIRC_DRIVER_NAME))
|
|
+ return ite8709_cleanup(ite8709_dev, 3, -EBUSY,
|
|
+ "i/o port already in use");
|
|
+
|
|
+ /* Reserve IRQ line */
|
|
+ ret = request_irq(ite8709_dev->irq, ite8709_interrupt, 0,
|
|
+ LIRC_DRIVER_NAME, ite8709_dev);
|
|
+ if (ret < 0)
|
|
+ return ite8709_cleanup(ite8709_dev, 4, ret,
|
|
+ "IRQ already in use");
|
|
+
|
|
+ /* Initialize hardware */
|
|
+ ite8709_drop_hardware(ite8709_dev); /* Shutdown hw until first use */
|
|
+
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": device found : irq=%d io=0x%x\n",
|
|
+ ite8709_dev->irq, ite8709_dev->io);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __devexit ite8709_pnp_remove(struct pnp_dev *dev)
|
|
+{
|
|
+ struct ite8709_device *ite8709_dev;
|
|
+ ite8709_dev = pnp_get_drvdata(dev);
|
|
+
|
|
+ ite8709_cleanup(ite8709_dev, 6, 0, NULL);
|
|
+
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": device removed\n");
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int ite8709_pnp_suspend(struct pnp_dev *dev, pm_message_t state)
|
|
+{
|
|
+ struct ite8709_device *ite8709_dev;
|
|
+ ite8709_dev = pnp_get_drvdata(dev);
|
|
+
|
|
+ if (ite8709_dev->use_count > 0)
|
|
+ ite8709_drop_hardware(ite8709_dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ite8709_pnp_resume(struct pnp_dev *dev)
|
|
+{
|
|
+ struct ite8709_device *ite8709_dev;
|
|
+ ite8709_dev = pnp_get_drvdata(dev);
|
|
+
|
|
+ if (ite8709_dev->use_count > 0)
|
|
+ ite8709_init_hardware(ite8709_dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+#define ite8709_pnp_suspend NULL
|
|
+#define ite8709_pnp_resume NULL
|
|
+#endif
|
|
+
|
|
+static const struct pnp_device_id pnp_dev_table[] = {
|
|
+ {"ITE8709", 0},
|
|
+ {}
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(pnp, pnp_dev_table);
|
|
+
|
|
+static struct pnp_driver ite8709_pnp_driver = {
|
|
+ .name = LIRC_DRIVER_NAME,
|
|
+ .probe = ite8709_pnp_probe,
|
|
+ .remove = __devexit_p(ite8709_pnp_remove),
|
|
+ .suspend = ite8709_pnp_suspend,
|
|
+ .resume = ite8709_pnp_resume,
|
|
+ .id_table = pnp_dev_table,
|
|
+};
|
|
+
|
|
+int init_module(void)
|
|
+{
|
|
+ return pnp_register_driver(&ite8709_pnp_driver);
|
|
+}
|
|
+
|
|
+void cleanup_module(void)
|
|
+{
|
|
+ pnp_unregister_driver(&ite8709_pnp_driver);
|
|
+}
|
|
+
|
|
+MODULE_DESCRIPTION("LIRC driver for ITE8709 CIR port");
|
|
+MODULE_AUTHOR("Grégory Lardière");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
diff --git a/drivers/input/lirc/lirc_mceusb.c b/drivers/input/lirc/lirc_mceusb.c
|
|
new file mode 100644
|
|
index 0000000..8b404e2
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_mceusb.c
|
|
@@ -0,0 +1,1222 @@
|
|
+/*
|
|
+ * LIRC driver for Windows Media Center Edition USB Infrared Transceivers
|
|
+ *
|
|
+ * (C) by Martin A. Blatter <martin_a_blatter@yahoo.com>
|
|
+ *
|
|
+ * Transmitter support and reception code cleanup.
|
|
+ * (C) by Daniel Melander <lirc@rajidae.se>
|
|
+ *
|
|
+ * Original lirc_mceusb driver for 1st-gen device:
|
|
+ * Copyright (c) 2003-2004 Dan Conti <dconti@acm.wwu.edu>
|
|
+ *
|
|
+ * Original lirc_mceusb driver deprecated in favor of this driver, which
|
|
+ * supports the 1st-gen device now too. Transmit and receive support for
|
|
+ * the 1st-gen device added June-September 2009,
|
|
+ * by Jarod Wilson <jarod@wilsonet.com> and Patrick Calhoun <phineas@ou.edu>
|
|
+ *
|
|
+ * Derived from ATI USB driver by Paul Miller and the original
|
|
+ * MCE USB driver by Dan Conti (and now including chunks of the latter
|
|
+ * relevant to the 1st-gen device initialization)
|
|
+ *
|
|
+ *
|
|
+ * 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/kernel.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/kmod.h>
|
|
+#include <linux/smp_lock.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/usb.h>
|
|
+#include <linux/wait.h>
|
|
+#include <linux/time.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+#define DRIVER_VERSION "1.90"
|
|
+#define DRIVER_AUTHOR "Daniel Melander <lirc@rajidae.se>, " \
|
|
+ "Martin Blatter <martin_a_blatter@yahoo.com>, " \
|
|
+ "Dan Conti <dconti@acm.wwu.edu>"
|
|
+#define DRIVER_DESC "Windows Media Center Edition USB IR Transceiver " \
|
|
+ "driver for LIRC"
|
|
+#define DRIVER_NAME "lirc_mceusb"
|
|
+
|
|
+#define USB_BUFLEN 32 /* USB reception buffer length */
|
|
+#define LIRCBUF_SIZE 256 /* LIRC work buffer length */
|
|
+
|
|
+/* MCE constants */
|
|
+#define MCE_CMDBUF_SIZE 384 /* MCE Command buffer length */
|
|
+#define MCE_TIME_UNIT 50 /* Approx 50us resolution */
|
|
+#define MCE_CODE_LENGTH 5 /* Normal length of packet (with header) */
|
|
+#define MCE_PACKET_SIZE 4 /* Normal length of packet (without header) */
|
|
+#define MCE_PACKET_HEADER 0x84 /* Actual header format is 0x80 + num_bytes */
|
|
+#define MCE_CONTROL_HEADER 0x9F /* MCE status header */
|
|
+#define MCE_TX_HEADER_LENGTH 3 /* # of bytes in the initializing tx header */
|
|
+#define MCE_MAX_CHANNELS 2 /* Two transmitters, hardware dependent? */
|
|
+#define MCE_DEFAULT_TX_MASK 0x03 /* Val opts: TX1=0x01, TX2=0x02, ALL=0x03 */
|
|
+#define MCE_PULSE_BIT 0x80 /* Pulse bit, MSB set == PULSE else SPACE */
|
|
+#define MCE_PULSE_MASK 0x7F /* Pulse mask */
|
|
+#define MCE_MAX_PULSE_LENGTH 0x7F /* Longest transmittable pulse symbol */
|
|
+#define MCE_PACKET_LENGTH_MASK 0x7F /* Pulse mask */
|
|
+
|
|
+
|
|
+/* module parameters */
|
|
+#ifdef CONFIG_USB_DEBUG
|
|
+static int debug = 1;
|
|
+#else
|
|
+static int debug;
|
|
+#endif
|
|
+
|
|
+/* general constants */
|
|
+#define SEND_FLAG_IN_PROGRESS 1
|
|
+#define SEND_FLAG_COMPLETE 2
|
|
+#define RECV_FLAG_IN_PROGRESS 3
|
|
+#define RECV_FLAG_COMPLETE 4
|
|
+
|
|
+#define MCEUSB_INBOUND 1
|
|
+#define MCEUSB_OUTBOUND 2
|
|
+
|
|
+#define VENDOR_PHILIPS 0x0471
|
|
+#define VENDOR_SMK 0x0609
|
|
+#define VENDOR_TATUNG 0x1460
|
|
+#define VENDOR_GATEWAY 0x107b
|
|
+#define VENDOR_SHUTTLE 0x1308
|
|
+#define VENDOR_SHUTTLE2 0x051c
|
|
+#define VENDOR_MITSUMI 0x03ee
|
|
+#define VENDOR_TOPSEED 0x1784
|
|
+#define VENDOR_RICAVISION 0x179d
|
|
+#define VENDOR_ITRON 0x195d
|
|
+#define VENDOR_FIC 0x1509
|
|
+#define VENDOR_LG 0x043e
|
|
+#define VENDOR_MICROSOFT 0x045e
|
|
+#define VENDOR_FORMOSA 0x147a
|
|
+#define VENDOR_FINTEK 0x1934
|
|
+#define VENDOR_PINNACLE 0x2304
|
|
+#define VENDOR_ECS 0x1019
|
|
+#define VENDOR_WISTRON 0x0fb8
|
|
+#define VENDOR_COMPRO 0x185b
|
|
+#define VENDOR_NORTHSTAR 0x04eb
|
|
+#define VENDOR_REALTEK 0x0bda
|
|
+#define VENDOR_TIVO 0x105a
|
|
+
|
|
+static struct usb_device_id mceusb_dev_table[] = {
|
|
+ /* Original Microsoft MCE IR Transceiver (often HP-branded) */
|
|
+ { USB_DEVICE(VENDOR_MICROSOFT, 0x006d) },
|
|
+ /* Philips Infrared Transceiver - Sahara branded */
|
|
+ { USB_DEVICE(VENDOR_PHILIPS, 0x0608) },
|
|
+ /* Philips Infrared Transceiver - HP branded */
|
|
+ { USB_DEVICE(VENDOR_PHILIPS, 0x060c) },
|
|
+ /* Philips SRM5100 */
|
|
+ { USB_DEVICE(VENDOR_PHILIPS, 0x060d) },
|
|
+ /* Philips Infrared Transceiver - Omaura */
|
|
+ { USB_DEVICE(VENDOR_PHILIPS, 0x060f) },
|
|
+ /* Philips Infrared Transceiver - Spinel plus */
|
|
+ { USB_DEVICE(VENDOR_PHILIPS, 0x0613) },
|
|
+ /* Philips eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_PHILIPS, 0x0815) },
|
|
+ /* Realtek MCE IR Receiver */
|
|
+ { USB_DEVICE(VENDOR_REALTEK, 0x0161) },
|
|
+ /* SMK/Toshiba G83C0004D410 */
|
|
+ { USB_DEVICE(VENDOR_SMK, 0x031d) },
|
|
+ /* SMK eHome Infrared Transceiver (Sony VAIO) */
|
|
+ { USB_DEVICE(VENDOR_SMK, 0x0322) },
|
|
+ /* bundled with Hauppauge PVR-150 */
|
|
+ { USB_DEVICE(VENDOR_SMK, 0x0334) },
|
|
+ /* SMK eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_SMK, 0x0338) },
|
|
+ /* Tatung eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_TATUNG, 0x9150) },
|
|
+ /* Shuttle eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_SHUTTLE, 0xc001) },
|
|
+ /* Shuttle eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_SHUTTLE2, 0xc001) },
|
|
+ /* Gateway eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_GATEWAY, 0x3009) },
|
|
+ /* Mitsumi */
|
|
+ { USB_DEVICE(VENDOR_MITSUMI, 0x2501) },
|
|
+ /* Topseed eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0001) },
|
|
+ /* Topseed HP eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0006) },
|
|
+ /* Topseed eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0007) },
|
|
+ /* Topseed eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0008) },
|
|
+ /* Topseed eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x000a) },
|
|
+ /* Ricavision internal Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_RICAVISION, 0x0010) },
|
|
+ /* Itron ione Libra Q-11 */
|
|
+ { USB_DEVICE(VENDOR_ITRON, 0x7002) },
|
|
+ /* FIC eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_FIC, 0x9242) },
|
|
+ /* LG eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_LG, 0x9803) },
|
|
+ /* Microsoft MCE Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_MICROSOFT, 0x00a0) },
|
|
+ /* Formosa eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe015) },
|
|
+ /* Formosa21 / eHome Infrared Receiver */
|
|
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe016) },
|
|
+ /* Formosa aim / Trust MCE Infrared Receiver */
|
|
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe017) },
|
|
+ /* Formosa Industrial Computing / Beanbag Emulation Device */
|
|
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe018) },
|
|
+ /* Formosa21 / eHome Infrared Receiver */
|
|
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe03a) },
|
|
+ /* Formosa Industrial Computing AIM IR605/A */
|
|
+ { USB_DEVICE(VENDOR_FORMOSA, 0xe03c) },
|
|
+ /* Fintek eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_FINTEK, 0x0602) },
|
|
+ /* Fintek eHome Infrared Transceiver (in the AOpen MP45) */
|
|
+ { USB_DEVICE(VENDOR_FINTEK, 0x0702) },
|
|
+ /* Pinnacle Remote Kit */
|
|
+ { USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
|
|
+ /* Elitegroup Computer Systems IR */
|
|
+ { USB_DEVICE(VENDOR_ECS, 0x0f38) },
|
|
+ /* Wistron Corp. eHome Infrared Receiver */
|
|
+ { USB_DEVICE(VENDOR_WISTRON, 0x0002) },
|
|
+ /* Compro K100 */
|
|
+ { USB_DEVICE(VENDOR_COMPRO, 0x3020) },
|
|
+ /* Compro K100 v2 */
|
|
+ { USB_DEVICE(VENDOR_COMPRO, 0x3082) },
|
|
+ /* Northstar Systems, Inc. eHome Infrared Transceiver */
|
|
+ { USB_DEVICE(VENDOR_NORTHSTAR, 0xe004) },
|
|
+ /* TiVo PC IR Receiver */
|
|
+ { USB_DEVICE(VENDOR_TIVO, 0x2000) },
|
|
+ /* Terminating entry */
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct usb_device_id pinnacle_list[] = {
|
|
+ { USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+static struct usb_device_id microsoft_gen1_list[] = {
|
|
+ { USB_DEVICE(VENDOR_MICROSOFT, 0x006d) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+static struct usb_device_id transmitter_mask_list[] = {
|
|
+ { USB_DEVICE(VENDOR_MICROSOFT, 0x006d) },
|
|
+ { USB_DEVICE(VENDOR_PHILIPS, 0x060c) },
|
|
+ { USB_DEVICE(VENDOR_SMK, 0x031d) },
|
|
+ { USB_DEVICE(VENDOR_SMK, 0x0322) },
|
|
+ { USB_DEVICE(VENDOR_SMK, 0x0334) },
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0001) },
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0006) },
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0007) },
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x0008) },
|
|
+ { USB_DEVICE(VENDOR_TOPSEED, 0x000a) },
|
|
+ { USB_DEVICE(VENDOR_PINNACLE, 0x0225) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* data structure for each usb transceiver */
|
|
+struct mceusb_dev {
|
|
+
|
|
+ /* usb */
|
|
+ struct usb_device *usbdev;
|
|
+ struct urb *urb_in;
|
|
+ int devnum;
|
|
+ struct usb_endpoint_descriptor *usb_ep_in;
|
|
+ struct usb_endpoint_descriptor *usb_ep_out;
|
|
+
|
|
+ /* buffers and dma */
|
|
+ unsigned char *buf_in;
|
|
+ unsigned int len_in;
|
|
+ dma_addr_t dma_in;
|
|
+ dma_addr_t dma_out;
|
|
+ unsigned int overflow_len;
|
|
+
|
|
+ /* lirc */
|
|
+ struct lirc_driver *d;
|
|
+ int lircdata;
|
|
+ unsigned char is_pulse;
|
|
+ struct {
|
|
+ u32 connected:1;
|
|
+ u32 pinnacle:1;
|
|
+ u32 transmitter_mask_inverted:1;
|
|
+ u32 microsoft_gen1:1;
|
|
+ u32 reserved:28;
|
|
+ } flags;
|
|
+
|
|
+ unsigned char transmitter_mask;
|
|
+ unsigned int carrier_freq;
|
|
+
|
|
+ /* handle sending (init strings) */
|
|
+ int send_flags;
|
|
+ wait_queue_head_t wait_out;
|
|
+
|
|
+ struct mutex dev_lock;
|
|
+};
|
|
+
|
|
+/* init strings */
|
|
+static char init1[] = {0x00, 0xff, 0xaa, 0xff, 0x0b};
|
|
+static char init2[] = {0xff, 0x18};
|
|
+
|
|
+static char pin_init1[] = { 0x9f, 0x07};
|
|
+static char pin_init2[] = { 0x9f, 0x13};
|
|
+static char pin_init3[] = { 0x9f, 0x0d};
|
|
+
|
|
+static void mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, int len)
|
|
+{
|
|
+ char codes[USB_BUFLEN * 3 + 1];
|
|
+ int i;
|
|
+
|
|
+ if (len <= 0)
|
|
+ return;
|
|
+
|
|
+ if (ir->flags.microsoft_gen1 && len <= 2)
|
|
+ return;
|
|
+
|
|
+ for (i = 0; i < len && i < USB_BUFLEN; i++)
|
|
+ snprintf(codes + i * 3, 4, "%02x ", buf[i] & 0xFF);
|
|
+
|
|
+ dev_info(ir->d->dev, "data received %s (length=%d)\n", codes, len);
|
|
+}
|
|
+
|
|
+static void usb_async_callback(struct urb *urb, struct pt_regs *regs)
|
|
+{
|
|
+ struct mceusb_dev *ir;
|
|
+ int len;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+
|
|
+ ir = urb->context;
|
|
+ if (ir) {
|
|
+ len = urb->actual_length;
|
|
+
|
|
+ dev_dbg(ir->d->dev, "callback called (status=%d len=%d)\n",
|
|
+ urb->status, len);
|
|
+
|
|
+ if (debug)
|
|
+ mceusb_dev_printdata(ir, urb->transfer_buffer, len);
|
|
+ }
|
|
+
|
|
+}
|
|
+
|
|
+/* request incoming or send outgoing usb packet - used to initialize remote */
|
|
+static void request_packet_async(struct mceusb_dev *ir,
|
|
+ struct usb_endpoint_descriptor *ep,
|
|
+ unsigned char *data, int size, int urb_type)
|
|
+{
|
|
+ int res;
|
|
+ struct urb *async_urb;
|
|
+ unsigned char *async_buf;
|
|
+
|
|
+ if (urb_type) {
|
|
+ async_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (unlikely(!async_urb))
|
|
+ return;
|
|
+
|
|
+ async_buf = kzalloc(size, GFP_KERNEL);
|
|
+ if (!async_buf) {
|
|
+ usb_free_urb(async_urb);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (urb_type == MCEUSB_OUTBOUND) {
|
|
+ /* outbound data */
|
|
+ usb_fill_int_urb(async_urb, ir->usbdev,
|
|
+ usb_sndintpipe(ir->usbdev,
|
|
+ ep->bEndpointAddress),
|
|
+ async_buf, size,
|
|
+ (usb_complete_t) usb_async_callback,
|
|
+ ir, ep->bInterval);
|
|
+ memcpy(async_buf, data, size);
|
|
+ } else {
|
|
+ /* inbound data */
|
|
+ usb_fill_int_urb(async_urb, ir->usbdev,
|
|
+ usb_rcvintpipe(ir->usbdev,
|
|
+ ep->bEndpointAddress),
|
|
+ async_buf, size,
|
|
+ (usb_complete_t) usb_async_callback,
|
|
+ ir, ep->bInterval);
|
|
+ }
|
|
+
|
|
+ } else {
|
|
+ /* standard request */
|
|
+ async_urb = ir->urb_in;
|
|
+ ir->send_flags = RECV_FLAG_IN_PROGRESS;
|
|
+ }
|
|
+
|
|
+ dev_dbg(ir->d->dev, "receive request called (size=%#x)\n", size);
|
|
+
|
|
+ async_urb->transfer_buffer_length = size;
|
|
+ async_urb->dev = ir->usbdev;
|
|
+
|
|
+ res = usb_submit_urb(async_urb, GFP_ATOMIC);
|
|
+ if (res) {
|
|
+ dev_dbg(ir->d->dev, "receive request FAILED! (res=%d)\n", res);
|
|
+ return;
|
|
+ }
|
|
+ dev_dbg(ir->d->dev, "receive request complete (res=%d)\n", res);
|
|
+}
|
|
+
|
|
+static int unregister_from_lirc(struct mceusb_dev *ir)
|
|
+{
|
|
+ struct lirc_driver *d = ir->d;
|
|
+ int devnum;
|
|
+ int rtn;
|
|
+
|
|
+ devnum = ir->devnum;
|
|
+ dev_dbg(ir->d->dev, "unregister from lirc called\n");
|
|
+
|
|
+ rtn = lirc_unregister_driver(d->minor);
|
|
+ if (rtn > 0) {
|
|
+ dev_info(ir->d->dev, "error in lirc_unregister minor: %d\n"
|
|
+ "Trying again...\n", d->minor);
|
|
+ if (rtn == -EBUSY) {
|
|
+ dev_info(ir->d->dev, "device is opened, will "
|
|
+ "unregister on close\n");
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ schedule_timeout(HZ);
|
|
+
|
|
+ rtn = lirc_unregister_driver(d->minor);
|
|
+ if (rtn > 0)
|
|
+ dev_info(ir->d->dev, "lirc_unregister failed\n");
|
|
+ }
|
|
+
|
|
+ if (rtn) {
|
|
+ dev_info(ir->d->dev, "didn't free resources\n");
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+
|
|
+ dev_info(ir->d->dev, "usb remote disconnected\n");
|
|
+
|
|
+ lirc_buffer_free(d->rbuf);
|
|
+ kfree(d->rbuf);
|
|
+ kfree(d);
|
|
+ kfree(ir);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mceusb_ir_open(void *data)
|
|
+{
|
|
+ struct mceusb_dev *ir = data;
|
|
+
|
|
+ if (!ir) {
|
|
+ printk(KERN_WARNING DRIVER_NAME
|
|
+ "[?]: %s called with no context\n", __func__);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ dev_dbg(ir->d->dev, "mceusb IR device opened\n");
|
|
+
|
|
+ if (!ir->flags.connected) {
|
|
+ if (!ir->usbdev)
|
|
+ return -ENOENT;
|
|
+ ir->flags.connected = 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void mceusb_ir_close(void *data)
|
|
+{
|
|
+ struct mceusb_dev *ir = data;
|
|
+
|
|
+ if (!ir) {
|
|
+ printk(KERN_WARNING DRIVER_NAME
|
|
+ "[?]: %s called with no context\n", __func__);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ dev_dbg(ir->d->dev, "mceusb IR device closed\n");
|
|
+
|
|
+ if (ir->flags.connected) {
|
|
+ mutex_lock(&ir->dev_lock);
|
|
+ ir->flags.connected = 0;
|
|
+ mutex_unlock(&ir->dev_lock);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void send_packet_to_lirc(struct mceusb_dev *ir)
|
|
+{
|
|
+ if (ir->lircdata) {
|
|
+ lirc_buffer_write(ir->d->rbuf,
|
|
+ (unsigned char *) &ir->lircdata);
|
|
+ wake_up(&ir->d->rbuf->wait_poll);
|
|
+ ir->lircdata = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len)
|
|
+{
|
|
+ int i, j;
|
|
+ int packet_len = 0;
|
|
+ int start_index = 0;
|
|
+
|
|
+ /* skip meaningless 0xb1 0x60 header bytes on orig receiver */
|
|
+ if (ir->flags.microsoft_gen1)
|
|
+ start_index = 2;
|
|
+
|
|
+ /* this should only trigger w/the 1st-gen mce receiver */
|
|
+ for (i = start_index; i < (start_index + ir->overflow_len) &&
|
|
+ i < buf_len; i++) {
|
|
+ /* rising/falling flank */
|
|
+ if (ir->is_pulse != (ir->buf_in[i] & MCE_PULSE_BIT)) {
|
|
+ send_packet_to_lirc(ir);
|
|
+ ir->is_pulse = ir->buf_in[i] & MCE_PULSE_BIT;
|
|
+ }
|
|
+
|
|
+ /* accumulate mce pulse/space values */
|
|
+ ir->lircdata += (ir->buf_in[i] & MCE_PULSE_MASK) *
|
|
+ MCE_TIME_UNIT;
|
|
+ ir->lircdata |= (ir->is_pulse ? PULSE_BIT : 0);
|
|
+ }
|
|
+ start_index += ir->overflow_len;
|
|
+ ir->overflow_len = 0;
|
|
+
|
|
+ for (i = start_index; i < buf_len; i++) {
|
|
+ /* decode mce packets of the form (84),AA,BB,CC,DD */
|
|
+
|
|
+ /* data headers */
|
|
+ if (ir->buf_in[i] >= 0x80 && ir->buf_in[i] <= 0x9e) {
|
|
+ /* decode packet data */
|
|
+ packet_len = ir->buf_in[i] & MCE_PACKET_LENGTH_MASK;
|
|
+ ir->overflow_len = i + 1 + packet_len - buf_len;
|
|
+ for (j = 1; j <= packet_len && (i + j < buf_len); j++) {
|
|
+ /* rising/falling flank */
|
|
+ if (ir->is_pulse !=
|
|
+ (ir->buf_in[i + j] & MCE_PULSE_BIT)) {
|
|
+ send_packet_to_lirc(ir);
|
|
+ ir->is_pulse =
|
|
+ ir->buf_in[i + j] &
|
|
+ MCE_PULSE_BIT;
|
|
+ }
|
|
+
|
|
+ /* accumulate mce pulse/space values */
|
|
+ ir->lircdata +=
|
|
+ (ir->buf_in[i + j] & MCE_PULSE_MASK) *
|
|
+ MCE_TIME_UNIT;
|
|
+ ir->lircdata |= (ir->is_pulse ? PULSE_BIT : 0);
|
|
+ }
|
|
+
|
|
+ i += packet_len;
|
|
+
|
|
+ /* status header (0x9F) */
|
|
+ } else if (ir->buf_in[i] == MCE_CONTROL_HEADER) {
|
|
+ /*
|
|
+ * A transmission containing one or more consecutive ir
|
|
+ * commands always ends with a GAP of 100ms followed by
|
|
+ * the sequence 0x9F 0x01 0x01 0x9F 0x15 0x00 0x00 0x80
|
|
+ */
|
|
+
|
|
+#if 0
|
|
+ Uncomment this if the last 100ms "infinity"-space should be transmitted
|
|
+ to lirc directly instead of at the beginning of the next transmission.
|
|
+ Changes pulse/space order.
|
|
+
|
|
+ if (++i < buf_len && ir->buf_in[i] == 0x01)
|
|
+ send_packet_to_lirc(ir);
|
|
+
|
|
+#endif
|
|
+
|
|
+ /* end decode loop */
|
|
+ dev_dbg(ir->d->dev, "[%d] %s: found control header\n",
|
|
+ ir->devnum, __func__);
|
|
+ ir->overflow_len = 0;
|
|
+ break;
|
|
+ } else {
|
|
+ dev_dbg(ir->d->dev, "[%d] %s: stray packet?\n",
|
|
+ ir->devnum, __func__);
|
|
+ ir->overflow_len = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+static void mceusb_dev_recv(struct urb *urb, struct pt_regs *regs)
|
|
+{
|
|
+ struct mceusb_dev *ir;
|
|
+ int buf_len;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+
|
|
+ ir = urb->context;
|
|
+ if (!ir) {
|
|
+ usb_unlink_urb(urb);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ buf_len = urb->actual_length;
|
|
+
|
|
+ if (debug)
|
|
+ mceusb_dev_printdata(ir, urb->transfer_buffer, buf_len);
|
|
+
|
|
+ if (ir->send_flags == RECV_FLAG_IN_PROGRESS) {
|
|
+ ir->send_flags = SEND_FLAG_COMPLETE;
|
|
+ dev_dbg(ir->d->dev, "setup answer received %d bytes\n",
|
|
+ buf_len);
|
|
+ }
|
|
+
|
|
+ switch (urb->status) {
|
|
+ /* success */
|
|
+ case 0:
|
|
+ mceusb_process_ir_data(ir, buf_len);
|
|
+ break;
|
|
+
|
|
+ case -ECONNRESET:
|
|
+ case -ENOENT:
|
|
+ case -ESHUTDOWN:
|
|
+ usb_unlink_urb(urb);
|
|
+ return;
|
|
+
|
|
+ case -EPIPE:
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ usb_submit_urb(urb, GFP_ATOMIC);
|
|
+}
|
|
+
|
|
+
|
|
+static ssize_t mceusb_transmit_ir(struct file *file, const char *buf,
|
|
+ size_t n, loff_t *ppos)
|
|
+{
|
|
+ int i, count = 0, cmdcount = 0;
|
|
+ struct mceusb_dev *ir = NULL;
|
|
+ int wbuf[LIRCBUF_SIZE]; /* Workbuffer with values from lirc */
|
|
+ unsigned char cmdbuf[MCE_CMDBUF_SIZE]; /* MCE command buffer */
|
|
+ unsigned long signal_duration = 0; /* Singnal length in us */
|
|
+ struct timeval start_time, end_time;
|
|
+
|
|
+ do_gettimeofday(&start_time);
|
|
+
|
|
+ /* Retrieve lirc_driver data for the device */
|
|
+ ir = lirc_get_pdata(file);
|
|
+ if (!ir || !ir->usb_ep_out)
|
|
+ return -EFAULT;
|
|
+
|
|
+ if (n % sizeof(int))
|
|
+ return -EINVAL;
|
|
+ count = n / sizeof(int);
|
|
+
|
|
+ /* Check if command is within limits */
|
|
+ if (count > LIRCBUF_SIZE || count%2 == 0)
|
|
+ return -EINVAL;
|
|
+ if (copy_from_user(wbuf, buf, n))
|
|
+ return -EFAULT;
|
|
+
|
|
+ /* MCE tx init header */
|
|
+ cmdbuf[cmdcount++] = MCE_CONTROL_HEADER;
|
|
+ cmdbuf[cmdcount++] = 0x08;
|
|
+ cmdbuf[cmdcount++] = ir->transmitter_mask;
|
|
+
|
|
+ /* Generate mce packet data */
|
|
+ for (i = 0; (i < count) && (cmdcount < MCE_CMDBUF_SIZE); i++) {
|
|
+ signal_duration += wbuf[i];
|
|
+ wbuf[i] = wbuf[i] / MCE_TIME_UNIT;
|
|
+
|
|
+ do { /* loop to support long pulses/spaces > 127*50us=6.35ms */
|
|
+
|
|
+ /* Insert mce packet header every 4th entry */
|
|
+ if ((cmdcount < MCE_CMDBUF_SIZE) &&
|
|
+ (cmdcount - MCE_TX_HEADER_LENGTH) %
|
|
+ MCE_CODE_LENGTH == 0)
|
|
+ cmdbuf[cmdcount++] = MCE_PACKET_HEADER;
|
|
+
|
|
+ /* Insert mce packet data */
|
|
+ if (cmdcount < MCE_CMDBUF_SIZE)
|
|
+ cmdbuf[cmdcount++] =
|
|
+ (wbuf[i] < MCE_PULSE_BIT ?
|
|
+ wbuf[i] : MCE_MAX_PULSE_LENGTH) |
|
|
+ (i & 1 ? 0x00 : MCE_PULSE_BIT);
|
|
+ else
|
|
+ return -EINVAL;
|
|
+ } while ((wbuf[i] > MCE_MAX_PULSE_LENGTH) &&
|
|
+ (wbuf[i] -= MCE_MAX_PULSE_LENGTH));
|
|
+ }
|
|
+
|
|
+ /* Fix packet length in last header */
|
|
+ cmdbuf[cmdcount - (cmdcount - MCE_TX_HEADER_LENGTH) % MCE_CODE_LENGTH] =
|
|
+ 0x80 + (cmdcount - MCE_TX_HEADER_LENGTH) % MCE_CODE_LENGTH - 1;
|
|
+
|
|
+ /* Check if we have room for the empty packet at the end */
|
|
+ if (cmdcount >= MCE_CMDBUF_SIZE)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* All mce commands end with an empty packet (0x80) */
|
|
+ cmdbuf[cmdcount++] = 0x80;
|
|
+
|
|
+ /* Transmit the command to the mce device */
|
|
+ request_packet_async(ir, ir->usb_ep_out, cmdbuf,
|
|
+ cmdcount, MCEUSB_OUTBOUND);
|
|
+
|
|
+ /*
|
|
+ * The lircd gap calculation expects the write function to
|
|
+ * wait the time it takes for the ircommand to be sent before
|
|
+ * it returns.
|
|
+ */
|
|
+ do_gettimeofday(&end_time);
|
|
+ signal_duration -= (end_time.tv_usec - start_time.tv_usec) +
|
|
+ (end_time.tv_sec - start_time.tv_sec) * 1000000;
|
|
+
|
|
+ /* delay with the closest number of ticks */
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ schedule_timeout(usecs_to_jiffies(signal_duration));
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static void set_transmitter_mask(struct mceusb_dev *ir, unsigned int mask)
|
|
+{
|
|
+ if (ir->flags.transmitter_mask_inverted)
|
|
+ ir->transmitter_mask = (mask != 0x03 ? mask ^ 0x03 : mask) << 1;
|
|
+ else
|
|
+ ir->transmitter_mask = mask;
|
|
+}
|
|
+
|
|
+
|
|
+/* Sets the send carrier frequency */
|
|
+static int set_send_carrier(struct mceusb_dev *ir, int carrier)
|
|
+{
|
|
+ int clk = 10000000;
|
|
+ int prescaler = 0, divisor = 0;
|
|
+ unsigned char cmdbuf[] = { 0x9F, 0x06, 0x01, 0x80 };
|
|
+
|
|
+ /* Carrier is changed */
|
|
+ if (ir->carrier_freq != carrier) {
|
|
+
|
|
+ if (carrier <= 0) {
|
|
+ ir->carrier_freq = carrier;
|
|
+ dev_dbg(ir->d->dev, "SET_CARRIER disabling carrier "
|
|
+ "modulation\n");
|
|
+ request_packet_async(ir, ir->usb_ep_out,
|
|
+ cmdbuf, sizeof(cmdbuf),
|
|
+ MCEUSB_OUTBOUND);
|
|
+ return carrier;
|
|
+ }
|
|
+
|
|
+ for (prescaler = 0; prescaler < 4; ++prescaler) {
|
|
+ divisor = (clk >> (2 * prescaler)) / carrier;
|
|
+ if (divisor <= 0xFF) {
|
|
+ ir->carrier_freq = carrier;
|
|
+ cmdbuf[2] = prescaler;
|
|
+ cmdbuf[3] = divisor;
|
|
+ dev_dbg(ir->d->dev, "SET_CARRIER requesting "
|
|
+ "%d Hz\n", carrier);
|
|
+
|
|
+ /* Transmit new carrier to mce device */
|
|
+ request_packet_async(ir, ir->usb_ep_out,
|
|
+ cmdbuf, sizeof(cmdbuf),
|
|
+ MCEUSB_OUTBOUND);
|
|
+ return carrier;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return -EINVAL;
|
|
+
|
|
+ }
|
|
+
|
|
+ return carrier;
|
|
+}
|
|
+
|
|
+
|
|
+static int mceusb_lirc_ioctl(struct inode *node, struct file *filep,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ int result;
|
|
+ unsigned int ivalue;
|
|
+ unsigned long lvalue;
|
|
+ struct mceusb_dev *ir = NULL;
|
|
+
|
|
+ /* Retrieve lirc_driver data for the device */
|
|
+ ir = lirc_get_pdata(filep);
|
|
+ if (!ir || !ir->usb_ep_out)
|
|
+ return -EFAULT;
|
|
+
|
|
+
|
|
+ switch (cmd) {
|
|
+ case LIRC_SET_TRANSMITTER_MASK:
|
|
+
|
|
+ result = get_user(ivalue, (unsigned int *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ switch (ivalue) {
|
|
+ case 0x01: /* Transmitter 1 => 0x04 */
|
|
+ case 0x02: /* Transmitter 2 => 0x02 */
|
|
+ case 0x03: /* Transmitter 1 & 2 => 0x06 */
|
|
+ set_transmitter_mask(ir, ivalue);
|
|
+ break;
|
|
+
|
|
+ default: /* Unsupported transmitter mask */
|
|
+ return MCE_MAX_CHANNELS;
|
|
+ }
|
|
+
|
|
+ dev_dbg(ir->d->dev, ": SET_TRANSMITTERS mask=%d\n", ivalue);
|
|
+ break;
|
|
+
|
|
+ case LIRC_GET_SEND_MODE:
|
|
+
|
|
+ result = put_user(LIRC_SEND2MODE(LIRC_CAN_SEND_PULSE &
|
|
+ LIRC_CAN_SEND_MASK),
|
|
+ (unsigned long *) arg);
|
|
+
|
|
+ if (result)
|
|
+ return result;
|
|
+ break;
|
|
+
|
|
+ case LIRC_SET_SEND_MODE:
|
|
+
|
|
+ result = get_user(lvalue, (unsigned long *) arg);
|
|
+
|
|
+ if (result)
|
|
+ return result;
|
|
+ if (lvalue != (LIRC_MODE_PULSE&LIRC_CAN_SEND_MASK))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case LIRC_SET_SEND_CARRIER:
|
|
+
|
|
+ result = get_user(ivalue, (unsigned int *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+
|
|
+ set_send_carrier(ir, ivalue);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ return lirc_dev_fop_ioctl(node, filep, cmd, arg);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct file_operations lirc_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .write = mceusb_transmit_ir,
|
|
+ .ioctl = mceusb_lirc_ioctl,
|
|
+ .read = lirc_dev_fop_read,
|
|
+ .poll = lirc_dev_fop_poll,
|
|
+ .open = lirc_dev_fop_open,
|
|
+ .release = lirc_dev_fop_close,
|
|
+};
|
|
+
|
|
+static int mceusb_gen1_init(struct mceusb_dev *ir)
|
|
+{
|
|
+ int i, ret;
|
|
+ char junk[64], data[8];
|
|
+ int partial = 0;
|
|
+
|
|
+ /*
|
|
+ * Clear off the first few messages. These look like calibration
|
|
+ * or test data, I can't really tell. This also flushes in case
|
|
+ * we have random ir data queued up.
|
|
+ */
|
|
+ for (i = 0; i < 40; i++)
|
|
+ usb_bulk_msg(ir->usbdev,
|
|
+ usb_rcvbulkpipe(ir->usbdev,
|
|
+ ir->usb_ep_in->bEndpointAddress),
|
|
+ junk, 64, &partial, HZ * 10);
|
|
+
|
|
+ ir->is_pulse = 1;
|
|
+
|
|
+ memset(data, 0, 8);
|
|
+
|
|
+ /* Get Status */
|
|
+ ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
|
|
+ USB_REQ_GET_STATUS, USB_DIR_IN,
|
|
+ 0, 0, data, 2, HZ * 3);
|
|
+
|
|
+ /* ret = usb_get_status( ir->usbdev, 0, 0, data ); */
|
|
+ dev_dbg(ir->d->dev, "%s - ret = %d status = 0x%x 0x%x\n", __func__,
|
|
+ ret, data[0], data[1]);
|
|
+
|
|
+ /*
|
|
+ * This is a strange one. They issue a set address to the device
|
|
+ * on the receive control pipe and expect a certain value pair back
|
|
+ */
|
|
+ memset(data, 0, 8);
|
|
+
|
|
+ ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
|
|
+ USB_REQ_SET_ADDRESS, USB_TYPE_VENDOR, 0, 0,
|
|
+ data, 2, HZ * 3);
|
|
+ dev_dbg(ir->d->dev, "%s - ret = %d, devnum = %d\n",
|
|
+ __func__, ret, ir->usbdev->devnum);
|
|
+ dev_dbg(ir->d->dev, "%s - data[0] = %d, data[1] = %d\n",
|
|
+ __func__, data[0], data[1]);
|
|
+
|
|
+ /* set feature: bit rate 38400 bps */
|
|
+ ret = usb_control_msg(ir->usbdev, usb_sndctrlpipe(ir->usbdev, 0),
|
|
+ USB_REQ_SET_FEATURE, USB_TYPE_VENDOR,
|
|
+ 0xc04e, 0x0000, NULL, 0, HZ * 3);
|
|
+
|
|
+ dev_dbg(ir->d->dev, "%s - ret = %d\n", __func__, ret);
|
|
+
|
|
+ /* bRequest 4: set char length to 8 bits */
|
|
+ ret = usb_control_msg(ir->usbdev, usb_sndctrlpipe(ir->usbdev, 0),
|
|
+ 4, USB_TYPE_VENDOR,
|
|
+ 0x0808, 0x0000, NULL, 0, HZ * 3);
|
|
+ dev_dbg(ir->d->dev, "%s - retB = %d\n", __func__, ret);
|
|
+
|
|
+ /* bRequest 2: set handshaking to use DTR/DSR */
|
|
+ ret = usb_control_msg(ir->usbdev, usb_sndctrlpipe(ir->usbdev, 0),
|
|
+ 2, USB_TYPE_VENDOR,
|
|
+ 0x0000, 0x0100, NULL, 0, HZ * 3);
|
|
+ dev_dbg(ir->d->dev, "%s - retC = %d\n", __func__, ret);
|
|
+
|
|
+ return ret;
|
|
+
|
|
+};
|
|
+
|
|
+static int mceusb_dev_probe(struct usb_interface *intf,
|
|
+ const struct usb_device_id *id)
|
|
+{
|
|
+ struct usb_device *dev = interface_to_usbdev(intf);
|
|
+ struct usb_host_interface *idesc;
|
|
+ struct usb_endpoint_descriptor *ep = NULL;
|
|
+ struct usb_endpoint_descriptor *ep_in = NULL;
|
|
+ struct usb_endpoint_descriptor *ep_out = NULL;
|
|
+ struct usb_host_config *config;
|
|
+ struct mceusb_dev *ir = NULL;
|
|
+ struct lirc_driver *driver = NULL;
|
|
+ struct lirc_buffer *rbuf = NULL;
|
|
+ int devnum, pipe, maxp;
|
|
+ int minor = 0;
|
|
+ int i;
|
|
+ char buf[63], name[128] = "";
|
|
+ int mem_failure = 0;
|
|
+ int is_pinnacle;
|
|
+ int is_microsoft_gen1;
|
|
+
|
|
+ dev_dbg(&intf->dev, ": %s called\n", __func__);
|
|
+
|
|
+ usb_reset_device(dev);
|
|
+
|
|
+ config = dev->actconfig;
|
|
+
|
|
+ idesc = intf->cur_altsetting;
|
|
+
|
|
+ is_pinnacle = usb_match_id(intf, pinnacle_list) ? 1 : 0;
|
|
+
|
|
+ is_microsoft_gen1 = usb_match_id(intf, microsoft_gen1_list) ? 1 : 0;
|
|
+
|
|
+ /* step through the endpoints to find first bulk in and out endpoint */
|
|
+ for (i = 0; i < idesc->desc.bNumEndpoints; ++i) {
|
|
+ ep = &idesc->endpoint[i].desc;
|
|
+
|
|
+ if ((ep_in == NULL)
|
|
+ && ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
|
|
+ == USB_DIR_IN)
|
|
+ && (((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
|
|
+ == USB_ENDPOINT_XFER_BULK)
|
|
+ || ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
|
|
+ == USB_ENDPOINT_XFER_INT))) {
|
|
+
|
|
+ dev_dbg(&intf->dev, ": acceptable inbound endpoint "
|
|
+ "found\n");
|
|
+ ep_in = ep;
|
|
+ ep_in->bmAttributes = USB_ENDPOINT_XFER_INT;
|
|
+ if (is_pinnacle)
|
|
+ /*
|
|
+ * setting seems to 1 seem to cause issues with
|
|
+ * Pinnacle timing out on transfer.
|
|
+ */
|
|
+ ep_in->bInterval = ep->bInterval;
|
|
+ else
|
|
+ ep_in->bInterval = 1;
|
|
+ }
|
|
+
|
|
+ if ((ep_out == NULL)
|
|
+ && ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
|
|
+ == USB_DIR_OUT)
|
|
+ && (((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
|
|
+ == USB_ENDPOINT_XFER_BULK)
|
|
+ || ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
|
|
+ == USB_ENDPOINT_XFER_INT))) {
|
|
+
|
|
+ dev_dbg(&intf->dev, ": acceptable outbound endpoint "
|
|
+ "found\n");
|
|
+ ep_out = ep;
|
|
+ ep_out->bmAttributes = USB_ENDPOINT_XFER_INT;
|
|
+ if (is_pinnacle)
|
|
+ /*
|
|
+ * setting seems to 1 seem to cause issues with
|
|
+ * Pinnacle timing out on transfer.
|
|
+ */
|
|
+ ep_out->bInterval = ep->bInterval;
|
|
+ else
|
|
+ ep_out->bInterval = 1;
|
|
+ }
|
|
+ }
|
|
+ if (ep_in == NULL) {
|
|
+ dev_dbg(&intf->dev, ": inbound and/or endpoint not found\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ devnum = dev->devnum;
|
|
+ pipe = usb_rcvintpipe(dev, ep_in->bEndpointAddress);
|
|
+ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
|
|
+
|
|
+ mem_failure = 0;
|
|
+ ir = kzalloc(sizeof(struct mceusb_dev), GFP_KERNEL);
|
|
+ if (!ir)
|
|
+ goto mem_alloc_fail;
|
|
+
|
|
+ driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
|
|
+ if (!driver)
|
|
+ goto mem_alloc_fail;
|
|
+
|
|
+ rbuf = kzalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
|
|
+ if (!rbuf)
|
|
+ goto mem_alloc_fail;
|
|
+
|
|
+ if (lirc_buffer_init(rbuf, sizeof(int), LIRCBUF_SIZE))
|
|
+ goto mem_alloc_fail;
|
|
+
|
|
+ ir->buf_in = usb_buffer_alloc(dev, maxp, GFP_ATOMIC, &ir->dma_in);
|
|
+ if (!ir->buf_in)
|
|
+ goto buf_in_alloc_fail;
|
|
+
|
|
+ ir->urb_in = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!ir->urb_in)
|
|
+ goto urb_in_alloc_fail;
|
|
+
|
|
+ strcpy(driver->name, DRIVER_NAME);
|
|
+ driver->minor = -1;
|
|
+ driver->features = LIRC_CAN_SEND_PULSE |
|
|
+ LIRC_CAN_SET_TRANSMITTER_MASK |
|
|
+ LIRC_CAN_REC_MODE2 |
|
|
+ LIRC_CAN_SET_SEND_CARRIER;
|
|
+ driver->data = ir;
|
|
+ driver->rbuf = rbuf;
|
|
+ driver->set_use_inc = &mceusb_ir_open;
|
|
+ driver->set_use_dec = &mceusb_ir_close;
|
|
+ driver->code_length = sizeof(int) * 8;
|
|
+ driver->fops = &lirc_fops;
|
|
+ driver->dev = &intf->dev;
|
|
+ driver->owner = THIS_MODULE;
|
|
+
|
|
+ mutex_init(&ir->dev_lock);
|
|
+ init_waitqueue_head(&ir->wait_out);
|
|
+
|
|
+ minor = lirc_register_driver(driver);
|
|
+ if (minor < 0)
|
|
+ goto lirc_register_fail;
|
|
+
|
|
+ driver->minor = minor;
|
|
+ ir->d = driver;
|
|
+ ir->devnum = devnum;
|
|
+ ir->usbdev = dev;
|
|
+ ir->len_in = maxp;
|
|
+ ir->overflow_len = 0;
|
|
+ ir->flags.connected = 0;
|
|
+ ir->flags.pinnacle = is_pinnacle;
|
|
+ ir->flags.microsoft_gen1 = is_microsoft_gen1;
|
|
+ ir->flags.transmitter_mask_inverted =
|
|
+ usb_match_id(intf, transmitter_mask_list) ? 0 : 1;
|
|
+
|
|
+ ir->lircdata = PULSE_MASK;
|
|
+ ir->is_pulse = 0;
|
|
+
|
|
+ /* ir->flags.transmitter_mask_inverted must be set */
|
|
+ set_transmitter_mask(ir, MCE_DEFAULT_TX_MASK);
|
|
+ /* Saving usb interface data for use by the transmitter routine */
|
|
+ ir->usb_ep_in = ep_in;
|
|
+ ir->usb_ep_out = ep_out;
|
|
+
|
|
+ if (dev->descriptor.iManufacturer
|
|
+ && usb_string(dev, dev->descriptor.iManufacturer,
|
|
+ buf, sizeof(buf)) > 0)
|
|
+ strlcpy(name, buf, sizeof(name));
|
|
+ if (dev->descriptor.iProduct
|
|
+ && usb_string(dev, dev->descriptor.iProduct,
|
|
+ buf, sizeof(buf)) > 0)
|
|
+ snprintf(name + strlen(name), sizeof(name) - strlen(name),
|
|
+ " %s", buf);
|
|
+
|
|
+ /* inbound data */
|
|
+ usb_fill_int_urb(ir->urb_in, dev, pipe, ir->buf_in,
|
|
+ maxp, (usb_complete_t) mceusb_dev_recv, ir, ep_in->bInterval);
|
|
+ ir->urb_in->transfer_dma = ir->dma_in;
|
|
+ ir->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
|
+
|
|
+ /* initialize device */
|
|
+ if (ir->flags.pinnacle) {
|
|
+ int usbret;
|
|
+
|
|
+ /*
|
|
+ * I have no idea why but this reset seems to be crucial to
|
|
+ * getting the device to do outbound IO correctly - without
|
|
+ * this the device seems to hang, ignoring all input - although
|
|
+ * IR signals are correctly sent from the device, no input is
|
|
+ * interpreted by the device and the host never does the
|
|
+ * completion routine
|
|
+ */
|
|
+
|
|
+ usbret = usb_reset_configuration(dev);
|
|
+ dev_info(ir->d->dev, "usb reset config ret %x\n", usbret);
|
|
+
|
|
+ /*
|
|
+ * its possible we really should wait for a return
|
|
+ * for each of these...
|
|
+ */
|
|
+ request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
|
|
+ request_packet_async(ir, ep_out, pin_init1, sizeof(pin_init1),
|
|
+ MCEUSB_OUTBOUND);
|
|
+ request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
|
|
+ request_packet_async(ir, ep_out, pin_init2, sizeof(pin_init2),
|
|
+ MCEUSB_OUTBOUND);
|
|
+ request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
|
|
+ request_packet_async(ir, ep_out, pin_init3, sizeof(pin_init3),
|
|
+ MCEUSB_OUTBOUND);
|
|
+ } else if (ir->flags.microsoft_gen1) {
|
|
+ /* original ms mce device requires some additional setup */
|
|
+ mceusb_gen1_init(ir);
|
|
+ } else {
|
|
+
|
|
+ request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
|
|
+ request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
|
|
+ request_packet_async(ir, ep_out, init1,
|
|
+ sizeof(init1), MCEUSB_OUTBOUND);
|
|
+ request_packet_async(ir, ep_in, NULL, maxp, MCEUSB_INBOUND);
|
|
+ request_packet_async(ir, ep_out, init2,
|
|
+ sizeof(init2), MCEUSB_OUTBOUND);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * if we don't issue the correct number of receives (MCEUSB_INBOUND)
|
|
+ * for each outbound, then the first few ir pulses will be interpreted
|
|
+ * by the usb_async_callback routine - we should ensure we have the
|
|
+ * right amount OR less - as the mceusb_dev_recv routine will handle
|
|
+ * the control packets OK - they start with 0x9f - but the async
|
|
+ * callback doesn't handle ir pulse packets
|
|
+ */
|
|
+ request_packet_async(ir, ep_in, NULL, maxp, 0);
|
|
+
|
|
+ usb_set_intfdata(intf, ir);
|
|
+
|
|
+ dev_info(ir->d->dev, "Registered %s on usb%d:%d\n", name,
|
|
+ dev->bus->busnum, devnum);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+ /* Error-handling path */
|
|
+lirc_register_fail:
|
|
+ usb_free_urb(ir->urb_in);
|
|
+urb_in_alloc_fail:
|
|
+ usb_buffer_free(dev, maxp, ir->buf_in, ir->dma_in);
|
|
+buf_in_alloc_fail:
|
|
+ lirc_buffer_free(rbuf);
|
|
+mem_alloc_fail:
|
|
+ kfree(rbuf);
|
|
+ kfree(driver);
|
|
+ kfree(ir);
|
|
+ dev_info(&intf->dev, "out of memory (code=%d)\n", mem_failure);
|
|
+
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+
|
|
+static void mceusb_dev_disconnect(struct usb_interface *intf)
|
|
+{
|
|
+ struct usb_device *dev = interface_to_usbdev(intf);
|
|
+ struct mceusb_dev *ir = usb_get_intfdata(intf);
|
|
+
|
|
+ usb_set_intfdata(intf, NULL);
|
|
+
|
|
+ if (!ir || !ir->d)
|
|
+ return;
|
|
+
|
|
+ ir->usbdev = NULL;
|
|
+ wake_up_all(&ir->wait_out);
|
|
+
|
|
+ mutex_lock(&ir->dev_lock);
|
|
+ usb_kill_urb(ir->urb_in);
|
|
+ usb_free_urb(ir->urb_in);
|
|
+ usb_buffer_free(dev, ir->len_in, ir->buf_in, ir->dma_in);
|
|
+ mutex_unlock(&ir->dev_lock);
|
|
+
|
|
+ unregister_from_lirc(ir);
|
|
+}
|
|
+
|
|
+static int mceusb_dev_suspend(struct usb_interface *intf, pm_message_t message)
|
|
+{
|
|
+ struct mceusb_dev *ir = usb_get_intfdata(intf);
|
|
+ dev_info(ir->d->dev, "suspend\n");
|
|
+ usb_kill_urb(ir->urb_in);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mceusb_dev_resume(struct usb_interface *intf)
|
|
+{
|
|
+ struct mceusb_dev *ir = usb_get_intfdata(intf);
|
|
+ dev_info(ir->d->dev, "resume\n");
|
|
+ if (usb_submit_urb(ir->urb_in, GFP_ATOMIC))
|
|
+ return -EIO;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct usb_driver mceusb_dev_driver = {
|
|
+ .name = DRIVER_NAME,
|
|
+ .probe = mceusb_dev_probe,
|
|
+ .disconnect = mceusb_dev_disconnect,
|
|
+ .suspend = mceusb_dev_suspend,
|
|
+ .resume = mceusb_dev_resume,
|
|
+ .reset_resume = mceusb_dev_resume,
|
|
+ .id_table = mceusb_dev_table
|
|
+};
|
|
+
|
|
+static int __init mceusb_dev_init(void)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ printk(KERN_INFO DRIVER_NAME ": " DRIVER_DESC " " DRIVER_VERSION "\n");
|
|
+ printk(KERN_INFO DRIVER_NAME ": " DRIVER_AUTHOR "\n");
|
|
+ if (debug)
|
|
+ printk(KERN_DEBUG DRIVER_NAME ": debug mode enabled\n");
|
|
+
|
|
+ i = usb_register(&mceusb_dev_driver);
|
|
+ if (i < 0) {
|
|
+ printk(KERN_ERR DRIVER_NAME
|
|
+ ": usb register failed, result = %d\n", i);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit mceusb_dev_exit(void)
|
|
+{
|
|
+ usb_deregister(&mceusb_dev_driver);
|
|
+}
|
|
+
|
|
+module_init(mceusb_dev_init);
|
|
+module_exit(mceusb_dev_exit);
|
|
+
|
|
+MODULE_DESCRIPTION(DRIVER_DESC);
|
|
+MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DEVICE_TABLE(usb, mceusb_dev_table);
|
|
+/* this was originally lirc_mceusb2, lirc_mceusb and lirc_mceusb2 merged now */
|
|
+MODULE_ALIAS("lirc_mceusb2");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Debug enabled or not");
|
|
diff --git a/drivers/input/lirc/lirc_parallel.c b/drivers/input/lirc/lirc_parallel.c
|
|
new file mode 100644
|
|
index 0000000..bb57b3e
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_parallel.c
|
|
@@ -0,0 +1,709 @@
|
|
+/*
|
|
+ * lirc_parallel.c
|
|
+ *
|
|
+ * lirc_parallel - device driver for infra-red signal receiving and
|
|
+ * transmitting unit built by the author
|
|
+ *
|
|
+ * Copyright (C) 1998 Christoph Bartelmus <lirc@bartelmus.de>
|
|
+ *
|
|
+ * 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
|
|
+ *
|
|
+ */
|
|
+
|
|
+/*** Includes ***/
|
|
+
|
|
+#ifdef CONFIG_SMP
|
|
+#error "--- Sorry, this driver is not SMP safe. ---"
|
|
+#endif
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/signal.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+#include <linux/io.h>
|
|
+#include <linux/signal.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <asm/div64.h>
|
|
+
|
|
+#include <linux/poll.h>
|
|
+#include <linux/parport.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+#include "lirc_parallel.h"
|
|
+
|
|
+#define LIRC_DRIVER_NAME "lirc_parallel"
|
|
+
|
|
+#ifndef LIRC_IRQ
|
|
+#define LIRC_IRQ 7
|
|
+#endif
|
|
+#ifndef LIRC_PORT
|
|
+#define LIRC_PORT 0x378
|
|
+#endif
|
|
+#ifndef LIRC_TIMER
|
|
+#define LIRC_TIMER 65536
|
|
+#endif
|
|
+
|
|
+/*** Global Variables ***/
|
|
+
|
|
+static int debug;
|
|
+static int check_pselecd;
|
|
+
|
|
+unsigned int irq = LIRC_IRQ;
|
|
+unsigned int io = LIRC_PORT;
|
|
+#ifdef LIRC_TIMER
|
|
+unsigned int timer;
|
|
+unsigned int default_timer = LIRC_TIMER;
|
|
+#endif
|
|
+
|
|
+#define WBUF_SIZE (256)
|
|
+#define RBUF_SIZE (256) /* this must be a power of 2 larger than 1 */
|
|
+
|
|
+static int wbuf[WBUF_SIZE];
|
|
+static int rbuf[RBUF_SIZE];
|
|
+
|
|
+DECLARE_WAIT_QUEUE_HEAD(lirc_wait);
|
|
+
|
|
+unsigned int rptr;
|
|
+unsigned int wptr;
|
|
+unsigned int lost_irqs;
|
|
+int is_open;
|
|
+
|
|
+struct parport *pport;
|
|
+struct pardevice *ppdevice;
|
|
+int is_claimed;
|
|
+
|
|
+unsigned int tx_mask = 1;
|
|
+
|
|
+/*** Internal Functions ***/
|
|
+
|
|
+static unsigned int in(int offset)
|
|
+{
|
|
+ switch (offset) {
|
|
+ case LIRC_LP_BASE:
|
|
+ return parport_read_data(pport);
|
|
+ case LIRC_LP_STATUS:
|
|
+ return parport_read_status(pport);
|
|
+ case LIRC_LP_CONTROL:
|
|
+ return parport_read_control(pport);
|
|
+ }
|
|
+ return 0; /* make compiler happy */
|
|
+}
|
|
+
|
|
+static void out(int offset, int value)
|
|
+{
|
|
+ switch (offset) {
|
|
+ case LIRC_LP_BASE:
|
|
+ parport_write_data(pport, value);
|
|
+ break;
|
|
+ case LIRC_LP_CONTROL:
|
|
+ parport_write_control(pport, value);
|
|
+ break;
|
|
+ case LIRC_LP_STATUS:
|
|
+ printk(KERN_INFO "%s: attempt to write to status register\n",
|
|
+ LIRC_DRIVER_NAME);
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static unsigned int lirc_get_timer(void)
|
|
+{
|
|
+ return in(LIRC_PORT_TIMER) & LIRC_PORT_TIMER_BIT;
|
|
+}
|
|
+
|
|
+static unsigned int lirc_get_signal(void)
|
|
+{
|
|
+ return in(LIRC_PORT_SIGNAL) & LIRC_PORT_SIGNAL_BIT;
|
|
+}
|
|
+
|
|
+static void lirc_on(void)
|
|
+{
|
|
+ out(LIRC_PORT_DATA, tx_mask);
|
|
+}
|
|
+
|
|
+static void lirc_off(void)
|
|
+{
|
|
+ out(LIRC_PORT_DATA, 0);
|
|
+}
|
|
+
|
|
+static unsigned int init_lirc_timer(void)
|
|
+{
|
|
+ struct timeval tv, now;
|
|
+ unsigned int level, newlevel, timeelapsed, newtimer;
|
|
+ int count = 0;
|
|
+
|
|
+ do_gettimeofday(&tv);
|
|
+ tv.tv_sec++; /* wait max. 1 sec. */
|
|
+ level = lirc_get_timer();
|
|
+ do {
|
|
+ newlevel = lirc_get_timer();
|
|
+ if (level == 0 && newlevel != 0)
|
|
+ count++;
|
|
+ level = newlevel;
|
|
+ do_gettimeofday(&now);
|
|
+ } while (count < 1000 && (now.tv_sec < tv.tv_sec
|
|
+ || (now.tv_sec == tv.tv_sec
|
|
+ && now.tv_usec < tv.tv_usec)));
|
|
+
|
|
+ timeelapsed = ((now.tv_sec + 1 - tv.tv_sec)*1000000
|
|
+ + (now.tv_usec - tv.tv_usec));
|
|
+ if (count >= 1000 && timeelapsed > 0) {
|
|
+ if (default_timer == 0) {
|
|
+ /* autodetect timer */
|
|
+ newtimer = (1000000*count)/timeelapsed;
|
|
+ printk(KERN_INFO "%s: %u Hz timer detected\n",
|
|
+ LIRC_DRIVER_NAME, newtimer);
|
|
+ return newtimer;
|
|
+ } else {
|
|
+ newtimer = (1000000*count)/timeelapsed;
|
|
+ if (abs(newtimer - default_timer) > default_timer/10) {
|
|
+ /* bad timer */
|
|
+ printk(KERN_NOTICE "%s: bad timer: %u Hz\n",
|
|
+ LIRC_DRIVER_NAME, newtimer);
|
|
+ printk(KERN_NOTICE "%s: using default timer: "
|
|
+ "%u Hz\n",
|
|
+ LIRC_DRIVER_NAME, default_timer);
|
|
+ return default_timer;
|
|
+ } else {
|
|
+ printk(KERN_INFO "%s: %u Hz timer detected\n",
|
|
+ LIRC_DRIVER_NAME, newtimer);
|
|
+ return newtimer; /* use detected value */
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ printk(KERN_NOTICE "%s: no timer detected\n", LIRC_DRIVER_NAME);
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int lirc_claim(void)
|
|
+{
|
|
+ if (parport_claim(ppdevice) != 0) {
|
|
+ printk(KERN_WARNING "%s: could not claim port\n",
|
|
+ LIRC_DRIVER_NAME);
|
|
+ printk(KERN_WARNING "%s: waiting for port becoming available"
|
|
+ "\n", LIRC_DRIVER_NAME);
|
|
+ if (parport_claim_or_block(ppdevice) < 0) {
|
|
+ printk(KERN_NOTICE "%s: could not claim port, giving"
|
|
+ " up\n", LIRC_DRIVER_NAME);
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+ out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP);
|
|
+ is_claimed = 1;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+/*** interrupt handler ***/
|
|
+
|
|
+static void rbuf_write(int signal)
|
|
+{
|
|
+ unsigned int nwptr;
|
|
+
|
|
+ nwptr = (wptr + 1) & (RBUF_SIZE - 1);
|
|
+ if (nwptr == rptr) {
|
|
+ /* no new signals will be accepted */
|
|
+ lost_irqs++;
|
|
+ printk(KERN_NOTICE "%s: buffer overrun\n", LIRC_DRIVER_NAME);
|
|
+ return;
|
|
+ }
|
|
+ rbuf[wptr] = signal;
|
|
+ wptr = nwptr;
|
|
+}
|
|
+
|
|
+static void irq_handler(void *blah)
|
|
+{
|
|
+ struct timeval tv;
|
|
+ static struct timeval lasttv;
|
|
+ static int init;
|
|
+ long signal;
|
|
+ int data;
|
|
+ unsigned int level, newlevel;
|
|
+ unsigned int timeout;
|
|
+
|
|
+ if (!module_refcount(THIS_MODULE))
|
|
+ return;
|
|
+
|
|
+ if (!is_claimed)
|
|
+ return;
|
|
+
|
|
+#if 0
|
|
+ /* disable interrupt */
|
|
+ disable_irq(irq);
|
|
+ out(LIRC_PORT_IRQ, in(LIRC_PORT_IRQ) & (~LP_PINTEN));
|
|
+#endif
|
|
+ if (check_pselecd && (in(1) & LP_PSELECD))
|
|
+ return;
|
|
+
|
|
+#ifdef LIRC_TIMER
|
|
+ if (init) {
|
|
+ do_gettimeofday(&tv);
|
|
+
|
|
+ signal = tv.tv_sec - lasttv.tv_sec;
|
|
+ if (signal > 15)
|
|
+ /* really long time */
|
|
+ data = PULSE_MASK;
|
|
+ else
|
|
+ data = (int) (signal*1000000 +
|
|
+ tv.tv_usec - lasttv.tv_usec +
|
|
+ LIRC_SFH506_DELAY);
|
|
+
|
|
+ rbuf_write(data); /* space */
|
|
+ } else {
|
|
+ if (timer == 0) {
|
|
+ /*
|
|
+ * wake up; we'll lose this signal, but it will be
|
|
+ * garbage if the device is turned on anyway
|
|
+ */
|
|
+ timer = init_lirc_timer();
|
|
+ /* enable_irq(irq); */
|
|
+ return;
|
|
+ }
|
|
+ init = 1;
|
|
+ }
|
|
+
|
|
+ timeout = timer/10; /* timeout after 1/10 sec. */
|
|
+ signal = 1;
|
|
+ level = lirc_get_timer();
|
|
+ do {
|
|
+ newlevel = lirc_get_timer();
|
|
+ if (level == 0 && newlevel != 0)
|
|
+ signal++;
|
|
+ level = newlevel;
|
|
+
|
|
+ /* giving up */
|
|
+ if (signal > timeout
|
|
+ || (check_pselecd && (in(1) & LP_PSELECD))) {
|
|
+ signal = 0;
|
|
+ printk(KERN_NOTICE "%s: timeout\n", LIRC_DRIVER_NAME);
|
|
+ break;
|
|
+ }
|
|
+ } while (lirc_get_signal());
|
|
+
|
|
+ if (signal != 0) {
|
|
+ /* ajust value to usecs */
|
|
+ unsigned long long helper;
|
|
+
|
|
+ helper = ((unsigned long long) signal)*1000000;
|
|
+ do_div(helper, timer);
|
|
+ signal = (long) helper;
|
|
+
|
|
+ if (signal > LIRC_SFH506_DELAY)
|
|
+ data = signal - LIRC_SFH506_DELAY;
|
|
+ else
|
|
+ data = 1;
|
|
+ rbuf_write(PULSE_BIT|data); /* pulse */
|
|
+ }
|
|
+ do_gettimeofday(&lasttv);
|
|
+#else
|
|
+ /* add your code here */
|
|
+#endif
|
|
+
|
|
+ wake_up_interruptible(&lirc_wait);
|
|
+
|
|
+ /* enable interrupt */
|
|
+ /*
|
|
+ enable_irq(irq);
|
|
+ out(LIRC_PORT_IRQ, in(LIRC_PORT_IRQ)|LP_PINTEN);
|
|
+ */
|
|
+}
|
|
+
|
|
+/*** file operations ***/
|
|
+
|
|
+static loff_t lirc_lseek(struct file *filep, loff_t offset, int orig)
|
|
+{
|
|
+ return -ESPIPE;
|
|
+}
|
|
+
|
|
+static ssize_t lirc_read(struct file *filep, char *buf, size_t n, loff_t *ppos)
|
|
+{
|
|
+ int result = 0;
|
|
+ int count = 0;
|
|
+ DECLARE_WAITQUEUE(wait, current);
|
|
+
|
|
+ if (n % sizeof(int))
|
|
+ return -EINVAL;
|
|
+
|
|
+ add_wait_queue(&lirc_wait, &wait);
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ while (count < n) {
|
|
+ if (rptr != wptr) {
|
|
+ if (copy_to_user(buf+count, (char *) &rbuf[rptr],
|
|
+ sizeof(int))) {
|
|
+ result = -EFAULT;
|
|
+ break;
|
|
+ }
|
|
+ rptr = (rptr + 1) & (RBUF_SIZE - 1);
|
|
+ count += sizeof(int);
|
|
+ } else {
|
|
+ if (filep->f_flags & O_NONBLOCK) {
|
|
+ result = -EAGAIN;
|
|
+ break;
|
|
+ }
|
|
+ if (signal_pending(current)) {
|
|
+ result = -ERESTARTSYS;
|
|
+ break;
|
|
+ }
|
|
+ schedule();
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ }
|
|
+ }
|
|
+ remove_wait_queue(&lirc_wait, &wait);
|
|
+ set_current_state(TASK_RUNNING);
|
|
+ return count ? count : result;
|
|
+}
|
|
+
|
|
+static ssize_t lirc_write(struct file *filep, const char *buf, size_t n,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ int count;
|
|
+ unsigned int i;
|
|
+ unsigned int level, newlevel;
|
|
+ unsigned long flags;
|
|
+ int counttimer;
|
|
+
|
|
+ if (!is_claimed)
|
|
+ return -EBUSY;
|
|
+
|
|
+ if (n % sizeof(int))
|
|
+ return -EINVAL;
|
|
+
|
|
+ count = n / sizeof(int);
|
|
+
|
|
+ if (count > WBUF_SIZE || count % 2 == 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (copy_from_user(wbuf, buf, n))
|
|
+ return -EFAULT;
|
|
+
|
|
+#ifdef LIRC_TIMER
|
|
+ if (timer == 0) {
|
|
+ /* try again if device is ready */
|
|
+ timer = init_lirc_timer();
|
|
+ if (timer == 0)
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ /* adjust values from usecs */
|
|
+ for (i = 0; i < count; i++) {
|
|
+ unsigned long long helper;
|
|
+
|
|
+ helper = ((unsigned long long) wbuf[i])*timer;
|
|
+ do_div(helper, 1000000);
|
|
+ wbuf[i] = (int) helper;
|
|
+ }
|
|
+
|
|
+ local_irq_save(flags);
|
|
+ i = 0;
|
|
+ while (i < count) {
|
|
+ level = lirc_get_timer();
|
|
+ counttimer = 0;
|
|
+ lirc_on();
|
|
+ do {
|
|
+ newlevel = lirc_get_timer();
|
|
+ if (level == 0 && newlevel != 0)
|
|
+ counttimer++;
|
|
+ level = newlevel;
|
|
+ if (check_pselecd && (in(1) & LP_PSELECD)) {
|
|
+ lirc_off();
|
|
+ local_irq_restore(flags);
|
|
+ return -EIO;
|
|
+ }
|
|
+ } while (counttimer < wbuf[i]);
|
|
+ i++;
|
|
+
|
|
+ lirc_off();
|
|
+ if (i == count)
|
|
+ break;
|
|
+ counttimer = 0;
|
|
+ do {
|
|
+ newlevel = lirc_get_timer();
|
|
+ if (level == 0 && newlevel != 0)
|
|
+ counttimer++;
|
|
+ level = newlevel;
|
|
+ if (check_pselecd && (in(1) & LP_PSELECD)) {
|
|
+ local_irq_restore(flags);
|
|
+ return -EIO;
|
|
+ }
|
|
+ } while (counttimer < wbuf[i]);
|
|
+ i++;
|
|
+ }
|
|
+ local_irq_restore(flags);
|
|
+#else
|
|
+ /* place code that handles write without external timer here */
|
|
+#endif
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static unsigned int lirc_poll(struct file *file, poll_table *wait)
|
|
+{
|
|
+ poll_wait(file, &lirc_wait, wait);
|
|
+ if (rptr != wptr)
|
|
+ return POLLIN | POLLRDNORM;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int lirc_ioctl(struct inode *node, struct file *filep, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ int result;
|
|
+ unsigned long features = LIRC_CAN_SET_TRANSMITTER_MASK |
|
|
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2;
|
|
+ unsigned long mode;
|
|
+ unsigned int ivalue;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case LIRC_GET_FEATURES:
|
|
+ result = put_user(features, (unsigned long *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ break;
|
|
+ case LIRC_GET_SEND_MODE:
|
|
+ result = put_user(LIRC_MODE_PULSE, (unsigned long *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ break;
|
|
+ case LIRC_GET_REC_MODE:
|
|
+ result = put_user(LIRC_MODE_MODE2, (unsigned long *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ break;
|
|
+ case LIRC_SET_SEND_MODE:
|
|
+ result = get_user(mode, (unsigned long *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ if (mode != LIRC_MODE_PULSE)
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+ case LIRC_SET_REC_MODE:
|
|
+ result = get_user(mode, (unsigned long *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ if (mode != LIRC_MODE_MODE2)
|
|
+ return -ENOSYS;
|
|
+ break;
|
|
+ case LIRC_SET_TRANSMITTER_MASK:
|
|
+ result = get_user(ivalue, (unsigned int *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ if ((ivalue & LIRC_PARALLEL_TRANSMITTER_MASK) != ivalue)
|
|
+ return LIRC_PARALLEL_MAX_TRANSMITTERS;
|
|
+ tx_mask = ivalue;
|
|
+ break;
|
|
+ default:
|
|
+ return -ENOIOCTLCMD;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int lirc_open(struct inode *node, struct file *filep)
|
|
+{
|
|
+ if (module_refcount(THIS_MODULE) || !lirc_claim())
|
|
+ return -EBUSY;
|
|
+
|
|
+ parport_enable_irq(pport);
|
|
+
|
|
+ /* init read ptr */
|
|
+ rptr = 0;
|
|
+ wptr = 0;
|
|
+ lost_irqs = 0;
|
|
+
|
|
+ is_open = 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int lirc_close(struct inode *node, struct file *filep)
|
|
+{
|
|
+ if (is_claimed) {
|
|
+ is_claimed = 0;
|
|
+ parport_release(ppdevice);
|
|
+ }
|
|
+ is_open = 0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct file_operations lirc_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .llseek = lirc_lseek,
|
|
+ .read = lirc_read,
|
|
+ .write = lirc_write,
|
|
+ .poll = lirc_poll,
|
|
+ .ioctl = lirc_ioctl,
|
|
+ .open = lirc_open,
|
|
+ .release = lirc_close
|
|
+};
|
|
+
|
|
+static int set_use_inc(void *data)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void set_use_dec(void *data)
|
|
+{
|
|
+}
|
|
+
|
|
+static struct lirc_driver driver = {
|
|
+ .name = LIRC_DRIVER_NAME,
|
|
+ .minor = -1,
|
|
+ .code_length = 1,
|
|
+ .sample_rate = 0,
|
|
+ .data = NULL,
|
|
+ .add_to_buf = NULL,
|
|
+ .set_use_inc = set_use_inc,
|
|
+ .set_use_dec = set_use_dec,
|
|
+ .fops = &lirc_fops,
|
|
+ .dev = NULL,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+static int pf(void *handle);
|
|
+static void kf(void *handle);
|
|
+
|
|
+static struct timer_list poll_timer;
|
|
+static void poll_state(unsigned long ignored);
|
|
+
|
|
+static void poll_state(unsigned long ignored)
|
|
+{
|
|
+ printk(KERN_NOTICE "%s: time\n",
|
|
+ LIRC_DRIVER_NAME);
|
|
+ del_timer(&poll_timer);
|
|
+ if (is_claimed)
|
|
+ return;
|
|
+ kf(NULL);
|
|
+ if (!is_claimed) {
|
|
+ printk(KERN_NOTICE "%s: could not claim port, giving up\n",
|
|
+ LIRC_DRIVER_NAME);
|
|
+ init_timer(&poll_timer);
|
|
+ poll_timer.expires = jiffies + HZ;
|
|
+ poll_timer.data = (unsigned long)current;
|
|
+ poll_timer.function = poll_state;
|
|
+ add_timer(&poll_timer);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int pf(void *handle)
|
|
+{
|
|
+ parport_disable_irq(pport);
|
|
+ is_claimed = 0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void kf(void *handle)
|
|
+{
|
|
+ if (!is_open)
|
|
+ return;
|
|
+ if (!lirc_claim())
|
|
+ return;
|
|
+ parport_enable_irq(pport);
|
|
+ lirc_off();
|
|
+ /* this is a bit annoying when you actually print...*/
|
|
+ /*
|
|
+ printk(KERN_INFO "%s: reclaimed port\n", LIRC_DRIVER_NAME);
|
|
+ */
|
|
+}
|
|
+
|
|
+/*** module initialization and cleanup ***/
|
|
+
|
|
+static int __init lirc_parallel_init(void)
|
|
+{
|
|
+ pport = parport_find_base(io);
|
|
+ if (pport == NULL) {
|
|
+ printk(KERN_NOTICE "%s: no port at %x found\n",
|
|
+ LIRC_DRIVER_NAME, io);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ ppdevice = parport_register_device(pport, LIRC_DRIVER_NAME,
|
|
+ pf, kf, irq_handler, 0, NULL);
|
|
+ parport_put_port(pport);
|
|
+ if (ppdevice == NULL) {
|
|
+ printk(KERN_NOTICE "%s: parport_register_device() failed\n",
|
|
+ LIRC_DRIVER_NAME);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ if (parport_claim(ppdevice) != 0)
|
|
+ goto skip_init;
|
|
+ is_claimed = 1;
|
|
+ out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP);
|
|
+
|
|
+#ifdef LIRC_TIMER
|
|
+ if (debug)
|
|
+ out(LIRC_PORT_DATA, tx_mask);
|
|
+
|
|
+ timer = init_lirc_timer();
|
|
+
|
|
+#if 0 /* continue even if device is offline */
|
|
+ if (timer == 0) {
|
|
+ is_claimed = 0;
|
|
+ parport_release(pport);
|
|
+ parport_unregister_device(ppdevice);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+#endif
|
|
+ if (debug)
|
|
+ out(LIRC_PORT_DATA, 0);
|
|
+#endif
|
|
+
|
|
+ is_claimed = 0;
|
|
+ parport_release(ppdevice);
|
|
+ skip_init:
|
|
+ driver.minor = lirc_register_driver(&driver);
|
|
+ if (driver.minor < 0) {
|
|
+ printk(KERN_NOTICE "%s: register_chrdev() failed\n",
|
|
+ LIRC_DRIVER_NAME);
|
|
+ parport_unregister_device(ppdevice);
|
|
+ return -EIO;
|
|
+ }
|
|
+ printk(KERN_INFO "%s: installed using port 0x%04x irq %d\n",
|
|
+ LIRC_DRIVER_NAME, io, irq);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit lirc_parallel_exit(void)
|
|
+{
|
|
+ parport_unregister_device(ppdevice);
|
|
+ lirc_unregister_driver(driver.minor);
|
|
+}
|
|
+
|
|
+module_init(lirc_parallel_init);
|
|
+module_exit(lirc_parallel_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("Infrared receiver driver for parallel ports.");
|
|
+MODULE_AUTHOR("Christoph Bartelmus");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_param(io, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(io, "I/O address base (0x3bc, 0x378 or 0x278)");
|
|
+
|
|
+module_param(irq, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(irq, "Interrupt (7 or 5)");
|
|
+
|
|
+module_param(tx_mask, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(tx_maxk, "Transmitter mask (default: 0x01)");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
+
|
|
+module_param(check_pselecd, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Check for printer (default: 0)");
|
|
diff --git a/drivers/input/lirc/lirc_parallel.h b/drivers/input/lirc/lirc_parallel.h
|
|
new file mode 100644
|
|
index 0000000..4bed6af
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_parallel.h
|
|
@@ -0,0 +1,26 @@
|
|
+/* lirc_parallel.h */
|
|
+
|
|
+#ifndef _LIRC_PARALLEL_H
|
|
+#define _LIRC_PARALLEL_H
|
|
+
|
|
+#include <linux/lp.h>
|
|
+
|
|
+#define LIRC_PORT_LEN 3
|
|
+
|
|
+#define LIRC_LP_BASE 0
|
|
+#define LIRC_LP_STATUS 1
|
|
+#define LIRC_LP_CONTROL 2
|
|
+
|
|
+#define LIRC_PORT_DATA LIRC_LP_BASE /* base */
|
|
+#define LIRC_PORT_TIMER LIRC_LP_STATUS /* status port */
|
|
+#define LIRC_PORT_TIMER_BIT LP_PBUSY /* busy signal */
|
|
+#define LIRC_PORT_SIGNAL LIRC_LP_STATUS /* status port */
|
|
+#define LIRC_PORT_SIGNAL_BIT LP_PACK /* ack signal */
|
|
+#define LIRC_PORT_IRQ LIRC_LP_CONTROL /* control port */
|
|
+
|
|
+#define LIRC_SFH506_DELAY 0 /* delay t_phl in usecs */
|
|
+
|
|
+#define LIRC_PARALLEL_MAX_TRANSMITTERS 8
|
|
+#define LIRC_PARALLEL_TRANSMITTER_MASK ((1<<LIRC_PARALLEL_MAX_TRANSMITTERS) - 1)
|
|
+
|
|
+#endif
|
|
diff --git a/drivers/input/lirc/lirc_sasem.c b/drivers/input/lirc/lirc_sasem.c
|
|
new file mode 100644
|
|
index 0000000..4015684
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_sasem.c
|
|
@@ -0,0 +1,931 @@
|
|
+/*
|
|
+ * lirc_sasem.c - USB remote support for LIRC
|
|
+ * Version 0.5
|
|
+ *
|
|
+ * Copyright (C) 2004-2005 Oliver Stabel <oliver.stabel@gmx.de>
|
|
+ * Tim Davies <tim@opensystems.net.au>
|
|
+ *
|
|
+ * This driver was derived from:
|
|
+ * Venky Raju <dev@venky.ws>
|
|
+ * "lirc_imon - "LIRC/VFD driver for Ahanix/Soundgraph IMON IR/VFD"
|
|
+ * Paul Miller <pmiller9@users.sourceforge.net>'s 2003-2004
|
|
+ * "lirc_atiusb - USB remote support for LIRC"
|
|
+ * Culver Consulting Services <henry@culcon.com>'s 2003
|
|
+ * "Sasem OnAir VFD/IR USB driver"
|
|
+ *
|
|
+ *
|
|
+ * NOTE - The LCDproc iMon driver should work with this module. More info at
|
|
+ * http://www.frogstorm.info/sasem
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * 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/errno.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/usb.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+
|
|
+#define MOD_AUTHOR "Oliver Stabel <oliver.stabel@gmx.de>, " \
|
|
+ "Tim Davies <tim@opensystems.net.au>"
|
|
+#define MOD_DESC "USB Driver for Sasem Remote Controller V1.1"
|
|
+#define MOD_NAME "lirc_sasem"
|
|
+#define MOD_VERSION "0.5"
|
|
+
|
|
+#define VFD_MINOR_BASE 144 /* Same as LCD */
|
|
+#define DEVICE_NAME "lcd%d"
|
|
+
|
|
+#define BUF_CHUNK_SIZE 8
|
|
+#define BUF_SIZE 128
|
|
+
|
|
+#define IOCTL_LCD_CONTRAST 1
|
|
+
|
|
+/*** P R O T O T Y P E S ***/
|
|
+
|
|
+/* USB Callback prototypes */
|
|
+static int sasem_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id);
|
|
+static void sasem_disconnect(struct usb_interface *interface);
|
|
+static void usb_rx_callback(struct urb *urb);
|
|
+static void usb_tx_callback(struct urb *urb);
|
|
+
|
|
+/* VFD file_operations function prototypes */
|
|
+static int vfd_open(struct inode *inode, struct file *file);
|
|
+static int vfd_ioctl(struct inode *inode, struct file *file,
|
|
+ unsigned cmd, unsigned long arg);
|
|
+static int vfd_close(struct inode *inode, struct file *file);
|
|
+static ssize_t vfd_write(struct file *file, const char *buf,
|
|
+ size_t n_bytes, loff_t *pos);
|
|
+
|
|
+/* LIRC driver function prototypes */
|
|
+static int ir_open(void *data);
|
|
+static void ir_close(void *data);
|
|
+
|
|
+/* Driver init/exit prototypes */
|
|
+static int __init sasem_init(void);
|
|
+static void __exit sasem_exit(void);
|
|
+
|
|
+/*** G L O B A L S ***/
|
|
+
|
|
+struct sasem_context {
|
|
+
|
|
+ struct usb_device *dev;
|
|
+ int vfd_isopen; /* VFD port has been opened */
|
|
+ unsigned int vfd_contrast; /* VFD contrast */
|
|
+ int ir_isopen; /* IR port has been opened */
|
|
+ int dev_present; /* USB device presence */
|
|
+ struct mutex ctx_lock; /* to lock this object */
|
|
+ wait_queue_head_t remove_ok; /* For unexpected USB disconnects */
|
|
+
|
|
+ struct lirc_driver *driver;
|
|
+ struct usb_endpoint_descriptor *rx_endpoint;
|
|
+ struct usb_endpoint_descriptor *tx_endpoint;
|
|
+ struct urb *rx_urb;
|
|
+ struct urb *tx_urb;
|
|
+ unsigned char usb_rx_buf[8];
|
|
+ unsigned char usb_tx_buf[8];
|
|
+
|
|
+ struct tx_t {
|
|
+ unsigned char data_buf[32]; /* user data buffer */
|
|
+ struct completion finished; /* wait for write to finish */
|
|
+ atomic_t busy; /* write in progress */
|
|
+ int status; /* status of tx completion */
|
|
+ } tx;
|
|
+
|
|
+ /* for dealing with repeat codes (wish there was a toggle bit!) */
|
|
+ struct timeval presstime;
|
|
+ char lastcode[8];
|
|
+ int codesaved;
|
|
+};
|
|
+
|
|
+/* VFD file operations */
|
|
+static struct file_operations vfd_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = &vfd_open,
|
|
+ .write = &vfd_write,
|
|
+ .ioctl = &vfd_ioctl,
|
|
+ .release = &vfd_close,
|
|
+};
|
|
+
|
|
+/* USB Device ID for Sasem USB Control Board */
|
|
+static struct usb_device_id sasem_usb_id_table[] = {
|
|
+ /* Sasem USB Control Board */
|
|
+ { USB_DEVICE(0x11ba, 0x0101) },
|
|
+ /* Terminating entry */
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* USB Device data */
|
|
+static struct usb_driver sasem_driver = {
|
|
+ .name = MOD_NAME,
|
|
+ .probe = sasem_probe,
|
|
+ .disconnect = sasem_disconnect,
|
|
+ .id_table = sasem_usb_id_table,
|
|
+};
|
|
+
|
|
+static struct usb_class_driver sasem_class = {
|
|
+ .name = DEVICE_NAME,
|
|
+ .fops = &vfd_fops,
|
|
+ .minor_base = VFD_MINOR_BASE,
|
|
+};
|
|
+
|
|
+/* to prevent races between open() and disconnect() */
|
|
+static DEFINE_MUTEX(disconnect_lock);
|
|
+
|
|
+static int debug;
|
|
+
|
|
+
|
|
+/*** M O D U L E C O D E ***/
|
|
+
|
|
+MODULE_AUTHOR(MOD_AUTHOR);
|
|
+MODULE_DESCRIPTION(MOD_DESC);
|
|
+MODULE_LICENSE("GPL");
|
|
+module_param(debug, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes (default: no)");
|
|
+
|
|
+static void delete_context(struct sasem_context *context)
|
|
+{
|
|
+ usb_free_urb(context->tx_urb); /* VFD */
|
|
+ usb_free_urb(context->rx_urb); /* IR */
|
|
+ lirc_buffer_free(context->driver->rbuf);
|
|
+ kfree(context->driver->rbuf);
|
|
+ kfree(context->driver);
|
|
+ kfree(context);
|
|
+
|
|
+ if (debug)
|
|
+ printk(KERN_INFO "%s: context deleted\n", __func__);
|
|
+}
|
|
+
|
|
+static void deregister_from_lirc(struct sasem_context *context)
|
|
+{
|
|
+ int retval;
|
|
+ int minor = context->driver->minor;
|
|
+
|
|
+ retval = lirc_unregister_driver(minor);
|
|
+ if (retval)
|
|
+ err("%s: unable to deregister from lirc (%d)",
|
|
+ __func__, retval);
|
|
+ else
|
|
+ printk(KERN_INFO "Deregistered Sasem driver (minor:%d)\n",
|
|
+ minor);
|
|
+
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called when the VFD device (e.g. /dev/usb/lcd)
|
|
+ * is opened by the application.
|
|
+ */
|
|
+static int vfd_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct usb_interface *interface;
|
|
+ struct sasem_context *context = NULL;
|
|
+ int subminor;
|
|
+ int retval = 0;
|
|
+
|
|
+ /* prevent races with disconnect */
|
|
+ mutex_lock(&disconnect_lock);
|
|
+
|
|
+ subminor = iminor(inode);
|
|
+ interface = usb_find_interface(&sasem_driver, subminor);
|
|
+ if (!interface) {
|
|
+ err("%s: could not find interface for minor %d",
|
|
+ __func__, subminor);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+ context = usb_get_intfdata(interface);
|
|
+
|
|
+ if (!context) {
|
|
+ err("%s: no context found for minor %d",
|
|
+ __func__, subminor);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ if (context->vfd_isopen) {
|
|
+ err("%s: VFD port is already open", __func__);
|
|
+ retval = -EBUSY;
|
|
+ } else {
|
|
+ context->vfd_isopen = 1;
|
|
+ file->private_data = context;
|
|
+ printk(KERN_INFO "VFD port opened\n");
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+
|
|
+exit:
|
|
+ mutex_unlock(&disconnect_lock);
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called when the VFD device (e.g. /dev/usb/lcd)
|
|
+ * is closed by the application.
|
|
+ */
|
|
+static int vfd_ioctl(struct inode *inode, struct file *file,
|
|
+ unsigned cmd, unsigned long arg)
|
|
+{
|
|
+ struct sasem_context *context = NULL;
|
|
+
|
|
+ context = (struct sasem_context *) file->private_data;
|
|
+
|
|
+ if (!context) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ switch (cmd) {
|
|
+ case IOCTL_LCD_CONTRAST:
|
|
+ if (arg > 1000)
|
|
+ arg = 1000;
|
|
+ context->vfd_contrast = (unsigned int)arg;
|
|
+ break;
|
|
+ default:
|
|
+ printk(KERN_INFO "Unknown IOCTL command\n");
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ return -ENOIOCTLCMD; /* not supported */
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called when the VFD device (e.g. /dev/usb/lcd)
|
|
+ * is closed by the application.
|
|
+ */
|
|
+static int vfd_close(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct sasem_context *context = NULL;
|
|
+ int retval = 0;
|
|
+
|
|
+ context = (struct sasem_context *) file->private_data;
|
|
+
|
|
+ if (!context) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ if (!context->vfd_isopen) {
|
|
+ err("%s: VFD is not open", __func__);
|
|
+ retval = -EIO;
|
|
+ } else {
|
|
+ context->vfd_isopen = 0;
|
|
+ printk(KERN_INFO "VFD port closed\n");
|
|
+ if (!context->dev_present && !context->ir_isopen) {
|
|
+
|
|
+ /* Device disconnected before close and IR port is
|
|
+ * not open. If IR port is open, context will be
|
|
+ * deleted by ir_close. */
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ delete_context(context);
|
|
+ return retval;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Sends a packet to the VFD.
|
|
+ */
|
|
+static int send_packet(struct sasem_context *context)
|
|
+{
|
|
+ unsigned int pipe;
|
|
+ int interval = 0;
|
|
+ int retval = 0;
|
|
+
|
|
+ pipe = usb_sndintpipe(context->dev,
|
|
+ context->tx_endpoint->bEndpointAddress);
|
|
+ interval = context->tx_endpoint->bInterval;
|
|
+
|
|
+ usb_fill_int_urb(context->tx_urb, context->dev, pipe,
|
|
+ context->usb_tx_buf, sizeof(context->usb_tx_buf),
|
|
+ usb_tx_callback, context, interval);
|
|
+
|
|
+ context->tx_urb->actual_length = 0;
|
|
+
|
|
+ init_completion(&context->tx.finished);
|
|
+ atomic_set(&(context->tx.busy), 1);
|
|
+
|
|
+ retval = usb_submit_urb(context->tx_urb, GFP_KERNEL);
|
|
+ if (retval) {
|
|
+ atomic_set(&(context->tx.busy), 0);
|
|
+ err("%s: error submitting urb (%d)", __func__, retval);
|
|
+ } else {
|
|
+ /* Wait for transmission to complete (or abort) */
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ wait_for_completion(&context->tx.finished);
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ retval = context->tx.status;
|
|
+ if (retval)
|
|
+ err("%s: packet tx failed (%d)", __func__, retval);
|
|
+ }
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Writes data to the VFD. The Sasem VFD is 2x16 characters
|
|
+ * and requires data in 9 consecutive USB interrupt packets,
|
|
+ * each packet carrying 8 bytes.
|
|
+ */
|
|
+static ssize_t vfd_write(struct file *file, const char *buf,
|
|
+ size_t n_bytes, loff_t *pos)
|
|
+{
|
|
+ int i;
|
|
+ int retval = 0;
|
|
+ struct sasem_context *context;
|
|
+
|
|
+ context = (struct sasem_context *) file->private_data;
|
|
+ if (!context) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ if (!context->dev_present) {
|
|
+ err("%s: no Sasem device present", __func__);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (n_bytes <= 0 || n_bytes > 32) {
|
|
+ err("%s: invalid payload size", __func__);
|
|
+ retval = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ retval = copy_from_user(context->tx.data_buf, buf, n_bytes);
|
|
+ if (retval < 0)
|
|
+ goto exit;
|
|
+
|
|
+ /* Pad with spaces */
|
|
+ for (i = n_bytes; i < 32; ++i)
|
|
+ context->tx.data_buf[i] = ' ';
|
|
+
|
|
+ /* Nine 8 byte packets to be sent */
|
|
+ /* NOTE: "\x07\x01\0\0\0\0\0\0" or "\x0c\0\0\0\0\0\0\0"
|
|
+ * will clear the VFD */
|
|
+ for (i = 0; i < 9; i++) {
|
|
+ switch (i) {
|
|
+ case 0:
|
|
+ memcpy(context->usb_tx_buf, "\x07\0\0\0\0\0\0\0", 8);
|
|
+ context->usb_tx_buf[1] = (context->vfd_contrast) ?
|
|
+ (0x2B - (context->vfd_contrast - 1) / 250)
|
|
+ : 0x2B;
|
|
+ break;
|
|
+ case 1:
|
|
+ memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8);
|
|
+ break;
|
|
+ case 2:
|
|
+ memcpy(context->usb_tx_buf, "\x0b\x01\0\0\0\0\0\0", 8);
|
|
+ break;
|
|
+ case 3:
|
|
+ memcpy(context->usb_tx_buf, context->tx.data_buf, 8);
|
|
+ break;
|
|
+ case 4:
|
|
+ memcpy(context->usb_tx_buf,
|
|
+ context->tx.data_buf + 8, 8);
|
|
+ break;
|
|
+ case 5:
|
|
+ memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8);
|
|
+ break;
|
|
+ case 6:
|
|
+ memcpy(context->usb_tx_buf, "\x0b\x02\0\0\0\0\0\0", 8);
|
|
+ break;
|
|
+ case 7:
|
|
+ memcpy(context->usb_tx_buf,
|
|
+ context->tx.data_buf + 16, 8);
|
|
+ break;
|
|
+ case 8:
|
|
+ memcpy(context->usb_tx_buf,
|
|
+ context->tx.data_buf + 24, 8);
|
|
+ break;
|
|
+ }
|
|
+ retval = send_packet(context);
|
|
+ if (retval) {
|
|
+
|
|
+ err("%s: send packet failed for packet #%d",
|
|
+ __func__, i);
|
|
+ goto exit;
|
|
+ }
|
|
+ }
|
|
+exit:
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+
|
|
+ return (!retval) ? n_bytes : retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: transmit data
|
|
+ */
|
|
+static void usb_tx_callback(struct urb *urb)
|
|
+{
|
|
+ struct sasem_context *context;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+ context = (struct sasem_context *) urb->context;
|
|
+ if (!context)
|
|
+ return;
|
|
+
|
|
+ context->tx.status = urb->status;
|
|
+
|
|
+ /* notify waiters that write has finished */
|
|
+ atomic_set(&context->tx.busy, 0);
|
|
+ complete(&context->tx.finished);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called by lirc_dev when the application opens /dev/lirc
|
|
+ */
|
|
+static int ir_open(void *data)
|
|
+{
|
|
+ int retval = 0;
|
|
+ struct sasem_context *context;
|
|
+
|
|
+ /* prevent races with disconnect */
|
|
+ mutex_lock(&disconnect_lock);
|
|
+
|
|
+ context = (struct sasem_context *) data;
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ if (context->ir_isopen) {
|
|
+ err("%s: IR port is already open", __func__);
|
|
+ retval = -EBUSY;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ usb_fill_int_urb(context->rx_urb, context->dev,
|
|
+ usb_rcvintpipe(context->dev,
|
|
+ context->rx_endpoint->bEndpointAddress),
|
|
+ context->usb_rx_buf, sizeof(context->usb_rx_buf),
|
|
+ usb_rx_callback, context, context->rx_endpoint->bInterval);
|
|
+
|
|
+ retval = usb_submit_urb(context->rx_urb, GFP_KERNEL);
|
|
+
|
|
+ if (retval)
|
|
+ err("%s: usb_submit_urb failed for ir_open (%d)",
|
|
+ __func__, retval);
|
|
+ else {
|
|
+ context->ir_isopen = 1;
|
|
+ printk(KERN_INFO "IR port opened\n");
|
|
+ }
|
|
+
|
|
+exit:
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+
|
|
+ mutex_unlock(&disconnect_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called by lirc_dev when the application closes /dev/lirc
|
|
+ */
|
|
+static void ir_close(void *data)
|
|
+{
|
|
+ struct sasem_context *context;
|
|
+
|
|
+ context = (struct sasem_context *)data;
|
|
+ if (!context) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ usb_kill_urb(context->rx_urb);
|
|
+ context->ir_isopen = 0;
|
|
+ printk(KERN_INFO "IR port closed\n");
|
|
+
|
|
+ if (!context->dev_present) {
|
|
+
|
|
+ /*
|
|
+ * Device disconnected while IR port was
|
|
+ * still open. Driver was not deregistered
|
|
+ * at disconnect time, so do it now.
|
|
+ */
|
|
+ deregister_from_lirc(context);
|
|
+
|
|
+ if (!context->vfd_isopen) {
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ delete_context(context);
|
|
+ return;
|
|
+ }
|
|
+ /* If VFD port is open, context will be deleted by vfd_close */
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ return;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Process the incoming packet
|
|
+ */
|
|
+static void incoming_packet(struct sasem_context *context,
|
|
+ struct urb *urb)
|
|
+{
|
|
+ int len = urb->actual_length;
|
|
+ unsigned char *buf = urb->transfer_buffer;
|
|
+ long ms;
|
|
+ struct timeval tv;
|
|
+
|
|
+ if (len != 8) {
|
|
+ printk(KERN_WARNING "%s: invalid incoming packet size (%d)\n",
|
|
+ __func__, len);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+#ifdef DEBUG
|
|
+ int i;
|
|
+ for (i = 0; i < 8; ++i)
|
|
+ printk(KERN_INFO "%02x ", buf[i]);
|
|
+ printk(KERN_INFO "\n");
|
|
+#endif
|
|
+
|
|
+ /*
|
|
+ * Lirc could deal with the repeat code, but we really need to block it
|
|
+ * if it arrives too late. Otherwise we could repeat the wrong code.
|
|
+ */
|
|
+
|
|
+ /* get the time since the last button press */
|
|
+ do_gettimeofday(&tv);
|
|
+ ms = (tv.tv_sec - context->presstime.tv_sec) * 1000 +
|
|
+ (tv.tv_usec - context->presstime.tv_usec) / 1000;
|
|
+
|
|
+ if (memcmp(buf, "\x08\0\0\0\0\0\0\0", 8) == 0) {
|
|
+ /*
|
|
+ * the repeat code is being sent, so we copy
|
|
+ * the old code to LIRC
|
|
+ */
|
|
+
|
|
+ /*
|
|
+ * NOTE: Only if the last code was less than 250ms ago
|
|
+ * - no one should be able to push another (undetected) button
|
|
+ * in that time and then get a false repeat of the previous
|
|
+ * press but it is long enough for a genuine repeat
|
|
+ */
|
|
+ if ((ms < 250) && (context->codesaved != 0)) {
|
|
+ memcpy(buf, &context->lastcode, 8);
|
|
+ context->presstime.tv_sec = tv.tv_sec;
|
|
+ context->presstime.tv_usec = tv.tv_usec;
|
|
+ }
|
|
+ } else {
|
|
+ /* save the current valid code for repeats */
|
|
+ memcpy(&context->lastcode, buf, 8);
|
|
+ /*
|
|
+ * set flag to signal a valid code was save;
|
|
+ * just for safety reasons
|
|
+ */
|
|
+ context->codesaved = 1;
|
|
+ context->presstime.tv_sec = tv.tv_sec;
|
|
+ context->presstime.tv_usec = tv.tv_usec;
|
|
+ }
|
|
+
|
|
+ lirc_buffer_write(context->driver->rbuf, buf);
|
|
+ wake_up(&context->driver->rbuf->wait_poll);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: receive data
|
|
+ */
|
|
+static void usb_rx_callback(struct urb *urb)
|
|
+{
|
|
+ struct sasem_context *context;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+ context = (struct sasem_context *) urb->context;
|
|
+ if (!context)
|
|
+ return;
|
|
+
|
|
+ switch (urb->status) {
|
|
+
|
|
+ case -ENOENT: /* usbcore unlink successful! */
|
|
+ return;
|
|
+
|
|
+ case 0:
|
|
+ if (context->ir_isopen)
|
|
+ incoming_packet(context, urb);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ printk(KERN_WARNING "%s: status (%d): ignored",
|
|
+ __func__, urb->status);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ usb_submit_urb(context->rx_urb, GFP_ATOMIC);
|
|
+ return;
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: Probe
|
|
+ */
|
|
+static int sasem_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id)
|
|
+{
|
|
+ struct usb_device *dev = NULL;
|
|
+ struct usb_host_interface *iface_desc = NULL;
|
|
+ struct usb_endpoint_descriptor *rx_endpoint = NULL;
|
|
+ struct usb_endpoint_descriptor *tx_endpoint = NULL;
|
|
+ struct urb *rx_urb = NULL;
|
|
+ struct urb *tx_urb = NULL;
|
|
+ struct lirc_driver *driver = NULL;
|
|
+ struct lirc_buffer *rbuf = NULL;
|
|
+ int lirc_minor = 0;
|
|
+ int num_endpoints;
|
|
+ int retval = 0;
|
|
+ int vfd_ep_found;
|
|
+ int ir_ep_found;
|
|
+ int alloc_status;
|
|
+ struct sasem_context *context = NULL;
|
|
+ int i;
|
|
+
|
|
+ printk(KERN_INFO "%s: found Sasem device\n", __func__);
|
|
+
|
|
+
|
|
+ dev = usb_get_dev(interface_to_usbdev(interface));
|
|
+ iface_desc = interface->cur_altsetting;
|
|
+ num_endpoints = iface_desc->desc.bNumEndpoints;
|
|
+
|
|
+ /*
|
|
+ * Scan the endpoint list and set:
|
|
+ * first input endpoint = IR endpoint
|
|
+ * first output endpoint = VFD endpoint
|
|
+ */
|
|
+
|
|
+ ir_ep_found = 0;
|
|
+ vfd_ep_found = 0;
|
|
+
|
|
+ for (i = 0; i < num_endpoints && !(ir_ep_found && vfd_ep_found); ++i) {
|
|
+
|
|
+ struct usb_endpoint_descriptor *ep;
|
|
+ int ep_dir;
|
|
+ int ep_type;
|
|
+ ep = &iface_desc->endpoint [i].desc;
|
|
+ ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
|
|
+ ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
|
|
+
|
|
+ if (!ir_ep_found &&
|
|
+ ep_dir == USB_DIR_IN &&
|
|
+ ep_type == USB_ENDPOINT_XFER_INT) {
|
|
+
|
|
+ rx_endpoint = ep;
|
|
+ ir_ep_found = 1;
|
|
+ if (debug)
|
|
+ printk(KERN_INFO "%s: found IR endpoint\n",
|
|
+ __func__);
|
|
+
|
|
+ } else if (!vfd_ep_found &&
|
|
+ ep_dir == USB_DIR_OUT &&
|
|
+ ep_type == USB_ENDPOINT_XFER_INT) {
|
|
+
|
|
+ tx_endpoint = ep;
|
|
+ vfd_ep_found = 1;
|
|
+ if (debug)
|
|
+ printk(KERN_INFO "%s: found VFD endpoint\n",
|
|
+ __func__);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Input endpoint is mandatory */
|
|
+ if (!ir_ep_found) {
|
|
+
|
|
+ err("%s: no valid input (IR) endpoint found.", __func__);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (!vfd_ep_found)
|
|
+ printk(KERN_INFO "%s: no valid output (VFD) endpoint found.\n",
|
|
+ __func__);
|
|
+
|
|
+
|
|
+ /* Allocate memory */
|
|
+ alloc_status = 0;
|
|
+
|
|
+ context = kzalloc(sizeof(struct sasem_context), GFP_KERNEL);
|
|
+ if (!context) {
|
|
+ err("%s: kzalloc failed for context", __func__);
|
|
+ alloc_status = 1;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
|
|
+ if (!driver) {
|
|
+ err("%s: kzalloc failed for lirc_driver", __func__);
|
|
+ alloc_status = 2;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
|
|
+ if (!rbuf) {
|
|
+ err("%s: kmalloc failed for lirc_buffer", __func__);
|
|
+ alloc_status = 3;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) {
|
|
+ err("%s: lirc_buffer_init failed", __func__);
|
|
+ alloc_status = 4;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ rx_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!rx_urb) {
|
|
+ err("%s: usb_alloc_urb failed for IR urb", __func__);
|
|
+ alloc_status = 5;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ if (vfd_ep_found) {
|
|
+ tx_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!tx_urb) {
|
|
+ err("%s: usb_alloc_urb failed for VFD urb",
|
|
+ __func__);
|
|
+ alloc_status = 6;
|
|
+ goto alloc_status_switch;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mutex_init(&context->ctx_lock);
|
|
+
|
|
+ strcpy(driver->name, MOD_NAME);
|
|
+ driver->minor = -1;
|
|
+ driver->code_length = 64;
|
|
+ driver->sample_rate = 0;
|
|
+ driver->features = LIRC_CAN_REC_LIRCCODE;
|
|
+ driver->data = context;
|
|
+ driver->rbuf = rbuf;
|
|
+ driver->set_use_inc = ir_open;
|
|
+ driver->set_use_dec = ir_close;
|
|
+ driver->dev = &interface->dev;
|
|
+ driver->owner = THIS_MODULE;
|
|
+
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ lirc_minor = lirc_register_driver(driver);
|
|
+ if (lirc_minor < 0) {
|
|
+ err("%s: lirc_register_driver failed", __func__);
|
|
+ alloc_status = 7;
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+ } else
|
|
+ printk(KERN_INFO "%s: Registered Sasem driver (minor:%d)\n",
|
|
+ __func__, lirc_minor);
|
|
+
|
|
+alloc_status_switch:
|
|
+
|
|
+ switch (alloc_status) {
|
|
+
|
|
+ case 7:
|
|
+ if (vfd_ep_found)
|
|
+ usb_free_urb(tx_urb);
|
|
+ case 6:
|
|
+ usb_free_urb(rx_urb);
|
|
+ case 5:
|
|
+ lirc_buffer_free(rbuf);
|
|
+ case 4:
|
|
+ kfree(rbuf);
|
|
+ case 3:
|
|
+ kfree(driver);
|
|
+ case 2:
|
|
+ kfree(context);
|
|
+ context = NULL;
|
|
+ case 1:
|
|
+ retval = -ENOMEM;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ /* Needed while unregistering! */
|
|
+ driver->minor = lirc_minor;
|
|
+
|
|
+ context->dev = dev;
|
|
+ context->dev_present = 1;
|
|
+ context->rx_endpoint = rx_endpoint;
|
|
+ context->rx_urb = rx_urb;
|
|
+ if (vfd_ep_found) {
|
|
+ context->tx_endpoint = tx_endpoint;
|
|
+ context->tx_urb = tx_urb;
|
|
+ context->vfd_contrast = 1000; /* range 0 - 1000 */
|
|
+ }
|
|
+ context->driver = driver;
|
|
+
|
|
+ usb_set_intfdata(interface, context);
|
|
+
|
|
+ if (vfd_ep_found) {
|
|
+
|
|
+ if (debug)
|
|
+ printk(KERN_INFO "Registering VFD with sysfs\n");
|
|
+ if (usb_register_dev(interface, &sasem_class))
|
|
+ /* Not a fatal error, so ignore */
|
|
+ printk(KERN_INFO "%s: could not get a minor number "
|
|
+ "for VFD\n", __func__);
|
|
+ }
|
|
+
|
|
+ printk(KERN_INFO "%s: Sasem device on usb<%d:%d> initialized\n",
|
|
+ __func__, dev->bus->busnum, dev->devnum);
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+exit:
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: disonnect
|
|
+ */
|
|
+static void sasem_disconnect(struct usb_interface *interface)
|
|
+{
|
|
+ struct sasem_context *context;
|
|
+
|
|
+ /* prevent races with ir_open()/vfd_open() */
|
|
+ mutex_lock(&disconnect_lock);
|
|
+
|
|
+ context = usb_get_intfdata(interface);
|
|
+ mutex_lock(&context->ctx_lock);
|
|
+
|
|
+ printk(KERN_INFO "%s: Sasem device disconnected\n", __func__);
|
|
+
|
|
+ usb_set_intfdata(interface, NULL);
|
|
+ context->dev_present = 0;
|
|
+
|
|
+ /* Stop reception */
|
|
+ usb_kill_urb(context->rx_urb);
|
|
+
|
|
+ /* Abort ongoing write */
|
|
+ if (atomic_read(&context->tx.busy)) {
|
|
+
|
|
+ usb_kill_urb(context->tx_urb);
|
|
+ wait_for_completion(&context->tx.finished);
|
|
+ }
|
|
+
|
|
+ /* De-register from lirc_dev if IR port is not open */
|
|
+ if (!context->ir_isopen)
|
|
+ deregister_from_lirc(context);
|
|
+
|
|
+ usb_deregister_dev(interface, &sasem_class);
|
|
+
|
|
+ mutex_unlock(&context->ctx_lock);
|
|
+
|
|
+ if (!context->ir_isopen && !context->vfd_isopen)
|
|
+ delete_context(context);
|
|
+
|
|
+ mutex_unlock(&disconnect_lock);
|
|
+}
|
|
+
|
|
+static int __init sasem_init(void)
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ printk(KERN_INFO MOD_DESC ", v" MOD_VERSION "\n");
|
|
+ printk(KERN_INFO MOD_AUTHOR "\n");
|
|
+
|
|
+ rc = usb_register(&sasem_driver);
|
|
+ if (rc < 0) {
|
|
+ err("%s: usb register failed (%d)", __func__, rc);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit sasem_exit(void)
|
|
+{
|
|
+ usb_deregister(&sasem_driver);
|
|
+ printk(KERN_INFO "module removed. Goodbye!\n");
|
|
+}
|
|
+
|
|
+
|
|
+module_init(sasem_init);
|
|
+module_exit(sasem_exit);
|
|
diff --git a/drivers/input/lirc/lirc_serial.c b/drivers/input/lirc/lirc_serial.c
|
|
new file mode 100644
|
|
index 0000000..f4fcc37
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_serial.c
|
|
@@ -0,0 +1,1317 @@
|
|
+/*
|
|
+ * lirc_serial.c
|
|
+ *
|
|
+ * lirc_serial - Device driver that records pulse- and pause-lengths
|
|
+ * (space-lengths) between DDCD event on a serial port.
|
|
+ *
|
|
+ * Copyright (C) 1996,97 Ralph Metzler <rjkm@thp.uni-koeln.de>
|
|
+ * Copyright (C) 1998 Trent Piepho <xyzzy@u.washington.edu>
|
|
+ * Copyright (C) 1998 Ben Pfaff <blp@gnu.org>
|
|
+ * Copyright (C) 1999 Christoph Bartelmus <lirc@bartelmus.de>
|
|
+ * Copyright (C) 2007 Andrei Tanas <andrei@tanas.ca> (suspend/resume support)
|
|
+ * 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
|
|
+ *
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * Steve's changes to improve transmission fidelity:
|
|
+ * - for systems with the rdtsc instruction and the clock counter, a
|
|
+ * send_pule that times the pulses directly using the counter.
|
|
+ * This means that the LIRC_SERIAL_TRANSMITTER_LATENCY fudge is
|
|
+ * not needed. Measurement shows very stable waveform, even where
|
|
+ * PCI activity slows the access to the UART, which trips up other
|
|
+ * versions.
|
|
+ * - For other system, non-integer-microsecond pulse/space lengths,
|
|
+ * done using fixed point binary. So, much more accurate carrier
|
|
+ * frequency.
|
|
+ * - fine tuned transmitter latency, taking advantage of fractional
|
|
+ * microseconds in previous change
|
|
+ * - Fixed bug in the way transmitter latency was accounted for by
|
|
+ * tuning the pulse lengths down - the send_pulse routine ignored
|
|
+ * this overhead as it timed the overall pulse length - so the
|
|
+ * pulse frequency was right but overall pulse length was too
|
|
+ * long. Fixed by accounting for latency on each pulse/space
|
|
+ * iteration.
|
|
+ *
|
|
+ * Steve Davies <steve@daviesfam.org> July 2001
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/signal.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/serial_reg.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/wait.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/platform_device.h>
|
|
+
|
|
+#include <asm/system.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/fcntl.h>
|
|
+#include <linux/spinlock.h>
|
|
+
|
|
+#ifdef CONFIG_LIRC_SERIAL_NSLU2
|
|
+#include <asm/hardware.h>
|
|
+#endif
|
|
+/* From Intel IXP42X Developer's Manual (#252480-005): */
|
|
+/* ftp://download.intel.com/design/network/manuals/25248005.pdf */
|
|
+#define UART_IE_IXP42X_UUE 0x40 /* IXP42X UART Unit enable */
|
|
+#define UART_IE_IXP42X_RTOIE 0x10 /* IXP42X Receiver Data Timeout int.enable */
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+#define LIRC_DRIVER_NAME "lirc_serial"
|
|
+
|
|
+struct lirc_serial {
|
|
+ int signal_pin;
|
|
+ int signal_pin_change;
|
|
+ u8 on;
|
|
+ u8 off;
|
|
+ long (*send_pulse)(unsigned long length);
|
|
+ void (*send_space)(long length);
|
|
+ int features;
|
|
+ spinlock_t lock;
|
|
+};
|
|
+
|
|
+#define LIRC_HOMEBREW 0
|
|
+#define LIRC_IRDEO 1
|
|
+#define LIRC_IRDEO_REMOTE 2
|
|
+#define LIRC_ANIMAX 3
|
|
+#define LIRC_IGOR 4
|
|
+#define LIRC_NSLU2 5
|
|
+
|
|
+/*** module parameters ***/
|
|
+static int type;
|
|
+static int io;
|
|
+static int irq;
|
|
+static int iommap;
|
|
+static int ioshift;
|
|
+static int softcarrier = 1;
|
|
+static int share_irq;
|
|
+static int debug;
|
|
+static int sense = -1; /* -1 = auto, 0 = active high, 1 = active low */
|
|
+static int txsense; /* 0 = active high, 1 = active low */
|
|
+
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \
|
|
+ fmt, ## args); \
|
|
+ } while (0)
|
|
+
|
|
+/* forward declarations */
|
|
+static long send_pulse_irdeo(unsigned long length);
|
|
+static long send_pulse_homebrew(unsigned long length);
|
|
+static void send_space_irdeo(long length);
|
|
+static void send_space_homebrew(long length);
|
|
+
|
|
+static struct lirc_serial hardware[] = {
|
|
+ [LIRC_HOMEBREW] = {
|
|
+ .signal_pin = UART_MSR_DCD,
|
|
+ .signal_pin_change = UART_MSR_DDCD,
|
|
+ .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR),
|
|
+ .off = (UART_MCR_RTS | UART_MCR_OUT2),
|
|
+ .send_pulse = send_pulse_homebrew,
|
|
+ .send_space = send_space_homebrew,
|
|
+#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER
|
|
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
|
|
+ LIRC_CAN_SET_SEND_CARRIER |
|
|
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
|
|
+#else
|
|
+ .features = LIRC_CAN_REC_MODE2
|
|
+#endif
|
|
+ },
|
|
+
|
|
+ [LIRC_IRDEO] = {
|
|
+ .signal_pin = UART_MSR_DSR,
|
|
+ .signal_pin_change = UART_MSR_DDSR,
|
|
+ .on = UART_MCR_OUT2,
|
|
+ .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
|
|
+ .send_pulse = send_pulse_irdeo,
|
|
+ .send_space = send_space_irdeo,
|
|
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
|
|
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
|
|
+ },
|
|
+
|
|
+ [LIRC_IRDEO_REMOTE] = {
|
|
+ .signal_pin = UART_MSR_DSR,
|
|
+ .signal_pin_change = UART_MSR_DDSR,
|
|
+ .on = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
|
|
+ .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
|
|
+ .send_pulse = send_pulse_irdeo,
|
|
+ .send_space = send_space_irdeo,
|
|
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
|
|
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
|
|
+ },
|
|
+
|
|
+ [LIRC_ANIMAX] = {
|
|
+ .signal_pin = UART_MSR_DCD,
|
|
+ .signal_pin_change = UART_MSR_DDCD,
|
|
+ .on = 0,
|
|
+ .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
|
|
+ .send_pulse = NULL,
|
|
+ .send_space = NULL,
|
|
+ .features = LIRC_CAN_REC_MODE2
|
|
+ },
|
|
+
|
|
+ [LIRC_IGOR] = {
|
|
+ .signal_pin = UART_MSR_DSR,
|
|
+ .signal_pin_change = UART_MSR_DDSR,
|
|
+ .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR),
|
|
+ .off = (UART_MCR_RTS | UART_MCR_OUT2),
|
|
+ .send_pulse = send_pulse_homebrew,
|
|
+ .send_space = send_space_homebrew,
|
|
+#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER
|
|
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
|
|
+ LIRC_CAN_SET_SEND_CARRIER |
|
|
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
|
|
+#else
|
|
+ .features = LIRC_CAN_REC_MODE2
|
|
+#endif
|
|
+ },
|
|
+
|
|
+#ifdef CONFIG_LIRC_SERIAL_NSLU2
|
|
+ /*
|
|
+ * Modified Linksys Network Storage Link USB 2.0 (NSLU2):
|
|
+ * We receive on CTS of the 2nd serial port (R142,LHS), we
|
|
+ * transmit with a IR diode between GPIO[1] (green status LED),
|
|
+ * and ground (Matthias Goebl <matthias.goebl@goebl.net>).
|
|
+ * See also http://www.nslu2-linux.org for this device
|
|
+ */
|
|
+ [LIRC_NSLU2] = {
|
|
+ .signal_pin = UART_MSR_CTS,
|
|
+ .signal_pin_change = UART_MSR_DCTS,
|
|
+ .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR),
|
|
+ .off = (UART_MCR_RTS | UART_MCR_OUT2),
|
|
+ .send_pulse = send_pulse_homebrew,
|
|
+ .send_space = send_space_homebrew,
|
|
+#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER
|
|
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
|
|
+ LIRC_CAN_SET_SEND_CARRIER |
|
|
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
|
|
+#else
|
|
+ .features = LIRC_CAN_REC_MODE2
|
|
+#endif
|
|
+ },
|
|
+#endif
|
|
+
|
|
+};
|
|
+
|
|
+#define RS_ISR_PASS_LIMIT 256
|
|
+
|
|
+/*
|
|
+ * A long pulse code from a remote might take up to 300 bytes. The
|
|
+ * daemon should read the bytes as soon as they are generated, so take
|
|
+ * the number of keys you think you can push before the daemon runs
|
|
+ * and multiply by 300. The driver will warn you if you overrun this
|
|
+ * buffer. If you have a slow computer or non-busmastering IDE disks,
|
|
+ * maybe you will need to increase this.
|
|
+ */
|
|
+
|
|
+/* This MUST be a power of two! It has to be larger than 1 as well. */
|
|
+
|
|
+#define RBUF_LEN 256
|
|
+#define WBUF_LEN 256
|
|
+
|
|
+static struct timeval lasttv = {0, 0};
|
|
+
|
|
+static struct lirc_buffer rbuf;
|
|
+
|
|
+static int wbuf[WBUF_LEN];
|
|
+
|
|
+static unsigned int freq = 38000;
|
|
+static unsigned int duty_cycle = 50;
|
|
+
|
|
+/* Initialized in init_timing_params() */
|
|
+static unsigned long period;
|
|
+static unsigned long pulse_width;
|
|
+static unsigned long space_width;
|
|
+
|
|
+#if defined(__i386__)
|
|
+/*
|
|
+ * From:
|
|
+ * Linux I/O port programming mini-HOWTO
|
|
+ * Author: Riku Saikkonen <Riku.Saikkonen@hut.fi>
|
|
+ * v, 28 December 1997
|
|
+ *
|
|
+ * [...]
|
|
+ * Actually, a port I/O instruction on most ports in the 0-0x3ff range
|
|
+ * takes almost exactly 1 microsecond, so if you're, for example, using
|
|
+ * the parallel port directly, just do additional inb()s from that port
|
|
+ * to delay.
|
|
+ * [...]
|
|
+ */
|
|
+/* transmitter latency 1.5625us 0x1.90 - this figure arrived at from
|
|
+ * comment above plus trimming to match actual measured frequency.
|
|
+ * This will be sensitive to cpu speed, though hopefully most of the 1.5us
|
|
+ * is spent in the uart access. Still - for reference test machine was a
|
|
+ * 1.13GHz Athlon system - Steve
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * changed from 400 to 450 as this works better on slower machines;
|
|
+ * faster machines will use the rdtsc code anyway
|
|
+ */
|
|
+#define LIRC_SERIAL_TRANSMITTER_LATENCY 450
|
|
+
|
|
+#else
|
|
+
|
|
+/* does anybody have information on other platforms ? */
|
|
+/* 256 = 1<<8 */
|
|
+#define LIRC_SERIAL_TRANSMITTER_LATENCY 256
|
|
+
|
|
+#endif /* __i386__ */
|
|
+/*
|
|
+ * FIXME: should we be using hrtimers instead of this
|
|
+ * LIRC_SERIAL_TRANSMITTER_LATENCY nonsense?
|
|
+ */
|
|
+
|
|
+/* fetch serial input packet (1 byte) from register offset */
|
|
+static u8 sinp(int offset)
|
|
+{
|
|
+ if (iommap != 0)
|
|
+ /* the register is memory-mapped */
|
|
+ offset <<= ioshift;
|
|
+
|
|
+ return inb(io + offset);
|
|
+}
|
|
+
|
|
+/* write serial output packet (1 byte) of value to register offset */
|
|
+static void soutp(int offset, u8 value)
|
|
+{
|
|
+ if (iommap != 0)
|
|
+ /* the register is memory-mapped */
|
|
+ offset <<= ioshift;
|
|
+
|
|
+ outb(value, io + offset);
|
|
+}
|
|
+
|
|
+static void on(void)
|
|
+{
|
|
+#ifdef CONFIG_LIRC_SERIAL_NSLU2
|
|
+ /*
|
|
+ * On NSLU2, we put the transmit diode between the output of the green
|
|
+ * status LED and ground
|
|
+ */
|
|
+ if (type == LIRC_NSLU2) {
|
|
+ gpio_line_set(NSLU2_LED_GRN, IXP4XX_GPIO_LOW);
|
|
+ return;
|
|
+ }
|
|
+#endif
|
|
+ if (txsense)
|
|
+ soutp(UART_MCR, hardware[type].off);
|
|
+ else
|
|
+ soutp(UART_MCR, hardware[type].on);
|
|
+}
|
|
+
|
|
+static void off(void)
|
|
+{
|
|
+#ifdef CONFIG_LIRC_SERIAL_NSLU2
|
|
+ if (type == LIRC_NSLU2) {
|
|
+ gpio_line_set(NSLU2_LED_GRN, IXP4XX_GPIO_HIGH);
|
|
+ return;
|
|
+ }
|
|
+#endif
|
|
+ if (txsense)
|
|
+ soutp(UART_MCR, hardware[type].on);
|
|
+ else
|
|
+ soutp(UART_MCR, hardware[type].off);
|
|
+}
|
|
+
|
|
+#ifndef MAX_UDELAY_MS
|
|
+#define MAX_UDELAY_US 5000
|
|
+#else
|
|
+#define MAX_UDELAY_US (MAX_UDELAY_MS*1000)
|
|
+#endif
|
|
+
|
|
+static void safe_udelay(unsigned long usecs)
|
|
+{
|
|
+ while (usecs > MAX_UDELAY_US) {
|
|
+ udelay(MAX_UDELAY_US);
|
|
+ usecs -= MAX_UDELAY_US;
|
|
+ }
|
|
+ udelay(usecs);
|
|
+}
|
|
+
|
|
+#ifdef USE_RDTSC
|
|
+/*
|
|
+ * This is an overflow/precision juggle, complicated in that we can't
|
|
+ * do long long divide in the kernel
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * When we use the rdtsc instruction to measure clocks, we keep the
|
|
+ * pulse and space widths as clock cycles. As this is CPU speed
|
|
+ * dependent, the widths must be calculated in init_port and ioctl
|
|
+ * time
|
|
+ */
|
|
+
|
|
+/* So send_pulse can quickly convert microseconds to clocks */
|
|
+static unsigned long conv_us_to_clocks;
|
|
+
|
|
+static int init_timing_params(unsigned int new_duty_cycle,
|
|
+ unsigned int new_freq)
|
|
+{
|
|
+ unsigned long long loops_per_sec, work;
|
|
+
|
|
+ duty_cycle = new_duty_cycle;
|
|
+ freq = new_freq;
|
|
+
|
|
+ loops_per_sec = current_cpu_data.loops_per_jiffy;
|
|
+ loops_per_sec *= HZ;
|
|
+
|
|
+ /* How many clocks in a microsecond?, avoiding long long divide */
|
|
+ work = loops_per_sec;
|
|
+ work *= 4295; /* 4295 = 2^32 / 1e6 */
|
|
+ conv_us_to_clocks = (work >> 32);
|
|
+
|
|
+ /*
|
|
+ * Carrier period in clocks, approach good up to 32GHz clock,
|
|
+ * gets carrier frequency within 8Hz
|
|
+ */
|
|
+ period = loops_per_sec >> 3;
|
|
+ period /= (freq >> 3);
|
|
+
|
|
+ /* Derive pulse and space from the period */
|
|
+ pulse_width = period * duty_cycle / 100;
|
|
+ space_width = period - pulse_width;
|
|
+ dprintk("in init_timing_params, freq=%d, duty_cycle=%d, "
|
|
+ "clk/jiffy=%ld, pulse=%ld, space=%ld, "
|
|
+ "conv_us_to_clocks=%ld\n",
|
|
+ freq, duty_cycle, current_cpu_data.loops_per_jiffy,
|
|
+ pulse_width, space_width, conv_us_to_clocks);
|
|
+ return 0;
|
|
+}
|
|
+#else /* ! USE_RDTSC */
|
|
+static int init_timing_params(unsigned int new_duty_cycle,
|
|
+ unsigned int new_freq)
|
|
+{
|
|
+/*
|
|
+ * period, pulse/space width are kept with 8 binary places -
|
|
+ * IE multiplied by 256.
|
|
+ */
|
|
+ if (256 * 1000000L / new_freq * new_duty_cycle / 100 <=
|
|
+ LIRC_SERIAL_TRANSMITTER_LATENCY)
|
|
+ return -EINVAL;
|
|
+ if (256 * 1000000L / new_freq * (100 - new_duty_cycle) / 100 <=
|
|
+ LIRC_SERIAL_TRANSMITTER_LATENCY)
|
|
+ return -EINVAL;
|
|
+ duty_cycle = new_duty_cycle;
|
|
+ freq = new_freq;
|
|
+ period = 256 * 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;
|
|
+}
|
|
+#endif /* USE_RDTSC */
|
|
+
|
|
+
|
|
+/* return value: space length delta */
|
|
+
|
|
+static long send_pulse_irdeo(unsigned long length)
|
|
+{
|
|
+ long rawbits, ret;
|
|
+ int i;
|
|
+ unsigned char output;
|
|
+ unsigned char chunk, shifted;
|
|
+
|
|
+ /* how many bits have to be sent ? */
|
|
+ rawbits = length * 1152 / 10000;
|
|
+ if (duty_cycle > 50)
|
|
+ chunk = 3;
|
|
+ else
|
|
+ chunk = 1;
|
|
+ for (i = 0, output = 0x7f; rawbits > 0; rawbits -= 3) {
|
|
+ shifted = chunk << (i * 3);
|
|
+ shifted >>= 1;
|
|
+ output &= (~shifted);
|
|
+ i++;
|
|
+ if (i == 3) {
|
|
+ soutp(UART_TX, output);
|
|
+ while (!(sinp(UART_LSR) & UART_LSR_THRE))
|
|
+ ;
|
|
+ output = 0x7f;
|
|
+ i = 0;
|
|
+ }
|
|
+ }
|
|
+ if (i != 0) {
|
|
+ soutp(UART_TX, output);
|
|
+ while (!(sinp(UART_LSR) & UART_LSR_TEMT))
|
|
+ ;
|
|
+ }
|
|
+
|
|
+ if (i == 0)
|
|
+ ret = (-rawbits) * 10000 / 1152;
|
|
+ else
|
|
+ ret = (3 - i) * 3 * 10000 / 1152 + (-rawbits) * 10000 / 1152;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+#ifdef USE_RDTSC
|
|
+/* Version that uses Pentium rdtsc instruction to measure clocks */
|
|
+
|
|
+/*
|
|
+ * This version does sub-microsecond timing using rdtsc instruction,
|
|
+ * and does away with the fudged LIRC_SERIAL_TRANSMITTER_LATENCY
|
|
+ * Implicitly i586 architecture... - Steve
|
|
+ */
|
|
+
|
|
+static long send_pulse_homebrew_softcarrier(unsigned long length)
|
|
+{
|
|
+ int flag;
|
|
+ unsigned long target, start, now;
|
|
+
|
|
+ /* Get going quick as we can */
|
|
+ rdtscl(start);
|
|
+ on();
|
|
+ /* Convert length from microseconds to clocks */
|
|
+ length *= conv_us_to_clocks;
|
|
+ /* And loop till time is up - flipping at right intervals */
|
|
+ now = start;
|
|
+ target = pulse_width;
|
|
+ flag = 1;
|
|
+ /*
|
|
+ * FIXME: This looks like a hard busy wait, without even an occasional,
|
|
+ * polite, cpu_relax() call. There's got to be a better way?
|
|
+ *
|
|
+ * The i2c code has the result of a lot of bit-banging work, I wonder if
|
|
+ * there's something there which could be helpful here.
|
|
+ */
|
|
+ while ((now - start) < length) {
|
|
+ /* Delay till flip time */
|
|
+ do {
|
|
+ rdtscl(now);
|
|
+ } while ((now - start) < target);
|
|
+
|
|
+ /* flip */
|
|
+ if (flag) {
|
|
+ rdtscl(now);
|
|
+ off();
|
|
+ target += space_width;
|
|
+ } else {
|
|
+ rdtscl(now); on();
|
|
+ target += pulse_width;
|
|
+ }
|
|
+ flag = !flag;
|
|
+ }
|
|
+ rdtscl(now);
|
|
+ return ((now - start) - length) / conv_us_to_clocks;
|
|
+}
|
|
+#else /* ! USE_RDTSC */
|
|
+/* Version using udelay() */
|
|
+
|
|
+/*
|
|
+ * here we use fixed point arithmetic, with 8
|
|
+ * fractional bits. that gets us within 0.1% or so of the right average
|
|
+ * frequency, albeit with some jitter in pulse length - Steve
|
|
+ */
|
|
+
|
|
+/* To match 8 fractional bits used for pulse/space length */
|
|
+
|
|
+static long send_pulse_homebrew_softcarrier(unsigned long length)
|
|
+{
|
|
+ int flag;
|
|
+ unsigned long actual, target, d;
|
|
+ length <<= 8;
|
|
+
|
|
+ actual = 0; target = 0; flag = 0;
|
|
+ while (actual < length) {
|
|
+ if (flag) {
|
|
+ off();
|
|
+ target += space_width;
|
|
+ } else {
|
|
+ on();
|
|
+ target += pulse_width;
|
|
+ }
|
|
+ d = (target - actual -
|
|
+ LIRC_SERIAL_TRANSMITTER_LATENCY + 128) >> 8;
|
|
+ /*
|
|
+ * Note - we've checked in ioctl that the pulse/space
|
|
+ * widths are big enough so that d is > 0
|
|
+ */
|
|
+ udelay(d);
|
|
+ actual += (d << 8) + LIRC_SERIAL_TRANSMITTER_LATENCY;
|
|
+ flag = !flag;
|
|
+ }
|
|
+ return (actual-length) >> 8;
|
|
+}
|
|
+#endif /* USE_RDTSC */
|
|
+
|
|
+static long send_pulse_homebrew(unsigned long length)
|
|
+{
|
|
+ if (length <= 0)
|
|
+ return 0;
|
|
+
|
|
+ if (softcarrier)
|
|
+ return send_pulse_homebrew_softcarrier(length);
|
|
+ else {
|
|
+ on();
|
|
+ safe_udelay(length);
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void send_space_irdeo(long length)
|
|
+{
|
|
+ if (length <= 0)
|
|
+ return;
|
|
+
|
|
+ safe_udelay(length);
|
|
+}
|
|
+
|
|
+static void send_space_homebrew(long length)
|
|
+{
|
|
+ off();
|
|
+ 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 timeval tv;
|
|
+ int counter, dcd;
|
|
+ u8 status;
|
|
+ long deltv;
|
|
+ int data;
|
|
+ static int last_dcd = -1;
|
|
+
|
|
+ if ((sinp(UART_IIR) & UART_IIR_NO_INT)) {
|
|
+ /* not our interrupt */
|
|
+ return IRQ_NONE;
|
|
+ }
|
|
+
|
|
+ counter = 0;
|
|
+ do {
|
|
+ counter++;
|
|
+ status = sinp(UART_MSR);
|
|
+ if (counter > RS_ISR_PASS_LIMIT) {
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME ": AIEEEE: "
|
|
+ "We're caught!\n");
|
|
+ break;
|
|
+ }
|
|
+ if ((status & hardware[type].signal_pin_change)
|
|
+ && sense != -1) {
|
|
+ /* get current time */
|
|
+ do_gettimeofday(&tv);
|
|
+
|
|
+ /* New mode, written by Trent Piepho
|
|
+ <xyzzy@u.washington.edu>. */
|
|
+
|
|
+ /*
|
|
+ * The old format was not very portable.
|
|
+ * We now use an int to pass pulses
|
|
+ * and spaces to user space.
|
|
+ *
|
|
+ * If PULSE_BIT is set a pulse has been
|
|
+ * received, otherwise a space has been
|
|
+ * received. The driver needs to know if your
|
|
+ * receiver is active high or active low, or
|
|
+ * the space/pulse sense could be
|
|
+ * inverted. The bits denoted by PULSE_MASK are
|
|
+ * the length in microseconds. Lengths greater
|
|
+ * than or equal to 16 seconds are clamped to
|
|
+ * PULSE_MASK. All other bits are unused.
|
|
+ * This is a much simpler interface for user
|
|
+ * programs, as well as eliminating "out of
|
|
+ * phase" errors with space/pulse
|
|
+ * autodetection.
|
|
+ */
|
|
+
|
|
+ /* calc time since last interrupt in microseconds */
|
|
+ dcd = (status & hardware[type].signal_pin) ? 1 : 0;
|
|
+
|
|
+ if (dcd == last_dcd) {
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME
|
|
+ ": ignoring spike: %d %d %lx %lx %lx %lx\n",
|
|
+ dcd, sense,
|
|
+ tv.tv_sec, lasttv.tv_sec,
|
|
+ tv.tv_usec, lasttv.tv_usec);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ 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",
|
|
+ dcd, 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 (!(dcd^sense)) {
|
|
+ /* sanity check */
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME
|
|
+ ": AIEEEE: "
|
|
+ "%d %d %lx %lx %lx %lx\n",
|
|
+ dcd, 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(dcd^sense ? data : (data|PULSE_BIT));
|
|
+ lasttv = tv;
|
|
+ last_dcd = dcd;
|
|
+ wake_up_interruptible(&rbuf.wait_poll);
|
|
+ }
|
|
+ } while (!(sinp(UART_IIR) & UART_IIR_NO_INT)); /* still pending ? */
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+
|
|
+static int hardware_init_port(void)
|
|
+{
|
|
+ u8 scratch, scratch2, scratch3;
|
|
+
|
|
+ /*
|
|
+ * This is a simple port existence test, borrowed from the autoconfig
|
|
+ * function in drivers/serial/8250.c
|
|
+ */
|
|
+ scratch = sinp(UART_IER);
|
|
+ soutp(UART_IER, 0);
|
|
+#ifdef __i386__
|
|
+ outb(0xff, 0x080);
|
|
+#endif
|
|
+ scratch2 = sinp(UART_IER) & 0x0f;
|
|
+ soutp(UART_IER, 0x0f);
|
|
+#ifdef __i386__
|
|
+ outb(0x00, 0x080);
|
|
+#endif
|
|
+ scratch3 = sinp(UART_IER) & 0x0f;
|
|
+ soutp(UART_IER, scratch);
|
|
+ if (scratch2 != 0 || scratch3 != 0x0f) {
|
|
+ /* we fail, there's nothing here */
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME ": port existence test "
|
|
+ "failed, cannot continue\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+
|
|
+
|
|
+ /* Set DLAB 0. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
|
|
+
|
|
+ /* First of all, disable all interrupts */
|
|
+ soutp(UART_IER, sinp(UART_IER) &
|
|
+ (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI)));
|
|
+
|
|
+ /* Clear registers. */
|
|
+ sinp(UART_LSR);
|
|
+ sinp(UART_RX);
|
|
+ sinp(UART_IIR);
|
|
+ sinp(UART_MSR);
|
|
+
|
|
+#ifdef CONFIG_LIRC_SERIAL_NSLU2
|
|
+ if (type == LIRC_NSLU2) {
|
|
+ /* Setup NSLU2 UART */
|
|
+
|
|
+ /* Enable UART */
|
|
+ soutp(UART_IER, sinp(UART_IER) | UART_IE_IXP42X_UUE);
|
|
+ /* Disable Receiver data Time out interrupt */
|
|
+ soutp(UART_IER, sinp(UART_IER) & ~UART_IE_IXP42X_RTOIE);
|
|
+ /* set out2 = interrupt unmask; off() doesn't set MCR
|
|
+ on NSLU2 */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ /* Set line for power source */
|
|
+ off();
|
|
+
|
|
+ /* Clear registers again to be sure. */
|
|
+ sinp(UART_LSR);
|
|
+ sinp(UART_RX);
|
|
+ sinp(UART_IIR);
|
|
+ sinp(UART_MSR);
|
|
+
|
|
+ switch (type) {
|
|
+ case LIRC_IRDEO:
|
|
+ case LIRC_IRDEO_REMOTE:
|
|
+ /* setup port to 7N1 @ 115200 Baud */
|
|
+ /* 7N1+start = 9 bits at 115200 ~ 3 bits at 38kHz */
|
|
+
|
|
+ /* Set DLAB 1. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB);
|
|
+ /* Set divisor to 1 => 115200 Baud */
|
|
+ soutp(UART_DLM, 0);
|
|
+ soutp(UART_DLL, 1);
|
|
+ /* Set DLAB 0 + 7N1 */
|
|
+ soutp(UART_LCR, UART_LCR_WLEN7);
|
|
+ /* THR interrupt already disabled at this point */
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int init_port(void)
|
|
+{
|
|
+ int i, nlow, nhigh;
|
|
+
|
|
+ /* Reserve io region. */
|
|
+ /*
|
|
+ * Future MMAP-Developers: Attention!
|
|
+ * For memory mapped I/O you *might* need to use ioremap() first,
|
|
+ * for the NSLU2 it's done in boot code.
|
|
+ */
|
|
+ if (((iommap != 0)
|
|
+ && (request_mem_region(iommap, 8 << ioshift,
|
|
+ LIRC_DRIVER_NAME) == NULL))
|
|
+ || ((iommap == 0)
|
|
+ && (request_region(io, 8, LIRC_DRIVER_NAME) == NULL))) {
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
+ ": port %04x already in use\n", io);
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME
|
|
+ ": use 'setserial /dev/ttySX uart none'\n");
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME
|
|
+ ": or compile the serial port driver as module and\n");
|
|
+ printk(KERN_WARNING LIRC_DRIVER_NAME
|
|
+ ": make sure this module is loaded first\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ if (hardware_init_port() < 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* Initialize pulse/space widths */
|
|
+ init_timing_params(duty_cycle, freq);
|
|
+
|
|
+ /* 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 (sinp(UART_MSR) & hardware[type].signal_pin)
|
|
+ nlow++;
|
|
+ else
|
|
+ nhigh++;
|
|
+ msleep(40);
|
|
+ }
|
|
+ sense = (nlow >= nhigh ? 1 : 0);
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": auto-detected active "
|
|
+ "%s receiver\n", sense ? "low" : "high");
|
|
+ } else
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": Manually using active "
|
|
+ "%s receiver\n", sense ? "low" : "high");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int set_use_inc(void *data)
|
|
+{
|
|
+ int result;
|
|
+ unsigned long flags;
|
|
+
|
|
+ /* initialize timestamp */
|
|
+ do_gettimeofday(&lasttv);
|
|
+
|
|
+ result = request_irq(irq, irq_handler,
|
|
+ IRQF_DISABLED | (share_irq ? IRQF_SHARED : 0),
|
|
+ LIRC_DRIVER_NAME, (void *)&hardware);
|
|
+
|
|
+ switch (result) {
|
|
+ case -EBUSY:
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME ": IRQ %d busy\n", irq);
|
|
+ return -EBUSY;
|
|
+ case -EINVAL:
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
+ ": Bad irq number or handler\n");
|
|
+ return -EINVAL;
|
|
+ default:
|
|
+ dprintk("Interrupt %d, port %04x obtained\n", irq, io);
|
|
+ break;
|
|
+ };
|
|
+
|
|
+ spin_lock_irqsave(&hardware[type].lock, flags);
|
|
+
|
|
+ /* Set DLAB 0. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
|
|
+
|
|
+ soutp(UART_IER, sinp(UART_IER)|UART_IER_MSI);
|
|
+
|
|
+ spin_unlock_irqrestore(&hardware[type].lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void set_use_dec(void *data)
|
|
+{ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&hardware[type].lock, flags);
|
|
+
|
|
+ /* Set DLAB 0. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
|
|
+
|
|
+ /* First of all, disable all interrupts */
|
|
+ soutp(UART_IER, sinp(UART_IER) &
|
|
+ (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI)));
|
|
+ spin_unlock_irqrestore(&hardware[type].lock, flags);
|
|
+
|
|
+ free_irq(irq, (void *)&hardware);
|
|
+
|
|
+ dprintk("freed IRQ %d\n", irq);
|
|
+}
|
|
+
|
|
+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;
|
|
+
|
|
+ if (!(hardware[type].features&LIRC_CAN_SEND_PULSE))
|
|
+ return -EBADF;
|
|
+
|
|
+ if (n % sizeof(int))
|
|
+ return -EINVAL;
|
|
+ count = n / sizeof(int);
|
|
+ if (count > WBUF_LEN || count % 2 == 0)
|
|
+ return -EINVAL;
|
|
+ if (copy_from_user(wbuf, buf, n))
|
|
+ return -EFAULT;
|
|
+ spin_lock_irqsave(&hardware[type].lock, flags);
|
|
+ if (type == LIRC_IRDEO) {
|
|
+ /* DTR, RTS down */
|
|
+ on();
|
|
+ }
|
|
+ for (i = 0; i < count; i++) {
|
|
+ if (i%2)
|
|
+ hardware[type].send_space(wbuf[i]-delta);
|
|
+ else
|
|
+ delta = hardware[type].send_pulse(wbuf[i]);
|
|
+ }
|
|
+ off();
|
|
+ spin_unlock_irqrestore(&hardware[type].lock, flags);
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static int lirc_ioctl(struct inode *node, struct file *filep, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ int result;
|
|
+ unsigned long value;
|
|
+ unsigned int ivalue;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case LIRC_GET_SEND_MODE:
|
|
+ if (!(hardware[type].features&LIRC_CAN_SEND_MASK))
|
|
+ return -ENOIOCTLCMD;
|
|
+
|
|
+ result = put_user(LIRC_SEND2MODE
|
|
+ (hardware[type].features&LIRC_CAN_SEND_MASK),
|
|
+ (unsigned long *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ break;
|
|
+
|
|
+ case LIRC_SET_SEND_MODE:
|
|
+ if (!(hardware[type].features&LIRC_CAN_SEND_MASK))
|
|
+ return -ENOIOCTLCMD;
|
|
+
|
|
+ result = get_user(value, (unsigned long *) 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");
|
|
+ if (!(hardware[type].features&LIRC_CAN_SET_SEND_DUTY_CYCLE))
|
|
+ return -ENOIOCTLCMD;
|
|
+
|
|
+ result = get_user(ivalue, (unsigned int *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ if (ivalue <= 0 || ivalue > 100)
|
|
+ return -EINVAL;
|
|
+ return init_timing_params(ivalue, freq);
|
|
+ break;
|
|
+
|
|
+ case LIRC_SET_SEND_CARRIER:
|
|
+ dprintk("SET_SEND_CARRIER\n");
|
|
+ if (!(hardware[type].features&LIRC_CAN_SET_SEND_CARRIER))
|
|
+ return -ENOIOCTLCMD;
|
|
+
|
|
+ result = get_user(ivalue, (unsigned int *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ if (ivalue > 500000 || ivalue < 20000)
|
|
+ return -EINVAL;
|
|
+ return init_timing_params(duty_cycle, ivalue);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ return lirc_dev_fop_ioctl(node, filep, cmd, arg);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct file_operations lirc_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .write = lirc_write,
|
|
+ .ioctl = lirc_ioctl,
|
|
+ .read = lirc_dev_fop_read,
|
|
+ .poll = lirc_dev_fop_poll,
|
|
+ .open = lirc_dev_fop_open,
|
|
+ .release = lirc_dev_fop_close,
|
|
+};
|
|
+
|
|
+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 struct platform_device *lirc_serial_dev;
|
|
+
|
|
+static int __devinit lirc_serial_probe(struct platform_device *dev)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __devexit lirc_serial_remove(struct platform_device *dev)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int lirc_serial_suspend(struct platform_device *dev,
|
|
+ pm_message_t state)
|
|
+{
|
|
+ /* Set DLAB 0. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
|
|
+
|
|
+ /* Disable all interrupts */
|
|
+ soutp(UART_IER, sinp(UART_IER) &
|
|
+ (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI)));
|
|
+
|
|
+ /* Clear registers. */
|
|
+ sinp(UART_LSR);
|
|
+ sinp(UART_RX);
|
|
+ sinp(UART_IIR);
|
|
+ sinp(UART_MSR);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* twisty maze... need a forward-declaration here... */
|
|
+static void lirc_serial_exit(void);
|
|
+
|
|
+static int lirc_serial_resume(struct platform_device *dev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (hardware_init_port() < 0) {
|
|
+ lirc_serial_exit();
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&hardware[type].lock, flags);
|
|
+ /* Enable Interrupt */
|
|
+ do_gettimeofday(&lasttv);
|
|
+ soutp(UART_IER, sinp(UART_IER)|UART_IER_MSI);
|
|
+ off();
|
|
+
|
|
+ lirc_buffer_clear(&rbuf);
|
|
+
|
|
+ spin_unlock_irqrestore(&hardware[type].lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver lirc_serial_driver = {
|
|
+ .probe = lirc_serial_probe,
|
|
+ .remove = __devexit_p(lirc_serial_remove),
|
|
+ .suspend = lirc_serial_suspend,
|
|
+ .resume = lirc_serial_resume,
|
|
+ .driver = {
|
|
+ .name = "lirc_serial",
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int __init lirc_serial_init(void)
|
|
+{
|
|
+ int result;
|
|
+
|
|
+ /* Init read buffer. */
|
|
+ result = lirc_buffer_init(&rbuf, sizeof(int), RBUF_LEN);
|
|
+ if (result < 0)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ result = platform_driver_register(&lirc_serial_driver);
|
|
+ if (result) {
|
|
+ printk("lirc register returned %d\n", result);
|
|
+ goto exit_buffer_free;
|
|
+ }
|
|
+
|
|
+ lirc_serial_dev = platform_device_alloc("lirc_serial", 0);
|
|
+ if (!lirc_serial_dev) {
|
|
+ result = -ENOMEM;
|
|
+ goto exit_driver_unregister;
|
|
+ }
|
|
+
|
|
+ result = platform_device_add(lirc_serial_dev);
|
|
+ if (result)
|
|
+ goto exit_device_put;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+exit_device_put:
|
|
+ platform_device_put(lirc_serial_dev);
|
|
+exit_driver_unregister:
|
|
+ platform_driver_unregister(&lirc_serial_driver);
|
|
+exit_buffer_free:
|
|
+ lirc_buffer_free(&rbuf);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static void lirc_serial_exit(void)
|
|
+{
|
|
+ platform_device_unregister(lirc_serial_dev);
|
|
+ platform_driver_unregister(&lirc_serial_driver);
|
|
+ lirc_buffer_free(&rbuf);
|
|
+}
|
|
+
|
|
+static int __init lirc_serial_init_module(void)
|
|
+{
|
|
+ int result;
|
|
+
|
|
+ result = lirc_serial_init();
|
|
+ if (result)
|
|
+ return result;
|
|
+
|
|
+ switch (type) {
|
|
+ case LIRC_HOMEBREW:
|
|
+ case LIRC_IRDEO:
|
|
+ case LIRC_IRDEO_REMOTE:
|
|
+ case LIRC_ANIMAX:
|
|
+ case LIRC_IGOR:
|
|
+ /* if nothing specified, use ttyS0/com1 and irq 4 */
|
|
+ io = io ? io : 0x3f8;
|
|
+ irq = irq ? irq : 4;
|
|
+ break;
|
|
+#ifdef CONFIG_LIRC_SERIAL_NSLU2
|
|
+ case LIRC_NSLU2:
|
|
+ io = io ? io : IRQ_IXP4XX_UART2;
|
|
+ irq = irq ? irq : (IXP4XX_UART2_BASE_VIRT + REG_OFFSET);
|
|
+ iommap = iommap ? iommap : IXP4XX_UART2_BASE_PHYS;
|
|
+ ioshift = ioshift ? ioshift : 2;
|
|
+ break;
|
|
+#endif
|
|
+ default:
|
|
+ result = -EINVAL;
|
|
+ goto exit_serial_exit;
|
|
+ }
|
|
+ if (!softcarrier) {
|
|
+ switch (type) {
|
|
+ case LIRC_HOMEBREW:
|
|
+ case LIRC_IGOR:
|
|
+#ifdef CONFIG_LIRC_SERIAL_NSLU2
|
|
+ case LIRC_NSLU2:
|
|
+#endif
|
|
+ hardware[type].features &=
|
|
+ ~(LIRC_CAN_SET_SEND_DUTY_CYCLE|
|
|
+ LIRC_CAN_SET_SEND_CARRIER);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ result = init_port();
|
|
+ if (result < 0)
|
|
+ goto exit_serial_exit;
|
|
+ driver.features = hardware[type].features;
|
|
+ driver.dev = &lirc_serial_dev->dev;
|
|
+ driver.minor = lirc_register_driver(&driver);
|
|
+ if (driver.minor < 0) {
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
+ ": register_chrdev failed!\n");
|
|
+ result = -EIO;
|
|
+ goto exit_release;
|
|
+ }
|
|
+ return 0;
|
|
+exit_release:
|
|
+ release_region(io, 8);
|
|
+exit_serial_exit:
|
|
+ lirc_serial_exit();
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static void __exit lirc_serial_exit_module(void)
|
|
+{
|
|
+ lirc_serial_exit();
|
|
+ if (iommap != 0)
|
|
+ release_mem_region(iommap, 8 << ioshift);
|
|
+ else
|
|
+ release_region(io, 8);
|
|
+ lirc_unregister_driver(driver.minor);
|
|
+ dprintk("cleaned up module\n");
|
|
+}
|
|
+
|
|
+
|
|
+module_init(lirc_serial_init_module);
|
|
+module_exit(lirc_serial_exit_module);
|
|
+
|
|
+MODULE_DESCRIPTION("Infra-red receiver driver for serial ports.");
|
|
+MODULE_AUTHOR("Ralph Metzler, Trent Piepho, Ben Pfaff, "
|
|
+ "Christoph Bartelmus, Andrei Tanas");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_param(type, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(type, "Hardware type (0 = home-brew, 1 = IRdeo,"
|
|
+ " 2 = IRdeo Remote, 3 = AnimaX, 4 = IgorPlug,"
|
|
+ " 5 = NSLU2 RX:CTS2/TX:GreenLED)");
|
|
+
|
|
+module_param(io, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)");
|
|
+
|
|
+/* some architectures (e.g. intel xscale) have memory mapped registers */
|
|
+module_param(iommap, bool, S_IRUGO);
|
|
+MODULE_PARM_DESC(iommap, "physical base for memory mapped I/O"
|
|
+ " (0 = no memory mapped io)");
|
|
+
|
|
+/*
|
|
+ * some architectures (e.g. intel xscale) align the 8bit serial registers
|
|
+ * on 32bit word boundaries.
|
|
+ * See linux-kernel/serial/8250.c serial_in()/out()
|
|
+ */
|
|
+module_param(ioshift, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(ioshift, "shift I/O register offset (0 = no shift)");
|
|
+
|
|
+module_param(irq, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(irq, "Interrupt (4 or 3)");
|
|
+
|
|
+module_param(share_irq, bool, S_IRUGO);
|
|
+MODULE_PARM_DESC(share_irq, "Share interrupts (0 = off, 1 = on)");
|
|
+
|
|
+module_param(sense, bool, S_IRUGO);
|
|
+MODULE_PARM_DESC(sense, "Override autodetection of IR receiver circuit"
|
|
+ " (0 = active high, 1 = active low )");
|
|
+
|
|
+#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER
|
|
+module_param(txsense, bool, S_IRUGO);
|
|
+MODULE_PARM_DESC(txsense, "Sense of transmitter circuit"
|
|
+ " (0 = active high, 1 = active low )");
|
|
+#endif
|
|
+
|
|
+module_param(softcarrier, bool, S_IRUGO);
|
|
+MODULE_PARM_DESC(softcarrier, "Software carrier (0 = off, 1 = on, default on)");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
diff --git a/drivers/input/lirc/lirc_sir.c b/drivers/input/lirc/lirc_sir.c
|
|
new file mode 100644
|
|
index 0000000..4a471d6
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_sir.c
|
|
@@ -0,0 +1,1283 @@
|
|
+/*
|
|
+ * LIRC SIR driver, (C) 2000 Milan Pikula <www@fornax.sk>
|
|
+ *
|
|
+ * lirc_sir - Device driver for use with SIR (serial infra red)
|
|
+ * mode of IrDA on many notebooks.
|
|
+ *
|
|
+ * 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
|
|
+ *
|
|
+ *
|
|
+ * 2000/09/16 Frank Przybylski <mail@frankprzybylski.de> :
|
|
+ * added timeout and relaxed pulse detection, removed gap bug
|
|
+ *
|
|
+ * 2000/12/15 Christoph Bartelmus <lirc@bartelmus.de> :
|
|
+ * added support for Tekram Irmate 210 (sending does not work yet,
|
|
+ * kind of disappointing that nobody was able to implement that
|
|
+ * before),
|
|
+ * major clean-up
|
|
+ *
|
|
+ * 2001/02/27 Christoph Bartelmus <lirc@bartelmus.de> :
|
|
+ * added support for StrongARM SA1100 embedded microprocessor
|
|
+ * parts cut'n'pasted from sa1100_ir.c (C) 2000 Russell King
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/signal.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/serial_reg.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/wait.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/poll.h>
|
|
+#include <asm/system.h>
|
|
+#include <linux/io.h>
|
|
+#include <asm/irq.h>
|
|
+#include <linux/fcntl.h>
|
|
+#ifdef LIRC_ON_SA1100
|
|
+#include <asm/hardware.h>
|
|
+#ifdef CONFIG_SA1100_COLLIE
|
|
+#include <asm/arch/tc35143.h>
|
|
+#include <asm/ucb1200.h>
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+#include <linux/timer.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+/* SECTION: Definitions */
|
|
+
|
|
+/*** Tekram dongle ***/
|
|
+#ifdef LIRC_SIR_TEKRAM
|
|
+/* stolen from kernel source */
|
|
+/* definitions for Tekram dongle */
|
|
+#define TEKRAM_115200 0x00
|
|
+#define TEKRAM_57600 0x01
|
|
+#define TEKRAM_38400 0x02
|
|
+#define TEKRAM_19200 0x03
|
|
+#define TEKRAM_9600 0x04
|
|
+#define TEKRAM_2400 0x08
|
|
+
|
|
+#define TEKRAM_PW 0x10 /* Pulse select bit */
|
|
+
|
|
+/* 10bit * 1s/115200bit in milliseconds = 87ms*/
|
|
+#define TIME_CONST (10000000ul/115200ul)
|
|
+
|
|
+#endif
|
|
+
|
|
+#ifdef LIRC_SIR_ACTISYS_ACT200L
|
|
+static void init_act200(void);
|
|
+#elif defined(LIRC_SIR_ACTISYS_ACT220L)
|
|
+static void init_act220(void);
|
|
+#endif
|
|
+
|
|
+/*** SA1100 ***/
|
|
+#ifdef LIRC_ON_SA1100
|
|
+struct sa1100_ser2_registers {
|
|
+ /* HSSP control register */
|
|
+ unsigned char hscr0;
|
|
+ /* UART registers */
|
|
+ unsigned char utcr0;
|
|
+ unsigned char utcr1;
|
|
+ unsigned char utcr2;
|
|
+ unsigned char utcr3;
|
|
+ unsigned char utcr4;
|
|
+ unsigned char utdr;
|
|
+ unsigned char utsr0;
|
|
+ unsigned char utsr1;
|
|
+} sr;
|
|
+
|
|
+static int irq = IRQ_Ser2ICP;
|
|
+
|
|
+#define LIRC_ON_SA1100_TRANSMITTER_LATENCY 0
|
|
+
|
|
+/* pulse/space ratio of 50/50 */
|
|
+static unsigned long pulse_width = (13-LIRC_ON_SA1100_TRANSMITTER_LATENCY);
|
|
+/* 1000000/freq-pulse_width */
|
|
+static unsigned long space_width = (13-LIRC_ON_SA1100_TRANSMITTER_LATENCY);
|
|
+static unsigned int freq = 38000; /* modulation frequency */
|
|
+static unsigned int duty_cycle = 50; /* duty cycle of 50% */
|
|
+
|
|
+#endif
|
|
+
|
|
+#define RBUF_LEN 1024
|
|
+#define WBUF_LEN 1024
|
|
+
|
|
+#define LIRC_DRIVER_NAME "lirc_sir"
|
|
+
|
|
+#define PULSE '['
|
|
+
|
|
+#ifndef LIRC_SIR_TEKRAM
|
|
+/* 9bit * 1s/115200bit in milli seconds = 78.125ms*/
|
|
+#define TIME_CONST (9000000ul/115200ul)
|
|
+#endif
|
|
+
|
|
+
|
|
+/* timeout for sequences in jiffies (=5/100s), must be longer than TIME_CONST */
|
|
+#define SIR_TIMEOUT (HZ*5/100)
|
|
+
|
|
+#ifndef LIRC_ON_SA1100
|
|
+#ifndef LIRC_IRQ
|
|
+#define LIRC_IRQ 4
|
|
+#endif
|
|
+#ifndef LIRC_PORT
|
|
+/* for external dongles, default to com1 */
|
|
+#if defined(LIRC_SIR_ACTISYS_ACT200L) || \
|
|
+ defined(LIRC_SIR_ACTISYS_ACT220L) || \
|
|
+ defined(LIRC_SIR_TEKRAM)
|
|
+#define LIRC_PORT 0x3f8
|
|
+#else
|
|
+/* onboard sir ports are typically com3 */
|
|
+#define LIRC_PORT 0x3e8
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+static int io = LIRC_PORT;
|
|
+static int irq = LIRC_IRQ;
|
|
+static int threshold = 3;
|
|
+#endif
|
|
+
|
|
+static DEFINE_SPINLOCK(timer_lock);
|
|
+static struct timer_list timerlist;
|
|
+/* time of last signal change detected */
|
|
+static struct timeval last_tv = {0, 0};
|
|
+/* time of last UART data ready interrupt */
|
|
+static struct timeval last_intr_tv = {0, 0};
|
|
+static int last_value;
|
|
+
|
|
+static DECLARE_WAIT_QUEUE_HEAD(lirc_read_queue);
|
|
+
|
|
+static DEFINE_SPINLOCK(hardware_lock);
|
|
+
|
|
+static int rx_buf[RBUF_LEN];
|
|
+static unsigned int rx_tail, rx_head;
|
|
+static int tx_buf[WBUF_LEN];
|
|
+
|
|
+static int debug;
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \
|
|
+ fmt, ## args); \
|
|
+ } while (0)
|
|
+
|
|
+/* SECTION: Prototypes */
|
|
+
|
|
+/* Communication with user-space */
|
|
+static unsigned int lirc_poll(struct file *file, poll_table *wait);
|
|
+static ssize_t lirc_read(struct file *file, char *buf, size_t count,
|
|
+ loff_t *ppos);
|
|
+static ssize_t lirc_write(struct file *file, const char *buf, size_t n,
|
|
+ loff_t *pos);
|
|
+static int lirc_ioctl(struct inode *node, struct file *filep, unsigned int cmd,
|
|
+ unsigned long arg);
|
|
+static void add_read_queue(int flag, unsigned long val);
|
|
+static int init_chrdev(void);
|
|
+static void drop_chrdev(void);
|
|
+/* Hardware */
|
|
+static irqreturn_t sir_interrupt(int irq, void *dev_id);
|
|
+static void send_space(unsigned long len);
|
|
+static void send_pulse(unsigned long len);
|
|
+static int init_hardware(void);
|
|
+static void drop_hardware(void);
|
|
+/* Initialisation */
|
|
+static int init_port(void);
|
|
+static void drop_port(void);
|
|
+
|
|
+#ifdef LIRC_ON_SA1100
|
|
+static void on(void)
|
|
+{
|
|
+ PPSR |= PPC_TXD2;
|
|
+}
|
|
+
|
|
+static void off(void)
|
|
+{
|
|
+ PPSR &= ~PPC_TXD2;
|
|
+}
|
|
+#else
|
|
+static inline unsigned int sinp(int offset)
|
|
+{
|
|
+ return inb(io + offset);
|
|
+}
|
|
+
|
|
+static inline void soutp(int offset, int value)
|
|
+{
|
|
+ outb(value, io + offset);
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifndef MAX_UDELAY_MS
|
|
+#define MAX_UDELAY_US 5000
|
|
+#else
|
|
+#define MAX_UDELAY_US (MAX_UDELAY_MS*1000)
|
|
+#endif
|
|
+
|
|
+static void safe_udelay(unsigned long usecs)
|
|
+{
|
|
+ while (usecs > MAX_UDELAY_US) {
|
|
+ udelay(MAX_UDELAY_US);
|
|
+ usecs -= MAX_UDELAY_US;
|
|
+ }
|
|
+ udelay(usecs);
|
|
+}
|
|
+
|
|
+/* SECTION: Communication with user-space */
|
|
+
|
|
+static unsigned int lirc_poll(struct file *file, poll_table *wait)
|
|
+{
|
|
+ poll_wait(file, &lirc_read_queue, wait);
|
|
+ if (rx_head != rx_tail)
|
|
+ return POLLIN | POLLRDNORM;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t lirc_read(struct file *file, char *buf, size_t count,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ int n = 0;
|
|
+ int retval = 0;
|
|
+ DECLARE_WAITQUEUE(wait, current);
|
|
+
|
|
+ if (count % sizeof(int))
|
|
+ return -EINVAL;
|
|
+
|
|
+ add_wait_queue(&lirc_read_queue, &wait);
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ while (n < count) {
|
|
+ if (rx_head != rx_tail) {
|
|
+ if (copy_to_user((void *) buf + n,
|
|
+ (void *) (rx_buf + rx_head),
|
|
+ sizeof(int))) {
|
|
+ retval = -EFAULT;
|
|
+ break;
|
|
+ }
|
|
+ rx_head = (rx_head + 1) & (RBUF_LEN - 1);
|
|
+ n += sizeof(int);
|
|
+ } else {
|
|
+ if (file->f_flags & O_NONBLOCK) {
|
|
+ retval = -EAGAIN;
|
|
+ break;
|
|
+ }
|
|
+ if (signal_pending(current)) {
|
|
+ retval = -ERESTARTSYS;
|
|
+ break;
|
|
+ }
|
|
+ schedule();
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ }
|
|
+ }
|
|
+ remove_wait_queue(&lirc_read_queue, &wait);
|
|
+ set_current_state(TASK_RUNNING);
|
|
+ return n ? n : retval;
|
|
+}
|
|
+static ssize_t lirc_write(struct file *file, const char *buf, size_t n,
|
|
+ loff_t *pos)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+
|
|
+ if (n % sizeof(int) || (n / sizeof(int)) > WBUF_LEN)
|
|
+ return -EINVAL;
|
|
+ if (copy_from_user(tx_buf, buf, n))
|
|
+ return -EFAULT;
|
|
+ i = 0;
|
|
+ n /= sizeof(int);
|
|
+#ifdef LIRC_ON_SA1100
|
|
+ /* disable receiver */
|
|
+ Ser2UTCR3 = 0;
|
|
+#endif
|
|
+ local_irq_save(flags);
|
|
+ while (1) {
|
|
+ if (i >= n)
|
|
+ break;
|
|
+ if (tx_buf[i])
|
|
+ send_pulse(tx_buf[i]);
|
|
+ i++;
|
|
+ if (i >= n)
|
|
+ break;
|
|
+ if (tx_buf[i])
|
|
+ send_space(tx_buf[i]);
|
|
+ i++;
|
|
+ }
|
|
+ local_irq_restore(flags);
|
|
+#ifdef LIRC_ON_SA1100
|
|
+ off();
|
|
+ udelay(1000); /* wait 1ms for IR diode to recover */
|
|
+ Ser2UTCR3 = 0;
|
|
+ /* clear status register to prevent unwanted interrupts */
|
|
+ Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB);
|
|
+ /* enable receiver */
|
|
+ Ser2UTCR3 = UTCR3_RXE|UTCR3_RIE;
|
|
+#endif
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static int lirc_ioctl(struct inode *node, struct file *filep, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ int retval = 0;
|
|
+ unsigned long value = 0;
|
|
+#ifdef LIRC_ON_SA1100
|
|
+ unsigned int ivalue;
|
|
+
|
|
+ if (cmd == LIRC_GET_FEATURES)
|
|
+ value = LIRC_CAN_SEND_PULSE |
|
|
+ LIRC_CAN_SET_SEND_DUTY_CYCLE |
|
|
+ LIRC_CAN_SET_SEND_CARRIER |
|
|
+ LIRC_CAN_REC_MODE2;
|
|
+ else if (cmd == LIRC_GET_SEND_MODE)
|
|
+ value = LIRC_MODE_PULSE;
|
|
+ else if (cmd == LIRC_GET_REC_MODE)
|
|
+ value = LIRC_MODE_MODE2;
|
|
+#else
|
|
+ if (cmd == LIRC_GET_FEATURES)
|
|
+ value = LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2;
|
|
+ else if (cmd == LIRC_GET_SEND_MODE)
|
|
+ value = LIRC_MODE_PULSE;
|
|
+ else if (cmd == LIRC_GET_REC_MODE)
|
|
+ value = LIRC_MODE_MODE2;
|
|
+#endif
|
|
+
|
|
+ switch (cmd) {
|
|
+ case LIRC_GET_FEATURES:
|
|
+ case LIRC_GET_SEND_MODE:
|
|
+ case LIRC_GET_REC_MODE:
|
|
+ retval = put_user(value, (unsigned long *) arg);
|
|
+ break;
|
|
+
|
|
+ case LIRC_SET_SEND_MODE:
|
|
+ case LIRC_SET_REC_MODE:
|
|
+ retval = get_user(value, (unsigned long *) arg);
|
|
+ break;
|
|
+#ifdef LIRC_ON_SA1100
|
|
+ case LIRC_SET_SEND_DUTY_CYCLE:
|
|
+ retval = get_user(ivalue, (unsigned int *) arg);
|
|
+ if (retval)
|
|
+ return retval;
|
|
+ if (ivalue <= 0 || ivalue > 100)
|
|
+ return -EINVAL;
|
|
+ /* (ivalue/100)*(1000000/freq) */
|
|
+ duty_cycle = ivalue;
|
|
+ pulse_width = (unsigned long) duty_cycle*10000/freq;
|
|
+ space_width = (unsigned long) 1000000L/freq-pulse_width;
|
|
+ if (pulse_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY)
|
|
+ pulse_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY;
|
|
+ if (space_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY)
|
|
+ space_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY;
|
|
+ break;
|
|
+ case LIRC_SET_SEND_CARRIER:
|
|
+ retval = get_user(ivalue, (unsigned int *) arg);
|
|
+ if (retval)
|
|
+ return retval;
|
|
+ if (ivalue > 500000 || ivalue < 20000)
|
|
+ return -EINVAL;
|
|
+ freq = ivalue;
|
|
+ pulse_width = (unsigned long) duty_cycle*10000/freq;
|
|
+ space_width = (unsigned long) 1000000L/freq-pulse_width;
|
|
+ if (pulse_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY)
|
|
+ pulse_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY;
|
|
+ if (space_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY)
|
|
+ space_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY;
|
|
+ break;
|
|
+#endif
|
|
+ default:
|
|
+ retval = -ENOIOCTLCMD;
|
|
+
|
|
+ }
|
|
+
|
|
+ if (retval)
|
|
+ return retval;
|
|
+ if (cmd == LIRC_SET_REC_MODE) {
|
|
+ if (value != LIRC_MODE_MODE2)
|
|
+ retval = -ENOSYS;
|
|
+ } else if (cmd == LIRC_SET_SEND_MODE) {
|
|
+ if (value != LIRC_MODE_PULSE)
|
|
+ retval = -ENOSYS;
|
|
+ }
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static void add_read_queue(int flag, unsigned long val)
|
|
+{
|
|
+ unsigned int new_rx_tail;
|
|
+ int newval;
|
|
+
|
|
+ dprintk("add flag %d with val %lu\n", flag, val);
|
|
+
|
|
+ newval = val & PULSE_MASK;
|
|
+
|
|
+ /*
|
|
+ * statistically, pulses are ~TIME_CONST/2 too long. we could
|
|
+ * maybe make this more exact, but this is good enough
|
|
+ */
|
|
+ if (flag) {
|
|
+ /* pulse */
|
|
+ if (newval > TIME_CONST/2)
|
|
+ newval -= TIME_CONST/2;
|
|
+ else /* should not ever happen */
|
|
+ newval = 1;
|
|
+ newval |= PULSE_BIT;
|
|
+ } else {
|
|
+ newval += TIME_CONST/2;
|
|
+ }
|
|
+ new_rx_tail = (rx_tail + 1) & (RBUF_LEN - 1);
|
|
+ if (new_rx_tail == rx_head) {
|
|
+ dprintk("Buffer overrun.\n");
|
|
+ return;
|
|
+ }
|
|
+ rx_buf[rx_tail] = newval;
|
|
+ rx_tail = new_rx_tail;
|
|
+ wake_up_interruptible(&lirc_read_queue);
|
|
+}
|
|
+
|
|
+static struct file_operations lirc_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .read = lirc_read,
|
|
+ .write = lirc_write,
|
|
+ .poll = lirc_poll,
|
|
+ .ioctl = lirc_ioctl,
|
|
+ .open = lirc_dev_fop_open,
|
|
+ .release = lirc_dev_fop_close,
|
|
+};
|
|
+
|
|
+static int set_use_inc(void *data)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void set_use_dec(void *data)
|
|
+{
|
|
+}
|
|
+
|
|
+static struct lirc_driver driver = {
|
|
+ .name = LIRC_DRIVER_NAME,
|
|
+ .minor = -1,
|
|
+ .code_length = 1,
|
|
+ .sample_rate = 0,
|
|
+ .data = NULL,
|
|
+ .add_to_buf = NULL,
|
|
+ .set_use_inc = set_use_inc,
|
|
+ .set_use_dec = set_use_dec,
|
|
+ .fops = &lirc_fops,
|
|
+ .dev = NULL,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+
|
|
+static int init_chrdev(void)
|
|
+{
|
|
+ driver.minor = lirc_register_driver(&driver);
|
|
+ if (driver.minor < 0) {
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME ": init_chrdev() failed.\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void drop_chrdev(void)
|
|
+{
|
|
+ lirc_unregister_driver(driver.minor);
|
|
+}
|
|
+
|
|
+/* SECTION: Hardware */
|
|
+static long delta(struct timeval *tv1, struct timeval *tv2)
|
|
+{
|
|
+ unsigned long deltv;
|
|
+
|
|
+ deltv = tv2->tv_sec - tv1->tv_sec;
|
|
+ if (deltv > 15)
|
|
+ deltv = 0xFFFFFF;
|
|
+ else
|
|
+ deltv = deltv*1000000 +
|
|
+ tv2->tv_usec -
|
|
+ tv1->tv_usec;
|
|
+ return deltv;
|
|
+}
|
|
+
|
|
+static void sir_timeout(unsigned long data)
|
|
+{
|
|
+ /*
|
|
+ * if last received signal was a pulse, but receiving stopped
|
|
+ * within the 9 bit frame, we need to finish this pulse and
|
|
+ * simulate a signal change to from pulse to space. Otherwise
|
|
+ * upper layers will receive two sequences next time.
|
|
+ */
|
|
+
|
|
+ unsigned long flags;
|
|
+ unsigned long pulse_end;
|
|
+
|
|
+ /* avoid interference with interrupt */
|
|
+ spin_lock_irqsave(&timer_lock, flags);
|
|
+ if (last_value) {
|
|
+#ifndef LIRC_ON_SA1100
|
|
+ /* clear unread bits in UART and restart */
|
|
+ outb(UART_FCR_CLEAR_RCVR, io + UART_FCR);
|
|
+#endif
|
|
+ /* determine 'virtual' pulse end: */
|
|
+ pulse_end = delta(&last_tv, &last_intr_tv);
|
|
+ dprintk("timeout add %d for %lu usec\n", last_value, pulse_end);
|
|
+ add_read_queue(last_value, pulse_end);
|
|
+ last_value = 0;
|
|
+ last_tv = last_intr_tv;
|
|
+ }
|
|
+ spin_unlock_irqrestore(&timer_lock, flags);
|
|
+}
|
|
+
|
|
+static irqreturn_t sir_interrupt(int irq, void *dev_id)
|
|
+{
|
|
+ unsigned char data;
|
|
+ struct timeval curr_tv;
|
|
+ static unsigned long deltv;
|
|
+#ifdef LIRC_ON_SA1100
|
|
+ int status;
|
|
+ static int n;
|
|
+
|
|
+ status = Ser2UTSR0;
|
|
+ /*
|
|
+ * Deal with any receive errors first. The bytes in error may be
|
|
+ * the only bytes in the receive FIFO, so we do this first.
|
|
+ */
|
|
+ while (status & UTSR0_EIF) {
|
|
+ int bstat;
|
|
+
|
|
+ if (debug) {
|
|
+ dprintk("EIF\n");
|
|
+ bstat = Ser2UTSR1;
|
|
+
|
|
+ if (bstat & UTSR1_FRE)
|
|
+ dprintk("frame error\n");
|
|
+ if (bstat & UTSR1_ROR)
|
|
+ dprintk("receive fifo overrun\n");
|
|
+ if (bstat & UTSR1_PRE)
|
|
+ dprintk("parity error\n");
|
|
+ }
|
|
+
|
|
+ bstat = Ser2UTDR;
|
|
+ n++;
|
|
+ status = Ser2UTSR0;
|
|
+ }
|
|
+
|
|
+ if (status & (UTSR0_RFS | UTSR0_RID)) {
|
|
+ do_gettimeofday(&curr_tv);
|
|
+ deltv = delta(&last_tv, &curr_tv);
|
|
+ do {
|
|
+ data = Ser2UTDR;
|
|
+ dprintk("%d data: %u\n", n, (unsigned int) data);
|
|
+ n++;
|
|
+ } while (status & UTSR0_RID && /* do not empty fifo in order to
|
|
+ * get UTSR0_RID in any case */
|
|
+ Ser2UTSR1 & UTSR1_RNE); /* data ready */
|
|
+
|
|
+ if (status&UTSR0_RID) {
|
|
+ add_read_queue(0 , deltv - n * TIME_CONST); /*space*/
|
|
+ add_read_queue(1, n * TIME_CONST); /*pulse*/
|
|
+ n = 0;
|
|
+ last_tv = curr_tv;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (status & UTSR0_TFS)
|
|
+ printk(KERN_ERR "transmit fifo not full, shouldn't happen\n");
|
|
+
|
|
+ /* We must clear certain bits. */
|
|
+ status &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB);
|
|
+ if (status)
|
|
+ Ser2UTSR0 = status;
|
|
+#else
|
|
+ unsigned long deltintrtv;
|
|
+ unsigned long flags;
|
|
+ int iir, lsr;
|
|
+
|
|
+ while ((iir = inb(io + UART_IIR) & UART_IIR_ID)) {
|
|
+ switch (iir&UART_IIR_ID) { /* FIXME toto treba preriedit */
|
|
+ case UART_IIR_MSI:
|
|
+ (void) inb(io + UART_MSR);
|
|
+ break;
|
|
+ case UART_IIR_RLSI:
|
|
+ (void) inb(io + UART_LSR);
|
|
+ break;
|
|
+ case UART_IIR_THRI:
|
|
+#if 0
|
|
+ if (lsr & UART_LSR_THRE) /* FIFO is empty */
|
|
+ outb(data, io + UART_TX)
|
|
+#endif
|
|
+ break;
|
|
+ case UART_IIR_RDI:
|
|
+ /* avoid interference with timer */
|
|
+ spin_lock_irqsave(&timer_lock, flags);
|
|
+ do {
|
|
+ del_timer(&timerlist);
|
|
+ data = inb(io + UART_RX);
|
|
+ do_gettimeofday(&curr_tv);
|
|
+ deltv = delta(&last_tv, &curr_tv);
|
|
+ deltintrtv = delta(&last_intr_tv, &curr_tv);
|
|
+ dprintk("t %lu, d %d\n", deltintrtv, (int)data);
|
|
+ /*
|
|
+ * if nothing came in last X cycles,
|
|
+ * it was gap
|
|
+ */
|
|
+ if (deltintrtv > TIME_CONST * threshold) {
|
|
+ if (last_value) {
|
|
+ dprintk("GAP\n");
|
|
+ /* simulate signal change */
|
|
+ add_read_queue(last_value,
|
|
+ deltv -
|
|
+ deltintrtv);
|
|
+ last_value = 0;
|
|
+ last_tv.tv_sec =
|
|
+ last_intr_tv.tv_sec;
|
|
+ last_tv.tv_usec =
|
|
+ last_intr_tv.tv_usec;
|
|
+ deltv = deltintrtv;
|
|
+ }
|
|
+ }
|
|
+ data = 1;
|
|
+ if (data ^ last_value) {
|
|
+ /*
|
|
+ * deltintrtv > 2*TIME_CONST, remember?
|
|
+ * the other case is timeout
|
|
+ */
|
|
+ add_read_queue(last_value,
|
|
+ deltv-TIME_CONST);
|
|
+ last_value = data;
|
|
+ last_tv = curr_tv;
|
|
+ if (last_tv.tv_usec >= TIME_CONST) {
|
|
+ last_tv.tv_usec -= TIME_CONST;
|
|
+ } else {
|
|
+ last_tv.tv_sec--;
|
|
+ last_tv.tv_usec += 1000000 -
|
|
+ TIME_CONST;
|
|
+ }
|
|
+ }
|
|
+ last_intr_tv = curr_tv;
|
|
+ if (data) {
|
|
+ /*
|
|
+ * start timer for end of
|
|
+ * sequence detection
|
|
+ */
|
|
+ timerlist.expires = jiffies +
|
|
+ SIR_TIMEOUT;
|
|
+ add_timer(&timerlist);
|
|
+ }
|
|
+
|
|
+ lsr = inb(io + UART_LSR);
|
|
+ } while (lsr & UART_LSR_DR); /* data ready */
|
|
+ spin_unlock_irqrestore(&timer_lock, flags);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+ return IRQ_RETVAL(IRQ_HANDLED);
|
|
+}
|
|
+
|
|
+#ifdef LIRC_ON_SA1100
|
|
+static void send_pulse(unsigned long length)
|
|
+{
|
|
+ unsigned long k, delay;
|
|
+ int flag;
|
|
+
|
|
+ if (length == 0)
|
|
+ return;
|
|
+ /*
|
|
+ * this won't give us the carrier frequency we really want
|
|
+ * due to integer arithmetic, but we can accept this inaccuracy
|
|
+ */
|
|
+
|
|
+ for (k = flag = 0; k < length; k += delay, flag = !flag) {
|
|
+ if (flag) {
|
|
+ off();
|
|
+ delay = space_width;
|
|
+ } else {
|
|
+ on();
|
|
+ delay = pulse_width;
|
|
+ }
|
|
+ safe_udelay(delay);
|
|
+ }
|
|
+ off();
|
|
+}
|
|
+
|
|
+static void send_space(unsigned long length)
|
|
+{
|
|
+ if (length == 0)
|
|
+ return;
|
|
+ off();
|
|
+ safe_udelay(length);
|
|
+}
|
|
+#else
|
|
+static void send_space(unsigned long len)
|
|
+{
|
|
+ safe_udelay(len);
|
|
+}
|
|
+
|
|
+static void send_pulse(unsigned long len)
|
|
+{
|
|
+ long bytes_out = len / TIME_CONST;
|
|
+ long time_left;
|
|
+
|
|
+ time_left = (long)len - (long)bytes_out * (long)TIME_CONST;
|
|
+ if (bytes_out == 0) {
|
|
+ bytes_out++;
|
|
+ time_left = 0;
|
|
+ }
|
|
+ while (bytes_out--) {
|
|
+ outb(PULSE, io + UART_TX);
|
|
+ /* FIXME treba seriozne cakanie z char/serial.c */
|
|
+ while (!(inb(io + UART_LSR) & UART_LSR_THRE))
|
|
+ ;
|
|
+ }
|
|
+#if 0
|
|
+ if (time_left > 0)
|
|
+ safe_udelay(time_left);
|
|
+#endif
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_SA1100_COLLIE
|
|
+static int sa1100_irda_set_power_collie(int state)
|
|
+{
|
|
+ if (state) {
|
|
+ /*
|
|
+ * 0 - off
|
|
+ * 1 - short range, lowest power
|
|
+ * 2 - medium range, medium power
|
|
+ * 3 - maximum range, high power
|
|
+ */
|
|
+ ucb1200_set_io_direction(TC35143_GPIO_IR_ON,
|
|
+ TC35143_IODIR_OUTPUT);
|
|
+ ucb1200_set_io(TC35143_GPIO_IR_ON, TC35143_IODAT_LOW);
|
|
+ udelay(100);
|
|
+ } else {
|
|
+ /* OFF */
|
|
+ ucb1200_set_io_direction(TC35143_GPIO_IR_ON,
|
|
+ TC35143_IODIR_OUTPUT);
|
|
+ ucb1200_set_io(TC35143_GPIO_IR_ON, TC35143_IODAT_HIGH);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int init_hardware(void)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&hardware_lock, flags);
|
|
+ /* reset UART */
|
|
+#ifdef LIRC_ON_SA1100
|
|
+#ifdef CONFIG_SA1100_BITSY
|
|
+ if (machine_is_bitsy()) {
|
|
+ printk(KERN_INFO "Power on IR module\n");
|
|
+ set_bitsy_egpio(EGPIO_BITSY_IR_ON);
|
|
+ }
|
|
+#endif
|
|
+#ifdef CONFIG_SA1100_COLLIE
|
|
+ sa1100_irda_set_power_collie(3); /* power on */
|
|
+#endif
|
|
+ sr.hscr0 = Ser2HSCR0;
|
|
+
|
|
+ sr.utcr0 = Ser2UTCR0;
|
|
+ sr.utcr1 = Ser2UTCR1;
|
|
+ sr.utcr2 = Ser2UTCR2;
|
|
+ sr.utcr3 = Ser2UTCR3;
|
|
+ sr.utcr4 = Ser2UTCR4;
|
|
+
|
|
+ sr.utdr = Ser2UTDR;
|
|
+ sr.utsr0 = Ser2UTSR0;
|
|
+ sr.utsr1 = Ser2UTSR1;
|
|
+
|
|
+ /* configure GPIO */
|
|
+ /* output */
|
|
+ PPDR |= PPC_TXD2;
|
|
+ PSDR |= PPC_TXD2;
|
|
+ /* set output to 0 */
|
|
+ off();
|
|
+
|
|
+ /* Enable HP-SIR modulation, and ensure that the port is disabled. */
|
|
+ Ser2UTCR3 = 0;
|
|
+ Ser2HSCR0 = sr.hscr0 & (~HSCR0_HSSP);
|
|
+
|
|
+ /* clear status register to prevent unwanted interrupts */
|
|
+ Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB);
|
|
+
|
|
+ /* 7N1 */
|
|
+ Ser2UTCR0 = UTCR0_1StpBit|UTCR0_7BitData;
|
|
+ /* 115200 */
|
|
+ Ser2UTCR1 = 0;
|
|
+ Ser2UTCR2 = 1;
|
|
+ /* use HPSIR, 1.6 usec pulses */
|
|
+ Ser2UTCR4 = UTCR4_HPSIR|UTCR4_Z1_6us;
|
|
+
|
|
+ /* enable receiver, receive fifo interrupt */
|
|
+ Ser2UTCR3 = UTCR3_RXE|UTCR3_RIE;
|
|
+
|
|
+ /* clear status register to prevent unwanted interrupts */
|
|
+ Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB);
|
|
+
|
|
+#elif defined(LIRC_SIR_TEKRAM)
|
|
+ /* disable FIFO */
|
|
+ soutp(UART_FCR,
|
|
+ UART_FCR_CLEAR_RCVR|
|
|
+ UART_FCR_CLEAR_XMIT|
|
|
+ UART_FCR_TRIGGER_1);
|
|
+
|
|
+ /* Set DLAB 0. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
|
|
+
|
|
+ /* First of all, disable all interrupts */
|
|
+ soutp(UART_IER, sinp(UART_IER) &
|
|
+ (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI)));
|
|
+
|
|
+ /* Set DLAB 1. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB);
|
|
+
|
|
+ /* Set divisor to 12 => 9600 Baud */
|
|
+ soutp(UART_DLM, 0);
|
|
+ soutp(UART_DLL, 12);
|
|
+
|
|
+ /* Set DLAB 0. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
|
|
+
|
|
+ /* power supply */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ safe_udelay(50*1000);
|
|
+
|
|
+ /* -DTR low -> reset PIC */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2);
|
|
+ udelay(1*1000);
|
|
+
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ udelay(100);
|
|
+
|
|
+
|
|
+ /* -RTS low -> send control byte */
|
|
+ soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ udelay(7);
|
|
+ soutp(UART_TX, TEKRAM_115200|TEKRAM_PW);
|
|
+
|
|
+ /* one byte takes ~1042 usec to transmit at 9600,8N1 */
|
|
+ udelay(1500);
|
|
+
|
|
+ /* back to normal operation */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ udelay(50);
|
|
+
|
|
+ udelay(1500);
|
|
+
|
|
+ /* read previous control byte */
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
+ ": 0x%02x\n", sinp(UART_RX));
|
|
+
|
|
+ /* Set DLAB 1. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB);
|
|
+
|
|
+ /* Set divisor to 1 => 115200 Baud */
|
|
+ soutp(UART_DLM, 0);
|
|
+ soutp(UART_DLL, 1);
|
|
+
|
|
+ /* Set DLAB 0, 8 Bit */
|
|
+ soutp(UART_LCR, UART_LCR_WLEN8);
|
|
+ /* enable interrupts */
|
|
+ soutp(UART_IER, sinp(UART_IER)|UART_IER_RDI);
|
|
+#else
|
|
+ outb(0, io + UART_MCR);
|
|
+ outb(0, io + UART_IER);
|
|
+ /* init UART */
|
|
+ /* set DLAB, speed = 115200 */
|
|
+ outb(UART_LCR_DLAB | UART_LCR_WLEN7, io + UART_LCR);
|
|
+ outb(1, io + UART_DLL); outb(0, io + UART_DLM);
|
|
+ /* 7N1+start = 9 bits at 115200 ~ 3 bits at 44000 */
|
|
+ outb(UART_LCR_WLEN7, io + UART_LCR);
|
|
+ /* FIFO operation */
|
|
+ outb(UART_FCR_ENABLE_FIFO, io + UART_FCR);
|
|
+ /* interrupts */
|
|
+ /* outb(UART_IER_RLSI|UART_IER_RDI|UART_IER_THRI, io + UART_IER); */
|
|
+ outb(UART_IER_RDI, io + UART_IER);
|
|
+ /* turn on UART */
|
|
+ outb(UART_MCR_DTR|UART_MCR_RTS|UART_MCR_OUT2, io + UART_MCR);
|
|
+#ifdef LIRC_SIR_ACTISYS_ACT200L
|
|
+ init_act200();
|
|
+#elif defined(LIRC_SIR_ACTISYS_ACT220L)
|
|
+ init_act220();
|
|
+#endif
|
|
+#endif
|
|
+ spin_unlock_irqrestore(&hardware_lock, flags);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void drop_hardware(void)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&hardware_lock, flags);
|
|
+
|
|
+#ifdef LIRC_ON_SA1100
|
|
+ Ser2UTCR3 = 0;
|
|
+
|
|
+ Ser2UTCR0 = sr.utcr0;
|
|
+ Ser2UTCR1 = sr.utcr1;
|
|
+ Ser2UTCR2 = sr.utcr2;
|
|
+ Ser2UTCR4 = sr.utcr4;
|
|
+ Ser2UTCR3 = sr.utcr3;
|
|
+
|
|
+ Ser2HSCR0 = sr.hscr0;
|
|
+#ifdef CONFIG_SA1100_BITSY
|
|
+ if (machine_is_bitsy())
|
|
+ clr_bitsy_egpio(EGPIO_BITSY_IR_ON);
|
|
+#endif
|
|
+#ifdef CONFIG_SA1100_COLLIE
|
|
+ sa1100_irda_set_power_collie(0); /* power off */
|
|
+#endif
|
|
+#else
|
|
+ /* turn off interrupts */
|
|
+ outb(0, io + UART_IER);
|
|
+#endif
|
|
+ spin_unlock_irqrestore(&hardware_lock, flags);
|
|
+}
|
|
+
|
|
+/* SECTION: Initialisation */
|
|
+
|
|
+static int init_port(void)
|
|
+{
|
|
+ int retval;
|
|
+
|
|
+ /* get I/O port access and IRQ line */
|
|
+#ifndef LIRC_ON_SA1100
|
|
+ if (request_region(io, 8, LIRC_DRIVER_NAME) == NULL) {
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
+ ": i/o port 0x%.4x already in use.\n", io);
|
|
+ return -EBUSY;
|
|
+ }
|
|
+#endif
|
|
+ retval = request_irq(irq, sir_interrupt, IRQF_DISABLED,
|
|
+ LIRC_DRIVER_NAME, NULL);
|
|
+ if (retval < 0) {
|
|
+# ifndef LIRC_ON_SA1100
|
|
+ release_region(io, 8);
|
|
+# endif
|
|
+ printk(KERN_ERR LIRC_DRIVER_NAME
|
|
+ ": IRQ %d already in use.\n",
|
|
+ irq);
|
|
+ return retval;
|
|
+ }
|
|
+#ifndef LIRC_ON_SA1100
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
+ ": I/O port 0x%.4x, IRQ %d.\n",
|
|
+ io, irq);
|
|
+#endif
|
|
+
|
|
+ init_timer(&timerlist);
|
|
+ timerlist.function = sir_timeout;
|
|
+ timerlist.data = 0xabadcafe;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void drop_port(void)
|
|
+{
|
|
+ free_irq(irq, NULL);
|
|
+ del_timer_sync(&timerlist);
|
|
+#ifndef LIRC_ON_SA1100
|
|
+ release_region(io, 8);
|
|
+#endif
|
|
+}
|
|
+
|
|
+#ifdef LIRC_SIR_ACTISYS_ACT200L
|
|
+/* Crystal/Cirrus CS8130 IR transceiver, used in Actisys Act200L dongle */
|
|
+/* some code borrowed from Linux IRDA driver */
|
|
+
|
|
+/* Register 0: Control register #1 */
|
|
+#define ACT200L_REG0 0x00
|
|
+#define ACT200L_TXEN 0x01 /* Enable transmitter */
|
|
+#define ACT200L_RXEN 0x02 /* Enable receiver */
|
|
+#define ACT200L_ECHO 0x08 /* Echo control chars */
|
|
+
|
|
+/* Register 1: Control register #2 */
|
|
+#define ACT200L_REG1 0x10
|
|
+#define ACT200L_LODB 0x01 /* Load new baud rate count value */
|
|
+#define ACT200L_WIDE 0x04 /* Expand the maximum allowable pulse */
|
|
+
|
|
+/* Register 3: Transmit mode register #2 */
|
|
+#define ACT200L_REG3 0x30
|
|
+#define ACT200L_B0 0x01 /* DataBits, 0=6, 1=7, 2=8, 3=9(8P) */
|
|
+#define ACT200L_B1 0x02 /* DataBits, 0=6, 1=7, 2=8, 3=9(8P) */
|
|
+#define ACT200L_CHSY 0x04 /* StartBit Synced 0=bittime, 1=startbit */
|
|
+
|
|
+/* Register 4: Output Power register */
|
|
+#define ACT200L_REG4 0x40
|
|
+#define ACT200L_OP0 0x01 /* Enable LED1C output */
|
|
+#define ACT200L_OP1 0x02 /* Enable LED2C output */
|
|
+#define ACT200L_BLKR 0x04
|
|
+
|
|
+/* Register 5: Receive Mode register */
|
|
+#define ACT200L_REG5 0x50
|
|
+#define ACT200L_RWIDL 0x01 /* fixed 1.6us pulse mode */
|
|
+ /*.. other various IRDA bit modes, and TV remote modes..*/
|
|
+
|
|
+/* Register 6: Receive Sensitivity register #1 */
|
|
+#define ACT200L_REG6 0x60
|
|
+#define ACT200L_RS0 0x01 /* receive threshold bit 0 */
|
|
+#define ACT200L_RS1 0x02 /* receive threshold bit 1 */
|
|
+
|
|
+/* Register 7: Receive Sensitivity register #2 */
|
|
+#define ACT200L_REG7 0x70
|
|
+#define ACT200L_ENPOS 0x04 /* Ignore the falling edge */
|
|
+
|
|
+/* Register 8,9: Baud Rate Divider register #1,#2 */
|
|
+#define ACT200L_REG8 0x80
|
|
+#define ACT200L_REG9 0x90
|
|
+
|
|
+#define ACT200L_2400 0x5f
|
|
+#define ACT200L_9600 0x17
|
|
+#define ACT200L_19200 0x0b
|
|
+#define ACT200L_38400 0x05
|
|
+#define ACT200L_57600 0x03
|
|
+#define ACT200L_115200 0x01
|
|
+
|
|
+/* Register 13: Control register #3 */
|
|
+#define ACT200L_REG13 0xd0
|
|
+#define ACT200L_SHDW 0x01 /* Enable access to shadow registers */
|
|
+
|
|
+/* Register 15: Status register */
|
|
+#define ACT200L_REG15 0xf0
|
|
+
|
|
+/* Register 21: Control register #4 */
|
|
+#define ACT200L_REG21 0x50
|
|
+#define ACT200L_EXCK 0x02 /* Disable clock output driver */
|
|
+#define ACT200L_OSCL 0x04 /* oscillator in low power, medium accuracy mode */
|
|
+
|
|
+static void init_act200(void)
|
|
+{
|
|
+ int i;
|
|
+ __u8 control[] = {
|
|
+ ACT200L_REG15,
|
|
+ ACT200L_REG13 | ACT200L_SHDW,
|
|
+ ACT200L_REG21 | ACT200L_EXCK | ACT200L_OSCL,
|
|
+ ACT200L_REG13,
|
|
+ ACT200L_REG7 | ACT200L_ENPOS,
|
|
+ ACT200L_REG6 | ACT200L_RS0 | ACT200L_RS1,
|
|
+ ACT200L_REG5 | ACT200L_RWIDL,
|
|
+ ACT200L_REG4 | ACT200L_OP0 | ACT200L_OP1 | ACT200L_BLKR,
|
|
+ ACT200L_REG3 | ACT200L_B0,
|
|
+ ACT200L_REG0 | ACT200L_TXEN | ACT200L_RXEN,
|
|
+ ACT200L_REG8 | (ACT200L_115200 & 0x0f),
|
|
+ ACT200L_REG9 | ((ACT200L_115200 >> 4) & 0x0f),
|
|
+ ACT200L_REG1 | ACT200L_LODB | ACT200L_WIDE
|
|
+ };
|
|
+
|
|
+ /* Set DLAB 1. */
|
|
+ soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8);
|
|
+
|
|
+ /* Set divisor to 12 => 9600 Baud */
|
|
+ soutp(UART_DLM, 0);
|
|
+ soutp(UART_DLL, 12);
|
|
+
|
|
+ /* Set DLAB 0. */
|
|
+ soutp(UART_LCR, UART_LCR_WLEN8);
|
|
+ /* Set divisor to 12 => 9600 Baud */
|
|
+
|
|
+ /* power supply */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ for (i = 0; i < 50; i++)
|
|
+ safe_udelay(1000);
|
|
+
|
|
+ /* Reset the dongle : set RTS low for 25 ms */
|
|
+ soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ for (i = 0; i < 25; i++)
|
|
+ udelay(1000);
|
|
+
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ udelay(100);
|
|
+
|
|
+ /* Clear DTR and set RTS to enter command mode */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2);
|
|
+ udelay(7);
|
|
+
|
|
+ /* send out the control register settings for 115K 7N1 SIR operation */
|
|
+ for (i = 0; i < sizeof(control); i++) {
|
|
+ soutp(UART_TX, control[i]);
|
|
+ /* one byte takes ~1042 usec to transmit at 9600,8N1 */
|
|
+ udelay(1500);
|
|
+ }
|
|
+
|
|
+ /* back to normal operation */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ udelay(50);
|
|
+
|
|
+ udelay(1500);
|
|
+ soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB);
|
|
+
|
|
+ /* Set DLAB 1. */
|
|
+ soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN7);
|
|
+
|
|
+ /* Set divisor to 1 => 115200 Baud */
|
|
+ soutp(UART_DLM, 0);
|
|
+ soutp(UART_DLL, 1);
|
|
+
|
|
+ /* Set DLAB 0. */
|
|
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
|
|
+
|
|
+ /* Set DLAB 0, 7 Bit */
|
|
+ soutp(UART_LCR, UART_LCR_WLEN7);
|
|
+
|
|
+ /* enable interrupts */
|
|
+ soutp(UART_IER, sinp(UART_IER)|UART_IER_RDI);
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifdef LIRC_SIR_ACTISYS_ACT220L
|
|
+/*
|
|
+ * Derived from linux IrDA driver (net/irda/actisys.c)
|
|
+ * Drop me a mail for any kind of comment: maxx@spaceboyz.net
|
|
+ */
|
|
+
|
|
+void init_act220(void)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ /* DLAB 1 */
|
|
+ soutp(UART_LCR, UART_LCR_DLAB|UART_LCR_WLEN7);
|
|
+
|
|
+ /* 9600 baud */
|
|
+ soutp(UART_DLM, 0);
|
|
+ soutp(UART_DLL, 12);
|
|
+
|
|
+ /* DLAB 0 */
|
|
+ soutp(UART_LCR, UART_LCR_WLEN7);
|
|
+
|
|
+ /* reset the dongle, set DTR low for 10us */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2);
|
|
+ udelay(10);
|
|
+
|
|
+ /* back to normal (still 9600) */
|
|
+ soutp(UART_MCR, UART_MCR_DTR|UART_MCR_RTS|UART_MCR_OUT2);
|
|
+
|
|
+ /*
|
|
+ * send RTS pulses until we reach 115200
|
|
+ * i hope this is really the same for act220l/act220l+
|
|
+ */
|
|
+ for (i = 0; i < 3; i++) {
|
|
+ udelay(10);
|
|
+ /* set RTS low for 10 us */
|
|
+ soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ udelay(10);
|
|
+ /* set RTS high for 10 us */
|
|
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
|
|
+ }
|
|
+
|
|
+ /* back to normal operation */
|
|
+ udelay(1500); /* better safe than sorry ;) */
|
|
+
|
|
+ /* Set DLAB 1. */
|
|
+ soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN7);
|
|
+
|
|
+ /* Set divisor to 1 => 115200 Baud */
|
|
+ soutp(UART_DLM, 0);
|
|
+ soutp(UART_DLL, 1);
|
|
+
|
|
+ /* Set DLAB 0, 7 Bit */
|
|
+ /* The dongle doesn't seem to have any problems with operation at 7N1 */
|
|
+ soutp(UART_LCR, UART_LCR_WLEN7);
|
|
+
|
|
+ /* enable interrupts */
|
|
+ soutp(UART_IER, UART_IER_RDI);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int init_lirc_sir(void)
|
|
+{
|
|
+ int retval;
|
|
+
|
|
+ init_waitqueue_head(&lirc_read_queue);
|
|
+ retval = init_port();
|
|
+ if (retval < 0)
|
|
+ return retval;
|
|
+ init_hardware();
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME
|
|
+ ": Installed.\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static int __init lirc_sir_init(void)
|
|
+{
|
|
+ int retval;
|
|
+
|
|
+ retval = init_chrdev();
|
|
+ if (retval < 0)
|
|
+ return retval;
|
|
+ retval = init_lirc_sir();
|
|
+ if (retval) {
|
|
+ drop_chrdev();
|
|
+ return retval;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit lirc_sir_exit(void)
|
|
+{
|
|
+ drop_hardware();
|
|
+ drop_chrdev();
|
|
+ drop_port();
|
|
+ printk(KERN_INFO LIRC_DRIVER_NAME ": Uninstalled.\n");
|
|
+}
|
|
+
|
|
+module_init(lirc_sir_init);
|
|
+module_exit(lirc_sir_exit);
|
|
+
|
|
+#ifdef LIRC_SIR_TEKRAM
|
|
+MODULE_DESCRIPTION("Infrared receiver driver for Tekram Irmate 210");
|
|
+MODULE_AUTHOR("Christoph Bartelmus");
|
|
+#elif defined(LIRC_ON_SA1100)
|
|
+MODULE_DESCRIPTION("LIRC driver for StrongARM SA1100 embedded microprocessor");
|
|
+MODULE_AUTHOR("Christoph Bartelmus");
|
|
+#elif defined(LIRC_SIR_ACTISYS_ACT200L)
|
|
+MODULE_DESCRIPTION("LIRC driver for Actisys Act200L");
|
|
+MODULE_AUTHOR("Karl Bongers");
|
|
+#elif defined(LIRC_SIR_ACTISYS_ACT220L)
|
|
+MODULE_DESCRIPTION("LIRC driver for Actisys Act220L(+)");
|
|
+MODULE_AUTHOR("Jan Roemisch");
|
|
+#else
|
|
+MODULE_DESCRIPTION("Infrared receiver driver for SIR type serial ports");
|
|
+MODULE_AUTHOR("Milan Pikula");
|
|
+#endif
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+#ifdef LIRC_ON_SA1100
|
|
+module_param(irq, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(irq, "Interrupt (16)");
|
|
+#else
|
|
+module_param(io, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)");
|
|
+
|
|
+module_param(irq, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(irq, "Interrupt (4 or 3)");
|
|
+
|
|
+module_param(threshold, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(threshold, "space detection threshold (3)");
|
|
+#endif
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
diff --git a/drivers/input/lirc/lirc_streamzap.c b/drivers/input/lirc/lirc_streamzap.c
|
|
new file mode 100644
|
|
index 0000000..f4374e8
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_streamzap.c
|
|
@@ -0,0 +1,794 @@
|
|
+/*
|
|
+ * Streamzap Remote Control driver
|
|
+ *
|
|
+ * Copyright (c) 2005 Christoph Bartelmus <lirc@bartelmus.de>
|
|
+ *
|
|
+ * This driver was based on the work of Greg Wickham and Adrian
|
|
+ * Dewhurst. It was substantially rewritten to support correct signal
|
|
+ * gaps and now maintains a delay buffer, which is used to present
|
|
+ * consistent timing behaviour to user space applications. Without the
|
|
+ * delay buffer an ugly hack would be required in lircd, which can
|
|
+ * cause sluggish signal decoding in certain situations.
|
|
+ *
|
|
+ * This driver is based on the USB skeleton driver packaged with the
|
|
+ * kernel; copyright (C) 2001-2003 Greg Kroah-Hartman (greg@kroah.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/kernel.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/smp_lock.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/usb.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+#define DRIVER_VERSION "1.28"
|
|
+#define DRIVER_NAME "lirc_streamzap"
|
|
+#define DRIVER_DESC "Streamzap Remote Control driver"
|
|
+
|
|
+static int debug;
|
|
+
|
|
+#define USB_STREAMZAP_VENDOR_ID 0x0e9c
|
|
+#define USB_STREAMZAP_PRODUCT_ID 0x0000
|
|
+
|
|
+/* Use our own dbg macro */
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG DRIVER_NAME "[%d]: " \
|
|
+ fmt "\n", ## args); \
|
|
+ } while (0)
|
|
+
|
|
+/* table of devices that work with this driver */
|
|
+static struct usb_device_id streamzap_table[] = {
|
|
+ /* Streamzap Remote Control */
|
|
+ { USB_DEVICE(USB_STREAMZAP_VENDOR_ID, USB_STREAMZAP_PRODUCT_ID) },
|
|
+ /* Terminating entry */
|
|
+ { }
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(usb, streamzap_table);
|
|
+
|
|
+#define STREAMZAP_PULSE_MASK 0xf0
|
|
+#define STREAMZAP_SPACE_MASK 0x0f
|
|
+#define STREAMZAP_RESOLUTION 256
|
|
+
|
|
+/* number of samples buffered */
|
|
+#define STREAMZAP_BUF_LEN 128
|
|
+
|
|
+enum StreamzapDecoderState {
|
|
+ PulseSpace,
|
|
+ FullPulse,
|
|
+ FullSpace,
|
|
+ IgnorePulse
|
|
+};
|
|
+
|
|
+/* Structure to hold all of our device specific stuff
|
|
+ *
|
|
+ * some remarks regarding locking:
|
|
+ * theoretically this struct can be accessed from three threads:
|
|
+ *
|
|
+ * - from lirc_dev through set_use_inc/set_use_dec
|
|
+ *
|
|
+ * - from the USB layer throuh probe/disconnect/irq
|
|
+ *
|
|
+ * Careful placement of lirc_register_driver/lirc_unregister_driver
|
|
+ * calls will prevent conflicts. lirc_dev makes sure that
|
|
+ * set_use_inc/set_use_dec are not being executed and will not be
|
|
+ * called after lirc_unregister_driver returns.
|
|
+ *
|
|
+ * - by the timer callback
|
|
+ *
|
|
+ * The timer is only running when the device is connected and the
|
|
+ * LIRC device is open. Making sure the timer is deleted by
|
|
+ * set_use_dec will make conflicts impossible.
|
|
+ */
|
|
+struct usb_streamzap {
|
|
+
|
|
+ /* usb */
|
|
+ /* save off the usb device pointer */
|
|
+ struct usb_device *udev;
|
|
+ /* the interface for this device */
|
|
+ struct usb_interface *interface;
|
|
+
|
|
+ /* buffer & dma */
|
|
+ unsigned char *buf_in;
|
|
+ dma_addr_t dma_in;
|
|
+ unsigned int buf_in_len;
|
|
+
|
|
+ struct usb_endpoint_descriptor *endpoint;
|
|
+
|
|
+ /* IRQ */
|
|
+ struct urb *urb_in;
|
|
+
|
|
+ /* lirc */
|
|
+ struct lirc_driver *driver;
|
|
+ struct lirc_buffer *delay_buf;
|
|
+
|
|
+ /* timer used to support delay buffering */
|
|
+ struct timer_list delay_timer;
|
|
+ int timer_running;
|
|
+ spinlock_t timer_lock;
|
|
+
|
|
+ /* tracks whether we are currently receiving some signal */
|
|
+ int idle;
|
|
+ /* sum of signal lengths received since signal start */
|
|
+ unsigned long sum;
|
|
+ /* start time of signal; necessary for gap tracking */
|
|
+ struct timeval signal_last;
|
|
+ struct timeval signal_start;
|
|
+ enum StreamzapDecoderState decoder_state;
|
|
+ struct timer_list flush_timer;
|
|
+ int flush;
|
|
+ int in_use;
|
|
+};
|
|
+
|
|
+
|
|
+/* local function prototypes */
|
|
+static int streamzap_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id);
|
|
+static void streamzap_disconnect(struct usb_interface *interface);
|
|
+static void usb_streamzap_irq(struct urb *urb);
|
|
+static int streamzap_use_inc(void *data);
|
|
+static void streamzap_use_dec(void *data);
|
|
+static int streamzap_ioctl(struct inode *node, struct file *filep,
|
|
+ unsigned int cmd, unsigned long arg);
|
|
+static int streamzap_suspend(struct usb_interface *intf, pm_message_t message);
|
|
+static int streamzap_resume(struct usb_interface *intf);
|
|
+
|
|
+/* usb specific object needed to register this driver with the usb subsystem */
|
|
+
|
|
+static struct usb_driver streamzap_driver = {
|
|
+ .name = DRIVER_NAME,
|
|
+ .probe = streamzap_probe,
|
|
+ .disconnect = streamzap_disconnect,
|
|
+ .suspend = streamzap_suspend,
|
|
+ .resume = streamzap_resume,
|
|
+ .id_table = streamzap_table,
|
|
+};
|
|
+
|
|
+static void stop_timer(struct usb_streamzap *sz)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&sz->timer_lock, flags);
|
|
+ if (sz->timer_running) {
|
|
+ sz->timer_running = 0;
|
|
+ spin_unlock_irqrestore(&sz->timer_lock, flags);
|
|
+ del_timer_sync(&sz->delay_timer);
|
|
+ } else {
|
|
+ spin_unlock_irqrestore(&sz->timer_lock, flags);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void flush_timeout(unsigned long arg)
|
|
+{
|
|
+ struct usb_streamzap *sz = (struct usb_streamzap *) arg;
|
|
+
|
|
+ /* finally start accepting data */
|
|
+ sz->flush = 0;
|
|
+}
|
|
+static void delay_timeout(unsigned long arg)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ /* deliver data every 10 ms */
|
|
+ static unsigned long timer_inc =
|
|
+ (10000/(1000000/HZ)) == 0 ? 1 : (10000/(1000000/HZ));
|
|
+ struct usb_streamzap *sz = (struct usb_streamzap *) arg;
|
|
+ int data;
|
|
+
|
|
+ spin_lock_irqsave(&sz->timer_lock, flags);
|
|
+
|
|
+ if (!lirc_buffer_empty(sz->delay_buf) &&
|
|
+ !lirc_buffer_full(sz->driver->rbuf)) {
|
|
+ lirc_buffer_read(sz->delay_buf, (unsigned char *) &data);
|
|
+ lirc_buffer_write(sz->driver->rbuf, (unsigned char *) &data);
|
|
+ }
|
|
+ if (!lirc_buffer_empty(sz->delay_buf)) {
|
|
+ while (lirc_buffer_available(sz->delay_buf) <
|
|
+ STREAMZAP_BUF_LEN / 2 &&
|
|
+ !lirc_buffer_full(sz->driver->rbuf)) {
|
|
+ lirc_buffer_read(sz->delay_buf,
|
|
+ (unsigned char *) &data);
|
|
+ lirc_buffer_write(sz->driver->rbuf,
|
|
+ (unsigned char *) &data);
|
|
+ }
|
|
+ if (sz->timer_running) {
|
|
+ sz->delay_timer.expires = jiffies + timer_inc;
|
|
+ add_timer(&sz->delay_timer);
|
|
+ }
|
|
+ } else {
|
|
+ sz->timer_running = 0;
|
|
+ }
|
|
+
|
|
+ if (!lirc_buffer_empty(sz->driver->rbuf))
|
|
+ wake_up(&sz->driver->rbuf->wait_poll);
|
|
+
|
|
+ spin_unlock_irqrestore(&sz->timer_lock, flags);
|
|
+}
|
|
+
|
|
+static void flush_delay_buffer(struct usb_streamzap *sz)
|
|
+{
|
|
+ int data;
|
|
+ int empty = 1;
|
|
+
|
|
+ while (!lirc_buffer_empty(sz->delay_buf)) {
|
|
+ empty = 0;
|
|
+ lirc_buffer_read(sz->delay_buf, (unsigned char *) &data);
|
|
+ if (!lirc_buffer_full(sz->driver->rbuf)) {
|
|
+ lirc_buffer_write(sz->driver->rbuf,
|
|
+ (unsigned char *) &data);
|
|
+ } else {
|
|
+ dprintk("buffer overflow", sz->driver->minor);
|
|
+ }
|
|
+ }
|
|
+ if (!empty)
|
|
+ wake_up(&sz->driver->rbuf->wait_poll);
|
|
+}
|
|
+
|
|
+static void push(struct usb_streamzap *sz, unsigned char *data)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&sz->timer_lock, flags);
|
|
+ if (lirc_buffer_full(sz->delay_buf)) {
|
|
+ int read_data;
|
|
+
|
|
+ lirc_buffer_read(sz->delay_buf,
|
|
+ (unsigned char *) &read_data);
|
|
+ if (!lirc_buffer_full(sz->driver->rbuf)) {
|
|
+ lirc_buffer_write(sz->driver->rbuf,
|
|
+ (unsigned char *) &read_data);
|
|
+ } else {
|
|
+ dprintk("buffer overflow", sz->driver->minor);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ lirc_buffer_write(sz->delay_buf, data);
|
|
+
|
|
+ if (!sz->timer_running) {
|
|
+ sz->delay_timer.expires = jiffies + HZ/10;
|
|
+ add_timer(&sz->delay_timer);
|
|
+ sz->timer_running = 1;
|
|
+ }
|
|
+
|
|
+ spin_unlock_irqrestore(&sz->timer_lock, flags);
|
|
+}
|
|
+
|
|
+static void push_full_pulse(struct usb_streamzap *sz,
|
|
+ unsigned char value)
|
|
+{
|
|
+ int pulse;
|
|
+
|
|
+ if (sz->idle) {
|
|
+ long deltv;
|
|
+ int tmp;
|
|
+
|
|
+ sz->signal_last = sz->signal_start;
|
|
+ do_gettimeofday(&sz->signal_start);
|
|
+
|
|
+ deltv = sz->signal_start.tv_sec-sz->signal_last.tv_sec;
|
|
+ if (deltv > 15) {
|
|
+ tmp = PULSE_MASK; /* really long time */
|
|
+ } else {
|
|
+ tmp = (int) (deltv*1000000+
|
|
+ sz->signal_start.tv_usec -
|
|
+ sz->signal_last.tv_usec);
|
|
+ tmp -= sz->sum;
|
|
+ }
|
|
+ dprintk("ls %u", sz->driver->minor, tmp);
|
|
+ push(sz, (char *)&tmp);
|
|
+
|
|
+ sz->idle = 0;
|
|
+ sz->sum = 0;
|
|
+ }
|
|
+
|
|
+ pulse = ((int) value) * STREAMZAP_RESOLUTION;
|
|
+ pulse += STREAMZAP_RESOLUTION / 2;
|
|
+ sz->sum += pulse;
|
|
+ pulse |= PULSE_BIT;
|
|
+
|
|
+ dprintk("p %u", sz->driver->minor, pulse & PULSE_MASK);
|
|
+ push(sz, (char *)&pulse);
|
|
+}
|
|
+
|
|
+static void push_half_pulse(struct usb_streamzap *sz,
|
|
+ unsigned char value)
|
|
+{
|
|
+ push_full_pulse(sz, (value & STREAMZAP_PULSE_MASK)>>4);
|
|
+}
|
|
+
|
|
+static void push_full_space(struct usb_streamzap *sz,
|
|
+ unsigned char value)
|
|
+{
|
|
+ int space;
|
|
+
|
|
+ space = ((int) value)*STREAMZAP_RESOLUTION;
|
|
+ space += STREAMZAP_RESOLUTION/2;
|
|
+ sz->sum += space;
|
|
+ dprintk("s %u", sz->driver->minor, space);
|
|
+ push(sz, (char *)&space);
|
|
+}
|
|
+
|
|
+static void push_half_space(struct usb_streamzap *sz,
|
|
+ unsigned char value)
|
|
+{
|
|
+ push_full_space(sz, value & STREAMZAP_SPACE_MASK);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * usb_streamzap_irq - IRQ handler
|
|
+ *
|
|
+ * This procedure is invoked on reception of data from
|
|
+ * the usb remote.
|
|
+ */
|
|
+static void usb_streamzap_irq(struct urb *urb)
|
|
+{
|
|
+ struct usb_streamzap *sz;
|
|
+ int len;
|
|
+ unsigned int i = 0;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+
|
|
+ sz = urb->context;
|
|
+ len = urb->actual_length;
|
|
+
|
|
+ switch (urb->status) {
|
|
+ case -ECONNRESET:
|
|
+ case -ENOENT:
|
|
+ case -ESHUTDOWN:
|
|
+ /*
|
|
+ * this urb is terminated, clean up.
|
|
+ * sz might already be invalid at this point
|
|
+ */
|
|
+ dprintk("urb status: %d", -1, urb->status);
|
|
+ return;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ dprintk("received %d", sz->driver->minor, urb->actual_length);
|
|
+ if (!sz->flush) {
|
|
+ for (i = 0; i < urb->actual_length; i++) {
|
|
+ dprintk("%d: %x", sz->driver->minor,
|
|
+ i, (unsigned char) sz->buf_in[i]);
|
|
+ switch (sz->decoder_state) {
|
|
+ case PulseSpace:
|
|
+ if ((sz->buf_in[i]&STREAMZAP_PULSE_MASK) ==
|
|
+ STREAMZAP_PULSE_MASK) {
|
|
+ sz->decoder_state = FullPulse;
|
|
+ continue;
|
|
+ } else if ((sz->buf_in[i]&STREAMZAP_SPACE_MASK)
|
|
+ == STREAMZAP_SPACE_MASK) {
|
|
+ push_half_pulse(sz, sz->buf_in[i]);
|
|
+ sz->decoder_state = FullSpace;
|
|
+ continue;
|
|
+ } else {
|
|
+ push_half_pulse(sz, sz->buf_in[i]);
|
|
+ push_half_space(sz, sz->buf_in[i]);
|
|
+ }
|
|
+ break;
|
|
+ case FullPulse:
|
|
+ push_full_pulse(sz, sz->buf_in[i]);
|
|
+ sz->decoder_state = IgnorePulse;
|
|
+ break;
|
|
+ case FullSpace:
|
|
+ if (sz->buf_in[i] == 0xff) {
|
|
+ sz->idle = 1;
|
|
+ stop_timer(sz);
|
|
+ flush_delay_buffer(sz);
|
|
+ } else
|
|
+ push_full_space(sz, sz->buf_in[i]);
|
|
+ sz->decoder_state = PulseSpace;
|
|
+ break;
|
|
+ case IgnorePulse:
|
|
+ if ((sz->buf_in[i]&STREAMZAP_SPACE_MASK) ==
|
|
+ STREAMZAP_SPACE_MASK) {
|
|
+ sz->decoder_state = FullSpace;
|
|
+ continue;
|
|
+ }
|
|
+ push_half_space(sz, sz->buf_in[i]);
|
|
+ sz->decoder_state = PulseSpace;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ usb_submit_urb(urb, GFP_ATOMIC);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+static struct file_operations streamzap_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .ioctl = streamzap_ioctl,
|
|
+ .read = lirc_dev_fop_read,
|
|
+ .write = lirc_dev_fop_write,
|
|
+ .poll = lirc_dev_fop_poll,
|
|
+ .open = lirc_dev_fop_open,
|
|
+ .release = lirc_dev_fop_close,
|
|
+};
|
|
+
|
|
+
|
|
+/**
|
|
+ * streamzap_probe
|
|
+ *
|
|
+ * Called by usb-core to associated with a candidate device
|
|
+ * On any failure the return value is the ERROR
|
|
+ * On success return 0
|
|
+ */
|
|
+static int streamzap_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id)
|
|
+{
|
|
+ struct usb_device *udev = interface_to_usbdev(interface);
|
|
+ struct usb_host_interface *iface_host;
|
|
+ struct usb_streamzap *sz;
|
|
+ struct lirc_driver *driver;
|
|
+ struct lirc_buffer *lirc_buf;
|
|
+ struct lirc_buffer *delay_buf;
|
|
+ char buf[63], name[128] = "";
|
|
+ int retval = -ENOMEM;
|
|
+ int minor = 0;
|
|
+
|
|
+ /* Allocate space for device driver specific data */
|
|
+ sz = kzalloc(sizeof(struct usb_streamzap), GFP_KERNEL);
|
|
+ if (sz == NULL)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ sz->udev = udev;
|
|
+ sz->interface = interface;
|
|
+
|
|
+ /* Check to ensure endpoint information matches requirements */
|
|
+ iface_host = interface->cur_altsetting;
|
|
+
|
|
+ if (iface_host->desc.bNumEndpoints != 1) {
|
|
+ err("%s: Unexpected desc.bNumEndpoints (%d)", __func__,
|
|
+ iface_host->desc.bNumEndpoints);
|
|
+ retval = -ENODEV;
|
|
+ goto free_sz;
|
|
+ }
|
|
+
|
|
+ sz->endpoint = &(iface_host->endpoint[0].desc);
|
|
+ if ((sz->endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
|
|
+ != USB_DIR_IN) {
|
|
+ err("%s: endpoint doesn't match input device 02%02x",
|
|
+ __func__, sz->endpoint->bEndpointAddress);
|
|
+ retval = -ENODEV;
|
|
+ goto free_sz;
|
|
+ }
|
|
+
|
|
+ if ((sz->endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
|
|
+ != USB_ENDPOINT_XFER_INT) {
|
|
+ err("%s: endpoint attributes don't match xfer 02%02x",
|
|
+ __func__, sz->endpoint->bmAttributes);
|
|
+ retval = -ENODEV;
|
|
+ goto free_sz;
|
|
+ }
|
|
+
|
|
+ if (sz->endpoint->wMaxPacketSize == 0) {
|
|
+ err("%s: endpoint message size==0? ", __func__);
|
|
+ retval = -ENODEV;
|
|
+ goto free_sz;
|
|
+ }
|
|
+
|
|
+ /* Allocate the USB buffer and IRQ URB */
|
|
+
|
|
+ sz->buf_in_len = sz->endpoint->wMaxPacketSize;
|
|
+ sz->buf_in = usb_buffer_alloc(sz->udev, sz->buf_in_len,
|
|
+ GFP_ATOMIC, &sz->dma_in);
|
|
+ if (sz->buf_in == NULL)
|
|
+ goto free_sz;
|
|
+
|
|
+ sz->urb_in = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (sz->urb_in == NULL)
|
|
+ goto free_sz;
|
|
+
|
|
+ /* Connect this device to the LIRC sub-system */
|
|
+ driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
|
|
+ if (!driver)
|
|
+ goto free_sz;
|
|
+
|
|
+ lirc_buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
|
|
+ if (!lirc_buf)
|
|
+ goto free_driver;
|
|
+ if (lirc_buffer_init(lirc_buf, sizeof(int), STREAMZAP_BUF_LEN))
|
|
+ goto kfree_lirc_buf;
|
|
+
|
|
+ delay_buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
|
|
+ if (!delay_buf)
|
|
+ goto free_lirc_buf;
|
|
+ if (lirc_buffer_init(delay_buf, sizeof(int), STREAMZAP_BUF_LEN))
|
|
+ goto kfree_delay_buf;
|
|
+
|
|
+ sz->driver = driver;
|
|
+ strcpy(sz->driver->name, DRIVER_NAME);
|
|
+ sz->driver->minor = -1;
|
|
+ sz->driver->sample_rate = 0;
|
|
+ sz->driver->code_length = sizeof(int) * 8;
|
|
+ sz->driver->features = LIRC_CAN_REC_MODE2 | LIRC_CAN_GET_REC_RESOLUTION;
|
|
+ sz->driver->data = sz;
|
|
+ sz->driver->rbuf = lirc_buf;
|
|
+ sz->delay_buf = delay_buf;
|
|
+ sz->driver->set_use_inc = &streamzap_use_inc;
|
|
+ sz->driver->set_use_dec = &streamzap_use_dec;
|
|
+ sz->driver->fops = &streamzap_fops;
|
|
+ sz->driver->dev = &interface->dev;
|
|
+ sz->driver->owner = THIS_MODULE;
|
|
+
|
|
+ sz->idle = 1;
|
|
+ sz->decoder_state = PulseSpace;
|
|
+ init_timer(&sz->delay_timer);
|
|
+ sz->delay_timer.function = delay_timeout;
|
|
+ sz->delay_timer.data = (unsigned long) sz;
|
|
+ sz->timer_running = 0;
|
|
+ spin_lock_init(&sz->timer_lock);
|
|
+
|
|
+ init_timer(&sz->flush_timer);
|
|
+ sz->flush_timer.function = flush_timeout;
|
|
+ sz->flush_timer.data = (unsigned long) sz;
|
|
+ /* Complete final initialisations */
|
|
+
|
|
+ usb_fill_int_urb(sz->urb_in, udev,
|
|
+ usb_rcvintpipe(udev, sz->endpoint->bEndpointAddress),
|
|
+ sz->buf_in, sz->buf_in_len, usb_streamzap_irq, sz,
|
|
+ sz->endpoint->bInterval);
|
|
+ sz->urb_in->transfer_dma = sz->dma_in;
|
|
+ sz->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
|
+
|
|
+ if (udev->descriptor.iManufacturer
|
|
+ && usb_string(udev, udev->descriptor.iManufacturer,
|
|
+ buf, sizeof(buf)) > 0)
|
|
+ strlcpy(name, buf, sizeof(name));
|
|
+
|
|
+ if (udev->descriptor.iProduct
|
|
+ && usb_string(udev, udev->descriptor.iProduct,
|
|
+ buf, sizeof(buf)) > 0)
|
|
+ snprintf(name + strlen(name), sizeof(name) - strlen(name),
|
|
+ " %s", buf);
|
|
+
|
|
+ minor = lirc_register_driver(driver);
|
|
+
|
|
+ if (minor < 0)
|
|
+ goto free_delay_buf;
|
|
+
|
|
+ sz->driver->minor = minor;
|
|
+
|
|
+ usb_set_intfdata(interface, sz);
|
|
+
|
|
+ printk(KERN_INFO DRIVER_NAME "[%d]: %s on usb%d:%d attached\n",
|
|
+ sz->driver->minor, name,
|
|
+ udev->bus->busnum, sz->udev->devnum);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+free_delay_buf:
|
|
+ lirc_buffer_free(sz->delay_buf);
|
|
+kfree_delay_buf:
|
|
+ kfree(delay_buf);
|
|
+free_lirc_buf:
|
|
+ lirc_buffer_free(sz->driver->rbuf);
|
|
+kfree_lirc_buf:
|
|
+ kfree(lirc_buf);
|
|
+free_driver:
|
|
+ kfree(driver);
|
|
+free_sz:
|
|
+ if (retval == -ENOMEM)
|
|
+ err("Out of memory");
|
|
+
|
|
+ if (sz) {
|
|
+ usb_free_urb(sz->urb_in);
|
|
+ usb_buffer_free(udev, sz->buf_in_len, sz->buf_in, sz->dma_in);
|
|
+ kfree(sz);
|
|
+ }
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static int streamzap_use_inc(void *data)
|
|
+{
|
|
+ struct usb_streamzap *sz = data;
|
|
+
|
|
+ if (!sz) {
|
|
+ dprintk("%s called with no context", -1, __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ dprintk("set use inc", sz->driver->minor);
|
|
+
|
|
+ lirc_buffer_clear(sz->driver->rbuf);
|
|
+ lirc_buffer_clear(sz->delay_buf);
|
|
+
|
|
+ sz->flush_timer.expires = jiffies + HZ;
|
|
+ sz->flush = 1;
|
|
+ add_timer(&sz->flush_timer);
|
|
+
|
|
+ sz->urb_in->dev = sz->udev;
|
|
+ if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) {
|
|
+ dprintk("open result = -EIO error submitting urb",
|
|
+ sz->driver->minor);
|
|
+ return -EIO;
|
|
+ }
|
|
+ sz->in_use++;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void streamzap_use_dec(void *data)
|
|
+{
|
|
+ struct usb_streamzap *sz = data;
|
|
+
|
|
+ if (!sz) {
|
|
+ dprintk("%s called with no context", -1, __func__);
|
|
+ return;
|
|
+ }
|
|
+ dprintk("set use dec", sz->driver->minor);
|
|
+
|
|
+ if (sz->flush) {
|
|
+ sz->flush = 0;
|
|
+ del_timer_sync(&sz->flush_timer);
|
|
+ }
|
|
+
|
|
+ usb_kill_urb(sz->urb_in);
|
|
+
|
|
+ stop_timer(sz);
|
|
+
|
|
+ sz->in_use--;
|
|
+}
|
|
+
|
|
+static int streamzap_ioctl(struct inode *node, struct file *filep,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ int result;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case LIRC_GET_REC_RESOLUTION:
|
|
+ result = put_user(STREAMZAP_RESOLUTION, (unsigned int *) arg);
|
|
+ if (result)
|
|
+ return result;
|
|
+ break;
|
|
+ default:
|
|
+ return lirc_dev_fop_ioctl(node, filep, cmd, arg);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * streamzap_disconnect
|
|
+ *
|
|
+ * Called by the usb core when the device is removed from the system.
|
|
+ *
|
|
+ * This routine guarantees that the driver will not submit any more urbs
|
|
+ * by clearing dev->udev. It is also supposed to terminate any currently
|
|
+ * active urbs. Unfortunately, usb_bulk_msg(), used in streamzap_read(),
|
|
+ * does not provide any way to do this.
|
|
+ */
|
|
+static void streamzap_disconnect(struct usb_interface *interface)
|
|
+{
|
|
+ struct usb_streamzap *sz;
|
|
+ int errnum;
|
|
+ int minor;
|
|
+
|
|
+ sz = usb_get_intfdata(interface);
|
|
+
|
|
+ /* unregister from the LIRC sub-system */
|
|
+
|
|
+ errnum = lirc_unregister_driver(sz->driver->minor);
|
|
+ if (errnum != 0)
|
|
+ dprintk("error in lirc_unregister: (returned %d)",
|
|
+ sz->driver->minor, errnum);
|
|
+
|
|
+ lirc_buffer_free(sz->delay_buf);
|
|
+ lirc_buffer_free(sz->driver->rbuf);
|
|
+
|
|
+ /* unregister from the USB sub-system */
|
|
+
|
|
+ usb_free_urb(sz->urb_in);
|
|
+
|
|
+ usb_buffer_free(sz->udev, sz->buf_in_len, sz->buf_in, sz->dma_in);
|
|
+
|
|
+ minor = sz->driver->minor;
|
|
+ kfree(sz->driver->rbuf);
|
|
+ kfree(sz->driver);
|
|
+ kfree(sz->delay_buf);
|
|
+ kfree(sz);
|
|
+
|
|
+ printk(KERN_INFO DRIVER_NAME "[%d]: disconnected\n", minor);
|
|
+}
|
|
+
|
|
+static int streamzap_suspend(struct usb_interface *intf, pm_message_t message)
|
|
+{
|
|
+ struct usb_streamzap *sz = usb_get_intfdata(intf);
|
|
+
|
|
+ printk(KERN_INFO DRIVER_NAME "[%d]: suspend\n", sz->driver->minor);
|
|
+ if (sz->in_use) {
|
|
+ if (sz->flush) {
|
|
+ sz->flush = 0;
|
|
+ del_timer_sync(&sz->flush_timer);
|
|
+ }
|
|
+
|
|
+ stop_timer(sz);
|
|
+
|
|
+ usb_kill_urb(sz->urb_in);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int streamzap_resume(struct usb_interface *intf)
|
|
+{
|
|
+ struct usb_streamzap *sz = usb_get_intfdata(intf);
|
|
+
|
|
+ lirc_buffer_clear(sz->driver->rbuf);
|
|
+ lirc_buffer_clear(sz->delay_buf);
|
|
+
|
|
+ if (sz->in_use) {
|
|
+ sz->flush_timer.expires = jiffies + HZ;
|
|
+ sz->flush = 1;
|
|
+ add_timer(&sz->flush_timer);
|
|
+
|
|
+ sz->urb_in->dev = sz->udev;
|
|
+ if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) {
|
|
+ dprintk("open result = -EIO error submitting urb",
|
|
+ sz->driver->minor);
|
|
+ return -EIO;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * usb_streamzap_init
|
|
+ */
|
|
+static int __init usb_streamzap_init(void)
|
|
+{
|
|
+ int result;
|
|
+
|
|
+ /* register this driver with the USB subsystem */
|
|
+ result = usb_register(&streamzap_driver);
|
|
+
|
|
+ if (result) {
|
|
+ err("usb_register failed. Error number %d",
|
|
+ result);
|
|
+ return result;
|
|
+ }
|
|
+
|
|
+ printk(KERN_INFO DRIVER_NAME " " DRIVER_VERSION " registered\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * usb_streamzap_exit
|
|
+ */
|
|
+static void __exit usb_streamzap_exit(void)
|
|
+{
|
|
+ usb_deregister(&streamzap_driver);
|
|
+}
|
|
+
|
|
+
|
|
+module_init(usb_streamzap_init);
|
|
+module_exit(usb_streamzap_exit);
|
|
+
|
|
+MODULE_AUTHOR("Christoph Bartelmus, Greg Wickham, Adrian Dewhurst");
|
|
+MODULE_DESCRIPTION(DRIVER_DESC);
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
diff --git a/drivers/input/lirc/lirc_ttusbir.c b/drivers/input/lirc/lirc_ttusbir.c
|
|
new file mode 100644
|
|
index 0000000..b0a4e8b
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_ttusbir.c
|
|
@@ -0,0 +1,397 @@
|
|
+/*
|
|
+ * lirc_ttusbir.c
|
|
+ *
|
|
+ * lirc_ttusbir - LIRC device driver for the TechnoTrend USB IR Receiver
|
|
+ *
|
|
+ * Copyright (C) 2007 Stefan Macher <st_maker-lirc@yahoo.de>
|
|
+ *
|
|
+ * This LIRC driver provides access to the TechnoTrend USB IR Receiver.
|
|
+ * The receiver delivers the IR signal as raw sampled true/false data in
|
|
+ * isochronous USB packets each of size 128 byte.
|
|
+ * Currently the driver reduces the sampling rate by factor of 8 as this
|
|
+ * is still more than enough to decode RC-5 - others should be analyzed.
|
|
+ * But the driver does not rely on RC-5 it should be able to decode every
|
|
+ * IR signal that is not too fast.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * 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/version.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/usb.h>
|
|
+
|
|
+#include <linux/lirc.h>
|
|
+#include "lirc_dev.h"
|
|
+
|
|
+MODULE_DESCRIPTION("TechnoTrend USB IR device driver for LIRC");
|
|
+MODULE_AUTHOR("Stefan Macher (st_maker-lirc@yahoo.de)");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+/* #define DEBUG */
|
|
+#ifdef DEBUG
|
|
+#define DPRINTK printk
|
|
+#else
|
|
+#define DPRINTK(_x_, a...)
|
|
+#endif
|
|
+
|
|
+/* function declarations */
|
|
+static int probe(struct usb_interface *intf, const struct usb_device_id *id);
|
|
+static void disconnect(struct usb_interface *intf);
|
|
+static void urb_complete(struct urb *urb);
|
|
+static int set_use_inc(void *data);
|
|
+static void set_use_dec(void *data);
|
|
+
|
|
+static int num_urbs = 2;
|
|
+module_param(num_urbs, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(num_urbs,
|
|
+ "Number of URBs in queue. Try to increase to 4 in case "
|
|
+ "of problems (default: 2; minimum: 2)");
|
|
+
|
|
+/* table of devices that work with this driver */
|
|
+static struct usb_device_id device_id_table[] = {
|
|
+ /* TechnoTrend USB IR Receiver */
|
|
+ { USB_DEVICE(0x0B48, 0x2003) },
|
|
+ /* Terminating entry */
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(usb, device_id_table);
|
|
+
|
|
+/* USB driver definition */
|
|
+static struct usb_driver usb_driver = {
|
|
+ .name = "TTUSBIR",
|
|
+ .id_table = &(device_id_table[0]),
|
|
+ .probe = probe,
|
|
+ .disconnect = disconnect,
|
|
+};
|
|
+
|
|
+/* USB device definition */
|
|
+struct ttusbir_device {
|
|
+ struct usb_driver *usb_driver;
|
|
+ struct usb_device *udev;
|
|
+ struct usb_interface *interf;
|
|
+ struct usb_class_driver class_driver;
|
|
+ unsigned int ifnum; /* Interface number to use */
|
|
+ unsigned int alt_setting; /* alternate setting to use */
|
|
+ unsigned int endpoint; /* Endpoint to use */
|
|
+ struct urb **urb; /* num_urb URB pointers*/
|
|
+ char **buffer; /* 128 byte buffer for each URB */
|
|
+ struct lirc_buffer rbuf; /* Buffer towards LIRC */
|
|
+ struct lirc_driver driver;
|
|
+ int minor;
|
|
+ int last_pulse; /* remembers if last received byte was pulse or space */
|
|
+ int last_num; /* remembers how many last bytes appeared */
|
|
+ int opened;
|
|
+};
|
|
+
|
|
+/*** LIRC specific functions ***/
|
|
+static int set_use_inc(void *data)
|
|
+{
|
|
+ int i, retval;
|
|
+ struct ttusbir_device *ttusbir = data;
|
|
+
|
|
+ DPRINTK("Sending first URBs\n");
|
|
+ /* @TODO Do I need to check if I am already opened */
|
|
+ ttusbir->opened = 1;
|
|
+
|
|
+ for (i = 0; i < num_urbs; i++) {
|
|
+ retval = usb_submit_urb(ttusbir->urb[i], GFP_KERNEL);
|
|
+ if (retval) {
|
|
+ err("%s: usb_submit_urb failed on urb %d",
|
|
+ __func__, i);
|
|
+ return retval;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void set_use_dec(void *data)
|
|
+{
|
|
+ struct ttusbir_device *ttusbir = data;
|
|
+
|
|
+ DPRINTK("Device closed\n");
|
|
+
|
|
+ ttusbir->opened = 0;
|
|
+}
|
|
+
|
|
+/*** USB specific functions ***/
|
|
+
|
|
+/*
|
|
+ * This mapping table is used to do a very simple filtering of the
|
|
+ * input signal.
|
|
+ * For a value with at least 4 bits set it returns 0xFF otherwise
|
|
+ * 0x00. For faster IR signals this can not be used. But for RC-5 we
|
|
+ * still have about 14 samples per pulse/space, i.e. we sample with 14
|
|
+ * times higher frequency than the signal frequency
|
|
+ */
|
|
+const unsigned char map_table[] =
|
|
+{
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
|
+};
|
|
+
|
|
+static void urb_complete(struct urb *urb)
|
|
+{
|
|
+ struct ttusbir_device *ttusbir;
|
|
+ unsigned char *buf;
|
|
+ int i;
|
|
+ int l;
|
|
+
|
|
+ ttusbir = urb->context;
|
|
+
|
|
+ if (!ttusbir->opened)
|
|
+ return;
|
|
+
|
|
+ buf = (unsigned char *)urb->transfer_buffer;
|
|
+
|
|
+ for (i = 0; i < 128; i++) {
|
|
+ /* Here we do the filtering and some kind of down sampling */
|
|
+ buf[i] = ~map_table[buf[i]];
|
|
+ if (ttusbir->last_pulse == buf[i]) {
|
|
+ if (ttusbir->last_num < PULSE_MASK/63)
|
|
+ ttusbir->last_num++;
|
|
+ /*
|
|
+ * else we are in a idle period and do not need to
|
|
+ * increment any longer
|
|
+ */
|
|
+ } else {
|
|
+ l = ttusbir->last_num * 62; /* about 62 = us/byte */
|
|
+ if (ttusbir->last_pulse) /* pulse or space? */
|
|
+ l |= PULSE_BIT;
|
|
+ if (!lirc_buffer_full(&ttusbir->rbuf)) {
|
|
+ lirc_buffer_write(&ttusbir->rbuf, (void *)&l);
|
|
+ wake_up_interruptible(&ttusbir->rbuf.wait_poll);
|
|
+ }
|
|
+ ttusbir->last_num = 0;
|
|
+ ttusbir->last_pulse = buf[i];
|
|
+ }
|
|
+ }
|
|
+ usb_submit_urb(urb, GFP_ATOMIC); /* keep data rolling :-) */
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Called whenever the USB subsystem thinks we could be the right driver
|
|
+ * to handle this device
|
|
+ */
|
|
+static int probe(struct usb_interface *intf, const struct usb_device_id *id)
|
|
+{
|
|
+ int alt_set, endp;
|
|
+ int found = 0;
|
|
+ int i, j;
|
|
+ int struct_size;
|
|
+ struct usb_host_interface *host_interf;
|
|
+ struct usb_interface_descriptor *interf_desc;
|
|
+ struct usb_host_endpoint *host_endpoint;
|
|
+ struct ttusbir_device *ttusbir;
|
|
+
|
|
+ DPRINTK("Module ttusbir probe\n");
|
|
+
|
|
+ /* To reduce memory fragmentation we use only one allocation */
|
|
+ struct_size = sizeof(struct ttusbir_device) +
|
|
+ (sizeof(struct urb *) * num_urbs) +
|
|
+ (sizeof(char *) * num_urbs) +
|
|
+ (num_urbs * 128);
|
|
+ ttusbir = kzalloc(struct_size, GFP_KERNEL);
|
|
+ if (!ttusbir)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ttusbir->urb = (struct urb **)((char *)ttusbir +
|
|
+ sizeof(struct ttusbir_device));
|
|
+ ttusbir->buffer = (char **)((char *)ttusbir->urb +
|
|
+ (sizeof(struct urb *) * num_urbs));
|
|
+ for (i = 0; i < num_urbs; i++)
|
|
+ ttusbir->buffer[i] = (char *)ttusbir->buffer +
|
|
+ (sizeof(char *)*num_urbs) + (i * 128);
|
|
+
|
|
+ ttusbir->usb_driver = &usb_driver;
|
|
+ ttusbir->alt_setting = -1;
|
|
+ /* @TODO check if error can be returned */
|
|
+ ttusbir->udev = usb_get_dev(interface_to_usbdev(intf));
|
|
+ ttusbir->interf = intf;
|
|
+ ttusbir->last_pulse = 0x00;
|
|
+ ttusbir->last_num = 0;
|
|
+
|
|
+ /*
|
|
+ * Now look for interface setting we can handle
|
|
+ * We are searching for the alt setting where end point
|
|
+ * 0x82 has max packet size 16
|
|
+ */
|
|
+ for (alt_set = 0; alt_set < intf->num_altsetting && !found; alt_set++) {
|
|
+ host_interf = &intf->altsetting[alt_set];
|
|
+ interf_desc = &host_interf->desc;
|
|
+ for (endp = 0; endp < interf_desc->bNumEndpoints; endp++) {
|
|
+ host_endpoint = &host_interf->endpoint[endp];
|
|
+ if ((host_endpoint->desc.bEndpointAddress == 0x82) &&
|
|
+ (host_endpoint->desc.wMaxPacketSize == 0x10)) {
|
|
+ ttusbir->alt_setting = alt_set;
|
|
+ ttusbir->endpoint = endp;
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (ttusbir->alt_setting != -1)
|
|
+ DPRINTK("alt setting: %d\n", ttusbir->alt_setting);
|
|
+ else {
|
|
+ err("Could not find alternate setting\n");
|
|
+ kfree(ttusbir);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* OK lets setup this interface setting */
|
|
+ usb_set_interface(ttusbir->udev, 0, ttusbir->alt_setting);
|
|
+
|
|
+ /* Store device info in interface structure */
|
|
+ usb_set_intfdata(intf, ttusbir);
|
|
+
|
|
+ /* Register as a LIRC driver */
|
|
+ if (lirc_buffer_init(&ttusbir->rbuf, sizeof(int), 256) < 0) {
|
|
+ err("Could not get memory for LIRC data buffer\n");
|
|
+ usb_set_intfdata(intf, NULL);
|
|
+ kfree(ttusbir);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ strcpy(ttusbir->driver.name, "TTUSBIR");
|
|
+ ttusbir->driver.minor = -1;
|
|
+ ttusbir->driver.code_length = 1;
|
|
+ ttusbir->driver.sample_rate = 0;
|
|
+ ttusbir->driver.data = ttusbir;
|
|
+ ttusbir->driver.add_to_buf = NULL;
|
|
+ ttusbir->driver.rbuf = &ttusbir->rbuf;
|
|
+ ttusbir->driver.set_use_inc = set_use_inc;
|
|
+ ttusbir->driver.set_use_dec = set_use_dec;
|
|
+ ttusbir->driver.dev = &intf->dev;
|
|
+ ttusbir->driver.owner = THIS_MODULE;
|
|
+ ttusbir->driver.features = LIRC_CAN_REC_MODE2;
|
|
+ ttusbir->minor = lirc_register_driver(&ttusbir->driver);
|
|
+ if (ttusbir->minor < 0) {
|
|
+ err("Error registering as LIRC driver\n");
|
|
+ usb_set_intfdata(intf, NULL);
|
|
+ lirc_buffer_free(&ttusbir->rbuf);
|
|
+ kfree(ttusbir);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ /* Allocate and setup the URB that we will use to talk to the device */
|
|
+ for (i = 0; i < num_urbs; i++) {
|
|
+ ttusbir->urb[i] = usb_alloc_urb(8, GFP_KERNEL);
|
|
+ if (!ttusbir->urb[i]) {
|
|
+ err("Could not allocate memory for the URB\n");
|
|
+ for (j = i - 1; j >= 0; j--)
|
|
+ kfree(ttusbir->urb[j]);
|
|
+ lirc_buffer_free(&ttusbir->rbuf);
|
|
+ lirc_unregister_driver(ttusbir->minor);
|
|
+ kfree(ttusbir);
|
|
+ usb_set_intfdata(intf, NULL);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ ttusbir->urb[i]->dev = ttusbir->udev;
|
|
+ ttusbir->urb[i]->context = ttusbir;
|
|
+ ttusbir->urb[i]->pipe = usb_rcvisocpipe(ttusbir->udev,
|
|
+ ttusbir->endpoint);
|
|
+ ttusbir->urb[i]->interval = 1;
|
|
+ ttusbir->urb[i]->transfer_flags = URB_ISO_ASAP;
|
|
+ ttusbir->urb[i]->transfer_buffer = &ttusbir->buffer[i][0];
|
|
+ ttusbir->urb[i]->complete = urb_complete;
|
|
+ ttusbir->urb[i]->number_of_packets = 8;
|
|
+ ttusbir->urb[i]->transfer_buffer_length = 128;
|
|
+ for (j = 0; j < 8; j++) {
|
|
+ ttusbir->urb[i]->iso_frame_desc[j].offset = j*16;
|
|
+ ttusbir->urb[i]->iso_frame_desc[j].length = 16;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called when the driver is unloaded or the device is unplugged
|
|
+ */
|
|
+static void disconnect(struct usb_interface *intf)
|
|
+{
|
|
+ int i;
|
|
+ struct ttusbir_device *ttusbir;
|
|
+
|
|
+ DPRINTK("Module ttusbir disconnect\n");
|
|
+
|
|
+ ttusbir = (struct ttusbir_device *) usb_get_intfdata(intf);
|
|
+ usb_set_intfdata(intf, NULL);
|
|
+ lirc_unregister_driver(ttusbir->minor);
|
|
+ DPRINTK("unregistered\n");
|
|
+
|
|
+ for (i = 0; i < num_urbs; i++) {
|
|
+ usb_kill_urb(ttusbir->urb[i]);
|
|
+ usb_free_urb(ttusbir->urb[i]);
|
|
+ }
|
|
+ DPRINTK("URBs killed\n");
|
|
+ lirc_buffer_free(&ttusbir->rbuf);
|
|
+ kfree(ttusbir);
|
|
+}
|
|
+
|
|
+static int ttusbir_init_module(void)
|
|
+{
|
|
+ int result;
|
|
+
|
|
+ DPRINTK(KERN_DEBUG "Module ttusbir init\n");
|
|
+
|
|
+ /* register this driver with the USB subsystem */
|
|
+ result = usb_register(&usb_driver);
|
|
+ if (result)
|
|
+ err("usb_register failed. Error number %d", result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static void ttusbir_exit_module(void)
|
|
+{
|
|
+ printk(KERN_DEBUG "Module ttusbir exit\n");
|
|
+ usb_deregister(&usb_driver);
|
|
+}
|
|
+
|
|
+module_init(ttusbir_init_module);
|
|
+module_exit(ttusbir_exit_module);
|
|
diff --git a/drivers/input/lirc/lirc_zilog.c b/drivers/input/lirc/lirc_zilog.c
|
|
new file mode 100644
|
|
index 0000000..3a5bc34
|
|
--- /dev/null
|
|
+++ b/drivers/input/lirc/lirc_zilog.c
|
|
@@ -0,0 +1,1396 @@
|
|
+/*
|
|
+ * i2c IR lirc driver for devices with zilog IR processors
|
|
+ *
|
|
+ * Copyright (c) 2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
|
|
+ * modified for PixelView (BT878P+W/FM) by
|
|
+ * Michal Kochanowicz <mkochano@pld.org.pl>
|
|
+ * Christoph Bartelmus <lirc@bartelmus.de>
|
|
+ * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by
|
|
+ * Ulrich Mueller <ulrich.mueller42@web.de>
|
|
+ * modified for Asus TV-Box and Creative/VisionTek BreakOut-Box by
|
|
+ * Stefan Jahn <stefan@lkcc.org>
|
|
+ * modified for inclusion into kernel sources by
|
|
+ * Jerome Brock <jbrock@users.sourceforge.net>
|
|
+ * modified for Leadtek Winfast PVR2000 by
|
|
+ * Thomas Reitmayr (treitmayr@yahoo.com)
|
|
+ * modified for Hauppauge PVR-150 IR TX device by
|
|
+ * Mark Weaver <mark@npsl.co.uk>
|
|
+ * changed name from lirc_pvr150 to lirc_zilog, works on more than pvr-150
|
|
+ * Jarod Wilson <jarod@redhat.com>
|
|
+ *
|
|
+ * parts are cut&pasted from the lirc_i2c.c driver
|
|
+ *
|
|
+ * 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/version.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/kmod.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/timer.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/firmware.h>
|
|
+#include <linux/vmalloc.h>
|
|
+
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/kthread.h>
|
|
+
|
|
+#include "lirc_dev.h"
|
|
+#include <linux/lirc.h>
|
|
+
|
|
+struct IR {
|
|
+ struct lirc_driver l;
|
|
+
|
|
+ /* Device info */
|
|
+ struct mutex ir_lock;
|
|
+ int open;
|
|
+
|
|
+ /* RX device */
|
|
+ struct i2c_client c_rx;
|
|
+ int have_rx;
|
|
+
|
|
+ /* RX device buffer & lock */
|
|
+ struct lirc_buffer buf;
|
|
+ struct mutex buf_lock;
|
|
+
|
|
+ /* RX polling thread data */
|
|
+ struct completion *t_notify;
|
|
+ struct completion *t_notify2;
|
|
+ int shutdown;
|
|
+ struct task_struct *task;
|
|
+
|
|
+ /* RX read data */
|
|
+ unsigned char b[3];
|
|
+
|
|
+ /* TX device */
|
|
+ struct i2c_client c_tx;
|
|
+ int need_boot;
|
|
+ int have_tx;
|
|
+};
|
|
+
|
|
+/* Minor -> data mapping */
|
|
+static struct IR *ir_devices[MAX_IRCTL_DEVICES];
|
|
+
|
|
+/* Block size for IR transmitter */
|
|
+#define TX_BLOCK_SIZE 99
|
|
+
|
|
+/* Hauppauge IR transmitter data */
|
|
+struct tx_data_struct {
|
|
+ /* Boot block */
|
|
+ unsigned char *boot_data;
|
|
+
|
|
+ /* Start of binary data block */
|
|
+ unsigned char *datap;
|
|
+
|
|
+ /* End of binary data block */
|
|
+ unsigned char *endp;
|
|
+
|
|
+ /* Number of installed codesets */
|
|
+ unsigned int num_code_sets;
|
|
+
|
|
+ /* Pointers to codesets */
|
|
+ unsigned char **code_sets;
|
|
+
|
|
+ /* Global fixed data template */
|
|
+ int fixed[TX_BLOCK_SIZE];
|
|
+};
|
|
+
|
|
+static struct tx_data_struct *tx_data;
|
|
+static struct mutex tx_data_lock;
|
|
+
|
|
+#define zilog_notify(s, args...) printk(KERN_NOTICE KBUILD_MODNAME ": " s, \
|
|
+ ## args)
|
|
+#define zilog_error(s, args...) printk(KERN_ERR KBUILD_MODNAME ": " s, ## args)
|
|
+
|
|
+#define ZILOG_HAUPPAUGE_IR_RX_NAME "Zilog/Hauppauge IR RX"
|
|
+#define ZILOG_HAUPPAUGE_IR_TX_NAME "Zilog/Hauppauge IR TX"
|
|
+
|
|
+/* module parameters */
|
|
+static int debug; /* debug output */
|
|
+static int disable_rx; /* disable RX device */
|
|
+static int disable_tx; /* disable TX device */
|
|
+static int minor = -1; /* minor number */
|
|
+
|
|
+#define dprintk(fmt, args...) \
|
|
+ do { \
|
|
+ if (debug) \
|
|
+ printk(KERN_DEBUG KBUILD_MODNAME ": " fmt, \
|
|
+ ## args); \
|
|
+ } while (0)
|
|
+
|
|
+static int add_to_buf(struct IR *ir)
|
|
+{
|
|
+ __u16 code;
|
|
+ unsigned char codes[2];
|
|
+ unsigned char keybuf[6];
|
|
+ int got_data = 0;
|
|
+ int ret;
|
|
+ int failures = 0;
|
|
+ unsigned char sendbuf[1] = { 0 };
|
|
+
|
|
+ if (lirc_buffer_full(&ir->buf)) {
|
|
+ dprintk("buffer overflow\n");
|
|
+ return -EOVERFLOW;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * service the device as long as it is returning
|
|
+ * data and we have space
|
|
+ */
|
|
+ do {
|
|
+ /*
|
|
+ * Lock i2c bus for the duration. RX/TX chips interfere so
|
|
+ * this is worth it
|
|
+ */
|
|
+ mutex_lock(&ir->ir_lock);
|
|
+
|
|
+ /*
|
|
+ * Send random "poll command" (?) Windows driver does this
|
|
+ * and it is a good point to detect chip failure.
|
|
+ */
|
|
+ ret = i2c_master_send(&ir->c_rx, sendbuf, 1);
|
|
+ if (ret != 1) {
|
|
+ zilog_error("i2c_master_send failed with %d\n", ret);
|
|
+ if (failures >= 3) {
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ zilog_error("unable to read from the IR chip "
|
|
+ "after 3 resets, giving up\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Looks like the chip crashed, reset it */
|
|
+ zilog_error("polling the IR receiver chip failed, "
|
|
+ "trying reset\n");
|
|
+
|
|
+ set_current_state(TASK_UNINTERRUPTIBLE);
|
|
+ schedule_timeout((100 * HZ + 999) / 1000);
|
|
+ ir->need_boot = 1;
|
|
+
|
|
+ ++failures;
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ret = i2c_master_recv(&ir->c_rx, keybuf, sizeof(keybuf));
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ if (ret != sizeof(keybuf)) {
|
|
+ zilog_error("i2c_master_recv failed with %d -- "
|
|
+ "keeping last read buffer\n", ret);
|
|
+ } else {
|
|
+ ir->b[0] = keybuf[3];
|
|
+ ir->b[1] = keybuf[4];
|
|
+ ir->b[2] = keybuf[5];
|
|
+ dprintk("key (0x%02x/0x%02x)\n", ir->b[0], ir->b[1]);
|
|
+ }
|
|
+
|
|
+ /* key pressed ? */
|
|
+#ifdef I2C_HW_B_HDPVR
|
|
+ if (ir->c_rx.adapter->id == I2C_HW_B_HDPVR) {
|
|
+ if (got_data && (keybuf[0] == 0x80))
|
|
+ return 0;
|
|
+ else if (got_data && (keybuf[0] == 0x00))
|
|
+ return -ENODATA;
|
|
+ } else if ((ir->b[0] & 0x80) == 0)
|
|
+#else
|
|
+ if ((ir->b[0] & 0x80) == 0)
|
|
+#endif
|
|
+ return got_data ? 0 : -ENODATA;
|
|
+
|
|
+ /* look what we have */
|
|
+ code = (((__u16)ir->b[0] & 0x7f) << 6) | (ir->b[1] >> 2);
|
|
+
|
|
+ codes[0] = (code >> 8) & 0xff;
|
|
+ codes[1] = code & 0xff;
|
|
+
|
|
+ /* return it */
|
|
+ lirc_buffer_write(&ir->buf, codes);
|
|
+ ++got_data;
|
|
+ } while (!lirc_buffer_full(&ir->buf));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Main function of the polling thread -- from lirc_dev.
|
|
+ * We don't fit the LIRC model at all anymore. This is horrible, but
|
|
+ * basically we have a single RX/TX device with a nasty failure mode
|
|
+ * that needs to be accounted for across the pair. lirc lets us provide
|
|
+ * fops, but prevents us from using the internal polling, etc. if we do
|
|
+ * so. Hence the replication. Might be neater to extend the LIRC model
|
|
+ * to account for this but I'd think it's a very special case of seriously
|
|
+ * messed up hardware.
|
|
+ */
|
|
+static int lirc_thread(void *arg)
|
|
+{
|
|
+ struct IR *ir = arg;
|
|
+
|
|
+ if (ir->t_notify != NULL)
|
|
+ complete(ir->t_notify);
|
|
+
|
|
+ dprintk("poll thread started\n");
|
|
+
|
|
+ do {
|
|
+ if (ir->open) {
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+
|
|
+ /*
|
|
+ * This is ~113*2 + 24 + jitter (2*repeat gap +
|
|
+ * code length). We use this interval as the chip
|
|
+ * resets every time you poll it (bad!). This is
|
|
+ * therefore just sufficient to catch all of the
|
|
+ * button presses. It makes the remote much more
|
|
+ * responsive. You can see the difference by
|
|
+ * running irw and holding down a button. With
|
|
+ * 100ms, the old polling interval, you'll notice
|
|
+ * breaks in the repeat sequence corresponding to
|
|
+ * lost keypresses.
|
|
+ */
|
|
+ schedule_timeout((260 * HZ) / 1000);
|
|
+ if (ir->shutdown)
|
|
+ break;
|
|
+ if (!add_to_buf(ir))
|
|
+ wake_up_interruptible(&ir->buf.wait_poll);
|
|
+ } else {
|
|
+ /* if device not opened so we can sleep half a second */
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ schedule_timeout(HZ/2);
|
|
+ }
|
|
+ } while (!ir->shutdown);
|
|
+
|
|
+ if (ir->t_notify2 != NULL)
|
|
+ wait_for_completion(ir->t_notify2);
|
|
+
|
|
+ ir->task = NULL;
|
|
+ if (ir->t_notify != NULL)
|
|
+ complete(ir->t_notify);
|
|
+
|
|
+ dprintk("poll thread ended\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int set_use_inc(void *data)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+
|
|
+ if (ir->l.owner == NULL || try_module_get(ir->l.owner) == 0)
|
|
+ return -ENODEV;
|
|
+
|
|
+ /* lock bttv in memory while /dev/lirc is in use */
|
|
+ /*
|
|
+ * this is completely broken code. lirc_unregister_driver()
|
|
+ * must be possible even when the device is open
|
|
+ */
|
|
+ if (ir->c_rx.addr)
|
|
+ i2c_use_client(&ir->c_rx);
|
|
+ if (ir->c_tx.addr)
|
|
+ i2c_use_client(&ir->c_tx);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void set_use_dec(void *data)
|
|
+{
|
|
+ struct IR *ir = data;
|
|
+
|
|
+ if (ir->c_rx.addr)
|
|
+ i2c_release_client(&ir->c_rx);
|
|
+ if (ir->c_tx.addr)
|
|
+ i2c_release_client(&ir->c_tx);
|
|
+ if (ir->l.owner != NULL)
|
|
+ module_put(ir->l.owner);
|
|
+}
|
|
+
|
|
+/* safe read of a uint32 (always network byte order) */
|
|
+static int read_uint32(unsigned char **data,
|
|
+ unsigned char *endp, unsigned int *val)
|
|
+{
|
|
+ if (*data + 4 > endp)
|
|
+ return 0;
|
|
+ *val = ((*data)[0] << 24) | ((*data)[1] << 16) |
|
|
+ ((*data)[2] << 8) | (*data)[3];
|
|
+ *data += 4;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+/* safe read of a uint8 */
|
|
+static int read_uint8(unsigned char **data,
|
|
+ unsigned char *endp, unsigned char *val)
|
|
+{
|
|
+ if (*data + 1 > endp)
|
|
+ return 0;
|
|
+ *val = *((*data)++);
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+/* safe skipping of N bytes */
|
|
+static int skip(unsigned char **data,
|
|
+ unsigned char *endp, unsigned int distance)
|
|
+{
|
|
+ if (*data + distance > endp)
|
|
+ return 0;
|
|
+ *data += distance;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+/* decompress key data into the given buffer */
|
|
+static int get_key_data(unsigned char *buf,
|
|
+ unsigned int codeset, unsigned int key)
|
|
+{
|
|
+ unsigned char *data, *endp, *diffs, *key_block;
|
|
+ unsigned char keys, ndiffs, id;
|
|
+ unsigned int base, lim, pos, i;
|
|
+
|
|
+ /* Binary search for the codeset */
|
|
+ for (base = 0, lim = tx_data->num_code_sets; lim; lim >>= 1) {
|
|
+ pos = base + (lim >> 1);
|
|
+ data = tx_data->code_sets[pos];
|
|
+
|
|
+ if (!read_uint32(&data, tx_data->endp, &i))
|
|
+ goto corrupt;
|
|
+
|
|
+ if (i == codeset)
|
|
+ break;
|
|
+ else if (codeset > i) {
|
|
+ base = pos + 1;
|
|
+ --lim;
|
|
+ }
|
|
+ }
|
|
+ /* Not found? */
|
|
+ if (!lim)
|
|
+ return -EPROTO;
|
|
+
|
|
+ /* Set end of data block */
|
|
+ endp = pos < tx_data->num_code_sets - 1 ?
|
|
+ tx_data->code_sets[pos + 1] : tx_data->endp;
|
|
+
|
|
+ /* Read the block header */
|
|
+ if (!read_uint8(&data, endp, &keys) ||
|
|
+ !read_uint8(&data, endp, &ndiffs) ||
|
|
+ ndiffs > TX_BLOCK_SIZE || keys == 0)
|
|
+ goto corrupt;
|
|
+
|
|
+ /* Save diffs & skip */
|
|
+ diffs = data;
|
|
+ if (!skip(&data, endp, ndiffs))
|
|
+ goto corrupt;
|
|
+
|
|
+ /* Read the id of the first key */
|
|
+ if (!read_uint8(&data, endp, &id))
|
|
+ goto corrupt;
|
|
+
|
|
+ /* Unpack the first key's data */
|
|
+ for (i = 0; i < TX_BLOCK_SIZE; ++i) {
|
|
+ if (tx_data->fixed[i] == -1) {
|
|
+ if (!read_uint8(&data, endp, &buf[i]))
|
|
+ goto corrupt;
|
|
+ } else {
|
|
+ buf[i] = (unsigned char)tx_data->fixed[i];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Early out key found/not found */
|
|
+ if (key == id)
|
|
+ return 0;
|
|
+ if (keys == 1)
|
|
+ return -EPROTO;
|
|
+
|
|
+ /* Sanity check */
|
|
+ key_block = data;
|
|
+ if (!skip(&data, endp, (keys - 1) * (ndiffs + 1)))
|
|
+ goto corrupt;
|
|
+
|
|
+ /* Binary search for the key */
|
|
+ for (base = 0, lim = keys - 1; lim; lim >>= 1) {
|
|
+ /* Seek to block */
|
|
+ unsigned char *key_data;
|
|
+ pos = base + (lim >> 1);
|
|
+ key_data = key_block + (ndiffs + 1) * pos;
|
|
+
|
|
+ if (*key_data == key) {
|
|
+ /* skip key id */
|
|
+ ++key_data;
|
|
+
|
|
+ /* found, so unpack the diffs */
|
|
+ for (i = 0; i < ndiffs; ++i) {
|
|
+ unsigned char val;
|
|
+ if (!read_uint8(&key_data, endp, &val) ||
|
|
+ diffs[i] >= TX_BLOCK_SIZE)
|
|
+ goto corrupt;
|
|
+ buf[diffs[i]] = val;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+ } else if (key > *key_data) {
|
|
+ base = pos + 1;
|
|
+ --lim;
|
|
+ }
|
|
+ }
|
|
+ /* Key not found */
|
|
+ return -EPROTO;
|
|
+
|
|
+corrupt:
|
|
+ zilog_error("firmware is corrupt\n");
|
|
+ return -EFAULT;
|
|
+}
|
|
+
|
|
+/* send a block of data to the IR TX device */
|
|
+static int send_data_block(struct IR *ir, unsigned char *data_block)
|
|
+{
|
|
+ int i, j, ret;
|
|
+ unsigned char buf[5];
|
|
+
|
|
+ for (i = 0; i < TX_BLOCK_SIZE;) {
|
|
+ int tosend = TX_BLOCK_SIZE - i;
|
|
+ if (tosend > 4)
|
|
+ tosend = 4;
|
|
+ buf[0] = (unsigned char)(i + 1);
|
|
+ for (j = 0; j < tosend; ++j)
|
|
+ buf[1 + j] = data_block[i + j];
|
|
+ dprintk("%02x %02x %02x %02x %02x",
|
|
+ buf[0], buf[1], buf[2], buf[3], buf[4]);
|
|
+ ret = i2c_master_send(&ir->c_tx, buf, tosend + 1);
|
|
+ if (ret != tosend + 1) {
|
|
+ zilog_error("i2c_master_send failed with %d\n", ret);
|
|
+ return ret < 0 ? ret : -EFAULT;
|
|
+ }
|
|
+ i += tosend;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* send boot data to the IR TX device */
|
|
+static int send_boot_data(struct IR *ir)
|
|
+{
|
|
+ int ret;
|
|
+ unsigned char buf[4];
|
|
+
|
|
+ /* send the boot block */
|
|
+ ret = send_data_block(ir, tx_data->boot_data);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ /* kick it off? */
|
|
+ buf[0] = 0x00;
|
|
+ buf[1] = 0x20;
|
|
+ ret = i2c_master_send(&ir->c_tx, buf, 2);
|
|
+ if (ret != 2) {
|
|
+ zilog_error("i2c_master_send failed with %d\n", ret);
|
|
+ return ret < 0 ? ret : -EFAULT;
|
|
+ }
|
|
+ ret = i2c_master_send(&ir->c_tx, buf, 1);
|
|
+ if (ret != 1) {
|
|
+ zilog_error("i2c_master_send failed with %d\n", ret);
|
|
+ return ret < 0 ? ret : -EFAULT;
|
|
+ }
|
|
+
|
|
+ /* Here comes the firmware version... (hopefully) */
|
|
+ ret = i2c_master_recv(&ir->c_tx, buf, 4);
|
|
+ if (ret != 4) {
|
|
+ zilog_error("i2c_master_recv failed with %d\n", ret);
|
|
+ return 0;
|
|
+ }
|
|
+ if (buf[0] != 0x80) {
|
|
+ zilog_error("unexpected IR TX response: %02x\n", buf[0]);
|
|
+ return 0;
|
|
+ }
|
|
+ zilog_notify("Zilog/Hauppauge IR blaster firmware version "
|
|
+ "%d.%d.%d loaded\n", buf[1], buf[2], buf[3]);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* unload "firmware", lock held */
|
|
+static void fw_unload_locked(void)
|
|
+{
|
|
+ if (tx_data) {
|
|
+ if (tx_data->code_sets)
|
|
+ vfree(tx_data->code_sets);
|
|
+
|
|
+ if (tx_data->datap)
|
|
+ vfree(tx_data->datap);
|
|
+
|
|
+ vfree(tx_data);
|
|
+ tx_data = NULL;
|
|
+ dprintk("successfully unloaded IR blaster firmware\n");
|
|
+ }
|
|
+}
|
|
+
|
|
+/* unload "firmware" for the IR TX device */
|
|
+static void fw_unload(void)
|
|
+{
|
|
+ mutex_lock(&tx_data_lock);
|
|
+ fw_unload_locked();
|
|
+ mutex_unlock(&tx_data_lock);
|
|
+}
|
|
+
|
|
+/* load "firmware" for the IR TX device */
|
|
+static int fw_load(struct IR *ir)
|
|
+{
|
|
+ int ret;
|
|
+ unsigned int i;
|
|
+ unsigned char *data, version, num_global_fixed;
|
|
+ const struct firmware *fw_entry;
|
|
+
|
|
+ /* Already loaded? */
|
|
+ mutex_lock(&tx_data_lock);
|
|
+ if (tx_data) {
|
|
+ ret = 0;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* Request codeset data file */
|
|
+ ret = request_firmware(&fw_entry, "haup-ir-blaster.bin", &ir->c_tx.dev);
|
|
+ if (ret != 0) {
|
|
+ zilog_error("firmware haup-ir-blaster.bin not available "
|
|
+ "(%d)\n", ret);
|
|
+ ret = ret < 0 ? ret : -EFAULT;
|
|
+ goto out;
|
|
+ }
|
|
+ dprintk("firmware of size %zu loaded\n", fw_entry->size);
|
|
+
|
|
+ /* Parse the file */
|
|
+ tx_data = vmalloc(sizeof(*tx_data));
|
|
+ if (tx_data == NULL) {
|
|
+ zilog_error("out of memory\n");
|
|
+ release_firmware(fw_entry);
|
|
+ ret = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+ tx_data->code_sets = NULL;
|
|
+
|
|
+ /* Copy the data so hotplug doesn't get confused and timeout */
|
|
+ tx_data->datap = vmalloc(fw_entry->size);
|
|
+ if (tx_data->datap == NULL) {
|
|
+ zilog_error("out of memory\n");
|
|
+ release_firmware(fw_entry);
|
|
+ vfree(tx_data);
|
|
+ ret = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+ memcpy(tx_data->datap, fw_entry->data, fw_entry->size);
|
|
+ tx_data->endp = tx_data->datap + fw_entry->size;
|
|
+ release_firmware(fw_entry); fw_entry = NULL;
|
|
+
|
|
+ /* Check version */
|
|
+ data = tx_data->datap;
|
|
+ if (!read_uint8(&data, tx_data->endp, &version))
|
|
+ goto corrupt;
|
|
+ if (version != 1) {
|
|
+ zilog_error("unsupported code set file version (%u, expected"
|
|
+ "1) -- please upgrade to a newer driver",
|
|
+ version);
|
|
+ fw_unload_locked();
|
|
+ ret = -EFAULT;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* Save boot block for later */
|
|
+ tx_data->boot_data = data;
|
|
+ if (!skip(&data, tx_data->endp, TX_BLOCK_SIZE))
|
|
+ goto corrupt;
|
|
+
|
|
+ if (!read_uint32(&data, tx_data->endp,
|
|
+ &tx_data->num_code_sets))
|
|
+ goto corrupt;
|
|
+
|
|
+ dprintk("%u IR blaster codesets loaded\n", tx_data->num_code_sets);
|
|
+
|
|
+ tx_data->code_sets = vmalloc(
|
|
+ tx_data->num_code_sets * sizeof(char *));
|
|
+ if (tx_data->code_sets == NULL) {
|
|
+ fw_unload_locked();
|
|
+ ret = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < TX_BLOCK_SIZE; ++i)
|
|
+ tx_data->fixed[i] = -1;
|
|
+
|
|
+ /* Read global fixed data template */
|
|
+ if (!read_uint8(&data, tx_data->endp, &num_global_fixed) ||
|
|
+ num_global_fixed > TX_BLOCK_SIZE)
|
|
+ goto corrupt;
|
|
+ for (i = 0; i < num_global_fixed; ++i) {
|
|
+ unsigned char pos, val;
|
|
+ if (!read_uint8(&data, tx_data->endp, &pos) ||
|
|
+ !read_uint8(&data, tx_data->endp, &val) ||
|
|
+ pos >= TX_BLOCK_SIZE)
|
|
+ goto corrupt;
|
|
+ tx_data->fixed[pos] = (int)val;
|
|
+ }
|
|
+
|
|
+ /* Filch out the position of each code set */
|
|
+ for (i = 0; i < tx_data->num_code_sets; ++i) {
|
|
+ unsigned int id;
|
|
+ unsigned char keys;
|
|
+ unsigned char ndiffs;
|
|
+
|
|
+ /* Save the codeset position */
|
|
+ tx_data->code_sets[i] = data;
|
|
+
|
|
+ /* Read header */
|
|
+ if (!read_uint32(&data, tx_data->endp, &id) ||
|
|
+ !read_uint8(&data, tx_data->endp, &keys) ||
|
|
+ !read_uint8(&data, tx_data->endp, &ndiffs) ||
|
|
+ ndiffs > TX_BLOCK_SIZE || keys == 0)
|
|
+ goto corrupt;
|
|
+
|
|
+ /* skip diff positions */
|
|
+ if (!skip(&data, tx_data->endp, ndiffs))
|
|
+ goto corrupt;
|
|
+
|
|
+ /*
|
|
+ * After the diffs we have the first key id + data -
|
|
+ * global fixed
|
|
+ */
|
|
+ if (!skip(&data, tx_data->endp,
|
|
+ 1 + TX_BLOCK_SIZE - num_global_fixed))
|
|
+ goto corrupt;
|
|
+
|
|
+ /* Then we have keys-1 blocks of key id+diffs */
|
|
+ if (!skip(&data, tx_data->endp,
|
|
+ (ndiffs + 1) * (keys - 1)))
|
|
+ goto corrupt;
|
|
+ }
|
|
+ ret = 0;
|
|
+ goto out;
|
|
+
|
|
+corrupt:
|
|
+ zilog_error("firmware is corrupt\n");
|
|
+ fw_unload_locked();
|
|
+ ret = -EFAULT;
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&tx_data_lock);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* initialise the IR TX device */
|
|
+static int tx_init(struct IR *ir)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /* Load 'firmware' */
|
|
+ ret = fw_load(ir);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Send boot block */
|
|
+ ret = send_boot_data(ir);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+ ir->need_boot = 0;
|
|
+
|
|
+ /* Looks good */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* do nothing stub to make LIRC happy */
|
|
+static loff_t lseek(struct file *filep, loff_t offset, int orig)
|
|
+{
|
|
+ return -ESPIPE;
|
|
+}
|
|
+
|
|
+/* copied from lirc_dev */
|
|
+static ssize_t read(struct file *filep, char *outbuf, size_t n, loff_t *ppos)
|
|
+{
|
|
+ struct IR *ir = (struct IR *)filep->private_data;
|
|
+ unsigned char buf[ir->buf.chunk_size];
|
|
+ int ret = 0, written = 0;
|
|
+ DECLARE_WAITQUEUE(wait, current);
|
|
+
|
|
+ dprintk("read called\n");
|
|
+ if (ir->c_rx.addr == 0)
|
|
+ return -ENODEV;
|
|
+
|
|
+ if (mutex_lock_interruptible(&ir->buf_lock))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ if (n % ir->buf.chunk_size) {
|
|
+ dprintk("read result = -EINVAL\n");
|
|
+ mutex_unlock(&ir->buf_lock);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * we add ourselves to the task queue before buffer check
|
|
+ * to avoid losing scan code (in case when queue is awaken somewhere
|
|
+ * between while condition checking and scheduling)
|
|
+ */
|
|
+ add_wait_queue(&ir->buf.wait_poll, &wait);
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+
|
|
+ /*
|
|
+ * while we didn't provide 'length' bytes, device is opened in blocking
|
|
+ * mode and 'copy_to_user' is happy, wait for data.
|
|
+ */
|
|
+ while (written < n && ret == 0) {
|
|
+ if (lirc_buffer_empty(&ir->buf)) {
|
|
+ /*
|
|
+ * According to the read(2) man page, 'written' can be
|
|
+ * returned as less than 'n', instead of blocking
|
|
+ * again, returning -EWOULDBLOCK, or returning
|
|
+ * -ERESTARTSYS
|
|
+ */
|
|
+ if (written)
|
|
+ break;
|
|
+ if (filep->f_flags & O_NONBLOCK) {
|
|
+ ret = -EWOULDBLOCK;
|
|
+ break;
|
|
+ }
|
|
+ if (signal_pending(current)) {
|
|
+ ret = -ERESTARTSYS;
|
|
+ break;
|
|
+ }
|
|
+ schedule();
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ } else {
|
|
+ lirc_buffer_read(&ir->buf, buf);
|
|
+ ret = copy_to_user((void *)outbuf+written, buf,
|
|
+ ir->buf.chunk_size);
|
|
+ written += ir->buf.chunk_size;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ remove_wait_queue(&ir->buf.wait_poll, &wait);
|
|
+ set_current_state(TASK_RUNNING);
|
|
+ mutex_unlock(&ir->buf_lock);
|
|
+
|
|
+ dprintk("read result = %s (%d)\n",
|
|
+ ret ? "-EFAULT" : "OK", ret);
|
|
+
|
|
+ return ret ? ret : written;
|
|
+}
|
|
+
|
|
+/* send a keypress to the IR TX device */
|
|
+static int send_code(struct IR *ir, unsigned int code, unsigned int key)
|
|
+{
|
|
+ unsigned char data_block[TX_BLOCK_SIZE];
|
|
+ unsigned char buf[2];
|
|
+ int i, ret;
|
|
+
|
|
+ /* Get data for the codeset/key */
|
|
+ ret = get_key_data(data_block, code, key);
|
|
+
|
|
+ if (ret == -EPROTO) {
|
|
+ zilog_error("failed to get data for code %u, key %u -- check "
|
|
+ "lircd.conf entries\n", code, key);
|
|
+ return ret;
|
|
+ } else if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Send the data block */
|
|
+ ret = send_data_block(ir, data_block);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ /* Send data block length? */
|
|
+ buf[0] = 0x00;
|
|
+ buf[1] = 0x40;
|
|
+ ret = i2c_master_send(&ir->c_tx, buf, 2);
|
|
+ if (ret != 2) {
|
|
+ zilog_error("i2c_master_send failed with %d\n", ret);
|
|
+ return ret < 0 ? ret : -EFAULT;
|
|
+ }
|
|
+ ret = i2c_master_send(&ir->c_tx, buf, 1);
|
|
+ if (ret != 1) {
|
|
+ zilog_error("i2c_master_send failed with %d\n", ret);
|
|
+ return ret < 0 ? ret : -EFAULT;
|
|
+ }
|
|
+
|
|
+ /* Send finished download? */
|
|
+ ret = i2c_master_recv(&ir->c_tx, buf, 1);
|
|
+ if (ret != 1) {
|
|
+ zilog_error("i2c_master_recv failed with %d\n", ret);
|
|
+ return ret < 0 ? ret : -EFAULT;
|
|
+ }
|
|
+ if (buf[0] != 0xA0) {
|
|
+ zilog_error("unexpected IR TX response #1: %02x\n",
|
|
+ buf[0]);
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ /* Send prepare command? */
|
|
+ buf[0] = 0x00;
|
|
+ buf[1] = 0x80;
|
|
+ ret = i2c_master_send(&ir->c_tx, buf, 2);
|
|
+ if (ret != 2) {
|
|
+ zilog_error("i2c_master_send failed with %d\n", ret);
|
|
+ return ret < 0 ? ret : -EFAULT;
|
|
+ }
|
|
+
|
|
+#ifdef I2C_HW_B_HDPVR
|
|
+ /*
|
|
+ * The sleep bits aren't necessary on the HD PVR, and in fact, the
|
|
+ * last i2c_master_recv always fails with a -5, so for now, we're
|
|
+ * going to skip this whole mess and say we're done on the HD PVR
|
|
+ */
|
|
+ if (ir->c_rx.adapter->id == I2C_HW_B_HDPVR)
|
|
+ goto done;
|
|
+#endif
|
|
+
|
|
+ /*
|
|
+ * This bit NAKs until the device is ready, so we retry it
|
|
+ * sleeping a bit each time. This seems to be what the windows
|
|
+ * driver does, approximately.
|
|
+ * Try for up to 1s.
|
|
+ */
|
|
+ for (i = 0; i < 20; ++i) {
|
|
+ set_current_state(TASK_UNINTERRUPTIBLE);
|
|
+ schedule_timeout((50 * HZ + 999) / 1000);
|
|
+ ret = i2c_master_send(&ir->c_tx, buf, 1);
|
|
+ if (ret == 1)
|
|
+ break;
|
|
+ dprintk("NAK expected: i2c_master_send "
|
|
+ "failed with %d (try %d)\n", ret, i+1);
|
|
+ }
|
|
+ if (ret != 1) {
|
|
+ zilog_error("IR TX chip never got ready: last i2c_master_send "
|
|
+ "failed with %d\n", ret);
|
|
+ return ret < 0 ? ret : -EFAULT;
|
|
+ }
|
|
+
|
|
+ /* Seems to be an 'ok' response */
|
|
+ i = i2c_master_recv(&ir->c_tx, buf, 1);
|
|
+ if (i != 1) {
|
|
+ zilog_error("i2c_master_recv failed with %d\n", ret);
|
|
+ return -EFAULT;
|
|
+ }
|
|
+ if (buf[0] != 0x80) {
|
|
+ zilog_error("unexpected IR TX response #2: %02x\n", buf[0]);
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+done:
|
|
+ /* Oh good, it worked */
|
|
+ dprintk("sent code %u, key %u\n", code, key);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Write a code to the device. We take in a 32-bit number (an int) and then
|
|
+ * decode this to a codeset/key index. The key data is then decompressed and
|
|
+ * sent to the device. We have a spin lock as per i2c documentation to prevent
|
|
+ * multiple concurrent sends which would probably cause the device to explode.
|
|
+ */
|
|
+static ssize_t write(struct file *filep, const char *buf, size_t n,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ struct IR *ir = (struct IR *)filep->private_data;
|
|
+ size_t i;
|
|
+ int failures = 0;
|
|
+
|
|
+ if (ir->c_tx.addr == 0)
|
|
+ return -ENODEV;
|
|
+
|
|
+ /* Validate user parameters */
|
|
+ if (n % sizeof(int))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* Lock i2c bus for the duration */
|
|
+ mutex_lock(&ir->ir_lock);
|
|
+
|
|
+ /* Send each keypress */
|
|
+ for (i = 0; i < n;) {
|
|
+ int ret = 0;
|
|
+ int command;
|
|
+
|
|
+ if (copy_from_user(&command, buf + i, sizeof(command))) {
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ /* Send boot data first if required */
|
|
+ if (ir->need_boot == 1) {
|
|
+ ret = send_boot_data(ir);
|
|
+ if (ret == 0)
|
|
+ ir->need_boot = 0;
|
|
+ }
|
|
+
|
|
+ /* Send the code */
|
|
+ if (ret == 0) {
|
|
+ ret = send_code(ir, (unsigned)command >> 16,
|
|
+ (unsigned)command & 0xFFFF);
|
|
+ if (ret == -EPROTO) {
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Hmm, a failure. If we've had a few then give up, otherwise
|
|
+ * try a reset
|
|
+ */
|
|
+ if (ret != 0) {
|
|
+ /* Looks like the chip crashed, reset it */
|
|
+ zilog_error("sending to the IR transmitter chip "
|
|
+ "failed, trying reset\n");
|
|
+
|
|
+ if (failures >= 3) {
|
|
+ zilog_error("unable to send to the IR chip "
|
|
+ "after 3 resets, giving up\n");
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ return ret;
|
|
+ }
|
|
+ set_current_state(TASK_UNINTERRUPTIBLE);
|
|
+ schedule_timeout((100 * HZ + 999) / 1000);
|
|
+ ir->need_boot = 1;
|
|
+ ++failures;
|
|
+ } else
|
|
+ i += sizeof(int);
|
|
+ }
|
|
+
|
|
+ /* Release i2c bus */
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+
|
|
+ /* All looks good */
|
|
+ return n;
|
|
+}
|
|
+
|
|
+/* copied from lirc_dev */
|
|
+static unsigned int poll(struct file *filep, poll_table *wait)
|
|
+{
|
|
+ struct IR *ir = (struct IR *)filep->private_data;
|
|
+ unsigned int ret;
|
|
+
|
|
+ dprintk("poll called\n");
|
|
+ if (ir->c_rx.addr == 0)
|
|
+ return -ENODEV;
|
|
+
|
|
+ mutex_lock(&ir->buf_lock);
|
|
+
|
|
+ poll_wait(filep, &ir->buf.wait_poll, wait);
|
|
+
|
|
+ dprintk("poll result = %s\n",
|
|
+ lirc_buffer_empty(&ir->buf) ? "0" : "POLLIN|POLLRDNORM");
|
|
+
|
|
+ ret = lirc_buffer_empty(&ir->buf) ? 0 : (POLLIN|POLLRDNORM);
|
|
+
|
|
+ mutex_unlock(&ir->buf_lock);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ioctl(struct inode *node, struct file *filep, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ struct IR *ir = (struct IR *)filep->private_data;
|
|
+ int result;
|
|
+ unsigned long mode, features = 0;
|
|
+
|
|
+ if (ir->c_rx.addr != 0)
|
|
+ features |= LIRC_CAN_REC_LIRCCODE;
|
|
+ if (ir->c_tx.addr != 0)
|
|
+ features |= LIRC_CAN_SEND_PULSE;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case LIRC_GET_LENGTH:
|
|
+ result = put_user((unsigned long)13,
|
|
+ (unsigned long *)arg);
|
|
+ break;
|
|
+ case LIRC_GET_FEATURES:
|
|
+ result = put_user(features, (unsigned long *) arg);
|
|
+ break;
|
|
+ case LIRC_GET_REC_MODE:
|
|
+ if (!(features&LIRC_CAN_REC_MASK))
|
|
+ return -ENOSYS;
|
|
+
|
|
+ result = put_user(LIRC_REC2MODE
|
|
+ (features&LIRC_CAN_REC_MASK),
|
|
+ (unsigned long *)arg);
|
|
+ break;
|
|
+ case LIRC_SET_REC_MODE:
|
|
+ if (!(features&LIRC_CAN_REC_MASK))
|
|
+ return -ENOSYS;
|
|
+
|
|
+ result = get_user(mode, (unsigned long *)arg);
|
|
+ if (!result && !(LIRC_MODE2REC(mode) & features))
|
|
+ result = -EINVAL;
|
|
+ break;
|
|
+ case LIRC_GET_SEND_MODE:
|
|
+ if (!(features&LIRC_CAN_SEND_MASK))
|
|
+ return -ENOSYS;
|
|
+
|
|
+ result = put_user(LIRC_MODE_PULSE, (unsigned long *) arg);
|
|
+ break;
|
|
+ case LIRC_SET_SEND_MODE:
|
|
+ if (!(features&LIRC_CAN_SEND_MASK))
|
|
+ return -ENOSYS;
|
|
+
|
|
+ result = get_user(mode, (unsigned long *) arg);
|
|
+ if (!result && mode != LIRC_MODE_PULSE)
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Open the IR device. Get hold of our IR structure and
|
|
+ * stash it in private_data for the file
|
|
+ */
|
|
+static int open(struct inode *node, struct file *filep)
|
|
+{
|
|
+ struct IR *ir;
|
|
+ int ret;
|
|
+
|
|
+ /* find our IR struct */
|
|
+ unsigned minor = MINOR(node->i_rdev);
|
|
+ if (minor >= MAX_IRCTL_DEVICES) {
|
|
+ dprintk("minor %d: open result = -ENODEV\n",
|
|
+ minor);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ ir = ir_devices[minor];
|
|
+
|
|
+ /* increment in use count */
|
|
+ mutex_lock(&ir->ir_lock);
|
|
+ ++ir->open;
|
|
+ ret = set_use_inc(ir);
|
|
+ if (ret != 0) {
|
|
+ --ir->open;
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ return ret;
|
|
+ }
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+
|
|
+ /* stash our IR struct */
|
|
+ filep->private_data = ir;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Close the IR device */
|
|
+static int close(struct inode *node, struct file *filep)
|
|
+{
|
|
+ /* find our IR struct */
|
|
+ struct IR *ir = (struct IR *)filep->private_data;
|
|
+ if (ir == NULL) {
|
|
+ zilog_error("close: no private_data attached to the file!\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ /* decrement in use count */
|
|
+ mutex_lock(&ir->ir_lock);
|
|
+ --ir->open;
|
|
+ set_use_dec(ir);
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct lirc_driver lirc_template = {
|
|
+ .name = "lirc_zilog",
|
|
+ .set_use_inc = set_use_inc,
|
|
+ .set_use_dec = set_use_dec,
|
|
+ .owner = THIS_MODULE
|
|
+};
|
|
+
|
|
+static int ir_remove(struct i2c_client *client);
|
|
+static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id);
|
|
+static int ir_command(struct i2c_client *client, unsigned int cmd, void *arg);
|
|
+
|
|
+static const struct i2c_device_id ir_transceiver_id[] = {
|
|
+ /* Generic entry for any IR transceiver */
|
|
+ { "ir_video", 0 },
|
|
+ /* IR device specific entries should be added here */
|
|
+ { "ir_tx_z8f0811_haup", 0 },
|
|
+ { "ir_rx_z8f0811_haup", 0 },
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct i2c_driver driver = {
|
|
+ .driver = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .name = "Zilog/Hauppauge i2c IR",
|
|
+ },
|
|
+ .probe = ir_probe,
|
|
+ .remove = ir_remove,
|
|
+ .command = ir_command,
|
|
+ .id_table = ir_transceiver_id,
|
|
+};
|
|
+
|
|
+static struct file_operations lirc_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .llseek = lseek,
|
|
+ .read = read,
|
|
+ .write = write,
|
|
+ .poll = poll,
|
|
+ .ioctl = ioctl,
|
|
+ .open = open,
|
|
+ .release = close
|
|
+};
|
|
+
|
|
+static int ir_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct IR *ir = i2c_get_clientdata(client);
|
|
+
|
|
+ mutex_lock(&ir->ir_lock);
|
|
+
|
|
+ if (ir->have_rx || ir->have_tx) {
|
|
+ DECLARE_COMPLETION(tn);
|
|
+ DECLARE_COMPLETION(tn2);
|
|
+
|
|
+ /* end up polling thread */
|
|
+ if (ir->task && !IS_ERR(ir->task)) {
|
|
+ ir->t_notify = &tn;
|
|
+ ir->t_notify2 = &tn2;
|
|
+ ir->shutdown = 1;
|
|
+ wake_up_process(ir->task);
|
|
+ complete(&tn2);
|
|
+ wait_for_completion(&tn);
|
|
+ ir->t_notify = NULL;
|
|
+ ir->t_notify2 = NULL;
|
|
+ }
|
|
+
|
|
+ } else {
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ zilog_error("%s: detached from something we didn't "
|
|
+ "attach to\n", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ /* unregister lirc driver */
|
|
+ if (ir->l.minor >= 0 && ir->l.minor < MAX_IRCTL_DEVICES) {
|
|
+ lirc_unregister_driver(ir->l.minor);
|
|
+ ir_devices[ir->l.minor] = NULL;
|
|
+ }
|
|
+
|
|
+ /* free memory */
|
|
+ lirc_buffer_free(&ir->buf);
|
|
+ mutex_unlock(&ir->ir_lock);
|
|
+ kfree(ir);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|
+{
|
|
+ struct IR *ir = NULL;
|
|
+ struct i2c_adapter *adap = client->adapter;
|
|
+ char buf;
|
|
+ int ret;
|
|
+ int have_rx = 0, have_tx = 0;
|
|
+
|
|
+ dprintk("%s: adapter id=0x%x, client addr=0x%02x\n",
|
|
+ __func__, adap->id, client->addr);
|
|
+
|
|
+ /* if this isn't an appropriate device, bail w/-ENODEV now */
|
|
+ if (!(adap->id == I2C_HW_B_BT848 ||
|
|
+#ifdef I2C_HW_B_HDPVR
|
|
+ adap->id == I2C_HW_B_HDPVR ||
|
|
+#endif
|
|
+ adap->id == I2C_HW_B_CX2341X))
|
|
+ goto out_nodev;
|
|
+
|
|
+ /*
|
|
+ * The external IR receiver is at i2c address 0x71.
|
|
+ * The IR transmitter is at 0x70.
|
|
+ */
|
|
+ client->addr = 0x70;
|
|
+
|
|
+ if (!disable_tx) {
|
|
+ if (i2c_master_recv(client, &buf, 1) == 1)
|
|
+ have_tx = 1;
|
|
+ dprintk("probe 0x70 @ %s: %s\n",
|
|
+ adap->name, have_tx ? "success" : "failed");
|
|
+ }
|
|
+
|
|
+ if (!disable_rx) {
|
|
+ client->addr = 0x71;
|
|
+ if (i2c_master_recv(client, &buf, 1) == 1)
|
|
+ have_rx = 1;
|
|
+ dprintk("probe 0x71 @ %s: %s\n",
|
|
+ adap->name, have_rx ? "success" : "failed");
|
|
+ }
|
|
+
|
|
+ if (!(have_rx || have_tx)) {
|
|
+ zilog_error("%s: no devices found\n", adap->name);
|
|
+ goto out_nodev;
|
|
+ }
|
|
+
|
|
+ printk(KERN_INFO "lirc_zilog: chip found with %s\n",
|
|
+ have_rx && have_tx ? "RX and TX" :
|
|
+ have_rx ? "RX only" : "TX only");
|
|
+
|
|
+ ir = kzalloc(sizeof(struct IR), GFP_KERNEL);
|
|
+
|
|
+ if (!ir)
|
|
+ goto out_nomem;
|
|
+
|
|
+ ret = lirc_buffer_init(&ir->buf, 2, BUFLEN / 2);
|
|
+ if (ret)
|
|
+ goto out_nomem;
|
|
+
|
|
+ mutex_init(&ir->ir_lock);
|
|
+ mutex_init(&ir->buf_lock);
|
|
+ ir->need_boot = 1;
|
|
+
|
|
+ memcpy(&ir->l, &lirc_template, sizeof(struct lirc_driver));
|
|
+ ir->l.minor = -1;
|
|
+
|
|
+ /* I2C attach to device */
|
|
+ i2c_set_clientdata(client, ir);
|
|
+
|
|
+ /* initialise RX device */
|
|
+ if (have_rx) {
|
|
+ DECLARE_COMPLETION(tn);
|
|
+ memcpy(&ir->c_rx, client, sizeof(struct i2c_client));
|
|
+
|
|
+ ir->c_rx.addr = 0x71;
|
|
+ strncpy(ir->c_rx.name, ZILOG_HAUPPAUGE_IR_RX_NAME,
|
|
+ I2C_NAME_SIZE);
|
|
+
|
|
+ /* try to fire up polling thread */
|
|
+ ir->t_notify = &tn;
|
|
+ ir->task = kthread_run(lirc_thread, ir, "lirc_zilog");
|
|
+ if (IS_ERR(ir->task)) {
|
|
+ ret = PTR_ERR(ir->task);
|
|
+ zilog_error("lirc_register_driver: cannot run "
|
|
+ "poll thread %d\n", ret);
|
|
+ goto err;
|
|
+ }
|
|
+ wait_for_completion(&tn);
|
|
+ ir->t_notify = NULL;
|
|
+ ir->have_rx = 1;
|
|
+ }
|
|
+
|
|
+ /* initialise TX device */
|
|
+ if (have_tx) {
|
|
+ memcpy(&ir->c_tx, client, sizeof(struct i2c_client));
|
|
+ ir->c_tx.addr = 0x70;
|
|
+ strncpy(ir->c_tx.name, ZILOG_HAUPPAUGE_IR_TX_NAME,
|
|
+ I2C_NAME_SIZE);
|
|
+ ir->have_tx = 1;
|
|
+ }
|
|
+
|
|
+ /* set lirc_dev stuff */
|
|
+ ir->l.code_length = 13;
|
|
+ ir->l.rbuf = &ir->buf;
|
|
+ ir->l.fops = &lirc_fops;
|
|
+ ir->l.data = ir;
|
|
+ ir->l.minor = minor;
|
|
+ ir->l.dev = &adap->dev;
|
|
+ ir->l.sample_rate = 0;
|
|
+
|
|
+ /* register with lirc */
|
|
+ ir->l.minor = lirc_register_driver(&ir->l);
|
|
+ if (ir->l.minor < 0 || ir->l.minor >= MAX_IRCTL_DEVICES) {
|
|
+ zilog_error("ir_attach: \"minor\" must be between 0 and %d "
|
|
+ "(%d)!\n", MAX_IRCTL_DEVICES-1, ir->l.minor);
|
|
+ ret = -EBADRQC;
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ /* store this for getting back in open() later on */
|
|
+ ir_devices[ir->l.minor] = ir;
|
|
+
|
|
+ /*
|
|
+ * if we have the tx device, load the 'firmware'. We do this
|
|
+ * after registering with lirc as otherwise hotplug seems to take
|
|
+ * 10s to create the lirc device.
|
|
+ */
|
|
+ if (have_tx) {
|
|
+ /* Special TX init */
|
|
+ ret = tx_init(ir);
|
|
+ if (ret != 0)
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err:
|
|
+ /* undo everything, hopefully... */
|
|
+ if (ir->c_rx.addr)
|
|
+ ir_remove(&ir->c_rx);
|
|
+ if (ir->c_tx.addr)
|
|
+ ir_remove(&ir->c_tx);
|
|
+ return ret;
|
|
+
|
|
+out_nodev:
|
|
+ zilog_error("no device found\n");
|
|
+ return -ENODEV;
|
|
+
|
|
+out_nomem:
|
|
+ zilog_error("memory allocation failure\n");
|
|
+ kfree(ir);
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+static int ir_command(struct i2c_client *client, unsigned int cmd, void *arg)
|
|
+{
|
|
+ /* nothing */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __init zilog_init(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ zilog_notify("Zilog/Hauppauge IR driver initializing\n");
|
|
+
|
|
+ mutex_init(&tx_data_lock);
|
|
+
|
|
+ request_module("firmware_class");
|
|
+
|
|
+ ret = i2c_add_driver(&driver);
|
|
+ if (ret)
|
|
+ zilog_error("initialization failed\n");
|
|
+ else
|
|
+ zilog_notify("initialization complete\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void __exit zilog_exit(void)
|
|
+{
|
|
+ i2c_del_driver(&driver);
|
|
+ /* if loaded */
|
|
+ fw_unload();
|
|
+ zilog_notify("Zilog/Hauppauge IR driver unloaded\n");
|
|
+}
|
|
+
|
|
+module_init(zilog_init);
|
|
+module_exit(zilog_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("Zilog/Hauppauge infrared transmitter driver (i2c stack)");
|
|
+MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, "
|
|
+ "Ulrich Mueller, Stefan Jahn, Jerome Brock, Mark Weaver");
|
|
+MODULE_LICENSE("GPL");
|
|
+/* for compat with old name, which isn't all that accurate anymore */
|
|
+MODULE_ALIAS("lirc_pvr150");
|
|
+
|
|
+module_param(minor, int, 0444);
|
|
+MODULE_PARM_DESC(minor, "Preferred minor device number");
|
|
+
|
|
+module_param(debug, bool, 0644);
|
|
+MODULE_PARM_DESC(debug, "Enable debugging messages");
|
|
+
|
|
+module_param(disable_rx, bool, 0644);
|
|
+MODULE_PARM_DESC(disable_rx, "Disable the IR receiver device");
|
|
+
|
|
+module_param(disable_tx, bool, 0644);
|
|
+MODULE_PARM_DESC(disable_tx, "Disable the IR transmitter device");
|
|
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
|
|
index 16ec523..1196110 100644
|
|
--- a/drivers/input/misc/Kconfig
|
|
+++ b/drivers/input/misc/Kconfig
|
|
@@ -319,4 +319,16 @@ config INPUT_PCAP
|
|
To compile this driver as a module, choose M here: the
|
|
module will be called pcap_keys.
|
|
|
|
+config INPUT_IMON
|
|
+ tristate "SoundGraph iMON Receiver and Display"
|
|
+ depends on USB_ARCH_HAS_HCD
|
|
+ select USB
|
|
+ select INPUT_SPARSEKMAP
|
|
+ help
|
|
+ Say Y here if you want to use a SoundGraph iMON (aka Antec Veris)
|
|
+ IR Receiver and/or LCD/VFD/VGA display.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called imon.
|
|
+
|
|
endif
|
|
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
|
|
index a8b8485..79358ff 100644
|
|
--- a/drivers/input/misc/Makefile
|
|
+++ b/drivers/input/misc/Makefile
|
|
@@ -13,6 +13,7 @@ obj-$(CONFIG_INPUT_CM109) += cm109.o
|
|
obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
|
|
obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o
|
|
obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
|
|
+obj-$(CONFIG_INPUT_IMON) += imon.o
|
|
obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o
|
|
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
|
|
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
|
|
diff --git a/drivers/input/misc/imon.c b/drivers/input/misc/imon.c
|
|
new file mode 100644
|
|
index 0000000..71223e2
|
|
--- /dev/null
|
|
+++ b/drivers/input/misc/imon.c
|
|
@@ -0,0 +1,2430 @@
|
|
+/*
|
|
+ * imon.c: input and display driver for SoundGraph iMON IR/VFD/LCD
|
|
+ *
|
|
+ * Copyright(C) 2009 Jarod Wilson <jarod@wilsonet.com>
|
|
+ * Portions based on the original lirc_imon driver,
|
|
+ * Copyright(C) 2004 Venky Raju(dev@venky.ws)
|
|
+ *
|
|
+ * imon 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
+ */
|
|
+
|
|
+#include <linux/errno.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/input/sparse-keymap.h>
|
|
+#include <linux/usb.h>
|
|
+#include <linux/usb/input.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/timer.h>
|
|
+
|
|
+#define MOD_AUTHOR "Jarod Wilson <jarod@wilsonet.com>"
|
|
+#define MOD_DESC "Driver for SoundGraph iMON MultiMedia IR/Display"
|
|
+#define MOD_NAME "imon"
|
|
+#define MOD_VERSION "0.8"
|
|
+
|
|
+#define DISPLAY_MINOR_BASE 144
|
|
+#define DEVICE_NAME "lcd%d"
|
|
+
|
|
+#define BUF_CHUNK_SIZE 8
|
|
+#define BUF_SIZE 128
|
|
+
|
|
+#define BIT_DURATION 250 /* each bit received is 250us */
|
|
+
|
|
+#define IMON_CLOCK_ENABLE_PACKETS 2
|
|
+#define IMON_KEY_RELEASE_OFFSET 1000
|
|
+
|
|
+/*** P R O T O T Y P E S ***/
|
|
+
|
|
+/* USB Callback prototypes */
|
|
+static int imon_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id);
|
|
+static void imon_disconnect(struct usb_interface *interface);
|
|
+static void usb_rx_callback_intf0(struct urb *urb);
|
|
+static void usb_rx_callback_intf1(struct urb *urb);
|
|
+static void usb_tx_callback(struct urb *urb);
|
|
+
|
|
+/* suspend/resume support */
|
|
+static int imon_resume(struct usb_interface *intf);
|
|
+static int imon_suspend(struct usb_interface *intf, pm_message_t message);
|
|
+
|
|
+/* Display file_operations function prototypes */
|
|
+static int display_open(struct inode *inode, struct file *file);
|
|
+static int display_close(struct inode *inode, struct file *file);
|
|
+
|
|
+/* VFD write operation */
|
|
+static ssize_t vfd_write(struct file *file, const char *buf,
|
|
+ size_t n_bytes, loff_t *pos);
|
|
+
|
|
+/* LCD file_operations override function prototypes */
|
|
+static ssize_t lcd_write(struct file *file, const char *buf,
|
|
+ size_t n_bytes, loff_t *pos);
|
|
+
|
|
+/*** G L O B A L S ***/
|
|
+
|
|
+struct imon_context {
|
|
+ struct device *dev;
|
|
+ struct usb_device *usbdev_intf0;
|
|
+ /* Newer devices have two interfaces */
|
|
+ struct usb_device *usbdev_intf1;
|
|
+ bool display_supported; /* not all controllers do */
|
|
+ bool display_isopen; /* display port has been opened */
|
|
+ bool ir_isassociating; /* IR port open for association */
|
|
+ bool dev_present_intf0; /* USB device presence, interface 0 */
|
|
+ bool dev_present_intf1; /* USB device presence, interface 1 */
|
|
+ struct mutex lock; /* to lock this object */
|
|
+ wait_queue_head_t remove_ok; /* For unexpected USB disconnects */
|
|
+
|
|
+ struct usb_endpoint_descriptor *rx_endpoint_intf0;
|
|
+ struct usb_endpoint_descriptor *rx_endpoint_intf1;
|
|
+ struct usb_endpoint_descriptor *tx_endpoint;
|
|
+ struct urb *rx_urb_intf0;
|
|
+ struct urb *rx_urb_intf1;
|
|
+ struct urb *tx_urb;
|
|
+ bool tx_control;
|
|
+ unsigned char usb_rx_buf[8];
|
|
+ unsigned char usb_tx_buf[8];
|
|
+
|
|
+ struct tx_t {
|
|
+ unsigned char data_buf[35]; /* user data buffer */
|
|
+ struct completion finished; /* wait for write to finish */
|
|
+ bool busy; /* write in progress */
|
|
+ int status; /* status of tx completion */
|
|
+ } tx;
|
|
+
|
|
+ u16 vendor; /* usb vendor ID */
|
|
+ u16 product; /* usb product ID */
|
|
+ int ir_protocol; /* iMON or MCE (RC6) IR protocol? */
|
|
+ struct input_dev *idev; /* input device for remote */
|
|
+ struct input_dev *touch; /* input device for touchscreen */
|
|
+ int ki; /* current input keycode key index */
|
|
+ u16 kc; /* current input keycode */
|
|
+ u16 last_keycode; /* last reported input keycode */
|
|
+ u8 mce_toggle_bit; /* last mce toggle bit */
|
|
+ int display_type; /* store the display type */
|
|
+ bool pad_mouse; /* toggle kbd(0)/mouse(1) mode */
|
|
+ int touch_x; /* x coordinate on touchscreen */
|
|
+ int touch_y; /* y coordinate on touchscreen */
|
|
+ char name_idev[128]; /* input device name */
|
|
+ char phys_idev[64]; /* input device phys path */
|
|
+ struct timer_list itimer; /* input device timer, need for rc6 */
|
|
+ char name_touch[128]; /* touch screen name */
|
|
+ char phys_touch[64]; /* touch screen phys path */
|
|
+ struct timer_list ttimer; /* touch screen timer */
|
|
+};
|
|
+
|
|
+#define TOUCH_TIMEOUT (HZ/30)
|
|
+#define MCE_TIMEOUT_MS 200
|
|
+
|
|
+/* vfd character device file operations */
|
|
+static const struct file_operations vfd_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = &display_open,
|
|
+ .write = &vfd_write,
|
|
+ .release = &display_close
|
|
+};
|
|
+
|
|
+/* lcd character device file operations */
|
|
+static const struct file_operations lcd_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = &display_open,
|
|
+ .write = &lcd_write,
|
|
+ .release = &display_close
|
|
+};
|
|
+
|
|
+enum {
|
|
+ IMON_DISPLAY_TYPE_AUTO = 0,
|
|
+ IMON_DISPLAY_TYPE_VFD = 1,
|
|
+ IMON_DISPLAY_TYPE_LCD = 2,
|
|
+ IMON_DISPLAY_TYPE_VGA = 3,
|
|
+ IMON_DISPLAY_TYPE_NONE = 4,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ IMON_IR_PROTOCOL_IMON = 0,
|
|
+ IMON_IR_PROTOCOL_MCE = 1,
|
|
+ IMON_IR_PROTOCOL_IMON_NOPAD = 2,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ IMON_BUTTON_IMON = 0,
|
|
+ IMON_BUTTON_MCE = 1,
|
|
+ IMON_BUTTON_PANEL = 2,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * USB Device ID for iMON USB Control Boards
|
|
+ *
|
|
+ * The Windows drivers contain 6 different inf files, more or less one for
|
|
+ * each new device until the 0x0034-0x0046 devices, which all use the same
|
|
+ * driver. Some of the devices in the 34-46 range haven't been definitively
|
|
+ * identified yet. Early devices have either a TriGem Computer, Inc. or a
|
|
+ * Samsung vendor ID (0x0aa8 and 0x04e8 respectively), while all later
|
|
+ * devices use the SoundGraph vendor ID (0x15c2). This driver only supports
|
|
+ * the ffdc and later devices, which do onboard decoding.
|
|
+ */
|
|
+static struct usb_device_id imon_usb_id_table[] = {
|
|
+ /*
|
|
+ * Several devices with this same device ID, all use iMON_PAD.inf
|
|
+ * SoundGraph iMON PAD (IR & VFD)
|
|
+ * SoundGraph iMON PAD (IR & LCD)
|
|
+ * SoundGraph iMON Knob (IR only)
|
|
+ */
|
|
+ { USB_DEVICE(0x15c2, 0xffdc) },
|
|
+
|
|
+ /*
|
|
+ * Newer devices, all driven by the latest iMON Windows driver, full
|
|
+ * list of device IDs extracted via 'strings Setup/data1.hdr |grep 15c2'
|
|
+ * Need user input to fill in details on unknown devices.
|
|
+ */
|
|
+ /* SoundGraph iMON OEM Touch LCD (IR & 7" VGA LCD) */
|
|
+ { USB_DEVICE(0x15c2, 0x0034) },
|
|
+ /* SoundGraph iMON OEM Touch LCD (IR & 4.3" VGA LCD) */
|
|
+ { USB_DEVICE(0x15c2, 0x0035) },
|
|
+ /* SoundGraph iMON OEM VFD (IR & VFD) */
|
|
+ { USB_DEVICE(0x15c2, 0x0036) },
|
|
+ /* device specifics unknown */
|
|
+ { USB_DEVICE(0x15c2, 0x0037) },
|
|
+ /* SoundGraph iMON OEM LCD (IR & LCD) */
|
|
+ { USB_DEVICE(0x15c2, 0x0038) },
|
|
+ /* SoundGraph iMON UltraBay (IR & LCD) */
|
|
+ { USB_DEVICE(0x15c2, 0x0039) },
|
|
+ /* device specifics unknown */
|
|
+ { USB_DEVICE(0x15c2, 0x003a) },
|
|
+ /* device specifics unknown */
|
|
+ { USB_DEVICE(0x15c2, 0x003b) },
|
|
+ /* SoundGraph iMON OEM Inside (IR only) */
|
|
+ { USB_DEVICE(0x15c2, 0x003c) },
|
|
+ /* device specifics unknown */
|
|
+ { USB_DEVICE(0x15c2, 0x003d) },
|
|
+ /* device specifics unknown */
|
|
+ { USB_DEVICE(0x15c2, 0x003e) },
|
|
+ /* device specifics unknown */
|
|
+ { USB_DEVICE(0x15c2, 0x003f) },
|
|
+ /* device specifics unknown */
|
|
+ { USB_DEVICE(0x15c2, 0x0040) },
|
|
+ /* SoundGraph iMON MINI (IR only) */
|
|
+ { USB_DEVICE(0x15c2, 0x0041) },
|
|
+ /* Antec Veris Multimedia Station EZ External (IR only) */
|
|
+ { USB_DEVICE(0x15c2, 0x0042) },
|
|
+ /* Antec Veris Multimedia Station Basic Internal (IR only) */
|
|
+ { USB_DEVICE(0x15c2, 0x0043) },
|
|
+ /* Antec Veris Multimedia Station Elite (IR & VFD) */
|
|
+ { USB_DEVICE(0x15c2, 0x0044) },
|
|
+ /* Antec Veris Multimedia Station Premiere (IR & LCD) */
|
|
+ { USB_DEVICE(0x15c2, 0x0045) },
|
|
+ /* device specifics unknown */
|
|
+ { USB_DEVICE(0x15c2, 0x0046) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* iMON LCD models use a different write op */
|
|
+static struct usb_device_id lcd_device_list[] = {
|
|
+ { USB_DEVICE(0x15c2, 0xffdc) },
|
|
+ { USB_DEVICE(0x15c2, 0x0038) },
|
|
+ { USB_DEVICE(0x15c2, 0x0039) },
|
|
+ { USB_DEVICE(0x15c2, 0x0045) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* Some iMON devices have no lcd/vfd, don't set one up */
|
|
+static struct usb_device_id ir_only_list[] = {
|
|
+ /* the first imon lcd and the knob share this device id. :\ */
|
|
+ /*{ USB_DEVICE(0x15c2, 0xffdc) },*/
|
|
+ { USB_DEVICE(0x15c2, 0x003c) },
|
|
+ { USB_DEVICE(0x15c2, 0x0041) },
|
|
+ { USB_DEVICE(0x15c2, 0x0042) },
|
|
+ { USB_DEVICE(0x15c2, 0x0043) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* iMON devices with VGA touchscreens */
|
|
+static struct usb_device_id imon_touchscreen_list[] = {
|
|
+ { USB_DEVICE(0x15c2, 0x0034) },
|
|
+ { USB_DEVICE(0x15c2, 0x0035) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+/* USB Device data */
|
|
+static struct usb_driver imon_driver = {
|
|
+ .name = MOD_NAME,
|
|
+ .probe = imon_probe,
|
|
+ .disconnect = imon_disconnect,
|
|
+ .suspend = imon_suspend,
|
|
+ .resume = imon_resume,
|
|
+ .id_table = imon_usb_id_table,
|
|
+};
|
|
+
|
|
+static struct usb_class_driver imon_vfd_class = {
|
|
+ .name = DEVICE_NAME,
|
|
+ .fops = &vfd_fops,
|
|
+ .minor_base = DISPLAY_MINOR_BASE,
|
|
+};
|
|
+
|
|
+static struct usb_class_driver imon_lcd_class = {
|
|
+ .name = DEVICE_NAME,
|
|
+ .fops = &lcd_fops,
|
|
+ .minor_base = DISPLAY_MINOR_BASE,
|
|
+};
|
|
+
|
|
+/* standard imon remote key table */
|
|
+static const struct key_entry imon_remote_key_table[] = {
|
|
+ /* keys sorted mostly by frequency of use to optimize lookups */
|
|
+ { KE_KEY, 0x2a8195b7, { KEY_REWIND } },
|
|
+ { KE_KEY, 0x298315b7, { KEY_REWIND } },
|
|
+ { KE_KEY, 0x2b8115b7, { KEY_FASTFORWARD } },
|
|
+ { KE_KEY, 0x2b8315b7, { KEY_FASTFORWARD } },
|
|
+ { KE_KEY, 0x2b9115b7, { KEY_PREVIOUS } },
|
|
+ { KE_KEY, 0x298195b7, { KEY_NEXT } },
|
|
+
|
|
+ { KE_KEY, 0x2a8115b7, { KEY_PLAY } },
|
|
+ { KE_KEY, 0x2a8315b7, { KEY_PLAY } },
|
|
+ { KE_KEY, 0x2a9115b7, { KEY_PAUSE } },
|
|
+ { KE_KEY, 0x2b9715b7, { KEY_STOP } },
|
|
+ { KE_KEY, 0x298115b7, { KEY_RECORD } },
|
|
+
|
|
+ { KE_KEY, 0x01008000, { KEY_UP } },
|
|
+ { KE_KEY, 0x01007f00, { KEY_DOWN } },
|
|
+ { KE_KEY, 0x01000080, { KEY_LEFT } },
|
|
+ { KE_KEY, 0x0100007f, { KEY_RIGHT } },
|
|
+
|
|
+ { KE_KEY, 0x2aa515b7, { KEY_UP } },
|
|
+ { KE_KEY, 0x289515b7, { KEY_DOWN } },
|
|
+ { KE_KEY, 0x29a515b7, { KEY_LEFT } },
|
|
+ { KE_KEY, 0x2ba515b7, { KEY_RIGHT } },
|
|
+
|
|
+ { KE_KEY, 0x0200002c, { KEY_SPACE } }, /* Select/Space */
|
|
+ { KE_KEY, 0x02000028, { KEY_ENTER } },
|
|
+ { KE_KEY, 0x288195b7, { KEY_EXIT } },
|
|
+ { KE_KEY, 0x02000029, { KEY_ESC } },
|
|
+ { KE_KEY, 0x0200002a, { KEY_BACKSPACE } },
|
|
+
|
|
+ { KE_KEY, 0x2b9595b7, { KEY_MUTE } },
|
|
+ { KE_KEY, 0x28a395b7, { KEY_VOLUMEUP } },
|
|
+ { KE_KEY, 0x28a595b7, { KEY_VOLUMEDOWN } },
|
|
+ { KE_KEY, 0x289395b7, { KEY_CHANNELUP } },
|
|
+ { KE_KEY, 0x288795b7, { KEY_CHANNELDOWN } },
|
|
+
|
|
+ { KE_KEY, 0x0200001e, { KEY_NUMERIC_1 } },
|
|
+ { KE_KEY, 0x0200001f, { KEY_NUMERIC_2 } },
|
|
+ { KE_KEY, 0x02000020, { KEY_NUMERIC_3 } },
|
|
+ { KE_KEY, 0x02000021, { KEY_NUMERIC_4 } },
|
|
+ { KE_KEY, 0x02000022, { KEY_NUMERIC_5 } },
|
|
+ { KE_KEY, 0x02000023, { KEY_NUMERIC_6 } },
|
|
+ { KE_KEY, 0x02000024, { KEY_NUMERIC_7 } },
|
|
+ { KE_KEY, 0x02000025, { KEY_NUMERIC_8 } },
|
|
+ { KE_KEY, 0x02000026, { KEY_NUMERIC_9 } },
|
|
+ { KE_KEY, 0x02000027, { KEY_NUMERIC_0 } },
|
|
+
|
|
+ { KE_KEY, 0x02200025, { KEY_NUMERIC_STAR } },
|
|
+ { KE_KEY, 0x02200020, { KEY_NUMERIC_POUND } },
|
|
+
|
|
+ { KE_KEY, 0x2b8515b7, { KEY_VIDEO } },
|
|
+ { KE_KEY, 0x299195b7, { KEY_AUDIO } },
|
|
+ { KE_KEY, 0x2ba115b7, { KEY_CAMERA } },
|
|
+ { KE_KEY, 0x28a515b7, { KEY_TV } },
|
|
+ { KE_KEY, 0x29a395b7, { KEY_DVD } },
|
|
+ { KE_KEY, 0x29a295b7, { KEY_DVD } },
|
|
+
|
|
+ /* the Menu key between DVD and Subtitle on the RM-200... */
|
|
+ { KE_KEY, 0x2ba385b7, { KEY_MENU } },
|
|
+ { KE_KEY, 0x2ba395b7, { KEY_MENU } },
|
|
+
|
|
+ { KE_KEY, 0x288515b7, { KEY_BOOKMARKS } },
|
|
+ { KE_KEY, 0x2ab715b7, { KEY_MEDIA } }, /* Thumbnail */
|
|
+ { KE_KEY, 0x298595b7, { KEY_SUBTITLE } },
|
|
+ { KE_KEY, 0x2b8595b7, { KEY_LANGUAGE } },
|
|
+
|
|
+ { KE_KEY, 0x29a595b7, { KEY_ZOOM } },
|
|
+ { KE_KEY, 0x2aa395b7, { KEY_SCREEN } }, /* FullScreen */
|
|
+
|
|
+ { KE_KEY, 0x299115b7, { KEY_KEYBOARD } },
|
|
+ { KE_KEY, 0x299135b7, { KEY_KEYBOARD } },
|
|
+
|
|
+ { KE_KEY, 0x01010000, { BTN_LEFT } },
|
|
+ { KE_KEY, 0x01020000, { BTN_RIGHT } },
|
|
+ { KE_KEY, 0x01010080, { BTN_LEFT } },
|
|
+ { KE_KEY, 0x01020080, { BTN_RIGHT } },
|
|
+
|
|
+ { KE_KEY, 0x2a9395b7, { KEY_CYCLEWINDOWS } }, /* TaskSwitcher */
|
|
+ { KE_KEY, 0x2b8395b7, { KEY_TIME } }, /* Timer */
|
|
+
|
|
+ { KE_KEY, 0x289115b7, { KEY_POWER } },
|
|
+ { KE_KEY, 0x29b195b7, { KEY_EJECTCD } }, /* the one next to play */
|
|
+ { KE_KEY, 0x299395b7, { KEY_EJECTCLOSECD } }, /* eject (by TaskSw) */
|
|
+
|
|
+ { KE_KEY, 0x02800000, { KEY_MENU } }, /* Left Menu */
|
|
+ { KE_KEY, 0x02000065, { KEY_COMPOSE } }, /* RightMenu */
|
|
+ { KE_KEY, 0x2ab195b7, { KEY_PROG1 } }, /* Go */
|
|
+ { KE_KEY, 0x29b715b7, { KEY_DASHBOARD } }, /* AppLauncher */
|
|
+ { KE_END, 0 }
|
|
+};
|
|
+
|
|
+/* mce-mode imon mce remote key table */
|
|
+static const struct key_entry imon_mce_key_table[] = {
|
|
+ /* keys sorted mostly by frequency of use to optimize lookups */
|
|
+ { KE_KEY, 0x800f8415, { KEY_REWIND } },
|
|
+ { KE_KEY, 0x800f8414, { KEY_FASTFORWARD } },
|
|
+ { KE_KEY, 0x800f841b, { KEY_PREVIOUS } },
|
|
+ { KE_KEY, 0x800f841a, { KEY_NEXT } },
|
|
+
|
|
+ { KE_KEY, 0x800f8416, { KEY_PLAY } },
|
|
+ { KE_KEY, 0x800f8418, { KEY_PAUSE } },
|
|
+ { KE_KEY, 0x800f8418, { KEY_PAUSE } },
|
|
+ { KE_KEY, 0x800f8419, { KEY_STOP } },
|
|
+ { KE_KEY, 0x800f8417, { KEY_RECORD } },
|
|
+
|
|
+ { KE_KEY, 0x02000052, { KEY_UP } },
|
|
+ { KE_KEY, 0x02000051, { KEY_DOWN } },
|
|
+ { KE_KEY, 0x02000050, { KEY_LEFT } },
|
|
+ { KE_KEY, 0x0200004f, { KEY_RIGHT } },
|
|
+
|
|
+ { KE_KEY, 0x02000028, { KEY_ENTER } },
|
|
+/* the OK and Enter buttons decode to the same value
|
|
+ { KE_KEY, 0x02000028, { KEY_OK } }, */
|
|
+ { KE_KEY, 0x0200002a, { KEY_EXIT } },
|
|
+ { KE_KEY, 0x02000029, { KEY_DELETE } },
|
|
+
|
|
+ { KE_KEY, 0x800f840e, { KEY_MUTE } },
|
|
+ { KE_KEY, 0x800f8410, { KEY_VOLUMEUP } },
|
|
+ { KE_KEY, 0x800f8411, { KEY_VOLUMEDOWN } },
|
|
+ { KE_KEY, 0x800f8412, { KEY_CHANNELUP } },
|
|
+ { KE_KEY, 0x800f8413, { KEY_CHANNELDOWN } },
|
|
+
|
|
+ { KE_KEY, 0x0200001e, { KEY_NUMERIC_1 } },
|
|
+ { KE_KEY, 0x0200001f, { KEY_NUMERIC_2 } },
|
|
+ { KE_KEY, 0x02000020, { KEY_NUMERIC_3 } },
|
|
+ { KE_KEY, 0x02000021, { KEY_NUMERIC_4 } },
|
|
+ { KE_KEY, 0x02000022, { KEY_NUMERIC_5 } },
|
|
+ { KE_KEY, 0x02000023, { KEY_NUMERIC_6 } },
|
|
+ { KE_KEY, 0x02000024, { KEY_NUMERIC_7 } },
|
|
+ { KE_KEY, 0x02000025, { KEY_NUMERIC_8 } },
|
|
+ { KE_KEY, 0x02000026, { KEY_NUMERIC_9 } },
|
|
+ { KE_KEY, 0x02000027, { KEY_NUMERIC_0 } },
|
|
+
|
|
+ { KE_KEY, 0x02200025, { KEY_NUMERIC_STAR } },
|
|
+ { KE_KEY, 0x02200020, { KEY_NUMERIC_POUND } },
|
|
+
|
|
+ { KE_KEY, 0x800f8446, { KEY_TV } },
|
|
+ { KE_KEY, 0x800f8447, { KEY_AUDIO } },
|
|
+ { KE_KEY, 0x800f8448, { KEY_PVR } }, /* RecordedTV */
|
|
+ { KE_KEY, 0x800f8449, { KEY_CAMERA } },
|
|
+ { KE_KEY, 0x800f844a, { KEY_VIDEO } },
|
|
+ { KE_KEY, 0x800f8424, { KEY_DVD } },
|
|
+ { KE_KEY, 0x800f8425, { KEY_TUNER } }, /* LiveTV */
|
|
+
|
|
+ { KE_KEY, 0x800f845b, { KEY_RED } },
|
|
+ { KE_KEY, 0x800f845c, { KEY_GREEN } },
|
|
+ { KE_KEY, 0x800f845d, { KEY_YELLOW } },
|
|
+ { KE_KEY, 0x800f845e, { KEY_BLUE } },
|
|
+
|
|
+ { KE_KEY, 0x800f840f, { KEY_INFO } },
|
|
+ { KE_KEY, 0x800f8426, { KEY_EPG } }, /* Guide */
|
|
+ { KE_KEY, 0x800f845a, { KEY_SUBTITLE } }, /* Caption */
|
|
+
|
|
+ { KE_KEY, 0x800f840c, { KEY_POWER } },
|
|
+ { KE_KEY, 0x800f840d, { KEY_PROG1 } }, /* Windows MCE button */
|
|
+ { KE_END, 0 }
|
|
+
|
|
+};
|
|
+
|
|
+/* imon receiver front panel/knob key table */
|
|
+static const struct {
|
|
+ u64 hw_code;
|
|
+ u16 keycode;
|
|
+} imon_panel_key_table[] = {
|
|
+ { 0x000000000f000fee, KEY_PROG1 }, /* Go */
|
|
+ { 0x000000001f000fee, KEY_AUDIO },
|
|
+ { 0x0000000020000fee, KEY_VIDEO },
|
|
+ { 0x0000000021000fee, KEY_CAMERA },
|
|
+ { 0x0000000027000fee, KEY_DVD },
|
|
+/* the TV key on my panel is broken, doesn't work under any OS
|
|
+ { 0x0000000000000fee, KEY_TV }, */
|
|
+ { 0x0000000005000fee, KEY_PREVIOUS },
|
|
+ { 0x0000000007000fee, KEY_REWIND },
|
|
+ { 0x0000000004000fee, KEY_STOP },
|
|
+ { 0x000000003c000fee, KEY_PLAYPAUSE },
|
|
+ { 0x0000000008000fee, KEY_FASTFORWARD },
|
|
+ { 0x0000000006000fee, KEY_NEXT },
|
|
+ { 0x0000000100000fee, KEY_RIGHT },
|
|
+ { 0x0000010000000fee, KEY_LEFT },
|
|
+ { 0x000000003d000fee, KEY_SELECT },
|
|
+ { 0x0001000000000fee, KEY_VOLUMEUP },
|
|
+ { 0x0100000000000fee, KEY_VOLUMEDOWN },
|
|
+ { 0x0000000001000fee, KEY_MUTE },
|
|
+};
|
|
+
|
|
+/* to prevent races between open() and disconnect(), probing, etc */
|
|
+static DEFINE_MUTEX(driver_lock);
|
|
+
|
|
+/* Module bookkeeping bits */
|
|
+MODULE_AUTHOR(MOD_AUTHOR);
|
|
+MODULE_DESCRIPTION(MOD_DESC);
|
|
+MODULE_VERSION(MOD_VERSION);
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DEVICE_TABLE(usb, imon_usb_id_table);
|
|
+
|
|
+static bool debug;
|
|
+module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes(default: no)");
|
|
+
|
|
+/* lcd, vfd, vga or none? should be auto-detected, but can be overridden... */
|
|
+static int display_type;
|
|
+module_param(display_type, int, S_IRUGO);
|
|
+MODULE_PARM_DESC(display_type, "Type of attached display. 0=autodetect, "
|
|
+ "1=vfd, 2=lcd, 3=vga, 4=none (default: autodetect)");
|
|
+
|
|
+/* IR protocol: native iMON, Windows MCE (RC-6), or iMON w/o PAD stabilize */
|
|
+static int ir_protocol;
|
|
+module_param(ir_protocol, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(ir_protocol, "Which IR protocol to use. 0=native iMON, "
|
|
+ "1=Windows Media Center Ed. (RC-6), 2=iMON w/o PAD stabilize "
|
|
+ "(default: native iMON)");
|
|
+
|
|
+/*
|
|
+ * In certain use cases, mouse mode isn't really helpful, and could actually
|
|
+ * cause confusion, so allow disabling it when the IR device is open.
|
|
+ */
|
|
+static bool nomouse;
|
|
+module_param(nomouse, bool, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(nomouse, "Disable mouse input device mode when IR device is "
|
|
+ "open. 0=don't disable, 1=disable. (default: don't disable)");
|
|
+
|
|
+/* threshold at which a pad push registers as an arrow key in kbd mode */
|
|
+static int pad_thresh;
|
|
+module_param(pad_thresh, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(pad_thresh, "Threshold at which a pad push registers as an "
|
|
+ "arrow key in kbd mode (default: 28)");
|
|
+
|
|
+
|
|
+static void free_imon_context(struct imon_context *ictx)
|
|
+{
|
|
+ struct device *dev = ictx->dev;
|
|
+
|
|
+ usb_free_urb(ictx->tx_urb);
|
|
+ usb_free_urb(ictx->rx_urb_intf0);
|
|
+ usb_free_urb(ictx->rx_urb_intf1);
|
|
+ kfree(ictx);
|
|
+
|
|
+ dev_dbg(dev, "%s: iMON context freed\n", __func__);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called when the Display device (e.g. /dev/lcd0)
|
|
+ * is opened by the application.
|
|
+ */
|
|
+static int display_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct usb_interface *interface;
|
|
+ struct imon_context *ictx = NULL;
|
|
+ int subminor;
|
|
+ int retval = 0;
|
|
+
|
|
+ /* prevent races with disconnect */
|
|
+ mutex_lock(&driver_lock);
|
|
+
|
|
+ subminor = iminor(inode);
|
|
+ interface = usb_find_interface(&imon_driver, subminor);
|
|
+ if (!interface) {
|
|
+ err("%s: could not find interface for minor %d",
|
|
+ __func__, subminor);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+ ictx = usb_get_intfdata(interface);
|
|
+
|
|
+ if (!ictx) {
|
|
+ err("%s: no context found for minor %d", __func__, subminor);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ if (!ictx->display_supported) {
|
|
+ err("%s: display not supported by device", __func__);
|
|
+ retval = -ENODEV;
|
|
+ } else if (ictx->display_isopen) {
|
|
+ err("%s: display port is already open", __func__);
|
|
+ retval = -EBUSY;
|
|
+ } else {
|
|
+ ictx->display_isopen = 1;
|
|
+ file->private_data = ictx;
|
|
+ dev_dbg(ictx->dev, "display port opened\n");
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&ictx->lock);
|
|
+
|
|
+exit:
|
|
+ mutex_unlock(&driver_lock);
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Called when the display device (e.g. /dev/lcd0)
|
|
+ * is closed by the application.
|
|
+ */
|
|
+static int display_close(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct imon_context *ictx = NULL;
|
|
+ int retval = 0;
|
|
+
|
|
+ ictx = (struct imon_context *)file->private_data;
|
|
+
|
|
+ if (!ictx) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ if (!ictx->display_supported) {
|
|
+ err("%s: display not supported by device", __func__);
|
|
+ retval = -ENODEV;
|
|
+ } else if (!ictx->display_isopen) {
|
|
+ err("%s: display is not open", __func__);
|
|
+ retval = -EIO;
|
|
+ } else {
|
|
+ ictx->display_isopen = 0;
|
|
+ dev_dbg(ictx->dev, "display port closed\n");
|
|
+ if (!ictx->dev_present_intf0) {
|
|
+ /*
|
|
+ * Device disconnected before close and IR port is not
|
|
+ * open. If IR port is open, context will be deleted by
|
|
+ * ir_close.
|
|
+ */
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ free_imon_context(ictx);
|
|
+ return retval;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Sends a packet to the device -- this function must be called
|
|
+ * with ictx->lock held.
|
|
+ */
|
|
+static int send_packet(struct imon_context *ictx)
|
|
+{
|
|
+ unsigned int pipe;
|
|
+ int interval = 0;
|
|
+ int retval = 0;
|
|
+ struct usb_ctrlrequest *control_req = NULL;
|
|
+
|
|
+ /* Check if we need to use control or interrupt urb */
|
|
+ if (!ictx->tx_control) {
|
|
+ pipe = usb_sndintpipe(ictx->usbdev_intf0,
|
|
+ ictx->tx_endpoint->bEndpointAddress);
|
|
+ interval = ictx->tx_endpoint->bInterval;
|
|
+
|
|
+ usb_fill_int_urb(ictx->tx_urb, ictx->usbdev_intf0, pipe,
|
|
+ ictx->usb_tx_buf,
|
|
+ sizeof(ictx->usb_tx_buf),
|
|
+ usb_tx_callback, ictx, interval);
|
|
+
|
|
+ ictx->tx_urb->actual_length = 0;
|
|
+ } else {
|
|
+ /* fill request into kmalloc'ed space: */
|
|
+ control_req = kmalloc(sizeof(struct usb_ctrlrequest),
|
|
+ GFP_KERNEL);
|
|
+ if (control_req == NULL)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* setup packet is '21 09 0200 0001 0008' */
|
|
+ control_req->bRequestType = 0x21;
|
|
+ control_req->bRequest = 0x09;
|
|
+ control_req->wValue = cpu_to_le16(0x0200);
|
|
+ control_req->wIndex = cpu_to_le16(0x0001);
|
|
+ control_req->wLength = cpu_to_le16(0x0008);
|
|
+
|
|
+ /* control pipe is endpoint 0x00 */
|
|
+ pipe = usb_sndctrlpipe(ictx->usbdev_intf0, 0);
|
|
+
|
|
+ /* build the control urb */
|
|
+ usb_fill_control_urb(ictx->tx_urb, ictx->usbdev_intf0,
|
|
+ pipe, (unsigned char *)control_req,
|
|
+ ictx->usb_tx_buf,
|
|
+ sizeof(ictx->usb_tx_buf),
|
|
+ usb_tx_callback, ictx);
|
|
+ ictx->tx_urb->actual_length = 0;
|
|
+ }
|
|
+
|
|
+ init_completion(&ictx->tx.finished);
|
|
+ ictx->tx.busy = 1;
|
|
+ smp_rmb(); /* ensure later readers know we're busy */
|
|
+
|
|
+ retval = usb_submit_urb(ictx->tx_urb, GFP_KERNEL);
|
|
+ if (retval) {
|
|
+ ictx->tx.busy = 0;
|
|
+ smp_rmb(); /* ensure later readers know we're not busy */
|
|
+ err("%s: error submitting urb(%d)", __func__, retval);
|
|
+ } else {
|
|
+ /* Wait for transmission to complete (or abort) */
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ retval = wait_for_completion_interruptible(
|
|
+ &ictx->tx.finished);
|
|
+ if (retval)
|
|
+ err("%s: task interrupted", __func__);
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ retval = ictx->tx.status;
|
|
+ if (retval)
|
|
+ err("%s: packet tx failed (%d)", __func__, retval);
|
|
+ }
|
|
+
|
|
+ kfree(control_req);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Sends an associate packet to the iMON 2.4G.
|
|
+ *
|
|
+ * This might not be such a good idea, since it has an id collision with
|
|
+ * some versions of the "IR & VFD" combo. The only way to determine if it
|
|
+ * is an RF version is to look at the product description string. (Which
|
|
+ * we currently do not fetch).
|
|
+ */
|
|
+static int send_associate_24g(struct imon_context *ictx)
|
|
+{
|
|
+ int retval;
|
|
+ const unsigned char packet[8] = { 0x01, 0x00, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x20 };
|
|
+
|
|
+ if (!ictx) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ if (!ictx->dev_present_intf0) {
|
|
+ err("%s: no iMON device present", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ memcpy(ictx->usb_tx_buf, packet, sizeof(packet));
|
|
+ retval = send_packet(ictx);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Sends packets to setup and show clock on iMON display
|
|
+ *
|
|
+ * Arguments: year - last 2 digits of year, month - 1..12,
|
|
+ * day - 1..31, dow - day of the week (0-Sun...6-Sat),
|
|
+ * hour - 0..23, minute - 0..59, second - 0..59
|
|
+ */
|
|
+static int send_set_imon_clock(struct imon_context *ictx,
|
|
+ unsigned int year, unsigned int month,
|
|
+ unsigned int day, unsigned int dow,
|
|
+ unsigned int hour, unsigned int minute,
|
|
+ unsigned int second)
|
|
+{
|
|
+ unsigned char clock_enable_pkt[IMON_CLOCK_ENABLE_PACKETS][8];
|
|
+ int retval = 0;
|
|
+ int i;
|
|
+
|
|
+ if (!ictx) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ switch (ictx->display_type) {
|
|
+ case IMON_DISPLAY_TYPE_LCD:
|
|
+ clock_enable_pkt[0][0] = 0x80;
|
|
+ clock_enable_pkt[0][1] = year;
|
|
+ clock_enable_pkt[0][2] = month-1;
|
|
+ clock_enable_pkt[0][3] = day;
|
|
+ clock_enable_pkt[0][4] = hour;
|
|
+ clock_enable_pkt[0][5] = minute;
|
|
+ clock_enable_pkt[0][6] = second;
|
|
+
|
|
+ clock_enable_pkt[1][0] = 0x80;
|
|
+ clock_enable_pkt[1][1] = 0;
|
|
+ clock_enable_pkt[1][2] = 0;
|
|
+ clock_enable_pkt[1][3] = 0;
|
|
+ clock_enable_pkt[1][4] = 0;
|
|
+ clock_enable_pkt[1][5] = 0;
|
|
+ clock_enable_pkt[1][6] = 0;
|
|
+
|
|
+ if (ictx->product == 0xffdc) {
|
|
+ clock_enable_pkt[0][7] = 0x50;
|
|
+ clock_enable_pkt[1][7] = 0x51;
|
|
+ } else {
|
|
+ clock_enable_pkt[0][7] = 0x88;
|
|
+ clock_enable_pkt[1][7] = 0x8a;
|
|
+ }
|
|
+
|
|
+ break;
|
|
+
|
|
+ case IMON_DISPLAY_TYPE_VFD:
|
|
+ clock_enable_pkt[0][0] = year;
|
|
+ clock_enable_pkt[0][1] = month-1;
|
|
+ clock_enable_pkt[0][2] = day;
|
|
+ clock_enable_pkt[0][3] = dow;
|
|
+ clock_enable_pkt[0][4] = hour;
|
|
+ clock_enable_pkt[0][5] = minute;
|
|
+ clock_enable_pkt[0][6] = second;
|
|
+ clock_enable_pkt[0][7] = 0x40;
|
|
+
|
|
+ clock_enable_pkt[1][0] = 0;
|
|
+ clock_enable_pkt[1][1] = 0;
|
|
+ clock_enable_pkt[1][2] = 1;
|
|
+ clock_enable_pkt[1][3] = 0;
|
|
+ clock_enable_pkt[1][4] = 0;
|
|
+ clock_enable_pkt[1][5] = 0;
|
|
+ clock_enable_pkt[1][6] = 0;
|
|
+ clock_enable_pkt[1][7] = 0x42;
|
|
+
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < IMON_CLOCK_ENABLE_PACKETS; i++) {
|
|
+ memcpy(ictx->usb_tx_buf, clock_enable_pkt[i], 8);
|
|
+ retval = send_packet(ictx);
|
|
+ if (retval) {
|
|
+ err("%s: send_packet failed for packet %d",
|
|
+ __func__, i);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * These are the sysfs functions to handle the association on the iMON 2.4G LT.
|
|
+ */
|
|
+static ssize_t show_associate_remote(struct device *d,
|
|
+ struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct imon_context *ictx = dev_get_drvdata(d);
|
|
+
|
|
+ if (!ictx)
|
|
+ return -ENODEV;
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+ if (ictx->ir_isassociating)
|
|
+ strcpy(buf, "associating\n");
|
|
+ else
|
|
+ strcpy(buf, "closed\n");
|
|
+
|
|
+ dev_info(d, "Visit http://www.lirc.org/html/imon-24g.html for "
|
|
+ "instructions on how to associate your iMON 2.4G DT/LT "
|
|
+ "remote\n");
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ return strlen(buf);
|
|
+}
|
|
+
|
|
+static ssize_t store_associate_remote(struct device *d,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct imon_context *ictx;
|
|
+
|
|
+ ictx = dev_get_drvdata(d);
|
|
+
|
|
+ if (!ictx)
|
|
+ return -ENODEV;
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+ ictx->ir_isassociating = 1;
|
|
+ send_associate_24g(ictx);
|
|
+ mutex_unlock(&ictx->lock);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sysfs functions to control internal imon clock
|
|
+ */
|
|
+static ssize_t show_imon_clock(struct device *d,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct imon_context *ictx = dev_get_drvdata(d);
|
|
+ size_t len;
|
|
+
|
|
+ if (!ictx)
|
|
+ return -ENODEV;
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ if (!ictx->display_supported) {
|
|
+ len = snprintf(buf, PAGE_SIZE, "Not supported.");
|
|
+ } else {
|
|
+ len = snprintf(buf, PAGE_SIZE,
|
|
+ "To set the clock on your iMON display:\n"
|
|
+ "# date \"+%%y %%m %%d %%w %%H %%M %%S\" > imon_clock\n"
|
|
+ "%s", ictx->display_isopen ?
|
|
+ "\nNOTE: imon device must be closed\n" : "");
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&ictx->lock);
|
|
+
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static ssize_t store_imon_clock(struct device *d,
|
|
+ struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct imon_context *ictx = dev_get_drvdata(d);
|
|
+ ssize_t retval;
|
|
+ unsigned int year, month, day, dow, hour, minute, second;
|
|
+
|
|
+ if (!ictx)
|
|
+ return -ENODEV;
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ if (!ictx->display_supported) {
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ } else if (ictx->display_isopen) {
|
|
+ retval = -EBUSY;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (sscanf(buf, "%u %u %u %u %u %u %u", &year, &month, &day, &dow,
|
|
+ &hour, &minute, &second) != 7) {
|
|
+ retval = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if ((month < 1 || month > 12) ||
|
|
+ (day < 1 || day > 31) || (dow > 6) ||
|
|
+ (hour > 23) || (minute > 59) || (second > 59)) {
|
|
+ retval = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ retval = send_set_imon_clock(ictx, year, month, day, dow,
|
|
+ hour, minute, second);
|
|
+ if (retval)
|
|
+ goto exit;
|
|
+
|
|
+ retval = count;
|
|
+exit:
|
|
+ mutex_unlock(&ictx->lock);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+
|
|
+static DEVICE_ATTR(imon_clock, S_IWUSR | S_IRUGO, show_imon_clock,
|
|
+ store_imon_clock);
|
|
+
|
|
+static DEVICE_ATTR(associate_remote, S_IWUSR | S_IRUGO, show_associate_remote,
|
|
+ store_associate_remote);
|
|
+
|
|
+static struct attribute *imon_display_sysfs_entries[] = {
|
|
+ &dev_attr_imon_clock.attr,
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static struct attribute_group imon_display_attribute_group = {
|
|
+ .attrs = imon_display_sysfs_entries
|
|
+};
|
|
+
|
|
+static struct attribute *imon_rf_sysfs_entries[] = {
|
|
+ &dev_attr_associate_remote.attr,
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static struct attribute_group imon_rf_attribute_group = {
|
|
+ .attrs = imon_rf_sysfs_entries
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Writes data to the VFD. The iMON VFD is 2x16 characters
|
|
+ * and requires data in 5 consecutive USB interrupt packets,
|
|
+ * each packet but the last carrying 7 bytes.
|
|
+ *
|
|
+ * I don't know if the VFD board supports features such as
|
|
+ * scrolling, clearing rows, blanking, etc. so at
|
|
+ * the caller must provide a full screen of data. If fewer
|
|
+ * than 32 bytes are provided spaces will be appended to
|
|
+ * generate a full screen.
|
|
+ */
|
|
+static ssize_t vfd_write(struct file *file, const char *buf,
|
|
+ size_t n_bytes, loff_t *pos)
|
|
+{
|
|
+ int i;
|
|
+ int offset;
|
|
+ int seq;
|
|
+ int retval = 0;
|
|
+ struct imon_context *ictx;
|
|
+ const unsigned char vfd_packet6[] = {
|
|
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
|
|
+
|
|
+ ictx = (struct imon_context *)file->private_data;
|
|
+ if (!ictx) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ if (!ictx->dev_present_intf0) {
|
|
+ err("%s: no iMON device present", __func__);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (n_bytes <= 0 || n_bytes > 32) {
|
|
+ err("%s: invalid payload size", __func__);
|
|
+ retval = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (copy_from_user(ictx->tx.data_buf, buf, n_bytes)) {
|
|
+ retval = -EFAULT;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ /* Pad with spaces */
|
|
+ for (i = n_bytes; i < 32; ++i)
|
|
+ ictx->tx.data_buf[i] = ' ';
|
|
+
|
|
+ for (i = 32; i < 35; ++i)
|
|
+ ictx->tx.data_buf[i] = 0xFF;
|
|
+
|
|
+ offset = 0;
|
|
+ seq = 0;
|
|
+
|
|
+ do {
|
|
+ memcpy(ictx->usb_tx_buf, ictx->tx.data_buf + offset, 7);
|
|
+ ictx->usb_tx_buf[7] = (unsigned char) seq;
|
|
+
|
|
+ retval = send_packet(ictx);
|
|
+ if (retval) {
|
|
+ err("%s: send packet failed for packet #%d",
|
|
+ __func__, seq/2);
|
|
+ goto exit;
|
|
+ } else {
|
|
+ seq += 2;
|
|
+ offset += 7;
|
|
+ }
|
|
+
|
|
+ } while (offset < 35);
|
|
+
|
|
+ /* Send packet #6 */
|
|
+ memcpy(ictx->usb_tx_buf, &vfd_packet6, sizeof(vfd_packet6));
|
|
+ ictx->usb_tx_buf[7] = (unsigned char) seq;
|
|
+ retval = send_packet(ictx);
|
|
+ if (retval)
|
|
+ err("%s: send packet failed for packet #%d",
|
|
+ __func__, seq / 2);
|
|
+
|
|
+exit:
|
|
+ mutex_unlock(&ictx->lock);
|
|
+
|
|
+ return (!retval) ? n_bytes : retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Writes data to the LCD. The iMON OEM LCD screen expects 8-byte
|
|
+ * packets. We accept data as 16 hexadecimal digits, followed by a
|
|
+ * newline (to make it easy to drive the device from a command-line
|
|
+ * -- even though the actual binary data is a bit complicated).
|
|
+ *
|
|
+ * The device itself is not a "traditional" text-mode display. It's
|
|
+ * actually a 16x96 pixel bitmap display. That means if you want to
|
|
+ * display text, you've got to have your own "font" and translate the
|
|
+ * text into bitmaps for display. This is really flexible (you can
|
|
+ * display whatever diacritics you need, and so on), but it's also
|
|
+ * a lot more complicated than most LCDs...
|
|
+ */
|
|
+static ssize_t lcd_write(struct file *file, const char *buf,
|
|
+ size_t n_bytes, loff_t *pos)
|
|
+{
|
|
+ int retval = 0;
|
|
+ struct imon_context *ictx;
|
|
+
|
|
+ ictx = (struct imon_context *)file->private_data;
|
|
+ if (!ictx) {
|
|
+ err("%s: no context for device", __func__);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ if (!ictx->display_supported) {
|
|
+ err("%s: no iMON display present", __func__);
|
|
+ retval = -ENODEV;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (n_bytes != 8) {
|
|
+ err("%s: invalid payload size: %d (expecting 8)",
|
|
+ __func__, (int) n_bytes);
|
|
+ retval = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (copy_from_user(ictx->usb_tx_buf, buf, 8)) {
|
|
+ retval = -EFAULT;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ retval = send_packet(ictx);
|
|
+ if (retval) {
|
|
+ err("%s: send packet failed!", __func__);
|
|
+ goto exit;
|
|
+ } else {
|
|
+ dev_dbg(ictx->dev, "%s: write %d bytes to LCD\n",
|
|
+ __func__, (int) n_bytes);
|
|
+ }
|
|
+exit:
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ return (!retval) ? n_bytes : retval;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: transmit data
|
|
+ */
|
|
+static void usb_tx_callback(struct urb *urb)
|
|
+{
|
|
+ struct imon_context *ictx;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+ ictx = (struct imon_context *)urb->context;
|
|
+ if (!ictx)
|
|
+ return;
|
|
+
|
|
+ ictx->tx.status = urb->status;
|
|
+
|
|
+ /* notify waiters that write has finished */
|
|
+ ictx->tx.busy = 0;
|
|
+ smp_rmb(); /* ensure later readers know we're not busy */
|
|
+ complete(&ictx->tx.finished);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * iMON IR receivers support two different signal sets -- those used by
|
|
+ * the iMON remotes, and those used by the Windows MCE remotes (which is
|
|
+ * really just RC-6), but only one or the other at a time, as the signals
|
|
+ * are decoded onboard the receiver.
|
|
+ */
|
|
+static void imon_set_ir_protocol(struct imon_context *ictx)
|
|
+{
|
|
+ int retval;
|
|
+ struct device *dev = ictx->dev;
|
|
+ unsigned char ir_proto_packet[] = {
|
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86 };
|
|
+
|
|
+ switch (ir_protocol) {
|
|
+ case IMON_IR_PROTOCOL_MCE:
|
|
+ dev_dbg(dev, "Configuring IR receiver for MCE protocol\n");
|
|
+ ir_proto_packet[0] = 0x01;
|
|
+ ictx->ir_protocol = IMON_IR_PROTOCOL_MCE;
|
|
+ ictx->pad_mouse = 0;
|
|
+ break;
|
|
+ case IMON_IR_PROTOCOL_IMON:
|
|
+ dev_dbg(dev, "Configuring IR receiver for iMON protocol\n");
|
|
+ /* ir_proto_packet[0] = 0x00; // already the default */
|
|
+ ictx->ir_protocol = IMON_IR_PROTOCOL_IMON;
|
|
+ ictx->pad_mouse = 1;
|
|
+ break;
|
|
+ case IMON_IR_PROTOCOL_IMON_NOPAD:
|
|
+ dev_dbg(dev, "Configuring IR receiver for iMON protocol "
|
|
+ "without PAD stabilize function enabled\n");
|
|
+ /* ir_proto_packet[0] = 0x00; // already the default */
|
|
+ ictx->ir_protocol = IMON_IR_PROTOCOL_IMON_NOPAD;
|
|
+ ictx->pad_mouse = 0;
|
|
+ break;
|
|
+ default:
|
|
+ dev_info(dev, "%s: unknown IR protocol specified, will "
|
|
+ "just default to iMON protocol\n", __func__);
|
|
+ ictx->ir_protocol = IMON_IR_PROTOCOL_IMON;
|
|
+ ictx->pad_mouse = 1;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ memcpy(ictx->usb_tx_buf, &ir_proto_packet, sizeof(ir_proto_packet));
|
|
+
|
|
+ retval = send_packet(ictx);
|
|
+ if (retval) {
|
|
+ dev_info(dev, "%s: failed to set IR protocol, falling back "
|
|
+ "to standard iMON protocol mode\n", __func__);
|
|
+ ir_protocol = IMON_IR_PROTOCOL_IMON;
|
|
+ ictx->ir_protocol = IMON_IR_PROTOCOL_IMON;
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline int tv2int(const struct timeval *a, const struct timeval *b)
|
|
+{
|
|
+ int usecs = 0;
|
|
+ int sec = 0;
|
|
+
|
|
+ if (b->tv_usec > a->tv_usec) {
|
|
+ usecs = 1000000;
|
|
+ sec--;
|
|
+ }
|
|
+
|
|
+ usecs += a->tv_usec - b->tv_usec;
|
|
+
|
|
+ sec += a->tv_sec - b->tv_sec;
|
|
+ sec *= 1000;
|
|
+ usecs /= 1000;
|
|
+ sec += usecs;
|
|
+
|
|
+ if (sec < 0)
|
|
+ sec = 1000;
|
|
+
|
|
+ return sec;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * The directional pad behaves a bit differently, depending on whether this is
|
|
+ * one of the older ffdc devices or a newer device. Newer devices appear to
|
|
+ * have a higher resolution matrix for more precise mouse movement, but it
|
|
+ * makes things overly sensitive in keyboard mode, so we do some interesting
|
|
+ * contortions to make it less touchy. Older devices run through the same
|
|
+ * routine with shorter timeout and a smaller threshold.
|
|
+ */
|
|
+static int stabilize(int a, int b, u16 timeout, u16 threshold)
|
|
+{
|
|
+ struct timeval ct;
|
|
+ static struct timeval prev_time = {0, 0};
|
|
+ static struct timeval hit_time = {0, 0};
|
|
+ static int x, y, prev_result, hits;
|
|
+ int result = 0;
|
|
+ int msec, msec_hit;
|
|
+
|
|
+ do_gettimeofday(&ct);
|
|
+ msec = tv2int(&ct, &prev_time);
|
|
+ msec_hit = tv2int(&ct, &hit_time);
|
|
+
|
|
+ if (msec > 100) {
|
|
+ x = 0;
|
|
+ y = 0;
|
|
+ hits = 0;
|
|
+ }
|
|
+
|
|
+ x += a;
|
|
+ y += b;
|
|
+
|
|
+ prev_time = ct;
|
|
+
|
|
+ if (abs(x) > threshold || abs(y) > threshold) {
|
|
+ if (abs(y) > abs(x))
|
|
+ result = (y > 0) ? 0x7F : 0x80;
|
|
+ else
|
|
+ result = (x > 0) ? 0x7F00 : 0x8000;
|
|
+
|
|
+ x = 0;
|
|
+ y = 0;
|
|
+
|
|
+ if (result == prev_result) {
|
|
+ hits++;
|
|
+
|
|
+ if (hits > 3) {
|
|
+ switch (result) {
|
|
+ case 0x7F:
|
|
+ y = 17 * threshold / 30;
|
|
+ break;
|
|
+ case 0x80:
|
|
+ y -= 17 * threshold / 30;
|
|
+ break;
|
|
+ case 0x7F00:
|
|
+ x = 17 * threshold / 30;
|
|
+ break;
|
|
+ case 0x8000:
|
|
+ x -= 17 * threshold / 30;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (hits == 2 && msec_hit < timeout) {
|
|
+ result = 0;
|
|
+ hits = 1;
|
|
+ }
|
|
+ } else {
|
|
+ prev_result = result;
|
|
+ hits = 1;
|
|
+ hit_time = ct;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static int imon_remote_key_lookup(u32 hw_code)
|
|
+{
|
|
+ int i;
|
|
+ u32 code = be32_to_cpu(hw_code);
|
|
+
|
|
+ /* Look for the initial press of a button */
|
|
+ for (i = 0; i < ARRAY_SIZE(imon_remote_key_table); i++)
|
|
+ if (imon_remote_key_table[i].code == code)
|
|
+ return i;
|
|
+
|
|
+ /* Look for the release of a button, return index + offset */
|
|
+ for (i = 0; i < ARRAY_SIZE(imon_remote_key_table); i++)
|
|
+ if ((imon_remote_key_table[i].code | 0x4000) == code)
|
|
+ return i + IMON_KEY_RELEASE_OFFSET;
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static int imon_mce_key_lookup(u32 hw_code)
|
|
+{
|
|
+ int i;
|
|
+ u32 code = be32_to_cpu(hw_code);
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(imon_mce_key_table); i++)
|
|
+ if (imon_mce_key_table[i].code == code)
|
|
+ return i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(imon_mce_key_table); i++)
|
|
+ if (imon_mce_key_table[i].code == (code | 0x8000))
|
|
+ return i;
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static int imon_panel_key_lookup(u64 hw_code)
|
|
+{
|
|
+ int i;
|
|
+ u64 code = be64_to_cpu(hw_code);
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(imon_panel_key_table); i++)
|
|
+ if (imon_panel_key_table[i].hw_code == (code | 0xfee))
|
|
+ return i;
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static bool imon_mouse_event(struct imon_context *ictx,
|
|
+ unsigned char *buf, int len)
|
|
+{
|
|
+ char rel_x = 0x00, rel_y = 0x00;
|
|
+ u8 right_shift = 1;
|
|
+ bool mouse_input = 1;
|
|
+ int dir = 0;
|
|
+
|
|
+ /* newer iMON device PAD or mouse button */
|
|
+ if (ictx->product != 0xffdc && (buf[0] & 0x01) && len == 5) {
|
|
+ rel_x = buf[2];
|
|
+ rel_y = buf[3];
|
|
+ right_shift = 1;
|
|
+ /* 0xffdc iMON PAD or mouse button input */
|
|
+ } else if (ictx->product == 0xffdc && (buf[0] & 0x40) &&
|
|
+ !((buf[1] & 0x01) || ((buf[1] >> 2) & 0x01))) {
|
|
+ rel_x = (buf[1] & 0x08) | (buf[1] & 0x10) >> 2 |
|
|
+ (buf[1] & 0x20) >> 4 | (buf[1] & 0x40) >> 6;
|
|
+ if (buf[0] & 0x02)
|
|
+ rel_x |= ~0x0f;
|
|
+ rel_x = rel_x + rel_x / 2;
|
|
+ rel_y = (buf[2] & 0x08) | (buf[2] & 0x10) >> 2 |
|
|
+ (buf[2] & 0x20) >> 4 | (buf[2] & 0x40) >> 6;
|
|
+ if (buf[0] & 0x01)
|
|
+ rel_y |= ~0x0f;
|
|
+ rel_y = rel_y + rel_y / 2;
|
|
+ right_shift = 2;
|
|
+ /* some ffdc devices decode mouse buttons differently... */
|
|
+ } else if (ictx->product == 0xffdc && (buf[0] == 0x68)) {
|
|
+ right_shift = 2;
|
|
+ /* ch+/- buttons, which we use for an emulated scroll wheel */
|
|
+ } else if (ictx->kc == KEY_CHANNELUP && (buf[2] & 0x40) != 0x40) {
|
|
+ dir = 1;
|
|
+ } else if (ictx->kc == KEY_CHANNELDOWN && (buf[2] & 0x40) != 0x40) {
|
|
+ dir = -1;
|
|
+ } else
|
|
+ mouse_input = 0;
|
|
+
|
|
+ if (mouse_input) {
|
|
+ dev_dbg(ictx->dev, "sending mouse data via input subsystem\n");
|
|
+
|
|
+ if (dir) {
|
|
+ input_report_rel(ictx->idev, REL_WHEEL, dir);
|
|
+ } else if (rel_x || rel_y) {
|
|
+ input_report_rel(ictx->idev, REL_X, rel_x);
|
|
+ input_report_rel(ictx->idev, REL_Y, rel_y);
|
|
+ } else {
|
|
+ input_report_key(ictx->idev, BTN_LEFT, buf[1] & 0x1);
|
|
+ input_report_key(ictx->idev, BTN_RIGHT,
|
|
+ buf[1] >> right_shift & 0x1);
|
|
+ }
|
|
+ input_sync(ictx->idev);
|
|
+ ictx->last_keycode = ictx->kc;
|
|
+ }
|
|
+
|
|
+ return mouse_input;
|
|
+}
|
|
+
|
|
+static void imon_touch_event(struct imon_context *ictx, unsigned char *buf)
|
|
+{
|
|
+ mod_timer(&ictx->ttimer, jiffies + TOUCH_TIMEOUT);
|
|
+ ictx->touch_x = (buf[0] << 4) | (buf[1] >> 4);
|
|
+ ictx->touch_y = 0xfff - ((buf[2] << 4) | (buf[1] & 0xf));
|
|
+ input_report_abs(ictx->touch, ABS_X, ictx->touch_x);
|
|
+ input_report_abs(ictx->touch, ABS_Y, ictx->touch_y);
|
|
+ input_report_key(ictx->touch, BTN_TOUCH, 0x01);
|
|
+ input_sync(ictx->touch);
|
|
+}
|
|
+
|
|
+static void imon_pad_to_keys(struct imon_context *ictx, unsigned char *buf)
|
|
+{
|
|
+ int ki = 1;
|
|
+ int dir = 0;
|
|
+ int offset = IMON_KEY_RELEASE_OFFSET;
|
|
+ char rel_x = 0x00, rel_y = 0x00;
|
|
+ u16 timeout, threshold;
|
|
+ u64 temp_key;
|
|
+ u32 remote_key;
|
|
+
|
|
+ /*
|
|
+ * The imon directional pad functions more like a touchpad. Bytes 3 & 4
|
|
+ * contain a position coordinate (x,y), with each component ranging
|
|
+ * from -14 to 14. We want to down-sample this to only 4 discrete values
|
|
+ * for up/down/left/right arrow keys. Also, when you get too close to
|
|
+ * diagonals, it has a tendancy to jump back and forth, so lets try to
|
|
+ * ignore when they get too close.
|
|
+ */
|
|
+ if (ictx->product != 0xffdc) {
|
|
+ /* first, pad to 8 bytes so it conforms with everything else */
|
|
+ buf[5] = buf[6] = buf[7] = 0;
|
|
+ timeout = 500; /* in msecs */
|
|
+ /* (2*threshold) x (2*threshold) square */
|
|
+ threshold = pad_thresh ? pad_thresh : 28;
|
|
+ rel_x = buf[2];
|
|
+ rel_y = buf[3];
|
|
+
|
|
+ if (ictx->ir_protocol == IMON_IR_PROTOCOL_IMON) {
|
|
+ if ((buf[1] == 0) && ((rel_x != 0) || (rel_y != 0))) {
|
|
+ dir = stabilize((int)rel_x, (int)rel_y,
|
|
+ timeout, threshold);
|
|
+ if (!dir) {
|
|
+ ictx->kc = KEY_UNKNOWN;
|
|
+ return;
|
|
+ }
|
|
+ buf[2] = dir & 0xFF;
|
|
+ buf[3] = (dir >> 8) & 0xFF;
|
|
+ memcpy(&temp_key, buf, sizeof(temp_key));
|
|
+ remote_key = (u32) (le64_to_cpu(temp_key)
|
|
+ & 0xffffffff);
|
|
+ ki = imon_remote_key_lookup(remote_key);
|
|
+ ictx->kc =
|
|
+ imon_remote_key_table[ki % offset].keycode;
|
|
+ }
|
|
+ } else {
|
|
+ if (abs(rel_y) > abs(rel_x)) {
|
|
+ buf[2] = (rel_y > 0) ? 0x7F : 0x80;
|
|
+ buf[3] = 0;
|
|
+ ictx->kc = (rel_y > 0) ? KEY_DOWN : KEY_UP;
|
|
+ } else {
|
|
+ buf[2] = 0;
|
|
+ buf[3] = (rel_x > 0) ? 0x7F : 0x80;
|
|
+ ictx->kc = (rel_x > 0) ? KEY_RIGHT : KEY_LEFT;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Handle on-board decoded pad events for e.g. older VFD/iMON-Pad
|
|
+ * device (15c2:ffdc). The remote generates various codes from
|
|
+ * 0x68nnnnB7 to 0x6AnnnnB7, the left mouse button generates
|
|
+ * 0x688301b7 and the right one 0x688481b7. All other keys generate
|
|
+ * 0x2nnnnnnn. Position coordinate is encoded in buf[1] and buf[2] with
|
|
+ * reversed endianess. Extract direction from buffer, rotate endianess,
|
|
+ * adjust sign and feed the values into stabilize(). The resulting codes
|
|
+ * will be 0x01008000, 0x01007F00, which match the newer devices.
|
|
+ */
|
|
+ } else {
|
|
+ timeout = 10; /* in msecs */
|
|
+ /* (2*threshold) x (2*threshold) square */
|
|
+ threshold = pad_thresh ? pad_thresh : 15;
|
|
+
|
|
+ /* buf[1] is x */
|
|
+ rel_x = (buf[1] & 0x08) | (buf[1] & 0x10) >> 2 |
|
|
+ (buf[1] & 0x20) >> 4 | (buf[1] & 0x40) >> 6;
|
|
+ if (buf[0] & 0x02)
|
|
+ rel_x |= ~0x10+1;
|
|
+ /* buf[2] is y */
|
|
+ rel_y = (buf[2] & 0x08) | (buf[2] & 0x10) >> 2 |
|
|
+ (buf[2] & 0x20) >> 4 | (buf[2] & 0x40) >> 6;
|
|
+ if (buf[0] & 0x01)
|
|
+ rel_y |= ~0x10+1;
|
|
+
|
|
+ buf[0] = 0x01;
|
|
+ buf[1] = buf[4] = buf[5] = buf[6] = buf[7] = 0;
|
|
+
|
|
+ if (ictx->ir_protocol == IMON_IR_PROTOCOL_IMON) {
|
|
+ dir = stabilize((int)rel_x, (int)rel_y,
|
|
+ timeout, threshold);
|
|
+ if (!dir) {
|
|
+ ictx->kc = KEY_UNKNOWN;
|
|
+ return;
|
|
+ }
|
|
+ buf[2] = dir & 0xFF;
|
|
+ buf[3] = (dir >> 8) & 0xFF;
|
|
+ memcpy(&temp_key, buf, sizeof(temp_key));
|
|
+ remote_key = (u32) (le64_to_cpu(temp_key) & 0xffffffff);
|
|
+ ki = imon_remote_key_lookup(remote_key);
|
|
+ ictx->kc = imon_remote_key_table[ki % offset].keycode;
|
|
+ } else {
|
|
+ if (abs(rel_y) > abs(rel_x)) {
|
|
+ buf[2] = (rel_y > 0) ? 0x7F : 0x80;
|
|
+ buf[3] = 0;
|
|
+ ictx->kc = (rel_y > 0) ? KEY_DOWN : KEY_UP;
|
|
+ } else {
|
|
+ buf[2] = 0;
|
|
+ buf[3] = (rel_x > 0) ? 0x7F : 0x80;
|
|
+ ictx->kc = (rel_x > 0) ? KEY_RIGHT : KEY_LEFT;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ictx->ki = ki;
|
|
+}
|
|
+
|
|
+static int imon_parse_press_type(struct imon_context *ictx,
|
|
+ unsigned char *buf, u8 ksrc)
|
|
+{
|
|
+ int press_type = 0;
|
|
+
|
|
+ /* key release of 0x02XXXXXX key */
|
|
+ if (ictx->ki == -1 && buf[0] == 0x02 && buf[3] == 0x00)
|
|
+ ictx->kc = ictx->last_keycode;
|
|
+
|
|
+ /* mce-specific button handling */
|
|
+ else if (ksrc == IMON_BUTTON_MCE) {
|
|
+ /* initial press */
|
|
+ if (ictx->kc != ictx->last_keycode
|
|
+ || buf[2] != ictx->mce_toggle_bit) {
|
|
+ ictx->last_keycode = ictx->kc;
|
|
+ ictx->mce_toggle_bit = buf[2];
|
|
+ press_type = 1;
|
|
+ mod_timer(&ictx->itimer,
|
|
+ jiffies + msecs_to_jiffies(MCE_TIMEOUT_MS));
|
|
+ /* repeat */
|
|
+ } else {
|
|
+ press_type = 2;
|
|
+ mod_timer(&ictx->itimer,
|
|
+ jiffies + msecs_to_jiffies(MCE_TIMEOUT_MS));
|
|
+ }
|
|
+
|
|
+ /* incoherent or irrelevant data */
|
|
+ } else if (ictx->ki == -1)
|
|
+ press_type = -EINVAL;
|
|
+
|
|
+ /* key release of 0xXXXXXXb7 key */
|
|
+ else if (ictx->ki >= IMON_KEY_RELEASE_OFFSET)
|
|
+ press_type = 0;
|
|
+
|
|
+ /* this is a button press */
|
|
+ else
|
|
+ press_type = 1;
|
|
+
|
|
+ return press_type;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Process the incoming packet
|
|
+ */
|
|
+static void imon_incoming_packet(struct imon_context *ictx,
|
|
+ struct urb *urb, int intf)
|
|
+{
|
|
+ int len = urb->actual_length;
|
|
+ unsigned char *buf = urb->transfer_buffer;
|
|
+ struct device *dev = ictx->dev;
|
|
+ u16 kc;
|
|
+ bool norelease = 0;
|
|
+ int i, ki;
|
|
+ int offset = IMON_KEY_RELEASE_OFFSET;
|
|
+ u64 temp_key;
|
|
+ u64 panel_key = 0;
|
|
+ u32 remote_key;
|
|
+ struct input_dev *idev = NULL;
|
|
+ int press_type = 0;
|
|
+ int msec;
|
|
+ struct timeval t;
|
|
+ static struct timeval prev_time = { 0, 0 };
|
|
+ u8 ksrc = IMON_BUTTON_IMON;
|
|
+
|
|
+ idev = ictx->idev;
|
|
+
|
|
+ /* Figure out what key was pressed */
|
|
+ memcpy(&temp_key, buf, sizeof(temp_key));
|
|
+ if (len == 8 && buf[7] == 0xee) {
|
|
+ ksrc = IMON_BUTTON_PANEL;
|
|
+ panel_key = le64_to_cpu(temp_key);
|
|
+ ki = imon_panel_key_lookup(panel_key);
|
|
+ kc = imon_panel_key_table[ki].keycode;
|
|
+ } else {
|
|
+ remote_key = (u32) (le64_to_cpu(temp_key) & 0xffffffff);
|
|
+ if (ictx->ir_protocol == IMON_IR_PROTOCOL_MCE) {
|
|
+ if (buf[0] == 0x80)
|
|
+ ksrc = IMON_BUTTON_MCE;
|
|
+ ki = imon_mce_key_lookup(remote_key);
|
|
+ kc = imon_mce_key_table[ki].keycode;
|
|
+ } else {
|
|
+ ki = imon_remote_key_lookup(remote_key);
|
|
+ kc = imon_remote_key_table[ki % offset].keycode;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* keyboard/mouse mode toggle button */
|
|
+ if (kc == KEY_KEYBOARD && ki < offset) {
|
|
+ if (!nomouse) {
|
|
+ ictx->pad_mouse = ~(ictx->pad_mouse) & 0x1;
|
|
+ dev_dbg(dev, "toggling to %s mode\n",
|
|
+ ictx->pad_mouse ? "mouse" : "keyboard");
|
|
+ } else {
|
|
+ ictx->pad_mouse = 0;
|
|
+ dev_dbg(dev, "mouse mode was disabled by modparam\n");
|
|
+ }
|
|
+ ictx->last_keycode = kc;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ictx->ki = ki;
|
|
+ ictx->kc = kc;
|
|
+
|
|
+ /* send touchscreen events through input subsystem if touchpad data */
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA && len == 8 &&
|
|
+ buf[7] == 0x86) {
|
|
+ imon_touch_event(ictx, buf);
|
|
+
|
|
+ /* look for mouse events with pad in mouse mode */
|
|
+ } else if (ictx->pad_mouse) {
|
|
+ if (imon_mouse_event(ictx, buf, len))
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Now for some special handling to convert pad input to arrow keys */
|
|
+ if (((len == 5) && (buf[0] == 0x01) && (buf[4] == 0x00)) ||
|
|
+ ((len == 8) && (buf[0] & 0x40) &&
|
|
+ !(buf[1] & 0x1 || buf[1] >> 2 & 0x1))) {
|
|
+ len = 8;
|
|
+ imon_pad_to_keys(ictx, buf);
|
|
+ norelease = 1;
|
|
+ }
|
|
+
|
|
+ if (debug) {
|
|
+ printk(KERN_INFO "intf%d decoded packet: ", intf);
|
|
+ for (i = 0; i < len; ++i)
|
|
+ printk("%02x ", buf[i]);
|
|
+ printk("\n");
|
|
+ }
|
|
+
|
|
+ press_type = imon_parse_press_type(ictx, buf, ksrc);
|
|
+ if (press_type < 0)
|
|
+ goto not_input_data;
|
|
+
|
|
+ /* KEY_MUTE repeats from MCE and knob need to be suppressed */
|
|
+ if ((ictx->kc == KEY_MUTE && ictx->kc == ictx->last_keycode)
|
|
+ && (buf[7] == 0xee || ksrc == IMON_BUTTON_MCE)) {
|
|
+ do_gettimeofday(&t);
|
|
+ msec = tv2int(&t, &prev_time);
|
|
+ prev_time = t;
|
|
+ if (msec < 200)
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ input_report_key(idev, ictx->kc, press_type);
|
|
+ input_sync(idev);
|
|
+
|
|
+ /* panel keys and some remote keys don't generate a release */
|
|
+ if (panel_key || norelease) {
|
|
+ input_report_key(idev, ictx->kc, 0);
|
|
+ input_sync(idev);
|
|
+ }
|
|
+
|
|
+ ictx->last_keycode = ictx->kc;
|
|
+
|
|
+ return;
|
|
+
|
|
+not_input_data:
|
|
+ if (len != 8) {
|
|
+ dev_warn(dev, "imon %s: invalid incoming packet "
|
|
+ "size (len = %d, intf%d)\n", __func__, len, intf);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* iMON 2.4G associate frame */
|
|
+ if (buf[0] == 0x00 &&
|
|
+ buf[2] == 0xFF && /* REFID */
|
|
+ buf[3] == 0xFF &&
|
|
+ buf[4] == 0xFF &&
|
|
+ buf[5] == 0xFF && /* iMON 2.4G */
|
|
+ ((buf[6] == 0x4E && buf[7] == 0xDF) || /* LT */
|
|
+ (buf[6] == 0x5E && buf[7] == 0xDF))) { /* DT */
|
|
+ dev_warn(dev, "%s: remote associated refid=%02X\n",
|
|
+ __func__, buf[1]);
|
|
+ ictx->ir_isassociating = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * mce/rc6 keypresses have no distinct release code, use timer
|
|
+ */
|
|
+static void imon_mce_timeout(unsigned long data)
|
|
+{
|
|
+ struct imon_context *ictx = (struct imon_context *)data;
|
|
+
|
|
+ input_report_key(ictx->idev, ictx->last_keycode, 0);
|
|
+ input_sync(ictx->idev);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * report touchscreen input
|
|
+ */
|
|
+static void imon_touch_display_timeout(unsigned long data)
|
|
+{
|
|
+ struct imon_context *ictx = (struct imon_context *)data;
|
|
+
|
|
+ if (!ictx->display_type == IMON_DISPLAY_TYPE_VGA)
|
|
+ return;
|
|
+
|
|
+ input_report_abs(ictx->touch, ABS_X, ictx->touch_x);
|
|
+ input_report_abs(ictx->touch, ABS_Y, ictx->touch_y);
|
|
+ input_report_key(ictx->touch, BTN_TOUCH, 0x00);
|
|
+ input_sync(ictx->touch);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: receive data
|
|
+ */
|
|
+static void usb_rx_callback_intf0(struct urb *urb)
|
|
+{
|
|
+ struct imon_context *ictx;
|
|
+ unsigned char *buf;
|
|
+ int len;
|
|
+ int intfnum = 0;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+
|
|
+ ictx = (struct imon_context *)urb->context;
|
|
+ if (!ictx)
|
|
+ return;
|
|
+
|
|
+ buf = urb->transfer_buffer;
|
|
+ len = urb->actual_length;
|
|
+
|
|
+ switch (urb->status) {
|
|
+ case -ENOENT: /* usbcore unlink successful! */
|
|
+ return;
|
|
+
|
|
+ case -ESHUTDOWN: /* transport endpoint was shut down */
|
|
+ break;
|
|
+
|
|
+ case 0:
|
|
+ imon_incoming_packet(ictx, urb, intfnum);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ dev_warn(ictx->dev, "imon %s: status(%d): ignored\n",
|
|
+ __func__, urb->status);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ usb_submit_urb(ictx->rx_urb_intf0, GFP_ATOMIC);
|
|
+}
|
|
+
|
|
+static void usb_rx_callback_intf1(struct urb *urb)
|
|
+{
|
|
+ struct imon_context *ictx;
|
|
+ unsigned char *buf;
|
|
+ int len;
|
|
+ int intfnum = 1;
|
|
+
|
|
+ if (!urb)
|
|
+ return;
|
|
+
|
|
+ ictx = (struct imon_context *)urb->context;
|
|
+ if (!ictx)
|
|
+ return;
|
|
+
|
|
+ buf = urb->transfer_buffer;
|
|
+ len = urb->actual_length;
|
|
+
|
|
+ switch (urb->status) {
|
|
+ case -ENOENT: /* usbcore unlink successful! */
|
|
+ return;
|
|
+
|
|
+ case -ESHUTDOWN: /* transport endpoint was shut down */
|
|
+ break;
|
|
+
|
|
+ case 0:
|
|
+ imon_incoming_packet(ictx, urb, intfnum);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ dev_warn(ictx->dev, "imon %s: status(%d): ignored\n",
|
|
+ __func__, urb->status);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ usb_submit_urb(ictx->rx_urb_intf1, GFP_ATOMIC);
|
|
+}
|
|
+
|
|
+static struct input_dev *imon_init_idev(struct imon_context *ictx)
|
|
+{
|
|
+ struct input_dev *idev;
|
|
+ int ret, i;
|
|
+
|
|
+ idev = input_allocate_device();
|
|
+ if (!idev) {
|
|
+ dev_err(ictx->dev, "remote input dev allocation failed\n");
|
|
+ goto idev_alloc_failed;
|
|
+ }
|
|
+
|
|
+ snprintf(ictx->name_idev, sizeof(ictx->name_idev),
|
|
+ "iMON Remote (%04x:%04x)", ictx->vendor, ictx->product);
|
|
+ idev->name = ictx->name_idev;
|
|
+
|
|
+ usb_make_path(ictx->usbdev_intf0, ictx->phys_idev,
|
|
+ sizeof(ictx->phys_idev));
|
|
+ strlcat(ictx->phys_idev, "/input0", sizeof(ictx->phys_idev));
|
|
+ idev->phys = ictx->phys_idev;
|
|
+
|
|
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
|
|
+
|
|
+ idev->keybit[BIT_WORD(BTN_MOUSE)] =
|
|
+ BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT);
|
|
+ idev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y) |
|
|
+ BIT_MASK(REL_WHEEL);
|
|
+
|
|
+ input_set_drvdata(idev, ictx);
|
|
+
|
|
+ if (ir_protocol == IMON_IR_PROTOCOL_MCE)
|
|
+ ret = sparse_keymap_setup(idev, imon_mce_key_table, NULL);
|
|
+ else
|
|
+ ret = sparse_keymap_setup(idev, imon_remote_key_table, NULL);
|
|
+ if (ret)
|
|
+ goto keymap_failed;
|
|
+
|
|
+ /* can't use sparse keymap atm, 64-bit keycodes */
|
|
+ for (i = 0; i < ARRAY_SIZE(imon_panel_key_table); i++) {
|
|
+ u16 kc = imon_panel_key_table[i].keycode;
|
|
+ __set_bit(kc, idev->keybit);
|
|
+ }
|
|
+
|
|
+ usb_to_input_id(ictx->usbdev_intf0, &idev->id);
|
|
+ idev->dev.parent = ictx->dev;
|
|
+ ret = input_register_device(idev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(ictx->dev, "remote input dev register failed\n");
|
|
+ goto idev_register_failed;
|
|
+ }
|
|
+
|
|
+ return idev;
|
|
+
|
|
+idev_register_failed:
|
|
+ sparse_keymap_free(idev);
|
|
+keymap_failed:
|
|
+ input_free_device(idev);
|
|
+idev_alloc_failed:
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static struct input_dev *imon_init_touch(struct imon_context *ictx)
|
|
+{
|
|
+ struct input_dev *touch;
|
|
+ int ret;
|
|
+
|
|
+ touch = input_allocate_device();
|
|
+ if (!touch) {
|
|
+ dev_err(ictx->dev, "touchscreen input dev allocation failed\n");
|
|
+ goto touch_alloc_failed;
|
|
+ }
|
|
+
|
|
+ snprintf(ictx->name_touch, sizeof(ictx->name_touch),
|
|
+ "iMON USB Touchscreen (%04x:%04x)",
|
|
+ ictx->vendor, ictx->product);
|
|
+ touch->name = ictx->name_touch;
|
|
+
|
|
+ usb_make_path(ictx->usbdev_intf1, ictx->phys_touch,
|
|
+ sizeof(ictx->phys_touch));
|
|
+ strlcat(ictx->phys_touch, "/input1", sizeof(ictx->phys_touch));
|
|
+ touch->phys = ictx->phys_touch;
|
|
+
|
|
+ touch->evbit[0] =
|
|
+ BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
|
|
+ touch->keybit[BIT_WORD(BTN_TOUCH)] =
|
|
+ BIT_MASK(BTN_TOUCH);
|
|
+ input_set_abs_params(touch, ABS_X,
|
|
+ 0x00, 0xfff, 0, 0);
|
|
+ input_set_abs_params(touch, ABS_Y,
|
|
+ 0x00, 0xfff, 0, 0);
|
|
+
|
|
+ input_set_drvdata(touch, ictx);
|
|
+
|
|
+ usb_to_input_id(ictx->usbdev_intf1, &touch->id);
|
|
+ touch->dev.parent = ictx->dev;
|
|
+ ret = input_register_device(touch);
|
|
+ if (ret < 0) {
|
|
+ dev_info(ictx->dev, "touchscreen input dev register failed\n");
|
|
+ goto touch_register_failed;
|
|
+ }
|
|
+
|
|
+ return touch;
|
|
+
|
|
+touch_register_failed:
|
|
+ input_free_device(ictx->touch);
|
|
+ mutex_unlock(&ictx->lock);
|
|
+
|
|
+touch_alloc_failed:
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static bool imon_find_endpoints(struct imon_context *ictx,
|
|
+ struct usb_host_interface *iface_desc)
|
|
+{
|
|
+ struct usb_endpoint_descriptor *ep;
|
|
+ struct usb_endpoint_descriptor *rx_endpoint = NULL;
|
|
+ struct usb_endpoint_descriptor *tx_endpoint = NULL;
|
|
+ int ifnum = iface_desc->desc.bInterfaceNumber;
|
|
+ int num_endpts = iface_desc->desc.bNumEndpoints;
|
|
+ int i, ep_dir, ep_type;
|
|
+ bool ir_ep_found = 0;
|
|
+ bool display_ep_found = 0;
|
|
+ bool tx_control = 0;
|
|
+
|
|
+ /*
|
|
+ * Scan the endpoint list and set:
|
|
+ * first input endpoint = IR endpoint
|
|
+ * first output endpoint = display endpoint
|
|
+ */
|
|
+ for (i = 0; i < num_endpts && !(ir_ep_found && display_ep_found); ++i) {
|
|
+ ep = &iface_desc->endpoint[i].desc;
|
|
+ ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
|
|
+ ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
|
|
+
|
|
+ if (!ir_ep_found && ep_dir == USB_DIR_IN &&
|
|
+ ep_type == USB_ENDPOINT_XFER_INT) {
|
|
+
|
|
+ rx_endpoint = ep;
|
|
+ ir_ep_found = 1;
|
|
+ dev_dbg(ictx->dev, "%s: found IR endpoint\n", __func__);
|
|
+
|
|
+ } else if (!display_ep_found && ep_dir == USB_DIR_OUT &&
|
|
+ ep_type == USB_ENDPOINT_XFER_INT) {
|
|
+ tx_endpoint = ep;
|
|
+ display_ep_found = 1;
|
|
+ dev_dbg(ictx->dev, "%s: found display endpoint\n", __func__);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (ifnum == 0) {
|
|
+ ictx->rx_endpoint_intf0 = rx_endpoint;
|
|
+ /*
|
|
+ * tx is used to send characters to lcd/vfd, associate RF
|
|
+ * remotes, set IR protocol, and maybe more...
|
|
+ */
|
|
+ ictx->tx_endpoint = tx_endpoint;
|
|
+ } else {
|
|
+ ictx->rx_endpoint_intf1 = rx_endpoint;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If we didn't find a display endpoint, this is probably one of the
|
|
+ * newer iMON devices that use control urb instead of interrupt
|
|
+ */
|
|
+ if (!display_ep_found) {
|
|
+ tx_control = 1;
|
|
+ display_ep_found = 1;
|
|
+ dev_dbg(ictx->dev, "%s: device uses control endpoint, not "
|
|
+ "interface OUT endpoint\n", __func__);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Some iMON receivers have no display. Unfortunately, it seems
|
|
+ * that SoundGraph recycles device IDs between devices both with
|
|
+ * and without... :\
|
|
+ */
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_NONE) {
|
|
+ display_ep_found = 0;
|
|
+ dev_dbg(ictx->dev, "%s: device has no display\n", __func__);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * iMON Touch devices have a VGA touchscreen, but no "display", as
|
|
+ * that refers to e.g. /dev/lcd0 (a character device LCD or VFD).
|
|
+ */
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
|
|
+ display_ep_found = 0;
|
|
+ dev_dbg(ictx->dev, "%s: iMON Touch device found\n", __func__);
|
|
+ }
|
|
+
|
|
+ /* Input endpoint is mandatory */
|
|
+ if (!ir_ep_found)
|
|
+ err("%s: no valid input (IR) endpoint found.", __func__);
|
|
+
|
|
+ ictx->tx_control = tx_control;
|
|
+
|
|
+ if (display_ep_found)
|
|
+ ictx->display_supported = 1;
|
|
+
|
|
+ return ir_ep_found;
|
|
+
|
|
+}
|
|
+
|
|
+static struct imon_context *imon_init_intf0(struct usb_interface *intf)
|
|
+{
|
|
+ struct imon_context *ictx;
|
|
+ struct urb *rx_urb;
|
|
+ struct urb *tx_urb;
|
|
+ struct device *dev = &intf->dev;
|
|
+ struct usb_host_interface *iface_desc;
|
|
+ int ret;
|
|
+
|
|
+ ictx = kzalloc(sizeof(struct imon_context), GFP_KERNEL);
|
|
+ if (!ictx) {
|
|
+ dev_err(dev, "%s: kzalloc failed for context", __func__);
|
|
+ goto exit;
|
|
+ }
|
|
+ rx_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!rx_urb) {
|
|
+ dev_err(dev, "%s: usb_alloc_urb failed for IR urb", __func__);
|
|
+ goto rx_urb_alloc_failed;
|
|
+ }
|
|
+ tx_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!tx_urb) {
|
|
+ dev_err(dev, "%s: usb_alloc_urb failed for display urb",
|
|
+ __func__);
|
|
+ goto tx_urb_alloc_failed;
|
|
+ }
|
|
+
|
|
+ mutex_init(&ictx->lock);
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ if (ir_protocol == IMON_IR_PROTOCOL_MCE) {
|
|
+ init_timer(&ictx->itimer);
|
|
+ ictx->itimer.data = (unsigned long)ictx;
|
|
+ ictx->itimer.function = imon_mce_timeout;
|
|
+ }
|
|
+
|
|
+ ictx->dev = dev;
|
|
+ ictx->usbdev_intf0 = usb_get_dev(interface_to_usbdev(intf));
|
|
+ ictx->dev_present_intf0 = 1;
|
|
+ ictx->rx_urb_intf0 = rx_urb;
|
|
+ ictx->tx_urb = tx_urb;
|
|
+
|
|
+ ictx->vendor = le16_to_cpu(ictx->usbdev_intf0->descriptor.idVendor);
|
|
+ ictx->product = le16_to_cpu(ictx->usbdev_intf0->descriptor.idProduct);
|
|
+
|
|
+ iface_desc = intf->cur_altsetting;
|
|
+ if (!imon_find_endpoints(ictx, iface_desc))
|
|
+ goto find_endpoint_failed;
|
|
+
|
|
+ ictx->idev = imon_init_idev(ictx);
|
|
+ if (!ictx->idev) {
|
|
+ dev_err(dev, "%s: input device setup failed\n", __func__);
|
|
+ goto idev_setup_failed;
|
|
+ }
|
|
+
|
|
+ usb_fill_int_urb(ictx->rx_urb_intf0, ictx->usbdev_intf0,
|
|
+ usb_rcvintpipe(ictx->usbdev_intf0,
|
|
+ ictx->rx_endpoint_intf0->bEndpointAddress),
|
|
+ ictx->usb_rx_buf, sizeof(ictx->usb_rx_buf),
|
|
+ usb_rx_callback_intf0, ictx,
|
|
+ ictx->rx_endpoint_intf0->bInterval);
|
|
+
|
|
+ ret = usb_submit_urb(ictx->rx_urb_intf0, GFP_KERNEL);
|
|
+ if (ret) {
|
|
+ err("%s: usb_submit_urb failed for intf0 (%d)",
|
|
+ __func__, ret);
|
|
+ goto urb_submit_failed;
|
|
+ }
|
|
+
|
|
+ return ictx;
|
|
+
|
|
+urb_submit_failed:
|
|
+ sparse_keymap_free(ictx->idev);
|
|
+ input_unregister_device(ictx->idev);
|
|
+ input_free_device(ictx->idev);
|
|
+idev_setup_failed:
|
|
+find_endpoint_failed:
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ usb_free_urb(tx_urb);
|
|
+tx_urb_alloc_failed:
|
|
+ usb_free_urb(rx_urb);
|
|
+rx_urb_alloc_failed:
|
|
+ kfree(ictx);
|
|
+exit:
|
|
+ dev_err(dev, "unable to initialize intf0, err %d\n", ret);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static struct imon_context *imon_init_intf1(struct usb_interface *intf,
|
|
+ struct imon_context *ictx)
|
|
+{
|
|
+ struct urb *rx_urb;
|
|
+ struct usb_host_interface *iface_desc;
|
|
+ int ret;
|
|
+
|
|
+ rx_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!rx_urb) {
|
|
+ err("%s: usb_alloc_urb failed for IR urb", __func__);
|
|
+ ret = -ENOMEM;
|
|
+ goto rx_urb_alloc_failed;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
|
|
+ init_timer(&ictx->ttimer);
|
|
+ ictx->ttimer.data = (unsigned long)ictx;
|
|
+ ictx->ttimer.function = imon_touch_display_timeout;
|
|
+ }
|
|
+
|
|
+ ictx->usbdev_intf1 = usb_get_dev(interface_to_usbdev(intf));
|
|
+ ictx->dev_present_intf1 = 1;
|
|
+ ictx->rx_urb_intf1 = rx_urb;
|
|
+
|
|
+ iface_desc = intf->cur_altsetting;
|
|
+ if (!imon_find_endpoints(ictx, iface_desc))
|
|
+ goto find_endpoint_failed;
|
|
+
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
|
|
+ ictx->touch = imon_init_touch(ictx);
|
|
+ if (!ictx->touch)
|
|
+ goto touch_setup_failed;
|
|
+ } else
|
|
+ ictx->touch = NULL;
|
|
+
|
|
+ usb_fill_int_urb(ictx->rx_urb_intf1, ictx->usbdev_intf1,
|
|
+ usb_rcvintpipe(ictx->usbdev_intf1,
|
|
+ ictx->rx_endpoint_intf1->bEndpointAddress),
|
|
+ ictx->usb_rx_buf, sizeof(ictx->usb_rx_buf),
|
|
+ usb_rx_callback_intf1, ictx,
|
|
+ ictx->rx_endpoint_intf1->bInterval);
|
|
+
|
|
+ ret = usb_submit_urb(ictx->rx_urb_intf1, GFP_KERNEL);
|
|
+
|
|
+ if (ret) {
|
|
+ err("%s: usb_submit_urb failed for intf1 (%d)",
|
|
+ __func__, ret);
|
|
+ goto urb_submit_failed;
|
|
+ }
|
|
+
|
|
+ return ictx;
|
|
+
|
|
+urb_submit_failed:
|
|
+ if (ictx->touch) {
|
|
+ input_unregister_device(ictx->touch);
|
|
+ input_free_device(ictx->touch);
|
|
+ }
|
|
+touch_setup_failed:
|
|
+find_endpoint_failed:
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ usb_free_urb(rx_urb);
|
|
+rx_urb_alloc_failed:
|
|
+ dev_err(ictx->dev, "unable to initialize intf0, err %d\n", ret);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void imon_set_display_type(struct imon_context *ictx,
|
|
+ struct usb_interface *intf)
|
|
+{
|
|
+ int configured_display_type = IMON_DISPLAY_TYPE_VFD;
|
|
+
|
|
+ /*
|
|
+ * Try to auto-detect the type of display if the user hasn't set
|
|
+ * it by hand via the display_type modparam. Default is VFD.
|
|
+ */
|
|
+ if (display_type == IMON_DISPLAY_TYPE_AUTO) {
|
|
+ if (usb_match_id(intf, lcd_device_list))
|
|
+ configured_display_type = IMON_DISPLAY_TYPE_LCD;
|
|
+ else if (usb_match_id(intf, imon_touchscreen_list))
|
|
+ configured_display_type = IMON_DISPLAY_TYPE_VGA;
|
|
+ else if (usb_match_id(intf, ir_only_list))
|
|
+ configured_display_type = IMON_DISPLAY_TYPE_NONE;
|
|
+ else
|
|
+ configured_display_type = IMON_DISPLAY_TYPE_VFD;
|
|
+ } else {
|
|
+ configured_display_type = display_type;
|
|
+ dev_dbg(ictx->dev, "%s: overriding display type to %d via "
|
|
+ "modparam\n", __func__, display_type);
|
|
+ }
|
|
+
|
|
+ ictx->display_type = configured_display_type;
|
|
+}
|
|
+
|
|
+static void imon_init_display(struct imon_context *ictx,
|
|
+ struct usb_interface *intf)
|
|
+{
|
|
+ int ret;
|
|
+ const unsigned char fp_packet[] = { 0x40, 0x00, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x88 };
|
|
+
|
|
+ dev_dbg(ictx->dev, "Registering iMON display with sysfs\n");
|
|
+
|
|
+ /* set up sysfs entry for built-in clock */
|
|
+ ret = sysfs_create_group(&intf->dev.kobj,
|
|
+ &imon_display_attribute_group);
|
|
+ if (ret)
|
|
+ dev_err(ictx->dev, "Could not create display sysfs "
|
|
+ "entries(%d)", ret);
|
|
+
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_LCD)
|
|
+ ret = usb_register_dev(intf, &imon_lcd_class);
|
|
+ else
|
|
+ ret = usb_register_dev(intf, &imon_vfd_class);
|
|
+ if (ret)
|
|
+ /* Not a fatal error, so ignore */
|
|
+ dev_info(ictx->dev, "could not get a minor number for "
|
|
+ "display\n");
|
|
+
|
|
+ /* Enable front-panel buttons and/or knobs */
|
|
+ memcpy(ictx->usb_tx_buf, &fp_packet, sizeof(fp_packet));
|
|
+ ret = send_packet(ictx);
|
|
+ /* Not fatal, but warn about it */
|
|
+ if (ret)
|
|
+ dev_info(ictx->dev, "failed to enable front-panel "
|
|
+ "buttons and/or knobs\n");
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: Probe
|
|
+ */
|
|
+static int __devinit imon_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id)
|
|
+{
|
|
+ struct usb_device *usbdev = NULL;
|
|
+ struct usb_host_interface *iface_desc = NULL;
|
|
+ struct usb_interface *first_if;
|
|
+ struct device *dev = &interface->dev;
|
|
+ int ifnum, code_length, sysfs_err;
|
|
+ int ret = 0;
|
|
+ struct imon_context *ictx = NULL;
|
|
+ struct imon_context *first_if_ctx = NULL;
|
|
+ u16 vendor, product;
|
|
+
|
|
+ code_length = BUF_CHUNK_SIZE * 8;
|
|
+
|
|
+ usbdev = usb_get_dev(interface_to_usbdev(interface));
|
|
+ iface_desc = interface->cur_altsetting;
|
|
+ ifnum = iface_desc->desc.bInterfaceNumber;
|
|
+ vendor = le16_to_cpu(usbdev->descriptor.idVendor);
|
|
+ product = le16_to_cpu(usbdev->descriptor.idProduct);
|
|
+
|
|
+ dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n",
|
|
+ __func__, vendor, product, ifnum);
|
|
+
|
|
+ /* prevent races probing devices w/multiple interfaces */
|
|
+ mutex_lock(&driver_lock);
|
|
+
|
|
+ first_if = usb_ifnum_to_if(usbdev, 0);
|
|
+ first_if_ctx = (struct imon_context *)usb_get_intfdata(first_if);
|
|
+
|
|
+
|
|
+ if (ifnum == 0) {
|
|
+ ictx = imon_init_intf0(interface);
|
|
+ if (!ictx) {
|
|
+ err("%s: failed to initialize context!\n", __func__);
|
|
+ ret = -ENODEV;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ imon_set_display_type(ictx, interface);
|
|
+
|
|
+ if (ictx->display_supported)
|
|
+ imon_init_display(ictx, interface);
|
|
+
|
|
+ if (product == 0xffdc) {
|
|
+ /* RF products *also* use 0xffdc... sigh... */
|
|
+ sysfs_err = sysfs_create_group(&interface->dev.kobj,
|
|
+ &imon_rf_attribute_group);
|
|
+ if (sysfs_err)
|
|
+ err("%s: Could not create RF sysfs entries(%d)",
|
|
+ __func__, sysfs_err);
|
|
+ }
|
|
+
|
|
+ } else {
|
|
+ /* this is the secondary interface on the device */
|
|
+ ictx = imon_init_intf1(interface, first_if_ctx);
|
|
+ if (!ictx) {
|
|
+ err("%s: failed to attach to context!\n", __func__);
|
|
+ ret = -ENODEV;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ usb_set_intfdata(interface, ictx);
|
|
+
|
|
+ /* set IR protocol/remote type */
|
|
+ imon_set_ir_protocol(ictx);
|
|
+
|
|
+ dev_info(dev, "iMON device (%04x:%04x, intf%d) on "
|
|
+ "usb<%d:%d> initialized\n", vendor, product, ifnum,
|
|
+ usbdev->bus->busnum, usbdev->devnum);
|
|
+
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ mutex_unlock(&driver_lock);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail:
|
|
+ mutex_unlock(&driver_lock);
|
|
+ dev_err(dev, "unable to register, err %d\n", ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * Callback function for USB core API: disconnect
|
|
+ */
|
|
+static void __devexit imon_disconnect(struct usb_interface *interface)
|
|
+{
|
|
+ struct imon_context *ictx;
|
|
+ struct device *dev;
|
|
+ int ifnum;
|
|
+
|
|
+ /* prevent races with multi-interface device probing and display_open */
|
|
+ mutex_lock(&driver_lock);
|
|
+
|
|
+ ictx = usb_get_intfdata(interface);
|
|
+ dev = ictx->dev;
|
|
+ ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
|
|
+
|
|
+ mutex_lock(&ictx->lock);
|
|
+
|
|
+ /*
|
|
+ * sysfs_remove_group is safe to call even if sysfs_create_group
|
|
+ * hasn't been called
|
|
+ */
|
|
+ sysfs_remove_group(&interface->dev.kobj,
|
|
+ &imon_display_attribute_group);
|
|
+ sysfs_remove_group(&interface->dev.kobj,
|
|
+ &imon_rf_attribute_group);
|
|
+
|
|
+ usb_set_intfdata(interface, NULL);
|
|
+
|
|
+ /* Abort ongoing write */
|
|
+ if (ictx->tx.busy) {
|
|
+ usb_kill_urb(ictx->tx_urb);
|
|
+ complete_all(&ictx->tx.finished);
|
|
+ }
|
|
+
|
|
+ if (ifnum == 0) {
|
|
+ ictx->dev_present_intf0 = 0;
|
|
+ usb_kill_urb(ictx->rx_urb_intf0);
|
|
+ sparse_keymap_free(ictx->idev);
|
|
+ input_unregister_device(ictx->idev);
|
|
+ if (ictx->display_supported) {
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_LCD)
|
|
+ usb_deregister_dev(interface, &imon_lcd_class);
|
|
+ else
|
|
+ usb_deregister_dev(interface, &imon_vfd_class);
|
|
+ }
|
|
+ } else {
|
|
+ ictx->dev_present_intf1 = 0;
|
|
+ usb_kill_urb(ictx->rx_urb_intf1);
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA)
|
|
+ input_unregister_device(ictx->touch);
|
|
+ }
|
|
+
|
|
+ if (!ictx->dev_present_intf0 && !ictx->dev_present_intf1) {
|
|
+ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA)
|
|
+ del_timer_sync(&ictx->ttimer);
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ if (!ictx->display_isopen)
|
|
+ free_imon_context(ictx);
|
|
+ } else {
|
|
+ if (ictx->ir_protocol == IMON_IR_PROTOCOL_MCE)
|
|
+ del_timer_sync(&ictx->itimer);
|
|
+ mutex_unlock(&ictx->lock);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&driver_lock);
|
|
+
|
|
+ dev_dbg(dev, "%s: iMON device (intf%d) disconnected\n",
|
|
+ __func__, ifnum);
|
|
+}
|
|
+
|
|
+static int imon_suspend(struct usb_interface *intf, pm_message_t message)
|
|
+{
|
|
+ struct imon_context *ictx = usb_get_intfdata(intf);
|
|
+ int ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
|
|
+
|
|
+ if (ifnum == 0)
|
|
+ usb_kill_urb(ictx->rx_urb_intf0);
|
|
+ else
|
|
+ usb_kill_urb(ictx->rx_urb_intf1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int imon_resume(struct usb_interface *intf)
|
|
+{
|
|
+ int rc = 0;
|
|
+ struct imon_context *ictx = usb_get_intfdata(intf);
|
|
+ int ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
|
|
+
|
|
+ if (ifnum == 0) {
|
|
+ usb_fill_int_urb(ictx->rx_urb_intf0, ictx->usbdev_intf0,
|
|
+ usb_rcvintpipe(ictx->usbdev_intf0,
|
|
+ ictx->rx_endpoint_intf0->bEndpointAddress),
|
|
+ ictx->usb_rx_buf, sizeof(ictx->usb_rx_buf),
|
|
+ usb_rx_callback_intf0, ictx,
|
|
+ ictx->rx_endpoint_intf0->bInterval);
|
|
+
|
|
+ rc = usb_submit_urb(ictx->rx_urb_intf0, GFP_ATOMIC);
|
|
+
|
|
+ } else {
|
|
+ usb_fill_int_urb(ictx->rx_urb_intf1, ictx->usbdev_intf1,
|
|
+ usb_rcvintpipe(ictx->usbdev_intf1,
|
|
+ ictx->rx_endpoint_intf1->bEndpointAddress),
|
|
+ ictx->usb_rx_buf, sizeof(ictx->usb_rx_buf),
|
|
+ usb_rx_callback_intf1, ictx,
|
|
+ ictx->rx_endpoint_intf1->bInterval);
|
|
+
|
|
+ rc = usb_submit_urb(ictx->rx_urb_intf1, GFP_ATOMIC);
|
|
+ }
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int __init imon_init(void)
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ rc = usb_register(&imon_driver);
|
|
+ if (rc) {
|
|
+ err("%s: usb register failed(%d)", __func__, rc);
|
|
+ rc = -ENODEV;
|
|
+ }
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static void __exit imon_exit(void)
|
|
+{
|
|
+ usb_deregister(&imon_driver);
|
|
+}
|
|
+
|
|
+module_init(imon_init);
|
|
+module_exit(imon_exit);
|