/* * 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 MODULE_NAME "AUO-TCON" #define BUSY_WAIT_TIMEOUT (40*5*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_DEBUG, INFO_VERBOSE, }; # ifndef VERBOSE_LEVEL # define VERBOSE_LEVEL INFO_VERBOSE # 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; 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; 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, }, [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, }, }; static Tcon_Speedclasses tcon_currentSpeedClass = EN_I80_NONE; // =========================================================================== // 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 ) { return 0; } tmp = __raw_readl(S3C2410_GPBDAT); } return 1; } /* 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_VERBOSE, ("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); 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; tmp = __raw_readl(S3C_SIFCCON0); // nWE enable tmp |= SYS_WR_CON; __raw_writel(tmp, S3C_SIFCCON0); if ( !tcon_inPortraitMode ) tcon_ndelay(25); __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_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(); } int tcon_send_command_start(sAUOCommand *cmd) { FUNC_IN(); INFOL(INFO_VERBOSE,("cmd #%08lX", cmd->cmd)); /* First: verify that the K1900 is ready */ INFOL(INFO_VERBOSE, ("/* First: verify that the K1900 is ready */")); if (GET_COMMAND_NEED_WAIT(cmd->cmd) != 0x00) { INFOL(INFO_VERBOSE, ("Wait for non BUSY...")); tcon_wait_ready(); } if (cmd->cmd == AUOCMD_INIT_SET) { tcon_inPortraitMode = ~(cmd->params[0]) & (0x1 << 10); if (tcon_inPortraitMode) tcon_i80bus_set_speed(EN_I80_PORTRAIT, 800, 600, false); else tcon_i80bus_set_speed(EN_I80_LANDSCAPE, 600, 800, false); INFOL(INFO_VERBOSE, ("Rotation set to 0x%08X...", tcon_inPortraitMode)); } /* Second: init the i80 interface */ INFOL(INFO_VERBOSE, ("/* Second: init the i80 interface */")); tcon_i80bus_init_interface(); /* Third: Select the chip and set to Command mode */ INFOL(INFO_VERBOSE, ("/* Third: Select the chip and set to Command mode */")); tcon_select_chip(); tcon_set_write_to_command(); /* Fourth: Send command */ INFOL(INFO_VERBOSE, ("/* 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_VERBOSE, ("/* 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_VERBOSE, ("YES! We have %d parameters", paramNumbers)); tcon_set_write_to_data(); for (i = 0; i < paramNumbers; i++) { INFOL(INFO_VERBOSE, (" parameter [%02d] = 0x%04X", i, cmd->params[i])); tcon_i80bus_write(cmd->params[i]); } } FUNC_OUT(); return 0; } 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; } int tcon_send_command_end(sAUOCommand *cmd) { FUNC_IN(); INFOL(INFO_VERBOSE,("cmd #%08lX START:[#%08X]", cmd->cmd, AUOCMD_DISPLAY_START)); if (cmd->cmd == AUOCMD_DISPLAY_START) { tcon_set_write_to_command(); INFOL(INFO_VERBOSE, ("/* Eight: Send STOP command */")); tcon_i80bus_write(AUOCMD_DISPLAY_STOP & 0xFFFF); tcon_set_write_to_data(); } INFOL(INFO_VERBOSE, ("/* Nineth: Close all */")); tcon_unselect_chip(); tcon_i80bus_deinit_interface(); FUNC_OUT(); return 0; } // =========================================================================== // 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; } // --------------------------------------------------------------------------- ssize_t tcon_read (struct file *file, char *buf, size_t count, loff_t *ppos) { FUNC_IN(); FUNC_OUT(); return 0; } // --------------------------------------------------------------------------- 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, copysize16; unsigned short *ptr16; void __user *argp = (void __user *)arg; FUNC_IN(); switch ( ioctl_cmd ) { /* [MTR] */ case IOCTL_AUO_SENDCOMMAND: if ( copy_from_user(&cmd, argp, sizeof (cmd)) ) return -EFAULT; /* Now execute the command */ tcon_send_command_start(&cmd); ///INFOL(INFO_VERBOSE, ("/* Seventh: Send data if needed */")); if ( GET_COMMAND_HAVE_DATA(cmd.cmd) != 0 ) { //INFOL(INFO_VERBOSE, ("Yes, we have data to send!")); 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 ( copy_from_user(buffer, user_buffer, copysize) ) return -EFAULT; copysize16 = (copysize + 1) / 2; //printk(KERN_ERR "cp16=%ld cp=%ld\n", copysize16, copysize); ptr16 = (unsigned short *)buffer; tcon_send_data((unsigned short *)buffer, copysize16); user_buflen -= copysize; user_buffer += copysize; } } tcon_send_command_end(&cmd); break; } FUNC_OUT(); return -EINVAL; } // =========================================================================== 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; // 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); printk("Cybook Orizon Layout\n"); 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; 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); // goes to LOW tmp &= ~(1 << 9); __raw_writel(tmp, S3C2410_GPDDAT); tcon_delay(5); tmp = __raw_readl(S3C2410_GPDDAT); // 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 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(); FUNC_OUT(); return 0; } // -------------------------------------------------------------------------- static int tcon_suspend (struct platform_device *dev, pm_message_t state) { FUNC_IN(); DBG("state event: %X", state.event); 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 __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); 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); 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"); // ==========================================================================