/* Copyright 2016 Fred Sundvik 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 (at your option) 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, see . */ #include "gfx.h" #if GFX_USE_GDISP #define GDISP_DRIVER_VMT GDISPVMT_IS31FL3731C_ERGODOX #include "drivers/gdisp/IS31FL3731C/gdisp_lld_config.h" #include "src/gdisp/gdisp_driver.h" #include "board_IS31FL3731C.h" /*===========================================================================*/ /* Driver local definitions. */ /*===========================================================================*/ #ifndef GDISP_SCREEN_HEIGHT #define GDISP_SCREEN_HEIGHT 9 #endif #ifndef GDISP_SCREEN_WIDTH #define GDISP_SCREEN_WIDTH 16 #endif #ifndef GDISP_INITIAL_CONTRAST #define GDISP_INITIAL_CONTRAST 0 #endif #ifndef GDISP_INITIAL_BACKLIGHT #define GDISP_INITIAL_BACKLIGHT 100 #endif #define GDISP_FLG_NEEDFLUSH (GDISP_FLG_DRIVER<<0) #define IS31_ADDR_DEFAULT 0x74 #define IS31_REG_CONFIG 0x00 // bits in reg #define IS31_REG_CONFIG_PICTUREMODE 0x00 #define IS31_REG_CONFIG_AUTOPLAYMODE 0x08 #define IS31_REG_CONFIG_AUDIOPLAYMODE 0x18 // D2:D0 bits are starting frame for autoplay mode #define IS31_REG_PICTDISP 0x01 // D2:D0 frame select for picture mode #define IS31_REG_AUTOPLAYCTRL1 0x02 // D6:D4 number of loops (000=infty) // D2:D0 number of frames to be used #define IS31_REG_AUTOPLAYCTRL2 0x03 // D5:D0 delay time (*11ms) #define IS31_REG_DISPLAYOPT 0x05 #define IS31_REG_DISPLAYOPT_INTENSITY_SAME 0x20 // same intensity for all frames #define IS31_REG_DISPLAYOPT_BLINK_ENABLE 0x8 // D2:D0 bits blink period time (*0.27s) #define IS31_REG_AUDIOSYNC 0x06 #define IS31_REG_AUDIOSYNC_ENABLE 0x1 #define IS31_REG_FRAMESTATE 0x07 #define IS31_REG_BREATHCTRL1 0x08 // D6:D4 fade out time (26ms*2^i) // D2:D0 fade in time (26ms*2^i) #define IS31_REG_BREATHCTRL2 0x09 #define IS31_REG_BREATHCTRL2_ENABLE 0x10 // D2:D0 extinguish time (3.5ms*2^i) #define IS31_REG_SHUTDOWN 0x0A #define IS31_REG_SHUTDOWN_OFF 0x0 #define IS31_REG_SHUTDOWN_ON 0x1 #define IS31_REG_AGCCTRL 0x0B #define IS31_REG_ADCRATE 0x0C #define IS31_COMMANDREGISTER 0xFD #define IS31_FUNCTIONREG 0x0B // helpfully called 'page nine' #define IS31_FUNCTIONREG_SIZE 0xD #define IS31_FRAME_SIZE 0xB4 #define IS31_PWM_REG 0x24 #define IS31_PWM_SIZE 0x90 #define IS31_LED_MASK_SIZE 0x12 #define IS31_SCREEN_WIDTH 16 #define IS31 //Generated by http://jared.geek.nz/2013/feb/linear-led-pwm const unsigned char cie[256] = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 28, 28, 29, 29, 30, 31, 31, 32, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 47, 48, 49, 50, 51, 52, 53, 54, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 72, 73, 74, 75, 76, 77, 79, 80, 81, 82, 83, 85, 86, 87, 88, 90, 91, 92, 94, 95, 96, 98, 99, 100, 102, 103, 105, 106, 108, 109, 110, 112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131, 132, 134, 136, 138, 139, 141, 143, 145, 146, 148, 150, 152, 154, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 196, 198, 200, 202, 204, 207, 209, 211, 214, 216, 218, 220, 223, 225, 228, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252, 255, }; /*===========================================================================*/ /* Driver local functions. */ /*===========================================================================*/ typedef struct{ uint8_t write_buffer_offset; uint8_t write_buffer[IS31_FRAME_SIZE]; uint8_t frame_buffer[GDISP_SCREEN_HEIGHT * GDISP_SCREEN_WIDTH]; uint8_t page; }__attribute__((__packed__)) PrivData; // Some common routines and macros #define PRIV(g) ((PrivData*)g->priv) /*===========================================================================*/ /* Driver exported functions. */ /*===========================================================================*/ static GFXINLINE void write_page(GDisplay* g, uint8_t page) { uint8_t tx[2] __attribute__((aligned(2))); tx[0] = IS31_COMMANDREGISTER; tx[1] = page; write_data(g, tx, 2); } static GFXINLINE void write_register(GDisplay* g, uint8_t page, uint8_t reg, uint8_t data) { uint8_t tx[2] __attribute__((aligned(2))); tx[0] = reg; tx[1] = data; write_page(g, page); write_data(g, tx, 2); } static GFXINLINE void write_ram(GDisplay *g, uint8_t page, uint16_t offset, uint16_t length) { PRIV(g)->write_buffer_offset = offset; write_page(g, page); write_data(g, (uint8_t*)PRIV(g), length + 1); } LLDSPEC bool_t gdisp_lld_init(GDisplay *g) { // The private area is the display surface. g->priv = gfxAlloc(sizeof(PrivData)); __builtin_memset(PRIV(g), 0, sizeof(PrivData)); PRIV(g)->page = 0; // Initialise the board interface init_board(g); gfxSleepMilliseconds(10); // zero function page, all registers (assuming full_page is all zeroes) write_ram(g, IS31_FUNCTIONREG, 0, IS31_FUNCTIONREG_SIZE); set_hardware_shutdown(g, false); gfxSleepMilliseconds(10); // software shutdown write_register(g, IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_OFF); gfxSleepMilliseconds(10); // zero function page, all registers write_ram(g, IS31_FUNCTIONREG, 0, IS31_FUNCTIONREG_SIZE); gfxSleepMilliseconds(10); // zero all LED registers on all 8 pages, and enable the mask __builtin_memcpy(PRIV(g)->write_buffer, get_led_mask(g), IS31_LED_MASK_SIZE); for(uint8_t i=0; i<8; i++) { write_ram(g, i, 0, IS31_FRAME_SIZE); gfxSleepMilliseconds(1); } // software shutdown disable (i.e. turn stuff on) write_register(g, IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_ON); gfxSleepMilliseconds(10); // Finish Init post_init_board(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 = GDISP_INITIAL_BACKLIGHT; g->g.Contrast = GDISP_INITIAL_CONTRAST; return TRUE; } #if GDISP_HARDWARE_FLUSH LLDSPEC void gdisp_lld_flush(GDisplay *g) { // Don't flush if we don't need it. if (!(g->flags & GDISP_FLG_NEEDFLUSH)) return; PRIV(g)->page++; PRIV(g)->page %= 2; // TODO: some smarter algorithm for this // We should run only one physical page at a time // This way we don't need to send so much data, and // we could use slightly less memory uint8_t* src = PRIV(g)->frame_buffer; for (int y=0;ywrite_buffer[get_led_address(g, x, y)]=cie[*src]; ++src; } } write_ram(g, PRIV(g)->page, IS31_PWM_REG, IS31_PWM_SIZE); gfxSleepMilliseconds(1); write_register(g, IS31_FUNCTIONREG, IS31_REG_PICTDISP, PRIV(g)->page); g->flags &= ~GDISP_FLG_NEEDFLUSH; } #endif #if GDISP_HARDWARE_DRAWPIXEL LLDSPEC void gdisp_lld_draw_pixel(GDisplay *g) { coord_t x, y; switch(g->g.Orientation) { default: case GDISP_ROTATE_0: x = g->p.x; y = g->p.y; break; case GDISP_ROTATE_180: x = GDISP_SCREEN_WIDTH-1 - g->p.x; y = g->p.y; break; } PRIV(g)->frame_buffer[y * GDISP_SCREEN_WIDTH + x] = gdispColor2Native(g->p.color); g->flags |= GDISP_FLG_NEEDFLUSH; } #endif #if GDISP_HARDWARE_PIXELREAD LLDSPEC color_t gdisp_lld_get_pixel_color(GDisplay *g) { coord_t x, y; switch(g->g.Orientation) { default: case GDISP_ROTATE_0: x = g->p.x; y = g->p.y; break; case GDISP_ROTATE_180: x = GDISP_SCREEN_WIDTH-1 - g->p.x; y = g->p.y; break; } return gdispNative2Color(PRIV(g)->frame_buffer[y * GDISP_SCREEN_WIDTH + x]); } #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: write_register(g, IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_OFF); break; case powerOn: write_register(g, IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_ON); 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) { /* Rotation is handled by the drawing routines */ 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; case GDISP_CONTROL_CONTRAST: return; } } #endif // GDISP_NEED_CONTROL #endif // GFX_USE_GDISP