Commit 0549fbac authored by Jiri Kosina's avatar Jiri Kosina
Browse files

Merge branch 'for-6.4/logitech-hidpp' into for-linus

- support for ADC measurement (Bastien Nocera)
- support for Logitech G935 (Bastien Nocera)
parents d411b5aa 539adfed
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -166,6 +166,23 @@ Description:
		The file will be present for all speeds of USB devices, and will
		always read "no" for USB 1.1 and USB 2.0 devices.

What:		/sys/bus/usb/devices/<INTERFACE>/wireless_status
Date:		February 2023
Contact:	Bastien Nocera <hadess@hadess.net>
Description:
		Some USB devices use a USB receiver dongle to communicate
		wirelessly with their device using proprietary protocols. This
		attribute allows user-space to know whether the device is
		connected to its receiver dongle, and, for example, consider
		the device to be absent when choosing whether to show the
		device's battery, show a headset in a list of outputs, or show
		an on-screen keyboard if the only wireless keyboard is
		turned off.
		This attribute is not to be used to replace protocol specific
		statuses available in WWAN, WLAN/Wi-Fi, Bluetooth, etc.
		If the device does not use a receiver dongle with a wireless
		device, then this attribute will not exist.

What:		/sys/bus/usb/devices/.../<hub_interface>/port<X>
Date:		August 2012
Contact:	Lan Tianyu <tianyu.lan@intel.com>
+249 −7
Original line number Diff line number Diff line
@@ -74,6 +74,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS	BIT(27)
#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS	BIT(28)
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0		BIT(29)
#define HIDPP_QUIRK_WIRELESS_STATUS		BIT(30)

/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -94,6 +95,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL	BIT(7)
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL	BIT(8)
#define HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL	BIT(9)
#define HIDPP_CAPABILITY_ADC_MEASUREMENT	BIT(10)

#define lg_map_key_clear(c)  hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))

@@ -145,6 +147,7 @@ struct hidpp_battery {
	u8 feature_index;
	u8 solar_feature_index;
	u8 voltage_feature_index;
	u8 adc_measurement_feature_index;
	struct power_supply_desc desc;
	struct power_supply *ps;
	char name[64];
@@ -471,6 +474,26 @@ static void hidpp_prefix_name(char **name, int name_length)
	*name = new_name;
}

/*
 * Updates the USB wireless_status based on whether the headset
 * is turned on and reachable.
 */
static void hidpp_update_usb_wireless_status(struct hidpp_device *hidpp)
{
	struct hid_device *hdev = hidpp->hid_dev;
	struct usb_interface *intf;

	if (!(hidpp->quirks & HIDPP_QUIRK_WIRELESS_STATUS))
		return;
	if (!hid_is_usb(hdev))
		return;

	intf = to_usb_interface(hdev->dev.parent);
	usb_set_wireless_status(intf, hidpp->battery.online ?
				USB_WIRELESS_STATUS_CONNECTED :
				USB_WIRELESS_STATUS_DISCONNECTED);
}

/**
 * hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll
 *                                        events given a high-resolution wheel
@@ -853,8 +876,7 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp)
	if (ret)
		return ret;

	snprintf(hdev->uniq, sizeof(hdev->uniq), "%04x-%4phD",
		 hdev->product, &serial);
	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
	dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq);

	name = hidpp_unifying_get_name(hidpp);
@@ -947,6 +969,54 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
	return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x0003: Device Information                                                 */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_DEVICE_INFORMATION			0x0003

#define CMD_GET_DEVICE_INFO				0x00

static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
{
	struct hidpp_report response;
	u8 feature_type;
	u8 feature_index;
	int ret;

	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION,
				     &feature_index,
				     &feature_type);
	if (ret)
		return ret;

	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
					  CMD_GET_DEVICE_INFO,
					  NULL, 0, &response);
	if (ret)
		return ret;

	/* See hidpp_unifying_get_serial() */
	*serial = *((u32 *)&response.rap.params[1]);
	return 0;
}

