/*
 * Copyright (C) 2012-2015 Freescale Semiconductor, Inc. All Rights Reserved.
 */
/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
#include <video/of_display_timing.h>
#include "mxc_dispdrv.h"

#define DRIVER_NAME	"ldb"

#define LDB_BGREF_RMODE_INT		(0x1 << 15)

#define LDB_DI1_VS_POL_ACT_LOW		(0x1 << 10)
#define LDB_DI0_VS_POL_ACT_LOW		(0x1 << 9)

#define LDB_BIT_MAP_CH1_JEIDA		(0x1 << 8)
#define LDB_BIT_MAP_CH0_JEIDA		(0x1 << 6)

#define LDB_DATA_WIDTH_CH1_24		(0x1 << 7)
#define LDB_DATA_WIDTH_CH0_24		(0x1 << 5)

#define LDB_CH1_MODE_MASK		(0x3 << 2)
#define LDB_CH1_MODE_EN_TO_DI1		(0x3 << 2)
#define LDB_CH1_MODE_EN_TO_DI0		(0x1 << 2)
#define LDB_CH0_MODE_MASK		(0x3 << 0)
#define LDB_CH0_MODE_EN_TO_DI1		(0x3 << 0)
#define LDB_CH0_MODE_EN_TO_DI0		(0x1 << 0)

#define LDB_SPLIT_MODE_EN		(0x1 << 4)

#define INVALID_BUS_REG			(~0UL)

struct crtc_mux {
	enum crtc crtc;
	u32 val;
};

struct bus_mux {
	int reg;
	int shift;
	int mask;
	int crtc_mux_num;
	const struct crtc_mux *crtcs;
};

struct ldb_info {
	bool split_cap;
	bool dual_cap;
	bool ext_bgref_cap;
	bool clk_fixup;
	int ctrl_reg;
	int bus_mux_num;
	const struct bus_mux *buses;
};

struct ldb_data;

struct ldb_chan {
	struct ldb_data *ldb;
	struct fb_info *fbi;
	struct videomode vm;
	enum crtc crtc;
	int chno;
	bool is_used;
	bool online;
	struct device_node *np_timings;
};

struct ldb_data {
	struct regmap *regmap;
	struct device *dev;
	struct mxc_dispdrv_handle *mddh;
	struct ldb_chan chan[2];
	int bus_mux_num;
	const struct bus_mux *buses;
	int primary_chno;
	int ctrl_reg;
	u32 ctrl;
	bool spl_mode;
	bool dual_mode;
	bool clk_fixup;
	struct clk *di_clk[4];
	struct clk *ldb_di_clk[2];
	struct clk *div_3_5_clk[2];
	struct clk *div_7_clk[2];
	struct clk *div_sel_clk[2];
};

static const struct crtc_mux imx6q_lvds0_crtc_mux[] = {
	{
		.crtc = CRTC_IPU1_DI0,
		.val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI0,
	}, {
		.crtc = CRTC_IPU1_DI1,
		.val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI1,
	}, {
		.crtc = CRTC_IPU2_DI0,
		.val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI0,
	}, {
		.crtc = CRTC_IPU2_DI1,
		.val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI1,
	}
};

static const struct crtc_mux imx6q_lvds1_crtc_mux[] = {
	{
		.crtc = CRTC_IPU1_DI0,
		.val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU1_DI0,
	}, {
		.crtc = CRTC_IPU1_DI1,
		.val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU1_DI1,
	}, {
		.crtc = CRTC_IPU2_DI0,
		.val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU2_DI0,
	}, {
		.crtc = CRTC_IPU2_DI1,
		.val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU2_DI1,
	}
};

