/*
 * Toshiba TC358768AXBG/TC358778XBG DPI to DSI encoder
 *
 * Copyright (C) 2015 Texas Instruments
 * Author: Marcus Cooksey <mcooksey@ti.com>
 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>

#include <video/of_display_timing.h>
#include <video/mipi_display.h>

#include "../dss/omapdss.h"

#define TC358768_NAME		"tc358768"

/* Global (16-bit addressable) */
#define TC358768_CHIPID			0x0000
#define TC358768_SYSCTL			0x0002
#define TC358768_CONFCTL		0x0004
#define TC358768_VSDLY			0x0006
#define TC358768_DATAFMT		0x0008
#define TC358768_GPIOEN			0x000E
#define TC358768_GPIODIR		0x0010
#define TC358768_GPIOIN			0x0012
#define TC358768_GPIOOUT		0x0014
#define TC358768_PLLCTL0		0x0016
#define TC358768_PLLCTL1		0x0018
#define TC358768_CMDBYTE		0x0022
#define TC358768_PP_MISC		0x0032
#define TC358768_DSITX_DT		0x0050
#define TC358768_FIFOSTATUS		0x00F8

/* Debug (16-bit addressable) */
#define TC358768_VBUFCTRL		0x00E0
#define TC358768_DBG_WIDTH		0x00E2
#define TC358768_DBG_VBLANK		0x00E4
#define TC358768_DBG_DATA		0x00E8

/* TX PHY (32-bit addressable) */
#define TC358768_CLW_DPHYCONTTX		0x0100
#define TC358768_D0W_DPHYCONTTX		0x0104
#define TC358768_D1W_DPHYCONTTX		0x0108
#define TC358768_D2W_DPHYCONTTX		0x010C
#define TC358768_D3W_DPHYCONTTX		0x0110
#define TC358768_CLW_CNTRL		0x0140
#define TC358768_D0W_CNTRL		0x0144
#define TC358768_D1W_CNTRL		0x0148
#define TC358768_D2W_CNTRL		0x014C
#define TC358768_D3W_CNTRL		0x0150

/* TX PPI (32-bit addressable) */
#define TC358768_STARTCNTRL		0x0204
#define TC358768_DSITXSTATUS		0x0208
#define TC358768_LINEINITCNT		0x0210
#define TC358768_LPTXTIMECNT		0x0214
#define TC358768_TCLK_HEADERCNT		0x0218
#define TC358768_TCLK_TRAILCNT		0x021C
#define TC358768_THS_HEADERCNT		0x0220
#define TC358768_TWAKEUP		0x0224
#define TC358768_TCLK_POSTCNT		0x0228
#define TC358768_THS_TRAILCNT		0x022C
#define TC358768_HSTXVREGCNT		0x0230
#define TC358768_HSTXVREGEN		0x0234
#define TC358768_TXOPTIONCNTRL		0x0238
#define TC358768_BTACNTRL1		0x023C

/* TX CTRL (32-bit addressable) */
#define TC358768_DSI_STATUS		0x0410
#define TC358768_DSI_INT		0x0414
#define TC358768_DSICMD_RXFIFO		0x0430
#define TC358768_DSI_ACKERR		0x0434
#define TC358768_DSI_RXERR		0x0440
#define TC358768_DSI_ERR		0x044C
#define TC358768_DSI_CONFW		0x0500
#define TC358768_DSI_RESET		0x0504
#define TC358768_DSI_INT_CLR		0x050C
#define TC358768_DSI_START		0x0518

/* DSITX CTRL (16-bit addressable) */
#define TC358768_DSICMD_TX		0x0600
#define TC358768_DSICMD_TYPE		0x0602
#define TC358768_DSICMD_WC		0x0604
#define TC358768_DSICMD_WD0		0x0610
#define TC358768_DSICMD_WD1		0x0612
#define TC358768_DSICMD_WD2		0x0614
#define TC358768_DSICMD_WD3		0x0616
#define TC358768_DSI_EVENT		0x0620
#define TC358768_DSI_VSW		0x0622
#define TC358768_DSI_VBPR		0x0624
#define TC358768_DSI_VACT		0x0626
#define TC358768_DSI_HSW		0x0628
#define TC358768_DSI_HBPR		0x062A
#define TC358768_DSI_HACT		0x062C

