/*
 *************************************************************************
 *
 * serdisp_control.c
 * routines for controlling serial lc-displays (eg: optrex323, nokia displays, ... )
 *
 *************************************************************************
 *
 * copyright (C) 2003-2006  wolfgang astleitner
 * email     mrwastl@users.sourceforge.net
 *
 *************************************************************************
 *
 * initially based on:
 *   http://www.thiemo.net/projects/orpheus/optrex/
 *
 *************************************************************************
 * This program 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 of the License, or (at  
 * your option) any later version.                                        
 *                                                                        
 * This program 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., 59 Temple Place - Suite 330, Boston, MA              
 * 02111-1307, USA.  Or, point your browser to                            
 * http://www.gnu.org/copyleft/gpl.html                                   
 *************************************************************************
 */


#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <sys/ioctl.h>


#include "serdisplib/serdisp_control.h"
#include "serdisplib/serdisp_connect.h"
#include "serdisplib/serdisp_tools.h"
#include "serdisplib/serdisp_messages.h"
#include "serdisplib/serdisp_colour.h"
#include "serdisplib/serdisp_specific_pcd8544.h"
#include "serdisplib/serdisp_specific_sed156x.h"
#include "serdisplib/serdisp_specific_sed153x.h"
#include "serdisplib/serdisp_specific_i2c.h"
#include "serdisplib/serdisp_specific_t6963.h"
#include "serdisplib/serdisp_specific_sed133x.h"
#include "serdisplib/serdisp_specific_nokcol.h"
#include "serdisplib/serdisp_specific_ks0108.h"


typedef struct serdisp_setup_s {
  char* dispname;
  char* aliasnames;
  serdisp_t* (*fp_setup) (const char*, const char*);
  char* defaultoptions;
  char* description;
} serdisp_setup_t;


/* supported displays */
serdisp_setup_t serdisp_displays[] = {
  /* display name   alias names                 function pointer         default options         decription            */
   {"OPTREX323",    "",                         serdisp_sed153x_setup,   "",                     "Optrex 323 display"}
  ,{"LSU7S1011A",   "ALPS",                     serdisp_sed153x_setup,   "WIRING=1",             "ALPS display with display module kit by pollin"}
  ,{"E08552",       "",                         serdisp_sed153x_setup,   "WIRING=2",             "EPSON E0855-2 display with display module kit by pollin"}
  ,{"PCD8544",      "",                         serdisp_pcd8544_setup,   "WIRING=1",             "generic driver for PCD8544-based displays"}
  ,{"LPH7366",      "",                         serdisp_pcd8544_setup,   "WIRING=0",             "LPH7366 display with backlight"}
  ,{"LPH7690",      "",                         serdisp_pcd8544_setup,   "WIRING=1",             "LPH7690 display"}
  ,{"NOKIA7110",    "SED1565",                  serdisp_sed156x_setup,   "",                     "Nokia 7110 display (SED1565-based)"}
  ,{"NEC21A",       "SKYPER",                   serdisp_sed156x_setup,   "WIRING=1",             "NEC 21a (Skyper) display module"}
  ,{"LPH7508",      "",                         serdisp_sed156x_setup,   "WIRING=2",             "LPH7508 display module with display module kit by pollin"}
  ,{"HP12542R",     "",                         serdisp_sed156x_setup,   "WIRING=3",             "Hyundai HP12542R display module with display module kit by pollin"}
  ,{"ERICSSONT2X",  "E///T2X",                  serdisp_i2c_setup,       "",                     "Ericsson T20/T28/T29 i2c-displays"}
  ,{"T6963",        "",                         serdisp_t6963_setup,     "",                     "generic driver for T6963-based displays"}
  ,{"TLX1391",      "",                         serdisp_t6963_setup,     "WIDTH=128;HEIGHT=128", "Toshiba TLX1391 display (T6963-based)"}
  ,{"SED133X",      "SED1330,SED1335",          serdisp_sed133x_setup,   "",                     "generic driver for SED1330/SED1335-based displays"}
  ,{"N3510I",       "N3530",                    serdisp_nokcol_setup,    "",                     "driver for Nokia 3510i/3530 displays (S1D15G14-based)"}
  ,{"ERICSSONR520", "E///R520,R520",            serdisp_i2c_setup,       "",                     "Ericsson R520 i2c-display"}
#if 0   /* r320 support not tested yet */
  ,{"ERICSSONR320", "E///R320,R320",            serdisp_i2c_setup,       "",                     "Ericsson R320 i2c-display"}
#endif  
  ,{"KS0108",       "",                         serdisp_ks0108_setup,    "",                     "generic driver for KS0108-based displays"}
  ,{"CTINCLUD",     "",                         serdisp_ks0108_setup,    "",                     "c't includ USB-display"}
};



/* standard options + aliasnames and defines 
   (eg: for better legibility one may write BACKLIGHT=YES instead of BACKLIGHT=1 
*/
serdisp_options_t serdisp_standardoptions[] = {
   /*  name       aliasnames min  max  mod flag                  defines  */
   {  "ROTATE",    "ROT",      0, 360,  90,   SD_OPTIONFLAG_STD, "YES=180,NO=0,TRUE=180,FALSE=0,TOGGLE=2"}
  ,{  "INVERT",    "INV",      0,   1,   1,   SD_OPTIONFLAG_STD, "YES=1,NO=0,TRUE=1,FALSE=0"}
  ,{  "CONTRAST",  "",        -1,  -1,  -1,   0,                  ""}
  ,{  "BACKLIGHT", "BG",      -1,  -1,  -1,   0,                  "YES=1,NO=0,TRUE=1,FALSE=0,ON=1,OFF=0"}
  ,{  "DELAY",     "",        -1,  -1,  -1,   0,                  "NONE=0"}
  ,{  "WIDTH",     "W",       -1,  -1,  -1,   0,                  ""}
  ,{  "HEIGHT",    "H",       -1,  -1,  -1,   0,                  ""}
  ,{  "DEPTH",     "",        -1,  -1,  -1,   0,                  ""}
};



/* prototypes for locally used functions */
void  serdisp_freeresources(serdisp_t* dd);
char* serdisp_getwiresignalname(serdisp_t* dd, int idx);
int   serdisp_getstandardoptionindex(const char* optionname);
int   serdisp_getoptionindex(serdisp_t* dd, const char* optionname);

