diff options
Diffstat (limited to 'drivers/gdisp/ED060SC4/gdisp_lld_ED060SC4.c')
-rw-r--r-- | drivers/gdisp/ED060SC4/gdisp_lld_ED060SC4.c | 625 |
1 files changed, 625 insertions, 0 deletions
diff --git a/drivers/gdisp/ED060SC4/gdisp_lld_ED060SC4.c b/drivers/gdisp/ED060SC4/gdisp_lld_ED060SC4.c new file mode 100644 index 00000000..14e7f88a --- /dev/null +++ b/drivers/gdisp/ED060SC4/gdisp_lld_ED060SC4.c @@ -0,0 +1,625 @@ +/* + * This file is subject to the terms of the GFX License. If a copy of + * the license was not distributed with this file, you can obtain one at: + * + * http://ugfx.org/license.html + */ + +/** + * @file drivers/gdisp/ED060SC4/gdisp_lld.c + * @brief GDISP Graphics Driver for the E-ink panel ED060SC4. + */ + +#include "gfx.h" + +#if GFX_USE_GDISP + +#define GDISP_DRIVER_VMT GDISPVMT_ED060SC4 +#include "../drivers/gdisp/ED060SC4/gdisp_lld_config.h" +#include "gdisp/lld/gdisp_lld.h" + +#include "board_ED060SC4.h" + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +#ifndef GDISP_SCREEN_HEIGHT + #define GDISP_SCREEN_HEIGHT 600 +#endif +#ifndef GDISP_SCREEN_WIDTH + #define GDISP_SCREEN_WIDTH 800 +#endif + +/* Number of pixels per byte */ +#ifndef EINK_PPB + #define EINK_PPB 4 +#endif + +/* Delay for generating clock pulses. + * Unit is approximate clock cycles of the CPU (0 to 15). + * This should be atleast 50 ns. + */ +#ifndef EINK_CLOCKDELAY + #define EINK_CLOCKDELAY 0 +#endif + +/* Width of one framebuffer block. + * Must be divisible by EINK_PPB and evenly divide GDISP_SCREEN_WIDTH. */ +#ifndef EINK_BLOCKWIDTH + #define EINK_BLOCKWIDTH 20 +#endif + +/* Height of one framebuffer block. + * Must evenly divide GDISP_SCREEN_WIDTH. */ +#ifndef EINK_BLOCKHEIGHT + #define EINK_BLOCKHEIGHT 20 +#endif + +/* Number of block buffers to use for framebuffer emulation. */ +#ifndef EINK_NUMBUFFERS + #define EINK_NUMBUFFERS 40 +#endif + +/* Do a "blinking" clear, i.e. clear to opposite polarity first. + * This reduces the image persistence. */ +#ifndef EINK_BLINKCLEAR + #define EINK_BLINKCLEAR TRUE +#endif + +/* Number of passes to use when clearing the display */ +#ifndef EINK_CLEARCOUNT + #define EINK_CLEARCOUNT 10 +#endif + +/* Number of passes to use when writing to the display */ +#ifndef EINK_WRITECOUNT + #define EINK_WRITECOUNT 4 +#endif + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +#define PRIV(g) ((drvPriv *)g->priv) + +/** Delay between signal changes, to give time for IO pins to change state. */ +static inline void clockdelay(void) +{ + #if EINK_CLOCKDELAY & 1 + asm("nop"); + #endif + #if EINK_CLOCKDELAY & 2 + asm("nop"); + asm("nop"); + #endif + #if EINK_CLOCKDELAY & 4 + asm("nop"); + asm("nop"); + asm("nop"); + asm("nop"); + #endif + #if EINK_CLOCKDELAY & 8 + asm("nop"); + asm("nop"); + asm("nop"); + asm("nop"); + asm("nop"); + asm("nop"); + asm("nop"); + asm("nop"); + #endif +} + +/** Fast vertical clock pulse for gate driver, used during initializations */ +static void vclock_quick(GDisplay *g) +{ + setpin_ckv(g, TRUE); + eink_delay(1); + setpin_ckv(g, FALSE); + eink_delay(4); +} + +/** Horizontal clock pulse for clocking data into source driver */ +static void hclock(GDisplay *g) +{ + clockdelay(); + setpin_cl(g, TRUE); + clockdelay(); + setpin_cl(g, FALSE); +} + +/** Start a new vertical gate driver scan from top. + * Note: Does not clear any previous bits in the shift register, + * so you should always scan through the whole display before + * starting a new scan. + */ +static void vscan_start(GDisplay *g) +{ + setpin_gmode(g, TRUE); + vclock_quick(g); + setpin_spv(g, FALSE); + vclock_quick(g); + setpin_spv(g, TRUE); + vclock_quick(g); +} + +/** Waveform for strobing a row of data onto the display. + * Attempts to minimize the leaking of color to other rows by having + * a long idle period after a medium-length strobe period. + */ +static void vscan_write(GDisplay *g) +{ + setpin_ckv(g, TRUE); + setpin_oe(g, TRUE); + eink_delay(5); + setpin_oe(g, FALSE); + setpin_ckv(g, FALSE); + eink_delay(200); +} + +/** Waveform used when clearing the display. Strobes a row of data to the + * screen, but does not mind some of it leaking to other rows. + */ +static void vscan_bulkwrite(GDisplay *g) +{ + setpin_ckv(g, TRUE); + eink_delay(20); + setpin_ckv(g, FALSE); + eink_delay(200); +} + +/** Waveform for skipping a vertical row without writing anything. + * Attempts to minimize the amount of change in any row. + */ +static void vscan_skip(GDisplay *g) +{ + setpin_ckv(g, TRUE); + eink_delay(1); + setpin_ckv(g, FALSE); + eink_delay(100); +} + +/** Stop the vertical scan. The significance of this escapes me, but it seems + * necessary or the next vertical scan may be corrupted. + */ +static void vscan_stop(GDisplay *g) +{ + setpin_gmode(g, FALSE); + vclock_quick(g); + vclock_quick(g); + vclock_quick(g); + vclock_quick(g); + vclock_quick(g); +} + +/** Start updating the source driver data (from left to right). */ +static void hscan_start(GDisplay *g) +{ + /* Disable latching and output enable while we are modifying the row. */ + setpin_le(g, FALSE); + setpin_oe(g, FALSE); + + /* The start pulse should remain low for the duration of the row. */ + setpin_sph(g, FALSE); +} + +/** Write data to the horizontal row. */ +static void hscan_write(GDisplay *g, const uint8_t *data, int count) +{ + while (count--) + { + /* Set the next byte on the data pins */ + setpins_data(g, *data++); + + /* Give a clock pulse to the shift register */ + hclock(g); + } +} + +/** Finish and transfer the row to the source drivers. + * Does not set the output enable, so the drivers are not yet active. */ +static void hscan_stop(GDisplay *g) +{ + /* End the scan */ + setpin_sph(g, TRUE); + hclock(g); + + /* Latch the new data */ + setpin_le(g, TRUE); + clockdelay(); + setpin_le(g, FALSE); +} + +/** Turn on the power to the E-Ink panel, observing proper power sequencing. */ +static void power_on(GDisplay *g) +{ + unsigned i; + + /* First the digital power supply and signal levels. */ + setpower_vdd(g, TRUE); + setpin_le(g, FALSE); + setpin_oe(g, FALSE); + setpin_cl(g, FALSE); + setpin_sph(g, TRUE); + setpins_data(g, 0); + setpin_ckv(g, FALSE); + setpin_gmode(g, FALSE); + setpin_spv(g, TRUE); + + /* Min. 100 microsecond delay after digital supply */ + gfxSleepMicroseconds(100); + + /* Then negative voltages and min. 1000 microsecond delay. */ + setpower_vneg(g, TRUE); + gfxSleepMicroseconds(1000); + + /* Finally the positive voltages. */ + setpower_vpos(g, TRUE); + + /* Clear the vscan shift register */ + vscan_start(g); + for (i = 0; i < GDISP_SCREEN_HEIGHT; i++) + vclock_quick(g); + vscan_stop(g); +} + +/** Turn off the power, observing proper power sequencing. */ +static void power_off(GDisplay *g) +{ + /* First the high voltages */ + setpower_vpos(g, FALSE); + setpower_vneg(g, FALSE); + + /* Wait for any capacitors to drain */ + gfxSleepMilliseconds(100); + + /* Then put all signals and digital supply to ground. */ + setpin_le(g, FALSE); + setpin_oe(g, FALSE); + setpin_cl(g, FALSE); + setpin_sph(g, FALSE); + setpins_data(g, 0); + setpin_ckv(g, FALSE); + setpin_gmode(g, FALSE); + setpin_spv(g, FALSE); + setpower_vdd(g, FALSE); +} + +/* ==================================== + * Framebuffer emulation layer + * ==================================== */ + +#if EINK_PPB == 4 + #define PIXELMASK 3 + #define PIXEL_WHITE 2 + #define PIXEL_BLACK 1 + #define BYTE_WHITE 0xAA + #define BYTE_BLACK 0x55 +#else + #error Unsupported EINK_PPB value. +#endif + +#if GDISP_SCREEN_HEIGHT % EINK_BLOCKHEIGHT != 0 + #error GDISP_SCREEN_HEIGHT must be evenly divisible by EINK_BLOCKHEIGHT +#endif + +#if GDISP_SCREEN_WIDTH % EINK_BLOCKWIDTH != 0 + #error GDISP_SCREEN_WIDTH must be evenly divisible by EINK_BLOCKWIDTH +#endif + +#if EINK_BLOCKWIDTH % EINK_PPB != 0 + #error EINK_BLOCKWIDTH must be evenly divisible by EINK_PPB +#endif + +#if EINK_NUMBUFFERS > 254 + #error EINK_NUMBUFFERS must be at most 254. +#endif + +#define BLOCKS_Y (GDISP_SCREEN_HEIGHT / EINK_BLOCKHEIGHT) +#define BLOCKS_X (GDISP_SCREEN_WIDTH / EINK_BLOCKWIDTH) +#define WIDTH_BYTES (EINK_BLOCKWIDTH / EINK_PPB) + +/* Buffers that store the data for a small area of the display. */ +typedef struct { + uint8_t data[EINK_BLOCKHEIGHT][WIDTH_BYTES]; +} block_t; + +typedef struct drvPriv { + uint8_t g_next_block; /* Index of the next free block buffer. */ + block_t g_blocks[EINK_NUMBUFFERS]; + + /* Map that stores the buffers associated to each area of the display. + * Value of 0 means that the block is not allocated. + * Other values are the index in g_blocks + 1. + */ + uint8_t g_blockmap[BLOCKS_Y][BLOCKS_X]; +} drvPriv; + +/** Check if the row contains any allocated blocks. */ +static bool_t blocks_on_row(GDisplay *g, unsigned by) +{ + unsigned bx; + for (bx = 0; bx < BLOCKS_X; bx++) + { + if (PRIV(g)->g_blockmap[by][bx] != 0) + { + return TRUE; + } + } + return FALSE; +} + +/** Write out a block row. */ +static void write_block_row(GDisplay *g, unsigned by) +{ + unsigned bx, dy, dx; + for (dy = 0; dy < EINK_BLOCKHEIGHT; dy++) + { + hscan_start(g); + for (bx = 0; bx < BLOCKS_X; bx++) + { + if (PRIV(g)->g_blockmap[by][bx] == 0) + { + for (dx = 0; dx < WIDTH_BYTES; dx++) + { + const uint8_t dummy = 0; + hscan_write(g, &dummy, 1); + } + } + else + { + block_t *block = &PRIV(g)->g_blocks[PRIV(g)->g_blockmap[by][bx] - 1]; + hscan_write(g, &block->data[dy][0], WIDTH_BYTES); + } + } + hscan_stop(g); + + vscan_write(g); + } +} + +/** Clear the block map, i.e. deallocate all blocks */ +static void clear_block_map(GDisplay *g) +{ + unsigned bx, by; + for (by = 0; by < BLOCKS_Y; by++) + { + for (bx = 0; bx < BLOCKS_X; bx++) + { + PRIV(g)->g_blockmap[by][bx] = 0; + } + } + + PRIV(g)->g_next_block = 0; +} + +/** Initialize a newly allocated block. */ +static void zero_block(block_t *block) +{ + unsigned dx, dy; + for (dy = 0; dy < EINK_BLOCKHEIGHT; dy++) + { + for (dx = 0; dx < WIDTH_BYTES; dx++) + { + block->data[dy][dx] = 0; + } + } +} + +/** Allocate a buffer + * Automatically flushes if all buffers are full. */ +static block_t *alloc_buffer(GDisplay *g, unsigned bx, unsigned by) +{ + block_t *result; + drvPriv *priv; + + priv = PRIV(g); + if (priv->g_blockmap[by][bx] == 0) + { + if (priv->g_next_block >= EINK_NUMBUFFERS) + gdisp_lld_flush(g); + + result = &priv->g_blocks[priv->g_next_block]; + priv->g_blockmap[by][bx] = priv->g_next_block + 1; + priv->g_next_block++; + zero_block(result); + return result; + } + else + { + result = &priv->g_blocks[priv->g_blockmap[by][bx] - 1]; + return result; + } +} + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +LLDSPEC bool_t gdisp_lld_init(GDisplay *g) { + g->priv = gfxAlloc(sizeof(drvPriv)); + + init_board(g); + + /* Make sure that all the pins are in "off" state. + * Having any pin high could cause voltage leaking to the + * display, which in turn causes the image to leak slowly away. + */ + power_off(g); + + clear_block_map(g); + + /* Initialise the GDISP structure */ + g->g.Width = GDISP_SCREEN_WIDTH; + g->g.Height = GDISP_SCREEN_HEIGHT; + g->g.Orientation = GDISP_ROTATE_0; + g->g.Powermode = powerOn; + g->g.Backlight = 100; + g->g.Contrast = 100; + return TRUE; +} + +#if GDISP_HARDWARE_FLUSH + LLDSPEC void gdisp_lld_flush(GDisplay *g) { + unsigned by, dy, i; + + for (i = 0; i < EINK_WRITECOUNT; i++) { + vscan_start(g); + + for (by = 0; by < BLOCKS_Y; by++) { + if (!blocks_on_row(g, by)) { + /* Skip the whole row of blocks. */ + for (dy = 0; dy < EINK_BLOCKHEIGHT; dy++) + vscan_skip(g); + } else { + /* Write out the blocks. */ + write_block_row(g, by); + } + } + + vscan_stop(g); + } + + clear_block_map(g); + } +#endif + +#if GDISP_HARDWARE_DRAWPIXEL + void gdisp_lld_draw_pixel(GDisplay *g) { + block_t *block; + uint8_t byte; + unsigned bx, by, dx, dy; + uint8_t bitpos; + + switch(g->g.Orientation) { + case GDISP_ROTATE_0: + bx = g->p.x / EINK_BLOCKWIDTH; + dx = g->p.x % EINK_BLOCKWIDTH; + by = g->p.y / EINK_BLOCKHEIGHT; + dy = g->p.y % EINK_BLOCKHEIGHT; + break; + case GDISP_ROTATE_90: + bx = g->p.y / EINK_BLOCKWIDTH; + dx = g->p.y % EINK_BLOCKWIDTH; + by = (GDISP_SCREEN_HEIGHT-1 - g->p.x) / EINK_BLOCKHEIGHT; + dy = (GDISP_SCREEN_HEIGHT-1 - g->p.x) % EINK_BLOCKHEIGHT; + break; + case GDISP_ROTATE_180: + bx = (GDISP_SCREEN_WIDTH-1 - g->p.x) / EINK_BLOCKWIDTH; + dx = (GDISP_SCREEN_WIDTH-1 - g->p.x) % EINK_BLOCKWIDTH; + by = (GDISP_SCREEN_HEIGHT-1 - g->p.y) / EINK_BLOCKHEIGHT; + dy = (GDISP_SCREEN_HEIGHT-1 - g->p.y) % EINK_BLOCKHEIGHT; + break; + case GDISP_ROTATE_270: + bx = (GDISP_SCREEN_WIDTH-1 - g->p.y) / EINK_BLOCKWIDTH; + dx = (GDISP_SCREEN_WIDTH-1 - g->p.y) % EINK_BLOCKWIDTH; + by = g->p.x / EINK_BLOCKHEIGHT; + dy = g->p.x % EINK_BLOCKHEIGHT; + break; + } + + block = alloc_buffer(g, bx, by); + + bitpos = (6 - 2 * (dx % EINK_PPB)); + byte = block->data[dy][dx / EINK_PPB]; + byte &= ~(PIXELMASK << bitpos); + if (g->p.color != Black) + byte |= PIXEL_WHITE << bitpos; + else + byte |= PIXEL_BLACK << bitpos; + block->data[dy][dx / EINK_PPB] = byte; + } +#endif + +#if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL + LLDSPEC void gdisp_lld_control(GDisplay *g) { + switch(g->p.x) { + case GDISP_CONTROL_POWER: + if (g->g.Powermode == (powermode_t)g->p.ptr) + return; + switch((powermode_t)g->p.ptr) { + case powerOff: + case powerSleep: + case powerDeepSleep: + gdisp_lld_flush(g); + power_off(g); + break; + case powerOn: + power_on(g); + break; + default: + return; + } + g->g.Powermode = (powermode_t)g->p.ptr; + return; + + case GDISP_CONTROL_ORIENTATION: + if (g->g.Orientation == (orientation_t)g->p.ptr) + return; + switch((orientation_t)g->p.ptr) { + case GDISP_ROTATE_0: + case GDISP_ROTATE_180: + g->g.Height = GDISP_SCREEN_HEIGHT; + g->g.Width = GDISP_SCREEN_WIDTH; + break; + case GDISP_ROTATE_90: + case GDISP_ROTATE_270: + g->g.Height = GDISP_SCREEN_WIDTH; + g->g.Width = GDISP_SCREEN_HEIGHT; + break; + default: + return; + } + g->g.Orientation = (orientation_t)g->p.ptr; + return; + + default: + return; + } + } +#endif + +/* =============================== + * Accelerated routines + * =============================== */ + +#if GDISP_HARDWARE_CLEARS + static void subclear(GDisplay *g, color_t color) { + unsigned x, y; + uint8_t byte; + + hscan_start(g); + byte = color ? BYTE_WHITE : BYTE_BLACK; + for (x = 0; x < GDISP_SCREEN_WIDTH; x++) + { + hscan_write(g, &byte, 1); + } + hscan_stop(g); + + setpin_oe(g, TRUE); + vscan_start(g); + for (y = 0; y < GDISP_SCREEN_HEIGHT; y++) + vscan_bulkwrite(g); + vscan_stop(g); + setpin_oe(g, FALSE); + } + + void gdisp_lld_clear(GDisplay *g) { + unsigned i; + + clear_block_map(g); + + if (EINK_BLINKCLEAR) { + subclear(g, !g->p.color); + gfxSleepMilliseconds(50); + } + + for (i = 0; i < EINK_CLEARCOUNT; i++) { + subclear(g, g->p.color); + gfxSleepMilliseconds(10); + } + } +#endif + +#endif // GFX_USE_GDISP |