Blame view

drivers/hid/hid-asus.c 28.8 KB
2874c5fd2   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
2
  /*
b94f7d5dd   Yusuke Fujimaki   HID: asus: add su...
3
   *  HID driver for Asus notebook built-in keyboard.
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
4
5
   *  Fixes small logical maximum to match usage maximum.
   *
b94f7d5dd   Yusuke Fujimaki   HID: asus: add su...
6
7
8
9
   *  Currently supported devices are:
   *    EeeBook X205TA
   *    VivoBook E200HA
   *
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
10
11
12
13
14
   *  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
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
15
16
17
18
19
20
   *
   *  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>
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
21
22
23
   */
  
  /*
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
24
   */
762f948c9   Hans de Goede   HID: asus: Add pr...
25
  #include <linux/dmi.h>
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
26
27
  #include <linux/hid.h>
  #include <linux/module.h>
3b692c55e   Daniel Drake   HID: asus: only s...
28
  #include <linux/platform_data/x86/asus-wmi.h>
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
29
  #include <linux/input/mt.h>
57573c541   Hans de Goede   HID: asus: Add su...
30
  #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
31
  #include <linux/power_supply.h>
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
32
33
  
  #include "hid-ids.h"
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
34
35
36
37
38
  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");
57573c541   Hans de Goede   HID: asus: Add su...
39
  #define T100_TPAD_INTF 2
73c75d395   Hans de Goede   HID: asus: Add T1...
40
  #define T100CHI_MOUSE_REPORT_ID 0x06
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
41
42
  #define FEATURE_REPORT_ID 0x0d
  #define INPUT_REPORT_ID 0x5d
af22a610b   Carlo Caione   HID: asus: suppor...
43
  #define FEATURE_KBD_REPORT_ID 0x5a
af22a610b   Carlo Caione   HID: asus: suppor...
44
45
46
  #define FEATURE_KBD_REPORT_SIZE 16
  
  #define SUPPORT_KBD_BACKLIGHT BIT(0)
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
47

9ce12d8be   Brendan McGrath   HID: asus: Add i2...
48
49
  #define MAX_TOUCH_MAJOR 8
  #define MAX_PRESSURE 128
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
50
51
52
53
54
55
  #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
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
56
57
58
59
60
61
  #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)
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
62
63
64
65
  #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)
0485b1ec2   Matjaz Hegedic   HID: asus: ignore...
66
  #define QUIRK_NO_CONSUMER_USAGES	BIT(4)
af22a610b   Carlo Caione   HID: asus: suppor...
67
  #define QUIRK_USE_KBD_BACKLIGHT		BIT(5)
76dd1fbeb   Hans de Goede   HID: asus: Add su...
68
  #define QUIRK_T100_KEYBOARD		BIT(6)
5703e52cc   Hans de Goede   HID: asus: Add T1...
69
  #define QUIRK_T100CHI			BIT(7)
832e1eeeb   Maxime Bellengé   HID: asus: Add su...
70
  #define QUIRK_G752_KEYBOARD		BIT(8)
0e1f37d12   Aleix Roca Nonell   HID: asus: Add su...
71
  #define QUIRK_T101HA_DOCK		BIT(9)
300c64d7f   NOGUCHI Hiroshi   HID: hid-asus: Ad...
72
  #define QUIRK_T90CHI			BIT(10)
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
73

a93913e14   Matjaz Hegedic   HID: asus: fix an...
74
  #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
0485b1ec2   Matjaz Hegedic   HID: asus: ignore...
75
76
  						 QUIRK_NO_INIT_REPORTS | \
  						 QUIRK_NO_CONSUMER_USAGES)
c81760b99   Hans de Goede   HID: asus: Parame...
77
  #define I2C_TOUCHPAD_QUIRKS			(QUIRK_NO_INIT_REPORTS | \
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
78
79
80
81
  						 QUIRK_SKIP_INPUT_MAPPING | \
  						 QUIRK_IS_MULTITOUCH)
  
  #define TRKID_SGN       ((TRKID_MAX + 1) >> 1)
af22a610b   Carlo Caione   HID: asus: suppor...
82
83
84
85
86
87
88
  struct asus_kbd_leds {
  	struct led_classdev cdev;
  	struct hid_device *hdev;
  	struct work_struct work;
  	unsigned int brightness;
  	bool removed;
  };
c81760b99   Hans de Goede   HID: asus: Parame...
89
90
91
  struct asus_touchpad_info {
  	int max_x;
  	int max_y;
b61d43e6b   Hans de Goede   HID: asus: Add T1...
92
93
  	int res_x;
  	int res_y;
c81760b99   Hans de Goede   HID: asus: Parame...
94
95
96
  	int contact_size;
  	int max_contacts;
  };
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
97
98
  struct asus_drvdata {
  	unsigned long quirks;
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
99
  	struct hid_device *hdev;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
100
  	struct input_dev *input;
af22a610b   Carlo Caione   HID: asus: suppor...
101
  	struct asus_kbd_leds *kbd_backlight;
c81760b99   Hans de Goede   HID: asus: Parame...
102
  	const struct asus_touchpad_info *tp;
af22a610b   Carlo Caione   HID: asus: suppor...
103
  	bool enable_backlight;
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
104
105
106
107
108
109
  	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;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
110
  };
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
111
  static int asus_report_battery(struct asus_drvdata *, u8 *, int);
c81760b99   Hans de Goede   HID: asus: Parame...
112
113
114
115
116
117
118
119
120
  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,
25cc2611a   Hans de Goede   HID: asus: Fix T1...
121
  	.max_y = 1120,
b61d43e6b   Hans de Goede   HID: asus: Add T1...
122
123
  	.res_x = 30, /* units/mm */
  	.res_y = 27, /* units/mm */