/* *********************************
   serdisp_t* serdisp_init(sdcd, dispname, optionstring)
   *********************************
   initialises a display
   *********************************
   sdcd          ... output device handle
   dispname      ... display name
   optionstring  ... option string (extra options eg. from outside)
   *********************************
   returns a display descriptor
*/
serdisp_t* serdisp_init(serdisp_CONN_t* sdcd, const char dispname[], const char optionstring[]) {
  serdisp_t* dd = (serdisp_t*)0;
  int found = 0;
  int i = 0;
  int cifiob = 0;   /* colour items fitting into one byte */
  int displayidx = 0;

  char* patternptr = (char*) optionstring;
  int patternlen = -1;
  int patternborder = strlen(patternptr);
  char* valueptr = 0;
  int valuelen = 0;

  sd_debug(2, "serdisp_init(): entering; dispname: %s, optionstring: %s", dispname, optionstring);


  while (!found && displayidx < sizeof(serdisp_displays) / sizeof(serdisp_setup_t) ) {
    if (strcasecmp(serdisp_displays[displayidx].dispname, dispname) == 0 ||
        sdtools_isinelemlist(serdisp_displays[displayidx].aliasnames, dispname, -1) > -1
    ) {
      found = 1;
    } else {
      displayidx++; 
    }
  }

  if (!found) {
    sd_error(SERDISP_ENOTSUP, "display '%s' not in display table", dispname);
    return (serdisp_t*)0;
  }

  dd = serdisp_displays[displayidx].fp_setup(dispname, optionstring);

  if (!dd) {
    sd_debug(1, "serdisp_init(); display %s could not be initialised. last error: %s", dispname, sd_errormsg);
    return (serdisp_t*)0;
  }

  /* if dsp_name not already set to dispname in specific setup, do it here */
  if (!dd->dsp_name) {
    dd->dsp_name = (char*)dispname;
  }

  /* if dsp_optionstring not already set to optionstring in specific setup, do it here */
  if (!dd->dsp_optionstring) {
    dd->dsp_optionstring = (char*)optionstring;
  }

  /* unsupported connection type */
  if (! (dd->connection_types & sdcd->conntype)) {
    serdisp_freeresources(dd);
    dd = 0;
    return (serdisp_t*)0;
  }


  /* initialise other items to default values not set by fp_setup */
  if (! dd->fp_setpixel)
    dd->fp_setpixel = &sdtools_generic_setpixel;

  if (! dd->fp_getpixel)
    dd->fp_getpixel = &sdtools_generic_getpixel;

  if (! dd->depth)
    dd->depth = 1;


  /* filter incompatible settings */
  if (dd->depth <=0 || dd->depth > 16/* || (8 % dd->depth)*/) {   /* unsupported colour depth */
    sd_debug(1, "serdisp_init(); colour depth must be 1, 2, 4, or 8. depth %d is not supported (%s)", dd->depth, dispname);
    serdisp_freeresources(dd);
    dd = 0;
    return (serdisp_t*)0;
  }


  if (! dd->scrbuf_size) {
    /* cifiob ... how many colour items can be stored into one byte */
    cifiob = 8 / dd->depth;
    dd->scrbuf_size = sizeof(byte) * (dd->width + dd->xcolgaps) * (( dd->height +dd->ycolgaps + (cifiob-1))/cifiob); 
    dd->scrbuf_chg_size = sizeof(byte) * (dd->width + dd->xcolgaps)  * ((( dd->height +dd->ycolgaps + (cifiob*8-1))/(cifiob*8)));
  }


  if (! dd->scrbuf) {
    /* allocate screen buffer and screen-change buffer */
    if (! (dd->scrbuf = (byte*) sdtools_malloc( dd->scrbuf_size ) ) ) {
      sd_error(SERDISP_EMALLOC, "serdisp_init(): cannot allocate screen buffer");
      serdisp_freeresources(dd);
      dd = 0;
      return (serdisp_t*)0;
    }
  }

  if (! dd->scrbuf_chg) {
    /* one byte is able to store 8 change infos -> cifiob * 8 */
    if (! (dd->scrbuf_chg = (byte*) sdtools_malloc( dd->scrbuf_chg_size ) )) {
      sd_error(SERDISP_EMALLOC, "serdisp_init(): cannot allocate screen change buffer");
      serdisp_freeresources(dd);
      dd = 0;
      return (serdisp_t*)0;
    }
  }

  /* generate default relocation tables if not set by fp_setup */
  if (! dd->xreloctab) {
    int j;

    if (! (dd->xreloctab = (int*) sdtools_malloc( sizeof(int) * (dd->width + dd->xcolgaps) ) ) ) {
      sd_error(SERDISP_EMALLOC, "serdisp_init(): cannot allocate relocation table");
      serdisp_freeresources(dd);
      dd = 0;
      return (serdisp_t*)0;
    }

    for (j = 0; j < dd->width + dd->xcolgaps; j++)
      dd->xreloctab[j] = j;
  }    
  if (! dd->yreloctab) {
    int j;

    if (! (dd->yreloctab = (int*) sdtools_malloc( sizeof(int) * (dd->height + dd->ycolgaps) ) ) ) {
      sd_error(SERDISP_EMALLOC, "serdisp_init(): cannot allocate relocation table");
      serdisp_freeresources(dd);
      dd = 0;
      return (serdisp_t*)0;
    }

    for (j = 0; j < dd->height + dd->ycolgaps; j++)
      dd->yreloctab[j] = j;
  }    

  /* colour spaces */
  if (!dd->colour_spaces) { /* auto detect */
    switch (serdisp_getdepth(dd)) {
      case 1:
      case 2:
      case 4:
        dd->colour_spaces = SD_CS_GREYSCALE;
        break;
      /* depth 8 may either be 256 grey levels or RGB332: so no auto-detect */
      case 12:
        dd->colour_spaces = SD_CS_RGB444;
        break;
      default:
        sd_error(SERDISP_ENOTSUP, "serdisp_init(): cannot auto-detect colour space. dd->colour_spaces has to be set for this driver");
        serdisp_freeresources(dd);
        dd = 0;
        return (serdisp_t*)0; 
    }
  }

  /* indexed colour table */
  /* initialise colour table (greyscale values and indexed colour schemes only; leave uninitialised otherwise) */
  if ((! dd->ctable) && ( dd->colour_spaces & SD_CS_INDEXED_SPACE)  ) {   /* no default colour table (only for indexed colours) */

    if (! (dd->ctable = (long*) sdtools_malloc( sizeof(long) *  serdisp_getcolours(dd) ) ) ) {
      sd_error(SERDISP_EMALLOC, "serdisp_init(): cannot allocate indexed colour table");
      serdisp_freeresources(dd);
      dd = 0;
      return (serdisp_t*)0;
    }

    /* special case for depth == 1: keep backward compatibility with old versions: 
     *                              white = idx[0]; black = idx[1] */
    if (serdisp_getdepth(dd) == 1) {
      serdisp_setcoltabentry(dd, 0, serdisp_GREY2ARGB(0xFF));
      serdisp_setcoltabentry(dd, 1, serdisp_GREY2ARGB(0x00));
    } else if (serdisp_getdepth(dd) <= 8) {
      int j, c, colours = serdisp_getcolours(dd);

      for (j = 0; j < colours; j++) {
        c = (255 / (colours - 1 )) * j;
        serdisp_setcoltabentry(dd, j, serdisp_GREY2ARGB(c));
      }
    }
  }
  
  if ( ! dd->fp_transcolour || ! dd->fp_transgrey || ! dd->fp_lookupcolour || ! dd->fp_lookupgrey ) {
    switch (serdisp_getdepth(dd)) {
      case 1:
        dd->fp_transcolour   = serdisp_transcolour_bw;
        dd->fp_transgrey     = serdisp_transgrey_bw;
        dd->fp_lookupcolour  = serdisp_lookupcolour_bw;
        dd->fp_lookupgrey    = serdisp_lookupgrey_bw;
        break;
      case 2:
      case 4:
        dd->fp_transcolour   = serdisp_transcolour_grey2_4;
        dd->fp_transgrey     = serdisp_transgrey_grey2_4;
        dd->fp_lookupcolour  = serdisp_lookupcolour_grey2_4;
        dd->fp_lookupgrey    = serdisp_lookupgrey_grey2_4;
        break;
      case 8:
        if (dd->colour_spaces & SD_CS_RGB332) {
          dd->fp_transcolour   = serdisp_transcolour_rgb332;
          dd->fp_transgrey     = serdisp_transgrey_rgb332;
          dd->fp_lookupcolour  = serdisp_lookupcolour_rgb332;
          dd->fp_lookupgrey    = serdisp_lookupgrey_rgb332;
        } else {
          dd->fp_transcolour   = serdisp_transcolour_grey8;
          dd->fp_transgrey     = serdisp_transgrey_grey8;
          dd->fp_lookupcolour  = serdisp_lookupcolour_grey8;
          dd->fp_lookupgrey    = serdisp_lookupgrey_grey8;
        }
        break;
      case 12:
        dd->fp_transcolour   = serdisp_transcolour_rgb444;
        dd->fp_transgrey     = serdisp_transgrey_rgb444;
        dd->fp_lookupcolour  = serdisp_lookupcolour_rgb444;
        dd->fp_lookupgrey    = serdisp_lookupgrey_rgb444;
        break;
      default:
        sd_error(SERDISP_ENOTSUP, "serdisp_init(): cannot initialise colour-space specific colour functions.");
        serdisp_freeresources(dd);
        dd = 0;
        return (serdisp_t*)0; 
    }    
  }

  dd->sdcd = sdcd;

  found = 0;
  /* search for wiring-definition */
  if (optionstring && strlen(optionstring) > 0) {

    while( !found && (patternptr = sdtools_nextpattern(patternptr, ';', &patternlen, &patternborder)) ) {
    
      if ( strncasecmp(patternptr, "WIRING=", strlen("WIRING=")) == 0 ||
           strncasecmp(patternptr, "WIRE=", strlen("WIRE=")) == 0
         ) {
        found = 1;
      }
    }
  }

  if (!found) {
    /* if no wiring-definition found in extern optionstring string, try defaultoptions string in serdisp_displays[] */
    if (serdisp_displays[displayidx].defaultoptions && strlen(serdisp_displays[displayidx].defaultoptions) > 0) {
      found = 0;
  
      patternptr = serdisp_displays[displayidx].defaultoptions;
      patternlen = -1;
      patternborder = strlen(serdisp_displays[displayidx].defaultoptions);

      while( !found && (patternptr = sdtools_nextpattern(patternptr, ';', &patternlen, &patternborder)) ) {
        if ( strncasecmp(patternptr, "WIRING=", strlen("WIRING=")) == 0 ||
             strncasecmp(patternptr, "WIRE=", strlen("WIRE=")) == 0
           ) {
          found = 1;
        }
      }
    }
  }

  if (found) {
    char* idxpos = index(patternptr, '=');
    int keylen = patternlen;

    /* '=' found and position not outside patternlen? */
    if (idxpos &&  serdisp_ptrstrlen(idxpos, patternptr) < patternlen ) {
      keylen = serdisp_ptrstrlen(idxpos, patternptr);
      valueptr = ++idxpos;
      valuelen = patternlen - keylen - 1;
    } else {
      sd_error(SERDISP_ERUNTIME, "serdisp_init(): invalid/incomplete wiring definition");
      serdisp_freeresources(dd);
      dd = 0;
      return (serdisp_t*)0; 
    }
    if (serdisp_setupwirings(dd, valueptr, valuelen)) {
      serdisp_freeresources(dd);
      dd = 0;
      return (serdisp_t*)0; 
    }
  } else {  /* nothing found: use default wiring number 0 */
    if (serdisp_setupwirings(dd, "0", 0)) {
      serdisp_freeresources(dd);
      dd = 0;
      return (serdisp_t*)0; 
    }
  }
  

  sd_debug(2, "serdisp_init(): wiring (before fp_init()):");
  sd_debug(2, "=======================");
  if (sdcd->conntype == SERDISPCONNTYPE_PARPORT) {
    sd_debug(2, "    signal-id    __CCSSDD  signal-name");
    sd_debug(2, "  -----------    --------  -----------");
  }
  for (i = 0; i < SD_MAX_SUPP_SIGNALS; i++)
    if (sdcd->signals[i])
      sd_debug(2, "  signals[%2d]: 0x%08lx  %s", i, sdcd->signals[i], serdisp_getwiresignalname(dd,i));
  sd_debug(2, "  invert mask: 0x%08lx", sdcd->signals_invert);
  sd_debug(2, "      perm on: 0x%08lx", sdcd->signals_permon);
  sd_debug(2, " ");
  sd_debug(2, "  io_flags_readstatus: 0x%02x", dd->sdcd->io_flags_readstatus);
  sd_debug(2, "  io_flags_writedata:  0x%02x", dd->sdcd->io_flags_writedata);
  sd_debug(2, "  io_flags_writecmd:   0x%02x", dd->sdcd->io_flags_writecmd);
  sd_debug(2, " ");

  if (dd->ctable) {
    sd_debug(2, "serdisp_init(): colour table:");
    sd_debug(2, "=============================");
    sd_debug(2, "    idx    AARRGGBB");
    sd_debug(2, "  -----    --------");
    for (i = 0; i < serdisp_getcolours(dd); i++)
      sd_debug(2, "  %5d  0x%08lX", i, dd->ctable[i]);
    sd_debug(2, " ");
  }

  dd->fp_init(dd);

  sd_debug(2, "serdisp_init(): information:");
  sd_debug(2, "============================");
  sd_debug(2, "  supported colour spaces: 0x%08lx", dd->colour_spaces);
  sd_debug(2, " ");

  sd_debug(2, "serdisp_init(): wiring (after fp_init()):");
  sd_debug(2, "=======================");
  if (sdcd->conntype == SERDISPCONNTYPE_PARPORT) {
    sd_debug(2, "    signal-id    __CCSSDD  signal-name");
    sd_debug(2, "  -----------    --------  -----------");
  }
  for (i = 0; i < SD_MAX_SUPP_SIGNALS; i++)
    if (sdcd->signals[i])
      sd_debug(2, "  signals[%2d]: 0x%08lx  %s", i, sdcd->signals[i], serdisp_getwiresignalname(dd,i));
  sd_debug(2, "  invert mask: 0x%08lx", sdcd->signals_invert);
  sd_debug(2, "      perm on: 0x%08lx", sdcd->signals_permon);
  sd_debug(2, " ");
  sd_debug(2, "  io_flags_readstatus: 0x%02x", dd->sdcd->io_flags_readstatus);
  sd_debug(2, "  io_flags_writedata:  0x%02x", dd->sdcd->io_flags_writedata);
  sd_debug(2, "  io_flags_writecmd:   0x%02x", dd->sdcd->io_flags_writecmd);
  sd_debug(2, "  (0x01: WRITEDB  0x02: WRITECB  0x04: READDB  0x08: READSB   0x10: READCB)");  
  sd_debug(2, " ");

  if (dd->ctable) {
    sd_debug(2, "serdisp_init(): colour table:");
    sd_debug(2, "=============================");
    sd_debug(2, "    idx    AARRGGBB");
    sd_debug(2, "  -----    --------");
    for (i = 0; i < serdisp_getcolours(dd); i++)
      sd_debug(2, "  %5d  0x%08lX", i, dd->ctable[i]);
    sd_debug(2, " ");
  }

  /* post-init settings */

  /* adjust rotation value */
  if (dd->curr_rotate > 1)
    serdisp_setoption(dd, "ROTATE", dd->curr_rotate);
  serdisp_setoption(dd, "INVERT", dd->curr_invert);
  if (dd->feature_backlight)
  serdisp_setoption(dd, "BACKLIGHT", dd->curr_backlight);
  if (dd->feature_contrast) {
    if (dd->curr_contrast <= 0) 
      serdisp_setoption(dd, "CONTRAST", MAX_CONTRASTSTEP / 2);
    else
      serdisp_setoption(dd, "CONTRAST", dd->curr_contrast);
  }
      
  return dd;
}




