From c567fc9be20d12b9f6e246dffa99472b66677c28 Mon Sep 17 00:00:00 2001 From: Andree Buschmann Date: Sat, 21 Feb 2009 11:10:50 +0000 Subject: Submit FS#9890 by Boris Gjenero. Enabling option for iPod Video to shut down LCD and BCM (controller) after backlight was switched off. With this option the user can decide whether to keep the transflective LCD switched on (e.g. during daylight use) or to switch it off (to save power). The power saving is extreme and increases the battery runtime by far (measured >30%). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@20076 a1c6a512-1295-4272-9138-f99709370657 --- firmware/export/config-ipodvideo.h | 8 + firmware/target/arm/ipod/backlight-nano_video.c | 11 +- firmware/target/arm/ipod/backlight-target.h | 4 + firmware/target/arm/ipod/power-ipod.c | 2 +- firmware/target/arm/ipod/video/lcd-video.c | 316 ++++++++++++++++++++++-- 5 files changed, 314 insertions(+), 27 deletions(-) (limited to 'firmware') diff --git a/firmware/export/config-ipodvideo.h b/firmware/export/config-ipodvideo.h index ca8e43db77..fb00fd99d1 100644 --- a/firmware/export/config-ipodvideo.h +++ b/firmware/export/config-ipodvideo.h @@ -204,4 +204,12 @@ #define IPOD_ACCESSORY_PROTOCOL #define HAVE_SERIAL +#ifndef BOOTLOADER +/* Support for LCD sleep/BCM shutdown */ +#define HAVE_LCD_SLEEP +#define HAVE_LCD_SLEEP_SETTING +/* The same code may also be used when shutting down the iPod */ +#define HAVE_LCD_SHUTDOWN +#endif + #endif diff --git a/firmware/target/arm/ipod/backlight-nano_video.c b/firmware/target/arm/ipod/backlight-nano_video.c index 5eb5198b73..2f56f94225 100644 --- a/firmware/target/arm/ipod/backlight-nano_video.c +++ b/firmware/target/arm/ipod/backlight-nano_video.c @@ -73,9 +73,18 @@ void _backlight_set_brightness(int val) void _backlight_hw_enable(bool on) { +#ifdef HAVE_LCD_SLEEP + if (on) + /* If the fade-out is interrupted, enabled will be true, but + lcd_awake() needs to be called anyways because the LCD + may be sleeping. + */ + lcd_awake(); +#endif + if (on == enabled) return; - + if (on) { GPIO_SET_BITWISE(GPIOB_OUTPUT_VAL, 0x08); diff --git a/firmware/target/arm/ipod/backlight-target.h b/firmware/target/arm/ipod/backlight-target.h index 50a7a0c7f1..28c519e4c0 100644 --- a/firmware/target/arm/ipod/backlight-target.h +++ b/firmware/target/arm/ipod/backlight-target.h @@ -29,6 +29,10 @@ void _backlight_led_on(void); void _backlight_led_off(void); void _backlight_hw_enable(bool on); +#ifdef HAVE_LCD_SLEEP +void lcd_awake(void); +#endif + #ifdef BOOTLOADER #define _backlight_on() do { _backlight_hw_enable(true); \ _backlight_led_on(); } while(0) diff --git a/firmware/target/arm/ipod/power-ipod.c b/firmware/target/arm/ipod/power-ipod.c index 4c6df882c6..66d703859c 100644 --- a/firmware/target/arm/ipod/power-ipod.c +++ b/firmware/target/arm/ipod/power-ipod.c @@ -130,7 +130,7 @@ bool ide_powered(void) void power_off(void) { -#if defined(HAVE_LCD_COLOR) +#if defined(HAVE_LCD_COLOR) && !defined(HAVE_LCD_SHUTDOWN) /* Clear the screen and backdrop to remove ghosting effect on shutdown */ lcd_set_backdrop(NULL); diff --git a/firmware/target/arm/ipod/video/lcd-video.c b/firmware/target/arm/ipod/video/lcd-video.c index 6e5523d72c..d1701ea3d7 100644 --- a/firmware/target/arm/ipod/video/lcd-video.c +++ b/firmware/target/arm/ipod/video/lcd-video.c @@ -30,6 +30,10 @@ #include "lcd.h" #include "kernel.h" #include "system.h" +#ifdef HAVE_LCD_SLEEP +/* Included only for lcd_awake() prototype */ +#include "backlight-target.h" +#endif /* The BCM bus width is 16 bits. But since the low address bits aren't decoded * by the chip (the 3 BCM address bits are mapped to address bits 16..18 of the @@ -50,12 +54,36 @@ #define BCM_ALT_RD_ADDR32 (*(volatile unsigned long *)(0x30060000)) #define BCM_ALT_CONTROL (*(volatile unsigned short*)(0x30070000)) -#define BCM_FB_BASE 0xE0020 /* Address of internal BCM framebuffer */ - /* Time until the BCM is considered stalled and will be re-kicked. * Must be guaranteed to be >~ 20ms. */ #define BCM_UPDATE_TIMEOUT (HZ/20) +/* Addresses within BCM */ +#define BCMA_SRAM_BASE 0 +#define BCMA_COMMAND 0x1F8 +#define BCMA_STATUS 0x1FC +#define BCMA_CMDPARAM 0xE0000 /* Parameters/data for commands */ +#define BCMA_SDRAM_BASE 0xC0000000 +#define BCMA_TV_FB 0xC0000000 /* TV out framebuffer */ +#define BCMA_TV_BMPDATA 0xC0200000 /* BMP data for TV out functions */ + +/* BCM commands. Write them to BCMA_COMMAND. Note BCM_CMD encoding. */ +#define BCM_CMD(x) ((~((unsigned long)x) << 16) | ((unsigned long)x)) +#define BCMCMD_LCD_UPDATE BCM_CMD(0) +/* Execute "M25 Diagnostics". Status displayed on LCD. Takes <40s */ +#define BCMCMD_SELFTEST BCM_CMD(1) +#define BCMCMD_TV_PALBMP BCM_CMD(2) +#define BCMCMD_TV_NTSCBMP BCM_CMD(3) +/* BCM_CMD(4) may be another TV-related command */ +/* The following might do more depending on word at 0xE00000 */ +#define BCMCMD_LCD_UPDATERECT BCM_CMD(5) +#define BCMCMD_LCD_SLEEP BCM_CMD(8) +/* BCM_CMD(12) involved in shutdown */ +/* Macrovision analog copy prevention is on by default on TV output. + Execute this command after enabling TV out to turn it off. + */ +#define BCMCMD_TV_MVOFF BCM_CMD(14) + enum lcd_status { LCD_IDLE, LCD_INITIAL, @@ -70,8 +98,60 @@ struct { #if NUM_CORES > 1 struct corelock cl; /* inter-core sync */ #endif +#ifdef HAVE_LCD_SLEEP + bool display_on; +#endif } lcd_state IBSS_ATTR; +#ifdef HAVE_LCD_SLEEP +static long bcm_off_wait; +const fb_data *flash_vmcs_offset; +unsigned flash_vmcs_length; +/* This mutex exists because enabling the backlight by changing a setting + will cause multiple concurrent lcd_wake() calls. + */ +static struct mutex lcdstate_lock SHAREDBSS_ATTR; + +#define ROM_BASE 0x20000000 +#define ROM_ID(a,b,c,d) (unsigned int)( ((unsigned int)(d)) | \ + (((unsigned int)(c)) << 8) | \ + (((unsigned int)(b)) << 16) | \ + (((unsigned int)(a)) << 24) ) + +/* Get address and length of iPod flash section. + Based on part of FS#6721. This may belong elsewhere. + (BCM initialization uploads the vmcs section to the BCM.) + */ +bool flash_get_section(const unsigned int imageid, + void **offset, + unsigned int *length) +{ + unsigned long *p = (unsigned long*)(ROM_BASE + 0xffe00); + unsigned char *csp, *csend; + unsigned long checksum; + + /* Find the image in the directory */ + while (1) { + if (p[0] != ROM_ID('f','l','s','h')) + return false; + if (p[1] == imageid) + break; + p += 10; + } + + *offset = (void *)(ROM_BASE + p[3]); + *length = p[4]; + + /* Verify checksum. Probably unnecessary, but it's fast. */ + checksum = 0; + csend = (unsigned char *)(ROM_BASE + p[3] + p[4]); + for(csp = (unsigned char *)(ROM_BASE + p[3]); csp < csend; csp++) { + checksum += *csp; + } + + return checksum == p[7]; +} +#endif /* HAVE_LCD_SLEEP */ static inline void bcm_write_addr(unsigned address) { @@ -99,16 +179,6 @@ static inline unsigned bcm_read32(unsigned address) return BCM_DATA32; /* read value */ } -static void bcm_setup_rect(unsigned x, unsigned y, - unsigned width, unsigned height) -{ - bcm_write_addr(0xE0004); - BCM_DATA32 = x; - BCM_DATA32 = y; - BCM_DATA32 = x + width - 1; - BCM_DATA32 = y + height - 1; -} - #ifndef BOOTLOADER static void lcd_tick(void) { @@ -119,15 +189,15 @@ static void lcd_tick(void) if (!lcd_state.blocked && lcd_state.state >= LCD_NEED_UPDATE) { - unsigned data = bcm_read32(0x1F8); - bool bcm_is_busy = (data == 0xFFFA0005 || data == 0xFFFF); + unsigned data = bcm_read32(BCMA_COMMAND); + bool bcm_is_busy = (data == BCMCMD_LCD_UPDATE || data == 0xFFFF); if (((lcd_state.state == LCD_NEED_UPDATE) && !bcm_is_busy) /* Update requested and BCM is no longer busy. */ || (TIME_AFTER(current_tick, lcd_state.update_timeout) && bcm_is_busy)) /* BCM still busy after timeout, i.e. stalled. */ { - bcm_write32(0x1F8, 0xFFFA0005); /* Kick off update */ + bcm_write32(BCMA_COMMAND, BCMCMD_LCD_UPDATE); /* Kick off update */ BCM_CONTROL = 0x31; lcd_state.update_timeout = current_tick + BCM_UPDATE_TIMEOUT; lcd_state.state = LCD_UPDATING; @@ -166,13 +236,13 @@ static void lcd_unblock_and_update(void) #if NUM_CORES > 1 corelock_lock(&lcd_state.cl); #endif - data = bcm_read32(0x1F8); - bcm_is_busy = (data == 0xFFFA0005 || data == 0xFFFF); + data = bcm_read32(BCMA_COMMAND); + bcm_is_busy = (data == BCMCMD_LCD_UPDATE || data == 0xFFFF); if (!bcm_is_busy || (lcd_state.state == LCD_INITIAL) || TIME_AFTER(current_tick, lcd_state.update_timeout)) { - bcm_write32(0x1F8, 0xFFFA0005); /* Kick off update */ + bcm_write32(BCMA_COMMAND, BCMCMD_LCD_UPDATE); /* Kick off update */ BCM_CONTROL = 0x31; lcd_state.update_timeout = current_tick + BCM_UPDATE_TIMEOUT; lcd_state.state = LCD_UPDATING; @@ -199,14 +269,14 @@ static void lcd_unblock_and_update(void) if (lcd_state.state != LCD_INITIAL) { - data = bcm_read32(0x1F8); - while (data == 0xFFFA0005 || data == 0xFFFF) + data = bcm_read32(BCMA_COMMAND); + while (data == BCMCMD_LCD_UPDATE || data == 0xFFFF) { yield(); - data = bcm_read32(0x1F8); + data = bcm_read32(BCMA_COMMAND); } } - bcm_write32(0x1F8, 0xFFFA0005); /* Kick off update */ + bcm_write32(BCMA_COMMAND, BCMCMD_LCD_UPDATE); /* Kick off update */ BCM_CONTROL = 0x31; lcd_state.state = LCD_IDLE; } @@ -236,12 +306,36 @@ void lcd_set_flip(bool yesno) /* LCD init */ void lcd_init_device(void) { - bcm_setup_rect(0, 0, LCD_WIDTH, LCD_HEIGHT); + /* These port initializations are supposed to be done when initializing + the BCM. None of it is changed when shutting down the BCM. + */ + GPO32_ENABLE |= 0x4000; + /* GPO32_VAL & 0x8000 may supply power for BCM sleep state */ + GPO32_ENABLE |= 0x8000; + GPO32_VAL &= ~0x8000; + GPIO_CLEAR_BITWISE(GPIOC_ENABLE, 0x80); + /* This pin is used for BCM interrupts */ + GPIOC_ENABLE |= 0x40; + GPIOC_OUTPUT_EN &= ~0x40; + GPO32_ENABLE &= ~1; + lcd_state.blocked = false; lcd_state.state = LCD_INITIAL; #ifndef BOOTLOADER #if NUM_CORES > 1 corelock_init(&lcd_state.cl); +#endif +#ifdef HAVE_LCD_SLEEP + lcd_state.display_on = true; /* Code in flash turned it on */ + if (!flash_get_section(ROM_ID('v', 'm', 'c', 's'), + (void **)(&flash_vmcs_offset), &flash_vmcs_length)) + /* BCM cannot be shut down because firmware wasn't found */ + flash_vmcs_length = 0; + else { + /* lcd_write_data needs an even number of 16 bit values */ + flash_vmcs_length = ((flash_vmcs_length + 3) >> 1) & ~1; + } + mutex_init(&lcdstate_lock); #endif tick_add_task(&lcd_tick); #endif /* !BOOTLOADER */ @@ -255,6 +349,11 @@ void lcd_update_rect(int x, int y, int width, int height) const fb_data *addr; unsigned bcmaddr; +#ifdef HAVE_LCD_SLEEP + if (!lcd_state.display_on) + return; +#endif + if (x + width >= LCD_WIDTH) width = LCD_WIDTH - x; if (y + height >= LCD_HEIGHT) @@ -272,7 +371,7 @@ void lcd_update_rect(int x, int y, int width, int height) lcd_block_tick(); addr = &lcd_framebuffer[y][x]; - bcmaddr = BCM_FB_BASE + (LCD_WIDTH*2) * y + (x << 1); + bcmaddr = BCMA_CMDPARAM + (LCD_WIDTH*2) * y + (x << 1); if (width == LCD_WIDTH) { @@ -315,6 +414,11 @@ void lcd_blit_yuv(unsigned char * const src[3], off_t z; unsigned char const * yuv_src[3]; +#ifdef HAVE_LCD_SLEEP + if (!lcd_state.display_on) + return; +#endif + /* Sorry, but width and height must be >= 2 or else */ width &= ~1; @@ -326,7 +430,7 @@ void lcd_blit_yuv(unsigned char * const src[3], /* Prevent the tick from triggering BCM updates while we're writing. */ lcd_block_tick(); - bcmaddr = BCM_FB_BASE + (LCD_WIDTH*2) * y + (x << 1); + bcmaddr = BCMA_CMDPARAM + (LCD_WIDTH*2) * y + (x << 1); height >>= 1; do @@ -341,3 +445,165 @@ void lcd_blit_yuv(unsigned char * const src[3], lcd_unblock_and_update(); } + +#ifdef HAVE_LCD_SLEEP +/* Executes a BCM command immediately and waits for it to complete. + Other BCM commands (eg. LCD updates or lcd_tick) must not interfere. + */ +static void bcm_command(unsigned cmd) { + unsigned status; + + bcm_write32(BCMA_COMMAND, cmd); + + BCM_CONTROL = 0x31; + + while (1) { + status = bcm_read32(BCMA_COMMAND); + if (status != cmd && status != 0xFFFF) + break; + yield(); + } +} + +void bcm_powerdown(void) +{ + bcm_write32(0x10001400, bcm_read32(0x10001400) & ~0xF0); + + /* Blanks the LCD and decreases power consumption + below what clearing the LCD would achieve. + Executing an LCD update command wakes it. + */ + bcm_command(BCMCMD_LCD_SLEEP); + + /* Not sure if this does anything */ + bcm_command(BCM_CMD(0xC)); + + /* Further cuts power use, probably by powering down BCM. + After this point, BCM needs to be bootstrapped + */ + GPO32_VAL &= ~0x4000; +} + +/* Data written to BCM_CONTROL and BCM_ALT_CONTROL */ +const unsigned char bcm_bootstrapdata[] = { + 0xA1, 0x81, 0x91, 0x02, 0x12, 0x22, 0x72, 0x62 +}; + +void bcm_init(void) +{ + int i; + + /* Power up BCM */ + GPO32_VAL |= 0x4000; + + /* Changed from HZ/2 to speed up this function */ + sleep(HZ/8); + + /* Bootstrap stage 1 */ + + STRAP_OPT_A &= ~0xF00; + outl(0x1313, 0x70000040); + + /* Interrupt-related code for future use + GPIOC_INT_LEV |= 0x40; + GPIOC_INT_EN |= 0x40; + CPU_HI_INT_EN |= 0x40000; + */ + + /* Bootstrap stage 2 */ + + for (i = 0; i < 8; i++) { + BCM_CONTROL = bcm_bootstrapdata[i]; + } + + for (i = 3; i < 8; i++) { + BCM_ALT_CONTROL = bcm_bootstrapdata[i]; + } + + while ((BCM_RD_ADDR & 1) == 0 || (BCM_ALT_RD_ADDR & 1) == 0) + yield(); + + (void)BCM_WR_ADDR; + (void)BCM_ALT_WR_ADDR; + + /* Bootstrap stage 3: upload firmware */ + + while (BCM_ALT_CONTROL & 0x80) + yield(); + + while (!(BCM_ALT_CONTROL & 0x40)) + yield(); + + /* Upload firmware to BCM SRAM */ + bcm_write_addr(BCMA_SRAM_BASE); + lcd_write_data(flash_vmcs_offset, flash_vmcs_length); + + bcm_write32(BCMA_COMMAND, 0); + bcm_write32(0x10000C00, 0xC0000000); + + while (!(bcm_read32(0x10000C00) & 1)) + yield(); + + bcm_write32(0x10000C00, 0); + bcm_write32(0x10000400, 0xA5A50002); + + while (bcm_read32(BCMA_COMMAND) == 0) + yield(); + + /* sleep(HZ/2) apparently unneeded */ +} + +void lcd_awake(void) +{ + mutex_lock(&lcdstate_lock); + if (!lcd_state.display_on && flash_vmcs_length != 0) + { + /* Ensure BCM has been off for 1/2 s at least */ + while (!TIME_AFTER(current_tick, lcd_state.update_timeout)) + yield(); + + bcm_init(); + + /* Update LCD, but don't use lcd_update(). Instead, wait here + until the command completes so LCD isn't white when the + backlight turns on + */ + bcm_write_addr(BCMA_CMDPARAM); + lcd_write_data(&(lcd_framebuffer[0][0]), LCD_WIDTH * LCD_HEIGHT); + bcm_command(BCMCMD_LCD_UPDATE); + + lcd_state.state = LCD_INITIAL; + tick_add_task(&lcd_tick); + lcd_state.display_on = true; + /* Note that only the RGB data from lcd_framebuffer has been + displayed. If YUV data was displayed, it needs to be updated + now. (eg. see lcd_call_enable_hook()) + */ + } + mutex_unlock(&lcdstate_lock); +} + +void lcd_sleep(void) +{ + mutex_lock(&lcdstate_lock); + if (lcd_state.display_on && flash_vmcs_length != 0) { + lcd_state.display_on = false; + + /* Wait for BCM to finish work */ + while (lcd_state.state != LCD_INITIAL && lcd_state.state != LCD_IDLE) + yield(); + + tick_remove_task(&lcd_tick); + bcm_powerdown(); + bcm_off_wait = current_tick + HZ/2; + } + mutex_unlock(&lcdstate_lock); +} + +#ifdef HAVE_LCD_SHUTDOWN +void lcd_shutdown(void) +{ + lcd_sleep(); +} +#endif /* HAVE_LCD_SHUTDOWN */ +#endif /* HAVE_LCD_SLEEP */ -- cgit v1.2.3