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_TIMEOUT_MS 5000
16 
17 #define ONLINE_REPORT_ID 3
18 #define BATTERY_REPORT_ID 4
19 
20 struct kysona_drvdata {
21 	struct hid_device *hdev;
22 	bool online;
23 
24 	struct power_supply_desc battery_desc;
25 	struct power_supply *battery;
26 	u8 battery_capacity;
27 	bool battery_charging;
28 	u16 battery_voltage;
29 	struct delayed_work battery_work;
30 };
31 
32 static enum power_supply_property kysona_battery_props[] = {
33 	POWER_SUPPLY_PROP_STATUS,
34 	POWER_SUPPLY_PROP_PRESENT,
35 	POWER_SUPPLY_PROP_CAPACITY,
36 	POWER_SUPPLY_PROP_SCOPE,
37 	POWER_SUPPLY_PROP_MODEL_NAME,
38 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
39 	POWER_SUPPLY_PROP_ONLINE
40 };
41 
42 static int kysona_battery_get_property(struct power_supply *psy,
43 				       enum power_supply_property psp,
44 				       union power_supply_propval *val)
45 {
46 	struct kysona_drvdata *drv_data = power_supply_get_drvdata(psy);
47 	int ret = 0;
48 
49 	switch (psp) {
50 	case POWER_SUPPLY_PROP_PRESENT:
51 		val->intval = 1;
52 		break;
53 	case POWER_SUPPLY_PROP_ONLINE:
54 		val->intval = drv_data->online;
55 		break;
56 	case POWER_SUPPLY_PROP_STATUS:
57 		if (drv_data->online)
58 			val->intval = drv_data->battery_charging ?
59 					POWER_SUPPLY_STATUS_CHARGING :
60 					POWER_SUPPLY_STATUS_DISCHARGING;
61 		else
62 			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
63 		break;
64 	case POWER_SUPPLY_PROP_SCOPE:
65 		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
66 		break;
67 	case POWER_SUPPLY_PROP_CAPACITY:
68 		val->intval = drv_data->battery_capacity;
69 		break;
70 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
71 		/* hardware reports voltage in mV. sysfs expects uV */
72 		val->intval = drv_data->battery_voltage * 1000;
73 		break;
74 	case POWER_SUPPLY_PROP_MODEL_NAME:
75 		val->strval = drv_data->hdev->name;
76 		break;
77 	default:
78 		ret = -EINVAL;
79 		break;
80 	}
81 	return ret;
82 }
83 
84 static const char kysona_online_request[] = {
85 	0x08, ONLINE_REPORT_ID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
86 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a
87 };
88 
89 static const char kysona_battery_request[] = {
90 	0x08, BATTERY_REPORT_ID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
91 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49
92 };
93 
94 static int kysona_m600_fetch_online(struct hid_device *hdev)
95 {
96 	u8 *write_buf;
97 	int ret;
98 
99 	/* Request online information */
100 	write_buf = kmemdup(kysona_online_request, sizeof(kysona_online_request), GFP_KERNEL);
101 	if (!write_buf)
102 		return -ENOMEM;
103 
104 	ret = hid_hw_raw_request(hdev, kysona_online_request[0],
105 				 write_buf, sizeof(kysona_online_request),
106 				 HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
107 	if (ret < (int)sizeof(kysona_online_request)) {
108 		hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
109 		ret = -ENODATA;
110 	}
111 	kfree(write_buf);
112 	return ret;
113 }
114 
115 static void kysona_fetch_online(struct hid_device *hdev)
116 {
117 	int ret = kysona_m600_fetch_online(hdev);
118 
119 	if (ret < 0)
120 		hid_dbg(hdev,
121 			"Online query failed (err: %d)\n", ret);
122 }
123 
124 static int kysona_m600_fetch_battery(struct hid_device *hdev)
125 {
126 	u8 *write_buf;
127 	int ret;
128 
129 	/* Request battery information */
130 	write_buf = kmemdup(kysona_battery_request, sizeof(kysona_battery_request), GFP_KERNEL);
131 	if (!write_buf)
132 		return -ENOMEM;
133 
134 	ret = hid_hw_raw_request(hdev, kysona_battery_request[0],
135 				 write_buf, sizeof(kysona_battery_request),
136 				 HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
137 	if (ret < (int)sizeof(kysona_battery_request)) {
138 		hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
139 		ret = -ENODATA;
140 	}
141 	kfree(write_buf);
142 	return ret;
143 }
144 
145 static void kysona_fetch_battery(struct hid_device *hdev)
146 {
147 	int ret = kysona_m600_fetch_battery(hdev);
148 
149 	if (ret < 0)
150 		hid_dbg(hdev,
151 			"Battery query failed (err: %d)\n", ret);
152 }
153 
154 static void kysona_battery_timer_tick(struct work_struct *work)
155 {
156 	struct kysona_drvdata *drv_data = container_of(work,
157 		struct kysona_drvdata, battery_work.work);
158 	struct hid_device *hdev = drv_data->hdev;
159 
160 	kysona_fetch_online(hdev);
161 	kysona_fetch_battery(hdev);
162 	schedule_delayed_work(&drv_data->battery_work,
163 			      msecs_to_jiffies(BATTERY_TIMEOUT_MS));
164 }
165 
166 static int kysona_battery_probe(struct hid_device *hdev)
167 {
168 	struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
169 	struct power_supply_config pscfg = { .drv_data = drv_data };
170 	int ret = 0;
171 
172 	drv_data->online = false;
173 	drv_data->battery_capacity = 100;
174 	drv_data->battery_voltage = 4200;
175 
176 	drv_data->battery_desc.properties = kysona_battery_props;
177 	drv_data->battery_desc.num_properties = ARRAY_SIZE(kysona_battery_props);
178 	drv_data->battery_desc.get_property = kysona_battery_get_property;
179 	drv_data->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
180 	drv_data->battery_desc.use_for_apm = 0;
181 	drv_data->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
182 						     "kysona-%s-battery",
183 						     strlen(hdev->uniq) ?
184 						     hdev->uniq : dev_name(&hdev->dev));
185 	if (!drv_data->battery_desc.name)
186 		return -ENOMEM;
187 
188 	drv_data->battery = devm_power_supply_register(&hdev->dev,
189 						       &drv_data->battery_desc, &pscfg);
190 	if (IS_ERR(drv_data->battery)) {
191 		ret = PTR_ERR(drv_data->battery);
192 		drv_data->battery = NULL;
193 		hid_err(hdev, "Unable to register battery device\n");
194 		return ret;
195 	}
196 
197 	power_supply_powers(drv_data->battery, &hdev->dev);
198 
199 	INIT_DELAYED_WORK(&drv_data->battery_work, kysona_battery_timer_tick);
200 	kysona_fetch_online(hdev);
201 	kysona_fetch_battery(hdev);
202 	schedule_delayed_work(&drv_data->battery_work,
203 			      msecs_to_jiffies(BATTERY_TIMEOUT_MS));
204 
205 	return ret;
206 }
207 
208 static int kysona_probe(struct hid_device *hdev, const struct hid_device_id *id)
209 {
210 	int ret;
211 	struct kysona_drvdata *drv_data;
212 	struct usb_interface *usbif;
213 
214 	if (!hid_is_usb(hdev))
215 		return -EINVAL;
216 
217 	usbif = to_usb_interface(hdev->dev.parent);
218 
219 	drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
220 	if (!drv_data)
221 		return -ENOMEM;
222 
223 	hid_set_drvdata(hdev, drv_data);
224 	drv_data->hdev = hdev;
225 
226 	ret = hid_parse(hdev);
227 	if (ret)
228 		return ret;
229 
230 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
231 	if (ret)
232 		return ret;
233 
234 	if (usbif->cur_altsetting->desc.bInterfaceNumber == 1) {
235 		if (kysona_battery_probe(hdev) < 0)
236 			hid_err(hdev, "Kysona hid battery_probe failed: %d\n", ret);
237 	}
238 
239 	return 0;
240 }
241 
242 static int kysona_raw_event(struct hid_device *hdev,
243 			    struct hid_report *report, u8 *data, int size)
244 {
245 	struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
246 
247 	if (size == sizeof(kysona_online_request) &&
248 	    data[0] == 8 && data[1] == ONLINE_REPORT_ID) {
249 		drv_data->online = data[6];
250 	}
251 
252 	if (size == sizeof(kysona_battery_request) &&
253 	    data[0] == 8 && data[1] == BATTERY_REPORT_ID) {
254 		drv_data->battery_capacity = data[6];
255 		drv_data->battery_charging = data[7];
256 		drv_data->battery_voltage = (data[8] << 8) | data[9];
257 	}
258 
259 	return 0;
260 }
261 
262 static void kysona_remove(struct hid_device *hdev)
263 {
264 	struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
265 
266 	if (drv_data->battery)
267 		cancel_delayed_work_sync(&drv_data->battery_work);
268 
269 	hid_hw_stop(hdev);
270 }
271 
272 static const struct hid_device_id kysona_devices[] = {
273 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE) },
274 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED) },
275 	{ }
276 };
277 MODULE_DEVICE_TABLE(hid, kysona_devices);
278 
279 static struct hid_driver kysona_driver = {
280 	.name			= "kysona",
281 	.id_table		= kysona_devices,
282 	.probe			= kysona_probe,
283 	.raw_event		= kysona_raw_event,
284 	.remove			= kysona_remove
285 };
286 module_hid_driver(kysona_driver);
287 
288 MODULE_LICENSE("GPL");
289 MODULE_DESCRIPTION("HID driver for Kysona devices");
290 MODULE_AUTHOR("Lode Willems <me@lodewillems.com>");
291