/* * Disclaimer (blabla) * * Author: Manoƫl Trapier * Copyright (c) 2003-2010 Bookeen * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_MESSAGES //#define DEBUG_TRACEFUNC #define VERBOSE_LEVEL INFO_VERBOSE //#define DEBUG_SPI #define MODULE_NAME "AUO-TCON" #define BUSY_WAIT_TIMEOUT (40*5*2*2) //panel time out = 1s #define SYS_WR_CON (1<<6) #define SYS_OE_CON (1<<7) // =========================================================================== #undef MSG #undef DBG #ifdef DEBUG_MESSAGES enum InfoLevel { INFO_ERROR = 0, INFO_WARNING, INFO_NORMAL, INFO_VERBOSE, INFO_DEBUG, }; # ifndef VERBOSE_LEVEL # define VERBOSE_LEVEL INFO_WARNING # endif # ifdef DEBUG_TRACEFUNC static int _dbg_FunctionLevel = 0; # define MSG(str) {\ int __i;\ printk(KERN_ALERT "+");\ for (__i = 0; __i < _dbg_FunctionLevel; __i++)\ printk("-");\ printk("||" str "\n");\ } # define DBG(str, ...) {\ int __i;\ printk(KERN_ALERT "+");\ for (__i = 0; __i < _dbg_FunctionLevel; __i++)\ printk("-");\ printk("||" str "\n", __VA_ARGS__);\ } # define INFOL(level, s) do {\ if (level <= VERBOSE_LEVEL) {\ int __i;\ printk(KERN_ALERT "+");\ for (__i = 0; __i < _dbg_FunctionLevel; __i++)\ printk("-");\ printk("<%d>%s:%s(): ", level, __FILE__, __func__); printk s; printk("\n");\ }\ } while(0) # define FUNC_IN() {\ int __i;\ _dbg_FunctionLevel++;\ printk(KERN_ALERT "+");\ for (__i = 0; __i < _dbg_FunctionLevel; __i++)\ printk("-");\ printk(">> %s() >>\n", __func__);\ } # define FUNC_OUT() {\ int __i;\ printk(KERN_ALERT "+");\ for (__i = 0; __i < _dbg_FunctionLevel; __i++)\ printk("-");\ printk("<< %s() <<\n", __func__);\ _dbg_FunctionLevel--;\ } # define FUNC_OUTR(val) {\ int __i;\ printk(KERN_ALERT "+");\ for (__i = 0; __i < _dbg_FunctionLevel; __i++)\ printk("-");\ printk("<< %s() = %d <<\n", __func__, val);\ _dbg_FunctionLevel--;\ } # else /* DEBUG_TRACEFUNC */ # define MSG(str) do {\ printk(KERN_ALERT MODULE_NAME ": " str "\n");\ } while(0) # define DBG(str, ...) do {\ printk(KERN_ALERT MODULE_NAME ": " str "\n", __VA_ARGS__);\ } while(0) # define FUNC_IN() do {\ } while(0) # define FUNC_OUT() do {\ } while(0) # define FUNC_OUTR(val) do {\ printk(KERN_ALERT MODULE_NAME ": %s() return %d\n", __func__, val);\ } while(0) # define INFOL(level, s) do {\ if (level <= VERBOSE_LEVEL) {\ printk("<%d>%s:%s(): ", level, __FILE__, __func__); printk s; printk("\n");\ }\ } while(0) # endif /* DEBUG_TRACEFUNC */ #else /* DEBUG_MESSAGES */ # define MSG(str) # define DBG(str, ...) # define FUNC_IN() # define FUNC_OUT() # define FUNC_OUTR(val) # define INFOL(level, s) # define INFO(s) #endif /* DEBUG_MESSAGES */ // =========================================================================== #define VIDCON0_S_CPU_IF_MAIN (2<<22) #define VIDCON0_S_RGB_PAR (0<<13) #define VIDCON0_S_VCLK_GATING_OFF (1<<5) #define VIDCON0_S_CLKDIR_DIVIDED (1<<4) #define VIDCON0_S_CLKSEL_HCLK (0<<2) #define VIDCON0_CLKVAL_F_SHIFT (6) #define VIDTCON2_LINEVAL_S (11) static int tcon_inPortraitMode = 0; static int tcon_lastModeWas = 0; typedef enum Tcon_Speedclasses { EN_I80_NONE = -1, EN_I80_LANDSCAPE = 0, EN_I80_PORTRAIT, EN_I80_LANDSCAPE_HANDWRITING, EN_I80_PORTRAIT_HANDWRITING, } Tcon_Speedclasses; typedef struct Tcon_SpeedclasseValue { u8 cs_setup; u8 wr_setup; u8 wr_act; u8 wr_hold; } Tcon_SpeedclasseValue; typedef enum Tcon_PowerMode { TCON_POWER_NORMALMODE, TCON_POWER_STANDBYMODE, TCON_POWER_SLEEPMODE, TCON_POWER_OFF, } Tcon_PowerMode; static Tcon_SpeedclasseValue tcon_speedtable[] ={ [EN_I80_LANDSCAPE] = { .cs_setup = 0, .wr_setup = 0, .wr_act = 1, .wr_hold = 0, }, [EN_I80_PORTRAIT] = { .cs_setup = 0, .wr_setup = 3, .wr_act = 8, .wr_hold = 3, }, #if 1 [EN_I80_LANDSCAPE_HANDWRITING] = { .cs_setup = 0, .wr_setup = 1, .wr_act = 1, .wr_hold = 0, }, [EN_I80_PORTRAIT_HANDWRITING] = { .cs_setup = 0, .wr_setup = 2, .wr_act = 6, .wr_hold = 2, }, #else [EN_I80_PORTRAIT_HANDWRITING] = { .cs_setup = 0, .wr_setup = 1, .wr_act = 1, .wr_hold = 0, }, [EN_I80_LANDSCAPE_HANDWRITING] = { .cs_setup = 0, .wr_setup = 2, .wr_act = 6, .wr_hold = 2, }, #endif }; static struct proc_dir_entry *epaperProcEntry; static Tcon_Speedclasses tcon_currentSpeedClass = EN_I80_NONE; static Tcon_PowerMode tcon_currentPowerMode = TCON_POWER_NORMALMODE; static unsigned short tcon_currentVersion = 0; void tcon_i80bus_set_speed (Tcon_Speedclasses i80_speed, unsigned short display_w, unsigned short display_h, unsigned char set_clock); static int tcon_command (sAUOCommand *cmd, char userland); static int tcon_send_command_start (sAUOCommand *cmd); static int tcon_send_command_end (sAUOCommand *cmd); // =========================================================================== // TCON Related functions // =========================================================================== /* 1. loops = 1 --> 25ns 2. loops = 75 --> 1 us 3. loops = 100000 --> 1ms */ static inline void tcon_ndelay (unsigned long loops) //in ns { __asm__ volatile ("1:\n" "subs %0, %1, #1\n" "bne 1b" : "=r" (loops) : "0"(loops)); } static inline void tcon_delay (unsigned long uTime) { unsigned long cnt = 0; for ( cnt = 0; cnt < uTime; cnt++ ) tcon_ndelay(75); } static int tcon_wait_ready (void) { unsigned long tmp; int iBusyCnt = 0; wait_queue_head_t busy_wq; init_waitqueue_head(&busy_wq); tmp = __raw_readl(S3C2410_GPBDAT); while ( (tmp & (1 << 2)) == 0 ) { sleep_on_timeout(&busy_wq, 4); iBusyCnt++; if ( iBusyCnt >= BUSY_WAIT_TIMEOUT ) { INFOL(INFO_WARNING, ("TCON Wait timedout!!!")); return -EIO; } tmp = __raw_readl(S3C2410_GPBDAT); } return 0; } /* On the i80 port, * i80 TCON * ----------- * RS -> D/C * CS0 -> CSEL * nWE -> HWE * OE -> HRD */ static inline void tcon_i80bus_init_interface (void) { unsigned long tmp; FUNC_IN(); tmp = __raw_readl(S3C_SYSIFCON0); //polarity of RS, set 1 for normal access tmp |= (1 << 2); __raw_writel(tmp, S3C_SYSIFCON0); tmp = __raw_readl(S3C_SIFCCON0); // command mode enable tmp |= (1 << 0); __raw_writel(tmp, S3C_SIFCCON0); FUNC_OUT(); } static inline void tcon_i80bus_deinit_interface (void) { unsigned long tmp; FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // command mode disable tmp &= ~(1 << 0); __raw_writel(tmp, S3C_SIFCCON0); FUNC_OUT(); } void tcon_i80bus_set_speed (Tcon_Speedclasses i80_speed, unsigned short display_w, unsigned short display_h, unsigned char set_clock) { unsigned long tmp; unsigned char clkval = 0; unsigned short h_cnt, v_cnt; unsigned long vclk; struct clk *lcd_clock; int HCLK; FUNC_IN(); if ( tcon_currentSpeedClass == EN_I80_NONE ) set_clock = true; tcon_currentSpeedClass = i80_speed; if ( set_clock == true ) { INFOL(INFO_DEBUG, ("Will set clocks...")); tmp = __raw_readl(S3C_VIDCON0); tmp = VIDCON0_S_CPU_IF_MAIN | VIDCON0_S_RGB_PAR | \ VIDCON0_S_VCLK_GATING_OFF | \ VIDCON0_S_CLKDIR_DIVIDED | VIDCON0_S_CLKSEL_HCLK; __raw_writel(tmp, S3C_VIDCON0); v_cnt = display_h; h_cnt = display_w; lcd_clock = clk_get(NULL, "hclk"); HCLK = clk_get_rate(lcd_clock); //printk(KERN_ERR "-------- HCLK: %d --------", HCLK); clkval = (unsigned int)(HCLK / (display_w * display_h * 50 /* FPS ???? */)); vclk = (HCLK / (clkval + 1)) / 1000; tmp = __raw_readl(S3C_VIDCON0); tmp |= (clkval << VIDCON0_CLKVAL_F_SHIFT); __raw_writel(tmp, S3C_VIDCON0); } tmp = __raw_readl(S3C_SYSIFCON0); tmp = (tcon_speedtable[i80_speed].cs_setup << 16) | (tcon_speedtable[i80_speed].wr_setup << 12) | (tcon_speedtable[i80_speed].wr_act << 8) | (tcon_speedtable[i80_speed].wr_hold << 4) | (1 << 2) | (1 << 1) | (1); __raw_writel(tmp, S3C_SYSIFCON0); tmp = __raw_readl(S3C_SYSIFCON1); tmp = (tcon_speedtable[i80_speed].cs_setup << 16) | (tcon_speedtable[i80_speed].wr_setup << 12) | (tcon_speedtable[i80_speed].wr_act << 8) | (tcon_speedtable[i80_speed].wr_hold << 4) | (1 << 2) | (1 << 1) | (1); __raw_writel(tmp, S3C_SYSIFCON1); tmp = __raw_readl(S3C_VIDTCON2); tmp = ((display_h - 1) << VIDTCON2_LINEVAL_S) | ((display_w / 4) - 1); __raw_writel(tmp, S3C_VIDTCON2); FUNC_OUT(); } static inline void tcon_i80bus_write (int data) { int tmp; if ( (tcon_lastModeWas == 4) && (!tcon_inPortraitMode) ) tcon_delay(1); tmp = __raw_readl(S3C_SIFCCON0); // nWE enable tmp |= SYS_WR_CON; __raw_writel(tmp, S3C_SIFCCON0); if ( (!tcon_inPortraitMode) && (tcon_lastModeWas != 4) ) { tcon_ndelay(25); } else if ( tcon_lastModeWas == 4 ) { if ( tcon_inPortraitMode ) tcon_ndelay(15); else tcon_delay(1); } __raw_writel(data, S3C_SIFCCON1); //rSIFCCON1 = CMD; tmp = __raw_readl(S3C_SIFCCON0); // nWE disables tmp &= ~SYS_WR_CON; __raw_writel(tmp, S3C_SIFCCON0); } static inline void tcon_i80bus_write_LUT (int data) { int tmp; tcon_delay(1); tmp = __raw_readl(S3C_SIFCCON0); // nWE enable tmp |= SYS_WR_CON; __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); __raw_writel(data, S3C_SIFCCON1); //rSIFCCON1 = CMD; tcon_delay(1); tmp = __raw_readl(S3C_SIFCCON0); // nWE disable tmp &= ~SYS_WR_CON; __raw_writel(tmp, S3C_SIFCCON0); } static inline void tcon_i80bus_read (unsigned short *data) { int tmp; tcon_delay(1); tmp = __raw_readl(S3C_SIFCCON0); // nRD enable tmp |= SYS_OE_CON; __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); *data = __raw_readl(S3C_SIFCCON2); //CMD = rSIFCCON2; tcon_delay(1); tmp = __raw_readl(S3C_SIFCCON0); // nRD disable tmp &= ~SYS_OE_CON; __raw_writel(tmp, S3C_SIFCCON0); } static inline void tcon_set_write_to_data (void) { unsigned long tmp; //FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // RS high -> D/nC set Data mode tmp |= (1 << 1); __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); //FUNC_OUT(); } static inline void tcon_set_write_to_command (void) { unsigned long tmp; //FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // RS low -> D/nC set Command mode tmp &= ~(1 << 1); __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); //FUNC_OUT(); } static inline void tcon_enable_write (void) { unsigned long tmp; FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // nWE -> HWE enable tmp |= (1 << 6); __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); FUNC_OUT(); } static inline void tcon_disable_write (void) { unsigned long tmp; FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // nWE -> HWE disable tmp &= ~(1 << 6); __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); FUNC_OUT(); } static inline void tcon_enable_read (void) { unsigned long tmp; FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // nRD enable tmp |= SYS_OE_CON; __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); FUNC_OUT(); } static inline void tcon_disable_read (void) { unsigned long tmp; FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // nRD disable tmp &= ~SYS_OE_CON; __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); FUNC_OUT(); } static inline void tcon_select_chip (void) { unsigned long tmp; FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // Chip Select tmp |= (1 << 8); __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); FUNC_OUT(); } static inline void tcon_unselect_chip (void) { unsigned long tmp; FUNC_IN(); tmp = __raw_readl(S3C_SIFCCON0); // nCS0(Main) enable tmp &= ~(1 << 8); __raw_writel(tmp, S3C_SIFCCON0); tcon_delay(1); FUNC_OUT(); } static inline void tcon_goto_sleep (void) { /* Set GPIO accordingly */ unsigned long tmp = __raw_readl(S3C2410_GPBDAT); tmp &= ~(1 << 1); //Set SLP_N to low __raw_writel(tmp, S3C2410_GPBDAT); msleep(10); } static inline void tcon_wakeup_sleep (void) { unsigned long tmp = __raw_readl(S3C2410_GPBDAT); tmp |= (1 << 1); //Set SLP_N to high __raw_writel(tmp, S3C2410_GPBDAT); msleep(10); } static inline void tcon_reset (void) { // LCD module reset unsigned long tmp = __raw_readl(S3C2410_GPDDAT); tmp |= (1 << 9); __raw_writel(tmp, S3C2410_GPDDAT); tmp = __raw_readl(S3C2410_GPDDAT); // RST_N goes to LOW tmp &= ~(1 << 9); __raw_writel(tmp, S3C2410_GPDDAT); tcon_delay(5); tmp = __raw_readl(S3C2410_GPDDAT); // RST_N goes to HIGH tmp |= (1 << 9); __raw_writel(tmp, S3C2410_GPDDAT); // delay about 10ms msleep(10); tcon_i80bus_set_speed(EN_I80_PORTRAIT, 800, 600, false); /* Reseting the TCON will set the tcon in Level0 power mode */ tcon_currentPowerMode = TCON_POWER_NORMALMODE; } static inline void tcon_set_power_on (void) { unsigned long tmp; tcon_i80bus_set_speed(EN_I80_LANDSCAPE, 800, 600, true); // POWER pin config tmp = __raw_readl(S3C2410_GPBCON); tmp = (tmp & ~(3 << 6)) | (1 << 6); __raw_writel(tmp, S3C2410_GPBCON); // BUSY pin config tmp = __raw_readl(S3C2410_GPBCON); tmp = (tmp & ~(3 << 4)); __raw_writel(tmp, S3C2410_GPBCON); // SLEEP pin config tmp = __raw_readl(S3C2410_GPBCON); tmp = (tmp & ~(3 << 2)) | (1 << 2); __raw_writel(tmp, S3C2410_GPBCON); msleep(1); // Panel power on tmp = __raw_readl(S3C2410_GPBDAT); tmp |= (1 << 3); //SLP_N high tmp |= (1 << 1); __raw_writel(tmp, S3C2410_GPBDAT); msleep(100); tcon_reset(); tcon_currentPowerMode = TCON_POWER_NORMALMODE; } static inline int tcon_set_power_off (void) { unsigned long tmp; sAUOCommand cmd; switch ( tcon_currentPowerMode ) { default: return -EBADF; /* Invalide power mode, can't shutdown... */ break; case TCON_POWER_NORMALMODE: cmd.cmd = AUOCMD_STANDBY; tcon_command(&cmd, false); case TCON_POWER_STANDBYMODE: tcon_goto_sleep(); case TCON_POWER_SLEEPMODE: /*Standby -> Off = shutdown the power pin */ tmp = __raw_readl(S3C2410_GPBDAT); tmp &= ~(1 << 3); __raw_writel(tmp, S3C2410_GPBDAT); case TCON_POWER_OFF: break; } tcon_currentPowerMode = TCON_POWER_OFF; return 0; } struct tcon_info { unsigned short temperature, epd_type, panel_size_int, panel_size_float, model, lut_version, tcon_version; }; static void tcon_get_info (struct tcon_info *info) { sAUOCommand cmd; unsigned short buf[4] = { 0, 0, 0, 0 }; if ( info == NULL ) return; cmd.cmd = AUOCMD_READ_FUNC; cmd.data = buf; cmd.datalen = 8; tcon_command(&cmd, false); info->temperature = buf[0] & 0x01FF; info->epd_type = buf[1] & 0x00FF; info->panel_size_int = (buf[2] & 0xFC00) >> 10; info->panel_size_float = (buf[2] & 0x03C0) >> 6; info->model = buf[2] & 0x003F; info->lut_version = buf[3] & 0x00FF; info->tcon_version = (buf[3] & 0xFF00) >> 8; tcon_currentVersion = info->tcon_version; } static void tcon_display_info (void) { struct tcon_info info; tcon_get_info(&info); printk("TCON FW v%d, LUT v%d, EPD {v[%d];Size[%d:%d];Model[%d]}\n", info.tcon_version, info.lut_version, info.epd_type, info.panel_size_int, info.panel_size_float, info.model); if ( tcon_currentVersion != AUO_FIRMWARE_VERSION ) printk(KERN_WARNING "!!! Warning: TCON Firmware version and Driver version mismatch !!!\n"); } static int tcon_send_command_start (sAUOCommand *cmd) { FUNC_IN(); INFOL(INFO_DEBUG, ("cmd #%08lX", cmd->cmd)); /* First: verify that the K1900 is ready */ INFOL(INFO_DEBUG, ("/* First: verify that the K1900 is ready */")); if ( GET_COMMAND_NEED_WAIT(cmd->cmd) != 0x00 ) { INFOL(INFO_DEBUG, ("Wait for non BUSY...")); // TODO: return error on timeout if ( tcon_wait_ready() < 0 ) { INFOL(INFO_ERROR, ("Waiting timed out")); return -EIO; } } if ( AUOCMD_EQUAL(cmd->cmd, AUOCMD_STANDBY) ) { INFOL(INFO_VERBOSE, ("TCON Switching to Standby mode (Level1)")); tcon_currentPowerMode = TCON_POWER_STANDBYMODE; } else if ( AUOCMD_EQUAL(cmd->cmd, AUOCMD_WAKEUP) ) { INFOL(INFO_VERBOSE, ("TCON Switching to Normal mode (Level0)")); tcon_currentPowerMode = TCON_POWER_NORMALMODE; } else if ( AUOCMD_EQUAL(cmd->cmd, AUOCMD_INIT_SET) ) { tcon_inPortraitMode = ~(cmd->params[0]) & (0x1 << 10); if ( tcon_lastModeWas != 4 ) { if ( tcon_inPortraitMode ) tcon_i80bus_set_speed(EN_I80_PORTRAIT, 800, 600, false); else tcon_i80bus_set_speed(EN_I80_LANDSCAPE, 600, 800, false); } else { if ( tcon_inPortraitMode ) tcon_i80bus_set_speed(EN_I80_PORTRAIT_HANDWRITING, 800, 600, false); else tcon_i80bus_set_speed(EN_I80_LANDSCAPE_HANDWRITING, 600, 800, false); } INFOL(INFO_DEBUG, ("Rotation set to 0x%08X...", tcon_inPortraitMode)); } else if ( AUOCMD_EQUAL(cmd->cmd, AUOCMD_DISPLAY_START) ) { INFOL(INFO_DEBUG, ("Display Start (lastMode: %d)...", tcon_lastModeWas)); if ( ((cmd->params[0] >> 12) & 0x07) == 4 ) /* Handwriting mode... */ { if ( tcon_inPortraitMode ) tcon_i80bus_set_speed(EN_I80_PORTRAIT_HANDWRITING, 800, 600, false); else tcon_i80bus_set_speed(EN_I80_LANDSCAPE_HANDWRITING, 600, 800, false); } else if ( tcon_lastModeWas == 4 ) { if ( tcon_inPortraitMode ) tcon_i80bus_set_speed(EN_I80_PORTRAIT, 800, 600, false); else tcon_i80bus_set_speed(EN_I80_LANDSCAPE, 600, 800, false); } tcon_lastModeWas = ((cmd->params[0] >> 12) & 0x07); } /* Second: init the i80 interface */ INFOL(INFO_DEBUG, ("/* Second: init the i80 interface */")); tcon_i80bus_init_interface(); /* Third: Select the chip and set to Command mode */ INFOL(INFO_DEBUG, ("/* Third: Select the chip and set to Command mode */")); tcon_select_chip(); tcon_set_write_to_command(); /* Fourth: Send command */ INFOL(INFO_DEBUG, ("/* Fourth: Send command */")); tcon_i80bus_write(cmd->cmd & 0xFFFF); /* This function already manage * no need to do it here. */ /* Sixth: If parameters is needed, send them */ INFOL(INFO_DEBUG, ("/* Sixth: If parameters is needed, send them */")); if ( GET_COMMAND_PARAM_NUM(cmd->cmd) > 0 ) { int i, paramNumbers = GET_COMMAND_PARAM_NUM(cmd->cmd); INFOL(INFO_DEBUG, ("YES! We have %d parameters", paramNumbers)); tcon_set_write_to_data(); for ( i = 0; i < paramNumbers; i++ ) { INFOL(INFO_DEBUG, (" parameter [%02d] = 0x%04X", i, cmd->params[i])); tcon_i80bus_write(cmd->params[i]); } } FUNC_OUT(); return 0; } static int tcon_send_data (unsigned short *buffer, unsigned long bufferLen) { /* Seventh: Send data if needed */ unsigned long i; //FUNC_IN(); //INFOL(INFO_VERBOSE, ("Bufferlen: %ld", bufferLen)); tcon_set_write_to_data(); for ( i = 0; i < bufferLen; i++ ) { tcon_i80bus_write(buffer[i]); } //FUNC_OUT(); return 0; } static int tcon_send_lut_data (unsigned short *buffer, unsigned long bufferLen) { /* Seventh: Send data if needed */ unsigned long i; FUNC_IN(); INFOL(INFO_DEBUG, ("Bufferlen: %ld", bufferLen)); tcon_set_write_to_data(); for ( i = 0; i < bufferLen; i++ ) { tcon_delay(1); tcon_wait_ready(); tcon_delay(1); tcon_i80bus_write_LUT(buffer[i]); } INFOL(INFO_DEBUG, ("Done.")); FUNC_OUT(); return 0; } static int tcon_read_data (unsigned short *buffer, unsigned long bufferLen) { unsigned long i; FUNC_IN(); //INFOL(INFO_VERBOSE, ("Bufferlen: %ld", bufferLen)); tcon_set_write_to_data(); for ( i = 0; i < bufferLen; i++ ) { tcon_i80bus_read(&(buffer[i])); } FUNC_OUT(); return 0; } static int tcon_send_command_end (sAUOCommand *cmd) { FUNC_IN(); INFOL(INFO_DEBUG, ("cmd #%08lX START:[#%08X]", cmd->cmd, AUOCMD_DISPLAY_START)); if ( AUOCMD_EQUAL(cmd->cmd, AUOCMD_DISPLAY_START) ) { tcon_set_write_to_command(); INFOL(INFO_DEBUG, ("/* Eight: Send STOP command */")); tcon_i80bus_write(AUOCMD_DISPLAY_STOP & 0xFFFF); tcon_set_write_to_data(); } INFOL(INFO_DEBUG, ("/* Nineth: Close all */")); tcon_unselect_chip(); tcon_i80bus_deinit_interface(); FUNC_OUT(); return 0; } // =========================================================================== // Bit Bang SPI Interface // =========================================================================== #define TCON_SPI_CLK S3C2410_GPE13 #define TCON_SPI_DI S3C2410_GPE11 #define TCON_SPI_DO S3C2410_GPE12 #define TCON_SPI_CS S3C2410_GPL13 static int tcon_spi_init (void) { FUNC_IN(); /* First be sure that the Wifi is off */ /* GPK5 = PA 3.3V */ writel((readl(S3C2416_GPKDAT) & ~(1 << 5)), S3C2416_GPKDAT); /* GPK6 = PA 1.8V */ writel((readl(S3C2416_GPKDAT) & ~(1 << 6)), S3C2416_GPKDAT); /* Next, shutdown the TCON */ if ( tcon_set_power_off() != 0 ) return -1; mdelay(2000); //GPD8, RESET s3c2410_gpio_cfgpin(S3C2410_GPD8, S3C2410_GPD8_OUTP); s3c2410_gpio_setpin(S3C2410_GPD8, 0); /* Next, Switch the SPI */ s3c2410_gpio_cfgpin(S3C2410_GPH6, S3C2410_GPH6_OUTP); s3c2410_gpio_setpin(S3C2410_GPH6, 1); mdelay(200); /* Next, init GPIOs */ /* GPL13 = SPI Chip Select */ s3c2410_gpio_cfgpin(S3C2410_GPL13, S3C2410_GPL13_OUTP); s3c2410_gpio_setpin(S3C2410_GPL13, 1); /* GPE11 = SPI DI */ s3c2410_gpio_cfgpin(S3C2410_GPE11, S3C2410_GPE11_INP); s3c2410_gpio_pullup(S3C2410_GPE11, 0); /* GPE12 = SPI DO */ s3c2410_gpio_cfgpin(S3C2410_GPE12, S3C2410_GPE12_OUTP); s3c2410_gpio_setpin(S3C2410_GPE12, 1); /* GPE13 = SPI CLK */ s3c2410_gpio_cfgpin(S3C2410_GPE13, S3C2410_GPE13_OUTP); s3c2410_gpio_setpin(S3C2410_GPE13, 0); mdelay(50); /* We are ready ! */ FUNC_OUT(); return 0; } static void tcon_spi_deinit (void) { FUNC_IN(); /* First, switch the SPI */ s3c2410_gpio_setpin(S3C2410_GPH6, 1); /* Then, restore GPIOs */ s3c2410_gpio_cfgpin(S3C2410_GPL13, S3C2410_GPL13_SS0); /* GPE11 = SPI DI */ s3c2410_gpio_cfgpin(S3C2410_GPE11, S3C2410_GPE11_SPIMISO0); /* GPE12 = SPI DO */ s3c2410_gpio_cfgpin(S3C2410_GPE12, S3C2410_GPE12_SPIMOSI0); /* GPE13 = SPI CLK */ s3c2410_gpio_cfgpin(S3C2410_GPE13, S3C2410_GPE13_SPICLK0); /* Next, switch back on the TCON */ tcon_set_power_on(); s3c2410_gpio_setpin(S3C2410_GPD8, 1); mdelay(100); /* The calling must restore the TCON status, we only set it back on */ FUNC_OUT(); } static inline unsigned char tcon_spi_read (void) { unsigned char k, tmp_data = 0, i = 0; u32 read_pin; #ifdef DEBUG_SPI char strobe_str[9]; strobe_str[8] = 0; #endif /* sure that the clock is low */ for ( k = 0; k < 8; k++ ) { /* Strobe clock */ s3c2410_gpio_setpin(TCON_SPI_CLK, 1); udelay(2); /* Sample */ read_pin = s3c2410_gpio_getpin(TCON_SPI_DI); //(readl(S3C2410_GPEDAT) & (1 << 11)); if ( read_pin ) { i = (0x80 >> k); tmp_data = (tmp_data | i); } #ifdef DEBUG_SPI strobe_str[k] = (read_pin ? '-' : '_'); #endif /* ACK bit read */ s3c2410_gpio_setpin(TCON_SPI_CLK, 0); udelay(2); } #ifdef DEBUG_SPI INFOL(INFO_VERBOSE, ("WFM:[%s] return %d", strobe_str, tmp_data)); #endif return (tmp_data); } static inline void tcon_spi_write (unsigned char value) { unsigned char k; #ifdef DEBUG_SPI char strobe_str[9]; strobe_str[8] = 0; #endif for ( k = 0; k < 8; k++ ) { if ( (value & 0x80) ) s3c2410_gpio_setpin(TCON_SPI_DO, 1); else s3c2410_gpio_setpin(TCON_SPI_DO, 0); #ifdef DEBUG_SPI strobe_str[k] = ((value & 0x80) ? '-' : '_'); #endif /* Strobe clock */ s3c2410_gpio_setpin(TCON_SPI_CLK, 1); udelay(2); s3c2410_gpio_setpin(TCON_SPI_CLK, 0); udelay(2); value = (value << 1); } #ifdef DEBUG_SPI INFOL(INFO_VERBOSE, ("WFM:[%s]", strobe_str)); #endif } static unsigned char tcon_spi_flash_read (unsigned char value) { unsigned char ret; s3c2410_gpio_setpin(TCON_SPI_CS, 0); udelay(50); tcon_spi_write(value); udelay(2); ret = tcon_spi_read(); s3c2410_gpio_setpin(TCON_SPI_CS, 1); udelay(50); return ret; } static void tcon_spi_flash_write (unsigned char value) { s3c2410_gpio_setpin(TCON_SPI_CS, 0); udelay(50); tcon_spi_write(value); s3c2410_gpio_setpin(TCON_SPI_CS, 1); udelay(50); } static void tcon_spi_flash_waitready (void) { unsigned char r_data; FUNC_IN(); do { s3c2410_gpio_setpin(TCON_SPI_CS, 0); udelay(50); tcon_spi_write(0x05); //Write Read Status command udelay(2); r_data = tcon_spi_read(); udelay(50); s3c2410_gpio_setpin(TCON_SPI_CS, 1); INFOL(INFO_DEBUG, ("r_data: 0x%02x [SRWD:%c BP2:%d BP1:%d BP0:%d, WEL:%c, WIP:%c]\n", r_data, (r_data & 0x80) ? 'E' : 'D', (r_data & 0x10) ? 1 : 0, (r_data & 0x08) ? 1 : 0, (r_data & 0x04) ? 1 : 0, (r_data & 0x02) ? 'E' : 'D', (r_data & 0x01) ? 'W' : 'r')); msleep(20); } while ( r_data & 0x01 ); FUNC_OUT(); } static void tcon_spi_flash_writepage (unsigned long addr, unsigned char *buffer, unsigned short buffer_len) { int i; FUNC_IN(); s3c2410_gpio_setpin(TCON_SPI_CS, 0); udelay(50); tcon_spi_write(0x02); //Write Page Program command tcon_spi_write((addr & 0xFF0000) >> 16); tcon_spi_write((addr & 0xFF00) >> 8); tcon_spi_write((addr & 0xFF)); for ( i = 0; i < buffer_len; i++ ) { tcon_spi_write(buffer[i]); } s3c2410_gpio_setpin(TCON_SPI_CS, 1); mdelay(10); tcon_spi_flash_waitready(); FUNC_OUT(); } // =========================================================================== // Device related functions // =========================================================================== static int tcon_open (struct inode *inode, struct file *file) { FUNC_IN(); FUNC_OUT(); return 0; } // --------------------------------------------------------------------------- static int tcon_release (struct inode *inode, struct file *file) { FUNC_IN(); FUNC_OUT(); return 0; } // --------------------------------------------------------------------------- static ssize_t tcon_read (struct file *file, char *buf, size_t count, loff_t *ppos) { FUNC_IN(); FUNC_OUT(); return 0; } // --------------------------------------------------------------------------- static int tcon_command (sAUOCommand *cmd, char userland) { unsigned char buffer[2048]; unsigned char *user_buffer; unsigned long user_buflen, copysize, copysize16; unsigned short *ptr16; //int block_id; int ret = 0; /* Now execute the command */ if ( tcon_send_command_start(cmd) != 0 ) { ret = -EIO; goto exit; } if ( GET_COMMAND_HAVE_DATA(cmd->cmd) != 0 ) { user_buflen = cmd->datalen; user_buffer = (unsigned char *)cmd->data; while ( user_buflen != 0 ) { copysize = user_buflen; if ( user_buflen > sizeof (buffer) ) copysize = sizeof (buffer); if ( GET_COMMAND_READ_WRITE(cmd->cmd) == 0 ) { /* Write mode */ if (userland) { if ( copy_from_user(buffer, user_buffer, copysize) ) { ret = -EFAULT; goto exit; } } else { memcpy(buffer, user_buffer, copysize); } } copysize16 = (copysize + 1) / 2; ptr16 = (unsigned short *)buffer; if ( GET_COMMAND_READ_WRITE(cmd->cmd) == 0 ) { /* Write mode */ if ( cmd->cmd == AUOCMD_LUT_START ) tcon_send_lut_data((unsigned short *)buffer, copysize16); else tcon_send_data((unsigned short *)buffer, copysize16); } else { /* Read mode */ tcon_read_data((unsigned short *)buffer, copysize16); if (userland) { if ( copy_to_user(user_buffer, buffer, copysize) ) return -EFAULT; } else { memcpy(user_buffer, buffer, copysize); } } user_buflen -= copysize; user_buffer += copysize; } } tcon_send_command_end(cmd); exit: return ret; } static int tcon_ioctl (struct inode *inode, struct file *file, unsigned int ioctl_cmd, unsigned long arg) { sAUOCommand cmd; unsigned char buffer[2048]; unsigned char *user_buffer; unsigned long user_buflen, copysize; void __user *argp = (void __user *)arg; int block_id; int flash_addr; int ret = -EINVAL; FUNC_IN(); block_id = 0; //INFOL(INFO_VERBOSE, ("Receive IOTCL #08%X", ioctl_cmd)); switch ( ioctl_cmd ) { case IOCTL_AUO_SENDCOMMAND: if ( copy_from_user(&cmd, argp, sizeof (cmd)) ) return -EFAULT; /* Prevent to send command if the user don't provide a buffer */ if ( (GET_COMMAND_READ_WRITE(cmd.cmd) != 0) && (cmd.datalen == 0) ) return -EFAULT; /* Prevent if we set data and data prt is NULL */ if ( (GET_COMMAND_HAVE_DATA(cmd.cmd) != 0) && (cmd.datalen > 0) && (cmd.data == NULL) ) return -EFAULT; ret = tcon_command(&cmd, true); //INFOL(INFO_VERBOSE, ("tcon_command returned: %d", ret)); break; case IOCTL_AUO_RESET: tcon_reset(); ret = 0; break; case IOCTL_AUO_POWEROFF: // INFOL(INFO_VERBOSE, ("Switching to power OFF (Level3)...")); ret = tcon_set_power_off(); break; case IOCTL_AUO_POWERON: // INFOL(INFO_VERBOSE, ("Switching to power Normal Mode (Level0)...")); tcon_set_power_on(); ret = 0; break; case IOCTL_AUO_WAITBUSY: ret = tcon_wait_ready(); break; case IOCTL_AUO_SLEEP: /* Only accept go to sleep if we are in standby, or else it will fail */ if ( tcon_currentPowerMode == TCON_POWER_STANDBYMODE ) { INFOL(INFO_VERBOSE, ("Switching Standby to Sleep mode (Level2)...")); tcon_currentPowerMode = TCON_POWER_SLEEPMODE; tcon_goto_sleep(); ret = 0; } else { ret = -ENAVAIL; } break; case IOCTL_AUO_WAKEUP: /* Only accept go to sleep if we are in standby, or else it will fail */ if ( tcon_currentPowerMode == TCON_POWER_SLEEPMODE ) { INFOL(INFO_VERBOSE, ("Switching Sleep to Standby Mode (Level1)...")); tcon_currentPowerMode = TCON_POWER_STANDBYMODE; /* Set GPIO accordingly */ tcon_wakeup_sleep(); ret = 0; } else ret = -ENAVAIL; break; case IOCTL_AUO_UPDATEFW: INFOL(INFO_VERBOSE, ("Starting update of TCON firmware...")); if ( copy_from_user(&cmd, argp, sizeof (cmd)) ) { INFOL(INFO_ERROR, ("Copy from User error...")); return -EFAULT; } if ( (cmd.data == NULL) || (cmd.datalen == 0) ) { INFOL(INFO_ERROR, ("Parameters error...")); return -EFAULT; } /* First init SPI */ INFOL(INFO_DEBUG, ("Will init BitBang SPI...")); tcon_spi_init(); mdelay(3000); flash_addr = 0; INFOL(INFO_VERBOSE, ("Erasing SPI flash...")); /* Erase Flash */ tcon_spi_flash_write(0x06); //Setting Write Enable Latch bit tcon_spi_flash_write(0x60); //Write Chip Erase command msleep(100); tcon_spi_flash_waitready(); user_buflen = cmd.datalen; user_buffer = (unsigned char *)cmd.data; INFOL(INFO_VERBOSE, ("Will start write of TCON firmware...")); /* Now, Write new Flash content */ while ( user_buflen != 0 ) { copysize = user_buflen; INFOL(INFO_DEBUG, ("Flash page @ addr 0x%08X", flash_addr)); if ( user_buflen > 256 ) copysize = 256; if ( copy_from_user(buffer, user_buffer, copysize) ) return -EFAULT; /* Send Buffer data */ tcon_spi_flash_write(0x06); /* Enable write... */ tcon_spi_flash_writepage(flash_addr, buffer, copysize); flash_addr += copysize; user_buflen -= copysize; user_buffer += copysize; } /* Restore SPI */ tcon_spi_deinit(); break; case IOCTL_AUO_PROGRESSBAR: { unsigned short *buf; copysize = (arg * 492) / 100; copysize -= (copysize % 8); buf = (unsigned short *)kmalloc(copysize * 24 * 4, GFP_KERNEL); if ( buf ) { memset(buf, 0x00, copysize * 24 * 4); cmd.cmd = AUOCMD_DISPLAY_START; cmd.params[0] = AUO_DSPPARAM_MODE_GRAYnFLASH | ((((600 - 500) / 2) + 4) & 0x0FFF); cmd.params[1] = (((800 - (30 + 20)) + 4) & 0x0FFF); cmd.params[2] = ((copysize) & 0x0FFF); cmd.params[3] = (24 & 0x0FFF); cmd.data = buf; cmd.datalen = ((copysize>>1) * 24) >> 1; tcon_command(&cmd, false); kfree(buf); } ret = 0; } break; default: printk(KERN_WARNING "Invalid IOCTL"); ret = -EINVAL; break; } FUNC_OUT(); return ret; } // =========================================================================== static struct file_operations s_tcon_fops ={ owner : THIS_MODULE, read : tcon_read, ioctl : tcon_ioctl, open : tcon_open, release : tcon_release, }; static struct miscdevice s_tcon_dev ={ .minor = 242, .name = "epaper", .fops = &s_tcon_fops, }; // =========================================================================== // --------------------------------------------------------------------------- static int tcon_probe (struct platform_device *dev) { int ret = -EINVAL; FUNC_IN(); printk("tcon: probe"); if ( GET_CAPABILITY(PLAT_CAP_VTCON) ) { unsigned long tmp; printk(" ok\n"); ret = 0; printk("Cybook Orizon Layout\n"); // set gpio for lcd tmp = __raw_readl(S3C2410_GPCCON); //tmp = (tmp & ~(0xffff03ff))|(0xaaaa02aa); tmp = (tmp & ~(0xffff033f)) | (0xaaaa022a); //Do not config SYS_CS1(GPC3) __raw_writel(tmp, S3C2410_GPCCON); tmp = __raw_readl(S3C2410_GPDCON); //tmp=0x40000 tmp = (tmp & ~(0x0CFFFF)) | (0x04AAAA); __raw_writel(tmp, S3C2410_GPDCON); // GPD9 is RST_N __raw_writel(0, S3C2410_GPCUP); //S3C2410_GPCUP = 0; __raw_writel(0, S3C2410_GPDUP); //S3C2410_GPDUP = 0; #if 0 tcon_i80bus_set_speed(EN_I80_LANDSCAPE, 800, 600, true); // POWER pin config tmp = __raw_readl(S3C2410_GPBCON); tmp = (tmp & ~(3 << 6)) | (1 << 6); __raw_writel(tmp, S3C2410_GPBCON); // BUSY pin config tmp = __raw_readl(S3C2410_GPBCON); tmp = (tmp & ~(3 << 4)); __raw_writel(tmp, S3C2410_GPBCON); // SLEEP pin config tmp = __raw_readl(S3C2410_GPBCON); tmp = (tmp & ~(3 << 2)) | (1 << 2); __raw_writel(tmp, S3C2410_GPBCON); msleep(1); // Panel power on tmp = __raw_readl(S3C2410_GPBDAT); tmp |= (1 << 3); //SLP_N high tmp |= (1 << 1); __raw_writel(tmp, S3C2410_GPBDAT); msleep(100); // LCD module reset tmp = __raw_readl(S3C2410_GPDDAT); tmp |= (1 << 9); __raw_writel(tmp, S3C2410_GPDDAT); tmp = __raw_readl(S3C2410_GPDDAT); // RST_N goes to LOW tmp &= ~(1 << 9); __raw_writel(tmp, S3C2410_GPDDAT); tcon_delay(5); tmp = __raw_readl(S3C2410_GPDDAT); // RST_N goes to HIGH tmp |= (1 << 9); __raw_writel(tmp, S3C2410_GPDDAT); // delay about 10ms msleep(10); tcon_i80bus_set_speed(EN_I80_PORTRAIT, 800, 600, false); #else tcon_set_power_on(); #endif tcon_display_info(); } else printk(" not ok\n"); FUNC_OUTR(ret); return ret; } // --------------------------------------------------------------------------- static int tcon_remove (struct platform_device *dev) { FUNC_IN(); misc_deregister(&s_tcon_dev); FUNC_OUT(); return 0; } // -------------------------------------------------------------------------- static int tcon_resume (struct platform_device *dev) { FUNC_IN(); //tcon_set_power_on(); if ( tcon_inPortraitMode ) tcon_i80bus_set_speed(EN_I80_PORTRAIT, 800, 600, true); else tcon_i80bus_set_speed(EN_I80_LANDSCAPE, 600, 800, true); FUNC_OUT(); return 0; } // -------------------------------------------------------------------------- static int tcon_suspend (struct platform_device *dev, pm_message_t state) { FUNC_IN(); DBG("state event: %X", state.event); //tcon_set_power_off(); FUNC_OUT(); return 0; } // --------------------------------------------------------------------------- static struct platform_driver tcon_driver = { .driver = { .name = "epaper-tcon", .owner = THIS_MODULE, }, .probe = tcon_probe, .remove = tcon_remove, .suspend = tcon_suspend, .resume = tcon_resume, }; // --------------------------------------------------------------------------- // =========================================================================== static int tcon_procReadEpaper (char *page, char **start, off_t off, int count, int *eof, void *data) { int len; struct tcon_info info; tcon_get_info(&info); len = sprintf(page, "tcon_k1900;%d;%d;%d;%d;%d;%d;%d;%d;%d\n", tcon_currentPowerMode, tcon_currentSpeedClass, info.tcon_version, info.lut_version, info.temperature>>1, info.model, info.epd_type, info.panel_size_int, info.panel_size_float); return len; } // =========================================================================== static int __init tcon_init (void) { int ret = 0; FUNC_IN(); printk("tcon: init\n"); if ( misc_register(&s_tcon_dev) ) { ret = -EBUSY; goto exit; } platform_driver_register(&tcon_driver); epaperProcEntry = create_proc_entry("epaper", 0644, proc_root_driver); epaperProcEntry->read_proc = tcon_procReadEpaper; epaperProcEntry->owner = THIS_MODULE; exit: FUNC_OUTR(ret); return ret; } // --------------------------------------------------------------------------- static void __exit tcon_exit (void) { FUNC_IN(); printk("tcon: exit\n"); platform_driver_unregister(&tcon_driver); misc_deregister(&s_tcon_dev); remove_proc_entry("epaper", proc_root_driver); FUNC_OUT(); } // --------------------------------------------------------------------------- module_init (tcon_init); module_exit (tcon_exit); // --------------------------------------------------------------------------- MODULE_LICENSE ("GPL"); MODULE_AUTHOR ("Bookeen "); MODULE_DESCRIPTION ("AUO ePaper TCON Driver"); MODULE_VERSION ("1.0"); // ==========================================================================