Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/hid/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 132 kB image not shown  

Quelle  hid-logitech-hidpp.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  HIDPP protocol for Logitech receivers
 *
 *  Copyright (c) 2011 Logitech (c)
 *  Copyright (c) 2012-2013 Google (c)
 *  Copyright (c) 2013-2014 Red Hat Inc.
 */



#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/device.h>
#include <linux/input.h>
#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/sched/clock.h>
#include <linux/kfifo.h>
#include <linux/input/mt.h>
#include <linux/workqueue.h>
#include <linux/atomic.h>
#include <linux/fixp-arith.h>
#include <linux/unaligned.h>
#include "usbhid/usbhid.h"
#include "hid-ids.h"

MODULE_DESCRIPTION("Support for Logitech devices relying on the HID++ specification");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Tissoires ");
MODULE_AUTHOR("Nestor Lopez Casado ");
MODULE_AUTHOR("Bastien Nocera ");

static bool disable_tap_to_click;
module_param(disable_tap_to_click, bool, 0644);
MODULE_PARM_DESC(disable_tap_to_click,
 "Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently).");

/* Define a non-zero software ID to identify our own requests */
#define LINUX_KERNEL_SW_ID   0x01

#define REPORT_ID_HIDPP_SHORT   0x10
#define REPORT_ID_HIDPP_LONG   0x11
#define REPORT_ID_HIDPP_VERY_LONG  0x12

#define HIDPP_REPORT_SHORT_LENGTH  7
#define HIDPP_REPORT_LONG_LENGTH  20
#define HIDPP_REPORT_VERY_LONG_MAX_LENGTH 64

#define HIDPP_REPORT_SHORT_SUPPORTED  BIT(0)
#define HIDPP_REPORT_LONG_SUPPORTED  BIT(1)
#define HIDPP_REPORT_VERY_LONG_SUPPORTED BIT(2)

#define HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS 0x03
#define HIDPP_SUB_ID_ROLLER   0x05
#define HIDPP_SUB_ID_MOUSE_EXTRA_BTNS  0x06
#define HIDPP_SUB_ID_USER_IFACE_EVENT  0x08
#define HIDPP_USER_IFACE_EVENT_ENCRYPTION_KEY_LOST BIT(5)

#define HIDPP_QUIRK_CLASS_WTP   BIT(0)
#define HIDPP_QUIRK_CLASS_M560   BIT(1)
#define HIDPP_QUIRK_CLASS_K400   BIT(2)
#define HIDPP_QUIRK_CLASS_G920   BIT(3)
#define HIDPP_QUIRK_CLASS_K750   BIT(4)

/* bits 2..20 are reserved for classes */
/* #define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) disabled */
#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22)
#define HIDPP_QUIRK_DELAYED_INIT  BIT(23)
#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24)
#define HIDPP_QUIRK_HIDPP_WHEELS  BIT(25)
#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(26)
#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(27)
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0  BIT(28)
#define HIDPP_QUIRK_WIRELESS_STATUS  BIT(29)
#define HIDPP_QUIRK_RESET_HI_RES_SCROLL  BIT(30)

/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
#define HIDPP_QUIRK_KBD_ZOOM_WHEEL   HIDPP_QUIRK_HIDPP_WHEELS

/* Convenience constant to check for any high-res support. */
#define HIDPP_CAPABILITY_HI_RES_SCROLL (HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL | \
      HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL | \
      HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL)

#define HIDPP_CAPABILITY_HIDPP10_BATTERY BIT(0)
#define HIDPP_CAPABILITY_HIDPP20_BATTERY BIT(1)
#define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2)
#define HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS BIT(3)
#define HIDPP_CAPABILITY_BATTERY_VOLTAGE BIT(4)
#define HIDPP_CAPABILITY_BATTERY_PERCENTAGE BIT(5)
#define HIDPP_CAPABILITY_UNIFIED_BATTERY BIT(6)
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL BIT(7)
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL BIT(8)
#define HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL BIT(9)
#define HIDPP_CAPABILITY_ADC_MEASUREMENT BIT(10)

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

/*
 * There are two hidpp protocols in use, the first version hidpp10 is known
 * as register access protocol or RAP, the second version hidpp20 is known as
 * feature access protocol or FAP
 *
 * Most older devices (including the Unifying usb receiver) use the RAP protocol
 * where as most newer devices use the FAP protocol. Both protocols are
 * compatible with the underlying transport, which could be usb, Unifiying, or
 * bluetooth. The message lengths are defined by the hid vendor specific report
 * descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and
 * the HIDPP_LONG report type (total message length 20 bytes)
 *
 * The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG
 * messages. The Unifying receiver itself responds to RAP messages (device index
 * is 0xFF for the receiver), and all messages (short or long) with a device
 * index between 1 and 6 are passed untouched to the corresponding paired
 * Unifying device.
 *
 * The paired device can be RAP or FAP, it will receive the message untouched
 * from the Unifiying receiver.
 */


struct fap {
 u8 feature_index;
 u8 funcindex_clientid;
 u8 params[HIDPP_REPORT_VERY_LONG_MAX_LENGTH - 4U];
};

struct rap {
 u8 sub_id;
 u8 reg_address;
 u8 params[HIDPP_REPORT_VERY_LONG_MAX_LENGTH - 4U];
};

struct hidpp_report {
 u8 report_id;
 u8 device_index;
 union {
  struct fap fap;
  struct rap rap;
  u8 rawbytes[sizeof(struct fap)];
 };
} __packed;

struct hidpp_battery {
 u8 feature_index;
 u8 solar_feature_index;
 u8 voltage_feature_index;
 u8 adc_measurement_feature_index;
 struct power_supply_desc desc;
 struct power_supply *ps;
 char name[64];
 int status;
 int capacity;
 int level;
 int voltage;
 int charge_type;
 bool online;
 u8 supported_levels_1004;
};

/**
 * struct hidpp_scroll_counter - Utility class for processing high-resolution
 *                             scroll events.
 * @dev: the input device for which events should be reported.
 * @wheel_multiplier: the scalar multiplier to be applied to each wheel event
 * @remainder: counts the number of high-resolution units moved since the last
 *             low-resolution event (REL_WHEEL or REL_HWHEEL) was sent. Should
 *             only be used by class methods.
 * @direction: direction of last movement (1 or -1)
 * @last_time: last event time, used to reset remainder after inactivity
 */

struct hidpp_scroll_counter {
 int wheel_multiplier;
 int remainder;
 int direction;
 unsigned long long last_time;
};

struct hidpp_device {
 struct hid_device *hid_dev;
 struct input_dev *input;
 struct mutex send_mutex;
 void *send_receive_buf;
 char *name;  /* will never be NULL and should not be freed */
 wait_queue_head_t wait;
 int very_long_report_length;
 bool answer_available;
 u8 protocol_major;
 u8 protocol_minor;

 void *private_data;

 struct work_struct work;
 struct work_struct reset_hi_res_work;
 struct kfifo delayed_work_fifo;
 struct input_dev *delayed_input;

 unsigned long quirks;
 unsigned long capabilities;
 u8 supported_reports;

 struct hidpp_battery battery;
 struct hidpp_scroll_counter vertical_wheel_counter;

 u8 wireless_feature_index;

 bool connected_once;
};

