cyb4_linux/drivers/video/epaper/auo-tcon-k1900.c
2021-11-21 00:20:20 +00:00

1389 lines
34 KiB
C

/*
* Disclaimer (blabla)
*
* Author: Manoël Trapier <manoelt@bookeen.com>
* Copyright (c) 2003-2010 Bookeen
*
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/pm.h>
#include <linux/proc_fs.h>
#include <asm/hardware.h>
#include <asm/delay.h>
#include <asm/uaccess.h>
#include <asm-arm/irq.h>
#include <asm/arch/gpio.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-irq.h>
#include <asm/arch/regs-lcd.h>
#include <asm-arm/arch-s3c2410/irqs.h>
#include <asm-arm/arch-s3c2410/gpio.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <linux/ioctl.h>
#include <linux/auofb_ioctl.h>
#include <cybook.h>
//#define DEBUG_MESSAGES
//#define DEBUG_TRACEFUNC
//#define VERBOSE_LEVEL INFO_WARNING
//#define DEBUG_SPI
#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_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 Tcon_Speedclasses tcon_currentSpeedClass = EN_I80_NONE;
static Tcon_PowerMode tcon_currentPowerMode = TCON_POWER_NORMALMODE;
// ===========================================================================
// 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();
INFOL(INFO_WARNING, ("speedclass: %d\n", i80_speed));
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);
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_lastModeWas != 4))
tcon_ndelay(25);
else if (( tcon_inPortraitMode ) && (tcon_lastModeWas == 4))
tcon_ndelay(15);
__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_set_power_on(void)
{
unsigned long tmp = __raw_readl(S3C2410_GPBDAT);
tmp |= (1 << 3);
__raw_writel(tmp, S3C2410_GPBDAT);
tcon_currentPowerMode = TCON_POWER_NORMALMODE;
}
static inline void tcon_set_power_off(void)
{
unsigned long tmp = __raw_readl(S3C2410_GPBDAT);
tmp &= ~(1 << 3);
__raw_writel(tmp, S3C2410_GPBDAT);
tcon_currentPowerMode = TCON_POWER_OFF;
}
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..."));
tcon_wait_ready();
}
if (cmd->cmd == AUOCMD_STANDBY)
tcon_currentPowerMode = TCON_POWER_STANDBYMODE;
else if (cmd->cmd == AUOCMD_WAKEUP)
tcon_currentPowerMode = TCON_POWER_NORMALMODE;
else if (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 (cmd->cmd == AUOCMD_DISPLAY_START)
{
INFOL(INFO_VERBOSE, ("Display Start (lastMode: %d)...", tcon_lastModeWas));
if (((cmd->params[0] >> 12) & 0x07) == 4) /* Handwriting mode... */
{
INFOL(INFO_VERBOSE, ("Mode 4"));
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)
{
INFOL(INFO_VERBOSE, ("--- NOT --- mode 4 (%d)", ((cmd->params[0] >> 12) & 0x07)));
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;
}
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_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;
}
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;
}
int tcon_send_command_end(sAUOCommand *cmd)
{
FUNC_IN();
INFOL(INFO_DEBUG,("cmd #%08lX START:[#%08X]", cmd->cmd, AUOCMD_DISPLAY_START));
if (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 void 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 */
tcon_set_power_off();
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();
}
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;
}
// ---------------------------------------------------------------------------
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;
unsigned long tmp;
void __user *argp = (void __user *)arg;
int block_id = 0;
int flash_addr;
int ret = -EINVAL;
FUNC_IN();
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;
/* 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 (GET_COMMAND_READ_WRITE(cmd.cmd) == 0)
{ /* Write mode */
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;
if (GET_COMMAND_READ_WRITE(cmd.cmd) == 0)
{ /* Write mode */
INFOL(INFO_DEBUG, ("Will send block %d", block_id++));
if (cmd.cmd == AUOCMD_LUT_START)
tcon_send_lut_data((unsigned short *)buffer, copysize16);
else
tcon_send_data((unsigned short *)buffer, copysize16);
}
else
{
tcon_read_data((unsigned short *)buffer, copysize16);
if ( copy_to_user(user_buffer, buffer, copysize) )
return -EFAULT;
}
user_buflen -= copysize;
user_buffer += copysize;
}
}
tcon_send_command_end(&cmd);
ret = 0;
break;
case IOCTL_AUO_RESET:
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);
ret = 0;
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)
{
tcon_currentPowerMode = TCON_POWER_SLEEPMODE;
/* Set GPIO accordingly */
tmp = __raw_readl(S3C2410_GPBDAT);
tmp &= ~(1 << 1); //Set SLP_N to low
__raw_writel(tmp, S3C2410_GPBDAT);
msleep(10);
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)
{
tcon_currentPowerMode = TCON_POWER_STANDBYMODE;
/* Set GPIO accordingly */
tmp = __raw_readl(S3C2410_GPBDAT);
tmp |= (1 << 1); //Set SLP_N to high
__raw_writel(tmp, S3C2410_GPBDAT);
msleep(10);
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;
}
INFOL(INFO_VERBOSE, ("Starting update of TCON firmware..."));
if ((cmd.data == NULL) || (cmd.datalen == 0))
{
INFOL(INFO_ERROR, ("Parameters error..."));
return -EFAULT;
}
/* First init SPI */
INFOL(INFO_VERBOSE, ("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;
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;
// 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); // 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
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();
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);
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 <developers@bookeen.com>");
MODULE_DESCRIPTION ("AUO ePaper TCON Driver");
MODULE_VERSION ("1.0");
// ==========================================================================