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(¶ms[0]); 230562306a36Sopenharmony_ci raw_info->y_size = get_unaligned_be16(¶ms[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(¶ms[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, ¶ms, 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