/* Copyright 2016 Fred Sundvik <fsundvik@gmail.com> 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 <http://www.gnu.org/licenses/>. */ #include "gfx.h" #if GFX_USE_GDISP # define GDISP_DRIVER_VMT GDISPVMT_IS31FL3731C_QMK # define GDISP_SCREEN_HEIGHT LED_HEIGHT # define GDISP_SCREEN_WIDTH LED_WIDTH # include "gdisp_lld_config.h" # include "src/gdisp/gdisp_driver.h" # include "board_is31fl3731c.h" // Can't include led_tables from here extern const uint8_t CIE1931_CURVE[]; /*===========================================================================*/ /* Driver local definitions. */ /*===========================================================================*/ # ifndef GDISP_INITIAL_CONTRAST # define GDISP_INITIAL_CONTRAST 0 # endif # ifndef GDISP_INITIAL_BACKLIGHT # define GDISP_INITIAL_BACKLIGHT 0 # 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 /*===========================================================================*/ /* 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_OFF); 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 = powerOff; 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; y < GDISP_SCREEN_HEIGHT; y++) { for (int x = 0; x < GDISP_SCREEN_WIDTH; x++) { uint8_t val = (uint16_t)*src * g->g.Backlight / 100; PRIV(g)->write_buffer[get_led_address(g, x, y)] = CIE1931_CURVE[val]; ++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_BACKLIGHT: if (g->g.Backlight == (unsigned)g->p.ptr) return; unsigned val = (unsigned)g->p.ptr; g->g.Backlight = val > 100 ? 100 : val; g->flags |= GDISP_FLG_NEEDFLUSH; return; } } # endif // GDISP_NEED_CONTROL #endif // GFX_USE_GDISP