static const struct bus_mux imx6q_ldb_buses[] = {
	{
		.reg = IOMUXC_GPR3,
		.shift = 6,
		.mask = IMX6Q_GPR3_LVDS0_MUX_CTL_MASK,
		.crtc_mux_num = ARRAY_SIZE(imx6q_lvds0_crtc_mux),
		.crtcs = imx6q_lvds0_crtc_mux,
	}, {
		.reg = IOMUXC_GPR3,
		.shift = 8,
		.mask = IMX6Q_GPR3_LVDS1_MUX_CTL_MASK,
		.crtc_mux_num = ARRAY_SIZE(imx6q_lvds1_crtc_mux),
		.crtcs = imx6q_lvds1_crtc_mux,
	}
};

static const struct ldb_info imx6q_ldb_info = {
	.split_cap = true,
	.dual_cap = true,
	.ext_bgref_cap = false,
	.clk_fixup = false,
	.ctrl_reg = IOMUXC_GPR2,
	.bus_mux_num = ARRAY_SIZE(imx6q_ldb_buses),
	.buses = imx6q_ldb_buses,
};

static const struct ldb_info imx6qp_ldb_info = {
	.split_cap = true,
	.dual_cap = true,
	.ext_bgref_cap = false,
	.clk_fixup = true,
	.ctrl_reg = IOMUXC_GPR2,
	.bus_mux_num = ARRAY_SIZE(imx6q_ldb_buses),
	.buses = imx6q_ldb_buses,
};

static const struct crtc_mux imx6dl_lvds0_crtc_mux[] = {
	{
		.crtc = CRTC_IPU1_DI0,
		.val = IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI0,
	}, {
		.crtc = CRTC_IPU1_DI1,
		.val = IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI1,
	}, {
		.crtc = CRTC_LCDIF1,
		.val = IMX6DL_GPR3_LVDS0_MUX_CTL_LCDIF,
	}
};

static const struct crtc_mux imx6dl_lvds1_crtc_mux[] = {
	{
		.crtc = CRTC_IPU1_DI0,
		.val = IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI0,
	}, {
		.crtc = CRTC_IPU1_DI1,
		.val = IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI1,
	}, {
		.crtc = CRTC_LCDIF1,
		.val = IMX6DL_GPR3_LVDS1_MUX_CTL_LCDIF,
	}
};

static const struct bus_mux imx6dl_ldb_buses[] = {
	{
		.reg = IOMUXC_GPR3,
		.shift = 6,
		.mask = IMX6DL_GPR3_LVDS0_MUX_CTL_MASK,
		.crtc_mux_num = ARRAY_SIZE(imx6dl_lvds0_crtc_mux),
		.crtcs = imx6dl_lvds0_crtc_mux,
	}, {
		.reg = IOMUXC_GPR3,
		.shift = 8,
		.mask = IMX6DL_GPR3_LVDS1_MUX_CTL_MASK,
		.crtc_mux_num = ARRAY_SIZE(imx6dl_lvds1_crtc_mux),
		.crtcs = imx6dl_lvds1_crtc_mux,
	}
};

static const struct ldb_info imx6dl_ldb_info = {
	.split_cap = true,
	.dual_cap = true,
	.ext_bgref_cap = false,
	.clk_fixup = false,
	.ctrl_reg = IOMUXC_GPR2,
	.bus_mux_num = ARRAY_SIZE(imx6dl_ldb_buses),
	.buses = imx6dl_ldb_buses,
};

static const struct crtc_mux imx6sx_lvds_crtc_mux[] = {
	{
		.crtc = CRTC_LCDIF1,
		.val = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_LCDIF1,
	}, {
		.crtc = CRTC_LCDIF2,
		.val = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_LCDIF2,
	}
};

static const struct bus_mux imx6sx_ldb_buses[] = {
	{
		.reg = IOMUXC_GPR5,
		.shift = 3,
		.mask = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_MASK,
		.crtc_mux_num = ARRAY_SIZE(imx6sx_lvds_crtc_mux),
		.crtcs = imx6sx_lvds_crtc_mux,
	}
};

static const struct ldb_info imx6sx_ldb_info = {
	.split_cap = false,
	.dual_cap = false,
	.ext_bgref_cap = false,
	.clk_fixup = false,
	.ctrl_reg = IOMUXC_GPR6,
	.bus_mux_num = ARRAY_SIZE(imx6sx_ldb_buses),
	.buses = imx6sx_ldb_buses,
};

