/* $Id$ * $URL$ * * new style driver for Crystalfontz display modules * * Copyright (C) 1999, 2000 Michael Reinelt * Copyright (C) 2004 The LCD4Linux Team * * 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. * */ /* * * exported fuctions: * * struct DRIVER drv_Crystalfontz * */ #include "config.h" #include #include #include #include #include #include "debug.h" #include "cfg.h" #include "qprintf.h" #include "thread.h" #include "timer.h" #include "plugin.h" #include "widget.h" #include "widget_text.h" #include "widget_icon.h" #include "widget_bar.h" #include "widget_keypad.h" #include "drv.h" #include "drv_generic_text.h" #include "drv_generic_gpio.h" #include "drv_generic_serial.h" #include "drv_generic_keypad.h" static char Name[] = "Crystalfontz"; static int Model; static int Protocol; static int Payload; /* ring buffer for bytes received from the display */ static unsigned char RingBuffer[256]; static unsigned int RingRPos = 0; static unsigned int RingWPos = 0; /* packet from the display */ struct { unsigned char type; unsigned char code; unsigned char size; unsigned char data[16 + 1]; /* trailing '\0' */ } Packet; /* Line Buffer for 633 displays */ static unsigned char Line[2 * 16]; /* Fan RPM */ static double Fan_RPM[4] = { 0.0, }; /* Temperature sensors */ static double Temperature[32] = { 0.0, }; typedef struct { int type; char *name; int rows; int cols; int gpis; int gpos; int protocol; int payload; } MODEL; /* Fixme #1: number of GPI's & GPO's should be verified */ /* Fixme #2: protocol should be verified */ /* Fixme #3: number of keys on the keypad should be verified */ static MODEL Models[] = { {626, "626", 2, 16, 0, 0, 1, 0}, {631, "631", 2, 20, 4, 0, 3, 22}, {632, "632", 2, 16, 0, 0, 1, 0}, {633, "633", 2, 16, 4, 4, 2, 18}, {634, "634", 4, 20, 0, 0, 1, 0}, {635, "635", 4, 20, 4, 12, 3, 22}, {636, "636", 2, 16, 0, 0, 1, 0}, {-1, "Unknown", -1, -1, 0, 0, 0, 0} }; /****************************************/ /*** hardware dependant functions ***/ /****************************************/ /* x^0 + x^5 + x^12 */ #define CRCPOLY 0x8408 static unsigned short CRC(const unsigned char *p, size_t len, unsigned short seed) { int i; while (len--) { seed ^= *p++; for (i = 0; i < 8; i++) seed = (seed >> 1) ^ ((seed & 1) ? CRCPOLY : 0); } return ~seed; } static unsigned char LSB(const unsigned short word) { return word & 0xff; } static unsigned char MSB(const unsigned short word) { return word >> 8; } static unsigned char byte(unsigned int pos) { pos += RingRPos; if (pos >= sizeof(RingBuffer)) pos -= sizeof(RingBuffer); return RingBuffer[pos]; } static void drv_CF_process_packet(void) { switch (Packet.type) { case 0x02: /* async response from display to host */ switch (Packet.code) { case 0x00: /* Key Activity */ debug("Key Activity: %d", Packet.data[0]); drv_generic_keypad_press(Packet.data[0]); break; case 0x01: /* Fan Speed Report */ if (Packet.data[1] == 0xff) { Fan_RPM[Packet.data[0]] = -1.0; } else if (Packet.data[1] < 4) { Fan_RPM[Packet.data[0]] = 0.0; } else { Fan_RPM[Packet.data[0]] = (double) 27692308L *(Packet.data[1] - 3) / (Packet.data[2] + 256 * Packet.data[3]); } break; case 0x02: /* Temperature Sensor Report */ switch (Packet.data[3]) { case 0: error("%s: 1-Wire device #%d: CRC error", Name, Packet.data[0]); break; case 1: case 2: Temperature[Packet.data[0]] = (Packet.data[1] + 256 * Packet.data[2]) / 16.0; break; default: error("%s: 1-Wire device #%d: unknown CRC status %d", Name, Packet.data[0], Packet.data[3]); break; } break; default: /* this should not happen */ error("%s: unexpected response type=0x%02x code=0x%02x size=%d", Name, Packet.type, Packet.code, Packet.size); break; } break; case 0x03: /* error response from display to host */ error("%s: error response type=0x%02x code=0x%02x size=%d", Name, Packet.type, Packet.code, Packet.size); break; default: /* these should not happen: */ /* type 0x00: command from host to display: should never come back */ /* type 0x01: command response from display to host: are processed within send() */ error("%s: unexpected packet type=0x%02x code=0x%02x size=%d", Name, Packet.type, Packet.code, Packet.size); break; } } static int drv_CF_poll(void) { /* read into RingBuffer */ while (1) { char buffer[32]; int num, n; num = drv_generic_serial_poll(buffer, sizeof(buffer)); if (num <= 0) break; /* put result into RingBuffer */ for (n = 0; n < num; n++) { RingBuffer[RingWPos++] = (unsigned char) buffer[n]; if (RingWPos >= sizeof(RingBuffer)) RingWPos = 0; } } /* process RingBuffer */ while (1) { unsigned char buffer[32]; int n, num, size; unsigned short crc; /* packet size */ num = RingWPos - RingRPos; if (num < 0) num += sizeof(RingBuffer); /* minimum packet size=4 */ if (num < 4) return 0; /* valid response types: 01xxxxx 10.. 11.. */ /* therefore: 00xxxxxx is invalid */ if (byte(0) >> 6 == 0) goto GARBAGE; /* command length */ size = byte(1); /* valid command length is 0 to 16 */ if (size > 16) goto GARBAGE; /* all bytes available? */ if (num < size + 4) return 0; /* check CRC */ for (n = 0; n < size + 4; n++) buffer[n] = byte(n); crc = CRC(buffer, size + 2, 0xffff); if (LSB(crc) != buffer[size + 2]) goto GARBAGE; if (MSB(crc) != buffer[size + 3]) goto GARBAGE; /* process packet */ Packet.type = buffer[0] >> 6; Packet.code = buffer[0] & 0x3f; Packet.size = size; memcpy(Packet.data, buffer + 2, size); Packet.data[size] = '\0'; /* trailing zero */ /* increment read pointer */ RingRPos += size + 4; if (RingRPos >= sizeof(RingBuffer)) RingRPos -= sizeof(RingBuffer); /* a packet arrived */ return 1; GARBAGE: debug("dropping garbage byte %02x", byte(0)); RingRPos++; if (RingRPos >= sizeof(RingBuffer)) RingRPos = 0; continue; } /* not reached */ return 0; } static void drv_CF_timer(void __attribute__ ((unused)) * notused) { while (drv_CF_poll()) { drv_CF_process_packet(); } } static void drv_CF_send(const unsigned char cmd, const unsigned char len, const unsigned char *data) { /* 1 cmd + 1 len + 22 payload + 2 crc = 26 */ unsigned char buffer[26]; unsigned short crc; struct timeval now, end; if (len > Payload) { error("%s: internal error: packet length %d exceeds payload size %d", Name, len, Payload); return; } buffer[0] = cmd; buffer[1] = len; memcpy(buffer + 2, data, len); crc = CRC(buffer, len + 2, 0xffff); buffer[len + 2] = LSB(crc); buffer[len + 3] = MSB(crc); drv_generic_serial_write((char *) buffer, len + 4); /* wait for acknowledge packet */ gettimeofday(&now, NULL); while (1) { /* delay 1 msec */ usleep(1 * 1000); if (drv_CF_poll()) { if (Packet.type == 0x01 && Packet.code == cmd) { /* this is the ack we're waiting for */ if (0) { gettimeofday(&end, NULL); debug("%s: ACK after %ld usec", Name, 1000000 * (end.tv_sec - now.tv_sec) + end.tv_usec - now.tv_usec); } break; } else { /* some other (maybe async) packet, just process it */ drv_CF_process_packet(); } } gettimeofday(&end, NULL); /* don't wait more than 250 msec */ if ((1000000 * (end.tv_sec - now.tv_sec) + end.tv_usec - now.tv_usec) > 250 * 1000) { error("%s: timeout waiting for response to cmd 0x%02x", Name, cmd); break; } } } /* check http://www.crystalfontz.com/products/634/cgrom.html */ /* HINT: the input should using the ISO-8859-1 charset */ void convertToCgrom2(char *str) { unsigned int i; for (i = 0; i < strlen(str); i++) { switch ((unsigned char) str[i]) { case 0x5d: /* ] */ str[i] = 252; break; case 0x5b: /* [ */ str[i] = 250; break; case 0x24: /* $ */ str[i] = 162; break; case 0x40: /* @ */ str[i] = 160; break; case 0x5c: /* \ */ str[i] = 251; break; case 0x7b: /* { */ str[i] = 253; break; case 0x7d: /* } */ str[i] = 255; break; case 0x7c: str[i] = 254; /* pipe */ break; case 0x27: case 0x60: case 0xB4: str[i] = 39; /* ' */ break; case 0xe8: str[i] = 164; /* french e */ break; case 0xe9: str[i] = 165; /* french e */ break; case 0xc8: str[i] = 197; /* french E */ break; case 0xc9: str[i] = 207; /* french E */ break; case 0xe4: str[i] = 123; /* small german ae */ break; case 0xc4: str[i] = 91; /* big german ae */ break; case 0xf6: str[i] = 124; /* small german oe */ break; case 0xd6: str[i] = 92; /* big german oe */ break; case 0xfc: str[i] = 126; /* small german ue */ break; case 0xdc: str[i] = 94; /* big german ue */ break; case 0x5e: /* ^ */ str[i] = 253; break; case 0x5f: /* _ */ str[i] = 254; break; default: break; } } } static void drv_CF_write1(const int row, const int col, const char *data, const int len) { char cmd[3] = "\021xy"; /* set cursor position */ if (row == 0 && col == 0) { drv_generic_serial_write("\001", 1); /* cursor home */ } else { cmd[1] = (char) col; cmd[2] = (char) row; drv_generic_serial_write(cmd, 3); } /* Model 634 and 632 use another ROM */ if (Model == 4 || Model == 2) { convertToCgrom2((char *) data); } drv_generic_serial_write(data, len); } static void drv_CF_write2(const int row, const int col, const char *data, const int len) { int l = len; /* limit length */ if (col + l > 16) l = 16 - col; if (l < 0) l = 0; /* sanity check */ if (row >= 2 || col + l > 16) { error("%s: internal error: write outside linebuffer bounds!", Name); return; } memcpy(Line + 16 * row + col, data, l); drv_CF_send(7 + row, 16, (unsigned char *) (Line + 16 * row)); } static void drv_CF_write3(const int row, const int col, const char *data, const int len) { int l = len; unsigned char cmd[23]; /* limit length */ if (col + l > DCOLS) l = DCOLS - col; if (l < 0) l = 0; /* sanity check */ if (row >= DROWS || col + l > DCOLS) { error("%s: internal error: write outside display bounds!", Name); return; } cmd[0] = col; cmd[1] = row; memcpy(cmd + 2, data, l); drv_CF_send(31, l + 2, cmd); } static void drv_CF_defchar1(const int ascii, const unsigned char *matrix) { int i; char cmd[10] = "\031n"; /* set custom char bitmap */ /* user-defineable chars start at 128, but are defined at 0 */ cmd[1] = (char) (ascii - CHAR0); for (i = 0; i < 8; i++) { cmd[i + 2] = matrix[i] & 0x3f; } drv_generic_serial_write(cmd, 10); } static void drv_CF_defchar23(const int ascii, const unsigned char *matrix) { int i; unsigned char buffer[9]; /* use
/* $Id$
 * $URL$
 * 
 * driver for WincorNixdorf serial cashier displays BA63 and BA66
 * 
 * Copyright (C) 2005 Michael Reinelt <michael@reinelt.co.at>
 * Copyright (C) 2005 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * based on the SimpleLCD driver which is
 * Copyright (C) 2005 Julien Aube <ob@obconseil.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.
 *
 */