/* *********************************
   long serdisp_getversioncode (void)
   *********************************
   get version code
   *********************************
   returns version code of serdisp library
*/
long serdisp_getversioncode(void) {
  return (long) SERDISP_VERSION_CODE;
}


/* *********************************
   int serdisp_getwidth (dd)
   *********************************
   get width of display
   *********************************
   dd     ... display descriptor
   *********************************
   returns width of display
*/
int serdisp_getwidth(serdisp_t* dd) {
  return ((dd->curr_rotate <= 1) ? dd->width : dd->height);
}


/* *********************************
   int serdisp_getheight (dd)
   *********************************
   get height of display
   *********************************
   dd     ... display descriptor
   *********************************
   returns height of display
*/
int serdisp_getheight(serdisp_t* dd) {
  return ((dd->curr_rotate <= 1) ? dd->height : dd->width);
}


/* *********************************
   int serdisp_getcolours (dd)
   *********************************
   get amount of colours supported by the configuration currently used
   *********************************
   dd     ... display descriptor
   *********************************
   returns the amount of supported colours
*/
int serdisp_getcolours(serdisp_t* dd) {
  return 1 << dd->depth;
}


/* *********************************
   int serdisp_getdepth (dd)
   *********************************
   get colour depth supported by the configuration currently used
   *********************************
   dd     ... display descriptor
   *********************************
   returns colour depth
*/
int serdisp_getdepth(serdisp_t* dd) {
  return dd->depth;
}



/* *********************************
   int serdisp_getpixelaspect (dd)
   *********************************
   get pixel aspect ratio in percent (to avoid floating-point values)
   *********************************
   dd     ... display descriptor
   *********************************
   returns pixel aspect ratio in percent
   eg:  pixels are quadratic: 100 will be returned
        pixel width is twice pixel height: 200 will be returned
        pixel width is half of pixel height: 50 will be returned

        formula:
                   w * ph       w, h: amount of pixels
         f = 100 * ------     pw, ph: display area (in micrometres, but unit has no influence on the calc.)
                   h * pw          f: pixel aspect ratio in percent
*/
int serdisp_getpixelaspect(serdisp_t* dd) {
  if (dd->dsparea_width && dd->dsparea_height) {
    if (dd->curr_rotate <= 1)  /* display rotated 0 or 180 degrees */
      return ( (100 * dd->width * dd->dsparea_height) / (dd->height * dd->dsparea_width));
    else                       /* display rotated 90 or 270 degrees */
      return ( (100 * dd->height * dd->dsparea_width) / (dd->width * dd->dsparea_height));
  } else {  /* display area dimensions unknown: default to quadratic pixels */
    return 100;
  }
}



/* *********************************
   serdisp_CONN_t* serdisp_getSDCONN (dd)
   *********************************
   get serdisp connect descriptor used by the display
   *********************************
   dd     ... display descriptor
   *********************************
   returns serdisp connect descriptor
*/
serdisp_CONN_t* serdisp_getSDCONN(serdisp_t* dd) {
  return dd->sdcd;
}



/* *********************************
   void serdisp_feature(dd, feature, value)
   *********************************
   change a display feature (DEPRECATED, supoerseded by serdisp_setoption())
   *********************************
   dd     ...  display descriptor
   feature ... feature to change:
               FEATURE_CONTRAST   .. change display contrast (value: 0-MAX_CONTRAST)
               FEATURE_BACKLIGHT  .. 0: off, 1: on, 2: toggle
               FEATURE_INVERT     .. 0: normal display, 1: inverted display, 2: toggle
               FEATURE_ROTATE     .. 0: normal, 1 or 180: bottom-up, 90: 90 degrees, 270: 270 degrees
   value  ... value for option (see above)
*/
void serdisp_feature(serdisp_t* dd, int feature, int value) {
  switch (feature) {
    case FEATURE_CONTRAST:
      serdisp_setoption(dd, "CONTRAST", value);
      break;
    case FEATURE_INVERT:
      serdisp_setoption(dd, "INVERT", value);
      break;
    case FEATURE_BACKLIGHT:
      serdisp_setoption(dd, "BACKLIGHT", value);
      break;
    case FEATURE_ROTATE:
      serdisp_setoption(dd, "ROTATE", value);
      break;
  }
}


/* *********************************
   void serdisp_setoption(dd, optionname, value)
   *********************************
   change a display option (old: 'feature') (new, preferred version)
   *********************************
   dd          ... display descriptor
   optionname  ... name of option to change
   value       ... value for option
*/
void serdisp_setoption(serdisp_t* dd, const char* optionname, long value) {
  int idx;

  /* if no specific implementation of needed option: use generic one */
  if (!dd->fp_setoption(dd, optionname, value)) {
    if ( ((idx = serdisp_getstandardoptionindex(optionname)) != -1 ) && (idx == serdisp_getstandardoptionindex("INVERT"))) {
      int oldvalue = dd->curr_invert;
      
      /* option "INVERT" not defined in driver although dd->feature_invert is set to 1: set it back to 0 */
      if (dd->feature_invert) {
        dd->feature_invert = 0;
      }
      
      if (value < 2) 
        dd->curr_invert = value;
      else
        dd->curr_invert = (dd->curr_invert) ? 0 : 1;

      if (oldvalue != dd->curr_invert)
        serdisp_rewrite(dd);
    } else if ( ((idx = serdisp_getstandardoptionindex(optionname)) != -1 ) && (idx == serdisp_getstandardoptionindex("ROTATE"))) {
      int old = dd->curr_rotate;

      /* calculate 'value' so that:
         value = 0 (B00):   0 degrees
                 1 (B01): 180 degrees
                 2 (B10):  90 degrees
                 3 (B11): 270 degrees
       */
      switch (value) {
        case SD_OPTION_TOGGLE:
          value = old  ^ 0x01; /* invert last bit */
          break;
        case   1:
        case 180: 
          value = 1; 
          break;
        case  90:
          value = 2;
          break;
        case 270:
          value = 3;
          break;
        default:
          value = 0;
          break;
      }

      /* rotate content only when 180 degree rotation (else clear display) */
      if (old != value) {
        if ((old & 0x02) == (value & 0x02))
          sdtools_generic_rotate(dd);
        else
          serdisp_clear(dd);

        dd->curr_rotate = value;
      }
    }
    /* silently ignore other - unknown - options */
  }
}



