root/trunk/drv_D4D.c

Revision 897, 18.0 kB (checked in by michael, 7 weeks ago)

properties fixed

  • Property svn:executable set to *
  • Property svn:keywords set to Id URL Rev
Line 
1/* $Id$
2 * $URL$
3 *
4 * LCD4Linux driver for 4D Systems Display Graphics Modules
5 *
6 * Copyright (C) 2008 Sven Killig <sven@killig.de>
7 * Modified from sample code by:
8 * Copyright (C) 2005 Michael Reinelt <michael@reinelt.co.at>
9 * Copyright (C) 2005, 2006, 2007 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
10 *
11 * This file is part of LCD4Linux.
12 *
13 * LCD4Linux is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2, or (at your option)
16 * any later version.
17 *
18 * LCD4Linux is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 *
27 */
28
29/*
30 *
31 * exported fuctions:
32 *
33 * struct DRIVER drv_D4D
34 *
35 */
36
37#include "config.h"
38
39#include <stdlib.h>
40#include <stdio.h>
41#include <unistd.h>
42#include <string.h>
43#include <errno.h>
44
45#include <termios.h>
46#include <fcntl.h>
47#include <math.h>
48
49#include "debug.h"
50#include "cfg.h"
51#include "qprintf.h"
52#include "udelay.h"
53#include "plugin.h"
54#include "widget.h"
55#include "widget_text.h"
56#include "widget_icon.h"
57#include "widget_bar.h"
58#include "drv.h"
59
60#include "drv_generic_text.h"
61#include "drv_generic_graphic.h"
62#include "drv_generic_serial.h"
63
64static char Name[] = "D4D";
65char NAME_[20];
66int FONT = 1, MODE = 0, EXTRA = 0, SECTOR = 0, SECTOR_SIZE, NOPOWERCYCLE = 0;
67/* int CONTRAST_; */
68
69#define address_mmsb(variable) ((variable >> 24) & 0xFF)
70#define address_mlsb(variable) ((variable >> 16) & 0xFF)
71#define address_lmsb(variable) ((variable >>  8) & 0xFF)
72#define address_llsb(variable)  (variable        & 0xFF)
73
74#define address_hi(variable)   ((variable >> 16) & 0xFF)
75#define address_mid(variable)  ((variable >>  8) & 0xFF)
76#define address_lo(variable)    (variable        & 0xFF)
77
78#define msb(variable)          ((variable >>  8) & 0xFF)
79#define lsb(variable)           (variable        & 0xFF)
80
81RGBA FG_COL_ = {.R = 0x00,.G = 0x00,.B = 0x00,.A = 0xff };
82RGBA BG_COL_ = {.R = 0xff,.G = 0xff,.B = 0xff,.A = 0xff };
83
84short int FG_COLOR, BG_COLOR;
85
86int RGB_24to16(int red, int grn, int blu)
87{
88    return (((red >> 3) << 11) | ((grn >> 2) << 5) | (blu >> 3));
89}
90
91int RGB_24to8(int red, int grn, int blu)
92{
93    return (((red >> 5) << 5) | ((grn >> 5) << 2) | (blu >> 6));
94}
95
96/****************************************/
97/***  hardware dependant functions    ***/
98/****************************************/
99
100
101static int drv_D4D_open(const char *section)
102{
103    int fd;
104    fd = drv_generic_serial_open(section, Name, 0);
105    if (fd < 0)
106  return -1;
107    fcntl(fd, F_SETFL, 0);  /* blocking read */
108    return 0;
109}
110
111
112static int drv_D4D_close(void)
113{
114    drv_generic_serial_close();
115    return 0;
116}
117
118
119static void drv_D4D_receive_ACK()
120{
121    char ret[1];
122    while (drv_generic_serial_read(ret, sizeof(ret)) != 1)
123  usleep(1);    /* loop should be unneccessary */
124    if (ret[0] == 0x15) {
125  error("NAK!");
126    } else if (ret[0] != 6) {
127  error("no ACK!");
128    }
129}
130
131static void drv_D4D_send_nowait(const char *data, const unsigned int len)
132{
133    drv_generic_serial_write(data, len);
134}
135
136static void drv_D4D_send(const char *data, const unsigned int len)
137{
138    drv_D4D_send_nowait(data, len);
139    drv_D4D_receive_ACK();
140}
141
142static void drv_D4D_send_nowait_extra(const char *data, const unsigned int len, unsigned char pos1, unsigned char pos2)
143{
144    /* possibly leave out bytes at pos1 and pos2 for older protocol format */
145    if (EXTRA) {
146  drv_D4D_send_nowait(data, len);
147    } else {
148  unsigned int i;
149  char send[1];
150  for (i = 0; i < len; i++) {
151      if (!pos1 || i != pos1) {
152    if (!pos2 || i != pos2) {
153        send[0] = data[i];
154        drv_generic_serial_write(send, 1);
155    }
156      }
157  }
158    }
159}
160
161static void drv_D4D_send_extra(const char *data, const unsigned int len, char pos1, char pos2)
162{
163    drv_D4D_send_nowait_extra(data, len, pos1, pos2);
164    drv_D4D_receive_ACK();
165}
166
167
168static void drv_D4D_clear(void)
169{
170    char cmd[] = { 'E' };
171    drv_D4D_send(cmd, sizeof(cmd));
172}
173
174
175static void drv_D4D_write(const int row, const int col, const char *data, int len)
176{
177    char out[len];
178    char user_char[len];
179    int user_x[len];
180    int user_y[len];
181    int i, k = 0;
182
183    if (!SECTOR) {    /* font in ROM */
184
185  for (i = 0; i < len; i++) {
186      if (data[i] >= 31 && data[i] < CHAR0) {
187    out[i] = data[i];
188      } else if (data[i] == 'µ') {  /* mu */
189    if (FONT == 2)
190        out[i] = 31;  /* undocumented */
191    else if (FONT == 0)
192        out[i] = 'u';
193    else
194        out[i] = 127; /* undocumented */
195      } else {
196    out[i] = ' ';
197    if (data[i] == '°')
198        user_char[k] = 0; /* degree */
199    else
200        user_char[k] = data[i] - CHAR0;
201    user_x[k] = (col + i) * XRES;
202    user_y[k] = row * YRES;
203    k++;
204      }
205  }
206
207  char cmd[] = { 's', col, row, FONT, msb(FG_COLOR), lsb(FG_COLOR) }; /* normal chars */
208  drv_D4D_send_nowait(cmd, sizeof(cmd));
209  if (len > 256)
210      len = 256;
211  drv_D4D_send_nowait(out, len);
212  char cmdNull[1];
213  cmdNull[0] = 0;
214  drv_D4D_send(cmdNull, 1);
215
216  char cmd_user[] = { 'D', 0, 0, 0, 0, 0, msb(FG_COLOR), lsb(FG_COLOR) }; /* user defined symbols */
217  for (i = 0; i < k; i++) {
218      cmd_user[2] = user_char[i];
219      cmd_user[3] = user_x[i];
220      cmd_user[4] = msb(user_y[i]);
221      cmd_user[5] = lsb(user_y[i]);
222      drv_D4D_send_extra(cmd_user, sizeof(cmd_user), 1, 4);
223  }
224    } else {      /* font on SD card */
225  int sec;
226  char cmd_sd[] = { '@', 'I', 0, msb(row * YRES), lsb(row * YRES), XRES, msb(YRES), lsb(YRES), 16, 0, 0, 0 };
227  for (i = 0; i < len; i++) {
228      cmd_sd[2] = (col + i) * XRES;
229      sec = SECTOR + (unsigned char) data[i] * SECTOR_SIZE;
230      cmd_sd[9] = address_hi(sec);
231      cmd_sd[10] = address_mid(sec);
232      cmd_sd[11] = address_lo(sec);
233      drv_D4D_send_extra(cmd_sd, sizeof(cmd_sd), 3, 6);
234  }
235    }
236}
237
238
239static void drv_D4D_defchar(const int ascii, const unsigned char *matrix)
240{
241    /* error("drv_D4D_defchar"); */
242    char cmd[11];
243    int i;
244
245    cmd[0] = 'A';
246    cmd[1] = 0;
247    cmd[2] = ascii - CHAR0;
248
249    for (i = 0; i < 8; i++) {
250  cmd[i + 3] = *matrix++;
251    }
252    drv_D4D_send_extra(cmd, sizeof(cmd), 1, 0);
253}
254
255
256static void drv_D4D_blit(const int row, const int col, const int height, const int width)
257{
258    /* error("drv_D4D_blit(%i, %i, %i, %i)",row, col, height, width); */
259    int r, c;
260    RGBA rgb, pixel0_0, pixel;
261    short int color;
262    char colorArray[2];
263
264
265    /* optimization: single colour rectangle? */
266    pixel0_0 = drv_generic_graphic_rgb(0, 0);
267    char unicolor = 1;
268    for (r = row; r < row + height; r++) {
269  if (!unicolor)
270      break;
271  for (c = col; c < col + width; c++) {
272      if (!unicolor)
273    break;
274      pixel = drv_generic_graphic_rgb(r, c);
275      if (pixel0_0.R != pixel.R || pixel0_0.G != pixel.G || pixel0_0.B != pixel.B || pixel0_0.A != pixel.A)
276    unicolor = 0;
277  }
278    }
279
280    if (unicolor) {
281  color = RGB_24to16(pixel0_0.R, pixel0_0.G, pixel0_0.B);
282  char row2 = row + height - 1;
283  char cmdRect[] =
284      { 'r', col, msb(row), lsb(row), col + width - 1, msb(row2), lsb(row2), msb(color), lsb(color) };
285  drv_D4D_send_extra(cmdRect, sizeof(cmdRect), 2, 5);
286    } else {
287  char cmd[] = { 'I', col, msb(row), lsb(row), width, msb(height), lsb(height), MODE };
288  drv_D4D_send_nowait_extra(cmd, sizeof(cmd), 2, 5);
289  for (r = row; r < row + height; r++) {
290      for (c = col; c < col + width; c++) {
291    rgb = drv_generic_graphic_rgb(r, c);
292    if (MODE == 8) {
293        colorArray[0] = RGB_24to8(rgb.R, rgb.G, rgb.B);
294        drv_D4D_send_nowait(colorArray, 1);
295    } else {
296        color = RGB_24to16(rgb.R, rgb.G, rgb.B);
297        colorArray[0] = msb(color);
298        drv_D4D_send_nowait(colorArray, 1); /* doesn't werk if sent together (error: "partial write(/dev/tts/1): len=2 ret=1") */
299        /* colorArray[1]=lsb(color); */
300        colorArray[0] = lsb(color);
301        drv_D4D_send_nowait(colorArray, 1);
302    }
303    /* drv_D4D_send_nowait(colorArray, MODE/8); */
304      }
305  }
306  drv_D4D_receive_ACK();
307    }
308}
309
310
311static int drv_D4D_contrast(int contrast)
312{
313    if (contrast < 0)
314  contrast = 0;
315    if (contrast > 15)
316  contrast = 15;
317
318    char cmd[] = { 'Y', 2, contrast };
319    drv_D4D_send(cmd, sizeof(cmd));
320
321    /* CONTRAST_=contrast; */
322    return contrast;
323}
324
325
326static int drv_D4D_start(const char *section)
327{
328    /* error("drv_D4D_start()"); */
329    int contrast;
330    int xres_cfg = -1, yres_cfg = -1;
331    char *s;
332    char *color;
333
334    if (drv_D4D_open(section) < 0) {
335  return -1;
336    }
337
338    char cmd[] = { 'U' };
339    drv_D4D_send(cmd, sizeof(cmd));
340
341
342    char getVersion[] = { 'V', 0 };
343    drv_D4D_send_nowait(getVersion, sizeof(getVersion));
344    char answer[5];
345    drv_generic_serial_read(answer, sizeof(answer));
346    char *ids[] = { "uOLED", "uLCD", "uVGA" };
347    char *id;
348    if (answer[0] <= 2)
349  id = ids[(int) answer[0]];
350    else
351  id = "Unknown";
352    qprintf(NAME_, sizeof(NAME_), "%s H%u S%u", id, (int) answer[1], (int) answer[2]);
353    int res[2];
354    int i;
355    for (i = 0; i < 2; i++) {
356  switch ((unsigned char) answer[3 + i]) {
357  case 0x22:
358      res[i] = 220;
359      break;
360  case 0x28:
361      res[i] = 128;
362      break;
363  case 0x32:
364      res[i] = 320;
365      EXTRA = 1;
366      break;
367  case 0x60:
368      res[i] = 160;
369      break;
370  case 0x64:
371      res[i] = 64;
372      break;
373  case 0x76:
374      res[i] = 176;
375      break;
376  case 0x96:
377      res[i] = 96;
378      break;
379  case 0x24:    /* undocumented? */
380      res[i] = 240;
381      break;
382  default:
383      error("Can't detect display dimensions!");
384      return -1;
385  }
386    }
387    DCOLS = res[0];
388    DROWS = res[1];
389
390
391    cfg_number(section, "Mode", 0, 0, 16, &MODE);
392
393    s = cfg_get(section, "Font", NULL);
394    if (s == NULL || *s == '\0') {
395  error("%s: no '%s.Font' entry from %s", Name, section, cfg_source());
396  return -1;
397    }
398    if (sscanf(s, "%dx%d", &xres_cfg, &yres_cfg) < 1 || xres_cfg < 0) {
399  error("%s: bad %s.Font '%s' from %s", Name, section, s, cfg_source());
400  free(s);
401  return -1;
402    }
403
404    if (yres_cfg == -1) { /* font on SD card */
405  SECTOR = xres_cfg * 512;
406  char setAddress[] =
407      { '@', 'A', address_mmsb(SECTOR), address_mlsb(SECTOR), address_lmsb(SECTOR), address_llsb(SECTOR) };
408  drv_D4D_send(setAddress, sizeof(setAddress));
409  char answer1[1];
410  char readByte[] = { '@', 'r' };
411  drv_D4D_send_nowait(readByte, sizeof(readByte));
412  drv_generic_serial_read(answer1, sizeof(answer1));
413  XRES = answer1[0];
414  drv_D4D_send_nowait(readByte, sizeof(readByte));
415  drv_generic_serial_read(answer1, sizeof(answer1));
416  YRES = answer1[0];
417  SECTOR = xres_cfg + 1;
418  SECTOR_SIZE = ceil((double) XRES * (double) YRES * 2.0 / 512.0);
419    } else {
420  XRES = xres_cfg;
421  YRES = yres_cfg;
422    }
423
424    if (MODE == 0) {
425
426  color = cfg_get(section, "foreground", "ffffff");
427  if (color2RGBA(color, &FG_COL_) < 0) {
428      error("%s: ignoring illegal color '%s'", Name, color);
429  }
430  if (color)
431      free(color);
432  FG_COLOR = RGB_24to16(FG_COL_.R, FG_COL_.G, FG_COL_.B);
433
434  color = cfg_get(section, "background", "000000");
435  if (color2RGBA(color, &BG_COL_) < 0) {
436      error("%s: ignoring illegal color '%s'", Name, color);
437  }
438  if (color)
439      free(color);
440  BG_COLOR = RGB_24to16(BG_COL_.R, BG_COL_.G, BG_COL_.B);
441
442  DCOLS = DCOLS / XRES;
443  DROWS = DROWS / YRES;
444  switch (yres_cfg) { /* font in ROM */
445  case 8:
446      switch (xres_cfg) {
447      case 6:
448    FONT = 0;
449    break;
450      case 8:
451    FONT = 1;
452    break;
453      default:
454    error("%s: unknown width in %s.Font '%s' from %s", Name, section, s, cfg_source());
455    return -1;
456      };
457      break;
458  case 12:
459      FONT = 2;
460      break;
461  case 16:
462      FONT = 3;
463      break;
464  case -1:
465      break;
466  default:
467      error("%s: unknown height in %s.Font '%s' from %s", Name, section, s, cfg_source());
468      return -1;
469  }
470    }
471
472    info("XRES=%i, YRES=%i;  DCOLS=%i, DROWS=%d;  FONT=%d, SECTOR=%i, SECTOR_SIZE=%i\n", XRES, YRES, DCOLS, DROWS,
473   FONT, SECTOR, SECTOR_SIZE);
474
475
476
477    cfg_number(section, "NoPowerCycle", 0, 1, 65536, &NOPOWERCYCLE);
478    if (!NOPOWERCYCLE) {
479  char powerOn[] = { 'Y', 3, 1 };
480  drv_D4D_send(powerOn, sizeof(powerOn));
481
482  /*char background[] = {'B', msb(BG_COLOR), lsb(BG_COLOR)};
483     drv_D4D_send(background, sizeof(background)); */
484    }
485
486    char opaque[] = { 'O', 1 };
487    drv_D4D_send(opaque, sizeof(opaque));
488
489    char penSize[] = { 'p', 0 };
490    drv_D4D_send(penSize, sizeof(penSize));
491
492
493    if (!MODE) {
494  unsigned char buffer[] = { 0x18, 0x24, 0x24, 0x18, 0, 0, 0, 0 };  /* degree */
495  drv_D4D_defchar(CHAR0, buffer);
496  CHARS--;
497  CHAR0++;
498    }
499
500    if (cfg_number(section, "Contrast", 0, 0, 255, &contrast) > 0) {
501  drv_D4D_contrast(contrast);
502    }
503
504    drv_D4D_clear();
505
506    return 0;
507}
508
509
510int drv_D4D_text_draw(WIDGET * W)
511{
512    short int FG_COLOR_old = FG_COLOR, BG_COLOR_old = BG_COLOR;
513    int ret;
514    RGBA fg, bg;
515    fg = W->fg_valid ? W->fg_color : FG_COL_;
516    bg = W->bg_valid ? W->bg_color : BG_COL_;
517    FG_COLOR = RGB_24to16(fg.R, fg.G, fg.B);
518    BG_COLOR = RGB_24to16(bg.R, bg.G, bg.B);
519    ret = drv_generic_text_draw(W);
520    FG_COLOR = FG_COLOR_old;
521    BG_COLOR = BG_COLOR_old;
522    return ret;
523}
524
525
526int lastVal[40 * 40 * 2]; /* ToDo: MAX_WIDGETS*2 */
527int drv_D4D_bar_draw(WIDGET * W)
528{
529    /* optimizations:
530       - draw filled rectangles instead of user defined bar characters
531       - cache last values */
532
533    WIDGET_BAR *Bar = W->data;
534    int row, col, len, res, max, val1, val2;
535    DIRECTION dir;
536    STYLE style;
537
538    row = W->row;
539    col = W->col;
540    dir = Bar->direction;
541    style = Bar->style;
542    len = Bar->length;
543
544    res = dir & (DIR_EAST | DIR_WEST) ? XRES : YRES;
545    max = len * res;
546    val1 = Bar->val1 * (double) (max);
547    val2 = Bar->val2 * (double) (max);
548
549    if (val1 < 1)
550  val1 = 1;
551    else if (val1 > max)
552  val1 = max;
553
554    if (val2 < 1)
555  val2 = 1;
556    else if (val2 > max)
557  val2 = max;
558
559    short int FG_COLOR_old = FG_COLOR, BG_COLOR_old = BG_COLOR;
560    RGBA fg, bg;
561    fg = W->fg_valid ? W->fg_color : FG_COL_;
562    bg = W->bg_valid ? W->bg_color : BG_COL_;
563    FG_COLOR = RGB_24to16(fg.R, fg.G, fg.B);
564    BG_COLOR = RGB_24to16(bg.R, bg.G, bg.B);
565
566    char cmd[9];
567    cmd[0] = 'r';
568    int val[2];
569    val[0] = val1;
570    val[1] = val2;
571    int i, vals;
572    int lastValIndex0 = row * DCOLS + col;
573    int cells = DROWS * DCOLS;
574    if (val1 == val2 && lastVal[lastValIndex0] == lastVal[lastValIndex0 + cells])
575  vals = 1;
576    else
577  vals = 2;
578    for (i = 0; i < vals; i++) {
579  int lastValIndex = lastValIndex0 + cells * (i /*+1 */ );
580  int y1 = row * YRES + (YRES / 2) * i;
581  cmd[2] = msb(y1);
582  cmd[3] = lsb(y1);
583  int y2 = y1 + (YRES) / vals - 1;
584  cmd[5] = msb(y2);
585  cmd[6] = lsb(y2);
586  if (val[i] > lastVal[lastValIndex]) {
587      cmd[1] = col * XRES + lastVal[lastValIndex];
588      cmd[4] = cmd[1] + val[i] - lastVal[lastValIndex] - 1;
589      cmd[7] = msb(FG_COLOR);
590      cmd[8] = lsb(FG_COLOR);
591      drv_D4D_send_extra(cmd, sizeof(cmd), 2, 5);
592  } else if (val[i] < lastVal[lastValIndex]) {
593      cmd[1] = col * XRES + val[i] - 1 + 1;
594      cmd[4] = cmd[1] + lastVal[lastValIndex] - val[i];
595      cmd[7] = msb(BG_COLOR);
596      cmd[8] = lsb(BG_COLOR);
597      drv_D4D_send_extra(cmd, sizeof(cmd), 2, 5);
598  }
599  lastVal[lastValIndex] = val[i];
600    }
601
602    FG_COLOR = FG_COLOR_old;
603    BG_COLOR = BG_COLOR_old;
604    return 0;
605}
606
607
608
609
610
611/****************************************/
612/***            plugins               ***/
613/****************************************/
614
615static void plugin_contrast(RESULT * result, RESULT * arg1)
616{
617    double contrast;
618
619    contrast = drv_D4D_contrast(R2N(arg1));
620    SetResult(&result, R_NUMBER, &contrast);
621}
622
623
624/****************************************/
625/***        widget callbacks          ***/
626/****************************************/
627
628
629/* using drv_generic_text_draw(W) */
630/* using drv_generic_text_icon_draw(W) */
631/* using drv_generic_text_bar_draw(W) */
632/* using drv_generic_gpio_draw(W) */
633
634
635/****************************************/
636/***        exported functions        ***/
637/****************************************/
638
639
640int drv_D4D_list(void)
641{
642    printf("4D Systems Display Graphics Modules");
643    return 0;
644}
645
646
647int drv_D4D_init(const char *section, const int quiet)
648{
649    /* error("drv_D4D_init()"); */
650    WIDGET_CLASS wc;
651    int ret;
652
653    info("%s: %s", Name, "$Rev$");
654
655    if ((ret = drv_D4D_start(section)) != 0)
656  return ret;
657
658    if (EXTRA)
659  CHARS = 64;
660    else
661  CHARS = 32;
662    CHAR0 = 128;
663    GOTO_COST = 7;
664
665    if (MODE) {
666  drv_generic_graphic_real_blit = drv_D4D_blit;
667    } else {
668  drv_generic_text_real_write = drv_D4D_write;
669  drv_generic_text_real_defchar = drv_D4D_defchar;
670    }
671
672
673
674
675    if (!quiet) {
676  char buffer[40];
677  qprintf(buffer, sizeof(buffer), "%s %dx%d", NAME_, DCOLS, DROWS);
678  if (MODE) {
679      if (drv_generic_graphic_greet(buffer, "www.4dsystems.com.au")) {
680    sleep(3);
681    drv_generic_graphic_clear();
682      }
683  } else {
684      if (drv_generic_text_greet(buffer, "www.4dsystems.com.au")) {
685    sleep(3);
686    drv_D4D_clear();
687      }
688  }
689    }
690
691
692    if (MODE) {
693  if ((ret = drv_generic_graphic_init(section, Name)) != 0)
694      return ret;
695    } else {
696
697  if ((ret = drv_generic_text_init(section, Name)) != 0)
698      return ret;
699
700  if ((ret = drv_generic_text_icon_init()) != 0)
701      return ret;
702
703  if ((ret = drv_generic_text_bar_init(0)) != 0)
704      return ret;
705
706  drv_generic_text_bar_add_segment(0, 0, 255, 32);
707
708
709  wc = Widget_Text;
710  wc.draw = drv_D4D_text_draw;
711  widget_register(&wc);
712
713  wc = Widget_Icon;
714  wc.draw = drv_generic_text_icon_draw;
715  widget_register(&wc);
716
717  wc = Widget_Bar;
718  wc.draw = drv_D4D_bar_draw;
719  widget_register(&wc);
720
721    }
722
723    AddFunction("LCD::contrast", 1, plugin_contrast);
724
725    return 0;
726}
727
728
729int drv_D4D_quit(const int quiet)
730{
731    /* error("drv_D4D_quit()"); */
732
733    info("%s: shutting down."