static int hidpp_serial_init(struct hidpp_device *hidpp)
{
	struct hid_device *hdev = hidpp->hid_dev;
	u32 serial;
	int ret;

	ret = hidpp_get_serial(hidpp, &serial);
	if (ret)
		return ret;

	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
	dbg_hid("HID++ DeviceInformation: Got serial: %s\n", hdev->uniq);

	return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x0005: GetDeviceNameType                                                  */
/* -------------------------------------------------------------------------- */
@@ -1357,7 +1427,7 @@ static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
	 * there are a few devices that use different battery technology.
	 */

	static const int voltages[] = {
	static const int voltages[100] = {
		4186, 4156, 4143, 4133, 4122, 4113, 4103, 4094, 4086, 4075,
		4067, 4059, 4051, 4043, 4035, 4027, 4019, 4011, 4003, 3997,
		3989, 3983, 3976, 3969, 3961, 3955, 3949, 3942, 3935, 3929,
@@ -1372,8 +1442,6 @@ static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)

	int i;

	BUILD_BUG_ON(ARRAY_SIZE(voltages) != 100);

	if (unlikely(voltage < 3500 || voltage >= 5000))
		hid_warn_once(hid_dev,
			      "%s: possibly using the wrong voltage curve\n",
@@ -1745,6 +1813,164 @@ static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp)
	return ret;
}

/* -------------------------------------------------------------------------- */
/* 0x1f20: ADC measurement                                                    */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_ADC_MEASUREMENT 0x1f20

#define CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT 0x00

#define EVENT_ADC_MEASUREMENT_STATUS_BROADCAST 0x00

static int hidpp20_map_adc_measurement_1f20_capacity(struct hid_device *hid_dev, int voltage)
{
	/* NB: This voltage curve doesn't necessarily map perfectly to all
	 * devices that implement the ADC_MEASUREMENT feature. This is because
	 * there are a few devices that use different battery technology.
	 *
	 * Adapted from:
	 * https://github.com/Sapd/HeadsetControl/blob/acd972be0468e039b93aae81221f20a54d2d60f7/src/devices/logitech_g633_g933_935.c#L44-L52
	 */
	static const int voltages[100] = {
		4030, 4024, 4018, 4011, 4003, 3994, 3985, 3975, 3963, 3951,
		3937, 3922, 3907, 3893, 3880, 3868, 3857, 3846, 3837, 3828,
		3820, 3812, 3805, 3798, 3791, 3785, 3779, 3773, 3768, 3762,
		3757, 3752, 3747, 3742, 3738, 3733, 3729, 3724, 3720, 3716,
		3712, 3708, 3704, 3700, 3696, 3692, 3688, 3685, 3681, 3677,
		3674, 3670, 3667, 3663, 3660, 3657, 3653, 3650, 3646, 3643,
		3640, 3637, 3633, 3630, 3627, 3624, 3620, 3617, 3614, 3611,
		3608, 3604, 3601, 3598, 3595, 3592, 3589, 3585, 3582, 3579,
		3576, 3573, 3569, 3566, 3563, 3560, 3556, 3553, 3550, 3546,
		3543, 3539, 3536, 3532, 3529, 3525, 3499, 3466, 3433, 3399,
	};

	int i;

	if (voltage == 0)
		return 0;

	if (unlikely(voltage < 3400 || voltage >= 5000))
		hid_warn_once(hid_dev,
			      "%s: possibly using the wrong voltage curve\n",
			      __func__);

	for (i = 0; i < ARRAY_SIZE(voltages); i++) {
		if (voltage >= voltages[i])
			return ARRAY_SIZE(voltages) - i;
	}

	return 0;
}

static int hidpp20_map_adc_measurement_1f20(u8 data[3], int *voltage)
{
	int status;
	u8 flags;

	flags = data[2];

	switch (flags) {
	case 0x01:
		status = POWER_SUPPLY_STATUS_DISCHARGING;
		break;
	case 0x03:
		status = POWER_SUPPLY_STATUS_CHARGING;
		break;
	case 0x07:
		status = POWER_SUPPLY_STATUS_FULL;
		break;
	case 0x0F:
	default:
		status = POWER_SUPPLY_STATUS_UNKNOWN;
		break;
	}

	*voltage = get_unaligned_be16(data);

	dbg_hid("Parsed 1f20 data as flag 0x%02x voltage %dmV\n",
		flags, *voltage);

	return status;
}

/* Return value is whether the device is online */
static bool hidpp20_get_adc_measurement_1f20(struct hidpp_device *hidpp,
						 u8 feature_index,
						 int *status, int *voltage)
{
	struct hidpp_report response;
	int ret;
	u8 *params = (u8 *)response.fap.params;

	*status = POWER_SUPPLY_STATUS_UNKNOWN;
	*voltage = 0;
	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
					  CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT,
					  NULL, 0, &response);

	if (ret > 0) {
		hid_dbg(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
			__func__, ret);
		return false;
	}

	*status = hidpp20_map_adc_measurement_1f20(params, voltage);
	return true;
}

