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 159 kB image not shown  

Quelle  wacom_wac.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  USB Wacom tablet support - Wacom specific code
 */


#include "wacom_wac.h"
#include "wacom.h"
#include <linux/input/mt.h>
#include <linux/jiffies.h>

/* resolution for penabled devices */
#define WACOM_PL_RES  20
#define WACOM_PENPRTN_RES 40
#define WACOM_VOLITO_RES 50
#define WACOM_GRAPHIRE_RES 80
#define WACOM_INTUOS_RES 100
#define WACOM_INTUOS3_RES 200

/* Newer Cintiq and DTU have an offset between tablet and screen areas */
#define WACOM_DTU_OFFSET 200
#define WACOM_CINTIQ_OFFSET 400

/*
 * Scale factor relating reported contact size to logical contact area.
 * 2^14/pi is a good approximation on Intuos5 and 3rd-gen Bamboo
 */

#define WACOM_CONTACT_AREA_SCALE 2607

static bool touch_arbitration = 1;
module_param(touch_arbitration, bool, 0644);
MODULE_PARM_DESC(touch_arbitration, " on (Y) off (N)");

static void wacom_report_numbered_buttons(struct input_dev *input_dev,
    int button_count, int mask);

static int wacom_numbered_button_to_key(int n);

static void wacom_update_led(struct wacom *wacom, int button_count, int mask,
        int group);

static void wacom_force_proxout(struct wacom_wac *wacom_wac)
{
 struct input_dev *input = wacom_wac->pen_input;

 wacom_wac->shared->stylus_in_proximity = 0;

 input_report_key(input, BTN_TOUCH, 0);
 input_report_key(input, BTN_STYLUS, 0);
 input_report_key(input, BTN_STYLUS2, 0);
 input_report_key(input, BTN_STYLUS3, 0);
 input_report_key(input, wacom_wac->tool[0], 0);
 if (wacom_wac->serial[0]) {
  input_report_abs(input, ABS_MISC, 0);
 }
 input_report_abs(input, ABS_PRESSURE, 0);

 wacom_wac->tool[0] = 0;
 wacom_wac->id[0] = 0;
 wacom_wac->serial[0] = 0;

 input_sync(input);
}

void wacom_idleprox_timeout(struct timer_list *list)
{
 struct wacom *wacom = timer_container_of(wacom, list, idleprox_timer);
 struct wacom_wac *wacom_wac = &wacom->wacom_wac;

 if (!wacom_wac->hid_data.sense_state) {
  return;
 }

 hid_warn(wacom->hdev, "%s: tool appears to be hung in-prox. forcing it out.\n", __func__);
 wacom_force_proxout(wacom_wac);
}

/*
 * Percent of battery capacity for Graphire.
 * 8th value means AC online and show 100% capacity.
 */

static unsigned short batcap_gr[8] = { 1, 15, 25, 35, 50, 70, 100, 100 };

/*
 * Percent of battery capacity for Intuos4 WL, AC has a separate bit.
 */

static unsigned short batcap_i4[8] = { 1, 15, 30, 45, 60, 70, 85, 100 };

static void __wacom_notify_battery(struct wacom_battery *battery,
       int bat_status, int bat_capacity,
       bool bat_charging, bool bat_connected,
       bool ps_connected)
{
 bool changed = battery->bat_status       != bat_status    ||
         battery->battery_capacity != bat_capacity  ||
         battery->bat_charging     != bat_charging  ||
         battery->bat_connected    != bat_connected ||
         battery->ps_connected     != ps_connected;

 if (changed) {
  battery->bat_status = bat_status;
  battery->battery_capacity = bat_capacity;
  battery->bat_charging = bat_charging;
  battery->bat_connected = bat_connected;
  battery->ps_connected = ps_connected;

  if (battery->battery)
   power_supply_changed(battery->battery);
 }
}

static void wacom_notify_battery(struct wacom_wac *wacom_wac,
 int bat_status, int bat_capacity, bool bat_charging,
 bool bat_connected, bool ps_connected)
{
 struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
 bool bat_initialized = wacom->battery.battery;
 bool has_quirk = wacom_wac->features.quirks & WACOM_QUIRK_BATTERY;

 if (bat_initialized != has_quirk)
  wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY);

 __wacom_notify_battery(&wacom->battery, bat_status, bat_capacity,
          bat_charging, bat_connected, ps_connected);
}

static int wacom_penpartner_irq(struct wacom_wac *wacom)
{
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;

 switch (data[0]) {
 case 1:
  if (data[5] & 0x80) {
   wacom->tool[0] = (data[5] & 0x20) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;
   wacom->id[0] = (data[5] & 0x20) ? ERASER_DEVICE_ID : STYLUS_DEVICE_ID;
   input_report_key(input, wacom->tool[0], 1);
   input_report_abs(input, ABS_MISC, wacom->id[0]); /* report tool id */
   input_report_abs(input, ABS_X, get_unaligned_le16(&data[1]));
   input_report_abs(input, ABS_Y, get_unaligned_le16(&data[3]));
   input_report_abs(input, ABS_PRESSURE, (signed char)data[6] + 127);
   input_report_key(input, BTN_TOUCH, ((signed char)data[6] > -127));
   input_report_key(input, BTN_STYLUS, (data[5] & 0x40));
  } else {
   input_report_key(input, wacom->tool[0], 0);
   input_report_abs(input, ABS_MISC, 0); /* report tool id */
   input_report_abs(input, ABS_PRESSURE, -1);
   input_report_key(input, BTN_TOUCH, 0);
  }
  break;

 case 2:
  input_report_key(input, BTN_TOOL_PEN, 1);
  input_report_abs(input, ABS_MISC, STYLUS_DEVICE_ID); /* report tool id */
  input_report_abs(input, ABS_X, get_unaligned_le16(&data[1]));
  input_report_abs(input, ABS_Y, get_unaligned_le16(&data[3]));
  input_report_abs(input, ABS_PRESSURE, (signed char)data[6] + 127);
  input_report_key(input, BTN_TOUCH, ((signed char)data[6] > -80) && !(data[5] & 0x20));
  input_report_key(input, BTN_STYLUS, (data[5] & 0x40));
  break;

 default:
  dev_dbg(input->dev.parent,
   "%s: received unknown report #%d\n", __func__, data[0]);
  return 0;
        }

 return 1;
}

static int wacom_pl_irq(struct wacom_wac *wacom)
{
 struct wacom_features *features = &wacom->features;
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;
 int prox, pressure;

 if (data[0] != WACOM_REPORT_PENABLED) {
  dev_dbg(input->dev.parent,
   "%s: received unknown report #%d\n", __func__, data[0]);
  return 0;
 }

 prox = data[1] & 0x40;

 if (!wacom->id[0]) {
  if ((data[0] & 0x10) || (data[4] & 0x20)) {
   wacom->tool[0] = BTN_TOOL_RUBBER;
   wacom->id[0] = ERASER_DEVICE_ID;
  }
  else {
   wacom->tool[0] = BTN_TOOL_PEN;
   wacom->id[0] = STYLUS_DEVICE_ID;
  }
 }

 /* If the eraser is in prox, STYLUS2 is always set. If we
 * mis-detected the type and notice that STYLUS2 isn't set
 * then force the eraser out of prox and let the pen in.
 */

 if (wacom->tool[0] == BTN_TOOL_RUBBER && !(data[4] & 0x20)) {
  input_report_key(input, BTN_TOOL_RUBBER, 0);
  input_report_abs(input, ABS_MISC, 0);
  input_sync(input);
  wacom->tool[0] = BTN_TOOL_PEN;
  wacom->id[0] = STYLUS_DEVICE_ID;
 }

 if (prox) {
  pressure = (signed char)((data[7] << 1) | ((data[4] >> 2) & 1));
  if (features->pressure_max > 255)
   pressure = (pressure << 1) | ((data[4] >> 6) & 1);
  pressure += (features->pressure_max + 1) / 2;

  input_report_abs(input, ABS_X, data[3] | (data[2] << 7) | ((data[1] & 0x03) << 14));
  input_report_abs(input, ABS_Y, data[6] | (data[5] << 7) | ((data[4] & 0x03) << 14));
  input_report_abs(input, ABS_PRESSURE, pressure);

  input_report_key(input, BTN_TOUCH, data[4] & 0x08);
  input_report_key(input, BTN_STYLUS, data[4] & 0x10);
  /* Only allow the stylus2 button to be reported for the pen tool. */
  input_report_key(input, BTN_STYLUS2, (wacom->tool[0] == BTN_TOOL_PEN) && (data[4] & 0x20));
 }

 if (!prox)
  wacom->id[0] = 0;
 input_report_key(input, wacom->tool[0], prox);
 input_report_abs(input, ABS_MISC, wacom->id[0]);
 return 1;
}

static int wacom_ptu_irq(struct wacom_wac *wacom)
{
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;

 if (data[0] != WACOM_REPORT_PENABLED) {
  dev_dbg(input->dev.parent,
   "%s: received unknown report #%d\n", __func__, data[0]);
  return 0;
 }

 if (data[1] & 0x04) {
  input_report_key(input, BTN_TOOL_RUBBER, data[1] & 0x20);
  input_report_key(input, BTN_TOUCH, data[1] & 0x08);
  wacom->id[0] = ERASER_DEVICE_ID;
 } else {
  input_report_key(input, BTN_TOOL_PEN, data[1] & 0x20);
  input_report_key(input, BTN_TOUCH, data[1] & 0x01);
  wacom->id[0] = STYLUS_DEVICE_ID;
 }
 input_report_abs(input, ABS_MISC, wacom->id[0]); /* report tool id */
 input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2]));
 input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4]));
 input_report_abs(input, ABS_PRESSURE, le16_to_cpup((__le16 *)&data[6]));
 input_report_key(input, BTN_STYLUS, data[1] & 0x02);
 input_report_key(input, BTN_STYLUS2, data[1] & 0x10);
 return 1;
}

