/* * 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);