/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id: lcd-nano2g.c 28868 2010-12-21 06:59:17Z Buschel $ * * Copyright (C) 2009 by Dave Chapman * * 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 #include #include #include "config.h" #include "kernel.h" #include "system.h" #include "cpu.h" #include "lcd.h" #ifdef IPOD_6G #include "pmu-target.h" #endif #include "backlight-target.h" #include "s5l87xx.h" #include "clocking-s5l8702.h" #include "dma-s5l8702.h" #include "lcd-s5l8702.h" #include "lcd-target.h" // TODO TODO TODO: HAVE_LCD_ENABLE /* Switch command/frame mode: * * XXX: WIP, TBC * * Configures MPU interface and pixel mode. * * Frame mode: * 6g: * 16-bit 8080-series parallel MPU interface * pixel mode: RBG565/1-transfer * APB[15:0] -> D[17:10,8:1] * nano3g: * 9-bit 8080-series parallel PMU interface (TBC) * pixel mode: RGB565/2-transfers (TBC) * APB[15:0] -> D[8:1] + D[8:1] (TBC) * * Command mode: * LCD HW that uses 8-bit command set (6g, nano3g): * 8-bit 8080-series parallel MPU interface * APB[7:0] -> D[8:1] * LCD HW that uses 16-bit command set (6g): * 18-bit 8080-series parallel MPU interface (TBC) * APB[15:0] -> D[17:10,8:1] * * See ILI9320, ILI9326, ILI9340 DS. */ /* XXX: see ili9340, pg.27 for MCU interface selection, * bit31 could be the one that selects interface D[7:0] or D[8:1] for 8-bit), Type-I uses D[7:0] and Type-II uses D[17:10] (ILI9340) * 9-bit D[8:0] D[17:9] * 16-bit D[15:0] D[17:10],D[8:1] * 18-bit D[17:0] D[17:0] * bit24 could be the one that selects interface D[8:1] or D[17:10] * MCU_interface Mode Pins Write Read * #define LCD_MODE_8 0x80000c20 8080 MCU 8-bit bus iface D[8:1] APB[7:0] -> D[8:1] D[8:1] -> LCD_DBUFF[8:1] * #define LCD_MODE_9 0x81100db8 9-bit * #define LCD_MODE_16 0x80100db0 16-bit D[17:10,8:1] APB[15:0] -> D[17:10,8:1] * #define LCD_MODE_18 0x80000da8 18-bit D[17:0] APB[17:0] -> D[17:0] (TBC) * * #define LCD_MODE_8 0x80000c20 // LCD_MODE_P8T1 // paralled 8-bit, 1 transfer, TBC: DB[17:10] or DB[8:1] ??? * #define LCD_MODE_9 0x81100db8 // LCD_MODE_P9T2 // TBC: DB[8:1] or DB[17:10] ???, 2-transfers or 1.5-transfers, RGB565 or RBG666 ??? * #define LCD_MODE_16 0x80100db0 // LCD_MODE_P16T1 // TBC: DB[17:10,8:1] * #define LCD_MODE_18 0x80000da8 // LCD_MODE_P18T1 // TBC: DB[17:10,8:1] */ #define LCD_MODE_P8 0x80000c20 // LCD_MODE_P8T1 // paralled 8-bit, 1 transfer, TBC: DB[17:10] or DB[8:1] ??? #define LCD_MODE_P8b 0x80100c20 // TODO: see if it influences, so far we are using it in nano3g with 0x80000c20 and it seems to be working fine // It may be a "pixel format" setting for 8 bit, which would only affect DATA and not CMD ??? #define LCD_MODE_P9 0x81100db8 // LCD_MODE_P9T2 // TBC: DB[8:1] or DB[17:10] ???, 2-transfers or 1.5-transfers, RGB565 or RBG666 ??? #define LCD_MODE_P16 0x80100db0 // LCD_MODE_P16T1 // TBC: DB[17:10,8:1] #define LCD_MODE_P18 0x80000da8 // LCD_MODE_P18T1 // TBC: DB[17:10,8:1] #define LCD_MODE_S8 0x41000c20 // nano4g #define LCD_MODE_S9 0x41100db8 // nano4g #ifdef S5L_LCD_WITH_CMDSET16 /* LCD type 16-bit register defines */ #define R_HORIZ_GRAM_ADDR_SET 0x200 #define R_VERT_GRAM_ADDR_SET 0x201 #define R_WRITE_DATA_TO_GRAM 0x202 #define R_HORIZ_ADDR_START_POS 0x210 #define R_HORIZ_ADDR_END_POS 0x211 #define R_VERT_ADDR_START_POS 0x212 #define R_VERT_ADDR_END_POS 0x213 #endif /* LCD type 8-bit register defines */ #define R_COLUMN_ADDR_SET 0x2a #define R_ROW_ADDR_SET 0x2b #define R_MEMORY_WRITE 0x2c /** globals **/ int lcd_type; /* also needed in debug-s5l8702.c */ static struct mutex lcd_mutex; static uint16_t lcd_dblbuf[LCD_HEIGHT][LCD_WIDTH] CACHEALIGN_ATTR; static bool lcd_ispowered; /* TODO: there is no info about these specific drivers, some useful info on similar HW such as ILI9320, ILI9326, ILI9340 and */ static struct lcd_info_rec *lcd_info; static void (*lcd_run_seq)(void*); /* LCD_CONFIG values for command/frame modes */ static uint32_t lcd_cmd_mode IDATA_ATTR; static uint32_t lcd_frame_mode IDATA_ATTR; // TODO /* Each target must define a list containing all supported LCD types */ // extern struct lcd_info_rec lcd_info_list[]; // static struct lcd_info_rec lcd_info_list[]; /* DMA configuration */ /* One single transfer at once, needed LLIs: * screen_size / (DMAC_LLI_MAX_COUNT << swidth) = * (320*240*2) / (4095*2) = 19 */ #define LCD_DMA_TSKBUF_SZ 1 /* N tasks, MUST be pow2 */ #define LCD_DMA_LLIBUF_SZ 32 /* N LLIs, MUST be pow2 */ static struct dmac_tsk lcd_dma_tskbuf[LCD_DMA_TSKBUF_SZ]; static struct dmac_lli volatile \ lcd_dma_llibuf[LCD_DMA_LLIBUF_SZ] CACHEALIGN_ATTR; static struct dmac_ch lcd_dma_ch = { .dmac = &s5l8702_dmac0, .prio = DMAC_CH_PRIO(4), .cb_fn = NULL, .tskbuf = lcd_dma_tskbuf, .tskbuf_mask = LCD_DMA_TSKBUF_SZ - 1, .queue_mode = QUEUE_NORMAL, .llibuf = lcd_dma_llibuf, .llibuf_mask = LCD_DMA_LLIBUF_SZ - 1, .llibuf_bus = DMAC_MASTER_AHB1, }; static struct dmac_ch_cfg lcd_dma_ch_cfg = { .srcperi = S5L8702_DMAC0_PERI_MEM, .dstperi = S5L8702_DMAC0_PERI_LCD_WR, .sbsize = DMACCxCONTROL_BSIZE_1, .dbsize = DMACCxCONTROL_BSIZE_1, .swidth = DMACCxCONTROL_WIDTH_16, .dwidth = DMACCxCONTROL_WIDTH_16, .sbus = DMAC_MASTER_AHB1, .dbus = DMAC_MASTER_AHB1, .sinc = DMACCxCONTROL_INC_ENABLE, .dinc = DMACCxCONTROL_INC_DISABLE, .prot = DMAC_PROT_CACH | DMAC_PROT_BUFF | DMAC_PROT_PRIV, .lli_xfer_max_count = DMAC_LLI_MAX_COUNT, }; /*** clocks ***/ // TODO: In mks5lboot --mkraw put a command to specify the address of the binary, // for example --address 0x6000, it must be greater than 0x310 which would be the default address, // pass this address (0x310..128Kb) in the dfu_options flag // TODO: to lcd-target.c static void lcd_target_enable_clocks(bool enable) { clockgate_enable(CLOCKGATE_LCD, enable); #ifdef IPOD_NANO4G clockgate_enable(CLOCKGATE_LCD_2, enable); #endif } /*** LCD controller - low level functions ***/ static void s5l_lcd_write_config(uint32_t config) ICODE_ATTR; static void s5l_lcd_write_config(uint32_t config) { while (!(LCD_STATUS & 0x2)); udelay(1); LCD_CON = config; } static void s5l_lcd_write_cmd(uint16_t cmd) ICODE_ATTR; static void s5l_lcd_write_cmd(uint16_t cmd) { while (LCD_STATUS & 0x10); LCD_WCMD = cmd; } static void s5l_lcd_write_data(uint16_t data) ICODE_ATTR; static void s5l_lcd_write_data(uint16_t data) { while (LCD_STATUS & 0x10); LCD_WDATA = data; } static void s5l_lcd_send_cmd8(uint8_t cmd, int len, uint8_t *data) ICODE_ATTR; static void s5l_lcd_send_cmd8(uint8_t cmd, int len, uint8_t *data) { s5l_lcd_write_cmd(cmd); while (len--) s5l_lcd_write_data(*data++); } #ifdef S5L_LCD_WITH_READID static void s5l_lcd_recv_cmd8(uint8_t cmd, int len, uint8_t *buf) { s5l_lcd_write_cmd(cmd); while (len--) { while (!(LCD_STATUS & 0x2)); LCD_RDATA = 0; while (!(LCD_STATUS & 1)); *buf++ = LCD_DBUFF >> 1; } } #endif #define s5l_lcd_set_command_mode() s5l_lcd_write_config(lcd_cmd_mode) #define s5l_lcd_set_frame_mode() s5l_lcd_write_config(lcd_frame_mode) static void s5l_lcd_run_seq8(void *seq8) { uint8_t *seq = seq8; while (1) switch (*seq++) { case CMD: { uint8_t cmd = *seq++; int len = *seq++; s5l_lcd_send_cmd8(cmd, len, seq); seq += len; break; } case SLEEP: sleep(*seq++); break; case END: default: /* bye */ return; } } #ifdef S5L_LCD_WITH_CMDSET16 static void s5l_lcd_write_reg(uint16_t cmd, uint16_t data) ICODE_ATTR; static void s5l_lcd_write_reg(uint16_t cmd, uint16_t data) { s5l_lcd_write_cmd(cmd); s5l_lcd_write_data(data); } static void s5l_lcd_run_seq16(void *seq16) { uint16_t *seq = seq16; int action, param; uint16_t reg; while (1) { action = *seq & 0xff; param = *seq++ >> 8; switch (action) { case CMD: s5l_lcd_write_cmd(*seq++); break; case MREG: reg = *seq++; while (param--) s5l_lcd_write_reg(reg++, *seq++); break; case SLEEP: sleep(param); break; case DELAY: udelay(param<<6); break; case END: default: /* bye */ return; } } } #endif /* S5L_LCD_WITH_CMDSET16 */ /*** Update functions ***/ /* 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) { lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT); } static void displaylcd_setup(int x, int y, int width, int height) ICODE_ATTR; static void displaylcd_setup(int x, int y, int width, int height) { int xe = (x + width) - 1; /* max horiz */ int ye = (y + height) - 1; /* max vert */ s5l_lcd_set_command_mode(); #ifdef S5L_LCD_WITH_CMDSET16 if (lcd_info->cmdset == LCD_CMDSET_16BIT) { s5l_lcd_write_reg(R_HORIZ_ADDR_START_POS, x); s5l_lcd_write_reg(R_HORIZ_ADDR_END_POS, xe); s5l_lcd_write_reg(R_VERT_ADDR_START_POS, y); s5l_lcd_write_reg(R_VERT_ADDR_END_POS, ye); s5l_lcd_write_reg(R_HORIZ_GRAM_ADDR_SET, x); s5l_lcd_write_reg(R_VERT_GRAM_ADDR_SET, y); s5l_lcd_write_cmd(R_WRITE_DATA_TO_GRAM); } else #endif { uint8_t col[] = { x >> 8, x & 0xff, xe >> 8, xe & 0xff }; uint8_t row[] = { y >> 8, y & 0xff, ye >> 8, ye & 0xff }; s5l_lcd_send_cmd8(R_COLUMN_ADDR_SET, 4, col); s5l_lcd_send_cmd8(R_ROW_ADDR_SET, 4, row); s5l_lcd_write_cmd(R_MEMORY_WRITE); } } static void displaylcd_dma(int pixels) ICODE_ATTR; static void displaylcd_dma(int pixels) { s5l_lcd_set_frame_mode(); commit_dcache(); dmac_ch_queue(&lcd_dma_ch, lcd_dblbuf, (void*)S5L8702_DADDR_PERI_LCD_WR, pixels*2, NULL); } // TODO: wait if there is a DMA transfer in progress static void displaylcd_wait_dma(void) ICODE_ATTR; static void displaylcd_wait_dma(void) { while (dmac_ch_running(&lcd_dma_ch)) yield(); } /* Update a fraction of the display. */ void lcd_update_rect(int, int, int, int) ICODE_ATTR; void lcd_update_rect(int x, int y, int width, int height) { int pixels = width * height; fb_data* p = FBADDR(x,y); uint16_t* out = lcd_dblbuf[0]; /* FIXME: ISR()->panicf()->lcd_update() blocks forever */ mutex_lock(&lcd_mutex); if (lcd_ispowered) { // XXX: We contemplate the case where the LCD has entered SLEEP, // the s5l_lcd_set_command_mode() writes LCD_CONFIG // XXX: It is also possible that if the LCD clockgate has been disabled, // the LCD_CONFIG = xxx may still be blocked by the while(status == ok), // since it does not check the same status bit as when writing command/data displaylcd_wait_dma(); displaylcd_setup(x, y, width, height); /* Copy display bitmap to hardware */ if (LCD_WIDTH == width) { /* Write all lines at once */ memcpy(out, p, pixels * 2); } else { do { /* Write a single line */ memcpy(out, p, width * 2); p += LCD_WIDTH; out += width; } while (--height); } displaylcd_dma(pixels); } mutex_unlock(&lcd_mutex); } /* 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], uint16_t* outbuf, int width, int stride); /* 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) ICODE_ATTR; 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 int z; unsigned char const * yuv_src[3]; width = (width + 1) & ~1; /* ensure width is even */ int pixels = width * height; uint16_t* out = lcd_dblbuf[0]; 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]); /* TODO: ISR()->panicf()->lcd_update() blocks forever */ mutex_lock(&lcd_mutex); if (lcd_ispowered) { displaylcd_wait_dma(); displaylcd_setup(x, y, width, height); height >>= 1; do { lcd_write_yuv420_lines(yuv_src, out, width, stride); yuv_src[0] += stride << 1; yuv_src[1] += stride >> 1; /* Skip down one chroma line */ yuv_src[2] += stride >> 1; out += width << 1; } while (--height); displaylcd_dma(pixels); } mutex_unlock(&lcd_mutex); } /*** hardware configuration ***/ int lcd_default_contrast(void) { return 0x1f; } void lcd_set_contrast(int val) { (void)val; } void lcd_set_invert_display(bool yesno) { (void)yesno; } void lcd_set_flip(bool yesno) { (void)yesno; } #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) bool lcd_active(void) { return lcd_ispowered; } #endif #if defined(HAVE_LCD_SHUTDOWN) || defined(HAVE_LCD_SLEEP) static void lcd_powersave(void) { mutex_lock(&lcd_mutex); displaylcd_wait_dma(); // XXX: Do not change modes while data is being sent s5l_lcd_set_command_mode(); lcd_run_seq(lcd_info->seq_sleep); lcd_target_enable_clocks(false); lcd_ispowered = false; mutex_unlock(&lcd_mutex); } #endif /* HAVE_LCD_SHUTDOWN || HAVE_LCD_SLEEP */ #ifdef HAVE_LCD_SHUTDOWN void lcd_shutdown(void) { backlight_hw_kill(); /* Kill the backlight, instantly. */ lcd_powersave(); } #endif #ifdef HAVE_LCD_SLEEP void lcd_sleep(void) { lcd_powersave(); } void lcd_awake(void) { mutex_lock(&lcd_mutex); lcd_target_enable_clocks(true); // XXX: Do not change modes while data is being sent s5l_lcd_set_command_mode(); lcd_run_seq(lcd_info->seq_awake); lcd_ispowered = true; // XXX: we have to put the lcd_ispowered before the lcd_update() lcd_update(); // XXX: update the display and wait for the update to finish before returning, displaylcd_wait_dma(); // we should really do sleep_out + lcd_update + display_on, // we can put a command in the sequence to do it mutex_unlock(&lcd_mutex); send_event(LCD_EVENT_ACTIVATION, NULL); } #endif #ifdef S5L_LCD_WITH_READID // TODO: protect it with mutex and while(dma) if you want to call it from other places (e.g. DEBUG) void lcd_read_display_id(int mpuiface, uint8_t *lcd_id) { s5l_lcd_write_config( (mpuiface == LCD_MPUIFACE_SERIAL) ? LCD_MODE_S8 : LCD_MODE_P8); s5l_lcd_recv_cmd8(4, 4, lcd_id); } #endif /* LCD init */ void lcd_init_device(void) { mutex_init(&lcd_mutex); lcd_target_enable_clocks(true); #if defined(IPOD_6G) || defined(IPOD_NANO3G) LCD_PHTIME = 0x33; #elif defined(IPOD_NANO4G) && defined(BOOTLOADER) cg16_config(&CG16_LCD, true, CG16_SEL_PLL0, 16, 1, 0x0); s5l_lcd_write_config(LCD_MODE_S9); cg16_config(&CG16_LCD, false, CG16_SEL_PLL0, 16, 1, 0x0); LCD_PHTIME = 0x11; #endif lcd_info = lcd_target_get_info(); lcd_type = lcd_info->lcd_type; /* select mode to send commands */ #ifdef S5L_LCD_WITH_CMDSET16 if (lcd_info->cmdset == LCD_CMDSET_16BIT) { lcd_cmd_mode = LCD_MODE_P18; lcd_run_seq = s5l_lcd_run_seq16; } else /* LCD_CMDSET_8BIT */ #endif { if (lcd_info->mpuiface == LCD_MPUIFACE_SERIAL) lcd_cmd_mode = LCD_MODE_S8; else lcd_cmd_mode = LCD_MODE_P8; lcd_run_seq = s5l_lcd_run_seq8; } /* select mode to send RGB565 data */ if (lcd_info->mpuiface == LCD_MPUIFACE_PAR9) lcd_frame_mode = LCD_MODE_P9; else if (lcd_info->mpuiface == LCD_MPUIFACE_PAR18) lcd_frame_mode = LCD_MODE_P16; else /* LCD_MPUIFACE_SERIAL */ lcd_frame_mode = LCD_MODE_S9; s5l_lcd_set_command_mode(); /* Configure DMA channel */ // TODO: this right after mutex_init() dmac_ch_init(&lcd_dma_ch, &lcd_dma_ch_cfg); #ifdef BOOTLOADER lcd_run_seq(lcd_info->seq_init); #endif lcd_ispowered = true; }