xref: /linux/drivers/hid/hid-kysona.c (revision 30c32d05294575c27bdb2fa13c67cd1b601bf400)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *  USB HID driver for Kysona
4  *  Kysona M600 mice.
5  *
6  *  Copyright (c) 2024 Lode Willems <me@lodewillems.com>
7  */
8 
9 #include <linux/device.h>
10 #include <linux/hid.h>
11 #include <linux/usb.h>
12 
13 #include "hid-ids.h"
14 
15 #define BATTERY_REPORT_ID 4
16 
17 struct kysona_drvdata {
18 	struct hid_device *hdev;
19 	struct power_supply_desc battery_desc;
20 	struct power_supply *battery;
21 	u8 battery_capacity;
22 	bool battery_charging;
23 	u16 battery_voltage;
24 };
25 
26 static enum power_supply_property kysona_battery_props[] = {
27 	POWER_SUPPLY_PROP_STATUS,
28 	POWER_SUPPLY_PROP_PRESENT,
29 	POWER_SUPPLY_PROP_CAPACITY,
30 	POWER_SUPPLY_PROP_SCOPE,
31 	POWER_SUPPLY_PROP_MODEL_NAME,
32 	POWER_SUPPLY_PROP_VOLTAGE_NOW
33 };
34 
35 static int kysona_battery_get_property(struct power_supply *psy,
36 				       enum power_supply_property psp,
37 				       union power_supply_propval *val)
38 {
39 	struct kysona_drvdata *drv_data = power_supply_get_drvdata(psy);
40 	int ret = 0;
41 
42 	switch (psp) {
43 	case POWER_SUPPLY_PROP_PRESENT:
44 		val->intval = 1;
45 		break;
46 	case POWER_SUPPLY_PROP_STATUS:
47 		// TODO: check if device is online
48 		val->intval = drv_data->battery_charging ?
49 			POWER_SUPPLY_STATUS_CHARGING :
50 			POWER_SUPPLY_STATUS_DISCHARGING;
51 		break;
52 	case POWER_SUPPLY_PROP_SCOPE:
53 		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
54 		break;
55 	case POWER_SUPPLY_PROP_CAPACITY:
56 		val->intval = drv_data->battery_capacity;
57 		break;
58 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
59 		/* hardware reports voltage in mV. sysfs expects uV */
60 		val->intval = drv_data->battery_voltage * 1000;
61 		break;
62 	case POWER_SUPPLY_PROP_MODEL_NAME:
63 		val->strval = drv_data->hdev->name;
64 		break;
65 	default:
66 		ret = -EINVAL;
67 		break;
68 	}
69 	return ret;
70 }
71 
72 static const char kysona_battery_request[] = {
73 	0x08, BATTERY_REPORT_ID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
74 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49
75 };
76 
77 static int kysona_m600_fetch_battery(struct hid_device *hdev)
78 {
79 	u8 *write_buf;
80 	int ret;
81 
82 	/* Request battery information */
83 	write_buf = kmemdup(kysona_battery_request, sizeof(kysona_battery_request), GFP_KERNEL);
84 	if (!write_buf)
85 		return -ENOMEM;
86 
87 	ret = hid_hw_raw_request(hdev, kysona_battery_request[0],
88 				 write_buf, sizeof(kysona_battery_request),
89 				 HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
90 	if (ret < (int)sizeof(kysona_battery_request)) {
91 		hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
92 		ret = -ENODATA;
93 	}
94 	kfree(write_buf);
95 	return ret;
96 }
97 
98 static void kysona_fetch_battery(struct hid_device *hdev)
99 {
100 	int ret = kysona_m600_fetch_battery(hdev);
101 
102 	if (ret < 0)
103 		hid_dbg(hdev,
104 			"Battery query failed (err: %d)\n", ret);
105 }
106 
107 static int kysona_battery_probe(struct hid_device *hdev)
108 {
109 	struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
110 	struct power_supply_config pscfg = { .drv_data = drv_data };
111 	int ret = 0;
112 
113 	drv_data->battery_capacity = 100;
114 
115 	drv_data->battery_desc.properties = kysona_battery_props;
116 	drv_data->battery_desc.num_properties = ARRAY_SIZE(kysona_battery_props);
117 	drv_data->battery_desc.get_property = kysona_battery_get_property;
118 	drv_data->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
119 	drv_data->battery_desc.use_for_apm = 0;
120 	drv_data->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
121 						     "kysona-%s-battery",
122 						     strlen(hdev->uniq) ?
123 						     hdev->uniq : dev_name(&hdev->dev));
124 	if (!drv_data->battery_desc.name)
125 		return -ENOMEM;
126 
127 	drv_data->battery = devm_power_supply_register(&hdev->dev,
128 						       &drv_data->battery_desc, &pscfg);
129 	if (IS_ERR(drv_data->battery)) {
130 		ret = PTR_ERR(drv_data->battery);
131 		drv_data->battery = NULL;
132 		hid_err(hdev, "Unable to register battery device\n");
133 		return ret;
134 	}
135 
136 	power_supply_powers(drv_data->battery, &hdev->dev);
137 	kysona_fetch_battery(hdev);
138 
139 	return ret;
140 }
141 
142 static int kysona_probe(struct hid_device *hdev, const struct hid_device_id *id)
143 {
144 	int ret;
145 	struct kysona_drvdata *drv_data;
146 	struct usb_interface *usbif;
147 
148 	if (!hid_is_usb(hdev))
149 		return -EINVAL;
150 
151 	usbif = to_usb_interface(hdev->dev.parent);
152 
153 	drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
154 	if (!drv_data)
155 		return -ENOMEM;
156 
157 	hid_set_drvdata(hdev, drv_data);
158 	drv_data->hdev = hdev;
159 
160 	ret = hid_parse(hdev);
161 	if (ret)
162 		return ret;
163 
164 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
165 	if (ret)
166 		return ret;
167 
168 	if (usbif->cur_altsetting->desc.bInterfaceNumber == 1) {
169 		if (kysona_battery_probe(hdev) < 0)
170 			hid_err(hdev, "Kysona hid battery_probe failed: %d\n", ret);
171 	}
172 
173 	return 0;
174 }
175 
176 static int kysona_raw_event(struct hid_device *hdev,
177 			    struct hid_report *report, u8 *data, int size)
178 {
179 	struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
180 
181 	if (drv_data->battery && size == sizeof(kysona_battery_request) &&
182 	    data[0] == 8 && data[1] == BATTERY_REPORT_ID) {
183 		drv_data->battery_capacity = data[6];
184 		drv_data->battery_charging = data[7];
185 		drv_data->battery_voltage = (data[8] << 8) | data[9];
186 	}
187 
188 	return 0;
189 }
190 
191 static const struct hid_device_id kysona_devices[] = {
192 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE) },
193 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED) },
194 	{ }
195 };
196 MODULE_DEVICE_TABLE(hid, kysona_devices);
197 
198 static struct hid_driver kysona_driver = {
199 	.name			= "kysona",
200 	.id_table		= kysona_devices,
201 	.probe			= kysona_probe,
202 	.raw_event		= kysona_raw_event
203 };
204 module_hid_driver(kysona_driver);
205 
206 MODULE_LICENSE("GPL");
207 MODULE_DESCRIPTION("HID driver for Kysona devices");
208 MODULE_AUTHOR("Lode Willems <me@lodewillems.com>");
209