aboutsummaryrefslogtreecommitdiffstats
path: root/drv_TeakLCM.c
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 /drv_TeakLCM.c
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
Diffstat (limited to 'drv_TeakLCM.c')
-rw-r--r--drv_TeakLCM.c1024
1 files changed, 1024 insertions, 0 deletions
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:
+ */