162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  HID driver for ELECOM devices:
462306a36Sopenharmony_ci *  - BM084 Bluetooth Mouse
562306a36Sopenharmony_ci *  - EX-G Trackballs (M-XT3DRBK, M-XT3URBK, M-XT4DRBK)
662306a36Sopenharmony_ci *  - DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK)
762306a36Sopenharmony_ci *  - HUGE Trackballs (M-HT1DRBK, M-HT1URBK)
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci *  Copyright (c) 2010 Richard Nauber <Richard.Nauber@gmail.com>
1062306a36Sopenharmony_ci *  Copyright (c) 2016 Yuxuan Shui <yshuiv7@gmail.com>
1162306a36Sopenharmony_ci *  Copyright (c) 2017 Diego Elio Pettenò <flameeyes@flameeyes.eu>
1262306a36Sopenharmony_ci *  Copyright (c) 2017 Alex Manoussakis <amanou@gnu.org>
1362306a36Sopenharmony_ci *  Copyright (c) 2017 Tomasz Kramkowski <tk@the-tk.com>
1462306a36Sopenharmony_ci *  Copyright (c) 2020 YOSHIOKA Takuma <lo48576@hard-wi.red>
1562306a36Sopenharmony_ci *  Copyright (c) 2022 Takahiro Fujii <fujii@xaxxi.net>
1662306a36Sopenharmony_ci */
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/*
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/device.h>
2262306a36Sopenharmony_ci#include <linux/hid.h>
2362306a36Sopenharmony_ci#include <linux/module.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include "hid-ids.h"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/*
2862306a36Sopenharmony_ci * Certain ELECOM mice misreport their button count meaning that they only work
2962306a36Sopenharmony_ci * correctly with the ELECOM mouse assistant software which is unavailable for
3062306a36Sopenharmony_ci * Linux. A four extra INPUT reports and a FEATURE report are described by the
3162306a36Sopenharmony_ci * report descriptor but it does not appear that these enable software to
3262306a36Sopenharmony_ci * control what the extra buttons map to. The only simple and straightforward
3362306a36Sopenharmony_ci * solution seems to involve fixing up the report descriptor.
3462306a36Sopenharmony_ci */
3562306a36Sopenharmony_ci#define MOUSE_BUTTONS_MAX 8
3662306a36Sopenharmony_cistatic void mouse_button_fixup(struct hid_device *hdev,
3762306a36Sopenharmony_ci			       __u8 *rdesc, unsigned int rsize,
3862306a36Sopenharmony_ci			       unsigned int button_bit_count,
3962306a36Sopenharmony_ci			       unsigned int padding_bit,
4062306a36Sopenharmony_ci			       unsigned int button_report_size,
4162306a36Sopenharmony_ci			       unsigned int button_usage_maximum,
4262306a36Sopenharmony_ci			       int nbuttons)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	if (rsize < 32 || rdesc[button_bit_count] != 0x95 ||
4562306a36Sopenharmony_ci	    rdesc[button_report_size] != 0x75 ||
4662306a36Sopenharmony_ci	    rdesc[button_report_size + 1] != 0x01 ||
4762306a36Sopenharmony_ci	    rdesc[button_usage_maximum] != 0x29 || rdesc[padding_bit] != 0x75)
4862306a36Sopenharmony_ci		return;
4962306a36Sopenharmony_ci	hid_info(hdev, "Fixing up Elecom mouse button count\n");
5062306a36Sopenharmony_ci	nbuttons = clamp(nbuttons, 0, MOUSE_BUTTONS_MAX);
5162306a36Sopenharmony_ci	rdesc[button_bit_count + 1] = nbuttons;
5262306a36Sopenharmony_ci	rdesc[button_usage_maximum + 1] = nbuttons;
5362306a36Sopenharmony_ci	rdesc[padding_bit + 1] = MOUSE_BUTTONS_MAX - nbuttons;
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
5762306a36Sopenharmony_ci		unsigned int *rsize)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	switch (hdev->product) {
6062306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_BM084:
6162306a36Sopenharmony_ci		/* The BM084 Bluetooth mouse includes a non-existing horizontal
6262306a36Sopenharmony_ci		 * wheel in the HID descriptor. */
6362306a36Sopenharmony_ci		if (*rsize >= 48 && rdesc[46] == 0x05 && rdesc[47] == 0x0c) {
6462306a36Sopenharmony_ci			hid_info(hdev, "Fixing up Elecom BM084 report descriptor\n");
6562306a36Sopenharmony_ci			rdesc[47] = 0x00;
6662306a36Sopenharmony_ci		}
6762306a36Sopenharmony_ci		break;
6862306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_XGL20DLBK:
6962306a36Sopenharmony_ci		/*
7062306a36Sopenharmony_ci		 * Report descriptor format:
7162306a36Sopenharmony_ci		 * 20: button bit count
7262306a36Sopenharmony_ci		 * 28: padding bit count
7362306a36Sopenharmony_ci		 * 22: button report size
7462306a36Sopenharmony_ci		 * 14: button usage maximum
7562306a36Sopenharmony_ci		 */
7662306a36Sopenharmony_ci		mouse_button_fixup(hdev, rdesc, *rsize, 20, 28, 22, 14, 8);
7762306a36Sopenharmony_ci		break;
7862306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_XT3URBK:
7962306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_XT3DRBK:
8062306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_XT4DRBK:
8162306a36Sopenharmony_ci		/*
8262306a36Sopenharmony_ci		 * Report descriptor format:
8362306a36Sopenharmony_ci		 * 12: button bit count
8462306a36Sopenharmony_ci		 * 30: padding bit count
8562306a36Sopenharmony_ci		 * 14: button report size
8662306a36Sopenharmony_ci		 * 20: button usage maximum
8762306a36Sopenharmony_ci		 */
8862306a36Sopenharmony_ci		mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 6);
8962306a36Sopenharmony_ci		break;
9062306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_DT1URBK:
9162306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_DT1DRBK:
9262306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_HT1URBK:
9362306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D:
9462306a36Sopenharmony_ci		/*
9562306a36Sopenharmony_ci		 * Report descriptor format:
9662306a36Sopenharmony_ci		 * 12: button bit count
9762306a36Sopenharmony_ci		 * 30: padding bit count
9862306a36Sopenharmony_ci		 * 14: button report size
9962306a36Sopenharmony_ci		 * 20: button usage maximum
10062306a36Sopenharmony_ci		 */
10162306a36Sopenharmony_ci		mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8);
10262306a36Sopenharmony_ci		break;
10362306a36Sopenharmony_ci	case USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C:
10462306a36Sopenharmony_ci		/*
10562306a36Sopenharmony_ci		 * Report descriptor format:
10662306a36Sopenharmony_ci		 * 22: button bit count
10762306a36Sopenharmony_ci		 * 30: padding bit count
10862306a36Sopenharmony_ci		 * 24: button report size
10962306a36Sopenharmony_ci		 * 16: button usage maximum
11062306a36Sopenharmony_ci		 */
11162306a36Sopenharmony_ci		mouse_button_fixup(hdev, rdesc, *rsize, 22, 30, 24, 16, 8);
11262306a36Sopenharmony_ci		break;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci	return rdesc;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic const struct hid_device_id elecom_devices[] = {
11862306a36Sopenharmony_ci	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
11962306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) },
12062306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) },
12162306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) },
12262306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) },
12362306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
12462306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
12562306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK) },
12662306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
12762306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
12862306a36Sopenharmony_ci	{ }
12962306a36Sopenharmony_ci};
13062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, elecom_devices);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic struct hid_driver elecom_driver = {
13362306a36Sopenharmony_ci	.name = "elecom",
13462306a36Sopenharmony_ci	.id_table = elecom_devices,
13562306a36Sopenharmony_ci	.report_fixup = elecom_report_fixup
13662306a36Sopenharmony_ci};
13762306a36Sopenharmony_cimodule_hid_driver(elecom_driver);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
140