/** \file server/drivers/imonlcd.c * Driver for SoundGraph iMON OEM LCD Modules * * Requires the lirc_imon module included with lirc >= 0.8.4a */ /*- * Copyright (c) 2004, Venky Raju * 2007, Dean Harding * 2007, Christian Leuschen * 2009, Jonathan Kyler * 2009, Eric Pooch < epooch (at) cox (dot) net> * * This file is released under the GNU General Public License. Refer to the * COPYING file distributed with this package. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #ifdef HAVE_STDINT_H # include #endif #include #include "lcd.h" #include "lcd_lib.h" #include "report.h" /* Defines a 6x8 font based on ISO 8859-15 */ #define LCD_DEFAULT_CELL_WIDTH 6 #define LCD_DEFAULT_CELL_HEIGHT 8 #include "imonlcd_font.h" #include "imonlcd.h" #define IMONLCD_PACKET_DATA_SIZE 7 #define DEFAULT_DEVICE "/dev/lcd0" #define DEFAULT_SIZE "96x16" /**< This is the size in "pixels" (colXrow) */ #define DEFAULT_CONTRAST 200 #define DEFAULT_BACKLIGHT 1 /**< turn backlight on */ #define DEFAULT_DISCMODE 0 /**< spin the "slim" disc */ #define DEFAULT_ON_EXIT 1 /**< show the big clock */ #define DEFAULT_PROTOCOL 0 /**< protocol for 15c2:ffdc device */ #define ON_EXIT_SHOWMSG 0 /**< Do nothing - just leave the "shutdown" * message there */ #define ON_EXIT_SHOWCLOCK 1 /**< Show the big clock */ #define ON_EXIT_BLANKSCREEN 2 /**< Blank the device completely */ #define PROTOCOL_FFDC 0 /**< protocol ID for 15c2:ffdc device */ #define PROTOCOL_0038 1 /**< protocol ID for 15c2:0038 device */ /* 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 = "imonlcd_"; /** Private data for the \c imonlcd driver */ typedef struct imonlcd_private_data { char info[255]; int imon_fd; /* send buffer for one command or display data */ unsigned char tx_buf[8]; /* framebuffer and backingstore for current contents */ unsigned char *framebuf; unsigned char *backingstore; int bytesperline; int width, height; int cellwidth, cellheight; int on_exit; int contrast; /* 0 = lowest contrast, 1000 = highest */ int backlightOn; /* stores the backlight state */ /* * 0 = two disc-segments spinning as default, 1 = their complement * spinning */ int discMode; /* * 0 = protocol for 15c2:ffdc device, 1 = protocol for 15c2:0038 * device */ int protocol; /* store commands appropriate for the version of the iMON LCD */ uint64_t command_display; uint64_t command_shutdown; uint64_t command_display_on; uint64_t command_clear_alarm; /* last "state" of the CD icon so that we can animate it */ int last_cd_state; /* save the last output state so we don't needlessly reset the icons */ int last_output_state; int last_output_bar_state; } PrivateData; /* * Just for convenience and to have the commands at one place. */ #define COMMANDS_SET_ICONS (uint64_t) 0x0100000000000000LL #define COMMANDS_SET_CONTRAST (uint64_t) 0x0300000000000000LL #define COMMANDS_DISPLAY (uint64_t) 0x0000000000000000LL // must be or'd with the applicable COMMANDS_DISPLAY_BYTE #define COMMANDS_SHUTDOWN (uint64_t) 0x0000000000000008LL // must be or'd with the applicable COMMANDS_DISPLAY_BYTE #define COMMANDS_DISPLAY_ON (uint64_t) 0x0000000000000040LL // must be or'd with the applicable COMMANDS_DISPLAY_BYTE #define COMMANDS_CLEAR_ALARM (uint64_t) 0x0000000000000000LL // must be or'd with the applicable COMMANDS_ALARM_BYTE #define COMMANDS_SET_LINES0 (uint64_t) 0x1000000000000000LL #define COMMANDS_SET_LINES1 (uint64_t) 0x1100000000000000LL #define COMMANDS_SET_LINES2 (uint64_t) 0x1200000000000000LL #define COMMANDS_INIT (uint64_t) 0x0200000000000000LL //not exactly sure what this does, but it's needed #define COMMANDS_LOW_CONTRAST (uint64_t) (COMMANDS_SET_CONTRAST + (uint64_t) 0x00FFFFFF00580A00LL) #define COMMAND_MASK (uint64_t) 0xFF00000000000000LL /* Allow for variations in Soundgraphs numerous protocols */ /* 15c2:0038 SoundGraph iMON */ #define COMMANDS_DISPLAY_BYTE_0038 (uint64_t) 0x8800000000000000LL #define COMMANDS_ALARM_BYTE_0038 (uint64_t) 0x8a00000000000000LL /* 15c2:ffdc SoundGraph iMON */ #define COMMANDS_DISPLAY_BYTE_FFDC (uint64_t) 0x5000000000000000LL #define COMMANDS_ALARM_BYTE_FFDC (uint64_t) 0x5100000000000000LL /* * These are used with the imon_output function to determine which icons to * turn on/off. Because we only get a 32-bit integer to play, some of the * icons are grouped into "sets" from which you can only select to turn one * on at a time. */ #define IMON_OUTPUT_CD_MASK 0x00000001 #define IMON_OUTPUT_TOPROW_MASK 0x0000000E #define IMON_OUTPUT_SPEAKER_MASK 0x00000030 #define IMON_OUTPUT_SPDIF_MASK 0x00000040 #define IMON_OUTPUT_SRC_MASK 0x00000080 #define IMON_OUTPUT_FIT_MASK 0x00000100 #define IMON_OUTPUT_TV_MASK 0x00000200 #define IMON_OUTPUT_HDTV_MASK 0x00000400 #define IMON_OUTPUT_SCR1_MASK 0x00000800 #define IMON_OUTPUT_SCR2_MASK 0x00001000 #define IMON_OUTPUT_BRICONS_MASK 0x0000E000 #define IMON_OUTPUT_BMICONS_MASK 0x00070000 #define IMON_OUTPUT_BLICONS_MASK 0x00380000 #define IMON_OUTPUT_VOL_MASK 0x00400000 #define IMON_OUTPUT_TIME_MASK 0x00800000 #define IMON_OUTPUT_ALARM_MASK 0x01000000 #define IMON_OUTPUT_REC_MASK 0x02000000 #define IMON_OUTPUT_REP_MASK 0x04000000 #define IMON_OUTPUT_SFL_MASK 0x08000000 #define IMON_OUTPUT_PBARS_MASK 0x10000000 #define IMON_OUTPUT_DISK_IN_MASK 0x20000000 #define IMON_ICON_ALL (uint64_t) 0x00FFFFFFFFFFFFFFLL /* Byte 6 */ #define IMON_ICON_DISK_OFF (uint64_t) 0x7F7000FFFFFFFFFFLL #define IMON_ICON_DISK_ON (uint64_t) 0x0080FF0000000000LL #define IMON_ICON_DISK_IN (uint64_t) 0x0080000000000000LL #define IMON_ICON_CD_IN (uint64_t) 0x00806B0000000000LL #define IMON_ICON_DVD_IN (uint64_t) 0x0080550000000000LL /* Byte 5 */ #define IMON_ICON_WMA2 ((uint64_t) 0x1 << 39) #define IMON_ICON_WAV ((uint64_t) 0x1 << 38) #define IMON_ICON_REP ((uint64_t) 0x1 << 37) #define IMON_ICON_SFL ((uint64_t) 0x1 << 36) #define IMON_ICON_ALARM ((uint64_t) 0x1 << 35) #define IMON_ICON_REC ((uint64_t) 0x1 << 34) #define IMON_ICON_VOL ((uint64_t) 0x1 << 33) #define IMON_ICON_TIME ((uint64_t) 0x1 << 32) /* Byte 4 */ #define IMON_ICON_XVID ((uint64_t) 0x1 << 31) #define IMON_ICON_WMV ((uint64_t) 0x1 << 30) #define IMON_ICON_MPG2 ((uint64_t) 0x1 << 29) #define IMON_ICON_AC3 ((uint64_t) 0x1 << 28) #define IMON_ICON_DTS ((uint64_t) 0x1 << 27) #define IMON_ICON_WMA ((uint64_t) 0x1 << 26) #define IMON_ICON_MP3 ((uint64_t) 0x1 << 25) #define IMON_ICON_OGG ((uint64_t) 0x1 << 24) /* Byte 3 */ #define IMON_ICON_SRC ((uint64_t) 0x1 << 23) #define IMON_ICON_FIT ((uint64_t) 0x1 << 22) #define IMON_ICON_TV_2 ((uint64_t) 0x1 << 21) #define IMON_ICON_HDTV ((uint64_t) 0x1 << 20) #define IMON_ICON_SCR1 ((uint64_t) 0x1 << 19) #define IMON_ICON_SCR2 ((uint64_t) 0x1 << 18) #define IMON_ICON_MPG ((uint64_t) 0x1 << 17) #define IMON_ICON_DIVX ((uint64_t) 0x1 << 16) /* Byte 2 */ #define IMON_SPKR_FC ((uint64_t) 0x1 << 15) #define IMON_SPKR_FR ((uint64_t) 0x1 << 14) #define IMON_SPKR_SL ((uint64_t) 0x1 << 13) #define IMON_SPKR_LFE ((uint64_t) 0x1 << 12) #define IMON_SPKR_SR ((uint64_t) 0x1 << 11) #define IMON_SPKR_RL ((uint64_t) 0x1 << 10) #define IMON_SPKR_SPDIF ((uint64_t) 0x1 << 9) #define IMON_SPKR_RR ((uint64_t) 0x1 << 8) /* Byte 1 */ #define IMON_ICON_MUSIC ((uint64_t) 0x1 << 7) #define IMON_ICON_MOVIE ((uint64_t) 0x1 << 6) #define IMON_ICON_PHOTO ((uint64_t) 0x1 << 5) #define IMON_ICON_CD_DVD ((uint64_t) 0x1 << 4) #define IMON_ICON_TV ((uint64_t) 0x1 << 3) #define IMON_ICON_WEBCAST ((uint64_t) 0x1 << 2) #define IMON_ICON_NEWS ((uint64_t) 0x1 << 1) #define IMON_SPKR_FL ((uint64_t) 0x1) /* prototypes for driver internal functions */ static void imonlcd_display_init(Driver *drvthis); static void draw_bigchar(imon_bigfont *font, int ch, int x, int y, PrivateData *p); static void setLineLength(int topLine, int botLine, int topProgress, int botProgress, PrivateData *p); static void setBuiltinProgressBars(int topLine, int botLine, int topProgress, int botProgress, PrivateData *p); static int lengthToPixels(int length); static void send_command_data(uint64_t commandData, PrivateData *p); static void send_packet(PrivateData *p); /** * Initialize the driver. * \param drvthis Pointer to driver structure. * \retval 0 Success. * \retval <0 Error. */ MODULE_EXPORT int imonlcd_init(Driver *drvthis) { PrivateData *p = NULL; char buf[256]; int graph_width, graph_height; /* width and height of screen, in pixels */ int tmp; /* Allocate, initialize and store private p */ p = (PrivateData *) calloc(1, sizeof(PrivateData)); if (p == NULL) { report(RPT_ERR, "%s: failed to allocate private data", drvthis->name); return -1; } if (drvthis->store_private_ptr(drvthis, p)) { report(RPT_ERR, "%s: failed to store private data pointer", drvthis->name); return -1; } p->imon_fd = -1; graph_width = 0; graph_height = 0; p->width = 0; /* Display width, in characters */ p->height = 0; /* Display height, in characters */ p->cellwidth = LCD_DEFAULT_CELL_WIDTH; /* width of a character, in pixels */ p->cellheight = LCD_DEFAULT_CELL_HEIGHT; /* height of a character, in pixels */ p->last_cd_state = 0; p->last_output_state = 0x0; /* no icons turned on at startup */ p->last_output_bar_state = 0x0; /* no bars turned on at startup */ p->discMode = 0; /* Get settings from config file */ /* Get device */ strncpy(buf, drvthis->config_get_string(drvthis->name, "Device", 0, DEFAULT_DEVICE), sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; report(RPT_INFO, "%s: using Device %s", drvthis->name, buf); /* Open device for writing */ if ((p->imon_fd = open(buf, O_WRONLY)) < 0) { report(RPT_ERR, "%s: ERROR opening %s (%s).", drvthis->name, buf, strerror(errno)); report(RPT_ERR, "%s: Did you load the iMON kernel module?", drvthis->name); return -1; } /* Get protocol version */ tmp = drvthis->config_get_int(drvthis->name, "Protocol", 0, DEFAULT_PROTOCOL); if ((tmp < 0) || (tmp > 1)) { report(RPT_WARNING, "%s: Protocol must be between 0 and 1; using default %d", drvthis->name, DEFAULT_PROTOCOL); tmp = DEFAULT_PROTOCOL; } p->protocol = tmp; /* Set commands based on protocol version */ if (p->protocol == PROTOCOL_FFDC) { p->command_display = (uint64_t) (COMMANDS_DISPLAY | COMMANDS_DISPLAY_BYTE_FFDC); p->command_shutdown = (uint64_t) (COMMANDS_SHUTDOWN | COMMANDS_DISPLAY_BYTE_FFDC); p->command_display_on = (uint64_t) (COMMANDS_DISPLAY_ON | COMMANDS_DISPLAY_BYTE_FFDC); p->command_clear_alarm = (uint64_t) (COMMANDS_CLEAR_ALARM | COMMANDS_ALARM_BYTE_FFDC); } else if (p->protocol == PROTOCOL_0038) { p->command_display = (uint64_t) (COMMANDS_DISPLAY | COMMANDS_DISPLAY_BYTE_0038); p->command_shutdown = (uint64_t) (COMMANDS_SHUTDOWN | COMMANDS_DISPLAY_BYTE_0038); p->command_display_on = (uint64_t) (COMMANDS_DISPLAY_ON | COMMANDS_DISPLAY_BYTE_0038); p->command_clear_alarm = (uint64_t) (COMMANDS_CLEAR_ALARM | COMMANDS_ALARM_BYTE_0038); } else { /* default - use FFDC */ report(RPT_WARNING, "%s: Protocol version not recognized: %d; using default %d", drvthis->name, p->protocol, PROTOCOL_FFDC); p->command_display = (uint64_t) (COMMANDS_DISPLAY | COMMANDS_DISPLAY_BYTE_FFDC); p->command_shutdown = (uint64_t) (COMMANDS_SHUTDOWN | COMMANDS_DISPLAY_BYTE_FFDC); p->command_display_on = (uint64_t) (COMMANDS_DISPLAY_ON | COMMANDS_DISPLAY_BYTE_FFDC); p->command_clear_alarm = (uint64_t) (COMMANDS_CLEAR_ALARM | COMMANDS_ALARM_BYTE_FFDC); } /* Get size settings */ strncpy(buf, drvthis->config_get_string(drvthis->name, "Size", 0, DEFAULT_SIZE), sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; if ((sscanf(buf, "%dx%d", &graph_width, &graph_height) != 2) || (graph_width <= 0) || (graph_height <= 0) || (graph_height < LCD_DEFAULT_CELL_HEIGHT) || ((graph_width / p->cellwidth) > LCD_MAX_WIDTH) || ((graph_height / p->cellheight) > LCD_MAX_HEIGHT)) { report(RPT_WARNING, "%s: cannot read Size: %s; using default %s", drvthis->name, buf, DEFAULT_SIZE); sscanf(DEFAULT_SIZE, "%dx%d", &graph_width, &graph_height); } /* Convert dimension in pixels to characters. */ p->width = (graph_width / p->cellwidth); p->height = (graph_height / p->cellheight); /* Store this to make working with the frame buffer memory easier. */ p->bytesperline = graph_width; /* * Get the "on exit" setting so we know what to do when we shut the * device down */ tmp = drvthis->config_get_int(drvthis->name, "OnExit", 0, DEFAULT_ON_EXIT); if ((tmp < 0) || (tmp > 3)) { report(RPT_WARNING, "%s: OnExit must be between 0 and 3; using default %d", drvthis->name, DEFAULT_ON_EXIT); tmp = DEFAULT_ON_EXIT; } p->on_exit = tmp; /* Get the "contrast" setting */ tmp = drvthis->config_get_int(drvthis->name, "Contrast", 0, DEFAULT_CONTRAST); if ((tmp < 0) || (tmp > 1000)) { report(RPT_WARNING, "%s: Contrast must be between 0 and 1000; using default %d", drvthis->name, DEFAULT_CONTRAST); tmp = DEFAULT_CONTRAST; } p->contrast = tmp; /* Get the "backlight" setting */ p->backlightOn = drvthis->config_get_bool(drvthis->name, "Backlight", 0, DEFAULT_BACKLIGHT); /* Get the "disc-mode" setting */ p->discMode = drvthis->config_get_bool(drvthis->name, "DiscMode", 0, DEFAULT_DISCMODE); /* * We need a little bit of extra memory in the frame buffer so that * all of the last 7-bit-long packet data will be within the frame * buffer. See imonlcd_flush() for where we take advantage of this. */ tmp = 0; while (((p->bytesperline * p->height) + tmp) % IMONLCD_PACKET_DATA_SIZE) { tmp++; } /* Make sure the frame buffer is there... */ p->framebuf = (unsigned char *)malloc((p->bytesperline * p->height) + tmp); if (p->framebuf == NULL) { report(RPT_ERR, "%s: unable to allocate framebuffer", drvthis->name); return -1; } /* Clear the framebuffer. */ imonlcd_clear(drvthis); /* * This is the extra memory that will fill out the last packet's * data. */ memset(p->framebuf + (p->bytesperline * p->height), 0xFF, tmp); /* Make sure the framebuffer backing store is there... */ p->backingstore = (unsigned char *)malloc(p->bytesperline * p->height); if (p->backingstore == NULL) { report(RPT_ERR, "%s: unable to create framebuffer backing store", drvthis->name); return -1; } memset(p->backingstore, ' ', p->bytesperline * p->height); imonlcd_display_init(drvthis); report(RPT_DEBUG, "%s: init() done", drvthis->name); return 0; } /** * Send the "initialize" commands to the screen. * \param drvthis Pointer to driver structure. */ static void imonlcd_display_init(Driver *drvthis) { PrivateData *p = drvthis->private_data; if (p->backlightOn) send_command_data(p->command_display_on, p); else send_command_data(p->command_shutdown, p); send_command_data(p->command_clear_alarm, p); imonlcd_set_contrast(drvthis, p->contrast); send_command_data(COMMANDS_INIT, p); /* unknown, required init command */ send_command_data(COMMANDS_SET_ICONS, p); /* clear the progress-bars on top and bottom of the display */ send_command_data(COMMANDS_SET_LINES0, p); send_command_data(COMMANDS_SET_LINES1, p); send_command_data(COMMANDS_SET_LINES2, p); } /** * Close the driver (do necessary clean-up). * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void imonlcd_close(Driver *drvthis) { PrivateData *p = drvthis->private_data; time_t tt; struct tm *t; uint64_t data; if (p != NULL) { if (p->imon_fd >= 0) { if (p->on_exit == ON_EXIT_SHOWMSG) { /* * "show message" means "do nothing" - the * message is there already */ report(RPT_INFO, "%s: closing, leaving \"goodbye\" message.", drvthis->name); } else if (p->on_exit == ON_EXIT_BLANKSCREEN) { /* * turning backlight off (confirmed for my * Silverstone LCD) (as "cybrmage" at * mediaportal pointed out, his LCD is an * Antec built-in one and turns completely * off with this command) */ report(RPT_INFO, "%s: closing, turning backlight off.", drvthis->name); send_command_data(p->command_shutdown, p); send_command_data(p->command_clear_alarm, p); } else { /* * by default, show the big clock. We need to * set it to the current time, then it just * keeps counting automatically. */ report(RPT_INFO, "%s: closing, showing clock.", drvthis->name); tt = time(NULL); t = localtime(&tt); data = p->command_display; data += ((uint64_t) t->tm_sec << 48); data += ((uint64_t) t->tm_min << 40); data += ((uint64_t) t->tm_hour << 32); data += ((uint64_t) t->tm_mday << 24); data += ((uint64_t) t->tm_mon << 16); data += (((uint64_t) t->tm_year) << 8); data += 0x80; send_command_data(data, p); send_command_data(p->command_clear_alarm, p); } close(p->imon_fd); } if (p->framebuf != NULL) free(p->framebuf); p->framebuf = NULL; free(p); } drvthis->store_private_ptr(drvthis, NULL); } /** * Provide some information about this driver. * \param drvthis Pointer to driver structure. * \return Constant string with information. */ MODULE_EXPORT const char * imonlcd_get_info(Driver *drvthis) { PrivateData *p = drvthis->private_data; strcpy(p->info, "SoundGraph iMON LCD driver v0.6 : 15c2:ffdc and 15c2:0038 "); return p->info; } /** * Clear the screen. * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void imonlcd_clear(Driver *drvthis) { PrivateData *p = drvthis->private_data; memset(p->framebuf, 0x00, p->bytesperline * p->height); } /** * Flush data on screen to the LCD. * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void imonlcd_flush(Driver *drvthis) { PrivateData *p = drvthis->private_data; unsigned char msb; int offset = 0; /* * The display only provides for a complete screen refresh. If * nothing has changed, don't refresh. */ if (memcmp(p->backingstore, p->framebuf, (p->bytesperline * p->height)) == 0) return; for (msb = 0x20; msb < 0x3c; msb++) { /* Copy the packet data from the frame buffer. */ memcpy(p->tx_buf, p->framebuf + offset, IMONLCD_PACKET_DATA_SIZE); /* Add the memory register byte to the packet data. */ p->tx_buf[IMONLCD_PACKET_DATA_SIZE] = msb; send_packet(p); offset += IMONLCD_PACKET_DATA_SIZE; } /* Update the backing store. */ memcpy(p->backingstore, p->framebuf, (p->bytesperline * p->height)); } /** * 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 imonlcd_string(Driver *drvthis, int x, int y, const char string[]) { int i; for (i = 0; string[i] != '\0'; i++) imonlcd_chr(drvthis, x + i, y, string[i]); } /** * 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 ch Character that gets written. (ISO 8859-1 + special characters) */ MODULE_EXPORT void imonlcd_chr(Driver *drvthis, int x, int y, char ch) { PrivateData *p = drvthis->private_data; imon_font *defn; int col; if ((x < 1) || (y < 1) || (x > p->width) || (y > p->height)) return; x--; y--; /* Convert from characters to pixels. */ x *= p->cellwidth; y *= p->bytesperline; defn = &font[(unsigned char) ch]; /* Copy character from font into frame buffer */ for (col = 0; col < p->cellwidth; col++) { p->framebuf[x + y] = defn->pixels[col]; x++; } } /** * Draw a vertical bar bottom-up. * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column) of the starting point. * \param y Vertical character position (row) of the starting point. * \param len Number of characters that the bar is high at 100% * \param promille Current height level of the bar in promille. * \param options Options (currently unused). */ MODULE_EXPORT void imonlcd_vbar(Driver *drvthis, int x, int y, int len, int promille, int options) { PrivateData *p = drvthis->private_data; /* * use the imonlcd_font narrow vbar characters and let lcd_lib do the * rest */ lib_vbar_static(drvthis, x, y, len, promille, options, p->cellheight, IMONLCD_FONT_START_VBAR_NARROW-1); } /** * Draw a horizontal bar to the right. * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column) of the starting point. * \param y Vertical character position (row) of the starting point. * \param len Number of characters that the bar is long at 100% * \param promille Current length level of the bar in promille (i.e. from 0 to 1000). * \param options Options (currently unused). */ MODULE_EXPORT void imonlcd_hbar(Driver *drvthis, int x, int y, int len, int promille, int options) { PrivateData *p = drvthis->private_data; /* * I don't like the reliance on SEAMLESS_HBARS, so the * lib_hbar_static code is replicated here. (./configure * --enable-seamless hbars would be required for the hbar display to * look right if lib_hbar_static() were used.) */ int total_pixels = ((long)2 * len * p->cellwidth + 1) * promille / 2000; int pos; for (pos = 0; pos < len; pos++) { int pixels = total_pixels - p->cellwidth * pos; if (pixels >= p->cellwidth) { /* write a "full" block to the screen... */ imonlcd_chr(drvthis, x + pos, y, p->cellwidth + IMONLCD_FONT_START_HBAR_NARROW - 1); } else if (pixels > 0) { /* write a partial block... */ imonlcd_chr(drvthis, x + pos, y, pixels + IMONLCD_FONT_START_HBAR_NARROW - 1); break; } else { ; /* write nothing (not even a space) */ } } } /** * 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 imonlcd_icon(Driver *drvthis, int x, int y, int icon) { char ch1 = '?'; char ch2 = '\0'; switch (icon) { case ICON_BLOCK_FILLED: ch1 = IMONLCD_FONT_FULL_BLOCK; break; case ICON_HEART_OPEN: ch1 = IMONLCD_FONT_SMALL_HEART; break; case ICON_HEART_FILLED: ch1 = IMONLCD_FONT_FULL_HEART; break; case ICON_ARROW_UP: ch1 = IMONLCD_FONT_UP_ARROW; break; case ICON_ARROW_DOWN: ch1 = IMONLCD_FONT_DOWN_ARROW; break; case ICON_ARROW_LEFT: ch1 = IMONLCD_FONT_LEFT_ARROW; break; case ICON_ARROW_RIGHT: ch1 = IMONLCD_FONT_RIGHT_ARROW; break; case ICON_STOP: ch1 = IMONLCD_FONT_STOP; ch2 = ' '; break; case ICON_PAUSE: ch1 = IMONLCD_FONT_PAUSE; ch2 = ' '; break; case ICON_PLAY: ch1 = IMONLCD_FONT_PLAY; ch2 = ' '; break; case ICON_PLAYR: ch1 = IMONLCD_FONT_REVERSE; ch2 = ' '; break; case ICON_FF: ch1 = IMONLCD_FONT_PLAY; ch2 = IMONLCD_FONT_PLAY; break; case ICON_FR: ch1 = IMONLCD_FONT_REVERSE; ch2 = IMONLCD_FONT_REVERSE; break; case ICON_NEXT: ch1 = IMONLCD_FONT_PLAY; ch2 = IMONLCD_FONT_FWD; break; case ICON_PREV: ch1 = IMONLCD_FONT_RWD; ch2 = IMONLCD_FONT_REVERSE; break; case ICON_REC: ch1 = IMONLCD_FONT_RECORD; ch2 = ' '; break; default: /* let the server core do the rest */ return -1; } imonlcd_chr(drvthis, x, y, ch1); if (ch2 != '\0') imonlcd_chr(drvthis, x + 1, y, ch2); return 0; } /** * Draws a "big" number at the specified x-coordinate. * * Normally, the number that is displayed is "meant" to be 3x4 characters, but * because we have a bit more flexibility, I've drawn the numbers as just being * 12x16 pixels. That means that while the client will pass x-values between 0 * and 16, we need to scale it and make sure the numbers remain centered. * * \param drvthis A pointer to the Driver structure. * \param x The x-coordinate to display the character at. * \param num The number to display ("10" is the colon). */ MODULE_EXPORT void imonlcd_num(Driver *drvthis, int x, int num) { PrivateData *p = drvthis->private_data; /* * This isn't that great, it makes assumptions about the coordinates * the client passes to us. However, it works for MythTV... and looks * pretty cool, too. */ if (num < 10) x = 12 + (int)(((x - 1) * p->cellwidth) * 0.75); else x = 12 + (int)(((x - 1) * p->cellwidth) * 0.72); draw_bigchar(bigfont, (num >= 10 ? ':' : (num + '0')), x, 0, p); } /** * Sets the "output state" for the device. We use this to control the icons * around the outside the display. The bits in \c state correspond to the * icons as follows: * * \verbatim * bit 0 : disc icon (0=off, 1='spin') , if Toprow==4, use CD-animation, * else use "HDD-recording-animation" * bit 1,2,3 : top row (0=none, 1=music, 2=movie, 3=photo, 4=CD/DVD, 5=TV, 6=Web, * 7=News/Weather) * bit 4,5 : 'speaker' icons (0=off, 1=LR, 2=5.1ch, 3=7.1ch) * bit 6 : S/PDIF icon * bit 7 : 'SRC' * bit 8 : 'FIT' * bit 9 : 'TV' * bit 10 : 'HDTV' * bit 11 : 'SRC1' * bit 12 : 'SRC2' * bit 13,14,15: bottom-right icons (0=off, 1=MP3, 2=OGG, 3=WMA, 4=WAV) * bit 16,17,18: bottom-middle icons (0=off, 1=MPG, 2=AC3, 3=DTS, 4=WMA) * bit 19,20,21: bottom-left icons (0=off, 1=MPG, 2=DIVX, 3=XVID, 4=WMV) * bit 22 : 'VOL' (volume) * bit 23 : 'TIME' * bit 24 : 'ALARM' * bit 25 : 'REC' (recording) * bit 26 : 'REP' (repeat) * bit 27 : 'SFL' (shuffle) * bit 28 : Abuse this for progress bars (if set to 1), lower bits represent * the length (6 bits each: P|6xTP|6xTL|6xBL|6xBP with P = bit 28, * TP=Top Progress, TL = Top Line, BL = Bottom Line, BP = Bottom Progress). * If bit 28 is set to 1, lower bits are interpreted as * lengths; otherwise setting the symbols as usual. * 0 <= length <= 32, bars extend from left to right. * length > 32, bars extend from right to left, length is counted * from 32 up (i.e. 35 means a length of 3). * * Remember: There are two kinds of calls! * With bit 28 set to 1: Set all bars (leaving the symbols as is), * with bit 28 set to 0: Set the symbols (leaving the bars as is). * Beware: TODO: May become a race condition, if both calls are executed * before the display gets updated. Keep this in mind in your * client-code. * bit 29 : 'disc-in icon' - half ellipsoid under the disc symbols (0=off, 1=on) * \endverbatim * * \param drvthis Pointer to driver structure. * \param state This symbols or bars to display. */ MODULE_EXPORT void imonlcd_output(Driver *drvthis, int state) { PrivateData *p = drvthis->private_data; uint64_t icon = 0x0; /* bit 28 : Abuse this for progress bars. See above for usage. */ if ( state & IMON_OUTPUT_PBARS_MASK || state == 0 ) { if (state != p->last_output_bar_state) { /* extract the bar-values for each bar separately */ int topProgress = (state & 63); int topLine = (state & (63 << 6)) >> 6; int botProgress = (state & (63 << 12)) >> 12; int botLine = (state & (63 << 18)) >> 18; botProgress = botProgress > 32 ? -(botProgress - 32) : botProgress; topProgress = topProgress > 32 ? -(topProgress - 32) : topProgress; botLine = botLine > 32 ? -(botLine - 32) : botLine; topLine = topLine > 32 ? -(topLine - 32) : topLine; setLineLength(topLine, botLine, topProgress, botProgress, p); p->last_output_bar_state = state; } /* * If the current output state is 'all on' (-1) or 'all off' (0), update * the icons with that state. Otherwise, update the icons with the last * icon output state (this is only used to keep the CD spinning). */ if (state != 0 && state != -1 ) { state = p->last_output_state; } } /* Don't update if no icons need to be changed. */ if (state == p->last_output_state && !(state != -1 && (state & IMON_OUTPUT_CD_MASK))) { return; } p->last_output_state = state; if (state == -1) { /* the value for "on" in the lcdproc-protocol */ icon = (uint64_t) IMON_ICON_ALL; send_command_data(COMMANDS_SET_ICONS | icon, p); return; } /* bit 0 : disc icon (0=off, 1='spin') */ if (state & IMON_OUTPUT_CD_MASK) { /* Each icon bit represents a section of the cd, * starting at the top as msb, and going counter-clockwise. * Start with the top-right & bottom-left on. */ unsigned char tmp_cd_bitmap = (0x01 | (0x01 << 4)); if (p->last_cd_state >= 3 ) p->last_cd_state = 0; else p->last_cd_state++; /* Shift the bits to the left, and the cd moves clock-wise. */ tmp_cd_bitmap <<= p->last_cd_state; if (p->discMode == 1) tmp_cd_bitmap = ~tmp_cd_bitmap; icon |= ((uint64_t)tmp_cd_bitmap) << 40; } /* * bit 1,2,3 : top row * (0=off, 1=MUSIC, 2=MOVIE, 3=PHOTO, 4=CD/DVD, 5=TV, 6=WEB, 7=NEWS/WEATHER) */ if (state & IMON_OUTPUT_TOPROW_MASK) { switch (((state & IMON_OUTPUT_TOPROW_MASK) >> 1)) { case 1: icon |= IMON_ICON_MUSIC; break; case 2: icon |= IMON_ICON_MOVIE; break; case 3: icon |= IMON_ICON_PHOTO; break; case 4: icon |= IMON_ICON_CD_DVD; break; case 5: icon |= IMON_ICON_TV; break; case 6: icon |= IMON_ICON_WEBCAST; break; case 7: icon |= IMON_ICON_NEWS; break; default: break; } } /* bit 4,5 : 'speaker' icons (0=off, 1=L+R, 2=5.1ch, 3=7.1ch) */ if (state & IMON_OUTPUT_SPEAKER_MASK) { switch (((state & IMON_OUTPUT_SPEAKER_MASK) >> 4)) { case 3: icon |= (IMON_SPKR_SL | IMON_SPKR_SR); case 2: icon |= (IMON_SPKR_FC | IMON_SPKR_RL | IMON_SPKR_RR); case 1: icon |= (IMON_SPKR_FL | IMON_SPKR_FR); default: break; } } /* bit 6 : S/PDIF icon */ if (state & IMON_OUTPUT_SPDIF_MASK) icon |= IMON_SPKR_SPDIF; /* bit 7 : 'SRC' */ if (state & IMON_OUTPUT_SRC_MASK) icon |= IMON_ICON_SRC; /* bit 8 : 'FIT' */ if (state & IMON_OUTPUT_FIT_MASK) icon |= IMON_ICON_FIT; /* bit 9 : 'TV' */ if (state & IMON_OUTPUT_TV_MASK) icon |= IMON_ICON_TV_2; /* bit 10 : 'HDTV' */ if (state & IMON_OUTPUT_HDTV_MASK) icon |= IMON_ICON_HDTV; /* bit 11 : 'SRC1' */ if (state & IMON_OUTPUT_SCR1_MASK) icon |= IMON_ICON_SCR1; /* bit 12 : 'SRC2' */ if (state & IMON_OUTPUT_SCR2_MASK) icon |= IMON_ICON_SCR2; /* * bit 13,14,15: bottom-right icons * (0=off, 1=MP3, 2=OGG, 3=WMA, 4=WAV) */ if (state & IMON_OUTPUT_BRICONS_MASK) { switch (((state & IMON_OUTPUT_BRICONS_MASK) >> 13)) { case 1: icon |= IMON_ICON_MP3; break; case 2: icon |= IMON_ICON_OGG; break; case 3: icon |= IMON_ICON_WMA2; break; case 4: icon |= IMON_ICON_WAV; break; default: break; } } /* * bit 16,17,18: bottom-middle icons * (0=off, 1=MPG, 2=AC3, 3=DTS, 4=WMA) */ if (state & IMON_OUTPUT_BMICONS_MASK) { switch (((state & IMON_OUTPUT_BMICONS_MASK) >> 16)) { case 1: icon |= IMON_ICON_MPG2; break; case 2: icon |= IMON_ICON_AC3; break; case 3: icon |= IMON_ICON_DTS; break; case 4: icon |= IMON_ICON_WMA; break; default: break; } } /* * bit 19,20,21: bottom-left icons * (0=off, 1=MPG, 2=DIVX, 3=XVID, 4=WMV) */ if (state & IMON_OUTPUT_BLICONS_MASK) { switch (((state & IMON_OUTPUT_BLICONS_MASK) >> 19)) { case 1: icon |= IMON_ICON_MPG; break; case 2: icon |= IMON_ICON_DIVX; break; case 3: icon |= IMON_ICON_XVID; break; case 4: icon |= IMON_ICON_WMV; break; default: break; } } /* bit 22 : 'VOL' (volume) */ if (state & IMON_OUTPUT_VOL_MASK) icon |= IMON_ICON_VOL; /* bit 23 : 'TIME' */ if (state & IMON_OUTPUT_TIME_MASK) icon |= IMON_ICON_TIME; /* bit 24 : 'ALARM' */ if (state & IMON_OUTPUT_ALARM_MASK) icon |= IMON_ICON_ALARM; /* bit 25 : 'REC' (recording) */ if (state & IMON_OUTPUT_REC_MASK) icon |= IMON_ICON_REC; /* bit 26 : 'REP' (repeat) */ if (state & IMON_OUTPUT_REP_MASK) icon |= IMON_ICON_REP; /* bit 27 : 'SFL' (shuffle) */ if (state & IMON_OUTPUT_SFL_MASK) icon |= IMON_ICON_SFL; /* bit 29 : 'disc-in' */ if (state & IMON_OUTPUT_DISK_IN_MASK) icon |= IMON_ICON_DISK_IN; send_command_data(COMMANDS_SET_ICONS | icon, p); } /** * Return the display width in characters. * \param drvthis Pointer to driver structure. * \return Number of characters the display is wide. */ MODULE_EXPORT int imonlcd_width(Driver *drvthis) { PrivateData *p = 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 imonlcd_height(Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->height; } /** * Return the width of a character in pixels. * \param drvthis Pointer to driver structure. * \return Number of pixel columns a character cell is wide. */ MODULE_EXPORT int imonlcd_cellwidth(Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->cellwidth; } /** * Return the height of a character in pixels. * \param drvthis Pointer to driver structure. * \return Number of pixel lines a character cell is high. */ MODULE_EXPORT int imonlcd_cellheight(Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->cellheight; } /** * Sends a command to the screen. The kernel module expects data to be * sent in 8 byte chunks, so for simplicity, we allow you to define * the data as a 64-bit integer. * However, we have to reverse the bytes to the order the display requires. * * \param value The data to send. Must be in a format that is recognized by * the device. The kernel module doesn't actually do validation. * \param p The private data containing the file descriptor to write to. */ static void send_command_data(uint64_t value, PrivateData *p) { int i; /* Fill the send buffer. */ for (i = 0; i < sizeof(p->tx_buf); i++) { p->tx_buf[i] = (unsigned char)((value >> (i * 8)) & 0xFF); } send_packet(p); } /** * Sends data to the screen. * * \param p The private data structure containing a tx_buf with the data to send. */ static void send_packet(PrivateData *p) { int err; err = write(p->imon_fd, p->tx_buf, sizeof(p->tx_buf)); if (err <= 0) printf("%s: error writing to file descriptor: %d", "imon", err); } /** * Sets the contrast of the display. * * \param drvthis Pointer to driver structure. * \param promille The value the contrast is set to in promille (0 = lowest * contrast; 1000 = highest contrast). * \return 0 on failure, >0 on success. */ MODULE_EXPORT void imonlcd_set_contrast(Driver *drvthis, int promille) { PrivateData *p = drvthis->private_data; if (promille < 0) { promille = 0; } else if (promille > 1000) { promille = 1000; } p->contrast = promille; /* * Send contrast normalized to the hardware-understandable-value (0 * to 40). 0 is the lowest and 40 is the highest. The actual * perceived contrast varies depending on the type of display. */ send_command_data(COMMANDS_LOW_CONTRAST + (uint64_t) (p->contrast / 25), p); } /** * Gets the current contrast of the display. * * \param drvthis Pointer to driver structure. * \return The current contrast in promille (0 = lowest contrast; * 1000 = highest contrast). */ MODULE_EXPORT int imonlcd_get_contrast(Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->contrast; } /** * Sets the backlight state of the display. * * \param drvthis Pointer to driver structure. * \param on The backlight state boolean-like: 0 = off; >0 = on. */ MODULE_EXPORT void imonlcd_backlight(Driver *drvthis, int on) { PrivateData *p = drvthis->private_data; /* To prevent superfluous (and erroneous) communication */ if (p->backlightOn == on) return; else p->backlightOn = on; if (on) send_command_data(p->command_display_on, p); else send_command_data(p->command_shutdown, p); } /** * Draws a "big" character - that is, one that's twice as big as a * normal character - at the specified position on the screen. * \param font font definition * \param ch the character to display * \param x horizontal position * \param y vertical position * \param p pointer to the PrivateData structure */ static void draw_bigchar(imon_bigfont *font, int ch, int x, int y, PrivateData *p) { imon_bigfont *defn = font; int i; int colBorder; while (defn->ch != ch && defn->ch != '\0') { defn++; } /* * correction for the number flashing with the colon running * "lcdproc K" */ colBorder = 12; if (ch == ':') colBorder = 6; for (i = 0; i < colBorder; i++) { p->framebuf[x + i + (y * colBorder)] = (defn->pixels[i] & 0xFF00) >> 8; } for (i = 0; i < colBorder; i++) { p->framebuf[x + i + (y * colBorder) + p->bytesperline] = (defn->pixels[i] & 0x00FF); } } /** * Sets the length of the built-in progress-bars and lines. * Values from -32 to 32 are allowed. Positive values indicate that bars extend * from left to right, negative values indicate that the run from right to left. * Conventient method to simplify setting the bars with "human understandable * values". * * \see setBuiltinProgressBars, lengthToPixels * * \param topLine Length of the top line (-32 to 32) * \param botLine Length of the bottom line (-32 to 32) * \param topProgress Length of the top progress bar (-32 to 32) * \param botProgress Length of the bottom progress bar (-32 to 32) * \param p The private data structure containing the file descriptor to write to. */ static void setLineLength(int topLine, int botLine, int topProgress, int botProgress, PrivateData *p) { setBuiltinProgressBars(lengthToPixels(topLine), lengthToPixels(botLine), lengthToPixels(topProgress), lengthToPixels(botProgress), p ); } /** * Sets the length of the built-in progress-bars and lines. * Values from -32 to 32 are allowed. Positive values indicate that bars extend * from left to right, negative values indicate that the run from right to left. * * \param topLine Pitmap of the top line * \param botLine Pitmap of the bottom line * \param topProgress Pitmap of the top progress bar * \param botProgress Pitmap of the bottom progress bar * \param p The private data structure containing the file descriptor to write to. */ static void setBuiltinProgressBars(int topLine, int botLine, int topProgress, int botProgress, PrivateData *p) { /* Least sig. bit is on the right */ uint64_t data; /* send bytes 1-4 of topLine and 1-3 of topProgress */ data = (uint64_t) topLine & 0x00000000FFFFFFFFLL; data |= (((uint64_t) topProgress) << 8 * 4) & 0x00FFFFFF00000000LL; send_command_data(COMMANDS_SET_LINES0 | data, p); /* send byte 4 of topProgress, bytes 1-4 of botProgress and 1-2 of botLine */ data = (((uint64_t) topProgress) >> 8 * 3) & 0x00000000000000FFLL; data |= (((uint64_t) botProgress) << 8) & 0x000000FFFFFFFF00LL; data |= (((uint64_t) botLine) << 8 * 5) & 0x00FFFF0000000000LL; send_command_data(COMMANDS_SET_LINES1 | data, p); /* send remaining bytes 3-4 of botLine */ data = ((uint64_t) botLine) >> 8 * 2; send_command_data(COMMANDS_SET_LINES2 | data, p); } /** * Maps values to corresponding pixmaps for the built-in progress bars. * Values from -32 to 32 are allowed. Positive values indicate that bars extend * from left to right, negative values indicate that they run from right to left. * * \param length The length of the bar. * \return The pixmap that represents the given length. */ static int lengthToPixels(int length) { int pixLen[] = { 0x00, 0x00000080, 0x000000c0, 0x000000e0, 0x000000f0, 0x000000f8, 0x000000fc, 0x000000fe, 0x000000ff, 0x000080ff, 0x0000c0ff, 0x0000e0ff, 0x0000f0ff, 0x0000f8ff, 0x0000fcff, 0x0000feff, 0x0000ffff, 0x0080ffff, 0x00c0ffff, 0x00e0ffff, 0x00f0ffff, 0x00f8ffff, 0x00fcffff, 0x00feffff, 0x00ffffff, 0x80ffffff, 0xc0ffffff, 0xe0ffffff, 0xf0ffffff, 0xf8ffffff, 0xfcffffff, 0xfeffffff, 0xffffffff }; if (abs(length) > 32) return (0); if (length >= 0) return pixLen[length]; else return (pixLen[32 + length] ^ 0xffffffff); } /* EOF */