/* HID++ 1.0 error codes */
#define HIDPP_ERROR    0x8f
#define HIDPP_ERROR_SUCCESS   0x00
#define HIDPP_ERROR_INVALID_SUBID  0x01
#define HIDPP_ERROR_INVALID_ADRESS  0x02
#define HIDPP_ERROR_INVALID_VALUE  0x03
#define HIDPP_ERROR_CONNECT_FAIL  0x04
#define HIDPP_ERROR_TOO_MANY_DEVICES  0x05
#define HIDPP_ERROR_ALREADY_EXISTS  0x06
#define HIDPP_ERROR_BUSY   0x07
#define HIDPP_ERROR_UNKNOWN_DEVICE  0x08
#define HIDPP_ERROR_RESOURCE_ERROR  0x09
#define HIDPP_ERROR_REQUEST_UNAVAILABLE  0x0a
#define HIDPP_ERROR_INVALID_PARAM_VALUE  0x0b
#define HIDPP_ERROR_WRONG_PIN_CODE  0x0c
/* HID++ 2.0 error codes */
#define HIDPP20_ERROR_NO_ERROR   0x00
#define HIDPP20_ERROR_UNKNOWN   0x01
#define HIDPP20_ERROR_INVALID_ARGS  0x02
#define HIDPP20_ERROR_OUT_OF_RANGE  0x03
#define HIDPP20_ERROR_HW_ERROR   0x04
#define HIDPP20_ERROR_NOT_ALLOWED  0x05
#define HIDPP20_ERROR_INVALID_FEATURE_INDEX 0x06
#define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07
#define HIDPP20_ERROR_BUSY   0x08
#define HIDPP20_ERROR_UNSUPPORTED  0x09
#define HIDPP20_ERROR    0xff

static int __hidpp_send_report(struct hid_device *hdev,
    struct hidpp_report *hidpp_report)
{
 struct hidpp_device *hidpp = hid_get_drvdata(hdev);
 int fields_count, ret;

 switch (hidpp_report->report_id) {
 case REPORT_ID_HIDPP_SHORT:
  fields_count = HIDPP_REPORT_SHORT_LENGTH;
  break;
 case REPORT_ID_HIDPP_LONG:
  fields_count = HIDPP_REPORT_LONG_LENGTH;
  break;
 case REPORT_ID_HIDPP_VERY_LONG:
  fields_count = hidpp->very_long_report_length;
  break;
 default:
  return -ENODEV;
 }

 /*
 * set the device_index as the receiver, it will be overwritten by
 * hid_hw_request if needed
 */

 hidpp_report->device_index = 0xff;

 if (hidpp->quirks & HIDPP_QUIRK_FORCE_OUTPUT_REPORTS) {
  ret = hid_hw_output_report(hdev, (u8 *)hidpp_report, fields_count);
 } else {
  ret = hid_hw_raw_request(hdev, hidpp_report->report_id,
   (u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT,
   HID_REQ_SET_REPORT);
 }

 return ret == fields_count ? 0 : -1;
}

/*
 * Effectively send the message to the device, waiting for its answer.
 *
 * Must be called with hidpp->send_mutex locked
 *
 * Same return protocol than hidpp_send_message_sync():
 * - success on 0
 * - negative error means transport error
 * - positive value means protocol error
 */

static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp,
 struct hidpp_report *message,
 struct hidpp_report *response)
{
 int ret;

 __must_hold(&hidpp->send_mutex);

 hidpp->send_receive_buf = response;
 hidpp->answer_available = false;

 /*
 * So that we can later validate the answer when it arrives
 * in hidpp_raw_event
 */

 *response = *message;

 ret = __hidpp_send_report(hidpp->hid_dev, message);
 if (ret) {
  dbg_hid("__hidpp_send_report returned err: %d\n", ret);
  memset(response, 0, sizeof(struct hidpp_report));
  return ret;
 }

 if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
    5*HZ)) {
  dbg_hid("%s:timeout waiting for response\n", __func__);
  memset(response, 0, sizeof(struct hidpp_report));
  return -ETIMEDOUT;
 }

 if (response->report_id == REPORT_ID_HIDPP_SHORT &&
     response->rap.sub_id == HIDPP_ERROR) {
  ret = response->rap.params[1];
  dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
  return ret;
 }

 if ((response->report_id == REPORT_ID_HIDPP_LONG ||
      response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
     response->fap.feature_index == HIDPP20_ERROR) {
  ret = response->fap.params[1];
  dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
  return ret;
 }

 return 0;
}

/*
 * hidpp_send_message_sync() returns 0 in case of success, and something else
 * in case of a failure.
 *
 * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
 * value.
 */

static int hidpp_send_message_sync(struct hidpp_device *hidpp,
 struct hidpp_report *message,
 struct hidpp_report *response)
{
 int ret;
 int max_retries = 3;

 mutex_lock(&hidpp->send_mutex);

 do {
  ret = __do_hidpp_send_message_sync(hidpp, message, response);
  if (ret != HIDPP20_ERROR_BUSY)
   break;

  dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
 } while (--max_retries);

 mutex_unlock(&hidpp->send_mutex);
 return ret;

}

/*
 * hidpp_send_fap_command_sync() returns 0 in case of success, and something else
 * in case of a failure.
 *
 * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
 * value.
 */

static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
 u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
 struct hidpp_report *response)
{
 struct hidpp_report *message;
 int ret;

 if (param_count > sizeof(message->fap.params)) {
  hid_dbg(hidpp->hid_dev,
   "Invalid number of parameters passed to command (%d != %llu)\n",
   param_count,
   (unsigned long longsizeof(message->fap.params));
  return -EINVAL;
 }

 message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
 if (!message)
  return -ENOMEM;

 if (param_count > (HIDPP_REPORT_LONG_LENGTH - 4))
  message->report_id = REPORT_ID_HIDPP_VERY_LONG;
 else
  message->report_id = REPORT_ID_HIDPP_LONG;
 message->fap.feature_index = feat_index;
 message->fap.funcindex_clientid = funcindex_clientid | LINUX_KERNEL_SW_ID;
 memcpy(&message->fap.params, params, param_count);

 ret = hidpp_send_message_sync(hidpp, message, response);
 kfree(message);
 return ret;
}

/*
 * hidpp_send_rap_command_sync() returns 0 in case of success, and something else
 * in case of a failure.
 *
 * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
 * value.
 */

static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
 u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
 struct hidpp_report *response)
{
 struct hidpp_report *message;
 int ret, max_count;

