aboutsummaryrefslogtreecommitdiff
path: root/drivers/video
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video')
-rw-r--r--drivers/video/Kconfig4
-rw-r--r--drivers/video/Makefile1
-rw-r--r--drivers/video/backlight/Kconfig6
-rw-r--r--drivers/video/backlight/Makefile1
-rw-r--r--drivers/video/backlight/da9052_bl.c464
-rw-r--r--drivers/video/fbmem.c11
-rw-r--r--drivers/video/mxc/Kconfig34
-rw-r--r--drivers/video/mxc/Makefile5
-rw-r--r--drivers/video/mxc/ldb.c646
-rw-r--r--drivers/video/mxc/mxc_dispdrv.c152
-rw-r--r--drivers/video/mxc/mxc_dispdrv.h43
-rw-r--r--drivers/video/mxc/mxc_edid.c451
-rw-r--r--drivers/video/mxc/mxc_edid.h48
-rw-r--r--drivers/video/mxc/mxc_ipuv3_fb.c2072
-rw-r--r--drivers/video/mxc/mxc_lcdif.c144
-rw-r--r--drivers/video/mxc/mxcfb_sii902x.c1307
-rw-r--r--drivers/video/mxc/tve.c1290
17 files changed, 6679 insertions, 0 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index d83e967e4e1..de5806bb9ba 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2415,6 +2415,10 @@ source "drivers/video/omap2/Kconfig"
source "drivers/video/backlight/Kconfig"
source "drivers/video/display/Kconfig"
+if ARCH_MXC
+source "drivers/video/mxc/Kconfig"
+endif
+
if VT
source "drivers/video/console/Kconfig"
endif
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 9b9d8fff773..bc77579e8de 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_FB_KYRO) += kyro/
obj-$(CONFIG_FB_SAVAGE) += savage/
obj-$(CONFIG_FB_GEODE) += geode/
obj-$(CONFIG_FB_MBX) += mbx/
+obj-$(CONFIG_FB_MXC) += mxc/
obj-$(CONFIG_FB_NEOMAGIC) += neofb.o
obj-$(CONFIG_FB_3DFX) += tdfxfb.o
obj-$(CONFIG_FB_CONTROL) += controlfb.o
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 278aeaa9250..cc795c014e0 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -342,6 +342,12 @@ config BACKLIGHT_AAT2870
If you have a AnalogicTech AAT2870 say Y to enable the
backlight driver.
+config BACKLIGHT_DA9052
+ tristate "Dialog DA9052 WLED"
+ depends on PMIC_DIALOG
+ help
+ Enable the DA9052 Backlight Driver
+
endif # BACKLIGHT_CLASS_DEVICE
endif # BACKLIGHT_LCD_SUPPORT
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index fdd1fc4b277..aea6797661d 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -39,4 +39,5 @@ obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o
obj-$(CONFIG_BACKLIGHT_88PM860X) += 88pm860x_bl.o
obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o
obj-$(CONFIG_BACKLIGHT_AAT2870) += aat2870_bl.o
+obj-$(CONFIG_BACKLIGHT_DA9052) += da9052_bl.o
diff --git a/drivers/video/backlight/da9052_bl.c b/drivers/video/backlight/da9052_bl.c
new file mode 100644
index 00000000000..68fac25d1b1
--- /dev/null
+++ b/drivers/video/backlight/da9052_bl.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright(c) 2009 Dialog Semiconductor Ltd.
+ *
+ * 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.
+ *
+ * da9052_bl.c: Backlight driver for DA9052
+ */
+
+#include <linux/platform_device.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+
+#include <linux/delay.h>
+
+#include <linux/mfd/da9052/da9052.h>
+#include <linux/mfd/da9052/reg.h>
+#include <linux/mfd/da9052/bl.h>
+
+
+#define DRIVER_NAME "da9052-backlight"
+#define DRIVER_NAME1 "WLED-1"
+#define DRIVER_NAME2 "WLED-2"
+#define DRIVER_NAME3 "WLED-3"
+
+/* These flags define if Backlight LEDs are present */
+/* Set the following macros to 1, if LEDs are present. Otherwise set to 0 */
+#define DA9052_LED1_PRESENT 1
+#define DA9052_LED2_PRESENT 1
+#define DA9052_LED3_PRESENT 1
+
+#define DA9052_MAX_BRIGHTNESS 0xFF
+
+struct da9052_backlight_data {
+ struct device *da9052_dev;
+ int current_brightness;
+ struct da9052 *da9052;
+
+ int is_led1_present;
+ int is_led2_present;
+ int is_led3_present;
+};
+
+enum da9052_led_number {
+ LED1 = 1,
+ LED2,
+ LED3,
+};
+
+static int da9052_backlight_brightness_set(struct da9052_backlight_data *data,
+ int brightness, enum da9052_led_number led)
+{
+ /*
+ * Mechanism for brightness control:
+ * For brightness control, current is used.
+ * PWM feature is not used.
+ * To use PWM feature, a fixed value of current should be defined.
+ */
+
+ int ret = 0;
+ unsigned int led_ramp_bit;
+ unsigned int led_current_register;
+ unsigned int led_current_sink_bit;
+ unsigned int led_boost_en_bit;
+ struct da9052_ssc_msg msg;
+
+ switch (led) {
+ case LED1:
+ led_ramp_bit = DA9052_LEDCONT_LED1RAMP;
+ led_current_register = DA9052_LED1CONF_REG;
+ led_current_sink_bit = DA9052_LEDCONT_LED1EN;
+ led_boost_en_bit = DA9052_BOOST_LED1INEN;
+ break;
+ case LED2:
+ led_ramp_bit = DA9052_LEDCONT_LED2RAMP;
+ led_current_register = DA9052_LED2CONF_REG;
+ led_current_sink_bit = DA9052_LEDCONT_LED2EN;
+ led_boost_en_bit = DA9052_BOOST_LED2INEN;
+ break;
+ case LED3:
+ led_ramp_bit = DA9052_LEDCONT_LED3RAMP;
+ led_current_register = DA9052_LED3CONF_REG;
+ led_current_sink_bit = DA9052_LEDCONT_LED3EN;
+ led_boost_en_bit = DA9052_BOOST_LED3INEN;
+ break;
+ default:
+ return -EIO;
+ }
+
+ /*
+ 1. Configure the boost register
+ 2. Configure the LED _CONT register
+ 3. Configure the LEDx_CONF registers to the brightness value.
+ */
+ msg.addr = DA9052_BOOST_REG;
+ msg.data = 0x3F;
+ if (brightness) {
+ da9052_lock(data->da9052);
+ ret = data->da9052->write(data->da9052, &msg);
+ if (ret) {
+ da9052_unlock(data->da9052);
+ return ret;
+ }
+ da9052_unlock(data->da9052);
+ }
+
+ msg.addr = DA9052_LEDCONT_REG;
+ msg.data = 0xFF;
+ if (brightness) {
+ da9052_lock(data->da9052);
+ ret = data->da9052->write(data->da9052, &msg);
+ if (ret) {
+ da9052_unlock(data->da9052);
+ return ret;
+ }
+ da9052_unlock(data->da9052);
+ }
+
+ msg.addr = led_current_register;
+ msg.data = 0;
+ /* Write to the DA9052 register */
+ da9052_lock(data->da9052);
+ ret = data->da9052->write(data->da9052, &msg);
+ if (ret) {
+ da9052_unlock(data->da9052);
+ return ret;
+ }
+ da9052_unlock(data->da9052);
+ msleep(20);
+ msg.data = brightness;
+ /* Write to the DA9052 register */
+ da9052_lock(data->da9052);
+ ret = data->da9052->write(data->da9052, &msg);
+ if (ret) {
+ da9052_unlock(data->da9052);
+ return ret;
+ }
+ da9052_unlock(data->da9052);
+
+ return 0;
+}
+
+static int da9052_backlight_set(struct backlight_device *bl, int brightness)
+{
+ struct da9052_backlight_data *data = bl_get_data(bl);
+ int ret = 0;
+ /* Check for LED1 */
+ if (1 == data->is_led1_present) {
+ ret = da9052_backlight_brightness_set(data, brightness, LED1);
+ if (ret)
+ return ret;
+ }
+ /* Check for LED2 */
+ if (1 == data->is_led2_present) {
+ ret = da9052_backlight_brightness_set(data, brightness, LED2);
+ if (ret)
+ return ret;
+ }
+ /* Check for LED3 */
+ if (1 == data->is_led3_present) {
+ ret = da9052_backlight_brightness_set(data, brightness, LED3);
+ if (ret)
+ return ret;
+ }
+
+ data->current_brightness = brightness;
+ return 0;
+}
+
+static int da9052_init_WLED(struct da9052_backlight_data *data,
+ enum da9052_led_number led)
+{
+ int ret = 0;
+ unsigned int led_current_register;
+ struct da9052_ssc_msg msg;
+
+ switch (led) {
+ case LED1:
+ led_current_register = DA9052_LED1CONF_REG;
+ break;
+ case LED2:
+ led_current_register = DA9052_LED2CONF_REG;
+ break;
+ case LED3:
+ led_current_register = DA9052_LED3CONF_REG;
+ break;
+ default:
+ return -EIO;
+ }
+
+ msg.addr = DA9052_BOOST_REG;
+ msg.data = 0x00;
+ da9052_lock(data->da9052);
+ ret = data->da9052->write(data->da9052, &msg);
+ if (ret) {
+ da9052_unlock(data->da9052);
+ return ret;
+ }
+ da9052_unlock(data->da9052);
+
+ msg.addr = DA9052_LEDCONT_REG;
+ msg.data = 0x00;
+ da9052_lock(data->da9052);
+ ret = data->da9052->write(data->da9052, &msg);
+ if (ret) {
+ da9052_unlock(data->da9052);
+ return ret;
+ }
+ da9052_unlock(data->da9052);
+
+ msg.addr = led_current_register;
+ msg.data = 0;
+ da9052_lock(data->da9052);
+ ret = data->da9052->write(data->da9052, &msg);
+ da9052_unlock(data->da9052);
+ return ret;
+}
+
+static int da9052_backlight_update_status(struct backlight_device *bl)
+{
+ int brightness = bl->props.brightness;
+
+ if (bl->props.power != FB_BLANK_UNBLANK)
+ brightness = 0;
+
+ if (bl->props.fb_blank != FB_BLANK_UNBLANK)
+ brightness = 0;
+ return da9052_backlight_set(bl, brightness);
+}
+
+static int da9052_backlight_get_brightness(struct backlight_device *bl)
+{
+ struct da9052_backlight_data *data = bl_get_data(bl);
+ return data->current_brightness;
+}
+
+const struct backlight_ops da9052_backlight_ops = {
+ .update_status = da9052_backlight_update_status,
+ .get_brightness = da9052_backlight_get_brightness,
+};
+
+static int da9052_backlight_probe1(struct platform_device *pdev)
+{
+ struct da9052_backlight_data *data;
+ struct backlight_device *bl;
+ struct backlight_properties props;
+ int ret = 0;
+ struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent);
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+ data->da9052_dev = pdev->dev.parent;
+ data->da9052 = da9052;
+ data->current_brightness = 0;
+ data->is_led1_present = DA9052_LED1_PRESENT;
+
+ /* Init the WLED-1 bank */
+ ret = da9052_init_WLED(data, LED1);
+ if (ret)
+ return ret;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.max_brightness = DA9052_MAX_BRIGHTNESS;
+ props.type = BACKLIGHT_RAW;
+ bl = backlight_device_register(pdev->name, data->da9052_dev,
+ data, &da9052_backlight_ops, &props);
+ if (IS_ERR(bl)) {
+ dev_err(&pdev->dev, "failed to register backlight\n");
+ kfree(data);
+ return PTR_ERR(bl);
+ }
+
+ bl->props.brightness = 0;
+
+ platform_set_drvdata(pdev, bl);
+ backlight_update_status(bl);
+
+ return 0;
+}
+static int da9052_backlight_probe2(struct platform_device *pdev)
+{
+ struct da9052_backlight_data *data;
+ struct backlight_device *bl;
+ struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent);
+ struct backlight_properties props;
+ int ret = 0;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+ data->da9052_dev = pdev->dev.parent;
+ data->da9052 = da9052;
+ data->current_brightness = 0;
+ data->is_led2_present = DA9052_LED2_PRESENT;
+
+ /* Init the WLED-2 bank */
+ ret = da9052_init_WLED(data, LED2);
+ if (ret)
+ return ret;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.max_brightness = DA9052_MAX_BRIGHTNESS;
+ props.type = BACKLIGHT_RAW;
+ bl = backlight_device_register(pdev->name, data->da9052_dev,
+ data, &da9052_backlight_ops, &props);
+ if (IS_ERR(bl)) {
+ dev_err(&pdev->dev, "failed to register backlight\n");
+ kfree(data);
+ return PTR_ERR(bl);
+ }
+
+ bl->props.brightness = 0;
+
+ platform_set_drvdata(pdev, bl);
+ backlight_update_status(bl);
+
+ return 0;
+}
+static int da9052_backlight_probe3(struct platform_device *pdev)
+{
+ struct da9052_backlight_data *data;
+ struct backlight_device *bl;
+ struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent);
+ struct backlight_properties props;
+ int ret = 0;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+ data->da9052_dev = pdev->dev.parent;
+ data->da9052 = da9052;
+ data->current_brightness = 0;
+ data->is_led3_present = DA9052_LED3_PRESENT;
+
+ /* Init the WLED-2 bank */
+ ret = da9052_init_WLED(data, LED3);
+ if (ret)
+ return ret;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.max_brightness = DA9052_MAX_BRIGHTNESS;
+ props.type = BACKLIGHT_RAW;
+ bl = backlight_device_register(pdev->name, data->da9052_dev,
+ data, &da9052_backlight_ops, &props);
+ if (IS_ERR(bl)) {
+ dev_err(&pdev->dev, "failed to register backlight\n");
+ kfree(data);
+ return PTR_ERR(bl);
+ }
+
+ bl->props.brightness = 0;
+
+ platform_set_drvdata(pdev, bl);
+
+ backlight_update_status(bl);
+ return 0;
+}
+
+static int da9052_backlight_remove1(struct platform_device *pdev)
+{
+ struct backlight_device *bl = platform_get_drvdata(pdev);
+ struct da9052_backlight_data *data = bl_get_data(bl);
+ int ret = 0;
+
+ /* Switch off the WLED-1 */
+ ret = da9052_init_WLED(data, LED1);
+ if (ret)
+ return ret;
+
+ backlight_device_unregister(bl);
+ kfree(data);
+ return 0;
+}
+
+static int da9052_backlight_remove2(struct platform_device *pdev)
+{
+ struct backlight_device *bl = platform_get_drvdata(pdev);
+ struct da9052_backlight_data *data = bl_get_data(bl);
+ int ret = 0;
+
+ /* Switch off the WLED-2 */
+ ret = da9052_init_WLED(data, LED2);
+ if (ret)
+ return ret;
+
+ backlight_device_unregister(bl);
+ kfree(data);
+ return 0;
+}
+static int da9052_backlight_remove3(struct platform_device *pdev)
+{
+ struct backlight_device *bl = platform_get_drvdata(pdev);
+ struct da9052_backlight_data *data = bl_get_data(bl);
+ int ret;
+
+ /* Switch off the WLED-3 */
+ ret = da9052_init_WLED(data, LED3);
+ if (ret)
+ return ret;
+
+ backlight_device_unregister(bl);
+ kfree(data);
+ return 0;
+}
+
+static struct platform_driver da9052_backlight_driver1 = {
+ .driver = {
+ .name = DRIVER_NAME1,
+ .owner = THIS_MODULE,
+ },
+ .probe = da9052_backlight_probe1,
+ .remove = da9052_backlight_remove1,
+};
+static struct platform_driver da9052_backlight_driver2 = {
+ .driver = {
+ .name = DRIVER_NAME2,
+ .owner = THIS_MODULE,
+ },
+ .probe = da9052_backlight_probe2,
+ .remove = da9052_backlight_remove2,
+};
+static struct platform_driver da9052_backlight_driver3 = {
+ .driver = {
+ .name = DRIVER_NAME3,
+ .owner = THIS_MODULE,
+ },
+ .probe = da9052_backlight_probe3,
+ .remove = da9052_backlight_remove3,
+};
+
+static int __init da9052_backlight_init(void)
+{
+ s32 ret;
+ ret = platform_driver_register(&da9052_backlight_driver1);
+ if (ret)
+ return ret;
+ ret = platform_driver_register(&da9052_backlight_driver2);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&da9052_backlight_driver3);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+module_init(da9052_backlight_init);
+
+static void __exit da9052_backlight_exit(void)
+{
+ platform_driver_unregister(&da9052_backlight_driver1);
+ platform_driver_unregister(&da9052_backlight_driver2);
+ platform_driver_unregister(&da9052_backlight_driver3);
+}
+module_exit(da9052_backlight_exit);
+
+MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>");
+MODULE_DESCRIPTION("Backlight driver for Dialog DA9052 PMIC");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+
diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c
index ad936295d8f..a65f5b57391 100644
--- a/drivers/video/fbmem.c
+++ b/drivers/video/fbmem.c
@@ -991,6 +991,17 @@ fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
old_var = info->var;
info->var = *var;
+ /* call pre-mode change */
+ if (flags & FBINFO_MISC_USEREVENT) {
+ struct fb_event event;
+ int evnt = FB_EVENT_PREMODE_CHANGE;
+
+ info->flags &= ~FBINFO_MISC_USEREVENT;
+ event.info = info;
+ event.data = &mode;
+ fb_notifier_call_chain(evnt, &event);
+ }
+
if (info->fbops->fb_set_par) {
ret = info->fbops->fb_set_par(info);
diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig
new file mode 100644
index 00000000000..bb836c3a63f
--- /dev/null
+++ b/drivers/video/mxc/Kconfig
@@ -0,0 +1,34 @@
+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_TVOUT_TVE
+ tristate "MXC TVE TV Out Encoder"
+ depends on FB_MXC_SYNC_PANEL
+ depends on MXC_IPU_V3
+
+config FB_MXC_SII902X
+ depends on FB_MXC_SYNC_PANEL
+ tristate "Si Image SII9022 DVI/HDMI Interface Chip"
+
+config FB_MXC_LDB
+ tristate "MXC LDB"
+ depends on FB_MXC_SYNC_PANEL
+ depends on MXC_IPU_V3
diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile
new file mode 100644
index 00000000000..4c75e1be2d3
--- /dev/null
+++ b/drivers/video/mxc/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_FB_MXC_TVOUT_TVE) += tve.o
+obj-$(CONFIG_FB_MXC_SII902X) += mxcfb_sii902x.o
+obj-$(CONFIG_FB_MXC_LDB) += ldb.o
+obj-$(CONFIG_FB_MODE_HELPERS) += mxc_edid.o
+obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_dispdrv.o mxc_lcdif.o mxc_ipuv3_fb.o
diff --git a/drivers/video/mxc/ldb.c b/drivers/video/mxc/ldb.c
new file mode 100644
index 00000000000..180bf7f6f8d
--- /dev/null
+++ b/drivers/video/mxc/ldb.c
@@ -0,0 +1,646 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*!
+ * @file mxc_ldb.c
+ *
+ * @brief This file contains the LDB driver device interface and fops
+ * functions.
+ */
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/io.h>
+#include <linux/ipu.h>
+#include <linux/mxcfb.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spinlock.h>
+#include <linux/fsl_devices.h>
+#include <mach/hardware.h>
+#include <mach/clock.h>
+#include "mxc_dispdrv.h"
+
+#define DISPDRV_LDB "ldb"
+
+#define LDB_BGREF_RMODE_MASK 0x00008000
+#define LDB_BGREF_RMODE_INT 0x00008000
+#define LDB_BGREF_RMODE_EXT 0x0
+
+#define LDB_DI1_VS_POL_MASK 0x00000400
+#define LDB_DI1_VS_POL_ACT_LOW 0x00000400
+#define LDB_DI1_VS_POL_ACT_HIGH 0x0
+#define LDB_DI0_VS_POL_MASK 0x00000200
+#define LDB_DI0_VS_POL_ACT_LOW 0x00000200
+#define LDB_DI0_VS_POL_ACT_HIGH 0x0
+
+#define LDB_BIT_MAP_CH1_MASK 0x00000100
+#define LDB_BIT_MAP_CH1_JEIDA 0x00000100
+#define LDB_BIT_MAP_CH1_SPWG 0x0
+#define LDB_BIT_MAP_CH0_MASK 0x00000040
+#define LDB_BIT_MAP_CH0_JEIDA 0x00000040
+#define LDB_BIT_MAP_CH0_SPWG 0x0
+
+#define LDB_DATA_WIDTH_CH1_MASK 0x00000080
+#define LDB_DATA_WIDTH_CH1_24 0x00000080
+#define LDB_DATA_WIDTH_CH1_18 0x0
+#define LDB_DATA_WIDTH_CH0_MASK 0x00000020
+#define LDB_DATA_WIDTH_CH0_24 0x00000020
+#define LDB_DATA_WIDTH_CH0_18 0x0
+
+#define LDB_CH1_MODE_MASK 0x0000000C
+#define LDB_CH1_MODE_EN_TO_DI1 0x0000000C
+#define LDB_CH1_MODE_EN_TO_DI0 0x00000004
+#define LDB_CH1_MODE_DISABLE 0x0
+#define LDB_CH0_MODE_MASK 0x00000003
+#define LDB_CH0_MODE_EN_TO_DI1 0x00000003
+#define LDB_CH0_MODE_EN_TO_DI0 0x00000001
+#define LDB_CH0_MODE_DISABLE 0x0
+
+#define LDB_SPLIT_MODE_EN 0x00000010
+
+struct ldb_data {
+ struct platform_device *pdev;
+ struct mxc_dispdrv_entry *disp_ldb;
+ uint32_t *reg;
+ uint32_t *control_reg;
+ uint32_t *gpr3_reg;
+ struct regulator *lvds_bg_reg;
+ int mode;
+ bool inited;
+ struct clk *di_clk[2];
+ struct clk *ldb_di_clk[2];
+ struct ldb_setting {
+ bool active;
+ bool clk_en;
+ int ipu;
+ int di;
+ } setting[2];
+ struct notifier_block nb;
+};
+
+static int g_ldb_mode;
+
+static struct fb_videomode ldb_modedb[] = {
+ {
+ "LDB-XGA", 60, 1024, 768, 15385,
+ 220, 40,
+ 21, 7,
+ 60, 10,
+ 0,
+ FB_VMODE_NONINTERLACED,
+ FB_MODE_IS_DETAILED,},
+ {
+ "LDB-1080P60", 60, 1920, 1080, 7692,
+ 100, 40,
+ 30, 3,
+ 10, 2,
+ 0,
+ FB_VMODE_NONINTERLACED,
+ FB_MODE_IS_DETAILED,},
+};
+static int ldb_modedb_sz = ARRAY_SIZE(ldb_modedb);
+
+static int bits_per_pixel(int pixel_fmt)
+{
+ switch (pixel_fmt) {
+ case IPU_PIX_FMT_BGR24:
+ case IPU_PIX_FMT_RGB24:
+ return 24;
+ break;
+ case IPU_PIX_FMT_BGR666:
+ case IPU_PIX_FMT_RGB666:
+ case IPU_PIX_FMT_LVDS666:
+ return 18;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int valid_mode(int pixel_fmt)
+{
+ return ((pixel_fmt == IPU_PIX_FMT_RGB24) ||
+ (pixel_fmt == IPU_PIX_FMT_BGR24) ||
+ (pixel_fmt == IPU_PIX_FMT_LVDS666) ||
+ (pixel_fmt == IPU_PIX_FMT_RGB666) ||
+ (pixel_fmt == IPU_PIX_FMT_BGR666));
+}
+
+/*
+ * "ldb=spl0/1" -- split mode on DI0/1
+ * "ldb=dul0/1" -- dual mode on DI0/1
+ * "ldb=sin0/1" -- single mode on LVDS0/1
+ * "ldb=sep0/1" -- separate mode begin from LVDS0/1
+ *
+ * there are two LVDS channels(LVDS0 and LVDS1) which can transfer video
+ * datas, there two channels can be used as split/dual/single/separate mode.
+ *
+ * split mode means display data from DI0 or DI1 will send to both channels
+ * LVDS0+LVDS1.
+ * dual mode means display data from DI0 or DI1 will be duplicated on LVDS0
+ * and LVDS1, it said, LVDS0 and LVDS1 has the same content.
+ * single mode means only work for DI0/DI1->LVDS0 or DI0/DI1->LVDS1.
+ * separate mode means you can make DI0/DI1->LVDS0 and DI0/DI1->LVDS1 work
+ * at the same time.
+ */
+static int __init ldb_setup(char *options)
+{
+ if (!strcmp(options, "spl0"))
+ g_ldb_mode = LDB_SPL_DI0;
+ else if (!strcmp(options, "spl1"))
+ g_ldb_mode = LDB_SPL_DI1;
+ else if (!strcmp(options, "dul0"))
+ g_ldb_mode = LDB_DUL_DI0;
+ else if (!strcmp(options, "dul1"))
+ g_ldb_mode = LDB_DUL_DI1;
+ else if (!strcmp(options, "sin0"))
+ g_ldb_mode = LDB_SIN0;
+ else if (!strcmp(options, "sin1"))
+ g_ldb_mode = LDB_SIN1;
+ else if (!strcmp(options, "sep0"))
+ g_ldb_mode = LDB_SEP0;
+ else if (!strcmp(options, "sep1"))
+ g_ldb_mode = LDB_SEP1;
+
+ return 1;
+}
+__setup("ldb=", ldb_setup);
+
+static int find_ldb_setting(struct ldb_data *ldb, struct fb_info *fbi)
+{
+ char *id_di[] = {
+ "DISP3 BG",
+ "DISP3 BG - DI1",
+ };
+ char id[16];
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (ldb->setting[i].active) {
+ memset(id, 0, 16);
+ memcpy(id, id_di[ldb->setting[i].di],
+ strlen(id_di[ldb->setting[i].di]));
+ id[4] += ldb->setting[i].ipu;
+ if (!strcmp(id, fbi->fix.id))
+ return i;
+ }
+ }
+ return -EINVAL;
+}
+
+int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v)
+{
+ struct ldb_data *ldb = container_of(nb, struct ldb_data, nb);
+ struct fb_event *event = v;
+ struct fb_info *fbi = event->info;
+ int setting_idx, di;
+
+ setting_idx = find_ldb_setting(ldb, fbi);
+ if (setting_idx < 0)
+ return 0;
+
+ di = ldb->setting[setting_idx].di;
+
+ fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var,
+ &fbi->modelist);
+
+ if (!fbi->mode) {
+ dev_warn(&ldb->pdev->dev,
+ "LDB: can not find mode for xres=%d, yres=%d\n",
+ fbi->var.xres, fbi->var.yres);
+ if (ldb->setting[setting_idx].clk_en) {
+ clk_disable(ldb->ldb_di_clk[di]);
+ ldb->setting[setting_idx].clk_en = false;
+ }
+ return 0;
+ }
+
+ switch (val) {
+ case FB_EVENT_PREMODE_CHANGE:
+ {
+ uint32_t reg;
+ uint32_t pixel_clk, rounded_pixel_clk;
+ struct clk *ldb_clk_parent;
+
+ /* vsync setup */
+ reg = readl(ldb->control_reg);
+ if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) {
+ if (di == 0)
+ reg = (reg & ~LDB_DI0_VS_POL_MASK)
+ | LDB_DI0_VS_POL_ACT_HIGH;
+ else
+ reg = (reg & ~LDB_DI1_VS_POL_MASK)
+ | LDB_DI1_VS_POL_ACT_HIGH;
+ } else {
+ if (di == 0)
+ reg = (reg & ~LDB_DI0_VS_POL_MASK)
+ | LDB_DI0_VS_POL_ACT_LOW;
+ else
+ reg = (reg & ~LDB_DI1_VS_POL_MASK)
+ | LDB_DI1_VS_POL_ACT_LOW;
+ }
+ writel(reg, ldb->control_reg);
+
+ /* clk setup */
+ pixel_clk = (PICOS2KHZ(fbi->var.pixclock)) * 1000UL;
+ ldb_clk_parent = clk_get_parent(ldb->ldb_di_clk[di]);
+ if ((ldb->mode == LDB_SPL_DI0) || (ldb->mode == LDB_SPL_DI1))
+ clk_set_rate(ldb_clk_parent, pixel_clk * 7 / 2);
+ else
+ clk_set_rate(ldb_clk_parent, pixel_clk * 7);
+ rounded_pixel_clk = clk_round_rate(ldb->ldb_di_clk[di],
+ pixel_clk);
+ clk_set_rate(ldb->ldb_di_clk[di], rounded_pixel_clk);
+ clk_enable(ldb->ldb_di_clk[di]);
+ ldb->setting[setting_idx].clk_en = true;
+ break;
+ }
+ case FB_EVENT_BLANK:
+ {
+ if (*((int *)event->data) == FB_BLANK_UNBLANK) {
+ if (!ldb->setting[setting_idx].clk_en) {
+ clk_enable(ldb->ldb_di_clk[di]);
+ ldb->setting[setting_idx].clk_en = true;
+ }
+ } else {
+ if (ldb->setting[setting_idx].clk_en) {
+ clk_disable(ldb->ldb_di_clk[di]);
+ ldb->setting[setting_idx].clk_en = false;
+ }
+ }
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int ldb_disp_init(struct mxc_dispdrv_entry *disp)
+{
+ int ret = 0, i;
+ struct ldb_data *ldb = mxc_dispdrv_getdata(disp);
+ struct mxc_dispdrv_setting *setting = mxc_dispdrv_getsetting(disp);
+ struct fsl_mxc_ldb_platform_data *plat_data = ldb->pdev->dev.platform_data;
+ struct resource *res;
+ uint32_t base_addr;
+ uint32_t reg, setting_idx;
+
+ /* if input format not valid, make RGB666 as default*/
+ if (!valid_mode(setting->if_fmt)) {
+ dev_warn(&ldb->pdev->dev, "Input pixel format not valid"
+ " use default RGB666\n");
+ setting->if_fmt = IPU_PIX_FMT_RGB666;
+ }
+
+ if (!ldb->inited) {
+ char di_clk[] = "ipu1_di0_clk";
+ char ldb_clk[] = "ldb_di0_clk";
+ int lvds_channel = 0;
+
+ res = platform_get_resource(ldb->pdev, IORESOURCE_MEM, 0);
+ if (IS_ERR(res))
+ return -ENOMEM;
+
+ base_addr = res->start;
+ ldb->reg = ioremap(base_addr, res->end - res->start + 1);
+ ldb->control_reg = ldb->reg + 2;
+ ldb->gpr3_reg = ldb->reg + 3;
+
+ ldb->lvds_bg_reg = regulator_get(&ldb->pdev->dev, plat_data->lvds_bg_reg);
+ if (!IS_ERR(ldb->lvds_bg_reg)) {
+ regulator_set_voltage(ldb->lvds_bg_reg, 2500000, 2500000);
+ regulator_enable(ldb->lvds_bg_reg);
+ }
+
+ /* ipu selected by platform data setting */
+ setting->dev_id = plat_data->ipu_id;
+
+ reg = readl(ldb->control_reg);
+
+ /* refrence resistor select */
+ reg &= ~LDB_BGREF_RMODE_MASK;
+ if (plat_data->ext_ref)
+ reg |= LDB_BGREF_RMODE_EXT;
+ else
+ reg |= LDB_BGREF_RMODE_INT;
+
+ /* TODO: now only use SPWG data mapping for both channel */
+ reg &= ~(LDB_BIT_MAP_CH0_MASK | LDB_BIT_MAP_CH1_MASK);
+ reg |= LDB_BIT_MAP_CH0_SPWG | LDB_BIT_MAP_CH1_SPWG;
+
+ /* channel mode setting */
+ reg &= ~(LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK);
+ reg &= ~(LDB_DATA_WIDTH_CH0_MASK | LDB_DATA_WIDTH_CH1_MASK);
+
+ if (bits_per_pixel(setting->if_fmt) == 24)
+ reg |= LDB_DATA_WIDTH_CH0_24 | LDB_DATA_WIDTH_CH1_24;
+ else
+ reg |= LDB_DATA_WIDTH_CH0_18 | LDB_DATA_WIDTH_CH1_18;
+
+ if (g_ldb_mode)
+ ldb->mode = g_ldb_mode;
+ else
+ ldb->mode = plat_data->mode;
+
+ if (ldb->mode == LDB_SPL_DI0) {
+ reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI0
+ | LDB_CH1_MODE_EN_TO_DI0;
+ setting->disp_id = 0;
+ } else if (ldb->mode == LDB_SPL_DI1) {
+ reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI1
+ | LDB_CH1_MODE_EN_TO_DI1;
+ setting->disp_id = 1;
+ } else if (ldb->mode == LDB_DUL_DI0) {
+ reg &= ~LDB_SPLIT_MODE_EN;
+ reg |= LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0;
+ setting->disp_id = 0;
+ } else if (ldb->mode == LDB_DUL_DI1) {
+ reg &= ~LDB_SPLIT_MODE_EN;
+ reg |= LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1;
+ setting->disp_id = 1;
+ } else if (ldb->mode == LDB_SIN0) {
+ reg &= ~LDB_SPLIT_MODE_EN;
+ setting->disp_id = plat_data->disp_id;
+ if (setting->disp_id == 0)
+ reg |= LDB_CH0_MODE_EN_TO_DI0;
+ else
+ reg |= LDB_CH0_MODE_EN_TO_DI1;
+ } else if (ldb->mode == LDB_SIN1) {
+ reg &= ~LDB_SPLIT_MODE_EN;
+ setting->disp_id = plat_data->disp_id;
+ if (setting->disp_id == 0)
+ reg |= LDB_CH1_MODE_EN_TO_DI0;
+ else
+ reg |= LDB_CH1_MODE_EN_TO_DI1;
+ } else { /* separate mode*/
+ setting->disp_id = plat_data->disp_id;
+
+ /* first output is LVDS0 or LVDS1 */
+ if (ldb->mode == LDB_SEP0)
+ lvds_channel = 0;
+ else
+ lvds_channel = 1;
+
+ reg &= ~LDB_SPLIT_MODE_EN;
+
+ if ((lvds_channel == 0) && (setting->disp_id == 0))
+ reg |= LDB_CH0_MODE_EN_TO_DI0;
+ else if ((lvds_channel == 0) && (setting->disp_id == 1))
+ reg |= LDB_CH0_MODE_EN_TO_DI1;
+ else if ((lvds_channel == 1) && (setting->disp_id == 0))
+ reg |= LDB_CH1_MODE_EN_TO_DI0;
+ else
+ reg |= LDB_CH1_MODE_EN_TO_DI1;
+
+ if (bits_per_pixel(setting->if_fmt) == 24) {
+ if (lvds_channel == 0)
+ reg &= ~LDB_DATA_WIDTH_CH1_24;
+ else
+ reg &= ~LDB_DATA_WIDTH_CH0_24;
+ } else {
+ if (lvds_channel == 0)
+ reg &= ~LDB_DATA_WIDTH_CH1_18;
+ else
+ reg &= ~LDB_DATA_WIDTH_CH0_18;
+ }
+ }
+
+ writel(reg, ldb->control_reg);
+
+ /* clock setting */
+ ldb_clk[6] += setting->disp_id;
+ ldb->ldb_di_clk[0] = clk_get(&ldb->pdev->dev, ldb_clk);
+ if (IS_ERR(ldb->ldb_di_clk[0])) {
+ dev_err(&ldb->pdev->dev, "get ldb clk0 failed\n");
+ iounmap(ldb->reg);
+ return PTR_ERR(ldb->ldb_di_clk[0]);
+ }
+ di_clk[3] += setting->dev_id;
+ di_clk[7] += setting->disp_id;
+ ldb->di_clk[0] = clk_get(&ldb->pdev->dev, di_clk);
+ if (IS_ERR(ldb->di_clk[0])) {
+ dev_err(&ldb->pdev->dev, "get di clk0 failed\n");
+ iounmap(ldb->reg);
+ return PTR_ERR(ldb->di_clk[0]);
+ }
+
+ dev_dbg(&ldb->pdev->dev, "ldb_clk to di clk: %s -> %s\n", ldb_clk, di_clk);
+
+ /* fb notifier for clk setting */
+ ldb->nb.notifier_call = ldb_fb_event,
+ ret = fb_register_client(&ldb->nb);
+ if (ret < 0) {
+ iounmap(ldb->reg);
+ return ret;
+ }
+
+ setting_idx = 0;
+ ldb->inited = true;
+ } else { /* second time for separate mode */
+ char di_clk[] = "ipu1_di0_clk";
+ char ldb_clk[] = "ldb_di0_clk";
+ int lvds_channel;
+
+ if ((ldb->mode == LDB_SPL_DI0) ||
+ (ldb->mode == LDB_SPL_DI1) ||
+ (ldb->mode == LDB_DUL_DI0) ||
+ (ldb->mode == LDB_DUL_DI1) ||
+ (ldb->mode == LDB_SIN0) ||
+ (ldb->mode == LDB_SIN1)) {
+ dev_err(&ldb->pdev->dev, "for second ldb disp"
+ "ldb mode should in separate mode\n");
+ return -EINVAL;
+ }
+
+ setting->dev_id = plat_data->ipu_id;
+ setting->disp_id = !plat_data->disp_id;
+
+ /* second output is LVDS0 or LVDS1 */
+ if (ldb->mode == LDB_SEP0)
+ lvds_channel = 1;
+ else
+ lvds_channel = 0;
+
+ reg = readl(ldb->control_reg);
+ if ((lvds_channel == 0) && (setting->disp_id == 0))
+ reg |= LDB_CH0_MODE_EN_TO_DI0;
+ else if ((lvds_channel == 0) && (setting->disp_id == 1))
+ reg |= LDB_CH0_MODE_EN_TO_DI1;
+ else if ((lvds_channel == 1) && (setting->disp_id == 0))
+ reg |= LDB_CH1_MODE_EN_TO_DI0;
+ else
+ reg |= LDB_CH1_MODE_EN_TO_DI1;
+
+ if (bits_per_pixel(setting->if_fmt) == 24) {
+ if (lvds_channel == 0)
+ reg |= LDB_DATA_WIDTH_CH0_24;
+ else
+ reg |= LDB_DATA_WIDTH_CH1_24;
+ } else {
+ if (lvds_channel == 0)
+ reg |= LDB_DATA_WIDTH_CH0_18;
+ else
+ reg |= LDB_DATA_WIDTH_CH1_18;
+ }
+ writel(reg, ldb->control_reg);
+
+ /* clock setting */
+ ldb_clk[6] += setting->disp_id;
+ ldb->ldb_di_clk[1] = clk_get(&ldb->pdev->dev, ldb_clk);
+ if (IS_ERR(ldb->ldb_di_clk[1])) {
+ dev_err(&ldb->pdev->dev, "get ldb clk1 failed\n");
+ return PTR_ERR(ldb->ldb_di_clk[1]);
+ }
+ di_clk[3] += setting->dev_id;
+ di_clk[7] += setting->disp_id;
+ ldb->di_clk[1] = clk_get(&ldb->pdev->dev, di_clk);
+ if (IS_ERR(ldb->di_clk[1])) {
+ dev_err(&ldb->pdev->dev, "get di clk1 failed\n");
+ return PTR_ERR(ldb->di_clk[1]);
+ }
+
+ dev_dbg(&ldb->pdev->dev, "ldb_clk to di clk: %s -> %s\n", ldb_clk, di_clk);
+
+ setting_idx = 1;
+ }
+
+ /*
+ * ldb_di0_clk -> ipux_di0_clk
+ * ldb_di1_clk -> ipux_di1_clk
+ */
+ clk_set_parent(ldb->di_clk[setting_idx],
+ ldb->ldb_di_clk[setting_idx]);
+
+ /* must use spec video mode defined by driver */
+ ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str,
+ ldb_modedb, ldb_modedb_sz, NULL, setting->default_bpp);
+ if (ret != 1)
+ fb_videomode_to_var(&setting->fbi->var, &ldb_modedb[0]);
+
+ INIT_LIST_HEAD(&setting->fbi->modelist);
+ for (i = 0; i < ldb_modedb_sz; i++) {
+ struct fb_videomode m;
+ fb_var_to_videomode(&m, &setting->fbi->var);
+ if (fb_mode_is_equal(&m, &ldb_modedb[i])) {
+ fb_add_videomode(&ldb_modedb[i],
+ &setting->fbi->modelist);
+ break;
+ }
+ }
+
+ /* save current ldb setting for fb notifier */
+ ldb->setting[setting_idx].active = true;
+ ldb->setting[setting_idx].ipu = setting->dev_id;
+ ldb->setting[setting_idx].di = setting->disp_id;
+
+ return ret;
+}
+
+static void ldb_disp_deinit(struct mxc_dispdrv_entry *disp)
+{
+ struct ldb_data *ldb = mxc_dispdrv_getdata(disp);
+ int i;
+
+ writel(0, ldb->control_reg);
+
+ for (i = 0; i < 2; i++) {
+ clk_disable(ldb->ldb_di_clk[i]);
+ clk_put(ldb->ldb_di_clk[i]);
+ }
+
+ fb_unregister_client(&ldb->nb);
+
+ iounmap(ldb->reg);
+}
+
+static struct mxc_dispdrv_driver ldb_drv = {
+ .name = DISPDRV_LDB,
+ .init = ldb_disp_init,
+ .deinit = ldb_disp_deinit,
+};
+
+/*!
+ * This function is called by the driver framework to initialize the LDB
+ * device.
+ *
+ * @param dev The device structure for the LDB passed in by the
+ * driver framework.
+ *
+ * @return Returns 0 on success or negative error code on error
+ */
+static int ldb_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct ldb_data *ldb;
+
+ ldb = kzalloc(sizeof(struct ldb_data), GFP_KERNEL);
+ if (!ldb) {
+ ret = -ENOMEM;
+ goto alloc_failed;
+ }
+
+ ldb->pdev = pdev;
+ ldb->disp_ldb = mxc_dispdrv_register(&ldb_drv);
+ mxc_dispdrv_setdata(ldb->disp_ldb, ldb);
+
+ dev_set_drvdata(&pdev->dev, ldb);
+
+alloc_failed:
+ return ret;
+}
+
+static int ldb_remove(struct platform_device *pdev)
+{
+ struct ldb_data *ldb = dev_get_drvdata(&pdev->dev);
+
+ mxc_dispdrv_unregister(ldb->disp_ldb);
+ kfree(ldb);
+ return 0;
+}
+
+static struct platform_driver mxcldb_driver = {
+ .driver = {
+ .name = "mxc_ldb",
+ },
+ .probe = ldb_probe,
+ .remove = ldb_remove,
+};
+
+static int __init ldb_init(void)
+{
+ return platform_driver_register(&mxcldb_driver);
+}
+
+static void __exit ldb_uninit(void)
+{
+ platform_driver_unregister(&mxcldb_driver);
+}
+
+module_init(ldb_init);
+module_exit(ldb_uninit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC LDB driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/mxc/mxc_dispdrv.c b/drivers/video/mxc/mxc_dispdrv.c
new file mode 100644
index 00000000000..06b7af944f5
--- /dev/null
+++ b/drivers/video/mxc/mxc_dispdrv.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file mxc_dispdrv.c
+ * @brief mxc display driver framework.
+ *
+ * A display device driver could call mxc_dispdrv_register(drv) in its dev_probe() function.
+ * Move all dev_probe() things into mxc_dispdrv_driver->init(), init() function should init
+ * and feedback setting;
+ * Move all dev_remove() things into mxc_dispdrv_driver->deinit();
+ * Move all dev_suspend() things into fb_notifier for SUSPEND, if there is;
+ * Move all dev_resume() things into fb_notifier for RESUME, if there is;
+ *
+ * ipuv3 fb driver could call mxc_dispdrv_init(setting) before a fb need be added, with fbi param
+ * passing by setting, after mxc_dispdrv_init() return, FB driver should get the basic setting
+ * about fbi info and ipuv3-hw (ipu_id and disp_id).
+ *
+ * @ingroup Framebuffer
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include "mxc_dispdrv.h"
+
+static LIST_HEAD(dispdrv_list);
+static DEFINE_MUTEX(dispdrv_lock);
+
+struct mxc_dispdrv_entry {
+ const char *name;
+ struct list_head list;
+ int (*init) (struct mxc_dispdrv_entry *);
+ void (*deinit) (struct mxc_dispdrv_entry *);
+ bool active;
+ struct mxc_dispdrv_setting setting;
+ void *priv;
+};
+
+struct mxc_dispdrv_entry *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv)
+{
+ struct mxc_dispdrv_entry *new;
+
+ mutex_lock(&dispdrv_lock);
+
+ new = kzalloc(sizeof(struct mxc_dispdrv_entry), GFP_KERNEL);
+ if (!new) {
+ mutex_unlock(&dispdrv_lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ new->name = drv->name;
+ new->init = drv->init;
+ new->deinit = drv->deinit;
+
+ list_add_tail(&new->list, &dispdrv_list);
+ mutex_unlock(&dispdrv_lock);
+
+ return new;
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_register);
+
+int mxc_dispdrv_unregister(struct mxc_dispdrv_entry *entry)
+{
+ if (entry) {
+ mutex_lock(&dispdrv_lock);
+ if (entry->active && entry->deinit)
+ entry->deinit(entry);
+ list_del(&entry->list);
+ mutex_unlock(&dispdrv_lock);
+ kfree(entry);
+ return 0;
+ } else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_unregister);
+
+int mxc_dispdrv_init(char *name, struct mxc_dispdrv_setting *setting)
+{
+ int ret = 0, found = 0;
+ struct mxc_dispdrv_entry *disp;
+
+ mutex_lock(&dispdrv_lock);
+ list_for_each_entry(disp, &dispdrv_list, list) {
+ if (!strcmp(disp->name, name)) {
+ if (disp->init) {
+ memcpy(&disp->setting, setting,
+ sizeof(struct mxc_dispdrv_setting));
+ ret = disp->init(disp);
+ if (ret >= 0) {
+ disp->active = true;
+ /* setting may need fix-up */
+ memcpy(setting, &disp->setting,
+ sizeof(struct mxc_dispdrv_setting));
+ found = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!found)
+ ret = -EINVAL;
+
+ mutex_unlock(&dispdrv_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_init);
+
+int mxc_dispdrv_setdata(struct mxc_dispdrv_entry *entry, void *data)
+{
+ if (entry) {
+ entry->priv = data;
+ return 0;
+ } else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_setdata);
+
+void *mxc_dispdrv_getdata(struct mxc_dispdrv_entry *entry)
+{
+ if (entry) {
+ return entry->priv;
+ } else
+ return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_getdata);
+
+struct mxc_dispdrv_setting
+ *mxc_dispdrv_getsetting(struct mxc_dispdrv_entry *entry)
+{
+ if (entry) {
+ return &entry->setting;
+ } else
+ return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_getsetting);
diff --git a/drivers/video/mxc/mxc_dispdrv.h b/drivers/video/mxc/mxc_dispdrv.h
new file mode 100644
index 00000000000..f5f62a2c3cb
--- /dev/null
+++ b/drivers/video/mxc/mxc_dispdrv.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef __MXC_DISPDRV_H__
+#define __MXC_DISPDRV_H__
+
+struct mxc_dispdrv_entry;
+
+struct mxc_dispdrv_driver {
+ const char *name;
+ int (*init) (struct mxc_dispdrv_entry *);
+ void (*deinit) (struct mxc_dispdrv_entry *);
+};
+
+struct mxc_dispdrv_setting {
+ /*input-feedback parameter*/
+ struct fb_info *fbi;
+ int if_fmt;
+ int default_bpp;
+ char *dft_mode_str;
+
+ /*feedback parameter*/
+ int dev_id;
+ int disp_id;
+};
+
+struct mxc_dispdrv_entry *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv);
+int mxc_dispdrv_unregister(struct mxc_dispdrv_entry *entry);
+int mxc_dispdrv_init(char *name, struct mxc_dispdrv_setting *setting);
+int mxc_dispdrv_setdata(struct mxc_dispdrv_entry *entry, void *data);
+void *mxc_dispdrv_getdata(struct mxc_dispdrv_entry *entry);
+struct mxc_dispdrv_setting
+ *mxc_dispdrv_getsetting(struct mxc_dispdrv_entry *entry);
+#endif
diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c
new file mode 100644
index 00000000000..0fa5cf97602
--- /dev/null
+++ b/drivers/video/mxc/mxc_edid.c
@@ -0,0 +1,451 @@
+/*
+ * 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 driver
+ *
+ * @ingroup Framebuffer
+ */
+
+/*!
+ * Include files
+ */
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/fb.h>
+#include "mxc_edid.h"
+#include "../edid.h"
+
+#undef DEBUG /* define this for verbose EDID parsing output */
+
+#ifdef DEBUG
+#define DPRINTK(fmt, args...) printk(fmt, ## args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+const struct fb_videomode mxc_cea_mode[64] = {
+ /* #1: 640x480p@59.94/60Hz */
+ [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,
+ },
+ /* #4: 1280x720p@59.94/60Hz */
+ [4] = {
+ NULL, 60, 1280, 720, 13468, 220, 110, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, 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,
+ },
+ /* #16: 1920x1080p@60Hz */
+ [16] = {
+ NULL, 60, 1920, 1080, 6734, 148, 88, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, 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,
+ },
+ /* #31: 1920x1080p@50Hz */
+ [31] = {
+ NULL, 50, 1920, 1080, 6734, 148, 528, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, 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_timing_desc_offset;
+ struct fb_videomode *mode, *m;
+ unsigned char index = 0x0;
+ unsigned char *block;
+ int i, num = 0, revision;
+
+ if (edid[index++] != 0x2) /* only support cea ext block now */
+ return -1;
+ revision = edid[index++];
+ DPRINTK("cea extent revision %d\n", revision);
+ mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL);
+ if (mode == NULL)
+ return -1;
+
+ detail_timing_desc_offset = edid[index++];
+
+ if (revision >= 2) {
+ cfg->cea_underscan = (edid[index] >> 7) & 0x1;
+ cfg->cea_basicaudio = (edid[index] >> 6) & 0x1;
+ cfg->cea_ycbcr444 = (edid[index] >> 5) & 0x1;
+ cfg->cea_ycbcr422 = (edid[index] >> 4) & 0x1;
+
+ DPRINTK("CEA underscan %d\n", cfg->cea_underscan);
+ DPRINTK("CEA basicaudio %d\n", cfg->cea_basicaudio);
+ DPRINTK("CEA ycbcr444 %d\n", cfg->cea_ycbcr444);
+ DPRINTK("CEA ycbcr422 %d\n", cfg->cea_ycbcr422);
+ }
+
+ if (revision >= 3) {
+ /* short desc */
+ DPRINTK("CEA Short desc timmings\n");
+ index++;
+ while (index < detail_timing_desc_offset) {
+ unsigned char tagcode, blklen;
+
+ tagcode = (edid[index] >> 5) & 0x7;
+ blklen = (edid[index]) & 0x1f;
+
+ DPRINTK("Tagcode %x Len %d\n", tagcode, blklen);
+
+ switch (tagcode) {
+ case 0x2: /*Video data block*/
+ {
+ int cea_idx;
+ i = 0;
+ while (i < blklen) {
+ index++;
+ cea_idx = edid[index] & 0x7f;
+ if (cea_idx < ARRAY_SIZE(mxc_cea_mode) &&
+ (mxc_cea_mode[cea_idx].xres)) {
+ DPRINTK("Support CEA Format #%d\n", cea_idx);
+ mode[num] = mxc_cea_mode[cea_idx];
+ mode[num].flag |= FB_MODE_IS_STANDARD;
+ num++;
+ }
+ i++;
+ }
+ break;
+ }
+ case 0x3: /*Vendor specific data*/
+ {
+ unsigned char IEEE_reg_iden[3];
+ unsigned char deep_color;
+ IEEE_reg_iden[0] = edid[index+1];
+ IEEE_reg_iden[1] = edid[index+2];
+ IEEE_reg_iden[2] = edid[index+3];
+ deep_color = edid[index+6];
+
+ if ((IEEE_reg_iden[0] == 0x03) &&
+ (IEEE_reg_iden[1] == 0x0c) &&
+ (IEEE_reg_iden[2] == 0x00))
+ cfg->hdmi_cap = 1;
+
+ if (deep_color & 0x40)
+ cfg->vsd_dc_48bit = true;
+ if (deep_color & 0x20)
+ cfg->vsd_dc_36bit = true;
+ if (deep_color & 0x10)
+ cfg->vsd_dc_30bit = true;
+ if (deep_color & 0x08)
+ cfg->vsd_dc_y444 = true;
+ if (deep_color & 0x01)
+ cfg->vsd_dvi_dual = true;
+
+ DPRINTK("VSD hdmi capability %d\n", cfg->hdmi_cap);
+ DPRINTK("VSD support deep color 48bit %d\n", cfg->vsd_dc_48bit);
+ DPRINTK("VSD support deep color 36bit %d\n", cfg->vsd_dc_36bit);
+ DPRINTK("VSD support deep color 30bit %d\n", cfg->vsd_dc_30bit);
+ DPRINTK("VSD support deep color y444 %d\n", cfg->vsd_dc_y444);
+ DPRINTK("VSD support dvi dual %d\n", cfg->vsd_dvi_dual);
+
+ 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_timing_desc_offset;
+ block = edid + index;
+ while (index < (EDID_LENGTH - DETAILED_TIMING_DESCRIPTION_SIZE)) {
+ if (!(block[0] == 0x00 && block[1] == 0x00)) {
+ get_detailed_timing(block, &mode[num]);
+ num++;
+ }
+ block += DETAILED_TIMING_DESCRIPTION_SIZE;
+ index += DETAILED_TIMING_DESCRIPTION_SIZE;
+ }
+
+ if (!num) {
+ kfree(mode);
+ return 0;
+ }
+
+ m = kmalloc((num + specs->modedb_len) *
+ sizeof(struct fb_videomode), GFP_KERNEL);
+ if (!m)
+ return 0;
+
+ if (specs->modedb_len) {
+ memmove(m, specs->modedb,
+ specs->modedb_len * sizeof(struct fb_videomode));
+ kfree(specs->modedb);
+ }
+ memmove(m+specs->modedb_len, mode,
+ num * sizeof(struct fb_videomode));
+ kfree(mode);
+
+ specs->modedb_len += num;
+ specs->modedb = m;
+
+ return 0;
+}
+
+static int mxc_edid_readblk(struct i2c_adapter *adp,
+ unsigned short addr, unsigned char *edid)
+{
+ int ret = 0, extblknum = 0;
+ unsigned char regaddr = 0x0;
+ struct i2c_msg msg[2] = {
+ {
+ .addr = addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &regaddr,
+ }, {
+ .addr = addr,
+ .flags = I2C_M_RD,
+ .len = EDID_LENGTH,
+ .buf = edid,
+ },
+ };
+
+ ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ DPRINTK("unable to read EDID block\n");
+ return -EIO;
+ }
+
+ if (edid[1] == 0x00)
+ return -ENOENT;
+
+ extblknum = edid[0x7E];
+
+ if (extblknum) {
+ regaddr = 128;
+ msg[1].buf = edid + EDID_LENGTH;
+
+ ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ DPRINTK("unable to read EDID ext block\n");
+ return -EIO;
+ }
+ }
+
+ return extblknum;
+}
+
+static int mxc_edid_readsegblk(struct i2c_adapter *adp, unsigned short addr,
+ unsigned char *edid, int seg_num)
+{
+ int ret = 0;
+ unsigned char segment = 0x1, regaddr = 0;
+ struct i2c_msg msg[3] = {
+ {
+ .addr = 0x30,
+ .flags = 0,
+ .len = 1,
+ .buf = &segment,
+ }, {
+ .addr = addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &regaddr,
+ }, {
+ .addr = addr,
+ .flags = I2C_M_RD,
+ .len = EDID_LENGTH,
+ .buf = edid,
+ },
+ };
+
+ ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ DPRINTK("unable to read EDID block\n");
+ return -EIO;
+ }
+
+ if (seg_num == 2) {
+ regaddr = 128;
+ msg[2].buf = edid + EDID_LENGTH;
+
+ ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ DPRINTK("unable to read EDID block\n");
+ return -EIO;
+ }
+ }
+
+ return ret;
+}
+
+int mxc_edid_var_to_vic(struct fb_var_screeninfo *var)
+{
+ int i;
+ struct fb_videomode m;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) {
+ fb_var_to_videomode(&m, var);
+ if (fb_mode_is_equal(&m, &mxc_cea_mode[i]))
+ break;
+ }
+
+ if (i == ARRAY_SIZE(mxc_cea_mode))
+ return 0;
+
+ return i;
+}
+EXPORT_SYMBOL(mxc_edid_var_to_vic);
+
+/* make sure edid has 512 bytes*/
+int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr,
+ unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi)
+{
+ int ret = 0, extblknum;
+ if (!adp || !edid || !cfg || !fbi)
+ return -EINVAL;
+
+ memset(edid, 0, EDID_LENGTH*4);
+ memset(cfg, 0, sizeof(struct mxc_edid_cfg));
+
+ extblknum = mxc_edid_readblk(adp, addr, edid);
+ if (extblknum < 0)
+ return extblknum;
+
+ /* edid first block parsing */
+ memset(&fbi->monspecs, 0, sizeof(fbi->monspecs));
+ fb_edid_to_monspecs(edid, &fbi->monspecs);
+
+ if (extblknum) {
+ int i;
+
+ /* need read segment block? */
+ if (extblknum > 1) {
+ ret = mxc_edid_readsegblk(adp, addr,
+ edid + EDID_LENGTH*2, extblknum - 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ for (i = 1; i <= extblknum; i++)
+ /* edid ext block parsing */
+ mxc_edid_parse_ext_blk(edid + i*EDID_LENGTH,
+ cfg, &fbi->monspecs);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mxc_edid_read);
diff --git a/drivers/video/mxc/mxc_edid.h b/drivers/video/mxc/mxc_edid.h
new file mode 100644
index 00000000000..ec65a5fa9bd
--- /dev/null
+++ b/drivers/video/mxc/mxc_edid.h
@@ -0,0 +1,48 @@
+/*
+ * 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.h
+ *
+ * @brief MXC EDID tools
+ *
+ * @ingroup Framebuffer
+ */
+
+#ifndef MXC_EDID_H
+#define MXC_EDID_H
+
+struct mxc_edid_cfg {
+ bool cea_underscan;
+ bool cea_basicaudio;
+ bool cea_ycbcr444;
+ bool cea_ycbcr422;
+ bool hdmi_cap;
+
+ /*VSD*/
+ bool vsd_dc_48bit;
+ bool vsd_dc_36bit;
+ bool vsd_dc_30bit;
+ bool vsd_dc_y444;
+ bool vsd_dvi_dual;
+};
+
+int mxc_edid_var_to_vic(struct fb_var_screeninfo *var);
+int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr,
+ unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi);
+
+#endif
diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c
new file mode 100644
index 00000000000..260b41a3ddf
--- /dev/null
+++ b/drivers/video/mxc/mxc_ipuv3_fb.c
@@ -0,0 +1,2072 @@
+/*
+ * 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>
+#include <mach/ipu-v3.h>
+#include "mxc_dispdrv.h"
+
+/*
+ * Driver name
+ */
+#define MXCFB_NAME "mxc_sdc_fb"
+
+/* Display port number */
+#define MXCFB_PORT_NUM 2
+/*!
+ * Structure containing the MXC specific framebuffer information.
+ */
+struct mxcfb_info {
+ int default_bpp;
+ int cur_blank;
+ int next_blank;
+ ipu_channel_t ipu_ch;
+ int ipu_id;
+ int ipu_di;
+ u32 ipu_di_pix_fmt;
+ bool ipu_int_clk;
+ bool overlay;
+ bool alpha_chan_en;
+ 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 mode_found;
+ volatile bool wait4vsync;
+ struct semaphore flip_sem;
+ struct semaphore alpha_flip_sem;
+ struct completion vsync_complete;
+
+ void *ipu;
+ struct fb_info *ovfbi;
+};
+
+struct mxcfb_alloc_list {
+ struct list_head list;
+ dma_addr_t phy_addr;
+ void *cpu_addr;
+ u32 size;
+};
+
+enum {
+ BOTH_ON,
+ SRC_ON,
+ TGT_ON,
+ BOTH_OFF
+};
+
+static bool g_dp_in_use[2];
+LIST_HEAD(fb_alloc_list);
+
+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 struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id)
+{
+ int i;
+ struct mxcfb_info *mxc_fbi;
+ struct fb_info *fbi = NULL;
+
+ for (i = 0; i < num_registered_fb; i++) {
+ mxc_fbi =
+ ((struct mxcfb_info *)(registered_fb[i]->par));
+
+ if ((mxc_fbi->ipu_ch == ipu_ch) &&
+ (mxc_fbi->ipu_id == ipu_id)) {
+ fbi = registered_fb[i];
+ break;
+ }
+ }
+ return fbi;
+}
+
+static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id);
+static int mxcfb_blank(int blank, struct fb_info *info);
+static int mxcfb_map_video_memory(struct fb_info *fbi);
+static int mxcfb_unmap_video_memory(struct fb_info *fbi);
+
+/*
+ * Set fixed framebuffer parameters based on variable settings.
+ *
+ * @param info framebuffer information pointer
+ */
+static int mxcfb_set_fix(struct fb_info *info)
+{
+ struct fb_fix_screeninfo *fix = &info->fix;
+ struct fb_var_screeninfo *var = &info->var;
+
+ fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+ fix->type = FB_TYPE_PACKED_PIXELS;
+ fix->accel = FB_ACCEL_NONE;
+ fix->visual = FB_VISUAL_TRUECOLOR;
+ fix->xpanstep = 1;
+ fix->ywrapstep = 1;
+ fix->ypanstep = 1;
+
+ return 0;
+}
+
+static int _setup_disp_channel1(struct fb_info *fbi)
+{
+ ipu_channel_params_t params;
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+
+ memset(&params, 0, sizeof(params));
+
+ if (mxc_fbi->ipu_ch == MEM_DC_SYNC) {
+ params.mem_dc_sync.di = mxc_fbi->ipu_di;
+ if (fbi->var.vmode & FB_VMODE_INTERLACED)
+ params.mem_dc_sync.interlaced = true;
+ params.mem_dc_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
+ params.mem_dc_sync.in_pixel_fmt = bpp_to_pixfmt(fbi);
+ } else {
+ params.mem_dp_bg_sync.di = mxc_fbi->ipu_di;
+ if (fbi->var.vmode & FB_VMODE_INTERLACED)
+ params.mem_dp_bg_sync.interlaced = true;
+ params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
+ params.mem_dp_bg_sync.in_pixel_fmt = 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, mxc_fbi->ipu_ch, &params);
+
+ 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 = 2;
+ 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 * fb_stride + fbi->var.xoffset);
+ base += fbi->fix.smem_start;
+
+ retval = ipu_init_channel_buffer(mxc_fbi->ipu,
+ mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
+ bpp_to_pixfmt(fbi),
+ fbi->var.xres, fbi->var.yres,
+ fb_stride,
+ fbi->var.rotate,
+ base,
+ base,
+ fbi->var.accel_flags &
+ FB_ACCEL_DOUBLE_FLAG ? 0 : base,
+ 0, 0);
+ if (retval) {
+ dev_err(fbi->device,
+ "ipu_init_channel_buffer error %d\n", retval);
+ }
+
+ if (mxc_fbi->alpha_chan_en) {
+ retval = ipu_init_channel_buffer(mxc_fbi->ipu,
+ mxc_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ IPU_PIX_FMT_GENERIC,
+ fbi->var.xres, fbi->var.yres,
+ fbi->var.xres,
+ fbi->var.rotate,
+ mxc_fbi->alpha_phy_addr1,
+ mxc_fbi->alpha_phy_addr0,
+ 0,
+ 0, 0);
+ if (retval) {
+ dev_err(fbi->device,
+ "ipu_init_channel_buffer error %d\n", retval);
+ return retval;
+ }
+ }
+
+ return retval;
+}
+
+/*
+ * 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_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true);
+ ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
+ 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 = mxc_fbi->ipu_di_pix_fmt;
+ if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */
+ sig_cfg.odd_field_first = true;
+ if (mxc_fbi->ipu_int_clk)
+ sig_cfg.int_clk = true;
+ if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT)
+ sig_cfg.Hsync_pol = true;
+ if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)
+ sig_cfg.Vsync_pol = true;
+ if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL))
+ sig_cfg.clk_pol = true;
+ if (fbi->var.sync & FB_SYNC_DATA_INVERT)
+ sig_cfg.data_pol = true;
+ if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT))
+ sig_cfg.enable_pol = true;
+ if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN)
+ sig_cfg.clkidle_en = true;
+
+ dev_dbg(fbi->device, "pixclock = %ul Hz\n",
+ (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL));
+
+ if (ipu_init_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di,
+ (PICOS2KHZ(fbi->var.pixclock)) * 1000UL,
+ fbi->var.xres, fbi->var.yres,
+ out_pixel_fmt,
+ fbi->var.left_margin,
+ fbi->var.hsync_len,
+ fbi->var.right_margin,
+ fbi->var.upper_margin,
+ fbi->var.vsync_len,
+ fbi->var.lower_margin,
+ 0, sig_cfg) != 0) {
+ dev_err(fbi->device,
+ "mxcfb: Error initializing panel.\n");
+ return -EINVAL;
+ }
+
+ fbi->mode =
+ (struct fb_videomode *)fb_match_mode(&fbi->var,
+ &fbi->modelist);
+
+ ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, 0, 0);
+ }
+
+ retval = _setup_disp_channel2(fbi);
+ if (retval)
+ return retval;
+
+ ipu_enable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
+
+ return retval;
+}
+
+static int _swap_channels(struct fb_info *fbi_from,
+ struct fb_info *fbi_to, bool both_on)
+{
+ int retval, tmp;
+ ipu_channel_t old_ch;
+ struct fb_info *ovfbi;
+ struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par;
+ struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par;
+
+ if (both_on) {
+ ipu_disable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch, true);
+ ipu_uninit_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch);
+ }
+
+ /* switch the mxc fbi parameters */
+ old_ch = mxc_fbi_from->ipu_ch;
+ mxc_fbi_from->ipu_ch = mxc_fbi_to->ipu_ch;
+ mxc_fbi_to->ipu_ch = old_ch;
+ tmp = mxc_fbi_from->ipu_ch_irq;
+ mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq;
+ mxc_fbi_to->ipu_ch_irq = tmp;
+ ovfbi = mxc_fbi_from->ovfbi;
+ mxc_fbi_from->ovfbi = mxc_fbi_to->ovfbi;
+ mxc_fbi_to->ovfbi = ovfbi;
+
+ _setup_disp_channel1(fbi_from);
+ retval = _setup_disp_channel2(fbi_from);
+ if (retval)
+ return retval;
+
+ /* switch between dp and dc, disable old idmac, enable new idmac */
+ retval = ipu_swap_channel(mxc_fbi_from->ipu, old_ch, mxc_fbi_from->ipu_ch);
+ ipu_uninit_channel(mxc_fbi_from->ipu, old_ch);
+
+ if (both_on) {
+ _setup_disp_channel1(fbi_to);
+ retval = _setup_disp_channel2(fbi_to);
+ if (retval)
+ return retval;
+ ipu_enable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch);
+ }
+
+ return retval;
+}
+
+static int swap_channels(struct fb_info *fbi_from)
+{
+ int i;
+ int swap_mode;
+ ipu_channel_t ch_to;
+ struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par;
+ struct fb_info *fbi_to = NULL;
+ struct mxcfb_info *mxc_fbi_to;
+
+ /* what's the target channel? */
+ if (mxc_fbi_from->ipu_ch == MEM_BG_SYNC)
+ ch_to = MEM_DC_SYNC;
+ else
+ ch_to = MEM_BG_SYNC;
+
+ fbi_to = found_registered_fb(ch_to, mxc_fbi_from->ipu_id);
+ if (!fbi_to)
+ return -1;
+ mxc_fbi_to = (struct mxcfb_info *)fbi_to->par;
+
+ ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq);
+ ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq);
+ ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, fbi_from);
+ ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, fbi_to);
+
+ if (mxc_fbi_from->cur_blank == FB_BLANK_UNBLANK) {
+ if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK)
+ swap_mode = BOTH_ON;
+ else
+ swap_mode = SRC_ON;
+ } else {
+ if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK)
+ swap_mode = TGT_ON;
+ else
+ swap_mode = BOTH_OFF;
+ }
+
+ switch (swap_mode) {
+ case BOTH_ON:
+ /* disable target->switch src->enable target */
+ _swap_channels(fbi_from, fbi_to, true);
+ break;
+ case SRC_ON:
+ /* just switch src */
+ _swap_channels(fbi_from, fbi_to, false);
+ break;
+ case TGT_ON:
+ /* just switch target */
+ _swap_channels(fbi_to, fbi_from, false);
+ break;
+ case BOTH_OFF:
+ /* switch directly, no more need to do */
+ mxc_fbi_to->ipu_ch = mxc_fbi_from->ipu_ch;
+ mxc_fbi_from->ipu_ch = ch_to;
+ i = mxc_fbi_from->ipu_ch_irq;
+ mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq;
+ mxc_fbi_to->ipu_ch_irq = i;
+ break;
+ default:
+ break;
+ }
+
+ if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, mxcfb_irq_handler, 0,
+ MXCFB_NAME, fbi_from) != 0) {
+ dev_err(fbi_from->device, "Error registering irq %d\n",
+ mxc_fbi_from->ipu_ch_irq);
+ return -EBUSY;
+ }
+ ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq);
+ if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, mxcfb_irq_handler, 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, 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;
+ int bg_xres = 0, bg_yres = 0;
+ int16_t pos_x, pos_y;
+
+ bg_xres = var->xres;
+ bg_yres = var->yres;
+
+ fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
+ if (fbi_tmp) {
+ bg_xres = fbi_tmp->var.xres;
+ bg_yres = fbi_tmp->var.yres;
+ }
+
+ ipu_disp_get_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, &pos_x, &pos_y);
+
+ if ((var->xres + pos_x) > bg_xres)
+ var->xres = bg_xres - pos_x;
+ if ((var->yres + pos_y) > bg_yres)
+ var->yres = bg_yres - pos_y;
+ }
+
+ if (var->rotate > IPU_ROTATE_VERT_FLIP)
+ var->rotate = IPU_ROTATE_NONE;
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres * 3;
+
+ if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) &&
+ (var->bits_per_pixel != 16) && (var->bits_per_pixel != 12) &&
+ (var->bits_per_pixel != 8))
+ var->bits_per_pixel = 16;
+
+ 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,
+ mxc_fbi->ipu_ch,
+ (bool)ga.enable,
+ ga.alpha)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ if (ga.enable)
+ mxc_fbi->alpha_chan_en = false;
+
+ if (ga.enable)
+ dev_dbg(fbi->device,
+ "Set global alpha of %s to %d\n",
+ fbi->fix.id, ga.alpha);
+ break;
+ }
+ case MXCFB_SET_LOC_ALPHA:
+ {
+ struct mxcfb_loc_alpha la;
+
+ if (copy_from_user(&la, (void *)arg, sizeof(la))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if (ipu_disp_set_global_alpha(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ !(bool)la.enable, 0)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ if (la.enable && !la.alpha_in_pixel) {
+ struct fb_info *fbi_tmp;
+ ipu_channel_t ipu_ch;
+
+ mxc_fbi->alpha_chan_en = true;
+
+ if (mxc_fbi->ipu_ch == MEM_FG_SYNC)
+ ipu_ch = MEM_BG_SYNC;
+ else if (mxc_fbi->ipu_ch == MEM_BG_SYNC)
+ ipu_ch = MEM_FG_SYNC;
+ else {
+ retval = -EINVAL;
+ break;
+ }
+
+ fbi_tmp = found_registered_fb(ipu_ch, mxc_fbi->ipu_id);
+ if (fbi_tmp)
+ ((struct mxcfb_info *)(fbi_tmp->par))->alpha_chan_en = false;
+ } else
+ mxc_fbi->alpha_chan_en = false;
+
+ 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, mxc_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ mxc_fbi->
+ cur_ipu_alpha_buf,
+ base) == 0) {
+ ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ mxc_fbi->cur_ipu_alpha_buf);
+ ipu_clear_irq(mxc_fbi->ipu, ipu_alp_ch_irq);
+ ipu_enable_irq(mxc_fbi->ipu, ipu_alp_ch_irq);
+ } else {
+ dev_err(fbi->device,
+ "Error updating %s SDC alpha buf %d "
+ "to address=0x%08lX\n",
+ fbi->fix.id,
+ mxc_fbi->cur_ipu_alpha_buf, base);
+ }
+ break;
+ }
+ case MXCFB_SET_CLR_KEY:
+ {
+ struct mxcfb_color_key key;
+ if (copy_from_user(&key, (void *)arg, sizeof(key))) {
+ retval = -EFAULT;
+ break;
+ }
+ retval = ipu_disp_set_color_key(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ key.enable,
+ key.color_key);
+ dev_dbg(fbi->device, "Set color key to 0x%08X\n",
+ key.color_key);
+ break;
+ }
+ case MXCFB_SET_GAMMA:
+ {
+ struct mxcfb_gamma gamma;
+ if (copy_from_user(&gamma, (void *)arg, sizeof(gamma))) {
+ retval = -EFAULT;
+ break;
+ }
+ retval = ipu_disp_set_gamma_correction(mxc_fbi->ipu,
+ mxc_fbi->ipu_ch,
+ gamma.enable,
+ gamma.constk,
+ gamma.slopek);
+ break;
+ }
+ case MXCFB_WAIT_FOR_VSYNC:
+ {
+ if (mxc_fbi->ipu_ch == MEM_FG_SYNC) {
+ /* BG should poweron */
+ struct mxcfb_info *bg_mxcfbi = NULL;
+ struct fb_info *fbi_tmp;
+
+ fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
+ if (fbi_tmp)
+ bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par));
+
+ if (!bg_mxcfbi) {
+ retval = -EINVAL;
+ break;
+ }
+ if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) {
+ retval = -EINVAL;
+ break;
+ }
+ }
+ if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) {
+ retval = -EINVAL;
+ break;
+ }
+
+ init_completion(&mxc_fbi->vsync_complete);
+
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ mxc_fbi->wait4vsync = true;
+ ipu_enable_irq(mxc_fbi->ipu, 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 = false;
+ 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;
+
+ if (mxc_fbi->ipu_ch != MEM_FG_SYNC) {
+ dev_err(fbi->device, "Should use the overlay "
+ "framebuffer to set the position of "
+ "the overlay window\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (copy_from_user(&pos, (void *)arg, sizeof(pos))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ bg_fbi = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
+ if (bg_fbi)
+ bg_mxcfbi = ((struct mxcfb_info *)(bg_fbi->par));
+
+ if (bg_fbi == NULL) {
+ dev_err(fbi->device, "Cannot find the "
+ "background framebuffer\n");
+ retval = -ENOENT;
+ break;
+ }
+
+ /* if fb is unblank, check if the pos fit the display */
+ if (mxc_fbi->cur_blank == FB_BLANK_UNBLANK) {
+ if (fbi->var.xres + pos.x > bg_fbi->var.xres) {
+ if (bg_fbi->var.xres < fbi->var.xres)
+ pos.x = 0;
+ else
+ pos.x = bg_fbi->var.xres - fbi->var.xres;
+ }
+ if (fbi->var.yres + pos.y > bg_fbi->var.yres) {
+ if (bg_fbi->var.yres < fbi->var.yres)
+ pos.y = 0;
+ else
+ pos.y = bg_fbi->var.yres - fbi->var.yres;
+ }
+ }
+
+ retval = ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ pos.x, pos.y);
+
+ if (copy_to_user((void *)arg, &pos, sizeof(pos))) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+ case MXCFB_GET_FB_IPU_CHAN:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (put_user(mxc_fbi->ipu_ch, argp))
+ return -EFAULT;
+ break;
+ }
+ case MXCFB_GET_DIFMT:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (put_user(mxc_fbi->ipu_di_pix_fmt, argp))
+ return -EFAULT;
+ break;
+ }
+ case MXCFB_GET_FB_IPU_DI:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (put_user(mxc_fbi->ipu_di, argp))
+ return -EFAULT;
+ break;
+ }
+ case MXCFB_GET_FB_BLANK:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (put_user(mxc_fbi->cur_blank, argp))
+ return -EFAULT;
+ break;
+ }
+ case MXCFB_SET_DIFMT:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (get_user(mxc_fbi->ipu_di_pix_fmt, argp))
+ return -EFAULT;
+
+ break;
+ }
+ 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, mxc_fbi->ipu_ch, true);
+ if (mxc_fbi->ipu_di >= 0)
+ ipu_uninit_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di);
+ ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
+ break;
+ case FB_BLANK_UNBLANK:
+ 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 fb_stride;
+ int i;
+
+ 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;
+ struct fb_info *fbi_tmp;
+
+ fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
+ if (fbi_tmp)
+ bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par));
+ if (!bg_mxcfbi)
+ return -EINVAL;
+ if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK)
+ return -EINVAL;
+ }
+ if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK)
+ return -EINVAL;
+
+ y_bottom = var->yoffset;
+
+ if (!(var->vmode & FB_VMODE_YWRAP))
+ y_bottom += var->yres;
+
+ if (y_bottom > info->var.yres_virtual)
+ return -EINVAL;
+
+ switch (bpp_to_pixfmt(info)) {
+ case IPU_PIX_FMT_YUV420P2:
+ case IPU_PIX_FMT_YVU420P:
+ case IPU_PIX_FMT_NV12:
+ case IPU_PIX_FMT_YUV422P:
+ case IPU_PIX_FMT_YVU422P:
+ case IPU_PIX_FMT_YUV420P:
+ fb_stride = info->var.xres_virtual;
+ break;
+ default:
+ fb_stride = info->fix.line_length;
+ }
+
+ base = (var->yoffset * fb_stride + var->xoffset);
+ 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 *bg_id = "DISP3 BG";
+ char *fg_id = "DISP3 FG";
+ char *idstr = registered_fb[i]->fix.id;
+ bg_id[4] += mxc_fbi->ipu_id;
+ fg_id[4] += mxc_fbi->ipu_id;
+ if ((strcmp(idstr, bg_id) == 0 ||
+ strcmp(idstr, fg_id) == 0) &&
+ ((struct mxcfb_info *)
+ (registered_fb[i]->par))->alpha_chan_en) {
+ loc_alpha_en = true;
+ mxc_graphic_fbi = (struct mxcfb_info *)
+ (registered_fb[i]->par);
+ active_alpha_phy_addr =
+ mxc_fbi->cur_ipu_alpha_buf ?
+ mxc_graphic_fbi->alpha_phy_addr1 :
+ mxc_graphic_fbi->alpha_phy_addr0;
+ dev_dbg(info->device, "Updating SDC alpha "
+ "buf %d address=0x%08lX\n",
+ !mxc_fbi->cur_ipu_alpha_buf,
+ active_alpha_phy_addr);
+ break;
+ }
+ }
+ }
+
+ down(&mxc_fbi->flip_sem);
+
+ mxc_fbi->cur_ipu_buf = (++mxc_fbi->cur_ipu_buf) % 3;
+ mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf;
+
+ dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n",
+ info->fix.id, mxc_fbi->cur_ipu_buf, base);
+
+ if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
+ mxc_fbi->cur_ipu_buf, base) == 0) {
+ /* Update the DP local alpha buffer only for graphic plane */
+ if (loc_alpha_en && mxc_graphic_fbi == mxc_fbi &&
+ ipu_update_channel_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ mxc_fbi->cur_ipu_alpha_buf,
+ active_alpha_phy_addr) == 0) {
+ ipu_select_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ mxc_fbi->cur_ipu_alpha_buf);
+ }
+
+ /* update u/v offset */
+ ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER,
+ bpp_to_pixfmt(info),
+ info->var.xres_virtual,
+ info->var.yres_virtual,
+ info->var.xres_virtual,
+ 0, 0,
+ var->yoffset,
+ var->xoffset);
+
+ ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
+ mxc_fbi->cur_ipu_buf);
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ } else {
+ dev_err(info->device,
+ "Error updating SDC buf %d to address=0x%08lX, "
+ "current buf %d, buf0 ready %d, buf1 ready %d, "
+ "buf2 ready %d\n", mxc_fbi->cur_ipu_buf, base,
+ ipu_get_cur_buffer_idx(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER),
+ ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER, 0),
+ ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER, 1),
+ ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER, 2));
+ mxc_fbi->cur_ipu_buf = (++mxc_fbi->cur_ipu_buf) % 3;
+ mxc_fbi->cur_ipu_buf = (++mxc_fbi->cur_ipu_buf) % 3;
+ mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf;
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ return -EBUSY;
+ }
+
+ dev_dbg(info->device, "Update complete\n");
+
+ info->var.yoffset = var->yoffset;
+
+ return 0;
+}
+
+/*
+ * Function to handle custom mmap for MXC framebuffer.
+ *
+ * @param fbi framebuffer information pointer
+ *
+ * @param vma Pointer to vm_area_struct
+ */
+static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma)
+{
+ bool found = false;
+ u32 len;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ struct mxcfb_alloc_list *mem;
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+
+ if (offset < fbi->fix.smem_len) {
+ /* mapping framebuffer memory */
+ len = fbi->fix.smem_len - offset;
+ vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT;
+ } else if ((vma->vm_pgoff ==
+ (mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) ||
+ (vma->vm_pgoff ==
+ (mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) {
+ len = mxc_fbi->alpha_mem_len;
+ } else {
+ list_for_each_entry(mem, &fb_alloc_list, list) {
+ if (offset == mem->phy_addr) {
+ found = true;
+ len = mem->size;
+ break;
+ }
+ }
+ if (!found)
+ return -EINVAL;
+ }
+
+ len = PAGE_ALIGN(len);
+ if (vma->vm_end - vma->vm_start > len)
+ return -EINVAL;
+
+ /* make buffers bufferable */
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+ vma->vm_flags |= VM_IO | VM_RESERVED;
+
+ if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
+ dev_dbg(fbi->device, "mmap remap_pfn_range failed\n");
+ return -ENOBUFS;
+ }
+
+ return 0;
+}
+
+/*!
+ * This structure contains the pointers to the control functions that are
+ * invoked by the core framebuffer driver to perform operations like
+ * blitting, rectangle filling, copy regions and cursor definition.
+ */
+static struct fb_ops mxcfb_ops = {
+ .owner = THIS_MODULE,
+ .fb_set_par = mxcfb_set_par,
+ .fb_check_var = mxcfb_check_var,
+ .fb_setcolreg = mxcfb_setcolreg,
+ .fb_pan_display = mxcfb_pan_display,
+ .fb_ioctl = mxcfb_ioctl,
+ .fb_mmap = mxcfb_mmap,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_blank = mxcfb_blank,
+};
+
+static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id)
+{
+ struct fb_info *fbi = dev_id;
+ struct mxcfb_info *mxc_fbi = fbi->par;
+
+ if (mxc_fbi->wait4vsync) {
+ complete(&mxc_fbi->vsync_complete);
+ ipu_disable_irq(mxc_fbi->ipu, irq);
+ mxc_fbi->wait4vsync = false;
+ } else {
+ up(&mxc_fbi->flip_sem);
+ ipu_disable_irq(mxc_fbi->ipu, irq);
+ }
+ 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(mxc_fbi->ipu, 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
+
+ if (mxc_fbi->ovfbi) {
+ struct mxcfb_info *mxc_fbi_fg =
+ (struct mxcfb_info *)mxc_fbi->ovfbi->par;
+
+ console_lock();
+ fb_set_suspend(mxc_fbi->ovfbi, 1);
+ saved_blank = mxc_fbi_fg->cur_blank;
+ mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi);
+ mxc_fbi_fg->next_blank = saved_blank;
+ console_unlock();
+ }
+
+ console_lock();
+ fb_set_suspend(fbi, 1);
+ saved_blank = mxc_fbi->cur_blank;
+ mxcfb_blank(FB_BLANK_POWERDOWN, fbi);
+ mxc_fbi->next_blank = saved_blank;
+ console_unlock();
+
+ return 0;
+}
+
+/*
+ * 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;
+
+ console_lock();
+ mxcfb_blank(mxc_fbi->next_blank, fbi);
+ fb_set_suspend(fbi, 0);
+ console_unlock();
+
+ if (mxc_fbi->ovfbi) {
+ struct mxcfb_info *mxc_fbi_fg =
+ (struct mxcfb_info *)mxc_fbi->ovfbi->par;
+ console_lock();
+ mxcfb_blank(mxc_fbi_fg->next_blank, mxc_fbi->ovfbi);
+ fb_set_suspend(mxc_fbi->ovfbi, 0);
+ console_unlock();
+ }
+
+ return 0;
+}
+
+/*
+ * 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;
+
+ console_lock();
+ /* swap only happen between DP-BG and DC, while DP-FG disable */
+ if (((mxcfbi->ipu_ch == MEM_BG_SYNC) &&
+ (strstr(buf, "1-layer-fb") != NULL)) ||
+ ((mxcfbi->ipu_ch == MEM_DC_SYNC) &&
+ (strstr(buf, "2-layer-fb-bg") != NULL))) {
+ struct fb_info *fbi_fg;
+
+ fbi_fg = found_registered_fb(MEM_FG_SYNC, mxcfbi->ipu_id);
+ if (fbi_fg)
+ fg_mxcfbi = (struct mxcfb_info *)fbi_fg->par;
+
+ if (!fg_mxcfbi ||
+ fg_mxcfbi->cur_blank == FB_BLANK_UNBLANK) {
+ dev_err(dev,
+ "Can not switch while fb2(fb-fg) is on.\n");
+ console_unlock();
+ return count;
+ }
+
+ if (swap_channels(info) < 0)
+ dev_err(dev, "Swap display channel failed.\n");
+ }
+
+ console_unlock();
+ return count;
+}
+DEVICE_ATTR(fsl_disp_property, 644, show_disp_chan, swap_disp_chan);
+
+static int mxcfb_dispdrv_init(struct platform_device *pdev,
+ struct fb_info *fbi)
+{
+ struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
+ struct mxc_dispdrv_setting setting;
+ char disp_dev[32], *default_dev = "lcd";
+ int ret = 0;
+
+ setting.if_fmt = plat_data->interface_pix_fmt;
+ setting.dft_mode_str = plat_data->mode_str;
+ setting.default_bpp = plat_data->default_bpp;
+ if (!setting.default_bpp)
+ setting.default_bpp = 16;
+ setting.fbi = fbi;
+ if (!strlen(plat_data->disp_dev)) {
+ memcpy(disp_dev, default_dev, strlen(default_dev));
+ disp_dev[strlen(default_dev)] = '\0';
+ } else {
+ memcpy(disp_dev, plat_data->disp_dev,
+ strlen(plat_data->disp_dev));
+ disp_dev[strlen(plat_data->disp_dev)] = '\0';
+ }
+
+ dev_info(&pdev->dev, "register mxc display driver %s\n", disp_dev);
+
+ ret = mxc_dispdrv_init(disp_dev, &setting);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "register mxc display driver failed with %d\n", ret);
+ } else {
+ /* fix-up */
+ mxcfbi->ipu_di_pix_fmt = setting.if_fmt;
+ mxcfbi->default_bpp = setting.default_bpp;
+
+ /* setting */
+ mxcfbi->ipu_id = setting.dev_id;
+ mxcfbi->ipu_di = setting.disp_id;
+ }
+
+ return ret;
+}
+
+/*
+ * Parse user specified options (`video=trident:')
+ * example:
+ * video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,bpp=16,noaccel
+ */
+static int mxcfb_option_setup(struct platform_device *pdev)
+{
+ struct ipuv3_fb_platform_data *pdata = pdev->dev.platform_data;
+ char *options, *opt, *fb_mode_str = NULL;
+ char name[] = "mxcfb0";
+
+ name[5] += pdev->id;
+ fb_get_options(name, &options);
+
+ if (!options || !*options)
+ return 0;
+
+ while ((opt = strsep(&options, ",")) != NULL) {
+ if (!*opt)
+ continue;
+
+ if (!strncmp(opt, "dev=", 4)) {
+ memcpy(pdata->disp_dev, opt + 4, strlen(opt) - 4);
+ pdata->disp_dev[strlen(opt) - 4] = '\0';
+ continue;
+ }
+ if (!strncmp(opt, "if=", 3)) {
+ if (!strncmp(opt+3, "RGB24", 5)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_RGB24;
+ continue;
+ } else if (!strncmp(opt+6, "BGR24", 5)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_BGR24;
+ continue;
+ }
+ if (!strncmp(opt+3, "GBR24", 5)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_GBR24;
+ continue;
+ }
+ if (!strncmp(opt+3, "RGB565", 6)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_RGB565;
+ continue;
+ }
+ if (!strncmp(opt+3, "RGB666", 6)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_RGB666;
+ continue;
+ }
+ if (!strncmp(opt+3, "YUV444", 6)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_YUV444;
+ continue;
+ }
+ if (!strncmp(opt+3, "LVDS666", 7)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_LVDS666;
+ continue;
+ }
+ if (!strncmp(opt+3, "YUYV16", 6)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_YUYV;
+ continue;
+ }
+ if (!strncmp(opt+3, "UYVY16", 6)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_UYVY;
+ continue;
+ }
+ if (!strncmp(opt+3, "YVYU16", 6)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_YVYU;
+ continue;
+ }
+ if (!strncmp(opt+3, "VYUY16", 6)) {
+ pdata->interface_pix_fmt = IPU_PIX_FMT_VYUY;
+ continue;
+ }
+ }
+ if (!strncmp(opt, "int_clk", 7)) {
+ pdata->int_clk = true;
+ continue;
+ }
+ if (!strncmp(opt, "bpp=", 4))
+ pdata->default_bpp =
+ simple_strtoul(opt + 4, NULL, 0);
+ else
+ fb_mode_str = opt;
+ }
+
+ if (fb_mode_str)
+ pdata->mode_str = fb_mode_str;
+
+ return 0;
+}
+
+static int mxcfb_register(struct fb_info *fbi)
+{
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
+ struct fb_videomode m;
+ int ret = 0;
+ char bg0_id[] = "DISP3 BG";
+ char bg1_id[] = "DISP3 BG - DI1";
+ char fg_id[] = "DISP3 FG";
+
+ if (mxcfbi->ipu_di == 0) {
+ bg0_id[4] += mxcfbi->ipu_id;
+ strcpy(fbi->fix.id, bg0_id);
+ } else if (mxcfbi->ipu_di == 1) {
+ bg1_id[4] += mxcfbi->ipu_id;
+ strcpy(fbi->fix.id, bg1_id);
+ } else { /* Overlay */
+ fg_id[4] += mxcfbi->ipu_id;
+ strcpy(fbi->fix.id, fg_id);
+ }
+
+ if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, mxcfb_irq_handler, 0,
+ MXCFB_NAME, fbi) != 0) {
+ dev_err(fbi->device, "Error registering BG irq handler.\n");
+ ret = -EBUSY;
+ goto err0;
+ }
+ ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq);
+
+ if (mxcfbi->ipu_alp_ch_irq != -1)
+ if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq,
+ mxcfb_alpha_irq_handler, 0,
+ MXCFB_NAME, fbi) != 0) {
+ dev_err(fbi->device, "Error registering alpha irq "
+ "handler.\n");
+ ret = -EBUSY;
+ goto err1;
+ }
+
+ mxcfb_check_var(&fbi->var, fbi);
+
+ mxcfb_set_fix(fbi);
+
+ /*added first mode to fbi modelist*/
+ if (!fbi->modelist.next || !fbi->modelist.prev)
+ INIT_LIST_HEAD(&fbi->modelist);
+ fb_var_to_videomode(&m, &fbi->var);
+ fb_add_videomode(&m, &fbi->modelist);
+
+ fbi->var.activate |= FB_ACTIVATE_FORCE;
+ console_lock();
+ fbi->flags |= FBINFO_MISC_USEREVENT;
+ ret = fb_set_var(fbi, &fbi->var);
+ fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+ if (ret < 0)
+ goto err2;
+
+ if (mxcfbi->next_blank == FB_BLANK_UNBLANK) {
+ console_lock();
+ fb_blank(fbi, FB_BLANK_UNBLANK);
+ console_unlock();
+ }
+
+ ret = register_framebuffer(fbi);
+ if (ret < 0)
+ goto err2;
+
+ return ret;
+err2:
+ if (mxcfbi->ipu_alp_ch_irq != -1)
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi);
+err1:
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi);
+err0:
+ return ret;
+}
+
+static void mxcfb_unregister(struct fb_info *fbi)
+{
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
+
+ if (mxcfbi->ipu_alp_ch_irq != -1)
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi);
+ if (mxcfbi->ipu_ch_irq)
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi);
+
+ unregister_framebuffer(fbi);
+}
+
+static int mxcfb_setup_overlay(struct platform_device *pdev,
+ struct fb_info *fbi_bg)
+{
+ struct fb_info *ovfbi;
+ struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par;
+ struct mxcfb_info *mxcfbi_fg;
+ int ret = 0;
+
+ ovfbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
+ if (!ovfbi) {
+ ret = -ENOMEM;
+ goto init_ovfbinfo_failed;
+ }
+ mxcfbi_fg = (struct mxcfb_info *)ovfbi->par;
+
+ mxcfbi_fg->ipu = ipu_get_soc(mxcfbi_bg->ipu_id);
+ if (IS_ERR(mxcfbi_fg->ipu)) {
+ ret = -ENODEV;
+ goto get_ipu_failed;
+ }
+ mxcfbi_fg->ipu_id = mxcfbi_bg->ipu_id;
+ mxcfbi_fg->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF;
+ mxcfbi_fg->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF;
+ mxcfbi_fg->ipu_ch = MEM_FG_SYNC;
+ mxcfbi_fg->ipu_di = -1;
+ mxcfbi_fg->ipu_di_pix_fmt = mxcfbi_bg->ipu_di_pix_fmt;
+ mxcfbi_fg->overlay = true;
+ mxcfbi_fg->cur_blank = mxcfbi_fg->next_blank = FB_BLANK_POWERDOWN;
+
+ /* Need dummy values until real panel is configured */
+ ovfbi->var.xres = 240;
+ ovfbi->var.yres = 320;
+
+ ret = mxcfb_register(ovfbi);
+ if (ret < 0)
+ goto register_ov_failed;
+
+ mxcfbi_bg->ovfbi = ovfbi;
+
+ return ret;
+
+register_ov_failed:
+get_ipu_failed:
+ fb_dealloc_cmap(&ovfbi->cmap);
+ framebuffer_release(ovfbi);
+init_ovfbinfo_failed:
+ return ret;
+}
+
+static void mxcfb_unsetup_overlay(struct fb_info *fbi_bg)
+{
+ struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par;
+ struct fb_info *ovfbi = mxcfbi_bg->ovfbi;
+
+ mxcfb_unregister(ovfbi);
+
+ if (&ovfbi->cmap)
+ fb_dealloc_cmap(&ovfbi->cmap);
+ framebuffer_release(ovfbi);
+}
+
+static bool ipu_usage[2][2];
+static int ipu_test_set_usage(int ipu, int di)
+{
+ if (ipu_usage[ipu][di])
+ return -EBUSY;
+ else
+ ipu_usage[ipu][di] = true;
+ return 0;
+}
+
+static void ipu_clear_usage(int ipu, int di)
+{
+ ipu_usage[ipu][di] = false;
+}
+
+/*!
+ * Probe routine for the framebuffer driver. It is called during the
+ * driver binding process. The following functions are performed in
+ * this routine: Framebuffer initialization, Memory allocation and
+ * mapping, Framebuffer registration, IPU initialization.
+ *
+ * @return Appropriate error code to the kernel common code
+ */
+static int mxcfb_probe(struct platform_device *pdev)
+{
+ struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+ struct fb_info *fbi;
+ struct mxcfb_info *mxcfbi;
+ struct resource *res;
+ int ret = 0;
+
+ /*
+ * Initialize FB structures
+ */
+ fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
+ if (!fbi) {
+ ret = -ENOMEM;
+ goto init_fbinfo_failed;
+ }
+
+ mxcfb_option_setup(pdev);
+
+ mxcfbi = (struct mxcfb_info *)fbi->par;
+ mxcfbi->ipu_int_clk = plat_data->int_clk;
+ ret = mxcfb_dispdrv_init(pdev, fbi);
+ if (ret < 0)
+ goto init_dispdrv_failed;
+
+ ret = ipu_test_set_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "ipu%d-di%d already in use\n",
+ mxcfbi->ipu_id, mxcfbi->ipu_di);
+ goto ipu_in_busy;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res && res->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);
+ }
+
+ mxcfbi->ipu = ipu_get_soc(mxcfbi->ipu_id);
+ if (IS_ERR(mxcfbi->ipu)) {
+ ret = -ENODEV;
+ goto get_ipu_failed;
+ }
+
+ /* first user uses DP with alpha feature */
+ if (!g_dp_in_use[mxcfbi->ipu_id]) {
+ mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF;
+ mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF;
+ mxcfbi->ipu_ch = MEM_BG_SYNC;
+ mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK;
+
+ ipu_disp_set_global_alpha(mxcfbi->ipu, mxcfbi->ipu_ch, true, 0x80);
+ ipu_disp_set_color_key(mxcfbi->ipu, mxcfbi->ipu_ch, false, 0);
+
+ ret = mxcfb_register(fbi);
+ if (ret < 0)
+ goto mxcfb_register_failed;
+
+ ret = mxcfb_setup_overlay(pdev, fbi);
+ if (ret < 0) {
+ mxcfb_unregister(fbi);
+ goto mxcfb_setupoverlay_failed;
+ }
+
+ g_dp_in_use[mxcfbi->ipu_id] = true;
+ } else {
+ mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF;
+ mxcfbi->ipu_alp_ch_irq = -1;
+ mxcfbi->ipu_ch = MEM_DC_SYNC;
+ mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN;
+
+ ret = mxcfb_register(fbi);
+ if (ret < 0)
+ goto mxcfb_register_failed;
+ }
+
+ platform_set_drvdata(pdev, fbi);
+
+ ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property);
+ if (ret)
+ dev_err(&pdev->dev, "Error %d on creating file\n", ret);
+
+#ifdef CONFIG_LOGO
+ fb_prepare_logo(fbi, 0);
+ fb_show_logo(fbi, 0);
+#endif
+
+ return 0;
+
+mxcfb_setupoverlay_failed:
+mxcfb_register_failed:
+get_ipu_failed:
+ ipu_clear_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
+ipu_in_busy:
+init_dispdrv_failed:
+ fb_dealloc_cmap(&fbi->cmap);
+ framebuffer_release(fbi);
+init_fbinfo_failed:
+ return ret;
+}
+
+static int mxcfb_remove(struct platform_device *pdev)
+{
+ struct fb_info *fbi = platform_get_drvdata(pdev);
+ struct mxcfb_info *mxc_fbi = fbi->par;
+
+ if (!fbi)
+ return 0;
+
+ mxcfb_blank(FB_BLANK_POWERDOWN, fbi);
+ mxcfb_unregister(fbi);
+ mxcfb_unmap_video_memory(fbi);
+
+ if (mxc_fbi->ovfbi) {
+ mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi);
+ mxcfb_unsetup_overlay(fbi);
+ mxcfb_unmap_video_memory(mxc_fbi->ovfbi);
+ }
+
+ ipu_clear_usage(mxc_fbi->ipu_id, mxc_fbi->ipu_di);
+ if (&fbi->cmap)
+ fb_dealloc_cmap(&fbi->cmap);
+ framebuffer_release(fbi);
+ return 0;
+}
+
+/*!
+ * This structure contains pointers to the power management callback functions.
+ */
+static struct platform_driver mxcfb_driver = {
+ .driver = {
+ .name = MXCFB_NAME,
+ },
+ .probe = mxcfb_probe,
+ .remove = mxcfb_remove,
+ .suspend = mxcfb_suspend,
+ .resume = mxcfb_resume,
+};
+
+/*!
+ * Main entry function for the framebuffer. The function registers the power
+ * management callback functions with the kernel and also registers the MXCFB
+ * callback functions with the core Linux framebuffer driver \b fbmem.c
+ *
+ * @return Error code indicating success or failure
+ */
+int __init mxcfb_init(void)
+{
+ return platform_driver_register(&mxcfb_driver);
+}
+
+void mxcfb_exit(void)
+{
+ platform_driver_unregister(&mxcfb_driver);
+}
+
+module_init(mxcfb_init);
+module_exit(mxcfb_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC framebuffer driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("fb");
diff --git a/drivers/video/mxc/mxc_lcdif.c b/drivers/video/mxc/mxc_lcdif.c
new file mode 100644
index 00000000000..d9d7fa306a8
--- /dev/null
+++ b/drivers/video/mxc/mxc_lcdif.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mxcfb.h>
+#include <linux/fsl_devices.h>
+#include "mxc_dispdrv.h"
+
+struct mxc_lcdif_data {
+ struct platform_device *pdev;
+ struct mxc_dispdrv_entry *disp_lcdif;
+};
+
+#define DISPDRV_LCD "lcd"
+
+static struct fb_videomode lcdif_modedb[] = {
+ {
+ /* 800x480 @ 57 Hz , pixel clk @ 27MHz */
+ "CLAA-WVGA", 57, 800, 480, 37037, 40, 60, 10, 10, 20, 10,
+ FB_SYNC_CLK_LAT_FALL,
+ FB_VMODE_NONINTERLACED,
+ 0,},
+ {
+ /* 800x480 @ 60 Hz , pixel clk @ 32MHz */
+ "SEIKO-WVGA", 60, 800, 480, 29850, 89, 164, 23, 10, 10, 10,
+ FB_SYNC_CLK_LAT_FALL,
+ FB_VMODE_NONINTERLACED,
+ 0,},
+};
+static int lcdif_modedb_sz = ARRAY_SIZE(lcdif_modedb);
+
+static int lcdif_init(struct mxc_dispdrv_entry *disp)
+{
+ int ret, i;
+ struct mxc_lcdif_data *lcdif = mxc_dispdrv_getdata(disp);
+ struct mxc_dispdrv_setting *setting = mxc_dispdrv_getsetting(disp);
+ struct fsl_mxc_lcd_platform_data *plat_data
+ = lcdif->pdev->dev.platform_data;
+ struct fb_videomode *modedb = lcdif_modedb;
+ int modedb_sz = lcdif_modedb_sz;
+
+ /* use platform defined ipu/di */
+ setting->dev_id = plat_data->ipu_id;
+ setting->disp_id = plat_data->disp_id;
+
+ ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str,
+ modedb, modedb_sz, NULL, setting->default_bpp);
+ if (!ret) {
+ fb_videomode_to_var(&setting->fbi->var, &modedb[0]);
+ setting->if_fmt = plat_data->default_ifmt;
+ }
+
+ INIT_LIST_HEAD(&setting->fbi->modelist);
+ for (i = 0; i < modedb_sz; i++) {
+ struct fb_videomode m;
+ fb_var_to_videomode(&m, &setting->fbi->var);
+ if (fb_mode_is_equal(&m, &modedb[i])) {
+ fb_add_videomode(&modedb[i],
+ &setting->fbi->modelist);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void lcdif_deinit(struct mxc_dispdrv_entry *disp)
+{
+ /*TODO*/
+}
+
+static struct mxc_dispdrv_driver lcdif_drv = {
+ .name = DISPDRV_LCD,
+ .init = lcdif_init,
+ .deinit = lcdif_deinit,
+};
+
+static int mxc_lcdif_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct mxc_lcdif_data *lcdif;
+
+ lcdif = kzalloc(sizeof(struct mxc_lcdif_data), GFP_KERNEL);
+ if (!lcdif) {
+ ret = -ENOMEM;
+ goto alloc_failed;
+ }
+
+ lcdif->pdev = pdev;
+ lcdif->disp_lcdif = mxc_dispdrv_register(&lcdif_drv);
+ mxc_dispdrv_setdata(lcdif->disp_lcdif, lcdif);
+
+ dev_set_drvdata(&pdev->dev, lcdif);
+
+alloc_failed:
+ return ret;
+}
+
+static int mxc_lcdif_remove(struct platform_device *pdev)
+{
+ struct mxc_lcdif_data *lcdif = dev_get_drvdata(&pdev->dev);
+
+ mxc_dispdrv_unregister(lcdif->disp_lcdif);
+ kfree(lcdif);
+ return 0;
+}
+
+static struct platform_driver mxc_lcdif_driver = {
+ .driver = {
+ .name = "mxc_lcdif",
+ },
+ .probe = mxc_lcdif_probe,
+ .remove = mxc_lcdif_remove,
+};
+
+static int __init mxc_lcdif_init(void)
+{
+ return platform_driver_register(&mxc_lcdif_driver);
+}
+
+static void __exit mxc_lcdif_exit(void)
+{
+ platform_driver_unregister(&mxc_lcdif_driver);
+}
+
+module_init(mxc_lcdif_init);
+module_exit(mxc_lcdif_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX ipuv3 LCD extern port driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/mxc/mxcfb_sii902x.c b/drivers/video/mxc/mxcfb_sii902x.c
new file mode 100644
index 00000000000..17ce8fca68b
--- /dev/null
+++ b/drivers/video/mxc/mxcfb_sii902x.c
@@ -0,0 +1,1307 @@
+/*
+ * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*!
+ * @defgroup Framebuffer Framebuffer Driver for SDC and ADC.
+ */
+
+/*!
+ * @file mxcfb_sii902x.c
+ *
+ * @brief MXC Frame buffer driver for SII902x
+ *
+ * @ingroup Framebuffer
+ */
+
+/*!
+ * Include files
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/i2c.h>
+#include <linux/ipu.h>
+#include <linux/mxcfb.h>
+#include <linux/fsl_devices.h>
+#include <linux/interrupt.h>
+#include <linux/uaccess.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include "mxc_edid.h"
+#include "mxc_dispdrv.h"
+
+#define DISPDRV_SII "hdmi"
+
+#define TPI_PIX_CLK_LSB (0x00)
+#define TPI_PIX_CLK_MSB (0x01)
+#define TPI_VERT_FREQ_LSB (0x02)
+#define TPI_VERT_FREQ_MSB (0x03)
+#define TPI_TOTAL_PIX_LSB (0x04)
+#define TPI_TOTAL_PIX_MSB (0x05)
+#define TPI_TOTAL_LINES_LSB (0x06)
+#define TPI_TOTAL_LINES_MSB (0x07)
+#define TPI_PIX_REPETITION (0x08)
+#define TPI_INPUT_FORMAT_REG (0x09)
+#define TPI_OUTPUT_FORMAT_REG (0x0A)
+
+#define TPI_AVI_BYTE_0 (0x0C)
+#define TPI_AVI_BYTE_1 (0x0D)
+#define TPI_AVI_BYTE_2 (0x0E)
+#define TPI_AVI_BYTE_3 (0x0F)
+#define TPI_AVI_BYTE_4 (0x10)
+#define TPI_AVI_BYTE_5 (0x11)
+
+#define TPI_END_TOP_BAR_LSB (0x12)
+#define TPI_END_TOP_BAR_MSB (0x13)
+
+#define TPI_START_BTM_BAR_LSB (0x14)
+#define TPI_START_BTM_BAR_MSB (0x15)
+
+#define TPI_END_LEFT_BAR_LSB (0x16)
+#define TPI_END_LEFT_BAR_MSB (0x17)
+
+#define TPI_END_RIGHT_BAR_LSB (0x18)
+#define TPI_END_RIGHT_BAR_MSB (0x19)
+
+#define TPI_SYSTEM_CONTROL_DATA_REG (0x1A)
+#define TPI_DEVICE_ID (0x1B)
+#define TPI_DEVICE_REV_ID (0x1C)
+#define TPI_RESERVED2 (0x1D)
+#define TPI_DEVICE_POWER_STATE_CTRL_REG (0x1E)
+
+#define TPI_I2S_EN (0x1F)
+#define TPI_I2S_IN_CFG (0x20)
+#define TPI_I2S_CHST_0 (0x21)
+#define TPI_I2S_CHST_1 (0x22)
+#define TPI_I2S_CHST_2 (0x23)
+#define TPI_I2S_CHST_3 (0x24)
+#define TPI_I2S_CHST_4 (0x25)
+
+#define TPI_AUDIO_HANDLING (0x25)
+#define TPI_AUDIO_INTERFACE_REG (0x26)
+#define TPI_AUDIO_SAMPLE_CTRL (0x27)
+
+#define TPI_INTERRUPT_ENABLE_REG (0x3C)
+#define TPI_INTERRUPT_STATUS_REG (0x3D)
+
+#define TPI_INTERNAL_PAGE_REG 0xBC
+#define TPI_INDEXED_OFFSET_REG 0xBD
+#define TPI_INDEXED_VALUE_REG 0xBE
+
+#define MISC_INFO_FRAMES_CTRL (0xBF)
+#define MISC_INFO_FRAMES_TYPE (0xC0)
+#define EN_AND_RPT_AUDIO 0xC2
+#define DISABLE_AUDIO 0x02
+
+#define TPI_ENABLE (0xC7)
+
+#define INDEXED_PAGE_0 0x01
+#define INDEXED_PAGE_1 0x02
+#define INDEXED_PAGE_2 0x03
+
+#define HOT_PLUG_EVENT 0x01
+#define RX_SENSE_EVENT 0x02
+#define HOT_PLUG_STATE 0x04
+#define RX_SENSE_STATE 0x08
+
+#define OUTPUT_MODE_MASK (0x01)
+#define OUTPUT_MODE_DVI (0x00)
+#define OUTPUT_MODE_HDMI (0x01)
+
+#define LINK_INTEGRITY_MODE_MASK 0x40
+#define LINK_INTEGRITY_STATIC (0x00)
+#define LINK_INTEGRITY_DYNAMIC (0x40)
+
+#define TMDS_OUTPUT_CONTROL_MASK 0x10
+#define TMDS_OUTPUT_CONTROL_ACTIVE (0x00)
+#define TMDS_OUTPUT_CONTROL_POWER_DOWN (0x10)
+
+#define AV_MUTE_MASK 0x08
+#define AV_MUTE_NORMAL (0x00)
+#define AV_MUTE_MUTED (0x08)
+
+#define TX_POWER_STATE_MASK 0x3
+#define TX_POWER_STATE_D0 (0x00)
+#define TX_POWER_STATE_D1 (0x01)
+#define TX_POWER_STATE_D2 (0x02)
+#define TX_POWER_STATE_D3 (0x03)
+
+#define AUDIO_MUTE_MASK 0x10
+#define AUDIO_MUTE_NORMAL (0x00)
+#define AUDIO_MUTE_MUTED (0x10)
+
+#define AUDIO_SEL_MASK 0xC0
+#define AUD_IF_SPDIF 0x40
+#define AUD_IF_I2S 0x80
+#define AUD_IF_DSD 0xC0
+#define AUD_IF_HBR 0x04
+
+#define REFER_TO_STREAM_HDR 0x00
+
+#define AUD_PASS_BASIC 0x00
+#define AUD_PASS_ALL 0x01
+#define AUD_DOWN_SAMPLE 0x02
+#define AUD_DO_NOT_CHECK 0x03
+
+#define BITS_IN_RGB 0x00
+#define BITS_IN_YCBCR444 0x01
+#define BITS_IN_YCBCR422 0x02
+
+#define BITS_IN_AUTO_RANGE 0x00
+#define BITS_IN_FULL_RANGE 0x04
+#define BITS_IN_LTD_RANGE 0x08
+
+#define BIT_EN_DITHER_10_8 0x40
+#define BIT_EXTENDED_MODE 0x80
+
+#define SII_EDID_LEN 512
+#define SIZE_AVI_INFOFRAME 0x0E
+#define SIZE_AUDIO_INFOFRAME 0x0F
+
+#define _4_To_3 0x10
+#define _16_To_9 0x20
+#define SAME_AS_AR 0x08
+
+struct sii902x_data {
+ struct platform_device *pdev;
+ struct i2c_client *client;
+ struct mxc_dispdrv_entry *disp_hdmi;
+ struct regulator *io_reg;
+ struct regulator *analog_reg;
+ struct delayed_work det_work;
+ struct fb_info *fbi;
+ struct mxc_edid_cfg edid_cfg;
+ bool cable_plugin;
+ bool rx_powerup;
+ bool need_mode_change;
+ u8 edid[SII_EDID_LEN];
+ struct notifier_block nb;
+
+ u8 power_state;
+ u8 tpivmode[3];
+ u8 pixrep;
+
+ /* SII902x video setting:
+ * 1. hdmi video fmt:
+ * 0 = CEA-861 VIC; 1 = HDMI_VIC; 2 = 3D
+ * 2. vic: video mode index
+ * 3. aspect ratio:
+ * 4x3 or 16x9
+ * 4. color space:
+ * 0 = RGB; 1 = YCbCr4:4:4; 2 = YCbCr4:2:2_16bits;
+ * 3 = YCbCr4:2:2_8bits;4 = xvYCC4:4:4
+ * 5. color depth:
+ * 0 = 8bits; 1 = 10bits; 2 = 12bits; 3 = 16bits
+ * 6. colorimetry:
+ * 0 = 601; 1 = 709
+ * 7. syncmode:
+ * 0 = external HS/VS/DE; 1 = external HS/VS and internal DE;
+ * 2 = embedded sync
+ */
+#define VMD_HDMIFORMAT_CEA_VIC 0x00
+#define VMD_HDMIFORMAT_HDMI_VIC 0x01
+#define VMD_HDMIFORMAT_3D 0x02
+#define VMD_HDMIFORMAT_PC 0x03
+ u8 hdmi_vid_fmt;
+ u8 vic;
+#define VMD_ASPECT_RATIO_4x3 0x01
+#define VMD_ASPECT_RATIO_16x9 0x02
+ u8 aspect_ratio;
+#define RGB 0
+#define YCBCR444 1
+#define YCBCR422_16BITS 2
+#define YCBCR422_8BITS 3
+#define XVYCC444 4
+ u8 icolor_space;
+ u8 ocolor_space;
+#define VMD_COLOR_DEPTH_8BIT 0x00
+#define VMD_COLOR_DEPTH_10BIT 0x01
+#define VMD_COLOR_DEPTH_12BIT 0x02
+#define VMD_COLOR_DEPTH_16BIT 0x03
+ u8 color_depth;
+#define COLORIMETRY_601 0
+#define COLORIMETRY_709 1
+ u8 colorimetry;
+#define EXTERNAL_HSVSDE 0
+#define INTERNAL_DE 1
+#define EMBEDDED_SYNC 2
+ u8 syncmode;
+ u8 threeDstruct;
+ u8 threeDextdata;
+
+#define AMODE_I2S 0
+#define AMODE_SPDIF 1
+#define AMODE_HBR 2
+#define AMODE_DSD 3
+ u8 audio_mode;
+#define ACHANNEL_2CH 1
+#define ACHANNEL_3CH 2
+#define ACHANNEL_4CH 3
+#define ACHANNEL_5CH 4
+#define ACHANNEL_6CH 5
+#define ACHANNEL_7CH 6
+#define ACHANNEL_8CH 7
+ u8 audio_channels;
+ u8 audiofs;
+ u8 audio_word_len;
+ u8 audio_i2s_fmt;
+};
+
+static __attribute__ ((unused)) void dump_regs(struct sii902x_data *sii902x,
+ u8 reg, int len)
+{
+ u8 buf[50];
+ int i;
+
+ i2c_smbus_read_i2c_block_data(sii902x->client, reg, len, buf);
+ for (i = 0; i < len; i++)
+ dev_dbg(&sii902x->client->dev, "reg[0x%02X]: 0x%02X\n",
+ i+reg, buf[i]);
+}
+
+static ssize_t sii902x_show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sii902x_data *sii902x = dev_get_drvdata(dev);
+
+ strcpy(buf, sii902x->fbi->fix.id);
+ sprintf(buf+strlen(buf), "\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(fb_name, S_IRUGO, sii902x_show_name, NULL);
+
+static ssize_t sii902x_show_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sii902x_data *sii902x = dev_get_drvdata(dev);
+
+ if (sii902x->cable_plugin == false)
+ strcpy(buf, "plugout\n");
+ else
+ strcpy(buf, "plugin\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(cable_state, S_IRUGO, sii902x_show_state, NULL);
+
+static ssize_t sii902x_show_edid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sii902x_data *sii902x = dev_get_drvdata(dev);
+ int i, j, len = 0;
+
+ for (j = 0; j < SII_EDID_LEN/16; j++) {
+ for (i = 0; i < 16; i++)
+ len += sprintf(buf+len, "0x%02X ",
+ sii902x->edid[j*16 + i]);
+ len += sprintf(buf+len, "\n");
+ }
+
+ return len;
+}
+
+static DEVICE_ATTR(edid, S_IRUGO, sii902x_show_edid, NULL);
+
+/*------------------------------------------------------------------------------
+ * Function Description: Write "0" to all bits in TPI offset "Offset" that are set
+ * to "1" in "Pattern"; Leave all other bits in "Offset"
+ * unchanged.
+ *-----------------------------------------------------------------------------
+ */
+void read_clr_write_tpi(struct i2c_client *client, u8 offset, u8 mask)
+{
+ u8 tmp;
+
+ tmp = i2c_smbus_read_byte_data(client, offset);
+ tmp &= ~mask;
+ i2c_smbus_write_byte_data(client, offset, tmp);
+}
+
+/*------------------------------------------------------------------------------
+ * Function Description: Write "1" to all bits in TPI offset "Offset" that are set
+ * to "1" in "Pattern"; Leave all other bits in "Offset"
+ * unchanged.
+ *-----------------------------------------------------------------------------
+ */
+void read_set_write_tpi(struct i2c_client *client, u8 offset, u8 mask)
+{
+ u8 tmp;
+
+ tmp = i2c_smbus_read_byte_data(client, offset);
+ tmp |= mask;
+ i2c_smbus_write_byte_data(client, offset, tmp);
+}
+
+/*------------------------------------------------------------------------------
+ * Function Description: Write "Value" to all bits in TPI offset "Offset" that are set
+ * to "1" in "Mask"; Leave all other bits in "Offset"
+ * unchanged.
+ *----------------------------------------------------------------------------
+ */
+void read_modify_tpi(struct i2c_client *client, u8 offset, u8 mask, u8 value)
+{
+ u8 tmp;
+
+ tmp = i2c_smbus_read_byte_data(client, offset);
+ tmp &= ~mask;
+ tmp |= (value & mask);
+ i2c_smbus_write_byte_data(client, offset, tmp);
+}
+
+/*------------------------------------------------------------------------------
+ * Function Description: Read an indexed register value
+ * Write:
+ * 1. 0xBC => Internal page num
+ * 2. 0xBD => Indexed register offset
+ * Read:
+ * 3. 0xBE => Returns the indexed register value
+ *----------------------------------------------------------------------------
+ */
+int read_idx_reg(struct i2c_client *client, u8 page, u8 regoffset)
+{
+ i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page);
+ i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset);
+ return i2c_smbus_read_byte_data(client, TPI_INDEXED_VALUE_REG);
+}
+
+/*------------------------------------------------------------------------------
+ * Function Description: Write a value to an indexed register
+ *
+ * Write:
+ * 1. 0xBC => Internal page num
+ * 2. 0xBD => Indexed register offset
+ * 3. 0xBE => Set the indexed register value
+ *------------------------------------------------------------------------------
+ */
+void write_idx_reg(struct i2c_client *client, u8 page, u8 regoffset, u8 regval)
+{
+ i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page);
+ i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset);
+ i2c_smbus_write_byte_data(client, TPI_INDEXED_VALUE_REG, regval);
+}
+
+/*------------------------------------------------------------------------------
+ * Function Description: Write "Value" to all bits in TPI offset "Offset" that are set
+ * to "1" in "Mask"; Leave all other bits in "Offset"
+ * unchanged.
+ *----------------------------------------------------------------------------
+ */
+void read_modify_idx_reg(struct i2c_client *client, u8 page, u8 regoffset, u8 mask, u8 value)
+{
+ u8 tmp;
+
+ i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page);
+ i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset);
+ tmp = i2c_smbus_read_byte_data(client, TPI_INDEXED_VALUE_REG);
+ tmp &= ~mask;
+ tmp |= (value & mask);
+ i2c_smbus_write_byte_data(client, TPI_INDEXED_VALUE_REG, tmp);
+}
+
+static void sii902x_set_powerstate(struct sii902x_data *sii902x, u8 state)
+{
+ if (sii902x->power_state != state) {
+ read_modify_tpi(sii902x->client, TPI_DEVICE_POWER_STATE_CTRL_REG,
+ TX_POWER_STATE_MASK, state);
+ sii902x->power_state = state;
+ }
+}
+
+static void sii902x_setAVI(struct sii902x_data *sii902x)
+{
+ u8 avi_data[SIZE_AVI_INFOFRAME];
+ u8 tmp;
+ int i;
+
+ dev_dbg(&sii902x->client->dev, "set AVI frame\n");
+
+ memset(avi_data, 0, SIZE_AVI_INFOFRAME);
+
+ if (sii902x->edid_cfg.cea_ycbcr444)
+ tmp = 2;
+ else if (sii902x->edid_cfg.cea_ycbcr422)
+ tmp = 1;
+ else
+ tmp = 0;
+
+ /* AVI byte1: Y1Y0 (output format) */
+ avi_data[1] = (tmp << 5) & 0x60;
+ /* A0 = 1; Active format identification data is present in the AVI InfoFrame.
+ * S1:S0 = 00;
+ */
+ avi_data[1] |= 0x10;
+
+ if (sii902x->ocolor_space == XVYCC444) {
+ avi_data[2] = 0xC0;
+ if (sii902x->colorimetry == COLORIMETRY_601)
+ avi_data[3] &= ~0x70;
+ else if (sii902x->colorimetry == COLORIMETRY_709)
+ avi_data[3] = (avi_data[3] & ~0x70) | 0x10;
+ } else if (sii902x->ocolor_space != RGB) {
+ if (sii902x->colorimetry == COLORIMETRY_709)
+ avi_data[2] = 0x80;/* AVI byte2: C1C0*/
+ else if (sii902x->colorimetry == COLORIMETRY_601)
+ avi_data[2] = 0x40;/* AVI byte2: C1C0 */
+ } else {/* Carries no data */
+ /* AVI Byte2: C1C0 */
+ avi_data[2] &= ~0xc0; /* colorimetry = 0 */
+ avi_data[3] &= ~0x70; /* Extended colorimetry = 0 */
+ }
+
+ avi_data[4] = sii902x->vic;
+
+ /* Set the Aspect Ration info into the Infoframe Byte 2 */
+ if (sii902x->aspect_ratio == VMD_ASPECT_RATIO_16x9)
+ avi_data[2] |= _16_To_9; /* AVI Byte2: M1M0 */
+ else
+ avi_data[2] |= _4_To_3;
+
+ avi_data[2] |= SAME_AS_AR; /* AVI Byte2: R3..R1 - Set to "Same as Picture Aspect Ratio" */
+ avi_data[5] = sii902x->pixrep; /* AVI Byte5: Pixel Replication - PR3..PR0 */
+
+ /* Calculate AVI InfoFrame ChecKsum */
+ avi_data[0] = 0x82 + 0x02 + 0x0D;
+ for (i = 1; i < SIZE_AVI_INFOFRAME; i++)
+ avi_data[0] += avi_data[i];
+ avi_data[0] = 0x100 - avi_data[0];
+
+ /* Write the Inforframe data to the TPI Infoframe registers */
+ for (i = 0; i < SIZE_AVI_INFOFRAME; i++)
+ i2c_smbus_write_byte_data(sii902x->client,
+ TPI_AVI_BYTE_0 + i, avi_data[i]);
+
+ dump_regs(sii902x, TPI_AVI_BYTE_0, SIZE_AVI_INFOFRAME);
+}
+
+#define TYPE_AUDIO_INFOFRAMES 0x84
+#define AUDIO_INFOFRAMES_VERSION 0x01
+#define AUDIO_INFOFRAMES_LENGTH 0x0A
+/*------------------------------------------------------------------------------
+* Function Description: Load Audio InfoFrame data into registers and send to sink
+*
+* Accepts: (1) Channel count
+* (2) speaker configuration per CEA-861D Tables 19, 20
+* (3) Coding type: 0x09 for DSD Audio. 0 (refer to stream header) for all the rest
+* (4) Sample Frequency. Non zero for HBR only
+* (5) Audio Sample Length. Non zero for HBR only.
+*------------------------------------------------------------------------------
+*/
+static void sii902x_setAIF(struct sii902x_data *sii902x,
+ u8 codingtype, u8 sample_size, u8 sample_freq,
+ u8 speaker_cfg)
+{
+ u8 aif_data[SIZE_AUDIO_INFOFRAME];
+ u8 channel_count = sii902x->audio_channels & 0x07;
+ int i;
+
+ dev_dbg(&sii902x->client->dev, "set AIF frame\n");
+
+ memset(aif_data, 0, SIZE_AUDIO_INFOFRAME);
+
+ /* Disbale MPEG/Vendor Specific InfoFrames */
+ i2c_smbus_write_byte_data(sii902x->client, MISC_INFO_FRAMES_CTRL, DISABLE_AUDIO);
+
+ aif_data[0] = TYPE_AUDIO_INFOFRAMES;
+ aif_data[1] = AUDIO_INFOFRAMES_VERSION;
+ aif_data[2] = AUDIO_INFOFRAMES_LENGTH;
+ /* Calculate checksum - 0x84 + 0x01 + 0x0A */
+ aif_data[3] = TYPE_AUDIO_INFOFRAMES +
+ AUDIO_INFOFRAMES_VERSION + AUDIO_INFOFRAMES_LENGTH;
+
+ aif_data[4] = channel_count; /* 0 for "Refer to Stream Header" or for 2 Channels. 0x07 for 8 Channels*/
+ aif_data[4] |= (codingtype << 4); /* 0xC7[7:4] == 0b1001 for DSD Audio */
+ aif_data[5] = ((sample_freq & 0x07) << 2) | (sample_size & 0x03);
+ aif_data[7] = speaker_cfg;
+
+ for (i = 4; i < SIZE_AUDIO_INFOFRAME; i++)
+ aif_data[3] += aif_data[i];
+
+ aif_data[3] = 0x100 - aif_data[3];
+
+ /* Re-enable Audio InfoFrame transmission and repeat */
+ i2c_smbus_write_byte_data(sii902x->client, MISC_INFO_FRAMES_CTRL, EN_AND_RPT_AUDIO);
+
+ for (i = 0; i < SIZE_AUDIO_INFOFRAME; i++)
+ i2c_smbus_write_byte_data(sii902x->client,
+ MISC_INFO_FRAMES_TYPE + i, aif_data[i]);
+
+ dump_regs(sii902x, MISC_INFO_FRAMES_TYPE, SIZE_AUDIO_INFOFRAME);
+}
+
+static void sii902x_setaudio(struct sii902x_data *sii902x)
+{
+ dev_dbg(&sii902x->client->dev, "set audio\n");
+
+ /* mute audio */
+ read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG,
+ AUDIO_MUTE_MASK, AUDIO_MUTE_MUTED);
+ if (sii902x->audio_mode == AMODE_I2S) {
+ read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG,
+ AUDIO_SEL_MASK, AUD_IF_I2S);
+ i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_HANDLING,
+ 0x08 | AUD_DO_NOT_CHECK);
+ } else {
+ read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG,
+ AUDIO_SEL_MASK, AUD_IF_SPDIF);
+ i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_HANDLING,
+ AUD_PASS_BASIC);
+ }
+
+ if (sii902x->audio_channels == ACHANNEL_2CH)
+ read_clr_write_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, 0x20);
+ else
+ read_set_write_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, 0x20);
+
+ if (sii902x->audio_mode == AMODE_I2S) {
+ /* I2S - Map channels */
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0x80);
+
+ if (sii902x->audio_channels > ACHANNEL_2CH)
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0x91);
+
+ if (sii902x->audio_channels > ACHANNEL_4CH)
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0xA2);
+
+ if (sii902x->audio_channels > ACHANNEL_6CH)
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0xB3);
+
+ /* I2S - Stream Header Settings */
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_0, 0x00);
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_1, 0x00);
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_2, 0x00);
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_3, sii902x->audiofs);
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_4,
+ (sii902x->audiofs << 4) | sii902x->audio_word_len);
+
+ /* added for 16bit auido noise issue */
+ write_idx_reg(sii902x->client, INDEXED_PAGE_1, 0x24, sii902x->audio_word_len);
+
+ /* I2S - Input Configuration */
+ i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_IN_CFG, sii902x->audio_i2s_fmt);
+ }
+
+ i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_SAMPLE_CTRL, REFER_TO_STREAM_HDR);
+
+ sii902x_setAIF(sii902x, REFER_TO_STREAM_HDR, REFER_TO_STREAM_HDR, REFER_TO_STREAM_HDR, 0x00);
+
+ /* unmute audio */
+ read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, AUDIO_MUTE_MASK, AUDIO_MUTE_NORMAL);
+}
+
+static void sii902x_setup(struct sii902x_data *sii902x, struct fb_info *fbi)
+{
+ u16 data[4];
+ u32 refresh;
+ u8 *tmp;
+ mm_segment_t old_fs;
+ unsigned int fmt;
+ int i;
+
+ dev_dbg(&sii902x->client->dev, "setup..\n");
+
+ sii902x->vic = mxc_edid_var_to_vic(&fbi->var);
+
+ /* set TPI video mode */
+ data[0] = PICOS2KHZ(fbi->var.pixclock) / 10;
+ data[2] = fbi->var.hsync_len + fbi->var.left_margin +
+ fbi->var.xres + fbi->var.right_margin;
+ data[3] = fbi->var.vsync_len + fbi->var.upper_margin +
+ fbi->var.yres + fbi->var.lower_margin;
+ refresh = data[2] * data[3];
+ refresh = (PICOS2KHZ(fbi->var.pixclock) * 1000) / refresh;
+ data[1] = refresh * 100;
+ tmp = (u8 *)data;
+ for (i = 0; i < 8; i++)
+ i2c_smbus_write_byte_data(sii902x->client, i, tmp[i]);
+
+ dump_regs(sii902x, 0, 8);
+
+ if (fbi->fbops->fb_ioctl) {
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ fbi->fbops->fb_ioctl(fbi, MXCFB_GET_DIFMT, (unsigned long)&fmt);
+ set_fs(old_fs);
+ if (fmt == IPU_PIX_FMT_VYU444) {
+ sii902x->icolor_space = YCBCR444;
+ dev_dbg(&sii902x->client->dev, "input color space YUV\n");
+ } else {
+ sii902x->icolor_space = RGB;
+ dev_dbg(&sii902x->client->dev, "input color space RGB\n");
+ }
+ }
+
+ /* reg 0x08: input bus/pixel: full pixel wide (24bit), rising edge */
+ sii902x->tpivmode[0] = 0x70;
+ /* reg 0x09: Set input format */
+ if (sii902x->icolor_space == RGB)
+ sii902x->tpivmode[1] =
+ (((BITS_IN_RGB | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE);
+ else if (sii902x->icolor_space == YCBCR444)
+ sii902x->tpivmode[1] =
+ (((BITS_IN_YCBCR444 | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE);
+ else if ((sii902x->icolor_space == YCBCR422_16BITS) || (sii902x->icolor_space == YCBCR422_8BITS))
+ sii902x->tpivmode[1] =
+ (((BITS_IN_YCBCR422 | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE);
+ /* reg 0x0a: set output format to RGB */
+ sii902x->tpivmode[2] = 0x00;
+
+ if (fbi->var.xres/16 == fbi->var.yres/9)
+ sii902x->aspect_ratio = VMD_ASPECT_RATIO_16x9;
+ else
+ sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3;
+
+ if ((sii902x->vic == 6) || (sii902x->vic == 7) ||
+ (sii902x->vic == 21) || (sii902x->vic == 22) ||
+ (sii902x->vic == 2) || (sii902x->vic == 3) ||
+ (sii902x->vic == 17) || (sii902x->vic == 18)) {
+ sii902x->tpivmode[2] &= ~0x10; /*BT.601*/
+ sii902x->colorimetry = COLORIMETRY_601;
+ sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3;
+ } else {
+ sii902x->tpivmode[2] |= 0x10; /*BT.709*/
+ sii902x->colorimetry = COLORIMETRY_709;
+ }
+
+ if ((sii902x->vic == 10) || (sii902x->vic == 11) ||
+ (sii902x->vic == 12) || (sii902x->vic == 13) ||
+ (sii902x->vic == 14) || (sii902x->vic == 15) ||
+ (sii902x->vic == 25) || (sii902x->vic == 26) ||
+ (sii902x->vic == 27) || (sii902x->vic == 28) ||
+ (sii902x->vic == 29) || (sii902x->vic == 30) ||
+ (sii902x->vic == 35) || (sii902x->vic == 36) ||
+ (sii902x->vic == 37) || (sii902x->vic == 38))
+ sii902x->pixrep = 1;
+ else
+ sii902x->pixrep = 0;
+
+ dev_dbg(&sii902x->client->dev, "vic %d\n", sii902x->vic);
+ dev_dbg(&sii902x->client->dev, "pixrep %d\n", sii902x->pixrep);
+ if (sii902x->aspect_ratio == VMD_ASPECT_RATIO_4x3) {
+ dev_dbg(&sii902x->client->dev, "aspect 4:3\n");
+ } else {
+ dev_dbg(&sii902x->client->dev, "aspect 16:9\n");
+ }
+ if (sii902x->colorimetry == COLORIMETRY_601) {
+ dev_dbg(&sii902x->client->dev, "COLORIMETRY_601\n");
+ } else {
+ dev_dbg(&sii902x->client->dev, "COLORIMETRY_709\n");
+ }
+ dev_dbg(&sii902x->client->dev, "hdmi capbility %d\n", sii902x->edid_cfg.hdmi_cap);
+
+ sii902x->ocolor_space = RGB;
+ if (sii902x->edid_cfg.hdmi_cap) {
+ if (sii902x->edid_cfg.cea_ycbcr444) {
+ sii902x->ocolor_space = YCBCR444;
+ sii902x->tpivmode[2] |= 0x1; /*Ycbcr444*/
+ } else if (sii902x->edid_cfg.cea_ycbcr422) {
+ sii902x->ocolor_space = YCBCR422_8BITS;
+ sii902x->tpivmode[2] |= 0x2; /*Ycbcr422*/
+ }
+ }
+
+ dev_dbg(&sii902x->client->dev, "write reg 0x08 0X%2X\n", sii902x->tpivmode[0]);
+ dev_dbg(&sii902x->client->dev, "write reg 0x09 0X%2X\n", sii902x->tpivmode[1]);
+ dev_dbg(&sii902x->client->dev, "write reg 0x0a 0X%2X\n", sii902x->tpivmode[2]);
+
+ i2c_smbus_write_byte_data(sii902x->client, TPI_PIX_REPETITION, sii902x->tpivmode[0]);
+ i2c_smbus_write_byte_data(sii902x->client, TPI_INPUT_FORMAT_REG, sii902x->tpivmode[1]);
+ i2c_smbus_write_byte_data(sii902x->client, TPI_OUTPUT_FORMAT_REG, sii902x->tpivmode[2]);
+
+ /* goto state D0*/
+ sii902x_set_powerstate(sii902x, TX_POWER_STATE_D0);
+
+ if (sii902x->edid_cfg.hdmi_cap) {
+ sii902x_setAVI(sii902x);
+ sii902x_setaudio(sii902x);
+ } else {
+ /* set last byte of TPI AVI InfoFrame for TPI AVI I/O format to take effect ?? */
+ i2c_smbus_write_byte_data(sii902x->client, TPI_END_RIGHT_BAR_MSB, 0x00);
+
+ /* mute audio */
+ read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG,
+ AUDIO_MUTE_MASK, AUDIO_MUTE_MUTED);
+ }
+}
+
+#ifdef CONFIG_FB_MODE_HELPERS
+static int sii902x_read_edid(struct sii902x_data *sii902x,
+ struct fb_info *fbi)
+{
+ int old, dat, ret, cnt = 100;
+ unsigned short addr = 0x50;
+ u8 edid_old[SII_EDID_LEN];
+
+ old = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG);
+
+ i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old | 0x4);
+ do {
+ cnt--;
+ msleep(10);
+ dat = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG);
+ } while ((!(dat & 0x2)) && cnt);
+
+ if (!cnt) {
+ ret = -1;
+ goto done;
+ }
+
+ i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old | 0x06);
+
+ /* save old edid */
+ memcpy(edid_old, sii902x->edid, SII_EDID_LEN);
+
+ /* edid reading */
+ ret = mxc_edid_read(sii902x->client->adapter, addr,
+ sii902x->edid, &sii902x->edid_cfg, fbi);
+
+ cnt = 100;
+ do {
+ cnt--;
+ i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old & ~0x6);
+ msleep(10);
+ dat = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG);
+ } while ((dat & 0x6) && cnt);
+
+ if (!cnt)
+ ret = -1;
+
+done:
+ i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old);
+
+ if (!memcmp(edid_old, sii902x->edid, SII_EDID_LEN))
+ ret = -2;
+ return ret;
+}
+#else
+static int sii902x_read_edid(struct sii902x_data *sii902x,
+ struct fb_info *fbi)
+{
+ return -1;
+}
+#endif
+
+static void sii902x_enable_tmds(struct sii902x_data *sii902x)
+{
+ /* goto state D0*/
+ sii902x_set_powerstate(sii902x, TX_POWER_STATE_D0);
+
+ /* Turn on DVI or HDMI */
+ if (sii902x->edid_cfg.hdmi_cap)
+ read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG,
+ OUTPUT_MODE_MASK, OUTPUT_MODE_HDMI);
+ else
+ read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG,
+ OUTPUT_MODE_MASK, OUTPUT_MODE_DVI);
+
+ read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG,
+ LINK_INTEGRITY_MODE_MASK | TMDS_OUTPUT_CONTROL_MASK | AV_MUTE_MASK,
+ LINK_INTEGRITY_DYNAMIC | TMDS_OUTPUT_CONTROL_ACTIVE | AV_MUTE_NORMAL);
+
+ i2c_smbus_write_byte_data(sii902x->client, TPI_PIX_REPETITION,
+ sii902x->tpivmode[0]);
+}
+
+static void sii902x_disable_tmds(struct sii902x_data *sii902x)
+{
+ read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG,
+ TMDS_OUTPUT_CONTROL_MASK | AV_MUTE_MASK | OUTPUT_MODE_MASK,
+ TMDS_OUTPUT_CONTROL_POWER_DOWN | AV_MUTE_MUTED | OUTPUT_MODE_DVI);
+
+ /* goto state D2*/
+ sii902x_set_powerstate(sii902x, TX_POWER_STATE_D2);
+}
+
+static void sii902x_poweron(struct sii902x_data *sii902x)
+{
+ struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data;
+
+ dev_dbg(&sii902x->client->dev, "power on\n");
+
+ /* Enable pins to HDMI */
+ if (plat->enable_pins)
+ plat->enable_pins();
+
+ if (sii902x->rx_powerup)
+ sii902x_enable_tmds(sii902x);
+}
+
+static void sii902x_poweroff(struct sii902x_data *sii902x)
+{
+ struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data;
+
+ dev_dbg(&sii902x->client->dev, "power off\n");
+
+ /* Disable pins to HDMI */
+ if (plat->disable_pins)
+ plat->disable_pins();
+
+ if (sii902x->rx_powerup)
+ sii902x_disable_tmds(sii902x);
+}
+
+static void sii902x_rx_powerup(struct sii902x_data *sii902x)
+{
+
+ dev_dbg(&sii902x->client->dev, "rx power up\n");
+
+ if (sii902x->need_mode_change) {
+ sii902x->fbi->var.activate |= FB_ACTIVATE_FORCE;
+ console_lock();
+ sii902x->fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(sii902x->fbi, &sii902x->fbi->var);
+ sii902x->fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+ sii902x->need_mode_change = false;
+ }
+
+ sii902x_enable_tmds(sii902x);
+
+ sii902x->rx_powerup = true;
+}
+
+static void sii902x_rx_powerdown(struct sii902x_data *sii902x)
+{
+ dev_dbg(&sii902x->client->dev, "rx power down\n");
+
+ sii902x_disable_tmds(sii902x);
+
+ sii902x->rx_powerup = false;
+}
+
+static int sii902x_cable_connected(struct sii902x_data *sii902x)
+{
+ int ret;
+
+ dev_dbg(&sii902x->client->dev, "cable connected\n");
+
+ sii902x->cable_plugin = true;
+
+ /* edid read */
+ ret = sii902x_read_edid(sii902x, sii902x->fbi);
+ if (ret == -1)
+ dev_err(&sii902x->client->dev,
+ "read edid fail\n");
+ else if (ret == -2)
+ dev_info(&sii902x->client->dev,
+ "same edid\n");
+ else {
+ if (sii902x->fbi->monspecs.modedb_len > 0) {
+ int i;
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+
+ fb_destroy_modelist(&sii902x->fbi->modelist);
+
+ for (i = 0; i < sii902x->fbi->monspecs.modedb_len; i++) {
+ /*FIXME now we do not support interlaced mode */
+ if (!(sii902x->fbi->monspecs.modedb[i].vmode & FB_VMODE_INTERLACED))
+ fb_add_videomode(&sii902x->fbi->monspecs.modedb[i],
+ &sii902x->fbi->modelist);
+ }
+
+ fb_var_to_videomode(&m, &sii902x->fbi->var);
+ mode = fb_find_nearest_mode(&m,
+ &sii902x->fbi->modelist);
+
+ fb_videomode_to_var(&sii902x->fbi->var, mode);
+ sii902x->need_mode_change = true;
+ }
+ }
+
+ /* ?? remain it for control back door register */
+ read_modify_idx_reg(sii902x->client, INDEXED_PAGE_0, 0x0a, 0x08, 0x08);
+
+ return 0;
+}
+
+static void sii902x_cable_disconnected(struct sii902x_data *sii902x)
+{
+ dev_dbg(&sii902x->client->dev, "cable disconnected\n");
+ sii902x_rx_powerdown(sii902x);
+ sii902x->cable_plugin = false;
+}
+
+static void det_worker(struct work_struct *work)
+{
+ struct delayed_work *delay_work = to_delayed_work(work);
+ struct sii902x_data *sii902x =
+ container_of(delay_work, struct sii902x_data, det_work);
+ int status;
+ char event_string[16];
+ char *envp[] = { event_string, NULL };
+
+ status = i2c_smbus_read_byte_data(sii902x->client, TPI_INTERRUPT_STATUS_REG);
+
+ /* check cable status */
+ if (status & HOT_PLUG_EVENT) {
+ /* cable connection changes */
+ if ((status & HOT_PLUG_STATE) != sii902x->cable_plugin) {
+ if (status & HOT_PLUG_STATE) {
+ sprintf(event_string, "EVENT=plugin");
+ sii902x_cable_connected(sii902x);
+ } else {
+ sprintf(event_string, "EVENT=plugout");
+ sii902x_cable_disconnected(sii902x);
+ }
+ kobject_uevent_env(&sii902x->pdev->dev.kobj, KOBJ_CHANGE, envp);
+ }
+ }
+
+ /* check rx power */
+ if (((status & RX_SENSE_STATE) >> 3) != sii902x->rx_powerup) {
+ if (sii902x->cable_plugin) {
+ if (status & RX_SENSE_STATE)
+ sii902x_rx_powerup(sii902x);
+ else
+ sii902x_rx_powerdown(sii902x);
+ }
+ }
+
+ /* clear interrupt pending status */
+ i2c_smbus_write_byte_data(sii902x->client, TPI_INTERRUPT_STATUS_REG, status);
+}
+
+static irqreturn_t sii902x_detect_handler(int irq, void *data)
+{
+ struct sii902x_data *sii902x = data;
+
+ schedule_delayed_work(&(sii902x->det_work), msecs_to_jiffies(20));
+
+ return IRQ_HANDLED;
+}
+
+static int sii902x_fb_event(struct notifier_block *nb, unsigned long val, void *v)
+{
+ struct fb_event *event = v;
+ struct fb_info *fbi = event->info;
+ struct sii902x_data *sii902x = container_of(nb, struct sii902x_data, nb);
+
+ if (strcmp(event->info->fix.id, sii902x->fbi->fix.id))
+ return 0;
+
+ switch (val) {
+ case FB_EVENT_MODE_CHANGE:
+ sii902x_setup(sii902x, fbi);
+ break;
+ case FB_EVENT_BLANK:
+ if (*((int *)event->data) == FB_BLANK_UNBLANK)
+ sii902x_poweron(sii902x);
+ else
+ sii902x_poweroff(sii902x);
+ break;
+ }
+ return 0;
+}
+
+static int sii902x_TPI_init(struct i2c_client *client)
+{
+ struct fsl_mxc_lcd_platform_data *plat = client->dev.platform_data;
+ u8 devid = 0;
+ u16 wid = 0;
+
+ if (plat->reset)
+ plat->reset();
+
+ /* sii902x back door register - Set terminations to default */
+ i2c_smbus_write_byte_data(client, 0x82, 0x25);
+ /* sii902x back door register - HW debounce to 64ms (0x14) */
+ i2c_smbus_write_byte_data(client, 0x7c, 0x14);
+
+ /* Set 902x in hardware TPI mode on and jump out of D3 state */
+ if (i2c_smbus_write_byte_data(client, TPI_ENABLE, 0x00) < 0) {
+ dev_err(&client->dev,
+ "cound not find device\n");
+ return -ENODEV;
+ }
+
+ msleep(100);
+
+ /* read device ID */
+ devid = read_idx_reg(client, INDEXED_PAGE_0, 0x03);
+ wid = devid;
+ wid <<= 8;
+ devid = read_idx_reg(client, INDEXED_PAGE_0, 0x02);
+ wid |= devid;
+ devid = i2c_smbus_read_byte_data(client, TPI_DEVICE_ID);
+
+ if (devid == 0xB0)
+ dev_info(&client->dev, "found device %04X", wid);
+ else {
+ dev_err(&client->dev, "cound not find device\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int sii902x_disp_init(struct mxc_dispdrv_entry *disp)
+{
+ int ret = 0;
+ struct sii902x_data *sii902x = mxc_dispdrv_getdata(disp);
+ struct mxc_dispdrv_setting *setting = mxc_dispdrv_getsetting(disp);
+ struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data;
+ bool found = false;
+ static bool inited;
+
+ if (inited)
+ return -EBUSY;
+
+ inited = true;
+
+ setting->dev_id = plat->ipu_id;
+ setting->disp_id = plat->disp_id;
+ setting->if_fmt = IPU_PIX_FMT_RGB24;
+
+ sii902x->fbi = setting->fbi;
+ sii902x->power_state = TX_POWER_STATE_D2;
+ sii902x->icolor_space = RGB;
+ sii902x->audio_mode = AMODE_SPDIF;
+ sii902x->audio_channels = ACHANNEL_2CH;
+
+ sii902x->pdev = platform_device_register_simple("sii902x", 0, NULL, 0);
+ if (IS_ERR(sii902x->pdev)) {
+ dev_err(&sii902x->client->dev,
+ "Unable to register Sii902x as a platform device\n");
+ ret = PTR_ERR(sii902x->pdev);
+ goto register_pltdev_failed;
+ }
+
+ if (plat->io_reg) {
+ sii902x->io_reg = regulator_get(&sii902x->client->dev, plat->io_reg);
+ if (!IS_ERR(sii902x->io_reg)) {
+ regulator_set_voltage(sii902x->io_reg, 3300000, 3300000);
+ regulator_enable(sii902x->io_reg);
+ }
+ }
+ if (plat->analog_reg) {
+ sii902x->analog_reg = regulator_get(&sii902x->client->dev, plat->analog_reg);
+ if (!IS_ERR(sii902x->analog_reg)) {
+ regulator_set_voltage(sii902x->analog_reg, 1300000, 1300000);
+ regulator_enable(sii902x->analog_reg);
+ }
+ }
+
+ /* Claim HDMI pins */
+ if (plat->get_pins)
+ if (!plat->get_pins()) {
+ ret = -EACCES;
+ goto get_pins_failed;
+ }
+
+ ret = sii902x_TPI_init(sii902x->client);
+ if (ret < 0)
+ goto init_failed;
+
+ /* try to read edid */
+ ret = sii902x_read_edid(sii902x, sii902x->fbi);
+ if (ret < 0)
+ dev_warn(&sii902x->client->dev, "Can not read edid\n");
+ else {
+ INIT_LIST_HEAD(&sii902x->fbi->modelist);
+ if (sii902x->fbi->monspecs.modedb_len > 0) {
+ int i;
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+
+ for (i = 0; i < sii902x->fbi->monspecs.modedb_len; i++) {
+ /*FIXME now we do not support interlaced mode */
+ if (!(sii902x->fbi->monspecs.modedb[i].vmode
+ & FB_VMODE_INTERLACED))
+ fb_add_videomode(
+ &sii902x->fbi->monspecs.modedb[i],
+ &sii902x->fbi->modelist);
+ }
+
+ fb_find_mode(&sii902x->fbi->var, sii902x->fbi, setting->dft_mode_str,
+ NULL, 0, NULL, setting->default_bpp);
+
+ fb_var_to_videomode(&m, &sii902x->fbi->var);
+ mode = fb_find_nearest_mode(&m,
+ &sii902x->fbi->modelist);
+ fb_videomode_to_var(&sii902x->fbi->var, mode);
+ found = true;
+ }
+
+ }
+
+ if (!found) {
+ ret = fb_find_mode(&sii902x->fbi->var, sii902x->fbi, setting->dft_mode_str,
+ NULL, 0, NULL, setting->default_bpp);
+ if (!ret) {
+ ret = -EINVAL;
+ goto find_mode_failed;
+ }
+ }
+
+ if (sii902x->client->irq) {
+ ret = request_irq(sii902x->client->irq, sii902x_detect_handler,
+ IRQF_TRIGGER_FALLING,
+ "SII902x_det", sii902x);
+ if (ret < 0)
+ dev_warn(&sii902x->client->dev,
+ "cound not request det irq %d\n",
+ sii902x->client->irq);
+ else {
+ /*enable cable hot plug irq*/
+ i2c_smbus_write_byte_data(sii902x->client,
+ TPI_INTERRUPT_ENABLE_REG,
+ HOT_PLUG_EVENT | RX_SENSE_EVENT);
+ INIT_DELAYED_WORK(&(sii902x->det_work), det_worker);
+ /*clear hot plug event status*/
+ i2c_smbus_write_byte_data(sii902x->client,
+ TPI_INTERRUPT_STATUS_REG,
+ HOT_PLUG_EVENT | RX_SENSE_EVENT);
+ }
+
+ ret = device_create_file(&sii902x->pdev->dev, &dev_attr_fb_name);
+ if (ret < 0)
+ dev_warn(&sii902x->client->dev,
+ "cound not create sys node for fb name\n");
+ ret = device_create_file(&sii902x->pdev->dev, &dev_attr_cable_state);
+ if (ret < 0)
+ dev_warn(&sii902x->client->dev,
+ "cound not create sys node for cable state\n");
+ ret = device_create_file(&sii902x->pdev->dev, &dev_attr_edid);
+ if (ret < 0)
+ dev_warn(&sii902x->client->dev,
+ "cound not create sys node for edid\n");
+
+ dev_set_drvdata(&sii902x->pdev->dev, sii902x);
+ }
+
+ sii902x->nb.notifier_call = sii902x_fb_event;
+ ret = fb_register_client(&sii902x->nb);
+ if (ret < 0)
+ goto reg_fbclient_failed;
+
+ return ret;
+
+reg_fbclient_failed:
+find_mode_failed:
+init_failed:
+get_pins_failed:
+ platform_device_unregister(sii902x->pdev);
+register_pltdev_failed:
+ return ret;
+}
+
+static void sii902x_disp_deinit(struct mxc_dispdrv_entry *disp)
+{
+ struct sii902x_data *sii902x = mxc_dispdrv_getdata(disp);
+ struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data;
+
+ if (sii902x->client->irq)
+ free_irq(sii902x->client->irq, sii902x);
+
+ fb_unregister_client(&sii902x->nb);
+
+ sii902x_poweroff(sii902x);
+
+ /* Release HDMI pins */
+ if (plat->put_pins)
+ plat->put_pins();
+
+ platform_device_unregister(sii902x->pdev);
+
+ kfree(sii902x);
+}
+
+static struct mxc_dispdrv_driver sii902x_drv = {
+ .name = DISPDRV_SII,
+ .init = sii902x_disp_init,
+ .deinit = sii902x_disp_deinit,
+};
+
+static int __devinit sii902x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sii902x_data *sii902x;
+ int ret = 0;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C))
+ return -ENODEV;
+
+ sii902x = kzalloc(sizeof(struct sii902x_data), GFP_KERNEL);
+ if (!sii902x) {
+ ret = -ENOMEM;
+ goto alloc_failed;
+ }
+
+ sii902x->client = client;
+
+ sii902x->disp_hdmi = mxc_dispdrv_register(&sii902x_drv);
+ mxc_dispdrv_setdata(sii902x->disp_hdmi, sii902x);
+
+ i2c_set_clientdata(client, sii902x);
+
+alloc_failed:
+ return ret;
+}
+
+static int __devexit sii902x_remove(struct i2c_client *client)
+{
+ struct sii902x_data *sii902x = i2c_get_clientdata(client);
+
+ mxc_dispdrv_unregister(sii902x->disp_hdmi);
+ kfree(sii902x);
+ return 0;
+}
+
+static const struct i2c_device_id sii902x_id[] = {
+ { "sii902x", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, sii902x_id);
+
+static struct i2c_driver sii902x_i2c_driver = {
+ .driver = {
+ .name = "sii902x",
+ },
+ .probe = sii902x_probe,
+ .remove = sii902x_remove,
+ .id_table = sii902x_id,
+};
+
+static int __init sii902x_init(void)
+{
+ return i2c_add_driver(&sii902x_i2c_driver);
+}
+
+static void __exit sii902x_exit(void)
+{
+ i2c_del_driver(&sii902x_i2c_driver);
+}
+
+module_init(sii902x_init);
+module_exit(sii902x_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("SII902x DVI/HDMI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/mxc/tve.c b/drivers/video/mxc/tve.c
new file mode 100644
index 00000000000..35b100349fe
--- /dev/null
+++ b/drivers/video/mxc/tve.c
@@ -0,0 +1,1290 @@
+/*
+ * 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");