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