/* *********************************
   long serdisp_getoption(dd, optionname, *typesize)
   *********************************
   get the value of a display option
   *********************************
   dd          ... display descriptor
   optionname  ... name of option to change
   typesize    ... pointer to value containing size of option's type (in byte)
                   (or, if 0 is passed here, this will be ignored)
   *********************************
   returns value of display option or -1 if option is unknown
*/
long serdisp_getoption(serdisp_t* dd, const char* optionname, int* typesize) {
  int stdidx = serdisp_getstandardoptionindex(optionname);


  if ( serdisp_compareoptionnames(dd, optionname, "INVERT") ) {
    if (typesize)
      *typesize = sizeof(int);

    return (long)(dd->curr_invert);
  } else if ( serdisp_compareoptionnames(dd, optionname, "ROTATE")) {
    int retval = 0;
    
    if (typesize)
      *typesize = sizeof(int);
      
    /* calculate 'value' so that:
       value = 0 (B00):   0 degrees
               1 (B01): 180 degrees
               2 (B10):  90 degrees
               3 (B11): 270 degrees
     */
    switch (dd->curr_rotate) {
      case 0:
        retval = 0; 
        break;
      case 1:
        retval = 180;
        break;
      case 2:
        retval = 90;
        break;
      case 3:
        retval = 270;
        break;
      default:
        retval = 0; 
        break;
    }
    return (long)retval;

  /* standard dd-descriptor values known in all drivers */
  } else if ( (serdisp_getstandardoptionindex("CONTRAST") == stdidx) && dd->feature_contrast) {
    if (typesize)
      *typesize = sizeof(int);

    /*  contrast_value in [0 .. MAX_CONTRASTSTEP]
    
        curr_contrast = floor((max_contrast - min_contrast) / MAX_CONTRASTSTEP) * contrast_value + min_value  
        contrast_value = (curr_contrast - min_value) / floor((max_contrast - min_contrast) / MAX_CONTRASTSTEP)
    */

    return (long) ( (dd->curr_contrast - dd->min_contrast) / ((dd->max_contrast - dd->min_contrast) / MAX_CONTRASTSTEP) );
  } else if ( (serdisp_getstandardoptionindex("BACKLIGHT") == stdidx) && dd->feature_backlight) {
    if (typesize)
      *typesize = sizeof(int);

    return (long) dd->curr_backlight;  
  } else if (serdisp_getstandardoptionindex("WIDTH") == stdidx) {
    if (typesize)
      *typesize = sizeof(int);

    return (long) dd->width;  
  } else if (serdisp_getstandardoptionindex("HEIGHT") == stdidx) {
    if (typesize)
      *typesize = sizeof(int);

    return (long) dd->height;  
  } else if (serdisp_getstandardoptionindex("DEPTH") == stdidx) {
    if (typesize)
      *typesize = sizeof(int);
    return (long) dd->depth;
  } else if (serdisp_getstandardoptionindex("DELAY") == stdidx) {
    if (typesize)
      *typesize = sizeof(long);

    return (long) dd->delay;

  /* driver-specific values */
  } else {
    int i = 0;
    long retval = -1;
    while (i < dd->amountoptions) {
      if (serdisp_compareoptionnames(dd, optionname, dd->options[i].name)) {
        int typesize_temp;
        void* valueptr;
        /* fp_getvalueptr needs to be defined */
        if (dd->fp_getvalueptr) {
          valueptr = dd->fp_getvalueptr(dd, optionname, &typesize_temp);
          switch (typesize_temp) {
            case 1:  retval = *((byte*) valueptr); break;
            case 2:  retval = *((short*) valueptr); break;
            case 4:  retval = *((long*) valueptr); break;
          }
          return retval;
        } else
          return -1;
      }
      i++;
    }
  }  
  return -1;
}



/* *********************************
   int serdisp_isoption(dd, optionname)
   *********************************
   test if option is supported
   *********************************
   dd          ... display descriptor
   optionname  ... name of option to test
   *********************************
   returns:  1 ... option is supported and read/writeable
            -1 ... option is supported but read-only
             0 ... option is not supported
*/
int serdisp_isoption(serdisp_t* dd, const char* optionname) {
  serdisp_options_t optiondesc;
  
  if (!serdisp_getoptiondescription(dd, optionname, &optiondesc))
    return 0;
  
  return (optiondesc.flag == SD_OPTIONFLAG_RW ) ? 1 : -1;
}




/* *********************************
   int serdisp_getoptiondescription(dd, optionname, optiondesc)
   *********************************
   gets a description to a given option
   *********************************
   dd          ... display descriptor
   optionname  ... name of option (name or aliasname)
   optiondesc  ... address of option descriptor
   *********************************
   returns:  1 ... options available
             0 ... option unknown/unsupported
*/
int serdisp_getoptiondescription(serdisp_t* dd, const char* optionname, serdisp_options_t* optiondesc) {
  int stdidx = serdisp_getstandardoptionindex(optionname);
  int optidx = serdisp_getoptionindex(dd, optionname);

  /* special treatment for contrast and backlight: if one is not supported: return 0 (unknown/unsupported) */
  if ( (serdisp_getstandardoptionindex("BACKLIGHT") == stdidx && !dd->feature_backlight) || 
       (serdisp_getstandardoptionindex("CONTRAST") == stdidx && !dd->feature_contrast)
     )
    return 0;

  /* optionname == alias name -> search again using 'name' */
  if (stdidx != -1 && optidx == -1)
    optidx = serdisp_getoptionindex(dd, serdisp_standardoptions[stdidx].name);

  /* read/write standard option or option defined in serdisp_standardoptions[] and not in driver options */
  if (stdidx != -1 && optidx == -1) {
    optiondesc->name       = serdisp_standardoptions[stdidx].name;
    optiondesc->aliasnames = serdisp_standardoptions[stdidx].aliasnames;
    optiondesc->minval     = serdisp_standardoptions[stdidx].minval;
    optiondesc->maxval     = serdisp_standardoptions[stdidx].maxval;
    optiondesc->modulo     = serdisp_standardoptions[stdidx].modulo;
    optiondesc->defines    = serdisp_standardoptions[stdidx].defines;
    optiondesc->flag       = (serdisp_standardoptions[stdidx].flag == SD_OPTIONFLAG_STD) ? SD_OPTIONFLAG_RW : SD_OPTIONFLAG_RO;
    return 1;
  }

  if (optidx != -1) {
    optiondesc->name       = dd->options[optidx].name;
    optiondesc->aliasnames = (stdidx == -1 || strlen(dd->options[optidx].aliasnames) > 0) 
                             ? dd->options[optidx].aliasnames
                             : serdisp_standardoptions[stdidx].aliasnames;
    optiondesc->minval     = (stdidx == -1 || dd->options[optidx].minval != -1) 
                             ? dd->options[optidx].minval
                             : serdisp_standardoptions[stdidx].minval;
    optiondesc->maxval     = (stdidx == -1 || dd->options[optidx].maxval != -1) 
                             ? dd->options[optidx].maxval
                             : serdisp_standardoptions[stdidx].maxval;
    optiondesc->modulo     = (stdidx == -1 || dd->options[optidx].modulo != -1) 
                             ? dd->options[optidx].modulo
                             : serdisp_standardoptions[stdidx].modulo;
    optiondesc->defines    = (stdidx == -1 || strlen(dd->options[optidx].defines) > 0) 
                             ? dd->options[optidx].defines
                             : serdisp_standardoptions[stdidx].defines;
    optiondesc->flag       = dd->options[optidx].flag;
    return 1;
  }
  return 0;
}


/* *********************************
   int serdisp_nextoptiondescription(dd, optiondesc)
   *********************************
   get the next option description (iterates through the options supported by the display)
   
   initialise optiondesc with optiondesc.name = "" to start the iteration
   
   eg:
   
   serdisp_options_t optiondesc;
   optiondesc.name = "";
   
   while(serdisp_nextoptiondescription(dd, &optiondesc)) {
     printf("%s\n", optiondesc.name);
   }
   
   *********************************
   dd          ... display descriptor
   optiondesc  ... address of option descriptor
   *********************************
   returns:  1 ... successful
             0 ... unsuccessful (no more option has been found)
*/
int serdisp_nextoptiondescription(serdisp_t* dd, serdisp_options_t* optiondesc) {
  int idx;
  int found;
  int foundtemp = 0;


  idx = 0;
  found = 0;
  if (optiondesc->name && strlen(optiondesc->name) > 0) {
    int stdidx = serdisp_getstandardoptionindex(optiondesc->name);

    if (stdidx != -1) {
      foundtemp = 1;  /* current optiondesc->name == standard option */
      idx = stdidx;

      while (!found && idx < sizeof(serdisp_standardoptions) / sizeof(serdisp_options_t) ) {
        idx++;
        /* search next standard option in serdisp_standardoptions[] */

        if ( (idx >= sizeof(serdisp_standardoptions) / sizeof(serdisp_options_t)) ||
             (idx == serdisp_getstandardoptionindex("BACKLIGHT") && !dd->feature_backlight) ||
             (idx == serdisp_getstandardoptionindex("CONTRAST") && !dd->feature_contrast)
        ) {
          ;
        } else {
          found = 1;
        }
      }
    }
  } else {
    /* empty name: return 1st standard option */
    idx = 0;
    found = 1;
  }
  
  if (found) {
    int retval = serdisp_getoptiondescription(dd, serdisp_standardoptions[idx].name, optiondesc);
    if (!retval) sd_error(SERDISP_ERUNTIME, "standardoption name %s -> retval 0\n",  serdisp_standardoptions[idx].name);
    return 1;
  }
  
  idx = 0;
  /* driver dep. options */
  if (foundtemp) {
    /* latest option description was last standard option, so return 1st driver dep. option 
       (not already defined in serdisp_standardoptions[]) */
    found = 1;
  } else {
    while (!found && idx < dd->amountoptions ) {
      int optidx = serdisp_getoptionindex(dd, optiondesc->name);
/*      if (serdisp_compareoptionnames(dd, optiondesc->name, dd->options[idx].name)) {*/
      if (optidx == idx) {
        if (idx+1 < dd->amountoptions)
          found = 1;
        idx++;
      } else
        idx++;
    }
  }

  if (found) {
    foundtemp = 0;
    /* if option at index found above equals option already defined in serdisp_standardoptions[] -> skip */
    while (!foundtemp && idx < dd->amountoptions ) {
      int stdidx = serdisp_getstandardoptionindex(dd->options[idx].name);
      if (stdidx == -1)
        foundtemp = 1;
      else
        idx++;
    }
  }

  if (found && foundtemp) {
    int retval = serdisp_getoptiondescription(dd, dd->options[idx].name, optiondesc);
    if (!retval) sd_error(SERDISP_ERUNTIME, "option name %s -> retval 0   idx: %d   amount: %d\n",  
                                           dd->options[idx].name, idx, dd->amountoptions);
    return 1;
  }

  return 0;
}





