aboutsummaryrefslogtreecommitdiffstats
path: root/drv_Crystalfontz.c
blob: db7b6cc8d7310e1abca18437d5d45ca957582c73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
pre { line-height: 125%; margin: 0; }
td.linenos pre { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }
span.linenos { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }
td.linenos pre.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* $Id: drv_USBHUB.c,v 1.3 2006/08/14 05:54:04 reinelt Exp $
 *
 * new style driver for USBLCD displays
 *
 * Copyright (C) 2006 Ernst Bachmann <e.bachmann@xebec.de>
 * Copyright (C) 2004,2006 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * Based on the USBLCD driver Copyright (C) 2003 Michael Reinelt <reinelt@eunet.at>
 *
 * This file is part of LCD4Linux.
 *
 * LCD4Linux is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * LCD4Linux is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * $Log: drv_USBHUB.c,v $
 * Revision 1.3  2006/08/14 05:54:04  reinelt
 * minor warnings fixed, CFLAGS changed (no-strict-aliasing)
 *
 * Revision 1.2  2006/08/13 06:46:51  reinelt
 * T6963 soft-timing & enhancements; indent
 *
 * Revision 1.1  2006/08/08 19:35:22  reinelt
 * USBHUB driver from Ernst Bachmann
 *
 *
 */

/* 
 *
 * exported fuctions:
 *
 * struct DRIVER drv_USBHUB
 *
 */

#include "config.h"

#ifdef HAVE_USB_H
# include <usb.h>
#else
# error The USB-HUB driver only makes sense with USB support
#endif

#include "debug.h"
#include "cfg.h"
#include "qprintf.h"
#include "udelay.h"
#include "drv.h"
#include "drv_generic_gpio.h"



#define HUB_CONTROL_PORT 0x23
#define HUB_SET_FEATURE 3
#define HUB_SET_INDICATOR 22

static char Name[] = "USBHUB";

/* TODO: Better not specify defaults here, 
 * instead look for the first suitable HUB arround if
 * no Vendor/Product specified in config.
 */

static int hubVendor = 0x0409;
static int hubProduct = 0x0058;

static usb_dev_handle *hub = NULL;

typedef struct _usb_hub_descriptor {
    u_int8_t bLength;
    u_int8_t bDescriptorType;
    u_int8_t nNbrPorts;
    u_int8_t wHubCharacteristicLow;
    u_int8_t wHubCharacteristicHigh;
    u_int8_t bPwrOn2PwrGood;
    u_int8_t bHubContrCurrent;
    u_int8_t deviceRemovable;
    u_int8_t PortPwrCtrlMask[8];
} usb_hub_descriptor;

/****************************************/
/***  hardware dependant functions    ***/
/****************************************/


static int drv_UH_open(void)
{
    struct usb_bus *busses, *bus;
    struct usb_device *dev;

    hub = NULL;

    info("%s: scanning for an USB HUB (0x%04x:0x%04x)...", Name, hubVendor, hubProduct);

    usb_init();
    usb_find_busses();
    usb_find_devices();
    busses = usb_get_busses();

    for (bus = busses; bus; bus = bus->next) {
	for (dev = bus->devices; dev; dev = dev->next) {
	    if ((dev->descriptor.idVendor == hubVendor) && (dev->descriptor.idProduct == hubProduct)) {

		unsigned int v = dev->descriptor.bcdDevice;

		info("%s: found USBHUB V%1d%1d.%1d%1d on bus %s device %s", Name,
		     (v & 0xF000) >> 12, (v & 0xF00) >> 8, (v & 0xF0) >> 4, (v & 0xF), bus->dirname, dev->filename);

		if (dev->descriptor.bDeviceClass != USB_CLASS_HUB) {
		    error("%s: the specified device claims to be no HUB");
		    return -1;
		}

		hub = usb_open(dev);
		if (!hub) {
		    error("%s: usb_open() failed!", Name);
		    return -1;
		}
		return 0;
	    }
	}
    }
    error("%s: could not find a USB HUB", Name);
    return -1;
}


static int drv_UH_close(void)
{
    debug("closing USB handle");

    usb_close(hub);

    return 0;
}


/*
 * Set the Indicator status on port "num+1" to val.
 * according to the USB Specification, the following values would be allowed:
 *
 *   0 : Automatic color (display link state etc)
 *   1 : Amber
 *   2 : Green
 *   3 : Off
 *   4..255: Reserved
 *
 */

static int drv_UH_set(const int num, const int val)
{
    int ret;

    if (!hub)
	return -1;

    if (val < 0 || val > 3) {
	info("%s: value %d out of range (0..3)", Name, val);
	return -1;
    }

    if ((ret = usb_control_msg(hub,
			       HUB_CONTROL_PORT,
			       HUB_SET_FEATURE, HUB_SET_INDICATOR, (val << 8) | (num + 1), NULL, 0, 1000)) != 0) {
	info("%s: usb_control_msg failed with %d", Name, ret);
	return -1;
    }

    return 0;
}


