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