/* *********************************
   int serdisp_getdisplaydescription(displayname, displaydesc)
   *********************************
   get display description
   *********************************
   displayname  ... name of display (name or aliasname)
   displaydesc  ... address of display descriptor
   *********************************
   returns:  1 ... display available
             0 ... display unknown/unsupported
*/
int serdisp_getdisplaydescription(const char* displayname, serdisp_display_t* displaydesc) {
  int idx = serdisp_getdispindex(displayname);
  
  if (idx != -1) {
    displaydesc->dispname = serdisp_displays[idx].dispname;
    displaydesc->aliasnames = serdisp_displays[idx].aliasnames;
    displaydesc->optionstring = serdisp_displays[idx].defaultoptions;
    displaydesc->description = serdisp_displays[idx].description;
    return 1;
  }
  return 0;
}



/* *********************************
   int serdisp_nextdisplaydescription(displaydesc)
   *********************************
   get the next display description
   
   initialise displaydesc with displaydesc.dispname = "" to start the iteration
   
   eg:
   
   serdisp_display_t displaydesc;
   displaydesc.dispname = "";
   
   while(serdisp_nextdisplaydescription(&displaydesc)) {
     printf("%s\n", displaydesc.dispname);
   }
   
   *********************************
   displaydesc  ... address of display descriptor
   *********************************
   returns:  1 ... successful
             0 ... unsuccessful (no more option has been found)
*/
int serdisp_nextdisplaydescription(serdisp_display_t* displaydesc) {
  int idx;
  if (displaydesc->dispname && strlen(displaydesc->dispname) > 0) {
    idx = serdisp_getdispindex(displaydesc->dispname);
    if (idx == -1) /* shouldn't occur in theory */
      return 0;
    idx++;
  } else {
    idx = 0;
  }

  if ( idx <  sizeof(serdisp_displays) / sizeof(serdisp_setup_t) ) {
    displaydesc->dispname = serdisp_displays[idx].dispname;
    displaydesc->aliasnames = serdisp_displays[idx].aliasnames;
    displaydesc->optionstring = serdisp_displays[idx].defaultoptions;
    displaydesc->description = serdisp_displays[idx].description;
    return 1;
  }
  return 0;
}



/* *********************************
   int serdisp_isdisplay(displayname)
   *********************************
   test if display is supported
   *********************************
   displayname  ... name or alias name of display to test
   *********************************
   returns:  1 ... display is supported
             0 ... display is not supported
*/
int serdisp_isdisplay(const char* displayname) {
  serdisp_display_t displaydesc;
  
  return (serdisp_getdisplaydescription(displayname, &displaydesc) == 1);
}





/* *********************************
   void serdisp_currdisplaydescription(dd, displaydesc)
   *********************************
   get display description for the display currently used
   *********************************
   dd           ... display descriptor
   displaydesc  ... address of display descriptor
   *********************************
*/
void serdisp_currdisplaydescription(serdisp_t* dd, serdisp_display_t* displaydesc) {
  if (dd) {
    int rv = serdisp_getdisplaydescription(dd->dsp_name, displaydesc);
    if (!rv) {
      /* shouldn't happen in theory */
      sd_debug(0, "serdisp_currdisplaydescription(): INTERNAL ERROR: no display description found for %s\n", dd->dsp_name);
      displaydesc->dispname = (char*)0;
      displaydesc->aliasnames = (char*)0;
      displaydesc->optionstring = (char*)0;
      displaydesc->description = (char*)0;
      return;
    }
    
    displaydesc->optionstring = dd->dsp_optionstring;  
  }
}



/* *********************************
   const char* serdisp_getdisplayname(dd)
   *********************************
   get display name
   *********************************
   dd           ... display descriptor
   *********************************
   returns display name
*/
const char* serdisp_getdisplayname(serdisp_t* dd) {
  return dd->dsp_name;
}




/* *********************************
   int serdisp_nextwiringdescription(displayname, wiredesc)
   *********************************
   get the next wiring description for display identified through 'displayname'
   
   initialise wiredesc with wiredesc.name = "" to start the iteration
   
   eg:
   
   serdisp_wiredef_t wiredesc;
   wiredesc.name = "";
   
   while(serdisp_nextdwiringdescription("PCD85644", &wiredesc)) {
     printf("%s\n", wiredesc.name);
   }
   
   *********************************
   displayname  ... display name or alias name
   wiredesc     ... address of wiring descriptor
   *********************************
   returns:  1 ... successful
             0 ... unsuccessful (no more wirings have been found)
*/
int serdisp_nextwiringdescription(const char* displayname, serdisp_wiredef_t* wiredesc) {
  int dispidx = serdisp_getdispindex(displayname);
  int idx = 0;
  serdisp_t* dd;

  if (dispidx == -1) /* display name not found */
    return 0;

  dd = serdisp_displays[dispidx].fp_setup(displayname, "");

  if (!dd) {
    sd_debug(0, "serdisp_nextwiringdescription(); could not get descriptor for display %s. last error: %s", displayname, sd_errormsg);
    return 0;
  }

  if (!dd->amountwiredefs)
    return 0;


  if (wiredesc->name && strlen(wiredesc->name) > 0) {
    int found = 0;
    idx = 0;
    while (!found && idx < dd->amountwiredefs) {
      if (sdtools_ismatching(wiredesc->name, -1, dd->wiredefs[idx].name, -1) )
        found = 1;

      idx++;
    }
  } else {
    idx = 0;
  }

  if ( idx <  dd->amountwiredefs ) {
    wiredesc->id = dd->wiredefs[idx].id;
    wiredesc->conntype = dd->wiredefs[idx].conntype;
    wiredesc->name = dd->wiredefs[idx].name;
    wiredesc->definition = dd->wiredefs[idx].definition;
    wiredesc->description = dd->wiredefs[idx].description;
    return 1;
  }

  return 0;
}





/* *********************************
   void serdisp_close(dd)
   *********************************
   close display but do NOT clear / switch off display. output device remains opened

   this function may for example be used for programs that want to output something and
   than exit, but without clearing the display (for this, SDCONN_close() shouldn't either
   be called)

   ATTENTION: this will NOT work as expected with serial port and ioctl
              (TxD will be set to low in any case -> so display will be w/o power)
              so the only solution would be a separate power supply when using ioctl

              but: directIO works as expected (TxD will NOT be reset after program exit)

   *********************************
   dd     ... display descriptor
*/
void serdisp_close(serdisp_t* dd) {
  sd_debug(2, "serdisp_close(): entering");
  /*  dd->fp_close(dd);*/
  /*  SDCONN_close(dd->sdcd);*/
  serdisp_freeresources(dd);
  dd = 0;
}


/* *********************************
   void serdisp_quit(dd)
   *********************************
   clear / switch off display and release output device
   *********************************
   dd     ... display descriptor
*/
void serdisp_quit(serdisp_t* dd) {
  sd_debug(2, "serdisp_quit(): entering");

  dd->fp_close(dd);
/*  free(dd->scrbuf);
  free(dd->scrbuf_chg);*/
  SDCONN_close(dd->sdcd);
  serdisp_freeresources(dd);
  dd = 0;
}


/* *********************************
   void serdisp_setpixel(dd,x,y,colour)
   *********************************
   changes a pixel in the display buffer
   *********************************
   dd     ... display descriptor
   x      ... x-position
   y      ... y-position
   colour ... colour representation dependent on colour-scheme in use
              monochrome:       0: pixel not set, <>0: pixel set
              greyscale:        if not 256 colours: index of greyvalue, else greyvalue
              indexed colour:   index of colour-entry
              packed colour:    packed representation (eg. RGB444, RGB332, ...)
              true colour:      eg. 0xAARRGGBB (dependend on RGB-scheme (RGB, BGR, ..)
              
*/
void serdisp_setpixel(serdisp_t* dd, int x, int y, long colour) {
  dd->fp_setpixel(dd, x, y, colour);
}




/* *********************************
   int serdisp_getpixel(dd,x,y)
   *********************************
   get colour of pixel at (x/y)
   *********************************
   dd     ... display descriptor
   x      ... x-position top/left
   y      ... y-position top/left
   *********************************
   returns the colour at (x/y)
*/
long serdisp_getpixel(serdisp_t* dd, int x, int y) {
  return dd->fp_getpixel(dd, x, y);
}




/* *********************************
   void serdisp_clearbuffer(dd)
   *********************************
   resets the display-buffer scrbuf+scrbuf_chg
   display will NOT be redrawn!
   *********************************
   dd     ... display descriptor
*/
void serdisp_clearbuffer(serdisp_t* dd) {
  sd_debug(2, "serdisp_clearbuffer(): entering");

  memset(dd->scrbuf, ((SD_CS_ISGREY(dd)) ? 0x00 : 0xFF), dd->scrbuf_size);
/*  memset(dd->scrbuf, ((dd->depth > 1) ? 0xFF : 0x00), dd->scrbuf_size); */
  memset(dd->scrbuf_chg, 0xFF, dd->scrbuf_chg_size);
  sd_debug(2, "serdisp_clearbuffer(): leaving");
}



/* *********************************
   void serdisp_update(dd)
   *********************************
   update whole display
   *********************************
   dd     ... display descriptor
*/
void serdisp_update(serdisp_t* dd) {
  sd_debug(2, "serdisp_update(): entering");

  dd->fp_update(dd);
  sd_debug(2, "serdisp_update(): leaving");
}


/* *********************************
   void serdisp_clear(dd)
   *********************************
   clear whole display
   *********************************
   dd     ... display descriptor
*/
void serdisp_clear(serdisp_t* dd) {
  sd_debug(2, "serdisp_clear(): entering");

  serdisp_clearbuffer(dd);
  dd->fp_update(dd);
  sd_debug(2, "serdisp_clear(): leaving");
}


/* *********************************
   void serdisp_rewrite(dd)
   *********************************
   rewrite whole display
   *********************************
   dd     ... display descriptor
*/
void serdisp_rewrite(serdisp_t* dd) {
  sd_debug(2, "serdisp_rewrite(): entering");

  memset(dd->scrbuf_chg, 0xFF, dd->scrbuf_chg_size);
  dd->fp_update(dd);
  sd_debug(2, "serdisp_rewrite(): leaving");
}


