summaryrefslogtreecommitdiffstats
path: root/drivers/hid/hid-creative-sb0540.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/hid-creative-sb0540.c')
-rw-r--r--drivers/hid/hid-creative-sb0540.c268
1 files changed, 268 insertions, 0 deletions
diff --git a/drivers/hid/hid-creative-sb0540.c b/drivers/hid/hid-creative-sb0540.c
new file mode 100644
index 0000000000..b4c8e7a5d3
--- /dev/null
+++ b/drivers/hid/hid-creative-sb0540.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID driver for the Creative SB0540 receiver
+ *
+ * Copyright (C) 2019 Red Hat Inc. All Rights Reserved
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
+MODULE_DESCRIPTION("HID Creative SB0540 receiver");
+MODULE_LICENSE("GPL");
+
+static const unsigned short creative_sb0540_key_table[] = {
+ KEY_POWER,
+ KEY_RESERVED, /* text: 24bit */
+ KEY_RESERVED, /* 24bit wheel up */
+ KEY_RESERVED, /* 24bit wheel down */
+ KEY_RESERVED, /* text: CMSS */
+ KEY_RESERVED, /* CMSS wheel Up */
+ KEY_RESERVED, /* CMSS wheel Down */
+ KEY_RESERVED, /* text: EAX */
+ KEY_RESERVED, /* EAX wheel up */
+ KEY_RESERVED, /* EAX wheel down */
+ KEY_RESERVED, /* text: 3D Midi */
+ KEY_RESERVED, /* 3D Midi wheel up */
+ KEY_RESERVED, /* 3D Midi wheel down */
+ KEY_MUTE,
+ KEY_VOLUMEUP,
+ KEY_VOLUMEDOWN,
+ KEY_UP,
+ KEY_LEFT,
+ KEY_RIGHT,
+ KEY_REWIND,
+ KEY_OK,
+ KEY_FASTFORWARD,
+ KEY_DOWN,
+ KEY_AGAIN, /* text: Return, symbol: Jump to */
+ KEY_PLAY, /* text: Start */
+ KEY_ESC, /* text: Cancel */
+ KEY_RECORD,
+ KEY_OPTION,
+ KEY_MENU, /* text: Display */
+ KEY_PREVIOUS,
+ KEY_PLAYPAUSE,
+ KEY_NEXT,
+ KEY_SLOW,
+ KEY_STOP,
+ 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_0
+};
+
+/*
+ * Codes and keys from lirc's
+ * remotes/creative/lircd.conf.alsa_usb
+ * order and size must match creative_sb0540_key_table[] above
+ */
+static const unsigned short creative_sb0540_codes[] = {
+ 0x619E,
+ 0x916E,
+ 0x926D,
+ 0x936C,
+ 0x718E,
+ 0x946B,
+ 0x956A,
+ 0x8C73,
+ 0x9669,
+ 0x9768,
+ 0x9867,
+ 0x9966,
+ 0x9A65,
+ 0x6E91,
+ 0x629D,
+ 0x639C,
+ 0x7B84,
+ 0x6B94,
+ 0x728D,
+ 0x8778,
+ 0x817E,
+ 0x758A,
+ 0x8D72,
+ 0x8E71,
+ 0x8877,
+ 0x7C83,
+ 0x738C,
+ 0x827D,
+ 0x7689,
+ 0x7F80,
+ 0x7986,
+ 0x7A85,
+ 0x7D82,
+ 0x857A,
+ 0x8B74,
+ 0x8F70,
+ 0x906F,
+ 0x8A75,
+ 0x847B,
+ 0x7887,
+ 0x8976,
+ 0x837C,
+ 0x7788,
+ 0x807F
+};
+
+struct creative_sb0540 {
+ struct input_dev *input_dev;
+ struct hid_device *hid;
+ unsigned short keymap[ARRAY_SIZE(creative_sb0540_key_table)];
+};
+
+static inline u64 reverse(u64 data, int bits)
+{
+ int i;
+ u64 c;
+
+ c = 0;
+ for (i = 0; i < bits; i++) {
+ c |= (u64) (((data & (((u64) 1) << i)) ? 1 : 0))
+ << (bits - 1 - i);
+ }
+ return (c);
+}
+
+static int get_key(struct creative_sb0540 *creative_sb0540, u64 keycode)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(creative_sb0540_codes); i++) {
+ if (creative_sb0540_codes[i] == keycode)
+ return creative_sb0540->keymap[i];
+ }
+
+ return 0;
+
+}
+
+static int creative_sb0540_raw_event(struct hid_device *hid,
+ struct hid_report *report, u8 *data, int len)
+{
+ struct creative_sb0540 *creative_sb0540 = hid_get_drvdata(hid);
+ u64 code, main_code;
+ int key;
+
+ if (len != 6)
+ return 0;
+
+ /* From daemons/hw_hiddev.c sb0540_rec() in lirc */
+ code = reverse(data[5], 8);
+ main_code = (code << 8) + ((~code) & 0xff);
+
+ /*
+ * Flip to get values in the same format as
+ * remotes/creative/lircd.conf.alsa_usb in lirc
+ */
+ main_code = ((main_code & 0xff) << 8) +
+ ((main_code & 0xff00) >> 8);
+
+ key = get_key(creative_sb0540, main_code);
+ if (key == 0 || key == KEY_RESERVED) {
+ hid_err(hid, "Could not get a key for main_code %llX\n",
+ main_code);
+ return 0;
+ }
+
+ input_report_key(creative_sb0540->input_dev, key, 1);
+ input_report_key(creative_sb0540->input_dev, key, 0);
+ input_sync(creative_sb0540->input_dev);
+
+ /* let hidraw and hiddev handle the report */
+ return 0;
+}
+
+static int creative_sb0540_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
+{
+ struct input_dev *input_dev = hidinput->input;
+ struct creative_sb0540 *creative_sb0540 = hid_get_drvdata(hid);
+ int i;
+
+ creative_sb0540->input_dev = input_dev;
+
+ input_dev->keycode = creative_sb0540->keymap;
+ input_dev->keycodesize = sizeof(unsigned short);
+ input_dev->keycodemax = ARRAY_SIZE(creative_sb0540->keymap);
+
+ input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
+
+ memcpy(creative_sb0540->keymap, creative_sb0540_key_table,
+ sizeof(creative_sb0540->keymap));
+ for (i = 0; i < ARRAY_SIZE(creative_sb0540_key_table); i++)
+ set_bit(creative_sb0540->keymap[i], input_dev->keybit);
+ clear_bit(KEY_RESERVED, input_dev->keybit);
+
+ return 0;
+}
+
+static int creative_sb0540_input_mapping(struct hid_device *hid,
+ struct hid_input *hi, struct hid_field *field,
+ struct hid_usage *usage, unsigned long **bit, int *max)
+{
+ /*
+ * We are remapping the keys ourselves, so ignore the hid-input
+ * keymap processing.
+ */
+ return -1;
+}
+
+static int creative_sb0540_probe(struct hid_device *hid,
+ const struct hid_device_id *id)
+{
+ int ret;
+ struct creative_sb0540 *creative_sb0540;
+
+ creative_sb0540 = devm_kzalloc(&hid->dev,
+ sizeof(struct creative_sb0540), GFP_KERNEL);
+
+ if (!creative_sb0540)
+ return -ENOMEM;
+
+ creative_sb0540->hid = hid;
+
+ /* force input as some remotes bypass the input registration */
+ hid->quirks |= HID_QUIRK_HIDINPUT_FORCE;
+
+ hid_set_drvdata(hid, creative_sb0540);
+
+ ret = hid_parse(hid);
+ if (ret) {
+ hid_err(hid, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hid, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hid, "hw start failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static const struct hid_device_id creative_sb0540_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB0540) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, creative_sb0540_devices);
+
+static struct hid_driver creative_sb0540_driver = {
+ .name = "creative-sb0540",
+ .id_table = creative_sb0540_devices,
+ .raw_event = creative_sb0540_raw_event,
+ .input_configured = creative_sb0540_input_configured,
+ .probe = creative_sb0540_probe,
+ .input_mapping = creative_sb0540_input_mapping,
+};
+module_hid_driver(creative_sb0540_driver);