]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
HID: Kysona: Add basic battery reporting for Kysona M600
authorLode Willems <me@lodewillems.com>
Sat, 5 Oct 2024 11:57:04 +0000 (13:57 +0200)
committerJiri Kosina <jkosina@suse.com>
Fri, 18 Oct 2024 10:28:40 +0000 (12:28 +0200)
In this initial the battery is only probed once, a following patch will add
periodic checking.

Signed-off-by: Lode Willems <me@lodewillems.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/hid-kysona.c [new file with mode: 0644]

index f8a56d6312425a2e4f2c466cfed43d2affdbea59..d05e851bc87c13462f0b2f79e2de0a9c08e1f605 100644 (file)
@@ -465,6 +465,15 @@ config HID_KYE
        - MousePen i608X tablet
        - EasyPen M610X tablet
 
+config HID_KYSONA
+       tristate "Kysona devices"
+       depends on USB_HID
+       help
+       Support for Kysona mice.
+
+       Say Y here if you have a Kysona M600 mouse
+       and want to be able to read its battery capacity.
+
 config HID_UCLOGIC
        tristate "UC-Logic"
        depends on USB_HID
index 496dab54c73a826489da590e458e56e7c5d73be2..51cf887f54bdf4eba1e67346d588c1b15e2404b0 100644 (file)
@@ -70,6 +70,7 @@ obj-$(CONFIG_HID_JABRA)               += hid-jabra.o
 obj-$(CONFIG_HID_KENSINGTON)   += hid-kensington.o
 obj-$(CONFIG_HID_KEYTOUCH)     += hid-keytouch.o
 obj-$(CONFIG_HID_KYE)          += hid-kye.o
+obj-$(CONFIG_HID_KYSONA)       += hid-kysona.o
 obj-$(CONFIG_HID_LCPOWER)      += hid-lcpower.o
 obj-$(CONFIG_HID_LENOVO)       += hid-lenovo.o
 obj-$(CONFIG_HID_LETSKETCH)    += hid-letsketch.o