/* *********************************
   int serdisp_reset(dd)
   *********************************
   resets the displays (re-inits display)
   *********************************
   dd     ... display descriptor
   *********************************
   returns 1 if reset was successful or 0 if not
*/
int serdisp_reset(serdisp_t* dd) {
  sd_debug(2, "serdisp_reset(): entering");

  /* close display (but NOt serdisp_close() because this one would also free dd) */
  dd->fp_close(dd);

  sleep(1);

  /* re-init display */
  dd->fp_init(dd);
  if (dd->feature_contrast) serdisp_setoption(dd, "CONTRAST", MAX_CONTRASTSTEP / 2);
  serdisp_rewrite(dd);
  sd_runtimeerror = 0;

  return (sd_runtimeerror) ? 0 : 1 ;
}



/* *********************************
   serdisp_t* serdisp_fullreset(dd)
   *********************************
   resets the display (clears runtime_error flag , closes and reopens device, re-inits display)
   *********************************
   dd     ... display descriptor
   *********************************
   returns new display descriptor if reset was successful or 0 if not
*/
serdisp_t* serdisp_fullreset(serdisp_t* dd) {
  serdisp_CONN_t* sdcd = dd->sdcd;
  char* sdcdev;
  char* dispname;
  char* optionstring;
  
  sd_debug(2, "serdisp_fullreset(): entering");

  /* full reset not supported for imported devices */
  if (!sdcd->sdcdev || strlen(sdcd->sdcdev) == 0) {
    sd_debug(1, "serdisp_fullreset(): device was imported using SDCONN_import_PP(). thus a full reset is not supported");
    sd_debug(1, "serdisp_fullreset(): serdisp_reset() will be used instead");
    return (serdisp_reset(dd)) ? dd : 0;
  }

  sdcdev = (char*) sdtools_malloc(strlen(sdcd->sdcdev)+1);
  dispname = (char*) sdtools_malloc(strlen(dd->dsp_name)+1);
  optionstring = (char*) sdtools_malloc(strlen(dd->dsp_optionstring)+1);
  
  if (!sdcdev || !dispname || !optionstring) {
    if (sdcdev) free(sdcdev);
    if (dispname) free(dispname);
    if (optionstring) free(optionstring);
    sd_runtimeerror = 1;
    sd_error(SERDISP_EMALLOC, "serdisp_reset() failed to allocate memory for temporary string space");
    return 0; /* unsuccessful reset */
  }  
  
  /* save current names, options, ... */
  strcpy(sdcdev, sdcd->sdcdev);
  strcpy(dispname, dd->dsp_name);
  strcpy(optionstring, dd->dsp_optionstring);
  
  /* quit display */
  serdisp_quit(dd);
  sleep(1);
  
  dd= 0;
  
  /* reopen device */
  sdcd = SDCONN_open(sdcdev);
  if (sdcd) {
    /* re-init display */
    dd = serdisp_init(sdcd, dispname, optionstring);
    if (dd) {
      sd_runtimeerror = 0;
      if (dd->feature_contrast) serdisp_setoption(dd, "CONTRAST", MAX_CONTRASTSTEP / 2);
      serdisp_rewrite(dd);
    } else {
      sd_error(SERDISP_ERUNTIME, "serdisp_reset() failed to initialise display %s", dispname);
      sd_runtimeerror = 1;
    }
  } else {
      sd_error(SERDISP_ERUNTIME, "serdisp_reset() failed to re-open device %s", sdcdev);
      sd_runtimeerror = 1;
  }

  /* free space needed for temporary string storage */
  if (sdcdev) free(sdcdev);
  if (dispname) free(dispname);
  if (optionstring) free(optionstring);

  return dd ;
}





/* *********************************
   void serdisp_blink(dd, what, cnt, delta)
   *********************************
   rewrite the whole display
   *********************************
   dd     ... display descriptor
   what   ... 0: blinking using backlight, 1: blinking using display reversing
   cnt    ... how often should there be blinking
   delta  ... delay between two blinking intervals
*/
void serdisp_blink(serdisp_t* dd, int what, int cnt, int delta) {
  int n;
  
  delta *= 1000;
  
  /* cnt will be cnt*2 because 10 times blinking ==> 20 times toggling */
  for (n = 1; n <= (cnt<<1); n++) {
    if (what == 0 && dd->feature_backlight) {
      serdisp_setoption(dd, "BACKLIGHT", SD_OPTION_TOGGLE);
      usleep(delta);
    } else if (what == 1 /* && dd->feature_invert*/) {
      serdisp_setoption(dd, "INVERT", SD_OPTION_TOGGLE);
      usleep(delta);
    }
  }
}



/* *********************************
   void serdisp_setpixels(dd,x,y,w,h,data)
   *********************************
   changes an area in the display buffer
   
   DEPRECATED!
   this functions only works for depths <= 8 and will be replaced through better functions
   *********************************
   dd     ... display descriptor
   x      ... x-position top/left
   y      ... y-position top/left
   w      ... width of content
   h      ... height of content
   data   ... data
   *********************************
*/
void serdisp_setpixels(serdisp_t* dd, int x, int y, int w, int h, byte* data) {
  int i,j;
  
  if (dd->depth <= 8)
    for (j = 0; j < h; j++)
      for (i = 0; i < w; i++)
        dd->fp_setpixel(dd, i+x, j+y, data[ j*w + i]);
#if 0  /* will not work -> better functions planned as replacement for serdisp_setpixels() */
  else /* 4 bytes per colour. never used/tested, but here for completeness */
    for (j = 0; j < h; j++)
      for (i = 0; i < w; i++)
        /* 4 bytes -> 1 int */
        dd->fp_setpixel(dd, i+x, j+y,  data[ 4*j*w + 4*i+0]      +
                                       (data[ 4*j*w + 4*i+1]<<8)  +
                                       (data[ 4*j*w + 4*i+2]<<16) +
                                       (data[ 4*j*w + 4*i+3]<<24)  );
#endif
}






/* *********************************
   int serdisp_getdispindex(dispname)
   *********************************
   returns index of of dispname in display array or -1 if not found
   *********************************
   dispname   ... display name
   *********************************
   returns index in display array or -1 if not found 
*/
int serdisp_getdispindex(const char* dispname) {
  int idx = 0;

  while (idx < sizeof(serdisp_displays) / sizeof(serdisp_setup_t) ) {
    if (sdtools_ismatching(serdisp_displays[idx].dispname, -1, dispname, -1) ||
        sdtools_isinelemlist(serdisp_displays[idx].aliasnames, dispname, -1) > -1
    ) {
      /* early exit */
      return idx;
    } else {
      idx++; 
    }
  }

  return -1;
}


/* *********************************
   int serdisp_getstandardoptionindex(optionnanme)
   *********************************
   returns index of of standardoption in standard options array or -1 if not found
   *********************************
   optionname   ... name of standard option
   *********************************
   returns index in standardoption array or -1 if not found 
*/
int serdisp_getstandardoptionindex(const char* optionname) {
  int idx = 0;

  while (idx < sizeof(serdisp_standardoptions) / sizeof(serdisp_options_t) ) {
    if (sdtools_ismatching(serdisp_standardoptions[idx].name, -1, optionname, -1) ||
        sdtools_isinelemlist(serdisp_standardoptions[idx].aliasnames, optionname, -1) > -1
    ) {
      /* early exit */
      return idx;
    } else {
      idx++; 
    }
  }

  return -1;
}



/* *********************************
   int serdisp_getoptionindex(dd, optionnanme)
   *********************************
   returns index of of driver specific option in options array or -1 if not found
   *********************************
   optionname   ... name of option
   *********************************
   returns index in driver specific option array or -1 if not found 
*/
int serdisp_getoptionindex(serdisp_t* dd, const char* optionname) {
  int idx = 0;

  if (!dd->options)
    return -1;

  while (idx < dd->amountoptions ) {
    if (sdtools_ismatching(dd->options[idx].name, -1, optionname, -1) ||
        sdtools_isinelemlist(dd->options[idx].aliasnames, optionname, -1) > -1
    ) {
      /* early exit */
      return idx;
    } else {
      idx++; 
    }
  }

  return -1;
}



/* *********************************
   int serdisp_comparedispnames(dispname1, dispname2)
   *********************************
   compares whether the two display names are equal and respectively aliases or not 
   *********************************
   dispname1   ... display name 1
   dispname2   ... display name 2
   *********************************
   returns 1 (equal or aliases) or 0 (not equal nor aliases)
*/
int serdisp_comparedispnames(const char* dispname1, const char* dispname2) {
  int i1 = serdisp_getdispindex(dispname1);
  int i2 = serdisp_getdispindex(dispname2);
  return ((i1 != -1) && (i1 == i2));
}


/* *********************************
   int serdisp_compareoptionnames(dd, optionname1, optionname2)
   *********************************
   compares whether the two option names are equal and respectively aliases or not 
   option has to be defined by specific driver (being known in standard options is not enough)
   *********************************
   dd            ... display descriptor
   optionname1   ... option name 1
   optionname2   ... option name 2
   *********************************
   returns 1 (equal or aliases) or 0 (not equal nor aliases)
*/
int serdisp_compareoptionnames(serdisp_t* dd, const char* optionname1, const char* optionname2) {
  int si1, si2, i1, i2;
  i1 = serdisp_getoptionindex(dd, optionname1);
  i2 = serdisp_getoptionindex(dd, optionname2);

  if (( i1 != -1) && ( i1 ==  i2))
    return 1;

  /* not equal / aliases when using only driver specific option definitions: try standard options */
  si1 = serdisp_getstandardoptionindex(optionname1);
  si2 = serdisp_getstandardoptionindex(optionname2);

  return (
    (si1 != -1) && (si1 == si2) && 
    /* either driver independent option (flag == 1) or also known in driver specific option array */
    ((serdisp_standardoptions[si1].flag == SD_OPTIONFLAG_STD) || 
     (serdisp_getoptionindex(dd, serdisp_standardoptions[si1].name) != -1)
    )
  );
}


/****************************/
/*                          */
/*   non public functions   */
/*                          */
/****************************/


