|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * TSC-40 serial touchscreen driver. It should be compatible with | 
|  | * TSC-10 and 25. | 
|  | * | 
|  | * Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de> | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/input.h> | 
|  | #include <linux/serio.h> | 
|  |  | 
|  | #define PACKET_LENGTH  5 | 
|  | struct tsc_ser { | 
|  | struct input_dev *dev; | 
|  | struct serio *serio; | 
|  | u32 idx; | 
|  | unsigned char data[PACKET_LENGTH]; | 
|  | char phys[32]; | 
|  | }; | 
|  |  | 
|  | static void tsc_process_data(struct tsc_ser *ptsc) | 
|  | { | 
|  | struct input_dev *dev = ptsc->dev; | 
|  | u8 *data = ptsc->data; | 
|  | u32 x; | 
|  | u32 y; | 
|  |  | 
|  | x = ((data[1] & 0x03) << 8) | data[2]; | 
|  | y = ((data[3] & 0x03) << 8) | data[4]; | 
|  |  | 
|  | input_report_abs(dev, ABS_X, x); | 
|  | input_report_abs(dev, ABS_Y, y); | 
|  | input_report_key(dev, BTN_TOUCH, 1); | 
|  |  | 
|  | input_sync(dev); | 
|  | } | 
|  |  | 
|  | static irqreturn_t tsc_interrupt(struct serio *serio, | 
|  | unsigned char data, unsigned int flags) | 
|  | { | 
|  | struct tsc_ser *ptsc = serio_get_drvdata(serio); | 
|  | struct input_dev *dev = ptsc->dev; | 
|  |  | 
|  | ptsc->data[ptsc->idx] = data; | 
|  | switch (ptsc->idx++) { | 
|  | case 0: | 
|  | if (unlikely((data & 0x3e) != 0x10)) { | 
|  | dev_dbg(&serio->dev, | 
|  | "unsynchronized packet start (0x%02x)\n", data); | 
|  | ptsc->idx = 0; | 
|  | } else if (!(data & 0x01)) { | 
|  | input_report_key(dev, BTN_TOUCH, 0); | 
|  | input_sync(dev); | 
|  | ptsc->idx = 0; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case 1: | 
|  | case 3: | 
|  | if (unlikely(data & 0xfc)) { | 
|  | dev_dbg(&serio->dev, | 
|  | "unsynchronized data 0x%02x at offset %d\n", | 
|  | data, ptsc->idx - 1); | 
|  | ptsc->idx = 0; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case 4: | 
|  | tsc_process_data(ptsc); | 
|  | ptsc->idx = 0; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static int tsc_connect(struct serio *serio, struct serio_driver *drv) | 
|  | { | 
|  | struct tsc_ser *ptsc; | 
|  | struct input_dev *input_dev; | 
|  | int error; | 
|  |  | 
|  | ptsc = kzalloc(sizeof(*ptsc), GFP_KERNEL); | 
|  | input_dev = input_allocate_device(); | 
|  | if (!ptsc || !input_dev) { | 
|  | error = -ENOMEM; | 
|  | goto fail1; | 
|  | } | 
|  |  | 
|  | ptsc->serio = serio; | 
|  | ptsc->dev = input_dev; | 
|  | snprintf(ptsc->phys, sizeof(ptsc->phys), "%s/input0", serio->phys); | 
|  |  | 
|  | input_dev->name = "TSC-10/25/40 Serial TouchScreen"; | 
|  | input_dev->phys = ptsc->phys; | 
|  | input_dev->id.bustype = BUS_RS232; | 
|  | input_dev->id.vendor = SERIO_TSC40; | 
|  | input_dev->id.product = 40; | 
|  | input_dev->id.version = 0x0001; | 
|  | input_dev->dev.parent = &serio->dev; | 
|  |  | 
|  | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | 
|  | __set_bit(BTN_TOUCH, input_dev->keybit); | 
|  | input_set_abs_params(ptsc->dev, ABS_X, 0, 0x3ff, 0, 0); | 
|  | input_set_abs_params(ptsc->dev, ABS_Y, 0, 0x3ff, 0, 0); | 
|  |  | 
|  | serio_set_drvdata(serio, ptsc); | 
|  |  | 
|  | error = serio_open(serio, drv); | 
|  | if (error) | 
|  | goto fail2; | 
|  |  | 
|  | error = input_register_device(ptsc->dev); | 
|  | if (error) | 
|  | goto fail3; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | fail3: | 
|  | serio_close(serio); | 
|  | fail2: | 
|  | serio_set_drvdata(serio, NULL); | 
|  | fail1: | 
|  | input_free_device(input_dev); | 
|  | kfree(ptsc); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static void tsc_disconnect(struct serio *serio) | 
|  | { | 
|  | struct tsc_ser *ptsc = serio_get_drvdata(serio); | 
|  |  | 
|  | serio_close(serio); | 
|  |  | 
|  | input_unregister_device(ptsc->dev); | 
|  | kfree(ptsc); | 
|  |  | 
|  | serio_set_drvdata(serio, NULL); | 
|  | } | 
|  |  | 
|  | static const struct serio_device_id tsc_serio_ids[] = { | 
|  | { | 
|  | .type   = SERIO_RS232, | 
|  | .proto  = SERIO_TSC40, | 
|  | .id     = SERIO_ANY, | 
|  | .extra  = SERIO_ANY, | 
|  | }, | 
|  | { 0 } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(serio, tsc_serio_ids); | 
|  |  | 
|  | #define DRIVER_DESC    "TSC-10/25/40 serial touchscreen driver" | 
|  |  | 
|  | static struct serio_driver tsc_drv = { | 
|  | .driver	= { | 
|  | .name   = "tsc40", | 
|  | }, | 
|  | .description    = DRIVER_DESC, | 
|  | .id_table	= tsc_serio_ids, | 
|  | .interrupt      = tsc_interrupt, | 
|  | .connect	= tsc_connect, | 
|  | .disconnect     = tsc_disconnect, | 
|  | }; | 
|  |  | 
|  | module_serio_driver(tsc_drv); | 
|  |  | 
|  | MODULE_AUTHOR("Sebastian Andrzej Siewior <bigeasy@linutronix.de>"); | 
|  | MODULE_DESCRIPTION(DRIVER_DESC); | 
|  | MODULE_LICENSE("GPL v2"); |