summaryrefslogtreecommitdiff
path: root/drivers/video
diff options
context:
space:
mode:
authorRob Herring <r.herring@freescale.com>2008-03-16 15:18:19 -0500
committerDaniel Schaeffer <daniel.schaeffer@timesys.com>2008-08-25 15:20:48 -0400
commit38f892a34aa0bb6fe62add4c21bc55d4106ebb0b (patch)
tree2cb25111bcfef2d7476a2ea9057cbd0461d8874e /drivers/video
parent5f7957c83288d2fc71ccf2527cddb5528e8d5af1 (diff)
ENGR00068852 Add TVE driver
Add driver for TVE TV encoder module on MX37. Signed-off-by: Rob Herring <r.herring@freescale.com>
Diffstat (limited to 'drivers/video')
-rw-r--r--drivers/video/mxc/Kconfig5
-rw-r--r--drivers/video/mxc/Makefile1
-rw-r--r--drivers/video/mxc/tve.c414
3 files changed, 420 insertions, 0 deletions
diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig
index c7c28d81ca0f..aeaf4ba914d2 100644
--- a/drivers/video/mxc/Kconfig
+++ b/drivers/video/mxc/Kconfig
@@ -23,6 +23,11 @@ config FB_MXC_EPSON_VGA_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_TVOUT
bool "FS453 TV Out Encoder"
depends on FB_MXC_SYNC_PANEL
diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile
index ef64833743de..0799d99787d8 100644
--- a/drivers/video/mxc/Makefile
+++ b/drivers/video/mxc/Makefile
@@ -16,4 +16,5 @@ endif
endif
obj-$(CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL) += mxcfb_epson_vga.o
obj-$(CONFIG_FB_MXC_TVOUT_CH7024) += ch7024.o
+obj-$(CONFIG_FB_MXC_TVOUT_TVE) += tve.o
diff --git a/drivers/video/mxc/tve.c b/drivers/video/mxc/tve.c
new file mode 100644
index 000000000000..44b340368fdb
--- /dev/null
+++ b/drivers/video/mxc/tve.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2008 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/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/mxcfb.h>
+
+#define TVE_COM_CONF_REG 0
+#define TVE_CD_CONT_REG 0x14
+#define TVE_INT_CONT_REG 0x28
+#define TVE_STAT_REG 0x2C
+#define TVE_MV_CONT_REG 0x48
+
+#define CD_EN 0x00000001
+#define CD_TRIG_MODE 0x00000002
+
+#define CD_LM_INT 0x00000001
+#define CD_SM_INT 0x00000002
+#define CD_MON_END_INT 0x00000004
+#define CD_MAN_TRIG 0x00010000
+
+#define TVOUT_FMT_OFF 0
+#define TVOUT_FMT_NTSC 1
+#define TVOUT_FMT_PAL 2
+
+static int enabled; /* enable power on or not */
+
+static struct fb_info *tve_fbi;
+
+struct tve_data {
+ struct platform_device *pdev;
+ int cur_mode;
+ int detect;
+ void *base;
+ int irq;
+ struct clk *clk;
+} tve;
+
+static struct fb_videomode video_modes[] = {
+ {
+ /* NTSC TV output */
+ "TV-NTSC", 60, 720, 480, 74074,
+ 121, 16,
+ 17, 5,
+ 1, 1,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT | FB_SYNC_OE_ACT_HIGH |
+ FB_SYNC_EXT,
+ FB_VMODE_INTERLACED,
+ 0,},
+ {
+ /* PAL TV output */
+ "TV-PAL", 50, 720, 576, 74074,
+ 131, 12,
+ 21, 3,
+ 1, 1,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT | FB_SYNC_OE_ACT_HIGH |
+ FB_SYNC_EXT,
+ FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST,
+ 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)
+{
+ if (tve.cur_mode == mode)
+ return 0;
+
+ tve.cur_mode = mode;
+
+ /* select output video format */
+ if (mode == TVOUT_FMT_PAL) {
+ __raw_writel(0x00840328, tve.base + TVE_COM_CONF_REG);
+ pr_debug("TVE: change to PAL video\n");
+ } else if (mode == TVOUT_FMT_NTSC) {
+ __raw_writel(0x00840028, tve.base + TVE_COM_CONF_REG);
+ pr_debug("TVE: change to NTSC video\n");
+ } else if (mode == TVOUT_FMT_OFF) {
+ __raw_writel(0x0, tve.base + TVE_COM_CONF_REG);
+ } else {
+ pr_debug("TVE: no such video format.\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * tve_enable
+ * Enable the tve Power to begin TV encoder
+ */
+static void tve_enable(void)
+{
+ u32 reg;
+
+ if (!enabled) {
+ enabled = 1;
+ reg = __raw_readl(tve.base + TVE_COM_CONF_REG);
+ __raw_writel(reg | 0x01, tve.base + TVE_COM_CONF_REG);
+ pr_debug("TVE power on.\n");
+ }
+}
+
+/**
+ * tve_disable
+ * Disable the tve Power to stop TV encoder
+ */
+static void tve_disable(void)
+{
+ u32 reg;
+
+ if (enabled) {
+ enabled = 0;
+ reg = __raw_readl(tve.base + TVE_COM_CONF_REG);
+ __raw_writel(reg & ~0x01, tve.base + TVE_COM_CONF_REG);
+ pr_debug("TVE power off.\n");
+ }
+}
+
+static int tve_update_detect_status(void)
+{
+ int old_detect = tve.detect;
+ u32 stat = __raw_readl(tve.base + TVE_STAT_REG);
+
+ if ((stat & CD_MON_END_INT) == 0)
+ return tve.detect;
+
+ if (stat & CD_LM_INT) {
+ if (stat & CD_SM_INT)
+ tve.detect = 2;
+ else
+ tve.detect = 1;
+ } else {
+ tve.detect = 0;
+ }
+
+ __raw_writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT,
+ tve.base + TVE_STAT_REG);
+
+ if (old_detect != tve.detect)
+ sysfs_notify(&tve.pdev->dev.driver->kobj, NULL, "headphone");
+
+ dev_dbg(&tve.pdev->dev, "detect = %d\n", tve.detect);
+ return tve.detect;
+}
+
+static int tve_man_detect(void)
+{
+ u32 cd_cont;
+ u32 int_cont;
+
+ int_cont = __raw_readl(tve.base + TVE_INT_CONT_REG);
+ __raw_writel(int_cont & ~(CD_SM_INT | CD_LM_INT),
+ tve.base + TVE_INT_CONT_REG);
+
+ cd_cont = __raw_readl(tve.base + TVE_CD_CONT_REG);
+ __raw_writel(cd_cont | CD_TRIG_MODE, tve.base + TVE_CD_CONT_REG);
+
+ __raw_writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT | CD_MAN_TRIG,
+ tve.base + TVE_STAT_REG);
+
+ while ((__raw_readl(tve.base + TVE_STAT_REG) & CD_MON_END_INT) == 0)
+ msleep(5);
+
+ tve_update_detect_status();
+
+ __raw_writel(cd_cont, tve.base + TVE_CD_CONT_REG);
+ __raw_writel(int_cont, tve.base + TVE_INT_CONT_REG);
+
+ return tve.detect;
+}
+
+static irqreturn_t tve_detect_handler(int irq, void *data)
+{
+ u32 stat;
+ int old_detect = tve.detect;
+
+ stat = __raw_readl(tve.base + TVE_STAT_REG);
+ stat &= __raw_readl(tve.base + TVE_INT_CONT_REG);
+
+ tve_update_detect_status();
+
+ __raw_writel(stat | CD_MON_END_INT, tve.base + TVE_STAT_REG);
+
+ if (old_detect != tve.detect)
+ sysfs_notify(&tve.pdev->dev.driver->kobj, NULL, "headphone");
+
+ return IRQ_HANDLED;
+}
+
+int tve_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:
+ pr_debug("fb registered event\n");
+ if ((tve_fbi != NULL) || strcmp(fbi->fix.id, "DISP3 BG - DI1"))
+ break;
+
+ tve_fbi = fbi;
+ fb_add_videomode(&video_modes[0], &tve_fbi->modelist);
+ fb_add_videomode(&video_modes[1], &tve_fbi->modelist);
+ break;
+ case FB_EVENT_MODE_CHANGE:
+ if (tve_fbi != fbi)
+ break;
+
+ if (!fbi->mode) {
+ tve_disable();
+ tve.cur_mode = TVOUT_FMT_OFF;
+ return 0;
+ }
+
+ pr_debug("fb mode change event: xres=%d, yres=%d\n",
+ fbi->mode->xres, fbi->mode->yres);
+
+ tve_disable();
+
+ if (fb_mode_is_equal(fbi->mode, &video_modes[0])) {
+ tve_setup(TVOUT_FMT_NTSC);
+ tve_enable();
+ } else if (fb_mode_is_equal(fbi->mode, &video_modes[1])) {
+ tve_setup(TVOUT_FMT_PAL);
+ tve_enable();
+ } else {
+ tve_setup(TVOUT_FMT_OFF);
+ }
+ break;
+ case FB_EVENT_BLANK:
+ if ((tve_fbi != fbi) || (tve.cur_mode == TVOUT_FMT_OFF))
+ return 0;
+
+ if (*((int *)event->data) == FB_BLANK_UNBLANK)
+ tve_enable();
+ else
+ tve_disable();
+ break;
+ }
+ return 0;
+}
+
+static struct notifier_block nb = {
+ .notifier_call = tve_fb_event,
+};
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+ int detect;
+
+ detect = tve_update_detect_status();
+
+ 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 int tve_probe(struct platform_device *pdev)
+{
+ int ret, i;
+ struct resource *res;
+
+ 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 = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+ if (ret < 0)
+ goto err1;
+
+ for (i = 0; i < num_registered_fb; i++) {
+ if (strcmp(registered_fb[i]->fix.id, "DISP3 BG - DI1") == 0) {
+ tve_fbi = registered_fb[i];
+ break;
+ }
+ }
+ if (tve_fbi != NULL) {
+ fb_add_videomode(&video_modes[0], &tve_fbi->modelist);
+ fb_add_videomode(&video_modes[1], &tve_fbi->modelist);
+ }
+
+ tve.clk = clk_get(&pdev->dev, "tve_clk");
+ clk_set_rate(tve.clk, 216000000);
+ clk_enable(tve.clk);
+
+ /* Setup cable detect */
+ __raw_writel(0x010777F1, tve.base + TVE_CD_CONT_REG);
+ /* tve_man_detect(); not working */
+
+ __raw_writel(CD_SM_INT | CD_LM_INT, tve.base + TVE_STAT_REG);
+ __raw_writel(CD_SM_INT | CD_LM_INT, tve.base + TVE_INT_CONT_REG);
+
+ __raw_writel(0x00000002, tve.base + TVE_MV_CONT_REG);
+
+ ret = fb_register_client(&nb);
+ if (ret < 0)
+ goto err2;
+
+ return 0;
+err2:
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+err1:
+ free_irq(tve.irq, pdev);
+err0:
+ iounmap(tve.base);
+ return ret;
+}
+
+static int tve_remove(struct platform_device *pdev)
+{
+ free_irq(tve.irq, pdev);
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+ fb_unregister_client(&nb);
+ return 0;
+}
+
+/*!
+ * PM suspend/resume routing
+ */
+static int tve_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ __raw_writel(0, tve.base + TVE_INT_CONT_REG);
+ __raw_writel(0, tve.base + TVE_CD_CONT_REG);
+ __raw_writel(0, tve.base + TVE_COM_CONF_REG);
+ return 0;
+}
+
+static int tve_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver tve_driver = {
+ .driver = {
+ .name = "tve",
+ },
+ .probe = tve_probe,
+ .remove = tve_remove,
+ .suspend = tve_suspend,
+ .resume = tve_resume,
+};
+
+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");