diff options
Diffstat (limited to 'drivers/video/mxc')
-rw-r--r-- | drivers/video/mxc/Kconfig | 34 | ||||
-rw-r--r-- | drivers/video/mxc/Makefile | 5 | ||||
-rw-r--r-- | drivers/video/mxc/ldb.c | 646 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_dispdrv.c | 152 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_dispdrv.h | 43 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_edid.c | 451 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_edid.h | 48 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_ipuv3_fb.c | 2072 | ||||
-rw-r--r-- | drivers/video/mxc/mxc_lcdif.c | 144 | ||||
-rw-r--r-- | drivers/video/mxc/mxcfb_sii902x.c | 1307 | ||||
-rw-r--r-- | drivers/video/mxc/tve.c | 1290 |
11 files changed, 6192 insertions, 0 deletions
diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig new file mode 100644 index 00000000000..bb836c3a63f --- /dev/null +++ b/drivers/video/mxc/Kconfig @@ -0,0 +1,34 @@ +config FB_MXC + tristate "MXC Framebuffer support" + depends on FB && (MXC_IPU || ARCH_MX21 || ARCH_MX27 || ARCH_MX25) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + default y + help + This is a framebuffer device for the MXC LCD Controller. + See <http://www.linux-fbdev.org/> for information on framebuffer + devices. + + If you plan to use the LCD display with your MXC system, say + Y here. + +config FB_MXC_SYNC_PANEL + depends on FB_MXC + tristate "Synchronous Panel Framebuffer" + default y + +config FB_MXC_TVOUT_TVE + tristate "MXC TVE TV Out Encoder" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 + +config FB_MXC_SII902X + depends on FB_MXC_SYNC_PANEL + tristate "Si Image SII9022 DVI/HDMI Interface Chip" + +config FB_MXC_LDB + tristate "MXC LDB" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile new file mode 100644 index 00000000000..4c75e1be2d3 --- /dev/null +++ b/drivers/video/mxc/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_FB_MXC_TVOUT_TVE) += tve.o +obj-$(CONFIG_FB_MXC_SII902X) += mxcfb_sii902x.o +obj-$(CONFIG_FB_MXC_LDB) += ldb.o +obj-$(CONFIG_FB_MODE_HELPERS) += mxc_edid.o +obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_dispdrv.o mxc_lcdif.o mxc_ipuv3_fb.o diff --git a/drivers/video/mxc/ldb.c b/drivers/video/mxc/ldb.c new file mode 100644 index 00000000000..180bf7f6f8d --- /dev/null +++ b/drivers/video/mxc/ldb.c @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @file mxc_ldb.c + * + * @brief This file contains the LDB driver device interface and fops + * functions. + */ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <linux/spinlock.h> +#include <linux/fsl_devices.h> +#include <mach/hardware.h> +#include <mach/clock.h> +#include "mxc_dispdrv.h" + +#define DISPDRV_LDB "ldb" + +#define LDB_BGREF_RMODE_MASK 0x00008000 +#define LDB_BGREF_RMODE_INT 0x00008000 +#define LDB_BGREF_RMODE_EXT 0x0 + +#define LDB_DI1_VS_POL_MASK 0x00000400 +#define LDB_DI1_VS_POL_ACT_LOW 0x00000400 +#define LDB_DI1_VS_POL_ACT_HIGH 0x0 +#define LDB_DI0_VS_POL_MASK 0x00000200 +#define LDB_DI0_VS_POL_ACT_LOW 0x00000200 +#define LDB_DI0_VS_POL_ACT_HIGH 0x0 + +#define LDB_BIT_MAP_CH1_MASK 0x00000100 +#define LDB_BIT_MAP_CH1_JEIDA 0x00000100 +#define LDB_BIT_MAP_CH1_SPWG 0x0 +#define LDB_BIT_MAP_CH0_MASK 0x00000040 +#define LDB_BIT_MAP_CH0_JEIDA 0x00000040 +#define LDB_BIT_MAP_CH0_SPWG 0x0 + +#define LDB_DATA_WIDTH_CH1_MASK 0x00000080 +#define LDB_DATA_WIDTH_CH1_24 0x00000080 +#define LDB_DATA_WIDTH_CH1_18 0x0 +#define LDB_DATA_WIDTH_CH0_MASK 0x00000020 +#define LDB_DATA_WIDTH_CH0_24 0x00000020 +#define LDB_DATA_WIDTH_CH0_18 0x0 + +#define LDB_CH1_MODE_MASK 0x0000000C +#define LDB_CH1_MODE_EN_TO_DI1 0x0000000C +#define LDB_CH1_MODE_EN_TO_DI0 0x00000004 +#define LDB_CH1_MODE_DISABLE 0x0 +#define LDB_CH0_MODE_MASK 0x00000003 +#define LDB_CH0_MODE_EN_TO_DI1 0x00000003 +#define LDB_CH0_MODE_EN_TO_DI0 0x00000001 +#define LDB_CH0_MODE_DISABLE 0x0 + +#define LDB_SPLIT_MODE_EN 0x00000010 + +struct ldb_data { + struct platform_device *pdev; + struct mxc_dispdrv_entry *disp_ldb; + uint32_t *reg; + uint32_t *control_reg; + uint32_t *gpr3_reg; + struct regulator *lvds_bg_reg; + int mode; + bool inited; + struct clk *di_clk[2]; + struct clk *ldb_di_clk[2]; + struct ldb_setting { + bool active; + bool clk_en; + int ipu; + int di; + } setting[2]; + struct notifier_block nb; +}; + +static int g_ldb_mode; + +static struct fb_videomode ldb_modedb[] = { + { + "LDB-XGA", 60, 1024, 768, 15385, + 220, 40, + 21, 7, + 60, 10, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + "LDB-1080P60", 60, 1920, 1080, 7692, + 100, 40, + 30, 3, + 10, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; +static int ldb_modedb_sz = ARRAY_SIZE(ldb_modedb); + +static int bits_per_pixel(int pixel_fmt) +{ + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 24; + break; + case IPU_PIX_FMT_BGR666: + case IPU_PIX_FMT_RGB666: + case IPU_PIX_FMT_LVDS666: + return 18; + break; + default: + break; + } + return 0; +} + +static int valid_mode(int pixel_fmt) +{ + return ((pixel_fmt == IPU_PIX_FMT_RGB24) || + (pixel_fmt == IPU_PIX_FMT_BGR24) || + (pixel_fmt == IPU_PIX_FMT_LVDS666) || + (pixel_fmt == IPU_PIX_FMT_RGB666) || + (pixel_fmt == IPU_PIX_FMT_BGR666)); +} + +/* + * "ldb=spl0/1" -- split mode on DI0/1 + * "ldb=dul0/1" -- dual mode on DI0/1 + * "ldb=sin0/1" -- single mode on LVDS0/1 + * "ldb=sep0/1" -- separate mode begin from LVDS0/1 + * + * there are two LVDS channels(LVDS0 and LVDS1) which can transfer video + * datas, there two channels can be used as split/dual/single/separate mode. + * + * split mode means display data from DI0 or DI1 will send to both channels + * LVDS0+LVDS1. + * dual mode means display data from DI0 or DI1 will be duplicated on LVDS0 + * and LVDS1, it said, LVDS0 and LVDS1 has the same content. + * single mode means only work for DI0/DI1->LVDS0 or DI0/DI1->LVDS1. + * separate mode means you can make DI0/DI1->LVDS0 and DI0/DI1->LVDS1 work + * at the same time. + */ +static int __init ldb_setup(char *options) +{ + if (!strcmp(options, "spl0")) + g_ldb_mode = LDB_SPL_DI0; + else if (!strcmp(options, "spl1")) + g_ldb_mode = LDB_SPL_DI1; + else if (!strcmp(options, "dul0")) + g_ldb_mode = LDB_DUL_DI0; + else if (!strcmp(options, "dul1")) + g_ldb_mode = LDB_DUL_DI1; + else if (!strcmp(options, "sin0")) + g_ldb_mode = LDB_SIN0; + else if (!strcmp(options, "sin1")) + g_ldb_mode = LDB_SIN1; + else if (!strcmp(options, "sep0")) + g_ldb_mode = LDB_SEP0; + else if (!strcmp(options, "sep1")) + g_ldb_mode = LDB_SEP1; + + return 1; +} +__setup("ldb=", ldb_setup); + +static int find_ldb_setting(struct ldb_data *ldb, struct fb_info *fbi) +{ + char *id_di[] = { + "DISP3 BG", + "DISP3 BG - DI1", + }; + char id[16]; + int i; + + for (i = 0; i < 2; i++) { + if (ldb->setting[i].active) { + memset(id, 0, 16); + memcpy(id, id_di[ldb->setting[i].di], + strlen(id_di[ldb->setting[i].di])); + id[4] += ldb->setting[i].ipu; + if (!strcmp(id, fbi->fix.id)) + return i; + } + } + return -EINVAL; +} + +int ldb_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct ldb_data *ldb = container_of(nb, struct ldb_data, nb); + struct fb_event *event = v; + struct fb_info *fbi = event->info; + int setting_idx, di; + + setting_idx = find_ldb_setting(ldb, fbi); + if (setting_idx < 0) + return 0; + + di = ldb->setting[setting_idx].di; + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + if (!fbi->mode) { + dev_warn(&ldb->pdev->dev, + "LDB: can not find mode for xres=%d, yres=%d\n", + fbi->var.xres, fbi->var.yres); + if (ldb->setting[setting_idx].clk_en) { + clk_disable(ldb->ldb_di_clk[di]); + ldb->setting[setting_idx].clk_en = false; + } + return 0; + } + + switch (val) { + case FB_EVENT_PREMODE_CHANGE: + { + uint32_t reg; + uint32_t pixel_clk, rounded_pixel_clk; + struct clk *ldb_clk_parent; + + /* vsync setup */ + reg = readl(ldb->control_reg); + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) { + if (di == 0) + reg = (reg & ~LDB_DI0_VS_POL_MASK) + | LDB_DI0_VS_POL_ACT_HIGH; + else + reg = (reg & ~LDB_DI1_VS_POL_MASK) + | LDB_DI1_VS_POL_ACT_HIGH; + } else { + if (di == 0) + reg = (reg & ~LDB_DI0_VS_POL_MASK) + | LDB_DI0_VS_POL_ACT_LOW; + else + reg = (reg & ~LDB_DI1_VS_POL_MASK) + | LDB_DI1_VS_POL_ACT_LOW; + } + writel(reg, ldb->control_reg); + + /* clk setup */ + pixel_clk = (PICOS2KHZ(fbi->var.pixclock)) * 1000UL; + ldb_clk_parent = clk_get_parent(ldb->ldb_di_clk[di]); + if ((ldb->mode == LDB_SPL_DI0) || (ldb->mode == LDB_SPL_DI1)) + clk_set_rate(ldb_clk_parent, pixel_clk * 7 / 2); + else + clk_set_rate(ldb_clk_parent, pixel_clk * 7); + rounded_pixel_clk = clk_round_rate(ldb->ldb_di_clk[di], + pixel_clk); + clk_set_rate(ldb->ldb_di_clk[di], rounded_pixel_clk); + clk_enable(ldb->ldb_di_clk[di]); + ldb->setting[setting_idx].clk_en = true; + break; + } + case FB_EVENT_BLANK: + { + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + if (!ldb->setting[setting_idx].clk_en) { + clk_enable(ldb->ldb_di_clk[di]); + ldb->setting[setting_idx].clk_en = true; + } + } else { + if (ldb->setting[setting_idx].clk_en) { + clk_disable(ldb->ldb_di_clk[di]); + ldb->setting[setting_idx].clk_en = false; + } + } + } + default: + break; + } + return 0; +} + +static int ldb_disp_init(struct mxc_dispdrv_entry *disp) +{ + int ret = 0, i; + struct ldb_data *ldb = mxc_dispdrv_getdata(disp); + struct mxc_dispdrv_setting *setting = mxc_dispdrv_getsetting(disp); + struct fsl_mxc_ldb_platform_data *plat_data = ldb->pdev->dev.platform_data; + struct resource *res; + uint32_t base_addr; + uint32_t reg, setting_idx; + + /* if input format not valid, make RGB666 as default*/ + if (!valid_mode(setting->if_fmt)) { + dev_warn(&ldb->pdev->dev, "Input pixel format not valid" + " use default RGB666\n"); + setting->if_fmt = IPU_PIX_FMT_RGB666; + } + + if (!ldb->inited) { + char di_clk[] = "ipu1_di0_clk"; + char ldb_clk[] = "ldb_di0_clk"; + int lvds_channel = 0; + + res = platform_get_resource(ldb->pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) + return -ENOMEM; + + base_addr = res->start; + ldb->reg = ioremap(base_addr, res->end - res->start + 1); + ldb->control_reg = ldb->reg + 2; + ldb->gpr3_reg = ldb->reg + 3; + + ldb->lvds_bg_reg = regulator_get(&ldb->pdev->dev, plat_data->lvds_bg_reg); + if (!IS_ERR(ldb->lvds_bg_reg)) { + regulator_set_voltage(ldb->lvds_bg_reg, 2500000, 2500000); + regulator_enable(ldb->lvds_bg_reg); + } + + /* ipu selected by platform data setting */ + setting->dev_id = plat_data->ipu_id; + + reg = readl(ldb->control_reg); + + /* refrence resistor select */ + reg &= ~LDB_BGREF_RMODE_MASK; + if (plat_data->ext_ref) + reg |= LDB_BGREF_RMODE_EXT; + else + reg |= LDB_BGREF_RMODE_INT; + + /* TODO: now only use SPWG data mapping for both channel */ + reg &= ~(LDB_BIT_MAP_CH0_MASK | LDB_BIT_MAP_CH1_MASK); + reg |= LDB_BIT_MAP_CH0_SPWG | LDB_BIT_MAP_CH1_SPWG; + + /* channel mode setting */ + reg &= ~(LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); + reg &= ~(LDB_DATA_WIDTH_CH0_MASK | LDB_DATA_WIDTH_CH1_MASK); + + if (bits_per_pixel(setting->if_fmt) == 24) + reg |= LDB_DATA_WIDTH_CH0_24 | LDB_DATA_WIDTH_CH1_24; + else + reg |= LDB_DATA_WIDTH_CH0_18 | LDB_DATA_WIDTH_CH1_18; + + if (g_ldb_mode) + ldb->mode = g_ldb_mode; + else + ldb->mode = plat_data->mode; + + if (ldb->mode == LDB_SPL_DI0) { + reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI0 + | LDB_CH1_MODE_EN_TO_DI0; + setting->disp_id = 0; + } else if (ldb->mode == LDB_SPL_DI1) { + reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI1 + | LDB_CH1_MODE_EN_TO_DI1; + setting->disp_id = 1; + } else if (ldb->mode == LDB_DUL_DI0) { + reg &= ~LDB_SPLIT_MODE_EN; + reg |= LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0; + setting->disp_id = 0; + } else if (ldb->mode == LDB_DUL_DI1) { + reg &= ~LDB_SPLIT_MODE_EN; + reg |= LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1; + setting->disp_id = 1; + } else if (ldb->mode == LDB_SIN0) { + reg &= ~LDB_SPLIT_MODE_EN; + setting->disp_id = plat_data->disp_id; + if (setting->disp_id == 0) + reg |= LDB_CH0_MODE_EN_TO_DI0; + else + reg |= LDB_CH0_MODE_EN_TO_DI1; + } else if (ldb->mode == LDB_SIN1) { + reg &= ~LDB_SPLIT_MODE_EN; + setting->disp_id = plat_data->disp_id; + if (setting->disp_id == 0) + reg |= LDB_CH1_MODE_EN_TO_DI0; + else + reg |= LDB_CH1_MODE_EN_TO_DI1; + } else { /* separate mode*/ + setting->disp_id = plat_data->disp_id; + + /* first output is LVDS0 or LVDS1 */ + if (ldb->mode == LDB_SEP0) + lvds_channel = 0; + else + lvds_channel = 1; + + reg &= ~LDB_SPLIT_MODE_EN; + + if ((lvds_channel == 0) && (setting->disp_id == 0)) + reg |= LDB_CH0_MODE_EN_TO_DI0; + else if ((lvds_channel == 0) && (setting->disp_id == 1)) + reg |= LDB_CH0_MODE_EN_TO_DI1; + else if ((lvds_channel == 1) && (setting->disp_id == 0)) + reg |= LDB_CH1_MODE_EN_TO_DI0; + else + reg |= LDB_CH1_MODE_EN_TO_DI1; + + if (bits_per_pixel(setting->if_fmt) == 24) { + if (lvds_channel == 0) + reg &= ~LDB_DATA_WIDTH_CH1_24; + else + reg &= ~LDB_DATA_WIDTH_CH0_24; + } else { + if (lvds_channel == 0) + reg &= ~LDB_DATA_WIDTH_CH1_18; + else + reg &= ~LDB_DATA_WIDTH_CH0_18; + } + } + + writel(reg, ldb->control_reg); + + /* clock setting */ + ldb_clk[6] += setting->disp_id; + ldb->ldb_di_clk[0] = clk_get(&ldb->pdev->dev, ldb_clk); + if (IS_ERR(ldb->ldb_di_clk[0])) { + dev_err(&ldb->pdev->dev, "get ldb clk0 failed\n"); + iounmap(ldb->reg); + return PTR_ERR(ldb->ldb_di_clk[0]); + } + di_clk[3] += setting->dev_id; + di_clk[7] += setting->disp_id; + ldb->di_clk[0] = clk_get(&ldb->pdev->dev, di_clk); + if (IS_ERR(ldb->di_clk[0])) { + dev_err(&ldb->pdev->dev, "get di clk0 failed\n"); + iounmap(ldb->reg); + return PTR_ERR(ldb->di_clk[0]); + } + + dev_dbg(&ldb->pdev->dev, "ldb_clk to di clk: %s -> %s\n", ldb_clk, di_clk); + + /* fb notifier for clk setting */ + ldb->nb.notifier_call = ldb_fb_event, + ret = fb_register_client(&ldb->nb); + if (ret < 0) { + iounmap(ldb->reg); + return ret; + } + + setting_idx = 0; + ldb->inited = true; + } else { /* second time for separate mode */ + char di_clk[] = "ipu1_di0_clk"; + char ldb_clk[] = "ldb_di0_clk"; + int lvds_channel; + + if ((ldb->mode == LDB_SPL_DI0) || + (ldb->mode == LDB_SPL_DI1) || + (ldb->mode == LDB_DUL_DI0) || + (ldb->mode == LDB_DUL_DI1) || + (ldb->mode == LDB_SIN0) || + (ldb->mode == LDB_SIN1)) { + dev_err(&ldb->pdev->dev, "for second ldb disp" + "ldb mode should in separate mode\n"); + return -EINVAL; + } + + setting->dev_id = plat_data->ipu_id; + setting->disp_id = !plat_data->disp_id; + + /* second output is LVDS0 or LVDS1 */ + if (ldb->mode == LDB_SEP0) + lvds_channel = 1; + else + lvds_channel = 0; + + reg = readl(ldb->control_reg); + if ((lvds_channel == 0) && (setting->disp_id == 0)) + reg |= LDB_CH0_MODE_EN_TO_DI0; + else if ((lvds_channel == 0) && (setting->disp_id == 1)) + reg |= LDB_CH0_MODE_EN_TO_DI1; + else if ((lvds_channel == 1) && (setting->disp_id == 0)) + reg |= LDB_CH1_MODE_EN_TO_DI0; + else + reg |= LDB_CH1_MODE_EN_TO_DI1; + + if (bits_per_pixel(setting->if_fmt) == 24) { + if (lvds_channel == 0) + reg |= LDB_DATA_WIDTH_CH0_24; + else + reg |= LDB_DATA_WIDTH_CH1_24; + } else { + if (lvds_channel == 0) + reg |= LDB_DATA_WIDTH_CH0_18; + else + reg |= LDB_DATA_WIDTH_CH1_18; + } + writel(reg, ldb->control_reg); + + /* clock setting */ + ldb_clk[6] += setting->disp_id; + ldb->ldb_di_clk[1] = clk_get(&ldb->pdev->dev, ldb_clk); + if (IS_ERR(ldb->ldb_di_clk[1])) { + dev_err(&ldb->pdev->dev, "get ldb clk1 failed\n"); + return PTR_ERR(ldb->ldb_di_clk[1]); + } + di_clk[3] += setting->dev_id; + di_clk[7] += setting->disp_id; + ldb->di_clk[1] = clk_get(&ldb->pdev->dev, di_clk); + if (IS_ERR(ldb->di_clk[1])) { + dev_err(&ldb->pdev->dev, "get di clk1 failed\n"); + return PTR_ERR(ldb->di_clk[1]); + } + + dev_dbg(&ldb->pdev->dev, "ldb_clk to di clk: %s -> %s\n", ldb_clk, di_clk); + + setting_idx = 1; + } + + /* + * ldb_di0_clk -> ipux_di0_clk + * ldb_di1_clk -> ipux_di1_clk + */ + clk_set_parent(ldb->di_clk[setting_idx], + ldb->ldb_di_clk[setting_idx]); + + /* must use spec video mode defined by driver */ + ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, + ldb_modedb, ldb_modedb_sz, NULL, setting->default_bpp); + if (ret != 1) + fb_videomode_to_var(&setting->fbi->var, &ldb_modedb[0]); + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < ldb_modedb_sz; i++) { + struct fb_videomode m; + fb_var_to_videomode(&m, &setting->fbi->var); + if (fb_mode_is_equal(&m, &ldb_modedb[i])) { + fb_add_videomode(&ldb_modedb[i], + &setting->fbi->modelist); + break; + } + } + + /* save current ldb setting for fb notifier */ + ldb->setting[setting_idx].active = true; + ldb->setting[setting_idx].ipu = setting->dev_id; + ldb->setting[setting_idx].di = setting->disp_id; + + return ret; +} + +static void ldb_disp_deinit(struct mxc_dispdrv_entry *disp) +{ + struct ldb_data *ldb = mxc_dispdrv_getdata(disp); + int i; + + writel(0, ldb->control_reg); + + for (i = 0; i < 2; i++) { + clk_disable(ldb->ldb_di_clk[i]); + clk_put(ldb->ldb_di_clk[i]); + } + + fb_unregister_client(&ldb->nb); + + iounmap(ldb->reg); +} + +static struct mxc_dispdrv_driver ldb_drv = { + .name = DISPDRV_LDB, + .init = ldb_disp_init, + .deinit = ldb_disp_deinit, +}; + +/*! + * This function is called by the driver framework to initialize the LDB + * device. + * + * @param dev The device structure for the LDB passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int ldb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ldb_data *ldb; + + ldb = kzalloc(sizeof(struct ldb_data), GFP_KERNEL); + if (!ldb) { + ret = -ENOMEM; + goto alloc_failed; + } + + ldb->pdev = pdev; + ldb->disp_ldb = mxc_dispdrv_register(&ldb_drv); + mxc_dispdrv_setdata(ldb->disp_ldb, ldb); + + dev_set_drvdata(&pdev->dev, ldb); + +alloc_failed: + return ret; +} + +static int ldb_remove(struct platform_device *pdev) +{ + struct ldb_data *ldb = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_unregister(ldb->disp_ldb); + kfree(ldb); + return 0; +} + +static struct platform_driver mxcldb_driver = { + .driver = { + .name = "mxc_ldb", + }, + .probe = ldb_probe, + .remove = ldb_remove, +}; + +static int __init ldb_init(void) +{ + return platform_driver_register(&mxcldb_driver); +} + +static void __exit ldb_uninit(void) +{ + platform_driver_unregister(&mxcldb_driver); +} + +module_init(ldb_init); +module_exit(ldb_uninit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC LDB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxc_dispdrv.c b/drivers/video/mxc/mxc_dispdrv.c new file mode 100644 index 00000000000..06b7af944f5 --- /dev/null +++ b/drivers/video/mxc/mxc_dispdrv.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_dispdrv.c + * @brief mxc display driver framework. + * + * A display device driver could call mxc_dispdrv_register(drv) in its dev_probe() function. + * Move all dev_probe() things into mxc_dispdrv_driver->init(), init() function should init + * and feedback setting; + * Move all dev_remove() things into mxc_dispdrv_driver->deinit(); + * Move all dev_suspend() things into fb_notifier for SUSPEND, if there is; + * Move all dev_resume() things into fb_notifier for RESUME, if there is; + * + * ipuv3 fb driver could call mxc_dispdrv_init(setting) before a fb need be added, with fbi param + * passing by setting, after mxc_dispdrv_init() return, FB driver should get the basic setting + * about fbi info and ipuv3-hw (ipu_id and disp_id). + * + * @ingroup Framebuffer + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/string.h> +#include "mxc_dispdrv.h" + +static LIST_HEAD(dispdrv_list); +static DEFINE_MUTEX(dispdrv_lock); + +struct mxc_dispdrv_entry { + const char *name; + struct list_head list; + int (*init) (struct mxc_dispdrv_entry *); + void (*deinit) (struct mxc_dispdrv_entry *); + bool active; + struct mxc_dispdrv_setting setting; + void *priv; +}; + +struct mxc_dispdrv_entry *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv) +{ + struct mxc_dispdrv_entry *new; + + mutex_lock(&dispdrv_lock); + + new = kzalloc(sizeof(struct mxc_dispdrv_entry), GFP_KERNEL); + if (!new) { + mutex_unlock(&dispdrv_lock); + return ERR_PTR(-ENOMEM); + } + + new->name = drv->name; + new->init = drv->init; + new->deinit = drv->deinit; + + list_add_tail(&new->list, &dispdrv_list); + mutex_unlock(&dispdrv_lock); + + return new; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_register); + +int mxc_dispdrv_unregister(struct mxc_dispdrv_entry *entry) +{ + if (entry) { + mutex_lock(&dispdrv_lock); + if (entry->active && entry->deinit) + entry->deinit(entry); + list_del(&entry->list); + mutex_unlock(&dispdrv_lock); + kfree(entry); + return 0; + } else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_unregister); + +int mxc_dispdrv_init(char *name, struct mxc_dispdrv_setting *setting) +{ + int ret = 0, found = 0; + struct mxc_dispdrv_entry *disp; + + mutex_lock(&dispdrv_lock); + list_for_each_entry(disp, &dispdrv_list, list) { + if (!strcmp(disp->name, name)) { + if (disp->init) { + memcpy(&disp->setting, setting, + sizeof(struct mxc_dispdrv_setting)); + ret = disp->init(disp); + if (ret >= 0) { + disp->active = true; + /* setting may need fix-up */ + memcpy(setting, &disp->setting, + sizeof(struct mxc_dispdrv_setting)); + found = 1; + break; + } + } + } + } + + if (!found) + ret = -EINVAL; + + mutex_unlock(&dispdrv_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_init); + +int mxc_dispdrv_setdata(struct mxc_dispdrv_entry *entry, void *data) +{ + if (entry) { + entry->priv = data; + return 0; + } else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_setdata); + +void *mxc_dispdrv_getdata(struct mxc_dispdrv_entry *entry) +{ + if (entry) { + return entry->priv; + } else + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_getdata); + +struct mxc_dispdrv_setting + *mxc_dispdrv_getsetting(struct mxc_dispdrv_entry *entry) +{ + if (entry) { + return &entry->setting; + } else + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(mxc_dispdrv_getsetting); diff --git a/drivers/video/mxc/mxc_dispdrv.h b/drivers/video/mxc/mxc_dispdrv.h new file mode 100644 index 00000000000..f5f62a2c3cb --- /dev/null +++ b/drivers/video/mxc/mxc_dispdrv.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_DISPDRV_H__ +#define __MXC_DISPDRV_H__ + +struct mxc_dispdrv_entry; + +struct mxc_dispdrv_driver { + const char *name; + int (*init) (struct mxc_dispdrv_entry *); + void (*deinit) (struct mxc_dispdrv_entry *); +}; + +struct mxc_dispdrv_setting { + /*input-feedback parameter*/ + struct fb_info *fbi; + int if_fmt; + int default_bpp; + char *dft_mode_str; + + /*feedback parameter*/ + int dev_id; + int disp_id; +}; + +struct mxc_dispdrv_entry *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv); +int mxc_dispdrv_unregister(struct mxc_dispdrv_entry *entry); +int mxc_dispdrv_init(char *name, struct mxc_dispdrv_setting *setting); +int mxc_dispdrv_setdata(struct mxc_dispdrv_entry *entry, void *data); +void *mxc_dispdrv_getdata(struct mxc_dispdrv_entry *entry); +struct mxc_dispdrv_setting + *mxc_dispdrv_getsetting(struct mxc_dispdrv_entry *entry); +#endif diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c new file mode 100644 index 00000000000..0fa5cf97602 --- /dev/null +++ b/drivers/video/mxc/mxc_edid.c @@ -0,0 +1,451 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.c + * + * @brief MXC EDID driver + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/fb.h> +#include "mxc_edid.h" +#include "../edid.h" + +#undef DEBUG /* define this for verbose EDID parsing output */ + +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +const struct fb_videomode mxc_cea_mode[64] = { + /* #1: 640x480p@59.94/60Hz */ + [1] = { + NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #3: 720x480p@59.94/60Hz */ + [3] = { + NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #4: 1280x720p@59.94/60Hz */ + [4] = { + NULL, 60, 1280, 720, 13468, 220, 110, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0, + }, + /* #5: 1920x1080i@59.94/60Hz */ + [5] = { + NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, 0, + }, + /* #7: 720(1440)x480iH@59.94/60Hz */ + [7] = { + NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, + FB_VMODE_INTERLACED, 0, + }, + /* #9: 720(1440)x240pH@59.94/60Hz */ + [9] = { + NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #16: 1920x1080p@60Hz */ + [16] = { + NULL, 60, 1920, 1080, 6734, 148, 88, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0, + }, + /* #18: 720x576pH@50Hz */ + [18] = { + NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, + FB_VMODE_NONINTERLACED, 0, + }, + /* #19: 1280x720p@50Hz */ + [19] = { + NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0, + }, + /* #20: 1920x1080i@50Hz */ + [20] = { + NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, 0, + }, + /* #31: 1920x1080p@50Hz */ + [31] = { + NULL, 50, 1920, 1080, 6734, 148, 528, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0, + }, + /* #32: 1920x1080p@23.98/24Hz */ + [32] = { + NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0, + }, + /* #35: (2880)x480p4x@59.94/60Hz */ + [35] = { + NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0, + FB_VMODE_NONINTERLACED, 0, + }, +}; + +static void get_detailed_timing(unsigned char *block, + struct fb_videomode *mode) +{ + mode->xres = H_ACTIVE; + mode->yres = V_ACTIVE; + mode->pixclock = PIXEL_CLOCK; + mode->pixclock /= 1000; + mode->pixclock = KHZ2PICOS(mode->pixclock); + mode->right_margin = H_SYNC_OFFSET; + mode->left_margin = (H_ACTIVE + H_BLANKING) - + (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); + mode->upper_margin = V_BLANKING - V_SYNC_OFFSET - + V_SYNC_WIDTH; + mode->lower_margin = V_SYNC_OFFSET; + mode->hsync_len = H_SYNC_WIDTH; + mode->vsync_len = V_SYNC_WIDTH; + if (HSYNC_POSITIVE) + mode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (VSYNC_POSITIVE) + mode->sync |= FB_SYNC_VERT_HIGH_ACT; + mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) * + (V_ACTIVE + V_BLANKING)); + if (INTERLACED) { + mode->yres *= 2; + mode->upper_margin *= 2; + mode->lower_margin *= 2; + mode->vsync_len *= 2; + mode->vmode |= FB_VMODE_INTERLACED; + } + mode->flag = FB_MODE_IS_DETAILED; + + DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000); + DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET, + H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING); + DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET, + V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING); + DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-", + (VSYNC_POSITIVE) ? "+" : "-"); +} + +int mxc_edid_parse_ext_blk(unsigned char *edid, + struct mxc_edid_cfg *cfg, + struct fb_monspecs *specs) +{ + char detail_timing_desc_offset; + struct fb_videomode *mode, *m; + unsigned char index = 0x0; + unsigned char *block; + int i, num = 0, revision; + + if (edid[index++] != 0x2) /* only support cea ext block now */ + return -1; + revision = edid[index++]; + DPRINTK("cea extent revision %d\n", revision); + mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); + if (mode == NULL) + return -1; + + detail_timing_desc_offset = edid[index++]; + + if (revision >= 2) { + cfg->cea_underscan = (edid[index] >> 7) & 0x1; + cfg->cea_basicaudio = (edid[index] >> 6) & 0x1; + cfg->cea_ycbcr444 = (edid[index] >> 5) & 0x1; + cfg->cea_ycbcr422 = (edid[index] >> 4) & 0x1; + + DPRINTK("CEA underscan %d\n", cfg->cea_underscan); + DPRINTK("CEA basicaudio %d\n", cfg->cea_basicaudio); + DPRINTK("CEA ycbcr444 %d\n", cfg->cea_ycbcr444); + DPRINTK("CEA ycbcr422 %d\n", cfg->cea_ycbcr422); + } + + if (revision >= 3) { + /* short desc */ + DPRINTK("CEA Short desc timmings\n"); + index++; + while (index < detail_timing_desc_offset) { + unsigned char tagcode, blklen; + + tagcode = (edid[index] >> 5) & 0x7; + blklen = (edid[index]) & 0x1f; + + DPRINTK("Tagcode %x Len %d\n", tagcode, blklen); + + switch (tagcode) { + case 0x2: /*Video data block*/ + { + int cea_idx; + i = 0; + while (i < blklen) { + index++; + cea_idx = edid[index] & 0x7f; + if (cea_idx < ARRAY_SIZE(mxc_cea_mode) && + (mxc_cea_mode[cea_idx].xres)) { + DPRINTK("Support CEA Format #%d\n", cea_idx); + mode[num] = mxc_cea_mode[cea_idx]; + mode[num].flag |= FB_MODE_IS_STANDARD; + num++; + } + i++; + } + break; + } + case 0x3: /*Vendor specific data*/ + { + unsigned char IEEE_reg_iden[3]; + unsigned char deep_color; + IEEE_reg_iden[0] = edid[index+1]; + IEEE_reg_iden[1] = edid[index+2]; + IEEE_reg_iden[2] = edid[index+3]; + deep_color = edid[index+6]; + + if ((IEEE_reg_iden[0] == 0x03) && + (IEEE_reg_iden[1] == 0x0c) && + (IEEE_reg_iden[2] == 0x00)) + cfg->hdmi_cap = 1; + + if (deep_color & 0x40) + cfg->vsd_dc_48bit = true; + if (deep_color & 0x20) + cfg->vsd_dc_36bit = true; + if (deep_color & 0x10) + cfg->vsd_dc_30bit = true; + if (deep_color & 0x08) + cfg->vsd_dc_y444 = true; + if (deep_color & 0x01) + cfg->vsd_dvi_dual = true; + + DPRINTK("VSD hdmi capability %d\n", cfg->hdmi_cap); + DPRINTK("VSD support deep color 48bit %d\n", cfg->vsd_dc_48bit); + DPRINTK("VSD support deep color 36bit %d\n", cfg->vsd_dc_36bit); + DPRINTK("VSD support deep color 30bit %d\n", cfg->vsd_dc_30bit); + DPRINTK("VSD support deep color y444 %d\n", cfg->vsd_dc_y444); + DPRINTK("VSD support dvi dual %d\n", cfg->vsd_dvi_dual); + + index += blklen; + break; + } + case 0x1: /*Audio data block*/ + case 0x4: /*Speaker allocation block*/ + case 0x7: /*User extended block*/ + default: + /* skip */ + index += blklen; + break; + } + + index++; + } + } + + /* long desc */ + DPRINTK("CEA long desc timmings\n"); + index = detail_timing_desc_offset; + block = edid + index; + while (index < (EDID_LENGTH - DETAILED_TIMING_DESCRIPTION_SIZE)) { + if (!(block[0] == 0x00 && block[1] == 0x00)) { + get_detailed_timing(block, &mode[num]); + num++; + } + block += DETAILED_TIMING_DESCRIPTION_SIZE; + index += DETAILED_TIMING_DESCRIPTION_SIZE; + } + + if (!num) { + kfree(mode); + return 0; + } + + m = kmalloc((num + specs->modedb_len) * + sizeof(struct fb_videomode), GFP_KERNEL); + if (!m) + return 0; + + if (specs->modedb_len) { + memmove(m, specs->modedb, + specs->modedb_len * sizeof(struct fb_videomode)); + kfree(specs->modedb); + } + memmove(m+specs->modedb_len, mode, + num * sizeof(struct fb_videomode)); + kfree(mode); + + specs->modedb_len += num; + specs->modedb = m; + + return 0; +} + +static int mxc_edid_readblk(struct i2c_adapter *adp, + unsigned short addr, unsigned char *edid) +{ + int ret = 0, extblknum = 0; + unsigned char regaddr = 0x0; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + + if (edid[1] == 0x00) + return -ENOENT; + + extblknum = edid[0x7E]; + + if (extblknum) { + regaddr = 128; + msg[1].buf = edid + EDID_LENGTH; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID ext block\n"); + return -EIO; + } + } + + return extblknum; +} + +static int mxc_edid_readsegblk(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, int seg_num) +{ + int ret = 0; + unsigned char segment = 0x1, regaddr = 0; + struct i2c_msg msg[3] = { + { + .addr = 0x30, + .flags = 0, + .len = 1, + .buf = &segment, + }, { + .addr = addr, + .flags = 0, + .len = 1, + .buf = ®addr, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + + if (seg_num == 2) { + regaddr = 128; + msg[2].buf = edid + EDID_LENGTH; + + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; + } + } + + return ret; +} + +int mxc_edid_var_to_vic(struct fb_var_screeninfo *var) +{ + int i; + struct fb_videomode m; + + for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) { + fb_var_to_videomode(&m, var); + if (fb_mode_is_equal(&m, &mxc_cea_mode[i])) + break; + } + + if (i == ARRAY_SIZE(mxc_cea_mode)) + return 0; + + return i; +} +EXPORT_SYMBOL(mxc_edid_var_to_vic); + +/* make sure edid has 512 bytes*/ +int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi) +{ + int ret = 0, extblknum; + if (!adp || !edid || !cfg || !fbi) + return -EINVAL; + + memset(edid, 0, EDID_LENGTH*4); + memset(cfg, 0, sizeof(struct mxc_edid_cfg)); + + extblknum = mxc_edid_readblk(adp, addr, edid); + if (extblknum < 0) + return extblknum; + + /* edid first block parsing */ + memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); + fb_edid_to_monspecs(edid, &fbi->monspecs); + + if (extblknum) { + int i; + + /* need read segment block? */ + if (extblknum > 1) { + ret = mxc_edid_readsegblk(adp, addr, + edid + EDID_LENGTH*2, extblknum - 1); + if (ret < 0) + return ret; + } + + for (i = 1; i <= extblknum; i++) + /* edid ext block parsing */ + mxc_edid_parse_ext_blk(edid + i*EDID_LENGTH, + cfg, &fbi->monspecs); + } + + return 0; +} +EXPORT_SYMBOL(mxc_edid_read); diff --git a/drivers/video/mxc/mxc_edid.h b/drivers/video/mxc/mxc_edid.h new file mode 100644 index 00000000000..ec65a5fa9bd --- /dev/null +++ b/drivers/video/mxc/mxc_edid.h @@ -0,0 +1,48 @@ +/* + * Copyright 2009-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.h + * + * @brief MXC EDID tools + * + * @ingroup Framebuffer + */ + +#ifndef MXC_EDID_H +#define MXC_EDID_H + +struct mxc_edid_cfg { + bool cea_underscan; + bool cea_basicaudio; + bool cea_ycbcr444; + bool cea_ycbcr422; + bool hdmi_cap; + + /*VSD*/ + bool vsd_dc_48bit; + bool vsd_dc_36bit; + bool vsd_dc_30bit; + bool vsd_dc_y444; + bool vsd_dvi_dual; +}; + +int mxc_edid_var_to_vic(struct fb_var_screeninfo *var); +int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi); + +#endif diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c new file mode 100644 index 00000000000..260b41a3ddf --- /dev/null +++ b/drivers/video/mxc/mxc_ipuv3_fb.c @@ -0,0 +1,2072 @@ +/* + * Copyright 2004-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/uaccess.h> +#include <linux/fsl_devices.h> +#include <asm/mach-types.h> +#include <mach/ipu-v3.h> +#include "mxc_dispdrv.h" + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" + +/* Display port number */ +#define MXCFB_PORT_NUM 2 +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + int default_bpp; + int cur_blank; + int next_blank; + ipu_channel_t ipu_ch; + int ipu_id; + int ipu_di; + u32 ipu_di_pix_fmt; + bool ipu_int_clk; + bool overlay; + bool alpha_chan_en; + dma_addr_t alpha_phy_addr0; + dma_addr_t alpha_phy_addr1; + void *alpha_virt_addr0; + void *alpha_virt_addr1; + uint32_t alpha_mem_len; + uint32_t ipu_ch_irq; + uint32_t ipu_alp_ch_irq; + uint32_t cur_ipu_buf; + uint32_t cur_ipu_alpha_buf; + + u32 pseudo_palette[16]; + + bool mode_found; + volatile bool wait4vsync; + struct semaphore flip_sem; + struct semaphore alpha_flip_sem; + struct completion vsync_complete; + + void *ipu; + struct fb_info *ovfbi; +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +enum { + BOTH_ON, + SRC_ON, + TGT_ON, + BOTH_OFF +}; + +static bool g_dp_in_use[2]; +LIST_HEAD(fb_alloc_list); + +static uint32_t bpp_to_pixfmt(struct fb_info *fbi) +{ + uint32_t pixfmt = 0; + + if (fbi->var.nonstd) + return fbi->var.nonstd; + + switch (fbi->var.bits_per_pixel) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +static struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id) +{ + int i; + struct mxcfb_info *mxc_fbi; + struct fb_info *fbi = NULL; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi = + ((struct mxcfb_info *)(registered_fb[i]->par)); + + if ((mxc_fbi->ipu_ch == ipu_ch) && + (mxc_fbi->ipu_id == ipu_id)) { + fbi = registered_fb[i]; + break; + } + } + return fbi; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ywrapstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static int _setup_disp_channel1(struct fb_info *fbi) +{ + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + memset(¶ms, 0, sizeof(params)); + + if (mxc_fbi->ipu_ch == MEM_DC_SYNC) { + params.mem_dc_sync.di = mxc_fbi->ipu_di; + if (fbi->var.vmode & FB_VMODE_INTERLACED) + params.mem_dc_sync.interlaced = true; + params.mem_dc_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + params.mem_dc_sync.in_pixel_fmt = bpp_to_pixfmt(fbi); + } else { + params.mem_dp_bg_sync.di = mxc_fbi->ipu_di; + if (fbi->var.vmode & FB_VMODE_INTERLACED) + params.mem_dp_bg_sync.interlaced = true; + params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + params.mem_dp_bg_sync.in_pixel_fmt = bpp_to_pixfmt(fbi); + if (mxc_fbi->alpha_chan_en) + params.mem_dp_bg_sync.alpha_chan_en = true; + } + ipu_init_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, ¶ms); + + return 0; +} + +static int _setup_disp_channel2(struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int fb_stride; + unsigned long base; + + switch (bpp_to_pixfmt(fbi)) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P: + fb_stride = fbi->var.xres_virtual; + break; + default: + fb_stride = fbi->fix.line_length; + } + + mxc_fbi->cur_ipu_buf = 2; + sema_init(&mxc_fbi->flip_sem, 1); + if (mxc_fbi->alpha_chan_en) { + mxc_fbi->cur_ipu_alpha_buf = 1; + sema_init(&mxc_fbi->alpha_flip_sem, 1); + } + fbi->var.xoffset = 0; + + base = (fbi->var.yoffset * fb_stride + fbi->var.xoffset); + base += fbi->fix.smem_start; + + retval = ipu_init_channel_buffer(mxc_fbi->ipu, + mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi), + fbi->var.xres, fbi->var.yres, + fb_stride, + fbi->var.rotate, + base, + base, + fbi->var.accel_flags & + FB_ACCEL_DOUBLE_FLAG ? 0 : base, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + } + + if (mxc_fbi->alpha_chan_en) { + retval = ipu_init_channel_buffer(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + IPU_PIX_FMT_GENERIC, + fbi->var.xres, fbi->var.yres, + fbi->var.xres, + fbi->var.rotate, + mxc_fbi->alpha_phy_addr1, + mxc_fbi->alpha_phy_addr0, + 0, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + } + + return retval; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + u32 mem_len, alpha_mem_len; + ipu_di_signal_cfg_t sig_cfg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + mxcfb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + + if (mxcfb_map_video_memory(fbi) < 0) + return -ENOMEM; + } + + if (mxc_fbi->alpha_chan_en) { + alpha_mem_len = fbi->var.xres * fbi->var.yres; + if ((!mxc_fbi->alpha_phy_addr0 && !mxc_fbi->alpha_phy_addr1) || + (alpha_mem_len > mxc_fbi->alpha_mem_len)) { + if (mxc_fbi->alpha_phy_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_phy_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + + mxc_fbi->alpha_virt_addr0 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr0, + GFP_DMA | GFP_KERNEL); + + mxc_fbi->alpha_virt_addr1 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr1, + GFP_DMA | GFP_KERNEL); + if (mxc_fbi->alpha_virt_addr0 == NULL || + mxc_fbi->alpha_virt_addr1 == NULL) { + dev_err(fbi->device, "mxcfb: dma alloc for" + " alpha buffer failed.\n"); + if (mxc_fbi->alpha_virt_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_virt_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + return -ENOMEM; + } + mxc_fbi->alpha_mem_len = alpha_mem_len; + } + } + + if (mxc_fbi->next_blank != FB_BLANK_UNBLANK) + return retval; + + _setup_disp_channel1(fbi); + + if (!mxc_fbi->overlay) { + uint32_t out_pixel_fmt; + + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.vmode & FB_VMODE_INTERLACED) + sig_cfg.interlaced = true; + out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */ + sig_cfg.odd_field_first = true; + if (mxc_fbi->ipu_int_clk) + sig_cfg.int_clk = true; + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_init_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + out_pixel_fmt, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin, + 0, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + + fbi->mode = + (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, 0, 0); + } + + retval = _setup_disp_channel2(fbi); + if (retval) + return retval; + + ipu_enable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + + return retval; +} + +static int _swap_channels(struct fb_info *fbi_from, + struct fb_info *fbi_to, bool both_on) +{ + int retval, tmp; + ipu_channel_t old_ch; + struct fb_info *ovfbi; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par; + struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + if (both_on) { + ipu_disable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch, true); + ipu_uninit_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch); + } + + /* switch the mxc fbi parameters */ + old_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = mxc_fbi_to->ipu_ch; + mxc_fbi_to->ipu_ch = old_ch; + tmp = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = tmp; + ovfbi = mxc_fbi_from->ovfbi; + mxc_fbi_from->ovfbi = mxc_fbi_to->ovfbi; + mxc_fbi_to->ovfbi = ovfbi; + + _setup_disp_channel1(fbi_from); + retval = _setup_disp_channel2(fbi_from); + if (retval) + return retval; + + /* switch between dp and dc, disable old idmac, enable new idmac */ + retval = ipu_swap_channel(mxc_fbi_from->ipu, old_ch, mxc_fbi_from->ipu_ch); + ipu_uninit_channel(mxc_fbi_from->ipu, old_ch); + + if (both_on) { + _setup_disp_channel1(fbi_to); + retval = _setup_disp_channel2(fbi_to); + if (retval) + return retval; + ipu_enable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch); + } + + return retval; +} + +static int swap_channels(struct fb_info *fbi_from) +{ + int i; + int swap_mode; + ipu_channel_t ch_to; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par; + struct fb_info *fbi_to = NULL; + struct mxcfb_info *mxc_fbi_to; + + /* what's the target channel? */ + if (mxc_fbi_from->ipu_ch == MEM_BG_SYNC) + ch_to = MEM_DC_SYNC; + else + ch_to = MEM_BG_SYNC; + + fbi_to = found_registered_fb(ch_to, mxc_fbi_from->ipu_id); + if (!fbi_to) + return -1; + mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq); + ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq); + ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, fbi_from); + ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, fbi_to); + + if (mxc_fbi_from->cur_blank == FB_BLANK_UNBLANK) { + if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK) + swap_mode = BOTH_ON; + else + swap_mode = SRC_ON; + } else { + if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK) + swap_mode = TGT_ON; + else + swap_mode = BOTH_OFF; + } + + switch (swap_mode) { + case BOTH_ON: + /* disable target->switch src->enable target */ + _swap_channels(fbi_from, fbi_to, true); + break; + case SRC_ON: + /* just switch src */ + _swap_channels(fbi_from, fbi_to, false); + break; + case TGT_ON: + /* just switch target */ + _swap_channels(fbi_to, fbi_from, false); + break; + case BOTH_OFF: + /* switch directly, no more need to do */ + mxc_fbi_to->ipu_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = ch_to; + i = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = i; + break; + default: + break; + } + + if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi_from) != 0) { + dev_err(fbi_from->device, "Error registering irq %d\n", + mxc_fbi_from->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq); + if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi_to) != 0) { + dev_err(fbi_to->device, "Error registering irq %d\n", + mxc_fbi_to->ipu_ch_irq); + return -EBUSY; + } + ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq); + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + /* fg should not bigger than bg */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct fb_info *fbi_tmp; + int bg_xres = 0, bg_yres = 0; + int16_t pos_x, pos_y; + + bg_xres = var->xres; + bg_yres = var->yres; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (fbi_tmp) { + bg_xres = fbi_tmp->var.xres; + bg_yres = fbi_tmp->var.yres; + } + + ipu_disp_get_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, &pos_x, &pos_y); + + if ((var->xres + pos_x) > bg_xres) + var->xres = bg_xres - pos_x; + if ((var->yres + pos_y) > bg_yres) + var->yres = bg_yres - pos_y; + } + + if (var->rotate > IPU_ROTATE_VERT_FLIP) + var->rotate = IPU_ROTATE_NONE; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres * 3; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 12) && + (var->bits_per_pixel != 8)) + var->bits_per_pixel = 16; + + switch (var->bits_per_pixel) { + case 8: + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + (bool)ga.enable, + ga.alpha)) { + retval = -EINVAL; + break; + } + + if (ga.enable) + mxc_fbi->alpha_chan_en = false; + + if (ga.enable) + dev_dbg(fbi->device, + "Set global alpha of %s to %d\n", + fbi->fix.id, ga.alpha); + break; + } + case MXCFB_SET_LOC_ALPHA: + { + struct mxcfb_loc_alpha la; + + if (copy_from_user(&la, (void *)arg, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu, mxc_fbi->ipu_ch, + !(bool)la.enable, 0)) { + retval = -EINVAL; + break; + } + + if (la.enable && !la.alpha_in_pixel) { + struct fb_info *fbi_tmp; + ipu_channel_t ipu_ch; + + mxc_fbi->alpha_chan_en = true; + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + ipu_ch = MEM_BG_SYNC; + else if (mxc_fbi->ipu_ch == MEM_BG_SYNC) + ipu_ch = MEM_FG_SYNC; + else { + retval = -EINVAL; + break; + } + + fbi_tmp = found_registered_fb(ipu_ch, mxc_fbi->ipu_id); + if (fbi_tmp) + ((struct mxcfb_info *)(fbi_tmp->par))->alpha_chan_en = false; + } else + mxc_fbi->alpha_chan_en = false; + + mxcfb_set_par(fbi); + + la.alpha_phy_addr0 = mxc_fbi->alpha_phy_addr0; + la.alpha_phy_addr1 = mxc_fbi->alpha_phy_addr1; + if (copy_to_user((void *)arg, &la, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (la.enable) + dev_dbg(fbi->device, + "Enable DP local alpha for %s\n", + fbi->fix.id); + break; + } + case MXCFB_SET_LOC_ALP_BUF: + { + unsigned long base; + uint32_t ipu_alp_ch_irq; + + if (!(((mxc_fbi->ipu_ch == MEM_FG_SYNC) || + (mxc_fbi->ipu_ch == MEM_BG_SYNC)) && + (mxc_fbi->alpha_chan_en))) { + dev_err(fbi->device, + "Should use background or overlay " + "framebuffer to set the alpha buffer " + "number\n"); + return -EINVAL; + } + + if (get_user(base, argp)) + return -EFAULT; + + if (base != mxc_fbi->alpha_phy_addr0 && + base != mxc_fbi->alpha_phy_addr1) { + dev_err(fbi->device, + "Wrong alpha buffer physical address " + "%lu\n", base); + return -EINVAL; + } + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + else + ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + + down(&mxc_fbi->alpha_flip_sem); + + mxc_fbi->cur_ipu_alpha_buf = + !mxc_fbi->cur_ipu_alpha_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi-> + cur_ipu_alpha_buf, + base) == 0) { + ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + ipu_clear_irq(mxc_fbi->ipu, ipu_alp_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, ipu_alp_ch_irq); + } else { + dev_err(fbi->device, + "Error updating %s SDC alpha buf %d " + "to address=0x%08lX\n", + fbi->fix.id, + mxc_fbi->cur_ipu_alpha_buf, base); + } + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_color_key(mxc_fbi->ipu, mxc_fbi->ipu_ch, + key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_SET_GAMMA: + { + struct mxcfb_gamma gamma; + if (copy_from_user(&gamma, (void *)arg, sizeof(gamma))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_gamma_correction(mxc_fbi->ipu, + mxc_fbi->ipu_ch, + gamma.enable, + gamma.constk, + gamma.slopek); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + /* BG should poweron */ + struct mxcfb_info *bg_mxcfbi = NULL; + struct fb_info *fbi_tmp; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (fbi_tmp) + bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par)); + + if (!bg_mxcfbi) { + retval = -EINVAL; + break; + } + if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) { + retval = -EINVAL; + break; + } + } + if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) { + retval = -EINVAL; + break; + } + + init_completion(&mxc_fbi->vsync_complete); + + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + mxc_fbi->wait4vsync = true; + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + retval = wait_for_completion_interruptible_timeout( + &mxc_fbi->vsync_complete, 1 * HZ); + if (retval == 0) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout %d\n", + retval); + mxc_fbi->wait4vsync = false; + retval = -ETIME; + } else if (retval > 0) { + retval = 0; + } + break; + } + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) + return -EFAULT; + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_DMA); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + if (put_user(mem->phy_addr, argp)) + return -EFAULT; + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + struct fb_info *bg_fbi = NULL; + struct mxcfb_info *bg_mxcfbi = NULL; + + if (mxc_fbi->ipu_ch != MEM_FG_SYNC) { + dev_err(fbi->device, "Should use the overlay " + "framebuffer to set the position of " + "the overlay window\n"); + retval = -EINVAL; + break; + } + + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + + bg_fbi = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (bg_fbi) + bg_mxcfbi = ((struct mxcfb_info *)(bg_fbi->par)); + + if (bg_fbi == NULL) { + dev_err(fbi->device, "Cannot find the " + "background framebuffer\n"); + retval = -ENOENT; + break; + } + + /* if fb is unblank, check if the pos fit the display */ + if (mxc_fbi->cur_blank == FB_BLANK_UNBLANK) { + if (fbi->var.xres + pos.x > bg_fbi->var.xres) { + if (bg_fbi->var.xres < fbi->var.xres) + pos.x = 0; + else + pos.x = bg_fbi->var.xres - fbi->var.xres; + } + if (fbi->var.yres + pos.y > bg_fbi->var.yres) { + if (bg_fbi->var.yres < fbi->var.yres) + pos.y = 0; + else + pos.y = bg_fbi->var.yres - fbi->var.yres; + } + } + + retval = ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, + pos.x, pos.y); + + if (copy_to_user((void *)arg, &pos, sizeof(pos))) { + retval = -EFAULT; + break; + } + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_DIFMT: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_di_pix_fmt, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_FB_IPU_DI: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_di, argp)) + return -EFAULT; + break; + } + case MXCFB_GET_FB_BLANK: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->cur_blank, argp)) + return -EFAULT; + break; + } + case MXCFB_SET_DIFMT: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (get_user(mxc_fbi->ipu_di_pix_fmt, argp)) + return -EFAULT; + + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (mxc_fbi->cur_blank == blank) + return 0; + + mxc_fbi->next_blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true); + if (mxc_fbi->ipu_di >= 0) + ipu_uninit_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di); + ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_par(info); + break; + } + mxc_fbi->cur_blank = blank; + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par, + *mxc_graphic_fbi = NULL; + u_int y_bottom; + unsigned long base, active_alpha_phy_addr = 0; + bool loc_alpha_en = false; + int fb_stride; + int i; + + if (info->var.yoffset == var->yoffset) + return 0; /* No change, do nothing */ + + /* no pan display during fb blank */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *bg_mxcfbi = NULL; + struct fb_info *fbi_tmp; + + fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id); + if (fbi_tmp) + bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par)); + if (!bg_mxcfbi) + return -EINVAL; + if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) + return -EINVAL; + } + if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) + return -EINVAL; + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + switch (bpp_to_pixfmt(info)) { + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YVU420P: + case IPU_PIX_FMT_NV12: + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + case IPU_PIX_FMT_YUV420P: + fb_stride = info->var.xres_virtual; + break; + default: + fb_stride = info->fix.line_length; + } + + base = (var->yoffset * fb_stride + var->xoffset); + base += info->fix.smem_start; + + /* Check if DP local alpha is enabled and find the graphic fb */ + if (mxc_fbi->ipu_ch == MEM_BG_SYNC || mxc_fbi->ipu_ch == MEM_FG_SYNC) { + for (i = 0; i < num_registered_fb; i++) { + char *bg_id = "DISP3 BG"; + char *fg_id = "DISP3 FG"; + char *idstr = registered_fb[i]->fix.id; + bg_id[4] += mxc_fbi->ipu_id; + fg_id[4] += mxc_fbi->ipu_id; + if ((strcmp(idstr, bg_id) == 0 || + strcmp(idstr, fg_id) == 0) && + ((struct mxcfb_info *) + (registered_fb[i]->par))->alpha_chan_en) { + loc_alpha_en = true; + mxc_graphic_fbi = (struct mxcfb_info *) + (registered_fb[i]->par); + active_alpha_phy_addr = + mxc_fbi->cur_ipu_alpha_buf ? + mxc_graphic_fbi->alpha_phy_addr1 : + mxc_graphic_fbi->alpha_phy_addr0; + dev_dbg(info->device, "Updating SDC alpha " + "buf %d address=0x%08lX\n", + !mxc_fbi->cur_ipu_alpha_buf, + active_alpha_phy_addr); + break; + } + } + } + + down(&mxc_fbi->flip_sem); + + mxc_fbi->cur_ipu_buf = (++mxc_fbi->cur_ipu_buf) % 3; + mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf; + + dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n", + info->fix.id, mxc_fbi->cur_ipu_buf, base); + + if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, base) == 0) { + /* Update the DP local alpha buffer only for graphic plane */ + if (loc_alpha_en && mxc_graphic_fbi == mxc_fbi && + ipu_update_channel_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf, + active_alpha_phy_addr) == 0) { + ipu_select_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + } + + /* update u/v offset */ + ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, + bpp_to_pixfmt(info), + info->var.xres_virtual, + info->var.yres_virtual, + info->var.xres_virtual, + 0, 0, + var->yoffset, + var->xoffset); + + ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX, " + "current buf %d, buf0 ready %d, buf1 ready %d, " + "buf2 ready %d\n", mxc_fbi->cur_ipu_buf, base, + ipu_get_cur_buffer_idx(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 0), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 1), + ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch, + IPU_INPUT_BUFFER, 2)); + mxc_fbi->cur_ipu_buf = (++mxc_fbi->cur_ipu_buf) % 3; + mxc_fbi->cur_ipu_buf = (++mxc_fbi->cur_ipu_buf) % 3; + mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf; + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq); + return -EBUSY; + } + + dev_dbg(info->device, "Update complete\n"); + + info->var.yoffset = var->yoffset; + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else if ((vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) || + (vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) { + len = mxc_fbi->alpha_mem_len; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) + return -EINVAL; + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + if (mxc_fbi->wait4vsync) { + complete(&mxc_fbi->vsync_complete); + ipu_disable_irq(mxc_fbi->ipu, irq); + mxc_fbi->wait4vsync = false; + } else { + up(&mxc_fbi->flip_sem); + ipu_disable_irq(mxc_fbi->ipu, irq); + } + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + up(&mxc_fbi->alpha_flip_sem); + ipu_disable_irq(mxc_fbi->ipu, irq); + return IRQ_HANDLED; +} + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int saved_blank; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + if (mxc_fbi->ovfbi) { + struct mxcfb_info *mxc_fbi_fg = + (struct mxcfb_info *)mxc_fbi->ovfbi->par; + + console_lock(); + fb_set_suspend(mxc_fbi->ovfbi, 1); + saved_blank = mxc_fbi_fg->cur_blank; + mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi); + mxc_fbi_fg->next_blank = saved_blank; + console_unlock(); + } + + console_lock(); + fb_set_suspend(fbi, 1); + saved_blank = mxc_fbi->cur_blank; + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxc_fbi->next_blank = saved_blank; + console_unlock(); + + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + console_lock(); + mxcfb_blank(mxc_fbi->next_blank, fbi); + fb_set_suspend(fbi, 0); + console_unlock(); + + if (mxc_fbi->ovfbi) { + struct mxcfb_info *mxc_fbi_fg = + (struct mxcfb_info *)mxc_fbi->ovfbi->par; + console_lock(); + mxcfb_blank(mxc_fbi_fg->next_blank, mxc_fbi->ovfbi); + fb_set_suspend(mxc_fbi->ovfbi, 0); + console_unlock(); + } + + return 0; +} + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + + fbi->screen_base = dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *)&fbi->fix.smem_start, + GFP_DMA); + if (fbi->screen_base == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + return -EBUSY; + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +static ssize_t show_disp_chan(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + + if (mxcfbi->ipu_ch == MEM_BG_SYNC) + return sprintf(buf, "2-layer-fb-bg\n"); + else if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return sprintf(buf, "2-layer-fb-fg\n"); + else if (mxcfbi->ipu_ch == MEM_DC_SYNC) + return sprintf(buf, "1-layer-fb\n"); + else + return sprintf(buf, "err: no display chan\n"); +} + +static ssize_t swap_disp_chan(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + struct mxcfb_info *fg_mxcfbi = NULL; + + console_lock(); + /* swap only happen between DP-BG and DC, while DP-FG disable */ + if (((mxcfbi->ipu_ch == MEM_BG_SYNC) && + (strstr(buf, "1-layer-fb") != NULL)) || + ((mxcfbi->ipu_ch == MEM_DC_SYNC) && + (strstr(buf, "2-layer-fb-bg") != NULL))) { + struct fb_info *fbi_fg; + + fbi_fg = found_registered_fb(MEM_FG_SYNC, mxcfbi->ipu_id); + if (fbi_fg) + fg_mxcfbi = (struct mxcfb_info *)fbi_fg->par; + + if (!fg_mxcfbi || + fg_mxcfbi->cur_blank == FB_BLANK_UNBLANK) { + dev_err(dev, + "Can not switch while fb2(fb-fg) is on.\n"); + console_unlock(); + return count; + } + + if (swap_channels(info) < 0) + dev_err(dev, "Swap display channel failed.\n"); + } + + console_unlock(); + return count; +} +DEVICE_ATTR(fsl_disp_property, 644, show_disp_chan, swap_disp_chan); + +static int mxcfb_dispdrv_init(struct platform_device *pdev, + struct fb_info *fbi) +{ + struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data; + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct mxc_dispdrv_setting setting; + char disp_dev[32], *default_dev = "lcd"; + int ret = 0; + + setting.if_fmt = plat_data->interface_pix_fmt; + setting.dft_mode_str = plat_data->mode_str; + setting.default_bpp = plat_data->default_bpp; + if (!setting.default_bpp) + setting.default_bpp = 16; + setting.fbi = fbi; + if (!strlen(plat_data->disp_dev)) { + memcpy(disp_dev, default_dev, strlen(default_dev)); + disp_dev[strlen(default_dev)] = '\0'; + } else { + memcpy(disp_dev, plat_data->disp_dev, + strlen(plat_data->disp_dev)); + disp_dev[strlen(plat_data->disp_dev)] = '\0'; + } + + dev_info(&pdev->dev, "register mxc display driver %s\n", disp_dev); + + ret = mxc_dispdrv_init(disp_dev, &setting); + if (ret < 0) { + dev_err(&pdev->dev, + "register mxc display driver failed with %d\n", ret); + } else { + /* fix-up */ + mxcfbi->ipu_di_pix_fmt = setting.if_fmt; + mxcfbi->default_bpp = setting.default_bpp; + + /* setting */ + mxcfbi->ipu_id = setting.dev_id; + mxcfbi->ipu_di = setting.disp_id; + } + + return ret; +} + +/* + * Parse user specified options (`video=trident:') + * example: + * video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,bpp=16,noaccel + */ +static int mxcfb_option_setup(struct platform_device *pdev) +{ + struct ipuv3_fb_platform_data *pdata = pdev->dev.platform_data; + char *options, *opt, *fb_mode_str = NULL; + char name[] = "mxcfb0"; + + name[5] += pdev->id; + fb_get_options(name, &options); + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "dev=", 4)) { + memcpy(pdata->disp_dev, opt + 4, strlen(opt) - 4); + pdata->disp_dev[strlen(opt) - 4] = '\0'; + continue; + } + if (!strncmp(opt, "if=", 3)) { + if (!strncmp(opt+3, "RGB24", 5)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB24; + continue; + } else if (!strncmp(opt+6, "BGR24", 5)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_BGR24; + continue; + } + if (!strncmp(opt+3, "GBR24", 5)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_GBR24; + continue; + } + if (!strncmp(opt+3, "RGB565", 6)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB565; + continue; + } + if (!strncmp(opt+3, "RGB666", 6)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_RGB666; + continue; + } + if (!strncmp(opt+3, "YUV444", 6)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_YUV444; + continue; + } + if (!strncmp(opt+3, "LVDS666", 7)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_LVDS666; + continue; + } + if (!strncmp(opt+3, "YUYV16", 6)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_YUYV; + continue; + } + if (!strncmp(opt+3, "UYVY16", 6)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_UYVY; + continue; + } + if (!strncmp(opt+3, "YVYU16", 6)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_YVYU; + continue; + } + if (!strncmp(opt+3, "VYUY16", 6)) { + pdata->interface_pix_fmt = IPU_PIX_FMT_VYUY; + continue; + } + } + if (!strncmp(opt, "int_clk", 7)) { + pdata->int_clk = true; + continue; + } + if (!strncmp(opt, "bpp=", 4)) + pdata->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + else + fb_mode_str = opt; + } + + if (fb_mode_str) + pdata->mode_str = fb_mode_str; + + return 0; +} + +static int mxcfb_register(struct fb_info *fbi) +{ + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct fb_videomode m; + int ret = 0; + char bg0_id[] = "DISP3 BG"; + char bg1_id[] = "DISP3 BG - DI1"; + char fg_id[] = "DISP3 FG"; + + if (mxcfbi->ipu_di == 0) { + bg0_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, bg0_id); + } else if (mxcfbi->ipu_di == 1) { + bg1_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, bg1_id); + } else { /* Overlay */ + fg_id[4] += mxcfbi->ipu_id; + strcpy(fbi->fix.id, fg_id); + } + + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering BG irq handler.\n"); + ret = -EBUSY; + goto err0; + } + ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq); + + if (mxcfbi->ipu_alp_ch_irq != -1) + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, + mxcfb_alpha_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering alpha irq " + "handler.\n"); + ret = -EBUSY; + goto err1; + } + + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + /*added first mode to fbi modelist*/ + if (!fbi->modelist.next || !fbi->modelist.prev) + INIT_LIST_HEAD(&fbi->modelist); + fb_var_to_videomode(&m, &fbi->var); + fb_add_videomode(&m, &fbi->modelist); + + fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + fbi->flags |= FBINFO_MISC_USEREVENT; + ret = fb_set_var(fbi, &fbi->var); + fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + if (ret < 0) + goto err2; + + if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { + console_lock(); + fb_blank(fbi, FB_BLANK_UNBLANK); + console_unlock(); + } + + ret = register_framebuffer(fbi); + if (ret < 0) + goto err2; + + return ret; +err2: + if (mxcfbi->ipu_alp_ch_irq != -1) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi); +err1: + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi); +err0: + return ret; +} + +static void mxcfb_unregister(struct fb_info *fbi) +{ + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + + if (mxcfbi->ipu_alp_ch_irq != -1) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi); + if (mxcfbi->ipu_ch_irq) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi); + + unregister_framebuffer(fbi); +} + +static int mxcfb_setup_overlay(struct platform_device *pdev, + struct fb_info *fbi_bg) +{ + struct fb_info *ovfbi; + struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par; + struct mxcfb_info *mxcfbi_fg; + int ret = 0; + + ovfbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!ovfbi) { + ret = -ENOMEM; + goto init_ovfbinfo_failed; + } + mxcfbi_fg = (struct mxcfb_info *)ovfbi->par; + + mxcfbi_fg->ipu = ipu_get_soc(mxcfbi_bg->ipu_id); + if (IS_ERR(mxcfbi_fg->ipu)) { + ret = -ENODEV; + goto get_ipu_failed; + } + mxcfbi_fg->ipu_id = mxcfbi_bg->ipu_id; + mxcfbi_fg->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF; + mxcfbi_fg->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + mxcfbi_fg->ipu_ch = MEM_FG_SYNC; + mxcfbi_fg->ipu_di = -1; + mxcfbi_fg->ipu_di_pix_fmt = mxcfbi_bg->ipu_di_pix_fmt; + mxcfbi_fg->overlay = true; + mxcfbi_fg->cur_blank = mxcfbi_fg->next_blank = FB_BLANK_POWERDOWN; + + /* Need dummy values until real panel is configured */ + ovfbi->var.xres = 240; + ovfbi->var.yres = 320; + + ret = mxcfb_register(ovfbi); + if (ret < 0) + goto register_ov_failed; + + mxcfbi_bg->ovfbi = ovfbi; + + return ret; + +register_ov_failed: +get_ipu_failed: + fb_dealloc_cmap(&ovfbi->cmap); + framebuffer_release(ovfbi); +init_ovfbinfo_failed: + return ret; +} + +static void mxcfb_unsetup_overlay(struct fb_info *fbi_bg) +{ + struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par; + struct fb_info *ovfbi = mxcfbi_bg->ovfbi; + + mxcfb_unregister(ovfbi); + + if (&ovfbi->cmap) + fb_dealloc_cmap(&ovfbi->cmap); + framebuffer_release(ovfbi); +} + +static bool ipu_usage[2][2]; +static int ipu_test_set_usage(int ipu, int di) +{ + if (ipu_usage[ipu][di]) + return -EBUSY; + else + ipu_usage[ipu][di] = true; + return 0; +} + +static void ipu_clear_usage(int ipu, int di) +{ + ipu_usage[ipu][di] = false; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data; + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct resource *res; + int ret = 0; + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto init_fbinfo_failed; + } + + mxcfb_option_setup(pdev); + + mxcfbi = (struct mxcfb_info *)fbi->par; + mxcfbi->ipu_int_clk = plat_data->int_clk; + ret = mxcfb_dispdrv_init(pdev, fbi); + if (ret < 0) + goto init_dispdrv_failed; + + ret = ipu_test_set_usage(mxcfbi->ipu_id, mxcfbi->ipu_di); + if (ret < 0) { + dev_err(&pdev->dev, "ipu%d-di%d already in use\n", + mxcfbi->ipu_id, mxcfbi->ipu_di); + goto ipu_in_busy; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res && res->end) { + fbi->fix.smem_len = res->end - res->start + 1; + fbi->fix.smem_start = res->start; + fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len); + } + + mxcfbi->ipu = ipu_get_soc(mxcfbi->ipu_id); + if (IS_ERR(mxcfbi->ipu)) { + ret = -ENODEV; + goto get_ipu_failed; + } + + /* first user uses DP with alpha feature */ + if (!g_dp_in_use[mxcfbi->ipu_id]) { + mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF; + mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + mxcfbi->ipu_ch = MEM_BG_SYNC; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK; + + ipu_disp_set_global_alpha(mxcfbi->ipu, mxcfbi->ipu_ch, true, 0x80); + ipu_disp_set_color_key(mxcfbi->ipu, mxcfbi->ipu_ch, false, 0); + + ret = mxcfb_register(fbi); + if (ret < 0) + goto mxcfb_register_failed; + + ret = mxcfb_setup_overlay(pdev, fbi); + if (ret < 0) { + mxcfb_unregister(fbi); + goto mxcfb_setupoverlay_failed; + } + + g_dp_in_use[mxcfbi->ipu_id] = true; + } else { + mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF; + mxcfbi->ipu_alp_ch_irq = -1; + mxcfbi->ipu_ch = MEM_DC_SYNC; + mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; + + ret = mxcfb_register(fbi); + if (ret < 0) + goto mxcfb_register_failed; + } + + platform_set_drvdata(pdev, fbi); + + ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property); + if (ret) + dev_err(&pdev->dev, "Error %d on creating file\n", ret); + +#ifdef CONFIG_LOGO + fb_prepare_logo(fbi, 0); + fb_show_logo(fbi, 0); +#endif + + return 0; + +mxcfb_setupoverlay_failed: +mxcfb_register_failed: +get_ipu_failed: + ipu_clear_usage(mxcfbi->ipu_id, mxcfbi->ipu_di); +ipu_in_busy: +init_dispdrv_failed: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); +init_fbinfo_failed: + return ret; +} + +static int mxcfb_remove(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = fbi->par; + + if (!fbi) + return 0; + + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxcfb_unregister(fbi); + mxcfb_unmap_video_memory(fbi); + + if (mxc_fbi->ovfbi) { + mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi); + mxcfb_unsetup_overlay(fbi); + mxcfb_unmap_video_memory(mxc_fbi->ovfbi); + } + + ipu_clear_usage(mxc_fbi->ipu_id, mxc_fbi->ipu_di); + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .remove = mxcfb_remove, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + return platform_driver_register(&mxcfb_driver); +} + +void mxcfb_exit(void) +{ + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxc_lcdif.c b/drivers/video/mxc/mxc_lcdif.c new file mode 100644 index 00000000000..d9d7fa306a8 --- /dev/null +++ b/drivers/video/mxc/mxc_lcdif.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mxcfb.h> +#include <linux/fsl_devices.h> +#include "mxc_dispdrv.h" + +struct mxc_lcdif_data { + struct platform_device *pdev; + struct mxc_dispdrv_entry *disp_lcdif; +}; + +#define DISPDRV_LCD "lcd" + +static struct fb_videomode lcdif_modedb[] = { + { + /* 800x480 @ 57 Hz , pixel clk @ 27MHz */ + "CLAA-WVGA", 57, 800, 480, 37037, 40, 60, 10, 10, 20, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 800x480 @ 60 Hz , pixel clk @ 32MHz */ + "SEIKO-WVGA", 60, 800, 480, 29850, 89, 164, 23, 10, 10, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, +}; +static int lcdif_modedb_sz = ARRAY_SIZE(lcdif_modedb); + +static int lcdif_init(struct mxc_dispdrv_entry *disp) +{ + int ret, i; + struct mxc_lcdif_data *lcdif = mxc_dispdrv_getdata(disp); + struct mxc_dispdrv_setting *setting = mxc_dispdrv_getsetting(disp); + struct fsl_mxc_lcd_platform_data *plat_data + = lcdif->pdev->dev.platform_data; + struct fb_videomode *modedb = lcdif_modedb; + int modedb_sz = lcdif_modedb_sz; + + /* use platform defined ipu/di */ + setting->dev_id = plat_data->ipu_id; + setting->disp_id = plat_data->disp_id; + + ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, + modedb, modedb_sz, NULL, setting->default_bpp); + if (!ret) { + fb_videomode_to_var(&setting->fbi->var, &modedb[0]); + setting->if_fmt = plat_data->default_ifmt; + } + + INIT_LIST_HEAD(&setting->fbi->modelist); + for (i = 0; i < modedb_sz; i++) { + struct fb_videomode m; + fb_var_to_videomode(&m, &setting->fbi->var); + if (fb_mode_is_equal(&m, &modedb[i])) { + fb_add_videomode(&modedb[i], + &setting->fbi->modelist); + break; + } + } + + return ret; +} + +void lcdif_deinit(struct mxc_dispdrv_entry *disp) +{ + /*TODO*/ +} + +static struct mxc_dispdrv_driver lcdif_drv = { + .name = DISPDRV_LCD, + .init = lcdif_init, + .deinit = lcdif_deinit, +}; + +static int mxc_lcdif_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_lcdif_data *lcdif; + + lcdif = kzalloc(sizeof(struct mxc_lcdif_data), GFP_KERNEL); + if (!lcdif) { + ret = -ENOMEM; + goto alloc_failed; + } + + lcdif->pdev = pdev; + lcdif->disp_lcdif = mxc_dispdrv_register(&lcdif_drv); + mxc_dispdrv_setdata(lcdif->disp_lcdif, lcdif); + + dev_set_drvdata(&pdev->dev, lcdif); + +alloc_failed: + return ret; +} + +static int mxc_lcdif_remove(struct platform_device *pdev) +{ + struct mxc_lcdif_data *lcdif = dev_get_drvdata(&pdev->dev); + + mxc_dispdrv_unregister(lcdif->disp_lcdif); + kfree(lcdif); + return 0; +} + +static struct platform_driver mxc_lcdif_driver = { + .driver = { + .name = "mxc_lcdif", + }, + .probe = mxc_lcdif_probe, + .remove = mxc_lcdif_remove, +}; + +static int __init mxc_lcdif_init(void) +{ + return platform_driver_register(&mxc_lcdif_driver); +} + +static void __exit mxc_lcdif_exit(void) +{ + platform_driver_unregister(&mxc_lcdif_driver); +} + +module_init(mxc_lcdif_init); +module_exit(mxc_lcdif_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX ipuv3 LCD extern port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_sii902x.c b/drivers/video/mxc/mxcfb_sii902x.c new file mode 100644 index 00000000000..17ce8fca68b --- /dev/null +++ b/drivers/video/mxc/mxcfb_sii902x.c @@ -0,0 +1,1307 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_sii902x.c + * + * @brief MXC Frame buffer driver for SII902x + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/fsl_devices.h> +#include <linux/interrupt.h> +#include <linux/uaccess.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include "mxc_edid.h" +#include "mxc_dispdrv.h" + +#define DISPDRV_SII "hdmi" + +#define TPI_PIX_CLK_LSB (0x00) +#define TPI_PIX_CLK_MSB (0x01) +#define TPI_VERT_FREQ_LSB (0x02) +#define TPI_VERT_FREQ_MSB (0x03) +#define TPI_TOTAL_PIX_LSB (0x04) +#define TPI_TOTAL_PIX_MSB (0x05) +#define TPI_TOTAL_LINES_LSB (0x06) +#define TPI_TOTAL_LINES_MSB (0x07) +#define TPI_PIX_REPETITION (0x08) +#define TPI_INPUT_FORMAT_REG (0x09) +#define TPI_OUTPUT_FORMAT_REG (0x0A) + +#define TPI_AVI_BYTE_0 (0x0C) +#define TPI_AVI_BYTE_1 (0x0D) +#define TPI_AVI_BYTE_2 (0x0E) +#define TPI_AVI_BYTE_3 (0x0F) +#define TPI_AVI_BYTE_4 (0x10) +#define TPI_AVI_BYTE_5 (0x11) + +#define TPI_END_TOP_BAR_LSB (0x12) +#define TPI_END_TOP_BAR_MSB (0x13) + +#define TPI_START_BTM_BAR_LSB (0x14) +#define TPI_START_BTM_BAR_MSB (0x15) + +#define TPI_END_LEFT_BAR_LSB (0x16) +#define TPI_END_LEFT_BAR_MSB (0x17) + +#define TPI_END_RIGHT_BAR_LSB (0x18) +#define TPI_END_RIGHT_BAR_MSB (0x19) + +#define TPI_SYSTEM_CONTROL_DATA_REG (0x1A) +#define TPI_DEVICE_ID (0x1B) +#define TPI_DEVICE_REV_ID (0x1C) +#define TPI_RESERVED2 (0x1D) +#define TPI_DEVICE_POWER_STATE_CTRL_REG (0x1E) + +#define TPI_I2S_EN (0x1F) +#define TPI_I2S_IN_CFG (0x20) +#define TPI_I2S_CHST_0 (0x21) +#define TPI_I2S_CHST_1 (0x22) +#define TPI_I2S_CHST_2 (0x23) +#define TPI_I2S_CHST_3 (0x24) +#define TPI_I2S_CHST_4 (0x25) + +#define TPI_AUDIO_HANDLING (0x25) +#define TPI_AUDIO_INTERFACE_REG (0x26) +#define TPI_AUDIO_SAMPLE_CTRL (0x27) + +#define TPI_INTERRUPT_ENABLE_REG (0x3C) +#define TPI_INTERRUPT_STATUS_REG (0x3D) + +#define TPI_INTERNAL_PAGE_REG 0xBC +#define TPI_INDEXED_OFFSET_REG 0xBD +#define TPI_INDEXED_VALUE_REG 0xBE + +#define MISC_INFO_FRAMES_CTRL (0xBF) +#define MISC_INFO_FRAMES_TYPE (0xC0) +#define EN_AND_RPT_AUDIO 0xC2 +#define DISABLE_AUDIO 0x02 + +#define TPI_ENABLE (0xC7) + +#define INDEXED_PAGE_0 0x01 +#define INDEXED_PAGE_1 0x02 +#define INDEXED_PAGE_2 0x03 + +#define HOT_PLUG_EVENT 0x01 +#define RX_SENSE_EVENT 0x02 +#define HOT_PLUG_STATE 0x04 +#define RX_SENSE_STATE 0x08 + +#define OUTPUT_MODE_MASK (0x01) +#define OUTPUT_MODE_DVI (0x00) +#define OUTPUT_MODE_HDMI (0x01) + +#define LINK_INTEGRITY_MODE_MASK 0x40 +#define LINK_INTEGRITY_STATIC (0x00) +#define LINK_INTEGRITY_DYNAMIC (0x40) + +#define TMDS_OUTPUT_CONTROL_MASK 0x10 +#define TMDS_OUTPUT_CONTROL_ACTIVE (0x00) +#define TMDS_OUTPUT_CONTROL_POWER_DOWN (0x10) + +#define AV_MUTE_MASK 0x08 +#define AV_MUTE_NORMAL (0x00) +#define AV_MUTE_MUTED (0x08) + +#define TX_POWER_STATE_MASK 0x3 +#define TX_POWER_STATE_D0 (0x00) +#define TX_POWER_STATE_D1 (0x01) +#define TX_POWER_STATE_D2 (0x02) +#define TX_POWER_STATE_D3 (0x03) + +#define AUDIO_MUTE_MASK 0x10 +#define AUDIO_MUTE_NORMAL (0x00) +#define AUDIO_MUTE_MUTED (0x10) + +#define AUDIO_SEL_MASK 0xC0 +#define AUD_IF_SPDIF 0x40 +#define AUD_IF_I2S 0x80 +#define AUD_IF_DSD 0xC0 +#define AUD_IF_HBR 0x04 + +#define REFER_TO_STREAM_HDR 0x00 + +#define AUD_PASS_BASIC 0x00 +#define AUD_PASS_ALL 0x01 +#define AUD_DOWN_SAMPLE 0x02 +#define AUD_DO_NOT_CHECK 0x03 + +#define BITS_IN_RGB 0x00 +#define BITS_IN_YCBCR444 0x01 +#define BITS_IN_YCBCR422 0x02 + +#define BITS_IN_AUTO_RANGE 0x00 +#define BITS_IN_FULL_RANGE 0x04 +#define BITS_IN_LTD_RANGE 0x08 + +#define BIT_EN_DITHER_10_8 0x40 +#define BIT_EXTENDED_MODE 0x80 + +#define SII_EDID_LEN 512 +#define SIZE_AVI_INFOFRAME 0x0E +#define SIZE_AUDIO_INFOFRAME 0x0F + +#define _4_To_3 0x10 +#define _16_To_9 0x20 +#define SAME_AS_AR 0x08 + +struct sii902x_data { + struct platform_device *pdev; + struct i2c_client *client; + struct mxc_dispdrv_entry *disp_hdmi; + struct regulator *io_reg; + struct regulator *analog_reg; + struct delayed_work det_work; + struct fb_info *fbi; + struct mxc_edid_cfg edid_cfg; + bool cable_plugin; + bool rx_powerup; + bool need_mode_change; + u8 edid[SII_EDID_LEN]; + struct notifier_block nb; + + u8 power_state; + u8 tpivmode[3]; + u8 pixrep; + + /* SII902x video setting: + * 1. hdmi video fmt: + * 0 = CEA-861 VIC; 1 = HDMI_VIC; 2 = 3D + * 2. vic: video mode index + * 3. aspect ratio: + * 4x3 or 16x9 + * 4. color space: + * 0 = RGB; 1 = YCbCr4:4:4; 2 = YCbCr4:2:2_16bits; + * 3 = YCbCr4:2:2_8bits;4 = xvYCC4:4:4 + * 5. color depth: + * 0 = 8bits; 1 = 10bits; 2 = 12bits; 3 = 16bits + * 6. colorimetry: + * 0 = 601; 1 = 709 + * 7. syncmode: + * 0 = external HS/VS/DE; 1 = external HS/VS and internal DE; + * 2 = embedded sync + */ +#define VMD_HDMIFORMAT_CEA_VIC 0x00 +#define VMD_HDMIFORMAT_HDMI_VIC 0x01 +#define VMD_HDMIFORMAT_3D 0x02 +#define VMD_HDMIFORMAT_PC 0x03 + u8 hdmi_vid_fmt; + u8 vic; +#define VMD_ASPECT_RATIO_4x3 0x01 +#define VMD_ASPECT_RATIO_16x9 0x02 + u8 aspect_ratio; +#define RGB 0 +#define YCBCR444 1 +#define YCBCR422_16BITS 2 +#define YCBCR422_8BITS 3 +#define XVYCC444 4 + u8 icolor_space; + u8 ocolor_space; +#define VMD_COLOR_DEPTH_8BIT 0x00 +#define VMD_COLOR_DEPTH_10BIT 0x01 +#define VMD_COLOR_DEPTH_12BIT 0x02 +#define VMD_COLOR_DEPTH_16BIT 0x03 + u8 color_depth; +#define COLORIMETRY_601 0 +#define COLORIMETRY_709 1 + u8 colorimetry; +#define EXTERNAL_HSVSDE 0 +#define INTERNAL_DE 1 +#define EMBEDDED_SYNC 2 + u8 syncmode; + u8 threeDstruct; + u8 threeDextdata; + +#define AMODE_I2S 0 +#define AMODE_SPDIF 1 +#define AMODE_HBR 2 +#define AMODE_DSD 3 + u8 audio_mode; +#define ACHANNEL_2CH 1 +#define ACHANNEL_3CH 2 +#define ACHANNEL_4CH 3 +#define ACHANNEL_5CH 4 +#define ACHANNEL_6CH 5 +#define ACHANNEL_7CH 6 +#define ACHANNEL_8CH 7 + u8 audio_channels; + u8 audiofs; + u8 audio_word_len; + u8 audio_i2s_fmt; +}; + +static __attribute__ ((unused)) void dump_regs(struct sii902x_data *sii902x, + u8 reg, int len) +{ + u8 buf[50]; + int i; + + i2c_smbus_read_i2c_block_data(sii902x->client, reg, len, buf); + for (i = 0; i < len; i++) + dev_dbg(&sii902x->client->dev, "reg[0x%02X]: 0x%02X\n", + i+reg, buf[i]); +} + +static ssize_t sii902x_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + + strcpy(buf, sii902x->fbi->fix.id); + sprintf(buf+strlen(buf), "\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(fb_name, S_IRUGO, sii902x_show_name, NULL); + +static ssize_t sii902x_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + + if (sii902x->cable_plugin == false) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, sii902x_show_state, NULL); + +static ssize_t sii902x_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sii902x_data *sii902x = dev_get_drvdata(dev); + int i, j, len = 0; + + for (j = 0; j < SII_EDID_LEN/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + sii902x->edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, sii902x_show_edid, NULL); + +/*------------------------------------------------------------------------------ + * Function Description: Write "0" to all bits in TPI offset "Offset" that are set + * to "1" in "Pattern"; Leave all other bits in "Offset" + * unchanged. + *----------------------------------------------------------------------------- + */ +void read_clr_write_tpi(struct i2c_client *client, u8 offset, u8 mask) +{ + u8 tmp; + + tmp = i2c_smbus_read_byte_data(client, offset); + tmp &= ~mask; + i2c_smbus_write_byte_data(client, offset, tmp); +} + +/*------------------------------------------------------------------------------ + * Function Description: Write "1" to all bits in TPI offset "Offset" that are set + * to "1" in "Pattern"; Leave all other bits in "Offset" + * unchanged. + *----------------------------------------------------------------------------- + */ +void read_set_write_tpi(struct i2c_client *client, u8 offset, u8 mask) +{ + u8 tmp; + + tmp = i2c_smbus_read_byte_data(client, offset); + tmp |= mask; + i2c_smbus_write_byte_data(client, offset, tmp); +} + +/*------------------------------------------------------------------------------ + * Function Description: Write "Value" to all bits in TPI offset "Offset" that are set + * to "1" in "Mask"; Leave all other bits in "Offset" + * unchanged. + *---------------------------------------------------------------------------- + */ +void read_modify_tpi(struct i2c_client *client, u8 offset, u8 mask, u8 value) +{ + u8 tmp; + + tmp = i2c_smbus_read_byte_data(client, offset); + tmp &= ~mask; + tmp |= (value & mask); + i2c_smbus_write_byte_data(client, offset, tmp); +} + +/*------------------------------------------------------------------------------ + * Function Description: Read an indexed register value + * Write: + * 1. 0xBC => Internal page num + * 2. 0xBD => Indexed register offset + * Read: + * 3. 0xBE => Returns the indexed register value + *---------------------------------------------------------------------------- + */ +int read_idx_reg(struct i2c_client *client, u8 page, u8 regoffset) +{ + i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page); + i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset); + return i2c_smbus_read_byte_data(client, TPI_INDEXED_VALUE_REG); +} + +/*------------------------------------------------------------------------------ + * Function Description: Write a value to an indexed register + * + * Write: + * 1. 0xBC => Internal page num + * 2. 0xBD => Indexed register offset + * 3. 0xBE => Set the indexed register value + *------------------------------------------------------------------------------ + */ +void write_idx_reg(struct i2c_client *client, u8 page, u8 regoffset, u8 regval) +{ + i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page); + i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset); + i2c_smbus_write_byte_data(client, TPI_INDEXED_VALUE_REG, regval); +} + +/*------------------------------------------------------------------------------ + * Function Description: Write "Value" to all bits in TPI offset "Offset" that are set + * to "1" in "Mask"; Leave all other bits in "Offset" + * unchanged. + *---------------------------------------------------------------------------- + */ +void read_modify_idx_reg(struct i2c_client *client, u8 page, u8 regoffset, u8 mask, u8 value) +{ + u8 tmp; + + i2c_smbus_write_byte_data(client, TPI_INTERNAL_PAGE_REG, page); + i2c_smbus_write_byte_data(client, TPI_INDEXED_OFFSET_REG, regoffset); + tmp = i2c_smbus_read_byte_data(client, TPI_INDEXED_VALUE_REG); + tmp &= ~mask; + tmp |= (value & mask); + i2c_smbus_write_byte_data(client, TPI_INDEXED_VALUE_REG, tmp); +} + +static void sii902x_set_powerstate(struct sii902x_data *sii902x, u8 state) +{ + if (sii902x->power_state != state) { + read_modify_tpi(sii902x->client, TPI_DEVICE_POWER_STATE_CTRL_REG, + TX_POWER_STATE_MASK, state); + sii902x->power_state = state; + } +} + +static void sii902x_setAVI(struct sii902x_data *sii902x) +{ + u8 avi_data[SIZE_AVI_INFOFRAME]; + u8 tmp; + int i; + + dev_dbg(&sii902x->client->dev, "set AVI frame\n"); + + memset(avi_data, 0, SIZE_AVI_INFOFRAME); + + if (sii902x->edid_cfg.cea_ycbcr444) + tmp = 2; + else if (sii902x->edid_cfg.cea_ycbcr422) + tmp = 1; + else + tmp = 0; + + /* AVI byte1: Y1Y0 (output format) */ + avi_data[1] = (tmp << 5) & 0x60; + /* A0 = 1; Active format identification data is present in the AVI InfoFrame. + * S1:S0 = 00; + */ + avi_data[1] |= 0x10; + + if (sii902x->ocolor_space == XVYCC444) { + avi_data[2] = 0xC0; + if (sii902x->colorimetry == COLORIMETRY_601) + avi_data[3] &= ~0x70; + else if (sii902x->colorimetry == COLORIMETRY_709) + avi_data[3] = (avi_data[3] & ~0x70) | 0x10; + } else if (sii902x->ocolor_space != RGB) { + if (sii902x->colorimetry == COLORIMETRY_709) + avi_data[2] = 0x80;/* AVI byte2: C1C0*/ + else if (sii902x->colorimetry == COLORIMETRY_601) + avi_data[2] = 0x40;/* AVI byte2: C1C0 */ + } else {/* Carries no data */ + /* AVI Byte2: C1C0 */ + avi_data[2] &= ~0xc0; /* colorimetry = 0 */ + avi_data[3] &= ~0x70; /* Extended colorimetry = 0 */ + } + + avi_data[4] = sii902x->vic; + + /* Set the Aspect Ration info into the Infoframe Byte 2 */ + if (sii902x->aspect_ratio == VMD_ASPECT_RATIO_16x9) + avi_data[2] |= _16_To_9; /* AVI Byte2: M1M0 */ + else + avi_data[2] |= _4_To_3; + + avi_data[2] |= SAME_AS_AR; /* AVI Byte2: R3..R1 - Set to "Same as Picture Aspect Ratio" */ + avi_data[5] = sii902x->pixrep; /* AVI Byte5: Pixel Replication - PR3..PR0 */ + + /* Calculate AVI InfoFrame ChecKsum */ + avi_data[0] = 0x82 + 0x02 + 0x0D; + for (i = 1; i < SIZE_AVI_INFOFRAME; i++) + avi_data[0] += avi_data[i]; + avi_data[0] = 0x100 - avi_data[0]; + + /* Write the Inforframe data to the TPI Infoframe registers */ + for (i = 0; i < SIZE_AVI_INFOFRAME; i++) + i2c_smbus_write_byte_data(sii902x->client, + TPI_AVI_BYTE_0 + i, avi_data[i]); + + dump_regs(sii902x, TPI_AVI_BYTE_0, SIZE_AVI_INFOFRAME); +} + +#define TYPE_AUDIO_INFOFRAMES 0x84 +#define AUDIO_INFOFRAMES_VERSION 0x01 +#define AUDIO_INFOFRAMES_LENGTH 0x0A +/*------------------------------------------------------------------------------ +* Function Description: Load Audio InfoFrame data into registers and send to sink +* +* Accepts: (1) Channel count +* (2) speaker configuration per CEA-861D Tables 19, 20 +* (3) Coding type: 0x09 for DSD Audio. 0 (refer to stream header) for all the rest +* (4) Sample Frequency. Non zero for HBR only +* (5) Audio Sample Length. Non zero for HBR only. +*------------------------------------------------------------------------------ +*/ +static void sii902x_setAIF(struct sii902x_data *sii902x, + u8 codingtype, u8 sample_size, u8 sample_freq, + u8 speaker_cfg) +{ + u8 aif_data[SIZE_AUDIO_INFOFRAME]; + u8 channel_count = sii902x->audio_channels & 0x07; + int i; + + dev_dbg(&sii902x->client->dev, "set AIF frame\n"); + + memset(aif_data, 0, SIZE_AUDIO_INFOFRAME); + + /* Disbale MPEG/Vendor Specific InfoFrames */ + i2c_smbus_write_byte_data(sii902x->client, MISC_INFO_FRAMES_CTRL, DISABLE_AUDIO); + + aif_data[0] = TYPE_AUDIO_INFOFRAMES; + aif_data[1] = AUDIO_INFOFRAMES_VERSION; + aif_data[2] = AUDIO_INFOFRAMES_LENGTH; + /* Calculate checksum - 0x84 + 0x01 + 0x0A */ + aif_data[3] = TYPE_AUDIO_INFOFRAMES + + AUDIO_INFOFRAMES_VERSION + AUDIO_INFOFRAMES_LENGTH; + + aif_data[4] = channel_count; /* 0 for "Refer to Stream Header" or for 2 Channels. 0x07 for 8 Channels*/ + aif_data[4] |= (codingtype << 4); /* 0xC7[7:4] == 0b1001 for DSD Audio */ + aif_data[5] = ((sample_freq & 0x07) << 2) | (sample_size & 0x03); + aif_data[7] = speaker_cfg; + + for (i = 4; i < SIZE_AUDIO_INFOFRAME; i++) + aif_data[3] += aif_data[i]; + + aif_data[3] = 0x100 - aif_data[3]; + + /* Re-enable Audio InfoFrame transmission and repeat */ + i2c_smbus_write_byte_data(sii902x->client, MISC_INFO_FRAMES_CTRL, EN_AND_RPT_AUDIO); + + for (i = 0; i < SIZE_AUDIO_INFOFRAME; i++) + i2c_smbus_write_byte_data(sii902x->client, + MISC_INFO_FRAMES_TYPE + i, aif_data[i]); + + dump_regs(sii902x, MISC_INFO_FRAMES_TYPE, SIZE_AUDIO_INFOFRAME); +} + +static void sii902x_setaudio(struct sii902x_data *sii902x) +{ + dev_dbg(&sii902x->client->dev, "set audio\n"); + + /* mute audio */ + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, + AUDIO_MUTE_MASK, AUDIO_MUTE_MUTED); + if (sii902x->audio_mode == AMODE_I2S) { + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, + AUDIO_SEL_MASK, AUD_IF_I2S); + i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_HANDLING, + 0x08 | AUD_DO_NOT_CHECK); + } else { + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, + AUDIO_SEL_MASK, AUD_IF_SPDIF); + i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_HANDLING, + AUD_PASS_BASIC); + } + + if (sii902x->audio_channels == ACHANNEL_2CH) + read_clr_write_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, 0x20); + else + read_set_write_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, 0x20); + + if (sii902x->audio_mode == AMODE_I2S) { + /* I2S - Map channels */ + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0x80); + + if (sii902x->audio_channels > ACHANNEL_2CH) + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0x91); + + if (sii902x->audio_channels > ACHANNEL_4CH) + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0xA2); + + if (sii902x->audio_channels > ACHANNEL_6CH) + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_EN, 0xB3); + + /* I2S - Stream Header Settings */ + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_0, 0x00); + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_1, 0x00); + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_2, 0x00); + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_3, sii902x->audiofs); + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_CHST_4, + (sii902x->audiofs << 4) | sii902x->audio_word_len); + + /* added for 16bit auido noise issue */ + write_idx_reg(sii902x->client, INDEXED_PAGE_1, 0x24, sii902x->audio_word_len); + + /* I2S - Input Configuration */ + i2c_smbus_write_byte_data(sii902x->client, TPI_I2S_IN_CFG, sii902x->audio_i2s_fmt); + } + + i2c_smbus_write_byte_data(sii902x->client, TPI_AUDIO_SAMPLE_CTRL, REFER_TO_STREAM_HDR); + + sii902x_setAIF(sii902x, REFER_TO_STREAM_HDR, REFER_TO_STREAM_HDR, REFER_TO_STREAM_HDR, 0x00); + + /* unmute audio */ + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, AUDIO_MUTE_MASK, AUDIO_MUTE_NORMAL); +} + +static void sii902x_setup(struct sii902x_data *sii902x, struct fb_info *fbi) +{ + u16 data[4]; + u32 refresh; + u8 *tmp; + mm_segment_t old_fs; + unsigned int fmt; + int i; + + dev_dbg(&sii902x->client->dev, "setup..\n"); + + sii902x->vic = mxc_edid_var_to_vic(&fbi->var); + + /* set TPI video mode */ + data[0] = PICOS2KHZ(fbi->var.pixclock) / 10; + data[2] = fbi->var.hsync_len + fbi->var.left_margin + + fbi->var.xres + fbi->var.right_margin; + data[3] = fbi->var.vsync_len + fbi->var.upper_margin + + fbi->var.yres + fbi->var.lower_margin; + refresh = data[2] * data[3]; + refresh = (PICOS2KHZ(fbi->var.pixclock) * 1000) / refresh; + data[1] = refresh * 100; + tmp = (u8 *)data; + for (i = 0; i < 8; i++) + i2c_smbus_write_byte_data(sii902x->client, i, tmp[i]); + + dump_regs(sii902x, 0, 8); + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_GET_DIFMT, (unsigned long)&fmt); + set_fs(old_fs); + if (fmt == IPU_PIX_FMT_VYU444) { + sii902x->icolor_space = YCBCR444; + dev_dbg(&sii902x->client->dev, "input color space YUV\n"); + } else { + sii902x->icolor_space = RGB; + dev_dbg(&sii902x->client->dev, "input color space RGB\n"); + } + } + + /* reg 0x08: input bus/pixel: full pixel wide (24bit), rising edge */ + sii902x->tpivmode[0] = 0x70; + /* reg 0x09: Set input format */ + if (sii902x->icolor_space == RGB) + sii902x->tpivmode[1] = + (((BITS_IN_RGB | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE); + else if (sii902x->icolor_space == YCBCR444) + sii902x->tpivmode[1] = + (((BITS_IN_YCBCR444 | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE); + else if ((sii902x->icolor_space == YCBCR422_16BITS) || (sii902x->icolor_space == YCBCR422_8BITS)) + sii902x->tpivmode[1] = + (((BITS_IN_YCBCR422 | BITS_IN_AUTO_RANGE) & ~BIT_EN_DITHER_10_8) & ~BIT_EXTENDED_MODE); + /* reg 0x0a: set output format to RGB */ + sii902x->tpivmode[2] = 0x00; + + if (fbi->var.xres/16 == fbi->var.yres/9) + sii902x->aspect_ratio = VMD_ASPECT_RATIO_16x9; + else + sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3; + + if ((sii902x->vic == 6) || (sii902x->vic == 7) || + (sii902x->vic == 21) || (sii902x->vic == 22) || + (sii902x->vic == 2) || (sii902x->vic == 3) || + (sii902x->vic == 17) || (sii902x->vic == 18)) { + sii902x->tpivmode[2] &= ~0x10; /*BT.601*/ + sii902x->colorimetry = COLORIMETRY_601; + sii902x->aspect_ratio = VMD_ASPECT_RATIO_4x3; + } else { + sii902x->tpivmode[2] |= 0x10; /*BT.709*/ + sii902x->colorimetry = COLORIMETRY_709; + } + + if ((sii902x->vic == 10) || (sii902x->vic == 11) || + (sii902x->vic == 12) || (sii902x->vic == 13) || + (sii902x->vic == 14) || (sii902x->vic == 15) || + (sii902x->vic == 25) || (sii902x->vic == 26) || + (sii902x->vic == 27) || (sii902x->vic == 28) || + (sii902x->vic == 29) || (sii902x->vic == 30) || + (sii902x->vic == 35) || (sii902x->vic == 36) || + (sii902x->vic == 37) || (sii902x->vic == 38)) + sii902x->pixrep = 1; + else + sii902x->pixrep = 0; + + dev_dbg(&sii902x->client->dev, "vic %d\n", sii902x->vic); + dev_dbg(&sii902x->client->dev, "pixrep %d\n", sii902x->pixrep); + if (sii902x->aspect_ratio == VMD_ASPECT_RATIO_4x3) { + dev_dbg(&sii902x->client->dev, "aspect 4:3\n"); + } else { + dev_dbg(&sii902x->client->dev, "aspect 16:9\n"); + } + if (sii902x->colorimetry == COLORIMETRY_601) { + dev_dbg(&sii902x->client->dev, "COLORIMETRY_601\n"); + } else { + dev_dbg(&sii902x->client->dev, "COLORIMETRY_709\n"); + } + dev_dbg(&sii902x->client->dev, "hdmi capbility %d\n", sii902x->edid_cfg.hdmi_cap); + + sii902x->ocolor_space = RGB; + if (sii902x->edid_cfg.hdmi_cap) { + if (sii902x->edid_cfg.cea_ycbcr444) { + sii902x->ocolor_space = YCBCR444; + sii902x->tpivmode[2] |= 0x1; /*Ycbcr444*/ + } else if (sii902x->edid_cfg.cea_ycbcr422) { + sii902x->ocolor_space = YCBCR422_8BITS; + sii902x->tpivmode[2] |= 0x2; /*Ycbcr422*/ + } + } + + dev_dbg(&sii902x->client->dev, "write reg 0x08 0X%2X\n", sii902x->tpivmode[0]); + dev_dbg(&sii902x->client->dev, "write reg 0x09 0X%2X\n", sii902x->tpivmode[1]); + dev_dbg(&sii902x->client->dev, "write reg 0x0a 0X%2X\n", sii902x->tpivmode[2]); + + i2c_smbus_write_byte_data(sii902x->client, TPI_PIX_REPETITION, sii902x->tpivmode[0]); + i2c_smbus_write_byte_data(sii902x->client, TPI_INPUT_FORMAT_REG, sii902x->tpivmode[1]); + i2c_smbus_write_byte_data(sii902x->client, TPI_OUTPUT_FORMAT_REG, sii902x->tpivmode[2]); + + /* goto state D0*/ + sii902x_set_powerstate(sii902x, TX_POWER_STATE_D0); + + if (sii902x->edid_cfg.hdmi_cap) { + sii902x_setAVI(sii902x); + sii902x_setaudio(sii902x); + } else { + /* set last byte of TPI AVI InfoFrame for TPI AVI I/O format to take effect ?? */ + i2c_smbus_write_byte_data(sii902x->client, TPI_END_RIGHT_BAR_MSB, 0x00); + + /* mute audio */ + read_modify_tpi(sii902x->client, TPI_AUDIO_INTERFACE_REG, + AUDIO_MUTE_MASK, AUDIO_MUTE_MUTED); + } +} + +#ifdef CONFIG_FB_MODE_HELPERS +static int sii902x_read_edid(struct sii902x_data *sii902x, + struct fb_info *fbi) +{ + int old, dat, ret, cnt = 100; + unsigned short addr = 0x50; + u8 edid_old[SII_EDID_LEN]; + + old = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG); + + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old | 0x4); + do { + cnt--; + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG); + } while ((!(dat & 0x2)) && cnt); + + if (!cnt) { + ret = -1; + goto done; + } + + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old | 0x06); + + /* save old edid */ + memcpy(edid_old, sii902x->edid, SII_EDID_LEN); + + /* edid reading */ + ret = mxc_edid_read(sii902x->client->adapter, addr, + sii902x->edid, &sii902x->edid_cfg, fbi); + + cnt = 100; + do { + cnt--; + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old & ~0x6); + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG); + } while ((dat & 0x6) && cnt); + + if (!cnt) + ret = -1; + +done: + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old); + + if (!memcmp(edid_old, sii902x->edid, SII_EDID_LEN)) + ret = -2; + return ret; +} +#else +static int sii902x_read_edid(struct sii902x_data *sii902x, + struct fb_info *fbi) +{ + return -1; +} +#endif + +static void sii902x_enable_tmds(struct sii902x_data *sii902x) +{ + /* goto state D0*/ + sii902x_set_powerstate(sii902x, TX_POWER_STATE_D0); + + /* Turn on DVI or HDMI */ + if (sii902x->edid_cfg.hdmi_cap) + read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, + OUTPUT_MODE_MASK, OUTPUT_MODE_HDMI); + else + read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, + OUTPUT_MODE_MASK, OUTPUT_MODE_DVI); + + read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, + LINK_INTEGRITY_MODE_MASK | TMDS_OUTPUT_CONTROL_MASK | AV_MUTE_MASK, + LINK_INTEGRITY_DYNAMIC | TMDS_OUTPUT_CONTROL_ACTIVE | AV_MUTE_NORMAL); + + i2c_smbus_write_byte_data(sii902x->client, TPI_PIX_REPETITION, + sii902x->tpivmode[0]); +} + +static void sii902x_disable_tmds(struct sii902x_data *sii902x) +{ + read_modify_tpi(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, + TMDS_OUTPUT_CONTROL_MASK | AV_MUTE_MASK | OUTPUT_MODE_MASK, + TMDS_OUTPUT_CONTROL_POWER_DOWN | AV_MUTE_MUTED | OUTPUT_MODE_DVI); + + /* goto state D2*/ + sii902x_set_powerstate(sii902x, TX_POWER_STATE_D2); +} + +static void sii902x_poweron(struct sii902x_data *sii902x) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data; + + dev_dbg(&sii902x->client->dev, "power on\n"); + + /* Enable pins to HDMI */ + if (plat->enable_pins) + plat->enable_pins(); + + if (sii902x->rx_powerup) + sii902x_enable_tmds(sii902x); +} + +static void sii902x_poweroff(struct sii902x_data *sii902x) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data; + + dev_dbg(&sii902x->client->dev, "power off\n"); + + /* Disable pins to HDMI */ + if (plat->disable_pins) + plat->disable_pins(); + + if (sii902x->rx_powerup) + sii902x_disable_tmds(sii902x); +} + +static void sii902x_rx_powerup(struct sii902x_data *sii902x) +{ + + dev_dbg(&sii902x->client->dev, "rx power up\n"); + + if (sii902x->need_mode_change) { + sii902x->fbi->var.activate |= FB_ACTIVATE_FORCE; + console_lock(); + sii902x->fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(sii902x->fbi, &sii902x->fbi->var); + sii902x->fbi->flags &= ~FBINFO_MISC_USEREVENT; + console_unlock(); + sii902x->need_mode_change = false; + } + + sii902x_enable_tmds(sii902x); + + sii902x->rx_powerup = true; +} + +static void sii902x_rx_powerdown(struct sii902x_data *sii902x) +{ + dev_dbg(&sii902x->client->dev, "rx power down\n"); + + sii902x_disable_tmds(sii902x); + + sii902x->rx_powerup = false; +} + +static int sii902x_cable_connected(struct sii902x_data *sii902x) +{ + int ret; + + dev_dbg(&sii902x->client->dev, "cable connected\n"); + + sii902x->cable_plugin = true; + + /* edid read */ + ret = sii902x_read_edid(sii902x, sii902x->fbi); + if (ret == -1) + dev_err(&sii902x->client->dev, + "read edid fail\n"); + else if (ret == -2) + dev_info(&sii902x->client->dev, + "same edid\n"); + else { + if (sii902x->fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + fb_destroy_modelist(&sii902x->fbi->modelist); + + for (i = 0; i < sii902x->fbi->monspecs.modedb_len; i++) { + /*FIXME now we do not support interlaced mode */ + if (!(sii902x->fbi->monspecs.modedb[i].vmode & FB_VMODE_INTERLACED)) + fb_add_videomode(&sii902x->fbi->monspecs.modedb[i], + &sii902x->fbi->modelist); + } + + fb_var_to_videomode(&m, &sii902x->fbi->var); + mode = fb_find_nearest_mode(&m, + &sii902x->fbi->modelist); + + fb_videomode_to_var(&sii902x->fbi->var, mode); + sii902x->need_mode_change = true; + } + } + + /* ?? remain it for control back door register */ + read_modify_idx_reg(sii902x->client, INDEXED_PAGE_0, 0x0a, 0x08, 0x08); + + return 0; +} + +static void sii902x_cable_disconnected(struct sii902x_data *sii902x) +{ + dev_dbg(&sii902x->client->dev, "cable disconnected\n"); + sii902x_rx_powerdown(sii902x); + sii902x->cable_plugin = false; +} + +static void det_worker(struct work_struct *work) +{ + struct delayed_work *delay_work = to_delayed_work(work); + struct sii902x_data *sii902x = + container_of(delay_work, struct sii902x_data, det_work); + int status; + char event_string[16]; + char *envp[] = { event_string, NULL }; + + status = i2c_smbus_read_byte_data(sii902x->client, TPI_INTERRUPT_STATUS_REG); + + /* check cable status */ + if (status & HOT_PLUG_EVENT) { + /* cable connection changes */ + if ((status & HOT_PLUG_STATE) != sii902x->cable_plugin) { + if (status & HOT_PLUG_STATE) { + sprintf(event_string, "EVENT=plugin"); + sii902x_cable_connected(sii902x); + } else { + sprintf(event_string, "EVENT=plugout"); + sii902x_cable_disconnected(sii902x); + } + kobject_uevent_env(&sii902x->pdev->dev.kobj, KOBJ_CHANGE, envp); + } + } + + /* check rx power */ + if (((status & RX_SENSE_STATE) >> 3) != sii902x->rx_powerup) { + if (sii902x->cable_plugin) { + if (status & RX_SENSE_STATE) + sii902x_rx_powerup(sii902x); + else + sii902x_rx_powerdown(sii902x); + } + } + + /* clear interrupt pending status */ + i2c_smbus_write_byte_data(sii902x->client, TPI_INTERRUPT_STATUS_REG, status); +} + +static irqreturn_t sii902x_detect_handler(int irq, void *data) +{ + struct sii902x_data *sii902x = data; + + schedule_delayed_work(&(sii902x->det_work), msecs_to_jiffies(20)); + + return IRQ_HANDLED; +} + +static int sii902x_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + struct sii902x_data *sii902x = container_of(nb, struct sii902x_data, nb); + + if (strcmp(event->info->fix.id, sii902x->fbi->fix.id)) + return 0; + + switch (val) { + case FB_EVENT_MODE_CHANGE: + sii902x_setup(sii902x, fbi); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) + sii902x_poweron(sii902x); + else + sii902x_poweroff(sii902x); + break; + } + return 0; +} + +static int sii902x_TPI_init(struct i2c_client *client) +{ + struct fsl_mxc_lcd_platform_data *plat = client->dev.platform_data; + u8 devid = 0; + u16 wid = 0; + + if (plat->reset) + plat->reset(); + + /* sii902x back door register - Set terminations to default */ + i2c_smbus_write_byte_data(client, 0x82, 0x25); + /* sii902x back door register - HW debounce to 64ms (0x14) */ + i2c_smbus_write_byte_data(client, 0x7c, 0x14); + + /* Set 902x in hardware TPI mode on and jump out of D3 state */ + if (i2c_smbus_write_byte_data(client, TPI_ENABLE, 0x00) < 0) { + dev_err(&client->dev, + "cound not find device\n"); + return -ENODEV; + } + + msleep(100); + + /* read device ID */ + devid = read_idx_reg(client, INDEXED_PAGE_0, 0x03); + wid = devid; + wid <<= 8; + devid = read_idx_reg(client, INDEXED_PAGE_0, 0x02); + wid |= devid; + devid = i2c_smbus_read_byte_data(client, TPI_DEVICE_ID); + + if (devid == 0xB0) + dev_info(&client->dev, "found device %04X", wid); + else { + dev_err(&client->dev, "cound not find device\n"); + return -ENODEV; + } + + return 0; +} + +static int sii902x_disp_init(struct mxc_dispdrv_entry *disp) +{ + int ret = 0; + struct sii902x_data *sii902x = mxc_dispdrv_getdata(disp); + struct mxc_dispdrv_setting *setting = mxc_dispdrv_getsetting(disp); + struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data; + bool found = false; + static bool inited; + + if (inited) + return -EBUSY; + + inited = true; + + setting->dev_id = plat->ipu_id; + setting->disp_id = plat->disp_id; + setting->if_fmt = IPU_PIX_FMT_RGB24; + + sii902x->fbi = setting->fbi; + sii902x->power_state = TX_POWER_STATE_D2; + sii902x->icolor_space = RGB; + sii902x->audio_mode = AMODE_SPDIF; + sii902x->audio_channels = ACHANNEL_2CH; + + sii902x->pdev = platform_device_register_simple("sii902x", 0, NULL, 0); + if (IS_ERR(sii902x->pdev)) { + dev_err(&sii902x->client->dev, + "Unable to register Sii902x as a platform device\n"); + ret = PTR_ERR(sii902x->pdev); + goto register_pltdev_failed; + } + + if (plat->io_reg) { + sii902x->io_reg = regulator_get(&sii902x->client->dev, plat->io_reg); + if (!IS_ERR(sii902x->io_reg)) { + regulator_set_voltage(sii902x->io_reg, 3300000, 3300000); + regulator_enable(sii902x->io_reg); + } + } + if (plat->analog_reg) { + sii902x->analog_reg = regulator_get(&sii902x->client->dev, plat->analog_reg); + if (!IS_ERR(sii902x->analog_reg)) { + regulator_set_voltage(sii902x->analog_reg, 1300000, 1300000); + regulator_enable(sii902x->analog_reg); + } + } + + /* Claim HDMI pins */ + if (plat->get_pins) + if (!plat->get_pins()) { + ret = -EACCES; + goto get_pins_failed; + } + + ret = sii902x_TPI_init(sii902x->client); + if (ret < 0) + goto init_failed; + + /* try to read edid */ + ret = sii902x_read_edid(sii902x, sii902x->fbi); + if (ret < 0) + dev_warn(&sii902x->client->dev, "Can not read edid\n"); + else { + INIT_LIST_HEAD(&sii902x->fbi->modelist); + if (sii902x->fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + for (i = 0; i < sii902x->fbi->monspecs.modedb_len; i++) { + /*FIXME now we do not support interlaced mode */ + if (!(sii902x->fbi->monspecs.modedb[i].vmode + & FB_VMODE_INTERLACED)) + fb_add_videomode( + &sii902x->fbi->monspecs.modedb[i], + &sii902x->fbi->modelist); + } + + fb_find_mode(&sii902x->fbi->var, sii902x->fbi, setting->dft_mode_str, + NULL, 0, NULL, setting->default_bpp); + + fb_var_to_videomode(&m, &sii902x->fbi->var); + mode = fb_find_nearest_mode(&m, + &sii902x->fbi->modelist); + fb_videomode_to_var(&sii902x->fbi->var, mode); + found = true; + } + + } + + if (!found) { + ret = fb_find_mode(&sii902x->fbi->var, sii902x->fbi, setting->dft_mode_str, + NULL, 0, NULL, setting->default_bpp); + if (!ret) { + ret = -EINVAL; + goto find_mode_failed; + } + } + + if (sii902x->client->irq) { + ret = request_irq(sii902x->client->irq, sii902x_detect_handler, + IRQF_TRIGGER_FALLING, + "SII902x_det", sii902x); + if (ret < 0) + dev_warn(&sii902x->client->dev, + "cound not request det irq %d\n", + sii902x->client->irq); + else { + /*enable cable hot plug irq*/ + i2c_smbus_write_byte_data(sii902x->client, + TPI_INTERRUPT_ENABLE_REG, + HOT_PLUG_EVENT | RX_SENSE_EVENT); + INIT_DELAYED_WORK(&(sii902x->det_work), det_worker); + /*clear hot plug event status*/ + i2c_smbus_write_byte_data(sii902x->client, + TPI_INTERRUPT_STATUS_REG, + HOT_PLUG_EVENT | RX_SENSE_EVENT); + } + + ret = device_create_file(&sii902x->pdev->dev, &dev_attr_fb_name); + if (ret < 0) + dev_warn(&sii902x->client->dev, + "cound not create sys node for fb name\n"); + ret = device_create_file(&sii902x->pdev->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&sii902x->client->dev, + "cound not create sys node for cable state\n"); + ret = device_create_file(&sii902x->pdev->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&sii902x->client->dev, + "cound not create sys node for edid\n"); + + dev_set_drvdata(&sii902x->pdev->dev, sii902x); + } + + sii902x->nb.notifier_call = sii902x_fb_event; + ret = fb_register_client(&sii902x->nb); + if (ret < 0) + goto reg_fbclient_failed; + + return ret; + +reg_fbclient_failed: +find_mode_failed: +init_failed: +get_pins_failed: + platform_device_unregister(sii902x->pdev); +register_pltdev_failed: + return ret; +} + +static void sii902x_disp_deinit(struct mxc_dispdrv_entry *disp) +{ + struct sii902x_data *sii902x = mxc_dispdrv_getdata(disp); + struct fsl_mxc_lcd_platform_data *plat = sii902x->client->dev.platform_data; + + if (sii902x->client->irq) + free_irq(sii902x->client->irq, sii902x); + + fb_unregister_client(&sii902x->nb); + + sii902x_poweroff(sii902x); + + /* Release HDMI pins */ + if (plat->put_pins) + plat->put_pins(); + + platform_device_unregister(sii902x->pdev); + + kfree(sii902x); +} + +static struct mxc_dispdrv_driver sii902x_drv = { + .name = DISPDRV_SII, + .init = sii902x_disp_init, + .deinit = sii902x_disp_deinit, +}; + +static int __devinit sii902x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sii902x_data *sii902x; + int ret = 0; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -ENODEV; + + sii902x = kzalloc(sizeof(struct sii902x_data), GFP_KERNEL); + if (!sii902x) { + ret = -ENOMEM; + goto alloc_failed; + } + + sii902x->client = client; + + sii902x->disp_hdmi = mxc_dispdrv_register(&sii902x_drv); + mxc_dispdrv_setdata(sii902x->disp_hdmi, sii902x); + + i2c_set_clientdata(client, sii902x); + +alloc_failed: + return ret; +} + +static int __devexit sii902x_remove(struct i2c_client *client) +{ + struct sii902x_data *sii902x = i2c_get_clientdata(client); + + mxc_dispdrv_unregister(sii902x->disp_hdmi); + kfree(sii902x); + return 0; +} + +static const struct i2c_device_id sii902x_id[] = { + { "sii902x", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, sii902x_id); + +static struct i2c_driver sii902x_i2c_driver = { + .driver = { + .name = "sii902x", + }, + .probe = sii902x_probe, + .remove = sii902x_remove, + .id_table = sii902x_id, +}; + +static int __init sii902x_init(void) +{ + return i2c_add_driver(&sii902x_i2c_driver); +} + +static void __exit sii902x_exit(void) +{ + i2c_del_driver(&sii902x_i2c_driver); +} + +module_init(sii902x_init); +module_exit(sii902x_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SII902x DVI/HDMI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/tve.c b/drivers/video/mxc/tve.c new file mode 100644 index 00000000000..35b100349fe --- /dev/null +++ b/drivers/video/mxc/tve.c @@ -0,0 +1,1290 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file tve.c + * @brief Driver for i.MX TV encoder + * + * @ingroup Framebuffer + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/clk.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/sysfs.h> +#include <linux/irq.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <linux/fsl_devices.h> +#include <linux/uaccess.h> +#include <asm/atomic.h> +#include <mach/hardware.h> +#include <mach/ipu-v3.h> +#include "mxc_dispdrv.h" + +#define TVE_ENABLE (1UL) +#define TVE_DAC_FULL_RATE (0UL<<1) +#define TVE_DAC_DIV2_RATE (1UL<<1) +#define TVE_DAC_DIV4_RATE (2UL<<1) +#define TVE_IPU_CLK_ENABLE (1UL<<3) + +#define CD_LM_INT 0x00000001 +#define CD_SM_INT 0x00000002 +#define CD_MON_END_INT 0x00000004 +#define CD_CH_0_LM_ST 0x00000001 +#define CD_CH_0_SM_ST 0x00000010 +#define CD_CH_1_LM_ST 0x00000002 +#define CD_CH_1_SM_ST 0x00000020 +#define CD_CH_2_LM_ST 0x00000004 +#define CD_CH_2_SM_ST 0x00000040 +#define CD_MAN_TRIG 0x00000100 + +#define TVE_STAND_MASK (0x0F<<8) +#define TVE_NTSC_STAND (0UL<<8) +#define TVE_PAL_STAND (3UL<<8) +#define TVE_HD720P60_STAND (4UL<<8) +#define TVE_HD720P50_STAND (5UL<<8) +#define TVE_HD720P30_STAND (6UL<<8) +#define TVE_HD720P25_STAND (7UL<<8) +#define TVE_HD720P24_STAND (8UL<<8) +#define TVE_HD1080I60_STAND (9UL<<8) +#define TVE_HD1080I50_STAND (10UL<<8) +#define TVE_HD1035I60_STAND (11UL<<8) +#define TVE_HD1080P30_STAND (12UL<<8) +#define TVE_HD1080P25_STAND (13UL<<8) +#define TVE_HD1080P24_STAND (14UL<<8) +#define TVE_DAC_SAMPRATE_MASK (0x3<<1) +#define TVEV2_DATA_SRC_MASK (0x3<<4) + +#define TVEV2_DATA_SRC_BUS_1 (0UL<<4) +#define TVEV2_DATA_SRC_BUS_2 (1UL<<4) +#define TVEV2_DATA_SRC_EXT (2UL<<4) + +#define TVEV2_INP_VIDEO_FORM (1UL<<6) +#define TVEV2_P2I_CONV_EN (1UL<<7) + +#define TVEV2_DAC_GAIN_MASK 0x3F +#define TVEV2_DAC_TEST_MODE_MASK 0x7 + +#define TVOUT_FMT_OFF 0 +#define TVOUT_FMT_NTSC 1 +#define TVOUT_FMT_PAL 2 +#define TVOUT_FMT_720P60 3 +#define TVOUT_FMT_720P30 4 +#define TVOUT_FMT_1080I60 5 +#define TVOUT_FMT_1080I50 6 +#define TVOUT_FMT_1080P30 7 +#define TVOUT_FMT_1080P25 8 +#define TVOUT_FMT_1080P24 9 +#define TVOUT_FMT_VGA_SVGA 10 +#define TVOUT_FMT_VGA_XGA 11 +#define TVOUT_FMT_VGA_SXGA 12 +#define TVOUT_FMT_VGA_WSXGA 13 + +#define DISPDRV_VGA "vga" +#define DISPDRV_TVE "tve" + +struct tve_data { + struct platform_device *pdev; + int revision; + int cur_mode; + int output_mode; + int detect; + void *base; + spinlock_t tve_lock; + bool inited; + int enabled; + int irq; + struct clk *clk; + struct clk *di_clk; + struct regulator *dac_reg; + struct regulator *dig_reg; + struct delayed_work cd_work; + struct tve_reg_mapping *regs; + struct tve_reg_fields_mapping *reg_fields; + struct mxc_dispdrv_entry *disp_tve; + struct mxc_dispdrv_entry *disp_vga; + struct notifier_block nb; +}; + +struct tve_reg_mapping { + u32 tve_com_conf_reg; + u32 tve_cd_cont_reg; + u32 tve_int_cont_reg; + u32 tve_stat_reg; + u32 tve_mv_cont_reg; + u32 tve_tvdac_cont_reg; + u32 tve_tst_mode_reg; +}; + +struct tve_reg_fields_mapping { + u32 cd_en; + u32 cd_trig_mode; + u32 cd_lm_int; + u32 cd_sm_int; + u32 cd_mon_end_int; + u32 cd_man_trig; + u32 sync_ch_mask; + u32 tvout_mode_mask; + u32 sync_ch_offset; + u32 tvout_mode_offset; + u32 cd_ch_stat_offset; +}; + +static struct tve_reg_mapping tve_regs_v1 = { + 0, 0x14, 0x28, 0x2C, 0x48, 0x08, 0x30 +}; + +static struct tve_reg_fields_mapping tve_reg_fields_v1 = { + 1, 2, 1, 2, 4, 0x00010000, 0x7000, 0x70, 12, 4, 8 +}; + +static struct tve_reg_mapping tve_regs_v2 = { + 0, 0x34, 0x64, 0x68, 0xDC, 0x28, 0x6c +}; + +static struct tve_reg_fields_mapping tve_reg_fields_v2 = { + 1, 2, 1, 2, 4, 0x01000000, 0x700000, 0x7000, 20, 12, 16 +}; + +static struct fb_videomode video_modes_tve[] = { + { + /* NTSC TV output */ + "TV-NTSC", 60, 720, 480, 74074, + 122, 15, + 18, 26, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* PAL TV output */ + "TV-PAL", 50, 720, 576, 74074, + 132, 11, + 22, 26, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 720p60 TV output */ + "TV-720P60", 60, 1280, 720, 13468, + 260, 109, + 25, 4, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 720p30 TV output */ + "TV-720P30", 30, 1280, 720, 13468, + 260, 1759, + 25, 4, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080i60 TV output */ + "TV-1080I60", 60, 1920, 1080, 13468, + 192, 87, + 20, 24, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 1080i50 TV output */ + "TV-1080I50", 50, 1920, 1080, 13468, + 192, 527, + 20, 24, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + FB_MODE_IS_DETAILED,}, + { + /* 1080p30 TV output */ + "TV-1080P30", 30, 1920, 1080, 13468, + 192, 87, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080p25 TV output */ + "TV-1080P25", 25, 1920, 1080, 13468, + 192, 527, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* 1080p24 TV output */ + "TV-1080P24", 24, 1920, 1080, 13468, + 192, 637, + 38, 6, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; +static int tve_modedb_sz = ARRAY_SIZE(video_modes_tve); + +static struct fb_videomode video_modes_vga[] = { + { + /* VGA 800x600 40M pixel clk output */ + "VGA-SVGA", 60, 800, 600, 25000, + 215, 28, + 24, 2, + 13, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1024x768 65M pixel clk output */ + "VGA-XGA", 60, 1024, 768, 15384, + 160, 24, + 29, 3, + 136, 6, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1280x1024 108M pixel clk output */ + "VGA-SXGA", 60, 1280, 1024, 9259, + 358, 38, + 38, 2, + 12, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, + { + /* VGA 1680x1050 294M pixel clk output */ + "VGA-WSXGA+", 60, 1680, 1050, 6796, + 288, 104, + 33, 2, + 184, 2, + 0, + FB_VMODE_NONINTERLACED, + FB_MODE_IS_DETAILED,}, +}; +static int vga_modedb_sz = ARRAY_SIZE(video_modes_vga); + +enum tvout_mode { + TV_OFF, + CVBS0, + CVBS2, + CVBS02, + SVIDEO, + SVIDEO_CVBS, + YPBPR, + TVRGB +}; + +static unsigned short tvout_mode_to_channel_map[8] = { + 0, /* TV_OFF */ + 1, /* CVBS0 */ + 4, /* CVBS2 */ + 5, /* CVBS02 */ + 1, /* SVIDEO */ + 5, /* SVIDEO_CVBS */ + 1, /* YPBPR */ + 7 /* TVRGB */ +}; + +static void tve_dump_regs(struct tve_data *tve) +{ + dev_dbg(&tve->pdev->dev, "tve_com_conf_reg 0x%x\n", + readl(tve->base + tve->regs->tve_com_conf_reg)); + dev_dbg(&tve->pdev->dev, "tve_cd_cont_reg 0x%x\n", + readl(tve->base + tve->regs->tve_cd_cont_reg)); + dev_dbg(&tve->pdev->dev, "tve_int_cont_reg 0x%x\n", + readl(tve->base + tve->regs->tve_int_cont_reg)); + dev_dbg(&tve->pdev->dev, "tve_tst_mode_reg 0x%x\n", + readl(tve->base + tve->regs->tve_tst_mode_reg)); + dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg0 0x%x\n", + readl(tve->base + tve->regs->tve_tvdac_cont_reg)); + dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg1 0x%x\n", + readl(tve->base + tve->regs->tve_tvdac_cont_reg + 4)); + dev_dbg(&tve->pdev->dev, "tve_tvdac_cont_reg2 0x%x\n", + readl(tve->base + tve->regs->tve_tvdac_cont_reg + 8)); +} + +static int is_vga_enabled(struct tve_data *tve) +{ + u32 reg; + + if (tve->revision == 2) { + reg = readl(tve->base + tve->regs->tve_tst_mode_reg); + if (reg & TVEV2_DAC_TEST_MODE_MASK) + return 1; + else + return 0; + } + return 0; +} + +static inline int is_vga_mode(int mode) +{ + return ((mode == TVOUT_FMT_VGA_SVGA) + || (mode == TVOUT_FMT_VGA_XGA) + || (mode == TVOUT_FMT_VGA_SXGA) + || (mode == TVOUT_FMT_VGA_WSXGA)); +} + +static inline int valid_mode(int mode) +{ + return (is_vga_mode(mode) + || (mode == TVOUT_FMT_NTSC) + || (mode == TVOUT_FMT_PAL) + || (mode == TVOUT_FMT_720P30) + || (mode == TVOUT_FMT_720P60) + || (mode == TVOUT_FMT_1080I50) + || (mode == TVOUT_FMT_1080I60) + || (mode == TVOUT_FMT_1080P24) + || (mode == TVOUT_FMT_1080P25) + || (mode == TVOUT_FMT_1080P30)); +} + +static int get_video_mode(struct fb_info *fbi) +{ + int mode; + + if (fb_mode_is_equal(fbi->mode, &video_modes_tve[0])) { + mode = TVOUT_FMT_NTSC; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[1])) { + mode = TVOUT_FMT_PAL; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[2])) { + mode = TVOUT_FMT_720P60; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[3])) { + mode = TVOUT_FMT_720P30; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[4])) { + mode = TVOUT_FMT_1080I60; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[5])) { + mode = TVOUT_FMT_1080I50; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[6])) { + mode = TVOUT_FMT_1080P30; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[7])) { + mode = TVOUT_FMT_1080P25; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_tve[8])) { + mode = TVOUT_FMT_1080P24; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[0])) { + mode = TVOUT_FMT_VGA_SVGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[1])) { + mode = TVOUT_FMT_VGA_XGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[2])) { + mode = TVOUT_FMT_VGA_SXGA; + } else if (fb_mode_is_equal(fbi->mode, &video_modes_vga[3])) { + mode = TVOUT_FMT_VGA_WSXGA; + } else { + mode = TVOUT_FMT_OFF; + } + return mode; +} + +static void tve_disable_vga_mode(struct tve_data *tve) +{ + if (tve->revision == 2) { + u32 reg; + /* disable test mode */ + reg = readl(tve->base + tve->regs->tve_tst_mode_reg); + reg = reg & ~TVEV2_DAC_TEST_MODE_MASK; + writel(reg, tve->base + tve->regs->tve_tst_mode_reg); + } +} + +static void tve_set_tvout_mode(struct tve_data *tve, int mode) +{ + u32 conf_reg; + + /* clear sync_ch and tvout_mode fields */ + conf_reg = readl(tve->base + tve->regs->tve_com_conf_reg); + conf_reg &= ~(tve->reg_fields->sync_ch_mask | + tve->reg_fields->tvout_mode_mask); + + conf_reg = conf_reg & ~TVE_DAC_SAMPRATE_MASK; + if (tve->revision == 2) { + conf_reg = (conf_reg & ~TVEV2_DATA_SRC_MASK) | + TVEV2_DATA_SRC_BUS_1; + conf_reg = conf_reg & ~TVEV2_INP_VIDEO_FORM; + conf_reg = conf_reg & ~TVEV2_P2I_CONV_EN; + } + + conf_reg |= + mode << tve->reg_fields-> + tvout_mode_offset | tvout_mode_to_channel_map[mode] << + tve->reg_fields->sync_ch_offset; + writel(conf_reg, tve->base + tve->regs->tve_com_conf_reg); +} + +static int _is_tvout_mode_hd_compatible(struct tve_data *tve) +{ + u32 conf_reg, mode; + + conf_reg = readl(tve->base + tve->regs->tve_com_conf_reg); + mode = (conf_reg >> tve->reg_fields->tvout_mode_offset) & 7; + if (mode == YPBPR || mode == TVRGB) { + return 1; + } else { + return 0; + } +} + +static int tve_setup_vga(struct tve_data *tve) +{ + u32 reg; + + if (tve->revision == 2) { + /* set gain */ + reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; + writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg); + reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg + 4); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; + writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg + 4); + reg = readl(tve->base + tve->regs->tve_tvdac_cont_reg + 8); + reg = (reg & ~TVEV2_DAC_GAIN_MASK) | 0xa; + writel(reg, tve->base + tve->regs->tve_tvdac_cont_reg + 8); + + /* set tve_com_conf_reg */ + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_DAC_SAMPRATE_MASK) | TVE_DAC_DIV2_RATE; + reg = (reg & ~TVEV2_DATA_SRC_MASK) | TVEV2_DATA_SRC_BUS_2; + reg = reg | TVEV2_INP_VIDEO_FORM; + reg = reg & ~TVEV2_P2I_CONV_EN; + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; + reg |= TVRGB << tve->reg_fields->tvout_mode_offset | + 1 << tve->reg_fields->sync_ch_offset; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + + /* set test mode */ + reg = readl(tve->base + tve->regs->tve_tst_mode_reg); + reg = (reg & ~TVEV2_DAC_TEST_MODE_MASK) | 1; + writel(reg, tve->base + tve->regs->tve_tst_mode_reg); + } + + return 0; +} + +/** + * tve_setup + * initial the CH7024 chipset by setting register + * @param: + * vos: output video format + * @return: + * 0 successful + * otherwise failed + */ +static int tve_setup(struct tve_data *tve, int mode) +{ + u32 reg; + struct clk *tve_parent_clk; + unsigned long parent_clock_rate = 216000000, di1_clock_rate = 27000000; + unsigned long tve_clock_rate = 216000000; + unsigned long lock_flags; + + if (tve->cur_mode == mode) + return 0; + + spin_lock_irqsave(&tve->tve_lock, lock_flags); + + switch (mode) { + case TVOUT_FMT_PAL: + case TVOUT_FMT_NTSC: + parent_clock_rate = 216000000; + di1_clock_rate = 27000000; + break; + case TVOUT_FMT_720P60: + case TVOUT_FMT_1080I60: + case TVOUT_FMT_1080I50: + case TVOUT_FMT_720P30: + case TVOUT_FMT_1080P30: + case TVOUT_FMT_1080P25: + case TVOUT_FMT_1080P24: + parent_clock_rate = 297000000; + tve_clock_rate = 297000000; + di1_clock_rate = 74250000; + break; + case TVOUT_FMT_VGA_SVGA: + parent_clock_rate = 160000000; + tve_clock_rate = 80000000; + di1_clock_rate = 40000000; + break; + case TVOUT_FMT_VGA_XGA: + parent_clock_rate = 520000000; + tve_clock_rate = 130000000; + di1_clock_rate = 65000000; + break; + case TVOUT_FMT_VGA_SXGA: + parent_clock_rate = 864000000; + tve_clock_rate = 216000000; + di1_clock_rate = 108000000; + break; + case TVOUT_FMT_VGA_WSXGA: + parent_clock_rate = 588560000; + tve_clock_rate = 294280000; + di1_clock_rate = 147140000; + break; + } + if (tve->enabled) + clk_disable(tve->clk); + + tve_parent_clk = clk_get_parent(tve->clk); + + clk_set_rate(tve_parent_clk, parent_clock_rate); + + tve_clock_rate = clk_round_rate(tve->clk, tve_clock_rate); + clk_set_rate(tve->clk, tve_clock_rate); + + clk_enable(tve->clk); + di1_clock_rate = clk_round_rate(tve->di_clk, di1_clock_rate); + clk_set_rate(tve->di_clk, di1_clock_rate); + + tve->cur_mode = mode; + + /* select output video format */ + if (mode == TVOUT_FMT_PAL) { + tve_disable_vga_mode(tve); + tve_set_tvout_mode(tve, YPBPR); + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_PAL_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to PAL video\n"); + } else if (mode == TVOUT_FMT_NTSC) { + tve_disable_vga_mode(tve); + tve_set_tvout_mode(tve, YPBPR); + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_NTSC_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to NTSC video\n"); + } else if (mode == TVOUT_FMT_720P60) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P60_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 720P60 video\n"); + } else if (mode == TVOUT_FMT_720P30) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P30_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 720P30 video\n"); + } else if (mode == TVOUT_FMT_1080I60) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I60_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080I60 video\n"); + } else if (mode == TVOUT_FMT_1080I50) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080I50_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080I50 video\n"); + } else if (mode == TVOUT_FMT_1080P30) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P30_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080P30 video\n"); + } else if (mode == TVOUT_FMT_1080P25) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P25_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080P25 video\n"); + } else if (mode == TVOUT_FMT_1080P24) { + tve_disable_vga_mode(tve); + if (!_is_tvout_mode_hd_compatible(tve)) { + tve_set_tvout_mode(tve, YPBPR); + dev_dbg(&tve->pdev->dev, "The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD1080P24_STAND; + writel(reg, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to 1080P24 video\n"); + } else if (is_vga_mode(mode)) { + /* do not need cable detect */ + tve_setup_vga(tve); + dev_dbg(&tve->pdev->dev, "TVE: change to VGA video\n"); + } else if (mode == TVOUT_FMT_OFF) { + writel(0x0, tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE: change to OFF video\n"); + } else { + dev_dbg(&tve->pdev->dev, "TVE: no such video format.\n"); + } + + if (!tve->enabled) + clk_disable(tve->clk); + + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); + return 0; +} + +/** + * tve_enable + * Enable the tve Power to begin TV encoder + */ +static void tve_enable(struct tve_data *tve) +{ + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&tve->tve_lock, lock_flags); + if (!tve->enabled) { + tve->enabled = 1; + clk_enable(tve->clk); + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + writel(reg | TVE_IPU_CLK_ENABLE | TVE_ENABLE, + tve->base + tve->regs->tve_com_conf_reg); + dev_dbg(&tve->pdev->dev, "TVE power on.\n"); + } + + if (is_vga_enabled(tve)) { + /* disable interrupt */ + dev_dbg(&tve->pdev->dev, "TVE VGA disable cable detect.\n"); + writel(0xffffffff, tve->base + tve->regs->tve_stat_reg); + writel(0, tve->base + tve->regs->tve_int_cont_reg); + } else { + /* enable interrupt */ + dev_dbg(&tve->pdev->dev, "TVE TVE enable cable detect.\n"); + writel(0xffffffff, tve->base + tve->regs->tve_stat_reg); + writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT, + tve->base + tve->regs->tve_int_cont_reg); + } + + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); + + tve_dump_regs(tve); +} + +/** + * tve_disable + * Disable the tve Power to stop TV encoder + */ +static void tve_disable(struct tve_data *tve) +{ + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&tve->tve_lock, lock_flags); + if (tve->enabled) { + tve->enabled = 0; + reg = readl(tve->base + tve->regs->tve_com_conf_reg); + writel(reg & ~TVE_ENABLE & ~TVE_IPU_CLK_ENABLE, + tve->base + tve->regs->tve_com_conf_reg); + clk_disable(tve->clk); + dev_dbg(&tve->pdev->dev, "TVE power off.\n"); + } + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); +} + +static int tve_update_detect_status(struct tve_data *tve) +{ + int old_detect = tve->detect; + u32 stat_lm, stat_sm, stat; + u32 int_ctl; + u32 cd_cont_reg; + u32 timeout = 40; + unsigned long lock_flags; + char event_string[16]; + char *envp[] = { event_string, NULL }; + + spin_lock_irqsave(&tve->tve_lock, lock_flags); + + if (!tve->enabled) { + dev_warn(&tve->pdev->dev, "Warning: update tve status while it disabled!\n"); + tve->detect = 0; + goto done; + } + + int_ctl = readl(tve->base + tve->regs->tve_int_cont_reg); + cd_cont_reg = readl(tve->base + tve->regs->tve_cd_cont_reg); + + if ((cd_cont_reg & 0x1) == 0) { + dev_warn(&tve->pdev->dev, "Warning: pls enable TVE CD first!\n"); + goto done; + } + + stat = readl(tve->base + tve->regs->tve_stat_reg); + while (((stat & CD_MON_END_INT) == 0) && (timeout > 0)) { + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); + msleep(2); + spin_lock_irqsave(&tve->tve_lock, lock_flags); + timeout -= 2; + if (!tve->enabled) { + dev_warn(&tve->pdev->dev, "Warning: update tve status while it disabled!\n"); + tve->detect = 0; + goto done; + } else + stat = readl(tve->base + tve->regs->tve_stat_reg); + } + if (((stat & CD_MON_END_INT) == 0) && (timeout <= 0)) { + dev_warn(&tve->pdev->dev, "Warning: get detect result without CD_MON_END_INT!\n"); + goto done; + } + + stat = stat >> tve->reg_fields->cd_ch_stat_offset; + stat_lm = stat & (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST); + if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST)) && + ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST | CD_CH_2_SM_ST)) == 0) + ) { + tve->detect = 3; + tve->output_mode = YPBPR; + } else if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST)) && + ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST)) == 0)) { + tve->detect = 4; + tve->output_mode = SVIDEO; + } else if (stat_lm == CD_CH_0_LM_ST) { + stat_sm = stat & CD_CH_0_SM_ST; + if (stat_sm != 0) { + /* headset */ + tve->detect = 2; + tve->output_mode = TV_OFF; + } else { + tve->detect = 1; + tve->output_mode = CVBS0; + } + } else if (stat_lm == CD_CH_2_LM_ST) { + stat_sm = stat & CD_CH_2_SM_ST; + if (stat_sm != 0) { + /* headset */ + tve->detect = 2; + tve->output_mode = TV_OFF; + } else { + tve->detect = 1; + tve->output_mode = CVBS2; + } + } else { + /* none */ + tve->detect = 0; + tve->output_mode = TV_OFF; + } + + tve_set_tvout_mode(tve, tve->output_mode); + + /* clear interrupt */ + writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve->base + tve->regs->tve_stat_reg); + + writel(int_ctl | CD_SM_INT | CD_LM_INT, + tve->base + tve->regs->tve_int_cont_reg); + +done: + spin_unlock_irqrestore(&tve->tve_lock, lock_flags); + + if (old_detect != tve->detect) { + sysfs_notify(&tve->pdev->dev.kobj, NULL, "headphone"); + if (tve->detect == 1) + sprintf(event_string, "EVENT=CVBS0"); + else if (tve->detect == 3) + sprintf(event_string, "EVENT=YPBPR"); + else if (tve->detect == 4) + sprintf(event_string, "EVENT=SVIDEO"); + else + sprintf(event_string, "EVENT=NONE"); + kobject_uevent_env(&tve->pdev->dev.kobj, KOBJ_CHANGE, envp); + } + + dev_dbg(&tve->pdev->dev, "detect = %d mode = %d\n", + tve->detect, tve->output_mode); + return tve->detect; +} + +static void cd_work_func(struct work_struct *work) +{ + struct delayed_work *delay_work = to_delayed_work(work); + struct tve_data *tve = + container_of(delay_work, struct tve_data, cd_work); + + tve_update_detect_status(tve); +} + +static irqreturn_t tve_detect_handler(int irq, void *data) +{ + struct tve_data *tve = data; + + u32 int_ctl = readl(tve->base + tve->regs->tve_int_cont_reg); + + /* disable INT first */ + int_ctl &= ~(CD_SM_INT | CD_LM_INT | CD_MON_END_INT); + writel(int_ctl, tve->base + tve->regs->tve_int_cont_reg); + + writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve->base + tve->regs->tve_stat_reg); + + schedule_delayed_work(&tve->cd_work, msecs_to_jiffies(1000)); + + return IRQ_HANDLED; +} + +/*! + * FB suspend/resume routing + */ +static int tve_suspend(struct tve_data *tve) +{ + if (tve->enabled) { + writel(0, tve->base + tve->regs->tve_int_cont_reg); + writel(0, tve->base + tve->regs->tve_cd_cont_reg); + writel(0, tve->base + tve->regs->tve_com_conf_reg); + clk_disable(tve->clk); + } + return 0; +} + +static int tve_resume(struct tve_data *tve, struct fb_info *fbi) +{ + int mode; + + if (tve->enabled) { + clk_enable(tve->clk); + + /* Setup cable detect */ + if (tve->revision == 1) + writel(0x01067701, + tve->base + tve->regs->tve_cd_cont_reg); + else + writel(0x00770601, + tve->base + tve->regs->tve_cd_cont_reg); + + if (valid_mode(tve->cur_mode)) { + mode = tve->cur_mode; + tve_disable(tve); + tve->cur_mode = TVOUT_FMT_OFF; + tve_setup(tve, mode); + } + tve_enable(tve); + } + + return 0; +} + +int tve_fb_setup(struct tve_data *tve, struct fb_info *fbi) +{ + int mode; + + fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + + if (!fbi->mode) { + dev_warn(&tve->pdev->dev, "TVE: can not find mode for xres=%d, yres=%d\n", + fbi->var.xres, fbi->var.yres); + tve_disable(tve); + tve->cur_mode = TVOUT_FMT_OFF; + return 0; + } + + dev_dbg(&tve->pdev->dev, "TVE: fb mode change event: xres=%d, yres=%d\n", + fbi->mode->xres, fbi->mode->yres); + + mode = get_video_mode(fbi); + if (mode != TVOUT_FMT_OFF) { + tve_disable(tve); + tve_setup(tve, mode); + tve_enable(tve); + } else { + tve_disable(tve); + tve_setup(tve, mode); + } + + return 0; +} + +int tve_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct tve_data *tve = container_of(nb, struct tve_data, nb); + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + /* only work for ipu0 di1*/ + if (strcmp(fbi->fix.id, "DISP3 BG - DI1")) + return 0; + + switch (val) { + case FB_EVENT_PREMODE_CHANGE: + { + tve_fb_setup(tve, fbi); + break; + } + case FB_EVENT_BLANK: + if (fbi->mode == NULL) + return 0; + + dev_dbg(&tve->pdev->dev, "TVE: fb blank event\n"); + + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + int mode; + mode = get_video_mode(fbi); + if (mode != TVOUT_FMT_OFF) { + if (tve->cur_mode != mode) { + tve_disable(tve); + tve_setup(tve, mode); + } + tve_enable(tve); + } else + tve_setup(tve, mode); + } else + tve_disable(tve); + break; + case FB_EVENT_SUSPEND: + tve_suspend(tve); + break; + case FB_EVENT_RESUME: + tve_resume(tve, fbi); + break; + } + return 0; +} + +static ssize_t show_headphone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tve_data *tve = dev_get_drvdata(dev); + int detect; + + if (!tve->enabled) { + strcpy(buf, "tve power off\n"); + return strlen(buf); + } + + detect = tve_update_detect_status(tve); + + if (detect == 0) + strcpy(buf, "none\n"); + else if (detect == 1) + strcpy(buf, "cvbs\n"); + else if (detect == 2) + strcpy(buf, "headset\n"); + else if (detect == 3) + strcpy(buf, "component\n"); + else + strcpy(buf, "svideo\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static int _tve_get_revision(struct tve_data *tve) +{ + u32 conf_reg; + u32 rev = 0; + + /* find out TVE rev based on the base addr default value + * can be used at the init/probe ONLY */ + conf_reg = readl(tve->base); + switch (conf_reg) { + case 0x00842000: + rev = 1; + break; + case 0x00100000: + rev = 2; + break; + } + return rev; +} + +static int tve_drv_init(struct mxc_dispdrv_entry *disp, bool vga) +{ + int ret; + struct tve_data *tve = mxc_dispdrv_getdata(disp); + struct mxc_dispdrv_setting *setting = mxc_dispdrv_getsetting(disp); + struct fsl_mxc_tve_platform_data *plat_data + = tve->pdev->dev.platform_data; + struct resource *res; + struct fb_videomode *modedb; + int modedb_sz; + u32 conf_reg; + + if (tve->inited == true) + return -ENODEV; + + /*tve&vga only use ipu0 and di1*/ + setting->dev_id = 0; + setting->disp_id = 1; + + res = platform_get_resource(tve->pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENOMEM; + goto get_res_failed; + } + + tve->irq = platform_get_irq(tve->pdev, 0); + if (tve->irq < 0) { + ret = tve->irq; + goto get_irq_failed; + } + + tve->base = ioremap(res->start, res->end - res->start); + if (!tve->base) { + ret = -ENOMEM; + goto ioremap_failed; + } + + ret = device_create_file(&tve->pdev->dev, &dev_attr_headphone); + if (ret < 0) + goto dev_file_create_failed; + + tve->dac_reg = regulator_get(&tve->pdev->dev, plat_data->dac_reg); + if (!IS_ERR(tve->dac_reg)) { + regulator_set_voltage(tve->dac_reg, 2750000, 2750000); + regulator_enable(tve->dac_reg); + } + tve->dig_reg = regulator_get(&tve->pdev->dev, plat_data->dig_reg); + if (!IS_ERR(tve->dig_reg)) { + regulator_set_voltage(tve->dig_reg, 1250000, 1250000); + regulator_enable(tve->dig_reg); + } + + tve->clk = clk_get(&tve->pdev->dev, "tve_clk"); + if (IS_ERR(tve->clk)) { + ret = PTR_ERR(tve->clk); + goto get_tveclk_failed; + } + tve->di_clk = clk_get(NULL, "ipu1_di1_clk"); + if (IS_ERR(tve->di_clk)) { + ret = PTR_ERR(tve->di_clk); + goto get_diclk_failed; + } + + clk_set_rate(tve->clk, 216000000); + clk_set_parent(tve->di_clk, tve->clk); + clk_enable(tve->clk); + + tve->revision = _tve_get_revision(tve); + if (tve->revision == 1) { + tve->regs = &tve_regs_v1; + tve->reg_fields = &tve_reg_fields_v1; + } else { + tve->regs = &tve_regs_v2; + tve->reg_fields = &tve_reg_fields_v2; + } + + if (vga && cpu_is_mx53()) { + setting->if_fmt = IPU_PIX_FMT_GBR24; + modedb = video_modes_vga; + modedb_sz = vga_modedb_sz; + } else { + setting->if_fmt = IPU_PIX_FMT_YUV444; + if (tve->revision == 1) { + modedb = video_modes_tve; + modedb_sz = 3; + } else { + modedb = video_modes_tve; + modedb_sz = tve_modedb_sz; + } + } + + fb_videomode_to_modelist(modedb, modedb_sz, &setting->fbi->modelist); + + /* must use spec video mode defined by driver */ + ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, + modedb, modedb_sz, NULL, setting->default_bpp); + if (ret != 1) + fb_videomode_to_var(&setting->fbi->var, &modedb[0]); + + ret = request_irq(tve->irq, tve_detect_handler, 0, tve->pdev->name, tve); + if (ret < 0) + goto req_irq_failed; + + /* Setup cable detect, for YPrPb mode, default use channel#-1 for Y */ + INIT_DELAYED_WORK(&tve->cd_work, cd_work_func); + if (tve->revision == 1) + writel(0x01067701, tve->base + tve->regs->tve_cd_cont_reg); + else + writel(0x00770601, tve->base + tve->regs->tve_cd_cont_reg); + + conf_reg = 0; + writel(conf_reg, tve->base + tve->regs->tve_com_conf_reg); + + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 5); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 4); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 3); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4 * 2); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg - 4); + writel(0x00000000, tve->base + tve->regs->tve_mv_cont_reg); + + clk_disable(tve->clk); + + tve->nb.notifier_call = tve_fb_event; + ret = fb_register_client(&tve->nb); + if (ret < 0) + goto reg_fbclient_failed; + + dev_set_drvdata(&tve->pdev->dev, tve); + + spin_lock_init(&tve->tve_lock); + + tve->inited = true; + + return 0; + +reg_fbclient_failed: + free_irq(tve->irq, tve->pdev); +req_irq_failed: +get_diclk_failed: +get_tveclk_failed: + device_remove_file(&tve->pdev->dev, &dev_attr_headphone); +dev_file_create_failed: + iounmap(tve->base); +ioremap_failed: +get_irq_failed: +get_res_failed: + return ret; + +} + +static int tvout_init(struct mxc_dispdrv_entry *disp) +{ + return tve_drv_init(disp, 0); +} + +static int vga_init(struct mxc_dispdrv_entry *disp) +{ + return tve_drv_init(disp, 1); +} + +void tvout_deinit(struct mxc_dispdrv_entry *disp) +{ + struct tve_data *tve = mxc_dispdrv_getdata(disp); + + if (tve->enabled) + clk_disable(tve->clk); + + fb_unregister_client(&tve->nb); + free_irq(tve->irq, tve->pdev); + device_remove_file(&tve->pdev->dev, &dev_attr_headphone); + iounmap(tve->base); +} + +static struct mxc_dispdrv_driver tve_drv = { + .name = DISPDRV_TVE, + .init = tvout_init, + .deinit = tvout_deinit, +}; + +static struct mxc_dispdrv_driver vga_drv = { + .name = DISPDRV_VGA, + .init = vga_init, + .deinit = tvout_deinit, +}; + +static int tve_dispdrv_init(struct tve_data *tve) +{ + tve->disp_tve = mxc_dispdrv_register(&tve_drv); + mxc_dispdrv_setdata(tve->disp_tve, tve); + tve->disp_vga = mxc_dispdrv_register(&vga_drv); + mxc_dispdrv_setdata(tve->disp_vga, tve); + return 0; +} + +static void tve_dispdrv_deinit(struct tve_data *tve) +{ + mxc_dispdrv_unregister(tve->disp_tve); + mxc_dispdrv_unregister(tve->disp_vga); +} + +static int tve_probe(struct platform_device *pdev) +{ + int ret; + struct tve_data *tve; + + tve = kzalloc(sizeof(struct tve_data), GFP_KERNEL); + if (!tve) { + ret = -ENOMEM; + goto alloc_failed; + } + + tve->pdev = pdev; + ret = tve_dispdrv_init(tve); + if (ret < 0) + goto dispdrv_init_failed; + + dev_set_drvdata(&pdev->dev, tve); + + return 0; + +dispdrv_init_failed: + kfree(tve); +alloc_failed: + return ret; +} + +static int tve_remove(struct platform_device *pdev) +{ + struct tve_data *tve = dev_get_drvdata(&pdev->dev); + + tve_dispdrv_deinit(tve); + kfree(tve); + return 0; +} + +static struct platform_driver tve_driver = { + .driver = { + .name = "mxc_tve", + }, + .probe = tve_probe, + .remove = tve_remove, +}; + +static int __init tve_init(void) +{ + return platform_driver_register(&tve_driver); +} + +static void __exit tve_exit(void) +{ + platform_driver_unregister(&tve_driver); +} + +module_init(tve_init); +module_exit(tve_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX TV encoder driver"); +MODULE_LICENSE("GPL"); |