c81760b99   Hans de Goede   HID: asus: Parame...
124
125
126
  	.contact_size = 5,
  	.max_contacts = 5,
  };
762f948c9   Hans de Goede   HID: asus: Add pr...
127
128
129
130
131
132
133
134
  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,
  };
dbd3ef28e   Hans de Goede   HID: asus: Add to...
135
136
137
138
139
140
141
142
  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,
  };
73c75d395   Hans de Goede   HID: asus: Add T1...
143
144
145
146
147
148
149
150
  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,
  };
c81760b99   Hans de Goede   HID: asus: Parame...
151
  static void asus_report_contact_down(struct asus_drvdata *drvdat,
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
152
153
  		int toolType, u8 *data)
  {
c81760b99   Hans de Goede   HID: asus: Parame...
154
155
156
157
158
  	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]);
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
159

73c75d395   Hans de Goede   HID: asus: Add T1...
160
161
162
163
164
  	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;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
165
166
167
168
169
170
171
  	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;
  	}
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
172
173
174
175
176
  	input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major);
  	input_report_abs(input, ABS_MT_PRESSURE, pressure);
  }
  
  /* Required for Synaptics Palm Detection */
c81760b99   Hans de Goede   HID: asus: Parame...
177
  static void asus_report_tool_width(struct asus_drvdata *drvdat)
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
178
  {
c81760b99   Hans de Goede   HID: asus: Parame...
179
  	struct input_mt *mt = drvdat->input->mt;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
180
181
  	struct input_mt_slot *oldest;
  	int oldid, count, i;
73c75d395   Hans de Goede   HID: asus: Add T1...
182
183
  	if (drvdat->tp->contact_size < 5)
  		return;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
  	oldest = NULL;
  	oldid = mt->trkid;
  	count = 0;
  
  	for (i = 0; i < mt->num_slots; ++i) {
  		struct input_mt_slot *ps = &mt->slots[i];
  		int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
  
  		if (id < 0)
  			continue;
  		if ((id - oldid) & TRKID_SGN) {
  			oldest = ps;
  			oldid = id;
  		}
  		count++;
  	}
  
  	if (oldest) {
c81760b99   Hans de Goede   HID: asus: Parame...
202
  		input_report_abs(drvdat->input, ABS_TOOL_WIDTH,
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
203
204
205
  			input_mt_get_value(oldest, ABS_MT_TOUCH_MAJOR));
  	}
  }
c81760b99   Hans de Goede   HID: asus: Parame...
206
  static int asus_report_input(struct asus_drvdata *drvdat, u8 *data, int size)
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
207
  {
73c75d395   Hans de Goede   HID: asus: Add T1...
208
  	int i, toolType = MT_TOOL_FINGER;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
209
  	u8 *contactData = data + 2;
c81760b99   Hans de Goede   HID: asus: Parame...
210
211
212
213
  	if (size != 3 + drvdat->tp->contact_size * drvdat->tp->max_contacts)
  		return 0;
  
  	for (i = 0; i < drvdat->tp->max_contacts; i++) {
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
214
  		bool down = !!(data[1] & BIT(i+3));
73c75d395   Hans de Goede   HID: asus: Add T1...
215
216
217
  
  		if (drvdat->tp->contact_size >= 5)
  			toolType = contactData[3] & CONTACT_TOOL_TYPE_MASK ?
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
218
  						MT_TOOL_PALM : MT_TOOL_FINGER;
c81760b99   Hans de Goede   HID: asus: Parame...
219
220
  		input_mt_slot(drvdat->input, i);
  		input_mt_report_slot_state(drvdat->input, toolType, down);
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
221
222
  
  		if (down) {
c81760b99   Hans de Goede   HID: asus: Parame...
223
224
  			asus_report_contact_down(drvdat, toolType, contactData);
  			contactData += drvdat->tp->contact_size;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
225
226
  		}
  	}
c81760b99   Hans de Goede   HID: asus: Parame...
227
228
229
230
231
  	input_report_key(drvdat->input, BTN_LEFT, data[1] & BTN_LEFT_MASK);
  	asus_report_tool_width(drvdat);
  
  	input_mt_sync_frame(drvdat->input);
  	input_sync(drvdat->input);
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
232

c81760b99   Hans de Goede   HID: asus: Parame...
233
  	return 1;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
234
  }
e98e38090   Hans de Goede   HID: asus: Add ev...
235
236
237
238
  static int asus_event(struct hid_device *hdev, struct hid_field *field,
  		      struct hid_usage *usage, __s32 value)
  {
  	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
854f39866   Hans de Goede   HID: asus: Ignore...
239
240
  	    (usage->hid & HID_USAGE) != 0x00 &&
  	    (usage->hid & HID_USAGE) != 0xff && !usage->type) {
e98e38090   Hans de Goede   HID: asus: Add ev...
241
242
243
244
245
246
247
  		hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x
  ",
  			 usage->hid & HID_USAGE);
  	}
  
  	return 0;
  }
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
248
249
250
251
  static int asus_raw_event(struct hid_device *hdev,
  		struct hid_report *report, u8 *data, int size)
  {
  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
252
253
  	if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
  		return asus_report_battery(drvdata, data, size);
c81760b99   Hans de Goede   HID: asus: Parame...
254
255
  	if (drvdata->tp && data[0] == INPUT_REPORT_ID)
  		return asus_report_input(drvdata, data, size);
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
256
257
258
  
  	return 0;
  }
