aboutsummaryrefslogtreecommitdiff
path: root/drivers/video
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video')
-rw-r--r--drivers/video/Kconfig7
-rw-r--r--drivers/video/Makefile1
-rw-r--r--drivers/video/backlight/pwm_bl.c40
-rw-r--r--drivers/video/fbmem.c11
-rw-r--r--drivers/video/mxc/Kconfig7
-rw-r--r--drivers/video/mxc/Makefile2
-rw-r--r--drivers/video/mxc/ldb.c370
-rw-r--r--drivers/video/mxc/mxc_dispdrv.c94
-rw-r--r--drivers/video/mxc/mxc_dispdrv.h34
-rw-r--r--drivers/video/mxc/mxc_dvi.c381
-rw-r--r--drivers/video/mxc/mxc_edid.c126
-rw-r--r--drivers/video/mxc/mxc_ipuv3_fb.c323
-rw-r--r--drivers/video/mxc/mxc_lcdif.c9
-rw-r--r--drivers/video/mxc/mxcfb_sii902x.c17
-rw-r--r--drivers/video/mxc/tve.c50
-rw-r--r--drivers/video/mxc_hdmi.c2363
16 files changed, 3575 insertions, 260 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index de5806bb9ba..2a5e760b313 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2419,6 +2419,13 @@ if ARCH_MXC
source "drivers/video/mxc/Kconfig"
endif
+config FB_MXC_HDMI
+ depends on FB_MXC_SYNC_PANEL && I2C
+ tristate "MXC HDMI driver support"
+ select MFD_MXC_HDMI
+ help
+ Driver for the on-chip MXC HDMI controller.
+
if VT
source "drivers/video/console/Kconfig"
endif
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index bc77579e8de..9ea192b5cac 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_HDMI) += mxc_hdmi.o
obj-$(CONFIG_FB_MXC) += mxc/
obj-$(CONFIG_FB_NEOMAGIC) += neofb.o
obj-$(CONFIG_FB_3DFX) += tdfxfb.o
diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
index 8b5b2a4124c..63ca212818a 100644
--- a/drivers/video/backlight/pwm_bl.c
+++ b/drivers/video/backlight/pwm_bl.c
@@ -83,6 +83,33 @@ static const struct backlight_ops pwm_backlight_ops = {
.check_fb = pwm_backlight_check_fb,
};
+struct platform_pwm_backlight_data of_data;
+static int of_get_pwm_data(struct platform_device *pdev,
+ struct platform_pwm_backlight_data *data)
+{
+ const __be32 *parp;
+ struct device_node *pwm_np, *np = pdev->dev.of_node;
+
+ if (!np)
+ return -EINVAL;
+
+ parp = of_get_property(np, "pwm-parent", NULL);
+ if (parp == NULL)
+ return -EINVAL;
+ of_node_put(np);
+
+ pwm_np = of_find_node_by_phandle(be32_to_cpup(parp));
+ if (pwm_np)
+ of_node_put(pwm_np);
+ data->pwm_id = (int)pwm_np;
+
+ of_property_read_u32(np, "max_brightness", &data->max_brightness);
+ of_property_read_u32(np, "dft_brightness", &data->dft_brightness);
+ of_property_read_u32(np, "pwm_period_ns", &data->pwm_period_ns);
+
+ return 0;
+}
+
static int pwm_backlight_probe(struct platform_device *pdev)
{
struct backlight_properties props;
@@ -92,8 +119,11 @@ static int pwm_backlight_probe(struct platform_device *pdev)
int ret;
if (!data) {
- dev_err(&pdev->dev, "failed to find platform data\n");
- return -EINVAL;
+ data = pdev->dev.platform_data = &of_data;
+ if (of_get_pwm_data(pdev, data) < 0) {
+ dev_err(&pdev->dev, "failed to find platform data\n");
+ return -EINVAL;
+ }
}
if (data->init) {
@@ -196,10 +226,16 @@ static int pwm_backlight_resume(struct platform_device *pdev)
#define pwm_backlight_resume NULL
#endif
+static const struct of_device_id pwm_bl_dt_ids[] = {
+ { .compatible = "pwm-bl", },
+ { /* sentinel */ }
+};
+
static struct platform_driver pwm_backlight_driver = {
.driver = {
.name = "pwm-backlight",
.owner = THIS_MODULE,
+ .of_match_table = pwm_bl_dt_ids,
},
.probe = pwm_backlight_probe,
.remove = pwm_backlight_remove,
diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c
index a65f5b57391..ad936295d8f 100644
--- a/drivers/video/fbmem.c
+++ b/drivers/video/fbmem.c
@@ -991,17 +991,6 @@ 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
index bb836c3a63f..053f62b7632 100644
--- a/drivers/video/mxc/Kconfig
+++ b/drivers/video/mxc/Kconfig
@@ -14,6 +14,11 @@ config FB_MXC
If you plan to use the LCD display with your MXC system, say
Y here.
+config FB_MXC_EDID
+ depends on FB_MXC && I2C
+ tristate "MXC EDID support"
+ default y
+
config FB_MXC_SYNC_PANEL
depends on FB_MXC
tristate "Synchronous Panel Framebuffer"
@@ -25,7 +30,7 @@ config FB_MXC_TVOUT_TVE
depends on MXC_IPU_V3
config FB_MXC_SII902X
- depends on FB_MXC_SYNC_PANEL
+ depends on FB_MXC_SYNC_PANEL && I2C
tristate "Si Image SII9022 DVI/HDMI Interface Chip"
config FB_MXC_LDB
diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile
index 4c75e1be2d3..e0d47ed90b4 100644
--- a/drivers/video/mxc/Makefile
+++ b/drivers/video/mxc/Makefile
@@ -1,5 +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_EDID) += mxc_edid.o mxc_dvi.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
index 180bf7f6f8d..29c90a1fdd0 100644
--- a/drivers/video/mxc/ldb.c
+++ b/drivers/video/mxc/ldb.c
@@ -37,6 +37,8 @@
#include <linux/regulator/consumer.h>
#include <linux/spinlock.h>
#include <linux/fsl_devices.h>
+#include <linux/of_gpio.h>
+#include <linux/of_device.h>
#include <mach/hardware.h>
#include <mach/clock.h>
#include "mxc_dispdrv.h"
@@ -81,7 +83,7 @@
struct ldb_data {
struct platform_device *pdev;
- struct mxc_dispdrv_entry *disp_ldb;
+ struct mxc_dispdrv_handle *disp_ldb;
uint32_t *reg;
uint32_t *control_reg;
uint32_t *gpr3_reg;
@@ -97,6 +99,30 @@ struct ldb_data {
int di;
} setting[2];
struct notifier_block nb;
+ uint8_t hwtype;
+};
+
+enum mxc_ldb_hwtype {
+ IMX5_LDB,
+ IMX6_LDB,
+};
+
+static struct platform_device_id mxc_ldb_devtype[] = {
+ {
+ .name = "imx5-ldb",
+ .driver_data = IMX5_LDB,
+ }, {
+ .name = "imx6-ldb",
+ .driver_data = IMX6_LDB,
+ }, {
+ /* sentinel */
+ }
+};
+
+static const struct of_device_id mxc_ldb_dt_ids[] = {
+ { .compatible = "fsl,imx5-ldb", .data = &mxc_ldb_devtype[IMX5_LDB], },
+ { .compatible = "fsl,imx6q-ldb", .data = &mxc_ldb_devtype[IMX6_LDB], },
+ { /* sentinel */ }
};
static int g_ldb_mode;
@@ -210,6 +236,55 @@ static int find_ldb_setting(struct ldb_data *ldb, struct fb_info *fbi)
return -EINVAL;
}
+static int ldb_disp_setup(struct mxc_dispdrv_handle *disp, struct fb_info *fbi)
+{
+ uint32_t reg;
+ uint32_t pixel_clk, rounded_pixel_clk;
+ struct clk *ldb_clk_parent;
+ struct ldb_data *ldb = mxc_dispdrv_getdata(disp);
+ int setting_idx, di;
+
+ setting_idx = find_ldb_setting(ldb, fbi);
+ if (setting_idx < 0)
+ return setting_idx;
+
+ di = ldb->setting[setting_idx].di;
+
+ /* 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;
+
+ return 0;
+}
+
int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v)
{
struct ldb_data *ldb = container_of(nb, struct ldb_data, nb);
@@ -238,45 +313,6 @@ int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v)
}
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) {
@@ -297,11 +333,200 @@ int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v)
return 0;
}
-static int ldb_disp_init(struct mxc_dispdrv_entry *disp)
+#define LVDS0_MUX_CTL_MASK (3 << 6)
+#define LVDS1_MUX_CTL_MASK (3 << 8)
+#define LVDS0_MUX_CTL_OFFS 6
+#define LVDS1_MUX_CTL_OFFS 8
+#define ROUTE_IPU0_DI0 0
+#define ROUTE_IPU0_DI1 1
+#define ROUTE_IPU1_DI0 2
+#define ROUTE_IPU1_DI1 3
+static int ldb_ipu_ldb_route(int ipu, int di, struct ldb_data *ldb)
+{
+ uint32_t reg;
+ int mode = ldb->mode;
+
+ reg = readl(ldb->gpr3_reg);
+ if ((mode == LDB_SPL_DI0) || (mode == LDB_DUL_DI0)) {
+ reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK);
+ if (ipu == 0)
+ reg |= (ROUTE_IPU0_DI0 << LVDS0_MUX_CTL_OFFS) |
+ (ROUTE_IPU0_DI0 << LVDS1_MUX_CTL_OFFS);
+ else
+ reg |= (ROUTE_IPU1_DI0 << LVDS0_MUX_CTL_OFFS) |
+ (ROUTE_IPU1_DI0 << LVDS1_MUX_CTL_OFFS);
+ dev_dbg(&ldb->pdev->dev,
+ "Dual/Split mode both channels route to IPU%d-DI0\n", ipu);
+ } else if ((mode == LDB_SPL_DI1) || (mode == LDB_DUL_DI1)) {
+ reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK);
+ if (ipu == 0)
+ reg |= (ROUTE_IPU0_DI1 << LVDS0_MUX_CTL_OFFS) |
+ (ROUTE_IPU0_DI1 << LVDS1_MUX_CTL_OFFS);
+ else
+ reg |= (ROUTE_IPU1_DI1 << LVDS0_MUX_CTL_OFFS) |
+ (ROUTE_IPU1_DI1 << LVDS1_MUX_CTL_OFFS);
+ dev_dbg(&ldb->pdev->dev,
+ "Dual/Split mode both channels route to IPU%d-DI1\n", ipu);
+ } else if (mode == LDB_SIN0) {
+ reg &= ~LVDS0_MUX_CTL_MASK;
+ if ((ipu == 0) && (di == 0))
+ reg |= ROUTE_IPU0_DI0 << LVDS0_MUX_CTL_OFFS;
+ else if ((ipu == 0) && (di == 1))
+ reg |= ROUTE_IPU0_DI1 << LVDS0_MUX_CTL_OFFS;
+ else if ((ipu == 1) && (di == 0))
+ reg |= ROUTE_IPU1_DI0 << LVDS0_MUX_CTL_OFFS;
+ else
+ reg |= ROUTE_IPU1_DI1 << LVDS0_MUX_CTL_OFFS;
+ dev_dbg(&ldb->pdev->dev,
+ "Single mode channel 0 route to IPU%d-DI%d\n", ipu, di);
+ } else if (mode == LDB_SIN1) {
+ reg &= ~LVDS1_MUX_CTL_MASK;
+ if ((ipu == 0) && (di == 0))
+ reg |= ROUTE_IPU0_DI0 << LVDS1_MUX_CTL_OFFS;
+ else if ((ipu == 0) && (di == 1))
+ reg |= ROUTE_IPU0_DI1 << LVDS1_MUX_CTL_OFFS;
+ else if ((ipu == 1) && (di == 0))
+ reg |= ROUTE_IPU1_DI0 << LVDS1_MUX_CTL_OFFS;
+ else
+ reg |= ROUTE_IPU1_DI1 << LVDS1_MUX_CTL_OFFS;
+ dev_dbg(&ldb->pdev->dev,
+ "Single mode channel 1 route to IPU%d-DI%d\n", ipu, di);
+ } else {
+ static bool first = true;
+ int channel;
+
+ if (first) {
+ if (mode == LDB_SEP0) {
+ reg &= ~LVDS0_MUX_CTL_MASK;
+ channel = 0;
+ } else {
+ reg &= ~LVDS1_MUX_CTL_MASK;
+ channel = 1;
+ }
+ first = false;
+ } else {
+ if (mode == LDB_SEP0) {
+ reg &= ~LVDS1_MUX_CTL_MASK;
+ channel = 1;
+ } else {
+ reg &= ~LVDS0_MUX_CTL_MASK;
+ channel = 0;
+ }
+ }
+
+ if ((ipu == 0) && (di == 0)) {
+ if (channel == 0)
+ reg |= ROUTE_IPU0_DI0 << LVDS0_MUX_CTL_OFFS;
+ else
+ reg |= ROUTE_IPU0_DI0 << LVDS1_MUX_CTL_OFFS;
+ } else if ((ipu == 0) && (di == 1)) {
+ if (channel == 0)
+ reg |= ROUTE_IPU0_DI1 << LVDS0_MUX_CTL_OFFS;
+ else
+ reg |= ROUTE_IPU0_DI1 << LVDS1_MUX_CTL_OFFS;
+ } else if ((ipu == 1) && (di == 0)) {
+ if (channel == 0)
+ reg |= ROUTE_IPU1_DI0 << LVDS0_MUX_CTL_OFFS;
+ else
+ reg |= ROUTE_IPU1_DI0 << LVDS1_MUX_CTL_OFFS;
+ } else {
+ if (channel == 0)
+ reg |= ROUTE_IPU1_DI1 << LVDS0_MUX_CTL_OFFS;
+ else
+ reg |= ROUTE_IPU1_DI1 << LVDS1_MUX_CTL_OFFS;
+ }
+
+ dev_dbg(&ldb->pdev->dev, "Separate mode channel %d route to IPU%d-DI%d\n", channel, ipu, di);
+ }
+ writel(reg, ldb->gpr3_reg);
+
+ return 0;
+}
+
+static int of_get_ldb_data(struct ldb_data *ldb,
+ struct fsl_mxc_ldb_platform_data *plat_data)
+{
+ struct platform_device *pdev = ldb->pdev;
+ const struct of_device_id *of_id =
+ of_match_device(mxc_ldb_dt_ids, &pdev->dev);
+ struct device_node *np = pdev->dev.of_node;
+ uint32_t lvds0[2] = {0}, lvds1[2] = {0};
+ const char *mode, *ext_ref;
+ int ret;
+
+ if (!np)
+ return -EINVAL;
+
+ if (of_id)
+ pdev->id_entry = of_id->data;
+ ldb->hwtype = pdev->id_entry->driver_data;
+
+ ret = of_property_read_string(np, "mode", &mode);
+ if (ret < 0)
+ g_ldb_mode = LDB_SEP0;
+ else {
+ if (!strcmp(mode, "spl0"))
+ g_ldb_mode = LDB_SPL_DI0;
+ else if (!strcmp(mode, "spl1"))
+ g_ldb_mode = LDB_SPL_DI1;
+ else if (!strcmp(mode, "dul0"))
+ g_ldb_mode = LDB_DUL_DI0;
+ else if (!strcmp(mode, "dul1"))
+ g_ldb_mode = LDB_DUL_DI1;
+ else if (!strcmp(mode, "sin0"))
+ g_ldb_mode = LDB_SIN0;
+ else if (!strcmp(mode, "sin1"))
+ g_ldb_mode = LDB_SIN1;
+ else if (!strcmp(mode, "sep0"))
+ g_ldb_mode = LDB_SEP0;
+ else if (!strcmp(mode, "sep1"))
+ g_ldb_mode = LDB_SEP1;
+ }
+
+ ret = of_property_read_string(np, "ext_ref", &ext_ref);
+ if (ret < 0)
+ plat_data->ext_ref = 1;
+ else if (!strcmp(ext_ref, "true"))
+ plat_data->ext_ref = 1;
+
+ of_property_read_u32_array(np, "lvds0",
+ lvds0, ARRAY_SIZE(lvds0));
+ of_property_read_u32_array(np, "lvds1",
+ lvds1, ARRAY_SIZE(lvds1));
+
+ if ((g_ldb_mode == LDB_SPL_DI0) || (g_ldb_mode == LDB_DUL_DI0)) {
+ plat_data->ipu_id = lvds0[0];
+ plat_data->disp_id = 0;
+ } else if ((g_ldb_mode == LDB_SPL_DI1) || (g_ldb_mode == LDB_DUL_DI1)) {
+ plat_data->ipu_id = lvds0[0];
+ plat_data->disp_id = 1;
+ } else if (g_ldb_mode == LDB_SIN0) {
+ plat_data->ipu_id = lvds0[0];
+ plat_data->disp_id = lvds0[1];
+ } else if (g_ldb_mode == LDB_SIN1) {
+ plat_data->ipu_id = lvds1[0];
+ plat_data->disp_id = lvds1[1];
+ } else if (g_ldb_mode == LDB_SEP0) {
+ plat_data->ipu_id = lvds0[0];
+ plat_data->disp_id = lvds0[1];
+ plat_data->sec_ipu_id = lvds1[0];
+ plat_data->sec_disp_id = lvds1[1];
+ } else if (g_ldb_mode == LDB_SEP1) {
+ plat_data->ipu_id = lvds1[0];
+ plat_data->disp_id = lvds1[1];
+ plat_data->sec_ipu_id = lvds0[0];
+ plat_data->sec_disp_id = lvds0[1];
+ }
+
+ return 0;
+}
+
+static int ldb_disp_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
{
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 of_data;
struct fsl_mxc_ldb_platform_data *plat_data = ldb->pdev->dev.platform_data;
struct resource *res;
uint32_t base_addr;
@@ -314,6 +539,14 @@ static int ldb_disp_init(struct mxc_dispdrv_entry *disp)
setting->if_fmt = IPU_PIX_FMT_RGB666;
}
+ if (!plat_data) {
+ plat_data = &of_data;
+ if (of_get_ldb_data(ldb, plat_data) < 0) {
+ dev_err(&ldb->pdev->dev, "no platform data\n");
+ return -EINVAL;
+ }
+ }
+
if (!ldb->inited) {
char di_clk[] = "ipu1_di0_clk";
char ldb_clk[] = "ldb_di0_clk";
@@ -430,7 +663,11 @@ static int ldb_disp_init(struct mxc_dispdrv_entry *disp)
writel(reg, ldb->control_reg);
/* clock setting */
- ldb_clk[6] += setting->disp_id;
+ if ((ldb->hwtype == IMX6_LDB) &&
+ ((ldb->mode == LDB_SEP0) || (ldb->mode == LDB_SEP1)))
+ ldb_clk[6] += lvds_channel;
+ else
+ 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");
@@ -474,8 +711,13 @@ static int ldb_disp_init(struct mxc_dispdrv_entry *disp)
return -EINVAL;
}
- setting->dev_id = plat_data->ipu_id;
- setting->disp_id = !plat_data->disp_id;
+ if (ldb->hwtype == IMX6_LDB) {
+ setting->dev_id = plat_data->sec_ipu_id;
+ setting->disp_id = plat_data->sec_disp_id;
+ } else {
+ 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)
@@ -507,7 +749,10 @@ static int ldb_disp_init(struct mxc_dispdrv_entry *disp)
writel(reg, ldb->control_reg);
/* clock setting */
- ldb_clk[6] += setting->disp_id;
+ if (ldb->hwtype == IMX6_LDB)
+ ldb_clk[6] += lvds_channel;
+ else
+ 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");
@@ -526,6 +771,14 @@ static int ldb_disp_init(struct mxc_dispdrv_entry *disp)
setting_idx = 1;
}
+ if (ldb->hwtype == IMX6_LDB) {
+ reg = readl(ldb->control_reg);
+ reg &= ~(LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK);
+ reg |= LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI1;
+ writel(reg, ldb->control_reg);
+ ldb_ipu_ldb_route(setting->dev_id, setting->disp_id, ldb);
+ }
+
/*
* ldb_di0_clk -> ipux_di0_clk
* ldb_di1_clk -> ipux_di1_clk
@@ -558,7 +811,7 @@ static int ldb_disp_init(struct mxc_dispdrv_entry *disp)
return ret;
}
-static void ldb_disp_deinit(struct mxc_dispdrv_entry *disp)
+static void ldb_disp_deinit(struct mxc_dispdrv_handle *disp)
{
struct ldb_data *ldb = mxc_dispdrv_getdata(disp);
int i;
@@ -579,8 +832,27 @@ static struct mxc_dispdrv_driver ldb_drv = {
.name = DISPDRV_LDB,
.init = ldb_disp_init,
.deinit = ldb_disp_deinit,
+ .setup = ldb_disp_setup,
};
+static void ldb_disp_pwr_up(struct platform_device *pdev)
+{
+ int ret, gpio_pwr;
+ struct device_node *np = pdev->dev.of_node;
+
+ if (!np)
+ return;
+
+ gpio_pwr = of_get_named_gpio(np, "disp-pwr-gpios", 0);
+ if (gpio_pwr < 0)
+ dev_warn(&pdev->dev, "no pwr gpio defined\n");
+ else {
+ ret = gpio_request_one(gpio_pwr, GPIOF_OUT_INIT_HIGH, "disp-pwr");
+ if (ret)
+ dev_warn(&pdev->dev, "fail to request pwr gpio\n");
+ }
+}
+
/*!
* This function is called by the driver framework to initialize the LDB
* device.
@@ -607,6 +879,8 @@ static int ldb_probe(struct platform_device *pdev)
dev_set_drvdata(&pdev->dev, ldb);
+ ldb_disp_pwr_up(pdev);
+
alloc_failed:
return ret;
}
@@ -615,6 +889,7 @@ static int ldb_remove(struct platform_device *pdev)
{
struct ldb_data *ldb = dev_get_drvdata(&pdev->dev);
+ mxc_dispdrv_puthandle(ldb->disp_ldb);
mxc_dispdrv_unregister(ldb->disp_ldb);
kfree(ldb);
return 0;
@@ -623,6 +898,7 @@ static int ldb_remove(struct platform_device *pdev)
static struct platform_driver mxcldb_driver = {
.driver = {
.name = "mxc_ldb",
+ .of_match_table = mxc_ldb_dt_ids,
},
.probe = ldb_probe,
.remove = ldb_remove,
diff --git a/drivers/video/mxc/mxc_dispdrv.c b/drivers/video/mxc/mxc_dispdrv.c
index 06b7af944f5..b455a6ab06f 100644
--- a/drivers/video/mxc/mxc_dispdrv.c
+++ b/drivers/video/mxc/mxc_dispdrv.c
@@ -22,8 +22,9 @@
* 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
+ * ipuv3 fb driver could call mxc_dispdrv_gethandle(name, setting) before a fb
+ * need be added, with fbi param passing by setting, after
+ * mxc_dispdrv_gethandle() return, FB driver should get the basic setting
* about fbi info and ipuv3-hw (ipu_id and disp_id).
*
* @ingroup Framebuffer
@@ -42,16 +43,15 @@ 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 *);
+ /* Note: drv always the first element */
+ struct mxc_dispdrv_driver *drv;
bool active;
struct mxc_dispdrv_setting setting;
void *priv;
+ struct list_head list;
};
-struct mxc_dispdrv_entry *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv)
+struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv)
{
struct mxc_dispdrv_entry *new;
@@ -63,23 +63,20 @@ struct mxc_dispdrv_entry *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv)
return ERR_PTR(-ENOMEM);
}
- new->name = drv->name;
- new->init = drv->init;
- new->deinit = drv->deinit;
-
+ new->drv = drv;
list_add_tail(&new->list, &dispdrv_list);
mutex_unlock(&dispdrv_lock);
- return new;
+ return (struct mxc_dispdrv_handle *)new;
}
EXPORT_SYMBOL_GPL(mxc_dispdrv_register);
-int mxc_dispdrv_unregister(struct mxc_dispdrv_entry *entry)
+int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle)
{
+ struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle;
+
if (entry) {
mutex_lock(&dispdrv_lock);
- if (entry->active && entry->deinit)
- entry->deinit(entry);
list_del(&entry->list);
mutex_unlock(&dispdrv_lock);
kfree(entry);
@@ -89,41 +86,48 @@ int mxc_dispdrv_unregister(struct mxc_dispdrv_entry *entry)
}
EXPORT_SYMBOL_GPL(mxc_dispdrv_unregister);
-int mxc_dispdrv_init(char *name, struct mxc_dispdrv_setting *setting)
+struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name,
+ struct mxc_dispdrv_setting *setting)
{
- int ret = 0, found = 0;
- struct mxc_dispdrv_entry *disp;
+ int ret, found = 0;
+ struct mxc_dispdrv_entry *entry;
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;
- }
+ list_for_each_entry(entry, &dispdrv_list, list) {
+ if (!strcmp(entry->drv->name, name) && (entry->drv->init)) {
+ ret = entry->drv->init((struct mxc_dispdrv_handle *)
+ entry, setting);
+ if (ret >= 0) {
+ entry->active = true;
+ found = 1;
+ break;
}
}
}
- if (!found)
- ret = -EINVAL;
-
mutex_unlock(&dispdrv_lock);
- return ret;
+ return found ? (struct mxc_dispdrv_handle *)entry:ERR_PTR(-ENODEV);
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_gethandle);
+
+void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle)
+{
+ struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle;
+
+ mutex_lock(&dispdrv_lock);
+ if (entry && entry->active && entry->drv->deinit) {
+ entry->drv->deinit(handle);
+ entry->active = false;
+ }
+ mutex_unlock(&dispdrv_lock);
}
-EXPORT_SYMBOL_GPL(mxc_dispdrv_init);
+EXPORT_SYMBOL_GPL(mxc_dispdrv_puthandle);
-int mxc_dispdrv_setdata(struct mxc_dispdrv_entry *entry, void *data)
+int mxc_dispdrv_setdata(struct mxc_dispdrv_handle *handle, void *data)
{
+ struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle;
+
if (entry) {
entry->priv = data;
return 0;
@@ -132,21 +136,13 @@ int mxc_dispdrv_setdata(struct mxc_dispdrv_entry *entry, void *data)
}
EXPORT_SYMBOL_GPL(mxc_dispdrv_setdata);
-void *mxc_dispdrv_getdata(struct mxc_dispdrv_entry *entry)
+void *mxc_dispdrv_getdata(struct mxc_dispdrv_handle *handle)
{
+ struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle;
+
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
index f5f62a2c3cb..580e9d2c561 100644
--- a/drivers/video/mxc/mxc_dispdrv.h
+++ b/drivers/video/mxc/mxc_dispdrv.h
@@ -13,12 +13,8 @@
#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_handle {
+ struct mxc_dispdrv_driver *drv;
};
struct mxc_dispdrv_setting {
@@ -33,11 +29,23 @@ struct mxc_dispdrv_setting {
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);
+struct mxc_dispdrv_driver {
+ const char *name;
+ int (*init) (struct mxc_dispdrv_handle *, struct mxc_dispdrv_setting *);
+ void (*deinit) (struct mxc_dispdrv_handle *);
+ /* display driver enable function for extension */
+ int (*enable) (struct mxc_dispdrv_handle *);
+ /* display driver disable function, called at early part of fb_blank */
+ void (*disable) (struct mxc_dispdrv_handle *);
+ /* display driver setup function, called at early part of fb_set_par */
+ int (*setup) (struct mxc_dispdrv_handle *, struct fb_info *fbi);
+};
+
+struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv);
+int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle);
+struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name,
+ struct mxc_dispdrv_setting *setting);
+void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle);
+int mxc_dispdrv_setdata(struct mxc_dispdrv_handle *handle, void *data);
+void *mxc_dispdrv_getdata(struct mxc_dispdrv_handle *handle);
#endif
diff --git a/drivers/video/mxc/mxc_dvi.c b/drivers/video/mxc/mxc_dvi.c
new file mode 100644
index 00000000000..a286e51014e
--- /dev/null
+++ b/drivers/video/mxc/mxc_dvi.c
@@ -0,0 +1,381 @@
+/*
+ * 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.
+ */
+
+/*!
+ * @defgroup Framebuffer Framebuffer Driver for SDC and ADC.
+ */
+
+/*!
+ * @file mxc_dvi.c
+ *
+ * @brief MXC DVI driver
+ *
+ * @ingroup Framebuffer
+ */
+
+/*!
+ * Include files
+ */
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/fb.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/ipu.h>
+#include <linux/mxcfb.h>
+#include <linux/fsl_devices.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/regulator/consumer.h>
+#include <mach/mxc_edid.h>
+#include "mxc_dispdrv.h"
+#include "../edid.h"
+
+#define MXC_EDID_LENGTH (EDID_LENGTH*4)
+
+#define DISPDRV_DVI "dvi"
+
+struct mxc_dvi_data {
+ struct i2c_client *client;
+ struct platform_device *pdev;
+ struct mxc_dispdrv_handle *disp_dvi;
+ struct delayed_work det_work;
+ struct fb_info *fbi;
+ struct mxc_edid_cfg edid_cfg;
+ u8 cable_plugin;
+ u8 edid[MXC_EDID_LENGTH];
+
+ u32 ipu;
+ u32 di;
+ void (*init)(void);
+ int (*update)(void);
+ struct regulator *analog_reg;
+};
+
+static ssize_t mxc_dvi_show_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_dvi_data *dvi = dev_get_drvdata(dev);
+
+ if (dvi->cable_plugin == 0)
+ strcpy(buf, "plugout\n");
+ else
+ strcpy(buf, "plugin\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(cable_state, S_IRUGO, mxc_dvi_show_state, NULL);
+
+static ssize_t mxc_dvi_show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_dvi_data *dvi = dev_get_drvdata(dev);
+
+ strcpy(buf, dvi->fbi->fix.id);
+ sprintf(buf+strlen(buf), "\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(fb_name, S_IRUGO, mxc_dvi_show_name, NULL);
+
+static ssize_t mxc_dvi_show_edid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_dvi_data *dvi = dev_get_drvdata(dev);
+ int i, j, len = 0;
+
+ for (j = 0; j < MXC_EDID_LENGTH/16; j++) {
+ for (i = 0; i < 16; i++)
+ len += sprintf(buf+len, "0x%02X ",
+ dvi->edid[j*16 + i]);
+ len += sprintf(buf+len, "\n");
+ }
+
+ return len;
+}
+
+static DEVICE_ATTR(edid, S_IRUGO, mxc_dvi_show_edid, NULL);
+
+static void det_worker(struct work_struct *work)
+{
+ struct delayed_work *delay_work = to_delayed_work(work);
+ struct mxc_dvi_data *dvi =
+ container_of(delay_work, struct mxc_dvi_data, det_work);
+ char event_string[16];
+ char *envp[] = { event_string, NULL };
+
+ /* cable connection changes */
+ if (dvi->update()) {
+ u8 edid_old[MXC_EDID_LENGTH];
+ dvi->cable_plugin = 1;
+ sprintf(event_string, "EVENT=plugin");
+
+ memcpy(edid_old, dvi->edid, MXC_EDID_LENGTH);
+
+ if (mxc_edid_read(dvi->client->adapter, dvi->client->addr,
+ dvi->edid, &dvi->edid_cfg, dvi->fbi) < 0)
+ dev_err(&dvi->client->dev,
+ "MXC dvi: read edid fail\n");
+ else {
+ if (!memcmp(edid_old, dvi->edid, MXC_EDID_LENGTH))
+ dev_info(&dvi->client->dev,
+ "Sii902x: same edid\n");
+ else if (dvi->fbi->monspecs.modedb_len > 0) {
+ int i;
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+
+ fb_destroy_modelist(&dvi->fbi->modelist);
+
+ for (i = 0; i < dvi->fbi->monspecs.modedb_len; i++)
+ /*FIXME now we do not support interlaced mode */
+ if (!(dvi->fbi->monspecs.modedb[i].vmode & FB_VMODE_INTERLACED))
+ fb_add_videomode(&dvi->fbi->monspecs.modedb[i],
+ &dvi->fbi->modelist);
+
+ fb_var_to_videomode(&m, &dvi->fbi->var);
+ mode = fb_find_nearest_mode(&m,
+ &dvi->fbi->modelist);
+
+ fb_videomode_to_var(&dvi->fbi->var, mode);
+
+ dvi->fbi->var.activate |= FB_ACTIVATE_FORCE;
+ console_lock();
+ dvi->fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(dvi->fbi, &dvi->fbi->var);
+ dvi->fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+ }
+ }
+ } else {
+ dvi->cable_plugin = 0;
+ sprintf(event_string, "EVENT=plugout");
+ }
+
+ kobject_uevent_env(&dvi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+}
+
+static irqreturn_t mxc_dvi_detect_handler(int irq, void *data)
+{
+ struct mxc_dvi_data *dvi = data;
+ schedule_delayed_work(&(dvi->det_work), msecs_to_jiffies(300));
+ return IRQ_HANDLED;
+}
+
+static int dvi_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
+{
+ int ret = 0;
+ struct mxc_dvi_data *dvi = mxc_dispdrv_getdata(disp);
+ struct fsl_mxc_dvi_platform_data *plat = dvi->client->dev.platform_data;
+
+ setting->dev_id = dvi->ipu = plat->ipu_id;
+ setting->disp_id = dvi->di = plat->disp_id;
+ setting->if_fmt = IPU_PIX_FMT_RGB24;
+ dvi->fbi = setting->fbi;
+ dvi->init = plat->init;
+ dvi->update = plat->update;
+
+ dvi->analog_reg = regulator_get(&dvi->pdev->dev, plat->analog_regulator);
+ if (!IS_ERR(dvi->analog_reg)) {
+ regulator_set_voltage(dvi->analog_reg, 2775000, 2775000);
+ regulator_enable(dvi->analog_reg);
+ }
+
+ if (dvi->init)
+ dvi->init();
+
+ /* get video mode from edid */
+ if (!dvi->update)
+ return -EINVAL;
+ else {
+ bool found = false;
+
+ INIT_LIST_HEAD(&dvi->fbi->modelist);
+ if (dvi->update()) {
+ dvi->cable_plugin = 1;
+ /* try to read edid */
+ if (mxc_edid_read(dvi->client->adapter, dvi->client->addr,
+ dvi->edid, &dvi->edid_cfg, dvi->fbi) < 0)
+ dev_warn(&dvi->client->dev, "Can not read edid\n");
+ else if (dvi->fbi->monspecs.modedb_len > 0) {
+ int i;
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+
+ for (i = 0; i < dvi->fbi->monspecs.modedb_len; i++) {
+ /*FIXME now we do not support interlaced mode */
+ if (!(dvi->fbi->monspecs.modedb[i].vmode
+ & FB_VMODE_INTERLACED))
+ fb_add_videomode(
+ &dvi->fbi->monspecs.modedb[i],
+ &dvi->fbi->modelist);
+ }
+
+ fb_find_mode(&dvi->fbi->var, dvi->fbi, setting->dft_mode_str,
+ NULL, 0, NULL, setting->default_bpp);
+
+ fb_var_to_videomode(&m, &dvi->fbi->var);
+ mode = fb_find_nearest_mode(&m,
+ &dvi->fbi->modelist);
+ fb_videomode_to_var(&dvi->fbi->var, mode);
+ found = 1;
+ }
+ } else
+ dvi->cable_plugin = 0;
+
+ if (!found) {
+ ret = fb_find_mode(&dvi->fbi->var, dvi->fbi, setting->dft_mode_str,
+ NULL, 0, NULL, setting->default_bpp);
+ if (!ret)
+ return -EINVAL;
+ }
+ }
+
+ /* cable detection */
+ if (dvi->client->irq) {
+ ret = request_irq(dvi->client->irq, mxc_dvi_detect_handler,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "dvi_det", dvi);
+ if (ret < 0) {
+ dev_warn(&dvi->client->dev,
+ "MXC dvi: cound not request det irq %d\n",
+ dvi->client->irq);
+ goto err;
+ } else {
+ INIT_DELAYED_WORK(&(dvi->det_work), det_worker);
+ ret = device_create_file(&dvi->pdev->dev, &dev_attr_fb_name);
+ if (ret < 0)
+ dev_warn(&dvi->client->dev,
+ "MXC dvi: cound not create sys node for fb name\n");
+ ret = device_create_file(&dvi->pdev->dev, &dev_attr_cable_state);
+ if (ret < 0)
+ dev_warn(&dvi->client->dev,
+ "MXC dvi: cound not create sys node for cable state\n");
+ ret = device_create_file(&dvi->pdev->dev, &dev_attr_edid);
+ if (ret < 0)
+ dev_warn(&dvi->client->dev,
+ "MXC dvi: cound not create sys node for edid\n");
+
+ dev_set_drvdata(&dvi->pdev->dev, dvi);
+ }
+ }
+
+err:
+ return ret;
+}
+
+static void dvi_deinit(struct mxc_dispdrv_handle *disp)
+{
+ struct mxc_dvi_data *dvi = mxc_dispdrv_getdata(disp);
+
+ if (!IS_ERR(dvi->analog_reg))
+ regulator_disable(dvi->analog_reg);
+
+ free_irq(dvi->client->irq, dvi);
+}
+
+static struct mxc_dispdrv_driver dvi_drv = {
+ .name = DISPDRV_DVI,
+ .init = dvi_init,
+ .deinit = dvi_deinit,
+};
+
+static int __devinit mxc_dvi_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mxc_dvi_data *dvi;
+ int ret = 0;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C))
+ return -ENODEV;
+
+ dvi = kzalloc(sizeof(struct mxc_dvi_data), GFP_KERNEL);
+ if (!dvi) {
+ ret = -ENOMEM;
+ goto alloc_failed;
+ }
+
+ dvi->pdev = platform_device_register_simple("mxc_dvi", 0, NULL, 0);
+ if (IS_ERR(dvi->pdev)) {
+ printk(KERN_ERR
+ "Unable to register MXC DVI as a platform device\n");
+ ret = PTR_ERR(dvi->pdev);
+ goto pdev_reg_failed;
+ }
+
+ dvi->client = client;
+ dvi->disp_dvi = mxc_dispdrv_register(&dvi_drv);
+ mxc_dispdrv_setdata(dvi->disp_dvi, dvi);
+
+ i2c_set_clientdata(client, dvi);
+
+ return ret;
+
+pdev_reg_failed:
+ kfree(dvi);
+alloc_failed:
+ return ret;
+}
+
+static int __devexit mxc_dvi_remove(struct i2c_client *client)
+{
+ struct mxc_dvi_data *dvi = i2c_get_clientdata(client);
+
+ mxc_dispdrv_puthandle(dvi->disp_dvi);
+ mxc_dispdrv_unregister(dvi->disp_dvi);
+ platform_device_unregister(dvi->pdev);
+ kfree(dvi);
+ return 0;
+}
+
+static const struct i2c_device_id mxc_dvi_id[] = {
+ { "mxc_dvi", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, mxc_dvi_id);
+
+static struct i2c_driver mxc_dvi_i2c_driver = {
+ .driver = {
+ .name = "mxc_dvi",
+ },
+ .probe = mxc_dvi_probe,
+ .remove = mxc_dvi_remove,
+ .id_table = mxc_dvi_id,
+};
+
+static int __init mxc_dvi_init(void)
+{
+ return i2c_add_driver(&mxc_dvi_i2c_driver);
+}
+
+static void __exit mxc_dvi_exit(void)
+{
+ i2c_del_driver(&mxc_dvi_i2c_driver);
+}
+
+module_init(mxc_dvi_init);
+module_exit(mxc_dvi_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC DVI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c
index 0fa5cf97602..de4ba8ec8bf 100644
--- a/drivers/video/mxc/mxc_edid.c
+++ b/drivers/video/mxc/mxc_edid.c
@@ -29,11 +29,10 @@
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fb.h>
-#include "mxc_edid.h"
+#include <mach/mxc_edid.h>
#include "../edid.h"
#undef DEBUG /* define this for verbose EDID parsing output */
-
#ifdef DEBUG
#define DPRINTK(fmt, args...) printk(fmt, ## args)
#else
@@ -41,80 +40,128 @@
#endif
const struct fb_videomode mxc_cea_mode[64] = {
- /* #1: 640x480p@59.94/60Hz */
+ /* #1: 640x480p@59.94/60Hz 4:3 */
[1] = {
NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0,
- FB_VMODE_NONINTERLACED, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #2: 720x480p@59.94/60Hz 4:3 */
+ [2] = {
+ NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
},
- /* #3: 720x480p@59.94/60Hz */
+ /* #3: 720x480p@59.94/60Hz 16:9 */
[3] = {
NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
- FB_VMODE_NONINTERLACED, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
},
- /* #4: 1280x720p@59.94/60Hz */
+ /* #4: 1280x720p@59.94/60Hz 16:9 */
[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,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
},
- /* #5: 1920x1080i@59.94/60Hz */
+ /* #5: 1920x1080i@59.94/60Hz 16:9 */
[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,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0,
},
- /* #7: 720(1440)x480iH@59.94/60Hz */
+ /* #6: 720(1440)x480iH@59.94/60Hz 4:3 */
+ [6] = {
+ NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #7: 720(1440)x480iH@59.94/60Hz 16:9 */
[7] = {
NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
- FB_VMODE_INTERLACED, 0,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #8: 720(1440)x240pH@59.94/60Hz 4:3 */
+ [8] = {
+ NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
},
- /* #9: 720(1440)x240pH@59.94/60Hz */
+ /* #9: 720(1440)x240pH@59.94/60Hz 16:9 */
[9] = {
NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0,
- FB_VMODE_NONINTERLACED, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
},
- /* #16: 1920x1080p@60Hz */
+ /* #16: 1920x1080p@60Hz 16:9 */
[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,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
},
- /* #18: 720x576pH@50Hz */
+ /* #17: 720x576pH@50Hz 4:3 */
+ [17] = {
+ NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #18: 720x576pH@50Hz 16:9 */
[18] = {
NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
- FB_VMODE_NONINTERLACED, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 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,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 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,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 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,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 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,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
},
/* #35: (2880)x480p4x@59.94/60Hz */
[35] = {
NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0,
- FB_VMODE_NONINTERLACED, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
},
};
+/*
+ * We have a special version of fb_mode_is_equal that ignores
+ * pixclock, since for many CEA modes, 2 frequencies are supported
+ * e.g. 640x480 @ 60Hz or 59.94Hz
+ */
+int mxc_edid_fb_mode_is_equal(bool use_aspect,
+ const struct fb_videomode *mode1,
+ const struct fb_videomode *mode2)
+{
+ u32 mask;
+
+ if (use_aspect)
+ mask = ~0;
+ else
+ mask = ~FB_VMODE_ASPECT_MASK;
+
+ return (mode1->xres == mode2->xres &&
+ mode1->yres == mode2->yres &&
+ mode1->hsync_len == mode2->hsync_len &&
+ mode1->vsync_len == mode2->vsync_len &&
+ mode1->left_margin == mode2->left_margin &&
+ mode1->right_margin == mode2->right_margin &&
+ mode1->upper_margin == mode2->upper_margin &&
+ mode1->lower_margin == mode2->lower_margin &&
+ mode1->sync == mode2->sync &&
+ (mode1->vmode & mask) == (mode2->vmode & mask));
+}
+
static void get_detailed_timing(unsigned char *block,
struct fb_videomode *mode)
{
@@ -146,6 +193,19 @@ static void get_detailed_timing(unsigned char *block,
}
mode->flag = FB_MODE_IS_DETAILED;
+ if ((H_SIZE / 16) == (V_SIZE / 9))
+ mode->vmode |= FB_VMODE_ASPECT_16_9;
+ else if ((H_SIZE / 4) == (V_SIZE / 3))
+ mode->vmode |= FB_VMODE_ASPECT_4_3;
+ else if ((mode->xres / 16) == (mode->yres / 9))
+ mode->vmode |= FB_VMODE_ASPECT_16_9;
+ else if ((mode->xres / 4) == (mode->yres / 3))
+ mode->vmode |= FB_VMODE_ASPECT_4_3;
+
+ if (mode->vmode & FB_VMODE_ASPECT_16_9)
+ DPRINTK("Aspect ratio: 16:9\n");
+ if (mode->vmode & FB_VMODE_ASPECT_4_3)
+ DPRINTK("Aspect ratio: 4:3\n");
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);
@@ -399,7 +459,7 @@ int mxc_edid_var_to_vic(struct fb_var_screeninfo *var)
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]))
+ if (mxc_edid_fb_mode_is_equal(false, &m, &mxc_cea_mode[i]))
break;
}
@@ -408,8 +468,26 @@ int mxc_edid_var_to_vic(struct fb_var_screeninfo *var)
return i;
}
+
EXPORT_SYMBOL(mxc_edid_var_to_vic);
+int mxc_edid_mode_to_vic(const struct fb_videomode *mode)
+{
+ int i;
+ bool use_aspect = (mode->vmode & FB_VMODE_ASPECT_MASK);
+
+ for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) {
+ if (mxc_edid_fb_mode_is_equal(use_aspect, mode, &mxc_cea_mode[i]))
+ break;
+ }
+
+ if (i == ARRAY_SIZE(mxc_cea_mode))
+ return 0;
+
+ return i;
+}
+EXPORT_SYMBOL(mxc_edid_mode_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)
diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c
index 260b41a3ddf..9ccafc2d95b 100644
--- a/drivers/video/mxc/mxc_ipuv3_fb.c
+++ b/drivers/video/mxc/mxc_ipuv3_fb.c
@@ -77,6 +77,7 @@ struct mxcfb_info {
void *alpha_virt_addr1;
uint32_t alpha_mem_len;
uint32_t ipu_ch_irq;
+ uint32_t ipu_ch_nf_irq;
uint32_t ipu_alp_ch_irq;
uint32_t cur_ipu_buf;
uint32_t cur_ipu_alpha_buf;
@@ -84,13 +85,15 @@ struct mxcfb_info {
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 mxc_dispdrv_handle *dispdrv;
+ struct ipuv3_fb_platform_data of_data;
};
struct mxcfb_alloc_list {
@@ -109,6 +112,7 @@ enum {
static bool g_dp_in_use[2];
LIST_HEAD(fb_alloc_list);
+static int of_dev_id;
static uint32_t bpp_to_pixfmt(struct fb_info *fbi)
{
@@ -131,6 +135,38 @@ static uint32_t bpp_to_pixfmt(struct fb_info *fbi)
return pixfmt;
}
+static int if_fmt_parse(const char *fmt)
+{
+ int if_fmt;
+
+ if (!strncmp(fmt, "RGB24", 5))
+ if_fmt = IPU_PIX_FMT_RGB24;
+ else if (!strncmp(fmt, "BGR24", 5))
+ if_fmt = IPU_PIX_FMT_BGR24;
+ else if (!strncmp(fmt, "GBR24", 5))
+ if_fmt = IPU_PIX_FMT_GBR24;
+ else if (!strncmp(fmt, "RGB565", 6))
+ if_fmt = IPU_PIX_FMT_RGB565;
+ else if (!strncmp(fmt, "RGB666", 6))
+ if_fmt = IPU_PIX_FMT_RGB666;
+ else if (!strncmp(fmt, "YUV444", 6))
+ if_fmt = IPU_PIX_FMT_YUV444;
+ else if (!strncmp(fmt, "LVDS666", 7))
+ if_fmt = IPU_PIX_FMT_LVDS666;
+ else if (!strncmp(fmt, "YUYV16", 6))
+ if_fmt = IPU_PIX_FMT_YUYV;
+ else if (!strncmp(fmt, "UYVY16", 6))
+ if_fmt = IPU_PIX_FMT_UYVY;
+ else if (!strncmp(fmt, "YVYU16", 6))
+ if_fmt = IPU_PIX_FMT_YVYU;
+ else if (!strncmp(fmt, "VYUY16", 6))
+ if_fmt = IPU_PIX_FMT_VYUY;
+ else
+ if_fmt = IPU_PIX_FMT_RGB24;
+
+ return if_fmt;
+}
+
static struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id)
{
int i;
@@ -151,6 +187,7 @@ static struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id)
}
static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id);
+static irqreturn_t mxcfb_nf_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);
@@ -210,6 +247,7 @@ static int _setup_disp_channel2(struct fb_info *fbi)
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
int fb_stride;
unsigned long base;
+ unsigned int fr_xoff, fr_yoff, fr_w, fr_h;
switch (bpp_to_pixfmt(fbi)) {
case IPU_PIX_FMT_YUV420P2:
@@ -224,16 +262,28 @@ static int _setup_disp_channel2(struct fb_info *fbi)
fb_stride = fbi->fix.line_length;
}
+ base = fbi->fix.smem_start;
+ fr_xoff = fbi->var.xoffset;
+ fr_w = fbi->var.xres_virtual;
+ if (!(fbi->var.vmode & FB_VMODE_YWRAP)) {
+ dev_dbg(fbi->device, "Y wrap disabled\n");
+ fr_yoff = fbi->var.yoffset % fbi->var.yres;
+ fr_h = fbi->var.yres;
+ base += fbi->fix.line_length * fbi->var.yres *
+ (fbi->var.yoffset / fbi->var.yres);
+ } else {
+ dev_dbg(fbi->device, "Y wrap enabled\n");
+ fr_yoff = fbi->var.yoffset;
+ fr_h = fbi->var.yres_virtual;
+ }
+ base += fr_yoff * fb_stride + fr_xoff;
+
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,
@@ -251,6 +301,17 @@ static int _setup_disp_channel2(struct fb_info *fbi)
"ipu_init_channel_buffer error %d\n", retval);
}
+ /* update u/v offset */
+ ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER,
+ bpp_to_pixfmt(fbi),
+ fr_w,
+ fr_h,
+ fr_w,
+ 0, 0,
+ fr_yoff,
+ fr_xoff);
+
if (mxc_fbi->alpha_chan_en) {
retval = ipu_init_channel_buffer(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
@@ -287,8 +348,19 @@ static int mxcfb_set_par(struct fb_info *fbi)
dev_dbg(fbi->device, "Reconfiguring framebuffer\n");
+ if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->setup) {
+ retval = mxc_fbi->dispdrv->drv->setup(mxc_fbi->dispdrv, fbi);
+ if (retval < 0) {
+ dev_err(fbi->device, "setup error, dispdrv:%s.\n",
+ mxc_fbi->dispdrv->drv->name);
+ return -EINVAL;
+ }
+ }
+
ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
+ ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_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);
@@ -433,6 +505,9 @@ static int _swap_channels(struct fb_info *fbi_from,
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;
+ tmp = mxc_fbi_from->ipu_ch_nf_irq;
+ mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq;
+ mxc_fbi_to->ipu_ch_nf_irq = tmp;
ovfbi = mxc_fbi_from->ovfbi;
mxc_fbi_from->ovfbi = mxc_fbi_to->ovfbi;
mxc_fbi_to->ovfbi = ovfbi;
@@ -481,6 +556,10 @@ static int swap_channels(struct fb_info *fbi_from)
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);
+ ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq);
+ ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq);
+ ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq, fbi_from);
+ ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq, fbi_to);
if (mxc_fbi_from->cur_blank == FB_BLANK_UNBLANK) {
if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK)
@@ -514,6 +593,9 @@ static int swap_channels(struct fb_info *fbi_from)
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;
+ i = mxc_fbi_from->ipu_ch_nf_irq;
+ mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq;
+ mxc_fbi_to->ipu_ch_nf_irq = i;
break;
default:
break;
@@ -533,6 +615,20 @@ static int swap_channels(struct fb_info *fbi_from)
return -EBUSY;
}
ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq);
+ if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq, mxcfb_nf_irq_handler, 0,
+ MXCFB_NAME, fbi_from) != 0) {
+ dev_err(fbi_from->device, "Error registering irq %d\n",
+ mxc_fbi_from->ipu_ch_nf_irq);
+ return -EBUSY;
+ }
+ ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq);
+ if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_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_nf_irq);
+ return -EBUSY;
+ }
+ ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq);
return 0;
}
@@ -928,17 +1024,14 @@ static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
}
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);
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
+ ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_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;
@@ -1155,6 +1248,7 @@ 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 int fr_xoff, fr_yoff, fr_w, fr_h;
unsigned long base, active_alpha_phy_addr = 0;
bool loc_alpha_en = false;
int fb_stride;
@@ -1181,9 +1275,6 @@ mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
y_bottom = var->yoffset;
- if (!(var->vmode & FB_VMODE_YWRAP))
- y_bottom += var->yres;
-
if (y_bottom > info->var.yres_virtual)
return -EINVAL;
@@ -1200,8 +1291,21 @@ mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
fb_stride = info->fix.line_length;
}
- base = (var->yoffset * fb_stride + var->xoffset);
- base += info->fix.smem_start;
+ base = info->fix.smem_start;
+ fr_xoff = var->xoffset;
+ fr_w = info->var.xres_virtual;
+ if (!(var->vmode & FB_VMODE_YWRAP)) {
+ dev_dbg(info->device, "Y wrap disabled\n");
+ fr_yoff = var->yoffset % info->var.yres;
+ fr_h = info->var.yres;
+ base += info->fix.line_length * info->var.yres *
+ (var->yoffset / info->var.yres);
+ } else {
+ dev_dbg(info->device, "Y wrap enabled\n");
+ fr_yoff = var->yoffset;
+ fr_h = info->var.yres_virtual;
+ }
+ base += fr_yoff * fb_stride + fr_xoff;
/* 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) {
@@ -1233,7 +1337,8 @@ mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
down(&mxc_fbi->flip_sem);
- 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;
dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n",
@@ -1256,12 +1361,12 @@ mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
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,
+ fr_w,
+ fr_h,
+ fr_w,
0, 0,
- var->yoffset,
- var->xoffset);
+ fr_yoff,
+ fr_xoff);
ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
mxc_fbi->cur_ipu_buf);
@@ -1280,8 +1385,10 @@ mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
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_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);
@@ -1373,14 +1480,18 @@ 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);
- }
+ up(&mxc_fbi->flip_sem);
+ ipu_disable_irq(mxc_fbi->ipu, irq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id)
+{
+ struct fb_info *fbi = dev_id;
+ struct mxcfb_info *mxc_fbi = fbi->par;
+
+ complete(&mxc_fbi->vsync_complete);
+ ipu_disable_irq(mxc_fbi->ipu, irq);
return IRQ_HANDLED;
}
@@ -1628,10 +1739,11 @@ static int mxcfb_dispdrv_init(struct platform_device *pdev,
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);
+ mxcfbi->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting);
+ if (IS_ERR(mxcfbi->dispdrv)) {
+ ret = PTR_ERR(mxcfbi->dispdrv);
+ dev_err(&pdev->dev, "NO mxc display driver found!\n");
+ return ret;
} else {
/* fix-up */
mxcfbi->ipu_di_pix_fmt = setting.if_fmt;
@@ -1672,49 +1784,8 @@ static int mxcfb_option_setup(struct platform_device *pdev)
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;
- }
+ pdata->interface_pix_fmt = if_fmt_parse(opt+3);
+ continue;
}
if (!strncmp(opt, "int_clk", 7)) {
pdata->int_clk = true;
@@ -1727,8 +1798,10 @@ static int mxcfb_option_setup(struct platform_device *pdev)
fb_mode_str = opt;
}
- if (fb_mode_str)
- pdata->mode_str = fb_mode_str;
+ if (fb_mode_str) {
+ memcpy(pdata->mode_str, fb_mode_str, strlen(fb_mode_str));
+ pdata->mode_str[strlen(fb_mode_str)] = '\0';
+ }
return 0;
}
@@ -1755,11 +1828,18 @@ static int mxcfb_register(struct fb_info *fbi)
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");
+ dev_err(fbi->device, "Error registering EOF irq handler.\n");
ret = -EBUSY;
goto err0;
}
ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq);
+ if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, mxcfb_nf_irq_handler, 0,
+ MXCFB_NAME, fbi) != 0) {
+ dev_err(fbi->device, "Error registering NFACK irq handler.\n");
+ ret = -EBUSY;
+ goto err1;
+ }
+ ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq);
if (mxcfbi->ipu_alp_ch_irq != -1)
if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq,
@@ -1768,7 +1848,7 @@ static int mxcfb_register(struct fb_info *fbi)
dev_err(fbi->device, "Error registering alpha irq "
"handler.\n");
ret = -EBUSY;
- goto err1;
+ goto err2;
}
mxcfb_check_var(&fbi->var, fbi);
@@ -1787,8 +1867,6 @@ static int mxcfb_register(struct fb_info *fbi)
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();
@@ -1798,12 +1876,14 @@ static int mxcfb_register(struct fb_info *fbi)
ret = register_framebuffer(fbi);
if (ret < 0)
- goto err2;
+ goto err3;
return ret;
-err2:
+err3:
if (mxcfbi->ipu_alp_ch_irq != -1)
ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi);
+err2:
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi);
err1:
ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi);
err0:
@@ -1818,6 +1898,8 @@ static void mxcfb_unregister(struct fb_info *fbi)
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);
+ if (mxcfbi->ipu_ch_nf_irq)
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi);
unregister_framebuffer(fbi);
}
@@ -1844,6 +1926,7 @@ static int mxcfb_setup_overlay(struct platform_device *pdev,
}
mxcfbi_fg->ipu_id = mxcfbi_bg->ipu_id;
mxcfbi_fg->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF;
+ mxcfbi_fg->ipu_ch_nf_irq = IPU_IRQ_FG_SYNC_NFACK;
mxcfbi_fg->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF;
mxcfbi_fg->ipu_ch = MEM_FG_SYNC;
mxcfbi_fg->ipu_di = -1;
@@ -1898,6 +1981,54 @@ static void ipu_clear_usage(int ipu, int di)
ipu_usage[ipu][di] = false;
}
+static int of_get_fb_data(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+ const char *disp_dev, *if_fmt, *mode_str, *int_clk;
+ const char *default_dev = "lcd";
+ const char *default_mode_str = "800x480M@55";
+ int ret;
+
+ if (!np)
+ return -EINVAL;
+
+ pdev->id = of_dev_id;
+ of_dev_id++;
+
+ plat_data->default_bpp = 16;
+
+ ret = of_property_read_string(np, "disp_dev", &disp_dev);
+ if (ret < 0)
+ memcpy(plat_data->disp_dev, default_dev, strlen(default_dev));
+ else
+ memcpy(plat_data->disp_dev, disp_dev, strlen(disp_dev));
+
+ ret = of_property_read_string(np, "interface_pix_fmt", &if_fmt);
+ if (ret < 0)
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB24;
+ else
+ plat_data->interface_pix_fmt = if_fmt_parse(if_fmt);
+
+ ret = of_property_read_string(np, "mode_str", &mode_str);
+ if (ret < 0)
+ memcpy(plat_data->mode_str, default_mode_str, strlen(default_mode_str));
+ else
+ memcpy(plat_data->mode_str, mode_str, strlen(mode_str));
+
+ ret = of_property_read_string(np, "internal_clk", &int_clk);
+ if (ret < 0)
+ plat_data->int_clk = false;
+ else {
+ if (!strcmp(int_clk, "true"))
+ plat_data->int_clk = true;
+ else
+ plat_data->int_clk = false;
+ }
+
+ return 0;
+}
+
/*!
* Probe routine for the framebuffer driver. It is called during the
* driver binding process. The following functions are performed in
@@ -1923,9 +2054,18 @@ static int mxcfb_probe(struct platform_device *pdev)
goto init_fbinfo_failed;
}
+ mxcfbi = (struct mxcfb_info *)fbi->par;
+
+ if (!plat_data) {
+ plat_data = pdev->dev.platform_data = &mxcfbi->of_data;
+ if (of_get_fb_data(pdev) < 0) {
+ dev_err(&pdev->dev, "no platform data\n");
+ goto platform_data_err;
+ }
+ }
+
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)
@@ -1954,6 +2094,7 @@ static int mxcfb_probe(struct platform_device *pdev)
/* 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_ch_nf_irq = IPU_IRQ_BG_SYNC_NFACK;
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;
@@ -1974,6 +2115,7 @@ static int mxcfb_probe(struct platform_device *pdev)
g_dp_in_use[mxcfbi->ipu_id] = true;
} else {
mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF;
+ mxcfbi->ipu_ch_nf_irq = IPU_IRQ_DC_SYNC_NFACK;
mxcfbi->ipu_alp_ch_irq = -1;
mxcfbi->ipu_ch = MEM_DC_SYNC;
mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN;
@@ -2002,6 +2144,7 @@ get_ipu_failed:
ipu_clear_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
ipu_in_busy:
init_dispdrv_failed:
+platform_data_err:
fb_dealloc_cmap(&fbi->cmap);
framebuffer_release(fbi);
init_fbinfo_failed:
@@ -2033,12 +2176,18 @@ static int mxcfb_remove(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id mxcfb_ipuv3_dt_ids[] = {
+ { .compatible = "fsl,mxcfb-ipuv3", },
+ { /* sentinel */ }
+};
+
/*!
* This structure contains pointers to the power management callback functions.
*/
static struct platform_driver mxcfb_driver = {
.driver = {
.name = MXCFB_NAME,
+ .of_match_table = mxcfb_ipuv3_dt_ids,
},
.probe = mxcfb_probe,
.remove = mxcfb_remove,
diff --git a/drivers/video/mxc/mxc_lcdif.c b/drivers/video/mxc/mxc_lcdif.c
index d9d7fa306a8..5cbdc73c4e7 100644
--- a/drivers/video/mxc/mxc_lcdif.c
+++ b/drivers/video/mxc/mxc_lcdif.c
@@ -21,7 +21,7 @@
struct mxc_lcdif_data {
struct platform_device *pdev;
- struct mxc_dispdrv_entry *disp_lcdif;
+ struct mxc_dispdrv_handle *disp_lcdif;
};
#define DISPDRV_LCD "lcd"
@@ -42,11 +42,11 @@ static struct fb_videomode lcdif_modedb[] = {
};
static int lcdif_modedb_sz = ARRAY_SIZE(lcdif_modedb);
-static int lcdif_init(struct mxc_dispdrv_entry *disp)
+static int lcdif_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
{
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;
@@ -77,7 +77,7 @@ static int lcdif_init(struct mxc_dispdrv_entry *disp)
return ret;
}
-void lcdif_deinit(struct mxc_dispdrv_entry *disp)
+void lcdif_deinit(struct mxc_dispdrv_handle *disp)
{
/*TODO*/
}
@@ -113,6 +113,7 @@ static int mxc_lcdif_remove(struct platform_device *pdev)
{
struct mxc_lcdif_data *lcdif = dev_get_drvdata(&pdev->dev);
+ mxc_dispdrv_puthandle(lcdif->disp_lcdif);
mxc_dispdrv_unregister(lcdif->disp_lcdif);
kfree(lcdif);
return 0;
diff --git a/drivers/video/mxc/mxcfb_sii902x.c b/drivers/video/mxc/mxcfb_sii902x.c
index 17ce8fca68b..f917674423c 100644
--- a/drivers/video/mxc/mxcfb_sii902x.c
+++ b/drivers/video/mxc/mxcfb_sii902x.c
@@ -50,7 +50,7 @@
#include <linux/uaccess.h>
#include <asm/mach-types.h>
#include <mach/hardware.h>
-#include "mxc_edid.h"
+#include <mach/mxc_edid.h>
#include "mxc_dispdrv.h"
#define DISPDRV_SII "hdmi"
@@ -188,7 +188,7 @@
struct sii902x_data {
struct platform_device *pdev;
struct i2c_client *client;
- struct mxc_dispdrv_entry *disp_hdmi;
+ struct mxc_dispdrv_handle *disp_hdmi;
struct regulator *io_reg;
struct regulator *analog_reg;
struct delayed_work det_work;
@@ -671,7 +671,11 @@ static void sii902x_setup(struct sii902x_data *sii902x, struct fb_info *fbi)
/* reg 0x0a: set output format to RGB */
sii902x->tpivmode[2] = 0x00;
- if (fbi->var.xres/16 == fbi->var.yres/9)
+ if (fbi->var.vmode & FB_VMODE_ASPECT_16_9)
+ sii902x->aspect_ratio = VMD_ASPECT_RATIO_16x9;
+ else if (fbi->var.vmode & FB_VMODE_ASPECT_4_3)
+ sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3;
+ else if (fbi->var.xres/16 == fbi->var.yres/9)
sii902x->aspect_ratio = VMD_ASPECT_RATIO_16x9;
else
sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3;
@@ -1060,11 +1064,11 @@ static int sii902x_TPI_init(struct i2c_client *client)
return 0;
}
-static int sii902x_disp_init(struct mxc_dispdrv_entry *disp)
+static int sii902x_disp_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
{
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;
@@ -1211,7 +1215,7 @@ register_pltdev_failed:
return ret;
}
-static void sii902x_disp_deinit(struct mxc_dispdrv_entry *disp)
+static void sii902x_disp_deinit(struct mxc_dispdrv_handle *disp)
{
struct sii902x_data *sii902x = mxc_dispdrv_getdata(disp);
struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data;
@@ -1269,6 +1273,7 @@ static int __devexit sii902x_remove(struct i2c_client *client)
{
struct sii902x_data *sii902x = i2c_get_clientdata(client);
+ mxc_dispdrv_puthandle(sii902x->disp_hdmi);
mxc_dispdrv_unregister(sii902x->disp_hdmi);
kfree(sii902x);
return 0;
diff --git a/drivers/video/mxc/tve.c b/drivers/video/mxc/tve.c
index 35b100349fe..bd125964a6d 100644
--- a/drivers/video/mxc/tve.c
+++ b/drivers/video/mxc/tve.c
@@ -38,7 +38,6 @@
#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)
@@ -121,8 +120,8 @@ struct tve_data {
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 mxc_dispdrv_handle *disp_tve;
+ struct mxc_dispdrv_handle *disp_vga;
struct notifier_block nb;
};
@@ -935,6 +934,15 @@ int tve_fb_setup(struct tve_data *tve, struct fb_info *fbi)
return 0;
}
+static inline int tve_disp_setup(struct mxc_dispdrv_handle *disp,
+ struct fb_info *fbi)
+{
+ struct tve_data *tve = mxc_dispdrv_getdata(disp);
+
+ tve_fb_setup(tve, fbi);
+ 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);
@@ -946,11 +954,6 @@ int tve_fb_event(struct notifier_block *nb, unsigned long val, void *v)
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;
@@ -1029,11 +1032,11 @@ static int _tve_get_revision(struct tve_data *tve)
return rev;
}
-static int tve_drv_init(struct mxc_dispdrv_entry *disp, bool vga)
+static int tve_drv_init(struct mxc_dispdrv_handle *disp, bool vga,
+ struct mxc_dispdrv_setting *setting)
{
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;
@@ -1105,6 +1108,18 @@ static int tve_drv_init(struct mxc_dispdrv_entry *disp, bool vga)
tve->reg_fields = &tve_reg_fields_v2;
}
+ /* adjust video mode for mx37 */
+ if (cpu_is_mx37()) {
+ video_modes_tve[0].left_margin = 121;
+ video_modes_tve[0].right_margin = 16;
+ video_modes_tve[0].upper_margin = 17;
+ video_modes_tve[0].lower_margin = 5;
+ video_modes_tve[1].left_margin = 131;
+ video_modes_tve[1].right_margin = 12;
+ video_modes_tve[1].upper_margin = 21;
+ video_modes_tve[1].lower_margin = 3;
+ }
+
if (vga && cpu_is_mx53()) {
setting->if_fmt = IPU_PIX_FMT_GBR24;
modedb = video_modes_vga;
@@ -1179,17 +1194,19 @@ get_res_failed:
}
-static int tvout_init(struct mxc_dispdrv_entry *disp)
+static int tvout_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
{
- return tve_drv_init(disp, 0);
+ return tve_drv_init(disp, 0, setting);
}
-static int vga_init(struct mxc_dispdrv_entry *disp)
+static int vga_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
{
- return tve_drv_init(disp, 1);
+ return tve_drv_init(disp, 1, setting);
}
-void tvout_deinit(struct mxc_dispdrv_entry *disp)
+void tvout_deinit(struct mxc_dispdrv_handle *disp)
{
struct tve_data *tve = mxc_dispdrv_getdata(disp);
@@ -1212,6 +1229,7 @@ static struct mxc_dispdrv_driver vga_drv = {
.name = DISPDRV_VGA,
.init = vga_init,
.deinit = tvout_deinit,
+ .setup = tve_disp_setup,
};
static int tve_dispdrv_init(struct tve_data *tve)
@@ -1225,6 +1243,8 @@ static int tve_dispdrv_init(struct tve_data *tve)
static void tve_dispdrv_deinit(struct tve_data *tve)
{
+ mxc_dispdrv_puthandle(tve->disp_tve);
+ mxc_dispdrv_puthandle(tve->disp_vga);
mxc_dispdrv_unregister(tve->disp_tve);
mxc_dispdrv_unregister(tve->disp_vga);
}
diff --git a/drivers/video/mxc_hdmi.c b/drivers/video/mxc_hdmi.c
new file mode 100644
index 00000000000..b212ad128b7
--- /dev/null
+++ b/drivers/video/mxc_hdmi.c
@@ -0,0 +1,2363 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/*
+ * SH-Mobile High-Definition Multimedia Interface (HDMI) driver
+ * for SLISHDMI13T and SLIPHDMIT IP cores
+ *
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <mach/clock.h>
+#include <linux/uaccess.h>
+#include <linux/cpufreq.h>
+#include <linux/firmware.h>
+#include <linux/kthread.h>
+#include <linux/regulator/driver.h>
+#include <linux/fsl_devices.h>
+#include <linux/ipu.h>
+
+#include <linux/console.h>
+#include <linux/types.h>
+
+#include <mach/mxc_edid.h>
+#include "mxc/mxc_dispdrv.h"
+
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <mach/mxc_hdmi.h>
+
+#define DISPDRV_HDMI "hdmi"
+#define HDMI_EDID_LEN 512
+
+/* status codes for reading edid */
+#define HDMI_EDID_SUCCESS 0
+#define HDMI_EDID_FAIL -1
+#define HDMI_EDID_SAME -2
+#define HDMI_EDID_NO_MODES -3
+
+#define NUM_CEA_VIDEO_MODES 64
+#define DEFAULT_VIDEO_MODE 16 /* 1080P */
+
+#define RGB 0
+#define YCBCR444 1
+#define YCBCR422_16BITS 2
+#define YCBCR422_8BITS 3
+#define XVYCC444 4
+
+/*
+ * We follow a flowchart which is in the "Synopsys DesignWare Courses
+ * HDMI Transmitter Controller User Guide, 1.30a", section 3.1
+ * (dwc_hdmi_tx_user.pdf)
+ *
+ * Below are notes that say "HDMI Initialization Step X"
+ * These correspond to the flowchart.
+ */
+
+/*
+ * We are required to configure VGA mode before reading edid
+ * in HDMI Initialization Step B
+ */
+static const struct fb_videomode vga_mode = {
+ /* 640x480 @ 60 Hz, 31.5 kHz hsync */
+ NULL, 60, 640, 480, 39721, 48, 16, 33, 10, 96, 2, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+};
+
+static const struct fb_videomode xga_mode = {
+ /* 13 1024x768-60 VESA */
+ NULL, 60, 1024, 768, 15384, 160, 24, 29, 3, 136, 6,
+ 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA
+};
+
+static const struct fb_videomode sxga_mode = {
+ /* 20 1280x1024-60 VESA */
+ NULL, 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA
+};
+
+enum hdmi_datamap {
+ RGB444_8B = 0x01,
+ RGB444_10B = 0x03,
+ RGB444_12B = 0x05,
+ RGB444_16B = 0x07,
+ YCbCr444_8B = 0x09,
+ YCbCr444_10B = 0x0B,
+ YCbCr444_12B = 0x0D,
+ YCbCr444_16B = 0x0F,
+ YCbCr422_8B = 0x16,
+ YCbCr422_10B = 0x14,
+ YCbCr422_12B = 0x12,
+};
+
+enum hdmi_colorimetry {
+ eITU601,
+ eITU709,
+};
+
+struct hdmi_vmode {
+ bool mDVI;
+ bool mHSyncPolarity;
+ bool mVSyncPolarity;
+ bool mInterlaced;
+ bool mDataEnablePolarity;
+
+ unsigned int mPixelClock;
+ unsigned int mPixelRepetitionInput;
+ unsigned int mPixelRepetitionOutput;
+};
+
+struct hdmi_data_info {
+ unsigned int enc_in_format;
+ unsigned int enc_out_format;
+ unsigned int enc_color_depth;
+ unsigned int colorimetry;
+ unsigned int pix_repet_factor;
+ unsigned int hdcp_enable;
+ struct hdmi_vmode video_mode;
+};
+
+struct mxc_hdmi {
+ struct platform_device *pdev;
+ struct platform_device *core_pdev;
+ struct mxc_dispdrv_handle *disp_mxc_hdmi;
+ struct fb_info *fbi;
+ struct clk *hdmi_isfr_clk;
+ struct clk *hdmi_iahb_clk;
+ struct delayed_work hotplug_work;
+ struct notifier_block nb;
+
+ struct hdmi_data_info hdmi_data;
+ int vic;
+ struct mxc_edid_cfg edid_cfg;
+ u8 edid[HDMI_EDID_LEN];
+ bool fb_reg;
+ bool cable_plugin;
+ bool dft_mode_set;
+ char *dft_mode_str;
+ int default_bpp;
+ u8 latest_intr_stat;
+ bool irq_enabled;
+ spinlock_t irq_lock;
+ bool phy_enabled;
+ struct fb_videomode previous_mode;
+ struct fb_videomode previous_non_vga_mode;
+ bool requesting_vga_for_initialization;
+};
+
+struct i2c_client *hdmi_i2c;
+
+extern const struct fb_videomode mxc_cea_mode[64];
+
+#ifdef DEBUG
+static void dump_fb_videomode(struct fb_videomode *m)
+{
+ pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
+ m->refresh, m->xres, m->yres, m->pixclock, m->left_margin,
+ m->right_margin, m->upper_margin, m->lower_margin,
+ m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag);
+}
+#else
+static void dump_fb_videomode(struct fb_videomode *m)
+{}
+#endif
+
+static ssize_t mxc_hdmi_show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+
+ strcpy(buf, hdmi->fbi->fix.id);
+ sprintf(buf+strlen(buf), "\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(fb_name, S_IRUGO, mxc_hdmi_show_name, NULL);
+
+static ssize_t mxc_hdmi_show_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+
+ if (hdmi->cable_plugin == false)
+ strcpy(buf, "plugout\n");
+ else
+ strcpy(buf, "plugin\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(cable_state, S_IRUGO, mxc_hdmi_show_state, NULL);
+
+static ssize_t mxc_hdmi_show_edid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+ int i, j, len = 0;
+
+ for (j = 0; j < HDMI_EDID_LEN/16; j++) {
+ for (i = 0; i < 16; i++)
+ len += sprintf(buf+len, "0x%02X ",
+ hdmi->edid[j*16 + i]);
+ len += sprintf(buf+len, "\n");
+ }
+
+ return len;
+}
+
+static DEVICE_ATTR(edid, S_IRUGO, mxc_hdmi_show_edid, NULL);
+
+/*!
+ * this submodule is responsible for the video data synchronization.
+ * for example, for RGB 4:4:4 input, the data map is defined as
+ * pin{47~40} <==> R[7:0]
+ * pin{31~24} <==> G[7:0]
+ * pin{15~8} <==> B[7:0]
+ */
+static void hdmi_video_sample(struct mxc_hdmi *hdmi)
+{
+ int color_format = 0;
+ u8 val;
+
+ if (hdmi->hdmi_data.enc_in_format == RGB) {
+ if (hdmi->hdmi_data.enc_color_depth == 8)
+ color_format = 0x01;
+ else if (hdmi->hdmi_data.enc_color_depth == 10)
+ color_format = 0x03;
+ else if (hdmi->hdmi_data.enc_color_depth == 12)
+ color_format = 0x05;
+ else if (hdmi->hdmi_data.enc_color_depth == 16)
+ color_format = 0x07;
+ else
+ return;
+ } else if (hdmi->hdmi_data.enc_in_format == XVYCC444) {
+ if (hdmi->hdmi_data.enc_color_depth == 8)
+ color_format = 0x09;
+ else if (hdmi->hdmi_data.enc_color_depth == 10)
+ color_format = 0x0B;
+ else if (hdmi->hdmi_data.enc_color_depth == 12)
+ color_format = 0x0D;
+ else if (hdmi->hdmi_data.enc_color_depth == 16)
+ color_format = 0x0F;
+ else
+ return;
+ } else if (hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) {
+ if (hdmi->hdmi_data.enc_color_depth == 8)
+ color_format = 0x16;
+ else if (hdmi->hdmi_data.enc_color_depth == 10)
+ color_format = 0x14;
+ else if (hdmi->hdmi_data.enc_color_depth == 12)
+ color_format = 0x12;
+ else
+ return;
+ }
+
+ val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE |
+ ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) &
+ HDMI_TX_INVID0_VIDEO_MAPPING_MASK);
+ hdmi_writeb(val, HDMI_TX_INVID0);
+
+ /* Enable TX stuffing: When DE is inactive, fix the output data to 0 */
+ val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE |
+ HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE |
+ HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE;
+ hdmi_writeb(val, HDMI_TX_INSTUFFING);
+ hdmi_writeb(0x0, HDMI_TX_GYDATA0);
+ hdmi_writeb(0x0, HDMI_TX_GYDATA1);
+ hdmi_writeb(0x0, HDMI_TX_RCRDATA0);
+ hdmi_writeb(0x0, HDMI_TX_RCRDATA1);
+ hdmi_writeb(0x0, HDMI_TX_BCBDATA0);
+ hdmi_writeb(0x0, HDMI_TX_BCBDATA1);
+}
+
+static int isColorSpaceConversion(struct mxc_hdmi *hdmi)
+{
+ return (hdmi->hdmi_data.enc_in_format !=
+ hdmi->hdmi_data.enc_out_format);
+}
+
+static int isColorSpaceDecimation(struct mxc_hdmi *hdmi)
+{
+ return ((hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS) &&
+ (hdmi->hdmi_data.enc_in_format == RGB ||
+ hdmi->hdmi_data.enc_in_format == XVYCC444));
+}
+
+static int isColorSpaceInterpolation(struct mxc_hdmi *hdmi)
+{
+ return ((hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) &&
+ (hdmi->hdmi_data.enc_out_format == RGB
+ || hdmi->hdmi_data.enc_out_format == XVYCC444));
+}
+
+/*!
+ * update the color space conversion coefficients.
+ */
+static void update_csc_coeffs(struct mxc_hdmi *hdmi)
+{
+ unsigned short csc_coeff[3][4];
+ unsigned int csc_scale = 1;
+ u8 val;
+ bool coeff_selected = false;
+
+ if (isColorSpaceConversion(hdmi)) { /* csc needed */
+ if (hdmi->hdmi_data.enc_out_format == RGB) {
+ if (hdmi->hdmi_data.colorimetry == eITU601) {
+ csc_coeff[0][0] = 0x2000;
+ csc_coeff[0][1] = 0x6926;
+ csc_coeff[0][2] = 0x74fd;
+ csc_coeff[0][3] = 0x010e;
+
+ csc_coeff[1][0] = 0x2000;
+ csc_coeff[1][1] = 0x2cdd;
+ csc_coeff[1][2] = 0x0000;
+ csc_coeff[1][3] = 0x7e9a;
+
+ csc_coeff[2][0] = 0x2000;
+ csc_coeff[2][1] = 0x0000;
+ csc_coeff[2][2] = 0x38b4;
+ csc_coeff[2][3] = 0x7e3b;
+
+ csc_scale = 1;
+ coeff_selected = true;
+ } else if (hdmi->hdmi_data.colorimetry == eITU709) {
+ csc_coeff[0][0] = 0x2000;
+ csc_coeff[0][1] = 0x7106;
+ csc_coeff[0][2] = 0x7a02;
+ csc_coeff[0][3] = 0x00a7;
+
+ csc_coeff[1][0] = 0x2000;
+ csc_coeff[1][1] = 0x3264;
+ csc_coeff[1][2] = 0x0000;
+ csc_coeff[1][3] = 0x7e6d;
+
+ csc_coeff[2][0] = 0x2000;
+ csc_coeff[2][1] = 0x0000;
+ csc_coeff[2][2] = 0x3b61;
+ csc_coeff[2][3] = 0x7e25;
+
+ csc_scale = 1;
+ coeff_selected = true;
+ }
+ } else if (hdmi->hdmi_data.enc_in_format == RGB) {
+ if (hdmi->hdmi_data.colorimetry == eITU601) {
+ csc_coeff[0][0] = 0x2591;
+ csc_coeff[0][1] = 0x1322;
+ csc_coeff[0][2] = 0x074b;
+ csc_coeff[0][3] = 0x0000;
+
+ csc_coeff[1][0] = 0x6535;
+ csc_coeff[1][1] = 0x2000;
+ csc_coeff[1][2] = 0x7acc;
+ csc_coeff[1][3] = 0x0200;
+
+ csc_coeff[2][0] = 0x6acd;
+ csc_coeff[2][1] = 0x7534;
+ csc_coeff[2][2] = 0x2000;
+ csc_coeff[2][3] = 0x0200;
+
+ csc_scale = 0;
+ coeff_selected = true;
+ } else if (hdmi->hdmi_data.colorimetry == eITU709) {
+ csc_coeff[0][0] = 0x2dc5;
+ csc_coeff[0][1] = 0x0d9b;
+ csc_coeff[0][2] = 0x049e;
+ csc_coeff[0][3] = 0x0000;
+
+ csc_coeff[1][0] = 0x62f0;
+ csc_coeff[1][1] = 0x2000;
+ csc_coeff[1][2] = 0x7d11;
+ csc_coeff[1][3] = 0x0200;
+
+ csc_coeff[2][0] = 0x6756;
+ csc_coeff[2][1] = 0x78ab;
+ csc_coeff[2][2] = 0x2000;
+ csc_coeff[2][3] = 0x0200;
+
+ csc_scale = 0;
+ coeff_selected = true;
+ }
+ }
+ }
+
+ if (!coeff_selected) {
+ csc_coeff[0][0] = 0x2000;
+ csc_coeff[0][1] = 0x0000;
+ csc_coeff[0][2] = 0x0000;
+ csc_coeff[0][3] = 0x0000;
+
+ csc_coeff[1][0] = 0x0000;
+ csc_coeff[1][1] = 0x2000;
+ csc_coeff[1][2] = 0x0000;
+ csc_coeff[1][3] = 0x0000;
+
+ csc_coeff[2][0] = 0x0000;
+ csc_coeff[2][1] = 0x0000;
+ csc_coeff[2][2] = 0x2000;
+ csc_coeff[2][3] = 0x0000;
+
+ csc_scale = 1;
+ }
+
+ /* Update CSC parameters in HDMI CSC registers */
+ hdmi_writeb((unsigned char)(csc_coeff[0][0] & 0xFF),
+ HDMI_CSC_COEF_A1_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][0] >> 8),
+ HDMI_CSC_COEF_A1_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][1] & 0xFF),
+ HDMI_CSC_COEF_A2_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][1] >> 8),
+ HDMI_CSC_COEF_A2_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][2] & 0xFF),
+ HDMI_CSC_COEF_A3_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][2] >> 8),
+ HDMI_CSC_COEF_A3_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][3] & 0xFF),
+ HDMI_CSC_COEF_A4_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][3] >> 8),
+ HDMI_CSC_COEF_A4_MSB);
+
+ hdmi_writeb((unsigned char)(csc_coeff[1][0] & 0xFF),
+ HDMI_CSC_COEF_B1_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][0] >> 8),
+ HDMI_CSC_COEF_B1_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][1] & 0xFF),
+ HDMI_CSC_COEF_B2_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][1] >> 8),
+ HDMI_CSC_COEF_B2_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][2] & 0xFF),
+ HDMI_CSC_COEF_B3_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][2] >> 8),
+ HDMI_CSC_COEF_B3_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][3] & 0xFF),
+ HDMI_CSC_COEF_B4_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][3] >> 8),
+ HDMI_CSC_COEF_B4_MSB);
+
+ hdmi_writeb((unsigned char)(csc_coeff[2][0] & 0xFF),
+ HDMI_CSC_COEF_C1_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][0] >> 8),
+ HDMI_CSC_COEF_C1_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][1] & 0xFF),
+ HDMI_CSC_COEF_C2_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][1] >> 8),
+ HDMI_CSC_COEF_C2_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][2] & 0xFF),
+ HDMI_CSC_COEF_C3_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][2] >> 8),
+ HDMI_CSC_COEF_C3_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][3] & 0xFF),
+ HDMI_CSC_COEF_C4_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][3] >> 8),
+ HDMI_CSC_COEF_C4_MSB);
+
+ val = hdmi_readb(HDMI_CSC_SCALE);
+ val &= ~HDMI_CSC_SCALE_CSCSCALE_MASK;
+ val |= csc_scale & HDMI_CSC_SCALE_CSCSCALE_MASK;
+ hdmi_writeb(val, HDMI_CSC_SCALE);
+}
+
+static void hdmi_video_csc(struct mxc_hdmi *hdmi)
+{
+ int color_depth = 0;
+ int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE;
+ int decimation = 0;
+ u8 val;
+
+ /* YCC422 interpolation to 444 mode */
+ if (isColorSpaceInterpolation(hdmi))
+ interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1;
+ else if (isColorSpaceDecimation(hdmi))
+ decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA1;
+
+ if (hdmi->hdmi_data.enc_color_depth == 8)
+ color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP;
+ else if (hdmi->hdmi_data.enc_color_depth == 10)
+ color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP;
+ else if (hdmi->hdmi_data.enc_color_depth == 12)
+ color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP;
+ else if (hdmi->hdmi_data.enc_color_depth == 16)
+ color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP;
+ else
+ return;
+
+ /*configure the CSC registers */
+ hdmi_writeb(interpolation | decimation, HDMI_CSC_CFG);
+ val = hdmi_readb(HDMI_CSC_SCALE);
+ val &= ~HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK;
+ val |= color_depth;
+ hdmi_writeb(val, HDMI_CSC_SCALE);
+
+ update_csc_coeffs(hdmi);
+}
+
+/*!
+ * HDMI video packetizer is used to packetize the data.
+ * for example, if input is YCC422 mode or repeater is used,
+ * data should be repacked this module can be bypassed.
+ */
+static void hdmi_video_packetize(struct mxc_hdmi *hdmi)
+{
+ unsigned int color_depth = 0;
+ unsigned int remap_size = HDMI_VP_REMAP_YCC422_16bit;
+ unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP;
+ struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data;
+ u8 val;
+
+ if (hdmi_data->enc_out_format == RGB
+ || hdmi_data->enc_out_format == YCBCR444) {
+ if (hdmi_data->enc_color_depth == 0)
+ output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
+ else if (hdmi_data->enc_color_depth == 8) {
+ color_depth = 4;
+ output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
+ } else if (hdmi_data->enc_color_depth == 10)
+ color_depth = 5;
+ else if (hdmi_data->enc_color_depth == 12)
+ color_depth = 6;
+ else if (hdmi_data->enc_color_depth == 16)
+ color_depth = 7;
+ else
+ return;
+ } else if (hdmi_data->enc_out_format == YCBCR422_8BITS) {
+ if (hdmi_data->enc_color_depth == 0 ||
+ hdmi_data->enc_color_depth == 8)
+ remap_size = HDMI_VP_REMAP_YCC422_16bit;
+ else if (hdmi_data->enc_color_depth == 10)
+ remap_size = HDMI_VP_REMAP_YCC422_20bit;
+ else if (hdmi_data->enc_color_depth == 12)
+ remap_size = HDMI_VP_REMAP_YCC422_24bit;
+ else
+ return;
+ output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422;
+ } else
+ return;
+
+ /* set the packetizer registers */
+ val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) &
+ HDMI_VP_PR_CD_COLOR_DEPTH_MASK) |
+ ((hdmi_data->pix_repet_factor <<
+ HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) &
+ HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK);
+ hdmi_writeb(val, HDMI_VP_PR_CD);
+
+ val = hdmi_readb(HDMI_VP_STUFF);
+ val &= ~HDMI_VP_STUFF_PR_STUFFING_MASK;
+ val |= HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE;
+ hdmi_writeb(val, HDMI_VP_STUFF);
+
+ /* Data from pixel repeater block */
+ if (hdmi_data->pix_repet_factor > 1) {
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_PR_EN_MASK |
+ HDMI_VP_CONF_BYPASS_SELECT_MASK);
+ val |= HDMI_VP_CONF_PR_EN_ENABLE |
+ HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ } else { /* data from packetizer block */
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_PR_EN_MASK |
+ HDMI_VP_CONF_BYPASS_SELECT_MASK);
+ val |= HDMI_VP_CONF_PR_EN_DISABLE |
+ HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ }
+
+ val = hdmi_readb(HDMI_VP_STUFF);
+ val &= ~HDMI_VP_STUFF_IDEFAULT_PHASE_MASK;
+ val |= 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET;
+ hdmi_writeb(val, HDMI_VP_STUFF);
+
+ hdmi_writeb(remap_size, HDMI_VP_REMAP);
+
+ if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) {
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
+ HDMI_VP_CONF_PP_EN_ENMASK |
+ HDMI_VP_CONF_YCC422_EN_MASK);
+ val |= HDMI_VP_CONF_BYPASS_EN_DISABLE |
+ HDMI_VP_CONF_PP_EN_ENABLE |
+ HDMI_VP_CONF_YCC422_EN_DISABLE;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) {
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
+ HDMI_VP_CONF_PP_EN_ENMASK |
+ HDMI_VP_CONF_YCC422_EN_MASK);
+ val |= HDMI_VP_CONF_BYPASS_EN_DISABLE |
+ HDMI_VP_CONF_PP_EN_DISABLE |
+ HDMI_VP_CONF_YCC422_EN_ENABLE;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) {
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
+ HDMI_VP_CONF_PP_EN_ENMASK |
+ HDMI_VP_CONF_YCC422_EN_MASK);
+ val |= HDMI_VP_CONF_BYPASS_EN_ENABLE |
+ HDMI_VP_CONF_PP_EN_DISABLE |
+ HDMI_VP_CONF_YCC422_EN_DISABLE;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ } else {
+ return;
+ }
+
+ val = hdmi_readb(HDMI_VP_STUFF);
+ val &= ~(HDMI_VP_STUFF_PP_STUFFING_MASK |
+ HDMI_VP_STUFF_YCC422_STUFFING_MASK);
+ val |= HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE |
+ HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE;
+ hdmi_writeb(val, HDMI_VP_STUFF);
+
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~HDMI_VP_CONF_OUTPUT_SELECTOR_MASK;
+ val |= output_select;
+ hdmi_writeb(val, HDMI_VP_CONF);
+}
+
+#if 0
+/* Force a fixed color screen */
+static void hdmi_video_force_output(struct mxc_hdmi *hdmi, unsigned char force)
+{
+ u8 val;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ if (force) {
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS2); /* R */
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS1); /* G */
+ hdmi_writeb(0xFF, HDMI_FC_DBGTMDS0); /* B */
+ val = hdmi_readb(HDMI_FC_DBGFORCE);
+ val |= HDMI_FC_DBGFORCE_FORCEVIDEO;
+ hdmi_writeb(val, HDMI_FC_DBGFORCE);
+ } else {
+ val = hdmi_readb(HDMI_FC_DBGFORCE);
+ val &= ~HDMI_FC_DBGFORCE_FORCEVIDEO;
+ hdmi_writeb(val, HDMI_FC_DBGFORCE);
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS2); /* R */
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS1); /* G */
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS0); /* B */
+ }
+}
+#endif
+
+static inline void hdmi_phy_test_clear(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ u8 val = hdmi_readb(HDMI_PHY_TST0);
+ val &= ~HDMI_PHY_TST0_TSTCLR_MASK;
+ val |= (bit << HDMI_PHY_TST0_TSTCLR_OFFSET) &
+ HDMI_PHY_TST0_TSTCLR_MASK;
+ hdmi_writeb(val, HDMI_PHY_TST0);
+}
+
+static inline void hdmi_phy_test_enable(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ u8 val = hdmi_readb(HDMI_PHY_TST0);
+ val &= ~HDMI_PHY_TST0_TSTEN_MASK;
+ val |= (bit << HDMI_PHY_TST0_TSTEN_OFFSET) &
+ HDMI_PHY_TST0_TSTEN_MASK;
+ hdmi_writeb(val, HDMI_PHY_TST0);
+}
+
+static inline void hdmi_phy_test_clock(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ u8 val = hdmi_readb(HDMI_PHY_TST0);
+ val &= ~HDMI_PHY_TST0_TSTCLK_MASK;
+ val |= (bit << HDMI_PHY_TST0_TSTCLK_OFFSET) &
+ HDMI_PHY_TST0_TSTCLK_MASK;
+ hdmi_writeb(val, HDMI_PHY_TST0);
+}
+
+static inline void hdmi_phy_test_din(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ hdmi_writeb(bit, HDMI_PHY_TST1);
+}
+
+static inline void hdmi_phy_test_dout(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ hdmi_writeb(bit, HDMI_PHY_TST2);
+}
+
+static bool hdmi_phy_wait_i2c_done(struct mxc_hdmi *hdmi, int msec)
+{
+ unsigned char val = 0;
+ val = hdmi_readb(HDMI_IH_I2CMPHY_STAT0) & 0x3;
+ while (val == 0) {
+ udelay(1000);
+ if (msec-- == 0)
+ return false;
+ val = hdmi_readb(HDMI_IH_I2CMPHY_STAT0) & 0x3;
+ }
+ return true;
+}
+
+static void hdmi_phy_i2c_write(struct mxc_hdmi *hdmi, unsigned short data,
+ unsigned char addr)
+{
+ hdmi_writeb(0xFF, HDMI_IH_I2CMPHY_STAT0);
+ hdmi_writeb(addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
+ hdmi_writeb((unsigned char)(data >> 8),
+ HDMI_PHY_I2CM_DATAO_1_ADDR);
+ hdmi_writeb((unsigned char)(data >> 0),
+ HDMI_PHY_I2CM_DATAO_0_ADDR);
+ hdmi_writeb(HDMI_PHY_I2CM_OPERATION_ADDR_WRITE,
+ HDMI_PHY_I2CM_OPERATION_ADDR);
+ hdmi_phy_wait_i2c_done(hdmi, 1000);
+}
+
+#if 0
+static unsigned short hdmi_phy_i2c_read(struct mxc_hdmi *hdmi,
+ unsigned char addr)
+{
+ unsigned short data;
+ unsigned char msb = 0, lsb = 0;
+ hdmi_writeb(0xFF, HDMI_IH_I2CMPHY_STAT0);
+ hdmi_writeb(addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
+ hdmi_writeb(HDMI_PHY_I2CM_OPERATION_ADDR_READ,
+ HDMI_PHY_I2CM_OPERATION_ADDR);
+ hdmi_phy_wait_i2c_done(hdmi, 1000);
+ msb = hdmi_readb(HDMI_PHY_I2CM_DATAI_1_ADDR);
+ lsb = hdmi_readb(HDMI_PHY_I2CM_DATAI_0_ADDR);
+ data = (msb << 8) | lsb;
+ return data;
+}
+
+static int hdmi_phy_i2c_write_verify(struct mxc_hdmi *hdmi, unsigned short data,
+ unsigned char addr)
+{
+ unsigned short val = 0;
+ hdmi_phy_i2c_write(hdmi, data, addr);
+ val = hdmi_phy_i2c_read(hdmi, addr);
+ return (val == data);
+}
+#endif
+
+/* "Power-down enable (active low)"
+ * That mean that power up == 1! */
+static void mxc_hdmi_phy_enable_power(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_PDZ_OFFSET,
+ HDMI_PHY_CONF0_PDZ_MASK);
+}
+
+static void mxc_hdmi_phy_enable_tmds(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_ENTMDS_OFFSET,
+ HDMI_PHY_CONF0_ENTMDS_MASK);
+}
+
+static void mxc_hdmi_phy_gen2_pddq(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET,
+ HDMI_PHY_CONF0_GEN2_PDDQ_MASK);
+}
+
+static void mxc_hdmi_phy_gen2_txpwron(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET,
+ HDMI_PHY_CONF0_GEN2_TXPWRON_MASK);
+}
+
+#if 0
+static void mxc_hdmi_phy_gen2_enhpdrxsense(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET,
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK);
+}
+#endif
+
+static void mxc_hdmi_phy_sel_data_en_pol(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_SELDATAENPOL_OFFSET,
+ HDMI_PHY_CONF0_SELDATAENPOL_MASK);
+}
+
+static void mxc_hdmi_phy_sel_interface_control(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_SELDIPIF_OFFSET,
+ HDMI_PHY_CONF0_SELDIPIF_MASK);
+}
+
+static int hdmi_phy_configure(struct mxc_hdmi *hdmi, unsigned char pRep,
+ unsigned char cRes, int cscOn)
+{
+ u8 val;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* color resolution 0 is 8 bit colour depth */
+ if (cRes == 0)
+ cRes = 8;
+
+ if (pRep != 0)
+ return false;
+ else if (cRes != 8 && cRes != 12)
+ return false;
+
+ if (cscOn)
+ val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH;
+ else
+ val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS;
+
+ hdmi_writeb(val, HDMI_MC_FLOWCTRL);
+
+ /* gen2 tx power off */
+ mxc_hdmi_phy_gen2_txpwron(0);
+
+ /* gen2 pddq */
+ mxc_hdmi_phy_gen2_pddq(1);
+
+ /* PHY reset */
+ hdmi_writeb(HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ);
+ hdmi_writeb(HDMI_MC_PHYRSTZ_ASSERT, HDMI_MC_PHYRSTZ);
+
+ hdmi_writeb(HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
+
+ hdmi_phy_test_clear(hdmi, 1);
+ hdmi_writeb(HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
+ HDMI_PHY_I2CM_SLAVE_ADDR);
+ hdmi_phy_test_clear(hdmi, 0);
+
+ if (hdmi->hdmi_data.video_mode.mPixelClock < 0) {
+ dev_dbg(&hdmi->pdev->dev, "Pixel clock (%d) must be positive\n",
+ hdmi->hdmi_data.video_mode.mPixelClock);
+ return false;
+ }
+
+ if (hdmi->hdmi_data.video_mode.mPixelClock <= 45250000) {
+ switch (cRes) {
+ case 8:
+ /* PLL/MPLL Cfg */
+ hdmi_phy_i2c_write(hdmi, 0x01e0, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x15); /* GMPCTRL */
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x21e1, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x15);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x41e2, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x15);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 92500000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x0140, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x2141, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x4142, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 148500000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x00a0, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x20a1, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x40a2, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+ default:
+ return false;
+ }
+ } else {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x00a0, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x2001, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000f, 0x15);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x4002, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000f, 0x15);
+ default:
+ return false;
+ }
+ }
+
+ if (hdmi->hdmi_data.video_mode.mPixelClock <= 54000000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); /* CURRCTRL */
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 58400000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 72000000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 74250000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x0b5c, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 118800000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 216000000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x0b5c, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else {
+ dev_err(&hdmi->pdev->dev,
+ "Pixel clock %d - unsupported by HDMI\n",
+ hdmi->hdmi_data.video_mode.mPixelClock);
+ return false;
+ }
+
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */
+ hdmi_phy_i2c_write(hdmi, 0x0006, 0x17);
+ /* RESISTANCE TERM 133Ohm Cfg */
+ hdmi_phy_i2c_write(hdmi, 0x0005, 0x19); /* TXTERM */
+ /* PREEMP Cgf 0.00 */
+ hdmi_phy_i2c_write(hdmi, 0x8009, 0x09); /* CKSYMTXCTRL */
+ /* TX/CK LVL 10 */
+ hdmi_phy_i2c_write(hdmi, 0x0210, 0x0E); /* VLEVCTRL */
+ /* REMOVE CLK TERM */
+ hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */
+
+ if (hdmi->hdmi_data.video_mode.mPixelClock > 148500000) {
+ hdmi_phy_i2c_write(hdmi, 0x800b, 0x09);
+ hdmi_phy_i2c_write(hdmi, 0x0129, 0x0E);
+ }
+
+ mxc_hdmi_phy_enable_power(1);
+
+ /* toggle TMDS enable */
+ mxc_hdmi_phy_enable_tmds(0);
+ mxc_hdmi_phy_enable_tmds(1);
+
+ /* gen2 tx power on */
+ mxc_hdmi_phy_gen2_txpwron(1);
+ mxc_hdmi_phy_gen2_pddq(0);
+
+ udelay(1000);
+
+ /* wait for the phy PLL to lock */
+ while ((hdmi_readb(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK) == 0)
+ ;
+
+ /* Has the PHY PLL locked? */
+ if ((hdmi_readb(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK) == 0)
+ return false;
+
+ return true;
+}
+
+static void mxc_hdmi_phy_init(struct mxc_hdmi *hdmi)
+{
+ int i;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* HDMI Phy spec says to do the phy initialization sequence twice */
+ for (i = 0 ; i < 2 ; i++) {
+ mxc_hdmi_phy_sel_data_en_pol(1);
+ mxc_hdmi_phy_sel_interface_control(0);
+ mxc_hdmi_phy_enable_tmds(0);
+ mxc_hdmi_phy_enable_power(0);
+
+ /* TODO: Enable CSC */
+ hdmi_phy_configure(hdmi, 0, 8, false);
+ }
+
+ hdmi->phy_enabled = true;
+}
+
+static void hdmi_tx_hdcp_config(struct mxc_hdmi *hdmi)
+{
+ u8 de, val;
+
+ if (hdmi->hdmi_data.video_mode.mDataEnablePolarity)
+ de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH;
+ else
+ de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW;
+
+ /* disable rx detect */
+ val = hdmi_readb(HDMI_A_HDCPCFG0);
+ val &= HDMI_A_HDCPCFG0_RXDETECT_MASK;
+ val |= HDMI_A_HDCPCFG0_RXDETECT_DISABLE;
+ hdmi_writeb(val, HDMI_A_HDCPCFG0);
+
+ val = hdmi_readb(HDMI_A_VIDPOLCFG);
+ val &= HDMI_A_VIDPOLCFG_DATAENPOL_MASK;
+ val |= de;
+ hdmi_writeb(val, HDMI_A_VIDPOLCFG);
+
+ val = hdmi_readb(HDMI_A_HDCPCFG1);
+ val &= HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK;
+ val |= HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE;
+ hdmi_writeb(val, HDMI_A_HDCPCFG1);
+}
+
+static void hdmi_config_AVI(struct mxc_hdmi *hdmi)
+{
+ u8 val;
+ u8 pix_fmt;
+ u8 act_ratio, coded_ratio, colorimetry, ext_colorimetry;
+ struct fb_videomode mode;
+ const struct fb_videomode *edid_mode;
+ bool aspect_16_9;
+
+ dev_dbg(&hdmi->pdev->dev, "set up AVI frame\n");
+
+ fb_var_to_videomode(&mode, &hdmi->fbi->var);
+ /* Use mode from list extracted from EDID to get aspect ratio */
+ if (!list_empty(&hdmi->fbi->modelist)) {
+ edid_mode = fb_find_nearest_mode(&mode, &hdmi->fbi->modelist);
+ if (edid_mode->vmode & FB_VMODE_ASPECT_16_9)
+ aspect_16_9 = true;
+ else
+ aspect_16_9 = false;
+ } else
+ aspect_16_9 = false;
+
+ /********************************************
+ * AVI Data Byte 1
+ ********************************************/
+ if (hdmi->edid_cfg.cea_ycbcr444)
+ pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR444;
+ else if (hdmi->edid_cfg.cea_ycbcr422)
+ pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR422;
+ else
+ pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_RGB;
+
+ /*
+ * Active format identification data is present in the AVI InfoFrame.
+ * No scan info, no bar data
+ */
+ val = pix_fmt |
+ HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT |
+ HDMI_FC_AVICONF0_BAR_DATA_NO_DATA |
+ HDMI_FC_AVICONF0_SCAN_INFO_NODATA;
+
+ hdmi_writeb(val, HDMI_FC_AVICONF0);
+
+ /********************************************
+ * AVI Data Byte 2
+ ********************************************/
+
+ /* Set the Aspect Ratio */
+ if (aspect_16_9) {
+ act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_16_9;
+ coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9;
+ } else {
+ act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_4_3;
+ coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3;
+ }
+
+ /* Set up colorimetry */
+ if (hdmi->hdmi_data.enc_out_format == XVYCC444) {
+ colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO;
+ if (hdmi->hdmi_data.colorimetry == eITU601)
+ ext_colorimetry =
+ HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
+ else /* hdmi->hdmi_data.colorimetry == eITU709 */
+ ext_colorimetry =
+ HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709;
+ } else if (hdmi->hdmi_data.enc_out_format != RGB) {
+ if (hdmi->hdmi_data.colorimetry == eITU601)
+ colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_SMPTE;
+ else /* hdmi->hdmi_data.colorimetry == eITU709 */
+ colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_ITUR;
+ ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
+ } else { /* Carries no data */
+ colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA;
+ ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
+ }
+
+ val = colorimetry | coded_ratio | act_ratio;
+ hdmi_writeb(val, HDMI_FC_AVICONF1);
+
+ /********************************************
+ * AVI Data Byte 3
+ ********************************************/
+
+ val = HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA | ext_colorimetry |
+ HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT |
+ HDMI_FC_AVICONF2_SCALING_NONE;
+ hdmi_writeb(val, HDMI_FC_AVICONF2);
+
+ /********************************************
+ * AVI Data Byte 4
+ ********************************************/
+ hdmi_writeb(hdmi->vic, HDMI_FC_AVIVID);
+
+ /********************************************
+ * AVI Data Byte 5
+ ********************************************/
+
+ /* Set up input and output pixel repetition */
+ val = (((hdmi->hdmi_data.video_mode.mPixelRepetitionInput + 1) <<
+ HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET) &
+ HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK) |
+ ((hdmi->hdmi_data.video_mode.mPixelRepetitionOutput <<
+ HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET) &
+ HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK);
+ hdmi_writeb(val, HDMI_FC_PRCONF);
+
+ /* IT Content and quantization range = don't care */
+ val = HDMI_FC_AVICONF2_IT_CONTENT_TYPE_GRAPHICS |
+ HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED;
+ hdmi_writeb(val, HDMI_FC_AVICONF3);
+
+ /********************************************
+ * AVI Data Bytes 6-13
+ ********************************************/
+ hdmi_writeb(0, HDMI_FC_AVIETB0);
+ hdmi_writeb(0, HDMI_FC_AVIETB1);
+ hdmi_writeb(0, HDMI_FC_AVISBB0);
+ hdmi_writeb(0, HDMI_FC_AVISBB1);
+ hdmi_writeb(0, HDMI_FC_AVIELB0);
+ hdmi_writeb(0, HDMI_FC_AVIELB1);
+ hdmi_writeb(0, HDMI_FC_AVISRB0);
+ hdmi_writeb(0, HDMI_FC_AVISRB1);
+}
+
+/*!
+ * this submodule is responsible for the video/audio data composition.
+ */
+static void hdmi_av_composer(struct mxc_hdmi *hdmi)
+{
+ u8 inv_val;
+ struct fb_info *fbi = hdmi->fbi;
+ struct fb_videomode fb_mode;
+ struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
+ int hblank, vblank;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ fb_var_to_videomode(&fb_mode, &fbi->var);
+
+ vmode->mHSyncPolarity = ((fb_mode.sync & FB_SYNC_HOR_HIGH_ACT) != 0);
+ vmode->mVSyncPolarity = ((fb_mode.sync & FB_SYNC_VERT_HIGH_ACT) != 0);
+ vmode->mInterlaced = ((fb_mode.vmode & FB_VMODE_INTERLACED) != 0);
+ vmode->mPixelClock = (fb_mode.xres + fb_mode.left_margin +
+ fb_mode.right_margin + fb_mode.hsync_len) * (fb_mode.yres +
+ fb_mode.upper_margin + fb_mode.lower_margin +
+ fb_mode.vsync_len) * fb_mode.refresh;
+
+ dev_dbg(&hdmi->pdev->dev, "final pixclk = %d\n", vmode->mPixelClock);
+
+ /* Set up HDMI_FC_INVIDCONF */
+ inv_val = (hdmi->hdmi_data.hdcp_enable ?
+ HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
+ HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
+
+ inv_val |= (vmode->mVSyncPolarity ?
+ HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW);
+
+ inv_val |= (vmode->mHSyncPolarity ?
+ HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW);
+
+ inv_val |= (vmode->mDataEnablePolarity ?
+ HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW);
+
+ if (hdmi->vic == 39)
+ inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH;
+ else
+ inv_val |= (vmode->mInterlaced ?
+ HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW);
+
+ inv_val |= (vmode->mInterlaced ?
+ HDMI_FC_INVIDCONF_IN_I_P_INTERLACED :
+ HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE);
+
+ inv_val |= (vmode->mDVI ?
+ HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE :
+ HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE);
+
+ hdmi_writeb(inv_val, HDMI_FC_INVIDCONF);
+
+ /* Set up horizontal active pixel region width */
+ hdmi_writeb(fb_mode.xres >> 8, HDMI_FC_INHACTV1);
+ hdmi_writeb(fb_mode.xres, HDMI_FC_INHACTV0);
+
+ /* Set up vertical blanking pixel region width */
+ hdmi_writeb(fb_mode.yres >> 8, HDMI_FC_INVACTV1);
+ hdmi_writeb(fb_mode.yres, HDMI_FC_INVACTV0);
+
+ /* Set up horizontal blanking pixel region width */
+ hblank = fb_mode.left_margin + fb_mode.right_margin +
+ fb_mode.hsync_len;
+ hdmi_writeb(hblank >> 8, HDMI_FC_INHBLANK1);
+ hdmi_writeb(hblank, HDMI_FC_INHBLANK0);
+
+ /* Set up vertical blanking pixel region width */
+ vblank = fb_mode.upper_margin + fb_mode.lower_margin +
+ fb_mode.vsync_len;
+ hdmi_writeb(vblank, HDMI_FC_INVBLANK);
+
+ /* Set up HSYNC active edge delay width (in pixel clks) */
+ hdmi_writeb(fb_mode.right_margin >> 8, HDMI_FC_HSYNCINDELAY1);
+ hdmi_writeb(fb_mode.right_margin, HDMI_FC_HSYNCINDELAY0);
+
+ /* Set up VSYNC active edge delay (in pixel clks) */
+ hdmi_writeb(fb_mode.lower_margin, HDMI_FC_VSYNCINDELAY);
+
+ /* Set up HSYNC active pulse width (in pixel clks) */
+ hdmi_writeb(fb_mode.hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1);
+ hdmi_writeb(fb_mode.hsync_len, HDMI_FC_HSYNCINWIDTH0);
+
+ /* Set up VSYNC active edge delay (in pixel clks) */
+ hdmi_writeb(fb_mode.vsync_len, HDMI_FC_VSYNCINWIDTH);
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
+}
+
+static int mxc_hdmi_read_edid(struct mxc_hdmi *hdmi)
+{
+ int ret;
+ u8 edid_old[HDMI_EDID_LEN];
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* save old edid */
+ memcpy(edid_old, hdmi->edid, HDMI_EDID_LEN);
+
+ ret = mxc_edid_read(hdmi_i2c->adapter, hdmi_i2c->addr, hdmi->edid,
+ &hdmi->edid_cfg, hdmi->fbi);
+
+ if (ret < 0)
+ return HDMI_EDID_FAIL;
+
+ if (!memcmp(edid_old, hdmi->edid, HDMI_EDID_LEN)) {
+ dev_info(&hdmi->pdev->dev, "same edid\n");
+ return HDMI_EDID_SAME;
+ }
+
+ if (hdmi->fbi->monspecs.modedb_len == 0) {
+ dev_info(&hdmi->pdev->dev, "No modes read from edid\n");
+ return HDMI_EDID_NO_MODES;
+ }
+
+ return HDMI_EDID_SUCCESS;
+}
+
+static void mxc_hdmi_enable_pins(struct mxc_hdmi *hdmi)
+{
+ struct fsl_mxc_hdmi_platform_data *plat = hdmi->pdev->dev.platform_data;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* Enable pins to HDMI */
+ if (plat->enable_pins)
+ plat->enable_pins();
+}
+
+static void mxc_hdmi_disable_pins(struct mxc_hdmi *hdmi)
+{
+ struct fsl_mxc_hdmi_platform_data *plat = hdmi->pdev->dev.platform_data;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* Disable pins to HDMI */
+ if (plat->disable_pins)
+ plat->disable_pins();
+}
+
+static void mxc_hdmi_phy_disable(struct mxc_hdmi *hdmi)
+{
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ if (!hdmi->phy_enabled)
+ return;
+
+ mxc_hdmi_phy_enable_tmds(0);
+ mxc_hdmi_phy_enable_power(0);
+
+ hdmi->phy_enabled = false;
+ dev_dbg(&hdmi->pdev->dev, "%s - exit\n", __func__);
+}
+
+/* HDMI Initialization Step B.4 */
+static void mxc_hdmi_enable_video_path(struct mxc_hdmi *hdmi)
+{
+ u8 clkdis;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* control period minimum duration */
+ hdmi_writeb(12, HDMI_FC_CTRLDUR);
+ hdmi_writeb(32, HDMI_FC_EXCTRLDUR);
+ hdmi_writeb(1, HDMI_FC_EXCTRLSPAC);
+
+ /* Set to fill TMDS data channels */
+ hdmi_writeb(0x0B, HDMI_FC_CH0PREAM);
+ hdmi_writeb(0x16, HDMI_FC_CH1PREAM);
+ hdmi_writeb(0x21, HDMI_FC_CH2PREAM);
+
+ /* Enable pixel clock and tmds data path */
+ clkdis = 0x7F;
+ clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+
+ clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+}
+
+static void hdmi_enable_audio_clk(struct mxc_hdmi *hdmi)
+{
+ u8 clkdis;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ clkdis = hdmi_readb(HDMI_MC_CLKDIS);
+ clkdis &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+}
+
+/* Workaround to clear the overflow condition */
+static void mxc_hdmi_clear_overflow(void)
+{
+ int count;
+ u8 val;
+
+ val = hdmi_readb(HDMI_FC_INVIDCONF);
+
+ for (count = 0 ; count < 5 ; count++)
+ hdmi_writeb(val, HDMI_FC_INVIDCONF);
+
+ /* TMDS software reset */
+ hdmi_writeb((u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ);
+}
+
+static void hdmi_enable_overflow_interrupts(void)
+{
+ pr_debug("%s\n", __func__);
+ hdmi_writeb(0, HDMI_FC_MASK2);
+ hdmi_writeb(0, HDMI_IH_MUTE_FC_STAT2);
+}
+
+static void hdmi_disable_overflow_interrupts(void)
+{
+ pr_debug("%s\n", __func__);
+ hdmi_writeb(HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK,
+ HDMI_IH_MUTE_FC_STAT2);
+}
+
+static void mxc_hdmi_notify_fb(struct mxc_hdmi *hdmi)
+{
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* Don't notify if we aren't registered yet */
+ WARN_ON(!hdmi->fb_reg);
+
+ /* disable the phy before ipu changes mode */
+ mxc_hdmi_phy_disable(hdmi);
+
+ /*
+ * Note that fb_set_var will block. During this time,
+ * FB_EVENT_MODE_CHANGE callback will happen.
+ * So by the end of this function, mxc_hdmi_setup()
+ * will be done.
+ */
+ hdmi->fbi->var.activate |= FB_ACTIVATE_FORCE;
+ console_lock();
+ hdmi->fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(hdmi->fbi, &hdmi->fbi->var);
+ hdmi->fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
+}
+
+static void mxc_hdmi_set_mode_to_previous(struct mxc_hdmi *hdmi)
+{
+ const struct fb_videomode *mode;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ mode = fb_find_nearest_mode(&hdmi->previous_non_vga_mode,
+ &hdmi->fbi->modelist);
+ if (mode) {
+ fb_videomode_to_var(&hdmi->fbi->var, mode);
+ mxc_hdmi_notify_fb(hdmi);
+ } else
+ pr_err("%s: could not find mode in modelist\n", __func__);
+}
+
+static void mxc_hdmi_edid_rebuild_modelist(struct mxc_hdmi *hdmi)
+{
+ int i;
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ console_lock();
+
+ fb_destroy_modelist(&hdmi->fbi->modelist);
+ fb_add_videomode(&vga_mode, &hdmi->fbi->modelist);
+
+ for (i = 0; i < hdmi->fbi->monspecs.modedb_len; i++) {
+ /*
+ * We might check here if mode is supported by HDMI.
+ * We do not currently support interlaced modes
+ */
+ if (!(hdmi->fbi->monspecs.modedb[i].vmode &
+ FB_VMODE_INTERLACED)) {
+ dev_dbg(&hdmi->pdev->dev, "Added mode %d:", i);
+ dev_dbg(&hdmi->pdev->dev,
+ "xres = %d, yres = %d, freq = %d\n",
+ hdmi->fbi->monspecs.modedb[i].xres,
+ hdmi->fbi->monspecs.modedb[i].yres,
+ hdmi->fbi->monspecs.modedb[i].refresh);
+
+ fb_add_videomode(&hdmi->fbi->monspecs.modedb[i],
+ &hdmi->fbi->modelist);
+ }
+ }
+
+ console_unlock();
+
+ /* Set the default mode only once. */
+ if (!hdmi->dft_mode_set) {
+ dev_dbg(&hdmi->pdev->dev, "%s: setting to default=%s bpp=%d\n",
+ __func__, hdmi->dft_mode_str, hdmi->default_bpp);
+
+ fb_find_mode(&hdmi->fbi->var, hdmi->fbi,
+ hdmi->dft_mode_str, NULL, 0, NULL,
+ hdmi->default_bpp);
+
+ hdmi->dft_mode_set = true;
+ } else
+ mxc_hdmi_set_mode_to_previous(hdmi);
+
+ fb_var_to_videomode(&m, &hdmi->fbi->var);
+ dump_fb_videomode(&m);
+ mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
+ if (mode) {
+ fb_videomode_to_var(&hdmi->fbi->var, mode);
+ dump_fb_videomode((struct fb_videomode *)mode);
+ mxc_hdmi_notify_fb(hdmi);
+ } else
+ pr_err("%s: could not find mode in modelist\n", __func__);
+}
+
+static void mxc_hdmi_default_modelist(struct mxc_hdmi *hdmi)
+{
+ u32 i;
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* If not EDID data read, set up default modelist */
+ dev_info(&hdmi->pdev->dev, "No modes read from edid\n");
+ dev_info(&hdmi->pdev->dev, "create default modelist\n");
+
+ /* Set the default mode only once. */
+ if (!hdmi->dft_mode_set) {
+ dev_dbg(&hdmi->pdev->dev, "%s: setting to default=%s bpp=%d\n",
+ __func__, hdmi->dft_mode_str, hdmi->default_bpp);
+
+ fb_find_mode(&hdmi->fbi->var, hdmi->fbi,
+ hdmi->dft_mode_str, NULL, 0, NULL,
+ hdmi->default_bpp);
+
+ hdmi->dft_mode_set = true;
+ } else {
+ fb_videomode_to_var(&hdmi->fbi->var, &hdmi->previous_non_vga_mode);
+ }
+
+ console_lock();
+
+ fb_destroy_modelist(&hdmi->fbi->modelist);
+
+ /*Add all no interlaced CEA mode to default modelist */
+ for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) {
+ mode = &mxc_cea_mode[i];
+ if (!(mode->vmode & FB_VMODE_INTERLACED) && (mode->xres != 0))
+ fb_add_videomode(mode, &hdmi->fbi->modelist);
+ }
+
+ /*Add XGA and SXGA to default modelist */
+ fb_add_videomode(&xga_mode, &hdmi->fbi->modelist);
+ fb_add_videomode(&sxga_mode, &hdmi->fbi->modelist);
+
+ console_unlock();
+
+ fb_var_to_videomode(&m, &hdmi->fbi->var);
+ dump_fb_videomode(&m);
+ mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
+ if (mode) {
+ fb_videomode_to_var(&hdmi->fbi->var, mode);
+ dump_fb_videomode((struct fb_videomode *)mode);
+ dev_warn(&hdmi->pdev->dev,
+ "Default modelist,the video mode may not support by monitor.\n");
+ mxc_hdmi_notify_fb(hdmi);
+ } else
+ pr_err("%s: could not find mode in default modelist\n", __func__);
+}
+
+static void mxc_hdmi_set_mode_to_vga_dvi(struct mxc_hdmi *hdmi)
+{
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ hdmi_disable_overflow_interrupts();
+
+ fb_videomode_to_var(&hdmi->fbi->var, &vga_mode);
+
+ hdmi->requesting_vga_for_initialization = true;
+ mxc_hdmi_notify_fb(hdmi);
+ hdmi->requesting_vga_for_initialization = false;
+}
+
+static void mxc_hdmi_cable_connected(struct mxc_hdmi *hdmi)
+{
+ int edid_status;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ hdmi->cable_plugin = true;
+
+ /* HDMI Initialization Step B */
+ mxc_hdmi_set_mode_to_vga_dvi(hdmi);
+
+ /* HDMI Initialization Step C */
+ edid_status = mxc_hdmi_read_edid(hdmi);
+
+ /* HDMI Initialization Steps D, E, F */
+ switch (edid_status) {
+ case HDMI_EDID_SUCCESS:
+ mxc_hdmi_edid_rebuild_modelist(hdmi);
+ break;
+
+ case HDMI_EDID_SAME:
+ mxc_hdmi_set_mode_to_previous(hdmi);
+ break;
+
+ case HDMI_EDID_NO_MODES:
+ case HDMI_EDID_FAIL:
+ default:
+ mxc_hdmi_default_modelist(hdmi);
+ break;
+ }
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
+}
+
+static void mxc_hdmi_cable_disconnected(struct mxc_hdmi *hdmi)
+{
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ hdmi_disable_overflow_interrupts();
+
+ hdmi->cable_plugin = false;
+}
+
+static void hotplug_worker(struct work_struct *work)
+{
+ struct delayed_work *delay_work = to_delayed_work(work);
+ struct mxc_hdmi *hdmi =
+ container_of(delay_work, struct mxc_hdmi, hotplug_work);
+ u32 phy_int_stat, phy_int_pol, phy_int_mask;
+ u8 val;
+ bool hdmi_disable = false;
+ int irq = platform_get_irq(hdmi->pdev, 0);
+ unsigned long flags;
+ char event_string[16];
+ char *envp[] = { event_string, NULL };
+
+ if (!hdmi->irq_enabled) {
+ /* Enable clock long enough to do a few register accesses */
+ clk_enable(hdmi->hdmi_iahb_clk);
+
+ /* Capture status - used in hotplug_worker ISR */
+ phy_int_stat = hdmi_readb(HDMI_IH_PHY_STAT0);
+ if ((phy_int_stat & HDMI_IH_PHY_STAT0_HPD) == 0) {
+ clk_disable(hdmi->hdmi_iahb_clk);
+ return; /* No interrupts to handle */
+ }
+
+ dev_dbg(&hdmi->pdev->dev, "\nHotplug interrupt received\n");
+
+ /* Unmask interrupts until handled */
+ val = hdmi_readb(HDMI_PHY_MASK0);
+ val |= HDMI_PHY_HPD;
+ hdmi_writeb(val, HDMI_PHY_MASK0);
+
+ /* Clear Hotplug interrupts */
+ hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+
+ phy_int_pol = hdmi_readb(HDMI_PHY_POL0);
+
+ clk_disable(hdmi->hdmi_iahb_clk);
+ } else {
+ /* Use saved interrupt status, since it was cleared in IST */
+ phy_int_stat = hdmi->latest_intr_stat;
+ phy_int_pol = hdmi_readb(HDMI_PHY_POL0);
+ }
+
+ /* Re-enable HDMI irq now that our interrupts have been masked off */
+ hdmi_irq_enable(irq);
+
+ /* check cable status */
+ if (phy_int_stat & HDMI_IH_PHY_STAT0_HPD) {
+ /* cable connection changes */
+ if (phy_int_pol & HDMI_PHY_HPD) {
+ /*
+ * Plugin event = assume that iahb clock was disabled.
+ */
+ dev_dbg(&hdmi->pdev->dev, "EVENT=plugin\n");
+
+ clk_enable(hdmi->hdmi_iahb_clk);
+ mxc_hdmi_cable_connected(hdmi);
+
+ /* Make HPD intr active low to capture unplug event */
+ val = hdmi_readb(HDMI_PHY_POL0);
+ val &= ~HDMI_PHY_HPD;
+ hdmi_writeb(val, HDMI_PHY_POL0);
+
+ sprintf(event_string, "EVENT=plugin");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+
+ } else if (!(phy_int_pol & HDMI_PHY_HPD)) {
+ /*
+ * Plugout event = assume that iahb clock was enabled.
+ */
+ dev_dbg(&hdmi->pdev->dev, "EVENT=plugout\n");
+ mxc_hdmi_cable_disconnected(hdmi);
+ hdmi_disable = true;
+
+ /* Make HPD intr active high to capture plugin event */
+ val = hdmi_readb(HDMI_PHY_POL0);
+ val |= HDMI_PHY_HPD;
+ hdmi_writeb(val, HDMI_PHY_POL0);
+
+ sprintf(event_string, "EVENT=plugout");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+
+ } else
+ dev_dbg(&hdmi->pdev->dev, "EVENT=none?\n");
+ }
+
+ /* Lock here to ensure full powerdown sequence
+ * completed before next interrupt processed */
+ spin_lock_irqsave(&hdmi->irq_lock, flags);
+
+ /* Re-enable HPD interrupts */
+ phy_int_mask = hdmi_readb(HDMI_PHY_MASK0);
+ phy_int_mask &= ~HDMI_PHY_HPD;
+ hdmi_writeb(phy_int_mask, HDMI_PHY_MASK0);
+
+ if (hdmi_readb(HDMI_FC_INT2) & HDMI_FC_INT2_OVERFLOW_MASK)
+ mxc_hdmi_clear_overflow();
+
+ /* We keep the iahb clock enabled only if we are plugged in. */
+ if (hdmi_disable) {
+ mxc_hdmi_phy_disable(hdmi);
+ clk_disable(hdmi->hdmi_iahb_clk);
+ }
+
+ spin_unlock_irqrestore(&hdmi->irq_lock, flags);
+}
+
+static irqreturn_t mxc_hdmi_hotplug(int irq, void *data)
+{
+ struct mxc_hdmi *hdmi = data;
+ unsigned int ret;
+ u8 val, intr_stat;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hdmi->irq_lock, flags);
+
+ /*
+ * We have to disable the irq, rather than just masking
+ * off the HDMI interrupts using HDMI registers. This is
+ * because the HDMI iahb clock is required to be on to
+ * access the HDMI registers, and we cannot enable it
+ * in an IST. This IRQ will be re-enabled in the
+ * interrupt handler workqueue function.
+ */
+ ret = hdmi_irq_disable(irq);
+ if (ret == IRQ_DISABLE_FAIL) {
+ if (hdmi_readb(HDMI_FC_INT2) & HDMI_FC_INT2_OVERFLOW_MASK) {
+ mxc_hdmi_clear_overflow();
+
+ /* clear irq status */
+ hdmi_writeb(HDMI_IH_FC_STAT2_OVERFLOW_MASK,
+ HDMI_IH_FC_STAT2);
+ }
+
+ /*
+ * We could not disable the irq. Probably the audio driver
+ * has enabled it. That also means that iahb clk is enabled.
+ */
+ /* Capture status - used in hotplug_worker ISR */
+ intr_stat = hdmi_readb(HDMI_IH_PHY_STAT0);
+ if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) == 0) {
+ hdmi_irq_enable(irq);
+ spin_unlock_irqrestore(&hdmi->irq_lock, flags);
+ return IRQ_HANDLED;
+ }
+ dev_dbg(&hdmi->pdev->dev, "Hotplug interrupt received\n");
+ hdmi->latest_intr_stat = intr_stat;
+
+ /* Unmask interrupts until handled */
+ val = hdmi_readb(HDMI_PHY_MASK0);
+ val |= HDMI_PHY_HPD;
+ hdmi_writeb(val, HDMI_PHY_MASK0);
+
+ /* Clear Hotplug interrupts */
+ hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+
+ hdmi->irq_enabled = true;
+ } else
+ hdmi->irq_enabled = false;
+
+ schedule_delayed_work(&(hdmi->hotplug_work), msecs_to_jiffies(20));
+
+ spin_unlock_irqrestore(&hdmi->irq_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static void mxc_hdmi_setup(struct mxc_hdmi *hdmi)
+{
+ struct fb_videomode m;
+ const struct fb_videomode *edid_mode;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ fb_var_to_videomode(&m, &hdmi->fbi->var);
+ dump_fb_videomode(&m);
+
+ /* Exit the setup if we are already set to this video mode */
+ if (fb_mode_is_equal(&hdmi->previous_mode, &m)) {
+ dev_dbg(&hdmi->pdev->dev,
+ "%s video mode did not change.\n", __func__);
+ mxc_hdmi_phy_init(hdmi);
+ return;
+ }
+ dev_dbg(&hdmi->pdev->dev, "%s - video mode changed\n", __func__);
+
+ hdmi_disable_overflow_interrupts();
+
+ /* Save mode as 'previous_mode' so that we can know if mode changed. */
+ memcpy(&hdmi->previous_mode, &m, sizeof(struct fb_videomode));
+
+ hdmi->vic = 0;
+ if (!hdmi->requesting_vga_for_initialization) {
+ /* Save mode if this isn't the result of requesting
+ * vga default. */
+ memcpy(&hdmi->previous_non_vga_mode, &m,
+ sizeof(struct fb_videomode));
+
+ if (!list_empty(&hdmi->fbi->modelist)) {
+ edid_mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
+ pr_debug("edid mode ");
+ dump_fb_videomode((struct fb_videomode *)edid_mode);
+ hdmi->vic = mxc_edid_mode_to_vic(edid_mode);
+ }
+ }
+
+ if (hdmi->vic == 0) {
+ dev_dbg(&hdmi->pdev->dev, "Non-CEA mode used in HDMI\n");
+ hdmi->hdmi_data.video_mode.mDVI = true;
+ } else {
+ dev_dbg(&hdmi->pdev->dev, "CEA mode used vic=%d\n", hdmi->vic);
+ hdmi->hdmi_data.video_mode.mDVI = false;
+ }
+
+ if ((hdmi->vic == 6) || (hdmi->vic == 7) ||
+ (hdmi->vic == 21) || (hdmi->vic == 22) ||
+ (hdmi->vic == 2) || (hdmi->vic == 3) ||
+ (hdmi->vic == 17) || (hdmi->vic == 18))
+ hdmi->hdmi_data.colorimetry = eITU601;
+ else
+ hdmi->hdmi_data.colorimetry = eITU709;
+
+ if ((hdmi->vic == 10) || (hdmi->vic == 11) ||
+ (hdmi->vic == 12) || (hdmi->vic == 13) ||
+ (hdmi->vic == 14) || (hdmi->vic == 15) ||
+ (hdmi->vic == 25) || (hdmi->vic == 26) ||
+ (hdmi->vic == 27) || (hdmi->vic == 28) ||
+ (hdmi->vic == 29) || (hdmi->vic == 30) ||
+ (hdmi->vic == 35) || (hdmi->vic == 36) ||
+ (hdmi->vic == 37) || (hdmi->vic == 38))
+ hdmi->hdmi_data.video_mode.mPixelRepetitionOutput = 1;
+ else
+ hdmi->hdmi_data.video_mode.mPixelRepetitionOutput = 0;
+
+ hdmi->hdmi_data.video_mode.mPixelRepetitionInput = 0;
+
+ /* TODO: Get input format from IPU (via FB driver iface) */
+ hdmi->hdmi_data.enc_in_format = RGB;
+
+ hdmi->hdmi_data.enc_out_format = RGB;
+ if (hdmi->edid_cfg.hdmi_cap) {
+ if (hdmi->edid_cfg.cea_ycbcr444)
+ hdmi->hdmi_data.enc_out_format = YCBCR444;
+ else if (hdmi->edid_cfg.cea_ycbcr422)
+ hdmi->hdmi_data.enc_out_format = YCBCR422_8BITS;
+ }
+
+ hdmi->hdmi_data.enc_color_depth = 8;
+ hdmi->hdmi_data.pix_repet_factor = 0;
+ hdmi->hdmi_data.hdcp_enable = 0;
+ hdmi->hdmi_data.video_mode.mDataEnablePolarity = true;
+
+ /* HDMI Initialization Step B.1 */
+ hdmi_av_composer(hdmi);
+
+ /* HDMI Initializateion Step B.2 */
+ mxc_hdmi_phy_init(hdmi);
+
+ /* HDMI Initialization Step B.3 */
+ mxc_hdmi_enable_video_path(hdmi);
+
+ /* not for DVI mode */
+ if (hdmi->hdmi_data.video_mode.mDVI)
+ dev_dbg(&hdmi->pdev->dev, "%s DVI mode\n", __func__);
+ else {
+ dev_dbg(&hdmi->pdev->dev, "%s CEA mode\n", __func__);
+
+ /* HDMI Initialization Step E - Configure audio */
+ hdmi_clk_regenerator_update_pixel_clock();
+ hdmi_enable_audio_clk(hdmi);
+
+ /* HDMI Initialization Step F - Configure AVI InfoFrame */
+ hdmi_config_AVI(hdmi);
+ }
+
+ hdmi_video_packetize(hdmi);
+ hdmi_video_csc(hdmi);
+ hdmi_video_sample(hdmi);
+ hdmi_tx_hdcp_config(hdmi);
+
+ mxc_hdmi_clear_overflow();
+ if (hdmi->cable_plugin && !hdmi->hdmi_data.video_mode.mDVI)
+ hdmi_enable_overflow_interrupts();
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n\n", __func__);
+}
+
+/* Wait until we are registered to enable interrupts */
+static void mxc_hdmi_fb_registered(struct mxc_hdmi *hdmi)
+{
+ unsigned long flags;
+
+ if (hdmi->fb_reg)
+ return;
+
+ clk_enable(hdmi->hdmi_iahb_clk);
+
+ spin_lock_irqsave(&hdmi->irq_lock, flags);
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ hdmi_writeb(HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
+ HDMI_PHY_I2CM_INT_ADDR);
+
+ hdmi_writeb(HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
+ HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
+ HDMI_PHY_I2CM_CTLINT_ADDR);
+
+ /* enable cable hot plug irq */
+ hdmi_writeb((u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0);
+
+ /* Clear Hotplug interrupts */
+ hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+
+ /* Unmute interrupts */
+ hdmi_writeb(~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
+
+ hdmi->fb_reg = true;
+
+ spin_unlock_irqrestore(&hdmi->irq_lock, flags);
+
+ clk_disable(hdmi->hdmi_iahb_clk);
+}
+
+static int mxc_hdmi_fb_event(struct notifier_block *nb,
+ unsigned long val, void *v)
+{
+ struct fb_event *event = v;
+ struct mxc_hdmi *hdmi = container_of(nb, struct mxc_hdmi, nb);
+
+ if (strcmp(event->info->fix.id, hdmi->fbi->fix.id))
+ return 0;
+
+ switch (val) {
+ case FB_EVENT_FB_REGISTERED:
+ dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_FB_REGISTERED\n");
+ mxc_hdmi_fb_registered(hdmi);
+ break;
+
+ case FB_EVENT_FB_UNREGISTERED:
+ dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_FB_UNREGISTERED\n");
+ hdmi->fb_reg = false;
+ break;
+
+ case FB_EVENT_MODE_CHANGE:
+ dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_MODE_CHANGE\n");
+ if (hdmi->fb_reg)
+ mxc_hdmi_setup(hdmi);
+ break;
+
+ case FB_EVENT_BLANK:
+ if (*((int *)event->data) == FB_BLANK_UNBLANK) {
+ dev_dbg(&hdmi->pdev->dev,
+ "event=FB_EVENT_BLANK - UNBLANK\n");
+ mxc_hdmi_enable_pins(hdmi);
+ } else {
+ dev_dbg(&hdmi->pdev->dev,
+ "event=FB_EVENT_BLANK - BLANK\n");
+ mxc_hdmi_disable_pins(hdmi);
+ }
+ break;
+ }
+ return 0;
+}
+
+/* HDMI Initialization Step A */
+static int mxc_hdmi_disp_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
+{
+ int ret = 0;
+ struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
+ struct fsl_mxc_hdmi_platform_data *plat = hdmi->pdev->dev.platform_data;
+ int irq = platform_get_irq(hdmi->pdev, 0);
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ if (!plat || irq < 0)
+ return -ENODEV;
+
+ hdmi->dft_mode_set = false;
+
+ setting->dev_id = mxc_hdmi_ipu_id;
+ setting->disp_id = mxc_hdmi_disp_id;
+ setting->if_fmt = IPU_PIX_FMT_RGB24;
+
+ hdmi->dft_mode_str = setting->dft_mode_str;
+ hdmi->default_bpp = setting->default_bpp;
+ dev_dbg(&hdmi->pdev->dev, "%s - default mode %s bpp=%d\n",
+ __func__, hdmi->dft_mode_str, hdmi->default_bpp);
+
+ hdmi->fbi = setting->fbi;
+
+ /* Claim HDMI pins */
+ if (plat->get_pins)
+ if (!plat->get_pins()) {
+ ret = -EACCES;
+ goto egetpins;
+ }
+
+ /* Initialize HDMI */
+ if (plat->init)
+ plat->init(mxc_hdmi_ipu_id, mxc_hdmi_disp_id);
+
+ hdmi->hdmi_isfr_clk = clk_get(&hdmi->pdev->dev, "hdmi_isfr_clk");
+ if (IS_ERR(hdmi->hdmi_isfr_clk)) {
+ ret = PTR_ERR(hdmi->hdmi_isfr_clk);
+ dev_err(&hdmi->pdev->dev,
+ "Unable to get HDMI clk: %d\n", ret);
+ goto egetclk1;
+ }
+
+ ret = clk_enable(hdmi->hdmi_isfr_clk);
+ if (ret < 0) {
+ dev_err(&hdmi->pdev->dev,
+ "Cannot enable HDMI isfr clock: %d\n", ret);
+ goto erate1;
+ }
+
+ hdmi->hdmi_iahb_clk = clk_get(&hdmi->pdev->dev, "hdmi_iahb_clk");
+ if (IS_ERR(hdmi->hdmi_iahb_clk)) {
+ ret = PTR_ERR(hdmi->hdmi_iahb_clk);
+ dev_err(&hdmi->pdev->dev,
+ "Unable to get HDMI clk: %d\n", ret);
+ goto egetclk2;
+ }
+
+ ret = clk_enable(hdmi->hdmi_iahb_clk);
+ if (ret < 0) {
+ dev_err(&hdmi->pdev->dev,
+ "Cannot enable HDMI iahb clock: %d\n", ret);
+ goto erate2;
+ }
+
+ dev_dbg(&hdmi->pdev->dev, "Enabled HDMI clocks\n");
+
+ /* Product and revision IDs */
+ dev_info(&hdmi->pdev->dev,
+ "Detected HDMI controller 0x%x:0x%x:0x%x:0x%x\n",
+ hdmi_readb(HDMI_DESIGN_ID),
+ hdmi_readb(HDMI_REVISION_ID),
+ hdmi_readb(HDMI_PRODUCT_ID0),
+ hdmi_readb(HDMI_PRODUCT_ID1));
+
+ /* To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator
+ * N and cts values before enabling phy */
+ hdmi_init_clk_regenerator();
+
+ INIT_LIST_HEAD(&hdmi->fbi->modelist);
+
+ spin_lock_init(&hdmi->irq_lock);
+
+ fb_add_videomode(&vga_mode, &hdmi->fbi->modelist);
+ fb_videomode_to_var(&hdmi->fbi->var, &vga_mode);
+
+ INIT_DELAYED_WORK(&hdmi->hotplug_work, hotplug_worker);
+
+ /* Configure registers related to HDMI interrupt
+ * generation before registering IRQ. */
+ hdmi_writeb(HDMI_PHY_HPD, HDMI_PHY_POL0);
+
+ /* Clear Hotplug interrupts */
+ hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+
+ hdmi->nb.notifier_call = mxc_hdmi_fb_event;
+ ret = fb_register_client(&hdmi->nb);
+ if (ret < 0)
+ goto efbclient;
+
+ memset(&hdmi->hdmi_data, 0, sizeof(struct hdmi_data_info));
+
+ /* Disable IAHB clock while waiting for hotplug interrupt.
+ * ISFR clock must remain enabled for hotplug to work. */
+ clk_disable(hdmi->hdmi_iahb_clk);
+
+ /* Initialize IRQ at HDMI core level */
+ hdmi_irq_init();
+
+ ret = request_irq(irq, mxc_hdmi_hotplug, IRQF_SHARED,
+ dev_name(&hdmi->pdev->dev), hdmi);
+ if (ret < 0) {
+ dev_err(&hdmi->pdev->dev,
+ "Unable to request irq: %d\n", ret);
+ goto ereqirq;
+ }
+
+ ret = device_create_file(&hdmi->pdev->dev, &dev_attr_fb_name);
+ if (ret < 0)
+ dev_warn(&hdmi->pdev->dev,
+ "cound not create sys node for fb name\n");
+ ret = device_create_file(&hdmi->pdev->dev, &dev_attr_cable_state);
+ if (ret < 0)
+ dev_warn(&hdmi->pdev->dev,
+ "cound not create sys node for cable state\n");
+ ret = device_create_file(&hdmi->pdev->dev, &dev_attr_edid);
+ if (ret < 0)
+ dev_warn(&hdmi->pdev->dev,
+ "cound not create sys node for edid\n");
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
+
+ return ret;
+
+efbclient:
+ free_irq(irq, hdmi);
+ereqirq:
+ clk_disable(hdmi->hdmi_iahb_clk);
+erate2:
+ clk_put(hdmi->hdmi_iahb_clk);
+egetclk2:
+ clk_disable(hdmi->hdmi_isfr_clk);
+erate1:
+ clk_put(hdmi->hdmi_isfr_clk);
+egetclk1:
+ plat->put_pins();
+egetpins:
+ dev_dbg(&hdmi->pdev->dev, "%s error exit\n", __func__);
+
+ return ret;
+}
+
+static void mxc_hdmi_disp_deinit(struct mxc_dispdrv_handle *disp)
+{
+ struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
+ struct fsl_mxc_hdmi_platform_data *plat = hdmi->pdev->dev.platform_data;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ fb_unregister_client(&hdmi->nb);
+
+ mxc_hdmi_disable_pins(hdmi);
+
+ clk_disable(hdmi->hdmi_isfr_clk);
+ clk_put(hdmi->hdmi_isfr_clk);
+ clk_disable(hdmi->hdmi_iahb_clk);
+ clk_put(hdmi->hdmi_iahb_clk);
+
+ /* Release HDMI pins */
+ if (plat->put_pins)
+ plat->put_pins();
+
+ platform_device_unregister(hdmi->pdev);
+
+ kfree(hdmi);
+}
+
+static struct mxc_dispdrv_driver mxc_hdmi_drv = {
+ .name = DISPDRV_HDMI,
+ .init = mxc_hdmi_disp_init,
+ .deinit = mxc_hdmi_disp_deinit,
+};
+
+static int __devinit mxc_hdmi_probe(struct platform_device *pdev)
+{
+ struct mxc_hdmi *hdmi;
+ int ret = 0;
+
+ /* Check that I2C driver is loaded and available */
+ if (!hdmi_i2c)
+ return -ENODEV;
+
+ hdmi = kzalloc(sizeof(*hdmi), GFP_KERNEL);
+ if (!hdmi) {
+ dev_err(&pdev->dev, "Cannot allocate device data\n");
+ ret = -ENOMEM;
+ goto ealloc;
+ }
+
+ hdmi->pdev = pdev;
+
+ hdmi->core_pdev = platform_device_alloc("mxc_hdmi_core", -1);
+ if (!hdmi->core_pdev) {
+ pr_err("%s failed platform_device_alloc for hdmi core\n",
+ __func__);
+ ret = -ENOMEM;
+ goto ecore;
+ }
+
+ hdmi->disp_mxc_hdmi = mxc_dispdrv_register(&mxc_hdmi_drv);
+ if (IS_ERR(hdmi->disp_mxc_hdmi)) {
+ dev_err(&pdev->dev, "Failed to register dispdrv - 0x%x\n",
+ (int)hdmi->disp_mxc_hdmi);
+ ret = (int)hdmi->disp_mxc_hdmi;
+ goto edispdrv;
+ }
+ mxc_dispdrv_setdata(hdmi->disp_mxc_hdmi, hdmi);
+
+ platform_set_drvdata(pdev, hdmi);
+
+ return 0;
+edispdrv:
+ platform_device_put(hdmi->core_pdev);
+ecore:
+ kfree(hdmi);
+ealloc:
+ return ret;
+}
+
+static int mxc_hdmi_remove(struct platform_device *pdev)
+{
+ struct mxc_hdmi *hdmi = platform_get_drvdata(pdev);
+ int irq = platform_get_irq(pdev, 0);
+
+ fb_unregister_client(&hdmi->nb);
+
+ mxc_dispdrv_puthandle(hdmi->disp_mxc_hdmi);
+ mxc_dispdrv_unregister(hdmi->disp_mxc_hdmi);
+ /* No new work will be scheduled, wait for running ISR */
+ free_irq(irq, hdmi);
+ kfree(hdmi);
+
+ return 0;
+}
+
+static struct platform_driver mxc_hdmi_driver = {
+ .probe = mxc_hdmi_probe,
+ .remove = mxc_hdmi_remove,
+ .driver = {
+ .name = "mxc_hdmi",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mxc_hdmi_init(void)
+{
+ return platform_driver_register(&mxc_hdmi_driver);
+}
+module_init(mxc_hdmi_init);
+
+static void __exit mxc_hdmi_exit(void)
+{
+ platform_driver_unregister(&mxc_hdmi_driver);
+}
+module_exit(mxc_hdmi_exit);
+
+static int __devinit mxc_hdmi_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C))
+ return -ENODEV;
+
+ hdmi_i2c = client;
+
+ return 0;
+}
+
+static int __devexit mxc_hdmi_i2c_remove(struct i2c_client *client)
+{
+ hdmi_i2c = NULL;
+ return 0;
+}
+
+static const struct i2c_device_id mxc_hdmi_i2c_id[] = {
+ { "mxc_hdmi_i2c", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, mxc_hdmi_i2c_id);
+
+static const struct of_device_id mxc_hdmi_ddc_dt_ids[] = {
+ { .compatible = "fsl,imx6q-hdmi-ddc", },
+ { /* sentinel */ }
+};
+
+static struct i2c_driver mxc_hdmi_i2c_driver = {
+ .driver = {
+ .name = "mxc_hdmi_i2c",
+ .of_match_table = mxc_hdmi_ddc_dt_ids,
+ },
+ .probe = mxc_hdmi_i2c_probe,
+ .remove = mxc_hdmi_i2c_remove,
+ .id_table = mxc_hdmi_i2c_id,
+};
+
+static int __init mxc_hdmi_i2c_init(void)
+{
+ return i2c_add_driver(&mxc_hdmi_i2c_driver);
+}
+
+static void __exit mxc_hdmi_i2c_exit(void)
+{
+ i2c_del_driver(&mxc_hdmi_i2c_driver);
+}
+
+module_init(mxc_hdmi_i2c_init);
+module_exit(mxc_hdmi_i2c_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");