 /* Send as long report if short reports are not supported. */
 if (report_id == REPORT_ID_HIDPP_SHORT &&
     !(hidpp_dev->supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
  report_id = REPORT_ID_HIDPP_LONG;

 switch (report_id) {
 case REPORT_ID_HIDPP_SHORT:
  max_count = HIDPP_REPORT_SHORT_LENGTH - 4;
  break;
 case REPORT_ID_HIDPP_LONG:
  max_count = HIDPP_REPORT_LONG_LENGTH - 4;
  break;
 case REPORT_ID_HIDPP_VERY_LONG:
  max_count = hidpp_dev->very_long_report_length - 4;
  break;
 default:
  return -EINVAL;
 }

 if (param_count > max_count)
  return -EINVAL;

 message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
 if (!message)
  return -ENOMEM;
 message->report_id = report_id;
 message->rap.sub_id = sub_id;
 message->rap.reg_address = reg_address;
 memcpy(&message->rap.params, params, param_count);

 ret = hidpp_send_message_sync(hidpp_dev, message, response);
 kfree(message);
 return ret;
}

static inline bool hidpp_match_answer(struct hidpp_report *question,
  struct hidpp_report *answer)
{
 return (answer->fap.feature_index == question->fap.feature_index) &&
    (answer->fap.funcindex_clientid == question->fap.funcindex_clientid);
}

static inline bool hidpp_match_error(struct hidpp_report *question,
  struct hidpp_report *answer)
{
 return ((answer->rap.sub_id == HIDPP_ERROR) ||
     (answer->fap.feature_index == HIDPP20_ERROR)) &&
     (answer->fap.funcindex_clientid == question->fap.feature_index) &&
     (answer->fap.params[0] == question->fap.funcindex_clientid);
}

static inline bool hidpp_report_is_connect_event(struct hidpp_device *hidpp,
  struct hidpp_report *report)
{
 return (hidpp->wireless_feature_index &&
  (report->fap.feature_index == hidpp->wireless_feature_index)) ||
  ((report->report_id == REPORT_ID_HIDPP_SHORT) &&
  (report->rap.sub_id == 0x41));
}

/*
 * hidpp_prefix_name() prefixes the current given name with "Logitech ".
 */

static void hidpp_prefix_name(char **name, int name_length)
{
#define PREFIX_LENGTH 9 /* "Logitech " */

 int new_length;
 char *new_name;

 if (name_length > PREFIX_LENGTH &&
     strncmp(*name, "Logitech ", PREFIX_LENGTH) == 0)
  /* The prefix has is already in the name */
  return;

 new_length = PREFIX_LENGTH + name_length;
 new_name = kzalloc(new_length, GFP_KERNEL);
 if (!new_name)
  return;

 snprintf(new_name, new_length, "Logitech %s", *name);

 kfree(*name);

 *name = new_name;
}

/*
 * Updates the USB wireless_status based on whether the headset
 * is turned on and reachable.
 */

static void hidpp_update_usb_wireless_status(struct hidpp_device *hidpp)
{
 struct hid_device *hdev = hidpp->hid_dev;
 struct usb_interface *intf;

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

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

/**
 * hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll
 *                                        events given a high-resolution wheel
 *                                        movement.
 * @input_dev: Pointer to the input device
 * @counter: a hid_scroll_counter struct describing the wheel.
 * @hi_res_value: the movement of the wheel, in the mouse's high-resolution
 *                units.
 *
 * Given a high-resolution movement, this function converts the movement into
 * fractions of 120 and emits high-resolution scroll events for the input
 * device. It also uses the multiplier from &struct hid_scroll_counter to
 * emit low-resolution scroll events when appropriate for
 * backwards-compatibility with userspace input libraries.
 */

static void hidpp_scroll_counter_handle_scroll(struct input_dev *input_dev,
            struct hidpp_scroll_counter *counter,
            int hi_res_value)
{
 int low_res_value, remainder, direction;
 unsigned long long now, previous;

 hi_res_value = hi_res_value * 120/counter->wheel_multiplier;
 input_report_rel(input_dev, REL_WHEEL_HI_RES, hi_res_value);

 remainder = counter->remainder;
 direction = hi_res_value > 0 ? 1 : -1;

 now = sched_clock();
 previous = counter->last_time;
 counter->last_time = now;
 /*
 * Reset the remainder after a period of inactivity or when the
 * direction changes. This prevents the REL_WHEEL emulation point
 * from sliding for devices that don't always provide the same
 * number of movements per detent.
 */

 if (now - previous > 1000000000 || direction != counter->direction)
  remainder = 0;

 counter->direction = direction;
 remainder += hi_res_value;

 /* Some wheels will rest 7/8ths of a detent from the previous detent
 * after slow movement, so we want the threshold for low-res events to
 * be in the middle between two detents (e.g. after 4/8ths) as
 * opposed to on the detents themselves (8/8ths).
 */

 if (abs(remainder) >= 60) {
  /* Add (or subtract) 1 because we want to trigger when the wheel
 * is half-way to the next detent (i.e. scroll 1 detent after a
 * 1/2 detent movement, 2 detents after a 1 1/2 detent movement,
 * etc.).
 */

  low_res_value = remainder / 120;
  if (low_res_value == 0)
   low_res_value = (hi_res_value > 0 ? 1 : -1);
  input_report_rel(input_dev, REL_WHEEL, low_res_value);
  remainder -= low_res_value * 120;
 }
 counter->remainder = remainder;
}

/* -------------------------------------------------------------------------- */
/* HIDP++ 1.0 commands                                                        */
/* -------------------------------------------------------------------------- */

#define HIDPP_SET_REGISTER    0x80
#define HIDPP_GET_REGISTER    0x81
#define HIDPP_SET_LONG_REGISTER    0x82
#define HIDPP_GET_LONG_REGISTER    0x83

/**
 * hidpp10_set_register - Modify a HID++ 1.0 register.
 * @hidpp_dev: the device to set the register on.
 * @register_address: the address of the register to modify.
 * @byte: the byte of the register to modify. Should be less than 3.
 * @mask: mask of the bits to modify
 * @value: new values for the bits in mask
 * Return: 0 if successful, otherwise a negative error code.
 */

static int hidpp10_set_register(struct hidpp_device *hidpp_dev,
 u8 register_address, u8 byte, u8 mask, u8 value)
{
 struct hidpp_report response;
 int ret;
 u8 params[3] = { 0 };

 ret = hidpp_send_rap_command_sync(hidpp_dev,
       REPORT_ID_HIDPP_SHORT,
       HIDPP_GET_REGISTER,
       register_address,
       NULL, 0, &response);
 if (ret)
  return ret;

 memcpy(params, response.rap.params, 3);

 params[byte] &= ~mask;
 params[byte] |= value & mask;

 return hidpp_send_rap_command_sync(hidpp_dev,
        REPORT_ID_HIDPP_SHORT,
        HIDPP_SET_REGISTER,
        register_address,
        params, 3, &response);
}

#define HIDPP_REG_ENABLE_REPORTS   0x00
#define HIDPP_ENABLE_CONSUMER_REPORT   BIT(0)
#define HIDPP_ENABLE_WHEEL_REPORT   BIT(2)
#define HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT  BIT(3)
#define HIDPP_ENABLE_BAT_REPORT    BIT(4)
#define HIDPP_ENABLE_HWHEEL_REPORT   BIT(5)

static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev)
{
 return hidpp10_set_register(hidpp_dev, HIDPP_REG_ENABLE_REPORTS, 0,
     HIDPP_ENABLE_BAT_REPORT, HIDPP_ENABLE_BAT_REPORT);
}

#define HIDPP_REG_FEATURES    0x01
#define HIDPP_ENABLE_SPECIAL_BUTTON_FUNC  BIT(1)
#define HIDPP_ENABLE_FAST_SCROLL   BIT(6)

/* On HID++ 1.0 devices, high-res scroll was called "scrolling acceleration". */
static int hidpp10_enable_scrolling_acceleration(struct hidpp_device *hidpp_dev)
{
 return hidpp10_set_register(hidpp_dev, HIDPP_REG_FEATURES, 0,
     HIDPP_ENABLE_FAST_SCROLL, HIDPP_ENABLE_FAST_SCROLL);
}

#define HIDPP_REG_BATTERY_STATUS   0x07

static int hidpp10_battery_status_map_level(u8 param)
{
 int level;

 switch (param) {
 case 1 ... 2:
  level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
  break;
 case 3 ... 4:
  level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
  break;
 case 5 ... 6:
  level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
  break;
 case 7:
  level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
  break;
 default:
  level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
 }

 return level;
}

static int hidpp10_battery_status_map_status(u8 param)
{
 int status;

 switch (param) {
 case 0x00:
  /* discharging (in use) */
  status = POWER_SUPPLY_STATUS_DISCHARGING;
  break;
 case 0x21: /* (standard) charging */
 case 0x24: /* fast charging */
 case 0x25: /* slow charging */
  status = POWER_SUPPLY_STATUS_CHARGING;
  break;
 case 0x26: /* topping charge */
 case 0x22: /* charge complete */
  status = POWER_SUPPLY_STATUS_FULL;
  break;
 case 0x20: /* unknown */
  status = POWER_SUPPLY_STATUS_UNKNOWN;
  break;
 /*
 * 0x01...0x1F = reserved (not charging)
 * 0x23 = charging error
 * 0x27..0xff = reserved
 */

 default:
  status = POWER_SUPPLY_STATUS_NOT_CHARGING;
  break;
 }

 return status;
}

static int hidpp10_query_battery_status(struct hidpp_device *hidpp)
{
 struct hidpp_report response;
 int ret, status;

 ret = hidpp_send_rap_command_sync(hidpp,
     REPORT_ID_HIDPP_SHORT,
     HIDPP_GET_REGISTER,
     HIDPP_REG_BATTERY_STATUS,
     NULL, 0, &response);
 if (ret)
  return ret;

 hidpp->battery.level =
  hidpp10_battery_status_map_level(response.rap.params[0]);
 status = hidpp10_battery_status_map_status(response.rap.params[1]);
 hidpp->battery.status = status;
 /* the capacity is only available when discharging or full */
 hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
    status == POWER_SUPPLY_STATUS_FULL;

 return 0;
}

#define HIDPP_REG_BATTERY_MILEAGE   0x0D

static int hidpp10_battery_mileage_map_status(u8 param)
{
 int status;

 switch (param >> 6) {
 case 0x00:
  /* discharging (in use) */
  status = POWER_SUPPLY_STATUS_DISCHARGING;
  break;
 case 0x01: /* charging */
  status = POWER_SUPPLY_STATUS_CHARGING;
  break;
 case 0x02: /* charge complete */
  status = POWER_SUPPLY_STATUS_FULL;
  break;
 /*
 * 0x03 = charging error
 */

 default:
  status = POWER_SUPPLY_STATUS_NOT_CHARGING;
  break;
 }

 return status;
}

static int hidpp10_query_battery_mileage(struct hidpp_device *hidpp)
{
 struct hidpp_report response;
 int ret, status;

 ret = hidpp_send_rap_command_sync(hidpp,
     REPORT_ID_HIDPP_SHORT,
     HIDPP_GET_REGISTER,
     HIDPP_REG_BATTERY_MILEAGE,
     NULL, 0, &response);
 if (ret)
  return ret;

 hidpp->battery.capacity = response.rap.params[0];
 status = hidpp10_battery_mileage_map_status(response.rap.params[2]);
 hidpp->battery.status = status;
 /* the capacity is only available when discharging or full */
 hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
    status == POWER_SUPPLY_STATUS_FULL;

 return 0;
}

static int hidpp10_battery_event(struct hidpp_device *hidpp, u8 *data, int size)
{
 struct hidpp_report *report = (struct hidpp_report *)data;
 int status, capacity, level;
 bool changed;

 if (report->report_id != REPORT_ID_HIDPP_SHORT)
  return 0;

 switch (report->rap.sub_id) {
 case HIDPP_REG_BATTERY_STATUS:
  capacity = hidpp->battery.capacity;
  level = hidpp10_battery_status_map_level(report->rawbytes[1]);
  status = hidpp10_battery_status_map_status(report->rawbytes[2]);
  break;
 case HIDPP_REG_BATTERY_MILEAGE:
  capacity = report->rap.params[0];
  level = hidpp->battery.level;
  status = hidpp10_battery_mileage_map_status(report->rawbytes[3]);
  break;
 default:
  return 0;
 }

 changed = capacity != hidpp->battery.capacity ||
    level != hidpp->battery.level ||
    status != hidpp->battery.status;

 /* the capacity is only available when discharging or full */
 hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
    status == POWER_SUPPLY_STATUS_FULL;

 if (changed) {
  hidpp->battery.level = level;
  hidpp->battery.status = status;
  if (hidpp->battery.ps)
   power_supply_changed(hidpp->battery.ps);
 }

 return 0;
}

#define HIDPP_REG_PAIRING_INFORMATION   0xB5
#define HIDPP_EXTENDED_PAIRING    0x30
#define HIDPP_DEVICE_NAME    0x40

static char *hidpp_unifying_get_name(struct hidpp_device *hidpp_dev)
{
 struct hidpp_report response;
 int ret;
 u8 params[1] = { HIDPP_DEVICE_NAME };
 char *name;
 int len;

 ret = hidpp_send_rap_command_sync(hidpp_dev,
     REPORT_ID_HIDPP_SHORT,
     HIDPP_GET_LONG_REGISTER,
     HIDPP_REG_PAIRING_INFORMATION,
     params, 1, &response);
 if (ret)
  return NULL;

 len = response.rap.params[1];

 if (2 + len > sizeof(response.rap.params))
  return NULL;

 if (len < 4) /* logitech devices are usually at least Xddd */
  return NULL;

 name = kzalloc(len + 1, GFP_KERNEL);
 if (!name)
  return NULL;

 memcpy(name, &response.rap.params[2], len);

 /* include the terminating '\0' */
 hidpp_prefix_name(&name, len + 1);

 return name;
}

static int hidpp_unifying_get_serial(struct hidpp_device *hidpp, u32 *serial)
{
 struct hidpp_report response;
 int ret;
 u8 params[1] = { HIDPP_EXTENDED_PAIRING };

 ret = hidpp_send_rap_command_sync(hidpp,
     REPORT_ID_HIDPP_SHORT,
     HIDPP_GET_LONG_REGISTER,
     HIDPP_REG_PAIRING_INFORMATION,
     params, 1, &response);
 if (ret)
  return ret;

 /*
 * We don't care about LE or BE, we will output it as a string
 * with %4phD, so we need to keep the order.
 */

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

static int hidpp_unifying_init(struct hidpp_device *hidpp)
{
 struct hid_device *hdev = hidpp->hid_dev;
 const char *name;
 u32 serial;
 int ret;

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

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

 name = hidpp_unifying_get_name(hidpp);
 if (!name)
  return -EIO;

 snprintf(hdev->name, sizeof(hdev->name), "%s", name);
 dbg_hid("HID++ Unifying: Got name: %s\n", name);

 kfree(name);
 return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x0000: Root                                                               */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_ROOT     0x0000
#define HIDPP_PAGE_ROOT_IDX    0x00

#define CMD_ROOT_GET_FEATURE    0x00
#define CMD_ROOT_GET_PROTOCOL_VERSION   0x10

static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
 u8 *feature_index)
{
 struct hidpp_report response;
 int ret;
 u8 params[2] = { feature >> 8, feature & 0x00FF };

 ret = hidpp_send_fap_command_sync(hidpp,
   HIDPP_PAGE_ROOT_IDX,
   CMD_ROOT_GET_FEATURE,
   params, 2, &response);
 if (ret)
  return ret;

 if (response.fap.params[0] == 0)
  return -ENOENT;

 *feature_index = response.fap.params[0];

 return ret;
}

static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
{
 const u8 ping_byte = 0x5a;
 u8 ping_data[3] = { 0, 0, ping_byte };
 struct hidpp_report response;
 int ret;

 ret = hidpp_send_rap_command_sync(hidpp,
   REPORT_ID_HIDPP_SHORT,
   HIDPP_PAGE_ROOT_IDX,
   CMD_ROOT_GET_PROTOCOL_VERSION | LINUX_KERNEL_SW_ID,
   ping_data, sizeof(ping_data), &response);

 if (ret == HIDPP_ERROR_INVALID_SUBID) {
  hidpp->protocol_major = 1;
  hidpp->protocol_minor = 0;
  goto print_version;
 }

 /* the device might not be connected */
 if (ret == HIDPP_ERROR_RESOURCE_ERROR)
  return -EIO;

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

 if (response.rap.params[2] != ping_byte) {
  hid_err(hidpp->hid_dev, "%s: ping mismatch 0x%02x != 0x%02x\n",
   __func__, response.rap.params[2], ping_byte);
  return -EPROTO;
 }

 hidpp->protocol_major = response.rap.params[0];
 hidpp->protocol_minor = response.rap.params[1];

print_version:
 if (!hidpp->connected_once) {
  hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
    hidpp->protocol_major, hidpp->protocol_minor);
  hidpp->connected_once = true;
 } else
  hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
    hidpp->protocol_major, hidpp->protocol_minor);
 return 0;
}

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

#define HIDPP_PAGE_DEVICE_INFORMATION   0x0003

#define CMD_GET_DEVICE_INFO    0x00

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

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

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

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

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

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

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