static int wacom_dtu_irq(struct wacom_wac *wacom)
{
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;
 int prox = data[1] & 0x20;

 dev_dbg(input->dev.parent,
  "%s: received report #%d", __func__, data[0]);

 if (prox) {
  /* Going into proximity select tool */
  wacom->tool[0] = (data[1] & 0x0c) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;
  if (wacom->tool[0] == BTN_TOOL_PEN)
   wacom->id[0] = STYLUS_DEVICE_ID;
  else
   wacom->id[0] = ERASER_DEVICE_ID;
 }
 input_report_key(input, BTN_STYLUS, data[1] & 0x02);
 input_report_key(input, BTN_STYLUS2, data[1] & 0x10);
 input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2]));
 input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4]));
 input_report_abs(input, ABS_PRESSURE, ((data[7] & 0x01) << 8) | data[6]);
 input_report_key(input, BTN_TOUCH, data[1] & 0x05);
 if (!prox) /* out-prox */
  wacom->id[0] = 0;
 input_report_key(input, wacom->tool[0], prox);
 input_report_abs(input, ABS_MISC, wacom->id[0]);
 return 1;
}

static int wacom_dtus_irq(struct wacom_wac *wacom)
{
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;
 unsigned short prox, pressure = 0;

 if (data[0] != WACOM_REPORT_DTUS && data[0] != WACOM_REPORT_DTUSPAD) {
  dev_dbg(input->dev.parent,
   "%s: received unknown report #%d", __func__, data[0]);
  return 0;
 } else if (data[0] == WACOM_REPORT_DTUSPAD) {
  input = wacom->pad_input;
  input_report_key(input, BTN_0, (data[1] & 0x01));
  input_report_key(input, BTN_1, (data[1] & 0x02));
  input_report_key(input, BTN_2, (data[1] & 0x04));
  input_report_key(input, BTN_3, (data[1] & 0x08));
  input_report_abs(input, ABS_MISC,
     data[1] & 0x0f ? PAD_DEVICE_ID : 0);
  return 1;
 } else {
  prox = data[1] & 0x80;
  if (prox) {
   switch ((data[1] >> 3) & 3) {
   case 1: /* Rubber */
    wacom->tool[0] = BTN_TOOL_RUBBER;
    wacom->id[0] = ERASER_DEVICE_ID;
    break;

   case 2: /* Pen */
    wacom->tool[0] = BTN_TOOL_PEN;
    wacom->id[0] = STYLUS_DEVICE_ID;
    break;
   }
  }

  input_report_key(input, BTN_STYLUS, data[1] & 0x20);
  input_report_key(input, BTN_STYLUS2, data[1] & 0x40);
  input_report_abs(input, ABS_X, get_unaligned_be16(&data[3]));
  input_report_abs(input, ABS_Y, get_unaligned_be16(&data[5]));
  pressure = ((data[1] & 0x03) << 8) | (data[2] & 0xff);
  input_report_abs(input, ABS_PRESSURE, pressure);
  input_report_key(input, BTN_TOUCH, pressure > 10);

  if (!prox) /* out-prox */
   wacom->id[0] = 0;
  input_report_key(input, wacom->tool[0], prox);
  input_report_abs(input, ABS_MISC, wacom->id[0]);
  return 1;
 }
}

static int wacom_graphire_irq(struct wacom_wac *wacom)
{
 struct wacom_features *features = &wacom->features;
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;
 struct input_dev *pad_input = wacom->pad_input;
 int battery_capacity, ps_connected;
 int prox;
 int rw = 0;
 int retval = 0;

 if (features->type == GRAPHIRE_BT) {
  if (data[0] != WACOM_REPORT_PENABLED_BT) {
   dev_dbg(input->dev.parent,
    "%s: received unknown report #%d\n", __func__,
    data[0]);
   goto exit;
  }
 } else if (data[0] != WACOM_REPORT_PENABLED) {
  dev_dbg(input->dev.parent,
   "%s: received unknown report #%d\n", __func__, data[0]);
  goto exit;
 }

 prox = data[1] & 0x80;
 if (prox || wacom->id[0]) {
  if (prox) {
   switch ((data[1] >> 5) & 3) {

   case 0: /* Pen */
    wacom->tool[0] = BTN_TOOL_PEN;
    wacom->id[0] = STYLUS_DEVICE_ID;
    break;

   case 1: /* Rubber */
    wacom->tool[0] = BTN_TOOL_RUBBER;
    wacom->id[0] = ERASER_DEVICE_ID;
    break;

   case 2: /* Mouse with wheel */
    input_report_key(input, BTN_MIDDLE, data[1] & 0x04);
    fallthrough;

   case 3: /* Mouse without wheel */
    wacom->tool[0] = BTN_TOOL_MOUSE;
    wacom->id[0] = CURSOR_DEVICE_ID;
    break;
   }
  }
  input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2]));
  input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4]));
  if (wacom->tool[0] != BTN_TOOL_MOUSE) {
   if (features->type == GRAPHIRE_BT)
    input_report_abs(input, ABS_PRESSURE, data[6] |
     (((__u16) (data[1] & 0x08)) << 5));
   else
    input_report_abs(input, ABS_PRESSURE, data[6] |
     ((data[7] & 0x03) << 8));
   input_report_key(input, BTN_TOUCH, data[1] & 0x01);
   input_report_key(input, BTN_STYLUS, data[1] & 0x02);
   input_report_key(input, BTN_STYLUS2, data[1] & 0x04);
  } else {
   input_report_key(input, BTN_LEFT, data[1] & 0x01);
   input_report_key(input, BTN_RIGHT, data[1] & 0x02);
   if (features->type == WACOM_G4 ||
     features->type == WACOM_MO) {
    input_report_abs(input, ABS_DISTANCE, data[6] & 0x3f);
    rw = (data[7] & 0x04) - (data[7] & 0x03);
   } else if (features->type == GRAPHIRE_BT) {
    /* Compute distance between mouse and tablet */
    rw = 44 - (data[6] >> 2);
    rw = clamp_val(rw, 0, 31);
    input_report_abs(input, ABS_DISTANCE, rw);
    if (((data[1] >> 5) & 3) == 2) {
     /* Mouse with wheel */
     input_report_key(input, BTN_MIDDLE,
       data[1] & 0x04);
     rw = (data[6] & 0x01) ? -1 :
      (data[6] & 0x02) ? 1 : 0;
    } else {
     rw = 0;
    }
   } else {
    input_report_abs(input, ABS_DISTANCE, data[7] & 0x3f);
    rw = -(signed char)data[6];
   }
   input_report_rel(input, REL_WHEEL, rw);
  }

  if (!prox)
   wacom->id[0] = 0;
  input_report_abs(input, ABS_MISC, wacom->id[0]); /* report tool id */
  input_report_key(input, wacom->tool[0], prox);
  input_sync(input); /* sync last event */
 }

 /* send pad data */
 switch (features->type) {
 case WACOM_G4:
  prox = data[7] & 0xf8;
  if (prox || wacom->id[1]) {
   wacom->id[1] = PAD_DEVICE_ID;
   input_report_key(pad_input, BTN_BACK, (data[7] & 0x40));
   input_report_key(pad_input, BTN_FORWARD, (data[7] & 0x80));
   rw = ((data[7] & 0x18) >> 3) - ((data[7] & 0x20) >> 3);
   input_report_rel(pad_input, REL_WHEEL, rw);
   if (!prox)
    wacom->id[1] = 0;
   input_report_abs(pad_input, ABS_MISC, wacom->id[1]);
   retval = 1;
  }
  break;

 case WACOM_MO:
  prox = (data[7] & 0xf8) || data[8];
  if (prox || wacom->id[1]) {
   wacom->id[1] = PAD_DEVICE_ID;
   input_report_key(pad_input, BTN_BACK, (data[7] & 0x08));
   input_report_key(pad_input, BTN_LEFT, (data[7] & 0x20));
   input_report_key(pad_input, BTN_FORWARD, (data[7] & 0x10));
   input_report_key(pad_input, BTN_RIGHT, (data[7] & 0x40));
   input_report_abs(pad_input, ABS_WHEEL, (data[8] & 0x7f));
   if (!prox)
    wacom->id[1] = 0;
   input_report_abs(pad_input, ABS_MISC, wacom->id[1]);
   retval = 1;
  }
  break;
 case GRAPHIRE_BT:
  prox = data[7] & 0x03;
  if (prox || wacom->id[1]) {
   wacom->id[1] = PAD_DEVICE_ID;
   input_report_key(pad_input, BTN_0, (data[7] & 0x02));
   input_report_key(pad_input, BTN_1, (data[7] & 0x01));
   if (!prox)
    wacom->id[1] = 0;
   input_report_abs(pad_input, ABS_MISC, wacom->id[1]);
   retval = 1;
  }
  break;
 }

 /* Store current battery capacity and power supply state */
 if (features->type == GRAPHIRE_BT) {
  rw = (data[7] >> 2 & 0x07);
  battery_capacity = batcap_gr[rw];
  ps_connected = rw == 7;
  wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
         battery_capacity, ps_connected, 1,
         ps_connected);
 }
exit:
 return retval;
}

static void wacom_intuos_schedule_prox_event(struct wacom_wac *wacom_wac)
{
 struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
 struct wacom_features *features = &wacom_wac->features;
 struct hid_report *r;
 struct hid_report_enum *re;

 re = &(wacom->hdev->report_enum[HID_FEATURE_REPORT]);
 if (features->type == INTUOSHT2)
  r = re->report_id_hash[WACOM_REPORT_INTUOSHT2_ID];
 else
  r = re->report_id_hash[WACOM_REPORT_INTUOS_ID1];
 if (r) {
  hid_hw_request(wacom->hdev, r, HID_REQ_GET_REPORT);
 }
}

