aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormichael <michael@3ae390bd-cb1e-0410-b409-cd5a39f66f1f>2012-02-14 03:17:04 +0000
committermichael <michael@3ae390bd-cb1e-0410-b409-cd5a39f66f1f>2012-02-14 03:17:04 +0000
commitcab1c1a4002c5967a26f1ecd1e6384c0f890d454 (patch)
treee95f079a865f981e06324cc963492b98b37f6923
parentaae7a3ee3765f10fc707a2d0d14446a30608ae4f (diff)
downloadlcd4linux-cab1c1a4002c5967a26f1ecd1e6384c0f890d454.tar.gz
driver for TeakLCM by Andreas Thienemann
git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@1173 3ae390bd-cb1e-0410-b409-cd5a39f66f1f
-rw-r--r--Makefile.am1
-rw-r--r--Makefile.in48
-rw-r--r--aclocal.m415
-rw-r--r--config.h.in3
-rwxr-xr-xconfigure24
-rw-r--r--drivers.m414
-rw-r--r--drv.c4
-rw-r--r--drv_TeakLCM.c1024
-rw-r--r--lcd4linux.conf.sample11
-rwxr-xr-xsmoketest.sh2
10 files changed, 1113 insertions, 33 deletions
diff --git a/Makefile.am b/Makefile.am
index b1a4ed2..894fbd8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -114,6 +114,7 @@ drv_serdisplib.c \
drv_ShuttleVFD.c \
drv_SimpleLCD.c \
drv_T6963.c \
+drv_TeakLCM.c \
drv_Trefon.c \
drv_ula200.c \
drv_USBHUB.c \
diff --git a/Makefile.in b/Makefile.in
index 8f2fb2d..95306f1 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1,4 +1,4 @@
-# Makefile.in generated by automake 1.11.2 from Makefile.am.
+# Makefile.in generated by automake 1.11.3 from Makefile.am.
# @configure_input@
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
@@ -97,9 +97,11 @@ DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
distdir = $(PACKAGE)-$(VERSION)
top_distdir = $(distdir)
am__remove_distdir = \
- { test ! -d "$(distdir)" \
- || { find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
- && rm -fr "$(distdir)"; }; }
+ if test -d "$(distdir)"; then \
+ find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
+ && rm -rf "$(distdir)" \
+ || { sleep 5 && rm -rf "$(distdir)"; }; \
+ else :; fi
DIST_ARCHIVES = $(distdir).tar.gz
GZIP_ENV = --best
distuninstallcheck_listfiles = find . -type f -print
@@ -348,6 +350,7 @@ drv_serdisplib.c \
drv_ShuttleVFD.c \
drv_SimpleLCD.c \
drv_T6963.c \
+drv_TeakLCM.c \
drv_Trefon.c \
drv_ula200.c \
drv_USBHUB.c \
@@ -516,7 +519,7 @@ clean-binPROGRAMS:
list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
echo " rm -f" $$list; \
rm -f $$list
-lcd4linux$(EXEEXT): $(lcd4linux_OBJECTS) $(lcd4linux_DEPENDENCIES)
+lcd4linux$(EXEEXT): $(lcd4linux_OBJECTS) $(lcd4linux_DEPENDENCIES) $(EXTRA_lcd4linux_DEPENDENCIES)
@rm -f lcd4linux$(EXEEXT)
$(lcd4linux_LINK) $(lcd4linux_OBJECTS) $(lcd4linux_LDADD) $(LIBS)
@@ -566,6 +569,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_ShuttleVFD.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_SimpleLCD.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_T6963.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_TeakLCM.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_Trefon.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_USBHUB.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_USBLCD.Po@am__quote@
@@ -782,6 +786,10 @@ dist-bzip2: distdir
tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2
$(am__remove_distdir)
+dist-lzip: distdir
+ tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz
+ $(am__remove_distdir)
+
dist-lzma: distdir
tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma
$(am__remove_distdir)
@@ -818,6 +826,8 @@ distcheck: dist
bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
*.tar.lzma*) \
lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\
+ *.tar.lz*) \
+ lzip -dc $(distdir).tar.lz | $(am__untar) ;;\
*.tar.xz*) \
xz -dc $(distdir).tar.xz | $(am__untar) ;;\
*.tar.Z*) \
@@ -1006,20 +1016,20 @@ uninstall-am: uninstall-binPROGRAMS
.PHONY: CTAGS GTAGS all all-am am--refresh check check-am clean \
clean-binPROGRAMS clean-generic clean-libtool ctags dist \
- dist-all dist-bzip2 dist-gzip dist-lzma dist-shar dist-tarZ \
- dist-xz dist-zip distcheck distclean distclean-compile \
- distclean-generic distclean-hdr distclean-libtool \
- distclean-tags distcleancheck distdir distuninstallcheck dvi \
- dvi-am html html-am info info-am install install-am \
- install-binPROGRAMS install-data install-data-am install-dvi \
- install-dvi-am install-exec install-exec-am install-html \
- install-html-am install-info install-info-am install-man \
- install-pdf install-pdf-am install-ps install-ps-am \
- install-strip installcheck installcheck-am installdirs \
- maintainer-clean maintainer-clean-generic mostlyclean \
- mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
- pdf pdf-am ps ps-am tags uninstall uninstall-am \
- uninstall-binPROGRAMS
+ dist-all dist-bzip2 dist-gzip dist-lzip dist-lzma dist-shar \
+ dist-tarZ dist-xz dist-zip distcheck distclean \
+ distclean-compile distclean-generic distclean-hdr \
+ distclean-libtool distclean-tags distcleancheck distdir \
+ distuninstallcheck dvi dvi-am html html-am info info-am \
+ install install-am install-binPROGRAMS install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags uninstall uninstall-am uninstall-binPROGRAMS
# create subversion version
diff --git a/aclocal.m4 b/aclocal.m4
index e901394..7b632e4 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -1,4 +1,4 @@
-# generated automatically by aclocal 1.11.2 -*- Autoconf -*-
+# generated automatically by aclocal 1.11.3 -*- Autoconf -*-
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
# 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation,
@@ -10130,7 +10130,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION],
[am__api_version='1.11'
dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
dnl require some minimum version. Point them to the right macro.
-m4_if([$1], [1.11.2], [],
+m4_if([$1], [1.11.3], [],
[AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
])
@@ -10146,7 +10146,7 @@ m4_define([_AM_AUTOCONF_VERSION], [])
# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
-[AM_AUTOMAKE_VERSION([1.11.2])dnl
+[AM_AUTOMAKE_VERSION([1.11.3])dnl
m4_ifndef([AC_AUTOCONF_VERSION],
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
@@ -11005,7 +11005,7 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
# Check how to create a tarball. -*- Autoconf -*-
-# Copyright (C) 2004, 2005 Free Software Foundation, Inc.
+# Copyright (C) 2004, 2005, 2012 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -11027,10 +11027,11 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
# a tarball read from stdin.
# $(am__untar) < result.tar
AC_DEFUN([_AM_PROG_TAR],
-[# Always define AMTAR for backward compatibility.
-AM_MISSING_PROG([AMTAR], [tar])
+[# Always define AMTAR for backward compatibility. Yes, it's still used
+# in the wild :-( We should find a proper way to deprecate it ...
+AC_SUBST([AMTAR], ['$${TAR-tar}'])
m4_if([$1], [v7],
- [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'],
+ [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'],
[m4_case([$1], [ustar],, [pax],,
[m4_fatal([Unknown tar format])])
AC_MSG_CHECKING([how to create a $1 tar archive])
diff --git a/config.h.in b/config.h.in
index 97c3e84..7fdebfc 100644
--- a/config.h.in
+++ b/config.h.in
@@ -621,6 +621,9 @@
/* T6963 driver */
#undef WITH_T6963
+/* TeakLCM driver */
+#undef WITH_TEAK_LCM
+
/* TREFON driver */
#undef WITH_TREFON
diff --git a/configure b/configure
index 40be149..1e1e1f6 100755
--- a/configure
+++ b/configure
@@ -1449,7 +1449,7 @@ Optional Packages:
Newhaven, Noritake, NULL, Pertelian, PHAnderson,
PICGraphic, picoLCD, picoLCDGraphic, PNG, PPM, RouterBoard,
Sample, serdisplib, ShuttleVFD, SimpleLCD, st2205, T6963,
- Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11
+ TeakLCM, Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11
--with-plugins=<list> choose which plugins to compile.
type --with-plugins=list for a list
of avaible plugins
@@ -2821,11 +2821,11 @@ MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
# We need awk for the "check" target. The system "awk" is bad on
# some platforms.
-# Always define AMTAR for backward compatibility.
+# Always define AMTAR for backward compatibility. Yes, it's still used
+# in the wild :-( We should find a proper way to deprecate it ...
+AMTAR='$${TAR-tar}'
-AMTAR=${AMTAR-"${am_missing_run}tar"}
-
-am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'
+am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'
@@ -6317,6 +6317,7 @@ for driver in $drivers; do
SHUTTLEVFD="yes"
SIMPLELCD="yes"
T6963="yes"
+ TeakLCM="yes"
Trefon="yes"
ULA200="yes"
USBHUB="yes"
@@ -6460,6 +6461,9 @@ for driver in $drivers; do
T6963)
T6963=$val
;;
+ TeakLCM)
+ TeakLCM=$val
+ ;;
Trefon)
Trefon=$val
;;
@@ -7081,6 +7085,16 @@ $as_echo "$as_me: WARNING: asm/io.h or {linux/parport.h and linux/ppdev.h} not f
fi
fi
+if test "$TeakLCM" = "yes"; then
+ TEXT="yes"
+ GPIO="no"
+ SERIAL="yes"
+ DRIVERS="$DRIVERS drv_TeakLCM.o"
+
+$as_echo "#define WITH_TEAK_LCM 1" >>confdefs.h
+
+fi
+
if test "$Trefon" = "yes"; then
if test "$has_usb" = "true"; then
TEXT="yes"
diff --git a/drivers.m4 b/drivers.m4
index 09bd152..f9f289d 100644
--- a/drivers.m4
+++ b/drivers.m4
@@ -39,7 +39,7 @@ AC_ARG_WITH(
[ Newhaven, Noritake, NULL, Pertelian, PHAnderson,]
[ PICGraphic, picoLCD, picoLCDGraphic, PNG, PPM, RouterBoard,]
[ Sample, serdisplib, ShuttleVFD, SimpleLCD, st2205, T6963,]
- [ Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11],
+ [ TeakLCM, Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11],
drivers=$withval,
drivers=all
)
@@ -105,6 +105,7 @@ for driver in $drivers; do
SHUTTLEVFD="yes"
SIMPLELCD="yes"
T6963="yes"
+ TeakLCM="yes"
Trefon="yes"
ULA200="yes"
USBHUB="yes"
@@ -248,6 +249,9 @@ for driver in $drivers; do
T6963)
T6963=$val
;;
+ TeakLCM)
+ TeakLCM=$val
+ ;;
Trefon)
Trefon=$val
;;
@@ -752,6 +756,14 @@ if test "$T6963" = "yes"; then
fi
fi
+if test "$TeakLCM" = "yes"; then
+ TEXT="yes"
+ GPIO="no"
+ SERIAL="yes"
+ DRIVERS="$DRIVERS drv_TeakLCM.o"
+ AC_DEFINE(WITH_TEAK_LCM,1,[TeakLCM driver])
+fi
+
if test "$Trefon" = "yes"; then
if test "$has_usb" = "true"; then
TEXT="yes"
diff --git a/drv.c b/drv.c
index 32883a8..9ce8a39 100644
--- a/drv.c
+++ b/drv.c
@@ -90,6 +90,7 @@ extern DRIVER drv_serdisplib;
extern DRIVER drv_ShuttleVFD;
extern DRIVER drv_SimpleLCD;
extern DRIVER drv_T6963;
+extern DRIVER drv_TeakLCM;
extern DRIVER drv_Trefon;
extern DRIVER drv_ula200;
extern DRIVER drv_USBHUB;
@@ -236,6 +237,9 @@ DRIVER *Driver[] = {
#ifdef WITH_T6963
&drv_T6963,
#endif
+#ifdef WITH_TEAK_LCM
+ &drv_TeakLCM,
+#endif
#ifdef WITH_TREFON
&drv_Trefon,
#endif
diff --git a/drv_TeakLCM.c b/drv_TeakLCM.c
new file mode 100644
index 0000000..7517b40
--- /dev/null
+++ b/drv_TeakLCM.c
@@ -0,0 +1,1024 @@
+/* $Id$
+ * $URL$
+ *
+ * TeakLCM lcd4linux driver
+ *
+ * Copyright (C) 2005 Michael Reinelt <michael@reinelt.co.at>
+ * Copyright (C) 2005, 2006, 2007 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
+ * Copyright (C) 2011 Hans Ulrich Niedermann <hun@n-dimensional.de>
+ * Copyright (C) 2011, 2012 Andreas Thienemann <andreas@bawue.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_TeakLCM
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <assert.h>
+
+#include "event.h"
+#include "timer.h"
+#include "debug.h"
+#include "cfg.h"
+#include "qprintf.h"
+#include "udelay.h"
+#include "plugin.h"
+#include "widget.h"
+#include "widget_text.h"
+#include "widget_icon.h"
+#include "widget_bar.h"
+#include "drv.h"
+
+#include "drv_generic_text.h"
+#include "drv_generic_serial.h"
+
+
+static char Name[] = "TeakLCM";
+
+
+static int global_reset_rx_flag = 0;
+
+
+#define HI8(value) ((u_int8_t)(((value)>>8) & 0xff))
+#define LO8(value) ((u_int8_t)((value) & 0xff))
+
+
+static u_int16_t CRC16(u_int8_t value, u_int16_t crcin)
+{
+ u_int16_t k = (((crcin >> 8) ^ value) & 255) << 8;
+ u_int16_t crc = 0;
+ int bits;
+ for (bits = 8; bits; --bits) {
+ if ((crc ^ k) & 0x8000)
+ crc = (crc << 1) ^ 0x1021;
+ else
+ crc <<= 1;
+ k <<= 1;
+ }
+ return ((crcin << 8) ^ crc);
+}
+
+
+/** Return a printable character */
+static char printable(const char ch)
+{
+ if ((32 <= ch) && (ch < 127)) {
+ return ch;
+ } else {
+ return '.';
+ }
+}
+
+
+static void debug_data_int(const char *prefix, const void *data, const size_t size, const unsigned int delta)
+{
+ const u_int8_t *b = (const u_int8_t *) data;
+ size_t y;
+ assert(delta <= 24);
+ for (y = 0; y < size; y += delta) {
+ char buf[100];
+ size_t x;
+ ssize_t idx = 0;
+ idx += sprintf(&(buf[idx]), "%04x ", y);
+ for (x = 0; x < delta; x++) {
+ const size_t i = x + y;
+ if (i < size) {
+ idx += sprintf(&(buf[idx]), " %02x", b[i]);
+ } else {
+ idx += sprintf(&(buf[idx]), " ");
+ }
+ }
+ idx += sprintf(&buf[idx], " ");
+ for (x = 0; x < delta; x++) {
+ const size_t i = x + y;
+ if (i < size) {
+ idx += sprintf(&buf[idx], "%c", printable(b[i]));
+ } else {
+ idx += sprintf(&buf[idx], " ");
+ }
+ }
+ debug("%s%s", prefix, buf);
+ }
+}
+
+
+static void debug_data(const char *prefix, const void *data, const size_t size)
+{
+ debug_data_int(prefix, data, size, 16);
+}
+
+
+typedef enum {
+ CMD_CONNECT = 0x05,
+ CMD_DISCONNECT = 0x06,
+ CMD_ALARM = 0x07,
+ CMD_WRITE = 0x08,
+ CMD_PRINT1 = 0x09,
+ CMD_PRINT2 = 0x0A,
+ CMD_ACK = 0x0B,
+ CMD_NACK = 0x0C,
+ CMD_CONFIRM = 0x0D,
+ CMD_RESET = 0x0E,
+
+ LCM_CLEAR = 0x21,
+ LCM_HOME = 0x22,
+ LCM_CURSOR_SHIFT_R = 0x23,
+ LCM_CURSOR_SHIFT_L = 0x24,
+ LCM_BACKLIGHT_ON = 0x25,
+ LCM_BACKLIGHT_OFF = 0x26,
+ LCM_LINE2 = 0x27,
+ LCM_DISPLAY_SHIFT_R = 0x28,
+ LCM_DISPLAY_SHIFT_L = 0x29,
+ LCM_CURSOR_ON = 0x2A,
+ LCM_CURSOR_OFF = 0x2B,
+ LCM_CURSOR_BLINK = 0x2C,
+ LCM_DISPLAY_ON = 0x2D,
+ LCM_DISPLAY_OFF = 0x2E
+} lcm_cmd_t;
+
+
+static
+const char *cmdstr(const lcm_cmd_t cmd)
+{
+ switch (cmd) {
+#define D(CMD) case CMD_ ## CMD: return "CMD_" # CMD; break;
+ D(CONNECT);
+ D(DISCONNECT);
+ D(ACK);
+ D(NACK);
+ D(CONFIRM);
+ D(RESET);
+ D(ALARM);
+ D(WRITE);
+ D(PRINT1);
+ D(PRINT2);
+#undef D
+#define D(CMD) case LCM_ ## CMD: return "LCM_" # CMD; break;
+ D(CLEAR);
+ D(HOME);
+ D(CURSOR_SHIFT_R);
+ D(CURSOR_SHIFT_L);
+ D(BACKLIGHT_ON);
+ D(BACKLIGHT_OFF);
+ D(LINE2);
+ D(DISPLAY_SHIFT_R);
+ D(DISPLAY_SHIFT_L);
+ D(CURSOR_ON);
+ D(CURSOR_OFF);
+ D(CURSOR_BLINK);
+ D(DISPLAY_ON);
+ D(DISPLAY_OFF);
+#undef D
+ }
+ return "CMD_UNKNOWN";
+}
+
+
+/*
+ * Magic defines
+ */
+
+#define LCM_FRAME_MASK 0xFF
+#define LCM_TIMEOUT 2
+#define LCM_ESC 0x1B
+
+#define LCM_KEY1 0x31
+#define LCM_KEY2 0x32
+#define LCM_KEY3 0x33
+#define LCM_KEY4 0x34
+#define LCM_KEY12 0x35
+#define LCM_KEY13 0x36
+#define LCM_KEY14 0x37
+#define LCM_KEY23 0x38
+#define LCM_KEY24 0x39
+#define LCM_KEY34 0x3A
+
+
+/****************************************/
+/*** hardware dependant functions ***/
+/****************************************/
+
+/* global LCM state machine */
+
+
+struct _lcm_fsm_t;
+typedef struct _lcm_fsm_t lcm_fsm_t;
+
+
+typedef enum {
+ ST_IDLE, /* mode == 0, IDLE */
+ ST_COMMAND, /* mode == 1, COMMAND */
+ ST_CONNECTED /* mode == 2, CONNECTED */
+} lcm_state_t;
+
+
+static
+const char *state2str(const lcm_state_t state)
+{
+ switch (state) {
+ case ST_IDLE:
+ return "ST_IDLE (0)";
+ break;
+ case ST_COMMAND:
+ return "ST_COMMAND (1)";
+ break;
+ case ST_CONNECTED:
+ return "ST_CONNECTED (2)";
+ break;
+ }
+ return "ST_UNKNOWN";
+}
+
+
+#if 0
+static
+void repeat_connect_to_display_callback(void *data);
+#endif
+
+static
+void lcm_send_cmd(lcm_cmd_t cmd);
+
+static
+void drv_TeakLCM_clear(void);
+
+
+static
+void raw_send_cmd_frame(lcm_cmd_t cmd);
+
+static
+void raw_send_data_frame(lcm_cmd_t cmd, const char *data, const unsigned int len);
+
+
+static
+lcm_state_t fsm_get_state(lcm_fsm_t * fsm);
+
+static
+void fsm_handle_cmd(lcm_fsm_t * fsm, const lcm_cmd_t cmd);
+
+static
+void fsm_handle_datacmd(lcm_fsm_t * fsm, const lcm_cmd_t cmd, const u_int8_t * payload, const unsigned int payload_len);
+
+static
+void try_reset(void);
+
+static
+void fsm_step(lcm_fsm_t * fsm);
+
+static
+void fsm_trans_noop(lcm_fsm_t * fsm, const lcm_state_t next_state);
+
+static
+void fsm_trans_cmd(lcm_fsm_t * fsm, const lcm_state_t next_state, const lcm_cmd_t cmd);
+
+static
+void fsm_trans_data(lcm_fsm_t * fsm,
+ const lcm_state_t next_state, const lcm_cmd_t cmd, const char *data, const unsigned int len);
+
+
+static
+void fsm_handle_bytes(lcm_fsm_t * fsm, u_int8_t * rxbuf, const unsigned int buflen)
+{
+ if ((buflen >= 3) && (rxbuf[0] == LCM_FRAME_MASK) && (rxbuf[2] == LCM_FRAME_MASK)) {
+ const lcm_cmd_t cmd = rxbuf[1];
+ debug("%s Received cmd frame (cmd=%d=%s)", __FUNCTION__, cmd, cmdstr(cmd));
+ fsm_handle_cmd(fsm, cmd);
+ if (buflen > 3) {
+ /* recursively handle remaining bytes */
+ fsm_handle_bytes(fsm, &rxbuf[3], buflen - 3);
+ }
+ return;
+ } else if ((buflen > 3) && (rxbuf[0] == LCM_FRAME_MASK)) {
+ unsigned int ri; /* raw indexed */
+ unsigned int ci; /* cooked indexed, i.e. after unescaping */
+
+ debug("%s Received possible data frame", __FUNCTION__);
+
+ /* unescape rxframe data in place */
+ u_int16_t crc0 = 0, crc1 = 0, crc2 = 0, crc3 = 0;
+ for (ri = 1, ci = 1; ri < buflen; ri++) {
+ switch (rxbuf[ri]) {
+ case LCM_ESC:
+ ri++;
+ /* fall through */
+ default:
+ rxbuf[ci++] = rxbuf[ri];
+ crc3 = crc2;
+ crc2 = crc1;
+ crc1 = crc0;
+ crc0 = CRC16(rxbuf[ri], crc0);
+ break;
+ }
+ if ((rxbuf[ci - 1] == LCM_FRAME_MASK) && (rxbuf[ci - 2] == LO8(crc3)) && (rxbuf[ci - 3] == HI8(crc3))) {
+ /* looks like a complete data frame */
+ lcm_cmd_t cmd = rxbuf[1];
+ u_int16_t len = (rxbuf[3] << 8) + rxbuf[2];
+ assert(ci == (unsigned int) (1 + 1 + 2 + len + 2 + 1));
+ fsm_handle_datacmd(fsm, cmd, &rxbuf[4], len);
+ if (ri + 1 < buflen) {
+ /* recursively handle remaining bytes */
+ fsm_handle_bytes(fsm, &rxbuf[ri + 1], buflen - ri);
+ }
+ return;
+ }
+ }
+
+ fsm_trans_cmd(fsm, fsm_get_state(fsm), /* TODO: Is this a good next_state value? */
+ CMD_NACK);
+ debug("%s checksum/framemask error", __FUNCTION__);
+ return;
+ } else {
+ debug("%s Received garbage data:", __FUNCTION__);
+ debug_data(" RXD ", rxbuf, buflen);
+ return;
+ }
+}
+
+
+static void fsm_handle_cmd(lcm_fsm_t * fsm, lcm_cmd_t cmd)
+{
+ // debug("fsm_handle_cmd: old state 0x%02x %s", lcm_mode, modestr(lcm_mode));
+ const lcm_state_t old_state = fsm_get_state(fsm);
+ if (CMD_RESET == cmd) {
+ global_reset_rx_flag = 1;
+ }
+ switch (old_state) {
+ case ST_IDLE:
+ case ST_COMMAND:
+ switch (cmd) {
+ case CMD_CONNECT:
+ fsm_trans_cmd(fsm, ST_COMMAND, CMD_ACK);
+ break;
+ case CMD_ACK:
+ fsm_trans_cmd(fsm, ST_CONNECTED, CMD_CONFIRM);
+ break;
+ case CMD_NACK:
+ fsm_trans_cmd(fsm, ST_IDLE, CMD_CONFIRM);
+ break;
+ case CMD_CONFIRM:
+ fsm_trans_noop(fsm, ST_CONNECTED);
+ break;
+ case CMD_RESET:
+ fsm_trans_cmd(fsm, ST_COMMAND, CMD_CONNECT);
+ break;
+ default:
+ error("%s: Unhandled cmd %s in state %s", Name, cmdstr(cmd), state2str(old_state));
+ fsm_trans_cmd(fsm, ST_IDLE, CMD_NACK);
+ break;
+ }
+ break;
+ case ST_CONNECTED: /* "if (mode == 2)" */
+ switch (cmd) {
+ case CMD_ACK:
+ fsm_trans_cmd(fsm, ST_CONNECTED, CMD_CONFIRM);
+ break;
+ case CMD_CONNECT:
+ fsm_trans_cmd(fsm, ST_CONNECTED, CMD_NACK);
+ break;
+ case CMD_DISCONNECT:
+ fsm_trans_cmd(fsm, ST_CONNECTED, CMD_ACK);
+ break;
+ case CMD_RESET:
+ fsm_trans_cmd(fsm, ST_IDLE, CMD_CONNECT);
+ break;
+ default:
+ debug("%s: Ignoring unhandled cmd %s in state %s", Name, cmdstr(cmd), state2str(old_state));
+ fsm_trans_noop(fsm, ST_CONNECTED);
+ break;
+ }
+ break;
+ }
+ fsm_step(fsm);
+}
+
+
+static
+void fsm_handle_datacmd(lcm_fsm_t * fsm, const lcm_cmd_t cmd, const u_int8_t * payload, unsigned int payload_len)
+{
+ const lcm_state_t old_state = fsm_get_state(fsm);
+ debug("fsm_handle_datacmd: old state 0x%02x %s", old_state, state2str(old_state));
+ switch (old_state) {
+ case ST_CONNECTED:
+ switch (cmd) {
+ case CMD_WRITE:
+ assert(payload_len == 1);
+ debug("Got a key code 0x%02x", *payload);
+ fsm_trans_noop(fsm, ST_CONNECTED);
+ // lcm_send_cmd_frame(CMD_ACK);
+ break;
+ default:
+ debug("Got an unknown data frame: %d=%s", cmd, cmdstr(cmd));
+ fsm_trans_noop(fsm, ST_CONNECTED);
+ // lcm_send_cmd_frame(CMD_NACK);
+ break;
+ }
+ break;
+ case ST_IDLE:
+ case ST_COMMAND:
+ fsm_trans_cmd(fsm, old_state, CMD_NACK);
+ break;
+ }
+ fsm_step(fsm);
+}
+
+
+struct _lcm_fsm_t {
+ lcm_state_t state;
+ lcm_state_t next_state;
+ enum {
+ ACTION_UNINITIALIZED,
+ ACTION_NOOP,
+ ACTION_CMD,
+ ACTION_DATA
+ } action_type;
+ union {
+ struct {
+ lcm_cmd_t cmd;
+ } cmd_frame;
+ struct {
+ lcm_cmd_t cmd;
+ const char *data;
+ unsigned int len;
+ } data_frame;
+ } action;
+};
+
+
+static
+lcm_state_t fsm_get_state(lcm_fsm_t * fsm)
+{
+ return fsm->state;
+}
+
+
+static
+void flush_shadow(void);
+
+
+static
+void fsm_step(lcm_fsm_t * fsm)
+{
+ debug("fsm: old_state=%s new_state=%s", state2str(fsm->state), state2str(fsm->next_state));
+ switch (fsm->action_type) {
+ case ACTION_UNINITIALIZED:
+ error("Uninitialized LCM FSM action");
+ abort();
+ break;
+ case ACTION_NOOP:
+ break;
+ case ACTION_CMD:
+ raw_send_cmd_frame(fsm->action.cmd_frame.cmd);
+ break;
+ case ACTION_DATA:
+ raw_send_data_frame(fsm->action.data_frame.cmd, fsm->action.data_frame.data, fsm->action.data_frame.len);
+ break;
+ }
+ fsm->action_type = ACTION_UNINITIALIZED;
+ switch (fsm->next_state) {
+ case ST_IDLE:
+ case ST_COMMAND:
+ fsm->state = fsm->next_state;
+ fsm->next_state = -1;
+ return;
+ break;
+ case ST_CONNECTED:
+ if (fsm->state != ST_CONNECTED) {
+ /* going from ST_IDLE or ST_COMMAND into ST_CONNECTED */
+ if (!global_reset_rx_flag) {
+ try_reset();
+#if 0
+ int timer_res = timer_add(repeat_connect_to_display_callback, NULL, 50 /*ms */ , 1);
+ debug("re-scheduled connect callback result: %d", timer_res);
+
+ done by try_reset / fsm_init fsm->state = fsm->next_state;
+ fsm->next_state = -1;
+#endif
+ return;
+ } else {
+ /* properly connected for the first time */
+ debug("%s: %s NOW CONNECTED!!!", Name, __FUNCTION__);
+
+ fsm->state = fsm->next_state;
+ fsm->next_state = -1;
+
+ lcm_send_cmd(LCM_DISPLAY_ON);
+ flush_shadow();
+ lcm_send_cmd(LCM_BACKLIGHT_ON);
+ return;
+ }
+ } else {
+ debug("no state change in ST_CONNECTED");
+ fsm->state = fsm->next_state;
+ fsm->next_state = -1;
+ return;
+ }
+ error("we should never arrive here");
+ abort();
+ break;
+ }
+ error("LCM FSM: Illegal next_state");
+ abort();
+}
+
+
+#if 0
+
+#endif
+
+
+static
+void fsm_trans_noop(lcm_fsm_t * fsm, const lcm_state_t next_state)
+{
+ fsm->next_state = next_state;
+ fsm->action_type = ACTION_NOOP;
+}
+
+
+static
+void fsm_trans_cmd(lcm_fsm_t * fsm, const lcm_state_t next_state, const lcm_cmd_t cmd)
+{
+ fsm->next_state = next_state;
+ fsm->action_type = ACTION_CMD;
+ fsm->action.cmd_frame.cmd = cmd;
+}
+
+
+static
+void fsm_trans_data(lcm_fsm_t * fsm,
+ const lcm_state_t next_state, const lcm_cmd_t cmd, const char *data, const unsigned int len)
+{
+ fsm->next_state = next_state;
+ fsm->action_type = ACTION_DATA;
+ fsm->action.data_frame.cmd = cmd;
+ fsm->action.data_frame.data = data;
+ fsm->action.data_frame.len = len;
+}
+
+
+static
+void fsm_send(lcm_fsm_t * fsm, const lcm_cmd_t cmd)
+{
+ const lcm_state_t old_state = fsm_get_state(fsm);
+ switch (old_state) {
+ case ST_IDLE:
+ case ST_COMMAND:
+ debug("%s: %s, ignoring cmd 0x%02x=%s", __FUNCTION__, state2str(old_state), cmd, cmdstr(cmd));
+ /* Silently ignore the command to send. */
+ /* TODO: Would it be better to queue it and send it later? */
+ break;
+ case ST_CONNECTED:
+ fsm_trans_cmd(fsm, ST_CONNECTED, cmd);
+ fsm_step(fsm);
+ break;
+ }
+}
+
+
+static
+void fsm_send_data(lcm_fsm_t * fsm, const lcm_cmd_t cmd, const void *data, const unsigned int len)
+{
+ const lcm_state_t old_state = fsm_get_state(fsm);
+ switch (old_state) {
+ case ST_IDLE:
+ case ST_COMMAND:
+ debug("%s: %s, ignoring data cmd 0x%02x=%s", __FUNCTION__, state2str(old_state), cmd, cmdstr(cmd));
+ /* Silently ignore the command to send. */
+ /* TODO: Would it be better to queue it and send it later? */
+ break;
+ case ST_CONNECTED:
+ fsm_trans_data(fsm, ST_CONNECTED, cmd, data, len);
+ fsm_step(fsm);
+ break;
+ }
+}
+
+
+static lcm_fsm_t lcm_fsm;
+
+
+static
+void fsm_init(void)
+{
+ lcm_fsm.state = ST_IDLE;
+ lcm_fsm.next_state = -1;
+ lcm_fsm.action_type = ACTION_UNINITIALIZED;
+}
+
+
+
+/* Send a command frame to the TCM board */
+static
+void raw_send_cmd_frame(lcm_cmd_t cmd)
+{
+ // lcm_receive_check();
+ char cmd_buf[3];
+ cmd_buf[0] = LCM_FRAME_MASK;
+ cmd_buf[1] = cmd;
+ cmd_buf[2] = LCM_FRAME_MASK;
+ debug("%s sending cmd frame cmd=0x%02x=%s", __FUNCTION__, cmd, cmdstr(cmd));
+ debug_data(" TX ", cmd_buf, 3);
+ drv_generic_serial_write(cmd_buf, 3);
+ usleep(100);
+#if 0
+ usleep(100000);
+ switch (cmd) {
+ case CMD_ACK:
+ //case CMD_CONFIRM:
+ case CMD_NACK:
+ lcm_receive_check();
+ break;
+ default:
+ if (1) {
+ int i;
+ for (i = 0; i < 20; i++) {
+ usleep(100000);
+ if (lcm_receive_check()) {
+ break;
+ }
+ }
+ }
+ break;
+ }
+#endif
+}
+
+
+/* Send a data frame to the TCM board */
+static
+void raw_send_data_frame(lcm_cmd_t cmd, const char *data, const unsigned int len)
+{
+ unsigned int di; /* data index */
+ unsigned int fi; /* frame index */
+ static char frame[32];
+ u_int16_t crc = 0;
+
+ frame[0] = LCM_FRAME_MASK;
+
+ frame[1] = cmd;
+ crc = CRC16(frame[1], crc);
+
+ frame[2] = HI8(len);
+ crc = CRC16(frame[2], crc);
+
+ frame[3] = LO8(len);
+ crc = CRC16(frame[3], crc);
+
+#define APPEND(value) \
+ do { \
+ const unsigned char v = (value); \
+ if ((v == LCM_FRAME_MASK) || (v == LCM_ESC)) { \
+ frame[fi++] = LCM_ESC; \
+ } \
+ frame[fi++] = v; \
+ crc = CRC16(v, crc); \
+ } while (0)
+
+#define APPEND_NOCRC(value) \
+ do { \
+ const unsigned char v = (value); \
+ if ((v == LCM_FRAME_MASK) || (v == LCM_ESC)) { \
+ frame[fi++] = LCM_ESC; \
+ } \
+ frame[fi++] = v; \
+ } while (0)
+
+ for (fi = 4, di = 0; di < len; di++) {
+ APPEND(data[di]);
+ }
+
+ APPEND_NOCRC(HI8(crc));
+ APPEND_NOCRC(LO8(crc));
+
+ frame[fi++] = LCM_FRAME_MASK;
+
+ debug_data(" TXD ", frame, fi);
+ drv_generic_serial_write(frame, fi);
+
+#undef APPEND
+
+ usleep(500);
+}
+
+
+static
+void lcm_send_cmd(lcm_cmd_t cmd)
+{
+ fsm_send(&lcm_fsm, cmd);
+}
+
+
+static
+void lcm_event_callback(event_flags_t flags, void *data)
+{
+ lcm_fsm_t *fsm = (lcm_fsm_t *) data;
+ debug("%s: flags=%d, data=%p", __FUNCTION__, flags, data);
+ if (flags & EVENT_READ) {
+ static u_int8_t rxbuf[32];
+ const int readlen = drv_generic_serial_poll((void *) rxbuf, sizeof(rxbuf));
+ if (readlen <= 0) {
+ debug("%s Received no data", __FUNCTION__);
+ } else {
+ debug("%s RECEIVED %d bytes", __FUNCTION__, readlen);
+ debug_data(" RX ", rxbuf, readlen);
+ fsm_handle_bytes(fsm, rxbuf, readlen);
+ }
+ }
+}
+
+
+static int drv_TeakLCM_open(const char *section)
+{
+ /* open serial port */
+ /* don't mind about device, speed and stuff, this function will take care of */
+
+ const int fd = drv_generic_serial_open(section, Name, 0);
+ if (fd < 0)
+ return -1;
+
+ return fd;
+}
+
+
+static int drv_TeakLCM_close(int fd)
+{
+ if (fd >= 0) {
+ event_del(fd);
+ }
+
+ /* close whatever port you've opened */
+ drv_generic_serial_close();
+
+ return 0;
+}
+
+
+/* shadow buffer */
+static
+char *shadow;
+
+
+static void debug_shadow(const char *prefix)
+{
+ debug_data_int(prefix, shadow, DCOLS * DROWS, 20);
+}
+
+
+static
+void flush_shadow(void)
+{
+ debug("%s called", __FUNCTION__);
+ debug_shadow(" shadow ");
+ usleep(50000);
+ fsm_send_data(&lcm_fsm, CMD_PRINT1, &shadow[DCOLS * 0], DCOLS);
+ usleep(50000);
+ fsm_send_data(&lcm_fsm, CMD_PRINT2, &shadow[DCOLS * 1], DCOLS);
+ usleep(50000);
+}
+
+
+/* text mode displays only */
+static
+void drv_TeakLCM_clear(void)
+{
+ /* do whatever is necessary to clear the display */
+ memset(shadow, ' ', DROWS * DCOLS);
+ flush_shadow();
+}
+
+
+/* text mode displays only */
+static void drv_TeakLCM_write(const int row, const int col, const char *data, int len)
+{
+ debug("%s row=%d col=%d len=%d data=\"%s\"", __FUNCTION__, row, col, len, data);
+
+ memcpy(&shadow[DCOLS * row + col], data, len);
+
+ debug_shadow(" shadow ");
+
+ fsm_send_data(&lcm_fsm, (row == 0) ? CMD_PRINT1 : CMD_PRINT2, &shadow[DCOLS * row], DCOLS);
+}
+
+
+static
+void try_reset(void)
+{
+ debug("%s called", __FUNCTION__);
+ fsm_init();
+ raw_send_cmd_frame(CMD_RESET);
+}
+
+
+#if 0
+static
+void repeat_connect_to_display_callback(void *data)
+{
+ static int already_called = 0;
+ if (!already_called) {
+ debug("%s(%p): called", __FUNCTION__, data);
+
+ /* reset & initialize display */
+ try_reset();
+ already_called = 1;
+ } else {
+ debug("%s(%p): already called, ignoring", __FUNCTION__, data);
+ }
+}
+#endif
+
+
+static
+int global_fd = -1;
+
+
+static
+void initial_connect_to_display_callback(void *data)
+{
+ debug("%s(%p): called", __FUNCTION__, data);
+
+ debug("Calling event_add for fd=%d", global_fd);
+ int ret = event_add(lcm_event_callback, &lcm_fsm, global_fd, 1, 0, 1);
+ debug("event_add result: %d", ret);
+
+ /* reset & initialize display */
+ try_reset();
+}
+
+
+/* start text mode display */
+static int drv_TeakLCM_start(const char *section)
+{
+ int rows = -1, cols = -1;
+ char *s;
+
+ s = cfg_get(section, "Size", NULL);
+ if (s == NULL || *s == '\0') {
+ error("%s: no '%s.Size' entry from %s", Name, section, cfg_source());
+ return -1;
+ }
+ if (sscanf(s, "%dx%d", &cols, &rows) != 2 || rows < 1 || cols < 1) {
+ error("%s: bad %s.Size '%s' from %s", Name, section, s, cfg_source());
+ free(s);
+ return -1;
+ }
+
+ DROWS = rows;
+ DCOLS = cols;
+ shadow = malloc(DROWS * DCOLS);
+ memset(shadow, ' ', DROWS * DCOLS);
+
+ /* open communication with the display */
+ global_fd = drv_TeakLCM_open(section);
+ if (global_fd < 0) {
+ return -1;
+ }
+ debug("%s: %s opened", Name, __FUNCTION__);
+
+ /* read initial garbage data */
+ static u_int8_t rxbuf[32];
+ const int readlen = drv_generic_serial_poll((void *) rxbuf, sizeof(rxbuf));
+ if (readlen >= 0) {
+ debug_data(" initial RX garbage ", rxbuf, readlen);
+ }
+
+ /* We need to do a delayed connect */
+ int timer_res = timer_add(initial_connect_to_display_callback, NULL, 10 /*ms */ , 1);
+ debug("timer_add for connect callback result: %d", timer_res);
+
+ debug("%s: %s done", Name, __FUNCTION__);
+ return 0;
+}
+
+
+/****************************************/
+/*** plugins ***/
+/****************************************/
+
+
+/****************************************/
+/*** widget callbacks ***/
+/****************************************/
+
+
+/* using drv_generic_text_draw(W) */
+/* using drv_generic_text_icon_draw(W) */
+/* using drv_generic_text_bar_draw(W) */
+
+
+/****************************************/
+/*** exported functions ***/
+/****************************************/
+
+
+/* list models */
+int drv_TeakLCM_list(void)
+{
+ printf("TeakLCM driver");
+ return 0;
+}
+
+
+/* initialize driver & display */
+/* use this function for a text display */
+int drv_TeakLCM_init(const char *section, const int quiet)
+{
+ WIDGET_CLASS wc;
+ int ret;
+
+ info("%s: %s (quiet=%d)", Name, "$Rev$", quiet);
+
+ /* display preferences */
+ XRES = 5; /* pixel width of one char */
+ YRES = 8; /* pixel height of one char */
+ CHARS = 0; /* number of user-defineable characters */
+ CHAR0 = 0; /* ASCII of first user-defineable char */
+ GOTO_COST = -1; /* number of bytes a goto command requires */
+
+ /* real worker functions */
+ drv_generic_text_real_write = drv_TeakLCM_write;
+
+ /* start display */
+ if ((ret = drv_TeakLCM_start(section)) != 0)
+ return ret;
+
+ /* initialize generic text driver */
+ if ((ret = drv_generic_text_init(section, Name)) != 0)
+ return ret;
+
+ /* register text widget */
+ wc = Widget_Text;
+ wc.draw = drv_generic_text_draw;
+ widget_register(&wc);
+
+ /* register plugins */
+
+ return 0;
+}
+
+
+/* close driver & display */
+/* use this function for a text display */
+int drv_TeakLCM_quit(const int quiet)
+{
+
+ info("%s: shutting down. (quiet=%d)", Name, quiet);
+
+ drv_generic_text_quit();
+
+ /* clear display */
+ drv_TeakLCM_clear();
+
+ lcm_send_cmd(LCM_DISPLAY_OFF);
+ // lcm_send_cmd_frame(LCM_BACKLIGHT_OFF);
+ lcm_send_cmd(CMD_DISCONNECT);
+
+ /* FIXME: consume final ack frame */
+ usleep(100000);
+
+ debug("closing connection");
+ drv_TeakLCM_close(global_fd);
+
+ return (0);
+}
+
+
+/* use this one for a text display */
+DRIVER drv_TeakLCM = {
+ .name = Name,
+ .list = drv_TeakLCM_list,
+ .init = drv_TeakLCM_init,
+ .quit = drv_TeakLCM_quit,
+};
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/lcd4linux.conf.sample b/lcd4linux.conf.sample
index 0e4da6c..9c312bd 100644
--- a/lcd4linux.conf.sample
+++ b/lcd4linux.conf.sample
@@ -27,6 +27,16 @@ Display SerDispLib {
}
+Display TeakLCM {
+ Driver 'TeakLCM'
+ Size '20x2'
+ Port '/dev/ttyS1'
+ Speed 38400
+ Backlight 1
+ Icons 0
+}
+
+
Display Trefon {
Driver 'TREFON'
Size '16x2'
@@ -1313,6 +1323,7 @@ Layout Debug {
#Display 'USBLCD'
#Display 'BWCT'
#Display 'Image'
+#Display 'TeakLCD'
#Display 'Trefon'
#Display 'LCD2USB'
#Display 'LPH7508-serdisplib'
diff --git a/smoketest.sh b/smoketest.sh
index 0077b86..afa92eb 100755
--- a/smoketest.sh
+++ b/smoketest.sh
@@ -9,7 +9,7 @@ rm -f smoketest.log lcd4linux
make distclean
./bootstrap
-for driver in ASTUSB BeckmannEgle BWCT CrystalFontz Curses Cwlinux D4D DPF EA232graphic EFN G15 GLCD2USB HD44780 IRLCD LCD2USB LCDLinux LCDTerm LEDMatrix LPH7508 LUIse LW_ABP M50530 MatrixOrbital MatrixOrbitalGX MilfordInstruments Noritake NULL Pertelian PHAnderson picoLCD picoLCDGraphic PNG PPM RouterBoard Sample serdisplib SimpleLCD T6963 Trefon ULA200 USBHUB USBLCD WincorNixdorf X11; do
+for driver in ASTUSB BeckmannEgle BWCT CrystalFontz Curses Cwlinux D4D DPF EA232graphic EFN G15 GLCD2USB HD44780 IRLCD LCD2USB LCDLinux LCDTerm LEDMatrix LPH7508 LUIse LW_ABP M50530 MatrixOrbital MatrixOrbitalGX MilfordInstruments Noritake NULL Pertelian PHAnderson picoLCD picoLCDGraphic PNG PPM RouterBoard Sample serdisplib SimpleLCD T6963 TeakLCM Trefon ULA200 USBHUB USBLCD WincorNixdorf X11; do
make distclean
./configure --with-drivers=$driver