linux/drivers/hid/hid-creative-sb0540.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * HID driver for the Creative SB0540 receiver
   4 *
   5 * Copyright (C) 2019 Red Hat Inc. All Rights Reserved
   6 *
   7 */
   8
   9#include <linux/device.h>
  10#include <linux/hid.h>
  11#include <linux/module.h>
  12#include "hid-ids.h"
  13
  14MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
  15MODULE_DESCRIPTION("HID Creative SB0540 receiver");
  16MODULE_LICENSE("GPL");
  17
  18static const unsigned short creative_sb0540_key_table[] = {
  19        KEY_POWER,
  20        KEY_RESERVED,           /* text: 24bit */
  21        KEY_RESERVED,           /* 24bit wheel up */
  22        KEY_RESERVED,           /* 24bit wheel down */
  23        KEY_RESERVED,           /* text: CMSS */
  24        KEY_RESERVED,           /* CMSS wheel Up */
  25        KEY_RESERVED,           /* CMSS wheel Down */
  26        KEY_RESERVED,           /* text: EAX */
  27        KEY_RESERVED,           /* EAX wheel up */
  28        KEY_RESERVED,           /* EAX wheel down */
  29        KEY_RESERVED,           /* text: 3D Midi */
  30        KEY_RESERVED,           /* 3D Midi wheel up */
  31        KEY_RESERVED,           /* 3D Midi wheel down */
  32        KEY_MUTE,
  33        KEY_VOLUMEUP,
  34        KEY_VOLUMEDOWN,
  35        KEY_UP,
  36        KEY_LEFT,
  37        KEY_RIGHT,
  38        KEY_REWIND,
  39        KEY_OK,
  40        KEY_FASTFORWARD,
  41        KEY_DOWN,
  42        KEY_AGAIN,              /* text: Return, symbol: Jump to */
  43        KEY_PLAY,               /* text: Start */
  44        KEY_ESC,                /* text: Cancel */
  45        KEY_RECORD,
  46        KEY_OPTION,
  47        KEY_MENU,               /* text: Display */
  48        KEY_PREVIOUS,
  49        KEY_PLAYPAUSE,
  50        KEY_NEXT,
  51        KEY_SLOW,
  52        KEY_STOP,
  53        KEY_NUMERIC_1,
  54        KEY_NUMERIC_2,
  55        KEY_NUMERIC_3,
  56        KEY_NUMERIC_4,
  57        KEY_NUMERIC_5,
  58        KEY_NUMERIC_6,
  59        KEY_NUMERIC_7,
  60        KEY_NUMERIC_8,
  61        KEY_NUMERIC_9,
  62        KEY_NUMERIC_0
  63};
  64
  65/*
  66 * Codes and keys from lirc's
  67 * remotes/creative/lircd.conf.alsa_usb
  68 * order and size must match creative_sb0540_key_table[] above
  69 */
  70static const unsigned short creative_sb0540_codes[] = {
  71        0x619E,
  72        0x916E,
  73        0x926D,
  74        0x936C,
  75        0x718E,
  76        0x946B,
  77        0x956A,
  78        0x8C73,
  79        0x9669,
  80        0x9768,
  81        0x9867,
  82        0x9966,
  83        0x9A65,
  84        0x6E91,
  85        0x629D,
  86        0x639C,
  87        0x7B84,
  88        0x6B94,
  89        0x728D,
  90        0x8778,
  91        0x817E,
  92        0x758A,
  93        0x8D72,
  94        0x8E71,
  95        0x8877,
  96        0x7C83,
  97        0x738C,
  98        0x827D,
  99        0x7689,
 100        0x7F80,
 101        0x7986,
 102        0x7A85,
 103        0x7D82,
 104        0x857A,
 105        0x8B74,
 106        0x8F70,
 107        0x906F,
 108        0x8A75,
 109        0x847B,
 110        0x7887,
 111        0x8976,
 112        0x837C,
 113        0x7788,
 114        0x807F
 115};
 116
 117struct creative_sb0540 {
 118        struct input_dev *input_dev;
 119        struct hid_device *hid;
 120        unsigned short keymap[ARRAY_SIZE(creative_sb0540_key_table)];
 121};
 122
 123static inline u64 reverse(u64 data, int bits)
 124{
 125        int i;
 126        u64 c;
 127
 128        c = 0;
 129        for (i = 0; i < bits; i++) {
 130                c |= (u64) (((data & (((u64) 1) << i)) ? 1 : 0))
 131                        << (bits - 1 - i);
 132        }
 133        return (c);
 134}
 135
 136static int get_key(struct creative_sb0540 *creative_sb0540, u64 keycode)
 137{
 138        int i;
 139
 140        for (i = 0; i < ARRAY_SIZE(creative_sb0540_codes); i++) {
 141                if (creative_sb0540_codes[i] == keycode)
 142                        return creative_sb0540->keymap[i];
 143        }
 144
 145        return 0;
 146
 147}
 148
 149static int creative_sb0540_raw_event(struct hid_device *hid,
 150        struct hid_report *report, u8 *data, int len)
 151{
 152        struct creative_sb0540 *creative_sb0540 = hid_get_drvdata(hid);
 153        u64 code, main_code;
 154        int key;
 155
 156        if (len != 6)
 157                return 0;
 158
 159        /* From daemons/hw_hiddev.c sb0540_rec() in lirc */
 160        code = reverse(data[5], 8);
 161        main_code = (code << 8) + ((~code) & 0xff);
 162
 163        /*
 164         * Flip to get values in the same format as
 165         * remotes/creative/lircd.conf.alsa_usb in lirc
 166         */
 167        main_code = ((main_code & 0xff) << 8) +
 168                ((main_code & 0xff00) >> 8);
 169
 170        key = get_key(creative_sb0540, main_code);
 171        if (key == 0 || key == KEY_RESERVED) {
 172                hid_err(hid, "Could not get a key for main_code %llX\n",
 173                        main_code);
 174                return 0;
 175        }
 176
 177        input_report_key(creative_sb0540->input_dev, key, 1);
 178        input_report_key(creative_sb0540->input_dev, key, 0);
 179        input_sync(creative_sb0540->input_dev);
 180
 181        /* let hidraw and hiddev handle the report */
 182        return 0;
 183}
 184
 185static int creative_sb0540_input_configured(struct hid_device *hid,
 186                struct hid_input *hidinput)
 187{
 188        struct input_dev *input_dev = hidinput->input;
 189        struct creative_sb0540 *creative_sb0540 = hid_get_drvdata(hid);
 190        int i;
 191
 192        creative_sb0540->input_dev = input_dev;
 193
 194        input_dev->keycode = creative_sb0540->keymap;
 195        input_dev->keycodesize = sizeof(unsigned short);
 196        input_dev->keycodemax = ARRAY_SIZE(creative_sb0540->keymap);
 197
 198        input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
 199
 200        memcpy(creative_sb0540->keymap, creative_sb0540_key_table,
 201                sizeof(creative_sb0540->keymap));
 202        for (i = 0; i < ARRAY_SIZE(creative_sb0540_key_table); i++)
 203                set_bit(creative_sb0540->keymap[i], input_dev->keybit);
 204        clear_bit(KEY_RESERVED, input_dev->keybit);
 205
 206        return 0;
 207}
 208
 209static int creative_sb0540_input_mapping(struct hid_device *hid,
 210                struct hid_input *hi, struct hid_field *field,
 211                struct hid_usage *usage, unsigned long **bit, int *max)
 212{
 213        /*
 214         * We are remapping the keys ourselves, so ignore the hid-input
 215         * keymap processing.
 216         */
 217        return -1;
 218}
 219
 220static int creative_sb0540_probe(struct hid_device *hid,
 221                const struct hid_device_id *id)
 222{
 223        int ret;
 224        struct creative_sb0540 *creative_sb0540;
 225
 226        creative_sb0540 = devm_kzalloc(&hid->dev,
 227                sizeof(struct creative_sb0540), GFP_KERNEL);
 228
 229        if (!creative_sb0540)
 230                return -ENOMEM;
 231
 232        creative_sb0540->hid = hid;
 233
 234        /* force input as some remotes bypass the input registration */
 235        hid->quirks |= HID_QUIRK_HIDINPUT_FORCE;
 236
 237        hid_set_drvdata(hid, creative_sb0540);
 238
 239        ret = hid_parse(hid);
 240        if (ret) {
 241                hid_err(hid, "parse failed\n");
 242                return ret;
 243        }
 244
 245        ret = hid_hw_start(hid, HID_CONNECT_DEFAULT);
 246        if (ret) {
 247                hid_err(hid, "hw start failed\n");
 248                return ret;
 249        }
 250
 251        return ret;
 252}
 253
 254static const struct hid_device_id creative_sb0540_devices[] = {
 255        { HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB0540) },
 256        { }
 257};
 258MODULE_DEVICE_TABLE(hid, creative_sb0540_devices);
 259
 260static struct hid_driver creative_sb0540_driver = {
 261        .name = "creative-sb0540",
 262        .id_table = creative_sb0540_devices,
 263        .raw_event = creative_sb0540_raw_event,
 264        .input_configured = creative_sb0540_input_configured,
 265        .probe = creative_sb0540_probe,
 266        .input_mapping = creative_sb0540_input_mapping,
 267};
 268module_hid_driver(creative_sb0540_driver);
 269