diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-06-05 16:05:14 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-06-05 16:05:14 -0700 |
commit | 2158091d9cda6f126f71973667e8a9fc1e795d03 (patch) | |
tree | 56b62dbed2eaf967be7e2d922e603140c3b71818 /drivers/input | |
parent | 3e1a29b3bf66c2850ea8eba78c59c234921c0b69 (diff) | |
parent | e6e7e9cd8eed0e18217c899843bffbe8c7dae564 (diff) | |
download | linux-2158091d9cda6f126f71973667e8a9fc1e795d03.tar.gz linux-2158091d9cda6f126f71973667e8a9fc1e795d03.tar.bz2 linux-2158091d9cda6f126f71973667e8a9fc1e795d03.zip |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
Pull input updates from Dmitry Torokhov:
- a new driver to ChipOne icn8505 based touchscreens
- on certain systems with Elan touch controllers they will be switched
away form PS/2 emulation and over to native SMbus mode
- assorted driver fixups and improvements
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (24 commits)
Input: elan_i2c - add ELAN0612 (Lenovo v330 14IKB) ACPI ID
Input: goodix - add new ACPI id for GPD Win 2 touch screen
Input: xpad - add GPD Win 2 Controller USB IDs
Input: ti_am335x_tsc - prevent system suspend when TSC is in use
Input: ti_am335x_tsc - ack pending IRQs at probe and before suspend
Input: cros_ec_keyb - mark cros_ec_keyb driver as wake enabled device.
Input: mk712 - update documentation web link
Input: atmel_mxt_ts - fix reset-gpio for level based irqs
Input: atmel_mxt_ts - require device properties present when probing
Input: psmouse-smbus - allow to control psmouse_deactivate
Input: elantech - detect new ICs and setup Host Notify for them
Input: elantech - add support for SMBus devices
Input: elantech - query the resolution in query_info
Input: elantech - split device info into a separate structure
Input: elan_i2c - add trackstick report
Input: usbtouchscreen - add sysfs attribute for 3M MTouch firmware rev
Input: ati_remote2 - fix typo 'can by' to 'can be'
Input: replace hard coded string with __func__ in pr_err()
Input: add support for ChipOne icn8505 based touchscreens
Input: gamecon - avoid using __set_bit() for capabilities
...
Diffstat (limited to 'drivers/input')
23 files changed, 1311 insertions, 377 deletions
diff --git a/drivers/input/input.c b/drivers/input/input.c index 9785546420a7..6365c1958264 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -1943,8 +1943,7 @@ void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int break; default: - pr_err("input_set_capability: unknown type %u (code %u)\n", - type, code); + pr_err("%s: unknown type %u (code %u)\n", __func__, type, code); dump_stack(); return; } diff --git a/drivers/input/joystick/as5011.c b/drivers/input/joystick/as5011.c index 005d852a06e9..f051993c568e 100644 --- a/drivers/input/joystick/as5011.c +++ b/drivers/input/joystick/as5011.c @@ -269,9 +269,7 @@ static int as5011_probe(struct i2c_client *client, input_dev->id.bustype = BUS_I2C; input_dev->dev.parent = &client->dev; - __set_bit(EV_KEY, input_dev->evbit); - __set_bit(EV_ABS, input_dev->evbit); - __set_bit(BTN_JOYSTICK, input_dev->keybit); + input_set_capability(input_dev, EV_KEY, BTN_JOYSTICK); input_set_abs_params(input_dev, ABS_X, AS5011_MIN_AXIS, AS5011_MAX_AXIS, AS5011_FUZZ, AS5011_FLAT); diff --git a/drivers/input/joystick/gamecon.c b/drivers/input/joystick/gamecon.c index 2ffb2e8bdc3b..4e10ffdf8a36 100644 --- a/drivers/input/joystick/gamecon.c +++ b/drivers/input/joystick/gamecon.c @@ -862,7 +862,7 @@ static int gc_setup_pad(struct gc *gc, int idx, int pad_type) case GC_N64: for (i = 0; i < 10; i++) - __set_bit(gc_n64_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, gc_n64_btn[i]); for (i = 0; i < 2; i++) { input_set_abs_params(input_dev, ABS_X + i, -127, 126, 0, 2); @@ -879,26 +879,27 @@ static int gc_setup_pad(struct gc *gc, int idx, int pad_type) break; case GC_SNESMOUSE: - __set_bit(BTN_LEFT, input_dev->keybit); - __set_bit(BTN_RIGHT, input_dev->keybit); - __set_bit(REL_X, input_dev->relbit); - __set_bit(REL_Y, input_dev->relbit); + input_set_capability(input_dev, EV_KEY, BTN_LEFT); + input_set_capability(input_dev, EV_KEY, BTN_RIGHT); + input_set_capability(input_dev, EV_REL, REL_X); + input_set_capability(input_dev, EV_REL, REL_Y); break; case GC_SNES: for (i = 4; i < 8; i++) - __set_bit(gc_snes_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, gc_snes_btn[i]); /* fall through */ case GC_NES: for (i = 0; i < 4; i++) - __set_bit(gc_snes_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, gc_snes_btn[i]); break; case GC_MULTI2: - __set_bit(BTN_THUMB, input_dev->keybit); + input_set_capability(input_dev, EV_KEY, BTN_THUMB); /* fall through */ case GC_MULTI: - __set_bit(BTN_TRIGGER, input_dev->keybit); + input_set_capability(input_dev, EV_KEY, BTN_TRIGGER); + /* fall through */ break; case GC_PSX: @@ -906,15 +907,17 @@ static int gc_setup_pad(struct gc *gc, int idx, int pad_type) input_set_abs_params(input_dev, gc_psx_abs[i], 4, 252, 0, 2); for (i = 0; i < 12; i++) - __set_bit(gc_psx_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, gc_psx_btn[i]); + break; break; case GC_DDR: for (i = 0; i < 4; i++) - __set_bit(gc_psx_ddr_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, + gc_psx_ddr_btn[i]); for (i = 0; i < 12; i++) - __set_bit(gc_psx_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, gc_psx_btn[i]); break; } diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 06e9650b3b30..48e36acbeb49 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -86,8 +86,10 @@ #define XPAD_PKT_LEN 64 -/* xbox d-pads should map to buttons, as is required for DDR pads - but we map them to axes when possible to simplify things */ +/* + * xbox d-pads should map to buttons, as is required for DDR pads + * but we map them to axes when possible to simplify things + */ #define MAP_DPAD_TO_BUTTONS (1 << 0) #define MAP_TRIGGERS_TO_BUTTONS (1 << 1) #define MAP_STICKS_TO_NULL (1 << 2) @@ -123,6 +125,7 @@ static const struct xpad_device { u8 mapping; u8 xtype; } xpad_device[] = { + { 0x0079, 0x18d4, "GPD Win 2 Controller", 0, XTYPE_XBOX360 }, { 0x044f, 0x0f00, "Thrustmaster Wheel", 0, XTYPE_XBOX }, { 0x044f, 0x0f03, "Thrustmaster Wheel", 0, XTYPE_XBOX }, { 0x044f, 0x0f07, "Thrustmaster, Inc. Controller", 0, XTYPE_XBOX }, @@ -387,15 +390,15 @@ static const signed short xpad_abs_triggers[] = { * match against vendor id as well. Wired Xbox 360 devices have protocol 1, * wireless controllers have protocol 129. */ -#define XPAD_XBOX360_VENDOR_PROTOCOL(vend,pr) \ +#define XPAD_XBOX360_VENDOR_PROTOCOL(vend, pr) \ .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \ .idVendor = (vend), \ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ .bInterfaceSubClass = 93, \ .bInterfaceProtocol = (pr) #define XPAD_XBOX360_VENDOR(vend) \ - { XPAD_XBOX360_VENDOR_PROTOCOL(vend,1) }, \ - { XPAD_XBOX360_VENDOR_PROTOCOL(vend,129) } + { XPAD_XBOX360_VENDOR_PROTOCOL((vend), 1) }, \ + { XPAD_XBOX360_VENDOR_PROTOCOL((vend), 129) } /* The Xbox One controller uses subclass 71 and protocol 208. */ #define XPAD_XBOXONE_VENDOR_PROTOCOL(vend, pr) \ @@ -405,10 +408,11 @@ static const signed short xpad_abs_triggers[] = { .bInterfaceSubClass = 71, \ .bInterfaceProtocol = (pr) #define XPAD_XBOXONE_VENDOR(vend) \ - { XPAD_XBOXONE_VENDOR_PROTOCOL(vend, 208) } + { XPAD_XBOXONE_VENDOR_PROTOCOL((vend), 208) } static const struct usb_device_id xpad_table[] = { { USB_INTERFACE_INFO('X', 'B', 0) }, /* X-Box USB-IF not approved class */ + XPAD_XBOX360_VENDOR(0x0079), /* GPD Win 2 Controller */ XPAD_XBOX360_VENDOR(0x044f), /* Thrustmaster X-Box 360 controllers */ XPAD_XBOX360_VENDOR(0x045e), /* Microsoft X-Box 360 controllers */ XPAD_XBOXONE_VENDOR(0x045e), /* Microsoft X-Box One controllers */ @@ -1573,7 +1577,6 @@ static void xpad_close(struct input_dev *dev) static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs) { struct usb_xpad *xpad = input_get_drvdata(input_dev); - set_bit(abs, input_dev->absbit); switch (abs) { case ABS_X: @@ -1593,6 +1596,9 @@ static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs) case ABS_HAT0Y: /* the d-pad (only if dpad is mapped to axes */ input_set_abs_params(input_dev, abs, -1, 1, 0, 0); break; + default: + input_set_abs_params(input_dev, abs, 0, 0, 0, 0); + break; } } @@ -1633,10 +1639,7 @@ static int xpad_init_input(struct usb_xpad *xpad) input_dev->close = xpad_close; } - __set_bit(EV_KEY, input_dev->evbit); - if (!(xpad->mapping & MAP_STICKS_TO_NULL)) { - __set_bit(EV_ABS, input_dev->evbit); /* set up axes */ for (i = 0; xpad_abs[i] >= 0; i++) xpad_set_up_abs(input_dev, xpad_abs[i]); @@ -1644,21 +1647,22 @@ static int xpad_init_input(struct usb_xpad *xpad) /* set up standard buttons */ for (i = 0; xpad_common_btn[i] >= 0; i++) - __set_bit(xpad_common_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, xpad_common_btn[i]); /* set up model-specific ones */ if (xpad->xtype == XTYPE_XBOX360 || xpad->xtype == XTYPE_XBOX360W || xpad->xtype == XTYPE_XBOXONE) { for (i = 0; xpad360_btn[i] >= 0; i++) - __set_bit(xpad360_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, xpad360_btn[i]); } else { for (i = 0; xpad_btn[i] >= 0; i++) - __set_bit(xpad_btn[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, xpad_btn[i]); } if (xpad->mapping & MAP_DPAD_TO_BUTTONS) { for (i = 0; xpad_btn_pad[i] >= 0; i++) - __set_bit(xpad_btn_pad[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, + xpad_btn_pad[i]); } /* @@ -1675,7 +1679,8 @@ static int xpad_init_input(struct usb_xpad *xpad) if (xpad->mapping & MAP_TRIGGERS_TO_BUTTONS) { for (i = 0; xpad_btn_triggers[i] >= 0; i++) - __set_bit(xpad_btn_triggers[i], input_dev->keybit); + input_set_capability(input_dev, EV_KEY, + xpad_btn_triggers[i]); } else { for (i = 0; xpad_abs_triggers[i] >= 0; i++) xpad_set_up_abs(input_dev, xpad_abs_triggers[i]); diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c index 79eb29550c34..489ddd37bd4e 100644 --- a/drivers/input/keyboard/cros_ec_keyb.c +++ b/drivers/input/keyboard/cros_ec_keyb.c @@ -244,24 +244,35 @@ static int cros_ec_keyb_work(struct notifier_block *nb, switch (ckdev->ec->event_data.event_type) { case EC_MKBP_EVENT_KEY_MATRIX: - /* - * If EC is not the wake source, discard key state changes - * during suspend. - */ - if (queued_during_suspend) - return NOTIFY_OK; + if (device_may_wakeup(ckdev->dev)) { + pm_wakeup_event(ckdev->dev, 0); + } else { + /* + * If keyboard is not wake enabled, discard key state + * changes during suspend. Switches will be re-checked + * in cros_ec_keyb_resume() to be sure nothing is lost. + */ + if (queued_during_suspend) + return NOTIFY_OK; + } if (ckdev->ec->event_size != ckdev->cols) { dev_err(ckdev->dev, "Discarded incomplete key matrix event.\n"); return NOTIFY_OK; } + cros_ec_keyb_process(ckdev, ckdev->ec->event_data.data.key_matrix, ckdev->ec->event_size); break; case EC_MKBP_EVENT_SYSRQ: + if (device_may_wakeup(ckdev->dev)) + pm_wakeup_event(ckdev->dev, 0); + else if (queued_during_suspend) + return NOTIFY_OK; + val = get_unaligned_le32(&ckdev->ec->event_data.data.sysrq); dev_dbg(ckdev->dev, "sysrq code from EC: %#x\n", val); handle_sysrq(val); @@ -269,12 +280,9 @@ static int cros_ec_keyb_work(struct notifier_block *nb, case EC_MKBP_EVENT_BUTTON: case EC_MKBP_EVENT_SWITCH: - /* - * If EC is not the wake source, discard key state - * changes during suspend. Switches will be re-checked in - * cros_ec_keyb_resume() to be sure nothing is lost. - */ - if (queued_during_suspend) + if (device_may_wakeup(ckdev->dev)) + pm_wakeup_event(ckdev->dev, 0); + else if (queued_during_suspend) return NOTIFY_OK; if (ckdev->ec->event_data.event_type == EC_MKBP_EVENT_BUTTON) { @@ -639,6 +647,7 @@ static int cros_ec_keyb_probe(struct platform_device *pdev) return err; } + device_init_wakeup(ckdev->dev, true); return 0; } diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c index ded5b84e336d..d8fd58fdf050 100644 --- a/drivers/input/misc/ati_remote2.c +++ b/drivers/input/misc/ati_remote2.c @@ -22,7 +22,7 @@ MODULE_LICENSE("GPL"); /* * ATI Remote Wonder II Channel Configuration * - * The remote control can by assigned one of sixteen "channels" in order to facilitate + * The remote control can be assigned one of sixteen "channels" in order to facilitate * the use of multiple remote controls within range of each other. * A remote's "channel" may be altered by pressing and holding the "PC" button for * approximately 3 seconds, after which the button will slowly flash the count of the diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig index 89ebb8f39fee..f27f23f2d99a 100644 --- a/drivers/input/mouse/Kconfig +++ b/drivers/input/mouse/Kconfig @@ -133,6 +133,18 @@ config MOUSE_PS2_ELANTECH If unsure, say N. +config MOUSE_PS2_ELANTECH_SMBUS + bool "Elantech PS/2 SMbus companion" if EXPERT + default y + depends on MOUSE_PS2 && MOUSE_PS2_ELANTECH + depends on I2C=y || I2C=MOUSE_PS2 + select MOUSE_PS2_SMBUS + help + Say Y here if you have a Elantech touchpad connected to + to an SMBus, but enumerated through PS/2. + + If unsure, say Y. + config MOUSE_PS2_SENTELIC bool "Sentelic Finger Sensing Pad PS/2 protocol extension" depends on MOUSE_PS2 diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index 38f9501acdf0..cb5579716dba 100644 --- a/drivers/input/mouse/alps.c +++ b/drivers/input/mouse/alps.c @@ -2049,14 +2049,11 @@ static int alps_hw_init_v1_v2(struct psmouse *psmouse) return 0; } -static int alps_hw_init_v6(struct psmouse *psmouse) +/* Must be in passthrough mode when calling this function */ +static int alps_trackstick_enter_extended_mode_v3_v6(struct psmouse *psmouse) { unsigned char param[2] = {0xC8, 0x14}; - /* Enter passthrough mode to let trackpoint enter 6byte raw mode */ - if (alps_passthrough_mode_v2(psmouse, true)) - return -1; - if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || @@ -2064,9 +2061,25 @@ static int alps_hw_init_v6(struct psmouse *psmouse) ps2_command(&psmouse->ps2dev, ¶m[1], PSMOUSE_CMD_SETRATE)) return -1; + return 0; +} + +static int alps_hw_init_v6(struct psmouse *psmouse) +{ + int ret; + + /* Enter passthrough mode to let trackpoint enter 6byte raw mode */ + if (alps_passthrough_mode_v2(psmouse, true)) + return -1; + + ret = alps_trackstick_enter_extended_mode_v3_v6(psmouse); + if (alps_passthrough_mode_v2(psmouse, false)) return -1; + if (ret) + return ret; + if (alps_absolute_mode_v6(psmouse)) { psmouse_err(psmouse, "Failed to enable absolute mode\n"); return -1; @@ -2140,10 +2153,18 @@ error: static int alps_setup_trackstick_v3(struct psmouse *psmouse, int reg_base) { - struct ps2dev *ps2dev = &psmouse->ps2dev; int ret = 0; + int reg_val; unsigned char param[4]; + /* + * We need to configure trackstick to report data for touchpad in + * extended format. And also we need to tell touchpad to expect data + * from trackstick in extended format. Without this configuration + * trackstick packets sent from touchpad are in basic format which is + * different from what we expect. + */ + if (alps_passthrough_mode_v3(psmouse, reg_base, true)) return -EIO; @@ -2161,39 +2182,36 @@ static int alps_setup_trackstick_v3(struct psmouse *psmouse, int reg_base) ret = -ENODEV; } else { psmouse_dbg(psmouse, "trackstick E7 report: %3ph\n", param); - - /* - * Not sure what this does, but it is absolutely - * essential. Without it, the touchpad does not - * work at all and the trackstick just emits normal - * PS/2 packets. - */ - if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || - ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || - ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) || - alps_command_mode_send_nibble(psmouse, 0x9) || - alps_command_mode_send_nibble(psmouse, 0x4)) { - psmouse_err(psmouse, - "Error sending magic E6 sequence\n"); + if (alps_trackstick_enter_extended_mode_v3_v6(psmouse)) { + psmouse_err(psmouse, "Failed to enter into trackstick extended mode\n"); ret = -EIO; - goto error; } + } + + if (alps_passthrough_mode_v3(psmouse, reg_base, false)) + return -EIO; + + if (ret) + return ret; + if (alps_enter_command_mode(psmouse)) + return -EIO; + + reg_val = alps_command_mode_read_reg(psmouse, reg_base + 0x08); + if (reg_val == -1) { + ret = -EIO; + } else { /* - * This ensures the trackstick packets are in the format - * supported by this driver. If bit 1 isn't set the packet - * format is different. + * Tell touchpad that trackstick is now in extended mode. + * If bit 1 isn't set the packet format is different. */ - if (alps_enter_command_mode(psmouse) || - alps_command_mode_write_reg(psmouse, - reg_base + 0x08, 0x82) || - alps_exit_command_mode(psmouse)) + reg_val |= BIT(1); + if (__alps_command_mode_write_reg(psmouse, reg_val)) ret = -EIO; } -error: - if (alps_passthrough_mode_v3(psmouse, reg_base, false)) - ret = -EIO; + if (alps_exit_command_mode(psmouse)) + return -EIO; return ret; } diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c index 75e757520ef0..8ff75114e762 100644 --- a/drivers/input/mouse/elan_i2c_core.c +++ b/drivers/input/mouse/elan_i2c_core.c @@ -36,6 +36,7 @@ #include <linux/jiffies.h> #include <linux/completion.h> #include <linux/of.h> +#include <linux/property.h> #include <linux/regulator/consumer.h> #include <asm/unaligned.h> @@ -51,6 +52,7 @@ #define ETP_MAX_FINGERS 5 #define ETP_FINGER_DATA_LEN 5 #define ETP_REPORT_ID 0x5D +#define ETP_TP_REPORT_ID 0x5E #define ETP_REPORT_ID_OFFSET 2 #define ETP_TOUCH_INFO_OFFSET 3 #define ETP_FINGER_DATA_OFFSET 4 @@ -61,6 +63,7 @@ struct elan_tp_data { struct i2c_client *client; struct input_dev *input; + struct input_dev *tp_input; /* trackpoint input node */ struct regulator *vcc; const struct elan_transport_ops *ops; @@ -930,6 +933,33 @@ static void elan_report_absolute(struct elan_tp_data *data, u8 *packet) input_sync(input); } +static void elan_report_trackpoint(struct elan_tp_data *data, u8 *report) +{ + struct input_dev *input = data->tp_input; + u8 *packet = &report[ETP_REPORT_ID_OFFSET + 1]; + int x, y; + + if (!data->tp_input) { + dev_warn_once(&data->client->dev, + "received a trackpoint report while no trackpoint device has been created. Please report upstream.\n"); + return; + } + + input_report_key(input, BTN_LEFT, packet[0] & 0x01); + input_report_key(input, BTN_RIGHT, packet[0] & 0x02); + input_report_key(input, BTN_MIDDLE, packet[0] & 0x04); + + if ((packet[3] & 0x0F) == 0x06) { + x = packet[4] - (int)((packet[1] ^ 0x80) << 1); + y = (int)((packet[2] ^ 0x80) << 1) - packet[5]; + + input_report_rel(input, REL_X, x); + input_report_rel(input, REL_Y, y); + } + + input_sync(input); +} + static irqreturn_t elan_isr(int irq, void *dev_id) { struct elan_tp_data *data = dev_id; @@ -951,11 +981,17 @@ static irqreturn_t elan_isr(int irq, void *dev_id) if (error) goto out; - if (report[ETP_REPORT_ID_OFFSET] != ETP_REPORT_ID) + switch (report[ETP_REPORT_ID_OFFSET]) { + case ETP_REPORT_ID: + elan_report_absolute(data, report); + break; + case ETP_TP_REPORT_ID: + elan_report_trackpoint(data, report); + break; + default: dev_err(dev, "invalid report id data (%x)\n", report[ETP_REPORT_ID_OFFSET]); - else - elan_report_absolute(data, report); + } out: return IRQ_HANDLED; @@ -966,6 +1002,36 @@ out: * Elan initialization functions ****************************************************************** */ + +static int elan_setup_trackpoint_input_device(struct elan_tp_data *data) +{ + struct device *dev = &data->client->dev; + struct input_dev *input; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + input->name = "Elan TrackPoint"; + input->id.bustype = BUS_I2C; + input->id.vendor = ELAN_VENDOR_ID; + input->id.product = data->product_id; + input_set_drvdata(input, data); + + input_set_capability(input, EV_REL, REL_X); + input_set_capability(input, EV_REL, REL_Y); + input_set_capability(input, EV_KEY, BTN_LEFT); + input_set_capability(input, EV_KEY, BTN_RIGHT); + input_set_capability(input, EV_KEY, BTN_MIDDLE); + + __set_bit(INPUT_PROP_POINTER, input->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, input->propbit); + + data->tp_input = input; + + return 0; +} + static int elan_setup_input_device(struct elan_tp_data *data) { struct device *dev = &data->client->dev; @@ -1140,6 +1206,12 @@ static int elan_probe(struct i2c_client *client, if (error) return error; + if (device_property_read_bool(&client->dev, "elan,trackpoint")) { + error = elan_setup_trackpoint_input_device(data); + if (error) + return error; + } + /* * Platform code (ACPI, DTS) should normally set up interrupt * for us, but in case it did not let's fall back to using falling @@ -1177,6 +1249,16 @@ static int elan_probe(struct i2c_client *client, return error; } + if (data->tp_input) { + error = input_register_device(data->tp_input); + if (error) { + dev_err(&client->dev, + "failed to register TrackPoint input device: %d\n", + error); + return error; + } + } + /* * Systems using device tree should set up wakeup via DTS, * the rest will configure device as wakeup source by default. @@ -1262,6 +1344,7 @@ static const struct acpi_device_id elan_acpi_id[] = { { "ELAN060B", 0 }, { "ELAN060C", 0 }, { "ELAN0611", 0 }, + { "ELAN0612", 0 }, { "ELAN1000", 0 }, { } }; diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c index db47a5e1d114..fb4d902c4403 100644 --- a/drivers/input/mouse/elantech.c +++ b/drivers/input/mouse/elantech.c @@ -14,17 +14,20 @@ #include <linux/dmi.h> #include <linux/slab.h> #include <linux/module.h> +#include <linux/i2c.h> #include <linux/input.h> #include <linux/input/mt.h> +#include <linux/platform_device.h> #include <linux/serio.h> #include <linux/libps2.h> #include <asm/unaligned.h> #include "psmouse.h" #include "elantech.h" +#include "elan_i2c.h" #define elantech_debug(fmt, ...) \ do { \ - if (etd->debug) \ + if (etd->info.debug) \ psmouse_printk(KERN_DEBUG, psmouse, \ fmt, ##__VA_ARGS__); \ } while (0) @@ -105,7 +108,7 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg, if (reg > 0x11 && reg < 0x20) return -1; - switch (etd->hw_version) { + switch (etd->info.hw_version) { case 1: if (ps2_sliced_command(&psmouse->ps2dev, ETP_REGISTER_READ) || ps2_sliced_command(&psmouse->ps2dev, reg) || @@ -137,7 +140,7 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg, if (rc) psmouse_err(psmouse, "failed to read register 0x%02x.\n", reg); - else if (etd->hw_version != 4) + else if (etd->info.hw_version != 4) *val = param[0]; else *val = param[1]; @@ -160,7 +163,7 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg, if (reg > 0x11 && reg < 0x20) return -1; - switch (etd->hw_version) { + switch (etd->info.hw_version) { case 1: if (ps2_sliced_command(&psmouse->ps2dev, ETP_REGISTER_WRITE) || ps2_sliced_command(&psmouse->ps2dev, reg) || @@ -237,7 +240,7 @@ static void elantech_report_absolute_v1(struct psmouse *psmouse) unsigned char *packet = psmouse->packet; int fingers; - if (etd->fw_version < 0x020000) { + if (etd->info.fw_version < 0x020000) { /* * byte 0: D U p1 p2 1 p3 R L * byte 1: f 0 th tw x9 x8 y9 y8 @@ -252,7 +255,7 @@ static void elantech_report_absolute_v1(struct psmouse *psmouse) fingers = (packet[0] & 0xc0) >> 6; } - if (etd->jumpy_cursor) { + if (etd->info.jumpy_cursor) { if (fingers != 1) { etd->single_finger_reports = 0; } else if (etd->single_finger_reports < 2) { @@ -282,8 +285,8 @@ static void elantech_report_absolute_v1(struct psmouse *psmouse) psmouse_report_standard_buttons(dev, packet[0]); - if (etd->fw_version < 0x020000 && - (etd->capabilities[0] & ETP_CAP_HAS_ROCKER)) { + if (etd->info.fw_version < 0x020000 && + (etd->info.capabilities[0] & ETP_CAP_HAS_ROCKER)) { /* rocker up */ input_report_key(dev, BTN_FORWARD, packet[0] & 0x40); /* rocker down */ @@ -391,7 +394,7 @@ static void elantech_report_absolute_v2(struct psmouse *psmouse) input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); input_report_key(dev, BTN_TOOL_QUADTAP, fingers == 4); psmouse_report_standard_buttons(dev, packet[0]); - if (etd->reports_pressure) { + if (etd->info.reports_pressure) { input_report_abs(dev, ABS_PRESSURE, pres); input_report_abs(dev, ABS_TOOL_WIDTH, width); } @@ -444,7 +447,7 @@ static void elantech_report_trackpoint(struct psmouse *psmouse, default: /* Dump unexpected packet sequences if debug=1 (default) */ - if (etd->debug == 1) + if (etd->info.debug == 1) elantech_packet_dump(psmouse); break; @@ -523,7 +526,7 @@ static void elantech_report_absolute_v3(struct psmouse *psmouse, input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); /* For clickpads map both buttons to BTN_LEFT */ - if (etd->fw_version & 0x001000) + if (etd->info.fw_version & 0x001000) input_report_key(dev, BTN_LEFT, packet[0] & 0x03); else psmouse_report_standard_buttons(dev, packet[0]); @@ -541,7 +544,7 @@ static void elantech_input_sync_v4(struct psmouse *psmouse) unsigned char *packet = psmouse->packet; /* For clickpads map both buttons to BTN_LEFT */ - if (etd->fw_version & 0x001000) + if (etd->info.fw_version & 0x001000) input_report_key(dev, BTN_LEFT, packet[0] & 0x03); else psmouse_report_standard_buttons(dev, packet[0]); @@ -669,7 +672,7 @@ static int elantech_packet_check_v1(struct psmouse *psmouse) unsigned char p1, p2, p3; /* Parity bits are placed differently */ - if (etd->fw_version < 0x020000) { + if (etd->info.fw_version < 0x020000) { /* byte 0: D U p1 p2 1 p3 R L */ p1 = (packet[0] & 0x20) >> 5; p2 = (packet[0] & 0x10) >> 4; @@ -714,7 +717,7 @@ static int elantech_packet_check_v2(struct psmouse *psmouse) * With all three cases, if the constant bits are not exactly what I * expected, I consider them invalid. */ - if (etd->reports_pressure) + if (etd->info.reports_pressure) return (packet[0] & 0x0c) == 0x04 && (packet[3] & 0x0f) == 0x02; @@ -751,7 +754,7 @@ static int elantech_packet_check_v3(struct psmouse *psmouse) * If the hardware flag 'crc_enabled' is set the packets have * different signatures. */ - if (etd->crc_enabled) { + if (etd->info.crc_enabled) { if ((packet[3] & 0x09) == 0x08) return PACKET_V3_HEAD; @@ -782,7 +785,7 @@ static int elantech_packet_check_v4(struct psmouse *psmouse) return PACKET_TRACKPOINT; /* This represents the version of IC body. */ - ic_version = (etd->fw_version & 0x0f0000) >> 16; + ic_version = (etd->info.fw_version & 0x0f0000) >> 16; /* * Sanity check based on the constant bits of a packet. @@ -791,9 +794,9 @@ static int elantech_packet_check_v4(struct psmouse *psmouse) * the IC body, but are the same for every packet, * regardless of the type. */ - if (etd->crc_enabled) + if (etd->info.crc_enabled) sanity_check = ((packet[3] & 0x08) == 0x00); - else if (ic_version == 7 && etd->samples[1] == 0x2A) + else if (ic_version == 7 && etd->info.samples[1] == 0x2A) sanity_check = ((packet[3] & 0x1c) == 0x10); else sanity_check = ((packet[0] & 0x0c) == 0x04 && @@ -827,12 +830,12 @@ static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse) if (psmouse->pktcnt < psmouse->pktsize) return PSMOUSE_GOOD_DATA; - if (etd->debug > 1) + if (etd->info.debug > 1) elantech_packet_dump(psmouse); - switch (etd->hw_version) { + switch (etd->info.hw_version) { case 1: - if (etd->paritycheck && !elantech_packet_check_v1(psmouse)) + if (etd->info.paritycheck && !elantech_packet_check_v1(psmouse)) return PSMOUSE_BAD_DATA; elantech_report_absolute_v1(psmouse); @@ -843,7 +846,7 @@ static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse) if (elantech_debounce_check_v2(psmouse)) return PSMOUSE_FULL_PACKET; - if (etd->paritycheck && !elantech_packet_check_v2(psmouse)) + if (etd->info.paritycheck && !elantech_packet_check_v2(psmouse)) return PSMOUSE_BAD_DATA; elantech_report_absolute_v2(psmouse); @@ -916,7 +919,7 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse) int tries = ETP_READ_BACK_TRIES; int rc = 0; - switch (etd->hw_version) { + switch (etd->info.hw_version) { case 1: etd->reg_10 = 0x16; etd->reg_11 = 0x8f; @@ -939,7 +942,7 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse) break; case 3: - if (etd->set_hw_resolution) + if (etd->info.set_hw_resolution) etd->reg_10 = 0x0b; else etd->reg_10 = 0x01; @@ -976,7 +979,7 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse) if (rc) { psmouse_err(psmouse, "failed to read back register 0x10.\n"); - } else if (etd->hw_version == 1 && + } else if (etd->info.hw_version == 1 && !(val & ETP_R10_ABSOLUTE_MODE)) { psmouse_err(psmouse, "touchpad refuses to switch to absolute mode.\n"); @@ -997,10 +1000,11 @@ static int elantech_set_range(struct psmouse *psmouse, unsigned int *width) { struct elantech_data *etd = psmouse->private; + struct elantech_device_info *info = &etd->info; unsigned char param[3]; unsigned char traces; - switch (etd->hw_version) { + switch (info->hw_version) { case 1: *x_min = ETP_XMIN_V1; *y_min = ETP_YMIN_V1; @@ -1009,9 +1013,9 @@ static int elantech_set_range(struct psmouse *psmouse, break; case 2: - if (etd->fw_version == 0x020800 || - etd->fw_version == 0x020b00 || - etd->fw_version == 0x020030) { + if (info->fw_version == 0x020800 || + info->fw_version == 0x020b00 || + info->fw_version == 0x020030) { *x_min = ETP_XMIN_V2; *y_min = ETP_YMIN_V2; *x_max = ETP_XMAX_V2; @@ -1020,35 +1024,35 @@ static int elantech_set_range(struct psmouse *psmouse, int i; int fixed_dpi; - i = (etd->fw_version > 0x020800 && - etd->fw_version < 0x020900) ? 1 : 2; + i = (info->fw_version > 0x020800 && + info->fw_version < 0x020900) ? 1 : 2; - if (etd->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) return -1; fixed_dpi = param[1] & 0x10; - if (((etd->fw_version >> 16) == 0x14) && fixed_dpi) { - if (etd->send_cmd(psmouse, ETP_SAMPLE_QUERY, param)) + if (((info->fw_version >> 16) == 0x14) && fixed_dpi) { + if (info->send_cmd(psmouse, ETP_SAMPLE_QUERY, param)) return -1; - *x_max = (etd->capabilities[1] - i) * param[1] / 2; - *y_max = (etd->capabilities[2] - i) * param[2] / 2; - } else if (etd->fw_version == 0x040216) { + *x_max = (info->capabilities[1] - i) * param[1] / 2; + *y_max = (info->capabilities[2] - i) * param[2] / 2; + } else if (info->fw_version == 0x040216) { *x_max = 819; *y_max = 405; - } else if (etd->fw_version == 0x040219 || etd->fw_version == 0x040215) { + } else if (info->fw_version == 0x040219 || info->fw_version == 0x040215) { *x_max = 900; *y_max = 500; } else { - *x_max = (etd->capabilities[1] - i) * 64; - *y_max = (etd->capabilities[2] - i) * 64; + *x_max = (info->capabilities[1] - i) * 64; + *y_max = (info->capabilities[2] - i) * 64; } } break; case 3: - if (etd->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) return -1; *x_max = (0x0f & param[0]) << 8 | param[1]; @@ -1056,12 +1060,12 @@ static int elantech_set_range(struct psmouse *psmouse, break; case 4: - if (etd->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) + if (info->send_cmd(psmouse, ETP_FW_ID_QUERY, param)) return -1; *x_max = (0x0f & param[0]) << 8 | param[1]; *y_max = (0xf0 & param[0]) << 4 | param[2]; - traces = etd->capabilities[1]; + traces = info->capabilities[1]; if ((traces < 2) || (traces > *x_max)) return -1; @@ -1083,7 +1087,8 @@ static unsigned int elantech_convert_res(unsigned int val) static int elantech_get_resolution_v4(struct psmouse *psmouse, unsigned int *x_res, - unsigned int *y_res) + unsigned int *y_res, + unsigned int *bus) { unsigned char param[3]; @@ -1092,6 +1097,7 @@ static int elantech_get_resolution_v4(struct psmouse *psmouse, *x_res = elantech_convert_res(param[1] & 0x0f); *y_res = elantech_convert_res((param[1] & 0xf0) >> 4); + *bus = param[2]; return 0; } @@ -1140,7 +1146,7 @@ static void elantech_set_buttonpad_prop(struct psmouse *psmouse) struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; - if (etd->fw_version & 0x001000) { + if (etd->info.fw_version & 0x001000) { __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit); __clear_bit(BTN_RIGHT, dev->keybit); } @@ -1176,8 +1182,8 @@ static int elantech_set_input_params(struct psmouse *psmouse) { struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; + struct elantech_device_info *info = &etd->info; unsigned int x_min = 0, y_min = 0, x_max = 0, y_max = 0, width = 0; - unsigned int x_res = 31, y_res = 31; if (elantech_set_range(psmouse, &x_min, &y_min, &x_max, &y_max, &width)) return -1; @@ -1197,11 +1203,11 @@ static int elantech_set_input_params(struct psmouse *psmouse) __set_bit(BTN_TOOL_DOUBLETAP, dev->keybit); __set_bit(BTN_TOOL_TRIPLETAP, dev->keybit); - switch (etd->hw_version) { + switch (info->hw_version) { case 1: /* Rocker button */ - if (etd->fw_version < 0x020000 && - (etd->capabilities[0] & ETP_CAP_HAS_ROCKER)) { + if (info->fw_version < 0x020000 && + (info->capabilities[0] & ETP_CAP_HAS_ROCKER)) { __set_bit(BTN_FORWARD, dev->keybit); __set_bit(BTN_BACK, dev->keybit); } @@ -1214,11 +1220,11 @@ static int elantech_set_input_params(struct psmouse *psmouse) __set_bit(INPUT_PROP_SEMI_MT, dev->propbit); /* fall through */ case 3: - if (etd->hw_version == 3) + if (info->hw_version == 3) elantech_set_buttonpad_prop(psmouse); input_set_abs_params(dev, ABS_X, x_min, x_max, 0, 0); input_set_abs_params(dev, ABS_Y, y_min, y_max, 0, 0); - if (etd->reports_pressure) { + if (info->reports_pressure) { input_set_abs_params(dev, ABS_PRESSURE, ETP_PMIN_V2, ETP_PMAX_V2, 0, 0); input_set_abs_params(dev, ABS_TOOL_WIDTH, ETP_WMIN_V2, @@ -1230,13 +1236,6 @@ static int elantech_set_input_params(struct psmouse *psmouse) break; case 4: - if (elantech_get_resolution_v4(psmouse, &x_res, &y_res)) { - /* - * if query failed, print a warning and leave the values - * zero to resemble synaptics.c behavior. - */ - psmouse_warn(psmouse, "couldn't query resolution data.\n"); - } elantech_set_buttonpad_prop(psmouse); __set_bit(BTN_TOOL_QUADTAP, dev->keybit); /* For X to recognize me as touchpad. */ @@ -1265,11 +1264,11 @@ static int elantech_set_input_params(struct psmouse *psmouse) break; } - input_abs_set_res(dev, ABS_X, x_res); - input_abs_set_res(dev, ABS_Y, y_res); - if (etd->hw_version > 1) { - input_abs_set_res(dev, ABS_MT_POSITION_X, x_res); - input_abs_set_res(dev, ABS_MT_POSITION_Y, y_res); + input_abs_set_res(dev, ABS_X, info->x_res); + input_abs_set_res(dev, ABS_Y, info->y_res); + if (info->hw_version > 1) { + input_abs_set_res(dev, ABS_MT_POSITION_X, info->x_res); + input_abs_set_res(dev, ABS_MT_POSITION_Y, info->y_res); } etd->y_max = y_max; @@ -1317,7 +1316,7 @@ static ssize_t elantech_set_int_attr(struct psmouse *psmouse, return err; /* Do we need to preserve some bits for version 2 hardware too? */ - if (etd->hw_version == 1) { + if (etd->info.hw_version == 1) { if (attr->reg == 0x10) /* Force absolute mode always on */ value |= ETP_R10_ABSOLUTE_MODE; @@ -1337,11 +1336,22 @@ static ssize_t elantech_set_int_attr(struct psmouse *psmouse, .field_offset = offsetof(struct elantech_data, _name), \ .reg = _register, \ }; \ - PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO, \ + PSMOUSE_DEFINE_ATTR(_name, 0644, \ &elantech_attr_##_name, \ elantech_show_int_attr, \ elantech_set_int_attr) +#define ELANTECH_INFO_ATTR(_name) \ + static struct elantech_attr_data elantech_attr_##_name = { \ + .field_offset = offsetof(struct elantech_data, info) + \ + offsetof(struct elantech_device_info, _name), \ + .reg = 0, \ + }; \ + PSMOUSE_DEFINE_ATTR(_name, 0644, \ + &elantech_attr_##_name, \ + elantech_show_int_attr, \ + elantech_set_int_attr) + ELANTECH_INT_ATTR(reg_07, 0x07); ELANTECH_INT_ATTR(reg_10, 0x10); ELANTECH_INT_ATTR(reg_11, 0x11); @@ -1352,9 +1362,9 @@ ELANTECH_INT_ATTR(reg_23, 0x23); ELANTECH_INT_ATTR(reg_24, 0x24); ELANTECH_INT_ATTR(reg_25, 0x25); ELANTECH_INT_ATTR(reg_26, 0x26); -ELANTECH_INT_ATTR(debug, 0); -ELANTECH_INT_ATTR(paritycheck, 0); -ELANTECH_INT_ATTR(crc_enabled, 0); +ELANTECH_INFO_ATTR(debug); +ELANTECH_INFO_ATTR(paritycheck); +ELANTECH_INFO_ATTR(crc_enabled); static struct attribute *elantech_attrs[] = { &psmouse_attr_reg_07.dattr.attr, @@ -1469,6 +1479,12 @@ static void elantech_disconnect(struct psmouse *psmouse) { struct elantech_data *etd = psmouse->private; + /* + * We might have left a breadcrumb when trying to + * set up SMbus companion. + */ + psmouse_smbus_cleanup(psmouse); + if (etd->tp_dev) input_unregister_device(etd->tp_dev); sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, @@ -1588,25 +1604,25 @@ static const struct dmi_system_id no_hw_res_dmi_table[] = { /* * determine hardware version and set some properties according to it. */ -static int elantech_set_properties(struct elantech_data *etd) +static int elantech_set_properties(struct elantech_device_info *info) { /* This represents the version of IC body. */ - int ver = (etd->fw_version & 0x0f0000) >> 16; + int ver = (info->fw_version & 0x0f0000) >> 16; /* Early version of Elan touchpads doesn't obey the rule. */ - if (etd->fw_version < 0x020030 || etd->fw_version == 0x020600) - etd->hw_version = 1; + if (info->fw_version < 0x020030 || info->fw_version == 0x020600) + info->hw_version = 1; else { switch (ver) { case 2: case 4: - etd->hw_version = 2; + info->hw_version = 2; break; case 5: - etd->hw_version = 3; + info->hw_version = 3; break; case 6 ... 15: - etd->hw_version = 4; + info->hw_version = 4; break; default: return -1; @@ -1614,100 +1630,88 @@ static int elantech_set_properties(struct elantech_data *etd) } /* decide which send_cmd we're gonna use early */ - etd->send_cmd = etd->hw_version >= 3 ? elantech_send_cmd : - synaptics_send_cmd; + info->send_cmd = info->hw_version >= 3 ? elantech_send_cmd : + synaptics_send_cmd; /* Turn on packet checking by default */ - etd->paritycheck = 1; + info->paritycheck = 1; /* * This firmware suffers from misreporting coordinates when * a touch action starts causing the mouse cursor or scrolled page * to jump. Enable a workaround. */ - etd->jumpy_cursor = - (etd->fw_version == 0x020022 || etd->fw_version == 0x020600); + info->jumpy_cursor = + (info->fw_version == 0x020022 || info->fw_version == 0x020600); - if (etd->hw_version > 1) { + if (info->hw_version > 1) { /* For now show extra debug information */ - etd->debug = 1; + info->debug = 1; - if (etd->fw_version >= 0x020800) - etd->reports_pressure = true; + if (info->fw_version >= 0x020800) + info->reports_pressure = true; } /* * The signatures of v3 and v4 packets change depending on the * value of this hardware flag. */ - etd->crc_enabled = (etd->fw_version & 0x4000) == 0x4000 || - dmi_check_system(elantech_dmi_force_crc_enabled); + info->crc_enabled = (info->fw_version & 0x4000) == 0x4000 || + dmi_check_system(elantech_dmi_force_crc_enabled); /* Enable real hardware resolution on hw_version 3 ? */ - etd->set_hw_resolution = !dmi_check_system(no_hw_res_dmi_table); + info->set_hw_resolution = !dmi_check_system(no_hw_res_dmi_table); return 0; } -/* - * Initialize the touchpad and create sysfs entries - */ -int elantech_init(struct psmouse *psmouse) +static int elantech_query_info(struct psmouse *psmouse, + struct elantech_device_info *info) { - struct elantech_data *etd; - int i; - int error = -EINVAL; unsigned char param[3]; - struct input_dev *tp_dev; - psmouse->private = etd = kzalloc(sizeof(struct elantech_data), GFP_KERNEL); - if (!etd) - return -ENOMEM; - - psmouse_reset(psmouse); - - etd->parity[0] = 1; - for (i = 1; i < 256; i++) - etd->parity[i] = etd->parity[i & (i - 1)] ^ 1; + memset(info, 0, sizeof(*info)); /* * Do the version query again so we can store the result */ if (synaptics_send_cmd(psmouse, ETP_FW_VERSION_QUERY, param)) { psmouse_err(psmouse, "failed to query firmware version.\n"); - goto init_fail; + return -EINVAL; } - etd->fw_version = (param[0] << 16) | (param[1] << 8) | param[2]; + info->fw_version = (param[0] << 16) | (param[1] << 8) | param[2]; - if (elantech_set_properties(etd)) { + if (elantech_set_properties(info)) { psmouse_err(psmouse, "unknown hardware version, aborting...\n"); - goto init_fail; + return -EINVAL; } psmouse_info(psmouse, "assuming hardware version %d (with firmware version 0x%02x%02x%02x)\n", - etd->hw_version, param[0], param[1], param[2]); + info->hw_version, param[0], param[1], param[2]); - if (etd->send_cmd(psmouse, ETP_CAPABILITIES_QUERY, - etd->capabilities)) { + if (info->send_cmd(psmouse, ETP_CAPABILITIES_QUERY, + info->capabilities)) { psmouse_err(psmouse, "failed to query capabilities.\n"); - goto init_fail; + return -EINVAL; } psmouse_info(psmouse, "Synaptics capabilities query result 0x%02x, 0x%02x, 0x%02x.\n", - etd->capabilities[0], etd->capabilities[1], - etd->capabilities[2]); + info->capabilities[0], info->capabilities[1], + info->capabilities[2]); - if (etd->hw_version != 1) { - if (etd->send_cmd(psmouse, ETP_SAMPLE_QUERY, etd->samples)) { + if (info->hw_version != 1) { + if (info->send_cmd(psmouse, ETP_SAMPLE_QUERY, info->samples)) { psmouse_err(psmouse, "failed to query sample data\n"); - goto init_fail; + return -EINVAL; } psmouse_info(psmouse, "Elan sample query result %02x, %02x, %02x\n", - etd->samples[0], etd->samples[1], etd->samples[2]); + info->samples[0], + info->samples[1], + info->samples[2]); } - if (etd->samples[1] == 0x74 && etd->hw_version == 0x03) { + if (info->samples[1] == 0x74 && info->hw_version == 0x03) { /* * This module has a bug which makes absolute mode * unusable, so let's abort so we'll be using standard @@ -1715,16 +1719,181 @@ int elantech_init(struct psmouse *psmouse) */ psmouse_info(psmouse, "absolute mode broken, forcing standard PS/2 protocol\n"); + return -ENODEV; + } + + /* The MSB indicates the presence of the trackpoint */ + info->has_trackpoint = (info->capabilities[0] & 0x80) == 0x80; + + info->x_res = 31; + info->y_res = 31; + if (info->hw_version == 4) { + if (elantech_get_resolution_v4(psmouse, + &info->x_res, + &info->y_res, + &info->bus)) { + psmouse_warn(psmouse, + "failed to query resolution data.\n"); + } + } + + return 0; +} + +#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS) + +/* + * The newest Elantech device can use a secondary bus (over SMBus) which + * provides a better bandwidth and allow a better control of the touchpads. + * This is used to decide if we need to use this bus or not. + */ +enum { + ELANTECH_SMBUS_NOT_SET = -1, + ELANTECH_SMBUS_OFF, + ELANTECH_SMBUS_ON, +}; + +static int elantech_smbus = IS_ENABLED(CONFIG_MOUSE_ELAN_I2C_SMBUS) ? + ELANTECH_SMBUS_NOT_SET : ELANTECH_SMBUS_OFF; +module_param_named(elantech_smbus, elantech_smbus, int, 0644); +MODULE_PARM_DESC(elantech_smbus, "Use a secondary bus for the Elantech device."); + +static int elantech_create_smbus(struct psmouse *psmouse, + struct elantech_device_info *info, + bool leave_breadcrumbs) +{ + const struct property_entry i2c_properties[] = { + PROPERTY_ENTRY_BOOL("elan,trackpoint"), + { }, + }; + struct i2c_board_info smbus_board = { + I2C_BOARD_INFO("elan_i2c", 0x15), + .flags = I2C_CLIENT_HOST_NOTIFY, + }; + + if (info->has_trackpoint) + smbus_board.properties = i2c_properties; + + return psmouse_smbus_init(psmouse, &smbus_board, NULL, 0, false, + leave_breadcrumbs); +} + +/** + * elantech_setup_smbus - called once the PS/2 devices are enumerated + * and decides to instantiate a SMBus InterTouch device. + */ +static int elantech_setup_smbus(struct psmouse *psmouse, + struct elantech_device_info *info, + bool leave_breadcrumbs) +{ + int error; + + if (elantech_smbus == ELANTECH_SMBUS_OFF) + return -ENXIO; + + if (elantech_smbus == ELANTECH_SMBUS_NOT_SET) { + /* + * New ICs are enabled by default. + * Old ICs are up to the user to decide. + */ + if (!ETP_NEW_IC_SMBUS_HOST_NOTIFY(info->fw_version)) + return -ENXIO; + } + + psmouse_info(psmouse, "Trying to set up SMBus access\n"); + + error = elantech_create_smbus(psmouse, info, leave_breadcrumbs); + if (error) { + if (error == -EAGAIN) + psmouse_info(psmouse, "SMbus companion is not ready yet\n"); + else + psmouse_err(psmouse, "unable to create intertouch device\n"); + + return error; + } + + return 0; +} + +static bool elantech_use_host_notify(struct psmouse *psmouse, + struct elantech_device_info *info) +{ + if (ETP_NEW_IC_SMBUS_HOST_NOTIFY(info->fw_version)) + return true; + + switch (info->bus) { + case ETP_BUS_PS2_ONLY: + /* expected case */ + break; + case ETP_BUS_SMB_ALERT_ONLY: + /* fall-through */ + case ETP_BUS_PS2_SMB_ALERT: + psmouse_dbg(psmouse, "Ignoring SMBus provider through alert protocol.\n"); + break; + case ETP_BUS_SMB_HST_NTFY_ONLY: + /* fall-through */ + case ETP_BUS_PS2_SMB_HST_NTFY: + return true; + default: + psmouse_dbg(psmouse, + "Ignoring SMBus bus provider %d.\n", + info->bus); + } + + return false; +} + +int elantech_init_smbus(struct psmouse *psmouse) +{ + struct elantech_device_info info; + int error = -EINVAL; + + psmouse_reset(psmouse); + + error = elantech_query_info(psmouse, &info); + if (error) + goto init_fail; + + if (info.hw_version < 4) { + error = -ENXIO; goto init_fail; } + return elantech_create_smbus(psmouse, &info, false); + init_fail: + psmouse_reset(psmouse); + return error; +} +#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */ + +/* + * Initialize the touchpad and create sysfs entries + */ +static int elantech_setup_ps2(struct psmouse *psmouse, + struct elantech_device_info *info) +{ + struct elantech_data *etd; + int i; + int error = -EINVAL; + struct input_dev *tp_dev; + + psmouse->private = etd = kzalloc(sizeof(*etd), GFP_KERNEL); + if (!etd) + return -ENOMEM; + + etd->info = *info; + + etd->parity[0] = 1; + for (i = 1; i < 256; i++) + etd->parity[i] = etd->parity[i & (i - 1)] ^ 1; + if (elantech_set_absolute_mode(psmouse)) { psmouse_err(psmouse, "failed to put touchpad into absolute mode.\n"); goto init_fail; } - if (etd->fw_version == 0x381f17) { + if (info->fw_version == 0x381f17) { etd->original_set_rate = psmouse->set_rate; psmouse->set_rate = elantech_set_rate_restore_reg_07; } @@ -1743,8 +1912,7 @@ int elantech_init(struct psmouse *psmouse) goto init_fail; } - /* The MSB indicates the presence of the trackpoint */ - if ((etd->capabilities[0] & 0x80) == 0x80) { + if (info->has_trackpoint) { tp_dev = input_allocate_device(); if (!tp_dev) { @@ -1780,7 +1948,7 @@ int elantech_init(struct psmouse *psmouse) psmouse->protocol_handler = elantech_process_byte; psmouse->disconnect = elantech_disconnect; psmouse->reconnect = elantech_reconnect; - psmouse->pktsize = etd->hw_version > 1 ? 6 : 4; + psmouse->pktsize = info->hw_version > 1 ? 6 : 4; return 0; init_fail_tp_reg: @@ -1789,7 +1957,70 @@ int elantech_init(struct psmouse *psmouse) sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, &elantech_attr_group); init_fail: - psmouse_reset(psmouse); kfree(etd); return error; } + +int elantech_init_ps2(struct psmouse *psmouse) +{ + struct elantech_device_info info; + int error = -EINVAL; + + psmouse_reset(psmouse); + + error = elantech_query_info(psmouse, &info); + if (error) + goto init_fail; + + error = elantech_setup_ps2(psmouse, &info); + if (error) + goto init_fail; + + return 0; + init_fail: + psmouse_reset(psmouse); + return error; +} + +int elantech_init(struct psmouse *psmouse) +{ + struct elantech_device_info info; + int error = -EINVAL; + + psmouse_reset(psmouse); + + error = elantech_query_info(psmouse, &info); + if (error) + goto init_fail; + +#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS) + + if (elantech_use_host_notify(psmouse, &info)) { + if (!IS_ENABLED(CONFIG_MOUSE_ELAN_I2C_SMBUS) || + !IS_ENABLED(CONFIG_MOUSE_PS2_ELANTECH_SMBUS)) { + psmouse_warn(psmouse, + "The touchpad can support a better bus than the too old PS/2 protocol. " + "Make sure MOUSE_PS2_ELANTECH_SMBUS and MOUSE_ELAN_I2C_SMBUS are enabled to get a better touchpad experience.\n"); + } + error = elantech_setup_smbus(psmouse, &info, true); + if (!error) + return PSMOUSE_ELANTECH_SMBUS; + } + +#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */ + + error = elantech_setup_ps2(psmouse, &info); + if (error < 0) { + /* + * Not using any flavor of Elantech support, so clean up + * SMbus breadcrumbs, if any. + */ + psmouse_smbus_cleanup(psmouse); + goto init_fail; + } + + return PSMOUSE_ELANTECH; + init_fail: + psmouse_reset(psmouse); + return error; +} diff --git a/drivers/input/mouse/elantech.h b/drivers/input/mouse/elantech.h index e1cbf409d9c8..119727085a60 100644 --- a/drivers/input/mouse/elantech.h +++ b/drivers/input/mouse/elantech.h @@ -107,6 +107,30 @@ #define ETP_WEIGHT_VALUE 5 /* + * Bus information on 3rd byte of query ETP_RESOLUTION_QUERY(0x04) + */ +#define ETP_BUS_PS2_ONLY 0 +#define ETP_BUS_SMB_ALERT_ONLY 1 +#define ETP_BUS_SMB_HST_NTFY_ONLY 2 +#define ETP_BUS_PS2_SMB_ALERT 3 +#define ETP_BUS_PS2_SMB_HST_NTFY 4 + +/* + * New ICs are either using SMBus Host Notify or just plain PS2. + * + * ETP_FW_VERSION_QUERY is: + * Byte 1: + * - bit 0..3: IC BODY + * Byte 2: + * - bit 4: HiddenButton + * - bit 5: PS2_SMBUS_NOTIFY + * - bit 6: PS2CRCCheck + */ +#define ETP_NEW_IC_SMBUS_HOST_NOTIFY(fw_version) \ + ((((fw_version) & 0x0f2000) == 0x0f2000) && \ + ((fw_version) & 0x0000ff) > 0) + +/* * The base position for one finger, v4 hardware */ struct finger_pos { @@ -114,6 +138,25 @@ struct finger_pos { unsigned int y; }; +struct elantech_device_info { + unsigned char capabilities[3]; + unsigned char samples[3]; + unsigned char debug; + unsigned char hw_version; + unsigned int fw_version; + unsigned int x_res; + unsigned int y_res; + unsigned int bus; + bool paritycheck; + bool jumpy_cursor; + bool reports_pressure; + bool crc_enabled; + bool set_hw_resolution; + bool has_trackpoint; + int (*send_cmd)(struct psmouse *psmouse, unsigned char c, + unsigned char *param); +}; + struct elantech_data { struct input_dev *tp_dev; /* Relative device for trackpoint */ char tp_phys[32]; @@ -127,27 +170,18 @@ struct elantech_data { unsigned char reg_24; unsigned char reg_25; unsigned char reg_26; - unsigned char debug; - unsigned char capabilities[3]; - unsigned char samples[3]; - bool paritycheck; - bool jumpy_cursor; - bool reports_pressure; - bool crc_enabled; - bool set_hw_resolution; - unsigned char hw_version; - unsigned int fw_version; unsigned int single_finger_reports; unsigned int y_max; unsigned int width; struct finger_pos mt[ETP_MAX_FINGERS]; unsigned char parity[256]; - int (*send_cmd)(struct psmouse *psmouse, unsigned char c, unsigned char *param); + struct elantech_device_info info; void (*original_set_rate)(struct psmouse *psmouse, unsigned int rate); }; #ifdef CONFIG_MOUSE_PS2_ELANTECH int elantech_detect(struct psmouse *psmouse, bool set_properties); +int elantech_init_ps2(struct psmouse *psmouse); int elantech_init(struct psmouse *psmouse); #else static inline int elantech_detect(struct psmouse *psmouse, bool set_properties) @@ -158,6 +192,19 @@ static inline int elantech_init(struct psmouse *psmouse) { return -ENOSYS; } +static inline int elantech_init_ps2(struct psmouse *psmouse) +{ + return -ENOSYS; +} #endif /* CONFIG_MOUSE_PS2_ELANTECH */ +#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS) +int elantech_init_smbus(struct psmouse *psmouse); +#else +static inline int elantech_init_smbus(struct psmouse *psmouse) +{ + return -ENOSYS; +} +#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */ + #endif diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index 8900c3166ebf..5ff5b1952be0 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -856,7 +856,17 @@ static const struct psmouse_protocol psmouse_protocols[] = { .name = "ETPS/2", .alias = "elantech", .detect = elantech_detect, - .init = elantech_init, + .init = elantech_init_ps2, + }, +#endif +#ifdef CONFIG_MOUSE_PS2_ELANTECH_SMBUS + { + .type = PSMOUSE_ELANTECH_SMBUS, + .name = "ETSMBus", + .alias = "elantech-smbus", + .detect = elantech_detect, + .init = elantech_init_smbus, + .smbus_companion = true, }, #endif #ifdef CONFIG_MOUSE_PS2_SENTELIC @@ -1158,8 +1168,13 @@ static int psmouse_extensions(struct psmouse *psmouse, /* Try Elantech touchpad */ if (max_proto > PSMOUSE_IMEX && psmouse_try_protocol(psmouse, PSMOUSE_ELANTECH, - &max_proto, set_properties, true)) { - return PSMOUSE_ELANTECH; + &max_proto, set_properties, false)) { + if (!set_properties) + return PSMOUSE_ELANTECH; + + ret = elantech_init(psmouse); + if (ret >= 0) + return ret; } if (max_proto > PSMOUSE_IMEX) { diff --git a/drivers/input/mouse/psmouse-smbus.c b/drivers/input/mouse/psmouse-smbus.c index c7ac24d119c1..852d4b486ddb 100644 --- a/drivers/input/mouse/psmouse-smbus.c +++ b/drivers/input/mouse/psmouse-smbus.c @@ -23,6 +23,7 @@ struct psmouse_smbus_dev { struct i2c_client *client; struct list_head node; bool dead; + bool need_deactivate; }; static LIST_HEAD(psmouse_smbus_list); @@ -118,7 +119,10 @@ static psmouse_ret_t psmouse_smbus_process_byte(struct psmouse *psmouse) static int psmouse_smbus_reconnect(struct psmouse *psmouse) { - psmouse_deactivate(psmouse); + struct psmouse_smbus_dev *smbdev = psmouse->private; + + if (smbdev->need_deactivate) + psmouse_deactivate(psmouse); return 0; } @@ -225,6 +229,7 @@ void psmouse_smbus_cleanup(struct psmouse *psmouse) int psmouse_smbus_init(struct psmouse *psmouse, const struct i2c_board_info *board, const void *pdata, size_t pdata_size, + bool need_deactivate, bool leave_breadcrumbs) { struct psmouse_smbus_dev *smbdev; @@ -236,13 +241,20 @@ int psmouse_smbus_init(struct psmouse *psmouse, smbdev->psmouse = psmouse; smbdev->board = *board; + smbdev->need_deactivate = need_deactivate; - smbdev->board.platform_data = kmemdup(pdata, pdata_size, GFP_KERNEL); - if (!smbdev->board.platform_data) { - kfree(smbdev); - return -ENOMEM; + if (pdata) { + smbdev->board.platform_data = kmemdup(pdata, pdata_size, + GFP_KERNEL); + if (!smbdev->board.platform_data) { + kfree(smbdev); + return -ENOMEM; + } } + if (need_deactivate) + psmouse_deactivate(psmouse); + psmouse->private = smbdev; psmouse->protocol_handler = psmouse_smbus_process_byte; psmouse->reconnect = psmouse_smbus_reconnect; @@ -250,8 +262,6 @@ int psmouse_smbus_init(struct psmouse *psmouse, psmouse->disconnect = psmouse_smbus_disconnect; psmouse->resync_time = 0; - psmouse_deactivate(psmouse); - mutex_lock(&psmouse_smbus_mutex); list_add_tail(&smbdev->node, &psmouse_smbus_list); mutex_unlock(&psmouse_smbus_mutex); diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h index 71ac50082c8b..64c3a5d3fb3e 100644 --- a/drivers/input/mouse/psmouse.h +++ b/drivers/input/mouse/psmouse.h @@ -68,6 +68,7 @@ enum psmouse_type { PSMOUSE_VMMOUSE, PSMOUSE_BYD, PSMOUSE_SYNAPTICS_SMBUS, + PSMOUSE_ELANTECH_SMBUS, PSMOUSE_AUTO /* This one should always be last */ }; @@ -224,6 +225,7 @@ struct i2c_board_info; int psmouse_smbus_init(struct psmouse *psmouse, const struct i2c_board_info *board, const void *pdata, size_t pdata_size, + bool need_deactivate, bool leave_breadcrumbs); void psmouse_smbus_cleanup(struct psmouse *psmouse); diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index a9591d278145..55d33500d55e 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -1754,7 +1754,7 @@ static int synaptics_create_intertouch(struct psmouse *psmouse, }; return psmouse_smbus_init(psmouse, &intertouch_board, - &pdata, sizeof(pdata), + &pdata, sizeof(pdata), true, leave_breadcrumbs); } diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 3e613afa10b4..32267c1afebc 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -164,6 +164,17 @@ config TOUCHSCREEN_CHIPONE_ICN8318 To compile this driver as a module, choose M here: the module will be called chipone_icn8318. +config TOUCHSCREEN_CHIPONE_ICN8505 + tristate "chipone icn8505 touchscreen controller" + depends on I2C && ACPI + help + Say Y here if you have a ChipOne icn8505 based I2C touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called chipone_icn8505. + config TOUCHSCREEN_CY8CTMG110 tristate "cy8ctmg110 touchscreen" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index dddae7973436..fd4fd32fb73f 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT) += atmel_mxt_ts.o obj-$(CONFIG_TOUCHSCREEN_AUO_PIXCIR) += auo-pixcir-ts.o obj-$(CONFIG_TOUCHSCREEN_BU21013) += bu21013_ts.o obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8318) += chipone_icn8318.o +obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8505) += chipone_icn8505.o obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110) += cy8ctmg110_ts.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_CORE) += cyttsp_core.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_I2C) += cyttsp_i2c.o cyttsp_i2c_common.o diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c index 09194721aed2..54fe190fd4bc 100644 --- a/drivers/input/touchscreen/atmel_mxt_ts.c +++ b/drivers/input/touchscreen/atmel_mxt_ts.c @@ -194,6 +194,8 @@ enum t100_type { /* Delay times */ #define MXT_BACKUP_TIME 50 /* msec */ +#define MXT_RESET_GPIO_TIME 20 /* msec */ +#define MXT_RESET_INVALID_CHG 100 /* msec */ #define MXT_RESET_TIME 200 /* msec */ #define MXT_RESET_TIMEOUT 3000 /* msec */ #define MXT_CRC_TIMEOUT 1000 /* msec */ @@ -1208,7 +1210,7 @@ static int mxt_soft_reset(struct mxt_data *data) return ret; /* Ignore CHG line for 100ms after reset */ - msleep(100); + msleep(MXT_RESET_INVALID_CHG); mxt_acquire_irq(data); @@ -2999,142 +3001,6 @@ static int mxt_parse_device_properties(struct mxt_data *data) return 0; } -#ifdef CONFIG_ACPI - -struct mxt_acpi_platform_data { - const char *hid; - const struct property_entry *props; -}; - -static unsigned int samus_touchpad_buttons[] = { - KEY_RESERVED, - KEY_RESERVED, - KEY_RESERVED, - BTN_LEFT -}; - -static const struct property_entry samus_touchpad_props[] = { - PROPERTY_ENTRY_U32_ARRAY("linux,gpio-keymap", samus_touchpad_buttons), - { } -}; - -static struct mxt_acpi_platform_data samus_platform_data[] = { - { - /* Touchpad */ - .hid = "ATML0000", - .props = samus_touchpad_props, - }, - { - /* Touchscreen */ - .hid = "ATML0001", - }, - { } -}; - -static unsigned int chromebook_tp_buttons[] = { - KEY_RESERVED, - KEY_RESERVED, - KEY_RESERVED, - KEY_RESERVED, - KEY_RESERVED, - BTN_LEFT -}; - -static const struct property_entry chromebook_tp_props[] = { - PROPERTY_ENTRY_U32_ARRAY("linux,gpio-keymap", chromebook_tp_buttons), - { } -}; - -static struct mxt_acpi_platform_data chromebook_platform_data[] = { - { - /* Touchpad */ - .hid = "ATML0000", - .props = chromebook_tp_props, - }, - { - /* Touchscreen */ - .hid = "ATML0001", - }, - { } -}; - -static const struct dmi_system_id mxt_dmi_table[] = { - { - /* 2015 Google Pixel */ - .ident = "Chromebook Pixel 2", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), - DMI_MATCH(DMI_PRODUCT_NAME, "Samus"), - }, - .driver_data = samus_platform_data, - }, - { - /* Samsung Chromebook Pro */ - .ident = "Samsung Chromebook Pro", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Google"), - DMI_MATCH(DMI_PRODUCT_NAME, "Caroline"), - }, - .driver_data = samus_platform_data, - }, - { - /* Other Google Chromebooks */ - .ident = "Chromebook", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), - }, - .driver_data = chromebook_platform_data, - }, - { } -}; - -static int mxt_prepare_acpi_properties(struct i2c_client *client) -{ - struct acpi_device *adev; - const struct dmi_system_id *system_id; - const struct mxt_acpi_platform_data *acpi_pdata; - - adev = ACPI_COMPANION(&client->dev); - if (!adev) - return -ENOENT; - - system_id = dmi_first_match(mxt_dmi_table); - if (!system_id) - return -ENOENT; - - acpi_pdata = system_id->driver_data; - if (!acpi_pdata) - return -ENOENT; - - while (acpi_pdata->hid) { - if (!strcmp(acpi_device_hid(adev), acpi_pdata->hid)) { - /* - * Remove previously installed properties if we - * are probing this device not for the very first - * time. - */ - device_remove_properties(&client->dev); - - /* - * Now install the platform-specific properties - * that are missing from ACPI. - */ - device_add_properties(&client->dev, acpi_pdata->props); - break; - } - - acpi_pdata++; - } - - return 0; -} -#else -static int mxt_prepare_acpi_properties(struct i2c_client *client) -{ - return -ENOENT; -} -#endif - static const struct dmi_system_id chromebook_T9_suspend_dmi[] = { { .matches = { @@ -3156,6 +3022,18 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id) int error; /* + * Ignore devices that do not have device properties attached to + * them, as we need help determining whether we are dealing with + * touch screen or touchpad. + * + * So far on x86 the only users of Atmel touch controllers are + * Chromebooks, and chromeos_laptop driver will ensure that + * necessary properties are provided (if firmware does not do that). + */ + if (!device_property_present(&client->dev, "compatible")) + return -ENXIO; + + /* * Ignore ACPI devices representing bootloader mode. * * This is a bit of a hack: Google Chromebook BIOS creates ACPI @@ -3186,10 +3064,6 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id) data->suspend_mode = dmi_check_system(chromebook_T9_suspend_dmi) ? MXT_SUSPEND_T9_CTRL : MXT_SUSPEND_DEEP_SLEEP; - error = mxt_prepare_acpi_properties(client); - if (error && error != -ENOENT) - return error; - error = mxt_parse_device_properties(data); if (error) return error; @@ -3210,20 +3084,14 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id) return error; } + disable_irq(client->irq); + if (data->reset_gpio) { - data->in_bootloader = true; - msleep(MXT_RESET_TIME); - reinit_completion(&data->bl_completion); + msleep(MXT_RESET_GPIO_TIME); gpiod_set_value(data->reset_gpio, 1); - error = mxt_wait_for_completion(data, &data->bl_completion, - MXT_RESET_TIMEOUT); - if (error) - return error; - data->in_bootloader = false; + msleep(MXT_RESET_INVALID_CHG); } - disable_irq(client->irq); - error = mxt_initialize(data); if (error) return error; diff --git a/drivers/input/touchscreen/chipone_icn8505.c b/drivers/input/touchscreen/chipone_icn8505.c new file mode 100644 index 000000000000..c768186ce856 --- /dev/null +++ b/drivers/input/touchscreen/chipone_icn8505.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for ChipOne icn8505 i2c touchscreen controller + * + * Copyright (c) 2015-2018 Red Hat Inc. + * + * Red Hat authors: + * Hans de Goede <hdegoede@redhat.com> + */ + +#include <asm/unaligned.h> +#include <linux/acpi.h> +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/input/touchscreen.h> +#include <linux/module.h> + +/* Normal operation mode defines */ +#define ICN8505_REG_ADDR_WIDTH 16 + +#define ICN8505_REG_POWER 0x0004 +#define ICN8505_REG_TOUCHDATA 0x1000 +#define ICN8505_REG_CONFIGDATA 0x8000 + +/* ICN8505_REG_POWER commands */ +#define ICN8505_POWER_ACTIVE 0x00 +#define ICN8505_POWER_MONITOR 0x01 +#define ICN8505_POWER_HIBERNATE 0x02 +/* + * The Android driver uses these to turn on/off the charger filter, but the + * filter is way too aggressive making e.g. onscreen keyboards unusable. + */ +#define ICN8505_POWER_ENA_CHARGER_MODE 0x55 +#define ICN8505_POWER_DIS_CHARGER_MODE 0x66 + +#define ICN8505_MAX_TOUCHES 10 + +/* Programming mode defines */ +#define ICN8505_PROG_I2C_ADDR 0x30 +#define ICN8505_PROG_REG_ADDR_WIDTH 24 + +#define MAX_FW_UPLOAD_TRIES 3 + +struct icn8505_touch { + u8 slot; + u8 x[2]; + u8 y[2]; + u8 pressure; /* Seems more like finger width then pressure really */ + u8 event; +/* The difference between 2 and 3 is unclear */ +#define ICN8505_EVENT_NO_DATA 1 /* No finger seen yet since wakeup */ +#define ICN8505_EVENT_UPDATE1 2 /* New or updated coordinates */ +#define ICN8505_EVENT_UPDATE2 3 /* New or updated coordinates */ +#define ICN8505_EVENT_END 4 /* Finger lifted */ +} __packed; + +struct icn8505_touch_data { + u8 softbutton; + u8 touch_count; + struct icn8505_touch touches[ICN8505_MAX_TOUCHES]; +} __packed; + +struct icn8505_data { + struct i2c_client *client; + struct input_dev *input; + struct gpio_desc *wake_gpio; + struct touchscreen_properties prop; + char firmware_name[32]; +}; + +static int icn8505_read_xfer(struct i2c_client *client, u16 i2c_addr, + int reg_addr, int reg_addr_width, + void *data, int len, bool silent) +{ + u8 buf[3]; + int i, ret; + struct i2c_msg msg[2] = { + { + .addr = i2c_addr, + .buf = buf, + .len = reg_addr_width / 8, + }, + { + .addr = i2c_addr, + .flags = I2C_M_RD, + .buf = data, + .len = len, + } + }; + + for (i = 0; i < (reg_addr_width / 8); i++) + buf[i] = (reg_addr >> (reg_addr_width - (i + 1) * 8)) & 0xff; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret != ARRAY_SIZE(msg)) { + if (ret >= 0) + ret = -EIO; + if (!silent) + dev_err(&client->dev, + "Error reading addr %#x reg %#x: %d\n", + i2c_addr, reg_addr, ret); + return ret; + } + + return 0; +} + +static int icn8505_write_xfer(struct i2c_client *client, u16 i2c_addr, + int reg_addr, int reg_addr_width, + const void *data, int len, bool silent) +{ + u8 buf[3 + 32]; /* 3 bytes for 24 bit reg-addr + 32 bytes max len */ + int i, ret; + struct i2c_msg msg = { + .addr = i2c_addr, + .buf = buf, + .len = reg_addr_width / 8 + len, + }; + + if (WARN_ON(len > 32)) + return -EINVAL; + + for (i = 0; i < (reg_addr_width / 8); i++) + buf[i] = (reg_addr >> (reg_addr_width - (i + 1) * 8)) & 0xff; + + memcpy(buf + reg_addr_width / 8, data, len); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) { + if (ret >= 0) + ret = -EIO; + if (!silent) + dev_err(&client->dev, + "Error writing addr %#x reg %#x: %d\n", + i2c_addr, reg_addr, ret); + return ret; + } + + return 0; +} + +static int icn8505_read_data(struct icn8505_data *icn8505, int reg, + void *buf, int len) +{ + return icn8505_read_xfer(icn8505->client, icn8505->client->addr, reg, + ICN8505_REG_ADDR_WIDTH, buf, len, false); +} + +static int icn8505_read_reg_silent(struct icn8505_data *icn8505, int reg) +{ + u8 buf; + int error; + + error = icn8505_read_xfer(icn8505->client, icn8505->client->addr, reg, + ICN8505_REG_ADDR_WIDTH, &buf, 1, true); + if (error) + return error; + + return buf; +} + +static int icn8505_write_reg(struct icn8505_data *icn8505, int reg, u8 val) +{ + return icn8505_write_xfer(icn8505->client, icn8505->client->addr, reg, + ICN8505_REG_ADDR_WIDTH, &val, 1, false); +} + +static int icn8505_read_prog_data(struct icn8505_data *icn8505, int reg, + void *buf, int len) +{ + return icn8505_read_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg, + ICN8505_PROG_REG_ADDR_WIDTH, buf, len, false); +} + +static int icn8505_write_prog_data(struct icn8505_data *icn8505, int reg, + const void *buf, int len) +{ + return icn8505_write_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg, + ICN8505_PROG_REG_ADDR_WIDTH, buf, len, false); +} + +static int icn8505_write_prog_reg(struct icn8505_data *icn8505, int reg, u8 val) +{ + return icn8505_write_xfer(icn8505->client, ICN8505_PROG_I2C_ADDR, reg, + ICN8505_PROG_REG_ADDR_WIDTH, &val, 1, false); +} + +/* + * Note this function uses a number of magic register addresses and values, + * there are deliberately no defines for these because the algorithm is taken + * from the icn85xx Android driver and I do not want to make up possibly wrong + * names for the addresses and/or values. + */ +static int icn8505_try_fw_upload(struct icn8505_data *icn8505, + const struct firmware *fw) +{ + struct device *dev = &icn8505->client->dev; + size_t offset, count; + int error; + u8 buf[4]; + u32 crc; + + /* Put the controller in programming mode */ + error = icn8505_write_prog_reg(icn8505, 0xcc3355, 0x5a); + if (error) + return error; + + usleep_range(2000, 5000); + + error = icn8505_write_prog_reg(icn8505, 0x040400, 0x01); + if (error) + return error; + + usleep_range(2000, 5000); + + error = icn8505_read_prog_data(icn8505, 0x040002, buf, 1); + if (error) + return error; + + if (buf[0] != 0x85) { + dev_err(dev, "Failed to enter programming mode\n"); + return -ENODEV; + } + + usleep_range(1000, 5000); + + /* Enable CRC mode */ + error = icn8505_write_prog_reg(icn8505, 0x40028, 1); + if (error) + return error; + + /* Send the firmware to SRAM */ + for (offset = 0; offset < fw->size; offset += count) { + count = min_t(size_t, fw->size - offset, 32); + error = icn8505_write_prog_data(icn8505, offset, + fw->data + offset, count); + if (error) + return error; + } + + /* Disable CRC mode */ + error = icn8505_write_prog_reg(icn8505, 0x40028, 0); + if (error) + return error; + + /* Get and check length and CRC */ + error = icn8505_read_prog_data(icn8505, 0x40034, buf, 2); + if (error) + return error; + + if (get_unaligned_le16(buf) != fw->size) { + dev_warn(dev, "Length mismatch after uploading fw\n"); + return -EIO; + } + + error = icn8505_read_prog_data(icn8505, 0x4002c, buf, 4); + if (error) + return error; + + crc = crc32_be(0, fw->data, fw->size); + if (get_unaligned_le32(buf) != crc) { + dev_warn(dev, "CRC mismatch after uploading fw\n"); + return -EIO; + } + + /* Boot controller from SRAM */ + error = icn8505_write_prog_reg(icn8505, 0x40400, 0x03); + if (error) + return error; + + usleep_range(2000, 5000); + return 0; +} + +static int icn8505_upload_fw(struct icn8505_data *icn8505) +{ + struct device *dev = &icn8505->client->dev; + const struct firmware *fw; + int i, error; + + /* + * Always load the firmware, even if we don't need it at boot, we + * we may need it at resume. Having loaded it once will make the + * firmware class code cache it at suspend/resume. + */ + error = request_firmware(&fw, icn8505->firmware_name, dev); + if (error) { + dev_err(dev, "Firmware request error %d\n", error); + return error; + } + + /* Check if the controller is not already up and running */ + if (icn8505_read_reg_silent(icn8505, 0x000a) == 0x85) + goto success; + + for (i = 1; i <= MAX_FW_UPLOAD_TRIES; i++) { + error = icn8505_try_fw_upload(icn8505, fw); + if (!error) + goto success; + + dev_err(dev, "Failed to upload firmware: %d (attempt %d/%d)\n", + error, i, MAX_FW_UPLOAD_TRIES); + usleep_range(2000, 5000); + } + +success: + release_firmware(fw); + return error; +} + +static bool icn8505_touch_active(u8 event) +{ + return event == ICN8505_EVENT_UPDATE1 || + event == ICN8505_EVENT_UPDATE2; +} + +static irqreturn_t icn8505_irq(int irq, void *dev_id) +{ + struct icn8505_data *icn8505 = dev_id; + struct device *dev = &icn8505->client->dev; + struct icn8505_touch_data touch_data; + int i, error; + + error = icn8505_read_data(icn8505, ICN8505_REG_TOUCHDATA, + &touch_data, sizeof(touch_data)); + if (error) { + dev_err(dev, "Error reading touch data: %d\n", error); + return IRQ_HANDLED; + } + + if (touch_data.touch_count > ICN8505_MAX_TOUCHES) { + dev_warn(dev, "Too many touches %d > %d\n", + touch_data.touch_count, ICN8505_MAX_TOUCHES); + touch_data.touch_count = ICN8505_MAX_TOUCHES; + } + + for (i = 0; i < touch_data.touch_count; i++) { + struct icn8505_touch *touch = &touch_data.touches[i]; + bool act = icn8505_touch_active(touch->event); + + input_mt_slot(icn8505->input, touch->slot); + input_mt_report_slot_state(icn8505->input, MT_TOOL_FINGER, act); + if (!act) + continue; + + touchscreen_report_pos(icn8505->input, &icn8505->prop, + get_unaligned_le16(touch->x), + get_unaligned_le16(touch->y), + true); + } + + input_mt_sync_frame(icn8505->input); + input_report_key(icn8505->input, KEY_LEFTMETA, + touch_data.softbutton == 1); + input_sync(icn8505->input); + + return IRQ_HANDLED; +} + +static int icn8505_probe_acpi(struct icn8505_data *icn8505, struct device *dev) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + const char *subsys = "unknown"; + struct acpi_device *adev; + union acpi_object *obj; + acpi_status status; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + + status = acpi_evaluate_object(adev->handle, "_SUB", NULL, &buffer); + if (ACPI_SUCCESS(status)) { + obj = buffer.pointer; + if (obj->type == ACPI_TYPE_STRING) + subsys = obj->string.pointer; + else + dev_warn(dev, "Warning ACPI _SUB did not return a string\n"); + } else { + dev_warn(dev, "Warning ACPI _SUB failed: %#x\n", status); + buffer.pointer = NULL; + } + + snprintf(icn8505->firmware_name, sizeof(icn8505->firmware_name), + "chipone/icn8505-%s.fw", subsys); + + kfree(buffer.pointer); + return 0; +} + +static int icn8505_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct icn8505_data *icn8505; + struct input_dev *input; + __le16 resolution[2]; + int error; + + if (!client->irq) { + dev_err(dev, "No irq specified\n"); + return -EINVAL; + } + + icn8505 = devm_kzalloc(dev, sizeof(*icn8505), GFP_KERNEL); + if (!icn8505) + return -ENOMEM; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + input->name = client->name; + input->id.bustype = BUS_I2C; + + input_set_capability(input, EV_ABS, ABS_MT_POSITION_X); + input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y); + input_set_capability(input, EV_KEY, KEY_LEFTMETA); + + icn8505->client = client; + icn8505->input = input; + input_set_drvdata(input, icn8505); + + error = icn8505_probe_acpi(icn8505, dev); + if (error) + return error; + + error = icn8505_upload_fw(icn8505); + if (error) + return error; + + error = icn8505_read_data(icn8505, ICN8505_REG_CONFIGDATA, + resolution, sizeof(resolution)); + if (error) { + dev_err(dev, "Error reading resolution: %d\n", error); + return error; + } + + input_set_abs_params(input, ABS_MT_POSITION_X, 0, + le16_to_cpu(resolution[0]) - 1, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, + le16_to_cpu(resolution[1]) - 1, 0, 0); + + touchscreen_parse_properties(input, true, &icn8505->prop); + if (!input_abs_get_max(input, ABS_MT_POSITION_X) || + !input_abs_get_max(input, ABS_MT_POSITION_Y)) { + dev_err(dev, "Error touchscreen-size-x and/or -y missing\n"); + return -EINVAL; + } + + error = input_mt_init_slots(input, ICN8505_MAX_TOUCHES, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (error) + return error; + + error = devm_request_threaded_irq(dev, client->irq, NULL, icn8505_irq, + IRQF_ONESHOT, client->name, icn8505); + if (error) { + dev_err(dev, "Error requesting irq: %d\n", error); + return error; + } + + error = input_register_device(input); + if (error) + return error; + + i2c_set_clientdata(client, icn8505); + return 0; +} + +static int __maybe_unused icn8505_suspend(struct device *dev) +{ + struct icn8505_data *icn8505 = i2c_get_clientdata(to_i2c_client(dev)); + + disable_irq(icn8505->client->irq); + + icn8505_write_reg(icn8505, ICN8505_REG_POWER, ICN8505_POWER_HIBERNATE); + + return 0; +} + +static int __maybe_unused icn8505_resume(struct device *dev) +{ + struct icn8505_data *icn8505 = i2c_get_clientdata(to_i2c_client(dev)); + int error; + + error = icn8505_upload_fw(icn8505); + if (error) + return error; + + enable_irq(icn8505->client->irq); + return 0; +} + +static SIMPLE_DEV_PM_OPS(icn8505_pm_ops, icn8505_suspend, icn8505_resume); + +static const struct acpi_device_id icn8505_acpi_match[] = { + { "CHPN0001" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, icn8505_acpi_match); + +static struct i2c_driver icn8505_driver = { + .driver = { + .name = "chipone_icn8505", + .pm = &icn8505_pm_ops, + .acpi_match_table = icn8505_acpi_match, + }, + .probe_new = icn8505_probe, +}; + +module_i2c_driver(icn8505_driver); + +MODULE_DESCRIPTION("ChipOne icn8505 I2C Touchscreen Driver"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c index 9736c83dd418..f2d9c2c41885 100644 --- a/drivers/input/touchscreen/goodix.c +++ b/drivers/input/touchscreen/goodix.c @@ -933,6 +933,7 @@ MODULE_DEVICE_TABLE(i2c, goodix_ts_id); #ifdef CONFIG_ACPI static const struct acpi_device_id goodix_acpi_match[] = { { "GDIX1001", 0 }, + { "GDIX1002", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, goodix_acpi_match); diff --git a/drivers/input/touchscreen/mk712.c b/drivers/input/touchscreen/mk712.c index bd5352824f77..c179060525ae 100644 --- a/drivers/input/touchscreen/mk712.c +++ b/drivers/input/touchscreen/mk712.c @@ -17,7 +17,7 @@ * found in Gateway AOL Connected Touchpad computers. * * Documentation for ICS MK712 can be found at: - * http://www.idt.com/products/getDoc.cfm?docID=18713923 + * https://www.idt.com/general-parts/mk712-touch-screen-controller */ /* diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c index f1043ae71dcc..b86c1e5fbc11 100644 --- a/drivers/input/touchscreen/ti_am335x_tsc.c +++ b/drivers/input/touchscreen/ti_am335x_tsc.c @@ -34,6 +34,8 @@ #define SEQ_SETTLE 275 #define MAX_12BIT ((1 << 12) - 1) +#define TSC_IRQENB_MASK (IRQENB_FIFO0THRES | IRQENB_EOS | IRQENB_HW_PEN) + static const int config_pins[] = { STEPCONFIG_XPP, STEPCONFIG_XNN, @@ -274,6 +276,7 @@ static irqreturn_t titsc_irq(int irq, void *dev) if (status & IRQENB_HW_PEN) { ts_dev->pen_down = true; irqclr |= IRQENB_HW_PEN; + pm_stay_awake(ts_dev->mfd_tscadc->dev); } if (status & IRQENB_PENUP) { @@ -283,6 +286,7 @@ static irqreturn_t titsc_irq(int irq, void *dev) input_report_key(input_dev, BTN_TOUCH, 0); input_report_abs(input_dev, ABS_PRESSURE, 0); input_sync(input_dev); + pm_relax(ts_dev->mfd_tscadc->dev); } else { ts_dev->pen_down = true; } @@ -432,6 +436,7 @@ static int titsc_probe(struct platform_device *pdev) goto err_free_mem; } + titsc_writel(ts_dev, REG_IRQSTATUS, TSC_IRQENB_MASK); titsc_writel(ts_dev, REG_IRQENABLE, IRQENB_FIFO0THRES); titsc_writel(ts_dev, REG_IRQENABLE, IRQENB_EOS); err = titsc_config_wires(ts_dev); @@ -495,6 +500,7 @@ static int __maybe_unused titsc_suspend(struct device *dev) tscadc_dev = ti_tscadc_dev_get(to_platform_device(dev)); if (device_may_wakeup(tscadc_dev->dev)) { + titsc_writel(ts_dev, REG_IRQSTATUS, TSC_IRQENB_MASK); idle = titsc_readl(ts_dev, REG_IRQENABLE); titsc_writel(ts_dev, REG_IRQENABLE, (idle | IRQENB_HW_PEN)); @@ -513,6 +519,7 @@ static int __maybe_unused titsc_resume(struct device *dev) titsc_writel(ts_dev, REG_IRQWAKEUP, 0x00); titsc_writel(ts_dev, REG_IRQCLR, IRQENB_HW_PEN); + pm_relax(ts_dev->mfd_tscadc->dev); } titsc_step_config(ts_dev); titsc_writel(ts_dev, REG_FIFO0THR, diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c index c6cf90868503..d61570d64ee7 100644 --- a/drivers/input/touchscreen/usbtouchscreen.c +++ b/drivers/input/touchscreen/usbtouchscreen.c @@ -440,6 +440,8 @@ static int panjit_read_data(struct usbtouch_usb *dev, unsigned char *pkt) #define MTOUCHUSB_RESET 7 #define MTOUCHUSB_REQ_CTRLLR_ID 10 +#define MTOUCHUSB_REQ_CTRLLR_ID_LEN 16 + static int mtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) { if (hwcalib_xy) { @@ -454,11 +456,93 @@ static int mtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) return 1; } +struct mtouch_priv { + u8 fw_rev_major; + u8 fw_rev_minor; +}; + +static ssize_t mtouch_firmware_rev_show(struct device *dev, + struct device_attribute *attr, char *output) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usbtouch_usb *usbtouch = usb_get_intfdata(intf); + struct mtouch_priv *priv = usbtouch->priv; + + return scnprintf(output, PAGE_SIZE, "%1x.%1x\n", + priv->fw_rev_major, priv->fw_rev_minor); +} +static DEVICE_ATTR(firmware_rev, 0444, mtouch_firmware_rev_show, NULL); + +static struct attribute *mtouch_attrs[] = { + &dev_attr_firmware_rev.attr, + NULL +}; + +static const struct attribute_group mtouch_attr_group = { + .attrs = mtouch_attrs, +}; + +static int mtouch_get_fw_revision(struct usbtouch_usb *usbtouch) +{ + struct usb_device *udev = interface_to_usbdev(usbtouch->interface); + struct mtouch_priv *priv = usbtouch->priv; + u8 *buf; + int ret; + + buf = kzalloc(MTOUCHUSB_REQ_CTRLLR_ID_LEN, GFP_NOIO); + if (!buf) + return -ENOMEM; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + MTOUCHUSB_REQ_CTRLLR_ID, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, buf, MTOUCHUSB_REQ_CTRLLR_ID_LEN, + USB_CTRL_SET_TIMEOUT); + if (ret != MTOUCHUSB_REQ_CTRLLR_ID_LEN) { + dev_warn(&usbtouch->interface->dev, + "Failed to read FW rev: %d\n", ret); + ret = ret < 0 ? ret : -EIO; + goto free; + } + + priv->fw_rev_major = buf[3]; + priv->fw_rev_minor = buf[4]; + + ret = 0; + +free: + kfree(buf); + return ret; +} + +static int mtouch_alloc(struct usbtouch_usb *usbtouch) +{ + int ret; + + usbtouch->priv = kmalloc(sizeof(struct mtouch_priv), GFP_KERNEL); + if (!usbtouch->priv) + return -ENOMEM; + + ret = sysfs_create_group(&usbtouch->interface->dev.kobj, + &mtouch_attr_group); + if (ret) { + kfree(usbtouch->priv); + usbtouch->priv = NULL; + return ret; + } + + return 0; +} + static int mtouch_init(struct usbtouch_usb *usbtouch) { int ret, i; struct usb_device *udev = interface_to_usbdev(usbtouch->interface); + ret = mtouch_get_fw_revision(usbtouch); + if (ret) + return ret; + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), MTOUCHUSB_RESET, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, @@ -492,6 +576,14 @@ static int mtouch_init(struct usbtouch_usb *usbtouch) return 0; } + +static void mtouch_exit(struct usbtouch_usb *usbtouch) +{ + struct mtouch_priv *priv = usbtouch->priv; + + sysfs_remove_group(&usbtouch->interface->dev.kobj, &mtouch_attr_group); + kfree(priv); +} #endif @@ -1119,7 +1211,9 @@ static struct usbtouch_device_info usbtouch_dev_info[] = { .max_yc = 0x4000, .rept_size = 11, .read_data = mtouch_read_data, + .alloc = mtouch_alloc, .init = mtouch_init, + .exit = mtouch_exit, }, #endif |