summaryrefslogtreecommitdiff
path: root/os/ipaq1110/devipaq.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/ipaq1110/devipaq.c')
-rw-r--r--os/ipaq1110/devipaq.c762
1 files changed, 762 insertions, 0 deletions
diff --git a/os/ipaq1110/devipaq.c b/os/ipaq1110/devipaq.c
new file mode 100644
index 00000000..cc432cb5
--- /dev/null
+++ b/os/ipaq1110/devipaq.c
@@ -0,0 +1,762 @@
+/*
+ * iPAQ H3650 touch screen and other devices
+ *
+ * Inferno driver derived from sketchy documentation and
+ * information gleaned from linux/char/h3650_ts.c
+ * by Charles Flynn.
+ *
+ * Copyright © 2000,2001 Vita Nuova Holdings Limited. All rights reserved.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "keyboard.h"
+#include <kernel.h>
+
+#include <draw.h>
+#include <memdraw.h>
+#include "screen.h"
+
+#define DEBUG 0
+
+/*
+ * packet format
+ *
+ * SOF (0x02)
+ * (id<<4) | len byte length
+ * data[len] bytes
+ * chk checksum mod 256 excluding SOF
+ */
+
+enum {
+ Csof = 0x02,
+ Ceof = 0x03,
+ Hdrlen = 3,
+
+ /* opcodes */
+
+ Oversion = 0,
+ Okeys = 2,
+ Otouch = 3,
+ Ordeeprom = 4,
+ Owreeprom = 5,
+ Othermal = 6,
+ Oled = 8,
+ Obattery = 9,
+ Ospiread = 11,
+ Ospiwrite = 12,
+ Obacklight = 13,
+ Oextstatus = 0xA1,
+ };
+
+enum {
+ Powerbit = 0, /* GPIO bit for power on/off key */
+};
+
+enum{
+ Qdir,
+ Qctl,
+ Qtouchctl,
+ Qbattery,
+ Qversion,
+};
+
+static
+Dirtab ipaqtab[]={
+ ".", {Qdir, 0, QTDIR}, 0, 0555,
+ "ipaqctl", {Qctl}, 0, 0600,
+ "battery", {Qbattery}, 0, 0444,
+ "version", {Qversion}, 0, 0444,
+ "touchctl", {Qtouchctl}, 0, 0644,
+};
+
+static struct {
+ QLock;
+ Chan* c;
+
+ Lock rl; /* protect cmd, reply */
+ int cmd;
+ Block* reply;
+ Rendez r;
+} atmel;
+
+/* to and from fixed point */
+#define FX(a,b) (((a)<<16)/(b))
+#define XF(v) ((v)>>16)
+
+static struct {
+ Lock;
+ int rate;
+ int m[2][3]; /* transformation matrix */
+ Point avg;
+ Point diff;
+ Point pts[4];
+ int n; /* number of points in pts */
+ int p; /* current index in pts */
+ int down;
+ int nout;
+} touch = {
+ {0},
+ .m {{-FX(1,3), 0, FX(346,1)},{0, -FX(1,4), FX(256, 1)}},
+};
+
+/*
+ * map rocker positions to same codes as plan 9
+ */
+static Rune rockermap[2][4] ={
+ {Right, Down, Up, Left}, /* landscape */
+ {Up, Right, Left, Down}, /* portrait */
+};
+
+static Rendez powerevent;
+
+static void cmdack(int, void*, int);
+static int cmdio(int, void*, int, void*, int);
+static void ipaqreadproc(void*);
+static void powerwaitproc(void*);
+static Block* rdevent(Block**);
+static long touchctl(char*, long);
+static void touched(Block*, int);
+static int wrcmd(int, void*, int, void*, int);
+static char* acstatus(int);
+static char* batstatus(int);
+static void powerintr(Ureg*, void*);
+
+static void
+ipaqreset(void)
+{
+ intrenable(Powerbit, powerintr, nil, BusGPIOfalling, "power off");
+}
+
+static void
+ipaqinit(void)
+{
+ kproc("powerwait", powerwaitproc, nil, 0);
+}
+
+static Chan*
+ipaqattach(char* spec)
+{
+ int fd;
+
+ qlock(&atmel);
+ if(waserror()){
+ qunlock(&atmel);
+ nexterror();
+ }
+ if(atmel.c == nil){
+ fd = kopen("#t/eia1ctl", ORDWR);
+ if(fd < 0)
+ error(up->env->errstr);
+ kwrite(fd, "b115200", 7); /* it's already pn, l8 */
+ kclose(fd);
+ fd = kopen("#t/eia1", ORDWR);
+ if(fd < 0)
+ error(up->env->errstr);
+ atmel.c = fdtochan(up->env->fgrp, fd, ORDWR, 0, 1);
+ kclose(fd);
+ atmel.cmd = -1;
+ kproc("ipaqread", ipaqreadproc, nil, 0);
+ }
+ poperror();
+ qunlock(&atmel);
+ return devattach('T', spec);
+}
+
+static Walkqid*
+ipaqwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, ipaqtab, nelem(ipaqtab), devgen);
+}
+
+static int
+ipaqstat(Chan* c, uchar *db, int n)
+{
+ return devstat(c, db, n, ipaqtab, nelem(ipaqtab), devgen);
+}
+
+static Chan*
+ipaqopen(Chan* c, int omode)
+{
+ return devopen(c, omode, ipaqtab, nelem(ipaqtab), devgen);
+}
+
+static void
+ipaqclose(Chan*)
+{
+}
+
+static long
+ipaqread(Chan* c, void* a, long n, vlong offset)
+{
+ char *tmp, buf[64];
+ uchar reply[12];
+ int v, p, l;
+
+ switch((ulong)c->qid.path){
+ case Qdir:
+ return devdirread(c, a, n, ipaqtab, nelem(ipaqtab), devgen);
+ case Qtouchctl:
+ tmp = malloc(READSTR);
+ if(waserror()){
+ free(tmp);
+ nexterror();
+ }
+ snprint(tmp, READSTR, "s%d\nr%d\nR%d\nX %d %d %d\nY %d %d %d\n",
+ 1000, 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]);
+ n = readstr(offset, a, n, tmp);
+ poperror();
+ free(tmp);
+ break;
+ case Qbattery:
+ cmdio(Obattery, reply, 0, reply, sizeof(reply));
+ tmp = malloc(READSTR);
+ if(waserror()){
+ free(tmp);
+ nexterror();
+ }
+ v = (reply[4]<<8)|reply[3];
+ p = 425*v/1000 - 298;
+ snprint(tmp, READSTR, "voltage: %d %dmV %d%% %d\nac: %s\nstatus: %d %s\nchem: %d\n",
+ v, 1000*v/228, p, 300*p/100, acstatus(reply[1]), reply[5], batstatus(reply[5]), reply[2]);
+ n = readstr(offset, a, n, tmp);
+ poperror();
+ free(tmp);
+ break;
+ case Qversion:
+ l = cmdio(Oversion, reply, 0, reply, sizeof(reply));
+ if(l > 4){
+ l--;
+ memmove(buf, reply+1, 4);
+ if(l > 8){
+ buf[4] = ' ';
+ memmove(buf+5, reply+5, 4); /* pack version */
+ sprint(buf+9, " %.2x\n", reply[9]); /* ``boot type'' */
+ }else{
+ buf[4] = '\n';
+ buf[5] = 0;
+ }
+ return readstr(offset, a, n, buf);
+ }
+ n=0;
+ break;
+ default:
+ n=0;
+ break;
+ }
+ return n;
+}
+
+static long
+ipaqwrite(Chan* c, void* a, long n, vlong)
+{
+ char cmd[64], op[32], *fields[6];
+ int nf;
+
+ switch((ulong)c->qid.path){
+ case Qctl:
+ if(n >= sizeof(cmd)-1)
+ n = sizeof(cmd)-1;
+ memmove(cmd, a, n);
+ cmd[n] = 0;
+ nf = getfields(cmd, fields, nelem(fields), 1, " \t\n");
+ if(nf <= 0)
+ error(Ebadarg);
+ if(nf >= 4 && strcmp(fields[0], "light") == 0){
+ op[0] = atoi(fields[1]); /* mode */
+ op[1] = atoi(fields[2]); /* power */
+ op[2] = atoi(fields[3]); /* brightness */
+ cmdack(Obacklight, op, 3);
+ }else if(nf >= 5 && strcmp(fields[0], "led") == 0){
+ op[0] = atoi(fields[1]);
+ op[1] = atoi(fields[2]);
+ op[2] = atoi(fields[3]);
+ op[3] = atoi(fields[4]);
+ cmdack(Oled, op, 4);
+ }else if(strcmp(fields[0], "suspend") == 0){
+ /* let the kproc do it */
+ wakeup(&powerevent);
+ }else
+ error(Ebadarg);
+ break;
+ case Qtouchctl:
+ return touchctl(a, n);
+ default:
+ error(Ebadusefd);
+ }
+ return n;
+}
+
+static void
+powerintr(Ureg*, void*)
+{
+ wakeup(&powerevent);
+}
+
+static void
+cmdack(int id, void *a, int n)
+{
+ uchar reply[16];
+
+ cmdio(id, a, n, reply, sizeof(reply));
+}
+
+static int
+cmdio(int id, void *a, int n, void *reply, int lim)
+{
+ qlock(&atmel);
+ if(waserror()){
+ qunlock(&atmel);
+ nexterror();
+ }
+ n = wrcmd(id, a, n, reply, lim);
+ poperror();
+ qunlock(&atmel);
+ return n;
+}
+
+static int
+havereply(void*)
+{
+ return atmel.reply != nil;
+}
+
+static int
+wrcmd(int id, void *a, int n, void *b, int lim)
+{
+ uchar buf[32];
+ int i, sum;
+ Block *e;
+
+ if(n >= 16)
+ error(Eio);
+ lock(&atmel.rl);
+ atmel.cmd = id;
+ unlock(&atmel.rl);
+ buf[0] = Csof;
+ buf[1] = (id<<4) | (n&0xF);
+ if(n)
+ memmove(buf+2, a, n);
+ sum = 0;
+ for(i=1; i<n+2; i++)
+ sum += buf[i];
+ buf[i++] = sum;
+ if(0){
+ iprint("msg=");
+ for(sum=0; sum<i; sum++)
+ iprint(" %2.2ux", buf[sum]);
+ iprint("\n");
+ }
+ if(kchanio(atmel.c, buf, i, OWRITE) != i)
+ error(Eio);
+ tsleep(&atmel.r, havereply, nil, 500);
+ lock(&atmel.rl);
+ e = atmel.reply;
+ atmel.reply = nil;
+ atmel.cmd = -1;
+ unlock(&atmel.rl);
+ if(e == nil){
+ print("ipaq: no reply\n");
+ error(Eio);
+ }
+ if(waserror()){
+ freeb(e);
+ nexterror();
+ }
+ if(e->rp[0] != id){
+ print("ipaq: rdreply: mismatched reply %d :: %d\n", id, e->rp[0]);
+ error(Eio);
+ }
+ n = BLEN(e);
+ if(n < lim)
+ lim = n;
+ memmove(b, e->rp, lim);
+ poperror();
+ freeb(e);
+ return lim;
+}
+
+static void
+ipaqreadproc(void*)
+{
+ Block *e, *b, *partial;
+ int c, mousemod;
+
+ while(waserror())
+ print("ipaqread: %r\n");
+ partial = nil;
+ mousemod = 0;
+ for(;;){
+ e = rdevent(&partial);
+ if(e == nil){
+ print("ipaqread: rdevent: %r\n");
+ continue;
+ }
+ switch(e->rp[0]){
+ case Otouch:
+ touched(e, mousemod);
+ freeb(e);
+ break;
+ case Okeys:
+ //print("key %2.2ux\n", e->rp[1]);
+ c = e->rp[1] & 0xF;
+ if(c >= 6 && c < 10){ /* rocker */
+ if((e->rp[1] & 0x80) == 0){
+ kbdrepeat(0);
+ kbdputc(kbdq, rockermap[conf.portrait&1][c-6]);
+ }else
+ kbdrepeat(0);
+ }else{
+ /* TO DO: change tkmouse and mousetrack to allow extra buttons */
+ if(--c == 0)
+ c = 5;
+ if(e->rp[1] & 0x80)
+ mousemod &= ~(1<<c);
+ else
+ mousemod |= 1<<c;
+ }
+ freeb(e);
+ break;
+ default:
+ lock(&atmel.rl);
+ if(atmel.cmd == e->rp[0]){
+ b = atmel.reply;
+ atmel.reply = e;
+ unlock(&atmel.rl);
+ wakeup(&atmel.r);
+ if(b != nil)
+ freeb(b);
+ }else{
+ unlock(&atmel.rl);
+ print("ipaqread: discard op %d\n", e->rp[0]);
+ freeb(e);
+ }
+ }
+ }
+}
+
+static Block *
+rdevent(Block **bp)
+{
+ Block *b, *e;
+ int s, c, len, csum;
+ enum {Ssof=16, Sid, Ssum};
+
+ s = Ssof;
+ csum = 0;
+ len = 0;
+ e = nil;
+ if(waserror()){
+ if(e != nil)
+ freeb(e);
+ nexterror();
+ }
+ for(;;){
+ b = *bp;
+ *bp = nil;
+ if(b == nil){
+ b = devtab[atmel.c->type]->bread(atmel.c, 128, 0);
+ if(b == nil)
+ error(Eio);
+ if(DEBUG)
+ iprint("r: %ld\n", BLEN(b));
+ }
+ while(b->rp < b->wp){
+ c = *b->rp++;
+ switch(s){
+ case Ssof:
+ if(c == Csof)
+ s = Sid;
+ else if(1)
+ iprint("!sof: %2.2ux %d\n", c, s);
+ break;
+ case Sid:
+ csum = c;
+ len = c & 0xF;
+ e = allocb(len+1);
+ if(e == nil)
+ error(Eio);
+ *e->wp++ = c>>4; /* id */
+ if(len)
+ s = 0;
+ else
+ s = Ssum;
+ break;
+ case Ssum:
+ csum &= 0xFF;
+ if(c != csum){
+ iprint("cksum: %2.2ux != %2.2ux\n", c, csum);
+ s = Ssof; /* try to resynchronise */
+ if(e != nil){
+ freeb(e);
+ e = nil;
+ }
+ break;
+ }
+ if(b->rp < b->wp)
+ *bp = b;
+ else
+ freeb(b);
+ if(DEBUG){
+ int i;
+ iprint("event: [%ld]", BLEN(e));
+ for(i=0; i<BLEN(e);i++)
+ iprint(" %2.2ux", e->rp[i]);
+ iprint("\n");
+ }
+ poperror();
+ return e;
+ default:
+ csum += c;
+ *e->wp++ = c;
+ if(++s >= len)
+ s = Ssum;
+ break;
+ }
+ }
+ freeb(b);
+ }
+ return 0; /* not reached */
+}
+
+static char *
+acstatus(int x)
+{
+ switch(x){
+ case 0: return "offline";
+ case 1: return "online";
+ case 2: return "backup";
+ }
+ return "unknown";
+}
+
+static char *
+batstatus(int x)
+{
+ if(x & 0x40)
+ return "charging"; /* not in linux but seems to be on mine */
+ switch(x){
+ case 0: return "ok";
+ case 1: return "high";
+ case 2: return "low";
+ case 4: return "critical";
+ case 8: return "charging";
+ case 0x80: return "none";
+ }
+ return "unknown";
+}
+
+static int
+ptmap(int *m, int x, int y)
+{
+ return XF(m[0]*x + m[1]*y + m[2]);
+}
+
+static void
+touched(Block *b, int buttons)
+{
+ int rx, ry, x, y, dx, dy, n;
+ Point op, *lp, cur;
+
+ if(BLEN(b) == 5){
+ /* id Xhi Xlo Yhi Ylo */
+ if(touch.down < 0){
+ touch.down = 0;
+ return;
+ }
+ rx = (b->rp[1]<<8)|b->rp[2];
+ ry = (b->rp[3]<<8)|b->rp[4];
+ if(conf.portrait){
+ dx = rx; rx = ry; ry = dx;
+ }
+ if(touch.down == 0){
+ touch.nout = 0;
+ touch.p = 1;
+ touch.n = 1;
+ touch.avg = Pt(rx, ry);
+ touch.pts[0] = touch.avg;
+ touch.down = 1;
+ return;
+ }
+ n = touch.p-1;
+ if(n < 0)
+ n = nelem(touch.pts)-1;
+ lp = &touch.pts[n]; /* last point */
+ if(touch.n > 0 && (rx-lp->x)*(ry-lp->y) > 50*50){ /* far out */
+ if(++touch.nout > 3){
+ touch.down = 0;
+ touch.n = 0;
+ }
+ return;
+ }
+ op = touch.pts[touch.p];
+ touch.pts[touch.p] = Pt(rx, ry);
+ touch.p = (touch.p+1) % nelem(touch.pts);
+ touch.avg.x += rx;
+ touch.avg.y += ry;
+ if(touch.n < nelem(touch.pts)){
+ touch.n++;
+ return;
+ }
+ touch.avg.x -= op.x;
+ touch.avg.y -= op.y;
+ cur = mousexy();
+ rx = touch.avg.x/touch.n;
+ ry = touch.avg.y/touch.n;
+ x = ptmap(touch.m[0], rx, ry);
+ dx = x-cur.x;
+ y = ptmap(touch.m[1], rx, ry);
+ dy = y-cur.y;
+ if(dx*dx + dy*dy <= 2){
+ dx = 0;
+ dy = 0;
+ }
+ if(buttons == 0)
+ buttons = 1<<0; /* by default, stylus down implies button 1 */
+ mousetrack(buttons&0x1f, dx, dy, 1); /* TO DO: allow more than 3 buttons */
+ /* TO DO: swcursupdate(oldx, oldy, x, y); */
+ touch.down = 1;
+ }else{
+ if(touch.down){
+ mousetrack(0, 0, 0, 1); /* stylus up */
+ touch.down = 0;
+ }else
+ touch.down = -1;
+ touch.n = 0;
+ touch.p = 0;
+ touch.avg.x = 0;
+ touch.avg.y = 0;
+ }
+}
+
+/*
+ * touchctl commands:
+ * X a b c - set X transformation
+ * Y d e f - set Y transformation
+ * s<delay> - set sample delay in millisec per sample
+ * r<delay> - set read delay in microsec
+ * R<l2nr> - set log2 of number of readings to average
+ */
+static long
+touchctl(char* a, long n)
+{
+ char buf[64];
+ char *cp;
+ int n0 = n;
+ int bn;
+ char *field[8];
+ int nf, cmd, pn, m[2][3];
+
+ while(n) {
+ bn = (cp = memchr(a, '\n', n))!=nil ? cp-a+1 : n;
+ n -= bn;
+ cp = a;
+ a += bn;
+ bn = bn > sizeof(buf)-1 ? sizeof(buf)-1 : bn;
+ memmove(buf, cp, bn);
+ buf[bn] = '\0';
+ nf = getfields(buf, field, nelem(field), 1, " \t\n");
+ if(nf <= 0)
+ continue;
+ if(strcmp(field[0], "calibrate") == 0){
+ if(nf == 1){
+ lock(&touch);
+ memset(touch.m, 0, sizeof(touch.m));
+ touch.m[0][0] = FX(1,1);
+ touch.m[1][1] = FX(1,1);
+ unlock(&touch);
+ }else if(nf >= 5){
+ memset(m, 0, sizeof(m));
+ m[0][0] = strtol(field[1], 0, 0);
+ m[1][1] = strtol(field[2], 0, 0);
+ m[0][2] = strtol(field[3], 0, 0);
+ m[1][2] = strtol(field[4], 0, 0);
+ if(nf > 5)
+ m[0][1] = strtol(field[5], 0, 0);
+ if(nf > 6)
+ m[1][0] = strtol(field[6], 0, 0);
+ lock(&touch);
+ memmove(touch.m, m, sizeof(touch.m[0]));
+ unlock(&touch);
+ }else
+ error(Ebadarg);
+ continue;
+ }
+ cmd = *field[0]++;
+ pn = *field[0] == 0;
+ switch(cmd) {
+ case 's':
+ pn = strtol(field[pn], 0, 0);
+ if(pn <= 0)
+ error(Ebadarg);
+ touch.rate = pn;
+ break;
+ case 'r':
+ /* touch read delay */
+ break;
+ case 'X':
+ case 'Y':
+ if(nf < pn+2)
+ error(Ebadarg);
+ m[0][0] = strtol(field[pn], 0, 0);
+ m[0][1] = strtol(field[pn+1], 0, 0);
+ m[0][2] = strtol(field[pn+2], 0, 0);
+ lock(&touch);
+ memmove(touch.m[cmd=='Y'], m[0], sizeof(touch.m[0]));
+ unlock(&touch);
+ break;
+ default:
+ error(Ebadarg);
+ }
+ }
+ return n0-n;
+}
+
+/*
+ * this might belong elsewhere
+ */
+static int
+powerwait(void*)
+{
+ return (GPIOREG->gplr & GPIO_PWR_ON_i) == 0;
+}
+
+static void
+powerwaitproc(void*)
+{
+ for(;;){
+ sleep(&powerevent, powerwait, nil);
+ do{
+ tsleep(&up->sleep, return0, nil, 50);
+ }while((GPIOREG->gplr & GPIO_PWR_ON_i) == 0);
+ powersuspend();
+ }
+}
+
+Dev ipaqdevtab = {
+ 'T',
+ "ipaq",
+
+ ipaqreset,
+ ipaqinit,
+ devshutdown,
+ ipaqattach,
+ ipaqwalk,
+ ipaqstat,
+ ipaqopen,
+ devcreate,
+ ipaqclose,
+ ipaqread,
+ devbread,
+ ipaqwrite,
+ devbwrite,
+ devremove,
+ devwstat,
+};