diff options
author | Roshni Shah <shah.roshni@yahoo.com> | 2011-03-14 06:49:42 -0400 |
---|---|---|
committer | Justin Waters <justin.waters@timesys.com> | 2012-03-02 16:59:46 -0500 |
commit | 2731b2eadeaa141e6f305fa8086106608112bbaa (patch) | |
tree | 2702d45bac84073cd580ccb1bd3eafb9a000d3b6 /drivers/video/mxc/tve.c | |
parent | 6d23f5084c975be637f7d748db82116bf84d3872 (diff) |
Add support for the i.MX53 QSB2.6.35.3-mx53-early-201103141049
This patch seems to have originated from the 11.01.00 release
from Freescale, which is no longer available except through the
gitweb interface from Freescale.
http://opensource.freescale.com/git?p=imx/linux-2.6-imx.git;a=commit;h=27fdf7bae11978d21e8aba09bb635f49b07edd4a
Diffstat (limited to 'drivers/video/mxc/tve.c')
-rw-r--r-- | drivers/video/mxc/tve.c | 1289 |
1 files changed, 1289 insertions, 0 deletions
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"); |