315 lines · 6.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Penmount serial touchscreen driver * * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> * Copyright (c) 2011 John Sung <penmount.touch@gmail.com> * * Based on ELO driver (drivers/input/touchscreen/elo.c) * Copyright (c) 2004 Vojtech Pavlik */  #include <linux/errno.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/input.h>#include <linux/input/mt.h>#include <linux/serio.h> #define DRIVER_DESC	"PenMount serial touchscreen driver" MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>");MODULE_AUTHOR("John Sung <penmount.touch@gmail.com>");MODULE_DESCRIPTION(DRIVER_DESC);MODULE_LICENSE("GPL"); /* * Definitions & global arrays. */ #define	PM_MAX_LENGTH	6#define	PM_MAX_MTSLOT	16#define	PM_3000_MTSLOT	2#define	PM_6250_MTSLOT	12 /* * Multi-touch slot */ struct mt_slot {	unsigned short x, y;	bool active; /* is the touch valid? */}; /* * Per-touchscreen data. */ struct pm {	struct input_dev *dev;	struct serio *serio;	int idx;	unsigned char data[PM_MAX_LENGTH];	char phys[32];	unsigned char packetsize;	unsigned char maxcontacts;	struct mt_slot slots[PM_MAX_MTSLOT];	void (*parse_packet)(struct pm *);}; /* * pm_mtevent() sends mt events and also emulates pointer movement */ static void pm_mtevent(struct pm *pm, struct input_dev *input){	int i; 	for (i = 0; i < pm->maxcontacts; ++i) {		input_mt_slot(input, i);		input_mt_report_slot_state(input, MT_TOOL_FINGER,				pm->slots[i].active);		if (pm->slots[i].active) {			input_event(input, EV_ABS, ABS_MT_POSITION_X, pm->slots[i].x);			input_event(input, EV_ABS, ABS_MT_POSITION_Y, pm->slots[i].y);		}	} 	input_mt_report_pointer_emulation(input, true);	input_sync(input);} /* * pm_checkpacket() checks if data packet is valid */ static bool pm_checkpacket(unsigned char *packet){	int total = 0;	int i; 	for (i = 0; i < 5; i++)		total += packet[i]; 	return packet[5] == (unsigned char)~(total & 0xff);} static void pm_parse_9000(struct pm *pm){	struct input_dev *dev = pm->dev; 	if ((pm->data[0] & 0x80) && pm->packetsize == ++pm->idx) {		input_report_abs(dev, ABS_X, pm->data[1] * 128 + pm->data[2]);		input_report_abs(dev, ABS_Y, pm->data[3] * 128 + pm->data[4]);		input_report_key(dev, BTN_TOUCH, !!(pm->data[0] & 0x40));		input_sync(dev);		pm->idx = 0;	}} static void pm_parse_6000(struct pm *pm){	struct input_dev *dev = pm->dev; 	if ((pm->data[0] & 0xbf) == 0x30 && pm->packetsize == ++pm->idx) {		if (pm_checkpacket(pm->data)) {			input_report_abs(dev, ABS_X,					pm->data[2] * 256 + pm->data[1]);			input_report_abs(dev, ABS_Y,					pm->data[4] * 256 + pm->data[3]);			input_report_key(dev, BTN_TOUCH, pm->data[0] & 0x40);			input_sync(dev);		}		pm->idx = 0;	}} static void pm_parse_3000(struct pm *pm){	struct input_dev *dev = pm->dev; 	if ((pm->data[0] & 0xce) == 0x40 && pm->packetsize == ++pm->idx) {		if (pm_checkpacket(pm->data)) {			int slotnum = pm->data[0] & 0x0f;			pm->slots[slotnum].active = pm->data[0] & 0x30;			pm->slots[slotnum].x = pm->data[2] * 256 + pm->data[1];			pm->slots[slotnum].y = pm->data[4] * 256 + pm->data[3];			pm_mtevent(pm, dev);		}		pm->idx = 0;	}} static void pm_parse_6250(struct pm *pm){	struct input_dev *dev = pm->dev; 	if ((pm->data[0] & 0xb0) == 0x30 && pm->packetsize == ++pm->idx) {		if (pm_checkpacket(pm->data)) {			int slotnum = pm->data[0] & 0x0f;			pm->slots[slotnum].active = pm->data[0] & 0x40;			pm->slots[slotnum].x = pm->data[2] * 256 + pm->data[1];			pm->slots[slotnum].y = pm->data[4] * 256 + pm->data[3];			pm_mtevent(pm, dev);		}		pm->idx = 0;	}} static irqreturn_t pm_interrupt(struct serio *serio,		unsigned char data, unsigned int flags){	struct pm *pm = serio_get_drvdata(serio); 	pm->data[pm->idx] = data; 	pm->parse_packet(pm); 	return IRQ_HANDLED;} /* * pm_disconnect() is the opposite of pm_connect() */ static void pm_disconnect(struct serio *serio){	struct pm *pm = serio_get_drvdata(serio); 	serio_close(serio); 	input_unregister_device(pm->dev);	kfree(pm); 	serio_set_drvdata(serio, NULL);} /* * pm_connect() is the routine that is called when someone adds a * new serio device that supports PenMount protocol and registers it as * an input device. */ static int pm_connect(struct serio *serio, struct serio_driver *drv){	struct pm *pm;	struct input_dev *input_dev;	int max_x, max_y;	int err; 	pm = kzalloc_obj(*pm);	input_dev = input_allocate_device();	if (!pm || !input_dev) {		err = -ENOMEM;		goto fail1;	} 	pm->serio = serio;	pm->dev = input_dev;	scnprintf(pm->phys, sizeof(pm->phys), "%s/input0", serio->phys);	pm->maxcontacts = 1; 	input_dev->name = "PenMount Serial TouchScreen";	input_dev->phys = pm->phys;	input_dev->id.bustype = BUS_RS232;	input_dev->id.vendor = SERIO_PENMOUNT;	input_dev->id.product = 0;	input_dev->id.version = 0x0100;	input_dev->dev.parent = &serio->dev; 	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); 	switch (serio->id.id) {	default:	case 0:		pm->packetsize = 5;		pm->parse_packet = pm_parse_9000;		input_dev->id.product = 0x9000;		max_x = max_y = 0x3ff;		break; 	case 1:		pm->packetsize = 6;		pm->parse_packet = pm_parse_6000;		input_dev->id.product = 0x6000;		max_x = max_y = 0x3ff;		break; 	case 2:		pm->packetsize = 6;		pm->parse_packet = pm_parse_3000;		input_dev->id.product = 0x3000;		max_x = max_y = 0x7ff;		pm->maxcontacts = PM_3000_MTSLOT;		break; 	case 3:		pm->packetsize = 6;		pm->parse_packet = pm_parse_6250;		input_dev->id.product = 0x6250;		max_x = max_y = 0x3ff;		pm->maxcontacts = PM_6250_MTSLOT;		break;	} 	input_set_abs_params(pm->dev, ABS_X, 0, max_x, 0, 0);	input_set_abs_params(pm->dev, ABS_Y, 0, max_y, 0, 0); 	if (pm->maxcontacts > 1) {		input_mt_init_slots(pm->dev, pm->maxcontacts, 0);		input_set_abs_params(pm->dev,				     ABS_MT_POSITION_X, 0, max_x, 0, 0);		input_set_abs_params(pm->dev,				     ABS_MT_POSITION_Y, 0, max_y, 0, 0);	} 	serio_set_drvdata(serio, pm); 	err = serio_open(serio, drv);	if (err)		goto fail2; 	err = input_register_device(pm->dev);	if (err)		goto fail3; 	return 0;  fail3:	serio_close(serio); fail2:	serio_set_drvdata(serio, NULL); fail1:	input_free_device(input_dev);	kfree(pm);	return err;} /* * The serio driver structure. */ static const struct serio_device_id pm_serio_ids[] = {	{		.type	= SERIO_RS232,		.proto	= SERIO_PENMOUNT,		.id	= SERIO_ANY,		.extra	= SERIO_ANY,	},	{ 0 }}; MODULE_DEVICE_TABLE(serio, pm_serio_ids); static struct serio_driver pm_drv = {	.driver		= {		.name	= "serio-penmount",	},	.description	= DRIVER_DESC,	.id_table	= pm_serio_ids,	.interrupt	= pm_interrupt,	.connect	= pm_connect,	.disconnect	= pm_disconnect,}; module_serio_driver(pm_drv);