 return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x0005: GetDeviceNameType                                                  */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_GET_DEVICE_NAME_TYPE   0x0005

#define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT  0x00
#define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME 0x10
#define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE  0x20

static int hidpp_devicenametype_get_count(struct hidpp_device *hidpp,
 u8 feature_index, u8 *nameLength)
{
 struct hidpp_report response;
 int ret;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
  CMD_GET_DEVICE_NAME_TYPE_GET_COUNT, NULL, 0, &response);

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

 *nameLength = response.fap.params[0];

 return ret;
}

static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp,
 u8 feature_index, u8 char_index, char *device_name, int len_buf)
{
 struct hidpp_report response;
 int ret, i;
 int count;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
  CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME, &char_index, 1,
  &response);

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

 switch (response.report_id) {
 case REPORT_ID_HIDPP_VERY_LONG:
  count = hidpp->very_long_report_length - 4;
  break;
 case REPORT_ID_HIDPP_LONG:
  count = HIDPP_REPORT_LONG_LENGTH - 4;
  break;
 case REPORT_ID_HIDPP_SHORT:
  count = HIDPP_REPORT_SHORT_LENGTH - 4;
  break;
 default:
  return -EPROTO;
 }

 if (len_buf < count)
  count = len_buf;

 for (i = 0; i < count; i++)
  device_name[i] = response.fap.params[i];

 return count;
}

