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