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
kysona_battery_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)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
kysona_m600_fetch_online(struct hid_device * hdev)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
kysona_fetch_online(struct hid_device * hdev)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
kysona_m600_fetch_battery(struct hid_device * hdev)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
kysona_fetch_battery(struct hid_device * hdev)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
kysona_battery_timer_tick(struct work_struct * work)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
kysona_battery_probe(struct hid_device * hdev)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
kysona_probe(struct hid_device * hdev,const struct hid_device_id * id)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
kysona_raw_event(struct hid_device * hdev,struct hid_report * report,u8 * data,int size)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
kysona_remove(struct hid_device * hdev)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