diff options
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/Kconfig | 7 | ||||
-rw-r--r-- | drivers/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/backlight/pwm_bl.c | 40 | ||||
-rw-r--r-- | drivers/video/fbmem.c | 11 | ||||
-rw-r--r-- | drivers/video/mxc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/video/mxc/Makefile | 2 | ||||
-rw-r--r-- | drivers/video/mxc/ldb.c | 370 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_dispdrv.c | 94 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_dispdrv.h | 34 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_dvi.c | 381 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_edid.c | 126 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_ipuv3_fb.c | 323 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_lcdif.c | 9 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb_sii902x.c | 17 | ||||
-rw-r--r-- | drivers/video/mxc/tve.c | 50 | ||||
-rw-r--r-- | drivers/video/mxc_hdmi.c | 2363 |
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."); |