/** \file server/drivers/glcd_drv.c * Unified driver for graphical displays. * * The \c glcd driver consists of a base driver module and sub-drivers. The * base driver implements the driver API and connects to sub-drivers called * ConnectionTypes (CT-drivers). This is very similar to the hd44780 driver. * * The base driver renders text received by the driver API into an internal * framebuffer and passes that on to CT-drivers on calls to \c glcd_flush(). * The CT-driver is responsible to convert the framebuffer into a memory layout * understood by the display and to transmit the data to the display. * * The framebuffer is of linear type, storing a black and white image of the * screen. Each byte contains 8 pixels (1bpp). * * The base driver does not implement incremental updates. Instead the * CT-driver is responsible for this. * * Additionally the CT-driver must create a data structure holding any * required data (configuration data, runtime information) and store a pointer * to it in the base driver's PrivateData->ct_data field. * * The base driver consists of the following files: * \li glcd_drv.c This file * \li glcd-drivers.h CT-driver registry. Add a pointer to your CT-driver's * init() function here. * \li glcd-low.h Base driver's PrivateData and ConnectionType API. * \li glcd_font5x8.h LCDproc's default fixed 5x8 font. */ /*- * Copyright (C) 2011, Markus Dolze * * 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 "lcd.h" #include "lcd_lib.h" #include "glcd_drv.h" #include "glcd-low.h" #include "glcd-drivers.h" #include "report.h" #include "glcd_font5x8.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 = 0; MODULE_EXPORT char *symbol_prefix = "glcd_"; static void glcd_render_char(Driver *drvthis, int x, int y, unsigned char c); static inline void glcd_draw_pixel(PrivateData *p, int x, int y, int color); /** * Initialize the driver. (Required) * \param drvthis Pointer to driver structure. * \retval 0 Success. * \retval <0 Error. */ MODULE_EXPORT int glcd_init(Driver *drvthis) { PrivateData *p; int i; int (*init_fn) (Driver *drvthis)= NULL; const char *s; char size[200]; int w, h; report(RPT_DEBUG, "%s()", __FUNCTION__); /* 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; /* Get and search for the connection type */ s = drvthis->config_get_string(drvthis->name, "ConnectionType", 0, "t6963"); for (i = 0; (connectionMapping[i].name != NULL) && (strcasecmp(s, connectionMapping[i].name) != 0); i++); if (connectionMapping[i].name == NULL) { report(RPT_ERR, "%s: unknown ConnectionType: %s", drvthis->name, s); return -1; } else { /* save connection type's init function */ init_fn = connectionMapping[i].init_fn; report(RPT_INFO, "%s: using ConnectionType: %s", drvthis->name, connectionMapping[i].name); } /* * Set up low-level functions. These should be overwritten by the * connection type's init function. */ if ((p->glcd_functions = (GLCD_functions *) calloc(1, sizeof(GLCD_functions))) == NULL) { report(RPT_ERR, "%s: error mallocing", drvthis->name); return -1; } p->glcd_functions->drv_report = report; p->glcd_functions->drv_debug = debug; p->glcd_functions->blit = NULL; p->glcd_functions->close = NULL; /* Read display size in pixels */ strncpy(size, drvthis->config_get_string(drvthis->name, "Size", 0, GLCD_DEFAULT_SIZE), sizeof(size)); size[sizeof(size) - 1] = '\0'; if ((sscanf(size, "%dx%d", &w, &h) != 2) || (w <= 0) || (w > GLCD_MAX_WIDTH) || (h <= 0) || (h > GLCD_MAX_HEIGHT)) { report(RPT_WARNING, "%s: cannot read Size: %s, Using default %s", drvthis->name, size, GLCD_DEFAULT_SIZE); sscanf(GLCD_DEFAULT_SIZE, "%dx%d", &w, &h); } p->px_width = w; p->px_height = h; /* Do local (=connection type specific) display init */ if (init_fn(drvthis) != 0) return -1; /* consistency check: fail if image transfer function was not defined */ if (p->glcd_functions->blit == NULL) { report(RPT_ERR, "%s: incomplete functions for connection type", drvthis->name); return -1; } /* * Calculate these values AFTER driver initialization, as the driver * may update them. */ if ((p->px_width > GLCD_MAX_WIDTH) || (p->px_height > GLCD_MAX_HEIGHT)) { report(RPT_ERR, "%s: Size %dx%d set by ConnectionType is not supported", drvthis->name, p->px_width, p->px_height); return -1; } p->cellwidth = GLCD_DEFAULT_CELLWIDTH; p->cellheight = GLCD_DEFAULT_CELLHEIGHT; p->width = p->px_width / p->cellwidth; p->height = p->px_height / p->cellheight; /* Allocate framebuffer */ p->framebuf = malloc(BYTES_PER_LINE * p->px_height); if (p->framebuf == NULL) { report(RPT_ERR, "%s: unable to allocate framebuffer", drvthis->name); return -1; } memset(p->framebuf, 0x00, BYTES_PER_LINE * p->px_height); glcd_clear(drvthis); return 0; } /** * Close the driver (do necessary clean-up). (Required) * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void glcd_close(Driver *drvthis) { PrivateData *p = drvthis->private_data; debug(RPT_DEBUG, "%s()", __FUNCTION__); if (p != NULL) { if (p->glcd_functions->close != NULL) p->glcd_functions->close(p); if (p->framebuf != NULL) free(p->framebuf); p->framebuf = NULL; free(p); } drvthis->store_private_ptr(drvthis, NULL); } /** * Return the display width in characters. (Required for output) * \param drvthis Pointer to driver structure. * \return Number of characters the display is wide. */ MODULE_EXPORT int glcd_width(Driver *drvthis) { PrivateData *p = drvthis->private_data; debug(RPT_DEBUG, "%s()", __FUNCTION__); return p->width; } /** * Return the display height in characters. (Required for output) * \param drvthis Pointer to driver structure. * \return Number of characters the display is high. */ MODULE_EXPORT int glcd_height(Driver *drvthis) { PrivateData *p = drvthis->private_data; debug(RPT_DEBUG, "%s()", __FUNCTION__); return p->height; } /** * Return the width of a character in pixels. (Optional) * \param drvthis Pointer to driver structure. * \return Number of pixel columns a character cell is wide. */ MODULE_EXPORT int glcd_cellwidth(Driver *drvthis) { PrivateData *p = drvthis->private_data; debug(RPT_DEBUG, "%s()", __FUNCTION__); return p->cellwidth; } /** * Return the height of a character in pixels. (Optional) * \param drvthis Pointer to driver structure. * \return Number of pixel lines a character cell is high. */ MODULE_EXPORT int glcd_cellheight(Driver *drvthis) { PrivateData *p = drvthis->private_data; debug(RPT_DEBUG, "%s()", __FUNCTION__); return p->cellheight; } /** * Clear the screen. (Required for output) * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void glcd_clear(Driver *drvthis) { PrivateData *p = drvthis->private_data; debug(RPT_DEBUG, "%s()", __FUNCTION__); memset(p->framebuf, 0x00, BYTES_PER_LINE * p->px_height); } /** * Flush data on screen to the display. (Optional) * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void glcd_flush(Driver *drvthis) { PrivateData *p = drvthis->private_data; debug(RPT_DEBUG, "%s()", __FUNCTION__); p->glcd_functions->blit(p); } /** * Print a string on the screen at position (x,y). (Required for output) * 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. * * \see glcd_flush */ MODULE_EXPORT void glcd_string(Driver *drvthis, int x, int y, const char string[]) { PrivateData *p = drvthis->private_data; int i; debug(RPT_DEBUG, "%s(%i,%i,%.40s)", __FUNCTION__, x, y, string); y--; /* Convert 1-based coords to 0-based... */ x--; if ((y < 0) || (y >= p->height)) return; for (i = 0; (string[i] != '\0') && (x < p->width); i++, x++) glcd_render_char(drvthis, x, y, string[i]); } /** * Print a character on the screen at position (x,y). (Required for output) * 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. * * \note Keep in mind that character 0x00 may be a valid character on your * display and it is used by the bignum library. Avoid using string handling * functions here! * * \see glcd_flush */ MODULE_EXPORT void glcd_chr(Driver *drvthis, int x, int y, char c) { debug(RPT_DEBUG, "%s(%i,%i,%c)", __FUNCTION__, x, y, c); x--; y--; glcd_render_char(drvthis, x, y, c); } /** * Place an icon on the screen. (Optional) * \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 glcd_icon(Driver *drvthis, int x, int y, int icon) { debug(RPT_DEBUG, "%s(%i,%i,%i)", __FUNCTION__, x, y, icon); return (glcd_icon5x8(drvthis, x, y, icon)); } /** * API: Draws a vertical bar, from the bottom of the screen up. */ MODULE_EXPORT void glcd_vbar(Driver *drvthis, int x, int y, int len, int promille, int options) { debug(RPT_DEBUG, "%s(%i,%i,%i,%i,%i)", __FUNCTION__, x, y, len, promille, options); lib_vbar_static(drvthis, x, y, len, promille, options, GLCD_FONT_HEIGHT, 0x90); } /** * API: Draws a horizontal bar to the right. * * \note For hBars the actual width of the font (5) has to be used instead * of the cell width (6)! */ MODULE_EXPORT void glcd_hbar(Driver *drvthis, int x, int y, int len, int promille, int options) { int pixels = ((long) 2 * len * GLCD_FONT_WIDTH) * promille / 2000; int pos; debug(RPT_DEBUG, "%s(%i,%i,%i,%i,%i)", __FUNCTION__, x, y, len, promille, options); for (pos = 0; pos < len; pos++) { if (pixels >= GLCD_FONT_WIDTH ) glcd_chr(drvthis, x + pos, y, 0x9e); else if (pixels >= 1) glcd_chr(drvthis, x + pos, y, 0x99 + pixels); else ; /* do nothing */ pixels -= GLCD_FONT_WIDTH; } } /** * Provide some information about this driver. (Optional) * \param drvthis Pointer to driver structure. * \return Constant string with information. */ MODULE_EXPORT const char * glcd_get_info(Driver *drvthis) { static char *info_string = "Unified driver for graphical displays"; debug(RPT_DEBUG, "%s()", __FUNCTION__); return info_string; } /** * Draw one pixel into the framebuffer using 1bpp (black and white). This * function actually decides about the format of the framebuffer. Using this * implementation (0,0) is top left and bytes contain pixels from left to right. * * \param p Pointer to driver's private data. * \param x X-position * \param y Y-position * \param color Pixel color: 1 = set (black), 0 = not set (blank) */ static inline void glcd_draw_pixel(PrivateData *p, int x, int y, int color) { unsigned int pos; /* Byte within the framebuffer */ unsigned char bit; /* Bit within the framebuffer byte */ pos = y * BYTES_PER_LINE + (x / 8); bit = 0x80 >> (x % 8); if (color == 1) p->framebuf[pos] |= bit; else p->framebuf[pos] &= ~bit; } /** * Draws char c from font definition to the framebuffer at position * x,y. These are zero-based textmode positions. * * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column). * \param y Vertical character position (row). * \param z Character that gets written. */ static void glcd_render_char(Driver *drvthis, int x, int y, unsigned char c) { PrivateData *p = drvthis->private_data; int font_x, font_y; /* Position in the font definition array */ int px, py; /* Pixel position on the display */ if (x < 0 || x >= p->width || y < 0 || y >= p->height) return; /* * Algorithm: For each row in the font definition check if bit (dot) * at each column is set. If yes, plot a dot into the framebuffer. If * the bit is not set then clear the dot. Currently it is wrong to * assume the framebuffer is clear (e.g. the heartbeat does not clear * it's contents in advance). */ /* FIXME: What happens if font is larger than cell size? */ py = y * p->cellheight; for (font_y = 0; (font_y < GLCD_FONT_HEIGHT) && (py < p->px_height); font_y++) { px = x * p->cellwidth; /* * Note: Initializing font_x with font's width leaves one * empty column to the left. */ for (font_x = GLCD_FONT_WIDTH; (font_x > -1) && (px < p->px_width); font_x--) { if (glcd_iso8859_1[c][font_y] & (1 << font_x)) glcd_draw_pixel(p, px, py, 1); else glcd_draw_pixel(p, px, py, 0); px++; } py++; } }