|
11 | 11 | * This module based on hid-ortek by |
12 | 12 | * Copyright (c) 2010 Johnathon Harris <[email protected]> |
13 | 13 | * Copyright (c) 2011 Jiri Kosina |
| 14 | + * |
| 15 | + * This module has been updated to add support for Asus i2c touchpad. |
| 16 | + * |
| 17 | + * Copyright (c) 2016 Brendan McGrath <[email protected]> |
| 18 | + * Copyright (c) 2016 Victor Vlasenko <[email protected]> |
| 19 | + * Copyright (c) 2016 Frederik Wenigwieser <[email protected]> |
14 | 20 | */ |
15 | 21 |
|
16 | 22 | /* |
|
20 | 26 | * any later version. |
21 | 27 | */ |
22 | 28 |
|
23 | | -#include <linux/device.h> |
24 | 29 | #include <linux/hid.h> |
25 | 30 | #include <linux/module.h> |
| 31 | +#include <linux/input/mt.h> |
26 | 32 |
|
27 | 33 | #include "hid-ids.h" |
28 | 34 |
|
| 35 | +MODULE_AUTHOR( "Yusuke Fujimaki <[email protected]>"); |
| 36 | +MODULE_AUTHOR( "Brendan McGrath <[email protected]>"); |
| 37 | +MODULE_AUTHOR( "Victor Vlasenko <[email protected]>"); |
| 38 | +MODULE_AUTHOR( "Frederik Wenigwieser <[email protected]>"); |
| 39 | +MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); |
| 40 | + |
| 41 | +#define FEATURE_REPORT_ID 0x0d |
| 42 | +#define INPUT_REPORT_ID 0x5d |
| 43 | + |
| 44 | +#define INPUT_REPORT_SIZE 28 |
| 45 | + |
| 46 | +#define MAX_CONTACTS 5 |
| 47 | + |
| 48 | +#define MAX_X 2794 |
| 49 | +#define MAX_Y 1758 |
| 50 | +#define MAX_TOUCH_MAJOR 8 |
| 51 | +#define MAX_PRESSURE 128 |
| 52 | + |
| 53 | +#define CONTACT_DATA_SIZE 5 |
| 54 | + |
| 55 | +#define BTN_LEFT_MASK 0x01 |
| 56 | +#define CONTACT_TOOL_TYPE_MASK 0x80 |
| 57 | +#define CONTACT_X_MSB_MASK 0xf0 |
| 58 | +#define CONTACT_Y_MSB_MASK 0x0f |
| 59 | +#define CONTACT_TOUCH_MAJOR_MASK 0x07 |
| 60 | +#define CONTACT_PRESSURE_MASK 0x7f |
| 61 | + |
| 62 | +#define QUIRK_FIX_NOTEBOOK_REPORT BIT(0) |
| 63 | +#define QUIRK_NO_INIT_REPORTS BIT(1) |
| 64 | +#define QUIRK_SKIP_INPUT_MAPPING BIT(2) |
| 65 | +#define QUIRK_IS_MULTITOUCH BIT(3) |
| 66 | + |
| 67 | +#define NOTEBOOK_QUIRKS QUIRK_FIX_NOTEBOOK_REPORT |
| 68 | +#define TOUCHPAD_QUIRKS (QUIRK_NO_INIT_REPORTS | \ |
| 69 | + QUIRK_SKIP_INPUT_MAPPING | \ |
| 70 | + QUIRK_IS_MULTITOUCH) |
| 71 | + |
| 72 | +#define TRKID_SGN ((TRKID_MAX + 1) >> 1) |
| 73 | + |
| 74 | +struct asus_drvdata { |
| 75 | + unsigned long quirks; |
| 76 | + struct input_dev *input; |
| 77 | +}; |
| 78 | + |
| 79 | +static void asus_report_contact_down(struct input_dev *input, |
| 80 | + int toolType, u8 *data) |
| 81 | +{ |
| 82 | + int touch_major, pressure; |
| 83 | + int x = (data[0] & CONTACT_X_MSB_MASK) << 4 | data[1]; |
| 84 | + int y = MAX_Y - ((data[0] & CONTACT_Y_MSB_MASK) << 8 | data[2]); |
| 85 | + |
| 86 | + if (toolType == MT_TOOL_PALM) { |
| 87 | + touch_major = MAX_TOUCH_MAJOR; |
| 88 | + pressure = MAX_PRESSURE; |
| 89 | + } else { |
| 90 | + touch_major = (data[3] >> 4) & CONTACT_TOUCH_MAJOR_MASK; |
| 91 | + pressure = data[4] & CONTACT_PRESSURE_MASK; |
| 92 | + } |
| 93 | + |
| 94 | + input_report_abs(input, ABS_MT_POSITION_X, x); |
| 95 | + input_report_abs(input, ABS_MT_POSITION_Y, y); |
| 96 | + input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major); |
| 97 | + input_report_abs(input, ABS_MT_PRESSURE, pressure); |
| 98 | +} |
| 99 | + |
| 100 | +/* Required for Synaptics Palm Detection */ |
| 101 | +static void asus_report_tool_width(struct input_dev *input) |
| 102 | +{ |
| 103 | + struct input_mt *mt = input->mt; |
| 104 | + struct input_mt_slot *oldest; |
| 105 | + int oldid, count, i; |
| 106 | + |
| 107 | + oldest = NULL; |
| 108 | + oldid = mt->trkid; |
| 109 | + count = 0; |
| 110 | + |
| 111 | + for (i = 0; i < mt->num_slots; ++i) { |
| 112 | + struct input_mt_slot *ps = &mt->slots[i]; |
| 113 | + int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID); |
| 114 | + |
| 115 | + if (id < 0) |
| 116 | + continue; |
| 117 | + if ((id - oldid) & TRKID_SGN) { |
| 118 | + oldest = ps; |
| 119 | + oldid = id; |
| 120 | + } |
| 121 | + count++; |
| 122 | + } |
| 123 | + |
| 124 | + if (oldest) { |
| 125 | + input_report_abs(input, ABS_TOOL_WIDTH, |
| 126 | + input_mt_get_value(oldest, ABS_MT_TOUCH_MAJOR)); |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +static void asus_report_input(struct input_dev *input, u8 *data) |
| 131 | +{ |
| 132 | + int i; |
| 133 | + u8 *contactData = data + 2; |
| 134 | + |
| 135 | + for (i = 0; i < MAX_CONTACTS; i++) { |
| 136 | + bool down = !!(data[1] & BIT(i+3)); |
| 137 | + int toolType = contactData[3] & CONTACT_TOOL_TYPE_MASK ? |
| 138 | + MT_TOOL_PALM : MT_TOOL_FINGER; |
| 139 | + |
| 140 | + input_mt_slot(input, i); |
| 141 | + input_mt_report_slot_state(input, toolType, down); |
| 142 | + |
| 143 | + if (down) { |
| 144 | + asus_report_contact_down(input, toolType, contactData); |
| 145 | + contactData += CONTACT_DATA_SIZE; |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + input_report_key(input, BTN_LEFT, data[1] & BTN_LEFT_MASK); |
| 150 | + asus_report_tool_width(input); |
| 151 | + |
| 152 | + input_mt_sync_frame(input); |
| 153 | + input_sync(input); |
| 154 | +} |
| 155 | + |
| 156 | +static int asus_raw_event(struct hid_device *hdev, |
| 157 | + struct hid_report *report, u8 *data, int size) |
| 158 | +{ |
| 159 | + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); |
| 160 | + |
| 161 | + if (drvdata->quirks & QUIRK_IS_MULTITOUCH && |
| 162 | + data[0] == INPUT_REPORT_ID && |
| 163 | + size == INPUT_REPORT_SIZE) { |
| 164 | + asus_report_input(drvdata->input, data); |
| 165 | + return 1; |
| 166 | + } |
| 167 | + |
| 168 | + return 0; |
| 169 | +} |
| 170 | + |
| 171 | +static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) |
| 172 | +{ |
| 173 | + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); |
| 174 | + |
| 175 | + if (drvdata->quirks & QUIRK_IS_MULTITOUCH) { |
| 176 | + int ret; |
| 177 | + struct input_dev *input = hi->input; |
| 178 | + |
| 179 | + input_set_abs_params(input, ABS_MT_POSITION_X, 0, MAX_X, 0, 0); |
| 180 | + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, MAX_Y, 0, 0); |
| 181 | + input_set_abs_params(input, ABS_TOOL_WIDTH, 0, MAX_TOUCH_MAJOR, 0, 0); |
| 182 | + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, MAX_TOUCH_MAJOR, 0, 0); |
| 183 | + input_set_abs_params(input, ABS_MT_PRESSURE, 0, MAX_PRESSURE, 0, 0); |
| 184 | + |
| 185 | + __set_bit(BTN_LEFT, input->keybit); |
| 186 | + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); |
| 187 | + |
| 188 | + ret = input_mt_init_slots(input, MAX_CONTACTS, INPUT_MT_POINTER); |
| 189 | + |
| 190 | + if (ret) { |
| 191 | + hid_err(hdev, "Asus input mt init slots failed: %d\n", ret); |
| 192 | + return ret; |
| 193 | + } |
| 194 | + |
| 195 | + drvdata->input = input; |
| 196 | + } |
| 197 | + |
| 198 | + return 0; |
| 199 | +} |
| 200 | + |
| 201 | +static int asus_input_mapping(struct hid_device *hdev, |
| 202 | + struct hid_input *hi, struct hid_field *field, |
| 203 | + struct hid_usage *usage, unsigned long **bit, |
| 204 | + int *max) |
| 205 | +{ |
| 206 | + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); |
| 207 | + |
| 208 | + if (drvdata->quirks & QUIRK_SKIP_INPUT_MAPPING) { |
| 209 | + /* Don't map anything from the HID report. |
| 210 | + * We do it all manually in asus_input_configured |
| 211 | + */ |
| 212 | + return -1; |
| 213 | + } |
| 214 | + |
| 215 | + return 0; |
| 216 | +} |
| 217 | + |
| 218 | +static int asus_start_multitouch(struct hid_device *hdev) |
| 219 | +{ |
| 220 | + int ret; |
| 221 | + const unsigned char buf[] = { FEATURE_REPORT_ID, 0x00, 0x03, 0x01, 0x00 }; |
| 222 | + unsigned char *dmabuf = kmemdup(buf, sizeof(buf), GFP_KERNEL); |
| 223 | + |
| 224 | + if (!dmabuf) { |
| 225 | + ret = -ENOMEM; |
| 226 | + hid_err(hdev, "Asus failed to alloc dma buf: %d\n", ret); |
| 227 | + return ret; |
| 228 | + } |
| 229 | + |
| 230 | + ret = hid_hw_raw_request(hdev, dmabuf[0], dmabuf, sizeof(buf), |
| 231 | + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); |
| 232 | + |
| 233 | + kfree(dmabuf); |
| 234 | + |
| 235 | + if (ret != sizeof(buf)) { |
| 236 | + hid_err(hdev, "Asus failed to start multitouch: %d\n", ret); |
| 237 | + return ret; |
| 238 | + } |
| 239 | + |
| 240 | + return 0; |
| 241 | +} |
| 242 | + |
| 243 | +static int __maybe_unused asus_reset_resume(struct hid_device *hdev) |
| 244 | +{ |
| 245 | + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); |
| 246 | + |
| 247 | + if (drvdata->quirks & QUIRK_IS_MULTITOUCH) |
| 248 | + return asus_start_multitouch(hdev); |
| 249 | + |
| 250 | + return 0; |
| 251 | +} |
| 252 | + |
| 253 | +static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) |
| 254 | +{ |
| 255 | + int ret; |
| 256 | + struct asus_drvdata *drvdata; |
| 257 | + |
| 258 | + drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); |
| 259 | + if (drvdata == NULL) { |
| 260 | + hid_err(hdev, "Can't alloc Asus descriptor\n"); |
| 261 | + return -ENOMEM; |
| 262 | + } |
| 263 | + |
| 264 | + hid_set_drvdata(hdev, drvdata); |
| 265 | + |
| 266 | + drvdata->quirks = id->driver_data; |
| 267 | + |
| 268 | + if (drvdata->quirks & QUIRK_NO_INIT_REPORTS) |
| 269 | + hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS; |
| 270 | + |
| 271 | + ret = hid_parse(hdev); |
| 272 | + if (ret) { |
| 273 | + hid_err(hdev, "Asus hid parse failed: %d\n", ret); |
| 274 | + return ret; |
| 275 | + } |
| 276 | + |
| 277 | + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
| 278 | + if (ret) { |
| 279 | + hid_err(hdev, "Asus hw start failed: %d\n", ret); |
| 280 | + return ret; |
| 281 | + } |
| 282 | + |
| 283 | + if (!drvdata->input) { |
| 284 | + hid_err(hdev, "Asus input not registered\n"); |
| 285 | + ret = -ENOMEM; |
| 286 | + goto err_stop_hw; |
| 287 | + } |
| 288 | + |
| 289 | + drvdata->input->name = "Asus TouchPad"; |
| 290 | + |
| 291 | + if (drvdata->quirks & QUIRK_IS_MULTITOUCH) { |
| 292 | + ret = asus_start_multitouch(hdev); |
| 293 | + if (ret) |
| 294 | + goto err_stop_hw; |
| 295 | + } |
| 296 | + |
| 297 | + return 0; |
| 298 | +err_stop_hw: |
| 299 | + hid_hw_stop(hdev); |
| 300 | + return ret; |
| 301 | +} |
| 302 | + |
29 | 303 | static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, |
30 | 304 | unsigned int *rsize) |
31 | 305 | { |
32 | | - if (*rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x65) { |
| 306 | + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); |
| 307 | + |
| 308 | + if (drvdata->quirks & QUIRK_FIX_NOTEBOOK_REPORT && |
| 309 | + *rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x65) { |
33 | 310 | hid_info(hdev, "Fixing up Asus notebook report descriptor\n"); |
34 | 311 | rdesc[55] = 0xdd; |
35 | 312 | } |
36 | 313 | return rdesc; |
37 | 314 | } |
38 | 315 |
|
39 | 316 | static const struct hid_device_id asus_devices[] = { |
40 | | - { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD) }, |
| 317 | + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 318 | + USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD), NOTEBOOK_QUIRKS}, |
| 319 | + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, |
| 320 | + USB_DEVICE_ID_ASUSTEK_TOUCHPAD), TOUCHPAD_QUIRKS }, |
41 | 321 | { } |
42 | 322 | }; |
43 | 323 | MODULE_DEVICE_TABLE(hid, asus_devices); |
44 | 324 |
|
45 | 325 | static struct hid_driver asus_driver = { |
46 | | - .name = "asus", |
47 | | - .id_table = asus_devices, |
48 | | - .report_fixup = asus_report_fixup |
| 326 | + .name = "asus", |
| 327 | + .id_table = asus_devices, |
| 328 | + .report_fixup = asus_report_fixup, |
| 329 | + .probe = asus_probe, |
| 330 | + .input_mapping = asus_input_mapping, |
| 331 | + .input_configured = asus_input_configured, |
| 332 | +#ifdef CONFIG_PM |
| 333 | + .reset_resume = asus_reset_resume, |
| 334 | +#endif |
| 335 | + .raw_event = asus_raw_event |
49 | 336 | }; |
50 | 337 | module_hid_driver(asus_driver); |
51 | 338 |
|
|
0 commit comments