static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
{
	u8 feature_type;

	if (hidpp->battery.adc_measurement_feature_index == 0xff) {
		int ret;

		ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
					     &hidpp->battery.adc_measurement_feature_index,
					     &feature_type);
		if (ret)
			return ret;

		hidpp->capabilities |= HIDPP_CAPABILITY_ADC_MEASUREMENT;
	}

	hidpp->battery.online = hidpp20_get_adc_measurement_1f20(hidpp,
								 hidpp->battery.adc_measurement_feature_index,
								 &hidpp->battery.status,
								 &hidpp->battery.voltage);
	hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev,
									    hidpp->battery.voltage);
	hidpp_update_usb_wireless_status(hidpp);

	return 0;
}

static int hidpp20_adc_measurement_event_1f20(struct hidpp_device *hidpp,
					    u8 *data, int size)
{
	struct hidpp_report *report = (struct hidpp_report *)data;
	int status, voltage;

	if (report->fap.feature_index != hidpp->battery.adc_measurement_feature_index ||
		report->fap.funcindex_clientid != EVENT_ADC_MEASUREMENT_STATUS_BROADCAST)
		return 0;

	status = hidpp20_map_adc_measurement_1f20(report->fap.params, &voltage);

	hidpp->battery.online = status != POWER_SUPPLY_STATUS_UNKNOWN;

	if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) {
		hidpp->battery.status = status;
		hidpp->battery.voltage = voltage;
		hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev, voltage);
		if (hidpp->battery.ps)
			power_supply_changed(hidpp->battery.ps);
		hidpp_update_usb_wireless_status(hidpp);
	}
	return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x2120: Hi-resolution scrolling                                            */
/* -------------------------------------------------------------------------- */
@@ -3663,6 +3889,9 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
		ret = hidpp20_battery_voltage_event(hidpp, data, size);
		if (ret != 0)
			return ret;
		ret = hidpp20_adc_measurement_event_1f20(hidpp, data, size);
		if (ret != 0)
			return ret;
	}

	if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
@@ -3786,6 +4015,7 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
	hidpp->battery.feature_index = 0xff;
	hidpp->battery.solar_feature_index = 0xff;
	hidpp->battery.voltage_feature_index = 0xff;
	hidpp->battery.adc_measurement_feature_index = 0xff;

	if (hidpp->protocol_major >= 2) {
		if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
@@ -3799,6 +4029,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
				ret = hidpp20_query_battery_info_1004(hidpp);
			if (ret)
				ret = hidpp20_query_battery_voltage_info(hidpp);
			if (ret)
				ret = hidpp20_query_adc_measurement_info_1f20(hidpp);
		}

		if (ret)
@@ -3828,7 +4060,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)

	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE ||
	    hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE ||
	    hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
	    hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
	    hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
		battery_props[num_battery_props++] =
				POWER_SUPPLY_PROP_CAPACITY;

@@ -3836,7 +4069,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
		battery_props[num_battery_props++] =
				POWER_SUPPLY_PROP_CAPACITY_LEVEL;

	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
	    hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
		battery_props[num_battery_props++] =
			POWER_SUPPLY_PROP_VOLTAGE_NOW;

@@ -4009,6 +4243,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
			hidpp20_query_battery_voltage_info(hidpp);
		else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY)
			hidpp20_query_battery_info_1004(hidpp);
		else if (hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
			hidpp20_query_adc_measurement_info_1f20(hidpp);
		else
			hidpp20_query_battery_info_1000(hidpp);
	}
@@ -4210,6 +4446,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)

	if (hidpp->quirks & HIDPP_QUIRK_UNIFYING)
		hidpp_unifying_init(hidpp);
	else if (hid_is_usb(hidpp->hid_dev))
		hidpp_serial_init(hidpp);

	connected = hidpp_root_get_protocol_version(hidpp) == 0;
	atomic_set(&hidpp->connected, connected);
@@ -4379,6 +4617,10 @@ static const struct hid_device_id hidpp_devices[] = {
	{ /* Logitech G Pro Gaming Mouse over USB */
	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },

	{ /* G935 Gaming Headset */
	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87),
		.driver_data = HIDPP_QUIRK_WIRELESS_STATUS },

	{ /* MX5000 keyboard over Bluetooth */
	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305),
	  .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
