| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * LCD driver for iPod Video |
| * |
| * Based on code from the ipodlinux project - http://ipodlinux.org/ |
| * Adapted for Rockbox in December 2005 |
| * |
| * Original file: linux/arch/armnommu/mach-ipod/fb.c |
| * |
| * Copyright (c) 2003-2005 Bernard Leach (leachbj@bouncycastle.org) |
| * |
| * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| #include "config.h" |
| #include "cpu.h" |
| #include "lcd.h" |
| #include "kernel.h" |
| #include "system.h" |
| |
| /* 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 |
| * PP5022), writing 32 bits (and even more, using 'stmia') at once works. */ |
| #define BCM_DATA (*(volatile unsigned short*)(0x30000000)) |
| #define BCM_DATA32 (*(volatile unsigned long *)(0x30000000)) |
| #define BCM_WR_ADDR (*(volatile unsigned short*)(0x30010000)) |
| #define BCM_WR_ADDR32 (*(volatile unsigned long *)(0x30010000)) |
| #define BCM_RD_ADDR (*(volatile unsigned short*)(0x30020000)) |
| #define BCM_RD_ADDR32 (*(volatile unsigned long *)(0x30020000)) |
| #define BCM_CONTROL (*(volatile unsigned short*)(0x30030000)) |
| |
| #define BCM_ALT_DATA (*(volatile unsigned short*)(0x30040000)) |
| #define BCM_ALT_DATA32 (*(volatile unsigned long *)(0x30040000)) |
| #define BCM_ALT_WR_ADDR (*(volatile unsigned short*)(0x30050000)) |
| #define BCM_ALT_WR_ADDR32 (*(volatile unsigned long *)(0x30050000)) |
| #define BCM_ALT_RD_ADDR (*(volatile unsigned short*)(0x30060000)) |
| #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) |
| |
| enum lcd_status { |
| LCD_IDLE, |
| LCD_INITIAL, |
| LCD_NEED_UPDATE, |
| LCD_UPDATING |
| }; |
| |
| struct { |
| long update_timeout; |
| enum lcd_status state; |
| bool blocked; |
| #if NUM_CORES > 1 |
| struct corelock cl; /* inter-core sync */ |
| #endif |
| } lcd_state IBSS_ATTR; |
| |
| |
| static inline void bcm_write_addr(unsigned address) |
| { |
| BCM_WR_ADDR32 = address; /* write destination address */ |
| |
| while (!(BCM_CONTROL & 0x2)); /* wait for it to be write ready */ |
| } |
| |
| static inline void bcm_write32(unsigned address, unsigned value) |
| { |
| |
| bcm_write_addr(address); /* set destination address */ |
| |
| BCM_DATA32 = value; /* write value */ |
| } |
| |
| static inline unsigned bcm_read32(unsigned address) |
| { |
| while (!(BCM_RD_ADDR & 1)); |
| |
| BCM_RD_ADDR32 = address; /* write source address */ |
| |
| while (!(BCM_CONTROL & 0x10)); /* wait for it to be read ready */ |
| |
| 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) |
| { |
| /* No core level interrupt mask - already in interrupt context */ |
| #if NUM_CORES > 1 |
| corelock_lock(&lcd_state.cl); |
| #endif |
| |
| if (!lcd_state.blocked && lcd_state.state >= LCD_NEED_UPDATE) |
| { |
| unsigned data = bcm_read32(0x1F8); |
| bool bcm_is_busy = (data == 0xFFFA0005 || 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_CONTROL = 0x31; |
| lcd_state.update_timeout = current_tick + BCM_UPDATE_TIMEOUT; |
| lcd_state.state = LCD_UPDATING; |
| } |
| else if ((lcd_state.state == LCD_UPDATING) && !bcm_is_busy) |
| { |
| /* Update finished properly and no new update pending. */ |
| lcd_state.state = LCD_IDLE; |
| } |
| } |
| #if NUM_CORES > 1 |
| corelock_unlock(&lcd_state.cl); |
| #endif |
| } |
| |
| static inline void lcd_block_tick(void) |
| { |
| int oldlevel = disable_irq_save(); |
| |
| #if NUM_CORES > 1 |
| corelock_lock(&lcd_state.cl); |
| lcd_state.blocked = true; |
| corelock_unlock(&lcd_state.cl); |
| #else |
| lcd_state.blocked = true; |
| #endif |
| restore_irq(oldlevel); |
| } |
| |
| static void lcd_unblock_and_update(void) |
| { |
| unsigned data; |
| bool bcm_is_busy; |
| int oldlevel = disable_irq_save(); |
| |
| #if NUM_CORES > 1 |
| corelock_lock(&lcd_state.cl); |
| #endif |
| data = bcm_read32(0x1F8); |
| bcm_is_busy = (data == 0xFFFA0005 || 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_CONTROL = 0x31; |
| lcd_state.update_timeout = current_tick + BCM_UPDATE_TIMEOUT; |
| lcd_state.state = LCD_UPDATING; |
| } |
| else |
| { |
| lcd_state.state = LCD_NEED_UPDATE; /* Post update request */ |
| } |
| lcd_state.blocked = false; |
| |
| #if NUM_CORES > 1 |
| corelock_unlock(&lcd_state.cl); |
| #endif |
| restore_irq(oldlevel); |
| } |
| |
| #else /* BOOTLOADER */ |
| |
| #define lcd_block_tick() |
| |
| static void lcd_unblock_and_update(void) |
| { |
| unsigned data; |
| |
| if (lcd_state.state != LCD_INITIAL) |
| { |
| data = bcm_read32(0x1F8); |
| while (data == 0xFFFA0005 || data == 0xFFFF) |
| { |
| yield(); |
| data = bcm_read32(0x1F8); |
| } |
| } |
| bcm_write32(0x1F8, 0xFFFA0005); /* Kick off update */ |
| BCM_CONTROL = 0x31; |
| lcd_state.state = LCD_IDLE; |
| } |
| #endif /* BOOTLOADER */ |
| |
| /*** hardware configuration ***/ |
| |
| void lcd_set_contrast(int val) |
| { |
| /* TODO: Implement lcd_set_contrast() */ |
| (void)val; |
| } |
| |
| void lcd_set_invert_display(bool yesno) |
| { |
| /* TODO: Implement lcd_set_invert_display() */ |
| (void)yesno; |
| } |
| |
| /* turn the display upside down (call lcd_update() afterwards) */ |
| void lcd_set_flip(bool yesno) |
| { |
| /* TODO: Implement lcd_set_flip() */ |
| (void)yesno; |
| } |
| |
| /* LCD init */ |
| void lcd_init_device(void) |
| { |
| bcm_setup_rect(0, 0, LCD_WIDTH, LCD_HEIGHT); |
| lcd_state.blocked = false; |
| lcd_state.state = LCD_INITIAL; |
| #ifndef BOOTLOADER |
| #if NUM_CORES > 1 |
| corelock_init(&lcd_state.cl); |
| #endif |
| tick_add_task(&lcd_tick); |
| #endif /* !BOOTLOADER */ |
| } |
| |
| /*** update functions ***/ |
| |
| /* Update a fraction of the display. */ |
| void lcd_update_rect(int x, int y, int width, int height) |
| { |
| const fb_data *addr; |
| unsigned bcmaddr; |
| |
| if (x + width >= LCD_WIDTH) |
| width = LCD_WIDTH - x; |
| if (y + height >= LCD_HEIGHT) |
| height = LCD_HEIGHT - y; |
| |
| if ((width <= 0) || (height <= 0)) |
| return; /* Nothing left to do. */ |
| |
| /* Ensure x and width are both even. The BCM doesn't like small unaligned |
| * writes and would just ignore them. */ |
| width = (width + (x & 1) + 1) & ~1; |
| x &= ~1; |
| |
| /* Prevent the tick from triggering BCM updates while we're writing. */ |
| lcd_block_tick(); |
| |
| addr = &lcd_framebuffer[y][x]; |
| bcmaddr = BCM_FB_BASE + (LCD_WIDTH*2) * y + (x << 1); |
| |
| if (width == LCD_WIDTH) |
| { |
| bcm_write_addr(bcmaddr); |
| lcd_write_data(addr, width * height); |
| } |
| else |
| { |
| do |
| { |
| bcm_write_addr(bcmaddr); |
| bcmaddr += (LCD_WIDTH*2); |
| lcd_write_data(addr, width); |
| addr += LCD_WIDTH; |
| } |
| while (--height > 0); |
| } |
| lcd_unblock_and_update(); |
| } |
| |
| /* Update the display. |
| This must be called after all other LCD functions that change the display. */ |
| void lcd_update(void) |
| { |
| lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT); |
| } |
| |
| /* Line write helper function for lcd_yuv_blit. Writes two lines of yuv420. */ |
| extern void lcd_write_yuv420_lines(unsigned char const * const src[3], |
| unsigned bcmaddr, |
| int width, |
| int stride); |
| |
| /* Performance function to blit a YUV bitmap directly to the LCD */ |
| void lcd_blit_yuv(unsigned char * const src[3], |
| int src_x, int src_y, int stride, |
| int x, int y, int width, int height) |
| { |
| unsigned bcmaddr; |
| off_t z; |
| unsigned char const * yuv_src[3]; |
| |
| /* Sorry, but width and height must be >= 2 or else */ |
| width &= ~1; |
| |
| z = stride * src_y; |
| yuv_src[0] = src[0] + z + src_x; |
| yuv_src[1] = src[1] + (z >> 2) + (src_x >> 1); |
| yuv_src[2] = src[2] + (yuv_src[1] - src[1]); |
| |
| /* Prevent the tick from triggering BCM updates while we're writing. */ |
| lcd_block_tick(); |
| |
| bcmaddr = BCM_FB_BASE + (LCD_WIDTH*2) * y + (x << 1); |
| height >>= 1; |
| |
| do |
| { |
| lcd_write_yuv420_lines(yuv_src, bcmaddr, width, stride); |
| bcmaddr += (LCD_WIDTH*4); /* Skip up two lines */ |
| yuv_src[0] += stride << 1; |
| yuv_src[1] += stride >> 1; /* Skip down one chroma line */ |
| yuv_src[2] += stride >> 1; |
| } |
| while (--height > 0); |
| |
| lcd_unblock_and_update(); |
| } |