static int drv_UH_start(const char *section, const __attribute__ ((unused)) int quiet)
{
    char *buf;

    usb_hub_descriptor hub_desc;
    int ret;


    buf = cfg_get(section, "Vendor", NULL);
    if (buf) {
	if (!*buf) {
	    error("%s: Strange Vendor Specification");
	    return -1;
	}
	if (sscanf(buf, "0x%x", &hubVendor) != 1) {
	    error("%s: Strange Vendor Specification: [%s]", buf);
	    return -1;
	}
    }

    buf = cfg_get(section, "Product", NULL);
    if (buf) {
	if (!*buf) {
	    error("%s: Strange Product Specification");
	    return -1;
	}
	if (sscanf(buf, "0x%x", &hubProduct) != 1) {
	    error("%s: Strange Product Specification: [%s]", buf);
	    return -1;
	}
    }

    if (drv_UH_open() < 0) {
	return -1;
    }


    if ((ret = usb_control_msg(hub,
			       USB_ENDPOINT_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE,
			       USB_REQ_GET_DESCRIPTOR, USB_DT_HUB << 8, 0, (char *) &hub_desc, sizeof(hub_desc),
			       1000)) <= 8) {
	error("%s: hub_get_descriptor failed with %d", Name, ret);
	drv_UH_close();
	return -1;
    }
    GPOS = hub_desc.nNbrPorts;
    debug("%s: HUB claims to have %d ports. Configuring them as GPOs", Name, GPOS);
    if (!(hub_desc.wHubCharacteristicLow & 0x80)) {
	error("%s: HUB claims to have no Indicator LEDs (Characteristics 0x%04x). Bailing out.", Name,
	      (hub_desc.wHubCharacteristicHigh << 8) | hub_desc.wHubCharacteristicLow);
	/* The HUB Tells us that there are no LEDs to control. Breaking? Maybe don't trust it and continue anyways? */
	drv_UH_close();
	return -1;

    }

    return 0;
}


/****************************************/
/***            plugins               ***/
/****************************************/

/* none at the moment... */


/****************************************/
/***        widget callbacks          ***/
/****************************************/


/* using drv_generic_text_draw(W) */
/* using drv_generic_text_icon_draw(W) */
/* using drv_generic_text_bar_draw(W) */


/****************************************/
/***        exported functions        ***/
/****************************************/


/* list models */
int drv_UH_list(void)
{
    printf("generic");
    return 0;
}


