1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * HID driver for CMedia CM6533 audio jack controls 4 * and HS100B mute buttons 5 * 6 * Copyright (C) 2015 Ben Chen <ben_chen@bizlinktech.com> 7 * Copyright (C) 2021 Thomas Weißschuh <linux@weissschuh.net> 8 */ 9 10 #include <linux/device.h> 11 #include <linux/hid.h> 12 #include <linux/module.h> 13 #include "hid-ids.h" 14 15 MODULE_AUTHOR("Ben Chen"); 16 MODULE_AUTHOR("Thomas Weißschuh"); 17 MODULE_DESCRIPTION("CM6533 HID jack controls and HS100B mute button"); 18 MODULE_LICENSE("GPL"); 19 20 #define CM6533_JD_TYPE_COUNT 1 21 #define CM6533_JD_RAWEV_LEN 16 22 #define CM6533_JD_SFX_OFFSET 8 23 24 #define HS100B_RDESC_ORIG_SIZE 60 25 26 /* Fixed report descriptor of HS-100B audio chip 27 * Bit 4 is an abolute Microphone mute usage instead of being unassigned. 28 */ 29 static __u8 hs100b_rdesc_fixed[] = { 30 0x05, 0x0C, /* Usage Page (Consumer), */ 31 0x09, 0x01, /* Usage (Consumer Control), */ 32 0xA1, 0x01, /* Collection (Application), */ 33 0x15, 0x00, /* Logical Minimum (0), */ 34 0x25, 0x01, /* Logical Maximum (1), */ 35 0x09, 0xE9, /* Usage (Volume Inc), */ 36 0x09, 0xEA, /* Usage (Volume Dec), */ 37 0x75, 0x01, /* Report Size (1), */ 38 0x95, 0x02, /* Report Count (2), */ 39 0x81, 0x02, /* Input (Variable), */ 40 0x09, 0xE2, /* Usage (Mute), */ 41 0x95, 0x01, /* Report Count (1), */ 42 0x81, 0x06, /* Input (Variable, Relative), */ 43 0x05, 0x0B, /* Usage Page (Telephony), */ 44 0x09, 0x2F, /* Usage (2Fh), */ 45 0x81, 0x02, /* Input (Variable), */ 46 0x09, 0x20, /* Usage (20h), */ 47 0x81, 0x06, /* Input (Variable, Relative), */ 48 0x05, 0x0C, /* Usage Page (Consumer), */ 49 0x09, 0x00, /* Usage (00h), */ 50 0x95, 0x03, /* Report Count (3), */ 51 0x81, 0x02, /* Input (Variable), */ 52 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ 53 0x09, 0x00, /* Usage (00h), */ 54 0x75, 0x08, /* Report Size (8), */ 55 0x95, 0x03, /* Report Count (3), */ 56 0x81, 0x02, /* Input (Variable), */ 57 0x09, 0x00, /* Usage (00h), */ 58 0x95, 0x04, /* Report Count (4), */ 59 0x91, 0x02, /* Output (Variable), */ 60 0xC0 /* End Collection */ 61 }; 62 63 /* 64 * 65 *CM6533 audio jack HID raw events: 66 * 67 *Plug in: 68 *01000600 002083xx 080008c0 10000000 69 *about 3 seconds later... 70 *01000a00 002083xx 08000380 10000000 71 *01000600 002083xx 08000380 10000000 72 * 73 *Plug out: 74 *01000400 002083xx 080008c0 x0000000 75 */ 76 77 static const u8 ji_sfx[] = { 0x08, 0x00, 0x08, 0xc0 }; 78 static const u8 ji_in[] = { 0x01, 0x00, 0x06, 0x00 }; 79 static const u8 ji_out[] = { 0x01, 0x00, 0x04, 0x00 }; 80 81 static int jack_switch_types[CM6533_JD_TYPE_COUNT] = { 82 SW_HEADPHONE_INSERT, 83 }; 84 85 struct cmhid { 86 struct input_dev *input_dev; 87 struct hid_device *hid; 88 unsigned short switch_map[CM6533_JD_TYPE_COUNT]; 89 }; 90 91 static void hp_ev(struct hid_device *hid, struct cmhid *cm, int value) 92 { 93 input_report_switch(cm->input_dev, SW_HEADPHONE_INSERT, value); 94 input_sync(cm->input_dev); 95 } 96 97 static int cmhid_raw_event(struct hid_device *hid, struct hid_report *report, 98 u8 *data, int len) 99 { 100 struct cmhid *cm = hid_get_drvdata(hid); 101 102 if (len != CM6533_JD_RAWEV_LEN) 103 goto out; 104 if (memcmp(data+CM6533_JD_SFX_OFFSET, ji_sfx, sizeof(ji_sfx))) 105 goto out; 106 107 if (!memcmp(data, ji_out, sizeof(ji_out))) { 108 hp_ev(hid, cm, 0); 109 goto out; 110 } 111 if (!memcmp(data, ji_in, sizeof(ji_in))) { 112 hp_ev(hid, cm, 1); 113 goto out; 114 } 115 116 out: 117 return 0; 118 } 119 120 static int cmhid_input_configured(struct hid_device *hid, 121 struct hid_input *hidinput) 122 { 123 struct input_dev *input_dev = hidinput->input; 124 struct cmhid *cm = hid_get_drvdata(hid); 125 int i; 126 127 cm->input_dev = input_dev; 128 memcpy(cm->switch_map, jack_switch_types, sizeof(cm->switch_map)); 129 input_dev->evbit[0] = BIT(EV_SW); 130 for (i = 0; i < CM6533_JD_TYPE_COUNT; i++) 131 input_set_capability(cm->input_dev, 132 EV_SW, jack_switch_types[i]); 133 return 0; 134 } 135 136 static int cmhid_input_mapping(struct hid_device *hid, 137 struct hid_input *hi, struct hid_field *field, 138 struct hid_usage *usage, unsigned long **bit, int *max) 139 { 140 return -1; 141 } 142 143 static int cmhid_probe(struct hid_device *hid, const struct hid_device_id *id) 144 { 145 int ret; 146 struct cmhid *cm; 147 148 cm = kzalloc(sizeof(struct cmhid), GFP_KERNEL); 149 if (!cm) { 150 ret = -ENOMEM; 151 goto allocfail; 152 } 153 154 cm->hid = hid; 155 156 hid->quirks |= HID_QUIRK_HIDINPUT_FORCE; 157 hid_set_drvdata(hid, cm); 158 159 ret = hid_parse(hid); 160 if (ret) { 161 hid_err(hid, "parse failed\n"); 162 goto fail; 163 } 164 165 ret = hid_hw_start(hid, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE); 166 if (ret) { 167 hid_err(hid, "hw start failed\n"); 168 goto fail; 169 } 170 171 return 0; 172 fail: 173 kfree(cm); 174 allocfail: 175 return ret; 176 } 177 178 static void cmhid_remove(struct hid_device *hid) 179 { 180 struct cmhid *cm = hid_get_drvdata(hid); 181 182 hid_hw_stop(hid); 183 kfree(cm); 184 } 185 186 static const struct hid_device_id cmhid_devices[] = { 187 { HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CM6533) }, 188 { } 189 }; 190 MODULE_DEVICE_TABLE(hid, cmhid_devices); 191 192 static struct hid_driver cmhid_driver = { 193 .name = "cm6533_jd", 194 .id_table = cmhid_devices, 195 .raw_event = cmhid_raw_event, 196 .input_configured = cmhid_input_configured, 197 .probe = cmhid_probe, 198 .remove = cmhid_remove, 199 .input_mapping = cmhid_input_mapping, 200 }; 201 202 static __u8 *cmhid_hs100b_report_fixup(struct hid_device *hid, __u8 *rdesc, 203 unsigned int *rsize) 204 { 205 if (*rsize == HS100B_RDESC_ORIG_SIZE) { 206 hid_info(hid, "Fixing CMedia HS-100B report descriptor\n"); 207 rdesc = hs100b_rdesc_fixed; 208 *rsize = sizeof(hs100b_rdesc_fixed); 209 } 210 return rdesc; 211 } 212 213 static const struct hid_device_id cmhid_hs100b_devices[] = { 214 { HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CMEDIA_HS100B) }, 215 { } 216 }; 217 MODULE_DEVICE_TABLE(hid, cmhid_hs100b_devices); 218 219 static struct hid_driver cmhid_hs100b_driver = { 220 .name = "cmedia_hs100b", 221 .id_table = cmhid_hs100b_devices, 222 .report_fixup = cmhid_hs100b_report_fixup, 223 }; 224 225 static int cmedia_init(void) 226 { 227 int ret; 228 229 ret = hid_register_driver(&cmhid_driver); 230 if (ret) 231 return ret; 232 233 ret = hid_register_driver(&cmhid_hs100b_driver); 234 if (ret) 235 hid_unregister_driver(&cmhid_driver); 236 237 return ret; 238 } 239 module_init(cmedia_init); 240 241 static void cmedia_exit(void) 242 { 243 hid_unregister_driver(&cmhid_driver); 244 hid_unregister_driver(&cmhid_hs100b_driver); 245 } 246 module_exit(cmedia_exit); 247