xref: /inferno-os/os/mpc/devtouch.c (revision 74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a)
1 #include "u.h"
2 #include "../port/lib.h"
3 #include "mem.h"
4 #include "dat.h"
5 #include "fns.h"
6 #include "io.h"
7 #include "../port/error.h"
8 
9 #define	DPRINT	if(0)print
10 #define	THREEBUT	0	/* !=0, enable 3-button emulation (see below) */
11 
12 /*
13  * DynaPro touch panel and Maxim MAX192 A/D converter on
14  * York Electronics Centre's BRD/98/024 (Version A) interface,
15  * accessed via mpc8xx SPI interface (see spi.c).
16  *
17  * The highest level of the driver is derived from the ARM/UCB touch panel driver,
18  * simplified because of the differences between the panels.
19  */
20 
21 /*
22  * Values determined by interface board
23  */
24 enum {
25 	/* MAX192 control words */
26 	MeasureX =	(1<<7)|(0<<6)|(1<<3)|(1<<2)|3,	/* start, channel 0, unipolar, single-ended, external clock */
27 	MeasureY =	(1<<7)|(1<<6)|(1<<3)|(1<<2)|3,	/* start, channel 1, unipolar, single-ended, external clock */
28 
29 	/* port B bits */
30 	ADselect = IBIT(16),	/* chip select to MAX192, active low */
31 
32 	/* port C bits */
33 	Xenable =	1<<2,	/* PC13: TOUCH_XEN, active low */
34 	Yenable =	1<<3,	/* PC12: TOUCH_YEN, active low */
35 	Touched = 1<<10,	/* PC5: contact detect, active low */
36 
37 	/* interrupt control via port C */
38 	TouchIRQ=	2,	/* parallel i/o - PC5 */
39 	TouchEnable=	1<<TouchIRQ,	/* mask for cimr */
40 
41 	/* other parameters */
42 	Nconverge =	10,	/* maximum iterations for convergence */
43 	MaxDelta =	2,	/* acceptable change in X/Y between iterations */
44 };
45 
46 /*
47  * ADC interface via SPI (see MAX192 data sheet)
48  *	select the ADC
49  *	send 8-bit control word and two zero bytes to clock the conversion
50  *	receive three data bytes
51  *	deselect the ADC and return the result
52  */
53 static int
getcoord(int cw)54 getcoord(int cw)
55 {
56 	uchar tbuf[3], rbuf[3];
57 	IMM *io;
58 	int nr;
59 
60 	tbuf[0] = cw;
61 	tbuf[1] = 0;
62 	tbuf[2] = 0;
63 	io = ioplock();
64 	io->pbdat &= ~ADselect;
65 	iopunlock();
66 	nr = spioutin(tbuf, sizeof(tbuf), rbuf);
67 	io = ioplock();
68 	io->pbdat |= ADselect;
69 	iopunlock();
70 	if(nr != 3)
71 		return -1;
72 	return ((rbuf[1]<<8)|rbuf[2])>>5;
73 }
74 
75 /*
76  * keep reading the a/d until the value stabilises
77  */
78 static int
dejitter(int enable,int cw)79 dejitter(int enable, int cw)
80 {
81 	int i, diff, prev, v;
82 	IMM *io;
83 
84 	io = ioplock();
85 	io->pcdat &= ~enable;	/* active low */
86 	iopunlock();
87 
88 	i = 0;
89 	v = getcoord(cw);
90 	do{
91 		prev = v;
92 		v = getcoord(cw);
93 		diff = v - prev;
94 		if(diff < 0)
95 			diff = -diff;
96 	}while(diff >= MaxDelta && ++i <= Nconverge);
97 
98 	io = ioplock();
99 	io->pcdat |= enable;
100 	iopunlock();
101 	return v;
102 }
103 
104 static void
adcreset(void)105 adcreset(void)
106 {
107 	IMM *io;
108 
109 	/* select port pins */
110 	io = ioplock();
111 	io->pcdir &= ~(Xenable|Yenable);	/* ensure set to input before changing state */
112 	io->pcpar &= ~(Xenable|Yenable);
113 	io->pcdat |= Xenable|Yenable;
114 	io->pcdir |= Xenable;	/* change enable bits to output one at a time to avoid both being low at once (could damage panel) */
115 	io->pcdat |= Xenable;	/* ensure it's high after making it an output */
116 	io->pcdir |= Yenable;
117 	io->pcdat |= Yenable;	/* ensure it's high after making it an output */
118 	io->pcso &= ~(Xenable|Yenable);
119 	io->pbdat |= ADselect;
120 	io->pbpar &= ~ADselect;
121 	io->pbdir |= ADselect;
122 	iopunlock();
123 }
124 
125 /*
126  * high-level touch panel interface
127  */
128 
129 /* to and from fixed point */
130 #define	FX(n)	((n)<<16)
131 #define	XF(v)		((v)>>16)
132 
133 typedef struct Touch Touch;
134 
135 struct Touch {
136 	Lock;
137 	Rendez	r;
138 	int	m[2][3];	/* transformation matrix */
139 	int	rate;
140 	int	down;
141 	int	raw_count;
142 	int	valid_count;
143 	int	wake_time;
144 	int	sleep_time;
145 };
146 
147 static Touch touch = {
148 	{0},
149 	.r {0},
150 	.m {{FX(1), 0, 0},{0, FX(1), 0}},	/* default is 1:1 */
151 	.rate 20,	/* milliseconds */
152 };
153 
154 /*
155  * panel-touched state and interrupt
156  */
157 
158 static int
touching(void)159 touching(void)
160 {
161 	eieio();
162 	return (m->iomem->pcdat & Touched) == 0;
163 }
164 
165 static int
ispendown(void *)166 ispendown(void*)
167 {
168 	return touch.down || touching();
169 }
170 
171 static void
touchintr(Ureg *,void *)172 touchintr(Ureg*, void*)
173 {
174 	if((m->iomem->pcdat & Touched) == 0){
175 		m->iomem->cimr &= ~TouchEnable;	/* mask interrupts when reading pen */
176 		touch.down = 1;
177 		wakeup(&touch.r);
178 	}
179 }
180 
181 static void
touchenable(void)182 touchenable(void)
183 {
184 	IMM *io;
185 
186 	io = ioplock();
187 	io->cimr |= TouchEnable;
188 	iopunlock();
189 }
190 
191 /*
192  * touchctl commands:
193  *	X a b c	- set X transformation
194  *	Y d e f	- set Y transformation
195  *	s<delay>		- set sample delay in millisec per sample
196  *	r<delay>		- set read delay in microsec
197  *	R<l2nr>			- set log2 of number of readings to average
198  */
199 
200 enum{
201 	Qdir,
202 	Qtouchctl,
203 	Qtouchstat,
204 	Qtouch,
205 };
206 
207 Dirtab touchdir[]={
208 	".",	{Qdir, 0, QTDIR}, 0, 0555,
209 	"touchctl",	{Qtouchctl, 0}, 	0,	0666,
210 	"touchstat",	{Qtouchstat, 0}, 	0,	0444,
211 	"touch",	{Qtouch, 0},	0,	0444,
212 };
213 
214 static int
ptmap(int * m,int x,int y)215 ptmap(int *m, int x, int y)
216 {
217 	return XF(m[0]*x + m[1]*y + m[2]);
218 }
219 
220 /*
221  * read a point from the touch panel;
222  * returns true iff the point is valid, otherwise x, y aren't changed
223  */
224 static int
touchreadxy(int * fx,int * fy)225 touchreadxy(int *fx, int *fy)
226 {
227 	int rx, ry;
228 
229 	if(touching()){
230 		rx = dejitter(Xenable, MeasureX);
231 		ry = dejitter(Yenable, MeasureY);
232 		microdelay(40);
233 		if(rx >=0 && ry >= 0){
234 			if(0)
235 				print("touch %d %d\n", rx, ry);
236 			*fx = ptmap(touch.m[0], rx, ry);
237 			*fy = ptmap(touch.m[1], rx, ry);
238 			touch.raw_count++;
239 			return 1;
240 		}
241 	}
242 	return 0;
243 }
244 
245 #define	timer_start()	0	/* could use TBL if necessary */
246 #define	tmr2us(n)	0
247 
248 static void
touchproc(void *)249 touchproc(void*)
250 {
251 	int b, i, x, y;
252 	ulong t1, t2;
253 
254 	t1 = timer_start();
255 	b = 1;
256 	for(;;) {
257 		//setpri(PriHi);
258 		do{
259 			touch.down = 0;
260 			touch.wake_time += (t2 = timer_start())-t1;
261 			touchenable();
262 			sleep(&touch.r, ispendown, nil);
263 			touch.sleep_time += (t1 = timer_start())-t2;
264 		}while(!touchreadxy(&x, &y));
265 
266 		/* 640x480-specific 3-button emulation hack: */
267 		if(THREEBUT){
268 			if(y > 481) {
269 				b = ((639-x) >> 7);
270 				continue;
271 			} else if(y < -2) {
272 				b = (x >> 7)+3;
273 				continue;
274 			}
275 		}
276 
277 		DPRINT("#%d %d", x, y);
278 		mousetrack(b, x, y, 0);
279 		setpri(PriNormal);
280 		while(touching()) {
281 			for(i=0; i<3; i++)
282 				if(touchreadxy(&x, &y)) {
283 					DPRINT("*%d %d", x, y);
284 					mousetrack(b, x, y, 0);
285 					break;
286 				}
287 			touch.wake_time += (t2 = timer_start())-t1;
288 			tsleep(&touch.r, return0, nil, touch.rate);
289 			touch.sleep_time += (t1 = timer_start())-t2;
290 		}
291 		mousetrack(0, x, y, 0);
292 		b = 1;	/* go back to just button one for next press */
293 	}
294 }
295 
296 static void
touchreset(void)297 touchreset(void)
298 {
299 	IMM *io;
300 
301 	spireset();
302 	adcreset();
303 	intrenable(VectorCPIC+TouchIRQ, touchintr, &touch, BUSUNKNOWN, "touch");
304 
305 	/* set i/o pin to interrupt when panel touched */
306 	io = ioplock();
307 	io->pcdat &= ~Touched;
308 	io->pcpar &= ~Touched;
309 	io->pcdir &= ~Touched;
310 	io->pcso &= ~Touched;
311 	io->pcint |= Touched;	/* high-to-low trigger */
312 	io->cimr &= ~TouchEnable;	/* touchproc will enable when ready */
313 	iopunlock();
314 }
315 
316 static void
touchinit(void)317 touchinit(void)
318 {
319 	static int done;
320 
321 	if(!done){
322 		done = 1;
323 		kproc( "touchscreen", touchproc, nil, 0);
324 	}
325 }
326 
327 static Chan*
touchattach(char * spec)328 touchattach(char* spec)
329 {
330 	return devattach('T', spec);
331 }
332 
333 static Walkqid*
touchwalk(Chan * c,Chan * nc,char ** name,int nname)334 touchwalk(Chan* c, Chan *nc, char **name, int nname)
335 {
336 	return devwalk(c, nc, name, nname, touchdir, nelem(touchdir), devgen);
337 }
338 
339 static int
touchstat(Chan * c,uchar * dp,int n)340 touchstat(Chan* c, uchar* dp, int n)
341 {
342 	return devstat(c, dp, n, touchdir, nelem(touchdir), devgen);
343 }
344 
345 static Chan*
touchopen(Chan * c,int omode)346 touchopen(Chan* c, int omode)
347 {
348 	omode = openmode(omode);
349 	switch((ulong)c->qid.path){
350 	case Qtouchctl:
351 	case Qtouchstat:
352 		if(!iseve())
353 			error(Eperm);
354 		break;
355 	}
356 	return devopen(c, omode, touchdir, nelem(touchdir), devgen);
357 }
358 
359 static void
touchclose(Chan *)360 touchclose(Chan*)
361 {
362 }
363 
364 static long
touchread(Chan * c,void * buf,long n,vlong offset)365 touchread(Chan* c, void* buf, long n, vlong offset)
366 {
367 	char *tmp;
368 	int x, y;
369 
370 	if(c->qid.type & QTDIR)
371 		return devdirread(c, buf, n, touchdir, nelem(touchdir), devgen);
372 
373 	tmp = malloc(READSTR);
374 	if(waserror()){
375 		free(tmp);
376 		nexterror();
377 	}
378 	switch((ulong)c->qid.path){
379 	case Qtouch:
380 		if(!touchreadxy(&x, &y))
381 			x = y = -1;
382 		snprint(tmp, READSTR, "%d %d", x, y);
383 		break;
384 	case Qtouchctl:
385 		snprint(tmp, READSTR, "s%d\nr%d\nR%d\nX %d %d %d\nY %d %d %d\n",
386 			touch.rate, 0, 1,
387 			touch.m[0][0], touch.m[0][1], touch.m[0][2],
388 			touch.m[1][0], touch.m[1][1], touch.m[1][2]);
389 		break;
390 	case Qtouchstat:
391 		snprint(tmp, READSTR, "%d %d\n%d %d\n",
392 			touch.raw_count, touch.valid_count, tmr2us(touch.sleep_time), tmr2us(touch.wake_time));
393 		touch.raw_count = 0;
394 		touch.valid_count = 0;
395 		touch.sleep_time = 0;
396 		touch.wake_time = 0;
397 		break;
398 	default:
399 		error(Ebadarg);
400 		return 0;
401 	}
402 	n = readstr(offset, buf, n, tmp);
403 	poperror();
404 	free(tmp);
405 	return n;
406 }
407 
408 static void
dotouchwrite(Chan * c,char * buf)409 dotouchwrite(Chan *c, char *buf)
410 {
411 	char *field[8];
412 	int nf, cmd, pn, m[3], n;
413 
414 	nf = getfields(buf, field, nelem(field), 1, " \t\n");
415 	if(nf <= 0)
416 		return;
417 	switch((ulong)c->qid.path){
418 	case Qtouchctl:
419 		cmd = *(field[0])++;
420 		pn = *field[0] == 0;
421 		switch(cmd) {
422 		case 's':
423 			n = strtol(field[pn], 0, 0);
424 			if(n <= 0)
425 				error(Ebadarg);
426 			touch.rate = n;
427 			break;
428 		case 'r':
429 			/* touch read delay */
430 			break;
431 		case 'X':
432 		case 'Y':
433 			if(nf < pn+2)
434 				error(Ebadarg);
435 			m[0] = strtol(field[pn], 0, 0);
436 			m[1] = strtol(field[pn+1], 0, 0);
437 			m[2] = strtol(field[pn+2], 0, 0);
438 			memmove(touch.m[cmd=='Y'], m, sizeof(touch.m[0]));
439 			break;
440 		case 'c':
441 		case 'C':
442 		case 'v':
443 		case 't':
444 		case 'e':
445 			/* not used */
446 			/* break; */
447 		default:
448 			error(Ebadarg);
449 		}
450 		break;
451 	default:
452 		error(Ebadarg);
453 		return;
454 	}
455 }
456 
457 static long
touchwrite(Chan * c,void * vp,long n,vlong)458 touchwrite(Chan* c, void* vp, long n, vlong)
459 {
460 	char buf[64];
461 	char *cp, *a;
462 	int n0 = n;
463 	int bn;
464 
465 	a = vp;
466 	while(n) {
467 		bn = (cp = memchr(a, '\n', n))!=nil ? cp-a+1 : n;
468 		n -= bn;
469 		bn = bn > sizeof(buf)-1 ? sizeof(buf)-1 : bn;
470 		memmove(buf, a, bn);
471 		buf[bn] = '\0';
472 		a = cp;
473 		dotouchwrite(c, buf);
474 	}
475 	return n0-n;
476 }
477 
478 Dev touchdevtab = {
479 	'T',
480 	"touch",
481 
482 	touchreset,
483 	touchinit,
484 	devshutdown,
485 	touchattach,
486 	touchwalk,
487 	touchstat,
488 	touchopen,
489 	devcreate,
490 	touchclose,
491 	touchread,
492 	devbread,
493 	touchwrite,
494 	devbwrite,
495 	devremove,
496 	devwstat,
497 };
498