fixup clear optimization
[ax89063.git] / ax89063.c
1 /** \file server/drivers/ax89063.c
2  * LCDd \c ax89063 driver for AXIOMTEK 89063 LCDs..
3  */
4
5 /*  This is the LCDproc driver for AXIOMTEK 89063 displays
6  as found in the following 1U AXIOMTEK rack servers:
7  NA-510
8  NA-710
9  NA-814
10  NA-820
11  NA-821
12  NA-822
13  ... and probably some more ;)
14
15  The (rather useless) datasheet is available at
16  <http://eservice.axiomtek.com.tw/attach_files/K0509-0016/AX89063%20LCM%20Control%20Spec.pdf>
17  (backup at <http://bitdump.msquadrat.de/2010/ax89063/AX89063%20LCM%20Control%20Spec.pdf>).
18
19  The protocol is simple: The AX89063 is a 16x2 display with a 40x2 character
20  framebuffer. Only the first 16 characters of each line are displayed. A
21  single 0x0d followed by the contents of the framebuffer trigger an update:
22  "\x0d0123456789abcdef************************0123456789abcdef************************"
23
24  The four buttons are encoded as (clockwise starting from top left): ULRD
25
26  Copyright (C) 2011, Alexander Bluem <bluem [at] gmit-gmbh [dot] de>,
27                                      <alex [at] binarchy [dot] net>
28  Copyright (C) 2010, Kai Falkenberg <kai [at] layer0 [dot] de>
29  Copyright (C) 2010, Malte S. Stretz <http://msquadrat.de>
30
31  The code is derived from the ms6931 and bayrad driver
32
33  This program is free software; you can redistribute it and/or modify
34  it under the terms of the GNU General Public License as published by
35  the Free Software Foundation; either version 2 of the License, or
36  any later version.
37
38  This program is distributed in the hope that it will be useful,
39  but WITHOUT ANY WARRANTY; without even the implied warranty of
40  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
41  GNU General Public License for more details.
42
43  You should have received a copy of the GNU General Public License
44  along with this program; if not, write to the Free Software
45  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */
46
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <unistd.h>
50 #include <termios.h>
51 #include <string.h>
52 #include <errno.h>
53 #include <fcntl.h>
54
55 #ifdef HAVE_CONFIG_H
56 #include "config.h"
57 #endif
58
59 #include "lcd.h"
60 #include "report.h"
61 #include "ax89063.h"
62
63 typedef struct driver_private_data {
64         char device[256];
65         int speed;
66         int fd;
67         int width;
68         int height;
69         char *framebuf;
70         int framebuf_size;
71         int framebuf_clear;
72         char *framebuf_hw;
73 } PrivateData;
74
75 MODULE_EXPORT char * api_version = API_VERSION;
76 MODULE_EXPORT int stay_in_foreground = 1;
77 MODULE_EXPORT int supports_multiple = 0;
78 MODULE_EXPORT char *symbol_prefix = "ax89063_";
79
80
81 /**
82  * Reset the framebuffer with spaces if clear() was called before. The
83  * clearing of the screen is postponed to avoid hammering of the display
84  * which makes the keypad unresponsible.
85  * \param drvthis  Pointer to driver structure.
86  */
87 static inline void ax89063_clear_if_needed(PrivateData *p) {
88         if (!p->framebuf_clear)
89                 return;
90         memset(p->framebuf, ' ', p->framebuf_size);
91         p->framebuf_clear = 0;
92 }
93
94
95 /**
96  * Initialize the driver.
97  * \param drvthis  Pointer to driver structure.
98  * \retval 0       Success.
99  * \retval <0      Error.
100  */
101 MODULE_EXPORT int ax89063_init(Driver *drvthis) {
102         PrivateData *p;
103         struct termios portset;
104
105         p = (PrivateData *) calloc(1, sizeof(PrivateData));
106         if ((p == NULL) || (drvthis->store_private_ptr(drvthis, p)))
107                 return -1;
108
109         /* Initialize private data */
110         /* These first items could be madeconfigurable */
111         p->speed = AX89063_SPEED;
112         p->width = AX89063_WIDTH;
113         p->height = AX89063_HEIGHT;
114
115         p->fd = -1;
116         p->framebuf = NULL;
117         p->framebuf_hw = NULL;
118         p->framebuf_size = p->width * p->height;
119         p->framebuf_clear = 0;
120
121         /* Read config file */
122         /* Get device name, use default if it cannot be retrieved.*/
123         strncpy(p->device, drvthis->config_get_string(drvthis->name, "Device", 0,
124                         AX89063_DEFAULT_DEVICE), sizeof(p->device));
125         p->device[sizeof(p->device) - 1] = '\0';
126         report(RPT_INFO, "%s: using Device %s", drvthis->name, p->device);
127
128         /* Get device speed. */
129         if (p->speed != AX89063_SPEED) {
130                 report(
131                                 RPT_WARNING,
132                                 "%s: Illegal speed (%d) detected in config file. Using default (%d).",
133                                 drvthis->name, p->speed, AX89063_SPEED);
134         } else {
135                 p->speed = AX89063_SPEED;
136         }
137
138         /* Set up serial port and open it. */
139         p->fd = open(p->device, O_RDWR | O_NOCTTY | O_NONBLOCK);
140
141         if (p->fd == -1) {
142                 report(RPT_ERR, "AX89063: serial: could not open device %s (%s)",
143                                 p->device, strerror(errno));
144                 return -1;
145         }
146
147         /* Get and set serial device parameters */
148         tcgetattr(p->fd, &portset);
149         cfsetospeed(&portset, p->speed);
150         cfsetispeed(&portset, p->speed);
151
152 #ifdef HAVE_CFMAKERAW
153         cfmakeraw(&portset);
154 #else
155         portset.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR
156                         | ICRNL | IXON);
157         portset.c_oflag &= ~OPOST;
158         portset.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
159         portset.c_cflag &= ~(CSIZE | PARENB | CRTSCTS);
160         portset.c_cflag |= CS8 | CREAD | CLOCAL;
161 #endif
162
163         portset.c_cc[VMIN] = 0;
164         portset.c_cc[VTIME] = 0;
165
166         tcsetattr(p->fd, TCSANOW, &portset);
167
168         /* Make sure the frame buffer is there... */
169         p->framebuf = (char *) malloc(p->framebuf_size);
170         if (p->framebuf == NULL) {
171                 report(RPT_ERR, "%s: unable to create framebuffer", drvthis->name);
172                 return -1;
173         }
174         /* Initialize the framebuffer */
175         ax89063_clear(drvthis);
176
177         /* Make sure the frame buffer is there... */
178         p->framebuf_hw = (char *) malloc(AX89063_HWFRAMEBUFLEN + 1);
179         if (p->framebuf_hw == NULL) {
180                 report(RPT_ERR, "%s: unable to create LCD framebuffer", drvthis->name);
181                 return -1;
182         }
183         /* Encode the start byte */
184         p->framebuf_hw[0] = (char) 0x0d;
185         /* Initialize the framebuffer */
186         memset(p->framebuf_hw + 1, '*', AX89063_HWFRAMEBUFLEN);
187
188         return 0;
189 }
190
191 /**
192  * Flush data on screen to the LCD.
193  * \param drvthis  Pointer to driver structure.
194  */
195 MODULE_EXPORT void ax89063_flush(Driver *drvthis) {
196         int x, y, result;
197         PrivateData *p = drvthis->private_data;
198         char *str = p->framebuf;
199         static int csum_s = 0;
200         int csum = 0;
201
202         int ret = 0;
203         struct timeval selectTimeout = { 0, 0 };
204         fd_set fdset;
205
206         FD_ZERO(&fdset);
207         FD_SET(p->fd, &fdset);
208
209         ax89063_clear_if_needed(p);
210
211         //p->width * p->height == p->framebuf_size
212         for (y = 0; y < p->height; y++)
213                 for (x = 0; x < p->width; x++)
214                         p->framebuf_hw[y * (AX89063_HWFRAMEBUFLEN / 2) + x + 1] = *(str++);
215
216         if ((ret = select(FD_SETSIZE, NULL, &fdset, NULL, &selectTimeout)) < 0) {
217                 report(RPT_ERR, "%s: get_key: select() failed (%s)", drvthis->name,
218                                 strerror(errno));
219                 return;
220         }
221
222         // select() timed out - unlikely
223         if (!ret) {
224                 FD_SET(p->fd, &fdset);
225                 return;
226         }
227
228         if (!FD_ISSET(p->fd, &fdset)) {
229                 report(RPT_INFO, "%s: flush: select() nothing was written",
230                                 drvthis->name);
231                 return;
232         }
233
234         // Minimize the number of writing cycles to max the responsiveness
235         for (x = 0; x < AX89063_HWFRAMEBUFLEN; x++)
236                 csum += p->framebuf_hw[x];
237
238         if (csum == csum_s)
239                 return;
240
241         csum_s = csum;
242
243         // Flush all 81 chars at once
244         result = write(p->fd, p->framebuf_hw, AX89063_HWFRAMEBUFLEN + 1);
245 }
246
247 /**
248  * Close the driver (do necessary clean-up).
249  * \param drvthis  Pointer to driver structure.
250  */
251 MODULE_EXPORT void ax89063_close(Driver *drvthis) {
252         PrivateData *p = drvthis->private_data;
253
254         if (p != NULL) {
255                 if (p->fd >= 0) {
256                         close(p->fd);
257                 }
258
259                 if (p->framebuf != NULL)
260                         free(p->framebuf);
261
262                 if (p->framebuf_hw != NULL)
263                         free(p->framebuf_hw);
264
265                 free(p);
266         }
267         drvthis->store_private_ptr(drvthis, NULL);
268 }
269
270 /**
271  * Return the display width in characters.
272  * \param drvthis  Pointer to driver structure.
273  * \return         Number of characters the display is wide.
274  */
275 MODULE_EXPORT int ax89063_width(Driver *drvthis) {
276         PrivateData *p = drvthis->private_data;
277         return p->width;
278 }
279
280 /**
281  * Return the display height in characters.
282  * \param drvthis  Pointer to driver structure.
283  * \return         Number of characters the display is high.
284  */
285 MODULE_EXPORT int ax89063_height(Driver *drvthis) {
286         PrivateData *p = drvthis->private_data;
287         return p->height;
288 }
289
290 /**
291  * Return the width of a character in pixels.
292  * \param drvthis  Pointer to driver structure.
293  * \return         Number of pixel columns a character cell is wide.
294  */
295 MODULE_EXPORT int ax89063_cellwidth(Driver *drvthis) {
296         return AX89063_CELLWIDTH;
297 }
298
299 /**
300  * Return the height of a character in pixels.
301  * \param drvthis  Pointer to driver structure.
302  * \return         Number of pixel lines a character cell is high.
303  */
304 MODULE_EXPORT int ax89063_cellheight(Driver *drvthis) {
305         return AX89063_CELLHEIGHT;
306 }
307
308 /**
309  * Clear the screen. clear() actually clears the buffer by
310  * filling it with spaces, 0x20.
311  * \param drvthis  Pointer to driver structure.
312  */
313 MODULE_EXPORT void ax89063_clear(Driver *drvthis) {
314         PrivateData *p = drvthis->private_data;
315         p->framebuf_clear = 1;
316 }
317
318 /**
319  * Print a string on the screen at position (x,y).
320  * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height).
321  * \param drvthis  Pointer to driver structure.
322  * \param x        Horizontal character position (column).
323  * \param y        Vertical character position (row).
324  * \param string   String that gets written.
325  */
326 MODULE_EXPORT void ax89063_string(Driver *drvthis, int x, int y,
327                 const char string[]) {
328         int i = 0;
329         PrivateData *p = drvthis->private_data;
330
331         /* Convert 1-based coords to 0-based... */
332         x--;
333         y--;
334
335         if ((y < 0) || (y >= p->height))
336                 return;
337
338         ax89063_clear_if_needed(p);
339         for (i = 0; (string[i] != '\0') && (x < p->width); i++, x++) {
340                 /* Check for buffer overflows... */
341                 if (x >= 0)
342                         p->framebuf[(y * p->width) + x] = string[i];
343         }
344 }
345
346 /**
347  * Print a character on the screen at position (x,y).
348  * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height).
349  * \param drvthis  Pointer to driver structure.
350  * \param x        Horizontal character position (column).
351  * \param y        Vertical character position (row).
352  * \param c        Character that gets written.
353  */
354 MODULE_EXPORT void ax89063_chr(Driver *drvthis, int x, int y, char c) {
355         PrivateData *p = drvthis->private_data;
356         y--;
357         x--;
358
359         ax89063_clear_if_needed(p);
360         if ((x >= 0) && (y >= 0) && (x < p->width) && (y < p->height))
361                 p->framebuf[(y * p->width) + x] = c;
362 }
363
364 /**
365  * Get key from the device.
366  * \param drvthis  Pointer to driver structure.
367  * \return         String representation of the key;
368  *                 \c NULL if nothing available / unmapped key.
369  */
370 MODULE_EXPORT const char *ax89063_get_key(Driver *drvthis) {
371         PrivateData *p = drvthis->private_data;
372         char key;
373         char *str = NULL;
374         int ret = 0;
375
376         /* the timeout time is set high, as the display is unresponsive when
377          * just written to */
378         struct timeval selectTimeout = { 0, 500E3 };
379         fd_set fdset;
380
381         FD_ZERO(&fdset);
382         FD_SET(p->fd, &fdset);
383
384         if ((ret = select(FD_SETSIZE, &fdset, NULL, NULL, &selectTimeout)) < 0) {
385                 report(RPT_ERR, "%s: get_key: select() failed (%s)", drvthis->name,
386                                 strerror(errno));
387                 return NULL;
388         }
389
390         // select() timed out
391         if (!ret) {
392                 FD_SET(p->fd, &fdset);
393                 return NULL;
394         }
395
396         if (!FD_ISSET(p->fd, &fdset)) {
397                 report(RPT_INFO, "%s: get_key: select() no keys pressed", drvthis->name);
398                 return NULL;
399         }
400
401         if ((ret = read(p->fd, &key, 1)) < 0) {
402                 report(RPT_ERR, "%s: get_key: read() failed and returned %d (%s)",
403                                 drvthis->name, ret, strerror(errno));
404                 return NULL;
405         }
406
407         if (ret == 1) {
408                 switch (key) {
409                 case 'U':
410                         str = "up";
411                         break;
412                 case 'D':
413                         str = "down";
414                         break;
415                 case 'L':
416                         str = "left";
417                         break;
418                 case 'R':
419                         str = "right";
420                         break;
421                 }
422         }
423         return str;
424 }