]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00277843-02 imx6sl: Enable sii902x hdmi function
authorSandor Yu <R01008@freescale.com>
Wed, 4 Sep 2013 07:45:00 +0000 (15:45 +0800)
committerJason Liu <r64343@freescale.com>
Wed, 30 Oct 2013 01:55:30 +0000 (09:55 +0800)
- Add sii902x hdmi chip driver.
- Sii902x initialized as I2C device.
- Support resolution change by application or FB command line.
- Max support resolution 1080p60.
- Support read EDID from hdmi sink.
- Support hdmi cable hotplug.
- Support default video mode read from dts.

Signed-off-by: Sandor Yu <R01008@freescale.com>
drivers/video/mxc/Kconfig
drivers/video/mxc/Makefile
drivers/video/mxc/mxsfb_sii902x.c [new file with mode: 0644]

index e1f5627830e8192b5ce4e841437f90448e9ba668..759ff096826cb70b93aecb80f3e0459234ff0054 100644 (file)
@@ -57,3 +57,7 @@ config FB_MXC_EINK_AUTO_UPDATE_MODE
     bool "E-Ink Auto-update Mode Support"
     default n
     depends on FB_MXC_EINK_PANEL
+
+config FB_MXS_SII902X
+       tristate "Si Image SII9022 DVI/HDMI Interface Chip"
+       depends on FB_MXS && I2C
index f8b589c4ec4264f69643e285be5f32bae84b89d7..6a5b855dbafa053c9634eb2131ddfdad74e66c2f 100644 (file)
@@ -5,3 +5,4 @@ obj-$(CONFIG_FB_MXC_HDMI)                       += mxc_hdmi.o
 obj-$(CONFIG_FB_MXC_EDID)                      += mxc_edid.o
 obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_dispdrv.o mxc_lcdif.o mxc_ipuv3_fb.o
 obj-$(CONFIG_FB_MXC_EINK_PANEL)             += mxc_epdc_fb.o
