diff options
Diffstat (limited to 'drivers/video/fbdev/mxc/ldb.c')
-rw-r--r-- | drivers/video/fbdev/mxc/ldb.c | 910 |
1 files changed, 910 insertions, 0 deletions
diff --git a/drivers/video/fbdev/mxc/ldb.c b/drivers/video/fbdev/mxc/ldb.c new file mode 100644 index 000000000000..1d6c9e08590c --- /dev/null +++ b/drivers/video/fbdev/mxc/ldb.c @@ -0,0 +1,910 @@ +/* + * 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 "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 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); + +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; + int chno; + + chno = ldb->chan[ldb->primary_chno].is_used ? + !ldb->primary_chno : ldb->primary_chno; + + chan = &ldb->chan[chno]; + + 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 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; +} + +static struct mxc_dispdrv_driver ldb_drv = { + .name = DRIVER_NAME, + .init = ldb_init, + .setup = ldb_setup, + .enable = ldb_enable, + .disable = ldb_disable +}; + +enum { + LVDS_BIT_MAP_SPWG, + LVDS_BIT_MAP_JEIDA, +}; + +static const char *ldb_bit_mappings[] = { + [LVDS_BIT_MAP_SPWG] = "spwg", + [LVDS_BIT_MAP_JEIDA] = "jeida", +}; + +static int of_get_data_mapping(struct device_node *np) +{ + const char *bm; + int ret, i; + + ret = of_property_read_string(np, "fsl,data-mapping", &bm); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ldb_bit_mappings); i++) + if (!strcasecmp(bm, ldb_bit_mappings[i])) + return i; + + return -EINVAL; +} + +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_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, data_width, mapping, 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; + enum crtc crtc; + 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; + + ret = of_property_read_u32(child, "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; + } + + mapping = of_get_data_mapping(child); + 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(child); + if (is_valid_crtc(ldb, crtc, chan->chno)) { + ldb->chan[i].crtc = crtc; + } else { + dev_err(dev, "crtc not specified or invalid\n"); + return -EINVAL; + } + + ret = of_get_videomode(child, &chan->vm, 0); + if (ret) + return -EINVAL; + + 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); |