1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * ICS MK712 touchscreen controller driver
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright (c) 1999-2002 Transmeta Corporation
6*4882a593Smuzhiyun * Copyright (c) 2005 Rick Koch <n1gp@hotmail.com>
7*4882a593Smuzhiyun * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
8*4882a593Smuzhiyun */
9*4882a593Smuzhiyun
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun /*
12*4882a593Smuzhiyun * This driver supports the ICS MicroClock MK712 TouchScreen controller,
13*4882a593Smuzhiyun * found in Gateway AOL Connected Touchpad computers.
14*4882a593Smuzhiyun *
15*4882a593Smuzhiyun * Documentation for ICS MK712 can be found at:
16*4882a593Smuzhiyun * https://www.idt.com/general-parts/mk712-touch-screen-controller
17*4882a593Smuzhiyun */
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun /*
20*4882a593Smuzhiyun * 1999-12-18: original version, Daniel Quinlan
21*4882a593Smuzhiyun * 1999-12-19: added anti-jitter code, report pen-up events, fixed mk712_poll
22*4882a593Smuzhiyun * to use queue_empty, Nathan Laredo
23*4882a593Smuzhiyun * 1999-12-20: improved random point rejection, Nathan Laredo
24*4882a593Smuzhiyun * 2000-01-05: checked in new anti-jitter code, changed mouse protocol, fixed
25*4882a593Smuzhiyun * queue code, added module options, other fixes, Daniel Quinlan
26*4882a593Smuzhiyun * 2002-03-15: Clean up for kernel merge <alan@redhat.com>
27*4882a593Smuzhiyun * Fixed multi open race, fixed memory checks, fixed resource
28*4882a593Smuzhiyun * allocation, fixed close/powerdown bug, switched to new init
29*4882a593Smuzhiyun * 2005-01-18: Ported to 2.6 from 2.4.28, Rick Koch
30*4882a593Smuzhiyun * 2005-02-05: Rewritten for the input layer, Vojtech Pavlik
31*4882a593Smuzhiyun *
32*4882a593Smuzhiyun */
33*4882a593Smuzhiyun
34*4882a593Smuzhiyun #include <linux/module.h>
35*4882a593Smuzhiyun #include <linux/kernel.h>
36*4882a593Smuzhiyun #include <linux/init.h>
37*4882a593Smuzhiyun #include <linux/errno.h>
38*4882a593Smuzhiyun #include <linux/delay.h>
39*4882a593Smuzhiyun #include <linux/ioport.h>
40*4882a593Smuzhiyun #include <linux/interrupt.h>
41*4882a593Smuzhiyun #include <linux/input.h>
42*4882a593Smuzhiyun #include <asm/io.h>
43*4882a593Smuzhiyun
44*4882a593Smuzhiyun MODULE_AUTHOR("Daniel Quinlan <quinlan@pathname.com>, Vojtech Pavlik <vojtech@suse.cz>");
45*4882a593Smuzhiyun MODULE_DESCRIPTION("ICS MicroClock MK712 TouchScreen driver");
46*4882a593Smuzhiyun MODULE_LICENSE("GPL");
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun static unsigned int mk712_io = 0x260; /* Also 0x200, 0x208, 0x300 */
49*4882a593Smuzhiyun module_param_hw_named(io, mk712_io, uint, ioport, 0);
50*4882a593Smuzhiyun MODULE_PARM_DESC(io, "I/O base address of MK712 touchscreen controller");
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun static unsigned int mk712_irq = 10; /* Also 12, 14, 15 */
53*4882a593Smuzhiyun module_param_hw_named(irq, mk712_irq, uint, irq, 0);
54*4882a593Smuzhiyun MODULE_PARM_DESC(irq, "IRQ of MK712 touchscreen controller");
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun /* eight 8-bit registers */
57*4882a593Smuzhiyun #define MK712_STATUS 0
58*4882a593Smuzhiyun #define MK712_X 2
59*4882a593Smuzhiyun #define MK712_Y 4
60*4882a593Smuzhiyun #define MK712_CONTROL 6
61*4882a593Smuzhiyun #define MK712_RATE 7
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun /* status */
64*4882a593Smuzhiyun #define MK712_STATUS_TOUCH 0x10
65*4882a593Smuzhiyun #define MK712_CONVERSION_COMPLETE 0x80
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun /* control */
68*4882a593Smuzhiyun #define MK712_ENABLE_INT 0x01
69*4882a593Smuzhiyun #define MK712_INT_ON_CONVERSION_COMPLETE 0x02
70*4882a593Smuzhiyun #define MK712_INT_ON_CHANGE_IN_TOUCH_STATUS 0x04
71*4882a593Smuzhiyun #define MK712_ENABLE_PERIODIC_CONVERSIONS 0x10
72*4882a593Smuzhiyun #define MK712_READ_ONE_POINT 0x20
73*4882a593Smuzhiyun #define MK712_POWERUP 0x40
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun static struct input_dev *mk712_dev;
76*4882a593Smuzhiyun static DEFINE_SPINLOCK(mk712_lock);
77*4882a593Smuzhiyun
mk712_interrupt(int irq,void * dev_id)78*4882a593Smuzhiyun static irqreturn_t mk712_interrupt(int irq, void *dev_id)
79*4882a593Smuzhiyun {
80*4882a593Smuzhiyun unsigned char status;
81*4882a593Smuzhiyun static int debounce = 1;
82*4882a593Smuzhiyun static unsigned short last_x;
83*4882a593Smuzhiyun static unsigned short last_y;
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun spin_lock(&mk712_lock);
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun status = inb(mk712_io + MK712_STATUS);
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun if (~status & MK712_CONVERSION_COMPLETE) {
90*4882a593Smuzhiyun debounce = 1;
91*4882a593Smuzhiyun goto end;
92*4882a593Smuzhiyun }
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun if (~status & MK712_STATUS_TOUCH) {
95*4882a593Smuzhiyun debounce = 1;
96*4882a593Smuzhiyun input_report_key(mk712_dev, BTN_TOUCH, 0);
97*4882a593Smuzhiyun goto end;
98*4882a593Smuzhiyun }
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun if (debounce) {
101*4882a593Smuzhiyun debounce = 0;
102*4882a593Smuzhiyun goto end;
103*4882a593Smuzhiyun }
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun input_report_key(mk712_dev, BTN_TOUCH, 1);
106*4882a593Smuzhiyun input_report_abs(mk712_dev, ABS_X, last_x);
107*4882a593Smuzhiyun input_report_abs(mk712_dev, ABS_Y, last_y);
108*4882a593Smuzhiyun
109*4882a593Smuzhiyun end:
110*4882a593Smuzhiyun last_x = inw(mk712_io + MK712_X) & 0x0fff;
111*4882a593Smuzhiyun last_y = inw(mk712_io + MK712_Y) & 0x0fff;
112*4882a593Smuzhiyun input_sync(mk712_dev);
113*4882a593Smuzhiyun spin_unlock(&mk712_lock);
114*4882a593Smuzhiyun return IRQ_HANDLED;
115*4882a593Smuzhiyun }
116*4882a593Smuzhiyun
mk712_open(struct input_dev * dev)117*4882a593Smuzhiyun static int mk712_open(struct input_dev *dev)
118*4882a593Smuzhiyun {
119*4882a593Smuzhiyun unsigned long flags;
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun spin_lock_irqsave(&mk712_lock, flags);
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun outb(0, mk712_io + MK712_CONTROL); /* Reset */
124*4882a593Smuzhiyun
125*4882a593Smuzhiyun outb(MK712_ENABLE_INT | MK712_INT_ON_CONVERSION_COMPLETE |
126*4882a593Smuzhiyun MK712_INT_ON_CHANGE_IN_TOUCH_STATUS |
127*4882a593Smuzhiyun MK712_ENABLE_PERIODIC_CONVERSIONS |
128*4882a593Smuzhiyun MK712_POWERUP, mk712_io + MK712_CONTROL);
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun outb(10, mk712_io + MK712_RATE); /* 187 points per second */
131*4882a593Smuzhiyun
132*4882a593Smuzhiyun spin_unlock_irqrestore(&mk712_lock, flags);
133*4882a593Smuzhiyun
134*4882a593Smuzhiyun return 0;
135*4882a593Smuzhiyun }
136*4882a593Smuzhiyun
mk712_close(struct input_dev * dev)137*4882a593Smuzhiyun static void mk712_close(struct input_dev *dev)
138*4882a593Smuzhiyun {
139*4882a593Smuzhiyun unsigned long flags;
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun spin_lock_irqsave(&mk712_lock, flags);
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun outb(0, mk712_io + MK712_CONTROL);
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun spin_unlock_irqrestore(&mk712_lock, flags);
146*4882a593Smuzhiyun }
147*4882a593Smuzhiyun
mk712_init(void)148*4882a593Smuzhiyun static int __init mk712_init(void)
149*4882a593Smuzhiyun {
150*4882a593Smuzhiyun int err;
151*4882a593Smuzhiyun
152*4882a593Smuzhiyun if (!request_region(mk712_io, 8, "mk712")) {
153*4882a593Smuzhiyun printk(KERN_WARNING "mk712: unable to get IO region\n");
154*4882a593Smuzhiyun return -ENODEV;
155*4882a593Smuzhiyun }
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun outb(0, mk712_io + MK712_CONTROL);
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun if ((inw(mk712_io + MK712_X) & 0xf000) || /* Sanity check */
160*4882a593Smuzhiyun (inw(mk712_io + MK712_Y) & 0xf000) ||
161*4882a593Smuzhiyun (inw(mk712_io + MK712_STATUS) & 0xf333)) {
162*4882a593Smuzhiyun printk(KERN_WARNING "mk712: device not present\n");
163*4882a593Smuzhiyun err = -ENODEV;
164*4882a593Smuzhiyun goto fail1;
165*4882a593Smuzhiyun }
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun mk712_dev = input_allocate_device();
168*4882a593Smuzhiyun if (!mk712_dev) {
169*4882a593Smuzhiyun printk(KERN_ERR "mk712: not enough memory\n");
170*4882a593Smuzhiyun err = -ENOMEM;
171*4882a593Smuzhiyun goto fail1;
172*4882a593Smuzhiyun }
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun mk712_dev->name = "ICS MicroClock MK712 TouchScreen";
175*4882a593Smuzhiyun mk712_dev->phys = "isa0260/input0";
176*4882a593Smuzhiyun mk712_dev->id.bustype = BUS_ISA;
177*4882a593Smuzhiyun mk712_dev->id.vendor = 0x0005;
178*4882a593Smuzhiyun mk712_dev->id.product = 0x0001;
179*4882a593Smuzhiyun mk712_dev->id.version = 0x0100;
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun mk712_dev->open = mk712_open;
182*4882a593Smuzhiyun mk712_dev->close = mk712_close;
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun mk712_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
185*4882a593Smuzhiyun mk712_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
186*4882a593Smuzhiyun input_set_abs_params(mk712_dev, ABS_X, 0, 0xfff, 88, 0);
187*4882a593Smuzhiyun input_set_abs_params(mk712_dev, ABS_Y, 0, 0xfff, 88, 0);
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun if (request_irq(mk712_irq, mk712_interrupt, 0, "mk712", mk712_dev)) {
190*4882a593Smuzhiyun printk(KERN_WARNING "mk712: unable to get IRQ\n");
191*4882a593Smuzhiyun err = -EBUSY;
192*4882a593Smuzhiyun goto fail1;
193*4882a593Smuzhiyun }
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun err = input_register_device(mk712_dev);
196*4882a593Smuzhiyun if (err)
197*4882a593Smuzhiyun goto fail2;
198*4882a593Smuzhiyun
199*4882a593Smuzhiyun return 0;
200*4882a593Smuzhiyun
201*4882a593Smuzhiyun fail2: free_irq(mk712_irq, mk712_dev);
202*4882a593Smuzhiyun fail1: input_free_device(mk712_dev);
203*4882a593Smuzhiyun release_region(mk712_io, 8);
204*4882a593Smuzhiyun return err;
205*4882a593Smuzhiyun }
206*4882a593Smuzhiyun
mk712_exit(void)207*4882a593Smuzhiyun static void __exit mk712_exit(void)
208*4882a593Smuzhiyun {
209*4882a593Smuzhiyun input_unregister_device(mk712_dev);
210*4882a593Smuzhiyun free_irq(mk712_irq, mk712_dev);
211*4882a593Smuzhiyun release_region(mk712_io, 8);
212*4882a593Smuzhiyun }
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun module_init(mk712_init);
215*4882a593Smuzhiyun module_exit(mk712_exit);
216