diff options
author | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
---|---|---|
committer | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
commit | 849369d6c66d3054688672f97d31fceb8e8230fb (patch) | |
tree | 6135abc790ca67dedbe07c39806591e70eda81ce /drivers/video/mxc | |
download | linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2 linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip |
initial_commit
Diffstat (limited to 'drivers/video/mxc')
53 files changed, 43029 insertions, 0 deletions
diff --git a/drivers/video/mxc/DataType.h b/drivers/video/mxc/DataType.h new file mode 100644 index 00000000..87f4aa48 --- /dev/null +++ b/drivers/video/mxc/DataType.h @@ -0,0 +1,111 @@ +/* + * DataType.h + * + */ + +#ifndef __DATA_TYPE_H__ + #define __DATA_TYPE_H__ + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #define _BIT0 0x00000001 + #define _BIT1 0x00000002 + #define _BIT2 0x00000004 + #define _BIT3 0x00000008 + #define _BIT4 0x00000010 + #define _BIT5 0x00000010 + #define _BIT6 0x00000040 + #define _BIT7 0x00000080 + #define _BIT8 0x00000100 + #define _BIT9 0x00000200 + #define _BIT10 0x00000400 + #define _BIT11 0x00000800 + #define _BIT12 0x00001000 + #define _BIT13 0x00002000 + #define _BIT14 0x00004000 + #define _BIT15 0x00008000 + #define _BIT16 0x00010000 + #define _BIT17 0x00020000 + #define _BIT18 0x00040000 + #define _BIT19 0x00080000 + #define _BIT20 0x00100000 + #define _BIT21 0x00200000 + #define _BIT22 0x00400000 + #define _BIT23 0x00800000 + #define _BIT24 0x01000000 + #define _BIT25 0x02000000 + #define _BIT26 0x04000000 + #define _BIT27 0x08000000 + #define _BIT28 0x10000000 + #define _BIT29 0x20000000 + #define _BIT30 0x40000000 + #define _BIT31 0x80000000 + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + typedef char S8; + typedef char PS8; + typedef unsigned char U8; + typedef unsigned char *PU8; + typedef unsigned char BYTE; + typedef unsigned char *PBYTE; + + typedef short int SINT16; + typedef short int *PSINT16; + typedef unsigned short int UINT16; + typedef unsigned short int *PUINT16; + typedef unsigned short int WORD; + typedef unsigned short int *PWORD; + + typedef int SINT32; + typedef int *PSINT32; + + typedef unsigned int UINT; + typedef unsigned int *PUINT; + typedef unsigned int UINT32; + typedef unsigned int *PUINT32; + typedef unsigned int DWORD; + typedef unsigned int *PDWORD; + + typedef void VOID; + typedef void *PVOID; + + typedef unsigned long ULONG; + typedef unsigned long *PULONG; + + typedef long LONG; + typedef long *PLONG; + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if 1 + typedef int BOOL; +#endif + + #define TRUE 1 + #define FALSE 0 + + typedef int HW_LOGIC; + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #define _HW_LOGIC_HIGH 1 + #define _HW_LOGIC_LOW 0 + + #define K_BYTES(x) (1024*(x)) + #define M_BYTES(x) (1024*1024*(x)) + #define MEM_PAGE_SIZE K_BYTES(4) + +#endif /* __DATA_TYPE_H__ */ diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig new file mode 100644 index 00000000..73427a82 --- /dev/null +++ b/drivers/video/mxc/Kconfig @@ -0,0 +1,153 @@ +config FB_MXC + tristate "MXC Framebuffer support" + depends on FB && (MXC_IPU || ARCH_MX21 || ARCH_MX27 || ARCH_MX25 || ARCH_MX6) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + default y + help + This is a framebuffer device for the MXC LCD Controller. + See <http://www.linux-fbdev.org/> for information on framebuffer + devices. + + If you plan to use the LCD display with your MXC system, say + Y here. + +config FB_MXC_EDID + depends on FB_MXC && I2C + tristate "MXC EDID support" + default y + +config FB_MXC_SYNC_PANEL + depends on FB_MXC + tristate "Synchronous Panel Framebuffer" + default y + +config FB_MXC_EPSON_VGA_SYNC_PANEL + depends on FB_MXC_SYNC_PANEL + tristate "Epson VGA Panel" + default n + +config FB_MXC_TVOUT_TVE + tristate "MXC TVE TV Out Encoder" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 && !ARCH_MX6Q + +config FB_MXC_LDB + tristate "MXC LDB" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 + +config FB_MXC_MIPI_DSI + tristate "MXC MIPI_DSI" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 + +config FB_MXC_TRULY_WVGA_SYNC_PANEL + tristate "TRULY WVGA Panel" + depends on FB_MXC_SYNC_PANEL && FB_MXC_MIPI_DSI + +config FB_MXC_CLAA_WVGA_SYNC_PANEL + depends on FB_MXC_SYNC_PANEL + tristate "CLAA WVGA Panel" + +config FB_MXC_SEIKO_WVGA_SYNC_PANEL + depends on FB_MXC_SYNC_PANEL + tristate "SEIKO WVGA Panel" + +config FB_MXC_SII902X + depends on FB_MXC_SYNC_PANEL && I2C + tristate "Si Image SII9022 DVI/HDMI Interface Chip" + +config FB_MXC_SII902X_ELCDIF + depends on FB_MXC_ELCDIF_FB && FB_MXC_SYNC_PANEL && I2C + tristate "Si Image SII9022 DVI/HDMI Interface Chip for ELCDIF FB" + +config FB_MXC_CH7026 + depends on FB_MXC_SYNC_PANEL + tristate "Chrontel CH7026 VGA Interface Chip" + +config FB_MXC_TVOUT_CH7024 + tristate "CH7024 TV Out Encoder" + depends on FB_MXC_SYNC_PANEL + +config FB_MXC_LOW_PWR_DISPLAY + bool "Low Power Display Refresh Mode" + depends on FB_MXC_SYNC_PANEL && MXC_FB_IRAM + default y + +config FB_MXC_INTERNAL_MEM + bool "Framebuffer in Internal RAM" + depends on FB_MXC_SYNC_PANEL && MXC_FB_IRAM + default y + +config FB_MXC_ASYNC_PANEL + depends on FB_MXC + bool "Asynchronous Panels" + default n + +menu "Asynchronous Panel Type" + depends on FB_MXC_ASYNC_PANEL && FB_MXC + +config FB_MXC_EPSON_PANEL + depends on FB_MXC_ASYNC_PANEL + default n + bool "Epson 176x220 Panel" + +endmenu + +config FB_MXC_EINK_PANEL + depends on FB_MXC + depends on DMA_ENGINE + select FB_DEFERRED_IO + tristate "E-Ink Panel Framebuffer" + +config FB_MXC_EINK_REGAL + depends on FB_MXC_EINK_PANEL + bool "E-Ink REGAL support" + +config FB_MXC_EINK_AUTO_UPDATE_MODE + bool "E-Ink Auto-update Mode Support" + default n + depends on FB_MXC_EINK_PANEL + +config FB_MXC_SIPIX_PANEL + depends on FB_MXC + depends on DMA_ENGINE + select FB_DEFERRED_IO + tristate "SIPIX Panel Framebuffer" + +config FB_MXC_SIPIX_AUTO_UPDATE_MODE + bool "SIPIX Auto-update Mode Support" + default n + depends on FB_MXC_SIPIX_PANEL + +config FB_MXC_ELCDIF_FB + depends on FB && ARCH_MXC + tristate "Support MXC ELCDIF framebuffer" + select IMX_HAVE_PLATFORM_IMX_ELCDIF + +config IMX_HAVE_PLATFORM_IMX_ELCDIF + bool "IMX_HAVE_PLATFORM_IMX_ELCDIF" + default n + depends on FB_MXC_ELCDIF_FB + help + + + +choice + prompt "Async Panel Interface Type" + depends on FB_MXC_ASYNC_PANEL && FB_MXC + default FB_MXC_ASYNC_PANEL_IFC_16_BIT + +config FB_MXC_ASYNC_PANEL_IFC_8_BIT + bool "8-bit Parallel Bus Interface" + +config FB_MXC_ASYNC_PANEL_IFC_16_BIT + bool "16-bit Parallel Bus Interface" + +config FB_MXC_ASYNC_PANEL_IFC_SERIAL + bool "Serial Bus Interface" + +endchoice diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile new file mode 100644 index 00000000..f186b7f8 --- /dev/null +++ b/drivers/video/mxc/Makefile @@ -0,0 +1,39 @@ +obj-$(CONFIG_FB_MXC_TVOUT_TVE) += tve.o +obj-$(CONFIG_FB_MXC_SII902X) += mxcfb_sii902x.o +obj-$(CONFIG_FB_MXC_SII902X_ELCDIF) += mxcfb_sii902x_elcdif.o +obj-$(CONFIG_FB_MXC_LDB) += ldb.o +obj-$(CONFIG_FB_MXC_MIPI_DSI) += mipi_dsi.o +obj-$(CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL) += mxcfb_hx8369_wvga.o +obj-$(CONFIG_FB_MXC_EDID) += mxc_edid.o +ifeq ($(CONFIG_ARCH_MX21)$(CONFIG_ARCH_MX27)$(CONFIG_ARCH_MX25),y) + obj-$(CONFIG_FB_MXC_TVOUT) += fs453.o + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mx2fb.o mxcfb_modedb.o + obj-$(CONFIG_FB_MXC_EPSON_PANEL) += mx2fb_epson.o +else +ifeq ($(CONFIG_MXC_IPU_V1),y) + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxcfb.o mxcfb_modedb.o +endif +ifeq ($(CONFIG_MXC_IPU_V3),y) + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_dispdrv.o mxc_lcdif.o mxc_ipuv3_fb.o + obj-$(CONFIG_FB_MXC_EDID) += mxc_dvi.o +endif + obj-$(CONFIG_FB_MXC_EPSON_PANEL) += mxcfb_epson.o + obj-$(CONFIG_FB_MXC_EPSON_QVGA_PANEL) += mxcfb_epson_qvga.o + obj-$(CONFIG_FB_MXC_TOSHIBA_QVGA_PANEL) += mxcfb_toshiba_qvga.o + obj-$(CONFIG_FB_MXC_SHARP_128_PANEL) += mxcfb_sharp_128x128.o +endif +obj-$(CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL) += mxcfb_epson_vga.o +obj-$(CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL) += mxcfb_claa_wvga.o +obj-$(CONFIG_FB_MXC_SEIKO_WVGA_SYNC_PANEL) += mxcfb_seiko_wvga.o +obj-$(CONFIG_FB_MXC_TVOUT_CH7024) += ch7024.o +obj-$(CONFIG_FB_MXC_CH7026) += mxcfb_ch7026.o +obj-$(CONFIG_FB_MXC_EINK_PANEL) += mxc_epdc_fb.o mxc_epdc_eink.o +ifeq ($(CONFIG_FB_MXC_EINK_REGAL),y) +mxc_epdc_eink-objs := mxc_epdc_eink_module.o lib.a +else +mxc_epdc_eink-objs := mxc_epdc_eink_module.o eink_processing2.o +endif +obj-$(CONFIG_FB_MXC_SIPIX_PANEL) += mxc_spdc_fb.o +obj-$(CONFIG_FB_MXC_ELCDIF_FB) += mxc_elcdif_fb.o +obj-y += lk_tps65185.o lk_fp9928.o fake_s1d13522.o epdfb_dc.o + diff --git a/drivers/video/mxc/ch7024.c b/drivers/video/mxc/ch7024.c new file mode 100644 index 00000000..32ca332d --- /dev/null +++ b/drivers/video/mxc/ch7024.c @@ -0,0 +1,866 @@ +/* + * Copyright 2007-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ch7024.c + * @brief Driver for CH7024 TV encoder + * + * @ingroup Framebuffer + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sysfs.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <asm/uaccess.h> +#include <asm/atomic.h> +#include <mach/gpio.h> +#include <mach/hw_events.h> + +/*! + * CH7024 registers + */ +#define CH7024_DEVID 0x00 +#define CH7024_REVID 0x01 +#define CH7024_PG 0x02 + +#define CH7024_RESET 0x03 +#define CH7024_POWER 0x04 +#define CH7024_TVHUE 0x05 +#define CH7024_TVSAT 0x06 +#define CH7024_TVCTA 0x07 +#define CH7024_TVBRI 0x08 +#define CH7024_TVSHARP 0x09 +#define CH7024_OUT_FMT 0x0A +#define CH7024_XTAL 0x0B +#define CH7024_IDF1 0x0C +#define CH7024_IDF2 0x0D +#define CH7024_SYNC 0x0E +#define CH7024_TVFILTER1 0x0F +#define CH7024_TVFILTER2 0x10 +#define CH7024_IN_TIMING1 0x11 +#define CH7024_IN_TIMING2 0x12 +#define CH7024_IN_TIMING3 0x13 +#define CH7024_IN_TIMING4 0x14 +#define CH7024_IN_TIMING5 0x15 +#define CH7024_IN_TIMING6 0x16 +#define CH7024_IN_TIMING7 0x17 +#define CH7024_IN_TIMING8 0x18 +#define CH7024_IN_TIMING9 0x19 +#define CH7024_IN_TIMING10 0x1A +#define CH7024_IN_TIMING11 0x1B +#define CH7024_ACIV 0x1C +#define CH7024_CLK_TREE 0x1D +#define CH7024_OUT_TIMING1 0x1E +#define CH7024_OUT_TIMING2 0x1F +#define CH7024_V_POS1 0x20 +#define CH7024_V_POS2 0x21 +#define CH7024_H_POS1 0x22 +#define CH7024_H_POS2 0x23 +#define CH7024_PCLK_A1 0x24 +#define CH7024_PCLK_A2 0x25 +#define CH7024_PCLK_A3 0x26 +#define CH7024_PCLK_A4 0x27 +#define CH7024_CLK_P1 0x28 +#define CH7024_CLK_P2 0x29 +#define CH7024_CLK_P3 0x2A +#define CH7024_CLK_N1 0x2B +#define CH7024_CLK_N2 0x2C +#define CH7024_CLK_N3 0x2D +#define CH7024_CLK_T 0x2E +#define CH7024_PLL1 0x2F +#define CH7024_PLL2 0x30 +#define CH7024_PLL3 0x31 +#define CH7024_SC_FREQ1 0x34 +#define CH7024_SC_FREQ2 0x35 +#define CH7024_SC_FREQ3 0x36 +#define CH7024_SC_FREQ4 0x37 +#define CH7024_DAC_TRIM 0x62 +#define CH7024_DATA_IO 0x63 +#define CH7024_ATT_DISP 0x7E + +/*! + * CH7024 register values + */ +/* video output formats */ +#define CH7024_VOS_NTSC_M 0x0 +#define CH7024_VOS_NTSC_J 0x1 +#define CH7024_VOS_NTSC_443 0x2 +#define CH7024_VOS_PAL_BDGHKI 0x3 +#define CH7024_VOS_PAL_M 0x4 +#define CH7024_VOS_PAL_N 0x5 +#define CH7024_VOS_PAL_NC 0x6 +#define CH7024_VOS_PAL_60 0x7 +/* crystal predefined */ +#define CH7024_XTAL_13MHZ 0x4 +#define CH7024_XTAL_26MHZ 0xB + +/* chip ID */ +#define CH7024_DEVICE_ID 0x45 + +/* clock source define */ +#define CLK_HIGH 0 +#define CLK_LOW 1 + +/* CH7024 presets structs */ +struct ch7024_clock { + u32 A; + u32 P; + u32 N; + u32 T; + u8 PLLN1; + u8 PLLN2; + u8 PLLN3; +}; + +struct ch7024_input_timing { + u32 HTI; + u32 VTI; + u32 HAI; + u32 VAI; + u32 HW; + u32 HO; + u32 VW; + u32 VO; + u32 VOS; +}; + +#define TVOUT_FMT_OFF 0 +#define TVOUT_FMT_NTSC 1 +#define TVOUT_FMT_PAL 2 + +static int enabled; /* enable power on or not */ +static int pm_status; /* status before suspend */ + +static struct i2c_client *ch7024_client; +static struct fb_info *ch7024_fbi; +static int ch7024_cur_mode; +static u32 detect_gpio; +static struct regulator *io_reg; +static struct regulator *core_reg; +static struct regulator *analog_reg; + +static void hp_detect_wq_handler(struct work_struct *); +DECLARE_DELAYED_WORK(ch7024_wq, hp_detect_wq_handler); + +static inline int ch7024_read_reg(u8 reg) +{ + return i2c_smbus_read_byte_data(ch7024_client, reg); +} + +static inline int ch7024_write_reg(u8 reg, u8 word) +{ + return i2c_smbus_write_byte_data(ch7024_client, reg, word); +} + +/** + * PAL B/D/G/H/K/I clock and timting structures + */ +static struct ch7024_clock ch7024_clk_pal = { + .A = 0x0, + .P = 0x36b00, + .N = 0x41eb00, + .T = 0x3f, + .PLLN1 = 0x0, + .PLLN2 = 0x1b, + .PLLN3 = 0x12, +}; + +static struct ch7024_input_timing ch7024_timing_pal = { + .HTI = 950, + .VTI = 560, + .HAI = 640, + .VAI = 480, + .HW = 60, + .HO = 250, + .VW = 40, + .VO = 40, + .VOS = CH7024_VOS_PAL_BDGHKI, +}; + +/** + * NTSC_M clock and timting structures + * TODO: change values to work well. + */ +static struct ch7024_clock ch7024_clk_ntsc = { + .A = 0x0, + .P = 0x2ac90, + .N = 0x36fc90, + .T = 0x3f, + .PLLN1 = 0x0, + .PLLN2 = 0x1b, + .PLLN3 = 0x12, +}; + +static struct ch7024_input_timing ch7024_timing_ntsc = { + .HTI = 801, + .VTI = 554, + .HAI = 640, + .VAI = 480, + .HW = 60, + .HO = 101, + .VW = 20, + .VO = 54, + .VOS = CH7024_VOS_NTSC_M, +}; + +static struct fb_videomode video_modes[] = { + { + /* NTSC TV output */ + "TV-NTSC", 60, 640, 480, 37594, + 0, 101, + 0, 54, + 60, 20, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* PAL TV output */ + "TV-PAL", 50, 640, 480, 37594, + 0, 250, + 0, 40, + 60, 40, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +/** + * ch7024_setup + * initial the CH7024 chipset by setting register + * @param: + * vos: output video format + * @return: + * 0 successful + * otherwise failed + */ +static int ch7024_setup(int vos) +{ + struct ch7024_input_timing *ch_timing; + struct ch7024_clock *ch_clk; +#ifdef DEBUG_CH7024 + int i, val; +#endif + + /* select output video format */ + if (vos == TVOUT_FMT_PAL) { + ch_timing = &ch7024_timing_pal; + ch_clk = &ch7024_clk_pal; + pr_debug("CH7024: change to PAL video\n"); + } else if (vos == TVOUT_FMT_NTSC) { + ch_timing = &ch7024_timing_ntsc; + ch_clk = &ch7024_clk_ntsc; + pr_debug("CH7024: change to NTSC video\n"); + } else { + + pr_debug("CH7024: no such video format.\n"); + return -EINVAL; + } + ch7024_write_reg(CH7024_RESET, 0x0); + ch7024_write_reg(CH7024_RESET, 0x3); + + ch7024_write_reg(CH7024_POWER, 0x0C); /* power on, disable DAC */ + ch7024_write_reg(CH7024_XTAL, CH7024_XTAL_26MHZ); + ch7024_write_reg(CH7024_SYNC, 0x0D); /* SLAVE mode, and TTL */ + ch7024_write_reg(CH7024_IDF1, 0x00); + ch7024_write_reg(CH7024_TVFILTER1, 0x00); /* set XCH=0 */ + ch7024_write_reg(CH7024_CLK_TREE, 0x9E); /* Invert input clk */ + + /* set input clock and divider */ + /* set PLL */ + ch7024_write_reg(CH7024_PLL1, ch_clk->PLLN1); + ch7024_write_reg(CH7024_PLL2, ch_clk->PLLN2); + ch7024_write_reg(CH7024_PLL3, ch_clk->PLLN3); + /* set A register */ + ch7024_write_reg(CH7024_PCLK_A1, (ch_clk->A >> 24) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A2, (ch_clk->A >> 16) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A3, (ch_clk->A >> 8) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A4, ch_clk->A & 0xFF); + /* set P register */ + ch7024_write_reg(CH7024_CLK_P1, (ch_clk->P >> 16) & 0xFF); + ch7024_write_reg(CH7024_CLK_P2, (ch_clk->P >> 8) & 0xFF); + ch7024_write_reg(CH7024_CLK_P3, ch_clk->P & 0xFF); + /* set N register */ + ch7024_write_reg(CH7024_CLK_N1, (ch_clk->N >> 16) & 0xFF); + ch7024_write_reg(CH7024_CLK_N2, (ch_clk->N >> 8) & 0xFF); + ch7024_write_reg(CH7024_CLK_N3, ch_clk->N & 0xFF); + /* set T register */ + ch7024_write_reg(CH7024_CLK_T, ch_clk->T & 0xFF); + + /* set sub-carrier frequency generation method */ + ch7024_write_reg(CH7024_ACIV, 0x00); /* ACIV = 0, automatical SCF */ + /* TV out pattern and DAC switch */ + ch7024_write_reg(CH7024_OUT_FMT, (0x10 | ch_timing->VOS) & 0xFF); + + /* input settings */ + /* input format, RGB666 */ + ch7024_write_reg(CH7024_IDF2, 0x02); + /* HAI/HTI VAI */ + ch7024_write_reg(CH7024_IN_TIMING1, ((ch_timing->HTI >> 5) & 0x38) | + ((ch_timing->HAI >> 8) & 0x07)); + ch7024_write_reg(CH7024_IN_TIMING2, ch_timing->HAI & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING8, ch_timing->VAI & 0xFF); + /* HTI VTI */ + ch7024_write_reg(CH7024_IN_TIMING3, ch_timing->HTI & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING9, ch_timing->VTI & 0xFF); + /* HW/HO(h) VW */ + ch7024_write_reg(CH7024_IN_TIMING4, ((ch_timing->HW >> 5) & 0x18) | + ((ch_timing->HO >> 8) & 0x7)); + ch7024_write_reg(CH7024_IN_TIMING6, ch_timing->HW & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING11, ch_timing->VW & 0x3F); + /* HO(l) VO/VAI/VTI */ + ch7024_write_reg(CH7024_IN_TIMING5, ch_timing->HO & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING7, ((ch_timing->VO >> 4) & 0x30) | + ((ch_timing->VTI >> 6) & 0x0C) | + ((ch_timing->VAI >> 8) & 0x03)); + ch7024_write_reg(CH7024_IN_TIMING10, ch_timing->VO & 0xFF); + + /* adjust the brightness */ + ch7024_write_reg(CH7024_TVBRI, 0x90); + + ch7024_write_reg(CH7024_OUT_TIMING1, 0x4); + ch7024_write_reg(CH7024_OUT_TIMING2, 0xe0); + + if (vos == TVOUT_FMT_PAL) { + ch7024_write_reg(CH7024_V_POS1, 0x03); + ch7024_write_reg(CH7024_V_POS2, 0x7d); + } else { + ch7024_write_reg(CH7024_V_POS1, 0x02); + ch7024_write_reg(CH7024_V_POS2, 0x7b); + } + + ch7024_write_reg(CH7024_POWER, 0x00); + +#ifdef DEBUG_CH7024 + for (i = 0; i < CH7024_SC_FREQ4; i++) { + + val = ch7024_read_reg(i); + pr_debug("CH7024, reg[0x%x] = %x\n", i, val); + } +#endif + return 0; +} + +/** + * ch7024_enable + * Enable the ch7024 Power to begin TV encoder + */ +static int ch7024_enable(void) +{ + int en = enabled; + + if (!enabled) { + regulator_enable(core_reg); + regulator_enable(io_reg); + regulator_enable(analog_reg); + msleep(200); + enabled = 1; + ch7024_write_reg(CH7024_POWER, 0x00); + pr_debug("CH7024 power on.\n"); + } + return en; +} + +/** + * ch7024_disable + * Disable the ch7024 Power to stop TV encoder + */ +static void ch7024_disable(void) +{ + if (enabled) { + enabled = 0; + ch7024_write_reg(CH7024_POWER, 0x0D); + regulator_disable(analog_reg); + regulator_disable(io_reg); + regulator_disable(core_reg); + pr_debug("CH7024 power off.\n"); + } +} + +static int ch7024_detect(void) +{ + int en; + int detect = 0; + + if (gpio_get_value(detect_gpio) == 1) { + set_irq_type(ch7024_client->irq, IRQF_TRIGGER_FALLING); + + en = ch7024_enable(); + + ch7024_write_reg(CH7024_DAC_TRIM, 0xB4); + msleep(50); + detect = ch7024_read_reg(CH7024_ATT_DISP) & 0x3; + ch7024_write_reg(CH7024_DAC_TRIM, 0x34); + + if (!en) + ch7024_disable(); + } else { + set_irq_type(ch7024_client->irq, IRQF_TRIGGER_RISING); + } + dev_dbg(&ch7024_client->dev, "detect = %d\n", detect); + return detect; +} + +static irqreturn_t hp_detect_handler(int irq, void *data) +{ + disable_irq(irq); + schedule_delayed_work(&ch7024_wq, 50); + + return IRQ_HANDLED; +} + +static void hp_detect_wq_handler(struct work_struct *work) +{ + int detect; + struct mxc_hw_event event = { HWE_PHONEJACK_PLUG, 0 }; + + detect = ch7024_detect(); + + enable_irq(ch7024_client->irq); + + sysfs_notify(&ch7024_client->dev.kobj, NULL, "headphone"); + + /* send hw event by netlink */ + event.args = detect; + hw_event_send(1, &event); +} + +int ch7024_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + if ((ch7024_fbi != NULL) || strcmp(fbi->fix.id, "DISP3 BG")) + break; + + ch7024_fbi = fbi; + fb_add_videomode(&video_modes[0], &ch7024_fbi->modelist); + fb_add_videomode(&video_modes[1], &ch7024_fbi->modelist); + break; + case FB_EVENT_MODE_CHANGE: + if (ch7024_fbi != fbi) + break; + + if (!fbi->mode) { + ch7024_disable(); + ch7024_cur_mode = TVOUT_FMT_OFF; + return 0; + } + + if (fb_mode_is_equal(fbi->mode, &video_modes[0])) { + ch7024_cur_mode = TVOUT_FMT_NTSC; + ch7024_enable(); + ch7024_setup(TVOUT_FMT_NTSC); + } else if (fb_mode_is_equal(fbi->mode, &video_modes[1])) { + ch7024_cur_mode = TVOUT_FMT_PAL; + ch7024_enable(); + ch7024_setup(TVOUT_FMT_PAL); + } else { + ch7024_disable(); + ch7024_cur_mode = TVOUT_FMT_OFF; + return 0; + } + break; + case FB_EVENT_BLANK: + if ((ch7024_fbi != fbi) || (ch7024_cur_mode == TVOUT_FMT_OFF)) + return 0; + + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + ch7024_enable(); + ch7024_setup(ch7024_cur_mode); + } else { + ch7024_disable(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = ch7024_fb_event, +}; + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + int detect; + + detect = ch7024_detect(); + + if (detect == 0) { + strcpy(buf, "none\n"); + } else if (detect == 1) { + strcpy(buf, "cvbs\n"); + } else { + strcpy(buf, "headset\n"); + } + + return strlen(buf); +} + +DRIVER_ATTR(headphone, 0644, show_headphone, NULL); + +static ssize_t show_brightness(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVBRI); + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_brightness(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int brightness = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + if (brightness > 255) + brightness = 255; + + ch7024_write_reg(CH7024_TVBRI, brightness); + + return count; +} + +DRIVER_ATTR(brightness, 0644, show_brightness, store_brightness); + +static ssize_t show_contrast(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVCTA); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_contrast(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int contrast = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + contrast /= 2; + if (contrast > 127) + contrast = 127; + + ch7024_write_reg(CH7024_TVCTA, contrast); + + return count; +} + +DRIVER_ATTR(contrast, 0644, show_contrast, store_contrast); + +static ssize_t show_hue(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVHUE); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_hue(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int hue = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + hue /= 2; + if (hue > 127) + hue = 127; + + ch7024_write_reg(CH7024_TVHUE, hue); + + return count; +} + +DRIVER_ATTR(hue, 0644, show_hue, store_hue); + +static ssize_t show_saturation(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVSAT); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_saturation(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int saturation = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + saturation /= 2; + if (saturation > 127) + saturation = 127; + + ch7024_write_reg(CH7024_TVSAT, saturation); + + return count; +} + +DRIVER_ATTR(saturation, 0644, show_saturation, store_saturation); + +static ssize_t show_sharpness(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVSHARP); + + reg *= 32; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_sharpness(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int sharpness = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + sharpness /= 32; /* Scale to 0 - 7 */ + if (sharpness > 7) + sharpness = 7; + + ch7024_write_reg(CH7024_TVSHARP, sharpness); + + return count; +} + +DRIVER_ATTR(sharpness, 0644, show_sharpness, store_sharpness); + +static int ch7024_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) +{ + int ret, i; + u32 id; + u32 irqtype; + struct mxc_tvout_platform_data *plat_data = client->dev.platform_data; + + ch7024_client = client; + + io_reg = regulator_get(&client->dev, plat_data->io_reg); + core_reg = regulator_get(&client->dev, plat_data->core_reg); + analog_reg = regulator_get(&client->dev, plat_data->analog_reg); + + regulator_enable(io_reg); + regulator_enable(core_reg); + regulator_enable(analog_reg); + msleep(200); + + id = ch7024_read_reg(CH7024_DEVID); + + regulator_disable(core_reg); + regulator_disable(io_reg); + regulator_disable(analog_reg); + + if (id < 0 || id != CH7024_DEVICE_ID) { + printk(KERN_ERR + "ch7024: TV encoder not present: id = %x\n", id); + return -ENODEV; + } + printk(KERN_ERR "ch7024: TV encoder present: id = %x\n", id); + + detect_gpio = plat_data->detect_line; + + if (client->irq > 0) { + if (ch7024_detect() == 0) + irqtype = IRQF_TRIGGER_RISING; + else + irqtype = IRQF_TRIGGER_FALLING; + + ret = request_irq(client->irq, hp_detect_handler, irqtype, + client->name, client); + if (ret < 0) + goto err0; + + ret = driver_create_file(&client->driver->driver, + &driver_attr_headphone); + if (ret < 0) + goto err1; + } + + ret = driver_create_file(&client->driver->driver, + &driver_attr_brightness); + if (ret) + goto err2; + + ret = driver_create_file(&client->driver->driver, + &driver_attr_contrast); + if (ret) + goto err3; + ret = driver_create_file(&client->driver->driver, &driver_attr_hue); + if (ret) + goto err4; + ret = driver_create_file(&client->driver->driver, + &driver_attr_saturation); + if (ret) + goto err5; + ret = driver_create_file(&client->driver->driver, + &driver_attr_sharpness); + if (ret) + goto err6; + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0) { + ch7024_fbi = registered_fb[i]; + break; + } + } + if (ch7024_fbi != NULL) { + fb_add_videomode(&video_modes[0], &ch7024_fbi->modelist); + fb_add_videomode(&video_modes[1], &ch7024_fbi->modelist); + } + fb_register_client(&nb); + + return 0; + err6: + driver_remove_file(&client->driver->driver, &driver_attr_saturation); + err5: + driver_remove_file(&client->driver->driver, &driver_attr_hue); + err4: + driver_remove_file(&client->driver->driver, &driver_attr_contrast); + err3: + driver_remove_file(&client->driver->driver, &driver_attr_brightness); + err2: + driver_remove_file(&client->driver->driver, &driver_attr_headphone); + err1: + free_irq(client->irq, client); + err0: + return ret; +} + +static int ch7024_remove(struct i2c_client *client) +{ + free_irq(client->irq, client); + + regulator_put(io_reg); + regulator_put(core_reg); + regulator_put(analog_reg); + + driver_remove_file(&client->driver->driver, &driver_attr_headphone); + driver_remove_file(&client->driver->driver, &driver_attr_brightness); + driver_remove_file(&client->driver->driver, &driver_attr_contrast); + driver_remove_file(&client->driver->driver, &driver_attr_hue); + driver_remove_file(&client->driver->driver, &driver_attr_saturation); + driver_remove_file(&client->driver->driver, &driver_attr_sharpness); + + fb_unregister_client(&nb); + + ch7024_client = 0; + + return 0; +} + +#ifdef CONFIG_PM +/*! + * PM suspend/resume routing + */ +static int ch7024_suspend(struct i2c_client *client, pm_message_t state) +{ + pr_debug("Ch7024 suspend routing..\n"); + if (enabled) { + ch7024_disable(); + pm_status = 1; + } else { + pm_status = 0; + } + return 0; +} + +static int ch7024_resume(struct i2c_client *client) +{ + pr_debug("Ch7024 resume routing..\n"); + if (pm_status) { + ch7024_enable(); + ch7024_setup(ch7024_cur_mode); + } + return 0; +} +#else +#define ch7024_suspend NULL +#define ch7024_resume NULL +#endif + +static const struct i2c_device_id ch7024_id[] = { + { "ch7024", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ch7024_id); + +static struct i2c_driver ch7024_driver = { + .driver = { + .name = "ch7024", + }, + .probe = ch7024_probe, + .remove = ch7024_remove, + .suspend = ch7024_suspend, + .resume = ch7024_resume, + .id_table = ch7024_id, +}; + +static int __init ch7024_init(void) +{ + return i2c_add_driver(&ch7024_driver); +} + +static void __exit ch7024_exit(void) +{ + i2c_del_driver(&ch7024_driver); +} + +module_init(ch7024_init); +module_exit(ch7024_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CH7024 TV encoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/eink_processing2.c b/drivers/video/mxc/eink_processing2.c new file mode 100644 index 00000000..bb814793 --- /dev/null +++ b/drivers/video/mxc/eink_processing2.c @@ -0,0 +1,103 @@ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/mxcfb.h> +#include <linux/mxcfb_epdc_kernel.h> + + +int do_aa_processing_v2_2_1( + unsigned short *working_buffer_ptr_in, + unsigned short *working_buffer_ptr_out, + struct mxcfb_rect *update_region, + long working_buffer_width, + long working_buffer_height) +{ + return -1; +} +int do_aad_processing_v2_1_1( + unsigned short *working_buffer_ptr_in, + unsigned short *working_buffer_ptr_out, + struct mxcfb_rect *update_region, + long working_buffer_width, + long working_buffer_height) +{ + return -1; +} +int do_aa_processing_v2_2_0( + unsigned char *update_region_ptr, struct mxcfb_rect *update_region, + unsigned long update_region_stride, unsigned short *working_buffer_ptr, + long working_buffer_width, long working_buffer_height) +{ + return -1; +} +int do_aad_processing_v2_1_0( + unsigned char *update_region_ptr, struct mxcfb_rect *update_region, + unsigned long update_region_stride, unsigned short *working_buffer_ptr, + long working_buffer_width, long working_buffer_height) +{ + return -1; +} +void set_aad_update_counter(unsigned long value) +{ + return ; +} + +int mxc_epdc_fb_prep_algorithm_data( unsigned char *waveform_acd_buffer, + unsigned wMode, + unsigned wTempRange, + unsigned waveform_mc, + unsigned waveform_trc, + unsigned waveform_magic_number, + unsigned ctrlFlags) +{ + return 0; +} + +/* function for handing the extra waveform information */ +int mxc_epdc_fb_fetch_wxi_data( unsigned char *waveform_xwi_buffer, char *xwiBuffer) +{ + if (xwiBuffer != NULL) { + memcpy(xwiBuffer, &waveform_xwi_buffer[1], (size_t) waveform_xwi_buffer[0]); + } + return (int) waveform_xwi_buffer[0]; +} + +/* function for copying the voltage control data to the allocated buffer */ +int mxc_epdc_fb_fetch_vc_data( unsigned char *waveform_vcd_buffer, + unsigned wMode, + unsigned wTempRange, + unsigned waveform_mc, + unsigned waveform_trc, + unsigned char *VoltageCtrlData) +{ + if ((wMode >= waveform_mc) || (wTempRange >= waveform_trc)) + return -1; + /* copy the waveform voltage control data */ + memcpy(VoltageCtrlData, waveform_vcd_buffer + 16 * (wMode * waveform_trc + wTempRange),16); + { + unsigned i; + unsigned char cs = 0; + for (i=0; i< 15; i++) + cs += VoltageCtrlData[i]; + if (cs != VoltageCtrlData[15]) return -1; + } + return 0; +} + + + +/* About advance algorithms in this device driver */ + +int mxc_epdc_fb_aa_info( void ) +{ +#if REAGL_ENABLED + printk(" This version contains GLR16 and GLD16 advance algorithms.\n"); + return 2; +#else + printk(" This version contains no advance algorithm.\n"); + return 0; +#endif +} + diff --git a/drivers/video/mxc/eink_processing2.h b/drivers/video/mxc/eink_processing2.h new file mode 100644 index 00000000..7278d7ac --- /dev/null +++ b/drivers/video/mxc/eink_processing2.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2012 - 2013 Freescale Semiconductor, Inc. All Rights Reserved. + * THIS SOURCE CODE IS CONFIDENTIAL AND PROPRIETARY AND MAY NOT + * BE USED OR DISTRIBUTED WITHOUT THE WRITTEN PERMISSION OF + * Freescale Semiconductor, Inc. + */ + +#ifndef __EINK_PROCESSING_H__ +#define __EINK_PROCESSING_H__ + +#include <linux/mxcfb.h> + +#define WAVEFORM_SET_ALGO_DATA 1 +#define WAVEFORM_RESTORE_DEFAULT_PMM 2 + +/* + * select a version according to SoCs on the platform + * i.MX6SL has Cortex A9 core + * i.MX508 has Cortex A8 core + * The algorithms are not compatible + * so use care to select proper lib.1_shipped for the correct core + * for the target platform. + */ +#undef IMX6SL_AA +#define IMX508_AA + +/* + * E-ink Gen II waveform algorithm aa implementation - version 2.2.1 + * + * Function Name: + * int do_aa_processing ( .. ) + * input parameters: + * unsigned short *working_buffer_ptr_in, + * unsigned short *working_buffer_ptr_out, + * struct mxcfb_rect *update_region, + * long working_buffer_width + * long working_buffer_height + * return: + * 0 - normal exit + * -1 - no advance algorithm + */ + +int do_aa_processing_v2_2_1( + unsigned short *working_buffer_ptr_in, + unsigned short *working_buffer_ptr_out, + struct mxcfb_rect *update_region, + long working_buffer_width, + long working_buffer_height); + +/* + * E-ink Gen II waveform algorithm AA-D implementation version 2.1.1 + * for i.MX50 (508: 86ms for 1024x768, 52ms for 800x600) + * Function Name: + * int do_aad_processing ( .. ) + * input parameters: + * unsigned short *working_buffer_ptr_in, + * unsigned short *working_buffer_ptr_out, + * struct mxcfb_rect *update_region, + * long working_buffer_width + * long working_buffer_height + * return: + * 0 - normal exit + * -1 - no advance algorithm + */ + +int do_aad_processing_v2_1_1( + unsigned short *working_buffer_ptr_in, + unsigned short *working_buffer_ptr_out, + struct mxcfb_rect *update_region, + long working_buffer_width, + long working_buffer_height); +/* + * E-ink Gen II waveform algorithm aa implementation - version 2.2.0 + * + * Function Name: + * int do_aa_processing( .. ) + * input parameters: + * unsigned char* update_region_ptr + * struct mxcfb_rect* update_region + * unsigned long update_region_stride + * unsigned short* working_buffer_ptr + * long working_buffer_width + * long working_buffer_height + * output: + * unsigned char* update_region_ptr + * return: + * 0 - normal exit + * -1 - no advance algorithm + */ + +int do_aa_processing_v2_2_0( + unsigned char *update_region_ptr, struct mxcfb_rect *update_region, + unsigned long update_region_stride, unsigned short *working_buffer_ptr, + long working_buffer_width, long working_buffer_height); + + +/* + * E-ink Gen II waveform algorithm AA-D implementation version 2.1.0 + * Function Name: + * int do_aad_processing( .. ) + * input parameters: + * unsigned char* update_region_ptr + * struct mxcfb_rect* update_region + * unsigned long update_region_stride + * unsigned short* working_buffer_ptr + * long working_buffer_width + * long working_buffer_height + * output parameters: + * unsigned char* update_region_ptr + * return: + * 0 - normal exit + * -1 - no advance algorithm + */ + +int do_aad_processing_v2_1_0( + unsigned char *update_region_ptr, struct mxcfb_rect *update_region, + unsigned long update_region_stride, unsigned short *working_buffer_ptr, + long working_buffer_width, long working_buffer_height); + +/* + * API for setting the update counter + * Function Name: + * void set_aad_update_counter( .. ) + * input parameters: + * unsigned long value + * return: + * none + */ +void set_aad_update_counter(unsigned long value); + +/* + * Function for fetching the voltage control data for EPDC PMIC + * name: int mxc_epdc_fb_fetch_vc_data ( .. ) + * return: 0 - normal exit, -1 - invalide waveform mode or invalid temperature range, -2 - data checksum error + * parameters: *waveform_vcd_buffer holds the voltage control data table for waveform modes, typically fb_data->waveform_vcd_buffer + * wMode - waveform mode for the interested vc data + * wTempRange - specific temperature range of the waveform mode for vc data + * waveform_mc - the number of waveform modes in the waveform file, typically it is fb_data-waveform_mc + * waveform_trc - the number of temperature ranges in each waveform mode, typically fb_data->waveform_trc + * *VoltageCtrlData - 16-byte data array for returning vc data + */ +int mxc_epdc_fb_fetch_vc_data( unsigned char *waveform_vcd_buffer, + unsigned wMode, + unsigned wTempRange, + unsigned waveform_mc, + unsigned waveform_trc, + unsigned char *VoltageCtrlData); + +/* + * function for extracting the extra waveform information data (usually the waveform name) + * name: int mxc_epdc_fb_fetch_wxi_data(( .. ) + * return: number of bytes in the extra information + * parameters: *waveform_xwi_buffer holds the extra waveform data, typically fb_data->waveform_xwi_buffer + * *ExtraWaveformString - NULL if no buffer allocated, the function will return the number of bytes in string + * and it can be used for allocating the string buffer + * Note: typically fetching the extra waveform information requires two function calls. + * The first call with NULL pointer to get the length of the information then allocate a string buffer with one byte larger than the size of the xwi + * The second call with the allocated buffer to get the data then set the null termination for completing the string. + */ +int mxc_epdc_fb_fetch_wxi_data( unsigned char *waveform_xwi_buffer, + char *ExtraWaveformString); + +/* + * Function for preparing the advance algorithm control data + * name: int mxc_epdc_fb_prep_algorithm_data( ( .. ) + * return: 0 - no advance algorithm data + * 1 - version 1 algorithm data (AA algorithm) + * 2 - version 2 algorithm data (AA/AA-D) + * -1 - no advance waveform support algorithms in this version of library + * -2 - invalid waveform mode or invalid temperature range + * < -2 values - data checksum errors + * parameters: *waveform_acd_buffer holds the algorithm control data table for waveform modes, typically fb_data->waveform_acd_buffer + * wMode - waveform mode for the interested vc data + * wTempRange - specific temperature range of the waveform mode for vc data + * waveform_mc - the number of waveform modes in the waveform file, typically it is fb_data-waveform_mc + * waveform_trc - the number of temperature ranges in each waveform mode, typically fb_data->waveform_trc + * waveform_magic_number - algorithm data code from the waveform file, typically fb_data->waveform_magic_number + * ctrlFlags - 0 just look up the version of the algorithm data, + * 1 - transfer the data to be used by algorithm if defined, + * 3 - same as 1 but restore the default data if there is no algorithm data. + */ +int mxc_epdc_fb_prep_algorithm_data( unsigned char *waveform_acd_buffer, + unsigned wMode, + unsigned wTempRange, + unsigned waveform_mc, + unsigned waveform_trc, + unsigned waveform_magic_number, + unsigned ctrlFlags); + +/* Function about advance algoritms in this device driver + * name: int mxc_epdc_fb_aa_info( void ) + * return: number of advance algorithms built into this device driver + * parameters: none + */ +int mxc_epdc_fb_aa_info( void ); + +#endif diff --git a/drivers/video/mxc/elcdif_regs.h b/drivers/video/mxc/elcdif_regs.h new file mode 100644 index 00000000..42e377de --- /dev/null +++ b/drivers/video/mxc/elcdif_regs.h @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +/* + * Based on arch/arm/mach-mx28/include/mach/regs-lcdif.h. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#ifndef __ELCDIF_REGS_INCLUDED_ +#define __ELCDIF_REGS_INCLUDED_ + +#define HW_ELCDIF_CTRL (0x00000000) +#define HW_ELCDIF_CTRL_SET (0x00000004) +#define HW_ELCDIF_CTRL_CLR (0x00000008) +#define HW_ELCDIF_CTRL_TOG (0x0000000c) + +#define BM_ELCDIF_CTRL_SFTRST 0x80000000 +#define BM_ELCDIF_CTRL_CLKGATE 0x40000000 +#define BM_ELCDIF_CTRL_YCBCR422_INPUT 0x20000000 +#define BM_ELCDIF_CTRL_READ_WRITEB 0x10000000 +#define BM_ELCDIF_CTRL_WAIT_FOR_VSYNC_EDGE 0x08000000 +#define BM_ELCDIF_CTRL_DATA_SHIFT_DIR 0x04000000 +#define BV_ELCDIF_CTRL_DATA_SHIFT_DIR__TXDATA_SHIFT_LEFT 0x0 +#define BV_ELCDIF_CTRL_DATA_SHIFT_DIR__TXDATA_SHIFT_RIGHT 0x1 +#define BP_ELCDIF_CTRL_SHIFT_NUM_BITS 21 +#define BM_ELCDIF_CTRL_SHIFT_NUM_BITS 0x03E00000 +#define BF_ELCDIF_CTRL_SHIFT_NUM_BITS(v) \ + (((v) << 21) & BM_ELCDIF_CTRL_SHIFT_NUM_BITS) +#define BM_ELCDIF_CTRL_DVI_MODE 0x00100000 +#define BM_ELCDIF_CTRL_BYPASS_COUNT 0x00080000 +#define BM_ELCDIF_CTRL_VSYNC_MODE 0x00040000 +#define BM_ELCDIF_CTRL_DOTCLK_MODE 0x00020000 +#define BM_ELCDIF_CTRL_DATA_SELECT 0x00010000 +#define BV_ELCDIF_CTRL_DATA_SELECT__CMD_MODE 0x0 +#define BV_ELCDIF_CTRL_DATA_SELECT__DATA_MODE 0x1 +#define BP_ELCDIF_CTRL_INPUT_DATA_SWIZZLE 14 +#define BM_ELCDIF_CTRL_INPUT_DATA_SWIZZLE 0x0000C000 +#define BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(v) \ + (((v) << 14) & BM_ELCDIF_CTRL_INPUT_DATA_SWIZZLE) +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__NO_SWAP 0x0 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__LITTLE_ENDIAN 0x0 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__BIG_ENDIAN_SWAP 0x1 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__SWAP_ALL_BYTES 0x1 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__HWD_SWAP 0x2 +#define BV_ELCDIF_CTRL_INPUT_DATA_SWIZZLE__HWD_BYTE_SWAP 0x3 +#define BP_ELCDIF_CTRL_CSC_DATA_SWIZZLE 12 +#define BM_ELCDIF_CTRL_CSC_DATA_SWIZZLE 0x00003000 +#define BF_ELCDIF_CTRL_CSC_DATA_SWIZZLE(v) \ + (((v) << 12) & BM_ELCDIF_CTRL_CSC_DATA_SWIZZLE) +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__NO_SWAP 0x0 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__LITTLE_ENDIAN 0x0 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__BIG_ENDIAN_SWAP 0x1 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__SWAP_ALL_BYTES 0x1 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__HWD_SWAP 0x2 +#define BV_ELCDIF_CTRL_CSC_DATA_SWIZZLE__HWD_BYTE_SWAP 0x3 +#define BP_ELCDIF_CTRL_LCD_DATABUS_WIDTH 10 +#define BM_ELCDIF_CTRL_LCD_DATABUS_WIDTH 0x00000C00 +#define BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(v) \ + (((v) << 10) & BM_ELCDIF_CTRL_LCD_DATABUS_WIDTH) +#define BV_ELCDIF_CTRL_LCD_DATABUS_WIDTH__16_BIT 0x0 +#define BV_ELCDIF_CTRL_LCD_DATABUS_WIDTH__8_BIT 0x1 +#define BV_ELCDIF_CTRL_LCD_DATABUS_WIDTH__18_BIT 0x2 +#define BV_ELCDIF_CTRL_LCD_DATABUS_WIDTH__24_BIT 0x3 +#define BP_ELCDIF_CTRL_WORD_LENGTH 8 +#define BM_ELCDIF_CTRL_WORD_LENGTH 0x00000300 +#define BF_ELCDIF_CTRL_WORD_LENGTH(v) \ + (((v) << 8) & BM_ELCDIF_CTRL_WORD_LENGTH) +#define BV_ELCDIF_CTRL_WORD_LENGTH__16_BIT 0x0 +#define BV_ELCDIF_CTRL_WORD_LENGTH__8_BIT 0x1 +#define BV_ELCDIF_CTRL_WORD_LENGTH__18_BIT 0x2 +#define BV_ELCDIF_CTRL_WORD_LENGTH__24_BIT 0x3 +#define BM_ELCDIF_CTRL_RGB_TO_YCBCR422_CSC 0x00000080 +#define BM_ELCDIF_CTRL_ENABLE_PXP_HANDSHAKE 0x00000040 +#define BM_ELCDIF_CTRL_ELCDIF_MASTER 0x00000020 +#define BM_ELCDIF_CTRL_RSRVD0 0x00000010 +#define BM_ELCDIF_CTRL_DATA_FORMAT_16_BIT 0x00000008 +#define BM_ELCDIF_CTRL_DATA_FORMAT_18_BIT 0x00000004 +#define BV_ELCDIF_CTRL_DATA_FORMAT_18_BIT__LOWER_18_BITS_VALID 0x0 +#define BV_ELCDIF_CTRL_DATA_FORMAT_18_BIT__UPPER_18_BITS_VALID 0x1 +#define BM_ELCDIF_CTRL_DATA_FORMAT_24_BIT 0x00000002 +#define BV_ELCDIF_CTRL_DATA_FORMAT_24_BIT__ALL_24_BITS_VALID 0x0 +#define BV_ELCDIF_CTRL_DATA_FORMAT_24_BIT__DROP_UPPER_2_BITS_PER_BYTE 0x1 +#define BM_ELCDIF_CTRL_RUN 0x00000001 + +#define HW_ELCDIF_CTRL1 (0x00000010) +#define HW_ELCDIF_CTRL1_SET (0x00000014) +#define HW_ELCDIF_CTRL1_CLR (0x00000018) +#define HW_ELCDIF_CTRL1_TOG (0x0000001c) + +#define BP_ELCDIF_CTRL1_RSRVD1 28 +#define BM_ELCDIF_CTRL1_RSRVD1 0xF0000000 +#define BF_ELCDIF_CTRL1_RSRVD1(v) \ + (((v) << 28) & BM_ELCDIF_CTRL1_RSRVD1) +#define BM_ELCDIF_CTRL1_COMBINE_MPU_WR_STRB 0x08000000 +#define BM_ELCDIF_CTRL1_BM_ERROR_IRQ_EN 0x04000000 +#define BM_ELCDIF_CTRL1_BM_ERROR_IRQ 0x02000000 +#define BV_ELCDIF_CTRL1_BM_ERROR_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_BM_ERROR_IRQ__REQUEST 0x1 +#define BM_ELCDIF_CTRL1_RECOVER_ON_UNDERFLOW 0x01000000 +#define BM_ELCDIF_CTRL1_INTERLACE_FIELDS 0x00800000 +#define BM_ELCDIF_CTRL1_START_INTERLACE_FROM_SECOND_FIELD 0x00400000 +#define BM_ELCDIF_CTRL1_FIFO_CLEAR 0x00200000 +#define BM_ELCDIF_CTRL1_IRQ_ON_ALTERNATE_FIELDS 0x00100000 +#define BP_ELCDIF_CTRL1_BYTE_PACKING_FORMAT 16 +#define BM_ELCDIF_CTRL1_BYTE_PACKING_FORMAT 0x000F0000 +#define BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(v) \ + (((v) << 16) & BM_ELCDIF_CTRL1_BYTE_PACKING_FORMAT) +#define BM_ELCDIF_CTRL1_OVERFLOW_IRQ_EN 0x00008000 +#define BM_ELCDIF_CTRL1_UNDERFLOW_IRQ_EN 0x00004000 +#define BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ_EN 0x00002000 +#define BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ_EN 0x00001000 +#define BM_ELCDIF_CTRL1_OVERFLOW_IRQ 0x00000800 +#define BV_ELCDIF_CTRL1_OVERFLOW_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_OVERFLOW_IRQ__REQUEST 0x1 +#define BM_ELCDIF_CTRL1_UNDERFLOW_IRQ 0x00000400 +#define BV_ELCDIF_CTRL1_UNDERFLOW_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_UNDERFLOW_IRQ__REQUEST 0x1 +#define BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ 0x00000200 +#define BV_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ__REQUEST 0x1 +#define BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ 0x00000100 +#define BV_ELCDIF_CTRL1_VSYNC_EDGE_IRQ__NO_REQUEST 0x0 +#define BV_ELCDIF_CTRL1_VSYNC_EDGE_IRQ__REQUEST 0x1 +#define BP_ELCDIF_CTRL1_RSRVD0 3 +#define BM_ELCDIF_CTRL1_RSRVD0 0x000000F8 +#define BF_ELCDIF_CTRL1_RSRVD0(v) \ + (((v) << 3) & BM_ELCDIF_CTRL1_RSRVD0) +#define BM_ELCDIF_CTRL1_BUSY_ENABLE 0x00000004 +#define BV_ELCDIF_CTRL1_BUSY_ENABLE__BUSY_DISABLED 0x0 +#define BV_ELCDIF_CTRL1_BUSY_ENABLE__BUSY_ENABLED 0x1 +#define BM_ELCDIF_CTRL1_MODE86 0x00000002 +#define BV_ELCDIF_CTRL1_MODE86__8080_MODE 0x0 +#define BV_ELCDIF_CTRL1_MODE86__6800_MODE 0x1 +#define BM_ELCDIF_CTRL1_RESET 0x00000001 +#define BV_ELCDIF_CTRL1_RESET__LCDRESET_LOW 0x0 +#define BV_ELCDIF_CTRL1_RESET__LCDRESET_HIGH 0x1 + +#define HW_ELCDIF_CTRL2 (0x00000020) +#define HW_ELCDIF_CTRL2_SET (0x00000024) +#define HW_ELCDIF_CTRL2_CLR (0x00000028) +#define HW_ELCDIF_CTRL2_TOG (0x0000002c) + +#define BP_ELCDIF_CTRL2_RSRVD5 24 +#define BM_ELCDIF_CTRL2_RSRVD5 0xFF000000 +#define BF_ELCDIF_CTRL2_RSRVD5(v) \ + (((v) << 24) & BM_ELCDIF_CTRL2_RSRVD5) +#define BP_ELCDIF_CTRL2_OUTSTANDING_REQS 21 +#define BM_ELCDIF_CTRL2_OUTSTANDING_REQS 0x00E00000 +#define BF_ELCDIF_CTRL2_OUTSTANDING_REQS(v) \ + (((v) << 21) & BM_ELCDIF_CTRL2_OUTSTANDING_REQS) +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_1 0x0 +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_2 0x1 +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_4 0x2 +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_8 0x3 +#define BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_16 0x4 +#define BM_ELCDIF_CTRL2_BURST_LEN_8 0x00100000 +#define BM_ELCDIF_CTRL2_RSRVD4 0x00080000 +#define BP_ELCDIF_CTRL2_ODD_LINE_PATTERN 16 +#define BM_ELCDIF_CTRL2_ODD_LINE_PATTERN 0x00070000 +#define BF_ELCDIF_CTRL2_ODD_LINE_PATTERN(v) \ + (((v) << 16) & BM_ELCDIF_CTRL2_ODD_LINE_PATTERN) +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__RGB 0x0 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__RBG 0x1 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__GBR 0x2 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__GRB 0x3 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__BRG 0x4 +#define BV_ELCDIF_CTRL2_ODD_LINE_PATTERN__BGR 0x5 +#define BM_ELCDIF_CTRL2_RSRVD3 0x00008000 +#define BP_ELCDIF_CTRL2_EVEN_LINE_PATTERN 12 +#define BM_ELCDIF_CTRL2_EVEN_LINE_PATTERN 0x00007000 +#define BF_ELCDIF_CTRL2_EVEN_LINE_PATTERN(v) \ + (((v) << 12) & BM_ELCDIF_CTRL2_EVEN_LINE_PATTERN) +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__RGB 0x0 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__RBG 0x1 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__GBR 0x2 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__GRB 0x3 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__BRG 0x4 +#define BV_ELCDIF_CTRL2_EVEN_LINE_PATTERN__BGR 0x5 +#define BM_ELCDIF_CTRL2_RSRVD2 0x00000800 +#define BM_ELCDIF_CTRL2_READ_PACK_DIR 0x00000400 +#define BM_ELCDIF_CTRL2_READ_MODE_OUTPUT_IN_RGB_FORMAT 0x00000200 +#define BM_ELCDIF_CTRL2_READ_MODE_6_BIT_INPUT 0x00000100 +#define BM_ELCDIF_CTRL2_RSRVD1 0x00000080 +#define BP_ELCDIF_CTRL2_READ_MODE_NUM_PACKED_SUBWORDS 4 +#define BM_ELCDIF_CTRL2_READ_MODE_NUM_PACKED_SUBWORDS 0x00000070 +#define BF_ELCDIF_CTRL2_READ_MODE_NUM_PACKED_SUBWORDS(v) \ + (((v) << 4) & BM_ELCDIF_CTRL2_READ_MODE_NUM_PACKED_SUBWORDS) +#define BP_ELCDIF_CTRL2_INITIAL_DUMMY_READ 1 +#define BM_ELCDIF_CTRL2_INITIAL_DUMMY_READ 0x0000000E +#define BF_ELCDIF_CTRL2_INITIAL_DUMMY_READ(v) \ + (((v) << 1) & BM_ELCDIF_CTRL2_INITIAL_DUMMY_READ) +#define BM_ELCDIF_CTRL2_RSRVD0 0x00000001 + +#define HW_ELCDIF_TRANSFER_COUNT (0x00000030) + +#define BP_ELCDIF_TRANSFER_COUNT_V_COUNT 16 +#define BM_ELCDIF_TRANSFER_COUNT_V_COUNT 0xFFFF0000 +#define BF_ELCDIF_TRANSFER_COUNT_V_COUNT(v) \ + (((v) << 16) & BM_ELCDIF_TRANSFER_COUNT_V_COUNT) +#define BP_ELCDIF_TRANSFER_COUNT_H_COUNT 0 +#define BM_ELCDIF_TRANSFER_COUNT_H_COUNT 0x0000FFFF +#define BF_ELCDIF_TRANSFER_COUNT_H_COUNT(v) \ + (((v) << 0) & BM_ELCDIF_TRANSFER_COUNT_H_COUNT) + +#define HW_ELCDIF_CUR_BUF (0x00000040) + +#define BP_ELCDIF_CUR_BUF_ADDR 0 +#define BM_ELCDIF_CUR_BUF_ADDR 0xFFFFFFFF +#define BF_ELCDIF_CUR_BUF_ADDR(v) (v) + +#define HW_ELCDIF_NEXT_BUF (0x00000050) + +#define BP_ELCDIF_NEXT_BUF_ADDR 0 +#define BM_ELCDIF_NEXT_BUF_ADDR 0xFFFFFFFF +#define BF_ELCDIF_NEXT_BUF_ADDR(v) (v) + +#define HW_ELCDIF_TIMING (0x00000060) + +#define BP_ELCDIF_TIMING_CMD_HOLD 24 +#define BM_ELCDIF_TIMING_CMD_HOLD 0xFF000000 +#define BF_ELCDIF_TIMING_CMD_HOLD(v) \ + (((v) << 24) & BM_ELCDIF_TIMING_CMD_HOLD) +#define BP_ELCDIF_TIMING_CMD_SETUP 16 +#define BM_ELCDIF_TIMING_CMD_SETUP 0x00FF0000 +#define BF_ELCDIF_TIMING_CMD_SETUP(v) \ + (((v) << 16) & BM_ELCDIF_TIMING_CMD_SETUP) +#define BP_ELCDIF_TIMING_DATA_HOLD 8 +#define BM_ELCDIF_TIMING_DATA_HOLD 0x0000FF00 +#define BF_ELCDIF_TIMING_DATA_HOLD(v) \ + (((v) << 8) & BM_ELCDIF_TIMING_DATA_HOLD) +#define BP_ELCDIF_TIMING_DATA_SETUP 0 +#define BM_ELCDIF_TIMING_DATA_SETUP 0x000000FF +#define BF_ELCDIF_TIMING_DATA_SETUP(v) \ + (((v) << 0) & BM_ELCDIF_TIMING_DATA_SETUP) + +#define HW_ELCDIF_VDCTRL0 (0x00000070) +#define HW_ELCDIF_VDCTRL0_SET (0x00000074) +#define HW_ELCDIF_VDCTRL0_CLR (0x00000078) +#define HW_ELCDIF_VDCTRL0_TOG (0x0000007c) + +#define BP_ELCDIF_VDCTRL0_RSRVD2 30 +#define BM_ELCDIF_VDCTRL0_RSRVD2 0xC0000000 +#define BF_ELCDIF_VDCTRL0_RSRVD2(v) \ + (((v) << 30) & BM_ELCDIF_VDCTRL0_RSRVD2) +#define BM_ELCDIF_VDCTRL0_VSYNC_OEB 0x20000000 +#define BV_ELCDIF_VDCTRL0_VSYNC_OEB__VSYNC_OUTPUT 0x0 +#define BV_ELCDIF_VDCTRL0_VSYNC_OEB__VSYNC_INPUT 0x1 +#define BM_ELCDIF_VDCTRL0_ENABLE_PRESENT 0x10000000 +#define BM_ELCDIF_VDCTRL0_VSYNC_POL 0x08000000 +#define BM_ELCDIF_VDCTRL0_HSYNC_POL 0x04000000 +#define BM_ELCDIF_VDCTRL0_DOTCLK_POL 0x02000000 +#define BM_ELCDIF_VDCTRL0_ENABLE_POL 0x01000000 +#define BP_ELCDIF_VDCTRL0_RSRVD1 22 +#define BM_ELCDIF_VDCTRL0_RSRVD1 0x00C00000 +#define BF_ELCDIF_VDCTRL0_RSRVD1(v) \ + (((v) << 22) & BM_ELCDIF_VDCTRL0_RSRVD1) +#define BM_ELCDIF_VDCTRL0_VSYNC_PERIOD_UNIT 0x00200000 +#define BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT 0x00100000 +#define BM_ELCDIF_VDCTRL0_HALF_LINE 0x00080000 +#define BM_ELCDIF_VDCTRL0_HALF_LINE_MODE 0x00040000 +#define BP_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH 0 +#define BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH 0x0003FFFF +#define BF_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH(v) \ + (((v) << 0) & BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH) + +#define HW_ELCDIF_VDCTRL1 (0x00000080) + +#define BP_ELCDIF_VDCTRL1_VSYNC_PERIOD 0 +#define BM_ELCDIF_VDCTRL1_VSYNC_PERIOD 0xFFFFFFFF +#define BF_ELCDIF_VDCTRL1_VSYNC_PERIOD(v) (v) + +#define HW_ELCDIF_VDCTRL2 (0x00000090) + +#define BP_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH 18 +#define BM_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH 0xFFFC0000 +#define BF_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH(v) \ + (((v) << 18) & BM_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH) +#define BP_ELCDIF_VDCTRL2_HSYNC_PERIOD 0 +#define BM_ELCDIF_VDCTRL2_HSYNC_PERIOD 0x0003FFFF +#define BF_ELCDIF_VDCTRL2_HSYNC_PERIOD(v) \ + (((v) << 0) & BM_ELCDIF_VDCTRL2_HSYNC_PERIOD) + +#define HW_ELCDIF_VDCTRL3 (0x000000a0) + +#define BP_ELCDIF_VDCTRL3_RSRVD0 30 +#define BM_ELCDIF_VDCTRL3_RSRVD0 0xC0000000 +#define BF_ELCDIF_VDCTRL3_RSRVD0(v) \ + (((v) << 30) & BM_ELCDIF_VDCTRL3_RSRVD0) +#define BM_ELCDIF_VDCTRL3_MUX_SYNC_SIGNALS 0x20000000 +#define BM_ELCDIF_VDCTRL3_VSYNC_ONLY 0x10000000 +#define BP_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT 16 +#define BM_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT 0x0FFF0000 +#define BF_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT(v) \ + (((v) << 16) & BM_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT) +#define BP_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT 0 +#define BM_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT 0x0000FFFF +#define BF_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT(v) \ + (((v) << 0) & BM_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT) + +#define HW_ELCDIF_VDCTRL4 (0x000000b0) + +#define BP_ELCDIF_VDCTRL4_DOTCLK_DLY_SEL 29 +#define BM_ELCDIF_VDCTRL4_DOTCLK_DLY_SEL 0xE0000000 +#define BF_ELCDIF_VDCTRL4_DOTCLK_DLY_SEL(v) \ + (((v) << 29) & BM_ELCDIF_VDCTRL4_DOTCLK_DLY_SEL) +#define BP_ELCDIF_VDCTRL4_RSRVD0 19 +#define BM_ELCDIF_VDCTRL4_RSRVD0 0x1FF80000 +#define BF_ELCDIF_VDCTRL4_RSRVD0(v) \ + (((v) << 19) & BM_ELCDIF_VDCTRL4_RSRVD0) +#define BM_ELCDIF_VDCTRL4_SYNC_SIGNALS_ON 0x00040000 +#define BP_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT 0 +#define BM_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT 0x0003FFFF +#define BF_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT(v) \ + (((v) << 0) & BM_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT) + +#define HW_ELCDIF_DVICTRL0 (0x000000c0) + +#define BP_ELCDIF_DVICTRL0_RSRVD1 28 +#define BM_ELCDIF_DVICTRL0_RSRVD1 0xF0000000 +#define BF_ELCDIF_DVICTRL0_RSRVD1(v) \ + (((v) << 28) & BM_ELCDIF_DVICTRL0_RSRVD1) +#define BP_ELCDIF_DVICTRL0_H_ACTIVE_CNT 16 +#define BM_ELCDIF_DVICTRL0_H_ACTIVE_CNT 0x0FFF0000 +#define BF_ELCDIF_DVICTRL0_H_ACTIVE_CNT(v) \ + (((v) << 16) & BM_ELCDIF_DVICTRL0_H_ACTIVE_CNT) +#define BP_ELCDIF_DVICTRL0_RSRVD0 12 +#define BM_ELCDIF_DVICTRL0_RSRVD0 0x0000F000 +#define BF_ELCDIF_DVICTRL0_RSRVD0(v) \ + (((v) << 12) & BM_ELCDIF_DVICTRL0_RSRVD0) +#define BP_ELCDIF_DVICTRL0_H_BLANKING_CNT 0 +#define BM_ELCDIF_DVICTRL0_H_BLANKING_CNT 0x00000FFF +#define BF_ELCDIF_DVICTRL0_H_BLANKING_CNT(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL0_H_BLANKING_CNT) + +#define HW_ELCDIF_DVICTRL1 (0x000000d0) + +#define BP_ELCDIF_DVICTRL1_RSRVD0 30 +#define BM_ELCDIF_DVICTRL1_RSRVD0 0xC0000000 +#define BF_ELCDIF_DVICTRL1_RSRVD0(v) \ + (((v) << 30) & BM_ELCDIF_DVICTRL1_RSRVD0) +#define BP_ELCDIF_DVICTRL1_F1_START_LINE 20 +#define BM_ELCDIF_DVICTRL1_F1_START_LINE 0x3FF00000 +#define BF_ELCDIF_DVICTRL1_F1_START_LINE(v) \ + (((v) << 20) & BM_ELCDIF_DVICTRL1_F1_START_LINE) +#define BP_ELCDIF_DVICTRL1_F1_END_LINE 10 +#define BM_ELCDIF_DVICTRL1_F1_END_LINE 0x000FFC00 +#define BF_ELCDIF_DVICTRL1_F1_END_LINE(v) \ + (((v) << 10) & BM_ELCDIF_DVICTRL1_F1_END_LINE) +#define BP_ELCDIF_DVICTRL1_F2_START_LINE 0 +#define BM_ELCDIF_DVICTRL1_F2_START_LINE 0x000003FF +#define BF_ELCDIF_DVICTRL1_F2_START_LINE(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL1_F2_START_LINE) + +#define HW_ELCDIF_DVICTRL2 (0x000000e0) + +#define BP_ELCDIF_DVICTRL2_RSRVD0 30 +#define BM_ELCDIF_DVICTRL2_RSRVD0 0xC0000000 +#define BF_ELCDIF_DVICTRL2_RSRVD0(v) \ + (((v) << 30) & BM_ELCDIF_DVICTRL2_RSRVD0) +#define BP_ELCDIF_DVICTRL2_F2_END_LINE 20 +#define BM_ELCDIF_DVICTRL2_F2_END_LINE 0x3FF00000 +#define BF_ELCDIF_DVICTRL2_F2_END_LINE(v) \ + (((v) << 20) & BM_ELCDIF_DVICTRL2_F2_END_LINE) +#define BP_ELCDIF_DVICTRL2_V1_BLANK_START_LINE 10 +#define BM_ELCDIF_DVICTRL2_V1_BLANK_START_LINE 0x000FFC00 +#define BF_ELCDIF_DVICTRL2_V1_BLANK_START_LINE(v) \ + (((v) << 10) & BM_ELCDIF_DVICTRL2_V1_BLANK_START_LINE) +#define BP_ELCDIF_DVICTRL2_V1_BLANK_END_LINE 0 +#define BM_ELCDIF_DVICTRL2_V1_BLANK_END_LINE 0x000003FF +#define BF_ELCDIF_DVICTRL2_V1_BLANK_END_LINE(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL2_V1_BLANK_END_LINE) + +#define HW_ELCDIF_DVICTRL3 (0x000000f0) + +#define BP_ELCDIF_DVICTRL3_RSRVD0 30 +#define BM_ELCDIF_DVICTRL3_RSRVD0 0xC0000000 +#define BF_ELCDIF_DVICTRL3_RSRVD0(v) \ + (((v) << 30) & BM_ELCDIF_DVICTRL3_RSRVD0) +#define BP_ELCDIF_DVICTRL3_V2_BLANK_START_LINE 20 +#define BM_ELCDIF_DVICTRL3_V2_BLANK_START_LINE 0x3FF00000 +#define BF_ELCDIF_DVICTRL3_V2_BLANK_START_LINE(v) \ + (((v) << 20) & BM_ELCDIF_DVICTRL3_V2_BLANK_START_LINE) +#define BP_ELCDIF_DVICTRL3_V2_BLANK_END_LINE 10 +#define BM_ELCDIF_DVICTRL3_V2_BLANK_END_LINE 0x000FFC00 +#define BF_ELCDIF_DVICTRL3_V2_BLANK_END_LINE(v) \ + (((v) << 10) & BM_ELCDIF_DVICTRL3_V2_BLANK_END_LINE) +#define BP_ELCDIF_DVICTRL3_V_LINES_CNT 0 +#define BM_ELCDIF_DVICTRL3_V_LINES_CNT 0x000003FF +#define BF_ELCDIF_DVICTRL3_V_LINES_CNT(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL3_V_LINES_CNT) + +#define HW_ELCDIF_DVICTRL4 (0x00000100) + +#define BP_ELCDIF_DVICTRL4_Y_FILL_VALUE 24 +#define BM_ELCDIF_DVICTRL4_Y_FILL_VALUE 0xFF000000 +#define BF_ELCDIF_DVICTRL4_Y_FILL_VALUE(v) \ + (((v) << 24) & BM_ELCDIF_DVICTRL4_Y_FILL_VALUE) +#define BP_ELCDIF_DVICTRL4_CB_FILL_VALUE 16 +#define BM_ELCDIF_DVICTRL4_CB_FILL_VALUE 0x00FF0000 +#define BF_ELCDIF_DVICTRL4_CB_FILL_VALUE(v) \ + (((v) << 16) & BM_ELCDIF_DVICTRL4_CB_FILL_VALUE) +#define BP_ELCDIF_DVICTRL4_CR_FILL_VALUE 8 +#define BM_ELCDIF_DVICTRL4_CR_FILL_VALUE 0x0000FF00 +#define BF_ELCDIF_DVICTRL4_CR_FILL_VALUE(v) \ + (((v) << 8) & BM_ELCDIF_DVICTRL4_CR_FILL_VALUE) +#define BP_ELCDIF_DVICTRL4_H_FILL_CNT 0 +#define BM_ELCDIF_DVICTRL4_H_FILL_CNT 0x000000FF +#define BF_ELCDIF_DVICTRL4_H_FILL_CNT(v) \ + (((v) << 0) & BM_ELCDIF_DVICTRL4_H_FILL_CNT) + +#define HW_ELCDIF_CSC_COEFF0 (0x00000110) + +#define BP_ELCDIF_CSC_COEFF0_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF0_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF0_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF0_RSRVD1) +#define BP_ELCDIF_CSC_COEFF0_C0 16 +#define BM_ELCDIF_CSC_COEFF0_C0 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF0_C0(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF0_C0) +#define BP_ELCDIF_CSC_COEFF0_RSRVD0 2 +#define BM_ELCDIF_CSC_COEFF0_RSRVD0 0x0000FFFC +#define BF_ELCDIF_CSC_COEFF0_RSRVD0(v) \ + (((v) << 2) & BM_ELCDIF_CSC_COEFF0_RSRVD0) +#define BP_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER 0 +#define BM_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER 0x00000003 +#define BF_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER) +#define BV_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER__SAMPLE_AND_HOLD 0x0 +#define BV_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER__RSRVD 0x1 +#define BV_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER__INTERSTITIAL 0x2 +#define BV_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER__COSITED 0x3 + +#define HW_ELCDIF_CSC_COEFF1 (0x00000120) + +#define BP_ELCDIF_CSC_COEFF1_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF1_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF1_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF1_RSRVD1) +#define BP_ELCDIF_CSC_COEFF1_C2 16 +#define BM_ELCDIF_CSC_COEFF1_C2 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF1_C2(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF1_C2) +#define BP_ELCDIF_CSC_COEFF1_RSRVD0 10 +#define BM_ELCDIF_CSC_COEFF1_RSRVD0 0x0000FC00 +#define BF_ELCDIF_CSC_COEFF1_RSRVD0(v) \ + (((v) << 10) & BM_ELCDIF_CSC_COEFF1_RSRVD0) +#define BP_ELCDIF_CSC_COEFF1_C1 0 +#define BM_ELCDIF_CSC_COEFF1_C1 0x000003FF +#define BF_ELCDIF_CSC_COEFF1_C1(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF1_C1) + +#define HW_ELCDIF_CSC_COEFF2 (0x00000130) + +#define BP_ELCDIF_CSC_COEFF2_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF2_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF2_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF2_RSRVD1) +#define BP_ELCDIF_CSC_COEFF2_C4 16 +#define BM_ELCDIF_CSC_COEFF2_C4 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF2_C4(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF2_C4) +#define BP_ELCDIF_CSC_COEFF2_RSRVD0 10 +#define BM_ELCDIF_CSC_COEFF2_RSRVD0 0x0000FC00 +#define BF_ELCDIF_CSC_COEFF2_RSRVD0(v) \ + (((v) << 10) & BM_ELCDIF_CSC_COEFF2_RSRVD0) +#define BP_ELCDIF_CSC_COEFF2_C3 0 +#define BM_ELCDIF_CSC_COEFF2_C3 0x000003FF +#define BF_ELCDIF_CSC_COEFF2_C3(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF2_C3) + +#define HW_ELCDIF_CSC_COEFF3 (0x00000140) + +#define BP_ELCDIF_CSC_COEFF3_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF3_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF3_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF3_RSRVD1) +#define BP_ELCDIF_CSC_COEFF3_C6 16 +#define BM_ELCDIF_CSC_COEFF3_C6 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF3_C6(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF3_C6) +#define BP_ELCDIF_CSC_COEFF3_RSRVD0 10 +#define BM_ELCDIF_CSC_COEFF3_RSRVD0 0x0000FC00 +#define BF_ELCDIF_CSC_COEFF3_RSRVD0(v) \ + (((v) << 10) & BM_ELCDIF_CSC_COEFF3_RSRVD0) +#define BP_ELCDIF_CSC_COEFF3_C5 0 +#define BM_ELCDIF_CSC_COEFF3_C5 0x000003FF +#define BF_ELCDIF_CSC_COEFF3_C5(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF3_C5) + +#define HW_ELCDIF_CSC_COEFF4 (0x00000150) + +#define BP_ELCDIF_CSC_COEFF4_RSRVD1 26 +#define BM_ELCDIF_CSC_COEFF4_RSRVD1 0xFC000000 +#define BF_ELCDIF_CSC_COEFF4_RSRVD1(v) \ + (((v) << 26) & BM_ELCDIF_CSC_COEFF4_RSRVD1) +#define BP_ELCDIF_CSC_COEFF4_C8 16 +#define BM_ELCDIF_CSC_COEFF4_C8 0x03FF0000 +#define BF_ELCDIF_CSC_COEFF4_C8(v) \ + (((v) << 16) & BM_ELCDIF_CSC_COEFF4_C8) +#define BP_ELCDIF_CSC_COEFF4_RSRVD0 10 +#define BM_ELCDIF_CSC_COEFF4_RSRVD0 0x0000FC00 +#define BF_ELCDIF_CSC_COEFF4_RSRVD0(v) \ + (((v) << 10) & BM_ELCDIF_CSC_COEFF4_RSRVD0) +#define BP_ELCDIF_CSC_COEFF4_C7 0 +#define BM_ELCDIF_CSC_COEFF4_C7 0x000003FF +#define BF_ELCDIF_CSC_COEFF4_C7(v) \ + (((v) << 0) & BM_ELCDIF_CSC_COEFF4_C7) + +#define HW_ELCDIF_CSC_OFFSET (0x00000160) + +#define BP_ELCDIF_CSC_OFFSET_RSRVD1 25 +#define BM_ELCDIF_CSC_OFFSET_RSRVD1 0xFE000000 +#define BF_ELCDIF_CSC_OFFSET_RSRVD1(v) \ + (((v) << 25) & BM_ELCDIF_CSC_OFFSET_RSRVD1) +#define BP_ELCDIF_CSC_OFFSET_CBCR_OFFSET 16 +#define BM_ELCDIF_CSC_OFFSET_CBCR_OFFSET 0x01FF0000 +#define BF_ELCDIF_CSC_OFFSET_CBCR_OFFSET(v) \ + (((v) << 16) & BM_ELCDIF_CSC_OFFSET_CBCR_OFFSET) +#define BP_ELCDIF_CSC_OFFSET_RSRVD0 9 +#define BM_ELCDIF_CSC_OFFSET_RSRVD0 0x0000FE00 +#define BF_ELCDIF_CSC_OFFSET_RSRVD0(v) \ + (((v) << 9) & BM_ELCDIF_CSC_OFFSET_RSRVD0) +#define BP_ELCDIF_CSC_OFFSET_Y_OFFSET 0 +#define BM_ELCDIF_CSC_OFFSET_Y_OFFSET 0x000001FF +#define BF_ELCDIF_CSC_OFFSET_Y_OFFSET(v) \ + (((v) << 0) & BM_ELCDIF_CSC_OFFSET_Y_OFFSET) + +#define HW_ELCDIF_CSC_LIMIT (0x00000170) + +#define BP_ELCDIF_CSC_LIMIT_CBCR_MIN 24 +#define BM_ELCDIF_CSC_LIMIT_CBCR_MIN 0xFF000000 +#define BF_ELCDIF_CSC_LIMIT_CBCR_MIN(v) \ + (((v) << 24) & BM_ELCDIF_CSC_LIMIT_CBCR_MIN) +#define BP_ELCDIF_CSC_LIMIT_CBCR_MAX 16 +#define BM_ELCDIF_CSC_LIMIT_CBCR_MAX 0x00FF0000 +#define BF_ELCDIF_CSC_LIMIT_CBCR_MAX(v) \ + (((v) << 16) & BM_ELCDIF_CSC_LIMIT_CBCR_MAX) +#define BP_ELCDIF_CSC_LIMIT_Y_MIN 8 +#define BM_ELCDIF_CSC_LIMIT_Y_MIN 0x0000FF00 +#define BF_ELCDIF_CSC_LIMIT_Y_MIN(v) \ + (((v) << 8) & BM_ELCDIF_CSC_LIMIT_Y_MIN) +#define BP_ELCDIF_CSC_LIMIT_Y_MAX 0 +#define BM_ELCDIF_CSC_LIMIT_Y_MAX 0x000000FF +#define BF_ELCDIF_CSC_LIMIT_Y_MAX(v) \ + (((v) << 0) & BM_ELCDIF_CSC_LIMIT_Y_MAX) + +#define HW_ELCDIF_DATA (0x00000180) + +#define BP_ELCDIF_DATA_DATA_THREE 24 +#define BM_ELCDIF_DATA_DATA_THREE 0xFF000000 +#define BF_ELCDIF_DATA_DATA_THREE(v) \ + (((v) << 24) & BM_ELCDIF_DATA_DATA_THREE) +#define BP_ELCDIF_DATA_DATA_TWO 16 +#define BM_ELCDIF_DATA_DATA_TWO 0x00FF0000 +#define BF_ELCDIF_DATA_DATA_TWO(v) \ + (((v) << 16) & BM_ELCDIF_DATA_DATA_TWO) +#define BP_ELCDIF_DATA_DATA_ONE 8 +#define BM_ELCDIF_DATA_DATA_ONE 0x0000FF00 +#define BF_ELCDIF_DATA_DATA_ONE(v) \ + (((v) << 8) & BM_ELCDIF_DATA_DATA_ONE) +#define BP_ELCDIF_DATA_DATA_ZERO 0 +#define BM_ELCDIF_DATA_DATA_ZERO 0x000000FF +#define BF_ELCDIF_DATA_DATA_ZERO(v) \ + (((v) << 0) & BM_ELCDIF_DATA_DATA_ZERO) + +#define HW_ELCDIF_BM_ERROR_STAT (0x00000190) + +#define BP_ELCDIF_BM_ERROR_STAT_ADDR 0 +#define BM_ELCDIF_BM_ERROR_STAT_ADDR 0xFFFFFFFF +#define BF_ELCDIF_BM_ERROR_STAT_ADDR(v) (v) + +#define HW_ELCDIF_CRC_STAT (0x000001a0) + +#define BP_ELCDIF_CRC_STAT_CRC_VALUE 0 +#define BM_ELCDIF_CRC_STAT_CRC_VALUE 0xFFFFFFFF +#define BF_ELCDIF_CRC_STAT_CRC_VALUE(v) (v) + +#define HW_ELCDIF_STAT (0x000001b0) + +#define BM_ELCDIF_STAT_PRESENT 0x80000000 +#define BM_ELCDIF_STAT_DMA_REQ 0x40000000 +#define BM_ELCDIF_STAT_LFIFO_FULL 0x20000000 +#define BM_ELCDIF_STAT_LFIFO_EMPTY 0x10000000 +#define BM_ELCDIF_STAT_TXFIFO_FULL 0x08000000 +#define BM_ELCDIF_STAT_TXFIFO_EMPTY 0x04000000 +#define BM_ELCDIF_STAT_BUSY 0x02000000 +#define BM_ELCDIF_STAT_DVI_CURRENT_FIELD 0x01000000 +#define BP_ELCDIF_STAT_RSRVD0 9 +#define BM_ELCDIF_STAT_RSRVD0 0x00FFFE00 +#define BF_ELCDIF_STAT_RSRVD0(v) \ + (((v) << 9) & BM_ELCDIF_STAT_RSRVD0) +#define BP_ELCDIF_STAT_LFIFO_COUNT 0 +#define BM_ELCDIF_STAT_LFIFO_COUNT 0x000001FF +#define BF_ELCDIF_STAT_LFIFO_COUNT(v) \ + (((v) << 0) & BM_ELCDIF_STAT_LFIFO_COUNT) + +#define HW_ELCDIF_VERSION (0x000001c0) + +#define BP_ELCDIF_VERSION_MAJOR 24 +#define BM_ELCDIF_VERSION_MAJOR 0xFF000000 +#define BF_ELCDIF_VERSION_MAJOR(v) \ + (((v) << 24) & BM_ELCDIF_VERSION_MAJOR) +#define BP_ELCDIF_VERSION_MINOR 16 +#define BM_ELCDIF_VERSION_MINOR 0x00FF0000 +#define BF_ELCDIF_VERSION_MINOR(v) \ + (((v) << 16) & BM_ELCDIF_VERSION_MINOR) +#define BP_ELCDIF_VERSION_STEP 0 +#define BM_ELCDIF_VERSION_STEP 0x0000FFFF +#define BF_ELCDIF_VERSION_STEP(v) \ + (((v) << 0) & BM_ELCDIF_VERSION_STEP) + +#define HW_ELCDIF_DEBUG0 (0x000001d0) + +#define BM_ELCDIF_DEBUG0_STREAMING_END_DETECTED 0x80000000 +#define BM_ELCDIF_DEBUG0_WAIT_FOR_VSYNC_EDGE_OUT 0x40000000 +#define BM_ELCDIF_DEBUG0_SYNC_SIGNALS_ON_REG 0x20000000 +#define BM_ELCDIF_DEBUG0_DMACMDKICK 0x10000000 +#define BM_ELCDIF_DEBUG0_ENABLE 0x08000000 +#define BM_ELCDIF_DEBUG0_HSYNC 0x04000000 +#define BM_ELCDIF_DEBUG0_VSYNC 0x02000000 +#define BM_ELCDIF_DEBUG0_CUR_FRAME_TX 0x01000000 +#define BM_ELCDIF_DEBUG0_EMPTY_WORD 0x00800000 +#define BP_ELCDIF_DEBUG0_CUR_STATE 16 +#define BM_ELCDIF_DEBUG0_CUR_STATE 0x007F0000 +#define BF_ELCDIF_DEBUG0_CUR_STATE(v) \ + (((v) << 16) & BM_ELCDIF_DEBUG0_CUR_STATE) +#define BM_ELCDIF_DEBUG0_PXP_ELCDIF_B0_READY 0x00008000 +#define BM_ELCDIF_DEBUG0_ELCDIF_PXP_B0_DONE 0x00004000 +#define BM_ELCDIF_DEBUG0_PXP_ELCDIF_B1_READY 0x00002000 +#define BM_ELCDIF_DEBUG0_ELCDIF_PXP_B1_DONE 0x00001000 +#define BP_ELCDIF_DEBUG0_CUR_REQ_STATE 10 +#define BM_ELCDIF_DEBUG0_CUR_REQ_STATE 0x00000C00 +#define BF_ELCDIF_DEBUG0_CUR_REQ_STATE(v) \ + (((v) << 10) & BM_ELCDIF_DEBUG0_CUR_REQ_STATE) +#define BM_ELCDIF_DEBUG0_MST_AVALID 0x00000200 +#define BP_ELCDIF_DEBUG0_MST_OUTSTANDING_REQS 4 +#define BM_ELCDIF_DEBUG0_MST_OUTSTANDING_REQS 0x000001F0 +#define BF_ELCDIF_DEBUG0_MST_OUTSTANDING_REQS(v) \ + (((v) << 4) & BM_ELCDIF_DEBUG0_MST_OUTSTANDING_REQS) +#define BP_ELCDIF_DEBUG0_MST_WORDS 0 +#define BM_ELCDIF_DEBUG0_MST_WORDS 0x0000000F +#define BF_ELCDIF_DEBUG0_MST_WORDS(v) \ + (((v) << 0) & BM_ELCDIF_DEBUG0_MST_WORDS) + +#define HW_ELCDIF_DEBUG1 (0x000001e0) + +#define BP_ELCDIF_DEBUG1_H_DATA_COUNT 16 +#define BM_ELCDIF_DEBUG1_H_DATA_COUNT 0xFFFF0000 +#define BF_ELCDIF_DEBUG1_H_DATA_COUNT(v) \ + (((v) << 16) & BM_ELCDIF_DEBUG1_H_DATA_COUNT) +#define BP_ELCDIF_DEBUG1_V_DATA_COUNT 0 +#define BM_ELCDIF_DEBUG1_V_DATA_COUNT 0x0000FFFF +#define BF_ELCDIF_DEBUG1_V_DATA_COUNT(v) \ + (((v) << 0) & BM_ELCDIF_DEBUG1_V_DATA_COUNT) + +#define HW_ELCDIF_DEBUG2 (0x000001f0) + +#define BP_ELCDIF_DEBUG2_MST_ADDRESS 0 +#define BM_ELCDIF_DEBUG2_MST_ADDRESS 0xFFFFFFFF +#define BF_ELCDIF_DEBUG2_MST_ADDRESS(v) (v) +#endif /* __ELCDIF_REGS_INCLUDED_ */ diff --git a/drivers/video/mxc/epdc_regs.h b/drivers/video/mxc/epdc_regs.h new file mode 100644 index 00000000..59c0f348 --- /dev/null +++ b/drivers/video/mxc/epdc_regs.h @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2010-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 + * + */ +#ifndef __EPDC_REGS_INCLUDED__ +#define __EPDC_REGS_INCLUDED__ + +extern void __iomem *epdc_base; + +/************************************* + * Register addresses + **************************************/ + +#define EPDC_CTRL (epdc_base + 0x000) +#define EPDC_CTRL_SET (epdc_base + 0x004) +#define EPDC_CTRL_CLEAR (epdc_base + 0x008) +#define EPDC_CTRL_TOGGLE (epdc_base + 0x00C) +#define EPDC_WVADDR (epdc_base + 0x020) +#define EPDC_WB_ADDR (epdc_base + 0x030) +#define EPDC_RES (epdc_base + 0x040) +#define EPDC_FORMAT (epdc_base + 0x050) +#define EPDC_FORMAT_SET (epdc_base + 0x054) +#define EPDC_FORMAT_CLEAR (epdc_base + 0x058) +#define EPDC_FORMAT_TOGGLE (epdc_base + 0x05C) +#define EPDC_FIFOCTRL (epdc_base + 0x0A0) +#define EPDC_FIFOCTRL_SET (epdc_base + 0x0A4) +#define EPDC_FIFOCTRL_CLEAR (epdc_base + 0x0A8) +#define EPDC_FIFOCTRL_TOGGLE (epdc_base + 0x0AC) +#define EPDC_UPD_ADDR (epdc_base + 0x100) +#define EPDC_UPD_STRIDE (epdc_base + 0x110) +#define EPDC_UPD_CORD (epdc_base + 0x120) +#define EPDC_UPD_SIZE (epdc_base + 0x140) +#define EPDC_UPD_CTRL (epdc_base + 0x160) +#define EPDC_UPD_FIXED (epdc_base + 0x180) +#define EPDC_TEMP (epdc_base + 0x1A0) +#define EPDC_AUTOWV_LUT (epdc_base + 0x1C0) +#define EPDC_TCE_CTRL (epdc_base + 0x200) +#define EPDC_TCE_SDCFG (epdc_base + 0x220) +#define EPDC_TCE_GDCFG (epdc_base + 0x240) +#define EPDC_TCE_HSCAN1 (epdc_base + 0x260) +#define EPDC_TCE_HSCAN2 (epdc_base + 0x280) +#define EPDC_TCE_VSCAN (epdc_base + 0x2A0) +#define EPDC_TCE_OE (epdc_base + 0x2C0) +#define EPDC_TCE_POLARITY (epdc_base + 0x2E0) +#define EPDC_TCE_TIMING1 (epdc_base + 0x300) +#define EPDC_TCE_TIMING2 (epdc_base + 0x310) +#define EPDC_TCE_TIMING3 (epdc_base + 0x320) +#define EPDC_PIGEON_CTRL0 (epdc_base + 0x380) +#define EPDC_PIGEON_CTRL1 (epdc_base + 0x390) +#define EPDC_IRQ_MASK1 (epdc_base + 0x3C0) +#define EPDC_IRQ_MASK1_SET (epdc_base + 0x3C4) +#define EPDC_IRQ_MASK1_CLEAR (epdc_base + 0x3C8) +#define EPDC_IRQ_MASK1_TOGGLE (epdc_base + 0x3CC) +#define EPDC_IRQ_MASK2 (epdc_base + 0x3D0) +#define EPDC_IRQ_MASK2_SET (epdc_base + 0x3D4) +#define EPDC_IRQ_MASK2_CLEAR (epdc_base + 0x3D8) +#define EPDC_IRQ_MASK2_TOGGLE (epdc_base + 0x3DC) +#define EPDC_IRQ1 (epdc_base + 0x3E0) +#define EPDC_IRQ1_SET (epdc_base + 0x3E4) +#define EPDC_IRQ1_CLEAR (epdc_base + 0x3E8) +#define EPDC_IRQ1_TOGGLE (epdc_base + 0x3EC) +#define EPDC_IRQ2 (epdc_base + 0x3F0) +#define EPDC_IRQ2_SET (epdc_base + 0x3F4) +#define EPDC_IRQ2_CLEAR (epdc_base + 0x3F8) +#define EPDC_IRQ2_TOGGLE (epdc_base + 0x3FC) +#define EPDC_IRQ_MASK (epdc_base + 0x400) +#define EPDC_IRQ_MASK_SET (epdc_base + 0x404) +#define EPDC_IRQ_MASK_CLEAR (epdc_base + 0x408) +#define EPDC_IRQ_MASK_TOGGLE (epdc_base + 0x40C) +#define EPDC_IRQ (epdc_base + 0x420) +#define EPDC_IRQ_SET (epdc_base + 0x424) +#define EPDC_IRQ_CLEAR (epdc_base + 0x428) +#define EPDC_IRQ_TOGGLE (epdc_base + 0x42C) +#define EPDC_STATUS_LUTS (epdc_base + 0x440) +#define EPDC_STATUS_LUTS_SET (epdc_base + 0x444) +#define EPDC_STATUS_LUTS_CLEAR (epdc_base + 0x448) +#define EPDC_STATUS_LUTS_TOGGLE (epdc_base + 0x44C) +#define EPDC_STATUS_LUTS2 (epdc_base + 0x450) +#define EPDC_STATUS_LUTS2_SET (epdc_base + 0x454) +#define EPDC_STATUS_LUTS2_CLEAR (epdc_base + 0x458) +#define EPDC_STATUS_LUTS2_TOGGLE (epdc_base + 0x45C) +#define EPDC_STATUS_NEXTLUT (epdc_base + 0x460) +#define EPDC_STATUS_COL (epdc_base + 0x480) +#define EPDC_STATUS_COL2 (epdc_base + 0x490) +#define EPDC_STATUS (epdc_base + 0x4A0) +#define EPDC_STATUS_SET (epdc_base + 0x4A4) +#define EPDC_STATUS_CLEAR (epdc_base + 0x4A8) +#define EPDC_STATUS_TOGGLE (epdc_base + 0x4AC) +#define EPDC_UPD_COL_CORD (epdc_base + 0x4C0) +#define EPDC_UPD_COL_SIZE (epdc_base + 0x4E0) +#define EPDC_DEBUG (epdc_base + 0x500) +#define EPDC_DEBUG_LUT (epdc_base + 0x530) +#define EPDC_HIST1_PARAM (epdc_base + 0x600) +#define EPDC_HIST2_PARAM (epdc_base + 0x610) +#define EPDC_HIST4_PARAM (epdc_base + 0x620) +#define EPDC_HIST8_PARAM0 (epdc_base + 0x630) +#define EPDC_HIST8_PARAM1 (epdc_base + 0x640) +#define EPDC_HIST16_PARAM0 (epdc_base + 0x650) +#define EPDC_HIST16_PARAM1 (epdc_base + 0x660) +#define EPDC_HIST16_PARAM2 (epdc_base + 0x670) +#define EPDC_HIST16_PARAM3 (epdc_base + 0x680) +#define EPDC_GPIO (epdc_base + 0x700) +#define EPDC_VERSION (epdc_base + 0x7F0) +#define EPDC_PIGEON_0_0 (epdc_base + 0x800) +#define EPDC_PIGEON_0_1 (epdc_base + 0x810) +#define EPDC_PIGEON_0_2 (epdc_base + 0x820) +#define EPDC_PIGEON_1_0 (epdc_base + 0x840) +#define EPDC_PIGEON_1_1 (epdc_base + 0x850) +#define EPDC_PIGEON_1_2 (epdc_base + 0x860) +#define EPDC_PIGEON_2_0 (epdc_base + 0x880) +#define EPDC_PIGEON_2_1 (epdc_base + 0x890) +#define EPDC_PIGEON_2_2 (epdc_base + 0x8A0) +#define EPDC_PIGEON_3_0 (epdc_base + 0x8C0) +#define EPDC_PIGEON_3_1 (epdc_base + 0x8D0) +#define EPDC_PIGEON_3_2 (epdc_base + 0x8E0) +#define EPDC_PIGEON_4_0 (epdc_base + 0x900) +#define EPDC_PIGEON_4_1 (epdc_base + 0x910) +#define EPDC_PIGEON_4_2 (epdc_base + 0x920) +#define EPDC_PIGEON_5_0 (epdc_base + 0x940) +#define EPDC_PIGEON_5_1 (epdc_base + 0x950) +#define EPDC_PIGEON_5_2 (epdc_base + 0x960) +#define EPDC_PIGEON_6_0 (epdc_base + 0x980) +#define EPDC_PIGEON_6_1 (epdc_base + 0x990) +#define EPDC_PIGEON_6_2 (epdc_base + 0x9A0) +#define EPDC_PIGEON_7_0 (epdc_base + 0x9C0) +#define EPDC_PIGEON_7_1 (epdc_base + 0x9D0) +#define EPDC_PIGEON_7_2 (epdc_base + 0x9E0) +#define EPDC_PIGEON_8_0 (epdc_base + 0xA00) +#define EPDC_PIGEON_8_1 (epdc_base + 0xA10) +#define EPDC_PIGEON_8_2 (epdc_base + 0xA20) +#define EPDC_PIGEON_9_0 (epdc_base + 0xA40) +#define EPDC_PIGEON_9_1 (epdc_base + 0xA50) +#define EPDC_PIGEON_9_2 (epdc_base + 0xA60) +#define EPDC_PIGEON_10_0 (epdc_base + 0xA80) +#define EPDC_PIGEON_10_1 (epdc_base + 0xA90) +#define EPDC_PIGEON_10_2 (epdc_base + 0xAA0) +#define EPDC_PIGEON_11_0 (epdc_base + 0xAC0) +#define EPDC_PIGEON_11_1 (epdc_base + 0xAD0) +#define EPDC_PIGEON_11_2 (epdc_base + 0xAE0) +#define EPDC_PIGEON_12_0 (epdc_base + 0xB00) +#define EPDC_PIGEON_12_1 (epdc_base + 0xB10) +#define EPDC_PIGEON_12_2 (epdc_base + 0xB20) +#define EPDC_PIGEON_13_0 (epdc_base + 0xB40) +#define EPDC_PIGEON_13_1 (epdc_base + 0xB50) +#define EPDC_PIGEON_13_2 (epdc_base + 0xB60) +#define EPDC_PIGEON_14_0 (epdc_base + 0xB80) +#define EPDC_PIGEON_14_1 (epdc_base + 0xB90) +#define EPDC_PIGEON_14_2 (epdc_base + 0xBA0) +#define EPDC_PIGEON_15_0 (epdc_base + 0xBC0) +#define EPDC_PIGEON_15_1 (epdc_base + 0xBD0) +#define EPDC_PIGEON_15_2 (epdc_base + 0xBE0) +#define EPDC_WB_ADDR_TCE (epdc_base + 0xC10) + +/* + * Register field definitions + */ + +enum { +/* EPDC_CTRL field values */ + EPDC_CTRL_SFTRST = 0x80000000, + EPDC_CTRL_CLKGATE = 0x40000000, + EPDC_CTRL_SRAM_POWERDOWN = 0x100, + EPDC_CTRL_UPD_DATA_SWIZZLE_MASK = 0xC0, + EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP = 0, + EPDC_CTRL_UPD_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x40, + EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_SWAP = 0x80, + EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_BYTE_SWAP = 0xC0, + EPDC_CTRL_LUT_DATA_SWIZZLE_MASK = 0x30, + EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP = 0, + EPDC_CTRL_LUT_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x10, + EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_SWAP = 0x20, + EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_BYTE_SWAP = 0x30, + EPDC_CTRL_BURST_LEN_8_8 = 0x1, + EPDC_CTRL_BURST_LEN_8_16 = 0, + +/* EPDC_RES field values */ + EPDC_RES_VERTICAL_MASK = 0x1FFF0000, + EPDC_RES_VERTICAL_OFFSET = 16, + EPDC_RES_HORIZONTAL_MASK = 0x1FFF, + EPDC_RES_HORIZONTAL_OFFSET = 0, + +/* EPDC_FORMAT field values */ + EPDC_FORMAT_BUF_PIXEL_SCALE_ROUND = 0x1000000, + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK = 0xFF0000, + EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET = 16, + EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK = 0x700, + EPDC_FORMAT_BUF_PIXEL_FORMAT_OFFSET = 8, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P2N = 0x200, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P3N = 0x300, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N = 0x400, + EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N = 0x500, + EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT = 0x0, + EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT_VCOM = 0x1, + EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT = 0x2, + EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT_VCOM = 0x3, + +/* EPDC_FIFOCTRL field values */ + EPDC_FIFOCTRL_ENABLE_PRIORITY = 0x80000000, + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK = 0xFF0000, + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET = 16, + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK = 0xFF00, + EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET = 8, + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK = 0xFF, + EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET = 0, + +/* EPDC_UPD_CORD field values */ + EPDC_UPD_CORD_YCORD_MASK = 0x1FFF0000, + EPDC_UPD_CORD_YCORD_OFFSET = 16, + EPDC_UPD_CORD_XCORD_MASK = 0x1FFF, + EPDC_UPD_CORD_XCORD_OFFSET = 0, + +/* EPDC_UPD_SIZE field values */ + EPDC_UPD_SIZE_HEIGHT_MASK = 0x1FFF0000, + EPDC_UPD_SIZE_HEIGHT_OFFSET = 16, + EPDC_UPD_SIZE_WIDTH_MASK = 0x1FFF, + EPDC_UPD_SIZE_WIDTH_OFFSET = 0, + +/* EPDC_UPD_CTRL field values */ + EPDC_UPD_CTRL_USE_FIXED = 0x80000000, + EPDC_UPD_CTRL_LUT_SEL_MASK = 0x3F0000, + EPDC_UPD_CTRL_LUT_SEL_OFFSET = 16, + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK = 0xFF00, + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET = 8, + EPDC_UPD_CTRL_AUTOWV_PAUSE = 0x8, + EPDC_UPD_CTRL_AUTOWV = 0x4, + EPDC_UPD_CTRL_DRY_RUN = 0x2, + EPDC_UPD_CTRL_UPDATE_MODE_FULL = 0x1, + +/* EPDC_UPD_FIXED field values */ + EPDC_UPD_FIXED_FIXNP_EN = 0x80000000, + EPDC_UPD_FIXED_FIXCP_EN = 0x40000000, + EPDC_UPD_FIXED_FIXNP_MASK = 0xFF00, + EPDC_UPD_FIXED_FIXNP_OFFSET = 8, + EPDC_UPD_FIXED_FIXCP_MASK = 0xFF, + EPDC_UPD_FIXED_FIXCP_OFFSET = 0, + +/* EPDC_AUTOWV_LUT field values */ + EPDC_AUTOWV_LUT_DATA_MASK = 0xFF0000, + EPDC_AUTOWV_LUT_DATA_OFFSET = 16, + EPDC_AUTOWV_LUT_ADDR_MASK = 0xFF, + EPDC_AUTOWV_LUT_ADDR_OFFSET = 0, + +/* EPDC_TCE_CTRL field values */ + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK = 0x1FF0000, + EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET = 16, + EPDC_TCE_CTRL_VCOM_VAL_MASK = 0xC00, + EPDC_TCE_CTRL_VCOM_VAL_OFFSET = 10, + EPDC_TCE_CTRL_VCOM_MODE_AUTO = 0x200, + EPDC_TCE_CTRL_VCOM_MODE_MANUAL = 0x000, + EPDC_TCE_CTRL_DDR_MODE_ENABLE = 0x100, + EPDC_TCE_CTRL_LVDS_MODE_CE_ENABLE = 0x80, + EPDC_TCE_CTRL_LVDS_MODE_ENABLE = 0x40, + EPDC_TCE_CTRL_SCAN_DIR_1_UP = 0x20, + EPDC_TCE_CTRL_SCAN_DIR_0_UP = 0x10, + EPDC_TCE_CTRL_DUAL_SCAN_ENABLE = 0x8, + EPDC_TCE_CTRL_SDDO_WIDTH_16BIT = 0x4, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_2 = 1, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4 = 2, + EPDC_TCE_CTRL_PIXELS_PER_SDCLK_8 = 3, + +/* EPDC_TCE_SDCFG field values */ + EPDC_TCE_SDCFG_SDCLK_HOLD = 0x200000, + EPDC_TCE_SDCFG_SDSHR = 0x100000, + EPDC_TCE_SDCFG_NUM_CE_MASK = 0xF0000, + EPDC_TCE_SDCFG_NUM_CE_OFFSET = 16, + EPDC_TCE_SDCFG_SDDO_REFORMAT_STANDARD = 0, + EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS = 0x4000, + EPDC_TCE_SDCFG_SDDO_INVERT_ENABLE = 0x2000, + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK = 0x1FFF, + EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET = 0, + +/* EPDC_TCE_GDCFG field values */ + EPDC_TCE_SDCFG_GDRL = 0x10, + EPDC_TCE_SDCFG_GDOE_MODE_DELAYED_GDCLK = 0x2, + EPDC_TCE_SDCFG_GDSP_MODE_FRAME_SYNC = 0x1, + EPDC_TCE_SDCFG_GDSP_MODE_ONE_LINE = 0x0, + +/* EPDC_TCE_HSCAN1 field values */ + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK = 0xFFF0000, + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET = 16, + EPDC_TCE_HSCAN1_LINE_SYNC_MASK = 0xFFF, + EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET = 0, + +/* EPDC_TCE_HSCAN2 field values */ + EPDC_TCE_HSCAN2_LINE_END_MASK = 0xFFF0000, + EPDC_TCE_HSCAN2_LINE_END_OFFSET = 16, + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK = 0xFFF, + EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET = 0, + +/* EPDC_TCE_VSCAN field values */ + EPDC_TCE_VSCAN_FRAME_END_MASK = 0xFF0000, + EPDC_TCE_VSCAN_FRAME_END_OFFSET = 16, + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK = 0xFF00, + EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET = 8, + EPDC_TCE_VSCAN_FRAME_SYNC_MASK = 0xFF, + EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET = 0, + +/* EPDC_TCE_OE field values */ + EPDC_TCE_OE_SDOED_WIDTH_MASK = 0xFF000000, + EPDC_TCE_OE_SDOED_WIDTH_OFFSET = 24, + EPDC_TCE_OE_SDOED_DLY_MASK = 0xFF0000, + EPDC_TCE_OE_SDOED_DLY_OFFSET = 16, + EPDC_TCE_OE_SDOEZ_WIDTH_MASK = 0xFF00, + EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET = 8, + EPDC_TCE_OE_SDOEZ_DLY_MASK = 0xFF, + EPDC_TCE_OE_SDOEZ_DLY_OFFSET = 0, + +/* EPDC_TCE_POLARITY field values */ + EPDC_TCE_POLARITY_GDSP_POL_ACTIVE_HIGH = 0x10, + EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH = 0x8, + EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH = 0x4, + EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH = 0x2, + EPDC_TCE_POLARITY_SDCE_POL_ACTIVE_HIGH = 0x1, + +/* EPDC_TCE_TIMING1 field values */ + EPDC_TCE_TIMING1_SDLE_SHIFT_NONE = 0x00, + EPDC_TCE_TIMING1_SDLE_SHIFT_1 = 0x10, + EPDC_TCE_TIMING1_SDLE_SHIFT_2 = 0x20, + EPDC_TCE_TIMING1_SDLE_SHIFT_3 = 0x30, + EPDC_TCE_TIMING1_SDCLK_INVERT = 0x8, + EPDC_TCE_TIMING1_SDCLK_SHIFT_NONE = 0, + EPDC_TCE_TIMING1_SDCLK_SHIFT_1CYCLE = 1, + EPDC_TCE_TIMING1_SDCLK_SHIFT_2CYCLES = 2, + EPDC_TCE_TIMING1_SDCLK_SHIFT_3CYCLES = 3, + +/* EPDC_TCE_TIMING2 field values */ + EPDC_TCE_TIMING2_GDCLK_HP_MASK = 0xFFFF0000, + EPDC_TCE_TIMING2_GDCLK_HP_OFFSET = 16, + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK = 0xFFFF, + EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET = 0, + +/* EPDC_TCE_TIMING3 field values */ + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK = 0xFFFF0000, + EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET = 16, + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK = 0xFFFF, + EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET = 0, + +/* EPDC_IRQ_MASK/EPDC_IRQ field values */ + EPDC_IRQ_WB_CMPLT_IRQ = 0x10000, + EPDC_IRQ_LUT_COL_IRQ = 0x20000, + EPDC_IRQ_TCE_UNDERRUN_IRQ = 0x40000, + EPDC_IRQ_FRAME_END_IRQ = 0x80000, + EPDC_IRQ_BUS_ERROR_IRQ = 0x100000, + EPDC_IRQ_TCE_IDLE_IRQ = 0x200000, + EPDC_IRQ_UPD_DONE_IRQ = 0x400000, + EPDC_IRQ_PWR_IRQ = 0x800000, + +/* EPDC_STATUS_NEXTLUT field values */ + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID = 0x100, + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK = 0x3F, + EPDC_STATUS_NEXTLUT_NEXT_LUT_OFFSET = 0, + +/* EPDC_STATUS field values */ + EPDC_STATUS_HISTOGRAM_CP_MASK = 0x1F0000, + EPDC_STATUS_HISTOGRAM_CP_OFFSET = 16, + EPDC_STATUS_HISTOGRAM_NP_MASK = 0x1F00, + EPDC_STATUS_HISTOGRAM_NP_OFFSET = 8, + EPDC_STATUS_UPD_VOID = 0x8, + EPDC_STATUS_LUTS_UNDERRUN = 0x4, + EPDC_STATUS_LUTS_BUSY = 0x2, + EPDC_STATUS_WB_BUSY = 0x1, + +/* EPDC_UPD_COL_CORD field values */ + EPDC_UPD_COL_CORD_YCORD_MASK = 0x1FFF0000, + EPDC_UPD_COL_CORD_YCORD_OFFSET = 16, + EPDC_UPD_COL_CORD_XCORD_MASK = 0x1FFF, + EPDC_UPD_COL_CORD_XCORD_OFFSET = 0, + +/* EPDC_UPD_COL_SIZE field values */ + EPDC_UPD_COL_SIZE_HEIGHT_MASK = 0x1FFF0000, + EPDC_UPD_COL_SIZE_HEIGHT_OFFSET = 16, + EPDC_UPD_COL_SIZE_WIDTH_MASK = 0x1FFF, + EPDC_UPD_COL_SIZE_WIDTH_OFFSET = 0, + +/* EPDC_DEBUG field values */ + EPDC_DEBUG_UNDERRUN_RECOVER = 0x2, + EPDC_DEBUG_COLLISION_OFF = 0x1, + +/* EPDC_HISTx_PARAM field values */ + EPDC_HIST_PARAM_VALUE0_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE0_OFFSET = 0, + EPDC_HIST_PARAM_VALUE1_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE1_OFFSET = 8, + EPDC_HIST_PARAM_VALUE2_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE2_OFFSET = 16, + EPDC_HIST_PARAM_VALUE3_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE3_OFFSET = 24, + EPDC_HIST_PARAM_VALUE4_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE4_OFFSET = 0, + EPDC_HIST_PARAM_VALUE5_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE5_OFFSET = 8, + EPDC_HIST_PARAM_VALUE6_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE6_OFFSET = 16, + EPDC_HIST_PARAM_VALUE7_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE7_OFFSET = 24, + EPDC_HIST_PARAM_VALUE8_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE8_OFFSET = 0, + EPDC_HIST_PARAM_VALUE9_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE9_OFFSET = 8, + EPDC_HIST_PARAM_VALUE10_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE10_OFFSET = 16, + EPDC_HIST_PARAM_VALUE11_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE11_OFFSET = 24, + EPDC_HIST_PARAM_VALUE12_MASK = 0x1F, + EPDC_HIST_PARAM_VALUE12_OFFSET = 0, + EPDC_HIST_PARAM_VALUE13_MASK = 0x1F00, + EPDC_HIST_PARAM_VALUE13_OFFSET = 8, + EPDC_HIST_PARAM_VALUE14_MASK = 0x1F0000, + EPDC_HIST_PARAM_VALUE14_OFFSET = 16, + EPDC_HIST_PARAM_VALUE15_MASK = 0x1F000000, + EPDC_HIST_PARAM_VALUE15_OFFSET = 24, + +/* EPDC_GPIO field values */ + EPDC_GPIO_PWRCOM = 0x40, + EPDC_GPIO_PWRCTRL_MASK = 0x3C, + EPDC_GPIO_PWRCTRL_OFFSET = 2, + EPDC_GPIO_BDR_MASK = 0x3, + EPDC_GPIO_BDR_OFFSET = 0, + +/* EPDC_VERSION field values */ + EPDC_VERSION_MAJOR_MASK = 0xFF000000, + EPDC_VERSION_MAJOR_OFFSET = 24, + EPDC_VERSION_MINOR_MASK = 0xFF0000, + EPDC_VERSION_MINOR_OFFSET = 16, + EPDC_VERSION_STEP_MASK = 0xFFFF, + EPDC_VERSION_STEP_OFFSET = 0, +}; + +#endif /* __EPDC_REGS_INCLUDED__ */ diff --git a/drivers/video/mxc/epdfb_dc.c b/drivers/video/mxc/epdfb_dc.c new file mode 100644 index 00000000..ac1679a7 --- /dev/null +++ b/drivers/video/mxc/epdfb_dc.c @@ -0,0 +1,1837 @@ +/*************************************************** + * EPD frame buffer device content manager (4 bits). + * + * Author : Gallen Lin + * Data : 2011/01/20 + * Revision : 1.0 + * File Name : epdfb_dc.c + * +****************************************************/ + +#include "epdfb_dc.h" + + + + +#ifdef __KERNEL__//[ + #include <linux/module.h> + #include <linux/kernel.h> + #include <linux/errno.h> + #include <linux/string.h> + #include <linux/delay.h> + #include <linux/interrupt.h> + #include <linux/fb.h> + + #define GDEBUG 0 + #include <linux/gallen_dbg.h> + #define my_malloc(sz) kmalloc(sz,GFP_KERNEL) + #define my_free(p) kfree(p) + #define my_memcpy(dest,src,size) memcpy(dest,src,size) + +#else //][!__KERNEL__ + #include <stdio.h> + #include <stdlib.h> + #include <string.h> + #define GDEBUG 0 + #include "gallen_dbg.h" + #define my_malloc(sz) malloc(sz) + #define my_free(p) free(p) + #define my_memcpy(dest,src,size) memcpy(dest,src,size) +#endif//] __KERNEL__ + + +#define EPDFB_MAGIC 0x15a815a8 + + +#define CHK_EPDFB_DC(pEPD_DC) \ +(pEPD_DC->dwMagicPrivateBegin==EPDFB_MAGIC)&&(pEPD_DC->dwMagicPrivateEnd==EPDFB_MAGIC) + +static const int giDither8[16][16] = { + { 1,235, 59,219, 15,231, 55,215, 2,232, 56,216, 12,228, 52,212}, + { 129, 65,187,123,143, 79,183,119,130, 66,184,120,140, 76,180,116}, + { 33,193, 17,251, 47,207, 31,247, 34,194, 18,248, 44,204, 28,244}, + { 161, 97,145, 81,175,111,159, 95,162, 98,146, 82,172,108,156, 92}, + { 9,225, 49,209, 5,239, 63,223, 10,226, 50,210, 6,236, 60,220}, + { 137, 73,177,113,133, 69,191,127,138, 74,178,114,134, 70,188,124}, + { 41,201, 25,241, 37,197, 21,255, 42,202, 26,242, 38,198, 22,252}, + { 169,105,153, 89,165,101,149, 85,170,106,154, 90,166,102,150, 86}, + { 3,233, 57,217, 13,229, 53,213, 0,234, 58,218, 14,230, 54,214}, + { 131, 67,185,121,141, 77,181,117,128, 64,186,122,142, 78,182,118}, + { 35,195, 19,249, 45,205, 29,245, 32,192, 16,250, 46,206, 30,246}, + { 163, 99,147, 83,173,109,157, 93,160, 96,144, 80,174,110,158, 94}, + { 11,227, 51,211, 7,237, 61,221, 8,224, 48,208, 4,238, 62,222}, + { 139, 75,179,115,135, 71,189,125,136, 72,176,112,132, 68,190,126}, + { 43,203, 27,243, 39,199, 23,253, 40,200, 24,240, 36,196, 20,254}, + { 171,107,155, 91,167,103,151, 87,168,104,152, 88,164,100,148, 84} +}; + + + +static void _fb_gray_8to4(unsigned char *data, unsigned long len) +{ + unsigned char *pb = (unsigned char *)data ; + unsigned long dwByteRdIdx,dwByteWrIdx; + unsigned char bTemp; + unsigned long dwLenNew = len; + + DBG_MSG("%s(%d):data=%p,len=%d\n",\ + __FUNCTION__,__LINE__,data,len); + + for(dwByteRdIdx=0,dwByteWrIdx=0;1;) + { + bTemp = (pb[dwByteRdIdx]>>4)&0x0f; + bTemp = bTemp<<4; + if(++dwByteRdIdx>=len){ + break; + } + bTemp |= (pb[dwByteRdIdx]>>4)&0x0f; + dwLenNew-=1; + pb[++dwByteWrIdx] = bTemp; + + if(++dwByteRdIdx>=len) { + break; + } + } + +} + +// get blue value to gray value . +#if 1 +#define _RGB565_to_Gray8(_wRGB_565) (((unsigned char)(_wRGB_565)&0x001f)<<3) +#define _RGB565_to_Gray4(_wRGB_565) (((unsigned char)(_wRGB_565)&0x001f)>>1) +#else +#define _RGB565_to_Gray8(_wRGB_565) \ + (unsigned char)(( \ + (((unsigned long)((unsigned char)(_wRGB_565)&0xf800)>>11)*299) + \ + (((unsigned long)((unsigned char)(_wRGB_565)&0x07e0)>>5)*587) + \ + ((unsigned long)((unsigned char)(_wRGB_565)&0x001f)*114) ) >> 8 ) + + +#define _RGB565_to_Gray4(_wRGB_565) \ + _RGB565_to_Gray8(_wRGB_565)>>4 +#endif +#define _RGB565_to_RGB888(_wRGB_565)({\ + unsigned long dwR,dwG,dwB;\ + unsigned long dwRGB888;\ + dwB=((_wRGB_565)&0x1f);\ + dwG=((_wRGB_565>>6)&0x3f);\ + dwR=((_wRGB_565>>11)&0x1f);\ + dwRGB888=dwR<<16|dwG<<8|dwB;\ + dwRGB888;\ +}) + +#define _RGB565_to_RGB8888(_wRGB_565) ({\ + unsigned long dwR,dwG,dwB,dwT;\ + unsigned long dwRGB8888;\ + dwB=((_wRGB_565)&0x1f);\ + dwG=((_wRGB_565>>6)&0x3f);\ + dwR=((_wRGB_565>>11)&0x1f);\ + dwT=0;\ + dwRGB8888=dwR<<24|dwG<<16|dwB<<8|dwT;\ + dwRGB8888;\ +}) + +#define _RGB565(_r,_g,_b) (unsigned short)((_r)<<11|(_g)<<6|(_b)) + +static const unsigned short gwGray4toRGB565_TableA[] = { + _RGB565(0x00,0x00,0x00), + _RGB565(0x02,0x02,0x02), + _RGB565(0x04,0x04,0x04), + _RGB565(0x06,0x06,0x06), + _RGB565(0x08,0x08,0x08), + _RGB565(0x0a,0x0a,0x0a), + _RGB565(0x0c,0x0c,0x0c), + _RGB565(0x0e,0x0e,0x0e), + _RGB565(0x10,0x10,0x10), + _RGB565(0x12,0x12,0x12), + _RGB565(0x14,0x14,0x14), + _RGB565(0x16,0x16,0x16), + _RGB565(0x18,0x18,0x18), + _RGB565(0x1a,0x1a,0x1a), + _RGB565(0x1c,0x1c,0x1c), + _RGB565(0x1e,0x1e,0x1e), +}; +static const unsigned short gwGray2toRGB565_TableA[] = { + _RGB565(0x00,0x00,0x00), + _RGB565(0x0a,0x0a,0x0a), + _RGB565(0x14,0x14,0x14), + _RGB565(0x1e,0x1e,0x1e), +}; + +static const unsigned char gbGray4toGray8_TableA[] = { + 0x00, + 0x10, + 0x20, + 0x30, + 0x40, + 0x50, + 0x60, + 0x70, + 0x80, + 0x90, + 0xa0, + 0xb0, + 0xc0, + 0xd0, + 0xe0, + 0xf0, +}; + +static void _fb_Gray4toRGB565( + unsigned char *IO_pbRGB565Buf,unsigned long I_dwRGB565BufSize, + unsigned char *I_pb4BitsSrc,unsigned long I_dw4BitsBufSize,int iIsPixelSwap) +{ + unsigned long dwRd; + unsigned short *pwWrBuf = (unsigned short *)IO_pbRGB565Buf; + unsigned char *pbRdSrc = I_pb4BitsSrc; + + int iIdxDot1,iIdxDot2; + + + for(dwRd=0;dwRd<I_dw4BitsBufSize;dwRd++) + { + + if(iIsPixelSwap) { + iIdxDot1 = (pbRdSrc[dwRd]>>4)&0xf; + iIdxDot2 = (pbRdSrc[dwRd])&0xf; + } + else { + iIdxDot1 = (pbRdSrc[dwRd])&0xf; + iIdxDot2 = (pbRdSrc[dwRd]>>4)&0xf; + } + + *pwWrBuf++ = gwGray4toRGB565_TableA[iIdxDot1]; + *pwWrBuf++ = gwGray4toRGB565_TableA[iIdxDot2]; + } +} + +static void _fb_Gray8toRGB565( + unsigned char *IO_pbRGB565Buf,unsigned long I_dwRGB565BufSize, + unsigned char *I_pb8BitsSrc,unsigned long I_dw8BitsBufSize) +{ + unsigned long dwRd,dwWr; + unsigned short *pwWrBuf = (unsigned short *)IO_pbRGB565Buf; + unsigned char *pbRdSrc = I_pb8BitsSrc; + + + + for(dwRd=0;dwRd<I_dw8BitsBufSize;dwRd++) + { + *pwWrBuf++ = (*pbRdSrc++)<<8; + if(++dwWr>=I_dwRGB565BufSize) { + ERR_MSG("%s(%d):write buffer too small \n",__FUNCTION__,__LINE__); + break; + } + } +} + +static void _fb_RGB565toGray4( + unsigned char *IO_pbGray4Buf,unsigned long I_dwGray4BufSize, + unsigned char *I_pbRGB565Buf,unsigned long I_dwRGB565BufSize,int iIsPixelSwap) +{ + unsigned long dwRd,dwWr; + unsigned char *pbWrBuf = (unsigned char *)IO_pbGray4Buf; + unsigned short *pwRdSrc = (unsigned short *)I_pbRGB565Buf; + + unsigned char bDot1,bDot2; + + ASSERT(!(I_dwRGB565BufSize&1)); + for(dwRd=0;dwRd<I_dwRGB565BufSize;dwRd+=2) + { + bDot1 = (*pwRdSrc) >> 12 ; + pwRdSrc ++ ; + bDot2 = (*pwRdSrc) >> 12 ; + pwRdSrc ++ ; + + if(iIsPixelSwap) { + *pbWrBuf = bDot2&(bDot1<<4) ; + } + else { + *pbWrBuf = (bDot2<<4)&bDot1 ; + } + pbWrBuf++; + if(++dwWr >= I_dwGray4BufSize) { + ERR_MSG("%s(%d):write buffer too small \n",__FUNCTION__,__LINE__); + break; + } + } +} + + +static void _fb_gray_4to8( + unsigned char *IO_pb8BitsBuf,unsigned long I_dw8BitsBufSize, + unsigned char *I_pb4BitsSrc,unsigned long I_dw4BitsBufSize,int iIsPixelSwap) +{ + unsigned long dwWr,dwRd; + + for(dwWr=0,dwRd=0;dwRd<I_dw4BitsBufSize;dwRd++) + { + if(iIsPixelSwap) { + if(dwWr>=I_dw8BitsBufSize) { + ERR_MSG("%s(%d):write buffer too small \n",__FUNCTION__,__LINE__); + break; + } + IO_pb8BitsBuf[dwWr++] = (I_pb4BitsSrc[dwRd]<<4)&0xf0; + + if(dwWr>=I_dw8BitsBufSize) { + ERR_MSG("%s(%d):write buffer too small \n",__FUNCTION__,__LINE__); + break; + } + IO_pb8BitsBuf[dwWr++] = I_pb4BitsSrc[dwRd]&0xf0; + } + else { + if(dwWr>=I_dw8BitsBufSize) { + ERR_MSG("%s(%d):write buffer too small \n",__FUNCTION__,__LINE__); + break; + } + IO_pb8BitsBuf[dwWr++] = I_pb4BitsSrc[dwRd]&0xf0; + + if(dwWr>=I_dw8BitsBufSize) { + ERR_MSG("%s(%d):write buffer too small \n",__FUNCTION__,__LINE__); + break; + } + IO_pb8BitsBuf[dwWr++] = (I_pb4BitsSrc[dwRd]<<4)&0xf0; + } + } +} + +EPDFB_DC *epdfbdc_create_ex2(unsigned long dwFBW,unsigned long dwFBH,\ + unsigned long dwW,unsigned long dwH,\ + unsigned char bPixelBits,unsigned char *pbDCbuf,unsigned long dwCreateFlag) +{ + EPDFB_DC *pDC = 0; + unsigned long dwFBBits = (dwFBW*dwFBH*bPixelBits); + unsigned long dwFBWBits = (dwFBW*bPixelBits); + unsigned long dwDCSize ,dwDCWidthBytes; + + dwDCSize=dwFBBits>>3; + dwDCWidthBytes=dwFBWBits>>3; + + if(dwFBWBits&0x7) { + dwDCWidthBytes+=1; + dwDCSize+=dwFBH; + } + + DBG_MSG("%s(%d):w=%d,h=%d,bits=%d,bufp=%p\n",__FUNCTION__,__LINE__,\ + dwW,dwH,bPixelBits,pbDCbuf); + + pDC = my_malloc(sizeof(EPDFB_DC)); + if(0==pDC) { + return 0; + } + + + if(pbDCbuf) { + pDC->dwBufSize = 0; + pDC->pbDCbuf = pbDCbuf; + + DBG_MSG("%s(%d):pDC malloc=%p\n",__FUNCTION__,__LINE__,pDC); + } + else { + pbDCbuf = my_malloc(dwDCSize); + if(0==pbDCbuf) { + my_free(pDC); + return 0; + } + DBG_MSG("%s(%d):pDC malloc=%p,pbDCbuf=%p,sz=%u\n", + __FUNCTION__,__LINE__,pDC,pbDCbuf,dwDCSize); + memset(pbDCbuf,0,dwDCSize); + pDC->pbDCbuf = pbDCbuf; + pDC->dwBufSize = dwDCSize; + } + ASSERT(dwFBW>=dwW); + pDC->dwFBWExtra = dwFBW-dwW ; + ASSERT(dwFBH>=dwH); + pDC->dwFBHExtra = dwFBH-dwH ; + pDC->dwWidth = dwW; + pDC->dwHeight = dwH; + pDC->dwFBXOffset = 0; + pDC->dwFBYOffset = 0; + + { + int maxval = 255; + int row,col; + + switch(bPixelBits) { + case 1: + maxval = 1; + break; + case 2: + maxval = 3; + break; + case 4: + maxval = 15; + break; + case 8: + maxval = 255; + break; + case 16: + maxval = 31; + break; + case 32: + maxval = 255; + break; + } + //printk("D8 Array[][] = {"); + for ( row = 0; row < 16; ++row ) { + //printk("{"); + for ( col = 0; col < 16; ++col ) { + pDC->dither8[row][col] = giDither8[row][col] * ( maxval + 1 ) / 256; + //printk("%d,",pDC->dither8[row][col]); + } + //printk("}\n"); + } + //printk("\n"); + } + + + pDC->bPixelBits = bPixelBits; + pDC->dwDCSize = dwDCSize; + + pDC->dwDCWidthBytes = dwDCWidthBytes; + + pDC->dwDirtyOffsetStart = 0; + pDC->dwDirtyOffsetEnd = dwDCSize; + + pDC->dwMagicPrivateBegin = EPDFB_MAGIC; + pDC->dwMagicPrivateEnd = EPDFB_MAGIC; + pDC->dwFlags = dwCreateFlag; + + pDC->pfnGetWaveformBpp = 0; + pDC->pfnVcomEnable = 0; + pDC->pfnSetPartialUpdate = 0; + pDC->pfnGetRealFrameBuf = 0; + pDC->pfnGetRealFrameBufEx = 0; + pDC->pfnDispStart = 0; + pDC->pfnGetWaveformMode = 0; + pDC->pfnSetWaveformMode = 0; + pDC->pfnIsUpdating = 0; + pDC->pfnWaitUpdateComplete = 0; + pDC->pfnSetUpdateRect = 0; + pDC->pfnPwrAutoOffIntervalMax = 0; + pDC->pfnAutoOffEnable = 0; + pDC->pfnPutImg = 0; + pDC->pfnSetVCOM = 0; + pDC->pfnSetVCOMToFlash = 0; + pDC->pfnGetVCOM = 0; + + pDC->iIsForceWaitUpdateFinished = 1; + + pDC->pfnPwrOnOff = 0; + + pDC->iWFMode = -1; + pDC->iLastWFMode = -1; + + return pDC; +} + +EPDFB_DC *epdfbdc_create_ex(unsigned long dwW,unsigned long dwH,\ + unsigned char bPixelBits,unsigned char *pbDCbuf,unsigned long dwCreateFlag) +{ + return epdfbdc_create_ex2(dwW,dwH,dwW,dwH,bPixelBits,pbDCbuf,dwCreateFlag); +} + + + +EPDFB_DC *epdfbdc_create(unsigned long dwW,unsigned long dwH,\ + unsigned char bPixelBits,unsigned char *pbDCbuf) +{ + return epdfbdc_create_ex(dwW,dwH,bPixelBits,pbDCbuf,EPDFB_DC_FLAG_DEFAUT); +} + + +EPDFB_DC_RET epdfbdc_delete(EPDFB_DC *pEPD_dc) +{ + DBG_MSG("%s\n",__FUNCTION__); + if(!CHK_EPDFB_DC(pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(pEPD_dc->dwBufSize>0) { + my_free((void *)pEPD_dc->pbDCbuf); + pEPD_dc->pbDCbuf = 0; + } + my_free(pEPD_dc); + return EPDFB_DC_SUCCESS; +} + +/* + * + * + * + */ +EPDFB_DC_RET epdfbdc_fbimg_normallize(EPDFB_DC *I_pEPD_dc,\ + EPDFB_IMG *IO_pEPD_img) +{ + EPDFB_DC_RET tRet=EPDFB_DC_PIXELBITSNOTSUPPORT; + unsigned char *pbBuf = 0; + unsigned long dwBufSize ; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + return EPDFB_DC_OBJECTERR; + } + + if(IO_pEPD_img->bPixelBits!=I_pEPD_dc->bPixelBits) { + switch(IO_pEPD_img->bPixelBits) { // source image + case 16: + if(4==I_pEPD_dc->bPixelBits) { + pbBuf = IO_pEPD_img->pbImgBuf; + dwBufSize = IO_pEPD_img->dwW*IO_pEPD_img->dwH*IO_pEPD_img->bPixelBits/8; + _fb_RGB565toGray4(IO_pEPD_img->pbImgBuf,dwBufSize, + pbBuf,dwBufSize,0); + tRet=EPDFB_DC_SUCCESS; + } + else if(8==I_pEPD_dc->bPixelBits) { + } + else if (16==I_pEPD_dc->bPixelBits){ + tRet=EPDFB_DC_SUCCESS; + } + break; + case 8: + if(4==I_pEPD_dc->bPixelBits) { + _fb_gray_8to4(IO_pEPD_img->pbImgBuf,\ + IO_pEPD_img->dwW*IO_pEPD_img->dwH ); + IO_pEPD_img->bPixelBits = 4; + tRet=EPDFB_DC_SUCCESS; + } + else if(16==I_pEPD_dc->bPixelBits) { + dwBufSize = IO_pEPD_img->dwW*IO_pEPD_img->dwH*I_pEPD_dc->bPixelBits/8; + pbBuf = my_malloc(dwBufSize); + if(!pbBuf) { + return EPDFB_DC_MEMMALLOCFAIL; + } + memcpy(pbBuf,IO_pEPD_img->pbImgBuf,IO_pEPD_img->dwW*IO_pEPD_img->dwH); + _fb_Gray8toRGB565(IO_pEPD_img->pbImgBuf,\ + IO_pEPD_img->dwW*IO_pEPD_img->dwH,pbBuf,dwBufSize); + my_free(pbBuf);pbBuf=0; + + } + else if(8==I_pEPD_dc->bPixelBits) { + tRet=EPDFB_DC_SUCCESS; + } + break; + case 4: + dwBufSize = IO_pEPD_img->dwW*IO_pEPD_img->dwH*I_pEPD_dc->bPixelBits/8; + pbBuf = my_malloc(dwBufSize); + if(!pbBuf) { + return EPDFB_DC_MEMMALLOCFAIL; + } + if(8==I_pEPD_dc->bPixelBits) { + memcpy(pbBuf,IO_pEPD_img->pbImgBuf, \ + IO_pEPD_img->dwW*IO_pEPD_img->dwH*IO_pEPD_img->bPixelBits/8); + _fb_gray_4to8(IO_pEPD_img->pbImgBuf,IO_pEPD_img->dwW*IO_pEPD_img->dwH, + pbBuf,dwBufSize,0); + tRet=EPDFB_DC_SUCCESS; + } + else if(16==I_pEPD_dc->bPixelBits) { + memcpy(pbBuf,IO_pEPD_img->pbImgBuf, \ + IO_pEPD_img->dwW*IO_pEPD_img->dwH*IO_pEPD_img->bPixelBits/8); + _fb_Gray4toRGB565(IO_pEPD_img->pbImgBuf, + IO_pEPD_img->dwW*IO_pEPD_img->dwH*IO_pEPD_img->bPixelBits/8, + pbBuf,dwBufSize,0); + tRet=EPDFB_DC_SUCCESS; + } + else if(4==I_pEPD_dc->bPixelBits) { + tRet=EPDFB_DC_SUCCESS; + } + my_free(pbBuf);pbBuf=0; + break; + default : + //tRet = EPDFB_DC_PIXELBITSNOTSUPPORT; + break; + } + } + return tRet; +} + +unsigned long _epdfbdc_pixel_dither(EPDFB_DC *pEPD_dc,unsigned char bSrcBitsPerPixel, + unsigned long dwSrcPxlVal,unsigned long I_dwSrcPxlX,unsigned long I_dwSrcPxlY) +{ + unsigned long dwPValDitherRet=dwSrcPxlVal; + + if(pEPD_dc->dwFlags & EPDFB_DC_FLAG_DITHER8) { + unsigned long dwPVal; + switch (bSrcBitsPerPixel) { + case 2: + dwPVal = dwSrcPxlVal & 0x3; + break; + case 4: + dwPVal = dwSrcPxlVal & 0xf; + break; + case 8: + dwPVal = dwSrcPxlVal & 0xff; + break; + case 16: + dwPVal = dwSrcPxlVal & 0x1f; + break; + case 24: + dwPVal = dwSrcPxlVal & 0xff; + break; + case 32: + dwPVal = dwSrcPxlVal & 0xff; + break; + } + if((int)(dwPVal)>=pEPD_dc->dither8[I_dwSrcPxlY%16][I_dwSrcPxlX%16]) { + dwPValDitherRet = 0xffffffff; + } + else { + dwPValDitherRet = 0; + } + } + return dwPValDitherRet; +} + +static inline unsigned long _pixel_value_convert(unsigned long dwPixelVal, + unsigned char bSrcBitsPerPixel,unsigned char bDestBitsPerPixel) +{ + unsigned long dwRetPixelVal; + + switch(bSrcBitsPerPixel) + { + case 1: + dwRetPixelVal = dwPixelVal?0x0:0xffffffff; + break; + + case 2: + if(4==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal<<2; + } + else if(8==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal<<6; + } + else if(16==bDestBitsPerPixel) { + dwRetPixelVal = (unsigned long)(gwGray2toRGB565_TableA[(dwPixelVal&0x3)]); + //printk("%x->%x.",dwPixelVal,dwRetPixelVal); + } + else if(32==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal<<6|dwPixelVal<<14|dwPixelVal<<22|dwPixelVal<<30; + //printk("%x->%x.",dwPixelVal,dwRetPixelVal); + } + else if(24==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal<<6|dwPixelVal<<14|dwPixelVal<<22; + //printk("%x->%x.",dwPixelVal,dwRetPixelVal); + } + else if(1==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>1; + } + else if(2==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal; + } + break; + + case 4: + if(4==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal; + } + else if(8==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal<<4; + //dwRetPixelVal = (unsigned long)gbGray4toGray8_TableA[(dwPixelVal&0xf)]; + } + else if(16==bDestBitsPerPixel) { + //dwRetPixelVal = dwPixelVal<<12; + dwRetPixelVal = (unsigned long)(gwGray4toRGB565_TableA[(dwPixelVal&0xf)]); + } + else if(32==bDestBitsPerPixel) { + //dwRetPixelVal = dwPixelVal<<8; + dwRetPixelVal = dwPixelVal<<4|dwPixelVal<<12|dwPixelVal<<20|dwPixelVal<<28; + } + else if(24==bDestBitsPerPixel) { + //dwRetPixelVal = dwPixelVal<<8; + dwRetPixelVal = dwPixelVal<<4|dwPixelVal<<12|dwPixelVal<<20; + } + else if(1==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>3; + } + else if(2==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>2; + } + break; + + case 8: + if(4==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>4; + } + else if(8==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal; + } + else if(16==bDestBitsPerPixel) { + //dwRetPixelVal = dwPixelVal<<8; + dwRetPixelVal = (unsigned long)(gwGray4toRGB565_TableA[(dwPixelVal>>4)]); + } + else if(32==bDestBitsPerPixel) { + //dwRetPixelVal = dwPixelVal<<8; + dwRetPixelVal = dwPixelVal|dwPixelVal<<8|dwPixelVal<<16|dwPixelVal<<24; + } + else if(24==bDestBitsPerPixel) { + //dwRetPixelVal = dwPixelVal<<8; + dwRetPixelVal = dwPixelVal|dwPixelVal<<8|dwPixelVal<<16; + } + else if(1==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>7; + } + else if(2==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>6; + } + break; + + case 16: + if(4==bDestBitsPerPixel) { + dwRetPixelVal = (unsigned long)_RGB565_to_Gray4(dwPixelVal); + } + else if(8==bDestBitsPerPixel) { + dwRetPixelVal = (unsigned long)_RGB565_to_Gray8(dwPixelVal); + } + else if(16==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal; + } + else if(32==bDestBitsPerPixel) { + dwRetPixelVal =_RGB565_to_RGB8888(dwPixelVal); + } + else if(24==bDestBitsPerPixel) { + dwRetPixelVal = _RGB565_to_RGB888(dwPixelVal); + } + else if(1==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>31; + } + else if(2==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>30; + } + break; + + case 24: + if(4==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>20; + } + else if(8==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>18; + } + else if(16==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>8; + } + else if(32==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal; + } + else if(24==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal; + } + else if(1==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>23; + } + else if(2==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>22; + } + break; + + case 32: + if(4==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>28; + } + else if(8==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>24; + } + else if(16==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>16; + } + else if(32==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal; + } + else if(1==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>31; + } + else if(2==bDestBitsPerPixel) { + dwRetPixelVal = dwPixelVal>>30; + } + break; + + default: + WARNING_MSG("convert %d -> %d not supported \n",bSrcBitsPerPixel,bDestBitsPerPixel); + dwRetPixelVal = dwPixelVal; + break; + } + //printf("0x%x->0x%x.",dwPixelVal,dwRetPixelVal); + + return dwRetPixelVal; +} + + + +static inline unsigned char *_epdfbdc_get_dcimg_ptr( + EPDFB_DC *I_pEPD_imgdc, unsigned long I_dwImgX,unsigned long I_dwImgY) +{ + unsigned char *pbRet ; + unsigned char bBitsPerPixel = I_pEPD_imgdc->bPixelBits; + + if(4==bBitsPerPixel) { + pbRet = I_pEPD_imgdc->pbDCbuf + + (I_dwImgX>>1)+(I_dwImgY*I_pEPD_imgdc->dwDCWidthBytes); + } + else if(8==bBitsPerPixel) { + pbRet = (unsigned char *)(I_pEPD_imgdc->pbDCbuf+(I_dwImgX)+\ + (I_dwImgY*I_pEPD_imgdc->dwDCWidthBytes)); + } + else if(16==bBitsPerPixel) { + pbRet = (unsigned char *)(I_pEPD_imgdc->pbDCbuf+(I_dwImgX>>1)+\ + (I_dwImgY*I_pEPD_imgdc->dwDCWidthBytes)); + } + else if(24==bBitsPerPixel) { + pbRet = (unsigned char *)(I_pEPD_imgdc->pbDCbuf+(I_dwImgX/3)+\ + (I_dwImgY*I_pEPD_imgdc->dwDCWidthBytes)); + } + else if(32==bBitsPerPixel) { + pbRet = (unsigned char *)(I_pEPD_imgdc->pbDCbuf+(I_dwImgX>>2)+\ + (I_dwImgY*I_pEPD_imgdc->dwDCWidthBytes)); + } + else if(1==bBitsPerPixel) { + pbRet =(unsigned char *)(I_pEPD_imgdc->pbDCbuf+(I_dwImgX>>3)+\ + (I_dwImgY*I_pEPD_imgdc->dwDCWidthBytes)); + } + else if(2==bBitsPerPixel) { + pbRet =(unsigned char *)(I_pEPD_imgdc->pbDCbuf+(I_dwImgX>>2)+\ + (I_dwImgY*I_pEPD_imgdc->dwDCWidthBytes)); + } + else { + pbRet = 0; + } + return pbRet; +} + +static inline unsigned long _epdfbdc_get_img_pixelvalue_from_ptr(EPDFB_DC *I_pEPD_imgdc, + unsigned char **IO_ppbImg,unsigned long I_dwX) +{ + unsigned long dwRet; + unsigned char bBitsPerPixel=I_pEPD_imgdc->bPixelBits; + + ASSERT(IO_ppbImg); + ASSERT(*IO_ppbImg); + ASSERT(*IO_ppbImg>=I_pEPD_imgdc->pbDCbuf); + + if((*IO_ppbImg)>I_pEPD_imgdc->pbDCbuf+I_pEPD_imgdc->dwDCSize) { + ERR_MSG("[Warning]*IO_ppbImg=%p>pbDCbuf=%p+dwDCSize=%u\n",(*IO_ppbImg), + I_pEPD_imgdc->pbDCbuf,I_pEPD_imgdc->dwDCSize); + return 0; + ASSERT(0); + } + + switch(bBitsPerPixel) { + case 4: + { + unsigned char b; + if(I_dwX&1) { + if(I_pEPD_imgdc->dwFlags&EPDFB_DC_FLAG_REVERSEINPDATA) { + b = (**IO_ppbImg)&0xf; + } + else { + b = ((**IO_ppbImg)>>4)&0xf; + } + (*IO_ppbImg) += 1; + } + else { + if(I_pEPD_imgdc->dwFlags&EPDFB_DC_FLAG_REVERSEINPDATA) { + b = ((**IO_ppbImg)>>4)&0xf; + } + else { + b = (**IO_ppbImg)&0xf; + } + } + dwRet = (unsigned long)b; + + break; + } + case 1: + { + unsigned char b=(I_dwX&0x7); + unsigned char bBitMask=0x80>>b; + + dwRet = (unsigned long)((**IO_ppbImg)&bBitMask); + if(7==b) { + (*IO_ppbImg) += 1; + } + break; + } + case 2: + { + unsigned char b=(I_dwX&0x3); + unsigned char bShift=6-(b<<1); + + /* + if(I_pEPD_imgdc->dwFlags&EPDFB_DC_FLAG_REVERSEINPDATA) { + + } + else { + switch(bShift) { + case 0: + bShift = 6; + break; + case 2: + bShift = 2; + break; + case 4: + bShift = 4; + break; + case 6: + bShift = 0; + break; + default : + break; + } + } + */ + + dwRet = (unsigned long)(((**IO_ppbImg)>>bShift)&0x03); + + if(3==b) { + (*IO_ppbImg) += 1; + } + break; + } + case 8: + { + dwRet = (unsigned long)(**((unsigned char **)IO_ppbImg)); + (*IO_ppbImg) += 1; + break; + } + case 16: + { + dwRet = (unsigned long)(**((unsigned short **)IO_ppbImg)); + (*IO_ppbImg) += 2; + break; + } + case 24: + { + dwRet = (unsigned long)(**((unsigned long **)IO_ppbImg)); + (*IO_ppbImg) += 3; + break; + } + case 32: + { + dwRet = (unsigned long)(**((unsigned short **)IO_ppbImg)); + (*IO_ppbImg) += 4; + break; + } + default: + ERR_MSG("%d Bits/Pixel not supported !!\n",bBitsPerPixel); + dwRet=0; + break; + } + return dwRet; +} +#if 0 +static inline void _epdfbdc_set_dcpixel_at_ptr(EPDFB_DC *I_pEPD_dc, + unsigned char **IO_ppbDCDest,unsigned long I_dwSrcPixelVal,unsigned char I_bSrcBitsPerPixel, + unsigned short I_dwDestX) +{ + unsigned long L_dwPixelVal; + unsigned char bDestBitsPerPixel; + + ASSERT(IO_ppbDCDest); + ASSERT(*IO_ppbDCDest); + + ASSERT(*IO_ppbDCDest>=I_pEPD_dc->pbDCbuf); + ASSERT(*IO_ppbDCDest<I_pEPD_dc->pbDCbuf+I_pEPD_dc->dwDCSize); + + bDestBitsPerPixel = I_pEPD_dc->bPixelBits; + L_dwPixelVal = _pixel_value_convert(I_dwSrcPixelVal,I_bSrcBitsPerPixel,bDestBitsPerPixel); + + switch(bDestBitsPerPixel) { + case 4: + { + unsigned char bTemp,bPixelVal; + + bPixelVal=(unsigned char)(L_dwPixelVal&0xf); + bTemp = **IO_ppbDCDest; + + if(I_pEPD_dc->dwFlags&EPDFB_DC_FLAG_REVERSEDRVDATA) { + if(I_dwDestX&0x1) { + bTemp &= 0x0f; + bTemp |= (bPixelVal<<4)&0xf0 ; + **IO_ppbDCDest = bTemp; + (*IO_ppbDCDest) += 1; + } + else { + bTemp &= 0xf0; + bTemp |= bPixelVal; + **IO_ppbDCDest = bTemp; + } + } + else { + if(I_dwDestX&0x1) { + bTemp &= 0xf0; + bTemp |= bPixelVal; + **IO_ppbDCDest = bTemp; + (*IO_ppbDCDest) += 1; + } + else { + bTemp &= 0x0f; + bTemp |= (bPixelVal<<4)&0xf0 ; + **IO_ppbDCDest = bTemp; + } + } + + + break; + } + case 1: + { + unsigned char bTemp; + unsigned char b=(I_dwDestX&0x7); + + bTemp = **IO_ppbDCDest; + if(L_dwPixelVal) { + bTemp |= 0x01<<b; + } + else { + bTemp &= ~(0x01<<b); + } + **IO_ppbDCDest = bTemp; + if(7==b) { + (*IO_ppbDCDest) += 1; + } + break; + } + case 2: + { + unsigned char bTemp; + unsigned char b=(I_dwDestX&0x3); + + bTemp = **IO_ppbDCDest; + bTemp &= ~(0x3<<(b<<1)); + bTemp |= L_dwPixelVal<<(b<<1); + **IO_ppbDCDest = bTemp; + if(3==b) { + (*IO_ppbDCDest) += 1; + } + break; + } + case 8: + { + **((unsigned char **)IO_ppbDCDest) = (unsigned char)(L_dwPixelVal&0x000000ff); + (*IO_ppbDCDest) += 1; + break; + } + case 16: + { + **((unsigned short **)IO_ppbDCDest) = (unsigned short)(L_dwPixelVal&0x0000ffff); + (*IO_ppbDCDest) += 2; + break; + } + case 24: + { + **((unsigned long **)IO_ppbDCDest) = (unsigned long)(L_dwPixelVal&0x00ffffff); + (*IO_ppbDCDest) += 3; + break; + } + case 32: + { + **((unsigned long **)IO_ppbDCDest) = (unsigned long)(L_dwPixelVal&0xffffffff); + (*IO_ppbDCDest) += 4; + break; + } + + default: + break; + + } +} +#endif + + +static inline unsigned long _epdfbdc_get_img_pixelvalue(EPDFB_DC *I_pEPD_dc, + EPDFB_IMG *I_pEPD_img,unsigned long dwX,unsigned long dwY,unsigned long dwImgWidthBytes) +{ + unsigned long dwRet = 0; + unsigned char bBitsPerPixel = I_pEPD_img->bPixelBits; + //unsigned long dwImgWidthBytes ; + + if(4==bBitsPerPixel) { + unsigned char *pb; + unsigned char b; + + pb = I_pEPD_img->pbImgBuf+ + (dwX>>1)+(dwY*dwImgWidthBytes); + + if(dwX&1) { + if(I_pEPD_dc->dwFlags&EPDFB_DC_FLAG_REVERSEINPDATA) { + b = (*pb)&0xf; + } + else { + b = ((*pb)>>4)&0xf; + } + } + else { + if(I_pEPD_dc->dwFlags&EPDFB_DC_FLAG_REVERSEINPDATA) { + b = ((*pb)>>4)&0xf; + } + else { + b = (*pb)&0xf; + } + } + dwRet = (unsigned long)b; + } + else if(2==bBitsPerPixel) { + unsigned char *pb = + ((unsigned char *)(I_pEPD_img->pbImgBuf+(dwX>>2)+(dwY*dwImgWidthBytes))); + unsigned char bShiftBits = (dwX%4)<<1; + dwRet = (unsigned long)((*pb)>>bShiftBits); + dwRet &= 0x3; + } + else if(1==bBitsPerPixel) { + unsigned char *pb = + ((unsigned char *)(I_pEPD_img->pbImgBuf+(dwX>>3)+(dwY*dwImgWidthBytes))); + unsigned char bBitMask=0x80>>(dwX&0x7); + dwRet = (unsigned long)((*pb)&bBitMask); + } + else if(8==bBitsPerPixel) { + dwRet = (unsigned long) + *((unsigned char *)(I_pEPD_img->pbImgBuf+(dwX)+(dwY*dwImgWidthBytes))); + } + else if(16==bBitsPerPixel) { + dwRet = (unsigned long) + *((unsigned short *)(I_pEPD_img->pbImgBuf+(dwX<<1)+(dwY*dwImgWidthBytes))); + } + else if(24==bBitsPerPixel) { + dwRet = (unsigned long) + *((unsigned long *)(I_pEPD_img->pbImgBuf+(dwX*3)+(dwY*dwImgWidthBytes))); + } + else if(32==bBitsPerPixel) { + dwRet = (unsigned long) + *((unsigned long *)(I_pEPD_img->pbImgBuf+(dwX<<2)+(dwY*dwImgWidthBytes))); + } + else { + ERR_MSG("%s : pixel bits %d not supported !\n",__FUNCTION__,bBitsPerPixel); + } + + return dwRet; +} + +static inline void _epdfbdc_set_pixel(EPDFB_DC *pEPD_dc, + unsigned long dwX,unsigned long dwY,unsigned long dwPixelVal,unsigned char bSrcBitsPerPixel) +{ + unsigned long L_dwPixelVal; + unsigned char bDestBitsPerPixel; + + L_dwPixelVal = _pixel_value_convert(dwPixelVal,bSrcBitsPerPixel,pEPD_dc->bPixelBits); + bDestBitsPerPixel = pEPD_dc->bPixelBits; + + + //DBG_MSG("(%d,%d)=0x%x",dwX,dwY,L_dwPixelVal); + + if( 4 == bDestBitsPerPixel ) { + volatile unsigned char *pbDCDest; + unsigned char bTemp,bPixelVal; + + + //DBG_MSG("%s(),x=0x%x,y=0x%x,pixel=0x%x\n",__FUNCTION__,dwX,dwY,dwPixelVal); + + pbDCDest = pEPD_dc->pbDCbuf + \ + (dwX>>1)+(dwY*pEPD_dc->dwDCWidthBytes); + + bPixelVal=(unsigned char)(L_dwPixelVal&0xf); + bTemp = *pbDCDest; + + if(pEPD_dc->dwFlags&EPDFB_DC_FLAG_REVERSEDRVDATA) { + if(dwX&0x1) { + bTemp &= 0x0f; + bTemp |= (bPixelVal<<4)&0xf0 ; + } + else { + bTemp &= 0xf0; + bTemp |= bPixelVal; + } + } + else { + if(dwX&0x1) { + bTemp &= 0xf0; + bTemp |= bPixelVal; + } + else { + bTemp &= 0x0f; + bTemp |= (bPixelVal<<4)&0xf0 ; + } + } + + *pbDCDest = bTemp; + } + else if( 16 == bDestBitsPerPixel ) { + volatile unsigned short *pwDCDest; + + pwDCDest = (unsigned short *)(pEPD_dc->pbDCbuf + \ + (dwX<<1)+(dwY*pEPD_dc->dwDCWidthBytes)); + *pwDCDest = (unsigned short)(L_dwPixelVal&0x0000ffff); + } + else if( 24 == bDestBitsPerPixel ) { + volatile unsigned long *pdwDCDest; + + pdwDCDest = (unsigned long *)(pEPD_dc->pbDCbuf + \ + (dwX/3)+(dwY*pEPD_dc->dwDCWidthBytes)); + *pdwDCDest = (unsigned long)(L_dwPixelVal&0x00ffffff); + } + else if( 32 == bDestBitsPerPixel ) { + volatile unsigned long *pdwDCDest; + + pdwDCDest = (unsigned long *)(pEPD_dc->pbDCbuf + \ + (dwX<<2)+(dwY*pEPD_dc->dwDCWidthBytes)); + *pdwDCDest = (unsigned long)(L_dwPixelVal&0xffffffff); + } + else if( 8 == bDestBitsPerPixel ) { + volatile unsigned char *pbDCDest; + + pbDCDest = (unsigned short *)(pEPD_dc->pbDCbuf + \ + (dwX)+(dwY*pEPD_dc->dwDCWidthBytes)); + *pbDCDest = (unsigned char)(L_dwPixelVal&0x000000ff); + } + else { + } +} + + +// +#define GETIMG_PIXEL_METHOD 1 + +EPDFB_DC_RET epdfbdc_put_dcimg(EPDFB_DC *pEPD_dc, + EPDFB_DC *pEPD_dcimg,EPDFB_ROTATE_T tRotateDegree, + unsigned long I_dwDCimgX,unsigned long I_dwDCimgY, + unsigned long I_dwDCimgW,unsigned long I_dwDCimgH, + unsigned long I_dwDCPutX,unsigned long I_dwDCPutY) +{ + EPDFB_DC_RET tRet=EPDFB_DC_SUCCESS; + + unsigned long dwDCH,dwDCW; + + //unsigned long dwImgW,dwImgH; + //unsigned long dwImgX,dwImgY; + + unsigned char bImgPixelBits; + unsigned long dwDCWidthBytes; + unsigned long dwX,dwY,dwEPD_dc_flags; + unsigned long dwSrcX,dwSrcY; + + unsigned long h,w;// image offset of x,y . + + unsigned long dwWHidden,dwHHidden; + + unsigned char *pbDCRow; + unsigned char *pbImgRow; + + //int tick=jiffies; + unsigned long dwImgWidthBytes; + + + #if (GETIMG_PIXEL_METHOD==1) + EPDFB_IMG tEPD_img,*pEPD_img=&tEPD_img; + pEPD_img->dwX = I_dwDCimgX; + pEPD_img->dwY = I_dwDCimgY; + pEPD_img->dwW = pEPD_dcimg->dwWidth+pEPD_dcimg->dwFBWExtra; + pEPD_img->dwH = pEPD_dcimg->dwHeight+pEPD_dcimg->dwFBHExtra; + pEPD_img->bPixelBits = pEPD_dcimg->bPixelBits; + pEPD_img->pbImgBuf = pEPD_dcimg->pbDCbuf; + #endif //] (GETIMG_PIXEL_METHOD==1) + + + dwImgWidthBytes = pEPD_dcimg->dwDCWidthBytes;//pEPD_dc->bPixelBits/8 + GALLEN_DBGLOCAL_BEGIN(); + + if(!CHK_EPDFB_DC(pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + GALLEN_DBGLOCAL_RUNLOG(0); + GALLEN_DBGLOCAL_ESC(); + return EPDFB_DC_OBJECTERR; + } + + I_dwDCPutX += pEPD_dc->dwFBXOffset; + I_dwDCPutY += pEPD_dc->dwFBYOffset; + + + dwWHidden = pEPD_dc->dwFBWExtra; + dwHHidden = pEPD_dc->dwFBHExtra; + dwDCH=pEPD_dc->dwHeight + dwHHidden; + dwDCW=pEPD_dc->dwWidth + dwWHidden; + dwEPD_dc_flags = pEPD_dc->dwFlags; + + bImgPixelBits=pEPD_dcimg->bPixelBits; + + //if(pEPD_dc->bPixelBits!=bImgPixelBits) { + // GALLEN_DBGLOCAL_ESC(); + // return EPDFB_DC_PIXELBITSNOTSUPPORT; + //} + + //ASSERT(4==pEPD_dc->bPixelBits); + //ASSERT(4==bImgPixelBits); + //ASSERT(0==(I_dwDCimgW&0x1)); // image width must be even . + //ASSERT(0==(I_dwDCimgH&0x1)); // image heigh must be even . + + + dwDCWidthBytes = pEPD_dc->dwDCWidthBytes; + + + DBG_MSG("dc_flag=0x%08x,imgdc.w=%u,imgdc.h=%u,img.w=%u,img.h=%u,img pixbits=%d,dc pixbits=%d\n", + dwEPD_dc_flags,pEPD_dcimg->dwWidth,pEPD_dcimg->dwHeight,I_dwDCimgW,I_dwDCimgH,pEPD_dcimg->bPixelBits,pEPD_dc->bPixelBits); + DBG_MSG("IMG_Wbytes=%u,DC_Wbytes=%u\n",dwImgWidthBytes,dwDCWidthBytes); + + switch(tRotateDegree) { + case EPDFB_R_0:GALLEN_DBGLOCAL_RUNLOG(2); + // left->right,up->down . + + { + unsigned long dwPixelVal; + + pEPD_dc->dwDirtyOffsetStart = I_dwDCimgY*dwDCWidthBytes; + pEPD_dc->dwDirtyOffsetEnd = (I_dwDCimgY+I_dwDCimgH)*dwDCWidthBytes; + + //dwImgPixelIdx = 0; + for(h=0;h<I_dwDCimgH;h+=1) { + + dwSrcY = I_dwDCimgY+h; + dwY = I_dwDCPutY+h; + if(dwY>=dwDCH) { + continue ; + } + //GALLEN_DBGLOCAL_PRINTMSG("y=%d,x=",h); + + pbImgRow = _epdfbdc_get_dcimg_ptr(pEPD_dcimg,I_dwDCimgX,dwSrcY); + + for(w=0;w<I_dwDCimgW;w+=1) { + + dwSrcX = I_dwDCimgX+w; + dwX = I_dwDCPutX+w; + if(dwX>=dwDCW) { + continue ; + } + + //GALLEN_DBGLOCAL_PRINTMSG("%d,",w,h); + #if (GETIMG_PIXEL_METHOD==1)//[ + dwPixelVal = _epdfbdc_get_img_pixelvalue(pEPD_dc,pEPD_img,dwSrcX,dwSrcY,dwImgWidthBytes); + #else //][!(GETIMG_PIXEL_METHOD==1) + dwPixelVal = _epdfbdc_get_img_pixelvalue_from_ptr(pEPD_dcimg,&pbImgRow,dwSrcX); + #endif //] + + dwPixelVal = _epdfbdc_pixel_dither(pEPD_dc,bImgPixelBits,dwPixelVal,dwSrcX,dwSrcY); + + if(dwEPD_dc_flags&EPDFB_DC_FLAG_SKIPLEFTPIXEL && 0==w) { + // skip left pixel output. + } + else if(dwEPD_dc_flags&EPDFB_DC_FLAG_SKIPRIGHTPIXEL && I_dwDCimgW==w+1) { + // skip right pixel output. + } + else { + _epdfbdc_set_pixel(pEPD_dc,dwX,dwY,dwPixelVal,bImgPixelBits); + } + } + //GALLEN_DBGLOCAL_PRINTMSG("\n,",w,h); + } + } + break; + case EPDFB_R_90:GALLEN_DBGLOCAL_RUNLOG(3); + // up->down,right->left . + { + unsigned long dwPixelVal; + + pEPD_dc->dwDirtyOffsetStart = I_dwDCimgX*dwDCWidthBytes; + pEPD_dc->dwDirtyOffsetEnd = (I_dwDCimgX+I_dwDCimgW)*dwDCWidthBytes; + + + //dwImgPixelIdx = 0; + + for(h=0;h<I_dwDCimgH;h+=1) { + dwSrcY = I_dwDCimgY+h; + dwX = dwDCW-h-1-I_dwDCPutY-dwWHidden; + if(dwX>=dwDCW) { + continue ; + } + + pbImgRow = _epdfbdc_get_dcimg_ptr(pEPD_dcimg,I_dwDCimgX,dwSrcY); + + for(w=0;w<I_dwDCimgW;w+=1) { + + dwSrcX = I_dwDCimgX+w; + dwY = I_dwDCPutX+w; + if(dwY>=dwDCH) { + continue ; + } + + #if (GETIMG_PIXEL_METHOD==1) //[ + dwPixelVal = _epdfbdc_get_img_pixelvalue(pEPD_dc,pEPD_img,dwSrcX,dwSrcY,dwImgWidthBytes); + #else //][ + dwPixelVal = _epdfbdc_get_img_pixelvalue_from_ptr(pEPD_dcimg,&pbImgRow,dwSrcX); + #endif //] + + dwPixelVal = _epdfbdc_pixel_dither(pEPD_dc,bImgPixelBits,dwPixelVal,dwSrcX,dwSrcY); + + if(dwEPD_dc_flags&EPDFB_DC_FLAG_SKIPLEFTPIXEL && 0==w) { + // skip left pixel output. + //DBG_MSG("[[%d]]",w); + } + else if(dwEPD_dc_flags&EPDFB_DC_FLAG_SKIPRIGHTPIXEL && I_dwDCimgW==w+1) { + // skip right pixel output. + //DBG_MSG("{{%d}}",w); + } + else { + _epdfbdc_set_pixel(pEPD_dc,dwX,dwY,dwPixelVal,bImgPixelBits); + } + } + }// image height loop . + } + break; + case EPDFB_R_180:GALLEN_DBGLOCAL_RUNLOG(4); + // right->left,down->up . + { + volatile unsigned long dwPixelVal; + + pEPD_dc->dwDirtyOffsetStart = (dwDCH-I_dwDCimgY-I_dwDCimgH)*dwDCWidthBytes; + pEPD_dc->dwDirtyOffsetEnd = (dwDCH-I_dwDCimgY)*dwDCWidthBytes; + + //dwImgPixelIdx = 0; + for(h=0;h<I_dwDCimgH;h+=1) { + + dwSrcY = I_dwDCimgY+h; + dwY = dwDCH-h-1-I_dwDCPutY-dwHHidden; + if(dwY>=dwDCH) { + continue ; + } + + pbImgRow = _epdfbdc_get_dcimg_ptr(pEPD_dcimg,I_dwDCimgX,dwSrcY); + + //DBG_MSG("y=%d->",I_dwDCimgH-h-1); + for(w=0;w<I_dwDCimgW;w+=1) { + dwSrcX = I_dwDCimgX+w; + dwX = dwDCW-w-1-I_dwDCPutX-dwWHidden; + //DBG_MSG("y=%d->",dwImgH-h-1); + if(dwX>=dwDCW) { + continue ; + } + + #if (GETIMG_PIXEL_METHOD==1)//[ + dwPixelVal = _epdfbdc_get_img_pixelvalue(pEPD_dc,pEPD_img,dwSrcX,dwSrcY,dwImgWidthBytes); + #else //][ + dwPixelVal = _epdfbdc_get_img_pixelvalue_from_ptr(pEPD_dcimg,&pbImgRow,dwSrcX); + #endif //] + + dwPixelVal = _epdfbdc_pixel_dither(pEPD_dc,bImgPixelBits,dwPixelVal,dwSrcX,dwSrcY); + + if(dwEPD_dc_flags&EPDFB_DC_FLAG_SKIPLEFTPIXEL && 0==w) { + // skip left pixel output. + } + else if(dwEPD_dc_flags&EPDFB_DC_FLAG_SKIPRIGHTPIXEL && I_dwDCimgW==w+1) { + // skip right pixel output. + } + else { + _epdfbdc_set_pixel(pEPD_dc,dwX,dwY,dwPixelVal,bImgPixelBits); + } + } + } + } + break; + case EPDFB_R_270:GALLEN_DBGLOCAL_RUNLOG(5); + // down->up,left->right . + { + unsigned long dwPixelVal; + + pEPD_dc->dwDirtyOffsetStart = (dwDCH-I_dwDCimgX-I_dwDCimgW)*dwDCWidthBytes; + pEPD_dc->dwDirtyOffsetEnd = (dwDCH-I_dwDCimgX)*dwDCWidthBytes; + + //dwImgPixelIdx = 0; + for(h=0;h<I_dwDCimgH;h+=1) { + + dwSrcY = I_dwDCimgY+h; + dwX = h+I_dwDCPutY; + if(dwX>=dwDCW) { + continue ; + } + + pbImgRow = _epdfbdc_get_dcimg_ptr(pEPD_dcimg,I_dwDCimgX,dwSrcY); + + for(w=0;w<I_dwDCimgW;w+=1) { + + dwSrcX = I_dwDCimgX+w; + dwY = dwDCH-w-1-I_dwDCPutX-dwHHidden; + if(dwY>=dwDCH) { + continue ; + } + + #if (GETIMG_PIXEL_METHOD==1) //[ + dwPixelVal = _epdfbdc_get_img_pixelvalue(pEPD_dc,pEPD_img,dwSrcX,dwSrcY,dwImgWidthBytes); + #else //][ + dwPixelVal = _epdfbdc_get_img_pixelvalue_from_ptr(pEPD_dcimg,&pbImgRow,dwSrcX); + #endif //] + + dwPixelVal = _epdfbdc_pixel_dither(pEPD_dc,bImgPixelBits,dwPixelVal,dwSrcX,dwSrcY); + + if(dwEPD_dc_flags&EPDFB_DC_FLAG_SKIPLEFTPIXEL && 0==w) { + // skip left pixel output. + } + else if(dwEPD_dc_flags&EPDFB_DC_FLAG_SKIPRIGHTPIXEL && I_dwDCimgW==w+1) { + // skip right pixel output. + } + else { + _epdfbdc_set_pixel(pEPD_dc,dwX,dwY,dwPixelVal,bImgPixelBits); + } + } + } + } + break; + } + +//printk ("[%s-%d] %d\n",__func__,__LINE__,jiffies-tick); + + GALLEN_DBGLOCAL_END(); + return tRet; +} + +EPDFB_DC_RET epdfbdc_put_fbimg(EPDFB_DC *pEPD_dc, + EPDFB_IMG *pEPD_img,EPDFB_ROTATE_T tRotateDegree) +{ + EPDFB_DC *ptEPD_dcimg ; + EPDFB_DC_RET tRet; + + ptEPD_dcimg = epdfbdc_create_ex(pEPD_img->dwW,pEPD_img->dwH,\ + pEPD_img->bPixelBits,pEPD_img->pbImgBuf,pEPD_dc->dwFlags); + + tRet = epdfbdc_put_dcimg(pEPD_dc,ptEPD_dcimg,tRotateDegree, + 0,0,pEPD_img->dwW,pEPD_img->dwH,pEPD_img->dwX,pEPD_img->dwY); + + epdfbdc_delete(ptEPD_dcimg); + + return tRet; +} + + +EPDFB_DC_RET epdfbdc_get_rotate_active(EPDFB_DC *I_pEPD_dc, + unsigned long *IO_pdwX,unsigned long *IO_pdwY, + unsigned long *IO_pdwW,unsigned long *IO_pdwH, + EPDFB_ROTATE_T I_tRotate) +{ + EPDFB_DC_RET tRet=EPDFB_DC_SUCCESS; + unsigned long dwX,dwY,dwH,dwW; + unsigned long dwDCW,dwDCH; + unsigned dwWHidden,dwHHidden; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(!(IO_pdwX&&IO_pdwY&&IO_pdwW&&IO_pdwH)) { + return EPDFB_DC_PARAMERR; + } + + dwWHidden = I_pEPD_dc->dwFBWExtra; + dwHHidden = I_pEPD_dc->dwFBHExtra; + dwDCH=I_pEPD_dc->dwHeight + dwHHidden; + dwDCW=I_pEPD_dc->dwWidth + dwWHidden; + + switch(I_tRotate) + { + default : + case EPDFB_R_0: + dwX = (*IO_pdwX); + dwY = (*IO_pdwY); + dwW = (*IO_pdwW); + dwH = (*IO_pdwH); + return tRet; + case EPDFB_R_90: + dwX = dwDCW-(*IO_pdwY)-(*IO_pdwH)-dwWHidden; + dwY = (*IO_pdwX); + dwW = (*IO_pdwH); + dwH = (*IO_pdwW); + break; + case EPDFB_R_180: + dwX = dwDCW-(*IO_pdwX)-(*IO_pdwW)-dwWHidden; + dwY = dwDCH-(*IO_pdwY)-(*IO_pdwH)-dwHHidden; + dwW = (*IO_pdwW); + dwH = (*IO_pdwH); + break; + case EPDFB_R_270: + dwX = (*IO_pdwY); + dwY = dwDCH-(*IO_pdwW)-(*IO_pdwX)-dwHHidden; + dwW = (*IO_pdwH); + dwH = (*IO_pdwW); + break; + } + *IO_pdwX = dwX; + *IO_pdwY = dwY; + *IO_pdwW = dwW; + *IO_pdwH = dwH; + return tRet; +} + + +EPDFB_DC_RET epdfbdc_set_pixel(EPDFB_DC *I_pEPD_dc,\ + unsigned long I_dwX,unsigned long I_dwY,unsigned long I_dwPVal) +{ + EPDFB_DC_RET tRet=EPDFB_DC_SUCCESS; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + _epdfbdc_set_pixel(I_pEPD_dc,I_dwX,I_dwY,I_dwPVal,4); + + return tRet; +} + +EPDFB_DC_RET epdfbdc_dcbuf_to_RGB565(EPDFB_DC *I_pEPD_dc,\ + unsigned char *O_pbRGB565Buf,unsigned long I_dwRGB565BufSize) +{ + EPDFB_DC_RET tRet=EPDFB_DC_SUCCESS; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + return EPDFB_DC_OBJECTERR; + } + + _fb_Gray4toRGB565(O_pbRGB565Buf,I_dwRGB565BufSize,\ + I_pEPD_dc->pbDCbuf,I_pEPD_dc->dwDCSize,I_pEPD_dc->dwFlags&EPDFB_DC_FLAG_REVERSEINPDATA); + + return tRet; +} + +EPDFB_DC_RET epdfbdc_get_dirty_region(EPDFB_DC *I_pEPD_dc,\ + unsigned long *O_pdwDirtyOffsetStart,unsigned long *O_pdwDirtyOffsetEnd) +{ + EPDFB_DC_RET tRet=EPDFB_DC_SUCCESS; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + return EPDFB_DC_OBJECTERR; + } + + if(O_pdwDirtyOffsetStart) { + *O_pdwDirtyOffsetStart = I_pEPD_dc->dwDirtyOffsetStart ; + } + I_pEPD_dc->dwDirtyOffsetStart = 0; + + if(O_pdwDirtyOffsetEnd) { + *O_pdwDirtyOffsetEnd = I_pEPD_dc->dwDirtyOffsetEnd ; + } + I_pEPD_dc->dwDirtyOffsetEnd = I_pEPD_dc->dwDCSize; + + return tRet; +} + + +EPDFB_DC_RET epdfbdc_set_width_height(EPDFB_DC *I_pEPD_dc, + unsigned long dwFBW,unsigned long dwFBH, + unsigned long dwW,unsigned long dwH) +{ + EPDFB_DC_RET tRet = EPDFB_DC_SUCCESS; + unsigned char bPixelBits; + unsigned long dwFBBits ; + unsigned long dwFBWBits; + unsigned long dwDCSize ,dwDCWidthBytes; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(dwFBW<dwW) { + ERR_MSG("%s(%d): FBW(%d)<W(%d) !\n",__FUNCTION__,__LINE__,dwFBW,dwW); + return EPDFB_DC_PARAMERR; + } + + if(dwFBH<dwH) { + ERR_MSG("%s(%d): FBH(%d)<H(%d) !\n",__FUNCTION__,__LINE__,dwFBH,dwH); + return EPDFB_DC_PARAMERR; + } + + bPixelBits = I_pEPD_dc->bPixelBits; + dwFBBits = (dwFBW*dwFBH*bPixelBits); + dwFBWBits = (dwFBW*bPixelBits); + dwDCSize=dwFBBits>>3; + dwDCWidthBytes=dwFBWBits>>3; + + if(dwFBWBits&0x7) { + dwDCWidthBytes+=1; + dwDCSize+=dwFBH; + } + + if( I_pEPD_dc->dwBufSize>0 && dwDCSize>I_pEPD_dc->dwDCSize) { + ERR_MSG("%s(%d): new DCSize(%d)>original DCSize(%d) !\n",__FUNCTION__,__LINE__, + dwDCSize,I_pEPD_dc->dwDCSize); + return EPDFB_DC_PARAMERR; + } + + I_pEPD_dc->dwDCWidthBytes = dwDCWidthBytes; + I_pEPD_dc->dwDirtyOffsetEnd = dwDCSize; + I_pEPD_dc->dwDCSize = dwDCSize; + + I_pEPD_dc->dwFBWExtra = dwFBW-dwW ; + I_pEPD_dc->dwFBHExtra = dwFBH-dwH ; + I_pEPD_dc->dwWidth = dwW; + I_pEPD_dc->dwHeight = dwH; + + return tRet; +} +EPDFB_DC_RET epdfbdc_set_fbxyoffset(EPDFB_DC *I_pEPD_dc, + unsigned long dwFBXOffset,unsigned long dwFBYOffset) +{ + EPDFB_DC_RET tRet = EPDFB_DC_SUCCESS; + unsigned long dwFBW,dwFBH; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + dwFBW = I_pEPD_dc->dwWidth+I_pEPD_dc->dwFBWExtra; + dwFBH = I_pEPD_dc->dwHeight+I_pEPD_dc->dwFBHExtra; + + if(dwFBW<dwFBXOffset) { + ERR_MSG("%s(%d): FBW(%d)<X(%d) !\n",__FUNCTION__,__LINE__,dwFBW,dwFBXOffset); + return EPDFB_DC_PARAMERR; + } + + if(dwFBH<dwFBYOffset) { + ERR_MSG("%s(%d): FBH(%d)<Y(%d) !\n",__FUNCTION__,__LINE__,dwFBH,dwFBYOffset); + return EPDFB_DC_PARAMERR; + } + + I_pEPD_dc->dwFBXOffset = dwFBXOffset; + I_pEPD_dc->dwFBYOffset = dwFBYOffset; + + return tRet; +} + +EPDFB_DC_RET epdfbdc_rotate(EPDFB_DC *I_pEPD_dc,EPDFB_ROTATE_T I_tRotate) +{ + EPDFB_DC_RET tRet = EPDFB_DC_SUCCESS; + unsigned char *pbWorkBuf; + EPDFB_IMG tEPD_img; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(I_tRotate == EPDFB_R_0) { + // nothing have to do . + return EPDFB_DC_SUCCESS; + } + + pbWorkBuf = my_malloc(I_pEPD_dc->dwDCSize); + if(pbWorkBuf) { + + tEPD_img.dwX = 0; + tEPD_img.dwY = 0; + tEPD_img.dwW = I_pEPD_dc->dwWidth; + tEPD_img.dwH = I_pEPD_dc->dwHeight; + + tEPD_img.bPixelBits = I_pEPD_dc->bPixelBits; + tEPD_img.pbImgBuf = pbWorkBuf; + + my_memcpy(pbWorkBuf,I_pEPD_dc->pbDCbuf,I_pEPD_dc->dwDCSize); + + tRet = epdfbdc_put_fbimg(I_pEPD_dc,&tEPD_img,I_tRotate); + my_free(pbWorkBuf);pbWorkBuf=0; + } + else { + ERR_MSG("%s(%d): memory not enough !\n",__FUNCTION__,__LINE__); + tRet = EPDFB_DC_MEMMALLOCFAIL; + } + + return tRet; +} + +EPDFB_DC_RET epdfbdc_set_host_dataswap(EPDFB_DC *I_pEPD_dc,int iIsSet) +{ + EPDFB_DC_RET tRet = EPDFB_DC_SUCCESS; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(iIsSet) { + I_pEPD_dc->dwFlags |= EPDFB_DC_FLAG_REVERSEINPDATA; + } + else { + I_pEPD_dc->dwFlags &= ~EPDFB_DC_FLAG_REVERSEINPDATA; + } + + return tRet; +} + +EPDFB_DC_RET epdfbdc_set_drive_dataswap(EPDFB_DC *I_pEPD_dc,int iIsSet) +{ + EPDFB_DC_RET tRet = EPDFB_DC_SUCCESS; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(iIsSet) { + I_pEPD_dc->dwFlags |= EPDFB_DC_FLAG_SKIPLEFTPIXEL; + } + else { + I_pEPD_dc->dwFlags &= ~EPDFB_DC_FLAG_SKIPLEFTPIXEL; + } + + return tRet; +} + +EPDFB_DC_RET epdfbdc_set_skip_pixel(EPDFB_DC *I_pEPD_dc,int iIsSet,int iIsSkipRight) +{ + EPDFB_DC_RET tRet = EPDFB_DC_SUCCESS; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(iIsSkipRight) { + if(iIsSet) { + I_pEPD_dc->dwFlags |= EPDFB_DC_FLAG_SKIPRIGHTPIXEL; + } + else { + I_pEPD_dc->dwFlags &= ~EPDFB_DC_FLAG_SKIPRIGHTPIXEL; + } + } + else { + if(iIsSet) { + I_pEPD_dc->dwFlags |= EPDFB_DC_FLAG_SKIPLEFTPIXEL; + } + else { + I_pEPD_dc->dwFlags &= ~EPDFB_DC_FLAG_SKIPLEFTPIXEL; + } + } + + return tRet; +} + +EPDFB_DC_RET epdfbdc_set_flags(EPDFB_DC *I_pEPD_dc,unsigned long *pIO_dwFlags) +{ + unsigned dwOldFlags; + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(!pIO_dwFlags) { + return EPDFB_DC_PARAMERR; + } + + dwOldFlags = I_pEPD_dc->dwFlags ; + I_pEPD_dc->dwFlags = *pIO_dwFlags; + *pIO_dwFlags = dwOldFlags; + return EPDFB_DC_SUCCESS; +} + +EPDFB_DC_RET epdfbdc_get_flags(EPDFB_DC *I_pEPD_dc,unsigned long *pO_dwFlags) +{ + + if(!CHK_EPDFB_DC(I_pEPD_dc)) { + ERR_MSG("%s(%d): object handle error !\n",__FUNCTION__,__LINE__); + return EPDFB_DC_OBJECTERR; + } + + if(!pO_dwFlags) { + return EPDFB_DC_PARAMERR; + } + + *pO_dwFlags = I_pEPD_dc->dwFlags; + return EPDFB_DC_SUCCESS; +} + + + diff --git a/drivers/video/mxc/epdfb_dc.h b/drivers/video/mxc/epdfb_dc.h new file mode 100644 index 00000000..d25eae07 --- /dev/null +++ b/drivers/video/mxc/epdfb_dc.h @@ -0,0 +1,184 @@ +/*************************************************** + * EPD frame buffer device content manager (4 bits). + * + * Author : Gallen Lin + * Data : 2011/01/20 + * Revision : 1.0 + * File Name : epdfb_dc.h + * +****************************************************/ + +#ifndef __epdfb_dc_h//[ +#define __epdfb_dc_h + + +typedef enum { + EPDFB_DC_SUCCESS = 0, + EPDFB_DC_PARAMERR = -1, + EPDFB_DC_MEMMALLOCFAIL = -2, + EPDFB_DC_OBJECTERR = -3, // dc object content error . + EPDFB_DC_PIXELBITSNOTSUPPORT = -4, +} EPDFB_DC_RET; + +typedef enum { + EPDFB_R_0 = 0, + EPDFB_R_90 = 1, + EPDFB_R_180 = 2, + EPDFB_R_270 = 3, +} EPDFB_ROTATE_T; + +typedef struct tagEPDFB_IMG { + unsigned long dwX,dwY; + unsigned long dwW,dwH; + unsigned char bPixelBits,bReserve; + unsigned char *pbImgBuf; +} EPDFB_IMG; + +typedef void (*fnVcomEnable)(int iIsEnable); +typedef void (*fnPwrOnOff)(int iIsOn); +typedef void (*fnPwrAutoOffIntervalMax)(int iIsEnable); +typedef void (*fnAutoOffEnable)(int iIsEnable); +typedef int (*fnGetWaveformBpp)(void); +typedef int (*fnSetPartialUpdate)(int iIsPartial); +typedef unsigned char *(*fnGetRealFrameBuf)(void); +typedef unsigned char *(*fnGetRealFrameBufEx)(unsigned long *O_pdwFBSize); +typedef void (*fnDispStart)(int iIsStart); +typedef int (*fnGetWaveformMode)(void); +typedef void (*fnSetWaveformMode)(int iWaveformMode); +typedef int (*fnIsUpdating)(void); +typedef int (*fnWaitUpdateComplete)(void); +typedef int (*fnSetUpdateRect)(unsigned short wX,unsigned short wY, + unsigned short wW,unsigned short wH); +typedef int (*fnPutImg)(EPDFB_IMG *I_ptPutImage,EPDFB_ROTATE_T I_tRotate); +typedef int (*fnSetVCOM)(int iVCOM_set_mV); +typedef int (*fnSetVCOMToFlash)(int iVCOM_set_mV); +typedef int (*fnGetVCOM)(int *O_piVCOM_get_mV); + + +// epd framebuffer device content ... +typedef struct tagEPDFB_DC { + // public : + unsigned long dwWidth;// visible DC width . + unsigned long dwHeight;// visible DC height . + unsigned long dwFBWExtra; // frame buffer width extra width . + unsigned long dwFBHExtra; // frame buffer height extra height . + unsigned long dwFBXOffset; // frame buffer X offset . + unsigned long dwFBYOffset; // frame buffer Y offset . + unsigned char bPixelBits; + unsigned char bReservedA[1]; + volatile unsigned char *pbDCbuf;// DC buffer . + + fnGetWaveformBpp pfnGetWaveformBpp; + fnVcomEnable pfnVcomEnable; + fnSetPartialUpdate pfnSetPartialUpdate; + fnGetRealFrameBuf pfnGetRealFrameBuf; + fnGetRealFrameBufEx pfnGetRealFrameBufEx; + fnDispStart pfnDispStart; + fnGetWaveformMode pfnGetWaveformMode; + fnSetWaveformMode pfnSetWaveformMode; + fnIsUpdating pfnIsUpdating; + fnWaitUpdateComplete pfnWaitUpdateComplete; + fnSetUpdateRect pfnSetUpdateRect; + fnPwrAutoOffIntervalMax pfnPwrAutoOffIntervalMax; + fnPwrOnOff pfnPwrOnOff; + fnAutoOffEnable pfnAutoOffEnable; + fnPutImg pfnPutImg; + fnSetVCOM pfnSetVCOM; + fnSetVCOMToFlash pfnSetVCOMToFlash; + fnGetVCOM pfnGetVCOM; + + // private : do not modify these member var . + // only for epdfbdc manager . + unsigned long dwMagicPrivateBegin; + + unsigned long dwDCSize; // visible dc size . + unsigned long dwBufSize; // dc buffer real size . + + unsigned long dwDCWidthBytes; + unsigned long dwFlags; + int iWFMode,iLastWFMode; + int iIsForceWaitUpdateFinished; + unsigned long dwDirtyOffsetStart,dwDirtyOffsetEnd; + unsigned long dwMagicPrivateEnd; + int dither8[16][16]; +} EPDFB_DC; + + +#define epdfbdc_create_e60mt2() epdfbdc_create(800,600,4,0) + +EPDFB_DC *epdfbdc_create(unsigned long dwW,unsigned long dwH,\ + unsigned char bPixelBits,unsigned char *pbDCbuf); + +#define EPDFB_DC_FLAG_DEFAUT (0x0) + +#define EPDFB_DC_FLAG_REVERSEDRVDATA 0x00000001 // reverse drive data -> pixel 3,pixel 4,pixel 1,pixel 2 . +#define EPDFB_DC_FLAG_REVERSEINPDATA 0x00000002 // reverse input data -> pixel 3,pixel 4,pixel 1,pixel 2 . + +#define EPDFB_DC_FLAG_SKIPLEFTPIXEL 0x00000004 // shift input image data (skip 1 pixel in image left side). +#define EPDFB_DC_FLAG_SKIPRIGHTPIXEL 0x00000008 // shift input image data (skip 1 pixel in image right side). + + +#define EPDFB_DC_FLAG_DITHER8 0x00000100 // + +#define EPDFB_DC_FLAG_OFB_RGB565 0x00010000 // output framebuffer data format is rgb565 . +#define EPDFB_DC_FLAG_FLASHDIRTY 0x00020000 // only flash dirty image . + + + +EPDFB_DC *epdfbdc_create_ex(unsigned long dwW,unsigned long dwH,\ + unsigned char bPixelBits,unsigned char *pbDCbuf,unsigned long dwCreateFlag); + +EPDFB_DC *epdfbdc_create_ex2(unsigned long dwFBW,unsigned long dwFBH,\ + unsigned long dwW,unsigned long dwH,\ + unsigned char bPixelBits,unsigned char *pbDCbuf,unsigned long dwCreateFlag); + + +EPDFB_DC_RET epdfbdc_delete(EPDFB_DC *I_pEPD_dc); + + + + +EPDFB_DC_RET epdfbdc_fbimg_normallize(EPDFB_DC *I_pEPD_dc,\ + EPDFB_IMG *IO_pEPD_img); + +EPDFB_DC_RET epdfbdc_put_fbimg(EPDFB_DC *I_pEPD_dc,\ + EPDFB_IMG *I_pEPD_img,EPDFB_ROTATE_T I_tRotateDegree); + +EPDFB_DC_RET epdfbdc_put_dcimg(EPDFB_DC *pEPD_dc, + EPDFB_DC *pEPD_dcimg,EPDFB_ROTATE_T tRotateDegree, + unsigned long I_dwDCimgX,unsigned long I_dwDCimgY, + unsigned long I_dwDCimgW,unsigned long I_dwDCimgH, + unsigned long I_dwDCPutX,unsigned long I_dwDCPutY); + +EPDFB_DC_RET epdfbdc_get_dirty_region(EPDFB_DC *I_pEPD_dc,\ + unsigned long *O_pdwDirtyOffsetStart,unsigned long *O_pdwDirtyOffsetEnd); + +EPDFB_DC_RET epdfbdc_set_pixel(EPDFB_DC *I_pEPD_dc,\ + unsigned long I_dwX,unsigned long I_dwY,unsigned long I_dwPVal); + +EPDFB_DC_RET epdfbdc_dcbuf_to_RGB565(EPDFB_DC *I_pEPD_dc,\ + unsigned char *O_pbRGB565Buf,unsigned long I_dwRGB565BufSize); + +EPDFB_DC_RET epdfbdc_rotate(EPDFB_DC *I_pEPD_dc,EPDFB_ROTATE_T I_tRotate); +EPDFB_DC_RET epdfbdc_set_host_dataswap(EPDFB_DC *I_pEPD_dc,int I_isSet); +EPDFB_DC_RET epdfbdc_set_drive_dataswap(EPDFB_DC *I_pEPD_dc,int I_isSet); +EPDFB_DC_RET epdfbdc_set_skip_pixel(EPDFB_DC *I_pEPD_dc,int iIsSet,int iIsSkipRight); + +EPDFB_DC_RET epdfbdc_get_rotate_active(EPDFB_DC *I_pEPD_dc, + unsigned long *IO_pdwX,unsigned long *IO_pdwY, + unsigned long *IO_pdwW,unsigned long *IO_pdwH, + EPDFB_ROTATE_T I_tRotate); + +EPDFB_DC_RET epdfbdc_set_width_height(EPDFB_DC *I_pEPD_dc, + unsigned long dwFBW,unsigned long dwFBH, + unsigned long dwW,unsigned long dwH); + +EPDFB_DC_RET epdfbdc_set_fbxyoffset(EPDFB_DC *I_pEPD_dc, + unsigned long dwFBXOffset,unsigned long dwFBYOffset); + +EPDFB_DC_RET epdfbdc_set_flags(EPDFB_DC *I_pEPD_dc,unsigned long *pIO_dwFlags); + +EPDFB_DC_RET epdfbdc_get_flags(EPDFB_DC *I_pEPD_dc,unsigned long *pO_dwFlags); + +#endif //]__epdfb_dc_h + diff --git a/drivers/video/mxc/fake_s1d13522.c b/drivers/video/mxc/fake_s1d13522.c new file mode 100644 index 00000000..ba0e26cb --- /dev/null +++ b/drivers/video/mxc/fake_s1d13522.c @@ -0,0 +1,3149 @@ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/syscalls.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/fcntl.h> +#include <asm/uaccess.h> +#include <linux/delay.h> + + +#include "DataType.h" +#include "s1d13521ioctl.h" + +#include "s1d13521.h" +#include "s1d13521fb.h" + +#ifndef GDEBUG //[ + #define GDEBUG 0 +#endif //]GDEBUG +#include <linux/gallen_dbg.h> + +#include "epdfb_dc.h" +#include "fake_s1d13522.h" +#include "ntx_hwconfig.h" + + +#define _PVI_IOCTL_INTERFACE 1 +#define _QT_SUPPORT 1 + + + + +#define BusIssueReadReg(wRegIdx) S1D13522_reg_read(wRegIdx) +#define BusIssueWriteReg(wRegIdx,wValue) S1D13522_reg_write(wRegIdx,wValue) + + + +extern volatile NTX_HWCONFIG *gptHWCFG; + + + +// for s1d13522 hardware registers . +#define S1D13522_REGADDR_PRODUCTCODE 0x0002 // Product code register . +#define S1D13522_REGADDR_LUTSTATUS 0x0336 // Lookup table status register . +#define S1D13522_REGADDR_HOSTIFMACONF 0x0140 // Host interface memory access configuration . +#define S1D13522_REGADDR_GENCONF 0x032C // General configuration register . +#define S1D13522_REGADDR_HOSTMEMPIXSWAP 0x015E // Host memory access pixel swap configuration . +#define S1D13522_REGADDR_PUBC 0x0330 // Panel Update Buffer Configuration . +#define S1D13522_REGADDR_DSPENGINTRSR 0x033A // Display Engine Interrupt Raw Status Register . +//static uint16_t gwS1d13522_hostifmaconf = 0x0000; //register 0x140, host packed pixel is 4 bits. +static uint16_t gwS1d13522_GenConf = 0x0000; //register 0x32C, General config configuration register . +static uint16_t gwS1d13522_hostmempixswapconf = 0x0000; //register 0x15E, host packed pixel is 4 bits. +static uint16_t gwS1d13522_DspEngIntrRSR = 0x0000;// Display Engine Interrupt Raw Status Register . + + +// for epdfb_dc . +static epdfb_rotate_t gtRotate = epdfb_rotate_0; +static int giPixelBits = 4;// +static EPDFB_DC *gptEPD_dc_current ;// epd device content . + +static unsigned short gwScrW=800,gwScrH=600; + +//static DECLARE_COMPLETION(fake13522_inited); + +//static u32 currHeight=800,currWidth=600; +#define S1D13522_PRODUCTCODE 0x004d + + +#ifdef _QT_SUPPORT //[ + // vars ... + static int resolve_conflict_step; + static unsigned long gLastUpdParm0; + + + static BOOL bConflictTimerRunning; // KEG 20090825 + static struct timer_list conflict_resolution_timer; // KEG 20090825 + + // functions ... + static void s1d13521fb_resolve_conflict(unsigned long dummy); + + #define _ES_LUTNOWAIT_ 1 + +#endif //] _QT_SUPPORT + +#ifdef _PVI_IOCTL_INTERFACE //[ +#define _PVI_IMAGE_SIZE (S1D_DISPLAY_HEIGHT*S1D_DISPLAY_WIDTH/4) +#define _PVI_IMAGE_SIZE_800x600 (800*600/4) + +//static BYTE *ImagePVI_BufferA[_PVI_IMAGE_SIZE]; +//static BYTE *ImagePVI = ImagePVI_BufferA; +static BYTE *ImagePVI ; + +//static BYTE gbTemp4BitsFBA[_PANEL_HEIGHT*_PANEL_WIDTH/(8/S1D_DISPLAY_BPP)]; + + +static struct timer_list pvi_refersh_timer; + +int pvi_Init(VOID); +void pvi_Deinit(VOID); +#endif //] _PVI_IOCTL_INTERFACE + +static ST_PVI_CONFIG ConfigPVI={ + .CurRotateMode =(_INIT_ROTMODE << 8), + // .PastRotateMode=(_INIT_ROTMODE << 8), + .PastRotateMode=0xffff, + .Deepth=2, + .StartX=0, + .StartY=0, + .Width =_PANEL_HEIGHT, + .Height=_PANEL_WIDTH, + + .Reg0x140=0, + .Reg0x330=0, + + .ReverseGrade=0 +}; + + +#ifdef SHOW_PROGRESS_BAR +static DECLARE_WAIT_QUEUE_HEAD(progress_WaitQueue); +volatile static int gIsProgessRunning=1; +static VOID PROGRESS_BAR(EPDFB_DC *pDC); +#endif + + + +/* +* +* for boot argument . +* +*/ +#define _TEST_CMDLINE 0 +#define _MYCMDLINE_PARSE 1 + +#if (_MYCMDLINE_PARSE==1) //[ +#define _MYINIT_TEXT __init +#define _MYINIT_DATA __initdata +#else//][ +#define _MYINIT_TEXT __init +#define _MYINIT_DATA __initdata +#endif//] + +volatile unsigned char *gpbLOGO_paddr; +volatile unsigned char *gpbLOGO_vaddr; +volatile unsigned long gdwLOGO_size; + +volatile unsigned char *gpbWF_paddr; +volatile unsigned char *gpbWF_vaddr; +volatile unsigned long gdwWF_size; + +volatile long glVCOM_uV; + +int giIsLogoByPass=0; + + + +/** + * \fn _MemoryRequest + * \brief allocate memory region for kermit + * \param address, + * \param memory length + * \param name assigned to memory region + * \return none + */ +static void * _MemoryRequest(u32 addr, u32 len, const char * name) +{ + void * mem = NULL; + do { + printk(KERN_DEBUG "***%s:%d: request memory region! addr=0x%08x, len=%d***\n", + __FUNCTION__, __LINE__, addr, len); + if (!request_mem_region(addr, len, name)) { + printk(KERN_CRIT "fake_s1d13522: request memory region failed! addr=0x%08x, len %d\n", addr, len); + break; + } + mem = (void *) ioremap_nocache(addr, len); + if (!mem) { + printk(KERN_CRIT "***%s:%d: could not ioremap %s***\n", __FUNCTION__, __LINE__, name); + release_mem_region(addr, len); + break; + } + } while (0); + return mem; +} + +/** + * \fn _MemoryRelease + * \brief free kermit's memory region + * \param memory region address + * \param address + * \param memory length + * \return none + */ +void _MemoryRelease(void * unmap_addr, unsigned long addr, unsigned long len) +{ + iounmap(unmap_addr); + release_mem_region(addr, len); + printk(KERN_INFO "fake_s1d13522: release memory region!\n"); +} + +static int _MYINIT_TEXT vcom_uV_setup(char *str) +{ + #if (_TEST_CMDLINE == 0) + glVCOM_uV = (long)simple_strtol(str,NULL,0); + printk("%s() VCOM uV=%d\n",__FUNCTION__,(int)glVCOM_uV); + #else + printk("%s() str=%s\n",__FUNCTION__,str); + #endif + return 1; +} + + + + +static int _MYINIT_TEXT waveform_p_setup(char *str) +{ + #if (_TEST_CMDLINE == 0) + gpbWF_paddr = (unsigned char *)simple_strtoul(str,NULL,0); + if(NULL==gpbWF_vaddr) { + gpbWF_vaddr = (u32 *)_MemoryRequest((u32)gpbWF_paddr, gdwWF_size, "waveform_p"); + if(!gpbWF_vaddr) { + return 0; + } + } + printk("%s() wf_p=%p,vaddr=%p,size=%lu\n",__FUNCTION__, + gpbWF_paddr,gpbWF_vaddr,gdwWF_size); + + #if 0//[ + { + volatile u8 *pbTemp; + int i,irequestsize=32; + + printk("gpbWF_vaddr (%p),size=%u = [\n\t",gpbWF_vaddr,irequestsize); + if(gpbWF_vaddr) { + pbTemp = (u8*)gpbWF_vaddr; + for(i=0;i<irequestsize;i++) { + printk("%02X,",pbTemp[i]); + } + } + printk("]\n"); + } + #endif //] + #else + printk("%s() str=%s\n",__FUNCTION__,str); + #endif + return 1; +} + + +static int _MYINIT_TEXT waveform_size_setup(char *str) +{ + #if (_TEST_CMDLINE == 0) + gdwWF_size = (unsigned int)simple_strtoul(str,NULL,0); + printk("%s() wf_size=%lu\n",__FUNCTION__,gdwWF_size); + #else + printk("%s() str=%s\n",__FUNCTION__,str); + #endif + return 1; +} + +static int _MYINIT_TEXT logo_setup(char *str) +{ + #if (_TEST_CMDLINE == 0) + if(0==strcmp(str,"bypass")) { + printk("%s() bypass logo\n",__FUNCTION__,str); + giIsLogoByPass=1; + } + #else + printk("%s() str=%s\n",__FUNCTION__,str); + #endif + return 1; +} + +static int _MYINIT_TEXT logo_p_setup(char *str) +{ + #if (_TEST_CMDLINE == 0) + gpbLOGO_paddr = (unsigned char *)simple_strtoul(str,NULL,0); + if(NULL==gpbLOGO_vaddr) { + gpbLOGO_vaddr = (u32 *)_MemoryRequest((u32)gpbLOGO_paddr, gdwLOGO_size, "logo_p"); + if(!gpbWF_vaddr) { + return 0; + } + } + printk("%s() logo_p=%p,vaddr=%p,size=%lu\n",__FUNCTION__, + gpbLOGO_paddr,gpbLOGO_vaddr,gdwLOGO_size); + #else + printk("%s() str=%s\n",__FUNCTION__,str); + #endif + return 1; +} + +static int _MYINIT_TEXT logo_size_setup(char *str) +{ + #if (_TEST_CMDLINE == 0) + gdwLOGO_size = (int)simple_strtoul(str,NULL,0); + printk("%s() logo_szie=%lu\n",__FUNCTION__,gdwLOGO_size); + #else + printk("%s() str=%s\n",__FUNCTION__,str); + #endif + return 1; +} + + + + +#if (_MYCMDLINE_PARSE==0) //[ +__setup("vcom=",vcom_uV_setup); +__setup("waveform_p=",waveform_p_setup); +__setup("waveform_sz=",waveform_size_setup); +__setup("logo_p=",logo_p_setup); +__setup("logo_sz=",logo_size_setup); +__setup("logo=",logo_setup); +#else //][ +void fake_s1d13522_parse_epd_cmdline(void) +{ + static int iParseCnt = 0; + char *pcPatternStart,*pcPatternVal,*pcPatternValEnd,cTempStore='\0'; + unsigned long ulPatternLen; + + char *szParsePatternA[]={"vcom=","waveform_sz=","waveform_p=","logo_sz=","logo_p=","logo="}; + int ((*pfnDispatchA[])(char *str))={ \ + vcom_uV_setup,waveform_size_setup,waveform_p_setup,\ + logo_size_setup,logo_p_setup,logo_setup}; + + int i; + char *pszCmdLineBuf; + + + if(iParseCnt++>0) { + WARNING_MSG("%s : epd cmdline parse already done .\n",__FUNCTION__); + return ; + } + //printk("%s():cmdline(%d)=%s\n",__FUNCTION__,strlen(saved_command_line),saved_command_line); + + pszCmdLineBuf = kmalloc(strlen(saved_command_line)+1,GFP_KERNEL); + ASSERT(pszCmdLineBuf); + strcpy(pszCmdLineBuf,saved_command_line); + //printk("%s():cp cmdline=%s\n",__FUNCTION__,pszCmdLineBuf); + + for(i=0;i<sizeof(szParsePatternA)/sizeof(szParsePatternA[0]);i++) { + ulPatternLen = strlen(szParsePatternA[i]); + pcPatternStart = strstr(pszCmdLineBuf,szParsePatternA[i]); + if(pcPatternStart) { + pcPatternVal=pcPatternStart + ulPatternLen ; + pcPatternValEnd = strchr(pcPatternVal,' '); + if(pcPatternValEnd) { + cTempStore = *pcPatternValEnd; + *pcPatternValEnd = '\0'; + } + //printk("%s():pattern \"%s\" ,val=\"%s\"\n",__FUNCTION__,szParsePatternA[i],pcPatternVal); + pfnDispatchA[i](pcPatternVal); + if(pcPatternValEnd) { + *pcPatternValEnd = cTempStore; + } + } + } + + if(pszCmdLineBuf) { + kfree(pszCmdLineBuf); + } +} +#endif //] + + + +int fake_s1d13522_is_logo_bypss(void) { + return giIsLogoByPass; +} + + +// +// file operation to emulate S1D13521 flash read/write . +// +static int read_file(char *filename, char *pBuffer) +{ + struct file *file; + loff_t pos = 0; + int result = 0; + mm_segment_t old_fs = get_fs(); + + file = filp_open(filename, O_RDONLY, 0); + + if (IS_ERR(file)) { + printk ("[%s-%d] failed open %s,(%p)\n",__func__,__LINE__,filename,file); + } + else { + ssize_t tSz; + set_fs(KERNEL_DS); + printk(KERN_DEBUG); + tSz = file->f_op->read(file, pBuffer, 4096, &file->f_pos); + if(tSz!=4096) { + printk ("[%s-%d] failed read %s,%d!=4096\n",__func__,__LINE__,filename,tSz); + result = 0; + } + else { + result = 1; + } + filp_close(file, NULL); + set_fs(old_fs); + } + return result; +} + +int fake_s1d13522_write_file_ex2(char *filename, char *data,unsigned long dwDataSize,unsigned long dwSeekSize) +{ + struct file *file; + loff_t pos = 0; + int fd; + int iRet = 1; + loff_t fsz_chk,fsz_temp ; + mm_segment_t old_fs; + + file = filp_open(filename, O_WRONLY|O_CREAT, 0644); + + if (IS_ERR(file)) { + printk ("[%s-%d] failed open %s,(%p)\n",__func__,__LINE__,filename,file); + } + else { + ssize_t tSz; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + fsz_temp = (loff_t)dwSeekSize; + fsz_chk = file->f_op->llseek(file,fsz_temp,SEEK_SET); + if(fsz_temp!=fsz_chk) { + printk("[WARNING] %s() : seek size check fail %d!=%d\n",__FUNCTION__,fsz_chk,fsz_temp); + } + tSz = file->f_op->write(file, (char *)data, dwDataSize, &file->f_pos); + if(dwDataSize!=tSz) { + printk ("[%s-%d] failed write %s,%d!=4096\n",__func__,__LINE__,filename,tSz); + iRet = 0; + } + filp_close(file, NULL); + set_fs(old_fs); + } + return iRet; + +} + +static int write_file_ex(char *filename, char *data,unsigned long dwSize) +{ + return fake_s1d13522_write_file_ex2(filename,data,dwSize,0); +} + +static int write_file(char *filename, char *data) +{ + return write_file_ex(filename, data,4096); +} + +static char *gFlashBuffer; +static BOOL Flash_File_Erase(DWORD FlashAddress, DWORD Length) +{ + BOOL fRet = TRUE; + unsigned char FilenameA[256]; + + if (!gFlashBuffer) + gFlashBuffer = __get_free_pages(GFP_KERNEL, 1); + sprintf(FilenameA,"/etc/epson_flash_%08X",(FlashAddress&0xFFFFF000)); + memset (gFlashBuffer, 0xFF, 4096); + write_file (FilenameA, gFlashBuffer); + return fRet; +} + +static BOOL Flash_File_Write(DWORD FlashAddress, DWORD Length, PBYTE pData) +{ + BOOL fRet = TRUE; + unsigned char FilenameA[256]; + + if (!gFlashBuffer) { + gFlashBuffer = __get_free_pages(GFP_KERNEL, 1); + ASSERT(gFlashBuffer); + } + sprintf(FilenameA,"/etc/epson_flash_%08X",(FlashAddress&0xFFFFF000)); + + if (!read_file (FilenameA, gFlashBuffer)) { + memset (gFlashBuffer, 0xFF, 4096); + } + memcpy (gFlashBuffer+(FlashAddress&0x0FFF), pData, Length); + if(!write_file (FilenameA, gFlashBuffer)) { + printk("[kenel]%s-%d : write_file %s fail !!\n",__FUNCTION__,__LINE__,FilenameA); + fRet = 0; + } + return fRet; +} + +static BOOL Flash_File_Read(DWORD FlashAddress, DWORD Length, PBYTE pData) +{ + BOOL fRet = TRUE; + unsigned char FilenameA[256]; + + if (!gFlashBuffer) { + gFlashBuffer = __get_free_pages(GFP_KERNEL, 1); + ASSERT(gFlashBuffer); + } + + + sprintf(FilenameA,"/etc/epson_flash_%08X",(FlashAddress&0xFFFFF000)); + if (read_file (FilenameA, gFlashBuffer)) { + memcpy (pData, gFlashBuffer+(FlashAddress&0x0FFF), Length); + } + else { + memset (pData, 0xFF, Length); + } + return fRet; +} + +#define WAVEFORM_4BIT_MARK 0x20000 +static int BusIssueFlashOperation(PS1D13532_FLASH_PACKAGE pFlashControl,EPDFB_DC *pDC) +{ +int RetCode=-1; + //DEBUGMSG(_DUMP_IOCTL_FLASH, "pFlashControl->Command=%u\n", pFlashControl->Command); + //DEBUGMSG(_DUMP_IOCTL_FLASH, "pFlashControl->StartAddr=0x%08x\n", pFlashControl->StartAddr); + //DEBUGMSG(_DUMP_IOCTL_FLASH, "pFlashControl->DataLength=0x%08x\n", pFlashControl->DataLength); + //DEBUGMSG(_DUMP_IOCTL_FLASH, "pFlashControl->Buf=0x%p\n", pFlashControl->Buf); + + switch(pFlashControl->Command) { + case _FLASH_CMD_GET_INFO: + printk ("[%s-%d] Flash_GetID (0x%08X);\n",__func__, __LINE__, pFlashControl->Buf); + //DEBUGMSG(_DUMP_IOCTL_FLASH, "IDCode is 0x%08x\n", *((PDWORD) pFlashControl->Buf)); + RetCode=4; + break; + + case _FLASH_CMD_ERASE: + if(Flash_File_Erase(pFlashControl->StartAddr, pFlashControl->DataLength) == FALSE) + break; + RetCode=0; + break; + + case _FLASH_CMD_WRITE: + if(Flash_File_Write(pFlashControl->StartAddr, pFlashControl->DataLength, pFlashControl->Buf) == FALSE) + break; + RetCode=0; + break; + + case _FLASH_CMD_READ: + Flash_File_Read(pFlashControl->StartAddr, pFlashControl->DataLength, pFlashControl->Buf); + if (WAVEFORM_4BIT_MARK == pFlashControl->StartAddr) { + ASSERT(pDC); + + if(4==pDC->pfnGetWaveformBpp()) { + printk ("[%s-%d] return wf 4 bit flag\n",__func__, __LINE__); + pFlashControl->Buf[0] = 0x0a; + } + else { + printk ("[%s-%d] return wf 3 bit flag\n",__func__, __LINE__); + pFlashControl->Buf[0] = 0x0f; + } + } + break; + + default: + return FALSE; + }; + return RetCode; +} + + +static void fake_s1d13522_setwfmode(EPDFB_DC *pDC,int iWFMode) +{ + DBG_MSG("%s(),wf.mode=%d\n",__FUNCTION__,iWFMode); + if(pDC->pfnSetWaveformMode) { + pDC->pfnSetWaveformMode(iWFMode); + } + pDC->iWFMode = iWFMode; +} + + +void fake_s1d13522_progress_start(EPDFB_DC *pDC) +{ +#ifdef SHOW_PROGRESS_BAR//[ + int ret; + ret = kernel_thread(PROGRESS_BAR,pDC,CLONE_KERNEL); + if(ret < 0){ + printk("Progress bar thread creat error\n"); + } +#endif //]SHOW_PROGRESS_BAR +} + +void fake_s1d13522_progress_stop(void) +{ + DBG_MSG("====%s()====!\n",__FUNCTION__); + gIsProgessRunning = 0; +} + +/********************************************************* + * + * + *********************************************************/ +EPDFB_DC *fake_s1d13522_initEx3(unsigned char bBitsPerPixel,unsigned char *pbDCBuf, + unsigned short wScrW,unsigned short wScrH,unsigned short wFBW,unsigned short wFBH) +{ + EPDFB_DC *pDC = 0; + +#ifdef _PVI_IOCTL_INTERFACE//[ + pvi_Init(); +#endif//]_PVI_IOCTL_INTERFACE + +#if (_MYCMDLINE_PARSE==1) //[ + fake_s1d13522_parse_epd_cmdline(); +#endif //] + + if(0==glVCOM_uV) { + unsigned short wVCOM_10mV = 0 ; + short sVCOM_10mV; + + wVCOM_10mV = gptHWCFG->m_val.bVCOM_10mV_HiByte<<8|gptHWCFG->m_val.bVCOM_10mV_LoByte; + sVCOM_10mV = (short) wVCOM_10mV; + glVCOM_uV = (long)sVCOM_10mV*10000; + + if(0!=glVCOM_uV) { + printk("%s:VCOM from HWCONFIG 0x%x=>%hd 10mV,%ld uV\n", + __FUNCTION__,wVCOM_10mV,sVCOM_10mV,glVCOM_uV); + } + else { + // Actually ,we don't need to set VCOM if glVCOM_uV=0 + } + } + + gwScrW = wScrW; + gwScrH = wScrH; + //pDC = epdfbdc_create_ex2(wFBW,wFBH,gwScrW,gwScrH,bBitsPerPixel,pbDCBuf,\ + // EPDFB_DC_FLAG_REVERSEDRVDATA|EPDFB_DC_FLAG_SKIPRIGHTPIXEL); + + pDC = epdfbdc_create_ex2(wFBW,wFBH,gwScrW,gwScrH,bBitsPerPixel,pbDCBuf,\ + EPDFB_DC_FLAG_REVERSEDRVDATA); + + //currWidth = _INIT_HSIZE; + //currHeight = _INIT_VSIZE; + if(pDC) { + //ImagePVI = pDC->pbDCbuf; + gptEPD_dc_current = pDC; + } + +#ifdef _QT_SUPPORT//[ + #ifndef _ES_LUTNOWAIT_ //[ + init_timer(&conflict_resolution_timer); + conflict_resolution_timer.function = s1d13521fb_resolve_conflict; + #endif //] + bConflictTimerRunning = FALSE; +#endif //] _QT_SUPPORT + + if(1==gptHWCFG->m_val.bUIStyle) { + gtRotate = epdfb_rotate_90; + } + + //complete_all(&fake13522_inited); + return pDC; +} +EPDFB_DC *fake_s1d13522_initEx2(unsigned char bBitsPerPixel,unsigned char *pbDCBuf, + unsigned short wScrW,unsigned short wScrH) +{ + return fake_s1d13522_initEx3(bBitsPerPixel,pbDCBuf,wScrW,wScrH,wScrW,wScrH); +} +EPDFB_DC *fake_s1d13522_initEx(unsigned char bBitsPerPixel,unsigned char *pbDCBuf) +{ + return fake_s1d13522_initEx2(bBitsPerPixel,pbDCBuf,800,600); +} + + +EPDFB_DC *fake_s1d13522_init(void) +{ + return fake_s1d13522_initEx(4,0); +} + +int fake_s1d13522_release(EPDFB_DC *pDC) +{ + int iRet = 0; + +#ifdef _QT_SUPPORT//[ + #ifndef _ES_LUTNOWAIT_ //[ + del_timer(&conflict_resolution_timer); // KEG 20090825 + #endif //] + bConflictTimerRunning = FALSE; +#endif //] _QT_SUPPORT + + if(pDC) { + epdfbdc_delete(pDC); + gptEPD_dc_current = 0; + } + if(gpbLOGO_vaddr) { + //kermitMemoryRelease(gpbLOGO_vaddr,,); + //gpbLOGO_vaddr = NULL; + } + if(gpbWF_vaddr) { + //gpbWF_vaddr = NULL; + } + pvi_Deinit(); + return iRet; +} + +int fake_s1d13522_display_img(u16 wX,u16 wY,u16 wW,u16 wH,u8 *pbImgBuf, + EPDFB_DC *pDC,int iPixelBits,epdfb_rotate_t I_tRotate) +{ + int iRet = 0; + EPDFB_IMG tEPD_img; + EPDFB_ROTATE_T tRotate; + u16 wTemp ; + unsigned int uiCnt; + int iIsFullScreen = 0; + volatile u8 *pbRealFB = 0; + + + ASSERT(pDC); + + DBG_MSG("%s() : x=%d,y=%d,w=%d,h=%d,r=%d,bit=%d,p=%p,dc.w=%d,dc.h=%d,dc.fb.w+=%d,dc.fb.h+=%d,dc.wfmode=%d\n",__FUNCTION__,\ + wX,wY,wW,wH,I_tRotate,iPixelBits,pbImgBuf, + pDC->dwWidth,pDC->dwHeight,pDC->dwFBWExtra, + pDC->dwFBHExtra,pDC->iWFMode); + + uiCnt = 0 ; + + + + if(0==wW||0==wH) { + DBG_MSG("[%s] skip !!,W or H =0 !!\n",__FUNCTION__); + return -1; + } + + if( 1==pDC->iIsForceWaitUpdateFinished || pDC->iWFMode!=pDC->iLastWFMode ) + { + if(pDC->pfnWaitUpdateComplete) { + pDC->pfnWaitUpdateComplete(); + } + else { + while(pDC->pfnIsUpdating()) { + schedule_timeout(1); + if(printk_ratelimit()) { + printk("EPD updating ... (t=%u,cnt=%lu)\n",jiffies,++uiCnt); + } + } + } + } + + //tEPD_img.dwX = wX&0xfffe; + //tEPD_img.dwY = wY&0xfffe; + + tEPD_img.dwX = wX; + tEPD_img.dwY = wY; + + tEPD_img.dwW = wW; + if(wW&0x1) { + tEPD_img.dwW += 1; + } + else { + } + + tEPD_img.dwH = wH; + + + tEPD_img.pbImgBuf = pbImgBuf; + tEPD_img.bPixelBits = (unsigned char)(iPixelBits); + switch (I_tRotate) { + case epdfb_rotate_0: + tRotate = EPDFB_R_0; + break; + case epdfb_rotate_90: + tRotate = EPDFB_R_90; + break; + case epdfb_rotate_180: + tRotate = EPDFB_R_180; + break; + case epdfb_rotate_270: + tRotate = EPDFB_R_270; + break; + default: + ASSERT(0); + break; + } + + + if(pDC->pfnVcomEnable) { + pDC->pfnVcomEnable(1); + //VCOM_TRUNON_BY_TIMER(20,pDC); + } + else { + } + + if(pDC->pfnSetPartialUpdate) { + #ifdef __KERNEL__//[ + //if( gptHWCFG && 0==gptHWCFG->m_val.bUIStyle ) + if( gptHWCFG ) + { + if(wW * wH == pDC->dwWidth * pDC->dwHeight) { + DBG_MSG("%s : fullscree update!\n",__FUNCTION__); + pDC->pfnSetPartialUpdate(0); + } + else { + pDC->pfnSetPartialUpdate(1); + } + } + #endif //] + } + + + + + if(pDC->pfnPutImg) { + pDC->pfnPutImg(&tEPD_img,tRotate); + } + else { + //epdfbdc_fbimg_normallize(pDC,&tEPD_img); + + epdfbdc_put_fbimg(pDC,&tEPD_img,tRotate); + + epdfbdc_get_rotate_active(pDC,&tEPD_img.dwX,&tEPD_img.dwY, + &tEPD_img.dwW,&tEPD_img.dwH,tRotate); + + if(pDC->pfnSetUpdateRect) { + pDC->pfnSetUpdateRect(tEPD_img.dwX,tEPD_img.dwY,tEPD_img.dwW,tEPD_img.dwH); + } + + if(pDC->pfnGetRealFrameBufEx) { + pbRealFB = (u8 *)pDC->pfnGetRealFrameBufEx(0); + } + else if(pDC->pfnGetRealFrameBuf) { + pbRealFB = (u8 *)pDC->pfnGetRealFrameBuf(); + } + + if(pDC->pbDCbuf==(unsigned char *)pbRealFB) { + } + else + if(pDC->dwFlags&EPDFB_DC_FLAG_FLASHDIRTY) + { + // dirty update ... + unsigned long dwDirtyOffsetEnd,dwDirtyOffsetStart; + + epdfbdc_get_dirty_region(pDC,&dwDirtyOffsetStart,&dwDirtyOffsetEnd); + if(pbRealFB) { + + memcpy((void *)(volatile u32 *)(pbRealFB+dwDirtyOffsetStart),\ + pDC->pbDCbuf+dwDirtyOffsetStart, dwDirtyOffsetEnd-dwDirtyOffsetStart); + + if(pDC->dwFlags&EPDFB_DC_FLAG_OFB_RGB565) { + epdfbdc_dcbuf_to_RGB565(pDC,pbRealFB,(pDC->dwWidth*pDC->dwHeight)>>1); + } + } + } + else + { + // full update ... + //extern unsigned int marvell_logo_800x600_size; + //extern unsigned const char marvell_logo_800x600[] ; + if(pDC->pfnGetRealFrameBufEx) { + pbRealFB = (u8 *)pDC->pfnGetRealFrameBufEx(0); + } + else if(pDC->pfnGetRealFrameBuf) { + pbRealFB = (u8 *)pDC->pfnGetRealFrameBuf(); + } + + if(pbRealFB) { + if(pDC->dwFlags&EPDFB_DC_FLAG_OFB_RGB565) { + epdfbdc_dcbuf_to_RGB565(pDC,pbRealFB,(pDC->dwWidth*pDC->dwHeight)>>1); + } + else { + memcpy((void *)(volatile u32 *)(pbRealFB),\ + pDC->pbDCbuf, pDC->dwDCSize); + + //memcpy((void *)(volatile u32 *)((u8 *)pDC->pfnGetRealFrameBuf()),\ + // marvell_logo_800x600, marvell_logo_800x600_size); + } + } + } + } + + + if(pDC->pfnDispStart) { + pDC->pfnDispStart(1); + } + + if(pDC->pfnVcomEnable) { + pDC->pfnVcomEnable(0); + //VCOM_TRUNOFF_BY_TIMER(20,pDC); + } + else { + } + + pDC->iLastWFMode = pDC->iWFMode; + pDC->iIsForceWaitUpdateFinished = 0; + + return iRet; +} + + +int _Epson_displayCMD(PST_IMAGE_PGM pt,EPDFB_DC *pDC) +{ + int iRet = 0; + u32 newMode ; + u32 currMode ; + int iCurWfBpp; + + GALLEN_DBGLOCAL_BEGIN(); + + ASSERT(pDC->pfnGetWaveformMode); + currMode = pDC->pfnGetWaveformMode(); + ASSERT(pDC->pfnGetWaveformBpp); + iCurWfBpp = pDC->pfnGetWaveformBpp(); + +// setWaveform(par,pt->WaveForm+1); + if(257==pt->WaveForm) { + // WAVEFORM_MODE_AUTO + newMode = pt->WaveForm; + } + else + if (pt->Width*pt->Height == pDC->dwWidth*pDC->dwHeight) { + GALLEN_DBGLOCAL_RUNLOG(2); + ASSERT(pDC->pfnGetWaveformBpp); + if(4==iCurWfBpp) + { + GALLEN_DBGLOCAL_RUNLOG(3); + newMode = 2;// GC16 . + } + else if(3==iCurWfBpp) { + GALLEN_DBGLOCAL_RUNLOG(4); + newMode = 3;// + } + else { + GALLEN_DBGLOCAL_RUNLOG(5); + ASSERT(0); + } + } + else + { + GALLEN_DBGLOCAL_RUNLOG(6); + newMode = pt->WaveForm; + } + + if (newMode != currMode) { + GALLEN_DBGLOCAL_RUNLOG(7); + /* set wafeforms new mode */ + ASSERT(pDC->pfnSetWaveformMode); + fake_s1d13522_setwfmode(pDC,(int)newMode); + } + + fake_s1d13522_display_img(pt->StartX,pt->StartY,pt->Width,pt->Height,\ + pt->Data,pDC,giPixelBits,gtRotate); + GALLEN_DBGLOCAL_END(); + + return iRet; +} + +static EN_EPSON_ERROR_CODE S1D13522_reg_write(u16 wIndex, u16 wValue) +{ + EN_EPSON_ERROR_CODE tRet = _EPSON_ERROR_SUCCESS; + + GALLEN_DBGLOCAL_BEGIN(); + + switch (wIndex) { + case S1D13522_REGADDR_HOSTIFMACONF:GALLEN_DBGLOCAL_RUNLOG(10); + { + unsigned short wRotate; + int iPixelBits; + + GALLEN_DBGLOCAL_RUNLOG(11); + //gwS1d13522_hostifmaconf = wValue; + ConfigPVI.Reg0x140 = wValue; + + + //wRotate = gwS1d13522_hostifmaconf&0x0300; + wRotate = ConfigPVI.Reg0x140&0x0300; + + #if 1 //[ auto rotate by ntx hw config . + if(gptHWCFG) + { + + if(0x0000==wRotate) { + //ASSERT(gptHWCFG); + if(0==gptHWCFG->m_val.bDisplayPanel||3==gptHWCFG->m_val.bDisplayPanel||\ + 6==gptHWCFG->m_val.bDisplayPanel||8==gptHWCFG->m_val.bDisplayPanel||\ + 9==gptHWCFG->m_val.bDisplayPanel) + { + // Left Out EPD Panel ... + GALLEN_DBGLOCAL_RUNLOG(12); + } + else { + // Right Out EPD Panel (E60MT2,E60820,E60822,E60830,E60832,...) + GALLEN_DBGLOCAL_RUNLOG(13); + wRotate = 0x0200;// 0->180 + } + } + else if(0x0200==wRotate) { + ASSERT(gptHWCFG); + if(0==gptHWCFG->m_val.bDisplayPanel||3==gptHWCFG->m_val.bDisplayPanel||\ + 6==gptHWCFG->m_val.bDisplayPanel||8==gptHWCFG->m_val.bDisplayPanel||\ + 9==gptHWCFG->m_val.bDisplayPanel) + { + // Left Out EPD Panel ... + GALLEN_DBGLOCAL_RUNLOG(12); + } + else { + // Right Out EPD Panel (E60MT2,E60820,E60822,E60830,E60832,...) + GALLEN_DBGLOCAL_RUNLOG(13); + wRotate = 0x0000;// 180->0 + } + } + else if(0x0100==wRotate) { + ASSERT(gptHWCFG); + if(0==gptHWCFG->m_val.bDisplayPanel||3==gptHWCFG->m_val.bDisplayPanel||\ + 6==gptHWCFG->m_val.bDisplayPanel||8==gptHWCFG->m_val.bDisplayPanel||\ + 9==gptHWCFG->m_val.bDisplayPanel) + { + // Left Out EPD Panel ... + GALLEN_DBGLOCAL_RUNLOG(24); + wRotate = 0x0300;// 90->270 + } + else { + // Right Out EPD Panel (E60MT2,E60820,E60822,E60830,E60832,...) + GALLEN_DBGLOCAL_RUNLOG(25); + } + } + else if(0x0300==wRotate) { + ASSERT(gptHWCFG); + if(0==gptHWCFG->m_val.bDisplayPanel||3==gptHWCFG->m_val.bDisplayPanel||\ + 6==gptHWCFG->m_val.bDisplayPanel||8==gptHWCFG->m_val.bDisplayPanel||\ + 9==gptHWCFG->m_val.bDisplayPanel) + { + // Left Out EPD Panel ... + GALLEN_DBGLOCAL_RUNLOG(26); + wRotate = 0x0100;// 270->90 + } + else { + // Right Out EPD Panel (E60MT2,E60820,E60822,E60830,E60832,...) + GALLEN_DBGLOCAL_RUNLOG(27); + } + } + } + else { + // without ntx hwconfig . + if(0x0000==wRotate) { + } + else if(0x0200==wRotate) { + } + else if(0x0100==wRotate) { + wRotate = 0x0300;// 90->270 + } + else if(0x0300==wRotate) { + wRotate = 0x0100;// 270->90 + } + } + #endif //] + + wRotate = wRotate >> 8; + + //wRotate = ((gwS1d13522_hostifmaconf>>8) & 0x0003); + + DBG_MSG("%s(%d):rotate change %d=>%d\n",__FILE__,__LINE__,\ + gtRotate,wRotate); + gtRotate = (epdfb_rotate_t)wRotate; + + + if((0x0020==(ConfigPVI.Reg0x140&0x0030))) { + GALLEN_DBGLOCAL_RUNLOG(14); + iPixelBits = 4; + } + else + if((0x0030==(ConfigPVI.Reg0x140&0x0030))) { + GALLEN_DBGLOCAL_RUNLOG(15); + iPixelBits = 8; + } + else + if((0x0010==(ConfigPVI.Reg0x140&0x0030))) { + GALLEN_DBGLOCAL_RUNLOG(16); + iPixelBits = 2; + } + else { + GALLEN_DBGLOCAL_RUNLOG(17); + iPixelBits = 1; + } + giPixelBits = iPixelBits; + }break; + case S1D13522_REGADDR_GENCONF:GALLEN_DBGLOCAL_RUNLOG(18); + { + GALLEN_DBGLOCAL_RUNLOG(19); + gwS1d13522_GenConf = wValue; + + //wRotate = ((gwS1d13522_GenConf>>4) & 0x0003); + }break; + case S1D13522_REGADDR_HOSTMEMPIXSWAP:GALLEN_DBGLOCAL_RUNLOG(20); + { + + gwS1d13522_hostmempixswapconf = wValue; + if(0x0000==(gwS1d13522_hostmempixswapconf&0x0003)) { + GALLEN_DBGLOCAL_RUNLOG(21); + epdfbdc_set_host_dataswap(gptEPD_dc_current,0); + } + else if(0x0003==(gwS1d13522_hostmempixswapconf&0x0003)) { + GALLEN_DBGLOCAL_RUNLOG(22); + epdfbdc_set_host_dataswap(gptEPD_dc_current,1); + } + }break; + default : + WARNING_MSG("%s %d(0x%x) not support !\n",__FUNCTION__,wIndex,wIndex); + GALLEN_DBGLOCAL_RUNLOG(23); + break; + } + + GALLEN_DBGLOCAL_END(); + return tRet; +} + +static u16 S1D13522_reg_read(u16 wIndex) +{ + u16 wRet = 0; + + //DBG_MSG("%s(0x%x)\n",__FUNCTION__,wIndex); + switch (wIndex) { + case S1D13522_REGADDR_HOSTIFMACONF: + wRet = ConfigPVI.Reg0x140; + break; + + case S1D13522_REGADDR_PRODUCTCODE: + wRet = S1D13522_PRODUCTCODE; + break; + + case S1D13522_REGADDR_LUTSTATUS: + ASSERT(gptEPD_dc_current); + ASSERT(gptEPD_dc_current->pfnIsUpdating); + if(gptEPD_dc_current->pfnIsUpdating()) { + wRet = 0xffff; + } + else { + wRet = 0x0000; + } + break; + + case S1D13522_REGADDR_GENCONF: + wRet = gwS1d13522_GenConf; + break; + case S1D13522_REGADDR_HOSTMEMPIXSWAP: + wRet = gwS1d13522_hostmempixswapconf; + break; + + case S1D13522_REGADDR_PUBC: + wRet = ConfigPVI.Reg0x330; + break; + case S1D13522_REGADDR_DSPENGINTRSR: + wRet = gwS1d13522_DspEngIntrRSR; + break; + + default : + WARNING_MSG("%s %d(0x%x) not support !\n",__FUNCTION__,wIndex,wIndex); + break; + } + return wRet; +} + + + +#ifdef _PVI_IOCTL_INTERFACE//[ +////////////////////////////////////////////////////////////// +// +// pvi ioctl interface ... +// + +// pvi ioctl helper functions ... + +static WORD packed_bpp2_to_bbp3_dot3[] = { + (0x0 << 1), + (0x2 << 1), + (0x5 << 1), + (0x7 << 1) +}; + +static WORD packed_bpp2_to_bbp3_dot2[] = { + (0x0 << (1+4)), + (0x2 << (1+4)), + (0x5 << (1+4)), + (0x7 << (1+4)) +}; + +static WORD packed_bpp2_to_bbp3_dot1[] = { + (0x0 << (1+4+4)), + (0x2 << (1+4+4)), + (0x5 << (1+4+4)), + (0x7 << (1+4+4)) +}; + +static WORD packed_bpp2_to_bbp3_dot0[] = { + (0x0 << (1+4+4+4)), + (0x2 << (1+4+4+4)), + (0x5 << (1+4+4+4)), + (0x7 << (1+4+4+4)) +}; + +static WORD packed_bpp1_to_bbp3_dot3[] = { + (0x0 << 1), + (0x7 << 1) +}; + +static WORD packed_bpp1_to_bbp3_dot2[] = { + (0x0 << (1+4)), + (0x7 << (1+4)) +}; + +static WORD packed_bpp1_to_bbp3_dot1[] = { + (0x0 << (1+4+4)), + (0x7 << (1+4+4)) +}; + +static WORD packed_bpp1_to_bbp3_dot0[] = { + (0x0 << (1+4+4+4)), + (0x7 << (1+4+4+4)) +}; + + + +static BOOL SPI_FlashWaitReady(VOID) +{ + return TRUE; +} + +static EN_EPSON_ERROR_CODE PutImage_PVI2EPSON(PBYTE pData, BOOL fPartial, UINT16 WaveForm) +{ +int length, cc; +s1d13521_ioctl_cmd_params CommandParam; +PBYTE pSource; +PUINT16 pDest; +BYTE data; +//EN_EPSON_ERROR_CODE ret; +EN_EPSON_ERROR_CODE ret=_EPSON_ERROR_SUCCESS; +// int save_index=-1; // KEG 20090914 commented out to stop compiler warning + + //DBG_MSG("%s() skip \n",__FUNCTION__); + //return _EPSON_ERROR_SUCCESS; + + #if 0//[ gallen remove . + if(BusWaitForHRDY() != _EPSON_ERROR_SUCCESS) + return _EPSON_ERROR_NOT_READY; + #endif //] + +// Joseph 100714, clear soft reset flag +// ConfigPVI.Reg0x330=BusIssueReadReg(0x330); + ConfigPVI.Reg0x330=BusIssueReadReg(0x330)&0x00FF; + + ret=BusIssueWriteReg(0x330, 0x84);// LUT auto,auto waveform disable ... + if(ret != _EPSON_ERROR_SUCCESS) + { + //DEBUGMSG(_MSG_ERROR, "%s] write 0x330=0x0x84 fail [0x330]=0x%04x !!!!!!!!!!!!\n", __FUNCTION__, BusIssueReadReg(0x330)); + goto _PutImage_PVI2EPSON_Fail; + } + DBG_MSG("%s() : CurRotateMod=0x%x,PastRotateMod=0x%x\n",__FUNCTION__,ConfigPVI.CurRotateMode,ConfigPVI.PastRotateMode); + if(ConfigPVI.PastRotateMode != ConfigPVI.CurRotateMode) + { + epdfb_rotate_t tRotateInPVI_modA[4] = {epdfb_rotate_0,epdfb_rotate_270,epdfb_rotate_180,epdfb_rotate_90}; + CommandParam.param[0]=ConfigPVI.CurRotateMode; + + //ret=BusIssueCmd(INIT_ROTMODE, &CommandParam, 1); + DBG_MSG("%s(%d):rotate change %d=>%d\n",__FILE__,__LINE__,\ + gtRotate,tRotateInPVI_modA[(epdfb_rotate_t)(ConfigPVI.CurRotateMode>>8)]); + + gtRotate = tRotateInPVI_modA[(epdfb_rotate_t)(ConfigPVI.CurRotateMode>>8)]; + //gtRotate = (epdfb_rotate_t)(ConfigPVI.CurRotateMode>>8); + + + ConfigPVI.PastRotateMode=ConfigPVI.CurRotateMode; + } + + + CommandParam.param[0] = (0x2 << 4);// image 4 bits/pixel . + if(fPartial == FALSE) + { + length=(gwScrH*gwScrW) >> 2; + } + else + { + // gallen remove : CommandParam.param[1]=ConfigPVI.StartX; + // gallen remove : CommandParam.param[2]=ConfigPVI.StartY; + // gallen remove : CommandParam.param[3]=ConfigPVI.Width; + // gallen remove : CommandParam.param[4]=ConfigPVI.Height; + // gallen remove : ret=BusIssueCmd(LD_IMG_AREA, &CommandParam, 5); + length=(ConfigPVI.Width*ConfigPVI.Height) >> 2; + } + if(ret != _EPSON_ERROR_SUCCESS) + { + //DEBUGMSG(_MSG_ERROR, "%s] LD_IMG/LD_IMG_AREA fail !!!!!!!!!!!!\n", __FUNCTION__); + goto _PutImage_PVI2EPSON_Fail; + } + pSource=(PBYTE) pData; + + + DBG_MSG("%s() : ConfigPVI.Deepth=0x%x,ReverseGrade=0x%x\n",\ + __FUNCTION__,ConfigPVI.Deepth,ConfigPVI.ReverseGrade); + + #if 0//[ gallen remove 20110315: no more hardware check ,this is fake hardware . + //pDest=(PUINT16) s1d13521fb_info.VirtualFramebufferAddr; + pDest=(PUINT16) gbTemp4BitsFBA; + + if(ConfigPVI.Deepth == 2) { + for(cc=length; cc--; pDest++, pSource++) + { + data=*pSource ^ ConfigPVI.ReverseGrade; + *pDest=(packed_bpp2_to_bbp3_dot0[(data >> 0) & 3] + | packed_bpp2_to_bbp3_dot1[(data >> 2) & 3] + | packed_bpp2_to_bbp3_dot2[(data >> 4) & 3] + | packed_bpp2_to_bbp3_dot3[(data >> 6) & 3]); + } + } + else { + for(cc=length >> 1; cc--; pDest++, pSource++) + { + data=*pSource ^ ConfigPVI.ReverseGrade; + *pDest=(packed_bpp1_to_bbp3_dot0[(data >> 0) & 1] + | packed_bpp1_to_bbp3_dot1[(data >> 1) & 1] + | packed_bpp1_to_bbp3_dot2[(data >> 2) & 1] + | packed_bpp1_to_bbp3_dot3[(data >> 3) & 1]); + pDest++; + *pDest=(packed_bpp1_to_bbp3_dot0[(data >> 4) & 1] + | packed_bpp1_to_bbp3_dot1[(data >> 5) & 1] + | packed_bpp1_to_bbp3_dot2[(data >> 6) & 1] + | packed_bpp1_to_bbp3_dot3[(data >> 7) & 1]); + } + } + + ret=BusIssueWriteRegBuf(0x154, (PUINT16) s1d13521fb_info.VirtualFramebufferAddr, length); + if(ret != _EPSON_ERROR_SUCCESS) + { + //DEBUGMSG(_MSG_ERROR, "%s] Download image fail !!!!!!!!!!!!\n", __FUNCTION__); + goto _PutImage_PVI2EPSON_Fail; + } + ret=BusIssueCmd(LD_IMG_END, &CommandParam, 0); + if(ret != _EPSON_ERROR_SUCCESS) + { + //DEBUGMSG(_MSG_ERROR, "%s] LD_IMG_END fail !!!!!!!!!!!!\n", __FUNCTION__); + goto _PutImage_PVI2EPSON_Fail; + } + // FRANKLIN ret=WaitDPEIdle(); + ret=WaitDPEIdle(); + EpsonReady(); // Joseph 100222 + if(fPartial == false) { + //mdelay (1000); + _msleep(1000); + } + else { + //mdelay (100); + _msleep(100); + } + + CommandParam.param[0] = (WaveForm << 8); + if(fPartial == FALSE) + { +#if (defined(__5_INCH__) || defined(__6_INCH__)) +printk ("[%s-%d] ............\n",__func__,__LINE__); + ret=BusIssueCmd(UPD_FULL, &CommandParam, 1); + if (g_is_s1d13522) { + BusIssueCmd(WAIT_DSPE_TRG,&CommandParam,0); + BusIssueCmd(WAIT_DSPE_FREND,&CommandParam,0); + } +#elif defined(__8_INCH__) +#elif defined(__9D7_INCH__) + CommandParam.param[1]=(1200-800)/2; + CommandParam.param[2]=(826-600)/2; + CommandParam.param[3]=800; + CommandParam.param[4]=600; + ret=BusIssueCmd(UPD_PART_AREA, &CommandParam, 5); + if (g_is_s1d13522) + BusIssueCmd(WAIT_DSPE_TRG,&CommandParam,0); +#endif + } + else + { + CommandParam.param[1]=ConfigPVI.StartX; + CommandParam.param[2]=ConfigPVI.StartY; + CommandParam.param[3]=ConfigPVI.Width; + CommandParam.param[4]=ConfigPVI.Height; + ret=BusIssueCmd(UPD_PART_AREA, &CommandParam, 5); + if (g_is_s1d13522) + BusIssueCmd(WAIT_DSPE_TRG,&CommandParam,0); + } + if(ret != _EPSON_ERROR_SUCCESS) + { + //DEBUGMSG(_MSG_ERROR, "%s] UPD_FULL fail !!!!!!!!!!!!!!\n", __FUNCTION__); + goto _PutImage_PVI2EPSON_Fail; + } + BusUpdateThermal(); // adon, 2009-04-09 + ret=BusIssueWriteReg(0x330, ConfigPVI.Reg0x330); + if(ret != _EPSON_ERROR_SUCCESS) + { + //DEBUGMSG(_MSG_ERROR, "%s] update 0x330 fail !!!!!!!!!!!!!!\n", __FUNCTION__); + goto _PutImage_PVI2EPSON_Fail; + } + #else //][ gallen + //gptEPD_dc_current->pfnIsUpdating + printk("%s() : w.f=%d,partial=%d\n",__FUNCTION__,WaveForm,fPartial); + fake_s1d13522_setwfmode(gptEPD_dc_current,(int)WaveForm); + if(gptEPD_dc_current->pfnSetPartialUpdate) { + //DBG_MSG("[%s] %s update mode\n",__FUNCTION__,fPartial?"partial":"full"); + gptEPD_dc_current->pfnSetPartialUpdate(fPartial); + } + + ret=BusIssueWriteReg(0x330, ConfigPVI.Reg0x330); + + #if 0 + + fake_s1d13522_display_img(ConfigPVI.StartX,ConfigPVI.StartY, + ConfigPVI.Width,ConfigPVI.Height,gbTemp4BitsFBA, + gptEPD_dc_current,4,gtRotate); + #else + fake_s1d13522_display_img(ConfigPVI.StartX,ConfigPVI.StartY, + ConfigPVI.Width,ConfigPVI.Height,pSource, + gptEPD_dc_current,ConfigPVI.Deepth,gtRotate); + #endif + + #endif //] gallen remove 20110315. + + +_PutImage_PVI2EPSON_Fail: + ret=BusIssueWriteReg(0x310,0xa600); + ret=BusIssueWriteReg(0x312, 0x0e); + return ret; +} + +static BOOL SPI_FlashWriteData(BYTE Data) +{ + BusIssueWriteReg(_EPSON_REG_SPI_WRITE_DATA, (Data | _EPSON_REG_SPI_WRITE_DATA_ENABLE)); + return SPI_FlashWaitReady(); +} +static BOOL SPI_FlashWriteDummy(VOID) +{ + BusIssueWriteReg(_EPSON_REG_SPI_WRITE_DATA, 0x00); + return SPI_FlashWaitReady(); +} + +///// +#if 1 +static void pvi_DoTimerForcedRefresh(ULONG dummy) +{ + if(ConfigPVI.fNormalMode == FALSE) + return; + pvi_ioctl_ForcedRefresh(NULL); + //pvi_refersh_timer.expires = jiffies+HZ*ConfigPVI.AutoRefreshTimer; + //add_timer(); + mod_timer(&pvi_refersh_timer,jiffies+HZ*ConfigPVI.AutoRefreshTimer); +} + +int pvi_Init(VOID) +{ + int temp,ret; + + if(gpbLOGO_vaddr&&gdwLOGO_size>=_PVI_IMAGE_SIZE) { + ImagePVI = gpbLOGO_vaddr; + } + else { + ImagePVI = kmalloc(_PVI_IMAGE_SIZE,GFP_KERNEL); + ASSERT(ImagePVI); + } + + ConfigPVI.fAutoRefreshMode=FALSE; + init_timer(&pvi_refersh_timer); + pvi_refersh_timer.function=pvi_DoTimerForcedRefresh; + pvi_ioctl_Reset(NULL); + ConfigPVI.fNormalMode=true; + +/* Joseph 100222 + //Battery Low is GPG1 + temp=__raw_readl(S3C2410_GPGCON); + temp&=~(0x03<<2); + temp|=0x01<<2; + //20090803 david use to check GPG1 + printk("GPGCON1=%x",temp); + __raw_writel(temp, S3C2410_GPGCON); +*/ + return 0; +} + +void pvi_Deinit(VOID) +{ + if(ConfigPVI.fAutoRefreshMode != FALSE) { + del_timer_sync(&pvi_refersh_timer); + } + + if(ImagePVI&&ImagePVI!=gpbLOGO_vaddr) { + kfree(ImagePVI);ImagePVI=0; + } +} +#endif // FRANKLIN + +int pvi_ioctl_DisplayImage(PTDisplayCommand puDisplayCommand) +{ + //char buf[128]; + + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + ASSERT(gptEPD_dc_current); + ASSERT(gptEPD_dc_current->pfnGetWaveformBpp); + + // gallen add 20110323 [ + ConfigPVI.StartX = 0; + ConfigPVI.StartY = 0; + if( ((ConfigPVI.CurRotateMode)==0x00) || + ((ConfigPVI.CurRotateMode)==0x02<<8) ) + { + ConfigPVI.Width = gptEPD_dc_current->dwWidth; + ConfigPVI.Height = gptEPD_dc_current->dwHeight; + } + else { + ConfigPVI.Width = gptEPD_dc_current->dwHeight; + ConfigPVI.Height = gptEPD_dc_current->dwWidth; + } + + DBG_MSG("%s(): rotate=0x%x,w=%d,h=%d\n",__FUNCTION__,\ + ConfigPVI.CurRotateMode,ConfigPVI.Width,ConfigPVI.Height); + if (ConfigPVI.Width & 1) { + ConfigPVI.Width += 1; + } + //] + + if(4==gptEPD_dc_current->pfnGetWaveformBpp()){//this is 4bit + printk("4bit waveform mark!\n"); + PutImage_PVI2EPSON(ImagePVI, FALSE, WF_MODE_GU); + }else{ + printk("3bit waveform mark!\n"); + PutImage_PVI2EPSON(ImagePVI, FALSE, WF_MODE_GC); + } + + return 0; +} + +int pvi_ioctl_NewImage(PTDisplayCommand puDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + //DEBUGMSG(_MSG_SEND_COMMAND, "dc_NewImage"); + dbgENTER(); + if(!gptHWCFG||0==gptHWCFG->m_val.bDisplayResolution) { + memcpy(ImagePVI, puDisplayCommand->Data, _PVI_IMAGE_SIZE_800x600); + } + else { + memcpy(ImagePVI, puDisplayCommand->Data, _PVI_IMAGE_SIZE); + } + dbgLEAVE(); + return 0; +} + +int pvi_ioctl_StopNewImage(PTDisplayCommand puDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + return 0; +} + + +int pvi_ioctl_PartialImage(PTDisplayCommand puDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + ConfigPVI.StartX=(puDisplayCommand->Data [0] << 8)+puDisplayCommand->Data[1]; + ConfigPVI.StartY=(puDisplayCommand->Data [2] << 8)+puDisplayCommand->Data[3]; + ConfigPVI.Width =((puDisplayCommand->Data [4] << 8)+puDisplayCommand->Data[5])+1-ConfigPVI.StartX; + ConfigPVI.Height=((puDisplayCommand->Data [6] << 8)+puDisplayCommand->Data[7])+1-ConfigPVI.StartY; + //DEBUGMSG(_MSG_SEND_COMMAND, "X=%u ", ConfigPVI.StartX); + //DEBUGMSG(_MSG_SEND_COMMAND, "Y=%u ", ConfigPVI.StartY); + //DEBUGMSG(_MSG_SEND_COMMAND, "W=%u ", ConfigPVI.Width); + //DEBUGMSG(_MSG_SEND_COMMAND, "H=%u", ConfigPVI.Height); + ///////////////////////////////////////////////////////// + // align pexil to modula 4 + //DEBUGMSG(_MSG_SEND_COMMAND, " [dc_PartialImage end 1] %u", ConfigPVI.Height*((ConfigPVI.Width+3) & ~3)/4); + memcpy(ImagePVI, &puDisplayCommand->Data[8], ConfigPVI.Height*((ConfigPVI.Width+3) & ~3)/4); + //DEBUGMSG(_MSG_SEND_COMMAND, " [dc_PartialImage end 2] "); + return 0; +} + +int pvi_ioctl_DisplayPartial(PTDisplayCommand puDisplayCommand) +{ +// printk("pvi_ioctl_DisplayPartial()\n"); + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + //DEBUGMSG(1, "zzzzz\n"); +#if 0 + BusIssueWriteReg(0x360, 0x1001); + BusIssueWriteReg(0x364, 0x1001); + PutImage_PVI2EPSON(ImagePVI, TRUE, WF_MODE_MU); + //DEBUGMSG(1, " [0x360]=0x%04x\n", BusIssueReadReg(0x360)); + //DEBUGMSG(1, " [0x364]=0x%04x\n", BusIssueReadReg(0x364)); + //DEBUGMSG(1, " [0x368]=0x%04x\n", BusIssueReadReg(0x368)); + //DEBUGMSG(1, " [0x36c]=0x%04x\n", BusIssueReadReg(0x36c)); +#else + PutImage_PVI2EPSON(ImagePVI, TRUE, WF_MODE_MU); +#endif + return 0; +} + +int pvi_ioctl_DisplayPartialGU(PTDisplayCommand puDisplayCommand) +{ +// printk("pvi_ioctl_DisplayPartial()\n"); + if(ConfigPVI.fNormalMode == FALSE) + return -EFAULT; + //DEBUGMSG(1, "yyyyy\n"); + //if (!g_is_s1d13522) + { + BusIssueWriteReg(0x360, 0x1001); + BusIssueWriteReg(0x364, 0x1001); + } + PutImage_PVI2EPSON(ImagePVI, TRUE, WF_MODE_GU); +#ifndef _S1D13522_ //Joseph 100629 + //DEBUGMSG(1, " [0x360]=0x%04x\n", BusIssueReadReg(0x360)); + //DEBUGMSG(1, " [0x364]=0x%04x\n", BusIssueReadReg(0x364)); + //DEBUGMSG(1, " [0x368]=0x%04x\n", BusIssueReadReg(0x368)); + //DEBUGMSG(1, " [0x36c]=0x%04x\n", BusIssueReadReg(0x36c)); +#endif + return 0; +} + +int pvi_ioctl_Reset(PTDisplayCommand puDisplayCommand) +{ + ConfigPVI.PastRotateMode = 0xffff; + ConfigPVI.CurRotateMode=(_INIT_ROTMODE << 8); + ConfigPVI.ReverseGrade=0; + ConfigPVI.Deepth=2; + ConfigPVI.fNormalMode=FALSE; + ConfigPVI.AutoRefreshTimer=100; + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + if(ConfigPVI.fAutoRefreshMode != FALSE) { + del_timer_sync(&pvi_refersh_timer); + } + //printk("\n==== [%s] 0x0a=0x%04x====\n", __FUNCTION__, BusIssueReadReg(0x0a)); + return 0; +} + +int pvi_ioctl_SetDepth(PTDisplayCommand puDisplayCommand) +{ +int RetCode=0; + printk("%s()\n",__FUNCTION__); + + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + if((puDisplayCommand->Data[0] == 0) || (puDisplayCommand->Data[0] == 2)) { + ConfigPVI.Deepth=puDisplayCommand->Data[0]; + } + else { + ERR_MSG("%s() : parameter value error (0x%x)!\n",__FUNCTION__,puDisplayCommand->Data[0]); + RetCode=-EFAULT; + } + return RetCode; +} + +int pvi_ioctl_EraseDisplay(PTDisplayCommand pDisplayCommand) +{ +BYTE Color=0xff; + + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + switch(pDisplayCommand->Data[0]) { + case 0: + Color=0x00; + break; + case 1: + Color=0xff; + break; + case 2: + Color=0x55; + break; + case 3: + Color=0xaa; + break; + }; + memset(ImagePVI, Color, _PVI_IMAGE_SIZE); + PutImage_PVI2EPSON(ImagePVI, FALSE, WF_MODE_GC); +// PutImage_PVI2EPSON(ImagePVI, FALSE, WF_MODE_MU); + return 0; +} + +int pvi_ioctl_Rotate(PTDisplayCommand puDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + DBG_MSG("%s():puDisplayCommand->Data[0]=0x%02x \n",__FUNCTION__,puDisplayCommand->Data[0]); + + switch(puDisplayCommand->Data[0]) { + case 1: + ConfigPVI.CurRotateMode=3<<8; + break; + case 2: + ConfigPVI.CurRotateMode=2<<8; + break; + case 3: + ConfigPVI.CurRotateMode=1<<8; + break; + case 0: + ConfigPVI.CurRotateMode=0<<8; + break; + }; + return 0; +} + +int pvi_ioctl_Positive(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + ConfigPVI.ReverseGrade=0x0000; + return 0; +} + +int pvi_ioctl_Negative(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + ConfigPVI.ReverseGrade=0xffff; + return 0; +} + +int pvi_ioctl_GoToNormal(PTDisplayCommand pDisplayCommand) +{ + DBG_MSG("%s()\n",__FUNCTION__); + + //BusIssueWriteRegX(0x0a, BusIssueReadReg(0x0a) | 0x1000); + // printk("pvi_ioctl_GoToNormal\n"); + //BusIssueWriteReg(0x16, 0); + // printk("pvi_ioctl_GoToNormal\n"); + pvi_ioctl_AutoRefreshOn(NULL); + ConfigPVI.fNormalMode=TRUE; + //BusIssueCmd(RUN_SYS, NULL, 0); + return 0; +} +int pvi_ioctl_GoToSleep(PTDisplayCommand pDisplayCommand) +{ + DBG_MSG("\n===== %s() =====\n",__FUNCTION__); + + ConfigPVI.fNormalMode=FALSE; + if(ConfigPVI.fAutoRefreshMode != FALSE) { + del_timer_sync(&pvi_refersh_timer); + } +//printk("+pvi_ioctl_GoToSleep\n"); + //BusIssueCmd(SLP, NULL, 0); +//printk("-pvi_ioctl_GoToSleep\n"); + return 0; +} + +int pvi_ioctl_GoToStandBy(PTDisplayCommand pDisplayCommand) +{ + DBG_MSG("\n====== %s() =======\n",__FUNCTION__); + ConfigPVI.fNormalMode=FALSE; + //BusIssueCmd(STBY, NULL, 0); + return 0; +} + +int pvi_ioctl_WriteToFlash(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + switch(ConfigPVI.StepSPI) { + case _STEP_NOR_BYPASS: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_NOR_BYPASS "); + if((pDisplayCommand->Data[0] == 0x00) && (pDisplayCommand->Data[1] == 0x0a) && (pDisplayCommand->Data[2] == 0xaa) && (pDisplayCommand->Data[3] == 0xaa)) + ConfigPVI.StepSPI=_STEP_NOR_WRITE_AA_INTO_555; + break; + + case _STEP_NOR_WRITE_AA_INTO_555: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_NOR_WRITE_AA_INTO_555 "); + if((pDisplayCommand->Data[0] == 0x00) && (pDisplayCommand->Data[1] == 0x05) && (pDisplayCommand->Data[2] == 0x55) && (pDisplayCommand->Data[3] == 0x55)) + ConfigPVI.StepSPI=_STEP_NOR_WRITE_55_INTO_2AA; + else + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + break; + + case _STEP_NOR_WRITE_55_INTO_2AA: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_NOR_WRITE_55_INTO_2AA "); + if((pDisplayCommand->Data[0] == 0x00) && (pDisplayCommand->Data[1] == 0x0a) && (pDisplayCommand->Data[2] == 0xaa)) + { + if(pDisplayCommand->Data[3] == 0x90) + ConfigPVI.StepSPI=_STEP_NOR_CMD_STATUS; + else if(pDisplayCommand->Data[3] == 0xa0) + ConfigPVI.StepSPI=_STEP_NOR_CMD_PROGRAM; + else if(pDisplayCommand->Data[3] == 0x80) + ConfigPVI.StepSPI=_STEP_NOR_CMD_ERASE; + else + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + } + else + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + break; + + case _STEP_NOR_CMD_PROGRAM: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_NOR_CMD_PROGRAM "); + //spi_FlashWriteByte(pDisplayCommand); + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + break; + + case _STEP_NOR_CMD_ERASE: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_NOR_CMD_ERASE "); + if((pDisplayCommand->Data[0] == 0x00) && (pDisplayCommand->Data[1] == 0x0a) && (pDisplayCommand->Data[2] == 0xaa) && (pDisplayCommand->Data[3] == 0xaa)) + ConfigPVI.StepSPI=_STEP_NOR_CMD_ERASE_WRITE_AA_INTO_555; + else + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + break; + + case _STEP_NOR_CMD_ERASE_WRITE_AA_INTO_555: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_NOR_CMD_ERASE_WRITE_AA_INTO_555 "); + if((pDisplayCommand->Data[0] == 0x00) && (pDisplayCommand->Data[1] == 0x05) && (pDisplayCommand->Data[2] == 0x55) && (pDisplayCommand->Data[3] == 0x55)) + ConfigPVI.StepSPI=_STEP_NOR_CMD_ERASE_WRITE_55_INTO_2AA; + else + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + break; + + case _STEP_NOR_CMD_ERASE_WRITE_55_INTO_2AA: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_NOR_CMD_ERASE_WRITE_55_INTO_2AA "); + if((pDisplayCommand->Data[0] == 0x00) && (pDisplayCommand->Data[1] == 0x0a) && (pDisplayCommand->Data[2] == 0xaa) && (pDisplayCommand->Data[3] == 0x10)) { + //spi_FlashEraseChip(); + } + else if(pDisplayCommand->Data[3] == 0x30) { + //spi_FlashEraseSector(pDisplayCommand); + } + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + break; + + case _STEP_NOR_CMD_STATUS: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_NOR_CMD_STATUS "); + if(pDisplayCommand->Data[3] == 0xf0) + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + break; + + default: + //DEBUGMSG(_MSG_SPI_WRITE, "_STEP_UNDEFINE "); + ConfigPVI.StepSPI=_STEP_NOR_BYPASS; + }; + return 0; +} + +int pvi_ioctl_ReadFromFlash(PTDisplayCommand pDisplayCommand) +{ +int RetCode=-EFAULT; + + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + if(ConfigPVI.StepSPI == _STEP_NOR_CMD_STATUS) + { + RetCode=0; + if((pDisplayCommand->Data[2] & 3) == 0) + pDisplayCommand->Data[0]=0x3e; + else if((pDisplayCommand->Data[2] & 3) == 1) + pDisplayCommand->Data[0]=0xc2; + else + pDisplayCommand->Data[0]=0; + return RetCode; + } + #if 0// gallen disable . + SPI_FlashSwitchToHost(); + if(SPI_FlashWriteData(0x03) == FALSE) + goto _pvi_ioctl_ReadFromFlashFail; + if(SPI_FlashWriteData(pDisplayCommand->Data[0]) == FALSE) + goto _pvi_ioctl_ReadFromFlashFail; + if(SPI_FlashWriteData(pDisplayCommand->Data[1]) == FALSE) + goto _pvi_ioctl_ReadFromFlashFail; + if(SPI_FlashWriteData(pDisplayCommand->Data[2]) == FALSE) + goto _pvi_ioctl_ReadFromFlashFail; + if(SPI_FlashWriteDummy() == FALSE) + goto _pvi_ioctl_ReadFromFlashFail; + pDisplayCommand->Data[0]=(BYTE) BusIssueReadReg(_EPSON_REG_SPI_READ_DATA); + //DEBUGMSG(_MSG_SPI_READ, "ReadData=0x%02x", pDisplayCommand->Data[0]); + #endif + RetCode=0; +_pvi_ioctl_ReadFromFlashFail: + //SPI_FlashSwitchToDPE(); + return RetCode; +} + +int pvi_ioctl_Init(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + +// BusIssueInitDisplay(); + return 0; +} + +int pvi_ioctl_AutoRefreshOn(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + if((ConfigPVI.AutoRefreshTimer != 0) && (ConfigPVI.fAutoRefreshMode == FALSE)) + { + //pvi_refersh_timer.expires = jiffies+HZ*ConfigPVI.AutoRefreshTimer; + //add_timer(&pvi_refersh_timer); + mod_timer(&pvi_refersh_timer,jiffies+HZ*ConfigPVI.AutoRefreshTimer); + ConfigPVI.fAutoRefreshMode=TRUE; + } + return 0; +} + +int pvi_ioctl_AutoRefreshOff(PTDisplayCommand pDisplayCommand) +{ + del_timer_sync(&pvi_refersh_timer); + ConfigPVI.fAutoRefreshMode=FALSE; + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + return 0; +} + +int pvi_ioctl_SetRefresh(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + ConfigPVI.AutoRefreshTimer=pDisplayCommand->Data[0]; + return 0; +} + +int pvi_ioctl_ForcedRefresh(PTDisplayCommand pDisplayCommand) +{ +#ifdef __TURN_ON_FORCE_DISPLAY__ +s1d13521_ioctl_cmd_params CommandParam; + + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + CommandParam.param[0]=((WF_MODE_GC << 8) | 0x4000); + BusIssueCmd(UPD_FULL, &CommandParam, 1); +// pvi_ioctl_AutoRefreshOn (NULL); +#endif + return 0; +} + +int pvi_ioctl_GetRefresh(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + pDisplayCommand->Data[0]=ConfigPVI.AutoRefreshTimer; + return 0; +} + +int pvi_ioctl_RestoreImage(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + return 0; +} + +int pvi_ioctl_ControllerVersion(PTDisplayCommand puDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + puDisplayCommand->Data[0]=0x06; + return 0; +} + +int pvi_ioctl_SoftwareVersion(PTDisplayCommand puDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + puDisplayCommand->Data[0]=0x06; + return 0; +} + +int pvi_ioctl_DisplaySize(PTDisplayCommand puDisplayCommand) +{ + puDisplayCommand->Data[0]=0x22; + return 0; +} + +int pvi_ioctl_GetStatus(PTDisplayCommand puDisplayCommand) +{ + puDisplayCommand->Data[0]=0x06; + if(ConfigPVI.fNormalMode == FALSE) + puDisplayCommand->Data[0]|=_BIT0; + if(ConfigPVI.Deepth != 0) + puDisplayCommand->Data[0]|=_BIT4; + return 0; +} + +int pvi_ioctl_Temperature(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + return 0; +} + +int pvi_ioctl_WriteRegister(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + return 0; +} + +int pvi_ioctl_ReadRegister(PTDisplayCommand pDisplayCommand) +{ + if(ConfigPVI.fNormalMode == FALSE) { + ERR_MSG("[WARNING] %s() : not in normal mode .\n",__FUNCTION__); + return -EFAULT; + } + + return 0; +} + + + +int pvi_SwitchCommand(PTDisplayCommand pDisplayCommand) + //printk("ubStatus =%d\n",PHMSCD_USB_STATUS_FAIL); +{ + static int partial=0; + +int ReturnCode=0; + + GALLEN_DBGLOCAL_MUTEBEGIN_EX(48); + DBG0_MSG("%s,cmd=0x%02x,wr=%u,rd=%u\n",__FUNCTION__,pDisplayCommand->Command, + pDisplayCommand->BytesToWrite,pDisplayCommand->BytesToRead); + + + //lock_kernel(); + switch(pDisplayCommand->Command) { + case dc_NewImage:GALLEN_DBGLOCAL_RUNLOG(0); + ReturnCode=pvi_ioctl_NewImage(pDisplayCommand); +//============================================================================= +// Arron + partial = 0; +//------------------------------------------------------------------------------ + break; + + case dc_StopNewImage:GALLEN_DBGLOCAL_RUNLOG(1); + ReturnCode=pvi_ioctl_StopNewImage(pDisplayCommand); + break; + + case dc_DisplayImage:GALLEN_DBGLOCAL_RUNLOG(2); + //printk("David: dc_DisplayImage\n"); +//============================================================================== + if(partial==0) + { + GALLEN_DBGLOCAL_RUNLOG(3); + ReturnCode=pvi_ioctl_DisplayImage(pDisplayCommand); + }else{ + GALLEN_DBGLOCAL_RUNLOG(4); + ReturnCode=pvi_ioctl_DisplayPartial(pDisplayCommand); + } + //kay add battery detect + // FRANKLIN s3c2410_adc_read_once(); + //check HARDWARE BATTERY LOW PIN ( GPF4 ) +//#if defined (__COOKIE__) + //printk("Check Cookie Battery State.\n"); + + //pvi_CheckHwBatteryLow(); +//#endif +//------------------------------------------------------------------------------ + break; + + case dc_PartialImage:GALLEN_DBGLOCAL_RUNLOG(5); + // FRANKLIN s3c2410_adc_read_once(); + ReturnCode=pvi_ioctl_PartialImage(pDisplayCommand); +//============================================================================= +// Arron + partial = 1; +//------------------------------------------------------------------------------ + break; + + case dc_DisplayPartial:GALLEN_DBGLOCAL_RUNLOG(6); + ReturnCode=pvi_ioctl_DisplayPartial(pDisplayCommand); + break; + + case dc_DisplayPartialGU:GALLEN_DBGLOCAL_RUNLOG(7); + ReturnCode=pvi_ioctl_DisplayPartialGU(pDisplayCommand); + break; + + case dc_Reset:GALLEN_DBGLOCAL_RUNLOG(8); + ReturnCode=pvi_ioctl_Reset(pDisplayCommand); + break; + + case dc_SetDepth:GALLEN_DBGLOCAL_RUNLOG(9); + ReturnCode=pvi_ioctl_SetDepth(pDisplayCommand); + break; + + case dc_EraseDisplay:GALLEN_DBGLOCAL_RUNLOG(10); + ReturnCode=pvi_ioctl_EraseDisplay(pDisplayCommand); + break; + + case dc_Rotate:GALLEN_DBGLOCAL_RUNLOG(11); + ReturnCode=pvi_ioctl_Rotate(pDisplayCommand); + break; + + case dc_Positive:GALLEN_DBGLOCAL_RUNLOG(12); + ReturnCode=pvi_ioctl_Positive(pDisplayCommand); + break; + + case dc_Negative:GALLEN_DBGLOCAL_RUNLOG(13); + ReturnCode=pvi_ioctl_Negative(pDisplayCommand); + break; + + case dc_GoToNormal:GALLEN_DBGLOCAL_RUNLOG(14); + ReturnCode=pvi_ioctl_GoToNormal(pDisplayCommand); + break; + + case dc_GoToSleep:GALLEN_DBGLOCAL_RUNLOG(15); + ReturnCode=pvi_ioctl_GoToSleep(pDisplayCommand); + break; + + case dc_GoToStandBy:GALLEN_DBGLOCAL_RUNLOG(16); + ReturnCode=pvi_ioctl_GoToStandBy(pDisplayCommand); + break; + + case dc_WriteToFlash:GALLEN_DBGLOCAL_RUNLOG(17); + ReturnCode=pvi_ioctl_WriteToFlash(pDisplayCommand); + break; + + case dc_ReadFromFlash:GALLEN_DBGLOCAL_RUNLOG(18); + ReturnCode=pvi_ioctl_ReadFromFlash(pDisplayCommand); + break; + + case dc_Init:GALLEN_DBGLOCAL_RUNLOG(19); + ReturnCode=pvi_ioctl_Init(pDisplayCommand); + break; + + case dc_AutoRefreshOn:GALLEN_DBGLOCAL_RUNLOG(20); + ReturnCode=pvi_ioctl_AutoRefreshOn(pDisplayCommand); + break; + + case dc_AutoRefreshOff:GALLEN_DBGLOCAL_RUNLOG(21); + ReturnCode=pvi_ioctl_AutoRefreshOff(pDisplayCommand); + break; + + case dc_SetRefresh:GALLEN_DBGLOCAL_RUNLOG(22); + ReturnCode=pvi_ioctl_SetRefresh(pDisplayCommand); + break; + + case dc_ForcedRefresh:GALLEN_DBGLOCAL_RUNLOG(23); + ReturnCode=pvi_ioctl_ForcedRefresh(pDisplayCommand); + break; + + case dc_GetRefresh:GALLEN_DBGLOCAL_RUNLOG(24); + ReturnCode=pvi_ioctl_GetRefresh(pDisplayCommand); + break; + + case dc_RestoreImage:GALLEN_DBGLOCAL_RUNLOG(25); + ReturnCode=pvi_ioctl_RestoreImage(pDisplayCommand); + break; + + case dc_ControllerVersion:GALLEN_DBGLOCAL_RUNLOG(26); + ReturnCode=pvi_ioctl_ControllerVersion(pDisplayCommand); + break; + + case dc_SoftwareVersion:GALLEN_DBGLOCAL_RUNLOG(27); + ReturnCode=pvi_ioctl_SoftwareVersion(pDisplayCommand); + break; + + case dc_DisplaySize:GALLEN_DBGLOCAL_RUNLOG(28); + ReturnCode=pvi_ioctl_DisplaySize(pDisplayCommand); + break; + + case dc_GetStatus:GALLEN_DBGLOCAL_RUNLOG(29); + ReturnCode=pvi_ioctl_GetStatus(pDisplayCommand); + break; + + case dc_Temperature:GALLEN_DBGLOCAL_RUNLOG(30); + ReturnCode=pvi_ioctl_Temperature(pDisplayCommand); + break; + + case dc_WriteRegister:GALLEN_DBGLOCAL_RUNLOG(31); + ReturnCode=pvi_ioctl_WriteRegister(pDisplayCommand); + break; + + case dc_ReadRegister:GALLEN_DBGLOCAL_RUNLOG(32); + ReturnCode=pvi_ioctl_ReadRegister(pDisplayCommand); + break; + + default:GALLEN_DBGLOCAL_RUNLOG(33); + ReturnCode=-EFAULT; + } + //unlock_kernel(); + GALLEN_DBGLOCAL_END(); + return ReturnCode; +} + +#endif //] _PVI_IOCTL_INTERFACE + + + +#ifdef _QT_SUPPORT //[ + + +static void s1d13521fb_resolve_conflict(unsigned long dummy) +{ +#ifndef _ES_LUTNOWAIT_ + struct s1d13521_ioctl_hwc ioctl_hwc; + s1d13521_ioctl_cmd_params cmd_params; + extern unsigned long gLastUpdParm0; + + if (BusIssueReadReg(0x0a) & 0x20) + { +// printk("[%s-%d] \n",__func__,__LINE__); + if (resolve_conflict_step) + mod_timer(&conflict_resolution_timer, jiffies+(HZ/10)); + else + mod_timer(&conflict_resolution_timer, jiffies+(HZ/4)); + return; + } +// printk("[%s-%d] \n",__func__,__LINE__); + switch (resolve_conflict_step) { + case 0: + // check the register to see if we have a conflict + ioctl_hwc.addr = 0x33A; + ioctl_hwc.value = BusIssueReadReg(ioctl_hwc.addr); + + if (ioctl_hwc.value & 0x0080) // if we have a conflict + { + resolve_conflict_step++; + printk("evidentpoint: In %s, conflict found\n", __FUNCTION__); + // wait for frame end + BusIssueCmd(WAIT_DSPE_FREND, &cmd_params, 0); + + + mod_timer(&conflict_resolution_timer, jiffies+(HZ/10)); + } + break; + case 1: + cmd_params.param[0] = gLastUpdParm0; + // printk("[%s-%d] update parm 0 0x%04X\n", __FUNCTION__, __LINE__,cmd_params.param[0]); + BusIssueCmd(UPD_PART,&cmd_params,1); + mod_timer(&conflict_resolution_timer, jiffies+(HZ/10)); + resolve_conflict_step++; + break; + case 2: + BusIssueWriteReg(0x33A, 0x80); + printk("[%s-%d] done with 0x%04X\n", __FUNCTION__, __LINE__,gLastUpdParm0); + default: + resolve_conflict_step = 0; + bConflictTimerRunning = FALSE; + break; + } +#else + bConflictTimerRunning = FALSE; +#endif +} + +static int ImagePGM(PST_IMAGE_PGM pPGM,EPDFB_DC *pDC) +{ + int ret=-1; + + DBG_MSG("[%s](%d) : w=%ld,h=%ld,r=%d,data=0x%p\n",__FUNCTION__,__LINE__,\ + pPGM->Width,pPGM->Height,gtRotate,pPGM->Data); + giPixelBits = 8; + ret = fake_s1d13522_display_img(0,0,pPGM->Width,pPGM->Height,pPGM->Data, + pDC,8,gtRotate) ; + + return ret; +} + + + +// ES modify for 4 bbp support. +// Qookie usage +static int Epson_LoadImageArea(PTloadImageArea area,EPDFB_DC *pDC) +{ + unsigned char *pbArea; + unsigned long dwAreaWidthBytes; + unsigned long dwFBSize; + + unsigned long dwStoreDCFlags = 0; + int iIsSetDCFlags = 0; + int iShifBits; + + ASSERT(pDC); + ASSERT(area); + + GALLEN_DBGLOCAL_MUTEBEGIN(); + GALLEN_DBGLOCAL_PRINTMSG("%s():x=%d,y=%d,w=%d,h=%d,mode=%d,cmd=%x\n",__FUNCTION__, + area->XStart,area->YStart,area->Width,area->Height,area->mode,area->cmd); + + if(pDC->pfnSetWaveformMode) { + pDC->pfnSetWaveformMode(area->mode); + } + + if(UPD_FULL==area->cmd) { + pDC->pfnSetPartialUpdate(0); + } + else { + pDC->pfnSetPartialUpdate(1); + } + + + if(pDC->pfnGetRealFrameBufEx) { + GALLEN_DBGLOCAL_RUNLOG(0); + + if(!(pDC->dwFlags&EPDFB_DC_FLAG_REVERSEINPDATA)) { + dwStoreDCFlags = pDC->dwFlags; + iIsSetDCFlags = 1; + pDC->dwFlags |= EPDFB_DC_FLAG_REVERSEINPDATA; + } + + pbArea = pDC->pfnGetRealFrameBufEx(&dwFBSize); + GALLEN_DBGLOCAL_PRINTMSG("fbsize=%u,depth=%d bit\n",dwFBSize,giPixelBits); + + switch(giPixelBits) { + default :GALLEN_DBGLOCAL_RUNLOG(1); + ERR_MSG("%s:depth %d not supported !\n",__FUNCTION__,giPixelBits); + GALLEN_DBGLOCAL_ESC(); + return -1; + case 8:GALLEN_DBGLOCAL_RUNLOG(2); + iShifBits = 0; + break; + case 4:GALLEN_DBGLOCAL_RUNLOG(3); + iShifBits = 1; + break; + case 2:GALLEN_DBGLOCAL_RUNLOG(4); + iShifBits = 2; + break; + case 1:GALLEN_DBGLOCAL_RUNLOG(5); + iShifBits = 3; + break; + } + + pbArea += dwFBSize ; + + #if 1 + + #if 1 //[ + { + EPDFB_DC *ptEPD_dcimg ; + + ptEPD_dcimg = epdfbdc_create_ex(pDC->dwHeight,pDC->dwWidth,\ + giPixelBits,pbArea,pDC->dwFlags); + + epdfbdc_put_dcimg(pDC,ptEPD_dcimg,gtRotate,area->XStart,area->YStart, + area->Width,area->Height,area->XStart,area->YStart); + + epdfbdc_delete(ptEPD_dcimg); + } + #else //][! + { + + int i; + unsigned char *pbRow; + EPDFB_IMG tImg; + + tImg.dwX = area->XStart; + tImg.dwW = area->Width; + tImg.dwH = 1; + tImg.bPixelBits = (unsigned char)giPixelBits; + for(i=0;i<area->Height;i++) + { + pbRow = pbArea + (area->XStart>>iShifBits)+((area->YStart+i)*(pDC->dwHeight>>iShifBits)); + + tImg.dwY = area->YStart+i; + tImg.pbImgBuf = pbRow; + + epdfbdc_put_fbimg(pDC,&tImg,gtRotate); + //fake_s1d13522_display_img(area->XStart,area->YStart+i,area->Width,1,\ + // pbRow,pDC,giPixelBits,gtRotate); + } + + + } + #endif//] + + if(pDC->pfnDispStart) { + if(pDC->pfnVcomEnable) { + pDC->pfnVcomEnable(1); + } + + if(pDC->pfnSetUpdateRect) { + unsigned long dwX,dwY,dwW,dwH; + dwX = area->XStart; + dwY = area->YStart; + dwW = area->Width; + dwH = area->Height; + epdfbdc_get_rotate_active(pDC,&dwX,&dwY,&dwW,&dwH,gtRotate); + + pDC->pfnSetUpdateRect(dwX,dwY,dwW,dwH); + } + + pDC->pfnDispStart(1); + + if(pDC->pfnVcomEnable) { + pDC->pfnVcomEnable(0); + } + } + + #else + switch(gtRotate) { + case epdfb_rotate_0: + case epdfb_rotate_180: + case epdfb_rotate_90: + case epdfb_rotate_270: + fake_s1d13522_display_img(0,0,pDC->dwHeight,pDC->dwWidth,\ + pbArea,pDC,giPixelBits,gtRotate); + break; + } + #endif + + if(iIsSetDCFlags) { + pDC->dwFlags = dwStoreDCFlags; + } + } + + GALLEN_DBGLOCAL_END(); + + return 0; +} +#endif //] _QT_SUPPORT + + +int fake_s1d13522_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + GALLEN_DBGLOCAL_BEGIN(); + if(gptHWCFG) { + if(1==gptHWCFG->m_val.bDisplayResolution) { + switch(gtRotate) + { + case epdfb_rotate_0: + case epdfb_rotate_180: + var->xres = 1024; + var->yres = 758; + break; + case epdfb_rotate_90: + case epdfb_rotate_270: + var->xres = 758; + var->yres = 1024; + break; + } + } + else if(2==gptHWCFG->m_val.bDisplayResolution) { + switch(gtRotate) + { + case epdfb_rotate_0: + case epdfb_rotate_180: + var->xres = 1024; + var->yres = 768; + break; + case epdfb_rotate_90: + case epdfb_rotate_270: + var->xres = 768; + var->yres = 1024; + break; + } + } + else if(3==gptHWCFG->m_val.bDisplayResolution) { + switch(gtRotate) + { + case epdfb_rotate_0: + case epdfb_rotate_180: + var->xres = 1440; + var->yres = 1080; + break; + case epdfb_rotate_90: + case epdfb_rotate_270: + var->xres = 1080; + var->yres = 1440; + break; + } + } + else if(5==gptHWCFG->m_val.bDisplayResolution) { + switch(gtRotate) + { + case epdfb_rotate_0: + case epdfb_rotate_180: + var->xres = 1448; + var->yres = 1072; + break; + case epdfb_rotate_90: + case epdfb_rotate_270: + var->xres = 1072; + var->yres = 1448; + break; + } + } + else if(6==gptHWCFG->m_val.bDisplayResolution) { + switch(gtRotate) + { + case epdfb_rotate_0: + case epdfb_rotate_180: + var->xres = 1600; + var->yres = 1200; + break; + case epdfb_rotate_90: + case epdfb_rotate_270: + var->xres = 1200; + var->yres = 1600; + break; + } + } + else { + switch(gtRotate) + { + case epdfb_rotate_0: + case epdfb_rotate_180: + var->xres = 800; + var->yres = 600; + break; + case epdfb_rotate_90: + case epdfb_rotate_270: + var->xres = 600; + var->yres = 800; + break; + } + } + } + else { + switch(gtRotate) + { + case epdfb_rotate_0: + case epdfb_rotate_180: + var->xres = 800; + var->yres = 600; + break; + case epdfb_rotate_90: + case epdfb_rotate_270: + var->xres = 600; + var->yres = 800; + break; + } + } + + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + var->xoffset = var->yoffset = 0; + var->bits_per_pixel = S1D_DISPLAY_BPP; + var->grayscale = 1; + var->nonstd = 0; /* != 0 Non standard pixel format */ + var->activate = FB_ACTIVATE_NOW; /* see FB_ACTIVATE_* */ + var->height = -1; /* height of picture in mm */ + var->width = -1; /* width of picture in mm */ + var->accel_flags = 0; /* acceleration flags (hints */ + var->pixclock = S1D_DISPLAY_PCLK; + var->right_margin = 0; + var->lower_margin = 0; + var->hsync_len = 0; + var->vsync_len = 0; + var->left_margin = 0; + var->upper_margin = 0; + var->sync = 0; + var->vmode = FB_VMODE_NONINTERLACED; + var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0; + var->transp.offset = var->transp.length = var->transp.msb_right = 0; + + //s1d13521fb_fix.visual = FB_VISUAL_TRUECOLOR; + + switch (info->var.bits_per_pixel) + { + case 1:GALLEN_DBGLOCAL_RUNLOG(0); + case 2:GALLEN_DBGLOCAL_RUNLOG(1); + case 4:GALLEN_DBGLOCAL_RUNLOG(2); + case 5:GALLEN_DBGLOCAL_RUNLOG(3); + case 8:GALLEN_DBGLOCAL_RUNLOG(4); + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = S1D_DISPLAY_BPP; + break; + + default: + printk(KERN_WARNING "%dbpp is not supported.\n", + info->var.bits_per_pixel); + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + + GALLEN_DBGLOCAL_END(); + return 0; +} + +// gallen 2011/05/24 copy from s1d13521base.c . +int fake_s1d13522_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ +#if 1 //[ + + printk("%s(): r.no=%02Xh (r,g,b,t)=(%04X,%04X,%04X,%04X)h\n", + __FUNCTION__, regno, red, green, blue, transp); + return 0; // ensure that setcolreg does nothing for now +#else //][ + // Make the first 16 LUT entries available to the console + + if (info->var.bits_per_pixel == 8 && s1d13521fb_fix.visual == FB_VISUAL_TRUECOLOR) + { + if (regno < 16) + { + // G= 30%R + 59%G + 11%B + unsigned gray = (red*30 + green*59 + blue*11)/100; + gray = (gray>>8) & 0xFF; + +#ifdef CONFIG_FB_EPSON_BLACK_AND_WHITE + if (gray != 0) { + gray = 0xFF; + } +#endif + + // invert: black on white + gray = 0xFF-gray; + ((u32*)info->pseudo_palette)[regno] = gray; + + dbg_info("%s(): regno=%02Xh red=%04Xh green=%04Xh blue=%04Xh transp=%04Xh ->gray=%02Xh\n", + __FUNCTION__, regno, red, green, blue, transp,gray); + } + } + else { + return 1; + } + + return 0; +#endif //] +} + + +int32_t fake_s1d13522_ioctl(unsigned int cmd,unsigned long arg,EPDFB_DC *pDC) +{ + + int32_t ret=0; + struct s1d13521_ioctl_hwc ioctl_hwc; + + + GALLEN_DBGLOCAL_MUTEBEGIN_EX(64); + GALLEN_DBGLOCAL_PRINTMSG("%s(%x,%x)\n",__FUNCTION__,cmd,arg); + + #ifdef SHOW_PROGRESS_BAR//[ + gIsProgessRunning = 0; + #endif //] SHOW_PROGRESS_BAR + + switch(cmd) + { + case S1D13521_DISPLAY:GALLEN_DBGLOCAL_RUNLOG(0); + { + + int iBpp = 4; + ST_IMAGE_PGM *pt = (ST_IMAGE_PGM *)arg; + + + pt = kmalloc(sizeof(*pt),GFP_KERNEL); + if(pt) { + unsigned int dwImgStructSize; + GALLEN_DBGLOCAL_RUNLOG(1); + + if(gptHWCFG&&0!=gptHWCFG->m_val.bDisplayResolution) { + switch (gptHWCFG->m_val.bDisplayResolution) { + case 1:// 1024x758 . + dwImgStructSize=((sizeof(unsigned int)*6)+(1024*758)); + break; + case 3:// 1440x1080 + dwImgStructSize=((sizeof(unsigned int)*6)+(1440*1080)); + break; + case 5:// 1448x1072 + dwImgStructSize=((sizeof(unsigned int)*6)+(1448*1072)); + break; + case 6:// 1600x1200 + dwImgStructSize=((sizeof(unsigned int)*6)+(1600*1200)); + break; + default : + dwImgStructSize=sizeof(ST_IMAGE_PGM); + break; + } + copy_from_user(pt,(ST_IMAGE_PGM *)arg,dwImgStructSize); + GALLEN_DBGLOCAL_PRINTMSG("DISPLAY INFO : X=%ld,Y=%ld,W=%ld,H=%ld\n\twaveform=%ld,LUT no.=%ld,datap=%p,size=%ld\n", + pt->StartX,pt->StartY,pt->Width,pt->Height, + pt->WaveForm,pt->LUT_NO,pt->Data,dwImgStructSize); + } + else { + copy_from_user(pt,(ST_IMAGE_PGM_800x600 *)arg,sizeof(ST_IMAGE_PGM_800x600)); + GALLEN_DBGLOCAL_PRINTMSG("DISPLAY INFO : X=%ld,Y=%ld,W=%ld,H=%ld\n\twaveform=%ld,LUT no.=%ld,datap=%p,size=%ld\n", + pt->StartX,pt->StartY,pt->Width,pt->Height, + pt->WaveForm,pt->LUT_NO,pt->Data,sizeof(ST_IMAGE_PGM_800x600)); + } + + + + _Epson_displayCMD(pt,pDC); + + + #if 0//(GDEBUG>0)//[ + { + int iShowCnt = 100,i; + DBG_MSG("data={{{\n\t"); + for(i=0;i<iShowCnt;i++) { + DBG_MSG("0x02,",pt->Data[i]); + } + DBG_MSG("\n}}}\n"); + } + #endif //] + + /* + //fr.pointer = ; + if((ConfigPVI.Reg0x140&0x0030)==0x0030) { + GALLEN_DBGLOCAL_RUNLOG(0); + iBpp = 8; + fr.offset = 0 ; + fr.count = pt->Width * pt->Height * iBpp / 8; + par->board->write_gray8(fr.offset,pt->Data,fr.count); + } + else { + GALLEN_DBGLOCAL_RUNLOG(1); + iBpp = 4; + fr.offset = 0 ; + fr.count = pt->Width * pt->Height * iBpp / 8; + par->board->write(fr.offset,pt->Data,fr.count); + } + */ + + kfree(pt); + } + else { + ERR_MSG("%s(%d):memory not enough !!\n",__func__,__LINE__); + GALLEN_DBGLOCAL_RUNLOG(8); + ret = -ENOMEM; + } + + + } + break; + + case S1D13521_REGWRITE:GALLEN_DBGLOCAL_RUNLOG(9); + { + struct s1d13521_ioctl_hwc *pIoctl_hwc = &ioctl_hwc; + + if (copy_from_user(&ioctl_hwc, arg, sizeof(ioctl_hwc))) { + GALLEN_DBGLOCAL_ESC(); + return -EFAULT; + } + + GALLEN_DBGLOCAL_PRINTMSG("REG[%x] <== 0x%04x\n",pIoctl_hwc->addr,pIoctl_hwc->value); + S1D13522_reg_write(pIoctl_hwc->addr,pIoctl_hwc->value); + } + break; + + case S1D13521_REGREAD:GALLEN_DBGLOCAL_RUNLOG(10); + { + struct s1d13521_ioctl_hwc *pIoctl_hwc = &ioctl_hwc; + + //DBG0_MSG("--- REGREAD 1----\n"); + + if (copy_from_user(&ioctl_hwc, arg, sizeof(ioctl_hwc))) { + GALLEN_DBGLOCAL_ESC(); + return -EFAULT; + } + //DBG0_MSG("--- REGREAD 2,addr=0x%x----\n",pIoctl_hwc->addr); + pIoctl_hwc->value = S1D13522_reg_read(pIoctl_hwc->addr); + if (copy_to_user(arg,&ioctl_hwc,sizeof(ioctl_hwc))) { + GALLEN_DBGLOCAL_ESC(); + return -EFAULT; + } + GALLEN_DBGLOCAL_PRINTMSG("REG[%x] ==> 0x%04x\n",pIoctl_hwc->addr,pIoctl_hwc->value); + } + break; + + case S1D13521_FLASH:GALLEN_DBGLOCAL_RUNLOG(11); + { + //S1D13532_FLASH_PACKAGE tFlashPack ; + PS1D13532_FLASH_PACKAGE pFlashPack=(PS1D13532_FLASH_PACKAGE)arg; + + //if (copy_from_user(pFlashPack, arg, sizeof(tFlashPack))) { + // GALLEN_DBGLOCAL_ESC(); + // return -EFAULT; + //} + + GALLEN_DBGLOCAL_PRINTMSG("Cmd=0x%x,StartAddr=0x%08x,DataLength=%lu\n,Buf=0x%02x,0x%02x,0x%02x,0x%02x\n", + pFlashPack->Command,pFlashPack->StartAddr,pFlashPack->DataLength, + pFlashPack->Buf[0],pFlashPack->Buf[1],pFlashPack->Buf[2],pFlashPack->Buf[3]); + BusIssueFlashOperation(pFlashPack,pDC); + } + break; + + case S1D13521_SETDEPTH:GALLEN_DBGLOCAL_RUNLOG(12); + { + s1d13521_ioctl_cmd_params cmd_params ,*pcmd_params = &cmd_params; + + if (copy_from_user(pcmd_params, arg, sizeof(cmd_params))) { + GALLEN_DBGLOCAL_ESC(); + return -EFAULT; + } + + GALLEN_DBGLOCAL_PRINTMSG("S1D13521 SETDEPTH param[0]=%x\n",pcmd_params->param[0]); + } + break; + + case S1D13521_LOAD_AREA:GALLEN_DBGLOCAL_RUNLOG(13); + { + int status = Epson_LoadImageArea((PTloadImageArea) arg,pDC); + + #ifndef _ES_LUTNOWAIT_//[ + // start a timer if there is not one already running. + + while (resolve_conflict_step) { + msleep (100); + } + if (bConflictTimerRunning) { + GALLEN_DBGLOCAL_RUNLOG(14); + // printk("evidentpoint: Pushing the conflict resolution timer forward\n"); + mod_timer(&conflict_resolution_timer, jiffies+(HZ/4)); + } else { + GALLEN_DBGLOCAL_RUNLOG(15); + // printk("evidentpoint: Starting the conflict resolution timer\n"); + bConflictTimerRunning = TRUE; + conflict_resolution_timer.expires = jiffies+(HZ/4); + add_timer(&conflict_resolution_timer); + } + #endif //] _ES_LUTNOWAIT_ + GALLEN_DBGLOCAL_ESC(); + return status; + // end KEG 20090814 + + } + break; + case S1D13521_PGM:GALLEN_DBGLOCAL_RUNLOG(16); + ImagePGM((PST_IMAGE_PGM) arg,pDC); + GALLEN_DBGLOCAL_ESC(); + return 0; + + case S1D13521_WAIT_DSPE_TRG:GALLEN_DBGLOCAL_RUNLOG(18); + GALLEN_DBGLOCAL_ESC(); + return 0; + case S1D13521_WAIT_DSPE_FREND:GALLEN_DBGLOCAL_RUNLOG(19); + pDC->iIsForceWaitUpdateFinished=1; + GALLEN_DBGLOCAL_ESC(); + return 0; + + #ifdef _PVI_IOCTL_INTERFACE //[ + + case CMD_SendCommand:GALLEN_DBGLOCAL_RUNLOG(17); + { + ret = pvi_SwitchCommand((PTDisplayCommand) arg); + } + break; + #endif //] _PVI_IOCTL_INTERFACE + + case EPDC_PWR_CTRL:GALLEN_DBGLOCAL_RUNLOG(20); + { + int iPwrCtrlCmd=*((int*)arg); + DBG_MSG("EPDC_PWR_CTRL %d\n",iPwrCtrlCmd); + switch(iPwrCtrlCmd) { + case EPDC_AUTOPWR_INTERVAL_MAX:// set auto off interval max . + case EPDC_AUTOPWR_INTERVAL_NORMAL:// set auto off interval mormal . + if(pDC->pfnPwrAutoOffIntervalMax) { + pDC->pfnPwrAutoOffIntervalMax(EPDC_AUTOPWR_INTERVAL_MAX==iPwrCtrlCmd?1:0); + ret = 0; + } + break; + + case EPDC_PWR_ON: + case EPDC_PWR_OFF: + if(pDC->pfnPwrOnOff) { + pDC->pfnPwrOnOff(EPDC_PWR_ON==iPwrCtrlCmd?1:0); + ret = 0; + } + break; + + case EPDC_VCOM_ON: + case EPDC_VCOM_OFF: + if(pDC->pfnVcomEnable) { + pDC->pfnVcomEnable(EPDC_VCOM_ON==iPwrCtrlCmd?1:0); + ret = 0; + } + break; + + case EPDC_AUTOOFF_ENABLE: + case EPDC_AUTOOFF_DISABLE: + if(pDC->pfnAutoOffEnable) { + pDC->pfnAutoOffEnable(EPDC_AUTOOFF_ENABLE==iPwrCtrlCmd?1:0); + ret = 0; + } + break; + } + } + break; + + case EPDC_VCOM_SET:GALLEN_DBGLOCAL_RUNLOG(21); + { + int iVCOM_set_val=(int)arg; + if(pDC->pfnSetVCOM) { + ret = pDC->pfnSetVCOM(iVCOM_set_val)>=0?0:-ENOTTY; + } + else { + ret = -ENOTTY; + } + } + break; + + case EPDC_VCOM_SET_TO_FLASH:GALLEN_DBGLOCAL_RUNLOG(22); + { + int iVCOM_set_val=(int)arg; + if(pDC->pfnSetVCOMToFlash) { + ret = pDC->pfnSetVCOMToFlash(iVCOM_set_val)>=0?0:-ENOTTY; + } + else { + ret = -ENOTTY; + } + } + break; + + case EPDC_VCOM_GET:GALLEN_DBGLOCAL_RUNLOG(23); + { + int iVCOM_get_val; + if(pDC->pfnGetVCOM) { + pDC->pfnGetVCOM(&iVCOM_get_val); + copy_to_user(arg,&iVCOM_get_val,sizeof(iVCOM_get_val)); + } + else { + ret = -ENOTTY; + } + } + break; + + default : + ERR_MSG("[fake_21d13522] %s() : unsupported cmd (0x%x)\n",__FUNCTION__,cmd); + ret = -ENOTTY; + break; + } + + + GALLEN_DBGLOCAL_END(); + return ret; +} + +int fb_capture_ex(EPDFB_DC *pDC,int iSrcImgX,int iSrcImgY,int iSrcImgW,int iSrcImgH, + int iBitsTo,EPDFB_ROTATE_T I_tRotateDegree,char *pszFileName, + unsigned long I_dwCapTotal) +{ + EPDFB_DC *ptDC_Capture; + char cfnbufA[256]; +// unsigned long _FBw,_FBh; + unsigned long _w,_h,_x,_y; + static unsigned long gdwCaptureCnt=0; +#ifdef OUTPUT_SNAPSHOT_IMGFILE//[ + const unsigned long dwCaptureTotals=OUTPUT_SNAPSHOT_IMGFILE; +#else//][!OUTPUT_SNAPSHOT_IMGFILE + const unsigned long dwCaptureTotals=I_dwCapTotal; +#endif //]OUTPUT_SNAPSHOT_IMGFILE + int iCaptureIDX; + + + ASSERT(pDC); + + if(0==pszFileName) { + return -1; + } + + if('\0'==pszFileName[0]) { + return -2; + } + + switch(I_tRotateDegree) + { + default: + case EPDFB_R_0: + case EPDFB_R_180: + _w = (unsigned long)iSrcImgW; + _h = (unsigned long)iSrcImgH; + //_FBw = _w + pDC->dwFBWExtra; + //_FBh = _h + pDC->dwFBHExtra; + _x = (unsigned long)iSrcImgX; + _y = (unsigned long)iSrcImgY; + break; + case EPDFB_R_90: + case EPDFB_R_270: + _w = (unsigned long)iSrcImgW; + _h = (unsigned long)iSrcImgH; + //_FBh = _w + pDC->dwFBWExtra; + //_FBw = _h + pDC->dwFBHExtra; + _x = (unsigned long)iSrcImgX; + _y = (unsigned long)iSrcImgY; + break; + } + + iCaptureIDX = gdwCaptureCnt++%dwCaptureTotals; + + ptDC_Capture = epdfbdc_create_ex2 ( _w,_h,_w,_h,iBitsTo,0,pDC->dwFlags); + epdfbdc_put_dcimg(ptDC_Capture,pDC,I_tRotateDegree,_x,_y,_w,_h,0,0); + + sprintf(cfnbufA,"%s_%ux%u-%d_%dx-%dy-%dx%d.raw%d", + pszFileName,pDC->dwWidth,pDC->dwHeight,iCaptureIDX, + _x,_y,_w,_h,iBitsTo); + + printk("write raw img %d/%d -> \"%s\" ,%d bits,%u bytes,w=%u,h=%u\n", + iCaptureIDX,dwCaptureTotals,cfnbufA,iBitsTo,ptDC_Capture->dwDCSize,_w,_h); + + write_file_ex(cfnbufA,ptDC_Capture->pbDCbuf, ptDC_Capture->dwDCSize) ; + + if(8==iBitsTo) { + printk("printf \"P5\\n%u %u\\n255\\n\"> %s.pgm \n",_w,_h,cfnbufA); + printk("cat \"%s\">> %s.pgm \n",cfnbufA,cfnbufA); + } + else if(16 == iBitsTo) { + printk("rawtoppm %u %u %s > %s.ppm \n",_w,_h,cfnbufA,cfnbufA); + } + else { + } + + epdfbdc_delete(ptDC_Capture); + + if(gdwCaptureCnt>=dwCaptureTotals) { + return 1; + } + else { + return 0; + } +} + +#ifdef SHOW_PROGRESS_BAR //[ + +static VOID PROGRESS_BAR(EPDFB_DC *pDC) +{ + int i; + + int startX = _PROGRESS_BAR_X_; + int startY = _PROGRESS_BAR_Y_; + int icons = _PROGRESS_BAR_ICONS_; + const int width = 16; + const int height = 16; + int space = 20; + unsigned long dwDrawDataSize ; + unsigned char *pbDrawData; + epdfb_rotate_t L_tRotate ; + + if(!gptHWCFG) { + WARNING_MSG("%s(%d): %s() skip without ntx hwconfig !\n", + __FILE__,__LINE__,__FUNCTION__); + return ; + } + + ASSERT(pDC); + //gIsProgessRunning = 1; + DBG_MSG("%s() ,progresscnt=%d\n",__FUNCTION__,gIsProgessRunning); + + icons = gptHWCFG->m_val.bProgressCnts; + startX = (int)(gptHWCFG->m_val.bProgressXHiByte<<8|gptHWCFG->m_val.bProgressXLoByte); + startY = (int)(gptHWCFG->m_val.bProgressYHiByte<<8|gptHWCFG->m_val.bProgressYLoByte); + + if (! icons) { + ERR_MSG("[%s-%d] No progess ...\n",__func__,__LINE__); + return; + } + else { + DBG_MSG("progress icons=%d,x=%d,y=%d\n",icons,startX,startY); + } + + + dwDrawDataSize = width*height; + pbDrawData = kmalloc(dwDrawDataSize,GFP_KERNEL); + ASSERT(pbDrawData); + + + + + if(pDC->pfnWaitUpdateComplete) { + pDC->pfnWaitUpdateComplete(); + } + else { + while(pDC->pfnIsUpdating()) { + //DBG0_MSG("%s(%d):wait for update done .\n"); + schedule(); + } + } + + msleep(500); + + fake_s1d13522_setwfmode(pDC,1); + pDC->pfnSetPartialUpdate(1); + + if(0==gptHWCFG->m_val.bDisplayPanel||3==gptHWCFG->m_val.bDisplayPanel||\ + 6==gptHWCFG->m_val.bDisplayPanel||8==gptHWCFG->m_val.bDisplayPanel) + { + L_tRotate = epdfb_rotate_270; + } + else { + L_tRotate = epdfb_rotate_90; + } + //DBG0_MSG("pbDrawData=%p,size=%u,rotate=%d\n",pbDrawData,dwDrawDataSize,L_tRotate); + + + + while (gIsProgessRunning) { + for(i=0;i<icons && gIsProgessRunning;){ + //DBG0_MSG("%s[%d]: rotate=%d\n",__FUNCTION__,__LINE__,L_tRotate); + if(pDC->pfnIsUpdating()) { + DBG_MSG("%s[%d]: wait update done ...\n",__FUNCTION__,__LINE__); + schedule(); + continue; + } + else { + //fake_s1d13522_setwfmode(pDC,1); + memset(pbDrawData , 0 , dwDrawDataSize); + fake_s1d13522_display_img(startX+(space*i),startY,width,height,\ + pbDrawData,pDC,4,L_tRotate); + } + sleep_on_timeout(&progress_WaitQueue,HZ/2); + i++; + } + for(i=0;i<icons && gIsProgessRunning;){ + //DBG0_MSG("%s[%d]: rotate=%d\n",__FUNCTION__,__LINE__,L_tRotate); + if(pDC->pfnIsUpdating()) { + DBG_MSG("%s[%d]: wait update done ...\n",__FUNCTION__,__LINE__); + schedule(); + continue; + } + else { + //fake_s1d13522_setwfmode(pDC,1); + memset(pbDrawData , 0xFF , dwDrawDataSize); + fake_s1d13522_display_img(startX+(space*i)+1,startY+1,\ + width-2,height-2,pbDrawData,pDC,4,L_tRotate); + } + sleep_on_timeout(&progress_WaitQueue,HZ/2); + i++; + } + } + kfree(pbDrawData); +} +void fb_capture(EPDFB_DC *pDC,int iBitsTo,EPDFB_ROTATE_T I_tRotateDegree,char *pszFileName) +{ + fb_capture_ex(pDC,0,0,pDC->dwWidth,pDC->dwHeight, + iBitsTo,I_tRotateDegree,pszFileName,1); +} +#endif //] SHOW_PROGRESS_BAR + diff --git a/drivers/video/mxc/fake_s1d13522.h b/drivers/video/mxc/fake_s1d13522.h new file mode 100644 index 00000000..f9ec721e --- /dev/null +++ b/drivers/video/mxc/fake_s1d13522.h @@ -0,0 +1,67 @@ +#ifndef fake_s1d13522_h //[ +#define fake_s1d13522_h + +#include <linux/kernel.h> +#include "epdfb_dc.h" + + +//#define OUTPUT_SNAPSHOT_IMGFILE 16 + + +typedef enum { + epdfb_rotate_0=0, + epdfb_rotate_90=1, + epdfb_rotate_180=2, + epdfb_rotate_270=3, +} epdfb_rotate_t; + + + +extern volatile unsigned char *gpbLOGO_vaddr; +extern volatile unsigned char *gpbLOGO_paddr; +extern volatile unsigned long gdwLOGO_size; + +extern volatile unsigned char *gpbWF_vaddr; +extern volatile unsigned char *gpbWF_paddr; +extern volatile unsigned long gdwWF_size; + +extern volatile long glVCOM_uV; + + +EPDFB_DC *fake_s1d13522_init(void); +EPDFB_DC *fake_s1d13522_initEx(unsigned char bBitsPerPixel,unsigned char *pbDCBuf); +int fake_s1d13522_release(EPDFB_DC *pDC); +EPDFB_DC *fake_s1d13522_initEx2(unsigned char bBitsPerPixel,unsigned char *pbDCBuf, + unsigned short wScrW,unsigned short wScrH); +EPDFB_DC *fake_s1d13522_initEx3(unsigned char bBitsPerPixel,unsigned char *pbDCBuf, + unsigned short wScrW,unsigned short wScrH,unsigned short wFBW,unsigned short wFBH); + +int32_t fake_s1d13522_ioctl(unsigned int cmd,unsigned long arg,EPDFB_DC *pDC); +void fake_s1d13522_progress_start(EPDFB_DC *pDC); + +void fake_s1d13522_progress_stop(void); + +void fake_s1d13522_parse_epd_cmdline(void); + +int fake_s1d13522_display_img(u16 wX,u16 wY,u16 wW,u16 wH,u8 *pbImgBuf, + EPDFB_DC *pDC,int iPixelBits,epdfb_rotate_t I_tRotate); + + +// fb_check_var() helper ... +int fake_s1d13522_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +// gallen 2011/05/24 copy from s1d13521base.c . +int fake_s1d13522_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info); + + +int fake_s1d13522_write_file_ex2(char *filename, char *data,unsigned long dwDataSize,unsigned long dwSeekSize); + + +int fb_capture_ex(EPDFB_DC *pDC,int iSrcImgX,int iSrcImgY,int iSrcImgW,int iSrcImgH, + int iBitsTo,EPDFB_ROTATE_T I_tRotateDegree,char *pszFileName, + unsigned long I_dwCapTotal); + +void fb_capture(EPDFB_DC *pDC,int iBitsTo,EPDFB_ROTATE_T I_tRotateDegree,char *pszFileName); + +int fake_s1d13522_is_logo_bypss(void); +#endif //]fake_s1d13522_h diff --git a/drivers/video/mxc/ldb.c b/drivers/video/mxc/ldb.c new file mode 100644 index 00000000..fabc2529 --- /dev/null +++ b/drivers/video/mxc/ldb.c @@ -0,0 +1,974 @@ +/* + * Copyright (C) 2012-2013 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @file mxc_ldb.c + * + * @brief This file contains the LDB driver device interface and fops + * functions. + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <linux/spinlock.h> +#include <linux/fsl_devices.h> +#include <mach/hardware.h> +#include <mach/clock.h> +#include "mxc_dispdrv.h" + +#define DISPDRV_LDB "ldb" + +#define LDB_BGREF_RMODE_MASK 0x00008000 +#define LDB_BGREF_RMODE_INT 0x00008000 +#define LDB_BGREF_RMODE_EXT 0x0 + +#define LDB_DI1_VS_POL_MASK 0x00000400 +#define LDB_DI1_VS_POL_ACT_LOW 0x00000400 +#define LDB_DI1_VS_POL_ACT_HIGH 0x0 +#define LDB_DI0_VS_POL_MASK 0x00000200 +#define LDB_DI0_VS_POL_ACT_LOW 0x00000200 +#define LDB_DI0_VS_POL_ACT_HIGH 0x0 + +#define LDB_BIT_MAP_CH1_MASK 0x00000100 +#define LDB_BIT_MAP_CH1_JEIDA 0x00000100 +#define LDB_BIT_MAP_CH1_SPWG 0x0 +#define LDB_BIT_MAP_CH0_MASK 0x00000040 +#define LDB_BIT_MAP_CH0_JEIDA 0x00000040 +#define LDB_BIT_MAP_CH0_SPWG 0x0 + +#define LDB_DATA_WIDTH_CH1_MASK 0x00000080 +#define LDB_DATA_WIDTH_CH1_24 0x00000080 +#define LDB_DATA_WIDTH_CH1_18 0x0 +#define LDB_DATA_WIDTH_CH0_MASK 0x00000020 +#define LDB_DATA_WIDTH_CH0_24 0x00000020 +#define LDB_DATA_WIDTH_CH0_18 0x0 + +#define LDB_CH1_MODE_MASK 0x0000000C +#define LDB_CH1_MODE_EN_TO_DI1 0x0000000C +#define LDB_CH1_MODE_EN_TO_DI0 0x00000004 +#define LDB_CH1_MODE_DISABLE 0x0 +#define LDB_CH0_MODE_MASK 0x00000003 +#define LDB_CH0_MODE_EN_TO_DI1 0x00000003 +#define LDB_CH0_MODE_EN_TO_DI0 0x00000001 +#define LDB_CH0_MODE_DISABLE 0x0 + +#define LDB_SPLIT_MODE_EN 0x00000010 + +struct ldb_data { + struct platform_device *pdev; + struct mxc_dispdrv_handle *disp_ldb; + uint32_t *reg; + uint32_t *control_reg; + uint32_t *gpr3_reg; + uint32_t control_reg_data; + struct regulator *lvds_bg_reg; + int mode; + bool inited; + struct ldb_setting { + struct clk *di_clk; + struct clk *ldb_di_clk; + bool active; + bool clk_en; + int ipu; + int di; + uint32_t ch_mask; + uint32_t ch_val; + } setting[2]; + struct notifier_block nb; +}; + +static int g_ldb_mode; +static struct i2c_client *ldb_i2c_client[2]; + +static struct fb_videomode ldb_modedb[] = { + { + "LDB-WXGA", 60, 1280, 800, 14065, + 40, 40, + 10, 3, + 80, 10, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + "LDB-XGA", 60, 1024, 768, 15385, + 220, 40, + 21, 7, + 60, 10, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + "LDB-1080P60", 60, 1920, 1080, 7692, + 100, 40, + 30, 3, + 10, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; +static int ldb_modedb_sz = ARRAY_SIZE(ldb_modedb); + +static int bits_per_pixel(int pixel_fmt) +{ + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 24; + break; + case IPU_PIX_FMT_BGR666: + case IPU_PIX_FMT_RGB666: + case IPU_PIX_FMT_LVDS666: + return 18; + break; + default: + break; + } + return 0; +} + +static int valid_mode(int pixel_fmt) +{ + return ((pixel_fmt == IPU_PIX_FMT_RGB24) || + (pixel_fmt == IPU_PIX_FMT_BGR24) || + (pixel_fmt == IPU_PIX_FMT_LVDS666) || + (pixel_fmt == IPU_PIX_FMT_RGB666) || + (pixel_fmt == IPU_PIX_FMT_BGR666)); +} + +/* + * "ldb=spl0/1" -- split mode on DI0/1 + * "ldb=dul0/1" -- dual mode on DI0/1 + * "ldb=sin0/1" -- single mode on LVDS0/1 + * "ldb=sep0/1" -- separate mode begin from LVDS0/1 + * + * there are two LVDS channels(LVDS0 and LVDS1) which can transfer video + * datas, there two channels can be used as split/dual/single/separate mode. + * + * split mode means display data from DI0 or DI1 will send to both channels + * LVDS0+LVDS1. + * dual mode means display data from DI0 or DI1 will be duplicated on LVDS0 + * and LVDS1, it said, LVDS0 and LVDS1 has the same content. + * single mode means only work for DI0/DI1->LVDS0 or DI0/DI1->LVDS1. + * separate mode means you can make DI0/DI1->LVDS0 and DI0/DI1->LVDS1 work + * at the same time. + */ +static int __init ldb_setup(char *options) +{ + if (!strcmp(options, "spl0")) + g_ldb_mode = LDB_SPL_DI0; + else if (!strcmp(options, "spl1")) + g_ldb_mode = LDB_SPL_DI1; + else if (!strcmp(options, "dul0")) + g_ldb_mode = LDB_DUL_DI0; + else if (!strcmp(options, "dul1")) + g_ldb_mode = LDB_DUL_DI1; + else if (!strcmp(options, "sin0")) + g_ldb_mode = LDB_SIN0; + else if (!strcmp(options, "sin1")) + g_ldb_mode = LDB_SIN1; + else if (!strcmp(options, "sep0")) + g_ldb_mode = LDB_SEP0; + else if (!strcmp(options, "sep1")) + g_ldb_mode = LDB_SEP1; + + return 1; +} +__setup("ldb=", ldb_setup); + +static int find_ldb_setting(struct ldb_data *ldb, struct fb_info *fbi) +{ + char *id_di[] = { + "DISP3 BG", + "DISP3 BG - DI1", + }; + char id[16]; + int i; + + for (i = 0; i < 2; i++) { + if (ldb->setting[i].active) { + memset(id, 0, 16); + memcpy(id, id_di[ldb->setting[i].di], + strlen(id_di[ldb->setting[i].di])); + id[4] += ldb->setting[i].ipu; + if (!strcmp(id, fbi->fix.id)) + return i; + } + } + return -EINVAL; +} + +static int ldb_disp_setup(struct mxc_dispdrv_handle *disp, struct fb_info *fbi) +{ + uint32_t reg, val; + uint32_t pixel_clk, rounded_pixel_clk; + struct clk *ldb_clk_parent; + struct ldb_data *ldb = mxc_dispdrv_getdata(disp); + int setting_idx, di; + + setting_idx = find_ldb_setting(ldb, fbi); + if (setting_idx < 0) + return setting_idx; + + di = ldb->setting[setting_idx].di; + + /* restore channel mode setting */ + val = readl(ldb->control_reg); + val |= ldb->setting[setting_idx].ch_val; + writel(val, ldb->control_reg); + dev_dbg(&ldb->pdev->dev, "LDB setup, control reg:0x%x\n", + readl(ldb->control_reg)); + + /* vsync setup */ + reg = readl(ldb->control_reg); + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) { + if (di == 0) + reg = (reg & ~LDB_DI0_VS_POL_MASK) + | LDB_DI0_VS_POL_ACT_HIGH; + else + reg = (reg & ~LDB_DI1_VS_POL_MASK) + | LDB_DI1_VS_POL_ACT_HIGH; + } else { + if (di == 0) + reg = (reg & ~LDB_DI0_VS_POL_MASK) + | LDB_DI0_VS_POL_ACT_LOW; + else + reg = (reg & ~LDB_DI1_VS_POL_MASK) + | LDB_DI1_VS_POL_ACT_LOW; + } + writel(reg, ldb->control_reg); + + /* clk setup */ + if (ldb->setting[setting_idx].clk_en) + clk_disable(ldb->setting[setting_idx].ldb_di_clk); + pixel_clk = (PICOS2KHZ(fbi->var.pixclock)) * 1000UL; + ldb_clk_parent = clk_get_parent(ldb->setting[setting_idx].ldb_di_clk); + if ((ldb->mode == LDB_SPL_DI0) || (ldb->mode == LDB_SPL_DI1)) + clk_set_rate(ldb_clk_parent, pixel_clk * 7 / 2); + else + clk_set_rate(ldb_clk_parent, pixel_clk * 7); + rounded_pixel_clk = clk_round_rate(ldb->setting[setting_idx].ldb_di_clk, + pixel_clk); + clk_set_rate(ldb->setting[setting_idx].ldb_di_clk, rounded_pixel_clk); + clk_enable(ldb->setting[setting_idx].ldb_di_clk); + if (!ldb->setting[setting_idx].clk_en) + ldb->setting[setting_idx].clk_en = true; + + return 0; +} + +int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct ldb_data *ldb = container_of(nb, struct ldb_data, nb); + struct fb_event *event = v; + struct fb_info *fbi = event->info; + int index; + uint32_t data; + + index = find_ldb_setting(ldb, fbi); + if (index < 0) + return 0; + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + if (!fbi->mode) { + dev_warn(&ldb->pdev->dev, + "LDB: can not find mode for xres=%d, yres=%d\n", + fbi->var.xres, fbi->var.yres); + if (ldb->setting[index].clk_en) { + clk_disable(ldb->setting[index].ldb_di_clk); + ldb->setting[index].clk_en = false; + data = readl(ldb->control_reg); + data &= ~ldb->setting[index].ch_mask; + writel(data, ldb->control_reg); + } + return 0; + } + + switch (val) { + case FB_EVENT_BLANK: + { + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + if (!ldb->setting[index].clk_en) { + clk_enable(ldb->setting[index].ldb_di_clk); + ldb->setting[index].clk_en = true; + } + } else { + if (ldb->setting[index].clk_en) { + clk_disable(ldb->setting[index].ldb_di_clk); + ldb->setting[index].clk_en = false; + data = readl(ldb->control_reg); + data &= ~ldb->setting[index].ch_mask; + writel(data, ldb->control_reg); + dev_dbg(&ldb->pdev->dev, + "LDB blank, control reg:0x%x\n", + readl(ldb->control_reg)); + } + } + break; + } + case FB_EVENT_SUSPEND: + if (ldb->setting[index].clk_en) { + clk_disable(ldb->setting[index].ldb_di_clk); + ldb->setting[index].clk_en = false; + } + break; + default: + break; + } + return 0; +} + +#define LVDS_MUX_CTL_WIDTH 2 +#define LVDS_MUX_CTL_MASK 3 +#define LVDS0_MUX_CTL_OFFS 6 +#define LVDS1_MUX_CTL_OFFS 8 +#define LVDS0_MUX_CTL_MASK (LVDS_MUX_CTL_MASK << 6) +#define LVDS1_MUX_CTL_MASK (LVDS_MUX_CTL_MASK << 8) +#define ROUTE_IPU_DI(ipu, di) (((ipu << 1) | di) & LVDS_MUX_CTL_MASK) +static int ldb_ipu_ldb_route(int ipu, int di, struct ldb_data *ldb) +{ + uint32_t reg; + int channel; + int shift; + int mode = ldb->mode; + + reg = readl(ldb->gpr3_reg); + if (mode < LDB_SIN0) { + reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK); + reg |= (ROUTE_IPU_DI(ipu, di) << LVDS0_MUX_CTL_OFFS) | + (ROUTE_IPU_DI(ipu, di) << LVDS1_MUX_CTL_OFFS); + dev_dbg(&ldb->pdev->dev, + "Dual/Split mode both channels route to IPU%d-DI%d\n", + ipu, di); + } else if ((mode == LDB_SIN0) || (mode == LDB_SIN1)) { + reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK); + channel = mode - LDB_SIN0; + shift = LVDS0_MUX_CTL_OFFS + channel * LVDS_MUX_CTL_WIDTH; + reg |= ROUTE_IPU_DI(ipu, di) << shift; + dev_dbg(&ldb->pdev->dev, + "Single mode channel %d route to IPU%d-DI%d\n", + channel, ipu, di); + } else { + static bool first = true; + + if (first) { + if (mode == LDB_SEP0) { + reg &= ~LVDS0_MUX_CTL_MASK; + channel = 0; + } else { + reg &= ~LVDS1_MUX_CTL_MASK; + channel = 1; + } + first = false; + } else { + if (mode == LDB_SEP0) { + reg &= ~LVDS1_MUX_CTL_MASK; + channel = 1; + } else { + reg &= ~LVDS0_MUX_CTL_MASK; + channel = 0; + } + } + + shift = LVDS0_MUX_CTL_OFFS + channel * LVDS_MUX_CTL_WIDTH; + reg |= ROUTE_IPU_DI(ipu, di) << shift; + + dev_dbg(&ldb->pdev->dev, + "Separate mode channel %d route to IPU%d-DI%d\n", + channel, ipu, di); + } + writel(reg, ldb->gpr3_reg); + + return 0; +} + +static int ldb_disp_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + int ret = 0, i; + struct ldb_data *ldb = mxc_dispdrv_getdata(disp); + struct fsl_mxc_ldb_platform_data *plat_data = ldb->pdev->dev.platform_data; + struct i2c_client *i2c_dev; + struct resource *res; + uint32_t base_addr; + uint32_t reg, setting_idx; + uint32_t ch_mask = 0, ch_val = 0; + uint32_t ipu_id, disp_id; + + /* if input format not valid, make RGB666 as default*/ + if (!valid_mode(setting->if_fmt)) { + dev_warn(&ldb->pdev->dev, "Input pixel format not valid" + " use default RGB666\n"); + setting->if_fmt = IPU_PIX_FMT_RGB666; + } + + if (!ldb->inited) { + char di_clk[] = "ipu1_di0_clk"; + char ldb_clk[] = "ldb_di0_clk"; + int lvds_channel = 0; + + setting_idx = 0; + res = platform_get_resource(ldb->pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) + return -ENOMEM; + + base_addr = res->start; + ldb->reg = ioremap(base_addr, res->end - res->start + 1); + ldb->control_reg = ldb->reg + 2; + ldb->gpr3_reg = ldb->reg + 3; + + ldb->lvds_bg_reg = regulator_get(&ldb->pdev->dev, plat_data->lvds_bg_reg); + if (!IS_ERR(ldb->lvds_bg_reg)) { + regulator_set_voltage(ldb->lvds_bg_reg, 2500000, 2500000); + regulator_enable(ldb->lvds_bg_reg); + } + + /* ipu selected by platform data setting */ + setting->dev_id = plat_data->ipu_id; + + reg = readl(ldb->control_reg); + + /* refrence resistor select */ + reg &= ~LDB_BGREF_RMODE_MASK; + if (plat_data->ext_ref) + reg |= LDB_BGREF_RMODE_EXT; + else + reg |= LDB_BGREF_RMODE_INT; + + /* TODO: now only use SPWG data mapping for both channel */ + reg &= ~(LDB_BIT_MAP_CH0_MASK | LDB_BIT_MAP_CH1_MASK); + reg |= LDB_BIT_MAP_CH0_SPWG | LDB_BIT_MAP_CH1_SPWG; + + /* channel mode setting */ + reg &= ~(LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); + reg &= ~(LDB_DATA_WIDTH_CH0_MASK | LDB_DATA_WIDTH_CH1_MASK); + + if (bits_per_pixel(setting->if_fmt) == 24) + reg |= LDB_DATA_WIDTH_CH0_24 | LDB_DATA_WIDTH_CH1_24; + else + reg |= LDB_DATA_WIDTH_CH0_18 | LDB_DATA_WIDTH_CH1_18; + + if (g_ldb_mode) + ldb->mode = g_ldb_mode; + else + ldb->mode = plat_data->mode; + + if ((ldb->mode == LDB_SIN0) || (ldb->mode == LDB_SIN1)) { + ret = ldb->mode - LDB_SIN0; + if (plat_data->disp_id != ret) { + dev_warn(&ldb->pdev->dev, + "change IPU DI%d to IPU DI%d for LDB " + "channel%d.\n", + plat_data->disp_id, ret, ret); + plat_data->disp_id = ret; + } + } else if (((ldb->mode == LDB_SEP0) || (ldb->mode == LDB_SEP1)) + && (cpu_is_mx6q() || cpu_is_mx6dl())) { + if (plat_data->disp_id == plat_data->sec_disp_id) { + dev_err(&ldb->pdev->dev, + "For LVDS separate mode," + "two DIs should be different!\n"); + return -EINVAL; + } + + if (((!plat_data->disp_id) && (ldb->mode == LDB_SEP1)) + || ((plat_data->disp_id) && + (ldb->mode == LDB_SEP0))) { + dev_dbg(&ldb->pdev->dev, + "LVDS separate mode:" + "swap DI configuration!\n"); + ipu_id = plat_data->ipu_id; + disp_id = plat_data->disp_id; + plat_data->ipu_id = plat_data->sec_ipu_id; + plat_data->disp_id = plat_data->sec_disp_id; + plat_data->sec_ipu_id = ipu_id; + plat_data->sec_disp_id = disp_id; + } + } + + if (ldb->mode == LDB_SPL_DI0) { + reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI0 + | LDB_CH1_MODE_EN_TO_DI0; + setting->disp_id = 0; + } else if (ldb->mode == LDB_SPL_DI1) { + reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI1 + | LDB_CH1_MODE_EN_TO_DI1; + setting->disp_id = 1; + } else if (ldb->mode == LDB_DUL_DI0) { + reg &= ~LDB_SPLIT_MODE_EN; + reg |= LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0; + setting->disp_id = 0; + } else if (ldb->mode == LDB_DUL_DI1) { + reg &= ~LDB_SPLIT_MODE_EN; + reg |= LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1; + setting->disp_id = 1; + } else if (ldb->mode == LDB_SIN0) { + reg &= ~LDB_SPLIT_MODE_EN; + setting->disp_id = plat_data->disp_id; + if (setting->disp_id == 0) + reg |= LDB_CH0_MODE_EN_TO_DI0; + else + reg |= LDB_CH0_MODE_EN_TO_DI1; + ch_mask = LDB_CH0_MODE_MASK; + ch_val = reg & LDB_CH0_MODE_MASK; + } else if (ldb->mode == LDB_SIN1) { + reg &= ~LDB_SPLIT_MODE_EN; + setting->disp_id = plat_data->disp_id; + if (setting->disp_id == 0) + reg |= LDB_CH1_MODE_EN_TO_DI0; + else + reg |= LDB_CH1_MODE_EN_TO_DI1; + ch_mask = LDB_CH1_MODE_MASK; + ch_val = reg & LDB_CH1_MODE_MASK; + } else { /* separate mode*/ + setting->disp_id = plat_data->disp_id; + + /* first output is LVDS0 or LVDS1 */ + if (ldb->mode == LDB_SEP0) + lvds_channel = 0; + else + lvds_channel = 1; + + reg &= ~LDB_SPLIT_MODE_EN; + + if ((lvds_channel == 0) && (setting->disp_id == 0)) + reg |= LDB_CH0_MODE_EN_TO_DI0; + else if ((lvds_channel == 0) && (setting->disp_id == 1)) + reg |= LDB_CH0_MODE_EN_TO_DI1; + else if ((lvds_channel == 1) && (setting->disp_id == 0)) + reg |= LDB_CH1_MODE_EN_TO_DI0; + else + reg |= LDB_CH1_MODE_EN_TO_DI1; + ch_mask = lvds_channel ? LDB_CH1_MODE_MASK : + LDB_CH0_MODE_MASK; + ch_val = reg & ch_mask; + + if (bits_per_pixel(setting->if_fmt) == 24) { + if (lvds_channel == 0) + reg &= ~LDB_DATA_WIDTH_CH1_24; + else + reg &= ~LDB_DATA_WIDTH_CH0_24; + } else { + if (lvds_channel == 0) + reg &= ~LDB_DATA_WIDTH_CH1_18; + else + reg &= ~LDB_DATA_WIDTH_CH0_18; + } + } + + writel(reg, ldb->control_reg); + if (ldb->mode < LDB_SIN0) { + ch_mask = LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK; + ch_val = reg & (LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); + } + + /* clock setting */ + if ((cpu_is_mx6q() || cpu_is_mx6dl()) && + ((ldb->mode == LDB_SEP0) || (ldb->mode == LDB_SEP1))) + ldb_clk[6] += lvds_channel; + else + ldb_clk[6] += setting->disp_id; + ldb->setting[setting_idx].ldb_di_clk = clk_get(&ldb->pdev->dev, + ldb_clk); + if (IS_ERR(ldb->setting[setting_idx].ldb_di_clk)) { + dev_err(&ldb->pdev->dev, "get ldb clk0 failed\n"); + iounmap(ldb->reg); + return PTR_ERR(ldb->setting[setting_idx].ldb_di_clk); + } + di_clk[3] += setting->dev_id; + di_clk[7] += setting->disp_id; + ldb->setting[setting_idx].di_clk = clk_get(&ldb->pdev->dev, + di_clk); + if (IS_ERR(ldb->setting[setting_idx].di_clk)) { + dev_err(&ldb->pdev->dev, "get di clk0 failed\n"); + iounmap(ldb->reg); + return PTR_ERR(ldb->setting[setting_idx].di_clk); + } + + dev_dbg(&ldb->pdev->dev, "ldb_clk to di clk: %s -> %s\n", ldb_clk, di_clk); + + /* fb notifier for clk setting */ + ldb->nb.notifier_call = ldb_fb_event, + ret = fb_register_client(&ldb->nb); + if (ret < 0) { + iounmap(ldb->reg); + return ret; + } + + ldb->inited = true; + + i2c_dev = ldb_i2c_client[0]; + + } else { /* second time for separate mode */ + char di_clk[] = "ipu1_di0_clk"; + char ldb_clk[] = "ldb_di0_clk"; + int lvds_channel; + + if ((ldb->mode == LDB_SPL_DI0) || + (ldb->mode == LDB_SPL_DI1) || + (ldb->mode == LDB_DUL_DI0) || + (ldb->mode == LDB_DUL_DI1) || + (ldb->mode == LDB_SIN0) || + (ldb->mode == LDB_SIN1)) { + dev_err(&ldb->pdev->dev, "for second ldb disp" + "ldb mode should in separate mode\n"); + return -EINVAL; + } + + setting_idx = 1; + if (cpu_is_mx6q() || cpu_is_mx6dl()) { + setting->dev_id = plat_data->sec_ipu_id; + setting->disp_id = plat_data->sec_disp_id; + } else { + setting->dev_id = plat_data->ipu_id; + setting->disp_id = !plat_data->disp_id; + } + if (setting->disp_id == ldb->setting[0].di) { + dev_err(&ldb->pdev->dev, "Err: for second ldb disp in" + "separate mode, DI should be different!\n"); + return -EINVAL; + } + + /* second output is LVDS0 or LVDS1 */ + if (ldb->mode == LDB_SEP0) + lvds_channel = 1; + else + lvds_channel = 0; + + reg = readl(ldb->control_reg); + if ((lvds_channel == 0) && (setting->disp_id == 0)) + reg |= LDB_CH0_MODE_EN_TO_DI0; + else if ((lvds_channel == 0) && (setting->disp_id == 1)) + reg |= LDB_CH0_MODE_EN_TO_DI1; + else if ((lvds_channel == 1) && (setting->disp_id == 0)) + reg |= LDB_CH1_MODE_EN_TO_DI0; + else + reg |= LDB_CH1_MODE_EN_TO_DI1; + ch_mask = lvds_channel ? LDB_CH1_MODE_MASK : + LDB_CH0_MODE_MASK; + ch_val = reg & ch_mask; + + if (bits_per_pixel(setting->if_fmt) == 24) { + if (lvds_channel == 0) + reg |= LDB_DATA_WIDTH_CH0_24; + else + reg |= LDB_DATA_WIDTH_CH1_24; + } else { + if (lvds_channel == 0) + reg |= LDB_DATA_WIDTH_CH0_18; + else + reg |= LDB_DATA_WIDTH_CH1_18; + } + writel(reg, ldb->control_reg); + + /* clock setting */ + if (cpu_is_mx6q() || cpu_is_mx6dl()) + ldb_clk[6] += lvds_channel; + else + ldb_clk[6] += setting->disp_id; + ldb->setting[setting_idx].ldb_di_clk = clk_get(&ldb->pdev->dev, + ldb_clk); + if (IS_ERR(ldb->setting[setting_idx].ldb_di_clk)) { + dev_err(&ldb->pdev->dev, "get ldb clk1 failed\n"); + return PTR_ERR(ldb->setting[setting_idx].ldb_di_clk); + } + di_clk[3] += setting->dev_id; + di_clk[7] += setting->disp_id; + ldb->setting[setting_idx].di_clk = clk_get(&ldb->pdev->dev, + di_clk); + if (IS_ERR(ldb->setting[setting_idx].di_clk)) { + dev_err(&ldb->pdev->dev, "get di clk1 failed\n"); + return PTR_ERR(ldb->setting[setting_idx].di_clk); + } + + i2c_dev = ldb_i2c_client[1]; + + dev_dbg(&ldb->pdev->dev, "ldb_clk to di clk: %s -> %s\n", ldb_clk, di_clk); + } + + if (i2c_dev) + mxc_dispdrv_setdev(ldb->disp_ldb, &i2c_dev->dev); + else + mxc_dispdrv_setdev(ldb->disp_ldb, NULL); + + ldb->setting[setting_idx].ch_mask = ch_mask; + ldb->setting[setting_idx].ch_val = ch_val; + + if (cpu_is_mx6q() || cpu_is_mx6dl()) + ldb_ipu_ldb_route(setting->dev_id, setting->disp_id, ldb); + + /* + * ldb_di0_clk -> ipux_di0_clk + * ldb_di1_clk -> ipux_di1_clk + */ + clk_set_parent(ldb->setting[setting_idx].di_clk, + ldb->setting[setting_idx].ldb_di_clk); + + /* must use spec video mode defined by driver */ + ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, + ldb_modedb, ldb_modedb_sz, NULL, setting->default_bpp); + if (ret != 1) + fb_videomode_to_var(&setting->fbi->var, &ldb_modedb[0]); + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < ldb_modedb_sz; i++) { + struct fb_videomode m; + fb_var_to_videomode(&m, &setting->fbi->var); + if (fb_mode_is_equal(&m, &ldb_modedb[i])) { + fb_add_videomode(&ldb_modedb[i], + &setting->fbi->modelist); + break; + } + } + + /* save current ldb setting for fb notifier */ + ldb->setting[setting_idx].active = true; + ldb->setting[setting_idx].ipu = setting->dev_id; + ldb->setting[setting_idx].di = setting->disp_id; + + return ret; +} + +static void ldb_disp_deinit(struct mxc_dispdrv_handle *disp) +{ + struct ldb_data *ldb = mxc_dispdrv_getdata(disp); + int i; + + writel(0, ldb->control_reg); + + for (i = 0; i < 2; i++) { + clk_disable(ldb->setting[i].ldb_di_clk); + clk_put(ldb->setting[i].ldb_di_clk); + } + + fb_unregister_client(&ldb->nb); + + iounmap(ldb->reg); +} + +static struct mxc_dispdrv_driver ldb_drv = { + .name = DISPDRV_LDB, + .init = ldb_disp_init, + .deinit = ldb_disp_deinit, + .setup = ldb_disp_setup, +}; + +static int ldb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); + uint32_t data; + + if (!ldb->inited) + return 0; + data = readl(ldb->control_reg); + ldb->control_reg_data = data; + data &= ~(LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); + writel(data, ldb->control_reg); + + return 0; +} + +static int ldb_resume(struct platform_device *pdev) +{ + struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); + + if (!ldb->inited) + return 0; + writel(ldb->control_reg_data, ldb->control_reg); + + return 0; +} + +static int mxc_ldb_edidread(struct i2c_adapter *adp) +{ + int ret = 0; + u8 edid[512]; + unsigned char regaddr = 0; + struct i2c_msg msg[2] = { + { + .addr = 0x50, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = 0x50, + .flags = I2C_M_RD, + .len = 512, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + return -EIO; + } + + return ret; +} + +static ssize_t mxc_ldb_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + + ret = mxc_ldb_edidread(client->adapter); + if (ret < 0) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, mxc_ldb_show_state, NULL); + +static int __devinit mxc_ldb_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + int ldb_id = (int)client->dev.platform_data; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -ENODEV; + + ldb_i2c_client[ldb_id] = client; + + ret = device_create_file(&client->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&client->dev, + "cound not create sys node for cable state\n"); + + + return 0; +} + +static int __devexit mxc_ldb_i2c_remove(struct i2c_client *client) +{ + int ldb_id = (int)client->dev.platform_data; + ldb_i2c_client[ldb_id] = NULL; + return 0; +} + +static const struct i2c_device_id mxc_ldb_i2c_id[] = { + { "mxc_ldb_i2c", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mxc_ldb_i2c_id); + +static struct i2c_driver mxc_ldb_i2c_driver = { + .driver = { + .name = "mxc_ldb_i2c", + }, + .probe = mxc_ldb_i2c_probe, + .remove = mxc_ldb_i2c_remove, + .id_table = mxc_ldb_i2c_id, +}; + +static int __init mxc_ldb_i2c_init(void) +{ + return i2c_add_driver(&mxc_ldb_i2c_driver); +} + +static void __exit mxc_ldb_i2c_exit(void) +{ + i2c_del_driver(&mxc_ldb_i2c_driver); +} + +module_init(mxc_ldb_i2c_init); +module_exit(mxc_ldb_i2c_exit); + +/*! + * This function is called by the driver framework to initialize the LDB + * device. + * + * @param dev The device structure for the LDB passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int ldb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ldb_data *ldb; + + ldb = kzalloc(sizeof(struct ldb_data), GFP_KERNEL); + if (!ldb) { + ret = -ENOMEM; + goto alloc_failed; + } + + ldb->pdev = pdev; + ldb->disp_ldb = mxc_dispdrv_register(&ldb_drv); + mxc_dispdrv_setdata(ldb->disp_ldb, ldb); + + dev_set_drvdata(&pdev->dev, ldb); + +alloc_failed: + return ret; +} + +static int ldb_remove(struct platform_device *pdev) +{ + struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); + + if (!ldb->inited) + return 0; + mxc_dispdrv_puthandle(ldb->disp_ldb); + mxc_dispdrv_unregister(ldb->disp_ldb); + kfree(ldb); + return 0; +} + +static struct platform_driver mxcldb_driver = { + .driver = { + .name = "mxc_ldb", + }, + .probe = ldb_probe, + .remove = ldb_remove, + .suspend = ldb_suspend, + .resume = ldb_resume, +}; + +static int __init ldb_init(void) +{ + return platform_driver_register(&mxcldb_driver); +} + +static void __exit ldb_uninit(void) +{ + platform_driver_unregister(&mxcldb_driver); +} + +module_init(ldb_init); +module_exit(ldb_uninit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC LDB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/lib.a_shipped b/drivers/video/mxc/lib.a_shipped new file mode 120000 index 00000000..5c52a946 --- /dev/null +++ b/drivers/video/mxc/lib.a_shipped @@ -0,0 +1 @@ +lib.a_shipped-imx6sl-aa
\ No newline at end of file diff --git a/drivers/video/mxc/lib.a_shipped-imx6sl-aa b/drivers/video/mxc/lib.a_shipped-imx6sl-aa Binary files differnew file mode 100755 index 00000000..ef1b1544 --- /dev/null +++ b/drivers/video/mxc/lib.a_shipped-imx6sl-aa diff --git a/drivers/video/mxc/lib.a_shipped-imx6sl-noaa b/drivers/video/mxc/lib.a_shipped-imx6sl-noaa Binary files differnew file mode 100755 index 00000000..674ba155 --- /dev/null +++ b/drivers/video/mxc/lib.a_shipped-imx6sl-noaa diff --git a/drivers/video/mxc/lk_fp9928.c b/drivers/video/mxc/lk_fp9928.c new file mode 100644 index 00000000..b259a128 --- /dev/null +++ b/drivers/video/mxc/lk_fp9928.c @@ -0,0 +1,1070 @@ + + +/* + * + * Purpose : FP9928 driver + * Author : Gallen Lin + * versions : + * + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/semaphore.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/slab.h> +#include <linux/interrupt.h> + + +#include <mach/hardware.h> +#include <mach/gpio.h> + +#include <mach/iomux-mx6sl.h> + + +#include "ntx_hwconfig.h" +#include "fake_s1d13522.h" + +#define GDEBUG 0 +#include <linux/gallen_dbg.h> + +#include "lk_fp9928.h" + + +#define DRIVER_NAME "FP9928" + +#define INIT_POWER_STATE 0 + +#define GPIO_FP9928_VIN_PADCFG MX6SL_PAD_EPDC_PWRWAKEUP__GPIO_2_14 +#define GPIO_FP9928_VIN IMX_GPIO_NR(2,14) + +#if 1 +// +#define GPIO_FP9928_EN_PADCFG MX6SL_PAD_EPDC_PWRCTRL1__GPIO_2_8 +#define GPIO_FP9928_EN IMX_GPIO_NR(2,8) +#define GPIO_FP9928_VCOM_PADCFG MX6SL_PAD_EPDC_VCOM0__GPIO_2_3 +#define GPIO_FP9928_VCOM IMX_GPIO_NR(2,3) +#define GPIO_FP9928_EP_3V3_IN IMX_GPIO_NR(4,3) // EPDC_PWRWAKEUP +#define GPIO_FP9928_EP_3V3_IN_PADCFG MX6SL_PAD_KEY_ROW5__GPIO_4_3 +#else +// beta version . +#define GPIO_FP9928_EN_PADCFG MX6SL_PAD_EPDC_VCOM0__GPIO_2_3 +#define GPIO_FP9928_EN IMX_GPIO_NR(2,3) +#define GPIO_FP9928_VCOM_PADCFG MX6SL_PAD_EPDC_PWRCTRL1__GPIO_2_8 +#define GPIO_FP9928_VCOM IMX_GPIO_NR(2,8) +#endif + +#define VIN_ON 1 +#define VIN_OFF 0 +#define EN_ON 1 +#define EN_OFF 0 +#define VCOM_ON 1 +#define VCOM_OFF 0 + + +#define FP9928_EP3V3OFF_TICKS_MAX 350 +#define FP9928_POWEROFF_TICKS_MAX 0 // +#define FP9928_POWERON_WAIT_TICKS 2 //ç‰å¾…FP9928 POWERON->READY的時間. +#define FP9928_PWROFFDELAYWORK_TICKS 50 + + +#if 0 +#define FP9928_VCOM_MV_MAX (-302) +#define FP9928_VCOM_MV_MIN (-2501) +//#define FP9928_VCOM_MV_STEP (11) +#define FP9928_VCOM_UV_STEP (11000) +#else +#define FP9928_VCOM_MV_MAX (-302) +#define FP9928_VCOM_MV_MIN (-6000) +//#define FP9928_VCOM_MV_STEP (22) +#define FP9928_VCOM_UV_STEP (21569) +#endif + +#define FP9928_WAIT_TICKSTAMP(_TickEnd,_wait_item) \ +{\ + unsigned long dwTickNow=jiffies,dwTicks;\ + if ( time_before(dwTickNow,_TickEnd) ) {\ + dwTicks = _TickEnd-dwTickNow;\ + DBG0_MSG("%s() waiting to %ld ticks for %s ... ",__FUNCTION__,dwTicks,_wait_item);\ + if(in_interrupt()) {\ + mdelay(jiffies_to_msecs(dwTicks));\ + DBG0_MSG("done(@INT)\n");\ + }\ + else {\ + msleep(jiffies_to_msecs(dwTicks));\ + DBG0_MSG("done\n");\ + }\ + }\ +} + + +typedef struct tagFP9928_PWRDWN_WORK_PARAM { + struct delayed_work pwrdwn_work; + int iIsTurnOffChipPwr; +} FP9928_PWRDWN_WORK_PARAM; + + + +typedef struct tagFP9228_data { + int iCurrent_temprature; + unsigned short wTempratureData,wReserved; + struct i2c_adapter *ptI2C_adapter; + struct i2c_client *ptI2C_client; + struct mutex tI2CLock; + struct mutex tPwrLock; + int iIsPoweredON; + int iIsOutputEnabled; + int iIsOutputPowerDownCounting; + int iIsVCOMNeedReInit; + int iCurrent_VCOM_mV; + unsigned long dwTickPowerOffEnd; + unsigned long dwTickPowerOnEnd; + unsigned long dwTickEP3V3OffEnd; + FP9928_PWRDWN_WORK_PARAM tPwrdwn_work_param; + int iIsEP3V3_SW_enabled; +} FP9928_data; + + +static FP9928_data *gptFP9928_data ; + +// externals ... +extern volatile NTX_HWCONFIG *gptHWCFG; +extern volatile int gSleep_Mode_Suspend; + + +static struct i2c_board_info gtFP9928_BI = { + .type = "FP9928", + .addr = 0x48, + .platform_data = NULL, +}; + +static const unsigned short gwFP9928_AddrA[] = { + 0x48, + I2C_CLIENT_END +}; + + +static volatile unsigned char gbFP9928_REG_TMST_addr=0x00; +static volatile unsigned char gbFP9928_REG_TMST=0; + +#define FP9928_REG_FUNC_ADJUST_VCOM_ADJ 0x01 +#define FP9928_REG_FUNC_ADJUST_FIX_RD_PTR 0x02 +static volatile unsigned char gbFP9928_REG_FUNC_ADJUST_addr=0x01; +static volatile unsigned char gbFP9928_REG_FUNC_ADJUST=0x01; + +#define FP9928_REG_VCOM_SETTING_ALL 0xff +static volatile unsigned char gbFP9928_REG_VCOM_SETTING_addr=0x02; +static volatile unsigned char gbFP9928_REG_VCOM_SETTING=0x74; + + +static int _fp9928_set_reg(unsigned char bRegAddr,unsigned char bRegSetVal) +{ + int iRet=FP9928_RET_SUCCESS; + int iChk; + unsigned char bA[2] ; + int iIn_Interrupt = in_interrupt(); + unsigned long dwTickNow = jiffies,dwTicks; + + ASSERT(gptFP9928_data); + + if(!iIn_Interrupt) { + mutex_lock(&gptFP9928_data->tI2CLock); + } + + FP9928_WAIT_TICKSTAMP(gptFP9928_data->dwTickPowerOnEnd,"power on stable"); + + bA[0]=bRegAddr; + bA[1]=bRegSetVal; + iChk = i2c_master_send(gptFP9928_data->ptI2C_client, (const char *)bA, sizeof(bA)); + if (iChk < 0) { + ERR_MSG("%s(%d):%d=%s(),regAddr=0x%x,regVal=0x%x fail !\n",__FILE__,__LINE__,\ + iChk,"i2c_master_send",bRegAddr,bRegSetVal); + iRet=FP9928_RET_I2CTRANS_ERR; + } + + if(!iIn_Interrupt) { + mutex_unlock(&gptFP9928_data->tI2CLock); + } + + return iRet; +} + +static int _fp9928_get_reg(unsigned char bRegAddr,unsigned char *O_pbRegVal) +{ + int iRet=FP9928_RET_SUCCESS; + int iChk; + unsigned char bA[1] ; + int iIn_Interrupt = in_interrupt(); + unsigned long dwTickNow = jiffies,dwTicks; + + ASSERT(gptFP9928_data); + + if(!iIn_Interrupt) { + mutex_lock(&gptFP9928_data->tI2CLock); + } + + FP9928_WAIT_TICKSTAMP(gptFP9928_data->dwTickPowerOnEnd,"power on stable"); + + bA[0]=bRegAddr; + iChk = i2c_master_send(gptFP9928_data->ptI2C_client, (const char *)bA, 1); + if (iChk < 0) { + ERR_MSG("%s(%d):%s i2c_master_send fail !\n",__FILE__,__LINE__,__FUNCTION__); + iRet = FP9928_RET_I2CTRANS_ERR; + } + + + iChk = i2c_master_recv(gptFP9928_data->ptI2C_client, bA, 1); + if (iChk < 0) { + ERR_MSG("%s(%d):%s i2c_master_recv fail !\n",__FILE__,__LINE__,__FUNCTION__); + iRet = FP9928_RET_I2CTRANS_ERR; + } + + + if(iRet>=0) { + *O_pbRegVal = bA[0]; + } + + + //DBG_MSG("%s(0x%x,%p)==>0x%x\n",__FUNCTION__,bRegAddr,O_pbRegVal,bA[0]); + + if(!iIn_Interrupt) { + mutex_unlock(&gptFP9928_data->tI2CLock); + } + + return iRet; +} + + +#define FP9928_REG_SET(_regName,_bFieldName,_bSetVal) \ +({\ + int _iRet=FP9928_RET_SUCCESS;\ + int _iChk;\ + unsigned char _bNewReg,_bFieldMask;\ + \ + _bFieldMask=(unsigned char)FP9928_REG_##_regName##_##_bFieldName;\ + if(0xff==_bFieldMask) {\ + _bNewReg = _bSetVal;\ + }\ + else {\ + _bNewReg=gbFP9928_REG_##_regName;\ + if(_bSetVal) {\ + _bNewReg |= _bFieldMask ;\ + }\ + else {\ + _bNewReg &= ~_bFieldMask;\ + }\ + }\ + \ + _iChk = _fp9928_set_reg(gbFP9928_REG_##_regName##_##addr,_bNewReg);\ + if(_iChk<0) {\ + _iRet = _iChk;\ + }\ + else {\ + DBG_MSG("%s() : FP9928 write reg%s(%02Xh) 0x%02x->0x%02x\n",__FUNCTION__,\ + #_regName,gbFP9928_REG_##_regName##_##addr,gbFP9928_REG_##_regName,_bNewReg);\ + gbFP9928_REG_##_regName = _bNewReg;\ + }\ + _iRet;\ +}) + +#define FP9928_REG_GET(_regName) \ +({\ + int _iChk;\ + unsigned char bReadReg=0;\ + unsigned short _wRet=0;\ + \ + _iChk = _fp9928_get_reg(gbFP9928_REG_##_regName##_##addr,&bReadReg);\ + if(_iChk<0) {\ + _wRet = (unsigned short)(-1);\ + }\ + else {\ + _wRet = bReadReg;\ + gbFP9928_REG_##_regName = bReadReg;\ + DBG_MSG("%s() : FP9928 read reg%s(%02Xh)=0x%02x\n",__FUNCTION__,\ + #_regName,gbFP9928_REG_##_regName##_##addr,bReadReg);\ + }\ + _wRet;\ +}) + +#define FP9928_REG(_regName) gbFP9928_REG_##_regName + + + + + + +static int _fp9928_gpio_init(void) +{ + int iRet = FP9928_RET_SUCCESS; + int iVINState; + + GALLEN_DBGLOCAL_BEGIN(); + + mxc_iomux_v3_setup_pad(GPIO_FP9928_VIN_PADCFG); + if(0!=gpio_request(GPIO_FP9928_VIN, "fp9928_VIN")) { + WARNING_MSG("%s(),request gpio fp9928_VIN fail !!\n",__FUNCTION__); + //gpio_direction_input(GPIO_FP9928_VIN); + } + + iVINState = gpio_get_value(GPIO_FP9928_VIN); + //printk("%s():FP9928 VIN=%d\n",__FUNCTION__,iVINState); + gptFP9928_data->iIsPoweredON=(VIN_ON==iVINState)?1:0; + + mxc_iomux_v3_setup_pad(GPIO_FP9928_EN_PADCFG); + if(0!=gpio_request(GPIO_FP9928_EN, "fp9928_EN")) { + WARNING_MSG("%s(),request gpio fp9928_EN fail !!\n",__FUNCTION__); + //gpio_direction_input(GPIO_FP9928_EN); + } + gpio_direction_output(GPIO_FP9928_EN,EN_OFF); + gptFP9928_data->iIsOutputEnabled = 0; + + + mxc_iomux_v3_setup_pad(GPIO_FP9928_VCOM_PADCFG); + gpio_request(GPIO_FP9928_VCOM, "fp9928_VCOM"); + if(0!=gpio_request(GPIO_FP9928_VCOM, "fp9928_VCOM")) { + WARNING_MSG("%s(),request gpio fp9928_VCOM fail !!\n",__FUNCTION__); + //gpio_direction_input(GPIO_FP9928_VCOM); + } + gpio_direction_output(GPIO_FP9928_VCOM,0); + +#if defined(GPIO_FP9928_EP_3V3_IN) && (GPIO_FP9928_EP_3V3_IN!=GPIO_FP9928_VIN) //[ + if(gptFP9928_data->iIsEP3V3_SW_enabled) { + // E60Q3X revA . + mxc_iomux_v3_setup_pad(GPIO_FP9928_EP_3V3_IN_PADCFG); + gpio_request(GPIO_FP9928_EP_3V3_IN, "fp9928_EP_3V3"); + if(0!=gpio_request(GPIO_FP9928_EP_3V3_IN, "fp9928_EP_3V3")) { + WARNING_MSG("%s(),request gpio fp9928_EP_3V3_IN fail !!\n",__FUNCTION__); + } + gpio_direction_output(GPIO_FP9928_EP_3V3_IN,0); + } +#endif //]GPIO_FP9928_EP_3V3_IN_PADCFG + + GALLEN_DBGLOCAL_END(); + + return iRet; +} + +static void _fp9928_gpio_release(void) +{ + GALLEN_DBGLOCAL_BEGIN(); + if(!gptFP9928_data) { + WARNING_MSG("%s(%d) : %s cannot work before init !\n",__FILE__,__LINE__,__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + return ; + } + gpio_free(GPIO_FP9928_VCOM); + gpio_free(GPIO_FP9928_EN); + gpio_free(GPIO_FP9928_VIN); + +#if defined(GPIO_FP9928_EP_3V3_IN) && (GPIO_FP9928_EP_3V3_IN!=GPIO_FP9928_VIN) //[ + if(gptFP9928_data->iIsEP3V3_SW_enabled) { + gpio_free(GPIO_FP9928_EP_3V3_IN); + } +#endif //]GPIO_FP9928_EP_3V3_IN_PADCFG + GALLEN_DBGLOCAL_END(); +} + + +static int _fp9928_reg_init(void) +{ + //FP9928_REG_SET(FUNC_ADJUST,FIX_RD_PTR,1); + //FP9928_REG_GET(TMST); + //FP9928_REG_GET(TMST); + return 0; +} + +static void _fp9928_reinit_vcom(void) +{ + ASSERT(gptFP9928_data); + if(gptFP9928_data->iIsVCOMNeedReInit) { + int iChk; + DBG_MSG("%s():re-write VCOM to 0x%02X\n",__FUNCTION__,FP9928_REG(VCOM_SETTING)); + iChk = FP9928_REG_SET(VCOM_SETTING,ALL,FP9928_REG(VCOM_SETTING)); + if(iChk>=0) { + gptFP9928_data->iIsVCOMNeedReInit = 0; + } + } +} + +static int _fp9928_output_en(int iIsEnable) +{ + int iRet = FP9928_RET_SUCCESS; + + DBG_MSG("%s(%d)\n",__FUNCTION__,iIsEnable); + + if(!gptFP9928_data) { + WARNING_MSG("%s(%d) : %s cannot work before init !\n",__FILE__,__LINE__,__FUNCTION__); + return FP9928_RET_NOTINITEDSTATE; + } + + if(gptFP9928_data->iIsOutputEnabled == iIsEnable) { + // nothing have to do when state not change . + if(iIsEnable) { + DBG_MSG("%s() : output power already enabled\n",__FUNCTION__); + gpio_direction_output(GPIO_FP9928_VCOM,VCOM_ON); + } + } + else { + //unsigned long dwTickNow = jiffies,dwTicks ; + if(iIsEnable) { + //FP9928_WAIT_TICKSTAMP(gptFP9928_data->dwTickPowerOffEnd,"pwroff stable"); + gpio_direction_output(GPIO_FP9928_EN,EN_ON); + gptFP9928_data->iIsOutputEnabled = 1; + + msleep(10); + _fp9928_reinit_vcom(); + msleep(13); + + gpio_direction_output(GPIO_FP9928_VCOM,VCOM_ON); + msleep(10); + } + else { + + gpio_direction_output(GPIO_FP9928_EN,EN_OFF); + gptFP9928_data->iIsOutputEnabled = 0; + gptFP9928_data->dwTickPowerOffEnd = jiffies + FP9928_POWEROFF_TICKS_MAX; + } + } + + return iRet ; +} + + + +static int _fp9928_vin_onoff(int iIsON) +{ + int iRet = FP9928_RET_SUCCESS; + + ASSERT(gptFP9928_data); + + DBG_MSG("%s(%d)\n",__FUNCTION__,iIsON); + + if(iIsON==gptFP9928_data->iIsPoweredON) { + } + else { + if(iIsON) { + gpio_direction_output(GPIO_FP9928_VIN,VIN_ON); + gptFP9928_data->iIsPoweredON = 1; + //msleep(10); + gptFP9928_data->dwTickPowerOnEnd = jiffies + FP9928_POWERON_WAIT_TICKS; + } + else { + _fp9928_output_en(0); + FP9928_WAIT_TICKSTAMP(gptFP9928_data->dwTickPowerOffEnd,"pwroff stable"); + gpio_direction_output(GPIO_FP9928_VIN,VIN_OFF); + gptFP9928_data->iIsPoweredON = 0; + gptFP9928_data->iIsVCOMNeedReInit = 1; + if(3==gptHWCFG->m_val.bUIConfig) { + // MP/RD mode . + gptFP9928_data->dwTickEP3V3OffEnd = jiffies + 0; + } + else { + gptFP9928_data->dwTickEP3V3OffEnd = jiffies + FP9928_EP3V3OFF_TICKS_MAX; + } + } + } + + return iRet; +} + + +static void _fp9928_pwrdwn_work_func(struct work_struct *work) +{ + GALLEN_DBGLOCAL_BEGIN(); + + mutex_lock(&gptFP9928_data->tPwrLock); + + if(!gptFP9928_data->iIsOutputPowerDownCounting) { + WARNING_MSG("[WARNING]%s(%d): race condition occured !\n",__FILE__,__LINE__); + return ; + } + + _fp9928_output_en(0); + gptFP9928_data->iIsOutputPowerDownCounting = 0; + + if(gptFP9928_data->tPwrdwn_work_param.iIsTurnOffChipPwr) { + _fp9928_vin_onoff(0); + } + + mutex_unlock(&gptFP9928_data->tPwrLock); + + GALLEN_DBGLOCAL_END(); +} + + +/********************************************************************** + * + * public functions . + * +***********************************************************************/ + +int fp9928_power_onoff(int iIsPowerOn,int iIsOutputPwr) +{ + int iRet = FP9928_RET_SUCCESS; + + GALLEN_DBGLOCAL_BEGIN(); + if(!gptFP9928_data) { + WARNING_MSG("%s(%d) : %s cannot work before init !\n",__FILE__,__LINE__,__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + return FP9928_RET_NOTINITEDSTATE; + } + + + + mutex_lock(&gptFP9928_data->tPwrLock); + if (iIsPowerOn) { + _fp9928_vin_onoff(1); + + if(iIsOutputPwr==1) { + gptFP9928_data->iIsOutputPowerDownCounting = 0; + cancel_delayed_work_sync(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work); + _fp9928_output_en(1); + } + } + else { + gptFP9928_data->tPwrdwn_work_param.iIsTurnOffChipPwr = 1; + if(!gptFP9928_data->iIsOutputPowerDownCounting) + { + gptFP9928_data->iIsOutputPowerDownCounting = 1; + cancel_delayed_work_sync(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work); + schedule_delayed_work(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work, \ + FP9928_PWROFFDELAYWORK_TICKS); + } + } + mutex_unlock(&gptFP9928_data->tPwrLock); + + GALLEN_DBGLOCAL_BEGIN(); + return iRet; +} + +int fp9928_output_power(int iIsOutputPwr,int iIsChipPowerDown) +{ + int iRet=FP9928_RET_SUCCESS; + + GALLEN_DBGLOCAL_BEGIN(); + + if(!gptFP9928_data) { + WARNING_MSG("%s(%d) : %s cannot work before init !\n",__FILE__,__LINE__,__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + return FP9928_RET_NOTINITEDSTATE; + } + + + mutex_lock(&gptFP9928_data->tPwrLock); + if(iIsOutputPwr) { + GALLEN_DBGLOCAL_RUNLOG(0); + gptFP9928_data->iIsOutputPowerDownCounting = 0; + cancel_delayed_work_sync(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work); + + if(!gptFP9928_data->iIsPoweredON) { + GALLEN_DBGLOCAL_RUNLOG(1); + // auto power on chip . + _fp9928_vin_onoff(1); + } + + iRet = _fp9928_output_en(1); + } + else { + GALLEN_DBGLOCAL_RUNLOG(2); + if(!gptFP9928_data->iIsOutputPowerDownCounting) { + GALLEN_DBGLOCAL_RUNLOG(3); + + udelay(100);gpio_direction_output(GPIO_FP9928_VCOM,VCOM_OFF); + + gptFP9928_data->tPwrdwn_work_param.iIsTurnOffChipPwr = iIsChipPowerDown; + gptFP9928_data->iIsOutputPowerDownCounting = 1; + cancel_delayed_work_sync(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work); + schedule_delayed_work(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work, \ + FP9928_PWROFFDELAYWORK_TICKS); + } + else { + GALLEN_DBGLOCAL_RUNLOG(4); + DBG_MSG("%s(%d),power down work already exist \n",__FUNCTION__,__LINE__); + } + } + mutex_unlock(&gptFP9928_data->tPwrLock); + + GALLEN_DBGLOCAL_END(); + return iRet; +} + +#define FP9928_SUSPEND_ENABLED 1 + +int fp9928_suspend(void) +{ +#ifdef FP9928_SUSPEND_ENABLED //[ + int iRet = FP9928_RET_SUCCESS; + //int iChk; + + if(!gptFP9928_data) { + WARNING_MSG("%s(%d) : %s cannot work before init !\n", + __FILE__,__LINE__,__FUNCTION__); + return FP9928_RET_NOTINITEDSTATE; + } + + if(gptFP9928_data->iIsOutputEnabled) { + WARNING_MSG("%s() : skip suspend when PMIC output enabled !! (%d)\n",__FUNCTION__, + delayed_work_pending(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work)); + return FP9928_RET_PWRDWNWORKPENDING; + } + + if(gSleep_Mode_Suspend) { + mutex_lock(&gptFP9928_data->tPwrLock); + _fp9928_vin_onoff(0); + mutex_unlock(&gptFP9928_data->tPwrLock); + +#if 0 + //FP9928_WAIT_TICKSTAMP(gptFP9928_data->dwTickEP3V3OffEnd,"pwroff->EP3V3 off stable"); +#else + if(time_before(jiffies,gptFP9928_data->dwTickEP3V3OffEnd)) { + return FP9928_RET_PWRDWNWORKPENDING; + } + else { + #if defined(GPIO_FP9928_EP_3V3_IN) && (GPIO_FP9928_EP_3V3_IN!=GPIO_FP9928_VIN) //[ + gpio_direction_output(GPIO_FP9928_EP_3V3_IN,0); + #endif //] defined(GPIO_FP9928_EP_3V3_IN) && (GPIO_FP9928_EP_3V3_IN!=GPIO_FP9928_VIN) + } +#endif + } + + return iRet; +#else //][! FP9928_SUSPEND_ENABLED + printk("%s() skipped !\n",__FUNCTION__); + return 0; +#endif //] FP9928_SUSPEND_ENABLED +} + +#define AVOID_ANIMATION_LOOP + +void fp9928_shutdown(void) +{ + if(!gptFP9928_data) { + WARNING_MSG("%s(%d) : %s cannot work before init !\n", + __FILE__,__LINE__,__FUNCTION__); + return ; + } + if(delayed_work_pending(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work)) { +#ifdef AVOID_ANIMATION_LOOP //[ + cancel_delayed_work_sync(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work); +#else //][!AVOID_ANIMATION_LOOP + fp9928_power_onoff(0,0); +#endif //] AVOID_ANIMATION_LOOP + } +#ifdef AVOID_ANIMATION_LOOP //[ + //DBG0_ENTRY_TAG(); + mutex_lock(&gptFP9928_data->tPwrLock); + + msleep(jiffies_to_msecs(FP9928_PWROFFDELAYWORK_TICKS)); + //DBG0_ENTRY_TAG(); + _fp9928_output_en(0); + //DBG0_ENTRY_TAG(); + _fp9928_vin_onoff(0); + //DBG0_ENTRY_TAG(); + +#else //][!AVOID_ANIMATION_LOOP + + while (1) { + if(gptFP9928_data->iIsOutputEnabled) { + DBG0_MSG("%s() : waiting for PMIC output disabled !! (%d)\n",__FUNCTION__, + delayed_work_pending(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work)); + msleep(100); + } + else { + break; + } + }// while end . + +#endif //] AVOID_ANIMATION_LOOP + + while (1) { + if(time_before(jiffies,gptFP9928_data->dwTickEP3V3OffEnd)) { + DBG0_MSG("%s() : waiting for VEE stable to power off the EP3V3 ...\n",__FUNCTION__); + msleep(100); + } + else { + #if defined(GPIO_FP9928_EP_3V3_IN) && (GPIO_FP9928_EP_3V3_IN!=GPIO_FP9928_VIN) //[ + gpio_direction_output(GPIO_FP9928_EP_3V3_IN,0); + #endif //] defined(GPIO_FP9928_EP_3V3_IN) && (GPIO_FP9928_EP_3V3_IN!=GPIO_FP9928_VIN) + break; + } + }// while end . + +#ifdef AVOID_ANIMATION_LOOP//[ + mutex_unlock(&gptFP9928_data->tPwrLock); +#endif //]AVOID_ANIMATION_LOOP + +} + +void fp9928_resume(void) +{ +#ifdef FP9928_SUSPEND_ENABLED//[ + if(!gptFP9928_data) { + WARNING_MSG("%s(%d) : %s cannot work before init !\n",__FILE__,__LINE__,__FUNCTION__); + return ; + } + + if(gSleep_Mode_Suspend) { + mutex_lock(&gptFP9928_data->tPwrLock); + _fp9928_vin_onoff(1); + mutex_unlock(&gptFP9928_data->tPwrLock); + + } +#else + printk("%s() skipped !\n",__FUNCTION__); +#endif //] FP9928_SUSPEND_ENABLED +} + +int fp9928_ONOFF(int iIsON) +{ + int iRet=FP9928_RET_SUCCESS; + + if(!(8==gptHWCFG->m_val.bDisplayCtrl)) { + WARNING_MSG("%s() display controller (%d) not match !\n", + __FUNCTION__,(int)gptHWCFG->m_val.bDisplayCtrl); + return 1; + } + + + + mutex_lock(&gptFP9928_data->tPwrLock); + if(iIsON) { +#if defined(GPIO_FP9928_EP_3V3_IN) && (GPIO_FP9928_EP_3V3_IN!=GPIO_FP9928_VIN) //[ + if(gptFP9928_data->iIsEP3V3_SW_enabled) { + DBG_MSG("%s() : Trun ON EP_3V3\n",__FUNCTION__); + gpio_direction_output(GPIO_FP9928_EP_3V3_IN,1); + } +#endif //] + + _fp9928_vin_onoff(1); + } + else { + _fp9928_vin_onoff(0); + +#if defined(GPIO_FP9928_EP_3V3_IN) && (GPIO_FP9928_EP_3V3_IN!=GPIO_FP9928_VIN) //[ + /* + if(gptFP9928_data->iIsEP3V3_SW_enabled) + { + DBG_MSG("%s() : Trun OFF EP_3V3\n",__FUNCTION__); + FP9928_WAIT_TICKSTAMP(gptFP9928_data->dwTickEP3V3OffEnd, + "pwroff->EP3V3 off stable"); + gpio_direction_output(GPIO_FP9928_EP_3V3_IN,0); + } + */ +#endif //] + } + mutex_unlock(&gptFP9928_data->tPwrLock); + + return iRet; +} + + +int fp9928_get_temperature(int *O_piTemperature) +{ + unsigned short wReg; + int iRet=FP9928_RET_SUCCESS; + + int iTemp; + unsigned char bReg,bTemp; + int iOldPowerState; + + //return FP9928_RET_SUCCESS; + + if(!gptFP9928_data) { + WARNING_MSG("%s() cannot work if driver is not initialed \n",__FUNCTION__); + return FP9928_RET_NOTINITEDSTATE; + } + + iOldPowerState = gptFP9928_data->iIsPoweredON; + fp9928_output_power(1,0); + + wReg = FP9928_REG_GET(TMST); + + fp9928_ONOFF(iOldPowerState); + + if(((unsigned short)(-1))==wReg) { + ERR_MSG("%s(%d):%s regTMST read fail !\n",__FILE__,__LINE__,__FUNCTION__); + return FP9928_RET_I2CTRANS_ERR; + } + + bReg = (unsigned char)wReg; + gptFP9928_data->wTempratureData = wReg; + if(bReg&0x80) { + // negative . + bTemp=(~bReg)+1; + iTemp = bTemp; + iTemp = (~iTemp)+1; + } + else { + // positive . + iTemp = (int)(bReg); + } + gptFP9928_data->iCurrent_temprature = iTemp; + printk("%s temprature data = 0x%x,%d\n",DRIVER_NAME,wReg,gptFP9928_data->iCurrent_temprature); + + if(O_piTemperature) { + *O_piTemperature = gptFP9928_data->iCurrent_temprature; + } + + return iRet; +} + + + + + +int fp9928_vcom_set(int iVCOM_mV,int iIsWriteToFlash) +{ + + const int iVCOM_mV_max=FP9928_VCOM_MV_MAX,iVCOM_mV_min=FP9928_VCOM_MV_MIN; + int iVCOM_mV_ABS ; + int iRet=FP9928_RET_SUCCESS; + + if(!gptFP9928_data) { + WARNING_MSG("%s() cannot work if driver is not initialed \n",__FUNCTION__); + return FP9928_RET_NOTINITEDSTATE; + } + + if(iVCOM_mV<iVCOM_mV_min) { + ERR_MSG("%s(%d),VCOM %d cannot < %d mV\n", + __FUNCTION__,__LINE__,iVCOM_mV,iVCOM_mV_min); + } + else if(iVCOM_mV>iVCOM_mV_max) { + ERR_MSG("%s(%d),VCOM %d cannot > %d\n", + __FUNCTION__,__LINE__,iVCOM_mV,iVCOM_mV_max); + } + else { + unsigned char bReg; + int i10uV_Steps; + int i10uV_Steps_mod10; + + int iOldPowerState ; + + + if( iVCOM_mV<(gptFP9928_data->iCurrent_VCOM_mV+FP9928_VCOM_UV_STEP/1000) && \ + iVCOM_mV>(gptFP9928_data->iCurrent_VCOM_mV-FP9928_VCOM_UV_STEP/1000) ) + { + // the VCOM range you want to set is close to current VCOM. + return FP9928_RET_SUCCESS; + } + + if(iVCOM_mV<0) { + iVCOM_mV_ABS = -iVCOM_mV; + } + else { + iVCOM_mV_ABS = iVCOM_mV; + } + + iOldPowerState = gptFP9928_data->iIsPoweredON; + fp9928_ONOFF(1); + i10uV_Steps = (int)(iVCOM_mV_ABS*10000/FP9928_VCOM_UV_STEP); + i10uV_Steps_mod10 = (int)(i10uV_Steps%10); + if(i10uV_Steps_mod10>=5) { + bReg = (unsigned char) ((i10uV_Steps/10)+1); + } + else { + bReg = (unsigned char) (i10uV_Steps/10); + } + printk("%s():want set VCOM %dmV,reg=0x%02X,output %dmV,to flash=%d\n",\ + __FUNCTION__,iVCOM_mV,bReg,bReg*FP9928_VCOM_UV_STEP/1000,iIsWriteToFlash); + iRet = FP9928_REG_SET(VCOM_SETTING,ALL,bReg); + if(iRet>=0) { + gptFP9928_data->iCurrent_VCOM_mV=-((int)FP9928_REG(VCOM_SETTING)*FP9928_VCOM_UV_STEP/1000); + } + fp9928_ONOFF(iOldPowerState); + + } + + return iRet; +} + + +int fp9928_vcom_get(int *O_piVCOM_mV) +{ + int iVCOM_mV; + unsigned short wReg; + int iOldPowerState; + int iRet = FP9928_RET_SUCCESS; + + if(!gptFP9928_data) { + WARNING_MSG("%s() cannot work if driver is not initialed \n",__FUNCTION__); + return FP9928_RET_NOTINITEDSTATE; + } + + iOldPowerState = gptFP9928_data->iIsPoweredON; + fp9928_ONOFF(1); + + _fp9928_reinit_vcom(); + + wReg = FP9928_REG_GET(VCOM_SETTING); + iVCOM_mV = (int)(wReg); + iVCOM_mV = -(iVCOM_mV*FP9928_VCOM_UV_STEP/1000); + //DBG_MSG("%s(%d):iVCOM_mV=%d,wReg=0x%x\n",__FUNCTION__,__LINE__,iVCOM_mV,wReg); + + if(iVCOM_mV!=gptFP9928_data->iCurrent_VCOM_mV) { + WARNING_MSG("%s(%d) VCOM read from register is 0x%x not equal with stored \n",__FUNCTION__,__LINE__,wReg); + } + + if(O_piVCOM_mV) { + *O_piVCOM_mV = iVCOM_mV; + gptFP9928_data->iCurrent_VCOM_mV=iVCOM_mV; + } + + fp9928_ONOFF(iOldPowerState); + + return iRet; +} + +int fp9928_vcom_get_cached(int *O_piVCOM_mV) +{ + if(!gptFP9928_data) { + WARNING_MSG("%s() cannot work if driver is not initialed \n",__FUNCTION__); + return FP9928_RET_NOTINITEDSTATE; + } + + if(O_piVCOM_mV) { + *O_piVCOM_mV = gptFP9928_data->iCurrent_VCOM_mV; + } + + return FP9928_RET_SUCCESS; +} + + +void fp9928_release(void) +{ + if(!gptFP9928_data) { + WARNING_MSG("%s() cannot work if driver is not initialed \n",__FUNCTION__); + return ; + } + + + mutex_lock(&gptFP9928_data->tPwrLock); + _fp9928_vin_onoff(0); + mutex_unlock(&gptFP9928_data->tPwrLock); + + gptFP9928_data->ptI2C_adapter = 0; + i2c_unregister_device(gptFP9928_data->ptI2C_client); + gptFP9928_data->ptI2C_client = 0; + + _fp9928_gpio_release(); + + kfree(gptFP9928_data);gptFP9928_data = 0; + +} + +int fp9928_init(int iPort) +{ + + int iRet = FP9928_RET_SUCCESS; + int iChk; + + unsigned long dwSize; + + GALLEN_DBGLOCAL_BEGIN(); + + if(gptFP9928_data) { + WARNING_MSG("skipped %s() calling over twice !!\n",__FUNCTION__); + return 0; + } + + dwSize=sizeof(FP9928_data); + + gptFP9928_data = kmalloc(dwSize,GFP_KERNEL); + if(!gptFP9928_data) { + iRet = FP9928_RET_MEMNOTENOUGH; + ERR_MSG("%s(%d) : memory not enough !!\n",__FILE__,__LINE__); + GALLEN_DBGLOCAL_RUNLOG(0); + goto MEM_MALLOC_FAIL; + } + + memset(gptFP9928_data,0,sizeof(FP9928_data)); + + if(36==gptHWCFG->m_val.bPCB || 40==gptHWCFG->m_val.bPCB) { + // E60Q3X/E60Q5X + if((0==gptHWCFG->m_val.bPCB_LVL&&gptHWCFG->m_val.bPCB_REV>=0x10) || 40==gptHWCFG->m_val.bPCB) { + // >= E60Q30A10 ,E60Q5X + printk("%s(): EP3V3 switch enabled",__FUNCTION__); + gptFP9928_data->iIsEP3V3_SW_enabled = 1; + } + else { + gptFP9928_data->iIsEP3V3_SW_enabled = 0; + } + } + else { + gptFP9928_data->iIsEP3V3_SW_enabled = 0; + } + + gptFP9928_data->dwTickPowerOffEnd = jiffies; + gptFP9928_data->dwTickPowerOnEnd = jiffies; + gptFP9928_data->dwTickEP3V3OffEnd = jiffies; + + iChk = _fp9928_gpio_init(); + if(iChk<0) { + iRet = FP9928_RET_GPIOINITFAIL; + ERR_MSG("%s(%d) : gpio init fail !!\n",__FILE__,__LINE__); + GALLEN_DBGLOCAL_RUNLOG(1); + goto GPIO_INIT_FAIL; + } + + { + unsigned long dwTicks,dwTickNow; + + _fp9928_vin_onoff(1); + + dwTickNow = jiffies; + FP9928_WAIT_TICKSTAMP(gptFP9928_data->dwTickPowerOnEnd,"power on stable"); + } + + gptFP9928_data->ptI2C_adapter = i2c_get_adapter(iPort-1);// + if( NULL == gptFP9928_data->ptI2C_adapter) { + ERR_MSG ("[Error] %s : FP9928_RET_I2CCHN_NOTFOUND,chn=%d\n",__FUNCTION__,iPort); + GALLEN_DBGLOCAL_RUNLOG(2); + iRet=FP9928_RET_I2CCHN_NOTFOUND; + goto I2CCHN_GET_FAIL; + } + + gptFP9928_data->ptI2C_client = i2c_new_probed_device(gptFP9928_data->ptI2C_adapter, >FP9928_BI,gwFP9928_AddrA,0); + if( NULL == gptFP9928_data->ptI2C_client ) { + GALLEN_DBGLOCAL_RUNLOG(3); + ERR_MSG("[Error] %s : FP9928 probe fail \n",__FUNCTION__); + goto I2CPROBE_DEVICE_FAIL; + } + + gptFP9928_data->iCurrent_VCOM_mV=-((int)FP9928_REG(VCOM_SETTING)*FP9928_VCOM_UV_STEP/1000);// default VCOM voltage . + //fp9928_vcom_get(&gptFP9928_data->iCurrent_VCOM_mV); + + + // kernel objects initialize ... + mutex_init(&gptFP9928_data->tPwrLock); + mutex_init(&gptFP9928_data->tI2CLock); + + INIT_DELAYED_WORK(&gptFP9928_data->tPwrdwn_work_param.pwrdwn_work,_fp9928_pwrdwn_work_func); + + + _fp9928_reg_init(); + fp9928_ONOFF(INIT_POWER_STATE); + + GALLEN_DBGLOCAL_ESC(); + return FP9928_RET_SUCCESS; + + + i2c_unregister_device(gptFP9928_data->ptI2C_client); + gptFP9928_data->ptI2C_client = 0; +I2CPROBE_DEVICE_FAIL: + gptFP9928_data->ptI2C_adapter = 0; +I2CCHN_GET_FAIL: + _fp9928_gpio_release(); +GPIO_INIT_FAIL: + kfree(gptFP9928_data);gptFP9928_data = 0; +MEM_MALLOC_FAIL: + + GALLEN_DBGLOCAL_END(); + return iRet; +} + + diff --git a/drivers/video/mxc/lk_fp9928.h b/drivers/video/mxc/lk_fp9928.h new file mode 100644 index 00000000..4c409682 --- /dev/null +++ b/drivers/video/mxc/lk_fp9928.h @@ -0,0 +1,34 @@ +#ifndef __LK_FP9928_H //[ +#define __LK_FP9928_H + + +#define FP9928_RET_SUCCESS 0 +#define FP9928_RET_GPIOINITFAIL (-1) +#define FP9928_RET_MEMNOTENOUGH (-2) +#define FP9928_RET_NOTINITEDSTATE (-3) +#define FP9928_RET_PWRDWNWORKPENDING (-4) +#define FP9928_RET_I2CCHN_NOTFOUND (-5) +#define FP9928_RET_I2CTRANS_ERR (-6) + + +int fp9928_init(int iPort); +void fp9928_release(void); + + +int fp9928_suspend(void); +void fp9928_resume(void); +void fp9928_shutdown(void); + + +int fp9928_output_power(int iIsOutputPwr,int iIsChipPowerDown); +int fp9928_power_onoff(int iIsPowerOn,int iIsOutputPwr); + +int fp9928_get_temperature(int *O_piTemperature); +int fp9928_vcom_set(int iVCOM_mV,int iIsWriteToFlash); +int fp9928_vcom_get(int *O_piVCOM_mV); +int fp9928_vcom_get_cached(int *O_piVCOM_mV); + +int fp9928_ONOFF(int iIsON); + +#endif //] __LK_FP9928_H + diff --git a/drivers/video/mxc/lk_tps65185.c b/drivers/video/mxc/lk_tps65185.c new file mode 100644 index 00000000..c3937c90 --- /dev/null +++ b/drivers/video/mxc/lk_tps65185.c @@ -0,0 +1,2311 @@ + + +/* + * purpose : TPS65185 driver + * + * author : Gallen Lin + * versions : + * + */ + + +#include <linux/kernel.h> +//#include <linux/config.h> + +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/delay.h> + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/workqueue.h> +#include <linux/completion.h> +#include <linux/wait.h> +#include <linux/semaphore.h> + + +#ifdef CONFIG_MACH_MX6SL_NTX//[ + #define TPS65185_PLATFORM_MX6 1 +#endif//]CONFIG_MACH_MX6SL_NTX + + +#include <mach/hardware.h> +#include <mach/gpio.h> + +#ifdef TPS65185_PLATFORM_MX6//[ + #include <mach/iomux-mx6sl.h> +#else //][!TPS65185_PLATFORM_MX6 + #include <mach/iomux-mx50.h> +#endif //] TPS65185_PLATFORM_MX6 + +#include "ntx_hwconfig.h" + +#define GDEBUG 0 +#include <linux/gallen_dbg.h> + +#include "lk_tps65185.h" + +#define TPS65185_SUSPEND 1 + +// command byte definitions ... + +#define DRIVERNAME "TPS65185" +#define REG_UNKOWN_VAL 0xcc + +#define TOTAL_CHIPS 1 + +//MODULE_LICENSE("GPL"); + +//#define TPS65185_EP3V3_PWROFF 1 // turn off the EP_3V3 when EPD ON/OFF . +#define TPS65185_PWR_ONOFF_INT 1 +//#define TPS65185_PWR_ONOFF_WAITBYCOMPLETE 1 + +#define TPS65185_PWROFFDELAYWORK_TICKS 50 +#define TPS65185_RESUME_EP3V3_ON 1 + +#define VCOM_ENABLE 1 +#define VCOM_DISABLE 0 + +#ifdef TPS65185_PLATFORM_MX6//[ + + #define GPIO_TPS65185_PWRUP IMX_GPIO_NR(2,8) // EPDC_PWRCTRL1 + #define GPIO_TPS65185_PWRUP_PADCFG MX6SL_PAD_EPDC_PWRCTRL1__GPIO_2_8 + + #define GPIO_TPS65185_WAKEUP IMX_GPIO_NR(2,7) // EPDC_PWRCTRL0 + #define GPIO_TPS65185_WAKEUP_PADCFG MX6SL_PAD_EPDC_PWRCTRL0__GPIO_2_7 + + #define GPIO_TPS65185_VCOMCTRL IMX_GPIO_NR(2,3) // EPDC_VCOM0 + #define GPIO_TPS65185_VCOMCTRL_PADCFG MX6SL_PAD_EPDC_VCOM0__GPIO_2_3 + + #define GPIO_TPS65185_VIN IMX_GPIO_NR(2,14) // EPDC_PWRWAKEUP + #define GPIO_TPS65185_VIN_PADCFG MX6SL_PAD_EPDC_PWRWAKEUP__GPIO_2_14 + + #define GPIO_TPS65185_EP_3V3_IN IMX_GPIO_NR(4,3) // EPDC_VDD 1.8V/3.3V . + #define GPIO_TPS65185_EP_3V3_IN_PADCFG MX6SL_PAD_KEY_ROW5__GPIO_4_3 + + // GPIO input + #define GPIO_TPS65185_PWRGOOD IMX_GPIO_NR(2,13) // EPDC_PWRSTAT + #define GPIO_TPS65185_PWRGOOD_INT_PADCFG MX6SL_PAD_EPDC_PWRSTAT__GPIO_2_13_PUINT + #define GPIO_TPS65185_PWRGOOD_GPIO_PADCFG MX6SL_PAD_EPDC_PWRSTAT__GPIO_2_13 + + #define GPIO_TPS65185_INT IMX_GPIO_NR(2,9) // EPDC_PWRCTRL2 + #define GPIO_TPS65185_INT_PADCFG MX6SL_PAD_EPDC_PWRCTRL2__GPIO_2_9_PUINT + #define GPIO_TPS65185_INT_GPIO_PADCFG MX6SL_PAD_EPDC_PWRCTRL2__GPIO_2_9 + + //#define GPIO_TPS65185_SDA + //#define GPIO_TPS65185_SDL + #define set_irq_type(_irq,_irqtype) irq_set_irq_type((_irq),(_irqtype)) + +#else //][!TPS65185_PLATFORM_MX6 + +////////////////////////////////////////////////////////////// +// definitions gpio ... +// GPIO output + #define GPIO_TPS65185_PWRUP (2*32 + 30) /* GPIO_3_30 */ + #define GPIO_TPS65185_PWRUP_PADCFG MX50_PAD_EPDC_PWRCTRL1__GPIO_3_30 + + #define GPIO_TPS65185_WAKEUP (2*32 + 29) /* GPIO_3_29 */ + #define GPIO_TPS65185_WAKEUP_PADCFG MX50_PAD_EPDC_PWRCTRL0__GPIO_3_29 + + #define GPIO_TPS65185_VCOMCTRL (3*32 + 21) /* GPIO_4_21 */ + #define GPIO_TPS65185_VCOMCTRL_PADCFG MX50_PAD_EPDC_VCOM0__GPIO_4_21 + + #define GPIO_TPS65185_VIN (0*32 + 27) /* GPIO_1_27 */ + #define GPIO_TPS65185_VIN_PADCFG MX50_PAD_EIM_CRE__GPIO_1_27 + + // GPIO input + #define GPIO_TPS65185_PWRGOOD (2*32 + 28) /* GPIO_3_28 */ + #define GPIO_TPS65185_PWRGOOD_INT_PADCFG MX50_PAD_EPDC_PWRSTAT__GPIO_3_28_INT + #define GPIO_TPS65185_PWRGOOD_GPIO_PADCFG MX50_PAD_EPDC_PWRSTAT__GPIO_3_28 + + #define GPIO_TPS65185_INT (3*32 + 15) /* GPIO_4_15 */ + #define GPIO_TPS65185_INT_PADCFG MX50_PAD_ECSPI1_SS0__GPIO_4_15_PUINT + #define GPIO_TPS65185_INT_GPIO_PADCFG MX50_PAD_ECSPI1_SS0__GPIO_4_15 + + //#define GPIO_TPS65185_SDA (5*32+21) /* GPIO_6_21 */ + //#define GPIO_TPS65185_SDL (5*32+20) /* GPIO_6_20 */ + // +#endif //] TPS65185_PLATFORM_MX6 +/////////////////////////////////////////////////////// +// definitions for config_epd_timing() ... + +typedef struct tagTPS65185_VERSIONS{ + unsigned char bMajor; + unsigned char bMinor; + unsigned char bVersion; + unsigned char bRevID; +} TPS65185_VERSIONS; + +typedef struct tagTPS65185_data { + int iCurrent_temprature; + unsigned short wTempratureData; + unsigned long dwCurrent_mode;// active , sleep , standby . + TPS65185_VERSIONS t65185_versions; + int iCurrentPwrupState; + int iCurrentWakeupState; + int iIsInitPwrON;// is first power on or not , if yes ,you must to delay 1.8ms for i2c protocol initial . + //unsigned char bRegENABLE; + //int iLast_temprature; + struct semaphore i2clock; + struct semaphore chmod_lock; + int iCurrentVCOM; + int iRestoreVCOM; + unsigned long dwEP3V3_Off_Ticks; +} TPS65185_data; + + +typedef struct tagTPS65185_PWRDWN_WORK_PARAM { + struct delayed_work pwrdwn_work; + unsigned long dwNewMode; + int iIsWaitPwrOff; +} TPS65185_PWRDWN_WORK_PARAM; + +#define LKDRIVER_DATA_INIT(_iChipIdx) \ +{\ + gtTPS65185_DataA[_iChipIdx].iCurrent_temprature=-1;\ + gtTPS65185_DataA[_iChipIdx].wTempratureData=0;\ + gtTPS65185_DataA[_iChipIdx].dwCurrent_mode=TPS65185_MODE_UNKOWN;\ + gtTPS65185_DataA[_iChipIdx].iCurrentPwrupState=-1;\ + gtTPS65185_DataA[_iChipIdx].iCurrentWakeupState=-1;\ + gtTPS65185_DataA[_iChipIdx].iIsInitPwrON=1;\ + sema_init(>TPS65185_DataA[_iChipIdx].i2clock,1);\ + sema_init(>TPS65185_DataA[_iChipIdx].chmod_lock,1);\ +} + +// externals ... +extern volatile NTX_HWCONFIG *gptHWCFG; +extern volatile int gSleep_Mode_Suspend; + + +// +static TPS65185_PWRDWN_WORK_PARAM gtPwrdwn_work_param; + +static struct i2c_adapter *gpI2C_adapter = 0; +static struct i2c_client *gpI2C_clientA[TOTAL_CHIPS] = {0,}; +volatile static int giIsTPS65185_inited=0; +volatile static int giIsTPS65185_gpio_inited=0; +volatile static int giIsTPS65185_turnoff_EP3V3=0; +volatile static unsigned long gdwSafeTick_To_TurnON_RailPower; +volatile static unsigned long gdwSafeTick_To_TurnOFF_EP3V3; + +static struct i2c_board_info gtTPS65185_BIA[TOTAL_CHIPS] = { + { + .type = "tps65185-1", + .addr = 0x68, + .platform_data = NULL, + }, +}; + + +static const unsigned short gwTPS65185_AddrA[] = { + 0x68, + I2C_CLIENT_END +}; + + +static TPS65185_data gtTPS65185_DataA[TOTAL_CHIPS] = { + {-1,0x0000,TPS65185_MODE_UNKOWN,}, +}; + + + +////////////////////////////////////////////////////////////////////////// +// +// internal hardware helper ... +// + + + +// registers (write).... +#define TPS65185_REG_ENABLE_ACTIVE 0x80 +#define TPS65185_REG_ENABLE_STANDBY 0x40 +#define TPS65185_REG_ENABLE_V3P3_EN 0x20 +#define TPS65185_REG_ENABLE_VCOM_EN 0x10 +#define TPS65185_REG_ENABLE_VDDH_EN 0x08 +#define TPS65185_REG_ENABLE_VPOS_EN 0x04 +#define TPS65185_REG_ENABLE_VEE_EN 0x02 +#define TPS65185_REG_ENABLE_VNEG_EN 0x01 +#define TPS65185_REG_ENABLE_ALL 0xff +static volatile unsigned char gbTPS65185_REG_ENABLE=0; // default reset value is zero . +static const unsigned char gbTPS65185_REG_ENABLE_addr=0x01; + +static volatile unsigned char gbTPS65185_REG_VADJ=0x23; // 15V . +static const unsigned char gbTPS65185_REG_VADJ_addr=0x02; + + +#define TPS65185_REG_VCOM1_ALL 0xff +static volatile unsigned char gbTPS65185_REG_VCOM1=0x7d; // . +static const unsigned char gbTPS65185_REG_VCOM1_addr=0x03; + + +#define TPS65185_REG_VCOM2_ACQ 0x80 +#define TPS65185_REG_VCOM2_PROG 0x40 +#define TPS65185_REG_VCOM2_HiZ 0x20 +//#define TPS65185_REG_VCOM2_AVG 0x18 +#define TPS65185_REG_VCOM2_VCOM8 0x01 +#define TPS65185_REG_VCOM2_ALL 0xff +static volatile unsigned char gbTPS65185_REG_VCOM2=0x04; // . +static const unsigned char gbTPS65185_REG_VCOM2_addr=0x04; + +//#define TPS65185_REG_INT_EN1_DTX_EN 0x80 +#define TPS65185_REG_INT_EN1_TSD_EN 0x40 +#define TPS65185_REG_INT_EN1_HOT_EN 0x20 +#define TPS65185_REG_INT_EN1_TMST_HOT_EN 0x10 +#define TPS65185_REG_INT_EN1_TMST_COLD_EN 0x08 +#define TPS65185_REG_INT_EN1_UVLO_EN 0x04 +#define TPS65185_REG_INT_EN1_ACQC_EN 0x02 +#define TPS65185_REG_INT_EN1_PRGC_EN 0x01 +#define TPS65185_REG_INT_EN1_ALL 0xff +static volatile unsigned char gbTPS65185_REG_INT_EN1=0x7f; // . +static const unsigned char gbTPS65185_REG_INT_EN1_addr=0x05; + +#define TPS65185_REG_INT_EN2_VBUVEN 0x80 +#define TPS65185_REG_INT_EN2_VDDHUVEN 0x40 +#define TPS65185_REG_INT_EN2_VNUV_EN 0x20 +#define TPS65185_REG_INT_EN2_VPOSUVEN 0x10 +#define TPS65185_REG_INT_EN2_VEEUVEN 0x08 +#define TPS65185_REG_INT_EN2_VCOMFEN 0x04 +#define TPS65185_REG_INT_EN2_VNEGUVEN 0x02 +#define TPS65185_REG_INT_EN2_EOCEN 0x01 +#define TPS65185_REG_INT_EN2_ALL 0xff +static volatile unsigned char gbTPS65185_REG_INT_EN2=0xff; // . +static const unsigned char gbTPS65185_REG_INT_EN2_addr=0x06; + +#define TPS65185_REG_INT1_ACQC 0x02 +#define TPS65185_REG_INT1_PRGC 0x01 +#define TPS65185_REG_INT1_UVLO 0x04 +#define TPS65185_REG_INT1_HOT 0x20 +#define TPS65185_REG_INT1_TSD 0x40 +static volatile unsigned char gbTPS65185_REG_INT1=0x0; // . +static const unsigned char gbTPS65185_REG_INT1_addr=0x07; + +#define TPS65185_REG_INT2_VB_UV 0x80 +#define TPS65185_REG_INT2_VDDH_UV 0x40 +#define TPS65185_REG_INT2_VN_UV 0x20 +#define TPS65185_REG_INT2_VPOS_UV 0x10 +#define TPS65185_REG_INT2_VEE_UV 0x08 +#define TPS65185_REG_INT2_VCOMF 0x04 +#define TPS65185_REG_INT2_VNEG_UV 0x02 +#define TPS65185_REG_INT2_EOC 0x01 +static volatile unsigned char gbTPS65185_REG_INT2=0x0; // . +static const unsigned char gbTPS65185_REG_INT2_addr=0x08; + + + +static volatile unsigned char gbTPS65185_REG_UPSEQ0=0xe4; // . +static const unsigned char gbTPS65185_REG_UPSEQ0_addr=0x09; + +static volatile unsigned char gbTPS65185_REG_UPSEQ1=0x55; // . +static const unsigned char gbTPS65185_REG_UPSEQ1_addr=0x0a; + + +#define TPS65185_REG_DWNSEQ0_ALL 0xff +static volatile unsigned char gbTPS65185_REG_DWNSEQ0=0x1e; // . +static const unsigned char gbTPS65185_REG_DWNSEQ0_default=0x1e; // . +//static volatile unsigned char gbTPS65185_REG_DWNSEQ0=0x1b; // . +static const unsigned char gbTPS65185_REG_DWNSEQ0_addr=0x0b; + +static volatile unsigned char gbTPS65185_REG_DWNSEQ1=0xe0; // . +static const unsigned char gbTPS65185_REG_DWNSEQ1_addr=0x0c; + +#define TPS65185_REG_TMST1_READ_THERM 0x80 +#define TPS65185_REG_TMST1_CONV_END 0x20 +static volatile unsigned char gbTPS65185_REG_TMST1=0x20; // . +static const unsigned char gbTPS65185_REG_TMST1_addr=0x0d; + +//static unsigned char gbTPS65185_REG_TMST2=0x78; // . +//static const unsigned char gbTPS65185_REG_TMST2_addr=0x0e; + +// registers (read).... +static volatile unsigned char gbTPS65185_REG_TMST_VALUE=0; // +static const unsigned char gbTPS65185_REG_TMST_VALUE_addr=0x00; + +static volatile unsigned char gbTPS65185_REG_PG=0; +static const unsigned char gbTPS65185_REG_PG_addr=0x0f; + +static volatile unsigned char gbTPS65185_REG_REVID=0x45; // default is TPS65185 1p0 . +static const unsigned char gbTPS65185_REG_REVID_addr=0x10; + + + +static int tps65185_set_reg(unsigned char bRegAddr,unsigned char bRegSetVal) +{ + int iRet=TPS65185_RET_SUCCESS; + int iChk; + unsigned char bA[2] ; + //int irq_INT,irq_PG; + +#if 0 + ASSERT(gpI2C_adapter); + ASSERT(gpI2C_clientA[0]); +#else + if(!gpI2C_adapter) { + WARNING_MSG("%s gpI2C_adapter null \n",__FUNCTION__); + return (TPS65185_RET_PARAMERR); + } + if(!gpI2C_clientA[0]) { + WARNING_MSG("%s gpI2C_clientA[0] null \n",__FUNCTION__); + return (TPS65185_RET_PARAMERR); + } +#endif + //irq_INT = gpio_to_irq(GPIO_TPS65185_INT); + //irq_PG = gpio_to_irq(GPIO_TPS65185_PWRGOOD); + + //disable_irq(irq_INT); + //disable_irq(irq_PG); + + down(>TPS65185_DataA[0].i2clock); + bA[0]=bRegAddr; + bA[1]=bRegSetVal; + iChk = i2c_master_send(gpI2C_clientA[0], (const char *)bA, sizeof(bA)); + if (iChk < 0) { + ERR_MSG("%s(%d):%d=%s(),regAddr=0x%x,regVal=0x%x fail !\n",__FILE__,__LINE__,\ + iChk,"i2c_master_send",bRegAddr,bRegSetVal); + iRet=TPS65185_RET_I2CTRANS_ERR; + } + up(>TPS65185_DataA[0].i2clock); + //enable_irq(irq_PG); + //enable_irq(irq_INT); + return iRet; +} + +static int tps65185_get_reg(unsigned char bRegAddr,unsigned char *O_pbRegVal) +{ + int iRet=TPS65185_RET_SUCCESS; + int iChk; + unsigned char bA[1] ; + //int irq_INT , irq_PG; + +#if 0 + ASSERT(gpI2C_adapter); + ASSERT(gpI2C_clientA[0]); +#else + if(!gpI2C_adapter) { + WARNING_MSG("%s gpI2C_adapter null \n",__FUNCTION__); + return (TPS65185_RET_PARAMERR); + } + if(!gpI2C_clientA[0]) { + WARNING_MSG("%s gpI2C_clientA[0] null \n",__FUNCTION__); + return (TPS65185_RET_PARAMERR); + } +#endif + + ASSERT(O_pbRegVal); + + bA[0]=bRegAddr; + + + if(!in_interrupt()) { + + //irq_INT = gpio_to_irq(GPIO_TPS65185_INT); + //irq_PG = gpio_to_irq(GPIO_TPS65185_PWRGOOD); + + //disable_irq(irq_INT); + //disable_irq(irq_PG); + down(>TPS65185_DataA[0].i2clock); + } + + iChk = i2c_master_send(gpI2C_clientA[0], (const char *)bA, 1); + if (iChk < 0) { + ERR_MSG("%s(%d):%s i2c_master_send fail !\n",__FILE__,__LINE__,__FUNCTION__); + iRet = TPS65185_RET_I2CTRANS_ERR; + } + + + iChk = i2c_master_recv(gpI2C_clientA[0], bA, 1); + if (iChk < 0) { + ERR_MSG("%s(%d):%s i2c_master_recv fail !\n",__FILE__,__LINE__,__FUNCTION__); + iRet = TPS65185_RET_I2CTRANS_ERR; + } + + if(!in_interrupt()) { + up(>TPS65185_DataA[0].i2clock); + //enable_irq(irq_PG); + //enable_irq(irq_INT); + } + + if(iRet>=0) { + *O_pbRegVal = bA[0]; + } + + return iRet; +} + + +#define TPS65185_REG_SET(_regName,_bFieldName,_bSetVal) \ +({\ + int _iRet=TPS65185_RET_SUCCESS;\ + int _iChk;\ + unsigned char _bNewReg,_bFieldMask;\ + \ + _bFieldMask=(unsigned char)TPS65185_REG_##_regName##_##_bFieldName;\ + if(0xff==_bFieldMask) {\ + _bNewReg = _bSetVal;\ + }\ + else {\ + _bNewReg=gbTPS65185_REG_##_regName;\ + if(_bSetVal) {\ + _bNewReg |= _bFieldMask ;\ + }\ + else {\ + _bNewReg &= ~_bFieldMask;\ + }\ + }\ + \ + _iChk = tps65185_set_reg(gbTPS65185_REG_##_regName##_##addr,_bNewReg);\ + if(_iChk<0) {\ + _iRet = _iChk;\ + }\ + else {\ + DBG_MSG("%s() : tps65185 write reg%s(%02Xh) 0x%02x->0x%02x\n",__FUNCTION__,\ + #_regName,gbTPS65185_REG_##_regName##_##addr,gbTPS65185_REG_##_regName,_bNewReg);\ + gbTPS65185_REG_##_regName = _bNewReg;\ + }\ + _iRet;\ +}) + +#define TPS65185_REG_GET(_regName) \ +({\ + int _iChk;\ + unsigned char bReadReg=0;\ + unsigned short _wRet=0;\ + \ + _iChk = tps65185_get_reg(gbTPS65185_REG_##_regName##_##addr,&bReadReg);\ + if(_iChk<0) {\ + _wRet = (unsigned short)(-1);\ + }\ + else {\ + _wRet = bReadReg;\ + gbTPS65185_REG_##_regName = bReadReg;\ + DBG_MSG("%s() : tps65185 read reg%s(%02Xh)=0x%02x\n",__FUNCTION__,\ + #_regName,gbTPS65185_REG_##_regName##_##addr,bReadReg);\ + }\ + _wRet;\ +}) + +#define TPS65185_REG(_regName) gbTPS65185_REG_##_regName + +#if 0 //[ +#define TPS65185_VCOM_OUT(_out_val) \ + if(_out_val) { \ + gpio_direction_output(GPIO_TPS65185_VCOMCTRL, VCOM_ENABLE); \ + } \ + else { \ + gpio_direction_output(GPIO_TPS65185_VCOMCTRL, VCOM_DISABLE); \ + } +#else //][! +#define TPS65185_VCOM_OUT(_out_val) \ + if(_out_val) { \ + unsigned char bReg;\ + gpio_direction_output(GPIO_TPS65185_VCOMCTRL, VCOM_ENABLE); \ + bReg = (unsigned char)TPS65185_REG_GET(ENABLE);\ + if(!(bReg&TPS65185_REG_ENABLE_VCOM_EN)) {\ + WARNING_MSG("[WARNING] %s():ENABLE=0x%x, VOM_EN=0 but gpio=1\n",__FUNCTION__,bReg);\ + TPS65185_REG_SET(ENABLE,VCOM_EN,1);\ + }\ + } \ + else { \ + gpio_direction_output(GPIO_TPS65185_VCOMCTRL, VCOM_DISABLE); \ + } +#endif //] + +DECLARE_WAIT_QUEUE_HEAD(tps65185_ACQC_WQ); +DECLARE_WAIT_QUEUE_HEAD(tps65185_PRGC_WQ); + + +static struct work_struct tps65185_int_work; +static struct workqueue_struct *tps65185_int_workqueue; + +static void tps65185_int_func(struct work_struct *work) +{ + unsigned char bRegINT1,bRegINT2; + unsigned short wReg; + + + wReg = TPS65185_REG_GET(INT1); + if(((unsigned short)(-1))==wReg) { + ERR_MSG("%s(%d):%s regINT1 read fail !\n",__FILE__,__LINE__,__FUNCTION__); + } + bRegINT1=(unsigned char)wReg; + + wReg = TPS65185_REG_GET(INT2); + if(((unsigned short)(-1))==wReg) { + ERR_MSG("%s(%d):%s regINT2 read fail !\n",__FILE__,__LINE__,__FUNCTION__); + } + bRegINT2=(unsigned char)wReg; + + if(bRegINT1&TPS65185_REG_INT1_ACQC) { + wake_up_all(&tps65185_ACQC_WQ); + } + + if(bRegINT1&TPS65185_REG_INT1_PRGC) { + wake_up_all(&tps65185_PRGC_WQ); + } + + if(bRegINT1&TPS65185_REG_INT1_UVLO) { + ERR_MSG("%s(%d):%s input voltage is below UVLO threshold !\n",__FILE__,__LINE__,__FUNCTION__); + } + if(bRegINT1&TPS65185_REG_INT1_TSD) { + ERR_MSG("%s(%d):%s chip is over-temperature shutdown !\n",__FILE__,__LINE__,__FUNCTION__); + } + if(bRegINT1&TPS65185_REG_INT1_HOT) { + ERR_MSG("%s(%d):%s chip is approaching over-temperature shutdown !\n",__FILE__,__LINE__,__FUNCTION__); + } + + if(bRegINT2&TPS65185_REG_INT2_VB_UV) { + ERR_MSG("%s(%d):%s under-voltage on DCDC1 detected !\n",__FILE__,__LINE__,__FUNCTION__); + } + if(bRegINT2&TPS65185_REG_INT2_VDDH_UV) { + ERR_MSG("%s(%d):%s under-voltage on VDDH charge pump detected !\n",__FILE__,__LINE__,__FUNCTION__); + } + if(bRegINT2&TPS65185_REG_INT2_VN_UV) { + ERR_MSG("%s(%d):%s under-voltage on DCDC2 detected !\n",__FILE__,__LINE__,__FUNCTION__); + } + if(bRegINT2&TPS65185_REG_INT2_VPOS_UV) { + ERR_MSG("%s(%d):%s under-voltage on LDO1(VPOS) detected !\n",__FILE__,__LINE__,__FUNCTION__); + } + if(bRegINT2&TPS65185_REG_INT2_VEE_UV) { + ERR_MSG("%s(%d):%s under-voltage on VEE charge pump detected !\n",__FILE__,__LINE__,__FUNCTION__); + } + if(bRegINT2&TPS65185_REG_INT2_VCOMF) { + ERR_MSG("%s(%d):%s fault on VCOM detected !\n",__FILE__,__LINE__,__FUNCTION__); + } + if(bRegINT2&TPS65185_REG_INT2_VNEG_UV) { + ERR_MSG("%s(%d):%s under-voltage on LDO2(VNEG) detected !\n",__FILE__,__LINE__,__FUNCTION__); + } + +#if 0 + if(bRegINT2&TPS65185_REG_INT2_EOC) { + ERR_MSG("%s(%d):%s ADC conversion is complete (temperature acquisition is complete !\n",__FILE__,__LINE__,__FUNCTION__); + } +#endif + + //DBG0_MSG("%s() : INT1=0x%x,INT2=0x%x\n",__FUNCTION__,bRegINT1,bRegINT2); +} + + +static irqreturn_t tps65185_int(int irq, void *dev_id) +{ + DBG_MSG("[%s-%d] tps65185 interrupt triggered !!!\n",__func__,__LINE__); + queue_work(tps65185_int_workqueue,&tps65185_int_work); + + return 0; +} + + +#ifdef TPS65185_PWR_ONOFF_WAITBYCOMPLETE//[ + +DECLARE_COMPLETION(tps65185_pwrgood_on_completion); +DECLARE_COMPLETION(tps65185_pwrgood_off_completion); +#else //][!TPS65185_PWR_ONOFF_WAITBYCOMPLETE +DECLARE_WAIT_QUEUE_HEAD(tps65185_pwron_wq); +DECLARE_WAIT_QUEUE_HEAD(tps65185_pwroff_wq); +#endif //] TPS65185_PWR_ONOFF_WAITBYCOMPLETE + +static struct work_struct tps65185_pwrgood_work; +static struct workqueue_struct *tps65185_pwrgood_workqueue; +volatile int giIsTPS65185_PwrOn=0; +static void tps65185_pwrgood_func(struct work_struct *work) +{ + int iIsPwrOn; + + #if 0 + { + unsigned char bRegPG; + unsigned short wReg; + + wReg = TPS65185_REG_GET(PG); + if(((unsigned short)(-1))==wReg) { + ERR_MSG("%s(%d):%s regPG read fail !\n",__FILE__,__LINE__,__FUNCTION__); + } + else { + bRegPG=(unsigned char)wReg; + DBG_MSG("%s() : PG=0x%x \n",__FUNCTION__,bRegPG); + } + } + #endif + + iIsPwrOn=giIsTPS65185_PwrOn=gpio_get_value(GPIO_TPS65185_PWRGOOD); + DBG_MSG("%s() : powergood signal=%d \n",__FUNCTION__,iIsPwrOn); + + if(iIsPwrOn) { +#ifdef TPS65185_PWR_ONOFF_WAITBYCOMPLETE//[ + + complete_all(&tps65185_pwrgood_on_completion); +#else //][!TPS65185_PWR_ONOFF_WAITBYCOMPLETE + wake_up_interruptible_all(&tps65185_pwron_wq); +#endif //] TPS65185_PWR_ONOFF_WAITBYCOMPLETE + } + else{ +#ifdef TPS65185_PWR_ONOFF_WAITBYCOMPLETE//[ + + complete_all(&tps65185_pwrgood_off_completion); +#else//][!TPS65185_PWR_ONOFF_WAITBYCOMPLETE + wake_up_interruptible_all(&tps65185_pwroff_wq); +#endif//] TPS65185_PWR_ONOFF_WAITBYCOMPLETE + } +} + + +static irqreturn_t tps65185_pwrgood_inthandler(int irq, void *dev_id) +{ + DBG_MSG("[%s-%d] tps65185 pwrgood interrupt triggered !!!\n",__func__,__LINE__); + //queue_work(tps65185_pwrgood_workqueue,&tps65185_pwrgood_work); + // + tps65185_pwrgood_func(&tps65185_pwrgood_work); + return 0; +} + +static void _tps65185_pwrdwn(void) +{ + unsigned long dwCurrentMode,dwNewMode; + int iIsWaitPwrOff; + + // parameters setup ... + dwCurrentMode = gtTPS65185_DataA[0].dwCurrent_mode; + dwNewMode = gtPwrdwn_work_param.dwNewMode ; + iIsWaitPwrOff = gtPwrdwn_work_param.iIsWaitPwrOff; + + DBG_MSG("%s : mode=%ld begin\n",__FUNCTION__,dwNewMode); + + ASSERT(giIsTPS65185_inited); + + if(dwCurrentMode==dwNewMode) { + DBG_MSG("%s : skip same mode\n",__FUNCTION__); + goto exit ; + } + + gpio_direction_output(GPIO_TPS65185_PWRUP, 0); + if( TPS65185_MODE_ACTIVE==dwCurrentMode) { + if(3==gptHWCFG->m_val.bUIConfig) { + // MP/RD mode . + gdwSafeTick_To_TurnOFF_EP3V3 = jiffies+0; + } + else { + gdwSafeTick_To_TurnOFF_EP3V3 = jiffies+ \ + gtTPS65185_DataA[0].dwEP3V3_Off_Ticks; + } + if(iIsWaitPwrOff) { + tps65185_wait_panel_poweroff(); + } + } + + + if( dwNewMode == TPS65185_MODE_SLEEP) { + if( giIsTPS65185_turnoff_EP3V3 ) + { + // Turn off EV_3V3 ... + //TPS65185_REG_SET(ENABLE,V3P3_EN,0); +#ifdef GPIO_TPS65185_EP_3V3_IN //[ + //udelay(10);gpio_direction_output(GPIO_TPS65185_EP_3V3_IN, 0); +#endif //]GPIO_TPS65185_EP_3V3_IN + } + gpio_direction_output(GPIO_TPS65185_WAKEUP, 0); + } + + gtTPS65185_DataA[0].dwCurrent_mode = dwNewMode; + +exit: + DBG_MSG("%s : mode=%ld end .\n",__FUNCTION__,dwNewMode); +} + +static void tps65185_pwrdwn_work_func(struct work_struct *work) +{ + down(>TPS65185_DataA[0].chmod_lock); + _tps65185_pwrdwn(); + up(>TPS65185_DataA[0].chmod_lock); +} + + + +// +static int tps65185_gpio_init(void) +{ + int iRet=TPS65185_RET_SUCCESS; + int iChk; + int irq; + + if(giIsTPS65185_gpio_inited) { + return TPS65185_RET_SUCCESS; + } + + // inputs + mxc_iomux_v3_setup_pad(GPIO_TPS65185_PWRGOOD_INT_PADCFG); + gpio_request(GPIO_TPS65185_PWRGOOD, "tps65185_PWRGOOD"); + gpio_direction_input(GPIO_TPS65185_PWRGOOD); + //giIsTPS65185_PwrOn=gpio_get_value(GPIO_TPS65185_PWRGOOD); + +#ifdef TPS65185_PWR_ONOFF_INT//[ + + tps65185_pwrgood_workqueue=create_singlethread_workqueue("tps65185_PWRGOOD"); + INIT_WORK(&tps65185_pwrgood_work, tps65185_pwrgood_func); + + irq = gpio_to_irq(GPIO_TPS65185_PWRGOOD); + set_irq_type(irq, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING);// + //disable_irq_wake(irq); +#endif //] TPS65185_PWR_ONOFF_INT + + + + mxc_iomux_v3_setup_pad(GPIO_TPS65185_INT_PADCFG); + gpio_request(GPIO_TPS65185_INT, "tps65185_INT"); + gpio_direction_input (GPIO_TPS65185_INT); + + tps65185_int_workqueue=create_singlethread_workqueue("tps65185_INT"); + INIT_WORK(&tps65185_int_work, tps65185_int_func); + + irq = gpio_to_irq(GPIO_TPS65185_INT); + set_irq_type(irq, IRQF_TRIGGER_FALLING);//IRQF_TRIGGER_RISING| + //disable_irq_wake(irq); + + + // outputs +#ifdef GPIO_TPS65185_EP_3V3_IN //[ + mxc_iomux_v3_setup_pad(GPIO_TPS65185_EP_3V3_IN_PADCFG); + gpio_request(GPIO_TPS65185_EP_3V3_IN, "tps65185_EP_3V3_IN"); + gpio_direction_output(GPIO_TPS65185_EP_3V3_IN, 1); +#endif //]GPIO_TPS65185_EP_3V3_IN + gdwSafeTick_To_TurnOFF_EP3V3 = jiffies; + + mxc_iomux_v3_setup_pad(GPIO_TPS65185_VIN_PADCFG); + gpio_request(GPIO_TPS65185_VIN, "tps65185_VIN"); + gpio_direction_output(GPIO_TPS65185_VIN, 1); + mdelay(5); + gdwSafeTick_To_TurnON_RailPower = jiffies+4; + + + mxc_iomux_v3_setup_pad(GPIO_TPS65185_PWRUP_PADCFG); + gpio_request(GPIO_TPS65185_PWRUP, "tps65185_PWRUP"); + gpio_direction_output(GPIO_TPS65185_PWRUP, 0); + + mxc_iomux_v3_setup_pad(GPIO_TPS65185_WAKEUP_PADCFG); + gpio_request(GPIO_TPS65185_WAKEUP, "tps65185_WAKEUP"); + gpio_direction_output(GPIO_TPS65185_WAKEUP, 1); + mdelay(2); + gtTPS65185_DataA[0].dwCurrent_mode=TPS65185_MODE_STANDBY; + + mxc_iomux_v3_setup_pad(GPIO_TPS65185_VCOMCTRL_PADCFG); + gpio_request(GPIO_TPS65185_VCOMCTRL, "tps65185_VCOMCTRL"); + gpio_direction_output(GPIO_TPS65185_VCOMCTRL, VCOM_DISABLE); + + giIsTPS65185_gpio_inited=1; + + return iRet; +} + + +static int tps65185_gpio_release(void) +{ + int iRet=TPS65185_RET_SUCCESS; + int irq; + + + // release gpios ... + + +#if 0 +#ifdef TPS65185_PWR_ONOFF_INT//[ + + irq = gpio_to_irq(GPIO_TPS65185_PWRGOOD); + free_irq(irq,0); + flush_workqueue(tps65185_pwrgood_workqueue); + destroy_workqueue(tps65185_pwrgood_workqueue); + +#endif //] TPS65185_PWR_ONOFF_INT + + irq = gpio_to_irq(GPIO_TPS65185_INT); + free_irq(irq,0); + flush_workqueue(tps65185_int_workqueue); + destroy_workqueue(tps65185_int_workqueue); + + if(giIsTPS65185_gpio_inited) { + gpio_free(GPIO_TPS65185_PWRGOOD); + gpio_free(GPIO_TPS65185_INT); + gpio_free(GPIO_TPS65185_VIN); + gpio_free(GPIO_TPS65185_PWRUP); + gpio_free(GPIO_TPS65185_WAKEUP); + gpio_free(GPIO_TPS65185_VCOMCTRL); + } +#endif + + return iRet; +} + + + + + + + +////////////////////////////////////////////////////////////////////////// +// +// internal helper ... +// + + + +static int tps65185_chk_PG(unsigned char I_bChkMask) +{ + int iRet; + unsigned short wReg; + unsigned char bReg; + + + wReg = TPS65185_REG_GET(PG); + + if(((unsigned short)(-1))==wReg) { + return TPS65185_RET_REGREADFAIL; + } + + bReg=(unsigned char)wReg; + //bChkMask=0xfa; + + bReg &= ~I_bChkMask; + if(I_bChkMask==bReg) { + iRet = TPS65185_RET_ALLPOWERGOOD; + } + else { + //printk(KERN_ERR "PG reg=0x%02X,Chk=0x02%X\n",bReg,I_bChkMask); + iRet = TPS65185_RET_POWERNOTGOOD; + } + + return iRet; +} + + + +static int tps65185_get_versions(TPS65185_VERSIONS *O_pt65185ver) +{ + int iRet=TPS65185_RET_SUCCESS; + int iChk; + unsigned short wReg; + unsigned char bReg; + + ASSERT(gpI2C_adapter); + ASSERT(O_pt65185ver); + + wReg = TPS65185_REG_GET(REVID); + + if(((unsigned short)(-1))==wReg) { + return TPS65185_RET_REGREADFAIL; + } + + bReg=(unsigned char)wReg; + O_pt65185ver->bMajor = (bReg>>6)&0x3; + O_pt65185ver->bMinor = (bReg>>4)&0x3; + O_pt65185ver->bVersion = (bReg)&0xf; + O_pt65185ver->bRevID = bReg; + + return iRet; +} + + + +static int tps65185_config_epd_timing(int iEPDTimingType) +{ + int iRet=TPS65185_RET_SUCCESS; + + + return iRet; +} + +static int tps65185_reg_init(int I_iIsEP_3V3_ON) +{ + int iRet=TPS65185_RET_SUCCESS; + unsigned char bRegVal; + + GALLEN_DBGLOCAL_BEGIN(); + + bRegVal = TPS65185_REG_ENABLE_VCOM_EN|TPS65185_REG_ENABLE_VDDH_EN|\ + TPS65185_REG_ENABLE_VPOS_EN|TPS65185_REG_ENABLE_VEE_EN|TPS65185_REG_ENABLE_VNEG_EN; + if(1==I_iIsEP_3V3_ON) { + GALLEN_DBGLOCAL_RUNLOG(0); + bRegVal |= TPS65185_REG_ENABLE_V3P3_EN; + } + else if(0==I_iIsEP_3V3_ON) { + GALLEN_DBGLOCAL_RUNLOG(5); + bRegVal &= ~TPS65185_REG_ENABLE_V3P3_EN; + } + else if(-1==I_iIsEP_3V3_ON) { + GALLEN_DBGLOCAL_RUNLOG(6); + if( TPS65185_REG(ENABLE) & TPS65185_REG_ENABLE_V3P3_EN) { + bRegVal |= TPS65185_REG_ENABLE_V3P3_EN; + } + else { + bRegVal &= ~TPS65185_REG_ENABLE_V3P3_EN; + } + } + + DBG_MSG("%s() EP3V3ON=%d,ENABLE REG=>%x\n",__FUNCTION__,I_iIsEP_3V3_ON,bRegVal); + + iRet = TPS65185_REG_SET(ENABLE,ALL,bRegVal); + if(iRet<0) { + GALLEN_DBGLOCAL_RUNLOG(1); + goto error; + } + + + //bRegVal = 0; + bRegVal = 0x7f; + iRet = TPS65185_REG_SET(INT_EN1,ALL,bRegVal); + if(iRet<0) { + GALLEN_DBGLOCAL_RUNLOG(2); + goto error; + } + + bRegVal = 0xff; + iRet = TPS65185_REG_SET(INT_EN2,ALL,bRegVal); + if(iRet<0) { + GALLEN_DBGLOCAL_RUNLOG(3); + goto error; + } + + bRegVal = gbTPS65185_REG_DWNSEQ0_default; + iRet = TPS65185_REG_SET(DWNSEQ0,ALL,bRegVal); + if(iRet<0) { + GALLEN_DBGLOCAL_RUNLOG(4); + goto error; + } + +error: + GALLEN_DBGLOCAL_END(); + return iRet; +} + +static void tps65185_restore_vcom(void) +{ + if(gtTPS65185_DataA[0].iRestoreVCOM) { + int iRestoreVCOM = gtTPS65185_DataA[0].iRestoreVCOM; + int iCurrentVCOM ; + + gtTPS65185_DataA[0].iRestoreVCOM=0; + tps65185_vcom_get(&iCurrentVCOM); + printk("restore vcom from %dmV to %dmV\n",iCurrentVCOM,iRestoreVCOM); + tps65185_vcom_set(iRestoreVCOM,0); + } +} + + +// auto detect tps65185 . +// parameters : +// iPort : i2c channel in system (from 1~3) . +// iEPDTimingType : +int tps65185_init(int iPort,int iEPDTimingType) +{ + int iRet = TPS65185_RET_SUCCESS; + int iChipIdx; + unsigned long dw65185mode; + int iChk; + int irq; + + GALLEN_DBGLOCAL_BEGIN(); + + //printk ("%s(%d) \n",__func__,iPort); + + if(0!=giIsTPS65185_inited) { + WARNING_MSG("%s(%d):skip ps65185 init twice !\n",__FILE__,__LINE__); + return TPS65185_RET_SUCCESS; + } + + iChk = tps65185_gpio_init(); + if(iChk<0) { + ERR_MSG("[Error] %s : gpio init fail !\n",__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + return iChk; + } + + gpI2C_adapter = i2c_get_adapter(iPort-1);// + if( NULL == gpI2C_adapter ) + { + ERR_MSG ("[Error] %s : TPS65185_RET_I2CCHN_NOTFOUND\n",__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + return TPS65185_RET_I2CCHN_NOTFOUND; + } + + + for(iChipIdx=0;iChipIdx<TOTAL_CHIPS;iChipIdx++) { + LKDRIVER_DATA_INIT(iChipIdx); + +#if 0 //[ + gpI2C_clientA[iChipIdx] = i2c_new_device(gpI2C_adapter, >TPS65185_BIA[iChipIdx]); +#else //][! + +#ifdef TPS65185_PLATFORM_MX6//[ + gpI2C_clientA[iChipIdx] = \ + i2c_new_probed_device(gpI2C_adapter, >TPS65185_BIA[iChipIdx],gwTPS65185_AddrA,0); +#else //][!TPS65185_PLATFORM_MX6 + gpI2C_clientA[iChipIdx] = \ + i2c_new_probed_device(gpI2C_adapter, >TPS65185_BIA[iChipIdx],gwTPS65185_AddrA); +#endif //]TPS65185_PLATFORM_MX6 + +#endif//] + + if(NULL == gpI2C_clientA[iChipIdx]) { + tps65185_release(); + GALLEN_DBGLOCAL_ESC(); + ERR_MSG("[Error] %s : TPS65185_RET_NEWDEVFAIL\n",__FUNCTION__); + return TPS65185_RET_NEWDEVFAIL; + } + printk("client%d@i2c%d ,addr=0x%x,name=%s\n",iChipIdx,iPort, + gpI2C_clientA[iChipIdx]->addr,gpI2C_clientA[iChipIdx]->name); + + } + + + INIT_DELAYED_WORK(>Pwrdwn_work_param.pwrdwn_work,tps65185_pwrdwn_work_func); + + + if( gptHWCFG->m_val.bPCB>=34) + { + // new HW design after E606FXA... + giIsTPS65185_turnoff_EP3V3=1; + if(5==gptHWCFG->m_val.bDisplayResolution) { + // resolution is 1448x1072 ... +#if 1 + // measured on Q92 . + gtTPS65185_DataA[0].dwEP3V3_Off_Ticks = 800;// times wait VEE to 0V . +#else + gtTPS65185_DataA[0].dwEP3V3_Off_Ticks = 200;// times wait VEE >= -7.4V . +#endif + } + else if(1==gptHWCFG->m_val.bDisplayResolution) { + // resolution is 1024x758 ... + gtTPS65185_DataA[0].dwEP3V3_Off_Ticks = 400;//measured on Q32 . + } + else if(0==gptHWCFG->m_val.bDisplayResolution) { + // resolution is 800x600 ... + gtTPS65185_DataA[0].dwEP3V3_Off_Ticks = 650;//measured on Q92 . + } + else { + gtTPS65185_DataA[0].dwEP3V3_Off_Ticks = 0; + } + } + printk("%s(%d): EP_3V3 ON/OFF control enabled !EP3V3 OFF ticks=%d\n",__FILE__,__LINE__,gtTPS65185_DataA[0].dwEP3V3_Off_Ticks); + giIsTPS65185_inited=1; + + // change TPS65185 to standby mode . + dw65185mode = TPS65185_MODE_STANDBY; + iChk = tps65185_chg_mode(&dw65185mode,1); + if(iChk<0) { + ERR_MSG("[Error] %s : change to standby mode fail !\n",__FUNCTION__); + tps65185_release(); + GALLEN_DBGLOCAL_ESC(); + return iChk; + } + +#if 0 + iChk = tps65185_reg_init(0); + if(iChk<0) { + ERR_MSG("[Error] %s : tps65185 regs init fail !\n",__FUNCTION__); + tps65185_release(); + GALLEN_DBGLOCAL_ESC(); + return iChk; + } +#endif + + + iChk = tps65185_get_versions(>TPS65185_DataA[0].t65185_versions); + printk("TPS65185 versions : major=0x%x,minor=0x%x,version=0x%x,RevID=0x%x\n", + gtTPS65185_DataA[0].t65185_versions.bMajor, + gtTPS65185_DataA[0].t65185_versions.bMinor, + gtTPS65185_DataA[0].t65185_versions.bVersion, + gtTPS65185_DataA[0].t65185_versions.bRevID); + + if(iChk<0) { + ERR_MSG("[Error] %s : get TPS65185 version fail !\n",__FUNCTION__); + tps65185_release(); + GALLEN_DBGLOCAL_ESC(); + return iChk; + } + + + //tps65185_get_temperature(0,0);// test . + + + // + // config registers of TPS65185 ... + // + iChk = tps65185_config_epd_timing(iEPDTimingType); + if(iChk<0) { + ERR_MSG("[Error] %s : config EPD timing fail !\n",__FUNCTION__); + tps65185_release(); + GALLEN_DBGLOCAL_ESC(); + return iChk; + } + printk ("[%s-%d] gpio_to_irq ()\n",__func__,__LINE__); + +#ifdef TPS65185_PWR_ONOFF_INT//[ + + irq = gpio_to_irq(GPIO_TPS65185_PWRGOOD); + iChk = request_irq(irq, tps65185_pwrgood_inthandler, 0, "tps65185_PWRGOOD", 0); + if (iChk) { + pr_info("register TPS65185 pwrgood interrupt failed\n"); + } + else { + //enable_irq_wake(irq); + //disable_irq_wake(irq); + //disable_irq(irq); + } +#endif //]TPS65185_PWR_ONOFF_INT + +#if 1 + irq = gpio_to_irq(GPIO_TPS65185_INT); + iChk = request_irq(irq, tps65185_int, 0, "tps65185_INT", 0); + if (iChk) { + pr_info("register TPS65185 interrupt failed\n"); + } + else { + //enable_irq_wake(irq); + //disable_irq_wake(irq); + //disable_irq(irq); + } +#endif + + + /* + dw65185mode = TPS65185_MODE_ACTIVE; + iChk = tps65185_chg_mode(&dw65185mode,1); + if(iChk<0) { + ERR_MSG("[Error] %s : change to active mode fail !\n",__FUNCTION__); + tps65185_release(); + GALLEN_DBGLOCAL_ESC(); + return iChk; + } + */ + GALLEN_DBGLOCAL_END(); + return iRet; +} + + + + + +int tps65185_release(void) +{ + int iRet = TPS65185_RET_SUCCESS; + int iChipIdx ; + int iChk; + unsigned long dw65185mode ; + + GALLEN_DBGLOCAL_BEGIN(); + //printk("%s(%d):%s()\n",__FILE__,__LINE__,__FUNCTION__); + for(iChipIdx=0;iChipIdx<TOTAL_CHIPS;iChipIdx++) { + if(gpI2C_clientA[iChipIdx]) { + i2c_unregister_device(gpI2C_clientA[iChipIdx]); + gpI2C_clientA[iChipIdx] = NULL; + } + gtTPS65185_DataA[iChipIdx].iCurrent_temprature = -1; + //gtTPS65185_DataA[iChipIdx].iLast_temprature = -1; + } + + if(giIsTPS65185_inited) { + dw65185mode = TPS65185_MODE_SLEEP; + iChk = tps65185_chg_mode(&dw65185mode,1); + if(iChk<0) { + ERR_MSG("[Error] %s : change to power down mode fail !\n",__FUNCTION__); + tps65185_release(); + GALLEN_DBGLOCAL_ESC(); + return iChk; + } + // release interrupt ... + + iChk = tps65185_gpio_release(); + if(iChk<0) { + WARNING_MSG("[Warnig] %s : gpio release fail !\n",__FUNCTION__); + } + } + + + gpI2C_adapter = NULL; + + GALLEN_DBGLOCAL_END(); + return iRet; +} + + + + + +int tps65185_get_temperature(int *O_piTemperature) +{ + int iRet = TPS65185_RET_SUCCESS; + int iChk; + + unsigned short wTemp,wReg; + + unsigned char bTemp,bReg; + int iTemp; + + int iChipIdx = 0; + + unsigned long ulTimeoutTick; + unsigned long ulCurTick; + unsigned long dw65185mode; + + GALLEN_DBGLOCAL_BEGIN(); + + if(TPS65185_MODE_STANDBY!=gtTPS65185_DataA[0].dwCurrent_mode&&TPS65185_MODE_ACTIVE!=gtTPS65185_DataA[0].dwCurrent_mode) + { + dw65185mode = TPS65185_MODE_STANDBY; + iChk = tps65185_chg_mode(&dw65185mode,1); + if(iChk<0) { + ERR_MSG("[Error] %s : change to standby mode fail !\n",__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + return iChk; + } + } + + //printk("%s()\n",__FUNCTION__); + iChk = TPS65185_REG_SET(TMST1,READ_THERM,1); + + //udelay(1); + ulTimeoutTick = jiffies + 50; + do { + + wReg = TPS65185_REG_GET(TMST1); + if(((unsigned short)(-1))==wReg) { + GALLEN_DBGLOCAL_ESC(); + return TPS65185_RET_REGREADFAIL; + } + if(wReg&TPS65185_REG_TMST1_CONV_END) { + break; + } + + schedule_timeout(1); + + ulCurTick = jiffies; + if(ulCurTick>ulTimeoutTick) { + ERR_MSG("%s(%d):wait TMST1 ADC timeout \n",__FILE__,__LINE__); + GALLEN_DBGLOCAL_ESC(); + return TPS65185_RET_TIMEOUT; + } + } while(1); + + wReg = TPS65185_REG_GET(TMST_VALUE); + + if(((unsigned short)(-1))==wReg) { + GALLEN_DBGLOCAL_ESC(); + return TPS65185_RET_REGREADFAIL; + } + + bReg = (unsigned char)wReg; + gtTPS65185_DataA[iChipIdx].wTempratureData = wReg; + if(bReg&0x80) { + GALLEN_DBGLOCAL_RUNLOG(0); + // negative . + bTemp=(~bReg)+1; + iTemp = bTemp; + iTemp = (~iTemp)+1; + } + else { + GALLEN_DBGLOCAL_RUNLOG(1); + // positive . + iTemp = (int)(bReg); + } + gtTPS65185_DataA[iChipIdx].iCurrent_temprature = iTemp; + printk("%s temprature data = 0x%x,%d\n",DRIVERNAME,wReg,gtTPS65185_DataA[iChipIdx].iCurrent_temprature); + + //gtTPS65185_DataA[iChipIdx].iCurrent_temprature = bA[0]; + if(O_piTemperature) { + GALLEN_DBGLOCAL_RUNLOG(2); + *O_piTemperature = gtTPS65185_DataA[iChipIdx].iCurrent_temprature; + } + + GALLEN_DBGLOCAL_END(); + return iRet; +} + +int tps65185_set_ep3v3_pwrdn_delay_ticks(unsigned long dwTicks) +{ + printk("%s(%d)\n",__FUNCTION__,dwTicks); + gtTPS65185_DataA[0].dwEP3V3_Off_Ticks=dwTicks; + return 0; +} + +int tps65185_is_panel_poweron(void) +{ + int iRet; + #if 0 + int iChk; + + iChk = tps65185_chk_PG(0xfa); + if(TPS65185_RET_ALLPOWERGOOD==iChk) { + iRet = 1; + } + else { + iRet = 0; + } + #else + iRet = gpio_get_value(GPIO_TPS65185_PWRGOOD)?1:0; + #endif + + return iRet; +} + +int tps65185_wait_panel_poweron(void) +{ +#if 0 + //printk("%s(%d);\n",__FILE__,__LINE__); + //msleep(30); + return TPS65185_RET_SUCCESS; +#else + int iRet = TPS65185_RET_SUCCESS; + int iChk; + int ilpcnt=0; + + + #ifdef TPS65185_PWR_ONOFF_INT//[ + + if(in_interrupt()) { + printk("%s(%d):[warning] call %s in interrupt !!\n", + __FILE__,__LINE__,__FUNCTION__); + } + else + { + do { + #ifdef TPS65185_PWR_ONOFF_WAITBYCOMPLETE//[ + + if(!tps65185_is_panel_poweron()) { + ERR_MSG("%s(),before wait PG,jiffies=%ld\n",__FUNCTION__,jiffies); + wait_for_completion_timeout(&tps65185_pwrgood_on_completion,500); + ERR_MSG("%s(),after wait PG,jiffies=%ld\n",__FUNCTION__,jiffies); + } + + #else //][!TPS65185_PWR_ONOFF_WAITBYCOMPLETE + iChk = wait_event_interruptible_timeout(tps65185_pwron_wq,tps65185_is_panel_poweron(),50); + //iChk = wait_event_timeout(tps65185_pwron_wq,tps65185_is_panel_poweron(),50); + #endif//]TPS65185_PWR_ONOFF_WAITBYCOMPLETE + if(!tps65185_is_panel_poweron()) { + iRet = TPS65185_RET_TIMEOUT; + ERR_MSG("%s(%d):wait power on timeout lpcnt=%d!\n",__FILE__,__LINE__,ilpcnt); + } + else { + iRet = TPS65185_RET_SUCCESS; + break; + } + + } while(++ilpcnt<10); + } + + #else //][!TPS65185_PWR_ONOFF_INT + + volatile unsigned long dwCnt=0; + + do { + ++dwCnt; + if(dwCnt>=100) { + ERR_MSG("%s timeout >=%ld !!\n",__FUNCTION__,dwCnt); + break; + } + + //if(tps65185_is_panel_poweron()) + if(giIsTPS65185_PwrOn) + { + DBG_MSG("%s poweron@cnt=%ld\n",__FUNCTION__,dwCnt); + break; + } + + schedule_timeout(1); + + }while(1) ; + + + #endif //]TPS65185_PWR_ONOFF_INT + + DBG_MSG("%s:PMIC poweron=%d,%d\n",__FUNCTION__,tps65185_is_panel_poweron(),giIsTPS65185_PwrOn); + return iRet; +#endif +} + +int tps65185_wait_panel_poweroff(void) +{ +#if 0 + //schedule_timeout(1); + //printk("%s(%d);\n",__FILE__,__LINE__); + //msleep(30); + return TPS65185_RET_SUCCESS; +#else + + int iRet = TPS65185_RET_SUCCESS; + int iChk; + int ilpcnt=0; + + + #ifdef TPS65185_PWR_ONOFF_INT//[ + + if(in_interrupt()) { + printk("%s(%d):[warning] call %s in interrupt !!\n", + __FILE__,__LINE__,__FUNCTION__); + } + else + { + do { + #ifdef TPS65185_PWR_ONOFF_WAITBYCOMPLETE//[ + + if(tps65185_is_panel_poweron()) { + ERR_MSG("%s(),before wait PGoff,jiffies=%ld\n",__FUNCTION__,jiffies); + wait_for_completion_timeout(&tps65185_pwrgood_off_completion,500); + ERR_MSG("%s(),after wait PGoff,jiffies=%ld\n",__FUNCTION__,jiffies); + } + #else //][!TPS65185_PWR_ONOFF_WAITBYCOMPLETE + iChk = wait_event_interruptible_timeout(tps65185_pwroff_wq,!tps65185_is_panel_poweron(),50); + //iChk = wait_event_timeout(tps65185_pwroff_wq,!tps65185_is_panel_poweron(),50); + #endif //] TPS65185_PWR_ONOFF_WAITBYCOMPLETE + if(tps65185_is_panel_poweron()) { + iRet = TPS65185_RET_TIMEOUT; + ERR_MSG("%s(%d):wait power off timeout lpcnt=%d!\n",__FILE__,__LINE__,ilpcnt); + } + else { + iRet = TPS65185_RET_SUCCESS; + break; + } + + }while(++ilpcnt<10); + + } + + #else //][!TPS65185_PWR_ONOFF_INT + + volatile unsigned long dwCnt=0; + + do { + ++dwCnt; + if(dwCnt>=100) { + ERR_MSG("%s timeout >=%ld !!\n",__FUNCTION__,dwCnt); + break; + } + //if(!tps65185_is_panel_poweron()) + if(!giIsTPS65185_PwrOn) + { + DBG_MSG("%s poweroff@cnt=%ld\n",__FUNCTION__,dwCnt); + break; + } + + schedule_timeout(1); + }while(1) ; + + #endif //] TPS65185_PWR_ONOFF_INT + + DBG_MSG("%s:PMIC poweron=%d,%d\n",__FUNCTION__,tps65185_is_panel_poweron(),giIsTPS65185_PwrOn); + return iRet; +#endif +} + + +int tps65185_chg_mode(unsigned long *IO_pdwMode,int iIsWaitPwrOff) +{ + int iRet=TPS65185_RET_SUCCESS; + int iChk; + unsigned long dwCurrent_mode; + unsigned long dwNewMode; + int iRetryCnt; + unsigned long dwCurTick,dwTicks; + int iPoweringOn; + //int irq_INT,irq_PG; + + GALLEN_DBGLOCAL_BEGIN(); + + //int iNewWakeupState,iNewPwrupState; + + //irq_INT = gpio_to_irq(GPIO_TPS65185_INT); + //irq_PG = gpio_to_irq(GPIO_TPS65185_PWRGOOD); + + //disable_irq(irq_INT); + //disable_irq(irq_PG); + if(*IO_pdwMode==TPS65185_MODE_ACTIVE) { + tps65185_restore_vcom(); + } + + down(>TPS65185_DataA[0].chmod_lock); + + dwCurrent_mode = gtTPS65185_DataA[0].dwCurrent_mode; + dwNewMode = *IO_pdwMode; + + if(0==giIsTPS65185_inited) { + ERR_MSG("[Error] %s : tps65185 must be initialized first !\n",__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + iRet = TPS65185_RET_INITNOTYET; + goto exit; + } + + if(!IO_pdwMode) { + ERR_MSG("[Warning] %s : parameter error !\n",__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + iRet = TPS65185_RET_PARAMERR; + goto exit; + } + + if(dwNewMode!=TPS65185_MODE_ACTIVE && dwCurrent_mode == dwNewMode) { + DBG_MSG("%s : skip same mode \n",__FUNCTION__); + GALLEN_DBGLOCAL_ESC(); + iRet = TPS65185_RET_SUCCESS; + goto exit; + } + + + // change tps65185 work mode ... + //gpio_direction_input(GPIO_TPS65185_WAKEUP); + //iCurrentWakeupState = gpio_get_value(GPIO_TPS65185_WAKEUP); + //gpio_direction_input(GPIO_TPS65185_PWRUP); + //iCurrentPwrupState = gpio_get_value(GPIO_TPS65185_PWRUP); + + DBG_MSG("%s begin %ld->%ld\n",__FUNCTION__,dwCurrent_mode,dwNewMode); + DBG_MSG("%s():TPS65185 wakeup=%d,pwrup=%d\n",__FUNCTION__,\ + gpio_get_value(GPIO_TPS65185_WAKEUP),\ + gpio_get_value(GPIO_TPS65185_PWRUP)); + + switch(dwNewMode) { + case TPS65185_MODE_ACTIVE:GALLEN_DBGLOCAL_RUNLOG(0); + #if 0 + if( TPS65185_MODE_SLEEP==dwCurrent_mode || + TPS65185_MODE_STANDBY==dwCurrent_mode|| + TPS65185_MODE_UNKOWN==dwCurrent_mode) + { + tps65185_wait_panel_poweroff(); + } + #endif + + up(>TPS65185_DataA[0].chmod_lock); + iChk = cancel_delayed_work_sync(>Pwrdwn_work_param.pwrdwn_work); + down(>TPS65185_DataA[0].chmod_lock); + + //iNewWakeupState = 1; + //iNewPwrupState = 1; + + if(TPS65185_MODE_SLEEP==dwCurrent_mode || TPS65185_MODE_UNKOWN==dwCurrent_mode) { + GALLEN_DBGLOCAL_RUNLOG(3); + gpio_direction_output(GPIO_TPS65185_WAKEUP, 1); + if(gtTPS65185_DataA[0].iIsInitPwrON) { + GALLEN_DBGLOCAL_RUNLOG(2); + gtTPS65185_DataA[0].iIsInitPwrON = 0; + } + + iRetryCnt = 0; + do { + mdelay(2); + //if(giIsTPS65185_turnoff_EP3V3) { + //iRet = tps65185_reg_init(0); + //iRet = tps65185_reg_init(-1); + iRet = tps65185_reg_init(1); + //} + //else { + // iRet = tps65185_reg_init(1); + //} + + if(iRet>=0) { + dwCurTick=jiffies; + if(time_before(dwCurTick,gdwSafeTick_To_TurnON_RailPower)) { + dwTicks = gdwSafeTick_To_TurnON_RailPower-dwCurTick; + msleep(jiffies_to_msecs(dwTicks)); + DBG_MSG("msleep %ld ticks for resume times\n",dwTicks); + } + +#ifdef GPIO_TPS65185_EP_3V3_IN //[ + //gpio_direction_output(GPIO_TPS65185_EP_3V3_IN, 1);udelay(30); +#endif //]GPIO_TPS65185_EP_3V3_IN + + //TPS65185_REG_SET(ENABLE,V3P3_EN,1);udelay(100); + gpio_direction_output(GPIO_TPS65185_PWRUP, 1); + break; + } + WARNING_MSG("PMIC sleep->active, retry %d\n",iRetryCnt); + } while(++iRetryCnt<10); + + iPoweringOn = 1; + } + else if(TPS65185_MODE_STANDBY==dwCurrent_mode) { + //gpio_direction_output(GPIO_TPS65185_WAKEUP, 1); + //gpio_direction_output(GPIO_TPS65185_PWRUP, 0); + //ERR_MSG("."); + //udelay(1900); + if(giIsTPS65185_turnoff_EP3V3) { + if(!(TPS65185_REG_GET(ENABLE)&TPS65185_REG_ENABLE_V3P3_EN)) { + dwCurTick=jiffies; + if(time_before(dwCurTick,gdwSafeTick_To_TurnON_RailPower)) { + dwTicks = gdwSafeTick_To_TurnON_RailPower-dwCurTick; + msleep(jiffies_to_msecs(dwTicks)); + DBG_MSG("msleep %ld ticks for resume times\n",dwTicks); + } + +#ifdef GPIO_TPS65185_EP_3V3_IN //[ + //gpio_direction_output(GPIO_TPS65185_EP_3V3_IN, 1);udelay(10); +#endif //]GPIO_TPS65185_EP_3V3_IN + + //TPS65185_REG_SET(ENABLE,V3P3_EN,1);udelay(100); + } + } + + gpio_direction_output(GPIO_TPS65185_PWRUP, 1); + //ERR_MSG("."); + iPoweringOn = 1; + } + else if(TPS65185_MODE_ACTIVE==dwCurrent_mode) { + iPoweringOn = 0; + } + else + { + GALLEN_DBGLOCAL_RUNLOG(4); + ERR_MSG("wrong mode !!? (mode=%ld)\n",dwCurrent_mode); + iRet = TPS65185_REG_SET(ENABLE,ACTIVE,1); + iPoweringOn = 0; + } + + if(iPoweringOn) + { + //ERR_MSG("."); + tps65185_wait_panel_poweron(); + } + + msleep(5); + TPS65185_VCOM_OUT(1); + msleep(5); + //ERR_MSG(".\n"); + gtTPS65185_DataA[0].dwCurrent_mode = dwNewMode; + + break; + + case TPS65185_MODE_SLEEP:GALLEN_DBGLOCAL_RUNLOG(5); + //iNewWakeupState = 0; + //iNewPwrupState = 0; + + gtPwrdwn_work_param.dwNewMode = dwNewMode; + gtPwrdwn_work_param.iIsWaitPwrOff = iIsWaitPwrOff; + + if(TPS65185_MODE_ACTIVE==dwCurrent_mode) + { + msleep(2);TPS65185_VCOM_OUT(0); + +#ifdef TPS65185_PWROFFDELAYWORK_TICKS//[ + + if(!delayed_work_pending(>Pwrdwn_work_param.pwrdwn_work)) { + GALLEN_DBGLOCAL_RUNLOG(13); + up(>TPS65185_DataA[0].chmod_lock); + iChk = cancel_delayed_work_sync(>Pwrdwn_work_param.pwrdwn_work); + down(>TPS65185_DataA[0].chmod_lock); + schedule_delayed_work(>Pwrdwn_work_param.pwrdwn_work, TPS65185_PWROFFDELAYWORK_TICKS); + } +#else//][! TPS65185_PWROFFDELAYWORK_TICKS + GALLEN_DBGLOCAL_RUNLOG(14); + _tps65185_pwrdwn(); +#endif //] TPS65185_PWROFFDELAYWORK_TICKS + + } + else + { + GALLEN_DBGLOCAL_RUNLOG(15); + _tps65185_pwrdwn(); + } + + break; + + case TPS65185_MODE_STANDBY:GALLEN_DBGLOCAL_RUNLOG(6); + //iNewWakeupState = 1; + //iNewPwrupState = 0; + + if(TPS65185_MODE_SLEEP==dwCurrent_mode || TPS65185_MODE_UNKOWN==dwCurrent_mode) + { + //GALLEN_DBGLOCAL_RUNLOG(9); + GALLEN_DBGLOCAL_RUNLOG(7); + + + gpio_direction_output(GPIO_TPS65185_WAKEUP, 1); + if(gtTPS65185_DataA[0].iIsInitPwrON) { + GALLEN_DBGLOCAL_RUNLOG(8); + gtTPS65185_DataA[0].iIsInitPwrON = 0; + } + + + //gpio_direction_output(GPIO_TPS65185_PWRUP, 0); + iRetryCnt = 0; + do { + mdelay(2); + //if(giIsTPS65185_turnoff_EP3V3) { + //iRet = tps65185_reg_init(-1); + iRet = tps65185_reg_init(1); + //} + //else { + //iRet = tps65185_reg_init(1); + //} + if(iRet>=0) { + gtTPS65185_DataA[0].dwCurrent_mode = dwNewMode; + +#ifdef TPS65185_RESUME_EP3V3_ON //[ + +#ifdef GPIO_TPS65185_EP_3V3_IN //[ + //gpio_direction_output(GPIO_TPS65185_EP_3V3_IN, 1);udelay(10); +#endif //]GPIO_TPS65185_EP_3V3_IN + + //TPS65185_REG_SET(ENABLE,V3P3_EN,1);udelay(100); +#endif //]TPS65185_RESUME_EP3V3_ON + break; + } + WARNING_MSG("PMIC sleep->standby, retry %d\n",iRetryCnt); + } while(++iRetryCnt<10); + + + } + else if(TPS65185_MODE_ACTIVE==dwCurrent_mode) { + msleep(2);TPS65185_VCOM_OUT(0); + + gtPwrdwn_work_param.dwNewMode = dwNewMode; + gtPwrdwn_work_param.iIsWaitPwrOff = iIsWaitPwrOff; +#ifdef TPS65185_PWROFFDELAYWORK_TICKS//[ + + if(!delayed_work_pending(>Pwrdwn_work_param.pwrdwn_work)) { + GALLEN_DBGLOCAL_RUNLOG(16); + up(>TPS65185_DataA[0].chmod_lock); + iChk = cancel_delayed_work_sync(>Pwrdwn_work_param.pwrdwn_work); + down(>TPS65185_DataA[0].chmod_lock); + schedule_delayed_work(>Pwrdwn_work_param.pwrdwn_work, TPS65185_PWROFFDELAYWORK_TICKS); + } +#else//][! TPS65185_PWROFFDELAYWORK_TICKS + GALLEN_DBGLOCAL_RUNLOG(17); + _tps65185_pwrdwn(); +#endif //] TPS65185_PWROFFDELAYWORK_TICKS + + } + else { + GALLEN_DBGLOCAL_RUNLOG(10); + ERR_MSG("wrong mode !!? (mode=%ld)\n",dwCurrent_mode); + iRet = TPS65185_REG_SET(ENABLE,STANDBY,1); + gtTPS65185_DataA[0].dwCurrent_mode = dwNewMode; + } + + + break; + + default: + GALLEN_DBGLOCAL_RUNLOG(11); + WARNING_MSG("%s : mode unsupported (0x%x) !!\n",__FUNCTION__, + (unsigned int)dwNewMode); + GALLEN_DBGLOCAL_ESC(); + iRet = TPS65185_RET_PARAMERR; + goto exit; + } + + DBG_MSG("%s :[mode] 0x%x->0x%x \n",__FUNCTION__,\ + (unsigned int)dwCurrent_mode,(unsigned int)dwNewMode); + + //gtTPS65185_DataA[0].iCurrentPwrupState = iCurrentPwrupState; + //gtTPS65185_DataA[0].iCurrentWakeupState = iCurrentWakeupState; + + *IO_pdwMode = dwCurrent_mode; + +exit: + + DBG_MSG("%s end,mode %lu->%lu \n",__FUNCTION__,dwCurrent_mode,dwNewMode); + + GALLEN_DBGLOCAL_END(); + + up(>TPS65185_DataA[0].chmod_lock); + //enable_irq(irq_PG); + //enable_irq(irq_INT); + + return iRet; +} + + + +int tps65185_vcom_set(int I_iVCOM_mv,int iIsWriteToFlash) +{ + int iRet = TPS65185_RET_SUCCESS; + int iChk; + unsigned short wVCOM_val; + unsigned long dwTPS65185_mode; + int iChkVCOM; + // + + GALLEN_DBGLOCAL_BEGIN(); + + // set VCOM @ VCOM1/VCOM2 register ... + if(I_iVCOM_mv>0) { + GALLEN_DBGLOCAL_RUNLOG(0); + I_iVCOM_mv = 0; + wVCOM_val = 0; + } + else { + GALLEN_DBGLOCAL_RUNLOG(1); + wVCOM_val = (unsigned short)((-I_iVCOM_mv)/10); + } + + // TPS65185 should be in Standby or Active mode . + dwTPS65185_mode = TPS65185_MODE_STANDBY; + iChk = tps65185_chg_mode(&dwTPS65185_mode,1); + + //printk(KERN_ERR"====>[wVCOM_val]:%x\n",wVCOM_val); + if(wVCOM_val&0x100) { + GALLEN_DBGLOCAL_RUNLOG(2); + TPS65185_REG_SET(VCOM2,VCOM8,1); + } + else { + TPS65185_REG_SET(VCOM2,VCOM8,0); + } + TPS65185_REG_SET(VCOM1,ALL,(unsigned char)wVCOM_val); + + if(iIsWriteToFlash) { +#if 1//[ + + GALLEN_DBGLOCAL_RUNLOG(3); + TPS65185_REG_SET(INT_EN1,PRGC_EN,1); + TPS65185_REG_SET(VCOM2,PROG,1); + iChk = wait_event_timeout(tps65185_PRGC_WQ, + TPS65185_REG(INT1)&TPS65185_REG_INT1_PRGC,100); + if(!(TPS65185_REG(INT1)&TPS65185_REG_INT1_PRGC)) { + iRet = TPS65185_RET_TIMEOUT; + ERR_MSG("%s(%d):wait TPS65185 PRGC timeout !\n",__FILE__,__LINE__); + } +#else//!][ + { + unsigned long ulTimeoutTick,ulCurTick; + unsigned short wReg; + + GALLEN_DBGLOCAL_RUNLOG(4); + //printk("%s()\n",__FUNCTION__); + iChk = TPS65185_REG_SET(INT_EN1,PRGC_EN,1); + iChk = TPS65185_REG_SET(VCOM2,PROG,1); + + //udelay(1); + ulTimeoutTick = jiffies + 100; + do { + + wReg = TPS65185_REG(INT1); + if(((unsigned short)(-1))==wReg) { + GALLEN_DBGLOCAL_ESC(); + return TPS65185_RET_REGREADFAIL; + } + if(wReg&TPS65185_REG_INT1_PRGC) { + GALLEN_DBGLOCAL_RUNLOG(5); + break; + } + + + GALLEN_DBGLOCAL_RUNLOG(6); + schedule_timeout(1); + + ulCurTick = jiffies; + if(ulCurTick>ulTimeoutTick) { + + ERR_MSG("%s(%d):wait TMST1 ADC timeout ,wReg=0x%x\n", + __FILE__,__LINE__,wReg); + + GALLEN_DBGLOCAL_ESC(); + return TPS65185_RET_TIMEOUT; + } + + } while(1); + } +#endif//] + +#if 1 + dwTPS65185_mode = TPS65185_MODE_SLEEP; + iChk = tps65185_chg_mode(&dwTPS65185_mode,1); + + dwTPS65185_mode = TPS65185_MODE_STANDBY; + iChk = tps65185_chg_mode(&dwTPS65185_mode,1); + + iChk = tps65185_vcom_get(&iChkVCOM); + //printk("%s:iChkVCOM = %d mV\n",__FUNCTION__,iChkVCOM); + if(iChkVCOM!=(I_iVCOM_mv/10*10)) { + + ERR_MSG("%s(%d):VCOM check fail (%d!=%d)!\n", + __FILE__,__LINE__,iChk,I_iVCOM_mv); + + GALLEN_DBGLOCAL_ESC(); + return TPS65185_RET_VCOMWRFAIL; + } +#endif + + } + else { + iChk = tps65185_vcom_get(&iChkVCOM); + } + GALLEN_DBGLOCAL_END(); + + return iRet; +} +int tps65185_vcom_get(int *O_piVCOM_mv) +{ + int iRet = TPS65185_RET_SUCCESS; + + unsigned char bRegVCOM1,bRegVCOM2; + unsigned short wTemp; + int iTemp; + + + bRegVCOM2 = TPS65185_REG_GET(VCOM2); + bRegVCOM1 = TPS65185_REG_GET(VCOM1); + wTemp = (bRegVCOM1|bRegVCOM2<<8)&0x1ff; + iTemp = -(wTemp*10); + gtTPS65185_DataA[0].iCurrentVCOM = iTemp; + //printk("%s():VCOM=%d\n",__FUNCTION__,iTemp); + if(O_piVCOM_mv) { + *O_piVCOM_mv = iTemp; + } + else { + iRet = TPS65185_RET_PARAMERR; + } + + return iRet; +} + +int tps65185_vcom_get_cached(int *O_piVCOM_mv) +{ + int iRet = TPS65185_RET_SUCCESS; + if(O_piVCOM_mv) { + if(0!=gtTPS65185_DataA[0].iCurrentVCOM) { + *O_piVCOM_mv = gtTPS65185_DataA[0].iCurrentVCOM; + } + else { + return tps65185_vcom_get(O_piVCOM_mv); + } + } + return iRet; +} + +int tps65185_vcom_kickback_measurement(int *O_piVCOM_mv) +{ + int iRet = TPS65185_RET_SUCCESS; + int iChk; + + GALLEN_DBGLOCAL_BEGIN(); + + if(O_piVCOM_mv) { + unsigned long dwTPS65185_mode; + + + // Pull the WAKEUP pin and the PWRUP pin to enable all output rails . + dwTPS65185_mode = TPS65185_MODE_ACTIVE; + iChk = tps65185_chg_mode(&dwTPS65185_mode,1); + // Set the HiZ bit in the VCOM2 register. This puts the VCOM pin in high-impedance state . + TPS65185_REG_SET(VCOM2,HiZ,1); + + // Drive the panel with the Null waveform. + + + // Set ACQ bt in the VCOM2 register to 1. This starts the mesurement routine . + TPS65185_REG_SET(VCOM2,ACQ,1); + + // When the measurement is complete, the ACQC (Acquisition Complete) + // bit in the INT1 register is set and the nINT pin is pulled low . + + iChk = wait_event_timeout(tps65185_ACQC_WQ, + TPS65185_REG(INT1)&TPS65185_REG_INT1_ACQC,50); + if(!(TPS65185_REG(INT1)&TPS65185_REG_INT1_ACQC)) { + iRet = TPS65185_RET_TIMEOUT; + ERR_MSG("%s(%d):wait TPS65185 ACQC timeout !\n",__FILE__,__LINE__); + } + + // The measurement result is stored in the VCOM[8:0] bits of the VCOM1 and VCOM2 register . + iRet = tps65185_vcom_get(O_piVCOM_mv); + + } + else { + iRet = TPS65185_RET_PARAMERR; + } + + GALLEN_DBGLOCAL_END(); + return iRet; +} + + +int tps65185_ONOFF(int iIsON) +{ +#ifdef TPS65185_SUSPEND //[ + int iRet=0; + + if(!giIsTPS65185_turnoff_EP3V3) { + return 1; + } + + if(!(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl)) { + WARNING_MSG("%s() display controller (%d) not match !\n", + __FUNCTION__,(int)gptHWCFG->m_val.bDisplayCtrl); + return 1; + } + + //printk("%s(%d)\n",__FUNCTION__,iIsON); + + if(iIsON) { + // Turn on the TPS65185 power . + + //gpio_free(GPIO_TPS65185_SDL); + //gpio_free(GPIO_TPS65185_SDA); + + //mxc_iomux_v3_setup_pad(MX50_PAD_I2C2_SCL__I2C2_SCL); + //mxc_iomux_v3_setup_pad(MX50_PAD_I2C2_SDA__I2C2_SDA); + + //TPS65185_VCOM_OUT(1); + +#ifdef GPIO_TPS65185_EP_3V3_IN //[ + gpio_direction_output(GPIO_TPS65185_EP_3V3_IN, 1); +#endif //]GPIO_TPS65185_EP_3V3_IN + + gpio_direction_output(GPIO_TPS65185_VIN, 1); + gdwSafeTick_To_TurnON_RailPower = jiffies+4; + + mxc_iomux_v3_setup_pad(GPIO_TPS65185_PWRGOOD_INT_PADCFG); // POWERGOOD . + mxc_iomux_v3_setup_pad(GPIO_TPS65185_INT_PADCFG); // EP_INT + gpio_direction_input(GPIO_TPS65185_PWRGOOD); + gpio_direction_input(GPIO_TPS65185_INT); + + + mdelay(5); + + } + else { + + + // Cut off the TPS65185 power . + // + mxc_iomux_v3_setup_pad(GPIO_TPS65185_PWRGOOD_GPIO_PADCFG); // POWERGOOD + mxc_iomux_v3_setup_pad(GPIO_TPS65185_INT_GPIO_PADCFG); // EP_INT + //udelay(50); + //mxc_iomux_v3_setup_pad(MX50_PAD_I2C2_SCL__GPIO_6_20); + //gpio_request(GPIO_TPS65185_SDL, "tps65185_i2c_sdl"); + //gpio_direction_input (GPIO_TPS65185_SDL); + //gpio_direction_output(GPIO_TPS65185_SDL, 1); + + //mxc_iomux_v3_setup_pad(MX50_PAD_I2C2_SDA__GPIO_6_21); + //gpio_request(GPIO_TPS65185_SDA, "tps65185_i2c_sda"); + //gpio_direction_input (GPIO_TPS65185_SDL); + //gpio_direction_output(GPIO_TPS65185_SDA, 1); + + + gpio_direction_output(GPIO_TPS65185_PWRUP, 0); + gpio_direction_output(GPIO_TPS65185_WAKEUP, 0); + + gpio_direction_output(GPIO_TPS65185_PWRGOOD, 0); + gpio_direction_output(GPIO_TPS65185_INT, 0); + + TPS65185_VCOM_OUT(0); + + gpio_direction_output(GPIO_TPS65185_VIN, 0); + gtTPS65185_DataA[0].iRestoreVCOM=gtTPS65185_DataA[0].iCurrentVCOM; + gtTPS65185_DataA[0].iCurrentVCOM=0; + + down(>TPS65185_DataA[0].chmod_lock); + gtTPS65185_DataA[0].dwCurrent_mode=TPS65185_MODE_SLEEP; + up(>TPS65185_DataA[0].chmod_lock); +#ifdef GPIO_TPS65185_EP_3V3_IN //[ + gpio_direction_output(GPIO_TPS65185_EP_3V3_IN, 0); +#endif //]GPIO_TPS65185_EP_3V3_IN + } + + return iRet; +#else //][!TPS65185_SUSPEND + return 0; +#endif //] TPS65185_SUSPEND +} + + + +void tps65185_irqs_enable(int iIsEnable) +{ + int irq,iChk; + + if(iIsEnable) { +#ifdef TPS65185_PWR_ONOFF_INT//[ + irq = gpio_to_irq(GPIO_TPS65185_INT); + #ifdef TPS65185_PLATFORM_MX6//[ + iChk = request_irq(irq, tps65185_int, 0, "tps65185_INT", 0); + if (iChk) { + pr_info("register TPS65185 interrupt failed\n"); + } + #else //][!TPS65185_PLATFORM_MX6 + enable_irq(irq); + #endif //] TPS65185_PLATFORM_MX6 + + + irq = gpio_to_irq(GPIO_TPS65185_PWRGOOD); + #ifdef TPS65185_PLATFORM_MX6//[ + iChk = request_irq(irq, tps65185_pwrgood_inthandler, 0, "tps65185_PWRGOOD", 0); + if (iChk) { + pr_info("register TPS65185 pwrgood interrupt failed\n"); + } + #else //][!TPS65185_PLATFORM_MX6 + enable_irq(irq); + #endif //] TPS65185_PLATFORM_MX6 +#endif //]TPS65185_PWR_ONOFF_INT + } + else { +#ifdef TPS65185_PWR_ONOFF_INT//[ + irq = gpio_to_irq(GPIO_TPS65185_INT); + #ifdef TPS65185_PLATFORM_MX6//[ + free_irq(irq,0); + #else //][!TPS65185_PLATFORM_MX6 + disable_irq(irq); + #endif //] TPS65185_PLATFORM_MX6 + + + irq = gpio_to_irq(GPIO_TPS65185_PWRGOOD); + #ifdef TPS65185_PLATFORM_MX6//[ + free_irq(irq,0); + #else //][!TPS65185_PLATFORM_MX6 + disable_irq(irq); + #endif //] TPS65185_PLATFORM_MX6 +#endif //] TPS65185_PWR_ONOFF_INT + } +} + +int tps65185_suspend(void) +{ +#ifdef TPS65185_SUSPEND //[ + unsigned long dwTPS65185_mode; + unsigned char bVal; + int irq; + int iRet = 0; + unsigned long dwCurTick; + int iIsIRQ_Disabled = 0; + int iIsChkVEE_Stable=0; + + dbgENTER(); + + + //ERR_MSG(KERN_ERR "%s()\n",__FUNCTION__); + if(gSleep_Mode_Suspend) { + DBG_MSG("%s:Go to sleep mode\n",__FUNCTION__); + //dwTPS65185_mode = TPS65185_MODE_SLEEP; // sleep mode will turn off EP_3V3 . + dwTPS65185_mode = TPS65185_MODE_STANDBY; + } + else { + dwTPS65185_mode = TPS65185_MODE_STANDBY; + } + + if( TPS65185_MODE_SLEEP == dwTPS65185_mode ) { + tps65185_irqs_enable(0);iIsIRQ_Disabled=1; + } + + tps65185_chg_mode(&dwTPS65185_mode,1); + + //flush_workqueue(tps65185_pwrgood_workqueue); + //flush_workqueue(tps65185_int_workqueue); + + //tps65185_wait_panel_poweroff(); + + //bVal=0; + //TPS65185_REG_SET(INT_EN1,ALL,bVal); + //TPS65185_REG_SET(INT_EN2,ALL,bVal); + //TPS65185_REG_SET(ENABLE,ALL,bVal); + + if(delayed_work_pending(>Pwrdwn_work_param.pwrdwn_work)) { + WARNING_MSG("pmic pwrdwn delay work pending !!\n"); + //flush_delayed_work(>Pwrdwn_work_param.pwrdwn_work); + iRet = -1;goto error_out; + } + +#if 0 + if( (1==gptHWCFG->m_val.bPMIC) && (/*PMIC is Ricoh */ + NTXHWCFG_TST_FLAG(gptHWCFG->m_val.bPCB_Flags,4)/*Panel is designed for low voltage */ || + NTXHWCFG_TST_FLAG(gptHWCFG->m_val.bPCB_Flags,5)/*EPD VDD source is standalone */ + ) ) + { + if(gSleep_Mode_Suspend) { + iIsChkVEE_Stable=1; + } + else { + iIsChkVEE_Stable=0; + } + } + else { + iIsChkVEE_Stable=1; + } +#else + if(gSleep_Mode_Suspend) { + iIsChkVEE_Stable=1; + } + else { + iIsChkVEE_Stable=0; + } +#endif + dwCurTick=jiffies; + if(iIsChkVEE_Stable && time_before(dwCurTick,gdwSafeTick_To_TurnOFF_EP3V3)) + { + WARNING_MSG("waiting for VEE stable ,please retry suspend later !!!\n"); + iRet = -2;goto error_out; + } + else { + if(!iIsIRQ_Disabled) { + tps65185_irqs_enable(0);iIsIRQ_Disabled=1; + } + dwTPS65185_mode = TPS65185_MODE_SLEEP; + tps65185_chg_mode(&dwTPS65185_mode,1); + } + + + dbgLEAVE(); + return iRet; +error_out: + if(iIsIRQ_Disabled) { + tps65185_irqs_enable(1); + } + return iRet; +#else + printk("%s() skipped !!\n",__FUNCTION__); + return 0; +#endif //]TPS65185_SUSPEND +} + +void tps65185_shutdown(void) +{ + unsigned long dwTPS65185_mode; + unsigned long dwCurTick; + +#if 0 + if(!delayed_work_pending(>Pwrdwn_work_param.pwrdwn_work)) { + dwTPS65185_mode = TPS65185_MODE_STANDBY; + tps65185_chg_mode(&dwTPS65185_mode,1); + } +#endif + + while (1) { + if(delayed_work_pending(>Pwrdwn_work_param.pwrdwn_work)) { + DBG0_MSG("%s() : waiting for TPS65185 pwrdwn !!\n",__FUNCTION__); + msleep(100); + } + else { + break; + } + } + + while (1) { + dwCurTick=jiffies; + if(time_before(dwCurTick,gdwSafeTick_To_TurnOFF_EP3V3)) { + DBG0_MSG("%s() : waiting for VEE stable to turn off EP3V3 .\n",__FUNCTION__); + msleep(100); + } + else { + dwTPS65185_mode = TPS65185_MODE_SLEEP; + tps65185_chg_mode(&dwTPS65185_mode,1); + break; + } + } + +} + +void tps65185_resume(void) +{ +#ifdef TPS65185_SUSPEND //[ + unsigned long dwTPS65185_mode,dwTPS65185_current_mode; + + dbgENTER(); + dwTPS65185_current_mode=gtTPS65185_DataA[0].dwCurrent_mode; + + dwTPS65185_mode = TPS65185_MODE_STANDBY; + tps65185_chg_mode(&dwTPS65185_mode,1); + + if( TPS65185_MODE_SLEEP == dwTPS65185_current_mode ) { + tps65185_irqs_enable(1); + } + + //tps65185_reg_init(1); + + dbgLEAVE(); +#else + printk("%s() skipped !!\n",__FUNCTION__); +#endif //]TPS65185_SUSPEND +} + +//EXPORT_SYMBOL(tps65185_init); +//EXPORT_SYMBOL(tps65185_release); +//EXPORT_SYMBOL(tps65185_get_temperature); + diff --git a/drivers/video/mxc/lk_tps65185.h b/drivers/video/mxc/lk_tps65185.h new file mode 100644 index 00000000..77d20ec9 --- /dev/null +++ b/drivers/video/mxc/lk_tps65185.h @@ -0,0 +1,53 @@ +#ifndef LK_TPS65185_H//[ +#define LK_TPS65185_H + + +#define TPS65185_RET_ALLPOWEROFF (2) // all power is turned off . +#define TPS65185_RET_ALLPOWERGOOD (1) // all power is good . +#define TPS65185_RET_SUCCESS (0) // all right . +#define TPS65185_RET_PARAMERR (-1) // parameter error ! +#define TPS65185_RET_I2CCHN_NOTFOUND (-2) // i2c adapter not found ! +#define TPS65185_RET_NEWDEVFAIL (-3) // register i2c client fail !! +#define TPS65185_RET_CHIP_NOTFOUND (-4) // hardware chip not found ! +#define TPS65185_RET_I2CTRANS_ERR (-5) // i2c trans error ! +#define TPS65185_RET_INITNOTYET (-6) // should init first ! +#define TPS65185_RET_IOCONFIG_ERR (-7) // gpio configuration error ! +#define TPS65185_RET_REGREADFAIL (-8) // register read fail ! +#define TPS65185_RET_POWERNOTGOOD (-9) // . +#define TPS65185_RET_TIMEOUT (-10) // . +#define TPS65185_RET_VCOMWRFAIL (-11) // VCOM write fail . + + +int tps65185_release(void); + +#define EPDTIMING_V110 1 +#define EPDTIMING_V220 2 +int tps65185_init(int iPort,int iEPDTimingType); + +int tps65185_get_temperature(int *O_piTemperature); + +int tps65185_is_panel_poweron(void); + +int tps65185_wait_panel_poweron(void); +int tps65185_wait_panel_poweroff(void); + +//////////////////////////////////////////////////////// +// definitions for chg_mode() ... +#define TPS65185_MODE_UNKOWN 0x00000000 +#define TPS65185_MODE_ACTIVE 0x00000001 +#define TPS65185_MODE_SLEEP 0x00000002 +#define TPS65185_MODE_STANDBY 0x00000004 +int tps65185_chg_mode(unsigned long *IO_pdwMode,int iIsWaitPwrOff); + +int tps65185_vcom_set(int I_iVCOM_mv,int iIsWriteToFlash); +int tps65185_vcom_get(int *O_piVCOM_mv); +int tps65185_vcom_get_cached(int *O_piVCOM_mv); +int tps65185_vcom_kickback_measurement(int *O_piVCOM_mv); +int tps65185_suspend(void); +void tps65185_resume(void); +void tps65185_shutdown(void); + +int tps65185_ONOFF(int iIsON); +int tps65185_set_ep3v3_pwrdn_delay_ticks(unsigned long dwTicks); + +#endif //]LK_TPS65185_H diff --git a/drivers/video/mxc/mipi_dsi.c b/drivers/video/mxc/mipi_dsi.c new file mode 100644 index 00000000..73c2dda0 --- /dev/null +++ b/drivers/video/mxc/mipi_dsi.c @@ -0,0 +1,1010 @@ +/* + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <linux/backlight.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/fsl_devices.h> +#include <video/mipi_display.h> + +#include <mach/hardware.h> +#include <mach/clock.h> +#include <mach/mipi_dsi.h> + +#include "mxc_dispdrv.h" +#include "mipi_dsi.h" + +#define DISPDRV_MIPI "mipi_dsi" +#define ROUND_UP(x) ((x)+1) +#define NS2PS_RATIO (1000) +#define NUMBER_OF_CHUNKS (0x8) +#define NULL_PKT_SIZE (0x8) +#define PHY_BTA_MAXTIME (0xd00) +#define PHY_LP2HS_MAXTIME (0x40) +#define PHY_HS2LP_MAXTIME (0x40) +#define PHY_STOP_WAIT_TIME (0x20) +#define DSI_CLKMGR_CFG_CLK_DIV (0x107) +#define DSI_GEN_PLD_DATA_BUF_ENTRY (0x10) +#define MIPI_MUX_CTRL(v) (((v) & 0x3) << 4) +#define IOMUXC_GPR3_OFFSET (0xc) +#define MIPI_LCD_SLEEP_MODE_DELAY (120) +#define MIPI_DSI_REG_RW_TIMEOUT (20) +#define MIPI_DSI_PHY_TIMEOUT (10) + +static struct mipi_dsi_match_lcd mipi_dsi_lcd_db[] = { +#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL + { + "TRULY-WVGA", + {mipid_hx8369_get_lcd_videomode, mipid_hx8369_lcd_setup} + }, +#endif + { + "", {NULL, NULL} + } +}; + +struct _mipi_dsi_phy_pll_clk { + u32 max_phy_clk; + u32 config; +}; + +/* configure data for DPHY PLL 27M reference clk out */ +static const struct _mipi_dsi_phy_pll_clk mipi_dsi_phy_pll_clk_table[] = { + {1000, 0x74}, /* 950-1000MHz */ + {950, 0x54}, /* 900-950Mhz */ + {900, 0x34}, /* 850-900Mhz */ + {850, 0x14}, /* 800-850MHz */ + {800, 0x32}, /* 750-800MHz */ + {750, 0x12}, /* 700-750Mhz */ + {700, 0x30}, /* 650-700Mhz */ + {650, 0x10}, /* 600-650MHz */ + {600, 0x2e}, /* 550-600MHz */ + {550, 0x0e}, /* 500-550Mhz */ + {500, 0x2c}, /* 450-500Mhz */ + {450, 0x0c}, /* 400-450MHz */ + {400, 0x4a}, /* 360-400MHz */ + {360, 0x2a}, /* 330-360Mhz */ + {330, 0x48}, /* 300-330Mhz */ + {300, 0x28}, /* 270-300MHz */ + {270, 0x08}, /* 250-270MHz */ + {250, 0x46}, /* 240-250Mhz */ + {240, 0x26}, /* 210-240Mhz */ + {210, 0x06}, /* 200-210MHz */ + {200, 0x44}, /* 180-200MHz */ + {180, 0x24}, /* 160-180MHz */ + {160, 0x04}, /* 150-160MHz */ +}; + +static int valid_mode(int pixel_fmt) +{ + return ((pixel_fmt == IPU_PIX_FMT_RGB24) || + (pixel_fmt == IPU_PIX_FMT_BGR24) || + (pixel_fmt == IPU_PIX_FMT_RGB666) || + (pixel_fmt == IPU_PIX_FMT_RGB565) || + (pixel_fmt == IPU_PIX_FMT_BGR666) || + (pixel_fmt == IPU_PIX_FMT_RGB332)); +} + +static inline void mipi_dsi_read_register(struct mipi_dsi_info *mipi_dsi, + u32 reg, u32 *val) +{ + *val = ioread32(mipi_dsi->mmio_base + reg); + dev_dbg(&mipi_dsi->pdev->dev, "read_reg:0x%02x, val:0x%08x.\n", + reg, *val); +} + +static inline void mipi_dsi_write_register(struct mipi_dsi_info *mipi_dsi, + u32 reg, u32 val) +{ + iowrite32(val, mipi_dsi->mmio_base + reg); + dev_dbg(&mipi_dsi->pdev->dev, "\t\twrite_reg:0x%02x, val:0x%08x.\n", + reg, val); +} + +int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi_dsi, + u8 data_type, const u32 *buf, int len) +{ + u32 val; + u32 status = 0; + int write_len = len; + uint32_t timeout = 0; + + if (len) { + /* generic long write command */ + while (len / DSI_GEN_PLD_DATA_BUF_SIZE) { + mipi_dsi_write_register(mipi_dsi, + MIPI_DSI_GEN_PLD_DATA, *buf); + buf++; + len -= DSI_GEN_PLD_DATA_BUF_SIZE; + mipi_dsi_read_register(mipi_dsi, + MIPI_DSI_CMD_PKT_STATUS, &status); + while ((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) == + DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, + MIPI_DSI_CMD_PKT_STATUS, &status); + } + } + /* write the remainder bytes */ + if (len > 0) { + while ((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) == + DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, + MIPI_DSI_CMD_PKT_STATUS, &status); + } + mipi_dsi_write_register(mipi_dsi, + MIPI_DSI_GEN_PLD_DATA, *buf); + } + + val = data_type | ((write_len & DSI_GEN_HDR_DATA_MASK) + << DSI_GEN_HDR_DATA_SHIFT); + } else { + /* generic short write command */ + val = data_type | ((*buf & DSI_GEN_HDR_DATA_MASK) + << DSI_GEN_HDR_DATA_SHIFT); + } + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &status); + while ((status & DSI_CMD_PKT_STATUS_GEN_CMD_FULL) == + DSI_CMD_PKT_STATUS_GEN_CMD_FULL) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &status); + } + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_GEN_HDR, val); + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &status); + while (!((status & DSI_CMD_PKT_STATUS_GEN_CMD_EMPTY) == + DSI_CMD_PKT_STATUS_GEN_CMD_EMPTY) || + !((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_EMPTY) == + DSI_CMD_PKT_STATUS_GEN_PLD_W_EMPTY)) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &status); + } + + return 0; +} + +int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi_dsi, + u8 data_type, u32 *buf, int len) +{ + u32 val; + int read_len = 0; + uint32_t timeout = 0; + + if (!len) { + mipi_dbg("%s, len = 0 invalid error!\n", __func__); + return -EINVAL; + } + + val = data_type | ((*buf & DSI_GEN_HDR_DATA_MASK) + << DSI_GEN_HDR_DATA_SHIFT); + memset(buf, 0, len); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_GEN_HDR, val); + + /* wait for cmd to sent out */ + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &val); + while ((val & DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) != + DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &val); + } + /* wait for entire response stroed in FIFO */ + while ((val & DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) == + DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_REG_RW_TIMEOUT) + return -EIO; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &val); + } + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &val); + while (!(val & DSI_CMD_PKT_STATUS_GEN_PLD_R_EMPTY)) { + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_GEN_PLD_DATA, buf); + read_len += DSI_GEN_PLD_DATA_BUF_SIZE; + buf++; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, + &val); + if (read_len == (DSI_GEN_PLD_DATA_BUF_ENTRY * + DSI_GEN_PLD_DATA_BUF_SIZE)) + break; + } + + if ((len <= read_len) && + ((len + DSI_GEN_PLD_DATA_BUF_SIZE) >= read_len)) + return 0; + else { + dev_err(&mipi_dsi->pdev->dev, + "actually read_len:%d != len:%d.\n", read_len, len); + return -ERANGE; + } +} + +int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi_dsi, + u8 cmd, const u32 *param, int num) +{ + int err = 0; + u32 buf[DSI_CMD_BUF_MAXSIZE]; + + switch (cmd) { + case MIPI_DCS_EXIT_SLEEP_MODE: + case MIPI_DCS_ENTER_SLEEP_MODE: + case MIPI_DCS_SET_DISPLAY_ON: + case MIPI_DCS_SET_DISPLAY_OFF: + buf[0] = cmd; + err = mipi_dsi_pkt_write(mipi_dsi, + MIPI_DSI_DCS_SHORT_WRITE, buf, 0); + break; + + default: + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command:0x%x Not supported!\n", cmd); + break; + } + + return err; +} + +static void mipi_dsi_dphy_init(struct mipi_dsi_info *mipi_dsi, + u32 cmd, u32 data) +{ + u32 val; + u32 timeout = 0; + + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, + DSI_PHY_IF_CTRL_RESET); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, DSI_PWRUP_POWERUP); + + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL1, + (0x10000 | cmd)); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 2); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL1, (0 | data)); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 2); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); + val = DSI_PHY_RSTZ_EN_CLK | DSI_PHY_RSTZ_DISABLE_RST | + DSI_PHY_RSTZ_DISABLE_SHUTDOWN; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, val); + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); + while ((val & DSI_PHY_STATUS_LOCK) != DSI_PHY_STATUS_LOCK) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_PHY_TIMEOUT) { + dev_err(&mipi_dsi->pdev->dev, + "Error: phy lock timeout!\n"); + break; + } + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); + } + timeout = 0; + while ((val & DSI_PHY_STATUS_STOPSTATE_CLK_LANE) != + DSI_PHY_STATUS_STOPSTATE_CLK_LANE) { + msleep(1); + timeout++; + if (timeout == MIPI_DSI_PHY_TIMEOUT) { + dev_err(&mipi_dsi->pdev->dev, + "Error: phy lock lane timeout!\n"); + break; + } + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); + } +} + +static void mipi_dsi_enable_controller(struct mipi_dsi_info *mipi_dsi, + bool init) +{ + u32 val; + u32 lane_byte_clk_period; + struct fb_videomode *mode = mipi_dsi->mode; + struct mipi_lcd_config *lcd_config = mipi_dsi->lcd_config; + + if (init) { + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_RESET); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, + DSI_PHY_RSTZ_RST); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CLKMGR_CFG, + DSI_CLKMGR_CFG_CLK_DIV); + + if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT)) + val = DSI_DPI_CFG_VSYNC_ACT_LOW; + if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT)) + val |= DSI_DPI_CFG_HSYNC_ACT_LOW; + if ((mode->sync & FB_SYNC_OE_LOW_ACT)) + val |= DSI_DPI_CFG_DATAEN_ACT_LOW; + if (MIPI_RGB666_LOOSELY == lcd_config->dpi_fmt) + val |= DSI_DPI_CFG_EN18LOOSELY; + val |= (lcd_config->dpi_fmt & DSI_DPI_CFG_COLORCODE_MASK) + << DSI_DPI_CFG_COLORCODE_SHIFT; + val |= (lcd_config->virtual_ch & DSI_DPI_CFG_VID_MASK) + << DSI_DPI_CFG_VID_SHIFT; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_DPI_CFG, val); + + val = DSI_PCKHDL_CFG_EN_BTA | + DSI_PCKHDL_CFG_EN_ECC_RX | + DSI_PCKHDL_CFG_EN_CRC_RX; + + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PCKHDL_CFG, val); + + val = (mode->xres & DSI_VID_PKT_CFG_VID_PKT_SZ_MASK) + << DSI_VID_PKT_CFG_VID_PKT_SZ_SHIFT; + val |= (NUMBER_OF_CHUNKS & DSI_VID_PKT_CFG_NUM_CHUNKS_MASK) + << DSI_VID_PKT_CFG_NUM_CHUNKS_SHIFT; + val |= (NULL_PKT_SIZE & DSI_VID_PKT_CFG_NULL_PKT_SZ_MASK) + << DSI_VID_PKT_CFG_NULL_PKT_SZ_SHIFT; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_PKT_CFG, val); + + /* enable LP mode when TX DCS cmd and enable DSI command mode */ + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, + MIPI_DSI_CMD_MODE_CFG_EN_LOWPOWER); + + /* mipi lane byte clk period in ns unit */ + lane_byte_clk_period = NS2PS_RATIO / + (lcd_config->max_phy_clk / BITS_PER_BYTE); + val = ROUND_UP(mode->hsync_len * mode->pixclock / + NS2PS_RATIO / lane_byte_clk_period) + << DSI_TME_LINE_CFG_HSA_TIME_SHIFT; + val |= ROUND_UP(mode->left_margin * mode->pixclock / + NS2PS_RATIO / lane_byte_clk_period) + << DSI_TME_LINE_CFG_HBP_TIME_SHIFT; + val |= ROUND_UP((mode->left_margin + mode->right_margin + + mode->hsync_len + mode->xres) * mode->pixclock + / NS2PS_RATIO / lane_byte_clk_period) + << DSI_TME_LINE_CFG_HLINE_TIME_SHIFT; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_TMR_LINE_CFG, val); + + val = ((mode->vsync_len & DSI_VTIMING_CFG_VSA_LINES_MASK) + << DSI_VTIMING_CFG_VSA_LINES_SHIFT); + val |= ((mode->upper_margin & DSI_VTIMING_CFG_VBP_LINES_MASK) + << DSI_VTIMING_CFG_VBP_LINES_SHIFT); + val |= ((mode->lower_margin & DSI_VTIMING_CFG_VFP_LINES_MASK) + << DSI_VTIMING_CFG_VFP_LINES_SHIFT); + val |= ((mode->yres & DSI_VTIMING_CFG_V_ACT_LINES_MASK) + << DSI_VTIMING_CFG_V_ACT_LINES_SHIFT); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VTIMING_CFG, val); + + val = ((PHY_BTA_MAXTIME & DSI_PHY_TMR_CFG_BTA_TIME_MASK) + << DSI_PHY_TMR_CFG_BTA_TIME_SHIFT); + val |= ((PHY_LP2HS_MAXTIME & DSI_PHY_TMR_CFG_LP2HS_TIME_MASK) + << DSI_PHY_TMR_CFG_LP2HS_TIME_SHIFT); + val |= ((PHY_HS2LP_MAXTIME & DSI_PHY_TMR_CFG_HS2LP_TIME_MASK) + << DSI_PHY_TMR_CFG_HS2LP_TIME_SHIFT); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TMR_CFG, val); + + val = (((lcd_config->data_lane_num - 1) & + DSI_PHY_IF_CFG_N_LANES_MASK) + << DSI_PHY_IF_CFG_N_LANES_SHIFT); + val |= ((PHY_STOP_WAIT_TIME & DSI_PHY_IF_CFG_WAIT_TIME_MASK) + << DSI_PHY_IF_CFG_WAIT_TIME_SHIFT); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CFG, val); + + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST0, &val); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST1, &val); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_ERROR_MSK0, 0); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_ERROR_MSK1, 0); + + mipi_dsi_dphy_init(mipi_dsi, DSI_PHY_CLK_INIT_COMMAND, + mipi_dsi->dphy_pll_config); + } else { + mipi_dsi_dphy_init(mipi_dsi, DSI_PHY_CLK_INIT_COMMAND, + mipi_dsi->dphy_pll_config); + } +} + +static void mipi_dsi_disable_controller(struct mipi_dsi_info *mipi_dsi) +{ + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, + DSI_PHY_IF_CTRL_RESET); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, DSI_PWRUP_RESET); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, DSI_PHY_RSTZ_RST); +} + +static irqreturn_t mipi_dsi_irq_handler(int irq, void *data) +{ + u32 mask0; + u32 mask1; + u32 status0; + u32 status1; + struct mipi_dsi_info *mipi_dsi; + + mipi_dsi = (struct mipi_dsi_info *)data; + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST0, &status0); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST1, &status1); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_MSK0, &mask0); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_MSK1, &mask1); + + if ((status0 & (~mask0)) || (status1 & (~mask1))) { + dev_err(&mipi_dsi->pdev->dev, + "mipi_dsi IRQ status0:0x%x, status1:0x%x!\n", + status0, status1); + } + + return IRQ_HANDLED; +} + +static inline void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi, + bool cmd_mode) +{ + u32 val; + + if (cmd_mode) { + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_RESET); + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, &val); + val |= MIPI_DSI_CMD_MODE_CFG_EN_CMD_MODE; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, val); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_MODE_CFG, 0); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_POWERUP); + } else { + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_RESET); + /* Disable Command mode when tranfering video data */ + mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, &val); + val &= ~MIPI_DSI_CMD_MODE_CFG_EN_CMD_MODE; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, val); + val = DSI_VID_MODE_CFG_EN | DSI_VID_MODE_CFG_EN_BURSTMODE | + DSI_VID_MODE_CFG_EN_LP_MODE; + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_MODE_CFG, val); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, + DSI_PWRUP_POWERUP); + mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, + DSI_PHY_IF_CTRL_TX_REQ_CLK_HS); + } +} + +static int mipi_dsi_power_on(struct mxc_dispdrv_handle *disp) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + if (!mipi_dsi->dsi_power_on) { + clk_enable(mipi_dsi->dphy_clk); + mipi_dsi_enable_controller(mipi_dsi, false); + mipi_dsi_set_mode(mipi_dsi, false); + /* host send pclk/hsync/vsync for two frames before sleep-out */ + msleep((1000/mipi_dsi->mode->refresh + 1) << 1); + mipi_dsi_set_mode(mipi_dsi, true); + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_EXIT_SLEEP_MODE, + NULL, 0); + if (err) { + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command sleep-in error!\n"); + } + msleep(MIPI_LCD_SLEEP_MODE_DELAY); + mipi_dsi_set_mode(mipi_dsi, false); + mipi_dsi->dsi_power_on = 1; + } + + return 0; +} + +void mipi_dsi_power_off(struct mxc_dispdrv_handle *disp) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + if (mipi_dsi->dsi_power_on) { + mipi_dsi_set_mode(mipi_dsi, true); + err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_ENTER_SLEEP_MODE, + NULL, 0); + if (err) { + dev_err(&mipi_dsi->pdev->dev, + "MIPI DSI DCS Command display on error!\n"); + } + /* To allow time for the supply voltages + * and clock circuits to stabilize. + */ + msleep(5); + /* video stream timing on */ + mipi_dsi_set_mode(mipi_dsi, false); + msleep(MIPI_LCD_SLEEP_MODE_DELAY); + + mipi_dsi_set_mode(mipi_dsi, true); + mipi_dsi_disable_controller(mipi_dsi); + mipi_dsi->dsi_power_on = 0; + clk_disable(mipi_dsi->dphy_clk); + } +} + +static int mipi_dsi_lcd_init(struct mipi_dsi_info *mipi_dsi, + struct mxc_dispdrv_setting *setting) +{ + int err; + int size; + int i; + struct fb_videomode *mipi_lcd_modedb; + struct fb_videomode mode; + struct device *dev = &mipi_dsi->pdev->dev; + + for (i = 0; i < ARRAY_SIZE(mipi_dsi_lcd_db); i++) { + if (!strcmp(mipi_dsi->lcd_panel, + mipi_dsi_lcd_db[i].lcd_panel)) { + mipi_dsi->lcd_callback = + &mipi_dsi_lcd_db[i].lcd_callback; + break; + } + } + if (i == ARRAY_SIZE(mipi_dsi_lcd_db)) { + dev_err(dev, "failed to find supported lcd panel.\n"); + return -EINVAL; + } + /* get the videomode in the order: cmdline->platform data->driver */ + mipi_dsi->lcd_callback->get_mipi_lcd_videomode(&mipi_lcd_modedb, &size, + &mipi_dsi->lcd_config); + err = fb_find_mode(&setting->fbi->var, setting->fbi, + setting->dft_mode_str, + mipi_lcd_modedb, size, NULL, + setting->default_bpp); + if (err != 1) + fb_videomode_to_var(&setting->fbi->var, mipi_lcd_modedb); + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < size; i++) { + fb_var_to_videomode(&mode, &setting->fbi->var); + if (fb_mode_is_equal(&mode, mipi_lcd_modedb + i)) { + err = fb_add_videomode(mipi_lcd_modedb + i, + &setting->fbi->modelist); + /* Note: only support fb mode from driver */ + mipi_dsi->mode = mipi_lcd_modedb + i; + break; + } + } + if ((err < 0) || (size == i)) { + dev_err(dev, "failed to add videomode.\n"); + return err; + } + + for (i = 0; i < ARRAY_SIZE(mipi_dsi_phy_pll_clk_table); i++) { + if (mipi_dsi_phy_pll_clk_table[i].max_phy_clk < + mipi_dsi->lcd_config->max_phy_clk) + break; + } + if ((i == ARRAY_SIZE(mipi_dsi_phy_pll_clk_table)) || + (mipi_dsi->lcd_config->max_phy_clk > + mipi_dsi_phy_pll_clk_table[0].max_phy_clk)) { + dev_err(dev, "failed to find data in" + "mipi_dsi_phy_pll_clk_table.\n"); + return -EINVAL; + } + mipi_dsi->dphy_pll_config = mipi_dsi_phy_pll_clk_table[--i].config; + dev_dbg(dev, "dphy_pll_config:0x%x.\n", mipi_dsi->dphy_pll_config); + + return 0; +} + +int mipi_dsi_enable(struct mxc_dispdrv_handle *disp) +{ + int err; + struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); + + if (!mipi_dsi->lcd_inited) { + err = clk_enable(mipi_dsi->dphy_clk); + if (err) + dev_err(&mipi_dsi->pdev->dev, + "clk_enable error:%d!\n", err); + mipi_dsi_enable_controller(mipi_dsi, true); + err = mipi_dsi->lcd_callback->mipi_lcd_setup( + mipi_dsi); + if (err < 0) { + dev_err(&mipi_dsi->pdev->dev, + "failed to init mipi lcd."); + clk_disable(mipi_dsi->dphy_clk); + return err; + } + mipi_dsi_set_mode(mipi_dsi, false); + mipi_dsi->dsi_power_on = 1; + mipi_dsi->lcd_inited = 1; + } + mipi_dsi_power_on(mipi_dsi->disp_mipi); + + return 0; +} + +static int mipi_dsi_disp_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + int err; + void __iomem *reg_base; + u32 val; + char dphy_clk[] = "mipi_pllref_clk"; + struct resource *res; + struct resource *res_irq; + struct device *dev; + struct mipi_dsi_info *mipi_dsi; + struct mipi_dsi_platform_data *pdata; + + mipi_dsi = mxc_dispdrv_getdata(disp); + if (IS_ERR(mipi_dsi)) { + pr_err("failed to get dispdrv data\n"); + return -EINVAL; + } + dev = &mipi_dsi->pdev->dev; + pdata = dev->platform_data; + if (!pdata) { + dev_err(dev, "No platform_data available\n"); + return -EINVAL; + } + + mipi_dsi->lcd_panel = kstrdup(pdata->lcd_panel, GFP_KERNEL); + if (!mipi_dsi->lcd_panel) + return -ENOMEM; + + if (!valid_mode(setting->if_fmt)) { + dev_warn(dev, "Input pixel format not valid" + "use default RGB24\n"); + setting->if_fmt = IPU_PIX_FMT_RGB24; + } + + res = platform_get_resource(mipi_dsi->pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "get platform resource 0 error\n"); + return -ENODEV; + } + + res = request_mem_region(res->start, resource_size(res), + mipi_dsi->pdev->name); + if (!res) { + dev_err(dev, "request mem region error\n"); + return -EBUSY; + } + mipi_dsi->mmio_base = ioremap(res->start, resource_size(res)); + if (!mipi_dsi->mmio_base) { + dev_err(dev, "Cannot map mipi dsi registers\n"); + err = -EIO; + goto err_ioremap; + } + + res = platform_get_resource(mipi_dsi->pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(dev, "get platform resource 1 error\n"); + err = -ENODEV; + goto err_get_res1; + } + + reg_base = ioremap(res->start, resource_size(res)); + if (!reg_base) { + dev_err(dev, "Cannot map iomuxc registers\n"); + err = -EIO; + goto err_ioremap_iomuxc; + } + val = ioread32(reg_base + IOMUXC_GPR3_OFFSET); + val |= MIPI_MUX_CTRL((pdata->ipu_id << 1) | (pdata->disp_id)); + iowrite32(val, reg_base + IOMUXC_GPR3_OFFSET); + iounmap(reg_base); + + res_irq = platform_get_resource(mipi_dsi->pdev, IORESOURCE_IRQ, 0); + if (!res_irq) { + dev_err(dev, "failed to acquire irq resource\n"); + err = -ENODEV; + goto err_get_irq; + } + mipi_dsi->irq = res_irq->start; + + mipi_dsi->dphy_clk = clk_get(dev, dphy_clk); + if (IS_ERR(mipi_dsi->dphy_clk)) { + dev_err(dev, "failed to get dphy pll_ref_clk\n"); + err = PTR_ERR(mipi_dsi->dphy_clk); + goto err_clk; + } + + dev_dbg(dev, "got resources: regs %p, irq:%d\n", + mipi_dsi->mmio_base, mipi_dsi->irq); + + if (pdata->io_regulator) { + mipi_dsi->io_regulator = regulator_get(dev, + pdata->io_regulator); + if (IS_ERR(mipi_dsi->io_regulator)) { + dev_err(dev, "failed to get io_regulator\n"); + err = PTR_ERR(mipi_dsi->io_regulator); + goto err_ioreg; + } + err = regulator_set_voltage(mipi_dsi->io_regulator, + pdata->io_volt, + pdata->io_volt); + if (err < 0) { + dev_err(dev, "failed to set io_regulator voltage\n"); + goto err_corereg; + } + err = regulator_enable(mipi_dsi->io_regulator); + if (err < 0) { + dev_err(dev, "failed to enable io_regulator voltage\n"); + goto err_corereg; + } + } + if (pdata->core_regulator) { + mipi_dsi->core_regulator = regulator_get(dev, + pdata->core_regulator); + if (IS_ERR(mipi_dsi->core_regulator)) { + dev_err(dev, "failed to get core_regulator\n"); + err = PTR_ERR(mipi_dsi->core_regulator); + goto err_corereg; + } + err = regulator_set_voltage(mipi_dsi->core_regulator, + pdata->core_volt, + pdata->core_volt); + if (err < 0) { + dev_err(dev, "failed to set core_regulator voltage\n"); + goto err_analogreg; + } + err = regulator_enable(mipi_dsi->core_regulator); + if (err < 0) { + dev_err(dev, + "failed to enable core_regulator voltage\n"); + goto err_analogreg; + } + } + if (pdata->analog_regulator) { + mipi_dsi->analog_regulator = + regulator_get(dev, pdata->analog_regulator); + if (IS_ERR(mipi_dsi->analog_regulator)) { + dev_err(dev, "failed to get analog_regulator\n"); + err = PTR_ERR(mipi_dsi->analog_regulator); + goto err_analogreg; + } + err = regulator_set_voltage(mipi_dsi->analog_regulator, + pdata->analog_volt, + pdata->analog_volt); + if (err < 0) { + dev_err(dev, + "failed to set analog_regulator voltage\n"); + goto err_pdata_init; + } + err = regulator_enable(mipi_dsi->analog_regulator); + if (err < 0) { + dev_err(dev, + "failed to enable analog_regulator voltage\n"); + goto err_pdata_init; + } + } + if (pdata->lcd_power) + pdata->lcd_power(true); + if (pdata->backlight_power) + pdata->backlight_power(true); + + if (pdata->init) { + err = pdata->init(mipi_dsi->pdev); + if (err < 0) { + dev_err(dev, "failed to do pdata->init()\n"); + goto err_pdata_init; + } + } + if (pdata->reset) + pdata->reset(); + + mipi_dsi->ipu_id = pdata->ipu_id; + mipi_dsi->disp_id = pdata->disp_id; + mipi_dsi->reset = pdata->reset; + mipi_dsi->lcd_power = pdata->lcd_power; + mipi_dsi->backlight_power = pdata->backlight_power; + + /* ipu selected by platform data setting */ + setting->dev_id = pdata->ipu_id; + setting->disp_id = pdata->disp_id; + + err = request_irq(mipi_dsi->irq, mipi_dsi_irq_handler, + 0, "mipi_dsi", mipi_dsi); + if (err) { + dev_err(dev, "failed to request irq\n"); + err = -EBUSY; + goto err_req_irq; + } + + err = mipi_dsi_lcd_init(mipi_dsi, setting); + if (err < 0) { + dev_err(dev, "failed to init mipi dsi lcd\n"); + goto err_dsi_lcd; + } + + dev_dbg(dev, "MIPI DSI dispdrv inited!\n"); + return 0; + +err_dsi_lcd: + free_irq(mipi_dsi->irq, mipi_dsi); +err_req_irq: + if (pdata->exit) + pdata->exit(mipi_dsi->pdev); +err_pdata_init: + /* cannot disable analog/io/core regulator, maybe others use it, + * according to board design + */ + if (mipi_dsi->analog_regulator) + regulator_put(mipi_dsi->analog_regulator); +err_analogreg: + if (mipi_dsi->core_regulator) + regulator_put(mipi_dsi->core_regulator); +err_corereg: + if (mipi_dsi->io_regulator) + regulator_put(mipi_dsi->io_regulator); +err_ioreg: + clk_put(mipi_dsi->dphy_clk); +err_clk: +err_get_irq: +err_get_res1: +err_ioremap_iomuxc: + iounmap(mipi_dsi->mmio_base); +err_ioremap: + release_mem_region(res->start, resource_size(res)); + + return err; +} + +static void mipi_dsi_disp_deinit(struct mxc_dispdrv_handle *disp) +{ + struct mipi_dsi_info *mipi_dsi; + struct resource *res; + + mipi_dsi = mxc_dispdrv_getdata(disp); + res = platform_get_resource(mipi_dsi->pdev, IORESOURCE_MEM, 0); + + disable_irq(mipi_dsi->irq); + free_irq(mipi_dsi->irq, mipi_dsi); + mipi_dsi_power_off(mipi_dsi->disp_mipi); + if (mipi_dsi->bl) + backlight_device_unregister(mipi_dsi->bl); + if (mipi_dsi->analog_regulator) + regulator_put(mipi_dsi->analog_regulator); + if (mipi_dsi->core_regulator) + regulator_put(mipi_dsi->core_regulator); + if (mipi_dsi->io_regulator) + regulator_put(mipi_dsi->io_regulator); + clk_put(mipi_dsi->dphy_clk); + iounmap(mipi_dsi->mmio_base); + release_mem_region(res->start, resource_size(res)); +} + +static struct mxc_dispdrv_driver mipi_dsi_drv = { + .name = DISPDRV_MIPI, + .init = mipi_dsi_disp_init, + .deinit = mipi_dsi_disp_deinit, + .enable = mipi_dsi_enable, + .disable = mipi_dsi_power_off, +}; + +/** + * This function is called by the driver framework to initialize the MIPI DSI + * device. + * + * @param pdev The device structure for the MIPI DSI passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int mipi_dsi_probe(struct platform_device *pdev) +{ + int ret; + struct mipi_dsi_info *mipi_dsi; + + mipi_dsi = kzalloc(sizeof(struct mipi_dsi_info), GFP_KERNEL); + if (!mipi_dsi) { + ret = -ENOMEM; + goto alloc_failed; + } + + mipi_dsi->pdev = pdev; + mipi_dsi->disp_mipi = mxc_dispdrv_register(&mipi_dsi_drv); + if (IS_ERR(mipi_dsi->disp_mipi)) { + dev_err(&pdev->dev, "mxc_dispdrv_register error:%ld!\n", + PTR_ERR(mipi_dsi->disp_mipi)); + ret = -ENOMEM; + goto register_failed; + } + + mxc_dispdrv_setdata(mipi_dsi->disp_mipi, mipi_dsi); + dev_set_drvdata(&pdev->dev, mipi_dsi); + + mxc_dispdrv_setdev(mipi_dsi->disp_mipi, &pdev->dev); + + dev_info(&pdev->dev, "i.MX MIPI DSI driver probed\n"); + return 0; + +register_failed: + kfree(mipi_dsi); +alloc_failed: + return ret; +} + +static void mipi_dsi_shutdown(struct platform_device *pdev) +{ + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + mipi_dsi_power_off(mipi_dsi->disp_mipi); + if (mipi_dsi->lcd_power) + mipi_dsi->lcd_power(false); + if (mipi_dsi->backlight_power) + mipi_dsi->backlight_power(false); +} + +static int __devexit mipi_dsi_remove(struct platform_device *pdev) +{ + struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_puthandle(mipi_dsi->disp_mipi); + mxc_dispdrv_unregister(mipi_dsi->disp_mipi); + kfree(mipi_dsi); + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +static struct platform_driver mipi_dsi_driver = { + .driver = { + .name = "mxc_mipi_dsi", + }, + .probe = mipi_dsi_probe, + .remove = __devexit_p(mipi_dsi_remove), + .shutdown = mipi_dsi_shutdown, +}; + +static int __init mipi_dsi_init(void) +{ + int err; + + err = platform_driver_register(&mipi_dsi_driver); + if (err) { + pr_err("mipi_dsi_driver register failed\n"); + return -ENODEV; + } + pr_info("MIPI DSI driver module loaded\n"); + return 0; +} + +static void __exit mipi_dsi_cleanup(void) +{ + platform_driver_unregister(&mipi_dsi_driver); +} + +module_init(mipi_dsi_init); +module_exit(mipi_dsi_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX MIPI DSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mipi_dsi.h b/drivers/video/mxc/mipi_dsi.h new file mode 100644 index 00000000..75a57f41 --- /dev/null +++ b/drivers/video/mxc/mipi_dsi.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __MIPI_DSI_H__ +#define __MIPI_DSI_H__ + +#ifdef DEBUG +#define mipi_dbg(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) +#else +#define mipi_dbg(fmt, ...) +#endif + +#define DSI_CMD_BUF_MAXSIZE (32) + +/* DPI interface pixel color coding map */ +enum mipi_dsi_dpi_fmt { + MIPI_RGB565_PACKED = 0, + MIPI_RGB565_LOOSELY, + MIPI_RGB565_CONFIG3, + MIPI_RGB666_PACKED, + MIPI_RGB666_LOOSELY, + MIPI_RGB888, +}; + +struct mipi_lcd_config { + u32 virtual_ch; + u32 data_lane_num; + /* device max DPHY clock in MHz unit */ + u32 max_phy_clk; + enum mipi_dsi_dpi_fmt dpi_fmt; +}; + +struct mipi_dsi_info; +struct mipi_dsi_lcd_callback { + /* callback for lcd panel operation */ + void (*get_mipi_lcd_videomode)(struct fb_videomode **, int *, + struct mipi_lcd_config **); + int (*mipi_lcd_setup)(struct mipi_dsi_info *); + +}; + +struct mipi_dsi_match_lcd { + char *lcd_panel; + struct mipi_dsi_lcd_callback lcd_callback; +}; + +/* driver private data */ +struct mipi_dsi_info { + struct platform_device *pdev; + void __iomem *mmio_base; + int dsi_power_on; + int lcd_inited; + u32 dphy_pll_config; + int ipu_id; + int disp_id; + char *lcd_panel; + int irq; + struct clk *dphy_clk; + struct mxc_dispdrv_handle *disp_mipi; + struct fb_videomode *mode; + struct mipi_lcd_config *lcd_config; + /* board related power control */ + struct backlight_device *bl; + struct regulator *io_regulator; + struct regulator *core_regulator; + struct regulator *analog_regulator; + int io_volt; + int core_volt; + int analog_volt; + void (*reset) (void); + void (*lcd_power)(int); + void (*backlight_power)(int); + /* callback for lcd panel operation */ + struct mipi_dsi_lcd_callback *lcd_callback; +}; + +int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi, + u8 data_type, const u32 *buf, int len); +int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi, + u8 data_type, u32 *buf, int len); +int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi, + u8 cmd, const u32 *param, int num); + +#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL +void mipid_hx8369_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data); +int mipid_hx8369_lcd_setup(struct mipi_dsi_info *); +#endif + +#ifndef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL +#error "Please configure MIPI LCD panel, we cannot find one!" +#endif + +#endif diff --git a/drivers/video/mxc/mx2fb.c b/drivers/video/mxc/mx2fb.c new file mode 100644 index 00000000..0b967712 --- /dev/null +++ b/drivers/video/mxc/mx2fb.c @@ -0,0 +1,1349 @@ +/* + * Copyright (C) 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer_MX27 Framebuffer Driver for MX27. + */ + +/*! + * @file mx2fb.c + * + * @brief Frame buffer driver for MX27 ADS. + * + * @ingroup Framebuffer_MX27 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/mxcfb.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> + +#include "mx2fb.h" + +#define MX2FB_TYPE_BG 0 +#define MX2FB_TYPE_GW 1 + +extern void gpio_lcdc_active(void); +extern void gpio_lcdc_inactive(void); +extern void board_power_lcd(int on); + +static char *fb_mode; +static int fb_enabled; +static unsigned long default_bpp = 16; +static ATOMIC_NOTIFIER_HEAD(mx2fb_notifier_list); +static struct clk *lcdc_clk; +/*! + * @brief Structure containing the MX2 specific framebuffer information. + */ +struct mx2fb_info { + int type; + char *id; + int registered; + int blank; + unsigned long pseudo_palette[16]; +}; + +/* Framebuffer APIs */ +static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +static int mx2fb_set_par(struct fb_info *info); +static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info); +static int mx2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int mx2fb_blank(int blank_mode, struct fb_info *info); +static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg); + +/* Driver entries */ +int __init mx2fb_init(void); +void __exit mx2fb_exit(void); +#ifndef MODULE +static int __init mx2fb_setup(char *); +#endif + +/* Internal functions */ +static int __init _init_fbinfo(struct fb_info *info, + struct platform_device *pdev); +static int __init _install_fb(struct fb_info *info, + struct platform_device *pdev); +static void __exit _uninstall_fb(struct fb_info *info); +static int _map_video_memory(struct fb_info *info); +static void _unmap_video_memory(struct fb_info *info); +static void _set_fix(struct fb_info *info); +static void _enable_lcdc(struct fb_info *info); +static void _disable_lcdc(struct fb_info *info); +static void _enable_graphic_window(struct fb_info *info); +static void _disable_graphic_window(struct fb_info *info); +static void _update_lcdc(struct fb_info *info); +static void _request_irq(void); +static void _free_irq(void); + +#ifdef CONFIG_PM +static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state); +static int mx2fb_resume(struct platform_device *pdev); +#else +#define mx2fb_suspend 0 +#define mx2fb_resume 0 +#endif + +static int mx2fb_probe(struct platform_device *pdev); + +#ifdef CONFIG_FB_MXC_TVOUT +#include <linux/video_encoder.h> +/* + * FIXME: VGA mode is not defined by video_encoder.h + * while FS453 supports VGA output. + */ +#ifndef VIDEO_ENCODER_VGA +#define VIDEO_ENCODER_VGA 32 +#endif + +#define MODE_PAL "TV-PAL" +#define MODE_NTSC "TV-NTSC" +#define MODE_VGA "TV-VGA" + +extern int fs453_ioctl(unsigned int cmd, void *arg); +#endif + +struct mx2fb_info mx2fbi_bg = { + .type = MX2FB_TYPE_BG, + .id = "DISP0 BG", + .registered = 0, +}; + +static struct mx2fb_info mx2fbi_gw = { + .type = MX2FB_TYPE_GW, + .id = "DISP0 FG", + .registered = 0, +}; + +/*! Current graphic window information */ +static struct fb_gwinfo g_gwinfo = { + .enabled = 0, + .alpha_value = 255, + .ck_enabled = 0, + .ck_red = 0, + .ck_green = 0, + .ck_blue = 0, + .xpos = 0, + .ypos = 0, +}; + +/*! + * @brief Framebuffer information structures. + * There are up to 3 framebuffers: background, TVout, and graphic window. + * If graphic window is configured, it must be the last framebuffer. + */ +static struct fb_info mx2fb_info[] = { + {.par = &mx2fbi_bg}, + {.par = &mx2fbi_gw}, +}; + +/*! + * @brief This structure contains pointers to the power management + * callback functions. + */ +static struct platform_driver mx2fb_driver = { + .driver = { + .name = "mxc_sdc_fb", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = mx2fb_probe, + .suspend = mx2fb_suspend, + .resume = mx2fb_resume, +}; + +/*! + * @brief Framebuffer file operations + */ +static struct fb_ops mx2fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mx2fb_check_var, + .fb_set_par = mx2fb_set_par, + .fb_setcolreg = mx2fb_setcolreg, + .fb_blank = mx2fb_blank, + .fb_pan_display = mx2fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_ioctl = mx2fb_ioctl, +}; + +/*! + * @brief Validates a var passed in. + * + * @param var Frame buffer variable screen structure + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Checks to see if the hardware supports the state requested by var passed + * in. This function does not alter the hardware state! If the var passed in + * is slightly off by what the hardware can support then we alter the var + * PASSED in to what we can do. If the hardware doesn't support mode change + * a -EINVAL will be returned by the upper layers. + * + */ +static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + unsigned long htotal, vtotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xoffset < 0) + var->xoffset = 0; + + if (var->yoffset < 0) + var->yoffset = 0; + + if (var->xoffset + info->var.xres > info->var.xres_virtual) + var->xoffset = info->var.xres_virtual - info->var.xres; + + if (var->yoffset + info->var.yres > info->var.yres_virtual) + var->yoffset = info->var.yres_virtual - info->var.yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* Copy nonstd field to/from sync for fbset usage */ + var->sync |= var->nonstd; + var->nonstd |= var->sync; + + return 0; +} + +/*! + * @brief Alters the hardware state. + * + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Zero on success others on failure + * + * Using the fb_var_screeninfo in fb_info we set the resolution of this + * particular framebuffer. This function alters the fb_fix_screeninfo stored + * in fb_info. It doesn't not alter var in fb_info since we are using that + * data. This means we depend on the data in var inside fb_info to be + * supported by the hardware. mx2fb_check_var is always called before + * mx2fb_set_par to ensure this. + */ +static int mx2fb_set_par(struct fb_info *info) +{ + unsigned long len; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + _set_fix(info); + + len = info->var.yres_virtual * info->fix.line_length; + if (len > info->fix.smem_len) { + if (info->fix.smem_start) + _unmap_video_memory(info); + + /* Memory allocation for framebuffer */ + if (_map_video_memory(info)) { + dev_err(info->device, "Unable to allocate fb memory\n"); + return -ENOMEM; + } + } + + _update_lcdc(info); + if (info->fbops->fb_blank) + info->fbops->fb_blank(mx2fbi->blank, info); + + return 0; +} + +/*! + * @brief Sets a color register. + * + * @param regno Which register in the CLUT we are programming + * @param red The red value which can be up to 16 bits wide + * @param green The green value which can be up to 16 bits wide + * @param blue The blue value which can be up to 16 bits wide. + * @param transp If supported the alpha value which can be up to + * 16 bits wide. + * @param info Frame buffer info structure + * + * @return Negative errno on error, or zero on success. + * + * Set a single color register. The values supplied have a 16 bit magnitude + * which needs to be scaled in this function for the hardware. Things to take + * into consideration are how many color registers, if any, are supported with + * the current color visual. With truecolor mode no color palettes are + * supported. Here a psuedo palette is created which we store the value in + * pseudo_palette in struct fb_info. For pseudocolor mode we have a limited + * color palette. + */ +static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + u32 v; + +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); +#undef CNVT_TOHW + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + pal[regno] = v; + ret = 0; + } + break; + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/*! + * @brief Pans the display. + * + * @param var Frame buffer variable screen structure + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Pan (or wrap, depending on the `vmode' field) the display using the + * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values + * don't fit, return -EINVAL. + */ +static int mx2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) { + return 0; /* No change, do nothing */ + } + + if (var->xoffset < 0 || var->yoffset < 0 + || var->xoffset + info->var.xres > info->var.xres_virtual + || var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + _update_lcdc(info); + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + return 0; +} + +/*! + * @brief Blanks the display. + * + * @param blank_mode The blank mode we want. + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Blank the screen if blank_mode != 0, else unblank. Return 0 if blanking + * succeeded, != 0 if un-/blanking failed. + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + */ +static int mx2fb_blank(int blank_mode, struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + dev_dbg(info->device, "blank mode = %d\n", blank_mode); + + mx2fbi->blank = blank_mode; + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + _disable_lcdc(info); + break; + case FB_BLANK_UNBLANK: + _enable_lcdc(info); + break; + } + + return 0; +} + +/*! + * @brief Ioctl function to support customized ioctl operations. + * + * @param info Framebuffer structure that represents a single frame buffer + * @param cmd The command number + * @param arg Argument which depends on cmd + * + * @return Negative errno on error, or zero on success. + */ +static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + struct mx2fb_gbl_alpha ga; + struct mx2fb_color_key ck; + + switch (cmd) { + case MX2FB_SET_GBL_ALPHA: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&ga, (void *)arg, sizeof(ga))) + return -EFAULT; + + g_gwinfo.alpha_value = ga.alpha; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; + case MX2FB_SET_CLR_KEY: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&ck, (void *)arg, sizeof(ck))) + return -EFAULT; + + g_gwinfo.ck_enabled = ck.enable; + g_gwinfo.ck_red = (ck.color_key & 0x003F0000) >> 16; + g_gwinfo.ck_green = (ck.color_key & 0x00003F00) >> 8; + g_gwinfo.ck_blue = ck.color_key & 0x0000003F; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; + case FBIOGET_GWINFO: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* get graphic window information */ + if (copy_to_user((void *)arg, (void *)&g_gwinfo, + sizeof(g_gwinfo))) + return -EFAULT; + break; + case FBIOPUT_GWINFO: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&g_gwinfo, (void *)arg, + sizeof(g_gwinfo))) + return -EFAULT; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; +#ifdef CONFIG_FB_MXC_TVOUT + case ENCODER_GET_CAPABILITIES:{ + int ret; + struct video_encoder_capability cap; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + ret = fs453_ioctl(cmd, &cap); + if (ret) + return ret; + + if (copy_to_user((void *)arg, &cap, sizeof(cap))) + return -EFAULT; + break; + } + case ENCODER_SET_NORM:{ + int ret; + unsigned long mode; + char *smode; + struct fb_var_screeninfo var; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + if (copy_from_user(&mode, (void *)arg, sizeof(mode))) + return -EFAULT; + ret = fs453_ioctl(cmd, &mode); + if (ret) + return ret; + + if (mode == VIDEO_ENCODER_PAL) + smode = MODE_PAL; + else if (mode == VIDEO_ENCODER_NTSC) + smode = MODE_NTSC; + else + smode = MODE_VGA; + + var = info->var; + var.nonstd = 0; + ret = fb_find_mode(&var, info, smode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp); + /* check for specified mode not found */ + if ((ret != 1) && (ret != 2)) + return -ENODEV; + + info->var = var; + fb_mode = smode; + return mx2fb_set_par(info); + } + case ENCODER_SET_INPUT: + case ENCODER_SET_OUTPUT: + case ENCODER_ENABLE_OUTPUT:{ + unsigned long varg; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + if (copy_from_user(&varg, (void *)arg, sizeof(varg))) + return -EFAULT; + return fs453_ioctl(cmd, &varg); + } +#endif + default: + dev_dbg(info->device, "Unknown ioctl command (0x%08X)\n", cmd); + return -EINVAL; + } + + return 0; +} + +/*! + * @brief Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + * @return Negative errno on error, or zero on success. + */ +static void _set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + strncpy(fix->id, mx2fbi->id, strlen(mx2fbi->id)); + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; +} + +/*! + * @brief Initialize framebuffer information structure. + * + * @param info framebuffer information pointer + * @param pdev pointer to struct device + * @return Negative errno on error, or zero on success. + */ +static int __init _init_fbinfo(struct fb_info *info, + struct platform_device *pdev) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + info->device = &pdev->dev; + info->var.activate = FB_ACTIVATE_NOW; + info->fbops = &mx2fb_ops; + info->flags = FBINFO_FLAG_DEFAULT; + info->pseudo_palette = &mx2fbi->pseudo_palette; + + /* Allocate colormap */ + fb_alloc_cmap(&info->cmap, 16, 0); + + return 0; +} + +/*! + * @brief Install framebuffer into the system. + * + * @param info framebuffer information pointer + * @param pdev pointer to struct device + * @return Negative errno on error, or zero on success. + */ +static int __init _install_fb(struct fb_info *info, + struct platform_device *pdev) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (_init_fbinfo(info, pdev)) + return -EINVAL; + + if (fb_mode == 0) + fb_mode = pdev->dev.platform_data; + + if (!fb_find_mode(&info->var, info, fb_mode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp)) { + fb_dealloc_cmap(&info->cmap); + return -EBUSY; + } + + /* Default Y virtual size is 2x panel size */ + /* info->var.yres_virtual = info->var.yres << 1; */ + + if (mx2fbi->type == MX2FB_TYPE_GW) + mx2fbi->blank = FB_BLANK_NORMAL; + else + mx2fbi->blank = FB_BLANK_UNBLANK; + + if (mx2fb_set_par(info)) { + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + if (register_framebuffer(info) < 0) { + _unmap_video_memory(info); + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + mx2fbi->registered = 1; + dev_info(info->device, "fb%d: %s fb device registered successfully.\n", + info->node, info->fix.id); + + return 0; +} + +/*! + * @brief Uninstall framebuffer from the system. + * + * @param info framebuffer information pointer + */ +static void __exit _uninstall_fb(struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (!mx2fbi->registered) + return; + + unregister_framebuffer(info); + _unmap_video_memory(info); + if (&info->cmap) + fb_dealloc_cmap(&info->cmap); + + mx2fbi->registered = 0; +} + +/*! + * @brief Allocate memory for framebuffer. + * + * @param info framebuffer information pointer + * @return Negative errno on error, or zero on success. + */ +static int _map_video_memory(struct fb_info *info) +{ + info->fix.smem_len = info->fix.line_length * info->var.yres_virtual; + info->screen_base = dma_alloc_coherent(0, + info->fix.smem_len, + (dma_addr_t *) &info->fix.smem_start, + GFP_DMA | GFP_KERNEL); + + if (info->screen_base == 0) { + dev_err(info->device, "Unable to allocate fb memory\n"); + return -EBUSY; + } + dev_dbg(info->device, "Allocated fb @ paddr=0x%08lX, size=%d.\n", + info->fix.smem_start, info->fix.smem_len); + + info->screen_size = info->fix.smem_len; + + /* Clear the screen */ + memset((char *)info->screen_base, 0, info->fix.smem_len); + + return 0; +} + +/*! + * @brief Release memory for framebuffer. + * @param info framebuffer information pointer + */ +static void _unmap_video_memory(struct fb_info *info) +{ + dma_free_coherent(0, info->fix.smem_len, info->screen_base, + (dma_addr_t) info->fix.smem_start); + + info->screen_base = 0; + info->fix.smem_start = 0; + info->fix.smem_len = 0; +} + +/*! + * @brief Enable LCD controller. + * @param info framebuffer information pointer + */ +static void _enable_lcdc(struct fb_info *info) +{ + static int first_enable = 1; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + /* + * Graphic window can only be enabled while the HCLK to the LCDC + * is disabled. Once enabled it can subsequently be disabled and + * enabled without turning off the HCLK. + * The graphic window is enabled and then disabled here. So next + * time to enable graphic window the HCLK to LCDC does not need + * to be disabled, and the flicker (due to disabling of HCLK to + * LCDC) is avoided. + */ + if (first_enable) { + _enable_graphic_window(info); + _disable_graphic_window(info); + first_enable = 0; + } + + if (mx2fbi->type == MX2FB_TYPE_GW) + _enable_graphic_window(info); + else if (!fb_enabled) { + clk_enable(lcdc_clk); + gpio_lcdc_active(); + board_power_lcd(1); + fb_enabled++; +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + unsigned long mode = 0; + + if (strcmp(fb_mode, MODE_VGA) == 0) + mode = VIDEO_ENCODER_VGA; + else if (strcmp(fb_mode, MODE_NTSC) == 0) + mode = VIDEO_ENCODER_NTSC; + else if (strcmp(fb_mode, MODE_PAL) == 0) + mode = VIDEO_ENCODER_PAL; + if (mode) + fs453_ioctl(ENCODER_SET_NORM, &mode); + } +#endif + } +} + +/*! + * @brief Disable LCD controller. + * @param info framebuffer information pointer + */ +static void _disable_lcdc(struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (mx2fbi->type == MX2FB_TYPE_GW) + _disable_graphic_window(info); + else { + if (fb_enabled) { + gpio_lcdc_inactive(); + board_power_lcd(0); + clk_disable(lcdc_clk); + fb_enabled = 0; + } +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + int enable = 0; + + if ((strcmp(fb_mode, MODE_VGA) == 0) + || (strcmp(fb_mode, MODE_NTSC) == 0) + || (strcmp(fb_mode, MODE_PAL) == 0)) + fs453_ioctl(ENCODER_ENABLE_OUTPUT, &enable); + } +#endif + } +} + +/*! + * @brief Enable graphic window. + * @param info framebuffer information pointer + */ +static void _enable_graphic_window(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + + g_gwinfo.enabled = 1; + + g_gwinfo.base = (var->yoffset * var->xres_virtual + var->xoffset); + g_gwinfo.base *= (var->bits_per_pixel) / 8; + g_gwinfo.base += info->fix.smem_start; + + g_gwinfo.xres = var->xres; + g_gwinfo.yres = var->yres; + g_gwinfo.xres_virtual = var->xres_virtual; + + mx2_gw_set(&g_gwinfo); +} + +/*! + * @brief Disable graphic window. + * @param info framebuffer information pointer + */ +static void _disable_graphic_window(struct fb_info *info) +{ + unsigned long i = 0; + + g_gwinfo.enabled = 0; + + /* + * Set alpha value to zero and reduce gw size, otherwise the graphic + * window will not be able to be enabled again. + */ + __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & 0x00FFFFFF, + LCDC_REG(LCDC_LGWCR)); + __raw_writel(((16 >> 4) << 20) + 16, LCDC_REG(LCDC_LGWSR)); + while (i < 1000) + i++; + + /* Now disable graphic window */ + __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & ~0x00400000, + LCDC_REG(LCDC_LGWCR)); + + dev_dbg(info->device, "Graphic window disabled.\n"); +} + +/*! + * @brief Setup graphic window properties. + * @param gwinfo graphic window information pointer + */ +void mx2_gw_set(struct fb_gwinfo *gwinfo) +{ + int width, height, xpos, ypos; + int width_bg, height_bg; + /* Graphic window control register */ + unsigned long lgwcr = 0x00400000; + + if (!gwinfo->enabled) { + _disable_graphic_window(0); + return; + } + + /* Graphic window start address register */ + __raw_writel(gwinfo->base, LCDC_REG(LCDC_LGWSAR)); + + /* + * The graphic window width, height, x position and y position + * must be synced up width the background window, otherwise there + * may be flickering. + */ + width_bg = (__raw_readl(LCDC_REG(LCDC_LSR)) & 0x03F00000) >> 16; + height_bg = __raw_readl(LCDC_REG(LCDC_LSR)) & 0x000003FF; + + width = (gwinfo->xres > width_bg) ? width_bg : gwinfo->xres; + height = (gwinfo->yres > height_bg) ? height_bg : gwinfo->yres; + + xpos = gwinfo->xpos; + ypos = gwinfo->ypos; + + if (xpos + width > width_bg) + xpos = width_bg - width; + if (ypos + height > height_bg) + ypos = height_bg - height; + + /* Graphic window size register */ + __raw_writel(((width >> 4) << 20) + height, LCDC_REG(LCDC_LGWSR)); + + /* Graphic window virtual page width register */ + __raw_writel(gwinfo->xres_virtual >> 1, LCDC_REG(LCDC_LGWVPWR)); + + /* Graphic window position register */ + __raw_writel(((xpos & 0x000003FF) << 16) | (ypos & 0x000003FF), + LCDC_REG(LCDC_LGWPR)); + + /* Graphic window panning offset register */ + __raw_writel(0, LCDC_REG(LCDC_LGWPOR)); + + /* Graphic window DMA control register */ + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + __raw_writel(0x00040060, LCDC_REG(LCDC_LGWDCR)); + else + __raw_writel(0x00020010, LCDC_REG(LCDC_LGWDCR)); + + /* Graphic window control register */ + lgwcr |= (gwinfo->alpha_value & 0x000000FF) << 24; + lgwcr |= gwinfo->ck_enabled ? 0x00800000 : 0; + lgwcr |= gwinfo->vs_reversed ? 0x00200000 : 0; + + /* + * Color keying value + * Todo: assume always use RGB565 + */ + lgwcr |= (gwinfo->ck_red & 0x0000003F) << 12; + lgwcr |= (gwinfo->ck_green & 0x0000003F) << 6; + lgwcr |= gwinfo->ck_blue & 0x0000003F; + + __raw_writel(lgwcr, LCDC_REG(LCDC_LGWCR)); + + pr_debug("Graphic window enabled.\n"); +} +EXPORT_SYMBOL(mx2_gw_set); + +/*! + * @brief Update LCDC registers + * @param info framebuffer information pointer + */ +static void _update_lcdc(struct fb_info *info) +{ + unsigned long base; + unsigned long perclk, pcd, pcr; + struct fb_var_screeninfo *var = &info->var; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (mx2fbi->type == MX2FB_TYPE_GW) { + _enable_graphic_window(info); + return; + } + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + /* Screen start address register */ + __raw_writel(base, LCDC_REG(LCDC_LSSAR)); + + /* Size register */ + dev_dbg(info->device, "xres = %d, yres = %d\n", + info->var.xres, info->var.yres); + __raw_writel(((info->var.xres >> 4) << 20) + info->var.yres, + LCDC_REG(LCDC_LSR)); + + /* Virtual page width register */ + __raw_writel(info->var.xres_virtual >> 1, LCDC_REG(LCDC_LVPWR)); + + /* To setup LCDC pixel clock */ + perclk = clk_round_rate(lcdc_clk, 134000000); + if (clk_set_rate(lcdc_clk, perclk)) { + printk(KERN_INFO "mx2fb: Unable to set clock to %lu\n", perclk); + perclk = clk_get_rate(lcdc_clk); + } + + /* Calculate pixel clock divider, and round to the nearest integer */ + pcd = (perclk * 8 / (PICOS2KHZ(var->pixclock) * 1000UL) + 4) / 8; + if (--pcd > 0x3F) + pcd = 0x3F; + + /* Panel configuration register */ + pcr = 0xFA008B80 | pcd; + pcr |= (var->sync & FB_SYNC_CLK_LAT_FALL) ? 0x00200000 : 0; + pcr |= (var->sync & FB_SYNC_DATA_INVERT) ? 0x01000000 : 0; + pcr |= (var->sync & FB_SYNC_SHARP_MODE) ? 0x00000040 : 0; + pcr |= (var->sync & FB_SYNC_OE_LOW_ACT) ? 0x00100000 : 0; + __raw_writel(pcr, LCDC_REG(LCDC_LPCR)); + + /* Horizontal and vertical configuration register */ + __raw_writel(((var->hsync_len - 1) << 26) + + ((var->right_margin - 1) << 8) + + (var->left_margin - 3), LCDC_REG(LCDC_LHCR)); + __raw_writel((var->vsync_len << 26) + + (var->lower_margin << 8) + + var->upper_margin, LCDC_REG(LCDC_LVCR)); + + /* Sharp configuration register */ + __raw_writel(0x00120300, LCDC_REG(LCDC_LSCR)); + + /* Refresh mode control reigster */ + __raw_writel(0x00000000, LCDC_REG(LCDC_LRMCR)); + + /* DMA control register */ + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + __raw_writel(0x00040060, LCDC_REG(LCDC_LDCR)); + else + __raw_writel(0x00020010, LCDC_REG(LCDC_LDCR)); +} + +/*! + * @brief Set LCD brightness + * @param level brightness level + */ +void mx2fb_set_brightness(uint8_t level) +{ + /* Set LCDC PWM contract control register */ + __raw_writel(0x00A90300 | level, LCDC_REG(LCDC_LPCCR)); +} +EXPORT_SYMBOL(mx2fb_set_brightness); + +/* + * @brief LCDC interrupt handler + */ +static irqreturn_t mx2fb_isr(int irq, void *dev_id) +{ + struct fb_event event; + unsigned long status = __raw_readl(LCDC_REG(LCDC_LISR)); + + if (status & MX2FB_INT_EOF) { + event.info = &mx2fb_info[0]; + atomic_notifier_call_chain(&mx2fb_notifier_list, + FB_EVENT_MXC_EOF, &event); + } + + if (status & MX2FB_INT_GW_EOF) { + event.info = &mx2fb_info[1]; + atomic_notifier_call_chain(&mx2fb_notifier_list, + FB_EVENT_MXC_EOF, &event); + } + + return IRQ_HANDLED; +} + +/*! + * @brief Config and request LCDC interrupt + */ +static void _request_irq(void) +{ + unsigned long status; + unsigned long flags; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + if (request_irq(MXC_INT_LCDC, mx2fb_isr, 0, "LCDC", 0)) + pr_info("Request LCDC IRQ failed.\n"); + else { + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Enable interrupt in case client has registered */ + if (mx2fb_notifier_list.head != NULL) { + unsigned long status; + unsigned long ints = MX2FB_INT_EOF; + + ints |= MX2FB_INT_GW_EOF; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + /* Configure interrupt condition for EOF */ + __raw_writel(0x0, LCDC_REG(LCDC_LICR)); + + /* Enable EOF and graphic window EOF interrupt */ + __raw_writel(ints, LCDC_REG(LCDC_LIER)); + } + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + } +} + +/*! + * @brief Free LCDC interrupt handler + */ +static void _free_irq(void) +{ + /* Disable all LCDC interrupt */ + __raw_writel(0x0, LCDC_REG(LCDC_LIER)); + + free_irq(MXC_INT_LCDC, 0); +} + +/*! + * @brief Register a client notifier + * @param nb notifier block to callback on events + */ +int mx2fb_register_client(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + ret = atomic_notifier_chain_register(&mx2fb_notifier_list, nb); + + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Enable interrupt in case client has registered */ + if (mx2fb_notifier_list.head != NULL) { + unsigned long status; + unsigned long ints = MX2FB_INT_EOF; + + ints |= MX2FB_INT_GW_EOF; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + /* Configure interrupt condition for EOF */ + __raw_writel(0x0, LCDC_REG(LCDC_LICR)); + + /* Enable EOF and graphic window EOF interrupt */ + __raw_writel(ints, LCDC_REG(LCDC_LIER)); + } + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + + return ret; +} +EXPORT_SYMBOL(mx2fb_register_client); + +/*! + * @brief Unregister a client notifier + * @param nb notifier block to callback on events + */ +int mx2fb_unregister_client(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + ret = atomic_notifier_chain_unregister(&mx2fb_notifier_list, nb); + + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Mask interrupt in case no client registered */ + if (mx2fb_notifier_list.head == NULL) + __raw_writel(0x0, LCDC_REG(LCDC_LIER)); + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + + return ret; +} +EXPORT_SYMBOL(mx2fb_unregister_client); + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * @brief Suspends the framebuffer and blanks the screen. + * Power management support + */ +static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + _disable_lcdc(&mx2fb_info[0]); + + return 0; +} + +/*! + * @brief Resumes the framebuffer and unblanks the screen. + * Power management support + */ +static int mx2fb_resume(struct platform_device *pdev) +{ + _enable_lcdc(&mx2fb_info[0]); + + return 0; +} + +#endif /* CONFIG_PM */ + +/*! + * @brief Probe routine for the framebuffer driver. It is called during the + * driver binding process. + * + * @return Appropriate error code to the kernel common code + */ +static int mx2fb_probe(struct platform_device *pdev) +{ + int ret, i; + + lcdc_clk = clk_get(&pdev->dev, "lcdc_clk"); + + for (i = 0; i < sizeof(mx2fb_info) / sizeof(struct fb_info); i++) { + ret = _install_fb(&mx2fb_info[i], pdev); + if (ret) { + dev_err(&pdev->dev, + "Failed to register framebuffer %d\n", i); + return ret; + } + } + _request_irq(); + + return 0; +} + +/*! + * @brief Initialization + */ +int __init mx2fb_init(void) +{ + /* + * For kernel boot options (in 'video=xxxfb:<options>' format) + */ +#ifndef MODULE + { + char *option; + + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mx2fb_setup(option); + } +#endif + return platform_driver_register(&mx2fb_driver); +} + +/*! + * @brief Cleanup + */ +void __exit mx2fb_exit(void) +{ + int i; + + _free_irq(); + for (i = sizeof(mx2fb_info) / sizeof(struct fb_info); i > 0; i--) + _uninstall_fb(&mx2fb_info[i - 1]); + + platform_driver_unregister(&mx2fb_driver); +} + +#ifndef MODULE +/*! + * @brief Setup + * Parse user specified options + * Example: video=mxcfb:240x320,bpp=16,Sharp-QVGA + */ +static int __init mx2fb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + fb_mode = 0; + fb_enabled = 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + + return 0; +} +#endif + +/* Modularization */ +module_init(mx2fb_init); +module_exit(mx2fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX2 framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mx2fb.h b/drivers/video/mxc/mx2fb.h new file mode 100644 index 00000000..86c7abfd --- /dev/null +++ b/drivers/video/mxc/mx2fb.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2004-2007, 2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx2fb.h + * + * @brief Header file for the MX27 Frame buffer + * + * @ingroup Framebuffer + */ +#ifndef __MX2FB_H__ +#define __MX2FB_H__ + +/*! @brief MX27 LCDC graphic window information */ +struct fb_gwinfo { + /*! Non-zero if graphic window is enabled */ + __u32 enabled; + + /* The fields below are valid only when graphic window is enabled */ + + /*! Graphic window alpha value from 0 to 255 */ + __u32 alpha_value; + + /*! Non-zero if graphic window color keying is enabled. */ + __u32 ck_enabled; + + /* + * The fields ck_red, ck_green and ck_blue are valid only when + * graphic window and the color keying are enabled. They are the + * color component of graphic window color keying. + */ + + /*! Color keying red component */ + __u32 ck_red; + + /*! Color keying green component */ + __u32 ck_green; + + /*! Color keying blue component */ + __u32 ck_blue; + + /*! Graphic window x position */ + __u32 xpos; + + /*! Graphic window y position */ + __u32 ypos; + + /*! Non-zero if graphic window vertical scan in reverse direction. */ + __u32 vs_reversed; + + /* + * The following fields are valid for FBIOGET_GWINFO and + * mx2_gw_set(). FBIOPUT_GWINFO ignores these fields. + */ + __u32 base; /* Graphic window start address */ + __u32 xres; /* Visible x resolution */ + __u32 yres; /* Visible y resolution */ + __u32 xres_virtual; /* Virtual x resolution */ +}; + +/* 0x46E0-0x46FF are reserved for MX27 */ +#define FBIOGET_GWINFO 0x46E0 /*!< Get graphic window information */ +#define FBIOPUT_GWINFO 0x46E1 /*!< Set graphic window information */ + +struct mx2fb_gbl_alpha { + int enable; + int alpha; +}; + +struct mx2fb_color_key { + int enable; + __u32 color_key; +}; + +#define MX2FB_SET_GBL_ALPHA _IOW('M', 0, struct mx2fb_gbl_alpha) +#define MX2FB_SET_CLR_KEY _IOW('M', 1, struct mx2fb_color_key) +#define MX2FB_WAIT_FOR_VSYNC _IOW('F', 0x20, u_int32_t) + +#ifdef __KERNEL__ + +/* + * LCDC register definitions + */ +#define LCDC_LSSAR 0x00 +#define LCDC_LSR 0x04 +#define LCDC_LVPWR 0x08 +#define LCDC_LCPR 0x0C +#define LCDC_LCWHBR 0x10 +#define LCDC_LCCMR 0x14 +#define LCDC_LPCR 0x18 +#define LCDC_LHCR 0x1C +#define LCDC_LVCR 0x20 +#define LCDC_LPOR 0x24 +#define LCDC_LSCR 0x28 +#define LCDC_LPCCR 0x2C +#define LCDC_LDCR 0x30 +#define LCDC_LRMCR 0x34 +#define LCDC_LICR 0x38 +#define LCDC_LIER 0x3C +#define LCDC_LISR 0x40 +#define LCDC_LGWSAR 0x50 +#define LCDC_LGWSR 0x54 +#define LCDC_LGWVPWR 0x58 +#define LCDC_LGWPOR 0x5C +#define LCDC_LGWPR 0x60 +#define LCDC_LGWCR 0x64 +#define LCDC_LGWDCR 0x68 +#define LCDC_LAUSCR 0x80 +#define LCDC_LAUSCCR 0x84 + +#define LCDC_REG(reg) (IO_ADDRESS(LCDC_BASE_ADDR) + reg) + +#define MX2FB_INT_BOF 0x0001 /* Beginning of Frame */ +#define MX2FB_INT_EOF 0x0002 /* End of Frame */ +#define MX2FB_INT_ERR_RES 0x0004 /* Error Response */ +#define MX2FB_INT_UDR_ERR 0x0008 /* Under Run Error */ +#define MX2FB_INT_GW_BOF 0x0010 /* Graphic Window BOF */ +#define MX2FB_INT_GW_EOF 0x0020 /* Graphic Window EOF */ +#define MX2FB_INT_GW_ERR_RES 0x0040 /* Graphic Window ERR_RES */ +#define MX2FB_INT_GW_UDR_ERR 0x0080 /* Graphic Window UDR_ERR */ + +#define FB_EVENT_MXC_EOF 0x8001 /* End of Frame event */ + +int mx2fb_register_client(struct notifier_block *nb); +int mx2fb_unregister_client(struct notifier_block *nb); + +void mx2_gw_set(struct fb_gwinfo *gwinfo); + +#endif /* __KERNEL__ */ + +#endif /* __MX2FB_H__ */ diff --git a/drivers/video/mxc/mxc_dispdrv.c b/drivers/video/mxc/mxc_dispdrv.c new file mode 100644 index 00000000..3d1cb43e --- /dev/null +++ b/drivers/video/mxc/mxc_dispdrv.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_dispdrv.c + * @brief mxc display driver framework. + * + * A display device driver could call mxc_dispdrv_register(drv) in its dev_probe() function. + * Move all dev_probe() things into mxc_dispdrv_driver->init(), init() function should init + * and feedback setting; + * Move all dev_remove() things into mxc_dispdrv_driver->deinit(); + * Move all dev_suspend() things into fb_notifier for SUSPEND, if there is; + * Move all dev_resume() things into fb_notifier for RESUME, if there is; + * + * ipuv3 fb driver could call mxc_dispdrv_gethandle(name, setting) before a fb + * need be added, with fbi param passing by setting, after + * mxc_dispdrv_gethandle() return, FB driver should get the basic setting + * about fbi info and ipuv3-hw (ipu_id and disp_id). + * + * @ingroup Framebuffer + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/string.h> +#include "mxc_dispdrv.h" + +static LIST_HEAD(dispdrv_list); +static DEFINE_MUTEX(dispdrv_lock); + +struct mxc_dispdrv_entry { + /* Note: drv always the first element */ + struct mxc_dispdrv_driver *drv; + bool active; + void *priv; + struct list_head list; + struct device *dev; +}; + +void mxc_dispdrv_setdev(struct mxc_dispdrv_handle *drv_handle, struct device *dev) +{ + struct mxc_dispdrv_entry *dentry; + dentry = (struct mxc_dispdrv_entry *)drv_handle; + dentry->dev = dev; +} + +struct device *mxc_dispdrv_getdev(struct mxc_dispdrv_handle *drv_handle) +{ + struct mxc_dispdrv_entry *dentry; + dentry = (struct mxc_dispdrv_entry *)drv_handle; + return dentry->dev; +} + +struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv) +{ + struct mxc_dispdrv_entry *new; + + mutex_lock(&dispdrv_lock); + + new = kzalloc(sizeof(struct mxc_dispdrv_entry), GFP_KERNEL); + if (!new) { + mutex_unlock(&dispdrv_lock); + return ERR_PTR(-ENOMEM); + } + + new->drv = drv; + list_add_tail(&new->list, &dispdrv_list); + + mutex_unlock(&dispdrv_lock); + + return (struct mxc_dispdrv_handle *)new; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_register); + +int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle) +{ + struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle; + + if (entry) { + mutex_lock(&dispdrv_lock); + list_del(&entry->list); + mutex_unlock(&dispdrv_lock); + kfree(entry); + return 0; + } else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_unregister); + +struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name, + struct mxc_dispdrv_setting *setting) +{ + int ret, found = 0; + struct mxc_dispdrv_entry *entry; + + mutex_lock(&dispdrv_lock); + list_for_each_entry(entry, &dispdrv_list, list) { + if (!strcmp(entry->drv->name, name) && (entry->drv->init)) { + ret = entry->drv->init((struct mxc_dispdrv_handle *) + entry, setting); + if (ret >= 0) { + entry->active = true; + found = 1; + break; + } + } + } + mutex_unlock(&dispdrv_lock); + + return found ? (struct mxc_dispdrv_handle *)entry:ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_gethandle); + +void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle) +{ + struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle; + + mutex_lock(&dispdrv_lock); + if (entry && entry->active && entry->drv->deinit) { + entry->drv->deinit(handle); + entry->active = false; + } + mutex_unlock(&dispdrv_lock); + +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_puthandle); + +int mxc_dispdrv_setdata(struct mxc_dispdrv_handle *handle, void *data) +{ + struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle; + + if (entry) { + entry->priv = data; + return 0; + } else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_setdata); + +void *mxc_dispdrv_getdata(struct mxc_dispdrv_handle *handle) +{ + struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle; + + if (entry) { + return entry->priv; + } else + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_getdata); diff --git a/drivers/video/mxc/mxc_dispdrv.h b/drivers/video/mxc/mxc_dispdrv.h new file mode 100644 index 00000000..9a722176 --- /dev/null +++ b/drivers/video/mxc/mxc_dispdrv.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_DISPDRV_H__ +#define __MXC_DISPDRV_H__ +#include <linux/fb.h> + +struct mxc_dispdrv_handle { + struct mxc_dispdrv_driver *drv; +}; + +struct mxc_dispdrv_setting { + /*input-feedback parameter*/ + struct fb_info *fbi; + int if_fmt; + int default_bpp; + char *dft_mode_str; + + /*feedback parameter*/ + int dev_id; + int disp_id; +}; + +struct mxc_dispdrv_driver { + const char *name; + int (*init) (struct mxc_dispdrv_handle *, struct mxc_dispdrv_setting *); + void (*deinit) (struct mxc_dispdrv_handle *); + /* display driver enable function for extension */ + int (*enable) (struct mxc_dispdrv_handle *); + /* display driver disable function, called at early part of fb_blank */ + void (*disable) (struct mxc_dispdrv_handle *); + /* display driver setup function, called at early part of fb_set_par */ + int (*setup) (struct mxc_dispdrv_handle *, struct fb_info *fbi); +}; + +struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv); +int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle); +struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name, + struct mxc_dispdrv_setting *setting); +void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle); +int mxc_dispdrv_setdata(struct mxc_dispdrv_handle *handle, void *data); +void *mxc_dispdrv_getdata(struct mxc_dispdrv_handle *handle); +void mxc_dispdrv_setdev(struct mxc_dispdrv_handle *drv_handle, struct device *dev); +struct device *mxc_dispdrv_getdev(struct mxc_dispdrv_handle *drv_handle); +#endif diff --git a/drivers/video/mxc/mxc_dvi.c b/drivers/video/mxc/mxc_dvi.c new file mode 100644 index 00000000..923db438 --- /dev/null +++ b/drivers/video/mxc/mxc_dvi.c @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_dvi.c + * + * @brief MXC DVI driver + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/i2c.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/fsl_devices.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/regulator/consumer.h> +#include <mach/mxc_edid.h> +#include "mxc_dispdrv.h" +#include "../edid.h" + +#define MXC_EDID_LENGTH (EDID_LENGTH*4) + +#define DISPDRV_DVI "dvi" + +struct mxc_dvi_data { + struct i2c_client *client; + struct platform_device *pdev; + struct mxc_dispdrv_handle *disp_dvi; + struct delayed_work det_work; + struct fb_info *fbi; + struct mxc_edid_cfg edid_cfg; + u8 cable_plugin; + u8 edid[MXC_EDID_LENGTH]; + + u32 ipu; + u32 di; + void (*init)(void); + int (*update)(void); + struct regulator *analog_reg; +}; + +static ssize_t mxc_dvi_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxc_dvi_data *dvi = dev_get_drvdata(dev); + + if (dvi->cable_plugin == 0) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, mxc_dvi_show_state, NULL); + +static ssize_t mxc_dvi_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxc_dvi_data *dvi = dev_get_drvdata(dev); + + strcpy(buf, dvi->fbi->fix.id); + sprintf(buf+strlen(buf), "\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(fb_name, S_IRUGO, mxc_dvi_show_name, NULL); + +static ssize_t mxc_dvi_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxc_dvi_data *dvi = dev_get_drvdata(dev); + int i, j, len = 0; + + for (j = 0; j < MXC_EDID_LENGTH/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + dvi->edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, mxc_dvi_show_edid, NULL); + +static void det_worker(struct work_struct *work) +{ + struct delayed_work *delay_work = to_delayed_work(work); + struct mxc_dvi_data *dvi = + container_of(delay_work, struct mxc_dvi_data, det_work); + char event_string[16]; + char *envp[] = { event_string, NULL }; + + /* cable connection changes */ + if (dvi->update()) { + u8 edid_old[MXC_EDID_LENGTH]; + dvi->cable_plugin = 1; + sprintf(event_string, "EVENT=plugin"); + + memcpy(edid_old, dvi->edid, MXC_EDID_LENGTH); + + if (mxc_edid_read(dvi->client->adapter, dvi->client->addr, + dvi->edid, &dvi->edid_cfg, dvi->fbi) < 0) + dev_err(&dvi->client->dev, + "MXC dvi: read edid fail\n"); + else { + if (!memcmp(edid_old, dvi->edid, MXC_EDID_LENGTH)) + dev_info(&dvi->client->dev, + "Sii902x: same edid\n"); + else if (dvi->fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + fb_destroy_modelist(&dvi->fbi->modelist); + + for (i = 0; i < dvi->fbi->monspecs.modedb_len; i++) + /*FIXME now we do not support interlaced mode */ + if (!(dvi->fbi->monspecs.modedb[i].vmode & FB_VMODE_INTERLACED)) + fb_add_videomode(&dvi->fbi->monspecs.modedb[i], + &dvi->fbi->modelist); + + fb_var_to_videomode(&m, &dvi->fbi->var); + mode = fb_find_nearest_mode(&m, + &dvi->fbi->modelist); + + fb_videomode_to_var(&dvi->fbi->var, mode); + + dvi->fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + dvi->fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(dvi->fbi, &dvi->fbi->var); + dvi->fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + } + } + } else { + dvi->cable_plugin = 0; + sprintf(event_string, "EVENT=plugout"); + } + + kobject_uevent_env(&dvi->pdev->dev.kobj, KOBJ_CHANGE, envp); +} + +static irqreturn_t mxc_dvi_detect_handler(int irq, void *data) +{ + struct mxc_dvi_data *dvi = data; + schedule_delayed_work(&(dvi->det_work), msecs_to_jiffies(300)); + return IRQ_HANDLED; +} + +static int dvi_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + int ret = 0; + struct mxc_dvi_data *dvi = mxc_dispdrv_getdata(disp); + struct fsl_mxc_dvi_platform_data *plat = dvi->client->dev.platform_data; + + setting->dev_id = dvi->ipu = plat->ipu_id; + setting->disp_id = dvi->di = plat->disp_id; + setting->if_fmt = IPU_PIX_FMT_RGB24; + dvi->fbi = setting->fbi; + dvi->init = plat->init; + dvi->update = plat->update; + + dvi->analog_reg = regulator_get(&dvi->pdev->dev, plat->analog_regulator); + if (!IS_ERR(dvi->analog_reg)) { + regulator_set_voltage(dvi->analog_reg, 2775000, 2775000); + regulator_enable(dvi->analog_reg); + } + + if (dvi->init) + dvi->init(); + + /* get video mode from edid */ + if (!dvi->update) + return -EINVAL; + else { + bool found = false; + + INIT_LIST_HEAD(&dvi->fbi->modelist); + if (dvi->update()) { + dvi->cable_plugin = 1; + /* try to read edid */ + if (mxc_edid_read(dvi->client->adapter, dvi->client->addr, + dvi->edid, &dvi->edid_cfg, dvi->fbi) < 0) + dev_warn(&dvi->client->dev, "Can not read edid\n"); + else if (dvi->fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + for (i = 0; i < dvi->fbi->monspecs.modedb_len; i++) { + /*FIXME now we do not support interlaced mode */ + if (!(dvi->fbi->monspecs.modedb[i].vmode + & FB_VMODE_INTERLACED)) + fb_add_videomode( + &dvi->fbi->monspecs.modedb[i], + &dvi->fbi->modelist); + } + + fb_find_mode(&dvi->fbi->var, dvi->fbi, setting->dft_mode_str, + NULL, 0, NULL, setting->default_bpp); + + fb_var_to_videomode(&m, &dvi->fbi->var); + mode = fb_find_nearest_mode(&m, + &dvi->fbi->modelist); + fb_videomode_to_var(&dvi->fbi->var, mode); + found = 1; + } + } else + dvi->cable_plugin = 0; + + if (!found) { + ret = fb_find_mode(&dvi->fbi->var, dvi->fbi, setting->dft_mode_str, + NULL, 0, NULL, setting->default_bpp); + if (!ret) + return -EINVAL; + } + } + + /* cable detection */ + if (dvi->client->irq) { + ret = request_irq(dvi->client->irq, mxc_dvi_detect_handler, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "dvi_det", dvi); + if (ret < 0) { + dev_warn(&dvi->client->dev, + "MXC dvi: cound not request det irq %d\n", + dvi->client->irq); + goto err; + } else { + INIT_DELAYED_WORK(&(dvi->det_work), det_worker); + ret = device_create_file(&dvi->pdev->dev, &dev_attr_fb_name); + if (ret < 0) + dev_warn(&dvi->client->dev, + "MXC dvi: cound not create sys node for fb name\n"); + ret = device_create_file(&dvi->pdev->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&dvi->client->dev, + "MXC dvi: cound not create sys node for cable state\n"); + ret = device_create_file(&dvi->pdev->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&dvi->client->dev, + "MXC dvi: cound not create sys node for edid\n"); + + dev_set_drvdata(&dvi->pdev->dev, dvi); + } + } + +err: + return ret; +} + +static void dvi_deinit(struct mxc_dispdrv_handle *disp) +{ + struct mxc_dvi_data *dvi = mxc_dispdrv_getdata(disp); + + if (!IS_ERR(dvi->analog_reg)) + regulator_disable(dvi->analog_reg); + + free_irq(dvi->client->irq, dvi); +} + +static struct mxc_dispdrv_driver dvi_drv = { + .name = DISPDRV_DVI, + .init = dvi_init, + .deinit = dvi_deinit, +}; + +static int __devinit mxc_dvi_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxc_dvi_data *dvi; + int ret = 0; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -ENODEV; + + dvi = kzalloc(sizeof(struct mxc_dvi_data), GFP_KERNEL); + if (!dvi) { + ret = -ENOMEM; + goto alloc_failed; + } + + dvi->pdev = platform_device_register_simple("mxc_dvi", 0, NULL, 0); + if (IS_ERR(dvi->pdev)) { + printk(KERN_ERR + "Unable to register MXC DVI as a platform device\n"); + ret = PTR_ERR(dvi->pdev); + goto pdev_reg_failed; + } + + dvi->client = client; + dvi->disp_dvi = mxc_dispdrv_register(&dvi_drv); + mxc_dispdrv_setdata(dvi->disp_dvi, dvi); + + i2c_set_clientdata(client, dvi); + + return ret; + +pdev_reg_failed: + kfree(dvi); +alloc_failed: + return ret; +} + +static int __devexit mxc_dvi_remove(struct i2c_client *client) +{ + struct mxc_dvi_data *dvi = i2c_get_clientdata(client); + + mxc_dispdrv_puthandle(dvi->disp_dvi); + mxc_dispdrv_unregister(dvi->disp_dvi); + platform_device_unregister(dvi->pdev); + kfree(dvi); + return 0; +} + +static const struct i2c_device_id mxc_dvi_id[] = { + { "mxc_dvi", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mxc_dvi_id); + +static struct i2c_driver mxc_dvi_i2c_driver = { + .driver = { + .name = "mxc_dvi", + }, + .probe = mxc_dvi_probe, + .remove = mxc_dvi_remove, + .id_table = mxc_dvi_id, +}; + +static int __init mxc_dvi_init(void) +{ + return i2c_add_driver(&mxc_dvi_i2c_driver); +} + +static void __exit mxc_dvi_exit(void) +{ + i2c_del_driver(&mxc_dvi_i2c_driver); +} + +module_init(mxc_dvi_init); +module_exit(mxc_dvi_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC DVI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c new file mode 100644 index 00000000..721ff886 --- /dev/null +++ b/drivers/video/mxc/mxc_edid.c @@ -0,0 +1,763 @@ +/* + * Copyright 2009-2013 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.c + * + * @brief MXC EDID driver + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/i2c.h> +#include <linux/fb.h> +#include <mach/mxc_edid.h> +#include "../edid.h" + +#undef DEBUG /* define this for verbose EDID parsing output */ +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +const struct fb_videomode mxc_cea_mode[64] = { + /* #1: 640x480p@59.94/60Hz 4:3 */ + [1] = { + NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #2: 720x480p@59.94/60Hz 4:3 */ + [2] = { + NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #3: 720x480p@59.94/60Hz 16:9 */ + [3] = { + NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #4: 1280x720p@59.94/60Hz 16:9 */ + [4] = { + NULL, 60, 1280, 720, 13468, 220, 110, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0 + }, + /* #5: 1920x1080i@59.94/60Hz 16:9 */ + [5] = { + NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #6: 720(1440)x480iH@59.94/60Hz 4:3 */ + [6] = { + NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_INTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #7: 720(1440)x480iH@59.94/60Hz 16:9 */ + [7] = { + NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #8: 720(1440)x240pH@59.94/60Hz 4:3 */ + [8] = { + NULL, 60, 1440, 240, 37108, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #9: 720(1440)x240pH@59.94/60Hz 16:9 */ + [9] = { + NULL, 60, 1440, 240, 37108, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #14: 1440x480p@59.94/60Hz 4:3 */ + [14] = { + NULL, 60, 1440, 480, 18500, 120, 32, 30, 9, 124, 6, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #15: 1440x480p@59.94/60Hz 16:9 */ + [15] = { + NULL, 60, 1440, 480, 18500, 120, 32, 30, 9, 124, 6, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #16: 1920x1080p@60Hz 16:9 */ + [16] = { + NULL, 60, 1920, 1080, 6734, 148, 88, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #17: 720x576pH@50Hz 4:3 */ + [17] = { + NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #18: 720x576pH@50Hz 16:9 */ + [18] = { + NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #19: 1280x720p@50Hz */ + [19] = { + NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #20: 1920x1080i@50Hz */ + [20] = { + NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #23: 720(1440)x288pH@50Hz 4:3 */ + [23] = { + NULL, 50, 1440, 288, 37037, 138, 24, 19, 2, 126, 3, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #24: 720(1440)x288pH@50Hz 16:9 */ + [24] = { + NULL, 50, 1440, 288, 37037, 138, 24, 19, 2, 126, 3, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #29: 720(1440)x576pH@50Hz 4:3 */ + [29] = { + NULL, 50, 1440, 576, 18518, 136, 24, 39, 5, 128, 5, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0, + }, + /* #30: 720(1440)x576pH@50Hz 16:9 */ + [30] = { + NULL, 50, 1440, 576, 18518, 136, 24, 39, 5, 128, 5, 0, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #31: 1920x1080p@50Hz */ + [31] = { + NULL, 50, 1920, 1080, 6734, 148, 528, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #32: 1920x1080p@23.98/24Hz */ + [32] = { + NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #33: 1920x1080p@25Hz */ + [33] = { + NULL, 25, 1920, 1080, 13468, 148, 528, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #34: 1920x1080p@30Hz */ + [34] = { + NULL, 30, 1920, 1080, 13468, 148, 88, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0, + }, + /* #41: 1280x720p@100Hz 16:9 */ + [41] = { + NULL, 100, 1280, 720, 6734, 220, 440, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0 + }, + /* #47: 1280x720p@119.88/120Hz 16:9 */ + [47] = { + NULL, 120, 1280, 720, 6734, 220, 110, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0 + }, +}; + +/* + * We have a special version of fb_mode_is_equal that ignores + * pixclock, since for many CEA modes, 2 frequencies are supported + * e.g. 640x480 @ 60Hz or 59.94Hz + */ +int mxc_edid_fb_mode_is_equal(bool use_aspect, + const struct fb_videomode *mode1, + const struct fb_videomode *mode2) +{ + u32 mask; + + if (use_aspect) + mask = ~0; + else + mask = ~FB_VMODE_ASPECT_MASK; + + return (mode1->xres == mode2->xres && + mode1->yres == mode2->yres && + mode1->hsync_len == mode2->hsync_len && + mode1->vsync_len == mode2->vsync_len && + mode1->left_margin == mode2->left_margin && + mode1->right_margin == mode2->right_margin && + mode1->upper_margin == mode2->upper_margin && + mode1->lower_margin == mode2->lower_margin && + mode1->sync == mode2->sync && + /* refresh check, 59.94Hz and 60Hz have the same parameter + * in struct of mxc_cea_mode */ + abs(mode1->refresh - mode2->refresh) <= 1 && + (mode1->vmode & mask) == (mode2->vmode & mask)); +} + +static void get_detailed_timing(unsigned char *block, + struct fb_videomode *mode) +{ + mode->xres = H_ACTIVE; + mode->yres = V_ACTIVE; + mode->pixclock = PIXEL_CLOCK; + mode->pixclock /= 1000; + mode->pixclock = KHZ2PICOS(mode->pixclock); + mode->right_margin = H_SYNC_OFFSET; + mode->left_margin = (H_ACTIVE + H_BLANKING) - + (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); + mode->upper_margin = V_BLANKING - V_SYNC_OFFSET - + V_SYNC_WIDTH; + mode->lower_margin = V_SYNC_OFFSET; + mode->hsync_len = H_SYNC_WIDTH; + mode->vsync_len = V_SYNC_WIDTH; + if (HSYNC_POSITIVE) + mode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (VSYNC_POSITIVE) + mode->sync |= FB_SYNC_VERT_HIGH_ACT; + mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) * + (V_ACTIVE + V_BLANKING)); + if (INTERLACED) { + mode->yres *= 2; + mode->upper_margin *= 2; + mode->lower_margin *= 2; + mode->vsync_len *= 2; + mode->vmode |= FB_VMODE_INTERLACED; + } + mode->flag = FB_MODE_IS_DETAILED; + + if ((H_SIZE / 16) == (V_SIZE / 9)) + mode->vmode |= FB_VMODE_ASPECT_16_9; + else if ((H_SIZE / 4) == (V_SIZE / 3)) + mode->vmode |= FB_VMODE_ASPECT_4_3; + else if ((mode->xres / 16) == (mode->yres / 9)) + mode->vmode |= FB_VMODE_ASPECT_16_9; + else if ((mode->xres / 4) == (mode->yres / 3)) + mode->vmode |= FB_VMODE_ASPECT_4_3; + + if (mode->vmode & FB_VMODE_ASPECT_16_9) + DPRINTK("Aspect ratio: 16:9\n"); + if (mode->vmode & FB_VMODE_ASPECT_4_3) + DPRINTK("Aspect ratio: 4:3\n"); + DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000); + DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET, + H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING); + DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET, + V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING); + DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-", + (VSYNC_POSITIVE) ? "+" : "-"); +} + +int mxc_edid_parse_ext_blk(unsigned char *edid, + struct mxc_edid_cfg *cfg, + struct fb_monspecs *specs) +{ + char detail_timing_desc_offset; + struct fb_videomode *mode, *m; + unsigned char index = 0x0; + unsigned char *block; + int i, num = 0, revision; + + if (edid[index++] != 0x2) /* only support cea ext block now */ + return -1; + revision = edid[index++]; + DPRINTK("cea extent revision %d\n", revision); + mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); + if (mode == NULL) + return -1; + + detail_timing_desc_offset = edid[index++]; + + if (revision >= 2) { + cfg->cea_underscan = (edid[index] >> 7) & 0x1; + cfg->cea_basicaudio = (edid[index] >> 6) & 0x1; + cfg->cea_ycbcr444 = (edid[index] >> 5) & 0x1; + cfg->cea_ycbcr422 = (edid[index] >> 4) & 0x1; + + DPRINTK("CEA underscan %d\n", cfg->cea_underscan); + DPRINTK("CEA basicaudio %d\n", cfg->cea_basicaudio); + DPRINTK("CEA ycbcr444 %d\n", cfg->cea_ycbcr444); + DPRINTK("CEA ycbcr422 %d\n", cfg->cea_ycbcr422); + } + + if (revision >= 3) { + /* short desc */ + DPRINTK("CEA Short desc timmings\n"); + index++; + while (index < detail_timing_desc_offset) { + unsigned char tagcode, blklen; + + tagcode = (edid[index] >> 5) & 0x7; + blklen = (edid[index]) & 0x1f; + + DPRINTK("Tagcode %x Len %d\n", tagcode, blklen); + + switch (tagcode) { + case 0x2: /*Video data block*/ + { + int cea_idx; + i = 0; + while (i < blklen) { + index++; + cea_idx = edid[index] & 0x7f; + if (cea_idx < ARRAY_SIZE(mxc_cea_mode) && + (mxc_cea_mode[cea_idx].xres)) { + DPRINTK("Support CEA Format #%d\n", cea_idx); + mode[num] = mxc_cea_mode[cea_idx]; + mode[num].flag |= FB_MODE_IS_STANDARD; + num++; + } + i++; + } + break; + } + case 0x3: /*Vendor specific data*/ + { + unsigned char IEEE_reg_iden[3]; + unsigned char deep_color; + unsigned char latency_present; + unsigned char I_latency_present; + unsigned char hdmi_video_present; + unsigned char hdmi_3d_present; + unsigned char hdmi_3d_multi_present; + unsigned char hdmi_vic_len; + unsigned char hdmi_3d_len; + unsigned char index_inc = 0; + unsigned char vsd_end; + + vsd_end = index + blklen; + + IEEE_reg_iden[0] = edid[index+1]; + IEEE_reg_iden[1] = edid[index+2]; + IEEE_reg_iden[2] = edid[index+3]; + cfg->physical_address[0] = (edid[index+4] & 0xf0) >> 4; + cfg->physical_address[1] = (edid[index+4] & 0x0f); + cfg->physical_address[2] = (edid[index+5] & 0xf0) >> 4; + cfg->physical_address[3] = (edid[index+5] & 0x0f); + + if ((IEEE_reg_iden[0] == 0x03) && + (IEEE_reg_iden[1] == 0x0c) && + (IEEE_reg_iden[2] == 0x00)) + cfg->hdmi_cap = 1; + + if (blklen > 5) { + deep_color = edid[index+6]; + if (deep_color & 0x80) + cfg->vsd_support_ai = true; + if (deep_color & 0x40) + cfg->vsd_dc_48bit = true; + if (deep_color & 0x20) + cfg->vsd_dc_36bit = true; + if (deep_color & 0x10) + cfg->vsd_dc_30bit = true; + if (deep_color & 0x08) + cfg->vsd_dc_y444 = true; + if (deep_color & 0x01) + cfg->vsd_dvi_dual = true; + } + + DPRINTK("VSD hdmi capability %d\n", cfg->hdmi_cap); + DPRINTK("VSD support ai %d\n", cfg->vsd_support_ai); + DPRINTK("VSD support deep color 48bit %d\n", cfg->vsd_dc_48bit); + DPRINTK("VSD support deep color 36bit %d\n", cfg->vsd_dc_36bit); + DPRINTK("VSD support deep color 30bit %d\n", cfg->vsd_dc_30bit); + DPRINTK("VSD support deep color y444 %d\n", cfg->vsd_dc_y444); + DPRINTK("VSD support dvi dual %d\n", cfg->vsd_dvi_dual); + + if (blklen > 6) + cfg->vsd_max_tmdsclk_rate = edid[index+7] * 5; + DPRINTK("VSD MAX TMDS CLOCK RATE %d\n", cfg->vsd_max_tmdsclk_rate); + + if (blklen > 7) { + latency_present = edid[index+8] >> 7; + I_latency_present = (edid[index+8] & 0x40) >> 6; + hdmi_video_present = (edid[index+8] & 0x20) >> 5; + cfg->vsd_cnc3 = (edid[index+8] & 0x8) >> 3; + cfg->vsd_cnc2 = (edid[index+8] & 0x4) >> 2; + cfg->vsd_cnc1 = (edid[index+8] & 0x2) >> 1; + cfg->vsd_cnc0 = edid[index+8] & 0x1; + + DPRINTK("VSD cnc0 %d\n", cfg->vsd_cnc0); + DPRINTK("VSD cnc1 %d\n", cfg->vsd_cnc1); + DPRINTK("VSD cnc2 %d\n", cfg->vsd_cnc2); + DPRINTK("VSD cnc3 %d\n", cfg->vsd_cnc3); + DPRINTK("latency_present %d\n", latency_present); + DPRINTK("I_latency_present %d\n", I_latency_present); + DPRINTK("hdmi_video_present %d\n", hdmi_video_present); + + } else { + index += blklen; + break; + } + + index += 9; + + /*latency present */ + if (latency_present) { + cfg->vsd_video_latency = edid[index++]; + cfg->vsd_audio_latency = edid[index++]; + + if (I_latency_present) { + cfg->vsd_I_video_latency = edid[index++]; + cfg->vsd_I_audio_latency = edid[index++]; + } else { + cfg->vsd_I_video_latency = cfg->vsd_video_latency; + cfg->vsd_I_audio_latency = cfg->vsd_audio_latency; + } + + DPRINTK("VSD latency video_latency %d\n", cfg->vsd_video_latency); + DPRINTK("VSD latency audio_latency %d\n", cfg->vsd_audio_latency); + DPRINTK("VSD latency I_video_latency %d\n", cfg->vsd_I_video_latency); + DPRINTK("VSD latency I_audio_latency %d\n", cfg->vsd_I_audio_latency); + } + + if (hdmi_video_present) { + hdmi_3d_present = edid[index] >> 7; + hdmi_3d_multi_present = (edid[index] & 0x60) >> 5; + index++; + hdmi_vic_len = (edid[index] & 0xe0) >> 5; + hdmi_3d_len = edid[index] & 0x1f; + index++; + + DPRINTK("hdmi_3d_present %d\n", hdmi_3d_present); + DPRINTK("hdmi_3d_multi_present %d\n", hdmi_3d_multi_present); + DPRINTK("hdmi_vic_len %d\n", hdmi_vic_len); + DPRINTK("hdmi_3d_len %d\n", hdmi_3d_len); + + if (hdmi_vic_len > 0) { + for (i = 0; i < hdmi_vic_len; i++) { + cfg->hdmi_vic[i] = edid[index++]; + DPRINTK("HDMI_vic=%d\n", cfg->hdmi_vic[i]); + } + } + + if (hdmi_3d_len > 0) { + if (hdmi_3d_present) { + if (hdmi_3d_multi_present == 0x1) { + cfg->hdmi_3d_struct_all = (edid[index] << 8) | edid[index+1]; + index_inc = 2; + } else if (hdmi_3d_multi_present == 0x2) { + cfg->hdmi_3d_struct_all = (edid[index] << 8) | edid[index+1]; + cfg->hdmi_3d_mask_all = (edid[index+2] << 8) | edid[index+3]; + index_inc = 4; + } else + index_inc = 0; + } + + DPRINTK("HDMI 3d struct all =0x%x\n", cfg->hdmi_3d_struct_all); + DPRINTK("HDMI 3d mask all =0x%x\n", cfg->hdmi_3d_mask_all); + + /* Read 2D vic 3D_struct */ + if ((hdmi_3d_len - index_inc) > 0) { + DPRINTK("Support 3D video format\n"); + i = 0; + while ((hdmi_3d_len - index_inc) > 0) { + + cfg->hdmi_3d_format[i].vic_order_2d = edid[index+index_inc] >> 4; + cfg->hdmi_3d_format[i].struct_3d = edid[index+index_inc] & 0x0f; + index_inc++; + + if (cfg->hdmi_3d_format[i].struct_3d == 8) { + cfg->hdmi_3d_format[i].detail_3d = edid[index+index_inc] >> 4; + index_inc++; + } else if (cfg->hdmi_3d_format[i].struct_3d > 8) { + cfg->hdmi_3d_format[i].detail_3d = 0; + index_inc++; + } + + DPRINTK("vic_order_2d=%d, 3d_struct=%d, 3d_detail=0x%x\n", + cfg->hdmi_3d_format[i].vic_order_2d, + cfg->hdmi_3d_format[i].struct_3d, + cfg->hdmi_3d_format[i].detail_3d); + i++; + } + } + index += index_inc; + } + } + + index = vsd_end; + + break; + } + case 0x1: /*Audio data block*/ + { + u8 audio_format, max_ch, byte1, byte2, byte3; + + i = 0; + cfg->max_channels = 0; + cfg->sample_rates = 0; + cfg->sample_sizes = 0; + + while (i < blklen) { + byte1 = edid[index + 1]; + byte2 = edid[index + 2]; + byte3 = edid[index + 3]; + index += 3; + i += 3; + + audio_format = byte1 >> 3; + max_ch = (byte1 & 0x07) + 1; + + DPRINTK("Audio Format Descriptor : %2d\n", audio_format); + DPRINTK("Max Number of Channels : %2d\n", max_ch); + DPRINTK("Sample Rates : %02x\n", byte2); + + /* ALSA can't specify specific compressed + * formats, so only care about PCM for now. */ + if (audio_format == AUDIO_CODING_TYPE_LPCM) { + if (max_ch > cfg->max_channels) + cfg->max_channels = max_ch; + + cfg->sample_rates |= byte2; + cfg->sample_sizes |= byte3 & 0x7; + DPRINTK("Sample Sizes : %02x\n", + byte3 & 0x7); + } + } + break; + } + case 0x4: /*Speaker allocation block*/ + { + i = 0; + while (i < blklen) { + cfg->speaker_alloc = edid[index + 1]; + index += 3; + i += 3; + DPRINTK("Speaker Alloc : %02x\n", cfg->speaker_alloc); + } + break; + } + case 0x7: /*User extended block*/ + default: + /* skip */ + DPRINTK("Not handle block, tagcode = 0x%x\n", tagcode); + index += blklen; + break; + } + + index++; + } + } + + /* long desc */ + DPRINTK("CEA long desc timmings\n"); + index = detail_timing_desc_offset; + block = edid + index; + while (index < (EDID_LENGTH - DETAILED_TIMING_DESCRIPTION_SIZE)) { + if (!(block[0] == 0x00 && block[1] == 0x00)) { + get_detailed_timing(block, &mode[num]); + num++; + } + block += DETAILED_TIMING_DESCRIPTION_SIZE; + index += DETAILED_TIMING_DESCRIPTION_SIZE; + } + + if (!num) { + kfree(mode); + return 0; + } + + m = kmalloc((num + specs->modedb_len) * + sizeof(struct fb_videomode), GFP_KERNEL); + if (!m) + return 0; + + if (specs->modedb_len) { + memmove(m, specs->modedb, + specs->modedb_len * sizeof(struct fb_videomode)); + kfree(specs->modedb); + } + memmove(m+specs->modedb_len, mode, + num * sizeof(struct fb_videomode)); + kfree(mode); + + specs->modedb_len += num; + specs->modedb = m; + + return 0; +} +EXPORT_SYMBOL(mxc_edid_parse_ext_blk); + +static int mxc_edid_readblk(struct i2c_adapter *adp, + unsigned short addr, unsigned char *edid) +{ + int ret = 0, extblknum = 0; + unsigned char regaddr = 0x0; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + + if (edid[1] == 0x00) + return -ENOENT; + + extblknum = edid[0x7E]; + + if (extblknum) { + regaddr = 128; + msg[1].buf = edid + EDID_LENGTH; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID ext block\n"); + return -EIO; + } + } + + return extblknum; +} + +static int mxc_edid_readsegblk(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, int seg_num) +{ + int ret = 0; + unsigned char segment = 0x1, regaddr = 0; + struct i2c_msg msg[3] = { + { + .addr = 0x30, + .flags = 0, + .len = 1, + .buf = &segment, + }, { + .addr = addr, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + + if (seg_num == 2) { + regaddr = 128; + msg[2].buf = edid + EDID_LENGTH; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + } + + return ret; +} + +int mxc_edid_var_to_vic(struct fb_var_screeninfo *var) +{ + int i; + struct fb_videomode m; + + for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) { + fb_var_to_videomode(&m, var); + if (mxc_edid_fb_mode_is_equal(false, &m, &mxc_cea_mode[i])) + break; + } + + if (i == ARRAY_SIZE(mxc_cea_mode)) + return 0; + + return i; +} + +EXPORT_SYMBOL(mxc_edid_var_to_vic); + +int mxc_edid_mode_to_vic(const struct fb_videomode *mode) +{ + int i; + bool use_aspect = (mode->vmode & FB_VMODE_ASPECT_MASK); + + for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) { + if (mxc_edid_fb_mode_is_equal(use_aspect, mode, &mxc_cea_mode[i])) + break; + } + + if (i == ARRAY_SIZE(mxc_cea_mode)) + return 0; + + return i; +} +EXPORT_SYMBOL(mxc_edid_mode_to_vic); + +/* make sure edid has 512 bytes*/ +int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi) +{ + int ret = 0, extblknum; + if (!adp || !edid || !cfg || !fbi) + return -EINVAL; + + memset(edid, 0, EDID_LENGTH*4); + memset(cfg, 0, sizeof(struct mxc_edid_cfg)); + + extblknum = mxc_edid_readblk(adp, addr, edid); + if (extblknum < 0) + return extblknum; + + /* edid first block parsing */ + memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); + fb_edid_to_monspecs(edid, &fbi->monspecs); + + if (extblknum) { + int i; + + /* need read segment block? */ + if (extblknum > 1) { + ret = mxc_edid_readsegblk(adp, addr, + edid + EDID_LENGTH*2, extblknum - 1); + if (ret < 0) + return ret; + } + + for (i = 1; i <= extblknum; i++) + /* edid ext block parsing */ + mxc_edid_parse_ext_blk(edid + i*EDID_LENGTH, + cfg, &fbi->monspecs); + } + + return 0; +} +EXPORT_SYMBOL(mxc_edid_read); + diff --git a/drivers/video/mxc/mxc_elcdif_fb.c b/drivers/video/mxc/mxc_elcdif_fb.c new file mode 100644 index 00000000..08ca1e07 --- /dev/null +++ b/drivers/video/mxc/mxc_elcdif_fb.c @@ -0,0 +1,1723 @@ +/* + * Copyright (C) 2010-2013 Freescale Semiconductor, Inc. + */ + +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +/* + * Based on drivers/video/mxc/mxc_ipuv3_fb.c, drivers/video/mxs/lcdif.c + * and arch/arm/mach-mx28/include/mach/lcdif.h. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/fsl_devices.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/mxcfb.h> +#include <linux/uaccess.h> + +#include <mach/hardware.h> + +#include "elcdif_regs.h" + +#define GDEBUG 10 +#include <linux/gallen_dbg.h> + +/* ELCDIF Pixel format definitions */ +/* Four-character-code (FOURCC) */ +#define fourcc(a, b, c, d) \ + (((__u32)(a)<<0)|((__u32)(b)<<8)|((__u32)(c)<<16)|((__u32)(d)<<24)) + +/* + * ELCDIF RGB Formats + */ +#define ELCDIF_PIX_FMT_RGB332 fourcc('R', 'G', 'B', '1') +#define ELCDIF_PIX_FMT_RGB555 fourcc('R', 'G', 'B', 'O') +#define ELCDIF_PIX_FMT_RGB565 fourcc('R', 'G', 'B', 'P') +#define ELCDIF_PIX_FMT_RGB666 fourcc('R', 'G', 'B', '6') +#define ELCDIF_PIX_FMT_BGR666 fourcc('B', 'G', 'R', '6') +#define ELCDIF_PIX_FMT_BGR24 fourcc('B', 'G', 'R', '3') +#define ELCDIF_PIX_FMT_RGB24 fourcc('R', 'G', 'B', '3') +#define ELCDIF_PIX_FMT_BGR32 fourcc('B', 'G', 'R', '4') +#define ELCDIF_PIX_FMT_BGRA32 fourcc('B', 'G', 'R', 'A') +#define ELCDIF_PIX_FMT_RGB32 fourcc('R', 'G', 'B', '4') +#define ELCDIF_PIX_FMT_RGBA32 fourcc('R', 'G', 'B', 'A') +#define ELCDIF_PIX_FMT_ABGR32 fourcc('A', 'B', 'G', 'R') + +struct mxc_elcdif_fb_data { + int cur_blank; + int next_blank; + int output_pix_fmt; + int dma_irq; + bool wait4vsync; + bool wait4framedone; + bool panning; + bool running; + struct completion vsync_complete; + struct completion frame_done_complete; + struct semaphore flip_sem; + struct fb_var_screeninfo var; + u32 pseudo_palette[16]; +}; + +struct elcdif_signal_cfg { + unsigned clk_pol:1; /* true = falling edge */ + unsigned enable_pol:1; /* true = active high */ + unsigned Hsync_pol:1; /* true = active high */ + unsigned Vsync_pol:1; /* true = active high */ +}; + +struct mxcfb_mode { + int dev_mode; + int num_modes; + struct fb_videomode *mode; +}; + +static int mxc_elcdif_fb_blank(int blank, struct fb_info *info); +static int mxc_elcdif_fb_map_video_memory(struct fb_info *info); +static int mxc_elcdif_fb_unmap_video_memory(struct fb_info *info); +static char *fb_mode; +static unsigned long default_bpp = 16; +static void __iomem *elcdif_base; +static struct device *g_elcdif_dev; +static bool g_elcdif_axi_clk_enable; +static bool g_elcdif_pix_clk_enable; +static struct clk *g_elcdif_axi_clk; +static struct clk *g_elcdif_pix_clk; +static struct mxcfb_mode mxc_disp_mode; + +static void dump_fb_videomode(struct fb_videomode *m) +{ + pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + m->refresh, m->xres, m->yres, m->pixclock, m->left_margin, + m->right_margin, m->upper_margin, m->lower_margin, + m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); +} + +static inline void setup_dotclk_panel(u32 pixel_clk, + u16 v_pulse_width, + u16 v_period, + u16 v_wait_cnt, + u16 v_active, + u16 h_pulse_width, + u16 h_period, + u16 h_wait_cnt, + u16 h_active, + int in_pixel_format, + int out_pixel_format, + struct elcdif_signal_cfg sig_cfg, + int enable_present) +{ + u32 val, rounded_pixel_clk; + u32 rounded_parent_clk; + struct clk *clk_parent; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + /* Init clocking */ + dev_dbg(g_elcdif_dev, "pixel clk = %d\n", pixel_clk); + + clk_parent = clk_get_parent(g_elcdif_pix_clk); + if (clk_parent == NULL) + dev_err(g_elcdif_dev, "%s Failed get clk parent\n", __func__); + + rounded_pixel_clk = pixel_clk * 2; + + rounded_parent_clk = clk_round_rate(clk_parent, + rounded_pixel_clk); + + while (rounded_pixel_clk < rounded_parent_clk) { + /* the max divider from parent to di is 8 */ + if (rounded_parent_clk / pixel_clk < 8) + rounded_pixel_clk += pixel_clk * 2; + } + + clk_set_rate(clk_parent, rounded_pixel_clk); + rounded_pixel_clk = + clk_round_rate(g_elcdif_pix_clk, pixel_clk); + clk_set_rate(g_elcdif_pix_clk, rounded_pixel_clk); + + msleep(5); + + __raw_writel(BM_ELCDIF_CTRL_DATA_SHIFT_DIR, + elcdif_base + HW_ELCDIF_CTRL_CLR); + + __raw_writel(BM_ELCDIF_CTRL_SHIFT_NUM_BITS, + elcdif_base + HW_ELCDIF_CTRL_CLR); + + __raw_writel(BM_ELCDIF_CTRL2_OUTSTANDING_REQS, + elcdif_base + HW_ELCDIF_CTRL2_CLR); + __raw_writel(BF_ELCDIF_CTRL2_OUTSTANDING_REQS + (BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_16), + elcdif_base + HW_ELCDIF_CTRL2_SET); + + /* Recover on underflow */ + __raw_writel(BM_ELCDIF_CTRL1_RECOVER_ON_UNDERFLOW, + elcdif_base + HW_ELCDIF_CTRL1_SET); + + /* Configure the input pixel format */ + __raw_writel(BM_ELCDIF_CTRL_WORD_LENGTH | + BM_ELCDIF_CTRL_INPUT_DATA_SWIZZLE | + BM_ELCDIF_CTRL_DATA_FORMAT_16_BIT | + BM_ELCDIF_CTRL_DATA_FORMAT_18_BIT | + BM_ELCDIF_CTRL_DATA_FORMAT_24_BIT, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL1_BYTE_PACKING_FORMAT, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + switch (in_pixel_format) { + case ELCDIF_PIX_FMT_RGB565: + __raw_writel(BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(0xF), + elcdif_base + HW_ELCDIF_CTRL1_SET); + __raw_writel(BF_ELCDIF_CTRL_WORD_LENGTH(0) | + BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(0), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + case ELCDIF_PIX_FMT_RGB24: + __raw_writel(BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(0xF), + elcdif_base + HW_ELCDIF_CTRL1_SET); + __raw_writel(BF_ELCDIF_CTRL_WORD_LENGTH(3) | + BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(0), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + case ELCDIF_PIX_FMT_RGB32: + __raw_writel(BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(0x7), + elcdif_base + HW_ELCDIF_CTRL1_SET); + __raw_writel(BF_ELCDIF_CTRL_WORD_LENGTH(3) | + BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(0), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + default: + dev_err(g_elcdif_dev, "ELCDIF unsupported input pixel format " + "%d\n", in_pixel_format); + break; + } + + /* Configure the output pixel format */ + __raw_writel(BM_ELCDIF_CTRL_LCD_DATABUS_WIDTH, + elcdif_base + HW_ELCDIF_CTRL_CLR); + switch (out_pixel_format) { + case ELCDIF_PIX_FMT_RGB565: + __raw_writel(BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(0), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + case ELCDIF_PIX_FMT_RGB666: + __raw_writel(BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(2), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + case ELCDIF_PIX_FMT_RGB24: + __raw_writel(BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(3), + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + default: + dev_err(g_elcdif_dev, "ELCDIF unsupported output pixel format " + "%d\n", out_pixel_format); + break; + } + + val = __raw_readl(elcdif_base + HW_ELCDIF_TRANSFER_COUNT); + val &= ~(BM_ELCDIF_TRANSFER_COUNT_V_COUNT | + BM_ELCDIF_TRANSFER_COUNT_H_COUNT); + val |= BF_ELCDIF_TRANSFER_COUNT_H_COUNT(h_active) | + BF_ELCDIF_TRANSFER_COUNT_V_COUNT(v_active); + __raw_writel(val, elcdif_base + HW_ELCDIF_TRANSFER_COUNT); + + __raw_writel(BM_ELCDIF_CTRL_VSYNC_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_WAIT_FOR_VSYNC_EDGE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_DVI_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_DOTCLK_MODE, + elcdif_base + HW_ELCDIF_CTRL_SET); + __raw_writel(BM_ELCDIF_CTRL_BYPASS_COUNT, + elcdif_base + HW_ELCDIF_CTRL_SET); + + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL0); + val &= ~(BM_ELCDIF_VDCTRL0_VSYNC_POL | + BM_ELCDIF_VDCTRL0_HSYNC_POL | + BM_ELCDIF_VDCTRL0_ENABLE_POL | + BM_ELCDIF_VDCTRL0_DOTCLK_POL); + if (sig_cfg.Vsync_pol) + val |= BM_ELCDIF_VDCTRL0_VSYNC_POL; + if (sig_cfg.Hsync_pol) + val |= BM_ELCDIF_VDCTRL0_HSYNC_POL; + if (sig_cfg.clk_pol) + val |= BM_ELCDIF_VDCTRL0_DOTCLK_POL; + if (sig_cfg.enable_pol) + val |= BM_ELCDIF_VDCTRL0_ENABLE_POL; + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL0); + + /* vsync is output */ + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL0); + val &= ~(BM_ELCDIF_VDCTRL0_VSYNC_OEB); + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL0); + + /* + * need enable sig for true RGB i/f. Or, if not true RGB, leave it + * zero. + */ + if (enable_present) { + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL0); + val |= BM_ELCDIF_VDCTRL0_ENABLE_PRESENT; + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL0); + } + + /* + * For DOTCLK mode, count VSYNC_PERIOD in terms of complete hz lines + */ + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL0); + val &= ~(BM_ELCDIF_VDCTRL0_VSYNC_PERIOD_UNIT | + BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT); + val |= BM_ELCDIF_VDCTRL0_VSYNC_PERIOD_UNIT | + BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT; + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL0); + + __raw_writel(BM_ELCDIF_VDCTRL0_VSYNC_PULSE_WIDTH, + elcdif_base + HW_ELCDIF_VDCTRL0_CLR); + __raw_writel(v_pulse_width, elcdif_base + HW_ELCDIF_VDCTRL0_SET); + + __raw_writel(BF_ELCDIF_VDCTRL1_VSYNC_PERIOD(v_period), + elcdif_base + HW_ELCDIF_VDCTRL1); + + __raw_writel(BF_ELCDIF_VDCTRL2_HSYNC_PULSE_WIDTH(h_pulse_width) | + BF_ELCDIF_VDCTRL2_HSYNC_PERIOD(h_period), + elcdif_base + HW_ELCDIF_VDCTRL2); + + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL4); + val &= ~BM_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT; + val |= BF_ELCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT(h_active); + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL4); + + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL3); + val &= ~(BM_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT | + BM_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT); + val |= BF_ELCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT(h_wait_cnt) | + BF_ELCDIF_VDCTRL3_VERTICAL_WAIT_CNT(v_wait_cnt); + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL3); + + val = __raw_readl(elcdif_base + HW_ELCDIF_VDCTRL4); + val |= BM_ELCDIF_VDCTRL4_SYNC_SIGNALS_ON; + __raw_writel(val, elcdif_base + HW_ELCDIF_VDCTRL4); + + return; +} + +static inline void release_dotclk_panel(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_DOTCLK_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(0, elcdif_base + HW_ELCDIF_VDCTRL0); + __raw_writel(0, elcdif_base + HW_ELCDIF_VDCTRL1); + __raw_writel(0, elcdif_base + HW_ELCDIF_VDCTRL2); + __raw_writel(0, elcdif_base + HW_ELCDIF_VDCTRL3); + + return; +} + +static inline void setup_dvi_panel(u16 h_active, u16 v_active, + u16 h_blanking, u16 v_lines, + u16 v1_blank_start, u16 v1_blank_end, + u16 v2_blank_start, u16 v2_blank_end, + u16 f1_start, u16 f1_end, + u16 f2_start, u16 f2_end) +{ + u32 val; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + /* 32bit packed format (RGB) */ + __raw_writel(BM_ELCDIF_CTRL1_BYTE_PACKING_FORMAT, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + __raw_writel(BF_ELCDIF_CTRL1_BYTE_PACKING_FORMAT(0x7) | + BM_ELCDIF_CTRL1_RECOVER_ON_UNDERFLOW, + elcdif_base + HW_ELCDIF_CTRL1_SET); + + val = __raw_readl(elcdif_base + HW_ELCDIF_TRANSFER_COUNT); + val &= ~(BM_ELCDIF_TRANSFER_COUNT_V_COUNT | + BM_ELCDIF_TRANSFER_COUNT_H_COUNT); + val |= BF_ELCDIF_TRANSFER_COUNT_H_COUNT(h_active) | + BF_ELCDIF_TRANSFER_COUNT_V_COUNT(v_active); + __raw_writel(val, elcdif_base + HW_ELCDIF_TRANSFER_COUNT); + + /* set elcdif to DVI mode */ + __raw_writel(BM_ELCDIF_CTRL_DVI_MODE, + elcdif_base + HW_ELCDIF_CTRL_SET); + __raw_writel(BM_ELCDIF_CTRL_VSYNC_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_DOTCLK_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + + __raw_writel(BM_ELCDIF_CTRL_BYPASS_COUNT, + elcdif_base + HW_ELCDIF_CTRL_SET); + /* convert input RGB -> YCbCr */ + __raw_writel(BM_ELCDIF_CTRL_RGB_TO_YCBCR422_CSC, + elcdif_base + HW_ELCDIF_CTRL_SET); + /* interlace odd and even fields */ + __raw_writel(BM_ELCDIF_CTRL1_INTERLACE_FIELDS, + elcdif_base + HW_ELCDIF_CTRL1_SET); + + __raw_writel(BM_ELCDIF_CTRL_WORD_LENGTH | + BM_ELCDIF_CTRL_INPUT_DATA_SWIZZLE | + BM_ELCDIF_CTRL_LCD_DATABUS_WIDTH, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BF_ELCDIF_CTRL_WORD_LENGTH(3) | /* 24 bit */ + BM_ELCDIF_CTRL_DATA_SELECT | /* data mode */ + BF_ELCDIF_CTRL_INPUT_DATA_SWIZZLE(0) | /* no swap */ + BF_ELCDIF_CTRL_LCD_DATABUS_WIDTH(1), /* 8 bit */ + elcdif_base + HW_ELCDIF_CTRL_SET); + + /* ELCDIF_DVI */ + /* set frame size */ + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL0); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL0); + + /* set start/end of field-1 and start of field-2 */ + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL1); + val &= ~(BM_ELCDIF_DVICTRL1_F1_START_LINE | + BM_ELCDIF_DVICTRL1_F1_END_LINE | + BM_ELCDIF_DVICTRL1_F2_START_LINE); + val |= BF_ELCDIF_DVICTRL1_F1_START_LINE(f1_start) | + BF_ELCDIF_DVICTRL1_F1_END_LINE(f1_end) | + BF_ELCDIF_DVICTRL1_F2_START_LINE(f2_start); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL1); + + /* set first vertical blanking interval and end of filed-2 */ + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL2); + val &= ~(BM_ELCDIF_DVICTRL2_F2_END_LINE | + BM_ELCDIF_DVICTRL2_V1_BLANK_START_LINE | + BM_ELCDIF_DVICTRL2_V1_BLANK_END_LINE); + val |= BF_ELCDIF_DVICTRL2_F2_END_LINE(f2_end) | + BF_ELCDIF_DVICTRL2_V1_BLANK_START_LINE(v1_blank_start) | + BF_ELCDIF_DVICTRL2_V1_BLANK_END_LINE(v1_blank_end); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL2); + + /* set second vertical blanking interval */ + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL3); + val &= ~(BM_ELCDIF_DVICTRL3_V2_BLANK_START_LINE | + BM_ELCDIF_DVICTRL3_V2_BLANK_END_LINE); + val |= BF_ELCDIF_DVICTRL3_V2_BLANK_START_LINE(v2_blank_start) | + BF_ELCDIF_DVICTRL3_V2_BLANK_END_LINE(v2_blank_end); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL3); + + /* fill the rest area black color if the input frame + * is not 720 pixels/line + */ + if (h_active != 720) { + /* the input frame can't be less then (720-256) pixels/line */ + if (720 - h_active > 0xff) + h_active = 720 - 0xff; + + val = __raw_readl(elcdif_base + HW_ELCDIF_DVICTRL4); + val &= ~(BM_ELCDIF_DVICTRL4_H_FILL_CNT | + BM_ELCDIF_DVICTRL4_Y_FILL_VALUE | + BM_ELCDIF_DVICTRL4_CB_FILL_VALUE | + BM_ELCDIF_DVICTRL4_CR_FILL_VALUE); + val |= BF_ELCDIF_DVICTRL4_H_FILL_CNT(720 - h_active) | + BF_ELCDIF_DVICTRL4_Y_FILL_VALUE(16) | + BF_ELCDIF_DVICTRL4_CB_FILL_VALUE(128) | + BF_ELCDIF_DVICTRL4_CR_FILL_VALUE(128); + __raw_writel(val, elcdif_base + HW_ELCDIF_DVICTRL4); + } + + /* Color Space Conversion RGB->YCbCr */ + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF0); + val &= ~(BM_ELCDIF_CSC_COEFF0_C0 | + BM_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER); + val |= BF_ELCDIF_CSC_COEFF0_C0(0x41) | + BF_ELCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER(3); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF0); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF1); + val &= ~(BM_ELCDIF_CSC_COEFF1_C1 | BM_ELCDIF_CSC_COEFF1_C2); + val |= BF_ELCDIF_CSC_COEFF1_C1(0x81) | + BF_ELCDIF_CSC_COEFF1_C2(0x19); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF1); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF2); + val &= ~(BM_ELCDIF_CSC_COEFF2_C3 | BM_ELCDIF_CSC_COEFF2_C4); + val |= BF_ELCDIF_CSC_COEFF2_C3(0x3DB) | + BF_ELCDIF_CSC_COEFF2_C4(0x3B6); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF2); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF3); + val &= ~(BM_ELCDIF_CSC_COEFF3_C5 | BM_ELCDIF_CSC_COEFF3_C6); + val |= BF_ELCDIF_CSC_COEFF3_C5(0x70) | + BF_ELCDIF_CSC_COEFF3_C6(0x70); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF3); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_COEFF4); + val &= ~(BM_ELCDIF_CSC_COEFF4_C7 | BM_ELCDIF_CSC_COEFF4_C8); + val |= BF_ELCDIF_CSC_COEFF4_C7(0x3A2) | + BF_ELCDIF_CSC_COEFF4_C8(0x3EE); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_COEFF4); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_OFFSET); + val &= ~(BM_ELCDIF_CSC_OFFSET_CBCR_OFFSET | + BM_ELCDIF_CSC_OFFSET_Y_OFFSET); + val |= BF_ELCDIF_CSC_OFFSET_CBCR_OFFSET(0x80) | + BF_ELCDIF_CSC_OFFSET_Y_OFFSET(0x10); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_OFFSET); + + val = __raw_readl(elcdif_base + HW_ELCDIF_CSC_LIMIT); + val &= ~(BM_ELCDIF_CSC_LIMIT_CBCR_MIN | + BM_ELCDIF_CSC_LIMIT_CBCR_MAX | + BM_ELCDIF_CSC_LIMIT_Y_MIN | + BM_ELCDIF_CSC_LIMIT_Y_MAX); + val |= BF_ELCDIF_CSC_LIMIT_CBCR_MIN(16) | + BF_ELCDIF_CSC_LIMIT_CBCR_MAX(240) | + BF_ELCDIF_CSC_LIMIT_Y_MIN(16) | + BF_ELCDIF_CSC_LIMIT_Y_MAX(235); + __raw_writel(val, elcdif_base + HW_ELCDIF_CSC_LIMIT); + + return; +} + +static inline void release_dvi_panel(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_DVI_MODE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + return; +} + +static inline void mxc_init_elcdif(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_CLKGATE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + /* Reset controller */ + __raw_writel(BM_ELCDIF_CTRL_SFTRST, + elcdif_base + HW_ELCDIF_CTRL_SET); + udelay(10); + + /* Take controller out of reset */ + __raw_writel(BM_ELCDIF_CTRL_SFTRST | BM_ELCDIF_CTRL_CLKGATE, + elcdif_base + HW_ELCDIF_CTRL_CLR); + + /* Setup the bus protocol */ + __raw_writel(BM_ELCDIF_CTRL1_MODE86, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + __raw_writel(BM_ELCDIF_CTRL1_BUSY_ENABLE, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + + /* Take display out of reset */ + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_SET); + + /* VSYNC is an input by default */ + __raw_writel(BM_ELCDIF_VDCTRL0_VSYNC_OEB, + elcdif_base + HW_ELCDIF_VDCTRL0_SET); + + /* Reset display */ + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + udelay(10); + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_SET); + udelay(10); + + return; +} + +void mxcfb_elcdif_register_mode(const struct fb_videomode *modedb, + int num_modes, int dev_mode) +{ + struct fb_videomode *mode; + + mode = kzalloc(num_modes * sizeof(struct fb_videomode), GFP_KERNEL); + + if (!mode) { + dev_err(g_elcdif_dev, "%s Failed to allocate mode data\n", __func__); + return; + } + + if (mxc_disp_mode.num_modes) + memcpy(mode, mxc_disp_mode.mode, + mxc_disp_mode.num_modes * sizeof(struct fb_videomode)); + if (modedb) + memcpy(mode + mxc_disp_mode.num_modes, modedb, + num_modes * sizeof(struct fb_videomode)); + + if (mxc_disp_mode.num_modes) + kfree(mxc_disp_mode.mode); + + mxc_disp_mode.mode = mode; + mxc_disp_mode.num_modes += num_modes; + mxc_disp_mode.dev_mode = dev_mode; + + return; +} +EXPORT_SYMBOL(mxcfb_elcdif_register_mode); + +int mxc_elcdif_frame_addr_setup(dma_addr_t phys) +{ + int ret = 0; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_ELCDIF_MASTER, + elcdif_base + HW_ELCDIF_CTRL_SET); + + __raw_writel(phys, elcdif_base + HW_ELCDIF_CUR_BUF); + __raw_writel(phys, elcdif_base + HW_ELCDIF_NEXT_BUF); + return ret; +} + +static inline void mxc_elcdif_dma_release(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_ELCDIF_MASTER, + elcdif_base + HW_ELCDIF_CTRL_CLR); + return; +} + +static inline void mxc_elcdif_run(struct mxc_elcdif_fb_data *data) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_ELCDIF_MASTER, + elcdif_base + HW_ELCDIF_CTRL_SET); + __raw_writel(BM_ELCDIF_CTRL_RUN, + elcdif_base + HW_ELCDIF_CTRL_SET); + + data->running = true; + + return; +} + +static inline void mxc_elcdif_stop(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + __raw_writel(BM_ELCDIF_CTRL_RUN, + elcdif_base + HW_ELCDIF_CTRL_CLR); + __raw_writel(BM_ELCDIF_CTRL_ELCDIF_MASTER, + elcdif_base + HW_ELCDIF_CTRL_CLR); + msleep(1); + __raw_writel(BM_ELCDIF_CTRL_CLKGATE, elcdif_base + HW_ELCDIF_CTRL_SET); + return; +} + +static int mxc_elcdif_blank_panel(int blank) +{ + int ret = 0, count; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + switch (blank) { + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + __raw_writel(BM_ELCDIF_CTRL_BYPASS_COUNT, + elcdif_base + HW_ELCDIF_CTRL_CLR); + for (count = 10000; count; count--) { + if (__raw_readl(elcdif_base + HW_ELCDIF_STAT) & + BM_ELCDIF_STAT_TXFIFO_EMPTY) + break; + msleep(1); + } + break; + + case FB_BLANK_UNBLANK: + __raw_writel(BM_ELCDIF_CTRL_BYPASS_COUNT, + elcdif_base + HW_ELCDIF_CTRL_SET); + break; + + default: + dev_err(g_elcdif_dev, "unknown blank parameter\n"); + ret = -EINVAL; + break; + } + return ret; +} + +static int mxc_elcdif_init_panel(void) +{ + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + /* + * Make sure we do a high-to-low transition to reset the panel. + * First make it low for 100 msec, hi for 10 msec, low for 10 msec, + * then hi. + */ + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_CLR); /* low */ + msleep(100); + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_SET); /* high */ + msleep(10); + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_CLR); /* low */ + + /* For the Samsung, Reset must be held low at least 30 uSec + * Therefore, we'll hold it low for about 10 mSec just to be sure. + * Then we'll wait 1 mSec afterwards. + */ + msleep(10); + __raw_writel(BM_ELCDIF_CTRL1_RESET, + elcdif_base + HW_ELCDIF_CTRL1_SET); /* high */ + msleep(1); + + return 0; +} + +static uint32_t bpp_to_pixfmt(struct fb_info *fbi) +{ + uint32_t pixfmt = 0; + + if (fbi->var.nonstd) + return fbi->var.nonstd; + + switch (fbi->var.bits_per_pixel) { + case 32: + pixfmt = ELCDIF_PIX_FMT_RGB32; + break; + case 24: + pixfmt = ELCDIF_PIX_FMT_RGB24; + break; + case 18: + pixfmt = ELCDIF_PIX_FMT_RGB666; + break; + case 16: + pixfmt = ELCDIF_PIX_FMT_RGB565; + break; + case 8: + pixfmt = ELCDIF_PIX_FMT_RGB332; + break; + } + return pixfmt; +} + +static int mxc_elcdif_fb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static irqreturn_t lcd_irq_handler(int irq, void *dev_id) +{ + struct mxc_elcdif_fb_data *data = dev_id; + u32 status_lcd = __raw_readl(elcdif_base + HW_ELCDIF_CTRL1); + dev_dbg(g_elcdif_dev, "%s: irq %d\n", __func__, irq); + + if ((status_lcd & BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ) && + data->wait4vsync) { + dev_dbg(g_elcdif_dev, "%s: VSYNC irq\n", __func__); + __raw_writel(BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ_EN, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + data->wait4vsync = 0; + complete(&data->vsync_complete); + } + if ((status_lcd & BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ) && + data->wait4framedone) { + dev_dbg(g_elcdif_dev, "%s: frame done irq\n", __func__); + __raw_writel(BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ_EN, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + if (data->panning) { + up(&data->flip_sem); + data->panning = 0; + } + data->wait4framedone = 0; + complete(&data->frame_done_complete); + } + if (status_lcd & BM_ELCDIF_CTRL1_UNDERFLOW_IRQ) { + dev_dbg(g_elcdif_dev, "%s: underflow irq\n", __func__); + __raw_writel(BM_ELCDIF_CTRL1_UNDERFLOW_IRQ, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + } + if (status_lcd & BM_ELCDIF_CTRL1_OVERFLOW_IRQ) { + dev_dbg(g_elcdif_dev, "%s: overflow irq\n", __func__); + __raw_writel(BM_ELCDIF_CTRL1_OVERFLOW_IRQ, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + } + return IRQ_HANDLED; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxc_elcdif_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + return ret; +} + +/** + This function compare the fb parameter see whether it was different + parameter for hardware, if it was different parameter, the hardware + will reinitialize. All will compared except x/y offset. + */ +static bool mxc_elcdif_fb_par_equal(struct fb_info *fbi, struct mxc_elcdif_fb_data *data) +{ + /* Here we set the xoffset, yoffset to zero, and compare two + * var see have different or not. */ + struct fb_var_screeninfo oldvar = data->var; + struct fb_var_screeninfo newvar = fbi->var; + + if ((fbi->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW && + fbi->var.activate & FB_ACTIVATE_FORCE) + return false; + + oldvar.xoffset = newvar.xoffset = 0; + oldvar.yoffset = newvar.yoffset = 0; + + return memcmp(&oldvar, &newvar, sizeof(struct fb_var_screeninfo)) == 0; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + * + */ +static int mxc_elcdif_fb_set_par(struct fb_info *fbi) +{ + struct mxc_elcdif_fb_data *data = (struct mxc_elcdif_fb_data *)fbi->par; + struct elcdif_signal_cfg sig_cfg; + int mem_len; + + GALLEN_DBGLOCAL_BEGIN(); + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + /* If parameter no change, don't reconfigure. */ + if (mxc_elcdif_fb_par_equal(fbi, data) && (data->running == true)) { + GALLEN_DBGLOCAL_ESC(); + return 0; + } + + sema_init(&data->flip_sem, 1); + + /* release prev panel */ + if (!g_elcdif_pix_clk_enable) { + GALLEN_DBGLOCAL_RUNLOG(0); + clk_enable(g_elcdif_pix_clk); + printk("[GALLEN DBG][SKIP %s-%d]\n",__FUNCTION__,__LINE__);GALLEN_DBGLOCAL_ESC();return -EIO; + g_elcdif_pix_clk_enable = true; + } + printk("[GALLEN DBG][SKIP %s-%d]\n",__FUNCTION__,__LINE__);GALLEN_DBGLOCAL_ESC();return -EIO; + mxc_elcdif_blank_panel(FB_BLANK_POWERDOWN); + printk("[GALLEN DBG][SKIP %s-%d]\n",__FUNCTION__,__LINE__);GALLEN_DBGLOCAL_ESC();return -EIO; + mxc_elcdif_stop(); + release_dotclk_panel(); + mxc_elcdif_dma_release(); + mxc_elcdif_fb_set_fix(fbi); + if (g_elcdif_pix_clk_enable) { + GALLEN_DBGLOCAL_RUNLOG(1); + clk_disable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = false; + } + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) { + GALLEN_DBGLOCAL_RUNLOG(2); + if (fbi->fix.smem_start) { + GALLEN_DBGLOCAL_RUNLOG(3); + mxc_elcdif_fb_unmap_video_memory(fbi); + } + + if (mxc_elcdif_fb_map_video_memory(fbi) < 0) { + GALLEN_DBGLOCAL_ESC(); + return -ENOMEM; + } + } + + if (data->next_blank != FB_BLANK_UNBLANK) { + GALLEN_DBGLOCAL_ESC(); + return 0; + } + + printk("[GALLEN DBG][SKIP %s-%d]\n",__FUNCTION__,__LINE__);return -EIO; // pass + /* init next panel */ + if (!g_elcdif_pix_clk_enable) { + GALLEN_DBGLOCAL_RUNLOG(4); + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + mxc_init_elcdif(); + mxc_elcdif_init_panel(); + + dev_dbg(fbi->device, "pixclock = %u Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) { + GALLEN_DBGLOCAL_RUNLOG(5); + sig_cfg.Hsync_pol = true; + } + + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) { + GALLEN_DBGLOCAL_RUNLOG(6); + sig_cfg.Vsync_pol = true; + } + + if (fbi->var.sync & FB_SYNC_CLK_LAT_FALL) { + GALLEN_DBGLOCAL_RUNLOG(6); + sig_cfg.clk_pol = true; + } + + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) { + GALLEN_DBGLOCAL_RUNLOG(7); + sig_cfg.enable_pol = true; + } + + printk("[GALLEN DBG][SKIP %s-%d]\n",__FUNCTION__,__LINE__);return -EIO; + + setup_dotclk_panel((PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.vsync_len, + fbi->var.upper_margin + fbi->var.yres + + fbi->var.lower_margin + fbi->var.vsync_len, + fbi->var.upper_margin + fbi->var.vsync_len, + fbi->var.yres, + fbi->var.hsync_len, + fbi->var.left_margin + fbi->var.xres + + fbi->var.right_margin + fbi->var.hsync_len, + fbi->var.left_margin + fbi->var.hsync_len, + fbi->var.xres, + bpp_to_pixfmt(fbi), + data->output_pix_fmt, + sig_cfg, + 1); + mxc_elcdif_frame_addr_setup(fbi->fix.smem_start); + mxc_elcdif_run(data); + mxc_elcdif_blank_panel(FB_BLANK_UNBLANK); + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + data->var = fbi->var; + + GALLEN_DBGLOCAL_END(); + return 0; +} + +static int mxc_elcdif_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + //GALLEN_DBGLOCAL_BEGIN(); + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8)) + var->bits_per_pixel = default_bpp; + + switch (var->bits_per_pixel) { + case 8: + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + //GALLEN_DBGLOCAL_END(); + return 0; +} + +static int mxc_elcdif_fb_wait_for_vsync(struct fb_info *info) +{ + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + int ret = 0; + + if (data->cur_blank != FB_BLANK_UNBLANK) { + dev_err(info->device, "can't wait for VSYNC when fb " + "is blank\n"); + return -EINVAL; + } + + init_completion(&data->vsync_complete); + + __raw_writel(BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + data->wait4vsync = 1; + __raw_writel(BM_ELCDIF_CTRL1_VSYNC_EDGE_IRQ_EN, + elcdif_base + HW_ELCDIF_CTRL1_SET); + ret = wait_for_completion_interruptible_timeout( + &data->vsync_complete, 1 * HZ); + if (ret == 0) { + dev_err(info->device, + "MXC ELCDIF wait for vsync timeout\n"); + data->wait4vsync = 0; + ret = -ETIME; + } else if (ret > 0) { + ret = 0; + } + return ret; +} + +static int mxc_elcdif_fb_wait_for_frame_done(struct fb_info *info) +{ + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + int ret = 0; + + if (data->cur_blank != FB_BLANK_UNBLANK) { + dev_err(info->device, "can't wait for frame done when fb " + "is blank\n"); + return -EINVAL; + } + + init_completion(&data->frame_done_complete); + + __raw_writel(BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ, + elcdif_base + HW_ELCDIF_CTRL1_CLR); + data->wait4framedone = 1; + __raw_writel(BM_ELCDIF_CTRL1_CUR_FRAME_DONE_IRQ_EN, + elcdif_base + HW_ELCDIF_CTRL1_SET); + ret = wait_for_completion_interruptible_timeout( + &data->frame_done_complete, 1 * HZ); + if (ret == 0) { + dev_err(info->device, + "MXC ELCDIF wait for frame done timeout\n"); + data->wait4framedone = 0; + ret = -ETIME; + } else if (ret > 0) { + ret = 0; + } + return ret; +} + +static int mxc_elcdif_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + int ret = -EINVAL; + + switch (cmd) { + case MXCFB_WAIT_FOR_VSYNC: + ret = mxc_elcdif_fb_wait_for_vsync(info); + break; + case MXCFB_GET_FB_BLANK: + { + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + + if (put_user(data->cur_blank, (__u32 __user *)arg)) + return -EFAULT; + break; + } + default: + break; + } + return ret; +} + +static int mxc_elcdif_fb_blank(int blank, struct fb_info *info) +{ + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + int ret = 0; + + if (data->cur_blank == blank) + return ret; + + data->next_blank = blank; + + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + ret = mxc_elcdif_blank_panel(blank); + if (ret == 0) + data->cur_blank = blank; + else + return ret; + + if (blank == FB_BLANK_UNBLANK) { + info->var.activate = (info->var.activate & ~FB_ACTIVATE_MASK) | + FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + ret = mxc_elcdif_fb_set_par(info); + if (ret) + return ret; + } + + if (data->cur_blank != FB_BLANK_UNBLANK) { + if (g_elcdif_axi_clk_enable) { + clk_disable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = false; + } + if (g_elcdif_pix_clk_enable) { + clk_disable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = false; + } + } else { + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + } + + return ret; +} + +static int mxc_elcdif_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_elcdif_fb_data *data = + (struct mxc_elcdif_fb_data *)info->par; + unsigned long base = 0; + + if (data->cur_blank != FB_BLANK_UNBLANK) { + dev_err(info->device, "can't do pan display when fb " + "is blank\n"); + return -EINVAL; + } + + if (var->xoffset > 0) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((var->yoffset + var->yres > var->yres_virtual)) { + dev_err(info->device, "y panning exceeds\n"); + return -EINVAL; + } + + /* update framebuffer visual */ + base = (var->yoffset * var->xres_virtual + var->xoffset); + base = (var->bits_per_pixel) * base / 8; + base += info->fix.smem_start; + + if (down_timeout(&data->flip_sem, HZ / 2)) { + dev_err(info->device, "timeout when waiting for flip irq\n"); + return -ETIMEDOUT; + } + + __raw_writel(base, elcdif_base + HW_ELCDIF_NEXT_BUF); + + data->panning = 1; + return mxc_elcdif_fb_wait_for_frame_done(info); +} + +static struct fb_ops mxc_elcdif_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxc_elcdif_fb_check_var, + .fb_set_par = mxc_elcdif_fb_set_par, + .fb_setcolreg = mxc_elcdif_fb_setcolreg, + .fb_ioctl = mxc_elcdif_fb_ioctl, + .fb_blank = mxc_elcdif_fb_blank, + .fb_pan_display = mxc_elcdif_fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxc_elcdif_fb_map_video_memory(struct fb_info *fbi) +{ + if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + + fbi->screen_base = dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *)&fbi->fix.smem_start, + GFP_DMA); + if (fbi->screen_base == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + return -EBUSY; + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxc_elcdif_fb_unmap_video_memory(struct fb_info *fbi) +{ + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +static ssize_t show_disp_dev(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (dev) { + char *panel_type = dev->platform_data; + + if (panel_type) + return sprintf(buf, "%s", panel_type); + else + return sprintf(buf, "elcd default"); + } else { + dev_err(dev, "%s[%s], none dev\n", __FILE__, __func__); + return 0; + } +} +static DEVICE_ATTR(fsl_disp_dev_property, S_IRUGO, show_disp_dev, NULL); + +static int mxc_elcdif_fb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_elcdif_fb_data *data; + struct resource *res; + struct fb_info *fbi; + struct mxc_fb_platform_data *pdata = pdev->dev.platform_data; + const struct fb_videomode *mode; + struct fb_videomode m; + int num; + + GALLEN_DBGLOCAL_BEGIN(); + + fbi = framebuffer_alloc(sizeof(struct mxc_elcdif_fb_data), &pdev->dev); + if (fbi == NULL) { + ret = -ENOMEM; + goto out; + } + + data = (struct mxc_elcdif_fb_data *)fbi->par; + data->cur_blank = data->next_blank = FB_BLANK_UNBLANK; + + fbi->var.activate = FB_ACTIVATE_NOW; + fbi->fbops = &mxc_elcdif_fb_ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = data->pseudo_palette; + + ret = fb_alloc_cmap(&fbi->cmap, 16, 0); + if (ret) + goto out; + + g_elcdif_dev = &pdev->dev; + + { + g_elcdif_pix_clk = clk_get(g_elcdif_dev, "elcdif_pix"); + clk_set_rate(g_elcdif_pix_clk, 25000000); + clk_enable(g_elcdif_pix_clk); + printk("[GALLEN DBG][SKIP %s-%d]\n",__FUNCTION__,__LINE__); + goto err0; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto err0; + } + data->dma_irq = res->start; + + ret = request_irq(data->dma_irq, lcd_irq_handler, 0, + "mxc_elcdif_fb", data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + data->dma_irq, ret); + goto err0; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto err1; + } + elcdif_base = ioremap(res->start, SZ_4K); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + fbi->fix.smem_len = res->end - res->start + 1; + fbi->fix.smem_start = res->start; + fbi->screen_base = ioremap(fbi->fix.smem_start, + fbi->fix.smem_len); + } + + strcpy(fbi->fix.id, "mxc_elcdif_fb"); + + fbi->var.xres = 800; + fbi->var.yres = 480; + + if (pdata && !data->output_pix_fmt) + data->output_pix_fmt = pdata->interface_pix_fmt; + + INIT_LIST_HEAD(&fbi->modelist); + + if (pdata && pdata->mode && pdata->num_modes) + fb_videomode_to_modelist(pdata->mode, pdata->num_modes, + &fbi->modelist); + + if (mxc_disp_mode.num_modes) { + int i; + mode = mxc_disp_mode.mode; + num = mxc_disp_mode.num_modes; + + for (i = 0; i < num; i++) { + /* + * FIXME now we do not support interlaced + * mode for ddc mode + */ + if ((mxc_disp_mode.dev_mode + & MXC_DISP_DDC_DEV) && + (mode[i].vmode & FB_VMODE_INTERLACED)) + continue; + else { + dev_dbg(&pdev->dev, "Added mode %d:", i); + dev_dbg(&pdev->dev, + "xres = %d, yres = %d, freq = %d, vmode = %d, flag = %d\n", + mode[i].xres, mode[i].yres, mode[i].refresh, mode[i].vmode, + mode[i].flag); + fb_add_videomode(&mode[i], &fbi->modelist); + } + } + } + + if (!fb_mode && pdata && pdata->mode_str) + fb_mode = pdata->mode_str; + + if (fb_mode) { + dev_dbg(&pdev->dev, "default video mode %s\n", fb_mode); + ret = fb_find_mode(&fbi->var, fbi, fb_mode, NULL, 0, NULL, + default_bpp); + if ((ret == 1) || (ret == 2)) { + fb_var_to_videomode(&m, &fbi->var); + dump_fb_videomode(&m); + mode = fb_find_nearest_mode(&m, + &fbi->modelist); + fb_videomode_to_var(&fbi->var, mode); + } else if (pdata && pdata->mode && pdata->num_modes) { + ret = fb_find_mode(&fbi->var, fbi, fb_mode, pdata->mode, + pdata->num_modes, NULL, default_bpp); + if (!ret) { + dev_err(fbi->device, + "No valid video mode found"); + goto err2; + } + } else { + dev_err(fbi->device, + "No valid video mode found"); + goto err2; + } + } + + mxc_elcdif_fb_check_var(&fbi->var, fbi); + + fbi->var.xres_virtual = fbi->var.xres; + fbi->var.yres_virtual = fbi->var.yres * 3; + + mxc_elcdif_fb_set_fix(fbi); + + if (!res || !res->end) + if (mxc_elcdif_fb_map_video_memory(fbi) < 0) { + ret = -ENOMEM; + goto err2; + } + + g_elcdif_axi_clk = clk_get(g_elcdif_dev, "elcdif_axi"); + if (g_elcdif_axi_clk == NULL) { + dev_err(&pdev->dev, "can't get ELCDIF axi clk\n"); + ret = -ENODEV; + goto err3; + } + g_elcdif_pix_clk = clk_get(g_elcdif_dev, "elcdif_pix"); + if (g_elcdif_pix_clk == NULL) { + dev_err(&pdev->dev, "can't get ELCDIF pix clk\n"); + ret = -ENODEV; + goto err3; + } + /* + * Set an appropriate pixel clk rate first, so that we can + * access ELCDIF registers. + */ + clk_set_rate(g_elcdif_pix_clk, 25000000); + { + clk_enable(g_elcdif_pix_clk); + printk("skip elcdif ...\n"); + goto err3; + } + + + fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + fbi->flags |= FBINFO_MISC_USEREVENT; + ret = fb_set_var(fbi, &fbi->var); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + + + if (data->cur_blank == FB_BLANK_UNBLANK) { + console_lock(); + fb_blank(fbi, FB_BLANK_UNBLANK); + console_unlock(); + } + + ret = register_framebuffer(fbi); + if (ret) + goto err3; + + + fbi->dev->platform_data = pdata->panel_type; + + ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_dev_property); + if (ret) + dev_err(&pdev->dev, "Error %d on creating file for disp " + " device property\n", ret); + + GALLEN_DBGLOCAL_ESC(); + + return 0; +err3: + mxc_elcdif_fb_unmap_video_memory(fbi); +err2: + iounmap(elcdif_base); +err1: + free_irq(data->dma_irq, data); +err0: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); +out: + + GALLEN_DBGLOCAL_END(); + return ret; +} + +static int mxc_elcdif_fb_remove(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxc_elcdif_fb_data *data = (struct mxc_elcdif_fb_data *)fbi->par; + + mxc_elcdif_fb_blank(FB_BLANK_POWERDOWN, fbi); + mxc_elcdif_stop(); + release_dotclk_panel(); + mxc_elcdif_dma_release(); + + if (g_elcdif_axi_clk_enable) { + clk_disable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = false; + } + if (g_elcdif_pix_clk_enable) { + clk_disable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = false; + } + clk_put(g_elcdif_axi_clk); + clk_put(g_elcdif_pix_clk); + + free_irq(data->dma_irq, data); + mxc_elcdif_fb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +#if 0 +//#ifdef CONFIG_PM +static int mxc_elcdif_fb_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxc_elcdif_fb_data *data = (struct mxc_elcdif_fb_data *)fbi->par; + int saved_blank; + + GALLEN_DBGLOCAL_BEGIN(); + + console_lock(); + fb_set_suspend(fbi, 1); + saved_blank = data->cur_blank; + mxc_elcdif_fb_blank(FB_BLANK_POWERDOWN, fbi); + data->next_blank = saved_blank; + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + mxc_elcdif_stop(); + mxc_elcdif_dma_release(); + if (g_elcdif_pix_clk_enable) { + clk_disable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = false; + } + if (g_elcdif_axi_clk_enable) { + clk_disable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = false; + } + data->running = false; + console_unlock(); + + GALLEN_DBGLOCAL_END(); + + return 0; +} + +static int mxc_elcdif_fb_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxc_elcdif_fb_data *data = (struct mxc_elcdif_fb_data *)fbi->par; + + GALLEN_DBGLOCAL_BEGIN(); + + console_lock(); + mxc_elcdif_fb_blank(data->next_blank, fbi); + fb_set_suspend(fbi, 0); + console_unlock(); + + GALLEN_DBGLOCAL_END(); + return 0; +} +#else +#define mxc_elcdif_fb_suspend NULL +#define mxc_elcdif_fb_resume NULL +#endif + +static struct platform_driver mxc_elcdif_fb_driver = { + .probe = mxc_elcdif_fb_probe, + .remove = mxc_elcdif_fb_remove, + .suspend = mxc_elcdif_fb_suspend, + .resume = mxc_elcdif_fb_resume, + .driver = { + .name = "mxc_elcdif_fb", + .owner = THIS_MODULE, + }, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=trident:800x600,bpp=16,noaccel + */ +int mxc_elcdif_fb_setup(char *options) +{ + char *opt; + if (!options || !*options) + return 0; + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + return 0; +} + +static int __init mxc_elcdif_fb_init(void) +{ + char *option = NULL; + + if (fb_get_options("mxc_elcdif_fb", &option)) + return -ENODEV; + mxc_elcdif_fb_setup(option); + + return platform_driver_register(&mxc_elcdif_fb_driver); +} + +static void __exit mxc_elcdif_fb_exit(void) +{ + platform_driver_unregister(&mxc_elcdif_fb_driver); +} + +module_init(mxc_elcdif_fb_init); +module_exit(mxc_elcdif_fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC ELCDIF Framebuffer Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxc_epdc_eink_module.c b/drivers/video/mxc/mxc_epdc_eink_module.c new file mode 100644 index 00000000..1366a600 --- /dev/null +++ b/drivers/video/mxc/mxc_epdc_eink_module.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL FREESCALE SEMICONDUCTOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/module.h> +#include <linux/version.h> +#include "eink_processing2.h" + +/*#if (LINUX_VERSION_CODE == KERNEL_VERSION(2,6,31))*/ + +EXPORT_SYMBOL(do_aa_processing_v2_2_1); +EXPORT_SYMBOL(do_aa_processing_v2_2_0); +EXPORT_SYMBOL(do_aad_processing_v2_1_1); +EXPORT_SYMBOL(do_aad_processing_v2_1_0); +EXPORT_SYMBOL(set_aad_update_counter); +EXPORT_SYMBOL(mxc_epdc_fb_prep_algorithm_data); +EXPORT_SYMBOL(mxc_epdc_fb_fetch_vc_data); +EXPORT_SYMBOL(mxc_epdc_fb_fetch_wxi_data); +EXPORT_SYMBOL(mxc_epdc_fb_aa_info); +/*#endif*/ + +MODULE_LICENSE("Proprietary"); diff --git a/drivers/video/mxc/mxc_epdc_fake_s1d13522.c b/drivers/video/mxc/mxc_epdc_fake_s1d13522.c new file mode 100644 index 00000000..1b0fac3e --- /dev/null +++ b/drivers/video/mxc/mxc_epdc_fake_s1d13522.c @@ -0,0 +1,1123 @@ + +// this file should be included by epdc driver from manufacturer . + + +#include "fake_s1d13522.h" +#include "lk_tps65185.h" +#include "lk_fp9928.h" +#include <linux/completion.h> + + +#ifdef CONFIG_MACH_MX6SL_NTX//[ + #define FAKE_S1D13522_PLATFORM_MX6 1 +#endif //]CONFIG_MACH_MX6SL_NTX +//#define LM75_ENABLED 1 + + +#ifdef LM75_ENABLED//[ +#include "lk_lm75.h" +#endif //]LM75_ENABLED + + +//#define SYSFS_DBG 1 +//#define DITHER_ENABLE 1 +#define FW_IN_RAM 1 + +#define WF_INIT 0 +#define WF_DU 1 +#define WF_GC16 2 +#define WF_GC4 3 +// +static EPDFB_DC *gptDC; + +// global mxc update data .... +static struct mxcfb_update_data g_mxc_upd_data; +#ifdef FAKE_S1D13522_PLATFORM_MX6//[ +static struct mxcfb_update_marker_data g_mxc_upd_marker_data; +#endif //]FAKE_S1D13522_PLATFORM_MX6 + +extern int check_hardware_name(void); + + + +#include "ntx_hwconfig.h" +extern volatile NTX_HWCONFIG *gptHWCFG; + +static int giLastTemprature = DEFAULT_TEMP; +static volatile unsigned long gdwLastUpdateJiffies = 0; +static int giIsInited = 0; +DECLARE_COMPLETION(mxc_epdc_fake13522_inited); + + +static char gcFB_snapshot_pathA[512+2]; +static int giFB_snapshot_enable; +#ifdef OUTPUT_SNAPSHOT_IMGFILE//[ +static int giFB_snapshot_total=OUTPUT_SNAPSHOT_IMGFILE; +#else //]OUTPUT_SNAPSHOT_IMGFILE +static int giFB_snapshot_total=3; +#endif//]OUTPUT_SNAPSHOT_IMGFILE +static int giFB_snapshot_repeat; +#ifdef DITHER_ENABLE//[ +static int giDither_enable; +#endif //]DITHER_ENABLE + + + +static void epdc_powerup(struct mxc_epdc_fb_data *fb_data); + + +// +// private help functions prototype ... +// + + +static ssize_t waveform_mode_ver_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t waveform_mode_ver_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +static ssize_t waveform_rev_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t waveform_rev_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + + +static ssize_t temperature_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t temperature_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +static ssize_t waveform_mode_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t waveform_mode_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +#ifdef SYSFS_DBG//[ +static ssize_t dbg_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t dbg_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +#endif //]SYSFS_DBG +static ssize_t fbsnapshot_path_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t fbsnapshot_path_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t fbsnapshot_enable_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t fbsnapshot_enable_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +static ssize_t vcom_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t vcom_write(struct device *dev, struct device_attribute *attr,const char *buf, size_t count); + +static ssize_t xwi_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t xwi_write(struct device *dev, struct device_attribute *attr,const char *buf, size_t count); +#ifdef DITHER_ENABLE//[ +static ssize_t dither_enable_read(struct device *dev, struct device_attribute *attr,char *buf); +static ssize_t dither_enable_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); +#endif //]DITHER_ENABLE + +static DEVICE_ATTR(waveform_mode_ver, 0666, waveform_mode_ver_read, waveform_mode_ver_write); +static DEVICE_ATTR(waveform_rev, 0666, waveform_rev_read, waveform_rev_write); +static DEVICE_ATTR(temperature, 0666, temperature_read, temperature_write); +static DEVICE_ATTR(waveform_mode_gc16, 0666, waveform_mode_read, waveform_mode_write); +static DEVICE_ATTR(waveform_mode_du, 0666, waveform_mode_read, waveform_mode_write); +static DEVICE_ATTR(waveform_mode_gl16, 0666, waveform_mode_read, waveform_mode_write); +static DEVICE_ATTR(waveform_mode_reagl, 0666, waveform_mode_read, waveform_mode_write); +static DEVICE_ATTR(waveform_mode_reagld, 0666, waveform_mode_read, waveform_mode_write); +static DEVICE_ATTR(waveform_mode_a2, 0666, waveform_mode_read, waveform_mode_write); +static DEVICE_ATTR(fbsnapshot_path, 0666, fbsnapshot_path_read, fbsnapshot_path_write); +static DEVICE_ATTR(fbsnapshot_enable, 0666, fbsnapshot_enable_read, fbsnapshot_enable_write); +static DEVICE_ATTR(vcom, 0666, vcom_read, vcom_write); +static DEVICE_ATTR(xwi, 0666, xwi_read, xwi_write); +#ifdef SYSFS_DBG//[ +static DEVICE_ATTR(dbg, 0666, dbg_read, dbg_write); +#endif //]SYSFS_DBG +#ifdef DITHER_ENABLE//[ +static DEVICE_ATTR(dither_enable, 0666, dither_enable_read, dither_enable_write); +#endif //]DITHER_ENABLE + + +static ssize_t xwi_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + if(g_fb_data&&g_fb_data->waveform_xwi_string) + { + sprintf(buf,"%s\n",g_fb_data->waveform_xwi_string); + return strlen(buf); + } + else { + return 0; + } +} +static ssize_t xwi_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} +static ssize_t vcom_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + int iVCOM_mV; + int iChk; + + if(8==gptHWCFG->m_val.bDisplayCtrl) { + iChk = fp9928_vcom_get_cached(&iVCOM_mV); + if(iChk>=0) { + sprintf (buf,"%dmV\n",iVCOM_mV); + } + } + else if(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl) { + iChk = tps65185_vcom_get_cached(&iVCOM_mV); + if(iChk>=0) { + sprintf (buf,"%dmV\n",iVCOM_mV); + } + } + else { + } + return strlen(buf); +} + +static ssize_t vcom_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int iVCOM_mV; + int iChk; + + sscanf(buf,"%dmV",&iVCOM_mV); + //printk("%s():user input %dmV\n",__FUNCTION__,iVCOM_mV); + + if(iVCOM_mV<0) { + if(8==gptHWCFG->m_val.bDisplayCtrl) { + iChk = fp9928_vcom_set(iVCOM_mV,0); + if(iChk<0) { + printk(KERN_ERR"FP9928 VCOM %dmV write failed !\n",iVCOM_mV); + } + } + else if(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl) { + iChk = tps65185_vcom_set(iVCOM_mV,0); + if(iChk<0) { + printk(KERN_ERR"TPS65185 VCOM %dmV write failed !\n",iVCOM_mV); + } + } + else { + } + } + return strlen(buf); +} + +#ifdef SYSFS_DBG//[ +static char gcDbgStringA[128]; +static ssize_t dbg_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + sprintf (buf,"%s\n",gcDbgStringA); + //printk(KERN_ALERT"%s():%s\n",__FUNCTION__,gcDbgStringA); + return strlen(buf); +} +static ssize_t dbg_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + sscanf(buf,"%s\n",gcDbgStringA); + //strcpy(gcFB_snapshot_pathA,buf); + printk(KERN_ALERT"%s():%s\n",__FUNCTION__,gcDbgStringA); + return strlen(buf); +} +#endif //]SYSFS_DBG +static ssize_t fbsnapshot_path_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + + sprintf (buf,"%s\n",gcFB_snapshot_pathA); + return strlen(buf); +} + +static ssize_t fbsnapshot_path_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + sscanf(buf,"%s\n",gcFB_snapshot_pathA); + //strcpy(gcFB_snapshot_pathA,buf); + return strlen(buf); +} +static ssize_t fbsnapshot_enable_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + sprintf (buf,"%d %d %d\n",giFB_snapshot_enable,giFB_snapshot_total,giFB_snapshot_repeat); + return strlen(buf); +} +static ssize_t fbsnapshot_enable_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int iEnable=-1,iCapTotal=-1,iRepeat=-1; + sscanf(buf,"%d %d %d\n",&iEnable,&iCapTotal,&iRepeat); + switch(iEnable) { + case 0: + case 1: + case 2: + giFB_snapshot_enable=iEnable; + if(-1!=iRepeat) { + giFB_snapshot_repeat=iRepeat; + } + if(iCapTotal>0) { + giFB_snapshot_total=iCapTotal; + } + break; + default: + break; + } + return strlen(buf); +} + +#ifdef DITHER_ENABLE//[ +static ssize_t dither_enable_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + sprintf (buf,"%d\n",giDither_enable); + return strlen(buf); +} + +static ssize_t dither_enable_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int iEnable; + sscanf(buf,"%d\n",&iEnable); + switch(iEnable) { + case 0: + case 1: + case 2: + case 3: + giDither_enable=iEnable; + break; + default: + break; + } + return strlen(buf); +} + +static int dither_enable_state(void) +{ + return giDither_enable; +} +#else //][!DITHER_ENABLE +static int dither_enable_state(void) +{ + return 0; +} +#endif //]DITHER_ENABLE + +static ssize_t waveform_mode_ver_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + sprintf (buf,"0x%x\n",gbModeVersion); + return strlen(buf); +} + +static ssize_t waveform_mode_ver_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + DBG_MSG("%s()\n",__FUNCTION__); + return count; +} + +static ssize_t waveform_rev_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + sprintf (buf,"0x%x\n",gbWFM_REV); + return strlen(buf); +} + +static ssize_t waveform_rev_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + DBG_MSG("%s()\n",__FUNCTION__); + return count; +} + +static ssize_t temperature_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + sprintf (buf,"%d\n",giLastTemprature); + return strlen(buf); +} + +static ssize_t temperature_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int iChk,iLastTemprature; + + iLastTemprature = simple_strtol(buf,NULL,0); + iChk = mxc_epdc_fb_set_temperature(iLastTemprature,&g_fb_data->info); + if(0==iChk) { + DBG_MSG("%s(),temp <== %d\n",__FUNCTION__,iLastTemprature); + giLastTemprature = iLastTemprature; + gdwLastUpdateJiffies = jiffies+(60*HZ); + } + else { + ERR_MSG("%s(),temp <== %d fail!\n",__FUNCTION__,iLastTemprature); + } + + return count; +} + +static ssize_t waveform_mode_read(struct device *dev, struct device_attribute *attr,char *buf) +{ + int iWaveform_mode=-1,iWaveform_mode_eink=-1; + + if(attr==&dev_attr_waveform_mode_du.attr) { + iWaveform_mode_eink = giNTX_waveform_modeA[NTX_WFM_MODE_DU]; + iWaveform_mode = NTX_WFM_MODE_DU; + } + else + if(attr==&dev_attr_waveform_mode_a2.attr) { + iWaveform_mode_eink = giNTX_waveform_modeA[NTX_WFM_MODE_A2]; + iWaveform_mode = NTX_WFM_MODE_A2; + } + else + if(attr==&dev_attr_waveform_mode_gc16.attr) { + iWaveform_mode_eink = giNTX_waveform_modeA[NTX_WFM_MODE_GC16]; + iWaveform_mode = NTX_WFM_MODE_GC16; + } + else + if(attr==&dev_attr_waveform_mode_gl16.attr) { + iWaveform_mode_eink = giNTX_waveform_modeA[NTX_WFM_MODE_GL16]; + iWaveform_mode = NTX_WFM_MODE_GL16; + } + else + if(attr==&dev_attr_waveform_mode_reagl.attr) { + iWaveform_mode_eink = giNTX_waveform_modeA[NTX_WFM_MODE_GLR16]; + iWaveform_mode = NTX_WFM_MODE_GLR16; + } + else + if(attr==&dev_attr_waveform_mode_reagld.attr) { + iWaveform_mode_eink = giNTX_waveform_modeA[NTX_WFM_MODE_GLD16]; + iWaveform_mode = NTX_WFM_MODE_GLD16; + } + + sprintf (buf,"%d:%d\n",iWaveform_mode,iWaveform_mode_eink); + return strlen(buf); +} + +static ssize_t waveform_mode_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + DBG_MSG("%s() not supported!\n",__FUNCTION__); + return count; +} + +static const struct attribute *sysfs_epdc_attrs[] = { + &dev_attr_waveform_mode_ver.attr, + &dev_attr_waveform_rev.attr, + &dev_attr_temperature.attr, + &dev_attr_waveform_mode_du.attr, + &dev_attr_waveform_mode_a2.attr, + &dev_attr_waveform_mode_gc16.attr, + &dev_attr_waveform_mode_gl16.attr, + &dev_attr_waveform_mode_reagl.attr, + &dev_attr_waveform_mode_reagld.attr, +#ifdef OUTPUT_SNAPSHOT_IMGFILE//[ + &dev_attr_fbsnapshot_path.attr, + &dev_attr_fbsnapshot_enable.attr, +#endif //]OUTPUT_SNAPSHOT_IMGFILE + &dev_attr_vcom.attr, + &dev_attr_xwi.attr, +#ifdef SYSFS_DBG//[ + &dev_attr_dbg.attr, +#endif //]SYSFS_DBG +#ifdef DITHER_ENABLE//[ + &dev_attr_dither_enable.attr, +#endif //]DITHER_ENABLE + NULL, +}; + + +////////////////////////////////////////////////////// +// +// driver extention helper functions ... +// +int mxc_epdc_fb_check_update_complete(u32 update_marker, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + unsigned long flags; + bool marker_found = false; + int ret = 0; + + //GALLEN_DBGLOCAL_BEGIN(); + + /* 0 is an invalid update_marker value */ + if (update_marker == 0) { + //GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + + /* + * Find completion associated with update_marker requested. + * Note: If update completed already, marker will have been + * cleared, it won't be found, and function will just return. + */ + + /* Grab queue lock to protect access to marker list */ +#ifdef FAKE_S1D13522_PLATFORM_MX6 //[ + mutex_lock(&fb_data->queue_mutex); +#else //][!FAKE_S1D13522_PLATFORM_MX6 + spin_lock_irqsave(&fb_data->queue_lock, flags); +#endif //] FAKE_S1D13522_PLATFORM_MX6 + + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, full_list) { + //GALLEN_DBGLOCAL_RUNLOG(0); + if (next_marker->update_marker == update_marker) { + //GALLEN_DBGLOCAL_RUNLOG(1); + dev_dbg(fb_data->dev, "Waiting for marker %d\n", + update_marker); + next_marker->waiting = true; + marker_found = true; + break; + } + } + +#ifdef FAKE_S1D13522_PLATFORM_MX6 //[ + mutex_unlock(&fb_data->queue_mutex); +#else //][!FAKE_S1D13522_PLATFORM_MX6 + spin_unlock_irqrestore(&fb_data->queue_lock, flags); +#endif //] FAKE_S1D13522_PLATFORM_MX6 + + /* + * If marker not found, it has either been signalled already + * or the update request failed. In either case, just return. + */ + if (!marker_found) { + //GALLEN_DBGLOCAL_ESC(); + return ret; + } + + ret = completion_done(&next_marker->update_completion)?1:0; + + + //GALLEN_DBGLOCAL_END(); + return ret; +} +//EXPORT_SYMBOL(mxc_epdc_fb_check_update_complete); + + + +////////////////////////////////////////////////////// +// +// fake_s1d13522 HAL interface . +// + + +static void k_fake_s1d13522_progress_start(void) +{ + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return ; + } + + //fake_s1d13522_progress_start(gptDC); +} + + +static int k_fake_s1d13522_wait_inited(void) +{ + int iRet=0; + + if(giIsInited<=1) + { + if(in_interrupt()) { + printk("[%s]:skip before init (interrupt).",__FUNCTION__); + } + else { + printk("[%s]:wait init .",__FUNCTION__); + wait_for_completion(&mxc_epdc_fake13522_inited); + printk("[%s]:wait init ok.",__FUNCTION__); + } + } + return iRet; +} + +static int32_t k_fake_s1d13522_ioctl(unsigned int cmd,unsigned long arg) +{ + //k_fake_s1d13522_wait_inited(); + return fake_s1d13522_ioctl(cmd,arg,gptDC); +} + + +static void k_vcom_enable(int iIsEnable) +{ + if(iIsEnable) { + //vcom enable . + } + else { + //vcom disable . + } +} + +static int k_get_wfbpp(void) +{ + int i_wf_bpp=4; + + if(*(gpbWF_vaddr+0x10) == 0x2) { + i_wf_bpp=3; + } + + return i_wf_bpp; +} + +static int k_set_partial(int iIsSetPartial) +{ + u32 temp; + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return -1; + } + + if(iIsSetPartial) { + g_mxc_upd_data.update_mode = UPDATE_MODE_PARTIAL; + } + else { + g_mxc_upd_data.update_mode = UPDATE_MODE_FULL; + } + return 0; +} + +static unsigned char *k_get_realfbEx(unsigned long *O_pdwFBSize) +{ + unsigned char *pbRet ; + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return 0; + } + + //pbRet = (unsigned char *)g_fb_data->working_buffer_virt; + pbRet = (unsigned char *)g_fb_data->info.screen_base; + if(O_pdwFBSize) { + *O_pdwFBSize = g_fb_data->info.screen_size; + } + + return pbRet; +} + +static void k_display_start(int iIsStart) +{ + int iChk; + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return ; + } + + + if(iIsStart) { + #if 0 + printk("%s(%d):==============================>\n",__FUNCTION__,__LINE__) ; + printk("\t%d bits/pixel\n",g_fb_data->info.var.bits_per_pixel) ; + printk("\t grayscale=%d \n",g_fb_data->info.var.grayscale) ; + printk("\t yoffset=%d \n",g_fb_data->info.var.yoffset) ; + printk("\t rotate=%d \n",g_fb_data->info.var.rotate) ; + printk("\t activate=%d \n",g_fb_data->info.var.activate) ; + printk("<======================================\n") ; + #endif + + g_mxc_upd_data.update_marker=g_mxc_upd_marker_data.update_marker; + DBG_MSG("%s() (x,y)=(%u,%u),(w,h)(%u,%u),marker=%d\n",__FUNCTION__, + g_mxc_upd_data.update_region.top,g_mxc_upd_data.update_region.left, + g_mxc_upd_data.update_region.width,g_mxc_upd_data.update_region.height, + g_mxc_upd_data.update_marker); + + + iChk = mxc_epdc_fb_send_update(&g_mxc_upd_data,&g_fb_data->info); + if(iChk<0) { + printk(KERN_WARNING"%s(%d):mxc_epdc_fb_send_update fail !\n", + __FUNCTION__,__LINE__); + } + + } + else { + } +} + + +static int k_get_wfmode(void) +{ + int i_wf_mode; + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return 0; + } + + i_wf_mode = g_mxc_upd_data.waveform_mode; + return i_wf_mode; +} + +static void k_set_wfmode(int iWaveform) +{ + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return ; + } + + g_mxc_upd_data.waveform_mode = iWaveform; +} + + +static int k_is_updating(void) +{ + int iRet; + int iChk = 0; + + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return 0; + } + + //if(epdc_is_working_buffer_busy()) + iChk = mxc_epdc_fb_check_update_complete(g_mxc_upd_data.update_marker,&g_fb_data->info); + + if(1==iChk) + { + // updating ... + iRet = 1; + } + else { + // update done + iRet = 0; + } + return iRet; +} + +static int k_wait_update_complete(void) +{ + int iRet; + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return 0; + } + + + //if(1==g_mxc_upd_data.waveform_mode||4==g_mxc_upd_data.waveform_mode) { + // skip wait update complete at DOC mode and A2 mode . + //iRet = 0; + //} + //else if(k_is_updating()) + { + unsigned long dwJiffiesStart,dwJiffiesEnd; + dwJiffiesStart = jiffies ; +#ifdef FAKE_S1D13522_PLATFORM_MX6//[ + iRet = mxc_epdc_fb_wait_update_complete(&g_mxc_upd_marker_data,&g_fb_data->info); + g_mxc_upd_marker_data.update_marker++; +#else //][! FAKE_S1D13522_PLATFORM_MX6 + iRet = mxc_epdc_fb_wait_update_complete(g_mxc_upd_data.update_marker++,&g_fb_data->info); +#endif //] FAKE_S1D13522_PLATFORM_MX6 + dwJiffiesEnd = jiffies; + printk("[%s]waitupdate ret=%d,%u->%u\n",__FUNCTION__,iRet,\ + (unsigned int)dwJiffiesStart,(unsigned int)dwJiffiesEnd); + } + return iRet; +} + + + +///////////////////////////////////////////////////////////// +// calling by real epdc driver . +static int k_set_temperature(struct fb_info *info) +{ + int iRet; + int iChk; + + + //printk("%s(),timeout_tick=%u,current_tick=%u\n",__FUNCTION__, + // gdwLastUpdateJiffies,jiffies); + + if(0==gdwLastUpdateJiffies||time_after(jiffies,gdwLastUpdateJiffies)) { + + + gdwLastUpdateJiffies = jiffies+(60*HZ); + + ASSERT(gptHWCFG); + + if(8==gptHWCFG->m_val.bDisplayCtrl) { + iChk = fp9928_get_temperature(&giLastTemprature); + } + else + if(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl) + { + // imx508 + tps16585 . + iChk = tps65185_get_temperature(&giLastTemprature); + } + +#ifdef LM75_ENABLED//[ + else { + iChk = lm75_get_temperature(0,&giLastTemprature); + } +#else + else { + iChk = -100; + } +#endif //]LM75_ENABLED + + if(iChk>=0) { + //printk("%s():mxc_epdc_fb_set_temperature...\n",__FUNCTION__);// DBG + iChk = mxc_epdc_fb_set_temperature(giLastTemprature,info); + } + else { + gdwLastUpdateJiffies = jiffies; + } + } + return giLastTemprature; +} + +//////////////////////////////////////////////////////////// + +static int k_set_update_rect(unsigned short wX,unsigned short wY, + unsigned short wW,unsigned short wH) +{ + int iRet = 0; + if(0==giIsInited) { + printk("[%s]:skip before init .",__FUNCTION__); + return 0; + } + + + DBG_MSG("%s() x=%u,y=%u,w=%u,h=%u\n",__FUNCTION__,wX,wY,wW,wH); + g_mxc_upd_data.update_region.top = wY; + g_mxc_upd_data.update_region.left = wX; + g_mxc_upd_data.update_region.height = wH; + g_mxc_upd_data.update_region.width = wW; + + + return iRet; +} + +static int k_set_vcom(int iVCOM_set_mV) +{ + int iRet=0; + ASSERT(gptHWCFG); + //printk("%s(%d):%s\n",__FILE__,__LINE__,__FUNCTION__); + if(8==gptHWCFG->m_val.bDisplayCtrl) { + iRet = fp9928_vcom_set(iVCOM_set_mV,0); + if(iRet>=0) { + int iVCOM_get_mV; + if(fp9928_vcom_get(&iVCOM_get_mV)>=0) { + vcom_nominal=iVCOM_get_mV*1000;/* save the vcom_nominal value in uV */ + } + } + } + else + if(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl) { + iRet = tps65185_vcom_set(iVCOM_set_mV,0); + if(iRet>=0) { + int iVCOM_get_mV; + if(tps65185_vcom_get(&iVCOM_get_mV)>=0) { + vcom_nominal=iVCOM_get_mV*1000;/* save the vcom_nominal value in uV */ + } + } + } + else { + } + + return iRet; +} +static int k_set_vcom_to_flash(int iVCOM_set_mV) +{ + int iRet=0; + //printk("%s(%d):%s\n",__FILE__,__LINE__,__FUNCTION__); + if(8==gptHWCFG->m_val.bDisplayCtrl) { + iRet = fp9928_vcom_set(iVCOM_set_mV,1); + if(iRet>=0) { + int iVCOM_get_mV; + if(fp9928_vcom_get(&iVCOM_get_mV)>=0) { + vcom_nominal=iVCOM_get_mV*1000;/* save the vcom_nominal value in uV */ + } + } + } + else + if(gptHWCFG&&(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl)) { + iRet = tps65185_vcom_set(iVCOM_set_mV,1); + if(iRet>=0) { + int iVCOM_get_mV; + if(tps65185_vcom_get(&iVCOM_get_mV)>=0) { + vcom_nominal=iVCOM_get_mV*1000;/* save the vcom_nominal value in uV */ + } + } + } + else { + } + return iRet; +} + +static int k_get_vcom(int *O_piVCOM_get_mV) +{ + int iRet=0; + //printk("%s(%d):%s\n",__FILE__,__LINE__,__FUNCTION__); + if(8==gptHWCFG->m_val.bDisplayCtrl) { + if(vcom_nominal) { + if(O_piVCOM_get_mV) { + *O_piVCOM_get_mV = vcom_nominal/1000; + } + } + else { + iRet = fp9928_vcom_get(O_piVCOM_get_mV); + } + } + else + if(gptHWCFG&&(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl)) { + if(vcom_nominal) { + if(O_piVCOM_get_mV) { + *O_piVCOM_get_mV = vcom_nominal/1000; + } + } + else { + iRet = tps65185_vcom_get(O_piVCOM_get_mV); + } + } + else { + } + + return iRet; +} + +static void k_create_sys_attr(void) +{ + int err; + DBG_MSG("%s()\n",__FUNCTION__); + ASSERT(g_fb_data); + ASSERT(g_fb_data->dev); + //ASSERT(g_fb_data->dev->kobj); + err = sysfs_create_files(&g_fb_data->info.dev->kobj, sysfs_epdc_attrs); + if (err) { + pr_err("Can't create epdc attr sysfs !\n"); + } +} + +static int k_fake_s1d13522_init(unsigned char *pbInitDCbuf) +{ + + int iChk; + int vcom_mV; + + + //printk("\n%s() DisplayCtrl=%d\n\n",__FUNCTION__,(int)gptHWCFG->m_val.bDisplayCtrl); + + gptDC = fake_s1d13522_initEx3(default_bpp,g_fb_data->info.screen_base,\ + g_fb_data->info.var.xres,g_fb_data->info.var.yres, \ + ALIGN(g_fb_data->info.var.xres,32),ALIGN(g_fb_data->info.var.yres,128)); + + if(gptDC) { + gptDC->pfnGetWaveformBpp = k_get_wfbpp; + gptDC->pfnVcomEnable = k_vcom_enable; + gptDC->pfnSetPartialUpdate = k_set_partial; + //gptDC->pfnGetRealFrameBuf = k_get_realfb; + gptDC->pfnGetRealFrameBufEx = k_get_realfbEx; + gptDC->pfnDispStart = k_display_start; + gptDC->pfnGetWaveformMode = k_get_wfmode; + gptDC->pfnSetWaveformMode = k_set_wfmode; + gptDC->pfnIsUpdating = k_is_updating; + gptDC->pfnWaitUpdateComplete = k_wait_update_complete; + gptDC->pfnSetUpdateRect = k_set_update_rect; + gptDC->pfnSetVCOM = k_set_vcom; + gptDC->pfnGetVCOM = k_get_vcom; + gptDC->pfnSetVCOMToFlash = k_set_vcom_to_flash; + + //gptDC->dwFlags |= EPDFB_DC_FLAG_OFB_RGB565; + gptDC->dwFlags |= EPDFB_DC_FLAG_FLASHDIRTY; + + // + g_mxc_upd_data.update_region.top = 0; + g_mxc_upd_data.update_region.left = 0; + g_mxc_upd_data.update_region.height = g_fb_data->info.var.yres; + g_mxc_upd_data.update_region.width = g_fb_data->info.var.xres; + + //g_mxc_upd_data.waveform_mode = g_fb_data->wv_modes.mode_gc16; + g_mxc_upd_data.waveform_mode = WF_GC16; + + g_mxc_upd_data.update_mode = UPDATE_MODE_FULL; + g_mxc_upd_data.update_marker = 0; + g_mxc_upd_data.temp = TEMP_USE_AMBIENT; + g_mxc_upd_data.flags = 0; + //g_mxc_upd_data.alt_buffer_data = ; + //mxc_epdc_fb_set_upd_scheme(UPDATE_SCHEME_SNAPSHOT,&g_fb_data->info); + + // printk("%s(%d):%s,Display=%s\n",__FILE__,__LINE__,__FUNCTION__, + // NtxHwCfg_GetCfgFldStrVal(gptHWCFG,HWCFG_FLDIDX_DisplayCtrl)); +#ifdef LM75_ENABLED//[ + if(gptHWCFG&&\ + (6==gptHWCFG->m_val.bDisplayCtrl||\ + 7==gptHWCFG->m_val.bDisplayCtrl||\ + 8==gptHWCFG->m_val.bDisplayCtrl)) + { +#endif //]LM75_ENABLED + int iPortA[2]={-1,-1} ; + int i; + // imx508 + tps16585 . + +#ifdef FAKE_S1D13522_PLATFORM_MX6 //[ + +#else//][!FAKE_S1D13522_PLATFORM_MX6 + + if(28==gptHWCFG->m_val.bPCB) { + // E606C2B3 PMIC in Channel 3. before E606C2B3 in Channel 2. + + #if 0 //[ + // <E606C2B3 + iPortA[0] = 2; + iPortA[1] = -1; + #else //][ + // >=E606C2B3 . + iPortA[0] = 1; + iPortA[1] = 2; + #endif//] + + } + else +#endif //]FAKE_S1D13522_PLATFORM_MX6 + { + iPortA[0] = 2; + } + + for(i=0;i<2;i++) { + if(iPortA[i]>0) { + //printk("%s(),init TPS65185 @ i2c%d\n",__FUNCTION__,iPortA[i]); + if(8==gptHWCFG->m_val.bDisplayCtrl) { + iChk = fp9928_init(iPortA[i]); + if(iChk>=0){ + if(0!=glVCOM_uV) { + if(fp9928_vcom_set((int)(glVCOM_uV/1000),0)<0) { + WARNING_MSG("%s(),FP9928 vcom set fail !\n",__FUNCTION__); + } + } + fp9928_vcom_get(&vcom_mV); + } + } + else { + iChk = tps65185_init(iPortA[i],EPDTIMING_V110); + if(iChk>=0) { + if(0!=glVCOM_uV) { + if(tps65185_vcom_set((int)(glVCOM_uV/1000),0)<0) { + WARNING_MSG("%s(),tps65185 vcom set fail !\n",__FUNCTION__); + } + } + tps65185_vcom_get(&vcom_mV); + } + } + if(iChk>=0) { + break; + } + else { + WARNING_MSG("%s(),init @ i2c%d fail !\n",__FUNCTION__,iPortA[i]); + } + } + } +#ifdef LM75_ENABLED//[ + } + else { + if ((4 == check_hardware_name()) || (3 == check_hardware_name())) { + lm75_init (3); + } + else { + lm75_init (2); + } + } +#endif //]LM75_ENABLED// + + vcom_nominal=vcom_mV*1000;/* save the vcom_nominal value in uV */ + + DBG_MSG("%s():PMIC VCOM=%d mV,vcom_nominal=%d\n", + __FUNCTION__,vcom_mV,vcom_nominal); + + //if(!fake_s1d13522_is_logo_bypss()) + { + g_fb_data->wfm = 0; /* initial waveform mode should be INIT */ + //printk("draw mode0\n"); + epdc_powerup(g_fb_data); + draw_mode0(g_fb_data); + //printk("draw mode0 end\n"); + + g_fb_data->powering_down = true; + schedule_delayed_work(&g_fb_data->epdc_done_work, + msecs_to_jiffies(g_fb_data->pwrdown_delay)); + } + //epdc_powerdown(g_fb_data); + + giIsInited=1; + + //while(k_is_updating()) { + //DBG0_MSG("%s(%d):wait for update done .\n"); + //schedule(); + //} + if(fake_s1d13522_is_logo_bypss()) { + printk("%s() bypass logo\n",__FUNCTION__); + } + else { + if(pbInitDCbuf) { + int ilogo_width ,ilogo_height; + + #if 1 + if(k_get_wfbpp() == 4) + { + k_set_wfmode(WF_GC16); // fill LUT with default waveform, for 4bit, use mode 2 + } + else { + k_set_wfmode(WF_GC4); // fill LUT with default waveform, for 3bit, use mode 3 (GC) + } + #endif + + if(gptHWCFG) { + if(1==gptHWCFG->m_val.bDisplayResolution) { + ilogo_width = 1024 ; + ilogo_height = 758 ; + } + else if(2==gptHWCFG->m_val.bDisplayResolution) { + ilogo_width = 1024 ; + ilogo_height = 768 ; + } + else if(3==gptHWCFG->m_val.bDisplayResolution) { + ilogo_width = 1440 ; + ilogo_height = 1080 ; + } + else if(5==gptHWCFG->m_val.bDisplayResolution) { + ilogo_width = 1448 ; + ilogo_height = 1072 ; + } + else if(6==gptHWCFG->m_val.bDisplayResolution) { + ilogo_width = 1600 ; + ilogo_height = 1200 ; + } + else { + ilogo_width = 800 ; + ilogo_height = 600 ; + } + } + else { + ilogo_width = 800 ; + ilogo_height = 600 ; + } + + if( gdwLOGO_size>=((ilogo_width*ilogo_height)>>1) ) { + u32 old_upd_scheme=g_fb_data->upd_scheme; + u32 old_auto_mode=g_fb_data->auto_mode; + u32 old_rotate=g_fb_data->info.var.rotate; + + + DBG0_MSG("drawing logo begin ...\n"); + g_fb_data->epdc_fb_var.rotate = FB_ROTATE_UR; + mxc_epdc_fb_check_var(&g_fb_data->info.var,&g_fb_data->info); + + mxc_epdc_fb_set_auto_update(AUTO_UPDATE_MODE_REGION_MODE,&g_fb_data->info); + mxc_epdc_fb_set_upd_scheme(UPDATE_SCHEME_SNAPSHOT,&g_fb_data->info); + + fake_s1d13522_display_img(0,0,ilogo_width,ilogo_height,pbInitDCbuf,gptDC,4,0); + k_wait_update_complete(); + + mxc_epdc_fb_set_upd_scheme(old_upd_scheme,&g_fb_data->info); + mxc_epdc_fb_set_auto_update(old_auto_mode,&g_fb_data->info); + + g_fb_data->info.var.rotate = old_rotate; + mxc_epdc_fb_check_var(&g_fb_data->info.var,&g_fb_data->info); + + DBG_MSG("drawing logo end ...\n"); + } + else { + printk("logo skip : logosize %u < %u !! \n ",(unsigned int)gdwLOGO_size, + ((ilogo_width*ilogo_height)>>1)); + } + } + } + fake_s1d13522_progress_start(gptDC); + giIsInited=2; + k_create_sys_attr(); + complete_all(&mxc_epdc_fake13522_inited); + + return 0; + } + else { + printk("%s(%d): init fail !!\n",__FUNCTION__,__LINE__); + return -1; + } +} + diff --git a/drivers/video/mxc/mxc_epdc_fb.c b/drivers/video/mxc/mxc_epdc_fb.c new file mode 100644 index 00000000..39de51db --- /dev/null +++ b/drivers/video/mxc/mxc_epdc_fb.c @@ -0,0 +1,8045 @@ +/* + * Copyright (C) 2010-2012 Freescale Semiconductor, Inc. + * + * 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 + * + */ +/* + * Based on STMP378X LCDIF + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <linux/cpufreq.h> +#include <linux/firmware.h> +#include <linux/kthread.h> +#include <linux/dmaengine.h> +#include <linux/pxp_dma.h> +#include <linux/mxcfb.h> +#include <linux/mxcfb_epdc_kernel.h> +#include <linux/gpio.h> +#include <linux/regulator/driver.h> +#include <linux/fsl_devices.h> +#include <linux/bitops.h> +#include <mach/epdc.h> +#include <mach/dma.h> +#include <asm/cacheflush.h> + + +#include "epdc_regs.h" + +#include "lk_tps65185.h" +#include "lk_fp9928.h" + +//#define USE_BSP_PMIC 1 + +#define GDEBUG 0 +#define giDbgLvl 1000 +#include <linux/gallen_dbg.h> + +#define FIX_WRONG_REGION_TEMP 1 +#define AVOID_REAGL_DIRTY_TEMP 1 +//#define DO_NOT_POWEROFF 1 +#define NTX_AUTOMODE_PATCH 1 +#define NTX_WFM_MODE_OPTIMIZED 1 +//#define NTX_WFM_MODE_OPTIMIZED_REAGL 1 +#ifdef CONFIG_FB_MXC_EINK_REGAL//[ + #define NO_AUTO_REAGL_MODE 1 + #define NO_CUS_REAGL_MODE 1 +#elif defined(CONFIG_ANDROID)//[ + //#define NO_AUTO_REAGL_MODE 1 + //#define NO_CUS_REAGL_MODE 1 +#else //][!CONFIG_ANDROID + #define NO_AUTO_REAGL_MODE 1 + //#define NO_CUS_REAGL_MODE 1 +#endif//] CONFIG_ANDROID +#include "eink_processing2.h" + +/******************************************* + * AA/AA-D testing + *******************************************/ + +static uint32_t get_uSecs(void); +static uint32_t getTimeDiff(uint32_t, uint32_t); + +static uint32_t get_uSecs(void) { + struct timeval tv; + do_gettimeofday(&tv); + return (uint32_t) (tv.tv_usec); // mircoseconds +}; + +static uint32_t getTimeDiff(uint32_t time1, uint32_t time2) +{ + return (time2 >= time1) ? (time2 - time1) : (1000000 + time2 -time1); +} + +uint32_t aa_time_stamp[2]; + + + +/******************************************* + * End of AA/AAD testing header part + *******************************************/ + +/* + * define the default parameters for the histogram registers + */ +#define EPDC_HIST1_P4N_PARAM (0x00000000) +#define EPDC_HIST2_P4N_PARAM (0x00000f00) +#define EPDC_HIST4_P4N_PARAM (0x0f0a0500) +#define EPDC_HIST8_P4N_PARAM0 (0x06040200) +#define EPDC_HIST8_P4N_PARAM1 (0x0f0d0b09) +#define EPDC_HIST16_P4N_PARAM0 (0x03020100) +#define EPDC_HIST16_P4N_PARAM1 (0x07060504) +#define EPDC_HIST16_P4N_PARAM2 (0x0b0a0908) +#define EPDC_HIST16_P4N_PARAM3 (0x0f0e0d0c) + +#define EPDC_HIST1_P5N_PARAM (EPDC_HIST1_P4N_PARAM << 1) +#define EPDC_HIST2_P5N_PARAM (EPDC_HIST2_P4N_PARAM << 1) +#define EPDC_HIST4_P5N_PARAM (EPDC_HIST4_P4N_PARAM << 1) +#define EPDC_HIST8_P5N_PARAM0 (EPDC_HIST8_P4N_PARAM0 << 1) +#define EPDC_HIST8_P5N_PARAM1 (EPDC_HIST8_P4N_PARAM1 << 1) +#define EPDC_HIST16_P5N_PARAM0 (EPDC_HIST16_P4N_PARAM0 << 1) +#define EPDC_HIST16_P5N_PARAM1 (EPDC_HIST16_P4N_PARAM1 << 1) +#define EPDC_HIST16_P5N_PARAM2 (EPDC_HIST16_P4N_PARAM2 << 1) +#define EPDC_HIST16_P5N_PARAM3 (EPDC_HIST16_P4N_PARAM3 << 1) + +/* + * Enable this define to have a default panel + * loaded during driver initialization + */ +//#ifdef CONFIG_ANDROID//[ + #define DEFAULT_PANEL_HW_INIT +//#endif//]CONFIG_ANDROID + +#define NUM_SCREENS_MIN 2 + +#define EPDC_V1_NUM_LUTS 16 +#define EPDC_V1_MAX_NUM_UPDATES 20 +#define EPDC_V2_NUM_LUTS 64 +#define EPDC_V2_MAX_NUM_UPDATES 64 +#define EPDC_MAX_NUM_BUFFERS 2 +#define INVALID_LUT (-1) +#define DRY_RUN_NO_LUT 100 + +/* Maximum update buffer image width due to v2.0 and v2.1 errata ERR005313. */ +#define EPDC_V2_MAX_UPDATE_WIDTH 2047 +#define EPDC_V2_ROTATION_ALIGNMENT 8 + +#define DEFAULT_TEMP_INDEX 0 +#define DEFAULT_TEMP 20 /* room temp in deg Celsius */ + +#define INIT_UPDATE_MARKER 0x12345678 +#define PAN_UPDATE_MARKER 0x12345679 + +#define POWER_STATE_OFF 0 +#define POWER_STATE_ON 1 + +#define MERGE_OK 0 +#define MERGE_FAIL 1 +#define MERGE_BLOCK 2 + + +#define NTX_WFM_MODE_INIT 0 +#define NTX_WFM_MODE_DU 1 +#define NTX_WFM_MODE_GC16 2 +#define NTX_WFM_MODE_GC4 3 +#define NTX_WFM_MODE_A2 4 +#define NTX_WFM_MODE_GL16 5 +#define NTX_WFM_MODE_GLR16 6 +#define NTX_WFM_MODE_GLD16 7 +#define NTX_WFM_MODE_TOTAL 8 +static int giNTX_waveform_modeA[NTX_WFM_MODE_TOTAL]; + + +unsigned char gbModeVersion=0 ; +unsigned char gbWFM_REV ; +unsigned char gbFPL_Platform ; + +static int vcom_nominal; + + +static unsigned long default_bpp = 16; + +struct update_marker_data { + struct list_head full_list; + struct list_head upd_list; + u32 update_marker; + struct completion update_completion; + int lut_num; + bool collision_test; + bool waiting; +}; + +struct update_desc_list { + struct list_head list; + struct mxcfb_update_data upd_data;/* Update parameters */ + u32 epdc_offs; /* Added to buffer ptr to resolve alignment */ + u32 epdc_stride; /* Depends on rotation & whether we skip PxP */ + struct list_head upd_marker_list; /* List of markers for this update */ + u32 update_order; /* Numeric ordering value for update */ + bool is_aa; /* Does the update require advance algorithms? */ + bool wb_pause; /* Boolean to identify whether the EPDC update + requires pause for AA processing */ + int iDither_state; /* Dither state */ +}; + +/* This structure represents a list node containing both + * a memory region allocated as an output buffer for the PxP + * update processing task, and the update description (mode, region, etc.) */ +struct update_data_list { + struct list_head list; + dma_addr_t phys_addr; /* Pointer to phys address of processed Y buf */ + void *virt_addr; + struct update_desc_list *update_desc; + int lut_num; /* Assigned before update is processed into working buffer */ + u64 collision_mask; /* Set when update creates collision */ + /* Mask of the LUTs the update collides with */ +}; + +struct mxc_epdc_fb_data { + struct fb_info info; + struct fb_var_screeninfo epdc_fb_var; /* Internal copy of screeninfo + so we can sync changes to it */ + u32 pseudo_palette[16]; + char fw_str[64]; + struct list_head list; + struct imx_epdc_fb_mode *cur_mode; + struct imx_epdc_fb_platform_data *pdata; + int blank; + u32 max_pix_size; + ssize_t map_size; + dma_addr_t phys_start; + u32 fb_offset; + int default_bpp; + int native_width; + int native_height; + int num_screens; + int epdc_irq; + struct device *dev; + int power_state; + int wait_for_powerdown; + struct completion powerdown_compl; + struct clk *epdc_clk_axi; + struct clk *epdc_clk_pix; + struct regulator *display_regulator; + struct regulator *vcom_regulator; + struct regulator *v3p3_regulator; + struct regulator *vdd_1v8_regulator; + bool fw_default_load; + int rev; + + /* FB elements related to EPDC updates */ + int num_luts; + int max_num_updates; + bool in_init; + bool hw_ready; + bool hw_initializing; + bool waiting_for_idle; + u32 auto_mode; + u32 upd_scheme; + struct list_head upd_pending_list; + struct list_head upd_buf_queue; + struct list_head upd_buf_free_list; + struct list_head upd_buf_collision_list; + struct update_data_list *cur_update; + struct mutex queue_mutex; + int trt_entries; + int temp_index; + u8 *temp_range_bounds; + int buf_pix_fmt; + int wfm; + struct mxcfb_waveform_modes wv_modes; + bool wv_modes_update; + u32 *waveform_buffer_virt; + u32 waveform_buffer_phys; + u32 waveform_buffer_size; + u32 *working_buffer_A_virt; + u32 working_buffer_A_phys; + u32 *working_buffer_B_virt; + u32 working_buffer_B_phys; + u32 working_buffer_size; + dma_addr_t *phys_addr_updbuf; + void **virt_addr_updbuf; + u32 upd_buffer_num; + u32 max_num_buffers; + dma_addr_t phys_addr_copybuf; /* Phys address of copied update data */ + void *virt_addr_copybuf; /* Used for PxP SW workaround */ + u32 order_cnt; + struct list_head full_marker_list; + u32 *lut_update_order; /* Array size = number of luts */ + u64 epdc_colliding_luts; + u64 luts_complete_wb; + struct completion updates_done; + struct delayed_work epdc_done_work; + struct workqueue_struct *epdc_submit_workqueue; + struct work_struct epdc_submit_work; + struct workqueue_struct *epdc_intr_workqueue; + struct work_struct epdc_intr_work; +//#ifdef FW_IN_RAM//[ + struct workqueue_struct *epdc_firmware_workqueue; + //struct work_struct epdc_firmware_work; + struct delayed_work epdc_firmware_work; + struct semaphore firmware_work_lock; +//#endif //]FW_IN_RAM + struct workqueue_struct *epdc_aa_workqueue; + struct work_struct epdc_aa_work; + bool waiting_for_wb; + bool waiting_for_lut; + bool waiting_for_lut15; + struct completion update_res_free; + struct completion lut15_free; + struct completion eof_event; + int eof_sync_period; + struct mutex power_mutex; + bool powering_down; + bool updates_active; + int pwrdown_delay; + unsigned long tce_prevent; + bool restrict_width; /* work around rev >=2.0 width and + stride restriction */ + + /* FB elements related to gen2 waveform data */ + u8 *waveform_vcd_buffer; + u8 *waveform_acd_buffer; + u32 waveform_magic_number; + u8 *waveform_xwi_buffer; + char *waveform_xwi_string; + unsigned waveform_xwi_string_length; + u32 waveform_mc; + u32 waveform_trc; + + /* FB elements related to PxP DMA */ + struct completion pxp_tx_cmpl; + struct pxp_channel *pxp_chan; + struct pxp_config_data pxp_conf; + struct dma_async_tx_descriptor *txd; + dma_cookie_t cookie; + struct scatterlist sg[2]; + struct mutex pxp_mutex; /* protects access to PxP */ +}; + +struct waveform_data_header { + unsigned int wi0; + unsigned int wi1; + unsigned int wi2; + unsigned int wi3; + unsigned int wi4; + unsigned int wi5; + unsigned int wi6; + unsigned int xwia:24; + unsigned int cs1:8; + unsigned int wmta:24; + unsigned int fvsn:8; + unsigned int luts:8; + unsigned int mc:8; + unsigned int trc:8; + unsigned int awv:8; + unsigned int eb:8; + unsigned int sb:8; + unsigned int reserved0_1:8; + unsigned int reserved0_2:8; + unsigned int reserved0_3:8; + unsigned int reserved0_4:8; + unsigned int reserved0_5:8; + unsigned int cs2:8; +}; + +#include "ntx_hwconfig.h" +extern volatile NTX_HWCONFIG *gptHWCFG ; + +struct mxcfb_waveform_data_file { + struct waveform_data_header wdh; + u32 *data; /* Temperature Range Table + Waveform Data */ +}; + +void __iomem *epdc_base; + +volatile struct mxc_epdc_fb_data *g_fb_data; + +/* forward declaration */ +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, + int temp); +static void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data); +static int mxc_epdc_fb_blank(int blank, struct fb_info *info); +static int mxc_epdc_fb_init_hw(struct fb_info *info); +static int pxp_process_update(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region); +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat); + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data); +static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data); +static void epdc_submit_paused_update(void); + +static void do_dithering_processing_Y1_v1_0( + unsigned char *update_region_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist); +static void do_dithering_processing_Y4_v1_0( + unsigned char *update_region_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist); + +// FW_IN_RAM //[ +static void mxc_epdc_fb_fw_handler(const struct firmware *fw,void *context); +// FW_IN_RAM //] + +static int mxc_epdc_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +#ifdef DEBUG +static void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) +{ + dev_info(fb_data->dev, "S0 fmt 0x%x", + pxp_conf->s0_param.pixel_fmt); + dev_info(fb_data->dev, "S0 width 0x%x", + pxp_conf->s0_param.width); + dev_info(fb_data->dev, "S0 height 0x%x", + pxp_conf->s0_param.height); + dev_info(fb_data->dev, "S0 ckey 0x%x", + pxp_conf->s0_param.color_key); + dev_info(fb_data->dev, "S0 ckey en 0x%x", + pxp_conf->s0_param.color_key_enable); + + dev_info(fb_data->dev, "OL0 combine en 0x%x", + pxp_conf->ol_param[0].combine_enable); + dev_info(fb_data->dev, "OL0 fmt 0x%x", + pxp_conf->ol_param[0].pixel_fmt); + dev_info(fb_data->dev, "OL0 width 0x%x", + pxp_conf->ol_param[0].width); + dev_info(fb_data->dev, "OL0 height 0x%x", + pxp_conf->ol_param[0].height); + dev_info(fb_data->dev, "OL0 ckey 0x%x", + pxp_conf->ol_param[0].color_key); + dev_info(fb_data->dev, "OL0 ckey en 0x%x", + pxp_conf->ol_param[0].color_key_enable); + dev_info(fb_data->dev, "OL0 alpha 0x%x", + pxp_conf->ol_param[0].global_alpha); + dev_info(fb_data->dev, "OL0 alpha en 0x%x", + pxp_conf->ol_param[0].global_alpha_enable); + dev_info(fb_data->dev, "OL0 local alpha en 0x%x", + pxp_conf->ol_param[0].local_alpha_enable); + + dev_info(fb_data->dev, "Out fmt 0x%x", + pxp_conf->out_param.pixel_fmt); + dev_info(fb_data->dev, "Out width 0x%x", + pxp_conf->out_param.width); + dev_info(fb_data->dev, "Out height 0x%x", + pxp_conf->out_param.height); + + dev_info(fb_data->dev, + "drect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.drect.left, pxp_conf->proc_data.drect.top, + pxp_conf->proc_data.drect.width, + pxp_conf->proc_data.drect.height); + dev_info(fb_data->dev, + "srect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.srect.left, pxp_conf->proc_data.srect.top, + pxp_conf->proc_data.srect.width, + pxp_conf->proc_data.srect.height); + dev_info(fb_data->dev, "Scaling en 0x%x", pxp_conf->proc_data.scaling); + dev_info(fb_data->dev, "HFlip en 0x%x", pxp_conf->proc_data.hflip); + dev_info(fb_data->dev, "VFlip en 0x%x", pxp_conf->proc_data.vflip); + dev_info(fb_data->dev, "Rotation 0x%x", pxp_conf->proc_data.rotate); + dev_info(fb_data->dev, "BG Color 0x%x", pxp_conf->proc_data.bgcolor); +} + +static void dump_epdc_reg(void) +{ + printk(KERN_DEBUG "\n\n"); + printk(KERN_DEBUG "EPDC_CTRL 0x%x\n", __raw_readl(EPDC_CTRL)); + printk(KERN_DEBUG "EPDC_WVADDR 0x%x\n", __raw_readl(EPDC_WVADDR)); + printk(KERN_DEBUG "EPDC_WB_ADDR 0x%x\n", __raw_readl(EPDC_WB_ADDR)); + printk(KERN_DEBUG "EPDC_RES 0x%x\n", __raw_readl(EPDC_RES)); + printk(KERN_DEBUG "EPDC_FORMAT 0x%x\n", __raw_readl(EPDC_FORMAT)); + printk(KERN_DEBUG "EPDC_FIFOCTRL 0x%x\n", __raw_readl(EPDC_FIFOCTRL)); + printk(KERN_DEBUG "EPDC_UPD_ADDR 0x%x\n", __raw_readl(EPDC_UPD_ADDR)); + printk(KERN_DEBUG "EPDC_UPD_STRIDE 0x%x\n", __raw_readl(EPDC_UPD_STRIDE)); + printk(KERN_DEBUG "EPDC_UPD_FIXED 0x%x\n", __raw_readl(EPDC_UPD_FIXED)); + printk(KERN_DEBUG "EPDC_UPD_CORD 0x%x\n", __raw_readl(EPDC_UPD_CORD)); + printk(KERN_DEBUG "EPDC_UPD_SIZE 0x%x\n", __raw_readl(EPDC_UPD_SIZE)); + printk(KERN_DEBUG "EPDC_UPD_CTRL 0x%x\n", __raw_readl(EPDC_UPD_CTRL)); + printk(KERN_DEBUG "EPDC_TEMP 0x%x\n", __raw_readl(EPDC_TEMP)); + printk(KERN_DEBUG "EPDC_AUTOWV_LUT 0x%x\n", __raw_readl(EPDC_AUTOWV_LUT)); + printk(KERN_DEBUG "EPDC_TCE_CTRL 0x%x\n", __raw_readl(EPDC_TCE_CTRL)); + printk(KERN_DEBUG "EPDC_TCE_SDCFG 0x%x\n", __raw_readl(EPDC_TCE_SDCFG)); + printk(KERN_DEBUG "EPDC_TCE_GDCFG 0x%x\n", __raw_readl(EPDC_TCE_GDCFG)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN1 0x%x\n", __raw_readl(EPDC_TCE_HSCAN1)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN2 0x%x\n", __raw_readl(EPDC_TCE_HSCAN2)); + printk(KERN_DEBUG "EPDC_TCE_VSCAN 0x%x\n", __raw_readl(EPDC_TCE_VSCAN)); + printk(KERN_DEBUG "EPDC_TCE_OE 0x%x\n", __raw_readl(EPDC_TCE_OE)); + printk(KERN_DEBUG "EPDC_TCE_POLARITY 0x%x\n", __raw_readl(EPDC_TCE_POLARITY)); + printk(KERN_DEBUG "EPDC_TCE_TIMING1 0x%x\n", __raw_readl(EPDC_TCE_TIMING1)); + printk(KERN_DEBUG "EPDC_TCE_TIMING2 0x%x\n", __raw_readl(EPDC_TCE_TIMING2)); + printk(KERN_DEBUG "EPDC_TCE_TIMING3 0x%x\n", __raw_readl(EPDC_TCE_TIMING3)); + printk(KERN_DEBUG "EPDC_PIGEON_CTRL0 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL0)); + printk(KERN_DEBUG "EPDC_PIGEON_CTRL1 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL1)); + printk(KERN_DEBUG "EPDC_IRQ_MASK1 0x%x\n", __raw_readl(EPDC_IRQ_MASK1)); + printk(KERN_DEBUG "EPDC_IRQ_MASK2 0x%x\n", __raw_readl(EPDC_IRQ_MASK2)); + printk(KERN_DEBUG "EPDC_IRQ1 0x%x\n", __raw_readl(EPDC_IRQ1)); + printk(KERN_DEBUG "EPDC_IRQ2 0x%x\n", __raw_readl(EPDC_IRQ2)); + printk(KERN_DEBUG "EPDC_IRQ_MASK 0x%x\n", __raw_readl(EPDC_IRQ_MASK)); + printk(KERN_DEBUG "EPDC_IRQ 0x%x\n", __raw_readl(EPDC_IRQ)); + printk(KERN_DEBUG "EPDC_STATUS_LUTS 0x%x\n", __raw_readl(EPDC_STATUS_LUTS)); + printk(KERN_DEBUG "EPDC_STATUS_LUTS2 0x%x\n", __raw_readl(EPDC_STATUS_LUTS2)); + printk(KERN_DEBUG "EPDC_STATUS_NEXTLUT 0x%x\n", __raw_readl(EPDC_STATUS_NEXTLUT)); + printk(KERN_DEBUG "EPDC_STATUS_COL1 0x%x\n", __raw_readl(EPDC_STATUS_COL)); + printk(KERN_DEBUG "EPDC_STATUS_COL2 0x%x\n", __raw_readl(EPDC_STATUS_COL2)); + printk(KERN_DEBUG "EPDC_STATUS 0x%x\n", __raw_readl(EPDC_STATUS)); + printk(KERN_DEBUG "EPDC_UPD_COL_CORD 0x%x\n", __raw_readl(EPDC_UPD_COL_CORD)); + printk(KERN_DEBUG "EPDC_UPD_COL_SIZE 0x%x\n", __raw_readl(EPDC_UPD_COL_SIZE)); + printk(KERN_DEBUG "EPDC_DEBUG 0x%x\n", __raw_readl(EPDC_DEBUG)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT 0x%x\n", __raw_readl(EPDC_DEBUG_LUT)); + printk(KERN_DEBUG "EPDC_HIST1_PARAM 0x%x\n", __raw_readl(EPDC_HIST1_PARAM)); + printk(KERN_DEBUG "EPDC_HIST2_PARAM 0x%x\n", __raw_readl(EPDC_HIST2_PARAM)); + printk(KERN_DEBUG "EPDC_HIST4_PARAM 0x%x\n", __raw_readl(EPDC_HIST4_PARAM)); + printk(KERN_DEBUG "EPDC_HIST8_PARAM0 0x%x\n", __raw_readl(EPDC_HIST8_PARAM0)); + printk(KERN_DEBUG "EPDC_HIST8_PARAM1 0x%x\n", __raw_readl(EPDC_HIST8_PARAM1)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM0 0x%x\n", __raw_readl(EPDC_HIST16_PARAM0)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM1 0x%x\n", __raw_readl(EPDC_HIST16_PARAM1)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM2 0x%x\n", __raw_readl(EPDC_HIST16_PARAM2)); + printk(KERN_DEBUG "EPDC_HIST16_PARAM3 0x%x\n", __raw_readl(EPDC_HIST16_PARAM3)); + printk(KERN_DEBUG "EPDC_GPIO 0x%x\n", __raw_readl(EPDC_GPIO)); + printk(KERN_DEBUG "EPDC_VERSION 0x%x\n", __raw_readl(EPDC_VERSION)); + printk(KERN_DEBUG "\n\n"); +} + +static void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) +{ + dev_info(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " + "LUT = %d, Coll Mask = 0x%llx, order = %d\n", + upd_data_list->update_desc->upd_data.update_region.left, + upd_data_list->update_desc->upd_data.update_region.top, + upd_data_list->update_desc->upd_data.update_region.width, + upd_data_list->update_desc->upd_data.update_region.height, + upd_data_list->update_desc->upd_data.waveform_mode, + upd_data_list->lut_num, + upd_data_list->collision_mask, + upd_data_list->update_desc->update_order); +} + +static void dump_collision_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Collision List:\n"); + if (list_empty(&fb_data->upd_buf_collision_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_collision_list, list) { + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_free_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Free List:\n"); + if (list_empty(&fb_data->upd_buf_free_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); +} + +static void dump_queue(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_info(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_buf_queue)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_queue, list) { + dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_desc_data(struct device *dev, + struct update_desc_list *upd_desc_list) +{ + dev_info(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " + "order = %d\n", + upd_desc_list->upd_data.update_region.left, + upd_desc_list->upd_data.update_region.top, + upd_desc_list->upd_data.update_region.width, + upd_desc_list->upd_data.update_region.height, + upd_desc_list->upd_data.waveform_mode, + upd_desc_list->update_order); +} + +static void dump_pending_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_desc_list *plist; + + dev_info(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_pending_list)) + dev_info(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_pending_list, list) + dump_desc_data(fb_data->dev, plist); +} + +static void dump_all_updates(struct mxc_epdc_fb_data *fb_data) +{ + dump_free_list(fb_data); + dump_queue(fb_data); + dump_collision_list(fb_data); + dev_info(fb_data->dev, "Current update being processed:\n"); + if (fb_data->cur_update == NULL) + dev_info(fb_data->dev, "No current update\n"); + else + dump_update_data(fb_data->dev, fb_data->cur_update); +} +#else +static inline void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) {} +static inline void dump_epdc_reg(void) {} +static inline void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) {} +static inline void dump_collision_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_free_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_queue(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_all_updates(struct mxc_epdc_fb_data *fb_data) {} + +#endif + +/* + * EPDC Voltage Control data handler + */ +struct epd_vc_data { + unsigned version:16; + unsigned v1:16; + unsigned v2:16; + unsigned v3:16; + unsigned v4:16; + unsigned v5:16; + unsigned v6:16; + unsigned v7:8; + u8 cs:8; + }; +void fetch_Epdc_Pmic_Voltages( struct epd_vc_data *vcd, struct mxc_epdc_fb_data *fb_data, + u32 waveform_mode, + u32 waveform_tempRange) +{ + /* fetch and display the voltage control data */ + if (fb_data->waveform_vcd_buffer) { + + /* fetch the voltage control data */ + if (mxc_epdc_fb_fetch_vc_data( fb_data->waveform_vcd_buffer, waveform_mode, waveform_tempRange, fb_data->waveform_mc, fb_data->waveform_trc, (unsigned char *) vcd) < 0) + dev_err(fb_data->dev, " *** Extra Waveform Data checksum error ***\n"); + else { +#if 0 + dev_info(fb_data->dev, " -- VC Data version 0x%04x : v1 = 0x%04x, v2 = 0x%04x, v3 = 0x%04x, v4 = 0x%04x, v5 = 0x%04x, v6 = 0x%04x, v7 = 0x%02x --\n", + vcd->version, vcd->v1, vcd->v2, vcd->v3, vcd->v4, vcd->v5, vcd->v6, vcd->v7 ); +#endif + } + + } +} + +/******************************************************** + * Start Low-Level EPDC Functions + ********************************************************/ + +static inline void epdc_lut_complete_intr(int rev, u32 lut_num, bool enable) +{ + if (rev < 20) { + if (enable) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_SET); + else + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_CLEAR); + } else { + if (enable) { + if (lut_num < 32) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK1_SET); + else + __raw_writel(1 << (lut_num - 32), + EPDC_IRQ_MASK2_SET); + } else { + if (lut_num < 32) + __raw_writel(1 << lut_num, + EPDC_IRQ_MASK1_CLEAR); + else + __raw_writel(1 << (lut_num - 32), + EPDC_IRQ_MASK2_CLEAR); + } + } +} + +static inline void epdc_working_buf_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_clear_working_buf_irq(void) +{ + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ | EPDC_IRQ_LUT_COL_IRQ, + EPDC_IRQ_CLEAR); +} + +static inline void epdc_upd_done_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_UPD_DONE_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_UPD_DONE_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_eof_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_clear_eof_irq(void) +{ + __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_CLEAR); +} + +static inline bool epdc_signal_eof(void) +{ + return (__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ) + & EPDC_IRQ_FRAME_END_IRQ) ? true : false; +} + +static inline void epdc_set_temp(u32 temp) +{ + __raw_writel(temp, EPDC_TEMP); +} + +static inline void epdc_set_screen_res(u32 width, u32 height) +{ + u32 val = (height << EPDC_RES_VERTICAL_OFFSET) | width; + __raw_writel(val, EPDC_RES); +} + +static inline void epdc_set_update_addr(u32 addr) +{ + __raw_writel(addr, EPDC_UPD_ADDR); +} + +static inline void epdc_set_update_coord(u32 x, u32 y) +{ + DBG_MSG("%s(),x=%hd,y=%hd\n",__FUNCTION__,x,y); + u32 val = (y << EPDC_UPD_CORD_YCORD_OFFSET) | x; + __raw_writel(val, EPDC_UPD_CORD); +} + +static inline void epdc_set_update_dimensions(u32 width, u32 height) +{ + DBG_MSG("%s(),w=%hd,h=%hd\n",__FUNCTION__,width,height); + u32 val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width; + __raw_writel(val, EPDC_UPD_SIZE); +} + +static void epdc_set_update_waveform(struct mxcfb_waveform_modes *wv_modes) +{ + u32 val; + + /* Configure the auto-waveform look-up table based on waveform modes */ + + /* Entry 1 = DU, 2 = GC4, 3 = GC8, etc. */ + val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (0 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (1 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc4 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (2 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc8 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (3 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc16 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (4 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); + val = (wv_modes->mode_gc32 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (5 << EPDC_AUTOWV_LUT_ADDR_OFFSET); + __raw_writel(val, EPDC_AUTOWV_LUT); +} + +static void epdc_set_update_stride(u32 stride) +{ + __raw_writel(stride, EPDC_UPD_STRIDE); +} + +static void epdc_submit_update(u32 lut_num, u32 waveform_mode, u32 update_mode, + bool use_dry_run, bool update_pause, + bool use_test_mode, u32 np_val) +{ + volatile static int giLast_waveform_mode=-1; + u32 reg_val = 0; + + + if(giLast_waveform_mode!=waveform_mode) { + if( g_fb_data->wv_modes.mode_a2==waveform_mode && + g_fb_data->wv_modes.mode_du!=giLast_waveform_mode) + { + waveform_mode=g_fb_data->wv_modes.mode_du; + DBG_MSG("%s():waveform mode has been force chage to DU before A2\n",__FUNCTION__); + } + + DBG_MSG("%s(%d):%s(),lut=%d,wf_mode=%d,last_wf_mode=%d,upd_mode=%d,test=%d,np_val=%d\n", + __FILE__,__LINE__,__FUNCTION__,lut_num,waveform_mode,giLast_waveform_mode,update_mode,use_test_mode,np_val); + } + else + DBG_MSG("%s(%d):%s(),lut=%d,wf_mode=%d,last_wf_mode=%d,upd_mode=%d,test=%d,np_val=%d\n", + __FILE__,__LINE__,__FUNCTION__,lut_num,waveform_mode,giLast_waveform_mode,update_mode,use_test_mode,np_val); + + if (use_test_mode) { + reg_val |= + ((np_val << EPDC_UPD_FIXED_FIXNP_OFFSET) & + EPDC_UPD_FIXED_FIXNP_MASK) | EPDC_UPD_FIXED_FIXNP_EN; + + __raw_writel(reg_val, EPDC_UPD_FIXED); + + reg_val = EPDC_UPD_CTRL_USE_FIXED; + } else { + __raw_writel(reg_val, EPDC_UPD_FIXED); + } + + if (waveform_mode == WAVEFORM_MODE_AUTO) { + // disable autowv mode + reg_val |= ((g_fb_data->wv_modes.mode_gc16 << + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) & + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK); + //reg_val |= EPDC_UPD_CTRL_AUTOWV; + } + else { + reg_val |= ((waveform_mode << + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) & + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK); + } + + if (update_pause) + reg_val |= EPDC_UPD_CTRL_AUTOWV_PAUSE; + + reg_val |= (use_dry_run ? EPDC_UPD_CTRL_DRY_RUN : 0) | + ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) & + EPDC_UPD_CTRL_LUT_SEL_MASK) | + update_mode; + + __raw_writel(reg_val, EPDC_UPD_CTRL); + + giLast_waveform_mode = waveform_mode; + +} + +static void epdc_submit_paused_update(void) +{ + u32 reg_val = __raw_readl(EPDC_UPD_CTRL); + reg_val &= ~EPDC_UPD_CTRL_AUTOWV_PAUSE; + __raw_writel(reg_val, EPDC_UPD_CTRL); +} + +static inline bool epdc_is_lut_complete(int rev, u32 lut_num) +{ + u32 val; + bool is_compl; + if (rev < 20) { + val = __raw_readl(EPDC_IRQ); + is_compl = val & (1 << lut_num) ? true : false; + } else if (lut_num < 32) { + val = __raw_readl(EPDC_IRQ1); + is_compl = val & (1 << lut_num) ? true : false; + } else { + val = __raw_readl(EPDC_IRQ2); + is_compl = val & (1 << (lut_num - 32)) ? true : false; + } + + return is_compl; +} + +static inline void epdc_clear_lut_complete_irq(int rev, u32 lut_num) +{ + if (rev < 20) + __raw_writel(1 << lut_num, EPDC_IRQ_CLEAR); + else if (lut_num < 32) + __raw_writel(1 << lut_num, EPDC_IRQ1_CLEAR); + else + __raw_writel(1 << (lut_num - 32), EPDC_IRQ2_CLEAR); +} + +static inline bool epdc_is_lut_active(u32 lut_num) +{ + u32 val; + bool is_active; + + if (lut_num < 32) { + val = __raw_readl(EPDC_STATUS_LUTS); + is_active = val & (1 << lut_num) ? true : false; + } else { + val = __raw_readl(EPDC_STATUS_LUTS2); + is_active = val & (1 << (lut_num - 32)) ? true : false; + } + + return is_active; +} + +static inline bool epdc_any_luts_active(int rev) +{ + bool any_active; + + if (rev < 20) + any_active = __raw_readl(EPDC_STATUS_LUTS) ? true : false; + else + any_active = (__raw_readl(EPDC_STATUS_LUTS) | + __raw_readl(EPDC_STATUS_LUTS2)) ? true : false; + + return any_active; +} + +static inline bool epdc_any_luts_available(void) +{ + bool luts_available = + (__raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false; + return luts_available; +} + +static inline int epdc_get_next_lut(void) +{ + u32 val = + __raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK; + return val; +} + +static int epdc_choose_next_lut(int rev, int *next_lut) +{ + u64 luts_status, unprocessed_luts; + bool next_lut_found = false; + u32 format_p5n = ((__raw_readl(EPDC_FORMAT) & EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK) == + EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N); + + luts_status = __raw_readl(EPDC_STATUS_LUTS); + if (rev < 20 || format_p5n) + luts_status &= 0xFFFF; + else + luts_status |= ((u64)__raw_readl(EPDC_STATUS_LUTS2) << 32); + + if (rev < 20) { + unprocessed_luts = __raw_readl(EPDC_IRQ) & 0xFFFF; + } else { + unprocessed_luts = __raw_readl(EPDC_IRQ1) | + ((u64)__raw_readl(EPDC_IRQ2) << 32); + if (format_p5n) + unprocessed_luts &= 0xFFFF; + } + + while (!next_lut_found) { + /* + * Selecting a LUT to minimize incidence of TCE Underrun Error + * -------------------------------------------------------- + * We want to find the lowest order LUT that is of greater + * order than all other active LUTs. If highest order LUT + * is active, then we want to choose the lowest order + * available LUT. + * + * NOTE: For EPDC version 2.0 and later, TCE Underrun error + * bug is fixed, so it doesn't matter which LUT is used. + */ + *next_lut = fls64(luts_status); + + if (rev < 20 || format_p5n) { + if (*next_lut > 15) + *next_lut = ffz(luts_status); + } else { + if (*next_lut > 63) { + *next_lut = ffz((u32)luts_status); + if (*next_lut == -1) + *next_lut = + ffz((u32)(luts_status >> 32)) + 32; + } + } + + /* + * Note on unprocessed_luts: There is a race condition + * where a LUT completes, but has not been processed by + * IRQ handler workqueue, and then a new update request + * attempts to use that LUT. We prevent that here by + * ensuring that the LUT we choose doesn't have its IRQ + * bit set (indicating it has completed but not yet been + * processed). + */ + if ((1 << *next_lut) & unprocessed_luts) + luts_status |= (1 << *next_lut); + else + next_lut_found = true; + } + + if (luts_status & 0x8000) + return 1; + else + return 0; +} + +static inline bool epdc_is_working_buffer_busy(void) +{ + u32 val = __raw_readl(EPDC_STATUS); + bool is_busy = (val & EPDC_STATUS_WB_BUSY) ? true : false; + + return is_busy; +} + +static inline bool epdc_is_working_buffer_complete(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + bool is_compl = (val & EPDC_IRQ_WB_CMPLT_IRQ) ? true : false; + + return is_compl; +} + +static inline bool epdc_is_lut_cancelled(void) +{ + u32 val = __raw_readl(EPDC_STATUS); + bool is_void = (val & EPDC_STATUS_UPD_VOID) ? true : false; + + return is_void; +} + +static inline bool epdc_is_collision(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false; +} + +static inline u64 epdc_get_colliding_luts(int rev) +{ + u64 val = (u64)(__raw_readl(EPDC_STATUS_COL)); + if (rev >= 20) + val |= (u64)__raw_readl(EPDC_STATUS_COL2) << 32; + return val; +} + +static void epdc_set_horizontal_timing(u32 horiz_start, u32 horiz_end, + u32 hsync_width, u32 hsync_line_length) +{ + u32 reg_val = + ((hsync_width << EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK) + | ((hsync_line_length << EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN1); + + reg_val = + ((horiz_start << EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET) & + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK) + | ((horiz_end << EPDC_TCE_HSCAN2_LINE_END_OFFSET) & + EPDC_TCE_HSCAN2_LINE_END_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN2); +} + +static void epdc_set_vertical_timing(u32 vert_start, u32 vert_end, + u32 vsync_width) +{ + u32 reg_val = + ((vert_start << EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET) & + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK) + | ((vert_end << EPDC_TCE_VSCAN_FRAME_END_OFFSET) & + EPDC_TCE_VSCAN_FRAME_END_MASK) + | ((vsync_width << EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET) & + EPDC_TCE_VSCAN_FRAME_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_VSCAN); +} + +static void epdc_init_settings(struct mxc_epdc_fb_data *fb_data) +{ + struct imx_epdc_fb_mode *epdc_mode = fb_data->cur_mode; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 reg_val; + int num_ce; + int i; + + /* Enable clocks to access EPDC regs */ + clk_enable(fb_data->epdc_clk_axi); + clk_enable(fb_data->epdc_clk_pix); + + /* Reset */ + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_SET); + while (!(__raw_readl(EPDC_CTRL) & EPDC_CTRL_CLKGATE)) + ; + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_CLEAR); + + /* Enable clock gating (clear to enable) */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + while (__raw_readl(EPDC_CTRL) & (EPDC_CTRL_SFTRST | EPDC_CTRL_CLKGATE)) + ; + + /* EPDC_CTRL */ + reg_val = __raw_readl(EPDC_CTRL); + reg_val &= ~EPDC_CTRL_UPD_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP; + reg_val &= ~EPDC_CTRL_LUT_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP; + __raw_writel(reg_val, EPDC_CTRL_SET); + + /* EPDC_FORMAT - 2bit TFT and buf_pix_fmt Buf pixel format */ + reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT + | fb_data->buf_pix_fmt + | ((0x0 << EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET) & + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK); + __raw_writel(reg_val, EPDC_FORMAT); + + /* EPDC_FIFOCTRL (disabled) */ + reg_val = + ((100 << EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK) + | ((200 << EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK) + | ((100 << EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK); + __raw_writel(reg_val, EPDC_FIFOCTRL); + + /* EPDC_TEMP - Use default temp to get index */ + epdc_set_temp(fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP)); + /* EPDC_RES */ + epdc_set_screen_res(epdc_mode->vmode->xres, epdc_mode->vmode->yres); + + /* EPDC_AUTOWV_LUT */ + /* Initialize all auto-wavefrom look-up values to 2 - GC16 */ + for (i = 0; i < 8; i++) + __raw_writel((2 << EPDC_AUTOWV_LUT_DATA_OFFSET) | + (i << EPDC_AUTOWV_LUT_ADDR_OFFSET), EPDC_AUTOWV_LUT); + + DBG_MSG("hwcfg@(%p),display bus width=0x%02x\n",gptHWCFG,gptHWCFG->m_val.bDisplayBusWidth); + + switch(gptHWCFG->m_val.bDisplayBusWidth) + { + case 1: + // 16 bits . + // + + /* + * EPDC_TCE_CTRL + * VSCAN_HOLDOFF = 4 + * VCOM_MODE = MANUAL + * VCOM_VAL = 0 + * DDR_MODE = DISABLED + * LVDS_MODE_CE = DISABLED + * LVDS_MODE = DISABLED + * DUAL_SCAN = DISABLED + * SDDO_WIDTH = 16bit + * PIXELS_PER_SDCLK = 8 + */ + + DBG_MSG("16bits display bus width \n"); + reg_val = + ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) & + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK) + | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_8 + | EPDC_TCE_CTRL_SDDO_WIDTH_16BIT + ; + break; + + case 2: + // 8 bits ,mirror . + // + + DBG_MSG("8bits mirror display bus width \n"); + + reg_val = + ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) & + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK) + | EPDC_TCE_CTRL_SCAN_DIR_0_UP + | EPDC_TCE_CTRL_SCAN_DIR_1_UP + | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4 + //| EPDC_TCE_CTRL_SDDO_WIDTH_8BIT + ; + break; + + case 3: + // 16 bits ,mirror . + // + + DBG_MSG("16bits mirror display bus width \n"); + + reg_val = + ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) & + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK) + | EPDC_TCE_CTRL_SCAN_DIR_0_UP + // | EPDC_TCE_CTRL_SCAN_DIR_1_UP //@Sam 20140114 Marked for E60QC2 + | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_8 + | EPDC_TCE_CTRL_SDDO_WIDTH_16BIT + ; + break; + + default: + // normal 8 bits . + + DBG_MSG("8bits normal display bus width \n"); + /* + * EPDC_TCE_CTRL + * VSCAN_HOLDOFF = 4 + * VCOM_MODE = MANUAL + * VCOM_VAL = 0 + * DDR_MODE = DISABLED + * LVDS_MODE_CE = DISABLED + * LVDS_MODE = DISABLED + * DUAL_SCAN = DISABLED + * SDDO_WIDTH = 8bit + * PIXELS_PER_SDCLK = 4 + */ + reg_val = + ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) & + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK) + | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4; + break; + } + + __raw_writel(reg_val, EPDC_TCE_CTRL); + + +#if 1 + { + struct fb_videomode *vmode = fb_data->cur_mode->vmode; + //DBG_MSG("%s() : left_margin=%d,right_margin=%d,hsync_len=%d\n", + // __FUNCTION__,vmode->left_margin, + // vmode->right_margin,vmode->hsync_len); + /* EPDC_TCE_HSCAN */ + epdc_set_horizontal_timing(vmode->left_margin, + vmode->right_margin, + vmode->hsync_len, + vmode->hsync_len); + + //DBG_MSG("%s() : upper_margin=%d,lower_margin=%d,vsync_len=%d\n", + // __FUNCTION__,vmode->upper_margin, + // vmode->lower_margin,vmode->vsync_len); + /* EPDC_TCE_VSCAN */ + epdc_set_vertical_timing(vmode->upper_margin, + vmode->lower_margin, + vmode->vsync_len); + } +#else + DBG_MSG("%s() : left_margin=%d,right_margin=%d,hsync_len=%d\n", + __FUNCTION__,screeninfo->left_margin, + screeninfo->right_margin,screeninfo->hsync_len); + + /* EPDC_TCE_HSCAN */ + epdc_set_horizontal_timing(screeninfo->left_margin, + screeninfo->right_margin, + screeninfo->hsync_len, + screeninfo->hsync_len); + + DBG_MSG("%s() : upper_margin=%d,lower_margin=%d,vsync_len=%d\n", + __FUNCTION__,screeninfo->upper_margin, + screeninfo->lower_margin,screeninfo->vsync_len); + /* EPDC_TCE_VSCAN */ + epdc_set_vertical_timing(screeninfo->upper_margin, + screeninfo->lower_margin, + screeninfo->vsync_len); +#endif + + /* EPDC_TCE_OE */ + reg_val = + ((epdc_mode->sdoed_width << EPDC_TCE_OE_SDOED_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOED_WIDTH_MASK) + | ((epdc_mode->sdoed_delay << EPDC_TCE_OE_SDOED_DLY_OFFSET) & + EPDC_TCE_OE_SDOED_DLY_MASK) + | ((epdc_mode->sdoez_width << EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOEZ_WIDTH_MASK) + | ((epdc_mode->sdoez_delay << EPDC_TCE_OE_SDOEZ_DLY_OFFSET) & + EPDC_TCE_OE_SDOEZ_DLY_MASK); + __raw_writel(reg_val, EPDC_TCE_OE); + + /* EPDC_TCE_TIMING1 */ + __raw_writel(0x0, EPDC_TCE_TIMING1); + + /* EPDC_TCE_TIMING2 */ + reg_val = + ((epdc_mode->gdclk_hp_offs << EPDC_TCE_TIMING2_GDCLK_HP_OFFSET) & + EPDC_TCE_TIMING2_GDCLK_HP_MASK) + | ((epdc_mode->gdsp_offs << EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET) & + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING2); + + /* EPDC_TCE_TIMING3 */ + reg_val = + ((epdc_mode->gdoe_offs << EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK) + | ((epdc_mode->gdclk_offs << EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING3); + + /* + * EPDC_TCE_SDCFG + * SDCLK_HOLD = 1 + * SDSHR = 1 + * NUM_CE = 1 + * SDDO_REFORMAT = FLIP_PIXELS + * SDDO_INVERT = DISABLED + * PIXELS_PER_CE = display horizontal resolution + */ + num_ce = epdc_mode->num_ce; + if (num_ce == 0) + num_ce = 1; + reg_val = EPDC_TCE_SDCFG_SDCLK_HOLD | EPDC_TCE_SDCFG_SDSHR + | ((num_ce << EPDC_TCE_SDCFG_NUM_CE_OFFSET) & + EPDC_TCE_SDCFG_NUM_CE_MASK) + | EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS + | ((epdc_mode->vmode->xres/num_ce << EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET) & + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK); + __raw_writel(reg_val, EPDC_TCE_SDCFG); + + /* + * EPDC_TCE_GDCFG + * GDRL = 1 + * GDOE_MODE = 0; + * GDSP_MODE = 0; + */ + reg_val = EPDC_TCE_SDCFG_GDRL; + __raw_writel(reg_val, EPDC_TCE_GDCFG); + + /* + * EPDC_TCE_POLARITY + * SDCE_POL = ACTIVE LOW + * SDLE_POL = ACTIVE HIGH + * SDOE_POL = ACTIVE HIGH + * GDOE_POL = ACTIVE HIGH + * GDSP_POL = ACTIVE LOW + */ + reg_val = EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH; + __raw_writel(reg_val, EPDC_TCE_POLARITY); + + /* EPDC_IRQ_MASK */ + __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_MASK); + + /* + * EPDC_GPIO + * PWRCOM = ? + * PWRCTRL = ? + * BDR = ? + */ + reg_val = ((0 << EPDC_GPIO_PWRCTRL_OFFSET) & EPDC_GPIO_PWRCTRL_MASK) + | ((0 << EPDC_GPIO_BDR_OFFSET) & EPDC_GPIO_BDR_MASK); + __raw_writel(reg_val, EPDC_GPIO); + + __raw_writel(fb_data->waveform_buffer_phys, EPDC_WVADDR); + __raw_writel(fb_data->working_buffer_A_phys, EPDC_WB_ADDR); + __raw_writel(fb_data->working_buffer_A_phys, EPDC_WB_ADDR_TCE); + + /* + * init histogram registers according to the buffer pixel format + */ + if (fb_data->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N) { + __raw_writel(EPDC_HIST1_P4N_PARAM, EPDC_HIST1_PARAM); + __raw_writel(EPDC_HIST2_P4N_PARAM, EPDC_HIST2_PARAM); + __raw_writel(EPDC_HIST4_P4N_PARAM, EPDC_HIST4_PARAM); + __raw_writel(EPDC_HIST8_P4N_PARAM0, EPDC_HIST8_PARAM0); + __raw_writel(EPDC_HIST8_P4N_PARAM1, EPDC_HIST8_PARAM1); + __raw_writel(EPDC_HIST16_P4N_PARAM0, EPDC_HIST16_PARAM0); + __raw_writel(EPDC_HIST16_P4N_PARAM1, EPDC_HIST16_PARAM1); + __raw_writel(EPDC_HIST16_P4N_PARAM2, EPDC_HIST16_PARAM2); + __raw_writel(EPDC_HIST16_P4N_PARAM3, EPDC_HIST16_PARAM3); + } + else { + __raw_writel(EPDC_HIST1_P5N_PARAM, EPDC_HIST1_PARAM); + __raw_writel(EPDC_HIST2_P5N_PARAM, EPDC_HIST2_PARAM); + __raw_writel(EPDC_HIST4_P5N_PARAM, EPDC_HIST4_PARAM); + __raw_writel(EPDC_HIST8_P5N_PARAM0, EPDC_HIST8_PARAM0); + __raw_writel(EPDC_HIST8_P5N_PARAM1, EPDC_HIST8_PARAM1); + __raw_writel(EPDC_HIST16_P5N_PARAM0, EPDC_HIST16_PARAM0); + __raw_writel(EPDC_HIST16_P5N_PARAM1, EPDC_HIST16_PARAM1); + __raw_writel(EPDC_HIST16_P5N_PARAM2, EPDC_HIST16_PARAM2); + __raw_writel(EPDC_HIST16_P5N_PARAM3, EPDC_HIST16_PARAM3); + } + + /* Disable clock */ + clk_disable(fb_data->epdc_clk_axi); + clk_disable(fb_data->epdc_clk_pix); +} + +#include "mxc_epdc_fake_s1d13522.c" + +static void epdc_powerup(struct mxc_epdc_fb_data *fb_data) +{ + struct epd_vc_data vcd; + //int ret = 0; + + GALLEN_DBGLOCAL_BEGIN(); + + mutex_lock(&fb_data->power_mutex); + + /* + * If power down request is pending, clear + * powering_down to cancel the request. + */ + if (fb_data->powering_down) { + GALLEN_DBGLOCAL_RUNLOG(0); + fb_data->powering_down = false; + } + + if (fb_data->power_state == POWER_STATE_ON) { + mutex_unlock(&fb_data->power_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + dev_dbg(fb_data->dev, "EPDC Powerup\n"); + if (fb_data->wfm < 256) { + /* fetch and display the voltage control data for waveform mode 0, temp range 0 */ + fetch_Epdc_Pmic_Voltages(&vcd, fb_data, fb_data->wfm, fb_data->temp_index); + } + else + vcd.v5 = 0; + + + fb_data->updates_active = true; +#ifdef USE_BSP_PMIC //[ + + /* Enable the v3p3 regulator */ + ret = regulator_enable(fb_data->v3p3_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable V3P3 regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + msleep(1); +#else //][!USE_BSP_PMIC + + if(fb_data->waveform_vcd_buffer) { + DBG_MSG(" *** enabling the VCOM (%d,%d) ***\n", fb_data->wfm, fb_data->temp_index); + if (fb_data->wfm < 256) { + int vcom_uV, new_vcom_uV; + int v5_sign = 1; + int v5_offset = vcd.v5 & 0x7fff; + int vcom_mV; + int vcom_mV_offset; + int vcd_offset_uV; + + + //if(0x09==gbFPL_Platform) + if(36==gptHWCFG->m_val.bPCB||40==gptHWCFG->m_val.bPCB) + { + // V320 waveform . + vcom_mV_offset=-250; + } + else + { + vcom_mV_offset=0; + } + + /* get vcom offset value */ + if (vcd.v5 & 0x8000) { + v5_sign = -1; + } + + if(8==gptHWCFG->m_val.bDisplayCtrl) { + fp9928_vcom_get_cached(&vcom_mV); + } + else { + tps65185_vcom_get_cached(&vcom_mV); + } + vcom_uV=vcom_mV*1000; + + DBG_MSG("**** PMIC VCOM mV=%d,vcom_nominal=%d,v5_offset=%d,v5_sign=%d,vcom_mv_offset=%d *****\n", + vcom_mV,vcom_nominal,v5_offset,v5_sign,vcom_mV_offset); + + vcd_offset_uV = v5_offset * 3125 * v5_sign; + new_vcom_uV = vcom_nominal + vcd_offset_uV+(vcom_mV_offset*1000); + if(vcom_uV!=new_vcom_uV) { + //if ((new_vcom_uV >= -3050000) && ( new_vcom_uV <= -500000)) + if ((new_vcom_uV <= 0) && ( new_vcom_uV >= -5000000)) + { + printk("vcom %d000 ==> %d uV (vcd offset:0x%04x=%duV,force %d000 uV)\n", + vcom_mV, new_vcom_uV, vcd.v5,vcd_offset_uV,vcom_mV_offset); + + vcom_mV=new_vcom_uV/1000; + if(8==gptHWCFG->m_val.bDisplayCtrl) { + fp9928_vcom_set(vcom_mV,0); + } + else { + tps65185_vcom_set(vcom_mV,0); + } + } + else { + printk(" adjusted VCOM is out of range, %d uV (offset:0x%04x)\n", new_vcom_uV, vcd.v5); + } + } + + } + } + + + if(8==gptHWCFG->m_val.bDisplayCtrl) { + int iChk; + iChk = fp9928_output_power(1,0); + if(iChk<0) { + printk(KERN_ERR "%s(%d):[warning] output power from FP9928 fail ,errno=%d !\n", + __FILE__,__LINE__,iChk); + } + } + else + { + int iChk; + unsigned long dwTPS65185_mode = TPS65185_MODE_ACTIVE; + + iChk = tps65185_chg_mode(&dwTPS65185_mode,1); + if(iChk<0) { + printk(KERN_ERR "%s(%d):[warning] change to power active fail,errno=%d !\n", + __FILE__,__LINE__,iChk); + } + } + +#endif //] USE_BSP_PMIC + + k_set_temperature(&fb_data->info); + + /* Enable pins used by EPDC */ + if (fb_data->pdata->enable_pins) { + GALLEN_DBGLOCAL_RUNLOG(1); + fb_data->pdata->enable_pins(); + } + + /* Enable clocks to EPDC */ + clk_enable(fb_data->epdc_clk_axi); + clk_enable(fb_data->epdc_clk_pix); + + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + +#ifdef USE_BSP_PMIC //[ + printk(" *** enabling the EPDC PMIC (%d,%d) ***\n",fb_data->wfm, fb_data->temp_index); + + /* Enable power to the EPD panel */ + ret = regulator_enable(fb_data->display_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable DISPLAY regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + printk(" *** enabling the VCOM (%d,%d) ***\n", fb_data->wfm, fb_data->temp_index); + if (fb_data->wfm < 256) { + int vcom_uV, new_vcom_uV; + int v5_sign = 1; + int v5_offset = vcd.v5 & 0x7fff; + + /* get vcom offset value */ + if (vcd.v5 & 0x8000) { + v5_sign = -1; + } + vcom_uV = regulator_get_voltage(fb_data->vcom_regulator); + new_vcom_uV = vcom_nominal + v5_offset * 3125 * v5_sign; + if ((new_vcom_uV >= -3050000) && ( new_vcom_uV <= -500000)) { + printk(" current vcom is %d uV, adjusted VCOM will be %d uV (offset:0x%04x)\n", vcom_uV, new_vcom_uV, vcd.v5); + regulator_set_voltage(fb_data->vcom_regulator, new_vcom_uV, new_vcom_uV); + } + printk(" adjusted VCOM is out of range, %d uV (offset:0x%04x)\n", new_vcom_uV, vcd.v5); + } + ret = regulator_enable(fb_data->vcom_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable VCOM regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } +#endif //] USE_BSP_PMIC + + fb_data->power_state = POWER_STATE_ON; + + mutex_unlock(&fb_data->power_mutex); + + GALLEN_DBGLOCAL_END(); +} + +static void epdc_powerdown(struct mxc_epdc_fb_data *fb_data) +{ + GALLEN_DBGLOCAL_BEGIN(); + mutex_lock(&fb_data->power_mutex); + + /* If powering_down has been cleared, a powerup + * request is pre-empting this powerdown request. + */ + if (!fb_data->powering_down + || (fb_data->power_state == POWER_STATE_OFF)) { + mutex_unlock(&fb_data->power_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + dev_dbg(fb_data->dev, "EPDC Powerdown\n"); + +#ifdef USE_BSP_PMIC //[ + + /* Disable power to the EPD panel */ + regulator_disable(fb_data->vcom_regulator); + regulator_disable(fb_data->display_regulator); +#else //][!USB_BSP_PMIC + +#endif //] USB_BSP_PMIC + + /* Disable clocks to EPDC */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); + clk_disable(fb_data->epdc_clk_pix); + clk_disable(fb_data->epdc_clk_axi); + + /* Disable pins used by EPDC (to prevent leakage current) */ + if (fb_data->pdata->disable_pins) { + GALLEN_DBGLOCAL_RUNLOG(0); + fb_data->pdata->disable_pins(); + } + +#ifdef DO_NOT_POWEROFF +#else//][!DO_NOT_POWEROFF +#ifdef USE_BSP_PMIC //[ + /* turn off the V3p3 */ + regulator_disable(fb_data->v3p3_regulator); +#else //][!USE_BSP_PMIC + if(8==gptHWCFG->m_val.bDisplayCtrl) { + int iChk; + iChk = fp9928_output_power(0,1); + if(iChk<0) { + printk(KERN_ERR "%s(%d):[warning] disable power from FP9928 fail ,errno=%d !\n", + __FILE__,__LINE__,iChk); + } + } + else + { + int iChk; + unsigned long dwTPS65185_mode = TPS65185_MODE_STANDBY; + + GALLEN_DBGLOCAL_RUNLOG(1); + iChk = tps65185_chg_mode(&dwTPS65185_mode,1); + if(iChk<0) { + printk(KERN_ERR "%s(%d):[warning] change to power sleep fail ,errno=%d !\n", + __FILE__,__LINE__,iChk); + } + + } +#endif //] USE_BSP_PMIC +#endif //] DO_NOT_POWEROFF + + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + + if (fb_data->wait_for_powerdown) { + GALLEN_DBGLOCAL_RUNLOG(2); + fb_data->wait_for_powerdown = false; + complete(&fb_data->powerdown_compl); + } + + fb_data->updates_active = false;// gallen add 20130116. + mutex_unlock(&fb_data->power_mutex); + GALLEN_DBGLOCAL_END(); +} + + +static void epdc_init_sequence(struct mxc_epdc_fb_data *fb_data) +{ + /* Initialize EPDC, passing pointer to EPDC registers */ + epdc_init_settings(fb_data); + fb_data->in_init = true; + +#if 0 + epdc_powerup(fb_data); + draw_mode0(fb_data); + /* Force power down event */ + fb_data->powering_down = true; + epdc_powerdown(fb_data); +#endif + + fb_data->updates_active = false; +} + +static int mxc_epdc_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + u32 mem_start,mem_len; + + GALLEN_DBGLOCAL_BEGIN(); + +#ifdef CONFIG_ANDROID//[ +#else//][!CONFIG_ANDROID + fake_s1d13522_progress_stop(); + k_fake_s1d13522_wait_inited(); +#endif //]CONFIG_ANDROID + + if(0==gptHWCFG->m_val.bUIStyle) { + GALLEN_DBGLOCAL_RUNLOG(0); + //mem_start = (u32)gpbLOGO_paddr; + //mem_len = (u32)((2048*1024*1024)-1024); + mem_start = info->fix.smem_start+info->fix.smem_len; + mem_len = info->fix.smem_len; + + } + else { + GALLEN_DBGLOCAL_RUNLOG(1); + mem_start = info->fix.smem_start; + mem_len = info->fix.smem_len; + } + + GALLEN_DBGLOCAL_PRINTMSG("mem_start=0x%x,mem_len=0x%x\n",\ + mem_start,mem_len); + GALLEN_DBGLOCAL_PRINTMSG("vma.vm_start=0x%x,vma.vm_end=0x%x\n",\ + vma->vm_start,vma->vm_end); + GALLEN_DBGLOCAL_PRINTMSG("offset=0x%x\n",\ + offset); + + + if (offset < mem_len) { + /* mapping framebuffer memory */ + GALLEN_DBGLOCAL_RUNLOG(2); + len = mem_len - offset; + vma->vm_pgoff = (mem_start + offset) >> PAGE_SHIFT; + } + else + { + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) { + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + GALLEN_DBGLOCAL_ESC(); + dev_dbg(info->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + GALLEN_DBGLOCAL_END(); + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxc_epdc_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, struct fb_info *info) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = _chan_to_field(red, &info->var.red); + val |= _chan_to_field(green, &info->var.green); + val |= _chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +static int mxc_epdc_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + int count, index, r; + u16 *red, *green, *blue, *transp; + u16 trans = 0xffff; + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int i; + + GALLEN_DBGLOCAL_BEGIN(); + + dev_dbg(fb_data->dev, "setcmap\n"); + + if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) { + /* Only support an 8-bit, 256 entry lookup */ + if (cmap->len != 256) { + GALLEN_DBGLOCAL_ESC(); + return 1; + } + + mxc_epdc_fb_flush_updates(fb_data); + + mutex_lock(&fb_data->pxp_mutex); + /* + * Store colormap in pxp_conf structure for later transmit + * to PxP during update process to convert gray pixels. + * + * Since red=blue=green for pseudocolor visuals, we can + * just use red values. + */ + for (i = 0; i < 256; i++) + fb_data->pxp_conf.proc_data.lut_map[i] = cmap->red[i] & 0xFF; + + fb_data->pxp_conf.proc_data.lut_map_updated = true; + + mutex_unlock(&fb_data->pxp_mutex); + } else { + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + index = cmap->start; + + for (count = 0; count < cmap->len; count++) { + if (transp) + trans = *transp++; + r = mxc_epdc_fb_setcolreg(index++, *red++, *green++, *blue++, + trans, info); + if (r != 0) + return r; + } + } + + GALLEN_DBGLOCAL_END(); + return 0; +} + +static void adjust_coordinates(u32 xres, u32 yres, u32 rotation, + struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region) +{ + u32 temp; + + /* If adj_update_region == NULL, pass result back in update_region */ + /* If adj_update_region == valid, use it to pass back result */ + if (adj_update_region) + switch (rotation) { + case FB_ROTATE_UR: + adj_update_region->top = update_region->top; + adj_update_region->left = update_region->left; + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + break; + case FB_ROTATE_CW: + adj_update_region->top = update_region->left; + if (yres < (update_region->top + update_region->height)) + adj_update_region->left = 0; + else + adj_update_region->left = yres - + (update_region->top + update_region->height); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + case FB_ROTATE_UD: + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + if (yres < (update_region->top + update_region->height)) + adj_update_region->top = 0; + else + adj_update_region->top = yres - + (update_region->top + update_region->height); + if (xres < (update_region->left + update_region->width)) + adj_update_region->left = 0; + else + adj_update_region->left = xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + adj_update_region->left = update_region->top; + if (xres < (update_region->left + update_region->width)) + adj_update_region->top = 0; + else + adj_update_region->top = xres - + (update_region->left + update_region->width); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + } + else + switch (rotation) { + case FB_ROTATE_UR: + /* No adjustment needed */ + break; + case FB_ROTATE_CW: + temp = update_region->top; + update_region->top = update_region->left; + if (yres < (temp + update_region->height)) + update_region->left = 0; + else + update_region->left = yres - + (temp + update_region->height); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + case FB_ROTATE_UD: + if (yres < (update_region->top + update_region->height)) + update_region->top = 0; + else + update_region->top = yres - + (update_region->top + update_region->height); + if (xres < (update_region->left + update_region->width)) + update_region->left = 0; + else + update_region->left = xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + temp = update_region->left; + update_region->left = update_region->top; + if (xres < (temp + update_region->width)) + update_region->top = 0; + else + update_region->top = xres - + (temp + update_region->width); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + } +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxc_epdc_fb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + if (var->grayscale) + fix->visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + else + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + * + */ +static int mxc_epdc_fb_set_par(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &pxp_conf->proc_data; + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + struct imx_epdc_fb_mode *epdc_modes = fb_data->pdata->epdc_mode; + int i; + int ret; + __u32 xoffset_old, yoffset_old; + + GALLEN_DBGLOCAL_BEGIN(); + + /* + * Can't change the FB parameters until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_epdc_fb_flush_updates(fb_data); + + mutex_lock(&fb_data->queue_mutex); + /* + * Set all screeninfo except for xoffset/yoffset + * Subsequent call to pan_display will handle those. + */ + xoffset_old = fb_data->epdc_fb_var.xoffset; + yoffset_old = fb_data->epdc_fb_var.yoffset; + fb_data->epdc_fb_var = *screeninfo; + fb_data->epdc_fb_var.xoffset = xoffset_old; + fb_data->epdc_fb_var.yoffset = yoffset_old; + mutex_unlock(&fb_data->queue_mutex); + + mutex_lock(&fb_data->pxp_mutex); + + /* + * Update PxP config data (used to process FB regions for updates) + * based on FB info and processing tasks required + */ + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = screeninfo->xres; + proc_data->drect.height = proc_data->srect.height = screeninfo->yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = screeninfo->rotate; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + + /* + * configure S0 channel parameters + * Parameters should match FB format/width/height + */ + if (screeninfo->grayscale) { + GALLEN_DBGLOCAL_RUNLOG(0); + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_GREY; + } + else { + GALLEN_DBGLOCAL_RUNLOG(1); + switch (screeninfo->bits_per_pixel) { + case 16: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + case 24: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB24; + break; + case 32: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB32; + break; + default: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + } + } + pxp_conf->s0_param.width = screeninfo->xres_virtual; + pxp_conf->s0_param.height = screeninfo->yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = screeninfo->xres; + pxp_conf->out_param.height = screeninfo->yres; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; + + mutex_unlock(&fb_data->pxp_mutex); + + /* + * If HW not yet initialized, check to see if we are being sent + * an initialization request. + */ + if (!fb_data->hw_ready) { + struct fb_videomode mode; + struct fb_videomode cur_mode; + bool found_match = false; + u32 xres_temp; + + fb_var_to_videomode(&mode, screeninfo); + + /* When comparing requested fb mode, + we need to use unrotated dimensions */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres_temp = mode.xres; + mode.xres = mode.yres; + mode.yres = xres_temp; + } + + if(fb_data->cur_mode->vmode) { + cur_mode = *(fb_data->cur_mode->vmode); + cur_mode.pixclock = 1000000000/(fb_data->cur_mode->vmode->pixclock/1000); + } + + /* if cur_mode resolution is correct, do not try to match it */ + //if (fb_mode_is_equal(fb_data->cur_mode->vmode, &mode)) + if (fb_mode_is_equal(&cur_mode, &mode)) + found_match = true; + + /* Match videomode against epdc modes */ + else + for (i = 0; i < fb_data->pdata->num_modes; i++) { + //if (!fb_mode_is_equal(epdc_modes[i].vmode, &mode)) + cur_mode = *(epdc_modes[i].vmode); + cur_mode.pixclock = 1000000000/(fb_data->cur_mode->vmode->pixclock/1000); + if(!fb_mode_is_equal(&cur_mode, &mode)) + continue; + fb_data->cur_mode = &epdc_modes[i]; + found_match = true; + break; + } + + if (!found_match) { + dev_err(fb_data->dev, + "Failed to match requested video mode\n"); + GALLEN_DBGLOCAL_ESC(); + return EINVAL; + } + + /* Found a match - Grab timing params */ + screeninfo->left_margin = mode.left_margin; + screeninfo->right_margin = mode.right_margin; + screeninfo->upper_margin = mode.upper_margin; + screeninfo->lower_margin = mode.lower_margin; + screeninfo->hsync_len = mode.hsync_len; + screeninfo->vsync_len = mode.vsync_len; + + fb_data->hw_initializing = true; + + /* Initialize EPDC settings and init panel */ + ret = + mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(fb_data->dev, + "Failed to load panel waveform data\n"); + GALLEN_DBGLOCAL_ESC(); + return ret; + } + } + + /* + * EOF sync delay (in us) should be equal to the vscan holdoff time + * VSCAN_HOLDOFF time = (VSCAN_HOLDOFF value + 1) * Vertical lines + * Add 25us for additional margin + */ + fb_data->eof_sync_period = (fb_data->cur_mode->vscan_holdoff + 1) * + 1000000/(fb_data->cur_mode->vmode->refresh * + (fb_data->cur_mode->vmode->upper_margin + + fb_data->cur_mode->vmode->yres + + fb_data->cur_mode->vmode->lower_margin + + fb_data->cur_mode->vmode->vsync_len)) + 25; + + mxc_epdc_fb_set_fix(info); + GALLEN_DBGLOCAL_END(); + return 0; +} + +static int mxc_epdc_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + GALLEN_DBGLOCAL_BEGIN(); + + if (!var->xres) { + GALLEN_DBGLOCAL_RUNLOG(0); + var->xres = 1; + } + if (!var->yres) { + GALLEN_DBGLOCAL_RUNLOG(1); + var->yres = 1; + } + + if (var->xres_virtual < var->xoffset + var->xres) { + GALLEN_DBGLOCAL_RUNLOG(2); + var->xres_virtual = var->xoffset + var->xres; + } + if (var->yres_virtual < var->yoffset + var->yres) { + GALLEN_DBGLOCAL_RUNLOG(3); + var->yres_virtual = var->yoffset + var->yres; + } + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8)) { + GALLEN_DBGLOCAL_RUNLOG(4); + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 8: + GALLEN_DBGLOCAL_RUNLOG(5); + if (var->grayscale != 0) { + GALLEN_DBGLOCAL_RUNLOG(6); + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var->red.length = 8; + var->red.offset = 0; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 0; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } else { + GALLEN_DBGLOCAL_RUNLOG(7); + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } + break; + case 16: + GALLEN_DBGLOCAL_RUNLOG(8); + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + GALLEN_DBGLOCAL_RUNLOG(9); + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + GALLEN_DBGLOCAL_RUNLOG(10); + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + switch (var->rotate) { + case FB_ROTATE_UR: + case FB_ROTATE_UD: + GALLEN_DBGLOCAL_RUNLOG(11); + var->xres = fb_data->native_width; + var->yres = fb_data->native_height; + break; + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + GALLEN_DBGLOCAL_RUNLOG(12); + var->xres = fb_data->native_height; + var->yres = fb_data->native_width; + break; + default: + GALLEN_DBGLOCAL_RUNLOG(13); + /* Invalid rotation value */ + var->rotate = 0; + dev_dbg(fb_data->dev, "Invalid rotation request\n"); + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + + var->xres_virtual = ALIGN(var->xres, 32); + var->yres_virtual = ALIGN(var->yres, 128) * fb_data->num_screens; + + epdfbdc_set_width_height(gptDC,ALIGN(var->xres, 32),ALIGN(var->yres, 128),var->xres,var->yres); + + var->height = -1; + var->width = -1; + + GALLEN_DBGLOCAL_END(); + return 0; +} + +void mxc_epdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + + WARNING_MSG("%s(%d):%s() skip\n",__FILE__,__LINE__,__FUNCTION__); + return ; + + mutex_lock(&fb_data->queue_mutex); + + memcpy(&fb_data->wv_modes, modes, sizeof(struct mxcfb_waveform_modes)); + + /* Set flag to ensure that new waveform modes + * are programmed into EPDC before next update */ + fb_data->wv_modes_update = true; + + mutex_unlock(&fb_data->queue_mutex); +} +EXPORT_SYMBOL(mxc_epdc_fb_set_waveform_modes); + +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, int temp) +{ + int i; + int index = -1; + int iTotallyHit=0; + + if (fb_data->trt_entries == 0) { + dev_err(fb_data->dev, + "No TRT index match (%d)...using default temp index\n", + temp); + + return DEFAULT_TEMP_INDEX; + } + +#if 1 + /* Search temperature ranges for a match */ + for (i = 0; i < fb_data->trt_entries; i++) { + if (temp >= fb_data->temp_range_bounds[i]) + { + index = i; + if(temp < fb_data->temp_range_bounds[i+1]) { + iTotallyHit = 1; + break; + } + } + } +#else + /* Search temperature ranges for a match */ + for (i = 0; i < fb_data->trt_entries; i++) { + if ((temp >= fb_data->temp_range_bounds[i]) + && (temp < fb_data->temp_range_bounds[i+1])) { + index = i; + iTotallyHit=1; + break; + } + } +#endif + + if (index < 0) { + dev_err(fb_data->dev, + "No TRT index (temp=%d) match,trt entries=%d...using default temp index,temp=%d\n", + temp,fb_data->trt_entries,fb_data->temp_range_bounds[0]); + return DEFAULT_TEMP_INDEX; + } + else { + if(1!=iTotallyHit) { + dev_err(fb_data->dev, + "closer TRT index (temp=%d) match,trt entries=%d...using temp index=%d,temp=%d~%d\n", + temp,fb_data->trt_entries,index,fb_data->temp_range_bounds[index],fb_data->temp_range_bounds[index+1]); + } + } + + dev_dbg(fb_data->dev, "Using temperature index %d\n", index); + + return index; +} + +int mxc_epdc_fb_set_temperature(int temperature, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + /* Store temp index. Used later when configuring updates. */ + mutex_lock(&fb_data->queue_mutex); + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, temperature); + mutex_unlock(&fb_data->queue_mutex); + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_temperature); + +int mxc_epdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + GALLEN_DBGLOCAL_BEGIN(); + dev_dbg(fb_data->dev, "Setting auto update mode to %d\n", auto_mode); + + if ((auto_mode == AUTO_UPDATE_MODE_AUTOMATIC_MODE) + || (auto_mode == AUTO_UPDATE_MODE_REGION_MODE)) { + GALLEN_DBGLOCAL_PRINTMSG("%s,auto_mode=%hd\n",__FUNCTION__,auto_mode); + fb_data->auto_mode = auto_mode; + } + else { + dev_err(fb_data->dev, "Invalid auto update mode parameter.\n"); + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + + GALLEN_DBGLOCAL_END(); + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_auto_update); + +int mxc_epdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + GALLEN_DBGLOCAL_BEGIN(); + + dev_dbg(fb_data->dev, "Setting optimization level to %d\n", upd_scheme); + + /* + * Can't change the scheme until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_epdc_fb_flush_updates(fb_data); + + if ((upd_scheme == UPDATE_SCHEME_SNAPSHOT) + || (upd_scheme == UPDATE_SCHEME_QUEUE) + || (upd_scheme == UPDATE_SCHEME_QUEUE_AND_MERGE)) { + GALLEN_DBGLOCAL_RUNLOG(0); + fb_data->upd_scheme = upd_scheme; + } + else { + dev_err(fb_data->dev, "Invalid update scheme specified.\n"); + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + + GALLEN_DBGLOCAL_END(); + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_upd_scheme); + +static void copy_before_process(struct mxc_epdc_fb_data *fb_data, + struct update_data_list *upd_data_list) +{ + struct mxcfb_update_data *upd_data = + &upd_data_list->update_desc->upd_data; + int i; + unsigned char *temp_buf_ptr = fb_data->virt_addr_copybuf; + unsigned char *src_ptr; + struct mxcfb_rect *src_upd_region; + int temp_buf_stride; + int src_stride; + int bpp = fb_data->epdc_fb_var.bits_per_pixel; + int left_offs, right_offs; + int x_trailing_bytes, y_trailing_bytes; + int alt_buf_offset; + + /* Set source buf pointer based on input source, panning, etc. */ + if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_upd_region = &upd_data->alt_buffer_data.alt_update_region; + src_stride = + upd_data->alt_buffer_data.width * bpp/8; + alt_buf_offset = upd_data->alt_buffer_data.phys_addr - + fb_data->info.fix.smem_start; + src_ptr = fb_data->info.screen_base + alt_buf_offset + + src_upd_region->top * src_stride; + } else { + src_upd_region = &upd_data->update_region; + src_stride = fb_data->epdc_fb_var.xres_virtual * bpp/8; + src_ptr = fb_data->info.screen_base + fb_data->fb_offset + + src_upd_region->top * src_stride; + } + + temp_buf_stride = ALIGN(src_upd_region->width, 8) * bpp/8; + left_offs = src_upd_region->left * bpp/8; + right_offs = src_upd_region->width * bpp/8; + x_trailing_bytes = (ALIGN(src_upd_region->width, 8) + - src_upd_region->width) * bpp/8; + + for (i = 0; i < src_upd_region->height; i++) { + /* Copy the full line */ + memcpy(temp_buf_ptr, src_ptr + left_offs, + src_upd_region->width * bpp/8); + + /* Clear any unwanted pixels at the end of each line */ + if (src_upd_region->width & 0x7) { + memset(temp_buf_ptr + right_offs, 0x0, + x_trailing_bytes); + } + + temp_buf_ptr += temp_buf_stride; + src_ptr += src_stride; + } + + /* Clear any unwanted pixels at the bottom of the end of each line */ + if (src_upd_region->height & 0x7) { + y_trailing_bytes = (ALIGN(src_upd_region->height, 8) + - src_upd_region->height) * + ALIGN(src_upd_region->width, 8) * bpp/8; + memset(temp_buf_ptr, 0x0, y_trailing_bytes); + } +} + +static int epdc_process_update(struct update_data_list *upd_data_list, + struct mxc_epdc_fb_data *fb_data) +{ + struct mxcfb_rect *src_upd_region; /* Region of src buffer for update */ + struct mxcfb_rect pxp_upd_region; + u32 src_width, src_height; + u32 offset_from_4, bytes_per_pixel; + u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; + u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; + u32 hist_stat = 0; + int width_unaligned, height_unaligned; + bool input_unaligned = false; + bool line_overflow = false; + int pix_per_line_added; + bool use_temp_buf = false; + struct mxcfb_rect temp_buf_upd_region; + struct update_desc_list *upd_desc_list = upd_data_list->update_desc; + + int ret; + + GALLEN_DBGLOCAL_BEGIN_EX2(32,giDbgLvl); + /* + * Gotta do a whole bunch of buffer ptr manipulation to + * work around HW restrictions for PxP & EPDC + * Note: Applies to pre-2.0 versions of EPDC/PxP + */ + + /* + * Are we using FB or an alternate (overlay) + * buffer for source of update? + */ + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_width = upd_desc_list->upd_data.alt_buffer_data.width; + src_height = upd_desc_list->upd_data.alt_buffer_data.height; + src_upd_region = &upd_desc_list->upd_data.alt_buffer_data.alt_update_region; + GALLEN_DBGLOCAL_RUNLOG(0); + } else { + src_width = fb_data->epdc_fb_var.xres_virtual; + src_height = fb_data->epdc_fb_var.yres; + src_upd_region = &upd_desc_list->upd_data.update_region; + GALLEN_DBGLOCAL_RUNLOG(1); + } + + bytes_per_pixel = fb_data->epdc_fb_var.bits_per_pixel/8; + + /* + * SW workaround for PxP limitation (for pre-v2.0 HW) + * + * There are 3 cases where we cannot process the update data + * directly from the input buffer: + * + * 1) PxP must process 8x8 pixel blocks, and all pixels in each block + * are considered for auto-waveform mode selection. If the + * update region is not 8x8 aligned, additional unwanted pixels + * will be considered in auto-waveform mode selection. + * + * 2) PxP input must be 32-bit aligned, so any update + * address not 32-bit aligned must be shifted to meet the + * 32-bit alignment. The PxP will thus end up processing pixels + * outside of the update region to satisfy this alignment restriction, + * which can affect auto-waveform mode selection. + * + * 3) If input fails 32-bit alignment, and the resulting expansion + * of the processed region would add at least 8 pixels more per + * line than the original update line width, the EPDC would + * cause screen artifacts by incorrectly handling the 8+ pixels + * at the end of each line. + * + * Workaround is to copy from source buffer into a temporary + * buffer, which we pad with zeros to match the 8x8 alignment + * requirement. This temp buffer becomes the input to the PxP. + */ + width_unaligned = src_upd_region->width & 0x7; + height_unaligned = src_upd_region->height & 0x7; + + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + + pix_per_line_added = (offset_from_4 * bytes_per_pixel % 4) + / bytes_per_pixel; + if ((((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || + fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) && + (ALIGN(src_upd_region->width, 8) < + ALIGN(src_upd_region->width + pix_per_line_added, 8))) { + GALLEN_DBGLOCAL_RUNLOG(2); + line_overflow = true; + } + + /* Grab pxp_mutex here so that we protect access + * to copybuf in addition to the PxP structures */ + mutex_lock(&fb_data->pxp_mutex); + + if (((((width_unaligned || height_unaligned || input_unaligned) && + (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) + || line_overflow) && (fb_data->rev < 20)) || + fb_data->restrict_width) { + + GALLEN_DBGLOCAL_RUNLOG(3); + dev_dbg(fb_data->dev, "Copying update before processing.\n"); + + /* Update to reflect what the new source buffer will be */ + src_width = ALIGN(src_upd_region->width, 8); + src_height = ALIGN(src_upd_region->height, 8); + + copy_before_process(fb_data, upd_data_list); + + /* + * src_upd_region should now describe + * the new update buffer attributes. + */ + temp_buf_upd_region.left = 0; + temp_buf_upd_region.top = 0; + temp_buf_upd_region.width = src_upd_region->width; + temp_buf_upd_region.height = src_upd_region->height; + src_upd_region = &temp_buf_upd_region; + + use_temp_buf = true; + } + + /* + * For pre-2.0 HW, input address must be 32-bit aligned + * Compute buffer offset to account for this PxP limitation + */ + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + if ((fb_data->rev < 20) && input_unaligned) { + GALLEN_DBGLOCAL_RUNLOG(4); + /* Leave a gap between PxP input addr and update region pixels */ + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel & 0xFFFFFFFC; + /* Update region left changes to reflect relative position to input ptr */ + pxp_upd_region.left = (offset_from_4 * bytes_per_pixel % 4) + / bytes_per_pixel; + } else { + GALLEN_DBGLOCAL_RUNLOG(5); + + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel; + pxp_upd_region.left = 0; + } + + pxp_upd_region.top = 0; + + /* + * For version 2.0 and later of EPDC & PxP, if no rotation, we don't + * need to align width & height (rotation always requires 8-pixel + * width & height alignment, per PxP limitations) + */ + if ((fb_data->epdc_fb_var.rotate == 0) && (fb_data->rev >= 20)) { + GALLEN_DBGLOCAL_RUNLOG(6); + pxp_upd_region.width = src_upd_region->width; + pxp_upd_region.height = src_upd_region->height; + } else { + GALLEN_DBGLOCAL_RUNLOG(7); + /* Update region dimensions to meet 8x8 pixel requirement */ + pxp_upd_region.width = ALIGN(src_upd_region->width + pxp_upd_region.left, 8); + pxp_upd_region.height = ALIGN(src_upd_region->height, 8); + } + + switch (fb_data->epdc_fb_var.rotate) { + case FB_ROTATE_UR: + default:GALLEN_DBGLOCAL_RUNLOG(8); + post_rotation_xcoord = pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.top; + width_pxp_blocks = pxp_upd_region.width; + break; + case FB_ROTATE_CW:GALLEN_DBGLOCAL_RUNLOG(9); + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = width_pxp_blocks - src_upd_region->height; + post_rotation_ycoord = pxp_upd_region.left; + break; + case FB_ROTATE_UD:GALLEN_DBGLOCAL_RUNLOG(10); + width_pxp_blocks = pxp_upd_region.width; + post_rotation_xcoord = width_pxp_blocks - src_upd_region->width - pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.height - src_upd_region->height - pxp_upd_region.top; + break; + case FB_ROTATE_CCW:GALLEN_DBGLOCAL_RUNLOG(11); + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = pxp_upd_region.top; + post_rotation_ycoord = pxp_upd_region.width - src_upd_region->width - pxp_upd_region.left; + break; + } + + /* Update region start coord to force PxP to process full 8x8 regions */ + pxp_upd_region.top &= ~0x7; + pxp_upd_region.left &= ~0x7; + + if (fb_data->rev < 20) { + GALLEN_DBGLOCAL_RUNLOG(12); + pxp_output_shift = ALIGN(post_rotation_xcoord, 8) + - post_rotation_xcoord; + + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + pxp_output_shift; + + upd_desc_list->epdc_offs = ALIGN(pxp_output_offs, 8); + } else { + GALLEN_DBGLOCAL_RUNLOG(13); + pxp_output_shift = 0; + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + post_rotation_xcoord; + + upd_desc_list->epdc_offs = pxp_output_offs; + } + + upd_desc_list->epdc_stride = width_pxp_blocks; + + /* Source address either comes from alternate buffer + provided in update data, or from the framebuffer. */ + if (use_temp_buf) { + + GALLEN_DBGLOCAL_RUNLOG(14); + sg_dma_address(&fb_data->sg[0]) = + fb_data->phys_addr_copybuf; + } + else if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { + + GALLEN_DBGLOCAL_RUNLOG(15); + sg_dma_address(&fb_data->sg[0]) = + upd_desc_list->upd_data.alt_buffer_data.phys_addr + + pxp_input_offs; + } + else { + + GALLEN_DBGLOCAL_RUNLOG(16); + sg_dma_address(&fb_data->sg[0]) = + fb_data->info.fix.smem_start + fb_data->fb_offset + + pxp_input_offs; + sg_set_page(&fb_data->sg[0], + virt_to_page(fb_data->info.screen_base), + fb_data->info.fix.smem_len, + offset_in_page(fb_data->info.screen_base)); + } + + /* Update sg[1] to point to output of PxP proc task */ + sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr + + pxp_output_shift; + sg_set_page(&fb_data->sg[1], virt_to_page(upd_data_list->virt_addr), + fb_data->max_pix_size, + offset_in_page(upd_data_list->virt_addr)); + + /* + * Set PxP LUT transform type based on update flags. + */ + fb_data->pxp_conf.proc_data.lut_transform = 0; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION) { + GALLEN_DBGLOCAL_RUNLOG(17); + fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT; + } +#if 0 + if (upd_desc_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME || + upd_desc_list->iDither_state ) +#else + if (upd_desc_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME) +#endif + { + GALLEN_DBGLOCAL_RUNLOG(18); + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_BLACK_WHITE; + } + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_CMAP) { + GALLEN_DBGLOCAL_RUNLOG(19); + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_USE_CMAP; + } + + /* + * Toggle inversion processing if 8-bit + * inverted is the current pixel format. + */ + if (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT_INVERTED) { + GALLEN_DBGLOCAL_RUNLOG(20); + fb_data->pxp_conf.proc_data.lut_transform ^= PXP_LUT_INVERT; + } + + /* Enable PxP LUT option to configure pixel data for AA processing */ + if (fb_data->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N) { + /* turn off PXP_LUT_AA if dithering flag is set */ + if ((upd_desc_list->upd_data.flags & EPDC_FLAG_USE_DITHERING_Y1) || (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_DITHERING_Y4)) { + fb_data->pxp_conf.proc_data.lut_transform &= ~PXP_LUT_AA; + } + else { + fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_AA; + } + } + + DBG_MSG("iDiterStat=%d,pxp lut tranform=0x%x\n", + upd_desc_list->iDither_state,fb_data->pxp_conf.proc_data.lut_transform); + flush_cache_all(); + outer_flush_all(); + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_process_update(fb_data, src_width, src_height, + &pxp_upd_region); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + GALLEN_DBGLOCAL_ESC(); + return ret; + } + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + GALLEN_DBGLOCAL_RUNLOG(21); + epdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &hist_stat); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + GALLEN_DBGLOCAL_ESC(); + return ret; + } + + mutex_unlock(&fb_data->pxp_mutex); + flush_cache_all(); + outer_flush_all(); + + /* Update waveform mode from PxP histogram results */ +#if 0 + if ((fb_data->rev <= 20) && + (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) +#else + + if ((upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) +#endif + { + GALLEN_DBGLOCAL_RUNLOG(22); + if (hist_stat & 0x1) { + GALLEN_DBGLOCAL_RUNLOG(23); + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_du; + } + else if (hist_stat & 0x2) { + GALLEN_DBGLOCAL_RUNLOG(24); + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc4; + } + else if (hist_stat & 0x4) { + GALLEN_DBGLOCAL_RUNLOG(25); + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc8; + } + else if (hist_stat & 0x8) { + GALLEN_DBGLOCAL_RUNLOG(26); +#if defined(NTX_AUTOMODE_PATCH) //[ waveform mode change automatically . + #if !defined(NO_AUTO_REAGL_MODE) //[ + if (fb_data->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N && + fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) + { + DBG_MSG("assigned aa mode,5bits wf,colors>8\n"); + + if(UPDATE_MODE_FULL==upd_desc_list->upd_data.update_mode) { + GALLEN_DBGLOCAL_RUNLOG(24); + upd_data_list->update_desc->is_aa = 1; + upd_data_list->update_desc->upd_data.flags |= EPDC_FLAG_USE_AAD; + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_aad; + } + else { + #if 0 + upd_data_list->update_desc->is_aa = 1; + upd_data_list->update_desc->upd_data.flags &= ~EPDC_FLAG_USE_AAD; + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_aa; + #else + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gl16; + #endif + } + } + else + #endif //] + { + if(UPDATE_MODE_FULL==upd_desc_list->upd_data.update_mode) { + GALLEN_DBGLOCAL_RUNLOG(28); + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; + } + else { + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gl16; + } + } +#else //][!NTX_AUTOMODE_PATCH + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; +#endif//] NTX_AUTOMODE_PATCH + + } + else { + GALLEN_DBGLOCAL_RUNLOG(27); + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc32; + } + + dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", + hist_stat, upd_desc_list->upd_data.waveform_mode); + } + + GALLEN_DBGLOCAL_END(); + return 0; +} + +static int epdc_submit_merge(struct update_desc_list *upd_desc_list, + struct update_desc_list *update_to_merge, + struct mxc_epdc_fb_data *fb_data) +{ + struct mxcfb_update_data *a, *b; + struct mxcfb_rect *arect, *brect; + struct mxcfb_rect combine; + bool use_flags = false; + + GALLEN_DBGLOCAL_BEGIN_EX2(32,giDbgLvl); + + a = &upd_desc_list->upd_data; + b = &update_to_merge->upd_data; + arect = &upd_desc_list->upd_data.update_region; + brect = &update_to_merge->upd_data.update_region; + + /* Do not merge a dry-run collision test update */ + if ((a->flags & EPDC_FLAG_TEST_COLLISION) || + (b->flags & EPDC_FLAG_TEST_COLLISION)) + { + GALLEN_DBGLOCAL_ESC(); + return MERGE_BLOCK; + } + + /* + * Updates with different flags must be executed sequentially. + * Halt the merge process to ensure this. + */ + if (a->flags != b->flags) { + GALLEN_DBGLOCAL_RUNLOG(0); + /* + * Special exception: if update regions are identical, + * we may be able to merge them. + */ + if ((arect->left != brect->left) || + (arect->top != brect->top) || + (arect->width != brect->width) || + (arect->height != brect->height)) + { + GALLEN_DBGLOCAL_ESC(); + return MERGE_BLOCK; + } + + use_flags = true; + } + + if (arect->left > (brect->left + brect->width) || + brect->left > (arect->left + arect->width) || + arect->top > (brect->top + brect->height) || + brect->top > (arect->top + arect->height)) + { + GALLEN_DBGLOCAL_ESC(); + return MERGE_FAIL; + } + + combine.left = arect->left < brect->left ? arect->left : brect->left; + combine.top = arect->top < brect->top ? arect->top : brect->top; + combine.width = (arect->left + arect->width) > + (brect->left + brect->width) ? + (arect->left + arect->width - combine.left) : + (brect->left + brect->width - combine.left); + combine.height = (arect->top + arect->height) > + (brect->top + brect->height) ? + (arect->top + arect->height - combine.top) : + (brect->top + brect->height - combine.top); + + DBG_MSG("%s():upd rect merge,a@%d(%d,%d,%d,%d)+b@%d(%d,%d,%d,%d)=>a(%d,%d,%d,%d)\n", + __FUNCTION__,(int)upd_desc_list->update_order,\ + arect->left,arect->top,arect->width,arect->height,\ + (int)update_to_merge->update_order, + brect->left,brect->top,brect->width,brect->height,\ + combine.left,combine.top,combine.width,combine.height); + + /* Don't merge if combined width exceeds max width */ + if (fb_data->restrict_width) { + u32 max_width = EPDC_V2_MAX_UPDATE_WIDTH; + u32 combined_width = combine.width; + + GALLEN_DBGLOCAL_RUNLOG(4); + + if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR) + { + GALLEN_DBGLOCAL_RUNLOG(5); + max_width -= EPDC_V2_ROTATION_ALIGNMENT; + } + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_CW) || + (fb_data->epdc_fb_var.rotate == FB_ROTATE_CCW)) + { + GALLEN_DBGLOCAL_RUNLOG(6); + combined_width = combine.height; + } + if (combined_width > max_width) + { + GALLEN_DBGLOCAL_ESC(); + return MERGE_FAIL; + } + } + + if (a->update_mode != b->update_mode) + { + GALLEN_DBGLOCAL_RUNLOG(1); + a->update_mode = UPDATE_MODE_FULL; + } + + if (a->waveform_mode != b->waveform_mode) + { + GALLEN_DBGLOCAL_RUNLOG(2); +#if 1 //[ + DBG_MSG("%s():wf mode merge %d->%d\n",__FUNCTION__,a->waveform_mode,fb_data->wv_modes.mode_gc16); + a->waveform_mode = fb_data->wv_modes.mode_gc16; +#else //][! + DBG_MSG("%s():wf mode merge %d->%d",__FUNCTION__,a->waveform_mode,WAVEFORM_MODE_AUTO); + a->waveform_mode = WAVEFORM_MODE_AUTO; +#endif //] + +#if defined(NO_AUTO_REAGL_MODE) //[ + upd_desc_list->is_aa=0; +#else //][ + if(fb_data->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N) { + GALLEN_DBGLOCAL_RUNLOG(8); + if( a->waveform_mode==fb_data->wv_modes.mode_aa|| + a->waveform_mode==fb_data->wv_modes.mode_aad ) + { + GALLEN_DBGLOCAL_RUNLOG(9); + upd_desc_list->is_aa=1; + } + else { + upd_desc_list->is_aa=0; + } + } + else { + GALLEN_DBGLOCAL_RUNLOG(10); + upd_desc_list->is_aa=0; + } + DBG_MSG("%s():is_aa=%d,wf_mode=%d\n",__FUNCTION__, + upd_desc_list->is_aa,a->waveform_mode); +#endif//] + } + + *arect = combine; + + /* Use flags of the later update */ + if (use_flags) + { + GALLEN_DBGLOCAL_RUNLOG(7); + DBG_MSG("%s():flag merge 0x%x->0x%x\n",__FUNCTION__,a->flags,b->flags); + a->flags = b->flags; + if( upd_desc_list->is_aa && + (a->waveform_mode==fb_data->wv_modes.mode_aad) && + (fb_data->wv_modes.mode_aad != fb_data->wv_modes.mode_aa)) + { + a->flags |= EPDC_FLAG_USE_AAD; + } + } + + + /* Merge markers */ + list_splice_tail(&update_to_merge->upd_marker_list, + &upd_desc_list->upd_marker_list); + + /* Merged update should take on the earliest order */ + upd_desc_list->update_order = + (upd_desc_list->update_order > update_to_merge->update_order) ? + upd_desc_list->update_order : update_to_merge->update_order; + + + GALLEN_DBGLOCAL_END(); + return MERGE_OK; +} + +#ifdef FW_IN_RAM //[ + +static void epdc_firmware_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_firmware_work); + struct firmware fw; + + + if(!delayed_work_pending(&fb_data->epdc_firmware_work)) { + + down(&fb_data->firmware_work_lock); + if(0==gbModeVersion) { + fw.size = gdwWF_size; + fw.data = (u8*)gpbWF_vaddr; + + printk("[%s]:fw p=%p,size=%u,fb_data@%p,pdata=%p\n",__FUNCTION__, + fw.data,fw.size,fb_data,fb_data->pdata); + mxc_epdc_fb_fw_handler(&fw,fb_data); + } + else { + printk(KERN_WARNING"%s skipped cause firmware bas been setup\n",__FUNCTION__); + } + up(&fb_data->firmware_work_lock); + } + else { + printk(KERN_WARNING"%s skipped cause firmware work is pending \n",__FUNCTION__); + } +} + +#endif //] FW_IN_RAM + +static void epdc_submit_work_func(struct work_struct *work) +{ + struct update_data_list *next_update, *temp_update; + struct update_desc_list *next_desc, *temp_desc; + struct update_marker_data *next_marker, *temp_marker; + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_submit_work); + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect adj_update_region, *upd_region; + bool end_merge = false; + bool is_transform; + u32 update_addr; + int *err_dist; + int ret; + struct mxcfb_rect actual_update_region; + int direct_draw = 0; + + GALLEN_DBGLOCAL_BEGIN_EX2(64,giDbgLvl); + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry_safe(next_update, temp_update, + &fb_data->upd_buf_collision_list, list) { + + if (next_update->collision_mask != 0) { + GALLEN_DBGLOCAL_RUNLOG(0); + continue; + } + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + +#ifdef AVOID_REAGL_DIRTY_TEMP//[ + /* Force waveform mode to gc15 for resubmitted collisions + * to avoid dirty issue since we use auto mode to enable reagl . + */ + next_update->update_desc->upd_data.waveform_mode = + g_fb_data->wv_modes.mode_gc16; +#else //][!AVOID_REAGL_DIRTY_TEMP + /* Force waveform mode to auto for resubmitted collisions */ + next_update->update_desc->upd_data.waveform_mode = + WAVEFORM_MODE_AUTO; +#endif //]AVOID_REAGL_DIRTY_TEMP + + /* + * We have a collision cleared, so select it for resubmission. + * If an update is already selected, attempt to merge. + */ + if (!upd_data_list) { + GALLEN_DBGLOCAL_RUNLOG(1); + upd_data_list = next_update; + list_del_init(&next_update->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) { + GALLEN_DBGLOCAL_RUNLOG(2); + /* If not merging, we have our update */ + break; + } + } else { + GALLEN_DBGLOCAL_RUNLOG(3); + switch (epdc_submit_merge(upd_data_list->update_desc, + next_update->update_desc, + fb_data)) { + case MERGE_OK:GALLEN_DBGLOCAL_RUNLOG(4); + dev_dbg(fb_data->dev, + "Update merged [collision]\n"); + list_del_init(&next_update->update_desc->list); + kfree(next_update->update_desc); + next_update->update_desc = NULL; + list_del_init(&next_update->list); + /* Add to free buffer list */ + list_add_tail(&next_update->list, + &fb_data->upd_buf_free_list); + break; + case MERGE_FAIL:GALLEN_DBGLOCAL_RUNLOG(5); + dev_dbg(fb_data->dev, + "Update not merged [collision]\n"); + break; + case MERGE_BLOCK:GALLEN_DBGLOCAL_RUNLOG(6); + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) { + GALLEN_DBGLOCAL_RUNLOG(7); + end_merge = false; + break; + } + } + } + + /* + * Skip pending update list only if we found a collision + * update and we are not merging + */ + if (!((fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) && + upd_data_list)) { + GALLEN_DBGLOCAL_RUNLOG(8); + /* + * If we didn't find a collision update ready to go, we + * need to get a free buffer and match it to a pending update. + */ + + /* + * Can't proceed if there are no free buffers (and we don't + * already have a collision update selected) + */ + if (!upd_data_list && + list_empty(&fb_data->upd_buf_free_list)) { + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + list_for_each_entry_safe(next_desc, temp_desc, + &fb_data->upd_pending_list, list) { + GALLEN_DBGLOCAL_RUNLOG(9); + + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + if (!upd_data_list) { + GALLEN_DBGLOCAL_RUNLOG(10); + if (list_empty(&fb_data->upd_buf_free_list)) { + GALLEN_DBGLOCAL_RUNLOG(11); + break; + } + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + list_del_init(&upd_data_list->list); + upd_data_list->update_desc = next_desc; + list_del_init(&next_desc->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) { + GALLEN_DBGLOCAL_RUNLOG(12); + /* If not merging, we have an update */ + break; + } + } else { + GALLEN_DBGLOCAL_RUNLOG(13); + switch (epdc_submit_merge(upd_data_list->update_desc, + next_desc, fb_data)) { + case MERGE_OK:GALLEN_DBGLOCAL_RUNLOG(14); + dev_dbg(fb_data->dev, + "Update merged [queue]\n"); + list_del_init(&next_desc->list); + kfree(next_desc); + break; + case MERGE_FAIL:GALLEN_DBGLOCAL_RUNLOG(15); + dev_dbg(fb_data->dev, + "Update not merged [queue]\n"); + break; + case MERGE_BLOCK:GALLEN_DBGLOCAL_RUNLOG(16); + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) { + GALLEN_DBGLOCAL_RUNLOG(17); + break; + } + } + } + } + /* Is update list empty? */ + if (!upd_data_list) { + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + /* Backup the actual update region */ + upd_region = &upd_data_list->update_desc->upd_data.update_region; + actual_update_region.left = upd_region->left; + actual_update_region.top = upd_region->top; + actual_update_region.width = upd_region->width; + actual_update_region.height = upd_region->height; + + // align update region to 8x8 boundary for PxP + { + int start, dim; + + start = (upd_region->left/8)*8; + dim = ((upd_region->width + 7)/8)*8; + if (start + dim < + upd_region->left + upd_region->width) { + dim += 8; + } + upd_region->left = start; + upd_region->width = dim; + + start = (upd_region->top/8)*8; + dim = ((upd_region->height + 7)/8)*8; + if (start + dim < + upd_region->top + upd_region->height) { + dim += 8; + } + upd_region->top = start; + upd_region->height = dim; + } + + /* + * If no processing required, skip update processing + * No processing means: + * - FB unrotated + * - FB pixel format = 8-bit grayscale + * - No look-up transformations (inversion, posterization, etc.) + * - No advance algorithms, which requires a look-up transformation + * to convert from 4-bit to 5-bit lookup mode + * + * Note: A bug with EPDC stride prevents us from skipping + * PxP in versions 2.0 and earlier of EPDC. + */ + is_transform = (upd_data_list->update_desc->upd_data.flags & + (EPDC_FLAG_ENABLE_INVERSION | + EPDC_FLAG_USE_DITHERING_Y1 | EPDC_FLAG_USE_DITHERING_Y4 | + EPDC_FLAG_FORCE_MONOCHROME | EPDC_FLAG_USE_CMAP)) || + (fb_data->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N) ? + true : false; + DBG_MSG("%s(),is_transform=%d\n",__FUNCTION__,is_transform); + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) && + (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT) && + !is_transform && (fb_data->rev > 20) && + !fb_data->restrict_width) { + GALLEN_DBGLOCAL_RUNLOG(18); + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + GALLEN_DBGLOCAL_RUNLOG(19); + epdc_powerup(fb_data); + } + + /* + * Set update buffer pointer to the start of + * the update region in the frame buffer. + */ + upd_data_list->update_desc->epdc_stride = + fb_data->info.var.xres_virtual * + fb_data->info.var.bits_per_pixel/8; + direct_draw = 1; + flush_cache_all(); + outer_flush_all(); + } else { + GALLEN_DBGLOCAL_RUNLOG(20); + /* Select from PxP output buffers */ + upd_data_list->phys_addr = + fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; + upd_data_list->virt_addr = + fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; + fb_data->upd_buffer_num++; + if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) { + GALLEN_DBGLOCAL_RUNLOG(21); + fb_data->upd_buffer_num = 0; + } + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + /* Perform PXP processing - EPDC power will also be enabled */ + if (epdc_process_update(upd_data_list, fb_data)) { + dev_dbg(fb_data->dev, "PXP processing error.\n"); + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + list_del_init(&upd_data_list->update_desc->list); + kfree(upd_data_list->update_desc); + upd_data_list->update_desc = NULL; + /* Add to free buffer list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_free_list); + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + } + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + &upd_data_list->update_desc->upd_data.update_region, + &adj_update_region); + + /* + * Is the working buffer idle? + * If the working buffer is busy, we must wait for the resource + * to become free. The IST will signal this event. + */ + if (fb_data->cur_update != NULL) { + GALLEN_DBGLOCAL_RUNLOG(22); + dev_dbg(fb_data->dev, "working buf busy!\n"); + + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_wb = true; + + /* Leave spinlock while waiting for WB to complete */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->update_res_free); + mutex_lock(&fb_data->queue_mutex); + } + + /* + * Dithering Processing (Version 1.0 - for i.MX508 and i.MX6SL) + */ + if (upd_data_list->update_desc->upd_data.flags & + EPDC_FLAG_USE_DITHERING_Y1) { + + GALLEN_DBGLOCAL_RUNLOG(23); + + err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3 + * sizeof(int), GFP_KERNEL); + + mutex_unlock(&fb_data->queue_mutex); + mutex_lock(&fb_data->pxp_mutex); + + /* Dithering Y8 -> Y1 */ + do_dithering_processing_Y1_v1_0( + (uint8_t *)(upd_data_list->virt_addr + + upd_data_list->update_desc->epdc_offs), + &adj_update_region, + (fb_data->rev < 20) ? + ALIGN(adj_update_region.width, 8) : + adj_update_region.width, + err_dist); + + mutex_unlock(&fb_data->pxp_mutex); + mutex_lock(&fb_data->queue_mutex); + + kfree(err_dist); + } else if (upd_data_list->update_desc->upd_data.flags & + EPDC_FLAG_USE_DITHERING_Y4) { + + GALLEN_DBGLOCAL_RUNLOG(24); + + err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3 + * sizeof(int), GFP_KERNEL); + + mutex_unlock(&fb_data->queue_mutex); + mutex_lock(&fb_data->pxp_mutex); + + /* Dithering Y8 -> Y4 */ + do_dithering_processing_Y4_v1_0( + (uint8_t *)(upd_data_list->virt_addr + + upd_data_list->update_desc->epdc_offs), + &adj_update_region, + (fb_data->rev < 20) ? + ALIGN(adj_update_region.width, 8) : + adj_update_region.width, + err_dist); + + mutex_unlock(&fb_data->pxp_mutex); + mutex_lock(&fb_data->queue_mutex); + + kfree(err_dist); + } + + /* + * advance algorithm flow + * Working buffer is now ensured to not be busy, + * so we can begin special waveform processing. + */ + DBG_MSG("%s(%d):upd_order=%d,is_aa=%d,wb_pause=%d,flags=0x%x\n", + __FUNCTION__,__LINE__, + upd_data_list->update_desc->update_order, + upd_data_list->update_desc->is_aa, + upd_data_list->update_desc->wb_pause, + upd_data_list->update_desc->upd_data.flags); + + if (upd_data_list->update_desc->is_aa && + !upd_data_list->update_desc->wb_pause && + !(upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_USE_AAD)) { + /* aa algorithm */ + /* collect the system time data */ + flush_cache_all(); + outer_flush_all(); + aa_time_stamp[0] = get_uSecs(); + + + mutex_unlock(&fb_data->queue_mutex); + mutex_lock(&fb_data->pxp_mutex); + + if (do_aa_processing_v2_2_0((uint8_t *)(upd_data_list->virt_addr + + upd_data_list->update_desc->epdc_offs), + &adj_update_region, + ALIGN(adj_update_region.width, 8), + (uint16_t *)fb_data->working_buffer_A_virt, + fb_data->native_width, + fb_data->native_height)) + dev_err(fb_data->dev," AAD algorithm is not available in this EPDC driver!\n"); + + mutex_unlock(&fb_data->pxp_mutex); + mutex_lock(&fb_data->queue_mutex); + + /* collect the system time data */ + aa_time_stamp[1] = get_uSecs(); + + DBG_MSG (" --- AAl(v2.2.0) Algo Exec Time = %d\n", getTimeDiff(aa_time_stamp[0], aa_time_stamp[1])); + flush_cache_all(); + outer_flush_all(); + } + else if (upd_data_list->update_desc->is_aa && + !upd_data_list->update_desc->wb_pause && + (upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_USE_AAD)) { + + flush_cache_all(); + outer_flush_all(); + /* AAD algorithm */ + /* collect the system time data */ + aa_time_stamp[0] = get_uSecs(); + + mutex_unlock(&fb_data->queue_mutex); + mutex_lock(&fb_data->pxp_mutex); + + if (do_aad_processing_v2_1_0((uint8_t *)(upd_data_list->virt_addr + + upd_data_list->update_desc->epdc_offs), + &adj_update_region, + ALIGN(adj_update_region.width, 8), + (uint16_t *)fb_data->working_buffer_A_virt, + fb_data->native_width, + fb_data->native_height)) + dev_err(fb_data->dev," AA algorithm is not available in this EPDC driver!\n"); + + mutex_unlock(&fb_data->pxp_mutex); + mutex_lock(&fb_data->queue_mutex); + + /* collect the system time data */ + aa_time_stamp[1] = get_uSecs(); + + DBG_MSG (" --- AA-D(v2.1.0) Algo Exec Time = %d\n", getTimeDiff(aa_time_stamp[0], aa_time_stamp[1])); + flush_cache_all(); + outer_flush_all(); + } + + /* Finished all PxP processing, restore the original update region */ + upd_region = &upd_data_list->update_desc->upd_data.update_region; + + if(direct_draw) { + upd_region->left = actual_update_region.left; + upd_region->top = actual_update_region.top; + upd_region->width = actual_update_region.width; + upd_region->height = actual_update_region.height; + + upd_data_list->update_desc->epdc_offs = ((upd_region->top * fb_data->info.var.xres_virtual) + + upd_region->left) * fb_data->info.var.bits_per_pixel/8; + update_addr = fb_data->info.fix.smem_start + upd_data_list->update_desc->epdc_offs; + } + else { + u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; + + width_pxp_blocks = upd_data_list->update_desc->epdc_stride; + + switch (fb_data->epdc_fb_var.rotate) { + default: + case FB_ROTATE_UR: + post_rotation_xcoord = actual_update_region.left - upd_region->left; + post_rotation_ycoord = actual_update_region.top - upd_region->top; + break; + case FB_ROTATE_CW: + post_rotation_xcoord = upd_region->height - actual_update_region.height - + (actual_update_region.top - upd_region->top); + post_rotation_ycoord = (actual_update_region.left - upd_region->left); + break; + case FB_ROTATE_UD: + post_rotation_xcoord = upd_region->width - actual_update_region.width - + (actual_update_region.left - upd_region->left); + post_rotation_ycoord = upd_region->height - actual_update_region.height - + (actual_update_region.top - upd_region->top); + break; + case FB_ROTATE_CCW: + post_rotation_xcoord = (actual_update_region.top - upd_region->top); + post_rotation_ycoord = upd_region->width - actual_update_region.width - + (actual_update_region.left - upd_region->left); + break; + } + + if (fb_data->rev < 20) { + u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; + + pxp_output_shift = ALIGN(post_rotation_xcoord, 8) - post_rotation_xcoord; + pxp_output_offs = (post_rotation_ycoord * upd_data_list->update_desc->epdc_stride) + pxp_output_shift; + upd_data_list->update_desc->epdc_offs = ALIGN(pxp_output_offs, 8); + } else { + upd_data_list->update_desc->epdc_offs = + (post_rotation_ycoord * upd_data_list->update_desc->epdc_stride) + post_rotation_xcoord; + } + + update_addr = upd_data_list->phys_addr + upd_data_list->update_desc->epdc_offs; + + upd_region->left = actual_update_region.left; + upd_region->top = actual_update_region.top; + upd_region->width = actual_update_region.width; + upd_region->height = actual_update_region.height; + } + + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + upd_region, + &adj_update_region); + + /* + * If there are no LUTs available, + * then we must wait for the resource to become free. + * The IST will signal this event. + */ + if (!epdc_any_luts_available()) { + GALLEN_DBGLOCAL_RUNLOG(25); + + dev_dbg(fb_data->dev, "no luts available!\n"); + + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_lut = true; + + /* Leave spinlock while waiting for LUT to free up */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->update_res_free); + mutex_lock(&fb_data->queue_mutex); + } + + ret = epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num); + /* + * If LUT15 is in use (for pre-EPDC v2.0 hardware): + * - Wait for LUT15 to complete is if TCE underrun prevent is enabled + * - If we go ahead with update, sync update submission with EOF + */ + if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) { + GALLEN_DBGLOCAL_RUNLOG(26); + + dev_dbg(fb_data->dev, "Waiting for LUT15\n"); + + /* Initialize event signalling that lut15 is free */ + init_completion(&fb_data->lut15_free); + + fb_data->waiting_for_lut15 = true; + + /* Leave spinlock while waiting for LUT to free up */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->lut15_free); + mutex_lock(&fb_data->queue_mutex); + + epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num); + } else if (ret && (fb_data->rev < 20)) { + GALLEN_DBGLOCAL_RUNLOG(27); + + /* Synchronize update submission time to reduce + chances of TCE underrun */ + init_completion(&fb_data->eof_event); + + epdc_eof_intr(true); + + /* Leave spinlock while waiting for EOF event */ + mutex_unlock(&fb_data->queue_mutex); + ret = wait_for_completion_timeout(&fb_data->eof_event, + msecs_to_jiffies(1000)); + if (!ret) { + GALLEN_DBGLOCAL_RUNLOG(28); + dev_err(fb_data->dev, "Missed EOF event!\n"); + epdc_eof_intr(false); + } + udelay(fb_data->eof_sync_period); + mutex_lock(&fb_data->queue_mutex); + + } + + /* LUTs are available, so we get one here */ + fb_data->cur_update = upd_data_list; + + /* Reset mask for LUTS that have completed during WB processing */ + fb_data->luts_complete_wb = 0; + + /* If we are just testing for collision, we don't assign a LUT, + * so we don't need to update LUT-related resources. */ + if (!(upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION)) { + GALLEN_DBGLOCAL_RUNLOG(29); + /* Associate LUT with update marker */ + list_for_each_entry_safe(next_marker, temp_marker, + &upd_data_list->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = fb_data->cur_update->lut_num; + + /* Mark LUT with order */ + fb_data->lut_update_order[upd_data_list->lut_num] = + upd_data_list->update_desc->update_order; + + epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num, + true); + } + + /* + * If this update requires a pause after WB processing, + * we will get UPD_DONE interrupt but not WB_CMPLT. + * For all other updates, we need an interrupt for WB_CMPLT. + */ + if (upd_data_list->update_desc->wb_pause) { + __raw_writel(EPDC_IRQ_UPD_DONE_IRQ, EPDC_IRQ_CLEAR); + epdc_upd_done_intr(true); + } else + epdc_working_buf_intr(true); + + /* Program EPDC update to process buffer */ + if (upd_data_list->update_desc->upd_data.temp != TEMP_USE_AMBIENT) { + GALLEN_DBGLOCAL_RUNLOG(30); + + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, + upd_data_list->update_desc->upd_data.temp); + } + epdc_set_temp(fb_data->temp_index); + epdc_set_update_addr(update_addr); + epdc_set_update_coord(adj_update_region.left, adj_update_region.top); + epdc_set_update_dimensions(adj_update_region.width, + adj_update_region.height); + if (fb_data->rev > 20) { + GALLEN_DBGLOCAL_RUNLOG(32); + + epdc_set_update_stride(upd_data_list->update_desc->epdc_stride); + } + if (fb_data->wv_modes_update && + (upd_data_list->update_desc->upd_data.waveform_mode + == WAVEFORM_MODE_AUTO)) { + GALLEN_DBGLOCAL_RUNLOG(33); + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + if (upd_data_list->update_desc->wb_pause) + __raw_writel(fb_data->working_buffer_B_phys, EPDC_WB_ADDR_TCE); + else + __raw_writel(fb_data->working_buffer_A_phys, EPDC_WB_ADDR_TCE); + + epdc_submit_update(upd_data_list->lut_num, + upd_data_list->update_desc->upd_data.waveform_mode, + upd_data_list->update_desc->upd_data.update_mode, + (upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) ? true : false, + upd_data_list->update_desc->wb_pause, false, 0); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_END(); +} + +static int mxc_epdc_fb_send_single_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect *screen_upd_region; /* Region on screen to update */ + int ret; + struct update_desc_list *upd_desc; + struct update_marker_data *marker_data, *next_marker, *temp_marker; + int algorithmVersion = 0; /* assume no advance algorithm */ + int iDither_state; + + GALLEN_DBGLOCAL_MUTEBEGIN(); + GALLEN_DBGLOCAL_DBGLVL_SET(giDbgLvl); + + DBG_MSG("%s():upd update mode=%s,wf mode=%d,left=%d,top=%d,w=%d,h=%d,flag=0x%x,marker=%d\n", + __FUNCTION__, + upd_data->update_mode==UPDATE_MODE_PARTIAL?"partial":"full", + (int)upd_data->waveform_mode,(int)upd_data->update_region.left,\ + (int)upd_data->update_region.top,(int)upd_data->update_region.width,\ + (int)upd_data->update_region.height,upd_data->flags,upd_data->update_marker); + + iDither_state = dither_enable_state(); + if( upd_data->flags&EPDC_FLAG_USE_DITHERING_NTX_D8||\ + 1==iDither_state||2==iDither_state||3==iDither_state) + { + unsigned long dwFlags,dwOldFlags; + + epdfbdc_get_flags(gptDC,&dwOldFlags); + dwFlags = dwOldFlags | EPDFB_DC_FLAG_DITHER8; + epdfbdc_set_flags(gptDC,&dwFlags); + + if (EPDFB_DC_SUCCESS!=epdfbdc_put_dcimg(gptDC,gptDC,EPDFB_R_0, + (unsigned long)upd_data->update_region.left, + (unsigned long)upd_data->update_region.top, + (unsigned long)upd_data->update_region.width, + (unsigned long)upd_data->update_region.height, + (unsigned long)upd_data->update_region.left, + (unsigned long)upd_data->update_region.top)) + { + printk(KERN_ERR"%s() : dithering process failed !!\n",__FUNCTION__); + } + epdfbdc_set_flags(gptDC,&dwOldFlags); + //upd_data->update_mode = UPDATE_MODE_PARTIAL; + } + else if(4==iDither_state||5==iDither_state) { + upd_data->flags |= EPDC_FLAG_USE_DITHERING_Y1; + //upd_data->update_mode = UPDATE_MODE_PARTIAL; + } + else if(6==iDither_state||7==iDither_state) { + upd_data->flags |= EPDC_FLAG_USE_DITHERING_Y4; + //upd_data->update_mode = UPDATE_MODE_PARTIAL; + } + +#ifdef OUTPUT_SNAPSHOT_IMGFILE //[ + if(2==giFB_snapshot_enable) { + int iChk; + // capture partial update retangle of framebuffer . + // + + iChk = fb_capture_ex(gptDC,\ + (int)upd_data->update_region.left,\ + (int)upd_data->update_region.top,\ + (int)upd_data->update_region.width,\ + (int)upd_data->update_region.height,\ + 8,EPDFB_R_0,gcFB_snapshot_pathA,\ + (unsigned long) giFB_snapshot_total); + + if( 1==giFB_snapshot_repeat && 1==iChk) { + giFB_snapshot_enable = 0; + } + } + else if(1==giFB_snapshot_enable){ + int iChk; + + iChk = fb_capture_ex(gptDC,0,0,gptDC->dwWidth,gptDC->dwHeight, + 8,EPDFB_R_0,gcFB_snapshot_pathA,\ + (unsigned long) giFB_snapshot_total); + + if( 1==giFB_snapshot_repeat && 1==iChk) { + giFB_snapshot_enable = 0; + } + } +#endif //]OUTPUT_SNAPSHOT_IMGFILE + /* Has EPDC HW been initialized? */ + if (!fb_data->hw_ready) { + /* Throw message if we are not mid-initialization */ + if (!fb_data->hw_initializing) + dev_err(fb_data->dev, "Display HW not properly" + "initialized. Aborting update.\n"); + + GALLEN_DBGLOCAL_ESC(); + return -EPERM; + } + + /* Check validity of update params */ + if ((upd_data->update_mode != UPDATE_MODE_PARTIAL) && + (upd_data->update_mode != UPDATE_MODE_FULL)) { + dev_err(fb_data->dev, + "Update mode 0x%x is invalid. Aborting update.\n", + upd_data->update_mode); + + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + +#if (NTX_WFM_MODE_TOTAL>0) //[ + if ( (upd_data->waveform_mode < 0) || + ((upd_data->waveform_mode >= NTX_WFM_MODE_TOTAL) && + (upd_data->waveform_mode != WAVEFORM_MODE_AUTO)) ) +#else + if ((upd_data->waveform_mode > 255) && + (upd_data->waveform_mode != WAVEFORM_MODE_AUTO)) +#endif + { + dev_err(fb_data->dev, + "Update waveform mode 0x%x is invalid." + " Aborting update.\n", + upd_data->waveform_mode); + + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + if ((upd_data->update_region.left + upd_data->update_region.width > fb_data->epdc_fb_var.xres) || + (upd_data->update_region.top + upd_data->update_region.height > fb_data->epdc_fb_var.yres)) { + dev_err(fb_data->dev, + "Update region is outside bounds of framebuffer." + "Aborting update.\n"); + + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { + if ((upd_data->update_region.width != + upd_data->alt_buffer_data.alt_update_region.width) || + (upd_data->update_region.height != + upd_data->alt_buffer_data.alt_update_region.height)) { + dev_err(fb_data->dev, + "Alternate update region dimensions must " + "match screen update region dimensions.\n"); + + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + /* Validate physical address parameter */ + if ((upd_data->alt_buffer_data.phys_addr < + fb_data->info.fix.smem_start) || + (upd_data->alt_buffer_data.phys_addr > + fb_data->info.fix.smem_start + fb_data->map_size)) { + dev_err(fb_data->dev, + "Invalid physical address for alternate " + "buffer. Aborting update...\n"); + + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + } + + //k_set_temperature(info); + mutex_lock(&fb_data->queue_mutex); + + /* + * If we are waiting to go into suspend, or the FB is blanked, + * we do not accept new updates + */ + if ((fb_data->waiting_for_idle) || + (fb_data->blank != FB_BLANK_UNBLANK)) { + dev_dbg(fb_data->dev, "EPDC not active." + "Update request abort.\n"); + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_ESC(); + return -EPERM; + } + + if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { + int count = 0; + struct update_data_list *plist; + + GALLEN_DBGLOCAL_RUNLOG(0); + /* + * If next update is a FULL mode update, then we must + * ensure that all pending & active updates are complete + * before submitting the update. Otherwise, the FULL + * mode update may cause an endless collision loop with + * other updates. Block here until updates are flushed. + */ + if (upd_data->update_mode == UPDATE_MODE_FULL) { + GALLEN_DBGLOCAL_RUNLOG(1); + mutex_unlock(&fb_data->queue_mutex); + mxc_epdc_fb_flush_updates(fb_data); + mutex_lock(&fb_data->queue_mutex); + } + + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + count++; + + /* Use count to determine if we have enough + * free buffers to handle this update request */ + if (count + fb_data->max_num_buffers + <= fb_data->max_num_updates) { + dev_err(fb_data->dev, + "No free intermediate buffers available.\n"); + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_ESC(); + return -ENOMEM; + } + + /* Grab first available buffer and delete from the free list */ + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + + list_del_init(&upd_data_list->list); + } + + /* + * Create new update data structure, fill it with new update + * data and add it to the list of pending updates + */ + upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL); + if (!upd_desc) { + dev_err(fb_data->dev, + "Insufficient system memory for update! Aborting.\n"); + if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { + + GALLEN_DBGLOCAL_RUNLOG(2); + list_add(&upd_data_list->list, + &fb_data->upd_buf_free_list); + } + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_ESC(); + return -EPERM; + } + + + /* Initialize per-update marker list */ + INIT_LIST_HEAD(&upd_desc->upd_marker_list); + upd_desc->upd_data = *upd_data; + upd_desc->update_order = fb_data->order_cnt++; + + + upd_desc->iDither_state = iDither_state; +#if 1 //[ auto select mode + { + + + if( 2==iDither_state || 4==iDither_state || 6==iDither_state ) { + // force dither and A2 mode . + upd_desc->upd_data.waveform_mode=NTX_WFM_MODE_A2; + DBG_MSG("%s():force ntx wf mode %d for dither state %d\n",__FUNCTION__,upd_desc->upd_data.waveform_mode,iDither_state); + } + else + if( 3==iDither_state || 5==iDither_state || 7==iDither_state) { + // force dither and A2 mode . + upd_desc->upd_data.waveform_mode=NTX_WFM_MODE_DU; + DBG_MSG("%s():force ntx wf mode %d for dither state %d\n",__FUNCTION__,upd_desc->upd_data.waveform_mode,iDither_state); + } + + if(WAVEFORM_MODE_AUTO!=upd_desc->upd_data.waveform_mode) { + GALLEN_DBGLOCAL_RUNLOG(12); + +#ifdef NTX_WFM_MODE_OPTIMIZED //[ + //if(0==gptHWCFG->m_val.bUIStyle) + { + + if(NTX_WFM_MODE_GC16==upd_desc->upd_data.waveform_mode) { + GALLEN_DBGLOCAL_RUNLOG(13); + if(upd_desc->upd_data.update_mode == UPDATE_MODE_FULL) { + GALLEN_DBGLOCAL_RUNLOG(14); + #ifdef NTX_WFM_MODE_OPTIMIZED_REAGL//[ + if(giNTX_waveform_modeA[NTX_WFM_MODE_GLD16]!=giNTX_waveform_modeA[NTX_WFM_MODE_GC16]) { + GALLEN_DBGLOCAL_RUNLOG(15); + // has GLD16 mode . + DBG_MSG("WF Mode version=0x%02x,chg W.F Mode GC16(%d)->GLD16(%d) @ full\n", + gbModeVersion,NTX_WFM_MODE_GC16,NTX_WFM_MODE_GLD16); + upd_desc->upd_data.waveform_mode = NTX_WFM_MODE_GLD16; + } + #endif //]NTX_WFM_MODE_OPTIMIZED_REAGL + } + else if(upd_desc->upd_data.update_mode == UPDATE_MODE_PARTIAL){ + GALLEN_DBGLOCAL_RUNLOG(16); + #ifdef NTX_WFM_MODE_OPTIMIZED_REAGL//[ + if(giNTX_waveform_modeA[NTX_WFM_MODE_GLR16]!=giNTX_waveform_modeA[NTX_WFM_MODE_GC16]) { + GALLEN_DBGLOCAL_RUNLOG(17); + DBG_MSG("WF Mode version=0x%02x,chg W.F Mode GC16(%d)->GLR16(%d) @ partial\n", + gbModeVersion,NTX_WFM_MODE_GC16,NTX_WFM_MODE_GLR16); + upd_desc->upd_data.waveform_mode = NTX_WFM_MODE_GLR16; + } + else + #endif //]NTX_WFM_MODE_OPTIMIZED_REAGL + if (giNTX_waveform_modeA[NTX_WFM_MODE_GL16]!=giNTX_waveform_modeA[NTX_WFM_MODE_GC16]) { + GALLEN_DBGLOCAL_RUNLOG(18); + DBG_MSG("chg W.F Mode GC16(%d)->GL16(%d) @ partial\n", + NTX_WFM_MODE_GC16,NTX_WFM_MODE_GL16); + upd_desc->upd_data.waveform_mode = NTX_WFM_MODE_GL16; + } + } + } + + } +#endif //] NTX_WFM_MODE_OPTIMIZED + + if( NTX_WFM_MODE_GLD16==upd_desc->upd_data.waveform_mode && + giNTX_waveform_modeA[NTX_WFM_MODE_GLD16]!=giNTX_waveform_modeA[NTX_WFM_MODE_GC16]) + { + GALLEN_DBGLOCAL_RUNLOG(19); + upd_desc->upd_data.flags |= EPDC_FLAG_USE_AAD; + } + /* + else { + upd_desc->upd_data.flags &= ~EPDC_FLAG_USE_AAD; + } + */ + + DBG_MSG("ntx wfm mode %d->eink wfm mode %d x\n", + upd_desc->upd_data.waveform_mode,giNTX_waveform_modeA[upd_desc->upd_data.waveform_mode]); + upd_desc->upd_data.waveform_mode = giNTX_waveform_modeA[upd_desc->upd_data.waveform_mode]; + + } + else + { + /* + if(2==gptHWCFG->m_val.bUIStyle) { + // android . + if(giNTX_waveform_modeA[NTX_WFM_MODE_A2]!=giNTX_waveform_modeA[NTX_WFM_MODE_DU]) { + extern int g_touch_pressed_cnt; + // waveform has A2 !! + if( g_touch_pressed_cnt>5 && + upd_desc->upd_data.update_mode == UPDATE_MODE_PARTIAL) + { + DBG_MSG("WF Mode version=0x%02x,chg W.F Mode %d->A2(%d) @ partial\n", + bModeVersion,upd_desc->upd_data.waveform_mode,NTX_WFM_MODE_A2); + upd_desc->upd_data.waveform_mode = NTX_WFM_MODE_A2; + } + + } + + } + */ + } + } +#endif //] + + + +#if 1//[ + /* Conditions for using advance algorithms : + * 1) AA waveform mode is specified and supported (note: advance algorithms may not available for all waveform modes and/or temperature ranges) + * 2) Waveform format is 5-bit + * 3) Scheme is Queue or Queue & Merge + */ + if ((fb_data->waveform_acd_buffer) && (upd_desc->upd_data.waveform_mode < fb_data->waveform_mc) && (upd_desc->upd_data.waveform_mode > 0)) { + GALLEN_DBGLOCAL_RUNLOG(20); + + /* prepare the advance algorithm data for this waveform mode and temperature range */ + algorithmVersion = mxc_epdc_fb_prep_algorithm_data( fb_data->waveform_acd_buffer, + upd_desc->upd_data.waveform_mode /*upd_data_list->update_desc->upd_data.waveform_mode*/, + fb_data->temp_index, + fb_data->waveform_mc, + fb_data->waveform_trc, + fb_data->waveform_magic_number, + 0x3); + if (algorithmVersion < 0) + dev_info (fb_data->dev, " --- Advance Algorithm Data error: %d ---\n",algorithmVersion); + } +#endif //] + + /* algorithmVersion = 0 means no advance algorithm for this waveform and/or temperature range + * algorithmVersion = 1 means there is aa algorithm associated with this waveform and temperture range + * algorithmVersion = 2 means there area aa and aad algorithms associated with this waveform and temperature range + */ + if (algorithmVersion > 0) { + GALLEN_DBGLOCAL_RUNLOG(21); + upd_desc->is_aa = + ((algorithmVersion > 0) && (algorithmVersion <= 2) && + (fb_data->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N) && + (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT)) ? 1 : 0; + } + else { + GALLEN_DBGLOCAL_RUNLOG(22); +#if 1 // 20131022 gallen disabled aa selected by user . + upd_desc->is_aa = 0; +#else + upd_desc->is_aa = + ((upd_desc->upd_data.waveform_mode == fb_data->wv_modes.mode_aa || + upd_desc->upd_data.waveform_mode == fb_data->wv_modes.mode_aad) && + (fb_data->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N) && + (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT)) ? 1 : 0; + /* Warn against AA updates w/o AA waveform */ + if ((upd_desc->upd_data.waveform_mode == fb_data->wv_modes.mode_aa || + upd_desc->upd_data.waveform_mode == fb_data->wv_modes.mode_aad) && + (fb_data->buf_pix_fmt != EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N)) + dev_err(fb_data->dev, "AA waveform update specified" + "without valid AA waveform file.\n"); +#endif + } + + DBG_MSG("%s():upd_order=%d,AlgorithmVerion=%d,is_aa=%d\n", + __FUNCTION__,(int)upd_desc->update_order, + algorithmVersion,upd_desc->is_aa); + +#if 1 + upd_desc->wb_pause = 0; +#else + upd_desc->wb_pause = upd_desc->is_aa && (fb_data->rev > 20) ? 1 : 0; +#endif + + list_add_tail(&upd_desc->list, &fb_data->upd_pending_list); + + + + + /* If marker specified, associate it with a completion */ + if (upd_data->update_marker != 0) { + GALLEN_DBGLOCAL_RUNLOG(3); + /* Allocate new update marker and set it up */ + marker_data = kzalloc(sizeof(struct update_marker_data), + GFP_KERNEL); + if (!marker_data) { + dev_err(fb_data->dev, "No memory for marker!\n"); + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_ESC(); + return -ENOMEM; + } + list_add_tail(&marker_data->upd_list, + &upd_desc->upd_marker_list); + marker_data->update_marker = upd_data->update_marker; + if (upd_desc->upd_data.flags & EPDC_FLAG_TEST_COLLISION) { + GALLEN_DBGLOCAL_RUNLOG(4); + marker_data->lut_num = DRY_RUN_NO_LUT; + } + else { + GALLEN_DBGLOCAL_RUNLOG(5); + marker_data->lut_num = INVALID_LUT; + } + + init_completion(&marker_data->update_completion); + /* Add marker to master marker list */ + list_add_tail(&marker_data->full_list, + &fb_data->full_marker_list); + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + mutex_unlock(&fb_data->queue_mutex); + + /* Signal workqueue to handle new update */ + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + GALLEN_DBGLOCAL_ESC(); + return 0; + } + + /* Snapshot update scheme processing */ + + /* Set descriptor for current update, delete from pending list */ + upd_data_list->update_desc = upd_desc; + list_del_init(&upd_desc->list); + + mutex_unlock(&fb_data->queue_mutex); + + /* + * Hold on to original screen update region, which we + * will ultimately use when telling EPDC where to update on panel + */ + screen_upd_region = &upd_desc->upd_data.update_region; + + /* Select from PxP output buffers */ + upd_data_list->phys_addr = + fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; + upd_data_list->virt_addr = + fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; + fb_data->upd_buffer_num++; + if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) { + GALLEN_DBGLOCAL_RUNLOG(6); + fb_data->upd_buffer_num = 0; + } + + ret = epdc_process_update(upd_data_list, fb_data); + if (ret) { + mutex_unlock(&fb_data->pxp_mutex); + + GALLEN_DBGLOCAL_ESC(); + return ret; + } + + /* Pass selected waveform mode back to user */ + upd_data->waveform_mode = upd_desc->upd_data.waveform_mode; + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + &upd_desc->upd_data.update_region, NULL); + + /* Grab lock for queue manipulation and update submission */ + mutex_lock(&fb_data->queue_mutex); + + /* + * Is the working buffer idle? + * If either the working buffer is busy, or there are no LUTs available, + * then we return and let the ISR handle the update later + */ + if ((fb_data->cur_update != NULL) || !epdc_any_luts_available()) { + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); + + /* Return and allow the update to be submitted by the ISR. */ + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_ESC(); + return 0; + } + + /* LUTs are available, so we get one here */ + ret = epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num); + if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) { + dev_dbg(fb_data->dev, "Must wait for LUT15\n"); + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); + + /* Return and allow the update to be submitted by the ISR. */ + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_ESC(); + return 0; + } + + if (!(upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION)) { + + GALLEN_DBGLOCAL_RUNLOG(7); + + /* Save current update */ + fb_data->cur_update = upd_data_list; + + /* Reset mask for LUTS that have completed during WB processing */ + fb_data->luts_complete_wb = 0; + + /* Associate LUT with update marker */ + list_for_each_entry_safe(next_marker, temp_marker, + &upd_data_list->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = upd_data_list->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[upd_data_list->lut_num] = + upd_desc->update_order; + + epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num, + true); + } + + /* Clear status and Enable LUT complete and WB complete IRQs */ + epdc_working_buf_intr(true); + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(upd_data_list->phys_addr + upd_desc->epdc_offs); + epdc_set_update_coord(screen_upd_region->left, screen_upd_region->top); + epdc_set_update_dimensions(screen_upd_region->width, + screen_upd_region->height); + if (fb_data->rev > 20) { + GALLEN_DBGLOCAL_RUNLOG(8); + epdc_set_update_stride(upd_desc->epdc_stride); + } + if (upd_desc->upd_data.temp != TEMP_USE_AMBIENT) { + GALLEN_DBGLOCAL_RUNLOG(9); + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, + upd_desc->upd_data.temp); + } + epdc_set_temp(fb_data->temp_index); + if (fb_data->wv_modes_update && + (upd_desc->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { + GALLEN_DBGLOCAL_RUNLOG(11); + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + epdc_submit_update(upd_data_list->lut_num, + upd_desc->upd_data.waveform_mode, + upd_desc->upd_data.update_mode, + (upd_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) ? true : false, + upd_data_list->update_desc->wb_pause, false, 0); + + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_END(); + return 0; +} + +int mxc_epdc_fb_send_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + GALLEN_DBGLOCAL_BEGIN(); + + //Yian: patch from freescale HK. Peter, fixed the line noise on EPD + flush_cache_all(); + outer_flush_all(); + + if (!fb_data->restrict_width) { + GALLEN_DBGLOCAL_ESC(); + /* No width restriction, send entire update region */ + return mxc_epdc_fb_send_single_update(upd_data, info); + } else { + int ret; + __u32 width, left; + __u32 marker; + __u32 *region_width, *region_left; + u32 max_upd_width = EPDC_V2_MAX_UPDATE_WIDTH; + + GALLEN_DBGLOCAL_RUNLOG(0); + + /* Further restrict max width due to pxp rotation + * alignment requirement + */ + if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR) { + GALLEN_DBGLOCAL_RUNLOG(1); + max_upd_width -= EPDC_V2_ROTATION_ALIGNMENT; + } + + /* Select split of width or height based on rotation */ + if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || + (fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) { + GALLEN_DBGLOCAL_RUNLOG(2); + region_width = &upd_data->update_region.width; + region_left = &upd_data->update_region.left; + } else { + GALLEN_DBGLOCAL_RUNLOG(3); + region_width = &upd_data->update_region.height; + region_left = &upd_data->update_region.top; + } + + if (*region_width <= max_upd_width) { + GALLEN_DBGLOCAL_ESC(); + return mxc_epdc_fb_send_single_update(upd_data, info); + } + + width = *region_width; + left = *region_left; + marker = upd_data->update_marker; + upd_data->update_marker = 0; + + do { + GALLEN_DBGLOCAL_RUNLOG(4); + *region_width = max_upd_width; + *region_left = left; + ret = mxc_epdc_fb_send_single_update(upd_data, info); + if (ret) { + GALLEN_DBGLOCAL_ESC(); + return ret; + } + width -= max_upd_width; + left += max_upd_width; + } while (width > max_upd_width); + + *region_width = width; + *region_left = left; + upd_data->update_marker = marker; + GALLEN_DBGLOCAL_ESC(); + return mxc_epdc_fb_send_single_update(upd_data, info); + } + + GALLEN_DBGLOCAL_END(); +} +EXPORT_SYMBOL(mxc_epdc_fb_send_update); + +int mxc_epdc_fb_wait_update_complete(struct mxcfb_update_marker_data *marker_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + bool marker_found = false; + int ret = 0; + + + GALLEN_DBGLOCAL_MUTEBEGIN(); + GALLEN_DBGLOCAL_DBGLVL_SET(giDbgLvl); + + DBG_MSG("%s() : fb_info@%p,marker_data@%p,update_marker=%d\n", + __FUNCTION__,info,marker_data,marker_data->update_marker); + + /* 0 is an invalid update_marker value */ + if (marker_data->update_marker == 0) { + GALLEN_DBGLOCAL_ESC(); + return -EINVAL; + } + + /* + * Find completion associated with update_marker requested. + * Note: If update completed already, marker will have been + * cleared, it won't be found, and function will just return. + */ + + /* Grab queue lock to protect access to marker list */ + mutex_lock(&fb_data->queue_mutex); + + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, full_list) { + GALLEN_DBGLOCAL_RUNLOG(0); + if (next_marker->update_marker == marker_data->update_marker) { + GALLEN_DBGLOCAL_RUNLOG(1); + dev_dbg(fb_data->dev, "Waiting for marker %d\n", + marker_data->update_marker); + next_marker->waiting = true; + marker_found = true; + break; + } + } + + mutex_unlock(&fb_data->queue_mutex); + + /* + * If marker not found, it has either been signalled already + * or the update request failed. In either case, just return. + */ + if (!marker_found) { + GALLEN_DBGLOCAL_ESC(); + return ret; + } + + ret = wait_for_completion_timeout(&next_marker->update_completion, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(fb_data->dev, + "Timed out waiting for update marker%d completion\n",marker_data->update_marker); + GALLEN_DBGLOCAL_ESC(); + return -ETIMEDOUT; + } + + marker_data->collision_test = next_marker->collision_test; + + /* Free update marker object */ + kfree(next_marker); + + flush_cache_all(); + outer_flush_all(); + GALLEN_DBGLOCAL_END(); + return ret; +} +EXPORT_SYMBOL(mxc_epdc_fb_wait_update_complete); + +int mxc_epdc_fb_set_pwrdown_delay(u32 pwrdown_delay, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + fb_data->pwrdown_delay = pwrdown_delay; + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_pwrdown_delay); + +int mxc_epdc_get_pwrdown_delay(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + + return fb_data->pwrdown_delay; +} +EXPORT_SYMBOL(mxc_epdc_get_pwrdown_delay); + + +static int mxc_epdc_fb_open (struct fb_info *info, int user) +{ + GALLEN_DBGLOCAL_BEGIN(); + fake_s1d13522_progress_stop(); + k_fake_s1d13522_wait_inited(); + GALLEN_DBGLOCAL_END(); + return 0; +} + +static int mxc_epdc_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int ret = -EINVAL; + + GALLEN_DBGLOCAL_BEGIN(); + +#ifdef CONFIG_ANDROID//[ +#else//][!CONFIG_ANDROID + fake_s1d13522_progress_stop(); + k_fake_s1d13522_wait_inited(); +#endif //]CONFIG_ANDROID + +#if 0 //[ + // the command code will be 0x400446XX . + DBG0_MSG("MXCFB_WAIT_FOR_UPDATE_COMPLETE=0x%08x\n",MXCFB_WAIT_FOR_UPDATE_COMPLETE); + //DBG0_MSG("MXCFB_WAIT_FOR_UPDATE_COMPLETE2=0x%08x\n",MXCFB_WAIT_FOR_UPDATE_COMPLETE2); + DBG0_MSG("MXCFB_SEND_UPDATE=0x%08x\n",MXCFB_SEND_UPDATE); + DBG0_MSG("cmd=0x%08x\n",cmd); +#endif //] + + + //printk("skip %s()\n",__FUNCTION__);return 0; + + + switch (cmd) { + case MXCFB_SET_WAVEFORM_MODES:GALLEN_DBGLOCAL_RUNLOG(0); + //printk("\n==>skip MXCFB_SET_WAVEFORM_MODES\n");ret=0;break; + { + struct mxcfb_waveform_modes modes; + if (!copy_from_user(&modes, argp, sizeof(modes))) { + mxc_epdc_fb_set_waveform_modes(&modes, info); + ret = 0; + } + break; + } + case MXCFB_SET_TEMPERATURE:GALLEN_DBGLOCAL_RUNLOG(1); + //printk("\n==>skip MXCFB_SET_TEMPERATURE\n");ret=0;break; + { + int temperature; + if (!get_user(temperature, (int32_t __user *) arg)) + ret = mxc_epdc_fb_set_temperature(temperature, + info); + break; + } + case MXCFB_SET_AUTO_UPDATE_MODE:GALLEN_DBGLOCAL_RUNLOG(2); + //printk("\n==>skip MXCFB_SET_AUTO_UPDATE_MODE\n");ret=0;break; + { + u32 auto_mode = 0; + if (!get_user(auto_mode, (__u32 __user *) arg)) + ret = mxc_epdc_fb_set_auto_update(auto_mode, + info); + break; + } + case MXCFB_SET_UPDATE_SCHEME:GALLEN_DBGLOCAL_RUNLOG(3); + //printk("\n==>skip MXCFB_SET_UPDATE_SCHEME\n");ret=0;break; + { + u32 upd_scheme = 0; + if (!get_user(upd_scheme, (__u32 __user *) arg)) + ret = mxc_epdc_fb_set_upd_scheme(upd_scheme, + info); + break; + } + case MXCFB_SEND_UPDATE:GALLEN_DBGLOCAL_RUNLOG(4); + //printk("\n==> MXCFB_SEND_UPDATE\n"); + { + struct mxcfb_update_data upd_data; + if (!copy_from_user(&upd_data, argp, + sizeof(upd_data))) { + ret = mxc_epdc_fb_send_update(&upd_data, info); + if (ret == 0 && copy_to_user(argp, &upd_data, + sizeof(upd_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + break; + } + +#ifdef MX50_IOCTL_IF//[ + case MXCFB_WAIT_FOR_UPDATE_COMPLETE:GALLEN_DBGLOCAL_RUNLOG(10); + //printk("\n==> MXCFB_WAIT_FOR_UPDATE_COMPLETE mx50\n"); + { + u32 update_marker; + struct mxcfb_update_marker_data *p_mxc_upd_marker_data; + + + if (!get_user(update_marker, (__u32 __user *) arg)) { + GALLEN_DBGLOCAL_RUNLOG(13); + p_mxc_upd_marker_data = kmalloc(sizeof(struct mxcfb_update_marker_data), GFP_KERNEL); + if(p_mxc_upd_marker_data) { + p_mxc_upd_marker_data->collision_test = 0; + p_mxc_upd_marker_data->update_marker=update_marker; + + GALLEN_DBGLOCAL_PRINTMSG("MXCFB_WAIT_FOR_UPDATE_COMPLETE marker=%d\n",update_marker); + ret = mxc_epdc_fb_wait_update_complete(p_mxc_upd_marker_data,info); + kfree(p_mxc_upd_marker_data); + } + else { + printk(KERN_ERR"%s(%d):memory not enough !\n",__FILE__,__LINE__); + } + } + break; + } + + case MXCFB_WAIT_FOR_UPDATE_COMPLETE2:GALLEN_DBGLOCAL_RUNLOG(5); + printk("\n==> MXCFB_WAIT_FOR_UPDATE_COMPLETE2 mx50\n"); +#else//][!MX50_IOCTL_IF + case MXCFB_WAIT_FOR_UPDATE_COMPLETE:GALLEN_DBGLOCAL_RUNLOG(11); + //printk("\n==> MXCFB_WAIT_FOR_UPDATE_COMPLETE mx6sl\n"); +#endif//] MX50_IOCTL_IF + { + struct mxcfb_update_marker_data upd_marker_data; + if (!copy_from_user(&upd_marker_data, argp, + sizeof(upd_marker_data))) { + ret = mxc_epdc_fb_wait_update_complete( + &upd_marker_data, info); + if (copy_to_user(argp, &upd_marker_data, + sizeof(upd_marker_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + break; + } + + case MXCFB_SET_PWRDOWN_DELAY:GALLEN_DBGLOCAL_RUNLOG(6); + //printk("\n==>skip MXCFB_SET_PWRDOWN_DELAY\n");ret=0;break; + { + int delay = 0; + if (!get_user(delay, (__u32 __user *) arg)) + ret = + mxc_epdc_fb_set_pwrdown_delay(delay, info); + break; + } + + case MXCFB_GET_PWRDOWN_DELAY:GALLEN_DBGLOCAL_RUNLOG(7); + //printk("\n==>skip MXCFB_GET_PWRDOWN_DELAY\n");ret=0;break; + { + int pwrdown_delay = mxc_epdc_get_pwrdown_delay(info); + if (put_user(pwrdown_delay, + (int __user *)argp)) + ret = -EFAULT; + ret = 0; + break; + } + +#if 0 + case MXCFB_GET_WORK_BUFFER:GALLEN_DBGLOCAL_RUNLOG(8); + //printk("\n==>skip MXCFB_GET_WORK_BUFFER\n");ret=0;break; + { + /* copy the epdc working buffer to the user space */ + struct mxc_epdc_fb_data *fb_data = info ? + (struct mxc_epdc_fb_data *)info:g_fb_data; + flush_cache_all(); + outer_flush_all(); + if (copy_to_user((void __user *)arg, + (const void *) fb_data->working_buffer_A_virt, + fb_data->working_buffer_size)) + ret = -EFAULT; + else + ret = 0; + flush_cache_all(); + outer_flush_all(); + break; + } + +#endif + default:GALLEN_DBGLOCAL_RUNLOG(9); + //printk("\n==>skip unkown fsl epd command %d\n",cmd);ret=0;break; + ret = k_fake_s1d13522_ioctl(cmd,arg); + break; + } + + GALLEN_DBGLOCAL_END(); + return ret; +} + +static void mxc_epdc_fb_update_pages(struct mxc_epdc_fb_data *fb_data, + u16 y1, u16 y2) +{ + struct mxcfb_update_data update; + + /* Do partial screen update, Update full horizontal lines */ + update.update_region.left = 0; + update.update_region.width = fb_data->epdc_fb_var.xres; + update.update_region.top = y1; + update.update_region.height = y2 - y1; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_mode = UPDATE_MODE_FULL; + update.update_marker = 0; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, &fb_data->info); +} + +/* this is called back from the deferred io workqueue */ +static void mxc_epdc_fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct page *page; + unsigned long beg, end; + int y1, y2, miny, maxy; + + GALLEN_DBGLOCAL_BEGIN(); + + if (fb_data->auto_mode != AUTO_UPDATE_MODE_AUTOMATIC_MODE) { + return; + } + + miny = INT_MAX; + maxy = 0; + list_for_each_entry(page, pagelist, lru) { + beg = page->index << PAGE_SHIFT; + end = beg + PAGE_SIZE - 1; + y1 = beg / info->fix.line_length; + y2 = end / info->fix.line_length; + if (y2 >= fb_data->epdc_fb_var.yres) + y2 = fb_data->epdc_fb_var.yres - 1; + if (miny > y1) + miny = y1; + if (maxy < y2) + maxy = y2; + } + + mxc_epdc_fb_update_pages(fb_data, miny, maxy); + + GALLEN_DBGLOCAL_END(); +} + +void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data) +{ + int ret; + + GALLEN_DBGLOCAL_BEGIN(); + + if (fb_data->in_init) { + GALLEN_DBGLOCAL_ESC(); + return; + } + + /* Grab queue lock to prevent any new updates from being submitted */ + mutex_lock(&fb_data->queue_mutex); + + /* + * 3 places to check for updates that are active or pending: + * 1) Updates in the pending list + * 2) Update buffers in use (e.g., PxP processing) + * 3) Active updates to panel - We can key off of EPDC + * power state to know if we have active updates. + */ +#if 1 + //printk("%s(%d) : \n",__FUNCTION__,__LINE__); + //printk("\tpower_state=%d\n",fb_data->power_state); + //printk("\tpending list is empty=%d\n",list_empty(&fb_data->upd_pending_list)); + //printk("\tfree list is full=%d\n",is_free_list_full(fb_data)); + if ( (fb_data->power_state == POWER_STATE_ON) && + (!list_empty(&fb_data->upd_pending_list) || + !is_free_list_full(fb_data)) ) +#else + if (!list_empty(&fb_data->upd_pending_list) || + !is_free_list_full(fb_data) || + (fb_data->updates_active == true)) +#endif + { + + GALLEN_DBGLOCAL_RUNLOG(0); + + /* Initialize event signalling updates are done */ + init_completion(&fb_data->updates_done); + fb_data->waiting_for_idle = true; + + mutex_unlock(&fb_data->queue_mutex); + /* Wait for any currently active updates to complete */ + ret = wait_for_completion_timeout(&fb_data->updates_done, + msecs_to_jiffies(8000)); + if (!ret) + dev_err(fb_data->dev, + "Flush updates timeout! ret = 0x%x\n", ret); + + mutex_lock(&fb_data->queue_mutex); + fb_data->waiting_for_idle = false; + } + + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_END(); +} + +static int mxc_epdc_fb_blank(int blank, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int ret; + + GALLEN_DBGLOCAL_BEGIN(); + + dev_dbg(fb_data->dev, "blank = %d\n", blank); + + if (fb_data->blank == blank) + { + GALLEN_DBGLOCAL_ESC(); + return 0; + } + + //fb_data->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN:GALLEN_DBGLOCAL_RUNLOG(0); + mxc_epdc_fb_flush_updates(fb_data); + /* Wait for powerdown */ + mutex_lock(&fb_data->power_mutex); + if ((fb_data->power_state == POWER_STATE_ON) && + (fb_data->pwrdown_delay == FB_POWERDOWN_DISABLE)) { + + /* Powerdown disabled, so we disable EPDC manually */ + int count = 0; + int sleep_ms = 10; + + GALLEN_DBGLOCAL_RUNLOG(1); + + mutex_unlock(&fb_data->power_mutex); + + /* If any active updates, wait for them to complete */ + while (fb_data->updates_active) { + GALLEN_DBGLOCAL_RUNLOG(2); + /* Timeout after 1 sec */ + if ((count * sleep_ms) > 1000) { + GALLEN_DBGLOCAL_RUNLOG(3); + break; + } + msleep(sleep_ms); + count++; + } + + fb_data->powering_down = true; + epdc_powerdown(fb_data); + } else if (fb_data->power_state != POWER_STATE_OFF) { + GALLEN_DBGLOCAL_RUNLOG(4); + + fb_data->wait_for_powerdown = true; + init_completion(&fb_data->powerdown_compl); + mutex_unlock(&fb_data->power_mutex); + ret = wait_for_completion_timeout(&fb_data->powerdown_compl, + msecs_to_jiffies(5000)); + if (!ret) { + + dev_err(fb_data->dev, + "No powerdown received!\n"); + + GALLEN_DBGLOCAL_ESC(); + return -ETIMEDOUT; + } + } else { + GALLEN_DBGLOCAL_RUNLOG(6); + mutex_unlock(&fb_data->power_mutex); + } + break; + case FB_BLANK_VSYNC_SUSPEND:GALLEN_DBGLOCAL_RUNLOG(7); + case FB_BLANK_HSYNC_SUSPEND:GALLEN_DBGLOCAL_RUNLOG(8); + case FB_BLANK_NORMAL:GALLEN_DBGLOCAL_RUNLOG(9); + mxc_epdc_fb_flush_updates(fb_data); + break; + } + + GALLEN_DBGLOCAL_END(); + return 0; +} + +static int mxc_epdc_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + u_int y_bottom; + + dev_dbg(info->device, "%s: var->yoffset %d, info->var.yoffset %d\n", + __func__, var->yoffset, info->var.yoffset); + /* check if var is valid; also, xpan is not supported */ + if (!var || (var->xoffset != info->var.xoffset) || + (var->yoffset + var->yres > var->yres_virtual)) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((fb_data->epdc_fb_var.xoffset == var->xoffset) && + (fb_data->epdc_fb_var.yoffset == var->yoffset)) + return 0; /* No change, do nothing */ + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + //mutex_lock(&fb_data->queue_mutex); + mutex_lock(&fb_data->pxp_mutex); + + fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset) + * (var->bits_per_pixel) / 8; + + fb_data->epdc_fb_var.xoffset = var->xoffset; + fb_data->epdc_fb_var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + mutex_unlock(&fb_data->pxp_mutex); + //mutex_unlock(&fb_data->queue_mutex); + + return 0; +} + +static struct fb_ops mxc_epdc_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxc_epdc_fb_check_var, + .fb_set_par = mxc_epdc_fb_set_par, + .fb_setcmap = mxc_epdc_fb_setcmap, + .fb_setcolreg = mxc_epdc_fb_setcolreg, + .fb_pan_display = mxc_epdc_fb_pan_display, +#ifdef CONFIG_ANDROID //[ + .fb_open = mxc_epdc_fb_open, +#endif //] CONFIG_ANDROID + .fb_ioctl = mxc_epdc_fb_ioctl, + .fb_mmap = mxc_epdc_fb_mmap, + .fb_blank = mxc_epdc_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_deferred_io mxc_epdc_fb_defio = { + .delay = HZ, + .deferred_io = mxc_epdc_fb_deferred_io, +}; + +static void epdc_done_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, + epdc_done_work.work); + epdc_powerdown(fb_data); +} + +static void epdc_aa_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_aa_work); + struct update_data_list *upd_data_list = fb_data->cur_update; + struct mxcfb_rect adj_update_region; + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->epdc_fb_var.xres, + fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, + &upd_data_list->update_desc->upd_data.update_region, + &adj_update_region); + + /* + * We are paused between WB Processing and TCE scan. Perform AA + * or AA-D processing and then allow the update to complete. + */ + if (!(upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_USE_AAD)) { + /* + * AA processing API with Dual WB feature (EPDC v2.0+) + * do_aa_processing( + * unsigned short *working_buffer_ptr_in, + * unsigned short *working_buffer_ptr_out, + * struct mxcfb_rect *update_region, + * long working_buffer_width, + * long working_buffer_height); + */ + + /* collect the system time data */ + aa_time_stamp[0] = get_uSecs(); + + flush_cache_all(); + outer_flush_all(); + if (do_aa_processing_v2_2_1( + (uint16_t *)fb_data->working_buffer_A_virt, + (uint16_t *)fb_data->working_buffer_B_virt, + &adj_update_region, + fb_data->native_width, + fb_data->native_height)) + dev_err(fb_data->dev," AAD algorithm is not available in this EPDC driver!\n"); + + + flush_cache_all(); + outer_flush_all(); + /* collect the system time data */ + aa_time_stamp[1] = get_uSecs(); + + dev_info (fb_data->dev, "AA(v2.2.1) Algo Exec Time = %d\n", getTimeDiff(aa_time_stamp[0], aa_time_stamp[1])); + } + else if (upd_data_list->update_desc->upd_data.flags + & EPDC_FLAG_USE_AAD) { + /* AAD algorithm */ + + /* collect the system time data */ + aa_time_stamp[0] = get_uSecs(); + + flush_cache_all(); + outer_flush_all(); + + if (do_aad_processing_v2_1_1( + (uint16_t *)fb_data->working_buffer_A_virt, + (uint16_t *)fb_data->working_buffer_B_virt, + &adj_update_region, + fb_data->native_width, + fb_data->native_height)) + dev_err(fb_data->dev," AAD algorithm is not available in this EPDC driver!\n"); + + flush_cache_all(); + outer_flush_all(); + + /* collect the system time data */ + aa_time_stamp[1] = get_uSecs(); + + dev_info (fb_data->dev, "AA-D(v2.1.1) Algo Exec Time = %d\n", getTimeDiff(aa_time_stamp[0], aa_time_stamp[1])); + } + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + fb_data->cur_update->update_desc->wb_pause = false; + + epdc_submit_paused_update(); + + mutex_unlock(&fb_data->queue_mutex); +} + +static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data) +{ + int count = 0; + struct update_data_list *plist; + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + count++; + + /* Check to see if all buffers are in this list */ + if (count == fb_data->max_num_updates) + return true; + else + return false; +} + +static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id) +{ + struct mxc_epdc_fb_data *fb_data = dev_id; + u32 ints_fired, luts1_ints_fired, luts2_ints_fired; + + GALLEN_DBGLOCAL_BEGIN(); + + /* + * If we just completed one-time panel init, bypass + * queue handling, clear interrupt and return + */ + if (fb_data->in_init) { + GALLEN_DBGLOCAL_RUNLOG(0); + + if (epdc_is_working_buffer_complete()) { + GALLEN_DBGLOCAL_RUNLOG(1); + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + dev_dbg(fb_data->dev, "Cleared WB for init update\n"); + } + + if (epdc_is_lut_complete(fb_data->rev, 0)) { + GALLEN_DBGLOCAL_RUNLOG(2); + epdc_lut_complete_intr(fb_data->rev, 0, false); + epdc_clear_lut_complete_irq(fb_data->rev, 0); + fb_data->in_init = false; + dev_dbg(fb_data->dev, "Cleared LUT complete for init update\n"); + } + + GALLEN_DBGLOCAL_ESC(); + return IRQ_HANDLED; + } + + ints_fired = __raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ); + if (fb_data->rev < 20) { + GALLEN_DBGLOCAL_RUNLOG(3); + luts1_ints_fired = 0; + luts2_ints_fired = 0; + } else { + GALLEN_DBGLOCAL_RUNLOG(4); + luts1_ints_fired = __raw_readl(EPDC_IRQ_MASK1) & __raw_readl(EPDC_IRQ1); + luts2_ints_fired = __raw_readl(EPDC_IRQ_MASK2) & __raw_readl(EPDC_IRQ2); + } + + if (!(ints_fired || luts1_ints_fired || luts2_ints_fired)) { + GALLEN_DBGLOCAL_ESC(); + return IRQ_HANDLED; + } + + if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) { + GALLEN_DBGLOCAL_RUNLOG(6); + + dev_err(fb_data->dev, + "TCE underrun! Will continue to update panel\n"); + /* Clear TCE underrun IRQ */ + __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_CLEAR); + } + + /* Check if we are waiting on EOF to sync a new update submission */ + if (epdc_signal_eof()) { + GALLEN_DBGLOCAL_RUNLOG(7); + + epdc_eof_intr(false); + epdc_clear_eof_irq(); + complete(&fb_data->eof_event); + } + + /* + * Workaround for EPDC v2.0/v2.1 errata: Must read collision status + * before clearing IRQ, or else collision status for bits 16:63 + * will be automatically cleared. So we read it here, and there is + * no conflict with using it in epdc_intr_work_func since the + * working buffer processing flow is strictly sequential (i.e., + * only one WB processing done at a time, so the data grabbed + * here should be up-to-date and accurate when the WB processing + * completes. Also, note that there is no impact to other versions + * of EPDC by reading LUT status here. + */ + if (fb_data->cur_update != NULL) { + GALLEN_DBGLOCAL_RUNLOG(8); + fb_data->epdc_colliding_luts = epdc_get_colliding_luts(fb_data->rev); + } + + /* Clear the interrupt mask for any interrupts signalled */ + __raw_writel(ints_fired, EPDC_IRQ_MASK_CLEAR); + __raw_writel(luts1_ints_fired, EPDC_IRQ_MASK1_CLEAR); + __raw_writel(luts2_ints_fired, EPDC_IRQ_MASK2_CLEAR); + + dev_dbg(fb_data->dev, "EPDC interrupts fired = 0x%x, " + "LUTS1 fired = 0x%x, LUTS2 fired = 0x%x\n", + ints_fired, luts1_ints_fired, luts2_ints_fired); + + queue_work(fb_data->epdc_intr_workqueue, + &fb_data->epdc_intr_work); + + GALLEN_DBGLOCAL_END(); + return IRQ_HANDLED; +} + +static void epdc_intr_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, epdc_intr_work); + struct update_data_list *collision_update; + struct mxcfb_rect *next_upd_region; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + u64 temp_mask; + u32 lut; + bool ignore_collision = false; + int i; + bool wb_lut_done = false; + bool free_update = true; + int next_lut, epdc_next_lut_15; + u32 epdc_luts_active, epdc_wb_busy, epdc_upd_done, epdc_luts_avail; + u32 epdc_lut_cancelled; + u32 epdc_collision; + u64 epdc_irq_stat; + bool epdc_waiting_on_wb; + u32 coll_coord, coll_size; + struct mxcfb_rect coll_region; + bool update_paused = false; + bool wb_pause; + bool update_is_valid = true; + + GALLEN_DBGLOCAL_BEGIN_EX(64); + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* Capture EPDC status one time to limit exposure to race conditions */ + epdc_luts_active = epdc_any_luts_active(fb_data->rev); + epdc_wb_busy = epdc_is_working_buffer_busy(); + epdc_upd_done = __raw_readl(EPDC_IRQ) & EPDC_IRQ_UPD_DONE_IRQ; + epdc_lut_cancelled = epdc_is_lut_cancelled(); + epdc_luts_avail = epdc_any_luts_available(); + epdc_collision = epdc_is_collision(); + if (fb_data->rev < 20) { + GALLEN_DBGLOCAL_RUNLOG(0); + epdc_irq_stat = __raw_readl(EPDC_IRQ); + } + else { + GALLEN_DBGLOCAL_RUNLOG(1); + epdc_irq_stat = (u64)__raw_readl(EPDC_IRQ1) | + ((u64)__raw_readl(EPDC_IRQ2) << 32); + } + epdc_waiting_on_wb = (fb_data->cur_update != NULL) ? true : false; + if (fb_data->cur_update && fb_data->cur_update->update_desc->wb_pause) + wb_pause = true; + else + wb_pause = false; + + /* Free any LUTs that have completed */ + for (i = 0; i < fb_data->num_luts; i++) { + + GALLEN_DBGLOCAL_RUNLOG(2); + if ((epdc_irq_stat & (1ULL << i)) == 0) { + + GALLEN_DBGLOCAL_RUNLOG(3); + continue; + } + + dev_dbg(fb_data->dev, "LUT %d completed\n", i); + + /* Disable IRQ for completed LUT */ + epdc_lut_complete_intr(fb_data->rev, i, false); + + /* + * Go through all updates in the collision list and + * unmask any updates that were colliding with + * the completed LUT. + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list, list) { + + GALLEN_DBGLOCAL_RUNLOG(4); + collision_update->collision_mask = + collision_update->collision_mask & ~(1ULL << i); + } + + epdc_clear_lut_complete_irq(fb_data->rev, i); + + fb_data->luts_complete_wb |= 1ULL << i; + + fb_data->lut_update_order[i] = 0; + + /* Signal completion if submit workqueue needs a LUT */ + if (fb_data->waiting_for_lut) { + GALLEN_DBGLOCAL_RUNLOG(5); + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + /* Signal completion if LUT15 free and is needed */ + if (fb_data->waiting_for_lut15 && (i == 15)) { + GALLEN_DBGLOCAL_RUNLOG(6); + complete(&fb_data->lut15_free); + fb_data->waiting_for_lut15 = false; + } + + /* Detect race condition where WB and its LUT complete + (i.e. full update completes) in one swoop */ + if (epdc_waiting_on_wb && + (i == fb_data->cur_update->lut_num)) { + GALLEN_DBGLOCAL_RUNLOG(7); + wb_lut_done = true; + } + + /* Signal completion if anyone waiting on this LUT */ + if (!wb_lut_done) { + + GALLEN_DBGLOCAL_RUNLOG(8); + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, + full_list) { + + GALLEN_DBGLOCAL_RUNLOG(9); + if (next_marker->lut_num != i) { + GALLEN_DBGLOCAL_RUNLOG(10); + continue; + } + + /* Found marker to signal - remove from list */ + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, "Signaling marker %d\n", + next_marker->update_marker); + if (next_marker->waiting) { + GALLEN_DBGLOCAL_RUNLOG(11); + DBG_MSG("%s()[%d]:update_complete @ marker %d\n",\ + __FUNCTION__,__LINE__,next_marker->update_marker); + complete(&next_marker->update_completion); + } + else { + GALLEN_DBGLOCAL_RUNLOG(12); + kfree(next_marker); + } + } + } + } + + /* Check to see if all updates have completed */ + if (list_empty(&fb_data->upd_pending_list) && + is_free_list_full(fb_data) && + !epdc_waiting_on_wb && + !epdc_luts_active) { + + GALLEN_DBGLOCAL_RUNLOG(13); + + fb_data->updates_active = false; + + if (fb_data->pwrdown_delay != FB_POWERDOWN_DISABLE) { + + GALLEN_DBGLOCAL_RUNLOG(14); + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule task to disable EPDC HW until next update */ + schedule_delayed_work(&fb_data->epdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) { + GALLEN_DBGLOCAL_RUNLOG(15); + complete(&fb_data->updates_done); + } + } + + /* + * Is Working Buffer busy (for normal update case)? + * Is UPD_DONE complete (for paused update case)? + */ + if ((!wb_pause && epdc_wb_busy) || (wb_pause && !epdc_upd_done)) { + /* Can't submit another update until WB is done */ + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + /* + * Were we waiting on working buffer? + * If so, update queues and check for collisions + */ + if (epdc_waiting_on_wb) { + GALLEN_DBGLOCAL_RUNLOG(16); + dev_dbg(fb_data->dev, "\nWorking buffer completed\n"); + + /* Signal completion if submit workqueue was waiting on WB */ + if (fb_data->waiting_for_wb && !wb_pause) { + GALLEN_DBGLOCAL_RUNLOG(17); + complete(&fb_data->update_res_free); + fb_data->waiting_for_wb = false; + } + + /* + * Handle 3 possible events exclusive of each other, in + * the following priority order: + * 1) This is a collision test update + * 2) The LUT was cancelled (no pixels changing) + * 3) The update is being paused after WB processing + */ + if (fb_data->cur_update->update_desc->upd_data.flags + & EPDC_FLAG_TEST_COLLISION) { + + GALLEN_DBGLOCAL_RUNLOG(18); + /* This was a dry run to test for collision */ + + /* Signal marker */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, + full_list) { + + GALLEN_DBGLOCAL_RUNLOG(19); + if (next_marker->lut_num != DRY_RUN_NO_LUT) { + GALLEN_DBGLOCAL_RUNLOG(20); + continue; + } + + if (epdc_collision) { + GALLEN_DBGLOCAL_RUNLOG(21); + next_marker->collision_test = true; + } + else { + GALLEN_DBGLOCAL_RUNLOG(22); + next_marker->collision_test = false; + } + + dev_dbg(fb_data->dev, + "In IRQ, collision_test = %d\n", + next_marker->collision_test); + + /* Found marker to signal - remove from list */ + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, "Signaling marker " + "for dry-run - %d\n", + next_marker->update_marker); + + DBG_MSG("%s()[%d]:update_complete @ marker %d\n",\ + __FUNCTION__,__LINE__,next_marker->update_marker); + complete(&next_marker->update_completion); + + update_is_valid = false; + } + } else if (epdc_lut_cancelled && !epdc_collision) { + GALLEN_DBGLOCAL_RUNLOG(23); + + /* + * Note: The update may be cancelled (void) if all + * pixels collided. In that case we handle it as a + * collision, not a cancel. + */ + + /* Clear LUT status (might be set if no AUTOWV used) */ + + /* + * Disable and clear IRQ for the LUT used. + * Even though LUT is cancelled in HW, the LUT + * complete bit may be set if AUTOWV not used. + */ + epdc_lut_complete_intr(fb_data->rev, + fb_data->cur_update->lut_num, false); + epdc_clear_lut_complete_irq(fb_data->rev, + fb_data->cur_update->lut_num); + + fb_data->lut_update_order[fb_data->cur_update->lut_num] = 0; + + /* Signal completion if submit workqueue needs a LUT */ + if (fb_data->waiting_for_lut) { + GALLEN_DBGLOCAL_RUNLOG(24); + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) { + + GALLEN_DBGLOCAL_RUNLOG(25); + + /* Del from per-update & full list */ + list_del_init(&next_marker->upd_list); + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker (cancelled) %d\n", + next_marker->update_marker); + if (next_marker->waiting) { + GALLEN_DBGLOCAL_RUNLOG(26); + DBG_MSG("%s()[%d]:update_complete @ marker %d\n",\ + __FUNCTION__,__LINE__,next_marker->update_marker); + complete(&next_marker->update_completion); + } + else { + GALLEN_DBGLOCAL_RUNLOG(27); + kfree(next_marker); + } + } + + update_is_valid = false; + } else if (fb_data->cur_update->update_desc->wb_pause) { + GALLEN_DBGLOCAL_RUNLOG(28); + dev_dbg(fb_data->dev, "\nShould be paused??\n"); + /* Disable UPD_DONE interrupt */ + epdc_upd_done_intr(false); + + /* Need to schedule task to process WB + before we can complete the update */ + queue_work(fb_data->epdc_aa_workqueue, + &fb_data->epdc_aa_work); + + update_paused = true; + } else { + dev_dbg(fb_data->dev, + "\nNo pausing this time folks!\n"); + } + + if (epdc_collision & update_is_valid) { + /* aa update (no dry-run), collision occurred */ + + /* Check list of colliding LUTs, and add to our collision mask */ + fb_data->cur_update->collision_mask = + fb_data->epdc_colliding_luts; + + /* Clear collisions that completed since WB began */ + fb_data->cur_update->collision_mask &= + ~fb_data->luts_complete_wb; + + dev_dbg(fb_data->dev, "Collision mask = 0x%llx\n", + fb_data->epdc_colliding_luts); + + /* For EPDC 2.0 and later, minimum collision bounds + are provided by HW. Recompute new bounds here. */ + if ((fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) + && (fb_data->rev >= 20)) { + u32 xres, yres, rotate; + int start, end; + struct mxcfb_rect *cur_upd_rect = + &fb_data->cur_update->update_desc->upd_data.update_region; + + GALLEN_DBGLOCAL_RUNLOG(29); + + /* Get collision region coords from EPDC */ + coll_coord = __raw_readl(EPDC_UPD_COL_CORD); + coll_size = __raw_readl(EPDC_UPD_COL_SIZE); + coll_region.left = + (coll_coord & EPDC_UPD_COL_CORD_XCORD_MASK) + >> EPDC_UPD_COL_CORD_XCORD_OFFSET; + coll_region.top = + (coll_coord & EPDC_UPD_COL_CORD_YCORD_MASK) + >> EPDC_UPD_COL_CORD_YCORD_OFFSET; + coll_region.width = + (coll_size & EPDC_UPD_COL_SIZE_WIDTH_MASK) + >> EPDC_UPD_COL_SIZE_WIDTH_OFFSET; + coll_region.height = + (coll_size & EPDC_UPD_COL_SIZE_HEIGHT_MASK) + >> EPDC_UPD_COL_SIZE_HEIGHT_OFFSET; + dev_dbg(fb_data->dev, "Coll region: l = %d, " + "t = %d, w = %d, h = %d\n", + coll_region.left, coll_region.top, + coll_region.width, coll_region.height); + + /* Convert coords back to orig orientation */ + switch (fb_data->epdc_fb_var.rotate) { + case FB_ROTATE_CW: + GALLEN_DBGLOCAL_RUNLOG(30); + xres = fb_data->epdc_fb_var.yres; + yres = fb_data->epdc_fb_var.xres; + rotate = FB_ROTATE_CCW; + break; + case FB_ROTATE_UD: + GALLEN_DBGLOCAL_RUNLOG(31); + xres = fb_data->epdc_fb_var.xres; + yres = fb_data->epdc_fb_var.yres; + rotate = FB_ROTATE_UD; + break; + case FB_ROTATE_CCW: + GALLEN_DBGLOCAL_RUNLOG(32); + xres = fb_data->epdc_fb_var.yres; + yres = fb_data->epdc_fb_var.xres; + rotate = FB_ROTATE_CW; + break; + default: + GALLEN_DBGLOCAL_RUNLOG(33); + xres = fb_data->epdc_fb_var.xres; + yres = fb_data->epdc_fb_var.yres; + rotate = FB_ROTATE_UR; + break; + } + adjust_coordinates(xres, yres, rotate, + &coll_region, cur_upd_rect); + + dev_dbg(fb_data->dev, "Adj coll region: l = %d, " + "t = %d, w = %d, h = %d\n", + cur_upd_rect->left, cur_upd_rect->top, + cur_upd_rect->width, + cur_upd_rect->height); + } + + /* + * If we collide with newer updates, then + * we don't need to re-submit the update. The + * idea is that the newer updates should take + * precedence anyways, so we don't want to + * overwrite them. + */ + for (temp_mask = fb_data->cur_update->collision_mask, lut = 0; + temp_mask != 0; + lut++, temp_mask = temp_mask >> 1) { + GALLEN_DBGLOCAL_RUNLOG(34); + if (!(temp_mask & 0x1)) { + GALLEN_DBGLOCAL_RUNLOG(35); + continue; + } + + if (fb_data->lut_update_order[lut] >= + fb_data->cur_update->update_desc->update_order) { + dev_dbg(fb_data->dev, + "Ignoring collision with" + "newer update.\n"); + ignore_collision = true; + break; + } + } + + if (!ignore_collision) { + GALLEN_DBGLOCAL_RUNLOG(36); + free_update = false; + /* + * If update has markers, clear the LUTs to + * avoid signalling that they have completed. + */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) + next_marker->lut_num = INVALID_LUT; + + /* Move to collision list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_collision_list); + } + } + + /* Do we need to free the current update descriptor? */ + if (free_update && !update_paused) { + GALLEN_DBGLOCAL_RUNLOG(37); + /* Handle condition where WB & LUT are both complete */ + if (wb_lut_done) { + GALLEN_DBGLOCAL_RUNLOG(38); + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) { + + + GALLEN_DBGLOCAL_RUNLOG(39); + + /* Del from per-update & full list */ + list_del_init(&next_marker->upd_list); + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker (wb) %d\n", + next_marker->update_marker); + if (next_marker->waiting) { + GALLEN_DBGLOCAL_RUNLOG(40); + DBG_MSG("%s()[%d]:update_complete @ marker %d\n",\ + __FUNCTION__,__LINE__,next_marker->update_marker); + complete(&next_marker->update_completion); + } + else { + GALLEN_DBGLOCAL_RUNLOG(41); + kfree(next_marker); + } + } + } + + /* Free marker list and update descriptor */ + kfree(fb_data->cur_update->update_desc); + + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list); + + /* Check to see if all updates have completed */ + if (list_empty(&fb_data->upd_pending_list) && + is_free_list_full(fb_data) && + !epdc_luts_active) { + + GALLEN_DBGLOCAL_RUNLOG(42); + + fb_data->updates_active = false; + + if (fb_data->pwrdown_delay != + FB_POWERDOWN_DISABLE) { + GALLEN_DBGLOCAL_RUNLOG(43); + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule EPDC disable */ + schedule_delayed_work(&fb_data->epdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) { + GALLEN_DBGLOCAL_RUNLOG(44); + complete(&fb_data->updates_done); + } + } + } + + if (!update_paused) { + /* Clear current update */ + fb_data->cur_update = NULL; + + /* Clear IRQ for working buffer */ + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + } + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + GALLEN_DBGLOCAL_RUNLOG(45); + /* Queued update scheme processing */ + + /* Schedule task to submit collision and pending update */ + if (!fb_data->powering_down) { + GALLEN_DBGLOCAL_RUNLOG(46); + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + } + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + GALLEN_DBGLOCAL_ESC(); + return; + } + + /* Snapshot update scheme processing */ + + /* Check to see if any LUTs are free */ + if (!epdc_luts_avail) { + dev_dbg(fb_data->dev, "No luts available.\n"); + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + epdc_next_lut_15 = epdc_choose_next_lut(fb_data->rev, &next_lut); + /* Check to see if there is a valid LUT to use */ + if (epdc_next_lut_15 && fb_data->tce_prevent && (fb_data->rev < 20)) { + dev_dbg(fb_data->dev, "Must wait for LUT15\n"); + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list, list) { + GALLEN_DBGLOCAL_RUNLOG(47); + + if (collision_update->collision_mask != 0) { + GALLEN_DBGLOCAL_RUNLOG(48); + continue; + } + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + /* + * We have a collision cleared, so select it + * and we will retry the update + */ + fb_data->cur_update = collision_update; + list_del_init(&fb_data->cur_update->list); + break; + } + + /* + * If we didn't find a collision update ready to go, + * we try to grab one from the update queue + */ + if (fb_data->cur_update == NULL) { + GALLEN_DBGLOCAL_RUNLOG(49); + /* Is update list empty? */ + if (list_empty(&fb_data->upd_buf_queue)) { + GALLEN_DBGLOCAL_RUNLOG(50); + dev_dbg(fb_data->dev, "No pending updates.\n"); + + /* No updates pending, so we are done */ + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_ESC(); + return; + } else { + GALLEN_DBGLOCAL_RUNLOG(51); + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + /* Process next item in update list */ + fb_data->cur_update = + list_entry(fb_data->upd_buf_queue.next, + struct update_data_list, list); + list_del_init(&fb_data->cur_update->list); + } + } + + /* Use LUT selected above */ + fb_data->cur_update->lut_num = next_lut; + + /* Associate LUT with update markers */ + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, upd_list) + next_marker->lut_num = fb_data->cur_update->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[fb_data->cur_update->lut_num] = + fb_data->cur_update->update_desc->update_order; + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->rev, fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + next_upd_region = + &fb_data->cur_update->update_desc->upd_data.update_region; + if (fb_data->cur_update->update_desc->upd_data.temp + != TEMP_USE_AMBIENT) { + GALLEN_DBGLOCAL_RUNLOG(52); + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, + fb_data->cur_update->update_desc->upd_data.temp); + } + epdc_set_temp(fb_data->temp_index); + epdc_set_update_addr(fb_data->cur_update->phys_addr + + fb_data->cur_update->update_desc->epdc_offs); + epdc_set_update_coord(next_upd_region->left, next_upd_region->top); + epdc_set_update_dimensions(next_upd_region->width, + next_upd_region->height); + if (fb_data->rev > 20) { + GALLEN_DBGLOCAL_RUNLOG(54); + epdc_set_update_stride(fb_data->cur_update->update_desc->epdc_stride); + } + if (fb_data->wv_modes_update && + (fb_data->cur_update->update_desc->upd_data.waveform_mode + == WAVEFORM_MODE_AUTO)) { + + GALLEN_DBGLOCAL_RUNLOG(55); + epdc_set_update_waveform(&fb_data->wv_modes); + fb_data->wv_modes_update = false; + } + + epdc_submit_update(fb_data->cur_update->lut_num, + fb_data->cur_update->update_desc->upd_data.waveform_mode, + fb_data->cur_update->update_desc->upd_data.update_mode, + false, fb_data->cur_update->update_desc->wb_pause, + false, 0); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + GALLEN_DBGLOCAL_END(); + return; +} + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data) +{ + u32 *upd_buf_ptr; + int i; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + + GALLEN_DBGLOCAL_BEGIN(); + + upd_buf_ptr = (u32 *)fb_data->info.screen_base; + + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->rev, 0, true); + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + GALLEN_DBGLOCAL_RUNLOG(0); + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + GALLEN_DBGLOCAL_RUNLOG(1); + xres = screeninfo->xres; + yres = screeninfo->yres; + } + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(fb_data->phys_start); + epdc_set_update_coord(0, 0); + epdc_set_update_dimensions(xres, yres); + if (fb_data->rev > 20) { + GALLEN_DBGLOCAL_RUNLOG(2); + epdc_set_update_stride(0); + } + epdc_submit_update(0, fb_data->wv_modes.mode_init, UPDATE_MODE_FULL, + false, false, true, 0xFF); + + dev_dbg(fb_data->dev, "Mode0 update - Waiting for LUT to complete...\n"); + + /* Will timeout after ~4-5 seconds */ + + for (i = 0; i < 40; i++) { + if (!epdc_is_lut_active(0)) { + dev_dbg(fb_data->dev, "Mode0 init complete\n"); + GALLEN_DBGLOCAL_ESC(); + return; + } + msleep(100); + } + + dev_err(fb_data->dev, "Mode0 init failed!\n"); + + GALLEN_DBGLOCAL_END(); + return; +} + + +static void mxc_epdc_fb_fw_handler(const struct firmware *fw, + void *context) +{ + struct mxc_epdc_fb_data *fb_data = context; + int ret; + struct mxcfb_waveform_data_file *wv_file; + int wv_data_offs; + int i; + struct mxcfb_update_data update; + struct mxcfb_update_marker_data upd_marker_data; + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + struct clk *epdc_parent; + unsigned long rounded_parent_rate, epdc_pix_rate, + rounded_pix_clk, target_pix_clk; + +// unsigned char bModeVersion ; +// unsigned char bWFM_REV ; + +#ifdef FW_IN_RAM //[ + struct firmware ram_fw; +#endif //] FW_IN_RAM + + GALLEN_DBGLOCAL_BEGIN(); + //msleep(10);// GALLEN DBG + + if (fw == NULL) { + GALLEN_DBGLOCAL_RUNLOG(0); + + /* If default FW file load failed, we give up */ + if (fb_data->fw_default_load) { + GALLEN_DBGLOCAL_ESC(); + return; + } + + /* Try to load default waveform */ + dev_dbg(fb_data->dev, + "Can't find firmware. Trying fallback fw\n"); + fb_data->fw_default_load = true; +#ifdef FW_IN_RAM//[ gallen modify 20110609 : waveform pass from bootloader in RAM . + { + + GALLEN_DBGLOCAL_RUNLOG(2); + + ram_fw.size = gdwWF_size; + ram_fw.data = (u8*)gpbWF_vaddr; + //fw.page = 0; + printk("[%s]:fw p=%p,size=%u\n",__FUNCTION__,ram_fw.data,ram_fw.size); + //mxc_epdc_fb_fw_handler(&fw,fb_data); + ret = 0; + fw=&ram_fw; + } +#else //][!FW_IN_RAM befrom modify ... + + + GALLEN_DBGLOCAL_RUNLOG(3); + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "imx/epdc.fw", fb_data->dev, GFP_KERNEL, fb_data, + mxc_epdc_fb_fw_handler); + if (ret) + dev_err(fb_data->dev, + "Failed request_firmware_nowait err %d\n", ret); +#endif //]FW_IN_RAM + + + + GALLEN_DBGLOCAL_ESC(); + return; + } + + + gbModeVersion = *(fw->data+0x10); + gbWFM_REV = *(fw->data+0x16); + gbFPL_Platform=*(fw->data+0x0d); + + + if((0x20==gbModeVersion)||(0x19==gbModeVersion)||(0x18==gbModeVersion)) + { + GALLEN_DBGLOCAL_RUNLOG(6); + fb_data->wv_modes.mode_gl16 = 3; /* GL16 mode */ + fb_data->wv_modes.mode_a2 = 6; /* A2 mode */ + if(0x18==gbModeVersion) { + fb_data->wv_modes.mode_gc4 = 2; /* GC4 mode */ + } + else { + fb_data->wv_modes.mode_gc4 = 7; /* GC4 mode */ + } + fb_data->wv_modes.mode_du = 1; /* DU mode */ + fb_data->wv_modes.mode_aa = 4; /* REAGL mode */ + fb_data->wv_modes.mode_aad = 5; /*REAGL-D mode */ + } + else if(0x13==gbModeVersion) { + fb_data->wv_modes.mode_du = 1; /* DU mode */ + fb_data->wv_modes.mode_a2 = 4; /* A2 mode */ + fb_data->wv_modes.mode_gl16 = 3; /* GL16 mode as GLR16*/ + fb_data->wv_modes.mode_aa = 3; /* REAGL mode as GLR16*/ + fb_data->wv_modes.mode_aad = 2; /*REAGL-D mode as GC16*/ + } + else if(0x23==gbModeVersion) { + // AD type non regal waveform . + fb_data->wv_modes.mode_du = 1; /* DU mode */ + fb_data->wv_modes.mode_a2 = 4; /* A2 mode */ + fb_data->wv_modes.mode_gl16 = 3; /* GL16 mode */ + fb_data->wv_modes.mode_aa = 3; /* REAGL mode as GLR16*/ + fb_data->wv_modes.mode_aad = 2; /*REAGL-D mode as GC16*/ + } + else if(0x4==gbModeVersion) { + fb_data->wv_modes.mode_gl16 = 5; /* GL16 mode */ + fb_data->wv_modes.mode_aa = 5; /* REAGL mode */ + fb_data->wv_modes.mode_aad = 5; /* REAGL-D mode */ + fb_data->wv_modes.mode_a2 = 4; /* A2 mode */ + } + else if(0x15==gbModeVersion||0x9==gbModeVersion) { + // WY type . + fb_data->wv_modes.mode_gl16 = 3; /* GL16 mode */ + fb_data->wv_modes.mode_aa = 2; /* REAGL mode */ + fb_data->wv_modes.mode_aad = 2; /* REAGL-D mode */ + fb_data->wv_modes.mode_a2 = 4; /* A2 mode */ + + // when mode version is 0x15 + // GC4=5 + // GL4=6 + + } + else { + fb_data->wv_modes.mode_gl16 = 2; /* GL16 mode */ + fb_data->wv_modes.mode_aa = 2; /* REAGL mode */ + fb_data->wv_modes.mode_aad = 2; /* REAGL-D mode */ + fb_data->wv_modes.mode_a2 = 1; /* A2 mode as DU */ + } +#ifdef NO_CUS_REAGL_MODE//[ + fb_data->wv_modes.mode_aa = fb_data->wv_modes.mode_gl16; /* REAGL mode */ + fb_data->wv_modes.mode_aad = fb_data->wv_modes.mode_gc16; /* REAGL-D mode */ +#endif //]NO_CUS_REAGL_MODE + fb_data->wv_modes_update = true; + + giNTX_waveform_modeA[NTX_WFM_MODE_INIT] = fb_data->wv_modes.mode_init; + giNTX_waveform_modeA[NTX_WFM_MODE_DU] = fb_data->wv_modes.mode_du; + giNTX_waveform_modeA[NTX_WFM_MODE_GC16] = fb_data->wv_modes.mode_gc16; + giNTX_waveform_modeA[NTX_WFM_MODE_GC4] = fb_data->wv_modes.mode_gc4; + giNTX_waveform_modeA[NTX_WFM_MODE_A2] = fb_data->wv_modes.mode_a2; + giNTX_waveform_modeA[NTX_WFM_MODE_GL16] = fb_data->wv_modes.mode_gl16; + giNTX_waveform_modeA[NTX_WFM_MODE_GLR16] = fb_data->wv_modes.mode_aa; + giNTX_waveform_modeA[NTX_WFM_MODE_GLD16] = fb_data->wv_modes.mode_aad; + + + + wv_file = (struct mxcfb_waveform_data_file *)fw->data; + + if(1==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1024x758 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[2];// + } + else if(3==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1440x1080 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[5];// + } + else if(5==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1448x1072 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[6];// + } + else if(6==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1600x1200 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[7];// + } + else if(2==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1024x768 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[3];// + } + else + { + unsigned char *pbWFHdr = (unsigned char *)(fw->data); + //if(*(pbWFHdr+0x15)==0xd5)// AMEPD is ED060SCQ . + if(gptHWCFG->m_val.bPCB>=46) + { + printk("%s(%d):using parameters of ED060SCQ \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[8];// . + } + else { + switch (*(pbWFHdr+0x0d)) {// FPL platform detect . + case 0x06: // V220 . + case 0x09: // V320 . + if(27==gptHWCFG->m_val.bPCB) { + printk("%s(%d):V220 for E50610 FPL platform \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[4];//v220 parameters for E50612 . + } + else { + printk("%s(%d):V220 FPL platform \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[0];//v220 parameters + } + break; + case 0x03: // V110 . + case 0x04: // V110A . + printk("%s(%d):V110/V110A FPL platform \n",__FILE__,__LINE__); + ASSERT(fb_data->pdata->num_modes>1); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[1];//v110 parameters + break; + default: + ERR_MSG("%s(%d):new FPL platform=0x%x ,please modify source to support this \n",__FILE__,__LINE__,*(pbWFHdr+0x0d)); + break; + } + } + } + + + //msleep(10);// GALLEN DBG . + + /* Get size and allocate temperature range table */ + fb_data->trt_entries = wv_file->wdh.trc + 1; + fb_data->temp_range_bounds = kzalloc(fb_data->trt_entries+1, GFP_KERNEL); + + /* Copy TRT data */ + memcpy(fb_data->temp_range_bounds, &wv_file->data, fb_data->trt_entries+1); + + for (i = 0; i <= fb_data->trt_entries; i++) { + GALLEN_DBGLOCAL_RUNLOG(4); + //dev_dbg(fb_data->dev, "trt entry #%d = 0x%x\n", i, *((u8 *)&wv_file->data + i)); + DBG_MSG("trt entry #%d = 0x%x(%d~%d)\n", + i, *((u8 *)&wv_file->data + i),fb_data->temp_range_bounds[i],fb_data->temp_range_bounds[i+1]-1); + } + + + /* Set default temperature index using TRT and room temp */ + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP); + + /* Get offset and size for waveform data */ + wv_data_offs = sizeof(wv_file->wdh) + fb_data->trt_entries + 1; + fb_data->waveform_buffer_size = fw->size - wv_data_offs; + + /* process 2nd generation waveform data which may contain + * the voltage control data, advance waveform data, + * and extra waveform data + */ + + { + int awv, wmc, wtrc, xwia; + u64 longOffset; + u32 bufferSize; + u8 *fwDataBuffer = (u8 *)(fw->data) + wv_data_offs; + u8 *waveform_xwi_buffer = NULL; + + wtrc = wv_file->wdh.trc + 1; + wmc = wv_file->wdh.mc + 1; + awv = wv_file->wdh.awv; + xwia = wv_file->wdh.xwia; + memcpy (&longOffset,fwDataBuffer,8); +// printk("%s() : awv=0x%02X\n",__FUNCTION__,awv); + if ((unsigned) longOffset > (8*wmc)) + { + u64 avcOffset, acdOffset, acdMagic, xwiOffset; + avcOffset = acdOffset = acdMagic = xwiOffset = 0l; + /* look at the advance waveform flags */ + switch ( awv ) { + case 0 : /* voltage control flag is set */ + if (xwia > 0) { + /* extra waveform information */ + memcpy (&xwiOffset,fwDataBuffer + (8*wmc),8); + bufferSize = (unsigned)(fb_data->waveform_buffer_size - xwiOffset); + fb_data->waveform_xwi_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_xwi_buffer, fwDataBuffer+xwiOffset, bufferSize ); + fb_data->waveform_buffer_size = xwiOffset; + } + break; + case 1 : /* voltage control flag is set */ + memcpy (&avcOffset,fwDataBuffer + (8*wmc),8); + if (xwia > 0) { + /* extra waveform information */ + memcpy (&xwiOffset,fwDataBuffer + (8*wmc) +8,8); + bufferSize = (unsigned)(fb_data->waveform_buffer_size - xwiOffset); + fb_data->waveform_xwi_buffer = kmalloc(bufferSize, GFP_KERNEL); + /* voltage control data */ + memcpy(fb_data->waveform_xwi_buffer, fwDataBuffer+xwiOffset, bufferSize ); + bufferSize = (unsigned)(xwiOffset - avcOffset); + fb_data->waveform_vcd_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_vcd_buffer, fwDataBuffer+avcOffset, bufferSize ); + } + else { + /* voltage control data */ + bufferSize = (unsigned)(fb_data->waveform_buffer_size - avcOffset); + fb_data->waveform_vcd_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_vcd_buffer, fwDataBuffer+avcOffset, bufferSize ); + } + fb_data->waveform_buffer_size = avcOffset; + break; + case 2 : /* voltage control flag is set */ + memcpy (&acdOffset,fwDataBuffer + (8*wmc),8); + memcpy (&acdMagic,fwDataBuffer + (8*wmc) + 8,8); + if (xwia > 0) { + /* extra waveform information */ + memcpy (&xwiOffset,fwDataBuffer + (8*wmc) + 16,8); + bufferSize = (unsigned)(fb_data->waveform_buffer_size - xwiOffset); + fb_data->waveform_xwi_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_xwi_buffer, fwDataBuffer+xwiOffset, bufferSize ); + /* algorithm control data */ + bufferSize = (unsigned)(xwiOffset - acdOffset); + fb_data->waveform_acd_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_acd_buffer, fwDataBuffer+acdOffset, bufferSize ); + } + else { + /* algorithm control data */ + bufferSize = (unsigned)(fb_data->waveform_buffer_size - acdOffset); + fb_data->waveform_acd_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_acd_buffer, fwDataBuffer+acdOffset, bufferSize ); + } + fb_data->waveform_buffer_size = acdOffset; + break; + case 3 : /* voltage control flag is set */ + memcpy (&avcOffset,fwDataBuffer + (8*wmc),8); + memcpy (&acdOffset,fwDataBuffer + (8*wmc) + 8,8); + memcpy (&acdMagic,fwDataBuffer + (8*wmc) + 16,8); + if (xwia > 0) { + /* extra waveform information */ + memcpy (&xwiOffset,fwDataBuffer + (8*wmc) + 24,8); + bufferSize = (unsigned)(fb_data->waveform_buffer_size - xwiOffset); + fb_data->waveform_xwi_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_xwi_buffer, fwDataBuffer+xwiOffset, bufferSize ); + /* algorithm control data */ + bufferSize = (unsigned)(xwiOffset - acdOffset); + fb_data->waveform_acd_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_acd_buffer, fwDataBuffer+acdOffset, bufferSize ); + /* voltage control data */ + bufferSize = (unsigned)(acdOffset - avcOffset); + fb_data->waveform_vcd_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_vcd_buffer, fwDataBuffer+avcOffset, bufferSize ); + } + else { + /* algorithm control data */ + bufferSize = (unsigned)(fb_data->waveform_buffer_size - acdOffset); + fb_data->waveform_acd_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_acd_buffer, fwDataBuffer+avcOffset, bufferSize ); + /* voltage control data */ + bufferSize = (unsigned)(acdOffset - avcOffset); + fb_data->waveform_vcd_buffer = kmalloc(bufferSize, GFP_KERNEL); + memcpy(fb_data->waveform_vcd_buffer, fwDataBuffer+avcOffset, bufferSize ); + } + fb_data->waveform_buffer_size = avcOffset; + break; + } + if (acdMagic) fb_data->waveform_magic_number = acdMagic; + /* store the waveform mode count and waveform temperature range count + */ + fb_data->waveform_mc = wmc; + fb_data->waveform_trc =wtrc; + } + } + + /* get the extra waveform info and display it - This can be removed! It is here for illustration only */ + if (fb_data->waveform_xwi_buffer) { + //char *xwiString; + unsigned strLength = mxc_epdc_fb_fetch_wxi_data(fb_data->waveform_xwi_buffer, NULL); + + dev_info(fb_data->dev, " --- Extra Waveform Data length: %d bytes---\n",strLength); + if (strLength > 0) { + //xwiString = (char *) kmalloc(strLength + 1, GFP_KERNEL); + fb_data->waveform_xwi_string = (char *) kmalloc(strLength + 1, GFP_KERNEL); + if (mxc_epdc_fb_fetch_wxi_data(fb_data->waveform_xwi_buffer, fb_data->waveform_xwi_string) > 0) + { + fb_data->waveform_xwi_string[strLength+1] = '\0'; + fb_data->waveform_xwi_string_length = strLength; + //xwiString[strLength+1] = '\0'; + dev_info(fb_data->dev, " Extra Waveform Data: %s\n",fb_data->waveform_xwi_string); + } + else + dev_err(fb_data->dev, " *** Extra Waveform Data checksum error ***\n"); + + //kfree(xwiString); + } + } + + /* show the vcd */ + if (fb_data->waveform_vcd_buffer) { + { + struct epd_vc_data vcd; + /* fetch and display the voltage control data for waveform mode 0, temp range 0 */ + fetch_Epdc_Pmic_Voltages(&vcd, fb_data, 0, 0); + } + } + + /* Allocate memory for waveform data */ + fb_data->waveform_buffer_virt = dma_alloc_coherent(fb_data->dev, + fb_data->waveform_buffer_size, + &fb_data->waveform_buffer_phys, + GFP_DMA); + if (fb_data->waveform_buffer_virt == NULL) { + dev_err(fb_data->dev, "Can't allocate mem for waveform!\n"); + GALLEN_DBGLOCAL_ESC(); + return; + } + + DBG_MSG("[%s]waveform:vir_p=%p,phy_p=%p,sz=%d,trt_entries=%d\n",__FUNCTION__,\ + fb_data->waveform_buffer_virt,fb_data->waveform_buffer_phys,\ + fb_data->waveform_buffer_size,fb_data->trt_entries); + + memcpy(fb_data->waveform_buffer_virt, (u8 *)(fw->data) + wv_data_offs, + fb_data->waveform_buffer_size); + + /* Read field to determine if 4-bit or 5-bit mode */ + if ((wv_file->wdh.luts & 0xC) == 0x4) + fb_data->buf_pix_fmt = EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N; + else + fb_data->buf_pix_fmt = EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N; + +#ifdef FW_IN_RAM//[ +#else//][!FW_IN_RAM + + release_firmware(fw); +#endif //]FW_IN_RAM + + + /* Enable clocks to access EPDC regs */ + clk_enable(fb_data->epdc_clk_axi); + + target_pix_clk = fb_data->cur_mode->vmode->pixclock; + /* Enable pix clk for EPDC */ + clk_enable(fb_data->epdc_clk_pix); + rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk); + + if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || + (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) { + GALLEN_DBGLOCAL_RUNLOG(5); + /* Can't get close enough without changing parent clk */ + epdc_parent = clk_get_parent(fb_data->epdc_clk_pix); + rounded_parent_rate = clk_round_rate(epdc_parent, target_pix_clk); + + epdc_pix_rate = target_pix_clk; + while (epdc_pix_rate < rounded_parent_rate) { + GALLEN_DBGLOCAL_RUNLOG(6); + epdc_pix_rate *= 2; + } + clk_set_rate(epdc_parent, epdc_pix_rate); + + rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk); + if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || + (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) { + /* Still can't get a good clock, provide warning */ + dev_err(fb_data->dev, "Unable to get an accurate EPDC pix clk" + "desired = %lu, actual = %lu\n", target_pix_clk, + rounded_pix_clk); + + GALLEN_DBGLOCAL_RUNLOG(7); + } + } + + clk_set_rate(fb_data->epdc_clk_pix, rounded_pix_clk); + + epdc_init_sequence(fb_data); + + /* Disable clocks */ + clk_disable(fb_data->epdc_clk_axi); + clk_disable(fb_data->epdc_clk_pix); + + fb_data->hw_ready = true; + fb_data->hw_initializing = false; + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + + GALLEN_DBGLOCAL_RUNLOG(8); + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + GALLEN_DBGLOCAL_RUNLOG(9); + xres = screeninfo->xres; + yres = screeninfo->yres; + } + +#if 0 //[ gallen remove . + + update.update_region.left = 0; + update.update_region.width = xres; + update.update_region.top = 0; + update.update_region.height = yres; + update.update_mode = UPDATE_MODE_FULL; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + upd_marker_data.update_marker = update.update_marker; + + mxc_epdc_fb_send_update(&update, &fb_data->info); + + /* Block on initial update */ + ret = mxc_epdc_fb_wait_update_complete(&upd_marker_data, + &fb_data->info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for initial update complete failed." + " Error = 0x%x", ret); +#endif //] + + //msleep(10);// GALLEN DBG . + k_fake_s1d13522_init((unsigned char *)gpbLOGO_vaddr); + //k_fake_s1d13522_init(0); + GALLEN_DBGLOCAL_END(); +} + +static int mxc_epdc_fb_init_hw(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int ret; + + /* + * Create fw search string based on ID string in selected videomode. + * Format is "imx/epdc_[panel string].fw" + */ + if (fb_data->cur_mode) { + strcat(fb_data->fw_str, "imx/epdc_"); + strcat(fb_data->fw_str, fb_data->cur_mode->vmode->name); + strcat(fb_data->fw_str, ".fw"); + } + + fb_data->fw_default_load = false; +#ifdef FW_IN_RAM//[ gallen modify 20110609 : waveform pass from bootloader in RAM . + #if 1 + //queue_work(fb_data->epdc_firmware_workqueue,&fb_data->epdc_firmware_work); + schedule_delayed_work(&fb_data->epdc_firmware_work,0); + + ret = 0; + #else + { + + struct firmware fw; + + fw.size = gdwWF_size; + fw.data = gpbWF_vaddr; + //fw.page = 0; + printk("[%s]:fw p=%p,size=%u\n",__FUNCTION__,fw.data,fw.size); + mxc_epdc_fb_fw_handler(&fw,fb_data); + ret = 0; + } + #endif +#else //][ !FW_IN_RAM befrom modify ... + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fb_data->fw_str, fb_data->dev, GFP_KERNEL, + fb_data, mxc_epdc_fb_fw_handler); + if (ret) + dev_dbg(fb_data->dev, + "Failed request_firmware_nowait err %d\n", ret); +#endif //] FW_IN_RAM + return ret; +} + +static ssize_t store_update(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxcfb_update_data update; + struct fb_info *info = dev_get_drvdata(device); + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + if (strncmp(buf, "direct", 6) == 0) + update.waveform_mode = fb_data->wv_modes.mode_du; + else if (strncmp(buf, "gc16", 4) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc16; + else if (strncmp(buf, "gc4", 3) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc4; + + /* Now, request full screen update */ + update.update_region.left = 0; + update.update_region.width = fb_data->epdc_fb_var.xres; + update.update_region.top = 0; + update.update_region.height = fb_data->epdc_fb_var.yres; + update.update_mode = UPDATE_MODE_FULL; + update.temp = TEMP_USE_AMBIENT; + update.update_marker = 0; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, info); + + return count; +} + +static struct device_attribute fb_attrs[] = { + __ATTR(update, S_IRUGO|S_IWUSR, NULL, store_update), +}; + +int __devinit mxc_epdc_fb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_epdc_fb_data *fb_data; + struct resource *res; + struct fb_info *info; + char *options, *opt; + char *panel_str = NULL; + char name[] = "mxcepdcfb"; + struct fb_videomode *vmode; + int xres_virt, yres_virt, buf_size; + int xres_virt_rot, yres_virt_rot, pix_size_rot; + struct fb_var_screeninfo *var_info; + struct fb_fix_screeninfo *fix_info; + struct pxp_config_data *pxp_conf; + struct pxp_proc_data *proc_data; + struct scatterlist *sg; + struct update_data_list *upd_list; + struct update_data_list *plist, *temp_list; + int i; + unsigned long x_mem_size = 0; + u32 val; + + fb_data = (struct mxc_epdc_fb_data *)framebuffer_alloc( + sizeof(struct mxc_epdc_fb_data), &pdev->dev); + if (fb_data == NULL) { + ret = -ENOMEM; + goto out; + } + + + /* Get platform data and check validity */ + fb_data->pdata = pdev->dev.platform_data; + if ((fb_data->pdata == NULL) || (fb_data->pdata->num_modes < 1) + || (fb_data->pdata->epdc_mode == NULL) + || (fb_data->pdata->epdc_mode->vmode == NULL)) { + ret = -EINVAL; + goto out_fbdata; + } + + DBG_MSG("[%s]epdc_fb_data@%p size=%d,pdata@%p\n", + __FUNCTION__,fb_data,sizeof(struct mxc_epdc_fb_data), + fb_data->pdata); + + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto out_fbdata; + } + + fb_data->tce_prevent = 1; + + if (options) + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + fb_data->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + else if (!strncmp(opt, "x_mem=", 6)) + x_mem_size = memparse(opt + 6, NULL); + else if (!strncmp(opt, "tce_prevent", 11)) + fb_data->tce_prevent = 1; + else if (panel_str == NULL) + panel_str = opt; + else dev_err(fb_data->dev, " --> invalid epdc options - %s\n", opt); + } + + fb_data->dev = &pdev->dev; + + if (!fb_data->default_bpp) { + fb_data->default_bpp = default_bpp; + } + + /* Set default (first defined mode) before searching for a match */ + if(1==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1024x758 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[2];// + } + else if(3==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1440x1080 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[5];// + } + else if(5==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1448x1072 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[6];// + } + else if(6==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1600x1200 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[7];// + } + else if(2==gptHWCFG->m_val.bDisplayResolution) { + printk("%s(%d):EPD 1024x768 \n",__FILE__,__LINE__); + fb_data->cur_mode = &fb_data->pdata->epdc_mode[3];// + } + else { + fb_data->cur_mode = &fb_data->pdata->epdc_mode[0]; + } + + if(panel_str) { + printk("-> requested panel string %s\n",panel_str); + for (i = 0; i < fb_data->pdata->num_modes; i++) { + printk("-> epdc modes[%d] - %s\n", i, fb_data->pdata->epdc_mode[i].vmode->name); + + if (!strcmp(fb_data->pdata->epdc_mode[i].vmode->name, + panel_str)) { + fb_data->cur_mode = + &fb_data->pdata->epdc_mode[i]; + break; + } + } + } + + vmode = fb_data->cur_mode->vmode; + + platform_set_drvdata(pdev, fb_data); + info = &fb_data->info; + + /* Allocate color map for the FB */ + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) + goto out_fbdata; + + dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n", + vmode->xres, vmode->yres, fb_data->default_bpp); + + DBG_MSG("resolution %dx%d,bpp %d\n",vmode->xres, vmode->yres, fb_data->default_bpp); + /* + * GPU alignment restrictions dictate framebuffer parameters: + * - 32-byte alignment for buffer width + * - 128-byte alignment for buffer height + * => 4K buffer alignment for buffer start + */ + xres_virt = ALIGN(vmode->xres, 32); + yres_virt = ALIGN(vmode->yres, 128); + fb_data->max_pix_size = PAGE_ALIGN(xres_virt * yres_virt); + + /* + * Have to check to see if aligned buffer size when rotated + * is bigger than when not rotated, and use the max + */ + xres_virt_rot = ALIGN(vmode->yres, 32); + yres_virt_rot = ALIGN(vmode->xres, 128); + pix_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot); + fb_data->max_pix_size = (fb_data->max_pix_size > pix_size_rot) ? + fb_data->max_pix_size : pix_size_rot; + + buf_size = fb_data->max_pix_size * fb_data->default_bpp/8; + + /* Compute the number of screens needed based on X memory requested */ + if (x_mem_size > 0) { + fb_data->num_screens = DIV_ROUND_UP(x_mem_size, buf_size); + if (fb_data->num_screens < NUM_SCREENS_MIN) + fb_data->num_screens = NUM_SCREENS_MIN; + else if (buf_size * fb_data->num_screens > SZ_16M) + fb_data->num_screens = SZ_16M / buf_size; + } else + fb_data->num_screens = NUM_SCREENS_MIN; + + fb_data->map_size = buf_size * fb_data->num_screens; + dev_dbg(&pdev->dev, "memory to allocate: %d\n", fb_data->map_size); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto out_cmap; + } + + epdc_base = ioremap(res->start, SZ_4K); + if (epdc_base == NULL) { + ret = -ENOMEM; + goto out_cmap; + } + + /* Allocate FB memory */ + + if(0==gptHWCFG->m_val.bUIStyle) { + info->screen_base = dma_alloc_writecombine(&pdev->dev, + fb_data->map_size<<1, + &fb_data->phys_start, + GFP_DMA); + } + else { + info->screen_base = dma_alloc_writecombine(&pdev->dev, + fb_data->map_size, + &fb_data->phys_start, + GFP_DMA); + } + + if (info->screen_base == NULL) { + ret = -ENOMEM; + goto out_mapregs; + } + dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", info->screen_base, + fb_data->phys_start); + + var_info = &info->var; + var_info->activate = FB_ACTIVATE_TEST; + var_info->bits_per_pixel = fb_data->default_bpp; + var_info->xres = vmode->xres; + var_info->yres = vmode->yres; + var_info->xres_virtual = xres_virt; + /* Additional screens allow for panning and buffer flipping */ + var_info->yres_virtual = yres_virt * fb_data->num_screens; + + //var_info->pixclock = vmode->pixclock; + // Should be (1000000000000LL/vmode->pixclock) but the kernel does not have long long division library! + var_info->pixclock = 1000000000/(vmode->pixclock/1000); + + var_info->left_margin = vmode->left_margin; + var_info->right_margin = vmode->right_margin; + var_info->upper_margin = vmode->upper_margin; + var_info->lower_margin = vmode->lower_margin; + var_info->hsync_len = vmode->hsync_len; + var_info->vsync_len = vmode->vsync_len; + var_info->vmode = FB_VMODE_NONINTERLACED; + + if (2 == gptHWCFG->m_val.bUIStyle) { //Android + if (5 == gptHWCFG->m_val.bDisplayPanel || 10 == gptHWCFG->m_val.bDisplayPanel || 12 == gptHWCFG->m_val.bDisplayPanel) { //6 top, 6.8 bottom, 13.3 left + var_info->rotate = FB_ROTATE_UD; + } + } + + switch (fb_data->default_bpp) { + case 32: + case 24: + var_info->red.offset = 16; + var_info->red.length = 8; + var_info->green.offset = 8; + var_info->green.length = 8; + var_info->blue.offset = 0; + var_info->blue.length = 8; + break; + + case 16: + var_info->red.offset = 11; + var_info->red.length = 5; + var_info->green.offset = 5; + var_info->green.length = 6; + var_info->blue.offset = 0; + var_info->blue.length = 5; + break; + + case 8: + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var_info->grayscale = GRAYSCALE_8BIT; + + var_info->red.length = 8; + var_info->red.offset = 0; + var_info->red.msb_right = 0; + var_info->green.length = 8; + var_info->green.offset = 0; + var_info->green.msb_right = 0; + var_info->blue.length = 8; + var_info->blue.offset = 0; + var_info->blue.msb_right = 0; + break; + + default: + dev_err(&pdev->dev, "unsupported bitwidth %d\n", + fb_data->default_bpp); + ret = -EINVAL; + goto out_dma_fb; + } + + fix_info = &info->fix; + + strcpy(fix_info->id, "mxc_epdc_fb"); + fix_info->type = FB_TYPE_PACKED_PIXELS; + fix_info->visual = FB_VISUAL_TRUECOLOR; + fix_info->xpanstep = 0; + fix_info->ypanstep = 0; + fix_info->ywrapstep = 0; + fix_info->accel = FB_ACCEL_NONE; + fix_info->smem_start = fb_data->phys_start; + fix_info->smem_len = fb_data->map_size; + fix_info->ypanstep = 0; + + fb_data->native_width = vmode->xres; + fb_data->native_height = vmode->yres; + + info->fbops = &mxc_epdc_fb_ops; + + //DBG_MSG("%s():info@%p,info->fbops@%p,info->fbops->fbmmap@%p\n", + // __FUNCTION__,info,info->fbops,info->fbops->fb_mmap); + info->var.activate = FB_ACTIVATE_NOW; + info->pseudo_palette = fb_data->pseudo_palette; + info->screen_size = info->fix.smem_len; + info->flags = FBINFO_FLAG_DEFAULT; + + mxc_epdc_fb_set_fix(info); + + fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE; + fb_data->upd_scheme = UPDATE_SCHEME_QUEUE_AND_MERGE; + //fb_data->upd_scheme = UPDATE_SCHEME_SNAPSHOT; + + /* Initialize our internal copy of the screeninfo */ + fb_data->epdc_fb_var = *var_info; + fb_data->fb_offset = 0; + fb_data->eof_sync_period = 0; + + fb_data->epdc_clk_axi = clk_get(fb_data->dev, "epdc_axi"); + if (IS_ERR(fb_data->epdc_clk_axi)) { + dev_err(&pdev->dev, "Unable to get EPDC AXI clk." + "err = 0x%x\n", (int)fb_data->epdc_clk_axi); + ret = -ENODEV; + goto out_dma_fb; + } + fb_data->epdc_clk_pix = clk_get(fb_data->dev, "epdc_pix"); + if (IS_ERR(fb_data->epdc_clk_pix)) { + dev_err(&pdev->dev, "Unable to get EPDC pix clk." + "err = 0x%x\n", (int)fb_data->epdc_clk_pix); + ret = -ENODEV; + goto out_dma_fb; + } + + clk_enable(fb_data->epdc_clk_axi); + val = __raw_readl(EPDC_VERSION); + clk_disable(fb_data->epdc_clk_axi); + fb_data->rev = ((val & EPDC_VERSION_MAJOR_MASK) >> + EPDC_VERSION_MAJOR_OFFSET) * 10 + + ((val & EPDC_VERSION_MINOR_MASK) >> + EPDC_VERSION_MINOR_OFFSET); + dev_dbg(&pdev->dev, "EPDC version = %d\n", fb_data->rev); + + if (fb_data->rev < 20) { + fb_data->num_luts = EPDC_V1_NUM_LUTS; + fb_data->max_num_updates = EPDC_V1_MAX_NUM_UPDATES; + } else { + fb_data->num_luts = EPDC_V2_NUM_LUTS; + fb_data->max_num_updates = EPDC_V2_MAX_NUM_UPDATES; + if (vmode->xres > EPDC_V2_MAX_UPDATE_WIDTH) + fb_data->restrict_width = true; + } + fb_data->max_num_buffers = EPDC_MAX_NUM_BUFFERS; + + /* + * Initialize lists for pending updates, + * active update requests, update collisions, + * and freely available updates. + */ + INIT_LIST_HEAD(&fb_data->upd_pending_list); + INIT_LIST_HEAD(&fb_data->upd_buf_queue); + INIT_LIST_HEAD(&fb_data->upd_buf_free_list); + INIT_LIST_HEAD(&fb_data->upd_buf_collision_list); + + /* Allocate update buffers and add them to the list */ + for (i = 0; i < fb_data->max_num_updates; i++) { + upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL); + if (upd_list == NULL) { + ret = -ENOMEM; + goto out_upd_lists; + } + + /* Add newly allocated buffer to free list */ + list_add(&upd_list->list, &fb_data->upd_buf_free_list); + } + + fb_data->virt_addr_updbuf = + kzalloc(sizeof(void *) * fb_data->max_num_buffers, GFP_KERNEL); + fb_data->phys_addr_updbuf = + kzalloc(sizeof(dma_addr_t) * fb_data->max_num_buffers, + GFP_KERNEL); + for (i = 0; i < fb_data->max_num_buffers; i++) { + /* + * Allocate memory for PxP output buffer. + * Each update buffer is 1 byte per pixel, and can + * be as big as the full-screen frame buffer + */ + fb_data->virt_addr_updbuf[i] = + kmalloc(fb_data->max_pix_size, GFP_KERNEL); + fb_data->phys_addr_updbuf[i] = + virt_to_phys(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf[i] == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + dev_dbg(fb_data->info.device, "allocated %d bytes @ 0x%08X\n", + fb_data->max_pix_size, fb_data->phys_addr_updbuf[i]); + } + + /* Counter indicating which update buffer should be used next. */ + fb_data->upd_buffer_num = 0; + + /* + * Allocate memory for PxP SW workaround buffer + * These buffers are used to hold copy of the update region, + * before sending it to PxP for processing. + */ + fb_data->virt_addr_copybuf = + dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, + &fb_data->phys_addr_copybuf, GFP_DMA); + if (fb_data->virt_addr_copybuf == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + fb_data->working_buffer_size = vmode->yres * vmode->xres * 2; + /* Allocate EPDC working buffer A (main WB Processing buffer) */ + fb_data->working_buffer_A_virt = + kmalloc(fb_data->working_buffer_size, GFP_KERNEL); + fb_data->working_buffer_A_phys = + virt_to_phys(fb_data->working_buffer_A_virt); + if (fb_data->working_buffer_A_virt == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for working buf A!\n"); + ret = -ENOMEM; + goto out_copybuffer; + } + + /* Allocate EPDC working buffer B (post-waveform algorithm) */ + fb_data->working_buffer_B_virt = + kmalloc(fb_data->working_buffer_size, GFP_KERNEL); + fb_data->working_buffer_B_phys = + virt_to_phys(fb_data->working_buffer_B_virt); + if (fb_data->working_buffer_B_virt == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for working buf B!\n"); + ret = -ENOMEM; + goto out_dma_work_buf_A; + } + + /* Initialize EPDC pins */ + if (fb_data->pdata->get_pins) + fb_data->pdata->get_pins(); + + fb_data->in_init = false; + + fb_data->hw_ready = false; + fb_data->hw_initializing = false; + + /* + * Set default waveform mode values. + * Should be overwritten via ioctl. + */ + fb_data->wv_modes.mode_init = 0; + fb_data->wv_modes.mode_du = 1; + fb_data->wv_modes.mode_gc4 = 2; + fb_data->wv_modes.mode_gc8 = 2; + fb_data->wv_modes.mode_gc16 = 2; + fb_data->wv_modes.mode_gc32 = 2; + /* + * advance algorithm flow + */ + fb_data->wv_modes.mode_aa = 3; /* default REAGL mode */ + fb_data->wv_modes.mode_aad = 3; /* default REAGL-D mode */ + fb_data->wv_modes.mode_gl16 = 2; /* default GL16 mode */ + fb_data->wv_modes.mode_a2 = 1; /* default A2 mode */ + + fb_data->wv_modes_update = true; + + /* Initialize marker list */ + INIT_LIST_HEAD(&fb_data->full_marker_list); + + /* Initialize all LUTs to inactive */ + fb_data->lut_update_order = + kzalloc(fb_data->num_luts * sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < fb_data->num_luts; i++) + fb_data->lut_update_order[i] = 0; + + INIT_DELAYED_WORK(&fb_data->epdc_done_work, epdc_done_work_func); + + fb_data->epdc_submit_workqueue = alloc_workqueue("EPDC Submit", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->epdc_submit_work, epdc_submit_work_func); + + fb_data->epdc_intr_workqueue = alloc_workqueue("EPDC Interrupt", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->epdc_intr_work, epdc_intr_work_func); + fb_data->epdc_aa_workqueue = alloc_workqueue("EPDC AA", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->epdc_aa_work, epdc_aa_work_func); + +#ifdef FW_IN_RAM//[ + //fb_data->epdc_firmware_workqueue = alloc_workqueue("EPDC Firmware",0, 1); + //INIT_WORK(&fb_data->epdc_firmware_work, epdc_firmware_work_func); + sema_init(&fb_data->firmware_work_lock,1); + INIT_DELAYED_WORK(&fb_data->epdc_firmware_work, epdc_firmware_work_func); +#endif //]FW_IN_RAM + + /* Retrieve EPDC IRQ num */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto out_dma_work_buf_B; + } + fb_data->epdc_irq = res->start; + + /* Register IRQ handler */ + ret = request_irq(fb_data->epdc_irq, mxc_epdc_irq_handler, 0, + "fb_dma", fb_data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + fb_data->epdc_irq, ret); + ret = -ENODEV; + goto out_dma_work_buf_B; + } + + info->fbdefio = &mxc_epdc_fb_defio; +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_init(info); +#endif + +#ifdef USE_BSP_PMIC//[ + /* get pmic regulators */ + fb_data->display_regulator = regulator_get(NULL, "DISPLAY"); + if (IS_ERR(fb_data->display_regulator)) { + dev_err(&pdev->dev, "Unable to get display PMIC regulator." + "err = 0x%x\n", (int)fb_data->display_regulator); + ret = -ENODEV; + goto out_irq; + } + fb_data->vcom_regulator = regulator_get(NULL, "VCOM"); + if (IS_ERR(fb_data->vcom_regulator)) { + regulator_put(fb_data->display_regulator); + dev_err(&pdev->dev, "Unable to get VCOM regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto out_regulator1; + } + + /* save the nominal vcom value */ + fb_data->wfm = 0; /* initial waveform mode should be INIT */ + vcom_nominal = regulator_get_voltage(fb_data->vcom_regulator); /* save the vcom_nominal value in uV */ + + fb_data->v3p3_regulator = regulator_get(NULL, "V3P3"); + if (IS_ERR(fb_data->v3p3_regulator)) { + regulator_put(fb_data->vcom_regulator); + regulator_put(fb_data->display_regulator); + dev_err(&pdev->dev, "Unable to get V3P3 regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto out_regulator2; + } +#else //][!USE_BSP_PMIC + if( NTXHWCFG_TST_FLAG(gptHWCFG->m_val.bPCB_Flags,4)|| + NTXHWCFG_TST_FLAG(gptHWCFG->m_val.bPCB_Flags,5)) + { + // Panel is designed for low voltage or VDD source is not comes from PMIC . + fb_data->vdd_1v8_regulator = regulator_get(NULL, "ldo_1v8"); + if (IS_ERR(fb_data->vdd_1v8_regulator)) { + dev_err(&pdev->dev, "Unable to get vdd 1.8v regulator." + "err = 0x%x\n", (int)fb_data->vdd_1v8_regulator); + ret = -ENODEV; + goto out_irq; + } + regulator_enable(fb_data->vdd_1v8_regulator); + } + + + fb_data->wfm = 0; /* initial waveform mode should be INIT */ +#endif //]USE_BSP_PMIC + + if (device_create_file(info->dev, &fb_attrs[0])) + dev_err(&pdev->dev, "Unable to create file from fb_attrs\n"); + + fb_data->cur_update = NULL; + + mutex_init(&fb_data->queue_mutex); + mutex_init(&fb_data->pxp_mutex); + mutex_init(&fb_data->power_mutex); + + /* + * Fill out PxP config data structure based on FB info and + * processing tasks required + */ + pxp_conf = &fb_data->pxp_conf; + proc_data = &pxp_conf->proc_data; + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = fb_data->info.var.xres; + proc_data->drect.height = proc_data->srect.height = fb_data->info.var.yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = 0; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + proc_data->lut_map = NULL; + + /* + * We initially configure PxP for RGB->YUV conversion, + * and only write out Y component of the result. + */ + + /* + * Initialize S0 channel parameters + * Parameters should match FB format/width/height + */ + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->s0_param.width = fb_data->info.var.xres_virtual; + pxp_conf->s0_param.height = fb_data->info.var.yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize OL0 channel parameters + * No overlay will be used for PxP operation + */ + for (i = 0; i < 8; i++) { + pxp_conf->ol_param[i].combine_enable = false; + pxp_conf->ol_param[i].width = 0; + pxp_conf->ol_param[i].height = 0; + pxp_conf->ol_param[i].pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->ol_param[i].color_key_enable = false; + pxp_conf->ol_param[i].color_key = -1; + pxp_conf->ol_param[i].global_alpha_enable = false; + pxp_conf->ol_param[i].global_alpha = 0; + pxp_conf->ol_param[i].local_alpha_enable = false; + } + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = fb_data->info.var.xres; + pxp_conf->out_param.height = fb_data->info.var.yres; + pxp_conf->out_param.stride = pxp_conf->out_param.width; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; + + /* Initialize color map for conversion of 8-bit gray pixels */ + fb_data->pxp_conf.proc_data.lut_map = kmalloc(256, GFP_KERNEL); + if (fb_data->pxp_conf.proc_data.lut_map == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for lut map!\n"); + ret = -ENOMEM; + goto out_regulator3; + } + for (i = 0; i < 256; i++) + fb_data->pxp_conf.proc_data.lut_map[i] = i; + + fb_data->pxp_conf.proc_data.lut_map_updated = true; + + /* + * Ensure this is set to NULL here...we will initialize pxp_chan + * later in our thread. + */ + fb_data->pxp_chan = NULL; + + /* Initialize Scatter-gather list containing 2 buffer addresses. */ + sg = fb_data->sg; + sg_init_table(sg, 2); + + /* + * For use in PxP transfers: + * sg[0] holds the FB buffer pointer + * sg[1] holds the Output buffer pointer (configured before TX request) + */ + sg_dma_address(&sg[0]) = info->fix.smem_start; + sg_set_page(&sg[0], virt_to_page(info->screen_base), + info->fix.smem_len, offset_in_page(info->screen_base)); + + fb_data->order_cnt = 0; + fb_data->waiting_for_wb = false; + fb_data->waiting_for_lut = false; + fb_data->waiting_for_lut15 = false; + fb_data->waiting_for_idle = false; + fb_data->blank = FB_BLANK_UNBLANK; + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + fb_data->wait_for_powerdown = false; + fb_data->updates_active = false; +#ifdef CONFIG_ANDROID//[ + fb_data->pwrdown_delay = 20; +#else //][!CONFIG_ANDROID + fb_data->pwrdown_delay = 20; +#endif //] CONFIG_ANDROID + +#if 0 + set_aad_update_counter(0); +#endif + fake_s1d13522_parse_epd_cmdline(); + +#if 1//[ compatibility for mx5 . + if(sysfs_create_link(&fb_data->dev->parent->kobj,&fb_data->dev->kobj,"mxc_epdc_fb")) { + ERR_MSG("create link 'mxc_epdc_fb' fail !\n"); + } +#endif //] + + + g_fb_data = fb_data; +#if defined(DEFAULT_PANEL_HW_INIT) //[ + ret = mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HW!\n"); + } +#endif //] + + /* Register FB */ + ret = register_framebuffer(info); + if (ret) { + dev_err(&pdev->dev, + "register_framebuffer failed with error %d\n", ret); + goto out_lutmap; + } + + g_fb_data = fb_data; + + goto out; + +out_lutmap: + kfree(fb_data->pxp_conf.proc_data.lut_map); +out_regulator3: +#ifdef USE_BSP_PMIC//[ + regulator_put(fb_data->v3p3_regulator); +#endif //] USE_BSP_PMIC +out_regulator2: +#ifdef USE_BSP_PMIC//[ + regulator_put(fb_data->vcom_regulator); +#endif //] USE_BSP_PMIC +out_regulator1: +#ifdef USE_BSP_PMIC//[ + regulator_put(fb_data->display_regulator); +#endif //] USE_BSP_PMIC +out_irq: + free_irq(fb_data->epdc_irq, fb_data); +out_dma_work_buf_B: + kfree(fb_data->working_buffer_B_virt); +out_dma_work_buf_A: + kfree(fb_data->working_buffer_A_virt); + if (fb_data->pdata->put_pins) + fb_data->pdata->put_pins(); +out_copybuffer: + dma_free_writecombine(&pdev->dev, fb_data->max_pix_size*2, + fb_data->virt_addr_copybuf, + fb_data->phys_addr_copybuf); +out_upd_buffers: + for (i = 0; i < fb_data->max_num_buffers; i++) + if (fb_data->virt_addr_updbuf[i] != NULL) + kfree(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf != NULL) + kfree(fb_data->virt_addr_updbuf); + if (fb_data->phys_addr_updbuf != NULL) + kfree(fb_data->phys_addr_updbuf); +out_upd_lists: + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, + list) { + list_del(&plist->list); + kfree(plist); + } +out_dma_fb: + + if(0==gptHWCFG->m_val.bUIStyle) { + dma_free_writecombine(&pdev->dev, fb_data->map_size<<1, info->screen_base, + fb_data->phys_start); + } + else { + dma_free_writecombine(&pdev->dev, fb_data->map_size, info->screen_base, + fb_data->phys_start); + } + +out_mapregs: + iounmap(epdc_base); +out_cmap: + fb_dealloc_cmap(&info->cmap); +out_fbdata: + kfree(fb_data); +out: + if(NTXHWCFG_TST_FLAG(gptHWCFG->m_val.bPCB_Flags,4)) { + // PCB is designed for low voltage . + if (!IS_ERR(fb_data->vdd_1v8_regulator)) { + regulator_put(fb_data->vdd_1v8_regulator); + } + } + return ret; +} + +static int mxc_epdc_fb_remove(struct platform_device *pdev) +{ + struct update_data_list *plist, *temp_list; + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + int i; + + /* restore the vcom_nominal value */ +#ifdef USE_BSP_PMIC //[ + regulator_set_voltage(fb_data->vcom_regulator, vcom_nominal, vcom_nominal); +#else //][!USE_BSP_PMIC + { + int vcom_mV=vcom_nominal/1000; + if(8==gptHWCFG->m_val.bDisplayCtrl) { + fp9928_vcom_set(vcom_mV,0); + } + else { + tps65185_vcom_set(vcom_mV,0); + } + } +#endif //] USB_BSP_PMIC + + mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info); + + flush_workqueue(fb_data->epdc_submit_workqueue); + destroy_workqueue(fb_data->epdc_submit_workqueue); + +#ifdef FW_IN_RAM //[ + flush_workqueue(fb_data->epdc_firmware_workqueue); + destroy_workqueue(fb_data->epdc_firmware_workqueue); +#endif //]FW_IN_RAM + +#ifdef USE_BSP_PMIC//[ + regulator_put(fb_data->display_regulator); + regulator_put(fb_data->vcom_regulator); + regulator_put(fb_data->v3p3_regulator); +#endif //]USE_BSP_PMIC + + unregister_framebuffer(&fb_data->info); + free_irq(fb_data->epdc_irq, fb_data); + + for (i = 0; i < fb_data->max_num_buffers; i++) + if (fb_data->virt_addr_updbuf[i] != NULL) + kfree(fb_data->virt_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf != NULL) + kfree(fb_data->virt_addr_updbuf); + if (fb_data->phys_addr_updbuf != NULL) + kfree(fb_data->phys_addr_updbuf); + + kfree(fb_data->working_buffer_A_virt); + kfree(fb_data->working_buffer_B_virt); + if (fb_data->waveform_buffer_virt != NULL) + dma_free_writecombine(&pdev->dev, fb_data->waveform_buffer_size, + fb_data->waveform_buffer_virt, + fb_data->waveform_buffer_phys); + if (fb_data->virt_addr_copybuf != NULL) + dma_free_writecombine(&pdev->dev, fb_data->max_pix_size*2, + fb_data->virt_addr_copybuf, + fb_data->phys_addr_copybuf); + + if (fb_data->temp_range_bounds) kfree(fb_data->temp_range_bounds); + kfree(fb_data->pxp_conf.proc_data.lut_map); + + /* release gen2 waveform buffers */ + if (fb_data->waveform_vcd_buffer) kfree (fb_data->waveform_vcd_buffer); + if (fb_data->waveform_acd_buffer) kfree (fb_data->waveform_acd_buffer); + if (fb_data->waveform_xwi_buffer) kfree (fb_data->waveform_xwi_buffer); + if (fb_data->waveform_xwi_string) kfree (fb_data->waveform_xwi_string); + + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, + list) { + list_del(&plist->list); + kfree(plist); + } +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_cleanup(&fb_data->info); +#endif + + dma_free_writecombine(&pdev->dev, fb_data->map_size, fb_data->info.screen_base, + fb_data->phys_start); + + if (fb_data->pdata->put_pins) + fb_data->pdata->put_pins(); + + /* Release PxP-related resources */ + if (fb_data->pxp_chan != NULL) + dma_release_channel(&fb_data->pxp_chan->dma_chan); + + iounmap(epdc_base); + + fb_dealloc_cmap(&fb_data->info.cmap); + + framebuffer_release(&fb_data->info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM + +//#define SKIP_SUSPEND +// +#ifndef DO_NOT_POWEROFF //[ + #define EPD_PMIC_SUSPEND 1 +#endif //] DO_NOT_POWEROFF + +#ifndef CONFIG_ANDROID //[ + #define EPD_FL_SUSPEND 1 +#endif //]CONFIG_ANDROID + +//#define EPD_SUSPEND_BLANK 1 + +static int mxc_epdc_fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + + GALLEN_DBGLOCAL_BEGIN(); + + struct mxc_epdc_fb_data *data = platform_get_drvdata(pdev); + int ret=0; + +#ifdef SKIP_SUSPEND //[ + return 0; +#endif //] SKIP_SUSPEND + + +#ifdef EPD_FL_SUSPEND //[ + if(0!=gptHWCFG->m_val.bFrontLight) { + extern int FL_suspend(void); + GALLEN_DBGLOCAL_RUNLOG(0); + + if(FL_suspend()<0) { + GALLEN_DBGLOCAL_RUNLOG(1); + printk(KERN_WARNING"%s(%d) : F.L suspend fail !\n",__FILE__,__LINE__); + ret = -1; + goto out; + } + } +#endif //] EPD_FL_SUSPEND + +#ifdef EPD_SUSPEND_BLANK//[ + data->pwrdown_delay = FB_POWERDOWN_DISABLE; + ret = mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &data->info); + if (ret) { + GALLEN_DBGLOCAL_RUNLOG(2); + goto out; + } +#endif //]EPD_SUSPEND_BLANK + + mxc_epdc_fb_flush_updates(data); + + if(data->power_state != POWER_STATE_OFF) { + printk("%s():skip suspend cause EPD in POWER OFF state !!\n",__FUNCTION__); + ret = -2; + goto out; + } + +#ifdef EPD_PMIC_SUSPEND//[ + if(8==gptHWCFG->m_val.bDisplayCtrl) { + + if(fp9928_suspend()>=0) { + ret = 0; + } + else { + ret = -1; + } + + } + else + if(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl) { + GALLEN_DBGLOCAL_RUNLOG(3); + if(tps65185_suspend()>=0) { + GALLEN_DBGLOCAL_RUNLOG(4); + ret = 0; + } + else { + GALLEN_DBGLOCAL_RUNLOG(5); + ret = -1; + } + } + else { + GALLEN_DBGLOCAL_RUNLOG(6); + //lm75_suspend (); + } +#endif //]EPD_PMIC_SUSPEND + +#if 0 + if(NTXHWCFG_TST_FLAG(gptHWCFG->m_val.bPCB_Flags,4)) { + // PCB is designed for low voltage . + regulator_disable(data->vdd_1v8_regulator); + } +#endif + +out: + GALLEN_DBGLOCAL_ESC(); + return ret; + GALLEN_DBGLOCAL_END(); +} + +static int mxc_epdc_fb_resume(struct platform_device *pdev) +{ + struct mxc_epdc_fb_data *data = platform_get_drvdata(pdev); + + +#ifdef SKIP_SUSPEND //[ + return 0; +#endif //] SKIP_SUSPEND + + GALLEN_DBGLOCAL_BEGIN(); + +#if 0 + if(NTXHWCFG_TST_FLAG(gptHWCFG->m_val.bPCB_Flags,4)) { + // PCB is designed for low voltage . + regulator_enable(data->vdd_1v8_regulator); + } +#endif + +#ifdef EPD_PMIC_SUSPEND //[ + if(8==gptHWCFG->m_val.bDisplayCtrl) { + fp9928_resume(); + } + else + if(6==gptHWCFG->m_val.bDisplayCtrl||7==gptHWCFG->m_val.bDisplayCtrl) { + tps65185_resume(); + } + else { + //lm75_resume(); + } +#endif //]EPD_PMIC_SUSPEND + + gdwLastUpdateJiffies = 0; + +#ifdef EPD_SUSPEND_BLANK//[ + mxc_epdc_fb_blank(FB_BLANK_UNBLANK, &data->info); +#endif//] EPD_SUSPEND_BLANK + + if(mx6sl_revision() >= IMX_CHIP_REVISION_1_2) { + // the gp maybe powered down on chip TO1.2 ,we should reinit epd registers again . + epdc_init_settings(data); + } + + data->updates_active = false; + + GALLEN_DBGLOCAL_END(); + return 0; +} + +static int mxc_epdc_fb_shutdown(struct platform_device *pdev) +{ +#if 1 + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + GALLEN_DBGLOCAL_BEGIN(); + + mxc_epdc_fb_flush_updates(fb_data); + + //fb_data->powering_down = true; + //epdc_powerdown(fb_data); + + if(8==gptHWCFG->m_val.bDisplayCtrl) { + GALLEN_DBGLOCAL_RUNLOG(0); + fp9928_shutdown(); + } + else + { + GALLEN_DBGLOCAL_RUNLOG(1); + tps65185_shutdown(); + } + + GALLEN_DBGLOCAL_END(); + return 0; +#else + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + + /* Disable power to the EPD panel */ + if (regulator_is_enabled(fb_data->vcom_regulator)) + regulator_disable(fb_data->vcom_regulator); + if (regulator_is_enabled(fb_data->display_regulator)) + regulator_disable(fb_data->display_regulator); + + /* Disable clocks to EPDC */ + clk_enable(fb_data->epdc_clk_axi); + clk_enable(fb_data->epdc_clk_pix); + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); + clk_disable(fb_data->epdc_clk_pix); + clk_disable(fb_data->epdc_clk_axi); + + /* Disable pins used by EPDC (to prevent leakage current) */ + if (fb_data->pdata->disable_pins) + fb_data->pdata->disable_pins(); + + /* turn off the V3p3 */ + if (regulator_is_enabled(fb_data->v3p3_regulator)) + regulator_disable(fb_data->v3p3_regulator); + return 0; +#endif + +} +#else +#define mxc_epdc_fb_suspend NULL +#define mxc_epdc_fb_resume NULL +#define mxc_epdc_fb_shutdown NULL +#endif + +static struct platform_driver mxc_epdc_fb_driver = { + .probe = mxc_epdc_fb_probe, + .remove = mxc_epdc_fb_remove, + .suspend = mxc_epdc_fb_suspend, + .resume = mxc_epdc_fb_resume, + .shutdown = mxc_epdc_fb_shutdown, + .driver = { + .name = "imx_epdc_fb", + .owner = THIS_MODULE, + }, +}; + +/* Callback function triggered after PxP receives an EOF interrupt */ +static void pxp_dma_done(void *arg) +{ + struct pxp_tx_desc *tx_desc = to_tx_desc(arg); + struct dma_chan *chan = tx_desc->txd.chan; + struct pxp_channel *pxp_chan = to_pxp_channel(chan); + struct mxc_epdc_fb_data *fb_data = pxp_chan->client; + + GALLEN_DBGLOCAL_BEGIN(); + + /* This call will signal wait_for_completion_timeout() in send_buffer_to_pxp */ + complete(&fb_data->pxp_tx_cmpl); + GALLEN_DBGLOCAL_END(); +} + +static bool chan_filter(struct dma_chan *chan, void *arg) +{ + if (imx_dma_is_pxp(chan)) + return true; + else + return false; +} + +/* Function to request PXP DMA channel */ +static int pxp_chan_init(struct mxc_epdc_fb_data *fb_data) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + + /* + * Request a free channel + */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + chan = dma_request_channel(mask, chan_filter, NULL); + if (!chan) { + dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n"); + return -EBUSY; + } + + fb_data->pxp_chan = to_pxp_channel(chan); + fb_data->pxp_chan->client = fb_data; + + init_completion(&fb_data->pxp_tx_cmpl); + + return 0; +} + +/* + * Function to call PxP DMA driver and send our latest FB update region + * through the PxP and out to an intermediate buffer. + * Note: This is a blocking call, so upon return the PxP tx should be complete. + */ +static int pxp_process_update(struct mxc_epdc_fb_data *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + int i, ret; + int length; + + GALLEN_DBGLOCAL_BEGIN(); + + dev_dbg(fb_data->dev, "Starting PxP Send Buffer\n"); + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + GALLEN_DBGLOCAL_RUNLOG(0); + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + GALLEN_DBGLOCAL_ESC(); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + GALLEN_DBGLOCAL_ESC(); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + /* + * Configure PxP for processing of new update region + * The rest of our config params were set up in + * probe() and should not need to be changed. + */ + pxp_conf->s0_param.width = src_width; + pxp_conf->s0_param.height = src_height; + proc_data->srect.top = update_region->top; + proc_data->srect.left = update_region->left; + proc_data->srect.width = update_region->width; + proc_data->srect.height = update_region->height; + + /* + * Because only YUV/YCbCr image can be scaled, configure + * drect equivalent to srect, as such do not perform scaling. + */ + proc_data->drect.top = 0; + proc_data->drect.left = 0; + proc_data->drect.width = proc_data->srect.width; + proc_data->drect.height = proc_data->srect.height; + + /* PXP expects rotation in terms of degrees */ + proc_data->rotate = fb_data->epdc_fb_var.rotate * 90; + if (proc_data->rotate > 270) { + GALLEN_DBGLOCAL_RUNLOG(1); + proc_data->rotate = 0; + } + + pxp_conf->out_param.width = update_region->width; + pxp_conf->out_param.height = update_region->height; + + if ((proc_data->rotate == 90) || (proc_data->rotate == 270)) { + GALLEN_DBGLOCAL_RUNLOG(2); + pxp_conf->out_param.stride = update_region->height; + } + else { + GALLEN_DBGLOCAL_RUNLOG(3); + pxp_conf->out_param.stride = update_region->width; + } + + /* For EPDC v2.0, we need output to be 64-bit + * aligned since EPDC stride does not work. */ + if (fb_data->rev <= 20) { + GALLEN_DBGLOCAL_RUNLOG(4); + pxp_conf->out_param.stride = ALIGN(pxp_conf->out_param.stride, 8); + } + + + desc = to_tx_desc(txd); + length = desc->len; + for (i = 0; i < length; i++) { + GALLEN_DBGLOCAL_RUNLOG(5); + if (i == 0) {/* S0 */ + GALLEN_DBGLOCAL_RUNLOG(6); + memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); + pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]); + memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param, + sizeof(struct pxp_layer_param)); + } else if (i == 1) { + GALLEN_DBGLOCAL_RUNLOG(7); + pxp_conf->out_param.paddr = sg_dma_address(&sg[1]); + memcpy(&desc->layer_param.out_param, &pxp_conf->out_param, + sizeof(struct pxp_layer_param)); + } + /* TODO: OverLay */ + + desc = desc->next; + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + GALLEN_DBGLOCAL_ESC(); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + GALLEN_DBGLOCAL_END(); + return 0; +} + +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat) +{ + int ret; + /* + * Wait for completion event, which will be set + * through our TX callback function. + */ + ret = wait_for_completion_timeout(&fb_data->pxp_tx_cmpl, HZ / 10); + if (ret <= 0) { + dev_info(fb_data->info.device, + "PxP operation failed due to %s\n", + ret < 0 ? "user interrupt" : "timeout"); + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + return ret ? : -ETIMEDOUT; + } + + if ((fb_data->pxp_conf.proc_data.lut_transform & EPDC_FLAG_USE_CMAP) && + fb_data->pxp_conf.proc_data.lut_map_updated) + fb_data->pxp_conf.proc_data.lut_map_updated = false; + + *hist_stat = to_tx_desc(fb_data->txd)->hist_status; + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + + dev_dbg(fb_data->dev, "TX completed\n"); + + return 0; +} + +/* + * Different dithering algorithm can be used. We chose + * to implement Bill Atkinson's algorithm as an example + * Thanks Bill Atkinson for his dithering algorithm. + */ + +/* + * Dithering algorithm implementation - Y8->Y1 version 1.0 for i.MX + */ +static void do_dithering_processing_Y1_v1_0( + unsigned char *update_region_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist) +{ + + /* create a temp error distribution array */ + int bwPix; + int y; + int col; + int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error; + int width_3 = update_region->width + 3; + char *y8buf; + int x_offset = 0; + + /* prime a few elements the error distribution array */ + for (y = 0; y < update_region->height; y++) { + /* Dithering the Y8 in sbuf to BW suitable for A2 waveform */ + err_dist_l0 = err_dist + (width_3) * (y % 3); + err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3); + err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3); + + y8buf = update_region_ptr + x_offset; + + /* scan the line and convert the Y8 to BW */ + for (col = 1; col <= update_region->width; col++) { + bwPix = *(err_dist_l0 + col) + *y8buf; + + if (bwPix >= 128) { + *y8buf++ = 0xf0; + distrib_error = (bwPix - 255) >> 3; + } else { + *y8buf++ = 0; + distrib_error = bwPix >> 3; + } + + /* modify the error distribution buffer */ + *(err_dist_l0 + col + 2) += distrib_error; + *(err_dist_l1 + col + 1) += distrib_error; + *(err_dist_l0 + col + 1) += distrib_error; + *(err_dist_l1 + col - 1) += distrib_error; + *(err_dist_l1 + col) += distrib_error; + *(err_dist_l2 + col) = distrib_error; + } + x_offset += update_region_stride; + } + + flush_cache_all(); + outer_flush_all(); +} + +/* + * Dithering algorithm implementation - Y8->Y4 version 1.0 for i.MX + */ + +static void do_dithering_processing_Y4_v1_0( + unsigned char *update_region_ptr, + struct mxcfb_rect *update_region, + unsigned long update_region_stride, + int *err_dist) +{ + + /* create a temp error distribution array */ + int gcPix; + int y; + int col; + int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error; + int width_3 = update_region->width + 3; + char *y8buf; + int x_offset = 0; + + /* prime a few elements the error distribution array */ + for (y = 0; y < update_region->height; y++) { + /* Dithering the Y8 in sbuf to Y4 */ + err_dist_l0 = err_dist + (width_3) * (y % 3); + err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3); + err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3); + + y8buf = update_region_ptr + x_offset; + + /* scan the line and convert the Y8 to Y4 */ + for (col = 1; col <= update_region->width; col++) { + gcPix = *(err_dist_l0 + col) + *y8buf; + + if (gcPix > 255) + gcPix = 255; + else if (gcPix < 0) + gcPix = 0; + + distrib_error = (*y8buf - (gcPix & 0xf0)) >> 3; + + *y8buf++ = gcPix & 0xf0; + + /* modify the error distribution buffer */ + *(err_dist_l0 + col + 2) += distrib_error; + *(err_dist_l1 + col + 1) += distrib_error; + *(err_dist_l0 + col + 1) += distrib_error; + *(err_dist_l1 + col - 1) += distrib_error; + *(err_dist_l1 + col) += distrib_error; + *(err_dist_l2 + col) = distrib_error; + } + x_offset += update_region_stride; + } + + flush_cache_all(); + outer_flush_all(); +} + +int mxc_epdc_fb_ep1v8_output(int iIsOutput) +{ + if(!g_fb_data) { + printk(KERN_ERR"%s cannot set without initialized !!\n",__FUNCTION__); + return -1; + } + + if(NTXHWCFG_TST_FLAG(gptHWCFG->m_val.bPCB_Flags,4)) { + // PCB is designed for low voltage . + if (!IS_ERR(g_fb_data->vdd_1v8_regulator)) { + if(iIsOutput) { + regulator_enable(g_fb_data->vdd_1v8_regulator); + } + else { + regulator_disable(g_fb_data->vdd_1v8_regulator); + } + } + else { + printk(KERN_ERR"%s():regulator \"ldo_1v8\" not avalible !\n",__FUNCTION__); + } + } + return 0; +} + +static int __init mxc_epdc_fb_init(void) +{ + return platform_driver_register(&mxc_epdc_fb_driver); +} +//late_initcall(mxc_epdc_fb_init); +module_init(mxc_epdc_fb_init); + + +static void __exit mxc_epdc_fb_exit(void) +{ + platform_driver_unregister(&mxc_epdc_fb_driver); +} +module_exit(mxc_epdc_fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC EPDC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c new file mode 100644 index 00000000..f806f948 --- /dev/null +++ b/drivers/video/mxc/mxc_ipuv3_fb.c @@ -0,0 +1,2647 @@ +/* + * Copyright 2004-2013 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/uaccess.h> +#include <linux/fsl_devices.h> +#include <linux/earlysuspend.h> +#include <linux/workqueue.h> +#include <asm/mach-types.h> +#include <mach/ipu-v3.h> +#include "mxc_dispdrv.h" + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" + +/* Display port number */ +#define MXCFB_PORT_NUM 2 +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + spinlock_t lock; + struct fb_info *fbi; + int default_bpp; + int cur_blank; + int next_blank; + ipu_channel_t ipu_ch; + int ipu_id; + int ipu_di; + u32 ipu_di_pix_fmt; + bool ipu_int_clk; + bool overlay; + bool alpha_chan_en; + bool late_init; + bool first_set_par; + dma_addr_t alpha_phy_addr0; + dma_addr_t alpha_phy_addr1; + void *alpha_virt_addr0; + void *alpha_virt_addr1; + uint32_t alpha_mem_len; + uint32_t ipu_ch_irq; + uint32_t ipu_ch_nf_irq; + uint32_t ipu_vsync_pre_irq; + uint32_t ipu_alp_ch_irq; + uint32_t cur_ipu_buf; + uint32_t cur_ipu_alpha_buf; + + u32 pseudo_palette[16]; + + bool mode_found; + struct completion flip_complete; + struct completion alpha_flip_complete; + struct completion vsync_complete; + + void *ipu; + struct fb_info *ovfbi; + + struct mxc_dispdrv_handle *dispdrv; + + struct fb_var_screeninfo cur_var; + + int vsync_pre_report_active; + ktime_t vsync_pre_timestamp; + struct workqueue_struct *vsync_pre_queue; + struct work_struct vsync_pre_work; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend fbdrv_earlysuspend; +#endif +}; + +struct mxcfb_pfmt { + u32 fb_pix_fmt; + int bpp; + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; +}; + +static const struct mxcfb_pfmt mxcfb_pfmts[] = { + /* pixel bpp red green blue transp */ + {IPU_PIX_FMT_RGB565, 16, {11, 5, 0}, { 5, 6, 0}, { 0, 5, 0}, { 0, 0, 0} }, + {IPU_PIX_FMT_RGB24, 24, { 0, 8, 0}, { 8, 8, 0}, {16, 8, 0}, { 0, 0, 0} }, + {IPU_PIX_FMT_BGR24, 24, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, { 0, 0, 0} }, + {IPU_PIX_FMT_RGB32, 32, { 0, 8, 0}, { 8, 8, 0}, {16, 8, 0}, {24, 8, 0} }, + {IPU_PIX_FMT_BGR32, 32, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, {24, 8, 0} }, + {IPU_PIX_FMT_ABGR32, 32, {24, 8, 0}, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0} }, +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +enum { + BOTH_ON, + SRC_ON, + TGT_ON, + BOTH_OFF +}; + +static bool g_dp_in_use[2]; +LIST_HEAD(fb_alloc_list); + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mxcfb_early_suspend(struct early_suspend *h); +static void mxcfb_later_resume(struct early_suspend *h); +#endif + +/* Return default standard(RGB) pixel format */ +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +static inline int bitfield_is_equal(struct fb_bitfield f1, + struct fb_bitfield f2) +{ + return !memcmp(&f1, &f2, sizeof(f1)); +} + +static int pixfmt_to_var(uint32_t pixfmt, struct fb_var_screeninfo *var) +{ + int i, ret = -1; + + for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) { + if (pixfmt == mxcfb_pfmts[i].fb_pix_fmt) { + var->red = mxcfb_pfmts[i].red; + var->green = mxcfb_pfmts[i].green; + var->blue = mxcfb_pfmts[i].blue; + var->transp = mxcfb_pfmts[i].transp; + var->bits_per_pixel = mxcfb_pfmts[i].bpp; + ret = 0; + break; + } + } + return ret; +} + +static int bpp_to_var(int bpp, struct fb_var_screeninfo *var) +{ + uint32_t pixfmt = 0; + + pixfmt = bpp_to_pixfmt(bpp); + if (pixfmt) + return pixfmt_to_var(pixfmt, var); + else + return -1; +} + +static int check_var_pixfmt(struct fb_var_screeninfo *var) +{ + int i, ret = -1; + + for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) { + if (bitfield_is_equal(var->red, mxcfb_pfmts[i].red) && + bitfield_is_equal(var->green, mxcfb_pfmts[i].green) && + bitfield_is_equal(var->blue, mxcfb_pfmts[i].blue) && + bitfield_is_equal(var->transp, mxcfb_pfmts[i].transp) && + var->bits_per_pixel == mxcfb_pfmts[i].bpp) { + ret = 0; + break; + } + } + return ret; +} + +static uint32_t fbi_to_pixfmt(struct fb_info *fbi) +{ + int i; + uint32_t pixfmt = 0; + + if (fbi->var.nonstd) + return fbi->var.nonstd; + + for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) { + if (bitfield_is_equal(fbi->var.red, mxcfb_pfmts[i].red) && + bitfield_is_equal(fbi->var.green, mxcfb_pfmts[i].green) && + bitfield_is_equal(fbi->var.blue, mxcfb_pfmts[i].blue) && + bitfield_is_equal(fbi->var.transp, mxcfb_pfmts[i].transp)) { + pixfmt = mxcfb_pfmts[i].fb_pix_fmt; + break; + } + } + + if (pixfmt == 0) + dev_err(fbi->device, "cannot get pixel format\n"); + + return pixfmt; +} + +static struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id) +{ + int i; + struct mxcfb_info *mxc_fbi; + struct fb_info *fbi = NULL; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi = + ((struct mxcfb_info *)(registered_fb[i]->par)); + + if ((mxc_fbi->ipu_ch == ipu_ch) && + (mxc_fbi->ipu_id == ipu_id)) { + fbi = registered_fb[i]; + break; + } + } + return fbi; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id); +static irqreturn_t mxcfb_vsync_pre_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ywrapstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static int _setup_disp_channel1(struct fb_info *fbi) +{ + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + memset(¶ms, 0, sizeof(params)); + + if (mxc_fbi->ipu_ch == MEM_DC_SYNC) { + params.mem_dc_sync.di = mxc_fbi->ipu_di; + if (fbi->var.vmode & FB_VMODE_INTERLACED) + params.mem_dc_sync.interlaced = true; + params.mem_dc_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + params.mem_dc_sync.in_pixel_fmt = fbi_to_pixfmt(fbi); + } else { + params.mem_dp_bg_sync.di = mxc_fbi->ipu_di; + if (fbi->var.vmode & FB_VMODE_INTERLACED) + params.mem_dp_bg_sync.interlaced = true; + params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + params.mem_dp_bg_sync.in_pixel_fmt = fbi_to_pixfmt(fbi); + if (mxc_fbi->alpha_chan_en) + params.mem_dp_bg_sync.alpha_chan_en = true; + } + ipu_init_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, ¶ms); + + return 0; +} + +static int _setup_disp_channel2(struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int fb_stride; + unsigned long base; + unsigned int fr_xoff, fr_yoff, fr_w, fr_h; + + switch (fbi_to_pixfmt(fbi)) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV444P: + fb_stride = fbi->var.xres_virtual; + break; + default: + fb_stride = fbi->fix.line_length; + } + + base = fbi->fix.smem_start; + fr_xoff = fbi->var.xoffset; + fr_w = fbi->var.xres_virtual; + if (!(fbi->var.vmode & FB_VMODE_YWRAP)) { + dev_dbg(fbi->device, "Y wrap disabled\n"); + fr_yoff = fbi->var.yoffset % fbi->var.yres; + fr_h = fbi->var.yres; + base += fbi->fix.line_length * fbi->var.yres * + (fbi->var.yoffset / fbi->var.yres); + } else { + dev_dbg(fbi->device, "Y wrap enabled\n"); + fr_yoff = fbi->var.yoffset; + fr_h = fbi->var.yres_virtual; + } + base += fr_yoff * fb_stride + fr_xoff; + + mxc_fbi->cur_ipu_buf = 2; + init_completion(&mxc_fbi->flip_complete); + /* + * We don't need to wait for vsync at the first time + * we do pan display after fb is initialized, as IPU will + * switch to the newly selected buffer automatically, + * so we call complete() for both mxc_fbi->flip_complete + * and mxc_fbi->alpha_flip_complete. + */ + complete(&mxc_fbi->flip_complete); + if (mxc_fbi->alpha_chan_en) { + mxc_fbi->cur_ipu_alpha_buf = 1; + init_completion(&mxc_fbi->alpha_flip_complete); + complete(&mxc_fbi->alpha_flip_complete); + } + + retval = ipu_init_channel_buffer(mxc_fbi->ipu, + mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + fbi_to_pixfmt(fbi), + fbi->var.xres, fbi->var.yres, + fb_stride, + fbi->var.rotate, + base, + base, + fbi->var.accel_flags & + FB_ACCEL_DOUBLE_FLAG ? 0 : base, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + + /* update u/v offset */ + ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + fbi_to_pixfmt(fbi), + fr_w, + fr_h, + fr_w, + 0, 0, + fr_yoff, + fr_xoff); + + if (mxc_fbi->alpha_chan_en) { + retval = ipu_init_channel_buffer(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + IPU_PIX_FMT_GENERIC, + fbi->var.xres, fbi->var.yres, + fbi->var.xres, + fbi->var.rotate, + mxc_fbi->alpha_phy_addr1, + mxc_fbi->alpha_phy_addr0, + 0, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + } + + return retval; +} + +static bool mxcfb_need_to_set_par(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((fbi->var.activate & FB_ACTIVATE_FORCE) && + (fbi->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) + return true; + + /* + * Ignore xoffset and yoffset update, + * because pan display handles this case. + */ + mxc_fbi->cur_var.xoffset = fbi->var.xoffset; + mxc_fbi->cur_var.yoffset = fbi->var.yoffset; + + return !!memcmp(&mxc_fbi->cur_var, &fbi->var, + sizeof(struct fb_var_screeninfo)); +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + u32 mem_len, alpha_mem_len; + ipu_di_signal_cfg_t sig_cfg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + int16_t ov_pos_x = 0, ov_pos_y = 0; + int ov_pos_ret = 0; + struct mxcfb_info *mxc_fbi_fg = NULL; + bool ovfbi_enable = false; + + if (mxc_fbi->ovfbi) + mxc_fbi_fg = (struct mxcfb_info *)mxc_fbi->ovfbi->par; + + if (mxc_fbi->ovfbi && mxc_fbi_fg) + if (mxc_fbi_fg->next_blank == FB_BLANK_UNBLANK) + ovfbi_enable = true; + + if (!mxcfb_need_to_set_par(fbi)) + return 0; + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + if (fbi->var.xres == 0 || fbi->var.yres == 0) + return 0; + + if (ovfbi_enable) { + ov_pos_ret = ipu_disp_get_window_pos( + mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch, + &ov_pos_x, &ov_pos_y); + if (ov_pos_ret < 0) + dev_err(fbi->device, "Get overlay pos failed, dispdrv:%s.\n", + mxc_fbi->dispdrv->drv->name); + + ipu_clear_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_irq); + ipu_disable_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_irq); + ipu_clear_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_nf_irq); + ipu_disable_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_nf_irq); + ipu_disable_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch, true); + ipu_uninit_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch); + } + + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); + ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); + ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + + /* + * Disable IPU hsp clock if it is enabled for an + * additional time in ipu common driver. + */ + if (mxc_fbi->first_set_par && mxc_fbi->late_init) + ipu_disable_hsp_clk(mxc_fbi->ipu); + + mxcfb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + + if (mxcfb_map_video_memory(fbi) < 0) + return -ENOMEM; + } + + if (mxc_fbi->first_set_par) { + /* + * Clear the screen in case uboot fb pixel format is not + * the same to kernel fb pixel format. + */ + if (mxc_fbi->late_init) + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + mxc_fbi->first_set_par = false; + } + + if (mxc_fbi->alpha_chan_en) { + alpha_mem_len = fbi->var.xres * fbi->var.yres; + if ((!mxc_fbi->alpha_phy_addr0 && !mxc_fbi->alpha_phy_addr1) || + (alpha_mem_len > mxc_fbi->alpha_mem_len)) { + if (mxc_fbi->alpha_phy_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_phy_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + + mxc_fbi->alpha_virt_addr0 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr0, + GFP_DMA | GFP_KERNEL); + + mxc_fbi->alpha_virt_addr1 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr1, + GFP_DMA | GFP_KERNEL); + if (mxc_fbi->alpha_virt_addr0 == NULL || + mxc_fbi->alpha_virt_addr1 == NULL) { + dev_err(fbi->device, "mxcfb: dma alloc for" + " alpha buffer failed.\n"); + if (mxc_fbi->alpha_virt_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_virt_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + return -ENOMEM; + } + mxc_fbi->alpha_mem_len = alpha_mem_len; + } + } + + if (mxc_fbi->next_blank != FB_BLANK_UNBLANK) + return retval; + + if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->setup) { + retval = mxc_fbi->dispdrv->drv->setup(mxc_fbi->dispdrv, fbi); + if (retval < 0) { + dev_err(fbi->device, "setup error, dispdrv:%s.\n", + mxc_fbi->dispdrv->drv->name); + return -EINVAL; + } + } + + _setup_disp_channel1(fbi); + if (ovfbi_enable) + _setup_disp_channel1(mxc_fbi->ovfbi); + + if (!mxc_fbi->overlay) { + uint32_t out_pixel_fmt; + + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.vmode & FB_VMODE_INTERLACED) + sig_cfg.interlaced = true; + out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */ + sig_cfg.odd_field_first = true; + if (mxc_fbi->ipu_int_clk) + sig_cfg.int_clk = true; + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_init_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + out_pixel_fmt, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin, + 0, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + + fbi->mode = + (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, 0, 0); + } + + retval = _setup_disp_channel2(fbi); + if (retval) { + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + return retval; + } + + if (ovfbi_enable) { + if (ov_pos_ret >= 0) + ipu_disp_set_window_pos( + mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch, + ov_pos_x, ov_pos_y); + retval = _setup_disp_channel2(mxc_fbi->ovfbi); + if (retval) { + ipu_uninit_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch); + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + return retval; + } + } + + ipu_enable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + if (ovfbi_enable) + ipu_enable_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch); + + if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->enable) { + retval = mxc_fbi->dispdrv->drv->enable(mxc_fbi->dispdrv); + if (retval < 0) { + dev_err(fbi->device, "enable error, dispdrv:%s.\n", + mxc_fbi->dispdrv->drv->name); + return -EINVAL; + } + } + + mxc_fbi->cur_var = fbi->var; + + return retval; +} + +static int _swap_channels(struct fb_info *fbi_from, + struct fb_info *fbi_to, bool both_on) +{ + int retval, tmp; + ipu_channel_t old_ch; + struct fb_info *ovfbi; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par; + struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + if (both_on) { + ipu_disable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch, true); + ipu_uninit_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch); + } + + /* switch the mxc fbi parameters */ + old_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = mxc_fbi_to->ipu_ch; + mxc_fbi_to->ipu_ch = old_ch; + tmp = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = tmp; + tmp = mxc_fbi_from->ipu_ch_nf_irq; + mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq; + mxc_fbi_to->ipu_ch_nf_irq = tmp; + ovfbi = mxc_fbi_from->ovfbi; + mxc_fbi_from->ovfbi = mxc_fbi_to->ovfbi; + mxc_fbi_to->ovfbi = ovfbi; + + _setup_disp_channel1(fbi_from); + retval = _setup_disp_channel2(fbi_from); + if (retval) + return retval; + + /* switch between dp and dc, disable old idmac, enable new idmac */ + retval = ipu_swap_channel(mxc_fbi_from->ipu, old_ch, mxc_fbi_from->ipu_ch); + ipu_uninit_channel(mxc_fbi_from->ipu, old_ch); + + if (both_on) { + _setup_disp_channel1(fbi_to); + retval = _setup_disp_channel2(fbi_to); + if (retval) + return retval; + ipu_enable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch); + } + + return retval; +} + +static int swap_channels(struct fb_info *fbi_from) +{ + int i; + int swap_mode; + ipu_channel_t ch_to; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par; + struct fb_info *fbi_to = NULL; + struct mxcfb_info *mxc_fbi_to; + + /* what's the target channel? */ + if (mxc_fbi_from->ipu_ch == MEM_BG_SYNC) + ch_to = MEM_DC_SYNC; + else + ch_to = MEM_BG_SYNC; + + fbi_to = found_registered_fb(ch_to, mxc_fbi_from->ipu_id); + if (!fbi_to) + return -1; + mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq); + ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq); + ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, fbi_from); + ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, fbi_to); + ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq); + ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq); + ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq, fbi_from); + ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq, fbi_to); + + if (mxc_fbi_from->cur_blank == FB_BLANK_UNBLANK) { + if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK) + swap_mode = BOTH_ON; + else + swap_mode = SRC_ON; + } else { + if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK) + swap_mode = TGT_ON; + else + swap_mode = BOTH_OFF; + } + + switch (swap_mode) { + case BOTH_ON: + /* disable target->switch src->enable target */ + _swap_channels(fbi_from, fbi_to, true); + break; + case SRC_ON: + /* just switch src */ + _swap_channels(fbi_from, fbi_to, false); + break; + case TGT_ON: + /* just switch target */ + _swap_channels(fbi_to, fbi_from, false); + break; + case BOTH_OFF: + /* switch directly, no more need to do */ + mxc_fbi_to->ipu_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = ch_to; + i = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = i; + i = mxc_fbi_from->ipu_ch_nf_irq; + mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq; + mxc_fbi_to->ipu_ch_nf_irq = i; + break; + default: + break; + } + + if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, + mxcfb_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi_from) != 0) { + dev_err(fbi_from->device, "Error registering irq %d\n", + mxc_fbi_from->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq); + if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, + mxcfb_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi_to) != 0) { + dev_err(fbi_to->device, "Error registering irq %d\n", + mxc_fbi_to->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq); + if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq, + mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi_from) != 0) { + dev_err(fbi_from->device, "Error registering irq %d\n", + mxc_fbi_from->ipu_ch_nf_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq); + if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq, + mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi_to) != 0) { + dev_err(fbi_to->device, "Error registering irq %d\n", + mxc_fbi_to->ipu_ch_nf_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq); + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + + if (var->xres == 0 || var->yres == 0) + return 0; + + /* fg should not bigger than bg */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct fb_info *fbi_tmp; + int bg_xres = 0, bg_yres = 0; + int16_t pos_x, pos_y; + + bg_xres = var->xres; + bg_yres = var->yres; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (fbi_tmp) { + bg_xres = fbi_tmp->var.xres; + bg_yres = fbi_tmp->var.yres; + } + + ipu_disp_get_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, &pos_x, &pos_y); + + if ((var->xres + pos_x) > bg_xres) + var->xres = bg_xres - pos_x; + if ((var->yres + pos_y) > bg_yres) + var->yres = bg_yres - pos_y; + } + + if (var->rotate > IPU_ROTATE_VERT_FLIP) + var->rotate = IPU_ROTATE_NONE; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres * 3; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 12) && + (var->bits_per_pixel != 8)) + var->bits_per_pixel = 16; + + if (check_var_pixfmt(var)) + /* Fall back to default */ + bpp_to_var(var->bits_per_pixel, var); + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +static void mxcfb_vsync_pre_work(struct work_struct *work) +{ + struct mxcfb_info *mxc_fbi = + container_of(work, struct mxcfb_info, vsync_pre_work); + char *envp[2]; + char buf[64]; + unsigned long flags; + + spin_lock_irqsave(&mxc_fbi->lock, flags); + snprintf(buf, sizeof(buf), "VSYNC=%llu", + ktime_to_ns(mxc_fbi->vsync_pre_timestamp)); + spin_unlock_irqrestore(&mxc_fbi->lock, flags); + + envp[0] = buf; + envp[1] = NULL; + kobject_uevent_env(&mxc_fbi->fbi->dev->kobj, KOBJ_CHANGE, envp); +} + +static void mxcfb_enable_vsync_pre(struct mxcfb_info *mxc_fbi) +{ + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_vsync_pre_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_vsync_pre_irq); +} + +static void mxcfb_disable_vsync_pre(struct mxcfb_info *mxc_fbi) +{ + ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_vsync_pre_irq); +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + (bool)ga.enable, + ga.alpha)) { + retval = -EINVAL; + break; + } + + if (ga.enable) + mxc_fbi->alpha_chan_en = false; + + if (ga.enable) + dev_dbg(fbi->device, + "Set global alpha of %s to %d\n", + fbi->fix.id, ga.alpha); + break; + } + case MXCFB_SET_LOC_ALPHA: + { + struct mxcfb_loc_alpha la; + + if (copy_from_user(&la, (void *)arg, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu, mxc_fbi->ipu_ch, + !(bool)la.enable, 0)) { + retval = -EINVAL; + break; + } + + if (la.enable && !la.alpha_in_pixel) { + struct fb_info *fbi_tmp; + ipu_channel_t ipu_ch; + + mxc_fbi->alpha_chan_en = true; + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + ipu_ch = MEM_BG_SYNC; + else if (mxc_fbi->ipu_ch == MEM_BG_SYNC) + ipu_ch = MEM_FG_SYNC; + else { + retval = -EINVAL; + break; + } + + fbi_tmp = found_registered_fb(ipu_ch, mxc_fbi->ipu_id); + if (fbi_tmp) + ((struct mxcfb_info *)(fbi_tmp->par))->alpha_chan_en = false; + } else + mxc_fbi->alpha_chan_en = false; + + fbi->var.activate = (fbi->var.activate & ~FB_ACTIVATE_MASK) | + FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + mxcfb_set_par(fbi); + + la.alpha_phy_addr0 = mxc_fbi->alpha_phy_addr0; + la.alpha_phy_addr1 = mxc_fbi->alpha_phy_addr1; + if (copy_to_user((void *)arg, &la, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (la.enable) + dev_dbg(fbi->device, + "Enable DP local alpha for %s\n", + fbi->fix.id); + break; + } + case MXCFB_SET_LOC_ALP_BUF: + { + unsigned long base; + uint32_t ipu_alp_ch_irq; + + if (!(((mxc_fbi->ipu_ch == MEM_FG_SYNC) || + (mxc_fbi->ipu_ch == MEM_BG_SYNC)) && + (mxc_fbi->alpha_chan_en))) { + dev_err(fbi->device, + "Should use background or overlay " + "framebuffer to set the alpha buffer " + "number\n"); + return -EINVAL; + } + + if (get_user(base, argp)) + return -EFAULT; + + if (base != mxc_fbi->alpha_phy_addr0 && + base != mxc_fbi->alpha_phy_addr1) { + dev_err(fbi->device, + "Wrong alpha buffer physical address " + "%lu\n", base); + return -EINVAL; + } + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + else + ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + + retval = wait_for_completion_timeout( + &mxc_fbi->alpha_flip_complete, HZ/2); + if (retval == 0) { + dev_err(fbi->device, "timeout when waiting for alpha flip irq\n"); + retval = -ETIMEDOUT; + break; + } + + mxc_fbi->cur_ipu_alpha_buf = + !mxc_fbi->cur_ipu_alpha_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi-> + cur_ipu_alpha_buf, + base) == 0) { + ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + ipu_clear_irq(mxc_fbi->ipu, ipu_alp_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, ipu_alp_ch_irq); + } else { + dev_err(fbi->device, + "Error updating %s SDC alpha buf %d " + "to address=0x%08lX\n", + fbi->fix.id, + mxc_fbi->cur_ipu_alpha_buf, base); + } + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_color_key(mxc_fbi->ipu, mxc_fbi->ipu_ch, + key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_SET_GAMMA: + { + struct mxcfb_gamma gamma; + if (copy_from_user(&gamma, (void *)arg, sizeof(gamma))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_gamma_correction(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + gamma.enable, + gamma.constk, + gamma.slopek); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + /* BG should poweron */ + struct mxcfb_info *bg_mxcfbi = NULL; + struct fb_info *fbi_tmp; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (fbi_tmp) + bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par)); + + if (!bg_mxcfbi) { + retval = -EINVAL; + break; + } + if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) { + retval = -EINVAL; + break; + } + } + if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) { + retval = -EINVAL; + break; + } + + init_completion(&mxc_fbi->vsync_complete); + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); + retval = wait_for_completion_interruptible_timeout( + &mxc_fbi->vsync_complete, 1 * HZ); + if (retval == 0) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout %d\n", + retval); + retval = -ETIME; + } else if (retval > 0) { + retval = 0; + } + break; + } + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) + return -EFAULT; + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_KERNEL); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + if (put_user(mem->phy_addr, argp)) + return -EFAULT; + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + struct fb_info *bg_fbi = NULL; + struct mxcfb_info *bg_mxcfbi = NULL; + + if (mxc_fbi->ipu_ch != MEM_FG_SYNC) { + dev_err(fbi->device, "Should use the overlay " + "framebuffer to set the position of " + "the overlay window\n"); + retval = -EINVAL; + break; + } + + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + + bg_fbi = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (bg_fbi) + bg_mxcfbi = ((struct mxcfb_info *)(bg_fbi->par)); + + if (bg_fbi == NULL) { + dev_err(fbi->device, "Cannot find the " + "background framebuffer\n"); + retval = -ENOENT; + break; + } + + /* if fb is unblank, check if the pos fit the display */ + if (mxc_fbi->cur_blank == FB_BLANK_UNBLANK) { + if (fbi->var.xres + pos.x > bg_fbi->var.xres) { + if (bg_fbi->var.xres < fbi->var.xres) + pos.x = 0; + else + pos.x = bg_fbi->var.xres - fbi->var.xres; + } + if (fbi->var.yres + pos.y > bg_fbi->var.yres) { + if (bg_fbi->var.yres < fbi->var.yres) + pos.y = 0; + else + pos.y = bg_fbi->var.yres - fbi->var.yres; + } + } + + retval = ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, + pos.x, pos.y); + + if (copy_to_user((void *)arg, &pos, sizeof(pos))) { + retval = -EFAULT; + break; + } + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_DIFMT: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_di_pix_fmt, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_FB_IPU_DI: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_di, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_FB_BLANK: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->cur_blank, argp)) + return -EFAULT; + break; + } + case MXCFB_SET_DIFMT: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (get_user(mxc_fbi->ipu_di_pix_fmt, argp)) + return -EFAULT; + + break; + } + case MXCFB_ENABLE_VSYNC_EVENT: + { + int enable; + if (get_user(enable, (int __user *)arg)) + return -EFAULT; + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + return -EINVAL; + + /* Only can control the vsync state when screen is not blank */ + if (mxc_fbi->vsync_pre_report_active != enable && + mxc_fbi->cur_blank == FB_BLANK_UNBLANK) { + if (enable) + mxcfb_enable_vsync_pre(mxc_fbi); + else + mxcfb_disable_vsync_pre(mxc_fbi); + } + + mxc_fbi->vsync_pre_report_active = enable; + break; + } + case MXCFB_CSC_UPDATE: + { + struct mxcfb_csc_matrix csc; + + if (copy_from_user(&csc, (void *) arg, sizeof(csc))) + return -EFAULT; + + if ((mxc_fbi->ipu_ch != MEM_FG_SYNC) && + (mxc_fbi->ipu_ch != MEM_BG_SYNC) && + (mxc_fbi->ipu_ch != MEM_BG_ASYNC0)) + return -EFAULT; + ipu_set_csc_coefficients(mxc_fbi->ipu, mxc_fbi->ipu_ch, + csc.param); + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + int ret = 0; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (mxc_fbi->cur_blank == blank) + return 0; + + mxc_fbi->next_blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->disable) + mxc_fbi->dispdrv->drv->disable(mxc_fbi->dispdrv); + ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true); + if (mxc_fbi->ipu_di >= 0) + ipu_uninit_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di); + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + break; + case FB_BLANK_UNBLANK: + info->var.activate = (info->var.activate & ~FB_ACTIVATE_MASK) | + FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + ret = mxcfb_set_par(info); + break; + } + if (!ret) + mxc_fbi->cur_blank = blank; + return ret; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par, + *mxc_graphic_fbi = NULL; + u_int y_bottom; + unsigned int fr_xoff, fr_yoff, fr_w, fr_h; + unsigned long base, active_alpha_phy_addr = 0; + bool loc_alpha_en = false; + int fb_stride; + int i; + int ret; + + /* no pan display during fb blank */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *bg_mxcfbi = NULL; + struct fb_info *fbi_tmp; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (fbi_tmp) + bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par)); + if (!bg_mxcfbi) + return -EINVAL; + if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) + return -EINVAL; + } + if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) + return -EINVAL; + + y_bottom = var->yoffset; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + switch (fbi_to_pixfmt(info)) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV444P: + fb_stride = info->var.xres_virtual; + break; + default: + fb_stride = info->fix.line_length; + } + + base = info->fix.smem_start; + fr_xoff = var->xoffset; + fr_w = info->var.xres_virtual; + if (!(var->vmode & FB_VMODE_YWRAP)) { + dev_dbg(info->device, "Y wrap disabled\n"); + fr_yoff = var->yoffset % info->var.yres; + fr_h = info->var.yres; + base += info->fix.line_length * info->var.yres * + (var->yoffset / info->var.yres); + } else { + dev_dbg(info->device, "Y wrap enabled\n"); + fr_yoff = var->yoffset; + fr_h = info->var.yres_virtual; + } + base += fr_yoff * fb_stride + fr_xoff; + + /* Check if DP local alpha is enabled and find the graphic fb */ + if (mxc_fbi->ipu_ch == MEM_BG_SYNC || mxc_fbi->ipu_ch == MEM_FG_SYNC) { + for (i = 0; i < num_registered_fb; i++) { + char bg_id[] = "DISP3 BG"; + char fg_id[] = "DISP3 FG"; + char *idstr = registered_fb[i]->fix.id; + bg_id[4] += mxc_fbi->ipu_id; + fg_id[4] += mxc_fbi->ipu_id; + if ((strcmp(idstr, bg_id) == 0 || + strcmp(idstr, fg_id) == 0) && + ((struct mxcfb_info *) + (registered_fb[i]->par))->alpha_chan_en) { + loc_alpha_en = true; + mxc_graphic_fbi = (struct mxcfb_info *) + (registered_fb[i]->par); + active_alpha_phy_addr = + mxc_fbi->cur_ipu_alpha_buf ? + mxc_graphic_fbi->alpha_phy_addr1 : + mxc_graphic_fbi->alpha_phy_addr0; + dev_dbg(info->device, "Updating SDC alpha " + "buf %d address=0x%08lX\n", + !mxc_fbi->cur_ipu_alpha_buf, + active_alpha_phy_addr); + break; + } + } + } + + ret = wait_for_completion_timeout(&mxc_fbi->flip_complete, HZ/2); + if (ret == 0) { + dev_err(info->device, "timeout when waiting for flip irq\n"); + return -ETIMEDOUT; + } + + ++mxc_fbi->cur_ipu_buf; + mxc_fbi->cur_ipu_buf %= 3; + mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf; + + dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n", + info->fix.id, mxc_fbi->cur_ipu_buf, base); + + if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, base) == 0) { + /* Update the DP local alpha buffer only for graphic plane */ + if (loc_alpha_en && mxc_graphic_fbi == mxc_fbi && + ipu_update_channel_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf, + active_alpha_phy_addr) == 0) { + ipu_select_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + } + + /* update u/v offset */ + ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + fbi_to_pixfmt(info), + fr_w, + fr_h, + fr_w, + 0, 0, + fr_yoff, + fr_xoff); + + ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX, " + "current buf %d, buf0 ready %d, buf1 ready %d, " + "buf2 ready %d\n", mxc_fbi->cur_ipu_buf, base, + ipu_get_cur_buffer_idx(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 0), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 1), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 2)); + ++mxc_fbi->cur_ipu_buf; + mxc_fbi->cur_ipu_buf %= 3; + ++mxc_fbi->cur_ipu_buf; + mxc_fbi->cur_ipu_buf %= 3; + mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf; + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + return -EBUSY; + } + + dev_dbg(info->device, "Update complete\n"); + + info->var.yoffset = var->yoffset; + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else if ((vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) || + (vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) { + len = mxc_fbi->alpha_mem_len; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) + return -EINVAL; + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + complete(&mxc_fbi->flip_complete); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + complete(&mxc_fbi->vsync_complete); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_vsync_pre_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + spin_lock(&mxc_fbi->lock); + mxc_fbi->vsync_pre_timestamp = ktime_get(); + spin_unlock(&mxc_fbi->lock); + queue_work(mxc_fbi->vsync_pre_queue, &mxc_fbi->vsync_pre_work); + + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + complete(&mxc_fbi->alpha_flip_complete); + return IRQ_HANDLED; +} + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_core_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int saved_blank; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + if (mxc_fbi->ovfbi) { + struct mxcfb_info *mxc_fbi_fg = + (struct mxcfb_info *)mxc_fbi->ovfbi->par; + + console_lock(); + fb_set_suspend(mxc_fbi->ovfbi, 1); + saved_blank = mxc_fbi_fg->cur_blank; + mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi); + mxc_fbi_fg->next_blank = saved_blank; + console_unlock(); + } + + console_lock(); + fb_set_suspend(fbi, 1); + saved_blank = mxc_fbi->cur_blank; + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxc_fbi->next_blank = saved_blank; + console_unlock(); + + return 0; +} + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (strstr(mxc_fbi->dispdrv->drv->name, "hdmi")) + return mxcfb_core_suspend(pdev, state); + + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_core_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + console_lock(); + mxcfb_blank(mxc_fbi->next_blank, fbi); + fb_set_suspend(fbi, 0); + console_unlock(); + + if (mxc_fbi->ovfbi) { + struct mxcfb_info *mxc_fbi_fg = + (struct mxcfb_info *)mxc_fbi->ovfbi->par; + console_lock(); + mxcfb_blank(mxc_fbi_fg->next_blank, mxc_fbi->ovfbi); + fb_set_suspend(mxc_fbi->ovfbi, 0); + console_unlock(); + } + + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (strstr(mxc_fbi->dispdrv->drv->name, "hdmi")) + return mxcfb_core_resume(pdev); + + return 0; +} + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + + fbi->screen_base = dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *)&fbi->fix.smem_start, + GFP_DMA | GFP_KERNEL); + if (fbi->screen_base == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + return -EBUSY; + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +static ssize_t show_disp_chan(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + + if (mxcfbi->ipu_ch == MEM_BG_SYNC) + return sprintf(buf, "2-layer-fb-bg\n"); + else if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return sprintf(buf, "2-layer-fb-fg\n"); + else if (mxcfbi->ipu_ch == MEM_DC_SYNC) + return sprintf(buf, "1-layer-fb\n"); + else + return sprintf(buf, "err: no display chan\n"); +} + +static ssize_t swap_disp_chan(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + struct mxcfb_info *fg_mxcfbi = NULL; + + console_lock(); + /* swap only happen between DP-BG and DC, while DP-FG disable */ + if (((mxcfbi->ipu_ch == MEM_BG_SYNC) && + (strstr(buf, "1-layer-fb") != NULL)) || + ((mxcfbi->ipu_ch == MEM_DC_SYNC) && + (strstr(buf, "2-layer-fb-bg") != NULL))) { + struct fb_info *fbi_fg; + + fbi_fg = found_registered_fb(MEM_FG_SYNC, mxcfbi->ipu_id); + if (fbi_fg) + fg_mxcfbi = (struct mxcfb_info *)fbi_fg->par; + + if (!fg_mxcfbi || + fg_mxcfbi->cur_blank == FB_BLANK_UNBLANK) { + dev_err(dev, + "Can not switch while fb2(fb-fg) is on.\n"); + console_unlock(); + return count; + } + + if (swap_channels(info) < 0) + dev_err(dev, "Swap display channel failed.\n"); + } + + console_unlock(); + return count; +} +static DEVICE_ATTR(fsl_disp_property, 644, show_disp_chan, swap_disp_chan); + +static ssize_t show_disp_dev(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + + if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return sprintf(buf, "overlay\n"); + else + return sprintf(buf, "%s\n", mxcfbi->dispdrv->drv->name); +} +static DEVICE_ATTR(fsl_disp_dev_property, S_IRUGO, show_disp_dev, NULL); + +static int mxcfb_dispdrv_init(struct platform_device *pdev, + struct fb_info *fbi) +{ + struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data; + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct mxc_dispdrv_setting setting; + char disp_dev[32], *default_dev = "lcd"; + int ret = 0; + + setting.if_fmt = plat_data->interface_pix_fmt; + setting.dft_mode_str = plat_data->mode_str; + setting.default_bpp = plat_data->default_bpp; + if (!setting.default_bpp) + setting.default_bpp = 16; + setting.fbi = fbi; + if (!strlen(plat_data->disp_dev)) { + memcpy(disp_dev, default_dev, strlen(default_dev)); + disp_dev[strlen(default_dev)] = '\0'; + } else { + memcpy(disp_dev, plat_data->disp_dev, + strlen(plat_data->disp_dev)); + disp_dev[strlen(plat_data->disp_dev)] = '\0'; + } + + dev_info(&pdev->dev, "register mxc display driver %s\n", disp_dev); + + mxcfbi->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting); + if (IS_ERR(mxcfbi->dispdrv)) { + ret = PTR_ERR(mxcfbi->dispdrv); + dev_err(&pdev->dev, "NO mxc display driver found!\n"); + return ret; + } else { + /* fix-up */ + mxcfbi->ipu_di_pix_fmt = setting.if_fmt; + mxcfbi->default_bpp = setting.default_bpp; + + /* setting */ + mxcfbi->ipu_id = setting.dev_id; + mxcfbi->ipu_di = setting.disp_id; + } + + return ret; +} + +/* + * Parse user specified options (`video=trident:') + * example: + * video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,bpp=16,noaccel + * video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,fbpix=RGB565 + */ +static int mxcfb_option_setup(struct platform_device *pdev, struct fb_info *fbi) +{ + struct ipuv3_fb_platform_data *pdata = pdev->dev.platform_data; + char *options, *opt, *fb_mode_str = NULL; + char name[] = "mxcfb0"; + uint32_t fb_pix_fmt = 0; + + name[5] += pdev->id; + if (fb_get_options(name, &options)) { + dev_err(&pdev->dev, "Can't get fb option for %s!\n", name); + return -ENODEV; + } + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "dev=", 4)) { + memcpy(pdata->disp_dev, opt + 4, strlen(opt) - 4); + pdata->disp_dev[strlen(opt) - 4] = '\0'; + } else if (!strncmp(opt, "if=", 3)) { + if (!strncmp(opt+3, "RGB24", 5)) + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB24; + else if (!strncmp(opt+3, "BGR24", 5)) + pdata->interface_pix_fmt = IPU_PIX_FMT_BGR24; + else if (!strncmp(opt+3, "GBR24", 5)) + pdata->interface_pix_fmt = IPU_PIX_FMT_GBR24; + else if (!strncmp(opt+3, "RGB565", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB565; + else if (!strncmp(opt+3, "RGB666", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB666; + else if (!strncmp(opt+3, "YUV444", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_YUV444; + else if (!strncmp(opt+3, "LVDS666", 7)) + pdata->interface_pix_fmt = IPU_PIX_FMT_LVDS666; + else if (!strncmp(opt+3, "YUYV16", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_YUYV; + else if (!strncmp(opt+3, "UYVY16", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_UYVY; + else if (!strncmp(opt+3, "YVYU16", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_YVYU; + else if (!strncmp(opt+3, "VYUY16", 6)) + pdata->interface_pix_fmt = IPU_PIX_FMT_VYUY; + } else if (!strncmp(opt, "fbpix=", 6)) { + if (!strncmp(opt+6, "RGB24", 5)) + fb_pix_fmt = IPU_PIX_FMT_RGB24; + else if (!strncmp(opt+6, "BGR24", 5)) + fb_pix_fmt = IPU_PIX_FMT_BGR24; + else if (!strncmp(opt+6, "RGB32", 5)) + fb_pix_fmt = IPU_PIX_FMT_RGB32; + else if (!strncmp(opt+6, "BGR32", 5)) + fb_pix_fmt = IPU_PIX_FMT_BGR32; + else if (!strncmp(opt+6, "ABGR32", 6)) + fb_pix_fmt = IPU_PIX_FMT_ABGR32; + else if (!strncmp(opt+6, "RGB565", 6)) + fb_pix_fmt = IPU_PIX_FMT_RGB565; + + if (fb_pix_fmt) { + pixfmt_to_var(fb_pix_fmt, &fbi->var); + pdata->default_bpp = + fbi->var.bits_per_pixel; + } + } else if (!strncmp(opt, "int_clk", 7)) { + pdata->int_clk = true; + continue; + } else if (!strncmp(opt, "bpp=", 4)) { + /* bpp setting cannot overwirte fbpix setting */ + if (fb_pix_fmt) + continue; + + pdata->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + + fb_pix_fmt = bpp_to_pixfmt(pdata->default_bpp); + if (fb_pix_fmt) + pixfmt_to_var(fb_pix_fmt, &fbi->var); + } else + fb_mode_str = opt; + } + + if (fb_mode_str) + pdata->mode_str = fb_mode_str; + + return 0; +} + +static int mxcfb_register(struct fb_info *fbi) +{ + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct fb_videomode m; + int ret = 0; + char bg0_id[] = "DISP3 BG"; + char bg1_id[] = "DISP3 BG - DI1"; + char fg_id[] = "DISP3 FG"; + + if (mxcfbi->ipu_di == 0) { + bg0_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, bg0_id); + } else if (mxcfbi->ipu_di == 1) { + bg1_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, bg1_id); + } else { /* Overlay */ + fg_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, fg_id); + } + + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + /* Added first mode to fbi modelist. */ + if (!fbi->modelist.next || !fbi->modelist.prev) + INIT_LIST_HEAD(&fbi->modelist); + fb_var_to_videomode(&m, &fbi->var); + fb_add_videomode(&m, &fbi->modelist); + + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, + mxcfb_irq_handler, IPU_IRQF_ONESHOT, MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering EOF irq handler.\n"); + ret = -EBUSY; + goto err0; + } + ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq); + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, + mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT, MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering NFACK irq handler.\n"); + ret = -EBUSY; + goto err1; + } + ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq); + + if (mxcfbi->ipu_alp_ch_irq != -1) + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, + mxcfb_alpha_irq_handler, IPU_IRQF_ONESHOT, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering alpha irq " + "handler.\n"); + ret = -EBUSY; + goto err2; + } + + if (!mxcfbi->late_init) { + fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + fbi->flags |= FBINFO_MISC_USEREVENT; + ret = fb_set_var(fbi, &fbi->var); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + if (ret < 0) { + dev_err(fbi->device, "Error fb_set_var ret:%d\n", ret); + goto err3; + } + + if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { + console_lock(); + ret = fb_blank(fbi, FB_BLANK_UNBLANK); + console_unlock(); + if (ret < 0) { + dev_err(fbi->device, + "Error fb_blank ret:%d\n", ret); + goto err4; + } + } + } else { + /* + * Setup the channel again though bootloader + * has done this, then set_par() can stop the + * channel neatly and re-initialize it . + */ + if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { + console_lock(); + _setup_disp_channel1(fbi); + ipu_enable_channel(mxcfbi->ipu, mxcfbi->ipu_ch); + console_unlock(); + } + } + + + ret = register_framebuffer(fbi); + if (ret < 0) + goto err5; + + return ret; +err5: + if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { + console_lock(); + if (!mxcfbi->late_init) + fb_blank(fbi, FB_BLANK_POWERDOWN); + else { + ipu_disable_channel(mxcfbi->ipu, mxcfbi->ipu_ch, + true); + ipu_uninit_channel(mxcfbi->ipu, mxcfbi->ipu_ch); + } + console_unlock(); + } +err4: +err3: + if (mxcfbi->ipu_alp_ch_irq != -1) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi); +err2: + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi); +err1: + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi); +err0: + return ret; +} + +static void mxcfb_unregister(struct fb_info *fbi) +{ + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + + if (mxcfbi->ipu_alp_ch_irq != -1) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi); + if (mxcfbi->ipu_ch_irq) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi); + if (mxcfbi->ipu_ch_nf_irq) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi); + + unregister_framebuffer(fbi); +} + +static int mxcfb_setup_overlay(struct platform_device *pdev, + struct fb_info *fbi_bg, struct resource *res) +{ + struct fb_info *ovfbi; + struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par; + struct mxcfb_info *mxcfbi_fg; + int ret = 0; + + ovfbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!ovfbi) { + ret = -ENOMEM; + goto init_ovfbinfo_failed; + } + mxcfbi_fg = (struct mxcfb_info *)ovfbi->par; + + mxcfbi_fg->ipu = ipu_get_soc(mxcfbi_bg->ipu_id); + if (IS_ERR(mxcfbi_fg->ipu)) { + ret = -ENODEV; + goto get_ipu_failed; + } + mxcfbi_fg->ipu_id = mxcfbi_bg->ipu_id; + mxcfbi_fg->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF; + mxcfbi_fg->ipu_ch_nf_irq = IPU_IRQ_FG_SYNC_NFACK; + mxcfbi_fg->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + /* Vsync-pre irq is invalid for overlay channel. */ + mxcfbi_fg->ipu_vsync_pre_irq = -1; + mxcfbi_fg->ipu_ch = MEM_FG_SYNC; + mxcfbi_fg->ipu_di = -1; + mxcfbi_fg->ipu_di_pix_fmt = mxcfbi_bg->ipu_di_pix_fmt; + mxcfbi_fg->overlay = true; + mxcfbi_fg->cur_blank = mxcfbi_fg->next_blank = FB_BLANK_POWERDOWN; + + /* Need dummy values until real panel is configured */ + ovfbi->var.xres = 240; + ovfbi->var.yres = 320; + + if (res && res->start && res->end) { + ovfbi->fix.smem_len = res->end - res->start + 1; + ovfbi->fix.smem_start = res->start; + ovfbi->screen_base = ioremap( + ovfbi->fix.smem_start, + ovfbi->fix.smem_len); + } + + ret = mxcfb_register(ovfbi); + if (ret < 0) + goto register_ov_failed; + + mxcfbi_bg->ovfbi = ovfbi; + + return ret; + +register_ov_failed: +get_ipu_failed: + fb_dealloc_cmap(&ovfbi->cmap); + framebuffer_release(ovfbi); +init_ovfbinfo_failed: + return ret; +} + +static void mxcfb_unsetup_overlay(struct fb_info *fbi_bg) +{ + struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par; + struct fb_info *ovfbi = mxcfbi_bg->ovfbi; + + mxcfb_unregister(ovfbi); + + if (&ovfbi->cmap) + fb_dealloc_cmap(&ovfbi->cmap); + framebuffer_release(ovfbi); +} + +static bool ipu_usage[2][2]; +static int ipu_test_set_usage(int ipu, int di) +{ + if (ipu_usage[ipu][di]) + return -EBUSY; + else + ipu_usage[ipu][di] = true; + return 0; +} + +static void ipu_clear_usage(int ipu, int di) +{ + ipu_usage[ipu][di] = false; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data; + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct resource *res; + struct device *disp_dev; + char buf[32]; + int ret = 0; + + /* Initialize FB structures */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto init_fbinfo_failed; + } + + ret = mxcfb_option_setup(pdev, fbi); + if (ret) + goto get_fb_option_failed; + + mxcfbi = (struct mxcfb_info *)fbi->par; + spin_lock_init(&mxcfbi->lock); + mxcfbi->fbi = fbi; + mxcfbi->ipu_int_clk = plat_data->int_clk; + mxcfbi->late_init = plat_data->late_init; + mxcfbi->first_set_par = true; + ret = mxcfb_dispdrv_init(pdev, fbi); + if (ret < 0) + goto init_dispdrv_failed; + + ret = ipu_test_set_usage(mxcfbi->ipu_id, mxcfbi->ipu_di); + if (ret < 0) { + dev_err(&pdev->dev, "ipu%d-di%d already in use\n", + mxcfbi->ipu_id, mxcfbi->ipu_di); + goto ipu_in_busy; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res && res->start && res->end) { + fbi->fix.smem_len = res->end - res->start + 1; + fbi->fix.smem_start = res->start; + fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len); + /* Do not clear the fb content drawn in bootloader. */ + if (!mxcfbi->late_init) + memset(fbi->screen_base, 0, fbi->fix.smem_len); + } + + mxcfbi->ipu = ipu_get_soc(mxcfbi->ipu_id); + if (IS_ERR(mxcfbi->ipu)) { + ret = -ENODEV; + goto get_ipu_failed; + } + + /* first user uses DP with alpha feature */ + if (!g_dp_in_use[mxcfbi->ipu_id]) { + mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF; + mxcfbi->ipu_ch_nf_irq = IPU_IRQ_BG_SYNC_NFACK; + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + mxcfbi->ipu_vsync_pre_irq = mxcfbi->ipu_di ? + IPU_IRQ_VSYNC_PRE_1 : + IPU_IRQ_VSYNC_PRE_0; + mxcfbi->ipu_ch = MEM_BG_SYNC; + /* Unblank the primary fb only by default */ + if (pdev->id == 0) + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK; + else + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + + ret = mxcfb_register(fbi); + if (ret < 0) + goto mxcfb_register_failed; + + ipu_disp_set_global_alpha(mxcfbi->ipu, mxcfbi->ipu_ch, + true, 0x80); + ipu_disp_set_color_key(mxcfbi->ipu, mxcfbi->ipu_ch, false, 0); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + ret = mxcfb_setup_overlay(pdev, fbi, res); + + if (ret < 0) { + mxcfb_unregister(fbi); + goto mxcfb_setupoverlay_failed; + } + + g_dp_in_use[mxcfbi->ipu_id] = true; + + ret = device_create_file(mxcfbi->ovfbi->dev, + &dev_attr_fsl_disp_property); + if (ret) + dev_err(mxcfbi->ovfbi->dev, "Error %d on creating " + "file for disp property\n", + ret); + + ret = device_create_file(mxcfbi->ovfbi->dev, + &dev_attr_fsl_disp_dev_property); + if (ret) + dev_err(mxcfbi->ovfbi->dev, "Error %d on creating " + "file for disp device " + "propety\n", ret); + } else { + mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF; + mxcfbi->ipu_ch_nf_irq = IPU_IRQ_DC_SYNC_NFACK; + mxcfbi->ipu_alp_ch_irq = -1; + mxcfbi->ipu_vsync_pre_irq = mxcfbi->ipu_di ? + IPU_IRQ_VSYNC_PRE_1 : + IPU_IRQ_VSYNC_PRE_0; + mxcfbi->ipu_ch = MEM_DC_SYNC; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + + ret = mxcfb_register(fbi); + if (ret < 0) + goto mxcfb_register_failed; + } + + platform_set_drvdata(pdev, fbi); + + ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property); + if (ret) + dev_err(&pdev->dev, "Error %d on creating file for disp " + "property\n", ret); + + ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_dev_property); + if (ret) + dev_err(&pdev->dev, "Error %d on creating file for disp " + " device propety\n", ret); + + disp_dev = mxc_dispdrv_getdev(mxcfbi->dispdrv); + if (disp_dev) { + ret = sysfs_create_link(&fbi->dev->kobj, + &disp_dev->kobj, "disp_dev"); + if (ret) + dev_err(&pdev->dev, + "Error %d on creating file\n", ret); + } + + INIT_WORK(&mxcfbi->vsync_pre_work, mxcfb_vsync_pre_work); + + snprintf(buf, sizeof(buf), "mxcfb%d-vsync-pre", fbi->node); + mxcfbi->vsync_pre_queue = create_singlethread_workqueue(buf); + if (mxcfbi->vsync_pre_queue == NULL) { + dev_err(fbi->device, + "Failed to alloc vsync-pre workqueue\n"); + ret = -ENOMEM; + goto workqueue_alloc_failed; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + mxcfbi->fbdrv_earlysuspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + mxcfbi->fbdrv_earlysuspend.suspend = mxcfb_early_suspend; + mxcfbi->fbdrv_earlysuspend.resume = mxcfb_later_resume; + mxcfbi->fbdrv_earlysuspend.data = pdev; + register_early_suspend(&mxcfbi->fbdrv_earlysuspend); +#endif + +#ifdef CONFIG_LOGO + fb_prepare_logo(fbi, 0); + fb_show_logo(fbi, 0); +#endif + + return 0; + +workqueue_alloc_failed: +mxcfb_setupoverlay_failed: +mxcfb_register_failed: +get_ipu_failed: + ipu_clear_usage(mxcfbi->ipu_id, mxcfbi->ipu_di); +ipu_in_busy: +init_dispdrv_failed: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); +get_fb_option_failed: +init_fbinfo_failed: + return ret; +} + +static int mxcfb_remove(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = fbi->par; + + if (!fbi) + return 0; + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&mxc_fbi->fbdrv_earlysuspend); +#endif + + device_remove_file(fbi->dev, &dev_attr_fsl_disp_dev_property); + device_remove_file(fbi->dev, &dev_attr_fsl_disp_property); + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxcfb_unregister(fbi); + mxcfb_unmap_video_memory(fbi); + + if (mxc_fbi->ovfbi) { + device_remove_file(mxc_fbi->ovfbi->dev, + &dev_attr_fsl_disp_dev_property); + device_remove_file(mxc_fbi->ovfbi->dev, + &dev_attr_fsl_disp_property); + mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi); + mxcfb_unsetup_overlay(fbi); + mxcfb_unmap_video_memory(mxc_fbi->ovfbi); + } + + ipu_clear_usage(mxc_fbi->ipu_id, mxc_fbi->ipu_di); + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .remove = mxcfb_remove, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mxcfb_early_suspend(struct early_suspend *h) +{ + struct platform_device *pdev = (struct platform_device *)h->data; + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + pm_message_t state = { .event = PM_EVENT_SUSPEND }; + struct fb_event event; + int blank = FB_BLANK_POWERDOWN; + + if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return; + + if (strstr(mxcfbi->dispdrv->drv->name, "hdmi")) { + /* Only black the hdmi fb due to audio dependency */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return; + } + + mxcfb_core_suspend(pdev, state); + event.info = fbi; + event.data = ␣ + fb_notifier_call_chain(FB_EVENT_BLANK, &event); +} + +static void mxcfb_later_resume(struct early_suspend *h) +{ + struct platform_device *pdev = (struct platform_device *)h->data; + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct fb_event event; + + if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return; + + /* HDMI resume function has been called */ + if (strstr(mxcfbi->dispdrv->drv->name, "hdmi")) + return; + + mxcfb_core_resume(pdev); + event.info = fbi; + event.data = &mxcfbi->next_blank; + fb_notifier_call_chain(FB_EVENT_BLANK, &event); +} +#endif + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + return platform_driver_register(&mxcfb_driver); +} + +void mxcfb_exit(void) +{ + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxc_lcdif.c b/drivers/video/mxc/mxc_lcdif.c new file mode 100644 index 00000000..5cbdc73c --- /dev/null +++ b/drivers/video/mxc/mxc_lcdif.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mxcfb.h> +#include <linux/fsl_devices.h> +#include "mxc_dispdrv.h" + +struct mxc_lcdif_data { + struct platform_device *pdev; + struct mxc_dispdrv_handle *disp_lcdif; +}; + +#define DISPDRV_LCD "lcd" + +static struct fb_videomode lcdif_modedb[] = { + { + /* 800x480 @ 57 Hz , pixel clk @ 27MHz */ + "CLAA-WVGA", 57, 800, 480, 37037, 40, 60, 10, 10, 20, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 800x480 @ 60 Hz , pixel clk @ 32MHz */ + "SEIKO-WVGA", 60, 800, 480, 29850, 89, 164, 23, 10, 10, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, +}; +static int lcdif_modedb_sz = ARRAY_SIZE(lcdif_modedb); + +static int lcdif_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + int ret, i; + struct mxc_lcdif_data *lcdif = mxc_dispdrv_getdata(disp); + struct fsl_mxc_lcd_platform_data *plat_data + = lcdif->pdev->dev.platform_data; + struct fb_videomode *modedb = lcdif_modedb; + int modedb_sz = lcdif_modedb_sz; + + /* use platform defined ipu/di */ + setting->dev_id = plat_data->ipu_id; + setting->disp_id = plat_data->disp_id; + + ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, + modedb, modedb_sz, NULL, setting->default_bpp); + if (!ret) { + fb_videomode_to_var(&setting->fbi->var, &modedb[0]); + setting->if_fmt = plat_data->default_ifmt; + } + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < modedb_sz; i++) { + struct fb_videomode m; + fb_var_to_videomode(&m, &setting->fbi->var); + if (fb_mode_is_equal(&m, &modedb[i])) { + fb_add_videomode(&modedb[i], + &setting->fbi->modelist); + break; + } + } + + return ret; +} + +void lcdif_deinit(struct mxc_dispdrv_handle *disp) +{ + /*TODO*/ +} + +static struct mxc_dispdrv_driver lcdif_drv = { + .name = DISPDRV_LCD, + .init = lcdif_init, + .deinit = lcdif_deinit, +}; + +static int mxc_lcdif_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_lcdif_data *lcdif; + + lcdif = kzalloc(sizeof(struct mxc_lcdif_data), GFP_KERNEL); + if (!lcdif) { + ret = -ENOMEM; + goto alloc_failed; + } + + lcdif->pdev = pdev; + lcdif->disp_lcdif = mxc_dispdrv_register(&lcdif_drv); + mxc_dispdrv_setdata(lcdif->disp_lcdif, lcdif); + + dev_set_drvdata(&pdev->dev, lcdif); + +alloc_failed: + return ret; +} + +static int mxc_lcdif_remove(struct platform_device *pdev) +{ + struct mxc_lcdif_data *lcdif = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_puthandle(lcdif->disp_lcdif); + mxc_dispdrv_unregister(lcdif->disp_lcdif); + kfree(lcdif); + return 0; +} + +static struct platform_driver mxc_lcdif_driver = { + .driver = { + .name = "mxc_lcdif", + }, + .probe = mxc_lcdif_probe, + .remove = mxc_lcdif_remove, +}; + +static int __init mxc_lcdif_init(void) +{ + return platform_driver_register(&mxc_lcdif_driver); +} + +static void __exit mxc_lcdif_exit(void) +{ + platform_driver_unregister(&mxc_lcdif_driver); +} + +module_init(mxc_lcdif_init); +module_exit(mxc_lcdif_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX ipuv3 LCD extern port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxc_spdc_fb.c b/drivers/video/mxc/mxc_spdc_fb.c new file mode 100644 index 00000000..0c27a32b --- /dev/null +++ b/drivers/video/mxc/mxc_spdc_fb.c @@ -0,0 +1,4180 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 + * + */ +/* + * Based on MXC EPDC Driver, Freescale Semiconductor, Inc. All Rights Reserved. + */ + +#include "mxc_spdc_fb.h" + +#define MERGE_OK 0 +#define MERGE_FAIL 1 +#define MERGE_BLOCK 2 + +#define SPDC_DEFAULT_TEMP 30 +#define TEMP_NO_SET 0xFF +#define POWER_STATE_OFF 0 +#define POWER_STATE_ON 1 +#define POWER_READY_OFF false +#define POWER_READY_ON true + +#define INIT_UPDATE_MARKER 0x12345678 +#define PAN_UPDATE_MARKER 0x12345679 + +#define SPDC_MAX_NUM_UPDATES 32 +#define SPDC_MAX_NUM_BUFFERS 2 +#define SPDC_MAX_NUM_PREPROCESS 15 +#define NUM_SCREENS_MIN 2 +#define SPDC_DEFAULT_BPP 16 + +mxc_spdc_t *g_spdc_fb_data; + +static int mxc_spdc_fb_send_update(struct mxcfb_update_data *upd_data, + struct fb_info *info); +static int mxc_spdc_fb_wait_update_complete(struct mxcfb_update_marker_data + *marker_data, struct fb_info *info); + +static const struct mxc_spdc_resolution_map_para spdc_gray_res_map[] = { +/*define Gray Mode resolution mapping*/ + {0x18, 600, 800, PORTRAIT}, + {0x19, 768, 1024, PORTRAIT}, + {0x1a, 0, 0, RESERVED}, + {0x1b, 600, 1024, PORTRAIT}, + {0x1c, 825, 1200, PORTRAIT}, + {0x1d, 1024, 1280, PORTRAIT}, + {0x1e, 1200, 1600, PORTRAIT}, + {0x10, 800, 1024, PORTRAIT}, + {0x11, 825, 1280, PORTRAIT}, + {0x12, 800, 1280, PORTRAIT}, + {0x13, 768, 1280, PORTRAIT}, + {0x14, 960, 1280, PORTRAIT}, + {0x0, 800, 600, LANDSCAPE}, + {0x1, 1024, 768, LANDSCAPE}, + {0x2, 0, 0, RESERVED}, + {0x3, 1024, 600, LANDSCAPE}, + {0x4, 1200, 825, LANDSCAPE}, + {0x5, 1280, 1024, LANDSCAPE}, + {0x6, 1600, 1200, LANDSCAPE}, + {0x7, 1024, 800, LANDSCAPE}, + {0x8, 1280, 825, LANDSCAPE}, + {0x9, 1280, 800, LANDSCAPE}, + {0xa, 1280, 768, LANDSCAPE}, + {0xb, 1280, 960, LANDSCAPE}, + {0xFFFF, 800, 600, LANDSCAPE}, +}; + +static const struct mxc_spdc_resolution_map_para spdc_rgbw_res_map[] = { +/*define RGBW Mode resolution mapping*/ + {0x18, 300, 400, PORTRAIT}, + {0x19, 384, 512, PORTRAIT}, + {0x1a, 0, 0, RESERVED}, + {0x1b, 300, 512, PORTRAIT}, + {0x1c, 0, 0, RESERVED}, + {0x1d, 512, 640, PORTRAIT}, + {0x1e, 600, 800, PORTRAIT}, + {0x10, 400, 512, PORTRAIT}, + {0x11, 0, 0, RESERVED}, + {0x12, 400, 640, PORTRAIT}, + {0x13, 384, 640, PORTRAIT}, + {0x14, 480, 640, PORTRAIT}, + {0x0, 400, 300, LANDSCAPE}, + {0x1, 512, 384, LANDSCAPE}, + {0x2, 0, 0, RESERVED}, + {0x3, 512, 300, LANDSCAPE}, + {0x4, 0, 0, RESERVED}, + {0x5, 640, 512, LANDSCAPE}, + {0x6, 800, 600, LANDSCAPE}, + {0x7, 512, 400, LANDSCAPE}, + {0x8, 0, 0, RESERVED}, + {0x9, 640, 400, LANDSCAPE}, + {0xa, 640, 384, LANDSCAPE}, + {0xb, 640, 480, LANDSCAPE}, + {0xFFFF, 400, 300, LANDSCAPE}, +}; + +static void get_panel_init_set(struct imx_spdc_panel_init_set* + panel_set, u32 *val) +{ + *val = panel_set->yoe_pol | + (panel_set->dual_gate << 1) | + (panel_set->ud << 7) | + (panel_set->rl << 8) | + (panel_set->data_filter_n << 9) | + (panel_set->power_ready << 10) | + (panel_set->rgbw_mode_enable << 11) | + (panel_set->hburst_len_en << 13) | + ((panel_set->resolution & 0x1F) << 2); +} + +static inline void spdc_intr_enable(mxc_spdc_t *fb_data, u32 int_type) +{ + u32 status; + + status = __raw_readl(fb_data->hwp + SPDC_INT_ENABLE); + status |= (int_type & SPDC_IRQ_ALL_MASK); + __raw_writel(status, fb_data->hwp + SPDC_INT_ENABLE); +} + +static bool spdc_is_update_finish(mxc_spdc_t *fb_data) +{ + u32 val = __raw_readl(fb_data->hwp + SPDC_INT_STA_CLR); + bool is_finish = (val & SPDC_IRQ_STA_FRAME_UPDATE) ? true : false; + + return is_finish; +} + +static int spdc_get_intr_stat(mxc_spdc_t *fb_data) +{ + u32 status = __raw_readl(fb_data->hwp + SPDC_INT_STA_CLR); + return status & 0xF; +} + +static inline void +spdc_intr_stat_clear(mxc_spdc_t *fb_data, u32 int_type) +{ + /* write 1 to clear status */ + u32 status = (int_type & SPDC_IRQ_STA_ALL_MASK); + __raw_writel(status, fb_data->hwp + SPDC_INT_STA_CLR); +} + +static inline void spdc_set_nextbuf_addr(mxc_spdc_t *fb_data) +{ + u32 addr = fb_data->fresh_param.buf_addr.next_buf_phys_addr; + __raw_writel(addr, fb_data->hwp + SPDC_NEXT_BUF); + dev_dbg(fb_data->dev, "add: 0x%x\n", addr); +} + +static inline void spdc_set_curbuf_addr(mxc_spdc_t *fb_data) +{ + u32 addr = fb_data->fresh_param.buf_addr.cur_buf_phys_addr; + __raw_writel(addr, fb_data->hwp + SPDC_CURRENT_BUF); +} + +static inline void spdc_set_prebuf_addr(mxc_spdc_t *fb_data) +{ + u32 addr = fb_data->fresh_param.buf_addr.pre_buf_phys_addr; + __raw_writel(addr, fb_data->hwp + SPDC_PRE_BUF); +} + +static inline void spdc_set_cntbuf_addr(mxc_spdc_t *fb_data) +{ + u32 addr = fb_data->fresh_param.buf_addr.frm_cnt_buf_phys_addr; + __raw_writel(addr, fb_data->hwp + SPDC_CNT_BUF); +} + +static inline void spdc_set_lutbuf_addr(mxc_spdc_t *fb_data) +{ + u32 addr = fb_data->fresh_param.buf_addr.lut_buf_phys_addr; + __raw_writel(addr, fb_data->hwp + SPDC_LUT_BUF); +} + +static inline void spdc_set_update_coord(mxc_spdc_t *fb_data) +{ + u32 x = fb_data->fresh_param.update_region.left; + u32 y = fb_data->fresh_param.update_region.top; + + if (!x) + x++; + if (!y) + y++; + + dev_dbg(fb_data->dev, "x:%d, y:%d\n", x, y); + x = (u32)(((x & SPDC_UPDATE_X_Y_MAX_SIZE) << 16) | + (y & SPDC_UPDATE_X_Y_MAX_SIZE)); + __raw_writel(x, fb_data->hwp + SPDC_UPDATA_X_Y); +} + +static inline void spdc_set_update_dimensions(mxc_spdc_t *fb_data) +{ + u32 w = fb_data->fresh_param.update_region.width; + u32 h = fb_data->fresh_param.update_region.height; + + if (!w) + w++; + if (!h) + h++; + + dev_dbg(fb_data->dev, "w:%d, h:%d\n", w, h); + w = (u32)(((w & SPDC_UPDATE_W_H_MAX_SIZE) << 16) | + (h & SPDC_UPDATE_W_H_MAX_SIZE)); + __raw_writel(w, fb_data->hwp + SPDC_UPDATE_W_H); +} + +static inline void spdc_set_update_temper(mxc_spdc_t *fb_data) +{ + s8 temper = (s8)(fb_data->fresh_param.temper & 0xFF) << 1; + + if (temper > -110 && temper < 200) + __raw_writel(temper, fb_data->hwp + SPDC_TEMP_INFO); + else + __raw_writel(SPDC_DEFAULT_TEMP, fb_data->hwp + SPDC_TEMP_INFO); +} + +static inline void spdc_trigger_update(mxc_spdc_t *fb_data) +{ + u32 val; + struct partial_refresh_param *fresh_param = &fb_data->fresh_param; + + if ((fresh_param->wave_mode & SPDC_WAV_MODE_MASK) && fresh_param->flash) + val = SPDC_DISP_TRIGGER_FLASH; + else + val = 0; + + val |= fresh_param->wave_mode << 1; + val |= SPDC_DISP_TRIGGER_ENABLE; + dev_dbg(fb_data->dev, "wave:%d\n", fresh_param->wave_mode); + __raw_writel(val, fb_data->hwp + SPDC_DISP_TRIGGER); +} + +static bool is_lut_checksum_ok(mxc_spdc_t *fb_data) +{ + u32 status; + + status = __raw_readl(fb_data->hwp + SPDC_STATUS); + status &= SPDC_IRQ_STA_ERR; + + return status ? true : false; +} + +static void spdc_clk_gate(mxc_spdc_t *fb_data, bool enable) +{ + if (enable) + __raw_writel(SPDC_SW_GATE_CLK_ENABLE, + fb_data->hwp + SPDC_SW_GATE_CLK); + else + __raw_writel(~SPDC_SW_GATE_CLK_ENABLE, + fb_data->hwp + SPDC_SW_GATE_CLK); +} + +static int update_panel_init_set(mxc_spdc_t *fb_data) +{ + int ret = 0; + u32 init_val; + + get_panel_init_set(&fb_data->panel_set, &init_val); + dev_dbg(fb_data->dev, "panel init setting:%x\n", init_val); + + __raw_writel(init_val, fb_data->hwp + SPDC_PANEL_INIT_SET); + + /*wait init setting update finish*/ + ret = wait_for_completion_timeout(&fb_data->init_finish, + msecs_to_jiffies(4000)); + if (!ret) + dev_err(fb_data->dev, "Timed out for init setting!\n"); + + return ret; +} + +static void spdc_panel_pwr_on(mxc_spdc_t *fb_data) +{ + fb_data->panel_set.power_ready = POWER_READY_ON; +} + +static void spdc_panel_pwr_down(mxc_spdc_t *fb_data) +{ + fb_data->panel_set.power_ready = POWER_READY_OFF; +} + +static void spdc_powerdown(mxc_spdc_t *fb_data) +{ + mutex_lock(&fb_data->power_mutex); + + /* If powering_down has been cleared, a powerup + * request is pre-empting this powerdown request. + */ + if (!fb_data->powering_down + || (fb_data->power_state == POWER_STATE_OFF)) { + mutex_unlock(&fb_data->power_mutex); + return; + } + + dev_dbg(fb_data->dev, "spdc Powerdown\n"); + + /* Disable power to the AUO panel */ + regulator_disable(fb_data->vcom_regulator); + regulator_disable(fb_data->display_regulator); + + /*enable spdc clock gating*/ + spdc_clk_gate(fb_data, true); + clk_disable(fb_data->spdc_clk_pix); + clk_disable(fb_data->spdc_clk_axi); + + /* Disable pins used by SPDC (to prevent leakage current) */ + if (fb_data->pdata->disable_pins) + fb_data->pdata->disable_pins(); + + /* turn off the V3p3 */ + regulator_disable(fb_data->v3p3_regulator); + + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + spdc_panel_pwr_down(fb_data); + + if (fb_data->wait_for_powerdown) { + fb_data->wait_for_powerdown = false; + complete(&fb_data->powerdown_compl); + } + + mutex_unlock(&fb_data->power_mutex); +} + +static void spdc_powerup(mxc_spdc_t *fb_data) +{ + int ret = 0; + mutex_lock(&fb_data->power_mutex); + + /* + * If power down request is pending, clear + * powering_down to cancel the request. + */ + if (fb_data->powering_down) + fb_data->powering_down = false; + + if (fb_data->power_state == POWER_STATE_ON) { + mutex_unlock(&fb_data->power_mutex); + return; + } + + dev_dbg(fb_data->dev, "spdc Powerup\n"); + + /* Enable the v3p3 regulator */ + ret = regulator_enable(fb_data->v3p3_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable V3P3 regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + + msleep(1); + + /* Enable pins used by SPDC */ + if (fb_data->pdata->enable_pins) + fb_data->pdata->enable_pins(); + + /* Enable clocks to SPDC */ + clk_enable(fb_data->spdc_clk_axi); + clk_enable(fb_data->spdc_clk_pix); + + /*disable spdc gate*/ + spdc_clk_gate(fb_data, false); + + /* Enable power to the EPD panel */ + ret = regulator_enable(fb_data->display_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable DISPLAY regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + ret = regulator_enable(fb_data->vcom_regulator); + if (IS_ERR((void *)ret)) { + dev_err(fb_data->dev, "Unable to enable VCOM regulator." + "err = 0x%x\n", ret); + mutex_unlock(&fb_data->power_mutex); + return; + } + + fb_data->power_state = POWER_STATE_ON; + spdc_panel_pwr_on(fb_data); + + mutex_unlock(&fb_data->power_mutex); +} + +#ifdef DEBUG +static void +check_waveform(u32 *wv_buf_orig, u32 *wv_buf_cur, u32 wv_buf_size) +{ + int i; + bool is_mismatch = false; + for (i = 0; i < wv_buf_size; i++) { + if (wv_buf_orig[i] != wv_buf_cur[i]) { + is_mismatch = true; + printk(KERN_ERR "Waveform mismatch!\n"); + } + } + + if (!is_mismatch) + printk(KERN_DEBUG "No mismatches!\n"); +} +#else +static void +check_waveform(u32 *wv_buf_orig, u32 *wv_buf_cur, u32 wv_buf_size) {} +#endif + +static void get_spdc_version(mxc_spdc_t *fb_data) +{ + struct mxc_spdc_version *spdc_ver = &fb_data->spdc_ver; + u32 disp_id, tcon_id; + + disp_id = __raw_readl(fb_data->hwp + SPDC_DISP_VER); + tcon_id = __raw_readl(fb_data->hwp + SPDC_TCON_VER); + + spdc_ver->disp_ver.product_id = disp_id & 0xFFFF; + spdc_ver->disp_ver.lut_ver = (disp_id >> 16) & 0xFF; + spdc_ver->disp_ver.epd_type = (disp_id >> 24) & 0xFF; + spdc_ver->tcon_ver = tcon_id & 0xFF; + + dev_info(fb_data->dev, "EPD type ID:%x, Tcon ID:%x\n", + spdc_ver->disp_ver.product_id, spdc_ver->tcon_ver); +} + +static void spdc_set_update_concurrency(mxc_spdc_t *fb_data) +{ + u32 concur_mode; + + concur_mode = fb_data->fresh_param.concur & 0xFF; + concur_mode |= (SPDC_LUT_MODE_OFFSET << 8); + + __raw_writel(concur_mode, fb_data->hwp + SPDC_LUT_PARA_UPDATE); +} + +static bool is_preprocess_list_full(mxc_spdc_t *fb_data) +{ + /* Check to see if preprocess are full in this list */ + if (fb_data->upd_preprocess_num >= SPDC_MAX_NUM_PREPROCESS) + return true; + else + return false; +} + +static void spdc_submit_update(mxc_spdc_t *fb_data) +{ + fb_data->updates_active = true; + + spdc_set_nextbuf_addr(fb_data); + spdc_set_update_coord(fb_data); + spdc_set_update_dimensions(fb_data); + spdc_set_update_temper(fb_data); + spdc_trigger_update(fb_data); +} + +static int spdc_init_sequence(mxc_spdc_t *fb_data) +{ + struct fb_var_screeninfo *screeninfo = &fb_data->spdc_fb_var; + struct spdc_buffer_addr *buf_addr = &fb_data->fresh_param.buf_addr; + struct imx_spdc_panel_init_set *init_set = + fb_data->pdata->spdc_mode->init_set; + u32 xres, yres; + int ret = -EFAULT; + + /*init spdc power*/ + spdc_powerup(fb_data); + + /* enable all interrupt */ + spdc_intr_stat_clear(fb_data, SPDC_INT_STA_CLR); + spdc_intr_enable(fb_data, SPDC_IRQ_ALL_MASK); + /* set ACC concurrency update mode */ + if (fb_data->fresh_param.concur) + spdc_set_update_concurrency(fb_data); + + /* program SPDC register and trigger to process buffer*/ + buf_addr->next_buf_phys_addr = fb_data->phy_next_buf; + buf_addr->cur_buf_phys_addr = fb_data->phy_current_buf; + buf_addr->pre_buf_phys_addr = fb_data->phy_pre_buf; + buf_addr->frm_cnt_buf_phys_addr = fb_data->phy_cnt_buf; + buf_addr->lut_buf_phys_addr = fb_data->phy_lut_buf; + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + xres = screeninfo->xres; + yres = screeninfo->yres; + } + fb_data->fresh_param.update_region.left = 0; + fb_data->fresh_param.update_region.top = 0; + fb_data->fresh_param.update_region.width = xres; + fb_data->fresh_param.update_region.height = yres; + + /* set panel temperature as environment temperature */ + fb_data->fresh_param.temper = SPDC_DEFAULT_TEMP; + /* set waveform mode */ + fb_data->fresh_param.wave_mode = SPDC_WAV_MODE_DEFAULT; + + spdc_set_update_coord(fb_data); + spdc_set_update_dimensions(fb_data); + + spdc_set_update_temper(fb_data); + + spdc_set_nextbuf_addr(fb_data); + spdc_set_curbuf_addr(fb_data); + spdc_set_prebuf_addr(fb_data); + spdc_set_cntbuf_addr(fb_data); + + /* load waveform*/ + spdc_set_lutbuf_addr(fb_data); + ret = wait_for_completion_timeout(&fb_data->lut_down, + msecs_to_jiffies(4000)); + if (!ret) { + dev_err(fb_data->dev, + "Timed out for lut!\n"); + return ret; + } + + /* modify lut to fix DC com fading issue, + * and fix mode4 close over push + */ + __raw_writel(0x00003906, fb_data->hwp + SPDC_LUT_PARA_UPDATE); + __raw_writel(0x00003300, fb_data->hwp + SPDC_LUT_PARA_UPDATE); + + /* init SPDC setting, the setting get from platform data */ + fb_data->panel_set.yoe_pol = init_set->yoe_pol; + fb_data->panel_set.dual_gate = init_set->dual_gate; + fb_data->panel_set.ud = init_set->ud; + fb_data->panel_set.rl = init_set->rl; + fb_data->panel_set.data_filter_n = init_set->data_filter_n; + fb_data->panel_set.rgbw_mode_enable = init_set->rgbw_mode_enable; + fb_data->panel_set.hburst_len_en = init_set->hburst_len_en; + ret = update_panel_init_set(fb_data); + + return ret; +} + +static u32 mxc_spdc_partial_refresh_low(mxc_spdc_t *fb_data, void *buffer) +{ + u8 *fresh_addr; + void *pattern = buffer; + struct partial_refresh_param *fresh_param = &fb_data->fresh_param; + u32 fresh_size; + int ret = 0; + + fb_data->updates_active = true; + + fresh_addr = (u8 *)(fb_data->virt_start) + + (fresh_param->update_region.top * fresh_param->stride) + + ((fresh_param->update_region.left * fb_data->default_bpp) >> 3); + fresh_size = (u32)((fresh_param->update_region.width * + fresh_param->update_region.height * fb_data->default_bpp) >> 3); + + if (buffer != NULL) { + while (fresh_size > 0) { + memcpy((void *)fresh_addr, pattern, + fresh_param->update_region.width); + fresh_size -= fresh_param->update_region.width; + fresh_addr += fresh_param->update_region.top * + fresh_param->stride; + pattern += fresh_param->update_region.width; + } + } + + /* program SPDC register and trigger to process buffer*/ + fb_data->fresh_param.buf_addr.next_buf_phys_addr = + fb_data->info.fix.smem_start + fb_data->fb_offset; + fb_data->fresh_param.wave_mode = fb_data->wv_modes.mode_init; + spdc_submit_update(fb_data); + + ret = wait_for_completion_timeout(&fb_data->update_finish, + msecs_to_jiffies(3000)); + if (!ret) { + dev_err(fb_data->dev, + "display update timeout!\n"); + return -ETIMEDOUT; + } + + return ret; +} + +static u32 spdc_fb_dev_init(mxc_spdc_t *fb_data) +{ + fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE; + fb_data->fresh_param.wave_mode = SPDC_WAV_MODE_0; + fb_data->operation_mode = SPDC_NO_OPERATION; + fb_data->is_deep_fresh = false; + + /* Init the concurrency update */ + fb_data->fresh_param.concur = 0; + fb_data->upd_preprocess_num = 0; + fb_data->submit_upd_sta = 0; + + fb_data->fresh_param.temper = SPDC_DEFAULT_TEMP; + + return 0; +} + +/** + * mxc_spdc_device_is_busy - check spdc device busy status. + * Returns 0 if spdc device is idle. + */ +static int mxc_spdc_device_is_busy(mxc_spdc_t *fb_data) +{ + u32 status; + u32 orig_jiffies = jiffies; + + while (1) { + status = __raw_readl(fb_data->hwp + SPDC_STATUS); + if ((status & SPDC_PANEL_STAUTS_BUSY) && + ((status & 0xF0) == SPDC_TCON_STATUS_IDLE)) + break; + + if (signal_pending(current)) { + dev_dbg(fb_data->dev, "SPDC Interrupted\n"); + return -EINTR; + } + + if (time_after(jiffies, orig_jiffies + + msecs_to_jiffies(3000))) { + dev_dbg(fb_data->dev, "SPDC is busy\n"); + return -ETIMEDOUT; + } + + schedule(); + } + + return 0; +} + +static bool is_free_list_full(mxc_spdc_t *fb_data) +{ + int count = 0; + struct update_data_list *plist; + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + count++; + + /* Check to see if all buffers are in this list */ + if (count == fb_data->max_num_updates) + return true; + else + return false; +} + +static void spdc_draw_mode0(mxc_spdc_t *fb_data) +{ + struct mxcfb_update_data update; + struct mxcfb_update_marker_data upd_marker_data; + struct fb_var_screeninfo *screeninfo = &fb_data->spdc_fb_var; + u32 xres, yres; + int ret; + + fb_data->fresh_param.buf_addr.next_buf_phys_addr = + fb_data->phys_start; + + fb_data->hw_ready = true; + fb_data->hw_initializing = false; + + /* Use unrotated (native) width/height */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres = screeninfo->yres; + yres = screeninfo->xres; + } else { + xres = screeninfo->xres; + yres = screeninfo->yres; + } + + update.update_region.left = 0; + update.update_region.width = xres; + update.update_region.top = 0; + update.update_region.height = yres; + update.update_mode = UPDATE_MODE_FULL; + update.waveform_mode = fb_data->wv_modes.mode_init; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = SPDC_DEFAULT_TEMP; + update.flags = 0; + + upd_marker_data.update_marker = update.update_marker; + + mxc_spdc_fb_send_update(&update, &fb_data->info); + + /* Block on initial update */ + ret = mxc_spdc_fb_wait_update_complete(&upd_marker_data, + &fb_data->info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for update complete failed, Err:%d", ret); +} + +static void +spdc_fb_fw_handler(const struct firmware *fw, void *context) +{ + mxc_spdc_t *fb_data = (mxc_spdc_t *)context; + struct clk *spdc_parent; + unsigned long rounded_parent_rate, spdc_pix_rate, + rounded_pix_clk, target_pix_clk; + u8 *wv_file; + int ret; + + if (fw == NULL) { + /* If default FW file load failed, we give up */ + if (fb_data->fw_default_load) + return; + + /* Try to load default waveform */ + fb_data->fw_default_load = true; + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fb_data->fw_str, fb_data->dev, GFP_KERNEL, + fb_data, spdc_fb_fw_handler); + if (ret) { + dev_err(fb_data->dev, + "Failed to load waveform image with err %d\n", ret); + return; + } + } + + wv_file = (u8 *)fw->data; + memcpy(fb_data->virt_lut_buf, wv_file, fw->size); + + check_waveform((u32 *)wv_file, (u32 *)fb_data->virt_lut_buf, + fw->size / 4); + release_firmware(fw); + + /* Enable clocks to access SPDC regs */ + clk_enable(fb_data->spdc_clk_axi); + + target_pix_clk = fb_data->cur_mode->vmode->pixclock; + /* Enable pix clk for SPDC */ + clk_enable(fb_data->spdc_clk_pix); + rounded_pix_clk = clk_round_rate(fb_data->spdc_clk_pix, target_pix_clk); + + if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || + (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) { + /* Can't get close enough without changing parent clk */ + spdc_parent = clk_get_parent(fb_data->spdc_clk_pix); + rounded_parent_rate = + clk_round_rate(spdc_parent, target_pix_clk); + + spdc_pix_rate = target_pix_clk; + while (spdc_pix_rate < rounded_parent_rate) + spdc_pix_rate *= 2; + clk_set_rate(spdc_parent, spdc_pix_rate); + + rounded_pix_clk = + clk_round_rate(fb_data->spdc_clk_pix, target_pix_clk); + if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || + (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) + /* Still can't get a good clock, provide warning */ + dev_err(fb_data->dev, + "Unable to get an accurate SPDC pix clk" + "desired = %lu, actual = %lu\n", target_pix_clk, + rounded_pix_clk); + } + + clk_set_rate(fb_data->spdc_clk_pix, rounded_pix_clk); + /* Disable clocks */ + clk_disable(fb_data->spdc_clk_axi); + clk_disable(fb_data->spdc_clk_pix); + + if (!spdc_init_sequence(fb_data)) + return; + + /* display log on picture */ + spdc_draw_mode0(fb_data); +} + + +static int spdc_fb_init_hw(struct fb_info *info) +{ + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + int ret; + + fb_data->fw_default_load = false; + /* + * Create fw search string based on ID string in selected videomode. + * Format is "imx/spdc_[wave_timing].fw: spdc_pvi.fw, spdc_auo.fw" + */ + if (fb_data->cur_mode) { + memset(fb_data->fw_str, 0, sizeof(fb_data->fw_str)); + strcat(fb_data->fw_str, "imx/spdc_"); + strcat(fb_data->fw_str, fb_data->cur_mode->wave_timing); + strcat(fb_data->fw_str, ".fw"); + } else + strcat(fb_data->fw_str, "imx/spdc_pvi.fw"); + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fb_data->fw_str, fb_data->dev, GFP_KERNEL, + fb_data, spdc_fb_fw_handler); + if (ret) { + dev_err(fb_data->dev, + "Failed to load waveform image with err %d\n", ret); + return ret; + } + + return ret; +} + +static int mxc_spdc_partial_refresh(mxc_spdc_t *fb_data, void *buffer) +{ + int ret = 0; + struct partial_refresh_param *fresh_param = &fb_data->fresh_param; + + if (!fb_data->panel_set.power_ready) + spdc_powerup(fb_data); + + if (!fresh_param->blocking) { + if (mxc_spdc_device_is_busy(fb_data)) { + dev_err(fb_data->dev, "spdc busy!\n"); + return (u32) -1; + } + } else { + while (mxc_spdc_device_is_busy(fb_data)) { + dev_err(fb_data->dev, "Waiting for spdc idle..\n"); + msleep(500); + } + } + + ret = mxc_spdc_partial_refresh_low(fb_data, buffer); + + return ret; +} + +static int mxc_operaton_update(mxc_spdc_t *fb_data) +{ + int ret = 0; + struct partial_refresh_param *fresh_param = &fb_data->fresh_param; + u32 operation_mode = fb_data->operation_mode; + + if (!fb_data->panel_set.power_ready) + spdc_powerup(fb_data); + + if (operation_mode != SPDC_SW_TCON_RESET) { + if (!fresh_param->blocking) { + if (mxc_spdc_device_is_busy(fb_data)) { + dev_err(fb_data->dev, "spdc busy\n"); + return (u32) -1; + } + } else { + while (mxc_spdc_device_is_busy(fb_data)) { + dev_err(fb_data->dev, "Waiting spdc idle...\n"); + msleep(500); + } + } + } else + operation_mode = SPDC_SW_TCON_RESET_SET; + + /* don't add to queue list */ + mutex_lock(&fb_data->queue_mutex); + __raw_writel(operation_mode, fb_data->hwp + SPDC_OPERATE); + mutex_unlock(&fb_data->queue_mutex); + + if (operation_mode == SPDC_SW_TCON_RESET_SET) { + dev_dbg(fb_data->dev, "reinit hw\n"); + mdelay(500); + + fb_data->hw_ready = false; + fb_data->operation_mode = SPDC_NO_OPERATION; + ret = spdc_fb_init_hw(&fb_data->info); + if (ret && !fb_data->hw_ready) + dev_err(fb_data->dev, "Failed to init HW!\n"); + } + + return ret; +} + +static int mxc_spdc_refresh_display(mxc_spdc_t *fb_data) +{ + struct partial_refresh_param *fresh_param = &fb_data->fresh_param; + u32 operation_mode = fb_data->operation_mode; + int ret = 0; + + fresh_param->update_region.left = 0; + fresh_param->update_region.top = 0; + fresh_param->update_region.width = fb_data->spdc_fb_var.xres; + fresh_param->update_region.height = fb_data->spdc_fb_var.yres; + fresh_param->stride = (fb_data->spdc_fb_var.xres * + fb_data->spdc_fb_var.bits_per_pixel) >> 3; + + if (operation_mode && operation_mode < SPDC_FULL_REFRESH) + ret = mxc_operaton_update(fb_data); + else + ret = mxc_spdc_partial_refresh(fb_data, NULL); + + return ret; +} + +static void mxc_spdc_find_match_mode(mxc_spdc_t *fb_data) +{ + struct imx_spdc_fb_mode *spdc_mode = + &fb_data->pdata->spdc_mode[0]; + const struct mxc_spdc_resolution_map_para *spdc_res_map; + u32 i = 0; + u32 j = 0; + u32 default_mode = 0xFF; + + if (fb_data->panel_set.rgbw_mode_enable) + spdc_res_map = &spdc_rgbw_res_map[0]; + else + spdc_res_map = &spdc_gray_res_map[0]; + + while (spdc_mode != NULL) { + while (spdc_res_map[j].resolution != 0xFFFF) { + if (spdc_mode->vmode->xres == spdc_res_map[j].res_x + && spdc_mode->vmode->yres == spdc_res_map[j].res_y) { + fb_data->panel_set.resolution = + spdc_res_map[j].resolution; + default_mode = i; + break; + } + j++; + } + + if (default_mode != 0xFF) + break; + j = 0; + i++; + spdc_mode = &fb_data->pdata->spdc_mode[i]; + } + + fb_data->cur_mode = spdc_mode; +} + +static int mxc_spdc_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + + if (offset + size > info->fix.smem_len) + return -EINVAL; + + pos = (unsigned long)info->fix.smem_start + offset; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_flags |= VM_RESERVED | VM_IO; + + while (size > 0) { + page = pos; + if (io_remap_pfn_range(vma, start, page >> PAGE_SHIFT, + PAGE_SIZE, vma->vm_page_prot)) + return -EAGAIN; + + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxc_spdc_fb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return 1; + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_PSEUDOCOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + break; + case FB_VISUAL_DIRECTCOLOR: + red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */ + green = CNVT_TOHW(green, 8); + blue = CNVT_TOHW(blue, 8); + /* hey, there is bug in transp handling... */ + transp = CNVT_TOHW(transp, 8); + break; + } +#undef CNVT_TOHW + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + if (regno >= 16) + return 1; + + ((u32 *) (info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + } + + return 0; +} + +void mxc_spdc_fb_flush_updates(mxc_spdc_t *fb_data) +{ + int ret; + + /* Grab queue lock to prevent any new updates from being submitted */ + mutex_lock(&fb_data->queue_mutex); + + /* + * 3 places to check for updates that are active or pending: + * 1) Updates in the pending list + * 2) Update buffers in use (e.g., PxP processing) + * 3) Active updates to panel - We can key off of SPDC + * power state to know if we have active updates. + */ + if (!list_empty(&fb_data->upd_pending_list) || + !is_free_list_full(fb_data) || + (fb_data->updates_active == true)) { + /* Initialize event signalling updates are done */ + init_completion(&fb_data->updates_done); + fb_data->waiting_for_idle = true; + + mutex_unlock(&fb_data->queue_mutex); + /* Wait for any currently active updates to complete */ + ret = wait_for_completion_timeout(&fb_data->updates_done, + msecs_to_jiffies(8000)); + if (!ret) + dev_err(fb_data->dev, + "Flush updates timeout! ret = 0x%x\n", ret); + + mutex_lock(&fb_data->queue_mutex); + fb_data->waiting_for_idle = false; + } + + mutex_unlock(&fb_data->queue_mutex); +} + +static int mxc_spdc_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + int count, index, r; + u16 *red, *green, *blue, *transp; + u16 trans = 0xffff; + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + int i; + + dev_dbg(fb_data->dev, "setcmap\n"); + + if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) { + /* Only support an 8-bit, 256 entry lookup */ + if (cmap->len != 256) + return 1; + + mxc_spdc_fb_flush_updates(fb_data); + + mutex_lock(&fb_data->pxp_mutex); + /* + * Store colormap in pxp_conf structure for later transmit + * to PxP during update process to convert gray pixels. + * + * Since red=blue=green for pseudocolor visuals, we can + * just use red values. + */ + for (i = 0; i < 256; i++) + fb_data->pxp_conf.proc_data.lut_map[i] = + cmap->red[i] & 0xFF; + + fb_data->pxp_conf.proc_data.lut_map_updated = true; + + mutex_unlock(&fb_data->pxp_mutex); + } else { + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + index = cmap->start; + + for (count = 0; count < cmap->len; count++) { + if (transp) + trans = *transp++; + r = mxc_spdc_fb_setcolreg(index++, *red++, + *green++, *blue++, trans, info); + if (r != 0) + return r; + } + } + + return 0; +} + +static void adjust_coordinates(u32 xres, u32 yres, u32 rotation, + struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region) +{ + u32 temp; + + /* If adj_update_region == NULL, pass result back in update_region */ + /* If adj_update_region == valid, use it to pass back result */ + if (adj_update_region) + switch (rotation) { + case FB_ROTATE_UR: + adj_update_region->top = update_region->top; + adj_update_region->left = update_region->left; + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + break; + case FB_ROTATE_CW: + adj_update_region->top = update_region->left; + adj_update_region->left = yres - + (update_region->top + update_region->height); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + case FB_ROTATE_UD: + adj_update_region->width = update_region->width; + adj_update_region->height = update_region->height; + adj_update_region->top = yres - + (update_region->top + update_region->height); + adj_update_region->left = xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + adj_update_region->left = update_region->top; + adj_update_region->top = xres - + (update_region->left + update_region->width); + adj_update_region->width = update_region->height; + adj_update_region->height = update_region->width; + break; + } + else + switch (rotation) { + case FB_ROTATE_UR: + /* No adjustment needed */ + break; + case FB_ROTATE_CW: + temp = update_region->top; + update_region->top = update_region->left; + update_region->left = yres - + (temp + update_region->height); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + case FB_ROTATE_UD: + update_region->top = yres - + (update_region->top + update_region->height); + update_region->left = xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + temp = update_region->left; + update_region->left = update_region->top; + update_region->top = xres - + (temp + update_region->width); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + } +} + +static int mxc_spdc_fb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + if (var->grayscale) + fix->visual = FB_VISUAL_STATIC_PSEUDOCOLOR; + else + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static int mxc_spdc_fb_set_par(struct fb_info *info) +{ + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + struct imx_spdc_fb_mode *spdc_modes = fb_data->pdata->spdc_mode; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &pxp_conf->proc_data; + int i, ret; + __u32 xoffset_old, yoffset_old; + + /* + * Can't change the FB parameters until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_spdc_fb_flush_updates(fb_data); + + mutex_lock(&fb_data->queue_mutex); + /* + * Set all screeninfo except for xoffset/yoffset + * Subsequent call to pan_display will handle those. + */ + xoffset_old = fb_data->spdc_fb_var.xoffset; + yoffset_old = fb_data->spdc_fb_var.yoffset; + fb_data->spdc_fb_var = *screeninfo; + fb_data->spdc_fb_var.xoffset = xoffset_old; + fb_data->spdc_fb_var.yoffset = yoffset_old; + mutex_unlock(&fb_data->queue_mutex); + + mutex_lock(&fb_data->pxp_mutex); + + /* + * Update PxP config data (used to process FB regions for updates) + * based on FB info and processing tasks required + */ + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = proc_data->srect.width = screeninfo->xres; + proc_data->drect.height = proc_data->srect.height = screeninfo->yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = screeninfo->rotate; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + + /* + * configure S0 channel parameters + * Parameters should match FB format/width/height + */ + if (screeninfo->grayscale) + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_GY04; + else { + switch (screeninfo->bits_per_pixel) { + case 16: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + case 24: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB24; + break; + case 32: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB32; + break; + default: + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + break; + } + } + pxp_conf->s0_param.width = screeninfo->xres_virtual; + pxp_conf->s0_param.height = screeninfo->yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = screeninfo->xres; + pxp_conf->out_param.height = screeninfo->yres; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GY04; + + mutex_unlock(&fb_data->pxp_mutex); + + /* active new config, If HW not yet initialized, + * check to see if we are being sent + * an initialization request. + */ + if (!fb_data->hw_ready) { + struct fb_videomode mode; + bool found_match = false; + u32 xres_temp; + + fb_var_to_videomode(&mode, screeninfo); + + /* When comparing requested fb mode, + * we need to use unrotated dimensions + */ + if ((screeninfo->rotate == FB_ROTATE_CW) || + (screeninfo->rotate == FB_ROTATE_CCW)) { + xres_temp = mode.xres; + mode.xres = mode.yres; + mode.yres = xres_temp; + } + + /* Match videomode against spdc modes */ + for (i = 0; i < fb_data->pdata->num_modes; i++) { + if (!fb_mode_is_equal(spdc_modes[i].vmode, &mode)) + continue; + fb_data->cur_mode = &spdc_modes[i]; + found_match = true; + break; + } + + if (!found_match) { + dev_err(fb_data->dev, + "Failed to match requested video mode\n"); + return EINVAL; + } + + /* Initialize SPDC settings and init panel */ + ret = + spdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(fb_data->dev, + "Failed to load panel waveform data\n"); + return ret; + } + } + + mxc_spdc_fb_set_fix(info); + + return 0; +} + +static int +mxc_spdc_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8) && + (var->bits_per_pixel != 4)) + var->bits_per_pixel = SPDC_DEFAULT_BPP; + + switch (var->bits_per_pixel) { + case 4: + var->red.offset = 0; + var->red.length = var->bits_per_pixel; + var->green = var->red; + var->blue = var->red; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 8: + if (var->grayscale != 0) { + var->red.length = 8; + var->red.offset = 0; + var->red.msb_right = 0; + + var->green = var->red; + var->blue = var->red; + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } else { + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + } + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + switch (var->rotate) { + case FB_ROTATE_UR: + case FB_ROTATE_UD: + var->xres = fb_data->native_width; + var->yres = fb_data->native_height; + break; + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + var->xres = fb_data->native_height; + var->yres = fb_data->native_width; + break; + default: + /* Invalid rotation value */ + var->rotate = 0; + dev_dbg(fb_data->dev, "Invalid rotation request\n"); + return -EINVAL; + } + + var->xres_virtual = ALIGN(var->xres, 32); + var->yres_virtual = ALIGN(var->yres, 128) * fb_data->num_screens; + + var->height = -1; + var->width = -1; + + return 0; +} + +void mxc_spdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes, + struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + + mutex_lock(&fb_data->queue_mutex); + + memcpy(&fb_data->wv_modes, modes, sizeof(struct mxcfb_waveform_modes)); + + mutex_unlock(&fb_data->queue_mutex); +} +EXPORT_SYMBOL(mxc_spdc_fb_set_waveform_modes); + +/* To stick with non-fractional degrees for the sake + * of API consistency with EPDC. + */ +int mxc_spdc_fb_set_temperature(int temperature, struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + s8 temper = (s8)(temperature & 0xFF) << 1; + + mutex_lock(&fb_data->queue_mutex); + + if (temper > -110 && temper < 200) + __raw_writel(temper, fb_data->hwp + SPDC_TEMP_INFO); + else + __raw_writel(SPDC_DEFAULT_TEMP, fb_data->hwp + SPDC_TEMP_INFO); + + mutex_unlock(&fb_data->queue_mutex); + + return 0; +} +EXPORT_SYMBOL(mxc_spdc_fb_set_temperature); + + +int mxc_spdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + + dev_dbg(fb_data->dev, "Setting auto update mode to %d\n", auto_mode); + + if ((auto_mode == AUTO_UPDATE_MODE_AUTOMATIC_MODE) + || (auto_mode == AUTO_UPDATE_MODE_REGION_MODE)) + fb_data->auto_mode = auto_mode; + else { + dev_err(fb_data->dev, "Invalid auto update mode parameter.\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(mxc_spdc_fb_set_auto_update); + + +int mxc_spdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + + dev_dbg(fb_data->dev, "Setting optimization level to %d\n", upd_scheme); + + /* + * Can't change the scheme until current updates have completed. + * This function returns when all active updates are done. + */ + mxc_spdc_fb_flush_updates(fb_data); + + if ((upd_scheme == UPDATE_SCHEME_SNAPSHOT) + || (upd_scheme == UPDATE_SCHEME_QUEUE) + || (upd_scheme == UPDATE_SCHEME_QUEUE_AND_MERGE)) + fb_data->upd_scheme = upd_scheme; + else { + dev_err(fb_data->dev, "Invalid update scheme specified.\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(mxc_spdc_fb_set_upd_scheme); + +/* Callback function triggered after PxP receives an EOF interrupt */ +static void pxp_dma_done(void *arg) +{ + struct pxp_tx_desc *tx_desc = to_tx_desc(arg); + struct dma_chan *chan = tx_desc->txd.chan; + struct pxp_channel *pxp_chan = to_pxp_channel(chan); + mxc_spdc_t *fb_data = pxp_chan->client; + + /* This call will signal wait_for_completion_timeout() + * in send_buffer_to_pxp + */ + complete(&fb_data->pxp_tx_cmpl); +} + +static bool chan_filter(struct dma_chan *chan, void *arg) +{ + if (imx_dma_is_pxp(chan)) + return true; + else + return false; +} + +/* Function to request PXP DMA channel */ +static int pxp_chan_init(mxc_spdc_t *fb_data) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + + /* + * Request a free channel + */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + chan = dma_request_channel(mask, chan_filter, NULL); + if (!chan) { + dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n"); + return -EBUSY; + } + + fb_data->pxp_chan = to_pxp_channel(chan); + fb_data->pxp_chan->client = fb_data; + + init_completion(&fb_data->pxp_tx_cmpl); + + return 0; +} + +/* + * Function to call PxP DMA driver and send our latest FB update region + * through the PxP and out to an intermediate buffer. + * Note: This is a blocking call, so upon return the PxP tx should be complete. + */ +static int pxp_process_update(mxc_spdc_t *fb_data, + u32 src_width, u32 src_height, + struct mxcfb_rect *update_region) +{ + dma_cookie_t cookie; + struct scatterlist *sg = fb_data->sg; + struct dma_chan *dma_chan; + struct pxp_tx_desc *desc; + struct dma_async_tx_descriptor *txd; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; + int i, ret; + int length; + + /* First, check to see that we have acquired a PxP Channel object */ + if (fb_data->pxp_chan == NULL) { + /* + * PxP Channel has not yet been created and initialized, + * so let's go ahead and try + */ + ret = pxp_chan_init(fb_data); + if (ret) { + /* + * PxP channel init failed, and we can't use the + * PxP until the PxP DMA driver has loaded, so we abort + */ + dev_err(fb_data->dev, "PxP chan init failed\n"); + return -ENODEV; + } + } + + /* + * Init completion, so that we + * can be properly informed of the completion + * of the PxP task when it is done. + */ + init_completion(&fb_data->pxp_tx_cmpl); + + dma_chan = &fb_data->pxp_chan->dma_chan; + + txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2, + DMA_TO_DEVICE, + DMA_PREP_INTERRUPT); + if (!txd) { + dev_err(fb_data->info.device, + "Error preparing a DMA transaction descriptor.\n"); + return -EIO; + } + + txd->callback_param = txd; + txd->callback = pxp_dma_done; + + /* + * Configure PxP for processing of new update region + * The rest of our config params were set up in + * probe() and should not need to be changed. + */ + pxp_conf->s0_param.width = src_width; + pxp_conf->s0_param.height = src_height; + proc_data->srect.top = update_region->top; + proc_data->srect.left = update_region->left; + proc_data->srect.width = update_region->width; + proc_data->srect.height = update_region->height; + + /* + * Because only YUV/YCbCr image can be scaled, configure + * drect equivalent to srect, as such do not perform scaling. + */ + proc_data->drect.top = 0; + proc_data->drect.left = 0; + proc_data->drect.width = proc_data->srect.width; + proc_data->drect.height = proc_data->srect.height; + + /* PXP expects rotation in terms of degrees */ + proc_data->rotate = fb_data->spdc_fb_var.rotate * 90; + if (proc_data->rotate > 270) + proc_data->rotate = 0; + + pxp_conf->out_param.width = update_region->width; + pxp_conf->out_param.height = update_region->height; + + if ((proc_data->rotate == 90) || (proc_data->rotate == 270)) + pxp_conf->out_param.stride = update_region->height; + else + pxp_conf->out_param.stride = update_region->width; + + desc = to_tx_desc(txd); + length = desc->len; + for (i = 0; i < length; i++) { + if (i == 0) {/* S0 */ + memcpy(&desc->proc_data, proc_data, + sizeof(struct pxp_proc_data)); + pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]); + memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param, + sizeof(struct pxp_layer_param)); + } else if (i == 1) { + pxp_conf->out_param.paddr = sg_dma_address(&sg[1]); + memcpy(&desc->layer_param.out_param, + &pxp_conf->out_param, + sizeof(struct pxp_layer_param)); + } + /* TODO: OverLay */ + + desc = desc->next; + } + + /* Submitting our TX starts the PxP processing task */ + cookie = txd->tx_submit(txd); + if (cookie < 0) { + dev_err(fb_data->info.device, "Error sending FB through PxP\n"); + return -EIO; + } + + fb_data->txd = txd; + + /* trigger ePxP */ + dma_async_issue_pending(dma_chan); + + return 0; +} + +static int pxp_complete_update(mxc_spdc_t *fb_data, u32 *hist_stat) +{ + int ret; + /* + * Wait for completion event, which will be set + * through our TX callback function. + */ + ret = wait_for_completion_timeout(&fb_data->pxp_tx_cmpl, HZ / 10); + if (ret <= 0) { + dev_info(fb_data->info.device, + "PxP operation failed due to %s\n", + ret < 0 ? "user interrupt" : "timeout"); + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + return ret ? : -ETIMEDOUT; + } + + if ((fb_data->pxp_conf.proc_data.lut_transform & EPDC_FLAG_USE_CMAP) && + fb_data->pxp_conf.proc_data.lut_map_updated) + fb_data->pxp_conf.proc_data.lut_map_updated = false; + + *hist_stat = to_tx_desc(fb_data->txd)->hist_status; + dma_release_channel(&fb_data->pxp_chan->dma_chan); + fb_data->pxp_chan = NULL; + + dev_dbg(fb_data->dev, "TX completed\n"); + + return 0; +} + +static void copy_to_next_buffer(mxc_spdc_t *fb_data, + struct update_data_list *upd_data_list) +{ + struct mxcfb_update_data *upd_data = + &upd_data_list->update_desc->upd_data; + unsigned char *temp_buf_ptr = fb_data->virt_addr_copybuf; + unsigned char *dst_ptr = upd_data_list->virt_addr; + struct mxcfb_rect adj_update_region; + int dst_stride, left_offs, line_width; + int i; + + switch (fb_data->spdc_fb_var.rotate) { + case FB_ROTATE_UR: + adj_update_region.top = upd_data->update_region.top; + adj_update_region.left = upd_data->update_region.left; + adj_update_region.width = upd_data->update_region.width; + adj_update_region.height = upd_data->update_region.height; + dst_stride = fb_data->spdc_fb_var.xres_virtual / 2; + break; + case FB_ROTATE_CW: + adj_update_region.top = upd_data->update_region.left; + adj_update_region.left = fb_data->spdc_fb_var.yres - + (upd_data->update_region.top + + upd_data->update_region.height); + adj_update_region.width = upd_data->update_region.height; + adj_update_region.height = upd_data->update_region.width; + dst_stride = fb_data->spdc_fb_var.yres / 2; + break; + case FB_ROTATE_UD: + adj_update_region.width = upd_data->update_region.width; + adj_update_region.height = upd_data->update_region.height; + adj_update_region.top = fb_data->spdc_fb_var.yres - + (upd_data->update_region.top + upd_data->update_region.height); + adj_update_region.left = fb_data->spdc_fb_var.xres - + (upd_data->update_region.left + + upd_data->update_region.width); + dst_stride = fb_data->spdc_fb_var.xres_virtual / 2; + break; + case FB_ROTATE_CCW: + adj_update_region.left = upd_data->update_region.top; + adj_update_region.top = fb_data->spdc_fb_var.xres - + (upd_data->update_region.left + + upd_data->update_region.width); + adj_update_region.width = upd_data->update_region.height; + adj_update_region.height = upd_data->update_region.width; + dst_stride = fb_data->spdc_fb_var.yres / 2; + break; + } + + /* pxp output Y4 data. + * Copy the raw data to related region in next buffer. + */ + left_offs = adj_update_region.left / 2; + line_width = adj_update_region.width / 2; + + dst_ptr += (adj_update_region.top * dst_stride + left_offs); + for (i = 0; i < adj_update_region.height; i++) { + /* Copy the full line */ + memcpy(dst_ptr, temp_buf_ptr, line_width); + + dst_ptr += dst_stride; + temp_buf_ptr += line_width; + } +} + +static int spdc_process_update(struct update_data_list *upd_data_list, + mxc_spdc_t *fb_data) +{ + /* Region of src buffer for update */ + struct mxcfb_rect *src_upd_region; + struct mxcfb_rect pxp_upd_region; + struct update_desc_list *upd_desc_list = upd_data_list->update_desc; + u32 src_width, src_height; + u32 offset_from_4, bytes_per_pixel; + u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; + u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; + bool input_unaligned = false; + u32 hist_stat = 0; + bool use_temp_buf = false; + int ret; + + /* + * Are we using FB or an alternate (overlay) + * buffer for source of update? + */ + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_width = upd_desc_list->upd_data.alt_buffer_data.width; + src_height = upd_desc_list->upd_data.alt_buffer_data.height; + src_upd_region = + &upd_desc_list->upd_data.alt_buffer_data.alt_update_region; + } else { + src_width = fb_data->spdc_fb_var.xres_virtual; + src_height = fb_data->spdc_fb_var.yres; + src_upd_region = &upd_desc_list->upd_data.update_region; + } + + if (!(src_upd_region->width == fb_data->spdc_fb_var.xres_virtual && + fb_data->spdc_fb_var.rotate == FB_ROTATE_UR)) + use_temp_buf = true; + + bytes_per_pixel = fb_data->spdc_fb_var.bits_per_pixel / 8; + + /* Grab pxp_mutex here so that we protect access + * to copybuf in addition to the PxP structures */ + mutex_lock(&fb_data->pxp_mutex); + + offset_from_4 = src_upd_region->left & 0x3; + input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? + true : false; + + if (input_unaligned) { + /* Leave a gap between PxP input addr + * and update region pixels + */ + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel & 0xFFFFFFFC; + /* Update region left changes to reflect + * relative position to input ptr + */ + pxp_upd_region.left = (offset_from_4 * bytes_per_pixel % 4) + / bytes_per_pixel; + } else { + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel; + pxp_upd_region.left = 0; + } + pxp_upd_region.top = 0; + + /* Update region dimensions to meet 8x8 pixel requirement */ + if (fb_data->spdc_fb_var.rotate == 0) { + pxp_upd_region.width = ALIGN(src_upd_region->width, 8); + pxp_upd_region.height = ALIGN(src_upd_region->height, 8); + } else { + pxp_upd_region.width = + ALIGN(src_upd_region->width + pxp_upd_region.left, 8); + pxp_upd_region.height = ALIGN(src_upd_region->height, 8); + } + + switch (fb_data->spdc_fb_var.rotate) { + case FB_ROTATE_UR: + default: + post_rotation_xcoord = pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.top; + width_pxp_blocks = pxp_upd_region.width; + break; + case FB_ROTATE_CW: + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = width_pxp_blocks - + src_upd_region->height; + post_rotation_ycoord = pxp_upd_region.left; + break; + case FB_ROTATE_UD: + width_pxp_blocks = pxp_upd_region.width; + post_rotation_xcoord = width_pxp_blocks - + src_upd_region->width - pxp_upd_region.left; + post_rotation_ycoord = pxp_upd_region.height - + src_upd_region->height - pxp_upd_region.top; + break; + case FB_ROTATE_CCW: + width_pxp_blocks = pxp_upd_region.height; + post_rotation_xcoord = pxp_upd_region.top; + post_rotation_ycoord = pxp_upd_region.width - + src_upd_region->width - pxp_upd_region.left; + break; + } + + /* Update region start coord to force PxP to + * process full 8x8 regions + */ + pxp_upd_region.top &= ~0x7; + pxp_upd_region.left &= ~0x7; + + pxp_output_shift = ALIGN(post_rotation_xcoord, 8) + - post_rotation_xcoord; + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + pxp_output_shift; + upd_desc_list->spdc_offs = ALIGN(pxp_output_offs, 8); + + /* Source address either comes from alternate buffer + provided in update data, or from the framebuffer. */ + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) + sg_dma_address(&fb_data->sg[0]) = + upd_desc_list->upd_data.alt_buffer_data.phys_addr + + pxp_input_offs; + else { + sg_dma_address(&fb_data->sg[0]) = + fb_data->info.fix.smem_start + fb_data->fb_offset + + pxp_input_offs; + sg_set_page(&fb_data->sg[0], + virt_to_page(fb_data->info.screen_base), + fb_data->info.fix.smem_len, + offset_in_page(fb_data->info.screen_base)); + } + + /* Update sg[1] to point to output of PxP proc task */ + if (!use_temp_buf) { + sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr; + sg_set_page(&fb_data->sg[1], + virt_to_page(upd_data_list->virt_addr), + fb_data->max_pix_size, + offset_in_page(upd_data_list->virt_addr)); + } else { + sg_dma_address(&fb_data->sg[1]) = fb_data->phys_addr_copybuf; + sg_set_page(&fb_data->sg[1], + virt_to_page(fb_data->virt_addr_copybuf), + fb_data->max_pix_size, + offset_in_page(fb_data->virt_addr_copybuf)); + } + + /* + * Set PxP LUT transform type based on update flags. + */ + fb_data->pxp_conf.proc_data.lut_transform = 0; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION) + fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME) + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_BLACK_WHITE; + if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_CMAP) + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_USE_CMAP; + + /* + * Toggle inversion processing if 8-bit + * inverted is the current pixel format. + */ + if (fb_data->spdc_fb_var.grayscale == GRAYSCALE_4BIT_INVERTED) + fb_data->pxp_conf.proc_data.lut_transform ^= PXP_LUT_INVERT; + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_process_update(fb_data, src_width, src_height, + &pxp_upd_region); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* If needed, enable SPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + spdc_powerup(fb_data); + } + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_complete_update(fb_data, &hist_stat); + if (ret) { + dev_err(fb_data->dev, "Unable to complete PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + if (use_temp_buf) + copy_to_next_buffer(fb_data, upd_data_list); + + mutex_unlock(&fb_data->pxp_mutex); + + /* Update waveform mode from PxP histogram results */ + if (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO) { + if (hist_stat & 0x1) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_du; + else if (hist_stat & 0x2) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc4; + else if (hist_stat & 0x4) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc8; + else if (hist_stat & 0x8) + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; + else + upd_desc_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc32; + + dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", + hist_stat, upd_desc_list->upd_data.waveform_mode); + } + + return 0; +} + +static bool spdc_submit_concur(mxc_spdc_t *fb_data, + struct update_desc_list *update_to_concur) +{ + struct mxcfb_update_data *a, *b; + struct mxcfb_rect *arect, *brect; + struct update_data_list *next_upd; + int i = 0; + + a = &update_to_concur->upd_data; + arect = &update_to_concur->upd_data.update_region; + + list_for_each_entry(next_upd, + &fb_data->upd_buf_preprocess_list, list) { + b = &next_upd->update_desc->upd_data; + brect = &next_upd->update_desc->upd_data.update_region; + + /* Updates with different waveform + * must be executed sequentially. + */ + if (a->waveform_mode != b->waveform_mode) + break; + + /* + * Concurrency update must has no overlay + */ + if (!(arect->left > (brect->left + brect->width) || + brect->left > (arect->left + arect->width) || + arect->top > (brect->top + brect->height) || + brect->top > (arect->top + arect->height))) + break; + + i++; + } + + if (i != fb_data->upd_preprocess_num) + return false; + + return true; +} + +static int spdc_submit_merge(struct update_desc_list *upd_desc_list, + struct update_desc_list *update_to_merge) +{ + struct mxcfb_update_data *a, *b; + struct mxcfb_rect *arect, *brect; + struct mxcfb_rect combine; + bool use_flags = false; + + a = &upd_desc_list->upd_data; + b = &update_to_merge->upd_data; + arect = &upd_desc_list->upd_data.update_region; + brect = &update_to_merge->upd_data.update_region; + + /* + * Updates with different flags must be executed sequentially. + * Halt the merge process to ensure this. + */ + if (a->flags != b->flags) { + /* + * Special exception: if update regions are identical, + * we may be able to merge them. + */ + if ((arect->left != brect->left) || + (arect->top != brect->top) || + (arect->width != brect->width) || + (arect->height != brect->height)) + return MERGE_BLOCK; + + use_flags = true; + } + + if (a->waveform_mode != b->waveform_mode) + a->waveform_mode = WAVEFORM_MODE_AUTO; + + if (arect->left > (brect->left + brect->width) || + brect->left > (arect->left + arect->width) || + arect->top > (brect->top + brect->height) || + brect->top > (arect->top + arect->height)) + return MERGE_FAIL; + + combine.left = arect->left < brect->left ? arect->left : brect->left; + combine.top = arect->top < brect->top ? arect->top : brect->top; + combine.width = (arect->left + arect->width) > + (brect->left + brect->width) ? + (arect->left + arect->width - combine.left) : + (brect->left + brect->width - combine.left); + combine.height = (arect->top + arect->height) > + (brect->top + brect->height) ? + (arect->top + arect->height - combine.top) : + (brect->top + brect->height - combine.top); + + *arect = combine; + + /* Use flags of the later update */ + if (use_flags) + a->flags = b->flags; + + /* Merge markers */ + list_splice_tail(&update_to_merge->upd_marker_list, + &upd_desc_list->upd_marker_list); + + return MERGE_OK; + +} + +static void spdc_submit_work_func(struct work_struct *work) +{ + struct update_desc_list *next_desc, *temp_desc; + mxc_spdc_t *fb_data = + container_of(work, mxc_spdc_t, spdc_submit_work); + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect adj_update_region, *upd_region; + struct update_marker_data *current_marker; + bool end_merge = false; + bool is_transform; + u32 update_addr; + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* get a buffer from free list */ + if (list_empty(&fb_data->upd_buf_free_list)) { + mutex_unlock(&fb_data->queue_mutex); + return; + } + + if (fb_data->fresh_param.concur == SPDC_LUT_ACC_MODE) { + list_for_each_entry_safe(next_desc, temp_desc, + &fb_data->upd_pending_list, list) { + + current_marker = + list_entry((&next_desc->upd_marker_list)->next, + struct update_marker_data, upd_list); + + if (current_marker->update_marker) { + fb_data->submit_upd_sta = 0; + break; + } + + /* require free buffer list */ + if (list_empty(&fb_data->upd_buf_free_list)) { + dev_dbg(fb_data->dev, + "buf free list is empty\n"); + break; + } + + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + upd_data_list->update_desc = next_desc; + + if (!is_preprocess_list_full(fb_data)) { + if (fb_data->cur_update == NULL && + !fb_data->upd_preprocess_num) + list_del_init(&next_desc->list); + else if (spdc_submit_concur(fb_data, + next_desc)) { + list_del_init(&next_desc->list); + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_preprocess_list); + fb_data->upd_preprocess_num++; + fb_data->submit_upd_sta = + SPDC_CONCUR_UPD; + } else + break; + } else + break; + + /* submit to pxp process */ + list_del_init(&upd_data_list->list); + goto pxp_process; + } + + upd_data_list = NULL; + if (fb_data->submit_upd_sta == SPDC_CONCUR_UPD) + fb_data->submit_upd_sta |= SPDC_QUEUE_UPD; + else + fb_data->submit_upd_sta = SPDC_QUEUE_UPD; + } + + list_for_each_entry_safe(next_desc, temp_desc, + &fb_data->upd_pending_list, list) { + + if (!upd_data_list) { + + if (list_empty(&fb_data->upd_buf_free_list)) { + dev_dbg(fb_data->dev, + "buf_free_list is empty\n"); + break; + } + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + list_del_init(&upd_data_list->list); + upd_data_list->update_desc = next_desc; + list_del_init(&next_desc->list); + + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + break; + } else { + switch (spdc_submit_merge(upd_data_list->update_desc, + next_desc)) { + case MERGE_OK: + dev_dbg(fb_data->dev, + "Update merged [queue]\n"); + list_del_init(&next_desc->list); + kfree(next_desc); + break; + case MERGE_FAIL: + dev_dbg(fb_data->dev, + "Update not merged [queue]\n"); + break; + case MERGE_BLOCK: + dev_dbg(fb_data->dev, + "Merge blocked [collision]\n"); + end_merge = true; + break; + } + + if (end_merge) + break; + } + } + + /* Is update list empty? */ + if (!upd_data_list) { + mutex_unlock(&fb_data->queue_mutex); + return; + } + +pxp_process: + /* + * If no processing required, skip update processing + * No processing means: + * - FB unrotated + * - FB pixel format = 4-bit grayscale + * - No look-up transformations (inversion, posterization, etc.) + */ + is_transform = upd_data_list->update_desc->upd_data.flags & + (EPDC_FLAG_ENABLE_INVERSION | + EPDC_FLAG_FORCE_MONOCHROME | EPDC_FLAG_USE_CMAP) ? + true : false; + if ((fb_data->spdc_fb_var.rotate == FB_ROTATE_UR) && + (fb_data->spdc_fb_var.grayscale == GRAYSCALE_4BIT) && + !is_transform) { + + /* If needed, enable SPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) + spdc_powerup(fb_data); + + /* + * Set update buffer pointer to the start of + * the update region in the frame buffer. + */ + upd_region = + &upd_data_list->update_desc->upd_data.update_region; + update_addr = fb_data->info.fix.smem_start + + ((upd_region->top * fb_data->info.var.xres_virtual) + + upd_region->left) * fb_data->info.var.bits_per_pixel/8; + } else { + + /* Select from PxP output buffers */ + upd_data_list->phys_addr = + fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; + upd_data_list->virt_addr = + fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; + fb_data->upd_buffer_num++; + if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) + fb_data->upd_buffer_num = 0; + dev_dbg(fb_data->dev, + "pxp out addr:0x%x\n", upd_data_list->phys_addr); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + + /* Perform PXP processing - SPDC power will also be enabled */ + if (spdc_process_update(upd_data_list, fb_data)) { + + dev_dbg(fb_data->dev, "PXP processing error.\n"); + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + list_del_init(&upd_data_list->update_desc->list); + kfree(upd_data_list->update_desc); + upd_data_list->update_desc = NULL; + + /* Add to free buffer list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_free_list); + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* output Y4 format */ + update_addr = upd_data_list->phys_addr + + + (upd_data_list->update_desc->spdc_offs / 2); + } + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->spdc_fb_var.xres, + fb_data->spdc_fb_var.yres, fb_data->spdc_fb_var.rotate, + &upd_data_list->update_desc->upd_data.update_region, + &adj_update_region); + + /* + * Is the working buffer idle? + * If the working buffer is busy, we must wait for the resource + * to become free. + */ + if (fb_data->cur_update != NULL && + fb_data->submit_upd_sta != SPDC_CONCUR_UPD) { + /* Initialize event signalling an update resource is free */ + init_completion(&fb_data->update_res_free); + + fb_data->waiting_for_wb = true; + + /* Leave spinlock while waiting for WB to complete */ + mutex_unlock(&fb_data->queue_mutex); + wait_for_completion(&fb_data->update_res_free); + mutex_lock(&fb_data->queue_mutex); + } + + if (fb_data->submit_upd_sta != SPDC_CONCUR_UPD) + fb_data->cur_update = upd_data_list; + + /* program SPDC register and trigger to process buffer*/ + fb_data->fresh_param.buf_addr.next_buf_phys_addr = update_addr; + + fb_data->fresh_param.update_region.left = adj_update_region.left; + fb_data->fresh_param.update_region.top = adj_update_region.top; + fb_data->fresh_param.update_region.width = adj_update_region.width; + fb_data->fresh_param.update_region.height = adj_update_region.height; + fb_data->fresh_param.temper = upd_data_list->update_desc->upd_data.temp; + fb_data->fresh_param.wave_mode = + upd_data_list->update_desc->upd_data.waveform_mode; + + spdc_submit_update(fb_data); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); +} + +int mxc_spdc_fb_send_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect *screen_upd_region; /* Region on screen to update */ + struct update_desc_list *upd_desc; + struct update_marker_data *marker_data; + int ret; + + /* Has SPDC HW been initialized? */ + if (!fb_data->hw_ready) { + /* Throw message if we are not mid-initialization */ + if (!fb_data->hw_initializing) + dev_err(fb_data->dev, "Display HW not properly" + "initialized. Aborting update.\n"); + return -EPERM; + } + + if ((upd_data->waveform_mode > SPDC_WAV_MODE_5) && + (upd_data->waveform_mode != WAVEFORM_MODE_AUTO)) { + dev_err(fb_data->dev, + "Update waveform mode 0x%x is invalid." + " Aborting update.\n", + upd_data->waveform_mode); + return -EINVAL; + } + if ((upd_data->update_region.left + upd_data->update_region.width > + fb_data->spdc_fb_var.xres + 1) || + (upd_data->update_region.top + upd_data->update_region.height > + fb_data->spdc_fb_var.yres + 1)) { + dev_err(fb_data->dev, + "Update region is outside bounds of framebuffer." + "Aborting update.\n"); + return -EINVAL; + } + if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { + if ((upd_data->update_region.width != + upd_data->alt_buffer_data.alt_update_region.width) || + (upd_data->update_region.height != + upd_data->alt_buffer_data.alt_update_region.height)) { + dev_err(fb_data->dev, + "Alternate update region dimensions must " + "match screen update region dimensions.\n"); + return -EINVAL; + } + /* Validate physical address parameter */ + if ((upd_data->alt_buffer_data.phys_addr < + fb_data->info.fix.smem_start) || + (upd_data->alt_buffer_data.phys_addr > + fb_data->info.fix.smem_start + fb_data->map_size)) { + dev_err(fb_data->dev, + "Invalid physical address for alternate " + "buffer. Aborting update...\n"); + return -EINVAL; + } + } + + mutex_lock(&fb_data->queue_mutex); + + /* + * If we are waiting to go into suspend, or the FB is blanked, + * we do not accept new updates + */ + if (fb_data->waiting_for_idle) { + dev_dbg(fb_data->dev, "SPDC not active." + "Update request abort.\n"); + mutex_unlock(&fb_data->queue_mutex); + return -EPERM; + } + + if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { + int count = 0; + struct update_data_list *plist; + + /* Count buffers in free buffer list */ + list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) + count++; + + /* Use count to determine if we have enough + * free buffers to handle this update request */ + if (count + fb_data->max_num_buffers + <= fb_data->max_num_updates) { + dev_err(fb_data->dev, + "No free intermediate buffers available.\n"); + mutex_unlock(&fb_data->queue_mutex); + return -ENOMEM; + } + + /* Grab first available buffer and delete from the free list */ + upd_data_list = + list_entry(fb_data->upd_buf_free_list.next, + struct update_data_list, list); + + list_del_init(&upd_data_list->list); + } + + /* + * Create new update data structure, fill it with new update + * data and add it to the list of pending updates + */ + upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL); + if (!upd_desc) { + dev_err(fb_data->dev, + "Insufficient system memory for update! Aborting.\n"); + if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { + list_add(&upd_data_list->list, + &fb_data->upd_buf_free_list); + } + mutex_unlock(&fb_data->queue_mutex); + return -EPERM; + } + /* Initialize per-update marker list */ + INIT_LIST_HEAD(&upd_desc->upd_marker_list); + upd_desc->upd_data = *upd_data; + list_add_tail(&upd_desc->list, &fb_data->upd_pending_list); + + /* If marker specified, associate it with a completion */ + if (upd_data->update_marker != 0) { + + /* Allocate new update marker and set it up */ + marker_data = kzalloc(sizeof(struct update_marker_data), + GFP_KERNEL); + if (!marker_data) { + dev_err(fb_data->dev, "No memory for marker!\n"); + mutex_unlock(&fb_data->queue_mutex); + return -ENOMEM; + } + list_add_tail(&marker_data->upd_list, + &upd_desc->upd_marker_list); + marker_data->update_marker = upd_data->update_marker; + init_completion(&marker_data->update_completion); + + /* Add marker to master marker list */ + list_add_tail(&marker_data->full_list, + &fb_data->full_marker_list); + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + mutex_unlock(&fb_data->queue_mutex); + + /* Signal workqueue to handle new update */ + queue_work(fb_data->spdc_submit_workqueue, + &fb_data->spdc_submit_work); + + return 0; + } + + /* Set descriptor for current update, delete from pending list */ + upd_data_list->update_desc = upd_desc; + list_del_init(&upd_desc->list); + + mutex_unlock(&fb_data->queue_mutex); + + /* + * Hold on to original screen update region, which we + * will ultimately use when telling SPDC where to update on panel + */ + screen_upd_region = &upd_desc->upd_data.update_region; + + /* Select from PxP output buffers */ + upd_data_list->phys_addr = + fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; + upd_data_list->virt_addr = + fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; + fb_data->upd_buffer_num++; + if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) + fb_data->upd_buffer_num = 0; + + ret = spdc_process_update(upd_data_list, fb_data); + if (ret) { + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + /* Pass selected waveform mode back to user */ + upd_data->waveform_mode = upd_desc->upd_data.waveform_mode; + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data->spdc_fb_var.xres, + fb_data->spdc_fb_var.yres, fb_data->spdc_fb_var.rotate, + &upd_desc->upd_data.update_region, NULL); + + /* Grab lock for queue manipulation and update submission */ + mutex_lock(&fb_data->queue_mutex); + + /* + * Is the working buffer idle? + * If either the working buffer is busy, or there are no LUTs available, + * then we return and let the ISR handle the update later + */ + if (fb_data->cur_update != NULL) { + + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); + + /* Return and allow the update to be submitted by the ISR. */ + mutex_unlock(&fb_data->queue_mutex); + return 0; + } + + /* Save current update */ + fb_data->cur_update = upd_data_list; + + /* program SPDC register and trigger to process buffer*/ + fb_data->fresh_param.buf_addr.next_buf_phys_addr = + upd_data_list->phys_addr + (upd_data_list->update_desc->spdc_offs / 2); + + fb_data->fresh_param.update_region.left = screen_upd_region->left; + fb_data->fresh_param.update_region.top = screen_upd_region->top; + fb_data->fresh_param.update_region.width = screen_upd_region->width; + fb_data->fresh_param.update_region.height = screen_upd_region->height; + fb_data->fresh_param.temper = upd_desc->upd_data.temp; + fb_data->fresh_param.wave_mode = upd_desc->upd_data.waveform_mode; + spdc_submit_update(fb_data); + + mutex_unlock(&fb_data->queue_mutex); + return 0; +} +EXPORT_SYMBOL(mxc_spdc_fb_send_update); + +/* + * return 0 : spdc update is update + */ +int +mxc_spdc_fb_wait_update_complete(struct mxcfb_update_marker_data *marker_data, + struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + bool marker_found = false; + int ret = 0; + + /* 0 is an invalid update_marker value */ + if (marker_data->update_marker == 0) + return -EINVAL; + + /* + * Find completion associated with update_marker requested. + * Note: If update completed already, marker will have been + * cleared, it won't be found, and function will just return. + */ + + /* Grab queue lock to protect access to marker list */ + mutex_lock(&fb_data->queue_mutex); + + list_for_each_entry_safe(next_marker, temp, + &fb_data->full_marker_list, full_list) { + if (next_marker->update_marker == marker_data->update_marker) { + dev_dbg(fb_data->dev, "Waiting for marker %d\n", + marker_data->update_marker); + next_marker->waiting = true; + marker_found = true; + break; + } + } + + mutex_unlock(&fb_data->queue_mutex); + + /* + * If marker not found, it has either been signalled already + * or the update request failed. In either case, just return. + */ + if (!marker_found) + return ret; + + ret = wait_for_completion_timeout(&next_marker->update_completion, + msecs_to_jiffies(8000)); + if (!ret) { + dev_err(fb_data->dev, + "Timed out waiting for update completion\n"); + return -ETIMEDOUT; + } + + /** since SPDC don't support auto collision detect, + * there alway returns no collision + */ + marker_data->collision_test = false; + + /* Free update marker object */ + kfree(next_marker); + + return ret; +} +EXPORT_SYMBOL(mxc_spdc_fb_wait_update_complete); + +int mxc_spdc_fb_set_pwrdown_delay(u32 pwrdown_delay, + struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + + fb_data->pwrdown_delay = pwrdown_delay; + + return 0; +} +EXPORT_SYMBOL(mxc_spdc_fb_set_pwrdown_delay); + +int mxc_spdc_get_pwrdown_delay(struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + + return fb_data->pwrdown_delay; +} +EXPORT_SYMBOL(mxc_spdc_get_pwrdown_delay); + +static int +mxc_spdc_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + void __user *argp = (void __user *)arg; + int ret = -EINVAL; + + dev_dbg(fb_data->dev, "cmd = %08X, arg = %08X\n", cmd, (u32)arg); + + switch (cmd) { + case MXCFB_SET_WAVEFORM_MODES: + { + struct mxcfb_waveform_modes modes; + if (!copy_from_user(&modes, argp, sizeof(modes))) { + mxc_spdc_fb_set_waveform_modes(&modes, info); + ret = 0; + } + break; + } + case MXCFB_SET_TEMPERATURE: + { + int temperature; + if (!get_user(temperature, (int32_t __user *) arg)) + ret = mxc_spdc_fb_set_temperature(temperature, + info); + break; + } + case MXCFB_SET_AUTO_UPDATE_MODE: + { + u32 auto_mode = 0; + if (!get_user(auto_mode, (__u32 __user *) arg)) + ret = mxc_spdc_fb_set_auto_update(auto_mode, + info); + break; + } + case MXCFB_SET_UPDATE_SCHEME: + { + u32 upd_scheme = 0; + if (!get_user(upd_scheme, (__u32 __user *) arg)) + ret = mxc_spdc_fb_set_upd_scheme(upd_scheme, + info); + break; + } + case MXCFB_SEND_UPDATE: + { + struct mxcfb_update_data upd_data; + if (!copy_from_user(&upd_data, argp, + sizeof(upd_data))) { + ret = mxc_spdc_fb_send_update(&upd_data, info); + if (ret == 0 && copy_to_user(argp, &upd_data, + sizeof(upd_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + break; + } + case MXCFB_WAIT_FOR_UPDATE_COMPLETE: + { + struct mxcfb_update_marker_data upd_marker_data; + if (!copy_from_user(&upd_marker_data, argp, + sizeof(upd_marker_data))) { + ret = mxc_spdc_fb_wait_update_complete( + &upd_marker_data, info); + if (copy_to_user(argp, &upd_marker_data, + sizeof(upd_marker_data))) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + + break; + } + case MXCFB_SET_PWRDOWN_DELAY: + { + int delay = 0; + if (!get_user(delay, (__u32 __user *) arg)) + ret = + mxc_spdc_fb_set_pwrdown_delay(delay, info); + break; + } + case MXCFB_GET_PWRDOWN_DELAY: + { + int pwrdown_delay = mxc_spdc_get_pwrdown_delay(info); + if (put_user(pwrdown_delay, (int __user *)arg)) + ret = -EFAULT; + break; + } + default: + dev_err(fb_data->dev, "IOCTL_CMD: not such command\n"); + return -ENOTTY; + } + + return ret; +} + +static void mxc_spdc_fb_update_pages(mxc_spdc_t *fb_data, u16 y1, u16 y2) +{ + struct mxcfb_update_data update; + + /* Do partial screen update, Update full horizontal lines */ + update.update_region.left = 0; + update.update_region.width = fb_data->spdc_fb_var.xres; + update.update_region.top = y1; + update.update_region.height = y2 - y1; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_mode = UPDATE_MODE_FULL; + update.update_marker = 0; + update.temp = SPDC_DEFAULT_TEMP; + update.flags = 0; + + mxc_spdc_fb_send_update(&update, &fb_data->info); +} + +/* this is called back from the deferred io workqueue */ +static void mxc_spdc_fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + struct page *page; + unsigned long beg, end; + int y1, y2, miny, maxy; + + if (fb_data->auto_mode != AUTO_UPDATE_MODE_AUTOMATIC_MODE) + return; + + miny = INT_MAX; + maxy = 0; + list_for_each_entry(page, pagelist, lru) { + beg = page->index << PAGE_SHIFT; + end = beg + PAGE_SIZE - 1; + y1 = beg / info->fix.line_length; + y2 = end / info->fix.line_length; + if (y2 >= fb_data->spdc_fb_var.yres) + y2 = fb_data->spdc_fb_var.yres - 1; + if (miny > y1) + miny = y1; + if (maxy < y2) + maxy = y2; + } + + mxc_spdc_fb_update_pages(fb_data, miny, maxy); +} + +static int mxc_spdc_fb_blank(int blank, struct fb_info *info) +{ + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + int ret = 0; + + dev_dbg(fb_data->dev, "blank = %d\n", blank); + + if (fb_data->blank == blank) + return 0; + + fb_data->blank = blank; + switch (blank) { + case FB_BLANK_POWERDOWN: + mxc_spdc_fb_flush_updates(fb_data); + /* Wait for powerdown */ + mutex_lock(&fb_data->power_mutex); + if ((fb_data->power_state == POWER_STATE_ON) && + (fb_data->pwrdown_delay == FB_POWERDOWN_DISABLE)) { + + /* Powerdown disabled, so we disable SPDC manually */ + int count = 0; + int sleep_ms = 10; + + mutex_unlock(&fb_data->power_mutex); + + /* If any active updates, wait for them to complete */ + while (fb_data->updates_active) { + /* Timeout after 1 sec */ + if ((count * sleep_ms) > 1000) + break; + msleep(sleep_ms); + count++; + } + + fb_data->powering_down = true; + spdc_powerdown(fb_data); + } else if (fb_data->power_state != POWER_STATE_OFF) { + fb_data->wait_for_powerdown = true; + init_completion(&fb_data->powerdown_compl); + mutex_unlock(&fb_data->power_mutex); + ret = + wait_for_completion_timeout(&fb_data->powerdown_compl, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(fb_data->dev, + "No powerdown received!\n"); + return -ETIMEDOUT; + } + } else + mutex_unlock(&fb_data->power_mutex); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxc_spdc_fb_flush_updates(fb_data); + break; + } + + return ret; +} + +static int mxc_spdc_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + mxc_spdc_t *fb_data = info ? + (mxc_spdc_t *)info:g_spdc_fb_data; + u_int y_bottom; + + dev_dbg(info->device, "%s: var->yoffset %d, info->var.yoffset %d\n", + __func__, var->yoffset, info->var.yoffset); + /* check if var is valid; also, xpan is not supported */ + if (!var || (var->xoffset != info->var.xoffset) || + (var->yoffset + var->yres > var->yres_virtual)) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((fb_data->spdc_fb_var.xoffset == var->xoffset) && + (fb_data->spdc_fb_var.yoffset == var->yoffset)) + return 0; /* No change, do nothing */ + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + mutex_lock(&fb_data->queue_mutex); + + fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset) + * (var->bits_per_pixel) / 8; + + fb_data->spdc_fb_var.xoffset = var->xoffset; + fb_data->spdc_fb_var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + mutex_unlock(&fb_data->queue_mutex); + + return 0; +} + +static struct fb_ops mxc_spdc_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxc_spdc_fb_check_var, + .fb_set_par = mxc_spdc_fb_set_par, + .fb_setcmap = mxc_spdc_fb_setcmap, + .fb_setcolreg = mxc_spdc_fb_setcolreg, + .fb_pan_display = mxc_spdc_fb_pan_display, + .fb_ioctl = mxc_spdc_fb_ioctl, + .fb_mmap = mxc_spdc_fb_mmap, + .fb_blank = mxc_spdc_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_deferred_io mxc_spdc_fb_defio = { + .delay = HZ, + .deferred_io = mxc_spdc_fb_deferred_io, +}; + +static void spdc_done_work_func(struct work_struct *work) +{ + mxc_spdc_t *fb_data = + container_of(work, mxc_spdc_t, spdc_done_work.work); + spdc_powerdown(fb_data); +} + +static irqreturn_t mxc_spdc_irq_handler(int irq, void *dev_id) +{ + mxc_spdc_t *fb_data = (mxc_spdc_t *)dev_id; + ulong flags; + u32 int_events; + irqreturn_t ret = IRQ_NONE; + + spin_lock_irqsave(&fb_data->lock, flags); + + int_events = spdc_get_intr_stat(fb_data); + dev_dbg(fb_data->dev, "spdc int:%x\n", int_events); + + if (int_events & SPDC_IRQ_STA_ERR) { + ret = IRQ_HANDLED; + spdc_intr_stat_clear(fb_data, SPDC_IRQ_STA_ERR); + dev_err(fb_data->dev, "Error IRQ\n"); + return ret; + } + + /* + * If we just completed one-time panel init, bypass + * queue handling, clear interrupt and return + */ + if (fb_data->operation_mode && + (int_events & SPDC_IRQ_STA_FRAME_UPDATE)) { + mutex_lock(&fb_data->queue_mutex); + fb_data->updates_active = false; + complete(&fb_data->update_finish); + spdc_intr_stat_clear(fb_data, SPDC_IRQ_STA_FRAME_UPDATE); + + if ((fb_data->operation_mode == SPDC_DEEP_REFRESH) || + fb_data->is_deep_fresh) + fb_data->is_deep_fresh = false; + else + fb_data->operation_mode = SPDC_NO_OPERATION; + + mutex_unlock(&fb_data->queue_mutex); + + return IRQ_HANDLED; + } + + /* waveform loading to SPDC from memory */ + if (int_events & SPDC_IRQ_STA_LUT_DOWNLOAD) { + if (is_lut_checksum_ok(fb_data)) { + complete(&fb_data->lut_down); + spdc_intr_stat_clear(fb_data, + SPDC_IRQ_STA_LUT_DOWNLOAD); + } else + dev_dbg(fb_data->dev, "Lut checksum is err!\n"); + + return IRQ_HANDLED; + } + + /* SPDC init setting IRQ */ + if (int_events & SPDC_IRQ_STA_TCON_INIT) { + complete(&fb_data->init_finish); + spdc_intr_stat_clear(fb_data, SPDC_IRQ_STA_TCON_INIT); + + return IRQ_HANDLED; + } + + spin_unlock_irqrestore(&fb_data->lock, flags); + + if (spdc_is_update_finish(fb_data)) { + /* clear interrupt status */ + spdc_intr_stat_clear(fb_data, SPDC_IRQ_STA_FRAME_UPDATE); + queue_work(fb_data->spdc_intr_workqueue, + &fb_data->spdc_intr_work); + } + + return ret; +} + +static void spdc_intr_work_func(struct work_struct *work) +{ + mxc_spdc_t *fb_data = + container_of(work, mxc_spdc_t, spdc_intr_work); + struct mxcfb_rect *next_upd_region; + struct update_marker_data *next_marker; + struct update_marker_data *temp; + struct update_data_list *next_upd, *temp_upd; + + /* Protect access to buffer queues and to update HW */ + mutex_lock(&fb_data->queue_mutex); + + /* Check to see if all updates have completed */ + if (list_empty(&fb_data->upd_pending_list) && + is_free_list_full(fb_data) && + (fb_data->cur_update == NULL)) { + + fb_data->updates_active = false; + + if (fb_data->pwrdown_delay != FB_POWERDOWN_DISABLE) { + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule task to disable SPDC HW until next update */ + schedule_delayed_work(&fb_data->spdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) + complete(&fb_data->updates_done); + } + + if (mxc_spdc_device_is_busy(fb_data)) { + /* Can't submit another update until SPDC is idle */ + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* + * Were we waiting on working buffer? + * If so, update queues and check for collisions + */ + if (fb_data->cur_update != NULL) { + list_for_each_entry_safe(next_marker, temp, + &fb_data->cur_update->update_desc->upd_marker_list, + upd_list) { + + /* Del from per-update & full list */ + list_del_init(&next_marker->upd_list); + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker %d\n", + next_marker->update_marker); + if (next_marker->waiting) + complete(&next_marker->update_completion); + else + kfree(next_marker); + } + + /* Free marker list and update descriptor */ + kfree(fb_data->cur_update->update_desc); + + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list); + + if (fb_data->submit_upd_sta & SPDC_CONCUR_UPD) { + fb_data->upd_preprocess_num = 0; + fb_data->submit_upd_sta &= (~SPDC_CONCUR_UPD); + } + + /* free ACC update list */ + list_for_each_entry_safe(next_upd, temp_upd, + &fb_data->upd_buf_preprocess_list, list) { + next_marker = list_entry( + (&next_upd->update_desc->upd_marker_list)->next, + struct update_marker_data, upd_list); + + list_del_init(&next_marker->upd_list); + list_del_init(&next_marker->full_list); + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker %d\n", + next_marker->update_marker); + + if (next_marker->waiting) + complete(&next_marker->update_completion); + else + kfree(next_marker); + + + list_del_init(&next_upd->list); + /* Add to free buffer list */ + list_add_tail(&next_upd->list, + &fb_data->upd_buf_free_list); + kfree(next_upd->update_desc); + } + + /* Check to see if all updates have completed */ + if (list_empty(&fb_data->upd_pending_list) && + is_free_list_full(fb_data)) { + + fb_data->updates_active = false; + + if (fb_data->pwrdown_delay != + FB_POWERDOWN_DISABLE) { + /* + * Set variable to prevent overlapping + * enable/disable requests + */ + fb_data->powering_down = true; + + /* Schedule SPDC disable */ + schedule_delayed_work(&fb_data->spdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + + /* Reset counter to reduce chance of overflow */ + fb_data->order_cnt = 0; + } + + if (fb_data->waiting_for_idle) + complete(&fb_data->updates_done); + } + + /* Signal completion if submit workqueue was waiting on WB */ + if (fb_data->waiting_for_wb) { + dev_dbg(fb_data->dev, "free update_res_free\n"); + complete(&fb_data->update_res_free); + fb_data->waiting_for_wb = false; + } + + /* Clear current update */ + fb_data->cur_update = NULL; + } + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + /* Schedule task to submit collision and pending update */ + if (!fb_data->powering_down) + queue_work(fb_data->spdc_submit_workqueue, + &fb_data->spdc_submit_work); + + /* Release buffer queues */ + mutex_unlock(&fb_data->queue_mutex); + return; + } + + /* Snapshot update scheme processing */ + if (list_empty(&fb_data->upd_buf_queue)) { + dev_dbg(fb_data->dev, "No pending updates.\n"); + + /* No updates pending, so we are done */ + mutex_unlock(&fb_data->queue_mutex); + return; + } else { + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + /* Process next item in update list */ + fb_data->cur_update = + list_entry(fb_data->upd_buf_queue.next, + struct update_data_list, list); + list_del_init(&fb_data->cur_update->list); + } + + /* program SPDC register and trigger to process buffer*/ + next_upd_region = + &fb_data->cur_update->update_desc->upd_data.update_region; + fb_data->fresh_param.buf_addr.next_buf_phys_addr = + fb_data->cur_update->phys_addr + + (fb_data->cur_update->update_desc->spdc_offs / 2); + + fb_data->fresh_param.update_region.left = next_upd_region->left; + fb_data->fresh_param.update_region.top = next_upd_region->top; + fb_data->fresh_param.update_region.width = next_upd_region->width; + fb_data->fresh_param.update_region.height = next_upd_region->height; + fb_data->fresh_param.temper = + fb_data->cur_update->update_desc->upd_data.temp; + fb_data->fresh_param.wave_mode = + fb_data->cur_update->update_desc->upd_data.waveform_mode; + spdc_submit_update(fb_data); + + mutex_unlock(&fb_data->queue_mutex); + + return; +} + +/* + * Sysfs functions + */ +static ssize_t show_update(struct device *device, + struct device_attribute *attr, char *buf) { + struct fb_info *info = dev_get_drvdata(device); + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + + return sprintf(buf, "mode%d\n", fb_data->fresh_param.wave_mode); +} + +static ssize_t store_update(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxcfb_update_data update; + struct fb_info *info = dev_get_drvdata(device); + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + if (strncmp(buf, "direct", 6) == 0) + update.waveform_mode = fb_data->wv_modes.mode_du; + else if (strncmp(buf, "gc16", 4) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc16; + else if (strncmp(buf, "gc4", 3) == 0) + update.waveform_mode = fb_data->wv_modes.mode_gc4; + else if (strncmp(buf, "init", 4) == 0) + update.waveform_mode = fb_data->wv_modes.mode_init; + else if (strncmp(buf, "gu", 2) == 0) + update.waveform_mode = SPDC_WAV_MODE_1; + else if (strncmp(buf, "auto", 4) == 0) + update.waveform_mode = WAVEFORM_MODE_AUTO; + + /* Now, request full screen update */ + update.update_region.left = 0; + update.update_region.width = fb_data->spdc_fb_var.xres; + update.update_region.top = 0; + update.update_region.height = fb_data->spdc_fb_var.yres; + update.update_mode = UPDATE_MODE_FULL; + update.temp = SPDC_DEFAULT_TEMP; + update.update_marker = 0; + update.flags = 0; + dev_dbg(fb_data->dev, "rotation:%d, gray:%d\n", + fb_data->spdc_fb_var.rotate, fb_data->spdc_fb_var.grayscale); + + mxc_spdc_fb_send_update(&update, info); + + return count; +} + +static ssize_t fresh_show(struct device *device, struct device_attribute *attr, + char *buf) +{ + struct fb_info *info = dev_get_drvdata(device); + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + + return sprintf(buf, "%d\n", fb_data->operation_mode); +} + +static ssize_t fresh_store(struct device *device, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(device); + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + int ret, operation; + + ret = kstrtoint(buf, 10, &operation); + if (ret) + return ret; + + if (operation == SPDC_DEEP_REFRESH) + fb_data->is_deep_fresh = true; + fb_data->operation_mode = operation; + if (operation > SPDC_NO_OPERATION && + operation < SPDC_FULL_REFRESH) + mxc_operaton_update(fb_data); + else + mxc_spdc_refresh_display(fb_data); + + return count; +} + +static ssize_t temp_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(device); + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + int temp; + + temp = fb_data->fresh_param.temper >> 1; + return sprintf(buf, "%d\n", temp); +} + +static ssize_t initset_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(device); + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + u32 init_val; + + get_panel_init_set(&fb_data->panel_set, &init_val); + return sprintf(buf, "%x\n", init_val); +} + +static ssize_t concurrency_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(device); + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + bool temp; + + temp = (fb_data->fresh_param.concur ? 1 : 0); + return sprintf(buf, "%d\n", temp); +} + +static ssize_t concurrency_update(struct device *device, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(device); + mxc_spdc_t *fb_data = (mxc_spdc_t *)info; + int ret, concur; + + ret = kstrtoint(buf, 10, &concur); + if (ret) + return ret; + + if (fb_data->fresh_param.concur != concur) { + fb_data->fresh_param.concur = concur; + spdc_set_update_concurrency(fb_data); + } + + return count; +} + +static DEVICE_ATTR(store_update, 0644, show_update, store_update); +static DEVICE_ATTR(fresh, 0644, fresh_show, fresh_store); +static DEVICE_ATTR(temp, 0644, temp_show, NULL); +static DEVICE_ATTR(initset, 0644, initset_show, NULL); +static DEVICE_ATTR(concurrency, 0644, concurrency_show, concurrency_update); + +static struct attribute *spdc_attributes[] = { + &dev_attr_store_update.attr, + &dev_attr_fresh.attr, + &dev_attr_temp.attr, + &dev_attr_initset.attr, + &dev_attr_concurrency.attr, + NULL +}; + +static const struct attribute_group spdc_attr_group = { + .attrs = spdc_attributes, +}; + +static int __devinit mxc_spdc_fb_probe(struct platform_device *pdev) +{ + struct fb_info *info; + mxc_spdc_t *fb_data; + struct resource *res, *mem; + struct fb_videomode *vmode; + int xres_virt, yres_virt, buf_size; + int xres_virt_rot, yres_virt_rot, pix_size_rot; + struct fb_var_screeninfo *var_info; + struct fb_fix_screeninfo *fix_info; + struct pxp_config_data *pxp_conf; + struct pxp_proc_data *proc_data; + struct scatterlist *sg; + struct update_data_list *upd_list; + struct update_data_list *plist, *temp_list; + char *options, *opt; + char *panel_str = NULL; + char name[] = "mxcspdcfb"; + unsigned long x_mem_size = 0; + u32 i, cmap_size; + int ret = 0; + + fb_data = (mxc_spdc_t *)framebuffer_alloc(sizeof(mxc_spdc_t), + &pdev->dev); + if (!fb_data) { + ret = -ENOMEM; + goto out; + } + + info = &fb_data->info; + fb_data->dev = &pdev->dev; + platform_set_drvdata(pdev, fb_data); + + fb_data->pdata = pdev->dev.platform_data; + if ((fb_data->pdata == NULL) || (fb_data->pdata->num_modes < 1) + || (fb_data->pdata->spdc_mode == NULL) + || (fb_data->pdata->spdc_mode->vmode == NULL)) { + ret = -EINVAL; + goto dealloc_fb; + } + + /*get panel para from command*/ + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto dealloc_fb; + } + if (options) + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "bpp=", 4)) { + if (kstrtoint(opt + 4, 0, + &fb_data->default_bpp) < 0) + fb_data->default_bpp = 0; + } else if (!strncmp(opt, "x_mem=", 6)) + x_mem_size = memparse(opt + 6, NULL); + else + panel_str = opt; + } + + if (!fb_data->default_bpp) + fb_data->default_bpp = SPDC_DEFAULT_BPP; + + /* Set default (first defined mode) for a match */ + mxc_spdc_find_match_mode(fb_data); + + if (panel_str) + for (i = 0; i < fb_data->pdata->num_modes; i++) + if (!strcmp(fb_data->pdata->spdc_mode[i].vmode->name, + panel_str)) { + fb_data->cur_mode = + &fb_data->pdata->spdc_mode[i]; + break; + } + vmode = fb_data->cur_mode->vmode; + + /* Allocate color map for the FB */ + cmap_size = 256; + ret = fb_alloc_cmap(&info->cmap, cmap_size, 0); + if (ret) + goto dealloc_fb; + + /* + * GPU alignment restrictions dictate framebuffer parameters: + * - 32-byte alignment for buffer width + * - 128-byte alignment for buffer height + * => 4K buffer alignment for buffer start + */ + xres_virt = ALIGN(vmode->xres, 32); + yres_virt = ALIGN(vmode->yres, 128); + fb_data->max_pix_size = PAGE_ALIGN(xres_virt * yres_virt); + + /* + * Have to check to see if aligned buffer size when rotated + * is bigger than when not rotated, and use the max + */ + xres_virt_rot = ALIGN(vmode->yres, 32); + yres_virt_rot = ALIGN(vmode->xres, 128); + pix_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot); + fb_data->max_pix_size = (fb_data->max_pix_size > pix_size_rot) ? + fb_data->max_pix_size : pix_size_rot; + buf_size = fb_data->max_pix_size * fb_data->default_bpp/8; + + /* Compute the number of screens needed based on X memory requested */ + if (x_mem_size > 0) { + fb_data->num_screens = DIV_ROUND_UP(x_mem_size, buf_size); + if (fb_data->num_screens < NUM_SCREENS_MIN) + fb_data->num_screens = NUM_SCREENS_MIN; + else if (buf_size * fb_data->num_screens > SZ_16M) + fb_data->num_screens = SZ_16M / buf_size; + } else + fb_data->num_screens = NUM_SCREENS_MIN; + + fb_data->map_size = buf_size * fb_data->num_screens; + dev_dbg(&pdev->dev, "memory allocate: %d\n", fb_data->map_size); + + /* get IO memory*/ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get memory register\n"); + ret = -ENXIO; + goto release_cmap; + } + + mem = request_mem_region(res->start, resource_size(res), pdev->name); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get memory region\n"); + ret = -ENOENT; + goto release_cmap; + } + + fb_data->hwp = ioremap(res->start, SZ_4K); + if (fb_data->hwp == NULL) { + dev_err(&pdev->dev, "ioremap registers failed\n"); + ret = -ENOENT; + goto release_mem; + } + + /* Allocate FB memory */ + fb_data->virt_start = dma_alloc_writecombine(&pdev->dev, + fb_data->map_size, &fb_data->phys_start, GFP_DMA); + if (fb_data->virt_start == NULL) { + dev_err(&pdev->dev, "probe err - dma_alloc for framebuffer\n"); + ret = -ENOMEM; + goto release_hwp; + } + + var_info = &info->var; + var_info->activate = FB_ACTIVATE_TEST; + var_info->bits_per_pixel = fb_data->default_bpp; + var_info->xres = vmode->xres; + var_info->yres = vmode->yres; + var_info->xres_virtual = xres_virt; + var_info->yres_virtual = yres_virt * fb_data->num_screens; + var_info->pixclock = vmode->pixclock; + var_info->left_margin = vmode->left_margin; + var_info->right_margin = vmode->right_margin; + var_info->upper_margin = vmode->upper_margin; + var_info->lower_margin = vmode->lower_margin; + var_info->hsync_len = vmode->hsync_len; + var_info->vsync_len = vmode->vsync_len; + var_info->vmode = FB_VMODE_NONINTERLACED; + + switch (fb_data->default_bpp) { + case 32: + case 24: + var_info->red.offset = 16; + var_info->red.length = 8; + var_info->green.offset = 8; + var_info->green.length = 8; + var_info->blue.offset = 0; + var_info->blue.length = 8; + break; + case 16: + var_info->red.offset = 11; + var_info->red.length = 5; + var_info->green.offset = 5; + var_info->green.length = 6; + var_info->blue.offset = 0; + var_info->blue.length = 5; + break; + case 8: + var_info->grayscale = GRAYSCALE_8BIT; + + var_info->red.length = 8; + var_info->red.offset = 0; + var_info->red.msb_right = 0; + var_info->green.length = 8; + var_info->green.offset = 0; + var_info->green.msb_right = 0; + var_info->blue.length = 8; + var_info->blue.offset = 0; + var_info->blue.msb_right = 0; + break; + case 4: + var_info->grayscale = GRAYSCALE_4BIT; + + var_info->red.length = 4; + var_info->red.offset = 0; + var_info->red.msb_right = 0; + var_info->green.length = 4; + var_info->green.offset = 0; + var_info->green.msb_right = 0; + var_info->blue.length = 4; + var_info->blue.offset = 0; + var_info->blue.msb_right = 0; + break; + default: + dev_err(&pdev->dev, "unsupported bit-width:%d\n", + fb_data->default_bpp); + ret = -EINVAL; + goto release_dma_fb; + } + + fix_info = &info->fix; + strcpy(fix_info->id, SPDC_DRIVER_NAME); + fix_info->type = FB_TYPE_PACKED_PIXELS; + fix_info->visual = FB_VISUAL_TRUECOLOR; + fix_info->xpanstep = 0; + fix_info->ypanstep = 0; + fix_info->ywrapstep = 0; + fix_info->accel = FB_ACCEL_NONE; + fix_info->smem_start = fb_data->phys_start; + fix_info->smem_len = fb_data->map_size; + fix_info->ypanstep = 0; + + /* Set up FB info */ + fb_data->native_width = vmode->xres; + fb_data->native_height = vmode->yres; + info->screen_base = fb_data->virt_start; + info->screen_size = info->fix.smem_len; + info->fbops = &mxc_spdc_fb_ops; + info->var.activate = FB_ACTIVATE_NOW; + info->pseudo_palette = fb_data->pseudo_palette; + info->flags = FBINFO_FLAG_DEFAULT; + + mxc_spdc_fb_set_fix(info); + + /* use the same AXI and PIX clock source */ + fb_data->spdc_clk_axi = clk_get(fb_data->dev, "epdc_axi"); + if (IS_ERR(fb_data->spdc_clk_axi)) { + dev_err(&pdev->dev, "Unable to get AXI clk." + "err = 0x%x\n", (int)fb_data->spdc_clk_axi); + ret = -ENODEV; + goto release_dma_fb; + } + fb_data->spdc_clk_pix = clk_get(fb_data->dev, "epdc_pix"); + if (IS_ERR(fb_data->spdc_clk_pix)) { + dev_err(&pdev->dev, "Unable to get pix clk." + "err = 0x%x\n", (int)fb_data->spdc_clk_pix); + ret = -ENODEV; + goto release_dma_fb; + } + + /* + * Initialize update list and allocate buffer. + */ + INIT_LIST_HEAD(&fb_data->upd_pending_list); + INIT_LIST_HEAD(&fb_data->upd_buf_queue); + INIT_LIST_HEAD(&fb_data->upd_buf_free_list); + INIT_LIST_HEAD(&fb_data->upd_buf_preprocess_list); + INIT_LIST_HEAD(&fb_data->full_marker_list); + + fb_data->max_num_updates = SPDC_MAX_NUM_UPDATES; + fb_data->max_num_buffers = SPDC_MAX_NUM_BUFFERS; + + /* Allocate free buffers */ + for (i = 0; i < fb_data->max_num_updates; i++) { + upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL); + if (upd_list == NULL) { + ret = -ENOMEM; + goto release_dma_fb; + } + + /* Add newly allocated buffer to free list */ + list_add(&upd_list->list, &fb_data->upd_buf_free_list); + } + + /* Allocate PXP output buffer */ + fb_data->virt_addr_updbuf = + kzalloc(sizeof(void *) * fb_data->max_num_buffers, GFP_KERNEL); + fb_data->phys_addr_updbuf = + kzalloc(sizeof(dma_addr_t) * fb_data->max_num_buffers, + GFP_KERNEL); + for (i = 0; i < fb_data->max_num_buffers; i++) { + /* + * Allocate memory for PxP output buffer. + * Output raw data is Y4 format. + * Each update buffer is 1/2 byte per pixel, and can + * be as big as the full-screen frame buffer + */ + fb_data->virt_addr_updbuf[i] = + dma_alloc_coherent(fb_data->info.device, + fb_data->max_pix_size / 2, + &fb_data->phys_addr_updbuf[i], GFP_DMA); + if (fb_data->virt_addr_updbuf[i] == NULL) { + ret = -ENOMEM; + goto release_freebuf_lists; + } + } + /* Counter indicating which update buffer should be used next. */ + fb_data->upd_buffer_num = 0; + + /* Allocate memory for partical process region buffer. + * Output raw data is Y4 format. + */ + fb_data->virt_addr_copybuf = + dma_alloc_coherent(fb_data->info.device, + fb_data->max_pix_size / 2, + &fb_data->phys_addr_copybuf, GFP_DMA); + if (fb_data->virt_addr_copybuf == NULL) { + ret = -ENOMEM; + goto release_output_buf; + } + + /* Allocate next & current & privious & count & lut buffers. + * next buffer size is Y4 raw data + */ + fb_data->next_buf_size = + (fb_data->map_size / fb_data->num_screens) >> 1; + fb_data->virt_next_buf = + dma_alloc_coherent(&pdev->dev, fb_data->next_buf_size, + &fb_data->phy_next_buf, GFP_DMA); + if (fb_data->virt_next_buf == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for next buf!\n"); + ret = -ENOMEM; + goto release_copy_buf; + } + + fb_data->current_buf_size = + (fb_data->map_size / fb_data->num_screens) >> 1; + fb_data->virt_current_buf = + dma_alloc_coherent(&pdev->dev, fb_data->current_buf_size, + &fb_data->phy_current_buf, GFP_DMA); + if (fb_data->virt_current_buf == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for current buf!\n"); + ret = -ENOMEM; + goto release_next_buf; + } + + fb_data->pre_buf_size = + (fb_data->map_size / fb_data->num_screens) >> 1; + fb_data->virt_pre_buf = + dma_alloc_coherent(&pdev->dev, fb_data->pre_buf_size, + &fb_data->phy_pre_buf, GFP_DMA); + if (fb_data->virt_pre_buf == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for current buf!\n"); + ret = -ENOMEM; + goto release_current_buf; + } + + fb_data->cnt_buf_size = info->var.xres * info->var.yres; + fb_data->virt_cnt_buf = + dma_alloc_coherent(&pdev->dev, fb_data->cnt_buf_size, + &fb_data->phy_cnt_buf, GFP_DMA); + if (fb_data->virt_cnt_buf == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for current buf!\n"); + ret = -ENOMEM; + goto release_pre_buf; + } + + fb_data->lut_buf_size = SZ_1M; + fb_data->virt_lut_buf = + dma_alloc_coherent(&pdev->dev, fb_data->lut_buf_size, + &fb_data->phy_lut_buf, GFP_DMA); + if (fb_data->virt_lut_buf == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for current buf!\n"); + ret = -ENOMEM; + goto release_cnt_buf; + } + + /* Initialize SPDC pins */ + if (fb_data->pdata->get_pins) + fb_data->pdata->get_pins(); + + fb_data->hw_ready = false; + fb_data->hw_initializing = false; + + /* + * Set default waveform mode values. + * Should be overwritten via ioctl. + */ + fb_data->wv_modes.mode_init = SPDC_WAV_MODE_DEFAULT; + fb_data->wv_modes.mode_du = SPDC_WAV_MODE_2; + fb_data->wv_modes.mode_gc4 = SPDC_WAV_MODE_2; + fb_data->wv_modes.mode_gc8 = SPDC_WAV_MODE_1; + fb_data->wv_modes.mode_gc16 = SPDC_WAV_MODE_1; + fb_data->wv_modes.mode_gc32 = SPDC_WAV_MODE_1; + + fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE; + fb_data->upd_scheme = UPDATE_SCHEME_QUEUE_AND_MERGE; + fb_data->spdc_fb_var = *var_info; + fb_data->fb_offset = 0; + + fb_data->blank = FB_BLANK_UNBLANK; + fb_data->powering_down = false; + fb_data->power_state = POWER_STATE_OFF; + fb_data->pwrdown_delay = 0; + fb_data->cur_update = NULL; + fb_data->waiting_for_idle = false; + fb_data->order_cnt = 0; + fb_data->waiting_for_wb = false; + fb_data->wait_for_powerdown = false; + fb_data->updates_active = false; + + spin_lock_init(&fb_data->lock); + mutex_init(&fb_data->queue_mutex); + mutex_init(&fb_data->pxp_mutex); + mutex_init(&fb_data->power_mutex); + init_completion(&fb_data->lut_down); + init_completion(&fb_data->init_finish); + init_completion(&fb_data->update_finish); + INIT_DELAYED_WORK(&fb_data->spdc_done_work, spdc_done_work_func); + + fb_data->spdc_submit_workqueue = alloc_workqueue("SPDC Submit", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->spdc_submit_work, spdc_submit_work_func); + fb_data->spdc_intr_workqueue = alloc_workqueue("SPDC Interrupt", + WQ_MEM_RECLAIM | WQ_HIGHPRI | + WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); + INIT_WORK(&fb_data->spdc_intr_work, spdc_intr_work_func); + + /* Retrieve spdc IRQ num */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto release_lut_buf; + } + fb_data->spdc_irq = res->start; + ret = request_irq(fb_data->spdc_irq, mxc_spdc_irq_handler, 0, + "fb_dma", fb_data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + fb_data->spdc_irq, ret); + ret = -ENODEV; + goto release_lut_buf; + } + + /* define deferred io */ + info->fbdefio = &mxc_spdc_fb_defio; +#ifdef CONFIG_FB_MXC_SIPIX_AUTO_UPDATE_MODE + fb_deferred_io_init(info); +#endif + + /* get pmic regulators */ + fb_data->display_regulator = regulator_get(NULL, "DISPLAY"); + if (IS_ERR(fb_data->display_regulator)) { + dev_err(&pdev->dev, "Unable to get display PMIC regulator." + "err = 0x%x\n", (int)fb_data->display_regulator); + ret = -ENODEV; + goto release_irq; + } + fb_data->vcom_regulator = regulator_get(NULL, "VCOM"); + if (IS_ERR(fb_data->vcom_regulator)) { + regulator_put(fb_data->display_regulator); + dev_err(&pdev->dev, "Unable to get VCOM regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto release_regulator1; + } + fb_data->v3p3_regulator = regulator_get(NULL, "V3P3"); + if (IS_ERR(fb_data->v3p3_regulator)) { + regulator_put(fb_data->vcom_regulator); + regulator_put(fb_data->display_regulator); + dev_err(&pdev->dev, "Unable to get V3P3 regulator." + "err = 0x%x\n", (int)fb_data->vcom_regulator); + ret = -ENODEV; + goto release_regulator2; + } + + /* + * Fill out PxP config data structure based on FB info and + * processing tasks required + */ + pxp_conf = &fb_data->pxp_conf; + proc_data = &pxp_conf->proc_data; + + /* Initialize non-channel-specific PxP parameters */ + proc_data->drect.left = proc_data->srect.left = 0; + proc_data->drect.top = proc_data->srect.top = 0; + proc_data->drect.width = fb_data->info.var.xres; + proc_data->srect.width = fb_data->info.var.xres; + proc_data->drect.height = fb_data->info.var.yres; + proc_data->srect.height = fb_data->info.var.yres; + proc_data->scaling = 0; + proc_data->hflip = 0; + proc_data->vflip = 0; + proc_data->rotate = 0; + proc_data->bgcolor = 0; + proc_data->overlay_state = 0; + proc_data->lut_transform = PXP_LUT_NONE; + proc_data->lut_map = NULL; + + /* + * We initially configure PxP for RGB->YUV conversion, + * and only write out Y component of the result. + */ + + /* + * Initialize S0 channel parameters + * Parameters should match FB format/width/height + */ + pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->s0_param.width = fb_data->info.var.xres_virtual; + pxp_conf->s0_param.height = fb_data->info.var.yres; + pxp_conf->s0_param.color_key = -1; + pxp_conf->s0_param.color_key_enable = false; + + /* + * Initialize OL0 channel parameters + * No overlay will be used for PxP operation + */ + for (i = 0; i < 8; i++) { + pxp_conf->ol_param[i].combine_enable = false; + pxp_conf->ol_param[i].width = 0; + pxp_conf->ol_param[i].height = 0; + pxp_conf->ol_param[i].pixel_fmt = PXP_PIX_FMT_RGB565; + pxp_conf->ol_param[i].color_key_enable = false; + pxp_conf->ol_param[i].color_key = -1; + pxp_conf->ol_param[i].global_alpha_enable = false; + pxp_conf->ol_param[i].global_alpha = 0; + pxp_conf->ol_param[i].local_alpha_enable = false; + } + + /* + * Initialize Output channel parameters + * Output is Y-only greyscale + * Output width/height will vary based on update region size + */ + pxp_conf->out_param.width = fb_data->info.var.xres; + pxp_conf->out_param.height = fb_data->info.var.yres; + pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GY04; + pxp_conf->out_param.stride = pxp_conf->out_param.width; + + /* Initialize color map for conversion of 8-bit gray pixels */ + fb_data->pxp_conf.proc_data.lut_map = kmalloc(256, GFP_KERNEL); + if (fb_data->pxp_conf.proc_data.lut_map == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for lut map!\n"); + ret = -ENOMEM; + goto release_regulator3; + } + for (i = 0; i < 256; i++) + fb_data->pxp_conf.proc_data.lut_map[i] = i; + + fb_data->pxp_conf.proc_data.lut_map_updated = true; + + /* + * Ensure this is set to NULL here...we will initialize pxp_chan + * later in our thread. + */ + fb_data->pxp_chan = NULL; + + /* Initialize Scatter-gather list containing 2 buffer addresses. */ + sg = fb_data->sg; + sg_init_table(sg, 2); + + /* + * For use in PxP transfers: + * sg[0] holds the FB buffer pointer + * sg[1] holds the Output buffer pointer (configured before TX request) + */ + sg_dma_address(&sg[0]) = info->fix.smem_start; + sg_set_page(&sg[0], virt_to_page(info->screen_base), + info->fix.smem_len, offset_in_page(info->screen_base)); + + /* Register FB */ + ret = register_framebuffer(info); + if (ret) { + dev_err(&pdev->dev, + "register framebuffer failed\n"); + goto release_lutmap; + } + + ret = sysfs_create_group(&info->device->kobj, &spdc_attr_group); + if (ret) + dev_err(&pdev->dev, "Unable to create file from fb_attrs\n"); + + /* use for spdc test */ + g_spdc_fb_data = fb_data; + + /* hw init */ + spdc_fb_dev_init(fb_data); + + /*detect spdc epd disp & tcon version*/ + get_spdc_version(fb_data); + + goto out; + +release_lutmap: + kfree(fb_data->pxp_conf.proc_data.lut_map); +release_regulator3: + regulator_put(fb_data->v3p3_regulator); +release_regulator2: + regulator_put(fb_data->vcom_regulator); +release_regulator1: + regulator_put(fb_data->display_regulator); +release_irq: + free_irq(fb_data->spdc_irq, fb_data); +release_lut_buf: + dma_free_coherent(&pdev->dev, fb_data->lut_buf_size, + fb_data->virt_lut_buf, fb_data->phy_lut_buf); +release_cnt_buf: + dma_free_coherent(&pdev->dev, fb_data->cnt_buf_size, + fb_data->virt_cnt_buf, fb_data->phy_cnt_buf); +release_pre_buf: + dma_free_coherent(&pdev->dev, fb_data->pre_buf_size, + fb_data->virt_pre_buf, fb_data->phy_pre_buf); +release_current_buf: + dma_free_coherent(&pdev->dev, fb_data->current_buf_size, + fb_data->virt_current_buf, fb_data->phy_current_buf); +release_next_buf: + dma_free_coherent(&pdev->dev, fb_data->next_buf_size, + fb_data->virt_next_buf, fb_data->phy_next_buf); +release_copy_buf: + dma_free_writecombine(&pdev->dev, fb_data->max_pix_size / 2, + fb_data->virt_addr_copybuf, fb_data->phys_addr_copybuf); +release_output_buf: + for (i = 0; i < fb_data->max_num_buffers; i++) + if (fb_data->virt_addr_updbuf[i] != NULL) + dma_free_writecombine(&pdev->dev, + fb_data->max_pix_size / 2, fb_data->virt_addr_updbuf[i], + fb_data->phys_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf != NULL) + kfree(fb_data->virt_addr_updbuf); + if (fb_data->phys_addr_updbuf != NULL) + kfree(fb_data->phys_addr_updbuf); +release_freebuf_lists: + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, + list) { + list_del(&plist->list); + kfree(plist); + } +release_dma_fb: + dma_free_writecombine(&pdev->dev, + fb_data->map_size, fb_data->virt_start, fb_data->phys_start); +release_hwp: + iounmap(fb_data->hwp); +release_mem: + release_resource(mem); +release_cmap: + fb_dealloc_cmap(&info->cmap); +dealloc_fb: + framebuffer_release(info); +out: + return ret; +} + +static int mxc_spdc_fb_remove(struct platform_device *pdev) +{ + struct update_data_list *plist, *temp_list; + mxc_spdc_t *fb_data = platform_get_drvdata(pdev); + struct fb_info *info = &fb_data->info; + int i; + + mxc_spdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info); + + flush_workqueue(fb_data->spdc_submit_workqueue); + destroy_workqueue(fb_data->spdc_submit_workqueue); + + regulator_put(fb_data->display_regulator); + regulator_put(fb_data->vcom_regulator); + regulator_put(fb_data->v3p3_regulator); + + for (i = 0; i < fb_data->max_num_buffers; i++) + if (fb_data->virt_addr_updbuf[i] != NULL) + dma_free_writecombine(&pdev->dev, + fb_data->max_pix_size, + fb_data->virt_addr_updbuf[i], + fb_data->phys_addr_updbuf[i]); + if (fb_data->virt_addr_updbuf != NULL) + kfree(fb_data->virt_addr_updbuf); + if (fb_data->phys_addr_updbuf != NULL) + kfree(fb_data->phys_addr_updbuf); + + /* free output temporary buffer */ + dma_free_writecombine(&pdev->dev, fb_data->max_pix_size / 2, + fb_data->virt_addr_copybuf, fb_data->phys_addr_copybuf); + + list_for_each_entry_safe(plist, temp_list, + &fb_data->upd_buf_free_list, + list) { + list_del(&plist->list); + kfree(plist); + } + +#if defined(CONFIG_FB_MXC_SIPIX_AUTO_UPDATE_MODE) + fb_deferred_io_cleanup(&fb_data->info); +#endif + + /* free frame buffer */ + dma_free_writecombine(&pdev->dev, fb_data->map_size, + info->screen_base, fb_data->phys_start); + + /* free SPDC hw allocate buffer */ + dma_free_coherent(&pdev->dev, fb_data->next_buf_size, + fb_data->virt_next_buf, fb_data->phy_next_buf); + dma_free_coherent(&pdev->dev, fb_data->current_buf_size, + fb_data->virt_current_buf, fb_data->phy_current_buf); + dma_free_coherent(&pdev->dev, fb_data->pre_buf_size, + fb_data->virt_pre_buf, fb_data->phy_pre_buf); + dma_free_coherent(&pdev->dev, fb_data->cnt_buf_size, + fb_data->virt_cnt_buf, fb_data->phy_cnt_buf); + dma_free_coherent(&pdev->dev, fb_data->lut_buf_size, + fb_data->virt_lut_buf, fb_data->phy_lut_buf); + + sysfs_remove_group(&info->device->kobj, &spdc_attr_group); + unregister_framebuffer(info); + + if (fb_data->pdata->put_pins) + fb_data->pdata->put_pins(); + + free_irq(fb_data->spdc_irq, fb_data); + iounmap(fb_data->hwp); + + fb_dealloc_cmap(&info->cmap); + + framebuffer_release(info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int mxc_spdc_fb_suspend(struct platform_device *pdev, + pm_message_t state) +{ + mxc_spdc_t *data = platform_get_drvdata(pdev); + int ret; + + data->pwrdown_delay = FB_POWERDOWN_DISABLE; + ret = mxc_spdc_fb_blank(FB_BLANK_POWERDOWN, &data->info); + + return ret; +} + +static int mxc_spdc_fb_resume(struct platform_device *pdev) +{ + mxc_spdc_t *data = platform_get_drvdata(pdev); + + mxc_spdc_fb_blank(FB_BLANK_UNBLANK, &data->info); + spdc_init_sequence(data); + + return 0; +} +#else +#define mxc_spdc_fb_suspend NULL +#define mxc_spdc_fb_resume NULL +#endif + +static struct platform_driver mxc_spdc_fb_driver = { + .probe = mxc_spdc_fb_probe, + .remove = mxc_spdc_fb_remove, + .suspend = mxc_spdc_fb_suspend, + .resume = mxc_spdc_fb_resume, + .driver = { + .name = SPDC_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init mxc_spdc_fb_init(void) +{ + return platform_driver_register(&mxc_spdc_fb_driver); +} +late_initcall(mxc_spdc_fb_init); + +static void __exit mxc_spdc_fb_exit(void) +{ + platform_driver_unregister(&mxc_spdc_fb_driver); +} +module_exit(mxc_spdc_fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC SPDC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxc_spdc_fb.h b/drivers/video/mxc/mxc_spdc_fb.h new file mode 100644 index 00000000..34373788 --- /dev/null +++ b/drivers/video/mxc/mxc_spdc_fb.h @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 + * + */ + +#ifndef __MXC_SPDC_FB_H__ +#define __MXC_SPDC_FB_H__ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/dmaengine.h> +#include <linux/pxp_dma.h> +#include <linux/mxcfb.h> +#include <linux/list.h> +#include <linux/uaccess.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/regulator/driver.h> +#include <linux/dmaengine.h> +#include <linux/gpio.h> +#include <linux/kthread.h> +#include <mach/epdc.h> +#include <mach/dma.h> +#include <linux/platform_device.h> + +#include <linux/fsl_devices.h> +#include <linux/mxcfb.h> + +/************************************* +*Register addresses +*************************************/ +#define SPDC_DISP_TRIGGER (0x000) +#define SPDC_UPDATA_X_Y (0x004) +#define SPDC_UPDATE_W_H (0x008) +#define SPDC_LUT_PARA_UPDATE (0x00C) +#define SPDC_OPERATE (0x010) +#define SPDC_PANEL_INIT_SET (0x014) +#define SPDC_TEMP_INFO (0x018) +#define SPDC_NEXT_BUF (0x01C) +#define SPDC_CURRENT_BUF (0x020) +#define SPDC_PRE_BUF (0x024) +#define SPDC_CNT_BUF (0x028) +#define SPDC_LUT_BUF (0x02C) +#define SPDC_INT_ENABLE (0x030) +#define SPDC_INT_STA_CLR (0x034) +#define SPDC_INIT_STA_CLR (0x038) +#define SPDC_STATUS (0x03C) +#define SPDC_DISP_VER (0x040) +#define SPDC_TCON_VER (0x044) +#define SPDC_SW_GATE_CLK (0x048) + +/* + * Register field definitions + */ +enum { + SPDC_DISP_TRIGGER_ENABLE = 0x1, + SPDC_DISP_TRIGGER_FLASH = 0x10, + + /*SPDC clock gate*/ + SPDC_SW_GATE_CLK_ENABLE = 0x1, + + /* waveform mode mask */ + SPDC_WAV_MODE_MASK = (0x7 << 1), + + /* SPDC interrupt IRQ mask define */ + SPDC_IRQ_FRAME_UPDATE = (0x1 << 0), + SPDC_IRQ_TCON_INIT = (0x1 << 1), + SPDC_IRQ_LUT_DOWNLOAD = (0x1 << 2), + SPDC_IRQ_ERR = (0x1 << 3), + SPDC_IRQ_ALL_MASK = 0xF, + + /* SPDC interrupt status */ + SPDC_IRQ_STA_FRAME_UPDATE = (0x1 << 0), + SPDC_IRQ_STA_TCON_INIT = (0x1 << 1), + SPDC_IRQ_STA_LUT_DOWNLOAD = (0x1 << 2), + SPDC_IRQ_STA_ERR = (0x1 << 3), + SPDC_IRQ_STA_ALL_MASK = 0xF, + + /* SPDC update coordinate angle */ + SPDC_UPDATE_W_H_MAX_SIZE = ((0x1 << 12) - 1), + SPDC_UPDATE_X_Y_MAX_SIZE = ((0x1 << 12) - 1), + + /* SPDC TCON status */ + SPDC_PANEL_STAUTS_BUSY = 0x1, + SPDC_TCON_STATUS_IDLE = (0x4 << 4), + + /* SPDC EPD Operation mode */ + SPDC_NO_OPERATION = 0, + SPDC_DISP_REFRESH = 1, + SPDC_DEEP_REFRESH = 0x2, + SPDC_DISP_RESET = 0x4, + SPDC_SW_TCON_RESET = 0x5, + SPDC_SW_TCON_RESET_SET = 0x80000000, + SPDC_FULL_REFRESH = 0x6, + + /* SPDC Concurrency mechanism: ACC */ + SPDC_LUT_MODE_OFFSET = 0xb3, + SPDC_LUT_ACC_MODE = 0x4, + + /* SPDC waveform */ + SPDC_WAVEFORM_LUT_OFFSET_ADDR = 0x7600, + SPDC_LUT_PARA_LENTH = 0x100, + SPDC_LUT_FROM_MEM = 1, + SPDC_LUT_TO_MEM = 0, + + /* SPDC submit update status */ + SPDC_CONCUR_UPD = 0x4, + SPDC_QUEUE_UPD = 0x8, + SPDC_CONCUR_QUEUE = 0xc, +}; + +#define SPDC_DRIVER_NAME "imx_spdc_fb" + +/** + * SPDC EPD waveform display trigger mode + */ +enum { + SPDC_WAV_MODE_0 = 0, + SPDC_WAV_MODE_1, + SPDC_WAV_MODE_2, + SPDC_WAV_MODE_3, + SPDC_WAV_MODE_4, + SPDC_WAV_MODE_5, + SPDC_WAV_MODE_DEFAULT = SPDC_WAV_MODE_0, +}; + +/** + * SPDC controller ip version + */ +struct mxc_epd_disp_version { + u8 epd_type; + u8 lut_ver; + u16 product_id; +}; + +struct mxc_spdc_version { + struct mxc_epd_disp_version disp_ver; + u8 tcon_ver; +}; + +struct spdc_buffer_addr { + u32 next_buf_phys_addr; + u32 cur_buf_phys_addr; + u32 pre_buf_phys_addr; + u32 frm_cnt_buf_phys_addr; + u32 lut_buf_phys_addr; +}; + +struct partial_refresh_param { + struct spdc_buffer_addr buf_addr; + struct mxcfb_rect update_region; + int temper; + u32 blocking; + u32 wave_mode; + u32 stride; + u32 flash; /* only for waveform mode7 */ + u32 concur; /* Concurrency mechanism: ACC */ +}; + +/** + * SPDC lut data + */ +struct mxc_spdc_lut_para { + u8 lut_data[SPDC_LUT_PARA_LENTH]; + u8 lut_addr[SPDC_LUT_PARA_LENTH]; + bool lut_para_update_sta; +}; + +#define PORTRAIT "portrait" +#define LANDSCAPE "Landscape" +#define RESERVED "Reserved" +struct mxc_spdc_resolution_map_para { + u16 resolution; + u16 res_x; + u16 res_y; + char res_name[12]; +}; + +struct update_marker_data { + struct list_head full_list; + struct list_head upd_list; + u32 update_marker; + struct completion update_completion; + int lut_num; + bool collision_test; + bool waiting; +}; + +struct update_desc_list { + struct list_head list; + struct mxcfb_update_data upd_data; + u32 spdc_offs; + u32 spdc_stride; + struct list_head upd_marker_list; + u32 update_order; +}; + +/* This structure represents a list node containing both + * a memory region allocated as an output buffer for the PxP + * update processing task, and the update + * description (mode, region, etc.) + */ +struct update_data_list { + struct list_head list; + dma_addr_t phys_addr;/* Pointer to phys address of processed Y buf */ + void *virt_addr; + struct update_desc_list *update_desc; + int lut_num; + u64 collision_mask; /* SPDC cannot support collision detect, + * align with EPDC driver struct */ +}; + +typedef struct mxc_spdc_fb_param { + struct fb_info info; + struct fb_var_screeninfo spdc_fb_var; + + char fw_str[24]; + u32 pseudo_palette[16]; + struct mxc_spdc_version spdc_ver; + struct imx_spdc_fb_mode *cur_mode; + struct imx_spdc_fb_platform_data *pdata; + struct partial_refresh_param fresh_param; + int blank; + bool updates_active; + + u32 fb_offset; + u32 max_pix_size; + ssize_t map_size; + int native_width; + int native_height; + int default_bpp; + + dma_addr_t phys_start; + void *virt_start; + struct device *dev; + + int num_screens; + u32 order_cnt; + int max_num_updates; + u32 auto_mode; + u32 upd_scheme; + u32 operation_mode; + bool is_deep_fresh; + struct list_head full_marker_list; + struct list_head upd_pending_list; + struct list_head upd_buf_queue; + struct list_head upd_buf_free_list; + struct list_head upd_buf_preprocess_list; + u32 upd_preprocess_num; + int submit_upd_sta; + struct update_data_list *cur_update; + struct mutex queue_mutex; + struct imx_spdc_panel_init_set panel_set; + struct mxc_spdc_lut_para lut_para; + struct mxcfb_waveform_modes wv_modes; + + dma_addr_t phy_next_buf; + dma_addr_t phy_pre_buf; + dma_addr_t phy_current_buf; + dma_addr_t phy_cnt_buf; + dma_addr_t phy_lut_buf; + void *virt_next_buf; + void *virt_current_buf; + void *virt_pre_buf; + void *virt_cnt_buf; + void *virt_lut_buf; + u32 next_buf_size; + u32 current_buf_size; + u32 pre_buf_size; + u32 cnt_buf_size; + u32 lut_buf_size; + + dma_addr_t *phys_addr_updbuf; + void **virt_addr_updbuf; + u32 upd_buffer_num; + u32 max_num_buffers; + + /* copy the processed data to next buffer relative region */ + dma_addr_t phys_addr_copybuf; + void *virt_addr_copybuf; + + int spdc_irq; + char __iomem *hwp; + struct clk *spdc_clk_axi; + struct clk *spdc_clk_pix; + struct regulator *display_regulator; + struct regulator *vcom_regulator; + struct regulator *v3p3_regulator; + + spinlock_t lock; + int power_state; + bool powering_down; + int pwrdown_delay; + int wait_for_powerdown; + struct completion powerdown_compl; + struct mutex power_mutex; + bool fw_default_load; + bool hw_ready; + bool hw_initializing; + bool waiting_for_idle; + bool waiting_for_wb; + struct completion update_res_free; + struct completion lut_down; + struct completion init_finish; + struct completion update_finish; + struct completion updates_done; + struct delayed_work spdc_done_work; + struct workqueue_struct *spdc_submit_workqueue; + struct work_struct spdc_submit_work; + struct workqueue_struct *spdc_intr_workqueue; + struct work_struct spdc_intr_work; + + /* FB elements related to PxP DMA */ + struct completion pxp_tx_cmpl; + struct pxp_channel *pxp_chan; + struct pxp_config_data pxp_conf; + struct dma_async_tx_descriptor *txd; + dma_cookie_t cookie; + struct scatterlist sg[2]; + struct mutex pxp_mutex; /* protects access to PxP */ +} mxc_spdc_t; + +#endif diff --git a/drivers/video/mxc/mxcfb.c b/drivers/video/mxc/mxcfb.c new file mode 100644 index 00000000..4dffee5d --- /dev/null +++ b/drivers/video/mxc/mxcfb.c @@ -0,0 +1,1373 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/uaccess.h> +#include <asm/mach-types.h> + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + int blank; + ipu_channel_t ipu_ch; + uint32_t ipu_ch_irq; + uint32_t cur_ipu_buf; + + u32 pseudo_palette[16]; + + struct semaphore flip_sem; + spinlock_t fb_lock; +}; + +struct mxcfb_data { + struct fb_info *fbi; + struct fb_info *fbi_ovl; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; + int backlight_level; +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +static struct mxcfb_data mxcfb_drv_data; + +static char *fb_mode; +static unsigned long default_bpp = 16; +#ifdef CONFIG_FB_MXC_INTERNAL_MEM +static struct clk *iram_clk; +#endif +LIST_HEAD(fb_alloc_list); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +extern void gpio_lcd_active(void); +extern void gpio_lcd_inactive(void); +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + if (mxc_fbi->ipu_ch == MEM_SDC_FG) + strncpy(fix->id, "DISP3 FG", 8); + else + strncpy(fix->id, "DISP3 BG", 8); + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval; + bool use_iram = false; + u32 mem_len; + ipu_di_signal_cfg_t sig_cfg; + ipu_panel_t mode = IPU_PANEL_TFT; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + ipu_disable_irq(mxc_fbi->ipu_ch_irq); + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + mxcfb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (mem_len > fbi->fix.smem_len) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (mxc_fbi->ipu_ch == MEM_SDC_BG) { + use_iram = true; + } +#endif + if (mxcfb_map_video_memory(fbi, use_iram) < 0) + return -ENOMEM; + } + + ipu_init_channel(mxc_fbi->ipu_ch, NULL); + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + if (mxc_fbi->ipu_ch == MEM_SDC_BG) { + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + if (fbi->var.sync & FB_SYNC_SHARP_MODE) + mode = IPU_PANEL_SHARP_TFT; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_sdc_init_panel(mode, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + (fbi->var.sync & FB_SYNC_SWAP_RGB) ? + IPU_PIX_FMT_BGR666 : IPU_PIX_FMT_RGB666, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + + fbi->mode = + (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + } + + ipu_disp_set_window_pos(mxc_fbi->ipu_ch, 0, 0); + + mxc_fbi->cur_ipu_buf = 1; + sema_init(&mxc_fbi->flip_sem, 1); + fbi->var.xoffset = fbi->var.yoffset = 0; + + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + fbi->var.xres, fbi->var.yres, + fbi->var.xres_virtual, + IPU_ROTATE_NONE, + fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres), + fbi->fix.smem_start, + 0, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { + ipu_enable_channel(mxc_fbi->ipu_ch); + } + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if ((info->fix.smem_start == FB_RAM_BASE_ADDR) && + ((var->yres_virtual * var->xres_virtual * var->bits_per_pixel / 8) > + FB_RAM_SIZE)) { + return -EINVAL; + } +#endif + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* nonstd used for YUV formats, but only RGB supported */ + var->nonstd = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false)); + if (retval < 0) + return retval; + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + retval = + ipu_sdc_set_global_alpha((bool) ga.enable, + ga.alpha); + dev_dbg(fbi->device, "Set global alpha to %d\n", + ga.alpha); + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_sdc_set_color_key(MEM_SDC_BG, key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { +#ifndef CONFIG_ARCH_MX3 + mxcfb_drv_data.vsync_flag = 0; + ipu_enable_irq(IPU_IRQ_SDC_DISP3_VSYNC); + if (!wait_event_interruptible_timeout + (mxcfb_drv_data.vsync_wq, + mxcfb_drv_data.vsync_flag != 0, 1 * HZ)) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } +#endif + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl_ovl(struct fb_info *fbi, unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + switch (cmd) { + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) + return -EFAULT; + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_DMA); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + if (put_user(mem->phy_addr, argp)) + return -EFAULT; + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_window_pos(mxc_fbi->ipu_ch, + pos.x, pos.y); + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + int retval; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(MEM_SDC_BG, true); + gpio_lcd_inactive(); + break; + case FB_BLANK_UNBLANK: + gpio_lcd_active(); + ipu_enable_channel(MEM_SDC_BG); + break; + } + return 0; +} + +/* + * mxcfb_blank_ovl(): + * Blank the display. + */ +static int mxcfb_blank_ovl(int blank, struct fb_info *info) +{ + int retval; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "ovl blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(MEM_SDC_FG, true); + break; + case FB_BLANK_UNBLANK: + ipu_enable_channel(MEM_SDC_FG); + break; + } + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + unsigned long lock_flags = 0; + int retval; + u_int y_bottom; + unsigned long base; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + if (var->xoffset > 0) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) { + /* No change, do nothing */ + return 0; + } + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) { + y_bottom += var->yres; + } + + if (y_bottom > info->var.yres_virtual) { + return -EINVAL; + } + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + down(&mxc_fbi->flip_sem); + + spin_lock_irqsave(&mxc_fbi->fb_lock, lock_flags); + + dev_dbg(info->device, "Updating SDC BG buf %d address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, base) == 0) { + ipu_select_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + } + + spin_unlock_irqrestore(&mxc_fbi->fb_lock, lock_flags); + + dev_dbg(info->device, "Update complete\n"); + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) { + info->var.vmode |= FB_VMODE_YWRAP; + } else { + info->var.vmode &= ~FB_VMODE_YWRAP; + } + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) { + return -EINVAL; + } + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) { + return -EINVAL; + } + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static struct fb_ops mxcfb_ovl_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl_ovl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank_ovl, +}; + +static irqreturn_t mxcfb_vsync_irq_handler(int irq, void *dev_id) +{ + struct mxcfb_data *fb_data = dev_id; + + ipu_disable_irq(irq); + + fb_data->vsync_flag = 1; + wake_up_interruptible(&fb_data->vsync_wq); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + up(&mxc_fbi->flip_sem); + ipu_disable_irq(irq); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par; + struct mxcfb_info *mxc_fbi_ovl = + (struct mxcfb_info *)drv_data->fbi_ovl->par; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + drv_data->suspended = true; + + console_lock(); + fb_set_suspend(drv_data->fbi, 1); + fb_set_suspend(drv_data->fbi_ovl, 1); + console_unlock(); + + if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) { + ipu_disable_channel(MEM_SDC_FG, true); + } + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) { + fbmem = ioremap(FB_RAM_BASE_ADDR, FB_RAM_SIZE); + memcpy(fbmem, drv_data->fbi->screen_base, FB_RAM_SIZE); + iounmap(fbmem); + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + FB_RAM_BASE_ADDR); + ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + } + ipu_lowpwr_display_enable(); +#else + ipu_disable_channel(MEM_SDC_BG, true); + gpio_lcd_inactive(); +#endif + } + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par; + struct mxcfb_info *mxc_fbi_ovl = + (struct mxcfb_info *)drv_data->fbi_ovl->par; + + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + ipu_lowpwr_display_disable(); + if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) { + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + drv_data->fbi->fix. + smem_start); + ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + } +#else + gpio_lcd_active(); + ipu_enable_channel(MEM_SDC_BG); +#endif + } + + if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) { + ipu_enable_channel(MEM_SDC_FG); + } + + console_lock(); + fb_set_suspend(drv_data->fbi, 0); + fb_set_suspend(drv_data->fbi_ovl, 0); + console_unlock(); + + wake_up_interruptible(&drv_data->suspend_wq); + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @param use_internal_ram flag on whether to use internal RAM for memory + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram) +{ + int retval = 0; + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (use_internal_ram) { + fbi->fix.smem_len = FB_RAM_SIZE; + fbi->fix.smem_start = FB_RAM_BASE_ADDR; + if (fbi->fix.smem_len < + (fbi->var.yres_virtual * fbi->fix.line_length)) { + dev_err(fbi->device, + "Not enough internal RAM for framebuffer configuration\n"); + retval = -EINVAL; + goto err0; + } + + if (request_mem_region(fbi->fix.smem_start, fbi->fix.smem_len, + fbi->device->driver->name) == NULL) { + dev_err(fbi->device, + "Unable to request internal RAM\n"); + retval = -ENOMEM; + goto err0; + } + + fbi->screen_base = ioremap(fbi->fix.smem_start, + fbi->fix.smem_len); + if (!fbi->screen_base) { + dev_err(fbi->device, + "Unable to map fb memory to virtual address\n"); + release_mem_region(fbi->fix.smem_start, + fbi->fix.smem_len); + retval = -EIO; + goto err0; + } + + iram_clk = clk_get(NULL, "iram_clk"); + clk_enable(iram_clk); + } else +#endif + { + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + fbi->screen_base = + dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *) &fbi->fix.smem_start, + GFP_DMA); + + if (fbi->screen_base == 0) { + dev_err(fbi->device, + "Unable to allocate framebuffer memory\n"); + retval = -EBUSY; + goto err0; + } + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; + + err0: + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + fbi->screen_base = NULL; + return retval; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (fbi->fix.smem_start == FB_RAM_BASE_ADDR) { + iounmap(fbi->screen_base); + release_mem_region(fbi->fix.smem_start, fbi->fix.smem_len); + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + clk_disable(iram_clk); + } else +#endif + { + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + } + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + spin_lock_init(&mxcfbi->fb_lock); + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + char *mode = pdev->dev.platform_data; + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct fb_info *fbi_ovl; + int ret = 0; + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfbi = (struct mxcfb_info *)fbi->par; + + mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_BG_EOF; + mxcfbi->cur_ipu_buf = 0; + mxcfbi->ipu_ch = MEM_SDC_BG; + + ipu_sdc_set_global_alpha(true, 0xFF); + ipu_sdc_set_color_key(MEM_SDC_BG, false, 0); + + if (ipu_request_irq(IPU_IRQ_SDC_BG_EOF, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering BG irq handler.\n"); + ret = -EBUSY; + goto err1; + } + ipu_disable_irq(IPU_IRQ_SDC_BG_EOF); + + if (fb_mode == NULL) { + fb_mode = mode; + } + + if (!fb_find_mode(&fbi->var, fbi, fb_mode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp)) { + ret = -EBUSY; + goto err2; + } + fb_videomode_to_modelist(mxcfb_modedb, mxcfb_modedb_sz, &fbi->modelist); + + /* Default Y virtual size is 2x panel size */ +#ifndef CONFIG_FB_MXC_INTERNAL_MEM + fbi->var.yres_virtual = fbi->var.yres * 2; +#endif + + mxcfb_drv_data.fbi = fbi; + mxcfb_drv_data.backlight_level = 255; + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + mxcfbi->blank = FB_BLANK_NORMAL; + ret = mxcfb_set_par(fbi); + if (ret < 0) { + goto err2; + } + mxcfb_blank(FB_BLANK_UNBLANK, fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + /* + * Initialize Overlay FB structures + */ + fbi_ovl = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ovl_ops); + if (!fbi_ovl) { + ret = -ENOMEM; + goto err3; + } + mxcfb_drv_data.fbi_ovl = fbi_ovl; + mxcfbi = (struct mxcfb_info *)fbi_ovl->par; + + mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_FG_EOF; + mxcfbi->cur_ipu_buf = 0; + mxcfbi->ipu_ch = MEM_SDC_FG; + + if (ipu_request_irq(IPU_IRQ_SDC_FG_EOF, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi_ovl) != 0) { + dev_err(fbi->device, "Error registering FG irq handler.\n"); + ret = -EBUSY; + goto err4; + } + ipu_disable_irq(mxcfbi->ipu_ch_irq); + + /* Default Y virtual size is 2x panel size */ + fbi_ovl->var = fbi->var; + fbi_ovl->var.yres_virtual = fbi->var.yres * 2; + + /* Overlay is blanked by default */ + mxcfbi->blank = FB_BLANK_NORMAL; + + ret = mxcfb_set_par(fbi_ovl); + if (ret < 0) { + goto err5; + } + + /* + * Register overlay framebuffer + */ + ret = register_framebuffer(fbi_ovl); + if (ret < 0) { + goto err5; + } + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + init_waitqueue_head(&mxcfb_drv_data.vsync_wq); + if (!cpu_is_mx31() && !cpu_is_mx32()) { + ret = ipu_request_irq(IPU_IRQ_SDC_DISP3_VSYNC, + mxcfb_vsync_irq_handler, + 0, MXCFB_NAME, + &mxcfb_drv_data); + if (ret < 0) + goto err6; + ipu_disable_irq(IPU_IRQ_SDC_DISP3_VSYNC); + } + + printk(KERN_INFO "mxcfb: fb registered, using mode %s\n", fb_mode); + return 0; + + err6: + unregister_framebuffer(fbi_ovl); + err5: + ipu_free_irq(IPU_IRQ_SDC_FG_EOF, fbi_ovl); + err4: + fb_dealloc_cmap(&fbi_ovl->cmap); + framebuffer_release(fbi_ovl); + err3: + unregister_framebuffer(fbi); + err2: + ipu_free_irq(IPU_IRQ_SDC_BG_EOF, fbi); + err1: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + printk(KERN_ERR "mxcfb: failed to register fb\n"); + return ret; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=trident:800x600,bpp=16,noaccel + */ +int mxcfb_setup(char *options) +{ + char *opt; + if (!options || !*options) + return 0; + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + return 0; +} + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + int ret = 0; +#ifndef MODULE + char *option = NULL; +#endif + +#ifndef MODULE + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mxcfb_setup(option); +#endif + + ret = platform_driver_register(&mxcfb_driver); + return ret; +} + +void mxcfb_exit(void) +{ + struct fb_info *fbi = mxcfb_drv_data.fbi; + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + fbi = mxcfb_drv_data.fbi_ovl; + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } +#ifndef CONFIG_ARCH_MX3 + ipu_free_irq(IPU_IRQ_SDC_DISP3_VSYNC, &mxcfb_drv_data); +#endif + + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_ch7026.c b/drivers/video/mxc/mxcfb_ch7026.c new file mode 100644 index 00000000..6668b88b --- /dev/null +++ b/drivers/video/mxc/mxcfb_ch7026.c @@ -0,0 +1,370 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_epson_vga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/mxcfb.h> +#include <linux/ipu.h> +#include <linux/fsl_devices.h> +#include <mach/hardware.h> + +static struct i2c_client *ch7026_client; + +static int lcd_init(void); +static void lcd_poweron(struct fb_info *info); +static void lcd_poweroff(void); + +static void (*lcd_reset) (void); +static struct regulator *io_reg; +static struct regulator *core_reg; +static struct regulator *analog_reg; + + /* 8 800x600-60 VESA */ +static struct fb_videomode mode = { + NULL, 60, 800, 600, 25000, 88, 40, 23, 1, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &mode); + + var.activate = FB_ACTIVATE_ALL; + + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + fb_blank(info, FB_BLANK_UNBLANK); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG - DI1")) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + lcd_poweron(event->info); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) + lcd_poweron(event->info); + else + lcd_poweroff(); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct device *dev) +{ + int ret = 0; + int i; + struct mxc_lcd_platform_data *plat = dev->platform_data; + + if (plat) { + + io_reg = regulator_get(dev, plat->io_reg); + if (!IS_ERR(io_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + regulator_enable(io_reg); + } else { + io_reg = NULL; + } + + core_reg = regulator_get(dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(core_reg, 2500000, 2500000); + regulator_enable(core_reg); + } else { + core_reg = NULL; + } + analog_reg = regulator_get(dev, plat->analog_reg); + if (!IS_ERR(analog_reg)) { + regulator_set_voltage(analog_reg, 2775000, 2775000); + regulator_enable(analog_reg); + } else { + analog_reg = NULL; + } + msleep(100); + + lcd_reset = plat->reset; + if (lcd_reset) + lcd_reset(); + } + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG - DI1") == 0) { + ret = lcd_init(); + if (ret < 0) + goto err; + + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(registered_fb[i]); + } + } + + fb_register_client(&nb); + return 0; +err: + if (io_reg) + regulator_disable(io_reg); + if (core_reg) + regulator_disable(core_reg); + if (analog_reg) + regulator_disable(analog_reg); + + return ret; +} + +static int __devinit ch7026_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + ch7026_client = client; + + return lcd_probe(&client->dev); +} + +static int __devexit ch7026_remove(struct i2c_client *client) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + regulator_put(io_reg); + regulator_put(core_reg); + regulator_put(analog_reg); + + return 0; +} + +static int ch7026_suspend(struct i2c_client *client, pm_message_t message) +{ + return 0; +} + +static int ch7026_resume(struct i2c_client *client) +{ + return 0; +} + +u8 reg_init[][2] = { + { 0x02, 0x01 }, + { 0x02, 0x03 }, + { 0x03, 0x00 }, + { 0x06, 0x6B }, + { 0x08, 0x08 }, + { 0x09, 0x80 }, + { 0x0C, 0x0A }, + { 0x0D, 0x89 }, + { 0x0F, 0x23 }, + { 0x10, 0x20 }, + { 0x11, 0x20 }, + { 0x12, 0x40 }, + { 0x13, 0x28 }, + { 0x14, 0x80 }, + { 0x15, 0x52 }, + { 0x16, 0x58 }, + { 0x17, 0x74 }, + { 0x19, 0x01 }, + { 0x1A, 0x04 }, + { 0x1B, 0x23 }, + { 0x1C, 0x20 }, + { 0x1D, 0x20 }, + { 0x1F, 0x28 }, + { 0x20, 0x80 }, + { 0x21, 0x12 }, + { 0x22, 0x58 }, + { 0x23, 0x74 }, + { 0x25, 0x01 }, + { 0x26, 0x04 }, + { 0x37, 0x20 }, + { 0x39, 0x20 }, + { 0x3B, 0x20 }, + { 0x41, 0xA2 }, + { 0x4D, 0x03 }, + { 0x4E, 0x13 }, + { 0x4F, 0xB1 }, + { 0x50, 0x3B }, + { 0x51, 0x54 }, + { 0x52, 0x12 }, + { 0x53, 0x13 }, + { 0x55, 0xE5 }, + { 0x5E, 0x80 }, + { 0x69, 0x64 }, + { 0x7D, 0x62 }, + { 0x04, 0x00 }, + { 0x06, 0x69 }, + + /* + NOTE: The following five repeated sentences are used here to wait memory initial complete, please don't remove...(you could refer to Appendix A of programming guide document (CH7025(26)B Programming Guide Rev2.03.pdf) for detailed information about memory initialization! + */ + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + + { 0x06, 0x68 }, + { 0x02, 0x02 }, + { 0x02, 0x03 }, +}; + +#define REGMAP_LENGTH (sizeof(reg_init) / (2*sizeof(u8))) + +/* + * Send init commands to L4F00242T03 + * + */ +static int lcd_init(void) +{ + int i; + int dat; + + dev_dbg(&ch7026_client->dev, "initializing CH7026\n"); + + /* read device ID */ + msleep(100); + dat = i2c_smbus_read_byte_data(ch7026_client, 0x00); + dev_dbg(&ch7026_client->dev, "read id = 0x%02X\n", dat); + if (dat != 0x54) + return -ENODEV; + + for (i = 0; i < REGMAP_LENGTH; ++i) { + if (i2c_smbus_write_byte_data + (ch7026_client, reg_init[i][0], reg_init[i][1]) < 0) + return -EIO; + } + + return 0; +} + +static int lcd_on; +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(struct fb_info *info) +{ + u16 data[4]; + u32 refresh; + + if (lcd_on) + return; + + dev_dbg(&ch7026_client->dev, "turning on LCD\n"); + + data[0] = PICOS2KHZ(info->var.pixclock) / 10; + data[2] = info->var.hsync_len + info->var.left_margin + + info->var.xres + info->var.right_margin; + data[3] = info->var.vsync_len + info->var.upper_margin + + info->var.yres + info->var.lower_margin; + + refresh = data[2] * data[3]; + refresh = (PICOS2KHZ(info->var.pixclock) * 1000) / refresh; + data[1] = refresh * 100; + + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + if (!lcd_on) + return; + + dev_dbg(&ch7026_client->dev, "turning off LCD\n"); + + lcd_on = 0; +} + +static const struct i2c_device_id ch7026_id[] = { + {"ch7026", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ch7026_id); + +static struct i2c_driver ch7026_driver = { + .driver = { + .name = "ch7026", + }, + .probe = ch7026_probe, + .remove = ch7026_remove, + .suspend = ch7026_suspend, + .resume = ch7026_resume, + .id_table = ch7026_id, +}; + +static int __init ch7026_init(void) +{ + return i2c_add_driver(&ch7026_driver); +} + +static void __exit ch7026_exit(void) +{ + i2c_del_driver(&ch7026_driver); +} + +module_init(ch7026_init); +module_exit(ch7026_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CH7026 VGA driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_claa_wvga.c b/drivers/video/mxc/mxcfb_claa_wvga.c new file mode 100644 index 00000000..3dbad0dc --- /dev/null +++ b/drivers/video/mxc/mxcfb_claa_wvga.c @@ -0,0 +1,240 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_claa_wvga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/fsl_devices.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> + +static void lcd_poweron(void); +static void lcd_poweroff(void); + +static struct platform_device *plcd_dev; +static struct regulator *io_reg; +static struct regulator *core_reg; +static int lcd_on; + +static struct fb_videomode video_modes[] = { + { + /* 800x480 @ 57 Hz , pixel clk @ 27MHz */ + "CLAA-WVGA", 57, 800, 480, 37037, 40, 60, 10, 10, 20, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &video_modes[0]); + + var.activate = FB_ACTIVATE_ALL; + var.yres_virtual = var.yres * 3; + + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG") && + strcmp(event->info->fix.id, "mxc_elcdif_fb")) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + fb_show_logo(event->info, 0); + lcd_poweron(); + break; + case FB_EVENT_BLANK: + if ((event->info->var.xres != 800) || + (event->info->var.yres != 480)) { + break; + } + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + lcd_poweron(); + } else { + lcd_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct platform_device *pdev) +{ + int i; + struct mxc_lcd_platform_data *plat = pdev->dev.platform_data; + + if (plat) { + if (plat->reset) + plat->reset(); + + io_reg = regulator_get(&pdev->dev, plat->io_reg); + if (IS_ERR(io_reg)) + io_reg = NULL; + core_reg = regulator_get(&pdev->dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + } else { + core_reg = NULL; + } + } + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0 || + strcmp(registered_fb[i]->fix.id, "mxc_elcdif_fb") == 0) { + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(); + } else if (strcmp(registered_fb[i]->fix.id, "DISP3 FG") == 0) { + lcd_init_fb(registered_fb[i]); + } + } + + fb_register_client(&nb); + + plcd_dev = pdev; + + return 0; +} + +static int __devexit lcd_remove(struct platform_device *pdev) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + if (io_reg) + regulator_put(io_reg); + if (core_reg) + regulator_put(core_reg); + + return 0; +} + +#ifdef CONFIG_PM +static int lcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int lcd_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define lcd_suspend NULL +#define lcd_resume NULL +#endif + +/*! + * platform driver structure for CLAA WVGA + */ +static struct platform_driver lcd_driver = { + .driver = { + .name = "lcd_claa"}, + .probe = lcd_probe, + .remove = __devexit_p(lcd_remove), + .suspend = lcd_suspend, + .resume = lcd_resume, +}; + +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(void) +{ + if (lcd_on) + return; + + dev_dbg(&plcd_dev->dev, "turning on LCD\n"); + if (core_reg) + regulator_enable(core_reg); + if (io_reg) + regulator_enable(io_reg); + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + lcd_on = 0; + dev_dbg(&plcd_dev->dev, "turning off LCD\n"); + if (io_reg) + regulator_disable(io_reg); + if (core_reg) + regulator_disable(core_reg); +} + +static int __init claa_lcd_init(void) +{ + return platform_driver_register(&lcd_driver); +} + +static void __exit claa_lcd_exit(void) +{ + platform_driver_unregister(&lcd_driver); +} + +module_init(claa_lcd_init); +module_exit(claa_lcd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CLAA WVGA LCD init driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_epson.c b/drivers/video/mxc/mxcfb_epson.c new file mode 100644 index 00000000..f7d191fa --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson.c @@ -0,0 +1,1153 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxcfb_epson.c + * + * @brief MXC Frame buffer driver for ADC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <mach/hardware.h> +#include <asm/io.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <mach/ipu.h> +#include <mach/mxcfb.h> + +#define PARTIAL_REFRESH +#define MXCFB_REFRESH_DEFAULT MXCFB_REFRESH_PARTIAL +/* + * Driver name + */ +#define MXCFB_NAME "MXCFB_EPSON" + +#define MXCFB_SCREEN_TOP_OFFSET 0 +#define MXCFB_SCREEN_LEFT_OFFSET 2 +#define MXCFB_SCREEN_WIDTH 176 +#define MXCFB_SCREEN_HEIGHT 220 + +/*! + * Enum defining Epson panel commands. + */ +enum { + DISON = 0xAF, + DISOFF = 0xAE, + DISCTL = 0xCA, + SD_CSET = 0x15, + SD_PSET = 0x75, + DATCTL = 0xBC, + SLPIN = 0x95, + SLPOUT = 0x94, + DISNOR = 0xA6, + RAMWR = 0x5C, + VOLCTR = 0xC6, + GCP16 = 0xCC, + GCP64 = 0xCB, +}; + +struct mxcfb_info { + int open_count; + int blank; + uint32_t disp_num; + + u32 pseudo_palette[16]; + + int32_t cur_update_mode; + dma_addr_t alloc_start_paddr; + void *alloc_start_vaddr; + u32 alloc_size; + uint32_t snoop_window_size; +}; + +struct mxcfb_data { + struct fb_info *fbi; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; +}; + +static struct mxcfb_data mxcfb_drv_data; +static unsigned long default_bpp = 16; + +void slcd_gpio_config(void); +extern void gpio_lcd_active(void); +static int mxcfb_blank(int blank, struct fb_info *fbi); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +/*! + * This function sets display region in the Epson panel + * + * @param disp display panel to config + * @param x1 x-coordinate of one vertex. + * @param x2 x-coordinate of second vertex. + * @param y1 y-coordinate of one vertex. + * @param y2 y-coordinate of second vertex. + */ +void set_panel_region(int disp, uint32_t x1, uint32_t x2, + uint32_t y1, uint32_t y2) +{ + uint32_t param[8]; + + memset(param, 0, sizeof(uint32_t) * 8); + param[0] = x1; + param[2] = x2; + param[4] = y1; + param[6] = y2; + + /* SD_CSET */ + ipu_adc_write_cmd(disp, CMD, SD_CSET, param, 4); + + /* SD_PSET */ + ipu_adc_write_cmd(disp, CMD, SD_PSET, &(param[4]), 4); +} + +/*! + * Function to create and initiate template command buffer for ADC. This + * template will be written to Panel memory. + */ +static void init_channel_template(int disp) +{ + /* template command buffer for ADC is 32 */ + uint32_t tempCmd[TEMPLATE_BUF_SIZE]; + uint32_t i = 0; + + memset(tempCmd, 0, sizeof(uint32_t) * TEMPLATE_BUF_SIZE); + /* setup update display region */ + /* whole the screen during init */ + /*WRITE Y COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_PSET); + /*WRITE Y START ADDRESS CMND LSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x01); + /*WRITE Y START ADDRESS CMND MSB[22:16] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x09); + /*WRITE Y STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_HEIGHT - 1); + /*WRITE Y STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_CSET); + /*WRITE X ADDRESS CMND LSB[7:0] */ + tempCmd[i++] = ipu_adc_template_gen(WR_XADDR, 1, SINGLE_STEP, 0x01); + /*WRITE X ADDRESS CMND MSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_WIDTH + 1); + /*WRITE X STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE MEMORY CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, RAMWR); + /*WRITE DATA CMND and STP */ + tempCmd[i++] = ipu_adc_template_gen(WR_DATA, 1, STOP, 0); + + ipu_adc_write_template(disp, tempCmd, true); +} + +/*! + * Function to initialize the panel. First it resets the panel and then + * initilizes panel. + */ +static void _init_panel(int disp) +{ + uint32_t cmd_param; + uint32_t i; + + gpio_lcd_active(); + slcd_gpio_config(); + + /* DATCTL */ +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + /* 16-bit 565 mode */ + cmd_param = 0x28; +#else + /* 8-bit 666 mode */ + cmd_param = 0x08; +#endif + ipu_adc_write_cmd(disp, CMD, DATCTL, &cmd_param, 1); + + /* Sleep OUT */ + ipu_adc_write_cmd(disp, CMD, SLPOUT, 0, 0); + + /* Set display to white + Setup page and column addresses */ + set_panel_region(disp, MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET - 1, + 0, MXCFB_SCREEN_HEIGHT - 1); + /* Do RAM write cmd */ + ipu_adc_write_cmd(disp, CMD, RAMWR, 0, 0); +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT); i++) +#else + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT * 3); i++) +#endif + ipu_adc_write_cmd(disp, DAT, 0xFFFF, 0, 0); + + /* Pause 80 ms */ + mdelay(80); + + /* Display ON */ + ipu_adc_write_cmd(disp, CMD, DISON, 0, 0); + /* Pause 200 ms */ + mdelay(200); + + pr_debug("initialized panel\n"); +} + +#ifdef PARTIAL_REFRESH +static irqreturn_t mxcfb_sys2_eof_irq_handler(int irq, void *dev_id) +{ + ipu_channel_params_t params; + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + uint32_t stat[2], seg_size; + uint32_t lsb, msb; + uint32_t update_height, start_line, start_addr, end_line, end_addr; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + + ipu_adc_get_snooping_status(&stat[0], &stat[1]); + + if (!stat[0] && !stat[1]) { + dev_err(fbi->device, "error no bus snooping bits set\n"); + return IRQ_HANDLED; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + + lsb = ffs(stat[0]); + if (lsb) { + lsb--; + } else { + lsb = ffs(stat[1]); + lsb += 32 - 1; + } + msb = fls(stat[1]); + if (msb) { + msb += 32; + } else { + msb = fls(stat[0]); + } + + seg_size = mxc_fbi->snoop_window_size / 64; + + start_addr = lsb * seg_size; /* starting address offset */ + start_line = start_addr / fbi->fix.line_length; + start_addr = start_line * fbi->fix.line_length; /* Addr aligned to line */ + start_addr += fbi->fix.smem_start; + + end_addr = msb * seg_size; /* ending address offset */ + end_line = end_addr / fbi->fix.line_length; + end_line++; + + if (end_line > fbi->var.yres) { + end_line = fbi->var.yres; + } + + update_height = end_line - start_line; + dev_dbg(fbi->device, "updating rows %d to %d, start addr = 0x%08X\n", + start_line, end_line, start_addr); + + ipu_uninit_channel(ADC_SYS1); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = start_line; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, + update_height, + stride_pixels, + IPU_ROTATE_NONE, (dma_addr_t) start_addr, 0, + 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_sys1_eof_irq_handler(int irq, void *dev_id) +{ + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_channel(ADC_SYS1, false); + + ipu_enable_channel(ADC_SYS2); + ipu_enable_irq(IPU_IRQ_ADC_SYS2_EOF); + + return IRQ_HANDLED; +} +#endif + +/*! + * Function to initialize Asynchronous Display Controller. It also initilizes + * the ADC System 1 channel. Configure ADC display 0 parallel interface for + * the panel. + * + * @param fbi framebuffer information pointer + */ +static void mxcfb_init_panel(struct fb_info *fbi) +{ + int msb; + int panel_stride; + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#elif defined(CONFIG_FB_MXC_ASYNC_PANEL_IFC_8_BIT) + uint32_t pix_fmt = IPU_PIX_FMT_RGB666; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 8, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#else + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 1, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_SERIAL, + IPU_ADC_IFC_MODE_5WIRE_SERIAL_CLK, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + fbi->disp_num = DISP1; +#endif + +#ifdef PARTIAL_REFRESH + if (ipu_request_irq(IPU_IRQ_ADC_SYS2_EOF, mxcfb_sys2_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS2 irq handler.\n"); + return; + } + + if (ipu_request_irq(IPU_IRQ_ADC_SYS1_EOF, mxcfb_sys1_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS1 irq handler.\n"); + return; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + /* Init DI interface */ + msb = fls(MXCFB_SCREEN_WIDTH); + if (!(MXCFB_SCREEN_WIDTH & ((1UL << msb) - 1))) + msb--; /* Already aligned to power 2 */ + panel_stride = 1UL << msb; + ipu_adc_init_panel(mxc_fbi->disp_num, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_HEIGHT, + pix_fmt, panel_stride, sig, XY, 0, VsyncInternal); + + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, true, + 190, 17, 104, 190, 5000000); + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, false, 123, 17, 68, 0, 0); + + /* Needed to turn on ADC clock for panel init */ + memset(¶ms, 0, sizeof(params)); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + _init_panel(mxc_fbi->disp_num); + init_channel_template(mxc_fbi->disp_num); +} + +int mxcfb_set_refresh_mode(struct fb_info *fbi, int mode, + struct mxcfb_rect *update_region) +{ + unsigned long start_addr; + int ret_mode; + uint32_t dummy; + ipu_channel_params_t params; + struct mxcfb_rect rect; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + uint32_t memsize = fbi->fix.smem_len; + + if (mxc_fbi->cur_update_mode == mode) + return mode; + + ret_mode = mxc_fbi->cur_update_mode; + + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#ifdef PARTIAL_REFRESH + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#endif + + ipu_disable_channel(ADC_SYS1, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS1_EOF); +#ifdef PARTIAL_REFRESH + ipu_disable_channel(ADC_SYS2, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + ipu_adc_get_snooping_status(&dummy, &dummy); + + mxc_fbi->cur_update_mode = mode; + + switch (mode) { + case MXCFB_REFRESH_OFF: + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + if (ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); +#if 0 + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, + fbi->fix.smem_start, 0, 0); + ipu_enable_channel(ADC_SYS2); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 1); + msleep(10); +#endif + ipu_uninit_channel(ADC_SYS1); +#ifdef PARTIAL_REFRESH + ipu_uninit_channel(ADC_SYS2); +#endif + break; + case MXCFB_REFRESH_PARTIAL: +#ifdef PARTIAL_REFRESH + ipu_adc_get_snooping_status(&dummy, &dummy); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = 0; + params.adc_sys2.out_top = 0; + ipu_init_channel(ADC_SYS2, ¶ms); + + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + if (ipu_adc_set_update_mode + (ADC_SYS2, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + mxc_fbi->snoop_window_size = memsize; + + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, MXCFB_SCREEN_HEIGHT, + stride_pixels, IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + break; +#endif + case MXCFB_REFRESH_AUTO: + if (update_region == NULL) { + update_region = ▭ + rect.top = 0; + rect.left = 0; + rect.height = MXCFB_SCREEN_HEIGHT; + rect.width = MXCFB_SCREEN_WIDTH; + } + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET + + update_region->left; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET + + update_region->top; + ipu_init_channel(ADC_SYS1, ¶ms); + + /* Address aligned to line */ + start_addr = update_region->top * fbi->fix.line_length; + start_addr += fbi->fix.smem_start; + start_addr += update_region->left * fbi->var.bits_per_pixel / 8; + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + update_region->width, + update_region->height, stride_pixels, + IPU_ROTATE_NONE, start_addr, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + + if (ipu_adc_set_update_mode + (ADC_SYS1, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + + mxc_fbi->snoop_window_size = memsize; + + break; + } + return ret_mode; +} + +/* + * Open the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_open(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mxc_fbi->open_count++; + + retval = mxcfb_blank(FB_BLANK_UNBLANK, fbi); + return retval; +} + +/* + * Close the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_release(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + --mxc_fbi->open_count; + if (mxc_fbi->open_count == 0) { + retval = mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + } + return retval; +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + /* Set framebuffer id to IPU display number. */ + strcpy(fix->id, "DISP0 FB"); + fix->id[4] = '0' + mxc_fbi->disp_num; + + /* Init settings based on the panel size */ + fix->line_length = MXCFB_SCREEN_WIDTH * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ypanstep = 0; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + int mode; + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mode = mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + + mxcfb_set_fix(fbi); + + if (mode != MXCFB_REFRESH_OFF) { +#ifdef PARTIAL_REFRESH + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_PARTIAL, NULL); +#else + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_AUTO, NULL); +#endif + } + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + if (var->xres > MXCFB_SCREEN_WIDTH) + var->xres = MXCFB_SCREEN_WIDTH; + if (var->yres > MXCFB_SCREEN_HEIGHT) + var->yres = MXCFB_SCREEN_HEIGHT; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + var->nonstd = 0; + + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + dev_dbg(fbi->device, "blank = %d\n", blank); + + retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == false)); + if (retval < 0) + return retval; + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + break; + } + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_open = mxcfb_open, + .fb_release = mxcfb_release, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + u32 msb; + u32 offset; + struct mxcfb_info *mxcfbi = fbi->par; + + fbi->fix.smem_len = fbi->var.xres_virtual * fbi->var.yres_virtual * 4; + + /* Set size to power of 2. */ + msb = fls(fbi->fix.smem_len); + if (!(fbi->fix.smem_len & ((1UL << msb) - 1))) + msb--; /* Already aligned to power 2 */ + if (msb < 11) + msb = 11; + mxcfbi->alloc_size = (1UL << msb) * 2; + + mxcfbi->alloc_start_vaddr = dma_alloc_coherent(fbi->device, + mxcfbi->alloc_size, + &mxcfbi-> + alloc_start_paddr, + GFP_KERNEL | GFP_DMA); + + if (mxcfbi->alloc_start_vaddr == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + return -ENOMEM; + } + dev_dbg(fbi->device, "allocated fb memory @ paddr=0x%08X, size=%d.\n", + (uint32_t) mxcfbi->alloc_start_paddr, mxcfbi->alloc_size); + + offset = + ((mxcfbi->alloc_size / 2) - 1) & ~((mxcfbi->alloc_size / 2) - 1); + fbi->fix.smem_start = mxcfbi->alloc_start_paddr + offset; + dev_dbg(fbi->device, "aligned fb start @ paddr=0x%08lX, size=%u.\n", + fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_base = mxcfbi->alloc_start_vaddr + offset; + + /* Clear the screen */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dma_free_coherent(fbi->device, mxc_fbi->alloc_size, + mxc_fbi->alloc_start_vaddr, + mxc_fbi->alloc_start_paddr); + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + /* + * Fill in fb_info structure information + */ + fbi->var.xres = fbi->var.xres_virtual = MXCFB_SCREEN_WIDTH; + fbi->var.yres = fbi->var.yres_virtual = MXCFB_SCREEN_HEIGHT; + fbi->var.activate = FB_ACTIVATE_NOW; + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxc_fbi; + int ret; + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfb_drv_data.fbi = fbi; + mxc_fbi = fbi->par; + + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + /* + * Allocate memory + */ + ret = mxcfb_map_video_memory(fbi); + if (ret < 0) { + goto err1; + } + + mxcfb_init_panel(fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + dev_info(&pdev->dev, "%s registered\n", MXCFB_NAME); + + return 0; + + err2: + mxcfb_unmap_video_memory(fbi); + err1: + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + return ret; +} + +#ifdef CONFIG_PM +/*! + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * Suspends the framebuffer and blanks the screen. Power management support + * + * @param pdev pointer to device structure. + * @param state state of the device. + * + * @return success + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + drv_data->suspended = true; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + /* Display OFF */ + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISOFF, 0, 0); + + return 0; +} + +/*! + * Resumes the framebuffer and unblanks the screen. Power management support + * + * @param pdev pointer to device structure. + * + * @return success + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + /* Display ON */ + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISON, 0, 0); + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + wake_up_interruptible(&drv_data->suspend_wq); + + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Device definition for the Framebuffer + */ +static struct platform_device mxcfb_device = { + .name = MXCFB_NAME, + .id = 0, + .dev = { + .coherent_dma_mask = 0xFFFFFFFF, + } +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +static int mxcfb_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&mxcfb_driver); + if (ret == 0) { + ret = platform_device_register(&mxcfb_device); + if (ret != 0) { + platform_driver_unregister(&mxcfb_driver); + } + } + return ret; +} + +static void mxcfb_exit(void) +{ + struct fb_info *fbi = dev_get_drvdata(&mxcfb_device.dev); + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + platform_device_unregister(&mxcfb_device); + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +EXPORT_SYMBOL(mxcfb_set_refresh_mode); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Epson framebuffer driver"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_epson_vga.c b/drivers/video/mxc/mxcfb_epson_vga.c new file mode 100644 index 00000000..d7cf7d37 --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson_vga.c @@ -0,0 +1,362 @@ +/* + * Copyright 2007-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_epson_vga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/mxcfb.h> +#include <linux/ipu.h> +#include <linux/fsl_devices.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> + +static struct spi_device *lcd_spi; +static struct device *lcd_dev; + +static void lcd_init(void); +static void lcd_poweron(void); +static void lcd_poweroff(void); + +static void (*lcd_reset) (void); +static struct regulator *io_reg; +static struct regulator *core_reg; + +static struct fb_videomode video_modes[] = { + { + /* 480x640 @ 60 Hz */ + "Epson-VGA", 60, 480, 640, 41701, 60, 41, 10, 5, 20, 10, + 0, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &video_modes[0]); + + if (machine_is_mx31_3ds()) { + var.upper_margin = 0; + var.left_margin = 0; + } + + var.activate = FB_ACTIVATE_ALL; + var.yres_virtual = var.yres * 3; + + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG")) { + return 0; + } + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + lcd_poweron(); + break; + case FB_EVENT_BLANK: + if ((event->info->var.xres != 480) || + (event->info->var.yres != 640)) { + break; + } + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + lcd_poweron(); + } else { + lcd_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct device *dev) +{ + int i; + struct mxc_lcd_platform_data *plat = dev->platform_data; + + lcd_dev = dev; + + if (plat) { + io_reg = regulator_get(dev, plat->io_reg); + if (!IS_ERR(io_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + regulator_enable(io_reg); + } + core_reg = regulator_get(dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(core_reg, 2800000, 2800000); + regulator_enable(core_reg); + } + + lcd_reset = plat->reset; + if (lcd_reset) + lcd_reset(); + } + + lcd_init(); + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0) { + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(); + } + } + + fb_register_client(&nb); + + return 0; +} + +static int __devinit lcd_plat_probe(struct platform_device *pdev) +{ + ipu_adc_sig_cfg_t sig; + ipu_channel_params_t param; + + memset(&sig, 0, sizeof(sig)); + sig.ifc_width = 9; + sig.clk_pol = 1; + ipu_init_async_panel(0, IPU_PANEL_SERIAL, 90, IPU_PIX_FMT_GENERIC, sig); + + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + + return lcd_probe(&pdev->dev); +} + +static int __devinit lcd_spi_probe(struct spi_device *spi) +{ + lcd_spi = spi; + + spi->bits_per_word = 9; + spi_setup(spi); + + return lcd_probe(&spi->dev); +} + +static int __devexit lcd_remove(struct device *dev) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + regulator_put(io_reg); + regulator_put(core_reg); + + return 0; +} + +static int __devexit lcd_spi_remove(struct spi_device *spi) +{ + int ret = lcd_remove(&spi->dev); + lcd_spi = NULL; + return ret; +} + +static int __devexit lcd_plat_remove(struct platform_device *pdev) +{ + return lcd_remove(&pdev->dev); +} + +static int lcd_suspend(struct spi_device *spi, pm_message_t message) +{ + lcd_poweroff(); + return 0; +} + +static int lcd_resume(struct spi_device *spi) +{ + if (lcd_reset) + lcd_reset(); + + lcd_init(); + lcd_poweron(); + return 0; +} + +/*! + * spi driver structure for LTV350QV + */ +static struct spi_driver lcd_spi_dev_driver = { + + .driver = { + .name = "lcd_spi", + .owner = THIS_MODULE, + }, + .probe = lcd_spi_probe, + .remove = __devexit_p(lcd_spi_remove), + .suspend = lcd_suspend, + .resume = lcd_resume, +}; + +static struct platform_driver lcd_plat_driver = { + .driver = { + .name = "lcd_spi", + .owner = THIS_MODULE, + }, + .probe = lcd_plat_probe, + .remove = __devexit_p(lcd_plat_remove), +}; + +#define param(x) ((x) | 0x100) + +/* + * Send init commands to L4F00242T03 + * + */ +static void lcd_init(void) +{ + const u16 cmd[] = { 0x36, param(0), 0x3A, param(0x60) }; + + dev_dbg(lcd_dev, "initializing LCD\n"); + if (lcd_spi) { + spi_write(lcd_spi, (const u8 *)cmd, ARRAY_SIZE(cmd)); + } else { + ipu_disp_direct_write(DIRECT_ASYNC1, 0x36, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x100, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x3A, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x160, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } +} + +static int lcd_on; +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(void) +{ + const u16 slpout = 0x11; + const u16 dison = 0x29; + ipu_channel_params_t param; + if (lcd_on) + return; + + dev_dbg(lcd_dev, "turning on LCD\n"); + + if (lcd_spi) { + msleep(60); + spi_write(lcd_spi, (const u8 *)&slpout, 1); + msleep(60); + spi_write(lcd_spi, (const u8 *)&dison, 1); + } else { + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + ipu_disp_direct_write(DIRECT_ASYNC1, slpout, 0); + msleep(60); + ipu_disp_direct_write(DIRECT_ASYNC1, dison, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + const u16 slpin = 0x10; + const u16 disoff = 0x28; + ipu_channel_params_t param; + if (!lcd_on) + return; + + dev_dbg(lcd_dev, "turning off LCD\n"); + + if (lcd_spi) { + msleep(60); + spi_write(lcd_spi, (const u8 *)&disoff, 1); + msleep(60); + spi_write(lcd_spi, (const u8 *)&slpin, 1); + } else { + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + ipu_disp_direct_write(DIRECT_ASYNC1, disoff, 0); + msleep(60); + ipu_disp_direct_write(DIRECT_ASYNC1, slpin, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } + lcd_on = 0; +} + +static int __init epson_lcd_init(void) +{ + int ret; + + ret = platform_driver_register(&lcd_plat_driver); + if (ret) + return ret; + + return spi_register_driver(&lcd_spi_dev_driver); + +} + +static void __exit epson_lcd_exit(void) +{ + spi_unregister_driver(&lcd_spi_dev_driver); +} + +module_init(epson_lcd_init); +module_exit(epson_lcd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Epson VGA LCD init driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_hx8369_wvga.c b/drivers/video/mxc/mxcfb_hx8369_wvga.c new file mode 100644 index 00000000..08649d38 --- /dev/null +++ b/drivers/video/mxc/mxcfb_hx8369_wvga.c @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/spinlock.h> +#include <linux/mxcfb.h> +#include <linux/backlight.h> +#include <video/mipi_display.h> + +#include <mach/hardware.h> +#include <mach/clock.h> +#include <mach/mipi_dsi.h> + +#include "mipi_dsi.h" + +#define MIPI_DSI_MAX_RET_PACK_SIZE (0x4) + +#define HX8369BL_MAX_BRIGHT (255) +#define HX8369BL_DEF_BRIGHT (255) + +#define HX8369_MAX_DPHY_CLK (800) +#define HX8369_ONE_DATA_LANE (0x1) +#define HX8369_TWO_DATA_LANE (0x2) + +#define HX8369_CMD_SETEXTC (0xB9) +#define HX8369_CMD_SETEXTC_LEN (0x4) +#define HX8369_CMD_SETEXTC_PARAM_1 (0x6983ff) + +#define HX8369_CMD_GETHXID (0xF4) +#define HX8369_CMD_GETHXID_LEN (0x4) +#define HX8369_ID (0x69) +#define HX8369_ID_MASK (0xFF) + +#define HX8369_CMD_SETDISP (0xB2) +#define HX8369_CMD_SETDISP_LEN (16) +#define HX8369_CMD_SETDISP_1_HALT (0x00) +#define HX8369_CMD_SETDISP_2_RES_MODE (0x23) +#define HX8369_CMD_SETDISP_3_BP (0x03) +#define HX8369_CMD_SETDISP_4_FP (0x03) +#define HX8369_CMD_SETDISP_5_SAP (0x70) +#define HX8369_CMD_SETDISP_6_GENON (0x00) +#define HX8369_CMD_SETDISP_7_GENOFF (0xff) +#define HX8369_CMD_SETDISP_8_RTN (0x00) +#define HX8369_CMD_SETDISP_9_TEI (0x00) +#define HX8369_CMD_SETDISP_10_TEP_UP (0x00) +#define HX8369_CMD_SETDISP_11_TEP_LOW (0x00) +#define HX8369_CMD_SETDISP_12_BP_PE (0x03) +#define HX8369_CMD_SETDISP_13_FP_PE (0x03) +#define HX8369_CMD_SETDISP_14_RTN_PE (0x00) +#define HX8369_CMD_SETDISP_15_GON (0x01) + +#define HX8369_CMD_SETCYC (0xB4) +#define HX8369_CMD_SETCYC_LEN (6) +#define HX8369_CMD_SETCYC_PARAM_1 (0x5f1d00) +#define HX8369_CMD_SETCYC_PARAM_2 (0x060e) + +#define HX8369_CMD_SETGIP (0xD5) +#define HX8369_CMD_SETGIP_LEN (27) +#define HX8369_CMD_SETGIP_PARAM_1 (0x030400) +#define HX8369_CMD_SETGIP_PARAM_2 (0x1c050100) +#define HX8369_CMD_SETGIP_PARAM_3 (0x00030170) +#define HX8369_CMD_SETGIP_PARAM_4 (0x51064000) +#define HX8369_CMD_SETGIP_PARAM_5 (0x41000007) +#define HX8369_CMD_SETGIP_PARAM_6 (0x07075006) +#define HX8369_CMD_SETGIP_PARAM_7 (0x040f) + +#define HX8369_CMD_SETPOWER (0xB1) +#define HX8369_CMD_SETPOWER_LEN (20) +#define HX8369_CMD_SETPOWER_PARAM_1 (0x340001) +#define HX8369_CMD_SETPOWER_PARAM_2 (0x0f0f0006) +#define HX8369_CMD_SETPOWER_PARAM_3 (0x3f3f322a) +#define HX8369_CMD_SETPOWER_PARAM_4 (0xe6013a07) +#define HX8369_CMD_SETPOWER_PARAM_5 (0xe6e6e6e6) + +#define HX8369_CMD_SETVCOM (0xB6) +#define HX8369_CMD_SETVCOM_LEN (3) +#define HX8369_CMD_SETVCOM_PARAM_1 (0x5656) + +#define HX8369_CMD_SETPANEL (0xCC) +#define HX8369_CMD_SETPANEL_PARAM_1 (0x02) + +#define HX8369_CMD_SETGAMMA (0xE0) +#define HX8369_CMD_SETGAMMA_LEN (35) +#define HX8369_CMD_SETGAMMA_PARAM_1 (0x221d00) +#define HX8369_CMD_SETGAMMA_PARAM_2 (0x2e3f3d38) +#define HX8369_CMD_SETGAMMA_PARAM_3 (0x0f0d064a) +#define HX8369_CMD_SETGAMMA_PARAM_4 (0x16131513) +#define HX8369_CMD_SETGAMMA_PARAM_5 (0x1d001910) +#define HX8369_CMD_SETGAMMA_PARAM_6 (0x3f3d3822) +#define HX8369_CMD_SETGAMMA_PARAM_7 (0x0d064a2e) +#define HX8369_CMD_SETGAMMA_PARAM_8 (0x1315130f) +#define HX8369_CMD_SETGAMMA_PARAM_9 (0x191016) + +#define HX8369_CMD_SETMIPI (0xBA) +#define HX8369_CMD_SETMIPI_LEN (14) +#define HX8369_CMD_SETMIPI_PARAM_1 (0xc6a000) +#define HX8369_CMD_SETMIPI_PARAM_2 (0x10000a00) +#define HX8369_CMD_SETMIPI_ONELANE (0x10 << 24) +#define HX8369_CMD_SETMIPI_TWOLANE (0x11 << 24) +#define HX8369_CMD_SETMIPI_PARAM_3 (0x00026f30) +#define HX8369_CMD_SETMIPI_PARAM_4 (0x4018) + +#define HX8369_CMD_SETPIXEL_FMT (0x3A) +#define HX8369_CMD_SETPIXEL_FMT_24BPP (0x77) +#define HX8369_CMD_SETPIXEL_FMT_18BPP (0x66) +#define HX8369_CMD_SETPIXEL_FMT_16BPP (0x55) + +#define HX8369_CMD_SETCLUMN_ADDR (0x2A) +#define HX8369_CMD_SETCLUMN_ADDR_LEN (5) +#define HX8369_CMD_SETCLUMN_ADDR_PARAM_1 (0xdf0000) +#define HX8369_CMD_SETCLUMN_ADDR_PARAM_2 (0x01) + +#define HX8369_CMD_SETPAGE_ADDR (0x2B) +#define HX8369_CMD_SETPAGE_ADDR_LEN (5) +#define HX8369_CMD_SETPAGE_ADDR_PARAM_1 (0x1f0000) +#define HX8369_CMD_SETPAGE_ADDR_PARAM_2 (0x03) + +#define HX8369_CMD_WRT_DISP_BRIGHT (0x51) +#define HX8369_CMD_WRT_DISP_BRIGHT_PARAM_1 (0xFF) + +#define HX8369_CMD_WRT_CABC_MIN_BRIGHT (0x5E) +#define HX8369_CMD_WRT_CABC_MIN_BRIGHT_PARAM_1 (0x20) + +#define HX8369_CMD_WRT_CABC_CTRL (0x55) +#define HX8369_CMD_WRT_CABC_CTRL_PARAM_1 (0x1) + +#define HX8369_CMD_WRT_CTRL_DISP (0x53) +#define HX8369_CMD_WRT_CTRL_DISP_PARAM_1 (0x24) + +#define CHECK_RETCODE(ret) \ +do { \ + if (ret < 0) { \ + dev_err(&mipi_dsi->pdev->dev, \ + "%s ERR: ret:%d, line:%d.\n", \ + __func__, ret, __LINE__); \ + return ret; \ + } \ +} while (0) + +static int hx8369bl_brightness; +static int mipid_init_backlight(struct mipi_dsi_info *mipi_dsi); + +static struct fb_videomode truly_lcd_modedb[] = { + { + "TRULY-WVGA", 64, 480, 800, 37880, + 8, 8, + 6, 6, + 8, 6, + FB_SYNC_OE_LOW_ACT, + FB_VMODE_NONINTERLACED, + 0, + }, +}; + +static struct mipi_lcd_config lcd_config = { + .virtual_ch = 0x0, + .data_lane_num = HX8369_TWO_DATA_LANE, + .max_phy_clk = HX8369_MAX_DPHY_CLK, + .dpi_fmt = MIPI_RGB888, +}; +void mipid_hx8369_get_lcd_videomode(struct fb_videomode **mode, int *size, + struct mipi_lcd_config **data) +{ + if (cpu_is_mx6dl()) + truly_lcd_modedb[0].pixclock = 37037; /* 27M clock*/ + *mode = &truly_lcd_modedb[0]; + *size = ARRAY_SIZE(truly_lcd_modedb); + *data = &lcd_config; +} + +int mipid_hx8369_lcd_setup(struct mipi_dsi_info *mipi_dsi) +{ + u32 buf[DSI_CMD_BUF_MAXSIZE]; + int err; + + dev_dbg(&mipi_dsi->pdev->dev, "MIPI DSI LCD setup.\n"); + buf[0] = HX8369_CMD_SETEXTC | (HX8369_CMD_SETEXTC_PARAM_1 << 8); + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETEXTC_LEN); + CHECK_RETCODE(err); + buf[0] = MIPI_DSI_MAX_RET_PACK_SIZE; + err = mipi_dsi_pkt_write(mipi_dsi, + MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE, + buf, 0); + CHECK_RETCODE(err); + buf[0] = HX8369_CMD_GETHXID; + err = mipi_dsi_pkt_read(mipi_dsi, + MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM, + buf, HX8369_CMD_GETHXID_LEN); + if (!err && ((buf[0] & HX8369_ID_MASK) == HX8369_ID)) { + dev_info(&mipi_dsi->pdev->dev, + "MIPI DSI LCD ID:0x%x.\n", buf[0]); + } else { + dev_err(&mipi_dsi->pdev->dev, + "mipi_dsi_pkt_read err:%d, data:0x%x.\n", + err, buf[0]); + dev_info(&mipi_dsi->pdev->dev, + "MIPI DSI LCD not detected!\n"); + return err; + } + + /* set LCD resolution as 480RGBx800, DPI interface, + * display operation mode: RGB data bypass GRAM mode. + */ + buf[0] = HX8369_CMD_SETDISP | (HX8369_CMD_SETDISP_1_HALT << 8) | + (HX8369_CMD_SETDISP_2_RES_MODE << 16) | + (HX8369_CMD_SETDISP_3_BP << 24); + buf[1] = HX8369_CMD_SETDISP_4_FP | (HX8369_CMD_SETDISP_5_SAP << 8) | + (HX8369_CMD_SETDISP_6_GENON << 16) | + (HX8369_CMD_SETDISP_7_GENOFF << 24); + buf[2] = HX8369_CMD_SETDISP_8_RTN | (HX8369_CMD_SETDISP_9_TEI << 8) | + (HX8369_CMD_SETDISP_10_TEP_UP << 16) | + (HX8369_CMD_SETDISP_11_TEP_LOW << 24); + buf[3] = HX8369_CMD_SETDISP_12_BP_PE | + (HX8369_CMD_SETDISP_13_FP_PE << 8) | + (HX8369_CMD_SETDISP_14_RTN_PE << 16) | + (HX8369_CMD_SETDISP_15_GON << 24); + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETDISP_LEN); + CHECK_RETCODE(err); + + /* Set display waveform cycle */ + buf[0] = HX8369_CMD_SETCYC | (HX8369_CMD_SETCYC_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETCYC_PARAM_2; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETCYC_LEN); + CHECK_RETCODE(err); + + /* Set GIP timing output control */ + buf[0] = HX8369_CMD_SETGIP | (HX8369_CMD_SETGIP_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETGIP_PARAM_2; + buf[2] = HX8369_CMD_SETGIP_PARAM_3; + buf[3] = HX8369_CMD_SETGIP_PARAM_4; + buf[4] = HX8369_CMD_SETGIP_PARAM_5; + buf[5] = HX8369_CMD_SETGIP_PARAM_6; + buf[6] = HX8369_CMD_SETGIP_PARAM_7; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETGIP_LEN); + CHECK_RETCODE(err); + + /* Set power: standby, DC etc. */ + buf[0] = HX8369_CMD_SETPOWER | (HX8369_CMD_SETPOWER_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETPOWER_PARAM_2; + buf[2] = HX8369_CMD_SETPOWER_PARAM_3; + buf[3] = HX8369_CMD_SETPOWER_PARAM_4; + buf[4] = HX8369_CMD_SETPOWER_PARAM_5; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETPOWER_LEN); + CHECK_RETCODE(err); + + /* Set VCOM voltage. */ + buf[0] = HX8369_CMD_SETVCOM | (HX8369_CMD_SETVCOM_PARAM_1 << 8); + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETVCOM_LEN); + CHECK_RETCODE(err); + + /* Set Panel: BGR/RGB or Inversion. */ + buf[0] = HX8369_CMD_SETPANEL | (HX8369_CMD_SETPANEL_PARAM_1 << 8); + err = mipi_dsi_pkt_write(mipi_dsi, + MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, buf, 0); + CHECK_RETCODE(err); + + /* Set gamma curve related setting */ + buf[0] = HX8369_CMD_SETGAMMA | (HX8369_CMD_SETGAMMA_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETGAMMA_PARAM_2; + buf[2] = HX8369_CMD_SETGAMMA_PARAM_3; + buf[3] = HX8369_CMD_SETGAMMA_PARAM_4; + buf[4] = HX8369_CMD_SETGAMMA_PARAM_5; + buf[5] = HX8369_CMD_SETGAMMA_PARAM_6; + buf[7] = HX8369_CMD_SETGAMMA_PARAM_7; + buf[7] = HX8369_CMD_SETGAMMA_PARAM_8; + buf[8] = HX8369_CMD_SETGAMMA_PARAM_9; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETGAMMA_LEN); + CHECK_RETCODE(err); + + /* Set MIPI: DPHYCMD & DSICMD, data lane number */ + buf[0] = HX8369_CMD_SETMIPI | (HX8369_CMD_SETMIPI_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETMIPI_PARAM_2; + buf[2] = HX8369_CMD_SETMIPI_PARAM_3; + if (lcd_config.data_lane_num == HX8369_ONE_DATA_LANE) + buf[2] |= HX8369_CMD_SETMIPI_ONELANE; + else + buf[2] |= HX8369_CMD_SETMIPI_TWOLANE; + buf[3] = HX8369_CMD_SETMIPI_PARAM_4; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf, + HX8369_CMD_SETMIPI_LEN); + CHECK_RETCODE(err); + + /* Set pixel format:24bpp */ + buf[0] = HX8369_CMD_SETPIXEL_FMT; + switch (lcd_config.dpi_fmt) { + case MIPI_RGB565_PACKED: + case MIPI_RGB565_LOOSELY: + case MIPI_RGB565_CONFIG3: + buf[0] |= (HX8369_CMD_SETPIXEL_FMT_16BPP << 8); + break; + + case MIPI_RGB666_LOOSELY: + case MIPI_RGB666_PACKED: + buf[0] |= (HX8369_CMD_SETPIXEL_FMT_18BPP << 8); + break; + + case MIPI_RGB888: + buf[0] |= (HX8369_CMD_SETPIXEL_FMT_24BPP << 8); + break; + + default: + buf[0] |= (HX8369_CMD_SETPIXEL_FMT_24BPP << 8); + break; + } + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + buf, 0); + CHECK_RETCODE(err); + + /* Set column address: 0~479 */ + buf[0] = HX8369_CMD_SETCLUMN_ADDR | + (HX8369_CMD_SETCLUMN_ADDR_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETCLUMN_ADDR_PARAM_2; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETCLUMN_ADDR_LEN); + CHECK_RETCODE(err); + + /* Set page address: 0~799 */ + buf[0] = HX8369_CMD_SETPAGE_ADDR | + (HX8369_CMD_SETPAGE_ADDR_PARAM_1 << 8); + buf[1] = HX8369_CMD_SETPAGE_ADDR_PARAM_2; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, + buf, HX8369_CMD_SETPAGE_ADDR_LEN); + CHECK_RETCODE(err); + + /* Set display brightness related */ + buf[0] = HX8369_CMD_WRT_DISP_BRIGHT | + (HX8369_CMD_WRT_DISP_BRIGHT_PARAM_1 << 8); + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + buf, 0); + CHECK_RETCODE(err); + + buf[0] = HX8369_CMD_WRT_CABC_CTRL | + (HX8369_CMD_WRT_CABC_CTRL_PARAM_1 << 8); + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + buf, 0); + CHECK_RETCODE(err); + + buf[0] = HX8369_CMD_WRT_CTRL_DISP | + (HX8369_CMD_WRT_CTRL_DISP_PARAM_1 << 8); + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + buf, 0); + CHECK_RETCODE(err); + + /* exit sleep mode and set display on */ + buf[0] = MIPI_DCS_EXIT_SLEEP_MODE; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM, + buf, 0); + CHECK_RETCODE(err); + /* To allow time for the supply voltages + * and clock circuits to stabilize. + */ + msleep(5); + buf[0] = MIPI_DCS_SET_DISPLAY_ON; + err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM, + buf, 0); + CHECK_RETCODE(err); + + err = mipid_init_backlight(mipi_dsi); + return err; +} + +static int mipid_bl_update_status(struct backlight_device *bl) +{ + u32 buf; + int brightness = bl->props.brightness; + struct mipi_dsi_info *mipi_dsi = bl_get_data(bl); + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.fb_blank != FB_BLANK_UNBLANK) + brightness = 0; + + buf = HX8369_CMD_WRT_DISP_BRIGHT | + ((brightness & HX8369BL_MAX_BRIGHT) << 8); + mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, + &buf, 0); + + hx8369bl_brightness = brightness & HX8369BL_MAX_BRIGHT; + + dev_dbg(&bl->dev, "mipid backlight bringtness:%d.\n", brightness); + return 0; +} + +static int mipid_bl_get_brightness(struct backlight_device *bl) +{ + return hx8369bl_brightness; +} + +static int mipi_bl_check_fb(struct backlight_device *bl, struct fb_info *fbi) +{ + return 0; +} + +static const struct backlight_ops mipid_lcd_bl_ops = { + .update_status = mipid_bl_update_status, + .get_brightness = mipid_bl_get_brightness, + .check_fb = mipi_bl_check_fb, +}; + +static int mipid_init_backlight(struct mipi_dsi_info *mipi_dsi) +{ + struct backlight_properties props; + struct backlight_device *bl; + + if (mipi_dsi->bl) { + pr_debug("mipid backlight already init!\n"); + return 0; + } + memset(&props, 0, sizeof(struct backlight_properties)); + props.max_brightness = HX8369BL_MAX_BRIGHT; + props.type = BACKLIGHT_RAW; + bl = backlight_device_register("mipid-bl", &mipi_dsi->pdev->dev, + mipi_dsi, &mipid_lcd_bl_ops, &props); + if (IS_ERR(bl)) { + pr_err("error %ld on backlight register\n", PTR_ERR(bl)); + return PTR_ERR(bl); + } + mipi_dsi->bl = bl; + bl->props.power = FB_BLANK_UNBLANK; + bl->props.fb_blank = FB_BLANK_UNBLANK; + bl->props.brightness = HX8369BL_DEF_BRIGHT; + + mipid_bl_update_status(bl); + return 0; +} diff --git a/drivers/video/mxc/mxcfb_modedb.c b/drivers/video/mxc/mxcfb_modedb.c new file mode 100644 index 00000000..acaaef2c --- /dev/null +++ b/drivers/video/mxc/mxcfb_modedb.c @@ -0,0 +1,69 @@ +/* + * Copyright 2007-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/mxcfb.h> + +struct fb_videomode mxcfb_modedb[] = { + { + /* 240x320 @ 60 Hz */ + "Sharp-QVGA", 60, 240, 320, 185925, 9, 16, 7, 9, 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_DATA_INVERT | FB_SYNC_CLK_IDLE_EN, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 240x33 @ 60 Hz */ + "Sharp-CLI", 60, 240, 33, 185925, 9, 16, 7, 9 + 287, 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_DATA_INVERT | FB_SYNC_CLK_IDLE_EN, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 640x480 @ 60 Hz */ + "NEC-VGA", 60, 640, 480, 38255, 144, 0, 34, 40, 1, 1, + FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 640x480 @ 60 Hz */ + "CPT-VGA", 60, 640, 480, 39683, 45, 114, 33, 11, 1, 1, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* NTSC TV output */ + "TV-NTSC", 60, 640, 480, 37538, + 38, 858 - 640 - 38 - 3, + 36, 518 - 480 - 36 - 1, + 3, 1, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* PAL TV output */ + "TV-PAL", 50, 640, 480, 37538, + 38, 960 - 640 - 38 - 32, + 32, 555 - 480 - 32 - 3, + 32, 3, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* TV output VGA mode, 640x480 @ 65 Hz */ + "TV-VGA", 60, 640, 480, 40574, 35, 45, 9, 1, 46, 5, + 0, FB_VMODE_NONINTERLACED, 0, + }, +}; + +int mxcfb_modedb_sz = ARRAY_SIZE(mxcfb_modedb); diff --git a/drivers/video/mxc/mxcfb_seiko_wvga.c b/drivers/video/mxc/mxcfb_seiko_wvga.c new file mode 100644 index 00000000..6e9abaf8 --- /dev/null +++ b/drivers/video/mxc/mxcfb_seiko_wvga.c @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2011-2012 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_seiko_wvga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/fsl_devices.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> + +static void lcd_poweron(void); +static void lcd_poweroff(void); + +static struct platform_device *plcd_dev; +static struct regulator *io_reg; +static struct regulator *core_reg; +static int lcd_on; + +static struct fb_videomode video_modes[] = { + { + /* 800x480 @ 57 Hz , pixel clk @ 32MHz */ + "SEIKO-WVGA", 60, 800, 480, 29850, 99, 164, 33, 10, 10, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &video_modes[0]); + + var.activate = FB_ACTIVATE_ALL; + var.yres_virtual = var.yres * 3; + + console_lock(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "mxc_elcdif_fb")) + return 0; + + switch (val) { + case FB_EVENT_BLANK: + if ((event->info->var.xres != 800) || + (event->info->var.yres != 480)) { + break; + } + if (*((int *)event->data) == FB_BLANK_UNBLANK) + lcd_poweron(); + else + lcd_poweroff(); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the platform device is detected. + * + * @param pdev the platform device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct platform_device *pdev) +{ + int i; + struct fsl_mxc_lcd_platform_data *plat = pdev->dev.platform_data; + + if (plat) { + if (plat->reset) + plat->reset(); + + io_reg = regulator_get(&pdev->dev, plat->io_reg); + if (IS_ERR(io_reg)) + io_reg = NULL; + core_reg = regulator_get(&pdev->dev, plat->core_reg); + if (!IS_ERR(core_reg)) + regulator_set_voltage(io_reg, 1800000, 1800000); + else + core_reg = NULL; + } + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "mxc_elcdif_fb") == 0) { + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(); + } + } + + fb_register_client(&nb); + + plcd_dev = pdev; + + return 0; +} + +static int __devexit lcd_remove(struct platform_device *pdev) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + if (io_reg) + regulator_put(io_reg); + if (core_reg) + regulator_put(core_reg); + + return 0; +} + +#ifdef CONFIG_PM +static int lcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int lcd_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define lcd_suspend NULL +#define lcd_resume NULL +#endif + +/*! + * platform driver structure for SEIKO WVGA + */ +static struct platform_driver lcd_driver = { + .driver = { + .name = "lcd_seiko"}, + .probe = lcd_probe, + .remove = __devexit_p(lcd_remove), + .suspend = lcd_suspend, + .resume = lcd_resume, +}; + +/* + * Send Power + * + */ +static void lcd_poweron(void) +{ + if (lcd_on) + return; + + dev_dbg(&plcd_dev->dev, "turning on LCD\n"); + if (core_reg) + regulator_enable(core_reg); + if (io_reg) + regulator_enable(io_reg); + lcd_on = 1; +} + +/* + * Send Power Off + * + */ +static void lcd_poweroff(void) +{ + lcd_on = 0; + dev_dbg(&plcd_dev->dev, "turning off LCD\n"); + if (io_reg) + regulator_disable(io_reg); + if (core_reg) + regulator_disable(core_reg); +} + +static int __init seiko_wvga_lcd_init(void) +{ + return platform_driver_register(&lcd_driver); +} + +static void __exit seiko_wvga_lcd_exit(void) +{ + platform_driver_unregister(&lcd_driver); +} + +module_init(seiko_wvga_lcd_init); +module_exit(seiko_wvga_lcd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SEIKO WVGA LCD init driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_sii902x.c b/drivers/video/mxc/mxcfb_sii902x.c new file mode 100644 index 00000000..b71afc6a --- /dev/null +++ b/drivers/video/mxc/mxcfb_sii902x.c @@ -0,0 +1,1312 @@ +/* + * Copyright (C) 2010-2012 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_sii902x.c + * + * @brief MXC Frame buffer driver for SII902x + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/fsl_devices.h> +#include <linux/interrupt.h> +#include <linux/uaccess.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/mxc_edid.h> +#include "mxc_dispdrv.h" + +#define DISPDRV_SII "sii902x_hdmi" + +#define TPI_PIX_CLK_LSB (0x00) +#define TPI_PIX_CLK_MSB (0x01) +#define TPI_VERT_FREQ_LSB (0x02) +#define TPI_VERT_FREQ_MSB (0x03) +#define TPI_TOTAL_PIX_LSB (0x04) +#define TPI_TOTAL_PIX_MSB (0x05) +#define TPI_TOTAL_LINES_LSB (0x06) +#define TPI_TOTAL_LINES_MSB (0x07) +#define TPI_PIX_REPETITION (0x08) +#define TPI_INPUT_FORMAT_REG (0x09) +#define TPI_OUTPUT_FORMAT_REG (0x0A) + +#define TPI_AVI_BYTE_0 (0x0C) +#define TPI_AVI_BYTE_1 (0x0D) +#define TPI_AVI_BYTE_2 (0x0E) +#define TPI_AVI_BYTE_3 (0x0F) +#define TPI_AVI_BYTE_4 (0x10) +#define TPI_AVI_BYTE_5 (0x11) + +#define TPI_END_TOP_BAR_LSB (0x12) +#define TPI_END_TOP_BAR_MSB (0x13) + +#define TPI_START_BTM_BAR_LSB (0x14) +#define TPI_START_BTM_BAR_MSB (0x15) + +#define TPI_END_LEFT_BAR_LSB (0x16) +#define TPI_END_LEFT_BAR_MSB (0x17) + +#define TPI_END_RIGHT_BAR_LSB (0x18) +#define TPI_END_RIGHT_BAR_MSB (0x19) + +#define TPI_SYSTEM_CONTROL_DATA_REG (0x1A) +#define TPI_DEVICE_ID (0x1B) +#define TPI_DEVICE_REV_ID (0x1C) +#define TPI_RESERVED2 (0x1D) +#define TPI_DEVICE_POWER_STATE_CTRL_REG (0x1E) + +#define TPI_I2S_EN (0x1F) +#define TPI_I2S_IN_CFG (0x20) +#define TPI_I2S_CHST_0 (0x21) +#define TPI_I2S_CHST_1 (0x22) +#define TPI_I2S_CHST_2 (0x23) +#define TPI_I2S_CHST_3 (0x24) +#define TPI_I2S_CHST_4 (0x25) + +#define TPI_AUDIO_HANDLING (0x25) +#define TPI_AUDIO_INTERFACE_REG (0x26) +#define TPI_AUDIO_SAMPLE_CTRL (0x27) + +#define TPI_INTERRUPT_ENABLE_REG (0x3C) +#define TPI_INTERRUPT_STATUS_REG (0x3D) + +#define TPI_INTERNAL_PAGE_REG 0xBC +#define TPI_INDEXED_OFFSET_REG 0xBD +#define TPI_INDEXED_VALUE_REG 0xBE + +#define MISC_INFO_FRAMES_CTRL (0xBF) +#define MISC_INFO_FRAMES_TYPE (0xC0) +#define EN_AND_RPT_AUDIO 0xC2 +#define DISABLE_AUDIO 0x02 + +#define TPI_ENABLE (0xC7) + +#define INDEXED_PAGE_0 0x01 +#define INDEXED_PAGE_1 0x02 +#define INDEXED_PAGE_2 0x03 + +#define HOT_PLUG_EVENT 0x01 +#define RX_SENSE_EVENT 0x02 +#define HOT_PLUG_STATE 0x04 +#define RX_SENSE_STATE 0x08 + +#define OUTPUT_MODE_MASK (0x01) +#define OUTPUT_MODE_DVI (0x00) +#define OUTPUT_MODE_HDMI (0x01) + +#define LINK_INTEGRITY_MODE_MASK 0x40 +#define LINK_INTEGRITY_STATIC (0x00) +#define LINK_INTEGRITY_DYNAMIC (0x40) + +#define TMDS_OUTPUT_CONTROL_MASK 0x10 +#define TMDS_OUTPUT_CONTROL_ACTIVE (0x00) +#define TMDS_OUTPUT_CONTROL_POWER_DOWN (0x10) + +#define AV_MUTE_MASK 0x08 +#define AV_MUTE_NORMAL (0x00) +#define AV_MUTE_MUTED (0x08) + +#define TX_POWER_STATE_MASK 0x3 +#define TX_POWER_STATE_D0 (0x00) +#define TX_POWER_STATE_D1 (0x01) +#define TX_POWER_STATE_D2 (0x02) +#define TX_POWER_STATE_D3 (0x03) + +#define AUDIO_MUTE_MASK 0x10 +#define AUDIO_MUTE_NORMAL (0x00) +#define AUDIO_MUTE_MUTED (0x10) + +#define AUDIO_SEL_MASK 0xC0 +#define AUD_IF_SPDIF 0x40 +#define AUD_IF_I2S 0x80 +#define AUD_IF_DSD 0xC0 +#define AUD_IF_HBR 0x04 + +#define REFER_TO_STREAM_HDR 0x00 + +#define AUD_PASS_BASIC 0x00 +#define AUD_PASS_ALL 0x01 +#define AUD_DOWN_SAMPLE 0x02 +#define AUD_DO_NOT_CHECK 0x03 + +#define BITS_IN_RGB 0x00 +#define BITS_IN_YCBCR444 0x01 +#define BITS_IN_YCBCR422 0x02 + +#define BITS_IN_AUTO_RANGE 0x00 +#define BITS_IN_FULL_RANGE 0x04 +#define BITS_IN_LTD_RANGE 0x08 + +#define BIT_EN_DITHER_10_8 0x40 +#define BIT_EXTENDED_MODE 0x80 + +#define SII_EDID_LEN 512 +#define SIZE_AVI_INFOFRAME 0x0E +#define SIZE_AUDIO_INFOFRAME 0x0F + +#define _4_To_3 0x10 +#define _16_To_9 0x20 +#define SAME_AS_AR 0x08 + +struct sii902x_data { + struct platform_device *pdev; + struct i2c_client *client; + struct mxc_dispdrv_handle *disp_hdmi; + struct regulator *io_reg; + struct regulator *analog_reg; + struct delayed_work det_work; + struct fb_info *fbi; + struct mxc_edid_cfg edid_cfg; + bool cable_plugin; + bool rx_powerup; + bool need_mode_change; + u8 edid[SII_EDID_LEN]; + struct notifier_block nb; + + u8 power_state; + u8 tpivmode[3]; + u8 pixrep; + + /* SII902x video setting: + * 1. hdmi video fmt: + * 0 = CEA-861 VIC; 1 = HDMI_VIC; 2 = 3D + * 2. vic: video mode index + * 3. aspect ratio: + * 4x3 or 16x9 + * 4. color space: + * 0 = RGB; 1 = YCbCr4:4:4; 2 = YCbCr4:2:2_16bits; + * 3 = YCbCr4:2:2_8bits;4 = xvYCC4:4:4 + * 5. color depth: + * 0 = 8bits; 1 = 10bits; 2 = 12bits; 3 = 16bits + * 6. colorimetry: + * 0 = 601; 1 = 709 + * 7. syncmode: + * 0 = external HS/VS/DE; 1 = external HS/VS and internal DE; + * 2 = embedded sync + */ +#define VMD_HDMIFORMAT_CEA_VIC 0x00 +#define VMD_HDMIFORMAT_HDMI_VIC 0x01 +#define VMD_HDMIFORMAT_3D 0x02 +#define VMD_HDMIFORMAT_PC 0x03 + u8 hdmi_vid_fmt; + u8 vic; +#define VMD_ASPECT_RATIO_4x3 0x01 +#define VMD_ASPECT_RATIO_16x9 0x02 + u8 aspect_ratio; +#define RGB 0 +#define YCBCR444 1 +#define YCBCR422_16BITS 2 +#define YCBCR422_8BITS 3 +#define XVYCC444 4 + u8 icolor_space; + u8 ocolor_space; +#define VMD_COLOR_DEPTH_8BIT 0x00 +#define VMD_COLOR_DEPTH_10BIT 0x01 +#define VMD_COLOR_DEPTH_12BIT 0x02 +#define VMD_COLOR_DEPTH_16BIT 0x03 + u8 color_depth; +#define COLORIMETRY_601 0 +#define COLORIMETRY_709 1 + u8 colorimetry; +#define EXTERNAL_HSVSDE 0 +#define INTERNAL_DE 1 +#define EMBEDDED_SYNC 2 + u8 syncmode; + u8 threeDstruct; + u8 threeDextdata; + +#define AMODE_I2S 0 +#define AMODE_SPDIF 1 +#define AMODE_HBR 2 +#define AMODE_DSD 3 + u8 audio_mode; +#define ACHANNEL_2CH 1 +#define ACHANNEL_3CH 2 +#define ACHANNEL_4CH 3 +#define ACHANNEL_5CH 4 +#define ACHANNEL_6CH 5 +#define ACHANNEL_7CH 6 +#define ACHANNEL_8CH 7 + u8 audio_channels; + u8 audiofs; + u8 audio_word_len; + u8 audio_i2s_fmt; +}; + +static __attribute__ ((unused)) void dump_regs(struct sii902x_data *sii902x, + u8 reg, int len) +{ + u8 buf[50]; + int i; + + i2c_smbus_read_i2c_block_data(sii902x->client, reg, len, buf); + for (i = 0; i < len; i++) + dev_dbg(&sii902x->client->dev, "reg[0x%02X]: 0x%02X\n", + i+reg, buf[i]); +} + +static ssize_t sii902x_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + + strcpy(buf, sii902x->fbi->fix.id); + sprintf(buf+strlen(buf), "\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(fb_name, S_IRUGO, sii902x_show_name, NULL); + +static ssize_t sii902x_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + + if (sii902x->cable_plugin == false) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, sii902x_show_state, NULL); + +static ssize_t sii902x_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + int i, j, len = 0; + + for (j = 0; j < SII_EDID_LEN/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + sii902x->edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, sii902x_show_edid, NULL); + +/*------------------------------------------------------------------------------ + * Function Description: Write "0" to all bits in TPI offset "Offset" that are set + * to "1" in "Pattern"; Leave all other bits in "Offset" + * unchanged. + *----------------------------------------------------------------------------- + */ +void read_clr_write_tpi(struct i2c_client *client, u8 offset, u8 mask) +{ + u8 tmp; + + tmp = i2c_smbus_read_byte_data(client, offset); + tmp &= ~mask; + i2c_smbus_write_byte_data(client, offset, tmp); +} + +/*------------------------------------------------------------------------------ + * Function Description: Write "1" to all bits in TPI offset "Offset" that are set + * to "1" in "Pattern"; Leave all other bits in "Offset" + * unchanged. + *----------------------------------------------------------------------------- + */ +void read_set_write_tpi(struct i2c_client *client, u8 offset, u8 mask) +{ + u8 tmp; + + tmp = i2c_smbus_read_byte_data(client, offset); + tmp |= mask; + i2c_smbus_write_byte_data(client, offset, tmp); +} + +/*------------------------------------------------------------------------------ + * Function Description: Write "Value" to all bits in TPI offset "Offset" that are set + * to "1" in "Mask"; Leave all other bits in "Offset" + * unchanged. + *---------------------------------------------------------------------------- + */ +void read_modify_tpi(struct i2c_client *client, u8 offset, u8 mask, u8 value) +{ + u8 tmp; + + tmp = i2c_smbus_read_byte_data(client, offset); + tmp &= ~mask; + tmp |= (value & mask); + i2c_smbus_write_byte_data(client, offset, tmp); +} + +/*------------------------------------------------------------------------------ + * Function Description: Read an indexed register value + * Write: + * 1. 0xBC => Internal page num + * 2. 0xBD => Indexed register offset + * Read: + * 3. 0xBE => Returns the indexed register value + *---------------------------------------------------------------------------- + */ +int read_idx_reg(struct i2c_client *client, u8 page, u8 regoffset) +{ + i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page); + i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset); + return i2c_smbus_read_byte_data(client, TPI_INDEXED_VALUE_REG); +} + +/*------------------------------------------------------------------------------ + * Function Description: Write a value to an indexed register + * + * Write: + * 1. 0xBC => Internal page num + * 2. 0xBD => Indexed register offset + * 3. 0xBE => Set the indexed register value + *------------------------------------------------------------------------------ + */ +void write_idx_reg(struct i2c_client *client, u8 page, u8 regoffset, u8 regval) +{ + i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page); + i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset); + i2c_smbus_write_byte_data(client, TPI_INDEXED_VALUE_REG, regval); +} + +/*------------------------------------------------------------------------------ + * Function Description: Write "Value" to all bits in TPI offset "Offset" that are set + * to "1" in "Mask"; Leave all other bits in "Offset" + * unchanged. + *---------------------------------------------------------------------------- + */ +void read_modify_idx_reg(struct i2c_client *client, u8 page, u8 regoffset, u8 mask, u8 value) +{ + u8 tmp; + + i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page); + i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset); + tmp = i2c_smbus_read_byte_data(client, TPI_INDEXED_VALUE_REG); + tmp &= ~mask; + tmp |= (value & mask); + i2c_smbus_write_byte_data(client, TPI_INDEXED_VALUE_REG, tmp); +} + +static void sii902x_set_powerstate(struct sii902x_data *sii902x, u8 state) +{ + if (sii902x->power_state != state) { + read_modify_tpi(sii902x->client, TPI_DEVICE_POWER_STATE_CTRL_REG, + TX_POWER_STATE_MASK, state); + sii902x->power_state = state; + } +} + +static void sii902x_setAVI(struct sii902x_data *sii902x) +{ + u8 avi_data[SIZE_AVI_INFOFRAME]; + u8 tmp; + int i; + + dev_dbg(&sii902x->client->dev, "set AVI frame\n"); + + memset(avi_data, 0, SIZE_AVI_INFOFRAME); + + if (sii902x->edid_cfg.cea_ycbcr444) + tmp = 2; + else if (sii902x->edid_cfg.cea_ycbcr422) + tmp = 1; + else + tmp = 0; + + /* AVI byte1: Y1Y0 (output format) */ + avi_data[1] = (tmp << 5) & 0x60; + /* A0 = 1; Active format identification data is present in the AVI InfoFrame. + * S1:S0 = 00; + */ + avi_data[1] |= 0x10; + + if (sii902x->ocolor_space == XVYCC444) { + avi_data[2] = 0xC0; + if (sii902x->colorimetry == COLORIMETRY_601) + avi_data[3] &= ~0x70; + else if (sii902x->colorimetry == COLORIMETRY_709) + avi_data[3] = (avi_data[3] & ~0x70) | 0x10; + } else if (sii902x->ocolor_space != RGB) { + if (sii902x->colorimetry == COLORIMETRY_709) + avi_data[2] = 0x80;/* AVI byte2: C1C0*/ + else if (sii902x->colorimetry == COLORIMETRY_601) + avi_data[2] = 0x40;/* AVI byte2: C1C0 */ + } else {/* Carries no data */ + /* AVI Byte2: C1C0 */ + avi_data[2] &= ~0xc0; /* colorimetry = 0 */ + avi_data[3] &= ~0x70; /* Extended colorimetry = 0 */ + } + + avi_data[4] = sii902x->vic; + + /* Set the Aspect Ration info into the Infoframe Byte 2 */ + if (sii902x->aspect_ratio == VMD_ASPECT_RATIO_16x9) + avi_data[2] |= _16_To_9; /* AVI Byte2: M1M0 */ + else + avi_data[2] |= _4_To_3; + + avi_data[2] |= SAME_AS_AR; /* AVI Byte2: R3..R1 - Set to "Same as Picture Aspect Ratio" */ + avi_data[5] = sii902x->pixrep; /* AVI Byte5: Pixel Replication - PR3..PR0 */ + + /* Calculate AVI InfoFrame ChecKsum */ + avi_data[0] = 0x82 + 0x02 + 0x0D; + for (i = 1; i < SIZE_AVI_INFOFRAME; i++) + avi_data[0] += avi_data[i]; + avi_data[0] = 0x100 - avi_data[0]; + + /* Write the Inforframe data to the TPI Infoframe registers */ + for (i = 0; i < SIZE_AVI_INFOFRAME; i++) + i2c_smbus_write_byte_data(sii902x->client, + TPI_AVI_BYTE_0 + i, avi_data[i]); + + dump_regs(sii902x, TPI_AVI_BYTE_0, SIZE_AVI_INFOFRAME); +} + +#define TYPE_AUDIO_INFOFRAMES 0x84 +#define AUDIO_INFOFRAMES_VERSION 0x01 +#define AUDIO_INFOFRAMES_LENGTH 0x0A +/*------------------------------------------------------------------------------ +* Function Description: Load Audio InfoFrame data into registers and send to sink +* +* Accepts: (1) Channel count +* (2) speaker configuration per CEA-861D Tables 19, 20 +* (3) Coding type: 0x09 for DSD Audio. 0 (refer to stream header) for all the rest +* (4) Sample Frequency. Non zero for HBR only +* (5) Audio Sample Length. Non zero for HBR only. +*------------------------------------------------------------------------------ +*/ +static void sii902x_setAIF(struct sii902x_data *sii902x, + u8 codingtype, u8 sample_size, u8 sample_freq, + u8 speaker_cfg) +{ + u8 aif_data[SIZE_AUDIO_INFOFRAME]; + u8 channel_count = sii902x->audio_channels & 0x07; + int i; + + dev_dbg(&sii902x->client->dev, "set AIF frame\n"); + + memset(aif_data, 0, SIZE_AUDIO_INFOFRAME); + + /* Disbale MPEG/Vendor Specific InfoFrames */ + i2c_smbus_write_byte_data(sii902x->client, MISC_INFO_FRAMES_CTRL, DISABLE_AUDIO); + + aif_data[0] = TYPE_AUDIO_INFOFRAMES; + aif_data[1] = AUDIO_INFOFRAMES_VERSION; + aif_data[2] = AUDIO_INFOFRAMES_LENGTH; + /* Calculate checksum - 0x84 + 0x01 + 0x0A */ + aif_data[3] = TYPE_AUDIO_INFOFRAMES + + AUDIO_INFOFRAMES_VERSION + AUDIO_INFOFRAMES_LENGTH; + + aif_data[4] = channel_count; /* 0 for "Refer to Stream Header" or for 2 Channels. 0x07 for 8 Channels*/ + aif_data[4] |= (codingtype << 4); /* 0xC7[7:4] == 0b1001 for DSD Audio */ + aif_data[5] = ((sample_freq & 0x07) << 2) | (sample_size & 0x03); + aif_data[7] = speaker_cfg; + + for (i = 4; i < SIZE_AUDIO_INFOFRAME; i++) + aif_data[3] += aif_data[i]; + + aif_data[3] = 0x100 - aif_data[3]; + + /* Re-enable Audio InfoFrame transmission and repeat */ + i2c_smbus_write_byte_data(sii902x->client, MISC_INFO_FRAMES_CTRL, EN_AND_RPT_AUDIO); + + for (i = 0; i < SIZE_AUDIO_INFOFRAME; i++) + i2c_smbus_write_byte_data(sii902x->client, + MISC_INFO_FRAMES_TYPE + i, aif_data[i]); + + dump_regs(sii902x, MISC_INFO_FRAMES_TYPE, SIZE_AUDIO_INFOFRAME); +} + +static void sii902x_setaudio(struct sii902x_data *sii902x) +{ + dev_dbg(&sii902x->client->dev, "set audio\n"); + + /* mute audio */ + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, + AUDIO_MUTE_MASK, AUDIO_MUTE_MUTED); + if (sii902x->audio_mode == AMODE_I2S) { + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, + AUDIO_SEL_MASK, AUD_IF_I2S); + i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_HANDLING, + 0x08 | AUD_DO_NOT_CHECK); + } else { + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, + AUDIO_SEL_MASK, AUD_IF_SPDIF); + i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_HANDLING, + AUD_PASS_BASIC); + } + + if (sii902x->audio_channels == ACHANNEL_2CH) + read_clr_write_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, 0x20); + else + read_set_write_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, 0x20); + + if (sii902x->audio_mode == AMODE_I2S) { + /* I2S - Map channels */ + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0x80); + + if (sii902x->audio_channels > ACHANNEL_2CH) + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0x91); + + if (sii902x->audio_channels > ACHANNEL_4CH) + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0xA2); + + if (sii902x->audio_channels > ACHANNEL_6CH) + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0xB3); + + /* I2S - Stream Header Settings */ + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_0, 0x00); + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_1, 0x00); + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_2, 0x00); + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_3, sii902x->audiofs); + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_4, + (sii902x->audiofs << 4) | sii902x->audio_word_len); + + /* added for 16bit auido noise issue */ + write_idx_reg(sii902x->client, INDEXED_PAGE_1, 0x24, sii902x->audio_word_len); + + /* I2S - Input Configuration */ + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_IN_CFG, sii902x->audio_i2s_fmt); + } + + i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_SAMPLE_CTRL, REFER_TO_STREAM_HDR); + + sii902x_setAIF(sii902x, REFER_TO_STREAM_HDR, REFER_TO_STREAM_HDR, REFER_TO_STREAM_HDR, 0x00); + + /* unmute audio */ + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, AUDIO_MUTE_MASK, AUDIO_MUTE_NORMAL); +} + +static void sii902x_setup(struct sii902x_data *sii902x, struct fb_info *fbi) +{ + u16 data[4]; + u32 refresh; + u8 *tmp; + mm_segment_t old_fs; + unsigned int fmt; + int i; + + dev_dbg(&sii902x->client->dev, "setup..\n"); + + sii902x->vic = mxc_edid_var_to_vic(&fbi->var); + + /* set TPI video mode */ + data[0] = PICOS2KHZ(fbi->var.pixclock) / 10; + data[2] = fbi->var.hsync_len + fbi->var.left_margin + + fbi->var.xres + fbi->var.right_margin; + data[3] = fbi->var.vsync_len + fbi->var.upper_margin + + fbi->var.yres + fbi->var.lower_margin; + refresh = data[2] * data[3]; + refresh = (PICOS2KHZ(fbi->var.pixclock) * 1000) / refresh; + data[1] = refresh * 100; + tmp = (u8 *)data; + for (i = 0; i < 8; i++) + i2c_smbus_write_byte_data(sii902x->client, i, tmp[i]); + + dump_regs(sii902x, 0, 8); + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_GET_DIFMT, (unsigned long)&fmt); + set_fs(old_fs); + if (fmt == IPU_PIX_FMT_VYU444) { + sii902x->icolor_space = YCBCR444; + dev_dbg(&sii902x->client->dev, "input color space YUV\n"); + } else { + sii902x->icolor_space = RGB; + dev_dbg(&sii902x->client->dev, "input color space RGB\n"); + } + } + + /* reg 0x08: input bus/pixel: full pixel wide (24bit), rising edge */ + sii902x->tpivmode[0] = 0x70; + /* reg 0x09: Set input format */ + if (sii902x->icolor_space == RGB) + sii902x->tpivmode[1] = + (((BITS_IN_RGB | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE); + else if (sii902x->icolor_space == YCBCR444) + sii902x->tpivmode[1] = + (((BITS_IN_YCBCR444 | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE); + else if ((sii902x->icolor_space == YCBCR422_16BITS) || (sii902x->icolor_space == YCBCR422_8BITS)) + sii902x->tpivmode[1] = + (((BITS_IN_YCBCR422 | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE); + /* reg 0x0a: set output format to RGB */ + sii902x->tpivmode[2] = 0x00; + + if (fbi->var.vmode & FB_VMODE_ASPECT_16_9) + sii902x->aspect_ratio = VMD_ASPECT_RATIO_16x9; + else if (fbi->var.vmode & FB_VMODE_ASPECT_4_3) + sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3; + else if (fbi->var.xres/16 == fbi->var.yres/9) + sii902x->aspect_ratio = VMD_ASPECT_RATIO_16x9; + else + sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3; + + if ((sii902x->vic == 6) || (sii902x->vic == 7) || + (sii902x->vic == 21) || (sii902x->vic == 22) || + (sii902x->vic == 2) || (sii902x->vic == 3) || + (sii902x->vic == 17) || (sii902x->vic == 18)) { + sii902x->tpivmode[2] &= ~0x10; /*BT.601*/ + sii902x->colorimetry = COLORIMETRY_601; + sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3; + } else { + sii902x->tpivmode[2] |= 0x10; /*BT.709*/ + sii902x->colorimetry = COLORIMETRY_709; + } + + if ((sii902x->vic == 10) || (sii902x->vic == 11) || + (sii902x->vic == 12) || (sii902x->vic == 13) || + (sii902x->vic == 14) || (sii902x->vic == 15) || + (sii902x->vic == 25) || (sii902x->vic == 26) || + (sii902x->vic == 27) || (sii902x->vic == 28) || + (sii902x->vic == 29) || (sii902x->vic == 30) || + (sii902x->vic == 35) || (sii902x->vic == 36) || + (sii902x->vic == 37) || (sii902x->vic == 38)) + sii902x->pixrep = 1; + else + sii902x->pixrep = 0; + + dev_dbg(&sii902x->client->dev, "vic %d\n", sii902x->vic); + dev_dbg(&sii902x->client->dev, "pixrep %d\n", sii902x->pixrep); + if (sii902x->aspect_ratio == VMD_ASPECT_RATIO_4x3) { + dev_dbg(&sii902x->client->dev, "aspect 4:3\n"); + } else { + dev_dbg(&sii902x->client->dev, "aspect 16:9\n"); + } + if (sii902x->colorimetry == COLORIMETRY_601) { + dev_dbg(&sii902x->client->dev, "COLORIMETRY_601\n"); + } else { + dev_dbg(&sii902x->client->dev, "COLORIMETRY_709\n"); + } + dev_dbg(&sii902x->client->dev, "hdmi capbility %d\n", sii902x->edid_cfg.hdmi_cap); + + sii902x->ocolor_space = RGB; + if (sii902x->edid_cfg.hdmi_cap) { + if (sii902x->edid_cfg.cea_ycbcr444) { + sii902x->ocolor_space = YCBCR444; + sii902x->tpivmode[2] |= 0x1; /*Ycbcr444*/ + } else if (sii902x->edid_cfg.cea_ycbcr422) { + sii902x->ocolor_space = YCBCR422_8BITS; + sii902x->tpivmode[2] |= 0x2; /*Ycbcr422*/ + } + } + + dev_dbg(&sii902x->client->dev, "write reg 0x08 0X%2X\n", sii902x->tpivmode[0]); + dev_dbg(&sii902x->client->dev, "write reg 0x09 0X%2X\n", sii902x->tpivmode[1]); + dev_dbg(&sii902x->client->dev, "write reg 0x0a 0X%2X\n", sii902x->tpivmode[2]); + + i2c_smbus_write_byte_data(sii902x->client, TPI_PIX_REPETITION, sii902x->tpivmode[0]); + i2c_smbus_write_byte_data(sii902x->client, TPI_INPUT_FORMAT_REG, sii902x->tpivmode[1]); + i2c_smbus_write_byte_data(sii902x->client, TPI_OUTPUT_FORMAT_REG, sii902x->tpivmode[2]); + + /* goto state D0*/ + sii902x_set_powerstate(sii902x, TX_POWER_STATE_D0); + + if (sii902x->edid_cfg.hdmi_cap) { + sii902x_setAVI(sii902x); + sii902x_setaudio(sii902x); + } else { + /* set last byte of TPI AVI InfoFrame for TPI AVI I/O format to take effect ?? */ + i2c_smbus_write_byte_data(sii902x->client, TPI_END_RIGHT_BAR_MSB, 0x00); + + /* mute audio */ + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, + AUDIO_MUTE_MASK, AUDIO_MUTE_MUTED); + } +} + +#ifdef CONFIG_FB_MODE_HELPERS +static int sii902x_read_edid(struct sii902x_data *sii902x, + struct fb_info *fbi) +{ + int old, dat, ret, cnt = 100; + unsigned short addr = 0x50; + u8 edid_old[SII_EDID_LEN]; + + old = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG); + + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old | 0x4); + do { + cnt--; + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG); + } while ((!(dat & 0x2)) && cnt); + + if (!cnt) { + ret = -1; + goto done; + } + + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old | 0x06); + + /* save old edid */ + memcpy(edid_old, sii902x->edid, SII_EDID_LEN); + + /* edid reading */ + ret = mxc_edid_read(sii902x->client->adapter, addr, + sii902x->edid, &sii902x->edid_cfg, fbi); + + cnt = 100; + do { + cnt--; + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old & ~0x6); + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG); + } while ((dat & 0x6) && cnt); + + if (!cnt) + ret = -1; + +done: + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old); + + if (!memcmp(edid_old, sii902x->edid, SII_EDID_LEN)) + ret = -2; + return ret; +} +#else +static int sii902x_read_edid(struct sii902x_data *sii902x, + struct fb_info *fbi) +{ + return -1; +} +#endif + +static void sii902x_enable_tmds(struct sii902x_data *sii902x) +{ + /* goto state D0*/ + sii902x_set_powerstate(sii902x, TX_POWER_STATE_D0); + + /* Turn on DVI or HDMI */ + if (sii902x->edid_cfg.hdmi_cap) + read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, + OUTPUT_MODE_MASK, OUTPUT_MODE_HDMI); + else + read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, + OUTPUT_MODE_MASK, OUTPUT_MODE_DVI); + + read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, + LINK_INTEGRITY_MODE_MASK | TMDS_OUTPUT_CONTROL_MASK | AV_MUTE_MASK, + LINK_INTEGRITY_DYNAMIC | TMDS_OUTPUT_CONTROL_ACTIVE | AV_MUTE_NORMAL); + + i2c_smbus_write_byte_data(sii902x->client, TPI_PIX_REPETITION, + sii902x->tpivmode[0]); +} + +static void sii902x_disable_tmds(struct sii902x_data *sii902x) +{ + read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, + TMDS_OUTPUT_CONTROL_MASK | AV_MUTE_MASK | OUTPUT_MODE_MASK, + TMDS_OUTPUT_CONTROL_POWER_DOWN | AV_MUTE_MUTED | OUTPUT_MODE_DVI); + + /* goto state D2*/ + sii902x_set_powerstate(sii902x, TX_POWER_STATE_D2); +} + +static void sii902x_poweron(struct sii902x_data *sii902x) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data; + + dev_dbg(&sii902x->client->dev, "power on\n"); + + /* Enable pins to HDMI */ + if (plat->enable_pins) + plat->enable_pins(); + + if (sii902x->rx_powerup) + sii902x_enable_tmds(sii902x); +} + +static void sii902x_poweroff(struct sii902x_data *sii902x) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data; + + dev_dbg(&sii902x->client->dev, "power off\n"); + + /* Disable pins to HDMI */ + if (plat->disable_pins) + plat->disable_pins(); + + if (sii902x->rx_powerup) + sii902x_disable_tmds(sii902x); +} + +static void sii902x_rx_powerup(struct sii902x_data *sii902x) +{ + + dev_dbg(&sii902x->client->dev, "rx power up\n"); + + if (sii902x->need_mode_change) { + sii902x->fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + sii902x->fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(sii902x->fbi, &sii902x->fbi->var); + sii902x->fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + sii902x->need_mode_change = false; + } + + sii902x_enable_tmds(sii902x); + + sii902x->rx_powerup = true; +} + +static void sii902x_rx_powerdown(struct sii902x_data *sii902x) +{ + dev_dbg(&sii902x->client->dev, "rx power down\n"); + + sii902x_disable_tmds(sii902x); + + sii902x->rx_powerup = false; +} + +static int sii902x_cable_connected(struct sii902x_data *sii902x) +{ + int ret; + + dev_dbg(&sii902x->client->dev, "cable connected\n"); + + sii902x->cable_plugin = true; + + /* edid read */ + ret = sii902x_read_edid(sii902x, sii902x->fbi); + if (ret == -1) + dev_err(&sii902x->client->dev, + "read edid fail\n"); + else if (ret == -2) + dev_info(&sii902x->client->dev, + "same edid\n"); + else { + if (sii902x->fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + fb_destroy_modelist(&sii902x->fbi->modelist); + + for (i = 0; i < sii902x->fbi->monspecs.modedb_len; i++) { + /*FIXME now we do not support interlaced mode */ + if (!(sii902x->fbi->monspecs.modedb[i].vmode & FB_VMODE_INTERLACED)) + fb_add_videomode(&sii902x->fbi->monspecs.modedb[i], + &sii902x->fbi->modelist); + } + + fb_var_to_videomode(&m, &sii902x->fbi->var); + mode = fb_find_nearest_mode(&m, + &sii902x->fbi->modelist); + + fb_videomode_to_var(&sii902x->fbi->var, mode); + sii902x->need_mode_change = true; + } + } + + /* ?? remain it for control back door register */ + read_modify_idx_reg(sii902x->client, INDEXED_PAGE_0, 0x0a, 0x08, 0x08); + + return 0; +} + +static void sii902x_cable_disconnected(struct sii902x_data *sii902x) +{ + dev_dbg(&sii902x->client->dev, "cable disconnected\n"); + sii902x_rx_powerdown(sii902x); + sii902x->cable_plugin = false; +} + +static void det_worker(struct work_struct *work) +{ + struct delayed_work *delay_work = to_delayed_work(work); + struct sii902x_data *sii902x = + container_of(delay_work, struct sii902x_data, det_work); + int status; + char event_string[16]; + char *envp[] = { event_string, NULL }; + + status = i2c_smbus_read_byte_data(sii902x->client, TPI_INTERRUPT_STATUS_REG); + + /* check cable status */ + if (status & HOT_PLUG_EVENT) { + /* cable connection changes */ + if ((status & HOT_PLUG_STATE) != sii902x->cable_plugin) { + if (status & HOT_PLUG_STATE) { + sprintf(event_string, "EVENT=plugin"); + sii902x_cable_connected(sii902x); + } else { + sprintf(event_string, "EVENT=plugout"); + sii902x_cable_disconnected(sii902x); + } + kobject_uevent_env(&sii902x->pdev->dev.kobj, KOBJ_CHANGE, envp); + } + } + + /* check rx power */ + if (((status & RX_SENSE_STATE) >> 3) != sii902x->rx_powerup) { + if (sii902x->cable_plugin) { + if (status & RX_SENSE_STATE) + sii902x_rx_powerup(sii902x); + else + sii902x_rx_powerdown(sii902x); + } + } + + /* clear interrupt pending status */ + i2c_smbus_write_byte_data(sii902x->client, TPI_INTERRUPT_STATUS_REG, status); +} + +static irqreturn_t sii902x_detect_handler(int irq, void *data) +{ + struct sii902x_data *sii902x = data; + + schedule_delayed_work(&(sii902x->det_work), msecs_to_jiffies(20)); + + return IRQ_HANDLED; +} + +static int sii902x_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + struct sii902x_data *sii902x = container_of(nb, struct sii902x_data, nb); + + if (strcmp(event->info->fix.id, sii902x->fbi->fix.id)) + return 0; + + switch (val) { + case FB_EVENT_MODE_CHANGE: + sii902x_setup(sii902x, fbi); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) + sii902x_poweron(sii902x); + else + sii902x_poweroff(sii902x); + break; + } + return 0; +} + +static int sii902x_TPI_init(struct i2c_client *client) +{ + struct fsl_mxc_lcd_platform_data *plat = client->dev.platform_data; + u8 devid = 0; + u16 wid = 0; + + if (plat->reset) + plat->reset(); + + /* sii902x back door register - Set terminations to default */ + i2c_smbus_write_byte_data(client, 0x82, 0x25); + /* sii902x back door register - HW debounce to 64ms (0x14) */ + i2c_smbus_write_byte_data(client, 0x7c, 0x14); + + /* Set 902x in hardware TPI mode on and jump out of D3 state */ + if (i2c_smbus_write_byte_data(client, TPI_ENABLE, 0x00) < 0) { + dev_err(&client->dev, + "cound not find device\n"); + return -ENODEV; + } + + msleep(100); + + /* read device ID */ + devid = read_idx_reg(client, INDEXED_PAGE_0, 0x03); + wid = devid; + wid <<= 8; + devid = read_idx_reg(client, INDEXED_PAGE_0, 0x02); + wid |= devid; + devid = i2c_smbus_read_byte_data(client, TPI_DEVICE_ID); + + if (devid == 0xB0) + dev_info(&client->dev, "found device %04X", wid); + else { + dev_err(&client->dev, "cound not find device\n"); + return -ENODEV; + } + + return 0; +} + +static int sii902x_disp_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + int ret = 0; + struct sii902x_data *sii902x = mxc_dispdrv_getdata(disp); + struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data; + bool found = false; + static bool inited; + + if (inited) + return -EBUSY; + + inited = true; + + setting->dev_id = plat->ipu_id; + setting->disp_id = plat->disp_id; + setting->if_fmt = IPU_PIX_FMT_RGB24; + + sii902x->fbi = setting->fbi; + sii902x->power_state = TX_POWER_STATE_D2; + sii902x->icolor_space = RGB; + sii902x->audio_mode = AMODE_SPDIF; + sii902x->audio_channels = ACHANNEL_2CH; + + sii902x->pdev = platform_device_register_simple("sii902x", 0, NULL, 0); + if (IS_ERR(sii902x->pdev)) { + dev_err(&sii902x->client->dev, + "Unable to register Sii902x as a platform device\n"); + ret = PTR_ERR(sii902x->pdev); + goto register_pltdev_failed; + } + + if (plat->io_reg) { + sii902x->io_reg = regulator_get(&sii902x->client->dev, plat->io_reg); + if (!IS_ERR(sii902x->io_reg)) { + regulator_set_voltage(sii902x->io_reg, 3300000, 3300000); + regulator_enable(sii902x->io_reg); + } + } + if (plat->analog_reg) { + sii902x->analog_reg = regulator_get(&sii902x->client->dev, plat->analog_reg); + if (!IS_ERR(sii902x->analog_reg)) { + regulator_set_voltage(sii902x->analog_reg, 1300000, 1300000); + regulator_enable(sii902x->analog_reg); + } + } + + /* Claim HDMI pins */ + if (plat->get_pins) + if (!plat->get_pins()) { + ret = -EACCES; + goto get_pins_failed; + } + + ret = sii902x_TPI_init(sii902x->client); + if (ret < 0) + goto init_failed; + + /* try to read edid */ + ret = sii902x_read_edid(sii902x, sii902x->fbi); + if (ret < 0) + dev_warn(&sii902x->client->dev, "Can not read edid\n"); + else { + INIT_LIST_HEAD(&sii902x->fbi->modelist); + if (sii902x->fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + for (i = 0; i < sii902x->fbi->monspecs.modedb_len; i++) { + /*FIXME now we do not support interlaced mode */ + if (!(sii902x->fbi->monspecs.modedb[i].vmode + & FB_VMODE_INTERLACED)) + fb_add_videomode( + &sii902x->fbi->monspecs.modedb[i], + &sii902x->fbi->modelist); + } + + fb_find_mode(&sii902x->fbi->var, sii902x->fbi, setting->dft_mode_str, + NULL, 0, NULL, setting->default_bpp); + + fb_var_to_videomode(&m, &sii902x->fbi->var); + mode = fb_find_nearest_mode(&m, + &sii902x->fbi->modelist); + fb_videomode_to_var(&sii902x->fbi->var, mode); + found = true; + } + + } + + if (!found) { + ret = fb_find_mode(&sii902x->fbi->var, sii902x->fbi, setting->dft_mode_str, + NULL, 0, NULL, setting->default_bpp); + if (!ret) { + ret = -EINVAL; + goto find_mode_failed; + } + } + + if (sii902x->client->irq) { + ret = request_irq(sii902x->client->irq, sii902x_detect_handler, + IRQF_TRIGGER_FALLING, + "SII902x_det", sii902x); + if (ret < 0) + dev_warn(&sii902x->client->dev, + "cound not request det irq %d\n", + sii902x->client->irq); + else { + /*enable cable hot plug irq*/ + i2c_smbus_write_byte_data(sii902x->client, + TPI_INTERRUPT_ENABLE_REG, + HOT_PLUG_EVENT | RX_SENSE_EVENT); + INIT_DELAYED_WORK(&(sii902x->det_work), det_worker); + /*clear hot plug event status*/ + i2c_smbus_write_byte_data(sii902x->client, + TPI_INTERRUPT_STATUS_REG, + HOT_PLUG_EVENT | RX_SENSE_EVENT); + } + + ret = device_create_file(&sii902x->pdev->dev, &dev_attr_fb_name); + if (ret < 0) + dev_warn(&sii902x->client->dev, + "cound not create sys node for fb name\n"); + ret = device_create_file(&sii902x->pdev->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&sii902x->client->dev, + "cound not create sys node for cable state\n"); + ret = device_create_file(&sii902x->pdev->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&sii902x->client->dev, + "cound not create sys node for edid\n"); + + dev_set_drvdata(&sii902x->pdev->dev, sii902x); + } + + sii902x->nb.notifier_call = sii902x_fb_event; + ret = fb_register_client(&sii902x->nb); + if (ret < 0) + goto reg_fbclient_failed; + + return ret; + +reg_fbclient_failed: +find_mode_failed: +init_failed: +get_pins_failed: + platform_device_unregister(sii902x->pdev); +register_pltdev_failed: + return ret; +} + +static void sii902x_disp_deinit(struct mxc_dispdrv_handle *disp) +{ + struct sii902x_data *sii902x = mxc_dispdrv_getdata(disp); + struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data; + + if (sii902x->client->irq) + free_irq(sii902x->client->irq, sii902x); + + fb_unregister_client(&sii902x->nb); + + sii902x_poweroff(sii902x); + + /* Release HDMI pins */ + if (plat->put_pins) + plat->put_pins(); + + platform_device_unregister(sii902x->pdev); + + kfree(sii902x); +} + +static struct mxc_dispdrv_driver sii902x_drv = { + .name = DISPDRV_SII, + .init = sii902x_disp_init, + .deinit = sii902x_disp_deinit, +}; + +static int __devinit sii902x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sii902x_data *sii902x; + int ret = 0; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -ENODEV; + + sii902x = kzalloc(sizeof(struct sii902x_data), GFP_KERNEL); + if (!sii902x) { + ret = -ENOMEM; + goto alloc_failed; + } + + sii902x->client = client; + + sii902x->disp_hdmi = mxc_dispdrv_register(&sii902x_drv); + mxc_dispdrv_setdata(sii902x->disp_hdmi, sii902x); + + i2c_set_clientdata(client, sii902x); + +alloc_failed: + return ret; +} + +static int __devexit sii902x_remove(struct i2c_client *client) +{ + struct sii902x_data *sii902x = i2c_get_clientdata(client); + + mxc_dispdrv_puthandle(sii902x->disp_hdmi); + mxc_dispdrv_unregister(sii902x->disp_hdmi); + kfree(sii902x); + return 0; +} + +static const struct i2c_device_id sii902x_id[] = { + { "sii902x", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, sii902x_id); + +static struct i2c_driver sii902x_i2c_driver = { + .driver = { + .name = "sii902x", + }, + .probe = sii902x_probe, + .remove = sii902x_remove, + .id_table = sii902x_id, +}; + +static int __init sii902x_init(void) +{ + return i2c_add_driver(&sii902x_i2c_driver); +} + +static void __exit sii902x_exit(void) +{ + i2c_del_driver(&sii902x_i2c_driver); +} + +module_init(sii902x_init); +module_exit(sii902x_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SII902x DVI/HDMI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_sii902x_elcdif.c b/drivers/video/mxc/mxcfb_sii902x_elcdif.c new file mode 100644 index 00000000..ecba5b8b --- /dev/null +++ b/drivers/video/mxc/mxcfb_sii902x_elcdif.c @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2010-2013 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_sii902x_elcdif.c + * + * @brief MXC ELCDIF Frame buffer driver for SII902x + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/mxcfb.h> +#include <linux/fsl_devices.h> +#include <linux/interrupt.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/mxc_edid.h> + +#define SII_EDID_LEN 512 +#define MXC_ENABLE 1 +#define MXC_DISABLE 2 + +struct sii902x_data { + struct platform_device *pdev; + struct i2c_client *client; + struct delayed_work det_work; + struct fb_info *fbi; + struct mxc_edid_cfg edid_cfg; + u8 cable_plugin; + u8 edid[SII_EDID_LEN]; + bool waiting_for_fb; +} sii902x; + +static void sii902x_poweron(void); +static void sii902x_poweroff(void); +static void (*sii902x_reset) (void); + +#ifdef DEBUG +static void dump_fb_videomode(struct fb_videomode *m) +{ + pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + m->refresh, m->xres, m->yres, m->pixclock, m->left_margin, + m->right_margin, m->upper_margin, m->lower_margin, + m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); +} +#else +static void dump_fb_videomode(struct fb_videomode *m) +{} +#endif + +static __attribute__ ((unused)) void dump_regs(u8 reg, int len) +{ + u8 buf[50]; + int i; + + i2c_smbus_read_i2c_block_data(sii902x.client, reg, len, buf); + for (i = 0; i < len; i++) + dev_dbg(&sii902x.client->dev, "reg[0x%02X]: 0x%02X\n", + i+reg, buf[i]); +} + +static ssize_t sii902x_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, sii902x.fbi->fix.id); + sprintf(buf+strlen(buf), "\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(fb_name, S_IRUGO, sii902x_show_name, NULL); + +static ssize_t sii902x_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (sii902x.cable_plugin == 0) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, sii902x_show_state, NULL); + +static ssize_t sii902x_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, j, len = 0; + + for (j = 0; j < SII_EDID_LEN/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + sii902x.edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, sii902x_show_edid, NULL); + +static void sii902x_setup(struct fb_info *fbi) +{ + u16 data[4]; + u32 refresh; + u8 *tmp; + int i; + + dev_dbg(&sii902x.client->dev, "Sii902x: setup..\n"); + + /* Power up */ + i2c_smbus_write_byte_data(sii902x.client, 0x1E, 0x00); + + /* set TPI video mode */ + data[0] = PICOS2KHZ(fbi->var.pixclock) / 10; + data[2] = fbi->var.hsync_len + fbi->var.left_margin + + fbi->var.xres + fbi->var.right_margin; + data[3] = fbi->var.vsync_len + fbi->var.upper_margin + + fbi->var.yres + fbi->var.lower_margin; + refresh = data[2] * data[3]; + refresh = (PICOS2KHZ(fbi->var.pixclock) * 1000) / refresh; + data[1] = refresh * 100; + tmp = (u8 *)data; + for (i = 0; i < 8; i++) + i2c_smbus_write_byte_data(sii902x.client, i, tmp[i]); + + /* input bus/pixel: full pixel wide (24bit), rising edge */ + i2c_smbus_write_byte_data(sii902x.client, 0x08, 0x70); + /* Set input format to RGB */ + i2c_smbus_write_byte_data(sii902x.client, 0x09, 0x00); + /* set output format to RGB */ + i2c_smbus_write_byte_data(sii902x.client, 0x0A, 0x00); + /* audio setup */ + i2c_smbus_write_byte_data(sii902x.client, 0x25, 0x00); + i2c_smbus_write_byte_data(sii902x.client, 0x26, 0x40); + i2c_smbus_write_byte_data(sii902x.client, 0x27, 0x00); +} + +#ifdef CONFIG_FB_MODE_HELPERS +static int sii902x_read_edid(struct fb_info *fbi) +{ + int old, dat, ret, cnt = 100; + unsigned short addr = 0x50; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + old = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x4); + do { + cnt--; + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + } while ((!(dat & 0x2)) && cnt); + + if (!cnt) { + ret = -1; + goto done; + } + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x06); + + /* edid reading */ + ret = mxc_edid_read(sii902x.client->adapter, addr, + sii902x.edid, &sii902x.edid_cfg, fbi); + + cnt = 100; + do { + cnt--; + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old & ~0x6); + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + } while ((dat & 0x6) && cnt); + + if (!cnt) + ret = -1; + +done: + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old); + return ret; +} +#else +static int sii902x_read_edid(struct fb_info *fbi) +{ + return -1; +} +#endif + +static void det_worker(struct work_struct *work) +{ + int dat; + char event_string[16]; + char *envp[] = { event_string, NULL }; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + dat = i2c_smbus_read_byte_data(sii902x.client, 0x3D); + if (dat & 0x1) { + /* cable connection changes */ + if (dat & 0x4) { + sii902x.cable_plugin = 1; + dev_dbg(&sii902x.pdev->dev, "EVENT=plugin\n"); + sprintf(event_string, "EVENT=plugin"); + + if (sii902x_read_edid(sii902x.fbi) < 0) + dev_err(&sii902x.client->dev, + "Sii902x: read edid fail\n"); + else { + if (sii902x.fbi->monspecs.modedb_len > 0) { + + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + fb_destroy_modelist(&sii902x.fbi->modelist); + + for (i = 0; i < sii902x.fbi->monspecs.modedb_len; i++) { + /*FIXME now we do not support interlaced mode */ + mode = &sii902x.fbi->monspecs.modedb[i]; + + if (!(mode->vmode & FB_VMODE_INTERLACED)) { + + dev_dbg(&sii902x.pdev->dev, "Added mode %d:", i); + dev_dbg(&sii902x.pdev->dev, + "xres = %d, yres = %d, freq = %d, vmode = %d, flag = %d\n", + mode->xres, mode->yres, mode->refresh, + mode->vmode, mode->flag); + + fb_add_videomode(mode, &sii902x.fbi->modelist); + } + } + + fb_var_to_videomode(&m, &sii902x.fbi->var); + dump_fb_videomode(&m); + + mode = fb_find_nearest_mode(&m, + &sii902x.fbi->modelist); + + fb_videomode_to_var(&sii902x.fbi->var, mode); + + sii902x.fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + sii902x.fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(sii902x.fbi, &sii902x.fbi->var); + sii902x.fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + } + /* Power on sii902x */ + sii902x_poweron(); + } + } else { + sii902x.cable_plugin = 0; + dev_dbg(&sii902x.pdev->dev, "EVENT=plugout\n"); + sprintf(event_string, "EVENT=plugout"); + /* Power off sii902x */ + sii902x_poweroff(); + } + kobject_uevent_env(&sii902x.pdev->dev.kobj, KOBJ_CHANGE, envp); + } + i2c_smbus_write_byte_data(sii902x.client, 0x3D, dat); + + dev_dbg(&sii902x.pdev->dev, "exit %s\n", __func__); + +} + +static irqreturn_t sii902x_detect_handler(int irq, void *data) +{ + if (sii902x.fbi) + schedule_delayed_work(&(sii902x.det_work), msecs_to_jiffies(20)); + else + sii902x.waiting_for_fb = true; + + return IRQ_HANDLED; +} + +static int sii902x_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + switch (val) { + case FB_EVENT_FB_REGISTERED: + if (sii902x.fbi == NULL) { + sii902x.fbi = fbi; + if (sii902x.waiting_for_fb) + det_worker(NULL); + } + fb_show_logo(fbi, 0); + break; + case FB_EVENT_MODE_CHANGE: + sii902x_setup(fbi); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + dev_dbg(&sii902x.pdev->dev, "FB_BLANK_UNBLANK\n"); + sii902x_poweron(); + } else { + dev_dbg(&sii902x.pdev->dev, "FB_BLANK_BLANK\n"); + sii902x_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = sii902x_fb_event, +}; + +static int __devinit sii902x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i, dat, ret; + struct fsl_mxc_lcd_platform_data *plat = client->dev.platform_data; + struct fb_info edid_fbi; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__);; + + sii902x.client = client; + + /* Claim HDMI pins */ + if (plat->get_pins) + if (!plat->get_pins()) + return -EACCES; + + if (plat->reset) { + sii902x_reset = plat->reset; + sii902x_reset(); + } + + /* Set 902x in hardware TPI mode on and jump out of D3 state */ + if (i2c_smbus_write_byte_data(sii902x.client, 0xc7, 0x00) < 0) { + dev_err(&sii902x.client->dev, + "Sii902x: cound not find device\n"); + return -ENODEV; + } + + /* read device ID */ + for (i = 10; i > 0; i--) { + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1B); + printk(KERN_DEBUG "Sii902x: read id = 0x%02X", dat); + if (dat == 0xb0) { + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1C); + printk(KERN_DEBUG "-0x%02X", dat); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1D); + printk(KERN_DEBUG "-0x%02X", dat); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x30); + printk(KERN_DEBUG "-0x%02X\n", dat); + break; + } + } + if (i == 0) { + dev_err(&sii902x.client->dev, + "Sii902x: cound not find device\n"); + return -ENODEV; + } + + /* try to read edid */ + ret = sii902x_read_edid(&edid_fbi); + if (ret < 0) + dev_warn(&sii902x.client->dev, "Can not read edid\n"); + + if (ret >= 0) + mxcfb_elcdif_register_mode(edid_fbi.monspecs.modedb, + edid_fbi.monspecs.modedb_len, MXC_DISP_DDC_DEV); + + if (sii902x.client->irq) { + ret = request_irq(sii902x.client->irq, sii902x_detect_handler, + IRQF_TRIGGER_FALLING, + "SII902x_det", &sii902x); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not request det irq %d\n", + sii902x.client->irq); + else { + /*enable cable hot plug irq*/ + i2c_smbus_write_byte_data(sii902x.client, 0x3c, 0x01); + INIT_DELAYED_WORK(&(sii902x.det_work), det_worker); + } + ret = device_create_file(&sii902x.pdev->dev, &dev_attr_fb_name); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for fb name\n"); + ret = device_create_file(&sii902x.pdev->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for cable state\n"); + ret = device_create_file(&sii902x.pdev->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for edid\n"); + + } + + fb_register_client(&nb); + + dev_dbg(&sii902x.pdev->dev, "%s exit\n", __func__);; + + return 0; +} + +static int __devexit sii902x_remove(struct i2c_client *client) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x.client->dev.platform_data; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + fb_unregister_client(&nb); + sii902x_poweroff(); + + /* Release HDMI pins */ + if (plat->put_pins) + plat->put_pins(); + + return 0; +} + +static void sii902x_poweron(void) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x.client->dev.platform_data; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + /* Enable pins to HDMI */ + if (plat->enable_pins) + plat->enable_pins(); + + /* Turn on DVI or HDMI */ + if (sii902x.edid_cfg.hdmi_cap) + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x01); + else + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x00); + return; +} + +static void sii902x_poweroff(void) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x.client->dev.platform_data; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + /* disable tmds before changing resolution */ + if (sii902x.edid_cfg.hdmi_cap) + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x11); + else + i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x10); + + /* Disable pins to HDMI */ + if (plat->disable_pins) + plat->disable_pins(); + + return; +} + +static const struct i2c_device_id sii902x_id[] = { + { "sii902x", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, sii902x_id); + +static struct i2c_driver sii902x_i2c_driver = { + .driver = { + .name = "sii902x", + }, + .probe = sii902x_probe, + .remove = sii902x_remove, + .id_table = sii902x_id, +}; + +static int __init sii902x_init(void) +{ + int ret; + + memset(&sii902x, 0, sizeof(sii902x)); + + sii902x.pdev = platform_device_register_simple("sii902x", 0, NULL, 0); + if (IS_ERR(sii902x.pdev)) { + printk(KERN_ERR + "Unable to register Sii902x as a platform device\n"); + ret = PTR_ERR(sii902x.pdev); + goto err; + } + + return i2c_add_driver(&sii902x_i2c_driver); +err: + return ret; +} + +static void __exit sii902x_exit(void) +{ + i2c_del_driver(&sii902x_i2c_driver); + platform_device_unregister(sii902x.pdev); +} + +module_init(sii902x_init); +module_exit(sii902x_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SII902x DVI/HDMI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/ntx_hwconfig.h b/drivers/video/mxc/ntx_hwconfig.h new file mode 120000 index 00000000..cf827696 --- /dev/null +++ b/drivers/video/mxc/ntx_hwconfig.h @@ -0,0 +1 @@ +../../../arch/arm/mach-mx6/ntx_hwconfig.h
\ No newline at end of file diff --git a/drivers/video/mxc/ntx_s1d13521fb.h b/drivers/video/mxc/ntx_s1d13521fb.h new file mode 100644 index 00000000..e9994a20 --- /dev/null +++ b/drivers/video/mxc/ntx_s1d13521fb.h @@ -0,0 +1,169 @@ +//----------------------------------------------------------------------------- +// +// linux/drivers/video/epson/s1d13521fb.h -- +// Function header for Epson S1D13521 controller frame buffer drivers. +// +// Copyright(c) Seiko Epson Corporation 2000-2008. +// All rights reserved. +// +// This file is subject to the terms and conditions of the GNU General Public +// License. See the file COPYING in the main directory of this archive for +// more details. +// +//---------------------------------------------------------------------------- + +#ifndef __NTX_S1D13521FB_H__ +#define __NTX_S1D13521FB_H__ + + #include <linux/kernel.h> + #include <linux/fb.h> + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #define _LOOP_TIMEOUT_READY 500000 + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#define __EB600__ +//#define __EB500__ + //#define __EB600EM__ + //#define __EB600E__ + #define __COOKIE__ + + //#if (_DEVICE_CODE == _DEVICE_CODE_EB600) + #if defined( __EB600__) + #define _DEIVE_NAME "EB600" + //#elif (_DEVICE_CODE == _DEVICE_CODE_EB500) + #elif defined( __EB500__) + #define _DEIVE_NAME "EB500" + //#elif (_DEVICE_CODE == _DEVICE_CODE_EB600E) + #elif defined( __EB600E__) + #define _DEIVE_NAME "EB600E" + //#elif (_DEVICE_CODE == _DEVICE_CODE_COOKIE) + #elif defined( __COOKIE__) + #define _DEIVE_NAME "COOKIE" + //#elif (_DEVICE_CODE == _DEVICE_CODE_EB600EM) + #elif defined( __EB600EM__) + #define _DEIVE_NAME "EB600EM" + #else + #error "What device!!!!!!!" + #endif + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// #define __TURN_ON_FORCE_DISPLAY__ +// #define __TURN_ON_EPSON_INIT__ + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// #define __5_INCH__ + #define __6_INCH__ +// #define __8_INCH__ +// #define __9D7_INCH__ + +// #define _WAVE_FORM_BASE 0x886 + #define _WAVE_FORM_BASE 0x1000 + + #define _S1D13522_WAVE_FORM_BASE 0xb0ea + #define _S1D13522_WAVE_FORM_BASE_H 0x0003 + +#if defined(__5_INCH__) + #define _PANEL_WIDTH 600 + #define _PANEL_HEIGHT 800 + + #define S1D_DISPLAY_WIDTH 600 + #define S1D_DISPLAY_HEIGHT 800 + + #define _INIT_HSIZE 800 + #define _INIT_VSIZE 600 + #define _INIT_FSLEN 4 + #define _INIT_FBLEN 4 + #define _INIT_FELEN 10 + #define _INIT_LSLEN 10 + #define _INIT_LBLEN 4 + #define _INIT_LELEN 100 + #define _INIT_PIXCLKDIV 6 + #define _INIT_SDRV_CFG (100 | (1<< 8) | (1<<9)) + #define _INIT_GDRV_CFG 0x2 + #define _INIT_LUTIDXFMT (4 | (1<<7)) + + #define _INIT_ROTMODE 3 // rotation mode = 270 degrees + +#elif defined(__6_INCH__) + #define _PANEL_WIDTH _INIT_VSIZE + #define _PANEL_HEIGHT _INIT_HSIZE + + #define S1D_DISPLAY_WIDTH _INIT_VSIZE + #define S1D_DISPLAY_HEIGHT _INIT_HSIZE + + //#define _INIT_HSIZE 800 // max panel original width . + //#define _INIT_VSIZE 600 // max panel original height . + #define _INIT_HSIZE 1600 // max panel original width . + #define _INIT_VSIZE 1200 // max panel original height . + + #define _INIT_FSLEN 4 + #define _INIT_FBLEN 4 + #define _INIT_FELEN 10 + #define _INIT_LSLEN 10 + #define _INIT_LBLEN 4 + #define _INIT_LELEN 100 + #define _INIT_PIXCLKDIV 6 + #define _INIT_SDRV_CFG (100 | (1<< 8) | (1<<9)) + #define _INIT_GDRV_CFG 0x2 + #define _INIT_LUTIDXFMT (4 | (1<<7)) + + #define _INIT_ROTMODE 3 // rotation mode = 270 degrees + //#define _INIT_ROTMODE 1 // rotation mode = 270 degrees + +#elif defined(__8_INCH__) +#elif defined(__9D7_INCH__) + #define _PANEL_WIDTH 1200 + #define _PANEL_HEIGHT 826 + + #define S1D_DISPLAY_WIDTH 1200 + #define S1D_DISPLAY_HEIGHT 826 + + #define _INIT_HSIZE 1200 + #define _INIT_VSIZE 826 + #define _INIT_FSLEN 0 + #define _INIT_FBLEN 4 + #define _INIT_FELEN 4 + #define _INIT_LSLEN 4 + #define _INIT_LBLEN 10 + #define _INIT_LELEN 60 + #define _INIT_PIXCLKDIV 3 + #define _INIT_SDRV_CFG (100 | (1<< 8) | (1<<9)) + #define _INIT_GDRV_CFG 0x2 + #define _INIT_LUTIDXFMT (4 | (1<<7)) + + #define _INIT_ROTMODE 0 // rotation mode = 270 degrees +//#define _INIT_ROTMODE 1 // rotation mode = 270 degrees +#else + #error "give me panel size" + +#endif + #define _PANEL_PEXIL_BPP 8 + #define _PANEL_TOTAL_PEXIL (_PANEL_WIDTH*_PANEL_HEIGHT) + #define _PANEL_BUFFER_SIZE (_PANEL_TOTAL_PEXIL*_PANEL_PEXIL_BPP)/8 + #define S1D_DISPLAY_BPP 4 + +#define SHOW_PROGRESS_BAR +// Joseph 20101116 +#define _PROGRESS_BAR_X_ 360 +#define _PROGRESS_BAR_Y_ 740 +#define _PROGRESS_BAR_ICONS_ 7 + +#define __LINUX_VERSION__ "2.6.18.2-ntx-cookie-v2.9" +#endif //__NTX_S1D13521FB_H__ diff --git a/drivers/video/mxc/s1d13521.h b/drivers/video/mxc/s1d13521.h new file mode 100644 index 00000000..305ac0d3 --- /dev/null +++ b/drivers/video/mxc/s1d13521.h @@ -0,0 +1,268 @@ +/*=============================================================================== +** Generic Header information generated by 13521CFG.EXE (Build 4) +** (C)SEIKO EPSON CORPORATION 2002-2007. All rights reserved. +** +** DISPLAYS WxH FREQ SUBTYPE +** ------------- ----------- ------- ------------------------------------------- +** *LCD1=Parallel 800x600 NA EPD Panel +** +** DIMENSIONS WxHxBPP @ STRIDE START SADDR ADDITIONAL +** ----------- ---------------------- ------- --------- ----------------------- +** *Main 800x600x4 @ 800 NA 000EA600h LUTAuto=on +** +** CLOCKS FREQ SOURCE +** ------------- ----------- --------------------------------------------------- +** INCLK 132.000 MHz PLL +** SYSCLK 66.000 MHz PLL/2 +** PCLK 26.400 MHz PLL/5 +** SPICLK 13.200 MHz SYSCLK/5 +** I2CCLK 4.125 MHz PLL/16 +** SDRAMCLK 132.000 MHz PLL +** SDRAMREFCLK 63.954 KHz CLKI/516 +** +** This file defines the configuration environment and registers, +** which can be used by any software, such as display drivers. +** +** Note: If you transfer this file to any non-PC system, use ASCII +** mode (not BINARY) to maintain system-specific line terminators. +**===============================================================================*/ +#ifndef __S1D13521_H__ + #define __S1D13521_H__ + + #include "ntx_s1d13521fb.h" + + +//#define IMX233_EPD +#define _S1D13522_ + +#ifdef IMX233_EPD +void gpio_epd_init(void); +void gpio_epd_cmd_write(unsigned short w); +void gpio_epd_data_write(unsigned short w); +unsigned short gpio_epd_data_read(void); +unsigned short gpio_epd_hrdy(void); +int gpio_epd_data_write_buffer (unsigned short *pBuffer, int length); + +#define EPD_WRITE_CMD(a) gpio_epd_cmd_write(a) +#define EPD_WRITE_DATA(a) gpio_epd_data_write(a) +#define EPD_READ_DATA() gpio_epd_data_read() +#endif + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + ///////////////////////////////////////////////////////////////////////// + // + // + // + ///////////////////////////////////////////////////////////////////////// + #define _EPSON_REG_STATUS 0x00a + #define _EPSON_REG_STATUS_PLL_LOCK 0x0001 + #define _EPSON_REG_STATUS_SDRAM_INITED 0x0002 + #define _EPSON_REG_STATUS_DPE_BUSY 0x0004 + #define _EPSON_REG_STATUS_HOST_MEM_BUSY 0x0008 + #define _EPSON_REG_STATUS_SDRAM_BUSY 0x0010 + #define _EPSON_REG_STATUS_HOST_BUS_BUSY 0x0020 + #define _EPSON_REG_STATUS_SPI_BUSY 0x0040 + #define _EPSON_REG_STATUS_I2C_BUSY 0x0080 + #define _EPSON_REG_STATUS_PWR_SEQ 0x0100 + #define _EPSON_REG_STATUS_SDRAM_SELF_REFRESH 0x0200 + #define _EPSON_REG_STATUS_PM_STATE 0x0c00 + #define _EPSON_REG_STATUS_PM_STATE_UNINIT 0x0000 + #define _EPSON_REG_STATUS_PM_STATE_RUN 0x0400 + #define _EPSON_REG_STATUS_PM_STATE_STANDBY 0x0800 + #define _EPSON_REG_STATUS_PM_STATE_SLEEP 0x0c00 + #define _EPSON_REG_STATUS_OSC_BYPASS 0x1000 + #define _EPSON_REG_STATUS_PM_BUSY 0x4000 + #define _EPSON_REG_STATUS_3W_BUSY 0x8000 + + ///////////////////////////////////////////////////////////////////////// + // + // + // + ///////////////////////////////////////////////////////////////////////// + #define _EPSON_REG_WRITE_IMAGE 0x154 + + ///////////////////////////////////////////////////////////////////////// + // + // + // + ///////////////////////////////////////////////////////////////////////// + #define _EPSON_REG_SPI_READ_DATA 0x200 + #define _EPSON_REG_SPI_WRITE_DATA 0x202 + #define _EPSON_REG_SPI_WRITE_DATA_ENABLE _BIT8 + #define _EPSON_REG_SPI_CONTROL 0x204 + #define _EPSON_REG_SPI_CONTROL_ENBALE _BIT0 + #define _EPSON_REG_SPI_CONTROL_DISBALE 0 + #define _EPSON_REG_SPI_CONTROL_CLK_POL _BIT1 + #define _EPSON_REG_SPI_CONTROL_CLK_PHASE _BIT2 + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER (_BIT3+_BIT4+_BIT5) + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER_2_1 (0 << 3) + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER_3_1 (1 << 3) + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER_4_1 (2 << 3) + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER_5_1 (3 << 3) + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER_6_1 (4 << 3) + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER_7_1 (5 << 3) + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER_8_1 (6 << 3) + #define _EPSON_REG_SPI_CONTROL_CLK_DIVIDER_9_1 (7 << 3) + #define _EPSON_REG_SPI_CONTROL_SPEED_HIGH _BIT6 + #define _EPSON_REG_SPI_CONTROL_ACCESS_BY_DPE _BIT7 + #define _EPSON_REG_SPI_CONTROL_ACCESS_BY_HOST 0 + #define _EPSON_REG_SPI_STATUS 0x206 + #define _EPSON_REG_SPI_STATUS_READ_READY _BIT0 + #define _EPSON_REG_SPI_STATUS_READ_OVERRUN _BIT1 + #define _EPSON_REG_SPI_STATUS_WRITE_EMPTY _BIT2 + #define _EPSON_REG_SPI_STATUS_BUSY _BIT3 + #define _EPSON_REG_SPI_CS_CONTROL 0x208 + #define _EPSON_REG_SPI_CS_CONTROL_ENABLE _BIT0 + + ///////////////////////////////////////////////////////////////////////// + // + // + // + ///////////////////////////////////////////////////////////////////////// + #define _EPSON_REG_THERMAL_SENSOR_CFG 0x210 + #define _EPSON_REG_THERMAL_SENSOR_STATUS 0x212 + #define _EPSON_REG_THERMAL_SENSOR_STATUS_BUSY _BIT0 + #define _EPSON_REG_THERMAL_SENSOR_STATUS_ID _BIT1 + #define _EPSON_REG_THERMAL_SENSOR_STATUS_SCL _BIT4 + #define _EPSON_REG_THERMAL_SENSOR_STATUS_SDA _BIT5 + #define _EPSON_REG_THERMAL_SENSOR_TRIGGER 0x214 + #define _EPSON_REG_THERMAL_SENSOR_TRIGGER_READ _BIT0 + #define _EPSON_REG_THERMAL_SENSOR_DATA 0x216 + + ///////////////////////////////////////////////////////////////////////// + // + // + // + ///////////////////////////////////////////////////////////////////////// + #define _EPSON_REG_SOURCE_DRV_CFG 0x30c + + ///////////////////////////////////////////////////////////////////////// + // + // + // + ///////////////////////////////////////////////////////////////////////// + #define _EPSON_REG_GATE_DRV_CFG 0x30e + + ///////////////////////////////////////////////////////////////////////// + // + // + // + ///////////////////////////////////////////////////////////////////////// + #define _EPSON_REG_DPE_CONTROL 0x330 + #define _EPSON_REG_DPE_CONTROL_LUT_FORMAT_P2N (0 << 0) + #define _EPSON_REG_DPE_CONTROL_LUT_FORMAT_P3N (2 << 0) + #define _EPSON_REG_DPE_CONTROL_LUT_FORMAT_P4N (4 << 0) + #define _EPSON_REG_DPE_CONTROL_LUT_FORMAT_P5N (6 << 0) + + #define _EPSON_REG_DPE_CONTROL_AUTO_WAVEFORM_OFF (0 << 6) + #define _EPSON_REG_DPE_CONTROL_AUTO_WAVEFORM_ON (1 << 6) + #define _EPSON_REG_DPE_CONTROL_AUTO_LUT_OFF (0 << 7) + #define _EPSON_REG_DPE_CONTROL_AUTO_LUT_ON (1 << 7) + + #define _EPSON_REG_DPE_CONTROL_LAST_WAVEFORM_MODE (0xf << 8) + + #define _EPSON_REG_DPE_CONTROL_RESET_OFF (0 << 15) + #define _EPSON_REG_DPE_CONTROL_RESET_ON (1 << 15) + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #define S1D_13521 + + #define S1D_DISPLAY_BPP 4 +// #define S1D_DISPLAY_BPP 8 +#if 1 +// #define S1D_DISPLAY_SCANLINE_BYTES 600 + #define S1D_DISPLAY_FRAME_RATE 0 +// #define S1D_PHYSICAL_REG_ADDR 0x00000000L + #define S1D_PHYSICAL_REG_ADDR 0xA0000000L + #define S1D_PHYSICAL_REG_SIZE 90L + #define S1D_PHYSICAL_VMEM_REQUIRED 640000L + #define S1D_PALETTE_SIZE 256 + #define S1D_POWER_DELAY_OFF 0 + #define S1D_POWER_DELAY_ON 0 + #define S1D_HWBLT + #define S1D_SWBLT +#endif + #define S1D_DISPLAY_PCLK 26400000L + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if 0 + #define BS60_INIT_LUTIDXFMT (_EPSON_REG_DPE_CONTROL_LUT_FORMAT_P4N | \ + _EPSON_REG_DPE_CONTROL_AUTO_LUT_ON) +#endif + + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #define WF_MODE_INIT 0 + #define WF_MODE_MU 1 + #define WF_MODE_GU 2 + #define WF_MODE_GC 3 + #define WF_MODE_PU 4 + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + typedef unsigned short S1D_INDEX; + typedef unsigned short S1D_VALUE; + + #define S1D_INSTANTIATE_REGISTERS(scope_prefix,variable_name) \ + scope_prefix S1D_VALUE variable_name[] = \ + { \ + INIT_SYS_RUN, 0, \ + INIT_DSPE_CFG, 5, _INIT_HSIZE, \ + _INIT_VSIZE, \ + _INIT_SDRV_CFG, \ + _INIT_GDRV_CFG, \ + _INIT_LUTIDXFMT, \ + INIT_DSPE_TMG, 5, _INIT_FSLEN, \ + (_INIT_FELEN<<8)|_INIT_FBLEN, \ + _INIT_LSLEN, \ + (_INIT_LELEN<<8)|_INIT_LBLEN, \ + _INIT_PIXCLKDIV, \ + RD_WFM_INFO, 2, _WAVE_FORM_BASE, 0, \ + UPD_GDRV_CLR, 0, \ + WAIT_DSPE_TRG, 0, \ + INIT_ROTMODE, 1, (_INIT_ROTMODE << 8) \ + } + +#if 0 // for s1d13522 auto boot mode. + #define S1D13522_INSTANTIATE_REGISTERS(scope_prefix_1,s1d13522_variable_name) \ + scope_prefix_1 S1D_VALUE s1d13522_variable_name[] = \ + { \ + INIT_SYS_RUN, 0, \ + INIT_DSPE_CFG, 5, _INIT_HSIZE, \ + _INIT_VSIZE, \ + _INIT_SDRV_CFG, \ + _INIT_GDRV_CFG, \ + _INIT_LUTIDXFMT, \ + INIT_DSPE_TMG, 5, _INIT_FSLEN, \ + (_INIT_FELEN<<8)|_INIT_FBLEN, \ + _INIT_LSLEN, \ + (_INIT_LELEN<<8)|_INIT_LBLEN, \ + _INIT_PIXCLKDIV, \ + RD_WFM_INFO, 2, _S1D13522_WAVE_FORM_BASE, _S1D13522_WAVE_FORM_BASE_H, \ + UPD_GDRV_CLR, 0, \ + WAIT_DSPE_TRG, 0, \ + INIT_ROTMODE, 1, (_INIT_ROTMODE << 8) \ + } +#endif + + +#endif // __S1D13521_H__ diff --git a/drivers/video/mxc/s1d13521_debug.h b/drivers/video/mxc/s1d13521_debug.h new file mode 100644 index 00000000..2a0cd5af --- /dev/null +++ b/drivers/video/mxc/s1d13521_debug.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// +// linux/drivers/video/epson/s1d13521fb.h -- +// Function header for Epson S1D13521 controller frame buffer drivers. +// +// Copyright(c) Seiko Epson Corporation 2000-2008. +// All rights reserved. +// +// This file is subject to the terms and conditions of the GNU General Public +// License. See the file COPYING in the main directory of this archive for +// more details. +// +//---------------------------------------------------------------------------- + +#ifndef __S1D13521_DEBUG_H__ + #define __S1D13521_DEBUG_H__ + + #include <linux/kernel.h> + + //----------------------------------------------------------------------------- + // Global Function Prototypes + //----------------------------------------------------------------------------- + +// #undef CONFIG_FB_EPSON_DEBUG_PRINTK + + #ifdef CONFIG_FB_EPSON_DEBUG_PRINTK +// #define dbg_info(fmt, args...) do { printk(KERN_INFO fmt, ## args); } while (0) + #define dbg_info(fmt, args...) do { printk(KERN_INFO fmt, ## args); } while (0) + #define DEBUGMSG(con, fmt, args...) do { if(con != 0) printk(fmt, ## args); } while (0) + #define DEBUGMSG_FUNC_ENTER(con) do { if(con != 0) printk("%s]+++\n", __FUNCTION__); } while (0) + #define DEBUGMSG_FUNC_EXIT(con) do { if(con != 0) printk("%s]---\n", __FUNCTION__); } while (0) + #define DEBUGMSG_FUNC_EXIT_CODE(con, x) do { if(con != 0) printk("%s]--- ret=%d\n", __FUNCTION__, x); } while (0) + #else + #define dbg_info(fmt, args...) do { } while (0) + #define DEBUGMSG(con, fmt, args...) do { } while (0) + #define DEBUGMSG_FUNC_ENTER(con) do { } while (0) + #define DEBUGMSG_FUNC_EXIT(con) do { } while (0) + #define DEBUGMSG_FUNC_EXIT_CODE(con, code) do { } while (0) + #endif + + #ifdef CONFIG_FB_EPSON_DEBUG_PRINTK + #define assert(expr) \ + if(!(expr)) { \ + printk( "Assertion failed! %s,%s,%s,line=%d\n",\ + #expr,__FILE__,__FUNCTION__,__LINE__); \ + BUG(); \ + } + #else + #define assert(expr) + #endif + + +#endif //__S1D13521_DEBUG_H__ diff --git a/drivers/video/mxc/s1d13521fb.h b/drivers/video/mxc/s1d13521fb.h new file mode 100644 index 00000000..2ae49b63 --- /dev/null +++ b/drivers/video/mxc/s1d13521fb.h @@ -0,0 +1,214 @@ +//----------------------------------------------------------------------------- +// +// linux/drivers/video/epson/s1d13521fb.h -- +// Function header for Epson S1D13521 controller frame buffer drivers. +// +// Copyright(c) Seiko Epson Corporation 2000-2008. +// All rights reserved. +// +// This file is subject to the terms and conditions of the GNU General Public +// License. See the file COPYING in the main directory of this archive for +// more details. +// +//---------------------------------------------------------------------------- + +#ifndef __S1D13521FB_H__ + #define __S1D13521FB_H__ + + #include <linux/kernel.h> + #include <linux/fb.h> + #include "DataType.h" + + #include "s1d13521ioctl.h" + #include "s1d13521.h" + + #include "s1d13521_debug.h" + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #define HRDY_TIMEOUT_MS 5000 + + //#define CONFIG_FB_EPSON_PROC + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // In Indirect Mode, a copy of the framebuffer is kept in system memory. + // A timer periodically writes this copy to the "real" framebuffer in + // hardware. This copy is called a virtual framebuffer. + + //---------------------------------------------------------------------------- + // Global structures used by s1d13521fb frame buffer code + //---------------------------------------------------------------------------- + typedef struct + { + volatile unsigned char *RegAddr; + unsigned RegAddrMappedSize; + volatile unsigned char *DataPort; + unsigned DataPortSzie; + volatile unsigned char *CommandPort; + unsigned CommandPortSzie; + + u32 VirtualFramebufferAddr; + int blank_mode; + u32 pseudo_palette[16]; + }FB_INFO_S1D13521; + + extern struct fb_fix_screeninfo s1d13521fb_fix; + extern struct fb_info s1d13521_fb; + extern FB_INFO_S1D13521 s1d13521fb_info; + extern char *s1d13521fb_version; + + //----------------------------------------------------------------------------- + // Global Function Prototypes + //----------------------------------------------------------------------------- + + #ifdef CONFIG_FB_EPSON_PROC + int __devinit s1d13521proc_init(void); + void __devexit s1d13521proc_terminate(void); + #endif + + #ifdef CONFIG_FB_EPSON_PCI + int __devinit s1d13521pci_init(long *physicalAddress); + void __devexit s1d13521pci_terminate(void); + #endif + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + typedef struct TAG_ST_A_GPIO_INFO{ + const UINT32 Info; + const char *Name; + } ST_A_GPIO_INFO, *PST_A_GPIO_INFO; + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + typedef enum { + _EPSON_ERROR_SUCCESS=0, + _EPSON_ERROR_NOT_READY, + _EPSON_ERROR_TIMEOUT, + _EPSON_ERROR_WRITE_FAIL, + } EN_EPSON_ERROR_CODE; + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + typedef struct TAG_ST_DESC_ACCESS_BUF { + UINT32 Len; + PUINT pData; + } ST_DESC_ACCESS_BUF, *PST_DESC_ACCESS_BUF; + + #pragma pack(1) + typedef struct TAG_ST_COMMAND_PACKAGE { + ST_DESC_ACCESS_BUF Write; + ST_DESC_ACCESS_BUF Read; + UINT16 Command; + } ST_COMMAND_PACKAGE, *PST_COMMAND_PACKAGE; + #pragma pack() + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + typedef struct TAG_ST_PARTIAL_RECT { + UINT16 StartX; + UINT16 StartY; + UINT16 Width; + UINT16 Height; + } ST_PARTIAL_RECT, *PST_PARTIAL_RECT; + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + int __devinit s1d13521if_InterfaceInit(FB_INFO_S1D13521 *info); + void __devexit s1d13521if_InterfaceTerminate(FB_INFO_S1D13521 *info); + + int BusIssueCmd(unsigned ioctlcmd,s1d13521_ioctl_cmd_params *params,int numparams); + + void BusIssueWriteBuf(u16 *ptr16, unsigned copysize16); + void BusIssueReadBuf(u16 *ptr16, unsigned copysize16); + + u16 BusIssueReadReg(u16 Index); + EN_EPSON_ERROR_CODE BusIssueWriteReg(u16 Index, u16 Value); + EN_EPSON_ERROR_CODE BusIssueWriteRegX(u16 Index, u16 Value); + EN_EPSON_ERROR_CODE BusIssueCmdX(UINT16 Cmd); + + EN_EPSON_ERROR_CODE BusIssueWriteRegBuf(u16 Index, PUINT16 pData, UINT32 Length); + EN_EPSON_ERROR_CODE BusWaitForHRDY(void); + void BusIssueDoRefreshDisplay(unsigned cmd,unsigned mode); + void BusIssueInitDisplay(void); + void BusIssueInitRegisters(void); + + int pvi_GoToSleep(void); + int pvi_GoToNormal(void); + + +// int s1d13521if_CmdPackage(PST_COMMAND_PACKAGE pCmdPkg); + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + int pvi_Init(VOID); + VOID pvi_Deinit(VOID); + + int pvi_ioctl_NewImage(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_StopNewImage(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_DisplayImage(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_PartialImage(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_DisplayPartial(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_Reset(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_SetDepth(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_EraseDisplay(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_Rotate(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_Positive(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_Negative(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_GoToNormal(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_GoToSleep(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_GoToStandBy(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_WriteToFlash(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_ReadFromFlash(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_Init(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_AutoRefreshOn(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_AutoRefreshOff(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_SetRefresh(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_ForcedRefresh(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_GetRefresh(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_RestoreImage(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_ControllerVersion(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_SoftwareVersion(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_DisplaySize(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_GetStatus(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_Temperature(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_WriteRegister(PTDisplayCommand puDisplayCommand); + int pvi_ioctl_ReadRegister(PTDisplayCommand puDisplayCommand); + + int pvi_SwitchCommand(PTDisplayCommand pDisplayCommand); + //int Epson_displayCMD(PST_IMAGE_PGM PST); + //int Epson_displayCMD_MEM(PST_IMAGE_PGM_MEM PST); //KEG 20090914 + //int Epson_LoadImageArea(PTloadImageArea area); // KEG 20090814 + + //BOOL BusIssueFlashOperation(PS1D13532_FLASH_PACKAGE pFlashControl); + BOOL BusIssueReset(void); + //int ImagePGM(PST_IMAGE_PGM pPGM); + + VOID DeviceReset(VOID); + + +#endif //__S1D13521FB_H__ diff --git a/drivers/video/mxc/s1d13521ioctl.h b/drivers/video/mxc/s1d13521ioctl.h new file mode 100644 index 00000000..0def971c --- /dev/null +++ b/drivers/video/mxc/s1d13521ioctl.h @@ -0,0 +1,409 @@ +//----------------------------------------------------------------------------- +// +// linux/drivers/video/epson/s1d1352ioctl.h -- IOCTL definitions for Epson +// S1D13521 controller frame buffer driver. +// +// Copyright(c) Seiko Epson Corporation 2008. +// All rights reserved. +// +// This file is subject to the terms and conditions of the GNU General Public +// License. See the file COPYING in the main directory of this archive for +// more details. +// +//---------------------------------------------------------------------------- +#ifndef __EPSON_IOCTL_H__ + #define __EPSON_IOCTL_H__ + + #include "ntx_s1d13521fb.h" + +/* ioctls + 0x45 is 'E' */ +struct s1d13521_ioctl_hwc +{ + unsigned addr; + unsigned value; + void* buffer; +}; + +#define S1D13521_REGREAD 0x4540 +#define S1D13521_REGWRITE 0x4541 +#define S1D13521_MEMBURSTREAD 0x4546 +#define S1D13521_MEMBURSTWRITE 0x4547 +#define S1D13521_VBUF_REFRESH 0x4548 // KEG 20090807 unused, + // but included to stay compatible with latest epson driver + + + +#define S1D13521_DISPLAY 0x4539 +#define S1D13521_SETDEPTH 0x4550 +#define S1D13521_DISPLAY_KAY 0x4542 //kay 20090511 +#define S1D13521_FORCE_REFRESH 0x4543 + +#define S1D13521_FORCE_REFRESH_FULL_AREA 0x4544 +#define S1D13521_FORCE_REFRESH_FULL_FULL 0x4545 +#define S1D13521_FORCE_REFRESH_PART_AREA 0x4556 +#define S1D13521_FORCE_REFRESH_PART_FULL 0x4557 + +#define S1D13521_FORCE_VFB_TO_SCREEN 0x4560 // KEG 20090805 +#define S1D13521_SET_AUTOREFRESH_TIME 0x4561 // KEG 20090807 +#define S1D13521_GET_AUTOREFRESH_TIME 0x4562 // KEG 20090807 +#define S1D13521_ERASE_SCREEN 0x4563 // KEG 20090807 +#define S1D13521_LOAD_AREA 0x4564 // KEG 20090814 + +// virtual hardware controll . +#define EPDC_PWR_CTRL 0x4570 // epdc power controll . + #define EPDC_AUTOPWR_INTERVAL_MAX 1 + #define EPDC_AUTOPWR_INTERVAL_NORMAL 2 + #define EPDC_PWR_ON 3 + #define EPDC_PWR_OFF 4 + #define EPDC_VCOM_ON 5 + #define EPDC_VCOM_OFF 6 + #define EPDC_AUTOOFF_ENABLE 7 + #define EPDC_AUTOOFF_DISABLE 8 + +#define EPDC_VCOM_SET 0x4571 +#define EPDC_VCOM_SET_TO_FLASH 0x4572 +#define EPDC_VCOM_GET 0x4573 + + + +//#define S1D13521_FLASH 0x0100 +#define S1D13521_FLASH 0x4700 +#define S1D13521_RESET 0x4701 +#define S1D13521_PGM 0x4702 + +// System commands +#define INIT_CMD_SET 0x00 +#define INIT_PLL_STANDBY 0x01 +#define RUN_SYS 0x02 +#define STBY 0x04 +#define SLP 0x05 +#define INIT_SYS_RUN 0x06 +#define INIT_SYS_STBY 0x07 +#define INIT_SDRAM 0x08 +#define INIT_DSPE_CFG 0x09 +#define INIT_DSPE_TMG 0x0A +#define INIT_ROTMODE 0x0B +//#define SET_REFRESH_CNT 0x0C +#define INIT_WAVEDEV 0x0C + + +// Register and memory access commands +#define RD_REG 0x10 +#define WR_REG 0x11 +#define RD_SFM 0x12 +#define WR_SFM 0x13 +#define END_SFM 0x14 + +// Burst access commands +#define BST_RD_SDR 0x1C +#define BST_WR_SDR 0x1D +#define BST_END_SDR 0x1E + +// Image loading commands +#define LD_IMG 0x20 +#define LD_IMG_AREA 0x22 +#define LD_IMG_END 0x23 +#define LD_IMG_WAIT 0x24 +#define LD_IMG_SETADR 0x25 +#define LD_IMG_DSPEADR 0x26 + +// Polling commands +#define WAIT_DSPE_TRG 0x28 +#define WAIT_DSPE_FREND 0x29 +#define WAIT_DSPE_LUTFREE 0x2A +#define WAIT_DSPE_MLUTFREE 0x2B + +// Waveform update commands +#define RD_WFM_INFO 0x30 +#define UPD_INIT 0x32 +#define UPD_FULL 0x33 +#define UPD_FULL_AREA 0x34 +#define UPD_PART 0x35 +#define UPD_PART_AREA 0x36 +#define UPD_GDRV_CLR 0x37 +#define UPD_SET_IMGADR 0x38 + +#pragma pack(1) + +typedef struct +{ + u16 param[5]; +}s1d13521_ioctl_cmd_params; + +#pragma pack() + +#define S1D13521_INIT_CMD_SET (0x4500 | INIT_CMD_SET) +#define S1D13521_INIT_PLL_STANDBY (0x4500 | INIT_PLL_STANDBY) +#define S1D13521_RUN_SYS (0x4500 | RUN_SYS) +#define S1D13521_STBY (0x4500 | STBY) +#define S1D13521_SLP (0x4500 | SLP) +#define S1D13521_INIT_SYS_RUN (0x4500 | INIT_SYS_RUN) +#define S1D13521_INIT_SYS_STBY (0x4500 | INIT_SYS_STBY) +#define S1D13521_INIT_SDRAM (0x4500 | INIT_SDRAM) +#define S1D13521_INIT_DSPE_CFG (0x4500 | INIT_DSPE_CFG) +#define S1D13521_INIT_DSPE_TMG (0x4500 | INIT_DSPE_TMG) +#define S1D13521_INIT_ROTMODE (0x4500 | INIT_ROTMODE) +#define S1D13522_INIT_WAVEDEV (0x4500 | INIT_WAVEDEV) +#define S1D13521_RD_REG (0x4500 | RD_REG) +#define S1D13521_WR_REG (0x4500 | WR_REG) +#define S1D13521_RD_SFM (0x4500 | RD_SFM) +#define S1D13521_WR_SFM (0x4500 | WR_SFM) +#define S1D13521_END_SFM (0x4500 | END_SFM) + +// Burst access commands +#define S1D13521_BST_RD_SDR (0x4500 | BST_RD_SDR) +#define S1D13521_BST_WR_SDR (0x4500 | BST_WR_SDR) +#define S1D13521_BST_END_SDR (0x4500 | BST_END_SDR) + +// Image loading IOCTL commands +#define S1D13521_LD_IMG (0x4500 | LD_IMG) +#define S1D13521_LD_IMG_AREA (0x4500 | LD_IMG_AREA) +#define S1D13521_LD_IMG_END (0x4500 | LD_IMG_END) +#define S1D13521_LD_IMG_WAIT (0x4500 | LD_IMG_WAIT) +#define S1D13521_LD_IMG_SETADR (0x4500 | LD_IMG_SETADR) +#define S1D13521_LD_IMG_DSPEADR (0x4500 | LD_IMG_DSPEADR) + +// Polling commands +#define S1D13521_WAIT_DSPE_TRG (0x4500 | WAIT_DSPE_TRG) +#define S1D13521_WAIT_DSPE_FREND (0x4500 | WAIT_DSPE_FREND) +#define S1D13521_WAIT_DSPE_LUTFREE (0x4500 | WAIT_DSPE_LUTFREE) +#define S1D13521_WAIT_DSPE_MLUTFREE (0x4500 | WAIT_DSPE_MLUTFREE) + +// Waveform update IOCTL commands +#define S1D13521_RD_WFM_INFO (0x4500 | RD_WFM_INFO) +#define S1D13521_UPD_INIT (0x4500 | UPD_INIT) +#define S1D13521_UPD_FULL (0x4500 | UPD_FULL) +#define S1D13521_UPD_FULL_AREA (0x4500 | UPD_FULL_AREA) +#define S1D13521_UPD_PART (0x4500 | UPD_PART) +#define S1D13521_UPD_PART_AREA (0x4500 | UPD_PART_AREA) +#define S1D13521_UPD_GDRV_CLR (0x4500 | UPD_GDRV_CLR) +#define S1D13521_UPD_SET_IMGADR (0x4500 | UPD_SET_IMGADR) + + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // + // + // + //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + typedef unsigned int HWND; +// #define MaxBufferSize (((800 * 600) / 2) + 50) + #define MaxBufferSize (((_PANEL_WIDTH * _PANEL_HEIGHT) / 2) + 50) + + typedef enum { + _STEP_NOR_BYPASS=0, + _STEP_NOR_WRITE_AA_INTO_555, + _STEP_NOR_WRITE_55_INTO_2AA, + + _STEP_NOR_CMD_PROGRAM, + _STEP_NOR_CMD_PROGRAM_AA_WRITE_INTO_555, + _STEP_NOR_CMD_PROGRAM_55_WRITE_INTO_2AA, + _STEP_NOR_CMD_PROGRAM_DATA, + + _STEP_NOR_CMD_ERASE, + _STEP_NOR_CMD_ERASE_WRITE_AA_INTO_555, + _STEP_NOR_CMD_ERASE_WRITE_55_INTO_2AA, + + _STEP_NOR_CMD_STATUS, + + + } STEP_NOR_2_SPI_FLASH; + +// #pragma pack(1) + typedef struct TAG_ST_PVI_CONFIG { + UINT16 CurRotateMode; + UINT16 PastRotateMode; + + STEP_NOR_2_SPI_FLASH StepSPI; + + UINT16 ReverseGrade; + + BOOL fAutoRefreshMode; + BYTE AutoRefreshTimer; + + UINT16 fNormalMode; + + BYTE Deepth; + + UINT16 StartX; + UINT16 StartY; + UINT16 Width; + UINT16 Height; + + UINT16 Reg0x140; + UINT16 Reg0x330; + + } ST_PVI_CONFIG, *PST_PVI_CONFIG; + + #pragma pack() + + typedef struct ST_IMAGE_PGM_TAG{ + unsigned int StartX; + unsigned int StartY; + unsigned int Width; + unsigned int Height; + unsigned int WaveForm; + unsigned int LUT_NO; + BYTE Data[_PANEL_WIDTH*_PANEL_HEIGHT]; + } ST_IMAGE_PGM, *PST_IMAGE_PGM; + + typedef struct ST_IMAGE_PGM_800x600_TAG{ + unsigned int StartX; + unsigned int StartY; + unsigned int Width; + unsigned int Height; + unsigned int WaveForm; + unsigned int LUT_NO; + BYTE Data[800*600]; + } ST_IMAGE_PGM_800x600, *PST_IMAGE_PGM_800x600; + + +/****kay*******/ +//kay 20090428 for epson memory control +typedef enum { + _EPSON_MEM_CLEAR=0, + _EPSON_MEM_CLEAR_BY_INDEX=1, + _EPSON_MEM_CLEAR_BY_NAME=2, + _EPSON_MEM_SAVE_BY_INDEX=3, + _EPSON_MEM_SAVE_BY_NAME=4, + _EPSON_MEM_RESTORE_BY_INDEX=5, + _EPSON_MEM_RESTORE_BY_NAME=6, + _EPSON_MEM_SAVE_BY_NAME_NOT_SHOW=7, + _EPSON_MEM_SHOW_PIC=8, + +} E_EPSON_MEM_CMD; + +#define MAX_EPSON_MEMORY_NAME_LENGTH 64 +#define COUNT_FOR_32M 60 +#define COUNT_FOR_64M 120 +//#define EPSON_MEMORY_CTL_BASE_START 800*600*2 +//#define EPSON_MEMORY_CTL_UNIT_SIZE 800*600 +#define MAX_EPSON_MEMORY_CTL_SIZE COUNT_FOR_32M + struct EPSON_MEMORY_CTL { + U8 cmd; + UINT16 x; + UINT16 y; + UINT16 width; + UINT16 height; + UINT16 rotate; + U8 index; + char name[MAX_EPSON_MEMORY_NAME_LENGTH]; + U8 waveform; + }; + + +// + + typedef struct ST_IMAGE_PGM_TAG_MEM{ + U8 cmd; + U8 index; + char name[MAX_EPSON_MEMORY_NAME_LENGTH]; + unsigned int StartX; + unsigned int StartY; + unsigned int Width; + unsigned int Height; + unsigned int WaveForm; + unsigned int LUT_NO; + unsigned int Depth; + BYTE Data[_PANEL_WIDTH*_PANEL_HEIGHT]; + } ST_IMAGE_PGM_MEM, *PST_IMAGE_PGM_MEM; + typedef struct ST_IMAGE_PGM_TAG_MEM_KERNEL{ + U8 cmd; + U8 index; + char name[MAX_EPSON_MEMORY_NAME_LENGTH]; + unsigned int StartX; + unsigned int StartY; + unsigned int Width; + unsigned int Height; + unsigned int WaveForm; + unsigned int LUT_NO; + unsigned int Depth; +// BYTE Data[800*600]; we don't save image data in kernel :P + } ST_IMAGE_PGM_MEM_KERNEL, *PST_IMAGE_PGM_MEM_KERNEL; + +/*******kay*******/ + + + + typedef struct TAG_TDisplayCommand{ + int Owner; + BYTE Command; + int BytesToWrite ; + int BytesToRead; + BYTE Data[MaxBufferSize]; + } TDisplayCommand, *PTDisplayCommand; + + typedef struct TAG_TDisplayCommandSmall{ + int Owner; + BYTE Command; + int BytesToWrite ; + int BytesToRead; + BYTE Data[16]; + } TDisplayCommandSmall, *PTDisplayCommandSmall; + + typedef struct TAG_TDisplayCommandHeader{ + int Owner; + BYTE Command; + int BytesToWrite ; + int BytesToRead; + } TDisplayCommandHeader, *PTDisplayCommandHeader; + + #define _OFFSET_DISP_COMMAND_FDATA (((PTDisplayCommand) 0)->Data) + + #define dc_NewImage 0xA0 + #define dc_StopNewImage 0xA1 + #define dc_DisplayImage 0xA2 + #define dc_PartialImage 0xB0 + #define dc_DisplayPartial 0xB1 + #define dc_DisplayPartialGU 0xB2 + #define dc_Reset 0xEE + #define dc_SetDepth 0xF3 + #define dc_EraseDisplay 0xA3 + #define dc_Rotate 0xF5 + #define dc_Positive 0xF7 + #define dc_Negative 0xF8 + #define dc_GoToNormal 0xF0 + #define dc_GoToSleep 0xF1 + #define dc_GoToStandBy 0xF2 + #define dc_WriteToFlash 0x01 + #define dc_ReadFromFlash 0x02 + #define dc_Init 0xA4 + #define dc_AutoRefreshOn 0xF9 + #define dc_AutoRefreshOff 0xFA + #define dc_SetRefresh 0xFB + #define dc_ForcedRefresh 0xFC + #define dc_GetRefresh 0xFD + #define dc_RestoreImage 0xA5 + #define dc_ControllerVersion 0xE0 + #define dc_SoftwareVersion 0xE1 + #define dc_DisplaySize 0xE2 + #define dc_GetStatus 0xAA + #define dc_Temperature 0x21 + #define dc_WriteRegister 0x10 + #define dc_ReadRegister 0x11 + #define dc_Abort 0xA1 + #define CMD_SendCommand 1 + #define CMD_EPSON_MEMORY_CTL 3 + + typedef struct _S1D13532_FLASH_PACKAGE_TAG { + DWORD Command; + DWORD StartAddr; + DWORD DataLength; + BYTE Buf[4]; + } S1D13532_FLASH_PACKAGE, *PS1D13532_FLASH_PACKAGE; + + #define _FLASH_CMD_GET_INFO 0 + #define _FLASH_CMD_ERASE 1 + #define _FLASH_CMD_WRITE 2 + #define _FLASH_CMD_READ 3 + + typedef struct TAG_LOADIMAGEAREA { + BYTE cmd; + BYTE mode; + int XStart; + int YStart; + int Width; + int Height; + } TLoadImageArea, *PTloadImageArea; + +#endif // __EPSON_IOCTL_H__ + diff --git a/drivers/video/mxc/tve.c b/drivers/video/mxc/tve.c new file mode 100644 index 00000000..7a5c3b5d --- /dev/null +++ b/drivers/video/mxc/tve.c @@ -0,0 +1,1310 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file tve.c + * @brief Driver for i.MX TV encoder + * + * @ingroup Framebuffer + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/clk.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/sysfs.h> +#include <linux/irq.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <linux/fsl_devices.h> +#include <linux/uaccess.h> +#include <asm/atomic.h> +#include <mach/hardware.h> +#include "mxc_dispdrv.h" + +#define TVE_ENABLE (1UL) +#define TVE_DAC_FULL_RATE (0UL<<1) +#define TVE_DAC_DIV2_RATE (1UL<<1) +#define TVE_DAC_DIV4_RATE (2UL<<1) +#define TVE_IPU_CLK_ENABLE (1UL<<3) + +#define CD_LM_INT 0x00000001 +#define CD_SM_INT 0x00000002 +#define CD_MON_END_INT 0x00000004 +#define CD_CH_0_LM_ST 0x00000001 +#define CD_CH_0_SM_ST 0x00000010 +#define CD_CH_1_LM_ST 0x00000002 +#define CD_CH_1_SM_ST 0x00000020 +#define CD_CH_2_LM_ST 0x00000004 +#define CD_CH_2_SM_ST 0x00000040 +#define CD_MAN_TRIG 0x00000100 + +#define TVE_STAND_MASK (0x0F<<8) +#define TVE_NTSC_STAND (0UL<<8) +#define TVE_PAL_STAND (3UL<<8) +#define TVE_HD720P60_STAND (4UL<<8) +#define TVE_HD720P50_STAND (5UL<<8) +#define TVE_HD720P30_STAND (6UL<<8) +#define TVE_HD720P25_STAND (7UL<<8) +#define TVE_HD720P24_STAND (8UL<<8) +#define TVE_HD1080I60_STAND (9UL<<8) +#define TVE_HD1080I50_STAND (10UL<<8) +#define TVE_HD1035I60_STAND (11UL<<8) +#define TVE_HD1080P30_STAND (12UL<<8) +#define TVE_HD1080P25_STAND (13UL<<8) +#define TVE_HD1080P24_STAND (14UL<<8) +#define TVE_DAC_SAMPRATE_MASK (0x3<<1) +#define TVEV2_DATA_SRC_MASK (0x3<<4) + +#define TVEV2_DATA_SRC_BUS_1 (0UL<<4) +#define TVEV2_DATA_SRC_BUS_2 (1UL<<4) +#define TVEV2_DATA_SRC_EXT (2UL<<4) + +#define TVEV2_INP_VIDEO_FORM (1UL<<6) +#define TVEV2_P2I_CONV_EN (1UL<<7) + +#define TVEV2_DAC_GAIN_MASK 0x3F +#define TVEV2_DAC_TEST_MODE_MASK 0x7 + +#define TVOUT_FMT_OFF 0 +#define TVOUT_FMT_NTSC 1 +#define TVOUT_FMT_PAL 2 +#define TVOUT_FMT_720P60 3 +#define TVOUT_FMT_720P30 4 +#define TVOUT_FMT_1080I60 5 +#define TVOUT_FMT_1080I50 6 +#define TVOUT_FMT_1080P30 7 +#define TVOUT_FMT_1080P25 8 +#define TVOUT_FMT_1080P24 9 +#define TVOUT_FMT_VGA_SVGA 10 +#define TVOUT_FMT_VGA_XGA 11 +#define TVOUT_FMT_VGA_SXGA 12 +#define TVOUT_FMT_VGA_WSXGA 13 + +#define DISPDRV_VGA "vga" +#define DISPDRV_TVE "tve" + +struct tve_data { + struct platform_device *pdev; + int revision; + int cur_mode; + int output_mode; + int detect; + void *base; + spinlock_t tve_lock; + bool inited; + int enabled; + int irq; + struct clk *clk; + struct clk *di_clk; + struct regulator *dac_reg; + struct regulator *dig_reg; + struct delayed_work cd_work; + struct tve_reg_mapping *regs; + struct tve_reg_fields_mapping *reg_fields; + struct mxc_dispdrv_handle *disp_tve; + struct mxc_dispdrv_handle *disp_vga; + struct notifier_block nb; +}; + +struct tve_reg_mapping { + u32 tve_com_conf_reg; + u32 tve_cd_cont_reg; + u32 tve_int_cont_reg; + u32 tve_stat_reg; + u32 tve_mv_cont_reg; + u32 tve_tvdac_cont_reg; + u32 tve_tst_mode_reg; +}; + +struct tve_reg_fields_mapping { + u32 cd_en; + u32 cd_trig_mode; + u32 cd_lm_int; + u32 cd_sm_int; + u32 cd_mon_end_int; + u32 cd_man_trig; + u32 sync_ch_mask; + u32 tvout_mode_mask; + u32 sync_ch_offset; + u32 tvout_mode_offset; + u32 cd_ch_stat_offset; +}; + +static struct tve_reg_mapping tve_regs_v1 = { + 0, 0x14, 0x28, 0x2C, 0x48, 0x08, 0x30 +}; + +static struct tve_reg_fields_mapping tve_reg_fields_v1 = { + 1, 2, 1, 2, 4, 0x00010000, 0x7000, 0x70, 12, 4, 8 +}; + +static struct tve_reg_mapping tve_regs_v2 = { + 0, 0x34, 0x64, 0x68, 0xDC, 0x28, 0x6c +}; + +static struct tve_reg_fields_mapping tve_reg_fields_v2 = { + 1, 2, 1, 2, 4, 0x01000000, 0x700000, 0x7000, 20, 12, 16 +}; + +static struct fb_videomode video_modes_tve[] = { + { + /* NTSC TV output */ + "TV-NTSC", 60, 720, 480, 74074, + 122, 15, + 18, 26, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* PAL TV output */ + "TV-PAL", 50, 720, 576, 74074, + 132, 11, + 22, 26, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 720p60 TV output */ + "TV-720P60", 60, 1280, 720, 13468, + 260, 109, + 25, 4, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 720p30 TV output */ + "TV-720P30", 30, 1280, 720, 13468, + 260, 1759, + 25, 4, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080i60 TV output */ + "TV-1080I60", 60, 1920, 1080, 13468, + 192, 87, + 20, 24, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 1080i50 TV output */ + "TV-1080I50", 50, 1920, 1080, 13468, + 192, 527, + 20, 24, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 1080p30 TV output */ + "TV-1080P30", 30, 1920, 1080, 13468, + 192, 87, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080p25 TV output */ + "TV-1080P25", 25, 1920, 1080, 13468, + 192, 527, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080p24 TV output */ + "TV-1080P24", 24, 1920, 1080, 13468, + 192, 637, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; +static int tve_modedb_sz = ARRAY_SIZE(video_modes_tve); + +static struct fb_videomode video_modes_vga[] = { + { + /* VGA 800x600 40M pixel clk output */ + "VGA-SVGA", 60, 800, 600, 25000, + 215, 28, + 24, 2, + 13, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1024x768 65M pixel clk output */ + "VGA-XGA", 60, 1024, 768, 15384, + 160, 24, + 29, 3, + 136, 6, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1280x1024 108M pixel clk output */ + "VGA-SXGA", 60, 1280, 1024, 9259, + 358, 38, + 38, 2, + 12, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1680x1050 294M pixel clk output */ + "VGA-WSXGA+", 60, 1680, 1050, 6796, + 288, 104, + 33, 2, + 184, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; +static int vga_modedb_sz = ARRAY_SIZE(video_modes_vga); + +enum tvout_mode { + TV_OFF, + CVBS0, + CVBS2, + CVBS02, + SVIDEO, + SVIDEO_CVBS, + YPBPR, + TVRGB +}; + +static unsigned short tvout_mode_to_channel_map[8] = { + 0, /* TV_OFF */ + 1, /* CVBS0 */ + 4, /* CVBS2 */ + 5, /* CVBS02 */ + 1, /* SVIDEO */ + 5, /* SVIDEO_CVBS */ + 1, /* YPBPR */ + 7 /* TVRGB */ +}; + +static void tve_dump_regs(struct tve_data *tve) +{ + dev_dbg(&tve->pdev->dev, "tve_com_conf_reg 0x%x\n", + readl(tve->base + tve->regs->tve_com_conf_reg)); + dev_dbg(&tve->pdev->dev, "tve_cd_cont_reg 0x%x\n", + readl(tve->base + tve->regs->tve_cd_cont_reg)); + dev_dbg(&tve->pdev->dev, "tve_int_cont_reg 0x%x\n", + readl(tve->base + tve->regs->tve_int_cont_reg)); + dev_dbg(&tve->pdev->dev, "tve_tst_mode_reg 0x%x\n", + readl(tve->base + tve->regs->tve_tst_mode_reg)); + dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg0 0x%x\n", + readl(tve->base + tve->regs->tve_tvdac_cont_reg)); + dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg1 0x%x\n", + readl(tve->base + tve->regs->tve_tvdac_cont_reg + 4)); + dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg2 0x%x\n", + readl(tve->base + tve->regs->tve_tvdac_cont_reg + 8)); +} + +static int is_vga_enabled(struct tve_data *tve) +{ + u32 reg; + + if (tve->revision == 2) { + reg = readl(tve->base + tve->regs->tve_tst_mode_reg); + if (reg & TVEV2_DAC_TEST_MODE_MASK) + return 1; + else + return 0; + } + return 0; +} + +static inline int is_vga_mode(int mode) +{ + return ((mode == TVOUT_FMT_VGA_SVGA) + || (mode == TVOUT_FMT_VGA_XGA) + || (mode == TVOUT_FMT_VGA_SXGA) + || (mode == TVOUT_FMT_VGA_WSXGA)); +} + +static inline int valid_mode(int mode) +{ + return (is_vga_mode(mode) + || (mode == TVOUT_FMT_NTSC) + || (mode == TVOUT_FMT_PAL) + || (mode == TVOUT_FMT_720P30) + || (mode == TVOUT_FMT_720P60) + || (mode == TVOUT_FMT_1080I50) + || (mode == TVOUT_FMT_1080I60) + || (mode == TVOUT_FMT_1080P24) + || (mode == TVOUT_FMT_1080P25) + || (mode == TVOUT_FMT_1080P30)); +} + +static int get_video_mode(struct fb_info *fbi) +{ + int mode; + + if (fb_mode_is_equal(fbi->mode, &video_modes_tve[0])) { + mode = TVOUT_FMT_NTSC; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[1])) { + mode = TVOUT_FMT_PAL; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[2])) { + mode = TVOUT_FMT_720P60; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[3])) { + mode = TVOUT_FMT_720P30; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[4])) { + mode = TVOUT_FMT_1080I60; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[5])) { + mode = TVOUT_FMT_1080I50; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[6])) { + mode = TVOUT_FMT_1080P30; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[7])) { + mode = TVOUT_FMT_1080P25; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[8])) { + mode = TVOUT_FMT_1080P24; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[0])) { + mode = TVOUT_FMT_VGA_SVGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[1])) { + mode = TVOUT_FMT_VGA_XGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[2])) { + mode = TVOUT_FMT_VGA_SXGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[3])) { + mode = TVOUT_FMT_VGA_WSXGA; + } else { + mode = TVOUT_FMT_OFF; + } + return mode; +} + +static void tve_disable_vga_mode(struct tve_data *tve) +{ + if (tve->revision == 2) { + u32 reg; + /* disable test mode */ + reg = readl(tve->base + tve->regs->tve_tst_mode_reg); + reg = reg & ~TVEV2_DAC_TEST_MODE_MASK; + writel(reg, tve->base + tve->regs->tve_tst_mode_reg); + } +} + +static void tve_set_tvout_mode(struct tve_data *tve, int mode) +{ + u32 conf_reg; + + /* clear sync_ch and tvout_mode fields */ + conf_reg = readl(tve->base + tve->regs->tve_com_conf_reg); + conf_reg &= ~(tve->reg_fields->sync_ch_mask | + tve->reg_fields->tvout_mode_mask); + + conf_reg = conf_reg & ~TVE_DAC_SAMPRATE_MASK; + if (tve->revision == 2) { + conf_reg = (conf_reg & ~TVEV2_DATA_SRC_MASK) | + TVEV2_DATA_SRC_BUS_1; + conf_reg = conf_reg & ~TVEV2_INP_VIDEO_FORM; + conf_reg = conf_reg & ~TVEV2_P2I_CONV_EN; + } + + conf_reg |= + mode << tve->reg_fields-> + tvout_mode_offset | tvout_mode_to_channel_map[mode] << + tve->reg_fields->sync_ch_offset; + writel(conf_reg, tve->base + tve->regs->tve_com_conf_reg); +} + +static int _is_tvout_mode_hd_compatible(struct tve_data *tve) +{ + u32 conf_reg, mode; + + conf_reg = readl(tve->base + tve->regs->tve_com_conf_reg); + mode = (conf_reg >> tve->reg_fields->tvout_mode_offset) & 7; + if (mode == YPBPR || mode == TVRGB) { + return 1; + } else { + return 0; + } +} + +static int tve_setup_vga(struct tve_data *tve) +{ + u32 reg; + + if (tve->revision == 2) { + /* set gain */ + reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; + writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg); + reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg + 4); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; + writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg + 4); + reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg + 8); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; + writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg + 8); + + /* set tve_com_conf_reg */ + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_DAC_SAMPRATE_MASK) | TVE_DAC_DIV2_RATE; + reg = (reg & ~TVEV2_DATA_SRC_MASK) | TVEV2_DATA_SRC_BUS_2; + reg = reg | TVEV2_INP_VIDEO_FORM; + reg = reg & ~TVEV2_P2I_CONV_EN; + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; + reg |= TVRGB << tve->reg_fields->tvout_mode_offset | + 1 << tve->reg_fields->sync_ch_offset; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + + /* set test mode */ + reg = readl(tve->base + tve->regs->tve_tst_mode_reg); + reg = (reg & ~TVEV2_DAC_TEST_MODE_MASK) | 1; + writel(reg, tve->base + tve->regs->tve_tst_mode_reg); + } + + return 0; +} + +/** + * tve_setup + * initial the CH7024 chipset by setting register + * @param: + * vos: output video format + * @return: + * 0 successful + * otherwise failed + */ +static int tve_setup(struct tve_data *tve, int mode) +{ + u32 reg; + struct clk *tve_parent_clk; + unsigned long parent_clock_rate = 216000000, di1_clock_rate = 27000000; + unsigned long tve_clock_rate = 216000000; + unsigned long lock_flags; + + if (tve->cur_mode == mode) + return 0; + + spin_lock_irqsave(&tve->tve_lock, lock_flags); + + switch (mode) { + case TVOUT_FMT_PAL: + case TVOUT_FMT_NTSC: + parent_clock_rate = 216000000; + di1_clock_rate = 27000000; + break; + case TVOUT_FMT_720P60: + case TVOUT_FMT_1080I60: + case TVOUT_FMT_1080I50: + case TVOUT_FMT_720P30: + case TVOUT_FMT_1080P30: + case TVOUT_FMT_1080P25: + case TVOUT_FMT_1080P24: + parent_clock_rate = 297000000; + tve_clock_rate = 297000000; + di1_clock_rate = 74250000; + break; + case TVOUT_FMT_VGA_SVGA: + parent_clock_rate = 160000000; + tve_clock_rate = 80000000; + di1_clock_rate = 40000000; + break; + case TVOUT_FMT_VGA_XGA: + parent_clock_rate = 520000000; + tve_clock_rate = 130000000; + di1_clock_rate = 65000000; + break; + case TVOUT_FMT_VGA_SXGA: + parent_clock_rate = 864000000; + tve_clock_rate = 216000000; + di1_clock_rate = 108000000; + break; + case TVOUT_FMT_VGA_WSXGA: + parent_clock_rate = 588560000; + tve_clock_rate = 294280000; + di1_clock_rate = 147140000; + break; + } + if (tve->enabled) + clk_disable(tve->clk); + + tve_parent_clk = clk_get_parent(tve->clk); + + clk_set_rate(tve_parent_clk, parent_clock_rate); + + tve_clock_rate = clk_round_rate(tve->clk, tve_clock_rate); + clk_set_rate(tve->clk, tve_clock_rate); + + clk_enable(tve->clk); + di1_clock_rate = clk_round_rate(tve->di_clk, di1_clock_rate); + clk_set_rate(tve->di_clk, di1_clock_rate); + + tve->cur_mode = mode; + + /* select output video format */ + if (mode == TVOUT_FMT_PAL) { + tve_disable_vga_mode(tve); + tve_set_tvout_mode(tve, YPBPR); + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_PAL_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to PAL video\n"); + } else if (mode == TVOUT_FMT_NTSC) { + tve_disable_vga_mode(tve); + tve_set_tvout_mode(tve, YPBPR); + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_NTSC_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to NTSC video\n"); + } else if (mode == TVOUT_FMT_720P60) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P60_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 720P60 video\n"); + } else if (mode == TVOUT_FMT_720P30) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P30_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 720P30 video\n"); + } else if (mode == TVOUT_FMT_1080I60) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I60_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080I60 video\n"); + } else if (mode == TVOUT_FMT_1080I50) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I50_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080I50 video\n"); + } else if (mode == TVOUT_FMT_1080P30) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080P30 video\n"); + } else if (mode == TVOUT_FMT_1080P25) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P25_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080P25 video\n"); + } else if (mode == TVOUT_FMT_1080P24) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P24_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080P24 video\n"); + } else if (is_vga_mode(mode)) { + /* do not need cable detect */ + tve_setup_vga(tve); + dev_dbg(&tve->pdev->dev, "TVE: change to VGA video\n"); + } else if (mode == TVOUT_FMT_OFF) { + writel(0x0, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to OFF video\n"); + } else { + dev_dbg(&tve->pdev->dev, "TVE: no such video format.\n"); + } + + if (!tve->enabled) + clk_disable(tve->clk); + + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); + return 0; +} + +/** + * tve_enable + * Enable the tve Power to begin TV encoder + */ +static void tve_enable(struct tve_data *tve) +{ + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&tve->tve_lock, lock_flags); + if (!tve->enabled) { + tve->enabled = 1; + clk_enable(tve->clk); + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + writel(reg | TVE_IPU_CLK_ENABLE | TVE_ENABLE, + tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE power on.\n"); + } + + if (is_vga_enabled(tve)) { + /* disable interrupt */ + dev_dbg(&tve->pdev->dev, "TVE VGA disable cable detect.\n"); + writel(0xffffffff, tve->base + tve->regs->tve_stat_reg); + writel(0, tve->base + tve->regs->tve_int_cont_reg); + } else { + /* enable interrupt */ + dev_dbg(&tve->pdev->dev, "TVE TVE enable cable detect.\n"); + writel(0xffffffff, tve->base + tve->regs->tve_stat_reg); + writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT, + tve->base + tve->regs->tve_int_cont_reg); + } + + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); + + tve_dump_regs(tve); +} + +/** + * tve_disable + * Disable the tve Power to stop TV encoder + */ +static void tve_disable(struct tve_data *tve) +{ + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&tve->tve_lock, lock_flags); + if (tve->enabled) { + tve->enabled = 0; + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + writel(reg & ~TVE_ENABLE & ~TVE_IPU_CLK_ENABLE, + tve->base + tve->regs->tve_com_conf_reg); + clk_disable(tve->clk); + dev_dbg(&tve->pdev->dev, "TVE power off.\n"); + } + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); +} + +static int tve_update_detect_status(struct tve_data *tve) +{ + int old_detect = tve->detect; + u32 stat_lm, stat_sm, stat; + u32 int_ctl; + u32 cd_cont_reg; + u32 timeout = 40; + unsigned long lock_flags; + char event_string[16]; + char *envp[] = { event_string, NULL }; + + spin_lock_irqsave(&tve->tve_lock, lock_flags); + + if (!tve->enabled) { + dev_warn(&tve->pdev->dev, "Warning: update tve status while it disabled!\n"); + tve->detect = 0; + goto done; + } + + int_ctl = readl(tve->base + tve->regs->tve_int_cont_reg); + cd_cont_reg = readl(tve->base + tve->regs->tve_cd_cont_reg); + + if ((cd_cont_reg & 0x1) == 0) { + dev_warn(&tve->pdev->dev, "Warning: pls enable TVE CD first!\n"); + goto done; + } + + stat = readl(tve->base + tve->regs->tve_stat_reg); + while (((stat & CD_MON_END_INT) == 0) && (timeout > 0)) { + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); + msleep(2); + spin_lock_irqsave(&tve->tve_lock, lock_flags); + timeout -= 2; + if (!tve->enabled) { + dev_warn(&tve->pdev->dev, "Warning: update tve status while it disabled!\n"); + tve->detect = 0; + goto done; + } else + stat = readl(tve->base + tve->regs->tve_stat_reg); + } + if (((stat & CD_MON_END_INT) == 0) && (timeout <= 0)) { + dev_warn(&tve->pdev->dev, "Warning: get detect result without CD_MON_END_INT!\n"); + goto done; + } + + stat = stat >> tve->reg_fields->cd_ch_stat_offset; + stat_lm = stat & (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST); + if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST)) && + ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST | CD_CH_2_SM_ST)) == 0) + ) { + tve->detect = 3; + tve->output_mode = YPBPR; + } else if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST)) && + ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST)) == 0)) { + tve->detect = 4; + tve->output_mode = SVIDEO; + } else if (stat_lm == CD_CH_0_LM_ST) { + stat_sm = stat & CD_CH_0_SM_ST; + if (stat_sm != 0) { + /* headset */ + tve->detect = 2; + tve->output_mode = TV_OFF; + } else { + tve->detect = 1; + tve->output_mode = CVBS0; + } + } else if (stat_lm == CD_CH_2_LM_ST) { + stat_sm = stat & CD_CH_2_SM_ST; + if (stat_sm != 0) { + /* headset */ + tve->detect = 2; + tve->output_mode = TV_OFF; + } else { + tve->detect = 1; + tve->output_mode = CVBS2; + } + } else { + /* none */ + tve->detect = 0; + tve->output_mode = TV_OFF; + } + + tve_set_tvout_mode(tve, tve->output_mode); + + /* clear interrupt */ + writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve->base + tve->regs->tve_stat_reg); + + writel(int_ctl | CD_SM_INT | CD_LM_INT, + tve->base + tve->regs->tve_int_cont_reg); + +done: + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); + + if (old_detect != tve->detect) { + sysfs_notify(&tve->pdev->dev.kobj, NULL, "headphone"); + if (tve->detect == 1) + sprintf(event_string, "EVENT=CVBS0"); + else if (tve->detect == 3) + sprintf(event_string, "EVENT=YPBPR"); + else if (tve->detect == 4) + sprintf(event_string, "EVENT=SVIDEO"); + else + sprintf(event_string, "EVENT=NONE"); + kobject_uevent_env(&tve->pdev->dev.kobj, KOBJ_CHANGE, envp); + } + + dev_dbg(&tve->pdev->dev, "detect = %d mode = %d\n", + tve->detect, tve->output_mode); + return tve->detect; +} + +static void cd_work_func(struct work_struct *work) +{ + struct delayed_work *delay_work = to_delayed_work(work); + struct tve_data *tve = + container_of(delay_work, struct tve_data, cd_work); + + tve_update_detect_status(tve); +} + +static irqreturn_t tve_detect_handler(int irq, void *data) +{ + struct tve_data *tve = data; + + u32 int_ctl = readl(tve->base + tve->regs->tve_int_cont_reg); + + /* disable INT first */ + int_ctl &= ~(CD_SM_INT | CD_LM_INT | CD_MON_END_INT); + writel(int_ctl, tve->base + tve->regs->tve_int_cont_reg); + + writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve->base + tve->regs->tve_stat_reg); + + schedule_delayed_work(&tve->cd_work, msecs_to_jiffies(1000)); + + return IRQ_HANDLED; +} + +/*! + * FB suspend/resume routing + */ +static int tve_suspend(struct tve_data *tve) +{ + if (tve->enabled) { + writel(0, tve->base + tve->regs->tve_int_cont_reg); + writel(0, tve->base + tve->regs->tve_cd_cont_reg); + writel(0, tve->base + tve->regs->tve_com_conf_reg); + clk_disable(tve->clk); + } + return 0; +} + +static int tve_resume(struct tve_data *tve, struct fb_info *fbi) +{ + int mode; + + if (tve->enabled) { + clk_enable(tve->clk); + + /* Setup cable detect */ + if (tve->revision == 1) + writel(0x01067701, + tve->base + tve->regs->tve_cd_cont_reg); + else + writel(0x00770601, + tve->base + tve->regs->tve_cd_cont_reg); + + if (valid_mode(tve->cur_mode)) { + mode = tve->cur_mode; + tve_disable(tve); + tve->cur_mode = TVOUT_FMT_OFF; + tve_setup(tve, mode); + } + tve_enable(tve); + } + + return 0; +} + +int tve_fb_setup(struct tve_data *tve, struct fb_info *fbi) +{ + int mode; + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + if (!fbi->mode) { + dev_warn(&tve->pdev->dev, "TVE: can not find mode for xres=%d, yres=%d\n", + fbi->var.xres, fbi->var.yres); + tve_disable(tve); + tve->cur_mode = TVOUT_FMT_OFF; + return 0; + } + + dev_dbg(&tve->pdev->dev, "TVE: fb mode change event: xres=%d, yres=%d\n", + fbi->mode->xres, fbi->mode->yres); + + mode = get_video_mode(fbi); + if (mode != TVOUT_FMT_OFF) { + tve_disable(tve); + tve_setup(tve, mode); + tve_enable(tve); + } else { + tve_disable(tve); + tve_setup(tve, mode); + } + + return 0; +} + +static inline int tve_disp_setup(struct mxc_dispdrv_handle *disp, + struct fb_info *fbi) +{ + struct tve_data *tve = mxc_dispdrv_getdata(disp); + + tve_fb_setup(tve, fbi); + return 0; +} + +int tve_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct tve_data *tve = container_of(nb, struct tve_data, nb); + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + /* only work for ipu0 di1*/ + if (strcmp(fbi->fix.id, "DISP3 BG - DI1")) + return 0; + + switch (val) { + case FB_EVENT_BLANK: + if (fbi->mode == NULL) + return 0; + + dev_dbg(&tve->pdev->dev, "TVE: fb blank event\n"); + + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + int mode; + mode = get_video_mode(fbi); + if (mode != TVOUT_FMT_OFF) { + if (tve->cur_mode != mode) { + tve_disable(tve); + tve_setup(tve, mode); + } + tve_enable(tve); + } else + tve_setup(tve, mode); + } else + tve_disable(tve); + break; + case FB_EVENT_SUSPEND: + tve_suspend(tve); + break; + case FB_EVENT_RESUME: + tve_resume(tve, fbi); + break; + } + return 0; +} + +static ssize_t show_headphone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tve_data *tve = dev_get_drvdata(dev); + int detect; + + if (!tve->enabled) { + strcpy(buf, "tve power off\n"); + return strlen(buf); + } + + detect = tve_update_detect_status(tve); + + if (detect == 0) + strcpy(buf, "none\n"); + else if (detect == 1) + strcpy(buf, "cvbs\n"); + else if (detect == 2) + strcpy(buf, "headset\n"); + else if (detect == 3) + strcpy(buf, "component\n"); + else + strcpy(buf, "svideo\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static int _tve_get_revision(struct tve_data *tve) +{ + u32 conf_reg; + u32 rev = 0; + + /* find out TVE rev based on the base addr default value + * can be used at the init/probe ONLY */ + conf_reg = readl(tve->base); + switch (conf_reg) { + case 0x00842000: + rev = 1; + break; + case 0x00100000: + rev = 2; + break; + } + return rev; +} + +static int tve_drv_init(struct mxc_dispdrv_handle *disp, bool vga, + struct mxc_dispdrv_setting *setting) +{ + int ret; + struct tve_data *tve = mxc_dispdrv_getdata(disp); + struct fsl_mxc_tve_platform_data *plat_data + = tve->pdev->dev.platform_data; + struct resource *res; + struct fb_videomode *modedb; + int modedb_sz; + u32 conf_reg; + + if (tve->inited == true) + return -ENODEV; + + /*tve&vga only use ipu0 and di1*/ + setting->dev_id = 0; + setting->disp_id = 1; + + res = platform_get_resource(tve->pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENOMEM; + goto get_res_failed; + } + + tve->irq = platform_get_irq(tve->pdev, 0); + if (tve->irq < 0) { + ret = tve->irq; + goto get_irq_failed; + } + + tve->base = ioremap(res->start, res->end - res->start); + if (!tve->base) { + ret = -ENOMEM; + goto ioremap_failed; + } + + ret = device_create_file(&tve->pdev->dev, &dev_attr_headphone); + if (ret < 0) + goto dev_file_create_failed; + + tve->dac_reg = regulator_get(&tve->pdev->dev, plat_data->dac_reg); + if (!IS_ERR(tve->dac_reg)) { + regulator_set_voltage(tve->dac_reg, 2750000, 2750000); + regulator_enable(tve->dac_reg); + } + tve->dig_reg = regulator_get(&tve->pdev->dev, plat_data->dig_reg); + if (!IS_ERR(tve->dig_reg)) { + regulator_set_voltage(tve->dig_reg, 1250000, 1250000); + regulator_enable(tve->dig_reg); + } + + tve->clk = clk_get(&tve->pdev->dev, "tve_clk"); + if (IS_ERR(tve->clk)) { + ret = PTR_ERR(tve->clk); + goto get_tveclk_failed; + } + tve->di_clk = clk_get(NULL, "ipu1_di1_clk"); + if (IS_ERR(tve->di_clk)) { + ret = PTR_ERR(tve->di_clk); + goto get_diclk_failed; + } + + clk_set_rate(tve->clk, 216000000); + clk_set_parent(tve->di_clk, tve->clk); + clk_enable(tve->clk); + + tve->revision = _tve_get_revision(tve); + if (tve->revision == 1) { + tve->regs = &tve_regs_v1; + tve->reg_fields = &tve_reg_fields_v1; + } else { + tve->regs = &tve_regs_v2; + tve->reg_fields = &tve_reg_fields_v2; + } + + /* adjust video mode for mx37 */ + if (cpu_is_mx37()) { + video_modes_tve[0].left_margin = 121; + video_modes_tve[0].right_margin = 16; + video_modes_tve[0].upper_margin = 17; + video_modes_tve[0].lower_margin = 5; + video_modes_tve[1].left_margin = 131; + video_modes_tve[1].right_margin = 12; + video_modes_tve[1].upper_margin = 21; + video_modes_tve[1].lower_margin = 3; + } + + if (vga && cpu_is_mx53()) { + setting->if_fmt = IPU_PIX_FMT_GBR24; + modedb = video_modes_vga; + modedb_sz = vga_modedb_sz; + } else { + setting->if_fmt = IPU_PIX_FMT_YUV444; + if (tve->revision == 1) { + modedb = video_modes_tve; + modedb_sz = 3; + } else { + modedb = video_modes_tve; + modedb_sz = tve_modedb_sz; + } + } + + fb_videomode_to_modelist(modedb, modedb_sz, &setting->fbi->modelist); + + /* must use spec video mode defined by driver */ + ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, + modedb, modedb_sz, NULL, setting->default_bpp); + if (ret != 1) + fb_videomode_to_var(&setting->fbi->var, &modedb[0]); + + ret = request_irq(tve->irq, tve_detect_handler, 0, tve->pdev->name, tve); + if (ret < 0) + goto req_irq_failed; + + /* Setup cable detect, for YPrPb mode, default use channel#-1 for Y */ + INIT_DELAYED_WORK(&tve->cd_work, cd_work_func); + if (tve->revision == 1) + writel(0x01067701, tve->base + tve->regs->tve_cd_cont_reg); + else + writel(0x00770601, tve->base + tve->regs->tve_cd_cont_reg); + + conf_reg = 0; + writel(conf_reg, tve->base + tve->regs->tve_com_conf_reg); + + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 5); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 4); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 3); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 2); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg); + + clk_disable(tve->clk); + + tve->nb.notifier_call = tve_fb_event; + ret = fb_register_client(&tve->nb); + if (ret < 0) + goto reg_fbclient_failed; + + dev_set_drvdata(&tve->pdev->dev, tve); + + spin_lock_init(&tve->tve_lock); + + tve->inited = true; + + return 0; + +reg_fbclient_failed: + free_irq(tve->irq, tve->pdev); +req_irq_failed: +get_diclk_failed: +get_tveclk_failed: + device_remove_file(&tve->pdev->dev, &dev_attr_headphone); +dev_file_create_failed: + iounmap(tve->base); +ioremap_failed: +get_irq_failed: +get_res_failed: + return ret; + +} + +static int tvout_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + return tve_drv_init(disp, 0, setting); +} + +static int vga_init(struct mxc_dispdrv_handle *disp, + struct mxc_dispdrv_setting *setting) +{ + return tve_drv_init(disp, 1, setting); +} + +void tvout_deinit(struct mxc_dispdrv_handle *disp) +{ + struct tve_data *tve = mxc_dispdrv_getdata(disp); + + if (tve->enabled) + clk_disable(tve->clk); + + fb_unregister_client(&tve->nb); + free_irq(tve->irq, tve->pdev); + device_remove_file(&tve->pdev->dev, &dev_attr_headphone); + iounmap(tve->base); +} + +static struct mxc_dispdrv_driver tve_drv = { + .name = DISPDRV_TVE, + .init = tvout_init, + .deinit = tvout_deinit, + .setup = tve_disp_setup, +}; + +static struct mxc_dispdrv_driver vga_drv = { + .name = DISPDRV_VGA, + .init = vga_init, + .deinit = tvout_deinit, +}; + +static int tve_dispdrv_init(struct tve_data *tve) +{ + tve->disp_tve = mxc_dispdrv_register(&tve_drv); + mxc_dispdrv_setdata(tve->disp_tve, tve); + tve->disp_vga = mxc_dispdrv_register(&vga_drv); + mxc_dispdrv_setdata(tve->disp_vga, tve); + return 0; +} + +static void tve_dispdrv_deinit(struct tve_data *tve) +{ + mxc_dispdrv_puthandle(tve->disp_tve); + mxc_dispdrv_puthandle(tve->disp_vga); + mxc_dispdrv_unregister(tve->disp_tve); + mxc_dispdrv_unregister(tve->disp_vga); +} + +static int tve_probe(struct platform_device *pdev) +{ + int ret; + struct tve_data *tve; + + tve = kzalloc(sizeof(struct tve_data), GFP_KERNEL); + if (!tve) { + ret = -ENOMEM; + goto alloc_failed; + } + + tve->pdev = pdev; + ret = tve_dispdrv_init(tve); + if (ret < 0) + goto dispdrv_init_failed; + + dev_set_drvdata(&pdev->dev, tve); + + return 0; + +dispdrv_init_failed: + kfree(tve); +alloc_failed: + return ret; +} + +static int tve_remove(struct platform_device *pdev) +{ + struct tve_data *tve = dev_get_drvdata(&pdev->dev); + + tve_dispdrv_deinit(tve); + kfree(tve); + return 0; +} + +static struct platform_driver tve_driver = { + .driver = { + .name = "mxc_tve", + }, + .probe = tve_probe, + .remove = tve_remove, +}; + +static int __init tve_init(void) +{ + return platform_driver_register(&tve_driver); +} + +static void __exit tve_exit(void) +{ + platform_driver_unregister(&tve_driver); +} + +module_init(tve_init); +module_exit(tve_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX TV encoder driver"); +MODULE_LICENSE("GPL"); |