static const struct crtc_mux imx53_lvds0_crtc_mux[] = {
	{ .crtc = CRTC_IPU1_DI0, },
};

static const struct crtc_mux imx53_lvds1_crtc_mux[] = {
	{ .crtc = CRTC_IPU1_DI1, }
};

static const struct bus_mux imx53_ldb_buses[] = {
	{
		.reg = INVALID_BUS_REG,
		.crtc_mux_num = ARRAY_SIZE(imx53_lvds0_crtc_mux),
		.crtcs = imx53_lvds0_crtc_mux,
	}, {
		.reg = INVALID_BUS_REG,
		.crtc_mux_num = ARRAY_SIZE(imx53_lvds1_crtc_mux),
		.crtcs = imx53_lvds1_crtc_mux,
	}
};

static const struct ldb_info imx53_ldb_info = {
	.split_cap = true,
	.dual_cap = false,
	.ext_bgref_cap = true,
	.clk_fixup = false,
	.ctrl_reg = IOMUXC_GPR2,
	.bus_mux_num = ARRAY_SIZE(imx53_ldb_buses),
	.buses = imx53_ldb_buses,
};

static const struct of_device_id ldb_dt_ids[] = {
	{ .compatible = "fsl,imx6qp-ldb", .data = &imx6qp_ldb_info, },
	{ .compatible = "fsl,imx6q-ldb", .data = &imx6q_ldb_info, },
	{ .compatible = "fsl,imx6dl-ldb", .data = &imx6dl_ldb_info, },
	{ .compatible = "fsl,imx6sx-ldb", .data = &imx6sx_ldb_info, },
	{ .compatible = "fsl,imx53-ldb", .data = &imx53_ldb_info, },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ldb_dt_ids);


#define LDB_SPL_DI0 1
#define LDB_SPL_DI1 2
#define LDB_DUL_DI0 3
#define LDB_DUL_DI1 4
#define LDB_SIN0    5
#define LDB_SIN1    6
#define LDB_SEP0    7
#define LDB_SEP1    8


#include <uapi/linux/ipu.h>
static int bits_per_pixel (int pixel_fmt) {
	switch (pixel_fmt) {
		case IPU_PIX_FMT_BGR24:
		case IPU_PIX_FMT_RGB24:
			return 24;
			break;
		case IPU_PIX_FMT_BGR666:
		case IPU_PIX_FMT_RGB666:
		case IPU_PIX_FMT_LVDS666:
			return 18;
			break;
		default:
			break;
	}
	return 0;
}


static int get_di_clk_id(struct ldb_chan chan, int *id)
{
	struct ldb_data *ldb = chan.ldb;
	int i = 0, chno = chan.chno, mask, shift;
	enum crtc crtc;
	u32 val;

	/* no pre-muxing, such as mx53 */
	if (ldb->buses[chno].reg == INVALID_BUS_REG) {
		*id = chno;
		return 0;
	}

	for (; i < ldb->buses[chno].crtc_mux_num; i++) {
		crtc = ldb->buses[chno].crtcs[i].crtc;
		val  = ldb->buses[chno].crtcs[i].val;
		mask = ldb->buses[chno].mask;
		shift = ldb->buses[chno].shift;
		if (chan.crtc == crtc) {
			*id = (val & mask) >> shift;
			return 0;
		}
	}

	return -EINVAL;
}

static int get_mux_val(struct bus_mux bus_mux, enum crtc crtc,
		       u32 *mux_val)
{
	int i = 0;

	for (; i < bus_mux.crtc_mux_num; i++)
		if (bus_mux.crtcs[i].crtc == crtc) {
			*mux_val = bus_mux.crtcs[i].val;
			return 0;
		}

	return -EINVAL;
}

static int find_ldb_chno(struct ldb_data *ldb,
			 struct fb_info *fbi, int *chno)
{
	struct device *dev = ldb->dev;
	int i = 0;