af22a610b   Carlo Caione   HID: asus: suppor...
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
  static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size)
  {
  	unsigned char *dmabuf;
  	int ret;
  
  	dmabuf = kmemdup(buf, buf_size, GFP_KERNEL);
  	if (!dmabuf)
  		return -ENOMEM;
  
  	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf,
  				 buf_size, HID_FEATURE_REPORT,
  				 HID_REQ_SET_REPORT);
  	kfree(dmabuf);
  
  	return ret;
  }
  
  static int asus_kbd_init(struct hid_device *hdev)
  {
  	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
  		     0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
  	int ret;
  
  	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
  	if (ret < 0)
  		hid_err(hdev, "Asus failed to send init command: %d
  ", ret);
  
  	return ret;
  }
  
  static int asus_kbd_get_functions(struct hid_device *hdev,
  				  unsigned char *kbd_func)
  {
  	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x05, 0x20, 0x31, 0x00, 0x08 };
  	u8 *readbuf;
  	int ret;
  
  	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
  	if (ret < 0) {
  		hid_err(hdev, "Asus failed to send configuration command: %d
  ", ret);
  		return ret;
  	}
  
  	readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
  	if (!readbuf)
  		return -ENOMEM;
  
  	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
  				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
  				 HID_REQ_GET_REPORT);
  	if (ret < 0) {
  		hid_err(hdev, "Asus failed to request functions: %d
  ", ret);
  		kfree(readbuf);
  		return ret;
  	}
  
  	*kbd_func = readbuf[6];
  
  	kfree(readbuf);
  	return ret;
  }
  
  static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
  				   enum led_brightness brightness)
  {
  	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
  						 cdev);
  	if (led->brightness == brightness)
  		return;
  
  	led->brightness = brightness;
  	schedule_work(&led->work);
  }
  
  static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
  {
  	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
  						 cdev);
  
  	return led->brightness;
  }
  
  static void asus_kbd_backlight_work(struct work_struct *work)
  {
  	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
  	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
  	int ret;
  
  	if (led->removed)
  		return;
  
  	buf[4] = led->brightness;
  
  	ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf));
  	if (ret < 0)
  		hid_err(led->hdev, "Asus failed to set keyboard backlight: %d
  ", ret);
  }
3b692c55e   Daniel Drake   HID: asus: only s...
360
361
362
363
364
365
366
367
  /* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
   * precedence. We only activate HID-based backlight control when the
   * WMI control is not available.
   */
  static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
  {
  	u32 value;
  	int ret;
3fc202e81   Arnd Bergmann   HID: asus: fix bu...
368
369
  	if (!IS_ENABLED(CONFIG_ASUS_WMI))
  		return false;
e0668f288   Yurii Pavlovskyi   platform/x86: asu...
370
  	ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
3b692c55e   Daniel Drake   HID: asus: only s...
371
372
373
374
375
376
377
  				       ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
  	hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
  	if (ret)
  		return false;
  
  	return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
  }
