diff options
Diffstat (limited to 'drivers/video/mxc')
-rw-r--r-- | drivers/video/mxc/Kconfig | 111 | ||||
-rw-r--r-- | drivers/video/mxc/Makefile | 26 | ||||
-rw-r--r-- | drivers/video/mxc/ch7024.c | 866 | ||||
-rw-r--r-- | drivers/video/mxc/elcdif_regs.h | 678 | ||||
-rw-r--r-- | drivers/video/mxc/epdc_regs.h | 301 | ||||
-rw-r--r-- | drivers/video/mxc/ldb.c | 1458 | ||||
-rw-r--r-- | drivers/video/mxc/mx2fb.c | 1349 | ||||
-rw-r--r-- | drivers/video/mxc/mx2fb.h | 141 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_edid.c | 319 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_elcdif_fb.c | 1466 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_epdc_fb.c | 3786 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_ipuv3_fb.c | 2074 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb.c | 1372 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb_ch7026.c | 370 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb_claa_wvga.c | 240 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb_epson.c | 1153 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb_epson_vga.c | 362 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb_modedb.c | 69 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb_seiko_wvga.c | 241 | ||||
-rw-r--r-- | drivers/video/mxc/tve.c | 1289 |
20 files changed, 17671 insertions, 0 deletions
diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig new file mode 100644 index 000000000000..e2b79faeaf35 --- /dev/null +++ b/drivers/video/mxc/Kconfig @@ -0,0 +1,111 @@ +config FB_MXC + tristate "MXC Framebuffer support" + depends on FB && (MXC_IPU || ARCH_MX21 || ARCH_MX27 || ARCH_MX25) + 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_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 + +config FB_MXC_LDB + tristate "MXC LDB" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 + +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 + tristate "Si Image SII9022 DVI/HDMI Interface Chip" + +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_AUTO_UPDATE_MODE + bool "E-Ink Auto-update Mode Support" + default n + depends on FB_MXC_EINK_PANEL + +config FB_MXC_ELCDIF_FB + depends on FB && ARCH_MXC + tristate "Support MXC ELCDIF framebuffer" + +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 000000000000..1b1a1dc73aef --- /dev/null +++ b/drivers/video/mxc/Makefile @@ -0,0 +1,26 @@ +obj-$(CONFIG_FB_MXC_TVOUT_TVE) += tve.o +obj-$(CONFIG_FB_MODE_HELPERS) += mxc_edid.o +obj-$(CONFIG_FB_MXC_SII902X) += mxcfb_sii902x.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 +else + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_ipuv3_fb.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_LDB) += ldb.o +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 +obj-$(CONFIG_FB_MXC_ELCDIF_FB) += mxc_elcdif_fb.o diff --git a/drivers/video/mxc/ch7024.c b/drivers/video/mxc/ch7024.c new file mode 100644 index 000000000000..48e6b09feecb --- /dev/null +++ b/drivers/video/mxc/ch7024.c @@ -0,0 +1,866 @@ +/* + * Copyright 2007-2010 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/elcdif_regs.h b/drivers/video/mxc/elcdif_regs.h new file mode 100644 index 000000000000..2eceba5864e0 --- /dev/null +++ b/drivers/video/mxc/elcdif_regs.h @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2010 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 000000000000..f9232d8a9a51 --- /dev/null +++ b/drivers/video/mxc/epdc_regs.h @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2010 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_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_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_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_NEXTLUT (epdc_base + 0x460) +#define EPDC_STATUS_COL (epdc_base + 0x480) +#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_DEBUG (epdc_base + 0x500) +#define EPDC_DEBUG_LUT0 (epdc_base + 0x540) +#define EPDC_DEBUG_LUT1 (epdc_base + 0x550) +#define EPDC_DEBUG_LUT2 (epdc_base + 0x560) +#define EPDC_DEBUG_LUT3 (epdc_base + 0x570) +#define EPDC_DEBUG_LUT4 (epdc_base + 0x580) +#define EPDC_DEBUG_LUT5 (epdc_base + 0x590) +#define EPDC_DEBUG_LUT6 (epdc_base + 0x5A0) +#define EPDC_DEBUG_LUT7 (epdc_base + 0x5B0) +#define EPDC_DEBUG_LUT8 (epdc_base + 0x5C0) +#define EPDC_DEBUG_LUT9 (epdc_base + 0x5D0) +#define EPDC_DEBUG_LUT10 (epdc_base + 0x5E0) +#define EPDC_DEBUG_LUT11 (epdc_base + 0x5F0) +#define EPDC_DEBUG_LUT12 (epdc_base + 0x600) +#define EPDC_DEBUG_LUT13 (epdc_base + 0x610) +#define EPDC_DEBUG_LUT14 (epdc_base + 0x620) +#define EPDC_DEBUG_LUT15 (epdc_base + 0x630) +#define EPDC_GPIO (epdc_base + 0x700) +#define EPDC_VERSION (epdc_base + 0x7F0) + +/* + * 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_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 = 0xF0000, + EPDC_UPD_CTRL_LUT_SEL_OFFSET = 16, + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK = 0xFF00, + EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET = 8, + 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_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_STATUS_NEXTLUT field values */ + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID = 0x100, + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK = 0xF, + EPDC_STATUS_NEXTLUT_NEXT_LUT_OFFSET = 0, + +/* EPDC_STATUS field values */ + EPDC_STATUS_LUTS_UNDERRUN = 0x4, + EPDC_STATUS_LUTS_BUSY = 0x2, + EPDC_STATUS_WB_BUSY = 0x1, + +/* EPDC_DEBUG field values */ + EPDC_DEBUG_UNDERRUN_RECOVER = 0x2, + EPDC_DEBUG_COLLISION_OFF = 0x1, + +/* 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, +}; + +#endif /* __EPDC_REGS_INCLUDED__ */ diff --git a/drivers/video/mxc/ldb.c b/drivers/video/mxc/ldb.c new file mode 100644 index 000000000000..258c51985b67 --- /dev/null +++ b/drivers/video/mxc/ldb.c @@ -0,0 +1,1458 @@ +/* + * Copyright (C) 2010 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/ldb.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/fsl_devices.h> +#include <mach/hardware.h> + +#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 + +enum ldb_chan_mode_opt { + LDB_SIN_DI0 = 0, + LDB_SIN_DI1 = 1, + LDB_SEP = 2, + LDB_DUL_DI0 = 3, + LDB_DUL_DI1 = 4, + LDB_SPL_DI0 = 5, + LDB_SPL_DI1 = 6, +}; + +static struct ldb_data { + struct fb_info *fbi[2]; + bool ch_working[2]; + uint32_t chan_mode_opt; + uint32_t chan_bit_map[2]; + uint32_t bgref_rmode; + uint32_t base_addr; + uint32_t *control_reg; + struct clk *ldb_di_clk[2]; + struct regulator *lvds_bg_reg; + struct list_head modelist; +} ldb; + +static struct device *g_ldb_dev; +static u32 *ldb_reg; +static bool enabled[2]; +static int g_chan_mode_opt; +static int g_chan_bit_map[2]; +static bool g_enable_ldb; +static bool g_boot_cmd; + +DEFINE_SPINLOCK(ldb_lock); + +struct fb_videomode mxcfb_ldb_modedb[] = { + { + "1080P60", 60, 1920, 1080, 7692, + 100, 40, + 30, 3, + 10, 2, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + "XGA", 60, 1024, 768, 15385, + 220, 40, + 21, 7, + 60, 10, + 0, + FB_VMODE_NONINTERLACED, + 0,}, +}; +int mxcfb_ldb_modedb_sz = ARRAY_SIZE(mxcfb_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)); +} + +static void ldb_disable(int ipu_di) +{ + uint32_t reg; + int i = 0; + + spin_lock(&ldb_lock); + + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + if (ipu_di != 0 || !ldb.ch_working[0] || !enabled[0]) { + spin_unlock(&ldb_lock); + return; + } + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_DISABLE, + ldb.control_reg); + + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + clk_disable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + + ldb.ch_working[0] = false; + enabled[0] = false; + break; + case LDB_SIN_DI1: + if (ipu_di != 1 || !ldb.ch_working[1] || !enabled[1]) { + spin_unlock(&ldb_lock); + return; + } + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_DISABLE, + ldb.control_reg); + + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + clk_disable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + + ldb.ch_working[1] = false; + enabled[1] = false; + break; + case LDB_SPL_DI0: + case LDB_DUL_DI0: + if (ipu_di != 0 || !enabled[0]) { + spin_unlock(&ldb_lock); + return; + } + + for (i = 0; i < 2; i++) { + if (ldb.ch_working[i]) { + reg = __raw_readl(ldb.control_reg); + if (i == 0) + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_DISABLE, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH1_MODE_DISABLE, + ldb.control_reg); + + if (ldb.chan_mode_opt == LDB_SPL_DI0) { + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg & ~LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + + ldb.ldb_di_clk[i] = clk_get(NULL, i ? + "ldb_di1_clk" : + "ldb_di0_clk"); + clk_disable(ldb.ldb_di_clk[i]); + clk_put(ldb.ldb_di_clk[i]); + + ldb.ch_working[i] = false; + } + } + enabled[0] = false; + break; + case LDB_SPL_DI1: + case LDB_DUL_DI1: + if (ipu_di != 1 || !enabled[1]) { + spin_unlock(&ldb_lock); + return; + } + + for (i = 0; i < 2; i++) { + if (ldb.ch_working[i]) { + reg = __raw_readl(ldb.control_reg); + if (i == 0) + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_DISABLE, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH1_MODE_DISABLE, + ldb.control_reg); + + if (ldb.chan_mode_opt == LDB_SPL_DI1) { + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg & ~LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + + ldb.ldb_di_clk[i] = clk_get(NULL, i ? + "ldb_di1_clk" : + "ldb_di0_clk"); + clk_disable(ldb.ldb_di_clk[i]); + clk_put(ldb.ldb_di_clk[i]); + + ldb.ch_working[i] = false; + } + } + enabled[1] = false; + break; + case LDB_SEP: + if (ldb.ch_working[ipu_di] && enabled[ipu_di]) { + reg = __raw_readl(ldb.control_reg); + if (ipu_di == 0) + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_DISABLE, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_DISABLE, + ldb.control_reg); + + ldb.ldb_di_clk[ipu_di] = clk_get(NULL, ipu_di ? + "ldb_di1_clk" : + "ldb_di0_clk"); + clk_disable(ldb.ldb_di_clk[ipu_di]); + clk_put(ldb.ldb_di_clk[ipu_di]); + + ldb.ch_working[ipu_di] = false; + enabled[ipu_di] = false; + } + break; + default: + break; + } + + spin_unlock(&ldb_lock); + return; +} + +static void ldb_enable(int ipu_di) +{ + uint32_t reg; + + spin_lock(&ldb_lock); + + reg = __raw_readl(ldb.control_reg); + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + if (ldb.ch_working[0] || ipu_di != 0 || enabled[0]) { + spin_unlock(&ldb_lock); + return; + } + + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + clk_enable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, ldb.control_reg); + ldb.ch_working[0] = true; + enabled[0] = true; + break; + case LDB_SIN_DI1: + if (ldb.ch_working[1] || ipu_di != 1 || enabled[1]) { + spin_unlock(&ldb_lock); + return; + } + + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, ldb.control_reg); + ldb.ch_working[1] = true; + enabled[1] = true; + break; + case LDB_SEP: + if (ldb.ch_working[ipu_di] || enabled[ipu_di]) { + spin_unlock(&ldb_lock); + return; + } + + if (ipu_di == 0) { + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + clk_enable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, + ldb.control_reg); + ldb.ch_working[0] = true; + } else { + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + ldb.ch_working[1] = true; + } + enabled[ipu_di] = true; + break; + case LDB_DUL_DI0: + case LDB_SPL_DI0: + if (ipu_di != 0 || enabled[0]) + return; + else + goto proc; + case LDB_DUL_DI1: + case LDB_SPL_DI1: + if (ipu_di != 1 || enabled[1]) + return; +proc: + if (ldb.ch_working[0] || ldb.ch_working[1]) { + spin_unlock(&ldb_lock); + return; + } + + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + clk_enable(ldb.ldb_di_clk[0]); + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[1]); + + if (ldb.chan_mode_opt == LDB_DUL_DI0 || + ldb.chan_mode_opt == LDB_SPL_DI0) { + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI0, + ldb.control_reg); + } else if (ldb.chan_mode_opt == LDB_DUL_DI1 || + ldb.chan_mode_opt == LDB_SPL_DI1) { + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI1, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + } + if (ldb.chan_mode_opt == LDB_SPL_DI0 || + ldb.chan_mode_opt == LDB_SPL_DI1) { + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + ldb.ch_working[0] = true; + ldb.ch_working[1] = true; + enabled[ipu_di] = true; + break; + default: + break; + } + spin_unlock(&ldb_lock); + return; +} + +int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + mm_segment_t old_fs; + int ipu_di = 0; + + switch (val) { + case FB_EVENT_BLANK: + if (ldb.fbi[0] != fbi && ldb.fbi[1] != fbi) + return 0; + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_IPU_DI, + (unsigned long)&ipu_di); + set_fs(old_fs); + } else + return 0; + + if (*((int *)event->data) == FB_BLANK_UNBLANK) + ldb_enable(ipu_di); + else + ldb_disable(ipu_di); + break; + default: + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = ldb_fb_event, +}; + +static int mxc_ldb_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + uint32_t reg; + + switch (cmd) { + case LDB_BGREF_RMODE: + { + ldb_bgref_parm parm; + + if (copy_from_user(&parm, (ldb_bgref_parm *) arg, + sizeof(ldb_bgref_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + reg = __raw_readl(ldb.control_reg); + if (parm.bgref_mode == LDB_EXT_REF) + __raw_writel((reg & ~LDB_BGREF_RMODE_MASK) | + LDB_BGREF_RMODE_EXT, ldb.control_reg); + else if (parm.bgref_mode == LDB_INT_REF) + __raw_writel((reg & ~LDB_BGREF_RMODE_MASK) | + LDB_BGREF_RMODE_INT, ldb.control_reg); + spin_unlock(&ldb_lock); + break; + } + case LDB_VSYNC_POL: + { + ldb_vsync_parm parm; + + if (copy_from_user(&parm, (ldb_vsync_parm *) arg, + sizeof(ldb_vsync_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + reg = __raw_readl(ldb.control_reg); + if (parm.vsync_mode == LDB_VS_ACT_H) { + if (parm.di == 0) + __raw_writel((reg & + ~LDB_DI0_VS_POL_MASK) | + LDB_DI0_VS_POL_ACT_HIGH, + ldb.control_reg); + else + __raw_writel((reg & + ~LDB_DI1_VS_POL_MASK) | + LDB_DI1_VS_POL_ACT_HIGH, + ldb.control_reg); + } else if (parm.vsync_mode == LDB_VS_ACT_L) { + if (parm.di == 0) + __raw_writel((reg & + ~LDB_DI0_VS_POL_MASK) | + LDB_DI0_VS_POL_ACT_LOW, + ldb.control_reg); + else + __raw_writel((reg & + ~LDB_DI1_VS_POL_MASK) | + LDB_DI1_VS_POL_ACT_LOW, + ldb.control_reg); + + } + spin_unlock(&ldb_lock); + break; + } + case LDB_BIT_MAP: + { + ldb_bitmap_parm parm; + + if (copy_from_user(&parm, (ldb_bitmap_parm *) arg, + sizeof(ldb_bitmap_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + reg = __raw_readl(ldb.control_reg); + if (parm.bitmap_mode == LDB_BIT_MAP_SPWG) { + if (parm.channel == 0) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + } else if (parm.bitmap_mode == LDB_BIT_MAP_JEIDA) { + if (parm.channel == 0) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + } + spin_unlock(&ldb_lock); + break; + } + case LDB_DATA_WIDTH: + { + ldb_data_width_parm parm; + + if (copy_from_user(&parm, (ldb_data_width_parm *) arg, + sizeof(ldb_data_width_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + reg = __raw_readl(ldb.control_reg); + if (parm.data_width == 24) { + if (parm.channel == 0) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_24, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + } else if (parm.data_width == 18) { + if (parm.channel == 0) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_18, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + } + spin_unlock(&ldb_lock); + break; + } + case LDB_CHAN_MODE: + { + ldb_chan_mode_parm parm; + struct clk *pll4_clk; + unsigned long pll4_rate = 0; + + if (copy_from_user(&parm, (ldb_chan_mode_parm *) arg, + sizeof(ldb_chan_mode_parm))) + return -EFAULT; + + spin_lock(&ldb_lock); + + /* TODO:Set the correct pll4 rate for all situations */ + pll4_clk = clk_get(NULL, "pll4"); + pll4_rate = clk_get_rate(pll4_clk); + pll4_rate = 455000000; + clk_set_rate(pll4_clk, pll4_rate); + clk_put(pll4_clk); + + reg = __raw_readl(ldb.control_reg); + switch (parm.channel_mode) { + case LDB_CHAN_MODE_SIN: + if (parm.di == 0) { + ldb.chan_mode_opt = LDB_SIN_DI0; + + ldb.ldb_di_clk[0] = clk_get(NULL, + "ldb_di0_clk"); + clk_set_rate(ldb.ldb_di_clk[0], pll4_rate/7); + clk_put(ldb.ldb_di_clk[0]); + + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, + ldb.control_reg); + } else { + ldb.chan_mode_opt = LDB_SIN_DI1; + + ldb.ldb_di_clk[1] = clk_get(NULL, + "ldb_di1_clk"); + clk_set_rate(ldb.ldb_di_clk[1], pll4_rate/7); + clk_put(ldb.ldb_di_clk[1]); + + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + } + break; + case LDB_CHAN_MODE_SEP: + ldb.chan_mode_opt = LDB_SEP; + + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + clk_set_rate(ldb.ldb_di_clk[0], pll4_rate/7); + clk_put(ldb.ldb_di_clk[0]); + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + clk_set_rate(ldb.ldb_di_clk[1], pll4_rate/7); + clk_put(ldb.ldb_di_clk[1]); + + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + break; + case LDB_CHAN_MODE_DUL: + case LDB_CHAN_MODE_SPL: + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + if (parm.di == 0) { + if (parm.channel_mode == LDB_CHAN_MODE_DUL) { + ldb.chan_mode_opt = LDB_DUL_DI0; + clk_set_rate(ldb.ldb_di_clk[0], + pll4_rate/7); + } else { + ldb.chan_mode_opt = LDB_SPL_DI0; + clk_set_rate(ldb.ldb_di_clk[0], + 2*pll4_rate/7); + clk_set_rate(ldb.ldb_di_clk[1], + 2*pll4_rate/7); + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI0, + ldb.control_reg); + } else { + if (parm.channel_mode == LDB_CHAN_MODE_DUL) { + ldb.chan_mode_opt = LDB_DUL_DI1; + clk_set_rate(ldb.ldb_di_clk[1], + pll4_rate/7); + } else { + ldb.chan_mode_opt = LDB_SPL_DI1; + clk_set_rate(ldb.ldb_di_clk[0], + 2*pll4_rate/7); + clk_set_rate(ldb.ldb_di_clk[1], + 2*pll4_rate/7); + reg = __raw_readl(ldb.control_reg); + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + } + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI1 | + LDB_CH1_MODE_EN_TO_DI1, + ldb.control_reg); + } + clk_put(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[1]); + break; + default: + ret = -EINVAL; + break; + } + spin_unlock(&ldb_lock); + break; + } + case LDB_ENABLE: + { + int ipu_di; + + if (copy_from_user(&ipu_di, (int *) arg, sizeof(int))) + return -EFAULT; + + ldb_enable(ipu_di); + break; + } + case LDB_DISABLE: + { + int ipu_di; + + if (copy_from_user(&ipu_di, (int *) arg, sizeof(int))) + return -EFAULT; + + ldb_disable(ipu_di); + break; + } + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int mxc_ldb_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int mxc_ldb_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int mxc_ldb_mmap(struct file *file, struct vm_area_struct *vma) +{ + return 0; +} + +static const struct file_operations mxc_ldb_fops = { + .owner = THIS_MODULE, + .open = mxc_ldb_open, + .mmap = mxc_ldb_mmap, + .release = mxc_ldb_release, + .ioctl = mxc_ldb_ioctl +}; + +/*! + * 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, i, ipu_di, ipu_di_pix_fmt[2]; + bool primary = false, find_1080p = false; + struct resource *res; + struct ldb_platform_data *plat_data = pdev->dev.platform_data; + mm_segment_t old_fs; + struct clk *ldb_clk_parent; + unsigned long ldb_clk_prate = 455000000; + struct fb_var_screeninfo *var[2]; + uint32_t reg; + struct device *temp; + int mxc_ldb_major; + const struct fb_videomode *mode; + struct class *mxc_ldb_class; + + if (g_enable_ldb == false) + return -ENODEV; + + spin_lock_init(&ldb_lock); + + g_ldb_dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) + return -ENODEV; + + memset(&ldb, 0, sizeof(struct ldb_data)); + enabled[0] = enabled[1] = false; + var[0] = var[1] = NULL; + if (g_boot_cmd) { + ldb.chan_mode_opt = g_chan_mode_opt; + ldb.chan_bit_map[0] = g_chan_bit_map[0]; + ldb.chan_bit_map[1] = g_chan_bit_map[1]; + } + + ldb.base_addr = res->start; + ldb_reg = ioremap(ldb.base_addr, res->end - res->start + 1); + ldb.control_reg = ldb_reg + 2; + + INIT_LIST_HEAD(&ldb.modelist); + for (i = 0; i < mxcfb_ldb_modedb_sz; i++) + fb_add_videomode(&mxcfb_ldb_modedb[i], &ldb.modelist); + + for (i = 0; i < num_registered_fb; i++) { + if (registered_fb[i]->var.vmode == FB_VMODE_NONINTERLACED) { + mode = fb_match_mode(®istered_fb[i]->var, + &ldb.modelist); + if (mode) { + dev_dbg(g_ldb_dev, "fb mode found\n"); + ldb.fbi[i] = registered_fb[i]; + fb_videomode_to_var(&ldb.fbi[i]->var, mode); + } else if (i == 0 && ldb.chan_mode_opt != LDB_SEP) { + continue; + } else { + dev_warn(g_ldb_dev, + "can't find video mode\n"); + goto err0; + } + /* + * Default ldb mode: + * 1080p: DI0 split, SPWG or DI1 split, SPWG + * others: single, SPWG + */ + if (g_boot_cmd == false) { + if (fb_mode_is_equal(mode, &mxcfb_ldb_modedb[0])) { + if (strcmp(ldb.fbi[i]->fix.id, + "DISP3 BG") == 0) { + ldb.chan_mode_opt = LDB_SPL_DI0; + dev_warn(g_ldb_dev, + "default di0 split mode\n"); + } else if (strcmp(ldb.fbi[i]->fix.id, + "DISP3 BG - DI1") == 0) { + ldb.chan_mode_opt = LDB_SPL_DI1; + dev_warn(g_ldb_dev, + "default di1 split mode\n"); + } + ldb.chan_bit_map[0] = LDB_BIT_MAP_SPWG; + ldb.chan_bit_map[1] = LDB_BIT_MAP_SPWG; + find_1080p = true; + } else if (!find_1080p) { + if (strcmp(ldb.fbi[i]->fix.id, + "DISP3 BG") == 0) { + ldb.chan_mode_opt = LDB_SIN_DI0; + ldb.chan_bit_map[0] = LDB_BIT_MAP_SPWG; + dev_warn(g_ldb_dev, + "default di0 single mode\n"); + } else if (strcmp(ldb.fbi[i]->fix.id, + "DISP3 BG - DI1") == 0) { + ldb.chan_mode_opt = LDB_SIN_DI1; + ldb.chan_bit_map[1] = LDB_BIT_MAP_SPWG; + dev_warn(g_ldb_dev, + "default di1 single mode\n"); + } + } + } + + acquire_console_sem(); + fb_blank(ldb.fbi[i], FB_BLANK_POWERDOWN); + release_console_sem(); + + if (i == 0) + primary = true; + + if (ldb.fbi[1] != NULL || ldb.chan_mode_opt != LDB_SEP) + break; + } + } + + /* + * We cannot support two LVDS panel with different pixel clock rates + * except that one's pixel clock rate is two times of the others'. + */ + if (ldb.fbi[1] && ldb.fbi[0] != NULL) { + if (ldb.fbi[0]->var.pixclock != ldb.fbi[1]->var.pixclock && + ldb.fbi[0]->var.pixclock != 2 * ldb.fbi[1]->var.pixclock && + ldb.fbi[1]->var.pixclock != 2 * ldb.fbi[0]->var.pixclock) + return -EINVAL; + } + + ldb.bgref_rmode = plat_data->ext_ref; + ldb.lvds_bg_reg = regulator_get(&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); + } + + for (i = 0; i < 2; i++) { + if (ldb.fbi[i] != NULL) { + if (strcmp(ldb.fbi[i]->fix.id, "DISP3 BG") == 0) + ipu_di = 0; + else if (strcmp(ldb.fbi[i]->fix.id, "DISP3 BG - DI1") + == 0) + ipu_di = 1; + else { + dev_err(g_ldb_dev, "Wrong framebuffer\n"); + goto err0; + } + + var[ipu_di] = &ldb.fbi[i]->var; + if (ldb.fbi[i]->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + ldb.fbi[i]->fbops->fb_ioctl(ldb.fbi[i], + MXCFB_GET_DIFMT, + (unsigned long)&(ipu_di_pix_fmt[ipu_di])); + set_fs(old_fs); + } else { + dev_err(g_ldb_dev, "Can't get framebuffer " + "information\n"); + goto err0; + } + + if (!valid_mode(ipu_di_pix_fmt[ipu_di])) { + dev_err(g_ldb_dev, "Unsupport pixel format " + "for ldb input\n"); + goto err0; + } + + reg = __raw_readl(ldb.control_reg); + if (var[ipu_di]->sync & FB_SYNC_VERT_HIGH_ACT) { + if (ipu_di == 0) + __raw_writel((reg & + ~LDB_DI0_VS_POL_MASK) | + LDB_DI0_VS_POL_ACT_HIGH, + ldb.control_reg); + else + __raw_writel((reg & + ~LDB_DI1_VS_POL_MASK) | + LDB_DI1_VS_POL_ACT_HIGH, + ldb.control_reg); + } else { + if (ipu_di == 0) + __raw_writel((reg & + ~LDB_DI0_VS_POL_MASK) | + LDB_DI0_VS_POL_ACT_LOW, + ldb.control_reg); + else + __raw_writel((reg & + ~LDB_DI1_VS_POL_MASK) | + LDB_DI1_VS_POL_ACT_LOW, + ldb.control_reg); + } + + /* TODO:Set the correct pll4 rate for all situations */ + if (ipu_di == 1) { + ldb.ldb_di_clk[1] = + clk_get(&pdev->dev, "ldb_di1_clk"); + ldb_clk_parent = + clk_get_parent(ldb.ldb_di_clk[1]); + clk_set_rate(ldb_clk_parent, ldb_clk_prate); + clk_put(ldb.ldb_di_clk[1]); + } else { + ldb.ldb_di_clk[0] = + clk_get(&pdev->dev, "ldb_di0_clk"); + ldb_clk_parent = + clk_get_parent(ldb.ldb_di_clk[0]); + clk_set_rate(ldb_clk_parent, ldb_clk_prate); + clk_put(ldb.ldb_di_clk[0]); + } + } + } + + reg = __raw_readl(ldb.control_reg); + if (ldb.bgref_rmode == LDB_EXT_REF) + __raw_writel((reg & ~LDB_BGREF_RMODE_MASK) | + LDB_BGREF_RMODE_EXT, ldb.control_reg); + else + __raw_writel((reg & ~LDB_BGREF_RMODE_MASK) | + LDB_BGREF_RMODE_INT, ldb.control_reg); + + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + if (var[0] == NULL) { + dev_err(g_ldb_dev, "Can't find framebuffer on DI0\n"); + break; + } + + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt[0]) == 24) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt[0]) == 18) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[0] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + clk_set_rate(ldb.ldb_di_clk[0], ldb_clk_prate/7); + clk_enable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH0_MODE_MASK) | + LDB_CH0_MODE_EN_TO_DI0, ldb.control_reg); + ldb.ch_working[0] = true; + break; + case LDB_SIN_DI1: + if (var[1] == NULL) { + dev_err(g_ldb_dev, "Can't find framebuffer on DI1\n"); + break; + } + + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt[1]) == 24) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH1_MASK) | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt[1]) == 18) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH1_MASK) | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[1] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + clk_set_rate(ldb.ldb_di_clk[1], ldb_clk_prate/7); + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~LDB_CH1_MODE_MASK) | + LDB_CH1_MODE_EN_TO_DI1, ldb.control_reg); + ldb.ch_working[1] = true; + break; + case LDB_SEP: + if (var[0] == NULL || var[1] == NULL) { + dev_err(g_ldb_dev, "Can't find framebuffers on DI0/1\n"); + break; + } + + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt[0]) == 24) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt[0]) == 18) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH0_MASK) | + LDB_DATA_WIDTH_CH0_18, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt[1]) == 24) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH1_MASK) | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt[1]) == 18) + __raw_writel((reg & ~LDB_DATA_WIDTH_CH1_MASK) | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[0] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[1] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + clk_set_rate(ldb.ldb_di_clk[0], ldb_clk_prate/7); + clk_enable(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[0]); + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + clk_set_rate(ldb.ldb_di_clk[1], ldb_clk_prate/7); + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[1]); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI1, ldb.control_reg); + ldb.ch_working[0] = true; + ldb.ch_working[1] = true; + break; + case LDB_DUL_DI0: + case LDB_SPL_DI0: + if (var[0] == NULL) { + dev_err(g_ldb_dev, "Can't find framebuffer on DI0\n"); + break; + } + + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt[0]) == 24) + __raw_writel((reg & ~(LDB_DATA_WIDTH_CH0_MASK | + LDB_DATA_WIDTH_CH1_MASK)) | + LDB_DATA_WIDTH_CH0_24 | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt[0]) == 18) + __raw_writel((reg & ~(LDB_DATA_WIDTH_CH0_MASK | + LDB_DATA_WIDTH_CH1_MASK)) | + LDB_DATA_WIDTH_CH0_18 | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[0] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[1] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_mode_opt == LDB_SPL_DI0) + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + if (ldb.chan_mode_opt == LDB_DUL_DI0) { + clk_set_rate(ldb.ldb_di_clk[0], ldb_clk_prate/7); + } else { + clk_set_rate(ldb.ldb_di_clk[0], 2*ldb_clk_prate/7); + clk_set_rate(ldb.ldb_di_clk[1], 2*ldb_clk_prate/7); + } + clk_enable(ldb.ldb_di_clk[0]); + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[1]); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI0 | + LDB_CH1_MODE_EN_TO_DI0, ldb.control_reg); + ldb.ch_working[0] = true; + ldb.ch_working[1] = true; + break; + case LDB_DUL_DI1: + case LDB_SPL_DI1: + if (var[1] == NULL) { + dev_err(g_ldb_dev, "Can't find framebuffer on DI1\n"); + break; + } + + reg = __raw_readl(ldb.control_reg); + if (bits_per_pixel(ipu_di_pix_fmt[1]) == 24) + __raw_writel((reg & ~(LDB_DATA_WIDTH_CH0_MASK | + LDB_DATA_WIDTH_CH1_MASK)) | + LDB_DATA_WIDTH_CH0_24 | + LDB_DATA_WIDTH_CH1_24, + ldb.control_reg); + else if (bits_per_pixel(ipu_di_pix_fmt[1]) == 18) + __raw_writel((reg & ~(LDB_DATA_WIDTH_CH0_MASK | + LDB_DATA_WIDTH_CH1_MASK)) | + LDB_DATA_WIDTH_CH0_18 | + LDB_DATA_WIDTH_CH1_18, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[0] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH0_MASK) | + LDB_BIT_MAP_CH0_JEIDA, + ldb.control_reg); + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_bit_map[1] == LDB_BIT_MAP_SPWG) + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_SPWG, + ldb.control_reg); + else + __raw_writel((reg & ~LDB_BIT_MAP_CH1_MASK) | + LDB_BIT_MAP_CH1_JEIDA, + ldb.control_reg); + + reg = __raw_readl(ldb.control_reg); + if (ldb.chan_mode_opt == LDB_SPL_DI1) + __raw_writel(reg | LDB_SPLIT_MODE_EN, + ldb.control_reg); + + ldb.ldb_di_clk[0] = clk_get(NULL, "ldb_di0_clk"); + ldb.ldb_di_clk[1] = clk_get(NULL, "ldb_di1_clk"); + if (ldb.chan_mode_opt == LDB_DUL_DI1) { + clk_set_rate(ldb.ldb_di_clk[1], ldb_clk_prate/7); + } else { + clk_set_rate(ldb.ldb_di_clk[0], 2*ldb_clk_prate/7); + clk_set_rate(ldb.ldb_di_clk[1], 2*ldb_clk_prate/7); + } + clk_enable(ldb.ldb_di_clk[0]); + clk_enable(ldb.ldb_di_clk[1]); + clk_put(ldb.ldb_di_clk[0]); + clk_put(ldb.ldb_di_clk[1]); + + reg = __raw_readl(ldb.control_reg); + __raw_writel((reg & ~(LDB_CH0_MODE_MASK | + LDB_CH1_MODE_MASK)) | + LDB_CH0_MODE_EN_TO_DI1 | + LDB_CH1_MODE_EN_TO_DI1, ldb.control_reg); + ldb.ch_working[0] = true; + ldb.ch_working[1] = true; + break; + default: + break; + } + + mxc_ldb_major = register_chrdev(0, "mxc_ldb", &mxc_ldb_fops); + if (mxc_ldb_major < 0) { + dev_err(g_ldb_dev, "Unable to register MXC LDB as a char " + "device\n"); + ret = mxc_ldb_major; + goto err0; + } + + mxc_ldb_class = class_create(THIS_MODULE, "mxc_ldb"); + if (IS_ERR(mxc_ldb_class)) { + dev_err(g_ldb_dev, "Unable to create class for MXC LDB\n"); + ret = PTR_ERR(mxc_ldb_class); + goto err1; + } + + temp = device_create(mxc_ldb_class, NULL, MKDEV(mxc_ldb_major, 0), + NULL, "mxc_ldb"); + if (IS_ERR(temp)) { + dev_err(g_ldb_dev, "Unable to create class device for " + "MXC LDB\n"); + ret = PTR_ERR(temp); + goto err2; + } + + ret = fb_register_client(&nb); + if (ret < 0) + goto err2; + + if (primary && ldb.fbi[0] != NULL) { + acquire_console_sem(); + fb_blank(ldb.fbi[0], FB_BLANK_UNBLANK); + release_console_sem(); + fb_show_logo(ldb.fbi[0], 0); + } + + return ret; +err2: + class_destroy(mxc_ldb_class); +err1: + unregister_chrdev(mxc_ldb_major, "mxc_ldb"); +err0: + iounmap(ldb_reg); + return ret; +} + +static int ldb_remove(struct platform_device *pdev) +{ + int i; + + __raw_writel(0, ldb.control_reg); + + for (i = 0; i < 2; i++) { + if (ldb.ch_working[i]) { + ldb.ldb_di_clk[i] = clk_get(NULL, + i ? "ldb_di1_clk" : "ldb_di0_clk"); + clk_disable(ldb.ldb_di_clk[i]); + clk_put(ldb.ldb_di_clk[i]); + ldb.ch_working[i] = false; + } + } + + fb_unregister_client(&nb); + return 0; +} + +static int ldb_suspend(struct platform_device *pdev, pm_message_t state) +{ + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + case LDB_DUL_DI0: + case LDB_SPL_DI0: + ldb_disable(0); + break; + case LDB_SIN_DI1: + case LDB_DUL_DI1: + case LDB_SPL_DI1: + ldb_disable(1); + break; + case LDB_SEP: + ldb_disable(0); + ldb_disable(1); + break; + default: + break; + } + return 0; +} + +static int ldb_resume(struct platform_device *pdev) +{ + switch (ldb.chan_mode_opt) { + case LDB_SIN_DI0: + case LDB_DUL_DI0: + case LDB_SPL_DI0: + ldb_enable(0); + break; + case LDB_SIN_DI1: + case LDB_DUL_DI1: + case LDB_SPL_DI1: + ldb_enable(1); + break; + case LDB_SEP: + ldb_enable(0); + ldb_enable(1); + break; + default: + break; + } + return 0; +} + +static struct platform_driver mxcldb_driver = { + .driver = { + .name = "mxc_ldb", + }, + .probe = ldb_probe, + .remove = ldb_remove, + .suspend = ldb_suspend, + .resume = ldb_resume, +}; + +/* + * Parse user specified options (`ldb=') + * example: + * ldb=single(separate, dual or split),(di=0 or di=1), + * ch0_map=SPWG or JEIDA,ch1_map=SPWG or JEIDA + * + */ +static int __init ldb_setup(char *options) +{ + g_enable_ldb = true; + + if (!strlen(options)) + return 1; + else if (!strsep(&options, "=")) + return 1; + + if (!strncmp(options, "single", 6)) { + strsep(&options, ","); + if (!strncmp(options, "di=0", 4)) + g_chan_mode_opt = LDB_SIN_DI0; + else + g_chan_mode_opt = LDB_SIN_DI1; + } else if (!strncmp(options, "separate", 8)) { + g_chan_mode_opt = LDB_SEP; + } else if (!strncmp(options, "dual", 4)) { + strsep(&options, ","); + if (!strncmp(options, "di=", 3)) { + if (simple_strtoul(options + 3, NULL, 0) == 0) + g_chan_mode_opt = LDB_DUL_DI0; + else + g_chan_mode_opt = LDB_DUL_DI1; + } + } else if (!strncmp(options, "split", 5)) { + strsep(&options, ","); + if (!strncmp(options, "di=", 3)) { + if (simple_strtoul(options + 3, NULL, 0) == 0) + g_chan_mode_opt = LDB_SPL_DI0; + else + g_chan_mode_opt = LDB_SPL_DI1; + } + } else + return 1; + + if ((strsep(&options, ",") != NULL) && + !strncmp(options, "ch0_map=", 8)) { + if (!strncmp(options + 8, "SPWG", 4)) + g_chan_bit_map[0] = LDB_BIT_MAP_SPWG; + else + g_chan_bit_map[0] = LDB_BIT_MAP_JEIDA; + } + + if (!(g_chan_mode_opt == LDB_SIN_DI0 || + g_chan_mode_opt == LDB_SIN_DI1) && + (strsep(&options, ",") != NULL) && + !strncmp(options, "ch1_map=", 8)) { + if (!strncmp(options + 8, "SPWG", 4)) + g_chan_bit_map[1] = LDB_BIT_MAP_SPWG; + else + g_chan_bit_map[1] = LDB_BIT_MAP_JEIDA; + } + + g_boot_cmd = true; + + return 1; +} +__setup("ldb", ldb_setup); + +static int __init ldb_init(void) +{ + int ret; + + ret = platform_driver_register(&mxcldb_driver); + return 0; +} + +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/mx2fb.c b/drivers/video/mxc/mx2fb.c new file mode 100644 index 000000000000..d1daf9d1ea16 --- /dev/null +++ b/drivers/video/mxc/mx2fb.c @@ -0,0 +1,1349 @@ +/* + * Copyright (C) 2004-2010 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 000000000000..25f5c0d5404e --- /dev/null +++ b/drivers/video/mxc/mx2fb.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2004-2007, 2010 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_edid.c b/drivers/video/mxc/mxc_edid.c new file mode 100644 index 000000000000..0289c5253ad7 --- /dev/null +++ b/drivers/video/mxc/mxc_edid.c @@ -0,0 +1,319 @@ +/* + * 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 mxc_edid.c + * + * @brief MXC EDID tools + * + * @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 cea_modes[64] = { + /* #1: 640x480p@59.94/60Hz */ + [1] = { + NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #3: 720x480p@59.94/60Hz */ + [3] = { + NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #5: 1920x1080i@59.94/60Hz */ + [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, 0, + }, + /* #7: 720(1440)x480iH@59.94/60Hz */ + [7] = { + NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_INTERLACED, 0, + }, + /* #9: 720(1440)x240pH@59.94/60Hz */ + [9] = { + NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #18: 720x576pH@50Hz */ + [18] = { + NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, + FB_VMODE_NONINTERLACED, 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, 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, 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, 0, + }, + /* #35: (2880)x480p4x@59.94/60Hz */ + [35] = { + NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0, + FB_VMODE_NONINTERLACED, 0, + }, +}; + +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; + + 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_timming_desc_offset; + struct fb_videomode *mode, *m; + unsigned char index = 0x0; + unsigned char *block; + int i, num = 0; + + if (edid[index++] != 0x2) /* only support cea ext block now */ + return -1; + if (edid[index++] != 0x3) /* only support version 3*/ + return -1; + mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); + if (mode == NULL) + return -1; + + detail_timming_desc_offset = edid[index++]; + + 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; + + /* short desc */ + DPRINTK("CEA Short desc timmings\n"); + index++; + while (index < detail_timming_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(cea_modes) && + (cea_modes[cea_idx].xres)) { + DPRINTK("Support CEA Format #%d\n", cea_idx); + mode[num] = cea_modes[cea_idx]; + mode[num].flag |= FB_MODE_IS_STANDARD; + num++; + } + i++; + } + break; + } + case 0x3: /*Vendor specific data*/ + { + unsigned char IEEE_reg_iden[3]; + IEEE_reg_iden[0] = edid[index+1]; + IEEE_reg_iden[1] = edid[index+2]; + IEEE_reg_iden[2] = edid[index+3]; + + if ((IEEE_reg_iden[0] == 0x03) && + (IEEE_reg_iden[1] == 0x0c) && + (IEEE_reg_iden[2] == 0x00)) + cfg->hdmi_cap = 1; + index += blklen; + break; + } + case 0x1: /*Audio data block*/ + case 0x4: /*Speaker allocation block*/ + case 0x7: /*User extended block*/ + default: + /* skip */ + index += blklen; + break; + } + + index++; + } + + /* long desc */ + DPRINTK("CEA long desc timmings\n"); + index = detail_timming_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; +} + +/* make sure edid has 256 bytes*/ +int mxc_edid_read(struct i2c_adapter *adp, unsigned char *edid, + struct mxc_edid_cfg *cfg, struct fb_info *fbi) +{ + u8 buf0[2] = {0, 0}; + int dat = 0; + u16 addr = 0x50; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .len = 1, + .buf = buf0, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + if (adp == NULL) + return -EINVAL; + + memset(edid, 0, 256); + memset(cfg, 0, sizeof(struct mxc_edid_cfg)); + + buf0[0] = 0x00; + dat = i2c_transfer(adp, msg, 2); + + /* If 0x50 fails, try 0x37. */ + if (edid[1] == 0x00) { + msg[0].addr = msg[1].addr = 0x37; + dat = i2c_transfer(adp, msg, 2); + if (dat < 0) + return dat; + } + + if (edid[1] == 0x00) + return -ENOENT; + + /* edid first block parsing */ + memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); + fb_edid_to_monspecs(edid, &fbi->monspecs); + + /* need read ext block? Only support one more blk now*/ + if (edid[0x7E]) { + if (edid[0x7E] > 1) + DPRINTK("Edid has %d ext block, \ + but now only support 1 ext blk\n", edid[0x7E]); + buf0[0] = 0x80; + msg[1].buf = edid + EDID_LENGTH; + dat = i2c_transfer(adp, msg, 2); + if (dat < 0) + return dat; + + /* edid ext block parsing */ + mxc_edid_parse_ext_blk(edid + 128, cfg, &fbi->monspecs); + } + + return 0; +} diff --git a/drivers/video/mxc/mxc_elcdif_fb.c b/drivers/video/mxc/mxc_elcdif_fb.c new file mode 100644 index 000000000000..90dfe55ece42 --- /dev/null +++ b/drivers/video/mxc/mxc_elcdif_fb.c @@ -0,0 +1,1466 @@ +/* + * Copyright (C) 2010 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" + +/* 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; + struct completion vsync_complete; + struct completion frame_done_complete; + struct semaphore flip_sem; + 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 */ +}; + +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 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; + + if (!g_elcdif_axi_clk_enable) { + clk_enable(g_elcdif_axi_clk); + g_elcdif_axi_clk_enable = true; + } + + dev_dbg(g_elcdif_dev, "pixel clk = %d\n", pixel_clk); + rounded_pixel_clk = clk_round_rate(g_elcdif_pix_clk, pixel_clk); + clk_set_rate(g_elcdif_pix_clk, rounded_pixel_clk); + + __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(BF_ELCDIF_CTRL2_OUTSTANDING_REQS + (BV_ELCDIF_CTRL2_OUTSTANDING_REQS__REQ_8), + 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; +} + +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(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_SET); + __raw_writel(BM_ELCDIF_CTRL_RUN, + elcdif_base + HW_ELCDIF_CTRL_SET); + 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 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; + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + sema_init(&data->flip_sem, 1); + + /* release prev panel */ + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + mxc_elcdif_blank_panel(FB_BLANK_POWERDOWN); + mxc_elcdif_stop(); + release_dotclk_panel(); + mxc_elcdif_dma_release(); + mxc_elcdif_fb_set_fix(fbi); + if (g_elcdif_pix_clk_enable) { + 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)) { + if (fbi->fix.smem_start) + mxc_elcdif_fb_unmap_video_memory(fbi); + + if (mxc_elcdif_fb_map_video_memory(fbi) < 0) + return -ENOMEM; + } + + if (data->next_blank != FB_BLANK_UNBLANK) + return 0; + + /* init next panel */ + if (!g_elcdif_pix_clk_enable) { + clk_enable(g_elcdif_pix_clk); + g_elcdif_pix_clk_enable = true; + } + mxc_init_elcdif(); + mxc_elcdif_init_panel(); + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + 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_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + + 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.upper_margin, + fbi->var.yres, + fbi->var.hsync_len, + fbi->var.left_margin + + fbi->var.xres + fbi->var.right_margin, + fbi->var.left_margin, + 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(); + mxc_elcdif_blank_panel(FB_BLANK_UNBLANK); + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + return 0; +} + +static int mxc_elcdif_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + 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; + + 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) { + 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; + + down(&data->flip_sem); + + __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 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; + + 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; + + 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; + + if (pdata && pdata->mode && pdata->num_modes) + fb_videomode_to_modelist(pdata->mode, pdata->num_modes, + &fbi->modelist); + + if (!fb_mode && pdata && pdata->mode_str) + fb_mode = pdata->mode_str; + + if (fb_mode) { + ret = fb_find_mode(&fbi->var, fbi, fb_mode, NULL, 0, NULL, + default_bpp); + if ((!ret || (ret > 2)) && pdata && pdata->mode && + pdata->num_modes) + fb_find_mode(&fbi->var, fbi, fb_mode, pdata->mode, + pdata->num_modes, NULL, default_bpp); + } + + 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); + + ret = register_framebuffer(fbi); + if (ret) + goto err3; + + platform_set_drvdata(pdev, fbi); + + 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: + 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; +} + +#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; + + acquire_console_sem(); + 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; + } + release_console_sem(); + 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; + + acquire_console_sem(); + mxc_elcdif_fb_blank(data->next_blank, fbi); + fb_set_suspend(fbi, 0); + release_console_sem(); + + 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_fb.c b/drivers/video/mxc/mxc_epdc_fb.c new file mode 100644 index 000000000000..9de9ffffd464 --- /dev/null +++ b/drivers/video/mxc/mxc_epdc_fb.c @@ -0,0 +1,3786 @@ +/* + * Copyright (C) 2010-2011 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/time.h> + +#include "epdc_regs.h" + +/* + * Enable this define to have a default panel + * loaded during driver initialization + */ +/*#define DEFAULT_PANEL_HW_INIT*/ + +#define NUM_SCREENS_MIN 2 +#define EPDC_NUM_LUTS 16 +#define EPDC_MAX_NUM_UPDATES 20 +#define INVALID_LUT -1 + +#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 + +static unsigned long default_bpp = 16; + +struct update_marker_data { + u32 update_marker; + struct completion update_completion; + int lut_num; +}; + +/* 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; + struct mxcfb_update_data upd_data;/* Update parameters */ + dma_addr_t phys_addr; /* Pointer to phys address of processed Y buf */ + void *virt_addr; + u32 epdc_offs; /* Add to buffer pointer to resolve alignment */ + u32 size; + int lut_num; /* Assigned before update is processed into working buffer */ + int collision_mask; /* Set when update results in collision */ + /* Represents other LUTs that we collide with */ + struct update_marker_data *upd_marker_data; + u32 update_order; /* Numeric ordering value for update */ + u32 fb_offset; /* FB offset associated with update */ +}; + +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[24]; + struct list_head list; + struct mxc_epdc_fb_mode *cur_mode; + struct mxc_epdc_fb_platform_data *pdata; + int blank; + 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; + struct clk *epdc_clk_axi; + struct clk *epdc_clk_pix; + struct regulator *display_regulator; + struct regulator *vcom_regulator; + bool fw_default_load; + + /* FB elements related to EPDC updates */ + bool in_init; + bool hw_ready; + bool waiting_for_idle; + u32 auto_mode; + u32 upd_scheme; + struct update_data_list *upd_buf_queue; + struct update_data_list *upd_buf_free_list; + struct update_data_list *upd_buf_collision_list; + struct update_data_list *cur_update; + spinlock_t queue_lock; + int trt_entries; + int temp_index; + u8 *temp_range_bounds; + struct mxcfb_waveform_modes wv_modes; + u32 *waveform_buffer_virt; + u32 waveform_buffer_phys; + u32 waveform_buffer_size; + u32 *working_buffer_virt; + u32 working_buffer_phys; + u32 working_buffer_size; + u32 order_cnt; + struct update_marker_data update_marker_array[EPDC_MAX_NUM_UPDATES]; + u32 lut_update_order[EPDC_NUM_LUTS]; + struct completion updates_done; + struct delayed_work epdc_done_work; + struct workqueue_struct *epdc_submit_workqueue; + struct work_struct epdc_submit_work; + bool waiting_for_wb; + bool waiting_for_lut; + struct completion update_res_free; + struct mutex power_mutex; + bool powering_down; + int pwrdown_delay; + + /* 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 reserved0_0: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; +}; + +struct mxcfb_waveform_data_file { + struct waveform_data_header wdh; + u32 *data; /* Temperature Range Table + Waveform Data */ +}; + +void __iomem *epdc_base; + +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, + int x_start_offs); +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); + + +#ifdef DEBUG +static void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) +{ + dev_err(fb_data->dev, "S0 fmt 0x%x", + pxp_conf->s0_param.pixel_fmt); + dev_err(fb_data->dev, "S0 width 0x%x", + pxp_conf->s0_param.width); + dev_err(fb_data->dev, "S0 height 0x%x", + pxp_conf->s0_param.height); + dev_err(fb_data->dev, "S0 ckey 0x%x", + pxp_conf->s0_param.color_key); + dev_err(fb_data->dev, "S0 ckey en 0x%x", + pxp_conf->s0_param.color_key_enable); + + dev_err(fb_data->dev, "OL0 combine en 0x%x", + pxp_conf->ol_param[0].combine_enable); + dev_err(fb_data->dev, "OL0 fmt 0x%x", + pxp_conf->ol_param[0].pixel_fmt); + dev_err(fb_data->dev, "OL0 width 0x%x", + pxp_conf->ol_param[0].width); + dev_err(fb_data->dev, "OL0 height 0x%x", + pxp_conf->ol_param[0].height); + dev_err(fb_data->dev, "OL0 ckey 0x%x", + pxp_conf->ol_param[0].color_key); + dev_err(fb_data->dev, "OL0 ckey en 0x%x", + pxp_conf->ol_param[0].color_key_enable); + dev_err(fb_data->dev, "OL0 alpha 0x%x", + pxp_conf->ol_param[0].global_alpha); + dev_err(fb_data->dev, "OL0 alpha en 0x%x", + pxp_conf->ol_param[0].global_alpha_enable); + dev_err(fb_data->dev, "OL0 local alpha en 0x%x", + pxp_conf->ol_param[0].local_alpha_enable); + + dev_err(fb_data->dev, "Out fmt 0x%x", + pxp_conf->out_param.pixel_fmt); + dev_err(fb_data->dev, "Out width 0x%x", + pxp_conf->out_param.width); + dev_err(fb_data->dev, "Out height 0x%x", + pxp_conf->out_param.height); + + dev_err(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_err(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_err(fb_data->dev, "Scaling en 0x%x", pxp_conf->proc_data.scaling); + dev_err(fb_data->dev, "HFlip en 0x%x", pxp_conf->proc_data.hflip); + dev_err(fb_data->dev, "VFlip en 0x%x", pxp_conf->proc_data.vflip); + dev_err(fb_data->dev, "Rotation 0x%x", pxp_conf->proc_data.rotate); + dev_err(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_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_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_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_NEXTLUT 0x%x\n", __raw_readl(EPDC_STATUS_NEXTLUT)); + printk(KERN_DEBUG "EPDC_STATUS_COL 0x%x\n", __raw_readl(EPDC_STATUS_COL)); + printk(KERN_DEBUG "EPDC_STATUS 0x%x\n", __raw_readl(EPDC_STATUS)); + printk(KERN_DEBUG "EPDC_DEBUG 0x%x\n", __raw_readl(EPDC_DEBUG)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT0 0x%x\n", __raw_readl(EPDC_DEBUG_LUT0)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT1 0x%x\n", __raw_readl(EPDC_DEBUG_LUT1)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT2 0x%x\n", __raw_readl(EPDC_DEBUG_LUT2)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT3 0x%x\n", __raw_readl(EPDC_DEBUG_LUT3)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT4 0x%x\n", __raw_readl(EPDC_DEBUG_LUT4)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT5 0x%x\n", __raw_readl(EPDC_DEBUG_LUT5)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT6 0x%x\n", __raw_readl(EPDC_DEBUG_LUT6)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT7 0x%x\n", __raw_readl(EPDC_DEBUG_LUT7)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT8 0x%x\n", __raw_readl(EPDC_DEBUG_LUT8)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT9 0x%x\n", __raw_readl(EPDC_DEBUG_LUT9)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT10 0x%x\n", __raw_readl(EPDC_DEBUG_LUT10)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT11 0x%x\n", __raw_readl(EPDC_DEBUG_LUT11)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT12 0x%x\n", __raw_readl(EPDC_DEBUG_LUT12)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT13 0x%x\n", __raw_readl(EPDC_DEBUG_LUT13)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT14 0x%x\n", __raw_readl(EPDC_DEBUG_LUT14)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT15 0x%x\n", __raw_readl(EPDC_DEBUG_LUT15)); + 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_err(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, LUT = %d, Coll Mask = %d\n", + upd_data_list->upd_data.update_region.left, + upd_data_list->upd_data.update_region.top, + upd_data_list->upd_data.update_region.width, + upd_data_list->upd_data.update_region.height, + upd_data_list->upd_data.waveform_mode, upd_data_list->lut_num, + upd_data_list->collision_mask); +} + +static void dump_collision_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_err(fb_data->dev, "Collision List:\n"); + if (list_empty(&fb_data->upd_buf_collision_list->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_collision_list->list, list) { + dev_err(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_err(fb_data->dev, "Free List:\n"); + if (list_empty(&fb_data->upd_buf_free_list->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_free_list->list, list) { + dev_err(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_queue(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_err(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_buf_queue->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_queue->list, list) { + dev_err(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_all_updates(struct mxc_epdc_fb_data *fb_data) +{ + dump_free_list(fb_data); + dump_queue(fb_data); + dump_collision_list(fb_data); + dev_err(fb_data->dev, "Current update being processed:\n"); + if (fb_data->cur_update == NULL) + dev_err(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 + + +/******************************************************** + * Start Low-Level EPDC Functions + ********************************************************/ + +static inline void epdc_lut_complete_intr(u32 lut_num, bool enable) +{ + if (enable) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_SET); + else + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_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_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) +{ + 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) +{ + u32 val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width; + __raw_writel(val, EPDC_UPD_SIZE); +} + +static void epdc_submit_update(u32 lut_num, u32 waveform_mode, u32 update_mode, + bool use_test_mode, u32 np_val) +{ + u32 reg_val = 0; + + 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); + } + + reg_val |= + ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) & + EPDC_UPD_CTRL_LUT_SEL_MASK) | + ((waveform_mode << EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) & + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK) | + update_mode; + + __raw_writel(reg_val, EPDC_UPD_CTRL); +} + +static inline bool epdc_is_lut_complete(u32 lut_num) +{ + u32 val = __raw_readl(EPDC_IRQ); + bool is_compl = val & (1 << lut_num) ? true : false; + + return is_compl; +} + +static inline void epdc_clear_lut_complete_irq(u32 lut_num) +{ + __raw_writel(1 << lut_num, EPDC_IRQ_CLEAR); +} + +static inline bool epdc_is_lut_active(u32 lut_num) +{ + u32 val = __raw_readl(EPDC_STATUS_LUTS); + bool is_active = val & (1 << lut_num) ? true : false; + + return is_active; +} + +static inline bool epdc_any_luts_active(void) +{ + bool any_active = __raw_readl(EPDC_STATUS_LUTS) ? 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 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_collision(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false; +} + +static inline int epdc_get_colliding_luts(void) +{ + u32 val = __raw_readl(EPDC_STATUS_COL); + 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); +} + +void epdc_init_settings(struct mxc_epdc_fb_data *fb_data) +{ + struct mxc_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; + + /* 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 4bit Buf pixel format */ + reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT + | EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N + | ((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(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_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; + __raw_writel(reg_val, EPDC_TCE_CTRL); + + /* EPDC_TCE_HSCAN */ + epdc_set_horizontal_timing(screeninfo->left_margin, + screeninfo->right_margin, + screeninfo->hsync_len, + screeninfo->hsync_len); + + /* EPDC_TCE_VSCAN */ + epdc_set_vertical_timing(screeninfo->upper_margin, + screeninfo->lower_margin, + screeninfo->vsync_len); + + /* 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); +} + +static void epdc_powerup(struct mxc_epdc_fb_data *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, "EPDC Powerup\n"); + + /* Enable pins used by EPDC */ + if (fb_data->pdata->enable_pins) + 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); + + /* 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; + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_powerdown(struct mxc_epdc_fb_data *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, "EPDC Powerdown\n"); + + /* Disable power to the EPD panel */ + regulator_disable(fb_data->vcom_regulator); + regulator_disable(fb_data->display_regulator); + + /* 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) + fb_data->pdata->disable_pins(); + + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_init_sequence(struct mxc_epdc_fb_data *fb_data) +{ + /* Initialize EPDC, passing pointer to EPDC registers */ + epdc_init_settings(fb_data); + __raw_writel(fb_data->waveform_buffer_phys, EPDC_WVADDR); + __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR); + epdc_powerup(fb_data); + draw_mode0(fb_data); + epdc_powerdown(fb_data); +} + +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; + + if (offset < info->fix.smem_len) { + /* mapping framebuffer memory */ + len = info->fix.smem_len - offset; + vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT; + } else + 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(info->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +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) +{ + if (regno >= 256) /* no. of hw registers */ + return 1; + /* + * Program hardware... do anything you want with transp + */ + + /* 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; +} + +static void adjust_coordinates(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region) +{ + struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 rotation = fb_data->epdc_fb_var.rotate; + 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 = screeninfo->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 = screeninfo->yres - + (update_region->top + update_region->height); + adj_update_region->left = screeninfo->xres - + (update_region->left + update_region->width); + break; + case FB_ROTATE_CCW: + adj_update_region->left = update_region->top; + adj_update_region->top = screeninfo->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 = screeninfo->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 = screeninfo->yres - + (update_region->top + update_region->height); + update_region->left = screeninfo->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 = screeninfo->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 mxc_epdc_fb_mode *epdc_modes = fb_data->pdata->epdc_mode; + int i; + int ret; + unsigned long flags; + + /* + * 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); + + spin_lock_irqsave(&fb_data->queue_lock, flags); + fb_data->epdc_fb_var = *screeninfo; + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + 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_GREY; + 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_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; + 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 epdc modes */ + for (i = 0; i < fb_data->pdata->num_modes; i++) { + if (!fb_mode_is_equal(epdc_modes[i].vmode, &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"); + 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; + + /* 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"); + return ret; + } + } + + mxc_epdc_fb_set_fix(info); + + 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; + + 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 = default_bpp; + + switch (var->bits_per_pixel) { + case 8: + if (var->grayscale != 0) { + /* + * 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 { + 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_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; + + memcpy(&fb_data->wv_modes, modes, sizeof(modes)); +} +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; + + if (fb_data->trt_entries == 0) { + dev_err(fb_data->dev, + "No TRT exists...using default temp index\n"); + return DEFAULT_TEMP_INDEX; + } + + /* Search temperature ranges for a match */ + for (i = 0; i < fb_data->trt_entries - 1; i++) { + if ((temp >= fb_data->temp_range_bounds[i]) + && (temp < fb_data->temp_range_bounds[i+1])) { + index = i; + break; + } + } + + if (index < 0) { + dev_err(fb_data->dev, + "No TRT index match...using default temp index\n"); + return DEFAULT_TEMP_INDEX; + } + + 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; + unsigned long flags; + + /* Store temp index. Used later when configuring updates. */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, temperature); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + 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; + + 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_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; + + 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)) + fb_data->upd_scheme = upd_scheme; + else { + dev_err(fb_data->dev, "Invalid update scheme specified.\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_set_upd_scheme); + +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_8, bytes_per_pixel; + u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; + u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; + int x_start_offs = 0; + u32 hist_stat = 0; + + int ret; + + /* + * Gotta do a whole bunch of buffer ptr manipulation to + * work around HW restrictions for PxP & EPDC + */ + + /* + * Are we using FB or an alternate (overlay) + * buffer for source of update? + */ + if (upd_data_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { + src_width = upd_data_list->upd_data.alt_buffer_data.width; + src_height = upd_data_list->upd_data.alt_buffer_data.height; + src_upd_region = &upd_data_list->upd_data.alt_buffer_data.alt_update_region; + } else { + src_width = fb_data->epdc_fb_var.xres_virtual; + src_height = fb_data->epdc_fb_var.yres; + src_upd_region = &upd_data_list->upd_data.update_region; + } + + /* + * Compute buffer offset to account for + * PxP limitation (must read 8x8 pixel blocks) + */ + offset_from_8 = src_upd_region->left & 0x7; + bytes_per_pixel = fb_data->epdc_fb_var.bits_per_pixel/8; + if ((offset_from_8 * fb_data->epdc_fb_var.bits_per_pixel/8 % 4) != 0) { + /* 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 should change to reflect relative position to input ptr */ + pxp_upd_region.top = 0; + pxp_upd_region.left = (offset_from_8 & 0x3) % bytes_per_pixel; + } else { + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel; + /* Update region should change to reflect relative position to input ptr */ + pxp_upd_region.top = 0; + pxp_upd_region.left = 0; + } + + /* + * We want PxP processing region to start at the first pixel + * that we have to process in each row, but PxP alignment + * restricts the input mem address to be 32-bit aligned + * + * We work around this by using x_start_offs + * to offset from our starting pixel location to the + * first pixel that is in the relevant update region + * for each row. + */ + x_start_offs = pxp_upd_region.left & 0x7; + + /* Update region to meet 8x8 pixel requirement */ + pxp_upd_region.width = ALIGN(src_upd_region->width, 8); + pxp_upd_region.height = ALIGN(src_upd_region->height, 8); + pxp_upd_region.top &= ~0x7; + pxp_upd_region.left &= ~0x7; + + switch (fb_data->epdc_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; + } + + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + post_rotation_xcoord; + + pxp_output_shift = ALIGN(pxp_output_offs, 8) - pxp_output_offs; + + upd_data_list->epdc_offs = pxp_output_offs + pxp_output_shift; + + mutex_lock(&fb_data->pxp_mutex); + + /* Source address either comes from alternate buffer + provided in update data, or from the framebuffer. */ + if (upd_data_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) + sg_dma_address(&fb_data->sg[0]) = + upd_data_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 + upd_data_list->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_offs; + sg_set_page(&fb_data->sg[1], virt_to_page(upd_data_list->virt_addr), + upd_data_list->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_data_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION) + fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT; + if (upd_data_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME) + fb_data->pxp_conf.proc_data.lut_transform |= + PXP_LUT_BLACK_WHITE; + + /* + * Toggle inversion processing if 8-bit + * inverted is the current pixel format. + */ + if (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT_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, x_start_offs); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + mutex_unlock(&fb_data->pxp_mutex); + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + epdc_powerup(fb_data); + } + + mutex_lock(&fb_data->pxp_mutex); + + /* 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; + } + + mutex_unlock(&fb_data->pxp_mutex); + + /* Update waveform mode from PxP histogram results */ + if (upd_data_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO) { + if (hist_stat & 0x1) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_du; + else if (hist_stat & 0x2) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc4; + else if (hist_stat & 0x4) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc8; + else if (hist_stat & 0x8) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; + else + upd_data_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_data_list->upd_data.waveform_mode); + } + + return 0; + +} + +static bool epdc_submit_merge(struct update_data_list *upd_data_list, + struct update_data_list *update_to_merge) +{ + struct mxcfb_update_data *a, *b; + struct mxcfb_rect *arect, *brect; + struct mxcfb_rect combine; + + a = &upd_data_list->upd_data; + b = &update_to_merge->upd_data; + arect = &upd_data_list->upd_data.update_region; + brect = &update_to_merge->upd_data.update_region; + + if ((a->waveform_mode != b->waveform_mode + && a->waveform_mode != WAVEFORM_MODE_AUTO) || + a->update_mode != b->update_mode || + (a->flags & EPDC_FLAG_USE_ALT_BUFFER) || + (b->flags & EPDC_FLAG_USE_ALT_BUFFER) || + arect->left > (brect->left + brect->width) || + brect->left > (arect->left + arect->width) || + arect->top > (brect->top + brect->height) || + brect->top > (arect->top + arect->height) || + (upd_data_list->fb_offset != update_to_merge->fb_offset) || + (b->update_marker != 0 && a->update_marker != 0)) + return false; + + 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->left = combine.left; + arect->top = combine.top; + arect->width = combine.width; + arect->height = combine.height; + + /* Preserve marker value for merged update */ + if (b->update_marker != 0) { + a->update_marker = b->update_marker; + upd_data_list->upd_marker_data = + update_to_merge->upd_marker_data; + } + + /* Merged update should take on the earliest order */ + upd_data_list->update_order = + (upd_data_list->update_order > update_to_merge->update_order) ? + upd_data_list->update_order : update_to_merge->update_order; + + return true; +} + +static void epdc_submit_work_func(struct work_struct *work) +{ + int temp_index; + struct update_data_list *next_update; + struct update_data_list *temp; + unsigned long flags; + 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; + + /* Protect access to buffer queues and to update HW */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * 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, + &fb_data->upd_buf_collision_list->list, list) { + + if (next_update->collision_mask != 0) + continue; + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + /* + * We have a collision cleared, so select it for resubmission. + * If an update is already selected, attempt to merge. + */ + if (!upd_data_list) { + upd_data_list = next_update; + list_del_init(&next_update->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + /* If not merging, we have our update */ + break; + } else if (epdc_submit_merge(upd_data_list, next_update)) { + dev_dbg(fb_data->dev, + "Update merged [work queue]\n"); + list_del_init(&next_update->list); + /* Add to free buffer list */ + list_add_tail(&next_update->list, + &fb_data->upd_buf_free_list->list); + } else + dev_dbg(fb_data->dev, + "Update not merged [work queue]\n"); + } + + /* + * Skip update queue only if we found a collision + * update and we are not merging + */ + if (!((fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) && + upd_data_list)) { + /* + * If we didn't find a collision update ready to go, + * we try to grab one from the update queue + */ + list_for_each_entry_safe(next_update, temp, + &fb_data->upd_buf_queue->list, list) { + + dev_dbg(fb_data->dev, "Found a pending update!\n"); + + if (!upd_data_list) { + upd_data_list = next_update; + list_del_init(&next_update->list); + if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) + /* If not merging, we have an update */ + break; + } else if (epdc_submit_merge(upd_data_list, + next_update)) { + dev_dbg(fb_data->dev, + "Update merged [work queue]\n"); + list_del_init(&next_update->list); + /* Add to free buffer list */ + list_add_tail(&next_update->list, + &fb_data->upd_buf_free_list->list); + } else + dev_dbg(fb_data->dev, + "Update not merged [work queue]\n"); + } + } + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + /* Is update list empty? */ + if (!upd_data_list) + return; + + /* 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 */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + /* Add to free buffer list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_free_list->list); + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return; + } + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data, &upd_data_list->upd_data.update_region, + &adj_update_region); + + /* Protect access to buffer queues and to update HW */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * 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) { + 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 */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + wait_for_completion(&fb_data->update_res_free); + spin_lock_irqsave(&fb_data->queue_lock, flags); + } + + /* + * 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()) { + 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 */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + wait_for_completion(&fb_data->update_res_free); + spin_lock_irqsave(&fb_data->queue_lock, flags); + } + + + /* LUTs are available, so we get one here */ + fb_data->cur_update = upd_data_list; + fb_data->cur_update->lut_num = epdc_get_next_lut(); + + /* Associate LUT with update marker */ + if ((fb_data->cur_update->upd_marker_data) + && (fb_data->cur_update->upd_marker_data->update_marker != 0)) + fb_data->cur_update->upd_marker_data->lut_num = + fb_data->cur_update->lut_num; + + /* Mark LUT with order */ + fb_data->lut_update_order[fb_data->cur_update->lut_num] = + fb_data->cur_update->update_order; + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + if (fb_data->cur_update->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + fb_data->cur_update->upd_data.temp); + epdc_set_temp(temp_index); + } + epdc_set_update_addr(fb_data->cur_update->phys_addr + + fb_data->cur_update->epdc_offs); + epdc_set_update_coord(adj_update_region.left, adj_update_region.top); + epdc_set_update_dimensions(adj_update_region.width, + adj_update_region.height); + epdc_submit_update(fb_data->cur_update->lut_num, + fb_data->cur_update->upd_data.waveform_mode, + fb_data->cur_update->upd_data.update_mode, false, 0); + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); +} + + +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; + struct update_data_list *upd_data_list = NULL; + unsigned long flags; + int i; + struct mxcfb_rect *screen_upd_region; /* Region on screen to update */ + int temp_index; + int ret; + + /* Has EPDC HW been initialized? */ + if (!fb_data->hw_ready) { + dev_err(fb_data->dev, "Display HW not properly initialized." + " Aborting update.\n"); + 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); + return -EINVAL; + } + if ((upd_data->waveform_mode > 255) && + (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->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"); + return -EINVAL; + } + if ((upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) && + ((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; + } + + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * 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"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return -EPERM; + } + + /* + * Get available intermediate (PxP output) buffer to hold + * processed update region + */ + if (list_empty(&fb_data->upd_buf_free_list->list)) { + dev_err(fb_data->dev, + "No free intermediate buffers available.\n"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return -ENOMEM; + } + + /* Grab first available buffer and delete it from the free list */ + upd_data_list = + list_entry(fb_data->upd_buf_free_list->list.next, + struct update_data_list, list); + + list_del_init(&upd_data_list->list); + + /* copy update parameters to the current update data object */ + memcpy(&upd_data_list->upd_data, upd_data, + sizeof(struct mxcfb_update_data)); + memcpy(&upd_data_list->upd_data.update_region, &upd_data->update_region, + sizeof(struct mxcfb_rect)); + + upd_data_list->fb_offset = fb_data->fb_offset; + /* If marker specified, associate it with a completion */ + if (upd_data->update_marker != 0) { + /* Find available update marker and set it up */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + /* Marker value set to 0 signifies it is not currently in use */ + if (fb_data->update_marker_array[i].update_marker == 0) { + fb_data->update_marker_array[i].update_marker = upd_data->update_marker; + init_completion(&fb_data->update_marker_array[i].update_completion); + upd_data_list->upd_marker_data = &fb_data->update_marker_array[i]; + break; + } + } + } else { + if (upd_data_list->upd_marker_data) + upd_data_list->upd_marker_data->update_marker = 0; + } + + upd_data_list->update_order = fb_data->order_cnt++; + + if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { + /* Queued update scheme processing */ + + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_queue->list); + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + /* Signal workqueue to handle new update */ + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + return 0; + } + + /* Snapshot update scheme processing */ + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + /* + * Hold on to original screen update region, which we + * will ultimately use when telling EPDC where to update on panel + */ + screen_upd_region = &upd_data_list->upd_data.update_region; + + ret = epdc_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_data_list->upd_data.waveform_mode; + + /* Get rotation-adjusted coordinates */ + adjust_coordinates(fb_data, &upd_data_list->upd_data.update_region, + NULL); + + /* Grab lock for queue manipulation and update submission */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * 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->list); + + /* Return and allow the update to be submitted by the ISR. */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return 0; + } + + /* Save current update */ + fb_data->cur_update = upd_data_list; + + /* LUTs are available, so we get one here */ + upd_data_list->lut_num = epdc_get_next_lut(); + + /* Associate LUT with update marker */ + if (upd_data_list->upd_marker_data) + if (upd_data_list->upd_marker_data->update_marker != 0) + upd_data_list->upd_marker_data->lut_num = upd_data_list->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_order[upd_data_list->lut_num] = + upd_data_list->update_order; + + /* Clear status and Enable LUT complete and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(upd_data_list->phys_addr + upd_data_list->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 (upd_data_list->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, + upd_data_list->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + + epdc_submit_update(upd_data_list->lut_num, + upd_data_list->upd_data.waveform_mode, + upd_data_list->upd_data.update_mode, false, 0); + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return 0; +} +EXPORT_SYMBOL(mxc_epdc_fb_send_update); + +int mxc_epdc_fb_wait_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; + int ret; + int i; + + /* 0 is an invalid update_marker value */ + if (update_marker == 0) + return -EINVAL; + + /* Wait for completion associated with update_marker requested */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + if (fb_data->update_marker_array[i].update_marker == update_marker) { + dev_dbg(fb_data->dev, "Waiting for marker %d\n", update_marker); + ret = wait_for_completion_timeout(&fb_data->update_marker_array[i].update_completion, msecs_to_jiffies(5000)); + if (!ret) + dev_err(fb_data->dev, "Timed out waiting for update completion\n"); + + dev_dbg(fb_data->dev, "marker %d signalled!\n", update_marker); + + /* Reset marker so it can be reused */ + fb_data->update_marker_array[i].update_marker = 0; + + break; + } + } + + return 0; +} +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_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int ret = -EINVAL; + + switch (cmd) { + case MXCFB_SET_WAVEFORM_MODES: + { + 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: + { + 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: + { + 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: + { + 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: + { + 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; + } + case MXCFB_WAIT_FOR_UPDATE_COMPLETE: + { + u32 update_marker = 0; + if (!get_user(update_marker, (__u32 __user *) arg)) + ret = + mxc_epdc_fb_wait_update_complete(update_marker, + info); + break; + } + + case MXCFB_SET_PWRDOWN_DELAY: + { + 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: + { + int pwrdown_delay = mxc_epdc_get_pwrdown_delay(info); + if (put_user(pwrdown_delay, + (int __user *)argp)) + ret = -EFAULT; + ret = 0; + break; + } + default: + break; + } + 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; + + 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); +} + +void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data) +{ + unsigned long flags; + /* Grab queue lock to prevent any new updates from being submitted */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + if (!is_free_list_full(fb_data)) { + /* Initialize event signalling updates are done */ + init_completion(&fb_data->updates_done); + fb_data->waiting_for_idle = true; + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + /* Wait for any currently active updates to complete */ + wait_for_completion_timeout(&fb_data->updates_done, msecs_to_jiffies(2000)); + spin_lock_irqsave(&fb_data->queue_lock, flags); + fb_data->waiting_for_idle = false; + } + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); +} + +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; + + 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: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxc_epdc_fb_flush_updates(fb_data); + break; + } + 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; + unsigned long flags; + + dev_dbg(info->device, "%s: var->xoffset %d, info->var.xoffset %d\n", + __func__, var->xoffset, info->var.xoffset); + /* 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 */ + + spin_lock_irqsave(&fb_data->queue_lock, flags); + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) { + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return -EINVAL; + } + + 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; + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + 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_setcolreg = mxc_epdc_fb_setcolreg, + .fb_pan_display = mxc_epdc_fb_pan_display, + .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 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, list) + count++; + + /* Check to see if all buffers are in this list */ + if (count == EPDC_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; + struct update_data_list *collision_update; + struct mxcfb_rect *next_upd_region; + unsigned long flags; + int temp_index; + u32 luts_completed_mask; + u32 temp_mask; + u32 lut; + bool ignore_collision = false; + int i, j; + + /* + * If we just completed one-time panel init, bypass + * queue handling, clear interrupt and return + */ + if (fb_data->in_init) { + if (epdc_is_working_buffer_complete()) { + 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(0)) { + epdc_lut_complete_intr(0, false); + epdc_clear_lut_complete_irq(0); + fb_data->in_init = false; + dev_dbg(fb_data->dev, "Cleared LUT complete for init update\n"); + } + + return IRQ_HANDLED; + } + + if (!(__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ))) + return IRQ_HANDLED; + + if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) { + dev_err(fb_data->dev, "TCE underrun! Panel may lock up.\n"); + return IRQ_HANDLED; + } + + /* Protect access to buffer queues and to update HW */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* Free any LUTs that have completed */ + luts_completed_mask = 0; + for (i = 0; i < EPDC_NUM_LUTS; i++) { + if (!epdc_is_lut_complete(i)) + continue; + + dev_dbg(fb_data->dev, "\nLUT %d completed\n", i); + + /* Disable IRQ for completed LUT */ + epdc_lut_complete_intr(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, list) { + collision_update->collision_mask = + collision_update->collision_mask & ~(1 << i); + } + + epdc_clear_lut_complete_irq(i); + + luts_completed_mask |= 1 << i; + + fb_data->lut_update_order[i] = 0; + + /* Signal completion if submit workqueue needs a LUT */ + if (fb_data->waiting_for_lut) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + /* Signal completion if anyone waiting on this LUT */ + for (j = 0; j < EPDC_MAX_NUM_UPDATES; j++) { + if (fb_data->update_marker_array[j].lut_num != i) + continue; + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker %d\n", + fb_data->update_marker_array[j].update_marker); + complete(&fb_data->update_marker_array[j].update_completion); + /* Ensure this doesn't get signaled again inadvertently */ + fb_data->update_marker_array[j].lut_num = INVALID_LUT; + } + } + + /* Check to see if all updates have completed */ + if (is_free_list_full(fb_data) && + (fb_data->cur_update == NULL) && + !epdc_any_luts_active()) { + + 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 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) + complete(&fb_data->updates_done); + } + + /* Is Working Buffer busy? */ + if (epdc_is_working_buffer_busy()) { + /* Can't submit another update until WB is done */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } + + /* + * Were we waiting on working buffer? + * If so, update queues and check for collisions + */ + if (fb_data->cur_update != NULL) { + dev_dbg(fb_data->dev, "\nWorking buffer completed\n"); + + /* Signal completion if submit workqueue was waiting on WB */ + if (fb_data->waiting_for_wb) { + complete(&fb_data->update_res_free); + fb_data->waiting_for_lut = false; + } + + /* Was there a collision? */ + if (epdc_is_collision()) { + /* Check list of colliding LUTs, and add to our collision mask */ + fb_data->cur_update->collision_mask = + epdc_get_colliding_luts(); + + /* Clear collisions that just completed */ + fb_data->cur_update->collision_mask &= ~luts_completed_mask; + + dev_dbg(fb_data->dev, "\nCollision mask = 0x%x\n", + epdc_get_colliding_luts()); + + /* + * 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) { + if (!(temp_mask & 0x1)) + continue; + + if (fb_data->lut_update_order[lut] >= + fb_data->cur_update->update_order) { + dev_dbg(fb_data->dev, "Ignoring collision with newer update.\n"); + ignore_collision = true; + break; + } + } + + if (ignore_collision) { + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list->list); + } else { + /* + * If update has a marker, clear the LUT, since we + * don't want to signal that it is complete. + */ + if (fb_data->cur_update->upd_marker_data) + if (fb_data->cur_update->upd_marker_data->update_marker != 0) + fb_data->cur_update->upd_marker_data->lut_num = INVALID_LUT; + + /* Move to collision list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_collision_list->list); + } + } else { + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list->list); + } + /* 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) { + /* Queued update scheme processing */ + + /* Schedule task to submit collision and pending update */ + if (!fb_data->powering_down) + queue_work(fb_data->epdc_submit_workqueue, + &fb_data->epdc_submit_work); + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + return IRQ_HANDLED; + } + + /* Snapshot update scheme processing */ + + /* Check to see if any LUTs are free */ + if (!epdc_any_luts_available()) { + dev_dbg(fb_data->dev, "No luts available.\n"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } + + /* + * 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, list) { + + if (collision_update->collision_mask != 0) + 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) { + /* Is update list empty? */ + if (list_empty(&fb_data->upd_buf_queue->list)) { + dev_dbg(fb_data->dev, "No pending updates.\n"); + + /* No updates pending, so we are done */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } 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->list.next, + struct update_data_list, list); + list_del_init(&fb_data->cur_update->list); + } + } + + /* LUTs are available, so we get one here */ + fb_data->cur_update->lut_num = epdc_get_next_lut(); + + /* Associate LUT with update marker */ + if ((fb_data->cur_update->upd_marker_data) + && (fb_data->cur_update->upd_marker_data->update_marker != 0)) + fb_data->cur_update->upd_marker_data->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_order; + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + next_upd_region = &fb_data->cur_update->upd_data.update_region; + if (fb_data->cur_update->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, fb_data->cur_update->upd_data.temp); + epdc_set_temp(temp_index); + } else + epdc_set_temp(fb_data->temp_index); + epdc_set_update_addr(fb_data->cur_update->phys_addr + fb_data->cur_update->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); + epdc_submit_update(fb_data->cur_update->lut_num, + fb_data->cur_update->upd_data.waveform_mode, + fb_data->cur_update->upd_data.update_mode, false, 0); + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + return IRQ_HANDLED; +} + +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; + + upd_buf_ptr = (u32 *)fb_data->info.screen_base; + + epdc_working_buf_intr(true); + epdc_lut_complete_intr(0, true); + fb_data->in_init = true; + + /* 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; + } + + /* 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); + epdc_submit_update(0, fb_data->wv_modes.mode_init, UPDATE_MODE_FULL, 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"); + return; + } + msleep(100); + } + + dev_err(fb_data->dev, "Mode0 init failed!\n"); + + 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 fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; + u32 xres, yres; + + if (fw == NULL) { + /* If default FW file load failed, we give up */ + if (fb_data->fw_default_load) + 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; + 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); + + return; + } + + wv_file = (struct mxcfb_waveform_data_file *)fw->data; + + /* 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, GFP_KERNEL); + + for (i = 0; i < fb_data->trt_entries; i++) + dev_dbg(fb_data->dev, "trt entry #%d = 0x%x\n", i, *((u8 *)&wv_file->data + i)); + + /* Copy TRT data */ + memcpy(fb_data->temp_range_bounds, &wv_file->data, fb_data->trt_entries); + + /* 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; + + /* 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"); + return; + } + + memcpy(fb_data->waveform_buffer_virt, (u8 *)(fw->data) + wv_data_offs, + fb_data->waveform_buffer_size); + + release_firmware(fw); + + /* Enable clocks to access EPDC regs */ + clk_enable(fb_data->epdc_clk_axi); + + /* Enable pix clk for EPDC */ + clk_enable(fb_data->epdc_clk_pix); + clk_set_rate(fb_data->epdc_clk_pix, fb_data->cur_mode->vmode->pixclock); + + 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; + + /* 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 = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, &fb_data->info); + + /* Block on initial update */ + ret = mxc_epdc_fb_wait_update_complete(update.update_marker, + &fb_data->info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for update complete failed. Error = 0x%x", ret); +} + +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; + + 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); + + 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, buf_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; +#ifdef CONFIG_FRAMEBUFFER_CONSOLE + struct mxcfb_update_data update; +#endif + + 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; + } + + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto out_fbdata; + } + + 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 + panel_str = opt; + } + + fb_data->dev = &pdev->dev; + + if (!fb_data->default_bpp) + fb_data->default_bpp = 16; + + /* Set default (first defined mode) before searching for a match */ + fb_data->cur_mode = &fb_data->pdata->epdc_mode[0]; + + if (panel_str) + for (i = 0; i < fb_data->pdata->num_modes; i++) + 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); + + /* + * 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); + buf_size = PAGE_ALIGN(xres_virt * yres_virt * fb_data->default_bpp/8); + + /* + * 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); + buf_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot + * fb_data->default_bpp/8); + buf_size = (buf_size > buf_size_rot) ? buf_size : buf_size_rot; + + /* 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 */ + 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; + 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: + /* + * 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; + 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_SNAPSHOT; + + /* Initialize our internal copy of the screeninfo */ + fb_data->epdc_fb_var = *var_info; + fb_data->fb_offset = 0; + + /* Allocate head objects for our lists */ + fb_data->upd_buf_queue = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + fb_data->upd_buf_collision_list = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + fb_data->upd_buf_free_list = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + if ((fb_data->upd_buf_queue == NULL) || (fb_data->upd_buf_free_list == NULL) + || (fb_data->upd_buf_collision_list == NULL)) { + ret = -ENOMEM; + goto out_dma_fb; + } + + /* + * Initialize lists for update requests, update collisions, + * and available update (PxP output) buffers + */ + INIT_LIST_HEAD(&fb_data->upd_buf_queue->list); + INIT_LIST_HEAD(&fb_data->upd_buf_free_list->list); + INIT_LIST_HEAD(&fb_data->upd_buf_collision_list->list); + + /* Allocate update buffers and add them to the list */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL); + if (upd_list == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Clear update data structure */ + memset(&upd_list->upd_data, 0, + sizeof(struct mxcfb_update_data)); + + /* + * Each update buffer is 1 byte per pixel, and can + * be as big as the full-screen frame buffer + */ + upd_list->size = info->var.xres * info->var.yres; + + /* Allocate memory for PxP output buffer */ + upd_list->virt_addr = + dma_alloc_coherent(fb_data->info.device, upd_list->size, + &upd_list->phys_addr, GFP_DMA); + if (upd_list->virt_addr == NULL) { + kfree(upd_list); + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Add newly allocated buffer to free list */ + list_add(&upd_list->list, &fb_data->upd_buf_free_list->list); + + dev_dbg(fb_data->info.device, "allocated %d bytes @ 0x%08X\n", + upd_list->size, upd_list->phys_addr); + } + + fb_data->working_buffer_size = vmode->yres * vmode->xres * 2; + /* Allocate memory for EPDC working buffer */ + fb_data->working_buffer_virt = + dma_alloc_coherent(&pdev->dev, fb_data->working_buffer_size, + &fb_data->working_buffer_phys, GFP_DMA); + if (fb_data->working_buffer_virt == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for working buf!\n"); + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Initialize EPDC pins */ + if (fb_data->pdata->get_pins) + fb_data->pdata->get_pins(); + + 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_upd_buffers; + } + 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_upd_buffers; + } + + fb_data->in_init = false; + + fb_data->hw_ready = 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 = 3; + fb_data->wv_modes.mode_gc8 = 2; + fb_data->wv_modes.mode_gc16 = 2; + fb_data->wv_modes.mode_gc32 = 2; + + /* Initialize markers */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + fb_data->update_marker_array[i].update_marker = 0; + fb_data->update_marker_array[i].lut_num = INVALID_LUT; + } + + /* Initialize all LUTs to inactive */ + for (i = 0; i < EPDC_NUM_LUTS; i++) + fb_data->lut_update_order[i] = 0; + + /* 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; + } + 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; + } + + INIT_DELAYED_WORK(&fb_data->epdc_done_work, epdc_done_work_func); + fb_data->epdc_submit_workqueue = create_rt_workqueue("submit"); + INIT_WORK(&fb_data->epdc_submit_work, epdc_submit_work_func); + + info->fbdefio = &mxc_epdc_fb_defio; +#ifdef CONFIG_FB_MXC_EINK_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 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_irq; + } + + 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; + + spin_lock_init(&fb_data->queue_lock); + + mutex_init(&fb_data->pxp_mutex); + + mutex_init(&fb_data->power_mutex); + + /* PxP DMA interface */ + dmaengine_get(); + + /* + * 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; + + /* + * 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_GREY; + + /* + * 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_idle = false; + fb_data->blank = FB_BLANK_UNBLANK; + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + fb_data->pwrdown_delay = 0; + + /* Register FB */ + ret = register_framebuffer(info); + if (ret) { + dev_err(&pdev->dev, + "register_framebuffer failed with error %d\n", ret); + goto out_dmaengine; + } + + g_fb_data = fb_data; + +#ifdef 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 + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE + /* If FB console included, update display to show logo */ + update.update_region.left = 0; + update.update_region.width = info->var.xres; + update.update_region.top = 0; + update.update_region.height = info->var.yres; + update.update_mode = UPDATE_MODE_PARTIAL; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.flags = 0; + + mxc_epdc_fb_send_update(&update, info); + + ret = mxc_epdc_fb_wait_update_complete(update.update_marker, info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for update complete failed. Error = 0x%x", ret); +#endif + + goto out; + +out_dmaengine: + dmaengine_put(); +out_irq: + free_irq(fb_data->epdc_irq, fb_data); +out_dma_work_buf: + dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, fb_data->working_buffer_phys); + if (fb_data->pdata->put_pins) + fb_data->pdata->put_pins(); +out_upd_buffers: + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list->list, list) { + list_del(&plist->list); + dma_free_writecombine(&pdev->dev, plist->size, plist->virt_addr, + plist->phys_addr); + kfree(plist); + } +out_dma_fb: + 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: + 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); + + mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info); + + flush_workqueue(fb_data->epdc_submit_workqueue); + destroy_workqueue(fb_data->epdc_submit_workqueue); + + regulator_put(fb_data->display_regulator); + regulator_put(fb_data->vcom_regulator); + + unregister_framebuffer(&fb_data->info); + free_irq(fb_data->epdc_irq, fb_data); + + dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, + fb_data->working_buffer_phys); + 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); + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list->list, list) { + list_del(&plist->list); + dma_free_writecombine(&pdev->dev, plist->size, plist->virt_addr, + plist->phys_addr); + 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); + + dmaengine_put(); + + 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 +static int mxc_epdc_fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxc_epdc_fb_data *data = platform_get_drvdata(pdev); + int ret; + + ret = mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &data->info); + if (ret) + goto out; + +out: + return ret; +} + +static int mxc_epdc_fb_resume(struct platform_device *pdev) +{ + struct mxc_epdc_fb_data *data = platform_get_drvdata(pdev); + + mxc_epdc_fb_blank(FB_BLANK_UNBLANK, &data->info); + return 0; +} +#else +#define mxc_epdc_fb_suspend NULL +#define mxc_epdc_fb_resume 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, + .driver = { + .name = "mxc_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; + + /* This call will signal wait_for_completion_timeout() in send_buffer_to_pxp */ + complete(&fb_data->pxp_tx_cmpl); +} + +/* 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, NULL, NULL); + if (!chan) { + dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n"); + return -EBUSY; + } + + dev_dbg(fb_data->dev, "Successfully received channel.\n"); + + 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, + int x_start_offs) +{ + 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; + + 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) { + /* + * 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); + + dev_dbg(fb_data->dev, "sg[0] = 0x%x, sg[1] = 0x%x\n", + sg_dma_address(&sg[0]), sg_dma_address(&sg[1])); + + 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 + x_start_offs; + 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) + proc_data->rotate = 0; + + pxp_conf->out_param.width = update_region->width; + pxp_conf->out_param.height = update_region->height; + + 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); + dev_dbg(fb_data->info.device, "%d: Submit %p #%d\n", __LINE__, txd, + cookie); + 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(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; + } + + *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 int __init mxc_epdc_fb_init(void) +{ + return platform_driver_register(&mxc_epdc_fb_driver); +} +late_initcall(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 000000000000..1b9930a214c0 --- /dev/null +++ b/drivers/video/mxc/mxc_ipuv3_fb.c @@ -0,0 +1,2074 @@ +/* + * 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 <linux/fsl_devices.h> +#include <asm/mach-types.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 { + char *fb_mode_str; + int default_bpp; + int cur_blank; + int next_blank; + ipu_channel_t ipu_ch; + int ipu_di; + u32 ipu_di_pix_fmt; + bool ipu_int_clk; + bool overlay; + bool alpha_chan_en; + 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_alp_ch_irq; + uint32_t cur_ipu_buf; + uint32_t cur_ipu_alpha_buf; + + u32 pseudo_palette[16]; + + bool wait4vsync; + uint32_t waitcnt; + struct semaphore flip_sem; + struct semaphore alpha_flip_sem; + struct completion vsync_complete; +}; + +struct mxcfb_mode { + int dev_mode; + int num_modes; + struct fb_videomode *mode; +}; + +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; +LIST_HEAD(fb_alloc_list); +static struct fb_info *mxcfb_info[3]; +static __initdata struct mxcfb_mode mxc_disp_mode[MXCFB_PORT_NUM]; +static __initdata int (*mxcfb_pre_setup[MXCFB_PORT_NUM])(struct fb_info *info); + +/* + * register pre-setup callback for some display + * driver which need prepare clk etc. + */ +void mxcfb_register_presetup(int disp_port, + int (*pre_setup)(struct fb_info *info)) +{ + if (pre_setup) + mxcfb_pre_setup[disp_port] = pre_setup; +} +EXPORT_SYMBOL(mxcfb_register_presetup); + +/* + * mode register from each display driver before + * primary fb setting. + */ +void mxcfb_register_mode(int disp_port, + const struct fb_videomode *modedb, + int num_modes, int dev_mode) +{ + struct fb_videomode *mode; + int mode_sum; + + if (disp_port > MXCFB_PORT_NUM) + return; + + /* + * if there is new DDC device, overwrite old modes. + * if there is old DDC device while new device is not DDC, + * just keep old DDC modes. + */ + if (dev_mode & MXC_DISP_DDC_DEV) { + if (mxc_disp_mode[disp_port].num_modes) { + kfree(mxc_disp_mode[disp_port].mode); + mxc_disp_mode[disp_port].num_modes = 0; + } + } else if (mxc_disp_mode[disp_port].dev_mode & MXC_DISP_DDC_DEV) + return; + + mode_sum = mxc_disp_mode[disp_port].num_modes + num_modes; + mode = kzalloc(mode_sum * sizeof(struct fb_videomode), GFP_KERNEL); + + if (mxc_disp_mode[disp_port].num_modes) + memcpy(mode, mxc_disp_mode[disp_port].mode, + mxc_disp_mode[disp_port].num_modes + * sizeof(struct fb_videomode)); + if (modedb) + memcpy(mode + mxc_disp_mode[disp_port].num_modes, + modedb, num_modes * sizeof(struct fb_videomode)); + + if (mxc_disp_mode[disp_port].num_modes) + kfree(mxc_disp_mode[disp_port].mode); + + mxc_disp_mode[disp_port].mode = mode; + mxc_disp_mode[disp_port].num_modes += num_modes; + mxc_disp_mode[disp_port].dev_mode = dev_mode; + + return; +} +EXPORT_SYMBOL(mxcfb_register_mode); + +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 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 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); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); +static int mxcfb_option_setup(struct fb_info *info, char *options); + +/* + * 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 = 0; + 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)); + params.mem_dp_bg_sync.di = mxc_fbi->ipu_di; + + /* + * Assuming interlaced means yuv output, below setting also + * valid for mem_dc_sync. FG should have the same vmode as BG. + */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *mxc_fbi_tmp; + int i; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi_tmp = (struct mxcfb_info *) + (registered_fb[i]->par); + if (mxc_fbi_tmp->ipu_ch == MEM_BG_SYNC) { + fbi->var.vmode = + registered_fb[i]->var.vmode; + mxc_fbi->ipu_di_pix_fmt = + mxc_fbi_tmp->ipu_di_pix_fmt; + break; + } + } + } + if (mxc_fbi->ipu_ch == MEM_DC_SYNC) { + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + params.mem_dc_sync.interlaced = true; + params.mem_dc_sync.out_pixel_fmt = + IPU_PIX_FMT_YUV444; + } else { + if (mxc_fbi->ipu_di_pix_fmt) + params.mem_dc_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + else + params.mem_dc_sync.out_pixel_fmt = IPU_PIX_FMT_RGB666; + } + params.mem_dc_sync.in_pixel_fmt = bpp_to_pixfmt(fbi); + } else { + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + params.mem_dp_bg_sync.interlaced = true; + params.mem_dp_bg_sync.out_pixel_fmt = + IPU_PIX_FMT_YUV444; + } else { + if (mxc_fbi->ipu_di_pix_fmt) + params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + else + params.mem_dp_bg_sync.out_pixel_fmt = IPU_PIX_FMT_RGB666; + } + params.mem_dp_bg_sync.in_pixel_fmt = bpp_to_pixfmt(fbi); + if (mxc_fbi->alpha_chan_en) + params.mem_dp_bg_sync.alpha_chan_en = true; + } + ipu_init_channel(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; + + switch (bpp_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: + fb_stride = fbi->var.xres_virtual; + break; + default: + fb_stride = fbi->fix.line_length; + } + + mxc_fbi->cur_ipu_buf = 1; + sema_init(&mxc_fbi->flip_sem, 1); + if (mxc_fbi->alpha_chan_en) { + mxc_fbi->cur_ipu_alpha_buf = 1; + sema_init(&mxc_fbi->alpha_flip_sem, 1); + } + fbi->var.xoffset = 0; + + base = (fbi->var.yoffset * fbi->var.xres_virtual + fbi->var.xoffset); + base = (fbi->var.bits_per_pixel) * base / 8; + base += fbi->fix.smem_start; + + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi), + fbi->var.xres, fbi->var.yres, + fb_stride, + IPU_ROTATE_NONE, + base, + base, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + } + + if (mxc_fbi->alpha_chan_en) { + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + IPU_PIX_FMT_GENERIC, + fbi->var.xres, fbi->var.yres, + fbi->var.xres, + IPU_ROTATE_NONE, + mxc_fbi->alpha_phy_addr1, + mxc_fbi->alpha_phy_addr0, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + } + + return retval; +} + +/* + * 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; + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + 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 (!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->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; + + _setup_disp_channel1(fbi); + + 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 = IPU_PIX_FMT_YUV444; + } else { + if (mxc_fbi->ipu_di_pix_fmt) + out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + else + out_pixel_fmt = IPU_PIX_FMT_RGB666; + } + 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_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_ch, 0, 0); + } + + retval = _setup_disp_channel2(fbi); + if (retval) + return retval; + + ipu_enable_channel(mxc_fbi->ipu_ch); + + return retval; +} + +static int _swap_channels(struct fb_info *fbi, + struct fb_info *fbi_to, bool both_on) +{ + int retval, tmp; + ipu_channel_t old_ch; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi->par; + struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + if (both_on) { + ipu_disable_channel(mxc_fbi_to->ipu_ch, true); + ipu_uninit_channel(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; + + _setup_disp_channel1(fbi); + retval = _setup_disp_channel2(fbi); + if (retval) + return retval; + + /* switch between dp and dc, disable old idmac, enable new idmac */ + retval = ipu_swap_channel(old_ch, mxc_fbi_from->ipu_ch); + ipu_uninit_channel(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_ch); + } + + return retval; +} + +static int swap_channels(struct fb_info *fbi) +{ + int i; + int swap_mode; + ipu_channel_t ch_to; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi->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; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi_to = + (struct mxcfb_info *)mxcfb_info[i]->par; + if (mxc_fbi_to->ipu_ch == ch_to) { + fbi_to = mxcfb_info[i]; + break; + } + } + if (fbi_to == NULL) + return -1; + + ipu_clear_irq(mxc_fbi_from->ipu_ch_irq); + ipu_clear_irq(mxc_fbi_to->ipu_ch_irq); + ipu_free_irq(mxc_fbi_from->ipu_ch_irq, fbi); + ipu_free_irq(mxc_fbi_to->ipu_ch_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; + } + + /* tvout di-1: for DC use UYVY, for DP use RGB */ + if (mxc_fbi_from->ipu_di == 1 && ch_to == MEM_DC_SYNC) { + fbi->var.bits_per_pixel = 16; + fbi->var.nonstd = IPU_PIX_FMT_UYVY; + } else if (mxc_fbi_from->ipu_di == 1 && ch_to == MEM_BG_SYNC) { + fbi->var.nonstd = 0; + } else if (mxc_fbi_from->ipu_di == 0 && ch_to == MEM_DC_SYNC) { + fbi_to->var.nonstd = 0; + } else if (mxc_fbi_from->ipu_di == 0 && ch_to == MEM_BG_SYNC) { + fbi->var.bits_per_pixel = 16; + fbi->var.nonstd = IPU_PIX_FMT_UYVY; + } + + switch (swap_mode) { + case BOTH_ON: + /* disable target->switch src->enable target */ + _swap_channels(fbi, fbi_to, true); + break; + case SRC_ON: + /* just switch src */ + _swap_channels(fbi, fbi_to, false); + break; + case TGT_ON: + /* just switch target */ + _swap_channels(fbi_to, fbi, 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; + break; + default: + break; + } + + if (ipu_request_irq(mxc_fbi_from->ipu_ch_irq, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering irq %d\n", + mxc_fbi_from->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_from->ipu_ch_irq); + if (ipu_request_irq(mxc_fbi_to->ipu_ch_irq, mxcfb_irq_handler, 0, + 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_ch_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; + + /* fg should not bigger than bg */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct fb_info *fbi_tmp; + struct mxcfb_info *mxc_fbi_tmp; + int i, bg_xres, bg_yres; + int16_t pos_x, pos_y; + + bg_xres = var->xres; + bg_yres = var->yres; + + for (i = 0; i < num_registered_fb; i++) { + fbi_tmp = registered_fb[i]; + mxc_fbi_tmp = (struct mxcfb_info *) + (fbi_tmp->par); + if (mxc_fbi_tmp->ipu_ch == MEM_BG_SYNC) { + bg_xres = fbi_tmp->var.xres; + bg_yres = fbi_tmp->var.yres; + break; + } + } + + ipu_disp_get_window_pos(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->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 != 12) && + (var->bits_per_pixel != 8)) + var->bits_per_pixel = 16; + + 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; + } + + 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; +} + +/* + * 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_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; + int i; + char *video_plane_idstr = ""; + + if (copy_from_user(&la, (void *)arg, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu_ch, + !(bool)la.enable, 0)) { + retval = -EINVAL; + break; + } + + if (la.enable && !la.alpha_in_pixel) { + mxc_fbi->alpha_chan_en = true; + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + video_plane_idstr = "DISP3 BG"; + else if (mxc_fbi->ipu_ch == MEM_BG_SYNC) + video_plane_idstr = "DISP3 FG"; + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, video_plane_idstr) == 0) { + ((struct mxcfb_info *)(registered_fb[i]->par))->alpha_chan_en = false; + break; + } + } + } else + mxc_fbi->alpha_chan_en = false; + + 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; + + down(&mxc_fbi->alpha_flip_sem); + + mxc_fbi->cur_ipu_alpha_buf = + !mxc_fbi->cur_ipu_alpha_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi-> + cur_ipu_alpha_buf, + base) == 0) { + ipu_select_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + ipu_clear_irq(ipu_alp_ch_irq); + ipu_enable_irq(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_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_ch, + gamma.enable, + gamma.constk, + gamma.slopek); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *bg_mxcfbi = NULL; + int i; + for (i = 0; i < num_registered_fb; i++) { + bg_mxcfbi = + ((struct mxcfb_info *)(registered_fb[i]->par)); + + if (bg_mxcfbi->ipu_ch == MEM_BG_SYNC) + 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_ch_irq); + mxc_fbi->wait4vsync = 1; + ipu_enable_irq(mxc_fbi->ipu_ch_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); + mxc_fbi->wait4vsync = 0; + 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_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; + struct fb_info *bg_fbi = NULL; + struct mxcfb_info *bg_mxcfbi = NULL; + int i; + + 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; + } + + for (i = 0; i < num_registered_fb; i++) { + bg_mxcfbi = + ((struct mxcfb_info *)(registered_fb[i]->par)); + + if (bg_mxcfbi->ipu_ch == MEM_BG_SYNC) { + bg_fbi = registered_fb[i]; + break; + } + } + + if (bg_fbi == NULL) { + dev_err(fbi->device, "Cannot find the " + "background framebuffer\n"); + retval = -ENOENT; + break; + } + + 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_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; + } + 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; + + 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: + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_par(info); + break; + } + mxc_fbi->cur_blank = blank; + 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, + *mxc_graphic_fbi = NULL; + u_int y_bottom; + unsigned long base, active_alpha_phy_addr = 0; + bool loc_alpha_en = false; + int i = 0; + + if (info->var.yoffset == var->yoffset) + return 0; /* No change, do nothing */ + + /* no pan display during fb blank */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *bg_mxcfbi = NULL; + int j; + for (j = 0; j < num_registered_fb; j++) { + bg_mxcfbi = + ((struct mxcfb_info *)(registered_fb[j]->par)); + + if (bg_mxcfbi->ipu_ch == MEM_BG_SYNC) + break; + } + 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 (!(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) * base / 8; + base += info->fix.smem_start; + + /* 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 *idstr = registered_fb[i]->fix.id; + if ((strcmp(idstr, "DISP3 BG") == 0 || + strcmp(idstr, "DISP3 FG") == 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_buf ? + mxc_graphic_fbi->alpha_phy_addr1 : + mxc_graphic_fbi->alpha_phy_addr0; + dev_dbg(info->device, "Updating SDC graphic " + "buf %d address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, + active_alpha_phy_addr); + break; + } + } + } + + down(&mxc_fbi->flip_sem); + + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_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_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_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_buf, + active_alpha_phy_addr) == 0) { + ipu_select_buffer(mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_buf); + } + + 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); + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(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; + + if (mxc_fbi->wait4vsync) { + complete(&mxc_fbi->vsync_complete); + ipu_disable_irq(irq); + mxc_fbi->wait4vsync = 0; + } else { + if (!ipu_check_buffer_ready(mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, mxc_fbi->cur_ipu_buf) + || (mxc_fbi->waitcnt > 1)) { + /* + * This code wait for EOF irq to make sure current + * buffer showed. + * + * Buffer ready will be clear after this buffer + * begin to show. If it keep 1, it represents this + * irq come from previous buffer. If so, wait for + * EOF irq again. + * + * Normally, waitcnt will not > 1, if so, something + * is wrong, then clear it manually. + */ + if (mxc_fbi->waitcnt > 1) + ipu_clear_buffer_ready(mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + up(&mxc_fbi->flip_sem); + ipu_disable_irq(irq); + mxc_fbi->waitcnt = 0; + } else + mxc_fbi->waitcnt++; + } + 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; + + up(&mxc_fbi->alpha_flip_sem); + ipu_disable_irq(irq); + return IRQ_HANDLED; +} + +/* + * 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; + int saved_blank; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + acquire_console_sem(); + fb_set_suspend(fbi, 1); + saved_blank = mxc_fbi->cur_blank; + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxc_fbi->next_blank = saved_blank; + release_console_sem(); + + 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; + + acquire_console_sem(); + mxcfb_blank(mxc_fbi->next_blank, fbi); + fb_set_suspend(fbi, 0); + release_console_sem(); + + 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); + 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; + + acquire_console_sem(); + /* 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))) { + int i; + + for (i = 0; i < num_registered_fb; i++) { + fg_mxcfbi = + (struct mxcfb_info *)mxcfb_info[i]->par; + if (fg_mxcfbi->ipu_ch == MEM_FG_SYNC) + break; + else + fg_mxcfbi = NULL; + } + if (!fg_mxcfbi || + fg_mxcfbi->cur_blank == FB_BLANK_UNBLANK) { + dev_err(dev, + "Can not switch while fb2(fb-fg) is on.\n"); + release_console_sem(); + return count; + } + + if (swap_channels(info) < 0) + dev_err(dev, "Swap display channel failed.\n"); + } + + release_console_sem(); + return count; +} +DEVICE_ATTR(fsl_disp_property, 644, show_disp_chan, swap_disp_chan); + +static int mxcfb_setup(struct fb_info *fbi, struct platform_device *pdev) +{ + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct mxc_fb_platform_data *plat_data = pdev->dev.platform_data; + int ret = 0; + + /* Need dummy values until real panel is configured */ + fbi->var.xres = 240; + fbi->var.yres = 320; + + if (!mxcfbi->default_bpp) + mxcfbi->default_bpp = 16; + + if (plat_data && !mxcfbi->ipu_di_pix_fmt) + mxcfbi->ipu_di_pix_fmt = plat_data->interface_pix_fmt; + + if (!mxcfbi->fb_mode_str && plat_data && plat_data->mode_str) + mxcfbi->fb_mode_str = plat_data->mode_str; + + if (mxcfbi->fb_mode_str) { + if (mxcfbi->ipu_di >= 0) { + const struct fb_videomode *mode; + struct fb_videomode m; + int num, found = 0; + + dev_dbg(fbi->device, "Config display port %d\n", + mxcfbi->ipu_di); + + INIT_LIST_HEAD(&fbi->modelist); + + if (mxc_disp_mode[mxcfbi->ipu_di].num_modes) { + mode = mxc_disp_mode[mxcfbi->ipu_di].mode; + num = mxc_disp_mode[mxcfbi->ipu_di].num_modes; + fb_videomode_to_modelist(mode, num, &fbi->modelist); + } + + if ((mxc_disp_mode[mxcfbi->ipu_di].dev_mode + & MXC_DISP_DDC_DEV) && + !list_empty(&fbi->modelist)) { + dev_dbg(fbi->device, + "Look for video mode %s in ddc modelist\n", + mxcfbi->fb_mode_str); + /* + * For DDC mode, try to get compatible mode first. + * If get one, try to find nearest mode, otherwise, + * use first mode provide by DDC. + */ + ret = fb_find_mode(&fbi->var, fbi, + mxcfbi->fb_mode_str, NULL, 0, + NULL, mxcfbi->default_bpp); + if (ret) { + fb_var_to_videomode(&m, &fbi->var); + mode = fb_find_nearest_mode(&m, + &fbi->modelist); + fb_videomode_to_var(&fbi->var, mode); + } else { + struct list_head *pos, *head; + struct fb_modelist *modelist; + + head = &fbi->modelist; + list_for_each(pos, head) { + modelist = list_entry(pos, + struct fb_modelist, list); + m = modelist->mode; + if (m.flag & FB_MODE_IS_FIRST) + break; + } + /* if no first mode, use last one */ + mode = &m; + fb_videomode_to_var(&fbi->var, mode); + } + found = 1; + } else if (!list_empty(&fbi->modelist)) { + dev_dbg(fbi->device, + "Look for video mode %s in spec modelist\n", + mxcfbi->fb_mode_str); + /* + * For specific mode, try to get specified mode + * from fbi modelist. + */ + ret = fb_find_mode(&fbi->var, fbi, + mxcfbi->fb_mode_str, mode, num, + NULL, mxcfbi->default_bpp); + if (ret == 1) + found = 1; + + } + /* + * if no DDC mode and spec mode found, + * try plat_data mode. + */ + if (!found) { + dev_dbg(fbi->device, + "Look for video mode %s in plat modelist\n", + mxcfbi->fb_mode_str); + if (plat_data && plat_data->mode + && plat_data->num_modes) + ret = fb_find_mode(&fbi->var, fbi, + mxcfbi->fb_mode_str, + plat_data->mode, + plat_data->num_modes, + NULL, + mxcfbi->default_bpp); + else + ret = fb_find_mode(&fbi->var, fbi, + mxcfbi->fb_mode_str, NULL, 0, + NULL, mxcfbi->default_bpp); + if (ret) + found = 1; + } + + if (!found) { + dev_err(fbi->device, + "Not found any valid video mode"); + ret = -EINVAL; + goto done; + } + + /*added found mode to fbi modelist*/ + fb_var_to_videomode(&m, &fbi->var); + fb_add_videomode(&m, &fbi->modelist); + } + } + + mxcfb_check_var(&fbi->var, fbi); + + /* Default Y virtual size is 3x panel size */ + fbi->var.yres_virtual = fbi->var.yres * 3; + + mxcfb_set_fix(fbi); + + /* setup display */ + if (mxcfbi->ipu_di >= 0) + if (mxcfb_pre_setup[mxcfbi->ipu_di]) + (mxcfb_pre_setup[mxcfbi->ipu_di])(fbi); + + fbi->var.activate |= FB_ACTIVATE_FORCE; + acquire_console_sem(); + fbi->flags |= FBINFO_MISC_USEREVENT; + ret = fb_set_var(fbi, &fbi->var); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); +done: + return ret; +} + +/*! + * 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 *mxcfbi; + struct resource *res; + char *options; + char name[] = "mxcdi0fb"; + 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; + + name[5] += pdev->id; + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto err1; + } + + if (options) + mxcfb_option_setup(fbi, options); + + if (!g_dp_in_use) { + mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF; + mxcfbi->ipu_ch = MEM_BG_SYNC; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK; + } else { + mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF; + mxcfbi->ipu_ch = MEM_DC_SYNC; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + } + + mxcfbi->ipu_di = pdev->id; + mxcfbi->ipu_alp_ch_irq = -1; + + if (pdev->id == 0) { + ipu_disp_set_global_alpha(mxcfbi->ipu_ch, true, 0x80); + ipu_disp_set_color_key(mxcfbi->ipu_ch, false, 0); + strcpy(fbi->fix.id, "DISP3 BG"); + + if (!g_dp_in_use) + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + g_dp_in_use = true; + } else if (pdev->id == 1) { + strcpy(fbi->fix.id, "DISP3 BG - DI1"); + + if (!g_dp_in_use) + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + g_dp_in_use = true; + } else if (pdev->id == 2) { /* Overlay */ + mxcfbi->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF; + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + mxcfbi->ipu_ch = MEM_FG_SYNC; + mxcfbi->ipu_di = -1; + mxcfbi->overlay = true; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + + strcpy(fbi->fix.id, "DISP3 FG"); + } + + mxcfb_info[pdev->id] = fbi; + + if (ipu_request_irq(mxcfbi->ipu_ch_irq, 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(mxcfbi->ipu_ch_irq); + + if (mxcfbi->ipu_alp_ch_irq != -1) + if (ipu_request_irq(mxcfbi->ipu_alp_ch_irq, + mxcfb_alpha_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering alpha irq " + "handler.\n"); + ret = -EBUSY; + goto err2; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res && 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); + } + + ret = mxcfb_setup(fbi, pdev); + if (ret < 0) + goto err3; + + ret = register_framebuffer(fbi); + if (ret < 0) + goto err3; + + 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\n", ret); + +#ifdef CONFIG_LOGO + fb_prepare_logo(fbi,0); + fb_show_logo(fbi, 0); +#endif + + return 0; +err3: + if (mxcfbi->ipu_alp_ch_irq != -1) + ipu_free_irq(mxcfbi->ipu_alp_ch_irq, fbi); +err2: + ipu_free_irq(mxcfbi->ipu_ch_irq, fbi); +err1: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); +err0: + 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; + + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + ipu_free_irq(mxc_fbi->ipu_ch_irq, fbi); + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + 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, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=mxcdi0fb:RGB24, 1024x768M-16@60,bpp=16,noaccel + */ +static int mxcfb_option_setup(struct fb_info *info, char *options) +{ + struct mxcfb_info *mxcfbi = info->par; + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "RGB24", 5)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_RGB24; + continue; + } + if (!strncmp(opt, "BGR24", 5)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_BGR24; + continue; + } + if (!strncmp(opt, "GBR24", 5)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_GBR24; + continue; + } + if (!strncmp(opt, "RGB565", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_RGB565; + continue; + } + if (!strncmp(opt, "RGB666", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_RGB666; + continue; + } + if (!strncmp(opt, "YUV444", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_YUV444; + continue; + } + if (!strncmp(opt, "LVDS666", 7)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_LVDS666; + continue; + } + if (!strncmp(opt, "YUYV16", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_YUYV; + continue; + } + if (!strncmp(opt, "UYVY16", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_UYVY; + continue; + } + if (!strncmp(opt, "YVYU16", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_YVYU; + continue; + } + if (!strncmp(opt, "VYUY16", 6)) { + mxcfbi->ipu_di_pix_fmt = IPU_PIX_FMT_VYUY; + continue; + } + if (!strncmp(opt, "int_clk", 7)) { + mxcfbi->ipu_int_clk = true; + continue; + } + if (!strncmp(opt, "bpp=", 4)) + mxcfbi->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + else + mxcfbi->fb_mode_str = 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) +{ + 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/mxcfb.c b/drivers/video/mxc/mxcfb.c new file mode 100644 index 000000000000..93ce7f664976 --- /dev/null +++ b/drivers/video/mxc/mxcfb.c @@ -0,0 +1,1372 @@ +/* + * Copyright 2004-2010 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); + 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; + + acquire_console_sem(); + fb_set_suspend(drv_data->fbi, 1); + fb_set_suspend(drv_data->fbi_ovl, 1); + release_console_sem(); + + 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); + } + + acquire_console_sem(); + fb_set_suspend(drv_data->fbi, 0); + fb_set_suspend(drv_data->fbi_ovl, 0); + release_console_sem(); + + 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 000000000000..a3f508450852 --- /dev/null +++ b/drivers/video/mxc/mxcfb_ch7026.c @@ -0,0 +1,370 @@ +/* + * Copyright 2009-2010 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; + + acquire_console_sem(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + fb_blank(info, FB_BLANK_UNBLANK); + info->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); +} + +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 000000000000..c69ee3433c68 --- /dev/null +++ b/drivers/video/mxc/mxcfb_claa_wvga.c @@ -0,0 +1,240 @@ +/* + * Copyright 2008-2010 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; + + acquire_console_sem(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); +} + +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 000000000000..2b9bbec8a7e4 --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson.c @@ -0,0 +1,1153 @@ +/* + * Copyright 2004-2010 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 000000000000..8b5ea1a945d4 --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson_vga.c @@ -0,0 +1,362 @@ +/* + * Copyright 2007-2010 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; + + acquire_console_sem(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); +} + +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_modedb.c b/drivers/video/mxc/mxcfb_modedb.c new file mode 100644 index 000000000000..2e73560c44b9 --- /dev/null +++ b/drivers/video/mxc/mxcfb_modedb.c @@ -0,0 +1,69 @@ +/* + * Copyright 2007-2010 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 000000000000..9b3e9fddafe7 --- /dev/null +++ b/drivers/video/mxc/mxcfb_seiko_wvga.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2010 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; + + acquire_console_sem(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); +} + +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_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 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 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/tve.c b/drivers/video/mxc/tve.c new file mode 100644 index 000000000000..2bb532f638cf --- /dev/null +++ b/drivers/video/mxc/tve.c @@ -0,0 +1,1289 @@ +/* + * 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> + +#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 IPU_DISP_PORT 1 + +static int enabled; /* enable power on or not */ +DEFINE_SPINLOCK(tve_lock); + +static struct fb_info *tve_fbi; +static bool g_enable_tve; +static bool g_enable_vga; + +struct tve_data { + struct platform_device *pdev; + int revision; + int cur_mode; + int output_mode; + int detect; + void *base; + int irq; + int blank; + struct clk *clk; + struct clk *di_clk; + struct regulator *dac_reg; + struct regulator *dig_reg; + struct delayed_work cd_work; +} tve; + +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 +}; + + +struct tve_reg_mapping *tve_regs; +struct tve_reg_fields_mapping *tve_reg_fields; + +/* For MX37 need modify some fields in tve_probe */ +static struct fb_videomode video_modes[] = { + { + /* 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 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,}, +}; + +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(void) +{ + dev_dbg(&tve.pdev->dev, "tve_com_conf_reg 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_com_conf_reg)); + dev_dbg(&tve.pdev->dev, "tve_cd_cont_reg 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_cd_cont_reg)); + dev_dbg(&tve.pdev->dev, "tve_int_cont_reg 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_int_cont_reg)); + dev_dbg(&tve.pdev->dev, "tve_tst_mode_reg 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_tst_mode_reg)); + dev_dbg(&tve.pdev->dev, "tve_tvdac_cont_reg0 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg)); + dev_dbg(&tve.pdev->dev, "tve_tvdac_cont_reg1 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg + 4)); + dev_dbg(&tve.pdev->dev, "tve_tvdac_cont_reg2 0x%x\n", + __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg + 8)); +} + +static int is_vga_enabled(void) +{ + u32 reg; + + if (tve.revision == 2) { + reg = __raw_readl(tve.base + tve_regs->tve_tst_mode_reg); + if (reg & TVEV2_DAC_TEST_MODE_MASK) + return 1; + else + return 0; + } + return 0; +} + +static int inline 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 int inline 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 *fmt) +{ + int mode; + + if (fb_mode_is_equal(fbi->mode, &video_modes[0])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_NTSC; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[1])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_PAL; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[2])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_720P60; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[3])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_720P30; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[4])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080I60; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[5])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080I50; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[6])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080P30; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[7])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080P25; + } else if (fb_mode_is_equal(fbi->mode, &video_modes[8])) { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_1080P24; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[0])) { + *fmt = IPU_PIX_FMT_GBR24; + mode = TVOUT_FMT_VGA_SVGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[1])) { + *fmt = IPU_PIX_FMT_GBR24; + mode = TVOUT_FMT_VGA_XGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[2])) { + *fmt = IPU_PIX_FMT_GBR24; + mode = TVOUT_FMT_VGA_SXGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[3])) { + *fmt = IPU_PIX_FMT_GBR24; + mode = TVOUT_FMT_VGA_WSXGA; + } else { + *fmt = IPU_PIX_FMT_YUV444; + mode = TVOUT_FMT_OFF; + } + return mode; +} + +static void tve_disable_vga_mode(void) +{ + if (tve.revision == 2) { + u32 reg; + /* disable test mode */ + reg = __raw_readl(tve.base + tve_regs->tve_tst_mode_reg); + reg = reg & ~TVEV2_DAC_TEST_MODE_MASK; + __raw_writel(reg, tve.base + tve_regs->tve_tst_mode_reg); + } +} + +static void tve_set_tvout_mode(int mode) +{ + u32 conf_reg; + + /* clear sync_ch and tvout_mode fields */ + conf_reg = __raw_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; + __raw_writel(conf_reg, tve.base + tve_regs->tve_com_conf_reg); +} + +static int _is_tvout_mode_hd_compatible(void) +{ + u32 conf_reg, mode; + + conf_reg = __raw_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(void) +{ + u32 reg; + + if (tve.revision == 2) { + /* set gain */ + reg = __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0; + __raw_writel(reg, tve.base + tve_regs->tve_tvdac_cont_reg); + reg = __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg + 4); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0; + __raw_writel(reg, tve.base + tve_regs->tve_tvdac_cont_reg + 4); + reg = __raw_readl(tve.base + tve_regs->tve_tvdac_cont_reg + 8); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0; + __raw_writel(reg, tve.base + tve_regs->tve_tvdac_cont_reg + 8); + + /* set tve_com_conf_reg */ + reg = __raw_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; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + + /* set test mode */ + reg = __raw_readl(tve.base + tve_regs->tve_tst_mode_reg); + reg = (reg & ~TVEV2_DAC_TEST_MODE_MASK) | 1; + __raw_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(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_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 (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_set_tvout_mode(YPBPR); + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_PAL_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to PAL video\n"); + } else if (mode == TVOUT_FMT_NTSC) { + tve_disable_vga_mode(); + tve_set_tvout_mode(YPBPR); + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_NTSC_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to NTSC video\n"); + } else if (mode == TVOUT_FMT_720P60) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P60_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 720P60 video\n"); + } else if (mode == TVOUT_FMT_720P30) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P30_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 720P30 video\n"); + } else if (mode == TVOUT_FMT_1080I60) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I60_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080I60 video\n"); + } else if (mode == TVOUT_FMT_1080I50) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I50_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080I50 video\n"); + } else if (mode == TVOUT_FMT_1080P30) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080P30 video\n"); + } else if (mode == TVOUT_FMT_1080P25) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P25_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080P25 video\n"); + } else if (mode == TVOUT_FMT_1080P24) { + tve_disable_vga_mode(); + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P24_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 1080P24 video\n"); + } else if (is_vga_mode(mode)) { + /* do not need cable detect */ + tve_setup_vga(); + pr_debug("TVE: change to VGA video\n"); + } else if (mode == TVOUT_FMT_OFF) { + __raw_writel(0x0, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to OFF video\n"); + } else { + pr_debug("TVE: no such video format.\n"); + } + + if (!enabled) + clk_disable(tve.clk); + + spin_unlock_irqrestore(&tve_lock, lock_flags); + return 0; +} + +/** + * tve_enable + * Enable the tve Power to begin TV encoder + */ +static void tve_enable(void) +{ + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&tve_lock, lock_flags); + if (!enabled) { + enabled = 1; + clk_enable(tve.clk); + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + __raw_writel(reg | TVE_IPU_CLK_ENABLE | TVE_ENABLE, + tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE power on.\n"); + } + + if (is_vga_enabled()) { + /* disable interrupt */ + pr_debug("TVE VGA disable cable detect.\n"); + __raw_writel(0xffffffff, tve.base + tve_regs->tve_stat_reg); + __raw_writel(0, tve.base + tve_regs->tve_int_cont_reg); + } else { + /* enable interrupt */ + pr_debug("TVE TVE enable cable detect.\n"); + __raw_writel(0xffffffff, tve.base + tve_regs->tve_stat_reg); + __raw_writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT, + tve.base + tve_regs->tve_int_cont_reg); + } + + spin_unlock_irqrestore(&tve_lock, lock_flags); + + tve_dump_regs(); +} + +/** + * tve_disable + * Disable the tve Power to stop TV encoder + */ +static void tve_disable(void) +{ + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&tve_lock, lock_flags); + if (enabled) { + enabled = 0; + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + __raw_writel(reg & ~TVE_ENABLE & ~TVE_IPU_CLK_ENABLE, + tve.base + tve_regs->tve_com_conf_reg); + clk_disable(tve.clk); + pr_debug("TVE power off.\n"); + } + spin_unlock_irqrestore(&tve_lock, lock_flags); +} + +static int tve_update_detect_status(void) +{ + 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; + + spin_lock_irqsave(&tve_lock, lock_flags); + + if (!enabled) { + pr_warning("Warning: update tve status while it disabled!\n"); + tve.detect = 0; + goto done; + } + + int_ctl = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + cd_cont_reg = __raw_readl(tve.base + tve_regs->tve_cd_cont_reg); + + if ((cd_cont_reg & 0x1) == 0) { + pr_warning("Warning: pls enable TVE CD first!\n"); + goto done; + } + + stat = __raw_readl(tve.base + tve_regs->tve_stat_reg); + while (((stat & CD_MON_END_INT) == 0) && (timeout > 0)) { + spin_unlock_irqrestore(&tve_lock, lock_flags); + msleep(2); + spin_lock_irqsave(&tve_lock, lock_flags); + timeout -= 2; + if (!enabled) { + pr_warning("Warning: update tve status while it disabled!\n"); + tve.detect = 0; + goto done; + } else + stat = __raw_readl(tve.base + tve_regs->tve_stat_reg); + } + if (((stat & CD_MON_END_INT) == 0) && (timeout <= 0)) { + pr_warning("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.output_mode); + + /* clear interrupt */ + __raw_writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve.base + tve_regs->tve_stat_reg); + + __raw_writel(int_ctl | CD_SM_INT | CD_LM_INT, + tve.base + tve_regs->tve_int_cont_reg); + + if (old_detect != tve.detect) + sysfs_notify(&tve.pdev->dev.kobj, NULL, "headphone"); + + dev_dbg(&tve.pdev->dev, "detect = %d mode = %d\n", + tve.detect, tve.output_mode); +done: + spin_unlock_irqrestore(&tve_lock, lock_flags); + return tve.detect; +} + +static void cd_work_func(struct work_struct *work) +{ + tve_update_detect_status(); +} +#if 0 +static int tve_man_detect(void) +{ + u32 cd_cont; + u32 int_cont; + + if (!enabled) + return -1; + + int_cont = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + __raw_writel(int_cont & + ~(tve_reg_fields->cd_sm_int | tve_reg_fields->cd_lm_int), + tve.base + tve_regs->tve_int_cont_reg); + + cd_cont = __raw_readl(tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(cd_cont | tve_reg_fields->cd_trig_mode, + tve.base + tve_regs->tve_cd_cont_reg); + + __raw_writel(tve_reg_fields->cd_sm_int | tve_reg_fields-> + cd_lm_int | tve_reg_fields-> + cd_mon_end_int | tve_reg_fields->cd_man_trig, + tve.base + tve_regs->tve_stat_reg); + + while ((__raw_readl(tve.base + tve_regs->tve_stat_reg) + & tve_reg_fields->cd_mon_end_int) == 0) + msleep(5); + + tve_update_detect_status(); + + __raw_writel(cd_cont, tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(int_cont, tve.base + tve_regs->tve_int_cont_reg); + + return tve.detect; +} +#endif + +static irqreturn_t tve_detect_handler(int irq, void *data) +{ + u32 int_ctl = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + + /* disable INT first */ + int_ctl &= ~(CD_SM_INT | CD_LM_INT | CD_MON_END_INT); + __raw_writel(int_ctl, tve.base + tve_regs->tve_int_cont_reg); + + __raw_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; +} + +static inline void tve_set_di_fmt(struct fb_info *fbi, unsigned int fmt) +{ + mm_segment_t old_fs; + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_DIFMT, (unsigned long)&fmt); + set_fs(old_fs); + } +} + +/*! + * FB suspend/resume routing + */ +static int tve_suspend(void) +{ + if (enabled) { + __raw_writel(0, tve.base + tve_regs->tve_int_cont_reg); + __raw_writel(0, tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(0, tve.base + tve_regs->tve_com_conf_reg); + clk_disable(tve.clk); + } + return 0; +} + +static int tve_resume(struct fb_info *fbi) +{ + int mode; + + if (enabled) { + clk_enable(tve.clk); + + /* Setup cable detect */ + if (tve.revision == 1) + __raw_writel(0x01067701, + tve.base + tve_regs->tve_cd_cont_reg); + else + __raw_writel(0x00770601, + tve.base + tve_regs->tve_cd_cont_reg); + + if (valid_mode(tve.cur_mode)) { + mode = tve.cur_mode; + tve_disable(); + tve.cur_mode = TVOUT_FMT_OFF; + tve_setup(mode); + } + tve_enable(); + } + + return 0; +} + +int tve_fb_setup(struct fb_info *fbi) +{ + int mode, fmt; + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + if (!fbi->mode) { + pr_warning("TVE: can not find mode for xres=%d, yres=%d\n", + fbi->var.xres, fbi->var.yres); + tve_disable(); + tve.cur_mode = TVOUT_FMT_OFF; + return 0; + } + + pr_debug("TVE: fb mode change event: xres=%d, yres=%d\n", + fbi->mode->xres, fbi->mode->yres); + + mode = get_video_mode(fbi, &fmt); + if (mode != TVOUT_FMT_OFF) { + tve_set_di_fmt(fbi, fmt); + tve_disable(); + tve_setup(mode); + if (tve.blank == FB_BLANK_UNBLANK) + tve_enable(); + } else { + tve_disable(); + tve_setup(mode); + } + + return 0; +} + +int tve_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + if (strcmp(fbi->fix.id, "DISP3 BG - DI1")) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + pr_debug("TVE: fb registered event\n"); + if (tve_fbi != NULL) + break; + + tve_fbi = fbi; + break; + case FB_EVENT_MODE_CHANGE: + { + if (tve_fbi != fbi) + break; + + tve_fb_setup(fbi); + break; + } + case FB_EVENT_BLANK: + if ((tve_fbi != fbi) || (fbi->mode == NULL)) + return 0; + + pr_debug("TVE: fb blank event\n"); + + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + if (tve.blank != FB_BLANK_UNBLANK) { + int mode, fmt; + mode = get_video_mode(fbi, &fmt); + if (mode != TVOUT_FMT_OFF) { + if (tve.cur_mode != mode) { + tve_disable(); + tve_setup(mode); + } + tve_enable(); + } else + tve_setup(mode); + tve.blank = FB_BLANK_UNBLANK; + } + } else { + tve_disable(); + tve.blank = FB_BLANK_POWERDOWN; + } + break; + case FB_EVENT_SUSPEND: + tve_suspend(); + break; + case FB_EVENT_RESUME: + tve_resume(fbi); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = tve_fb_event, +}; + +static ssize_t show_headphone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int detect; + + if (!enabled) { + strcpy(buf, "tve power off\n"); + return strlen(buf); + } + + detect = tve_update_detect_status(); + + 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(void) +{ + 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 = __raw_readl(tve.base); + switch (conf_reg) { + case 0x00842000: + rev = 1; + break; + case 0x00100000: + rev = 2; + break; + } + return rev; +} + +int tve_fb_pre_setup(struct fb_info *fbi) +{ + if (fbi->fbops->fb_ioctl) { + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_BLANK, + (unsigned int)(&tve.blank)); + set_fs(old_fs); + } + return tve_fb_setup(fbi); +} + +static int tve_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + struct tve_platform_data *plat_data = pdev->dev.platform_data; + u32 conf_reg; + + if (g_enable_tve == false && g_enable_vga == false) + return -EPERM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) + return -ENOMEM; + + tve.pdev = pdev; + tve.base = ioremap(res->start, res->end - res->start); + + tve.irq = platform_get_irq(pdev, 0); + if (tve.irq < 0) { + ret = tve.irq; + goto err0; + } + + ret = request_irq(tve.irq, tve_detect_handler, 0, pdev->name, pdev); + if (ret < 0) + goto err0; + + ret = device_create_file(&pdev->dev, &dev_attr_headphone); + if (ret < 0) + goto err1; + + tve.dac_reg = regulator_get(&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(&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(&pdev->dev, "tve_clk"); + if (IS_ERR(tve.clk)) { + ret = PTR_ERR(tve.clk); + goto err2; + } + tve.di_clk = clk_get(NULL, "ipu_di1_clk"); + if (IS_ERR(tve.di_clk)) { + ret = PTR_ERR(tve.di_clk); + goto err2; + } + clk_set_rate(tve.clk, 216000000); + clk_set_parent(tve.di_clk, tve.clk); + clk_enable(tve.clk); + + tve.revision = _tve_get_revision(); + 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[0].left_margin = 121; + video_modes[0].right_margin = 16; + video_modes[0].upper_margin = 17; + video_modes[0].lower_margin = 5; + video_modes[1].left_margin = 131; + video_modes[1].right_margin = 12; + video_modes[1].upper_margin = 21; + video_modes[1].lower_margin = 3; + } + + /* TVE is on disp port 1 */ + if (tve.revision == 1) { + if (g_enable_tve) + mxcfb_register_mode(IPU_DISP_PORT, video_modes, + 3, MXC_DISP_SPEC_DEV); + } else { + if (g_enable_tve) + mxcfb_register_mode(IPU_DISP_PORT, video_modes, + ARRAY_SIZE(video_modes), + MXC_DISP_SPEC_DEV); + + if (cpu_is_mx53() && g_enable_vga) + mxcfb_register_mode(IPU_DISP_PORT, video_modes_vga, + ARRAY_SIZE(video_modes_vga), + MXC_DISP_SPEC_DEV); + } + mxcfb_register_presetup(IPU_DISP_PORT, tve_fb_pre_setup); + + /* 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) + __raw_writel(0x01067701, tve.base + tve_regs->tve_cd_cont_reg); + else + __raw_writel(0x00770601, tve.base + tve_regs->tve_cd_cont_reg); + + conf_reg = 0; + __raw_writel(conf_reg, tve.base + tve_regs->tve_com_conf_reg); + + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 5); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 4); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 3); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 2); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg); + + clk_disable(tve.clk); + + ret = fb_register_client(&nb); + if (ret < 0) + goto err2; + + tve.blank = -1; + + return 0; +err2: + device_remove_file(&pdev->dev, &dev_attr_headphone); +err1: + free_irq(tve.irq, pdev); +err0: + iounmap(tve.base); + return ret; +} + +static int tve_remove(struct platform_device *pdev) +{ + if (enabled) { + clk_disable(tve.clk); + enabled = 0; + } + free_irq(tve.irq, pdev); + device_remove_file(&pdev->dev, &dev_attr_headphone); + fb_unregister_client(&nb); + return 0; +} + +static struct platform_driver tve_driver = { + .driver = { + .name = "tve", + }, + .probe = tve_probe, + .remove = tve_remove, +}; + +static int __init enable_tve_setup(char *options) +{ + g_enable_tve = true; + + return 1; +} +__setup("tve", enable_tve_setup); + +static int __init enable_vga_setup(char *options) +{ + g_enable_vga = true; + + return 1; +} +__setup("vga", enable_vga_setup); + +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"); |