	for (; i < 2; i++)
		if (ldb->chan[i].fbi == fbi) {
			*chno = ldb->chan[i].chno;
			return 0;
		}
	dev_err(dev, "failed to find channel number\n");
	return -EINVAL;
}

static void ldb_disable(struct mxc_dispdrv_handle *mddh,
			struct fb_info *fbi);

static int ldb_setup(struct mxc_dispdrv_handle *mddh,
		     struct fb_info *fbi)
{
	struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
	struct ldb_chan chan;
	struct device *dev = ldb->dev;
	struct clk *ldb_di_parent, *ldb_di_sel, *ldb_di_sel_parent;
	struct clk *other_ldb_di_sel = NULL;
	struct bus_mux bus_mux;
	int ret = 0, id = 0, chno, other_chno;
	unsigned long serial_clk;
	u32 mux_val;

	ret = find_ldb_chno(ldb, fbi, &chno);
	if (ret < 0)
		return ret;

	other_chno = chno ? 0 : 1;

	chan = ldb->chan[chno];

	bus_mux = ldb->buses[chno];

	ret = get_di_clk_id(chan, &id);
	if (ret < 0) {
		dev_err(dev, "failed to get ch%d di clk id\n",
			chan.chno);
		return ret;
	}

	ret = get_mux_val(bus_mux, chan.crtc, &mux_val);
	if (ret < 0) {
		dev_err(dev, "failed to get ch%d mux val\n",
			chan.chno);
		return ret;
	}


	if (ldb->clk_fixup) {
		/*
		 * ldb_di_sel_parent(plls) -> ldb_di_sel -> ldb_di[chno] ->
		 *
		 *     -> div_3_5[chno] ->
		 * -> |                   |-> div_sel[chno] -> di[id]
		 *     ->  div_7[chno] ->
		 */
		clk_set_parent(ldb->di_clk[id], ldb->div_sel_clk[chno]);
	} else {
		/*
		 * ldb_di_sel_parent(plls) -> ldb_di_sel ->
		 *
		 *     -> div_3_5[chno] ->
		 * -> |                   |-> div_sel[chno] ->
		 *     ->  div_7[chno] ->
		 *
		 * -> ldb_di[chno] -> di[id]
		 */
		clk_set_parent(ldb->di_clk[id], ldb->ldb_di_clk[chno]);
	}
	ldb_di_parent = ldb->spl_mode ? ldb->div_3_5_clk[chno] :
			ldb->div_7_clk[chno];
	clk_set_parent(ldb->div_sel_clk[chno], ldb_di_parent);
	ldb_di_sel = clk_get_parent(ldb_di_parent);
	ldb_di_sel_parent = clk_get_parent(ldb_di_sel);
	serial_clk = ldb->spl_mode ? chan.vm.pixelclock * 7 / 2 :
			chan.vm.pixelclock * 7;
	clk_set_rate(ldb_di_sel_parent, serial_clk);

	/*
	 * split mode or dual mode:
	 * clock tree for the other channel
	 */
	if (ldb->spl_mode) {
		clk_set_parent(ldb->div_sel_clk[other_chno],
			       ldb->div_3_5_clk[other_chno]);
		other_ldb_di_sel =
			clk_get_parent(ldb->div_3_5_clk[other_chno]);;
	}

	if (ldb->dual_mode) {
		clk_set_parent(ldb->div_sel_clk[other_chno],
			       ldb->div_7_clk[other_chno]);
		other_ldb_di_sel =
			clk_get_parent(ldb->div_7_clk[other_chno]);;
	}

	if (ldb->spl_mode || ldb->dual_mode)
		clk_set_parent(other_ldb_di_sel, ldb_di_sel_parent);