af22a610b   Carlo Caione   HID: asus: suppor...
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
  static int asus_kbd_register_leds(struct hid_device *hdev)
  {
  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
  	unsigned char kbd_func;
  	int ret;
  
  	/* Initialize keyboard */
  	ret = asus_kbd_init(hdev);
  	if (ret < 0)
  		return ret;
  
  	/* Get keyboard functions */
  	ret = asus_kbd_get_functions(hdev, &kbd_func);
  	if (ret < 0)
  		return ret;
  
  	/* Check for backlight support */
  	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
  		return -ENODEV;
  
  	drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
  					      sizeof(struct asus_kbd_leds),
  					      GFP_KERNEL);
  	if (!drvdata->kbd_backlight)
  		return -ENOMEM;
  
  	drvdata->kbd_backlight->removed = false;
  	drvdata->kbd_backlight->brightness = 0;
  	drvdata->kbd_backlight->hdev = hdev;
  	drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
  	drvdata->kbd_backlight->cdev.max_brightness = 3;
  	drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
  	drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
  	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
  
  	ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
  	if (ret < 0) {
  		/* No need to have this still around */
  		devm_kfree(&hdev->dev, drvdata->kbd_backlight);
  	}
  
  	return ret;
  }
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
  /*
   * [0]       REPORT_ID (same value defined in report descriptor)
   * [1]	     rest battery level. range [0..255]
   * [2]..[7]  Bluetooth hardware address (MAC address)
   * [8]       charging status
   *            = 0 : AC offline / discharging
   *            = 1 : AC online  / charging
   *            = 2 : AC online  / fully charged
   */
  static int asus_parse_battery(struct asus_drvdata *drvdata, u8 *data, int size)
  {
  	u8 sts;
  	u8 lvl;
  	int val;
  
  	lvl = data[1];
  	sts = data[8];
  
  	drvdata->battery_capacity = ((int)lvl * 100) / (int)BATTERY_LEVEL_MAX;
  
  	switch (sts) {
  	case BATTERY_STAT_CHARGING:
  		val = POWER_SUPPLY_STATUS_CHARGING;
  		break;
  	case BATTERY_STAT_FULL:
  		val = POWER_SUPPLY_STATUS_FULL;
  		break;
  	case BATTERY_STAT_DISCONNECT:
  	default:
  		val = POWER_SUPPLY_STATUS_DISCHARGING;
  		break;
  	}
  	drvdata->battery_stat = val;
  
  	return 0;
  }
  
  static int asus_report_battery(struct asus_drvdata *drvdata, u8 *data, int size)
  {
  	/* notify only the autonomous event by device */
  	if ((drvdata->battery_in_query == false) &&
  			 (size == BATTERY_REPORT_SIZE))
  		power_supply_changed(drvdata->battery);
  
  	return 0;
  }
  
  static int asus_battery_query(struct asus_drvdata *drvdata)
  {
  	u8 *buf;
  	int ret = 0;
  
  	buf = kmalloc(BATTERY_REPORT_SIZE, GFP_KERNEL);
  	if (!buf)
  		return -ENOMEM;
  
  	drvdata->battery_in_query = true;
  	ret = hid_hw_raw_request(drvdata->hdev, BATTERY_REPORT_ID,
  				buf, BATTERY_REPORT_SIZE,
  				HID_INPUT_REPORT, HID_REQ_GET_REPORT);
  	drvdata->battery_in_query = false;
  	if (ret == BATTERY_REPORT_SIZE)
  		ret = asus_parse_battery(drvdata, buf, BATTERY_REPORT_SIZE);
  	else
  		ret = -ENODATA;
  
  	kfree(buf);
  
  	return ret;
  }
  
  static enum power_supply_property asus_battery_props[] = {
  	POWER_SUPPLY_PROP_STATUS,
  	POWER_SUPPLY_PROP_PRESENT,
  	POWER_SUPPLY_PROP_CAPACITY,
  	POWER_SUPPLY_PROP_SCOPE,
  	POWER_SUPPLY_PROP_MODEL_NAME,
  };
  
  #define	QUERY_MIN_INTERVAL	(60 * HZ)	/* 60[sec] */
  
  static int asus_battery_get_property(struct power_supply *psy,
  				enum power_supply_property psp,
  				union power_supply_propval *val)
  {
  	struct asus_drvdata *drvdata = power_supply_get_drvdata(psy);
  	int ret = 0;
  
  	switch (psp) {
  	case POWER_SUPPLY_PROP_STATUS:
  	case POWER_SUPPLY_PROP_CAPACITY:
  		if (time_before(drvdata->battery_next_query, jiffies)) {
  			drvdata->battery_next_query =
  					 jiffies + QUERY_MIN_INTERVAL;
  			ret = asus_battery_query(drvdata);
  			if (ret)
  				return ret;
  		}
  		if (psp == POWER_SUPPLY_PROP_STATUS)
  			val->intval = drvdata->battery_stat;
  		else
  			val->intval = drvdata->battery_capacity;
  		break;
  	case POWER_SUPPLY_PROP_PRESENT:
  		val->intval = 1;
  		break;
  	case POWER_SUPPLY_PROP_SCOPE:
  		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
  		break;
  	case POWER_SUPPLY_PROP_MODEL_NAME:
  		val->strval = drvdata->hdev->name;
  		break;
  	default:
  		ret = -EINVAL;
  		break;
  	}
  
  	return ret;
  }
  
  static int asus_battery_probe(struct hid_device *hdev)
  {
  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
  	struct power_supply_config pscfg = { .drv_data = drvdata };
  	int ret = 0;
  
  	drvdata->battery_capacity = 0;
  	drvdata->battery_stat = POWER_SUPPLY_STATUS_UNKNOWN;
  	drvdata->battery_in_query = false;
  
  	drvdata->battery_desc.properties = asus_battery_props;
  	drvdata->battery_desc.num_properties = ARRAY_SIZE(asus_battery_props);
  	drvdata->battery_desc.get_property = asus_battery_get_property;
  	drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
  	drvdata->battery_desc.use_for_apm = 0;
  	drvdata->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
  					"asus-keyboard-%s-battery",
  					strlen(hdev->uniq) ?
  					hdev->uniq : dev_name(&hdev->dev));
  	if (!drvdata->battery_desc.name)
  		return -ENOMEM;
  
  	drvdata->battery_next_query = jiffies;
  
  	drvdata->battery = devm_power_supply_register(&hdev->dev,
  				&(drvdata->battery_desc), &pscfg);
  	if (IS_ERR(drvdata->battery)) {
  		ret = PTR_ERR(drvdata->battery);
  		drvdata->battery = NULL;
  		hid_err(hdev, "Unable to register battery device
  ");
  		return ret;
  	}
  
  	power_supply_powers(drvdata->battery, &hdev->dev);
  
  	return ret;
  }
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
579
580
  static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
  {
c8b1b3dd8   Brendan McGrath   HID: asus: Fix ke...
581
  	struct input_dev *input = hi->input;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
582
  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
73c75d395   Hans de Goede   HID: asus: Add T1...
583
584
585
586
  	/* T100CHI uses MULTI_INPUT, bind the touchpad to the mouse hid_input */
  	if (drvdata->quirks & QUIRK_T100CHI &&
  	    hi->report->id != T100CHI_MOUSE_REPORT_ID)
  		return 0;
c81760b99   Hans de Goede   HID: asus: Parame...
587
  	if (drvdata->tp) {
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
588
  		int ret;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
589

c81760b99   Hans de Goede   HID: asus: Parame...
590
591
592
593
  		input_set_abs_params(input, ABS_MT_POSITION_X, 0,
  				     drvdata->tp->max_x, 0, 0);
  		input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
  				     drvdata->tp->max_y, 0, 0);
b61d43e6b   Hans de Goede   HID: asus: Add T1...
594
595
  		input_abs_set_res(input, ABS_MT_POSITION_X, drvdata->tp->res_x);
  		input_abs_set_res(input, ABS_MT_POSITION_Y, drvdata->tp->res_y);
73c75d395   Hans de Goede   HID: asus: Add T1...
596
597
598
599
600
601
602
603
604
  
  		if (drvdata->tp->contact_size >= 5) {
  			input_set_abs_params(input, ABS_TOOL_WIDTH, 0,
  					     MAX_TOUCH_MAJOR, 0, 0);
  			input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0,
  					     MAX_TOUCH_MAJOR, 0, 0);
  			input_set_abs_params(input, ABS_MT_PRESSURE, 0,
  					      MAX_PRESSURE, 0, 0);
  		}
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
605
606
607
  
  		__set_bit(BTN_LEFT, input->keybit);
  		__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
c81760b99   Hans de Goede   HID: asus: Parame...
608
609
  		ret = input_mt_init_slots(input, drvdata->tp->max_contacts,
  					  INPUT_MT_POINTER);
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
610
611
612
613
614
615
  
  		if (ret) {
  			hid_err(hdev, "Asus input mt init slots failed: %d
  ", ret);
  			return ret;
  		}
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
616
  	}
