/** \file server/drivers/ula200.c * LCDd \c ula200 driver for the ULA-200 from ELV (http://www.elv.de). */ /*- * Copyright (C) 2006 Bernhard Walle * * 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 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include "lcd.h" #include "ula200.h" #include "report.h" #include "hd44780-charmap.h" #include "adv_bignum.h" /* Vars for the server core */ MODULE_EXPORT char *api_version = API_VERSION; MODULE_EXPORT int stay_in_foreground = 0; MODULE_EXPORT int supports_multiple = 1; MODULE_EXPORT char *symbol_prefix = "ula200_"; /* -------------------------- constants ---------------------------------- */ #define DISPLAY_VENDOR_ID 0x0403 #define DISPLAY_PRODUCT_ID 0xf06d #define CH_STX 0x02 #define CH_ETX 0x03 #define CH_ENQ 0x05 #define CH_ACK 0x06 #define CH_NAK 0x15 #define CH_DC2 0x12 #define CH_DC3 0x13 #define MAX_KEY_MAP 6 static char *default_key_map[MAX_KEY_MAP] = {"Up", "Down", "Left", "Right", "Enter", "Escape"}; #define CELLWIDTH 5 #define CELLHEIGHT 8 /* repeat conts for responses */ #define MAX_REPEATS 20 /* commands for senddata */ #define RS_DATA 0x00 #define RS_INSTR 0x01 #define SETCHAR 0x40 /* boolean data type */ #ifndef bool # define bool short # define true 1 # define false 0 #endif /* ----------- Key ring implementation from CFontz633io ------------------ */ /* KeyRing management */ #define KEYRINGSIZE 16 typedef struct ula200_keyring { unsigned char contents[KEYRINGSIZE]; int head; int tail; } KeyRing; /* * KeyRing handling functions. This separates the producer from the consumer. * It is just a small fifo of unsigned char. */ /** initialize/empty key ring by resetting its read & write pointers */ void EmptyKeyRing(KeyRing *kr) { kr->head = kr->tail = 0; } /** add byte to key ring; return success (byte added) / failure (key ring is full) */ int AddKeyToKeyRing(KeyRing *kr, unsigned char key) { if (((kr->head + 1) % KEYRINGSIZE) != (kr->tail % KEYRINGSIZE)) { kr->contents[kr->head % KEYRINGSIZE] = key; kr->head = (kr->head + 1) % KEYRINGSIZE; return 1; } /* KeyRing overflow: do not accept extra key */ return 0; } /** get byte from key ring (or '\\0' if key ring is empty) */ unsigned char GetKeyFromKeyRing(KeyRing *kr) { unsigned char retval = '\0'; kr->tail %= KEYRINGSIZE; if ((kr->head % KEYRINGSIZE) != kr->tail) { retval = kr->contents[kr->tail]; kr->tail = (kr->tail + 1) % KEYRINGSIZE; } return retval; } /* ---------------------- ULA-200 specific functions --------------------- */ /** private data for the \c ula200 driver */ typedef struct ula200_private_data { struct ftdi_context ftdic; /**< handle for the USB FTDI library */ int width; /**< display width in characters */ int height; /**< display height in characters */ unsigned char *framebuf; /**< framebuffer LCDd writes to */ unsigned char *lcd_contents; /**< content of screen for incr. upd. */ unsigned char all_dirty; /**< first time => all is dirty */ int backlight; /**< backlight: -1=unset, 0=off, 1=on */ KeyRing keyring; /**< input key ring */ char *key_map[MAX_KEY_MAP]; /**< mapping of input keys */ } PrivateData; /** * Reads a character from USB. * \param p Pointer to drivers private data * \return Character read from USB. */ static inline int ula200_ftdi_usb_read(PrivateData *p) { unsigned char buffer[1]; int err; while ((err = ftdi_read_data(&p->ftdic, buffer, 1)) == 0); if (err < 0) { return -1; } else { return buffer[0]; } } /** * Reads the response. * * \return true if ACK was responded * \return false if NAK was responded */ static bool ula200_ftdi_read_response(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; bool result = false; bool answer_read = false; int ret; int ch; while (!answer_read) { /* wait until STX */ while (((ret = ula200_ftdi_usb_read(p)) != CH_STX) && (ret > 0)); if (ret < 0) { return false; } /* read next char */ ch = ula200_ftdi_usb_read(p); switch (ch) { case 't': ch = ula200_ftdi_usb_read(p); AddKeyToKeyRing(&p->keyring, (unsigned char)(ch - 0x40)); break; case CH_ACK: answer_read = true; result = true; break; case CH_NAK: answer_read = true; break; default: answer_read = true; report(RPT_INFO, "%s: read invalid answer (0x%02X)", drvthis->name, ch); } /* wait until ETX */ while (((ret = ula200_ftdi_usb_read(p)) != CH_ETX) && (ret > 0)); if (ret < 0) { return false; } } return result; } /** * Write a command to the display. Adds the STX and ETX header/trailer. * * \param drvthis Pointer to driver structure * \param data the data bytes * \param length the number of bytes in @p data which are valid * \param escape if the data should be escaped (see the User's Guide of the * ULA-200) * \return 0 on success, negative value on error */ static int ula200_ftdi_write_command(Driver *drvthis, unsigned char *data, int length, bool escape) { PrivateData *p = (PrivateData *) drvthis->private_data; int i, err; int repeat_count = 0; int pos = 0; unsigned char buffer[1024]; if (length > 512) { return -EINVAL; } /* fill the array */ buffer[pos++] = CH_STX; for (i = 0; i < length; i++) { if (escape) { if (data[i] == CH_STX) { buffer[pos++] = CH_ENQ; buffer[pos++] = CH_DC2; } else if (data[i] == CH_ETX) { buffer[pos++] = CH_ENQ; buffer[pos++] = CH_DC3; } else if (data[i] == CH_ENQ) { buffer[pos++] = CH_ENQ; buffer[pos++] = CH_NAK; } else { buffer[pos++] = data[i]; } } else { buffer[pos++] = data[i]; } } buffer[pos++] = CH_ETX; do { /* bytes */ err = ftdi_write_data(&p->ftdic, buffer, pos); if (err < 0) { report(RPT_WARNING, "%s: ftdi_write_data failed", drvthis->name); return -1; } } while (!ula200_ftdi_read_response(drvthis)&&(repeat_count++ < MAX_REPEATS)); return 0; } /** * Clear the display using display command. * \return Error code from ula200_ftdi_write_command. */ static int ula200_ftdi_clear(Driver *drvthis) { unsigned char command[1]; int err; command[0] = 'l'; err = ula200_ftdi_write_command(drvthis, command, 1, true); if (err < 0) { report(RPT_WARNING, "%s: ula200_ftdi_clear: " "ula200_ftdi_write_command failed", drvthis->name); } return err; } /** * Set the position. */ static int ula200_ftdi_position(Driver *drvthis, int x, int y) { PrivateData *p = (PrivateData *) drvthis->private_data; unsigned char command[5]; int err; if (y >= 2) { y -= 2; x += p->width; } command[0] = 'p'; command[1] = x; command[2] = y; err = ula200_ftdi_write_command(drvthis, command, 3, true); if (err < 0) { report(RPT_WARNING, "%s: ula200_ftdi_position(%d,%d): " "ula200_ftdi_write_command failed", drvthis->name, x, y); } return err; } /** * Send raw data. * * \param drvthis Pointer to driver * \param flags Flag indicating byte is command or data * \param ch Data byte to send * \return Error code from ula200_ftdi_write_command. */ static int ula200_ftdi_rawdata(Driver *drvthis, unsigned char flags, unsigned char ch) { unsigned char command[3]; int err; command[0] = 'R'; command[1] = flags == (RS_DATA) ? '2' : '0'; command[2] = ch; err = ula200_ftdi_write_command(drvthis, command, 3, false); if (err < 0) { report(RPT_ERR, "%s: ftdi_write_command() failed", drvthis->name); } return err; } /** * Writes a string to the display. * * \param drvthis Pointer to driver * \param string Pointer to string * \param len Length of the string * \return -EINVAL If the string is to long (max 80 characters) * \return err Error code from ula200_ftdi_write_command */ static int ula200_ftdi_string(Driver *drvthis, const unsigned char *string, int len) { unsigned char buffer[128]; int err; int i; if (len > 80) { return -EINVAL; } buffer[0] = 's'; buffer[1] = len; for (i = 0; i < len; i++) { buffer[i + 2] = HD44780_charmap[(unsigned char)string[i]]; } err = ula200_ftdi_write_command(drvthis, buffer, len + 2, true); if (err < 0) { report(RPT_WARNING, "%s: ula200_ftdi_string: " "ula200_ftdi_write_command() failed", drvthis->name); } return err; } /** * Enables the raw register access mode. * \param drvthis Pointer to driver * \return Error code from ula200_ftdi_write_command. */ static int ula200_ftdi_enable_raw_mode(Driver *drvthis) { unsigned char command[3]; report(RPT_DEBUG, "%s: enable raw mode", drvthis->name); command[0] = 'R'; command[1] = 'E'; command[2] = '1'; return ula200_ftdi_write_command(drvthis, command, 3, false); } /** * Loads custom characters in the display. */ static int ula200_load_custom_chars(Driver *drvthis) { int i, row; int err = 0; unsigned char mask = (1 << CELLWIDTH) - 1; char custom_chars[8][CELLHEIGHT] = { { b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX }, { b__XXXXX, b__X_X_X, b_______, b_______, b_______, b__X___X, b__XX_XX, b__XXXXX }, { b__XXXXX, b__X_X_X, b___X_X_, b___XXX_, b___XXX_, b__X_X_X, b__XX_XX, b__XXXXX }, { b____X__, b___XXX_, b__X_X_X, b____X__, b____X__, b____X__, b____X__, b_______ }, { b____X__, b____X__, b____X__, b____X__, b__X_X_X, b___XXX_, b____X__, b_______ }, { b_______, b_______, b__XXXXX, b__X___X, b__X___X, b__X___X, b__XXXXX, b_______ }, { b____X__, b____X__, b__XXX_X, b__X_XX_, b__X_X_X, b__X___X, b__XXXXX, b_______ }, { b_______, b_______, b__XXXXX, b__X_X_X, b__XX_XX, b__X_X_X, b__XXXXX, b_______ } }; for (i = 0; i < 8 && err == 0; i++) { /* Tell the HD44780 we will redefine char number i */ ula200_ftdi_rawdata(drvthis, RS_INSTR, SETCHAR | i * 8); if (err < 0) { report(RPT_WARNING, "%s: ula200_ftdi_rawdata failed", drvthis->name); break; } /* Send the subsequent rows */ for (row = 0; row < CELLHEIGHT; row++) { int value = custom_chars[i][row] & mask; err = ula200_ftdi_rawdata(drvthis, RS_DATA, value); if (err < 0) { report(RPT_WARNING, "%s: ula200_ftdi_rawdata failed", drvthis->name); break; } } } return err; } /* --------------------------- API functions ----------------------------- */ /** * Initialize the driver. * \param drvthis Pointer to driver structure. * \retval 0 Success. * \retval <0 Error. */ MODULE_EXPORT int ula200_init(Driver *drvthis) { PrivateData *p; int err, i; const char *s; /* Allocate and store private data */ p = (PrivateData *) malloc(sizeof(PrivateData)); if (p == NULL) { return -1; } if (drvthis->store_private_ptr(drvthis, p)){ return -1; } p->backlight = -1; p->all_dirty = 1; EmptyKeyRing(&p->keyring); /* Get and parse size */ s = drvthis->config_get_string(drvthis->name, "size", 0, "20x4"); if ((sscanf(s, "%dx%d", &(p->width), &(p->height)) != 2) || (p->width <= 0) || (p->width > LCD_MAX_WIDTH) || (p->height <= 0) || (p->height > LCD_MAX_HEIGHT)) { report(RPT_ERR, "%s: cannot read Size %s", drvthis->name, s); return -1; } /* read the keymap */ for (i = 0; i < MAX_KEY_MAP; i++) { char buf[40]; /* First fill with default value */ p->key_map[i] = default_key_map[i]; /* Read config value */ sprintf(buf, "KeyMap_%c", i + 'A'); s = drvthis->config_get_string(drvthis->name, buf, 0, NULL); /* Was a key specified in the config file ? */ if (s != NULL) { p->key_map[i] = strdup(s); report(RPT_INFO, "%s: Key '%c' mapped to \"%s\"", drvthis->name, i + 'A', s); } } /* End of config file parsing */ /* Allocate framebuffer */ p->framebuf = (unsigned char *) malloc(p->width * p->height); if (p->framebuf == NULL) { report(RPT_ERR, "%s: unable to allocate framebuffer", drvthis->name); goto err_begin; } /* Allocate and clear the buffer for incremental updates */ p->lcd_contents = (unsigned char *) malloc(p->width * p->height); if (p->lcd_contents == NULL) { report(RPT_ERR, "%s: unable to allocate framebuffer backing store", drvthis->name); goto err_framebuf; } memset(p->lcd_contents, 0, p->width * p->height); /* open the FTDI library */ ftdi_init(&p->ftdic); (&p->ftdic)->usb_write_timeout = 20; (&p->ftdic)->usb_read_timeout = 20; /* open the device */ err = ftdi_usb_open(&p->ftdic, DISPLAY_VENDOR_ID, DISPLAY_PRODUCT_ID); if (err < 0) { report(RPT_ERR, "%s: cannot open USB device", drvthis->name); goto err_lcd; } /* set the baudrate */ err = ftdi_set_baudrate(&p->ftdic, 19200); if (err < 0) { report(RPT_ERR, "%s: cannot set baudrate", drvthis->name); goto err_ftdi; } /* set communication parameters */ err = ftdi_set_line_property(&p->ftdic, BITS_8, STOP_BIT_1, EVEN); if (err < 0) { report(RPT_ERR, "%s: cannot set line properties", drvthis->name); goto err_ftdi; } /* user is able to write commands */ err = ula200_ftdi_enable_raw_mode(drvthis); if (err < 0) { report(RPT_ERR, "%s: unable to enable the raw mode", drvthis->name); goto err_ftdi; } /* load the chars */ err = ula200_load_custom_chars(drvthis); if (err < 0) { report(RPT_ERR, "%s: unable to write the custom characters", drvthis->name); goto err_ftdi; } report(RPT_DEBUG, "%s: init() done", drvthis->name); return 0; err_ftdi: ftdi_usb_close(&p->ftdic); ftdi_deinit(&p->ftdic); err_lcd: free(p->lcd_contents); err_framebuf: free(p->framebuf); err_begin: return -1; } /** * Close the driver (do necessary clean-up). * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void ula200_close(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; if (p != NULL) { ftdi_usb_purge_buffers(&p->ftdic); ftdi_usb_close(&p->ftdic); ftdi_deinit(&p->ftdic); if (p->framebuf != NULL) free(p->framebuf); if (p->lcd_contents != NULL) free(p->lcd_contents); free(p); } drvthis->store_private_ptr(drvthis, NULL); } /** * Return the display width in characters. * \param drvthis Pointer to driver structure. * \return Number of characters the display is wide. */ MODULE_EXPORT int ula200_width(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; return p->width; } /** * Return the display height in characters. * \param drvthis Pointer to driver structure. * \return Number of characters the display is high. */ MODULE_EXPORT int ula200_height(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; return p->height; } /** * Clear the screen. * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void ula200_clear(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; memset(p->framebuf, ' ', p->width * p->height); } /** * Print a character on the screen at position (x,y). * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height). * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column). * \param y Vertical character position (row). * \param c Character that gets written. */ MODULE_EXPORT void ula200_chr(Driver *drvthis, int x, int y, char c) { PrivateData *p = (PrivateData *) drvthis->private_data; y--; x--; if ((x >= 0) && (y >= 0) && (x < p->width) && (y < p->height)) p->framebuf[(y * p->width) + x] = c; } /** * Print a string on the screen at position (x,y). * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height). * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column). * \param y Vertical character position (row). * \param string String that gets written. */ MODULE_EXPORT void ula200_string(Driver *drvthis, int x, int y, const char string[]) { PrivateData *p = (PrivateData *) drvthis->private_data; int i; x--; /* Convert 1-based coords to 0-based */ y--; for (i = 0; string[i] != '\0'; i++) { /* Check for buffer overflows... */ if ((y * p->width) + x + i > (p->width * p->height)) break; p->framebuf[(y * p->width) + x + i] = string[i]; } } /** * Turn the LCD backlight on or off. * \param drvthis Pointer to driver structure. * \param on New backlight status. */ MODULE_EXPORT void ula200_backlight(Driver *drvthis, int on) { PrivateData *p = (PrivateData *) drvthis->private_data; unsigned char command[2]; int err; if (p->backlight != on) { p->backlight = on; command[0] = 'h'; command[1] = (on) ? '1' : '0'; err = ula200_ftdi_write_command(drvthis, command, 2, false); if (err < 0) report(RPT_WARNING, "%s: error in ula200_ftdi_write_command", drvthis->name); else report(RPT_INFO, "%s: turn backlight %s", drvthis->name, (on) ? "on" : "off"); } } /** * Flush data on screen to the LCD. * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void ula200_flush(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; int x, y; int wid = p->width; char ch; char drawing; int count; int firstdiff; int lastdiff; if (p->all_dirty) { ula200_ftdi_clear(drvthis); p->all_dirty = 0; } /* Update LCD incrementally by comparing with last contents */ count = 0; for (y = 0; y < p->height; y++) { drawing = 0; firstdiff = -1; lastdiff = 0; for (x = 0; x < wid; x++) { ch = p->framebuf[(y * wid) + x]; if (ch != p->lcd_contents[(y * wid) + x]) { p->lcd_contents[(y * wid) + x] = ch; if (firstdiff == -1) { firstdiff = x; } lastdiff = x; } } if (firstdiff >= 0) { ula200_ftdi_position(drvthis, firstdiff, y); ula200_ftdi_string(drvthis, p->framebuf + (y * wid) + firstdiff, lastdiff - firstdiff + 1); } } } /** * Place an icon on the screen. * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column). * \param y Vertical character position (row). * \param icon synbolic value representing the icon. * \retval 0 Icon has been successfully defined/written. * \retval <0 Server core shall define/write the icon. */ MODULE_EXPORT int ula200_icon(Driver *drvthis, int x, int y, int icon) { switch (icon) { case ICON_BLOCK_FILLED: ula200_chr(drvthis, x, y, 0xff); break; case ICON_HEART_FILLED: ula200_chr(drvthis, x, y, 2); break; case ICON_HEART_OPEN: ula200_chr(drvthis, x, y, 1); break; case ICON_ARROW_UP: ula200_chr(drvthis, x, y, 3); break; case ICON_ARROW_DOWN: ula200_chr(drvthis, x, y, 4); break; case ICON_ARROW_LEFT: ula200_chr(drvthis, x, y, 0x1B); break; case ICON_ARROW_RIGHT: ula200_chr(drvthis, x, y, 0x1A); break; case ICON_CHECKBOX_OFF: ula200_chr(drvthis, x, y, 5); break; case ICON_CHECKBOX_ON: ula200_chr(drvthis, x, y, 6); break; case ICON_CHECKBOX_GRAY: ula200_chr(drvthis, x, y, 7); break; default: return -1; /* Let the core do other icons */ } return 0; } /** * Get key from the device. * \param drvthis Pointer to driver structure. * \return String representation of the key; * \c NULL for nothing available / unmapped key. */ MODULE_EXPORT const char * ula200_get_key(Driver *drvthis) { PrivateData *p = drvthis->private_data; unsigned char key; int i; /* * The libftdi has no non-blocking read (`select' system call), so we * force a read that could not block by updating one character on the * display. As long as lcdproc is single-threaded, we can write to * the display because we're not inside a read here. */ ula200_ftdi_position(drvthis, 0, 0); ula200_ftdi_string(drvthis, p->lcd_contents, 1); key = GetKeyFromKeyRing(&p->keyring); /* search the bit that was set by the hardware */ for (i = 0; i < MAX_KEY_MAP; i++) { if (key & (1 << i)) return p->key_map[i]; } if (key != '\0') { report(RPT_INFO, "%s: Untreated key 0x%02X", drvthis->name, key); } return NULL; }