+40 −0
Original line number Diff line number Diff line
@@ -1908,6 +1908,45 @@ static void __usb_queue_reset_device(struct work_struct *ws)
	usb_put_intf(iface);	/* Undo _get_ in usb_queue_reset_device() */
}

/*
 * Internal function to set the wireless_status sysfs attribute
 * See usb_set_wireless_status() for more details
 */
static void __usb_wireless_status_intf(struct work_struct *ws)
{
	struct usb_interface *iface =
		container_of(ws, struct usb_interface, wireless_status_work);

	device_lock(iface->dev.parent);
	if (iface->sysfs_files_created)
		usb_update_wireless_status_attr(iface);
	device_unlock(iface->dev.parent);
	usb_put_intf(iface);	/* Undo _get_ in usb_set_wireless_status() */
}

/**
 * usb_set_wireless_status - sets the wireless_status struct member
 * @iface: the interface to modify
 * @status: the new wireless status
 *
 * Set the wireless_status struct member to the new value, and emit
 * sysfs changes as necessary.
 *
 * Returns: 0 on success, -EALREADY if already set.
 */
int usb_set_wireless_status(struct usb_interface *iface,
		enum usb_wireless_status status)
{
	if (iface->wireless_status == status)
		return -EALREADY;

	usb_get_intf(iface);
	iface->wireless_status = status;
	schedule_work(&iface->wireless_status_work);

	return 0;
}
EXPORT_SYMBOL_GPL(usb_set_wireless_status);

/*
 * usb_set_configuration - Makes a particular device setting be current
@@ -2100,6 +2139,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
		intf->dev.type = &usb_if_device_type;
		intf->dev.groups = usb_interface_groups;
		INIT_WORK(&intf->reset_ws, __usb_queue_reset_device);
		INIT_WORK(&intf->wireless_status_work, __usb_wireless_status_intf);
		intf->minor = -1;
		device_initialize(&intf->dev);
		pm_runtime_no_callbacks(&intf->dev);
+50 −0
Original line number Diff line number Diff line
@@ -1227,9 +1227,59 @@ static const struct attribute_group intf_assoc_attr_grp = {
	.is_visible =	intf_assoc_attrs_are_visible,
};

static ssize_t wireless_status_show(struct device *dev,
				    struct device_attribute *attr, char *buf)
{
	struct usb_interface *intf;

	intf = to_usb_interface(dev);
	if (intf->wireless_status == USB_WIRELESS_STATUS_DISCONNECTED)
		return sysfs_emit(buf, "%s\n", "disconnected");
	return sysfs_emit(buf, "%s\n", "connected");
}
static DEVICE_ATTR_RO(wireless_status);

static struct attribute *intf_wireless_status_attrs[] = {
	&dev_attr_wireless_status.attr,
	NULL
};

static umode_t intf_wireless_status_attr_is_visible(struct kobject *kobj,
		struct attribute *a, int n)
{
	struct device *dev = kobj_to_dev(kobj);
	struct usb_interface *intf = to_usb_interface(dev);

	if (a != &dev_attr_wireless_status.attr ||
	    intf->wireless_status != USB_WIRELESS_STATUS_NA)
		return a->mode;
	return 0;
}

static const struct attribute_group intf_wireless_status_attr_grp = {
	.attrs =	intf_wireless_status_attrs,
	.is_visible =	intf_wireless_status_attr_is_visible,
};

int usb_update_wireless_status_attr(struct usb_interface *intf)
{
	struct device *dev = &intf->dev;
	int ret;

	ret = sysfs_update_group(&dev->kobj, &intf_wireless_status_attr_grp);
	if (ret < 0)
		return ret;

	sysfs_notify(&dev->kobj, NULL, "wireless_status");
	kobject_uevent(&dev->kobj, KOBJ_CHANGE);

	return 0;
}

const struct attribute_group *usb_interface_groups[] = {
	&intf_attr_grp,
	&intf_assoc_attr_grp,
	&intf_wireless_status_attr_grp,
	NULL
};

+1 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ extern int usb_create_sysfs_dev_files(struct usb_device *dev);
extern void usb_remove_sysfs_dev_files(struct usb_device *dev);
extern void usb_create_sysfs_intf_files(struct usb_interface *intf);
extern void usb_remove_sysfs_intf_files(struct usb_interface *intf);
extern int usb_update_wireless_status_attr(struct usb_interface *intf);
extern int usb_create_ep_devs(struct device *parent,
				struct usb_host_endpoint *endpoint,
				struct usb_device *udev);
Loading