c8b1b3dd8   Brendan McGrath   HID: asus: Fix ke...
617
  	drvdata->input = input;
3b692c55e   Daniel Drake   HID: asus: only s...
618
619
620
  	if (drvdata->enable_backlight &&
  	    !asus_kbd_wmi_led_control_present(hdev) &&
  	    asus_kbd_register_leds(hdev))
af22a610b   Carlo Caione   HID: asus: suppor...
621
622
  		hid_warn(hdev, "Failed to initialize backlight.
  ");
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
623
624
  	return 0;
  }
a93913e14   Matjaz Hegedic   HID: asus: fix an...
625
  #define asus_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, \
1caccc256   Chris Chiu   HID: asus: suppor...
626
  						    max, EV_KEY, (c))
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
627
628
629
630
631
632
633
634
635
636
637
638
639
  static int asus_input_mapping(struct hid_device *hdev,
  		struct hid_input *hi, struct hid_field *field,
  		struct hid_usage *usage, unsigned long **bit,
  		int *max)
  {
  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
  
  	if (drvdata->quirks & QUIRK_SKIP_INPUT_MAPPING) {
  		/* Don't map anything from the HID report.
  		 * We do it all manually in asus_input_configured
  		 */
  		return -1;
  	}
73c75d395   Hans de Goede   HID: asus: Add T1...
640
641
642
643
644
  	/*
  	 * Ignore a bunch of bogus collections in the T100CHI descriptor.
  	 * This avoids a bunch of non-functional hid_input devices getting
  	 * created because of the T100CHI using HID_QUIRK_MULTI_INPUT.
  	 */
300c64d7f   NOGUCHI Hiroshi   HID: hid-asus: Ad...
645
  	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
73c75d395   Hans de Goede   HID: asus: Add T1...
646
647
648
649
650
651
652
653
654
655
656
657
658
  		if (field->application == (HID_UP_GENDESK | 0x0080) ||
  		    usage->hid == (HID_UP_GENDEVCTRLS | 0x0024) ||
  		    usage->hid == (HID_UP_GENDEVCTRLS | 0x0025) ||
  		    usage->hid == (HID_UP_GENDEVCTRLS | 0x0026))
  			return -1;
  		/*
  		 * We use the hid_input for the mouse report for the touchpad,
  		 * keep the left button, to avoid the core removing it.
  		 */
  		if (field->application == HID_GD_MOUSE &&
  		    usage->hid != (HID_UP_BUTTON | 1))
  			return -1;
  	}
a93913e14   Matjaz Hegedic   HID: asus: fix an...
659
  	/* ASUS-specific keyboard hotkeys */
1caccc256   Chris Chiu   HID: asus: suppor...
660
661
662
  	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) {
  		set_bit(EV_REP, hi->input->evbit);
  		switch (usage->hid & HID_USAGE) {
a93913e14   Matjaz Hegedic   HID: asus: fix an...
663
664
665
666
  		case 0x10: asus_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
  		case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP);		break;
  		case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF);		break;
  		case 0x6c: asus_map_key_clear(KEY_SLEEP);		break;
2340bad5d   Hans de Goede   HID: asus: Add su...
667
  		case 0x7c: asus_map_key_clear(KEY_MICMUTE);		break;
a93913e14   Matjaz Hegedic   HID: asus: fix an...
668
  		case 0x82: asus_map_key_clear(KEY_CAMERA);		break;
802b24b47   Matjaz Hegedic   HID: asus: change...
669
  		case 0x88: asus_map_key_clear(KEY_RFKILL);			break;
a93913e14   Matjaz Hegedic   HID: asus: fix an...
670
671
672
  		case 0xb5: asus_map_key_clear(KEY_CALC);			break;
  		case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP);		break;
  		case 0xc5: asus_map_key_clear(KEY_KBDILLUMDOWN);		break;
1caccc256   Chris Chiu   HID: asus: suppor...
673
674
  
  		/* ASUS touchpad toggle */
a93913e14   Matjaz Hegedic   HID: asus: fix an...
675
  		case 0x6b: asus_map_key_clear(KEY_F21);			break;
1caccc256   Chris Chiu   HID: asus: suppor...
676
677
  
  		/* ROG key */
a93913e14   Matjaz Hegedic   HID: asus: fix an...
678
  		case 0x38: asus_map_key_clear(KEY_PROG1);		break;
1caccc256   Chris Chiu   HID: asus: suppor...
679
680
  
  		/* Fn+C ASUS Splendid */
a93913e14   Matjaz Hegedic   HID: asus: fix an...
681
  		case 0xba: asus_map_key_clear(KEY_PROG2);		break;
1caccc256   Chris Chiu   HID: asus: suppor...
682
683
  
  		/* Fn+Space Power4Gear Hybrid */
a93913e14   Matjaz Hegedic   HID: asus: fix an...
684
  		case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
1caccc256   Chris Chiu   HID: asus: suppor...
685

2340bad5d   Hans de Goede   HID: asus: Add su...
686
687
  		/* Fn+F5 "fan" symbol on FX503VD */
  		case 0x99: asus_map_key_clear(KEY_PROG4);		break;
1caccc256   Chris Chiu   HID: asus: suppor...
688
  		default:
0485b1ec2   Matjaz Hegedic   HID: asus: ignore...
689
690
691
  			/* ASUS lazily declares 256 usages, ignore the rest,
  			 * as some make the keyboard appear as a pointer device. */
  			return -1;
1caccc256   Chris Chiu   HID: asus: suppor...
692
  		}
af22a610b   Carlo Caione   HID: asus: suppor...
693
694
695
696
697
698
699
700
701
  
  		/*
  		 * Check and enable backlight only on devices with UsagePage ==
  		 * 0xff31 to avoid initializing the keyboard firmware multiple
  		 * times on devices with multiple HID descriptors but same
  		 * PID/VID.
  		 */
  		if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
  			drvdata->enable_backlight = true;