	if (!(chan.fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)) {
		if (ldb->spl_mode && bus_mux.reg == INVALID_BUS_REG)
			/* no pre-muxing, such as mx53 */
			ldb->ctrl |= (id == 0 ? LDB_DI0_VS_POL_ACT_LOW :
					LDB_DI1_VS_POL_ACT_LOW);
		else
			ldb->ctrl |= (chno == 0 ? LDB_DI0_VS_POL_ACT_LOW :
					LDB_DI1_VS_POL_ACT_LOW);
	}

	if (bus_mux.reg != INVALID_BUS_REG)
		regmap_update_bits(ldb->regmap, bus_mux.reg,
				   bus_mux.mask, mux_val);

	regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl);

	/* disable channel for correct sequence */
	ldb_disable(mddh, fbi);

	return ret;
}

static int ldb_enable(struct mxc_dispdrv_handle *mddh,
		      struct fb_info *fbi)
{
	struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
	struct ldb_chan chan;
	struct device *dev = ldb->dev;
	struct bus_mux bus_mux;
	int ret = 0, id = 0, chno, other_chno;

	ret = find_ldb_chno(ldb, fbi, &chno);
	if (ret < 0)
		return ret;

	chan = ldb->chan[chno];

	bus_mux = ldb->buses[chno];

	if (ldb->spl_mode || ldb->dual_mode) {
		other_chno = chno ? 0 : 1;
		clk_prepare_enable(ldb->ldb_di_clk[other_chno]);
	}

	if ((ldb->spl_mode || ldb->dual_mode) &&
	    bus_mux.reg == INVALID_BUS_REG) {
		/* no pre-muxing, such as mx53 */
		ret = get_di_clk_id(chan, &id);
		if (ret < 0) {
			dev_err(dev, "failed to get ch%d di clk id\n",
				chan.chno);
			return ret;
		}

		ldb->ctrl |= id ?
			(LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1) :
			(LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0);
	} else {
		if (ldb->spl_mode || ldb->dual_mode)
			ldb->ctrl |= LDB_CH0_MODE_EN_TO_DI0 |
				     LDB_CH1_MODE_EN_TO_DI0;
		else
			ldb->ctrl |= chno ? LDB_CH1_MODE_EN_TO_DI1 :
					    LDB_CH0_MODE_EN_TO_DI0;
	}

	regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl);
	return 0;
}

static void ldb_disable(struct mxc_dispdrv_handle *mddh,
		       struct fb_info *fbi)
{
	struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
	int ret, chno, other_chno;

	ret = find_ldb_chno(ldb, fbi, &chno);
	if (ret < 0)
		return;

	if (ldb->spl_mode || ldb->dual_mode) {
		ldb->ctrl &= ~(LDB_CH1_MODE_MASK | LDB_CH0_MODE_MASK);
		other_chno = chno ? 0 : 1;
		clk_disable_unprepare(ldb->ldb_di_clk[other_chno]);
	} else {
		ldb->ctrl &= ~(chno ? LDB_CH1_MODE_MASK :
				      LDB_CH0_MODE_MASK);
	}

	regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl);
	return;
}

enum {
	LVDS_BIT_MAP_SPWG,
	LVDS_BIT_MAP_JEIDA,
};

static int datamap_mode = LVDS_BIT_MAP_JEIDA;

static int __init datamap_mode_setup (char *options) {

        if (!strcmp(options, "jeida"))
                datamap_mode = LVDS_BIT_MAP_JEIDA;
        else if (!strcmp(options, "spwg"))
		datamap_mode = LVDS_BIT_MAP_SPWG;
	else
		datamap_mode = LVDS_BIT_MAP_JEIDA;

        return 1;
}
__setup("datamap=", datamap_mode_setup);

static const char *ldb_crtc_mappings[] = {
	[CRTC_IPU_DI0] = "ipu-di0",
	[CRTC_IPU_DI1] = "ipu-di1",
	[CRTC_IPU1_DI0] = "ipu1-di0",
	[CRTC_IPU1_DI1] = "ipu1-di1",
	[CRTC_IPU2_DI0] = "ipu2-di0",
	[CRTC_IPU2_DI1] = "ipu2-di1",
	[CRTC_LCDIF] = "lcdif",
	[CRTC_LCDIF1] = "lcdif1",
	[CRTC_LCDIF2] = "lcdif2",
};