/* 
 *
 * exported fuctions:
 *
 * struct DRIVER drv_WincorNixdorf
 *
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>

#include "debug.h"
#include "cfg.h"
#include "qprintf.h"
#include "plugin.h"
#include "widget.h"
#include "widget_text.h"
#include "widget_bar.h"
#include "drv.h"
#include "drv_generic_text.h"
#include "drv_generic_serial.h"


#define ESC "\033"


static char Name[] = "WincorNixdorf";

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

static MODEL Models[] = {
    {63, "BA63", 2, 20},
    {66, "BA66", 4, 20},
    {-1, "unknown", -1, -1},
};

static int Model;


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

static void drv_WN_clear(void)
{
    drv_generic_serial_write(ESC "[2J", 4);
}


static void drv_WN_write(const int row, const int col, const char *data, int len)
{
    char cmd[8] = ESC "[r;ccH";

    cmd[2] = '1' + row;
    cmd[4] = '0' + (col / 10);
    cmd[5] = '1' + (col % 10);

    drv_generic_serial_write(cmd, 7);
    drv_generic_serial_write(data, len);
}


static int drv_WN_start(const char *section, const int quiet)
{
    int i, len;
    int selftest;
    char *model = NULL;
    char buffer[32];

    model = cfg_get(section, "Model", NULL);
    if (model == NULL && *model == '\0') {
	error("%s: no '%s.Model' entry from %s", Name, section, cfg_source());
	return -1;
    }

    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;
    info("%s: using model '%s'", Name, Models[Model].name);

    /* initialize global variables */
    DROWS = Models[Model].rows;
    DCOLS = Models[Model].cols;

    if (drv_generic_serial_open(section, Name, CS8 | PARENB | PARODD) < 0)
	return -1;

    /* real worker functions */
    drv_generic_text_real_write = drv_WN_write;

    cfg_number(section, "SelfTest", 0, 0, 1, &selftest);
    if (selftest) {
	info("%s: initiating display selftest sequence", Name);

	/* read display identification */
	drv_generic_serial_write(ESC "[0c", 4);
	usleep(100 * 1000);

	if ((len = drv_generic_serial_read(buffer, -1 * (int) sizeof(buffer))) > 0) {
	    info("%s: waiting 15 seconds for selftest", Name);
	    drv_generic_serial_write(buffer, len);
	    sleep(15);
	    info("%s: selftest finished", Name);
	} else {
	    info("%s: selftest initiation failed", Name);
	}
    }

    /* clear display */
    drv_WN_clear();

    if (!quiet) {
	char buffer[40];
	qprintf(buffer, sizeof(buffer), "%s %dx%d", Name, DCOLS, DROWS);
	if (drv_generic_text_greet(buffer, NULL)) {
	    sleep(3);
	    drv_WN_clear();
	}
    }

    return 0;
}


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

/* none */


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


/* using drv_generic_text_draw(W) */


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


/* list models */
int drv_WN_list(void)
{
    printf("BA63 BA66");
    return 0;
}


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

    info("%s: %s", Name, "$Rev$");

    /* display preferences */
    XRES = 5;			/* pixel width of one char  */
    YRES = 7;			/* pixel height of one char  */
    CHARS = 0;			/* number of user-defineable characters */
    CHAR0 = 0;			/* ASCII of first user-defineable char */
    ICONS = 0;			/* number of user-defineable characters reserved for icons */
    GOTO_COST = 6;		/* number of bytes a goto command requires */

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

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

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

    cfg_number(section, "BarChar", '*', 1, 255, &ascii);

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

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

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

    /* register plugins */
    /* none */

    return 0;
}


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

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

    drv_generic_text_quit();

    /* clear display */
    drv_WN_clear();

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

    drv_generic_serial_close();

    return (0);
}


DRIVER drv_WincorNixdorf = {
    .name = Name,
    .list = drv_WN_list,
    .init = drv_WN_init,
    .quit = drv_WN_quit,
};