| // 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 || !(hid->claimed & HID_CLAIMED_INPUT)) | |
| 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); |