char* serdisp_getwiresignalname(serdisp_t* dd, int idx) {
  int found = 0;
  int i = 0;
  while (!found && i < dd->amountwiresignals) {
    if ((dd->wiresignals[i].index == idx) && (dd->sdcd->conntype == dd->wiresignals[i].conntype))
      found = 1;
    else
      i++;
  }
  
  if (found)
    return dd->wiresignals[i].signalname;
  else
    return "";
}


/* *********************************
   int serdisp_setupwirings(sdcd, wiredef, wiredef_len)
   *********************************
   set up wirings
   *********************************
   dd          ... display descriptor
   wiredef     ... string describing wirings
   wiredef_len ... max. of characters to read from wiredef (or 0 if whole string is to be read)
   *********************************
   returns 0 if everything was ok or 1 if an error ocurred
*/
int serdisp_setupwirings(serdisp_t* dd, const char* wiredef, int wiredef_len) {

  char buffer[50];  /* needed for error messages */

  int i, found;
  long tempnum;
  char* tempptr;
  
  char* patternptr = (char*) wiredef;
  int   patternlen = -1;
  int   patternborder = (wiredef_len <= 0) ? strlen(wiredef) : wiredef_len;

  /* initialise io flags */
  dd->sdcd->io_flags_readstatus = 0;
  dd->sdcd->io_flags_writedata  = 0;
  dd->sdcd->io_flags_writecmd   = 0;
    
  /* no customisable wiring supported by this driver -> return */
  if (! dd->wiresignals && ! dd->wiredefs)
    return 0;

  /* catch some errors */
  if (! dd->wiresignals && dd->wiredefs) {
    sd_error(SERDISP_EINVAL, "array containing wiring definitions without array containing wiring items given");
    return 1;
  }

  if (dd->wiresignals && ! dd->wiredefs) {
    sd_error(SERDISP_EINVAL, "array containing wiring items without array containing wiring definitions given");
    return 1;
  }

  if (strlen(wiredef) <= 0) {
    sd_error(SERDISP_EINVAL, "no or invalid wiring definition given");
    return 1;
  }

  if (! dd->amountwiresignals || ! dd->amountwiredefs) {
    sd_error(SERDISP_EINVAL, "error in driver module: amountwiresignals and/or amountwiredefs undefined. check source code");
    return 1;
  }

  /* no customisable wiring for connection type -> return */
  found = 0;
  i = 0;
  while (!found && i < dd->amountwiredefs) {
    if (dd->wiredefs[i].conntype == dd->sdcd->conntype)
      found = 1;
    else
      i++;
  }
  
  if (!found)
    return 0;
    

  /* test if wiredef = predefined numeric id or string name */

  tempnum = strtol(patternptr, &tempptr, 10);
  /* verify if patternptr contained a valid number */
  if (patternptr == tempptr || ( (*tempptr != '\0') && (tempptr < (patternptr + patternborder)) ) )
    tempnum = -1;   /* wiredef != valid numeric value */
  i = 0;
  found = 0;
  while (!found && i < dd->amountwiredefs) {

    if (dd->sdcd->conntype == dd->wiredefs[i].conntype &&   /* connection matches */
         ( tempnum == dd->wiredefs[i].id ||                 /* wiredef = matching numeric id  or string name */
           sdtools_ismatching(patternptr, patternborder, dd->wiredefs[i].name, -1)
         )
       ) {
      /* found predefined numeric id or string name -> use corresponding wire definition */
      found = 1;
      patternptr = dd->wiredefs[i].definition;
      patternborder = strlen(patternptr);
    }
    i++;
  }


  /* parse wiredef */
  i = 0;
  while( (patternptr = sdtools_nextpattern(patternptr, ',', &patternlen, &patternborder)) ) {
    char* valueptr = 0;
    int valuelen = 0;
    char* idxpos = index(patternptr, ':');
    int keylen = patternlen;
    
    int tabidxkey = 0, tabidxvalue;

    /* ':' found and position not outside patternlen? */
    if (idxpos &&  serdisp_ptrstrlen(idxpos, patternptr) < patternlen ) {
      keylen = serdisp_ptrstrlen(idxpos, patternptr);
      valueptr = ++idxpos;
      valuelen = patternlen - keylen - 1;
    }

    /* no key/value pair: try DATA definitions */
    if ( !valueptr ) {
      if (i != 0) {
        sd_error(SERDISP_EINVAL, "error in wiring definition: a DATA-definition is permitted only at 1st position (eg: 'DATA8,CS:INIT,...')");
        return 1;
      }
      if (dd->sdcd->conntype != SERDISPCONNTYPE_PARPORT) {
        snprintf(buffer, ( (keylen >= 49) ? 50 : keylen+1), "%s", patternptr);
        sd_error(SERDISP_EINVAL, "error in wiring definition: %s is only allowed with parallel wiring", buffer );
        return 1;
      }
      if ( sdtools_ismatching( patternptr, keylen, "DATA4", -1) || sdtools_ismatching( patternptr, keylen, "DATA8", -1)) {
        dd->sdcd->signals[0] = SD_PP_D0;
        dd->sdcd->signals[1] = SD_PP_D1;
        dd->sdcd->signals[2] = SD_PP_D2;
        dd->sdcd->signals[3] = SD_PP_D3;
        
        dd->sdcd->io_flags_writedata |= SD_PP_WRITEDB;
        dd->sdcd->io_flags_readstatus |= SD_PP_READDB;        
      }
      if ( sdtools_ismatching( patternptr, keylen, "DATA8", -1)) {
        dd->sdcd->signals[4] = SD_PP_D4;
        dd->sdcd->signals[5] = SD_PP_D5;
        dd->sdcd->signals[6] = SD_PP_D6;
        dd->sdcd->signals[7] = SD_PP_D7;
      }
    } else {
      found = 0;  /* stays 0 when 'permanent on'-definition */
      
      /* "permanent on" - definitions (eg: 1:nSTRB) */
      if (sdtools_ismatching(patternptr, keylen, "1", -1) ) {
        tabidxvalue = SDCONN_getsignalindex(valueptr, dd->sdcd->conntype, dd->sdcd->hardwaretype);
  
        if (tabidxvalue == -1) {
          snprintf(buffer, ( (valuelen >= 49) ? 50 : valuelen+1), "%s", valueptr);
          sd_error(SERDISP_EINVAL, "error in wiring definition: value '%s' is unknown in this context", buffer );
          return 1;
        }
        dd->sdcd->signals_permon |= SDCONN_getsignalvalue(tabidxvalue);        
      } else {
        tabidxkey = 0;
        while (!found && tabidxkey < dd->amountwiresignals) {

          if ( dd->sdcd->conntype == dd->wiresignals[tabidxkey].conntype && 
               sdtools_ismatching(patternptr, keylen, dd->wiresignals[tabidxkey].signalname, -1) 
             ) {
            found = 1;
          } else
            tabidxkey++;
        }
        if (!found) {
          snprintf(buffer, ( (keylen >= 49) ? 50 : keylen+1), "%s", patternptr);
          sd_error(SERDISP_EINVAL, "error in wiring definition: key '%s' is unknown in this context", buffer );
          return 1;
        }

        tabidxvalue = SDCONN_getsignalindex(valueptr, dd->sdcd->conntype, dd->sdcd->hardwaretype);
  
        if (tabidxvalue == -1) {
          snprintf(buffer, ( (valuelen >= 49) ? 50 : valuelen+1), "%s", valueptr);
          sd_error(SERDISP_EINVAL, "error in wiring definition: value '%s' is unknown in this context", buffer );
          return 1;
        }

        dd->sdcd->signals[ dd->wiresignals[tabidxkey].index ] = SDCONN_getsignalvalue(tabidxvalue);
      }

      if (dd->sdcd->conntype == SERDISPCONNTYPE_PARPORT) {
        char cord = (!found) ? 'C' : dd->wiresignals[tabidxkey].cord;
        
        if ( SDCONN_getsignalvalue(tabidxvalue) & 0x000000FF ) {  /* data bits */
          dd->sdcd->io_flags_readstatus   = 0;
          if (cord == 'D' ) dd->sdcd->io_flags_writedata  |= SD_PP_WRITEDB;
          else if (cord == 'C' ) dd->sdcd->io_flags_writecmd  |= SD_PP_WRITEDB;
          if (cord == 'C') dd->sdcd->io_flags_readstatus  |= SD_PP_READDB;
        } else if ( SDCONN_getsignalvalue(tabidxvalue) & 0x0000FF00 ) {  /* status bits */
          if (cord == 'C') dd->sdcd->io_flags_readstatus  |= SD_PP_READSB;
        } else if ( SDCONN_getsignalvalue(tabidxvalue) & 0x00FF0000 ) {  /* control bits */
          if (cord == 'D') dd->sdcd->io_flags_writecmd  |= SD_PP_WRITECB;
          else if (cord == 'C') dd->sdcd->io_flags_writecmd  |= SD_PP_WRITECB;
        }
      }
      /* default flags when parameter 'flags' for SDCONN_write() is not set (== 0) */
      dd->sdcd->io_flags_default = dd->sdcd->io_flags_writedata;
    }
    i++;
  }

  
  /* calculate active-low/active-high clashes and adapt signals_invert according to these */

  dd->sdcd->signals_invert = 0L;

  for (i = 0; i < SD_MAX_SUPP_SIGNALS; i++) {
    if (dd->sdcd->signals[i]) {
      int activelow = SDCONN_isactivelow(dd->sdcd->signals[i], dd->sdcd->conntype, dd->sdcd->hardwaretype);
      int found;
      int j;
      /* signal == active-low, */
      
      j = 0;
      found = 0;
      while(!found && j < dd->amountwiresignals) {
        if (dd->wiresignals[j].index == i) {
          found = 1;
        } else {
          j++;
        }
      }
      if (found && activelow != dd->wiresignals[j].activelow) {
        dd->sdcd->signals_invert ^= dd->sdcd->signals[i];
      }
    }
  }
  return 0;
}



