/* * Freescale STMP378X Samsung LMS350 LCD panel initialization * * Embedded Alley Solutions, Inc * * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DOTCLK_H_ACTIVE 320 #define DOTCLK_H_PULSE_WIDTH 3 #define DOTCLK_HF_PORCH 5 #define DOTCLK_HB_PORCH 4 #define DOTCLK_H_WAIT_CNT (DOTCLK_H_PULSE_WIDTH + (3 * DOTCLK_HB_PORCH)) #define DOTCLK_H_PERIOD (DOTCLK_H_WAIT_CNT + DOTCLK_HF_PORCH + DOTCLK_H_ACTIVE) #define DOTCLK_V_PULSE_WIDTH 2 #define DOTCLK_V_ACTIVE 240 #define DOTCLK_VF_PORCH 2 #define DOTCLK_VB_PORCH 5 #define DOTCLK_V_WAIT_CNT (DOTCLK_V_PULSE_WIDTH + DOTCLK_VB_PORCH) #define DOTCLK_V_PERIOD (DOTCLK_VF_PORCH + DOTCLK_V_ACTIVE + DOTCLK_V_WAIT_CNT) static struct stmp3xxx_platform_bl_data bl_data; extern struct pin_group lcd_pins; extern unsigned lcd_spi_pins[]; static void spi_write(u32 val) { u32 mask; gpio_set_value(lcd_spi_pins[SPI_MOSI], 0); gpio_set_value(lcd_spi_pins[SPI_SCLK], 1); gpio_set_value(lcd_spi_pins[SPI_CS], 0); for (mask = 0x00800000; mask != 0; mask >>= 1) { gpio_set_value(lcd_spi_pins[SPI_SCLK], 0); if (val & mask) gpio_set_value(lcd_spi_pins[SPI_MOSI], 1); else gpio_set_value(lcd_spi_pins[SPI_MOSI], 0); gpio_set_value(lcd_spi_pins[SPI_SCLK], 1); } udelay(10); gpio_set_value(lcd_spi_pins[SPI_MOSI], 1); gpio_set_value(lcd_spi_pins[SPI_SCLK], 1); gpio_set_value(lcd_spi_pins[SPI_CS], 1); } static void write_reg(u16 reg, u16 val) { pr_debug("%s: writing %x to %x\n", __func__, reg, val); spi_write(0x00740000 | reg); spi_write(0x00760000 | val); } static const unsigned short pon_seq[] = { /* power on */ 0x07, 0x0000, 20, 0x12, 0x1618, 0, 0x11, 0x222f, 0, 0x13, 0x40ca, 0, 0x10, 0x3108, 300, 0x12, 0x1658, 250, 0x01, 0x2b1d, 0, 0x02, 0x0300, 0, 0x03, 0xD040, 0, 0x08, (DOTCLK_VB_PORCH + DOTCLK_V_PULSE_WIDTH) - 2, 0, 0x09, ((DOTCLK_H_PULSE_WIDTH / 3) + DOTCLK_HB_PORCH) - 2, 0, 0x76, 0x2213, 0, 0x0b, 0x33e1, 0, 0x0c, 0x0020, 0, 0x76, 0x0000, 0, 0x0d, 0x0000, 0, 0x0e, 0x0000, 0, 0x14, 0x0000, 0, 0x15, 0x0803, 0, 0x16, 0x0000, 0, 0x30, 0x0209, 0, 0x31, 0x0404, 0, 0x32, 0x0e07, 0, 0x33, 0x0602, 0, 0x34, 0x0707, 0, 0x35, 0x0707, 0, 0x36, 0x0707, 0, 0x37, 0x0206, 0, 0x38, 0x0f06, 0, 0x39, 0x0611, 20, }; static const unsigned short don_seq[] = { /* display on */ 0x07, 0x0001, 150, 0x07, 0x0101, 150, 0x76, 0x2213, 0, 0x1c, 0x6650, 0, 0x0b, 0x33e0, 0, 0x76, 0x0000, 0, 0x07, 0x0103, 0, }; static const unsigned short doff_seq[] = { /* display off */ 0x0b, 0x33e1, 0, 0x07, 0x0102, 150, 0x07, 0x0100, 150, 0x12, 0x0000, 0, 0x10, 0x0000, 0, }; static const unsigned short poff_seq[] = { /* power off */ /* called after display off */ 0x07, 0x0000, 0, 0x10, 0x0000, 0, 0x11, 0x0000, 0, }; static const unsigned short sby_seq[] = { /* standby */ /* called after display off */ 0x10, 0x0001, 0 }; static const unsigned short csby_seq[] = { /* cancel standby */ /* called after display on */ 0x10, 0x0000, 0 }; static void display_off(void) { int i; const unsigned short *seq; seq = doff_seq; for (i = 0; i < ARRAY_SIZE(doff_seq); i += 3) { write_reg(seq[i], seq[i + 1]); if (seq[i + 2]) udelay(seq[i + 2]); } } static void display_on(void) { int i; const unsigned short *seq; seq = don_seq; for (i = 0; i < ARRAY_SIZE(don_seq); i += 3) { write_reg(seq[i], seq[i + 1]); if (seq[i + 2]) udelay(seq[i + 2]); } } static void init_panel_hw(void) { int i; const unsigned short *seq; seq = pon_seq; for (i = 0; i < ARRAY_SIZE(pon_seq); i += 3) { write_reg(seq[i], seq[i + 1]); if (seq[i + 2]) udelay(seq[i + 2]); } display_on(); } static int init_pinmux(void) { return stmp3xxx_request_pin_group(&lcd_pins, "lcd_lms350"); } static int init_pinmux_spi(void) { int ret = -EINVAL; ret = gpio_request(lcd_spi_pins[SPI_MOSI], "lcd_lms350"); if (ret) goto out_1; ret = gpio_request(lcd_spi_pins[SPI_SCLK], "lcd_lms350"); if (ret) goto out_2; ret = gpio_request(lcd_spi_pins[SPI_CS], "lcd_lms350"); if (ret) goto out_3; /* Enable these pins as outputs */ gpio_direction_output(lcd_spi_pins[SPI_MOSI], 1); gpio_direction_output(lcd_spi_pins[SPI_SCLK], 1); gpio_direction_output(lcd_spi_pins[SPI_CS], 1); return 0; out_3: gpio_free(lcd_spi_pins[SPI_SCLK]); out_2: gpio_free(lcd_spi_pins[SPI_MOSI]); out_1: return ret; } static void uninit_pinmux(void) { stmp3xxx_release_pin_group(&lcd_pins, "lcd_lms350"); } static void uninit_pinmux_spi(void) { gpio_free(lcd_spi_pins[SPI_MOSI]); gpio_free(lcd_spi_pins[SPI_SCLK]); gpio_free(lcd_spi_pins[SPI_CS]); } static struct clk *lcd_clk; static int init_panel(struct device *dev, dma_addr_t phys, int memsize, struct stmp3xxx_platform_fb_entry *pentry) { int ret = 0; lcd_clk = clk_get(dev, "lcdif"); if (IS_ERR(lcd_clk)) { ret = PTR_ERR(lcd_clk); goto out_1; } ret = clk_enable(lcd_clk); if (ret) { clk_put(lcd_clk); goto out_1; } ret = clk_set_rate(lcd_clk, 1000000/pentry->cycle_time_ns); /* kHz */ if (ret) { clk_disable(lcd_clk); clk_put(lcd_clk); goto out_1; } /* * Make sure we do a high-to-low transition to reset the panel. * First make it low for 100 msec, hi for 10 msec, low for 10 msec, * then hi. */ stmp3xxx_clearl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* low */ mdelay(100); stmp3xxx_setl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* high */ mdelay(10); stmp3xxx_clearl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* low */ /* For the Samsung, Reset must be held low at least 30 uSec * Therefore, we'll hold it low for about 10 mSec just to be sure. * Then we'll wait 1 mSec afterwards. */ mdelay(10); stmp3xxx_setl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* high */ mdelay(1); ret = init_pinmux(); if (ret) goto out_1; ret = init_pinmux_spi(); if (ret) goto out_2; init_panel_hw(); setup_dotclk_panel(DOTCLK_V_PULSE_WIDTH, DOTCLK_V_PERIOD, DOTCLK_V_WAIT_CNT, DOTCLK_V_ACTIVE, DOTCLK_H_PULSE_WIDTH, DOTCLK_H_PERIOD, DOTCLK_H_WAIT_CNT, DOTCLK_H_ACTIVE, 0); ret = stmp3xxx_lcdif_dma_init(dev, phys, memsize, 1); if (ret) goto out_3; stmp3xxx_lcd_set_bl_pdata(pentry->bl_data); stmp3xxx_lcdif_notify_clients(STMP3XXX_LCDIF_PANEL_INIT, pentry); return 0; out_3: uninit_pinmux_spi(); out_2: uninit_pinmux(); out_1: return ret; } static void release_panel(struct device *dev, struct stmp3xxx_platform_fb_entry *pentry) { stmp3xxx_lcdif_notify_clients(STMP3XXX_LCDIF_PANEL_RELEASE, pentry); display_off(); uninit_pinmux_spi(); uninit_pinmux(); release_dotclk_panel(); stmp3xxx_lcdif_dma_release(); clk_disable(lcd_clk); clk_put(lcd_clk); } static int blank_panel(int blank) { int ret = 0, count; switch (blank) { case FB_BLANK_NORMAL: case FB_BLANK_VSYNC_SUSPEND: case FB_BLANK_HSYNC_SUSPEND: case FB_BLANK_POWERDOWN: stmp3xxx_clearl(BM_LCDIF_CTRL_BYPASS_COUNT, REGS_LCDIF_BASE + HW_LCDIF_CTRL); for (count = 10000; count; count--) { if (__raw_readl(REGS_LCDIF_BASE + HW_LCDIF_STAT) & BM_LCDIF_STAT_TXFIFO_EMPTY) break; udelay(1); } break; case FB_BLANK_UNBLANK: stmp3xxx_setl(BM_LCDIF_CTRL_BYPASS_COUNT, REGS_LCDIF_BASE + HW_LCDIF_CTRL); break; default: ret = -EINVAL; } return ret; } static void stop_panel(void) { stmp3xxx_lcdif_stop(); display_off(); } static void run_panel(void) { display_on(); stmp3xxx_lcdif_run(); } static struct stmp3xxx_platform_fb_entry fb_entry = { .name = "lms350", .x_res = 240, .y_res = 320, .bpp = 32, .cycle_time_ns = 200, .lcd_type = STMP3XXX_LCD_PANEL_DOTCLK, .init_panel = init_panel, .release_panel = release_panel, .blank_panel = blank_panel, .run_panel = run_panel, .stop_panel = stop_panel, .pan_display = stmp3xxx_lcdif_pan_display, .bl_data = &bl_data, }; static struct clk *pwm_clk; static int init_bl(struct stmp3xxx_platform_bl_data *data) { int ret = 0; pwm_clk = clk_get(NULL, "pwm"); if (IS_ERR(pwm_clk)) { ret = PTR_ERR(pwm_clk); goto out; } clk_enable(pwm_clk); stmp3xxx_reset_block(REGS_PWM_BASE, 1); ret = stmp3xxx_request_pin(PINID_PWM2, PIN_FUN1, "lcd_lms350"); if (ret) goto out_mux; stmp3xxx_pin_voltage(PINID_PWM2, PIN_8MA, "lcd_lms350"); stmp3xxx_pin_strength(PINID_PWM2, PIN_3_3V, "lcd_lms350"); __raw_writel(BF(0, PWM_ACTIVEn_INACTIVE) | BF(0, PWM_ACTIVEn_ACTIVE), REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ BF(599, PWM_PERIODn_PERIOD), REGS_PWM_BASE + HW_PWM_PERIODn(2)); stmp3xxx_setl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); return 0; out_mux: clk_put(pwm_clk); out: return ret; } static void free_bl(struct stmp3xxx_platform_bl_data *data) { __raw_writel(BF(0, PWM_ACTIVEn_INACTIVE) | BF(0, PWM_ACTIVEn_ACTIVE), REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ BF(599, PWM_PERIODn_PERIOD), REGS_PWM_BASE + HW_PWM_PERIODn(2)); stmp3xxx_clearl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); stmp3xxx_pin_voltage(PINID_PWM2, PIN_4MA, "lcd_lms350"); stmp3xxx_pin_strength(PINID_PWM2, PIN_1_8V, "lcd_lms350"); stmp3xxx_release_pin(PINID_PWM2, "lcd_lms350"); clk_disable(pwm_clk); clk_put(pwm_clk); } static int values[] = { 6, 9, 12, 15, 19, 24, 30, 40, 55, 75, 100 }; static int power[] = { 0, 1500, 3600, 6100, 10300, 15500, 74200, 114200, 155200, 190100, 191000 }; static int bl_to_power(int br) { int base; int rem; if (br > 100) br = 100; base = power[br/10]; rem = br % 10; if (!rem) return base; else return base + (rem * (power[br/10 + 1]) - base) / 10; } static int set_bl_intensity(struct stmp3xxx_platform_bl_data *data, struct backlight_device *bd, int suspended) { int intensity = bd->props.brightness; int scaled_int; if (bd->props.power != FB_BLANK_UNBLANK) intensity = 0; if (bd->props.fb_blank != FB_BLANK_UNBLANK) intensity = 0; if (suspended) intensity = 0; /* * This is not too cool but what can we do? * Luminance changes non-linearly... */ if (regulator_set_current_limit(data->regulator, bl_to_power(intensity), bl_to_power(intensity))) return -EBUSY; scaled_int = values[intensity/10]; if (scaled_int < 100) { int rem = intensity - 10 * (intensity/10); /* r = i % 10;*/ scaled_int += rem*(values[intensity/10 + 1] - values[intensity/10])/10; } __raw_writel(BF(scaled_int, PWM_ACTIVEn_INACTIVE) | BF(0, PWM_ACTIVEn_ACTIVE), REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ BF(599, PWM_PERIODn_PERIOD), REGS_PWM_BASE + HW_PWM_PERIODn(2)); return 0; } static struct stmp3xxx_platform_bl_data bl_data = { .bl_max_intensity = 100, .bl_default_intensity = 50, .bl_cons_intensity = 50, .init_bl = init_bl, .free_bl = free_bl, .set_bl_intensity = set_bl_intensity, }; static int __init register_devices(void) { stmp3xxx_lcd_register_entry(&fb_entry, stmp3xxx_framebuffer.dev.platform_data); return 0; } subsys_initcall(register_devices);