From e17b1692f3380961972e97281a1263bb4147fc83 Mon Sep 17 00:00:00 2001 From: Sandor Yu Date: Fri, 28 Sep 2012 13:15:27 +0800 Subject: [PATCH] ENGR00225960-03 sii902x: Added sii902x HDMI driver with ELCDIF FB Enable sii902x HDMI driver with ELCDIF framebuffer interface. HDMI cable Hotplug support. Video mode switch support. Blank/Unblank HDMI display support. Added config variable CONFIG_FB_MXC_SII902X_ELCDIF for sii902x_elcdif module Signed-off-by: Sandor Yu --- drivers/video/mxc/Kconfig | 4 + drivers/video/mxc/Makefile | 1 + drivers/video/mxc/mxcfb_sii902x_elcdif.c | 560 +++++++++++++++++++++++ 3 files changed, 565 insertions(+) create mode 100644 drivers/video/mxc/mxcfb_sii902x_elcdif.c diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig index f0afddc95bd3..8064978733ae 100644 --- a/drivers/video/mxc/Kconfig +++ b/drivers/video/mxc/Kconfig @@ -60,6 +60,10 @@ config FB_MXC_SII902X depends on FB_MXC_SYNC_PANEL && I2C tristate "Si Image SII9022 DVI/HDMI Interface Chip" +config FB_MXC_SII902X_ELCDIF + depends on FB_MXC_SYNC_PANEL && I2C + tristate "Si Image SII9022 DVI/HDMI Interface Chip for ELCDIF FB" + config FB_MXC_CH7026 depends on FB_MXC_SYNC_PANEL tristate "Chrontel CH7026 VGA Interface Chip" diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile index 21aef052ecd6..4ce581ad3466 100644 --- a/drivers/video/mxc/Makefile +++ b/drivers/video/mxc/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_FB_MXC_TVOUT_TVE) += tve.o obj-$(CONFIG_FB_MXC_SII902X) += mxcfb_sii902x.o +obj-$(CONFIG_FB_MXC_SII902X_ELCDIF) += mxcfb_sii902x_elcdif.o obj-$(CONFIG_FB_MXC_LDB) += ldb.o obj-$(CONFIG_FB_MXC_MIPI_DSI) += mipi_dsi.o obj-$(CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL) += mxcfb_hx8369_wvga.o diff --git a/drivers/video/mxc/mxcfb_sii902x_elcdif.c b/drivers/video/mxc/mxcfb_sii902x_elcdif.c new file mode 100644 index 000000000000..ceab3e66e91c --- /dev/null +++ b/drivers/video/mxc/mxcfb_sii902x_elcdif.c @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2010-2012 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_elcdif.c + * + * @brief MXC ELCDIF Frame buffer driver for SII902x + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SII_EDID_LEN 512 +#define MXC_ENABLE 1 +#define MXC_DISABLE 2 + +struct sii902x_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[SII_EDID_LEN]; + bool waiting_for_fb; +} sii902x; + +static void sii902x_poweron(void); +static void sii902x_poweroff(void); +static void (*sii902x_reset) (void); + +#ifdef DEBUG +static void dump_fb_videomode(struct fb_videomode *m) +{ + pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + m->refresh, m->xres, m->yres, m->pixclock, m->left_margin, + m->right_margin, m->upper_margin, m->lower_margin, + m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); +} +#else +static void dump_fb_videomode(struct fb_videomode *m) +{} +#endif + +static __attribute__ ((unused)) void dump_regs(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) +{ + 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) +{ + if (sii902x.cable_plugin == 0) + 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) +{ + 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); + +static void sii902x_setup(struct fb_info *fbi) +{ + u16 data[4]; + u32 refresh; + u8 *tmp; + int i; + + dev_dbg(&sii902x.client->dev, "Sii902x: setup..\n"); + + /* Power up */ + i2c_smbus_write_byte_data(sii902x.client, 0x1E, 0x00); + + /* 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]); + + /* 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); +} + +#ifdef CONFIG_FB_MODE_HELPERS +static int sii902x_read_edid(struct fb_info *fbi) +{ + int old, dat, ret, cnt = 100; + unsigned short addr = 0x50; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + old = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x4); + do { + cnt--; + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + } while ((!(dat & 0x2)) && cnt); + + if (!cnt) { + ret = -1; + goto done; + } + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x06); + + /* 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, 0x1A, old & ~0x6); + msleep(10); + dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A); + } while ((dat & 0x6) && cnt); + + if (!cnt) + ret = -1; + +done: + + i2c_smbus_write_byte_data(sii902x.client, 0x1A, old); + return ret; +} +#else +static int sii902x_read_edid(struct fb_info *fbi) +{ + return -1; +} +#endif + +static void det_worker(struct work_struct *work) +{ + int dat; + char event_string[16]; + char *envp[] = { event_string, NULL }; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + dat = i2c_smbus_read_byte_data(sii902x.client, 0x3D); + if (dat & 0x1) { + /* cable connection changes */ + if (dat & 0x4) { + sii902x.cable_plugin = 1; + dev_dbg(&sii902x.pdev->dev, "EVENT=plugin\n"); + 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 */ + mode = &sii902x.fbi->monspecs.modedb[i]; + + if (!(mode->vmode & FB_VMODE_INTERLACED)) { + + dev_dbg(&sii902x.pdev->dev, "Added mode %d:", i); + dev_dbg(&sii902x.pdev->dev, + "xres = %d, yres = %d, freq = %d, vmode = %d, flag = %d\n", + mode->xres, mode->yres, mode->refresh, + mode->vmode, mode->flag); + + fb_add_videomode(mode, &sii902x.fbi->modelist); + } + } + + fb_var_to_videomode(&m, &sii902x.fbi->var); + dump_fb_videomode(&m); + + 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(); + } + } else { + sii902x.cable_plugin = 0; + dev_dbg(&sii902x.pdev->dev, "EVENT=plugout\n"); + 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); + } + i2c_smbus_write_byte_data(sii902x.client, 0x3D, dat); + + dev_dbg(&sii902x.pdev->dev, "exit %s\n", __func__); + +} + +static irqreturn_t sii902x_detect_handler(int irq, void *data) +{ + if (sii902x.fbi) + schedule_delayed_work(&(sii902x.det_work), msecs_to_jiffies(20)); + else + sii902x.waiting_for_fb = true; + + 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; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + switch (val) { + case FB_EVENT_FB_REGISTERED: + if (sii902x.fbi == NULL) { + sii902x.fbi = fbi; + if (sii902x.waiting_for_fb) + det_worker(NULL); + } + fb_show_logo(fbi, 0); + break; + case FB_EVENT_MODE_CHANGE: + sii902x_setup(fbi); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + dev_dbg(&sii902x.pdev->dev, "FB_BLANK_UNBLANK\n"); + sii902x_poweron(); + } else { + dev_dbg(&sii902x.pdev->dev, "FB_BLANK_BLANK\n"); + sii902x_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = sii902x_fb_event, +}; + +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; + struct fb_info edid_fbi; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__);; + + sii902x.client = client; + + /* Claim HDMI pins */ + if (plat->get_pins) + if (!plat->get_pins()) + return -EACCES; + + if (plat->reset) { + sii902x_reset = plat->reset; + sii902x_reset(); + } + + /* 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; + } + + /* 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 (i == 0) { + dev_err(&sii902x.client->dev, + "Sii902x: cound not find device\n"); + return -ENODEV; + } + + /* try to read edid */ + ret = sii902x_read_edid(&edid_fbi); + if (ret < 0) + dev_warn(&sii902x.client->dev, "Can not read edid\n"); + + if (ret >= 0) + mxcfb_elcdif_register_mode(edid_fbi.monspecs.modedb, + edid_fbi.monspecs.modedb_len, MXC_DISP_DDC_DEV); + + 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, + "Sii902x: 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); + } + 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); + 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); + if (ret < 0) + dev_warn(&sii902x.client->dev, + "Sii902x: cound not create sys node for edid\n"); + + } + + fb_register_client(&nb); + + dev_dbg(&sii902x.pdev->dev, "%s exit\n", __func__);; + + return 0; +} + +static int __devexit sii902x_remove(struct i2c_client *client) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x.client->dev.platform_data; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + fb_unregister_client(&nb); + sii902x_poweroff(); + + /* Release HDMI pins */ + if (plat->put_pins) + plat->put_pins(); + + return 0; +} + +static int sii902x_suspend(struct i2c_client *client, pm_message_t message) +{ + /*TODO*/ + return 0; +} + +static int sii902x_resume(struct i2c_client *client) +{ + /*TODO*/ + return 0; +} + +static void sii902x_poweron(void) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x.client->dev.platform_data; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + /* Enable pins to HDMI */ + if (plat->enable_pins) + plat->enable_pins(); + + /* 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) +{ + struct fsl_mxc_lcd_platform_data *plat = sii902x.client->dev.platform_data; + + dev_dbg(&sii902x.pdev->dev, "%s\n", __func__); + + /* 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); + + /* Disable pins to HDMI */ + if (plat->disable_pins) + plat->disable_pins(); + + return; +} + +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, + .suspend = sii902x_suspend, + .resume = sii902x_resume, + .id_table = sii902x_id, +}; + +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); +} + +module_init(sii902x_init); +module_exit(sii902x_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SII902x DVI/HDMI driver"); +MODULE_LICENSE("GPL"); -- 2.39.5