static char *hidpp_get_device_name(struct hidpp_device *hidpp)
{
 u8 feature_index;
 u8 __name_length;
 char *name;
 unsigned index = 0;
 int ret;

 ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
  &feature_index);
 if (ret)
  return NULL;

 ret = hidpp_devicenametype_get_count(hidpp, feature_index,
  &__name_length);
 if (ret)
  return NULL;

 name = kzalloc(__name_length + 1, GFP_KERNEL);
 if (!name)
  return NULL;

 while (index < __name_length) {
  ret = hidpp_devicenametype_get_device_name(hidpp,
   feature_index, index, name + index,
   __name_length - index);
  if (ret <= 0) {
   kfree(name);
   return NULL;
  }
  index += ret;
 }

 /* include the terminating '\0' */
 hidpp_prefix_name(&name, __name_length + 1);

 return name;
}

/* -------------------------------------------------------------------------- */
/* 0x1000: Battery level status                                               */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_BATTERY_LEVEL_STATUS    0x1000

#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS 0x00
#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY  0x10

#define EVENT_BATTERY_LEVEL_STATUS_BROADCAST   0x00

#define FLAG_BATTERY_LEVEL_DISABLE_OSD    BIT(0)
#define FLAG_BATTERY_LEVEL_MILEAGE    BIT(1)
#define FLAG_BATTERY_LEVEL_RECHARGEABLE    BIT(2)

static int hidpp_map_battery_level(int capacity)
{
 if (capacity < 11)
  return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
 /*
 * The spec says this should be < 31 but some devices report 30
 * with brand new batteries and Windows reports 30 as "Good".
 */

 else if (capacity < 30)
  return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
 else if (capacity < 81)
  return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
 return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
}

static int hidpp20_batterylevel_map_status_capacity(u8 data[3], int *capacity,
          int *next_capacity,
          int *level)
{
 int status;

 *capacity = data[0];
 *next_capacity = data[1];
 *level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;

 /* When discharging, we can rely on the device reported capacity.
 * For all other states the device reports 0 (unknown).
 */

 switch (data[2]) {
  case 0: /* discharging (in use) */
   status = POWER_SUPPLY_STATUS_DISCHARGING;
   *level = hidpp_map_battery_level(*capacity);
   break;
  case 1: /* recharging */
   status = POWER_SUPPLY_STATUS_CHARGING;
   break;
  case 2: /* charge in final stage */
   status = POWER_SUPPLY_STATUS_CHARGING;
   break;
  case 3: /* charge complete */
   status = POWER_SUPPLY_STATUS_FULL;
   *level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
   *capacity = 100;
   break;
  case 4: /* recharging below optimal speed */
   status = POWER_SUPPLY_STATUS_CHARGING;
   break;
  /* 5 = invalid battery type
   6 = thermal error
   7 = other charging error */

  default:
   status = POWER_SUPPLY_STATUS_NOT_CHARGING;
   break;
 }

 return status;
}

static int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp,
           u8 feature_index,
           int *status,
           int *capacity,
           int *next_capacity,
           int *level)
{
 struct hidpp_report response;
 int ret;
 u8 *params = (u8 *)response.fap.params;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
       CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS,
       NULL, 0, &response);
 /* Ignore these intermittent errors */
 if (ret == HIDPP_ERROR_RESOURCE_ERROR)
  return -EIO;
 if (ret > 0) {
  hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
   __func__, ret);
  return -EPROTO;
 }
 if (ret)
  return ret;

 *status = hidpp20_batterylevel_map_status_capacity(params, capacity,
          next_capacity,
          level);

 return 0;
}

static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp,
        u8 feature_index)
{
 struct hidpp_report response;
 int ret;
 u8 *params = (u8 *)response.fap.params;
 unsigned int level_count, flags;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
       CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY,
       NULL, 0, &response);
 if (ret > 0) {
  hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
   __func__, ret);
  return -EPROTO;
 }
 if (ret)
  return ret;

 level_count = params[0];
 flags = params[1];

 if (level_count < 10 || !(flags & FLAG_BATTERY_LEVEL_MILEAGE))
  hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
 else
  hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;

 return 0;
}

static int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp)
{
 int ret;
 int status, capacity, next_capacity, level;

 if (hidpp->battery.feature_index == 0xff) {
  ret = hidpp_root_get_feature(hidpp,
          HIDPP_PAGE_BATTERY_LEVEL_STATUS,
          &hidpp->battery.feature_index);
  if (ret)
   return ret;
 }

 ret = hidpp20_batterylevel_get_battery_capacity(hidpp,
      hidpp->battery.feature_index,
      &status, &capacity,
      &next_capacity, &level);
 if (ret)
  return ret;

 ret = hidpp20_batterylevel_get_battery_info(hidpp,
      hidpp->battery.feature_index);
 if (ret)
  return ret;

 hidpp->battery.status = status;
 hidpp->battery.capacity = capacity;
 hidpp->battery.level = level;
 /* the capacity is only available when discharging or full */
 hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
    status == POWER_SUPPLY_STATUS_FULL;

 return 0;
}