struct tc358768_drv_data {
	struct omap_dss_device dssdev;
	struct omap_dss_device *in;

	struct device *dev;

	struct omap_video_timings videomode;

	struct gpio_desc *reset_gpio;

	struct regmap *regmap;

	struct clk *refclk;

	u32 dpi_ndl;
	u32 dsi_ndl;

	unsigned fbd;
	unsigned prd;
	unsigned frs;

	u32 bitclk;
};

static const struct regmap_config tc358768_regmap_config = {
	.reg_bits = 16,
	.val_bits = 16,
	.reg_format_endian = REGMAP_ENDIAN_BIG,
	.val_format_endian = REGMAP_ENDIAN_BIG,
};

static int tc358768_write(struct tc358768_drv_data *ddata,
	unsigned int reg, unsigned int val)
{
	int r;

	/* 16-bit register? */
	if (reg < 0x100 || reg >= 0x600) {
		dev_dbg(ddata->dev, "WR16\t%04x\t%08x\n", reg, val);
		return regmap_write(ddata->regmap, reg, val);
	}

	dev_dbg(ddata->dev, "WR32\t%04x\t%08x\n", reg, val);

	/* 32-bit register, write in two parts */
	r = regmap_write(ddata->regmap, reg, val);
	if (r)
		return r;

	return regmap_write(ddata->regmap, reg + 2, val >> 16);
}

static int tc358768_read(struct tc358768_drv_data *ddata,
	unsigned int reg, unsigned int *val)
{
	int r;
	unsigned v1, v2;

	/* 16-bit register? */
	if (reg < 0x100 || reg >= 0x600)
		return regmap_read(ddata->regmap, reg, val);

	/* 32-bit register, read in two parts */
	r = regmap_read(ddata->regmap, reg, &v1);
	if (r)
		return r;

	r = regmap_read(ddata->regmap, reg + 2, &v2);
	if (r)
		return r;

	*val = v1 | (v2 << 16);
	return 0;
}

static int tc358768_update_bits(struct tc358768_drv_data *ddata,
	unsigned int reg, unsigned int mask, unsigned int val)
{
	int ret;
	unsigned int tmp, orig;

	ret = tc358768_read(ddata, reg, &orig);
	if (ret != 0)
		return ret;

	tmp = orig & ~mask;
	tmp |= val & mask;

	dev_dbg(ddata->dev, "UPD \t%04x\t%08x -> %08x\n", reg, orig, tmp);

	if (tmp != orig)
		ret = tc358768_write(ddata, reg, tmp);

	return ret;
}

static int tc358768_dsi_xfer_short(struct tc358768_drv_data *ddata,
	u8 data_id, u8 data0, u8 data1)
{
	const u8 packet_type = 0x10; /* DSI Short Packet */
	const u8 word_count = 0;

	tc358768_write(ddata, TC358768_DSICMD_TYPE,
		(packet_type << 8) | data_id);
	tc358768_write(ddata, TC358768_DSICMD_WC, (word_count & 0xf));
	tc358768_write(ddata, TC358768_DSICMD_WD0, (data1 << 8) | data0);
	tc358768_write(ddata, TC358768_DSICMD_TX, 1); /* start transfer */

	return 0;
}

static void tc358768_sw_reset(struct tc358768_drv_data *ddata)
{
	/* Assert Reset */
	tc358768_write(ddata, TC358768_SYSCTL, 1);
	/* Release Reset, Exit Sleep */
	tc358768_write(ddata, TC358768_SYSCTL, 0);
}

static u32 tc358768_pll_to_pclk(struct tc358768_drv_data *ddata, u32 pll)
{
	u32 byteclk;

	byteclk = pll / 2 / 4;

	return (u32)div_u64((u64)byteclk * 8 * ddata->dsi_ndl,
		ddata->dpi_ndl);
}

static u32 tc358768_pclk_to_pll(struct tc358768_drv_data *ddata, u32 pclk)
{
	u32 byteclk;

	byteclk = (u32)div_u64((u64)pclk * ddata->dpi_ndl,
		8 * ddata->dsi_ndl);

	return byteclk * 4 * 2;
}