static int wacom_intuos_pad(struct wacom_wac *wacom)
{
 struct wacom_features *features = &wacom->features;
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pad_input;
 int i;
 int buttons = 0, nbuttons = features->numbered_buttons;
 int keys = 0, nkeys = 0;
 int ring1 = 0, ring2 = 0;
 int strip1 = 0, strip2 = 0;
 bool prox = false;
 bool wrench = false, keyboard = false, mute_touch = false, menu = false,
      info = false;

 /* pad packets. Works as a second tool and is always in prox */
 if (!(data[0] == WACOM_REPORT_INTUOSPAD || data[0] == WACOM_REPORT_INTUOS5PAD ||
       data[0] == WACOM_REPORT_CINTIQPAD))
  return 0;

 if (features->type >= INTUOS4S && features->type <= INTUOS4L) {
  buttons = (data[3] << 1) | (data[2] & 0x01);
  ring1 = data[1];
 } else if (features->type == DTK) {
  buttons = data[6];
 } else if (features->type == WACOM_13HD) {
  buttons = (data[4] << 1) | (data[3] & 0x01);
 } else if (features->type == WACOM_24HD) {
  buttons = (data[8] << 8) | data[6];
  ring1 = data[1];
  ring2 = data[2];

  /*
 * Three "buttons" are available on the 24HD which are
 * physically implemented as a touchstrip. Each button
 * is approximately 3 bits wide with a 2 bit spacing.
 * The raw touchstrip bits are stored at:
 *    ((data[3] & 0x1f) << 8) | data[4])
 */

  nkeys = 3;
  keys = ((data[3] & 0x1C) ? 1<<2 : 0) |
         ((data[4] & 0xE0) ? 1<<1 : 0) |
         ((data[4] & 0x07) ? 1<<0 : 0);
  keyboard = !!(data[4] & 0xE0);
  info = !!(data[3] & 0x1C);

  if (features->oPid) {
   mute_touch = !!(data[4] & 0x07);
   if (mute_touch)
    wacom->shared->is_touch_on =
     !wacom->shared->is_touch_on;
  } else {
   wrench = !!(data[4] & 0x07);
  }
 } else if (features->type == WACOM_27QHD) {
  nkeys = 3;
  keys = data[2] & 0x07;

  wrench = !!(data[2] & 0x01);
  keyboard = !!(data[2] & 0x02);

  if (features->oPid) {
   mute_touch = !!(data[2] & 0x04);
   if (mute_touch)
    wacom->shared->is_touch_on =
     !wacom->shared->is_touch_on;
  } else {
   menu = !!(data[2] & 0x04);
  }
  input_report_abs(input, ABS_X, be16_to_cpup((__be16 *)&data[4]));
  input_report_abs(input, ABS_Y, be16_to_cpup((__be16 *)&data[6]));
  input_report_abs(input, ABS_Z, be16_to_cpup((__be16 *)&data[8]));
 } else if (features->type == CINTIQ_HYBRID) {
  /*
 * Do not send hardware buttons under Android. They
 * are already sent to the system through GPIO (and
 * have different meaning).
 *
 * d-pad right  -> data[4] & 0x10
 * d-pad up     -> data[4] & 0x20
 * d-pad left   -> data[4] & 0x40
 * d-pad down   -> data[4] & 0x80
 * d-pad center -> data[3] & 0x01
 */

  buttons = (data[4] << 1) | (data[3] & 0x01);
 } else if (features->type == CINTIQ_COMPANION_2) {
  /* d-pad right  -> data[2] & 0x10
 * d-pad up     -> data[2] & 0x20
 * d-pad left   -> data[2] & 0x40
 * d-pad down   -> data[2] & 0x80
 * d-pad center -> data[1] & 0x01
 */

  buttons = ((data[2] >> 4) << 7) |
            ((data[1] & 0x04) << 4) |
            ((data[2] & 0x0F) << 2) |
            (data[1] & 0x03);
 } else if (features->type >= INTUOS5S && features->type <= INTUOSPL) {
  /*
 * ExpressKeys on Intuos5/Intuos Pro have a capacitive sensor in
 * addition to the mechanical switch. Switch data is
 * stored in data[4], capacitive data in data[5].
 *
 * Touch ring mode switch (data[3]) has no capacitive sensor
 */

  buttons = (data[4] << 1) | (data[3] & 0x01);
  ring1 = data[2];
 } else {
  if (features->type == WACOM_21UX2 || features->type == WACOM_22HD) {
   buttons = (data[8] << 10) | ((data[7] & 0x01) << 9) |
             (data[6] << 1) | (data[5] & 0x01);

   if (features->type == WACOM_22HD) {
    nkeys = 3;
    keys = data[9] & 0x07;

    info = !!(data[9] & 0x01);
    wrench = !!(data[9] & 0x02);
   }
  } else {
   buttons = ((data[6] & 0x10) << 5)  |
             ((data[5] & 0x10) << 4)  |
             ((data[6] & 0x0F) << 4)  |
             (data[5] & 0x0F);
  }
  strip1 = ((data[1] & 0x1f) << 8) | data[2];
  strip2 = ((data[3] & 0x1f) << 8) | data[4];
 }

 prox = (buttons & ~(~0U << nbuttons)) | (keys & ~(~0U << nkeys)) |
        (ring1 & 0x80) | (ring2 & 0x80) | strip1 | strip2;

 wacom_report_numbered_buttons(input, nbuttons, buttons);

 for (i = 0; i < nkeys; i++)
  input_report_key(input, KEY_PROG1 + i, keys & (1 << i));

 input_report_key(input, KEY_BUTTONCONFIG, wrench);
 input_report_key(input, KEY_ONSCREEN_KEYBOARD, keyboard);
 input_report_key(input, KEY_CONTROLPANEL, menu);
 input_report_key(input, KEY_INFO, info);

 if (wacom->shared && wacom->shared->touch_input) {
  input_report_switch(wacom->shared->touch_input,
        SW_MUTE_DEVICE,
        !wacom->shared->is_touch_on);
  input_sync(wacom->shared->touch_input);
 }

 input_report_abs(input, ABS_RX, strip1);
 input_report_abs(input, ABS_RY, strip2);

 input_report_abs(input, ABS_WHEEL,    (ring1 & 0x80) ? (ring1 & 0x7f) : 0);
 input_report_abs(input, ABS_THROTTLE, (ring2 & 0x80) ? (ring2 & 0x7f) : 0);

 input_report_key(input, wacom->tool[1], prox ? 1 : 0);
 input_report_abs(input, ABS_MISC, prox ? PAD_DEVICE_ID : 0);

 input_event(input, EV_MSC, MSC_SERIAL, 0xffffffff);

 return 1;
}

static int wacom_intuos_id_mangle(int tool_id)
{
 return (tool_id & ~0xFFF) << 4 | (tool_id & 0xFFF);
}

static bool wacom_is_art_pen(int tool_id)
{
 bool is_art_pen = false;

 switch (tool_id) {
 case 0x885: /* Intuos3 Marker Pen */
 case 0x804: /* Intuos4/5 13HD/24HD Marker Pen */
 case 0x10804: /* Intuos4/5 13HD/24HD Art Pen */
 case 0x204:     /* Art Pen 2 */
  is_art_pen = true;
  break;
 }
 return is_art_pen;
}

static int wacom_intuos_get_tool_type(int tool_id)
{
 switch (tool_id) {
 case 0x812: /* Inking pen */
 case 0x801: /* Intuos3 Inking pen */
 case 0x12802: /* Intuos4/5 Inking Pen */
 case 0x012:
  return BTN_TOOL_PENCIL;

 case 0x832: /* Stroke pen */
 case 0x032:
  return BTN_TOOL_BRUSH;

 case 0x007: /* Mouse 4D and 2D */
 case 0x09c:
 case 0x094:
 case 0x017: /* Intuos3 2D Mouse */
 case 0x806: /* Intuos4 Mouse */
  return BTN_TOOL_MOUSE;

 case 0x096: /* Lens cursor */
 case 0x097: /* Intuos3 Lens cursor */
 case 0x006: /* Intuos4 Lens cursor */
  return BTN_TOOL_LENS;

 case 0xd12:
 case 0x912:
 case 0x112:
 case 0x913: /* Intuos3 Airbrush */
 case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
 case 0x10902: /* Intuos4/5 13HD/24HD Airbrush */
  return BTN_TOOL_AIRBRUSH;

 default:
  if (tool_id & 0x0008)
   return BTN_TOOL_RUBBER;
  return BTN_TOOL_PEN;
 }
}

static void wacom_exit_report(struct wacom_wac *wacom)
{
 struct input_dev *input = wacom->pen_input;
 struct wacom_features *features = &wacom->features;
 unsigned char *data = wacom->data;
 int idx = (features->type == INTUOS) ? (data[1] & 0x01) : 0;

 /*
 * Reset all states otherwise we lose the initial states
 * when in-prox next time
 */

 input_report_abs(input, ABS_X, 0);
 input_report_abs(input, ABS_Y, 0);
 input_report_abs(input, ABS_DISTANCE, 0);
 input_report_abs(input, ABS_TILT_X, 0);
 input_report_abs(input, ABS_TILT_Y, 0);
 if (wacom->tool[idx] >= BTN_TOOL_MOUSE) {
  input_report_key(input, BTN_LEFT, 0);
  input_report_key(input, BTN_MIDDLE, 0);
  input_report_key(input, BTN_RIGHT, 0);
  input_report_key(input, BTN_SIDE, 0);
  input_report_key(input, BTN_EXTRA, 0);
  input_report_abs(input, ABS_THROTTLE, 0);
  input_report_abs(input, ABS_RZ, 0);
 } else {
  input_report_abs(input, ABS_PRESSURE, 0);
  input_report_key(input, BTN_STYLUS, 0);
  input_report_key(input, BTN_STYLUS2, 0);
  input_report_key(input, BTN_TOUCH, 0);
  input_report_abs(input, ABS_WHEEL, 0);
  if (features->type >= INTUOS3S)
   input_report_abs(input, ABS_Z, 0);
 }
 input_report_key(input, wacom->tool[idx], 0);
 input_report_abs(input, ABS_MISC, 0); /* reset tool id */
 input_event(input, EV_MSC, MSC_SERIAL, wacom->serial[idx]);
 wacom->id[idx] = 0;
}

