| /* |
| * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. |
| */ |
| |
| /* |
| * The code contained herein is licensed under the GNU General Public |
| * License. You may obtain a copy of the GNU General Public License |
| * Version 2 or later at the following locations: |
| * |
| * http://www.opensource.org/licenses/gpl-license.html |
| * http://www.gnu.org/copyleft/gpl.html |
| */ |
| |
| /*! |
| * @file tve.c |
| * @brief Driver for i.MX TV encoder |
| * |
| * @ingroup Framebuffer |
| */ |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/console.h> |
| #include <linux/clk.h> |
| #include <linux/ctype.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/spinlock.h> |
| #include <linux/interrupt.h> |
| #include <linux/sysfs.h> |
| #include <linux/irq.h> |
| #include <linux/sysfs.h> |
| #include <linux/platform_device.h> |
| #include <linux/ipu.h> |
| #include <linux/mxcfb.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/fsl_devices.h> |
| #include <linux/uaccess.h> |
| #include <asm/atomic.h> |
| #include <mach/hardware.h> |
| #include <mach/ipu-v3.h> |
| #include "mxc_dispdrv.h" |
| |
| #define TVE_ENABLE (1UL) |
| #define TVE_DAC_FULL_RATE (0UL<<1) |
| #define TVE_DAC_DIV2_RATE (1UL<<1) |
| #define TVE_DAC_DIV4_RATE (2UL<<1) |
| #define TVE_IPU_CLK_ENABLE (1UL<<3) |
| |
| #define CD_LM_INT 0x00000001 |
| #define CD_SM_INT 0x00000002 |
| #define CD_MON_END_INT 0x00000004 |
| #define CD_CH_0_LM_ST 0x00000001 |
| #define CD_CH_0_SM_ST 0x00000010 |
| #define CD_CH_1_LM_ST 0x00000002 |
| #define CD_CH_1_SM_ST 0x00000020 |
| #define CD_CH_2_LM_ST 0x00000004 |
| #define CD_CH_2_SM_ST 0x00000040 |
| #define CD_MAN_TRIG 0x00000100 |
| |
| #define TVE_STAND_MASK (0x0F<<8) |
| #define TVE_NTSC_STAND (0UL<<8) |
| #define TVE_PAL_STAND (3UL<<8) |
| #define TVE_HD720P60_STAND (4UL<<8) |
| #define TVE_HD720P50_STAND (5UL<<8) |
| #define TVE_HD720P30_STAND (6UL<<8) |
| #define TVE_HD720P25_STAND (7UL<<8) |
| #define TVE_HD720P24_STAND (8UL<<8) |
| #define TVE_HD1080I60_STAND (9UL<<8) |
| #define TVE_HD1080I50_STAND (10UL<<8) |
| #define TVE_HD1035I60_STAND (11UL<<8) |
| #define TVE_HD1080P30_STAND (12UL<<8) |
| #define TVE_HD1080P25_STAND (13UL<<8) |
| #define TVE_HD1080P24_STAND (14UL<<8) |
| #define TVE_DAC_SAMPRATE_MASK (0x3<<1) |
| #define TVEV2_DATA_SRC_MASK (0x3<<4) |
| |
| #define TVEV2_DATA_SRC_BUS_1 (0UL<<4) |
| #define TVEV2_DATA_SRC_BUS_2 (1UL<<4) |
| #define TVEV2_DATA_SRC_EXT (2UL<<4) |
| |
| #define TVEV2_INP_VIDEO_FORM (1UL<<6) |
| #define TVEV2_P2I_CONV_EN (1UL<<7) |
| |
| #define TVEV2_DAC_GAIN_MASK 0x3F |
| #define TVEV2_DAC_TEST_MODE_MASK 0x7 |
| |
| #define TVOUT_FMT_OFF 0 |
| #define TVOUT_FMT_NTSC 1 |
| #define TVOUT_FMT_PAL 2 |
| #define TVOUT_FMT_720P60 3 |
| #define TVOUT_FMT_720P30 4 |
| #define TVOUT_FMT_1080I60 5 |
| #define TVOUT_FMT_1080I50 6 |
| #define TVOUT_FMT_1080P30 7 |
| #define TVOUT_FMT_1080P25 8 |
| #define TVOUT_FMT_1080P24 9 |
| #define TVOUT_FMT_VGA_SVGA 10 |
| #define TVOUT_FMT_VGA_XGA 11 |
| #define TVOUT_FMT_VGA_SXGA 12 |
| #define TVOUT_FMT_VGA_WSXGA 13 |
| |
| #define DISPDRV_VGA "vga" |
| #define DISPDRV_TVE "tve" |
| |
| struct tve_data { |
| struct platform_device *pdev; |
| int revision; |
| int cur_mode; |
| int output_mode; |
| int detect; |
| void *base; |
| spinlock_t tve_lock; |
| bool inited; |
| int enabled; |
| int irq; |
| struct clk *clk; |
| struct clk *di_clk; |
| struct regulator *dac_reg; |
| struct regulator *dig_reg; |
| struct delayed_work cd_work; |
| struct tve_reg_mapping *regs; |
| struct tve_reg_fields_mapping *reg_fields; |
| struct mxc_dispdrv_entry *disp_tve; |
| struct mxc_dispdrv_entry *disp_vga; |
| struct notifier_block nb; |
| }; |
| |
| struct tve_reg_mapping { |
| u32 tve_com_conf_reg; |
| u32 tve_cd_cont_reg; |
| u32 tve_int_cont_reg; |
| u32 tve_stat_reg; |
| u32 tve_mv_cont_reg; |
| u32 tve_tvdac_cont_reg; |
| u32 tve_tst_mode_reg; |
| }; |
| |
| struct tve_reg_fields_mapping { |
| u32 cd_en; |
| u32 cd_trig_mode; |
| u32 cd_lm_int; |
| u32 cd_sm_int; |
| u32 cd_mon_end_int; |
| u32 cd_man_trig; |
| u32 sync_ch_mask; |
| u32 tvout_mode_mask; |
| u32 sync_ch_offset; |
| u32 tvout_mode_offset; |
| u32 cd_ch_stat_offset; |
| }; |
| |
| static struct tve_reg_mapping tve_regs_v1 = { |
| 0, 0x14, 0x28, 0x2C, 0x48, 0x08, 0x30 |
| }; |
| |
| static struct tve_reg_fields_mapping tve_reg_fields_v1 = { |
| 1, 2, 1, 2, 4, 0x00010000, 0x7000, 0x70, 12, 4, 8 |
| }; |
| |
| static struct tve_reg_mapping tve_regs_v2 = { |
| 0, 0x34, 0x64, 0x68, 0xDC, 0x28, 0x6c |
| }; |
| |
| static struct tve_reg_fields_mapping tve_reg_fields_v2 = { |
| 1, 2, 1, 2, 4, 0x01000000, 0x700000, 0x7000, 20, 12, 16 |
| }; |
| |
| static struct fb_videomode video_modes_tve[] = { |
| { |
| /* NTSC TV output */ |
| "TV-NTSC", 60, 720, 480, 74074, |
| 122, 15, |
| 18, 26, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_INTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* PAL TV output */ |
| "TV-PAL", 50, 720, 576, 74074, |
| 132, 11, |
| 22, 26, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* 720p60 TV output */ |
| "TV-720P60", 60, 1280, 720, 13468, |
| 260, 109, |
| 25, 4, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* 720p30 TV output */ |
| "TV-720P30", 30, 1280, 720, 13468, |
| 260, 1759, |
| 25, 4, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* 1080i60 TV output */ |
| "TV-1080I60", 60, 1920, 1080, 13468, |
| 192, 87, |
| 20, 24, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* 1080i50 TV output */ |
| "TV-1080I50", 50, 1920, 1080, 13468, |
| 192, 527, |
| 20, 24, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* 1080p30 TV output */ |
| "TV-1080P30", 30, 1920, 1080, 13468, |
| 192, 87, |
| 38, 6, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* 1080p25 TV output */ |
| "TV-1080P25", 25, 1920, 1080, 13468, |
| 192, 527, |
| 38, 6, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* 1080p24 TV output */ |
| "TV-1080P24", 24, 1920, 1080, 13468, |
| 192, 637, |
| 38, 6, |
| 1, 1, |
| FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| }; |
| static int tve_modedb_sz = ARRAY_SIZE(video_modes_tve); |
| |
| static struct fb_videomode video_modes_vga[] = { |
| { |
| /* VGA 800x600 40M pixel clk output */ |
| "VGA-SVGA", 60, 800, 600, 25000, |
| 215, 28, |
| 24, 2, |
| 13, 2, |
| 0, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* VGA 1024x768 65M pixel clk output */ |
| "VGA-XGA", 60, 1024, 768, 15384, |
| 160, 24, |
| 29, 3, |
| 136, 6, |
| 0, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* VGA 1280x1024 108M pixel clk output */ |
| "VGA-SXGA", 60, 1280, 1024, 9259, |
| 358, 38, |
| 38, 2, |
| 12, 2, |
| 0, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| { |
| /* VGA 1680x1050 294M pixel clk output */ |
| "VGA-WSXGA+", 60, 1680, 1050, 6796, |
| 288, 104, |
| 33, 2, |
| 184, 2, |
| 0, |
| FB_VMODE_NONINTERLACED, |
| FB_MODE_IS_DETAILED,}, |
| }; |
| static int vga_modedb_sz = ARRAY_SIZE(video_modes_vga); |
| |
| enum tvout_mode { |
| TV_OFF, |
| CVBS0, |
| CVBS2, |
| CVBS02, |
| SVIDEO, |
| SVIDEO_CVBS, |
| YPBPR, |
| TVRGB |
| }; |
| |
| static unsigned short tvout_mode_to_channel_map[8] = { |
| 0, /* TV_OFF */ |
| 1, /* CVBS0 */ |
| 4, /* CVBS2 */ |
| 5, /* CVBS02 */ |
| 1, /* SVIDEO */ |
| 5, /* SVIDEO_CVBS */ |
| 1, /* YPBPR */ |
| 7 /* TVRGB */ |
| }; |
| |
| static void tve_dump_regs(struct tve_data *tve) |
| { |
| dev_dbg(&tve->pdev->dev, "tve_com_conf_reg 0x%x\n", |
| readl(tve->base + tve->regs->tve_com_conf_reg)); |
| dev_dbg(&tve->pdev->dev, "tve_cd_cont_reg 0x%x\n", |
| readl(tve->base + tve->regs->tve_cd_cont_reg)); |
| dev_dbg(&tve->pdev->dev, "tve_int_cont_reg 0x%x\n", |
| readl(tve->base + tve->regs->tve_int_cont_reg)); |
| dev_dbg(&tve->pdev->dev, "tve_tst_mode_reg 0x%x\n", |
| readl(tve->base + tve->regs->tve_tst_mode_reg)); |
| dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg0 0x%x\n", |
| readl(tve->base + tve->regs->tve_tvdac_cont_reg)); |
| dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg1 0x%x\n", |
| readl(tve->base + tve->regs->tve_tvdac_cont_reg + 4)); |
| dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg2 0x%x\n", |
| readl(tve->base + tve->regs->tve_tvdac_cont_reg + 8)); |
| } |
| |
| static int is_vga_enabled(struct tve_data *tve) |
| { |
| u32 reg; |
| |
| if (tve->revision == 2) { |
| reg = readl(tve->base + tve->regs->tve_tst_mode_reg); |
| if (reg & TVEV2_DAC_TEST_MODE_MASK) |
| return 1; |
| else |
| return 0; |
| } |
| return 0; |
| } |
| |
| static inline int is_vga_mode(int mode) |
| { |
| return ((mode == TVOUT_FMT_VGA_SVGA) |
| || (mode == TVOUT_FMT_VGA_XGA) |
| || (mode == TVOUT_FMT_VGA_SXGA) |
| || (mode == TVOUT_FMT_VGA_WSXGA)); |
| } |
| |
| static inline int valid_mode(int mode) |
| { |
| return (is_vga_mode(mode) |
| || (mode == TVOUT_FMT_NTSC) |
| || (mode == TVOUT_FMT_PAL) |
| || (mode == TVOUT_FMT_720P30) |
| || (mode == TVOUT_FMT_720P60) |
| || (mode == TVOUT_FMT_1080I50) |
| || (mode == TVOUT_FMT_1080I60) |
| || (mode == TVOUT_FMT_1080P24) |
| || (mode == TVOUT_FMT_1080P25) |
| || (mode == TVOUT_FMT_1080P30)); |
| } |
| |
| static int get_video_mode(struct fb_info *fbi) |
| { |
| int mode; |
| |
| if (fb_mode_is_equal(fbi->mode, &video_modes_tve[0])) { |
| mode = TVOUT_FMT_NTSC; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[1])) { |
| mode = TVOUT_FMT_PAL; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[2])) { |
| mode = TVOUT_FMT_720P60; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[3])) { |
| mode = TVOUT_FMT_720P30; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[4])) { |
| mode = TVOUT_FMT_1080I60; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[5])) { |
| mode = TVOUT_FMT_1080I50; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[6])) { |
| mode = TVOUT_FMT_1080P30; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[7])) { |
| mode = TVOUT_FMT_1080P25; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[8])) { |
| mode = TVOUT_FMT_1080P24; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[0])) { |
| mode = TVOUT_FMT_VGA_SVGA; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[1])) { |
| mode = TVOUT_FMT_VGA_XGA; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[2])) { |
| mode = TVOUT_FMT_VGA_SXGA; |
| } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[3])) { |
| mode = TVOUT_FMT_VGA_WSXGA; |
| } else { |
| mode = TVOUT_FMT_OFF; |
| } |
| return mode; |
| } |
| |
| static void tve_disable_vga_mode(struct tve_data *tve) |
| { |
| if (tve->revision == 2) { |
| u32 reg; |
| /* disable test mode */ |
| reg = readl(tve->base + tve->regs->tve_tst_mode_reg); |
| reg = reg & ~TVEV2_DAC_TEST_MODE_MASK; |
| writel(reg, tve->base + tve->regs->tve_tst_mode_reg); |
| } |
| } |
| |
| static void tve_set_tvout_mode(struct tve_data *tve, int mode) |
| { |
| u32 conf_reg; |
| |
| /* clear sync_ch and tvout_mode fields */ |
| conf_reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| conf_reg &= ~(tve->reg_fields->sync_ch_mask | |
| tve->reg_fields->tvout_mode_mask); |
| |
| conf_reg = conf_reg & ~TVE_DAC_SAMPRATE_MASK; |
| if (tve->revision == 2) { |
| conf_reg = (conf_reg & ~TVEV2_DATA_SRC_MASK) | |
| TVEV2_DATA_SRC_BUS_1; |
| conf_reg = conf_reg & ~TVEV2_INP_VIDEO_FORM; |
| conf_reg = conf_reg & ~TVEV2_P2I_CONV_EN; |
| } |
| |
| conf_reg |= |
| mode << tve->reg_fields-> |
| tvout_mode_offset | tvout_mode_to_channel_map[mode] << |
| tve->reg_fields->sync_ch_offset; |
| writel(conf_reg, tve->base + tve->regs->tve_com_conf_reg); |
| } |
| |
| static int _is_tvout_mode_hd_compatible(struct tve_data *tve) |
| { |
| u32 conf_reg, mode; |
| |
| conf_reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| mode = (conf_reg >> tve->reg_fields->tvout_mode_offset) & 7; |
| if (mode == YPBPR || mode == TVRGB) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| static int tve_setup_vga(struct tve_data *tve) |
| { |
| u32 reg; |
| |
| if (tve->revision == 2) { |
| /* set gain */ |
| reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg); |
| reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; |
| writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg); |
| reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg + 4); |
| reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; |
| writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg + 4); |
| reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg + 8); |
| reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; |
| writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg + 8); |
| |
| /* set tve_com_conf_reg */ |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_DAC_SAMPRATE_MASK) | TVE_DAC_DIV2_RATE; |
| reg = (reg & ~TVEV2_DATA_SRC_MASK) | TVEV2_DATA_SRC_BUS_2; |
| reg = reg | TVEV2_INP_VIDEO_FORM; |
| reg = reg & ~TVEV2_P2I_CONV_EN; |
| reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; |
| reg |= TVRGB << tve->reg_fields->tvout_mode_offset | |
| 1 << tve->reg_fields->sync_ch_offset; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| |
| /* set test mode */ |
| reg = readl(tve->base + tve->regs->tve_tst_mode_reg); |
| reg = (reg & ~TVEV2_DAC_TEST_MODE_MASK) | 1; |
| writel(reg, tve->base + tve->regs->tve_tst_mode_reg); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * tve_setup |
| * initial the CH7024 chipset by setting register |
| * @param: |
| * vos: output video format |
| * @return: |
| * 0 successful |
| * otherwise failed |
| */ |
| static int tve_setup(struct tve_data *tve, int mode) |
| { |
| u32 reg; |
| struct clk *tve_parent_clk; |
| unsigned long parent_clock_rate = 216000000, di1_clock_rate = 27000000; |
| unsigned long tve_clock_rate = 216000000; |
| unsigned long lock_flags; |
| |
| if (tve->cur_mode == mode) |
| return 0; |
| |
| spin_lock_irqsave(&tve->tve_lock, lock_flags); |
| |
| switch (mode) { |
| case TVOUT_FMT_PAL: |
| case TVOUT_FMT_NTSC: |
| parent_clock_rate = 216000000; |
| di1_clock_rate = 27000000; |
| break; |
| case TVOUT_FMT_720P60: |
| case TVOUT_FMT_1080I60: |
| case TVOUT_FMT_1080I50: |
| case TVOUT_FMT_720P30: |
| case TVOUT_FMT_1080P30: |
| case TVOUT_FMT_1080P25: |
| case TVOUT_FMT_1080P24: |
| parent_clock_rate = 297000000; |
| tve_clock_rate = 297000000; |
| di1_clock_rate = 74250000; |
| break; |
| case TVOUT_FMT_VGA_SVGA: |
| parent_clock_rate = 160000000; |
| tve_clock_rate = 80000000; |
| di1_clock_rate = 40000000; |
| break; |
| case TVOUT_FMT_VGA_XGA: |
| parent_clock_rate = 520000000; |
| tve_clock_rate = 130000000; |
| di1_clock_rate = 65000000; |
| break; |
| case TVOUT_FMT_VGA_SXGA: |
| parent_clock_rate = 864000000; |
| tve_clock_rate = 216000000; |
| di1_clock_rate = 108000000; |
| break; |
| case TVOUT_FMT_VGA_WSXGA: |
| parent_clock_rate = 588560000; |
| tve_clock_rate = 294280000; |
| di1_clock_rate = 147140000; |
| break; |
| } |
| if (tve->enabled) |
| clk_disable(tve->clk); |
| |
| tve_parent_clk = clk_get_parent(tve->clk); |
| |
| clk_set_rate(tve_parent_clk, parent_clock_rate); |
| |
| tve_clock_rate = clk_round_rate(tve->clk, tve_clock_rate); |
| clk_set_rate(tve->clk, tve_clock_rate); |
| |
| clk_enable(tve->clk); |
| di1_clock_rate = clk_round_rate(tve->di_clk, di1_clock_rate); |
| clk_set_rate(tve->di_clk, di1_clock_rate); |
| |
| tve->cur_mode = mode; |
| |
| /* select output video format */ |
| if (mode == TVOUT_FMT_PAL) { |
| tve_disable_vga_mode(tve); |
| tve_set_tvout_mode(tve, YPBPR); |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_PAL_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to PAL video\n"); |
| } else if (mode == TVOUT_FMT_NTSC) { |
| tve_disable_vga_mode(tve); |
| tve_set_tvout_mode(tve, YPBPR); |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_NTSC_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to NTSC video\n"); |
| } else if (mode == TVOUT_FMT_720P60) { |
| tve_disable_vga_mode(tve); |
| if (!_is_tvout_mode_hd_compatible(tve)) { |
| tve_set_tvout_mode(tve, YPBPR); |
| dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); |
| } |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P60_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to 720P60 video\n"); |
| } else if (mode == TVOUT_FMT_720P30) { |
| tve_disable_vga_mode(tve); |
| if (!_is_tvout_mode_hd_compatible(tve)) { |
| tve_set_tvout_mode(tve, YPBPR); |
| dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); |
| } |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P30_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to 720P30 video\n"); |
| } else if (mode == TVOUT_FMT_1080I60) { |
| tve_disable_vga_mode(tve); |
| if (!_is_tvout_mode_hd_compatible(tve)) { |
| tve_set_tvout_mode(tve, YPBPR); |
| dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); |
| } |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I60_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to 1080I60 video\n"); |
| } else if (mode == TVOUT_FMT_1080I50) { |
| tve_disable_vga_mode(tve); |
| if (!_is_tvout_mode_hd_compatible(tve)) { |
| tve_set_tvout_mode(tve, YPBPR); |
| dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); |
| } |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I50_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to 1080I50 video\n"); |
| } else if (mode == TVOUT_FMT_1080P30) { |
| tve_disable_vga_mode(tve); |
| if (!_is_tvout_mode_hd_compatible(tve)) { |
| tve_set_tvout_mode(tve, YPBPR); |
| dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); |
| } |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to 1080P30 video\n"); |
| } else if (mode == TVOUT_FMT_1080P25) { |
| tve_disable_vga_mode(tve); |
| if (!_is_tvout_mode_hd_compatible(tve)) { |
| tve_set_tvout_mode(tve, YPBPR); |
| dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); |
| } |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P25_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to 1080P25 video\n"); |
| } else if (mode == TVOUT_FMT_1080P24) { |
| tve_disable_vga_mode(tve); |
| if (!_is_tvout_mode_hd_compatible(tve)) { |
| tve_set_tvout_mode(tve, YPBPR); |
| dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); |
| } |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P24_STAND; |
| writel(reg, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to 1080P24 video\n"); |
| } else if (is_vga_mode(mode)) { |
| /* do not need cable detect */ |
| tve_setup_vga(tve); |
| dev_dbg(&tve->pdev->dev, "TVE: change to VGA video\n"); |
| } else if (mode == TVOUT_FMT_OFF) { |
| writel(0x0, tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE: change to OFF video\n"); |
| } else { |
| dev_dbg(&tve->pdev->dev, "TVE: no such video format.\n"); |
| } |
| |
| if (!tve->enabled) |
| clk_disable(tve->clk); |
| |
| spin_unlock_irqrestore(&tve->tve_lock, lock_flags); |
| return 0; |
| } |
| |
| /** |
| * tve_enable |
| * Enable the tve Power to begin TV encoder |
| */ |
| static void tve_enable(struct tve_data *tve) |
| { |
| u32 reg; |
| unsigned long lock_flags; |
| |
| spin_lock_irqsave(&tve->tve_lock, lock_flags); |
| if (!tve->enabled) { |
| tve->enabled = 1; |
| clk_enable(tve->clk); |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| writel(reg | TVE_IPU_CLK_ENABLE | TVE_ENABLE, |
| tve->base + tve->regs->tve_com_conf_reg); |
| dev_dbg(&tve->pdev->dev, "TVE power on.\n"); |
| } |
| |
| if (is_vga_enabled(tve)) { |
| /* disable interrupt */ |
| dev_dbg(&tve->pdev->dev, "TVE VGA disable cable detect.\n"); |
| writel(0xffffffff, tve->base + tve->regs->tve_stat_reg); |
| writel(0, tve->base + tve->regs->tve_int_cont_reg); |
| } else { |
| /* enable interrupt */ |
| dev_dbg(&tve->pdev->dev, "TVE TVE enable cable detect.\n"); |
| writel(0xffffffff, tve->base + tve->regs->tve_stat_reg); |
| writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT, |
| tve->base + tve->regs->tve_int_cont_reg); |
| } |
| |
| spin_unlock_irqrestore(&tve->tve_lock, lock_flags); |
| |
| tve_dump_regs(tve); |
| } |
| |
| /** |
| * tve_disable |
| * Disable the tve Power to stop TV encoder |
| */ |
| static void tve_disable(struct tve_data *tve) |
| { |
| u32 reg; |
| unsigned long lock_flags; |
| |
| spin_lock_irqsave(&tve->tve_lock, lock_flags); |
| if (tve->enabled) { |
| tve->enabled = 0; |
| reg = readl(tve->base + tve->regs->tve_com_conf_reg); |
| writel(reg & ~TVE_ENABLE & ~TVE_IPU_CLK_ENABLE, |
| tve->base + tve->regs->tve_com_conf_reg); |
| clk_disable(tve->clk); |
| dev_dbg(&tve->pdev->dev, "TVE power off.\n"); |
| } |
| spin_unlock_irqrestore(&tve->tve_lock, lock_flags); |
| } |
| |
| static int tve_update_detect_status(struct tve_data *tve) |
| { |
| int old_detect = tve->detect; |
| u32 stat_lm, stat_sm, stat; |
| u32 int_ctl; |
| u32 cd_cont_reg; |
| u32 timeout = 40; |
| unsigned long lock_flags; |
| char event_string[16]; |
| char *envp[] = { event_string, NULL }; |
| |
| spin_lock_irqsave(&tve->tve_lock, lock_flags); |
| |
| if (!tve->enabled) { |
| dev_warn(&tve->pdev->dev, "Warning: update tve status while it disabled!\n"); |
| tve->detect = 0; |
| goto done; |
| } |
| |
| int_ctl = readl(tve->base + tve->regs->tve_int_cont_reg); |
| cd_cont_reg = readl(tve->base + tve->regs->tve_cd_cont_reg); |
| |
| if ((cd_cont_reg & 0x1) == 0) { |
| dev_warn(&tve->pdev->dev, "Warning: pls enable TVE CD first!\n"); |
| goto done; |
| } |
| |
| stat = readl(tve->base + tve->regs->tve_stat_reg); |
| while (((stat & CD_MON_END_INT) == 0) && (timeout > 0)) { |
| spin_unlock_irqrestore(&tve->tve_lock, lock_flags); |
| msleep(2); |
| spin_lock_irqsave(&tve->tve_lock, lock_flags); |
| timeout -= 2; |
| if (!tve->enabled) { |
| dev_warn(&tve->pdev->dev, "Warning: update tve status while it disabled!\n"); |
| tve->detect = 0; |
| goto done; |
| } else |
| stat = readl(tve->base + tve->regs->tve_stat_reg); |
| } |
| if (((stat & CD_MON_END_INT) == 0) && (timeout <= 0)) { |
| dev_warn(&tve->pdev->dev, "Warning: get detect result without CD_MON_END_INT!\n"); |
| goto done; |
| } |
| |
| stat = stat >> tve->reg_fields->cd_ch_stat_offset; |
| stat_lm = stat & (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST); |
| if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST)) && |
| ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST | CD_CH_2_SM_ST)) == 0) |
| ) { |
| tve->detect = 3; |
| tve->output_mode = YPBPR; |
| } else if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST)) && |
| ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST)) == 0)) { |
| tve->detect = 4; |
| tve->output_mode = SVIDEO; |
| } else if (stat_lm == CD_CH_0_LM_ST) { |
| stat_sm = stat & CD_CH_0_SM_ST; |
| if (stat_sm != 0) { |
| /* headset */ |
| tve->detect = 2; |
| tve->output_mode = TV_OFF; |
| } else { |
| tve->detect = 1; |
| tve->output_mode = CVBS0; |
| } |
| } else if (stat_lm == CD_CH_2_LM_ST) { |
| stat_sm = stat & CD_CH_2_SM_ST; |
| if (stat_sm != 0) { |
| /* headset */ |
| tve->detect = 2; |
| tve->output_mode = TV_OFF; |
| } else { |
| tve->detect = 1; |
| tve->output_mode = CVBS2; |
| } |
| } else { |
| /* none */ |
| tve->detect = 0; |
| tve->output_mode = TV_OFF; |
| } |
| |
| tve_set_tvout_mode(tve, tve->output_mode); |
| |
| /* clear interrupt */ |
| writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, |
| tve->base + tve->regs->tve_stat_reg); |
| |
| writel(int_ctl | CD_SM_INT | CD_LM_INT, |
| tve->base + tve->regs->tve_int_cont_reg); |
| |
| done: |
| spin_unlock_irqrestore(&tve->tve_lock, lock_flags); |
| |
| if (old_detect != tve->detect) { |
| sysfs_notify(&tve->pdev->dev.kobj, NULL, "headphone"); |
| if (tve->detect == 1) |
| sprintf(event_string, "EVENT=CVBS0"); |
| else if (tve->detect == 3) |
| sprintf(event_string, "EVENT=YPBPR"); |
| else if (tve->detect == 4) |
| sprintf(event_string, "EVENT=SVIDEO"); |
| else |
| sprintf(event_string, "EVENT=NONE"); |
| kobject_uevent_env(&tve->pdev->dev.kobj, KOBJ_CHANGE, envp); |
| } |
| |
| dev_dbg(&tve->pdev->dev, "detect = %d mode = %d\n", |
| tve->detect, tve->output_mode); |
| return tve->detect; |
| } |
| |
| static void cd_work_func(struct work_struct *work) |
| { |
| struct delayed_work *delay_work = to_delayed_work(work); |
| struct tve_data *tve = |
| container_of(delay_work, struct tve_data, cd_work); |
| |
| tve_update_detect_status(tve); |
| } |
| |
| static irqreturn_t tve_detect_handler(int irq, void *data) |
| { |
| struct tve_data *tve = data; |
| |
| u32 int_ctl = readl(tve->base + tve->regs->tve_int_cont_reg); |
| |
| /* disable INT first */ |
| int_ctl &= ~(CD_SM_INT | CD_LM_INT | CD_MON_END_INT); |
| writel(int_ctl, tve->base + tve->regs->tve_int_cont_reg); |
| |
| writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, |
| tve->base + tve->regs->tve_stat_reg); |
| |
| schedule_delayed_work(&tve->cd_work, msecs_to_jiffies(1000)); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /*! |
| * FB suspend/resume routing |
| */ |
| static int tve_suspend(struct tve_data *tve) |
| { |
| if (tve->enabled) { |
| writel(0, tve->base + tve->regs->tve_int_cont_reg); |
| writel(0, tve->base + tve->regs->tve_cd_cont_reg); |
| writel(0, tve->base + tve->regs->tve_com_conf_reg); |
| clk_disable(tve->clk); |
| } |
| return 0; |
| } |
| |
| static int tve_resume(struct tve_data *tve, struct fb_info *fbi) |
| { |
| int mode; |
| |
| if (tve->enabled) { |
| clk_enable(tve->clk); |
| |
| /* Setup cable detect */ |
| if (tve->revision == 1) |
| writel(0x01067701, |
| tve->base + tve->regs->tve_cd_cont_reg); |
| else |
| writel(0x00770601, |
| tve->base + tve->regs->tve_cd_cont_reg); |
| |
| if (valid_mode(tve->cur_mode)) { |
| mode = tve->cur_mode; |
| tve_disable(tve); |
| tve->cur_mode = TVOUT_FMT_OFF; |
| tve_setup(tve, mode); |
| } |
| tve_enable(tve); |
| } |
| |
| return 0; |
| } |
| |
| int tve_fb_setup(struct tve_data *tve, struct fb_info *fbi) |
| { |
| int mode; |
| |
| fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, |
| &fbi->modelist); |
| |
| if (!fbi->mode) { |
| dev_warn(&tve->pdev->dev, "TVE: can not find mode for xres=%d, yres=%d\n", |
| fbi->var.xres, fbi->var.yres); |
| tve_disable(tve); |
| tve->cur_mode = TVOUT_FMT_OFF; |
| return 0; |
| } |
| |
| dev_dbg(&tve->pdev->dev, "TVE: fb mode change event: xres=%d, yres=%d\n", |
| fbi->mode->xres, fbi->mode->yres); |
| |
| mode = get_video_mode(fbi); |
| if (mode != TVOUT_FMT_OFF) { |
| tve_disable(tve); |
| tve_setup(tve, mode); |
| tve_enable(tve); |
| } else { |
| tve_disable(tve); |
| tve_setup(tve, mode); |
| } |
| |
| return 0; |
| } |
| |
| int tve_fb_event(struct notifier_block *nb, unsigned long val, void *v) |
| { |
| struct tve_data *tve = container_of(nb, struct tve_data, nb); |
| struct fb_event *event = v; |
| struct fb_info *fbi = event->info; |
| |
| /* only work for ipu0 di1*/ |
| if (strcmp(fbi->fix.id, "DISP3 BG - DI1")) |
| return 0; |
| |
| switch (val) { |
| case FB_EVENT_PREMODE_CHANGE: |
| { |
| tve_fb_setup(tve, fbi); |
| break; |
| } |
| case FB_EVENT_BLANK: |
| if (fbi->mode == NULL) |
| return 0; |
| |
| dev_dbg(&tve->pdev->dev, "TVE: fb blank event\n"); |
| |
| if (*((int *)event->data) == FB_BLANK_UNBLANK) { |
| int mode; |
| mode = get_video_mode(fbi); |
| if (mode != TVOUT_FMT_OFF) { |
| if (tve->cur_mode != mode) { |
| tve_disable(tve); |
| tve_setup(tve, mode); |
| } |
| tve_enable(tve); |
| } else |
| tve_setup(tve, mode); |
| } else |
| tve_disable(tve); |
| break; |
| case FB_EVENT_SUSPEND: |
| tve_suspend(tve); |
| break; |
| case FB_EVENT_RESUME: |
| tve_resume(tve, fbi); |
| break; |
| } |
| return 0; |
| } |
| |
| static ssize_t show_headphone(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct tve_data *tve = dev_get_drvdata(dev); |
| int detect; |
| |
| if (!tve->enabled) { |
| strcpy(buf, "tve power off\n"); |
| return strlen(buf); |
| } |
| |
| detect = tve_update_detect_status(tve); |
| |
| if (detect == 0) |
| strcpy(buf, "none\n"); |
| else if (detect == 1) |
| strcpy(buf, "cvbs\n"); |
| else if (detect == 2) |
| strcpy(buf, "headset\n"); |
| else if (detect == 3) |
| strcpy(buf, "component\n"); |
| else |
| strcpy(buf, "svideo\n"); |
| |
| return strlen(buf); |
| } |
| |
| static DEVICE_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); |
| |
| static int _tve_get_revision(struct tve_data *tve) |
| { |
| u32 conf_reg; |
| u32 rev = 0; |
| |
| /* find out TVE rev based on the base addr default value |
| * can be used at the init/probe ONLY */ |
| conf_reg = readl(tve->base); |
| switch (conf_reg) { |
| case 0x00842000: |
| rev = 1; |
| break; |
| case 0x00100000: |
| rev = 2; |
| break; |
| } |
| return rev; |
| } |
| |
| static int tve_drv_init(struct mxc_dispdrv_entry *disp, bool vga) |
| { |
| int ret; |
| struct tve_data *tve = mxc_dispdrv_getdata(disp); |
| struct mxc_dispdrv_setting *setting = mxc_dispdrv_getsetting(disp); |
| struct fsl_mxc_tve_platform_data *plat_data |
| = tve->pdev->dev.platform_data; |
| struct resource *res; |
| struct fb_videomode *modedb; |
| int modedb_sz; |
| u32 conf_reg; |
| |
| if (tve->inited == true) |
| return -ENODEV; |
| |
| /*tve&vga only use ipu0 and di1*/ |
| setting->dev_id = 0; |
| setting->disp_id = 1; |
| |
| res = platform_get_resource(tve->pdev, IORESOURCE_MEM, 0); |
| if (res == NULL) { |
| ret = -ENOMEM; |
| goto get_res_failed; |
| } |
| |
| tve->irq = platform_get_irq(tve->pdev, 0); |
| if (tve->irq < 0) { |
| ret = tve->irq; |
| goto get_irq_failed; |
| } |
| |
| tve->base = ioremap(res->start, res->end - res->start); |
| if (!tve->base) { |
| ret = -ENOMEM; |
| goto ioremap_failed; |
| } |
| |
| ret = device_create_file(&tve->pdev->dev, &dev_attr_headphone); |
| if (ret < 0) |
| goto dev_file_create_failed; |
| |
| tve->dac_reg = regulator_get(&tve->pdev->dev, plat_data->dac_reg); |
| if (!IS_ERR(tve->dac_reg)) { |
| regulator_set_voltage(tve->dac_reg, 2750000, 2750000); |
| regulator_enable(tve->dac_reg); |
| } |
| tve->dig_reg = regulator_get(&tve->pdev->dev, plat_data->dig_reg); |
| if (!IS_ERR(tve->dig_reg)) { |
| regulator_set_voltage(tve->dig_reg, 1250000, 1250000); |
| regulator_enable(tve->dig_reg); |
| } |
| |
| tve->clk = clk_get(&tve->pdev->dev, "tve_clk"); |
| if (IS_ERR(tve->clk)) { |
| ret = PTR_ERR(tve->clk); |
| goto get_tveclk_failed; |
| } |
| tve->di_clk = clk_get(NULL, "ipu1_di1_clk"); |
| if (IS_ERR(tve->di_clk)) { |
| ret = PTR_ERR(tve->di_clk); |
| goto get_diclk_failed; |
| } |
| |
| clk_set_rate(tve->clk, 216000000); |
| clk_set_parent(tve->di_clk, tve->clk); |
| clk_enable(tve->clk); |
| |
| tve->revision = _tve_get_revision(tve); |
| if (tve->revision == 1) { |
| tve->regs = &tve_regs_v1; |
| tve->reg_fields = &tve_reg_fields_v1; |
| } else { |
| tve->regs = &tve_regs_v2; |
| tve->reg_fields = &tve_reg_fields_v2; |
| } |
| |
| if (vga && cpu_is_mx53()) { |
| setting->if_fmt = IPU_PIX_FMT_GBR24; |
| modedb = video_modes_vga; |
| modedb_sz = vga_modedb_sz; |
| } else { |
| setting->if_fmt = IPU_PIX_FMT_YUV444; |
| if (tve->revision == 1) { |
| modedb = video_modes_tve; |
| modedb_sz = 3; |
| } else { |
| modedb = video_modes_tve; |
| modedb_sz = tve_modedb_sz; |
| } |
| } |
| |
| fb_videomode_to_modelist(modedb, modedb_sz, &setting->fbi->modelist); |
| |
| /* must use spec video mode defined by driver */ |
| ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, |
| modedb, modedb_sz, NULL, setting->default_bpp); |
| if (ret != 1) |
| fb_videomode_to_var(&setting->fbi->var, &modedb[0]); |
| |
| ret = request_irq(tve->irq, tve_detect_handler, 0, tve->pdev->name, tve); |
| if (ret < 0) |
| goto req_irq_failed; |
| |
| /* Setup cable detect, for YPrPb mode, default use channel#-1 for Y */ |
| INIT_DELAYED_WORK(&tve->cd_work, cd_work_func); |
| if (tve->revision == 1) |
| writel(0x01067701, tve->base + tve->regs->tve_cd_cont_reg); |
| else |
| writel(0x00770601, tve->base + tve->regs->tve_cd_cont_reg); |
| |
| conf_reg = 0; |
| writel(conf_reg, tve->base + tve->regs->tve_com_conf_reg); |
| |
| writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 5); |
| writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 4); |
| writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 3); |
| writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 2); |
| writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4); |
| writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg); |
| |
| clk_disable(tve->clk); |
| |
| tve->nb.notifier_call = tve_fb_event; |
| ret = fb_register_client(&tve->nb); |
| if (ret < 0) |
| goto reg_fbclient_failed; |
| |
| dev_set_drvdata(&tve->pdev->dev, tve); |
| |
| spin_lock_init(&tve->tve_lock); |
| |
| tve->inited = true; |
| |
| return 0; |
| |
| reg_fbclient_failed: |
| free_irq(tve->irq, tve->pdev); |
| req_irq_failed: |
| get_diclk_failed: |
| get_tveclk_failed: |
| device_remove_file(&tve->pdev->dev, &dev_attr_headphone); |
| dev_file_create_failed: |
| iounmap(tve->base); |
| ioremap_failed: |
| get_irq_failed: |
| get_res_failed: |
| return ret; |
| |
| } |
| |
| static int tvout_init(struct mxc_dispdrv_entry *disp) |
| { |
| return tve_drv_init(disp, 0); |
| } |
| |
| static int vga_init(struct mxc_dispdrv_entry *disp) |
| { |
| return tve_drv_init(disp, 1); |
| } |
| |
| void tvout_deinit(struct mxc_dispdrv_entry *disp) |
| { |
| struct tve_data *tve = mxc_dispdrv_getdata(disp); |
| |
| if (tve->enabled) |
| clk_disable(tve->clk); |
| |
| fb_unregister_client(&tve->nb); |
| free_irq(tve->irq, tve->pdev); |
| device_remove_file(&tve->pdev->dev, &dev_attr_headphone); |
| iounmap(tve->base); |
| } |
| |
| static struct mxc_dispdrv_driver tve_drv = { |
| .name = DISPDRV_TVE, |
| .init = tvout_init, |
| .deinit = tvout_deinit, |
| }; |
| |
| static struct mxc_dispdrv_driver vga_drv = { |
| .name = DISPDRV_VGA, |
| .init = vga_init, |
| .deinit = tvout_deinit, |
| }; |
| |
| static int tve_dispdrv_init(struct tve_data *tve) |
| { |
| tve->disp_tve = mxc_dispdrv_register(&tve_drv); |
| mxc_dispdrv_setdata(tve->disp_tve, tve); |
| tve->disp_vga = mxc_dispdrv_register(&vga_drv); |
| mxc_dispdrv_setdata(tve->disp_vga, tve); |
| return 0; |
| } |
| |
| static void tve_dispdrv_deinit(struct tve_data *tve) |
| { |
| mxc_dispdrv_unregister(tve->disp_tve); |
| mxc_dispdrv_unregister(tve->disp_vga); |
| } |
| |
| static int tve_probe(struct platform_device *pdev) |
| { |
| int ret; |
| struct tve_data *tve; |
| |
| tve = kzalloc(sizeof(struct tve_data), GFP_KERNEL); |
| if (!tve) { |
| ret = -ENOMEM; |
| goto alloc_failed; |
| } |
| |
| tve->pdev = pdev; |
| ret = tve_dispdrv_init(tve); |
| if (ret < 0) |
| goto dispdrv_init_failed; |
| |
| dev_set_drvdata(&pdev->dev, tve); |
| |
| return 0; |
| |
| dispdrv_init_failed: |
| kfree(tve); |
| alloc_failed: |
| return ret; |
| } |
| |
| static int tve_remove(struct platform_device *pdev) |
| { |
| struct tve_data *tve = dev_get_drvdata(&pdev->dev); |
| |
| tve_dispdrv_deinit(tve); |
| kfree(tve); |
| return 0; |
| } |
| |
| static struct platform_driver tve_driver = { |
| .driver = { |
| .name = "mxc_tve", |
| }, |
| .probe = tve_probe, |
| .remove = tve_remove, |
| }; |
| |
| static int __init tve_init(void) |
| { |
| return platform_driver_register(&tve_driver); |
| } |
| |
| static void __exit tve_exit(void) |
| { |
| platform_driver_unregister(&tve_driver); |
| } |
| |
| module_init(tve_init); |
| module_exit(tve_exit); |
| |
| MODULE_AUTHOR("Freescale Semiconductor, Inc."); |
| MODULE_DESCRIPTION("i.MX TV encoder driver"); |
| MODULE_LICENSE("GPL"); |