/* *********************************
   int serdisp_setupoptions(dd, dispname, optionstring)
   *********************************
   set up options
   *********************************
   dd                  ... display descriptor
   dispname            ... display name
   optionstring        ... string with options set by user / outside
   *********************************
   returns 0 if everything was ok or 1 if an error ocurred
*/
int serdisp_setupoptions(serdisp_t* dd, const char* dispname, const char* optionstring) {
  int optloop;
  char* optionptr;
  int optionlen;
  int optionborder;
  int dispidx = serdisp_getdispindex(dispname);

  char buffer[50];  /* needed for error messages */
  

  if (dispidx < 0) {   
    sd_error(SERDISP_EINVAL, "serdisp_setupoptions(): coding error: display name '%s' unknown", dispname);
    return 1;
  }

  /* loop two times: optloop #0: setup using default options (defaultoptstring),
                     optloop #1: setup using option string (optionstring) 
  */
  for (optloop = 0; optloop <= 1; optloop++) {
    optionptr = (char*) ((optloop == 0) ?  serdisp_displays[dispidx].defaultoptions : optionstring);
    optionlen = -1;
    optionborder = strlen(optionptr);

    /* ignore empty options string */
    if (optionborder < 1) 
      continue;
    
    /* parse option string */
    while( (optionptr = sdtools_nextpattern(optionptr, ';', &optionlen, &optionborder)) ) {
      char* valueptr = 0;
      int valuelen = 0;
      char* idxpos = index(optionptr, '=');
      int keylen = optionlen;

      int stdoptidx;   /* index of an option found in standard options */
      int foundstdopt;

      int optidx;      /* index of an option found in driver options array */
      int foundopt;
      
      char* defines;   /* string with defines (aliases)    eg:  NONE=0,OFF=0 */

      char* optvalueptr;
      int optvalueptrlen;
      
      int usestdopt;
      

      /* split key=value */
    
      /* '=' found and position not outside optionlen? */
      if (idxpos &&  serdisp_ptrstrlen(idxpos, optionptr) < optionlen ) {
        keylen = serdisp_ptrstrlen(idxpos, optionptr);
        valueptr = ++idxpos;
        valuelen = optionlen - keylen - 1;
      } else {  /* invalid option */
        snprintf(buffer, ( (keylen >= 49) ? 50 : keylen+1), "%s", optionptr);
        sd_error(SERDISP_EINVAL, "error in option string: no value given for option %s", buffer );
        return 1;
      }

      optvalueptr = valueptr;
      optvalueptrlen = valuelen;

      /* skip wiring definitions (handled elsewhere) */
      if ( strncasecmp(optionptr, "WIRING=", strlen("WIRING=")) == 0 ||
           strncasecmp(optionptr, "WIRE=", strlen("WIRE=")) == 0
         ) {
        continue;
      }
      
      /* look if option is found in standard options array */
      
      foundstdopt = 0;
      stdoptidx = 0;
      while (!foundstdopt && stdoptidx < sizeof(serdisp_standardoptions) / sizeof(serdisp_options_t) ) {
        if (sdtools_ismatching(optionptr, keylen, serdisp_standardoptions[stdoptidx].name, -1) ||
            (sdtools_isinelemlist(serdisp_standardoptions[stdoptidx].aliasnames, optionptr, keylen) > -1) )
          foundstdopt = 1;
        else
          stdoptidx++;
      }
      
      /* look if option is found in driver options array */

      foundopt = 0;
      optidx = 0;     
      while (!foundopt && optidx < dd->amountoptions ) {
        if (sdtools_ismatching(optionptr, keylen, dd->options[optidx].name, -1) ||
            (sdtools_isinelemlist(dd->options[optidx].aliasnames, optionptr, keylen) > -1) ||
            ( foundstdopt &&    /*  options name  in  stdoptions aliasnames ? */
              sdtools_ismatching(serdisp_standardoptions[stdoptidx].name, 
                                 strlen(serdisp_standardoptions[stdoptidx].name), dd->options[optidx].name, -1)  
            ) )
          foundopt = 1;
        else
          optidx++;
      }
      
      /* is option a driver independend standard option? */
      usestdopt = (foundstdopt && (serdisp_standardoptions[stdoptidx].flag == SD_OPTIONFLAG_STD));

      if (foundopt || usestdopt) {
        long optvalue;
        char* tempptr;
        serdisp_options_t curroption = (usestdopt) ? serdisp_standardoptions[stdoptidx] : dd->options[optidx];

        defines = curroption.defines;

        /* driver depend option but no defines: look if corresponding standard option has defines */
        if ( (!usestdopt) && foundstdopt && (strlen(defines) < 1))
          defines = serdisp_standardoptions[stdoptidx].defines;

        /* look if optvalue is a define (eg: NONE=0) */
        if (strlen(defines) > 0) {
          char* defineptr = defines;
          int definelen = -1;
          int defineborder = strlen(defineptr);
          int definefound = 0;

          while( !definefound && (defineptr = sdtools_nextpattern(defineptr, ',', &definelen, &defineborder)) ) {
            char* defineidxpos = index(defineptr, '=');
            int definekeylen = definelen;
            char* definevalueptr = 0;
            int definevaluelen = 0;

            /* '=' found and position not outside patternlen? */
            if (defineidxpos &&  serdisp_ptrstrlen(defineidxpos, defineptr) < definelen ) {
              definekeylen = serdisp_ptrstrlen(defineidxpos, defineptr);
              definevalueptr = ++defineidxpos;
              definevaluelen = definelen - definekeylen - 1;
            } else {  /* invalid define. should NEVER EVER occur (else: error in source code) */
              snprintf(buffer, ( (definekeylen >= 49) ? 50 : definekeylen+1), "%s", defineptr);
              sd_error(SERDISP_EINVAL, "coding error in define string: no value given for define %s", buffer );
              return 1;
            }

            if (sdtools_ismatching(valueptr, valuelen, defineptr, definekeylen) ) {
              definefound = 1;
              optvalueptr = definevalueptr;
              optvalueptrlen = definevaluelen;      
            }

          }
        }

        optvalue = strtol(optvalueptr, &tempptr, 10);
        /* verify if optvalueptr contained a valid number */
        if (optvalueptr == tempptr || ( (*tempptr != '\0') && (tempptr < (optvalueptr + optvalueptrlen)) ) ) {
          snprintf(buffer, ( (optvalueptrlen >= 49) ? 50 : optvalueptrlen+1), "%s", optvalueptr);
          sd_error(SERDISP_EINVAL, "invalid option %s", buffer );
          return 1;
        }

        /* check minval, maxval, modulo */
        if ( (curroption.minval != -1L && optvalue < curroption.minval) ||
             (curroption.maxval != -1L && optvalue > curroption.maxval) ||
             (curroption.modulo >= 1L && (optvalue % curroption.modulo != 0))
            ) {
          snprintf(buffer, ( (optvalueptrlen >= 49) ? 50 : optvalueptrlen+1), "%s", optvalueptr);
          sd_error(SERDISP_EINVAL, "option %s breaks mininum, maximum, or modulo rules", buffer );
          return 1;
        }

        /* assign option value to dd-structure variable */
        if (foundstdopt) {
          /* this should be solved in a better way ... */
          if (strcasecmp(curroption.name, "CONTRAST") == 0)
            dd->curr_contrast = (int)optvalue;
          else if (strcasecmp(curroption.name, "BACKLIGHT") == 0)
            dd->curr_backlight = (int)optvalue;
          else if (strcasecmp(curroption.name, "INVERT") == 0)
            dd->curr_invert = (int)optvalue;
          else if (strcasecmp(curroption.name, "ROTATE") == 0)
            dd->curr_rotate = (int)optvalue;
          else if (strcasecmp(curroption.name, "WIDTH") == 0)
            dd->width = (int)optvalue;
          else if (strcasecmp(curroption.name, "HEIGHT") == 0)
            dd->height = (int)optvalue;
          else if (strcasecmp(curroption.name, "DEPTH") == 0)
            dd->depth = (int)optvalue;
          else if (strcasecmp(curroption.name, "DELAY") == 0)
            dd->delay = optvalue;
          
        } else {  /* non standard option */
          if (dd->fp_getvalueptr) {
            int typesize;
            void* specific_valptr;
            
            specific_valptr = dd->fp_getvalueptr(dd, curroption.name, &typesize);
            if (!specific_valptr) { /* should NEVER EVER occur (else: error in source code) */
              sd_error(SERDISP_EINVAL, "coding error. specific value %s unknown to dd->fp_getvalueptr()", curroption.name);
              return 1;
            }
            
            switch (typesize) {
              case 1:  *((byte*) specific_valptr) = (byte) optvalue; break;
              case 2:  *((short*) specific_valptr) = (short) optvalue; break;
              case 4:  *((long*) specific_valptr) = (long) optvalue; break;
            }

          } else {  /* spec value, but no function pointer: should NEVER EVER occur (else: error in source code) */
            sd_error(SERDISP_EINVAL, "coding error. no function pointer given for dd->fp_getvalueptr()");
            return 1;
          }
        }

      } else {
        snprintf(buffer, ( (optionlen >= 49) ? 50 : optionlen+1), "%s", optionptr);
        sd_debug(0, "*** WARNING: option %s unsupported by this driver", buffer );
      }      
    }    
  }

  return 0;
}




/* *********************************
   void serdisp_freeresources(dd)
   *********************************
   frees all resources allocated by serdisplib-instance
   *********************************
   dd     ... display descriptor
   *********************************
*/
void serdisp_freeresources(serdisp_t* dd) {
  if (dd->scrbuf)
    free(dd->scrbuf);
  if (dd->scrbuf_chg)
    free(dd->scrbuf_chg);
  if(dd->specific_data)
    free(dd->specific_data);
  if(dd->xreloctab)
    free(dd->xreloctab);
  if(dd->yreloctab)
    free(dd->yreloctab);
  if(dd->ctable)
    free(dd->ctable);
  free(dd);
  dd = 0;
}
