summaryrefslogtreecommitdiffstats
path: root/drivers/media/IR/ir-keytable.c
diff options
context:
space:
mode:
authorJaroslav Kysela <perex@perex.cz>2010-01-08 09:26:34 +0100
committerJaroslav Kysela <perex@perex.cz>2010-01-08 09:26:34 +0100
commit1cb4f624ea38361b6397966470f0a1bed5532483 (patch)
tree418b05ddc854b09d64f7d5ee0c78875e42b5f151 /drivers/media/IR/ir-keytable.c
parent444c1953d496d272208902ff7010dc70d1f887f0 (diff)
parent2c1f1895ef2aa8f0e5497893eff71304aef332e1 (diff)
downloadlinux-1cb4f624ea38361b6397966470f0a1bed5532483.tar.gz
linux-1cb4f624ea38361b6397966470f0a1bed5532483.tar.bz2
linux-1cb4f624ea38361b6397966470f0a1bed5532483.zip
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6 into fixes
Diffstat (limited to 'drivers/media/IR/ir-keytable.c')
-rw-r--r--drivers/media/IR/ir-keytable.c485
1 files changed, 485 insertions, 0 deletions
diff --git a/drivers/media/IR/ir-keytable.c b/drivers/media/IR/ir-keytable.c
new file mode 100644
index 000000000000..bff7a5356037
--- /dev/null
+++ b/drivers/media/IR/ir-keytable.c
@@ -0,0 +1,485 @@
+/* ir-register.c - handle IR scancode->keycode tables
+ *
+ * Copyright (C) 2009 by Mauro Carvalho Chehab <mchehab@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+
+#include <linux/usb/input.h>
+#include <media/ir-common.h>
+
+#define IR_TAB_MIN_SIZE 32
+#define IR_TAB_MAX_SIZE 1024
+
+/**
+ * ir_seek_table() - returns the element order on the table
+ * @rc_tab: the ir_scancode_table with the keymap to be used
+ * @scancode: the scancode that we're seeking
+ *
+ * This routine is used by the input routines when a key is pressed at the
+ * IR. The scancode is received and needs to be converted into a keycode.
+ * If the key is not found, it returns KEY_UNKNOWN. Otherwise, returns the
+ * corresponding keycode from the table.
+ */
+static int ir_seek_table(struct ir_scancode_table *rc_tab, u32 scancode)
+{
+ int rc;
+ unsigned long flags;
+ struct ir_scancode *keymap = rc_tab->scan;
+
+ spin_lock_irqsave(&rc_tab->lock, flags);
+
+ /* FIXME: replace it by a binary search */
+
+ for (rc = 0; rc < rc_tab->size; rc++)
+ if (keymap[rc].scancode == scancode)
+ goto exit;
+
+ /* Not found */
+ rc = -EINVAL;
+
+exit:
+ spin_unlock_irqrestore(&rc_tab->lock, flags);
+ return rc;
+}
+
+/**
+ * ir_roundup_tablesize() - gets an optimum value for the table size
+ * @n_elems: minimum number of entries to store keycodes
+ *
+ * This routine is used to choose the keycode table size.
+ *
+ * In order to have some empty space for new keycodes,
+ * and knowing in advance that kmalloc allocates only power of two
+ * segments, it optimizes the allocated space to have some spare space
+ * for those new keycodes by using the maximum number of entries that
+ * will be effectively be allocated by kmalloc.
+ * In order to reduce the quantity of table resizes, it has a minimum
+ * table size of IR_TAB_MIN_SIZE.
+ */
+int ir_roundup_tablesize(int n_elems)
+{
+ size_t size;
+
+ if (n_elems < IR_TAB_MIN_SIZE)
+ n_elems = IR_TAB_MIN_SIZE;
+
+ /*
+ * As kmalloc only allocates sizes of power of two, get as
+ * much entries as possible for the allocated memory segment
+ */
+ size = roundup_pow_of_two(n_elems * sizeof(struct ir_scancode));
+ n_elems = size / sizeof(struct ir_scancode);
+
+ return n_elems;
+}
+EXPORT_SYMBOL_GPL(ir_roundup_tablesize);
+
+/**
+ * ir_copy_table() - copies a keytable, discarding the unused entries
+ * @destin: destin table
+ * @origin: origin table
+ *
+ * Copies all entries where the keycode is not KEY_UNKNOWN/KEY_RESERVED
+ */
+
+int ir_copy_table(struct ir_scancode_table *destin,
+ const struct ir_scancode_table *origin)
+{
+ int i, j = 0;
+
+ for (i = 0; i < origin->size; i++) {
+ if (origin->scan[i].keycode == KEY_UNKNOWN ||
+ origin->scan[i].keycode == KEY_RESERVED)
+ continue;
+
+ memcpy(&destin->scan[j], &origin->scan[i], sizeof(struct ir_scancode));
+ j++;
+ }
+ destin->size = j;
+
+ IR_dprintk(1, "Copied %d scancodes to the new keycode table\n", destin->size);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ir_copy_table);
+
+/**
+ * ir_getkeycode() - get a keycode at the evdev scancode ->keycode table
+ * @dev: the struct input_dev device descriptor
+ * @scancode: the desired scancode
+ * @keycode: the keycode to be retorned.
+ *
+ * This routine is used to handle evdev EVIOCGKEY ioctl.
+ * If the key is not found, returns -EINVAL, otherwise, returns 0.
+ */
+static int ir_getkeycode(struct input_dev *dev,
+ int scancode, int *keycode)
+{
+ int elem;
+ struct ir_input_dev *ir_dev = input_get_drvdata(dev);
+ struct ir_scancode_table *rc_tab = &ir_dev->rc_tab;
+
+ elem = ir_seek_table(rc_tab, scancode);
+ if (elem >= 0) {
+ *keycode = rc_tab->scan[elem].keycode;
+ return 0;
+ }
+
+ /*
+ * Scancode not found and table can't be expanded
+ */
+ if (elem < 0 && rc_tab->size == IR_TAB_MAX_SIZE)
+ return -EINVAL;
+
+ /*
+ * If is there extra space, returns KEY_RESERVED,
+ * otherwise, input core won't let ir_setkeycode to work
+ */
+ *keycode = KEY_RESERVED;
+ return 0;
+}
+
+/**
+ * ir_is_resize_needed() - Check if the table needs rezise
+ * @table: keycode table that may need to resize
+ * @n_elems: minimum number of entries to store keycodes
+ *
+ * Considering that kmalloc uses power of two storage areas, this
+ * routine detects if the real alloced size will change. If not, it
+ * just returns without doing nothing. Otherwise, it will extend or
+ * reduce the table size to meet the new needs.
+ *
+ * It returns 0 if no resize is needed, 1 otherwise.
+ */
+static int ir_is_resize_needed(struct ir_scancode_table *table, int n_elems)
+{
+ int cur_size = ir_roundup_tablesize(table->size);
+ int new_size = ir_roundup_tablesize(n_elems);
+
+ if (cur_size == new_size)
+ return 0;
+
+ /* Resize is needed */
+ return 1;
+}
+
+/**
+ * ir_delete_key() - remove a keycode from the table
+ * @rc_tab: keycode table
+ * @elem: element to be removed
+ *
+ */
+static void ir_delete_key(struct ir_scancode_table *rc_tab, int elem)
+{
+ unsigned long flags = 0;
+ int newsize = rc_tab->size - 1;
+ int resize = ir_is_resize_needed(rc_tab, newsize);
+ struct ir_scancode *oldkeymap = rc_tab->scan;
+ struct ir_scancode *newkeymap;
+
+ if (resize) {
+ newkeymap = kzalloc(ir_roundup_tablesize(newsize) *
+ sizeof(*newkeymap), GFP_ATOMIC);
+
+ /* There's no memory for resize. Keep the old table */
+ if (!newkeymap)
+ resize = 0;
+ }
+
+ if (!resize) {
+ newkeymap = oldkeymap;
+
+ /* We'll modify the live table. Lock it */
+ spin_lock_irqsave(&rc_tab->lock, flags);
+ }
+
+ /*
+ * Copy the elements before the one that will be deleted
+ * if (!resize), both oldkeymap and newkeymap points
+ * to the same place, so, there's no need to copy
+ */
+ if (resize && elem > 0)
+ memcpy(newkeymap, oldkeymap,
+ elem * sizeof(*newkeymap));
+
+ /*
+ * Copy the other elements overwriting the element to be removed
+ * This operation applies to both resize and non-resize case
+ */
+ if (elem < newsize)
+ memcpy(&newkeymap[elem], &oldkeymap[elem + 1],
+ (newsize - elem) * sizeof(*newkeymap));
+
+ if (resize) {
+ /*
+ * As the copy happened to a temporary table, only here
+ * it needs to lock while replacing the table pointers
+ * to use the new table
+ */
+ spin_lock_irqsave(&rc_tab->lock, flags);
+ rc_tab->size = newsize;
+ rc_tab->scan = newkeymap;
+ spin_unlock_irqrestore(&rc_tab->lock, flags);
+
+ /* Frees the old keytable */
+ kfree(oldkeymap);
+ } else {
+ rc_tab->size = newsize;
+ spin_unlock_irqrestore(&rc_tab->lock, flags);
+ }
+}
+
+/**
+ * ir_insert_key() - insert a keycode at the table
+ * @rc_tab: keycode table
+ * @scancode: the desired scancode
+ * @keycode: the keycode to be retorned.
+ *
+ */
+static int ir_insert_key(struct ir_scancode_table *rc_tab,
+ int scancode, int keycode)
+{
+ unsigned long flags;
+ int elem = rc_tab->size;
+ int newsize = rc_tab->size + 1;
+ int resize = ir_is_resize_needed(rc_tab, newsize);
+ struct ir_scancode *oldkeymap = rc_tab->scan;
+ struct ir_scancode *newkeymap;
+
+ if (resize) {
+ newkeymap = kzalloc(ir_roundup_tablesize(newsize) *
+ sizeof(*newkeymap), GFP_ATOMIC);
+ if (!newkeymap)
+ return -ENOMEM;
+
+ memcpy(newkeymap, oldkeymap,
+ rc_tab->size * sizeof(*newkeymap));
+ } else
+ newkeymap = oldkeymap;
+
+ /* Stores the new code at the table */
+ IR_dprintk(1, "#%d: New scan 0x%04x with key 0x%04x\n",
+ rc_tab->size, scancode, keycode);
+
+ spin_lock_irqsave(&rc_tab->lock, flags);
+ rc_tab->size = newsize;
+ if (resize) {
+ rc_tab->scan = newkeymap;
+ kfree(oldkeymap);
+ }
+ newkeymap[elem].scancode = scancode;
+ newkeymap[elem].keycode = keycode;
+ spin_unlock_irqrestore(&rc_tab->lock, flags);
+
+ return 0;
+}
+
+/**
+ * ir_setkeycode() - set a keycode at the evdev scancode ->keycode table
+ * @dev: the struct input_dev device descriptor
+ * @scancode: the desired scancode
+ * @keycode: the keycode to be retorned.
+ *
+ * This routine is used to handle evdev EVIOCSKEY ioctl.
+ * There's one caveat here: how can we increase the size of the table?
+ * If the key is not found, returns -EINVAL, otherwise, returns 0.
+ */
+static int ir_setkeycode(struct input_dev *dev,
+ int scancode, int keycode)
+{
+ int rc = 0;
+ struct ir_input_dev *ir_dev = input_get_drvdata(dev);
+ struct ir_scancode_table *rc_tab = &ir_dev->rc_tab;
+ struct ir_scancode *keymap = rc_tab->scan;
+ unsigned long flags;
+
+ /*
+ * Handle keycode table deletions
+ *
+ * If userspace is adding a KEY_UNKNOWN or KEY_RESERVED,
+ * deal as a trial to remove an existing scancode attribution
+ * if table become too big, reduce it to save space
+ */
+ if (keycode == KEY_UNKNOWN || keycode == KEY_RESERVED) {
+ rc = ir_seek_table(rc_tab, scancode);
+ if (rc < 0)
+ return 0;
+
+ IR_dprintk(1, "#%d: Deleting scan 0x%04x\n", rc, scancode);
+ clear_bit(keymap[rc].keycode, dev->keybit);
+ ir_delete_key(rc_tab, rc);
+
+ return 0;
+ }
+
+ /*
+ * Handle keycode replacements
+ *
+ * If the scancode exists, just replace by the new value
+ */
+ rc = ir_seek_table(rc_tab, scancode);
+ if (rc >= 0) {
+ IR_dprintk(1, "#%d: Replacing scan 0x%04x with key 0x%04x\n",
+ rc, scancode, keycode);
+
+ clear_bit(keymap[rc].keycode, dev->keybit);
+
+ spin_lock_irqsave(&rc_tab->lock, flags);
+ keymap[rc].keycode = keycode;
+ spin_unlock_irqrestore(&rc_tab->lock, flags);
+
+ set_bit(keycode, dev->keybit);
+
+ return 0;
+ }
+
+ /*
+ * Handle new scancode inserts
+ *
+ * reallocate table if needed and insert a new keycode
+ */
+
+ /* Avoid growing the table indefinitely */
+ if (rc_tab->size + 1 > IR_TAB_MAX_SIZE)
+ return -EINVAL;
+
+ rc = ir_insert_key(rc_tab, scancode, keycode);
+ if (rc < 0)
+ return rc;
+ set_bit(keycode, dev->keybit);
+
+ return 0;
+}
+
+/**
+ * ir_g_keycode_from_table() - gets the keycode that corresponds to a scancode
+ * @input_dev: the struct input_dev descriptor of the device
+ * @scancode: the scancode that we're seeking
+ *
+ * This routine is used by the input routines when a key is pressed at the
+ * IR. The scancode is received and needs to be converted into a keycode.
+ * If the key is not found, it returns KEY_UNKNOWN. Otherwise, returns the
+ * corresponding keycode from the table.
+ */
+u32 ir_g_keycode_from_table(struct input_dev *dev, u32 scancode)
+{
+ struct ir_input_dev *ir_dev = input_get_drvdata(dev);
+ struct ir_scancode_table *rc_tab = &ir_dev->rc_tab;
+ struct ir_scancode *keymap = rc_tab->scan;
+ int elem;
+
+ elem = ir_seek_table(rc_tab, scancode);
+ if (elem >= 0) {
+ IR_dprintk(1, "%s: scancode 0x%04x keycode 0x%02x\n",
+ dev->name, scancode, keymap[elem].keycode);
+
+ return rc_tab->scan[elem].keycode;
+ }
+
+ printk(KERN_INFO "%s: unknown key for scancode 0x%04x\n",
+ dev->name, scancode);
+
+ /* Reports userspace that an unknown keycode were got */
+ return KEY_RESERVED;
+}
+EXPORT_SYMBOL_GPL(ir_g_keycode_from_table);
+
+/**
+ * ir_input_register() - sets the IR keycode table and add the handlers
+ * for keymap table get/set
+ * @input_dev: the struct input_dev descriptor of the device
+ * @rc_tab: the struct ir_scancode_table table of scancode/keymap
+ *
+ * This routine is used to initialize the input infrastructure to work with
+ * an IR.
+ * It should be called before registering the IR device.
+ */
+int ir_input_register(struct input_dev *input_dev,
+ struct ir_scancode_table *rc_tab)
+{
+ struct ir_input_dev *ir_dev;
+ struct ir_scancode *keymap = rc_tab->scan;
+ int i, rc;
+
+ if (rc_tab->scan == NULL || !rc_tab->size)
+ return -EINVAL;
+
+ ir_dev = kzalloc(sizeof(*ir_dev), GFP_KERNEL);
+ if (!ir_dev)
+ return -ENOMEM;
+
+ spin_lock_init(&rc_tab->lock);
+
+ ir_dev->rc_tab.size = ir_roundup_tablesize(rc_tab->size);
+ ir_dev->rc_tab.scan = kzalloc(ir_dev->rc_tab.size *
+ sizeof(struct ir_scancode), GFP_KERNEL);
+ if (!ir_dev->rc_tab.scan)
+ return -ENOMEM;
+
+ IR_dprintk(1, "Allocated space for %d keycode entries (%zd bytes)\n",
+ ir_dev->rc_tab.size,
+ ir_dev->rc_tab.size * sizeof(ir_dev->rc_tab.scan));
+
+ ir_copy_table(&ir_dev->rc_tab, rc_tab);
+
+ /* set the bits for the keys */
+ IR_dprintk(1, "key map size: %d\n", rc_tab->size);
+ for (i = 0; i < rc_tab->size; i++) {
+ IR_dprintk(1, "#%d: setting bit for keycode 0x%04x\n",
+ i, keymap[i].keycode);
+ set_bit(keymap[i].keycode, input_dev->keybit);
+ }
+ clear_bit(0, input_dev->keybit);
+
+ set_bit(EV_KEY, input_dev->evbit);
+
+ input_dev->getkeycode = ir_getkeycode;
+ input_dev->setkeycode = ir_setkeycode;
+ input_set_drvdata(input_dev, ir_dev);
+
+ rc = input_register_device(input_dev);
+ if (rc < 0) {
+ kfree(rc_tab->scan);
+ kfree(ir_dev);
+ input_set_drvdata(input_dev, NULL);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(ir_input_register);
+
+void ir_input_unregister(struct input_dev *dev)
+{
+ struct ir_input_dev *ir_dev = input_get_drvdata(dev);
+ struct ir_scancode_table *rc_tab;
+
+ if (!ir_dev)
+ return;
+
+ IR_dprintk(1, "Freed keycode table\n");
+
+ rc_tab = &ir_dev->rc_tab;
+ rc_tab->size = 0;
+ kfree(rc_tab->scan);
+ rc_tab->scan = NULL;
+
+ kfree(ir_dev);
+ input_unregister_device(dev);
+}
+EXPORT_SYMBOL_GPL(ir_input_unregister);
+
+int ir_core_debug; /* ir_debug level (0,1,2) */
+EXPORT_SYMBOL_GPL(ir_core_debug);
+module_param_named(debug, ir_core_debug, int, 0644);
+
+MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>");
+MODULE_LICENSE("GPL");