--- /dev/null
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "amba-clcd-nomadik.h"
+
+static struct gpio_desc *grestb;
+static struct gpio_desc *scen;
+static struct gpio_desc *scl;
+static struct gpio_desc *sda;
+
+static u8 tpg110_readwrite_reg(bool write, u8 address, u8 outval)
+{
+       int i;
+       u8 inval = 0;
+
+       /* Assert SCEN */
+       gpiod_set_value_cansleep(scen, 1);
+       ndelay(150);
+       /* Hammer out the address */
+       for (i = 5; i >= 0; i--) {
+               if (address & BIT(i))
+                       gpiod_set_value_cansleep(sda, 1);
+               else
+                       gpiod_set_value_cansleep(sda, 0);
+               ndelay(150);
+               /* Send an SCL pulse */
+               gpiod_set_value_cansleep(scl, 1);
+               ndelay(160);
+               gpiod_set_value_cansleep(scl, 0);
+               ndelay(160);
+       }
+
+       if (write) {
+               /* WRITE */
+               gpiod_set_value_cansleep(sda, 0);
+       } else {
+               /* READ */
+               gpiod_set_value_cansleep(sda, 1);
+       }
+       ndelay(150);
+       /* Send an SCL pulse */
+       gpiod_set_value_cansleep(scl, 1);
+       ndelay(160);
+       gpiod_set_value_cansleep(scl, 0);
+       ndelay(160);
+
+       if (!write)
+               /* HiZ turn-around cycle */
+               gpiod_direction_input(sda);
+       ndelay(150);
+       /* Send an SCL pulse */
+       gpiod_set_value_cansleep(scl, 1);
+       ndelay(160);
+       gpiod_set_value_cansleep(scl, 0);
+       ndelay(160);
+
+       /* Hammer in/out the data */
+       for (i = 7; i >= 0; i--) {
+               int value;
+
+               if (write) {
+                       value = !!(outval & BIT(i));
+                       gpiod_set_value_cansleep(sda, value);
+               } else {
+                       value = gpiod_get_value(sda);
+                       if (value)
+                               inval |= BIT(i);
+               }
+               ndelay(150);
+               /* Send an SCL pulse */
+               gpiod_set_value_cansleep(scl, 1);
+               ndelay(160);
+               gpiod_set_value_cansleep(scl, 0);
+               ndelay(160);
+       }
+
+       gpiod_direction_output(sda, 0);
+       /* Deassert SCEN */
+       gpiod_set_value_cansleep(scen, 0);
+       /* Satisfies SCEN pulse width */
+       udelay(1);
+
+       return inval;
+}
+
+static u8 tpg110_read_reg(u8 address)
+{
+       return tpg110_readwrite_reg(false, address, 0);
+}
+
+static void tpg110_write_reg(u8 address, u8 outval)
+{
+       tpg110_readwrite_reg(true, address, outval);
+}
+
+static void tpg110_startup(struct device *dev)
+{
+       u8 val;
+
+       dev_info(dev, "TPG110 display enable\n");
+       /* De-assert the reset signal */
+       gpiod_set_value_cansleep(grestb, 0);
+       mdelay(1);
+       dev_info(dev, "de-asserted GRESTB\n");
+
+       /* Test display communication */
+       tpg110_write_reg(0x00, 0x55);
+       val = tpg110_read_reg(0x00);
+       if (val == 0x55)
+               dev_info(dev, "passed communication test\n");
+       val = tpg110_read_reg(0x01);
+       dev_info(dev, "TPG110 chip ID: %d version: %d\n",
+               val>>4, val&0x0f);
+
+       /* Show display resolution */
+       val = tpg110_read_reg(0x02);
+       val &= 7;
+       switch (val) {
+       case 0x0:
+               dev_info(dev, "IN 400x240 RGB -> OUT 800x480 RGB (dual scan)");
+               break;
+       case 0x1:
+               dev_info(dev, "IN 480x272 RGB -> OUT 800x480 RGB (dual scan)");
+               break;
+       case 0x4:
+               dev_info(dev, "480x640 RGB");
+               break;
+       case 0x5:
+               dev_info(dev, "480x272 RGB");
+               break;
+       case 0x6:
+               dev_info(dev, "640x480 RGB");
+               break;
+       case 0x7:
+               dev_info(dev, "800x480 RGB");
+               break;
+       default:
+               dev_info(dev, "ILLEGAL RESOLUTION");
+               break;
+       }
+
+       val = tpg110_read_reg(0x03);
+       dev_info(dev, "resolution is controlled by %s\n",
+               (val & BIT(7)) ? "software" : "hardware");
+}
+
+static void tpg110_enable(struct clcd_fb *fb)
+{
+       struct device *dev = &fb->dev->dev;
+       static bool startup;
+       u8 val;
+
+       if (!startup) {
+               tpg110_startup(dev);
+               startup = true;
+       }
+
+       /* Take chip out of standby */
+       val = tpg110_read_reg(0x03);
+       val |= BIT(0);
+       tpg110_write_reg(0x03, val);
+}
+
+static void tpg110_disable(struct clcd_fb *fb)
+{
+       u8 val;
+
+       dev_info(&fb->dev->dev, "TPG110 display disable\n");
+       val = tpg110_read_reg(0x03);
+       /* Put into standby */
+       val &= ~BIT(0);
+       tpg110_write_reg(0x03, val);
+}
+
+static void tpg110_init(struct device *dev, struct device_node *np,
+                       struct clcd_board *board)
+{
+       dev_info(dev, "TPG110 display init\n");
+
+       grestb = devm_get_gpiod_from_child(dev, "grestb", &np->fwnode);
+       if (IS_ERR(grestb)) {
+               dev_err(dev, "no GRESTB GPIO\n");
+               return;
+       }
+       /* This asserts the GRESTB signal, putting the display into reset */
+       gpiod_direction_output(grestb, 1);
+
+       scen = devm_get_gpiod_from_child(dev, "scen", &np->fwnode);
+       if (IS_ERR(scen)) {
+               dev_err(dev, "no SCEN GPIO\n");
+               return;
+       }
+       gpiod_direction_output(scen, 0);
+       scl = devm_get_gpiod_from_child(dev, "scl", &np->fwnode);
+       if (IS_ERR(scl)) {
+               dev_err(dev, "no SCL GPIO\n");
+               return;
+       }
+       gpiod_direction_output(scl, 0);
+       sda = devm_get_gpiod_from_child(dev, "sda", &np->fwnode);
+       if (IS_ERR(sda)) {
+               dev_err(dev, "no SDA GPIO\n");
+               return;
+       }
+       gpiod_direction_output(sda, 0);
+       board->enable = tpg110_enable;
+       board->disable = tpg110_disable;
+}
+
+int nomadik_clcd_init_panel(struct clcd_fb *fb,
+                           struct device_node *endpoint)
+{
+       struct device_node *panel;
+
+       panel = of_graph_get_remote_port_parent(endpoint);
+       if (!panel)
+               return -ENODEV;
+
+       if (of_device_is_compatible(panel, "tpo,tpg110"))
+               tpg110_init(&fb->dev->dev, panel, fb->board);
+       else
+               dev_info(&fb->dev->dev, "unknown panel\n");
+
+       /* Unknown panel, fall through */
+       return 0;
+}
+
+#define PMU_CTRL_OFFSET 0x0000
+#define PMU_CTRL_LCDNDIF BIT(26)
+
+int nomadik_clcd_init_board(struct amba_device *adev,
+                           struct clcd_board *board)
+{
+       struct regmap *pmu_regmap;
+
+       dev_info(&adev->dev, "Nomadik CLCD board init\n");
+       pmu_regmap =
+               syscon_regmap_lookup_by_compatible("stericsson,nomadik-pmu");
+       if (IS_ERR(pmu_regmap)) {
+               dev_err(&adev->dev, "could not find PMU syscon regmap\n");
+               return PTR_ERR(pmu_regmap);
+       }
+       regmap_update_bits(pmu_regmap,
+                          PMU_CTRL_OFFSET,
+                          PMU_CTRL_LCDNDIF,
+                          0);
+       dev_info(&adev->dev, "set PMU mux to CLCD mode\n");
+
+       return 0;
+}