// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HID driver for Asus notebook built-in keyboard.
* Fixes small logical maximum to match usage maximum.
*
* Currently supported devices are:
* EeeBook X205TA
* VivoBook E200HA
*
* Copyright (c) 2016 Yusuke Fujimaki <usk.fujimaki@gmail.com>
*
* This module based on hid-ortek by
* Copyright (c) 2010 Johnathon Harris <jmharris@gmail.com>
* Copyright (c) 2011 Jiri Kosina
*
* This module has been updated to add support for Asus i2c touchpad.
*
* Copyright (c) 2016 Brendan McGrath <redmcg@redmandi.dyndns.org>
* Copyright (c) 2016 Victor Vlasenko <victor.vlasenko@sysgears.com>
* Copyright (c) 2016 Frederik Wenigwieser <frederik.wenigwieser@gmail.com>
*/
/*
*/
#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/platform_data/x86/asus-wmi.h>
#include <linux/input/mt.h>
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
#include <linux/power_supply.h>
#include "hid-ids.h"
MODULE_AUTHOR("Yusuke Fujimaki <usk.fujimaki@gmail.com>");
MODULE_AUTHOR("Brendan McGrath <redmcg@redmandi.dyndns.org>");
MODULE_AUTHOR("Victor Vlasenko <victor.vlasenko@sysgears.com>");
MODULE_AUTHOR("Frederik Wenigwieser <frederik.wenigwieser@gmail.com>");
MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define T100_TPAD_INTF 2
#define T100CHI_MOUSE_REPORT_ID 0x06
#define FEATURE_REPORT_ID 0x0d
#define INPUT_REPORT_ID 0x5d
#define FEATURE_KBD_REPORT_ID 0x5a
#define FEATURE_KBD_REPORT_SIZE 16
#define SUPPORT_KBD_BACKLIGHT BIT(0)
#define MAX_TOUCH_MAJOR 8
#define MAX_PRESSURE 128
#define BTN_LEFT_MASK 0x01
#define CONTACT_TOOL_TYPE_MASK 0x80
#define CONTACT_X_MSB_MASK 0xf0
#define CONTACT_Y_MSB_MASK 0x0f
#define CONTACT_TOUCH_MAJOR_MASK 0x07
#define CONTACT_PRESSURE_MASK 0x7f
#define BATTERY_REPORT_ID (0x03)
#define BATTERY_REPORT_SIZE (1 + 8)
#define BATTERY_LEVEL_MAX ((u8)255)
#define BATTERY_STAT_DISCONNECT (0)
#define BATTERY_STAT_CHARGING (1)
#define BATTERY_STAT_FULL (2)
#define QUIRK_FIX_NOTEBOOK_REPORT BIT(0)
#define QUIRK_NO_INIT_REPORTS BIT(1)
#define QUIRK_SKIP_INPUT_MAPPING BIT(2)
#define QUIRK_IS_MULTITOUCH BIT(3)
#define QUIRK_NO_CONSUMER_USAGES BIT(4)
#define QUIRK_USE_KBD_BACKLIGHT BIT(5)
#define QUIRK_T100_KEYBOARD BIT(6)
#define QUIRK_T100CHI BIT(7)
#define QUIRK_G752_KEYBOARD BIT(8)
#define QUIRK_T101HA_DOCK BIT(9)
#define QUIRK_T90CHI BIT(10)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
QUIRK_NO_CONSUMER_USAGES)
#define I2C_TOUCHPAD_QUIRKS (QUIRK_NO_INIT_REPORTS | \
QUIRK_SKIP_INPUT_MAPPING | \
QUIRK_IS_MULTITOUCH)
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
struct asus_kbd_leds {
struct led_classdev cdev;
struct hid_device *hdev;
struct work_struct work;
unsigned int brightness;
bool removed;
};
struct asus_touchpad_info {
int max_x;
int max_y;
int res_x;
int res_y;
int contact_size;
int max_contacts;
};
struct asus_drvdata {
unsigned long quirks;
struct hid_device *hdev;
struct input_dev *input;
struct asus_kbd_leds *kbd_backlight;
const struct asus_touchpad_info *tp;
bool enable_backlight;
struct power_supply *battery;
struct power_supply_desc battery_desc;
int battery_capacity;
int battery_stat;
bool battery_in_query;
unsigned long battery_next_query;
};
static int asus_report_battery(struct asus_drvdata *, u8 *, int);
static const struct asus_touchpad_info asus_i2c_tp = {
.max_x = 2794,
.max_y = 1758,
.contact_size = 5,
.max_contacts = 5,
};
static const struct asus_touchpad_info asus_t100ta_tp = {
.max_x = 2240,
.max_y = 1120,
.res_x = 30, /* units/mm */
.res_y = 27, /* units/mm */
.contact_size = 5,
.max_contacts = 5,
};
static const struct asus_touchpad_info asus_t100ha_tp = {
.max_x = 2640,
.max_y = 1320,
.res_x = 30, /* units/mm */
.res_y = 29, /* units/mm */
.contact_size = 5,
.max_contacts = 5,
};
static const struct asus_touchpad_info asus_t200ta_tp = {
.max_x = 3120,
.max_y = 1716,
.res_x = 30, /* units/mm */
.res_y = 28, /* units/mm */
.contact_size = 5,
.max_contacts = 5,
};
static const struct asus_touchpad_info asus_t100chi_tp = {
.max_x = 2640,
.max_y = 1320,
.res_x = 31, /* units/mm */
.res_y = 29, /* units/mm */
.contact_size = 3,
.max_contacts = 4,
};
static void asus_report_contact_down(struct asus_drvdata *drvdat,
int toolType, u8 *data)
{
struct input_dev *input = drvdat->input;
int touch_major, pressure, x, y;
x = (data[0] & CONTACT_X_MSB_MASK) << 4 | data[1];
y = drvdat->tp->max_y - ((data[0] & CONTACT_Y_MSB_MASK) << 8 | data[2]);
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
if (drvdat->tp->contact_size < 5)
return;
if (toolType == MT_TOOL_PALM) {
touch_major = MAX_TOUCH_MAJOR;
pressure = MAX_PRESSURE;
} else {
touch_major = (data[3] >> 4) & CONTACT_TOUCH_MAJOR_MASK;
pressure = data[4] & CONTACT_PRESSURE_MASK;
}
input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major);
input_report_abs(input, ABS_MT_PRESSURE, pressure);
}
/* Required for Synaptics Palm Detection */
static void asus_report_tool_width(struct asus_drvdata *drvdat)
{
struct input_mt *mt = drvdat->input->mt;
struct input_mt_slot *oldest;
int oldid, count, i;
if (drvdat->tp->co