18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * ICS MK712 touchscreen controller driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 1999-2002 Transmeta Corporation
68c2ecf20Sopenharmony_ci * Copyright (c) 2005 Rick Koch <n1gp@hotmail.com>
78c2ecf20Sopenharmony_ci * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci/*
128c2ecf20Sopenharmony_ci * This driver supports the ICS MicroClock MK712 TouchScreen controller,
138c2ecf20Sopenharmony_ci * found in Gateway AOL Connected Touchpad computers.
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * Documentation for ICS MK712 can be found at:
168c2ecf20Sopenharmony_ci *	https://www.idt.com/general-parts/mk712-touch-screen-controller
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci/*
208c2ecf20Sopenharmony_ci * 1999-12-18: original version, Daniel Quinlan
218c2ecf20Sopenharmony_ci * 1999-12-19: added anti-jitter code, report pen-up events, fixed mk712_poll
228c2ecf20Sopenharmony_ci *             to use queue_empty, Nathan Laredo
238c2ecf20Sopenharmony_ci * 1999-12-20: improved random point rejection, Nathan Laredo
248c2ecf20Sopenharmony_ci * 2000-01-05: checked in new anti-jitter code, changed mouse protocol, fixed
258c2ecf20Sopenharmony_ci *             queue code, added module options, other fixes, Daniel Quinlan
268c2ecf20Sopenharmony_ci * 2002-03-15: Clean up for kernel merge <alan@redhat.com>
278c2ecf20Sopenharmony_ci *             Fixed multi open race, fixed memory checks, fixed resource
288c2ecf20Sopenharmony_ci *             allocation, fixed close/powerdown bug, switched to new init
298c2ecf20Sopenharmony_ci * 2005-01-18: Ported to 2.6 from 2.4.28, Rick Koch
308c2ecf20Sopenharmony_ci * 2005-02-05: Rewritten for the input layer, Vojtech Pavlik
318c2ecf20Sopenharmony_ci *
328c2ecf20Sopenharmony_ci */
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#include <linux/module.h>
358c2ecf20Sopenharmony_ci#include <linux/kernel.h>
368c2ecf20Sopenharmony_ci#include <linux/init.h>
378c2ecf20Sopenharmony_ci#include <linux/errno.h>
388c2ecf20Sopenharmony_ci#include <linux/delay.h>
398c2ecf20Sopenharmony_ci#include <linux/ioport.h>
408c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
418c2ecf20Sopenharmony_ci#include <linux/input.h>
428c2ecf20Sopenharmony_ci#include <asm/io.h>
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ciMODULE_AUTHOR("Daniel Quinlan <quinlan@pathname.com>, Vojtech Pavlik <vojtech@suse.cz>");
458c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ICS MicroClock MK712 TouchScreen driver");
468c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic unsigned int mk712_io = 0x260;	/* Also 0x200, 0x208, 0x300 */
498c2ecf20Sopenharmony_cimodule_param_hw_named(io, mk712_io, uint, ioport, 0);
508c2ecf20Sopenharmony_ciMODULE_PARM_DESC(io, "I/O base address of MK712 touchscreen controller");
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic unsigned int mk712_irq = 10;	/* Also 12, 14, 15 */
538c2ecf20Sopenharmony_cimodule_param_hw_named(irq, mk712_irq, uint, irq, 0);
548c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ of MK712 touchscreen controller");
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci/* eight 8-bit registers */
578c2ecf20Sopenharmony_ci#define MK712_STATUS		0
588c2ecf20Sopenharmony_ci#define MK712_X			2
598c2ecf20Sopenharmony_ci#define MK712_Y			4
608c2ecf20Sopenharmony_ci#define MK712_CONTROL		6
618c2ecf20Sopenharmony_ci#define MK712_RATE		7
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/* status */
648c2ecf20Sopenharmony_ci#define	MK712_STATUS_TOUCH			0x10
658c2ecf20Sopenharmony_ci#define	MK712_CONVERSION_COMPLETE		0x80
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci/* control */
688c2ecf20Sopenharmony_ci#define MK712_ENABLE_INT			0x01
698c2ecf20Sopenharmony_ci#define MK712_INT_ON_CONVERSION_COMPLETE	0x02
708c2ecf20Sopenharmony_ci#define MK712_INT_ON_CHANGE_IN_TOUCH_STATUS	0x04
718c2ecf20Sopenharmony_ci#define MK712_ENABLE_PERIODIC_CONVERSIONS	0x10
728c2ecf20Sopenharmony_ci#define MK712_READ_ONE_POINT			0x20
738c2ecf20Sopenharmony_ci#define MK712_POWERUP				0x40
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic struct input_dev *mk712_dev;
768c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(mk712_lock);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic irqreturn_t mk712_interrupt(int irq, void *dev_id)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	unsigned char status;
818c2ecf20Sopenharmony_ci	static int debounce = 1;
828c2ecf20Sopenharmony_ci	static unsigned short last_x;
838c2ecf20Sopenharmony_ci	static unsigned short last_y;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	spin_lock(&mk712_lock);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	status = inb(mk712_io + MK712_STATUS);
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	if (~status & MK712_CONVERSION_COMPLETE) {
908c2ecf20Sopenharmony_ci		debounce = 1;
918c2ecf20Sopenharmony_ci		goto end;
928c2ecf20Sopenharmony_ci	}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	if (~status & MK712_STATUS_TOUCH) {
958c2ecf20Sopenharmony_ci		debounce = 1;
968c2ecf20Sopenharmony_ci		input_report_key(mk712_dev, BTN_TOUCH, 0);
978c2ecf20Sopenharmony_ci		goto end;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	if (debounce) {
1018c2ecf20Sopenharmony_ci		debounce = 0;
1028c2ecf20Sopenharmony_ci		goto end;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	input_report_key(mk712_dev, BTN_TOUCH, 1);
1068c2ecf20Sopenharmony_ci	input_report_abs(mk712_dev, ABS_X, last_x);
1078c2ecf20Sopenharmony_ci	input_report_abs(mk712_dev, ABS_Y, last_y);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci end:
1108c2ecf20Sopenharmony_ci	last_x = inw(mk712_io + MK712_X) & 0x0fff;
1118c2ecf20Sopenharmony_ci	last_y = inw(mk712_io + MK712_Y) & 0x0fff;
1128c2ecf20Sopenharmony_ci	input_sync(mk712_dev);
1138c2ecf20Sopenharmony_ci	spin_unlock(&mk712_lock);
1148c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1158c2ecf20Sopenharmony_ci}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_cistatic int mk712_open(struct input_dev *dev)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	unsigned long flags;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mk712_lock, flags);
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	outb(0, mk712_io + MK712_CONTROL); /* Reset */
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	outb(MK712_ENABLE_INT | MK712_INT_ON_CONVERSION_COMPLETE |
1268c2ecf20Sopenharmony_ci		MK712_INT_ON_CHANGE_IN_TOUCH_STATUS |
1278c2ecf20Sopenharmony_ci		MK712_ENABLE_PERIODIC_CONVERSIONS |
1288c2ecf20Sopenharmony_ci		MK712_POWERUP, mk712_io + MK712_CONTROL);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	outb(10, mk712_io + MK712_RATE); /* 187 points per second */
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mk712_lock, flags);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	return 0;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic void mk712_close(struct input_dev *dev)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	unsigned long flags;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mk712_lock, flags);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	outb(0, mk712_io + MK712_CONTROL);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mk712_lock, flags);
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic int __init mk712_init(void)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	int err;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	if (!request_region(mk712_io, 8, "mk712")) {
1538c2ecf20Sopenharmony_ci		printk(KERN_WARNING "mk712: unable to get IO region\n");
1548c2ecf20Sopenharmony_ci		return -ENODEV;
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	outb(0, mk712_io + MK712_CONTROL);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	if ((inw(mk712_io + MK712_X) & 0xf000) ||	/* Sanity check */
1608c2ecf20Sopenharmony_ci	    (inw(mk712_io + MK712_Y) & 0xf000) ||
1618c2ecf20Sopenharmony_ci	    (inw(mk712_io + MK712_STATUS) & 0xf333)) {
1628c2ecf20Sopenharmony_ci		printk(KERN_WARNING "mk712: device not present\n");
1638c2ecf20Sopenharmony_ci		err = -ENODEV;
1648c2ecf20Sopenharmony_ci		goto fail1;
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	mk712_dev = input_allocate_device();
1688c2ecf20Sopenharmony_ci	if (!mk712_dev) {
1698c2ecf20Sopenharmony_ci		printk(KERN_ERR "mk712: not enough memory\n");
1708c2ecf20Sopenharmony_ci		err = -ENOMEM;
1718c2ecf20Sopenharmony_ci		goto fail1;
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	mk712_dev->name = "ICS MicroClock MK712 TouchScreen";
1758c2ecf20Sopenharmony_ci	mk712_dev->phys = "isa0260/input0";
1768c2ecf20Sopenharmony_ci	mk712_dev->id.bustype = BUS_ISA;
1778c2ecf20Sopenharmony_ci	mk712_dev->id.vendor  = 0x0005;
1788c2ecf20Sopenharmony_ci	mk712_dev->id.product = 0x0001;
1798c2ecf20Sopenharmony_ci	mk712_dev->id.version = 0x0100;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	mk712_dev->open    = mk712_open;
1828c2ecf20Sopenharmony_ci	mk712_dev->close   = mk712_close;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	mk712_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
1858c2ecf20Sopenharmony_ci	mk712_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
1868c2ecf20Sopenharmony_ci	input_set_abs_params(mk712_dev, ABS_X, 0, 0xfff, 88, 0);
1878c2ecf20Sopenharmony_ci	input_set_abs_params(mk712_dev, ABS_Y, 0, 0xfff, 88, 0);
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (request_irq(mk712_irq, mk712_interrupt, 0, "mk712", mk712_dev)) {
1908c2ecf20Sopenharmony_ci		printk(KERN_WARNING "mk712: unable to get IRQ\n");
1918c2ecf20Sopenharmony_ci		err = -EBUSY;
1928c2ecf20Sopenharmony_ci		goto fail1;
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	err = input_register_device(mk712_dev);
1968c2ecf20Sopenharmony_ci	if (err)
1978c2ecf20Sopenharmony_ci		goto fail2;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	return 0;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci fail2:	free_irq(mk712_irq, mk712_dev);
2028c2ecf20Sopenharmony_ci fail1:	input_free_device(mk712_dev);
2038c2ecf20Sopenharmony_ci	release_region(mk712_io, 8);
2048c2ecf20Sopenharmony_ci	return err;
2058c2ecf20Sopenharmony_ci}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_cistatic void __exit mk712_exit(void)
2088c2ecf20Sopenharmony_ci{
2098c2ecf20Sopenharmony_ci	input_unregister_device(mk712_dev);
2108c2ecf20Sopenharmony_ci	free_irq(mk712_irq, mk712_dev);
2118c2ecf20Sopenharmony_ci	release_region(mk712_io, 8);
2128c2ecf20Sopenharmony_ci}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_cimodule_init(mk712_init);
2158c2ecf20Sopenharmony_cimodule_exit(mk712_exit);
216