#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/error.h" #define DPRINT if(0)print #define THREEBUT 0 /* !=0, enable 3-button emulation (see below) */ /* * DynaPro touch panel and Maxim MAX192 A/D converter on * York Electronics Centre's BRD/98/024 (Version A) interface, * accessed via mpc8xx SPI interface (see spi.c). * * The highest level of the driver is derived from the ARM/UCB touch panel driver, * simplified because of the differences between the panels. */ /* * Values determined by interface board */ enum { /* MAX192 control words */ MeasureX = (1<<7)|(0<<6)|(1<<3)|(1<<2)|3, /* start, channel 0, unipolar, single-ended, external clock */ MeasureY = (1<<7)|(1<<6)|(1<<3)|(1<<2)|3, /* start, channel 1, unipolar, single-ended, external clock */ /* port B bits */ ADselect = IBIT(16), /* chip select to MAX192, active low */ /* port C bits */ Xenable = 1<<2, /* PC13: TOUCH_XEN, active low */ Yenable = 1<<3, /* PC12: TOUCH_YEN, active low */ Touched = 1<<10, /* PC5: contact detect, active low */ /* interrupt control via port C */ TouchIRQ= 2, /* parallel i/o - PC5 */ TouchEnable= 1<pbdat &= ~ADselect; iopunlock(); nr = spioutin(tbuf, sizeof(tbuf), rbuf); io = ioplock(); io->pbdat |= ADselect; iopunlock(); if(nr != 3) return -1; return ((rbuf[1]<<8)|rbuf[2])>>5; } /* * keep reading the a/d until the value stabilises */ static int dejitter(int enable, int cw) { int i, diff, prev, v; IMM *io; io = ioplock(); io->pcdat &= ~enable; /* active low */ iopunlock(); i = 0; v = getcoord(cw); do{ prev = v; v = getcoord(cw); diff = v - prev; if(diff < 0) diff = -diff; }while(diff >= MaxDelta && ++i <= Nconverge); io = ioplock(); io->pcdat |= enable; iopunlock(); return v; } static void adcreset(void) { IMM *io; /* select port pins */ io = ioplock(); io->pcdir &= ~(Xenable|Yenable); /* ensure set to input before changing state */ io->pcpar &= ~(Xenable|Yenable); io->pcdat |= Xenable|Yenable; io->pcdir |= Xenable; /* change enable bits to output one at a time to avoid both being low at once (could damage panel) */ io->pcdat |= Xenable; /* ensure it's high after making it an output */ io->pcdir |= Yenable; io->pcdat |= Yenable; /* ensure it's high after making it an output */ io->pcso &= ~(Xenable|Yenable); io->pbdat |= ADselect; io->pbpar &= ~ADselect; io->pbdir |= ADselect; iopunlock(); } /* * high-level touch panel interface */ /* to and from fixed point */ #define FX(n) ((n)<<16) #define XF(v) ((v)>>16) typedef struct Touch Touch; struct Touch { Lock; Rendez r; int m[2][3]; /* transformation matrix */ int rate; int down; int raw_count; int valid_count; int wake_time; int sleep_time; }; static Touch touch = { {0}, .r {0}, .m {{FX(1), 0, 0},{0, FX(1), 0}}, /* default is 1:1 */ .rate 20, /* milliseconds */ }; /* * panel-touched state and interrupt */ static int touching(void) { eieio(); return (m->iomem->pcdat & Touched) == 0; } static int ispendown(void*) { return touch.down || touching(); } static void touchintr(Ureg*, void*) { if((m->iomem->pcdat & Touched) == 0){ m->iomem->cimr &= ~TouchEnable; /* mask interrupts when reading pen */ touch.down = 1; wakeup(&touch.r); } } static void touchenable(void) { IMM *io; io = ioplock(); io->cimr |= TouchEnable; iopunlock(); } /* * touchctl commands: * X a b c - set X transformation * Y d e f - set Y transformation * s - set sample delay in millisec per sample * r - set read delay in microsec * R - set log2 of number of readings to average */ enum{ Qdir, Qtouchctl, Qtouchstat, Qtouch, }; Dirtab touchdir[]={ ".", {Qdir, 0, QTDIR}, 0, 0555, "touchctl", {Qtouchctl, 0}, 0, 0666, "touchstat", {Qtouchstat, 0}, 0, 0444, "touch", {Qtouch, 0}, 0, 0444, }; static int ptmap(int *m, int x, int y) { return XF(m[0]*x + m[1]*y + m[2]); } /* * read a point from the touch panel; * returns true iff the point is valid, otherwise x, y aren't changed */ static int touchreadxy(int *fx, int *fy) { int rx, ry; if(touching()){ rx = dejitter(Xenable, MeasureX); ry = dejitter(Yenable, MeasureY); microdelay(40); if(rx >=0 && ry >= 0){ if(0) print("touch %d %d\n", rx, ry); *fx = ptmap(touch.m[0], rx, ry); *fy = ptmap(touch.m[1], rx, ry); touch.raw_count++; return 1; } } return 0; } #define timer_start() 0 /* could use TBL if necessary */ #define tmr2us(n) 0 static void touchproc(void*) { int b, i, x, y; ulong t1, t2; t1 = timer_start(); b = 1; for(;;) { //setpri(PriHi); do{ touch.down = 0; touch.wake_time += (t2 = timer_start())-t1; touchenable(); sleep(&touch.r, ispendown, nil); touch.sleep_time += (t1 = timer_start())-t2; }while(!touchreadxy(&x, &y)); /* 640x480-specific 3-button emulation hack: */ if(THREEBUT){ if(y > 481) { b = ((639-x) >> 7); continue; } else if(y < -2) { b = (x >> 7)+3; continue; } } DPRINT("#%d %d", x, y); mousetrack(b, x, y, 0); setpri(PriNormal); while(touching()) { for(i=0; i<3; i++) if(touchreadxy(&x, &y)) { DPRINT("*%d %d", x, y); mousetrack(b, x, y, 0); break; } touch.wake_time += (t2 = timer_start())-t1; tsleep(&touch.r, return0, nil, touch.rate); touch.sleep_time += (t1 = timer_start())-t2; } mousetrack(0, x, y, 0); b = 1; /* go back to just button one for next press */ } } static void touchreset(void) { IMM *io; spireset(); adcreset(); intrenable(VectorCPIC+TouchIRQ, touchintr, &touch, BUSUNKNOWN, "touch"); /* set i/o pin to interrupt when panel touched */ io = ioplock(); io->pcdat &= ~Touched; io->pcpar &= ~Touched; io->pcdir &= ~Touched; io->pcso &= ~Touched; io->pcint |= Touched; /* high-to-low trigger */ io->cimr &= ~TouchEnable; /* touchproc will enable when ready */ iopunlock(); } static void touchinit(void) { static int done; if(!done){ done = 1; kproc( "touchscreen", touchproc, nil, 0); } } static Chan* touchattach(char* spec) { return devattach('T', spec); } static Walkqid* touchwalk(Chan* c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, touchdir, nelem(touchdir), devgen); } static int touchstat(Chan* c, uchar* dp, int n) { return devstat(c, dp, n, touchdir, nelem(touchdir), devgen); } static Chan* touchopen(Chan* c, int omode) { omode = openmode(omode); switch((ulong)c->qid.path){ case Qtouchctl: case Qtouchstat: if(!iseve()) error(Eperm); break; } return devopen(c, omode, touchdir, nelem(touchdir), devgen); } static void touchclose(Chan*) { } static long touchread(Chan* c, void* buf, long n, vlong offset) { char *tmp; int x, y; if(c->qid.type & QTDIR) return devdirread(c, buf, n, touchdir, nelem(touchdir), devgen); tmp = malloc(READSTR); if(waserror()){ free(tmp); nexterror(); } switch((ulong)c->qid.path){ case Qtouch: if(!touchreadxy(&x, &y)) x = y = -1; snprint(tmp, READSTR, "%d %d", x, y); break; case Qtouchctl: snprint(tmp, READSTR, "s%d\nr%d\nR%d\nX %d %d %d\nY %d %d %d\n", touch.rate, 0, 1, touch.m[0][0], touch.m[0][1], touch.m[0][2], touch.m[1][0], touch.m[1][1], touch.m[1][2]); break; case Qtouchstat: snprint(tmp, READSTR, "%d %d\n%d %d\n", touch.raw_count, touch.valid_count, tmr2us(touch.sleep_time), tmr2us(touch.wake_time)); touch.raw_count = 0; touch.valid_count = 0; touch.sleep_time = 0; touch.wake_time = 0; break; default: error(Ebadarg); return 0; } n = readstr(offset, buf, n, tmp); poperror(); free(tmp); return n; } static void dotouchwrite(Chan *c, char *buf) { char *field[8]; int nf, cmd, pn, m[3], n; nf = getfields(buf, field, nelem(field), 1, " \t\n"); if(nf <= 0) return; switch((ulong)c->qid.path){ case Qtouchctl: cmd = *(field[0])++; pn = *field[0] == 0; switch(cmd) { case 's': n = strtol(field[pn], 0, 0); if(n <= 0) error(Ebadarg); touch.rate = n; break; case 'r': /* touch read delay */ break; case 'X': case 'Y': if(nf < pn+2) error(Ebadarg); m[0] = strtol(field[pn], 0, 0); m[1] = strtol(field[pn+1], 0, 0); m[2] = strtol(field[pn+2], 0, 0); memmove(touch.m[cmd=='Y'], m, sizeof(touch.m[0])); break; case 'c': case 'C': case 'v': case 't': case 'e': /* not used */ /* break; */ default: error(Ebadarg); } break; default: error(Ebadarg); return; } } static long touchwrite(Chan* c, void* vp, long n, vlong) { char buf[64]; char *cp, *a; int n0 = n; int bn; a = vp; while(n) { bn = (cp = memchr(a, '\n', n))!=nil ? cp-a+1 : n; n -= bn; bn = bn > sizeof(buf)-1 ? sizeof(buf)-1 : bn; memmove(buf, a, bn); buf[bn] = '\0'; a = cp; dotouchwrite(c, buf); } return n0-n; } Dev touchdevtab = { 'T', "touch", touchreset, touchinit, devshutdown, touchattach, touchwalk, touchstat, touchopen, devcreate, touchclose, touchread, devbread, touchwrite, devbwrite, devremove, devwstat, };