static int wacom_intuos_inout(struct wacom_wac *wacom)
{
 struct wacom_features *features = &wacom->features;
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;
 int idx = (features->type == INTUOS) ? (data[1] & 0x01) : 0;

 if (!(((data[1] & 0xfc) == 0xc0) ||  /* in prox */
     ((data[1] & 0xfe) == 0x20) ||    /* in range */
     ((data[1] & 0xfe) == 0x80)))     /* out prox */
  return 0;

 /* Enter report */
 if ((data[1] & 0xfc) == 0xc0) {
  /* serial number of the tool */
  wacom->serial[idx] = ((__u64)(data[3] & 0x0f) << 28) +
   (data[4] << 20) + (data[5] << 12) +
   (data[6] << 4) + (data[7] >> 4);

  wacom->id[idx] = (data[2] << 4) | (data[3] >> 4) |
       ((data[7] & 0x0f) << 16) | ((data[8] & 0xf0) << 8);

  wacom->tool[idx] = wacom_intuos_get_tool_type(wacom->id[idx]);

  wacom->shared->stylus_in_proximity = true;
  return 1;
 }

 /* in Range */
 if ((data[1] & 0xfe) == 0x20) {
  if (features->type != INTUOSHT2)
   wacom->shared->stylus_in_proximity = true;

  /* in Range while exiting */
  if (wacom->reporting_data) {
   input_report_key(input, BTN_TOUCH, 0);
   input_report_abs(input, ABS_PRESSURE, 0);
   input_report_abs(input, ABS_DISTANCE, wacom->features.distance_max);
   return 2;
  }
  return 1;
 }

 /* Exit report */
 if ((data[1] & 0xfe) == 0x80) {
  wacom->shared->stylus_in_proximity = false;
  wacom->reporting_data = false;

  /* don't report exit if we don't know the ID */
  if (!wacom->id[idx])
   return 1;

  wacom_exit_report(wacom);
  return 2;
 }

 return 0;
}

static inline bool touch_is_muted(struct wacom_wac *wacom_wac)
{
 return wacom_wac->probe_complete &&
        wacom_wac->shared->has_mute_touch_switch &&
        !wacom_wac->shared->is_touch_on;
}

static inline bool report_touch_events(struct wacom_wac *wacom)
{
 return (touch_arbitration ? !wacom->shared->stylus_in_proximity : 1);
}

static inline bool delay_pen_events(struct wacom_wac *wacom)
{
 return (wacom->shared->touch_down && touch_arbitration);
}

static int wacom_intuos_general(struct wacom_wac *wacom)
{
 struct wacom_features *features = &wacom->features;
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;
 int idx = (features->type == INTUOS) ? (data[1] & 0x01) : 0;
 unsigned char type = (data[1] >> 1) & 0x0F;
 unsigned int x, y, distance, t;

 if (data[0] != WACOM_REPORT_PENABLED && data[0] != WACOM_REPORT_CINTIQ &&
  data[0] != WACOM_REPORT_INTUOS_PEN)
  return 0;

 if (delay_pen_events(wacom))
  return 1;

 /* don't report events if we don't know the tool ID */
 if (!wacom->id[idx]) {
  /* but reschedule a read of the current tool */
  wacom_intuos_schedule_prox_event(wacom);
  return 1;
 }

 /*
 * don't report events for invalid data
 */

 /* older I4 styli don't work with new Cintiqs */
 if ((!((wacom->id[idx] >> 16) & 0x01) &&
   (features->type == WACOM_21UX2)) ||
     /* Only large Intuos support Lense Cursor */
     (wacom->tool[idx] == BTN_TOOL_LENS &&
  (features->type == INTUOS3 ||
   features->type == INTUOS3S ||
   features->type == INTUOS4 ||
   features->type == INTUOS4S ||
   features->type == INTUOS5 ||
   features->type == INTUOS5S ||
   features->type == INTUOSPM ||
   features->type == INTUOSPS)) ||
    /* Cintiq doesn't send data when RDY bit isn't set */
    (features->type == CINTIQ && !(data[1] & 0x40)))
  return 1;

 x = (be16_to_cpup((__be16 *)&data[2]) << 1) | ((data[9] >> 1) & 1);
 y = (be16_to_cpup((__be16 *)&data[4]) << 1) | (data[9] & 1);
 distance = data[9] >> 2;
 if (features->type < INTUOS3S) {
  x >>= 1;
  y >>= 1;
  distance >>= 1;
 }
 if (features->type == INTUOSHT2)
  distance = features->distance_max - distance;
 input_report_abs(input, ABS_X, x);
 input_report_abs(input, ABS_Y, y);
 input_report_abs(input, ABS_DISTANCE, distance);

 switch (type) {
 case 0x00:
 case 0x01:
 case 0x02:
 case 0x03:
  /* general pen packet */
  t = (data[6] << 3) | ((data[7] & 0xC0) >> 5) | (data[1] & 1);
  if (features->pressure_max < 2047)
   t >>= 1;
  input_report_abs(input, ABS_PRESSURE, t);
  if (features->type != INTUOSHT2) {
      input_report_abs(input, ABS_TILT_X,
     (((data[7] << 1) & 0x7e) | (data[8] >> 7)) - 64);
      input_report_abs(input, ABS_TILT_Y, (data[8] & 0x7f) - 64);
  }
  input_report_key(input, BTN_STYLUS, data[1] & 2);
  input_report_key(input, BTN_STYLUS2, data[1] & 4);
  input_report_key(input, BTN_TOUCH, t > 10);
  break;

 case 0x0a:
  /* airbrush second packet */
  input_report_abs(input, ABS_WHEEL,
    (data[6] << 2) | ((data[7] >> 6) & 3));
  input_report_abs(input, ABS_TILT_X,
     (((data[7] << 1) & 0x7e) | (data[8] >> 7)) - 64);
  input_report_abs(input, ABS_TILT_Y, (data[8] & 0x7f) - 64);
  break;

 case 0x05:
  /* Rotation packet */
  if (features->type >= INTUOS3S) {
   /* I3 marker pen rotation */
   t = (data[6] << 3) | ((data[7] >> 5) & 7);
   t = (data[7] & 0x20) ? ((t > 900) ? ((t-1) / 2 - 1350) :
    ((t-1) / 2 + 450)) : (450 - t / 2) ;
   input_report_abs(input, ABS_Z, t);
  } else {
   /* 4D mouse 2nd packet */
   t = (data[6] << 3) | ((data[7] >> 5) & 7);
   input_report_abs(input, ABS_RZ, (data[7] & 0x20) ?
    ((t - 1) / 2) : -t / 2);
  }
  break;

 case 0x04:
  /* 4D mouse 1st packet */
  input_report_key(input, BTN_LEFT,   data[8] & 0x01);
  input_report_key(input, BTN_MIDDLE, data[8] & 0x02);
  input_report_key(input, BTN_RIGHT,  data[8] & 0x04);

  input_report_key(input, BTN_SIDE,   data[8] & 0x20);
  input_report_key(input, BTN_EXTRA,  data[8] & 0x10);
  t = (data[6] << 2) | ((data[7] >> 6) & 3);
  input_report_abs(input, ABS_THROTTLE, (data[8] & 0x08) ? -t : t);
  break;

 case 0x06:
  /* I4 mouse */
  input_report_key(input, BTN_LEFT,   data[6] & 0x01);
  input_report_key(input, BTN_MIDDLE, data[6] & 0x02);
  input_report_key(input, BTN_RIGHT,  data[6] & 0x04);
  input_report_rel(input, REL_WHEEL, ((data[7] & 0x80) >> 7)
     - ((data[7] & 0x40) >> 6));
  input_report_key(input, BTN_SIDE,   data[6] & 0x08);
  input_report_key(input, BTN_EXTRA,  data[6] & 0x10);

  input_report_abs(input, ABS_TILT_X,
   (((data[7] << 1) & 0x7e) | (data[8] >> 7)) - 64);
  input_report_abs(input, ABS_TILT_Y, (data[8] & 0x7f) - 64);
  break;

 case 0x08:
  if (wacom->tool[idx] == BTN_TOOL_MOUSE) {
   /* 2D mouse packet */
   input_report_key(input, BTN_LEFT,   data[8] & 0x04);
   input_report_key(input, BTN_MIDDLE, data[8] & 0x08);
   input_report_key(input, BTN_RIGHT,  data[8] & 0x10);
   input_report_rel(input, REL_WHEEL, (data[8] & 0x01)
      - ((data[8] & 0x02) >> 1));

   /* I3 2D mouse side buttons */
   if (features->type >= INTUOS3S && features->type <= INTUOS3L) {
    input_report_key(input, BTN_SIDE,   data[8] & 0x40);
    input_report_key(input, BTN_EXTRA,  data[8] & 0x20);
   }
  }
  else if (wacom->tool[idx] == BTN_TOOL_LENS) {
   /* Lens cursor packets */
   input_report_key(input, BTN_LEFT,   data[8] & 0x01);
   input_report_key(input, BTN_MIDDLE, data[8] & 0x02);
   input_report_key(input, BTN_RIGHT,  data[8] & 0x04);
   input_report_key(input, BTN_SIDE,   data[8] & 0x10);
   input_report_key(input, BTN_EXTRA,  data[8] & 0x08);
  }
  break;

 case 0x07:
 case 0x09:
 case 0x0b:
 case 0x0c:
 case 0x0d:
 case 0x0e:
 case 0x0f:
  /* unhandled */
  break;
 }

 input_report_abs(input, ABS_MISC,
    wacom_intuos_id_mangle(wacom->id[idx])); /* report tool id */
 input_report_key(input, wacom->tool[idx], 1);
 input_event(input, EV_MSC, MSC_SERIAL, wacom->serial[idx]);
 wacom->reporting_data = true;
 return 2;
}