1caccc256   Chris Chiu   HID: asus: suppor...
702
703
  		return 1;
  	}
5be918035   Daniel Drake   HID: move Asus ke...
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
  	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) {
  		set_bit(EV_REP, hi->input->evbit);
  		switch (usage->hid & HID_USAGE) {
  		case 0xff01: asus_map_key_clear(BTN_1);	break;
  		case 0xff02: asus_map_key_clear(BTN_2);	break;
  		case 0xff03: asus_map_key_clear(BTN_3);	break;
  		case 0xff04: asus_map_key_clear(BTN_4);	break;
  		case 0xff05: asus_map_key_clear(BTN_5);	break;
  		case 0xff06: asus_map_key_clear(BTN_6);	break;
  		case 0xff07: asus_map_key_clear(BTN_7);	break;
  		case 0xff08: asus_map_key_clear(BTN_8);	break;
  		case 0xff09: asus_map_key_clear(BTN_9);	break;
  		case 0xff0a: asus_map_key_clear(BTN_A);	break;
  		case 0xff0b: asus_map_key_clear(BTN_B);	break;
  		case 0x00f1: asus_map_key_clear(KEY_WLAN);	break;
  		case 0x00f2: asus_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
  		case 0x00f3: asus_map_key_clear(KEY_BRIGHTNESSUP);	break;
  		case 0x00f4: asus_map_key_clear(KEY_DISPLAY_OFF);	break;
  		case 0x00f7: asus_map_key_clear(KEY_CAMERA);	break;
  		case 0x00f8: asus_map_key_clear(KEY_PROG1);	break;
  		default:
  			return 0;
  		}
  
  		return 1;
  	}