static int tc358768_calc_pll(struct tc358768_drv_data *ddata)
{
	const unsigned frs_limits[] = {
		1000000000, 500000000, 250000000, 125000000, 62500000
	};
	unsigned fbd, prd, frs;
	u32 target_pll;
	unsigned long refclk;
	unsigned i;
	u32 max_pll, min_pll;

	u32 best_diff, best_pll, best_prd, best_fbd;

	target_pll = tc358768_pclk_to_pll(ddata, ddata->videomode.pixelclock);

	/* pll_clk = RefClk * [(FBD + 1)/ (PRD + 1)] * [1 / (2^FRS)] */

	frs = UINT_MAX;

	for (i = 0; i < ARRAY_SIZE(frs_limits) - 1; ++i) {
		if (target_pll < frs_limits[i] && target_pll >= frs_limits[i + 1]) {
			frs = i;
			max_pll = frs_limits[i];
			min_pll = frs_limits[i + 1];
			break;
		}
	}

	if (frs == UINT_MAX)
		return -EINVAL;

	refclk = clk_get_rate(ddata->refclk);

	best_pll = best_prd = best_fbd = 0;
	best_diff = UINT_MAX;

	for (prd = 0; prd < 16; ++prd) {
		u32 divisor = (prd + 1) * (1 << frs);

		for (fbd = 0; fbd < 512; ++fbd) {
			u32 pll, diff;

			pll = (u32)div_u64((u64)refclk * (fbd + 1), divisor);

			if (pll >= max_pll || pll < min_pll)
				continue;

			diff = max(pll, target_pll) - min(pll, target_pll);

			if (diff < best_diff) {
				best_diff = diff;
				best_pll = pll;
				best_prd = prd;
				best_fbd = fbd;
			}

			if (best_diff == 0)
				break;
		}

		if (best_diff == 0)
			break;
	}

	if (best_diff == UINT_MAX) {
		dev_err(ddata->dev, "could not find suitable PLL setup\n");
		return -EINVAL;
	}

	ddata->fbd = best_fbd;
	ddata->prd = best_prd;
	ddata->frs = frs;
	ddata->bitclk = best_pll / 2;

	return 0;
}

static void tc358768_setup_pll(struct tc358768_drv_data *ddata)
{
	unsigned fbd, prd, frs;

	fbd = ddata->fbd;
	prd = ddata->prd;
	frs = ddata->frs;

	dev_dbg(ddata->dev, "PLL: refclk %lu, fbd %u, prd %u, frs %u\n",
		clk_get_rate(ddata->refclk), fbd, prd, frs);

	dev_dbg(ddata->dev, "PLL: %u, BitClk %u, ByteClk %u, pclk %u\n",
		ddata->bitclk * 2, ddata->bitclk, ddata->bitclk / 4,
		tc358768_pll_to_pclk(ddata, ddata->bitclk * 2));

	/* PRD[15:12] FBD[8:0] */
	tc358768_write(ddata, TC358768_PLLCTL0, (prd << 12) | fbd);

	/* FRS[11:10] LBWS[9:8] CKEN[4] RESETB[1] EN[0] */
	tc358768_write(ddata, TC358768_PLLCTL1,
		(frs << 10) | (0x2 << 8) | (0 << 4) | (1 << 1) | (1 << 0));

	/* wait for lock */
	usleep_range(1000, 2000);

	/* FRS[11:10] LBWS[9:8] CKEN[4] RESETB[1] EN[0] */
	tc358768_write(ddata, TC358768_PLLCTL1,
		(frs << 10) | (0x2 << 8) | (1 << 4) | (1 << 1) | (1 << 0));
}