static enum crtc of_get_crtc_mapping(struct device_node *np)
{
	const char *cm;
	enum crtc i;
	int ret;

	ret = of_property_read_string(np, "crtc", &cm);
	if (ret < 0)
		return ret;

	for (i = 0; i < ARRAY_SIZE(ldb_crtc_mappings); i++)
		if (!strcasecmp(cm, ldb_crtc_mappings[i])) {
			switch (i) {
			case CRTC_IPU_DI0:
				i = CRTC_IPU1_DI0;
				break;
			case CRTC_IPU_DI1:
				i = CRTC_IPU1_DI1;
				break;
			case CRTC_LCDIF:
				i = CRTC_LCDIF1;
				break;
			default:
				break;
			}
			return i;
		}

	return -EINVAL;
}

static int mux_count(struct ldb_data *ldb)
{
	int i, j, count = 0;
	bool should_count[CRTC_MAX];
	enum crtc crtc;

	for (i = 0; i < CRTC_MAX; i++)
		should_count[i] = true;

	for (i = 0; i < ldb->bus_mux_num; i++) {
		for (j = 0; j < ldb->buses[i].crtc_mux_num; j++) {
			crtc = ldb->buses[i].crtcs[j].crtc;
			if (should_count[crtc]) {
				count++;
				should_count[crtc] = false;
			}
		}
	}

	return count;
}

static bool is_valid_crtc(struct ldb_data *ldb, enum crtc crtc,
			  int chno)
{
	int i = 0;

	if (chno > ldb->bus_mux_num - 1)
		return false;

	for (; i < ldb->buses[chno].crtc_mux_num; i++)
		if (ldb->buses[chno].crtcs[i].crtc == crtc)
			return true;

	return false;
}

static int ldb_init(struct mxc_dispdrv_handle *mddh,
		    struct mxc_dispdrv_setting *setting)
{
	struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
	struct device *dev = ldb->dev;
	struct fb_info *fbi = setting->fbi;
	struct ldb_chan *chan;
	struct fb_videomode fb_vm;
	enum crtc crtc;
	int chno, ret = 0;
	int data_width, mapping, i = 0;

	chno = ldb->chan[ldb->primary_chno].is_used ?
		!ldb->primary_chno : ldb->primary_chno;

	chan = &ldb->chan[chno];

	if ( bits_per_pixel(setting->if_fmt) != 18 &&
			bits_per_pixel(setting->if_fmt) != 24 ) {
		ret = of_property_read_u32 (chan->np_timings, "fsl,data-width",
				&data_width);
		if (ret || (data_width != 18 && data_width != 24)) {
			dev_err(dev, "data width not specified or invalid\n");
			return -EINVAL;
		}
	} else {
		data_width = bits_per_pixel(setting->if_fmt);
	}

		mapping = datamap_mode;

		switch (mapping) {
		case LVDS_BIT_MAP_SPWG:
			if (data_width == 24) {
				if (i == 0 || ldb->spl_mode || ldb->dual_mode)
					ldb->ctrl |= LDB_DATA_WIDTH_CH0_24;
				if (i == 1 || ldb->spl_mode || ldb->dual_mode)
					ldb->ctrl |= LDB_DATA_WIDTH_CH1_24;
			}
			break;
		case LVDS_BIT_MAP_JEIDA:
			if (data_width == 18) {
				dev_err(dev, "JEIDA only support 24bit\n");
				return -EINVAL;
			}
			if (i == 0 || ldb->spl_mode || ldb->dual_mode)
				ldb->ctrl |= LDB_DATA_WIDTH_CH0_24 |
					     LDB_BIT_MAP_CH0_JEIDA;
			if (i == 1 || ldb->spl_mode || ldb->dual_mode)
				ldb->ctrl |= LDB_DATA_WIDTH_CH1_24 |
					     LDB_BIT_MAP_CH1_JEIDA;
			break;
		default:
			dev_err(dev, "data mapping not specified or invalid\n");
			return -EINVAL;
		}

