/* * File: drivers/video/omap/lcd_lph8923.c * * Philips LPH8923 LCD driver * * Copyright (C) 2004-2005 Nokia Corporation * Authors: Juha Yrjölä * Imre Deak * * 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 program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include "../../ssi/omap-uwire.h" #include "../../cbus/tahvo.h" #include "hwa742.h" #include "debug.h" #define BACKLIGHT_USE_TABLE #define LPH8923_VER_BUGGY 1 #define LPH8923_VER_NON_BUGGY 3 struct { int enabled; int version; unsigned int display_id; unsigned int saved_bklight_level; unsigned long hw_guard_end; /* next value of jiffies when we can issue the next sleep in/out command */ unsigned long hw_guard_wait; /* max guard time in jiffies */ #ifdef BACKLIGHT_USE_TABLE unsigned char bklight_table[0x10]; #endif } lph8923; #define LPH8923_CS 3 #define LPH8923_CMD_READ_DISP_ID 0x04 #define LPH8923_CMD_READ_RED 0x06 #define LPH8923_CMD_READ_GREEN 0x07 #define LPH8923_CMD_READ_BLUE 0x08 #define LPH8923_CMD_READ_DISP_STATUS 0x09 #define LPH8923_CMD_SLEEP_IN 0x10 #define LPH8923_CMD_SLEEP_OUT 0x11 #define LPH8923_CMD_DISP_OFF 0x28 #define LPH8923_CMD_DISP_ON 0x29 static int lph8923_read(int cmd, int rcv_bytes, u32 *rcv_data) { u32 l; u16 w; int cmd_bits = 9, leave_cs = 0; l = cmd; if (rcv_bytes > 1) { /* Need dummy clock */ cmd_bits++; l <<= 1; } if (rcv_bytes > 0) leave_cs = 1; omap_uwire_data_transfer(LPH8923_CS, l, cmd_bits, 0, NULL, leave_cs); *rcv_data = 0; while (rcv_bytes > 0) { *rcv_data <<= 8; if (rcv_bytes == 1) leave_cs = 0; omap_uwire_data_transfer(LPH8923_CS, 0, 0, 8, &w, leave_cs); *rcv_data |= w; rcv_bytes--; } return 0; } static int lph8923_write(int cmd, int send_bytes, const u8 *send_data) { int leave_cs = 0; if (send_bytes > 0) leave_cs = 1; omap_uwire_data_transfer(LPH8923_CS, cmd, 9, 0, NULL, leave_cs); while (send_bytes > 0) { u16 w; w = (1 << 8) | *send_data; if (send_bytes == 1) leave_cs = 0; omap_uwire_data_transfer(LPH8923_CS, w, 9, 0, NULL, leave_cs); send_data++; send_bytes--; } return 0; } struct cmd_data { u8 cmd; u8 len; const u8 *data; } __attribute__ ((packed));; static const struct cmd_data init_cmds_buggy_lph8923[] = { { 0xb0, 1, "\x08" }, { 0xb1, 2, "\x0b\x1c" }, { 0xb2, 4, "\x00\x00\x00\x00" }, { 0xb3, 4, "\x00\x00\x00\x00" }, { 0xb4, 1, "\x87" }, { 0xb5, 4, "\x37\x07\x37\x07" }, { 0xb6, 2, "\x64\x24" }, { 0xb7, 1, "\x90" }, { 0xb8, 3, "\x10\x11\x20" }, { 0xb9, 2, "\x31\x02" }, { 0xba, 3, "\x04\xa3\x9d" }, { 0xbb, 4, "\x15\xb2\x8c\x00" }, { 0xc2, 3, "\x02\x00\x00" }, }; static const struct cmd_data init_cmds_non_buggy_lph8923[] = { { 0xc2, 3, "\x02\x00\x00" }, }; static inline void lph8923_set_16bit_mode(void) { lph8923_write(0x3a, 1, "\x50"); } static void lph8923_send_init_string(void) { int c; const struct cmd_data *cd; switch (lph8923.version) { case LPH8923_VER_BUGGY: c = sizeof(init_cmds_buggy_lph8923) / sizeof(init_cmds_buggy_lph8923[0]); cd = init_cmds_buggy_lph8923; break; case LPH8923_VER_NON_BUGGY: default: c = sizeof(init_cmds_non_buggy_lph8923) / sizeof(init_cmds_non_buggy_lph8923[0]); cd = init_cmds_non_buggy_lph8923; break; } while (c--) { lph8923_write(cd->cmd, cd->len, cd->data); cd++; } lph8923_set_16bit_mode(); } static void hw_guard_start(int guard_msec) { lph8923.hw_guard_wait = msecs_to_jiffies(guard_msec); lph8923.hw_guard_end = jiffies + lph8923.hw_guard_wait; } static void hw_guard_wait(void) { unsigned long wait = lph8923.hw_guard_end - jiffies; if ((long)wait > 0 && wait <= lph8923.hw_guard_wait) { set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(wait); } } static void lph8923_set_sleep_mode(int on) { int cmd, sleep_time = 5; if (on) cmd = LPH8923_CMD_SLEEP_IN; else cmd = LPH8923_CMD_SLEEP_OUT; hw_guard_wait(); lph8923_write(cmd, 0, NULL); hw_guard_start(120); /* When we enable the panel, it seems we _have_ to sleep * 120 ms before sending the init string */ if (!on) sleep_time = 120; set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(msecs_to_jiffies(sleep_time)); } static void lph8923_set_display_state(int enabled) { int cmd = enabled ? LPH8923_CMD_DISP_ON : LPH8923_CMD_DISP_OFF; lph8923_write(cmd, 0, NULL); } static void lph8923_detect(void) { lph8923_read(LPH8923_CMD_READ_DISP_ID, 3, &lph8923.display_id); printk(KERN_INFO "Philips LPH8923 display id: %06x\n", lph8923.display_id); if ((lph8923.display_id >> 16) == 0x45) { lph8923.version = LPH8923_VER_NON_BUGGY; printk(KERN_INFO "Non-buggy LPH8923 detected\n"); return; } else { lph8923.version = LPH8923_VER_BUGGY; printk(KERN_INFO "Buggy LPH8923 detected\n"); } } #ifdef BACKLIGHT_USE_TABLE static void lph8923_panel_init_bklight_table(void); static void lph8923_panel_init_bklight_table(void){ int level; unsigned int max=tahvo_get_max_backlight_level(); for (level=0;level<0x10;level++) lph8923.bklight_table[level]= (unsigned char) (level * max / 0x0f); } #endif static int lph8923_panel_init(struct lcd_panel *panel) { memset(&lph8923, 0, sizeof(lph8923)); /* We can't be sure, but assume the bootloader * enabled it already */ lph8923.enabled = 1; omap_uwire_configure_mode(LPH8923_CS, UWIRE_CLK_INVERTED | UWIRE_READ_FALLING_EDGE); lph8923_detect(); #ifdef BACKLIGHT_USE_TABLE lph8923_panel_init_bklight_table(); #endif return 0; } static void lph8923_panel_cleanup(struct lcd_panel *panel) { } #if 0 static int lph8923_enabled(void) { lph8923_read(LPH8923_CMD_READ_DISP_STATUS, 4, &disp_status); if (!(disp_status & (1 << 17)) && (disp_status & (1 << 10))) return 1; else return 0; } #endif static int lph8923_panel_set_bklight_level(struct lcd_panel *panel, unsigned int level) { #ifdef BACKLIGHT_USE_TABLE if (level > 0xf){ /* if invalid value, set translation table value */ unsigned int offset = level & 0xF; unsigned int value = level >> 4; if (offset==0) offset=lph8923.saved_bklight_level; /* zero means change current level */ if (offset==0 || value>tahvo_get_max_backlight_level()) /* make sure to keep zero level intact and do not go over maximum */ return -EINVAL; lph8923.bklight_table[offset]=value; if (offset!=lph8923.saved_bklight_level) return 0; /* not current level */ level=offset; /* we are changing current active level, continue */ } #else if (level > tahvo_get_max_backlight_level()) return -EINVAL; #endif lph8923.saved_bklight_level = level; if (!lph8923.enabled) { return 0; } #ifdef BACKLIGHT_USE_TABLE level = lph8923.bklight_table[level];/**/ /* use level translation table */ #endif tahvo_set_backlight_level(level); #if 0 /* We send the sleep_out command when setting the backlight level * (should happen periodically) to work around ESD spikes resetting * the display */ lph8923_set_sleep_mode(0); #endif return 0; } static unsigned int lph8923_panel_get_bklight_level(struct lcd_panel *panel) { return (unsigned int) lph8923.saved_bklight_level; /* return tahvo_get_backlight_level() * 0x0f / tahvo_get_max_backlight_level(); */ } static unsigned int lph8923_panel_get_bklight_max(struct lcd_panel *panel) { #ifdef BACKLIGHT_USE_TABLE return 0x0f; #else return tahvo_get_max_backlight_level(); #endif } static int lph8923_panel_enable(struct lcd_panel *panel) { if (lph8923.enabled) return 0; lph8923_set_sleep_mode(0); lph8923.enabled = 1; lph8923_send_init_string(); lph8923_set_display_state(1); lph8923_panel_set_bklight_level(panel, lph8923.saved_bklight_level); return 0; } static void lph8923_panel_disable(struct lcd_panel *panel) { if (!lph8923.enabled) return; /* saved on each change instead lph8923.saved_bklight_level = lph8923_panel_get_bklight_level(panel); */ lph8923_panel_set_bklight_level(panel, 0); lph8923_set_display_state(0); lph8923_set_sleep_mode(1); lph8923.enabled = 0; } static unsigned long lph8923_panel_get_caps(struct lcd_panel *panel) { return FBCAPS_SET_BACKLIGHT; } static u16 read_first_pixel(void) { u32 b; u16 pixel; lph8923_read(LPH8923_CMD_READ_RED, 1, &b); pixel = (b >> 1) << 11; lph8923_read(LPH8923_CMD_READ_GREEN, 1, &b); pixel |= b << 5; lph8923_read(LPH8923_CMD_READ_BLUE, 1, &b); pixel |= (b >> 1); return pixel; } static int lph8923_panel_test(struct omapfb_device *fbdev, int test_num) { static const u16 test_values[4] = { 0x0000, 0xffff, 0xaaaa, 0x5555, }; int i; if (test_num != LCD_LPH8923_TEST_RGB_LINES) return LCD_LPH8923_TEST_INVALID; for (i = 0; i < ARRAY_SIZE(test_values); i++) { int delay; unsigned long tmo; omapfb_write_first_pixel(fbdev, test_values[i]); tmo = jiffies + msecs_to_jiffies(100); delay = msecs_to_jiffies(25); while (1) { u16 pixel; set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(delay); pixel = read_first_pixel(); if (pixel == test_values[i]) break; if (time_after(jiffies, tmo)) { printk(KERN_ERR "LPH8923 RGB I/F test failed: " "expecting %04x, got %04x\n", test_values[i], pixel); return LCD_LPH8923_TEST_FAILED; } delay = msecs_to_jiffies(10); } } return 0; } static struct lcdc_video_mode mode800x480 = { .x_res = 800, .y_res = 480, .pixel_clock = 12500, /* Not used yet */ .bpp = 16, .hsw = 50, .hfp = 14, .hbp = 31, .vsw = 2, .vfp = 1, .vbp = 3, .pcd = 8, }; struct lcd_panel lph8923_panel = { .name = "lph8923", .config = LCD_PANEL_TFT, .signals = 0, .video_mode = &mode800x480, .init = lph8923_panel_init, .cleanup = lph8923_panel_cleanup, .enable = lph8923_panel_enable, .disable = lph8923_panel_disable, .get_caps= lph8923_panel_get_caps, .set_bklight_level = lph8923_panel_set_bklight_level, .get_bklight_level = lph8923_panel_get_bklight_level, .get_bklight_max = lph8923_panel_get_bklight_max, .run_test = lph8923_panel_test, };