Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v6.15 248 lines 6.5 kB view raw
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 19struct 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 31static 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 41static 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 83static 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 88static 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 109static 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 118static 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 129static 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 170static 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 204static 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 220static 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 230static 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}; 235MODULE_DEVICE_TABLE(hid, kysona_devices); 236 237static 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}; 244module_hid_driver(kysona_driver); 245 246MODULE_LICENSE("GPL"); 247MODULE_DESCRIPTION("HID driver for Kysona devices"); 248MODULE_AUTHOR("Lode Willems <me@lodewillems.com>");