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