From: Jason Chen Date: Wed, 13 Jul 2011 07:24:19 +0000 (+0800) Subject: ENGR00152359-3 sii902x hdmi: add AVI and AIF support for common changes X-Git-Tag: v3.0.35-fsl~2255 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=f06db664ec1bc5574999c08deff0084b580b8467;p=karo-tx-linux.git ENGR00152359-3 sii902x hdmi: add AVI and AIF support for common changes 1.add AVI and AIF support. 2.add edid 4-block reading support.(not test) For RGB input fmt support, pls input cmdline like: video=mxcdixfb:RGB24,1024x768M@60 For YUV input fmt support, pls input cmdline like: video=mxcdixfb:VYU444,1024x768M@60 Signed-off-by: Jason Chen --- diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile index 501107974b60..89f19269f233 100644 --- a/drivers/video/mxc/Makefile +++ b/drivers/video/mxc/Makefile @@ -1,7 +1,7 @@ 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_MODE_HELPERS) += mxc_edid.o mxc_ddc.o ifeq ($(CONFIG_ARCH_MX21)$(CONFIG_ARCH_MX27)$(CONFIG_ARCH_MX25),y) obj-$(CONFIG_FB_MXC_TVOUT) += fs453.o obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mx2fb.o mxcfb_modedb.o diff --git a/drivers/video/mxc/mxc_ddc.c b/drivers/video/mxc/mxc_ddc.c new file mode 100644 index 000000000000..60b74cd6e38b --- /dev/null +++ b/drivers/video/mxc/mxc_ddc.c @@ -0,0 +1,347 @@ +/* + * 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 + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.c + * + * @brief MXC EDID driver + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../edid.h" + +#define MXC_EDID_LENGTH (EDID_LENGTH*4) + +struct mxc_ddc_data { + struct platform_device *pdev; + struct i2c_client *client; + struct delayed_work det_work; + struct fb_info *fbi; + struct mxc_edid_cfg edid_cfg; + u8 cable_plugin; + u8 edid[MXC_EDID_LENGTH]; + + u32 di; + void (*init)(void); + int (*update)(void); + struct regulator *analog_reg; +} mxc_ddc; + +#define MXC_ENABLE 1 +#define MXC_DISABLE 2 +static int g_enable_ddc; + +static ssize_t mxc_ddc_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (mxc_ddc.cable_plugin == 0) + strcpy(buf, "plugout\n"); + else + strcpy(buf, "plugin\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(cable_state, S_IRUGO, mxc_ddc_show_state, NULL); + +static ssize_t mxc_ddc_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, mxc_ddc.fbi->fix.id); + sprintf(buf+strlen(buf), "\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(fb_name, S_IRUGO, mxc_ddc_show_name, NULL); + +static ssize_t mxc_ddc_show_edid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, j, len = 0; + + for (j = 0; j < MXC_EDID_LENGTH/16; j++) { + for (i = 0; i < 16; i++) + len += sprintf(buf+len, "0x%02X ", + mxc_ddc.edid[j*16 + i]); + len += sprintf(buf+len, "\n"); + } + + return len; +} + +static DEVICE_ATTR(edid, S_IRUGO, mxc_ddc_show_edid, NULL); + +static void det_worker(struct work_struct *work) +{ + char event_string[16]; + char *envp[] = { event_string, NULL }; + + /* cable connection changes */ + if (mxc_ddc.update()) { + u8 edid_old[MXC_EDID_LENGTH]; + mxc_ddc.cable_plugin = 1; + sprintf(event_string, "EVENT=plugin"); + + memcpy(edid_old, mxc_ddc.edid, MXC_EDID_LENGTH); + + if (mxc_edid_read(mxc_ddc.client->adapter, mxc_ddc.client->addr, + mxc_ddc.edid, &mxc_ddc.edid_cfg, mxc_ddc.fbi) < 0) + dev_err(&mxc_ddc.client->dev, + "MXC ddc: read edid fail\n"); + else { + if (!memcmp(edid_old, mxc_ddc.edid, MXC_EDID_LENGTH)) + dev_info(&mxc_ddc.client->dev, + "Sii902x: same edid\n"); + else if (mxc_ddc.fbi->monspecs.modedb_len > 0) { + int i; + const struct fb_videomode *mode; + struct fb_videomode m; + + /* make sure fb is powerdown */ + acquire_console_sem(); + fb_blank(mxc_ddc.fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + + fb_destroy_modelist(&mxc_ddc.fbi->modelist); + + for (i = 0; i < mxc_ddc.fbi->monspecs.modedb_len; i++) + /*FIXME now we do not support interlaced mode */ + if (!(mxc_ddc.fbi->monspecs.modedb[i].vmode & FB_VMODE_INTERLACED)) + fb_add_videomode(&mxc_ddc.fbi->monspecs.modedb[i], + &mxc_ddc.fbi->modelist); + + fb_var_to_videomode(&m, &mxc_ddc.fbi->var); + mode = fb_find_nearest_mode(&m, + &mxc_ddc.fbi->modelist); + + fb_videomode_to_var(&mxc_ddc.fbi->var, mode); + + mxc_ddc.fbi->var.activate |= FB_ACTIVATE_FORCE; + acquire_console_sem(); + mxc_ddc.fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(mxc_ddc.fbi, &mxc_ddc.fbi->var); + mxc_ddc.fbi->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); + + acquire_console_sem(); + fb_blank(mxc_ddc.fbi, FB_BLANK_UNBLANK); + release_console_sem(); + } + } + } else { + mxc_ddc.cable_plugin = 0; + sprintf(event_string, "EVENT=plugout"); + } + + kobject_uevent_env(&mxc_ddc.pdev->dev.kobj, KOBJ_CHANGE, envp); +} + +static irqreturn_t mxc_ddc_detect_handler(int irq, void *data) +{ + if (mxc_ddc.fbi) + schedule_delayed_work(&(mxc_ddc.det_work), msecs_to_jiffies(300)); + return IRQ_HANDLED; +} + +static int mxc_ddc_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + if ((mxc_ddc.di)) { + if (strcmp(event->info->fix.id, "DISP3 BG - DI1")) + return 0; + } else { + if (strcmp(event->info->fix.id, "DISP3 BG")) + return 0; + } + + switch (val) { + case FB_EVENT_FB_REGISTERED: + if (mxc_ddc.fbi != NULL) + break; + mxc_ddc.fbi = fbi; + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = mxc_ddc_fb_event, +}; + +static int __devinit mxc_ddc_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct fb_info edid_fbi; + struct mxc_ddc_platform_data *plat = client->dev.platform_data; + + if (plat->boot_enable && !g_enable_ddc) + g_enable_ddc = MXC_ENABLE; + if (!g_enable_ddc) + g_enable_ddc = MXC_DISABLE; + + if (g_enable_ddc == MXC_DISABLE) { + printk(KERN_WARNING "By setting, DDC driver will not be enabled\n"); + return -ENODEV; + } + + mxc_ddc.client = client; + mxc_ddc.di = plat->di; + mxc_ddc.init = plat->init; + mxc_ddc.update = plat->update; + + if (!mxc_ddc.update) + return -EINVAL; + + mxc_ddc.analog_reg = regulator_get(&mxc_ddc.pdev->dev, plat->analog_regulator); + if (!IS_ERR(mxc_ddc.analog_reg)) { + regulator_set_voltage(mxc_ddc.analog_reg, 2775000, 2775000); + regulator_enable(mxc_ddc.analog_reg); + } + + if (mxc_ddc.init) + mxc_ddc.init(); + + if (mxc_ddc.update()) { + mxc_ddc.cable_plugin = 1; + /* try to read edid */ + if (mxc_edid_read(client->adapter, client->addr, + mxc_ddc.edid, &mxc_ddc.edid_cfg, &edid_fbi) < 0) + dev_warn(&client->dev, "Can not read edid\n"); +#if defined(CONFIG_MXC_IPU_V3) && defined(CONFIG_FB_MXC_SYNC_PANEL) + else + mxcfb_register_mode(mxc_ddc.di, edid_fbi.monspecs.modedb, + edid_fbi.monspecs.modedb_len, MXC_DISP_DDC_DEV); +#endif + } else + mxc_ddc.cable_plugin = 0; + + if (client->irq) { + ret = request_irq(client->irq, mxc_ddc_detect_handler, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "ddc_det", &mxc_ddc); + if (ret < 0) { + dev_warn(&client->dev, + "MXC ddc: cound not request det irq %d\n", + client->irq); + goto err; + } else { + INIT_DELAYED_WORK(&(mxc_ddc.det_work), det_worker); + ret = device_create_file(&mxc_ddc.pdev->dev, &dev_attr_fb_name); + if (ret < 0) + dev_warn(&client->dev, + "MXC ddc: cound not create sys node for fb name\n"); + ret = device_create_file(&mxc_ddc.pdev->dev, &dev_attr_cable_state); + if (ret < 0) + dev_warn(&client->dev, + "MXC ddc: cound not create sys node for cable state\n"); + ret = device_create_file(&mxc_ddc.pdev->dev, &dev_attr_edid); + if (ret < 0) + dev_warn(&client->dev, + "MXC ddc: cound not create sys node for edid\n"); + } + } + + fb_register_client(&nb); + +err: + return ret; +} + +static int __devexit mxc_ddc_remove(struct i2c_client *client) +{ + fb_unregister_client(&nb); + if (!IS_ERR(mxc_ddc.analog_reg)) + regulator_disable(mxc_ddc.analog_reg); + return 0; +} + +static int __init enable_ddc_setup(char *options) +{ + if (!strcmp(options, "=off")) + g_enable_ddc = MXC_DISABLE; + else + g_enable_ddc = MXC_ENABLE; + + return 1; +} +__setup("ddc", enable_ddc_setup); + +static const struct i2c_device_id mxc_ddc_id[] = { + { "mxc_ddc", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mxc_ddc_id); + +static struct i2c_driver mxc_ddc_i2c_driver = { + .driver = { + .name = "mxc_ddc", + }, + .probe = mxc_ddc_probe, + .remove = mxc_ddc_remove, + .id_table = mxc_ddc_id, +}; + +static int __init mxc_ddc_init(void) +{ + int ret; + + memset(&mxc_ddc, 0, sizeof(mxc_ddc)); + + mxc_ddc.pdev = platform_device_register_simple("mxc_ddc", 0, NULL, 0); + if (IS_ERR(mxc_ddc.pdev)) { + printk(KERN_ERR + "Unable to register MXC DDC as a platform device\n"); + ret = PTR_ERR(mxc_ddc.pdev); + goto err; + } + + return i2c_add_driver(&mxc_ddc_i2c_driver); +err: + return ret; +} + +static void __exit mxc_ddc_exit(void) +{ + i2c_del_driver(&mxc_ddc_i2c_driver); + platform_device_unregister(mxc_ddc.pdev); +} + +module_init(mxc_ddc_init); +module_exit(mxc_ddc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC DDC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c index 50355b550bba..dae7b4f6c9cd 100644 --- a/drivers/video/mxc/mxc_edid.c +++ b/drivers/video/mxc/mxc_edid.c @@ -28,19 +28,9 @@ */ #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include "../edid.h" -#define MXC_EDID_LENGTH (EDID_LENGTH*2) - #undef DEBUG /* define this for verbose EDID parsing output */ #ifdef DEBUG @@ -49,56 +39,277 @@ #define DPRINTK(fmt, args...) #endif -struct mxc_ddc_data { - struct platform_device *pdev; - struct i2c_client *client; - struct delayed_work det_work; - struct fb_info *fbi; - struct mxc_edid_cfg edid_cfg; - u8 cable_plugin; - u8 edid[MXC_EDID_LENGTH]; - - u32 di; - void (*init)(void); - int (*update)(void); - struct regulator *analog_reg; -} mxc_ddc; - -#define MXC_ENABLE 1 -#define MXC_DISABLE 2 -static int g_enable_ddc; - -void mxc_edid_parse_ext_blk(unsigned char *edid, +const struct fb_videomode cea_modes[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_timming_desc_offset; + struct fb_videomode *mode, *m; unsigned char index = 0x0; + unsigned char *block; + int i, num = 0; if (edid[index++] != 0x2) /* only support cea ext block now */ - return; + return -1; if (edid[index++] != 0x3) /* only support version 3*/ - return; + return -1; + mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); + if (mode == NULL) + return -1; + + detail_timming_desc_offset = edid[index++]; 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; - return fb_edid_add_monspecs(edid, specs); + 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); + + /* short desc */ + DPRINTK("CEA Short desc timmings\n"); + index++; + while (index < detail_timming_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(cea_modes) && + (cea_modes[cea_idx].xres)) { + DPRINTK("Support CEA Format #%d\n", cea_idx); + mode[num] = cea_modes[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_timming_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; } -/* make sure edid has 256 bytes*/ -int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, - unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi) +static int mxc_edid_readblk(struct i2c_adapter *adp, + unsigned short addr, unsigned char *edid) { - u8 buf0[2] = {0, 0}; - int dat = 0; + int ret = 0, extblknum = 0; + unsigned char regaddr = 0x0; struct i2c_msg msg[2] = { { .addr = addr, .flags = 0, .len = 1, - .buf = buf0, + .buf = ®addr, }, { .addr = addr, .flags = I2C_M_RD, @@ -107,328 +318,130 @@ int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr, }, }; - if (adp == NULL) - return -EINVAL; - - memset(edid, 0, 256); - memset(cfg, 0, sizeof(struct mxc_edid_cfg)); - - buf0[0] = 0x00; - dat = i2c_transfer(adp, msg, 2); - - /* If 0x50 fails, try 0x37. */ - if (edid[1] == 0x00) { - msg[0].addr = msg[1].addr = 0x37; - dat = i2c_transfer(adp, msg, 2); - if (dat < 0) - return dat; + 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; - /* edid first block parsing */ - memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); - fb_edid_to_monspecs(edid, &fbi->monspecs); + extblknum = edid[0x7E]; - /* need read ext block? Only support one more blk now*/ - if (edid[0x7E]) { - if (edid[0x7E] > 1) - DPRINTK("Edid has %d ext block, \ - but now only support 1 ext blk\n", edid[0x7E]); - buf0[0] = 0x80; + if (extblknum) { + regaddr = 128; msg[1].buf = edid + EDID_LENGTH; - dat = i2c_transfer(adp, msg, 2); - if (dat < 0) - return dat; - /* edid ext block parsing */ - mxc_edid_parse_ext_blk(edid + 128, cfg, &fbi->monspecs); + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID ext block\n"); + return -EIO; + } } - return 0; -} -EXPORT_SYMBOL(mxc_edid_read); - -static ssize_t mxc_ddc_show_state(struct device *dev, - struct device_attribute *attr, char *buf) -{ - if (mxc_ddc.cable_plugin == 0) - strcpy(buf, "plugout\n"); - else - strcpy(buf, "plugin\n"); - - return strlen(buf); -} - -static DEVICE_ATTR(cable_state, S_IRUGO, mxc_ddc_show_state, NULL); - -static ssize_t mxc_ddc_show_name(struct device *dev, - struct device_attribute *attr, char *buf) -{ - strcpy(buf, mxc_ddc.fbi->fix.id); - sprintf(buf+strlen(buf), "\n"); - - return strlen(buf); + return extblknum; } -static DEVICE_ATTR(fb_name, S_IRUGO, mxc_ddc_show_name, NULL); - -static ssize_t mxc_ddc_show_edid(struct device *dev, - struct device_attribute *attr, char *buf) +static int mxc_edid_readsegblk(struct i2c_adapter *adp, unsigned short addr, + unsigned char *edid, int seg_num) { - int i, j, len = 0; + 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, + }, + }; - for (j = 0; j < MXC_EDID_LENGTH/16; j++) { - for (i = 0; i < 16; i++) - len += sprintf(buf+len, "0x%02X ", - mxc_ddc.edid[j*16 + i]); - len += sprintf(buf+len, "\n"); + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; } - return len; -} - -static DEVICE_ATTR(edid, S_IRUGO, mxc_ddc_show_edid, NULL); - -static void det_worker(struct work_struct *work) -{ - char event_string[16]; - char *envp[] = { event_string, NULL }; - - /* cable connection changes */ - if (mxc_ddc.update()) { - mxc_ddc.cable_plugin = 1; - sprintf(event_string, "EVENT=plugin"); - - /* make sure fb is powerdown */ - console_lock(); - fb_blank(mxc_ddc.fbi, FB_BLANK_POWERDOWN); - console_unlock(); - - if (mxc_edid_read(mxc_ddc.client->adapter, mxc_ddc.client->addr, - mxc_ddc.edid, &mxc_ddc.edid_cfg, mxc_ddc.fbi) < 0) - dev_err(&mxc_ddc.client->dev, - "MXC ddc: read edid fail\n"); - else { - if (mxc_ddc.fbi->monspecs.modedb_len > 0) { - int i; - const struct fb_videomode *mode; - struct fb_videomode m; - - fb_destroy_modelist(&mxc_ddc.fbi->modelist); - - for (i = 0; i < mxc_ddc.fbi->monspecs.modedb_len; i++) - /*FIXME now we do not support interlaced mode */ - if (!(mxc_ddc.fbi->monspecs.modedb[i].vmode & FB_VMODE_INTERLACED)) - fb_add_videomode(&mxc_ddc.fbi->monspecs.modedb[i], - &mxc_ddc.fbi->modelist); - - fb_var_to_videomode(&m, &mxc_ddc.fbi->var); - mode = fb_find_nearest_mode(&m, - &mxc_ddc.fbi->modelist); - - fb_videomode_to_var(&mxc_ddc.fbi->var, mode); - - mxc_ddc.fbi->var.activate |= FB_ACTIVATE_FORCE; - console_lock(); - mxc_ddc.fbi->flags |= FBINFO_MISC_USEREVENT; - fb_set_var(mxc_ddc.fbi, &mxc_ddc.fbi->var); - mxc_ddc.fbi->flags &= ~FBINFO_MISC_USEREVENT; - console_unlock(); - } + if (seg_num == 2) { + regaddr = 128; + msg[2].buf = edid + EDID_LENGTH; - console_lock(); - fb_blank(mxc_ddc.fbi, FB_BLANK_UNBLANK); - console_unlock(); + ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + DPRINTK("unable to read EDID block\n"); + return -EIO; } - } else { - mxc_ddc.cable_plugin = 0; - sprintf(event_string, "EVENT=plugout"); - console_lock(); - fb_blank(mxc_ddc.fbi, FB_BLANK_POWERDOWN); - console_unlock(); } - kobject_uevent_env(&mxc_ddc.pdev->dev.kobj, KOBJ_CHANGE, envp); -} - -static irqreturn_t mxc_ddc_detect_handler(int irq, void *data) -{ - if (mxc_ddc.fbi) - schedule_delayed_work(&(mxc_ddc.det_work), msecs_to_jiffies(300)); - return IRQ_HANDLED; + return ret; } -static int mxc_ddc_fb_event(struct notifier_block *nb, unsigned long val, void *v) +int mxc_edid_var_to_vic(struct fb_var_screeninfo *var) { - struct fb_event *event = v; - struct fb_info *fbi = event->info; - - if ((mxc_ddc.di)) { - if (strcmp(event->info->fix.id, "DISP3 BG - DI1")) - return 0; - } else { - if (strcmp(event->info->fix.id, "DISP3 BG")) - return 0; - } + int i; + struct fb_videomode m; - switch (val) { - case FB_EVENT_FB_REGISTERED: - if (mxc_ddc.fbi != NULL) + for (i = 0; i < ARRAY_SIZE(cea_modes); i++) { + fb_var_to_videomode(&m, var); + if (fb_mode_is_equal(&m, &cea_modes[i])) break; - mxc_ddc.fbi = fbi; - break; } - return 0; -} - -static struct notifier_block nb = { - .notifier_call = mxc_ddc_fb_event, -}; - -static int __devinit mxc_ddc_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - int ret = 0; - struct fb_info edid_fbi; - struct fsl_mxc_ddc_platform_data *plat = client->dev.platform_data; - - if (plat->boot_enable && !g_enable_ddc) - g_enable_ddc = MXC_ENABLE; - if (!g_enable_ddc) - g_enable_ddc = MXC_DISABLE; - if (g_enable_ddc == MXC_DISABLE) { - printk(KERN_WARNING "By setting, DDC driver will not be enabled\n"); + if (i == ARRAY_SIZE(cea_modes)) return 0; - } - - mxc_ddc.client = client; - mxc_ddc.di = plat->di; - mxc_ddc.init = plat->init; - mxc_ddc.update = plat->update; - - if (!mxc_ddc.update) - return -EINVAL; - - mxc_ddc.analog_reg = regulator_get(&mxc_ddc.pdev->dev, plat->analog_regulator); - if (!IS_ERR(mxc_ddc.analog_reg)) { - regulator_set_voltage(mxc_ddc.analog_reg, 2775000, 2775000); - regulator_enable(mxc_ddc.analog_reg); - } - if (mxc_ddc.init) - mxc_ddc.init(); - - if (mxc_ddc.update()) { - mxc_ddc.cable_plugin = 1; - /* try to read edid */ - if (mxc_edid_read(client->adapter, client->addr, - mxc_ddc.edid, &mxc_ddc.edid_cfg, &edid_fbi) < 0) - dev_warn(&client->dev, "Can not read edid\n"); -#if defined(CONFIG_MXC_IPU_V3) && defined(CONFIG_FB_MXC_SYNC_PANEL) - else - mxcfb_register_mode(mxc_ddc.di, edid_fbi.monspecs.modedb, - edid_fbi.monspecs.modedb_len, MXC_DISP_DDC_DEV); -#endif - } else - mxc_ddc.cable_plugin = 0; - - if (client->irq) { - ret = request_irq(client->irq, mxc_ddc_detect_handler, - IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, - "ddc_det", &mxc_ddc); - if (ret < 0) { - dev_warn(&client->dev, - "MXC ddc: cound not request det irq %d\n", - client->irq); - goto err; - } else { - INIT_DELAYED_WORK(&(mxc_ddc.det_work), det_worker); - ret = device_create_file(&mxc_ddc.pdev->dev, &dev_attr_fb_name); - if (ret < 0) - dev_warn(&client->dev, - "MXC ddc: cound not create sys node for fb name\n"); - ret = device_create_file(&mxc_ddc.pdev->dev, &dev_attr_cable_state); - if (ret < 0) - dev_warn(&client->dev, - "MXC ddc: cound not create sys node for cable state\n"); - ret = device_create_file(&mxc_ddc.pdev->dev, &dev_attr_edid); - if (ret < 0) - dev_warn(&client->dev, - "MXC ddc: cound not create sys node for edid\n"); - } - } - - fb_register_client(&nb); - -err: - return ret; + return i; } +EXPORT_SYMBOL(mxc_edid_var_to_vic); -static int __devexit mxc_ddc_remove(struct i2c_client *client) +/* 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) { - fb_unregister_client(&nb); - if (!IS_ERR(mxc_ddc.analog_reg)) - regulator_disable(mxc_ddc.analog_reg); - return 0; -} + int ret = 0, extblknum; + if (!adp || !edid || !cfg || !fbi) + return -EINVAL; -static int __init enable_ddc_setup(char *options) -{ - if (!strcmp(options, "=off")) - g_enable_ddc = MXC_DISABLE; - else - g_enable_ddc = MXC_ENABLE; + memset(edid, 0, EDID_LENGTH*4); + memset(cfg, 0, sizeof(struct mxc_edid_cfg)); - return 1; -} -__setup("ddc", enable_ddc_setup); + extblknum = mxc_edid_readblk(adp, addr, edid); + if (extblknum < 0) + return extblknum; -static const struct i2c_device_id mxc_ddc_id[] = { - { "mxc_ddc", 0 }, - {}, -}; -MODULE_DEVICE_TABLE(i2c, mxc_ddc_id); - -static struct i2c_driver mxc_ddc_i2c_driver = { - .driver = { - .name = "mxc_ddc", - }, - .probe = mxc_ddc_probe, - .remove = mxc_ddc_remove, - .id_table = mxc_ddc_id, -}; + /* edid first block parsing */ + memset(&fbi->monspecs, 0, sizeof(fbi->monspecs)); + fb_edid_to_monspecs(edid, &fbi->monspecs); -static int __init mxc_ddc_init(void) -{ - int ret; + if (extblknum) { + int i; - memset(&mxc_ddc, 0, sizeof(mxc_ddc)); + /* need read segment block? */ + if (extblknum > 1) { + ret = mxc_edid_readsegblk(adp, addr, + edid + EDID_LENGTH*2, extblknum - 1); + if (ret < 0) + return ret; + } - mxc_ddc.pdev = platform_device_register_simple("mxc_ddc", 0, NULL, 0); - if (IS_ERR(mxc_ddc.pdev)) { - printk(KERN_ERR - "Unable to register MXC DDC as a platform device\n"); - ret = PTR_ERR(mxc_ddc.pdev); - goto err; + for (i = 1; i <= extblknum; i++) + /* edid ext block parsing */ + mxc_edid_parse_ext_blk(edid + i*EDID_LENGTH, + cfg, &fbi->monspecs); } - return i2c_add_driver(&mxc_ddc_i2c_driver); -err: - return ret; -} - -static void __exit mxc_ddc_exit(void) -{ - i2c_del_driver(&mxc_ddc_i2c_driver); - platform_device_unregister(mxc_ddc.pdev); + return 0; } +EXPORT_SYMBOL(mxc_edid_read); -module_init(mxc_ddc_init); -module_exit(mxc_ddc_exit); - -MODULE_AUTHOR("Freescale Semiconductor, Inc."); -MODULE_DESCRIPTION("MXC DDC driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_sii902x.c b/drivers/video/mxc/mxcfb_sii902x.c index 08d8c2ce05d5..43613bbd9e26 100644 --- a/drivers/video/mxc/mxcfb_sii902x.c +++ b/drivers/video/mxc/mxcfb_sii902x.c @@ -43,15 +43,145 @@ #include #include #include +#include #include #include #include +#include #include #include #include -#define IPU_DISP_PORT 0 -#define SII_EDID_LEN 256 +#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 + #define MXC_ENABLE 1 #define MXC_DISABLE 2 static int g_enable_hdmi; @@ -64,29 +194,101 @@ struct sii902x_data { struct delayed_work det_work; struct fb_info *fbi; struct mxc_edid_cfg edid_cfg; - u8 cable_plugin; + char *fb_id; + bool cable_plugin; + bool rx_powerup; + bool need_mode_change; u8 edid[SII_EDID_LEN]; -} sii902x; -static void sii902x_poweron(void); -static void sii902x_poweroff(void); -static void (*sii902x_reset) (void); + 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 struct sii902x_data *g_sii902x; -static __attribute__ ((unused)) void dump_regs(u8 reg, int len) +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); + 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", + 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) { - strcpy(buf, sii902x.fbi->fix.id); + struct sii902x_data *sii902x = dev_get_drvdata(dev); + + strcpy(buf, sii902x->fbi->fix.id); sprintf(buf+strlen(buf), "\n"); return strlen(buf); @@ -97,7 +299,9 @@ 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) { - if (sii902x.cable_plugin == 0) + struct sii902x_data *sii902x = dev_get_drvdata(dev); + + if (sii902x->cable_plugin == false) strcpy(buf, "plugout\n"); else strcpy(buf, "plugin\n"); @@ -110,12 +314,13 @@ 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]); + sii902x->edid[j*16 + i]); len += sprintf(buf+len, "\n"); } @@ -124,17 +329,304 @@ static ssize_t sii902x_show_edid(struct device *dev, static DEVICE_ATTR(edid, S_IRUGO, sii902x_show_edid, NULL); -static void sii902x_setup(struct fb_info *fbi) +/*------------------------------------------------------------------------------ + * 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, "Sii902x: setup..\n"); + dev_dbg(&sii902x->client->dev, "setup..\n"); - /* Power up */ - i2c_smbus_write_byte_data(sii902x.client, 0x1E, 0x00); + sii902x->vic = mxc_edid_var_to_vic(&fbi->var); /* set TPI video mode */ data[0] = PICOS2KHZ(fbi->var.pixclock) / 10; @@ -147,33 +639,132 @@ static void sii902x_setup(struct fb_info *fbi) data[1] = refresh * 100; tmp = (u8 *)data; for (i = 0; i < 8; i++) - i2c_smbus_write_byte_data(sii902x.client, i, tmp[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); - /* input bus/pixel: full pixel wide (24bit), rising edge */ - i2c_smbus_write_byte_data(sii902x.client, 0x08, 0x70); - /* Set input format to RGB */ - i2c_smbus_write_byte_data(sii902x.client, 0x09, 0x00); - /* set output format to RGB */ - i2c_smbus_write_byte_data(sii902x.client, 0x0A, 0x00); - /* audio setup */ - i2c_smbus_write_byte_data(sii902x.client, 0x25, 0x00); - i2c_smbus_write_byte_data(sii902x.client, 0x26, 0x40); - i2c_smbus_write_byte_data(sii902x.client, 0x27, 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 fb_info *fbi) +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, 0x1A); + old = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG); - i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x4); + 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, 0x1A); + dat = i2c_smbus_read_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG); } while ((!(dat & 0x2)) && cnt); if (!cnt) { @@ -181,104 +772,228 @@ static int sii902x_read_edid(struct fb_info *fbi) goto done; } - i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x06); + 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); + 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, 0x1A, old & ~0x6); + i2c_smbus_write_byte_data(sii902x->client, TPI_SYSTEM_CONTROL_DATA_REG, old & ~0x6); msleep(10); - dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + 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, 0x1A, old); + 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 fb_info *fbi) +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 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 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; + acquire_console_sem(); + sii902x->fbi->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(sii902x->fbi, &sii902x->fbi->var); + sii902x->fbi->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); + 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) { - int dat; + 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 }; - dat = i2c_smbus_read_byte_data(sii902x.client, 0x3D); - if (dat & 0x1) { + status = i2c_smbus_read_byte_data(sii902x->client, TPI_INTERRUPT_STATUS_REG); + + /* check cable status */ + if (status & HOT_PLUG_EVENT) { /* cable connection changes */ - if (dat & 0x4) { - sii902x.cable_plugin = 1; - sprintf(event_string, "EVENT=plugin"); - - /* make sure fb is powerdown */ - console_lock(); - fb_blank(sii902x.fbi, FB_BLANK_POWERDOWN); - console_unlock(); - - if (sii902x_read_edid(sii902x.fbi) < 0) - dev_err(&sii902x.client->dev, - "Sii902x: read edid fail\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.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(); - } - - console_lock(); - fb_blank(sii902x.fbi, FB_BLANK_UNBLANK); - console_unlock(); + 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); } - } else { - sii902x.cable_plugin = 0; - sprintf(event_string, "EVENT=plugout"); - console_lock(); - fb_blank(sii902x.fbi, FB_BLANK_POWERDOWN); - console_unlock(); + 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); } - kobject_uevent_env(&sii902x.pdev->dev.kobj, KOBJ_CHANGE, envp); } - i2c_smbus_write_byte_data(sii902x.client, 0x3D, dat); + + /* 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) { - if (sii902x.fbi) - schedule_delayed_work(&(sii902x.det_work), msecs_to_jiffies(20)); + struct sii902x_data *sii902x = data; + if (sii902x->fbi) + schedule_delayed_work(&(sii902x->det_work), msecs_to_jiffies(20)); + return IRQ_HANDLED; } @@ -287,29 +1002,23 @@ static int sii902x_fb_event(struct notifier_block *nb, unsigned long val, void * struct fb_event *event = v; struct fb_info *fbi = event->info; - /* assume sii902x on DI0 only */ - if ((IPU_DISP_PORT)) { - if (strcmp(event->info->fix.id, "DISP3 BG - DI1")) - return 0; - } else { - if (strcmp(event->info->fix.id, "DISP3 BG")) - return 0; - } + if (strcmp(event->info->fix.id, g_sii902x->fb_id)) + return 0; switch (val) { case FB_EVENT_FB_REGISTERED: - if (sii902x.fbi != NULL) + if (g_sii902x->fbi != NULL) break; - sii902x.fbi = fbi; + g_sii902x->fbi = fbi; break; case FB_EVENT_MODE_CHANGE: - sii902x_setup(fbi); + sii902x_setup(g_sii902x, fbi); break; case FB_EVENT_BLANK: if (*((int *)event->data) == FB_BLANK_UNBLANK) - sii902x_poweron(); + sii902x_poweron(g_sii902x); else - sii902x_poweroff(); + sii902x_poweroff(g_sii902x); break; } return 0; @@ -319,12 +1028,54 @@ static struct notifier_block nb = { .notifier_call = sii902x_fb_event, }; +static int __devinit sii902x_TPI_init(struct i2c_client *client) +{ + struct 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 __devinit sii902x_probe(struct i2c_client *client, const struct i2c_device_id *id) { - int i, dat, ret; - struct fsl_mxc_lcd_platform_data *plat = client->dev.platform_data; + int ret = 0; + struct mxc_lcd_platform_data *plat = client->dev.platform_data; struct fb_info edid_fbi; + struct sii902x_data *sii902x; if (plat->boot_enable && !g_enable_hdmi) @@ -334,99 +1085,153 @@ static int __devinit sii902x_probe(struct i2c_client *client, if (g_enable_hdmi == MXC_DISABLE) { printk(KERN_WARNING "By setting, SII driver will not be enabled\n"); - return 0; + return -ENODEV; } - sii902x.client = client; + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -ENODEV; - 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); - } - 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); + sii902x = kzalloc(sizeof(struct sii902x_data), GFP_KERNEL); + if (!sii902x) { + ret = -ENOMEM; + goto alloc_failed; } - if (plat->reset) { - sii902x_reset = plat->reset; - sii902x_reset(); - } + sii902x->client = client; + sii902x->fb_id = plat->fb_id; + g_sii902x = sii902x; - /* Set 902x in hardware TPI mode on and jump out of D3 state */ - if (i2c_smbus_write_byte_data(sii902x.client, 0xc7, 0x00) < 0) { - dev_err(&sii902x.client->dev, - "Sii902x: cound not find device\n"); - return -ENODEV; + 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; } - /* read device ID */ - for (i = 10; i > 0; i--) { - dat = i2c_smbus_read_byte_data(sii902x.client, 0x1B); - printk(KERN_DEBUG "Sii902x: read id = 0x%02X", dat); - if (dat == 0xb0) { - dat = i2c_smbus_read_byte_data(sii902x.client, 0x1C); - printk(KERN_DEBUG "-0x%02X", dat); - dat = i2c_smbus_read_byte_data(sii902x.client, 0x1D); - printk(KERN_DEBUG "-0x%02X", dat); - dat = i2c_smbus_read_byte_data(sii902x.client, 0x30); - printk(KERN_DEBUG "-0x%02X\n", dat); - break; + 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 (i == 0) { - dev_err(&sii902x.client->dev, - "Sii902x: cound not find device\n"); - return -ENODEV; + 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(client); + if (ret < 0) + goto init_failed; + /* try to read edid */ - if (sii902x_read_edid(&edid_fbi) < 0) - dev_warn(&sii902x.client->dev, "Can not read edid\n"); + ret = sii902x_read_edid(sii902x, &edid_fbi); + if (ret < 0) + dev_warn(&sii902x->client->dev, "Can not read edid\n"); + #if defined(CONFIG_MXC_IPU_V3) && defined(CONFIG_FB_MXC_SYNC_PANEL) - else - mxcfb_register_mode(IPU_DISP_PORT, edid_fbi.monspecs.modedb, + if (ret >= 0) { + int di = 0; + if (!strcmp(sii902x->fb_id, "DISP3 BG - DI1")) + di = 1; + mxcfb_register_mode(di, edid_fbi.monspecs.modedb, + edid_fbi.monspecs.modedb_len, MXC_DISP_DDC_DEV); + } +#endif +#if defined(CONFIG_FB_MXC_ELCDIF_FB) + if (ret >= 0) + mxcfb_elcdif_register_mode(edid_fbi.monspecs.modedb, edid_fbi.monspecs.modedb_len, MXC_DISP_DDC_DEV); #endif - if (sii902x.client->irq) { - ret = request_irq(sii902x.client->irq, sii902x_detect_handler, + if (sii902x->client->irq) { + ret = request_irq(sii902x->client->irq, sii902x_detect_handler, IRQF_TRIGGER_FALLING, - "SII902x_det", &sii902x); + "SII902x_det", sii902x); if (ret < 0) - dev_warn(&sii902x.client->dev, - "Sii902x: cound not request det irq %d\n", - sii902x.client->irq); + 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, 0x3c, 0x01); - INIT_DELAYED_WORK(&(sii902x.det_work), det_worker); + 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); + + ret = device_create_file(&sii902x->pdev->dev, &dev_attr_fb_name); if (ret < 0) - dev_warn(&sii902x.client->dev, - "Sii902x: cound not create sys node for fb name\n"); - ret = device_create_file(&sii902x.pdev->dev, &dev_attr_cable_state); + 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, - "Sii902x: cound not create sys node for cable state\n"); - ret = device_create_file(&sii902x.pdev->dev, &dev_attr_edid); + 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, - "Sii902x: cound not create sys node for edid\n"); + dev_warn(&sii902x->client->dev, + "cound not create sys node for edid\n"); + + dev_set_drvdata(&sii902x->pdev->dev, sii902x); } fb_register_client(&nb); - return 0; + i2c_set_clientdata(client, sii902x); + + return ret; + +init_failed: +get_pins_failed: + platform_device_unregister(sii902x->pdev); +register_pltdev_failed: + kfree(sii902x); +alloc_failed: + return ret; } static int __devexit sii902x_remove(struct i2c_client *client) { + struct sii902x_data *sii902x = i2c_get_clientdata(client); + struct mxc_lcd_platform_data *plat = client->dev.platform_data; + + if (sii902x->client->irq) + free_irq(sii902x->client->irq, sii902x); + fb_unregister_client(&nb); - sii902x_poweroff(); + + sii902x_poweroff(sii902x); + + /* Release HDMI pins */ + if (plat->put_pins) + plat->put_pins(); + + platform_device_unregister(sii902x->pdev); + + kfree(sii902x); + return 0; } @@ -442,27 +1247,6 @@ static int sii902x_resume(struct i2c_client *client) return 0; } -static void sii902x_poweron(void) -{ - /* Turn on DVI or HDMI */ - if (sii902x.edid_cfg.hdmi_cap) - i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x01); - else - i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x00); - return; -} - -static void sii902x_poweroff(void) -{ - /* disable tmds before changing resolution */ - if (sii902x.edid_cfg.hdmi_cap) - i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x11); - else - i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x10); - - return; -} - static const struct i2c_device_id sii902x_id[] = { { "sii902x", 0 }, {}, @@ -482,27 +1266,12 @@ static struct i2c_driver sii902x_i2c_driver = { static int __init sii902x_init(void) { - int ret; - - memset(&sii902x, 0, sizeof(sii902x)); - - sii902x.pdev = platform_device_register_simple("sii902x", 0, NULL, 0); - if (IS_ERR(sii902x.pdev)) { - printk(KERN_ERR - "Unable to register Sii902x as a platform device\n"); - ret = PTR_ERR(sii902x.pdev); - goto err; - } - return i2c_add_driver(&sii902x_i2c_driver); -err: - return ret; } static void __exit sii902x_exit(void) { i2c_del_driver(&sii902x_i2c_driver); - platform_device_unregister(sii902x.pdev); } static int __init enable_hdmi_setup(char *options)