/* This is the LCDproc driver for tyan lcd module (tyan Barebone GS series) Author: yhlu@tyan.com Copyright (C) 2004 Tyan Corp 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 */ /* * See ftp://ftp.tyan.com/barebone_support/GS10-GS12%20LCD-Pack.zip * for documentation */ /* * Driver status * 02/04/2004: Working driver * * * */ #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "lcd.h" #include "tyan_lcdm.h" #include "report.h" #include "lcd_lib.h" #include "adv_bignum.h" /* Internal functions */ static void tyan_lcdm_switch_mode(int fdfd); static void tyan_lcdm_hardware_clear(int fd); static void tyan_lcdm_set_rampos(int fd, unsigned char pos); static void tyan_lcdm_write_str(int fd, unsigned char *str, unsigned char start_addr, int length); #if 0 static void tyan_lcdm_set_cursor(int fd, unsigned char start_addr, int pos); #endif static unsigned char tyan_lcdm_read_key(int fd); /* * Opens com port and sets baud correctly... */ MODULE_EXPORT int tyan_lcdm_init (Driver *drvthis, char *args) { PrivateData *p; struct termios portset; char size[200] = DEFAULT_SIZE; int tmp, w, h; /* Allocate and store private data */ p = (PrivateData *) calloc(1, sizeof(PrivateData)); if (p == NULL) return -1; if (drvthis->store_private_ptr(drvthis, p)) return -1; /* initialize private data */ p->speed = DEFAULT_SPEED; p->ccmode = standard; p->fd = -1; p->framebuf = NULL; p->backingstore = NULL; p->cellwidth = DEFAULT_CELL_WIDTH; p->cellheight = DEFAULT_CELL_HEIGHT; debug(RPT_INFO, "tyan_lcdm: init(%p,%s)", drvthis, args); /* Read config file */ /* Which serial device should be used */ strncpy(p->device, drvthis->config_get_string(drvthis->name, "Device", 0, DEFAULT_DEVICE), sizeof(p->device)); p->device[sizeof(p->device)-1] = '\0'; debug(RPT_INFO,"%s: using Device %s", drvthis->name, p->device); /* Which size */ strncpy(size, drvthis->config_get_string(drvthis->name, "Size", 0, DEFAULT_SIZE), sizeof(size)); size[sizeof(size)-1] = '\0'; if ((sscanf(size , "%dx%d", &w, &h) != 2) || (w <= 0) || (w > LCD_MAX_WIDTH) || (h <= 0) || (h > LCD_MAX_HEIGHT)) { report(RPT_WARNING, "%s: cannot read Size: %s; using default %s", drvthis->name, size, DEFAULT_SIZE); sscanf(DEFAULT_SIZE , "%dx%d", &w, &h); } p->width = w; p->height = h; /* Which speed */ tmp = drvthis->config_get_int(drvthis->name , "Speed", 0, DEFAULT_SPEED); if (tmp == 4800) p->speed = B4800; else if (tmp == 9600) p->speed = B9600; else { report(RPT_WARNING, "%s: Speed must be 4800 or 9600; using default %d", drvthis->name, DEFAULT_SPEED); p->speed = 9600; } /* Set up io port correctly, and open it... */ debug(RPT_DEBUG, "tyan_lcdm: Opening serial device: %s", p->device); p->fd = open(p->device, O_RDWR | O_NOCTTY | O_NDELAY); if (p->fd == -1) { report(RPT_ERR, "%s: open(%s) failed (%s)", drvthis->name, p->device, strerror(errno)); return -1; } tcgetattr(p->fd, &portset); /* We use RAW mode */ #ifdef HAVE_CFMAKERAW /* The easy way */ cfmakeraw(&portset); #else /* The hard way */ portset.c_cflag = CS8 | CREAD | CLOCAL; portset.c_iflag = IXON | IXOFF | IGNBRK | IGNCR; portset.c_oflag &= ~ONLCR; portset.c_lflag = 0; portset.c_cc[VMIN] = 1; portset.c_cc[VTIME] = 0; #endif /* Set port speed */ cfsetospeed(&portset, p->speed); cfsetispeed(&portset, p->speed); /* Do it... */ tcsetattr(p->fd, TCSANOW, &portset); /* Make sure the frame buffer is there... */ p->framebuf = (unsigned char *) malloc(p->width * p->height); if (p->framebuf == NULL) { report(RPT_ERR, "%s: unable to create framebuffer", drvthis->name); return -1; } memset(p->framebuf, ' ', p->width * p->height); p->backingstore = (unsigned char *) malloc(p->width * p->height); if (p->backingstore == NULL) { report(RPT_ERR, "%s: unable to create backing store", drvthis->name); return -1; } memset(p->backingstore, ' ', p->width * p->height); /* Set display-specific stuff.. */ tyan_lcdm_switch_mode(p->fd); tyan_lcdm_hardware_clear(p->fd); report(RPT_DEBUG, "%s: init() done", drvthis->name); return 1; } /* * Clean-up */ MODULE_EXPORT void tyan_lcdm_close (Driver *drvthis) { PrivateData *p = drvthis->private_data; if (p != NULL) { if (p->fd >= 0) close(p->fd); if (p->framebuf != NULL) free(p->framebuf); if (p->backingstore != NULL) free(p->backingstore); free(p); } drvthis->store_private_ptr(drvthis, NULL); } /* * Returns the display width */ MODULE_EXPORT int tyan_lcdm_width (Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->width; } /* * Returns the display height */ MODULE_EXPORT int tyan_lcdm_height (Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->height; } /* * Returns the display's character cell width */ MODULE_EXPORT int tyan_lcmd_cellwidth(Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->cellwidth; } /* * Returns the display's character cell height */ MODULE_EXPORT int tyan_lcmd_cellheight(Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->cellheight; } /* * Flushes all output to the lcd... */ MODULE_EXPORT void tyan_lcdm_flush (Driver *drvthis) { PrivateData *p = drvthis->private_data; int i; unsigned char *xp = p->framebuf; unsigned char *xq = p->backingstore; /* * We don't use delta update yet. * It is possible but not easy, we can only update a line, full or begining. */ for (i = 0; i < p->width; i++) { if (*xp != *xq) { tyan_lcdm_write_str(p->fd, p->framebuf, 0x80, 16); memcpy(p->backingstore, p->framebuf, p->width); break; } xp++; xq++; } xp = p->framebuf + p->width; xq = p->backingstore + p->width; for (i = 0; i < p->width; i++) { if (*xp != *xq) { tyan_lcdm_write_str(p->fd, p->framebuf + p->width, 0xc0, 16); memcpy(p->backingstore + p->width, p->framebuf + p->width, p->width); break; } xp++; xq++; } } /* * Return one char from the KeyRing */ MODULE_EXPORT const char * tyan_lcdm_get_key (Driver *drvthis) { PrivateData *p = drvthis->private_data; unsigned char key = tyan_lcdm_read_key(p->fd); report(RPT_DEBUG, "%s: get_key(): raw key code: 0x%02X", drvthis->name, key); switch (key) { case TYAN_LCDM_KEY_LEFT: return "Left"; break; case TYAN_LCDM_KEY_UP: return "Up"; break; case TYAN_LCDM_KEY_DOWN: return "Down"; break; case TYAN_LCDM_KEY_RIGHT: return "Right"; break; case TYAN_LCDM_KEY_ENTER: return "Enter"; break; case TYAN_LCDM_KEY_ESCAPE: return "Escape"; break; default: report(RPT_INFO, "%s: Untreated key 0x%02X", drvthis->name, key); return NULL; break; } } /* * Prints a character on the lcd display, at position (x,y). * The upper-left is (1,1), and the lower right should be (16,2). */ MODULE_EXPORT void tyan_lcdm_chr (Driver *drvthis, int x, int y, char c) { PrivateData *p = drvthis->private_data; y--; x--; if ((x >= 0) && (y >= 0) && (x < p->width) && (y < p->height)) p->framebuf[(y * p->width) + x] = c; } /* * Sets the backlight on or off. * The hardware support any value between 0 and 100. * Need to find out if we have support for intermediate value. */ MODULE_EXPORT void tyan_lcdm_backlight (Driver *drvthis, int on) { } /* * Draws a vertical bar... */ MODULE_EXPORT void tyan_lcdm_vbar (Driver *drvthis, int x, int y, int len, int promille, int options) { /* x and y are the start position of the bar. * The bar by default grows in the 'up' direction * (other direction not yet implemented). * len is the number of characters that the bar is long at 100% * promille is the number of promilles (0..1000) that the bar should be filled. */ PrivateData *p = drvthis->private_data; if (p->ccmode != vbar) { unsigned char vBar[p->cellheight]; int i; if (p->ccmode != standard) { /* Not supported(yet) */ report(RPT_WARNING, "%s: vbar: cannot combine two modes using user-defined characters", drvthis->name); return; } p->ccmode = vbar; memset(vBar, 0x00, sizeof(vBar)); for (i = 1; i < p->cellheight; i++) { // add pixel line per pixel line ... vBar[p->cellheight - i] = 0xFF; tyan_lcdm_set_char(drvthis, i, vBar); } } lib_vbar_static(drvthis, x, y, len, promille, options, p->cellheight, 0); } /* * Draws a horizontal bar to the right. */ MODULE_EXPORT void tyan_lcdm_hbar (Driver *drvthis, int x, int y, int len, int promille, int options) { /* x and y are the start position of the bar. * The bar by default grows in the 'right' direction * (other direction not yet implemented). * len is the number of characters that the bar is long at 100% * promille is the number of promilles (0..1000) that the bar should be filled. */ PrivateData *p = drvthis->private_data; if (p->ccmode != hbar) { unsigned char hBar[p->cellheight]; int i; if (p->ccmode != standard) { /* Not supported(yet) */ report(RPT_WARNING, "%s: hbar: cannot combine two modes using user-defined characters", drvthis->name); return; } p->ccmode = hbar; for (i = 1; i <= p->cellwidth; i++) { // fill pixel columns from left to right. memset(hBar, 0xFF & ~((1 << (p->cellwidth - i)) - 1), sizeof(hBar)); tyan_lcdm_set_char(drvthis, i, hBar); } } lib_hbar_static(drvthis, x, y, len, promille, options, p->cellwidth, 0); } /* * Writes a big number. */ MODULE_EXPORT void tyan_lcdm_num (Driver *drvthis, int x, int num) { PrivateData *p = drvthis->private_data; int do_init = 0; if ((num < 0) || (num > 10)) return; if (p->ccmode != bignum) { if (p->ccmode != standard) { /* Not supported (yet) */ report(RPT_WARNING, "%s: num: cannot combine two modes using user-defined characters", drvthis->name); return; } p->ccmode = bignum; do_init = 1; } // Lib_adv_bignum does everything needed to show the bignumbers. lib_adv_bignum(drvthis, x, num, 0, do_init); } /* * Gets number of custom chars (always NUM_CCs) */ MODULE_EXPORT int tyan_lcdm_get_free_chars(Driver *drvthis) { //PrivateData *p = drvthis->private_data; return NUM_CCs; } /* * Sets a custom character from 0 - (NUM_CCs-1)... * * The input is an array of bytes, each representing a pixel row * starting from the top to bottom. * The bits in each byte represent the pixels where the LSB * (least significant bit) is the rightmost pixel in each pixel row */ MODULE_EXPORT void tyan_lcdm_set_char (Driver *drvthis, int n, unsigned char *dat) { PrivateData *p = drvthis->private_data; unsigned char mask = (1 << p->cellwidth) - 1; unsigned char out[p->cellheight + 1]; int row; if ((n < 0) || (n >= NUM_CCs)) return; if (dat == NULL) return; for (row = 0; row < p->cellheight; row++) { int letter = dat[row] & mask; if (p->cc[n].cache[row] != letter) p->cc[n].clean = 0; /* only mark dirty if really different */ p->cc[n].cache[row] = letter; out[row+1] = letter; } tyan_lcdm_write_str(p->fd, out, (unsigned char) (0x40 + n * 8), 8); } /* * Places an icon on screen */ MODULE_EXPORT int tyan_lcdm_icon (Driver *drvthis, int x, int y, int icon) { PrivateData *p = drvthis->private_data; static unsigned char heart_open[] = { b__XXXXX, b__X_X_X, b_______, b_______, b_______, b__X___X, b__XX_XX, b__XXXXX }; static unsigned char heart_filled[] = { b__XXXXX, b__X_X_X, b___X_X_, b___XXX_, b___XXX_, b__X_X_X, b__XX_XX, b__XXXXX }; static unsigned char arrow_up[] = { b____X__, b___XXX_, b__X_X_X, b____X__, b____X__, b____X__, b____X__, b_______ }; static unsigned char arrow_down[] = { b____X__, b____X__, b____X__, b____X__, b__X_X_X, b___XXX_, b____X__, b_______ }; /* static unsigned char arrow_left[] = { b_______, b____X__, b___X___, b__XXXXX, b___X___, b____X__, b_______, b_______ }; static unsigned char arrow_right[] = { b_______, b____X__, b_____X_, b__XXXXX, b_____X_, b____X__, b_______, b_______ }; */ static unsigned char checkbox_off[] = { b_______, b_______, b__XXXXX, b__X___X, b__X___X, b__X___X, b__XXXXX, b_______ }; static unsigned char checkbox_on[] = { b____X__, b____X__, b__XXX_X, b__X_XX_, b__X_X_X, b__X___X, b__XXXXX, b_______ }; static unsigned char checkbox_gray[] = { b_______, b_______, b__XXXXX, b__X_X_X, b__XX_XX, b__X_X_X, b__XXXXX, b_______ }; /* static unsigned char selector_left[] = { b___X___, b___XX__, b___XXX_, b___XXXX, b___XXX_, b___XX__, b___X___, b_______ }; static unsigned char selector_right[] = { b_____X_, b____XX_, b___XXX_, b__XXXX_, b___XXX_, b____XX_, b_____X_, b_______ }; static unsigned char ellipsis[] = { b_______, b_______, b_______, b_______, b_______, b_______, b__X_X_X, b_______ }; static unsigned char block_filled[] = { b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX }; */ /* Yes we know, this is a VERY BAD implementation :-) */ switch (icon) { case ICON_BLOCK_FILLED: tyan_lcdm_chr(drvthis, x, y, 255); //tyan_lcdm_set_char(drvthis, 6, block_filled); //tyan_lcdm_chr(drvthis, x, y, 0); break; case ICON_HEART_OPEN: tyan_lcdm_set_char(drvthis, 0, heart_open); tyan_lcdm_chr(drvthis, x, y, 0); break; case ICON_HEART_FILLED: tyan_lcdm_set_char(drvthis, 0, heart_filled); tyan_lcdm_chr(drvthis, x, y, 0); break; case ICON_ARROW_UP: p->ccmode = custom; tyan_lcdm_set_char(drvthis, 1, arrow_up); tyan_lcdm_chr(drvthis, x, y, 1); break; case ICON_ARROW_DOWN: p->ccmode = custom; tyan_lcdm_set_char(drvthis, 2, arrow_down); tyan_lcdm_chr(drvthis, x, y, 2); break; case ICON_ARROW_LEFT: tyan_lcdm_chr(drvthis, x, y, 0x7F); break; case ICON_ARROW_RIGHT: tyan_lcdm_chr(drvthis, x, y, 0x7E); break; case ICON_CHECKBOX_OFF: p->ccmode = custom; tyan_lcdm_set_char(drvthis, 3, checkbox_off); tyan_lcdm_chr(drvthis, x, y, 3); break; case ICON_CHECKBOX_ON: p->ccmode = custom; tyan_lcdm_set_char(drvthis, 4, checkbox_on); tyan_lcdm_chr(drvthis, x, y, 4); break; case ICON_CHECKBOX_GRAY: p->ccmode = custom; tyan_lcdm_set_char(drvthis, 5, checkbox_gray); tyan_lcdm_chr(drvthis, x, y, 5); break; default: return -1; /* Let the core do other icons */ } return 0; } /* * Clears the LCD screen */ MODULE_EXPORT void tyan_lcdm_clear (Driver *drvthis) { PrivateData *p = drvthis->private_data; memset(p->framebuf, ' ', p->width * p->height); } /* * Prints a string on the lcd display, at position (x,y). The * upper-left is (1,1), and the lower right should be (16,2). */ MODULE_EXPORT void tyan_lcdm_string (Driver *drvthis, int x, int y, const char string[]) { PrivateData *p = drvthis->private_data; int i; /* Convert 1-based coords to 0-based... */ x--; y--; if ((y < 0) || (y >= p->height)) return; for (i = 0; (string[i] != '\0') && (x < p->width); i++, x++) { /* Check for buffer overflows... */ if (x >= 0) p->framebuf[(y * p->width) + x] = string[i]; } } /* * switch mode() */ static void tyan_lcdm_switch_mode(int fd) { char lcdcmd1[4] = { TYAN_LCDM_CMD_BEGIN, 0x73, 0x01, TYAN_LCDM_CMD_END }; char lcdcmd2[3] = { TYAN_LCDM_CMD_BEGIN, 0x6c, TYAN_LCDM_CMD_END }; //set os selection write(fd, lcdcmd1, 4); sleep(1); //send "LCD Ready" cmd write(fd, lcdcmd2, 3); sleep(1); } /* * Hardware clears the LCD screen */ static void tyan_lcdm_hardware_clear(int fd) { char lcdcmd[5] = { TYAN_LCDM_CMD_BEGIN, 0x70, 0x00, 0x01, TYAN_LCDM_CMD_END}; //set os selection write(fd, lcdcmd, 5); } static void tyan_lcdm_set_rampos(int fd, unsigned char pos) { char cmd_str[5] = { TYAN_LCDM_CMD_BEGIN, 0x70, 0x00, 0x00, TYAN_LCDM_CMD_END }; cmd_str[3] = pos; write(fd, cmd_str, 5); } static void tyan_lcdm_write_str(int fd, unsigned char *str,unsigned char start_addr, int length) { //CGRAM 0x40, 0x48,.... //if Line 1: start_addr = 0x80 //if Line 2: start_addr = 0xc0 // 1<= length <=16 unsigned char cmd_str[20] = { TYAN_LCDM_CMD_BEGIN, 0x70, 0x02 }; tyan_lcdm_set_rampos(fd, start_addr); memset(cmd_str, ' ', 20); cmd_str[0] = TYAN_LCDM_CMD_BEGIN; cmd_str[1] = 0x70; cmd_str[2] = 0x02; cmd_str[19] = TYAN_LCDM_CMD_END; memcpy(cmd_str+3, str, (length < 16) ? length : 16); write(fd, cmd_str, 20); } #if 0 static void tyan_lcdm_set_cursor(int fd, unsigned char start_addr, int pos) { char cmd_str[5] = { TYAN_LCDM_CMD_BEGIN, 0x70, 0x00, 0x0e, TYAN_LCDM_CMD_END }; tyan_lcdm_set_rampos(pos+start_addr); write(fd,cmd_str,5); } #endif /* Note: this implementation works although it differs from the documentation * available at ftp://ftp.tyan.com/barebone_support/GS10-GS12%20LCD-Pack.zip * (i.e. with respect to this the docs are wrong) */ static unsigned char tyan_lcdm_read_key(int fd) { int count = 0; char key_str[4]; memset(key_str, '\0', 4); count = read(fd, key_str, 4); if ((count == 4) && (key_str[0] == (char) TYAN_LCDM_CMD_BEGIN) && (key_str[1] == (char) 0x72) && (key_str[3] == (char) TYAN_LCDM_CMD_END)) { return key_str[2]; } return 0xF4; //error }