edfc419b36ee5caf6da0ec48ee3fb3f950eee644
[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         memset(p->framebuf, ' ', p->framebuf_size);
89         p->framebuf_clear = 0;
90 }
91
92
93 /**
94  * Initialize the driver.
95  * \param drvthis  Pointer to driver structure.
96  * \retval 0       Success.
97  * \retval <0      Error.
98  */
99 MODULE_EXPORT int ax89063_init(Driver *drvthis) {
100         PrivateData *p;
101         struct termios portset;
102
103         p = (PrivateData *) calloc(1, sizeof(PrivateData));
104         if ((p == NULL) || (drvthis->store_private_ptr(drvthis, p)))
105                 return -1;
106
107         /* Initialize private data */
108         /* These first items could be madeconfigurable */
109         p->speed = AX89063_SPEED;
110         p->width = AX89063_WIDTH;
111         p->height = AX89063_HEIGHT;
112
113         p->fd = -1;
114         p->framebuf = NULL;
115         p->framebuf_hw = NULL;
116         p->framebuf_size = p->width * p->height;
117         p->framebuf_clear = 0;
118
119         /* Read config file */
120         /* Get device name, use default if it cannot be retrieved.*/
121         strncpy(p->device, drvthis->config_get_string(drvthis->name, "Device", 0,
122                         AX89063_DEFAULT_DEVICE), sizeof(p->device));
123         p->device[sizeof(p->device) - 1] = '\0';
124         report(RPT_INFO, "%s: using Device %s", drvthis->name, p->device);
125
126         /* Get device speed. */
127         if (p->speed != AX89063_SPEED) {
128                 report(
129                                 RPT_WARNING,
130                                 "%s: Illegal speed (%d) detected in config file. Using default (%d).",
131                                 drvthis->name, p->speed, AX89063_SPEED);
132         } else {
133                 p->speed = AX89063_SPEED;
134         }
135
136         /* Set up serial port and open it. */
137         p->fd = open(p->device, O_RDWR | O_NOCTTY | O_NONBLOCK);
138
139         if (p->fd == -1) {
140                 report(RPT_ERR, "AX89063: serial: could not open device %s (%s)",
141                                 p->device, strerror(errno));
142                 return -1;
143         }
144
145         /* Get and set serial device parameters */
146         tcgetattr(p->fd, &portset);
147         cfsetospeed(&portset, p->speed);
148         cfsetispeed(&portset, p->speed);
149
150 #ifdef HAVE_CFMAKERAW
151         cfmakeraw(&portset);
152 #else
153         portset.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR
154                         | ICRNL | IXON);
155         portset.c_oflag &= ~OPOST;
156         portset.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
157         portset.c_cflag &= ~(CSIZE | PARENB | CRTSCTS);
158         portset.c_cflag |= CS8 | CREAD | CLOCAL;
159 #endif
160
161         portset.c_cc[VMIN] = 0;
162         portset.c_cc[VTIME] = 0;
163
164         tcsetattr(p->fd, TCSANOW, &portset);
165
166         /* Make sure the frame buffer is there... */
167         p->framebuf = (char *) malloc(p->framebuf_size);
168         if (p->framebuf == NULL) {
169                 report(RPT_ERR, "%s: unable to create framebuffer", drvthis->name);
170                 return -1;
171         }
172         /* Initialize the framebuffer */
173         ax89063_clear(drvthis);
174
175         /* Make sure the frame buffer is there... */
176         p->framebuf_hw = (char *) malloc(AX89063_HWFRAMEBUFLEN + 1);
177         if (p->framebuf_hw == NULL) {
178                 report(RPT_ERR, "%s: unable to create LCD framebuffer", drvthis->name);
179                 return -1;
180         }
181         /* Encode the start byte */
182         p->framebuf_hw[0] = (char) 0x0d;
183         /* Initialize the framebuffer */
184         memset(p->framebuf_hw + 1, '*', AX89063_HWFRAMEBUFLEN);
185
186         return 0;
187 }
188
189 /**
190  * Flush data on screen to the LCD.
191  * \param drvthis  Pointer to driver structure.
192  */
193 MODULE_EXPORT void ax89063_flush(Driver *drvthis) {
194         int x, y, result;
195         PrivateData *p = drvthis->private_data;
196         char *str = p->framebuf;
197         static int csum_s = 0;
198         int csum = 0;
199
200         int ret = 0;
201         struct timeval selectTimeout = { 0, 0 };
202         fd_set fdset;
203
204         FD_ZERO(&fdset);
205         FD_SET(p->fd, &fdset);
206
207         ax89063_clear_if_needed(p);
208
209         //p->width * p->height == p->framebuf_size
210         for (y = 0; y < p->height; y++)
211                 for (x = 0; x < p->width; x++)
212                         p->framebuf_hw[y * (AX89063_HWFRAMEBUFLEN / 2) + x + 1] = *(str++);
213
214         if ((ret = select(FD_SETSIZE, NULL, &fdset, NULL, &selectTimeout)) < 0) {
215                 report(RPT_ERR, "%s: get_key: select() failed (%s)", drvthis->name,
216                                 strerror(errno));
217                 return;
218         }
219
220         // select() timed out - unlikely
221         if (!ret) {
222                 FD_SET(p->fd, &fdset);
223                 return;
224         }
225
226         if (!FD_ISSET(p->fd, &fdset)) {
227                 report(RPT_INFO, "%s: flush: select() nothing was written",
228                                 drvthis->name);
229                 return;
230         }
231
232         // Minimize the number of writing cycles to max the responsiveness
233         for (x = 0; x < AX89063_HWFRAMEBUFLEN; x++)
234                 csum += p->framebuf_hw[x];
235
236         if (csum == csum_s)
237                 return;
238
239         csum_s = csum;
240
241         // Flush all 81 chars at once
242         result = write(p->fd, p->framebuf_hw, AX89063_HWFRAMEBUFLEN + 1);
243 }
244
245 /**
246  * Close the driver (do necessary clean-up).
247  * \param drvthis  Pointer to driver structure.
248  */
249 MODULE_EXPORT void ax89063_close(Driver *drvthis) {
250         PrivateData *p = drvthis->private_data;
251
252         if (p != NULL) {
253                 if (p->fd >= 0) {
254                         close(p->fd);
255                 }
256
257                 if (p->framebuf != NULL)
258                         free(p->framebuf);
259
260                 if (p->framebuf_hw != NULL)
261                         free(p->framebuf_hw);
262
263                 free(p);
264         }
265         drvthis->store_private_ptr(drvthis, NULL);
266 }
267
268 /**
269  * Return the display width in characters.
270  * \param drvthis  Pointer to driver structure.
271  * \return         Number of characters the display is wide.
272  */
273 MODULE_EXPORT int ax89063_width(Driver *drvthis) {
274         PrivateData *p = drvthis->private_data;
275         return p->width;
276 }
277
278 /**
279  * Return the display height in characters.
280  * \param drvthis  Pointer to driver structure.
281  * \return         Number of characters the display is high.
282  */
283 MODULE_EXPORT int ax89063_height(Driver *drvthis) {
284         PrivateData *p = drvthis->private_data;
285         return p->height;
286 }
287
288 /**
289  * Return the width of a character in pixels.
290  * \param drvthis  Pointer to driver structure.
291  * \return         Number of pixel columns a character cell is wide.
292  */
293 MODULE_EXPORT int ax89063_cellwidth(Driver *drvthis) {
294         return AX89063_CELLWIDTH;
295 }
296
297 /**
298  * Return the height of a character in pixels.
299  * \param drvthis  Pointer to driver structure.
300  * \return         Number of pixel lines a character cell is high.
301  */
302 MODULE_EXPORT int ax89063_cellheight(Driver *drvthis) {
303         return AX89063_CELLHEIGHT;
304 }
305
306 /**
307  * Clear the screen. clear() actually clears the buffer by
308  * filling it with spaces, 0x20.
309  * \param drvthis  Pointer to driver structure.
310  */
311 MODULE_EXPORT void ax89063_clear(Driver *drvthis) {
312         PrivateData *p = drvthis->private_data;
313         p->framebuf_clear = 1;
314 }
315
316 /**
317  * Print a string on the screen at position (x,y).
318  * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height).
319  * \param drvthis  Pointer to driver structure.
320  * \param x        Horizontal character position (column).
321  * \param y        Vertical character position (row).
322  * \param string   String that gets written.
323  */
324 MODULE_EXPORT void ax89063_string(Driver *drvthis, int x, int y,
325                 const char string[]) {
326         int i = 0;
327         PrivateData *p = drvthis->private_data;
328
329         /* Convert 1-based coords to 0-based... */
330         x--;
331         y--;
332
333         if ((y < 0) || (y >= p->height))
334                 return;
335
336         ax89063_clear_if_needed(p);
337         for (i = 0; (string[i] != '\0') && (x < p->width); i++, x++) {
338                 /* Check for buffer overflows... */
339                 if (x >= 0)
340                         p->framebuf[(y * p->width) + x] = string[i];
341         }
342 }
343
344 /**
345  * Print a character on the screen at position (x,y).
346  * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height).
347  * \param drvthis  Pointer to driver structure.
348  * \param x        Horizontal character position (column).
349  * \param y        Vertical character position (row).
350  * \param c        Character that gets written.
351  */
352 MODULE_EXPORT void ax89063_chr(Driver *drvthis, int x, int y, char c) {
353         PrivateData *p = drvthis->private_data;
354         y--;
355         x--;
356
357         ax89063_clear_if_needed(p);
358         if ((x >= 0) && (y >= 0) && (x < p->width) && (y < p->height))
359                 p->framebuf[(y * p->width) + x] = c;
360 }
361
362 /**
363  * Get key from the device.
364  * \param drvthis  Pointer to driver structure.
365  * \return         String representation of the key;
366  *                 \c NULL if nothing available / unmapped key.
367  */
368 MODULE_EXPORT const char *ax89063_get_key(Driver *drvthis) {
369         PrivateData *p = drvthis->private_data;
370         char key;
371         char *str = NULL;
372         int ret = 0;
373
374         /* the timeout time is set high, as the display is unresponsive when
375          * just written to */
376         struct timeval selectTimeout = { 0, 500E3 };
377         fd_set fdset;
378
379         FD_ZERO(&fdset);
380         FD_SET(p->fd, &fdset);
381
382         if ((ret = select(FD_SETSIZE, &fdset, NULL, NULL, &selectTimeout)) < 0) {
383                 report(RPT_ERR, "%s: get_key: select() failed (%s)", drvthis->name,
384                                 strerror(errno));
385                 return NULL;
386         }
387
388         // select() timed out
389         if (!ret) {
390                 FD_SET(p->fd, &fdset);
391                 return NULL;
392         }
393
394         if (!FD_ISSET(p->fd, &fdset)) {
395                 report(RPT_INFO, "%s: get_key: select() no keys pressed", drvthis->name);
396                 return NULL;
397         }
398
399         if ((ret = read(p->fd, &key, 1)) < 0) {
400                 report(RPT_ERR, "%s: get_key: read() failed and returned %d (%s)",
401                                 drvthis->name, ret, strerror(errno));
402                 return NULL;
403         }
404
405         if (ret == 1) {
406                 switch (key) {
407                 case 'U':
408                         str = "up";
409                         break;
410                 case 'D':
411                         str = "down";
412                         break;
413                 case 'L':
414                         str = "left";
415                         break;
416                 case 'R':
417                         str = "right";
418                         break;
419                 }
420         }
421         return str;
422 }