	crtc = of_get_crtc_mapping(chan->np_timings);
	if (is_valid_crtc(ldb, crtc, chan->chno)) {
		ldb->chan[chno].crtc = crtc;
	} else {
		dev_err(dev, "crtc not specified or invalid\n");
		return -EINVAL;
	}


	if ( setting->dft_mode_str != NULL ) {
		ret = of_search_and_get_videomode (chan->np_timings,
				setting->dft_mode_str, &chan->vm, -1);

		if ( ret ) {
			dev_warn (dev, "using display timing with name %s.\n", setting->dft_mode_str);
		} else {
			ret = of_get_videomode(chan->np_timings, &chan->vm, -1);
			if ( ret )
				return -EINVAL;

			dev_warn (dev, "using default display timing.\n");
		}

	} else {
		ret = of_get_videomode(chan->np_timings, &chan->vm, -1);
		if ( ret )
			return -EINVAL;

		dev_warn (dev, "using default display timing.\n");
	}

	if (chan->is_used) {
		dev_err(dev, "LVDS channel%d is already used\n", chno);
		return -EBUSY;
	}
	if (!chan->online) {
		dev_err(dev, "LVDS channel%d is not online\n", chno);
		return -ENODEV;
	}

	chan->is_used = true;

	chan->fbi = fbi;

	fb_videomode_from_videomode(&chan->vm, &fb_vm);
	fb_videomode_to_var(&fbi->var, &fb_vm);

	setting->crtc = chan->crtc;

	return 0;
}


static struct mxc_dispdrv_driver ldb_drv = {
	.name		= DRIVER_NAME,
	.init		= ldb_init,
	.setup		= ldb_setup,
	.enable		= ldb_enable,
	.disable	= ldb_disable
};


