/* $Id: drv_Crystalfontz.c,v 1.33 2005/04/02 05:28:58 reinelt 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.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 "debug.h"
#include "cfg.h"
#include "qprintf.h"
#include "timer.h"
#include "plugin.h"
#include "widget.h"
#include "widget_text.h"
#include "widget_icon.h"
#include "widget_bar.h"
#include "drv.h"
#include "drv_generic_text.h"
#include "drv_generic_serial.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 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,};


/* Fixme: GPO's not yet implemented */
/* static int GPO[8]; */
static int GPOS;


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

/* Fixme #1: number of gpo's should be verified */
/* Fixme #2: protocol should be verified */

static MODEL Models[] = {
  { 626, "626",      2, 16, 0, 1,  0 },
  { 631, "631",      2, 20, 0, 3, 22 },
  { 632, "632",      2, 16, 0, 1,  0 },
  { 633, "633",      2, 16, 0, 2, 18 },
  { 634, "634",      4, 20, 0, 1,  0 },
  { 636, "636",      2, 16, 0, 1,  0 },
  {  -1, "Unknown", -1, -1, 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 0x80: /* Key Activity */
    debug ("Key Activity: %d", Packet.data[0]);
    break;
    
  case 0x81: /* 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 0x82: /* 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:
    /* just ignore packet */
    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];
    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 int cmd, int len, const unsigned char *data)
{
  unsigned char buffer[22];
  unsigned short crc;
  
  if (len > Payload) {
    error ("%s: internal error: packet length %d exceeds payload size %d", Name, len, Payload);
    len = sizeof(buffer)-1;
  }
  
  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);
  
#if 0
  debug ("Tx Packet %d len=%d", buffer[0], buffer[1]);
#endif
  
  drv_generic_serial_write ((char*)buffer, len+4);
  
}


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 > 20) l = 20 - col;
  if (l < 0) l = 0;

  /* sanity check */
  if (row >= 2 || col + l > 20) {
    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_fan_pwm (int fan, int power)
{
  static unsigned char PWM[4] = {100,};
  
  /* sanity check */
  if (fan<1 || fan>4) return -1;
  
  /* fan ranges from 1 to 4 */
  fan--;
  
  /* -1 is used to query the current power */
  if (power == -1) return PWM[fan];
  
  if (power<0  ) power=0;
  if (power>100) power=100;
  PWM[fan]=power;
  
  switch (Protocol) {
  case 2:
    drv_CF_send (17, 4, PWM);
    break;
  }
  
  return PWM[fan];
}


static int drv_CF_autodetect (void)
{
  int i, m;
  
  /* only autodetect newer displays */
  if (Protocol<2) return -1;
    
  /* read display type */
  drv_CF_send (1, 0, NULL);
  
  i=0;
  while (1) {
    /* wait 10 msec */
    usleep(10*1000);
    /* packet available? */
    if (drv_CF_poll()) {
      /* display type */
      if (Packet.type==0x41) {
	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;
      }
      drv_CF_process_packet();
    }
    /* wait no longer than 300 msec */
    if (++i > 30) {
      error ("%s: display detection timed out", Name);
      return -1;
    }
  }
  
  /* not reached */
  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==0x52) {
	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;
  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, ""); 
  }
}


static void plugin_fan_pwm (RESULT *result, const int argc, RESULT *argv[])
{
  double pwm;
  
  switch (argc) {
  case 1:
    pwm = drv_CF_fan_pwm(R2N(argv[0]), -1);
    SetResult(&result, R_NUMBER, &pwm); 
    break;
  case 2:
    pwm = drv_CF_fan_pwm(R2N(argv[0]), R2N(argv[1]));
    SetResult(&result, R_NUMBER, &pwm); 
    break;
  default:
    error ("%s.pwm(): 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) */


/****************************************/
/***        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;  
  
  /* 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;
    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;
    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 */
  
  /* 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);
  if (Protocol == 2) {
    AddFunction ("LCD::fan_pwm",   -1, plugin_fan_pwm);
  }
  
  return 0;
}


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

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

  drv_generic_text_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, 
};