static int wacom_intuos_irq(struct wacom_wac *wacom)
{
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;
 int result;

 if (data[0] != WACOM_REPORT_PENABLED &&
     data[0] != WACOM_REPORT_INTUOS_ID1 &&
     data[0] != WACOM_REPORT_INTUOS_ID2 &&
     data[0] != WACOM_REPORT_INTUOSPAD &&
     data[0] != WACOM_REPORT_INTUOS_PEN &&
     data[0] != WACOM_REPORT_CINTIQ &&
     data[0] != WACOM_REPORT_CINTIQPAD &&
     data[0] != WACOM_REPORT_INTUOS5PAD) {
  dev_dbg(input->dev.parent,
   "%s: received unknown report #%d\n", __func__, data[0]);
                return 0;
 }

 /* process pad events */
 result = wacom_intuos_pad(wacom);
 if (result)
  return result;

 /* process in/out prox events */
 result = wacom_intuos_inout(wacom);
 if (result)
  return result - 1;

 /* process general packets */
 result = wacom_intuos_general(wacom);
 if (result)
  return result - 1;

 return 0;
}

static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len)
{
 unsigned char *data = wacom_wac->data;
 struct input_dev *input;
 struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
 struct wacom_remote *remote = wacom->remote;
 int bat_charging, bat_percent, touch_ring_mode;
 __u32 serial;
 int i, index = -1;
 unsigned long flags;

 if (data[0] != WACOM_REPORT_REMOTE) {
  hid_dbg(wacom->hdev, "%s: received unknown report #%d",
   __func__, data[0]);
  return 0;
 }

 serial = data[3] + (data[4] << 8) + (data[5] << 16);
 wacom_wac->id[0] = PAD_DEVICE_ID;

 spin_lock_irqsave(&remote->remote_lock, flags);

 for (i = 0; i < WACOM_MAX_REMOTES; i++) {
  if (remote->remotes[i].serial == serial) {
   index = i;
   break;
  }
 }

 if (index < 0 || !remote->remotes[index].registered)
  goto out;

 remote->remotes[i].active_time = ktime_get();
 input = remote->remotes[index].input;

 input_report_key(input, BTN_0, (data[9] & 0x01));
 input_report_key(input, BTN_1, (data[9] & 0x02));
 input_report_key(input, BTN_2, (data[9] & 0x04));
 input_report_key(input, BTN_3, (data[9] & 0x08));
 input_report_key(input, BTN_4, (data[9] & 0x10));
 input_report_key(input, BTN_5, (data[9] & 0x20));
 input_report_key(input, BTN_6, (data[9] & 0x40));
 input_report_key(input, BTN_7, (data[9] & 0x80));

 input_report_key(input, BTN_8, (data[10] & 0x01));
 input_report_key(input, BTN_9, (data[10] & 0x02));
 input_report_key(input, BTN_A, (data[10] & 0x04));
 input_report_key(input, BTN_B, (data[10] & 0x08));
 input_report_key(input, BTN_C, (data[10] & 0x10));
 input_report_key(input, BTN_X, (data[10] & 0x20));
 input_report_key(input, BTN_Y, (data[10] & 0x40));
 input_report_key(input, BTN_Z, (data[10] & 0x80));

 input_report_key(input, BTN_BASE, (data[11] & 0x01));
 input_report_key(input, BTN_BASE2, (data[11] & 0x02));

 if (data[12] & 0x80)
  input_report_abs(input, ABS_WHEEL, (data[12] & 0x7f) - 1);
 else
  input_report_abs(input, ABS_WHEEL, 0);

 bat_percent = data[7] & 0x7f;
 bat_charging = !!(data[7] & 0x80);

 if (data[9] | data[10] | (data[11] & 0x03) | data[12])
  input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
 else
  input_report_abs(input, ABS_MISC, 0);

 input_event(input, EV_MSC, MSC_SERIAL, serial);

 input_sync(input);

 /*Which mode select (LED light) is currently on?*/
 touch_ring_mode = (data[11] & 0xC0) >> 6;

 for (i = 0; i < WACOM_MAX_REMOTES; i++) {
  if (remote->remotes[i].serial == serial)
   wacom->led.groups[i].select = touch_ring_mode;
 }

 __wacom_notify_battery(&remote->remotes[index].battery,
    WACOM_POWER_SUPPLY_STATUS_AUTO, bat_percent,
    bat_charging, 1, bat_charging);

out:
 spin_unlock_irqrestore(&remote->remote_lock, flags);
 return 0;
}

static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len)
{
 struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
 unsigned char *data = wacom_wac->data;
 struct wacom_remote *remote = wacom->remote;
 struct wacom_remote_work_data remote_data;
 unsigned long flags;
 int i, ret;

 if (data[0] != WACOM_REPORT_DEVICE_LIST)
  return;

 memset(&remote_data, 0, sizeof(struct wacom_remote_work_data));

 for (i = 0; i < WACOM_MAX_REMOTES; i++) {
  int j = i * 6;
  int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4];

  remote_data.remote[i].serial = serial;
 }

 spin_lock_irqsave(&remote->remote_lock, flags);

 ret = kfifo_in(&remote->remote_fifo, &remote_data, sizeof(remote_data));
 if (ret != sizeof(remote_data)) {
  spin_unlock_irqrestore(&remote->remote_lock, flags);
  hid_err(wacom->hdev, "Can't queue Remote status event.\n");
  return;
 }

 spin_unlock_irqrestore(&remote->remote_lock, flags);

 wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE);
}

static int int_dist(int x1, int y1, int x2, int y2)
{
 int x = x2 - x1;
 int y = y2 - y1;

 return int_sqrt(x*x + y*y);
}

static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
  unsigned char *data)
{
 memcpy(wacom->data, data, 10);
 wacom_intuos_irq(wacom);

 input_sync(wacom->pen_input);
 if (wacom->pad_input)
  input_sync(wacom->pad_input);
}

static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
{
 u8 *data = kmemdup(wacom->data, len, GFP_KERNEL);
 int i = 1;
 unsigned power_raw, battery_capacity, bat_charging, ps_connected;

 switch (data[0]) {
 case 0x04:
  wacom_intuos_bt_process_data(wacom, data + i);
  i += 10;
  fallthrough;
 case 0x03:
  wacom_intuos_bt_process_data(wacom, data + i);
  i += 10;
  wacom_intuos_bt_process_data(wacom, data + i);
  i += 10;
  power_raw = data[i];
  bat_charging = (power_raw & 0x08) ? 1 : 0;
  ps_connected = (power_raw & 0x10) ? 1 : 0;
  battery_capacity = batcap_i4[power_raw & 0x07];
  wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
         battery_capacity, bat_charging,
         battery_capacity || bat_charging,
         ps_connected);
  break;
 default:
  dev_dbg(wacom->pen_input->dev.parent,
    "Unknown report: %d,%d size:%zu\n",
    data[0], data[1], len);
  break;
 }

 kfree(data);
 return 0;
}

static int wacom_wac_finger_count_touches(struct wacom_wac *wacom)
{
 struct input_dev *input = wacom->touch_input;
 unsigned touch_max = wacom->features.touch_max;
 int count = 0;
 int i;

 if (!touch_max)
  return 0;

 if (touch_max == 1)
  return test_bit(BTN_TOUCH, input->key) &&
   report_touch_events(wacom);

 for (i = 0; i < input->mt->num_slots; i++) {
  struct input_mt_slot *ps = &input->mt->slots[i];
  int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
  if (id >= 0)
   count++;
 }

 return count;
}

static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
{
 int pen_frame_len, pen_frames;

 struct input_dev *pen_input = wacom->pen_input;
 unsigned char *data = wacom->data;
 int number_of_valid_frames = 0;
 ktime_t time_interval = 15000000;
 ktime_t time_packet_received = ktime_get();
 int i;

 if (wacom->features.type == INTUOSP2_BT ||
     wacom->features.type == INTUOSP2S_BT) {
  wacom->serial[0] = get_unaligned_le64(&data[99]);
  wacom->id[0]     = get_unaligned_le16(&data[107]);
  pen_frame_len = 14;
  pen_frames = 7;
 } else {
  wacom->serial[0] = get_unaligned_le64(&data[33]);
  wacom->id[0]     = get_unaligned_le16(&data[41]);
  pen_frame_len = 8;
  pen_frames = 4;
 }

 if (wacom->serial[0] >> 52 == 1) {
  /* Add back in missing bits of ID for non-USI pens */
  wacom->id[0] |= (wacom->serial[0] >> 32) & 0xFFFFF;
 }

 /* number of valid frames */
 for (i = 0; i < pen_frames; i++) {
  unsigned char *frame = &data[i*pen_frame_len + 1];
  bool valid = frame[0] & 0x80;

  if (valid)
   number_of_valid_frames++;
 }

 if (number_of_valid_frames) {
  if (wacom->hid_data.time_delayed)
   time_interval = ktime_get() - wacom->hid_data.time_delayed;
  time_interval = div_u64(time_interval, number_of_valid_frames);
  wacom->hid_data.time_delayed = time_packet_received;
 }

 for (i = 0; i < number_of_valid_frames; i++) {
  unsigned char *frame = &data[i*pen_frame_len + 1];
  bool valid = frame[0] & 0x80;
  bool prox = frame[0] & 0x40;
  bool range = frame[0] & 0x20;
  bool invert = frame[0] & 0x10;
  int frames_number_reversed = number_of_valid_frames - i - 1;
  ktime_t event_timestamp = time_packet_received - frames_number_reversed * time_interval;

  if (!valid)
   continue;

  if (!prox) {
   wacom->shared->stylus_in_proximity = false;
   wacom_exit_report(wacom);
   input_sync(pen_input);

   wacom->tool[0] = 0;
   wacom->id[0] = 0;
   wacom->serial[0] = 0;
   wacom->hid_data.time_delayed = 0;
   return;
  }

  if (range) {
   if (!wacom->tool[0]) { /* first in range */
    /* Going into range select tool */
    if (invert)
     wacom->tool[0] = BTN_TOOL_RUBBER;
    else if (wacom->id[0])
     wacom->tool[0] = wacom_intuos_get_tool_type(wacom->id[0]);
    else
     wacom->tool[0] = BTN_TOOL_PEN;
   }

   input_report_abs(pen_input, ABS_X, get_unaligned_le16(&frame[1]));
   input_report_abs(pen_input, ABS_Y, get_unaligned_le16(&frame[3]));

   if (wacom->features.type == INTUOSP2_BT ||
       wacom->features.type == INTUOSP2S_BT) {
    /* Fix rotation alignment: userspace expects zero at left */
    int16_t rotation =
     (int16_t)get_unaligned_le16(&frame[9]);
    rotation += 1800/4;

    if (rotation > 899)
     rotation -= 1800;

    input_report_abs(pen_input, ABS_TILT_X,
       (signed char)frame[7]);
    input_report_abs(pen_input, ABS_TILT_Y,
       (signed char)frame[8]);
    input_report_abs(pen_input, ABS_Z, rotation);
    input_report_abs(pen_input, ABS_WHEEL,
       get_unaligned_le16(&frame[11]));
   }
  }

  if (wacom->tool[0]) {
   input_report_abs(pen_input, ABS_PRESSURE, get_unaligned_le16(&frame[5]));
   if (wacom->features.type == INTUOSP2_BT ||
       wacom->features.type == INTUOSP2S_BT) {
    input_report_abs(pen_input, ABS_DISTANCE,
       range ? frame[13] : wacom->features.distance_max);
   } else {
    input_report_abs(pen_input, ABS_DISTANCE,
       range ? frame[7] : wacom->features.distance_max);
   }

   input_report_key(pen_input, BTN_TOUCH, frame[0] & 0x09);
   input_report_key(pen_input, BTN_STYLUS, frame[0] & 0x02);
   input_report_key(pen_input, BTN_STYLUS2, frame[0] & 0x04);

   input_report_key(pen_input, wacom->tool[0], prox);
   input_event(pen_input, EV_MSC, MSC_SERIAL, wacom->serial[0]);
   input_report_abs(pen_input, ABS_MISC,
      wacom_intuos_id_mangle(wacom->id[0])); /* report tool id */
  }

  wacom->shared->stylus_in_proximity = prox;

  /* add timestamp to unpack the frames */
  input_set_timestamp(pen_input, event_timestamp);

  input_sync(pen_input);
 }
}