static int hidpp20_battery_event_1000(struct hidpp_device *hidpp,
     u8 *data, int size)
{
 struct hidpp_report *report = (struct hidpp_report *)data;
 int status, capacity, next_capacity, level;
 bool changed;

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

 status = hidpp20_batterylevel_map_status_capacity(report->fap.params,
         &capacity,
         &next_capacity,
         &level);

 /* the capacity is only available when discharging or full */
 hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
    status == POWER_SUPPLY_STATUS_FULL;

 changed = capacity != hidpp->battery.capacity ||
    level != hidpp->battery.level ||
    status != hidpp->battery.status;

 if (changed) {
  hidpp->battery.level = level;
  hidpp->battery.capacity = capacity;
  hidpp->battery.status = status;
  if (hidpp->battery.ps)
   power_supply_changed(hidpp->battery.ps);
 }

 return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x1001: Battery voltage                                                    */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_BATTERY_VOLTAGE 0x1001

#define CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE 0x00

#define EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST 0x00

static int hidpp20_battery_map_status_voltage(u8 data[3], int *voltage,
      int *level, int *charge_type)
{
 int status;

 long flags = (long) data[2];
 *level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;

 if (flags & 0x80)
  switch (flags & 0x07) {
  case 0:
   status = POWER_SUPPLY_STATUS_CHARGING;
   break;
  case 1:
   status = POWER_SUPPLY_STATUS_FULL;
   *level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
   break;
  case 2:
   status = POWER_SUPPLY_STATUS_NOT_CHARGING;
   break;
  default:
   status = POWER_SUPPLY_STATUS_UNKNOWN;
   break;
  }
 else
  status = POWER_SUPPLY_STATUS_DISCHARGING;

 *charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
 if (test_bit(3, &flags)) {
  *charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
 }
 if (test_bit(4, &flags)) {
  *charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
 }
 if (test_bit(5, &flags)) {
  *level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
 }

 *voltage = get_unaligned_be16(data);

 return status;
}

static int hidpp20_battery_get_battery_voltage(struct hidpp_device *hidpp,
       u8 feature_index,
       int *status, int *voltage,
       int *level, int *charge_type)
{
 struct hidpp_report response;
 int ret;
 u8 *params = (u8 *)response.fap.params;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
       CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE,
       NULL, 0, &response);

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

 hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_VOLTAGE;

 *status = hidpp20_battery_map_status_voltage(params, voltage,
           level, charge_type);

 return 0;
}

static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
{
 /* NB: This voltage curve doesn't necessarily map perfectly to all
 * devices that implement the BATTERY_VOLTAGE feature. This is because
 * there are a few devices that use different battery technology.
 */


 static const int voltages[100] = {
  4186, 4156, 4143, 4133, 4122, 4113, 4103, 4094, 4086, 4075,
  4067, 4059, 4051, 4043, 4035, 4027, 4019, 4011, 4003, 3997,
  3989, 3983, 3976, 3969, 3961, 3955, 3949, 3942, 3935, 3929,
  3922, 3916, 3909, 3902, 3896, 3890, 3883, 3877, 3870, 3865,
  3859, 3853, 3848, 3842, 3837, 3833, 3828, 3824, 3819, 3815,
  3811, 3808, 3804, 3800, 3797, 3793, 3790, 3787, 3784, 3781,
  3778, 3775, 3772, 3770, 3767, 3764, 3762, 3759, 3757, 3754,
  3751, 3748, 3744, 3741, 3737, 3734, 3730, 3726, 3724, 3720,
  3717, 3714, 3710, 3706, 3702, 3697, 3693, 3688, 3683, 3677,
  3671, 3666, 3662, 3658, 3654, 3646, 3633, 3612, 3579, 3537
 };

 int i;

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

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

 return 0;
}

static int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp)
{
 int ret;
 int status, voltage, level, charge_type;

 if (hidpp->battery.voltage_feature_index == 0xff) {
  ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
          &hidpp->battery.voltage_feature_index);
  if (ret)
   return ret;
 }

 ret = hidpp20_battery_get_battery_voltage(hidpp,
        hidpp->battery.voltage_feature_index,
        &status, &voltage, &level, &charge_type);

 if (ret)
  return ret;

 hidpp->battery.status = status;
 hidpp->battery.voltage = voltage;
 hidpp->battery.capacity = hidpp20_map_battery_capacity(hidpp->hid_dev,
              voltage);
 hidpp->battery.level = level;
 hidpp->battery.charge_type = charge_type;
 hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING;

 return 0;
}

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

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

 status = hidpp20_battery_map_status_voltage(report->fap.params, &voltage,
          &level, &charge_type);

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

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

/* -------------------------------------------------------------------------- */
/* 0x1004: Unified battery                                                    */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_UNIFIED_BATTERY    0x1004

#define CMD_UNIFIED_BATTERY_GET_CAPABILITIES   0x00
#define CMD_UNIFIED_BATTERY_GET_STATUS    0x10

#define EVENT_UNIFIED_BATTERY_STATUS_EVENT   0x00

#define FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL   BIT(0)
#define FLAG_UNIFIED_BATTERY_LEVEL_LOW    BIT(1)
#define FLAG_UNIFIED_BATTERY_LEVEL_GOOD    BIT(2)
#define FLAG_UNIFIED_BATTERY_LEVEL_FULL    BIT(3)

#define FLAG_UNIFIED_BATTERY_FLAGS_RECHARGEABLE   BIT(0)
#define FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE  BIT(1)

static int hidpp20_unifiedbattery_get_capabilities(struct hidpp_device *hidpp,
         u8 feature_index)
{
 struct hidpp_report response;
 int ret;
 u8 *params = (u8 *)response.fap.params;

 if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS ||
     hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) {
  /* we have already set the device capabilities, so let's skip */
  return 0;
 }

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
       CMD_UNIFIED_BATTERY_GET_CAPABILITIES,
       NULL, 0, &response);
 /* Ignore these intermittent errors */
 if (ret == HIDPP_ERROR_RESOURCE_ERROR)
  return -EIO;
 if (ret > 0) {
  hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
   __func__, ret);
  return -EPROTO;
 }
 if (ret)
  return ret;

 /*
 * If the device supports state of charge (battery percentage) we won't
 * export the battery level information. there are 4 possible battery
 * levels and they all are optional, this means that the device might
 * not support any of them, we are just better off with the battery
 * percentage.
 */

 if (params[1] & FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE) {
  hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_PERCENTAGE;
  hidpp->battery.supported_levels_1004 = 0;
 } else {
  hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
  hidpp->battery.supported_levels_1004 = params[0];
 }

 return 0;
}

static int hidpp20_unifiedbattery_map_status(struct hidpp_device *hidpp,
          u8 charging_status,
          u8 external_power_status)
{
 int status;

 switch (charging_status) {
  case 0: /* discharging */
   status = POWER_SUPPLY_STATUS_DISCHARGING;
   break;
  case 1: /* charging */
  case 2: /* charging slow */
   status = POWER_SUPPLY_STATUS_CHARGING;
   break;
  case 3: /* complete */
   status = POWER_SUPPLY_STATUS_FULL;
   break;
  case 4: /* error */
   status = POWER_SUPPLY_STATUS_NOT_CHARGING;
   hid_info(hidpp->hid_dev, "%s: charging error",
     hidpp->name);
   break;
  default:
   status = POWER_SUPPLY_STATUS_NOT_CHARGING;
   break;
 }

 return status;
}

static int hidpp20_unifiedbattery_map_level(struct hidpp_device *hidpp,
         u8 battery_level)
{
 /* cler unsupported level bits */
 battery_level &= hidpp->battery.supported_levels_1004;

 if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_FULL)
  return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
 else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_GOOD)
  return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
 else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_LOW)
  return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
 else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL)
  return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;

 return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
}

static int hidpp20_unifiedbattery_get_status(struct hidpp_device *hidpp,
          u8 feature_index,
          u8 *state_of_charge,
          int *status,
          int *level)
{
 struct hidpp_report response;
 int ret;
 u8 *params = (u8 *)response.fap.params;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
       CMD_UNIFIED_BATTERY_GET_STATUS,
       NULL, 0, &response);
 /* Ignore these intermittent errors */
 if (ret == HIDPP_ERROR_RESOURCE_ERROR)
  return -EIO;
 if (ret > 0) {
  hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
   __func__, ret);
  return -EPROTO;
 }
 if (ret)
  return ret;

 *state_of_charge = params[0];
 *status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]);
 *level = hidpp20_unifiedbattery_map_level(hidpp, params[1]);

 return 0;
}

