summaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Makefile2
-rw-r--r--drivers/hid/hid-chicony.c55
-rw-r--r--drivers/hid/hid-core.c9
-rw-r--r--drivers/hid/hid-google-hammer.c85
-rw-r--r--drivers/hid/hid-ids.h4
-rw-r--r--drivers/hid/hid-input.c12
-rw-r--r--drivers/hid/hid-ite.c12
-rw-r--r--drivers/hid/hid-lg-g15.c2
-rw-r--r--drivers/hid/hid-logitech-dj.c8
-rw-r--r--drivers/hid/hid-logitech-hidpp.c246
-rw-r--r--drivers/hid/hid-multitouch.c10
-rw-r--r--drivers/hid/hid-quirks.c25
-rw-r--r--drivers/hid/hid-roccat-arvo.c6
-rw-r--r--drivers/hid/i2c-hid/Kconfig47
-rw-r--r--drivers/hid/i2c-hid/Makefile6
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-acpi.c143
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-core.c254
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-of-goodix.c116
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-of.c143
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.h22
-rw-r--r--drivers/hid/intel-ish-hid/ipc/hw-ish.h2
-rw-r--r--drivers/hid/intel-ish-hid/ipc/ipc.c27
-rw-r--r--drivers/hid/intel-ish-hid/ipc/pci-ish.c55
-rw-r--r--drivers/hid/wacom_sys.c2
-rw-r--r--drivers/hid/wacom_wac.c7
25 files changed, 1029 insertions, 271 deletions
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 3cdbfb60ca57..c4f6d5c613dc 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -139,7 +139,7 @@ obj-$(CONFIG_USB_HID) += usbhid/
obj-$(CONFIG_USB_MOUSE) += usbhid/
obj-$(CONFIG_USB_KBD) += usbhid/
-obj-$(CONFIG_I2C_HID) += i2c-hid/
+obj-$(CONFIG_I2C_HID_CORE) += i2c-hid/
obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
diff --git a/drivers/hid/hid-chicony.c b/drivers/hid/hid-chicony.c
index 3f0ed6a95223..ca556d39da2a 100644
--- a/drivers/hid/hid-chicony.c
+++ b/drivers/hid/hid-chicony.c
@@ -21,6 +21,39 @@
#include "hid-ids.h"
+#define CH_WIRELESS_CTL_REPORT_ID 0x11
+
+static int ch_report_wireless(struct hid_report *report, u8 *data, int size)
+{
+ struct hid_device *hdev = report->device;
+ struct input_dev *input;
+
+ if (report->id != CH_WIRELESS_CTL_REPORT_ID || report->maxfield != 1)
+ return 0;
+
+ input = report->field[0]->hidinput->input;
+ if (!input) {
+ hid_warn(hdev, "can't find wireless radio control's input");
+ return 0;
+ }
+
+ input_report_key(input, KEY_RFKILL, 1);
+ input_sync(input);
+ input_report_key(input, KEY_RFKILL, 0);
+ input_sync(input);
+
+ return 1;
+}
+
+static int ch_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ if (report->application == HID_GD_WIRELESS_RADIO_CTLS)
+ return ch_report_wireless(report, data, size);
+
+ return 0;
+}
+
#define ch_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
@@ -77,10 +110,30 @@ static __u8 *ch_switch12_report_fixup(struct hid_device *hdev, __u8 *rdesc,
return rdesc;
}
+static int ch_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+
+ hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "Chicony hid parse failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hdev, "Chicony hw start failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
static const struct hid_device_id ch_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS3) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_ACER_SWITCH12) },
{ }
};
@@ -91,6 +144,8 @@ static struct hid_driver ch_driver = {
.id_table = ch_devices,
.report_fixup = ch_switch12_report_fixup,
.input_mapping = ch_input_mapping,
+ .probe = ch_probe,
+ .raw_event = ch_raw_event,
};
module_hid_driver(ch_driver);
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 56172fe6995c..097cb1ee3126 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -90,7 +90,7 @@ EXPORT_SYMBOL_GPL(hid_register_report);
* Register a new field for this report.
*/
-static struct hid_field *hid_register_field(struct hid_report *report, unsigned usages, unsigned values)
+static struct hid_field *hid_register_field(struct hid_report *report, unsigned usages)
{
struct hid_field *field;
@@ -101,7 +101,7 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned
field = kzalloc((sizeof(struct hid_field) +
usages * sizeof(struct hid_usage) +
- values * sizeof(unsigned)), GFP_KERNEL);
+ usages * sizeof(unsigned)), GFP_KERNEL);
if (!field)
return NULL;
@@ -300,7 +300,7 @@ static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsign
usages = max_t(unsigned, parser->local.usage_index,
parser->global.report_count);
- field = hid_register_field(report, usages, parser->global.report_count);
+ field = hid_register_field(report, usages);
if (!field)
return 0;
@@ -1307,6 +1307,9 @@ EXPORT_SYMBOL_GPL(hid_open_report);
static s32 snto32(__u32 value, unsigned n)
{
+ if (!value || !n)
+ return 0;
+
switch (n) {
case 8: return ((__s8)value);
case 16: return ((__s16)value);
diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
index 85a054f1ce38..d9319622da44 100644
--- a/drivers/hid/hid-google-hammer.c
+++ b/drivers/hid/hid-google-hammer.c
@@ -392,30 +392,34 @@ static int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
}
-static int hammer_event(struct hid_device *hid, struct hid_field *field,
- struct hid_usage *usage, __s32 value)
+static void hammer_folded_event(struct hid_device *hdev, bool folded)
{
unsigned long flags;
- if (usage->hid == HID_USAGE_KBD_FOLDED) {
- spin_lock_irqsave(&cbas_ec_lock, flags);
+ spin_lock_irqsave(&cbas_ec_lock, flags);
- /*
- * If we are getting events from Whiskers that means that it
- * is attached to the lid.
- */
- cbas_ec.base_present = true;
- cbas_ec.base_folded = value;
- hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
- cbas_ec.base_present, cbas_ec.base_folded);
-
- if (cbas_ec.input) {
- input_report_switch(cbas_ec.input,
- SW_TABLET_MODE, value);
- input_sync(cbas_ec.input);
- }
+ /*
+ * If we are getting events from Whiskers that means that it
+ * is attached to the lid.
+ */
+ cbas_ec.base_present = true;
+ cbas_ec.base_folded = folded;
+ hid_dbg(hdev, "%s: base: %d, folded: %d\n", __func__,
+ cbas_ec.base_present, cbas_ec.base_folded);
- spin_unlock_irqrestore(&cbas_ec_lock, flags);
+ if (cbas_ec.input) {
+ input_report_switch(cbas_ec.input, SW_TABLET_MODE, folded);
+ input_sync(cbas_ec.input);
+ }
+
+ spin_unlock_irqrestore(&cbas_ec_lock, flags);
+}
+
+static int hammer_event(struct hid_device *hid, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ if (usage->hid == HID_USAGE_KBD_FOLDED) {
+ hammer_folded_event(hid, value);
return 1; /* We handled this event */
}
@@ -457,6 +461,47 @@ static bool hammer_has_backlight_control(struct hid_device *hdev)
HID_GD_KEYBOARD, HID_AD_BRIGHTNESS);
}
+static void hammer_get_folded_state(struct hid_device *hdev)
+{
+ struct hid_report *report;
+ char *buf;
+ int len, rlen;
+ int a;
+
+ report = hdev->report_enum[HID_INPUT_REPORT].report_id_hash[0x0];
+
+ if (!report || report->maxfield < 1)
+ return;
+
+ len = hid_report_len(report) + 1;
+
+ buf = kmalloc(len, GFP_KERNEL);
+ if (!buf)
+ return;
+
+ rlen = hid_hw_raw_request(hdev, report->id, buf, len, report->type, HID_REQ_GET_REPORT);
+
+ if (rlen != len) {
+ hid_warn(hdev, "Unable to read base folded state: %d (expected %d)\n", rlen, len);
+ goto out;
+ }
+
+ for (a = 0; a < report->maxfield; a++) {
+ struct hid_field *field = report->field[a];
+
+ if (field->usage->hid == HID_USAGE_KBD_FOLDED) {
+ u32 value = hid_field_extract(hdev, buf+1,
+ field->report_offset, field->report_size);
+
+ hammer_folded_event(hdev, value);
+ break;
+ }
+ }
+
+out:
+ kfree(buf);
+}
+
static int hammer_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -481,6 +526,8 @@ static int hammer_probe(struct hid_device *hdev,
error = hid_hw_open(hdev);
if (error)
return error;
+
+ hammer_get_folded_state(hdev);
}
if (hammer_has_backlight_control(hdev)) {
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index d6dd6b7c9e36..ae4c8cefb64c 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -270,6 +270,7 @@
#define USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE 0x1053
#define USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2 0x0939
#define USB_DEVICE_ID_CHICONY_WIRELESS2 0x1123
+#define USB_DEVICE_ID_CHICONY_WIRELESS3 0x1236
#define USB_DEVICE_ID_ASUS_AK1D 0x1125
#define USB_DEVICE_ID_CHICONY_TOSHIBA_WT10A 0x1408
#define USB_DEVICE_ID_CHICONY_ACER_SWITCH12 0x1421
@@ -389,6 +390,7 @@
#define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401
#define USB_DEVICE_ID_HP_X2 0x074d
#define USB_DEVICE_ID_HP_X2_10_COVER 0x0755
+#define I2C_DEVICE_ID_HP_SPECTRE_X360_15 0x2817
#define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706
#define USB_VENDOR_ID_ELECOM 0x056e
@@ -641,6 +643,8 @@
#define USB_DEVICE_ID_INNEX_GENESIS_ATARI 0x4745
#define USB_VENDOR_ID_ITE 0x048d
+#define I2C_VENDOR_ID_ITE 0x103c
+#define I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15 0x184f
#define USB_DEVICE_ID_ITE_LENOVO_YOGA 0x8386
#define USB_DEVICE_ID_ITE_LENOVO_YOGA2 0x8350
#define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index f23027d2795b..236bccd37760 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -324,6 +324,8 @@ static const struct hid_device_id hid_battery_quirks[] = {
HID_BATTERY_QUIRK_IGNORE },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN),
HID_BATTERY_QUIRK_IGNORE },
+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_15),
+ HID_BATTERY_QUIRK_IGNORE },
{}
};
@@ -1854,6 +1856,16 @@ static struct hid_input *hidinput_match_application(struct hid_report *report)
list_for_each_entry(hidinput, &hid->inputs, list) {
if (hidinput->application == report->application)
return hidinput;
+
+ /*
+ * Keep SystemControl and ConsumerControl applications together
+ * with the main keyboard, if present.
+ */
+ if ((report->application == HID_GD_SYSTEM_CONTROL ||
+ report->application == HID_CP_CONSUMER_CONTROL) &&
+ hidinput->application == HID_GD_KEYBOARD) {
+ return hidinput;
+ }
}
return NULL;
diff --git a/drivers/hid/hid-ite.c b/drivers/hid/hid-ite.c
index 22bfbebceaf4..14fc068affad 100644
--- a/drivers/hid/hid-ite.c
+++ b/drivers/hid/hid-ite.c
@@ -23,11 +23,16 @@ static __u8 *ite_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int
hid_info(hdev, "Fixing up Acer Sw5-012 ITE keyboard report descriptor\n");
rdesc[163] = HID_MAIN_ITEM_RELATIVE;
}
- /* For Acer One S1002 keyboard-dock */
+ /* For Acer One S1002/S1003 keyboard-dock */
if (*rsize == 188 && rdesc[185] == 0x81 && rdesc[186] == 0x02) {
- hid_info(hdev, "Fixing up Acer S1002 ITE keyboard report descriptor\n");
+ hid_info(hdev, "Fixing up Acer S1002/S1003 ITE keyboard report descriptor\n");
rdesc[186] = HID_MAIN_ITEM_RELATIVE;
}
+ /* For Acer Aspire Switch 10E (SW3-016) keyboard-dock */
+ if (*rsize == 210 && rdesc[184] == 0x81 && rdesc[185] == 0x02) {
+ hid_info(hdev, "Fixing up Acer Aspire Switch 10E (SW3-016) ITE keyboard report descriptor\n");
+ rdesc[185] = HID_MAIN_ITEM_RELATIVE;
+ }
}
return rdesc;
@@ -114,7 +119,8 @@ static const struct hid_device_id ite_devices[] = {
/* ITE8910 USB kbd ctlr, with Synaptics touchpad connected to it. */
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_SYNAPTICS,
- USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1003) },
+ USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1003),
+ .driver_data = QUIRK_TOUCHPAD_ON_OFF_REPORT },
{ }
};
MODULE_DEVICE_TABLE(hid, ite_devices);
diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c
index fcaf8466e627..bfbba0d41933 100644
--- a/drivers/hid/hid-lg-g15.c
+++ b/drivers/hid/hid-lg-g15.c
@@ -647,7 +647,7 @@ static void lg_g15_input_close(struct input_dev *dev)
static int lg_g15_register_led(struct lg_g15_data *g15, int i)
{
- const char * const led_names[] = {
+ static const char * const led_names[] = {
"g15::kbd_backlight",
"g15::lcd_backlight",
"g15::macro_preset1",
diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c
index 45e7e0bdd382..271bd8d24339 100644
--- a/drivers/hid/hid-logitech-dj.c
+++ b/drivers/hid/hid-logitech-dj.c
@@ -980,6 +980,7 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev,
case 0x07:
device_type = "eQUAD step 4 Gaming";
logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
+ workitem.reports_supported |= STD_KEYBOARD;
break;
case 0x08:
device_type = "eQUAD step 4 for gamepads";
@@ -994,7 +995,12 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev,
workitem.reports_supported |= STD_KEYBOARD;
break;
case 0x0d:
- device_type = "eQUAD Lightspeed 1_1";
+ device_type = "eQUAD Lightspeed 1.1";
+ logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
+ workitem.reports_supported |= STD_KEYBOARD;
+ break;
+ case 0x0f:
+ device_type = "eQUAD Lightspeed 1.2";
logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
workitem.reports_supported |= STD_KEYBOARD;
break;
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 7eb9a6ddb46a..d459e2dbe647 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -92,6 +92,8 @@ MODULE_PARM_DESC(disable_tap_to_click,
#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 lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
@@ -152,6 +154,7 @@ struct hidpp_battery {
int voltage;
int charge_type;
bool online;
+ u8 supported_levels_1004;
};
/**
@@ -1171,7 +1174,7 @@ static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp,
return 0;
}
-static int hidpp20_query_battery_info(struct hidpp_device *hidpp)
+static int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp)
{
u8 feature_type;
int ret;
@@ -1208,7 +1211,7 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp)
return 0;
}
-static int hidpp20_battery_event(struct hidpp_device *hidpp,
+static int hidpp20_battery_event_1000(struct hidpp_device *hidpp,
u8 *data, int size)
{
struct hidpp_report *report = (struct hidpp_report *)data;
@@ -1380,6 +1383,224 @@ static int hidpp20_battery_voltage_event(struct hidpp_device *hidpp,
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)
+{
+ u8 feature_type;
+ 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,
+ &feature_type);
+ 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,
@@ -3307,7 +3528,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
}
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
- ret = hidpp20_battery_event(hidpp, data, size);
+ ret = hidpp20_battery_event_1000(hidpp, data, size);
+ if (ret != 0)
+ return ret;
+ ret = hidpp20_battery_event_1004(hidpp, data, size);
if (ret != 0)
return ret;
ret = hidpp_solar_battery_event(hidpp, data, size);
@@ -3443,9 +3667,14 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
ret = hidpp_solar_request_battery_event(hidpp);
else {
- ret = hidpp20_query_battery_voltage_info(hidpp);
+ /* we only support one battery feature right now, so let's
+ first check the ones that support battery level first
+ and leave voltage for last */
+ ret = hidpp20_query_battery_info_1000(hidpp);
+ if (ret)
+ ret = hidpp20_query_battery_info_1004(hidpp);
if (ret)
- ret = hidpp20_query_battery_info(hidpp);
+ ret = hidpp20_query_battery_voltage_info(hidpp);
}
if (ret)
@@ -3473,7 +3702,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 3;
- if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE)
+ if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE ||
+ hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE)
battery_props[num_battery_props++] =
POWER_SUPPLY_PROP_CAPACITY;
@@ -3650,8 +3880,10 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
} else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
hidpp20_query_battery_voltage_info(hidpp);
+ else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY)
+ hidpp20_query_battery_info_1004(hidpp);
else
- hidpp20_query_battery_info(hidpp);
+ hidpp20_query_battery_info_1000(hidpp);
}
if (hidpp->battery.ps)
power_supply_changed(hidpp->battery.ps);
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 8429ebe7097e..9d9f3e1bd5f4 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -1747,6 +1747,13 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
}
#ifdef CONFIG_PM
+static int mt_suspend(struct hid_device *hdev, pm_message_t state)
+{
+ /* High latency is desirable for power savings during S3/S0ix */
+ mt_set_modes(hdev, HID_LATENCY_HIGH, true, true);
+ return 0;
+}
+
static int mt_reset_resume(struct hid_device *hdev)
{
mt_release_contacts(hdev);
@@ -1762,6 +1769,8 @@ static int mt_resume(struct hid_device *hdev)
hid_hw_idle(hdev, 0, 0, HID_REQ_SET_IDLE);
+ mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
+
return 0;
}
#endif
@@ -2155,6 +2164,7 @@ static struct hid_driver mt_driver = {
.event = mt_event,
.report = mt_report,
#ifdef CONFIG_PM
+ .suspend = mt_suspend,
.reset_resume = mt_reset_resume,
.resume = mt_resume,
#endif
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index d9ca874dffac..15db7522b41e 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -1029,7 +1029,7 @@ static DEFINE_MUTEX(dquirks_lock);
/* Runtime ("dynamic") quirks manipulation functions */
/**
- * hid_exists_dquirk: find any dynamic quirks for a HID device
+ * hid_exists_dquirk - find any dynamic quirks for a HID device
* @hdev: the HID device to match
*
* Description:
@@ -1037,7 +1037,7 @@ static DEFINE_MUTEX(dquirks_lock);
* the pointer to the relevant struct hid_device_id if found.
* Must be called with a read lock held on dquirks_lock.
*
- * Returns: NULL if no quirk found, struct hid_device_id * if found.
+ * Return: NULL if no quirk found, struct hid_device_id * if found.
*/
static struct hid_device_id *hid_exists_dquirk(const struct hid_device *hdev)
{
@@ -1061,7 +1061,7 @@ static struct hid_device_id *hid_exists_dquirk(const struct hid_device *hdev)
/**
- * hid_modify_dquirk: add/replace a HID quirk
+ * hid_modify_dquirk - add/replace a HID quirk
* @id: the HID device to match
* @quirks: the unsigned long quirks value to add/replace
*
@@ -1070,7 +1070,7 @@ static struct hid_device_id *hid_exists_dquirk(const struct hid_device *hdev)
* quirks value with what was provided. Otherwise, add the quirk
* to the dynamic quirks list.
*
- * Returns: 0 OK, -error on failure.
+ * Return: 0 OK, -error on failure.
*/
static int hid_modify_dquirk(const struct hid_device_id *id,
const unsigned long quirks)
@@ -1122,7 +1122,7 @@ static int hid_modify_dquirk(const struct hid_device_id *id,
}
/**
- * hid_remove_all_dquirks: remove all runtime HID quirks from memory
+ * hid_remove_all_dquirks - remove all runtime HID quirks from memory
* @bus: bus to match against. Use HID_BUS_ANY if all need to be removed.
*
* Description:
@@ -1146,7 +1146,10 @@ static void hid_remove_all_dquirks(__u16 bus)
}
/**
- * hid_quirks_init: apply HID quirks specified at module load time
+ * hid_quirks_init - apply HID quirks specified at module load time
+ * @quirks_param: array of quirks strings (vendor:product:quirks)
+ * @bus: bus type
+ * @count: number of quirks to check
*/
int hid_quirks_init(char **quirks_param, __u16 bus, int count)
{
@@ -1177,7 +1180,7 @@ int hid_quirks_init(char **quirks_param, __u16 bus, int count)
EXPORT_SYMBOL_GPL(hid_quirks_init);
/**
- * hid_quirks_exit: release memory associated with dynamic_quirks
+ * hid_quirks_exit - release memory associated with dynamic_quirks
* @bus: a bus to match against
*
* Description:
@@ -1194,14 +1197,14 @@ void hid_quirks_exit(__u16 bus)
EXPORT_SYMBOL_GPL(hid_quirks_exit);
/**
- * hid_gets_squirk: return any static quirks for a HID device
+ * hid_gets_squirk - return any static quirks for a HID device
* @hdev: the HID device to match
*
* Description:
* Given a HID device, return a pointer to the quirked hid_device_id entry
* associated with that device.
*
- * Returns: the quirks.
+ * Return: the quirks.
*/
static unsigned long hid_gets_squirk(const struct hid_device *hdev)
{
@@ -1225,13 +1228,13 @@ static unsigned long hid_gets_squirk(const struct hid_device *hdev)
}
/**
- * hid_lookup_quirk: return any quirks associated with a HID device
+ * hid_lookup_quirk - return any quirks associated with a HID device
* @hdev: the HID device to look for
*
* Description:
* Given a HID device, return any quirks associated with that device.
*
- * Returns: an unsigned long quirks value.
+ * Return: an unsigned long quirks value.
*/
unsigned long hid_lookup_quirk(const struct hid_device *hdev)
{
diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c
index ffcd444ae2ba..4556d2a50f75 100644
--- a/drivers/hid/hid-roccat-arvo.c
+++ b/drivers/hid/hid-roccat-arvo.c
@@ -42,7 +42,7 @@ static ssize_t arvo_sysfs_show_mode_key(struct device *dev,
if (retval)
return retval;
- return snprintf(buf, PAGE_SIZE, "%d\n", temp_buf.state);
+ return sysfs_emit(buf, "%d\n", temp_buf.state);
}
static ssize_t arvo_sysfs_set_mode_key(struct device *dev,
@@ -92,7 +92,7 @@ static ssize_t arvo_sysfs_show_key_mask(struct device *dev,
if (retval)
return retval;
- return snprintf(buf, PAGE_SIZE, "%d\n", temp_buf.key_mask);
+ return sysfs_emit(buf, "%d\n", temp_buf.key_mask);
}
static ssize_t arvo_sysfs_set_key_mask(struct device *dev,
@@ -146,7 +146,7 @@ static ssize_t arvo_sysfs_show_actual_profile(struct device *dev,
struct arvo_device *arvo =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", arvo->actual_profile);
+ return sysfs_emit(buf, "%d\n", arvo->actual_profile);
}
static ssize_t arvo_sysfs_set_actual_profile(struct device *dev,
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig
index c4e5dfeab2bd..a16c6a69680b 100644
--- a/drivers/hid/i2c-hid/Kconfig
+++ b/drivers/hid/i2c-hid/Kconfig
@@ -2,18 +2,55 @@
menu "I2C HID support"
depends on I2C
-config I2C_HID
- tristate "HID over I2C transport layer"
+config I2C_HID_ACPI
+ tristate "HID over I2C transport layer ACPI driver"
default n
- depends on I2C && INPUT
- select HID
+ depends on I2C && INPUT && ACPI
+ help
+ Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
+ other HID based devices which is connected to your computer via I2C.
+ This driver supports ACPI-based systems.
+
+ If unsure, say N.
+
+ This support is also available as a module. If so, the module
+ will be called i2c-hid-acpi. It will also build/depend on the
+ module i2c-hid.
+
+config I2C_HID_OF
+ tristate "HID over I2C transport layer Open Firmware driver"
+ default n
+ depends on I2C && INPUT && OF
help
Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
other HID based devices which is connected to your computer via I2C.
+ This driver supports Open Firmware (Device Tree)-based systems.
If unsure, say N.
This support is also available as a module. If so, the module
- will be called i2c-hid.
+ will be called i2c-hid-of. It will also build/depend on the
+ module i2c-hid.
+
+config I2C_HID_OF_GOODIX
+ tristate "Driver for Goodix hid-i2c based devices on OF systems"
+ default n
+ depends on I2C && INPUT && OF
+ help
+ Say Y here if you want support for Goodix i2c devices that use
+ the i2c-hid protocol on Open Firmware (Device Tree)-based
+ systems.
+
+ If unsure, say N.
+
+ This support is also available as a module. If so, the module
+ will be called i2c-hid-of-goodix. It will also build/depend on
+ the module i2c-hid.
endmenu
+
+config I2C_HID_CORE
+ tristate
+ default y if I2C_HID_ACPI=y || I2C_HID_OF=y || I2C_HID_OF_GOODIX=y
+ default m if I2C_HID_ACPI=m || I2C_HID_OF=m || I2C_HID_OF_GOODIX=m
+ select HID
diff --git a/drivers/hid/i2c-hid/Makefile b/drivers/hid/i2c-hid/Makefile
index 681b3896898e..302545a771f3 100644
--- a/drivers/hid/i2c-hid/Makefile
+++ b/drivers/hid/i2c-hid/Makefile
@@ -3,7 +3,11 @@
# Makefile for the I2C input drivers
#
-obj-$(CONFIG_I2C_HID) += i2c-hid.o
+obj-$(CONFIG_I2C_HID_CORE) += i2c-hid.o
i2c-hid-objs = i2c-hid-core.o
i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o
+
+obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o
+obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o
+obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o
diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c
new file mode 100644
index 000000000000..bb8c00e6be78
--- /dev/null
+++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c
@@ -0,0 +1,143 @@
+/*
+ * HID over I2C ACPI Subclass
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ *
+ * This code was forked out of the core code, which was partly based on
+ * "USB HID support for Linux":
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ * Copyright (c) 2007-2008 Oliver Neukum
+ * Copyright (c) 2006-2010 Jiri Kosina
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+
+#include "i2c-hid.h"
+
+struct i2c_hid_acpi {
+ struct i2chid_ops ops;
+ struct i2c_client *client;
+};
+
+static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
+ /*
+ * The CHPN0001 ACPI device, which is used to describe the Chipone
+ * ICN8505 controller, has a _CID of PNP0C50 but is not HID compatible.
+ */
+ {"CHPN0001", 0 },
+ { },
+};
+
+static int i2c_hid_acpi_get_descriptor(struct i2c_client *client)
+{
+ static guid_t i2c_hid_guid =
+ GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
+ 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
+ union acpi_object *obj;
+ struct acpi_device *adev;
+ acpi_handle handle;
+ u16 hid_descriptor_address;
+
+ handle = ACPI_HANDLE(&client->dev);
+ if (!handle || acpi_bus_get_device(handle, &adev)) {
+ dev_err(&client->dev, "Error could not get ACPI device\n");
+ return -ENODEV;
+ }
+
+ if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
+ return -ENODEV;
+
+ obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL,
+ ACPI_TYPE_INTEGER);
+ if (!obj) {
+ dev_err(&client->dev, "Error _DSM call to get HID descriptor address failed\n");
+ return -ENODEV;
+ }
+
+ hid_descriptor_address = obj->integer.value;
+ ACPI_FREE(obj);
+
+ return hid_descriptor_address;
+}
+
+static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
+{
+ struct i2c_hid_acpi *ihid_acpi =
+ container_of(ops, struct i2c_hid_acpi, ops);
+ struct device *dev = &ihid_acpi->client->dev;
+ acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D3_COLD);
+}
+
+static int i2c_hid_acpi_probe(struct i2c_client *client,
+ const struct i2c_device_id *dev_id)
+{
+ struct device *dev = &client->dev;
+ struct i2c_hid_acpi *ihid_acpi;
+ struct acpi_device *adev;
+ u16 hid_descriptor_address;
+ int ret;
+
+ ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL);
+ if (!ihid_acpi)
+ return -ENOMEM;
+
+ ihid_acpi->client = client;
+ ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
+
+ ret = i2c_hid_acpi_get_descriptor(client);
+ if (ret < 0)
+ return ret;
+ hid_descriptor_address = ret;
+
+ adev = ACPI_COMPANION(dev);
+ if (adev)
+ acpi_device_fix_up_power(adev);
+
+ if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) {
+ device_set_wakeup_capable(dev, true);
+ device_set_wakeup_enable(dev, false);
+ }
+
+ return i2c_hid_core_probe(client, &ihid_acpi->ops,
+ hid_descriptor_address);
+}
+
+static const struct acpi_device_id i2c_hid_acpi_match[] = {
+ {"ACPI0C50", 0 },
+ {"PNP0C50", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
+
+static struct i2c_driver i2c_hid_acpi_driver = {
+ .driver = {
+ .name = "i2c_hid_acpi",
+ .pm = &i2c_hid_core_pm,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .acpi_match_table = ACPI_PTR(i2c_hid_acpi_match),
+ },
+
+ .probe = i2c_hid_acpi_probe,
+ .remove = i2c_hid_core_remove,
+ .shutdown = i2c_hid_core_shutdown,
+};
+
+module_i2c_driver(i2c_hid_acpi_driver);
+
+MODULE_DESCRIPTION("HID over I2C ACPI driver");
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index bfe716d7ea44..9993133989a5 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -35,11 +35,6 @@
#include <linux/kernel.h>
#include <linux/hid.h>
#include <linux/mutex.h>
-#include <linux/acpi.h>
-#include <linux/of.h>
-#include <linux/regulator/consumer.h>
-
-#include <linux/platform_data/i2c-hid.h>
#include "../hid-ids.h"
#include "i2c-hid.h"
@@ -156,10 +151,10 @@ struct i2c_hid {
wait_queue_head_t wait; /* For waiting the interrupt */
- struct i2c_hid_platform_data pdata;
-
bool irq_wake_enabled;
struct mutex reset_lock;
+
+ struct i2chid_ops *ops;
};
static const struct i2c_hid_quirks {
@@ -171,6 +166,8 @@ static const struct i2c_hid_quirks {
I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
{ I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288,
I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
+ { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15,
+ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
{ I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118,
I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
{ USB_VENDOR_ID_ELAN, HID_ANY_ID,
@@ -884,144 +881,36 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
return 0;
}
-#ifdef CONFIG_ACPI
-static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
- /*
- * The CHPN0001 ACPI device, which is used to describe the Chipone
- * ICN8505 controller, has a _CID of PNP0C50 but is not HID compatible.
- */
- {"CHPN0001", 0 },
- { },
-};
-
-static int i2c_hid_acpi_pdata(struct i2c_client *client,
- struct i2c_hid_platform_data *pdata)
-{
- static guid_t i2c_hid_guid =
- GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
- 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
- union acpi_object *obj;
- struct acpi_device *adev;
- acpi_handle handle;
-
- handle = ACPI_HANDLE(&client->dev);
- if (!handle || acpi_bus_get_device(handle, &adev)) {
- dev_err(&client->dev, "Error could not get ACPI device\n");
- return -ENODEV;
- }
-
- if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
- return -ENODEV;
-
- obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL,
- ACPI_TYPE_INTEGER);
- if (!obj) {
- dev_err(&client->dev, "Error _DSM call to get HID descriptor address failed\n");
- return -ENODEV;
- }
-
- pdata->hid_descriptor_address = obj->integer.value;
- ACPI_FREE(obj);
-
- return 0;
-}
-
-static void i2c_hid_acpi_fix_up_power(struct device *dev)
-{
- struct acpi_device *adev;
-
- adev = ACPI_COMPANION(dev);
- if (adev)
- acpi_device_fix_up_power(adev);
-}
-
-static void i2c_hid_acpi_enable_wakeup(struct device *dev)
-{
- if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) {
- device_set_wakeup_capable(dev, true);
- device_set_wakeup_enable(dev, false);
- }
-}
-
-static void i2c_hid_acpi_shutdown(struct device *dev)
+static int i2c_hid_core_power_up(struct i2c_hid *ihid)
{
- acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D3_COLD);
-}
+ if (!ihid->ops->power_up)
+ return 0;
-static const struct acpi_device_id i2c_hid_acpi_match[] = {
- {"ACPI0C50", 0 },
- {"PNP0C50", 0 },
- { },
-};
-MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
-#else
-static inline int i2c_hid_acpi_pdata(struct i2c_client *client,
- struct i2c_hid_platform_data *pdata)
-{
- return -ENODEV;
+ return ihid->ops->power_up(ihid->ops);
}
-static inline void i2c_hid_acpi_fix_up_power(struct device *dev) {}
-
-static inline void i2c_hid_acpi_enable_wakeup(struct device *dev) {}
-
-static inline void i2c_hid_acpi_shutdown(struct device *dev) {}
-#endif
-
-#ifdef CONFIG_OF
-static int i2c_hid_of_probe(struct i2c_client *client,
- struct i2c_hid_platform_data *pdata)
+static void i2c_hid_core_power_down(struct i2c_hid *ihid)
{
- struct device *dev = &client->dev;
- u32 val;
- int ret;
-
- ret = of_property_read_u32(dev->of_node, "hid-descr-addr", &val);
- if (ret) {
- dev_err(&client->dev, "HID register address not provided\n");
- return -ENODEV;
- }
- if (val >> 16) {
- dev_err(&client->dev, "Bad HID register address: 0x%08x\n",
- val);
- return -EINVAL;
- }
- pdata->hid_descriptor_address = val;
-
- return 0;
-}
+ if (!ihid->ops->power_down)
+ return;
-static const struct of_device_id i2c_hid_of_match[] = {
- { .compatible = "hid-over-i2c" },
- {},
-};
-MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
-#else
-static inline int i2c_hid_of_probe(struct i2c_client *client,
- struct i2c_hid_platform_data *pdata)
-{
- return -ENODEV;
+ ihid->ops->power_down(ihid->ops);
}
-#endif
-static void i2c_hid_fwnode_probe(struct i2c_client *client,
- struct i2c_hid_platform_data *pdata)
+static void i2c_hid_core_shutdown_tail(struct i2c_hid *ihid)
{
- u32 val;
+ if (!ihid->ops->shutdown_tail)
+ return;
- if (!device_property_read_u32(&client->dev, "post-power-on-delay-ms",
- &val))
- pdata->post_power_delay_ms = val;
+ ihid->ops->shutdown_tail(ihid->ops);
}
-static int i2c_hid_probe(struct i2c_client *client,
- const struct i2c_device_id *dev_id)
+int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
+ u16 hid_descriptor_address)
{
int ret;
struct i2c_hid *ihid;
struct hid_device *hid;
- __u16 hidRegister;
- struct i2c_hid_platform_data *platform_data = client->dev.platform_data;
dbg_hid("HID probe called for i2c 0x%02x\n", client->addr);
@@ -1042,44 +931,17 @@ static int i2c_hid_probe(struct i2c_client *client,
if (!ihid)
return -ENOMEM;
- if (client->dev.of_node) {
- ret = i2c_hid_of_probe(client, &ihid->pdata);
- if (ret)
- return ret;
- } else if (!platform_data) {
- ret = i2c_hid_acpi_pdata(client, &ihid->pdata);
- if (ret)
- return ret;
- } else {
- ihid->pdata = *platform_data;
- }
-
- /* Parse platform agnostic common properties from ACPI / device tree */
- i2c_hid_fwnode_probe(client, &ihid->pdata);
+ ihid->ops = ops;
- ihid->pdata.supplies[0].supply = "vdd";
- ihid->pdata.supplies[1].supply = "vddl";
-
- ret = devm_regulator_bulk_get(&client->dev,
- ARRAY_SIZE(ihid->pdata.supplies),
- ihid->pdata.supplies);
+ ret = i2c_hid_core_power_up(ihid);
if (ret)
return ret;
- ret = regulator_bulk_enable(ARRAY_SIZE(ihid->pdata.supplies),
- ihid->pdata.supplies);
- if (ret < 0)
- return ret;
-
- if (ihid->pdata.post_power_delay_ms)
- msleep(ihid->pdata.post_power_delay_ms);
-
i2c_set_clientdata(client, ihid);
ihid->client = client;
- hidRegister = ihid->pdata.hid_descriptor_address;
- ihid->wHIDDescRegister = cpu_to_le16(hidRegister);
+ ihid->wHIDDescRegister = cpu_to_le16(hid_descriptor_address);
init_waitqueue_head(&ihid->wait);
mutex_init(&ihid->reset_lock);
@@ -1089,11 +951,7 @@ static int i2c_hid_probe(struct i2c_client *client,
* real computation later. */
ret = i2c_hid_alloc_buffers(ihid, HID_MIN_BUFFER_SIZE);
if (ret < 0)
- goto err_regulator;
-
- i2c_hid_acpi_fix_up_power(&client->dev);
-
- i2c_hid_acpi_enable_wakeup(&client->dev);
+ goto err_powered;
device_enable_async_suspend(&client->dev);
@@ -1102,19 +960,19 @@ static int i2c_hid_probe(struct i2c_client *client,
if (ret < 0) {
dev_dbg(&client->dev, "nothing at this address: %d\n", ret);
ret = -ENXIO;
- goto err_regulator;
+ goto err_powered;
}
ret = i2c_hid_fetch_hid_descriptor(ihid);
if (ret < 0) {
dev_err(&client->dev,
"Failed to fetch the HID Descriptor\n");
- goto err_regulator;
+ goto err_powered;
}
ret = i2c_hid_init_irq(client);
if (ret < 0)
- goto err_regulator;
+ goto err_powered;
hid = hid_allocate_device();
if (IS_ERR(hid)) {
@@ -1153,14 +1011,14 @@ err_mem_free:
err_irq:
free_irq(client->irq, ihid);
-err_regulator:
- regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
- ihid->pdata.supplies);
+err_powered:
+ i2c_hid_core_power_down(ihid);
i2c_hid_free_buffers(ihid);
return ret;
}
+EXPORT_SYMBOL_GPL(i2c_hid_core_probe);
-static int i2c_hid_remove(struct i2c_client *client)
+int i2c_hid_core_remove(struct i2c_client *client)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
struct hid_device *hid;
@@ -1173,24 +1031,25 @@ static int i2c_hid_remove(struct i2c_client *client)
if (ihid->bufsize)
i2c_hid_free_buffers(ihid);
- regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
- ihid->pdata.supplies);
+ i2c_hid_core_power_down(ihid);
return 0;
}
+EXPORT_SYMBOL_GPL(i2c_hid_core_remove);
-static void i2c_hid_shutdown(struct i2c_client *client)
+void i2c_hid_core_shutdown(struct i2c_client *client)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
free_irq(client->irq, ihid);
- i2c_hid_acpi_shutdown(&client->dev);
+ i2c_hid_core_shutdown_tail(ihid);
}
+EXPORT_SYMBOL_GPL(i2c_hid_core_shutdown);
#ifdef CONFIG_PM_SLEEP
-static int i2c_hid_suspend(struct device *dev)
+static int i2c_hid_core_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct i2c_hid *ihid = i2c_get_clientdata(client);
@@ -1217,14 +1076,13 @@ static int i2c_hid_suspend(struct device *dev)
hid_warn(hid, "Failed to enable irq wake: %d\n",
wake_status);
} else {
- regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
- ihid->pdata.supplies);
+ i2c_hid_core_power_down(ihid);
}
return 0;
}
-static int i2c_hid_resume(struct device *dev)
+static int i2c_hid_core_resume(struct device *dev)
{
int ret;
struct i2c_client *client = to_i2c_client(dev);
@@ -1233,13 +1091,7 @@ static int i2c_hid_resume(struct device *dev)
int wake_status;
if (!device_may_wakeup(&client->dev)) {
- ret = regulator_bulk_enable(ARRAY_SIZE(ihid->pdata.supplies),
- ihid->pdata.supplies);
- if (ret)
- hid_warn(hid, "Failed to enable supplies: %d\n", ret);
-
- if (ihid->pdata.post_power_delay_ms)
- msleep(ihid->pdata.post_power_delay_ms);
+ i2c_hid_core_power_up(ihid);
} else if (ihid->irq_wake_enabled) {
wake_status = disable_irq_wake(client->irq);
if (!wake_status)
@@ -1276,34 +1128,10 @@ static int i2c_hid_resume(struct device *dev)
}
#endif
-static const struct dev_pm_ops i2c_hid_pm = {
- SET_SYSTEM_SLEEP_PM_OPS(i2c_hid_suspend, i2c_hid_resume)
+const struct dev_pm_ops i2c_hid_core_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(i2c_hid_core_suspend, i2c_hid_core_resume)
};
-
-static const struct i2c_device_id i2c_hid_id_table[] = {
- { "hid", 0 },
- { "hid-over-i2c", 0 },
- { },
-};
-MODULE_DEVICE_TABLE(i2c, i2c_hid_id_table);
-
-
-static struct i2c_driver i2c_hid_driver = {
- .driver = {
- .name = "i2c_hid",
- .pm = &i2c_hid_pm,
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- .acpi_match_table = ACPI_PTR(i2c_hid_acpi_match),
- .of_match_table = of_match_ptr(i2c_hid_of_match),
- },
-
- .probe = i2c_hid_probe,
- .remove = i2c_hid_remove,
- .shutdown = i2c_hid_shutdown,
- .id_table = i2c_hid_id_table,
-};
-
-module_i2c_driver(i2c_hid_driver);
+EXPORT_SYMBOL_GPL(i2c_hid_core_pm);
MODULE_DESCRIPTION("HID over I2C core driver");
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
diff --git a/drivers/hid/i2c-hid/i2c-hid-of-goodix.c b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c
new file mode 100644
index 000000000000..ee0225982a82
--- /dev/null
+++ b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Goodix touchscreens that use the i2c-hid protocol.
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/regulator/consumer.h>
+
+#include "i2c-hid.h"
+
+struct goodix_i2c_hid_timing_data {
+ unsigned int post_gpio_reset_delay_ms;
+ unsigned int post_power_delay_ms;
+};
+
+struct i2c_hid_of_goodix {
+ struct i2chid_ops ops;
+
+ struct regulator *vdd;
+ struct gpio_desc *reset_gpio;
+ const struct goodix_i2c_hid_timing_data *timings;
+};
+
+static int goodix_i2c_hid_power_up(struct i2chid_ops *ops)
+{
+ struct i2c_hid_of_goodix *ihid_goodix =
+ container_of(ops, struct i2c_hid_of_goodix, ops);
+ int ret;
+
+ ret = regulator_enable(ihid_goodix->vdd);
+ if (ret)
+ return ret;
+
+ if (ihid_goodix->timings->post_power_delay_ms)
+ msleep(ihid_goodix->timings->post_power_delay_ms);
+
+ gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 0);
+ if (ihid_goodix->timings->post_gpio_reset_delay_ms)
+ msleep(ihid_goodix->timings->post_gpio_reset_delay_ms);
+
+ return 0;
+}
+
+static void goodix_i2c_hid_power_down(struct i2chid_ops *ops)
+{
+ struct i2c_hid_of_goodix *ihid_goodix =
+ container_of(ops, struct i2c_hid_of_goodix, ops);
+
+ gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 1);
+ regulator_disable(ihid_goodix->vdd);
+}
+
+static int i2c_hid_of_goodix_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_hid_of_goodix *ihid_goodix;
+
+ ihid_goodix = devm_kzalloc(&client->dev, sizeof(*ihid_goodix),
+ GFP_KERNEL);
+ if (!ihid_goodix)
+ return -ENOMEM;
+
+ ihid_goodix->ops.power_up = goodix_i2c_hid_power_up;
+ ihid_goodix->ops.power_down = goodix_i2c_hid_power_down;
+
+ /* Start out with reset asserted */
+ ihid_goodix->reset_gpio =
+ devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(ihid_goodix->reset_gpio))
+ return PTR_ERR(ihid_goodix->reset_gpio);
+
+ ihid_goodix->vdd = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(ihid_goodix->vdd))
+ return PTR_ERR(ihid_goodix->vdd);
+
+ ihid_goodix->timings = device_get_match_data(&client->dev);
+
+ return i2c_hid_core_probe(client, &ihid_goodix->ops, 0x0001);
+}
+
+static const struct goodix_i2c_hid_timing_data goodix_gt7375p_timing_data = {
+ .post_power_delay_ms = 10,
+ .post_gpio_reset_delay_ms = 180,
+};
+
+static const struct of_device_id goodix_i2c_hid_of_match[] = {
+ { .compatible = "goodix,gt7375p", .data = &goodix_gt7375p_timing_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, goodix_i2c_hid_of_match);
+
+static struct i2c_driver goodix_i2c_hid_ts_driver = {
+ .driver = {
+ .name = "i2c_hid_of_goodix",
+ .pm = &i2c_hid_core_pm,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .of_match_table = of_match_ptr(goodix_i2c_hid_of_match),
+ },
+ .probe = i2c_hid_of_goodix_probe,
+ .remove = i2c_hid_core_remove,
+ .shutdown = i2c_hid_core_shutdown,
+};
+module_i2c_driver(goodix_i2c_hid_ts_driver);
+
+MODULE_AUTHOR("Douglas Anderson <dianders@chromium.org>");
+MODULE_DESCRIPTION("Goodix i2c-hid touchscreen driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/i2c-hid/i2c-hid-of.c b/drivers/hid/i2c-hid/i2c-hid-of.c
new file mode 100644
index 000000000000..4bf7cea92637
--- /dev/null
+++ b/drivers/hid/i2c-hid/i2c-hid-of.c
@@ -0,0 +1,143 @@
+/*
+ * HID over I2C Open Firmware Subclass
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ *
+ * This code was forked out of the core code, which was partly based on
+ * "USB HID support for Linux":
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ * Copyright (c) 2007-2008 Oliver Neukum
+ * Copyright (c) 2006-2010 Jiri Kosina
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/regulator/consumer.h>
+
+#include "i2c-hid.h"
+
+struct i2c_hid_of {
+ struct i2chid_ops ops;
+
+ struct i2c_client *client;
+ struct regulator_bulk_data supplies[2];
+ int post_power_delay_ms;
+};
+
+static int i2c_hid_of_power_up(struct i2chid_ops *ops)
+{
+ struct i2c_hid_of *ihid_of = container_of(ops, struct i2c_hid_of, ops);
+ struct device *dev = &ihid_of->client->dev;
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ihid_of->supplies),
+ ihid_of->supplies);
+ if (ret) {
+ dev_warn(dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ if (ihid_of->post_power_delay_ms)
+ msleep(ihid_of->post_power_delay_ms);
+
+ return 0;
+}
+
+static void i2c_hid_of_power_down(struct i2chid_ops *ops)
+{
+ struct i2c_hid_of *ihid_of = container_of(ops, struct i2c_hid_of, ops);
+
+ regulator_bulk_disable(ARRAY_SIZE(ihid_of->supplies),
+ ihid_of->supplies);
+}
+
+static int i2c_hid_of_probe(struct i2c_client *client,
+ const struct i2c_device_id *dev_id)
+{
+ struct device *dev = &client->dev;
+ struct i2c_hid_of *ihid_of;
+ u16 hid_descriptor_address;
+ int ret;
+ u32 val;
+
+ ihid_of = devm_kzalloc(&client->dev, sizeof(*ihid_of), GFP_KERNEL);
+ if (!ihid_of)
+ return -ENOMEM;
+
+ ihid_of->ops.power_up = i2c_hid_of_power_up;
+ ihid_of->ops.power_down = i2c_hid_of_power_down;
+
+ ret = of_property_read_u32(dev->of_node, "hid-descr-addr", &val);
+ if (ret) {
+ dev_err(&client->dev, "HID register address not provided\n");
+ return -ENODEV;
+ }
+ if (val >> 16) {
+ dev_err(&client->dev, "Bad HID register address: 0x%08x\n",
+ val);
+ return -EINVAL;
+ }
+ hid_descriptor_address = val;
+
+ if (!device_property_read_u32(&client->dev, "post-power-on-delay-ms",
+ &val))
+ ihid_of->post_power_delay_ms = val;
+
+ ihid_of->supplies[0].supply = "vdd";
+ ihid_of->supplies[1].supply = "vddl";
+ ret = devm_regulator_bulk_get(&client->dev,
+ ARRAY_SIZE(ihid_of->supplies),
+ ihid_of->supplies);
+ if (ret)
+ return ret;
+
+ return i2c_hid_core_probe(client, &ihid_of->ops,
+ hid_descriptor_address);
+}
+
+static const struct of_device_id i2c_hid_of_match[] = {
+ { .compatible = "hid-over-i2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
+
+static const struct i2c_device_id i2c_hid_of_id_table[] = {
+ { "hid", 0 },
+ { "hid-over-i2c", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, i2c_hid_of_id_table);
+
+static struct i2c_driver i2c_hid_of_driver = {
+ .driver = {
+ .name = "i2c_hid_of",
+ .pm = &i2c_hid_core_pm,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .of_match_table = of_match_ptr(i2c_hid_of_match),
+ },
+
+ .probe = i2c_hid_of_probe,
+ .remove = i2c_hid_core_remove,
+ .shutdown = i2c_hid_core_shutdown,
+ .id_table = i2c_hid_of_id_table,
+};
+
+module_i2c_driver(i2c_hid_of_driver);
+
+MODULE_DESCRIPTION("HID over I2C OF driver");
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/i2c-hid/i2c-hid.h b/drivers/hid/i2c-hid/i2c-hid.h
index a8c19aef5824..05a7827d211a 100644
--- a/drivers/hid/i2c-hid/i2c-hid.h
+++ b/drivers/hid/i2c-hid/i2c-hid.h
@@ -3,6 +3,7 @@
#ifndef I2C_HID_H
#define I2C_HID_H
+#include <linux/i2c.h>
#ifdef CONFIG_DMI
struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name);
@@ -17,4 +18,25 @@ static inline char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name,
{ return NULL; }
#endif
+/**
+ * struct i2chid_ops - Ops provided to the core.
+ *
+ * @power_up: do sequencing to power up the device.
+ * @power_down: do sequencing to power down the device.
+ * @shutdown_tail: called at the end of shutdown.
+ */
+struct i2chid_ops {
+ int (*power_up)(struct i2chid_ops *ops);
+ void (*power_down)(struct i2chid_ops *ops);
+ void (*shutdown_tail)(struct i2chid_ops *ops);
+};
+
+int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
+ u16 hid_descriptor_address);
+int i2c_hid_core_remove(struct i2c_client *client);
+
+void i2c_hid_core_shutdown(struct i2c_client *client);
+
+extern const struct dev_pm_ops i2c_hid_core_pm;
+
#endif
diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
index 1fb294ca463e..21b87e4003af 100644
--- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h
+++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
@@ -27,6 +27,7 @@
#define CMP_H_DEVICE_ID 0x06FC
#define EHL_Ax_DEVICE_ID 0x4BB3
#define TGL_LP_DEVICE_ID 0xA0FC
+#define TGL_H_DEVICE_ID 0x43FC
#define REVISION_ID_CHT_A0 0x6
#define REVISION_ID_CHT_Ax_SI 0x0
@@ -81,5 +82,6 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev);
int ish_hw_start(struct ishtp_device *dev);
void ish_device_disable(struct ishtp_device *dev);
int ish_disable_dma(struct ishtp_device *dev);
+void ish_set_host_ready(struct ishtp_device *dev);
#endif /* _ISHTP_HW_ISH_H_ */
diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c
index a45ac7fa417b..47bbeb8b492b 100644
--- a/drivers/hid/intel-ish-hid/ipc/ipc.c
+++ b/drivers/hid/intel-ish-hid/ipc/ipc.c
@@ -193,6 +193,33 @@ static void ish_clr_host_rdy(struct ishtp_device *dev)
ish_reg_write(dev, IPC_REG_HOST_COMM, host_status);
}
+static bool ish_chk_host_rdy(struct ishtp_device *dev)
+{
+ uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM);
+
+ return (host_status & IPC_HOSTCOMM_READY_BIT);
+}
+
+/**
+ * ish_set_host_ready() - reconfig ipc host registers
+ * @dev: ishtp device pointer
+ *
+ * Set host to ready state
+ * This API is called in some case:
+ * fw is still on, but ipc is powered down.
+ * such as OOB case.
+ *
+ * Return: 0 for success else error fault code
+ */
+void ish_set_host_ready(struct ishtp_device *dev)
+{
+ if (ish_chk_host_rdy(dev))
+ return;
+
+ ish_set_host_rdy(dev);
+ set_host_ready(dev);
+}
+
/**
* _ishtp_read_hdr() - Read message header
* @dev: ISHTP device pointer
diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
index c6d48a8648b7..06081cf9b85a 100644
--- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c
+++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
@@ -5,6 +5,7 @@
* Copyright (c) 2014-2016, Intel Corporation.
*/
+#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
@@ -37,6 +38,7 @@ static const struct pci_device_id ish_pci_tbl[] = {
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CMP_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, EHL_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_LP_DEVICE_ID)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_H_DEVICE_ID)},
{0, }
};
MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
@@ -111,6 +113,42 @@ static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID;
}
+static int enable_gpe(struct device *dev)
+{
+#ifdef CONFIG_ACPI
+ acpi_status acpi_sts;
+ struct acpi_device *adev;
+ struct acpi_device_wakeup *wakeup;
+
+ adev = ACPI_COMPANION(dev);
+ if (!adev) {
+ dev_err(dev, "get acpi handle failed\n");
+ return -ENODEV;
+ }
+ wakeup = &adev->wakeup;
+
+ acpi_sts = acpi_enable_gpe(wakeup->gpe_device, wakeup->gpe_number);
+ if (ACPI_FAILURE(acpi_sts)) {
+ dev_err(dev, "enable ose_gpe failed\n");
+ return -EIO;
+ }
+
+ return 0;
+#else
+ return -ENODEV;
+#endif
+}
+
+static void enable_pme_wake(struct pci_dev *pdev)
+{
+ if ((pci_pme_capable(pdev, PCI_D0) ||
+ pci_pme_capable(pdev, PCI_D3hot) ||
+ pci_pme_capable(pdev, PCI_D3cold)) && !enable_gpe(&pdev->dev)) {
+ pci_pme_active(pdev, true);
+ dev_dbg(&pdev->dev, "ish ipc driver pme wake enabled\n");
+ }
+}
+
/**
* ish_probe() - PCI driver probe callback
* @pdev: pci device
@@ -179,6 +217,10 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
init_waitqueue_head(&ishtp->suspend_wait);
init_waitqueue_head(&ishtp->resume_wait);
+ /* Enable PME for EHL */
+ if (pdev->device == EHL_Ax_DEVICE_ID)
+ enable_pme_wake(pdev);
+
ret = ish_init(ishtp);
if (ret)
return ret;
@@ -218,11 +260,15 @@ static void __maybe_unused ish_resume_handler(struct work_struct *work)
{
struct pci_dev *pdev = to_pci_dev(ish_resume_device);
struct ishtp_device *dev = pci_get_drvdata(pdev);
+ uint32_t fwsts = dev->ops->get_fw_status(dev);
int ret;
- if (ish_should_leave_d0i3(pdev) && !dev->suspend_flag) {
+ if (ish_should_leave_d0i3(pdev) && !dev->suspend_flag
+ && IPC_IS_ISH_ILUP(fwsts)) {
disable_irq_wake(pdev->irq);
+ ish_set_host_ready(dev);
+
ishtp_send_resume(dev);
/* Waiting to get resume response */
@@ -317,6 +363,13 @@ static int __maybe_unused ish_resume(struct device *device)
struct pci_dev *pdev = to_pci_dev(device);
struct ishtp_device *dev = pci_get_drvdata(pdev);
+ /* add this to finish power flow for EHL */
+ if (dev->pdev->device == EHL_Ax_DEVICE_ID) {
+ pci_set_power_state(pdev, PCI_D0);
+ enable_pme_wake(pdev);
+ dev_dbg(dev->devc, "set power state to D0 for ehl\n");
+ }
+
ish_resume_device = device;
dev->resume_flag = 1;
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index aa9e48876ced..8328ef155c46 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -1825,7 +1825,7 @@ static ssize_t wacom_show_speed(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct wacom *wacom = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%i\n", wacom->wacom_wac.bt_high_speed);
+ return sysfs_emit(buf, "%i\n", wacom->wacom_wac.bt_high_speed);
}
static ssize_t wacom_store_speed(struct device *dev,
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index 1bd0eb71559c..44d715c12f6a 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -2600,7 +2600,12 @@ static void wacom_wac_finger_event(struct hid_device *hdev,
wacom_wac->is_invalid_bt_frame = !value;
return;
case HID_DG_CONTACTMAX:
- features->touch_max = value;
+ if (!features->touch_max) {
+ features->touch_max = value;
+ } else {
+ hid_warn(hdev, "%s: ignoring attempt to overwrite non-zero touch_max "
+ "%d -> %d\n", __func__, features->touch_max, value);
+ }
return;
}