File Index
Files and directories
Sign in
FILE

hid-kysona.c

File preview with syntax highlighting.

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