xref: /linux/drivers/hid/hid-kysona.c (revision 94ec1cd82f55ebbf5c0d63c8aa7f849fdab2b535)
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