hid-creative-sb0540.c (5771B)
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);