static void wacom_intuos_pro2_bt_touch(struct wacom_wac *wacom)
{
 const int finger_touch_len = 8;
 const int finger_frames = 4;
 const int finger_frame_len = 43;

 struct input_dev *touch_input = wacom->touch_input;
 unsigned char *data = wacom->data;
 int num_contacts_left = 5;
 int i, j;

 for (i = 0; i < finger_frames; i++) {
  unsigned char *frame = &data[i*finger_frame_len + 109];
  int current_num_contacts = frame[0] & 0x7F;
  int contacts_to_send;

  if (!(frame[0] & 0x80))
   continue;

  /*
 * First packet resets the counter since only the first
 * packet in series will have non-zero current_num_contacts.
 */

  if (current_num_contacts)
   wacom->num_contacts_left = current_num_contacts;

  contacts_to_send = min(num_contacts_left, wacom->num_contacts_left);

  for (j = 0; j < contacts_to_send; j++) {
   unsigned char *touch = &frame[j*finger_touch_len + 1];
   int slot = input_mt_get_slot_by_key(touch_input, touch[0]);
   int x = get_unaligned_le16(&touch[2]);
   int y = get_unaligned_le16(&touch[4]);
   int w = touch[6] * input_abs_get_res(touch_input, ABS_MT_POSITION_X);
   int h = touch[7] * input_abs_get_res(touch_input, ABS_MT_POSITION_Y);

   if (slot < 0)
    continue;

   input_mt_slot(touch_input, slot);
   input_mt_report_slot_state(touch_input, MT_TOOL_FINGER, touch[1] & 0x01);
   input_report_abs(touch_input, ABS_MT_POSITION_X, x);
   input_report_abs(touch_input, ABS_MT_POSITION_Y, y);
   input_report_abs(touch_input, ABS_MT_TOUCH_MAJOR, max(w, h));
   input_report_abs(touch_input, ABS_MT_TOUCH_MINOR, min(w, h));
   input_report_abs(touch_input, ABS_MT_ORIENTATION, w > h);
  }

  input_mt_sync_frame(touch_input);

  wacom->num_contacts_left -= contacts_to_send;
  if (wacom->num_contacts_left <= 0) {
   wacom->num_contacts_left = 0;
   wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
   input_sync(touch_input);
  }
 }

 if (wacom->num_contacts_left == 0) {
  // Be careful that we don't accidentally call input_sync with
  // only a partial set of fingers of processed
  input_report_switch(touch_input, SW_MUTE_DEVICE, !(data[281] >> 7));
  input_sync(touch_input);
 }

}

static void wacom_intuos_pro2_bt_pad(struct wacom_wac *wacom)
{
 struct input_dev *pad_input = wacom->pad_input;
 unsigned char *data = wacom->data;
 int nbuttons = wacom->features.numbered_buttons;

 int expresskeys = data[282];
 int center = (data[281] & 0x40) >> 6;
 int ring = data[285] & 0x7F;
 bool ringstatus = data[285] & 0x80;
 bool prox = expresskeys || center || ringstatus;

 /* Fix touchring data: userspace expects 0 at left and increasing clockwise */
 ring = 71 - ring;
 ring += 3*72/16;
 if (ring > 71)
  ring -= 72;

 wacom_report_numbered_buttons(pad_input, nbuttons,
                                      expresskeys | (center << (nbuttons - 1)));

 input_report_abs(pad_input, ABS_WHEEL, ringstatus ? ring : 0);

 input_report_key(pad_input, wacom->tool[1], prox ? 1 : 0);
 input_report_abs(pad_input, ABS_MISC, prox ? PAD_DEVICE_ID : 0);
 input_event(pad_input, EV_MSC, MSC_SERIAL, 0xffffffff);

 input_sync(pad_input);
}

static void wacom_intuos_pro2_bt_battery(struct wacom_wac *wacom)
{
 unsigned char *data = wacom->data;

 bool chg = data[284] & 0x80;
 int battery_status = data[284] & 0x7F;

 wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
        battery_status, chg, 1, chg);
}

static void wacom_intuos_gen3_bt_pad(struct wacom_wac *wacom)
{
 struct input_dev *pad_input = wacom->pad_input;
 unsigned char *data = wacom->data;

 int buttons = data[44];

 wacom_report_numbered_buttons(pad_input, 4, buttons);

 input_report_key(pad_input, wacom->tool[1], buttons ? 1 : 0);
 input_report_abs(pad_input, ABS_MISC, buttons ? PAD_DEVICE_ID : 0);
 input_event(pad_input, EV_MSC, MSC_SERIAL, 0xffffffff);

 input_sync(pad_input);
}

static void wacom_intuos_gen3_bt_battery(struct wacom_wac *wacom)
{
 unsigned char *data = wacom->data;

 bool chg = data[45] & 0x80;
 int battery_status = data[45] & 0x7F;

 wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
        battery_status, chg, 1, chg);
}

static int wacom_intuos_pro2_bt_irq(struct wacom_wac *wacom, size_t len)
{
 unsigned char *data = wacom->data;

 if (data[0] != 0x80 && data[0] != 0x81) {
  dev_dbg(wacom->pen_input->dev.parent,
   "%s: received unknown report #%d\n", __func__, data[0]);
  return 0;
 }

 wacom_intuos_pro2_bt_pen(wacom);
 if (wacom->features.type == INTUOSP2_BT ||
     wacom->features.type == INTUOSP2S_BT) {
  wacom_intuos_pro2_bt_touch(wacom);
  wacom_intuos_pro2_bt_pad(wacom);
  wacom_intuos_pro2_bt_battery(wacom);
 } else {
  wacom_intuos_gen3_bt_pad(wacom);
  wacom_intuos_gen3_bt_battery(wacom);
 }
 return 0;
}

static int wacom_24hdt_irq(struct wacom_wac *wacom)
{
 struct input_dev *input = wacom->touch_input;
 unsigned char *data = wacom->data;
 int i;
 int current_num_contacts = data[61];
 int contacts_to_send = 0;
 int num_contacts_left = 4; /* maximum contacts per packet */
 int byte_per_packet = WACOM_BYTES_PER_24HDT_PACKET;
 int y_offset = 2;

 if (touch_is_muted(wacom) && !wacom->shared->touch_down)
  return 0;

 if (wacom->features.type == WACOM_27QHDT) {
  current_num_contacts = data[63];
  num_contacts_left = 10;
  byte_per_packet = WACOM_BYTES_PER_QHDTHID_PACKET;
  y_offset = 0;
 }

 /*
 * First packet resets the counter since only the first
 * packet in series will have non-zero current_num_contacts.
 */

 if (current_num_contacts)
  wacom->num_contacts_left = current_num_contacts;

 contacts_to_send = min(num_contacts_left, wacom->num_contacts_left);

 for (i = 0; i < contacts_to_send; i++) {
  int offset = (byte_per_packet * i) + 1;
  bool touch = (data[offset] & 0x1) && report_touch_events(wacom);
  int slot = input_mt_get_slot_by_key(input, data[offset + 1]);

  if (slot < 0)
   continue;
  input_mt_slot(input, slot);
  input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);

  if (touch) {
   int t_x = get_unaligned_le16(&data[offset + 2]);
   int t_y = get_unaligned_le16(&data[offset + 4 + y_offset]);

   input_report_abs(input, ABS_MT_POSITION_X, t_x);
   input_report_abs(input, ABS_MT_POSITION_Y, t_y);

   if (wacom->features.type != WACOM_27QHDT) {
    int c_x = get_unaligned_le16(&data[offset + 4]);
    int c_y = get_unaligned_le16(&data[offset + 8]);
    int w = get_unaligned_le16(&data[offset + 10]);
    int h = get_unaligned_le16(&data[offset + 12]);

    input_report_abs(input, ABS_MT_TOUCH_MAJOR, min(w,h));
    input_report_abs(input, ABS_MT_WIDTH_MAJOR,
       min(w, h) + int_dist(t_x, t_y, c_x, c_y));
    input_report_abs(input, ABS_MT_WIDTH_MINOR, min(w, h));
    input_report_abs(input, ABS_MT_ORIENTATION, w > h);
   }
  }
 }
 input_mt_sync_frame(input);

 wacom->num_contacts_left -= contacts_to_send;
 if (wacom->num_contacts_left <= 0) {
  wacom->num_contacts_left = 0;
  wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
 }
 return 1;
}

