| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (c) 2013 by Amaury Pouly |
| * |
| * 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 <sys/types.h> /* off_t */ |
| #include <string.h> |
| #include "cpu.h" |
| #include "system.h" |
| #include "backlight-target.h" |
| #include "lcd.h" |
| #include "lcdif-imx233.h" |
| #include "clkctrl-imx233.h" |
| #include "pinctrl-imx233.h" |
| #include "dma-imx233.h" |
| #include "regs/regs-uartdbg.h" |
| #include "logf.h" |
| #ifndef BOOTLOADER |
| #include "button.h" |
| #include "font.h" |
| #include "action.h" |
| #endif |
| |
| #ifdef HAVE_LCD_ENABLE |
| static bool lcd_on; |
| #endif |
| |
| /** |
| * DMA |
| */ |
| |
| /* Used for DMA */ |
| struct lcdif_dma_command_t |
| { |
| struct apb_dma_command_t dma; |
| uint32_t pad; |
| } __attribute__((packed)) CACHEALIGN_ATTR; |
| |
| __ENSURE_STRUCT_CACHE_FRIENDLY(struct lcdif_dma_command_t) |
| |
| #define NR_CMDS ((IMX233_FRAMEBUFFER_SIZE + IMX233_MAX_SINGLE_DMA_XFER_SIZE - 1) / IMX233_MAX_SINGLE_DMA_XFER_SIZE) |
| |
| struct lcdif_dma_command_t lcdif_dma[NR_CMDS]; |
| |
| /** |
| * Utils |
| */ |
| static int g_wait_nr_frame = 0; |
| static struct semaphore g_wait_sema; |
| |
| static void wait_frames_cb(void) |
| { |
| if(--g_wait_nr_frame == 0) |
| semaphore_release(&g_wait_sema); |
| } |
| |
| static void wait_nr_frames(int nr) |
| { |
| g_wait_nr_frame = 1 + nr; // +1 because we want entire frames |
| imx233_lcdif_set_vsync_edge_cb(wait_frames_cb); |
| imx233_lcdif_enable_vsync_edge_irq(true); |
| semaphore_wait(&g_wait_sema, TIMEOUT_BLOCK); |
| imx233_lcdif_enable_vsync_edge_irq(false); |
| } |
| |
| /** |
| * SPI |
| */ |
| |
| #define SPI_CS(v) imx233_pinctrl_set_gpio(1, 11, v) |
| #define SPI_SCL(v) imx233_pinctrl_set_gpio(1, 10, v) |
| #define SPI_SDO(v) imx233_pinctrl_set_gpio(1, 9, v) |
| |
| #define DEV_ID 0x74 |
| #define RS 0x2 |
| #define RW 0x1 |
| |
| static void spi_init(void) |
| { |
| imx233_pinctrl_acquire(1, 9, "lcd_spi_sdo"); |
| imx233_pinctrl_acquire(1, 10, "lcd_spi_scl"); |
| imx233_pinctrl_acquire(1, 11, "lcd_spi_cs"); |
| imx233_pinctrl_set_function(1, 9, PINCTRL_FUNCTION_GPIO); |
| imx233_pinctrl_set_function(1, 10, PINCTRL_FUNCTION_GPIO); |
| imx233_pinctrl_set_function(1, 11, PINCTRL_FUNCTION_GPIO); |
| imx233_pinctrl_set_gpio(1, 9, true); |
| imx233_pinctrl_set_gpio(1, 10, true); |
| imx233_pinctrl_set_gpio(1, 11, true); |
| imx233_pinctrl_enable_gpio(1, 9, true); |
| imx233_pinctrl_enable_gpio(1, 10, true); |
| imx233_pinctrl_enable_gpio(1, 11, true); |
| } |
| |
| static void spi_delay(void) |
| { |
| udelay(1); |
| } |
| |
| static void spi_begin(void) |
| { |
| SPI_CS(false); |
| spi_delay(); |
| } |
| |
| static void spi_write(unsigned char b) |
| { |
| for(int i = 7; i >= 0; i--) |
| { |
| SPI_SCL(false); |
| spi_delay(); |
| SPI_SDO((b >> i) & 1); |
| spi_delay(); |
| SPI_SCL(true); |
| spi_delay(); |
| } |
| } |
| |
| static void spi_end(void) |
| { |
| SPI_CS(true); |
| spi_delay(); |
| } |
| |
| static void spi_write_reg(uint8_t reg, uint16_t value) |
| { |
| spi_begin(); |
| spi_write(DEV_ID); |
| spi_write(0); |
| spi_write(reg); |
| spi_end(); |
| spi_begin(); |
| spi_write(DEV_ID | RS); |
| spi_write(value >> 8); |
| spi_write(value & 0xff); |
| spi_end(); |
| } |
| |
| /** |
| * LCD control |
| */ |
| |
| static void lcd_power(bool en) |
| { |
| imx233_pinctrl_set_gpio(1, 8, en); |
| mdelay(10); |
| } |
| |
| static void lcd_power_seq(void) |
| { |
| spi_write_reg(0x7, 0); |
| mdelay(10); |
| spi_write_reg(0x12, 0x1618); |
| spi_write_reg(0x11, 0x2227); |
| spi_write_reg(0x13, 0x61d1); |
| spi_write_reg(0x10, 0x550c); |
| wait_nr_frames(5); |
| spi_write_reg(0x12, 0x0c58); |
| } |
| |
| static void lcd_init_seq(void) |
| { |
| /* NOTE I don't understand why I have to use BGR, logic would say I should not */ |
| spi_write_reg(0x1, 0x2b1d);// inversion |
| spi_write_reg(0x2, 0x300); |
| /* NOTE by default stmp3700 has vsync/hsync active low and data launch |
| * at negative edge of dotclk, reflect this in the polarity settings */ |
| spi_write_reg(0x3, 0xd040);// polarity (OF uses 0xc040, seems incorrect) |
| spi_write_reg(0x8, 0); // vsync back porch (0=3H) |
| spi_write_reg(0x9, 0); // hsync back porhc (0=24clk) |
| spi_write_reg(0x76, 0x2213); |
| spi_write_reg(0xb, 0x33e1); |
| spi_write_reg(0xc, 0x23); |
| spi_write_reg(0x76, 0); |
| spi_write_reg(0xd, 7); |
| spi_write_reg(0xe, 0); |
| spi_write_reg(0x15, 0x803); |
| spi_write_reg(0x14, 0); |
| spi_write_reg(0x16, 0); |
| spi_write_reg(0x30, 0x706); |
| spi_write_reg(0x31, 0x406); |
| spi_write_reg(0x32, 0xc09); |
| spi_write_reg(0x33, 0x606); |
| spi_write_reg(0x34, 0x706); |
| spi_write_reg(0x35, 0x406); |
| spi_write_reg(0x36, 0xc06); |
| spi_write_reg(0x37, 0x601); |
| spi_write_reg(0x38, 0x504); |
| spi_write_reg(0x39, 0x504); |
| } |
| |
| static void lcd_display_on_seq(void) |
| { |
| spi_write_reg(0x7, 1); |
| wait_nr_frames(1); |
| spi_write_reg(0x7, 0x101); |
| wait_nr_frames(2); |
| spi_write_reg(0x76, 0x2213); |
| spi_write_reg(0x1c, 0x6650); |
| spi_write_reg(0xb, 0x33e1); |
| spi_write_reg(0x76, 0); |
| spi_write_reg(0x7, 0x103); |
| } |
| |
| #ifdef HAVE_LCD_ENABLE |
| static void lcd_display_off_seq(void) |
| { |
| spi_write_reg(0xb, 0x30e1); |
| spi_write_reg(0x7, 0x102); |
| wait_nr_frames(2); |
| spi_write_reg(0x7, 0); |
| spi_write_reg(0x12, 0); |
| spi_write_reg(0x10, 0x100); |
| } |
| |
| static void lcd_standby_in_seq(void) |
| { |
| lcd_display_off_seq(); |
| spi_write_reg(0x10, 0x1); |
| } |
| |
| static void lcd_standby_out_seq(void) |
| { |
| spi_write_reg(0x10, 0); |
| lcd_power_seq(); |
| lcd_display_on_seq(); |
| } |
| #endif |
| |
| /** |
| * Rockbox |
| */ |
| |
| void lcd_init_device(void) |
| { |
| semaphore_init(&g_wait_sema, 1, 0); |
| #ifdef HAVE_LCD_ENABLE |
| lcd_on = true; |
| #endif |
| /** lcd is 320x240, data bus is 8-bit, depth is 24-bit so we need 3clk/pix |
| * by running PIX clock at 24MHz we can sustain ~100 fps */ |
| imx233_clkctrl_enable(CLK_PIX, false); |
| imx233_clkctrl_set_div(CLK_PIX, 2); |
| imx233_clkctrl_set_bypass(CLK_PIX, true); /* use XTAL */ |
| imx233_clkctrl_enable(CLK_PIX, true); |
| imx233_dma_reset_channel(APB_LCDIF); |
| imx233_dma_clkgate_channel(APB_LCDIF, true); |
| imx233_lcdif_init(); |
| imx233_lcdif_setup_dotclk_pins(8, false); |
| imx233_lcdif_set_word_length(8); |
| /** Datasheet states: |
| * 257H >= VBP >= 3H, VBP > VLW, VFP > 1H |
| * 1533clk >= HBP >= 24clk, HBP > HLW, HFP >= 4clk |
| * |
| * Take VLW=1H, VBP=3H, VFP=2H, HLW=8, HBP=24, HFP=4 |
| * Take 3clk/pix because we send 24-bit/pix with 8-bit data bus |
| * Keep consistent with register setting in lcd_init_seq |
| */ |
| imx233_lcdif_setup_dotclk_ex(/*v_pulse_width*/1, /*v_back_porch*/3, |
| /*v_front_porch*/1, /*h_pulse_width*/8, /*h_back_porch*/24, |
| /*h_front_porch*/4, LCD_WIDTH, LCD_HEIGHT, /*clk_per_pix*/3, |
| /*enable_present*/false); |
| imx233_lcdif_set_byte_packing_format(0xf); |
| // prepare pins |
| spi_init(); |
| imx233_pinctrl_acquire(1, 8, "lcd_power"); |
| imx233_pinctrl_set_function(1, 8, PINCTRL_FUNCTION_GPIO); |
| imx233_pinctrl_enable_gpio(1, 8, true); |
| // reset lcd |
| imx233_lcdif_reset_lcd(true); |
| mdelay(10); |
| imx233_lcdif_reset_lcd(false); |
| mdelay(10); |
| imx233_lcdif_reset_lcd(true); |
| mdelay(10); |
| // power up |
| lcd_power(true); |
| // setup registers |
| imx233_lcdif_enable_sync_signals(true); // we need frame signals during init |
| lcd_power_seq(); |
| lcd_init_seq(); |
| lcd_display_on_seq(); |
| // setup refresh |
| unsigned size = IMX233_FRAMEBUFFER_SIZE; |
| uint8_t *frame_p = FRAME; |
| for(int i = 0; i < NR_CMDS; i++) |
| { |
| unsigned xfer = MIN(IMX233_MAX_SINGLE_DMA_XFER_SIZE, size); |
| lcdif_dma[i].dma.next = &lcdif_dma[(i + 1) % NR_CMDS].dma; |
| lcdif_dma[i].dma.cmd = BF_OR3(APB_CHx_CMD, CHAIN(1), |
| COMMAND(BV_APB_CHx_CMD_COMMAND__READ), XFER_COUNT(xfer)); |
| lcdif_dma[i].dma.buffer = frame_p; |
| size -= xfer; |
| frame_p += xfer; |
| } |
| imx233_dma_start_command(APB_LCDIF, &lcdif_dma[0].dma); |
| BF_SET(LCDIF_CTRL, RUN); |
| } |
| |
| #ifdef HAVE_LCD_ENABLE |
| bool lcd_active(void) |
| { |
| return lcd_on; |
| } |
| |
| void lcd_enable(bool enable) |
| { |
| if(lcd_on == enable) |
| return; |
| |
| lcd_on = enable; |
| if(lcd_on) |
| lcd_standby_out_seq(); |
| else |
| lcd_standby_in_seq(); |
| } |
| #endif |
| |
| void lcd_update(void) |
| { |
| lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT); |
| } |
| |
| void lcd_update_rect(int x, int y, int w, int h) |
| { |
| #ifdef HAVE_LCD_ENABLE |
| if(!lcd_on) |
| return; |
| #endif |
| for(int yy = y; yy < y + h; yy++) |
| { |
| uint16_t *pix = FBADDR(x, yy); |
| uint8_t *p = 3 * (yy * LCD_WIDTH + x) + (uint8_t *)FRAME; |
| for(int xx = 0; xx < w; xx++, pix++) |
| { |
| *p++ = RGB_UNPACK_RED(*pix); |
| *p++ = RGB_UNPACK_GREEN(*pix); |
| *p++ = RGB_UNPACK_BLUE(*pix); |
| } |
| } |
| } |