static void tc358768_power_on(struct tc358768_drv_data *ddata)
{
	const struct omap_video_timings *t = &ddata->videomode;

	tc358768_sw_reset(ddata);

	tc358768_setup_pll(ddata);

	/* VSDly[9:0] */
	tc358768_write(ddata, TC358768_VSDLY, 1);
	/* PDFormat[7:4] spmode_en[3] rdswap_en[2] dsitx_en[1] txdt_en[0] */
	tc358768_write(ddata, TC358768_DATAFMT, (0x3 << 4) | (1 << 2) | (1 << 1) | (1 << 0));
	/* dsitx_dt[7:0] 3e = Packed Pixel Stream, 24-bit RGB, 8-8-8 Format*/
	tc358768_write(ddata, TC358768_DSITX_DT, 0x003e);

	/* Enable D-PHY (HiZ->LP11) */
	tc358768_write(ddata, TC358768_CLW_CNTRL, 0x0000);
	tc358768_write(ddata, TC358768_D0W_CNTRL, 0x0000);
	tc358768_write(ddata, TC358768_D1W_CNTRL, 0x0000);
	tc358768_write(ddata, TC358768_D2W_CNTRL, 0x0000);
	tc358768_write(ddata, TC358768_D3W_CNTRL, 0x0000);

	/* DSI Timings */
	/* LP11 = 100 us for D-PHY Rx Init */
	tc358768_write(ddata, TC358768_LINEINITCNT,	0x00002c88);
	tc358768_write(ddata, TC358768_LPTXTIMECNT,	0x00000005);
	tc358768_write(ddata, TC358768_TCLK_HEADERCNT,	0x00001f06);
	tc358768_write(ddata, TC358768_TCLK_TRAILCNT,	0x00000003);
	tc358768_write(ddata, TC358768_THS_HEADERCNT,	0x00000606);
	tc358768_write(ddata, TC358768_TWAKEUP,		0x00004a88);
	tc358768_write(ddata, TC358768_TCLK_POSTCNT,	0x0000000b);
	tc358768_write(ddata, TC358768_THS_TRAILCNT,	0x00000004);
	tc358768_write(ddata, TC358768_HSTXVREGEN,	0x0000001f);

	/* CONTCLKMODE[0] */
	tc358768_write(ddata, TC358768_TXOPTIONCNTRL, 0x1);
	/* TXTAGOCNT[26:16] RXTASURECNT[10:0] */
	tc358768_write(ddata, TC358768_BTACNTRL1, (0x5 << 16) | (0x5));
	/* START[0] */
	tc358768_write(ddata, TC358768_STARTCNTRL, 0x1);

	/* DSI Tx Timing Control */

	/* Set event mode */
	tc358768_write(ddata, TC358768_DSI_EVENT, 1);

	/* vsw (+ vbp) */
	tc358768_write(ddata, TC358768_DSI_VSW, t->vsw + t->vbp);
	/* vbp (not used in event mode) */
	tc358768_write(ddata, TC358768_DSI_VBPR, 0);
	/* vact */
	tc358768_write(ddata, TC358768_DSI_VACT, t->y_res);

	/* (hsw + hbp) * byteclk * ndl / pclk */
	tc358768_write(ddata, TC358768_DSI_HSW,
		(u32)div_u64((t->hsw + t->hbp) * ((u64)ddata->bitclk / 4) * ddata->dsi_ndl, t->pixelclock));
	/* hbp (not used in event mode) */
	tc358768_write(ddata, TC358768_DSI_HBPR, 0);
	/* hact (bytes) */
	tc358768_write(ddata, TC358768_DSI_HACT, t->x_res * 3);

	/* Start DSI Tx */
	tc358768_write(ddata, TC358768_DSI_START, 0x1);

	/* SET, DSI_Control, 0xa7 */
	/* 0xa7 = HS | CONTCLK | 4-datalines | EoTDisable */
	tc358768_write(ddata, TC358768_DSI_CONFW, (5<<29) | (0x3 << 24) | 0xa7);
	/* CLEAR, DSI_Control, 0x8001 */
	/* 0x8001 = DSIMode */
	tc358768_write(ddata, TC358768_DSI_CONFW, (6<<29) | (0x3 << 24) | 0x8000);

	/* clear FrmStop and RstPtr */
	tc358768_update_bits(ddata, TC358768_PP_MISC, 0x3 << 14, 0);

	/* set PP_en */
	tc358768_update_bits(ddata, TC358768_CONFCTL, 1 << 6, 1 << 6);
}

static void tc358768_power_off(struct tc358768_drv_data *ddata)
{
	/* set FrmStop */
	tc358768_update_bits(ddata, TC358768_PP_MISC, 1 << 15, 1 << 15);

	/* wait at least for one frame */
	msleep(50);

	/* clear PP_en */
	tc358768_update_bits(ddata, TC358768_CONFCTL, 1 << 6, 0);

	/* set RstPtr */
	tc358768_update_bits(ddata, TC358768_PP_MISC, 1 << 14, 1 << 14);
}

