From dcd71e66bd5f0b521c9f98ebe83ff1f7abb62918 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Thu, 14 Dec 2017 06:22:04 +0100 Subject: Optimize lcd-ssd1303 driver (clip series) Saves 100+ bytes (50 of it in iram), saves a bit of power Internal LCD clock decreased but with added efficiency of drawing routines loses only around 2 Hz on the scanrate (~75Hz) while fps is slightly increased Column offsets are now calculated outside the loops saving a few instructions Passing a LCD_NOP command after lcd_update turns off Data/Cmd# gpio saving a bit more power Added a function lcd_write_cmd_triple() that allows 3 commands to be sent at once when enabled with LCD_USE_FIFO_FOR_COMMANDS it sends them back to back without checking FIFO status in between or sending to thhe D/C# Gpio. Makes an assumption about the FIFO being large enough to accept 3 commands after being emptied which should be the case on the clipv1, clipv2, clipplus. I have only enabled it for the clip plus as thats the only device I have to test it on. On clip+ the SSP clock is now turned off when screen is off Change-Id: Ib5fd24697bfe4ac8b8ee017361e789e4a7910d21 --- firmware/target/arm/as3525/lcd-clip.h | 10 + firmware/target/arm/as3525/lcd-ssd1303.c | 211 +++++++++++++-------- firmware/target/arm/as3525/sansa-clip/lcd-clip.c | 28 +++ .../arm/as3525/sansa-clipplus/lcd-clip-plus.c | 32 ++++ .../target/arm/as3525/sansa-clipv2/lcd-clipv2.c | 29 +++ 5 files changed, 228 insertions(+), 82 deletions(-) (limited to 'firmware/target') diff --git a/firmware/target/arm/as3525/lcd-clip.h b/firmware/target/arm/as3525/lcd-clip.h index eb76401bc0..3bde92430c 100644 --- a/firmware/target/arm/as3525/lcd-clip.h +++ b/firmware/target/arm/as3525/lcd-clip.h @@ -18,12 +18,22 @@ * KIND, either express or implied. * ****************************************************************************/ +#ifndef __LCDCLIP_H__ +#define __LCDCLIP_H__ #include "config.h" +#if !defined(BOOTLOADER) && defined(SANSA_CLIPPLUS) /* only tested for clipplus */ +/* Ensure empty FIFO for lcd commands is at least 3 deep */ +#define LCD_USE_FIFO_FOR_COMMANDS +#endif + +void lcd_write_cmd_triplet(int cmd1, int cmd2, int cmd3); + /* return variant number: 0 = clipv1, clipv2, old clip+, 1 = newer clip+ */ int lcd_hw_init(void) INIT_ATTR; /* target-specific power enable */ void lcd_enable_power(bool onoff); +#endif /*__LCDCLIP_H__*/ diff --git a/firmware/target/arm/as3525/lcd-ssd1303.c b/firmware/target/arm/as3525/lcd-ssd1303.c index a00398a998..2aa0b844c5 100644 --- a/firmware/target/arm/as3525/lcd-ssd1303.c +++ b/firmware/target/arm/as3525/lcd-ssd1303.c @@ -10,6 +10,7 @@ * Copyright (C) 2002 by Alan Korr * Copyright (C) 2008 François Dinel * Copyright (C) 2008-2009 Rafaël Carré + * Copyright (C) 2017 William Wilgus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -118,10 +119,10 @@ void lcd_set_flip(bool yesno) #ifdef HAVE_LCD_ENABLE void lcd_enable(bool enable) { - if(display_on == enable) + if (display_on == enable) return; - if( (display_on = enable) ) /* simple '=' is not a typo ! */ + if ( (display_on = enable) ) /* simple '=' is not a typo ! */ { lcd_enable_power(enable); lcd_write_command(LCD_SET_DISPLAY_ON); @@ -149,50 +150,65 @@ void lcd_init_device(void) variant = lcd_hw_init(); offset = (variant == 0) ? 2 : 0; - /* Set display clock (divide ratio = 1) and oscillator frequency (1) */ - lcd_write_command(LCD_SET_DISPLAY_CLOCK_AND_OSC_FREQ); - lcd_write_command(0x10); - - /* Set VCOM deselect level to 0.76V */ - lcd_write_command(LCD_SET_VCOM_DESELECT_LEVEL); - lcd_write_command(0x34); - - /* Set pre-charge period (p1period is 2 dclk and p2period is 5 dclk) */ - lcd_write_command(LCD_SET_PRECHARGE_PERIOD); - lcd_write_command(0x25); + /* power on display to accept commands */ + lcd_enable_power(true); + + lcd_write_cmd_triplet + ( + /* Set display clock */ + (LCD_SET_DISPLAY_CLOCK_AND_OSC_FREQ), + /* Set display clock (divide ratio = 1) and oscillator frequency (1) */ + (0x10), + /* Set VCOM deselect level */ + (LCD_SET_VCOM_DESELECT_LEVEL) + ); + + lcd_write_cmd_triplet + ( + /* Set VCOM deselect level to 0.76V */ + (0x34), + /* Set pre-charge period */ + (LCD_SET_PRECHARGE_PERIOD), + /* Set pre-charge period (p1period is 2 dclk and p2period is 5 dclk) */ + (0x25) + ); /* Set contrast register to 12% */ lcd_set_contrast(lcd_default_contrast()); - /* Configure DC-DC */ - lcd_write_command(LCD_SET_DC_DC); - lcd_write_command((variant == 0) ? 0x8A : 0x10); - - /* Set starting line as 0 */ - lcd_write_command(LCD_SET_DISPLAY_START_LINE /*|(0 & 0x3f)*/); - - /* Column 131 is remapped to SEG0 */ - lcd_write_command(LCD_SET_SEGMENT_REMAP_INV); - - /* Invert COM scan direction (N-1 to 0) */ - lcd_write_command(LCD_SET_COM_OUTPUT_SCAN_DIRECTION_INV); - - /* Set normal display mode (not every pixel ON) */ - lcd_write_command(LCD_SET_ENTIRE_DISPLAY_OFF); - - /* Set normal display mode (not inverted) */ - lcd_write_command(LCD_SET_NORMAL_DISPLAY); - - /* Clear whole framebuffer, including "overscan" - * We don't need to handle that out of screen columns in lcd_clear_display() - * since we will never write into it anymore - */ - lcd_write_command (LCD_SET_HIGHER_COLUMN_ADDRESS /*| 0*/); - lcd_write_command (LCD_SET_LOWER_COLUMN_ADDRESS /*| 0*/); + lcd_write_cmd_triplet + ( + /* Configure DC-DC */ + (LCD_SET_DC_DC), + /* Configure DC-DC */ + ((variant == 0) ? 0x8A : 0x10), + /* Set starting line as 0 */ + (LCD_SET_DISPLAY_START_LINE /*|(0 & 0x3f)*/) + ); + + lcd_write_cmd_triplet + ( + /* Column 131 is remapped to SEG0 */ + (LCD_SET_SEGMENT_REMAP_INV), + /* Invert COM scan direction (N-1 to 0) */ + (LCD_SET_COM_OUTPUT_SCAN_DIRECTION_INV), + /* Set normal display mode (not every pixel ON) */ + (LCD_SET_ENTIRE_DISPLAY_OFF) + ); + + lcd_write_cmd_triplet + ( + /* Set normal display mode (not inverted) */ + (LCD_SET_NORMAL_DISPLAY), + /* set upper 4 bits of 8-bit column address */ + (LCD_SET_HIGHER_COLUMN_ADDRESS /*| 0*/), + /* set lower 4 bits of 8-bit column address */ + (LCD_SET_LOWER_COLUMN_ADDRESS /*| 0*/) + ); fb_data p_bytes[LCD_WIDTH + 2 * offset]; memset(p_bytes, 0, sizeof(p_bytes)); /* fills with 0 : pixel off */ - for(i = 0; i < 8; i++) + for (i = 0; i < 8; i++) { lcd_write_command (LCD_SET_PAGE_ADDRESS | (i /*& 0xf*/)); lcd_write_data(p_bytes, LCD_WIDTH + 2 * offset); @@ -205,27 +221,48 @@ void lcd_init_device(void) /*** Update functions ***/ +/* returns LCD_CNTL_HIGHCOL or'd with higher 4 bits of + the 8-bit column address for the display data RAM. +*/ +static inline int get_column_high_byte(const int x) +{ + return (LCD_CNTL_HIGHCOL | (((x+offset) >> 4) & 0xf)); +} + +/* returns LCD_CNTL_LOWCOL or'd with lower 4 bits of + the 8-bit column address for the display data RAM. +*/ +static inline int get_column_low_byte(const int x) +{ + return (LCD_CNTL_LOWCOL | ((x+offset) & 0xf)); +} + /* Performance function that works with an external buffer note that by and bheight are in 8-pixel units! */ void lcd_blit_mono(const unsigned char *data, int x, int by, int width, int bheight, int stride) { - if(!display_on) + if (!display_on) return; + const int column_high = get_column_high_byte(x); + const int column_low = get_column_low_byte(x); + /* Copy display bitmap to hardware */ while (bheight--) { - lcd_write_command (LCD_CNTL_PAGE | (by++ & 0xf)); - lcd_write_command (LCD_CNTL_HIGHCOL | (((x+offset)>>4) & 0xf)); - lcd_write_command (LCD_CNTL_LOWCOL | ((x+offset) & 0xf)); + lcd_write_cmd_triplet + ( + (LCD_CNTL_PAGE | (by++ & 0xf)), + (column_high), + (column_low) + ); lcd_write_data(data, width); data += stride; } } - #ifndef BOOTLOADER /* Helper function for lcd_grey_phase_blit(). */ void lcd_grey_data(unsigned char *values, unsigned char *phases, int count); @@ -235,16 +272,22 @@ void lcd_grey_data(unsigned char *values, unsigned char *phases, int count); void lcd_blit_grey_phase(unsigned char *values, unsigned char *phases, int x, int by, int width, int bheight, int stride) { - if(!display_on) + if (!display_on) return; + const int column_high = get_column_high_byte(x); + const int column_low = get_column_low_byte(x); + stride <<= 3; /* 8 pixels per block */ /* Copy display bitmap to hardware */ while (bheight--) { - lcd_write_command (LCD_CNTL_PAGE | (by++ & 0xf)); - lcd_write_command (LCD_CNTL_HIGHCOL | (((x+offset)>>4) & 0xf)); - lcd_write_command (LCD_CNTL_LOWCOL | ((x+offset) & 0xf)); + lcd_write_cmd_triplet + ( + (LCD_CNTL_PAGE | (by++ & 0xf)), + (column_high), + (column_low) + ); lcd_grey_data(values, phases, width); @@ -255,26 +298,41 @@ void lcd_blit_grey_phase(unsigned char *values, unsigned char *phases, #endif +/* Shared internal function for lcd_update and lcd_update_rect + WARNING does NOT check bounds +*/ +static void internal_update_rect(int, int, int, int) ICODE_ATTR; +static void internal_update_rect(int x, int y, int width, int height) +{ + if (!display_on) + return; + + const int column_high = get_column_high_byte(x); + const int column_low = get_column_low_byte(x); + + /* Copy specified rectange bitmap to hardware */ + for (; y <= height; y++) + { + lcd_write_cmd_triplet + ( + (LCD_CNTL_PAGE | (y & 0xf)), + (column_high), + (column_low) + ); + + lcd_write_data (FBADDR(x,y), width); + } + lcd_write_command (LCD_NOP); /* return to command mode */ + +} /* Update the display. This must be called after all other LCD functions that change the display. */ void lcd_update(void) ICODE_ATTR; void lcd_update(void) { - int y; - - if(!display_on) - return; - /* Copy display bitmap to hardware */ - for (y = 0; y < LCD_FBHEIGHT; y++) - { - lcd_write_command (LCD_CNTL_PAGE | (y & 0xf)); - lcd_write_command (LCD_CNTL_HIGHCOL | ((offset >> 4) & 0xf)); - lcd_write_command (LCD_CNTL_LOWCOL | (offset & 0xf)); - - lcd_write_data (FBADDR(0, y), LCD_WIDTH); - } + internal_update_rect(0, 0, LCD_WIDTH, LCD_FBHEIGHT - 1); } /* Update a fraction of the display. */ @@ -282,45 +340,34 @@ void lcd_update_rect(int, int, int, int) ICODE_ATTR; void lcd_update_rect(int x, int y, int width, int height) { int ymax; - - if(!display_on) + if (!display_on) return; - /* The Y coordinates have to work on even 8 pixel rows */ - if (x < 0) + /* make sure the rectangle is bounded in the screen */ + if (width > LCD_WIDTH - x)/* Clip right */ + width = LCD_WIDTH - x; + if (x < 0)/* Clip left */ { width += x; x = 0; } - - if (x + width > LCD_WIDTH) - width = LCD_WIDTH - x; - if (width <= 0) return; /* nothing left to do, 0 is harmful to lcd_write_data() */ - if (y < 0) + if (height > LCD_HEIGHT - y) /* Clip bottom */ + height = LCD_HEIGHT - y; + if (y < 0) /* Clip top */ { height += y; y = 0; } - - if (y + height > LCD_HEIGHT) - height = LCD_HEIGHT - y; - if (height <= 0) return; /* nothing left to do */ + /* The Y coordinates have to work on even 8 pixel rows */ ymax = (y + height-1) >> 3; y >>= 3; /* Copy specified rectange bitmap to hardware */ - for (; y <= ymax; y++) - { - lcd_write_command (LCD_CNTL_PAGE | (y & 0xf)); - lcd_write_command (LCD_CNTL_HIGHCOL | (((x+offset) >> 4) & 0xf)); - lcd_write_command (LCD_CNTL_LOWCOL | ((x+offset) & 0xf)); - - lcd_write_data (FBADDR(x,y), width); - } + internal_update_rect(x, y, width, ymax); } diff --git a/firmware/target/arm/as3525/sansa-clip/lcd-clip.c b/firmware/target/arm/as3525/sansa-clip/lcd-clip.c index 21d8902739..8331b9e895 100644 --- a/firmware/target/arm/as3525/sansa-clip/lcd-clip.c +++ b/firmware/target/arm/as3525/sansa-clip/lcd-clip.c @@ -69,6 +69,34 @@ void lcd_write_command(int byte) ; } +void lcd_write_cmd_triplet(int cmd1, int cmd2, int cmd3) +{ +#ifndef LCD_USE_FIFO_FOR_COMMANDS + lcd_write_command(cmd1); + lcd_write_command(cmd2); + lcd_write_command(cmd3); +#else + /* combine writes to data register */ + + while ((DBOP_STAT & (1<<10)) == 0) /* While push fifo is not empty */ + ; + /* FIFO is empty at this point */ + + /* unset D/C# (data or command) */ + GPIOA_PIN(5) = 0; + + /* Write command */ + /* !!makes assumption FIFO is at least (3) levels deep! */ + /* Only bits 15:12 and 3:0 of DBOP_DOUT are meaningful */ + DBOP_DOUT = (cmd1 << 8) | cmd1; + DBOP_DOUT = (cmd2 << 8) | cmd2; + DBOP_DOUT = (cmd3 << 8) | cmd3; + /* While push fifo is not empty */ + while ((DBOP_STAT & (1<<10)) == 0) + ; +#endif +} + void lcd_write_data(const fb_data* p_bytes, int count) { volatile int i = 0; diff --git a/firmware/target/arm/as3525/sansa-clipplus/lcd-clip-plus.c b/firmware/target/arm/as3525/sansa-clipplus/lcd-clip-plus.c index c0b128a486..a50a9e5c80 100644 --- a/firmware/target/arm/as3525/sansa-clipplus/lcd-clip-plus.c +++ b/firmware/target/arm/as3525/sansa-clipplus/lcd-clip-plus.c @@ -64,6 +64,31 @@ void lcd_write_command(int byte) ; } +void lcd_write_cmd_triplet(int cmd1, int cmd2, int cmd3) +{ +#ifndef LCD_USE_FIFO_FOR_COMMANDS + lcd_write_command(cmd1); + lcd_write_command(cmd2); + lcd_write_command(cmd3); +#else + /* combine writes to data register */ + while(SSP_SR & (1<<4)) /* BSY flag */ + ; + /* FIFO is empty at this point */ + + /* LCD command mode */ + GPIOB_PIN(2) = 0; + + /* !!makes assumption FIFO is at least (3) levels deep!! */ + SSP_DATA = cmd1; + SSP_DATA = cmd2; + SSP_DATA = cmd3; + + while(SSP_SR & (1<<4)) /* BSY flag */ + ; +#endif +} + void lcd_write_data(const fb_data* p_bytes, int count) { /* LCD data mode */ @@ -80,6 +105,13 @@ void lcd_write_data(const fb_data* p_bytes, int count) void lcd_enable_power(bool onoff) { +#ifndef BOOTLOADER + if (onoff) + bitset32(&CGU_PERI, CGU_SSP_CLOCK_ENABLE); + else + bitclr32(&CGU_PERI, CGU_SSP_CLOCK_ENABLE); +#else (void) onoff; +#endif } diff --git a/firmware/target/arm/as3525/sansa-clipv2/lcd-clipv2.c b/firmware/target/arm/as3525/sansa-clipv2/lcd-clipv2.c index d7266f72a0..0b3317255a 100644 --- a/firmware/target/arm/as3525/sansa-clipv2/lcd-clipv2.c +++ b/firmware/target/arm/as3525/sansa-clipv2/lcd-clipv2.c @@ -63,6 +63,35 @@ void lcd_write_command(int byte) DBOP_TIMPOL_23 = 0xE037E037; } +void lcd_write_cmd_triplet(int cmd1, int cmd2, int cmd3) +{ +#ifndef LCD_USE_FIFO_FOR_COMMANDS + lcd_write_command(cmd1); + lcd_write_command(cmd2); + lcd_write_command(cmd3); +#else + /* combine writes to data register */ + + while ((DBOP_STAT & (1<<10)) == 0) /* While push fifo is not empty */ + ; + /* FIFO is empty at this point */ + /* unset D/C# (data or command) */ + GPIOB_PIN(2) = 0; + DBOP_TIMPOL_23 = 0xE0370036; + + /* Write command */ + /* !!makes assumption FIFO is at least (3) levels deep! */ + DBOP_DOUT8 = cmd1; + DBOP_DOUT8 = cmd2; + DBOP_DOUT8 = cmd3; + /* While push fifo is not empty */ + while ((DBOP_STAT & (1<<10)) == 0) + ; + + DBOP_TIMPOL_23 = 0xE037E037; +#endif +} + void lcd_write_data(const fb_data* p_bytes, int count) { volatile int i = 0; -- cgit