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 */