#define to_panel_data(p) container_of(p, struct tc358768_drv_data, dssdev)

static int tc358768_connect(struct omap_dss_device *dssdev,
		struct omap_dss_device *dst)
{
	struct tc358768_drv_data *ddata = to_panel_data(dssdev);
	struct omap_dss_device *in = ddata->in;
	int r;

	if (omapdss_device_is_connected(dssdev))
		return 0;

	r = in->ops.dpi->connect(in, dssdev);
	if (r)
		return r;

	dst->src = dssdev;
	dssdev->dst = dst;

	return 0;
}

static void tc358768_disconnect(struct omap_dss_device *dssdev,
		struct omap_dss_device *dst)
{
	struct tc358768_drv_data *ddata = to_panel_data(dssdev);
	struct omap_dss_device *in = ddata->in;

	WARN_ON(!omapdss_device_is_connected(dssdev));
	if (!omapdss_device_is_connected(dssdev))
		return;

	WARN_ON(dst != dssdev->dst);
	if (dst != dssdev->dst)
		return;

	dst->src = NULL;
	dssdev->dst = NULL;

	in->ops.dpi->disconnect(in, dssdev);
}

static int tc358768_enable(struct omap_dss_device *dssdev)
{
	struct tc358768_drv_data *ddata = to_panel_data(dssdev);
	struct omap_dss_device *in = ddata->in;
	int r;

	if (!omapdss_device_is_connected(dssdev))
		return -ENODEV;

	if (omapdss_device_is_enabled(dssdev))
		return 0;

	in->ops.dpi->set_timings(in, &ddata->videomode);

	r = tc358768_calc_pll(ddata);
	if (r)
		return r;

	r = in->ops.dpi->enable(in);
	if (r)
		return r;

	if (ddata->reset_gpio)
		gpiod_set_value_cansleep(ddata->reset_gpio, 1);

	/* wait for encoder clocks to stabilize */
	usleep_range(1000, 2000);

	tc358768_power_on(ddata);

	/* enable panel */
	tc358768_dsi_xfer_short(ddata, MIPI_DSI_TURN_ON_PERIPHERAL, 0, 0);

	dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;

	return 0;
}

static void tc358768_disable(struct omap_dss_device *dssdev)
{
	struct tc358768_drv_data *ddata = to_panel_data(dssdev);
	struct omap_dss_device *in = ddata->in;

	if (!omapdss_device_is_enabled(dssdev))
		return;

	tc358768_power_off(ddata);

	if (ddata->reset_gpio)
		gpiod_set_value_cansleep(ddata->reset_gpio, 0);

	in->ops.dpi->disable(in);

	dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}

static void tc358768_set_timings(struct omap_dss_device *dssdev,
				 struct omap_video_timings *timings)
{
	struct tc358768_drv_data *ddata = to_panel_data(dssdev);
	struct omap_dss_device *in = ddata->in;

	ddata->videomode = *timings;

	ddata->videomode.data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE;
	ddata->videomode.sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE;

	dssdev->panel.timings = ddata->videomode;

	in->ops.dpi->set_timings(in, &ddata->videomode);
}

static void tc358768_get_timings(struct omap_dss_device *dssdev,
				 struct omap_video_timings *timings)
{
	struct tc358768_drv_data *ddata = to_panel_data(dssdev);

	*timings = ddata->videomode;
}

static int tc358768_check_timings(struct omap_dss_device *dssdev,
				  struct omap_video_timings *timings)
{
	struct tc358768_drv_data *ddata = to_panel_data(dssdev);
	struct omap_dss_device *in = ddata->in;

	return in->ops.dpi->check_timings(in, timings);
}

static const struct omapdss_dpi_ops tc358768_dpi_ops = {
	.connect	= tc358768_connect,
	.disconnect	= tc358768_disconnect,

	.enable		= tc358768_enable,
	.disable	= tc358768_disable,

	.check_timings	= tc358768_check_timings,
	.set_timings	= tc358768_set_timings,
	.get_timings	= tc358768_get_timings,
};