0485b1ec2   Matjaz Hegedic   HID: asus: ignore...
730
731
732
733
734
735
736
737
738
739
740
741
742
743
  	if (drvdata->quirks & QUIRK_NO_CONSUMER_USAGES &&
  		(usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
  		switch (usage->hid & HID_USAGE) {
  		case 0xe2: /* Mute */
  		case 0xe9: /* Volume up */
  		case 0xea: /* Volume down */
  			return 0;
  		default:
  			/* Ignore dummy Consumer usages which make the
  			 * keyboard incorrectly appear as a pointer device.
  			 */
  			return -1;
  		}
  	}
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
744
745
746
747
748
749
  	return 0;
  }
  
  static int asus_start_multitouch(struct hid_device *hdev)
  {
  	int ret;
b9ec70092   Colin Ian King   HID: asus: make a...
750
751
752
  	static const unsigned char buf[] = {
  		FEATURE_REPORT_ID, 0x00, 0x03, 0x01, 0x00
  	};
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
  	unsigned char *dmabuf = kmemdup(buf, sizeof(buf), GFP_KERNEL);
  
  	if (!dmabuf) {
  		ret = -ENOMEM;
  		hid_err(hdev, "Asus failed to alloc dma buf: %d
  ", ret);
  		return ret;
  	}
  
  	ret = hid_hw_raw_request(hdev, dmabuf[0], dmabuf, sizeof(buf),
  					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
  
  	kfree(dmabuf);
  
  	if (ret != sizeof(buf)) {
  		hid_err(hdev, "Asus failed to start multitouch: %d
  ", ret);
  		return ret;
  	}
  
  	return 0;
  }
  
  static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
  {
  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
c81760b99   Hans de Goede   HID: asus: Parame...
779
  	if (drvdata->tp)
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
  		return asus_start_multitouch(hdev);
  
  	return 0;
  }
  
  static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
  {
  	int ret;
  	struct asus_drvdata *drvdata;
  
  	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
  	if (drvdata == NULL) {
  		hid_err(hdev, "Can't alloc Asus descriptor
  ");
  		return -ENOMEM;
  	}
  
  	hid_set_drvdata(hdev, drvdata);
  
  	drvdata->quirks = id->driver_data;
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
800
801
802
803
  	/*
  	 * T90CHI's keyboard dock returns same ID values as T100CHI's dock.
  	 * Thus, identify T90CHI dock with product name string.
  	 */
300c64d7f   NOGUCHI Hiroshi   HID: hid-asus: Ad...
804
805
806
807
  	if (strstr(hdev->name, "T90CHI")) {
  		drvdata->quirks &= ~QUIRK_T100CHI;
  		drvdata->quirks |= QUIRK_T90CHI;
  	}
c81760b99   Hans de Goede   HID: asus: Parame...
808
809
  	if (drvdata->quirks & QUIRK_IS_MULTITOUCH)
  		drvdata->tp = &asus_i2c_tp;
57573c541   Hans de Goede   HID: asus: Add su...
810
811
  	if (drvdata->quirks & QUIRK_T100_KEYBOARD) {
  		struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
c81760b99   Hans de Goede   HID: asus: Parame...
812
813
  		if (intf->altsetting->desc.bInterfaceNumber == T100_TPAD_INTF) {
  			drvdata->quirks = QUIRK_SKIP_INPUT_MAPPING;
762f948c9   Hans de Goede   HID: asus: Add pr...
814
  			/*
dbd3ef28e   Hans de Goede   HID: asus: Add to...
815
816
817
  			 * The T100HA uses the same USB-ids as the T100TAF and
  			 * the T200TA uses the same USB-ids as the T100TA, while
  			 * both have different max x/y values as the T100TA[F].
762f948c9   Hans de Goede   HID: asus: Add pr...
818
819
820
  			 */
  			if (dmi_match(DMI_PRODUCT_NAME, "T100HAN"))
  				drvdata->tp = &asus_t100ha_tp;
dbd3ef28e   Hans de Goede   HID: asus: Add to...
821
822
  			else if (dmi_match(DMI_PRODUCT_NAME, "T200TA"))
  				drvdata->tp = &asus_t200ta_tp;
762f948c9   Hans de Goede   HID: asus: Add pr...
823
824
  			else
  				drvdata->tp = &asus_t100ta_tp;
c81760b99   Hans de Goede   HID: asus: Parame...
825
  		}
57573c541   Hans de Goede   HID: asus: Add su...
826
  	}
73c75d395   Hans de Goede   HID: asus: Add T1...
827
828
829
830
831
  	if (drvdata->quirks & QUIRK_T100CHI) {
  		/*
  		 * All functionality is on a single HID interface and for
  		 * userspace the touchpad must be a separate input_dev.
  		 */
39335d1cb   Benjamin Tissoires   HID: core: remove...
832
  		hdev->quirks |= HID_QUIRK_MULTI_INPUT;
73c75d395   Hans de Goede   HID: asus: Add T1...
833
834
  		drvdata->tp = &asus_t100chi_tp;
  	}
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
835
836
  	if (drvdata->quirks & QUIRK_NO_INIT_REPORTS)
  		hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
6311d329e   NOGUCHI Hiroshi   HID: hid-asus: Ad...
837
838
839
840
841
842
843
844
845
846
847
  	drvdata->hdev = hdev;
  
  	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
  		ret = asus_battery_probe(hdev);
  		if (ret) {
  			hid_err(hdev,
  			    "Asus hid battery_probe failed: %d
  ", ret);
  			return ret;
  		}
  	}
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
848
849
850
851
852
853
  	ret = hid_parse(hdev);
  	if (ret) {
  		hid_err(hdev, "Asus hid parse failed: %d
  ", ret);
  		return ret;
  	}
0e1f37d12   Aleix Roca Nonell   HID: asus: Add su...
854
855
856
857
  	/* use hid-multitouch for T101HA touchpad */
  	if (id->driver_data & QUIRK_T101HA_DOCK &&
  	    hdev->collection->usage == HID_GD_MOUSE)
  		return -ENODEV;
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
858
859
860
861
862
863
864
865
866
867
868
869
870
  	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
  	if (ret) {
  		hid_err(hdev, "Asus hw start failed: %d
  ", ret);
  		return ret;
  	}
  
  	if (!drvdata->input) {
  		hid_err(hdev, "Asus input not registered
  ");
  		ret = -ENOMEM;
  		goto err_stop_hw;
  	}
c81760b99   Hans de Goede   HID: asus: Parame...
871
  	if (drvdata->tp) {
c8b1b3dd8   Brendan McGrath   HID: asus: Fix ke...
872
873
874
875
  		drvdata->input->name = "Asus TouchPad";
  	} else {
  		drvdata->input->name = "Asus Keyboard";
  	}
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
876

c81760b99   Hans de Goede   HID: asus: Parame...
877
  	if (drvdata->tp) {
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
878
879
880
881
882
883
884
885
886
887
  		ret = asus_start_multitouch(hdev);
  		if (ret)
  			goto err_stop_hw;
  	}
  
  	return 0;
  err_stop_hw:
  	hid_hw_stop(hdev);
  	return ret;
  }
af22a610b   Carlo Caione   HID: asus: suppor...
888
889
890
891
892
893
894
895
  static void asus_remove(struct hid_device *hdev)
  {
  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
  
  	if (drvdata->kbd_backlight) {
  		drvdata->kbd_backlight->removed = true;
  		cancel_work_sync(&drvdata->kbd_backlight->work);
  	}
715e944f8   Carlo Caione   HID: asus: Stop u...
896
897
  
  	hid_hw_stop(hdev);
af22a610b   Carlo Caione   HID: asus: suppor...
898
  }
832e1eeeb   Maxime Bellengé   HID: asus: Add su...
899
900
901
902
  static const __u8 asus_g752_fixed_rdesc[] = {
          0x19, 0x00,			/*   Usage Minimum (0x00)       */
          0x2A, 0xFF, 0x00,		/*   Usage Maximum (0xFF)       */
  };
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
903
904
905
  static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
  		unsigned int *rsize)
  {
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
906
907
908
909
  	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
  
  	if (drvdata->quirks & QUIRK_FIX_NOTEBOOK_REPORT &&
  			*rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x65) {
b94f7d5dd   Yusuke Fujimaki   HID: asus: add su...
910
911
  		hid_info(hdev, "Fixing up Asus notebook report descriptor
  ");
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
912
913
  		rdesc[55] = 0xdd;
  	}
33edee4f3   Hans de Goede   HID: asus: Fix sp...
914
  	/* For the T100TA/T200TA keyboard dock */
76dd1fbeb   Hans de Goede   HID: asus: Add su...
915
  	if (drvdata->quirks & QUIRK_T100_KEYBOARD &&
33edee4f3   Hans de Goede   HID: asus: Fix sp...
916
917
  		 (*rsize == 76 || *rsize == 101) &&
  		 rdesc[73] == 0x81 && rdesc[74] == 0x01) {
76dd1fbeb   Hans de Goede   HID: asus: Add su...
918
919
920
921
  		hid_info(hdev, "Fixing up Asus T100 keyb report descriptor
  ");
  		rdesc[74] &= ~HID_MAIN_ITEM_CONSTANT;
  	}
300c64d7f   NOGUCHI Hiroshi   HID: hid-asus: Ad...
922
923
924
925
926
927
928
929
930
931
932
933
  	/* For the T100CHI/T90CHI keyboard dock */
  	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
  		int rsize_orig;
  		int offs;
  
  		if (drvdata->quirks & QUIRK_T100CHI) {
  			rsize_orig = 403;
  			offs = 388;
  		} else {
  			rsize_orig = 306;
  			offs = 291;
  		}
5703e52cc   Hans de Goede   HID: asus: Add T1...
934
935
936
937
938
939
  		/*
  		 * Change Usage (76h) to Usage Minimum (00h), Usage Maximum
  		 * (FFh) and clear the flags in the Input() byte.
  		 * Note the descriptor has a bogus 0 byte at the end so we
  		 * only need 1 extra byte.
  		 */
300c64d7f   NOGUCHI Hiroshi   HID: hid-asus: Ad...
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
  		if (*rsize == rsize_orig &&
  			rdesc[offs] == 0x09 && rdesc[offs + 1] == 0x76) {
  			*rsize = rsize_orig + 1;
  			rdesc = kmemdup(rdesc, *rsize, GFP_KERNEL);
  			if (!rdesc)
  				return NULL;
  
  			hid_info(hdev, "Fixing up %s keyb report descriptor
  ",
  				drvdata->quirks & QUIRK_T100CHI ?
  				"T100CHI" : "T90CHI");
  			memmove(rdesc + offs + 4, rdesc + offs + 2, 12);
  			rdesc[offs] = 0x19;
  			rdesc[offs + 1] = 0x00;
  			rdesc[offs + 2] = 0x29;
  			rdesc[offs + 3] = 0xff;
  			rdesc[offs + 14] = 0x00;
  		}
5703e52cc   Hans de Goede   HID: asus: Add T1...
958
  	}
300c64d7f   NOGUCHI Hiroshi   HID: hid-asus: Ad...
959

832e1eeeb   Maxime Bellengé   HID: asus: Add su...
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
  	if (drvdata->quirks & QUIRK_G752_KEYBOARD &&
  		 *rsize == 75 && rdesc[61] == 0x15 && rdesc[62] == 0x00) {
  		/* report is missing usage mninum and maximum */
  		__u8 *new_rdesc;
  		size_t new_size = *rsize + sizeof(asus_g752_fixed_rdesc);
  
  		new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
  		if (new_rdesc == NULL)
  			return rdesc;
  
  		hid_info(hdev, "Fixing up Asus G752 keyb report descriptor
  ");
  		/* copy the valid part */
  		memcpy(new_rdesc, rdesc, 61);
  		/* insert missing part */
  		memcpy(new_rdesc + 61, asus_g752_fixed_rdesc, sizeof(asus_g752_fixed_rdesc));
  		/* copy remaining data */
  		memcpy(new_rdesc + 61 + sizeof(asus_g752_fixed_rdesc), rdesc + 61, *rsize - 61);
  
  		*rsize = new_size;
  		rdesc = new_rdesc;
  	}
76dd1fbeb   Hans de Goede   HID: asus: Add su...
982

eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
983
984
985
986
  	return rdesc;
  }
  
  static const struct hid_device_id asus_devices[] = {
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
987
  	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
a93913e14   Matjaz Hegedic   HID: asus: fix an...
988
  		USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD), I2C_KEYBOARD_QUIRKS},
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
989
  	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
c81760b99   Hans de Goede   HID: asus: Parame...
990
  		USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD), I2C_TOUCHPAD_QUIRKS },
1caccc256   Chris Chiu   HID: asus: suppor...
991
  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
339ee3fcb   Mustafa Kuscu   HID: add backligh...
992
  		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1), QUIRK_USE_KBD_BACKLIGHT },
1caccc256   Chris Chiu   HID: asus: suppor...
993
  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
af22a610b   Carlo Caione   HID: asus: suppor...
994
  		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT },
76dd1fbeb   Hans de Goede   HID: asus: Add su...
995
  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
832e1eeeb   Maxime Bellengé   HID: asus: Add su...
996
997
  		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3), QUIRK_G752_KEYBOARD },
  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