static int ldb_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	const struct of_device_id *of_id =
			of_match_device(ldb_dt_ids, dev);
	const struct ldb_info *ldb_info =
			(const struct ldb_info *)of_id->data;
	struct device_node *np = dev->of_node, *child;
	struct ldb_data *ldb;
	bool ext_ref;
	int i, child_count = 0;
	char clkname[16];

	ldb = devm_kzalloc(dev, sizeof(*ldb), GFP_KERNEL);
	if (!ldb)
		return -ENOMEM;

	ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
	if (IS_ERR(ldb->regmap)) {
		dev_err(dev, "failed to get parent regmap\n");
		return PTR_ERR(ldb->regmap);
	}

	ldb->dev = dev;
	ldb->bus_mux_num = ldb_info->bus_mux_num;
	ldb->buses = ldb_info->buses;
	ldb->ctrl_reg = ldb_info->ctrl_reg;
	ldb->clk_fixup = ldb_info->clk_fixup;
	ldb->primary_chno = -1;

	ext_ref = of_property_read_bool(np, "ext-ref");
	if (!ext_ref && ldb_info->ext_bgref_cap)
		ldb->ctrl |= LDB_BGREF_RMODE_INT;

	ldb->spl_mode = of_property_read_bool(np, "split-mode");
	if (ldb->spl_mode) {
		if (ldb_info->split_cap) {
			ldb->ctrl |= LDB_SPLIT_MODE_EN;
			dev_info(dev, "split mode\n");
		} else {
			dev_err(dev, "cannot support split mode\n");
			return -EINVAL;
		}
	}

	ldb->dual_mode = of_property_read_bool(np, "dual-mode");
	if (ldb->dual_mode) {
		if (ldb_info->dual_cap) {
			dev_info(dev, "dual mode\n");
		} else {
			dev_err(dev, "cannot support dual mode\n");
			return -EINVAL;
		}
	}

	if (ldb->dual_mode && ldb->spl_mode) {
		dev_err(dev, "cannot support dual mode and split mode "
				"simultaneously\n");
		return -EINVAL;
	}

	for (i = 0; i < mux_count(ldb); i++) {
		sprintf(clkname, "di%d_sel", i);
		ldb->di_clk[i] = devm_clk_get(dev, clkname);
		if (IS_ERR(ldb->di_clk[i])) {
			dev_err(dev, "failed to get clk %s\n", clkname);
			return PTR_ERR(ldb->di_clk[i]);
		}
	}

	for_each_child_of_node(np, child) {
		struct ldb_chan *chan;
		bool is_primary;
		int ret;

		ret = of_property_read_u32(child, "reg", &i);
		if (ret || i < 0 || i > 1 || i >= ldb->bus_mux_num) {
			dev_err(dev, "wrong LVDS channel number\n");
			return -EINVAL;
		}

		if ((ldb->spl_mode || ldb->dual_mode) && i > 0) {
			dev_warn(dev, "split mode or dual mode, ignoring "
				"second output\n");
			continue;
		}

		if (!of_device_is_available(child))
			continue;

		if (++child_count > ldb->bus_mux_num) {
			dev_err(dev, "too many LVDS channels\n");
			return -EINVAL;
		}

		chan = &ldb->chan[i];
		chan->chno = i;
		chan->ldb = ldb;
		chan->online = true;

		is_primary = of_property_read_bool(child, "primary");

		if (ldb->bus_mux_num == 1 || (ldb->primary_chno == -1 &&
		    (is_primary || ldb->spl_mode || ldb->dual_mode)))
			ldb->primary_chno = chan->chno;

		chan->np_timings = child;

		sprintf(clkname, "ldb_di%d", i);
		ldb->ldb_di_clk[i] = devm_clk_get(dev, clkname);
		if (IS_ERR(ldb->ldb_di_clk[i])) {
			dev_err(dev, "failed to get clk %s\n", clkname);
			return PTR_ERR(ldb->ldb_di_clk[i]);
		}

		sprintf(clkname, "ldb_di%d_div_3_5", i);
		ldb->div_3_5_clk[i] = devm_clk_get(dev, clkname);
		if (IS_ERR(ldb->div_3_5_clk[i])) {
			dev_err(dev, "failed to get clk %s\n", clkname);
			return PTR_ERR(ldb->div_3_5_clk[i]);
		}

		sprintf(clkname, "ldb_di%d_div_7", i);
		ldb->div_7_clk[i] = devm_clk_get(dev, clkname);
		if (IS_ERR(ldb->div_7_clk[i])) {
			dev_err(dev, "failed to get clk %s\n", clkname);
			return PTR_ERR(ldb->div_7_clk[i]);
		}

		sprintf(clkname, "ldb_di%d_div_sel", i);
		ldb->div_sel_clk[i] = devm_clk_get(dev, clkname);
		if (IS_ERR(ldb->div_sel_clk[i])) {
			dev_err(dev, "failed to get clk %s\n", clkname);
			return PTR_ERR(ldb->div_sel_clk[i]);
		}
	}

	if (child_count == 0) {
		dev_err(dev, "failed to find valid LVDS channel\n");
		return -EINVAL;
	}

	if (ldb->primary_chno == -1) {
		dev_err(dev, "failed to know primary channel\n");
		return -EINVAL;
	}

	ldb->mddh = mxc_dispdrv_register(&ldb_drv);
	mxc_dispdrv_setdata(ldb->mddh, ldb);
	dev_set_drvdata(&pdev->dev, ldb);

	return 0;
}

static int ldb_remove(struct platform_device *pdev)
{
	struct ldb_data *ldb = dev_get_drvdata(&pdev->dev);

	mxc_dispdrv_puthandle(ldb->mddh);
	mxc_dispdrv_unregister(ldb->mddh);
	return 0;
}

static struct platform_driver ldb_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.of_match_table	= ldb_dt_ids,
	},
	.probe = ldb_probe,
	.remove = ldb_remove,
};

module_platform_driver(ldb_driver);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("LDB driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRIVER_NAME);