+obj-$(CONFIG_FB_MXS_SII902X) += mxsfb_sii902x.o
diff --git a/drivers/video/mxc/mxsfb_sii902x.c b/drivers/video/mxc/mxsfb_sii902x.c
new file mode 100644 (file)
index 0000000..531d3f6
--- /dev/null
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2010-2013 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 Sii902x.
+ */
+
+/*!
+ * @file mxsfb_sii902x.c
+ *
+ * @brief  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/fsl_devices.h>
+#include <linux/interrupt.h>
+#include <linux/reset.h>
+#include <asm/mach-types.h>
+#include <video/mxc_edid.h>
+
+#define SII_EDID_LEN   512
+#define DRV_NAME "sii902x"
+
+struct sii902x_data {
+       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;
+       bool dft_mode_set;
+       const char *mode_str;
+       int bits_per_pixel;
+} sii902x;
+
+static void sii902x_poweron(void);
+static void sii902x_poweroff(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.client->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 sii902x_cable_connected(void)
+{
+       int i;
+       const struct fb_videomode *mode;
+       struct fb_videomode m;
+
+       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) {
+
+                       fb_destroy_modelist(&sii902x.fbi->modelist);
+
+                       for (i = 0; i < sii902x.fbi->monspecs.modedb_len; i++) {
+
+                               mode = &sii902x.fbi->monspecs.modedb[i];
+
+                               if (!(mode->vmode & FB_VMODE_INTERLACED)) {
+
+                                       dev_dbg(&sii902x.client->dev, "Added mode %d:", i);
+                                       dev_dbg(&sii902x.client->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);
+                               }
+                       }
+
+                       /* Set the default mode only once. */
+                       if (!sii902x.dft_mode_set &&
+                                       sii902x.mode_str && sii902x.bits_per_pixel) {
+
+                               dev_dbg(&sii902x.client->dev, "%s: setting to default=%s bpp=%d\n",
+                                               __func__, sii902x.mode_str, sii902x.bits_per_pixel);
+
+                               fb_find_mode(&sii902x.fbi->var, sii902x.fbi,
+                                               sii902x.mode_str, NULL, 0, NULL,
+                                               sii902x.bits_per_pixel);
+
+                               sii902x.dft_mode_set = true;
+                       }
+
+                       fb_var_to_videomode(&m, &sii902x.fbi->var);
+                       dump_fb_videomode(&m);
+
+                       mode = fb_find_nearest_mode(&m,
+                                       &sii902x.fbi->modelist);
+
+                       /* update fbi mode  */
+                       sii902x.fbi->mode = (struct fb_videomode *)mode;
+
+                       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();
+               }
+               /* Power on sii902x */
+               sii902x_poweron();
+       }
+}
+
+static void det_worker(struct work_struct *work)
+{
+       int dat;
+       char event_string[16];
+       char *envp[] = { event_string, NULL };
+
+       dev_dbg(&sii902x.client->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.client->dev, "EVENT=plugin\n");
+                       sprintf(event_string, "EVENT=plugin");
+                       sii902x_cable_connected();
+               } else {
+                       sii902x.cable_plugin = 0;
+                       dev_dbg(&sii902x.client->dev, "EVENT=plugout\n");
+                       sprintf(event_string, "EVENT=plugout");
+                       /* Power off sii902x */
+                       sii902x_poweroff();
+               }
+               kobject_uevent_env(&sii902x.client->dev.kobj, KOBJ_CHANGE, envp);
+       }
+       i2c_smbus_write_byte_data(sii902x.client, 0x3D, dat);
+
+       dev_dbg(&sii902x.client->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.client->dev, "%s event=0x%lx\n", __func__, val);
+
+       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.client->dev, "FB_BLANK_UNBLANK\n");
+                       sii902x_poweron();
+               } else {
+                       dev_dbg(&sii902x.client->dev, "FB_BLANK_BLANK\n");
+                       sii902x_poweroff();
+               }
+               break;
+       }
+       return 0;
+}
+
+static struct notifier_block nb = {
+       .notifier_call = sii902x_fb_event,
+};
+
+static int mxsfb_get_of_property(void)
+{
+       struct device_node *np = sii902x.client->dev.of_node;
+       const char *mode_str;
+       int bits_per_pixel, ret;
+
+       ret = of_property_read_string(np, "mode_str", &mode_str);
+       if (ret < 0) {
+               dev_warn(&sii902x.client->dev, "get of property mode_str fail\n");
+               return ret;
+       }
+       ret = of_property_read_u32(np, "bits-per-pixel", &bits_per_pixel);
+       if (ret) {
+               dev_warn(&sii902x.client->dev, "get of property bpp fail\n");
+               return ret;
+       }
+
+       sii902x.mode_str = mode_str;
+       sii902x.bits_per_pixel = bits_per_pixel;
+
+       return ret;
+}
+
+static int sii902x_probe(struct i2c_client *client,
+               const struct i2c_device_id *id)
+{
+       int i, dat, ret;
+       struct fb_info edid_fbi;
+
+       memset(&sii902x, 0, sizeof(sii902x));
+
+       sii902x.client = client;
+
+       dev_dbg(&sii902x.client->dev, "%s\n", __func__);;
+
+       /* Reset sii902x */
+       ret = device_reset(&sii902x.client->dev);
+       if (ret) {
+               dev_err(&sii902x.client->dev, "failed to reset: %d\n", ret);
+               return -ENODEV;
+       }
+
+       /* 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 (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.client->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.client->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.client->dev, &dev_attr_edid);
+               if (ret < 0)
+                       dev_warn(&sii902x.client->dev,
+                               "Sii902x: cound not create sys node for edid\n");
+
+       }
+
+       mxsfb_get_of_property();
+       fb_register_client(&nb);
+
+       return 0;
+}
+
+static int sii902x_remove(struct i2c_client *client)
+{
+       fb_unregister_client(&nb);
+       sii902x_poweroff();
+
+       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[] = {
+       { DRV_NAME, 0},
+       { },
+};
+MODULE_DEVICE_TABLE(i2c, sii902x_id);
+
+static const struct of_device_id sii902x_dt_ids[] = {
+       { .compatible = "SiI,sii902x", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sii902x_dt_ids);
+
+static struct i2c_driver sii902x_driver = {
+       .driver = {
+               .name = DRV_NAME,
+               .owner = THIS_MODULE,
+               .of_match_table = sii902x_dt_ids,
+       },
+       .probe          = sii902x_probe,
+       .remove         = sii902x_remove,
+       .id_table       = sii902x_id,
+};
+
+module_i2c_driver(sii902x_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("SII902x DVI/HDMI driver");
+MODULE_LICENSE("GPL");