2340bad5d   Hans de Goede   HID: asus: Add su...
998
999
1000
  		USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD),
  	  QUIRK_USE_KBD_BACKLIGHT },
  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
762f948c9   Hans de Goede   HID: asus: Add pr...
1001
1002
1003
1004
  		USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
  	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
  		USB_DEVICE_ID_ASUSTEK_T100TAF_KEYBOARD),
76dd1fbeb   Hans de Goede   HID: asus: Add su...
1005
  	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
0e1f37d12   Aleix Roca Nonell   HID: asus: Add su...
1006
1007
  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
  		USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD), QUIRK_T101HA_DOCK },
5be918035   Daniel Drake   HID: move Asus ke...
1008
  	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_ASUS_AK1D) },
38b2d78c5   Daniel Drake   HID: asus: Add su...
1009
  	{ HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_ASUS_MD_5110) },
5be918035   Daniel Drake   HID: move Asus ke...
1010
  	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_ASUS_MD_5112) },
5703e52cc   Hans de Goede   HID: asus: Add T1...
1011
1012
  	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK,
  		USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD), QUIRK_T100CHI },
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
1013
1014
1015
1016
1017
  	{ }
  };
  MODULE_DEVICE_TABLE(hid, asus_devices);
  
  static struct hid_driver asus_driver = {
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
1018
1019
1020
1021
  	.name			= "asus",
  	.id_table		= asus_devices,
  	.report_fixup		= asus_report_fixup,
  	.probe                  = asus_probe,
af22a610b   Carlo Caione   HID: asus: suppor...
1022
  	.remove			= asus_remove,
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
1023
1024
1025
1026
1027
  	.input_mapping          = asus_input_mapping,
  	.input_configured       = asus_input_configured,
  #ifdef CONFIG_PM
  	.reset_resume           = asus_reset_resume,
  #endif
e98e38090   Hans de Goede   HID: asus: Add ev...
1028
  	.event			= asus_event,
9ce12d8be   Brendan McGrath   HID: asus: Add i2...
1029
  	.raw_event		= asus_raw_event
eeb01a579   Yusuke Fujimaki   HID: Asus X205TA ...
1030
1031
1032
1033
  };
  module_hid_driver(asus_driver);
  
  MODULE_LICENSE("GPL");