--- /dev/null
+build-stamp
+configure-stamp
--- /dev/null
+#/usr/bin/make
+SRC = $(DESTDIR)/usr/src
+SHARE = $(DESTDIR)/usr/share/$(NAME)-dkms
+
+all:
+
+clean:
+
+install:
+
+#source tree
+ifeq ("$(wildcard $(NAME)-$(VERSION))", "$(NAME)-$(VERSION)")
+ install -d "$(SRC)"
+ cp -a $(NAME)-$(VERSION) $(SRC)
+ chmod 644 -R "$(SRC)/$(NAME)-$(VERSION)"
+endif
+
+#tarball, possibly with binaries
+ifeq ("$(wildcard $(NAME)-$(VERSION).dkms.tar.gz)", "$(NAME)-$(VERSION).dkms.tar.gz")
+ install -d "$(SHARE)"
+ install -m 644 $(NAME)-$(VERSION).dkms.tar.gz "$(SHARE)"
+endif
+
+#postinst, only if we are supporting legacy mode
+ifeq ("$(wildcard common.postinst)", "common.postinst")
+ install -d "$(SHARE)"
+ install -m 755 $(PREFIX)/usr/lib/dkms/common.postinst $(SHARE)/postinst
+endif
--- /dev/null
+#!/bin/sh
+# Copyright (C) 2002-2005 Flavio Stanchina
+# Copyright (C) 2005-2006 Aric Cyr
+# Copyright (C) 2007 Mario Limonciello
+# Copyright (C) 2009 Alberto Milone
+
+set -e
+
+uname_s=$(uname -s)
+
+_get_kernel_dir() {
+ KVER=$1
+ case ${uname_s} in
+ Linux) DIR="/lib/modules/$KVER/build" ;;
+ GNU/kFreeBSD) DIR="/usr/src/kfreebsd-headers-$KVER/sys" ;;
+ esac
+ echo $DIR
+}
+
+_check_kernel_dir() {
+ DIR=$(_get_kernel_dir $1)
+ case ${uname_s} in
+ Linux) test -e $DIR/include ;;
+ GNU/kFreeBSD) test -e $DIR/kern && test -e $DIR/conf/kmod.mk ;;
+ *) return 1 ;;
+ esac
+ return $?
+}
+
+# Check the existence of a kernel named as $1
+_is_kernel_name_correct() {
+ CORRECT="no"
+ KERNEL_NAME=$1
+
+ for kernel in /boot/config-*; do
+ KERNEL=${kernel#*-}
+ if [ "${KERNEL}" = "${KERNEL_NAME}" ]; then
+ CORRECT="yes"
+ break
+ fi
+ done
+
+ echo $CORRECT
+}
+
+
+# Get the most recent kernel on Debian based systems. This keeps
+# into account both the version and the ABI. If the current kernel
+# is the most recent kernel then the function will print a null string.
+_get_newest_kernel_debian() {
+ NEWEST_KERNEL=
+ NEWEST_VERSION=
+ NEWEST_ABI=
+
+ for kernel in /boot/config-*; do
+ KERNEL=${kernel#*-}
+ KERNEL_VERSION=${KERNEL%%-*}
+ ABI=${KERNEL#*-}
+ ABI=${ABI%%-*}
+
+ if [ -z "$NEWEST_KERNEL" ]; then
+ # The 1st time get a version which is bigger than $1
+ COMPARE_TO=$1
+ else
+ # Get the biggest version
+ COMPARE_TO="$NEWEST_VERSION-$NEWEST_ABI"
+ fi
+
+ # if $kernel is greater than $COMPARE_TO
+ if [ `dpkg --compare-versions "$KERNEL_VERSION-$ABI" gt "$COMPARE_TO" && echo "yes" || \
+ echo "no"` = "yes" ]; then
+ NEWEST_KERNEL=$KERNEL
+ NEWEST_VERSION=$KERNEL_VERSION
+ NEWEST_ABI=$ABI
+ fi
+ done
+
+ echo "$NEWEST_KERNEL"
+}
+
+# Get the most recent kernel in Rhel based systems. If the current kernel
+# is the most recent kernel then the function will print a null string.
+_get_newest_kernel_rhel() {
+ NEWEST_KERNEL=
+
+ LAST_INSTALLED_KERNEL=$(rpm -q --whatprovides kernel --last | grep kernel -m1 | cut -f1 -d' ')
+
+ LIK_FORMATTED_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{VERSION}-%{RELEASE}.%{ARCH}\n")
+
+ if [ `echo $LIK_FORMATTED_NAME | grep 2.6 >/dev/null` ]; then
+ # Fedora and Suse
+ NEWEST_KERNEL=$LIK_FORMATTED_NAME
+ else
+ # Hack for Mandriva where $LIK_FORMATTED_NAME is broken
+ LIK_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{NAME}\n")
+ LIK_TYPE=${LIK_NAME#kernel-}
+ LIK_TYPE=${LIK_TYPE%%-*}
+ LIK_STRIPPED=${LIK_NAME#kernel-}
+ LIK_STRIPPED=${LIK_STRIPPED#$LIK_TYPE-}
+ LIK_STRIPPED_BASE=${LIK_STRIPPED%%-*}
+ LIK_STRIPPED_END=${LIK_STRIPPED#$LIK_STRIPPED_BASE-}
+ LIK_FINAL=$LIK_STRIPPED_BASE-$LIK_TYPE-$LIK_STRIPPED_END
+
+ NEWEST_KERNEL=$LIK_FINAL
+ fi
+
+ echo $NEWEST_KERNEL
+}
+
+# Get the newest kernel on Debian and Rhel based systems.
+get_newest_kernel() {
+ NEWEST_KERNEL=
+ # Try Debian first as rpm can be installed in Debian based distros
+ if [ -e /usr/bin/dpkg ]; then
+ # If DEB based
+ CURRENT_KERNEL=$1
+ CURRENT_VERSION=${CURRENT_KERNEL%%-*}
+ CURRENT_ABI=${CURRENT_KERNEL#*-}
+ CURRENT_FLAVOUR=${CURRENT_ABI#*-}
+ CURRENT_ABI=${CURRENT_ABI%%-*}
+ NEWEST_KERNEL=$(_get_newest_kernel_debian "$CURRENT_VERSION-$CURRENT_ABI")
+
+ elif [ `which rpm >/dev/null` ]; then
+ # If RPM based
+ NEWEST_KERNEL=$(_get_newest_kernel_rhel)
+ fi
+
+ # Make sure that kernel name that we extracted corresponds to an installed
+ # kernel
+ if [ -n "$NEWEST_KERNEL" ] && [ `_is_kernel_name_correct $NEWEST_KERNEL` = "no" ]; then
+ NEWEST_KERNEL=
+ fi
+
+ echo $NEWEST_KERNEL
+}
+
+NAME=$1
+VERSION=$2
+TARBALL_ROOT=$3
+ARCH=$4
+UPGRADE=$5
+
+if [ -z "$NAME" ] || [ -z "$VERSION" ]; then
+ echo "Need NAME, and VERSION defined"
+ echo "ARCH is optional"
+ exit 1
+fi
+
+KERNELS=$(ls /lib/modules/ 2>/dev/null || true)
+CURRENT_KERNEL=$(uname -r)
+
+#We never want to keep an older version side by side to prevent conflicts
+if [ -e "/var/lib/dkms/$NAME/$VERSION" ]; then
+ echo "Removing old $NAME-$VERSION DKMS files..."
+ dkms remove -m $NAME -v $VERSION --all
+fi
+
+#Load new files, by source package and by tarball
+if [ -f "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz" ]; then
+ if ! dkms ldtarball --archive "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz"; then
+ echo ""
+ echo ""
+ echo "Unable to load DKMS tarball $TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz."
+ echo "Common causes include: "
+ echo " - You must be using DKMS 2.1.0.0 or later to support binaries only"
+ echo " distribution specific archives."
+ echo " - Corrupt distribution specific archive"
+ echo ""
+ echo ""
+ exit 2
+ fi
+elif [ -d "/usr/src/$NAME-$VERSION" ]; then
+ echo "Loading new $NAME-$VERSION DKMS files..."
+ dkms add -m $NAME -v $VERSION > /dev/null
+fi
+
+# On 1st installation, let us look for a directory
+# in /lib/modules which matches `uname -r`. If none
+# is found it is possible that buildd is being used
+# and that uname -r is giving us the name of the
+# kernel used by the buildd machine.
+#
+# If this is the case we try to build the kernel
+# module for each kernel which has a directory in
+# /lib/modules. Furthermore we will have to tell
+# DKMS which architecture it should build the module
+# for (e.g. if the buildd machine is using a
+# 2.6.24-23-xen 64bit kernel).
+#
+# NOTE: if the headers are not installed then the
+# module won't be built, as usual
+if [ -z "$UPGRADE" ]; then
+ echo "First Installation: checking all kernels..."
+ for KERNEL in $KERNELS; do
+ if [ ${KERNEL} = ${CURRENT_KERNEL} ]; then
+ # Kernel found
+ KERNELS=$CURRENT_KERNEL
+ break
+ fi
+ done
+else
+ KERNELS=$CURRENT_KERNEL
+fi
+
+# Here we look for the most recent kernel so that we can
+# build the module for it (in addition to doing it for the
+# current kernel.
+NEWEST_KERNEL=$(get_newest_kernel "$KERNELS")
+
+# If the current kernel doesn't come from the host of a chroot
+if [ `_is_kernel_name_correct $CURRENT_KERNEL` = "yes" ]; then
+ # See if it's worth building the module for both the newest kernel
+ # and for the current kernel
+ if [ -n "$NEWEST_KERNEL" ] && [ ${CURRENT_KERNEL} != ${NEWEST_KERNEL} ]; then
+ echo "Building for $CURRENT_KERNEL and $NEWEST_KERNEL"
+ KERNELS="$CURRENT_KERNEL $NEWEST_KERNEL"
+ else
+ echo "Building only for $CURRENT_KERNEL"
+ fi
+# The current kernel is not useful as it's the host's
+else
+ echo "It is likely that $CURRENT_KERNEL belongs to a chroot's host"
+
+ # Let's use only the newest kernel
+ if [ -n "$NEWEST_KERNEL" ]; then
+ KERNELS="$NEWEST_KERNEL"
+ echo "Building only for $NEWEST_KERNEL"
+ fi
+fi
+
+if [ -n "$ARCH" ]; then
+ if which lsb_release >/dev/null && [ $(lsb_release -s -i) = "Ubuntu" ]; then
+ case $ARCH in
+ amd64)
+ ARCH="x86_64"
+ ;;
+ lpia|i?86)
+ ARCH="i686"
+ ;;
+ esac
+ fi
+ echo "Building for architecture $ARCH"
+ ARCH="-a $ARCH"
+fi
+
+for KERNEL in $KERNELS; do
+ dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH`
+ if [ `echo $KERNEL | grep -c "BOOT"` -gt 0 ]; then
+ echo ""
+ echo "Module build and install for $KERNEL was skipped as "
+ echo "it is a BOOT variant"
+ continue
+ fi
+
+
+ #if the module isn't yet built, try to build it
+ if [ `echo $dkms_status | grep -c ": built"` -eq 0 ]; then
+ if [ ! -L /var/lib/dkms/$NAME/$VERSION/source ]; then
+ echo "This package appears to be a binaries-only package"
+ echo " you will not be able to build against kernel $KERNEL"
+ echo " since the package source was not provided"
+ continue
+ fi
+ if _check_kernel_dir $KERNEL; then
+ echo "Building initial module for $KERNEL"
+ set +e
+ dkms build -m $NAME -v $VERSION -k $KERNEL $ARCH > /dev/null
+ case $? in
+ 9)
+ set -e
+ echo "Skipped."
+ continue
+ ;;
+ 0)
+ set -e
+ echo "Done."
+ ;;
+ *)
+ exit $?
+ ;;
+ esac
+ dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH`
+ else
+ echo "Module build for the currently running kernel was skipped since the"
+ echo "kernel source for this kernel does not seem to be installed."
+ fi
+ fi
+
+ #if the module is built (either pre-built or just now), install it
+ if [ `echo $dkms_status | grep -c ": built"` -eq 1 ] &&
+ [ `echo $dkms_status | grep -c ": installed"` -eq 0 ]; then
+ dkms install -m $NAME -v $VERSION -k $KERNEL $ARCH
+ fi
+done
+
--- /dev/null
+*.log
+*.substvars
+sapphire-remote-dkms/
+sapphire-remote/
--- /dev/null
+sapphire-remote DKMS module for Debian
+
+This package was automatically generated by the DKMS system,
+for distribution on Debian based operating systems.
+
--- /dev/null
+sapphire-remote-dkms (6.6) stable; urgency=low
+
+ * Automatically packaged by DKMS.
+
+ -- Dynamic Kernel Modules Support Team <pkg-dkms-maint@lists.alioth.debian.org> Sun, 21 Aug 2016 21:31:06 +0000
+
--- /dev/null
+Source: sapphire-remote-dkms
+Section: misc
+Priority: optional
+Maintainer: Dynamic Kernel Modules Support Team <pkg-dkms-maint@lists.alioth.debian.org>
+Build-Depends: debhelper (>= 7), dkms, gawk
+Standards-Version: 3.8.1
+
+Package: sapphire-remote-dkms
+Architecture: all
+Depends: dkms (>= 1.95), ${misc:Depends}
+Description: sapphire-remote driver in DKMS format.
+
+Package: sapphire-remote
+Architecture: all
+Depends: sapphire-remote-dkms, ${misc:Depends}
+Description: sapphire-remote tools
--- /dev/null
+
+This copyright has not been completed by the author of this package.
--- /dev/null
+sapphire-remote-dkms_6.6_all.deb misc optional
+sapphire-remote_6.6_all.deb misc optional
--- /dev/null
+#!/bin/sh
+# Copyright (C) 2002-2005 Flavio Stanchina
+# Copyright (C) 2005-2006 Aric Cyr
+# Copyright (C) 2007 Mario Limonciello
+# Copyright (C) 2009 Alberto Milone
+
+set -e
+
+NAME=sapphire-remote
+PACKAGE_NAME=$NAME-dkms
+DEB_NAME=$(echo $PACKAGE_NAME | sed 's,_,-,')
+CVERSION=`dpkg-query -W -f='${Version}' $DEB_NAME | awk -F "-" '{print $1}' | cut -d\: -f2`
+ARCH=`dpkg --print-architecture`
+
+dkms_configure () {
+ for POSTINST in /usr/lib/dkms/common.postinst "/usr/share/$PACKAGE_NAME/postinst"; do
+ if [ -f "$POSTINST" ]; then
+ "$POSTINST" "$NAME" "$CVERSION" "/usr/share/$PACKAGE_NAME" "$ARCH" "$2"
+ return $?
+ fi
+ echo "WARNING: $POSTINST does not exist." >&2
+ done
+ echo "ERROR: DKMS version is too old and $PACKAGE_NAME was not" >&2
+ echo "built with legacy DKMS support." >&2
+ echo "You must either rebuild $PACKAGE_NAME with legacy postinst" >&2
+ echo "support or upgrade DKMS to a more current version." >&2
+ return 1
+}
+
+case "$1" in
+ configure)
+ dkms_configure
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
--- /dev/null
+#!/bin/sh
+
+NAME=sapphire-remote
+VERSION=6.6
+
+set -e
+
+case "$1" in
+ remove|upgrade|deconfigure)
+ if [ "`dkms status -m $NAME`" ]; then
+ dkms remove -m $NAME -v $VERSION --all
+ fi
+ ;;
+
+ failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
+
+
--- /dev/null
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+DEB_NAME=sapphire-remote
+NAME=sapphire-remote
+VERSION=6.6
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+ dh_testdir
+ $(MAKE)
+ touch $@
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+ -$(MAKE) clean
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_prep
+ dh_installdirs
+ $(MAKE) DESTDIR=$(CURDIR)/debian/$(DEB_NAME)-dkms NAME=$(NAME) VERSION=$(VERSION) install
+
+binary-arch: build install
+
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
--- /dev/null
+[Unit]
+Description=Sapphire HID driver
+
+[Service]
+ExecStartPre=/usr/lib/sapphire-remote/sapphire_keymap.sh
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+#
+# Makefile and (un-)Installer for Sapphire remote control driver,
+# by Mark Lord 2012-2015.
+#
+MODNAME=sapphire
+MODSOURCE=$(MODNAME).c
+CONFLICTS=hid_topseed
+BLACKLIST=/etc/modprobe.d/blacklist-$(CONFLICTS).conf
+KEYMAP_SCRIPT=sapphire_keymap.sh
+EXTRACT_KEYDEFS=extract_keydefs.sh
+INPUT_H=$(shell [ -e /usr/include/linux/input-event-codes.h ] && echo /usr/include/linux/input-event-codes.h || echo /usr/include/linux/input.h)
+
+## MODPARMS is not used any more, but we have to nuke old copies to prevent issues:
+MODPARMS=/etc/modprobe.d/$(MODNAME).conf
+
+CWD = $(shell pwd -P)
+KVER ?= $(shell uname -r)
+KDIR = /lib/modules/$(KVER)
+obj-m += $(MODNAME).o
+
+#EXTRA_CFLAGS += -Werror
+
+default: $(INPUT_H) kmod $(KEYMAP_SCRIPT)
+
+$(INPUT_H):
+ @echo "ERROR: $@: not found."
+ @echo
+ @echo "Please install the system development libraries first."
+ @echo "Eg. on Ubuntu/Mint systems: sudo apt-get install build-essential"
+ @echo
+ @exit 1
+
+kmod:
+ $(MAKE) -C $(KDIR)/build M=$(CWD) modules
+
+$(MODNAME).ko: $(MODSOURCE) $(MODNAME).h
+ $(MAKE) kmod
+
+$(KEYMAP_SCRIPT): $(INPUT_H) $(EXTRACT_KEYDEFS) $(KEYMAP_SCRIPT).part1 $(KEYMAP_SCRIPT).part3 $(MODNAME).h $(INPUT_H)
+ @bash -c "type -all gawk" >/dev/null 2>&1 || (\
+ echo ;\
+ echo "gawk not found, needed by installer; Aborted." ;\
+ echo "For Ubuntu/Debian based systems, try this: sudo apt-get install gawk" ;\
+ echo ;\
+ exit 2 ;\
+ )
+ chmod 0755 $(EXTRACT_KEYDEFS)
+ ./$(EXTRACT_KEYDEFS) $(INPUT_H) $(MODNAME).h | cat $(KEYMAP_SCRIPT).part1 - $(KEYMAP_SCRIPT).part3 > $(KEYMAP_SCRIPT)
+ chmod 0755 $(KEYMAP_SCRIPT)
+
+$(BLACKLIST):
+ @echo "Creating $(BLACKLIST)"
+ @echo "## $(CONFLICTS) Conflicts with $(MODNAME) driver" > $(BLACKLIST)
+ @echo "blacklist $(CONFLICTS)" >> $(BLACKLIST)
+
+modinstall: $(MODNAME).ko $(KEYMAP_SCRIPT)
+ $(MAKE) -C $(KDIR)/build M=$(CWD) modules_install || exit 0
+ @[ -e $(KDIR)/extra/sapphire.ko ] || exit 1
+ depmod $(KVER)
+ @if [ "$(KVER)" = "$$(uname -r)" ]; then \
+ rmmod $(MODNAME) 2>/dev/null ;\
+ rmmod $(CONFLICTS) 2>/dev/null ;\
+ ./sapphire_startup.sh ;\
+ else \
+ exit 0 ;\
+ fi
+
+clean:
+ $(MAKE) -C $(KDIR)/build M=$(CWD) clean
+ -rm -f $(KEYMAP_SCRIPT)
+
+clean_modparms:
+ @if [ -e $(MODPARAMS) ]; then rm -f $(MODPARMS) ; fi ; exit 0
+
+uninstall: clean clean_modparms
+ -rmmod $(MODNAME)
+ -rm -f $(BLACKLIST)
+ -rm -f /usr/local/bin/$(KEYMAP_SCRIPT)
+ -rm -f $(KDIR)/extra/$(MODNAME).ko
+ depmod
+
+install: $(MODNAME).ko clean_modparms modinstall $(BLACKLIST) $(KEYMAP_SCRIPT)
+ install -m 0755 $(KEYMAP_SCRIPT) /usr/local/bin/
+ install -m 0755 sapphire_startup.sh /usr/local/bin/
+
+all: install
--- /dev/null
+Sapphire hid driver, by Mark Lord <mlord@pobox.com>, September/2015.
+http://rtr.ca/sapphire_remote/
+
+This package contains a standalone Linux "hid" driver
+for the Sapphire Theatrix I/R remote control.
+
+To build/install the driver, simply do this:
+
+ sudo make install
+
+That command will need to be repeated after any
+subsequent kernel upgrades.
+
+This driver competes with hid-topseed for control of the device.
+As of 2012, several distros are now building the hid-topseed driver
+into the main kernel image, preventing it from being blacklisted
+and unloaded at run time. To work around this, there is now a script
+included in this package for unbinding the device(s) from hid-topseed.
+This script should be run from /etc/rc.local, by manually editing
+that file and adding this line:
+
+ /usr/local/bin/sapphire_startup.sh
+
+This driver comes pre-configured for use with Mythtv,
+with the default mappings for most buttons set to
+what Mythtv usually expects.
+
+You can customize the mappings, by copying/editing
+the keymap.default file, and then feeding your modified
+copy as a parameter to /usr/local/bin/sapphire_keymap.sh
+after each system startup.
+
+Or you can directly edit the default keymap that is
+built into the driver source code, but that runs a risk
+of conflicts when the driver gets updated in the future.
+
+Best method: copy/edit the keymap.default file,
+and save it as: /etc/sapphire.keymap
+
+Then ensure you have this line (from above) in /etc/rc.local:
+
+ /usr/local/bin/sapphire_startup.sh ## already discussed above
+
+Done.
+
+One can also use /proc/sapphire to inject KEY_ presses into the system
+from a script, just as if they had been typed on a keyboard or remote.
+
+ Eg. echo "SENDKEY 5" > /proc/sapphire ## sends KEY_4 (defined as 5 in input.h).
+
+This feature could be used to control mythfrontend from external scripts,
+or to automate keyboard input for any other application as well.
+
+A special keyword "REPEAT_RATE" is recognized by /proc/sapphire,
+and enables modifying the ramping repeat attack rate and maximum repeat rate.
+Two values must be provided: the number of times/second that the rate is
+adjusted, and the maxiumum number of repeats per second.
+The default values can be restored like this:
+
+ Eg. echo "REPEAT_RATE 40 10" > /proc/sapphire
+
+The same syntax can also be used in a keymap file.
+
+* * * * * * * * * *
+
+Below is a handy list of most of the available KEY_* names
+for use with definitions in the keymap file.
+These were extracted from /usr/include/linux/input.h:
+
+KEY_0
+KEY_102ND
+KEY_1
+KEY_2
+KEY_3
+KEY_4
+KEY_5
+KEY_6
+KEY_7
+KEY_8
+KEY_9
+KEY_A
+KEY_AB
+KEY_ADDRESSBOOK
+KEY_AGAIN
+KEY_ALTERASE
+KEY_ANGLE
+KEY_APOSTROPHE
+KEY_ARCHIVE
+KEY_AUDIO
+KEY_AUX
+KEY_B
+KEY_BACK
+KEY_BACKSLASH
+KEY_BACKSPACE
+KEY_BASSBOOST
+KEY_BATTERY
+KEY_BLUE
+KEY_BLUETOOTH
+KEY_BOOKMARKS
+KEY_BREAK
+KEY_BRIGHTNESS_CYCLE
+KEY_BRIGHTNESSDOWN
+KEY_BRIGHTNESSUP
+KEY_BRIGHTNESS_ZERO
+KEY_BRL_DOT10
+KEY_BRL_DOT1
+KEY_BRL_DOT2
+KEY_BRL_DOT3
+KEY_BRL_DOT4
+KEY_BRL_DOT5
+KEY_BRL_DOT6
+KEY_BRL_DOT7
+KEY_BRL_DOT8
+KEY_BRL_DOT9
+KEY_C
+KEY_CALC
+KEY_CALENDAR
+KEY_CAMERA
+KEY_CAMERA_FOCUS
+KEY_CANCEL
+KEY_CAPSLOCK
+KEY_CD
+KEY_CHANNEL
+KEY_CHANNELDOWN
+KEY_CHANNELUP
+KEY_CHAT
+KEY_CLEAR
+KEY_CLOSE
+KEY_CLOSECD
+KEY_COFFEE
+KEY_COMMA
+KEY_COMPOSE
+KEY_COMPUTER
+KEY_CONFIG
+KEY_CONNECT
+KEY_CONTEXT_MENU
+KEY_COPY
+KEY_CUT
+KEY_CYCLEWINDOWS
+KEY_D
+KEY_DASHBOARD
+KEY_DATABASE
+KEY_DEL_EOL
+KEY_DEL_EOS
+KEY_DELETE
+KEY_DELETEFILE
+KEY_DEL_LINE
+KEY_DIGITS
+KEY_DIRECTION
+KEY_DIRECTORY
+KEY_DISPLAY_OFF
+KEY_DISPLAYTOGGLE
+KEY_DOCUMENTS
+KEY_DOLLAR
+KEY_DOT
+KEY_DOWN
+KEY_DVD
+KEY_E
+KEY_EDIT
+KEY_EDITOR
+KEY_EJECTCD
+KEY_EJECTCLOSECD
+KEY_EMAIL
+KEY_END
+KEY_ENTER
+KEY_EPG
+KEY_EQUAL
+KEY_ESC
+KEY_EURO
+KEY_EXIT
+KEY_F10
+KEY_F11
+KEY_F12
+KEY_F13
+KEY_F14
+KEY_F15
+KEY_F1
+KEY_F16
+KEY_F17
+KEY_F18
+KEY_F19
+KEY_F20
+KEY_F21
+KEY_F22
+KEY_F23
+KEY_F24
+KEY_F2
+KEY_F
+KEY_F3
+KEY_F4
+KEY_F5
+KEY_F6
+KEY_F7
+KEY_F8
+KEY_F9
+KEY_FASTFORWARD
+KEY_FAVORITES
+KEY_FILE
+KEY_FINANCE
+KEY_FIND
+KEY_FIRST
+KEY_FN
+KEY_FN_1
+KEY_FN_2
+KEY_FN_B
+KEY_FN_D
+KEY_FN_E
+KEY_FN_ESC
+KEY_FN_F
+KEY_FN_F10
+KEY_FN_F1
+KEY_FN_F11
+KEY_FN_F12
+KEY_FN_F2
+KEY_FN_F3
+KEY_FN_F4
+KEY_FN_F5
+KEY_FN_F6
+KEY_FN_F7
+KEY_FN_F8
+KEY_FN_F9
+KEY_FN_S
+KEY_FORWARD
+KEY_FORWARDMAIL
+KEY_FRAMEBACK
+KEY_FRAMEFORWARD
+KEY_FRONT
+KEY_G
+KEY_GAMES
+KEY_GOTO
+KEY_GRAPHICSEDITOR
+KEY_GRAVE
+KEY_GREEN
+KEY_H
+KEY_HANGEUL
+KEY_HANJA
+KEY_HELP
+KEY_HENKAN
+KEY_HIRAGANA
+KEY_HOME
+KEY_HOMEPAGE
+KEY_HP
+KEY_I
+KEY_INFO
+KEY_INSERT
+KEY_INS_LINE
+KEY_ISO
+KEY_J
+KEY_K
+KEY_KATAKANA
+KEY_KATAKANAHIRAGANA
+KEY_KBDILLUMDOWN
+KEY_KBDILLUMTOGGLE
+KEY_KBDILLUMUP
+KEY_KEYBOARD
+KEY_KP0
+KEY_KP1
+KEY_KP2
+KEY_KP3
+KEY_KP4
+KEY_KP5
+KEY_KP6
+KEY_KP7
+KEY_KP8
+KEY_KP9
+KEY_KPASTERISK
+KEY_KPCOMMA
+KEY_KPDOT
+KEY_KPENTER
+KEY_KPEQUAL
+KEY_KPJPCOMMA
+KEY_KPLEFTPAREN
+KEY_KPMINUS
+KEY_KPPLUS
+KEY_KPPLUSMINUS
+KEY_KPRIGHTPAREN
+KEY_KPSLASH
+KEY_L
+KEY_LANGUAGE
+KEY_LAST
+KEY_LEFT
+KEY_LEFTALT
+KEY_LEFTBRACE
+KEY_LEFTCTRL
+KEY_LEFTMETA
+KEY_LEFTSHIFT
+KEY_LINEFEED
+KEY_LIST
+KEY_LOGOFF
+KEY_M
+KEY_MACRO
+KEY_MAIL
+KEY_MAX
+KEY_MEDIA
+KEY_MEDIA_REPEAT
+KEY_MEMO
+KEY_MENU
+KEY_MESSENGER
+KEY_MHP
+KEY_MINUS
+KEY_MODE
+KEY_MOVE
+KEY_MP3
+KEY_MSDOS
+KEY_MUHENKAN
+KEY_MUTE
+KEY_N
+KEY_NEW
+KEY_NEWS
+KEY_NEXT
+KEY_NEXTSONG
+KEY_NUMERIC_0
+KEY_NUMERIC_1
+KEY_NUMERIC_2
+KEY_NUMERIC_3
+KEY_NUMERIC_4
+KEY_NUMERIC_5
+KEY_NUMERIC_6
+KEY_NUMERIC_7
+KEY_NUMERIC_8
+KEY_NUMERIC_9
+KEY_NUMERIC_POUND
+KEY_NUMERIC_STAR
+KEY_NUMLOCK
+KEY_O
+KEY_OK
+KEY_OPEN
+KEY_OPTION
+KEY_P
+KEY_PAGEDOWN
+KEY_PAGEUP
+KEY_PASTE
+KEY_PAUSE
+KEY_PAUSECD
+KEY_PC
+KEY_PHONE
+KEY_PLAY
+KEY_PLAYCD
+KEY_PLAYER
+KEY_PLAYPAUSE
+KEY_POWER
+KEY_POWER2
+KEY_PRESENTATION
+KEY_PREVIOUS
+KEY_PREVIOUSSONG
+KEY_PRINT
+KEY_PROG1
+KEY_PROG2
+KEY_PROG3
+KEY_PROG4
+KEY_PROGRAM
+KEY_PROPS
+KEY_PVR
+KEY_Q
+KEY_QUESTION
+KEY_R
+KEY_RADIO
+KEY_RECORD
+KEY_RED
+KEY_REDO
+KEY_REFRESH
+KEY_REPLY
+KEY_RESERVED
+KEY_RESTART
+KEY_REWIND
+KEY_RFKILL
+KEY_RIGHT
+KEY_RIGHTALT
+KEY_RIGHTBRACE
+KEY_RIGHTCTRL
+KEY_RIGHTMETA
+KEY_RIGHTSHIFT
+KEY_RO
+KEY_S
+KEY_SAT
+KEY_SAT2
+KEY_SAVE
+KEY_SCALE
+KEY_SCREEN
+KEY_SCROLLDOWN
+KEY_SCROLLLOCK
+KEY_SCROLLUP
+KEY_SEARCH
+KEY_SELECT
+KEY_SEMICOLON
+KEY_SEND
+KEY_SENDFILE
+KEY_SETUP
+KEY_SHOP
+KEY_SHUFFLE
+KEY_SLASH
+KEY_SLEEP
+KEY_SLOW
+KEY_SOUND
+KEY_SPACE
+KEY_SPELLCHECK
+KEY_SPORT
+KEY_SPREADSHEET
+KEY_STOP
+KEY_STOPCD
+KEY_SUBTITLE
+KEY_SUSPEND
+KEY_SWITCHVIDEOMODE
+KEY_SYSRQ
+KEY_T
+KEY_TAB
+KEY_TAPE
+KEY_TEEN
+KEY_TEXT
+KEY_TIME
+KEY_TITLE
+KEY_TUNER
+KEY_TV
+KEY_TV2
+KEY_TWEN
+KEY_U
+KEY_UNDO
+KEY_UNKNOWN
+KEY_UP
+KEY_UWB
+KEY_V
+KEY_VCR
+KEY_VCR2
+KEY_VENDOR
+KEY_VIDEO
+KEY_VIDEO_NEXT
+KEY_VIDEOPHONE
+KEY_VIDEO_PREV
+KEY_VOICEMAIL
+KEY_VOLUMEDOWN
+KEY_VOLUMEUP
+KEY_W
+KEY_WAKEUP
+KEY_WIMAX
+KEY_WLAN
+KEY_WORDPROCESSOR
+KEY_WPS_BUTTON
+KEY_WWW
+KEY_X
+KEY_XFER
+KEY_Y
+KEY_YELLOW
+KEY_YEN
+KEY_Z
+KEY_ZENKAKUHANKAKU
+KEY_ZOOM
+KEY_ZOOMIN
+KEY_ZOOMOUT
+KEY_ZOOMRESET
+
--- /dev/null
+PACKAGE_VERSION="6.6"
+
+# Items below here should not have to change with each driver version
+PACKAGE_NAME="sapphire-remote"
+MAKE[0]="make -C ${kernel_source_dir} SUBDIRS=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build modules"
+CLEAN="make -C ${kernel_source_dir} SUBDIRS=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build clean"
+
+BUILT_MODULE_NAME[0]="sapphire"
+DEST_MODULE_LOCATION[0]="/extra/"
+
+AUTOINSTALL=yes
+REMAKE_INITRD=no
--- /dev/null
+#!/bin/bash
+#
+# Extract button and key definitions from sapphire.h and input.h:
+#
+
+function parse_sapphire_h(){
+ echo
+ echo "## buttons, macros, and other definitions extracted from $1:"
+ echo "##"
+ gawk '
+ ($2 == "BEGIN_PARSING"){c=1;next}
+ (c == 0){next}
+ /^ [XS]APPHIRE_([A-Z0-9_]+)\>([ ]+)= 0x/{printf "B[%s]=%s\n",$1,$3;next}
+ /^ KEY_MACRO_[0-7]\>([ ]+)= 0x/{printf "B[%s]=0x%08x\n",$1,strtonum($3)}
+ /^ KEY_([A-Z0-9_]+)\>([ ]+)= 0x/{printf "K[%s]=0x%08x\n",$1,strtonum($3);next}
+ /^ ([A-Z0-9_]+)\>([ ]+)= 0x/{printf "M[%s]=0x%08x\n",$1,strtonum($3);next}
+ /^[}];/{if (c == 1) nextfile}
+ ' < "$1"
+}
+
+function parse_input_h(){
+ echo
+ echo "## keycodes extracted from $1:"
+ echo "##"
+ gawk ' /^#define KEY_([A-Z0-9_]+)\>([ ]+)((0x[0-9a-fA-F]+)|([0-9]+))\>/{
+ printf "K[%s]=0x%08x\n",$2,strtonum($3)}
+ ' < "$1" | grep -v KEY_RESERVED
+}
+
+parse_input_h "$1" && parse_sapphire_h "$2"
+
--- /dev/null
+#
+# Default keymap from sapphire.c v3.0.
+#
+# Edit this as you please, keeping in mind that spaces are NOT permitted
+# within each column.
+#
+# Eg. this is INCORRECT: KEY_F15 | ALT
+# whereas this is good: KEY_F15|ALT
+#
+# Use the provided sapphire_keymap.sh script to install these
+# mappings at runtime in place of the driver's built-in mappings.
+# Keep your own copy of this file separate from the driver source,
+# so that subsequent changes in the driver mappings won't clobber
+# your own definitions here.
+#
+# As in the example above, "KeyCode" values can be combined with
+# any mix of these "modifier" keys: SHIFT, ALT, CTRL, and/or META.
+#
+# Eg. SAPPHIRE_PLAY KEY_P|CTRL NO_REPEAT ## Mythtv Play/Pause function
+# Or: SAPPHIRE_PLAY KEY_P|CTRL|ALT NO_REPEAT ## something else
+#
+# The "SpecialOptions" field is used to indicate the desired auto-repeat rate
+# for each button individually. The choices are:
+#
+# NO_REPEAT - the button will not auto-repeat when held down.
+# SLOW_REPEAT - the button will repeat slowly.
+# RAMP_REPEAT - the button will repeat slowly at first, but speed up if held down.
+# LONGKEY - no auto-repeat: special behaviour described below.
+#
+# The special value "LONGKEY" can be used in "SpecialOptions"
+# insted of an auto-repeat setting. This causes a button on the remote
+# to have two separate functions, depending upon how quickly it is released.
+#
+# A short-press/release gets the regular "KeyCode" value, but if the button
+# is held down for one second or longer, then an alternate value can be send instead.
+# Just combine the desired alternate keycode with the LONGKEY tag.
+#
+# Eg. SAPPHIRE_MUTE KEY_F9 LONGKEY|KEY_F15 ## Mute (tap) or Audiosync (hold)
+#
+# ButtonName KeyCode SpecialOptions ## Comments
+#
+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 SLOW_REPEAT ## channel up (CUSTOM)
+SAPPHIRE_CHDOWN KEY_PAGEDOWN SLOW_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 NO_REPEAT ## Menu
+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 (see below)
+SAPPHIRE_GREENMUSIC KEY_A NO_REPEAT ## Adjust time stretch
+
+#
+# Now for the macro definitions: each macro sends a list of up to eight keycodes:
+#
+KEY_MACRO_0 KEY_F15 KEY_DOWN KEY_LEFT ## Adjust AudioSync by +90msecs
+KEY_MACRO_1
+KEY_MACRO_2
+KEY_MACRO_2
+KEY_MACRO_3
+KEY_MACRO_4
+KEY_MACRO_5
+KEY_MACRO_6
+KEY_MACRO_7
--- /dev/null
+/*
+ * sapphire.c
+ *
+ * Copyright Mark Lord <mlord@pobox.com>, 2012-2015.
+ * 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 "6.6"
+
+/*
+ * "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 (2 * SAPPHIRE_NUM_BUTTONS) /* Extra room for a full XAPPHIRE_* set */
+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 , SLOW_REPEAT }, /* channel up (CUSTOM) */
+ {SAPPHIRE_CHDOWN , KEY_PAGEDOWN , SLOW_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,}
+};
+
+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 {
+ RAMPING = 0x80000000, /* flag to indicate current dev->down_key is using RAMP_REPEAT */
+ RAMPING_RATE = HZ / 40, /* amount to speed up by after each repeat */
+ RAMPING_MAX = HZ / 10, /* max repeats/sec when RAMPING */
+ 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 ramping_rate = RAMPING_RATE;
+static unsigned int ramping_max = RAMPING_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(unsigned long data)
+{
+ 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 > ramping_max) {
+ next_repeat -= ramping_rate;
+ if (next_repeat < ramping_max)
+ next_repeat = ramping_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)
+{
+ unsigned int repeat_delay = NO_REPEAT, 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:
+ repeat_delay = HZ/2;
+ next_repeat = HZ/SLOW_REPEAT;
+ break;
+ case FAST_REPEAT:
+ repeat_delay = HZ/3;
+ next_repeat = HZ/FAST_REPEAT;
+ break;
+ case RAMP_REPEAT:
+ repeat_delay = (HZ/2) - (HZ/6);
+ next_repeat = (HZ/3) | RAMPING;
+ break;
+ default: /* LONGKEY or NO_REPEAT */
+ 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, repeat_delay);
+ 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.. */
+}
+
+/*
+ * 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);
+ 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;
+}
+
+/*
+ * 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[] = "SENDKEY ";
+ static const char REPEAT_RATE[] = "REPEAT_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)-1)) {
+ line += sizeof(SENDKEY)-1;
+ sendkey = 1;
+ } else if (0 == strncmp(line, REPEAT_RATE, sizeof(REPEAT_RATE)-1)) {
+ unsigned int rate, max;
+ line += sizeof(REPEAT_RATE)-1;
+ if (2 != sscanf(line, "%u %u", &rate, &max)) {
+ ret = -EINVAL;
+ break;
+ }
+ ramping_rate = HZ / rate;
+ ramping_max = HZ / max;
+ goto next_line;
+ }
+ 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;
+}
+
+static const struct file_operations sapphire_newproc_fops = {
+ .read = sapphire_newproc_read,
+ .write = sapphire_newproc_write,
+ .llseek = default_llseek,
+};
+
+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);
+ 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)
+{
+ /*
+ * 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);
+ init_timer(&dev->key_timer);
+ dev->key_timer.function = sapphire_key_timeout;
+ dev->key_timer.data = (unsigned long)dev;
+
+ 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");
--- /dev/null
+/*
+ * sapphire.h
+ *
+ * Copyright Mark Lord <mlord@pobox.com>, 2012-2015.
+ * http://rtr.ca/sapphire_remote/
+ *
+ * Button definitions shared with sapphire_keymap.sh
+ * and the external glue 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; specifically version 2 of the License (GPLv2).
+ */
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/proc_fs.h>
+#include <linux/uaccess.h>
+
+/*
+ * These enum's are shared with external keymaps and the sapphire_keymap.sh script:
+ */
+enum { /*
+ * BEGIN_PARSING (marker used by script that extracts these for sapphire_keymap.sh)
+ *
+ * Raw button values from the Sapphire remote:
+ *
+ */
+ SAPPHIRE_UP = 0x00520000 , /* UP */
+ SAPPHIRE_DOWN = 0x00510000 , /* DOWN */
+ SAPPHIRE_RIGHT = 0x004f0000 , /* RIGHT */
+ SAPPHIRE_LEFT = 0x00500000 , /* LEFT */
+ SAPPHIRE_ENTEROK = 0x00280000 , /* ENTER/OK */
+ SAPPHIRE_BACK = 0x00040003 , /* BACK */
+ SAPPHIRE_PLAY = 0x40000003 , /* PLAY */
+ SAPPHIRE_PAUSE = 0x80000003 , /* PAUSE */
+ SAPPHIRE_VOLUP = 0x00002003 , /* VOL+ */
+ SAPPHIRE_VOLDOWN = 0x00004003 , /* VOL- */
+ SAPPHIRE_CHUP = 0x00010003 , /* CH/PG+ */
+ SAPPHIRE_CHDOWN = 0x00020003 , /* CH/PG- */
+ SAPPHIRE_MUTE = 0x00001003 , /* MUTE */
+ SAPPHIRE_RECORD = 0x00008003 , /* RECORD */
+ SAPPHIRE_FWD = 0x08000003 , /* FWD */
+ SAPPHIRE_REW = 0x04000003 , /* REW */
+ SAPPHIRE_ANGLE = 0x00010004 , /* ANGLE */
+ SAPPHIRE_SAP = 0x00100004 , /* SAP */
+ SAPPHIRE_DVDMENU = 0x00040004 , /* DVDMENU */
+ SAPPHIRE_INFOEPG = 0x02000003 , /* INFO/EPG */
+ SAPPHIRE_TAB = 0x00000103 , /* TAB */
+ SAPPHIRE_BACKTAB = 0x00000203 , /* BACKTAB */
+ SAPPHIRE_RADIO = 0x00000403 , /* RADIO */
+ SAPPHIRE_LASTCH = 0x00400004 , /* LASTCH */
+ SAPPHIRE_LANGUAGE = 0x00020004 , /* LANGUAGE */
+ SAPPHIRE_TELETEXTCC = 0x00200004 , /* TELETEXT/CC */
+ SAPPHIRE_SUBTITLE = 0x00080004 , /* SUBTITLE */
+ SAPPHIRE_HOMEHOUSE = 0x00000104 , /* HOME (house) */
+ SAPPHIRE_BLUEVIDEOS = 0x00002004 , /* BLUE/VIDEOS */
+ SAPPHIRE_LIVETV = 0x00000204 , /* LIVETV */
+ SAPPHIRE_REDDVDVCD = 0x00008004 , /* RED/DVD/VCD */
+ SAPPHIRE_YELLOWPICTURES = 0x00001004 , /* YELLOW/PICTURES*/
+ SAPPHIRE_1 = 0x001e0000 , /* 1 */
+ SAPPHIRE_2 = 0x001f0000 , /* 2 */
+ SAPPHIRE_3 = 0x00200000 , /* 3 */
+ SAPPHIRE_4 = 0x00210000 , /* 4 */
+ SAPPHIRE_5 = 0x00220000 , /* 5 */
+ SAPPHIRE_6 = 0x00230000 , /* 6 */
+ SAPPHIRE_7 = 0x00240000 , /* 7 */
+ SAPPHIRE_8 = 0x00250000 , /* 8 */
+ SAPPHIRE_9 = 0x00260000 , /* 9 */
+ SAPPHIRE_0 = 0x00270000 , /* 0 */
+ SAPPHIRE_STOP = 0x00100003 , /* STOP */
+ SAPPHIRE_POWER = 0x00000202 , /* POWER */
+ SAPPHIRE_CLEAR = 0x004c0000 , /* CLEAR */
+ SAPPHIRE_GREENMUSIC = 0x00000804 , /* GREEN/MUSIC */
+
+ /*
+ * A second set of "virtual" sapphire buttons,
+ * not for normal mapping use. Instead, these are
+ * intended for use with the external "glue" module.
+ * If you don't know what that is, then just ignore these!
+ */
+ XAPPHIRE_UP = 0x005200f0 , /* UP */
+ XAPPHIRE_DOWN = 0x005100f0 , /* DOWN */
+ XAPPHIRE_RIGHT = 0x004f00f0 , /* RIGHT */
+ XAPPHIRE_LEFT = 0x005000f0 , /* LEFT */
+ XAPPHIRE_ENTEROK = 0x002800f0 , /* ENTER/OK */
+ XAPPHIRE_BACK = 0x000400f3 , /* BACK */
+ XAPPHIRE_PLAY = 0x400000f3 , /* PLAY */
+ XAPPHIRE_PAUSE = 0x800000f3 , /* PAUSE */
+ XAPPHIRE_VOLUP = 0x000020f3 , /* VOL+ */
+ XAPPHIRE_VOLDOWN = 0x000040f3 , /* VOL- */
+ XAPPHIRE_CHUP = 0x000100f3 , /* CH/PG+ */
+ XAPPHIRE_CHDOWN = 0x000200f3 , /* CH/PG- */
+ XAPPHIRE_MUTE = 0x000010f3 , /* MUTE */
+ XAPPHIRE_RECORD = 0x000080f3 , /* RECORD */
+ XAPPHIRE_FWD = 0x080000f3 , /* FWD */
+ XAPPHIRE_REW = 0x040000f3 , /* REW */
+ XAPPHIRE_ANGLE = 0x000100f4 , /* ANGLE */
+ XAPPHIRE_SAP = 0x001000f4 , /* SAP */
+ XAPPHIRE_DVDMENU = 0x000400f4 , /* DVDMENU */
+ XAPPHIRE_INFOEPG = 0x020000f3 , /* INFO/EPG */
+ XAPPHIRE_TAB = 0x000001f3 , /* TAB */
+ XAPPHIRE_BACKTAB = 0x000002f3 , /* BACKTAB */
+ XAPPHIRE_RADIO = 0x000004f3 , /* RADIO */
+ XAPPHIRE_LASTCH = 0x004000f4 , /* LASTCH */
+ XAPPHIRE_LANGUAGE = 0x000200f4 , /* LANGUAGE */
+ XAPPHIRE_TELETEXTCC = 0x002000f4 , /* TELETEXT/CC */
+ XAPPHIRE_SUBTITLE = 0x000800f4 , /* SUBTITLE */
+ XAPPHIRE_HOMEHOUSE = 0x000001f4 , /* HOME (house) */
+ XAPPHIRE_BLUEVIDEOS = 0x000020f4 , /* BLUE/VIDEOS */
+ XAPPHIRE_LIVETV = 0x000002f4 , /* LIVETV */
+ XAPPHIRE_REDDVDVCD = 0x000080f4 , /* RED/DVD/VCD */
+ XAPPHIRE_YELLOWPICTURES = 0x000010f4 , /* YELLOW/PICTURES*/
+ XAPPHIRE_1 = 0x001e00f0 , /* 1 */
+ XAPPHIRE_2 = 0x001f00f0 , /* 2 */
+ XAPPHIRE_3 = 0x002000f0 , /* 3 */
+ XAPPHIRE_4 = 0x002100f0 , /* 4 */
+ XAPPHIRE_5 = 0x002200f0 , /* 5 */
+ XAPPHIRE_6 = 0x002300f0 , /* 6 */
+ XAPPHIRE_7 = 0x002400f0 , /* 7 */
+ XAPPHIRE_8 = 0x002500f0 , /* 8 */
+ XAPPHIRE_9 = 0x002600f0 , /* 9 */
+ XAPPHIRE_0 = 0x002700f0 , /* 0 */
+ XAPPHIRE_STOP = 0x001000f3 , /* STOP */
+ XAPPHIRE_POWER = 0x000002f2 , /* POWER */
+ XAPPHIRE_CLEAR = 0x004c00f0 , /* CLEAR */
+ XAPPHIRE_GREENMUSIC = 0x000008f4 , /* GREEN/MUSIC */
+
+ /*
+ * Modifier buttons: "OR" these with "regular" KEY_ values as desired:
+ */
+ CTRL = 0x80000000 ,
+ SHIFT = 0x40000000 ,
+ ALT = 0x20000000 ,
+ META = 0x10000000 ,
+
+ /*
+ * Special "macro" keys:
+ */
+ KEY_DELAY = 0x00fffff7 ,
+ KEY_MACRO_0 = 0x00fffff8 ,
+ KEY_MACRO_1 = 0x00fffff9 ,
+ KEY_MACRO_2 = 0x00fffffa ,
+ KEY_MACRO_3 = 0x00fffffb ,
+ KEY_MACRO_4 = 0x00fffffc ,
+ KEY_MACRO_5 = 0x00fffffd ,
+ KEY_MACRO_6 = 0x00fffffe ,
+ KEY_MACRO_7 = 0x00ffffff ,
+
+ /*
+ * Per-button automatic repeat rates:
+ */
+ NO_REPEAT = 0x00000000 , /* no autorepeat */
+ SLOW_REPEAT = 0x00000004 , /* repeats 4 times per second */
+ FAST_REPEAT = 0x00000008 , /* repeats 8 times per second */
+ RAMP_REPEAT = 0x00000003 , /* repeats increase in rate as button is held, up to 10/sec */
+ RAWKEY = 0xffffffff , /* do not send "release" event until button is really released */
+ LONGKEY = 0x08000000 , /* no autorepeat; short/long presses send different codes */
+};
+
+/*
+ * This is a hook for external modules to tie into,
+ * allowing other remote-drivers to use the sapphire
+ * driver's translations and advanced features.
+ */
+extern void sapphire_relay (u32 data);
--- /dev/null
+#!/bin/bash
+#
+# sapphire_keymap.sh by Mark Lord <mlord@pobox.com>, April/2013.
+# http://rtr.ca/sapphire_remote/
+#
+# Script to translate a symbolic keymap file for the Sapphire driver
+# into hex format and feed the results to /proc/sapphire.
+#
+# This can be invoked on the fly, as often as desired.
+# Eg. to change keymaps to match a specific program, such as mythfrontend or xbmc.
+#
+# By default, this script reads key mappings from /etc/sapphire.keymap
+# but this can be overridden by supplying an alternative path/file as a parameter.
+#
+# Eg. sapphire_keymap.sh ~/.sapphire_keymap
+#
+
+## Arrays for Buttons, Keycodes, and Modifiers:
+##'
+declare -A K B M
--- /dev/null
+
+PROCFILE=/proc/sapphire
+MYNAME="${0##*/}"
+
+function get_multipart_keycode(){
+ local parts="$1" val=0 zero_ok=0 p v
+ while [ "$parts" != "" ]; do
+ p="${parts%%|*}"
+ [ "$p" = "$parts" ] && parts="" || parts="${parts#*|}"
+ v="${K[$p]}"
+ if [ "$v" = "" ]; then
+ v="${M[$p]}"
+ if [ "$v" = "" ]; then
+ logger -s "$MYNAME: ERROR0: $p: unrecognized key/modifier"
+ exit 1
+ fi
+ fi
+ [ $((v)) -eq 0 ] && zero_ok=1 || val=$((val + v))
+ done
+ [ $val -ne 0 -o $zero_ok -eq 1 ] && printf "0x%08x" $val
+}
+
+function parse_keymap(){
+ local line blabel bval klabel kval olabel oval out i
+ while read line ; do
+ line="${line%%#*}"
+ set -- $line
+ [ "$1" = "" ] && continue
+ if [ "$1" = "REPEAT_RATE" -a "$3" != "" ]; then
+ out="$1 $2 $3"
+ else
+ blabel="$1"
+ bval="${B[$blabel]}"
+ if [ "$bval" = "" ]; then
+ logger -s "$MYNAME: ERROR1: $line blabel=$blabel"
+ exit 1
+ fi
+ out=
+ if [ "${blabel:0:10}" = "KEY_MACRO_" ]; then
+ out="$bval"
+ olabel=""
+ for i in {2..8} ; do
+ klabel="${!i}"
+ [ "$klabel" = "" ] && break
+ olabel="$olabel $klabel"
+ kval=$(get_multipart_keycode "$klabel")
+ if [ "$kval" = "" ]; then
+ logger -s "$MYNAME: ERROR2: $line"
+ exit 1
+ fi
+ out="$out $kval"
+ done
+ klabel=""
+ else
+ klabel="$2"
+ olabel="$3"
+ if [ "$blabel" = "" -o "$klabel" = "" -o "$olabel" = "" ]; then
+ logger -s "$MYNAME: ERROR3: $line"
+ exit 1
+ fi
+ kval=$(get_multipart_keycode "$klabel")
+ oval=$(get_multipart_keycode "$olabel")
+ if [ "$bval" = "" -o "$kval" = "" -o "$oval" = "" ]; then
+ logger -s "$MYNAME: ERROR4: $blabel=$bval $klabel=$kval $olabel=$oval"
+ exit 1
+ fi
+ out="$bval $kval $oval"
+ fi
+ fi
+ [ $VERBOSE -gt 0 ] && echo "echo \"$out\" >$PROCFILE # $blabel $klabel $olabel" >&2
+ echo "$out" >$PROCFILE || exit 2
+ out=
+ done
+}
+
+if [ ! -e $PROCFILE ]; then
+ echo "$PROCFILE: not found (is driver loaded?)"
+ exit 1
+fi
+
+VERBOSE=0
+while [ "$1" = "-v" -o "$1" = "--verbose" ]; do
+ VERBOSE=$((VERBOSE + 1))
+ shift
+done
+
+KEYMAP="$1"
+[ "$KEYMAP" = "" ] && KEYMAP=/etc/sapphire.keymap
+if [ ! -e "$KEYMAP" ]; then
+ logger -s "$MYNAME: $KEYMAP: not readable"
+ exit 1
+fi
+
+type -all logger &>/dev/null && logcmd="logger -s --" || logcmd=echo
+$logcmd "${0##*/}: sending \"$KEYMAP\" to $PROCFILE"
+cat "$KEYMAP" | parse_keymap
+
--- /dev/null
+#!/bin/bash
+#
+# Some distros seem to be building the hid-topseed driver into
+# the core kernel image, rather than leaving as a loadable module.
+#
+# So, for our sapphire driver to bind to a device,
+# we first have to get the hid-topseed driver to unbind from it.
+#
+TOPSEED=/sys/bus/hid/drivers/topseed
+SAPPHIRE=/sys/bus/hid/drivers/sapphire
+rmmod hid-topseed &>/dev/null
+modprobe sapphire &>/dev/null
+if [ -d $SAPPHIRE -a -e $TOPSEED/unbind ]; then
+ cd $TOPSEED
+ for dev in [0-9]*[-0-9A-F] ; do
+ if [ -e "$dev" ]; then
+ echo "$dev" > unbind
+ echo "$dev" > $SAPPHIRE/bind
+ fi
+ done
+fi
+#
+# Ubuntu/Mint kernels (and likely others too) don't like it
+# when we unload and reload the sapphire driver.
+# They disable the USB IR receiver and fail to reenable it.
+# The workaround below seems to restore functionality.
+#
+cd $SAPPHIRE || exit 1
+dev="$(/bin/ls -1d [0-9]*[0-9A-F] 2>/dev/null | head -1)"
+if [ "$dev" != "" -a -e "$dev" ]; then
+ if cd -P "$dev/../.." ; then
+ if [ -e authorized ]; then
+ echo 0 > authorized
+ echo 1 > authorized
+ fi
+ cd - >/dev/null
+ fi
+fi
+[ -e /etc/sapphire.keymap -a -x /usr/local/bin/sapphire_keymap.sh ] && /usr/local/bin/sapphire_keymap.sh
+exit 0