static int tc358768_i2c_probe(struct i2c_client *client,
			      const struct i2c_device_id *id)
{
	struct regmap *regmap;
	struct tc358768_drv_data *ddata;
	struct device *dev = &client->dev;
	struct omap_dss_device *dssdev;
	struct gpio_desc *gpio;
	struct device_node *np = dev->of_node;
	struct device_node *ep;
	int r;

	if (!np)
		return -ENODEV;

	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
	if (ddata == NULL)
		return -ENOMEM;

	dev_set_drvdata(dev, ddata);
	ddata->dev = dev;

	ddata->refclk = devm_clk_get(dev, "refclk");
	if (IS_ERR(ddata->refclk))
		return PTR_ERR(ddata->refclk);

	/* get input ep (port0/endpoint0) */
	ep = omapdss_of_get_endpoint(np, 0, 0);
	if (!ep)
		return -EINVAL;

	r = of_property_read_u32(ep, "data-lines", &ddata->dpi_ndl);
	if (r)
		return r;

	of_node_put(ep);

	/* get output ep (port1/endpoint0) */
	ep = omapdss_of_get_endpoint(np, 1, 0);
	if (!ep)
		return -EINVAL;

	r = of_property_count_u32_elems(ep, "lanes");
	if (r < 0)
		return r;
	ddata->dsi_ndl = r / 2 - 1;

	of_node_put(ep);

	regmap = devm_regmap_init_i2c(client, &tc358768_regmap_config);
	if (IS_ERR(regmap)) {
		r = PTR_ERR(regmap);
		dev_err(dev, "Failed to init regmap: %d\n", r);
		return r;
	}

	ddata->regmap = regmap;

	gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
	if (IS_ERR(gpio))
		return PTR_ERR(gpio);

	ddata->reset_gpio = gpio;

	ddata->in = omapdss_of_find_source_for_first_ep(np);
	if (IS_ERR(ddata->in)) {
		dev_err(dev, "failed to find video source\n");
		return PTR_ERR(ddata->in);
	}

	dssdev = &ddata->dssdev;
	dssdev->ops.dpi = &tc358768_dpi_ops;
	dssdev->dev = dev;
	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
	/*
	 * XXX: output is really DSI but for now we only support dummy DSI
	 * displays, and we can model that easily with "DPI" output.
	 */
	dssdev->output_type = OMAP_DISPLAY_TYPE_DPI;
	dssdev->owner = THIS_MODULE;
	dssdev->phy.dpi.data_lines = ddata->dpi_ndl;
	dssdev->port_num = 1;

	r = omapdss_register_output(dssdev);
	if (r) {
		dev_err(dev, "Failed to register tc358768\n");
		goto err_reg_display;
	}

	return 0;

err_reg_display:
	omap_dss_put_device(ddata->in);
	return r;
}

static int tc358768_i2c_remove(struct i2c_client *client)
{
	struct tc358768_drv_data *ddata = dev_get_drvdata(&client->dev);
	struct omap_dss_device *dssdev = &ddata->dssdev;
	struct omap_dss_device *in = ddata->in;

	omapdss_unregister_output(&ddata->dssdev);

	WARN_ON(omapdss_device_is_enabled(dssdev));
	if (omapdss_device_is_enabled(dssdev))
		tc358768_disable(dssdev);

	WARN_ON(omapdss_device_is_connected(dssdev));
	if (omapdss_device_is_connected(dssdev))
		tc358768_disconnect(dssdev, dssdev->dst);

	omap_dss_put_device(in);

	return 0;
}

static const struct i2c_device_id tc358768_id[] = {
	{ TC358768_NAME, 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, tc358768_id);

static const struct of_device_id tc358768_of_match[] = {
	{ .compatible = "omapdss,toshiba,tc358768", },
	{ }
};
MODULE_DEVICE_TABLE(of, tc358768_of_match);

static struct i2c_driver tc358768_i2c_driver = {
	.driver = {
		.owner		= THIS_MODULE,
		.name		= TC358768_NAME,
		.of_match_table	= tc358768_of_match,
	},
	.id_table	= tc358768_id,
	.probe		= tc358768_i2c_probe,
	.remove		= tc358768_i2c_remove,
};

module_i2c_driver(tc358768_i2c_driver);

MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("TC358768AXBG/TC358778XBG DPI-to-DSI Encoder");
MODULE_LICENSE("GPL");