/* * HD44780 LCD Controller * * Copyright (c) 2009 Filip Navara * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "sysbus.h" #include "console.h" #include "pixel_ops.h" /* pin mapping: 8x Data Register Select (input only) Read/Write (input only) Enable (input only) Backlight (input only) */ #define D(x) x #define PIN_RS 8 #define PIN_RW 9 #define PIN_E 10 #define PIN_BL 11 #define LCD_CLR 1 /* DB0: clear display */ #define LCD_HOME 2 /* DB1: return to home position */ #define LCD_ENTRY_MODE 4 /* DB2: set entry mode */ #define LCD_ENTRY_INC 2 /* DB1: increment */ #define LCD_ENTRY_SHIFT 1 /* DB2: shift */ #define LCD_ON_CTRL 8 /* DB3: turn lcd/cursor on */ #define LCD_ON_DISPLAY 4 /* DB2: turn display on */ #define LCD_ON_CURSOR 2 /* DB1: turn cursor on */ #define LCD_ON_BLINK 1 /* DB0: blinking cursor */ #define LCD_MOVE 16 /* DB4: move cursor/display */ #define LCD_MOVE_DISP 8 /* DB3: move display (0-> move cursor) */ #define LCD_MOVE_RIGHT 4 /* DB2: move right (0-> left) */ #define LCD_FUNCTION 32 /* DB5: function set */ #define LCD_FUNCTION_8BIT 16 /* DB4: set 8BIT mode (0->4BIT mode) */ #define LCD_FUNCTION_2LINES 8 /* DB3: two lines (0->one line) */ #define LCD_FUNCTION_10DOTS 4 /* DB2: 5x10 font (0->5x7 font) */ #define LCD_CGRAM 64 /* DB6: set CG RAM address */ #define LCD_DDRAM 128 /* DB7: set DD RAM address */ #define LCD_BUSY 64 /* DB7: LCD is busy */ #define WIDTH 20 #define HEIGHT 4 #define CZOOM 3 uint8_t font5x7[][7]={ /* */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* ! */ {0x04,0x04,0x04,0x04,0x00,0x00,0x04}, /* " */ {0x0A,0x0A,0x0A,0x00,0x00,0x00,0x00}, /* # */ {0x0A,0x0A,0x1F,0x0A,0x1F,0x0A,0x0A}, /* $ */ {0x04,0x0F,0x14,0x0E,0x05,0x1E,0x04}, /* % */ {0x18,0x19,0x02,0x04,0x08,0x13,0x03}, /* & */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* ' */ {0x0C,0x04,0x08,0x00,0x00,0x00,0x00}, /* ( */ {0x02,0x04,0x08,0x08,0x08,0x04,0x02}, /* ) */ {0x08,0x04,0x02,0x02,0x02,0x04,0x08}, /* * */ {0x00,0x04,0x15,0x0E,0x15,0x04,0x00}, /* + */ {0x00,0x04,0x04,0x1F,0x04,0x04,0x00}, /* , */ {0x00,0x00,0x00,0x00,0x0C,0x04,0x08}, /* - */ {0x00,0x00,0x00,0x1F,0x00,0x00,0x00}, /* . */ {0x00,0x00,0x00,0x00,0x00,0x0C,0x0C}, /* / */ {0x00,0x01,0x02,0x04,0x08,0x10,0x00}, /* 0 */ {0x0E,0x11,0x13,0x15,0x19,0x11,0x0E}, /* 1 */ {0x04,0x0C,0x04,0x04,0x04,0x04,0x0E}, /* 2 */ {0x0E,0x11,0x01,0x02,0x04,0x08,0x1F}, /* 3 */ {0x1F,0x02,0x04,0x02,0x01,0x11,0x0E}, /* 4 */ {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02}, /* 5 */ {0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E}, /* 6 */ {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E}, /* 7 */ {0x1F,0x01,0x02,0x04,0x08,0x08,0x08}, /* 8 */ {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E}, /* 9 */ {0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C}, /* : */ {0x00,0x0C,0x0C,0x00,0x0C,0x0C,0x00}, /* ; */ {0x00,0x0C,0x0C,0x00,0x0C,0x04,0x08}, /* < */ {0x02,0x04,0x08,0x10,0x08,0x04,0x02}, /* = */ {0x00,0x00,0x1F,0x00,0x1F,0x00,0x00}, /* > */ {0x08,0x04,0x02,0x01,0x02,0x04,0x08}, /* ? */ {0x0E,0x11,0x01,0x02,0x04,0x00,0x04}, /* @ */ {0x0E,0x11,0x01,0x0D,0x15,0x15,0x0E}, /* A */ {0x0E,0x11,0x11,0x11,0x1F,0x11,0x11}, /* B */ {0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E}, /* C */ {0x0E,0x11,0x10,0x10,0x10,0x11,0x0E}, /* D */ {0x1C,0x12,0x11,0x11,0x11,0x12,0x1C}, /* E */ {0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F}, /* F */ {0x1F,0x10,0x10,0x1E,0x10,0x10,0x10}, /* G */ {0x0E,0x11,0x10,0x17,0x11,0x11,0x0F}, /* H */ {0x11,0x11,0x11,0x1F,0x11,0x11,0x11}, /* I */ {0x0E,0x04,0x04,0x04,0x04,0x04,0x0E}, /* J */ {0x07,0x02,0x02,0x02,0x02,0x12,0x0C}, /* K */ {0x11,0x12,0x14,0x18,0x14,0x12,0x11}, /* L */ {0x10,0x10,0x10,0x10,0x10,0x10,0x1F}, /* M */ {0x11,0x1B,0x15,0x15,0x11,0x11,0x11}, /* N */ {0x11,0x11,0x19,0x15,0x13,0x11,0x11}, /* O */ {0x0E,0x11,0x11,0x11,0x11,0x11,0x0E}, /* P */ {0x1E,0x11,0x11,0x1E,0x10,0x10,0x10}, /* Q */ {0x0E,0x11,0x11,0x11,0x15,0x12,0x0D}, /* R */ {0x1E,0x11,0x11,0x1E,0x14,0x12,0x11}, /* S */ {0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E}, /* T */ {0x1F,0x04,0x04,0x04,0x04,0x04,0x04}, /* U */ {0x11,0x11,0x11,0x11,0x11,0x11,0x0E}, /* V */ {0x11,0x11,0x11,0x11,0x11,0x0A,0x04}, /* W */ {0x11,0x11,0x11,0x15,0x15,0x15,0x0A}, /* X */ {0x11,0x11,0x0A,0x04,0x0A,0x11,0x11}, /* Y */ {0x11,0x11,0x11,0x0A,0x04,0x04,0x04}, /* Z */ {0x1F,0x01,0x02,0x04,0x08,0x10,0x1F}, /* [ */ {0x0E,0x08,0x08,0x08,0x08,0x08,0x0E}, /* \ */ {0x00,0x10,0x08,0x04,0x02,0x01,0x00}, /* ] */ {0x0E,0x02,0x02,0x02,0x02,0x02,0x0E}, /* ^ */ {0x04,0x0A,0x11,0x00,0x00,0x00,0x00}, /* _ */ {0x00,0x00,0x00,0x00,0x00,0x00,0x1F}, /* ` */ {0x08,0x04,0x02,0x00,0x00,0x00,0x00}, /* a */ {0x00,0x00,0x0E,0x01,0x0F,0x11,0x0F}, /* b */ {0x10,0x10,0x10,0x16,0x19,0x11,0x1E}, /* c */ {0x00,0x00,0x0E,0x10,0x10,0x11,0x0E}, /* d */ {0x01,0x01,0x01,0x0D,0x13,0x11,0x0F}, /* e */ {0x00,0x00,0x0E,0x11,0x1F,0x10,0x0E}, /* f */ {0x06,0x09,0x08,0x1C,0x08,0x08,0x08}, /* g */ {0x00,0x0F,0x11,0x11,0x0F,0x01,0x0E}, /* h */ {0x10,0x10,0x16,0x19,0x11,0x11,0x11}, /* i */ {0x00,0x04,0x00,0x04,0x04,0x04,0x04}, /* j */ {0x02,0x00,0x06,0x02,0x02,0x12,0x0C}, /* k */ {0x10,0x10,0x12,0x14,0x18,0x14,0x12}, /* l */ {0x04,0x04,0x04,0x04,0x04,0x04,0x0F}, /* m */ {0x00,0x00,0x1A,0x15,0x15,0x11,0x11}, /* n */ {0x00,0x00,0x16,0x19,0x11,0x11,0x11}, /* o */ {0x00,0x00,0x0E,0x11,0x11,0x11,0x0E}, /* p */ {0x00,0x00,0x1E,0x11,0x1E,0x10,0x10}, /* q */ {0x00,0x00,0x0D,0x13,0x0F,0x01,0x01}, /* r */ {0x00,0x00,0x16,0x19,0x10,0x10,0x10}, /* s */ {0x00,0x00,0x0E,0x10,0x0E,0x01,0x1E}, /* t */ {0x08,0x08,0x1C,0x08,0x08,0x09,0x06}, /* u */ {0x00,0x00,0x11,0x11,0x11,0x13,0x0D}, /* v */ {0x00,0x00,0x11,0x11,0x11,0x0A,0x04}, /* w */ {0x00,0x00,0x11,0x11,0x15,0x15,0x0A}, /* x */ {0x00,0x00,0x11,0x0A,0x04,0x0A,0x11}, /* y */ {0x00,0x00,0x11,0x11,0x0F,0x01,0x0E}, /* z */ {0x00,0x00,0x1F,0x02,0x04,0x08,0x1F}, /* { */ {0x02,0x04,0x04,0x08,0x04,0x04,0x02}, /* | */ {0x04,0x04,0x04,0x04,0x04,0x04,0x04}, /* } */ {0x08,0x04,0x04,0x02,0x04,0x04,0x08}, }; typedef struct LCDState { SysBusDevice busdev; qemu_irq out[8]; DisplayState *ds; uint8_t input; uint32_t control : 1; uint32_t rw : 1; uint32_t backlight : 1; uint8_t mode8bit : 1; uint8_t write_low : 1; uint8_t ac; /* address counter */ uint8_t dispcol; /* first visible column (display shift!) */ uint8_t id : 1; /* cursor move increase(1)/decrease(0) */ uint8_t sh : 1; /* shift display(1) */ uint8_t ddram : 1; /* access ddram(1)/cgram(0) */ uint8_t display : 1; uint8_t cursor : 1; uint8_t blink : 1; uint8_t two_lines : 1; uint8_t font5x10 : 1; uint8_t need_update : 1; char data[20 * 4]; } LCDState; static void draw_char(DisplayState *ds, int x, int y, char ch, uint32_t color, uint32_t backcolor) { uint8_t *d; uint8_t cdata; int i, bpp, line; bpp = (ds_get_bits_per_pixel(ds) + 7) >> 3; for (line = 0; line < 7 * CZOOM; line++) { d = ds_get_data(ds) + ds_get_linesize(ds) * y + bpp * x; if (ch >= ' ' && (ch - ' ') < sizeof(font5x7)/sizeof(font5x7[0])) cdata = font5x7[(int)(ch - ' ')][line / CZOOM]; else cdata = font5x7[0][line / CZOOM]; switch(bpp) { case 1: d += 5 * CZOOM; for (i = 0; i < 5 * CZOOM; i++) { *((uint8_t *)d) = (cdata & (1 << (i / CZOOM))) ? color : backcolor; d--; } break; case 2: d += 10 * CZOOM; for (i = 0; i < 5 * CZOOM; i++) { *((uint16_t *)d) = (cdata & (1 << (i / CZOOM))) ? color : backcolor; d -= 2; } break; case 4: d += 20 * CZOOM; for (i = 0; i < 5 * CZOOM; i++) { *((uint32_t *)d) = (cdata & (1 << (i / CZOOM))) ? color : backcolor; d -= 4; } break; } y++; } } static void hd44780_enable(LCDState *s) { if (s->control) { //D(printf("CONTROL: %x\n", s->input)); if (s->input & LCD_DDRAM) { int ddram_addr = s->input & ~LCD_DDRAM; if (ddram_addr >= 0 && ddram_addr < WIDTH) s->ac = ddram_addr; else if (ddram_addr >= 64 && ddram_addr < 64 + WIDTH) s->ac = (ddram_addr - 64) + WIDTH; else if (ddram_addr >= 20 && ddram_addr < 20 + WIDTH) s->ac = (ddram_addr - 20) + WIDTH * 2; else if (ddram_addr >= 84 && ddram_addr < 84 + WIDTH) s->ac = (ddram_addr - 84) + WIDTH * 3; s->ddram = 1; } else if (s->input & LCD_CGRAM) { s->ac = s->input & ~LCD_CGRAM; s->ddram = 0; } else if (s->input & LCD_FUNCTION) { s->mode8bit = !!(s->input & LCD_FUNCTION_8BIT); s->two_lines = !!(s->input & LCD_FUNCTION_2LINES); s->font5x10 = !!(s->input & LCD_FUNCTION_10DOTS); } else if (s->input & LCD_MOVE) { if (s->input & LCD_MOVE_DISP) { if (s->input & LCD_MOVE_RIGHT) { s->dispcol--; } else { s->dispcol++; } s->dispcol %= sizeof(s->data); } else { // ... } } else if (s->input & LCD_ON_CTRL) { s->display = !!(s->input & LCD_ON_DISPLAY); s->cursor = !!(s->input & LCD_ON_CURSOR); s->blink = !!(s->input & LCD_ON_BLINK); } else if (s->input & LCD_ENTRY_MODE) { s->id = !!(s->input & LCD_ENTRY_INC); s->sh = !!(s->input & LCD_ENTRY_SHIFT); } else if (s->input & LCD_HOME) { s->ac = 0; s->dispcol = 0; s->ddram = 1; } else if (s->input & LCD_CLR) { memset(s->data, 32, sizeof(s->data)); s->ac = 0; s->dispcol = 0; s->id = 1; s->ddram = 1; } } else { if (s->ddram) { s->data[s->ac] = s->input; s->ac++; s->ac %= sizeof(s->data); if (s->sh) { if (s->id) { s->dispcol++; } else { s->dispcol--; } s->dispcol %= sizeof(s->data); } s->need_update = 1; } else { // ... } } } static void hd44780_set_pin(void *opaque, int pin, int level) { LCDState *s = opaque; if (pin >= 0 && pin <= 7) { if (!s->rw) { if (!s->mode8bit && s->write_low) pin -= 4; if (level) s->input |= 1 << pin; else s->input &= ~(1 << pin); } } else if (pin == PIN_RS) { s->control = !level; } else if (pin == PIN_RW) { s->rw = level; } else if (pin == PIN_E) { if (!level && !s->rw) { if (!s->mode8bit) { s->write_low = !s->write_low; if (!s->write_low) hd44780_enable(s); } else { hd44780_enable(s); } } } else if (pin == PIN_BL) { s->backlight = level; s->need_update = 1; } } static void hd44780_update_display(void *opaque) { LCDState *s = opaque; uint32_t color_segment, color_led; int y, x, r, g, b; if (s->need_update) { if (s->backlight) { r = 0; g = 0xff; b = 0x80; } else { r = 0xf0; g = 0xe0; b = 0xb0; } switch (ds_get_bits_per_pixel(s->ds)) { case 8: color_segment = rgb_to_pixel8(0, 0, 0); color_led = rgb_to_pixel8(r, g, b); break; case 15: color_segment = rgb_to_pixel15(0, 0, 0); color_led = rgb_to_pixel15(r, g, b); break; case 16: color_segment = rgb_to_pixel16(0, 0, 0); color_led = rgb_to_pixel16(r, g, b); break; case 24: color_segment = rgb_to_pixel24(0, 0, 0); color_led = rgb_to_pixel24(r, g, b); break; case 32: color_segment = rgb_to_pixel32(0, 0, 0); color_led = rgb_to_pixel32(r, g, b); break; default: return; } if (s->display) { for (y = 0; y < HEIGHT; y++) { for (x = 0; x < WIDTH; x++) { draw_char(s->ds, x * 5 * CZOOM, y * 7 * CZOOM, s->data[y * WIDTH + x], color_segment, color_led); } } } dpy_update(s->ds, 0, 0, WIDTH * CZOOM * 5, HEIGHT * CZOOM * 7); } } static void hd44780_invalidate_display(void * opaque) { LCDState *s = opaque; s->need_update = 1; } static void hd44780_init(SysBusDevice *dev) { LCDState *s = FROM_SYSBUS(LCDState, dev); s->need_update = 1; qdev_init_gpio_in(&dev->qdev, hd44780_set_pin, 12); qdev_init_gpio_out(&dev->qdev, s->out, 8); s->ds = graphic_console_init(hd44780_update_display, hd44780_invalidate_display, NULL, NULL, s); qemu_console_resize(s->ds, WIDTH * CZOOM * 5, HEIGHT * CZOOM * 7); } static void hd44780_register(void) { sysbus_register_dev("gpio,hd44780", sizeof(LCDState), hd44780_init); } device_init(hd44780_register)