HID: Kysona: Add basic battery reporting for Kysona M600

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>
This commit is contained in:
Lode Willems 2024-10-05 13:57:04 +02:00 committed by Jiri Kosina
parent ff39b0bbc2
commit 30c32d0529
3 changed files with 218 additions and 0 deletions

View 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

View 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

208
drivers/hid/hid-kysona.c Normal file
View File

@ -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>");