static int wacom_mt_touch(struct wacom_wac *wacom)
{
 struct input_dev *input = wacom->touch_input;
 unsigned char *data = wacom->data;
 int i;
 int current_num_contacts = data[2];
 int contacts_to_send = 0;
 int x_offset = 0;

 /* MTTPC does not support Height and Width */
 if (wacom->features.type == MTTPC || wacom->features.type == MTTPC_B)
  x_offset = -4;

 /*
 * First packet resets the counter since only the first
 * packet in series will have non-zero current_num_contacts.
 */

 if (current_num_contacts)
  wacom->num_contacts_left = current_num_contacts;

 /* There are at most 5 contacts per packet */
 contacts_to_send = min(5, wacom->num_contacts_left);

 for (i = 0; i < contacts_to_send; i++) {
  int offset = (WACOM_BYTES_PER_MT_PACKET + x_offset) * i + 3;
  bool touch = (data[offset] & 0x1) && report_touch_events(wacom);
  int id = get_unaligned_le16(&data[offset + 1]);
  int slot = input_mt_get_slot_by_key(input, id);

  if (slot < 0)
   continue;

  input_mt_slot(input, slot);
  input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
  if (touch) {
   int x = get_unaligned_le16(&data[offset + x_offset + 7]);
   int y = get_unaligned_le16(&data[offset + x_offset + 9]);
   input_report_abs(input, ABS_MT_POSITION_X, x);
   input_report_abs(input, ABS_MT_POSITION_Y, y);
  }
 }
 input_mt_sync_frame(input);

 wacom->num_contacts_left -= contacts_to_send;
 if (wacom->num_contacts_left <= 0) {
  wacom->num_contacts_left = 0;
  wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
 }
 return 1;
}

static int wacom_tpc_mt_touch(struct wacom_wac *wacom)
{
 struct input_dev *input = wacom->touch_input;
 unsigned char *data = wacom->data;
 int i;

 for (i = 0; i < 2; i++) {
  int p = data[1] & (1 << i);
  bool touch = p && report_touch_events(wacom);

  input_mt_slot(input, i);
  input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
  if (touch) {
   int x = le16_to_cpup((__le16 *)&data[i * 2 + 2]) & 0x7fff;
   int y = le16_to_cpup((__le16 *)&data[i * 2 + 6]) & 0x7fff;

   input_report_abs(input, ABS_MT_POSITION_X, x);
   input_report_abs(input, ABS_MT_POSITION_Y, y);
  }
 }
 input_mt_sync_frame(input);

 /* keep touch state for pen event */
 wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);

 return 1;
}

static int wacom_tpc_single_touch(struct wacom_wac *wacom, size_t len)
{
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->touch_input;
 bool prox = report_touch_events(wacom);
 int x = 0, y = 0;

 if (wacom->features.touch_max > 1 || len > WACOM_PKGLEN_TPC2FG)
  return 0;

 if (len == WACOM_PKGLEN_TPC1FG) {
  prox = prox && (data[0] & 0x01);
  x = get_unaligned_le16(&data[1]);
  y = get_unaligned_le16(&data[3]);
 } else if (len == WACOM_PKGLEN_TPC1FG_B) {
  prox = prox && (data[2] & 0x01);
  x = get_unaligned_le16(&data[3]);
  y = get_unaligned_le16(&data[5]);
 } else {
  prox = prox && (data[1] & 0x01);
  x = le16_to_cpup((__le16 *)&data[2]);
  y = le16_to_cpup((__le16 *)&data[4]);
 }

 if (prox) {
  input_report_abs(input, ABS_X, x);
  input_report_abs(input, ABS_Y, y);
 }
 input_report_key(input, BTN_TOUCH, prox);

 /* keep touch state for pen events */
 wacom->shared->touch_down = prox;

 return 1;
}

static int wacom_tpc_pen(struct wacom_wac *wacom)
{
 unsigned char *data = wacom->data;
 struct input_dev *input = wacom->pen_input;
 bool prox = data[1] & 0x20;

 if (!wacom->shared->stylus_in_proximity) /* first in prox */
  /* Going into proximity select tool */
  wacom->tool[0] = (data[1] & 0x0c) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;

 /* keep pen state for touch events */
 wacom->shared->stylus_in_proximity = prox;

 /* send pen events only when touch is up or forced out
 * or touch arbitration is off
 */

 if (!delay_pen_events(wacom)) {
  input_report_key(input, BTN_STYLUS, data[1] & 0x02);
  input_report_key(input, BTN_STYLUS2, data[1] & 0x10);
  input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2]));
  input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4]));
  input_report_abs(input, ABS_PRESSURE, ((data[7] & 0x07) << 8) | data[6]);
  input_report_key(input, BTN_TOUCH, data[1] & 0x05);
  input_report_key(input, wacom->tool[0], prox);
  return 1;
 }

 return 0;
}

static int wacom_tpc_irq(struct wacom_wac *wacom, size_t len)
{
 unsigned char *data = wacom->data;

 if (wacom->pen_input) {
  dev_dbg(wacom->pen_input->dev.parent,
   "%s: received report #%d\n", __func__, data[0]);

  if (len == WACOM_PKGLEN_PENABLED ||
      data[0] == WACOM_REPORT_PENABLED)
   return wacom_tpc_pen(wacom);
 }
 else if (wacom->touch_input) {
  dev_dbg(wacom->touch_input->dev.parent,
   "%s: received report #%d\n", __func__, data[0]);

  switch (len) {
  case WACOM_PKGLEN_TPC1FG:
   return wacom_tpc_single_touch(wacom, len);

  case WACOM_PKGLEN_TPC2FG:
   return wacom_tpc_mt_touch(wacom);

  default:
   switch (data[0]) {
   case WACOM_REPORT_TPC1FG:
   case WACOM_REPORT_TPCHID:
   case WACOM_REPORT_TPCST:
   case WACOM_REPORT_TPC1FGE:
    return wacom_tpc_single_touch(wacom, len);

   case WACOM_REPORT_TPCMT:
   case WACOM_REPORT_TPCMT2:
    return wacom_mt_touch(wacom);

   }
  }
 }

 return 0;
}

static int wacom_offset_rotation(struct input_dev *input, struct hid_usage *usage,
     int value, int num, int denom)
{
 struct input_absinfo *abs = &input->absinfo[usage->code];
 int range = (abs->maximum - abs->minimum + 1);

 value += num*range/denom;
 if (value > abs->maximum)
  value -= range;
 else if (value < abs->minimum)
  value += range;
 return value;
}

int wacom_equivalent_usage(int usage)
{
 if ((usage & HID_USAGE_PAGE) == WACOM_HID_UP_WACOMDIGITIZER) {
  int subpage = (usage & 0xFF00) << 8;
  int subusage = (usage & 0xFF);

  if (subpage == WACOM_HID_SP_PAD ||
      subpage == WACOM_HID_SP_BUTTON ||
      subpage == WACOM_HID_SP_DIGITIZER ||
      subpage == WACOM_HID_SP_DIGITIZERINFO ||
      usage == WACOM_HID_WD_SENSE ||
      usage == WACOM_HID_WD_SERIALHI ||
      usage == WACOM_HID_WD_TOOLTYPE ||
      usage == WACOM_HID_WD_DISTANCE ||
      usage == WACOM_HID_WD_TOUCHSTRIP ||
      usage == WACOM_HID_WD_TOUCHSTRIP2 ||
      usage == WACOM_HID_WD_TOUCHRING ||
      usage == WACOM_HID_WD_TOUCHRINGSTATUS ||
      usage == WACOM_HID_WD_REPORT_VALID ||
      usage == WACOM_HID_WD_BARRELSWITCH3 ||
      usage == WACOM_HID_WD_SEQUENCENUMBER) {
   return usage;
  }

  if (subpage == HID_UP_UNDEFINED)
   subpage = HID_UP_DIGITIZER;

  return subpage | subusage;
 }

 if ((usage & HID_USAGE_PAGE) == WACOM_HID_UP_WACOMTOUCH) {
  int subpage = (usage & 0xFF00) << 8;
  int subusage = (usage & 0xFF);

  if (usage == WACOM_HID_WT_REPORT_VALID)
   return usage;

  if (subpage == HID_UP_UNDEFINED)
   subpage = WACOM_HID_SP_DIGITIZER;

  return subpage | subusage;
 }

 return usage;
}

static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
  struct hid_field *field, __u8 type, __u16 code, int fuzz)
{
 struct wacom *wacom = input_get_drvdata(input);
 struct wacom_wac *wacom_wac = &wacom->wacom_wac;
 struct wacom_features *features = &wacom_wac->features;
 int fmin = field->logical_minimum;
 int fmax = field->logical_maximum;
 unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid);
 int resolution_code = code;
 int resolution;

 if (equivalent_usage == HID_DG_TWIST) {
  resolution_code = ABS_RZ;
 }

 resolution = hidinput_calc_abs_res(field, resolution_code);

 if (equivalent_usage == HID_GD_X) {
  fmin += features->offset_left;
  fmax -= features->offset_right;
 }
 if (equivalent_usage == HID_GD_Y) {
  fmin += features->offset_top;
  fmax -= features->offset_bottom;
 }

 usage->type = type;
 usage->code = code;

 switch (type) {
 case EV_ABS:
  input_set_abs_params(input, code, fmin, fmax, fuzz, 0);

  /* older tablet may miss physical usage */
  if ((code == ABS_X || code == ABS_Y) && !resolution) {
   resolution = WACOM_INTUOS_RES;
   hid_warn(input,
     "Using default resolution for axis type 0x%x code 0x%x\n",
     type, code);
  }
  input_abs_set_res(input, code, resolution);
  break;
 case EV_REL:
 case EV_KEY:
 case EV_MSC:
 case EV_SW:
  input_set_capability(input, type, code);
  break;
 }
}

