--- /dev/null
+/*
+ * sapphire.c
+ *
+ * Copyright Mark Lord <mlord@pobox.com>, 2012-2020.
+ * http://rtr.ca/sapphire_remote/
+ *
+ * This is a HID driver for the TopSeed Cyberlink receiver
+ * when paired with the "Sapphire TheatriX" remote control.
+ *
+ * This replaces the in-kernel "hid-topseed" driver,
+ * and is tailored specifically for use with Mythtv
+ * without need of any external configuration or LIRC etc.
+ *
+ * Buttons can have variable repeat rates, distinguish between short
+ * and long presses, and can send macros instead of just a single keycode.
+ *
+ * Wake-from-suspend works by pressing the remote's "Power" button,
+ * after correctly configuring /proc/acpi/wakeup before suspending.
+ *
+ * User-defined keymaps and macros can be (re)configured on the fly
+ * via a /proc/ interface, using the supplied sapphire_keymap.sh script.
+ *
+ * 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; specifically version 2 of the License (GPLv2).
+ */
+#include <linux/version.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/proc_fs.h>
+#include <linux/uaccess.h>
+#include <linux/jiffies.h>
+#include <linux/pm_runtime.h>
+#include <linux/usb.h>
+#include "sapphire.h"
+
+#define SAPPHIRE_NAME "sapphire"
+#define SAPPHIRE_VERSION "7.5"
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0)
+#define PROC_OPS
+#endif
+/*
+ * "LONGKEY": Any button can be used to generate a second, different keycode,
+ * by setting the "repeats" value to "LONGKEY | KEY_XXX" for the second keycode.
+ * When that button is then tapped (quick press/release), the regular keycode is sent.
+ * But when the button is pressed/held for one second, the second keycode is sent
+ * instead of the regular keycode. This allows buttons to have dual functionalty,
+ * to solve some sticky issues.
+ *
+ * Eg. by default below, the MUTE button is set up this way: a short tap of MUTE
+ * sends the "Mute" command, but holding it down longer will instead activate
+ * the "Adjust Audio Sync" pop-up. Very, VERY handy, that.
+ *
+ * EDIT the "keycode" and "repeats" columns below to customize the keymap to your needs:
+ */
+#define SAPPHIRE_NUM_BUTTONS 46 /* The Sapphire remote sends 46 unique button codes */
+#define SAPPHIRE_KEYMAP_SIZE (SAPPHIRE_NUM_BUTTONS)
+static struct sapphire_map {
+ unsigned int button; /* raw data from sensor */
+ unsigned int keycode; /* keyboard value to pass to mythtv, or "0" for nothing */
+ unsigned int repeats; /* auto-repeat setting */
+} sapphire_keymap[SAPPHIRE_KEYMAP_SIZE] = {
+ /* button keycode repeats mythtv action */
+ /* ========== ======== =========== ============= */
+ {SAPPHIRE_UP , KEY_UP , RAMP_REPEAT }, /* up (CUSTOM) */
+ {SAPPHIRE_DOWN , KEY_DOWN , RAMP_REPEAT }, /* down (CUSTOM) */
+ {SAPPHIRE_RIGHT , KEY_RIGHT , SLOW_REPEAT }, /* right */
+ {SAPPHIRE_LEFT , KEY_LEFT , SLOW_REPEAT }, /* left */
+ {SAPPHIRE_ENTEROK , KEY_ENTER , LONGKEY|KEY_F15 }, /* Select (tap) or Audiosync (hold) */
+ {SAPPHIRE_BACK , KEY_ESC , SLOW_REPEAT }, /* Back */
+ {SAPPHIRE_PLAY , KEY_P|CTRL , NO_REPEAT }, /* Play/Pause */
+ {SAPPHIRE_PAUSE , KEY_P , NO_REPEAT }, /* Play/Pause */
+ {SAPPHIRE_VOLUP , KEY_RIGHTBRACE , FAST_REPEAT }, /* Volume Up */
+ {SAPPHIRE_VOLDOWN , KEY_LEFTBRACE , FAST_REPEAT }, /* Volume Down */
+ {SAPPHIRE_CHUP , KEY_PAGEUP , MED_REPEAT }, /* channel up (CUSTOM) */
+ {SAPPHIRE_CHDOWN , KEY_PAGEDOWN , MED_REPEAT }, /* channel down (CUSTOM) */
+ {SAPPHIRE_MUTE , KEY_F9 , LONGKEY|KEY_F15 }, /* Mute (tap) or Audiosync (hold) */
+ {SAPPHIRE_RECORD , KEY_R , NO_REPEAT }, /* Record/Delete (CUSTOM) */
+ {SAPPHIRE_FWD , KEY_DOT , NO_REPEAT }, /* FFwd */
+ {SAPPHIRE_REW , KEY_COMMA , NO_REPEAT }, /* Rewind */
+ {SAPPHIRE_ANGLE , KEY_W , NO_REPEAT }, /* Adjust Fill */
+ {SAPPHIRE_SAP , KEY_W|CTRL , NO_REPEAT }, /* Toggle Aspect Ratio */
+ {SAPPHIRE_DVDMENU , KEY_M , LONGKEY|KEY_F13|ALT }, /* Menu/DVDMenu (CUSTOM) */
+ {SAPPHIRE_INFOEPG , KEY_I , NO_REPEAT }, /* Info */
+ {SAPPHIRE_TAB , KEY_END , SLOW_REPEAT }, /* Commskip Fwd */
+ {SAPPHIRE_BACKTAB , KEY_HOME , SLOW_REPEAT }, /* Commskip Rev */
+ {SAPPHIRE_RADIO , KEY_F7|ALT , NO_REPEAT }, /* Signal Monitor */
+ {SAPPHIRE_LASTCH , KEY_H , NO_REPEAT }, /* previous chan */
+ {SAPPHIRE_LANGUAGE , KEY_EQUAL|SHIFT, NO_REPEAT }, /* Next Audio Track */
+ {SAPPHIRE_TELETEXTCC , KEY_T , NO_REPEAT }, /* Toggle Closed-Caption */
+ {SAPPHIRE_SUBTITLE , KEY_F17|ALT , NO_REPEAT }, /* Toggle subtitle (CUSTOM) */
+ {SAPPHIRE_HOMEHOUSE , KEY_F18 , NO_REPEAT }, /* jump to MainMenu (CUSTOM jumppoint) */
+ {SAPPHIRE_BLUEVIDEOS , KEY_F14 , NO_REPEAT }, /* MythVideo (CUSTOM jumppoint) */
+ {SAPPHIRE_LIVETV , KEY_F16 , NO_REPEAT }, /* Program Guide (CUSTOM jumppoint) */
+ {SAPPHIRE_REDDVDVCD , KEY_F13 , LONGKEY|KEY_F13|ALT }, /* PlayDVD/DVDMenu (CUSTOM jumppoints) */
+ {SAPPHIRE_YELLOWPICTURES, KEY_F14|ALT , NO_REPEAT }, /* Watch Recordings (CUSTOM jumppoint) */
+ {SAPPHIRE_1 , KEY_1 , SLOW_REPEAT }, /* 1 */
+ {SAPPHIRE_2 , KEY_2 , SLOW_REPEAT }, /* 2 */
+ {SAPPHIRE_3 , KEY_3 , SLOW_REPEAT }, /* 3 */
+ {SAPPHIRE_4 , KEY_4 , SLOW_REPEAT }, /* 4 */
+ {SAPPHIRE_5 , KEY_5 , SLOW_REPEAT }, /* 5 */
+ {SAPPHIRE_6 , KEY_6 , SLOW_REPEAT }, /* 6 */
+ {SAPPHIRE_7 , KEY_7 , SLOW_REPEAT }, /* 7 */
+ {SAPPHIRE_8 , KEY_8 , SLOW_REPEAT }, /* 8 */
+ {SAPPHIRE_9 , KEY_9 , SLOW_REPEAT }, /* 9 */
+ {SAPPHIRE_0 , KEY_0 , SLOW_REPEAT }, /* 0 */
+ {SAPPHIRE_STOP , KEY_MINUS , LONGKEY|KEY_MINUS|SHIFT }, /* dash/underscore symbols */
+ {SAPPHIRE_POWER , KEY_F2 , NO_REPEAT }, /* start frontend (CUSTOM desktop script) */
+ {SAPPHIRE_CLEAR , KEY_MACRO_0 , NO_REPEAT }, /* Adjust audiosync by 90msecs */
+ {SAPPHIRE_GREENMUSIC , KEY_A , NO_REPEAT }, /* Adjust time stretch */
+};
+
+/*
+ * Up to 8 key "macros" can be defined here.
+ * These are mapped using the MACRO_[0-7] enums above,
+ * and each can consist of up to 8 KEY_* values,
+ * with zeros for any trailing unused values.
+ * The special KEY_DELAY can be used anywhere inside a macro
+ * to inject a half-second pause before continuing.
+ */
+static unsigned int sapphire_macros[8][8] = {
+ /* KEY_MACRO_0 */ {KEY_F15,KEY_DOWN,KEY_LEFT,0,}, /* adjust audiosync by 90msec */
+ /* KEY_MACRO_1 */ {0,},
+ /* KEY_MACRO_2 */ {0,},
+ /* KEY_MACRO_3 */ {0,},
+ /* KEY_MACRO_4 */ {0,},
+ /* KEY_MACRO_5 */ {0,},
+ /* KEY_MACRO_6 */ {0,},
+ /* KEY_MACRO_7 */ {0,}
+};
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
+#define SAPPHIRE_TIMER_ARG_T unsigned long
+static inline void sapphire_timer_setup(struct timer_list *timer_p, void *callback)
+{
+ init_timer(timer_p);
+ timer_p->function = callback;
+ timer_p->data = (long)timer_p;
+}
+#else
+#define sapphire_timer_setup(timer_p, callback) timer_setup(timer_p, callback, 0)
+#define SAPPHIRE_TIMER_ARG_T struct timer_list *
+#endif
+
+static struct sapphire_dev {
+ spinlock_t lock;
+ struct input_dev *idev;
+ struct timer_list key_timer;
+ u64 last_event;
+ unsigned long delay_end;
+ unsigned long next_wakeup;
+ unsigned int ready;
+ unsigned int event_count;
+ unsigned int down_key; /* button currently being pressed */
+ unsigned int next_repeat; /* repeat interval for down_key */
+ unsigned int last_key;
+ unsigned int dualkey_short; /* dual-function button "short press" value */
+ unsigned int dualkey_long; /* dual-function button "long press" value */
+ unsigned int *delayed_macro; /* macro to be resumed after a 1/2 second delay */
+ unsigned int delayed_next; /* next button within the macro to be resumed */
+ unsigned int raw_key; /* a completely untranslated button being pressed */
+} sapphire_dev, *dev = &sapphire_dev;
+
+enum {
+ SLOW_RATE = HZ / 4, /* repeat rate for SLOW_REPEAT */
+ MED_RATE = HZ / 6, /* repeat rate for MED_REPEAT */
+ FAST_RATE = HZ / 8, /* repeat rate for FAST_REPEAT */
+ RAMP_MAX = HZ / 10, /* max repeat rate for RAMP_REPEAT */
+ RAMP_ADJ = HZ / 40, /* amount to speed up by after each repeat */
+ RAMPING = 0x80000000, /* flag to indicate current dev->down_key is using RAMP_REPEAT */
+ MODIFIERS = CTRL|SHIFT|ALT|META, /* convenient mask of all modifier keys */
+ PRESSED = 1, /* for use with input_event() */
+ RELEASED = 0, /* for use with input_event() */
+};
+
+static unsigned int slow_rate = SLOW_RATE;
+static unsigned int med_rate = MED_RATE;
+static unsigned int fast_rate = FAST_RATE;
+static unsigned int ramp_adj = RAMP_ADJ;
+static unsigned int ramp_max = RAMP_MAX;
+
+/*
+ * Translate a [XS]APPHIRE_ "button" into a KEY_ "keycode" or "macro".
+ */
+static unsigned int sapphire_remap(unsigned int button, unsigned int *repeats)
+{
+ struct sapphire_map *map;
+
+ for (map = sapphire_keymap; map != (sapphire_keymap + SAPPHIRE_KEYMAP_SIZE); ++map) {
+ if (map->button == button && map->keycode) {
+ *repeats = map->repeats;
+ return map->keycode;
+ }
+ }
+ *repeats = NO_REPEAT;
+ return 0;
+}
+
+/*
+ * Remember the last key pressed for later retrieval from /proc/sapphire
+ */
+static void sapphire_save_key(unsigned int key)
+{
+ if (key)
+ dev->last_key = key;
+ dev->event_count++;
+ dev->last_event = get_jiffies_64();
+}
+
+/*
+ * Send "modifier" (CTRL, SHIFT, ALT, META) press/release events to the Linux input system.
+ */
+static void sapphire_send_modifiers(unsigned int modifiers, int pressed_released)
+{
+ if (modifiers & CTRL)
+ input_event(dev->idev, EV_KEY, KEY_LEFTCTRL, pressed_released);
+ if (modifiers & SHIFT)
+ input_event(dev->idev, EV_KEY, KEY_LEFTSHIFT, pressed_released);
+ if (modifiers & ALT)
+ input_event(dev->idev, EV_KEY, KEY_LEFTALT, pressed_released);
+ if (modifiers & META)
+ input_event(dev->idev, EV_KEY, KEY_LEFTMETA, pressed_released);
+}
+
+/*
+ * Start/modify the keypress/repeat timer:
+ */
+static void sapphire_set_timer (unsigned long timeout)
+{
+ if (timeout)
+ dev->next_wakeup = jiffies + timeout;
+ else if (!time_before(jiffies, dev->next_wakeup))
+ dev->next_wakeup = jiffies + 2;
+ mod_timer(&dev->key_timer, dev->next_wakeup);
+}
+
+static void sapphire_send_key(unsigned int key, unsigned int repeat_delay);
+
+static int sapphire_send_macro (unsigned int *macro, unsigned int next)
+{
+ unsigned int i;
+ for (i = next; i <= 7 && macro[i]; ++i) {
+ unsigned int key = macro[i];
+ if (key == KEY_DELAY) {
+ if (i < 7 && macro[i + 1]) {
+ dev->delayed_macro = macro;
+ dev->delayed_next = i + 1;
+ sapphire_set_timer(HZ / 2);
+ return 1;
+ }
+ break;
+ }
+ sapphire_send_key(key, NO_REPEAT);
+ }
+ return 0;
+}
+
+/*
+ * Send a translated key/macro to the Linux input system.
+ */
+static void sapphire_send_key(unsigned int key, unsigned int repeat_delay)
+{
+ if (!key)
+ return;
+ sapphire_save_key(key);
+ if ((key & KEY_MACRO_0) == KEY_MACRO_0) {
+ unsigned int n = key & (KEY_MACRO_0 ^ KEY_MACRO_7);
+ if (sapphire_send_macro(sapphire_macros[n], 0))
+ return;
+ } else {
+ unsigned int modifiers = key & MODIFIERS;
+ if (modifiers) {
+ key &= ~MODIFIERS;
+ sapphire_send_modifiers(modifiers, PRESSED);
+ }
+ input_event(dev->idev, EV_KEY, key, PRESSED);
+ input_event(dev->idev, EV_KEY, key, RELEASED);
+ if (modifiers)
+ sapphire_send_modifiers(modifiers, RELEASED);
+ input_sync(dev->idev);
+ }
+ if (repeat_delay)
+ sapphire_set_timer(repeat_delay);
+}
+
+/*
+ * Timer callback function, used to handle repeats and "LONGKEYs".
+ */
+static void sapphire_key_timeout(SAPPHIRE_TIMER_ARG_T arg)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->ready = 1;
+ /*
+ * FIXME: not sure if this can ever happen for real.
+ *
+ * In theory, we could get blocked on the spin_lock_irqsave() above
+ * while another part of the driver is busy modifying the timer value
+ * in response to a new button event. This could lead to us handling
+ * the new event immediately rather than after the newly set timeout.
+ * Guard against it by verifying that "next_wakeup" has really arrived.
+ */
+ if (time_before(jiffies, dev->next_wakeup)) {
+ /* Spinlock race on an old timer value; skip this wakeup. */
+ printk(KERN_INFO "%s: workaround activated: jiffies=%lu wakeup=%lu\n", __func__, jiffies, dev->next_wakeup);
+ sapphire_set_timer(0); /* reset the timer with same next_wakeup value */
+ } else if (dev->delayed_macro) {
+ unsigned int *macro = dev->delayed_macro;
+ dev->delayed_macro = NULL;
+ sapphire_send_macro(macro, dev->delayed_next);
+ } else if (dev->down_key) {
+ unsigned int next_repeat = dev->next_repeat & ~RAMPING;
+ if (dev->next_repeat & RAMPING) {
+ /* Gradually ramp-up the repeat rate for this key */
+ if (next_repeat > ramp_max) {
+ next_repeat -= ramp_adj;
+ if (next_repeat < ramp_max)
+ next_repeat = ramp_max;
+ }
+ dev->next_repeat = next_repeat | RAMPING;
+ }
+ sapphire_send_key(dev->down_key, next_repeat);
+ } else if (dev->dualkey_short) {
+ sapphire_send_key(dev->dualkey_long, NO_REPEAT);
+ dev->dualkey_long = dev->dualkey_short = 0;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/*
+ * Pass along a button press event, setting up the repeat mechanism appropriately.
+ */
+static void sapphire_handle_key (unsigned int key, unsigned int repeats)
+{
+ const unsigned int REPEAT_DELAY = (HZ/2) - (HZ/6);
+ unsigned int next_repeat = 0;
+
+ switch (repeats) {
+ case RAWKEY:
+ del_timer(&dev->key_timer);
+ input_event(dev->idev, EV_KEY, key, PRESSED);
+ input_sync(dev->idev);
+ dev->raw_key = key;
+ return;
+ case SLOW_REPEAT:
+ next_repeat = slow_rate;
+ break;
+ case MED_REPEAT:
+ next_repeat = med_rate;
+ break;
+ case FAST_REPEAT:
+ next_repeat = fast_rate;
+ break;
+ case RAMP_REPEAT:
+ next_repeat = (HZ/3) | RAMPING;
+ break;
+ default:
+ if (repeats & LONGKEY) {
+ /*
+ * We don't know if it's a short or long press yet.
+ * Start the key_timer to sort things out.
+ */
+ dev->dualkey_short = key;
+ dev->dualkey_long = repeats & ~LONGKEY;
+ sapphire_set_timer(HZ);
+ dev->delay_end = dev->next_wakeup;
+ return;
+ }
+ }
+ sapphire_send_key(key, next_repeat ? REPEAT_DELAY : NO_REPEAT);
+ if (next_repeat ) {
+ dev->down_key = key;
+ dev->next_repeat = next_repeat;
+ }
+}
+
+/*
+ * Handle a low-level press or release event,
+ */
+static void sapphire_event(u32 data)
+{
+ /* Treat each new event as a "release" for any previous "press" of a button */
+ if (dev->down_key || dev->dualkey_short) {
+ dev->down_key = 0;
+ del_timer(&dev->key_timer);
+ if (dev->dualkey_short) {
+ if (time_before(jiffies, dev->delay_end))
+ sapphire_send_key(dev->dualkey_short, NO_REPEAT);
+ else
+ sapphire_send_key(dev->dualkey_long, NO_REPEAT);
+ dev->dualkey_long = dev->dualkey_short = 0;
+ }
+ }
+ if (dev->raw_key) {
+ input_event(dev->idev, EV_KEY, dev->raw_key, RELEASED);
+ input_sync(dev->idev);
+ dev->raw_key = 0;
+ }
+ /* Key "press" events are > 0x10; nothing required here for "release" events (<= 0x10) */
+ if (data > 0x10) { /* key "press" event? */
+ unsigned int repeats, key = sapphire_remap(data, &repeats);
+ if (key) {
+ sapphire_handle_key(key, repeats);
+ return;
+ }
+ printk(KERN_INFO "%s: unmapped button: 0x%08x\n", __func__, data);
+ }
+ sapphire_save_key(0); /* timestamp the event in /proc/, for shutdown timing.. */
+}
+
+/*
+ * Debouncing, primarily for when multiple receivers are used with a single remote.
+ */
+static int sapphire_debounce (u32 data)
+{
+ static u32 debounce_data;
+ static u64 debounce_time;
+ u64 now = get_jiffies_64();
+
+ if (debounce_time && data == debounce_data) {
+ u64 elapsed = now - debounce_time;
+ if (elapsed < (HZ/8)) {
+ //printk(KERN_INFO "d=%08x ld=%08x t=%llu lt=%llu IGNORE\n", data, debounce_data, now, debounce_time);
+ return 0; /* discard */
+ }
+ }
+ //printk(KERN_INFO "k=%08x lk=%08x t=%llu lt=%llu\n", data, debounce_data, now, debounce_time);
+ debounce_data = data;
+ debounce_time = now;
+ return 1; /* keep */
+}
+
+/*
+ * This gets called each time the IR receiver "receives" something from a remote control.
+ * The "size" of "raw_data" bytes received varies depending upon the button pressed,
+ * but we need only the first four bytes to uniquely distinguish all buttons from each other.
+ * The exceptions are the "OK" and "Enter" buttons, which send 100% identical data,
+ * so we cannot distinguish those from each other. All other buttons send unique data.
+ */
+static int sapphire_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *raw_data, int size)
+{
+ unsigned long flags;
+ u32 data = 0;
+ int i;
+
+ if (!(hdev->claimed & HID_CLAIMED_INPUT))
+ return 0;
+ if (dev->ready) {
+ if (size > 4) /* Use only the first four bytes or less */
+ size = 4;
+ for (i = size; i > 0; )
+ data = (data << 8) | raw_data[--i];
+ spin_lock_irqsave(&dev->lock, flags);
+ if (sapphire_debounce(data))
+ sapphire_event(data);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ }
+ return -1; /* event was handled (used to return "1" here) */
+}
+
+/*
+ * This hook provides a means for other drivers to feed
+ * their button press/release events through this driver,
+ * to take advantage of sapphire's macros, variable repeats,
+ * long/short press mappings, modifier keys (CTRL,ALT,SHIFT,META),
+ * and the /proc/sapphire interface.
+ *
+ * The incoming data must be pre-translated to sapphire buttons
+ * by the caller, sending non-zero [XS]APPHIRE_* codes for presses,
+ * and zero for button release events.
+ *
+ * There is a "glue.c" driver available demonstrating this,
+ * in conjunction with a tiny patch to cx88-input.c.
+ * This allows cx88 remote controls to feed through sapphire.c
+ * and behave similarly to the native Sapphire remote-control.
+ */
+void sapphire_relay (u32 data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ sapphire_event(data);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+EXPORT_SYMBOL(sapphire_relay);
+
+/*
+ * Parse and save a single macro definition written via /proc/
+ */
+static int sapphire_store_macro(char *buf)
+{
+ unsigned long flags;
+ unsigned int vals[8] = {0,}, macro, n;
+ char excess;
+ int count;
+
+ count = sscanf(buf, "%x %x %x %x %x %x %x %x %x %c", ¯o,
+ &vals[0], &vals[1], &vals[2], &vals[3],
+ &vals[4], &vals[5], &vals[6], &vals[7], &excess);
+ if (count < 1 || count > 9 || (macro & KEY_MACRO_0) != KEY_MACRO_0)
+ return -EINVAL;
+ spin_lock_irqsave(&dev->lock, flags);
+ n = macro & (KEY_MACRO_0 ^ KEY_MACRO_7);
+ memcpy(sapphire_macros[n], vals, sizeof(vals));
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+}
+
+/*
+ * Parse and save a single button mapping written via /proc/
+ */
+static int sapphire_store_keymap(struct sapphire_map *map, char *buf)
+{
+ unsigned long flags;
+ unsigned int vals[3] = {0,};
+ char excess;
+
+ if (3 != sscanf(buf, "%x %x %x %c", &vals[0], &vals[1], &vals[2], &excess))
+ return -EINVAL;
+ spin_lock_irqsave(&dev->lock, flags);
+ map->button = vals[0];
+ map->keycode = vals[1];
+ map->repeats = vals[2];
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+}
+
+/*
+ * Provide useful info via /proc/
+ */
+static int sapphire_oldproc_read(char *buf, char **start, off_t offset, int count, int *eof, void *data)
+{
+ u64 last_event, now, elapsed;
+ unsigned long flags;
+ unsigned int len, last_key = 0, secs = ~0, event_count;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ event_count = dev->event_count;
+ last_event = dev->last_event;
+ last_key = dev->last_key;
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ now = get_jiffies_64();
+ elapsed = now - last_event;
+ secs = ((unsigned int)elapsed) / HZ;
+ len = sprintf(buf, "event_count %u\nlast_key 0x%08x\nelapsed_secs %u\n", event_count, last_key, secs);
+ *eof = 1;
+ return len;
+}
+
+static int sapphire_get_HZ_vals (u8 *line, const char *keyword, int *val1, int *val2)
+{
+ int v1, v2, keyword_len = strlen(keyword);
+
+ if (0 != strncmp(line, keyword, keyword_len) || line[keyword_len] != ' ')
+ return -EAGAIN;
+ line += keyword_len + 1;
+ if ((val2 && 2 == sscanf(line, "%u %u", &v1, &v2)) || (1 == sscanf(line, "%u", &v1))) {
+ *val1 = HZ / v1;
+ if (val2)
+ *val2 = HZ / v2;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Handles writes to /proc/ for altering key/macro mappings on the fly,
+ * and also for injecting keycodes from scripts etc.
+ */
+static int sapphire_oldproc_write(struct file *file, const char __user *ubuf, unsigned long count, void *data)
+{
+ static const char SENDKEY[7] = "SENDKEY";
+ static const char REPEAT_RATE[] = "REPEAT_RATE"; // Original backward-compatible name for RAMP_RATE
+ static const char RAMP_RATE[] = "RAMP_RATE";
+ static const char SLOW_RATE[] = "SLOW_RATE";
+ static const char MED_RATE[] = "MED_RATE";
+ static const char FAST_RATE[] = "FAST_RATE";
+ unsigned int len = count, val, ret = 0;
+ u8 *buf = kmalloc(len + 2, GFP_KERNEL), *line, *eol;
+ struct sapphire_map *map;
+
+ if (!buf)
+ return -ENOMEM;
+ if (copy_from_user(buf, ubuf, len)) {
+ kfree(buf);
+ return -EFAULT;
+ }
+ if (len == 0 || buf[len - 1] != '\n')
+ buf[len++] = '\n';
+ buf[len++] = '\0';
+ for (line = buf; line[0]; ) {
+ int sendkey = 0;
+ for (eol = line; *eol != '\n'; ++eol);
+ *eol = '\0';
+ if (0 == strncmp(line, SENDKEY, sizeof(SENDKEY))) {
+ line += sizeof(SENDKEY);
+ sendkey = 1;
+ } else {
+ ret = sapphire_get_HZ_vals(line, REPEAT_RATE, &ramp_adj, &ramp_max);
+ if (ret == -EAGAIN)
+ ret = sapphire_get_HZ_vals(line, RAMP_RATE, &ramp_adj, &ramp_max);
+ if (ret == -EAGAIN)
+ ret = sapphire_get_HZ_vals(line, SLOW_RATE, &slow_rate, NULL);
+ if (ret == -EAGAIN)
+ ret = sapphire_get_HZ_vals(line, MED_RATE, &med_rate, NULL);
+ if (ret == -EAGAIN)
+ ret = sapphire_get_HZ_vals(line, FAST_RATE, &fast_rate, NULL);
+ if (ret != -EAGAIN) {
+ if (ret == 0)
+ goto next_line;
+ break;
+ }
+ }
+ if (1 != sscanf(line, "%x", &val)) {
+ ret = -EINVAL;
+ break;
+ }
+ if (sendkey) {
+ unsigned long flags;
+ spin_lock_irqsave(&dev->lock, flags);
+ sapphire_send_key(val, NO_REPEAT);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ } else {
+ ret = sapphire_store_macro(line);
+ if (ret) {
+ ret = -EINVAL;
+ for (map = sapphire_keymap; map != (sapphire_keymap + SAPPHIRE_KEYMAP_SIZE); ++map) {
+ if (!map->button || map->button == val) {
+ ret = sapphire_store_keymap(map, line);
+ break;
+ }
+ }
+ if (ret)
+ break;
+ }
+ }
+next_line:
+ line = eol + 1;
+ }
+ if (ret && line)
+ printk(KERN_ERR "%s: parse error: \"%s\"\n", __func__, line);
+ kfree(buf);
+ return ret ? ret : count;
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0))
+
+static void sapphire_create_proc_entry (void)
+{
+ static struct proc_dir_entry *entry;
+
+ entry = create_proc_entry(SAPPHIRE_NAME, S_IFREG|S_IRUGO|S_IWUGO, NULL);
+ if (entry) {
+ entry->write_proc = sapphire_oldproc_write;
+ entry->read_proc = sapphire_oldproc_read;
+ }
+}
+
+#else /* LINUX_VERSION_CODE */
+
+static ssize_t sapphire_newproc_write (struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
+{
+ return sapphire_oldproc_write(file, buffer, count, PDE_DATA(file_inode(file)));
+}
+
+static ssize_t sapphire_newproc_read (struct file *file, char __user *buffer, size_t count, loff_t *ppos)
+{
+ unsigned char *kbuf = (void *)__get_free_page(GFP_KERNEL);
+ ssize_t len;
+ int eof = 0;
+
+ if (!kbuf)
+ return -ENOMEM;
+ len = sapphire_oldproc_read(kbuf, NULL, 0, PAGE_SIZE - 1, &eof, PDE_DATA(file_inode(file)));
+ len = simple_read_from_buffer(buffer, count, ppos, kbuf, len);
+ free_page((long)kbuf);
+ return len;
+}
+
+#ifdef PROC_OPS
+static const struct proc_ops sapphire_newproc_fops = {
+ .proc_read = sapphire_newproc_read,
+ .proc_write = sapphire_newproc_write,
+ .proc_lseek = default_llseek,
+};
+#else
+static const struct file_operations sapphire_newproc_fops = {
+ .read = sapphire_newproc_read,
+ .write = sapphire_newproc_write,
+ .llseek = default_llseek,
+};
+#endif
+
+static void sapphire_create_proc_entry (void)
+{
+ static struct proc_dir_entry *entry;
+ entry = proc_create_data(SAPPHIRE_NAME, S_IFREG|S_IRUGO|S_IWUGO, NULL, &sapphire_newproc_fops, dev);
+}
+
+#endif /* LINUX_VERSION_CODE */
+
+/*
+ * Invoked by the kernel each time one of our IR receivers is plugged into a USB port.
+ * We can/do handle multiple receivers, but there's no point to ever have more than one,
+ * because the receiver hardware is not keyed to a specific transmitter (remote control).
+ *
+ * If buttons are pressed before probe is run, the driver sometimes receives
+ * a single PRESS event at startup, with no corresponding RELEASE event.
+ * This is bad. We prevent the stray presses by ignoring all input from the device
+ * for one second after probe, and then set ready=1 to enable normal processing.
+ */
+static int sapphire_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret = hid_parse(hdev);
+printk(KERN_INFO "%s: hdev=%p id=%p\n", __func__, hdev, id);
+ if (ret)
+ return ret;
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ printk(KERN_INFO SAPPHIRE_NAME ": hw_start failed, err=%d\n", ret);
+ } else {
+ /* Eat spurious data from the device for one second after connect */
+ unsigned long flags;
+ spin_lock_irqsave(&dev->lock, flags);
+ sapphire_event(0); /* clear current downkey state */
+ dev->ready = 0; /* ignore input until the timer fires */
+ sapphire_set_timer(HZ);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ }
+ return ret;
+}
+
+/*
+ * Invoked by the kernel each time one of our IR receivers is unplugged from a USB port.
+ */
+static void sapphire_remove(struct hid_device *hdev)
+{
+printk(KERN_INFO "%s: hdev=%p\n", __func__, hdev);
+ /*
+ * We are supposed to do hid_hw_stop() here.
+ * But some Ubuntu kernels (eg. 3.11.x, 3.2.x) totally kill the device
+ * when doing this, such that even hid_hw_start() won't revive it.
+ * Other kernels handle it much better, but picking and choosing
+ * can be very problematic. So..
+ */
+ hid_hw_stop(hdev);
+}
+
+#ifdef CONFIG_PM
+/*
+ * The IR receiver becomes non-responsive when subjected to USB autosuspend.
+ * This is disabled by default at probe() time, but system scripts
+ * (particularly on suspend/resume) may modify the setting,
+ * and the result is that the IR receiver could still suffer autosuspend.
+ *
+ * So.. when the system does try it, by calling sapphire_suspend() below,
+ * we reject the attempt (-EBUSY) and also reset the autosuspend setting
+ * back to "on" again. Unfortunately, this cannot be done directly
+ * from sapphire_suspend() due to spinlock recursion in the PM code,
+ * so we schedule a worker to do it from a less-encumbered state.
+ */
+struct sapphire_pm_work {
+ struct work_struct work;
+ struct usb_device *udev;
+};
+
+static void sapphire_disable_usb_autosuspend(struct work_struct *work)
+{
+ struct sapphire_pm_work *w = container_of(work, struct sapphire_pm_work, work);
+
+ usb_disable_autosuspend(w->udev);
+ kfree(w);
+ printk(KERN_INFO SAPPHIRE_NAME ": disabled USB autosuspend\n");
+}
+
+static void sapphire_schedule_pm_work(struct hid_device *hdev)
+{
+ struct sapphire_pm_work *w = kmalloc(sizeof(struct sapphire_pm_work), GFP_ATOMIC);
+ if (w) {
+ w->udev = container_of(hdev->dev.parent->parent, struct usb_device, dev);
+ INIT_WORK(&w->work, sapphire_disable_usb_autosuspend);
+ schedule_work(&w->work);
+ }
+}
+
+static int sapphire_suspend(struct hid_device *hdev, pm_message_t message)
+{
+ if (PMSG_IS_AUTO(message)) {
+ sapphire_schedule_pm_work(hdev); /* prevent future attempts */
+ return -EBUSY; /* prevent _this_ USB autosuspend attempt */
+ }
+ return 0;
+}
+
+static int sapphire_resume(struct hid_device *hdev)
+{
+ return 0;
+}
+
+#endif
+
+/*
+ * Structures used to associate this driver with specific USB device types.
+ */
+static const struct hid_device_id sapphire_devices[] = {
+ {HID_USB_DEVICE(0x0766,0x0204)},
+ {}
+};
+MODULE_DEVICE_TABLE(hid, sapphire_devices);
+static struct hid_driver sapphire_driver = {
+ .name = SAPPHIRE_NAME,
+ .id_table = sapphire_devices,
+ .raw_event = sapphire_raw_event,
+ .probe = sapphire_probe,
+ .remove = sapphire_remove,
+#ifdef CONFIG_PM
+ .suspend = sapphire_suspend,
+ .resume = sapphire_resume,
+#endif
+};
+
+/*
+ * We don't use the input_dev provided by the hid layer,
+ * because (1) it provides two of them (mouse & keyboard),
+ * and (2) we need an input_dev even if no receiver is plugged in.
+ * Here we allocate a single global input_dev for use by the driver.
+ */
+static int sapphire_create (void)
+{
+ int keycode, err;
+
+ memset(dev, 0, sizeof(*dev)); /* paranoia */
+ spin_lock_init(&dev->lock);
+ sapphire_timer_setup(&dev->key_timer, sapphire_key_timeout);
+
+ dev->idev = input_allocate_device();
+ if (!dev->idev) {
+ printk(KERN_ERR "%s: input_allocate_device() failed\n", __func__);
+ return -ENOMEM;
+ }
+ dev->idev->name = SAPPHIRE_NAME;
+ dev->idev->phys = "none";
+ dev->idev->evbit[0] = BIT_MASK(EV_KEY);
+ for (keycode = 1; keycode < KEY_MAX; ++keycode)
+ __set_bit(keycode, dev->idev->keybit);
+ err = input_register_device(dev->idev);
+ if (err) {
+ printk(KERN_ERR "%s: input_register_device() failed, ret=%d\n", __func__, err);
+ input_free_device(dev->idev);
+ dev->idev = NULL;
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * This is the inverse of sapphire_create_inputdev().
+ */
+static void sapphire_destroy (void)
+{
+ del_timer_sync(&dev->key_timer);
+ input_unregister_device(dev->idev);
+ input_free_device(dev->idev);
+}
+
+/*
+ * Invoked by the kernel when this module is loaded.
+ */
+static int __init sapphire_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "%s: " SAPPHIRE_NAME " remote control driver v%s\n", __func__, SAPPHIRE_VERSION);
+ ret = sapphire_create();
+ if (ret)
+ return ret;
+ dev->last_event = get_jiffies_64();
+ ret = hid_register_driver(&sapphire_driver);
+ if (ret)
+ sapphire_destroy();
+ else
+ sapphire_create_proc_entry();
+ return ret;
+}
+
+/*
+ * Invoked by the kernel when this module is removed/unloaded.
+ */
+static void __exit sapphire_exit(void)
+{
+ remove_proc_entry(SAPPHIRE_NAME, NULL);
+ hid_unregister_driver(&sapphire_driver);
+ sapphire_destroy();
+}
+
+module_init(sapphire_init);
+module_exit(sapphire_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Lord");