/* initialize driver & display */
int drv_UH_init(const char *section, const int quiet)
{
    int ret;
    int i;

    info("%s: %s", Name, "$Revision: 1.3 $");



    /* start display */
    if ((ret = drv_UH_start(section, quiet)) != 0)
	return ret;


    /* real worker functions */<
/* $Id: drv_Crystalfontz.c,v 1.44 2006/07/19 01:35:31 cmay Exp $
 *
 * new style driver for Crystalfontz display modules
 *
 * Copyright (C) 1999, 2000 Michael Reinelt <reinelt@eunet.at>
 * Copyright (C) 2004 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * This file is part of LCD4Linux.
 *
 * LCD4Linux is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * LCD4Linux is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * $Log: drv_Crystalfontz.c,v $
 * Revision 1.44  2006/07/19 01:35:31  cmay
 * Renamed keypad direction names to avoid conflict with Curses library defs.
 * Added keypad support to Curses display driver.
 *
 * Revision 1.43  2006/07/14 20:15:11  reinelt
 * buffer too small (thanks to anonymous)
 *
 * Revision 1.42  2006/02/22 15:59:39  cmay
 * removed KEYPADSIZE cruft per harbaum's suggestion
 *
 * Revision 1.41  2006/02/21 15:52:30  cmay
 * added back CF635 GPO counts in model struct lost after last merge
 *
 * Revision 1.40  2006/02/21 05:50:34  reinelt
 * keypad support from Cris Maj
 *
 * Revision 1.39  2006/02/19 15:37:38  reinelt
 * CF635 GPO patch from cmaj
 *
 * Revision 1.38  2006/01/30 06:25:49  reinelt
 * added CVS Revision
 *
 * Revision 1.37  2006/01/06 08:12:19  reinelt
 * GPO's for Crystalfontz
 *
 * Revision 1.36  2005/09/07 06:51:44  reinelt
 * Support for CF635 added
 *
 * Revision 1.35  2005/08/21 08:18:56  reinelt
 * CrystalFontz ACK processing
 *
 * Revision 1.34  2005/05/08 04:32:44  reinelt
 * CodingStyle added and applied
 *
 * Revision 1.33  2005/04/02 05:28:58  reinelt
 * fixed gcc4 warnings about signed/unsigned mismatches
 *
 * Revision 1.32  2005/03/23 12:23:35  reinelt
 * fixed some signed/unsigned char mismatches in the Crystalfontz driver (ticket #12)
 *
 * Revision 1.31  2005/02/24 07:06:48  reinelt
 * SimpleLCD driver added
 *
 * Revision 1.30  2005/01/18 06:30:22  reinelt
 * added (C) to all copyright statements
 *
 * Revision 1.29  2004/06/26 12:04:59  reinelt
 *
 * uh-oh... the last CVS log message messed up things a lot...
 *
 * Revision 1.28  2004/06/26 09:27:20  reinelt
 *
 * added '-W' to CFLAGS
 * changed all C++ comments to C ones
 * cleaned up a lot of signed/unsigned mistakes
 *
 * Revision 1.27  2004/06/20 10:09:54  reinelt
 *
 * 'const'ified the whole source
 *
 * Revision 1.26  2004/06/06 06:51:59  reinelt
 *
 * do not display end splash screen if quiet=1
 *
 * Revision 1.25  2004/06/05 06:41:39  reinelt
 *
 * chancged splash screen again
 *
 * Revision 1.24  2004/06/05 06:13:11  reinelt
 *
 * splash screen for all text-based display drivers
 *
 * Revision 1.23  2004/06/02 09:41:19  reinelt
 *
 * prepared support for startup splash screen
 *
 * Revision 1.22  2004/06/02 05:56:25  reinelt
 *
 * extended contrast range for Crystalfontz
 *
 * Revision 1.21  2004/06/01 06:45:28  reinelt
 *
 * some Fixme's processed
 * documented some code
 *
 * Revision 1.20  2004/05/31 05:38:02  reinelt
 *
 * fixed possible bugs with user-defined chars (clear high bits)
 * thanks to Andy Baxter for debugging the MilfordInstruments driver!
 *
 * Revision 1.19  2004/05/30 08:25:50  reinelt
 *
 * Crystalfontz 631 driver finished
 *
 * Revision 1.18  2004/05/28 13:51:42  reinelt
 *
 * ported driver for Beckmann+Egle Mini-Terminals
 * added 'flags' parameter to serial_init()
 *
 * Revision 1.17  2004/05/27 03:39:47  reinelt
 *
 * changed function naming scheme to plugin::function
 *
 * Revision 1.16  2004/05/26 11:37:36  reinelt
 *
 * Curses driver ported.
 *
 * Revision 1.15  2004/05/25 14:26:29  reinelt
 *
 * added "Image" driver (was: Raster.c) for PPM and PNG creation
 * fixed some glitches in the X11 driver
 *
 * Revision 1.14  2004/03/19 09:17:46  reinelt
 *
 * removed the extra 'goto' function, row and col are additional parameters
 * of the write() function now.
 *
 * Revision 1.13  2004/03/03 03:41:02  reinelt
 * Crystalfontz Contrast issue fixed
 *
 * Revision 1.12  2004/03/01 04:29:51  reinelt
 * cfg_number() returns -1 on error, 0 if value not found (but default val used),
 *  and 1 if value was used from the configuration.
 * HD44780 driver adopted to new cfg_number()
 * Crystalfontz 631 driver nearly finished
 *
 * Revision 1.11  2004/02/14 11:56:17  reinelt
 * M50530 driver ported
 * changed lots of 'char' to 'unsigned char'
 *
 * Revision 1.10  2004/02/05 07:10:23  reinelt
 * evaluator function names are no longer case-sensitive
 * Crystalfontz Fan PWM control, Fan RPM monitoring, temperature monitoring
 *
 * Revision 1.9  2004/02/04 19:10:51  reinelt
 * Crystalfontz driver nearly finished
 *
 * Revision 1.8  2004/02/01 08:05:12  reinelt
 * Crystalfontz 633 extensions (CRC checking and stuff)
 * Models table for HD44780
 * Noritake VFD BVrightness patch from Bill Paxton
 *
 * Revision 1.7  2004/01/30 20:57:56  reinelt
 * HD44780 patch from Martin Hejl
 * dmalloc integrated
 *
 * Revision 1.6  2004/01/29 04:40:02  reinelt
 * every .c file includes "config.h" now
 *
 * Revision 1.5  2004/01/25 05:30:09  reinelt
 * plugin_netdev for parsing /proc/net/dev added
 *
 * Revision 1.4  2004/01/23 07:04:03  reinelt
 * icons finished!
 *
 * Revision 1.3  2004/01/23 04:53:34  reinelt
 * icon widget added (not finished yet!)
 *
 * Revision 1.2  2004/01/22 07:57:45  reinelt
 * several bugs fixed where segfaulting on layout>display
 * Crystalfontz driver optimized, 632 display already works
 *
 * Revision 1.1  2004/01/21 12:36:19  reinelt
 * Crystalfontz NextGeneration driver added
 *
 */

/* 
 *
 * exported fuctions:
 *
 * struct DRIVER drv_Crystalfontz
 *
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

#include "debug.h"
#include "cfg.h"
#include "qprintf.h"
#include "thread.h"
#include "timer.h"
#include "plugin.h"
#include "widget.h"
#include "widget_text.h"
#include "widget_icon.h"
#include "widget_bar.h"
#include "widget_keypad.h"
#include "drv.h"
#include "drv_generic_text.h"
#include "drv_generic_gpio.h"
#include "drv_generic_serial.h"
#include "drv_generic_keypad.h"


static char Name[] = "Crystalfontz";

static int Model;
static int Protocol;
static int Payload;

/* ring buffer for bytes received from the display */
static unsigned char RingBuffer[256];
static unsigned int RingRPos = 0;
static unsigned int RingWPos = 0;

/* packet from the display */
struct {
    unsigned char type;
    unsigned char code;
    unsigned char size;
    unsigned char data[16 + 1];	/* trailing '\0' */
} Packet;

/* Line Buffer for 633 displays */
static unsigned char Line[2 * 16];

/* Fan RPM */
static double Fan_RPM[4] = { 0.0, };

/* Temperature sensors */
static double Temperature[32] = { 0.0, };


typedef struct {
    int type;
    char *name;
    int rows;
    int cols;
    int gpis;
    int gpos;
    int protocol;
    int payload;
} MODEL;

/* Fixme #1: number of GPI's & GPO's should be verified */
/* Fixme #2: protocol should be verified */
/* Fixme #3: number of keys on the keypad should be verified */

static MODEL Models[] = {
    {626, "626", 2, 16, 0, 0, 1, 0},
    {631, "631", 2, 20, 0, 0, 3, 22},
    {632, "632", 2, 16, 0, 0, 1, 0},
    {633, "633", 2, 16, 4, 4, 2, 18},
    {634, "634", 4, 20, 0, 0, 1, 0},
    {635, "635", 4, 20, 4, 12, 3, 22},
    {636, "636", 2, 16, 0, 0, 1, 0},
    {-1, "Unknown", -1, -1, 0, 0, 0, 0}
};


/****************************************/
/***  hardware dependant functions    ***/
/****************************************/

/* x^0 + x^5 + x^12 */
#define CRCPOLY 0x8408

static unsigned short CRC(const unsigned char *p, size_t len, unsigned short seed)
{
    int i;
    while (len--) {
	seed ^= *p++;
	for (i = 0; i < 8; i++)
	    seed = (seed >> 1) ^ ((seed & 1) ? CRCPOLY : 0);
    }
    return ~seed;
}

static unsigned char LSB(const unsigned short word)
{
    return word & 0xff;
}

static unsigned char MSB(const unsigned short word)
{
    return word >> 8;
}


static unsigned char byte(unsigned int pos)
{
    pos += RingRPos;
    if (pos >= sizeof(RingBuffer))
	pos -= sizeof(RingBuffer);
    return RingBuffer[pos];
}


static void drv_CF_process_packet(void)
{

    switch (Packet.type) {

    case 0x02:

	/* async response from display to host */
	switch (Packet.code) {

	case 0x00:
	    /* Key Activity */
	    debug("Key Activity: %d", Packet.data[0]);
	    drv_generic_keypad_press(Packet.data[0]);
	    break;

	case 0x01:
	    /* Fan Speed Report */
	    if (Packet.data[1] == 0xff) {
		Fan_RPM[Packet.data[0]] = -1.0;
	    } else if (Packet.data[1] < 4) {
		Fan_RPM[Packet.data[0]] = 0.0;
	    } else {
		Fan_RPM[Packet.data[0]] =
		    (double) 27692308L *(Packet.data[1] - 3) / (Packet.data[2] + 256 * Packet.data[3]);
	    }
	    break;

	case 0x02:
	    /* Temperature Sensor Report */
	    switch (Packet.data[3]) {
	    case 0:
		error("%s: 1-Wire device #%d: CRC error", Name, Packet.data[0]);
		break;
	    case 1:
	    case 2:
		Temperature[Packet.data[0]] = (Packet.data[1] + 256 * Packet.data[2]) / 16.0;
		break;
	    default:
		error("%s: 1-Wire device #%d: unknown CRC status %d", Name, Packet.data[0], Packet.data[3]);
		break;
	    }
	    break;

	default:
	    /* this should not happen */
	    error("%s: unexpected response type=0x%02x code=0x%02x size=%d", Packet.type, Packet.code, Packet.size);
	    break;
	}

	break;

    case 0x03:
	/* error response from display to host */
	error("%s: error response type=0x%02x code=0x%02x size=%d", Packet.type, Packet.code, Packet.size);
	break;

    default:
	/* these should not happen: */
	/* type 0x00: command from host to display: should never come back */
	/* type 0x01: command response from display to host: are processed within send() */
	error("%s: unexpected packet type=0x%02x code=0x%02x size=%d", Packet.type, Packet.code, Packet.size);
	break;
    }

}


static int drv_CF_poll(void)
{
    /* read into RingBuffer */
    while (1) {
	char buffer[32];
	int num, n;
	num = drv_generic_serial_poll(buffer, sizeof(buffer));
	if (num <= 0)
	    break;
	/* put result into RingBuffer */
	for (n = 0; n < num; n++) {
	    RingBuffer[RingWPos++] = (unsigned char) buffer[n];
	    if (RingWPos >= sizeof(RingBuffer))
		RingWPos = 0;
	}
    }

    /* process RingBuffer */
    while (1) {
	unsigned char buffer[32];
	int n, num, size;
	unsigned short crc;
	/* packet size */
	num = RingWPos - RingRPos;
	if (num < 0)
	    num += sizeof(RingBuffer);
	/* minimum packet size=4 */
	if (num < 4)
	    return 0;
	/* valid response types: 01xxxxx 10.. 11.. */
	/* therefore: 00xxxxxx is invalid */
	if (byte(0) >> 6 == 0)
	    goto GARBAGE;
	/* command length */
	size = byte(1);
	/* valid command length is 0 to 16 */
	if (size > 16)
	    goto GARBAGE;
	/* all bytes available? */
	if (num < size + 4)
	    return 0;
	/* check CRC */
	for (n = 0; n < size + 4; n++)
	    buffer[n] = byte(n);
	crc = CRC(buffer, size + 2, 0xffff);
	if (LSB(crc) != buffer[size + 2])
	    goto GARBAGE;
	if (MSB(crc) != buffer[size + 3])
	    goto GARBAGE;
	/* process packet */
	Packet.type = buffer[0] >> 6;
	Packet.code = buffer[0] & 0x3f;
	Packet.size = size;
	memcpy(Packet.data, buffer + 2, size);
	Packet.data[size] = '\0';	/* trailing zero */
	/* increment read pointer */
	RingRPos += size + 4;
	if (RingRPos >= sizeof(RingBuffer))
	    RingRPos -= sizeof(RingBuffer);
	/* a packet arrived */
	return 1;
      GARBAGE:
	debug("dropping garbage byte %02x", byte(0));
	RingRPos++;
	if (RingRPos >= sizeof(RingBuffer))
	    RingRPos = 0;
	continue;
    }

    /* not reached */
    return 0;
}


static void drv_CF_timer(void __attribute__ ((unused)) * notused)
{
    while (drv_CF_poll()) {
	drv_CF_process_packet();
    }
}


static void drv_CF_send(const unsigned char cmd, const unsigned char len, const unsigned char *data)
{
    /* 1 cmd + 1 len + 22 payload + 2 crc = 26 */   
    unsigned char buffer[26];
    unsigned short crc;
    struct timeval now, end;

    if (len > Payload) {
	error("%s: internal error: packet length %d exceeds payload size %d", Name, len, Payload);
	return;
    }

    buffer[0] = cmd;
    buffer[1] = len;
    memcpy(buffer + 2, data, len);
    crc = CRC(buffer, len + 2, 0xffff);
    buffer[len + 2] = LSB(crc);
    buffer[len + 3] = MSB(crc);

    drv_generic_serial_write((char *) buffer, len + 4);

    /* wait for acknowledge packet */
    gettimeofday(&now, NULL);
    while (1) {
	/* delay 1 msec */
	usleep(1 * 1000);
	if (drv_CF_poll()) {
	    if (Packet.type == 0x01 && Packet.code == cmd) {
		/* this is the ack we're waiting for */
		if (0) {
		    gettimeofday(&end, NULL);
		    debug("%s: ACK after %d usec", Name,
			  1000000 * (end.tv_sec - now.tv_sec) + end.tv_usec - now.tv_usec);
		}
		break;
	    } else {
		/* some other (maybe async) packet, just process it */
		drv_CF_process_packet();
	    }
	}
	gettimeofday(&end, NULL);
	/* don't wait more than 250 msec */
	if ((1000000 * (end.tv_sec - now.tv_sec) + end.tv_usec - now.tv_usec) > 250 * 1000) {
	    error("%s: timeout waiting for response to cmd 0x%02x", Name, cmd);
	    break;
	}
    }
}


static void drv_CF_write1(const int row, const int col, const char *data, const int len)
{
    char cmd[3] = "\021xy";	/* set cursor position */

    if (row == 0 && col == 0) {
	drv_generic_serial_write("\001", 1);	/* cursor home */
    } else {
	cmd[1] = (char) col;
	cmd[2] = (char) row;
	drv_generic_serial_write(cmd, 3);
    }

    drv_generic_serial_write(data, len);
}


static void drv_CF_write2(const int row, const int col, const char *data, const int len)
{
    int l = len;

    /* limit length */
    if (col + l > 16)
	l = 16 - col;
    if (l < 0)
	l = 0;

    /* sanity check */
    if (row >= 2 || col + l > 16) {
	error("%s: internal error: write outside linebuffer bounds!", Name);
	return;
    }
    memcpy(Line + 16 * row + col, data, l);
    drv_CF_send(7 + row, 16, (unsigned char *) (Line + 16 * row));
}


static void drv_CF_write3(const int row, const int col, const char *data, const int len)
{
    int l = len;
    unsigned char cmd[23];

    /* limit length */
    if (col + l > DCOLS)
	l = DCOLS - col;
    if (l < 0)
	l = 0;

    /* sanity check */
    if (row >= DROWS || col + l > DCOLS) {
	error("%s: internal error: write outside display bounds!", Name);
	return;
    }

    cmd[0] = col;
    cmd[1] = row;
    memcpy(cmd + 2, data, l);

    drv_CF_send(31, l + 2, cmd);

}


static void drv_CF_defchar1(const int ascii, const unsigned char *matrix)
{
    int i;
    char cmd[10] = "\031n";	/* set custom char bitmap */

    /* user-defineable chars start at 128, but are defined at 0 */
    cmd[1] = (char) (ascii - CHAR0);
    for (i = 0; i < 8; i++) {
	cmd[i + 2] = matrix[i] & 0x3f;
    }
    drv_generic_serial_write(cmd, 10);
}


static void drv_CF_defchar23(const int ascii, const unsigned char *matrix)
{
    int i;
    unsigned char buffer[9];

    /* user-defineable chars start at 128, but are defined at 0 */
    buffer[0] = (char) (ascii - CHAR0);

    /* clear bit 6 and 7 of the bitmap (blinking) */
    for (i = 0; i < 8; i++) {
	buffer[i + 1] = matrix[i] & 0x3f;
    }

    drv_CF_send(9, 9, buffer);
}


static int drv_CF_contrast(int contrast)
{
    static unsigned char Contrast = 0;
    char buffer[2];

    /* -1 is used to query the current contrast */
    if (contrast == -1)
	return Contrast;

    if (contrast < 0)
	contrast = 0;
    if (contrast > 255)
	contrast = 255;
    Contrast = contrast;

    switch (Protocol) {

    case 1:
	/* contrast range 0 to 100 */
	if (Contrast > 100)
	    Contrast = 100;
	buffer[0] = 15;		/* Set LCD Contrast */
	buffer[1] = Contrast;
	drv_generic_serial_write(buffer, 2);
	break;

    case 2:
	/* contrast range 0 to 50 */
	if (Contrast > 50)
	    Contrast = 50;
	drv_CF_send(13, 1, &Contrast);
	break;

    case 3:
	/* contrast range 0 to 255 */
	drv_CF_send(13, 1, &Contrast);
	break;
    }

    return Contrast;
}


static int drv_CF_backlight(int backlight)
{
    static unsigned char Backlight = 0;
    char buffer[2];

    /* -1 is used to query the current backlight */
    if (backlight == -1)
	return Backlight;

    if (backlight < 0)
	backlight = 0;
    if (backlight > 100)
	backlight = 100;
    Backlight = backlight;

    switch (Protocol) {

    case 1:
	buffer[0] = 14;		/* Set LCD Backlight */
	buffer[1] = Backlight;
	drv_generic_serial_write(buffer, 2);
	break;

    case 2:
    case 3:
	drv_CF_send(14, 1, &Backlight);
	break;
    }

    return Backlight;
}


static int drv_CF_keypad(const int num)
{
    int val = 0;

    switch (Protocol) {
    case 1:
    case 2:
	break;
    case 3:
	if (num < 8)
	    val = WIDGET_KEY_PRESSED;
	else
	    val = WIDGET_KEY_RELEASED;
	switch (num) {
	case 1:
	case 8:
	    val += WIDGET_KEY_UP;
	    break;
	case 2:
	case 9:
	    val += WIDGET_KEY_DOWN;
	    break;
	case 3:
	case 10:
	    val += WIDGET_KEY_LEFT;
	    break;
	case 4:
	case 11:
	    val += WIDGET_KEY_RIGHT;
	    break;
	case 5:
	case 12:
	    val += WIDGET_KEY_CONFIRM;
	    break;
	case 7:
	case 13:
	    val += WIDGET_KEY_CANCEL;
	    break;
	}
	break;
    }

    return val;
}


static int drv_CF_GPI(const int num)
{
    if (num < 0 || num > 3) {
	return 0;
    }
    return Fan_RPM[num];
}


static int drv_CF_GPO(const int num, const int val)
{
    static unsigned char PWM2[4] = { 0, 0, 0, 0 };
    static unsigned char PWM3[2];

    int v = val;

    if (v < 0)
	v = 0;
    if (v > 100)
	v = 100;

    switch (Protocol) {
    case 2:
	PWM2[num] = v;
	drv_CF_send(17, 4, PWM2);
	break;
    case 3:
	PWM3[0] = num + 1;
	PWM3[1] = v;
	drv_CF_send(34, 2, PWM3);
	break;
    }

    return v;
}


static int drv_CF_autodetect(void)
{
    int m;

    /* only autodetect newer displays */
    if (Protocol < 2)
	return -1;

    /* read display type */
    drv_CF_send(1, 0, NULL);

    /* send() did already wait for response packet */
    if (Packet.type == 0x01 && Packet.code == 0x01) {
	char t[7], c;
	float h, v;
	info("%s: display identifies itself as '%s'", Name, Packet.data);
	if (sscanf((char *) Packet.data, "%6s:h%f,%c%f", t, &h, &c, &v) != 4) {
	    error("%s: error parsing display identification string", Name);
	    return -1;
	}
	info("%s: display type '%s', hardware version %3.1f, firmware version %c%3.1f", Name, t, h, c, v);
	if (strncmp(t, "CFA", 3) == 0) {
	    for (m = 0; Models[m].type != -1; m++) {
		/* omit the 'CFA' */
		if (strcasecmp(Models[m].name, t + 3) == 0)
		    return m;
	    }
	}
	error("%s: display type '%s' may be not supported!", Name, t);
	return -1;
    }

    error("%s: display detection failed!", Name);

    return -1;
}


static char *drv_CF_print_ROM(void)
{
    static char buffer[17];

    snprintf(buffer, sizeof(buffer), "0x%02x%02x%02x%02x%02x%02x%02x%02x",
	     Packet.data[1], Packet.data[2], Packet.data[3], Packet.data[4], Packet.data[5], Packet.data[6],
	     Packet.data[7], Packet.data[8]);

    return buffer;
}


static int drv_CF_scan_DOW(unsigned char index)
{
    int i;

    /* Read DOW Device Information */
    drv_CF_send(18, 1, &index);

    i = 0;
    while (1) {
	/* wait 10 msec */
	usleep(10 * 1000);
	/* packet available? */
	if (drv_CF_poll()) {
	    /* DOW Device Info */
	    if (Packet.type == 0x01 && Packet.code == 0x12) {
		switch (Packet.data[1]) {
		case 0x00:
		    /* no device found */
		    return 0;
		case 0x22:
		    info("%s: 1-Wire device #%d: DS1822 temperature sensor found at %s", Name, Packet.data[0],
			 drv_CF_print_ROM());
		    return 1;
		case 0x28:
		    info("%s: 1-Wire device #%d: DS18B20 temperature sensor found at %s", Name, Packet.data[0],
			 drv_CF_print_ROM());
		    return 1;
		default:
		    info("%s: 1-Wire device #%d: unknown device found at %s", Name, Packet.data[0], drv_CF_print_ROM());
		    return 0;
		}
	    } else {
		drv_CF_process_packet();
	    }
	}
	/* wait no longer than 300 msec */
	if (++i > 30) {
	    error("%s: 1-Wire device #%d detection timed out", Name, index);
	    return -1;
	}
    }

    /* not reached */
    return -1;
}


/* clear display */
static void drv_CF_clear(void)
{
    switch (Protocol) {
    case 1:
	drv_generic_serial_write("\014", 1);
	break;
    case 2:
    case 3:
	drv_CF_send(6, 0, NULL);
	break;
    }
}


/* init sequences for 626, 632, 634, 636  */
static void drv_CF_start_1(void)
{
    drv_generic_serial_write("\014", 1);	/* Form Feed (Clear Display) */
    drv_generic_serial_write("\004", 1);	/* hide cursor */
    drv_generic_serial_write("\024", 1);	/* scroll off */
    drv_generic_serial_write("\030", 1);	/* wrap off */
}


/* init sequences for 633 */
static void drv_CF_start_2(void)
{
    int i;
    unsigned long mask;
    unsigned char buffer[4];

    /* Clear Display */
    drv_CF_send(6, 0, NULL);

    /* Set LCD Cursor Style */
    buffer[0] = 0;
    drv_CF_send(12, 1, buffer);

    /* enable Fan Reporting */
    buffer[0] = 15;
    drv_CF_send(16, 1, buffer);

    /* Set Fan Power to 100% */
    buffer[0] = buffer[1] = buffer[2] = buffer[3] = 100;
    drv_CF_send(17, 4, buffer);

    /* Read DOW Device Information */
    mask = 0;
    for (i = 0; i < 32; i++) {
	if (drv_CF_scan_DOW(i) == 1) {
	    mask |= 1 << i;
	}
    }

    /* enable Temperature Reporting */
    buffer[0] = mask & 0xff;
    buffer[1] = (mask >> 8) & 0xff;
    buffer[2] = (mask >> 16) & 0xff;
    buffer[3] = (mask >> 24) & 0xff;
    drv_CF_send(19, 4, buffer);
}


/* init sequences for 631 */
static void drv_CF_start_3(void)
{
    unsigned char buffer[1];

    /* Clear Display */
    drv_CF_send(6, 0, NULL);

    /* Set LCD Cursor Style */
    buffer[0] = 0;
    drv_CF_send(12, 1, buffer);

}


static int drv_CF_start(const char *section)
{
    int i;
    char *model;

    model = cfg_get(section, "Model", NULL);
    if (model != NULL && *model != '\0') {
	for (i = 0; Models[i].type != -1; i++) {
	    if (strcasecmp(Models[i].name, model) == 0)
		break;
	}
	if (Models[i].type == -1) {
	    error("%s: %s.Model '%s' is unknown from %s", Name, section, model, cfg_source());
	    return -1;
	}
	Model = i;
	Protocol = Models[Model].protocol;
	info("%s: using model '%s'", Name, Models[Model].name);
    } else {
	Model = -1;
	Protocol = 2;		/*auto-detect only newer displays */
	info("%s: no '%s.Model' entry from %s, auto-detecting", Name, section, cfg_source());
    }

    /* open serial port */
    if (drv_generic_serial_open(section, Name, 0) < 0)
	return -1;

    /* Fixme: why such a large delay? */
    usleep(350 * 1000);

    /* display autodetection */
    i = drv_CF_autodetect();
    if (Model == -1)
	Model = i;
    if (Model == -1) {
	error("%s: auto-detection failed, please specify a '%s.Model' entry in %s", Name, section, cfg_source());
	return -1;
    }
    if (i != -1 && Model != i) {
	error("%s: %s.Model '%s' from %s does not match detected model '%s'", Name, section, model, cfg_source(),
	      Models[i].name);
	return -1;
    }

    /* initialize global variables */
    DROWS = Models[Model].rows;
    DCOLS = Models[Model].cols;
    GPIS = Models[Model].gpis;
    GPOS = Models[Model].gpos;
    Protocol = Models[Model].protocol;
    Payload = Models[Model].payload;


    switch (Protocol) {

    case 1:
	drv_CF_start_1();
	break;

    case 2:
	/* regularly process display answers */
	/* Fixme: make 100msec configurable */
	timer_add(drv_CF_timer, NULL, 100, 0);
	drv_CF_start_2();
	/* clear 633 linebuffer */
	memset(Line, ' ', sizeof(Line));
	break;

    case 3:
	/* regularly process display answers */
	/* Fixme: make 100msec configurable */
	timer_add(drv_CF_timer, NULL, 100, 0);
	drv_CF_start_3();
	break;
    }

    /* set contrast */
    if (cfg_number(section, "Contrast", 0, 0, 255, &i) > 0) {
	drv_CF_contrast(i);
    }

    /* set backlight */
    if (cfg_number(section, "Backlight", 0, 0, 100, &i) > 0) {
	drv_CF_backlight(i);
    }

    return 0;
}


/****************************************/
/***            plugins               ***/
/****************************************/


static void plugin_contrast(RESULT * result, const int argc, RESULT * argv[])
{
    double contrast;

    switch (argc) {
    case 0:
	contrast = drv_CF_contrast(-1);
	SetResult(&result, R_NUMBER, &contrast);
	break;
    case 1:
	contrast = drv_CF_contrast(R2N(argv[0]));
	SetResult(&result, R_NUMBER, &contrast);
	break;
    default:
	error("%s.contrast(): wrong number of parameters", Name);
	SetResult(&result, R_STRING, "");
    }
}


static void plugin_backlight(RESULT * result, const int argc, RESULT * argv[])
{
    double backlight;

    switch (argc) {
    case 0:
	backlight = drv_CF_backlight(-1);
	SetResult(&result, R_NUMBER, &backlight);
	break;
    case 1:
	backlight = drv_CF_backlight(R2N(argv[0]));
	SetResult(&result, R_NUMBER, &backlight);
	break;
    default:
	error("%s.backlight(): wrong number of parameters");
	SetResult(&result, R_STRING, "");
    }
}


/* Fixme: other plugins for Fans, Temperature sensors, ... */



/****************************************/
/***        widget callbacks          ***/
/****************************************/

/* using drv_generic_text_draw(W) */
/* using drv_generic_text_icon_draw(W) */
/* using drv_generic_text_bar_draw(W) */
/* using drv_generic_gpio_draw(W) */
/* using drv_generic_keypad_draw(W) */


/****************************************/
/***        exported functions        ***/
/****************************************/


/* list models */
int drv_CF_list(void)
{
    int i;

    for (i = 0; Models[i].type != -1; i++) {
	printf("%s ", Models[i].name);
    }
    return 0;
}


/* initialize driver & display */
int drv_CF_init(const char *section, const int quiet)
{
    WIDGET_CLASS wc;
    int ret;

    info("%s: %s", Name, "$Revision: 1.44 $");

    /* start display */
    if ((ret = drv_CF_start(section)) != 0) {
	return ret;
    }

    /* display preferences */
    XRES = 6;			/* pixel width of one char  */
    YRES = 8;			/* pixel height of one char  */
    CHARS = 8;			/* number of user-defineable characters */

    /* real worker functions */
    switch (Protocol) {
    case 1:
	CHAR0 = 128;		/* ASCII of first user-defineable char */
	GOTO_COST = 3;		/* number of bytes a goto command requires */
	drv_generic_text_real_write = drv_CF_write1;
	drv_generic_text_real_defchar = drv_CF_defchar1;
	break;
    case 2:
	CHAR0 = 0;		/* ASCII of first user-defineable char */
	GOTO_COST = -1;		/* there is no goto on 633 */
	drv_generic_text_real_write = drv_CF_write2;
	drv_generic_text_real_defchar = drv_CF_defchar23;
	drv_generic_gpio_real_get = drv_CF_GPI;
	drv_generic_gpio_real_set = drv_CF_GPO;
	break;
    case 3:
	CHAR0 = 0;		/* ASCII of first user-defineable char */
	GOTO_COST = 3;		/* number of bytes a goto command requires */
	drv_generic_text_real_write = drv_CF_write3;
	drv_generic_text_real_defchar = drv_CF_defchar23;
	drv_generic_gpio_real_get = drv_CF_GPI;
	drv_generic_gpio_real_set = drv_CF_GPO;
	drv_generic_keypad_real_press = drv_CF_keypad;
	break;
    }

    if (!quiet) {
	char buffer[40];
	qprintf(buffer, sizeof(buffer), "%s %s", Name, Models[Model].name);
	if (drv_generic_text_greet(buffer, "www.crystalfontz.com")) {
	    sleep(3);
	    drv_CF_clear();
	}
    }

    /* initialize generic text driver */
    if ((ret = drv_generic_text_init(section, Name)) != 0)
	return ret;

    /* initialize generic icon driver */
    if ((ret = drv_generic_text_icon_init()) != 0)
	return ret;

    /* initialize generic bar driver */
    if ((ret = drv_generic_text_bar_init(0)) != 0)
	return ret;

    /* add fixed chars to the bar driver */
    drv_generic_text_bar_add_segment(0, 0, 255, 32);	/* ASCII 32 = blank */
    if (Protocol == 2)
	drv_generic_text_bar_add_segment(255, 255, 255, 255);	/* ASCII 255 = block */

    /* initialize generic GPIO driver */
    if ((ret = drv_generic_gpio_init(section, Name)) != 0)
	return ret;

    /* initialize generic key pad driver */
    if ((ret = drv_generic_keypad_init(section, Name)) != 0)
	return ret;

    /* register text widget */
    wc = Widget_Text;
    wc.draw = drv_generic_text_draw;
    widget_register(&wc);

    /* register icon widget */
    wc = Widget_Icon;
    wc.draw = drv_generic_text_icon_draw;
    widget_register(&wc);

    /* register bar widget */
    wc = Widget_Bar;
    wc.draw = drv_generic_text_bar_draw;
    widget_register(&wc);

    /* register plugins */
    AddFunction("LCD::contrast", -1, plugin_contrast);
    AddFunction("LCD::backlight", -1, plugin_backlight);

    return 0;
}


/* close driver & display */
int drv_CF_quit(const int quiet)
{

    info("%s: shutting down display.", Name);

    drv_generic_text_quit();
    drv_generic_gpio_quit();
    drv_generic_keypad_quit();

    /* clear display */
    drv_CF_clear();

    /* say goodbye... */
    if (!quiet) {
	drv_generic_text_greet("goodbye!", NULL);
    }

    drv_generic_serial_close();

    return (0);
}


DRIVER drv_Crystalfontz = {
  name:Name,
  list:drv_CF_list,
  init:drv_CF_init,
  quit:drv_CF_quit,
};