diff --git a/drivers/hid/hid-kysona.c b/drivers/hid/hid-kysona.c
new file mode 100644 (file)
index 0000000..91e359c
--- /dev/null
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  USB HID driver for Kysona
+ *  Kysona M600 mice.
+ *
+ *  Copyright (c) 2024 Lode Willems <me@lodewillems.com>
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+
+#define BATTERY_REPORT_ID 4
+
+struct kysona_drvdata {
+       struct hid_device *hdev;
+       struct power_supply_desc battery_desc;
+       struct power_supply *battery;
+       u8 battery_capacity;
+       bool battery_charging;
+       u16 battery_voltage;
+};
+
+static enum power_supply_property kysona_battery_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_SCOPE,
+       POWER_SUPPLY_PROP_MODEL_NAME,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW
+};
+
+static int kysona_battery_get_property(struct power_supply *psy,
+                                      enum power_supply_property psp,
+                                      union power_supply_propval *val)
+{
+       struct kysona_drvdata *drv_data = power_supply_get_drvdata(psy);
+       int ret = 0;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = 1;
+               break;
+       case POWER_SUPPLY_PROP_STATUS:
+               // TODO: check if device is online
+               val->intval = drv_data->battery_charging ?
+                       POWER_SUPPLY_STATUS_CHARGING :
+                       POWER_SUPPLY_STATUS_DISCHARGING;
+               break;
+       case POWER_SUPPLY_PROP_SCOPE:
+               val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               val->intval = drv_data->battery_capacity;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               /* hardware reports voltage in mV. sysfs expects uV */
+               val->intval = drv_data->battery_voltage * 1000;
+               break;
+       case POWER_SUPPLY_PROP_MODEL_NAME:
+               val->strval = drv_data->hdev->name;
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static const char kysona_battery_request[] = {
+       0x08, BATTERY_REPORT_ID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49
+};
+
+static int kysona_m600_fetch_battery(struct hid_device *hdev)
+{
+       u8 *write_buf;
+       int ret;
+
+       /* Request battery information */
+       write_buf = kmemdup(kysona_battery_request, sizeof(kysona_battery_request), GFP_KERNEL);
+       if (!write_buf)
+               return -ENOMEM;
+
+       ret = hid_hw_raw_request(hdev, kysona_battery_request[0],
+                                write_buf, sizeof(kysona_battery_request),
+                                HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+       if (ret < (int)sizeof(kysona_battery_request)) {
+               hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
+               ret = -ENODATA;
+       }
+       kfree(write_buf);
+       return ret;
+}
+
+static void kysona_fetch_battery(struct hid_device *hdev)
+{
+       int ret = kysona_m600_fetch_battery(hdev);
+
+       if (ret < 0)
+               hid_dbg(hdev,
+                       "Battery query failed (err: %d)\n", ret);
+}
+
+static int kysona_battery_probe(struct hid_device *hdev)
+{
+       struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
+       struct power_supply_config pscfg = { .drv_data = drv_data };
+       int ret = 0;
+
+       drv_data->battery_capacity = 100;
+
+       drv_data->battery_desc.properties = kysona_battery_props;
+       drv_data->battery_desc.num_properties = ARRAY_SIZE(kysona_battery_props);
+       drv_data->battery_desc.get_property = kysona_battery_get_property;
+       drv_data->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+       drv_data->battery_desc.use_for_apm = 0;
+       drv_data->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+                                                    "kysona-%s-battery",
+                                                    strlen(hdev->uniq) ?
+                                                    hdev->uniq : dev_name(&hdev->dev));
+       if (!drv_data->battery_desc.name)
+               return -ENOMEM;
+
+       drv_data->battery = devm_power_supply_register(&hdev->dev,
+                                                      &drv_data->battery_desc, &pscfg);
+       if (IS_ERR(drv_data->battery)) {
+               ret = PTR_ERR(drv_data->battery);
+               drv_data->battery = NULL;
+               hid_err(hdev, "Unable to register battery device\n");
+               return ret;
+       }
+
+       power_supply_powers(drv_data->battery, &hdev->dev);
+       kysona_fetch_battery(hdev);
+
+       return ret;
+}
+
+static int kysona_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+       int ret;
+       struct kysona_drvdata *drv_data;
+       struct usb_interface *usbif;
+
+       if (!hid_is_usb(hdev))
+               return -EINVAL;
+
+       usbif = to_usb_interface(hdev->dev.parent);
+
+       drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
+       if (!drv_data)
+               return -ENOMEM;
+
+       hid_set_drvdata(hdev, drv_data);
+       drv_data->hdev = hdev;
+
+       ret = hid_parse(hdev);
+       if (ret)
+               return ret;
+
+       ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+       if (ret)
+               return ret;
+
+       if (usbif->cur_altsetting->desc.bInterfaceNumber == 1) {
+               if (kysona_battery_probe(hdev) < 0)
+                       hid_err(hdev, "Kysona hid battery_probe failed: %d\n", ret);
+       }
+
+       return 0;
+}
+
+static int kysona_raw_event(struct hid_device *hdev,
+                           struct hid_report *report, u8 *data, int size)
+{
+       struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
+
+       if (drv_data->battery && size == sizeof(kysona_battery_request) &&
+           data[0] == 8 && data[1] == BATTERY_REPORT_ID) {
+               drv_data->battery_capacity = data[6];
+               drv_data->battery_charging = data[7];
+               drv_data->battery_voltage = (data[8] << 8) | data[9];
+       }
+
+       return 0;
+}
+
+static const struct hid_device_id kysona_devices[] = {
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED) },
+       { }
+};
+MODULE_DEVICE_TABLE(hid, kysona_devices);
+
+static struct hid_driver kysona_driver = {
+       .name                   = "kysona",
+       .id_table               = kysona_devices,
+       .probe                  = kysona_probe,
+       .raw_event              = kysona_raw_event
+};
+module_hid_driver(kysona_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HID driver for Kysona devices");
+MODULE_AUTHOR("Lode Willems <me@lodewillems.com>");