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