162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  HIDPP protocol for Logitech receivers
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (c) 2011 Logitech (c)
662306a36Sopenharmony_ci *  Copyright (c) 2012-2013 Google (c)
762306a36Sopenharmony_ci *  Copyright (c) 2013-2014 Red Hat Inc.
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/device.h>
1462306a36Sopenharmony_ci#include <linux/input.h>
1562306a36Sopenharmony_ci#include <linux/usb.h>
1662306a36Sopenharmony_ci#include <linux/hid.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci#include <linux/sched.h>
2062306a36Sopenharmony_ci#include <linux/sched/clock.h>
2162306a36Sopenharmony_ci#include <linux/kfifo.h>
2262306a36Sopenharmony_ci#include <linux/input/mt.h>
2362306a36Sopenharmony_ci#include <linux/workqueue.h>
2462306a36Sopenharmony_ci#include <linux/atomic.h>
2562306a36Sopenharmony_ci#include <linux/fixp-arith.h>
2662306a36Sopenharmony_ci#include <asm/unaligned.h>
2762306a36Sopenharmony_ci#include "usbhid/usbhid.h"
2862306a36Sopenharmony_ci#include "hid-ids.h"
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
3162306a36Sopenharmony_ciMODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
3262306a36Sopenharmony_ciMODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
3362306a36Sopenharmony_ciMODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic bool disable_tap_to_click;
3662306a36Sopenharmony_cimodule_param(disable_tap_to_click, bool, 0644);
3762306a36Sopenharmony_ciMODULE_PARM_DESC(disable_tap_to_click,
3862306a36Sopenharmony_ci	"Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently).");
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/* Define a non-zero software ID to identify our own requests */
4162306a36Sopenharmony_ci#define LINUX_KERNEL_SW_ID			0x01
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci#define REPORT_ID_HIDPP_SHORT			0x10
4462306a36Sopenharmony_ci#define REPORT_ID_HIDPP_LONG			0x11
4562306a36Sopenharmony_ci#define REPORT_ID_HIDPP_VERY_LONG		0x12
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define HIDPP_REPORT_SHORT_LENGTH		7
4862306a36Sopenharmony_ci#define HIDPP_REPORT_LONG_LENGTH		20
4962306a36Sopenharmony_ci#define HIDPP_REPORT_VERY_LONG_MAX_LENGTH	64
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci#define HIDPP_REPORT_SHORT_SUPPORTED		BIT(0)
5262306a36Sopenharmony_ci#define HIDPP_REPORT_LONG_SUPPORTED		BIT(1)
5362306a36Sopenharmony_ci#define HIDPP_REPORT_VERY_LONG_SUPPORTED	BIT(2)
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci#define HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS	0x03
5662306a36Sopenharmony_ci#define HIDPP_SUB_ID_ROLLER			0x05
5762306a36Sopenharmony_ci#define HIDPP_SUB_ID_MOUSE_EXTRA_BTNS		0x06
5862306a36Sopenharmony_ci#define HIDPP_SUB_ID_USER_IFACE_EVENT		0x08
5962306a36Sopenharmony_ci#define HIDPP_USER_IFACE_EVENT_ENCRYPTION_KEY_LOST	BIT(5)
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci#define HIDPP_QUIRK_CLASS_WTP			BIT(0)
6262306a36Sopenharmony_ci#define HIDPP_QUIRK_CLASS_M560			BIT(1)
6362306a36Sopenharmony_ci#define HIDPP_QUIRK_CLASS_K400			BIT(2)
6462306a36Sopenharmony_ci#define HIDPP_QUIRK_CLASS_G920			BIT(3)
6562306a36Sopenharmony_ci#define HIDPP_QUIRK_CLASS_K750			BIT(4)
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/* bits 2..20 are reserved for classes */
6862306a36Sopenharmony_ci/* #define HIDPP_QUIRK_CONNECT_EVENTS		BIT(21) disabled */
6962306a36Sopenharmony_ci#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS	BIT(22)
7062306a36Sopenharmony_ci#define HIDPP_QUIRK_DELAYED_INIT		BIT(23)
7162306a36Sopenharmony_ci#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS	BIT(24)
7262306a36Sopenharmony_ci#define HIDPP_QUIRK_UNIFYING			BIT(25)
7362306a36Sopenharmony_ci#define HIDPP_QUIRK_HIDPP_WHEELS		BIT(26)
7462306a36Sopenharmony_ci#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS	BIT(27)
7562306a36Sopenharmony_ci#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS	BIT(28)
7662306a36Sopenharmony_ci#define HIDPP_QUIRK_HI_RES_SCROLL_1P0		BIT(29)
7762306a36Sopenharmony_ci#define HIDPP_QUIRK_WIRELESS_STATUS		BIT(30)
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci/* These are just aliases for now */
8062306a36Sopenharmony_ci#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
8162306a36Sopenharmony_ci#define HIDPP_QUIRK_KBD_ZOOM_WHEEL   HIDPP_QUIRK_HIDPP_WHEELS
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci/* Convenience constant to check for any high-res support. */
8462306a36Sopenharmony_ci#define HIDPP_CAPABILITY_HI_RES_SCROLL	(HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL | \
8562306a36Sopenharmony_ci					 HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL | \
8662306a36Sopenharmony_ci					 HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL)
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci#define HIDPP_CAPABILITY_HIDPP10_BATTERY	BIT(0)
8962306a36Sopenharmony_ci#define HIDPP_CAPABILITY_HIDPP20_BATTERY	BIT(1)
9062306a36Sopenharmony_ci#define HIDPP_CAPABILITY_BATTERY_MILEAGE	BIT(2)
9162306a36Sopenharmony_ci#define HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS	BIT(3)
9262306a36Sopenharmony_ci#define HIDPP_CAPABILITY_BATTERY_VOLTAGE	BIT(4)
9362306a36Sopenharmony_ci#define HIDPP_CAPABILITY_BATTERY_PERCENTAGE	BIT(5)
9462306a36Sopenharmony_ci#define HIDPP_CAPABILITY_UNIFIED_BATTERY	BIT(6)
9562306a36Sopenharmony_ci#define HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL	BIT(7)
9662306a36Sopenharmony_ci#define HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL	BIT(8)
9762306a36Sopenharmony_ci#define HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL	BIT(9)
9862306a36Sopenharmony_ci#define HIDPP_CAPABILITY_ADC_MEASUREMENT	BIT(10)
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci#define lg_map_key_clear(c)  hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci/*
10362306a36Sopenharmony_ci * There are two hidpp protocols in use, the first version hidpp10 is known
10462306a36Sopenharmony_ci * as register access protocol or RAP, the second version hidpp20 is known as
10562306a36Sopenharmony_ci * feature access protocol or FAP
10662306a36Sopenharmony_ci *
10762306a36Sopenharmony_ci * Most older devices (including the Unifying usb receiver) use the RAP protocol
10862306a36Sopenharmony_ci * where as most newer devices use the FAP protocol. Both protocols are
10962306a36Sopenharmony_ci * compatible with the underlying transport, which could be usb, Unifiying, or
11062306a36Sopenharmony_ci * bluetooth. The message lengths are defined by the hid vendor specific report
11162306a36Sopenharmony_ci * descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and
11262306a36Sopenharmony_ci * the HIDPP_LONG report type (total message length 20 bytes)
11362306a36Sopenharmony_ci *
11462306a36Sopenharmony_ci * The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG
11562306a36Sopenharmony_ci * messages. The Unifying receiver itself responds to RAP messages (device index
11662306a36Sopenharmony_ci * is 0xFF for the receiver), and all messages (short or long) with a device
11762306a36Sopenharmony_ci * index between 1 and 6 are passed untouched to the corresponding paired
11862306a36Sopenharmony_ci * Unifying device.
11962306a36Sopenharmony_ci *
12062306a36Sopenharmony_ci * The paired device can be RAP or FAP, it will receive the message untouched
12162306a36Sopenharmony_ci * from the Unifiying receiver.
12262306a36Sopenharmony_ci */
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistruct fap {
12562306a36Sopenharmony_ci	u8 feature_index;
12662306a36Sopenharmony_ci	u8 funcindex_clientid;
12762306a36Sopenharmony_ci	u8 params[HIDPP_REPORT_VERY_LONG_MAX_LENGTH - 4U];
12862306a36Sopenharmony_ci};
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistruct rap {
13162306a36Sopenharmony_ci	u8 sub_id;
13262306a36Sopenharmony_ci	u8 reg_address;
13362306a36Sopenharmony_ci	u8 params[HIDPP_REPORT_VERY_LONG_MAX_LENGTH - 4U];
13462306a36Sopenharmony_ci};
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistruct hidpp_report {
13762306a36Sopenharmony_ci	u8 report_id;
13862306a36Sopenharmony_ci	u8 device_index;
13962306a36Sopenharmony_ci	union {
14062306a36Sopenharmony_ci		struct fap fap;
14162306a36Sopenharmony_ci		struct rap rap;
14262306a36Sopenharmony_ci		u8 rawbytes[sizeof(struct fap)];
14362306a36Sopenharmony_ci	};
14462306a36Sopenharmony_ci} __packed;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistruct hidpp_battery {
14762306a36Sopenharmony_ci	u8 feature_index;
14862306a36Sopenharmony_ci	u8 solar_feature_index;
14962306a36Sopenharmony_ci	u8 voltage_feature_index;
15062306a36Sopenharmony_ci	u8 adc_measurement_feature_index;
15162306a36Sopenharmony_ci	struct power_supply_desc desc;
15262306a36Sopenharmony_ci	struct power_supply *ps;
15362306a36Sopenharmony_ci	char name[64];
15462306a36Sopenharmony_ci	int status;
15562306a36Sopenharmony_ci	int capacity;
15662306a36Sopenharmony_ci	int level;
15762306a36Sopenharmony_ci	int voltage;
15862306a36Sopenharmony_ci	int charge_type;
15962306a36Sopenharmony_ci	bool online;
16062306a36Sopenharmony_ci	u8 supported_levels_1004;
16162306a36Sopenharmony_ci};
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci/**
16462306a36Sopenharmony_ci * struct hidpp_scroll_counter - Utility class for processing high-resolution
16562306a36Sopenharmony_ci *                             scroll events.
16662306a36Sopenharmony_ci * @dev: the input device for which events should be reported.
16762306a36Sopenharmony_ci * @wheel_multiplier: the scalar multiplier to be applied to each wheel event
16862306a36Sopenharmony_ci * @remainder: counts the number of high-resolution units moved since the last
16962306a36Sopenharmony_ci *             low-resolution event (REL_WHEEL or REL_HWHEEL) was sent. Should
17062306a36Sopenharmony_ci *             only be used by class methods.
17162306a36Sopenharmony_ci * @direction: direction of last movement (1 or -1)
17262306a36Sopenharmony_ci * @last_time: last event time, used to reset remainder after inactivity
17362306a36Sopenharmony_ci */
17462306a36Sopenharmony_cistruct hidpp_scroll_counter {
17562306a36Sopenharmony_ci	int wheel_multiplier;
17662306a36Sopenharmony_ci	int remainder;
17762306a36Sopenharmony_ci	int direction;
17862306a36Sopenharmony_ci	unsigned long long last_time;
17962306a36Sopenharmony_ci};
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistruct hidpp_device {
18262306a36Sopenharmony_ci	struct hid_device *hid_dev;
18362306a36Sopenharmony_ci	struct input_dev *input;
18462306a36Sopenharmony_ci	struct mutex send_mutex;
18562306a36Sopenharmony_ci	void *send_receive_buf;
18662306a36Sopenharmony_ci	char *name;		/* will never be NULL and should not be freed */
18762306a36Sopenharmony_ci	wait_queue_head_t wait;
18862306a36Sopenharmony_ci	int very_long_report_length;
18962306a36Sopenharmony_ci	bool answer_available;
19062306a36Sopenharmony_ci	u8 protocol_major;
19162306a36Sopenharmony_ci	u8 protocol_minor;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	void *private_data;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	struct work_struct work;
19662306a36Sopenharmony_ci	struct kfifo delayed_work_fifo;
19762306a36Sopenharmony_ci	atomic_t connected;
19862306a36Sopenharmony_ci	struct input_dev *delayed_input;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	unsigned long quirks;
20162306a36Sopenharmony_ci	unsigned long capabilities;
20262306a36Sopenharmony_ci	u8 supported_reports;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	struct hidpp_battery battery;
20562306a36Sopenharmony_ci	struct hidpp_scroll_counter vertical_wheel_counter;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	u8 wireless_feature_index;
20862306a36Sopenharmony_ci};
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci/* HID++ 1.0 error codes */
21162306a36Sopenharmony_ci#define HIDPP_ERROR				0x8f
21262306a36Sopenharmony_ci#define HIDPP_ERROR_SUCCESS			0x00
21362306a36Sopenharmony_ci#define HIDPP_ERROR_INVALID_SUBID		0x01
21462306a36Sopenharmony_ci#define HIDPP_ERROR_INVALID_ADRESS		0x02
21562306a36Sopenharmony_ci#define HIDPP_ERROR_INVALID_VALUE		0x03
21662306a36Sopenharmony_ci#define HIDPP_ERROR_CONNECT_FAIL		0x04
21762306a36Sopenharmony_ci#define HIDPP_ERROR_TOO_MANY_DEVICES		0x05
21862306a36Sopenharmony_ci#define HIDPP_ERROR_ALREADY_EXISTS		0x06
21962306a36Sopenharmony_ci#define HIDPP_ERROR_BUSY			0x07
22062306a36Sopenharmony_ci#define HIDPP_ERROR_UNKNOWN_DEVICE		0x08
22162306a36Sopenharmony_ci#define HIDPP_ERROR_RESOURCE_ERROR		0x09
22262306a36Sopenharmony_ci#define HIDPP_ERROR_REQUEST_UNAVAILABLE		0x0a
22362306a36Sopenharmony_ci#define HIDPP_ERROR_INVALID_PARAM_VALUE		0x0b
22462306a36Sopenharmony_ci#define HIDPP_ERROR_WRONG_PIN_CODE		0x0c
22562306a36Sopenharmony_ci/* HID++ 2.0 error codes */
22662306a36Sopenharmony_ci#define HIDPP20_ERROR_NO_ERROR			0x00
22762306a36Sopenharmony_ci#define HIDPP20_ERROR_UNKNOWN			0x01
22862306a36Sopenharmony_ci#define HIDPP20_ERROR_INVALID_ARGS		0x02
22962306a36Sopenharmony_ci#define HIDPP20_ERROR_OUT_OF_RANGE		0x03
23062306a36Sopenharmony_ci#define HIDPP20_ERROR_HW_ERROR			0x04
23162306a36Sopenharmony_ci#define HIDPP20_ERROR_NOT_ALLOWED		0x05
23262306a36Sopenharmony_ci#define HIDPP20_ERROR_INVALID_FEATURE_INDEX	0x06
23362306a36Sopenharmony_ci#define HIDPP20_ERROR_INVALID_FUNCTION_ID	0x07
23462306a36Sopenharmony_ci#define HIDPP20_ERROR_BUSY			0x08
23562306a36Sopenharmony_ci#define HIDPP20_ERROR_UNSUPPORTED		0x09
23662306a36Sopenharmony_ci#define HIDPP20_ERROR				0xff
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic void hidpp_connect_event(struct hidpp_device *hidpp_dev);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic int __hidpp_send_report(struct hid_device *hdev,
24162306a36Sopenharmony_ci				struct hidpp_report *hidpp_report)
24262306a36Sopenharmony_ci{
24362306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
24462306a36Sopenharmony_ci	int fields_count, ret;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	switch (hidpp_report->report_id) {
24762306a36Sopenharmony_ci	case REPORT_ID_HIDPP_SHORT:
24862306a36Sopenharmony_ci		fields_count = HIDPP_REPORT_SHORT_LENGTH;
24962306a36Sopenharmony_ci		break;
25062306a36Sopenharmony_ci	case REPORT_ID_HIDPP_LONG:
25162306a36Sopenharmony_ci		fields_count = HIDPP_REPORT_LONG_LENGTH;
25262306a36Sopenharmony_ci		break;
25362306a36Sopenharmony_ci	case REPORT_ID_HIDPP_VERY_LONG:
25462306a36Sopenharmony_ci		fields_count = hidpp->very_long_report_length;
25562306a36Sopenharmony_ci		break;
25662306a36Sopenharmony_ci	default:
25762306a36Sopenharmony_ci		return -ENODEV;
25862306a36Sopenharmony_ci	}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	/*
26162306a36Sopenharmony_ci	 * set the device_index as the receiver, it will be overwritten by
26262306a36Sopenharmony_ci	 * hid_hw_request if needed
26362306a36Sopenharmony_ci	 */
26462306a36Sopenharmony_ci	hidpp_report->device_index = 0xff;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_FORCE_OUTPUT_REPORTS) {
26762306a36Sopenharmony_ci		ret = hid_hw_output_report(hdev, (u8 *)hidpp_report, fields_count);
26862306a36Sopenharmony_ci	} else {
26962306a36Sopenharmony_ci		ret = hid_hw_raw_request(hdev, hidpp_report->report_id,
27062306a36Sopenharmony_ci			(u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT,
27162306a36Sopenharmony_ci			HID_REQ_SET_REPORT);
27262306a36Sopenharmony_ci	}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	return ret == fields_count ? 0 : -1;
27562306a36Sopenharmony_ci}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci/*
27862306a36Sopenharmony_ci * Effectively send the message to the device, waiting for its answer.
27962306a36Sopenharmony_ci *
28062306a36Sopenharmony_ci * Must be called with hidpp->send_mutex locked
28162306a36Sopenharmony_ci *
28262306a36Sopenharmony_ci * Same return protocol than hidpp_send_message_sync():
28362306a36Sopenharmony_ci * - success on 0
28462306a36Sopenharmony_ci * - negative error means transport error
28562306a36Sopenharmony_ci * - positive value means protocol error
28662306a36Sopenharmony_ci */
28762306a36Sopenharmony_cistatic int __do_hidpp_send_message_sync(struct hidpp_device *hidpp,
28862306a36Sopenharmony_ci	struct hidpp_report *message,
28962306a36Sopenharmony_ci	struct hidpp_report *response)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	int ret;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	__must_hold(&hidpp->send_mutex);
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	hidpp->send_receive_buf = response;
29662306a36Sopenharmony_ci	hidpp->answer_available = false;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	/*
29962306a36Sopenharmony_ci	 * So that we can later validate the answer when it arrives
30062306a36Sopenharmony_ci	 * in hidpp_raw_event
30162306a36Sopenharmony_ci	 */
30262306a36Sopenharmony_ci	*response = *message;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	ret = __hidpp_send_report(hidpp->hid_dev, message);
30562306a36Sopenharmony_ci	if (ret) {
30662306a36Sopenharmony_ci		dbg_hid("__hidpp_send_report returned err: %d\n", ret);
30762306a36Sopenharmony_ci		memset(response, 0, sizeof(struct hidpp_report));
30862306a36Sopenharmony_ci		return ret;
30962306a36Sopenharmony_ci	}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
31262306a36Sopenharmony_ci				5*HZ)) {
31362306a36Sopenharmony_ci		dbg_hid("%s:timeout waiting for response\n", __func__);
31462306a36Sopenharmony_ci		memset(response, 0, sizeof(struct hidpp_report));
31562306a36Sopenharmony_ci		return -ETIMEDOUT;
31662306a36Sopenharmony_ci	}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	if (response->report_id == REPORT_ID_HIDPP_SHORT &&
31962306a36Sopenharmony_ci	    response->rap.sub_id == HIDPP_ERROR) {
32062306a36Sopenharmony_ci		ret = response->rap.params[1];
32162306a36Sopenharmony_ci		dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
32262306a36Sopenharmony_ci		return ret;
32362306a36Sopenharmony_ci	}
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	if ((response->report_id == REPORT_ID_HIDPP_LONG ||
32662306a36Sopenharmony_ci	     response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
32762306a36Sopenharmony_ci	    response->fap.feature_index == HIDPP20_ERROR) {
32862306a36Sopenharmony_ci		ret = response->fap.params[1];
32962306a36Sopenharmony_ci		dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
33062306a36Sopenharmony_ci		return ret;
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	return 0;
33462306a36Sopenharmony_ci}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci/*
33762306a36Sopenharmony_ci * hidpp_send_message_sync() returns 0 in case of success, and something else
33862306a36Sopenharmony_ci * in case of a failure.
33962306a36Sopenharmony_ci *
34062306a36Sopenharmony_ci * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
34162306a36Sopenharmony_ci * value.
34262306a36Sopenharmony_ci */
34362306a36Sopenharmony_cistatic int hidpp_send_message_sync(struct hidpp_device *hidpp,
34462306a36Sopenharmony_ci	struct hidpp_report *message,
34562306a36Sopenharmony_ci	struct hidpp_report *response)
34662306a36Sopenharmony_ci{
34762306a36Sopenharmony_ci	int ret;
34862306a36Sopenharmony_ci	int max_retries = 3;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	mutex_lock(&hidpp->send_mutex);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	do {
35362306a36Sopenharmony_ci		ret = __do_hidpp_send_message_sync(hidpp, message, response);
35462306a36Sopenharmony_ci		if (ret != HIDPP20_ERROR_BUSY)
35562306a36Sopenharmony_ci			break;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci		dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
35862306a36Sopenharmony_ci	} while (--max_retries);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	mutex_unlock(&hidpp->send_mutex);
36162306a36Sopenharmony_ci	return ret;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci}
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci/*
36662306a36Sopenharmony_ci * hidpp_send_fap_command_sync() returns 0 in case of success, and something else
36762306a36Sopenharmony_ci * in case of a failure.
36862306a36Sopenharmony_ci *
36962306a36Sopenharmony_ci * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
37062306a36Sopenharmony_ci * value.
37162306a36Sopenharmony_ci */
37262306a36Sopenharmony_cistatic int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
37362306a36Sopenharmony_ci	u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
37462306a36Sopenharmony_ci	struct hidpp_report *response)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	struct hidpp_report *message;
37762306a36Sopenharmony_ci	int ret;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	if (param_count > sizeof(message->fap.params)) {
38062306a36Sopenharmony_ci		hid_dbg(hidpp->hid_dev,
38162306a36Sopenharmony_ci			"Invalid number of parameters passed to command (%d != %llu)\n",
38262306a36Sopenharmony_ci			param_count,
38362306a36Sopenharmony_ci			(unsigned long long) sizeof(message->fap.params));
38462306a36Sopenharmony_ci		return -EINVAL;
38562306a36Sopenharmony_ci	}
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
38862306a36Sopenharmony_ci	if (!message)
38962306a36Sopenharmony_ci		return -ENOMEM;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	if (param_count > (HIDPP_REPORT_LONG_LENGTH - 4))
39262306a36Sopenharmony_ci		message->report_id = REPORT_ID_HIDPP_VERY_LONG;
39362306a36Sopenharmony_ci	else
39462306a36Sopenharmony_ci		message->report_id = REPORT_ID_HIDPP_LONG;
39562306a36Sopenharmony_ci	message->fap.feature_index = feat_index;
39662306a36Sopenharmony_ci	message->fap.funcindex_clientid = funcindex_clientid | LINUX_KERNEL_SW_ID;
39762306a36Sopenharmony_ci	memcpy(&message->fap.params, params, param_count);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	ret = hidpp_send_message_sync(hidpp, message, response);
40062306a36Sopenharmony_ci	kfree(message);
40162306a36Sopenharmony_ci	return ret;
40262306a36Sopenharmony_ci}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci/*
40562306a36Sopenharmony_ci * hidpp_send_rap_command_sync() returns 0 in case of success, and something else
40662306a36Sopenharmony_ci * in case of a failure.
40762306a36Sopenharmony_ci *
40862306a36Sopenharmony_ci * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
40962306a36Sopenharmony_ci * value.
41062306a36Sopenharmony_ci */
41162306a36Sopenharmony_cistatic int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
41262306a36Sopenharmony_ci	u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
41362306a36Sopenharmony_ci	struct hidpp_report *response)
41462306a36Sopenharmony_ci{
41562306a36Sopenharmony_ci	struct hidpp_report *message;
41662306a36Sopenharmony_ci	int ret, max_count;
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	/* Send as long report if short reports are not supported. */
41962306a36Sopenharmony_ci	if (report_id == REPORT_ID_HIDPP_SHORT &&
42062306a36Sopenharmony_ci	    !(hidpp_dev->supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
42162306a36Sopenharmony_ci		report_id = REPORT_ID_HIDPP_LONG;
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	switch (report_id) {
42462306a36Sopenharmony_ci	case REPORT_ID_HIDPP_SHORT:
42562306a36Sopenharmony_ci		max_count = HIDPP_REPORT_SHORT_LENGTH - 4;
42662306a36Sopenharmony_ci		break;
42762306a36Sopenharmony_ci	case REPORT_ID_HIDPP_LONG:
42862306a36Sopenharmony_ci		max_count = HIDPP_REPORT_LONG_LENGTH - 4;
42962306a36Sopenharmony_ci		break;
43062306a36Sopenharmony_ci	case REPORT_ID_HIDPP_VERY_LONG:
43162306a36Sopenharmony_ci		max_count = hidpp_dev->very_long_report_length - 4;
43262306a36Sopenharmony_ci		break;
43362306a36Sopenharmony_ci	default:
43462306a36Sopenharmony_ci		return -EINVAL;
43562306a36Sopenharmony_ci	}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	if (param_count > max_count)
43862306a36Sopenharmony_ci		return -EINVAL;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
44162306a36Sopenharmony_ci	if (!message)
44262306a36Sopenharmony_ci		return -ENOMEM;
44362306a36Sopenharmony_ci	message->report_id = report_id;
44462306a36Sopenharmony_ci	message->rap.sub_id = sub_id;
44562306a36Sopenharmony_ci	message->rap.reg_address = reg_address;
44662306a36Sopenharmony_ci	memcpy(&message->rap.params, params, param_count);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	ret = hidpp_send_message_sync(hidpp_dev, message, response);
44962306a36Sopenharmony_ci	kfree(message);
45062306a36Sopenharmony_ci	return ret;
45162306a36Sopenharmony_ci}
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_cistatic void delayed_work_cb(struct work_struct *work)
45462306a36Sopenharmony_ci{
45562306a36Sopenharmony_ci	struct hidpp_device *hidpp = container_of(work, struct hidpp_device,
45662306a36Sopenharmony_ci							work);
45762306a36Sopenharmony_ci	hidpp_connect_event(hidpp);
45862306a36Sopenharmony_ci}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_cistatic inline bool hidpp_match_answer(struct hidpp_report *question,
46162306a36Sopenharmony_ci		struct hidpp_report *answer)
46262306a36Sopenharmony_ci{
46362306a36Sopenharmony_ci	return (answer->fap.feature_index == question->fap.feature_index) &&
46462306a36Sopenharmony_ci	   (answer->fap.funcindex_clientid == question->fap.funcindex_clientid);
46562306a36Sopenharmony_ci}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_cistatic inline bool hidpp_match_error(struct hidpp_report *question,
46862306a36Sopenharmony_ci		struct hidpp_report *answer)
46962306a36Sopenharmony_ci{
47062306a36Sopenharmony_ci	return ((answer->rap.sub_id == HIDPP_ERROR) ||
47162306a36Sopenharmony_ci	    (answer->fap.feature_index == HIDPP20_ERROR)) &&
47262306a36Sopenharmony_ci	    (answer->fap.funcindex_clientid == question->fap.feature_index) &&
47362306a36Sopenharmony_ci	    (answer->fap.params[0] == question->fap.funcindex_clientid);
47462306a36Sopenharmony_ci}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_cistatic inline bool hidpp_report_is_connect_event(struct hidpp_device *hidpp,
47762306a36Sopenharmony_ci		struct hidpp_report *report)
47862306a36Sopenharmony_ci{
47962306a36Sopenharmony_ci	return (hidpp->wireless_feature_index &&
48062306a36Sopenharmony_ci		(report->fap.feature_index == hidpp->wireless_feature_index)) ||
48162306a36Sopenharmony_ci		((report->report_id == REPORT_ID_HIDPP_SHORT) &&
48262306a36Sopenharmony_ci		(report->rap.sub_id == 0x41));
48362306a36Sopenharmony_ci}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci/*
48662306a36Sopenharmony_ci * hidpp_prefix_name() prefixes the current given name with "Logitech ".
48762306a36Sopenharmony_ci */
48862306a36Sopenharmony_cistatic void hidpp_prefix_name(char **name, int name_length)
48962306a36Sopenharmony_ci{
49062306a36Sopenharmony_ci#define PREFIX_LENGTH 9 /* "Logitech " */
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	int new_length;
49362306a36Sopenharmony_ci	char *new_name;
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	if (name_length > PREFIX_LENGTH &&
49662306a36Sopenharmony_ci	    strncmp(*name, "Logitech ", PREFIX_LENGTH) == 0)
49762306a36Sopenharmony_ci		/* The prefix has is already in the name */
49862306a36Sopenharmony_ci		return;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	new_length = PREFIX_LENGTH + name_length;
50162306a36Sopenharmony_ci	new_name = kzalloc(new_length, GFP_KERNEL);
50262306a36Sopenharmony_ci	if (!new_name)
50362306a36Sopenharmony_ci		return;
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	snprintf(new_name, new_length, "Logitech %s", *name);
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci	kfree(*name);
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	*name = new_name;
51062306a36Sopenharmony_ci}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci/*
51362306a36Sopenharmony_ci * Updates the USB wireless_status based on whether the headset
51462306a36Sopenharmony_ci * is turned on and reachable.
51562306a36Sopenharmony_ci */
51662306a36Sopenharmony_cistatic void hidpp_update_usb_wireless_status(struct hidpp_device *hidpp)
51762306a36Sopenharmony_ci{
51862306a36Sopenharmony_ci	struct hid_device *hdev = hidpp->hid_dev;
51962306a36Sopenharmony_ci	struct usb_interface *intf;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	if (!(hidpp->quirks & HIDPP_QUIRK_WIRELESS_STATUS))
52262306a36Sopenharmony_ci		return;
52362306a36Sopenharmony_ci	if (!hid_is_usb(hdev))
52462306a36Sopenharmony_ci		return;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	intf = to_usb_interface(hdev->dev.parent);
52762306a36Sopenharmony_ci	usb_set_wireless_status(intf, hidpp->battery.online ?
52862306a36Sopenharmony_ci				USB_WIRELESS_STATUS_CONNECTED :
52962306a36Sopenharmony_ci				USB_WIRELESS_STATUS_DISCONNECTED);
53062306a36Sopenharmony_ci}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci/**
53362306a36Sopenharmony_ci * hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll
53462306a36Sopenharmony_ci *                                        events given a high-resolution wheel
53562306a36Sopenharmony_ci *                                        movement.
53662306a36Sopenharmony_ci * @input_dev: Pointer to the input device
53762306a36Sopenharmony_ci * @counter: a hid_scroll_counter struct describing the wheel.
53862306a36Sopenharmony_ci * @hi_res_value: the movement of the wheel, in the mouse's high-resolution
53962306a36Sopenharmony_ci *                units.
54062306a36Sopenharmony_ci *
54162306a36Sopenharmony_ci * Given a high-resolution movement, this function converts the movement into
54262306a36Sopenharmony_ci * fractions of 120 and emits high-resolution scroll events for the input
54362306a36Sopenharmony_ci * device. It also uses the multiplier from &struct hid_scroll_counter to
54462306a36Sopenharmony_ci * emit low-resolution scroll events when appropriate for
54562306a36Sopenharmony_ci * backwards-compatibility with userspace input libraries.
54662306a36Sopenharmony_ci */
54762306a36Sopenharmony_cistatic void hidpp_scroll_counter_handle_scroll(struct input_dev *input_dev,
54862306a36Sopenharmony_ci					       struct hidpp_scroll_counter *counter,
54962306a36Sopenharmony_ci					       int hi_res_value)
55062306a36Sopenharmony_ci{
55162306a36Sopenharmony_ci	int low_res_value, remainder, direction;
55262306a36Sopenharmony_ci	unsigned long long now, previous;
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci	hi_res_value = hi_res_value * 120/counter->wheel_multiplier;
55562306a36Sopenharmony_ci	input_report_rel(input_dev, REL_WHEEL_HI_RES, hi_res_value);
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	remainder = counter->remainder;
55862306a36Sopenharmony_ci	direction = hi_res_value > 0 ? 1 : -1;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci	now = sched_clock();
56162306a36Sopenharmony_ci	previous = counter->last_time;
56262306a36Sopenharmony_ci	counter->last_time = now;
56362306a36Sopenharmony_ci	/*
56462306a36Sopenharmony_ci	 * Reset the remainder after a period of inactivity or when the
56562306a36Sopenharmony_ci	 * direction changes. This prevents the REL_WHEEL emulation point
56662306a36Sopenharmony_ci	 * from sliding for devices that don't always provide the same
56762306a36Sopenharmony_ci	 * number of movements per detent.
56862306a36Sopenharmony_ci	 */
56962306a36Sopenharmony_ci	if (now - previous > 1000000000 || direction != counter->direction)
57062306a36Sopenharmony_ci		remainder = 0;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	counter->direction = direction;
57362306a36Sopenharmony_ci	remainder += hi_res_value;
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	/* Some wheels will rest 7/8ths of a detent from the previous detent
57662306a36Sopenharmony_ci	 * after slow movement, so we want the threshold for low-res events to
57762306a36Sopenharmony_ci	 * be in the middle between two detents (e.g. after 4/8ths) as
57862306a36Sopenharmony_ci	 * opposed to on the detents themselves (8/8ths).
57962306a36Sopenharmony_ci	 */
58062306a36Sopenharmony_ci	if (abs(remainder) >= 60) {
58162306a36Sopenharmony_ci		/* Add (or subtract) 1 because we want to trigger when the wheel
58262306a36Sopenharmony_ci		 * is half-way to the next detent (i.e. scroll 1 detent after a
58362306a36Sopenharmony_ci		 * 1/2 detent movement, 2 detents after a 1 1/2 detent movement,
58462306a36Sopenharmony_ci		 * etc.).
58562306a36Sopenharmony_ci		 */
58662306a36Sopenharmony_ci		low_res_value = remainder / 120;
58762306a36Sopenharmony_ci		if (low_res_value == 0)
58862306a36Sopenharmony_ci			low_res_value = (hi_res_value > 0 ? 1 : -1);
58962306a36Sopenharmony_ci		input_report_rel(input_dev, REL_WHEEL, low_res_value);
59062306a36Sopenharmony_ci		remainder -= low_res_value * 120;
59162306a36Sopenharmony_ci	}
59262306a36Sopenharmony_ci	counter->remainder = remainder;
59362306a36Sopenharmony_ci}
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
59662306a36Sopenharmony_ci/* HIDP++ 1.0 commands                                                        */
59762306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci#define HIDPP_SET_REGISTER				0x80
60062306a36Sopenharmony_ci#define HIDPP_GET_REGISTER				0x81
60162306a36Sopenharmony_ci#define HIDPP_SET_LONG_REGISTER				0x82
60262306a36Sopenharmony_ci#define HIDPP_GET_LONG_REGISTER				0x83
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci/**
60562306a36Sopenharmony_ci * hidpp10_set_register - Modify a HID++ 1.0 register.
60662306a36Sopenharmony_ci * @hidpp_dev: the device to set the register on.
60762306a36Sopenharmony_ci * @register_address: the address of the register to modify.
60862306a36Sopenharmony_ci * @byte: the byte of the register to modify. Should be less than 3.
60962306a36Sopenharmony_ci * @mask: mask of the bits to modify
61062306a36Sopenharmony_ci * @value: new values for the bits in mask
61162306a36Sopenharmony_ci * Return: 0 if successful, otherwise a negative error code.
61262306a36Sopenharmony_ci */
61362306a36Sopenharmony_cistatic int hidpp10_set_register(struct hidpp_device *hidpp_dev,
61462306a36Sopenharmony_ci	u8 register_address, u8 byte, u8 mask, u8 value)
61562306a36Sopenharmony_ci{
61662306a36Sopenharmony_ci	struct hidpp_report response;
61762306a36Sopenharmony_ci	int ret;
61862306a36Sopenharmony_ci	u8 params[3] = { 0 };
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	ret = hidpp_send_rap_command_sync(hidpp_dev,
62162306a36Sopenharmony_ci					  REPORT_ID_HIDPP_SHORT,
62262306a36Sopenharmony_ci					  HIDPP_GET_REGISTER,
62362306a36Sopenharmony_ci					  register_address,
62462306a36Sopenharmony_ci					  NULL, 0, &response);
62562306a36Sopenharmony_ci	if (ret)
62662306a36Sopenharmony_ci		return ret;
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	memcpy(params, response.rap.params, 3);
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci	params[byte] &= ~mask;
63162306a36Sopenharmony_ci	params[byte] |= value & mask;
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	return hidpp_send_rap_command_sync(hidpp_dev,
63462306a36Sopenharmony_ci					   REPORT_ID_HIDPP_SHORT,
63562306a36Sopenharmony_ci					   HIDPP_SET_REGISTER,
63662306a36Sopenharmony_ci					   register_address,
63762306a36Sopenharmony_ci					   params, 3, &response);
63862306a36Sopenharmony_ci}
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci#define HIDPP_REG_ENABLE_REPORTS			0x00
64162306a36Sopenharmony_ci#define HIDPP_ENABLE_CONSUMER_REPORT			BIT(0)
64262306a36Sopenharmony_ci#define HIDPP_ENABLE_WHEEL_REPORT			BIT(2)
64362306a36Sopenharmony_ci#define HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT		BIT(3)
64462306a36Sopenharmony_ci#define HIDPP_ENABLE_BAT_REPORT				BIT(4)
64562306a36Sopenharmony_ci#define HIDPP_ENABLE_HWHEEL_REPORT			BIT(5)
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_cistatic int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev)
64862306a36Sopenharmony_ci{
64962306a36Sopenharmony_ci	return hidpp10_set_register(hidpp_dev, HIDPP_REG_ENABLE_REPORTS, 0,
65062306a36Sopenharmony_ci			  HIDPP_ENABLE_BAT_REPORT, HIDPP_ENABLE_BAT_REPORT);
65162306a36Sopenharmony_ci}
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci#define HIDPP_REG_FEATURES				0x01
65462306a36Sopenharmony_ci#define HIDPP_ENABLE_SPECIAL_BUTTON_FUNC		BIT(1)
65562306a36Sopenharmony_ci#define HIDPP_ENABLE_FAST_SCROLL			BIT(6)
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci/* On HID++ 1.0 devices, high-res scroll was called "scrolling acceleration". */
65862306a36Sopenharmony_cistatic int hidpp10_enable_scrolling_acceleration(struct hidpp_device *hidpp_dev)
65962306a36Sopenharmony_ci{
66062306a36Sopenharmony_ci	return hidpp10_set_register(hidpp_dev, HIDPP_REG_FEATURES, 0,
66162306a36Sopenharmony_ci			  HIDPP_ENABLE_FAST_SCROLL, HIDPP_ENABLE_FAST_SCROLL);
66262306a36Sopenharmony_ci}
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci#define HIDPP_REG_BATTERY_STATUS			0x07
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_cistatic int hidpp10_battery_status_map_level(u8 param)
66762306a36Sopenharmony_ci{
66862306a36Sopenharmony_ci	int level;
66962306a36Sopenharmony_ci
67062306a36Sopenharmony_ci	switch (param) {
67162306a36Sopenharmony_ci	case 1 ... 2:
67262306a36Sopenharmony_ci		level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
67362306a36Sopenharmony_ci		break;
67462306a36Sopenharmony_ci	case 3 ... 4:
67562306a36Sopenharmony_ci		level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
67662306a36Sopenharmony_ci		break;
67762306a36Sopenharmony_ci	case 5 ... 6:
67862306a36Sopenharmony_ci		level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
67962306a36Sopenharmony_ci		break;
68062306a36Sopenharmony_ci	case 7:
68162306a36Sopenharmony_ci		level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
68262306a36Sopenharmony_ci		break;
68362306a36Sopenharmony_ci	default:
68462306a36Sopenharmony_ci		level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
68562306a36Sopenharmony_ci	}
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_ci	return level;
68862306a36Sopenharmony_ci}
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_cistatic int hidpp10_battery_status_map_status(u8 param)
69162306a36Sopenharmony_ci{
69262306a36Sopenharmony_ci	int status;
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci	switch (param) {
69562306a36Sopenharmony_ci	case 0x00:
69662306a36Sopenharmony_ci		/* discharging (in use) */
69762306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_DISCHARGING;
69862306a36Sopenharmony_ci		break;
69962306a36Sopenharmony_ci	case 0x21: /* (standard) charging */
70062306a36Sopenharmony_ci	case 0x24: /* fast charging */
70162306a36Sopenharmony_ci	case 0x25: /* slow charging */
70262306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_CHARGING;
70362306a36Sopenharmony_ci		break;
70462306a36Sopenharmony_ci	case 0x26: /* topping charge */
70562306a36Sopenharmony_ci	case 0x22: /* charge complete */
70662306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_FULL;
70762306a36Sopenharmony_ci		break;
70862306a36Sopenharmony_ci	case 0x20: /* unknown */
70962306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_UNKNOWN;
71062306a36Sopenharmony_ci		break;
71162306a36Sopenharmony_ci	/*
71262306a36Sopenharmony_ci	 * 0x01...0x1F = reserved (not charging)
71362306a36Sopenharmony_ci	 * 0x23 = charging error
71462306a36Sopenharmony_ci	 * 0x27..0xff = reserved
71562306a36Sopenharmony_ci	 */
71662306a36Sopenharmony_ci	default:
71762306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_NOT_CHARGING;
71862306a36Sopenharmony_ci		break;
71962306a36Sopenharmony_ci	}
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci	return status;
72262306a36Sopenharmony_ci}
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_cistatic int hidpp10_query_battery_status(struct hidpp_device *hidpp)
72562306a36Sopenharmony_ci{
72662306a36Sopenharmony_ci	struct hidpp_report response;
72762306a36Sopenharmony_ci	int ret, status;
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci	ret = hidpp_send_rap_command_sync(hidpp,
73062306a36Sopenharmony_ci					REPORT_ID_HIDPP_SHORT,
73162306a36Sopenharmony_ci					HIDPP_GET_REGISTER,
73262306a36Sopenharmony_ci					HIDPP_REG_BATTERY_STATUS,
73362306a36Sopenharmony_ci					NULL, 0, &response);
73462306a36Sopenharmony_ci	if (ret)
73562306a36Sopenharmony_ci		return ret;
73662306a36Sopenharmony_ci
73762306a36Sopenharmony_ci	hidpp->battery.level =
73862306a36Sopenharmony_ci		hidpp10_battery_status_map_level(response.rap.params[0]);
73962306a36Sopenharmony_ci	status = hidpp10_battery_status_map_status(response.rap.params[1]);
74062306a36Sopenharmony_ci	hidpp->battery.status = status;
74162306a36Sopenharmony_ci	/* the capacity is only available when discharging or full */
74262306a36Sopenharmony_ci	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
74362306a36Sopenharmony_ci				status == POWER_SUPPLY_STATUS_FULL;
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	return 0;
74662306a36Sopenharmony_ci}
74762306a36Sopenharmony_ci
74862306a36Sopenharmony_ci#define HIDPP_REG_BATTERY_MILEAGE			0x0D
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_cistatic int hidpp10_battery_mileage_map_status(u8 param)
75162306a36Sopenharmony_ci{
75262306a36Sopenharmony_ci	int status;
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci	switch (param >> 6) {
75562306a36Sopenharmony_ci	case 0x00:
75662306a36Sopenharmony_ci		/* discharging (in use) */
75762306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_DISCHARGING;
75862306a36Sopenharmony_ci		break;
75962306a36Sopenharmony_ci	case 0x01: /* charging */
76062306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_CHARGING;
76162306a36Sopenharmony_ci		break;
76262306a36Sopenharmony_ci	case 0x02: /* charge complete */
76362306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_FULL;
76462306a36Sopenharmony_ci		break;
76562306a36Sopenharmony_ci	/*
76662306a36Sopenharmony_ci	 * 0x03 = charging error
76762306a36Sopenharmony_ci	 */
76862306a36Sopenharmony_ci	default:
76962306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_NOT_CHARGING;
77062306a36Sopenharmony_ci		break;
77162306a36Sopenharmony_ci	}
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	return status;
77462306a36Sopenharmony_ci}
77562306a36Sopenharmony_ci
77662306a36Sopenharmony_cistatic int hidpp10_query_battery_mileage(struct hidpp_device *hidpp)
77762306a36Sopenharmony_ci{
77862306a36Sopenharmony_ci	struct hidpp_report response;
77962306a36Sopenharmony_ci	int ret, status;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	ret = hidpp_send_rap_command_sync(hidpp,
78262306a36Sopenharmony_ci					REPORT_ID_HIDPP_SHORT,
78362306a36Sopenharmony_ci					HIDPP_GET_REGISTER,
78462306a36Sopenharmony_ci					HIDPP_REG_BATTERY_MILEAGE,
78562306a36Sopenharmony_ci					NULL, 0, &response);
78662306a36Sopenharmony_ci	if (ret)
78762306a36Sopenharmony_ci		return ret;
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	hidpp->battery.capacity = response.rap.params[0];
79062306a36Sopenharmony_ci	status = hidpp10_battery_mileage_map_status(response.rap.params[2]);
79162306a36Sopenharmony_ci	hidpp->battery.status = status;
79262306a36Sopenharmony_ci	/* the capacity is only available when discharging or full */
79362306a36Sopenharmony_ci	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
79462306a36Sopenharmony_ci				status == POWER_SUPPLY_STATUS_FULL;
79562306a36Sopenharmony_ci
79662306a36Sopenharmony_ci	return 0;
79762306a36Sopenharmony_ci}
79862306a36Sopenharmony_ci
79962306a36Sopenharmony_cistatic int hidpp10_battery_event(struct hidpp_device *hidpp, u8 *data, int size)
80062306a36Sopenharmony_ci{
80162306a36Sopenharmony_ci	struct hidpp_report *report = (struct hidpp_report *)data;
80262306a36Sopenharmony_ci	int status, capacity, level;
80362306a36Sopenharmony_ci	bool changed;
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_ci	if (report->report_id != REPORT_ID_HIDPP_SHORT)
80662306a36Sopenharmony_ci		return 0;
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci	switch (report->rap.sub_id) {
80962306a36Sopenharmony_ci	case HIDPP_REG_BATTERY_STATUS:
81062306a36Sopenharmony_ci		capacity = hidpp->battery.capacity;
81162306a36Sopenharmony_ci		level = hidpp10_battery_status_map_level(report->rawbytes[1]);
81262306a36Sopenharmony_ci		status = hidpp10_battery_status_map_status(report->rawbytes[2]);
81362306a36Sopenharmony_ci		break;
81462306a36Sopenharmony_ci	case HIDPP_REG_BATTERY_MILEAGE:
81562306a36Sopenharmony_ci		capacity = report->rap.params[0];
81662306a36Sopenharmony_ci		level = hidpp->battery.level;
81762306a36Sopenharmony_ci		status = hidpp10_battery_mileage_map_status(report->rawbytes[3]);
81862306a36Sopenharmony_ci		break;
81962306a36Sopenharmony_ci	default:
82062306a36Sopenharmony_ci		return 0;
82162306a36Sopenharmony_ci	}
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci	changed = capacity != hidpp->battery.capacity ||
82462306a36Sopenharmony_ci		  level != hidpp->battery.level ||
82562306a36Sopenharmony_ci		  status != hidpp->battery.status;
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	/* the capacity is only available when discharging or full */
82862306a36Sopenharmony_ci	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
82962306a36Sopenharmony_ci				status == POWER_SUPPLY_STATUS_FULL;
83062306a36Sopenharmony_ci
83162306a36Sopenharmony_ci	if (changed) {
83262306a36Sopenharmony_ci		hidpp->battery.level = level;
83362306a36Sopenharmony_ci		hidpp->battery.status = status;
83462306a36Sopenharmony_ci		if (hidpp->battery.ps)
83562306a36Sopenharmony_ci			power_supply_changed(hidpp->battery.ps);
83662306a36Sopenharmony_ci	}
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci	return 0;
83962306a36Sopenharmony_ci}
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci#define HIDPP_REG_PAIRING_INFORMATION			0xB5
84262306a36Sopenharmony_ci#define HIDPP_EXTENDED_PAIRING				0x30
84362306a36Sopenharmony_ci#define HIDPP_DEVICE_NAME				0x40
84462306a36Sopenharmony_ci
84562306a36Sopenharmony_cistatic char *hidpp_unifying_get_name(struct hidpp_device *hidpp_dev)
84662306a36Sopenharmony_ci{
84762306a36Sopenharmony_ci	struct hidpp_report response;
84862306a36Sopenharmony_ci	int ret;
84962306a36Sopenharmony_ci	u8 params[1] = { HIDPP_DEVICE_NAME };
85062306a36Sopenharmony_ci	char *name;
85162306a36Sopenharmony_ci	int len;
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci	ret = hidpp_send_rap_command_sync(hidpp_dev,
85462306a36Sopenharmony_ci					REPORT_ID_HIDPP_SHORT,
85562306a36Sopenharmony_ci					HIDPP_GET_LONG_REGISTER,
85662306a36Sopenharmony_ci					HIDPP_REG_PAIRING_INFORMATION,
85762306a36Sopenharmony_ci					params, 1, &response);
85862306a36Sopenharmony_ci	if (ret)
85962306a36Sopenharmony_ci		return NULL;
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	len = response.rap.params[1];
86262306a36Sopenharmony_ci
86362306a36Sopenharmony_ci	if (2 + len > sizeof(response.rap.params))
86462306a36Sopenharmony_ci		return NULL;
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ci	if (len < 4) /* logitech devices are usually at least Xddd */
86762306a36Sopenharmony_ci		return NULL;
86862306a36Sopenharmony_ci
86962306a36Sopenharmony_ci	name = kzalloc(len + 1, GFP_KERNEL);
87062306a36Sopenharmony_ci	if (!name)
87162306a36Sopenharmony_ci		return NULL;
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci	memcpy(name, &response.rap.params[2], len);
87462306a36Sopenharmony_ci
87562306a36Sopenharmony_ci	/* include the terminating '\0' */
87662306a36Sopenharmony_ci	hidpp_prefix_name(&name, len + 1);
87762306a36Sopenharmony_ci
87862306a36Sopenharmony_ci	return name;
87962306a36Sopenharmony_ci}
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_cistatic int hidpp_unifying_get_serial(struct hidpp_device *hidpp, u32 *serial)
88262306a36Sopenharmony_ci{
88362306a36Sopenharmony_ci	struct hidpp_report response;
88462306a36Sopenharmony_ci	int ret;
88562306a36Sopenharmony_ci	u8 params[1] = { HIDPP_EXTENDED_PAIRING };
88662306a36Sopenharmony_ci
88762306a36Sopenharmony_ci	ret = hidpp_send_rap_command_sync(hidpp,
88862306a36Sopenharmony_ci					REPORT_ID_HIDPP_SHORT,
88962306a36Sopenharmony_ci					HIDPP_GET_LONG_REGISTER,
89062306a36Sopenharmony_ci					HIDPP_REG_PAIRING_INFORMATION,
89162306a36Sopenharmony_ci					params, 1, &response);
89262306a36Sopenharmony_ci	if (ret)
89362306a36Sopenharmony_ci		return ret;
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	/*
89662306a36Sopenharmony_ci	 * We don't care about LE or BE, we will output it as a string
89762306a36Sopenharmony_ci	 * with %4phD, so we need to keep the order.
89862306a36Sopenharmony_ci	 */
89962306a36Sopenharmony_ci	*serial = *((u32 *)&response.rap.params[1]);
90062306a36Sopenharmony_ci	return 0;
90162306a36Sopenharmony_ci}
90262306a36Sopenharmony_ci
90362306a36Sopenharmony_cistatic int hidpp_unifying_init(struct hidpp_device *hidpp)
90462306a36Sopenharmony_ci{
90562306a36Sopenharmony_ci	struct hid_device *hdev = hidpp->hid_dev;
90662306a36Sopenharmony_ci	const char *name;
90762306a36Sopenharmony_ci	u32 serial;
90862306a36Sopenharmony_ci	int ret;
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	ret = hidpp_unifying_get_serial(hidpp, &serial);
91162306a36Sopenharmony_ci	if (ret)
91262306a36Sopenharmony_ci		return ret;
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_ci	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
91562306a36Sopenharmony_ci	dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq);
91662306a36Sopenharmony_ci
91762306a36Sopenharmony_ci	name = hidpp_unifying_get_name(hidpp);
91862306a36Sopenharmony_ci	if (!name)
91962306a36Sopenharmony_ci		return -EIO;
92062306a36Sopenharmony_ci
92162306a36Sopenharmony_ci	snprintf(hdev->name, sizeof(hdev->name), "%s", name);
92262306a36Sopenharmony_ci	dbg_hid("HID++ Unifying: Got name: %s\n", name);
92362306a36Sopenharmony_ci
92462306a36Sopenharmony_ci	kfree(name);
92562306a36Sopenharmony_ci	return 0;
92662306a36Sopenharmony_ci}
92762306a36Sopenharmony_ci
92862306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
92962306a36Sopenharmony_ci/* 0x0000: Root                                                               */
93062306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci#define HIDPP_PAGE_ROOT					0x0000
93362306a36Sopenharmony_ci#define HIDPP_PAGE_ROOT_IDX				0x00
93462306a36Sopenharmony_ci
93562306a36Sopenharmony_ci#define CMD_ROOT_GET_FEATURE				0x00
93662306a36Sopenharmony_ci#define CMD_ROOT_GET_PROTOCOL_VERSION			0x10
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_cistatic int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
93962306a36Sopenharmony_ci	u8 *feature_index, u8 *feature_type)
94062306a36Sopenharmony_ci{
94162306a36Sopenharmony_ci	struct hidpp_report response;
94262306a36Sopenharmony_ci	int ret;
94362306a36Sopenharmony_ci	u8 params[2] = { feature >> 8, feature & 0x00FF };
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp,
94662306a36Sopenharmony_ci			HIDPP_PAGE_ROOT_IDX,
94762306a36Sopenharmony_ci			CMD_ROOT_GET_FEATURE,
94862306a36Sopenharmony_ci			params, 2, &response);
94962306a36Sopenharmony_ci	if (ret)
95062306a36Sopenharmony_ci		return ret;
95162306a36Sopenharmony_ci
95262306a36Sopenharmony_ci	if (response.fap.params[0] == 0)
95362306a36Sopenharmony_ci		return -ENOENT;
95462306a36Sopenharmony_ci
95562306a36Sopenharmony_ci	*feature_index = response.fap.params[0];
95662306a36Sopenharmony_ci	*feature_type = response.fap.params[1];
95762306a36Sopenharmony_ci
95862306a36Sopenharmony_ci	return ret;
95962306a36Sopenharmony_ci}
96062306a36Sopenharmony_ci
96162306a36Sopenharmony_cistatic int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
96262306a36Sopenharmony_ci{
96362306a36Sopenharmony_ci	const u8 ping_byte = 0x5a;
96462306a36Sopenharmony_ci	u8 ping_data[3] = { 0, 0, ping_byte };
96562306a36Sopenharmony_ci	struct hidpp_report response;
96662306a36Sopenharmony_ci	int ret;
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_ci	ret = hidpp_send_rap_command_sync(hidpp,
96962306a36Sopenharmony_ci			REPORT_ID_HIDPP_SHORT,
97062306a36Sopenharmony_ci			HIDPP_PAGE_ROOT_IDX,
97162306a36Sopenharmony_ci			CMD_ROOT_GET_PROTOCOL_VERSION | LINUX_KERNEL_SW_ID,
97262306a36Sopenharmony_ci			ping_data, sizeof(ping_data), &response);
97362306a36Sopenharmony_ci
97462306a36Sopenharmony_ci	if (ret == HIDPP_ERROR_INVALID_SUBID) {
97562306a36Sopenharmony_ci		hidpp->protocol_major = 1;
97662306a36Sopenharmony_ci		hidpp->protocol_minor = 0;
97762306a36Sopenharmony_ci		goto print_version;
97862306a36Sopenharmony_ci	}
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_ci	/* the device might not be connected */
98162306a36Sopenharmony_ci	if (ret == HIDPP_ERROR_RESOURCE_ERROR)
98262306a36Sopenharmony_ci		return -EIO;
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_ci	if (ret > 0) {
98562306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
98662306a36Sopenharmony_ci			__func__, ret);
98762306a36Sopenharmony_ci		return -EPROTO;
98862306a36Sopenharmony_ci	}
98962306a36Sopenharmony_ci	if (ret)
99062306a36Sopenharmony_ci		return ret;
99162306a36Sopenharmony_ci
99262306a36Sopenharmony_ci	if (response.rap.params[2] != ping_byte) {
99362306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: ping mismatch 0x%02x != 0x%02x\n",
99462306a36Sopenharmony_ci			__func__, response.rap.params[2], ping_byte);
99562306a36Sopenharmony_ci		return -EPROTO;
99662306a36Sopenharmony_ci	}
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_ci	hidpp->protocol_major = response.rap.params[0];
99962306a36Sopenharmony_ci	hidpp->protocol_minor = response.rap.params[1];
100062306a36Sopenharmony_ci
100162306a36Sopenharmony_ciprint_version:
100262306a36Sopenharmony_ci	hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
100362306a36Sopenharmony_ci		 hidpp->protocol_major, hidpp->protocol_minor);
100462306a36Sopenharmony_ci	return 0;
100562306a36Sopenharmony_ci}
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
100862306a36Sopenharmony_ci/* 0x0003: Device Information                                                 */
100962306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
101062306a36Sopenharmony_ci
101162306a36Sopenharmony_ci#define HIDPP_PAGE_DEVICE_INFORMATION			0x0003
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_ci#define CMD_GET_DEVICE_INFO				0x00
101462306a36Sopenharmony_ci
101562306a36Sopenharmony_cistatic int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
101662306a36Sopenharmony_ci{
101762306a36Sopenharmony_ci	struct hidpp_report response;
101862306a36Sopenharmony_ci	u8 feature_type;
101962306a36Sopenharmony_ci	u8 feature_index;
102062306a36Sopenharmony_ci	int ret;
102162306a36Sopenharmony_ci
102262306a36Sopenharmony_ci	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION,
102362306a36Sopenharmony_ci				     &feature_index,
102462306a36Sopenharmony_ci				     &feature_type);
102562306a36Sopenharmony_ci	if (ret)
102662306a36Sopenharmony_ci		return ret;
102762306a36Sopenharmony_ci
102862306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
102962306a36Sopenharmony_ci					  CMD_GET_DEVICE_INFO,
103062306a36Sopenharmony_ci					  NULL, 0, &response);
103162306a36Sopenharmony_ci	if (ret)
103262306a36Sopenharmony_ci		return ret;
103362306a36Sopenharmony_ci
103462306a36Sopenharmony_ci	/* See hidpp_unifying_get_serial() */
103562306a36Sopenharmony_ci	*serial = *((u32 *)&response.rap.params[1]);
103662306a36Sopenharmony_ci	return 0;
103762306a36Sopenharmony_ci}
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_cistatic int hidpp_serial_init(struct hidpp_device *hidpp)
104062306a36Sopenharmony_ci{
104162306a36Sopenharmony_ci	struct hid_device *hdev = hidpp->hid_dev;
104262306a36Sopenharmony_ci	u32 serial;
104362306a36Sopenharmony_ci	int ret;
104462306a36Sopenharmony_ci
104562306a36Sopenharmony_ci	ret = hidpp_get_serial(hidpp, &serial);
104662306a36Sopenharmony_ci	if (ret)
104762306a36Sopenharmony_ci		return ret;
104862306a36Sopenharmony_ci
104962306a36Sopenharmony_ci	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
105062306a36Sopenharmony_ci	dbg_hid("HID++ DeviceInformation: Got serial: %s\n", hdev->uniq);
105162306a36Sopenharmony_ci
105262306a36Sopenharmony_ci	return 0;
105362306a36Sopenharmony_ci}
105462306a36Sopenharmony_ci
105562306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
105662306a36Sopenharmony_ci/* 0x0005: GetDeviceNameType                                                  */
105762306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
105862306a36Sopenharmony_ci
105962306a36Sopenharmony_ci#define HIDPP_PAGE_GET_DEVICE_NAME_TYPE			0x0005
106062306a36Sopenharmony_ci
106162306a36Sopenharmony_ci#define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT		0x00
106262306a36Sopenharmony_ci#define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME	0x10
106362306a36Sopenharmony_ci#define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE		0x20
106462306a36Sopenharmony_ci
106562306a36Sopenharmony_cistatic int hidpp_devicenametype_get_count(struct hidpp_device *hidpp,
106662306a36Sopenharmony_ci	u8 feature_index, u8 *nameLength)
106762306a36Sopenharmony_ci{
106862306a36Sopenharmony_ci	struct hidpp_report response;
106962306a36Sopenharmony_ci	int ret;
107062306a36Sopenharmony_ci
107162306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
107262306a36Sopenharmony_ci		CMD_GET_DEVICE_NAME_TYPE_GET_COUNT, NULL, 0, &response);
107362306a36Sopenharmony_ci
107462306a36Sopenharmony_ci	if (ret > 0) {
107562306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
107662306a36Sopenharmony_ci			__func__, ret);
107762306a36Sopenharmony_ci		return -EPROTO;
107862306a36Sopenharmony_ci	}
107962306a36Sopenharmony_ci	if (ret)
108062306a36Sopenharmony_ci		return ret;
108162306a36Sopenharmony_ci
108262306a36Sopenharmony_ci	*nameLength = response.fap.params[0];
108362306a36Sopenharmony_ci
108462306a36Sopenharmony_ci	return ret;
108562306a36Sopenharmony_ci}
108662306a36Sopenharmony_ci
108762306a36Sopenharmony_cistatic int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp,
108862306a36Sopenharmony_ci	u8 feature_index, u8 char_index, char *device_name, int len_buf)
108962306a36Sopenharmony_ci{
109062306a36Sopenharmony_ci	struct hidpp_report response;
109162306a36Sopenharmony_ci	int ret, i;
109262306a36Sopenharmony_ci	int count;
109362306a36Sopenharmony_ci
109462306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
109562306a36Sopenharmony_ci		CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME, &char_index, 1,
109662306a36Sopenharmony_ci		&response);
109762306a36Sopenharmony_ci
109862306a36Sopenharmony_ci	if (ret > 0) {
109962306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
110062306a36Sopenharmony_ci			__func__, ret);
110162306a36Sopenharmony_ci		return -EPROTO;
110262306a36Sopenharmony_ci	}
110362306a36Sopenharmony_ci	if (ret)
110462306a36Sopenharmony_ci		return ret;
110562306a36Sopenharmony_ci
110662306a36Sopenharmony_ci	switch (response.report_id) {
110762306a36Sopenharmony_ci	case REPORT_ID_HIDPP_VERY_LONG:
110862306a36Sopenharmony_ci		count = hidpp->very_long_report_length - 4;
110962306a36Sopenharmony_ci		break;
111062306a36Sopenharmony_ci	case REPORT_ID_HIDPP_LONG:
111162306a36Sopenharmony_ci		count = HIDPP_REPORT_LONG_LENGTH - 4;
111262306a36Sopenharmony_ci		break;
111362306a36Sopenharmony_ci	case REPORT_ID_HIDPP_SHORT:
111462306a36Sopenharmony_ci		count = HIDPP_REPORT_SHORT_LENGTH - 4;
111562306a36Sopenharmony_ci		break;
111662306a36Sopenharmony_ci	default:
111762306a36Sopenharmony_ci		return -EPROTO;
111862306a36Sopenharmony_ci	}
111962306a36Sopenharmony_ci
112062306a36Sopenharmony_ci	if (len_buf < count)
112162306a36Sopenharmony_ci		count = len_buf;
112262306a36Sopenharmony_ci
112362306a36Sopenharmony_ci	for (i = 0; i < count; i++)
112462306a36Sopenharmony_ci		device_name[i] = response.fap.params[i];
112562306a36Sopenharmony_ci
112662306a36Sopenharmony_ci	return count;
112762306a36Sopenharmony_ci}
112862306a36Sopenharmony_ci
112962306a36Sopenharmony_cistatic char *hidpp_get_device_name(struct hidpp_device *hidpp)
113062306a36Sopenharmony_ci{
113162306a36Sopenharmony_ci	u8 feature_type;
113262306a36Sopenharmony_ci	u8 feature_index;
113362306a36Sopenharmony_ci	u8 __name_length;
113462306a36Sopenharmony_ci	char *name;
113562306a36Sopenharmony_ci	unsigned index = 0;
113662306a36Sopenharmony_ci	int ret;
113762306a36Sopenharmony_ci
113862306a36Sopenharmony_ci	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
113962306a36Sopenharmony_ci		&feature_index, &feature_type);
114062306a36Sopenharmony_ci	if (ret)
114162306a36Sopenharmony_ci		return NULL;
114262306a36Sopenharmony_ci
114362306a36Sopenharmony_ci	ret = hidpp_devicenametype_get_count(hidpp, feature_index,
114462306a36Sopenharmony_ci		&__name_length);
114562306a36Sopenharmony_ci	if (ret)
114662306a36Sopenharmony_ci		return NULL;
114762306a36Sopenharmony_ci
114862306a36Sopenharmony_ci	name = kzalloc(__name_length + 1, GFP_KERNEL);
114962306a36Sopenharmony_ci	if (!name)
115062306a36Sopenharmony_ci		return NULL;
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_ci	while (index < __name_length) {
115362306a36Sopenharmony_ci		ret = hidpp_devicenametype_get_device_name(hidpp,
115462306a36Sopenharmony_ci			feature_index, index, name + index,
115562306a36Sopenharmony_ci			__name_length - index);
115662306a36Sopenharmony_ci		if (ret <= 0) {
115762306a36Sopenharmony_ci			kfree(name);
115862306a36Sopenharmony_ci			return NULL;
115962306a36Sopenharmony_ci		}
116062306a36Sopenharmony_ci		index += ret;
116162306a36Sopenharmony_ci	}
116262306a36Sopenharmony_ci
116362306a36Sopenharmony_ci	/* include the terminating '\0' */
116462306a36Sopenharmony_ci	hidpp_prefix_name(&name, __name_length + 1);
116562306a36Sopenharmony_ci
116662306a36Sopenharmony_ci	return name;
116762306a36Sopenharmony_ci}
116862306a36Sopenharmony_ci
116962306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
117062306a36Sopenharmony_ci/* 0x1000: Battery level status                                               */
117162306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
117262306a36Sopenharmony_ci
117362306a36Sopenharmony_ci#define HIDPP_PAGE_BATTERY_LEVEL_STATUS				0x1000
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_ci#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS	0x00
117662306a36Sopenharmony_ci#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY		0x10
117762306a36Sopenharmony_ci
117862306a36Sopenharmony_ci#define EVENT_BATTERY_LEVEL_STATUS_BROADCAST			0x00
117962306a36Sopenharmony_ci
118062306a36Sopenharmony_ci#define FLAG_BATTERY_LEVEL_DISABLE_OSD				BIT(0)
118162306a36Sopenharmony_ci#define FLAG_BATTERY_LEVEL_MILEAGE				BIT(1)
118262306a36Sopenharmony_ci#define FLAG_BATTERY_LEVEL_RECHARGEABLE				BIT(2)
118362306a36Sopenharmony_ci
118462306a36Sopenharmony_cistatic int hidpp_map_battery_level(int capacity)
118562306a36Sopenharmony_ci{
118662306a36Sopenharmony_ci	if (capacity < 11)
118762306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
118862306a36Sopenharmony_ci	/*
118962306a36Sopenharmony_ci	 * The spec says this should be < 31 but some devices report 30
119062306a36Sopenharmony_ci	 * with brand new batteries and Windows reports 30 as "Good".
119162306a36Sopenharmony_ci	 */
119262306a36Sopenharmony_ci	else if (capacity < 30)
119362306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
119462306a36Sopenharmony_ci	else if (capacity < 81)
119562306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
119662306a36Sopenharmony_ci	return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
119762306a36Sopenharmony_ci}
119862306a36Sopenharmony_ci
119962306a36Sopenharmony_cistatic int hidpp20_batterylevel_map_status_capacity(u8 data[3], int *capacity,
120062306a36Sopenharmony_ci						    int *next_capacity,
120162306a36Sopenharmony_ci						    int *level)
120262306a36Sopenharmony_ci{
120362306a36Sopenharmony_ci	int status;
120462306a36Sopenharmony_ci
120562306a36Sopenharmony_ci	*capacity = data[0];
120662306a36Sopenharmony_ci	*next_capacity = data[1];
120762306a36Sopenharmony_ci	*level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci	/* When discharging, we can rely on the device reported capacity.
121062306a36Sopenharmony_ci	 * For all other states the device reports 0 (unknown).
121162306a36Sopenharmony_ci	 */
121262306a36Sopenharmony_ci	switch (data[2]) {
121362306a36Sopenharmony_ci		case 0: /* discharging (in use) */
121462306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_DISCHARGING;
121562306a36Sopenharmony_ci			*level = hidpp_map_battery_level(*capacity);
121662306a36Sopenharmony_ci			break;
121762306a36Sopenharmony_ci		case 1: /* recharging */
121862306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_CHARGING;
121962306a36Sopenharmony_ci			break;
122062306a36Sopenharmony_ci		case 2: /* charge in final stage */
122162306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_CHARGING;
122262306a36Sopenharmony_ci			break;
122362306a36Sopenharmony_ci		case 3: /* charge complete */
122462306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_FULL;
122562306a36Sopenharmony_ci			*level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
122662306a36Sopenharmony_ci			*capacity = 100;
122762306a36Sopenharmony_ci			break;
122862306a36Sopenharmony_ci		case 4: /* recharging below optimal speed */
122962306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_CHARGING;
123062306a36Sopenharmony_ci			break;
123162306a36Sopenharmony_ci		/* 5 = invalid battery type
123262306a36Sopenharmony_ci		   6 = thermal error
123362306a36Sopenharmony_ci		   7 = other charging error */
123462306a36Sopenharmony_ci		default:
123562306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_NOT_CHARGING;
123662306a36Sopenharmony_ci			break;
123762306a36Sopenharmony_ci	}
123862306a36Sopenharmony_ci
123962306a36Sopenharmony_ci	return status;
124062306a36Sopenharmony_ci}
124162306a36Sopenharmony_ci
124262306a36Sopenharmony_cistatic int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp,
124362306a36Sopenharmony_ci						     u8 feature_index,
124462306a36Sopenharmony_ci						     int *status,
124562306a36Sopenharmony_ci						     int *capacity,
124662306a36Sopenharmony_ci						     int *next_capacity,
124762306a36Sopenharmony_ci						     int *level)
124862306a36Sopenharmony_ci{
124962306a36Sopenharmony_ci	struct hidpp_report response;
125062306a36Sopenharmony_ci	int ret;
125162306a36Sopenharmony_ci	u8 *params = (u8 *)response.fap.params;
125262306a36Sopenharmony_ci
125362306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
125462306a36Sopenharmony_ci					  CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS,
125562306a36Sopenharmony_ci					  NULL, 0, &response);
125662306a36Sopenharmony_ci	/* Ignore these intermittent errors */
125762306a36Sopenharmony_ci	if (ret == HIDPP_ERROR_RESOURCE_ERROR)
125862306a36Sopenharmony_ci		return -EIO;
125962306a36Sopenharmony_ci	if (ret > 0) {
126062306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
126162306a36Sopenharmony_ci			__func__, ret);
126262306a36Sopenharmony_ci		return -EPROTO;
126362306a36Sopenharmony_ci	}
126462306a36Sopenharmony_ci	if (ret)
126562306a36Sopenharmony_ci		return ret;
126662306a36Sopenharmony_ci
126762306a36Sopenharmony_ci	*status = hidpp20_batterylevel_map_status_capacity(params, capacity,
126862306a36Sopenharmony_ci							   next_capacity,
126962306a36Sopenharmony_ci							   level);
127062306a36Sopenharmony_ci
127162306a36Sopenharmony_ci	return 0;
127262306a36Sopenharmony_ci}
127362306a36Sopenharmony_ci
127462306a36Sopenharmony_cistatic int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp,
127562306a36Sopenharmony_ci						  u8 feature_index)
127662306a36Sopenharmony_ci{
127762306a36Sopenharmony_ci	struct hidpp_report response;
127862306a36Sopenharmony_ci	int ret;
127962306a36Sopenharmony_ci	u8 *params = (u8 *)response.fap.params;
128062306a36Sopenharmony_ci	unsigned int level_count, flags;
128162306a36Sopenharmony_ci
128262306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
128362306a36Sopenharmony_ci					  CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY,
128462306a36Sopenharmony_ci					  NULL, 0, &response);
128562306a36Sopenharmony_ci	if (ret > 0) {
128662306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
128762306a36Sopenharmony_ci			__func__, ret);
128862306a36Sopenharmony_ci		return -EPROTO;
128962306a36Sopenharmony_ci	}
129062306a36Sopenharmony_ci	if (ret)
129162306a36Sopenharmony_ci		return ret;
129262306a36Sopenharmony_ci
129362306a36Sopenharmony_ci	level_count = params[0];
129462306a36Sopenharmony_ci	flags = params[1];
129562306a36Sopenharmony_ci
129662306a36Sopenharmony_ci	if (level_count < 10 || !(flags & FLAG_BATTERY_LEVEL_MILEAGE))
129762306a36Sopenharmony_ci		hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
129862306a36Sopenharmony_ci	else
129962306a36Sopenharmony_ci		hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;
130062306a36Sopenharmony_ci
130162306a36Sopenharmony_ci	return 0;
130262306a36Sopenharmony_ci}
130362306a36Sopenharmony_ci
130462306a36Sopenharmony_cistatic int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp)
130562306a36Sopenharmony_ci{
130662306a36Sopenharmony_ci	u8 feature_type;
130762306a36Sopenharmony_ci	int ret;
130862306a36Sopenharmony_ci	int status, capacity, next_capacity, level;
130962306a36Sopenharmony_ci
131062306a36Sopenharmony_ci	if (hidpp->battery.feature_index == 0xff) {
131162306a36Sopenharmony_ci		ret = hidpp_root_get_feature(hidpp,
131262306a36Sopenharmony_ci					     HIDPP_PAGE_BATTERY_LEVEL_STATUS,
131362306a36Sopenharmony_ci					     &hidpp->battery.feature_index,
131462306a36Sopenharmony_ci					     &feature_type);
131562306a36Sopenharmony_ci		if (ret)
131662306a36Sopenharmony_ci			return ret;
131762306a36Sopenharmony_ci	}
131862306a36Sopenharmony_ci
131962306a36Sopenharmony_ci	ret = hidpp20_batterylevel_get_battery_capacity(hidpp,
132062306a36Sopenharmony_ci						hidpp->battery.feature_index,
132162306a36Sopenharmony_ci						&status, &capacity,
132262306a36Sopenharmony_ci						&next_capacity, &level);
132362306a36Sopenharmony_ci	if (ret)
132462306a36Sopenharmony_ci		return ret;
132562306a36Sopenharmony_ci
132662306a36Sopenharmony_ci	ret = hidpp20_batterylevel_get_battery_info(hidpp,
132762306a36Sopenharmony_ci						hidpp->battery.feature_index);
132862306a36Sopenharmony_ci	if (ret)
132962306a36Sopenharmony_ci		return ret;
133062306a36Sopenharmony_ci
133162306a36Sopenharmony_ci	hidpp->battery.status = status;
133262306a36Sopenharmony_ci	hidpp->battery.capacity = capacity;
133362306a36Sopenharmony_ci	hidpp->battery.level = level;
133462306a36Sopenharmony_ci	/* the capacity is only available when discharging or full */
133562306a36Sopenharmony_ci	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
133662306a36Sopenharmony_ci				status == POWER_SUPPLY_STATUS_FULL;
133762306a36Sopenharmony_ci
133862306a36Sopenharmony_ci	return 0;
133962306a36Sopenharmony_ci}
134062306a36Sopenharmony_ci
134162306a36Sopenharmony_cistatic int hidpp20_battery_event_1000(struct hidpp_device *hidpp,
134262306a36Sopenharmony_ci				 u8 *data, int size)
134362306a36Sopenharmony_ci{
134462306a36Sopenharmony_ci	struct hidpp_report *report = (struct hidpp_report *)data;
134562306a36Sopenharmony_ci	int status, capacity, next_capacity, level;
134662306a36Sopenharmony_ci	bool changed;
134762306a36Sopenharmony_ci
134862306a36Sopenharmony_ci	if (report->fap.feature_index != hidpp->battery.feature_index ||
134962306a36Sopenharmony_ci	    report->fap.funcindex_clientid != EVENT_BATTERY_LEVEL_STATUS_BROADCAST)
135062306a36Sopenharmony_ci		return 0;
135162306a36Sopenharmony_ci
135262306a36Sopenharmony_ci	status = hidpp20_batterylevel_map_status_capacity(report->fap.params,
135362306a36Sopenharmony_ci							  &capacity,
135462306a36Sopenharmony_ci							  &next_capacity,
135562306a36Sopenharmony_ci							  &level);
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_ci	/* the capacity is only available when discharging or full */
135862306a36Sopenharmony_ci	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
135962306a36Sopenharmony_ci				status == POWER_SUPPLY_STATUS_FULL;
136062306a36Sopenharmony_ci
136162306a36Sopenharmony_ci	changed = capacity != hidpp->battery.capacity ||
136262306a36Sopenharmony_ci		  level != hidpp->battery.level ||
136362306a36Sopenharmony_ci		  status != hidpp->battery.status;
136462306a36Sopenharmony_ci
136562306a36Sopenharmony_ci	if (changed) {
136662306a36Sopenharmony_ci		hidpp->battery.level = level;
136762306a36Sopenharmony_ci		hidpp->battery.capacity = capacity;
136862306a36Sopenharmony_ci		hidpp->battery.status = status;
136962306a36Sopenharmony_ci		if (hidpp->battery.ps)
137062306a36Sopenharmony_ci			power_supply_changed(hidpp->battery.ps);
137162306a36Sopenharmony_ci	}
137262306a36Sopenharmony_ci
137362306a36Sopenharmony_ci	return 0;
137462306a36Sopenharmony_ci}
137562306a36Sopenharmony_ci
137662306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
137762306a36Sopenharmony_ci/* 0x1001: Battery voltage                                                    */
137862306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
137962306a36Sopenharmony_ci
138062306a36Sopenharmony_ci#define HIDPP_PAGE_BATTERY_VOLTAGE 0x1001
138162306a36Sopenharmony_ci
138262306a36Sopenharmony_ci#define CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE 0x00
138362306a36Sopenharmony_ci
138462306a36Sopenharmony_ci#define EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST 0x00
138562306a36Sopenharmony_ci
138662306a36Sopenharmony_cistatic int hidpp20_battery_map_status_voltage(u8 data[3], int *voltage,
138762306a36Sopenharmony_ci						int *level, int *charge_type)
138862306a36Sopenharmony_ci{
138962306a36Sopenharmony_ci	int status;
139062306a36Sopenharmony_ci
139162306a36Sopenharmony_ci	long flags = (long) data[2];
139262306a36Sopenharmony_ci	*level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
139362306a36Sopenharmony_ci
139462306a36Sopenharmony_ci	if (flags & 0x80)
139562306a36Sopenharmony_ci		switch (flags & 0x07) {
139662306a36Sopenharmony_ci		case 0:
139762306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_CHARGING;
139862306a36Sopenharmony_ci			break;
139962306a36Sopenharmony_ci		case 1:
140062306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_FULL;
140162306a36Sopenharmony_ci			*level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
140262306a36Sopenharmony_ci			break;
140362306a36Sopenharmony_ci		case 2:
140462306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_NOT_CHARGING;
140562306a36Sopenharmony_ci			break;
140662306a36Sopenharmony_ci		default:
140762306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_UNKNOWN;
140862306a36Sopenharmony_ci			break;
140962306a36Sopenharmony_ci		}
141062306a36Sopenharmony_ci	else
141162306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_DISCHARGING;
141262306a36Sopenharmony_ci
141362306a36Sopenharmony_ci	*charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
141462306a36Sopenharmony_ci	if (test_bit(3, &flags)) {
141562306a36Sopenharmony_ci		*charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
141662306a36Sopenharmony_ci	}
141762306a36Sopenharmony_ci	if (test_bit(4, &flags)) {
141862306a36Sopenharmony_ci		*charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
141962306a36Sopenharmony_ci	}
142062306a36Sopenharmony_ci	if (test_bit(5, &flags)) {
142162306a36Sopenharmony_ci		*level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
142262306a36Sopenharmony_ci	}
142362306a36Sopenharmony_ci
142462306a36Sopenharmony_ci	*voltage = get_unaligned_be16(data);
142562306a36Sopenharmony_ci
142662306a36Sopenharmony_ci	return status;
142762306a36Sopenharmony_ci}
142862306a36Sopenharmony_ci
142962306a36Sopenharmony_cistatic int hidpp20_battery_get_battery_voltage(struct hidpp_device *hidpp,
143062306a36Sopenharmony_ci						 u8 feature_index,
143162306a36Sopenharmony_ci						 int *status, int *voltage,
143262306a36Sopenharmony_ci						 int *level, int *charge_type)
143362306a36Sopenharmony_ci{
143462306a36Sopenharmony_ci	struct hidpp_report response;
143562306a36Sopenharmony_ci	int ret;
143662306a36Sopenharmony_ci	u8 *params = (u8 *)response.fap.params;
143762306a36Sopenharmony_ci
143862306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
143962306a36Sopenharmony_ci					  CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE,
144062306a36Sopenharmony_ci					  NULL, 0, &response);
144162306a36Sopenharmony_ci
144262306a36Sopenharmony_ci	if (ret > 0) {
144362306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
144462306a36Sopenharmony_ci			__func__, ret);
144562306a36Sopenharmony_ci		return -EPROTO;
144662306a36Sopenharmony_ci	}
144762306a36Sopenharmony_ci	if (ret)
144862306a36Sopenharmony_ci		return ret;
144962306a36Sopenharmony_ci
145062306a36Sopenharmony_ci	hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_VOLTAGE;
145162306a36Sopenharmony_ci
145262306a36Sopenharmony_ci	*status = hidpp20_battery_map_status_voltage(params, voltage,
145362306a36Sopenharmony_ci						     level, charge_type);
145462306a36Sopenharmony_ci
145562306a36Sopenharmony_ci	return 0;
145662306a36Sopenharmony_ci}
145762306a36Sopenharmony_ci
145862306a36Sopenharmony_cistatic int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
145962306a36Sopenharmony_ci{
146062306a36Sopenharmony_ci	/* NB: This voltage curve doesn't necessarily map perfectly to all
146162306a36Sopenharmony_ci	 * devices that implement the BATTERY_VOLTAGE feature. This is because
146262306a36Sopenharmony_ci	 * there are a few devices that use different battery technology.
146362306a36Sopenharmony_ci	 */
146462306a36Sopenharmony_ci
146562306a36Sopenharmony_ci	static const int voltages[100] = {
146662306a36Sopenharmony_ci		4186, 4156, 4143, 4133, 4122, 4113, 4103, 4094, 4086, 4075,
146762306a36Sopenharmony_ci		4067, 4059, 4051, 4043, 4035, 4027, 4019, 4011, 4003, 3997,
146862306a36Sopenharmony_ci		3989, 3983, 3976, 3969, 3961, 3955, 3949, 3942, 3935, 3929,
146962306a36Sopenharmony_ci		3922, 3916, 3909, 3902, 3896, 3890, 3883, 3877, 3870, 3865,
147062306a36Sopenharmony_ci		3859, 3853, 3848, 3842, 3837, 3833, 3828, 3824, 3819, 3815,
147162306a36Sopenharmony_ci		3811, 3808, 3804, 3800, 3797, 3793, 3790, 3787, 3784, 3781,
147262306a36Sopenharmony_ci		3778, 3775, 3772, 3770, 3767, 3764, 3762, 3759, 3757, 3754,
147362306a36Sopenharmony_ci		3751, 3748, 3744, 3741, 3737, 3734, 3730, 3726, 3724, 3720,
147462306a36Sopenharmony_ci		3717, 3714, 3710, 3706, 3702, 3697, 3693, 3688, 3683, 3677,
147562306a36Sopenharmony_ci		3671, 3666, 3662, 3658, 3654, 3646, 3633, 3612, 3579, 3537
147662306a36Sopenharmony_ci	};
147762306a36Sopenharmony_ci
147862306a36Sopenharmony_ci	int i;
147962306a36Sopenharmony_ci
148062306a36Sopenharmony_ci	if (unlikely(voltage < 3500 || voltage >= 5000))
148162306a36Sopenharmony_ci		hid_warn_once(hid_dev,
148262306a36Sopenharmony_ci			      "%s: possibly using the wrong voltage curve\n",
148362306a36Sopenharmony_ci			      __func__);
148462306a36Sopenharmony_ci
148562306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(voltages); i++) {
148662306a36Sopenharmony_ci		if (voltage >= voltages[i])
148762306a36Sopenharmony_ci			return ARRAY_SIZE(voltages) - i;
148862306a36Sopenharmony_ci	}
148962306a36Sopenharmony_ci
149062306a36Sopenharmony_ci	return 0;
149162306a36Sopenharmony_ci}
149262306a36Sopenharmony_ci
149362306a36Sopenharmony_cistatic int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp)
149462306a36Sopenharmony_ci{
149562306a36Sopenharmony_ci	u8 feature_type;
149662306a36Sopenharmony_ci	int ret;
149762306a36Sopenharmony_ci	int status, voltage, level, charge_type;
149862306a36Sopenharmony_ci
149962306a36Sopenharmony_ci	if (hidpp->battery.voltage_feature_index == 0xff) {
150062306a36Sopenharmony_ci		ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
150162306a36Sopenharmony_ci					     &hidpp->battery.voltage_feature_index,
150262306a36Sopenharmony_ci					     &feature_type);
150362306a36Sopenharmony_ci		if (ret)
150462306a36Sopenharmony_ci			return ret;
150562306a36Sopenharmony_ci	}
150662306a36Sopenharmony_ci
150762306a36Sopenharmony_ci	ret = hidpp20_battery_get_battery_voltage(hidpp,
150862306a36Sopenharmony_ci						  hidpp->battery.voltage_feature_index,
150962306a36Sopenharmony_ci						  &status, &voltage, &level, &charge_type);
151062306a36Sopenharmony_ci
151162306a36Sopenharmony_ci	if (ret)
151262306a36Sopenharmony_ci		return ret;
151362306a36Sopenharmony_ci
151462306a36Sopenharmony_ci	hidpp->battery.status = status;
151562306a36Sopenharmony_ci	hidpp->battery.voltage = voltage;
151662306a36Sopenharmony_ci	hidpp->battery.capacity = hidpp20_map_battery_capacity(hidpp->hid_dev,
151762306a36Sopenharmony_ci							       voltage);
151862306a36Sopenharmony_ci	hidpp->battery.level = level;
151962306a36Sopenharmony_ci	hidpp->battery.charge_type = charge_type;
152062306a36Sopenharmony_ci	hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING;
152162306a36Sopenharmony_ci
152262306a36Sopenharmony_ci	return 0;
152362306a36Sopenharmony_ci}
152462306a36Sopenharmony_ci
152562306a36Sopenharmony_cistatic int hidpp20_battery_voltage_event(struct hidpp_device *hidpp,
152662306a36Sopenharmony_ci					    u8 *data, int size)
152762306a36Sopenharmony_ci{
152862306a36Sopenharmony_ci	struct hidpp_report *report = (struct hidpp_report *)data;
152962306a36Sopenharmony_ci	int status, voltage, level, charge_type;
153062306a36Sopenharmony_ci
153162306a36Sopenharmony_ci	if (report->fap.feature_index != hidpp->battery.voltage_feature_index ||
153262306a36Sopenharmony_ci		report->fap.funcindex_clientid != EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST)
153362306a36Sopenharmony_ci		return 0;
153462306a36Sopenharmony_ci
153562306a36Sopenharmony_ci	status = hidpp20_battery_map_status_voltage(report->fap.params, &voltage,
153662306a36Sopenharmony_ci						    &level, &charge_type);
153762306a36Sopenharmony_ci
153862306a36Sopenharmony_ci	hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING;
153962306a36Sopenharmony_ci
154062306a36Sopenharmony_ci	if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) {
154162306a36Sopenharmony_ci		hidpp->battery.voltage = voltage;
154262306a36Sopenharmony_ci		hidpp->battery.capacity = hidpp20_map_battery_capacity(hidpp->hid_dev,
154362306a36Sopenharmony_ci								       voltage);
154462306a36Sopenharmony_ci		hidpp->battery.status = status;
154562306a36Sopenharmony_ci		hidpp->battery.level = level;
154662306a36Sopenharmony_ci		hidpp->battery.charge_type = charge_type;
154762306a36Sopenharmony_ci		if (hidpp->battery.ps)
154862306a36Sopenharmony_ci			power_supply_changed(hidpp->battery.ps);
154962306a36Sopenharmony_ci	}
155062306a36Sopenharmony_ci	return 0;
155162306a36Sopenharmony_ci}
155262306a36Sopenharmony_ci
155362306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
155462306a36Sopenharmony_ci/* 0x1004: Unified battery                                                    */
155562306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
155662306a36Sopenharmony_ci
155762306a36Sopenharmony_ci#define HIDPP_PAGE_UNIFIED_BATTERY				0x1004
155862306a36Sopenharmony_ci
155962306a36Sopenharmony_ci#define CMD_UNIFIED_BATTERY_GET_CAPABILITIES			0x00
156062306a36Sopenharmony_ci#define CMD_UNIFIED_BATTERY_GET_STATUS				0x10
156162306a36Sopenharmony_ci
156262306a36Sopenharmony_ci#define EVENT_UNIFIED_BATTERY_STATUS_EVENT			0x00
156362306a36Sopenharmony_ci
156462306a36Sopenharmony_ci#define FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL			BIT(0)
156562306a36Sopenharmony_ci#define FLAG_UNIFIED_BATTERY_LEVEL_LOW				BIT(1)
156662306a36Sopenharmony_ci#define FLAG_UNIFIED_BATTERY_LEVEL_GOOD				BIT(2)
156762306a36Sopenharmony_ci#define FLAG_UNIFIED_BATTERY_LEVEL_FULL				BIT(3)
156862306a36Sopenharmony_ci
156962306a36Sopenharmony_ci#define FLAG_UNIFIED_BATTERY_FLAGS_RECHARGEABLE			BIT(0)
157062306a36Sopenharmony_ci#define FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE		BIT(1)
157162306a36Sopenharmony_ci
157262306a36Sopenharmony_cistatic int hidpp20_unifiedbattery_get_capabilities(struct hidpp_device *hidpp,
157362306a36Sopenharmony_ci						   u8 feature_index)
157462306a36Sopenharmony_ci{
157562306a36Sopenharmony_ci	struct hidpp_report response;
157662306a36Sopenharmony_ci	int ret;
157762306a36Sopenharmony_ci	u8 *params = (u8 *)response.fap.params;
157862306a36Sopenharmony_ci
157962306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS ||
158062306a36Sopenharmony_ci	    hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) {
158162306a36Sopenharmony_ci		/* we have already set the device capabilities, so let's skip */
158262306a36Sopenharmony_ci		return 0;
158362306a36Sopenharmony_ci	}
158462306a36Sopenharmony_ci
158562306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
158662306a36Sopenharmony_ci					  CMD_UNIFIED_BATTERY_GET_CAPABILITIES,
158762306a36Sopenharmony_ci					  NULL, 0, &response);
158862306a36Sopenharmony_ci	/* Ignore these intermittent errors */
158962306a36Sopenharmony_ci	if (ret == HIDPP_ERROR_RESOURCE_ERROR)
159062306a36Sopenharmony_ci		return -EIO;
159162306a36Sopenharmony_ci	if (ret > 0) {
159262306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
159362306a36Sopenharmony_ci			__func__, ret);
159462306a36Sopenharmony_ci		return -EPROTO;
159562306a36Sopenharmony_ci	}
159662306a36Sopenharmony_ci	if (ret)
159762306a36Sopenharmony_ci		return ret;
159862306a36Sopenharmony_ci
159962306a36Sopenharmony_ci	/*
160062306a36Sopenharmony_ci	 * If the device supports state of charge (battery percentage) we won't
160162306a36Sopenharmony_ci	 * export the battery level information. there are 4 possible battery
160262306a36Sopenharmony_ci	 * levels and they all are optional, this means that the device might
160362306a36Sopenharmony_ci	 * not support any of them, we are just better off with the battery
160462306a36Sopenharmony_ci	 * percentage.
160562306a36Sopenharmony_ci	 */
160662306a36Sopenharmony_ci	if (params[1] & FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE) {
160762306a36Sopenharmony_ci		hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_PERCENTAGE;
160862306a36Sopenharmony_ci		hidpp->battery.supported_levels_1004 = 0;
160962306a36Sopenharmony_ci	} else {
161062306a36Sopenharmony_ci		hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
161162306a36Sopenharmony_ci		hidpp->battery.supported_levels_1004 = params[0];
161262306a36Sopenharmony_ci	}
161362306a36Sopenharmony_ci
161462306a36Sopenharmony_ci	return 0;
161562306a36Sopenharmony_ci}
161662306a36Sopenharmony_ci
161762306a36Sopenharmony_cistatic int hidpp20_unifiedbattery_map_status(struct hidpp_device *hidpp,
161862306a36Sopenharmony_ci					     u8 charging_status,
161962306a36Sopenharmony_ci					     u8 external_power_status)
162062306a36Sopenharmony_ci{
162162306a36Sopenharmony_ci	int status;
162262306a36Sopenharmony_ci
162362306a36Sopenharmony_ci	switch (charging_status) {
162462306a36Sopenharmony_ci		case 0: /* discharging */
162562306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_DISCHARGING;
162662306a36Sopenharmony_ci			break;
162762306a36Sopenharmony_ci		case 1: /* charging */
162862306a36Sopenharmony_ci		case 2: /* charging slow */
162962306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_CHARGING;
163062306a36Sopenharmony_ci			break;
163162306a36Sopenharmony_ci		case 3: /* complete */
163262306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_FULL;
163362306a36Sopenharmony_ci			break;
163462306a36Sopenharmony_ci		case 4: /* error */
163562306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_NOT_CHARGING;
163662306a36Sopenharmony_ci			hid_info(hidpp->hid_dev, "%s: charging error",
163762306a36Sopenharmony_ci				 hidpp->name);
163862306a36Sopenharmony_ci			break;
163962306a36Sopenharmony_ci		default:
164062306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_NOT_CHARGING;
164162306a36Sopenharmony_ci			break;
164262306a36Sopenharmony_ci	}
164362306a36Sopenharmony_ci
164462306a36Sopenharmony_ci	return status;
164562306a36Sopenharmony_ci}
164662306a36Sopenharmony_ci
164762306a36Sopenharmony_cistatic int hidpp20_unifiedbattery_map_level(struct hidpp_device *hidpp,
164862306a36Sopenharmony_ci					    u8 battery_level)
164962306a36Sopenharmony_ci{
165062306a36Sopenharmony_ci	/* cler unsupported level bits */
165162306a36Sopenharmony_ci	battery_level &= hidpp->battery.supported_levels_1004;
165262306a36Sopenharmony_ci
165362306a36Sopenharmony_ci	if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_FULL)
165462306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
165562306a36Sopenharmony_ci	else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_GOOD)
165662306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
165762306a36Sopenharmony_ci	else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_LOW)
165862306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
165962306a36Sopenharmony_ci	else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL)
166062306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
166162306a36Sopenharmony_ci
166262306a36Sopenharmony_ci	return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
166362306a36Sopenharmony_ci}
166462306a36Sopenharmony_ci
166562306a36Sopenharmony_cistatic int hidpp20_unifiedbattery_get_status(struct hidpp_device *hidpp,
166662306a36Sopenharmony_ci					     u8 feature_index,
166762306a36Sopenharmony_ci					     u8 *state_of_charge,
166862306a36Sopenharmony_ci					     int *status,
166962306a36Sopenharmony_ci					     int *level)
167062306a36Sopenharmony_ci{
167162306a36Sopenharmony_ci	struct hidpp_report response;
167262306a36Sopenharmony_ci	int ret;
167362306a36Sopenharmony_ci	u8 *params = (u8 *)response.fap.params;
167462306a36Sopenharmony_ci
167562306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
167662306a36Sopenharmony_ci					  CMD_UNIFIED_BATTERY_GET_STATUS,
167762306a36Sopenharmony_ci					  NULL, 0, &response);
167862306a36Sopenharmony_ci	/* Ignore these intermittent errors */
167962306a36Sopenharmony_ci	if (ret == HIDPP_ERROR_RESOURCE_ERROR)
168062306a36Sopenharmony_ci		return -EIO;
168162306a36Sopenharmony_ci	if (ret > 0) {
168262306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
168362306a36Sopenharmony_ci			__func__, ret);
168462306a36Sopenharmony_ci		return -EPROTO;
168562306a36Sopenharmony_ci	}
168662306a36Sopenharmony_ci	if (ret)
168762306a36Sopenharmony_ci		return ret;
168862306a36Sopenharmony_ci
168962306a36Sopenharmony_ci	*state_of_charge = params[0];
169062306a36Sopenharmony_ci	*status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]);
169162306a36Sopenharmony_ci	*level = hidpp20_unifiedbattery_map_level(hidpp, params[1]);
169262306a36Sopenharmony_ci
169362306a36Sopenharmony_ci	return 0;
169462306a36Sopenharmony_ci}
169562306a36Sopenharmony_ci
169662306a36Sopenharmony_cistatic int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
169762306a36Sopenharmony_ci{
169862306a36Sopenharmony_ci	u8 feature_type;
169962306a36Sopenharmony_ci	int ret;
170062306a36Sopenharmony_ci	u8 state_of_charge;
170162306a36Sopenharmony_ci	int status, level;
170262306a36Sopenharmony_ci
170362306a36Sopenharmony_ci	if (hidpp->battery.feature_index == 0xff) {
170462306a36Sopenharmony_ci		ret = hidpp_root_get_feature(hidpp,
170562306a36Sopenharmony_ci					     HIDPP_PAGE_UNIFIED_BATTERY,
170662306a36Sopenharmony_ci					     &hidpp->battery.feature_index,
170762306a36Sopenharmony_ci					     &feature_type);
170862306a36Sopenharmony_ci		if (ret)
170962306a36Sopenharmony_ci			return ret;
171062306a36Sopenharmony_ci	}
171162306a36Sopenharmony_ci
171262306a36Sopenharmony_ci	ret = hidpp20_unifiedbattery_get_capabilities(hidpp,
171362306a36Sopenharmony_ci					hidpp->battery.feature_index);
171462306a36Sopenharmony_ci	if (ret)
171562306a36Sopenharmony_ci		return ret;
171662306a36Sopenharmony_ci
171762306a36Sopenharmony_ci	ret = hidpp20_unifiedbattery_get_status(hidpp,
171862306a36Sopenharmony_ci						hidpp->battery.feature_index,
171962306a36Sopenharmony_ci						&state_of_charge,
172062306a36Sopenharmony_ci						&status,
172162306a36Sopenharmony_ci						&level);
172262306a36Sopenharmony_ci	if (ret)
172362306a36Sopenharmony_ci		return ret;
172462306a36Sopenharmony_ci
172562306a36Sopenharmony_ci	hidpp->capabilities |= HIDPP_CAPABILITY_UNIFIED_BATTERY;
172662306a36Sopenharmony_ci	hidpp->battery.capacity = state_of_charge;
172762306a36Sopenharmony_ci	hidpp->battery.status = status;
172862306a36Sopenharmony_ci	hidpp->battery.level = level;
172962306a36Sopenharmony_ci	hidpp->battery.online = true;
173062306a36Sopenharmony_ci
173162306a36Sopenharmony_ci	return 0;
173262306a36Sopenharmony_ci}
173362306a36Sopenharmony_ci
173462306a36Sopenharmony_cistatic int hidpp20_battery_event_1004(struct hidpp_device *hidpp,
173562306a36Sopenharmony_ci				 u8 *data, int size)
173662306a36Sopenharmony_ci{
173762306a36Sopenharmony_ci	struct hidpp_report *report = (struct hidpp_report *)data;
173862306a36Sopenharmony_ci	u8 *params = (u8 *)report->fap.params;
173962306a36Sopenharmony_ci	int state_of_charge, status, level;
174062306a36Sopenharmony_ci	bool changed;
174162306a36Sopenharmony_ci
174262306a36Sopenharmony_ci	if (report->fap.feature_index != hidpp->battery.feature_index ||
174362306a36Sopenharmony_ci	    report->fap.funcindex_clientid != EVENT_UNIFIED_BATTERY_STATUS_EVENT)
174462306a36Sopenharmony_ci		return 0;
174562306a36Sopenharmony_ci
174662306a36Sopenharmony_ci	state_of_charge = params[0];
174762306a36Sopenharmony_ci	status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]);
174862306a36Sopenharmony_ci	level = hidpp20_unifiedbattery_map_level(hidpp, params[1]);
174962306a36Sopenharmony_ci
175062306a36Sopenharmony_ci	changed = status != hidpp->battery.status ||
175162306a36Sopenharmony_ci		  (state_of_charge != hidpp->battery.capacity &&
175262306a36Sopenharmony_ci		   hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) ||
175362306a36Sopenharmony_ci		  (level != hidpp->battery.level &&
175462306a36Sopenharmony_ci		   hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS);
175562306a36Sopenharmony_ci
175662306a36Sopenharmony_ci	if (changed) {
175762306a36Sopenharmony_ci		hidpp->battery.capacity = state_of_charge;
175862306a36Sopenharmony_ci		hidpp->battery.status = status;
175962306a36Sopenharmony_ci		hidpp->battery.level = level;
176062306a36Sopenharmony_ci		if (hidpp->battery.ps)
176162306a36Sopenharmony_ci			power_supply_changed(hidpp->battery.ps);
176262306a36Sopenharmony_ci	}
176362306a36Sopenharmony_ci
176462306a36Sopenharmony_ci	return 0;
176562306a36Sopenharmony_ci}
176662306a36Sopenharmony_ci
176762306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
176862306a36Sopenharmony_ci/* Battery feature helpers                                                    */
176962306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
177062306a36Sopenharmony_ci
177162306a36Sopenharmony_cistatic enum power_supply_property hidpp_battery_props[] = {
177262306a36Sopenharmony_ci	POWER_SUPPLY_PROP_ONLINE,
177362306a36Sopenharmony_ci	POWER_SUPPLY_PROP_STATUS,
177462306a36Sopenharmony_ci	POWER_SUPPLY_PROP_SCOPE,
177562306a36Sopenharmony_ci	POWER_SUPPLY_PROP_MODEL_NAME,
177662306a36Sopenharmony_ci	POWER_SUPPLY_PROP_MANUFACTURER,
177762306a36Sopenharmony_ci	POWER_SUPPLY_PROP_SERIAL_NUMBER,
177862306a36Sopenharmony_ci	0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY, */
177962306a36Sopenharmony_ci	0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY_LEVEL, */
178062306a36Sopenharmony_ci	0, /* placeholder for POWER_SUPPLY_PROP_VOLTAGE_NOW, */
178162306a36Sopenharmony_ci};
178262306a36Sopenharmony_ci
178362306a36Sopenharmony_cistatic int hidpp_battery_get_property(struct power_supply *psy,
178462306a36Sopenharmony_ci				      enum power_supply_property psp,
178562306a36Sopenharmony_ci				      union power_supply_propval *val)
178662306a36Sopenharmony_ci{
178762306a36Sopenharmony_ci	struct hidpp_device *hidpp = power_supply_get_drvdata(psy);
178862306a36Sopenharmony_ci	int ret = 0;
178962306a36Sopenharmony_ci
179062306a36Sopenharmony_ci	switch(psp) {
179162306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_STATUS:
179262306a36Sopenharmony_ci			val->intval = hidpp->battery.status;
179362306a36Sopenharmony_ci			break;
179462306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_CAPACITY:
179562306a36Sopenharmony_ci			val->intval = hidpp->battery.capacity;
179662306a36Sopenharmony_ci			break;
179762306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
179862306a36Sopenharmony_ci			val->intval = hidpp->battery.level;
179962306a36Sopenharmony_ci			break;
180062306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_SCOPE:
180162306a36Sopenharmony_ci			val->intval = POWER_SUPPLY_SCOPE_DEVICE;
180262306a36Sopenharmony_ci			break;
180362306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_ONLINE:
180462306a36Sopenharmony_ci			val->intval = hidpp->battery.online;
180562306a36Sopenharmony_ci			break;
180662306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_MODEL_NAME:
180762306a36Sopenharmony_ci			if (!strncmp(hidpp->name, "Logitech ", 9))
180862306a36Sopenharmony_ci				val->strval = hidpp->name + 9;
180962306a36Sopenharmony_ci			else
181062306a36Sopenharmony_ci				val->strval = hidpp->name;
181162306a36Sopenharmony_ci			break;
181262306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_MANUFACTURER:
181362306a36Sopenharmony_ci			val->strval = "Logitech";
181462306a36Sopenharmony_ci			break;
181562306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_SERIAL_NUMBER:
181662306a36Sopenharmony_ci			val->strval = hidpp->hid_dev->uniq;
181762306a36Sopenharmony_ci			break;
181862306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_VOLTAGE_NOW:
181962306a36Sopenharmony_ci			/* hardware reports voltage in mV. sysfs expects uV */
182062306a36Sopenharmony_ci			val->intval = hidpp->battery.voltage * 1000;
182162306a36Sopenharmony_ci			break;
182262306a36Sopenharmony_ci		case POWER_SUPPLY_PROP_CHARGE_TYPE:
182362306a36Sopenharmony_ci			val->intval = hidpp->battery.charge_type;
182462306a36Sopenharmony_ci			break;
182562306a36Sopenharmony_ci		default:
182662306a36Sopenharmony_ci			ret = -EINVAL;
182762306a36Sopenharmony_ci			break;
182862306a36Sopenharmony_ci	}
182962306a36Sopenharmony_ci
183062306a36Sopenharmony_ci	return ret;
183162306a36Sopenharmony_ci}
183262306a36Sopenharmony_ci
183362306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
183462306a36Sopenharmony_ci/* 0x1d4b: Wireless device status                                             */
183562306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
183662306a36Sopenharmony_ci#define HIDPP_PAGE_WIRELESS_DEVICE_STATUS			0x1d4b
183762306a36Sopenharmony_ci
183862306a36Sopenharmony_cistatic int hidpp_get_wireless_feature_index(struct hidpp_device *hidpp, u8 *feature_index)
183962306a36Sopenharmony_ci{
184062306a36Sopenharmony_ci	u8 feature_type;
184162306a36Sopenharmony_ci	int ret;
184262306a36Sopenharmony_ci
184362306a36Sopenharmony_ci	ret = hidpp_root_get_feature(hidpp,
184462306a36Sopenharmony_ci				     HIDPP_PAGE_WIRELESS_DEVICE_STATUS,
184562306a36Sopenharmony_ci				     feature_index, &feature_type);
184662306a36Sopenharmony_ci
184762306a36Sopenharmony_ci	return ret;
184862306a36Sopenharmony_ci}
184962306a36Sopenharmony_ci
185062306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
185162306a36Sopenharmony_ci/* 0x1f20: ADC measurement                                                    */
185262306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
185362306a36Sopenharmony_ci
185462306a36Sopenharmony_ci#define HIDPP_PAGE_ADC_MEASUREMENT 0x1f20
185562306a36Sopenharmony_ci
185662306a36Sopenharmony_ci#define CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT 0x00
185762306a36Sopenharmony_ci
185862306a36Sopenharmony_ci#define EVENT_ADC_MEASUREMENT_STATUS_BROADCAST 0x00
185962306a36Sopenharmony_ci
186062306a36Sopenharmony_cistatic int hidpp20_map_adc_measurement_1f20_capacity(struct hid_device *hid_dev, int voltage)
186162306a36Sopenharmony_ci{
186262306a36Sopenharmony_ci	/* NB: This voltage curve doesn't necessarily map perfectly to all
186362306a36Sopenharmony_ci	 * devices that implement the ADC_MEASUREMENT feature. This is because
186462306a36Sopenharmony_ci	 * there are a few devices that use different battery technology.
186562306a36Sopenharmony_ci	 *
186662306a36Sopenharmony_ci	 * Adapted from:
186762306a36Sopenharmony_ci	 * https://github.com/Sapd/HeadsetControl/blob/acd972be0468e039b93aae81221f20a54d2d60f7/src/devices/logitech_g633_g933_935.c#L44-L52
186862306a36Sopenharmony_ci	 */
186962306a36Sopenharmony_ci	static const int voltages[100] = {
187062306a36Sopenharmony_ci		4030, 4024, 4018, 4011, 4003, 3994, 3985, 3975, 3963, 3951,
187162306a36Sopenharmony_ci		3937, 3922, 3907, 3893, 3880, 3868, 3857, 3846, 3837, 3828,
187262306a36Sopenharmony_ci		3820, 3812, 3805, 3798, 3791, 3785, 3779, 3773, 3768, 3762,
187362306a36Sopenharmony_ci		3757, 3752, 3747, 3742, 3738, 3733, 3729, 3724, 3720, 3716,
187462306a36Sopenharmony_ci		3712, 3708, 3704, 3700, 3696, 3692, 3688, 3685, 3681, 3677,
187562306a36Sopenharmony_ci		3674, 3670, 3667, 3663, 3660, 3657, 3653, 3650, 3646, 3643,
187662306a36Sopenharmony_ci		3640, 3637, 3633, 3630, 3627, 3624, 3620, 3617, 3614, 3611,
187762306a36Sopenharmony_ci		3608, 3604, 3601, 3598, 3595, 3592, 3589, 3585, 3582, 3579,
187862306a36Sopenharmony_ci		3576, 3573, 3569, 3566, 3563, 3560, 3556, 3553, 3550, 3546,
187962306a36Sopenharmony_ci		3543, 3539, 3536, 3532, 3529, 3525, 3499, 3466, 3433, 3399,
188062306a36Sopenharmony_ci	};
188162306a36Sopenharmony_ci
188262306a36Sopenharmony_ci	int i;
188362306a36Sopenharmony_ci
188462306a36Sopenharmony_ci	if (voltage == 0)
188562306a36Sopenharmony_ci		return 0;
188662306a36Sopenharmony_ci
188762306a36Sopenharmony_ci	if (unlikely(voltage < 3400 || voltage >= 5000))
188862306a36Sopenharmony_ci		hid_warn_once(hid_dev,
188962306a36Sopenharmony_ci			      "%s: possibly using the wrong voltage curve\n",
189062306a36Sopenharmony_ci			      __func__);
189162306a36Sopenharmony_ci
189262306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(voltages); i++) {
189362306a36Sopenharmony_ci		if (voltage >= voltages[i])
189462306a36Sopenharmony_ci			return ARRAY_SIZE(voltages) - i;
189562306a36Sopenharmony_ci	}
189662306a36Sopenharmony_ci
189762306a36Sopenharmony_ci	return 0;
189862306a36Sopenharmony_ci}
189962306a36Sopenharmony_ci
190062306a36Sopenharmony_cistatic int hidpp20_map_adc_measurement_1f20(u8 data[3], int *voltage)
190162306a36Sopenharmony_ci{
190262306a36Sopenharmony_ci	int status;
190362306a36Sopenharmony_ci	u8 flags;
190462306a36Sopenharmony_ci
190562306a36Sopenharmony_ci	flags = data[2];
190662306a36Sopenharmony_ci
190762306a36Sopenharmony_ci	switch (flags) {
190862306a36Sopenharmony_ci	case 0x01:
190962306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_DISCHARGING;
191062306a36Sopenharmony_ci		break;
191162306a36Sopenharmony_ci	case 0x03:
191262306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_CHARGING;
191362306a36Sopenharmony_ci		break;
191462306a36Sopenharmony_ci	case 0x07:
191562306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_FULL;
191662306a36Sopenharmony_ci		break;
191762306a36Sopenharmony_ci	case 0x0F:
191862306a36Sopenharmony_ci	default:
191962306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_UNKNOWN;
192062306a36Sopenharmony_ci		break;
192162306a36Sopenharmony_ci	}
192262306a36Sopenharmony_ci
192362306a36Sopenharmony_ci	*voltage = get_unaligned_be16(data);
192462306a36Sopenharmony_ci
192562306a36Sopenharmony_ci	dbg_hid("Parsed 1f20 data as flag 0x%02x voltage %dmV\n",
192662306a36Sopenharmony_ci		flags, *voltage);
192762306a36Sopenharmony_ci
192862306a36Sopenharmony_ci	return status;
192962306a36Sopenharmony_ci}
193062306a36Sopenharmony_ci
193162306a36Sopenharmony_ci/* Return value is whether the device is online */
193262306a36Sopenharmony_cistatic bool hidpp20_get_adc_measurement_1f20(struct hidpp_device *hidpp,
193362306a36Sopenharmony_ci						 u8 feature_index,
193462306a36Sopenharmony_ci						 int *status, int *voltage)
193562306a36Sopenharmony_ci{
193662306a36Sopenharmony_ci	struct hidpp_report response;
193762306a36Sopenharmony_ci	int ret;
193862306a36Sopenharmony_ci	u8 *params = (u8 *)response.fap.params;
193962306a36Sopenharmony_ci
194062306a36Sopenharmony_ci	*status = POWER_SUPPLY_STATUS_UNKNOWN;
194162306a36Sopenharmony_ci	*voltage = 0;
194262306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
194362306a36Sopenharmony_ci					  CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT,
194462306a36Sopenharmony_ci					  NULL, 0, &response);
194562306a36Sopenharmony_ci
194662306a36Sopenharmony_ci	if (ret > 0) {
194762306a36Sopenharmony_ci		hid_dbg(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
194862306a36Sopenharmony_ci			__func__, ret);
194962306a36Sopenharmony_ci		return false;
195062306a36Sopenharmony_ci	}
195162306a36Sopenharmony_ci
195262306a36Sopenharmony_ci	*status = hidpp20_map_adc_measurement_1f20(params, voltage);
195362306a36Sopenharmony_ci	return true;
195462306a36Sopenharmony_ci}
195562306a36Sopenharmony_ci
195662306a36Sopenharmony_cistatic int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
195762306a36Sopenharmony_ci{
195862306a36Sopenharmony_ci	u8 feature_type;
195962306a36Sopenharmony_ci
196062306a36Sopenharmony_ci	if (hidpp->battery.adc_measurement_feature_index == 0xff) {
196162306a36Sopenharmony_ci		int ret;
196262306a36Sopenharmony_ci
196362306a36Sopenharmony_ci		ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
196462306a36Sopenharmony_ci					     &hidpp->battery.adc_measurement_feature_index,
196562306a36Sopenharmony_ci					     &feature_type);
196662306a36Sopenharmony_ci		if (ret)
196762306a36Sopenharmony_ci			return ret;
196862306a36Sopenharmony_ci
196962306a36Sopenharmony_ci		hidpp->capabilities |= HIDPP_CAPABILITY_ADC_MEASUREMENT;
197062306a36Sopenharmony_ci	}
197162306a36Sopenharmony_ci
197262306a36Sopenharmony_ci	hidpp->battery.online = hidpp20_get_adc_measurement_1f20(hidpp,
197362306a36Sopenharmony_ci								 hidpp->battery.adc_measurement_feature_index,
197462306a36Sopenharmony_ci								 &hidpp->battery.status,
197562306a36Sopenharmony_ci								 &hidpp->battery.voltage);
197662306a36Sopenharmony_ci	hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev,
197762306a36Sopenharmony_ci									    hidpp->battery.voltage);
197862306a36Sopenharmony_ci	hidpp_update_usb_wireless_status(hidpp);
197962306a36Sopenharmony_ci
198062306a36Sopenharmony_ci	return 0;
198162306a36Sopenharmony_ci}
198262306a36Sopenharmony_ci
198362306a36Sopenharmony_cistatic int hidpp20_adc_measurement_event_1f20(struct hidpp_device *hidpp,
198462306a36Sopenharmony_ci					    u8 *data, int size)
198562306a36Sopenharmony_ci{
198662306a36Sopenharmony_ci	struct hidpp_report *report = (struct hidpp_report *)data;
198762306a36Sopenharmony_ci	int status, voltage;
198862306a36Sopenharmony_ci
198962306a36Sopenharmony_ci	if (report->fap.feature_index != hidpp->battery.adc_measurement_feature_index ||
199062306a36Sopenharmony_ci		report->fap.funcindex_clientid != EVENT_ADC_MEASUREMENT_STATUS_BROADCAST)
199162306a36Sopenharmony_ci		return 0;
199262306a36Sopenharmony_ci
199362306a36Sopenharmony_ci	status = hidpp20_map_adc_measurement_1f20(report->fap.params, &voltage);
199462306a36Sopenharmony_ci
199562306a36Sopenharmony_ci	hidpp->battery.online = status != POWER_SUPPLY_STATUS_UNKNOWN;
199662306a36Sopenharmony_ci
199762306a36Sopenharmony_ci	if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) {
199862306a36Sopenharmony_ci		hidpp->battery.status = status;
199962306a36Sopenharmony_ci		hidpp->battery.voltage = voltage;
200062306a36Sopenharmony_ci		hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev, voltage);
200162306a36Sopenharmony_ci		if (hidpp->battery.ps)
200262306a36Sopenharmony_ci			power_supply_changed(hidpp->battery.ps);
200362306a36Sopenharmony_ci		hidpp_update_usb_wireless_status(hidpp);
200462306a36Sopenharmony_ci	}
200562306a36Sopenharmony_ci	return 0;
200662306a36Sopenharmony_ci}
200762306a36Sopenharmony_ci
200862306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
200962306a36Sopenharmony_ci/* 0x2120: Hi-resolution scrolling                                            */
201062306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
201162306a36Sopenharmony_ci
201262306a36Sopenharmony_ci#define HIDPP_PAGE_HI_RESOLUTION_SCROLLING			0x2120
201362306a36Sopenharmony_ci
201462306a36Sopenharmony_ci#define CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE	0x10
201562306a36Sopenharmony_ci
201662306a36Sopenharmony_cistatic int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp,
201762306a36Sopenharmony_ci	bool enabled, u8 *multiplier)
201862306a36Sopenharmony_ci{
201962306a36Sopenharmony_ci	u8 feature_index;
202062306a36Sopenharmony_ci	u8 feature_type;
202162306a36Sopenharmony_ci	int ret;
202262306a36Sopenharmony_ci	u8 params[1];
202362306a36Sopenharmony_ci	struct hidpp_report response;
202462306a36Sopenharmony_ci
202562306a36Sopenharmony_ci	ret = hidpp_root_get_feature(hidpp,
202662306a36Sopenharmony_ci				     HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
202762306a36Sopenharmony_ci				     &feature_index,
202862306a36Sopenharmony_ci				     &feature_type);
202962306a36Sopenharmony_ci	if (ret)
203062306a36Sopenharmony_ci		return ret;
203162306a36Sopenharmony_ci
203262306a36Sopenharmony_ci	params[0] = enabled ? BIT(0) : 0;
203362306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
203462306a36Sopenharmony_ci					  CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE,
203562306a36Sopenharmony_ci					  params, sizeof(params), &response);
203662306a36Sopenharmony_ci	if (ret)
203762306a36Sopenharmony_ci		return ret;
203862306a36Sopenharmony_ci	*multiplier = response.fap.params[1];
203962306a36Sopenharmony_ci	return 0;
204062306a36Sopenharmony_ci}
204162306a36Sopenharmony_ci
204262306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
204362306a36Sopenharmony_ci/* 0x2121: HiRes Wheel                                                        */
204462306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
204562306a36Sopenharmony_ci
204662306a36Sopenharmony_ci#define HIDPP_PAGE_HIRES_WHEEL		0x2121
204762306a36Sopenharmony_ci
204862306a36Sopenharmony_ci#define CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY	0x00
204962306a36Sopenharmony_ci#define CMD_HIRES_WHEEL_SET_WHEEL_MODE		0x20
205062306a36Sopenharmony_ci
205162306a36Sopenharmony_cistatic int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
205262306a36Sopenharmony_ci	u8 *multiplier)
205362306a36Sopenharmony_ci{
205462306a36Sopenharmony_ci	u8 feature_index;
205562306a36Sopenharmony_ci	u8 feature_type;
205662306a36Sopenharmony_ci	int ret;
205762306a36Sopenharmony_ci	struct hidpp_report response;
205862306a36Sopenharmony_ci
205962306a36Sopenharmony_ci	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
206062306a36Sopenharmony_ci				     &feature_index, &feature_type);
206162306a36Sopenharmony_ci	if (ret)
206262306a36Sopenharmony_ci		goto return_default;
206362306a36Sopenharmony_ci
206462306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
206562306a36Sopenharmony_ci					  CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY,
206662306a36Sopenharmony_ci					  NULL, 0, &response);
206762306a36Sopenharmony_ci	if (ret)
206862306a36Sopenharmony_ci		goto return_default;
206962306a36Sopenharmony_ci
207062306a36Sopenharmony_ci	*multiplier = response.fap.params[0];
207162306a36Sopenharmony_ci	return 0;
207262306a36Sopenharmony_cireturn_default:
207362306a36Sopenharmony_ci	hid_warn(hidpp->hid_dev,
207462306a36Sopenharmony_ci		 "Couldn't get wheel multiplier (error %d)\n", ret);
207562306a36Sopenharmony_ci	return ret;
207662306a36Sopenharmony_ci}
207762306a36Sopenharmony_ci
207862306a36Sopenharmony_cistatic int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert,
207962306a36Sopenharmony_ci	bool high_resolution, bool use_hidpp)
208062306a36Sopenharmony_ci{
208162306a36Sopenharmony_ci	u8 feature_index;
208262306a36Sopenharmony_ci	u8 feature_type;
208362306a36Sopenharmony_ci	int ret;
208462306a36Sopenharmony_ci	u8 params[1];
208562306a36Sopenharmony_ci	struct hidpp_report response;
208662306a36Sopenharmony_ci
208762306a36Sopenharmony_ci	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
208862306a36Sopenharmony_ci				     &feature_index, &feature_type);
208962306a36Sopenharmony_ci	if (ret)
209062306a36Sopenharmony_ci		return ret;
209162306a36Sopenharmony_ci
209262306a36Sopenharmony_ci	params[0] = (invert          ? BIT(2) : 0) |
209362306a36Sopenharmony_ci		    (high_resolution ? BIT(1) : 0) |
209462306a36Sopenharmony_ci		    (use_hidpp       ? BIT(0) : 0);
209562306a36Sopenharmony_ci
209662306a36Sopenharmony_ci	return hidpp_send_fap_command_sync(hidpp, feature_index,
209762306a36Sopenharmony_ci					   CMD_HIRES_WHEEL_SET_WHEEL_MODE,
209862306a36Sopenharmony_ci					   params, sizeof(params), &response);
209962306a36Sopenharmony_ci}
210062306a36Sopenharmony_ci
210162306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
210262306a36Sopenharmony_ci/* 0x4301: Solar Keyboard                                                     */
210362306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
210462306a36Sopenharmony_ci
210562306a36Sopenharmony_ci#define HIDPP_PAGE_SOLAR_KEYBOARD			0x4301
210662306a36Sopenharmony_ci
210762306a36Sopenharmony_ci#define CMD_SOLAR_SET_LIGHT_MEASURE			0x00
210862306a36Sopenharmony_ci
210962306a36Sopenharmony_ci#define EVENT_SOLAR_BATTERY_BROADCAST			0x00
211062306a36Sopenharmony_ci#define EVENT_SOLAR_BATTERY_LIGHT_MEASURE		0x10
211162306a36Sopenharmony_ci#define EVENT_SOLAR_CHECK_LIGHT_BUTTON			0x20
211262306a36Sopenharmony_ci
211362306a36Sopenharmony_cistatic int hidpp_solar_request_battery_event(struct hidpp_device *hidpp)
211462306a36Sopenharmony_ci{
211562306a36Sopenharmony_ci	struct hidpp_report response;
211662306a36Sopenharmony_ci	u8 params[2] = { 1, 1 };
211762306a36Sopenharmony_ci	u8 feature_type;
211862306a36Sopenharmony_ci	int ret;
211962306a36Sopenharmony_ci
212062306a36Sopenharmony_ci	if (hidpp->battery.feature_index == 0xff) {
212162306a36Sopenharmony_ci		ret = hidpp_root_get_feature(hidpp,
212262306a36Sopenharmony_ci					     HIDPP_PAGE_SOLAR_KEYBOARD,
212362306a36Sopenharmony_ci					     &hidpp->battery.solar_feature_index,
212462306a36Sopenharmony_ci					     &feature_type);
212562306a36Sopenharmony_ci		if (ret)
212662306a36Sopenharmony_ci			return ret;
212762306a36Sopenharmony_ci	}
212862306a36Sopenharmony_ci
212962306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp,
213062306a36Sopenharmony_ci					  hidpp->battery.solar_feature_index,
213162306a36Sopenharmony_ci					  CMD_SOLAR_SET_LIGHT_MEASURE,
213262306a36Sopenharmony_ci					  params, 2, &response);
213362306a36Sopenharmony_ci	if (ret > 0) {
213462306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
213562306a36Sopenharmony_ci			__func__, ret);
213662306a36Sopenharmony_ci		return -EPROTO;
213762306a36Sopenharmony_ci	}
213862306a36Sopenharmony_ci	if (ret)
213962306a36Sopenharmony_ci		return ret;
214062306a36Sopenharmony_ci
214162306a36Sopenharmony_ci	hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;
214262306a36Sopenharmony_ci
214362306a36Sopenharmony_ci	return 0;
214462306a36Sopenharmony_ci}
214562306a36Sopenharmony_ci
214662306a36Sopenharmony_cistatic int hidpp_solar_battery_event(struct hidpp_device *hidpp,
214762306a36Sopenharmony_ci				     u8 *data, int size)
214862306a36Sopenharmony_ci{
214962306a36Sopenharmony_ci	struct hidpp_report *report = (struct hidpp_report *)data;
215062306a36Sopenharmony_ci	int capacity, lux, status;
215162306a36Sopenharmony_ci	u8 function;
215262306a36Sopenharmony_ci
215362306a36Sopenharmony_ci	function = report->fap.funcindex_clientid;
215462306a36Sopenharmony_ci
215562306a36Sopenharmony_ci
215662306a36Sopenharmony_ci	if (report->fap.feature_index != hidpp->battery.solar_feature_index ||
215762306a36Sopenharmony_ci	    !(function == EVENT_SOLAR_BATTERY_BROADCAST ||
215862306a36Sopenharmony_ci	      function == EVENT_SOLAR_BATTERY_LIGHT_MEASURE ||
215962306a36Sopenharmony_ci	      function == EVENT_SOLAR_CHECK_LIGHT_BUTTON))
216062306a36Sopenharmony_ci		return 0;
216162306a36Sopenharmony_ci
216262306a36Sopenharmony_ci	capacity = report->fap.params[0];
216362306a36Sopenharmony_ci
216462306a36Sopenharmony_ci	switch (function) {
216562306a36Sopenharmony_ci	case EVENT_SOLAR_BATTERY_LIGHT_MEASURE:
216662306a36Sopenharmony_ci		lux = (report->fap.params[1] << 8) | report->fap.params[2];
216762306a36Sopenharmony_ci		if (lux > 200)
216862306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_CHARGING;
216962306a36Sopenharmony_ci		else
217062306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_DISCHARGING;
217162306a36Sopenharmony_ci		break;
217262306a36Sopenharmony_ci	case EVENT_SOLAR_CHECK_LIGHT_BUTTON:
217362306a36Sopenharmony_ci	default:
217462306a36Sopenharmony_ci		if (capacity < hidpp->battery.capacity)
217562306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_DISCHARGING;
217662306a36Sopenharmony_ci		else
217762306a36Sopenharmony_ci			status = POWER_SUPPLY_STATUS_CHARGING;
217862306a36Sopenharmony_ci
217962306a36Sopenharmony_ci	}
218062306a36Sopenharmony_ci
218162306a36Sopenharmony_ci	if (capacity == 100)
218262306a36Sopenharmony_ci		status = POWER_SUPPLY_STATUS_FULL;
218362306a36Sopenharmony_ci
218462306a36Sopenharmony_ci	hidpp->battery.online = true;
218562306a36Sopenharmony_ci	if (capacity != hidpp->battery.capacity ||
218662306a36Sopenharmony_ci	    status != hidpp->battery.status) {
218762306a36Sopenharmony_ci		hidpp->battery.capacity = capacity;
218862306a36Sopenharmony_ci		hidpp->battery.status = status;
218962306a36Sopenharmony_ci		if (hidpp->battery.ps)
219062306a36Sopenharmony_ci			power_supply_changed(hidpp->battery.ps);
219162306a36Sopenharmony_ci	}
219262306a36Sopenharmony_ci
219362306a36Sopenharmony_ci	return 0;
219462306a36Sopenharmony_ci}
219562306a36Sopenharmony_ci
219662306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
219762306a36Sopenharmony_ci/* 0x6010: Touchpad FW items                                                  */
219862306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
219962306a36Sopenharmony_ci
220062306a36Sopenharmony_ci#define HIDPP_PAGE_TOUCHPAD_FW_ITEMS			0x6010
220162306a36Sopenharmony_ci
220262306a36Sopenharmony_ci#define CMD_TOUCHPAD_FW_ITEMS_SET			0x10
220362306a36Sopenharmony_ci
220462306a36Sopenharmony_cistruct hidpp_touchpad_fw_items {
220562306a36Sopenharmony_ci	uint8_t presence;
220662306a36Sopenharmony_ci	uint8_t desired_state;
220762306a36Sopenharmony_ci	uint8_t state;
220862306a36Sopenharmony_ci	uint8_t persistent;
220962306a36Sopenharmony_ci};
221062306a36Sopenharmony_ci
221162306a36Sopenharmony_ci/*
221262306a36Sopenharmony_ci * send a set state command to the device by reading the current items->state
221362306a36Sopenharmony_ci * field. items is then filled with the current state.
221462306a36Sopenharmony_ci */
221562306a36Sopenharmony_cistatic int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp,
221662306a36Sopenharmony_ci				       u8 feature_index,
221762306a36Sopenharmony_ci				       struct hidpp_touchpad_fw_items *items)
221862306a36Sopenharmony_ci{
221962306a36Sopenharmony_ci	struct hidpp_report response;
222062306a36Sopenharmony_ci	int ret;
222162306a36Sopenharmony_ci	u8 *params = (u8 *)response.fap.params;
222262306a36Sopenharmony_ci
222362306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
222462306a36Sopenharmony_ci		CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 1, &response);
222562306a36Sopenharmony_ci
222662306a36Sopenharmony_ci	if (ret > 0) {
222762306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
222862306a36Sopenharmony_ci			__func__, ret);
222962306a36Sopenharmony_ci		return -EPROTO;
223062306a36Sopenharmony_ci	}
223162306a36Sopenharmony_ci	if (ret)
223262306a36Sopenharmony_ci		return ret;
223362306a36Sopenharmony_ci
223462306a36Sopenharmony_ci	items->presence = params[0];
223562306a36Sopenharmony_ci	items->desired_state = params[1];
223662306a36Sopenharmony_ci	items->state = params[2];
223762306a36Sopenharmony_ci	items->persistent = params[3];
223862306a36Sopenharmony_ci
223962306a36Sopenharmony_ci	return 0;
224062306a36Sopenharmony_ci}
224162306a36Sopenharmony_ci
224262306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
224362306a36Sopenharmony_ci/* 0x6100: TouchPadRawXY                                                      */
224462306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
224562306a36Sopenharmony_ci
224662306a36Sopenharmony_ci#define HIDPP_PAGE_TOUCHPAD_RAW_XY			0x6100
224762306a36Sopenharmony_ci
224862306a36Sopenharmony_ci#define CMD_TOUCHPAD_GET_RAW_INFO			0x00
224962306a36Sopenharmony_ci#define CMD_TOUCHPAD_SET_RAW_REPORT_STATE		0x20
225062306a36Sopenharmony_ci
225162306a36Sopenharmony_ci#define EVENT_TOUCHPAD_RAW_XY				0x00
225262306a36Sopenharmony_ci
225362306a36Sopenharmony_ci#define TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT		0x01
225462306a36Sopenharmony_ci#define TOUCHPAD_RAW_XY_ORIGIN_UPPER_LEFT		0x03
225562306a36Sopenharmony_ci
225662306a36Sopenharmony_cistruct hidpp_touchpad_raw_info {
225762306a36Sopenharmony_ci	u16 x_size;
225862306a36Sopenharmony_ci	u16 y_size;
225962306a36Sopenharmony_ci	u8 z_range;
226062306a36Sopenharmony_ci	u8 area_range;
226162306a36Sopenharmony_ci	u8 timestamp_unit;
226262306a36Sopenharmony_ci	u8 maxcontacts;
226362306a36Sopenharmony_ci	u8 origin;
226462306a36Sopenharmony_ci	u16 res;
226562306a36Sopenharmony_ci};
226662306a36Sopenharmony_ci
226762306a36Sopenharmony_cistruct hidpp_touchpad_raw_xy_finger {
226862306a36Sopenharmony_ci	u8 contact_type;
226962306a36Sopenharmony_ci	u8 contact_status;
227062306a36Sopenharmony_ci	u16 x;
227162306a36Sopenharmony_ci	u16 y;
227262306a36Sopenharmony_ci	u8 z;
227362306a36Sopenharmony_ci	u8 area;
227462306a36Sopenharmony_ci	u8 finger_id;
227562306a36Sopenharmony_ci};
227662306a36Sopenharmony_ci
227762306a36Sopenharmony_cistruct hidpp_touchpad_raw_xy {
227862306a36Sopenharmony_ci	u16 timestamp;
227962306a36Sopenharmony_ci	struct hidpp_touchpad_raw_xy_finger fingers[2];
228062306a36Sopenharmony_ci	u8 spurious_flag;
228162306a36Sopenharmony_ci	u8 end_of_frame;
228262306a36Sopenharmony_ci	u8 finger_count;
228362306a36Sopenharmony_ci	u8 button;
228462306a36Sopenharmony_ci};
228562306a36Sopenharmony_ci
228662306a36Sopenharmony_cistatic int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp,
228762306a36Sopenharmony_ci	u8 feature_index, struct hidpp_touchpad_raw_info *raw_info)
228862306a36Sopenharmony_ci{
228962306a36Sopenharmony_ci	struct hidpp_report response;
229062306a36Sopenharmony_ci	int ret;
229162306a36Sopenharmony_ci	u8 *params = (u8 *)response.fap.params;
229262306a36Sopenharmony_ci
229362306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
229462306a36Sopenharmony_ci		CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response);
229562306a36Sopenharmony_ci
229662306a36Sopenharmony_ci	if (ret > 0) {
229762306a36Sopenharmony_ci		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
229862306a36Sopenharmony_ci			__func__, ret);
229962306a36Sopenharmony_ci		return -EPROTO;
230062306a36Sopenharmony_ci	}
230162306a36Sopenharmony_ci	if (ret)
230262306a36Sopenharmony_ci		return ret;
230362306a36Sopenharmony_ci
230462306a36Sopenharmony_ci	raw_info->x_size = get_unaligned_be16(&params[0]);
230562306a36Sopenharmony_ci	raw_info->y_size = get_unaligned_be16(&params[2]);
230662306a36Sopenharmony_ci	raw_info->z_range = params[4];
230762306a36Sopenharmony_ci	raw_info->area_range = params[5];
230862306a36Sopenharmony_ci	raw_info->maxcontacts = params[7];
230962306a36Sopenharmony_ci	raw_info->origin = params[8];
231062306a36Sopenharmony_ci	/* res is given in unit per inch */
231162306a36Sopenharmony_ci	raw_info->res = get_unaligned_be16(&params[13]) * 2 / 51;
231262306a36Sopenharmony_ci
231362306a36Sopenharmony_ci	return ret;
231462306a36Sopenharmony_ci}
231562306a36Sopenharmony_ci
231662306a36Sopenharmony_cistatic int hidpp_touchpad_set_raw_report_state(struct hidpp_device *hidpp_dev,
231762306a36Sopenharmony_ci		u8 feature_index, bool send_raw_reports,
231862306a36Sopenharmony_ci		bool sensor_enhanced_settings)
231962306a36Sopenharmony_ci{
232062306a36Sopenharmony_ci	struct hidpp_report response;
232162306a36Sopenharmony_ci
232262306a36Sopenharmony_ci	/*
232362306a36Sopenharmony_ci	 * Params:
232462306a36Sopenharmony_ci	 *   bit 0 - enable raw
232562306a36Sopenharmony_ci	 *   bit 1 - 16bit Z, no area
232662306a36Sopenharmony_ci	 *   bit 2 - enhanced sensitivity
232762306a36Sopenharmony_ci	 *   bit 3 - width, height (4 bits each) instead of area
232862306a36Sopenharmony_ci	 *   bit 4 - send raw + gestures (degrades smoothness)
232962306a36Sopenharmony_ci	 *   remaining bits - reserved
233062306a36Sopenharmony_ci	 */
233162306a36Sopenharmony_ci	u8 params = send_raw_reports | (sensor_enhanced_settings << 2);
233262306a36Sopenharmony_ci
233362306a36Sopenharmony_ci	return hidpp_send_fap_command_sync(hidpp_dev, feature_index,
233462306a36Sopenharmony_ci		CMD_TOUCHPAD_SET_RAW_REPORT_STATE, &params, 1, &response);
233562306a36Sopenharmony_ci}
233662306a36Sopenharmony_ci
233762306a36Sopenharmony_cistatic void hidpp_touchpad_touch_event(u8 *data,
233862306a36Sopenharmony_ci	struct hidpp_touchpad_raw_xy_finger *finger)
233962306a36Sopenharmony_ci{
234062306a36Sopenharmony_ci	u8 x_m = data[0] << 2;
234162306a36Sopenharmony_ci	u8 y_m = data[2] << 2;
234262306a36Sopenharmony_ci
234362306a36Sopenharmony_ci	finger->x = x_m << 6 | data[1];
234462306a36Sopenharmony_ci	finger->y = y_m << 6 | data[3];
234562306a36Sopenharmony_ci
234662306a36Sopenharmony_ci	finger->contact_type = data[0] >> 6;
234762306a36Sopenharmony_ci	finger->contact_status = data[2] >> 6;
234862306a36Sopenharmony_ci
234962306a36Sopenharmony_ci	finger->z = data[4];
235062306a36Sopenharmony_ci	finger->area = data[5];
235162306a36Sopenharmony_ci	finger->finger_id = data[6] >> 4;
235262306a36Sopenharmony_ci}
235362306a36Sopenharmony_ci
235462306a36Sopenharmony_cistatic void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
235562306a36Sopenharmony_ci		u8 *data, struct hidpp_touchpad_raw_xy *raw_xy)
235662306a36Sopenharmony_ci{
235762306a36Sopenharmony_ci	memset(raw_xy, 0, sizeof(struct hidpp_touchpad_raw_xy));
235862306a36Sopenharmony_ci	raw_xy->end_of_frame = data[8] & 0x01;
235962306a36Sopenharmony_ci	raw_xy->spurious_flag = (data[8] >> 1) & 0x01;
236062306a36Sopenharmony_ci	raw_xy->finger_count = data[15] & 0x0f;
236162306a36Sopenharmony_ci	raw_xy->button = (data[8] >> 2) & 0x01;
236262306a36Sopenharmony_ci
236362306a36Sopenharmony_ci	if (raw_xy->finger_count) {
236462306a36Sopenharmony_ci		hidpp_touchpad_touch_event(&data[2], &raw_xy->fingers[0]);
236562306a36Sopenharmony_ci		hidpp_touchpad_touch_event(&data[9], &raw_xy->fingers[1]);
236662306a36Sopenharmony_ci	}
236762306a36Sopenharmony_ci}
236862306a36Sopenharmony_ci
236962306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
237062306a36Sopenharmony_ci/* 0x8123: Force feedback support                                             */
237162306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
237262306a36Sopenharmony_ci
237362306a36Sopenharmony_ci#define HIDPP_FF_GET_INFO		0x01
237462306a36Sopenharmony_ci#define HIDPP_FF_RESET_ALL		0x11
237562306a36Sopenharmony_ci#define HIDPP_FF_DOWNLOAD_EFFECT	0x21
237662306a36Sopenharmony_ci#define HIDPP_FF_SET_EFFECT_STATE	0x31
237762306a36Sopenharmony_ci#define HIDPP_FF_DESTROY_EFFECT		0x41
237862306a36Sopenharmony_ci#define HIDPP_FF_GET_APERTURE		0x51
237962306a36Sopenharmony_ci#define HIDPP_FF_SET_APERTURE		0x61
238062306a36Sopenharmony_ci#define HIDPP_FF_GET_GLOBAL_GAINS	0x71
238162306a36Sopenharmony_ci#define HIDPP_FF_SET_GLOBAL_GAINS	0x81
238262306a36Sopenharmony_ci
238362306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_STATE_GET	0x00
238462306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_STATE_STOP	0x01
238562306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_STATE_PLAY	0x02
238662306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_STATE_PAUSE	0x03
238762306a36Sopenharmony_ci
238862306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_CONSTANT	0x00
238962306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_PERIODIC_SINE		0x01
239062306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_PERIODIC_SQUARE		0x02
239162306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_PERIODIC_TRIANGLE	0x03
239262306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHUP	0x04
239362306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHDOWN	0x05
239462306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_SPRING		0x06
239562306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_DAMPER		0x07
239662306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_FRICTION	0x08
239762306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_INERTIA		0x09
239862306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_RAMP		0x0A
239962306a36Sopenharmony_ci
240062306a36Sopenharmony_ci#define HIDPP_FF_EFFECT_AUTOSTART	0x80
240162306a36Sopenharmony_ci
240262306a36Sopenharmony_ci#define HIDPP_FF_EFFECTID_NONE		-1
240362306a36Sopenharmony_ci#define HIDPP_FF_EFFECTID_AUTOCENTER	-2
240462306a36Sopenharmony_ci#define HIDPP_AUTOCENTER_PARAMS_LENGTH	18
240562306a36Sopenharmony_ci
240662306a36Sopenharmony_ci#define HIDPP_FF_MAX_PARAMS	20
240762306a36Sopenharmony_ci#define HIDPP_FF_RESERVED_SLOTS	1
240862306a36Sopenharmony_ci
240962306a36Sopenharmony_cistruct hidpp_ff_private_data {
241062306a36Sopenharmony_ci	struct hidpp_device *hidpp;
241162306a36Sopenharmony_ci	u8 feature_index;
241262306a36Sopenharmony_ci	u8 version;
241362306a36Sopenharmony_ci	u16 gain;
241462306a36Sopenharmony_ci	s16 range;
241562306a36Sopenharmony_ci	u8 slot_autocenter;
241662306a36Sopenharmony_ci	u8 num_effects;
241762306a36Sopenharmony_ci	int *effect_ids;
241862306a36Sopenharmony_ci	struct workqueue_struct *wq;
241962306a36Sopenharmony_ci	atomic_t workqueue_size;
242062306a36Sopenharmony_ci};
242162306a36Sopenharmony_ci
242262306a36Sopenharmony_cistruct hidpp_ff_work_data {
242362306a36Sopenharmony_ci	struct work_struct work;
242462306a36Sopenharmony_ci	struct hidpp_ff_private_data *data;
242562306a36Sopenharmony_ci	int effect_id;
242662306a36Sopenharmony_ci	u8 command;
242762306a36Sopenharmony_ci	u8 params[HIDPP_FF_MAX_PARAMS];
242862306a36Sopenharmony_ci	u8 size;
242962306a36Sopenharmony_ci};
243062306a36Sopenharmony_ci
243162306a36Sopenharmony_cistatic const signed short hidpp_ff_effects[] = {
243262306a36Sopenharmony_ci	FF_CONSTANT,
243362306a36Sopenharmony_ci	FF_PERIODIC,
243462306a36Sopenharmony_ci	FF_SINE,
243562306a36Sopenharmony_ci	FF_SQUARE,
243662306a36Sopenharmony_ci	FF_SAW_UP,
243762306a36Sopenharmony_ci	FF_SAW_DOWN,
243862306a36Sopenharmony_ci	FF_TRIANGLE,
243962306a36Sopenharmony_ci	FF_SPRING,
244062306a36Sopenharmony_ci	FF_DAMPER,
244162306a36Sopenharmony_ci	FF_AUTOCENTER,
244262306a36Sopenharmony_ci	FF_GAIN,
244362306a36Sopenharmony_ci	-1
244462306a36Sopenharmony_ci};
244562306a36Sopenharmony_ci
244662306a36Sopenharmony_cistatic const signed short hidpp_ff_effects_v2[] = {
244762306a36Sopenharmony_ci	FF_RAMP,
244862306a36Sopenharmony_ci	FF_FRICTION,
244962306a36Sopenharmony_ci	FF_INERTIA,
245062306a36Sopenharmony_ci	-1
245162306a36Sopenharmony_ci};
245262306a36Sopenharmony_ci
245362306a36Sopenharmony_cistatic const u8 HIDPP_FF_CONDITION_CMDS[] = {
245462306a36Sopenharmony_ci	HIDPP_FF_EFFECT_SPRING,
245562306a36Sopenharmony_ci	HIDPP_FF_EFFECT_FRICTION,
245662306a36Sopenharmony_ci	HIDPP_FF_EFFECT_DAMPER,
245762306a36Sopenharmony_ci	HIDPP_FF_EFFECT_INERTIA
245862306a36Sopenharmony_ci};
245962306a36Sopenharmony_ci
246062306a36Sopenharmony_cistatic const char *HIDPP_FF_CONDITION_NAMES[] = {
246162306a36Sopenharmony_ci	"spring",
246262306a36Sopenharmony_ci	"friction",
246362306a36Sopenharmony_ci	"damper",
246462306a36Sopenharmony_ci	"inertia"
246562306a36Sopenharmony_ci};
246662306a36Sopenharmony_ci
246762306a36Sopenharmony_ci
246862306a36Sopenharmony_cistatic u8 hidpp_ff_find_effect(struct hidpp_ff_private_data *data, int effect_id)
246962306a36Sopenharmony_ci{
247062306a36Sopenharmony_ci	int i;
247162306a36Sopenharmony_ci
247262306a36Sopenharmony_ci	for (i = 0; i < data->num_effects; i++)
247362306a36Sopenharmony_ci		if (data->effect_ids[i] == effect_id)
247462306a36Sopenharmony_ci			return i+1;
247562306a36Sopenharmony_ci
247662306a36Sopenharmony_ci	return 0;
247762306a36Sopenharmony_ci}
247862306a36Sopenharmony_ci
247962306a36Sopenharmony_cistatic void hidpp_ff_work_handler(struct work_struct *w)
248062306a36Sopenharmony_ci{
248162306a36Sopenharmony_ci	struct hidpp_ff_work_data *wd = container_of(w, struct hidpp_ff_work_data, work);
248262306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = wd->data;
248362306a36Sopenharmony_ci	struct hidpp_report response;
248462306a36Sopenharmony_ci	u8 slot;
248562306a36Sopenharmony_ci	int ret;
248662306a36Sopenharmony_ci
248762306a36Sopenharmony_ci	/* add slot number if needed */
248862306a36Sopenharmony_ci	switch (wd->effect_id) {
248962306a36Sopenharmony_ci	case HIDPP_FF_EFFECTID_AUTOCENTER:
249062306a36Sopenharmony_ci		wd->params[0] = data->slot_autocenter;
249162306a36Sopenharmony_ci		break;
249262306a36Sopenharmony_ci	case HIDPP_FF_EFFECTID_NONE:
249362306a36Sopenharmony_ci		/* leave slot as zero */
249462306a36Sopenharmony_ci		break;
249562306a36Sopenharmony_ci	default:
249662306a36Sopenharmony_ci		/* find current slot for effect */
249762306a36Sopenharmony_ci		wd->params[0] = hidpp_ff_find_effect(data, wd->effect_id);
249862306a36Sopenharmony_ci		break;
249962306a36Sopenharmony_ci	}
250062306a36Sopenharmony_ci
250162306a36Sopenharmony_ci	/* send command and wait for reply */
250262306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(data->hidpp, data->feature_index,
250362306a36Sopenharmony_ci		wd->command, wd->params, wd->size, &response);
250462306a36Sopenharmony_ci
250562306a36Sopenharmony_ci	if (ret) {
250662306a36Sopenharmony_ci		hid_err(data->hidpp->hid_dev, "Failed to send command to device!\n");
250762306a36Sopenharmony_ci		goto out;
250862306a36Sopenharmony_ci	}
250962306a36Sopenharmony_ci
251062306a36Sopenharmony_ci	/* parse return data */
251162306a36Sopenharmony_ci	switch (wd->command) {
251262306a36Sopenharmony_ci	case HIDPP_FF_DOWNLOAD_EFFECT:
251362306a36Sopenharmony_ci		slot = response.fap.params[0];
251462306a36Sopenharmony_ci		if (slot > 0 && slot <= data->num_effects) {
251562306a36Sopenharmony_ci			if (wd->effect_id >= 0)
251662306a36Sopenharmony_ci				/* regular effect uploaded */
251762306a36Sopenharmony_ci				data->effect_ids[slot-1] = wd->effect_id;
251862306a36Sopenharmony_ci			else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
251962306a36Sopenharmony_ci				/* autocenter spring uploaded */
252062306a36Sopenharmony_ci				data->slot_autocenter = slot;
252162306a36Sopenharmony_ci		}
252262306a36Sopenharmony_ci		break;
252362306a36Sopenharmony_ci	case HIDPP_FF_DESTROY_EFFECT:
252462306a36Sopenharmony_ci		if (wd->effect_id >= 0)
252562306a36Sopenharmony_ci			/* regular effect destroyed */
252662306a36Sopenharmony_ci			data->effect_ids[wd->params[0]-1] = -1;
252762306a36Sopenharmony_ci		else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
252862306a36Sopenharmony_ci			/* autocenter spring destoyed */
252962306a36Sopenharmony_ci			data->slot_autocenter = 0;
253062306a36Sopenharmony_ci		break;
253162306a36Sopenharmony_ci	case HIDPP_FF_SET_GLOBAL_GAINS:
253262306a36Sopenharmony_ci		data->gain = (wd->params[0] << 8) + wd->params[1];
253362306a36Sopenharmony_ci		break;
253462306a36Sopenharmony_ci	case HIDPP_FF_SET_APERTURE:
253562306a36Sopenharmony_ci		data->range = (wd->params[0] << 8) + wd->params[1];
253662306a36Sopenharmony_ci		break;
253762306a36Sopenharmony_ci	default:
253862306a36Sopenharmony_ci		/* no action needed */
253962306a36Sopenharmony_ci		break;
254062306a36Sopenharmony_ci	}
254162306a36Sopenharmony_ci
254262306a36Sopenharmony_ciout:
254362306a36Sopenharmony_ci	atomic_dec(&data->workqueue_size);
254462306a36Sopenharmony_ci	kfree(wd);
254562306a36Sopenharmony_ci}
254662306a36Sopenharmony_ci
254762306a36Sopenharmony_cistatic int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id, u8 command, u8 *params, u8 size)
254862306a36Sopenharmony_ci{
254962306a36Sopenharmony_ci	struct hidpp_ff_work_data *wd = kzalloc(sizeof(*wd), GFP_KERNEL);
255062306a36Sopenharmony_ci	int s;
255162306a36Sopenharmony_ci
255262306a36Sopenharmony_ci	if (!wd)
255362306a36Sopenharmony_ci		return -ENOMEM;
255462306a36Sopenharmony_ci
255562306a36Sopenharmony_ci	INIT_WORK(&wd->work, hidpp_ff_work_handler);
255662306a36Sopenharmony_ci
255762306a36Sopenharmony_ci	wd->data = data;
255862306a36Sopenharmony_ci	wd->effect_id = effect_id;
255962306a36Sopenharmony_ci	wd->command = command;
256062306a36Sopenharmony_ci	wd->size = size;
256162306a36Sopenharmony_ci	memcpy(wd->params, params, size);
256262306a36Sopenharmony_ci
256362306a36Sopenharmony_ci	s = atomic_inc_return(&data->workqueue_size);
256462306a36Sopenharmony_ci	queue_work(data->wq, &wd->work);
256562306a36Sopenharmony_ci
256662306a36Sopenharmony_ci	/* warn about excessive queue size */
256762306a36Sopenharmony_ci	if (s >= 20 && s % 20 == 0)
256862306a36Sopenharmony_ci		hid_warn(data->hidpp->hid_dev, "Force feedback command queue contains %d commands, causing substantial delays!", s);
256962306a36Sopenharmony_ci
257062306a36Sopenharmony_ci	return 0;
257162306a36Sopenharmony_ci}
257262306a36Sopenharmony_ci
257362306a36Sopenharmony_cistatic int hidpp_ff_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old)
257462306a36Sopenharmony_ci{
257562306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = dev->ff->private;
257662306a36Sopenharmony_ci	u8 params[20];
257762306a36Sopenharmony_ci	u8 size;
257862306a36Sopenharmony_ci	int force;
257962306a36Sopenharmony_ci
258062306a36Sopenharmony_ci	/* set common parameters */
258162306a36Sopenharmony_ci	params[2] = effect->replay.length >> 8;
258262306a36Sopenharmony_ci	params[3] = effect->replay.length & 255;
258362306a36Sopenharmony_ci	params[4] = effect->replay.delay >> 8;
258462306a36Sopenharmony_ci	params[5] = effect->replay.delay & 255;
258562306a36Sopenharmony_ci
258662306a36Sopenharmony_ci	switch (effect->type) {
258762306a36Sopenharmony_ci	case FF_CONSTANT:
258862306a36Sopenharmony_ci		force = (effect->u.constant.level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
258962306a36Sopenharmony_ci		params[1] = HIDPP_FF_EFFECT_CONSTANT;
259062306a36Sopenharmony_ci		params[6] = force >> 8;
259162306a36Sopenharmony_ci		params[7] = force & 255;
259262306a36Sopenharmony_ci		params[8] = effect->u.constant.envelope.attack_level >> 7;
259362306a36Sopenharmony_ci		params[9] = effect->u.constant.envelope.attack_length >> 8;
259462306a36Sopenharmony_ci		params[10] = effect->u.constant.envelope.attack_length & 255;
259562306a36Sopenharmony_ci		params[11] = effect->u.constant.envelope.fade_level >> 7;
259662306a36Sopenharmony_ci		params[12] = effect->u.constant.envelope.fade_length >> 8;
259762306a36Sopenharmony_ci		params[13] = effect->u.constant.envelope.fade_length & 255;
259862306a36Sopenharmony_ci		size = 14;
259962306a36Sopenharmony_ci		dbg_hid("Uploading constant force level=%d in dir %d = %d\n",
260062306a36Sopenharmony_ci				effect->u.constant.level,
260162306a36Sopenharmony_ci				effect->direction, force);
260262306a36Sopenharmony_ci		dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
260362306a36Sopenharmony_ci				effect->u.constant.envelope.attack_level,
260462306a36Sopenharmony_ci				effect->u.constant.envelope.attack_length,
260562306a36Sopenharmony_ci				effect->u.constant.envelope.fade_level,
260662306a36Sopenharmony_ci				effect->u.constant.envelope.fade_length);
260762306a36Sopenharmony_ci		break;
260862306a36Sopenharmony_ci	case FF_PERIODIC:
260962306a36Sopenharmony_ci	{
261062306a36Sopenharmony_ci		switch (effect->u.periodic.waveform) {
261162306a36Sopenharmony_ci		case FF_SINE:
261262306a36Sopenharmony_ci			params[1] = HIDPP_FF_EFFECT_PERIODIC_SINE;
261362306a36Sopenharmony_ci			break;
261462306a36Sopenharmony_ci		case FF_SQUARE:
261562306a36Sopenharmony_ci			params[1] = HIDPP_FF_EFFECT_PERIODIC_SQUARE;
261662306a36Sopenharmony_ci			break;
261762306a36Sopenharmony_ci		case FF_SAW_UP:
261862306a36Sopenharmony_ci			params[1] = HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHUP;
261962306a36Sopenharmony_ci			break;
262062306a36Sopenharmony_ci		case FF_SAW_DOWN:
262162306a36Sopenharmony_ci			params[1] = HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHDOWN;
262262306a36Sopenharmony_ci			break;
262362306a36Sopenharmony_ci		case FF_TRIANGLE:
262462306a36Sopenharmony_ci			params[1] = HIDPP_FF_EFFECT_PERIODIC_TRIANGLE;
262562306a36Sopenharmony_ci			break;
262662306a36Sopenharmony_ci		default:
262762306a36Sopenharmony_ci			hid_err(data->hidpp->hid_dev, "Unexpected periodic waveform type %i!\n", effect->u.periodic.waveform);
262862306a36Sopenharmony_ci			return -EINVAL;
262962306a36Sopenharmony_ci		}
263062306a36Sopenharmony_ci		force = (effect->u.periodic.magnitude * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
263162306a36Sopenharmony_ci		params[6] = effect->u.periodic.magnitude >> 8;
263262306a36Sopenharmony_ci		params[7] = effect->u.periodic.magnitude & 255;
263362306a36Sopenharmony_ci		params[8] = effect->u.periodic.offset >> 8;
263462306a36Sopenharmony_ci		params[9] = effect->u.periodic.offset & 255;
263562306a36Sopenharmony_ci		params[10] = effect->u.periodic.period >> 8;
263662306a36Sopenharmony_ci		params[11] = effect->u.periodic.period & 255;
263762306a36Sopenharmony_ci		params[12] = effect->u.periodic.phase >> 8;
263862306a36Sopenharmony_ci		params[13] = effect->u.periodic.phase & 255;
263962306a36Sopenharmony_ci		params[14] = effect->u.periodic.envelope.attack_level >> 7;
264062306a36Sopenharmony_ci		params[15] = effect->u.periodic.envelope.attack_length >> 8;
264162306a36Sopenharmony_ci		params[16] = effect->u.periodic.envelope.attack_length & 255;
264262306a36Sopenharmony_ci		params[17] = effect->u.periodic.envelope.fade_level >> 7;
264362306a36Sopenharmony_ci		params[18] = effect->u.periodic.envelope.fade_length >> 8;
264462306a36Sopenharmony_ci		params[19] = effect->u.periodic.envelope.fade_length & 255;
264562306a36Sopenharmony_ci		size = 20;
264662306a36Sopenharmony_ci		dbg_hid("Uploading periodic force mag=%d/dir=%d, offset=%d, period=%d ms, phase=%d\n",
264762306a36Sopenharmony_ci				effect->u.periodic.magnitude, effect->direction,
264862306a36Sopenharmony_ci				effect->u.periodic.offset,
264962306a36Sopenharmony_ci				effect->u.periodic.period,
265062306a36Sopenharmony_ci				effect->u.periodic.phase);
265162306a36Sopenharmony_ci		dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
265262306a36Sopenharmony_ci				effect->u.periodic.envelope.attack_level,
265362306a36Sopenharmony_ci				effect->u.periodic.envelope.attack_length,
265462306a36Sopenharmony_ci				effect->u.periodic.envelope.fade_level,
265562306a36Sopenharmony_ci				effect->u.periodic.envelope.fade_length);
265662306a36Sopenharmony_ci		break;
265762306a36Sopenharmony_ci	}
265862306a36Sopenharmony_ci	case FF_RAMP:
265962306a36Sopenharmony_ci		params[1] = HIDPP_FF_EFFECT_RAMP;
266062306a36Sopenharmony_ci		force = (effect->u.ramp.start_level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
266162306a36Sopenharmony_ci		params[6] = force >> 8;
266262306a36Sopenharmony_ci		params[7] = force & 255;
266362306a36Sopenharmony_ci		force = (effect->u.ramp.end_level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
266462306a36Sopenharmony_ci		params[8] = force >> 8;
266562306a36Sopenharmony_ci		params[9] = force & 255;
266662306a36Sopenharmony_ci		params[10] = effect->u.ramp.envelope.attack_level >> 7;
266762306a36Sopenharmony_ci		params[11] = effect->u.ramp.envelope.attack_length >> 8;
266862306a36Sopenharmony_ci		params[12] = effect->u.ramp.envelope.attack_length & 255;
266962306a36Sopenharmony_ci		params[13] = effect->u.ramp.envelope.fade_level >> 7;
267062306a36Sopenharmony_ci		params[14] = effect->u.ramp.envelope.fade_length >> 8;
267162306a36Sopenharmony_ci		params[15] = effect->u.ramp.envelope.fade_length & 255;
267262306a36Sopenharmony_ci		size = 16;
267362306a36Sopenharmony_ci		dbg_hid("Uploading ramp force level=%d -> %d in dir %d = %d\n",
267462306a36Sopenharmony_ci				effect->u.ramp.start_level,
267562306a36Sopenharmony_ci				effect->u.ramp.end_level,
267662306a36Sopenharmony_ci				effect->direction, force);
267762306a36Sopenharmony_ci		dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
267862306a36Sopenharmony_ci				effect->u.ramp.envelope.attack_level,
267962306a36Sopenharmony_ci				effect->u.ramp.envelope.attack_length,
268062306a36Sopenharmony_ci				effect->u.ramp.envelope.fade_level,
268162306a36Sopenharmony_ci				effect->u.ramp.envelope.fade_length);
268262306a36Sopenharmony_ci		break;
268362306a36Sopenharmony_ci	case FF_FRICTION:
268462306a36Sopenharmony_ci	case FF_INERTIA:
268562306a36Sopenharmony_ci	case FF_SPRING:
268662306a36Sopenharmony_ci	case FF_DAMPER:
268762306a36Sopenharmony_ci		params[1] = HIDPP_FF_CONDITION_CMDS[effect->type - FF_SPRING];
268862306a36Sopenharmony_ci		params[6] = effect->u.condition[0].left_saturation >> 9;
268962306a36Sopenharmony_ci		params[7] = (effect->u.condition[0].left_saturation >> 1) & 255;
269062306a36Sopenharmony_ci		params[8] = effect->u.condition[0].left_coeff >> 8;
269162306a36Sopenharmony_ci		params[9] = effect->u.condition[0].left_coeff & 255;
269262306a36Sopenharmony_ci		params[10] = effect->u.condition[0].deadband >> 9;
269362306a36Sopenharmony_ci		params[11] = (effect->u.condition[0].deadband >> 1) & 255;
269462306a36Sopenharmony_ci		params[12] = effect->u.condition[0].center >> 8;
269562306a36Sopenharmony_ci		params[13] = effect->u.condition[0].center & 255;
269662306a36Sopenharmony_ci		params[14] = effect->u.condition[0].right_coeff >> 8;
269762306a36Sopenharmony_ci		params[15] = effect->u.condition[0].right_coeff & 255;
269862306a36Sopenharmony_ci		params[16] = effect->u.condition[0].right_saturation >> 9;
269962306a36Sopenharmony_ci		params[17] = (effect->u.condition[0].right_saturation >> 1) & 255;
270062306a36Sopenharmony_ci		size = 18;
270162306a36Sopenharmony_ci		dbg_hid("Uploading %s force left coeff=%d, left sat=%d, right coeff=%d, right sat=%d\n",
270262306a36Sopenharmony_ci				HIDPP_FF_CONDITION_NAMES[effect->type - FF_SPRING],
270362306a36Sopenharmony_ci				effect->u.condition[0].left_coeff,
270462306a36Sopenharmony_ci				effect->u.condition[0].left_saturation,
270562306a36Sopenharmony_ci				effect->u.condition[0].right_coeff,
270662306a36Sopenharmony_ci				effect->u.condition[0].right_saturation);
270762306a36Sopenharmony_ci		dbg_hid("          deadband=%d, center=%d\n",
270862306a36Sopenharmony_ci				effect->u.condition[0].deadband,
270962306a36Sopenharmony_ci				effect->u.condition[0].center);
271062306a36Sopenharmony_ci		break;
271162306a36Sopenharmony_ci	default:
271262306a36Sopenharmony_ci		hid_err(data->hidpp->hid_dev, "Unexpected force type %i!\n", effect->type);
271362306a36Sopenharmony_ci		return -EINVAL;
271462306a36Sopenharmony_ci	}
271562306a36Sopenharmony_ci
271662306a36Sopenharmony_ci	return hidpp_ff_queue_work(data, effect->id, HIDPP_FF_DOWNLOAD_EFFECT, params, size);
271762306a36Sopenharmony_ci}
271862306a36Sopenharmony_ci
271962306a36Sopenharmony_cistatic int hidpp_ff_playback(struct input_dev *dev, int effect_id, int value)
272062306a36Sopenharmony_ci{
272162306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = dev->ff->private;
272262306a36Sopenharmony_ci	u8 params[2];
272362306a36Sopenharmony_ci
272462306a36Sopenharmony_ci	params[1] = value ? HIDPP_FF_EFFECT_STATE_PLAY : HIDPP_FF_EFFECT_STATE_STOP;
272562306a36Sopenharmony_ci
272662306a36Sopenharmony_ci	dbg_hid("St%sing playback of effect %d.\n", value?"art":"opp", effect_id);
272762306a36Sopenharmony_ci
272862306a36Sopenharmony_ci	return hidpp_ff_queue_work(data, effect_id, HIDPP_FF_SET_EFFECT_STATE, params, ARRAY_SIZE(params));
272962306a36Sopenharmony_ci}
273062306a36Sopenharmony_ci
273162306a36Sopenharmony_cistatic int hidpp_ff_erase_effect(struct input_dev *dev, int effect_id)
273262306a36Sopenharmony_ci{
273362306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = dev->ff->private;
273462306a36Sopenharmony_ci	u8 slot = 0;
273562306a36Sopenharmony_ci
273662306a36Sopenharmony_ci	dbg_hid("Erasing effect %d.\n", effect_id);
273762306a36Sopenharmony_ci
273862306a36Sopenharmony_ci	return hidpp_ff_queue_work(data, effect_id, HIDPP_FF_DESTROY_EFFECT, &slot, 1);
273962306a36Sopenharmony_ci}
274062306a36Sopenharmony_ci
274162306a36Sopenharmony_cistatic void hidpp_ff_set_autocenter(struct input_dev *dev, u16 magnitude)
274262306a36Sopenharmony_ci{
274362306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = dev->ff->private;
274462306a36Sopenharmony_ci	u8 params[HIDPP_AUTOCENTER_PARAMS_LENGTH];
274562306a36Sopenharmony_ci
274662306a36Sopenharmony_ci	dbg_hid("Setting autocenter to %d.\n", magnitude);
274762306a36Sopenharmony_ci
274862306a36Sopenharmony_ci	/* start a standard spring effect */
274962306a36Sopenharmony_ci	params[1] = HIDPP_FF_EFFECT_SPRING | HIDPP_FF_EFFECT_AUTOSTART;
275062306a36Sopenharmony_ci	/* zero delay and duration */
275162306a36Sopenharmony_ci	params[2] = params[3] = params[4] = params[5] = 0;
275262306a36Sopenharmony_ci	/* set coeff to 25% of saturation */
275362306a36Sopenharmony_ci	params[8] = params[14] = magnitude >> 11;
275462306a36Sopenharmony_ci	params[9] = params[15] = (magnitude >> 3) & 255;
275562306a36Sopenharmony_ci	params[6] = params[16] = magnitude >> 9;
275662306a36Sopenharmony_ci	params[7] = params[17] = (magnitude >> 1) & 255;
275762306a36Sopenharmony_ci	/* zero deadband and center */
275862306a36Sopenharmony_ci	params[10] = params[11] = params[12] = params[13] = 0;
275962306a36Sopenharmony_ci
276062306a36Sopenharmony_ci	hidpp_ff_queue_work(data, HIDPP_FF_EFFECTID_AUTOCENTER, HIDPP_FF_DOWNLOAD_EFFECT, params, ARRAY_SIZE(params));
276162306a36Sopenharmony_ci}
276262306a36Sopenharmony_ci
276362306a36Sopenharmony_cistatic void hidpp_ff_set_gain(struct input_dev *dev, u16 gain)
276462306a36Sopenharmony_ci{
276562306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = dev->ff->private;
276662306a36Sopenharmony_ci	u8 params[4];
276762306a36Sopenharmony_ci
276862306a36Sopenharmony_ci	dbg_hid("Setting gain to %d.\n", gain);
276962306a36Sopenharmony_ci
277062306a36Sopenharmony_ci	params[0] = gain >> 8;
277162306a36Sopenharmony_ci	params[1] = gain & 255;
277262306a36Sopenharmony_ci	params[2] = 0; /* no boost */
277362306a36Sopenharmony_ci	params[3] = 0;
277462306a36Sopenharmony_ci
277562306a36Sopenharmony_ci	hidpp_ff_queue_work(data, HIDPP_FF_EFFECTID_NONE, HIDPP_FF_SET_GLOBAL_GAINS, params, ARRAY_SIZE(params));
277662306a36Sopenharmony_ci}
277762306a36Sopenharmony_ci
277862306a36Sopenharmony_cistatic ssize_t hidpp_ff_range_show(struct device *dev, struct device_attribute *attr, char *buf)
277962306a36Sopenharmony_ci{
278062306a36Sopenharmony_ci	struct hid_device *hid = to_hid_device(dev);
278162306a36Sopenharmony_ci	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
278262306a36Sopenharmony_ci	struct input_dev *idev = hidinput->input;
278362306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = idev->ff->private;
278462306a36Sopenharmony_ci
278562306a36Sopenharmony_ci	return scnprintf(buf, PAGE_SIZE, "%u\n", data->range);
278662306a36Sopenharmony_ci}
278762306a36Sopenharmony_ci
278862306a36Sopenharmony_cistatic ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
278962306a36Sopenharmony_ci{
279062306a36Sopenharmony_ci	struct hid_device *hid = to_hid_device(dev);
279162306a36Sopenharmony_ci	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
279262306a36Sopenharmony_ci	struct input_dev *idev = hidinput->input;
279362306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = idev->ff->private;
279462306a36Sopenharmony_ci	u8 params[2];
279562306a36Sopenharmony_ci	int range = simple_strtoul(buf, NULL, 10);
279662306a36Sopenharmony_ci
279762306a36Sopenharmony_ci	range = clamp(range, 180, 900);
279862306a36Sopenharmony_ci
279962306a36Sopenharmony_ci	params[0] = range >> 8;
280062306a36Sopenharmony_ci	params[1] = range & 0x00FF;
280162306a36Sopenharmony_ci
280262306a36Sopenharmony_ci	hidpp_ff_queue_work(data, -1, HIDPP_FF_SET_APERTURE, params, ARRAY_SIZE(params));
280362306a36Sopenharmony_ci
280462306a36Sopenharmony_ci	return count;
280562306a36Sopenharmony_ci}
280662306a36Sopenharmony_ci
280762306a36Sopenharmony_cistatic DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, hidpp_ff_range_show, hidpp_ff_range_store);
280862306a36Sopenharmony_ci
280962306a36Sopenharmony_cistatic void hidpp_ff_destroy(struct ff_device *ff)
281062306a36Sopenharmony_ci{
281162306a36Sopenharmony_ci	struct hidpp_ff_private_data *data = ff->private;
281262306a36Sopenharmony_ci	struct hid_device *hid = data->hidpp->hid_dev;
281362306a36Sopenharmony_ci
281462306a36Sopenharmony_ci	hid_info(hid, "Unloading HID++ force feedback.\n");
281562306a36Sopenharmony_ci
281662306a36Sopenharmony_ci	device_remove_file(&hid->dev, &dev_attr_range);
281762306a36Sopenharmony_ci	destroy_workqueue(data->wq);
281862306a36Sopenharmony_ci	kfree(data->effect_ids);
281962306a36Sopenharmony_ci}
282062306a36Sopenharmony_ci
282162306a36Sopenharmony_cistatic int hidpp_ff_init(struct hidpp_device *hidpp,
282262306a36Sopenharmony_ci			 struct hidpp_ff_private_data *data)
282362306a36Sopenharmony_ci{
282462306a36Sopenharmony_ci	struct hid_device *hid = hidpp->hid_dev;
282562306a36Sopenharmony_ci	struct hid_input *hidinput;
282662306a36Sopenharmony_ci	struct input_dev *dev;
282762306a36Sopenharmony_ci	struct usb_device_descriptor *udesc;
282862306a36Sopenharmony_ci	u16 bcdDevice;
282962306a36Sopenharmony_ci	struct ff_device *ff;
283062306a36Sopenharmony_ci	int error, j, num_slots = data->num_effects;
283162306a36Sopenharmony_ci	u8 version;
283262306a36Sopenharmony_ci
283362306a36Sopenharmony_ci	if (!hid_is_usb(hid)) {
283462306a36Sopenharmony_ci		hid_err(hid, "device is not USB\n");
283562306a36Sopenharmony_ci		return -ENODEV;
283662306a36Sopenharmony_ci	}
283762306a36Sopenharmony_ci
283862306a36Sopenharmony_ci	if (list_empty(&hid->inputs)) {
283962306a36Sopenharmony_ci		hid_err(hid, "no inputs found\n");
284062306a36Sopenharmony_ci		return -ENODEV;
284162306a36Sopenharmony_ci	}
284262306a36Sopenharmony_ci	hidinput = list_entry(hid->inputs.next, struct hid_input, list);
284362306a36Sopenharmony_ci	dev = hidinput->input;
284462306a36Sopenharmony_ci
284562306a36Sopenharmony_ci	if (!dev) {
284662306a36Sopenharmony_ci		hid_err(hid, "Struct input_dev not set!\n");
284762306a36Sopenharmony_ci		return -EINVAL;
284862306a36Sopenharmony_ci	}
284962306a36Sopenharmony_ci
285062306a36Sopenharmony_ci	/* Get firmware release */
285162306a36Sopenharmony_ci	udesc = &(hid_to_usb_dev(hid)->descriptor);
285262306a36Sopenharmony_ci	bcdDevice = le16_to_cpu(udesc->bcdDevice);
285362306a36Sopenharmony_ci	version = bcdDevice & 255;
285462306a36Sopenharmony_ci
285562306a36Sopenharmony_ci	/* Set supported force feedback capabilities */
285662306a36Sopenharmony_ci	for (j = 0; hidpp_ff_effects[j] >= 0; j++)
285762306a36Sopenharmony_ci		set_bit(hidpp_ff_effects[j], dev->ffbit);
285862306a36Sopenharmony_ci	if (version > 1)
285962306a36Sopenharmony_ci		for (j = 0; hidpp_ff_effects_v2[j] >= 0; j++)
286062306a36Sopenharmony_ci			set_bit(hidpp_ff_effects_v2[j], dev->ffbit);
286162306a36Sopenharmony_ci
286262306a36Sopenharmony_ci	error = input_ff_create(dev, num_slots);
286362306a36Sopenharmony_ci
286462306a36Sopenharmony_ci	if (error) {
286562306a36Sopenharmony_ci		hid_err(dev, "Failed to create FF device!\n");
286662306a36Sopenharmony_ci		return error;
286762306a36Sopenharmony_ci	}
286862306a36Sopenharmony_ci	/*
286962306a36Sopenharmony_ci	 * Create a copy of passed data, so we can transfer memory
287062306a36Sopenharmony_ci	 * ownership to FF core
287162306a36Sopenharmony_ci	 */
287262306a36Sopenharmony_ci	data = kmemdup(data, sizeof(*data), GFP_KERNEL);
287362306a36Sopenharmony_ci	if (!data)
287462306a36Sopenharmony_ci		return -ENOMEM;
287562306a36Sopenharmony_ci	data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL);
287662306a36Sopenharmony_ci	if (!data->effect_ids) {
287762306a36Sopenharmony_ci		kfree(data);
287862306a36Sopenharmony_ci		return -ENOMEM;
287962306a36Sopenharmony_ci	}
288062306a36Sopenharmony_ci	data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
288162306a36Sopenharmony_ci	if (!data->wq) {
288262306a36Sopenharmony_ci		kfree(data->effect_ids);
288362306a36Sopenharmony_ci		kfree(data);
288462306a36Sopenharmony_ci		return -ENOMEM;
288562306a36Sopenharmony_ci	}
288662306a36Sopenharmony_ci
288762306a36Sopenharmony_ci	data->hidpp = hidpp;
288862306a36Sopenharmony_ci	data->version = version;
288962306a36Sopenharmony_ci	for (j = 0; j < num_slots; j++)
289062306a36Sopenharmony_ci		data->effect_ids[j] = -1;
289162306a36Sopenharmony_ci
289262306a36Sopenharmony_ci	ff = dev->ff;
289362306a36Sopenharmony_ci	ff->private = data;
289462306a36Sopenharmony_ci
289562306a36Sopenharmony_ci	ff->upload = hidpp_ff_upload_effect;
289662306a36Sopenharmony_ci	ff->erase = hidpp_ff_erase_effect;
289762306a36Sopenharmony_ci	ff->playback = hidpp_ff_playback;
289862306a36Sopenharmony_ci	ff->set_gain = hidpp_ff_set_gain;
289962306a36Sopenharmony_ci	ff->set_autocenter = hidpp_ff_set_autocenter;
290062306a36Sopenharmony_ci	ff->destroy = hidpp_ff_destroy;
290162306a36Sopenharmony_ci
290262306a36Sopenharmony_ci	/* Create sysfs interface */
290362306a36Sopenharmony_ci	error = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
290462306a36Sopenharmony_ci	if (error)
290562306a36Sopenharmony_ci		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d!\n", error);
290662306a36Sopenharmony_ci
290762306a36Sopenharmony_ci	/* init the hardware command queue */
290862306a36Sopenharmony_ci	atomic_set(&data->workqueue_size, 0);
290962306a36Sopenharmony_ci
291062306a36Sopenharmony_ci	hid_info(hid, "Force feedback support loaded (firmware release %d).\n",
291162306a36Sopenharmony_ci		 version);
291262306a36Sopenharmony_ci
291362306a36Sopenharmony_ci	return 0;
291462306a36Sopenharmony_ci}
291562306a36Sopenharmony_ci
291662306a36Sopenharmony_ci/* ************************************************************************** */
291762306a36Sopenharmony_ci/*                                                                            */
291862306a36Sopenharmony_ci/* Device Support                                                             */
291962306a36Sopenharmony_ci/*                                                                            */
292062306a36Sopenharmony_ci/* ************************************************************************** */
292162306a36Sopenharmony_ci
292262306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
292362306a36Sopenharmony_ci/* Touchpad HID++ devices                                                     */
292462306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
292562306a36Sopenharmony_ci
292662306a36Sopenharmony_ci#define WTP_MANUAL_RESOLUTION				39
292762306a36Sopenharmony_ci
292862306a36Sopenharmony_cistruct wtp_data {
292962306a36Sopenharmony_ci	u16 x_size, y_size;
293062306a36Sopenharmony_ci	u8 finger_count;
293162306a36Sopenharmony_ci	u8 mt_feature_index;
293262306a36Sopenharmony_ci	u8 button_feature_index;
293362306a36Sopenharmony_ci	u8 maxcontacts;
293462306a36Sopenharmony_ci	bool flip_y;
293562306a36Sopenharmony_ci	unsigned int resolution;
293662306a36Sopenharmony_ci};
293762306a36Sopenharmony_ci
293862306a36Sopenharmony_cistatic int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
293962306a36Sopenharmony_ci		struct hid_field *field, struct hid_usage *usage,
294062306a36Sopenharmony_ci		unsigned long **bit, int *max)
294162306a36Sopenharmony_ci{
294262306a36Sopenharmony_ci	return -1;
294362306a36Sopenharmony_ci}
294462306a36Sopenharmony_ci
294562306a36Sopenharmony_cistatic void wtp_populate_input(struct hidpp_device *hidpp,
294662306a36Sopenharmony_ci			       struct input_dev *input_dev)
294762306a36Sopenharmony_ci{
294862306a36Sopenharmony_ci	struct wtp_data *wd = hidpp->private_data;
294962306a36Sopenharmony_ci
295062306a36Sopenharmony_ci	__set_bit(EV_ABS, input_dev->evbit);
295162306a36Sopenharmony_ci	__set_bit(EV_KEY, input_dev->evbit);
295262306a36Sopenharmony_ci	__clear_bit(EV_REL, input_dev->evbit);
295362306a36Sopenharmony_ci	__clear_bit(EV_LED, input_dev->evbit);
295462306a36Sopenharmony_ci
295562306a36Sopenharmony_ci	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, wd->x_size, 0, 0);
295662306a36Sopenharmony_ci	input_abs_set_res(input_dev, ABS_MT_POSITION_X, wd->resolution);
295762306a36Sopenharmony_ci	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, wd->y_size, 0, 0);
295862306a36Sopenharmony_ci	input_abs_set_res(input_dev, ABS_MT_POSITION_Y, wd->resolution);
295962306a36Sopenharmony_ci
296062306a36Sopenharmony_ci	/* Max pressure is not given by the devices, pick one */
296162306a36Sopenharmony_ci	input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 50, 0, 0);
296262306a36Sopenharmony_ci
296362306a36Sopenharmony_ci	input_set_capability(input_dev, EV_KEY, BTN_LEFT);
296462306a36Sopenharmony_ci
296562306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS)
296662306a36Sopenharmony_ci		input_set_capability(input_dev, EV_KEY, BTN_RIGHT);
296762306a36Sopenharmony_ci	else
296862306a36Sopenharmony_ci		__set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit);
296962306a36Sopenharmony_ci
297062306a36Sopenharmony_ci	input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER |
297162306a36Sopenharmony_ci		INPUT_MT_DROP_UNUSED);
297262306a36Sopenharmony_ci}
297362306a36Sopenharmony_ci
297462306a36Sopenharmony_cistatic void wtp_touch_event(struct hidpp_device *hidpp,
297562306a36Sopenharmony_ci	struct hidpp_touchpad_raw_xy_finger *touch_report)
297662306a36Sopenharmony_ci{
297762306a36Sopenharmony_ci	struct wtp_data *wd = hidpp->private_data;
297862306a36Sopenharmony_ci	int slot;
297962306a36Sopenharmony_ci
298062306a36Sopenharmony_ci	if (!touch_report->finger_id || touch_report->contact_type)
298162306a36Sopenharmony_ci		/* no actual data */
298262306a36Sopenharmony_ci		return;
298362306a36Sopenharmony_ci
298462306a36Sopenharmony_ci	slot = input_mt_get_slot_by_key(hidpp->input, touch_report->finger_id);
298562306a36Sopenharmony_ci
298662306a36Sopenharmony_ci	input_mt_slot(hidpp->input, slot);
298762306a36Sopenharmony_ci	input_mt_report_slot_state(hidpp->input, MT_TOOL_FINGER,
298862306a36Sopenharmony_ci					touch_report->contact_status);
298962306a36Sopenharmony_ci	if (touch_report->contact_status) {
299062306a36Sopenharmony_ci		input_event(hidpp->input, EV_ABS, ABS_MT_POSITION_X,
299162306a36Sopenharmony_ci				touch_report->x);
299262306a36Sopenharmony_ci		input_event(hidpp->input, EV_ABS, ABS_MT_POSITION_Y,
299362306a36Sopenharmony_ci				wd->flip_y ? wd->y_size - touch_report->y :
299462306a36Sopenharmony_ci					     touch_report->y);
299562306a36Sopenharmony_ci		input_event(hidpp->input, EV_ABS, ABS_MT_PRESSURE,
299662306a36Sopenharmony_ci				touch_report->area);
299762306a36Sopenharmony_ci	}
299862306a36Sopenharmony_ci}
299962306a36Sopenharmony_ci
300062306a36Sopenharmony_cistatic void wtp_send_raw_xy_event(struct hidpp_device *hidpp,
300162306a36Sopenharmony_ci		struct hidpp_touchpad_raw_xy *raw)
300262306a36Sopenharmony_ci{
300362306a36Sopenharmony_ci	int i;
300462306a36Sopenharmony_ci
300562306a36Sopenharmony_ci	for (i = 0; i < 2; i++)
300662306a36Sopenharmony_ci		wtp_touch_event(hidpp, &(raw->fingers[i]));
300762306a36Sopenharmony_ci
300862306a36Sopenharmony_ci	if (raw->end_of_frame &&
300962306a36Sopenharmony_ci	    !(hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS))
301062306a36Sopenharmony_ci		input_event(hidpp->input, EV_KEY, BTN_LEFT, raw->button);
301162306a36Sopenharmony_ci
301262306a36Sopenharmony_ci	if (raw->end_of_frame || raw->finger_count <= 2) {
301362306a36Sopenharmony_ci		input_mt_sync_frame(hidpp->input);
301462306a36Sopenharmony_ci		input_sync(hidpp->input);
301562306a36Sopenharmony_ci	}
301662306a36Sopenharmony_ci}
301762306a36Sopenharmony_ci
301862306a36Sopenharmony_cistatic int wtp_mouse_raw_xy_event(struct hidpp_device *hidpp, u8 *data)
301962306a36Sopenharmony_ci{
302062306a36Sopenharmony_ci	struct wtp_data *wd = hidpp->private_data;
302162306a36Sopenharmony_ci	u8 c1_area = ((data[7] & 0xf) * (data[7] & 0xf) +
302262306a36Sopenharmony_ci		      (data[7] >> 4) * (data[7] >> 4)) / 2;
302362306a36Sopenharmony_ci	u8 c2_area = ((data[13] & 0xf) * (data[13] & 0xf) +
302462306a36Sopenharmony_ci		      (data[13] >> 4) * (data[13] >> 4)) / 2;
302562306a36Sopenharmony_ci	struct hidpp_touchpad_raw_xy raw = {
302662306a36Sopenharmony_ci		.timestamp = data[1],
302762306a36Sopenharmony_ci		.fingers = {
302862306a36Sopenharmony_ci			{
302962306a36Sopenharmony_ci				.contact_type = 0,
303062306a36Sopenharmony_ci				.contact_status = !!data[7],
303162306a36Sopenharmony_ci				.x = get_unaligned_le16(&data[3]),
303262306a36Sopenharmony_ci				.y = get_unaligned_le16(&data[5]),
303362306a36Sopenharmony_ci				.z = c1_area,
303462306a36Sopenharmony_ci				.area = c1_area,
303562306a36Sopenharmony_ci				.finger_id = data[2],
303662306a36Sopenharmony_ci			}, {
303762306a36Sopenharmony_ci				.contact_type = 0,
303862306a36Sopenharmony_ci				.contact_status = !!data[13],
303962306a36Sopenharmony_ci				.x = get_unaligned_le16(&data[9]),
304062306a36Sopenharmony_ci				.y = get_unaligned_le16(&data[11]),
304162306a36Sopenharmony_ci				.z = c2_area,
304262306a36Sopenharmony_ci				.area = c2_area,
304362306a36Sopenharmony_ci				.finger_id = data[8],
304462306a36Sopenharmony_ci			}
304562306a36Sopenharmony_ci		},
304662306a36Sopenharmony_ci		.finger_count = wd->maxcontacts,
304762306a36Sopenharmony_ci		.spurious_flag = 0,
304862306a36Sopenharmony_ci		.end_of_frame = (data[0] >> 7) == 0,
304962306a36Sopenharmony_ci		.button = data[0] & 0x01,
305062306a36Sopenharmony_ci	};
305162306a36Sopenharmony_ci
305262306a36Sopenharmony_ci	wtp_send_raw_xy_event(hidpp, &raw);
305362306a36Sopenharmony_ci
305462306a36Sopenharmony_ci	return 1;
305562306a36Sopenharmony_ci}
305662306a36Sopenharmony_ci
305762306a36Sopenharmony_cistatic int wtp_raw_event(struct hid_device *hdev, u8 *data, int size)
305862306a36Sopenharmony_ci{
305962306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
306062306a36Sopenharmony_ci	struct wtp_data *wd = hidpp->private_data;
306162306a36Sopenharmony_ci	struct hidpp_report *report = (struct hidpp_report *)data;
306262306a36Sopenharmony_ci	struct hidpp_touchpad_raw_xy raw;
306362306a36Sopenharmony_ci
306462306a36Sopenharmony_ci	if (!wd || !hidpp->input)
306562306a36Sopenharmony_ci		return 1;
306662306a36Sopenharmony_ci
306762306a36Sopenharmony_ci	switch (data[0]) {
306862306a36Sopenharmony_ci	case 0x02:
306962306a36Sopenharmony_ci		if (size < 2) {
307062306a36Sopenharmony_ci			hid_err(hdev, "Received HID report of bad size (%d)",
307162306a36Sopenharmony_ci				size);
307262306a36Sopenharmony_ci			return 1;
307362306a36Sopenharmony_ci		}
307462306a36Sopenharmony_ci		if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) {
307562306a36Sopenharmony_ci			input_event(hidpp->input, EV_KEY, BTN_LEFT,
307662306a36Sopenharmony_ci					!!(data[1] & 0x01));
307762306a36Sopenharmony_ci			input_event(hidpp->input, EV_KEY, BTN_RIGHT,
307862306a36Sopenharmony_ci					!!(data[1] & 0x02));
307962306a36Sopenharmony_ci			input_sync(hidpp->input);
308062306a36Sopenharmony_ci			return 0;
308162306a36Sopenharmony_ci		} else {
308262306a36Sopenharmony_ci			if (size < 21)
308362306a36Sopenharmony_ci				return 1;
308462306a36Sopenharmony_ci			return wtp_mouse_raw_xy_event(hidpp, &data[7]);
308562306a36Sopenharmony_ci		}
308662306a36Sopenharmony_ci	case REPORT_ID_HIDPP_LONG:
308762306a36Sopenharmony_ci		/* size is already checked in hidpp_raw_event. */
308862306a36Sopenharmony_ci		if ((report->fap.feature_index != wd->mt_feature_index) ||
308962306a36Sopenharmony_ci		    (report->fap.funcindex_clientid != EVENT_TOUCHPAD_RAW_XY))
309062306a36Sopenharmony_ci			return 1;
309162306a36Sopenharmony_ci		hidpp_touchpad_raw_xy_event(hidpp, data + 4, &raw);
309262306a36Sopenharmony_ci
309362306a36Sopenharmony_ci		wtp_send_raw_xy_event(hidpp, &raw);
309462306a36Sopenharmony_ci		return 0;
309562306a36Sopenharmony_ci	}
309662306a36Sopenharmony_ci
309762306a36Sopenharmony_ci	return 0;
309862306a36Sopenharmony_ci}
309962306a36Sopenharmony_ci
310062306a36Sopenharmony_cistatic int wtp_get_config(struct hidpp_device *hidpp)
310162306a36Sopenharmony_ci{
310262306a36Sopenharmony_ci	struct wtp_data *wd = hidpp->private_data;
310362306a36Sopenharmony_ci	struct hidpp_touchpad_raw_info raw_info = {0};
310462306a36Sopenharmony_ci	u8 feature_type;
310562306a36Sopenharmony_ci	int ret;
310662306a36Sopenharmony_ci
310762306a36Sopenharmony_ci	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
310862306a36Sopenharmony_ci		&wd->mt_feature_index, &feature_type);
310962306a36Sopenharmony_ci	if (ret)
311062306a36Sopenharmony_ci		/* means that the device is not powered up */
311162306a36Sopenharmony_ci		return ret;
311262306a36Sopenharmony_ci
311362306a36Sopenharmony_ci	ret = hidpp_touchpad_get_raw_info(hidpp, wd->mt_feature_index,
311462306a36Sopenharmony_ci		&raw_info);
311562306a36Sopenharmony_ci	if (ret)
311662306a36Sopenharmony_ci		return ret;
311762306a36Sopenharmony_ci
311862306a36Sopenharmony_ci	wd->x_size = raw_info.x_size;
311962306a36Sopenharmony_ci	wd->y_size = raw_info.y_size;
312062306a36Sopenharmony_ci	wd->maxcontacts = raw_info.maxcontacts;
312162306a36Sopenharmony_ci	wd->flip_y = raw_info.origin == TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT;
312262306a36Sopenharmony_ci	wd->resolution = raw_info.res;
312362306a36Sopenharmony_ci	if (!wd->resolution)
312462306a36Sopenharmony_ci		wd->resolution = WTP_MANUAL_RESOLUTION;
312562306a36Sopenharmony_ci
312662306a36Sopenharmony_ci	return 0;
312762306a36Sopenharmony_ci}
312862306a36Sopenharmony_ci
312962306a36Sopenharmony_cistatic int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id)
313062306a36Sopenharmony_ci{
313162306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
313262306a36Sopenharmony_ci	struct wtp_data *wd;
313362306a36Sopenharmony_ci
313462306a36Sopenharmony_ci	wd = devm_kzalloc(&hdev->dev, sizeof(struct wtp_data),
313562306a36Sopenharmony_ci			GFP_KERNEL);
313662306a36Sopenharmony_ci	if (!wd)
313762306a36Sopenharmony_ci		return -ENOMEM;
313862306a36Sopenharmony_ci
313962306a36Sopenharmony_ci	hidpp->private_data = wd;
314062306a36Sopenharmony_ci
314162306a36Sopenharmony_ci	return 0;
314262306a36Sopenharmony_ci};
314362306a36Sopenharmony_ci
314462306a36Sopenharmony_cistatic int wtp_connect(struct hid_device *hdev, bool connected)
314562306a36Sopenharmony_ci{
314662306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
314762306a36Sopenharmony_ci	struct wtp_data *wd = hidpp->private_data;
314862306a36Sopenharmony_ci	int ret;
314962306a36Sopenharmony_ci
315062306a36Sopenharmony_ci	if (!wd->x_size) {
315162306a36Sopenharmony_ci		ret = wtp_get_config(hidpp);
315262306a36Sopenharmony_ci		if (ret) {
315362306a36Sopenharmony_ci			hid_err(hdev, "Can not get wtp config: %d\n", ret);
315462306a36Sopenharmony_ci			return ret;
315562306a36Sopenharmony_ci		}
315662306a36Sopenharmony_ci	}
315762306a36Sopenharmony_ci
315862306a36Sopenharmony_ci	return hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index,
315962306a36Sopenharmony_ci			true, true);
316062306a36Sopenharmony_ci}
316162306a36Sopenharmony_ci
316262306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */
316362306a36Sopenharmony_ci/* Logitech M560 devices                                                     */
316462306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */
316562306a36Sopenharmony_ci
316662306a36Sopenharmony_ci/*
316762306a36Sopenharmony_ci * Logitech M560 protocol overview
316862306a36Sopenharmony_ci *
316962306a36Sopenharmony_ci * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
317062306a36Sopenharmony_ci * the sides buttons are pressed, it sends some keyboard keys events
317162306a36Sopenharmony_ci * instead of buttons ones.
317262306a36Sopenharmony_ci * To complicate things further, the middle button keys sequence
317362306a36Sopenharmony_ci * is different from the odd press and the even press.
317462306a36Sopenharmony_ci *
317562306a36Sopenharmony_ci * forward button -> Super_R
317662306a36Sopenharmony_ci * backward button -> Super_L+'d' (press only)
317762306a36Sopenharmony_ci * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
317862306a36Sopenharmony_ci *                  2nd time: left-click (press only)
317962306a36Sopenharmony_ci * NB: press-only means that when the button is pressed, the
318062306a36Sopenharmony_ci * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
318162306a36Sopenharmony_ci * together sequentially; instead when the button is released, no event is
318262306a36Sopenharmony_ci * generated !
318362306a36Sopenharmony_ci *
318462306a36Sopenharmony_ci * With the command
318562306a36Sopenharmony_ci *	10<xx>0a 3500af03 (where <xx> is the mouse id),
318662306a36Sopenharmony_ci * the mouse reacts differently:
318762306a36Sopenharmony_ci * - it never sends a keyboard key event
318862306a36Sopenharmony_ci * - for the three mouse button it sends:
318962306a36Sopenharmony_ci *	middle button               press   11<xx>0a 3500af00...
319062306a36Sopenharmony_ci *	side 1 button (forward)     press   11<xx>0a 3500b000...
319162306a36Sopenharmony_ci *	side 2 button (backward)    press   11<xx>0a 3500ae00...
319262306a36Sopenharmony_ci *	middle/side1/side2 button   release 11<xx>0a 35000000...
319362306a36Sopenharmony_ci */
319462306a36Sopenharmony_ci
319562306a36Sopenharmony_cistatic const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03};
319662306a36Sopenharmony_ci
319762306a36Sopenharmony_ci/* how buttons are mapped in the report */
319862306a36Sopenharmony_ci#define M560_MOUSE_BTN_LEFT		0x01
319962306a36Sopenharmony_ci#define M560_MOUSE_BTN_RIGHT		0x02
320062306a36Sopenharmony_ci#define M560_MOUSE_BTN_WHEEL_LEFT	0x08
320162306a36Sopenharmony_ci#define M560_MOUSE_BTN_WHEEL_RIGHT	0x10
320262306a36Sopenharmony_ci
320362306a36Sopenharmony_ci#define M560_SUB_ID			0x0a
320462306a36Sopenharmony_ci#define M560_BUTTON_MODE_REGISTER	0x35
320562306a36Sopenharmony_ci
320662306a36Sopenharmony_cistatic int m560_send_config_command(struct hid_device *hdev, bool connected)
320762306a36Sopenharmony_ci{
320862306a36Sopenharmony_ci	struct hidpp_report response;
320962306a36Sopenharmony_ci	struct hidpp_device *hidpp_dev;
321062306a36Sopenharmony_ci
321162306a36Sopenharmony_ci	hidpp_dev = hid_get_drvdata(hdev);
321262306a36Sopenharmony_ci
321362306a36Sopenharmony_ci	return hidpp_send_rap_command_sync(
321462306a36Sopenharmony_ci		hidpp_dev,
321562306a36Sopenharmony_ci		REPORT_ID_HIDPP_SHORT,
321662306a36Sopenharmony_ci		M560_SUB_ID,
321762306a36Sopenharmony_ci		M560_BUTTON_MODE_REGISTER,
321862306a36Sopenharmony_ci		(u8 *)m560_config_parameter,
321962306a36Sopenharmony_ci		sizeof(m560_config_parameter),
322062306a36Sopenharmony_ci		&response
322162306a36Sopenharmony_ci	);
322262306a36Sopenharmony_ci}
322362306a36Sopenharmony_ci
322462306a36Sopenharmony_cistatic int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
322562306a36Sopenharmony_ci{
322662306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
322762306a36Sopenharmony_ci
322862306a36Sopenharmony_ci	/* sanity check */
322962306a36Sopenharmony_ci	if (!hidpp->input) {
323062306a36Sopenharmony_ci		hid_err(hdev, "error in parameter\n");
323162306a36Sopenharmony_ci		return -EINVAL;
323262306a36Sopenharmony_ci	}
323362306a36Sopenharmony_ci
323462306a36Sopenharmony_ci	if (size < 7) {
323562306a36Sopenharmony_ci		hid_err(hdev, "error in report\n");
323662306a36Sopenharmony_ci		return 0;
323762306a36Sopenharmony_ci	}
323862306a36Sopenharmony_ci
323962306a36Sopenharmony_ci	if (data[0] == REPORT_ID_HIDPP_LONG &&
324062306a36Sopenharmony_ci	    data[2] == M560_SUB_ID && data[6] == 0x00) {
324162306a36Sopenharmony_ci		/*
324262306a36Sopenharmony_ci		 * m560 mouse report for middle, forward and backward button
324362306a36Sopenharmony_ci		 *
324462306a36Sopenharmony_ci		 * data[0] = 0x11
324562306a36Sopenharmony_ci		 * data[1] = device-id
324662306a36Sopenharmony_ci		 * data[2] = 0x0a
324762306a36Sopenharmony_ci		 * data[5] = 0xaf -> middle
324862306a36Sopenharmony_ci		 *	     0xb0 -> forward
324962306a36Sopenharmony_ci		 *	     0xae -> backward
325062306a36Sopenharmony_ci		 *	     0x00 -> release all
325162306a36Sopenharmony_ci		 * data[6] = 0x00
325262306a36Sopenharmony_ci		 */
325362306a36Sopenharmony_ci
325462306a36Sopenharmony_ci		switch (data[5]) {
325562306a36Sopenharmony_ci		case 0xaf:
325662306a36Sopenharmony_ci			input_report_key(hidpp->input, BTN_MIDDLE, 1);
325762306a36Sopenharmony_ci			break;
325862306a36Sopenharmony_ci		case 0xb0:
325962306a36Sopenharmony_ci			input_report_key(hidpp->input, BTN_FORWARD, 1);
326062306a36Sopenharmony_ci			break;
326162306a36Sopenharmony_ci		case 0xae:
326262306a36Sopenharmony_ci			input_report_key(hidpp->input, BTN_BACK, 1);
326362306a36Sopenharmony_ci			break;
326462306a36Sopenharmony_ci		case 0x00:
326562306a36Sopenharmony_ci			input_report_key(hidpp->input, BTN_BACK, 0);
326662306a36Sopenharmony_ci			input_report_key(hidpp->input, BTN_FORWARD, 0);
326762306a36Sopenharmony_ci			input_report_key(hidpp->input, BTN_MIDDLE, 0);
326862306a36Sopenharmony_ci			break;
326962306a36Sopenharmony_ci		default:
327062306a36Sopenharmony_ci			hid_err(hdev, "error in report\n");
327162306a36Sopenharmony_ci			return 0;
327262306a36Sopenharmony_ci		}
327362306a36Sopenharmony_ci		input_sync(hidpp->input);
327462306a36Sopenharmony_ci
327562306a36Sopenharmony_ci	} else if (data[0] == 0x02) {
327662306a36Sopenharmony_ci		/*
327762306a36Sopenharmony_ci		 * Logitech M560 mouse report
327862306a36Sopenharmony_ci		 *
327962306a36Sopenharmony_ci		 * data[0] = type (0x02)
328062306a36Sopenharmony_ci		 * data[1..2] = buttons
328162306a36Sopenharmony_ci		 * data[3..5] = xy
328262306a36Sopenharmony_ci		 * data[6] = wheel
328362306a36Sopenharmony_ci		 */
328462306a36Sopenharmony_ci
328562306a36Sopenharmony_ci		int v;
328662306a36Sopenharmony_ci
328762306a36Sopenharmony_ci		input_report_key(hidpp->input, BTN_LEFT,
328862306a36Sopenharmony_ci			!!(data[1] & M560_MOUSE_BTN_LEFT));
328962306a36Sopenharmony_ci		input_report_key(hidpp->input, BTN_RIGHT,
329062306a36Sopenharmony_ci			!!(data[1] & M560_MOUSE_BTN_RIGHT));
329162306a36Sopenharmony_ci
329262306a36Sopenharmony_ci		if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT) {
329362306a36Sopenharmony_ci			input_report_rel(hidpp->input, REL_HWHEEL, -1);
329462306a36Sopenharmony_ci			input_report_rel(hidpp->input, REL_HWHEEL_HI_RES,
329562306a36Sopenharmony_ci					 -120);
329662306a36Sopenharmony_ci		} else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT) {
329762306a36Sopenharmony_ci			input_report_rel(hidpp->input, REL_HWHEEL, 1);
329862306a36Sopenharmony_ci			input_report_rel(hidpp->input, REL_HWHEEL_HI_RES,
329962306a36Sopenharmony_ci					 120);
330062306a36Sopenharmony_ci		}
330162306a36Sopenharmony_ci
330262306a36Sopenharmony_ci		v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12);
330362306a36Sopenharmony_ci		input_report_rel(hidpp->input, REL_X, v);
330462306a36Sopenharmony_ci
330562306a36Sopenharmony_ci		v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12);
330662306a36Sopenharmony_ci		input_report_rel(hidpp->input, REL_Y, v);
330762306a36Sopenharmony_ci
330862306a36Sopenharmony_ci		v = hid_snto32(data[6], 8);
330962306a36Sopenharmony_ci		if (v != 0)
331062306a36Sopenharmony_ci			hidpp_scroll_counter_handle_scroll(hidpp->input,
331162306a36Sopenharmony_ci					&hidpp->vertical_wheel_counter, v);
331262306a36Sopenharmony_ci
331362306a36Sopenharmony_ci		input_sync(hidpp->input);
331462306a36Sopenharmony_ci	}
331562306a36Sopenharmony_ci
331662306a36Sopenharmony_ci	return 1;
331762306a36Sopenharmony_ci}
331862306a36Sopenharmony_ci
331962306a36Sopenharmony_cistatic void m560_populate_input(struct hidpp_device *hidpp,
332062306a36Sopenharmony_ci				struct input_dev *input_dev)
332162306a36Sopenharmony_ci{
332262306a36Sopenharmony_ci	__set_bit(EV_KEY, input_dev->evbit);
332362306a36Sopenharmony_ci	__set_bit(BTN_MIDDLE, input_dev->keybit);
332462306a36Sopenharmony_ci	__set_bit(BTN_RIGHT, input_dev->keybit);
332562306a36Sopenharmony_ci	__set_bit(BTN_LEFT, input_dev->keybit);
332662306a36Sopenharmony_ci	__set_bit(BTN_BACK, input_dev->keybit);
332762306a36Sopenharmony_ci	__set_bit(BTN_FORWARD, input_dev->keybit);
332862306a36Sopenharmony_ci
332962306a36Sopenharmony_ci	__set_bit(EV_REL, input_dev->evbit);
333062306a36Sopenharmony_ci	__set_bit(REL_X, input_dev->relbit);
333162306a36Sopenharmony_ci	__set_bit(REL_Y, input_dev->relbit);
333262306a36Sopenharmony_ci	__set_bit(REL_WHEEL, input_dev->relbit);
333362306a36Sopenharmony_ci	__set_bit(REL_HWHEEL, input_dev->relbit);
333462306a36Sopenharmony_ci	__set_bit(REL_WHEEL_HI_RES, input_dev->relbit);
333562306a36Sopenharmony_ci	__set_bit(REL_HWHEEL_HI_RES, input_dev->relbit);
333662306a36Sopenharmony_ci}
333762306a36Sopenharmony_ci
333862306a36Sopenharmony_cistatic int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi,
333962306a36Sopenharmony_ci		struct hid_field *field, struct hid_usage *usage,
334062306a36Sopenharmony_ci		unsigned long **bit, int *max)
334162306a36Sopenharmony_ci{
334262306a36Sopenharmony_ci	return -1;
334362306a36Sopenharmony_ci}
334462306a36Sopenharmony_ci
334562306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */
334662306a36Sopenharmony_ci/* Logitech K400 devices                                                     */
334762306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */
334862306a36Sopenharmony_ci
334962306a36Sopenharmony_ci/*
335062306a36Sopenharmony_ci * The Logitech K400 keyboard has an embedded touchpad which is seen
335162306a36Sopenharmony_ci * as a mouse from the OS point of view. There is a hardware shortcut to disable
335262306a36Sopenharmony_ci * tap-to-click but the setting is not remembered accross reset, annoying some
335362306a36Sopenharmony_ci * users.
335462306a36Sopenharmony_ci *
335562306a36Sopenharmony_ci * We can toggle this feature from the host by using the feature 0x6010:
335662306a36Sopenharmony_ci * Touchpad FW items
335762306a36Sopenharmony_ci */
335862306a36Sopenharmony_ci
335962306a36Sopenharmony_cistruct k400_private_data {
336062306a36Sopenharmony_ci	u8 feature_index;
336162306a36Sopenharmony_ci};
336262306a36Sopenharmony_ci
336362306a36Sopenharmony_cistatic int k400_disable_tap_to_click(struct hidpp_device *hidpp)
336462306a36Sopenharmony_ci{
336562306a36Sopenharmony_ci	struct k400_private_data *k400 = hidpp->private_data;
336662306a36Sopenharmony_ci	struct hidpp_touchpad_fw_items items = {};
336762306a36Sopenharmony_ci	int ret;
336862306a36Sopenharmony_ci	u8 feature_type;
336962306a36Sopenharmony_ci
337062306a36Sopenharmony_ci	if (!k400->feature_index) {
337162306a36Sopenharmony_ci		ret = hidpp_root_get_feature(hidpp,
337262306a36Sopenharmony_ci			HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
337362306a36Sopenharmony_ci			&k400->feature_index, &feature_type);
337462306a36Sopenharmony_ci		if (ret)
337562306a36Sopenharmony_ci			/* means that the device is not powered up */
337662306a36Sopenharmony_ci			return ret;
337762306a36Sopenharmony_ci	}
337862306a36Sopenharmony_ci
337962306a36Sopenharmony_ci	ret = hidpp_touchpad_fw_items_set(hidpp, k400->feature_index, &items);
338062306a36Sopenharmony_ci	if (ret)
338162306a36Sopenharmony_ci		return ret;
338262306a36Sopenharmony_ci
338362306a36Sopenharmony_ci	return 0;
338462306a36Sopenharmony_ci}
338562306a36Sopenharmony_ci
338662306a36Sopenharmony_cistatic int k400_allocate(struct hid_device *hdev)
338762306a36Sopenharmony_ci{
338862306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
338962306a36Sopenharmony_ci	struct k400_private_data *k400;
339062306a36Sopenharmony_ci
339162306a36Sopenharmony_ci	k400 = devm_kzalloc(&hdev->dev, sizeof(struct k400_private_data),
339262306a36Sopenharmony_ci			    GFP_KERNEL);
339362306a36Sopenharmony_ci	if (!k400)
339462306a36Sopenharmony_ci		return -ENOMEM;
339562306a36Sopenharmony_ci
339662306a36Sopenharmony_ci	hidpp->private_data = k400;
339762306a36Sopenharmony_ci
339862306a36Sopenharmony_ci	return 0;
339962306a36Sopenharmony_ci};
340062306a36Sopenharmony_ci
340162306a36Sopenharmony_cistatic int k400_connect(struct hid_device *hdev, bool connected)
340262306a36Sopenharmony_ci{
340362306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
340462306a36Sopenharmony_ci
340562306a36Sopenharmony_ci	if (!disable_tap_to_click)
340662306a36Sopenharmony_ci		return 0;
340762306a36Sopenharmony_ci
340862306a36Sopenharmony_ci	return k400_disable_tap_to_click(hidpp);
340962306a36Sopenharmony_ci}
341062306a36Sopenharmony_ci
341162306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */
341262306a36Sopenharmony_ci/* Logitech G920 Driving Force Racing Wheel for Xbox One                     */
341362306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */
341462306a36Sopenharmony_ci
341562306a36Sopenharmony_ci#define HIDPP_PAGE_G920_FORCE_FEEDBACK			0x8123
341662306a36Sopenharmony_ci
341762306a36Sopenharmony_cistatic int g920_ff_set_autocenter(struct hidpp_device *hidpp,
341862306a36Sopenharmony_ci				  struct hidpp_ff_private_data *data)
341962306a36Sopenharmony_ci{
342062306a36Sopenharmony_ci	struct hidpp_report response;
342162306a36Sopenharmony_ci	u8 params[HIDPP_AUTOCENTER_PARAMS_LENGTH] = {
342262306a36Sopenharmony_ci		[1] = HIDPP_FF_EFFECT_SPRING | HIDPP_FF_EFFECT_AUTOSTART,
342362306a36Sopenharmony_ci	};
342462306a36Sopenharmony_ci	int ret;
342562306a36Sopenharmony_ci
342662306a36Sopenharmony_ci	/* initialize with zero autocenter to get wheel in usable state */
342762306a36Sopenharmony_ci
342862306a36Sopenharmony_ci	dbg_hid("Setting autocenter to 0.\n");
342962306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
343062306a36Sopenharmony_ci					  HIDPP_FF_DOWNLOAD_EFFECT,
343162306a36Sopenharmony_ci					  params, ARRAY_SIZE(params),
343262306a36Sopenharmony_ci					  &response);
343362306a36Sopenharmony_ci	if (ret)
343462306a36Sopenharmony_ci		hid_warn(hidpp->hid_dev, "Failed to autocenter device!\n");
343562306a36Sopenharmony_ci	else
343662306a36Sopenharmony_ci		data->slot_autocenter = response.fap.params[0];
343762306a36Sopenharmony_ci
343862306a36Sopenharmony_ci	return ret;
343962306a36Sopenharmony_ci}
344062306a36Sopenharmony_ci
344162306a36Sopenharmony_cistatic int g920_get_config(struct hidpp_device *hidpp,
344262306a36Sopenharmony_ci			   struct hidpp_ff_private_data *data)
344362306a36Sopenharmony_ci{
344462306a36Sopenharmony_ci	struct hidpp_report response;
344562306a36Sopenharmony_ci	u8 feature_type;
344662306a36Sopenharmony_ci	int ret;
344762306a36Sopenharmony_ci
344862306a36Sopenharmony_ci	memset(data, 0, sizeof(*data));
344962306a36Sopenharmony_ci
345062306a36Sopenharmony_ci	/* Find feature and store for later use */
345162306a36Sopenharmony_ci	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
345262306a36Sopenharmony_ci				     &data->feature_index, &feature_type);
345362306a36Sopenharmony_ci	if (ret)
345462306a36Sopenharmony_ci		return ret;
345562306a36Sopenharmony_ci
345662306a36Sopenharmony_ci	/* Read number of slots available in device */
345762306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
345862306a36Sopenharmony_ci					  HIDPP_FF_GET_INFO,
345962306a36Sopenharmony_ci					  NULL, 0,
346062306a36Sopenharmony_ci					  &response);
346162306a36Sopenharmony_ci	if (ret) {
346262306a36Sopenharmony_ci		if (ret < 0)
346362306a36Sopenharmony_ci			return ret;
346462306a36Sopenharmony_ci		hid_err(hidpp->hid_dev,
346562306a36Sopenharmony_ci			"%s: received protocol error 0x%02x\n", __func__, ret);
346662306a36Sopenharmony_ci		return -EPROTO;
346762306a36Sopenharmony_ci	}
346862306a36Sopenharmony_ci
346962306a36Sopenharmony_ci	data->num_effects = response.fap.params[0] - HIDPP_FF_RESERVED_SLOTS;
347062306a36Sopenharmony_ci
347162306a36Sopenharmony_ci	/* reset all forces */
347262306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
347362306a36Sopenharmony_ci					  HIDPP_FF_RESET_ALL,
347462306a36Sopenharmony_ci					  NULL, 0,
347562306a36Sopenharmony_ci					  &response);
347662306a36Sopenharmony_ci	if (ret)
347762306a36Sopenharmony_ci		hid_warn(hidpp->hid_dev, "Failed to reset all forces!\n");
347862306a36Sopenharmony_ci
347962306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
348062306a36Sopenharmony_ci					  HIDPP_FF_GET_APERTURE,
348162306a36Sopenharmony_ci					  NULL, 0,
348262306a36Sopenharmony_ci					  &response);
348362306a36Sopenharmony_ci	if (ret) {
348462306a36Sopenharmony_ci		hid_warn(hidpp->hid_dev,
348562306a36Sopenharmony_ci			 "Failed to read range from device!\n");
348662306a36Sopenharmony_ci	}
348762306a36Sopenharmony_ci	data->range = ret ?
348862306a36Sopenharmony_ci		900 : get_unaligned_be16(&response.fap.params[0]);
348962306a36Sopenharmony_ci
349062306a36Sopenharmony_ci	/* Read the current gain values */
349162306a36Sopenharmony_ci	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
349262306a36Sopenharmony_ci					  HIDPP_FF_GET_GLOBAL_GAINS,
349362306a36Sopenharmony_ci					  NULL, 0,
349462306a36Sopenharmony_ci					  &response);
349562306a36Sopenharmony_ci	if (ret)
349662306a36Sopenharmony_ci		hid_warn(hidpp->hid_dev,
349762306a36Sopenharmony_ci			 "Failed to read gain values from device!\n");
349862306a36Sopenharmony_ci	data->gain = ret ?
349962306a36Sopenharmony_ci		0xffff : get_unaligned_be16(&response.fap.params[0]);
350062306a36Sopenharmony_ci
350162306a36Sopenharmony_ci	/* ignore boost value at response.fap.params[2] */
350262306a36Sopenharmony_ci
350362306a36Sopenharmony_ci	return g920_ff_set_autocenter(hidpp, data);
350462306a36Sopenharmony_ci}
350562306a36Sopenharmony_ci
350662306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
350762306a36Sopenharmony_ci/* Logitech Dinovo Mini keyboard with builtin touchpad                        */
350862306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
350962306a36Sopenharmony_ci#define DINOVO_MINI_PRODUCT_ID		0xb30c
351062306a36Sopenharmony_ci
351162306a36Sopenharmony_cistatic int lg_dinovo_input_mapping(struct hid_device *hdev, struct hid_input *hi,
351262306a36Sopenharmony_ci		struct hid_field *field, struct hid_usage *usage,
351362306a36Sopenharmony_ci		unsigned long **bit, int *max)
351462306a36Sopenharmony_ci{
351562306a36Sopenharmony_ci	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
351662306a36Sopenharmony_ci		return 0;
351762306a36Sopenharmony_ci
351862306a36Sopenharmony_ci	switch (usage->hid & HID_USAGE) {
351962306a36Sopenharmony_ci	case 0x00d: lg_map_key_clear(KEY_MEDIA);	break;
352062306a36Sopenharmony_ci	default:
352162306a36Sopenharmony_ci		return 0;
352262306a36Sopenharmony_ci	}
352362306a36Sopenharmony_ci	return 1;
352462306a36Sopenharmony_ci}
352562306a36Sopenharmony_ci
352662306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
352762306a36Sopenharmony_ci/* HID++1.0 devices which use HID++ reports for their wheels                  */
352862306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
352962306a36Sopenharmony_cistatic int hidpp10_wheel_connect(struct hidpp_device *hidpp)
353062306a36Sopenharmony_ci{
353162306a36Sopenharmony_ci	return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0,
353262306a36Sopenharmony_ci			HIDPP_ENABLE_WHEEL_REPORT | HIDPP_ENABLE_HWHEEL_REPORT,
353362306a36Sopenharmony_ci			HIDPP_ENABLE_WHEEL_REPORT | HIDPP_ENABLE_HWHEEL_REPORT);
353462306a36Sopenharmony_ci}
353562306a36Sopenharmony_ci
353662306a36Sopenharmony_cistatic int hidpp10_wheel_raw_event(struct hidpp_device *hidpp,
353762306a36Sopenharmony_ci				   u8 *data, int size)
353862306a36Sopenharmony_ci{
353962306a36Sopenharmony_ci	s8 value, hvalue;
354062306a36Sopenharmony_ci
354162306a36Sopenharmony_ci	if (!hidpp->input)
354262306a36Sopenharmony_ci		return -EINVAL;
354362306a36Sopenharmony_ci
354462306a36Sopenharmony_ci	if (size < 7)
354562306a36Sopenharmony_ci		return 0;
354662306a36Sopenharmony_ci
354762306a36Sopenharmony_ci	if (data[0] != REPORT_ID_HIDPP_SHORT || data[2] != HIDPP_SUB_ID_ROLLER)
354862306a36Sopenharmony_ci		return 0;
354962306a36Sopenharmony_ci
355062306a36Sopenharmony_ci	value = data[3];
355162306a36Sopenharmony_ci	hvalue = data[4];
355262306a36Sopenharmony_ci
355362306a36Sopenharmony_ci	input_report_rel(hidpp->input, REL_WHEEL, value);
355462306a36Sopenharmony_ci	input_report_rel(hidpp->input, REL_WHEEL_HI_RES, value * 120);
355562306a36Sopenharmony_ci	input_report_rel(hidpp->input, REL_HWHEEL, hvalue);
355662306a36Sopenharmony_ci	input_report_rel(hidpp->input, REL_HWHEEL_HI_RES, hvalue * 120);
355762306a36Sopenharmony_ci	input_sync(hidpp->input);
355862306a36Sopenharmony_ci
355962306a36Sopenharmony_ci	return 1;
356062306a36Sopenharmony_ci}
356162306a36Sopenharmony_ci
356262306a36Sopenharmony_cistatic void hidpp10_wheel_populate_input(struct hidpp_device *hidpp,
356362306a36Sopenharmony_ci					 struct input_dev *input_dev)
356462306a36Sopenharmony_ci{
356562306a36Sopenharmony_ci	__set_bit(EV_REL, input_dev->evbit);
356662306a36Sopenharmony_ci	__set_bit(REL_WHEEL, input_dev->relbit);
356762306a36Sopenharmony_ci	__set_bit(REL_WHEEL_HI_RES, input_dev->relbit);
356862306a36Sopenharmony_ci	__set_bit(REL_HWHEEL, input_dev->relbit);
356962306a36Sopenharmony_ci	__set_bit(REL_HWHEEL_HI_RES, input_dev->relbit);
357062306a36Sopenharmony_ci}
357162306a36Sopenharmony_ci
357262306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
357362306a36Sopenharmony_ci/* HID++1.0 mice which use HID++ reports for extra mouse buttons              */
357462306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
357562306a36Sopenharmony_cistatic int hidpp10_extra_mouse_buttons_connect(struct hidpp_device *hidpp)
357662306a36Sopenharmony_ci{
357762306a36Sopenharmony_ci	return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0,
357862306a36Sopenharmony_ci				    HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT,
357962306a36Sopenharmony_ci				    HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT);
358062306a36Sopenharmony_ci}
358162306a36Sopenharmony_ci
358262306a36Sopenharmony_cistatic int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
358362306a36Sopenharmony_ci				    u8 *data, int size)
358462306a36Sopenharmony_ci{
358562306a36Sopenharmony_ci	int i;
358662306a36Sopenharmony_ci
358762306a36Sopenharmony_ci	if (!hidpp->input)
358862306a36Sopenharmony_ci		return -EINVAL;
358962306a36Sopenharmony_ci
359062306a36Sopenharmony_ci	if (size < 7)
359162306a36Sopenharmony_ci		return 0;
359262306a36Sopenharmony_ci
359362306a36Sopenharmony_ci	if (data[0] != REPORT_ID_HIDPP_SHORT ||
359462306a36Sopenharmony_ci	    data[2] != HIDPP_SUB_ID_MOUSE_EXTRA_BTNS)
359562306a36Sopenharmony_ci		return 0;
359662306a36Sopenharmony_ci
359762306a36Sopenharmony_ci	/*
359862306a36Sopenharmony_ci	 * Buttons are either delivered through the regular mouse report *or*
359962306a36Sopenharmony_ci	 * through the extra buttons report. At least for button 6 how it is
360062306a36Sopenharmony_ci	 * delivered differs per receiver firmware version. Even receivers with
360162306a36Sopenharmony_ci	 * the same usb-id show different behavior, so we handle both cases.
360262306a36Sopenharmony_ci	 */
360362306a36Sopenharmony_ci	for (i = 0; i < 8; i++)
360462306a36Sopenharmony_ci		input_report_key(hidpp->input, BTN_MOUSE + i,
360562306a36Sopenharmony_ci				 (data[3] & (1 << i)));
360662306a36Sopenharmony_ci
360762306a36Sopenharmony_ci	/* Some mice report events on button 9+, use BTN_MISC */
360862306a36Sopenharmony_ci	for (i = 0; i < 8; i++)
360962306a36Sopenharmony_ci		input_report_key(hidpp->input, BTN_MISC + i,
361062306a36Sopenharmony_ci				 (data[4] & (1 << i)));
361162306a36Sopenharmony_ci
361262306a36Sopenharmony_ci	input_sync(hidpp->input);
361362306a36Sopenharmony_ci	return 1;
361462306a36Sopenharmony_ci}
361562306a36Sopenharmony_ci
361662306a36Sopenharmony_cistatic void hidpp10_extra_mouse_buttons_populate_input(
361762306a36Sopenharmony_ci			struct hidpp_device *hidpp, struct input_dev *input_dev)
361862306a36Sopenharmony_ci{
361962306a36Sopenharmony_ci	/* BTN_MOUSE - BTN_MOUSE+7 are set already by the descriptor */
362062306a36Sopenharmony_ci	__set_bit(BTN_0, input_dev->keybit);
362162306a36Sopenharmony_ci	__set_bit(BTN_1, input_dev->keybit);
362262306a36Sopenharmony_ci	__set_bit(BTN_2, input_dev->keybit);
362362306a36Sopenharmony_ci	__set_bit(BTN_3, input_dev->keybit);
362462306a36Sopenharmony_ci	__set_bit(BTN_4, input_dev->keybit);
362562306a36Sopenharmony_ci	__set_bit(BTN_5, input_dev->keybit);
362662306a36Sopenharmony_ci	__set_bit(BTN_6, input_dev->keybit);
362762306a36Sopenharmony_ci	__set_bit(BTN_7, input_dev->keybit);
362862306a36Sopenharmony_ci}
362962306a36Sopenharmony_ci
363062306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
363162306a36Sopenharmony_ci/* HID++1.0 kbds which only report 0x10xx consumer usages through sub-id 0x03 */
363262306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
363362306a36Sopenharmony_ci
363462306a36Sopenharmony_ci/* Find the consumer-page input report desc and change Maximums to 0x107f */
363562306a36Sopenharmony_cistatic u8 *hidpp10_consumer_keys_report_fixup(struct hidpp_device *hidpp,
363662306a36Sopenharmony_ci					      u8 *_rdesc, unsigned int *rsize)
363762306a36Sopenharmony_ci{
363862306a36Sopenharmony_ci	/* Note 0 terminated so we can use strnstr to search for this. */
363962306a36Sopenharmony_ci	static const char consumer_rdesc_start[] = {
364062306a36Sopenharmony_ci		0x05, 0x0C,	/* USAGE_PAGE (Consumer Devices)       */
364162306a36Sopenharmony_ci		0x09, 0x01,	/* USAGE (Consumer Control)            */
364262306a36Sopenharmony_ci		0xA1, 0x01,	/* COLLECTION (Application)            */
364362306a36Sopenharmony_ci		0x85, 0x03,	/* REPORT_ID = 3                       */
364462306a36Sopenharmony_ci		0x75, 0x10,	/* REPORT_SIZE (16)                    */
364562306a36Sopenharmony_ci		0x95, 0x02,	/* REPORT_COUNT (2)                    */
364662306a36Sopenharmony_ci		0x15, 0x01,	/* LOGICAL_MIN (1)                     */
364762306a36Sopenharmony_ci		0x26, 0x00	/* LOGICAL_MAX (...                    */
364862306a36Sopenharmony_ci	};
364962306a36Sopenharmony_ci	char *consumer_rdesc, *rdesc = (char *)_rdesc;
365062306a36Sopenharmony_ci	unsigned int size;
365162306a36Sopenharmony_ci
365262306a36Sopenharmony_ci	consumer_rdesc = strnstr(rdesc, consumer_rdesc_start, *rsize);
365362306a36Sopenharmony_ci	size = *rsize - (consumer_rdesc - rdesc);
365462306a36Sopenharmony_ci	if (consumer_rdesc && size >= 25) {
365562306a36Sopenharmony_ci		consumer_rdesc[15] = 0x7f;
365662306a36Sopenharmony_ci		consumer_rdesc[16] = 0x10;
365762306a36Sopenharmony_ci		consumer_rdesc[20] = 0x7f;
365862306a36Sopenharmony_ci		consumer_rdesc[21] = 0x10;
365962306a36Sopenharmony_ci	}
366062306a36Sopenharmony_ci	return _rdesc;
366162306a36Sopenharmony_ci}
366262306a36Sopenharmony_ci
366362306a36Sopenharmony_cistatic int hidpp10_consumer_keys_connect(struct hidpp_device *hidpp)
366462306a36Sopenharmony_ci{
366562306a36Sopenharmony_ci	return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0,
366662306a36Sopenharmony_ci				    HIDPP_ENABLE_CONSUMER_REPORT,
366762306a36Sopenharmony_ci				    HIDPP_ENABLE_CONSUMER_REPORT);
366862306a36Sopenharmony_ci}
366962306a36Sopenharmony_ci
367062306a36Sopenharmony_cistatic int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp,
367162306a36Sopenharmony_ci					   u8 *data, int size)
367262306a36Sopenharmony_ci{
367362306a36Sopenharmony_ci	u8 consumer_report[5];
367462306a36Sopenharmony_ci
367562306a36Sopenharmony_ci	if (size < 7)
367662306a36Sopenharmony_ci		return 0;
367762306a36Sopenharmony_ci
367862306a36Sopenharmony_ci	if (data[0] != REPORT_ID_HIDPP_SHORT ||
367962306a36Sopenharmony_ci	    data[2] != HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS)
368062306a36Sopenharmony_ci		return 0;
368162306a36Sopenharmony_ci
368262306a36Sopenharmony_ci	/*
368362306a36Sopenharmony_ci	 * Build a normal consumer report (3) out of the data, this detour
368462306a36Sopenharmony_ci	 * is necessary to get some keyboards to report their 0x10xx usages.
368562306a36Sopenharmony_ci	 */
368662306a36Sopenharmony_ci	consumer_report[0] = 0x03;
368762306a36Sopenharmony_ci	memcpy(&consumer_report[1], &data[3], 4);
368862306a36Sopenharmony_ci	/* We are called from atomic context */
368962306a36Sopenharmony_ci	hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT,
369062306a36Sopenharmony_ci			     consumer_report, 5, 1);
369162306a36Sopenharmony_ci
369262306a36Sopenharmony_ci	return 1;
369362306a36Sopenharmony_ci}
369462306a36Sopenharmony_ci
369562306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
369662306a36Sopenharmony_ci/* High-resolution scroll wheels                                              */
369762306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
369862306a36Sopenharmony_ci
369962306a36Sopenharmony_cistatic int hi_res_scroll_enable(struct hidpp_device *hidpp)
370062306a36Sopenharmony_ci{
370162306a36Sopenharmony_ci	int ret;
370262306a36Sopenharmony_ci	u8 multiplier = 1;
370362306a36Sopenharmony_ci
370462306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL) {
370562306a36Sopenharmony_ci		ret = hidpp_hrw_set_wheel_mode(hidpp, false, true, false);
370662306a36Sopenharmony_ci		if (ret == 0)
370762306a36Sopenharmony_ci			ret = hidpp_hrw_get_wheel_capability(hidpp, &multiplier);
370862306a36Sopenharmony_ci	} else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL) {
370962306a36Sopenharmony_ci		ret = hidpp_hrs_set_highres_scrolling_mode(hidpp, true,
371062306a36Sopenharmony_ci							   &multiplier);
371162306a36Sopenharmony_ci	} else /* if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL) */ {
371262306a36Sopenharmony_ci		ret = hidpp10_enable_scrolling_acceleration(hidpp);
371362306a36Sopenharmony_ci		multiplier = 8;
371462306a36Sopenharmony_ci	}
371562306a36Sopenharmony_ci	if (ret) {
371662306a36Sopenharmony_ci		hid_dbg(hidpp->hid_dev,
371762306a36Sopenharmony_ci			"Could not enable hi-res scrolling: %d\n", ret);
371862306a36Sopenharmony_ci		return ret;
371962306a36Sopenharmony_ci	}
372062306a36Sopenharmony_ci
372162306a36Sopenharmony_ci	if (multiplier == 0) {
372262306a36Sopenharmony_ci		hid_dbg(hidpp->hid_dev,
372362306a36Sopenharmony_ci			"Invalid multiplier 0 from device, setting it to 1\n");
372462306a36Sopenharmony_ci		multiplier = 1;
372562306a36Sopenharmony_ci	}
372662306a36Sopenharmony_ci
372762306a36Sopenharmony_ci	hidpp->vertical_wheel_counter.wheel_multiplier = multiplier;
372862306a36Sopenharmony_ci	hid_dbg(hidpp->hid_dev, "wheel multiplier = %d\n", multiplier);
372962306a36Sopenharmony_ci	return 0;
373062306a36Sopenharmony_ci}
373162306a36Sopenharmony_ci
373262306a36Sopenharmony_cistatic int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp)
373362306a36Sopenharmony_ci{
373462306a36Sopenharmony_ci	int ret;
373562306a36Sopenharmony_ci	unsigned long capabilities;
373662306a36Sopenharmony_ci
373762306a36Sopenharmony_ci	capabilities = hidpp->capabilities;
373862306a36Sopenharmony_ci
373962306a36Sopenharmony_ci	if (hidpp->protocol_major >= 2) {
374062306a36Sopenharmony_ci		u8 feature_index;
374162306a36Sopenharmony_ci		u8 feature_type;
374262306a36Sopenharmony_ci
374362306a36Sopenharmony_ci		ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
374462306a36Sopenharmony_ci					     &feature_index, &feature_type);
374562306a36Sopenharmony_ci		if (!ret) {
374662306a36Sopenharmony_ci			hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL;
374762306a36Sopenharmony_ci			hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scroll wheel\n");
374862306a36Sopenharmony_ci			return 0;
374962306a36Sopenharmony_ci		}
375062306a36Sopenharmony_ci		ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
375162306a36Sopenharmony_ci					     &feature_index, &feature_type);
375262306a36Sopenharmony_ci		if (!ret) {
375362306a36Sopenharmony_ci			hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL;
375462306a36Sopenharmony_ci			hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n");
375562306a36Sopenharmony_ci		}
375662306a36Sopenharmony_ci	} else {
375762306a36Sopenharmony_ci		/* We cannot detect fast scrolling support on HID++ 1.0 devices */
375862306a36Sopenharmony_ci		if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) {
375962306a36Sopenharmony_ci			hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL;
376062306a36Sopenharmony_ci			hid_dbg(hidpp->hid_dev, "Detected HID++ 1.0 fast scroll\n");
376162306a36Sopenharmony_ci		}
376262306a36Sopenharmony_ci	}
376362306a36Sopenharmony_ci
376462306a36Sopenharmony_ci	if (hidpp->capabilities == capabilities)
376562306a36Sopenharmony_ci		hid_dbg(hidpp->hid_dev, "Did not detect HID++ hi-res scrolling hardware support\n");
376662306a36Sopenharmony_ci	return 0;
376762306a36Sopenharmony_ci}
376862306a36Sopenharmony_ci
376962306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
377062306a36Sopenharmony_ci/* Generic HID++ devices                                                      */
377162306a36Sopenharmony_ci/* -------------------------------------------------------------------------- */
377262306a36Sopenharmony_ci
377362306a36Sopenharmony_cistatic u8 *hidpp_report_fixup(struct hid_device *hdev, u8 *rdesc,
377462306a36Sopenharmony_ci			      unsigned int *rsize)
377562306a36Sopenharmony_ci{
377662306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
377762306a36Sopenharmony_ci
377862306a36Sopenharmony_ci	if (!hidpp)
377962306a36Sopenharmony_ci		return rdesc;
378062306a36Sopenharmony_ci
378162306a36Sopenharmony_ci	/* For 27 MHz keyboards the quirk gets set after hid_parse. */
378262306a36Sopenharmony_ci	if (hdev->group == HID_GROUP_LOGITECH_27MHZ_DEVICE ||
378362306a36Sopenharmony_ci	    (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS))
378462306a36Sopenharmony_ci		rdesc = hidpp10_consumer_keys_report_fixup(hidpp, rdesc, rsize);
378562306a36Sopenharmony_ci
378662306a36Sopenharmony_ci	return rdesc;
378762306a36Sopenharmony_ci}
378862306a36Sopenharmony_ci
378962306a36Sopenharmony_cistatic int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
379062306a36Sopenharmony_ci		struct hid_field *field, struct hid_usage *usage,
379162306a36Sopenharmony_ci		unsigned long **bit, int *max)
379262306a36Sopenharmony_ci{
379362306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
379462306a36Sopenharmony_ci
379562306a36Sopenharmony_ci	if (!hidpp)
379662306a36Sopenharmony_ci		return 0;
379762306a36Sopenharmony_ci
379862306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
379962306a36Sopenharmony_ci		return wtp_input_mapping(hdev, hi, field, usage, bit, max);
380062306a36Sopenharmony_ci	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
380162306a36Sopenharmony_ci			field->application != HID_GD_MOUSE)
380262306a36Sopenharmony_ci		return m560_input_mapping(hdev, hi, field, usage, bit, max);
380362306a36Sopenharmony_ci
380462306a36Sopenharmony_ci	if (hdev->product == DINOVO_MINI_PRODUCT_ID)
380562306a36Sopenharmony_ci		return lg_dinovo_input_mapping(hdev, hi, field, usage, bit, max);
380662306a36Sopenharmony_ci
380762306a36Sopenharmony_ci	return 0;
380862306a36Sopenharmony_ci}
380962306a36Sopenharmony_ci
381062306a36Sopenharmony_cistatic int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi,
381162306a36Sopenharmony_ci		struct hid_field *field, struct hid_usage *usage,
381262306a36Sopenharmony_ci		unsigned long **bit, int *max)
381362306a36Sopenharmony_ci{
381462306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
381562306a36Sopenharmony_ci
381662306a36Sopenharmony_ci	if (!hidpp)
381762306a36Sopenharmony_ci		return 0;
381862306a36Sopenharmony_ci
381962306a36Sopenharmony_ci	/* Ensure that Logitech G920 is not given a default fuzz/flat value */
382062306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
382162306a36Sopenharmony_ci		if (usage->type == EV_ABS && (usage->code == ABS_X ||
382262306a36Sopenharmony_ci				usage->code == ABS_Y || usage->code == ABS_Z ||
382362306a36Sopenharmony_ci				usage->code == ABS_RZ)) {
382462306a36Sopenharmony_ci			field->application = HID_GD_MULTIAXIS;
382562306a36Sopenharmony_ci		}
382662306a36Sopenharmony_ci	}
382762306a36Sopenharmony_ci
382862306a36Sopenharmony_ci	return 0;
382962306a36Sopenharmony_ci}
383062306a36Sopenharmony_ci
383162306a36Sopenharmony_ci
383262306a36Sopenharmony_cistatic void hidpp_populate_input(struct hidpp_device *hidpp,
383362306a36Sopenharmony_ci				 struct input_dev *input)
383462306a36Sopenharmony_ci{
383562306a36Sopenharmony_ci	hidpp->input = input;
383662306a36Sopenharmony_ci
383762306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
383862306a36Sopenharmony_ci		wtp_populate_input(hidpp, input);
383962306a36Sopenharmony_ci	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
384062306a36Sopenharmony_ci		m560_populate_input(hidpp, input);
384162306a36Sopenharmony_ci
384262306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS)
384362306a36Sopenharmony_ci		hidpp10_wheel_populate_input(hidpp, input);
384462306a36Sopenharmony_ci
384562306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
384662306a36Sopenharmony_ci		hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
384762306a36Sopenharmony_ci}
384862306a36Sopenharmony_ci
384962306a36Sopenharmony_cistatic int hidpp_input_configured(struct hid_device *hdev,
385062306a36Sopenharmony_ci				struct hid_input *hidinput)
385162306a36Sopenharmony_ci{
385262306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
385362306a36Sopenharmony_ci	struct input_dev *input = hidinput->input;
385462306a36Sopenharmony_ci
385562306a36Sopenharmony_ci	if (!hidpp)
385662306a36Sopenharmony_ci		return 0;
385762306a36Sopenharmony_ci
385862306a36Sopenharmony_ci	hidpp_populate_input(hidpp, input);
385962306a36Sopenharmony_ci
386062306a36Sopenharmony_ci	return 0;
386162306a36Sopenharmony_ci}
386262306a36Sopenharmony_ci
386362306a36Sopenharmony_cistatic int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
386462306a36Sopenharmony_ci		int size)
386562306a36Sopenharmony_ci{
386662306a36Sopenharmony_ci	struct hidpp_report *question = hidpp->send_receive_buf;
386762306a36Sopenharmony_ci	struct hidpp_report *answer = hidpp->send_receive_buf;
386862306a36Sopenharmony_ci	struct hidpp_report *report = (struct hidpp_report *)data;
386962306a36Sopenharmony_ci	int ret;
387062306a36Sopenharmony_ci
387162306a36Sopenharmony_ci	/*
387262306a36Sopenharmony_ci	 * If the mutex is locked then we have a pending answer from a
387362306a36Sopenharmony_ci	 * previously sent command.
387462306a36Sopenharmony_ci	 */
387562306a36Sopenharmony_ci	if (unlikely(mutex_is_locked(&hidpp->send_mutex))) {
387662306a36Sopenharmony_ci		/*
387762306a36Sopenharmony_ci		 * Check for a correct hidpp20 answer or the corresponding
387862306a36Sopenharmony_ci		 * error
387962306a36Sopenharmony_ci		 */
388062306a36Sopenharmony_ci		if (hidpp_match_answer(question, report) ||
388162306a36Sopenharmony_ci				hidpp_match_error(question, report)) {
388262306a36Sopenharmony_ci			*answer = *report;
388362306a36Sopenharmony_ci			hidpp->answer_available = true;
388462306a36Sopenharmony_ci			wake_up(&hidpp->wait);
388562306a36Sopenharmony_ci			/*
388662306a36Sopenharmony_ci			 * This was an answer to a command that this driver sent
388762306a36Sopenharmony_ci			 * We return 1 to hid-core to avoid forwarding the
388862306a36Sopenharmony_ci			 * command upstream as it has been treated by the driver
388962306a36Sopenharmony_ci			 */
389062306a36Sopenharmony_ci
389162306a36Sopenharmony_ci			return 1;
389262306a36Sopenharmony_ci		}
389362306a36Sopenharmony_ci	}
389462306a36Sopenharmony_ci
389562306a36Sopenharmony_ci	if (unlikely(hidpp_report_is_connect_event(hidpp, report))) {
389662306a36Sopenharmony_ci		atomic_set(&hidpp->connected,
389762306a36Sopenharmony_ci				!(report->rap.params[0] & (1 << 6)));
389862306a36Sopenharmony_ci		if (schedule_work(&hidpp->work) == 0)
389962306a36Sopenharmony_ci			dbg_hid("%s: connect event already queued\n", __func__);
390062306a36Sopenharmony_ci		return 1;
390162306a36Sopenharmony_ci	}
390262306a36Sopenharmony_ci
390362306a36Sopenharmony_ci	if (hidpp->hid_dev->group == HID_GROUP_LOGITECH_27MHZ_DEVICE &&
390462306a36Sopenharmony_ci	    data[0] == REPORT_ID_HIDPP_SHORT &&
390562306a36Sopenharmony_ci	    data[2] == HIDPP_SUB_ID_USER_IFACE_EVENT &&
390662306a36Sopenharmony_ci	    (data[3] & HIDPP_USER_IFACE_EVENT_ENCRYPTION_KEY_LOST)) {
390762306a36Sopenharmony_ci		dev_err_ratelimited(&hidpp->hid_dev->dev,
390862306a36Sopenharmony_ci			"Error the keyboard's wireless encryption key has been lost, your keyboard will not work unless you re-configure encryption.\n");
390962306a36Sopenharmony_ci		dev_err_ratelimited(&hidpp->hid_dev->dev,
391062306a36Sopenharmony_ci			"See: https://gitlab.freedesktop.org/jwrdegoede/logitech-27mhz-keyboard-encryption-setup/\n");
391162306a36Sopenharmony_ci	}
391262306a36Sopenharmony_ci
391362306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
391462306a36Sopenharmony_ci		ret = hidpp20_battery_event_1000(hidpp, data, size);
391562306a36Sopenharmony_ci		if (ret != 0)
391662306a36Sopenharmony_ci			return ret;
391762306a36Sopenharmony_ci		ret = hidpp20_battery_event_1004(hidpp, data, size);
391862306a36Sopenharmony_ci		if (ret != 0)
391962306a36Sopenharmony_ci			return ret;
392062306a36Sopenharmony_ci		ret = hidpp_solar_battery_event(hidpp, data, size);
392162306a36Sopenharmony_ci		if (ret != 0)
392262306a36Sopenharmony_ci			return ret;
392362306a36Sopenharmony_ci		ret = hidpp20_battery_voltage_event(hidpp, data, size);
392462306a36Sopenharmony_ci		if (ret != 0)
392562306a36Sopenharmony_ci			return ret;
392662306a36Sopenharmony_ci		ret = hidpp20_adc_measurement_event_1f20(hidpp, data, size);
392762306a36Sopenharmony_ci		if (ret != 0)
392862306a36Sopenharmony_ci			return ret;
392962306a36Sopenharmony_ci	}
393062306a36Sopenharmony_ci
393162306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
393262306a36Sopenharmony_ci		ret = hidpp10_battery_event(hidpp, data, size);
393362306a36Sopenharmony_ci		if (ret != 0)
393462306a36Sopenharmony_ci			return ret;
393562306a36Sopenharmony_ci	}
393662306a36Sopenharmony_ci
393762306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) {
393862306a36Sopenharmony_ci		ret = hidpp10_wheel_raw_event(hidpp, data, size);
393962306a36Sopenharmony_ci		if (ret != 0)
394062306a36Sopenharmony_ci			return ret;
394162306a36Sopenharmony_ci	}
394262306a36Sopenharmony_ci
394362306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) {
394462306a36Sopenharmony_ci		ret = hidpp10_extra_mouse_buttons_raw_event(hidpp, data, size);
394562306a36Sopenharmony_ci		if (ret != 0)
394662306a36Sopenharmony_ci			return ret;
394762306a36Sopenharmony_ci	}
394862306a36Sopenharmony_ci
394962306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
395062306a36Sopenharmony_ci		ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
395162306a36Sopenharmony_ci		if (ret != 0)
395262306a36Sopenharmony_ci			return ret;
395362306a36Sopenharmony_ci	}
395462306a36Sopenharmony_ci
395562306a36Sopenharmony_ci	return 0;
395662306a36Sopenharmony_ci}
395762306a36Sopenharmony_ci
395862306a36Sopenharmony_cistatic int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
395962306a36Sopenharmony_ci		u8 *data, int size)
396062306a36Sopenharmony_ci{
396162306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
396262306a36Sopenharmony_ci	int ret = 0;
396362306a36Sopenharmony_ci
396462306a36Sopenharmony_ci	if (!hidpp)
396562306a36Sopenharmony_ci		return 0;
396662306a36Sopenharmony_ci
396762306a36Sopenharmony_ci	/* Generic HID++ processing. */
396862306a36Sopenharmony_ci	switch (data[0]) {
396962306a36Sopenharmony_ci	case REPORT_ID_HIDPP_VERY_LONG:
397062306a36Sopenharmony_ci		if (size != hidpp->very_long_report_length) {
397162306a36Sopenharmony_ci			hid_err(hdev, "received hid++ report of bad size (%d)",
397262306a36Sopenharmony_ci				size);
397362306a36Sopenharmony_ci			return 1;
397462306a36Sopenharmony_ci		}
397562306a36Sopenharmony_ci		ret = hidpp_raw_hidpp_event(hidpp, data, size);
397662306a36Sopenharmony_ci		break;
397762306a36Sopenharmony_ci	case REPORT_ID_HIDPP_LONG:
397862306a36Sopenharmony_ci		if (size != HIDPP_REPORT_LONG_LENGTH) {
397962306a36Sopenharmony_ci			hid_err(hdev, "received hid++ report of bad size (%d)",
398062306a36Sopenharmony_ci				size);
398162306a36Sopenharmony_ci			return 1;
398262306a36Sopenharmony_ci		}
398362306a36Sopenharmony_ci		ret = hidpp_raw_hidpp_event(hidpp, data, size);
398462306a36Sopenharmony_ci		break;
398562306a36Sopenharmony_ci	case REPORT_ID_HIDPP_SHORT:
398662306a36Sopenharmony_ci		if (size != HIDPP_REPORT_SHORT_LENGTH) {
398762306a36Sopenharmony_ci			hid_err(hdev, "received hid++ report of bad size (%d)",
398862306a36Sopenharmony_ci				size);
398962306a36Sopenharmony_ci			return 1;
399062306a36Sopenharmony_ci		}
399162306a36Sopenharmony_ci		ret = hidpp_raw_hidpp_event(hidpp, data, size);
399262306a36Sopenharmony_ci		break;
399362306a36Sopenharmony_ci	}
399462306a36Sopenharmony_ci
399562306a36Sopenharmony_ci	/* If no report is available for further processing, skip calling
399662306a36Sopenharmony_ci	 * raw_event of subclasses. */
399762306a36Sopenharmony_ci	if (ret != 0)
399862306a36Sopenharmony_ci		return ret;
399962306a36Sopenharmony_ci
400062306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
400162306a36Sopenharmony_ci		return wtp_raw_event(hdev, data, size);
400262306a36Sopenharmony_ci	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
400362306a36Sopenharmony_ci		return m560_raw_event(hdev, data, size);
400462306a36Sopenharmony_ci
400562306a36Sopenharmony_ci	return 0;
400662306a36Sopenharmony_ci}
400762306a36Sopenharmony_ci
400862306a36Sopenharmony_cistatic int hidpp_event(struct hid_device *hdev, struct hid_field *field,
400962306a36Sopenharmony_ci	struct hid_usage *usage, __s32 value)
401062306a36Sopenharmony_ci{
401162306a36Sopenharmony_ci	/* This function will only be called for scroll events, due to the
401262306a36Sopenharmony_ci	 * restriction imposed in hidpp_usages.
401362306a36Sopenharmony_ci	 */
401462306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
401562306a36Sopenharmony_ci	struct hidpp_scroll_counter *counter;
401662306a36Sopenharmony_ci
401762306a36Sopenharmony_ci	if (!hidpp)
401862306a36Sopenharmony_ci		return 0;
401962306a36Sopenharmony_ci
402062306a36Sopenharmony_ci	counter = &hidpp->vertical_wheel_counter;
402162306a36Sopenharmony_ci	/* A scroll event may occur before the multiplier has been retrieved or
402262306a36Sopenharmony_ci	 * the input device set, or high-res scroll enabling may fail. In such
402362306a36Sopenharmony_ci	 * cases we must return early (falling back to default behaviour) to
402462306a36Sopenharmony_ci	 * avoid a crash in hidpp_scroll_counter_handle_scroll.
402562306a36Sopenharmony_ci	 */
402662306a36Sopenharmony_ci	if (!(hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL)
402762306a36Sopenharmony_ci	    || value == 0 || hidpp->input == NULL
402862306a36Sopenharmony_ci	    || counter->wheel_multiplier == 0)
402962306a36Sopenharmony_ci		return 0;
403062306a36Sopenharmony_ci
403162306a36Sopenharmony_ci	hidpp_scroll_counter_handle_scroll(hidpp->input, counter, value);
403262306a36Sopenharmony_ci	return 1;
403362306a36Sopenharmony_ci}
403462306a36Sopenharmony_ci
403562306a36Sopenharmony_cistatic int hidpp_initialize_battery(struct hidpp_device *hidpp)
403662306a36Sopenharmony_ci{
403762306a36Sopenharmony_ci	static atomic_t battery_no = ATOMIC_INIT(0);
403862306a36Sopenharmony_ci	struct power_supply_config cfg = { .drv_data = hidpp };
403962306a36Sopenharmony_ci	struct power_supply_desc *desc = &hidpp->battery.desc;
404062306a36Sopenharmony_ci	enum power_supply_property *battery_props;
404162306a36Sopenharmony_ci	struct hidpp_battery *battery;
404262306a36Sopenharmony_ci	unsigned int num_battery_props;
404362306a36Sopenharmony_ci	unsigned long n;
404462306a36Sopenharmony_ci	int ret;
404562306a36Sopenharmony_ci
404662306a36Sopenharmony_ci	if (hidpp->battery.ps)
404762306a36Sopenharmony_ci		return 0;
404862306a36Sopenharmony_ci
404962306a36Sopenharmony_ci	hidpp->battery.feature_index = 0xff;
405062306a36Sopenharmony_ci	hidpp->battery.solar_feature_index = 0xff;
405162306a36Sopenharmony_ci	hidpp->battery.voltage_feature_index = 0xff;
405262306a36Sopenharmony_ci	hidpp->battery.adc_measurement_feature_index = 0xff;
405362306a36Sopenharmony_ci
405462306a36Sopenharmony_ci	if (hidpp->protocol_major >= 2) {
405562306a36Sopenharmony_ci		if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
405662306a36Sopenharmony_ci			ret = hidpp_solar_request_battery_event(hidpp);
405762306a36Sopenharmony_ci		else {
405862306a36Sopenharmony_ci			/* we only support one battery feature right now, so let's
405962306a36Sopenharmony_ci			   first check the ones that support battery level first
406062306a36Sopenharmony_ci			   and leave voltage for last */
406162306a36Sopenharmony_ci			ret = hidpp20_query_battery_info_1000(hidpp);
406262306a36Sopenharmony_ci			if (ret)
406362306a36Sopenharmony_ci				ret = hidpp20_query_battery_info_1004(hidpp);
406462306a36Sopenharmony_ci			if (ret)
406562306a36Sopenharmony_ci				ret = hidpp20_query_battery_voltage_info(hidpp);
406662306a36Sopenharmony_ci			if (ret)
406762306a36Sopenharmony_ci				ret = hidpp20_query_adc_measurement_info_1f20(hidpp);
406862306a36Sopenharmony_ci		}
406962306a36Sopenharmony_ci
407062306a36Sopenharmony_ci		if (ret)
407162306a36Sopenharmony_ci			return ret;
407262306a36Sopenharmony_ci		hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY;
407362306a36Sopenharmony_ci	} else {
407462306a36Sopenharmony_ci		ret = hidpp10_query_battery_status(hidpp);
407562306a36Sopenharmony_ci		if (ret) {
407662306a36Sopenharmony_ci			ret = hidpp10_query_battery_mileage(hidpp);
407762306a36Sopenharmony_ci			if (ret)
407862306a36Sopenharmony_ci				return -ENOENT;
407962306a36Sopenharmony_ci			hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;
408062306a36Sopenharmony_ci		} else {
408162306a36Sopenharmony_ci			hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
408262306a36Sopenharmony_ci		}
408362306a36Sopenharmony_ci		hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_BATTERY;
408462306a36Sopenharmony_ci	}
408562306a36Sopenharmony_ci
408662306a36Sopenharmony_ci	battery_props = devm_kmemdup(&hidpp->hid_dev->dev,
408762306a36Sopenharmony_ci				     hidpp_battery_props,
408862306a36Sopenharmony_ci				     sizeof(hidpp_battery_props),
408962306a36Sopenharmony_ci				     GFP_KERNEL);
409062306a36Sopenharmony_ci	if (!battery_props)
409162306a36Sopenharmony_ci		return -ENOMEM;
409262306a36Sopenharmony_ci
409362306a36Sopenharmony_ci	num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 3;
409462306a36Sopenharmony_ci
409562306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE ||
409662306a36Sopenharmony_ci	    hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE ||
409762306a36Sopenharmony_ci	    hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
409862306a36Sopenharmony_ci	    hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
409962306a36Sopenharmony_ci		battery_props[num_battery_props++] =
410062306a36Sopenharmony_ci				POWER_SUPPLY_PROP_CAPACITY;
410162306a36Sopenharmony_ci
410262306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS)
410362306a36Sopenharmony_ci		battery_props[num_battery_props++] =
410462306a36Sopenharmony_ci				POWER_SUPPLY_PROP_CAPACITY_LEVEL;
410562306a36Sopenharmony_ci
410662306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
410762306a36Sopenharmony_ci	    hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
410862306a36Sopenharmony_ci		battery_props[num_battery_props++] =
410962306a36Sopenharmony_ci			POWER_SUPPLY_PROP_VOLTAGE_NOW;
411062306a36Sopenharmony_ci
411162306a36Sopenharmony_ci	battery = &hidpp->battery;
411262306a36Sopenharmony_ci
411362306a36Sopenharmony_ci	n = atomic_inc_return(&battery_no) - 1;
411462306a36Sopenharmony_ci	desc->properties = battery_props;
411562306a36Sopenharmony_ci	desc->num_properties = num_battery_props;
411662306a36Sopenharmony_ci	desc->get_property = hidpp_battery_get_property;
411762306a36Sopenharmony_ci	sprintf(battery->name, "hidpp_battery_%ld", n);
411862306a36Sopenharmony_ci	desc->name = battery->name;
411962306a36Sopenharmony_ci	desc->type = POWER_SUPPLY_TYPE_BATTERY;
412062306a36Sopenharmony_ci	desc->use_for_apm = 0;
412162306a36Sopenharmony_ci
412262306a36Sopenharmony_ci	battery->ps = devm_power_supply_register(&hidpp->hid_dev->dev,
412362306a36Sopenharmony_ci						 &battery->desc,
412462306a36Sopenharmony_ci						 &cfg);
412562306a36Sopenharmony_ci	if (IS_ERR(battery->ps))
412662306a36Sopenharmony_ci		return PTR_ERR(battery->ps);
412762306a36Sopenharmony_ci
412862306a36Sopenharmony_ci	power_supply_powers(battery->ps, &hidpp->hid_dev->dev);
412962306a36Sopenharmony_ci
413062306a36Sopenharmony_ci	return ret;
413162306a36Sopenharmony_ci}
413262306a36Sopenharmony_ci
413362306a36Sopenharmony_cistatic void hidpp_overwrite_name(struct hid_device *hdev)
413462306a36Sopenharmony_ci{
413562306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
413662306a36Sopenharmony_ci	char *name;
413762306a36Sopenharmony_ci
413862306a36Sopenharmony_ci	if (hidpp->protocol_major < 2)
413962306a36Sopenharmony_ci		return;
414062306a36Sopenharmony_ci
414162306a36Sopenharmony_ci	name = hidpp_get_device_name(hidpp);
414262306a36Sopenharmony_ci
414362306a36Sopenharmony_ci	if (!name) {
414462306a36Sopenharmony_ci		hid_err(hdev, "unable to retrieve the name of the device");
414562306a36Sopenharmony_ci	} else {
414662306a36Sopenharmony_ci		dbg_hid("HID++: Got name: %s\n", name);
414762306a36Sopenharmony_ci		snprintf(hdev->name, sizeof(hdev->name), "%s", name);
414862306a36Sopenharmony_ci	}
414962306a36Sopenharmony_ci
415062306a36Sopenharmony_ci	kfree(name);
415162306a36Sopenharmony_ci}
415262306a36Sopenharmony_ci
415362306a36Sopenharmony_cistatic int hidpp_input_open(struct input_dev *dev)
415462306a36Sopenharmony_ci{
415562306a36Sopenharmony_ci	struct hid_device *hid = input_get_drvdata(dev);
415662306a36Sopenharmony_ci
415762306a36Sopenharmony_ci	return hid_hw_open(hid);
415862306a36Sopenharmony_ci}
415962306a36Sopenharmony_ci
416062306a36Sopenharmony_cistatic void hidpp_input_close(struct input_dev *dev)
416162306a36Sopenharmony_ci{
416262306a36Sopenharmony_ci	struct hid_device *hid = input_get_drvdata(dev);
416362306a36Sopenharmony_ci
416462306a36Sopenharmony_ci	hid_hw_close(hid);
416562306a36Sopenharmony_ci}
416662306a36Sopenharmony_ci
416762306a36Sopenharmony_cistatic struct input_dev *hidpp_allocate_input(struct hid_device *hdev)
416862306a36Sopenharmony_ci{
416962306a36Sopenharmony_ci	struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev);
417062306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
417162306a36Sopenharmony_ci
417262306a36Sopenharmony_ci	if (!input_dev)
417362306a36Sopenharmony_ci		return NULL;
417462306a36Sopenharmony_ci
417562306a36Sopenharmony_ci	input_set_drvdata(input_dev, hdev);
417662306a36Sopenharmony_ci	input_dev->open = hidpp_input_open;
417762306a36Sopenharmony_ci	input_dev->close = hidpp_input_close;
417862306a36Sopenharmony_ci
417962306a36Sopenharmony_ci	input_dev->name = hidpp->name;
418062306a36Sopenharmony_ci	input_dev->phys = hdev->phys;
418162306a36Sopenharmony_ci	input_dev->uniq = hdev->uniq;
418262306a36Sopenharmony_ci	input_dev->id.bustype = hdev->bus;
418362306a36Sopenharmony_ci	input_dev->id.vendor  = hdev->vendor;
418462306a36Sopenharmony_ci	input_dev->id.product = hdev->product;
418562306a36Sopenharmony_ci	input_dev->id.version = hdev->version;
418662306a36Sopenharmony_ci	input_dev->dev.parent = &hdev->dev;
418762306a36Sopenharmony_ci
418862306a36Sopenharmony_ci	return input_dev;
418962306a36Sopenharmony_ci}
419062306a36Sopenharmony_ci
419162306a36Sopenharmony_cistatic void hidpp_connect_event(struct hidpp_device *hidpp)
419262306a36Sopenharmony_ci{
419362306a36Sopenharmony_ci	struct hid_device *hdev = hidpp->hid_dev;
419462306a36Sopenharmony_ci	int ret = 0;
419562306a36Sopenharmony_ci	bool connected = atomic_read(&hidpp->connected);
419662306a36Sopenharmony_ci	struct input_dev *input;
419762306a36Sopenharmony_ci	char *name, *devm_name;
419862306a36Sopenharmony_ci
419962306a36Sopenharmony_ci	if (!connected) {
420062306a36Sopenharmony_ci		if (hidpp->battery.ps) {
420162306a36Sopenharmony_ci			hidpp->battery.online = false;
420262306a36Sopenharmony_ci			hidpp->battery.status = POWER_SUPPLY_STATUS_UNKNOWN;
420362306a36Sopenharmony_ci			hidpp->battery.level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
420462306a36Sopenharmony_ci			power_supply_changed(hidpp->battery.ps);
420562306a36Sopenharmony_ci		}
420662306a36Sopenharmony_ci		return;
420762306a36Sopenharmony_ci	}
420862306a36Sopenharmony_ci
420962306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
421062306a36Sopenharmony_ci		ret = wtp_connect(hdev, connected);
421162306a36Sopenharmony_ci		if (ret)
421262306a36Sopenharmony_ci			return;
421362306a36Sopenharmony_ci	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
421462306a36Sopenharmony_ci		ret = m560_send_config_command(hdev, connected);
421562306a36Sopenharmony_ci		if (ret)
421662306a36Sopenharmony_ci			return;
421762306a36Sopenharmony_ci	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
421862306a36Sopenharmony_ci		ret = k400_connect(hdev, connected);
421962306a36Sopenharmony_ci		if (ret)
422062306a36Sopenharmony_ci			return;
422162306a36Sopenharmony_ci	}
422262306a36Sopenharmony_ci
422362306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) {
422462306a36Sopenharmony_ci		ret = hidpp10_wheel_connect(hidpp);
422562306a36Sopenharmony_ci		if (ret)
422662306a36Sopenharmony_ci			return;
422762306a36Sopenharmony_ci	}
422862306a36Sopenharmony_ci
422962306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) {
423062306a36Sopenharmony_ci		ret = hidpp10_extra_mouse_buttons_connect(hidpp);
423162306a36Sopenharmony_ci		if (ret)
423262306a36Sopenharmony_ci			return;
423362306a36Sopenharmony_ci	}
423462306a36Sopenharmony_ci
423562306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
423662306a36Sopenharmony_ci		ret = hidpp10_consumer_keys_connect(hidpp);
423762306a36Sopenharmony_ci		if (ret)
423862306a36Sopenharmony_ci			return;
423962306a36Sopenharmony_ci	}
424062306a36Sopenharmony_ci
424162306a36Sopenharmony_ci	/* the device is already connected, we can ask for its name and
424262306a36Sopenharmony_ci	 * protocol */
424362306a36Sopenharmony_ci	if (!hidpp->protocol_major) {
424462306a36Sopenharmony_ci		ret = hidpp_root_get_protocol_version(hidpp);
424562306a36Sopenharmony_ci		if (ret) {
424662306a36Sopenharmony_ci			hid_err(hdev, "Can not get the protocol version.\n");
424762306a36Sopenharmony_ci			return;
424862306a36Sopenharmony_ci		}
424962306a36Sopenharmony_ci	}
425062306a36Sopenharmony_ci
425162306a36Sopenharmony_ci	if (hidpp->protocol_major >= 2) {
425262306a36Sopenharmony_ci		u8 feature_index;
425362306a36Sopenharmony_ci
425462306a36Sopenharmony_ci		if (!hidpp_get_wireless_feature_index(hidpp, &feature_index))
425562306a36Sopenharmony_ci			hidpp->wireless_feature_index = feature_index;
425662306a36Sopenharmony_ci	}
425762306a36Sopenharmony_ci
425862306a36Sopenharmony_ci	if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) {
425962306a36Sopenharmony_ci		name = hidpp_get_device_name(hidpp);
426062306a36Sopenharmony_ci		if (name) {
426162306a36Sopenharmony_ci			devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
426262306a36Sopenharmony_ci						   "%s", name);
426362306a36Sopenharmony_ci			kfree(name);
426462306a36Sopenharmony_ci			if (!devm_name)
426562306a36Sopenharmony_ci				return;
426662306a36Sopenharmony_ci
426762306a36Sopenharmony_ci			hidpp->name = devm_name;
426862306a36Sopenharmony_ci		}
426962306a36Sopenharmony_ci	}
427062306a36Sopenharmony_ci
427162306a36Sopenharmony_ci	hidpp_initialize_battery(hidpp);
427262306a36Sopenharmony_ci	if (!hid_is_usb(hidpp->hid_dev))
427362306a36Sopenharmony_ci		hidpp_initialize_hires_scroll(hidpp);
427462306a36Sopenharmony_ci
427562306a36Sopenharmony_ci	/* forward current battery state */
427662306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
427762306a36Sopenharmony_ci		hidpp10_enable_battery_reporting(hidpp);
427862306a36Sopenharmony_ci		if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE)
427962306a36Sopenharmony_ci			hidpp10_query_battery_mileage(hidpp);
428062306a36Sopenharmony_ci		else
428162306a36Sopenharmony_ci			hidpp10_query_battery_status(hidpp);
428262306a36Sopenharmony_ci	} else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
428362306a36Sopenharmony_ci		if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
428462306a36Sopenharmony_ci			hidpp20_query_battery_voltage_info(hidpp);
428562306a36Sopenharmony_ci		else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY)
428662306a36Sopenharmony_ci			hidpp20_query_battery_info_1004(hidpp);
428762306a36Sopenharmony_ci		else if (hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
428862306a36Sopenharmony_ci			hidpp20_query_adc_measurement_info_1f20(hidpp);
428962306a36Sopenharmony_ci		else
429062306a36Sopenharmony_ci			hidpp20_query_battery_info_1000(hidpp);
429162306a36Sopenharmony_ci	}
429262306a36Sopenharmony_ci	if (hidpp->battery.ps)
429362306a36Sopenharmony_ci		power_supply_changed(hidpp->battery.ps);
429462306a36Sopenharmony_ci
429562306a36Sopenharmony_ci	if (hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL)
429662306a36Sopenharmony_ci		hi_res_scroll_enable(hidpp);
429762306a36Sopenharmony_ci
429862306a36Sopenharmony_ci	if (!(hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) || hidpp->delayed_input)
429962306a36Sopenharmony_ci		/* if the input nodes are already created, we can stop now */
430062306a36Sopenharmony_ci		return;
430162306a36Sopenharmony_ci
430262306a36Sopenharmony_ci	input = hidpp_allocate_input(hdev);
430362306a36Sopenharmony_ci	if (!input) {
430462306a36Sopenharmony_ci		hid_err(hdev, "cannot allocate new input device: %d\n", ret);
430562306a36Sopenharmony_ci		return;
430662306a36Sopenharmony_ci	}
430762306a36Sopenharmony_ci
430862306a36Sopenharmony_ci	hidpp_populate_input(hidpp, input);
430962306a36Sopenharmony_ci
431062306a36Sopenharmony_ci	ret = input_register_device(input);
431162306a36Sopenharmony_ci	if (ret) {
431262306a36Sopenharmony_ci		input_free_device(input);
431362306a36Sopenharmony_ci		return;
431462306a36Sopenharmony_ci	}
431562306a36Sopenharmony_ci
431662306a36Sopenharmony_ci	hidpp->delayed_input = input;
431762306a36Sopenharmony_ci}
431862306a36Sopenharmony_ci
431962306a36Sopenharmony_cistatic DEVICE_ATTR(builtin_power_supply, 0000, NULL, NULL);
432062306a36Sopenharmony_ci
432162306a36Sopenharmony_cistatic struct attribute *sysfs_attrs[] = {
432262306a36Sopenharmony_ci	&dev_attr_builtin_power_supply.attr,
432362306a36Sopenharmony_ci	NULL
432462306a36Sopenharmony_ci};
432562306a36Sopenharmony_ci
432662306a36Sopenharmony_cistatic const struct attribute_group ps_attribute_group = {
432762306a36Sopenharmony_ci	.attrs = sysfs_attrs
432862306a36Sopenharmony_ci};
432962306a36Sopenharmony_ci
433062306a36Sopenharmony_cistatic int hidpp_get_report_length(struct hid_device *hdev, int id)
433162306a36Sopenharmony_ci{
433262306a36Sopenharmony_ci	struct hid_report_enum *re;
433362306a36Sopenharmony_ci	struct hid_report *report;
433462306a36Sopenharmony_ci
433562306a36Sopenharmony_ci	re = &(hdev->report_enum[HID_OUTPUT_REPORT]);
433662306a36Sopenharmony_ci	report = re->report_id_hash[id];
433762306a36Sopenharmony_ci	if (!report)
433862306a36Sopenharmony_ci		return 0;
433962306a36Sopenharmony_ci
434062306a36Sopenharmony_ci	return report->field[0]->report_count + 1;
434162306a36Sopenharmony_ci}
434262306a36Sopenharmony_ci
434362306a36Sopenharmony_cistatic u8 hidpp_validate_device(struct hid_device *hdev)
434462306a36Sopenharmony_ci{
434562306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
434662306a36Sopenharmony_ci	int id, report_length;
434762306a36Sopenharmony_ci	u8 supported_reports = 0;
434862306a36Sopenharmony_ci
434962306a36Sopenharmony_ci	id = REPORT_ID_HIDPP_SHORT;
435062306a36Sopenharmony_ci	report_length = hidpp_get_report_length(hdev, id);
435162306a36Sopenharmony_ci	if (report_length) {
435262306a36Sopenharmony_ci		if (report_length < HIDPP_REPORT_SHORT_LENGTH)
435362306a36Sopenharmony_ci			goto bad_device;
435462306a36Sopenharmony_ci
435562306a36Sopenharmony_ci		supported_reports |= HIDPP_REPORT_SHORT_SUPPORTED;
435662306a36Sopenharmony_ci	}
435762306a36Sopenharmony_ci
435862306a36Sopenharmony_ci	id = REPORT_ID_HIDPP_LONG;
435962306a36Sopenharmony_ci	report_length = hidpp_get_report_length(hdev, id);
436062306a36Sopenharmony_ci	if (report_length) {
436162306a36Sopenharmony_ci		if (report_length < HIDPP_REPORT_LONG_LENGTH)
436262306a36Sopenharmony_ci			goto bad_device;
436362306a36Sopenharmony_ci
436462306a36Sopenharmony_ci		supported_reports |= HIDPP_REPORT_LONG_SUPPORTED;
436562306a36Sopenharmony_ci	}
436662306a36Sopenharmony_ci
436762306a36Sopenharmony_ci	id = REPORT_ID_HIDPP_VERY_LONG;
436862306a36Sopenharmony_ci	report_length = hidpp_get_report_length(hdev, id);
436962306a36Sopenharmony_ci	if (report_length) {
437062306a36Sopenharmony_ci		if (report_length < HIDPP_REPORT_LONG_LENGTH ||
437162306a36Sopenharmony_ci		    report_length > HIDPP_REPORT_VERY_LONG_MAX_LENGTH)
437262306a36Sopenharmony_ci			goto bad_device;
437362306a36Sopenharmony_ci
437462306a36Sopenharmony_ci		supported_reports |= HIDPP_REPORT_VERY_LONG_SUPPORTED;
437562306a36Sopenharmony_ci		hidpp->very_long_report_length = report_length;
437662306a36Sopenharmony_ci	}
437762306a36Sopenharmony_ci
437862306a36Sopenharmony_ci	return supported_reports;
437962306a36Sopenharmony_ci
438062306a36Sopenharmony_cibad_device:
438162306a36Sopenharmony_ci	hid_warn(hdev, "not enough values in hidpp report %d\n", id);
438262306a36Sopenharmony_ci	return false;
438362306a36Sopenharmony_ci}
438462306a36Sopenharmony_ci
438562306a36Sopenharmony_cistatic bool hidpp_application_equals(struct hid_device *hdev,
438662306a36Sopenharmony_ci				     unsigned int application)
438762306a36Sopenharmony_ci{
438862306a36Sopenharmony_ci	struct list_head *report_list;
438962306a36Sopenharmony_ci	struct hid_report *report;
439062306a36Sopenharmony_ci
439162306a36Sopenharmony_ci	report_list = &hdev->report_enum[HID_INPUT_REPORT].report_list;
439262306a36Sopenharmony_ci	report = list_first_entry_or_null(report_list, struct hid_report, list);
439362306a36Sopenharmony_ci	return report && report->application == application;
439462306a36Sopenharmony_ci}
439562306a36Sopenharmony_ci
439662306a36Sopenharmony_cistatic int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
439762306a36Sopenharmony_ci{
439862306a36Sopenharmony_ci	struct hidpp_device *hidpp;
439962306a36Sopenharmony_ci	int ret;
440062306a36Sopenharmony_ci	bool connected;
440162306a36Sopenharmony_ci	unsigned int connect_mask = HID_CONNECT_DEFAULT;
440262306a36Sopenharmony_ci	struct hidpp_ff_private_data data;
440362306a36Sopenharmony_ci
440462306a36Sopenharmony_ci	/* report_fixup needs drvdata to be set before we call hid_parse */
440562306a36Sopenharmony_ci	hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL);
440662306a36Sopenharmony_ci	if (!hidpp)
440762306a36Sopenharmony_ci		return -ENOMEM;
440862306a36Sopenharmony_ci
440962306a36Sopenharmony_ci	hidpp->hid_dev = hdev;
441062306a36Sopenharmony_ci	hidpp->name = hdev->name;
441162306a36Sopenharmony_ci	hidpp->quirks = id->driver_data;
441262306a36Sopenharmony_ci	hid_set_drvdata(hdev, hidpp);
441362306a36Sopenharmony_ci
441462306a36Sopenharmony_ci	ret = hid_parse(hdev);
441562306a36Sopenharmony_ci	if (ret) {
441662306a36Sopenharmony_ci		hid_err(hdev, "%s:parse failed\n", __func__);
441762306a36Sopenharmony_ci		return ret;
441862306a36Sopenharmony_ci	}
441962306a36Sopenharmony_ci
442062306a36Sopenharmony_ci	/*
442162306a36Sopenharmony_ci	 * Make sure the device is HID++ capable, otherwise treat as generic HID
442262306a36Sopenharmony_ci	 */
442362306a36Sopenharmony_ci	hidpp->supported_reports = hidpp_validate_device(hdev);
442462306a36Sopenharmony_ci
442562306a36Sopenharmony_ci	if (!hidpp->supported_reports) {
442662306a36Sopenharmony_ci		hid_set_drvdata(hdev, NULL);
442762306a36Sopenharmony_ci		devm_kfree(&hdev->dev, hidpp);
442862306a36Sopenharmony_ci		return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
442962306a36Sopenharmony_ci	}
443062306a36Sopenharmony_ci
443162306a36Sopenharmony_ci	if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
443262306a36Sopenharmony_ci		hidpp->quirks |= HIDPP_QUIRK_UNIFYING;
443362306a36Sopenharmony_ci
443462306a36Sopenharmony_ci	if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE &&
443562306a36Sopenharmony_ci	    hidpp_application_equals(hdev, HID_GD_MOUSE))
443662306a36Sopenharmony_ci		hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS |
443762306a36Sopenharmony_ci				 HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS;
443862306a36Sopenharmony_ci
443962306a36Sopenharmony_ci	if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE &&
444062306a36Sopenharmony_ci	    hidpp_application_equals(hdev, HID_GD_KEYBOARD))
444162306a36Sopenharmony_ci		hidpp->quirks |= HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS;
444262306a36Sopenharmony_ci
444362306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
444462306a36Sopenharmony_ci		ret = wtp_allocate(hdev, id);
444562306a36Sopenharmony_ci		if (ret)
444662306a36Sopenharmony_ci			return ret;
444762306a36Sopenharmony_ci	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
444862306a36Sopenharmony_ci		ret = k400_allocate(hdev);
444962306a36Sopenharmony_ci		if (ret)
445062306a36Sopenharmony_ci			return ret;
445162306a36Sopenharmony_ci	}
445262306a36Sopenharmony_ci
445362306a36Sopenharmony_ci	INIT_WORK(&hidpp->work, delayed_work_cb);
445462306a36Sopenharmony_ci	mutex_init(&hidpp->send_mutex);
445562306a36Sopenharmony_ci	init_waitqueue_head(&hidpp->wait);
445662306a36Sopenharmony_ci
445762306a36Sopenharmony_ci	/* indicates we are handling the battery properties in the kernel */
445862306a36Sopenharmony_ci	ret = sysfs_create_group(&hdev->dev.kobj, &ps_attribute_group);
445962306a36Sopenharmony_ci	if (ret)
446062306a36Sopenharmony_ci		hid_warn(hdev, "Cannot allocate sysfs group for %s\n",
446162306a36Sopenharmony_ci			 hdev->name);
446262306a36Sopenharmony_ci
446362306a36Sopenharmony_ci	/*
446462306a36Sopenharmony_ci	 * First call hid_hw_start(hdev, 0) to allow IO without connecting any
446562306a36Sopenharmony_ci	 * hid subdrivers (hid-input, hidraw). This allows retrieving the dev's
446662306a36Sopenharmony_ci	 * name and serial number and store these in hdev->name and hdev->uniq,
446762306a36Sopenharmony_ci	 * before the hid-input and hidraw drivers expose these to userspace.
446862306a36Sopenharmony_ci	 */
446962306a36Sopenharmony_ci	ret = hid_hw_start(hdev, 0);
447062306a36Sopenharmony_ci	if (ret) {
447162306a36Sopenharmony_ci		hid_err(hdev, "hw start failed\n");
447262306a36Sopenharmony_ci		goto hid_hw_start_fail;
447362306a36Sopenharmony_ci	}
447462306a36Sopenharmony_ci
447562306a36Sopenharmony_ci	ret = hid_hw_open(hdev);
447662306a36Sopenharmony_ci	if (ret < 0) {
447762306a36Sopenharmony_ci		dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n",
447862306a36Sopenharmony_ci			__func__, ret);
447962306a36Sopenharmony_ci		goto hid_hw_open_fail;
448062306a36Sopenharmony_ci	}
448162306a36Sopenharmony_ci
448262306a36Sopenharmony_ci	/* Allow incoming packets */
448362306a36Sopenharmony_ci	hid_device_io_start(hdev);
448462306a36Sopenharmony_ci
448562306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_UNIFYING)
448662306a36Sopenharmony_ci		hidpp_unifying_init(hidpp);
448762306a36Sopenharmony_ci	else if (hid_is_usb(hidpp->hid_dev))
448862306a36Sopenharmony_ci		hidpp_serial_init(hidpp);
448962306a36Sopenharmony_ci
449062306a36Sopenharmony_ci	connected = hidpp_root_get_protocol_version(hidpp) == 0;
449162306a36Sopenharmony_ci	atomic_set(&hidpp->connected, connected);
449262306a36Sopenharmony_ci	if (!(hidpp->quirks & HIDPP_QUIRK_UNIFYING)) {
449362306a36Sopenharmony_ci		if (!connected) {
449462306a36Sopenharmony_ci			ret = -ENODEV;
449562306a36Sopenharmony_ci			hid_err(hdev, "Device not connected");
449662306a36Sopenharmony_ci			goto hid_hw_init_fail;
449762306a36Sopenharmony_ci		}
449862306a36Sopenharmony_ci
449962306a36Sopenharmony_ci		hidpp_overwrite_name(hdev);
450062306a36Sopenharmony_ci	}
450162306a36Sopenharmony_ci
450262306a36Sopenharmony_ci	if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
450362306a36Sopenharmony_ci		ret = wtp_get_config(hidpp);
450462306a36Sopenharmony_ci		if (ret)
450562306a36Sopenharmony_ci			goto hid_hw_init_fail;
450662306a36Sopenharmony_ci	} else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
450762306a36Sopenharmony_ci		ret = g920_get_config(hidpp, &data);
450862306a36Sopenharmony_ci		if (ret)
450962306a36Sopenharmony_ci			goto hid_hw_init_fail;
451062306a36Sopenharmony_ci	}
451162306a36Sopenharmony_ci
451262306a36Sopenharmony_ci	schedule_work(&hidpp->work);
451362306a36Sopenharmony_ci	flush_work(&hidpp->work);
451462306a36Sopenharmony_ci
451562306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
451662306a36Sopenharmony_ci		connect_mask &= ~HID_CONNECT_HIDINPUT;
451762306a36Sopenharmony_ci
451862306a36Sopenharmony_ci	/* Now export the actual inputs and hidraw nodes to the world */
451962306a36Sopenharmony_ci	ret = hid_connect(hdev, connect_mask);
452062306a36Sopenharmony_ci	if (ret) {
452162306a36Sopenharmony_ci		hid_err(hdev, "%s:hid_connect returned error %d\n", __func__, ret);
452262306a36Sopenharmony_ci		goto hid_hw_init_fail;
452362306a36Sopenharmony_ci	}
452462306a36Sopenharmony_ci
452562306a36Sopenharmony_ci	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
452662306a36Sopenharmony_ci		ret = hidpp_ff_init(hidpp, &data);
452762306a36Sopenharmony_ci		if (ret)
452862306a36Sopenharmony_ci			hid_warn(hidpp->hid_dev,
452962306a36Sopenharmony_ci		     "Unable to initialize force feedback support, errno %d\n",
453062306a36Sopenharmony_ci				 ret);
453162306a36Sopenharmony_ci	}
453262306a36Sopenharmony_ci
453362306a36Sopenharmony_ci	/*
453462306a36Sopenharmony_ci	 * This relies on logi_dj_ll_close() being a no-op so that DJ connection
453562306a36Sopenharmony_ci	 * events will still be received.
453662306a36Sopenharmony_ci	 */
453762306a36Sopenharmony_ci	hid_hw_close(hdev);
453862306a36Sopenharmony_ci	return ret;
453962306a36Sopenharmony_ci
454062306a36Sopenharmony_cihid_hw_init_fail:
454162306a36Sopenharmony_ci	hid_hw_close(hdev);
454262306a36Sopenharmony_cihid_hw_open_fail:
454362306a36Sopenharmony_ci	hid_hw_stop(hdev);
454462306a36Sopenharmony_cihid_hw_start_fail:
454562306a36Sopenharmony_ci	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
454662306a36Sopenharmony_ci	cancel_work_sync(&hidpp->work);
454762306a36Sopenharmony_ci	mutex_destroy(&hidpp->send_mutex);
454862306a36Sopenharmony_ci	return ret;
454962306a36Sopenharmony_ci}
455062306a36Sopenharmony_ci
455162306a36Sopenharmony_cistatic void hidpp_remove(struct hid_device *hdev)
455262306a36Sopenharmony_ci{
455362306a36Sopenharmony_ci	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
455462306a36Sopenharmony_ci
455562306a36Sopenharmony_ci	if (!hidpp)
455662306a36Sopenharmony_ci		return hid_hw_stop(hdev);
455762306a36Sopenharmony_ci
455862306a36Sopenharmony_ci	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
455962306a36Sopenharmony_ci
456062306a36Sopenharmony_ci	hid_hw_stop(hdev);
456162306a36Sopenharmony_ci	cancel_work_sync(&hidpp->work);
456262306a36Sopenharmony_ci	mutex_destroy(&hidpp->send_mutex);
456362306a36Sopenharmony_ci}
456462306a36Sopenharmony_ci
456562306a36Sopenharmony_ci#define LDJ_DEVICE(product) \
456662306a36Sopenharmony_ci	HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, \
456762306a36Sopenharmony_ci		   USB_VENDOR_ID_LOGITECH, (product))
456862306a36Sopenharmony_ci
456962306a36Sopenharmony_ci#define L27MHZ_DEVICE(product) \
457062306a36Sopenharmony_ci	HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_27MHZ_DEVICE, \
457162306a36Sopenharmony_ci		   USB_VENDOR_ID_LOGITECH, (product))
457262306a36Sopenharmony_ci
457362306a36Sopenharmony_cistatic const struct hid_device_id hidpp_devices[] = {
457462306a36Sopenharmony_ci	{ /* wireless touchpad */
457562306a36Sopenharmony_ci	  LDJ_DEVICE(0x4011),
457662306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT |
457762306a36Sopenharmony_ci			 HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS },
457862306a36Sopenharmony_ci	{ /* wireless touchpad T650 */
457962306a36Sopenharmony_ci	  LDJ_DEVICE(0x4101),
458062306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT },
458162306a36Sopenharmony_ci	{ /* wireless touchpad T651 */
458262306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
458362306a36Sopenharmony_ci		USB_DEVICE_ID_LOGITECH_T651),
458462306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT },
458562306a36Sopenharmony_ci	{ /* Mouse Logitech Anywhere MX */
458662306a36Sopenharmony_ci	  LDJ_DEVICE(0x1017), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
458762306a36Sopenharmony_ci	{ /* Mouse logitech M560 */
458862306a36Sopenharmony_ci	  LDJ_DEVICE(0x402d),
458962306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
459062306a36Sopenharmony_ci	{ /* Mouse Logitech M705 (firmware RQM17) */
459162306a36Sopenharmony_ci	  LDJ_DEVICE(0x101b), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
459262306a36Sopenharmony_ci	{ /* Mouse Logitech Performance MX */
459362306a36Sopenharmony_ci	  LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
459462306a36Sopenharmony_ci	{ /* Keyboard logitech K400 */
459562306a36Sopenharmony_ci	  LDJ_DEVICE(0x4024),
459662306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_CLASS_K400 },
459762306a36Sopenharmony_ci	{ /* Solar Keyboard Logitech K750 */
459862306a36Sopenharmony_ci	  LDJ_DEVICE(0x4002),
459962306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_CLASS_K750 },
460062306a36Sopenharmony_ci	{ /* Keyboard MX5000 (Bluetooth-receiver in HID proxy mode) */
460162306a36Sopenharmony_ci	  LDJ_DEVICE(0xb305),
460262306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
460362306a36Sopenharmony_ci	{ /* Dinovo Edge (Bluetooth-receiver in HID proxy mode) */
460462306a36Sopenharmony_ci	  LDJ_DEVICE(0xb309),
460562306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
460662306a36Sopenharmony_ci	{ /* Keyboard MX5500 (Bluetooth-receiver in HID proxy mode) */
460762306a36Sopenharmony_ci	  LDJ_DEVICE(0xb30b),
460862306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
460962306a36Sopenharmony_ci
461062306a36Sopenharmony_ci	{ LDJ_DEVICE(HID_ANY_ID) },
461162306a36Sopenharmony_ci
461262306a36Sopenharmony_ci	{ /* Keyboard LX501 (Y-RR53) */
461362306a36Sopenharmony_ci	  L27MHZ_DEVICE(0x0049),
461462306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_KBD_ZOOM_WHEEL },
461562306a36Sopenharmony_ci	{ /* Keyboard MX3000 (Y-RAM74) */
461662306a36Sopenharmony_ci	  L27MHZ_DEVICE(0x0057),
461762306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_KBD_SCROLL_WHEEL },
461862306a36Sopenharmony_ci	{ /* Keyboard MX3200 (Y-RAV80) */
461962306a36Sopenharmony_ci	  L27MHZ_DEVICE(0x005c),
462062306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_KBD_ZOOM_WHEEL },
462162306a36Sopenharmony_ci	{ /* S510 Media Remote */
462262306a36Sopenharmony_ci	  L27MHZ_DEVICE(0x00fe),
462362306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_KBD_SCROLL_WHEEL },
462462306a36Sopenharmony_ci
462562306a36Sopenharmony_ci	{ L27MHZ_DEVICE(HID_ANY_ID) },
462662306a36Sopenharmony_ci
462762306a36Sopenharmony_ci	{ /* Logitech G403 Wireless Gaming Mouse over USB */
462862306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) },
462962306a36Sopenharmony_ci	{ /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */
463062306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) },
463162306a36Sopenharmony_ci	{ /* Logitech G703 Gaming Mouse over USB */
463262306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) },
463362306a36Sopenharmony_ci	{ /* Logitech G703 Hero Gaming Mouse over USB */
463462306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) },
463562306a36Sopenharmony_ci	{ /* Logitech G900 Gaming Mouse over USB */
463662306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC081) },
463762306a36Sopenharmony_ci	{ /* Logitech G903 Gaming Mouse over USB */
463862306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC086) },
463962306a36Sopenharmony_ci	{ /* Logitech G903 Hero Gaming Mouse over USB */
464062306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC091) },
464162306a36Sopenharmony_ci	{ /* Logitech G915 TKL Keyboard over USB */
464262306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC343) },
464362306a36Sopenharmony_ci	{ /* Logitech G920 Wheel over USB */
464462306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL),
464562306a36Sopenharmony_ci		.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS},
464662306a36Sopenharmony_ci	{ /* Logitech G923 Wheel (Xbox version) over USB */
464762306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL),
464862306a36Sopenharmony_ci		.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS },
464962306a36Sopenharmony_ci	{ /* Logitech G Pro Gaming Mouse over USB */
465062306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
465162306a36Sopenharmony_ci	{ /* Logitech G Pro X Superlight Gaming Mouse over USB */
465262306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC094) },
465362306a36Sopenharmony_ci	{ /* Logitech G Pro X Superlight 2 Gaming Mouse over USB */
465462306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC09b) },
465562306a36Sopenharmony_ci
465662306a36Sopenharmony_ci	{ /* G935 Gaming Headset */
465762306a36Sopenharmony_ci	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87),
465862306a36Sopenharmony_ci		.driver_data = HIDPP_QUIRK_WIRELESS_STATUS },
465962306a36Sopenharmony_ci
466062306a36Sopenharmony_ci	{ /* MX5000 keyboard over Bluetooth */
466162306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305),
466262306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
466362306a36Sopenharmony_ci	{ /* Dinovo Edge keyboard over Bluetooth */
466462306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb309),
466562306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
466662306a36Sopenharmony_ci	{ /* MX5500 keyboard over Bluetooth */
466762306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb30b),
466862306a36Sopenharmony_ci	  .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
466962306a36Sopenharmony_ci	{ /* Logitech G915 TKL keyboard over Bluetooth */
467062306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb35f) },
467162306a36Sopenharmony_ci	{ /* M-RCQ142 V470 Cordless Laser Mouse over Bluetooth */
467262306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb008) },
467362306a36Sopenharmony_ci	{ /* MX Master mouse over Bluetooth */
467462306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb012) },
467562306a36Sopenharmony_ci	{ /* M720 Triathlon mouse over Bluetooth */
467662306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb015) },
467762306a36Sopenharmony_ci	{ /* MX Ergo trackball over Bluetooth */
467862306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) },
467962306a36Sopenharmony_ci	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e) },
468062306a36Sopenharmony_ci	{ /* Signature M650 over Bluetooth */
468162306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
468262306a36Sopenharmony_ci	{ /* MX Master 3 mouse over Bluetooth */
468362306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
468462306a36Sopenharmony_ci	{ /* MX Anywhere 3 mouse over Bluetooth */
468562306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) },
468662306a36Sopenharmony_ci	{ /* MX Master 3S mouse over Bluetooth */
468762306a36Sopenharmony_ci	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) },
468862306a36Sopenharmony_ci	{}
468962306a36Sopenharmony_ci};
469062306a36Sopenharmony_ci
469162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, hidpp_devices);
469262306a36Sopenharmony_ci
469362306a36Sopenharmony_cistatic const struct hid_usage_id hidpp_usages[] = {
469462306a36Sopenharmony_ci	{ HID_GD_WHEEL, EV_REL, REL_WHEEL_HI_RES },
469562306a36Sopenharmony_ci	{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
469662306a36Sopenharmony_ci};
469762306a36Sopenharmony_ci
469862306a36Sopenharmony_cistatic struct hid_driver hidpp_driver = {
469962306a36Sopenharmony_ci	.name = "logitech-hidpp-device",
470062306a36Sopenharmony_ci	.id_table = hidpp_devices,
470162306a36Sopenharmony_ci	.report_fixup = hidpp_report_fixup,
470262306a36Sopenharmony_ci	.probe = hidpp_probe,
470362306a36Sopenharmony_ci	.remove = hidpp_remove,
470462306a36Sopenharmony_ci	.raw_event = hidpp_raw_event,
470562306a36Sopenharmony_ci	.usage_table = hidpp_usages,
470662306a36Sopenharmony_ci	.event = hidpp_event,
470762306a36Sopenharmony_ci	.input_configured = hidpp_input_configured,
470862306a36Sopenharmony_ci	.input_mapping = hidpp_input_mapping,
470962306a36Sopenharmony_ci	.input_mapped = hidpp_input_mapped,
471062306a36Sopenharmony_ci};
471162306a36Sopenharmony_ci
471262306a36Sopenharmony_cimodule_hid_driver(hidpp_driver);
4713