static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
{
 int ret;
 u8 state_of_charge;
 int status, level;

 if (hidpp->battery.feature_index == 0xff) {
  ret = hidpp_root_get_feature(hidpp,
          HIDPP_PAGE_UNIFIED_BATTERY,
          &hidpp->battery.feature_index);
  if (ret)
   return ret;
 }

 ret = hidpp20_unifiedbattery_get_capabilities(hidpp,
     hidpp->battery.feature_index);
 if (ret)
  return ret;

 ret = hidpp20_unifiedbattery_get_status(hidpp,
      hidpp->battery.feature_index,
      &state_of_charge,
      &status,
      &level);
 if (ret)
  return ret;

 hidpp->capabilities |= HIDPP_CAPABILITY_UNIFIED_BATTERY;
 hidpp->battery.capacity = state_of_charge;
 hidpp->battery.status = status;
 hidpp->battery.level = level;
 hidpp->battery.online = true;

 return 0;
}

static int hidpp20_battery_event_1004(struct hidpp_device *hidpp,
     u8 *data, int size)
{
 struct hidpp_report *report = (struct hidpp_report *)data;
 u8 *params = (u8 *)report->fap.params;
 int state_of_charge, status, level;
 bool changed;

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

 state_of_charge = params[0];
 status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]);
 level = hidpp20_unifiedbattery_map_level(hidpp, params[1]);

 changed = status != hidpp->battery.status ||
    (state_of_charge != hidpp->battery.capacity &&
     hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) ||
    (level != hidpp->battery.level &&
     hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS);

 if (changed) {
  hidpp->battery.capacity = state_of_charge;
  hidpp->battery.status = status;
  hidpp->battery.level = level;
  if (hidpp->battery.ps)
   power_supply_changed(hidpp->battery.ps);
 }

 return 0;
}

/* -------------------------------------------------------------------------- */
/* Battery feature helpers                                                    */
/* -------------------------------------------------------------------------- */

static enum power_supply_property hidpp_battery_props[] = {
 POWER_SUPPLY_PROP_ONLINE,
 POWER_SUPPLY_PROP_STATUS,
 POWER_SUPPLY_PROP_SCOPE,
 POWER_SUPPLY_PROP_MODEL_NAME,
 POWER_SUPPLY_PROP_MANUFACTURER,
 POWER_SUPPLY_PROP_SERIAL_NUMBER,
 0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY, */
 0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY_LEVEL, */
 0, /* placeholder for POWER_SUPPLY_PROP_VOLTAGE_NOW, */
};

static int hidpp_battery_get_property(struct power_supply *psy,
          enum power_supply_property psp,
          union power_supply_propval *val)
{
 struct hidpp_device *hidpp = power_supply_get_drvdata(psy);
 int ret = 0;

 switch(psp) {
  case POWER_SUPPLY_PROP_STATUS:
   val->intval = hidpp->battery.status;
   break;
  case POWER_SUPPLY_PROP_CAPACITY:
   val->intval = hidpp->battery.capacity;
   break;
  case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
   val->intval = hidpp->battery.level;
   break;
  case POWER_SUPPLY_PROP_SCOPE:
   val->intval = POWER_SUPPLY_SCOPE_DEVICE;
   break;
  case POWER_SUPPLY_PROP_ONLINE:
   val->intval = hidpp->battery.online;
   break;
  case POWER_SUPPLY_PROP_MODEL_NAME:
   if (!strncmp(hidpp->name, "Logitech ", 9))
    val->strval = hidpp->name + 9;
   else
    val->strval = hidpp->name;
   break;
  case POWER_SUPPLY_PROP_MANUFACTURER:
   val->strval = "Logitech";
   break;
  case POWER_SUPPLY_PROP_SERIAL_NUMBER:
   val->strval = hidpp->hid_dev->uniq;
   break;
  case POWER_SUPPLY_PROP_VOLTAGE_NOW:
   /* hardware reports voltage in mV. sysfs expects uV */
   val->intval = hidpp->battery.voltage * 1000;
   break;
  case POWER_SUPPLY_PROP_CHARGE_TYPE:
   val->intval = hidpp->battery.charge_type;
   break;
  default:
   ret = -EINVAL;
   break;
 }

 return ret;
}

/* -------------------------------------------------------------------------- */
/* 0x1d4b: Wireless device status                                             */
/* -------------------------------------------------------------------------- */
#define HIDPP_PAGE_WIRELESS_DEVICE_STATUS   0x1d4b

static int hidpp_get_wireless_feature_index(struct hidpp_device *hidpp, u8 *feature_index)
{
 return hidpp_root_get_feature(hidpp,
          HIDPP_PAGE_WIRELESS_DEVICE_STATUS,
          feature_index);
}

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

#define HIDPP_PAGE_ADC_MEASUREMENT 0x1f20

#define CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT 0x00

#define EVENT_ADC_MEASUREMENT_STATUS_BROADCAST 0x00

static int hidpp20_map_adc_measurement_1f20_capacity(struct hid_device *hid_dev, int voltage)
{
 /* NB: This voltage curve doesn't necessarily map perfectly to all
 * devices that implement the ADC_MEASUREMENT feature. This is because
 * there are a few devices that use different battery technology.
 *
 * Adapted from:
 * https://github.com/Sapd/HeadsetControl/blob/acd972be0468e039b93aae81221f20a54d2d60f7/src/devices/logitech_g633_g933_935.c#L44-L52
 */

 static const int voltages[100] = {
  4030, 4024, 4018, 4011, 4003, 3994, 3985, 3975, 3963, 3951,
  3937, 3922, 3907, 3893, 3880, 3868, 3857, 3846, 3837, 3828,
  3820, 3812, 3805, 3798, 3791, 3785, 3779, 3773, 3768, 3762,
  3757, 3752, 3747, 3742, 3738, 3733, 3729, 3724, 3720, 3716,
  3712, 3708, 3704, 3700, 3696, 3692, 3688, 3685, 3681, 3677,
  3674, 3670, 3667, 3663, 3660, 3657, 3653, 3650, 3646, 3643,
  3640, 3637, 3633, 3630, 3627, 3624, 3620, 3617, 3614, 3611,
  3608, 3604, 3601, 3598, 3595, 3592, 3589, 3585, 3582, 3579,
  3576, 3573, 3569, 3566, 3563, 3560, 3556, 3553, 3550, 3546,
  3543, 3539, 3536, 3532, 3529, 3525, 3499, 3466, 3433, 3399,
 };

 int i;

 if (voltage == 0)
  return 0;

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

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

 return 0;
}

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

 flags = data[2];

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

 *voltage = get_unaligned_be16(data);

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

 return status;
}

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

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

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

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

static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
{
 if (hidpp->battery.adc_measurement_feature_index == 0xff) {
  int ret;

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

  hidpp->capabilities |= HIDPP_CAPABILITY_ADC_MEASUREMENT;
 }

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

 return 0;
}

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

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

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

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

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

/* -------------------------------------------------------------------------- */
/* 0x2120: Hi-resolution scrolling                                            */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_HI_RESOLUTION_SCROLLING   0x2120

#define CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE 0x10

static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp,
 bool enabled, u8 *multiplier)
{
 u8 feature_index;
 int ret;
 u8 params[1];
 struct hidpp_report response;

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

 params[0] = enabled ? BIT(0) : 0;
 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
       CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE,
       params, sizeof(params), &response);
 if (ret)
  return ret;
 *multiplier = response.fap.params[1];
 return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x2121: HiRes Wheel                                                        */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_HIRES_WHEEL  0x2121

#define CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY 0x00
#define CMD_HIRES_WHEEL_SET_WHEEL_MODE  0x20

static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
 u8 *multiplier)
{
 u8 feature_index;
 int ret;
 struct hidpp_report response;

 ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
         &feature_index);
 if (ret)
  goto return_default;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
       CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY,
       NULL, 0, &response);
 if (ret)
  goto return_default;

 *multiplier = response.fap.params[0];
 return 0;
return_default:
 hid_warn(hidpp->hid_dev,
   "Couldn't get wheel multiplier (error %d)\n", ret);
 return ret;
}

