]> git.karo-electronics.de Git - karo-tx-redboot.git/blob - packages/services/gfx/mw/v2_0/src/drivers/mou_tp.c
Initial revision
[karo-tx-redboot.git] / packages / services / gfx / mw / v2_0 / src / drivers / mou_tp.c
1 /*
2  * drivers/mou_tp.c
3  *
4  * Touch-panel driver
5  *
6  * Designed for for use with the Linux-VR project touch-panel kernel driver.
7  * This includes the VTech Helio.
8  * Also runs with Embedded Planet's PowerPC LinuxPlanet.
9  * Also runs with Yopy (untested, can use mou_yopy.c also)
10  *
11  * Requires /dev/tpanel kernel driver (char special 10,11)
12  *
13  * Copyright (C) 1999 Bradley D. LaRonde <brad@ltc.com>
14  * Portions Copyright (c) 2001 Kevin Oh <webmaster@prg-lib.net>
15  * Portions Copyright (c) 1999, 2000 Greg Haerr <greg@censoft.com>
16  * Portions Copyright (c) 1991 David I. Bell
17  *
18  * Permission is granted to use, distribute, or modify this source,
19  * provided that this copyright notice remains intact.
20  *
21  * This program is free software; you may redistribute it and/or modify
22  * it under the terms of the GNU General Public License as published by
23  * the Free Software Foundation; either version 2 of the License, or
24  * (at your option) any later version.
25  *
26  * This program is distributed in the hope that it will be useful,
27  * but WITHOUT ANY WARRANTY; without even the implied warranty of
28  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29  * GNU General Public License for more details.
30  *
31  * You should have received a copy of the GNU General Public License
32  * along with this program; if not, write to the Free Software
33  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
34  */
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <math.h>
42 #include <sys/ioctl.h>
43 #if !defined(TPHELIO) && !defined(YOPY)
44 #include <linux/tpanel.h>
45 #endif
46 #include "device.h"
47 #include "mou_tp.h"
48
49 #define EMBEDDEDPLANET  0       /* =1 for embeddedplanet ppc framebuffer*/
50 #if EMBEDDEDPLANET
51 #define DATACHANGELIMIT 50
52 #else
53 #define DATACHANGELIMIT 100
54 #endif
55
56 /*
57  * Enable absolute coordinate transformation.
58  * Normally this should be left at 1.
59  * To disable transformation, set it to 0 before calling MwMain().
60  * This is done by the pointer calibration utility since it's used
61  * to produce the pointer calibration data file.
62  */
63 int enable_pointing_coordinate_transform = 1;
64
65 static TRANSFORMATION_COEFFICIENTS tc;
66
67 /* file descriptor for touch panel */
68 static int pd_fd;
69
70 int GetPointerCalibrationData(void)
71 {
72         /*
73          * Read the calibration data from the calibration file.
74          * Calibration file format is seven coefficients separated by spaces.
75          */
76
77         /* Get pointer calibration data from this file */
78         const char cal_filename[] = "/etc/pointercal";
79
80         int items;
81
82         FILE* f = fopen(cal_filename, "r");
83         if ( f == NULL )
84         {
85                 EPRINTF("Error %d opening pointer calibration file %s.\n",
86                         errno, cal_filename);
87                 return -1;
88         }
89
90         items = fscanf(f, "%d %d %d %d %d %d %d",
91                 &tc.a, &tc.b, &tc.c, &tc.d, &tc.e, &tc.f, &tc.s);
92         if ( items != 7 )
93         {
94                 EPRINTF("Improperly formatted pointer calibration file %s.\n",
95                         cal_filename);
96                 return -1;
97         }
98
99 #if TEST
100                 EPRINTF("a=%d b=%d c=%d d=%d e=%d f=%d s=%d\n",
101                         tc.a, tc.b, tc.c, tc.d, tc.e, tc.f, tc.s);
102 #endif
103
104         return 0;
105 }
106
107 inline MWPOINT DeviceToScreen(MWPOINT p)
108 {
109         /*
110          * Transform device coordinates to screen coordinates.
111          * Take a point p in device coordinates and return the corresponding
112          * point in screen coodinates.
113          * This can scale, translate, rotate and/or skew, based on the
114          * coefficients calculated above based on the list of screen
115          * vs. device coordinates.
116          */
117
118         static MWPOINT prev;
119         /* set slop at 3/4 pixel */
120         const short slop = TRANSFORMATION_UNITS_PER_PIXEL * 3 / 4;
121         MWPOINT new, out;
122
123         /* transform */
124         new.x = (tc.a * p.x + tc.b * p.y + tc.c) / tc.s;
125         new.y = (tc.d * p.x + tc.e * p.y + tc.f) / tc.s;
126
127         /* hysteresis (thanks to John Siau) */
128         if ( abs(new.x - prev.x) >= slop )
129                 out.x = (new.x | 0x3) ^ 0x3;
130         else
131                 out.x = prev.x;
132
133         if ( abs(new.y - prev.y) >= slop )
134                 out.y = (new.y | 0x3) ^ 0x3;
135         else
136                 out.y = prev.y;
137
138         prev = out;
139
140         return out;
141 }
142
143 #ifndef YOPY
144 static int PD_Open(MOUSEDEVICE *pmd)
145 {
146         /*
147          * open up the touch-panel device.
148          * Return the fd if successful, or negative if unsuccessful.
149          */
150 #ifndef TPHELIO
151         struct scanparam s;
152         int settle_upper_limit;
153         int result;
154 #endif
155
156         pd_fd = open("/dev/tpanel", O_NONBLOCK);
157         if (pd_fd < 0) {
158                 EPRINTF("Error %d opening touch panel\n", errno);
159                 return -1;
160         }
161
162 #ifndef TPHELIO
163         /* set interval to 5000us (200Hz) */
164         s.interval = 5000;
165         /*
166          * Upper limit on settle time is approximately (scan_interval / 5) - 60
167          * (5 conversions and some fixed overhead)
168          * The opmtimal value is the lowest that doesn't cause significant
169          * distortion.
170          * 50% of upper limit works well on my Clio.  25% gets into distortion.
171          */
172         settle_upper_limit = (s.interval / 5) - 60;
173         s.settletime = settle_upper_limit * 50 / 100;
174         result = ioctl(pd_fd, TPSETSCANPARM, &s);
175         if ( result < 0 )
176                 EPRINTF("Error %d, result %d setting scan parameters.\n",
177                         result, errno);
178 #endif
179
180         if (enable_pointing_coordinate_transform)
181         { 
182                 if (GetPointerCalibrationData() < 0)
183                 {
184                         close(pd_fd);
185                         return -1;
186                 }
187         }
188
189         return pd_fd;
190 }
191 #else   /* ifndef YOPY */
192
193 static TRANSFORMATION_COEFFICIENTS default_tc = {
194         68339, 328, -3042464, -508, 91638, -4339545, 65536
195 };
196
197 static int PD_Open(MOUSEDEVICE *pmd)
198
199 {
200         /*
201          * open up the touch-panel device.
202          * Return the fd if successful, or negative if unsuccessful.
203          */
204
205         pd_fd = open("/dev/yopy-ts", O_NONBLOCK);
206         if (pd_fd < 0) {
207                 EPRINTF("Error %d opening touch panel\n", errno);
208                 return -1;
209         }
210
211         if (enable_pointing_coordinate_transform)
212         { 
213                 if (GetPointerCalibrationData() < 0)
214                 {
215                         //close(pd_fd);
216                         //return -1;
217                         memcpy( &tc, &default_tc, sizeof(TRANSFORMATION_COEFFICIENTS) );
218                 }
219         }
220
221 #if 0
222                 EPRINTF("a=%d b=%d c=%d d=%d e=%d f=%d s=%d\n",
223                         tc.a, tc.b, tc.c, tc.d, tc.e, tc.f, tc.s);
224 #endif
225
226         return pd_fd;
227 }
228 #endif
229
230 static void PD_Close(void)
231 {
232         /* Close the touch panel device. */
233         if (pd_fd > 0)
234                 close(pd_fd);
235         pd_fd = 0;
236 }
237
238 static int PD_GetButtonInfo(void)
239 {
240         /* get "mouse" buttons supported */
241         return MWBUTTON_L;
242 }
243
244 static void PD_GetDefaultAccel(int *pscale,int *pthresh)
245 {
246         /*
247          * Get default mouse acceleration settings
248          * This doesn't make sense for a touch panel.
249          * Just return something inconspicuous for now.
250          */
251         *pscale = 3;
252         *pthresh = 5;
253 }
254
255 #ifndef YOPY
256 static int PD_Read(MWCOORD *px, MWCOORD *py, MWCOORD *pz, int *pb)
257 {
258         /*
259          * Read the tpanel state and position.
260          * Returns the position data in x, y, and button data in b.
261          * Returns -1 on error.
262          * Returns 0 if no new data is available.
263          * Returns 1 if position data is relative (i.e. mice).
264          * Returns 2 if position data is absolute (i.e. touch panels).
265          * Returns 3 if position data is not available, but button data is.
266          * This routine does not block.
267          *
268          * Unlike a mouse, this driver returns absolute postions, not deltas.
269          */
270
271         /* If z is below this value, ignore the data. */
272         /* const int low_z_limit = 900; EVEREX*/
273 #ifndef TPHELIO
274         const int low_z_limit = 815;
275 #endif
276         /*
277          * I do some error masking by tossing out really wild data points.
278          * Lower data_change_limit value means pointer get's "left behind"
279          * more easily.  Higher value means less errors caught.
280          * The right setting of this value is just slightly higher than
281          * the number of units traversed per sample during a "quick" stroke.
282          */
283 #ifndef TPHELIO
284         const int data_change_limit = DATACHANGELIMIT;
285 #endif
286         static int have_last_data = 0;
287         static int last_data_x = 0;
288         static int last_data_y = 0;
289
290         /*
291          * Thanks to John Siau <jsiau@benchmarkmedia.com> for help with the
292          * noise filter.  I use an infinite impulse response low-pass filter
293          * on the data to filter out high-frequency noise.  Results look
294          * better than a finite impulse response filter.
295          * If I understand it right, the nice thing is that the noise now
296          * acts as a dither signal that effectively increases the resolution
297          * of the a/d converter by a few bits and drops the noise level by
298          * about 10db.
299          * Please don't quote me on those db numbers however. :-)
300          * The end result is that the pointer goes from very fuzzy to much
301          * more steady.  Hysteresis really calms it down in the end (elsewhere).
302          *
303          * iir_shift_bits effectively sets the number of samples used by
304          * the filter * (number of samples is 2^iir_shift_bits).
305          * Lower iir_width means less pointer lag, higher iir_width means
306          * steadier pointer.
307          */
308         const int iir_shift_bits = 3;
309         const int iir_sample_depth = (1 << iir_shift_bits);
310         static int iir_accum_x = 0;
311         static int iir_accum_y = 0;
312         static int iir_accum_z = 0;
313         static int iir_count = 0;
314         int data_x, data_y, data_z;
315
316         /* read a data point */
317 #if TPHELIO
318         short data[3];
319 #else
320         short data[6];
321 #endif
322         int bytes_read;
323         bytes_read = read(pd_fd, data, sizeof(data));
324         if (bytes_read != sizeof(data)) {
325                 if (errno == EINTR || errno == EAGAIN) {
326                         return 0;
327                 }
328                 return -1;
329         }
330 #ifndef TPHELIO
331         /* did we lose any data? */
332         if ( (data[0] & 0x2000) )
333                 EPRINTF("Lost touch panel data\n");
334
335         /* do we only have contact state data (no position data)? */
336         if ( (data[0] & 0x8000) == 0 )
337         {
338                 /* is it a pen-release? */
339                 if ( (data[0] & 0x4000) == 0 )
340                 {
341                         /* reset the limiter */
342                         have_last_data = 0;
343
344                         /* reset the filter */
345                         iir_count = 0;
346                         iir_accum_x = 0;
347                         iir_accum_y = 0;
348                         iir_accum_z = 0;
349
350                         /* return the pen (button) state only, */
351                         /* indicating that the pen is up (no buttons are down)*/
352                         *pb = 0;
353                         return 3;
354                 }
355
356                 /* ignore pen-down since we don't know where it is */
357                 return 0;
358         }
359 #endif
360         /* we have position data */
361 #if TPHELIO
362         data_x = data[1];
363         data_y = data[2];
364         data_z = data[0] ? 2000 : 0;
365 #else
366         /*
367          * Combine the complementary panel readings into one value (except z)
368          * This effectively doubles the sampling freqency, reducing noise
369          * by approx 3db.
370          * Again, please don't quote the 3db figure.  I think it also
371          * cancels out changes in the overall resistance of the panel
372          * such as may be caused by changes in panel temperature.
373          */
374         data_x = data[2] - data[1];
375         data_y = data[4] - data[3];
376         data_z = data[5];
377
378         /* isn't z big enough for valid position data? */
379         if ( data_z <= low_z_limit ) {
380                 return 0;
381         }
382
383         /* has the position changed more than we will allow? */
384         if ( have_last_data )
385                 if ( (abs(data_x - last_data_x) > data_change_limit)
386                         || ( abs(data_y - last_data_y) > data_change_limit ) ) {
387                         return 0;
388                 }
389 #endif
390
391         /* save last position */
392         last_data_x = data_x;
393         last_data_y = data_y;
394         have_last_data = 1;
395
396         /* is filter ready? */
397         if ( iir_count == iir_sample_depth )
398         {
399 #if TPHELIO
400                 if (enable_pointing_coordinate_transform) {
401                         MWPOINT transformed = {data_x, data_y};
402                         transformed = DeviceToScreen(transformed);
403
404                         *px = transformed.x >> 2;
405                         *py = transformed.y >> 2;
406                 } else {
407                         *px = data_x;
408                         *py = data_y;
409                 }
410                 *pb = data[0] ? MWBUTTON_L : 0;
411 #else
412                 /* make room for new sample */
413                 iir_accum_x -= iir_accum_x >> iir_shift_bits;
414                 iir_accum_y -= iir_accum_y >> iir_shift_bits;
415                 iir_accum_z -= iir_accum_z >> iir_shift_bits;
416
417                 /* feed new sample to filter */
418                 iir_accum_x += data_x;
419                 iir_accum_y += data_y;
420                 iir_accum_z += data_z;
421
422                 /* transformation enabled? */
423                 if (enable_pointing_coordinate_transform)
424                 {
425                         /* transform x,y to screen coords */
426                         MWPOINT transformed = {iir_accum_x, iir_accum_y};
427                         transformed = DeviceToScreen(transformed);
428                         /*
429                          * HACK: move this from quarter pixels to whole
430                          * pixels for now at least until I decide on the
431                          * right interface to get the quarter-pixel data
432                          * up to the next layer.
433                          */
434                         *px = transformed.x >> 2;
435                         *py = transformed.y >> 2;
436                 }
437                 else
438                 {
439                         /* return untransformed coords (for calibration) */
440                         *px = iir_accum_x;
441                         *py = iir_accum_y;
442                 }
443                 *pb = MWBUTTON_L;
444 #endif
445                 /* return filtered pressure */
446                 *pz = iir_accum_z;
447
448 #ifdef TEST
449                 EPRINTF("In: %hd, %hd, %hd  Filtered: %d %d %d  Out: %d, %d, %d\n",
450                         data_x, data_y, data_z, iir_accum_x, iir_accum_y,
451                         iir_accum_z, *px, *py, *pz);
452 #endif
453                 return 2;
454         }
455
456         /* prime the filter */
457         iir_accum_x += data_x;
458         iir_accum_y += data_y;
459         iir_accum_z += data_z;
460         iir_count += 1;
461
462         return 0;
463 }
464 #else   /* ifdef YOPY */
465 static int PD_Read(MWCOORD *px, MWCOORD *py, MWCOORD *pz, int *pb)
466 {
467         /*
468          * Read the tpanel state and position.
469          * Returns the position data in x, y, and button data in b.
470          * Returns -1 on error.
471          * Returns 0 if no new data is available.
472          * Returns 1 if position data is relative (i.e. mice).
473          * Returns 2 if position data is absolute (i.e. touch panels).
474          * Returns 3 if position data is not available, but button data is.
475          * This routine does not block.
476          *
477          * Unlike a mouse, this driver returns absolute postions, not deltas.
478          */
479
480         /* If z is below this value, ignore the data. */
481         /* const int low_z_limit = 900; EVEREX*/
482
483         /*
484          * I do some error masking by tossing out really wild data points.
485          * Lower data_change_limit value means pointer get's "left behind"
486          * more easily.  Higher value means less errors caught.
487          * The right setting of this value is just slightly higher than
488          * the number of units traversed per sample during a "quick" stroke.
489          */
490
491         int data_x, data_y;
492
493         /* read a data point */
494         int bytes_read;
495         int     mou_data;
496
497         bytes_read = read(pd_fd, &mou_data, sizeof(mou_data));
498         if (bytes_read != sizeof(mou_data)) {
499                 if (errno == EINTR || errno == EAGAIN) {
500                         return 0;
501                 }
502                 return -1;
503         }
504
505         data_x = ((MWCOORD)(mou_data & 0x3ff));
506         data_y = (MWCOORD)(( mou_data>>10 ) & 0x3ff );
507
508         /* transformation enabled? */
509         if (enable_pointing_coordinate_transform)
510         {
511                 /* transform x,y to screen coords */
512                 MWPOINT transformed = {data_x, data_y};
513                 transformed = DeviceToScreen(transformed);
514                 /*
515                  * HACK: move this from quarter pixels to whole
516                  * pixels for now at least until I decide on the
517                  * right interface to get the quarter-pixel data
518                  * up to the next layer.
519                  */
520                 *px = transformed.x >> 2;
521                 *py = transformed.y >> 2;
522         }
523         else
524         {
525                 /* return untransformed coords (for calibration) */
526                 *px = data_x;
527                 *py = data_y;
528         }
529
530         *pz = 0;
531         *pb = ( mou_data & (1<<31) )? MWBUTTON_L: 0;
532
533         if ( ! *pb )
534                 return 3;
535         else
536                 return 2;
537 }
538 #endif
539
540 MOUSEDEVICE mousedev = {
541         PD_Open,
542         PD_Close,
543         PD_GetButtonInfo,
544         PD_GetDefaultAccel,
545         PD_Read,
546         NULL
547 };
548
549 #ifdef TEST
550 int main()
551 {
552         MWCOORD x, y, z;
553         int     b;
554         int result;
555
556         enable_pointing_coordinate_transform = 1;
557
558         DPRINTF("Opening touch panel...\n");
559
560         if((result=PD_Open(0)) < 0)
561                 DPRINTF("Error %d, result %d opening touch-panel\n", errno, result);
562
563         DPRINTF("Reading touch panel...\n");
564
565         while(1) {
566                 result = PD_Read(&x, &y, &z, &b);
567                 if( result > 0) {
568                         /* DPRINTF("%d,%d,%d,%d,%d\n", result, x, y, z, b); */
569                 }
570         }
571 }
572 #endif