static void wacom_wac_battery_usage_mapping(struct hid_device *hdev,
  struct hid_field *field, struct hid_usage *usage)
{
 return;
}

static void wacom_wac_battery_event(struct hid_device *hdev, struct hid_field *field,
  struct hid_usage *usage, __s32 value)
{
 struct wacom *wacom = hid_get_drvdata(hdev);
 struct wacom_wac *wacom_wac = &wacom->wacom_wac;
 unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);

 switch (equivalent_usage) {
 case HID_DG_BATTERYSTRENGTH:
  if (value == 0) {
   wacom_wac->hid_data.bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
  }
  else {
   value = value * 100 / (field->logical_maximum - field->logical_minimum);
   wacom_wac->hid_data.battery_capacity = value;
   wacom_wac->hid_data.bat_connected = 1;
   wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
  }
  wacom_wac->features.quirks |= WACOM_QUIRK_BATTERY;
  break;
 case WACOM_HID_WD_BATTERY_LEVEL:
  value = value * 100 / (field->logical_maximum - field->logical_minimum);
  wacom_wac->hid_data.battery_capacity = value;
  wacom_wac->hid_data.bat_connected = 1;
  wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
  wacom_wac->features.quirks |= WACOM_QUIRK_BATTERY;
  break;
 case WACOM_HID_WD_BATTERY_CHARGING:
  wacom_wac->hid_data.bat_charging = value;
  wacom_wac->hid_data.ps_connected = value;
  wacom_wac->hid_data.bat_connected = 1;
  wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
  wacom_wac->features.quirks |= WACOM_QUIRK_BATTERY;
  break;
 }
}

static void wacom_wac_battery_pre_report(struct hid_device *hdev,
  struct hid_report *report)
{
 return;
}

static void wacom_wac_battery_report(struct hid_device *hdev,
  struct hid_report *report)
{
 struct wacom *wacom = hid_get_drvdata(hdev);
 struct wacom_wac *wacom_wac = &wacom->wacom_wac;

 int status = wacom_wac->hid_data.bat_status;
 int capacity = wacom_wac->hid_data.battery_capacity;
 bool charging = wacom_wac->hid_data.bat_charging;
 bool connected = wacom_wac->hid_data.bat_connected;
 bool powered = wacom_wac->hid_data.ps_connected;

 wacom_notify_battery(wacom_wac, status, capacity, charging,
        connected, powered);
}

static void wacom_wac_pad_usage_mapping(struct hid_device *hdev,
  struct hid_field *field, struct hid_usage *usage)
{
 struct wacom *wacom = hid_get_drvdata(hdev);
 struct wacom_wac *wacom_wac = &wacom->wacom_wac;
 struct wacom_features *features = &wacom_wac->features;
 struct input_dev *input = wacom_wac->pad_input;
 unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);

 switch (equivalent_usage) {
 case WACOM_HID_WD_ACCELEROMETER_X:
  __set_bit(INPUT_PROP_ACCELEROMETER, input->propbit);
  wacom_map_usage(input, usage, field, EV_ABS, ABS_X, 0);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_ACCELEROMETER_Y:
  __set_bit(INPUT_PROP_ACCELEROMETER, input->propbit);
  wacom_map_usage(input, usage, field, EV_ABS, ABS_Y, 0);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_ACCELEROMETER_Z:
  __set_bit(INPUT_PROP_ACCELEROMETER, input->propbit);
  wacom_map_usage(input, usage, field, EV_ABS, ABS_Z, 0);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_BUTTONCENTER:
 case WACOM_HID_WD_BUTTONHOME:
 case WACOM_HID_WD_BUTTONUP:
 case WACOM_HID_WD_BUTTONDOWN:
 case WACOM_HID_WD_BUTTONLEFT:
 case WACOM_HID_WD_BUTTONRIGHT:
  wacom_map_usage(input, usage, field, EV_KEY,
    wacom_numbered_button_to_key(features->numbered_buttons),
    0);
  features->numbered_buttons++;
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_MUTE_DEVICE:
  /* softkey touch switch */
  wacom_wac->is_soft_touch_switch = true;
  fallthrough;
 case WACOM_HID_WD_TOUCHONOFF:
  /*
 * These two usages, which are used to mute touch events, come
 * from the pad packet, but are reported on the touch
 * interface. Because the touch interface may not have
 * been created yet, we cannot call wacom_map_usage(). In
 * order to process the usages when we receive them, we set
 * the usage type and code directly.
 */

  wacom_wac->has_mute_touch_switch = true;
  usage->type = EV_SW;
  usage->code = SW_MUTE_DEVICE;
  break;
 case WACOM_HID_WD_TOUCHSTRIP:
  wacom_map_usage(input, usage, field, EV_ABS, ABS_RX, 0);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_TOUCHSTRIP2:
  wacom_map_usage(input, usage, field, EV_ABS, ABS_RY, 0);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_TOUCHRING:
  if (field->flags & HID_MAIN_ITEM_RELATIVE) {
   wacom_wac->relring_count++;
   if (wacom_wac->relring_count == 1) {
    wacom_map_usage(input, usage, field, EV_REL, REL_WHEEL_HI_RES, 0);
    set_bit(REL_WHEEL, input->relbit);
   }
   else if (wacom_wac->relring_count == 2) {
    wacom_map_usage(input, usage, field, EV_REL, REL_HWHEEL_HI_RES, 0);
    set_bit(REL_HWHEEL, input->relbit);
   }
  } else {
   wacom_wac->absring_count++;
   if (wacom_wac->absring_count == 1)
    wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0);
   else if (wacom_wac->absring_count == 2)
    wacom_map_usage(input, usage, field, EV_ABS, ABS_THROTTLE, 0);
  }
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_TOUCHRINGSTATUS:
  /*
 * Only set up type/code association. Completely mapping
 * this usage may overwrite the axis resolution and range.
 */

  usage->type = EV_ABS;
  usage->code = ABS_WHEEL;
  set_bit(EV_ABS, input->evbit);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_BUTTONCONFIG:
  wacom_map_usage(input, usage, field, EV_KEY, KEY_BUTTONCONFIG, 0);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_ONSCREEN_KEYBOARD:
  wacom_map_usage(input, usage, field, EV_KEY, KEY_ONSCREEN_KEYBOARD, 0);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_CONTROLPANEL:
  wacom_map_usage(input, usage, field, EV_KEY, KEY_CONTROLPANEL, 0);
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 case WACOM_HID_WD_MODE_CHANGE:
  /* do not overwrite previous data */
  if (!wacom_wac->has_mode_change) {
   wacom_wac->has_mode_change = true;
   wacom_wac->is_direct_mode = true;
  }
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 }

 switch (equivalent_usage & 0xfffffff0) {
 case WACOM_HID_WD_EXPRESSKEY00:
  wacom_map_usage(input, usage, field, EV_KEY,
    wacom_numbered_button_to_key(features->numbered_buttons),
    0);
  features->numbered_buttons++;
  features->device_type |= WACOM_DEVICETYPE_PAD;
  break;
 }
}

static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field,
  struct hid_usage *usage, __s32 value)
{
 struct wacom *wacom = hid_get_drvdata(hdev);
 struct wacom_wac *wacom_wac = &wacom->wacom_wac;
 struct input_dev *input = wacom_wac->pad_input;
 struct wacom_features *features = &wacom_wac->features;
 unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
 int i;
 bool do_report = false;

 /*
 * Avoid reporting this event and setting inrange_state if this usage
 * hasn't been mapped.
 */

 if (!usage->type && equivalent_usage != WACOM_HID_WD_MODE_CHANGE)
  return;

 if (wacom_equivalent_usage(field->physical) == HID_DG_TABLETFUNCTIONKEY) {
  bool is_abs_touchring = usage->hid == WACOM_HID_WD_TOUCHRING &&
     !(field->flags & HID_MAIN_ITEM_RELATIVE);

  if (!is_abs_touchring)
   wacom_wac->hid_data.inrange_state |= value;
 }

 /* Process touch switch state first since it is reported through touch interface,
 * which is indepentent of pad interface. In the case when there are no other pad
 * events, the pad interface will not even be created.
 */

 if ((equivalent_usage == WACOM_HID_WD_MUTE_DEVICE) ||
    (equivalent_usage == WACOM_HID_WD_TOUCHONOFF)) {
  if (wacom_wac->shared->touch_input) {
   bool *is_touch_on = &wacom_wac->shared->is_touch_on;

   if (equivalent_usage == WACOM_HID_WD_MUTE_DEVICE && value)
    *is_touch_on = !(*is_touch_on);
   else if (equivalent_usage == WACOM_HID_WD_TOUCHONOFF)
    *is_touch_on = value;

   input_report_switch(wacom_wac->shared->touch_input,
         SW_MUTE_DEVICE, !(*is_touch_on));
   input_sync(wacom_wac->shared->touch_input);
  }
  return;
 }

 if (!input)
  return;

 switch (equivalent_usage) {
 case WACOM_HID_WD_TOUCHRING:
  /*
 * Userspace expects touchrings to increase in value with
 * clockwise gestures and have their zero point at the
 * tablet's left. HID events "should" be clockwise-
 * increasing and zero at top, though the MobileStudio
 * Pro and 2nd-gen Intuos Pro don't do this...
 */

  if (hdev->vendor == 0x56a &&
--> --------------------

--> maximum size reached

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

Messung V0.5
C=93 H=93 G=92

¤ Dauer der Verarbeitung: 0.23 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.