Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / gpu / drm / gma500 / tc35876x-dsi-lvds.c
diff --git a/kernel/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c b/kernel/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c
new file mode 100644 (file)
index 0000000..771ff66
--- /dev/null
@@ -0,0 +1,829 @@
+/*
+ * Copyright © 2011 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include "mdfld_dsi_dpi.h"
+#include "mdfld_output.h"
+#include "mdfld_dsi_pkg_sender.h"
+#include "tc35876x-dsi-lvds.h"
+#include <linux/i2c/tc35876x.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/intel_scu_ipc.h>
+
+static struct i2c_client *tc35876x_client;
+static struct i2c_client *cmi_lcd_i2c_client;
+
+#define FLD_MASK(start, end)   (((1 << ((start) - (end) + 1)) - 1) << (end))
+#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
+
+/* DSI D-PHY Layer Registers */
+#define D0W_DPHYCONTTX         0x0004
+#define CLW_DPHYCONTRX         0x0020
+#define D0W_DPHYCONTRX         0x0024
+#define D1W_DPHYCONTRX         0x0028
+#define D2W_DPHYCONTRX         0x002C
+#define D3W_DPHYCONTRX         0x0030
+#define COM_DPHYCONTRX         0x0038
+#define CLW_CNTRL              0x0040
+#define D0W_CNTRL              0x0044
+#define D1W_CNTRL              0x0048
+#define D2W_CNTRL              0x004C
+#define D3W_CNTRL              0x0050
+#define DFTMODE_CNTRL          0x0054
+
+/* DSI PPI Layer Registers */
+#define PPI_STARTPPI           0x0104
+#define PPI_BUSYPPI            0x0108
+#define PPI_LINEINITCNT                0x0110
+#define PPI_LPTXTIMECNT                0x0114
+#define PPI_LANEENABLE         0x0134
+#define PPI_TX_RX_TA           0x013C
+#define PPI_CLS_ATMR           0x0140
+#define PPI_D0S_ATMR           0x0144
+#define PPI_D1S_ATMR           0x0148
+#define PPI_D2S_ATMR           0x014C
+#define PPI_D3S_ATMR           0x0150
+#define PPI_D0S_CLRSIPOCOUNT   0x0164
+#define PPI_D1S_CLRSIPOCOUNT   0x0168
+#define PPI_D2S_CLRSIPOCOUNT   0x016C
+#define PPI_D3S_CLRSIPOCOUNT   0x0170
+#define CLS_PRE                        0x0180
+#define D0S_PRE                        0x0184
+#define D1S_PRE                        0x0188
+#define D2S_PRE                        0x018C
+#define D3S_PRE                        0x0190
+#define CLS_PREP               0x01A0
+#define D0S_PREP               0x01A4
+#define D1S_PREP               0x01A8
+#define D2S_PREP               0x01AC
+#define D3S_PREP               0x01B0
+#define CLS_ZERO               0x01C0
+#define D0S_ZERO               0x01C4
+#define D1S_ZERO               0x01C8
+#define D2S_ZERO               0x01CC
+#define D3S_ZERO               0x01D0
+#define PPI_CLRFLG             0x01E0
+#define PPI_CLRSIPO            0x01E4
+#define HSTIMEOUT              0x01F0
+#define HSTIMEOUTENABLE                0x01F4
+
+/* DSI Protocol Layer Registers */
+#define DSI_STARTDSI           0x0204
+#define DSI_BUSYDSI            0x0208
+#define DSI_LANEENABLE         0x0210
+#define DSI_LANESTATUS0                0x0214
+#define DSI_LANESTATUS1                0x0218
+#define DSI_INTSTATUS          0x0220
+#define DSI_INTMASK            0x0224
+#define DSI_INTCLR             0x0228
+#define DSI_LPTXTO             0x0230
+
+/* DSI General Registers */
+#define DSIERRCNT              0x0300
+
+/* DSI Application Layer Registers */
+#define APLCTRL                        0x0400
+#define RDPKTLN                        0x0404
+
+/* Video Path Registers */
+#define VPCTRL                 0x0450
+#define HTIM1                  0x0454
+#define HTIM2                  0x0458
+#define VTIM1                  0x045C
+#define VTIM2                  0x0460
+#define VFUEN                  0x0464
+
+/* LVDS Registers */
+#define LVMX0003               0x0480
+#define LVMX0407               0x0484
+#define LVMX0811               0x0488
+#define LVMX1215               0x048C
+#define LVMX1619               0x0490
+#define LVMX2023               0x0494
+#define LVMX2427               0x0498
+#define LVCFG                  0x049C
+#define LVPHY0                 0x04A0
+#define LVPHY1                 0x04A4
+
+/* System Registers */
+#define SYSSTAT                        0x0500
+#define SYSRST                 0x0504
+
+/* GPIO Registers */
+/*#define GPIOC                        0x0520*/
+#define GPIOO                  0x0524
+#define GPIOI                  0x0528
+
+/* I2C Registers */
+#define I2CTIMCTRL             0x0540
+#define I2CMADDR               0x0544
+#define WDATAQ                 0x0548
+#define RDATAQ                 0x054C
+
+/* Chip/Rev Registers */
+#define IDREG                  0x0580
+
+/* Debug Registers */
+#define DEBUG00                        0x05A0
+#define DEBUG01                        0x05A4
+
+/* Panel CABC registers */
+#define PANEL_PWM_CONTROL      0x90
+#define PANEL_FREQ_DIVIDER_HI  0x91
+#define PANEL_FREQ_DIVIDER_LO  0x92
+#define PANEL_DUTY_CONTROL     0x93
+#define PANEL_MODIFY_RGB       0x94
+#define PANEL_FRAMERATE_CONTROL        0x96
+#define PANEL_PWM_MIN          0x97
+#define PANEL_PWM_REF          0x98
+#define PANEL_PWM_MAX          0x99
+#define PANEL_ALLOW_DISTORT    0x9A
+#define PANEL_BYPASS_PWMI      0x9B
+
+/* Panel color management registers */
+#define PANEL_CM_ENABLE                0x700
+#define PANEL_CM_HUE           0x701
+#define PANEL_CM_SATURATION    0x702
+#define PANEL_CM_INTENSITY     0x703
+#define PANEL_CM_BRIGHTNESS    0x704
+#define PANEL_CM_CE_ENABLE     0x705
+#define PANEL_CM_PEAK_EN       0x710
+#define PANEL_CM_GAIN          0x711
+#define PANEL_CM_HUETABLE_START        0x730
+#define PANEL_CM_HUETABLE_END  0x747 /* inclusive */
+
+/* Input muxing for registers LVMX0003...LVMX2427 */
+enum {
+       INPUT_R0,       /* 0 */
+       INPUT_R1,
+       INPUT_R2,
+       INPUT_R3,
+       INPUT_R4,
+       INPUT_R5,
+       INPUT_R6,
+       INPUT_R7,
+       INPUT_G0,       /* 8 */
+       INPUT_G1,
+       INPUT_G2,
+       INPUT_G3,
+       INPUT_G4,
+       INPUT_G5,
+       INPUT_G6,
+       INPUT_G7,
+       INPUT_B0,       /* 16 */
+       INPUT_B1,
+       INPUT_B2,
+       INPUT_B3,
+       INPUT_B4,
+       INPUT_B5,
+       INPUT_B6,
+       INPUT_B7,
+       INPUT_HSYNC,    /* 24 */
+       INPUT_VSYNC,
+       INPUT_DE,
+       LOGIC_0,
+       /* 28...31 undefined */
+};
+
+#define INPUT_MUX(lvmx03, lvmx02, lvmx01, lvmx00)              \
+       (FLD_VAL(lvmx03, 29, 24) | FLD_VAL(lvmx02, 20, 16) |    \
+       FLD_VAL(lvmx01, 12, 8) | FLD_VAL(lvmx00, 4, 0))
+
+/**
+ * tc35876x_regw - Write DSI-LVDS bridge register using I2C
+ * @client: struct i2c_client to use
+ * @reg: register address
+ * @value: value to write
+ *
+ * Returns 0 on success, or a negative error value.
+ */
+static int tc35876x_regw(struct i2c_client *client, u16 reg, u32 value)
+{
+       int r;
+       u8 tx_data[] = {
+               /* NOTE: Register address big-endian, data little-endian. */
+               (reg >> 8) & 0xff,
+               reg & 0xff,
+               value & 0xff,
+               (value >> 8) & 0xff,
+               (value >> 16) & 0xff,
+               (value >> 24) & 0xff,
+       };
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = client->addr,
+                       .flags = 0,
+                       .buf = tx_data,
+                       .len = ARRAY_SIZE(tx_data),
+               },
+       };
+
+       r = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+       if (r < 0) {
+               dev_err(&client->dev, "%s: reg 0x%04x val 0x%08x error %d\n",
+                       __func__, reg, value, r);
+               return r;
+       }
+
+       if (r < ARRAY_SIZE(msgs)) {
+               dev_err(&client->dev, "%s: reg 0x%04x val 0x%08x msgs %d\n",
+                       __func__, reg, value, r);
+               return -EAGAIN;
+       }
+
+       dev_dbg(&client->dev, "%s: reg 0x%04x val 0x%08x\n",
+                       __func__, reg, value);
+
+       return 0;
+}
+
+/**
+ * tc35876x_regr - Read DSI-LVDS bridge register using I2C
+ * @client: struct i2c_client to use
+ * @reg: register address
+ * @value: pointer for storing the value
+ *
+ * Returns 0 on success, or a negative error value.
+ */
+static int tc35876x_regr(struct i2c_client *client, u16 reg, u32 *value)
+{
+       int r;
+       u8 tx_data[] = {
+               (reg >> 8) & 0xff,
+               reg & 0xff,
+       };
+       u8 rx_data[4];
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = client->addr,
+                       .flags = 0,
+                       .buf = tx_data,
+                       .len = ARRAY_SIZE(tx_data),
+               },
+               {
+                       .addr = client->addr,
+                       .flags = I2C_M_RD,
+                       .buf = rx_data,
+                       .len = ARRAY_SIZE(rx_data),
+                },
+       };
+
+       r = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+       if (r < 0) {
+               dev_err(&client->dev, "%s: reg 0x%04x error %d\n", __func__,
+                       reg, r);
+               return r;
+       }
+
+       if (r < ARRAY_SIZE(msgs)) {
+               dev_err(&client->dev, "%s: reg 0x%04x msgs %d\n", __func__,
+                       reg, r);
+               return -EAGAIN;
+       }
+
+       *value = rx_data[0] << 24 | rx_data[1] << 16 |
+               rx_data[2] << 8 | rx_data[3];
+
+       dev_dbg(&client->dev, "%s: reg 0x%04x value 0x%08x\n", __func__,
+               reg, *value);
+
+       return 0;
+}
+
+void tc35876x_set_bridge_reset_state(struct drm_device *dev, int state)
+{
+       struct tc35876x_platform_data *pdata;
+
+       if (WARN(!tc35876x_client, "%s called before probe", __func__))
+               return;
+
+       dev_dbg(&tc35876x_client->dev, "%s: state %d\n", __func__, state);
+
+       pdata = dev_get_platdata(&tc35876x_client->dev);
+
+       if (pdata->gpio_bridge_reset == -1)
+               return;
+
+       if (state) {
+               gpio_set_value_cansleep(pdata->gpio_bridge_reset, 0);
+               mdelay(10);
+       } else {
+               /* Pull MIPI Bridge reset pin to Low */
+               gpio_set_value_cansleep(pdata->gpio_bridge_reset, 0);
+               mdelay(20);
+               /* Pull MIPI Bridge reset pin to High */
+               gpio_set_value_cansleep(pdata->gpio_bridge_reset, 1);
+               mdelay(40);
+       }
+}
+
+void tc35876x_configure_lvds_bridge(struct drm_device *dev)
+{
+       struct i2c_client *i2c = tc35876x_client;
+       u32 ppi_lptxtimecnt;
+       u32 txtagocnt;
+       u32 txtasurecnt;
+       u32 id;
+
+       if (WARN(!tc35876x_client, "%s called before probe", __func__))
+               return;
+
+       dev_dbg(&tc35876x_client->dev, "%s\n", __func__);
+
+       if (!tc35876x_regr(i2c, IDREG, &id))
+               dev_info(&tc35876x_client->dev, "tc35876x ID 0x%08x\n", id);
+       else
+               dev_err(&tc35876x_client->dev, "Cannot read ID\n");
+
+       ppi_lptxtimecnt = 4;
+       txtagocnt = (5 * ppi_lptxtimecnt - 3) / 4;
+       txtasurecnt = 3 * ppi_lptxtimecnt / 2;
+       tc35876x_regw(i2c, PPI_TX_RX_TA, FLD_VAL(txtagocnt, 26, 16) |
+               FLD_VAL(txtasurecnt, 10, 0));
+       tc35876x_regw(i2c, PPI_LPTXTIMECNT, FLD_VAL(ppi_lptxtimecnt, 10, 0));
+
+       tc35876x_regw(i2c, PPI_D0S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0));
+       tc35876x_regw(i2c, PPI_D1S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0));
+       tc35876x_regw(i2c, PPI_D2S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0));
+       tc35876x_regw(i2c, PPI_D3S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0));
+
+       /* Enabling MIPI & PPI lanes, Enable 4 lanes */
+       tc35876x_regw(i2c, PPI_LANEENABLE,
+               BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0));
+       tc35876x_regw(i2c, DSI_LANEENABLE,
+               BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0));
+       tc35876x_regw(i2c, PPI_STARTPPI, BIT(0));
+       tc35876x_regw(i2c, DSI_STARTDSI, BIT(0));
+
+       /* Setting LVDS output frequency */
+       tc35876x_regw(i2c, LVPHY0, FLD_VAL(1, 20, 16) |
+               FLD_VAL(2, 15, 14) | FLD_VAL(6, 4, 0)); /* 0x00048006 */
+
+       /* Setting video panel control register,0x00000120 VTGen=ON ?!?!? */
+       tc35876x_regw(i2c, VPCTRL, BIT(8) | BIT(5));
+
+       /* Horizontal back porch and horizontal pulse width. 0x00280028 */
+       tc35876x_regw(i2c, HTIM1, FLD_VAL(40, 24, 16) | FLD_VAL(40, 8, 0));
+
+       /* Horizontal front porch and horizontal active video size. 0x00500500*/
+       tc35876x_regw(i2c, HTIM2, FLD_VAL(80, 24, 16) | FLD_VAL(1280, 10, 0));
+
+       /* Vertical back porch and vertical sync pulse width. 0x000e000a */
+       tc35876x_regw(i2c, VTIM1, FLD_VAL(14, 23, 16) | FLD_VAL(10, 7, 0));
+
+       /* Vertical front porch and vertical display size. 0x000e0320 */
+       tc35876x_regw(i2c, VTIM2, FLD_VAL(14, 23, 16) | FLD_VAL(800, 10, 0));
+
+       /* Set above HTIM1, HTIM2, VTIM1, and VTIM2 at next VSYNC. */
+       tc35876x_regw(i2c, VFUEN, BIT(0));
+
+       /* Soft reset LCD controller. */
+       tc35876x_regw(i2c, SYSRST, BIT(2));
+
+       /* LVDS-TX input muxing */
+       tc35876x_regw(i2c, LVMX0003,
+               INPUT_MUX(INPUT_R5, INPUT_R4, INPUT_R3, INPUT_R2));
+       tc35876x_regw(i2c, LVMX0407,
+               INPUT_MUX(INPUT_G2, INPUT_R7, INPUT_R1, INPUT_R6));
+       tc35876x_regw(i2c, LVMX0811,
+               INPUT_MUX(INPUT_G1, INPUT_G0, INPUT_G4, INPUT_G3));
+       tc35876x_regw(i2c, LVMX1215,
+               INPUT_MUX(INPUT_B2, INPUT_G7, INPUT_G6, INPUT_G5));
+       tc35876x_regw(i2c, LVMX1619,
+               INPUT_MUX(INPUT_B4, INPUT_B3, INPUT_B1, INPUT_B0));
+       tc35876x_regw(i2c, LVMX2023,
+               INPUT_MUX(LOGIC_0,  INPUT_B7, INPUT_B6, INPUT_B5));
+       tc35876x_regw(i2c, LVMX2427,
+               INPUT_MUX(INPUT_R0, INPUT_DE, INPUT_VSYNC, INPUT_HSYNC));
+
+       /* Enable LVDS transmitter. */
+       tc35876x_regw(i2c, LVCFG, BIT(0));
+
+       /* Clear notifications. Don't write reserved bits. Was write 0xffffffff
+        * to 0x0288, must be in error?! */
+       tc35876x_regw(i2c, DSI_INTCLR, FLD_MASK(31, 30) | FLD_MASK(22, 0));
+}
+
+#define GPIOPWMCTRL    0x38F
+#define PWM0CLKDIV0    0x62 /* low byte */
+#define PWM0CLKDIV1    0x61 /* high byte */
+
+#define SYSTEMCLK      19200000UL /* 19.2 MHz */
+#define PWM_FREQUENCY  9600 /* Hz */
+
+/* f = baseclk / (clkdiv + 1) => clkdiv = (baseclk - f) / f */
+static inline u16 calc_clkdiv(unsigned long baseclk, unsigned int f)
+{
+       return (baseclk - f) / f;
+}
+
+static void tc35876x_brightness_init(struct drm_device *dev)
+{
+       int ret;
+       u8 pwmctrl;
+       u16 clkdiv;
+
+       /* Make sure the PWM reference is the 19.2 MHz system clock. Read first
+        * instead of setting directly to catch potential conflicts between PWM
+        * users. */
+       ret = intel_scu_ipc_ioread8(GPIOPWMCTRL, &pwmctrl);
+       if (ret || pwmctrl != 0x01) {
+               if (ret)
+                       dev_err(&dev->pdev->dev, "GPIOPWMCTRL read failed\n");
+               else
+                       dev_warn(&dev->pdev->dev, "GPIOPWMCTRL was not set to system clock (pwmctrl = 0x%02x)\n", pwmctrl);
+
+               ret = intel_scu_ipc_iowrite8(GPIOPWMCTRL, 0x01);
+               if (ret)
+                       dev_err(&dev->pdev->dev, "GPIOPWMCTRL set failed\n");
+       }
+
+       clkdiv = calc_clkdiv(SYSTEMCLK, PWM_FREQUENCY);
+
+       ret = intel_scu_ipc_iowrite8(PWM0CLKDIV1, (clkdiv >> 8) & 0xff);
+       if (!ret)
+               ret = intel_scu_ipc_iowrite8(PWM0CLKDIV0, clkdiv & 0xff);
+
+       if (ret)
+               dev_err(&dev->pdev->dev, "PWM0CLKDIV set failed\n");
+       else
+               dev_dbg(&dev->pdev->dev, "PWM0CLKDIV set to 0x%04x (%d Hz)\n",
+                       clkdiv, PWM_FREQUENCY);
+}
+
+#define PWM0DUTYCYCLE                  0x67
+
+void tc35876x_brightness_control(struct drm_device *dev, int level)
+{
+       int ret;
+       u8 duty_val;
+       u8 panel_duty_val;
+
+       level = clamp(level, 0, MDFLD_DSI_BRIGHTNESS_MAX_LEVEL);
+
+       /* PWM duty cycle 0x00...0x63 corresponds to 0...99% */
+       duty_val = level * 0x63 / MDFLD_DSI_BRIGHTNESS_MAX_LEVEL;
+
+       /* I won't pretend to understand this formula. The panel spec is quite
+        * bad engrish.
+        */
+       panel_duty_val = (2 * level - 100) * 0xA9 /
+                        MDFLD_DSI_BRIGHTNESS_MAX_LEVEL + 0x56;
+
+       ret = intel_scu_ipc_iowrite8(PWM0DUTYCYCLE, duty_val);
+       if (ret)
+               dev_err(&tc35876x_client->dev, "%s: ipc write fail\n",
+                       __func__);
+
+       if (cmi_lcd_i2c_client) {
+               ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client,
+                                               PANEL_PWM_MAX, panel_duty_val);
+               if (ret < 0)
+                       dev_err(&cmi_lcd_i2c_client->dev, "%s: i2c write failed\n",
+                               __func__);
+       }
+}
+
+void tc35876x_toshiba_bridge_panel_off(struct drm_device *dev)
+{
+       struct tc35876x_platform_data *pdata;
+
+       if (WARN(!tc35876x_client, "%s called before probe", __func__))
+               return;
+
+       dev_dbg(&tc35876x_client->dev, "%s\n", __func__);
+
+       pdata = dev_get_platdata(&tc35876x_client->dev);
+
+       if (pdata->gpio_panel_bl_en != -1)
+               gpio_set_value_cansleep(pdata->gpio_panel_bl_en, 0);
+
+       if (pdata->gpio_panel_vadd != -1)
+               gpio_set_value_cansleep(pdata->gpio_panel_vadd, 0);
+}
+
+void tc35876x_toshiba_bridge_panel_on(struct drm_device *dev)
+{
+       struct tc35876x_platform_data *pdata;
+       struct drm_psb_private *dev_priv = dev->dev_private;
+
+       if (WARN(!tc35876x_client, "%s called before probe", __func__))
+               return;
+
+       dev_dbg(&tc35876x_client->dev, "%s\n", __func__);
+
+       pdata = dev_get_platdata(&tc35876x_client->dev);
+
+       if (pdata->gpio_panel_vadd != -1) {
+               gpio_set_value_cansleep(pdata->gpio_panel_vadd, 1);
+               msleep(260);
+       }
+
+       if (cmi_lcd_i2c_client) {
+               int ret;
+               dev_dbg(&cmi_lcd_i2c_client->dev, "setting TCON\n");
+               /* Bit 4 is average_saving. Setting it to 1, the brightness is
+                * referenced to the average of the frame content. 0 means
+                * reference to the maximum of frame contents. Bits 3:0 are
+                * allow_distort. When set to a nonzero value, all color values
+                * between 255-allow_distort*2 and 255 are mapped to the
+                * 255-allow_distort*2 value.
+                */
+               ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client,
+                                               PANEL_ALLOW_DISTORT, 0x10);
+               if (ret < 0)
+                       dev_err(&cmi_lcd_i2c_client->dev,
+                               "i2c write failed (%d)\n", ret);
+               ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client,
+                                               PANEL_BYPASS_PWMI, 0);
+               if (ret < 0)
+                       dev_err(&cmi_lcd_i2c_client->dev,
+                               "i2c write failed (%d)\n", ret);
+               /* Set minimum brightness value - this is tunable */
+               ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client,
+                                               PANEL_PWM_MIN, 0x35);
+               if (ret < 0)
+                       dev_err(&cmi_lcd_i2c_client->dev,
+                               "i2c write failed (%d)\n", ret);
+       }
+
+       if (pdata->gpio_panel_bl_en != -1)
+               gpio_set_value_cansleep(pdata->gpio_panel_bl_en, 1);
+
+       tc35876x_brightness_control(dev, dev_priv->brightness_adjusted);
+}
+
+static struct drm_display_mode *tc35876x_get_config_mode(struct drm_device *dev)
+{
+       struct drm_display_mode *mode;
+
+       dev_dbg(&dev->pdev->dev, "%s\n", __func__);
+
+       mode = kzalloc(sizeof(*mode), GFP_KERNEL);
+       if (!mode)
+               return NULL;
+
+       /* FIXME: do this properly. */
+       mode->hdisplay = 1280;
+       mode->vdisplay = 800;
+       mode->hsync_start = 1360;
+       mode->hsync_end = 1400;
+       mode->htotal = 1440;
+       mode->vsync_start = 814;
+       mode->vsync_end = 824;
+       mode->vtotal = 838;
+       mode->clock = 33324 << 1;
+
+       dev_info(&dev->pdev->dev, "hdisplay(w) = %d\n", mode->hdisplay);
+       dev_info(&dev->pdev->dev, "vdisplay(h) = %d\n", mode->vdisplay);
+       dev_info(&dev->pdev->dev, "HSS = %d\n", mode->hsync_start);
+       dev_info(&dev->pdev->dev, "HSE = %d\n", mode->hsync_end);
+       dev_info(&dev->pdev->dev, "htotal = %d\n", mode->htotal);
+       dev_info(&dev->pdev->dev, "VSS = %d\n", mode->vsync_start);
+       dev_info(&dev->pdev->dev, "VSE = %d\n", mode->vsync_end);
+       dev_info(&dev->pdev->dev, "vtotal = %d\n", mode->vtotal);
+       dev_info(&dev->pdev->dev, "clock = %d\n", mode->clock);
+
+       drm_mode_set_name(mode);
+       drm_mode_set_crtcinfo(mode, 0);
+
+       mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+       return mode;
+}
+
+/* DV1 Active area 216.96 x 135.6 mm */
+#define DV1_PANEL_WIDTH 217
+#define DV1_PANEL_HEIGHT 136
+
+static int tc35876x_get_panel_info(struct drm_device *dev, int pipe,
+                               struct panel_info *pi)
+{
+       if (!dev || !pi)
+               return -EINVAL;
+
+       pi->width_mm = DV1_PANEL_WIDTH;
+       pi->height_mm = DV1_PANEL_HEIGHT;
+
+       return 0;
+}
+
+static int tc35876x_bridge_probe(struct i2c_client *client,
+                               const struct i2c_device_id *id)
+{
+       struct tc35876x_platform_data *pdata;
+
+       dev_info(&client->dev, "%s\n", __func__);
+
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               dev_err(&client->dev, "%s: i2c_check_functionality() failed\n",
+                       __func__);
+               return -ENODEV;
+       }
+
+       pdata = dev_get_platdata(&client->dev);
+       if (!pdata) {
+               dev_err(&client->dev, "%s: no platform data\n", __func__);
+               return -ENODEV;
+       }
+
+       if (pdata->gpio_bridge_reset != -1) {
+               gpio_request(pdata->gpio_bridge_reset, "tc35876x bridge reset");
+               gpio_direction_output(pdata->gpio_bridge_reset, 0);
+       }
+
+       if (pdata->gpio_panel_bl_en != -1) {
+               gpio_request(pdata->gpio_panel_bl_en, "tc35876x panel bl en");
+               gpio_direction_output(pdata->gpio_panel_bl_en, 0);
+       }
+
+       if (pdata->gpio_panel_vadd != -1) {
+               gpio_request(pdata->gpio_panel_vadd, "tc35876x panel vadd");
+               gpio_direction_output(pdata->gpio_panel_vadd, 0);
+       }
+
+       tc35876x_client = client;
+
+       return 0;
+}
+
+static int tc35876x_bridge_remove(struct i2c_client *client)
+{
+       struct tc35876x_platform_data *pdata = dev_get_platdata(&client->dev);
+
+       dev_dbg(&client->dev, "%s\n", __func__);
+
+       if (pdata->gpio_bridge_reset != -1)
+               gpio_free(pdata->gpio_bridge_reset);
+
+       if (pdata->gpio_panel_bl_en != -1)
+               gpio_free(pdata->gpio_panel_bl_en);
+
+       if (pdata->gpio_panel_vadd != -1)
+               gpio_free(pdata->gpio_panel_vadd);
+
+       tc35876x_client = NULL;
+
+       return 0;
+}
+
+static const struct i2c_device_id tc35876x_bridge_id[] = {
+       { "i2c_disp_brig", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, tc35876x_bridge_id);
+
+static struct i2c_driver tc35876x_bridge_i2c_driver = {
+       .driver = {
+               .name = "i2c_disp_brig",
+       },
+       .id_table = tc35876x_bridge_id,
+       .probe = tc35876x_bridge_probe,
+       .remove = tc35876x_bridge_remove,
+};
+
+/* LCD panel I2C */
+static int cmi_lcd_i2c_probe(struct i2c_client *client,
+                            const struct i2c_device_id *id)
+{
+       dev_info(&client->dev, "%s\n", __func__);
+
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               dev_err(&client->dev, "%s: i2c_check_functionality() failed\n",
+                       __func__);
+               return -ENODEV;
+       }
+
+       cmi_lcd_i2c_client = client;
+
+       return 0;
+}
+
+static int cmi_lcd_i2c_remove(struct i2c_client *client)
+{
+       dev_dbg(&client->dev, "%s\n", __func__);
+
+       cmi_lcd_i2c_client = NULL;
+
+       return 0;
+}
+
+static const struct i2c_device_id cmi_lcd_i2c_id[] = {
+       { "cmi-lcd", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, cmi_lcd_i2c_id);
+
+static struct i2c_driver cmi_lcd_i2c_driver = {
+       .driver = {
+               .name = "cmi-lcd",
+       },
+       .id_table = cmi_lcd_i2c_id,
+       .probe = cmi_lcd_i2c_probe,
+       .remove = cmi_lcd_i2c_remove,
+};
+
+/* HACK to create I2C device while it's not created by platform code */
+#define CMI_LCD_I2C_ADAPTER    2
+#define CMI_LCD_I2C_ADDR       0x60
+
+static int cmi_lcd_hack_create_device(void)
+{
+       struct i2c_adapter *adapter;
+       struct i2c_client *client;
+       struct i2c_board_info info = {
+               .type = "cmi-lcd",
+               .addr = CMI_LCD_I2C_ADDR,
+       };
+
+       pr_debug("%s\n", __func__);
+
+       adapter = i2c_get_adapter(CMI_LCD_I2C_ADAPTER);
+       if (!adapter) {
+               pr_err("%s: i2c_get_adapter(%d) failed\n", __func__,
+                       CMI_LCD_I2C_ADAPTER);
+               return -EINVAL;
+       }
+
+       client = i2c_new_device(adapter, &info);
+       if (!client) {
+               pr_err("%s: i2c_new_device() failed\n", __func__);
+               i2c_put_adapter(adapter);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct drm_encoder_helper_funcs tc35876x_encoder_helper_funcs = {
+       .dpms = mdfld_dsi_dpi_dpms,
+       .mode_fixup = mdfld_dsi_dpi_mode_fixup,
+       .prepare = mdfld_dsi_dpi_prepare,
+       .mode_set = mdfld_dsi_dpi_mode_set,
+       .commit = mdfld_dsi_dpi_commit,
+};
+
+static const struct drm_encoder_funcs tc35876x_encoder_funcs = {
+       .destroy = drm_encoder_cleanup,
+};
+
+const struct panel_funcs mdfld_tc35876x_funcs = {
+       .encoder_funcs = &tc35876x_encoder_funcs,
+       .encoder_helper_funcs = &tc35876x_encoder_helper_funcs,
+       .get_config_mode = tc35876x_get_config_mode,
+       .get_panel_info = tc35876x_get_panel_info,
+};
+
+void tc35876x_init(struct drm_device *dev)
+{
+       int r;
+
+       dev_dbg(&dev->pdev->dev, "%s\n", __func__);
+
+       cmi_lcd_hack_create_device();
+
+       r = i2c_add_driver(&cmi_lcd_i2c_driver);
+       if (r < 0)
+               dev_err(&dev->pdev->dev,
+                       "%s: i2c_add_driver() for %s failed (%d)\n",
+                       __func__, cmi_lcd_i2c_driver.driver.name, r);
+
+       r = i2c_add_driver(&tc35876x_bridge_i2c_driver);
+       if (r < 0)
+               dev_err(&dev->pdev->dev,
+                       "%s: i2c_add_driver() for %s failed (%d)\n",
+                       __func__, tc35876x_bridge_i2c_driver.driver.name, r);
+
+       tc35876x_brightness_init(dev);
+}
+
+void tc35876x_exit(void)
+{
+       pr_debug("%s\n", __func__);
+
+       i2c_del_driver(&tc35876x_bridge_i2c_driver);
+
+       if (cmi_lcd_i2c_client)
+               i2c_del_driver(&cmi_lcd_i2c_driver);
+}