162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * HID driver for CMedia CM6533 audio jack controls 462306a36Sopenharmony_ci * and HS100B mute buttons 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2015 Ben Chen <ben_chen@bizlinktech.com> 762306a36Sopenharmony_ci * Copyright (C) 2021 Thomas Weißschuh <linux@weissschuh.net> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/hid.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include "hid-ids.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ciMODULE_AUTHOR("Ben Chen"); 1662306a36Sopenharmony_ciMODULE_AUTHOR("Thomas Weißschuh"); 1762306a36Sopenharmony_ciMODULE_DESCRIPTION("CM6533 HID jack controls and HS100B mute button"); 1862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define CM6533_JD_TYPE_COUNT 1 2162306a36Sopenharmony_ci#define CM6533_JD_RAWEV_LEN 16 2262306a36Sopenharmony_ci#define CM6533_JD_SFX_OFFSET 8 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define HS100B_RDESC_ORIG_SIZE 60 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* Fixed report descriptor of HS-100B audio chip 2762306a36Sopenharmony_ci * Bit 4 is an abolute Microphone mute usage instead of being unassigned. 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_cistatic __u8 hs100b_rdesc_fixed[] = { 3062306a36Sopenharmony_ci 0x05, 0x0C, /* Usage Page (Consumer), */ 3162306a36Sopenharmony_ci 0x09, 0x01, /* Usage (Consumer Control), */ 3262306a36Sopenharmony_ci 0xA1, 0x01, /* Collection (Application), */ 3362306a36Sopenharmony_ci 0x15, 0x00, /* Logical Minimum (0), */ 3462306a36Sopenharmony_ci 0x25, 0x01, /* Logical Maximum (1), */ 3562306a36Sopenharmony_ci 0x09, 0xE9, /* Usage (Volume Inc), */ 3662306a36Sopenharmony_ci 0x09, 0xEA, /* Usage (Volume Dec), */ 3762306a36Sopenharmony_ci 0x75, 0x01, /* Report Size (1), */ 3862306a36Sopenharmony_ci 0x95, 0x02, /* Report Count (2), */ 3962306a36Sopenharmony_ci 0x81, 0x02, /* Input (Variable), */ 4062306a36Sopenharmony_ci 0x09, 0xE2, /* Usage (Mute), */ 4162306a36Sopenharmony_ci 0x95, 0x01, /* Report Count (1), */ 4262306a36Sopenharmony_ci 0x81, 0x06, /* Input (Variable, Relative), */ 4362306a36Sopenharmony_ci 0x05, 0x0B, /* Usage Page (Telephony), */ 4462306a36Sopenharmony_ci 0x09, 0x2F, /* Usage (2Fh), */ 4562306a36Sopenharmony_ci 0x81, 0x02, /* Input (Variable), */ 4662306a36Sopenharmony_ci 0x09, 0x20, /* Usage (20h), */ 4762306a36Sopenharmony_ci 0x81, 0x06, /* Input (Variable, Relative), */ 4862306a36Sopenharmony_ci 0x05, 0x0C, /* Usage Page (Consumer), */ 4962306a36Sopenharmony_ci 0x09, 0x00, /* Usage (00h), */ 5062306a36Sopenharmony_ci 0x95, 0x03, /* Report Count (3), */ 5162306a36Sopenharmony_ci 0x81, 0x02, /* Input (Variable), */ 5262306a36Sopenharmony_ci 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ 5362306a36Sopenharmony_ci 0x09, 0x00, /* Usage (00h), */ 5462306a36Sopenharmony_ci 0x75, 0x08, /* Report Size (8), */ 5562306a36Sopenharmony_ci 0x95, 0x03, /* Report Count (3), */ 5662306a36Sopenharmony_ci 0x81, 0x02, /* Input (Variable), */ 5762306a36Sopenharmony_ci 0x09, 0x00, /* Usage (00h), */ 5862306a36Sopenharmony_ci 0x95, 0x04, /* Report Count (4), */ 5962306a36Sopenharmony_ci 0x91, 0x02, /* Output (Variable), */ 6062306a36Sopenharmony_ci 0xC0 /* End Collection */ 6162306a36Sopenharmony_ci}; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci/* 6462306a36Sopenharmony_ci* 6562306a36Sopenharmony_ci*CM6533 audio jack HID raw events: 6662306a36Sopenharmony_ci* 6762306a36Sopenharmony_ci*Plug in: 6862306a36Sopenharmony_ci*01000600 002083xx 080008c0 10000000 6962306a36Sopenharmony_ci*about 3 seconds later... 7062306a36Sopenharmony_ci*01000a00 002083xx 08000380 10000000 7162306a36Sopenharmony_ci*01000600 002083xx 08000380 10000000 7262306a36Sopenharmony_ci* 7362306a36Sopenharmony_ci*Plug out: 7462306a36Sopenharmony_ci*01000400 002083xx 080008c0 x0000000 7562306a36Sopenharmony_ci*/ 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic const u8 ji_sfx[] = { 0x08, 0x00, 0x08, 0xc0 }; 7862306a36Sopenharmony_cistatic const u8 ji_in[] = { 0x01, 0x00, 0x06, 0x00 }; 7962306a36Sopenharmony_cistatic const u8 ji_out[] = { 0x01, 0x00, 0x04, 0x00 }; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic int jack_switch_types[CM6533_JD_TYPE_COUNT] = { 8262306a36Sopenharmony_ci SW_HEADPHONE_INSERT, 8362306a36Sopenharmony_ci}; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistruct cmhid { 8662306a36Sopenharmony_ci struct input_dev *input_dev; 8762306a36Sopenharmony_ci struct hid_device *hid; 8862306a36Sopenharmony_ci unsigned short switch_map[CM6533_JD_TYPE_COUNT]; 8962306a36Sopenharmony_ci}; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic void hp_ev(struct hid_device *hid, struct cmhid *cm, int value) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci input_report_switch(cm->input_dev, SW_HEADPHONE_INSERT, value); 9462306a36Sopenharmony_ci input_sync(cm->input_dev); 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic int cmhid_raw_event(struct hid_device *hid, struct hid_report *report, 9862306a36Sopenharmony_ci u8 *data, int len) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci struct cmhid *cm = hid_get_drvdata(hid); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci if (len != CM6533_JD_RAWEV_LEN) 10362306a36Sopenharmony_ci goto out; 10462306a36Sopenharmony_ci if (memcmp(data+CM6533_JD_SFX_OFFSET, ji_sfx, sizeof(ji_sfx))) 10562306a36Sopenharmony_ci goto out; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (!memcmp(data, ji_out, sizeof(ji_out))) { 10862306a36Sopenharmony_ci hp_ev(hid, cm, 0); 10962306a36Sopenharmony_ci goto out; 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci if (!memcmp(data, ji_in, sizeof(ji_in))) { 11262306a36Sopenharmony_ci hp_ev(hid, cm, 1); 11362306a36Sopenharmony_ci goto out; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ciout: 11762306a36Sopenharmony_ci return 0; 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic int cmhid_input_configured(struct hid_device *hid, 12162306a36Sopenharmony_ci struct hid_input *hidinput) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci struct input_dev *input_dev = hidinput->input; 12462306a36Sopenharmony_ci struct cmhid *cm = hid_get_drvdata(hid); 12562306a36Sopenharmony_ci int i; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci cm->input_dev = input_dev; 12862306a36Sopenharmony_ci memcpy(cm->switch_map, jack_switch_types, sizeof(cm->switch_map)); 12962306a36Sopenharmony_ci input_dev->evbit[0] = BIT(EV_SW); 13062306a36Sopenharmony_ci for (i = 0; i < CM6533_JD_TYPE_COUNT; i++) 13162306a36Sopenharmony_ci input_set_capability(cm->input_dev, 13262306a36Sopenharmony_ci EV_SW, jack_switch_types[i]); 13362306a36Sopenharmony_ci return 0; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic int cmhid_input_mapping(struct hid_device *hid, 13762306a36Sopenharmony_ci struct hid_input *hi, struct hid_field *field, 13862306a36Sopenharmony_ci struct hid_usage *usage, unsigned long **bit, int *max) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci return -1; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int cmhid_probe(struct hid_device *hid, const struct hid_device_id *id) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci int ret; 14662306a36Sopenharmony_ci struct cmhid *cm; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci cm = kzalloc(sizeof(struct cmhid), GFP_KERNEL); 14962306a36Sopenharmony_ci if (!cm) { 15062306a36Sopenharmony_ci ret = -ENOMEM; 15162306a36Sopenharmony_ci goto allocfail; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci cm->hid = hid; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci hid->quirks |= HID_QUIRK_HIDINPUT_FORCE; 15762306a36Sopenharmony_ci hid_set_drvdata(hid, cm); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci ret = hid_parse(hid); 16062306a36Sopenharmony_ci if (ret) { 16162306a36Sopenharmony_ci hid_err(hid, "parse failed\n"); 16262306a36Sopenharmony_ci goto fail; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci ret = hid_hw_start(hid, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE); 16662306a36Sopenharmony_ci if (ret) { 16762306a36Sopenharmony_ci hid_err(hid, "hw start failed\n"); 16862306a36Sopenharmony_ci goto fail; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci return 0; 17262306a36Sopenharmony_cifail: 17362306a36Sopenharmony_ci kfree(cm); 17462306a36Sopenharmony_ciallocfail: 17562306a36Sopenharmony_ci return ret; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic void cmhid_remove(struct hid_device *hid) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci struct cmhid *cm = hid_get_drvdata(hid); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci hid_hw_stop(hid); 18362306a36Sopenharmony_ci kfree(cm); 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic const struct hid_device_id cmhid_devices[] = { 18762306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CM6533) }, 18862306a36Sopenharmony_ci { } 18962306a36Sopenharmony_ci}; 19062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, cmhid_devices); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic struct hid_driver cmhid_driver = { 19362306a36Sopenharmony_ci .name = "cm6533_jd", 19462306a36Sopenharmony_ci .id_table = cmhid_devices, 19562306a36Sopenharmony_ci .raw_event = cmhid_raw_event, 19662306a36Sopenharmony_ci .input_configured = cmhid_input_configured, 19762306a36Sopenharmony_ci .probe = cmhid_probe, 19862306a36Sopenharmony_ci .remove = cmhid_remove, 19962306a36Sopenharmony_ci .input_mapping = cmhid_input_mapping, 20062306a36Sopenharmony_ci}; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic __u8 *cmhid_hs100b_report_fixup(struct hid_device *hid, __u8 *rdesc, 20362306a36Sopenharmony_ci unsigned int *rsize) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci if (*rsize == HS100B_RDESC_ORIG_SIZE) { 20662306a36Sopenharmony_ci hid_info(hid, "Fixing CMedia HS-100B report descriptor\n"); 20762306a36Sopenharmony_ci rdesc = hs100b_rdesc_fixed; 20862306a36Sopenharmony_ci *rsize = sizeof(hs100b_rdesc_fixed); 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci return rdesc; 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic const struct hid_device_id cmhid_hs100b_devices[] = { 21462306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CMEDIA_HS100B) }, 21562306a36Sopenharmony_ci { } 21662306a36Sopenharmony_ci}; 21762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, cmhid_hs100b_devices); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic struct hid_driver cmhid_hs100b_driver = { 22062306a36Sopenharmony_ci .name = "cmedia_hs100b", 22162306a36Sopenharmony_ci .id_table = cmhid_hs100b_devices, 22262306a36Sopenharmony_ci .report_fixup = cmhid_hs100b_report_fixup, 22362306a36Sopenharmony_ci}; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic int cmedia_init(void) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci int ret; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci ret = hid_register_driver(&cmhid_driver); 23062306a36Sopenharmony_ci if (ret) 23162306a36Sopenharmony_ci return ret; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci ret = hid_register_driver(&cmhid_hs100b_driver); 23462306a36Sopenharmony_ci if (ret) 23562306a36Sopenharmony_ci hid_unregister_driver(&cmhid_driver); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci return ret; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_cimodule_init(cmedia_init); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic void cmedia_exit(void) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci hid_unregister_driver(&cmhid_driver); 24462306a36Sopenharmony_ci hid_unregister_driver(&cmhid_hs100b_driver); 24562306a36Sopenharmony_ci} 24662306a36Sopenharmony_cimodule_exit(cmedia_exit); 247