269 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * HID driver for the Creative SB0540 receiver4 *5 * Copyright (C) 2019 Red Hat Inc. All Rights Reserved6 *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_063};64 65/*66 * Codes and keys from lirc's67 * remotes/creative/lircd.conf.alsa_usb68 * order and size must match creative_sb0540_key_table[] above69 */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 0x807F115};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 as165 * remotes/creative/lircd.conf.alsa_usb in lirc166 */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-input215 * 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