static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert,
 bool high_resolution, bool use_hidpp)
{
 u8 feature_index;
 int ret;
 u8 params[1];
 struct hidpp_report response;

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

 params[0] = (invert          ? BIT(2) : 0) |
      (high_resolution ? BIT(1) : 0) |
      (use_hidpp       ? BIT(0) : 0);

 return hidpp_send_fap_command_sync(hidpp, feature_index,
        CMD_HIRES_WHEEL_SET_WHEEL_MODE,
        params, sizeof(params), &response);
}

/* -------------------------------------------------------------------------- */
/* 0x4301: Solar Keyboard                                                     */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_SOLAR_KEYBOARD   0x4301

#define CMD_SOLAR_SET_LIGHT_MEASURE   0x00

#define EVENT_SOLAR_BATTERY_BROADCAST   0x00
#define EVENT_SOLAR_BATTERY_LIGHT_MEASURE  0x10
#define EVENT_SOLAR_CHECK_LIGHT_BUTTON   0x20

static int hidpp_solar_request_battery_event(struct hidpp_device *hidpp)
{
 struct hidpp_report response;
 u8 params[2] = { 1, 1 };
 int ret;

 if (hidpp->battery.feature_index == 0xff) {
  ret = hidpp_root_get_feature(hidpp,
          HIDPP_PAGE_SOLAR_KEYBOARD,
          &hidpp->battery.solar_feature_index);
  if (ret)
   return ret;
 }

 ret = hidpp_send_fap_command_sync(hidpp,
       hidpp->battery.solar_feature_index,
       CMD_SOLAR_SET_LIGHT_MEASURE,
       params, 2, &response);
 if (ret > 0) {
  hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
   __func__, ret);
  return -EPROTO;
 }
 if (ret)
  return ret;

 hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;

 return 0;
}

static int hidpp_solar_battery_event(struct hidpp_device *hidpp,
         u8 *data, int size)
{
 struct hidpp_report *report = (struct hidpp_report *)data;
 int capacity, lux, status;
 u8 function;

 function = report->fap.funcindex_clientid;


 if (report->fap.feature_index != hidpp->battery.solar_feature_index ||
     !(function == EVENT_SOLAR_BATTERY_BROADCAST ||
       function == EVENT_SOLAR_BATTERY_LIGHT_MEASURE ||
       function == EVENT_SOLAR_CHECK_LIGHT_BUTTON))
  return 0;

 capacity = report->fap.params[0];

 switch (function) {
 case EVENT_SOLAR_BATTERY_LIGHT_MEASURE:
  lux = (report->fap.params[1] << 8) | report->fap.params[2];
  if (lux > 200)
   status = POWER_SUPPLY_STATUS_CHARGING;
  else
   status = POWER_SUPPLY_STATUS_DISCHARGING;
  break;
 case EVENT_SOLAR_CHECK_LIGHT_BUTTON:
 default:
  if (capacity < hidpp->battery.capacity)
   status = POWER_SUPPLY_STATUS_DISCHARGING;
  else
   status = POWER_SUPPLY_STATUS_CHARGING;

 }

 if (capacity == 100)
  status = POWER_SUPPLY_STATUS_FULL;

 hidpp->battery.online = true;
 if (capacity != hidpp->battery.capacity ||
     status != hidpp->battery.status) {
  hidpp->battery.capacity = capacity;
  hidpp->battery.status = status;
  if (hidpp->battery.ps)
   power_supply_changed(hidpp->battery.ps);
 }

 return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x6010: Touchpad FW items                                                  */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_TOUCHPAD_FW_ITEMS   0x6010

#define CMD_TOUCHPAD_FW_ITEMS_SET   0x10

struct hidpp_touchpad_fw_items {
 uint8_t presence;
 uint8_t desired_state;
 uint8_t state;
 uint8_t persistent;
};

/*
 * send a set state command to the device by reading the current items->state
 * field. items is then filled with the current state.
 */

static int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp,
           u8 feature_index,
           struct hidpp_touchpad_fw_items *items)
{
 struct hidpp_report response;
 int ret;
 u8 *params = (u8 *)response.fap.params;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
  CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 1, &response);

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

 items->presence = params[0];
 items->desired_state = params[1];
 items->state = params[2];
 items->persistent = params[3];

 return 0;
}

/* -------------------------------------------------------------------------- */
/* 0x6100: TouchPadRawXY                                                      */
/* -------------------------------------------------------------------------- */

#define HIDPP_PAGE_TOUCHPAD_RAW_XY   0x6100

#define CMD_TOUCHPAD_GET_RAW_INFO   0x00
#define CMD_TOUCHPAD_SET_RAW_REPORT_STATE  0x20

#define EVENT_TOUCHPAD_RAW_XY    0x00

#define TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT  0x01
#define TOUCHPAD_RAW_XY_ORIGIN_UPPER_LEFT  0x03

struct hidpp_touchpad_raw_info {
 u16 x_size;
 u16 y_size;
 u8 z_range;
 u8 area_range;
 u8 timestamp_unit;
 u8 maxcontacts;
 u8 origin;
 u16 res;
};

struct hidpp_touchpad_raw_xy_finger {
 u8 contact_type;
 u8 contact_status;
 u16 x;
 u16 y;
 u8 z;
 u8 area;
 u8 finger_id;
};

struct hidpp_touchpad_raw_xy {
 u16 timestamp;
 struct hidpp_touchpad_raw_xy_finger fingers[2];
 u8 spurious_flag;
 u8 end_of_frame;
 u8 finger_count;
 u8 button;
};

static int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp,
 u8 feature_index, struct hidpp_touchpad_raw_info *raw_info)
{
 struct hidpp_report response;
 int ret;
 u8 *params = (u8 *)response.fap.params;

 ret = hidpp_send_fap_command_sync(hidpp, feature_index,
  CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response);

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

 raw_info->x_size = get_unaligned_be16(¶ms[0]);
 raw_info->y_size = get_unaligned_be16(¶ms[2]);
 raw_info->z_range = params[4];
 raw_info->area_range = params[5];
 raw_info->maxcontacts = params[7];
 raw_info->origin = params[8];
 /* res is given in unit per inch */
 raw_info->res = get_unaligned_be16(¶ms[13]) * 2 / 51;

 return ret;
}

static int hidpp_touchpad_set_raw_report_state(struct hidpp_device *hidpp_dev,
  u8 feature_index, bool send_raw_reports,
  bool sensor_enhanced_settings)
{
 struct hidpp_report response;

 /*
 * Params:
 *   bit 0 - enable raw
 *   bit 1 - 16bit Z, no area
 *   bit 2 - enhanced sensitivity
 *   bit 3 - width, height (4 bits each) instead of area
 *   bit 4 - send raw + gestures (degrades smoothness)
 *   remaining bits - reserved
 */

 u8 params = send_raw_reports | (sensor_enhanced_settings << 2);

 return hidpp_send_fap_command_sync(hidpp_dev, feature_index,
  CMD_TOUCHPAD_SET_RAW_REPORT_STATE, ¶ms, 1, &response);
}

static void hidpp_touchpad_touch_event(u8 *data,
 struct hidpp_touchpad_raw_xy_finger *finger)
{
 u8 x_m = data[0] << 2;
 u8 y_m = data[2] << 2;

 finger->x = x_m << 6 | data[1];
 finger->y = y_m << 6 | data[3];

 finger->contact_type = data[0] >> 6;
 finger->contact_status = data[2] >> 6;

 finger->z = data[4];
 finger->area = data[5];
 finger->finger_id = data[6] >> 4;
}

static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
  u8 *data, struct hidpp_touchpad_raw_xy *raw_xy)
{
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=97 G=94

¤ Dauer der Verarbeitung: 0.35 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.