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