查看imx6 kernel中lvds設備和驅動的初始化過程。ios
相關文檔:
arm/arm/mach-mx6/board-mx6q_sabresd.capp
kernel/drivers/video/mxc/ldb.cide
arm/arm/mach-mx6/board-mx6q_sabresd.c MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board") /* Maintainer: Freescale Semiconductor, Inc. */ .boot_params = MX6_PHYS_OFFSET + 0x100, .fixup = fixup_mxc_board, .map_io = mx6_map_io, .init_irq = mx6_init_irq, .init_machine = mx6_sabresd_board_init, ----------+ .timer = &mx6_sabresd_timer, | .reserve = mx6q_sabresd_reserve, | MACHINE_END | | // board-mx6q_sabresd.c | static void __init mx6_sabresd_board_init(void) <---+ { ------------------------------+ ...... / | imx6q_add_ipuv3(0, &ipu_data[0]); ---+ | // ipu | | imx6q_add_ldb(&ldb_data); ----------------------------+ //ldb ...... | | | } | | | V | | #define imx6q_add_ipuv3(id, pdata) \ | | imx_add_ipuv3(id, &imx6q_ipuv3_data[id], pdata) | | | | struct platform_device *__init imx_add_ipuv3( | | const int id, | | const struct imx_ipuv3_data *data, | | struct imx_ipuv3_platform_data *pdata) | | { | | struct resource res[] = { | | { | | .start = data->iobase, | | .end = data->iobase + data->iosize - 1, | | .flags = IORESOURCE_MEM, | | }, { | | .start = data->irq_err, | | .end = data->irq_err, | | .flags = IORESOURCE_IRQ, | | }, { | | .start = data->irq, | | .end = data->irq, | | .flags = IORESOURCE_IRQ, | | }, | | }; | | | | pdata->init = data->init; | | pdata->pg = data->pg; | | | | return imx_add_platform_device_dmamask("imx-ipuv3", id,| | res, ARRAY_SIZE(res), pdata, sizeof(*pdata), | | DMA_BIT_MASK(32)); | | } | | | | static struct imx_ipuv3_platform_data ipu_data[] = { <--+ | { | .rev = 4, | .csi_clk[0] = "clko_clk", | .bypass_reset = false, | }, { | .rev = 4, | .csi_clk[0] = "clko_clk", | .bypass_reset = false, | }, | }; | | #define imx6q_add_ldb(pdata) \ <-------------+ imx_add_ldb(&imx6q_ldb_data, pdata); | | struct platform_device *__init imx_add_ldb( | const struct imx_ldb_data *data, | struct fsl_mxc_ldb_platform_data *pdata) | { | struct resource res[] = { | { | .start = data->iobase, | .end = data->iobase + data->iosize - 1, | .flags = IORESOURCE_MEM, | }, | }; | | return imx_add_platform_device("mxc_ldb", -1, | res, ARRAY_SIZE(res), pdata, sizeof(*pdata)); | } | | static struct fsl_mxc_ldb_platform_data ldb_data = { <------+ .ipu_id = 0, .disp_id = 1, .ext_ref = 1, .mode = LDB_SEP1, .sec_ipu_id = 0, .sec_disp_id = 0, };
kernel/drivers/video/mxc/ldb.c static int __init ldb_init(void) { return platform_driver_register(&mxcldb_driver); -----+ } | | static struct platform_driver mxcldb_driver = { <---+ .driver = { .name = "mxc_ldb", }, .probe = ldb_probe, -----+ .remove = ldb_remove, | .suspend = ldb_suspend, | .resume = ldb_resume, | }; | | static int ldb_probe(struct platform_device *pdev) <---+ { int ret = 0; struct ldb_data *ldb; ldb = kzalloc(sizeof(struct ldb_data), GFP_KERNEL); if (!ldb) { ret = -ENOMEM; goto alloc_failed; } +------------------+ ldb->pdev = pdev; | | ldb->disp_ldb = mxc_dispdrv_register(&ldb_drv); | mxc_dispdrv_setdata(ldb->disp_ldb, ldb); | | dev_set_drvdata(&pdev->dev, ldb); | | /* | * Disable HannStar touch panel CABC function, | * this function turns the panel's backlight automatically | * according to the content shown on the panel which | * may cause annoying unstable backlight issue. | * | */ | | alloc_failed: | return ret; | } | | | static struct mxc_dispdrv_driver ldb_drv = { <-------+ .name = DISPDRV_LDB, .init = ldb_disp_init, ----------------------+ .deinit = ldb_disp_deinit, | .setup = ldb_disp_setup, ----------------------------------------------+ }; | | | | static int ldb_disp_init(struct mxc_dispdrv_handle *disp, <--+ | struct mxc_dispdrv_setting *setting) | { | int ret = 0, i; | struct ldb_data *ldb = mxc_dispdrv_getdata(disp); | struct fsl_mxc_ldb_platform_data *plat_data = ldb->pdev->dev.platform_data; | struct i2c_client *i2c_dev; | struct resource *res; | uint32_t base_addr; | uint32_t reg, setting_idx; | uint32_t ch_mask = 0, ch_val = 0; | int lvds_channel = 0; | uint32_t ipu_id, disp_id; | | /* if input format not valid, make RGB666 as default*/ | if (!valid_mode(setting->if_fmt)) { | dev_warn(&ldb->pdev->dev, "Input pixel format not valid" | " use default RGB666\n"); | setting->if_fmt = IPU_PIX_FMT_RGB666; | } | //初始化 | if (!ldb->inited) { | char di_clk[] = "ipu1_di0_clk"; | char ldb_clk[] = "ldb_di0_clk"; | | setting_idx = 0; | res = platform_get_resource(ldb->pdev, IORESOURCE_MEM, 0); | if (IS_ERR(res)) | return -ENOMEM; | | base_addr = res->start; | ldb->reg = ioremap(base_addr, res->end - res->start + 1); | ldb->control_reg = ldb->reg + 2; | ldb->gpr3_reg = ldb->reg + 3; | | ldb->lvds_bg_reg = regulator_get(&ldb->pdev->dev, plat_data->lvds_bg_reg); | if (!IS_ERR(ldb->lvds_bg_reg)) { | regulator_set_voltage(ldb->lvds_bg_reg, 2500000, 2500000); | regulator_enable(ldb->lvds_bg_reg); | } | | /* ipu selected by platform data setting */ | setting->dev_id = plat_data->ipu_id; | | reg = readl(ldb->control_reg); | //ldb參考電阻選擇 | /* refrence resistor select */ | reg &= ~LDB_BGREF_RMODE_MASK; | if (plat_data->ext_ref) | reg |= LDB_BGREF_RMODE_EXT; | else | reg |= LDB_BGREF_RMODE_INT; | //使用SPWG標準對數據進行映射 | /* TODO: now only use SPWG data mapping for both channel */ | reg &= ~(LDB_BIT_MAP_CH0_MASK | LDB_BIT_MAP_CH1_MASK); | reg |= LDB_BIT_MAP_CH0_SPWG | LDB_BIT_MAP_CH1_SPWG; | | /* channel mode setting */ | reg &= ~(LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); | reg &= ~(LDB_DATA_WIDTH_CH0_MASK | LDB_DATA_WIDTH_CH1_MASK); | //指定數據寬度,24bit或者18bit | if (bits_per_pixel(setting->if_fmt) == 24) | reg |= LDB_DATA_WIDTH_CH0_24 | LDB_DATA_WIDTH_CH1_24; | else | reg |= LDB_DATA_WIDTH_CH0_18 | LDB_DATA_WIDTH_CH1_18; | | if (g_ldb_mode) | ldb->mode = g_ldb_mode; | else | ldb->mode = plat_data->mode; | //single mode | if ((ldb->mode == LDB_SIN0) || (ldb->mode == LDB_SIN1)) { | ret = ldb->mode - LDB_SIN0; | if (plat_data->disp_id != ret) { | dev_warn(&ldb->pdev->dev, | "change IPU DI%d to IPU DI%d for LDB " | "channel%d.\n", | plat_data->disp_id, ret, ret); | plat_data->disp_id = ret; | } | // separate mode | } else if (((ldb->mode == LDB_SEP0) || (ldb->mode == LDB_SEP1)) | && (cpu_is_mx6q() || cpu_is_mx6dl())) { | if (plat_data->disp_id == plat_data->sec_disp_id) { | dev_err(&ldb->pdev->dev, | "For LVDS separate mode," | "two DIs should be different!\n"); | return -EINVAL; | } | | if (((!plat_data->disp_id) && (ldb->mode == LDB_SEP1)) | || ((plat_data->disp_id) && | (ldb->mode == LDB_SEP0))) { | dev_dbg(&ldb->pdev->dev, | "LVDS separate mode:" | "swap DI configuration!\n"); | ipu_id = plat_data->ipu_id; | disp_id = plat_data->disp_id; | plat_data->ipu_id = plat_data->sec_ipu_id; | plat_data->disp_id = plat_data->sec_disp_id; | plat_data->sec_ipu_id = ipu_id; | plat_data->sec_disp_id = disp_id; | } | } | // split mode | if (ldb->mode == LDB_SPL_DI0) { | reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI0 | | LDB_CH1_MODE_EN_TO_DI0; | setting->disp_id = 0; | } else if (ldb->mode == LDB_SPL_DI1) { | reg |= LDB_SPLIT_MODE_EN | LDB_CH0_MODE_EN_TO_DI1 | | LDB_CH1_MODE_EN_TO_DI1; | setting->disp_id = 1; | } else if (ldb->mode == LDB_DUL_DI0) { | reg &= ~LDB_SPLIT_MODE_EN; | reg |= LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0; | setting->disp_id = 0; | } else if (ldb->mode == LDB_DUL_DI1) { | reg &= ~LDB_SPLIT_MODE_EN; | reg |= LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1; | setting->disp_id = 1; | } else if (ldb->mode == LDB_SIN0) { | reg &= ~LDB_SPLIT_MODE_EN; | setting->disp_id = plat_data->disp_id; | if (setting->disp_id == 0) | reg |= LDB_CH0_MODE_EN_TO_DI0; | else | reg |= LDB_CH0_MODE_EN_TO_DI1; | ch_mask = LDB_CH0_MODE_MASK; | ch_val = reg & LDB_CH0_MODE_MASK; | } else if (ldb->mode == LDB_SIN1) { | reg &= ~LDB_SPLIT_MODE_EN; | setting->disp_id = plat_data->disp_id; | if (setting->disp_id == 0) | reg |= LDB_CH1_MODE_EN_TO_DI0; | else | reg |= LDB_CH1_MODE_EN_TO_DI1; | ch_mask = LDB_CH1_MODE_MASK; | ch_val = reg & LDB_CH1_MODE_MASK; | } else { /* separate mode*/ | // dual mode | setting->disp_id = plat_data->disp_id; | | /* first output is LVDS0 or LVDS1 */ | if (ldb->mode == LDB_SEP0) | lvds_channel = 0; | else | lvds_channel = 1; | | reg &= ~LDB_SPLIT_MODE_EN; | | if ((lvds_channel == 0) && (setting->disp_id == 0)) | reg |= LDB_CH0_MODE_EN_TO_DI0; | else if ((lvds_channel == 0) && (setting->disp_id == 1)) | reg |= LDB_CH0_MODE_EN_TO_DI1; | else if ((lvds_channel == 1) && (setting->disp_id == 0)) | reg |= LDB_CH1_MODE_EN_TO_DI0; | else | reg |= LDB_CH1_MODE_EN_TO_DI1; | ch_mask = lvds_channel ? LDB_CH1_MODE_MASK : | LDB_CH0_MODE_MASK; | ch_val = reg & ch_mask; | | if (bits_per_pixel(setting->if_fmt) == 24) { | if (lvds_channel == 0) | reg &= ~LDB_DATA_WIDTH_CH1_24; | else | reg &= ~LDB_DATA_WIDTH_CH0_24; | } else { | if (lvds_channel == 0) | reg &= ~LDB_DATA_WIDTH_CH1_18; | else | reg &= ~LDB_DATA_WIDTH_CH0_18; | } | } | | writel(reg, ldb->control_reg); | if (ldb->mode < LDB_SIN0) { | ch_mask = LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK; | ch_val = reg & (LDB_CH0_MODE_MASK | LDB_CH1_MODE_MASK); | } | | /* clock setting */ | if ((cpu_is_mx6q() || cpu_is_mx6dl()) && | ((ldb->mode == LDB_SEP0) || (ldb->mode == LDB_SEP1))) | ldb_clk[6] += lvds_channel; | else | ldb_clk[6] += setting->disp_id; | ldb->setting[setting_idx].ldb_di_clk = clk_get(&ldb->pdev->dev, | ldb_clk); | if (IS_ERR(ldb->setting[setting_idx].ldb_di_clk)) { | dev_err(&ldb->pdev->dev, "get ldb clk0 failed\n"); | iounmap(ldb->reg); | return PTR_ERR(ldb->setting[setting_idx].ldb_di_clk); | } | di_clk[3] += setting->dev_id; | di_clk[7] += setting->disp_id; | ldb->setting[setting_idx].di_clk = clk_get(&ldb->pdev->dev, | di_clk); | if (IS_ERR(ldb->setting[setting_idx].di_clk)) { | dev_err(&ldb->pdev->dev, "get di clk0 failed\n"); | iounmap(ldb->reg); | return PTR_ERR(ldb->setting[setting_idx].di_clk); | } | | dev_dbg(&ldb->pdev->dev, "ldb_clk to di clk: %s -> %s\n", ldb_clk, di_clk); | | /* fb notifier for clk setting */ | ldb->nb.notifier_call = ldb_fb_event, | ret = fb_register_client(&ldb->nb); | if (ret < 0) { | iounmap(ldb->reg); | return ret; | } | | ldb->inited = true; | | i2c_dev = ldb_i2c_client[lvds_channel]; | | } else { /* second time for separate mode */ | char di_clk[] = "ipu1_di0_clk"; | char ldb_clk[] = "ldb_di0_clk"; | | if ((ldb->mode == LDB_SPL_DI0) || | (ldb->mode == LDB_SPL_DI1) || | (ldb->mode == LDB_DUL_DI0) || | (ldb->mode == LDB_DUL_DI1) || | (ldb->mode == LDB_SIN0) || | (ldb->mode == LDB_SIN1)) { | dev_err(&ldb->pdev->dev, "for second ldb disp" | "ldb mode should in separate mode\n"); | return -EINVAL; | } | | setting_idx = 1; | if (cpu_is_mx6q() || cpu_is_mx6dl()) { | setting->dev_id = plat_data->sec_ipu_id; | setting->disp_id = plat_data->sec_disp_id; | } else { | setting->dev_id = plat_data->ipu_id; | setting->disp_id = !plat_data->disp_id; | } | if (setting->disp_id == ldb->setting[0].di) { | dev_err(&ldb->pdev->dev, "Err: for second ldb disp in" | "separate mode, DI should be different!\n"); | return -EINVAL; | } | | /* second output is LVDS0 or LVDS1 */ | if (ldb->mode == LDB_SEP0) | lvds_channel = 1; | else | lvds_channel = 0; | | reg = readl(ldb->control_reg); | if ((lvds_channel == 0) && (setting->disp_id == 0)) | reg |= LDB_CH0_MODE_EN_TO_DI0; | else if ((lvds_channel == 0) && (setting->disp_id == 1)) | reg |= LDB_CH0_MODE_EN_TO_DI1; | else if ((lvds_channel == 1) && (setting->disp_id == 0)) | reg |= LDB_CH1_MODE_EN_TO_DI0; | else | reg |= LDB_CH1_MODE_EN_TO_DI1; | ch_mask = lvds_channel ? LDB_CH1_MODE_MASK : | LDB_CH0_MODE_MASK; | ch_val = reg & ch_mask; | | if (bits_per_pixel(setting->if_fmt) == 24) { | if (lvds_channel == 0) | reg |= LDB_DATA_WIDTH_CH0_24; | else | reg |= LDB_DATA_WIDTH_CH1_24; | } else { | if (lvds_channel == 0) | reg |= LDB_DATA_WIDTH_CH0_18; | else | reg |= LDB_DATA_WIDTH_CH1_18; | } | writel(reg, ldb->control_reg); | | /* clock setting */ | if (cpu_is_mx6q() || cpu_is_mx6dl()) | ldb_clk[6] += lvds_channel; | else | ldb_clk[6] += setting->disp_id; | ldb->setting[setting_idx].ldb_di_clk = clk_get(&ldb->pdev->dev, | ldb_clk); | if (IS_ERR(ldb->setting[setting_idx].ldb_di_clk)) { | dev_err(&ldb->pdev->dev, "get ldb clk1 failed\n"); | return PTR_ERR(ldb->setting[setting_idx].ldb_di_clk); | } | di_clk[3] += setting->dev_id; | di_clk[7] += setting->disp_id; | ldb->setting[setting_idx].di_clk = clk_get(&ldb->pdev->dev, | di_clk); | if (IS_ERR(ldb->setting[setting_idx].di_clk)) { | dev_err(&ldb->pdev->dev, "get di clk1 failed\n"); | return PTR_ERR(ldb->setting[setting_idx].di_clk); | } | | i2c_dev = ldb_i2c_client[lvds_channel]; | | dev_dbg(&ldb->pdev->dev, "ldb_clk to di clk: %s -> %s\n", ldb_clk, di_clk); | } | | if (i2c_dev) | mxc_dispdrv_setdev(ldb->disp_ldb, &i2c_dev->dev); | else | mxc_dispdrv_setdev(ldb->disp_ldb, NULL); | | ldb->setting[setting_idx].ch_mask = ch_mask; | ldb->setting[setting_idx].ch_val = ch_val; | | if (cpu_is_mx6q() || cpu_is_mx6dl()) | ldb_ipu_ldb_route(setting->dev_id, setting->disp_id, ldb); -----------------+ | | /* | | * ldb_di0_clk -> ipux_di0_clk | | * ldb_di1_clk -> ipux_di1_clk | | */ | | clk_set_parent(ldb->setting[setting_idx].di_clk, | | ldb->setting[setting_idx].ldb_di_clk); | | | | /* must use spec video mode defined by driver */ | | ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str, | | ldb_modedb, ldb_modedb_sz, NULL, setting->default_bpp); | | if (ret != 1) | | fb_videomode_to_var(&setting->fbi->var, &ldb_modedb[0]); | | | | INIT_LIST_HEAD(&setting->fbi->modelist); | | for (i = 0; i < ldb_modedb_sz; i++) { | | struct fb_videomode m; | | fb_var_to_videomode(&m, &setting->fbi->var); | | if (fb_mode_is_equal(&m, &ldb_modedb[i])) { | | fb_add_videomode(&ldb_modedb[i], | | &setting->fbi->modelist); | | break; | | } | | } | | | | /* get screen size in edid */ | | if (i2c_dev) { | | ret = mxc_ldb_edidread(i2c_dev); | | if (ret > 0) { | | fb_edid_to_monspecs(&g_edid[lvds_channel][0], | | &setting->fbi->monspecs); | | /* centimeter to millimeter */ | | setting->fbi->var.width = | | setting->fbi->monspecs.max_x * 10; | | setting->fbi->var.height = | | setting->fbi->monspecs.max_y * 10; | | } else { | | /* ignore i2c access failure */ | | ret = 0; | | } | | } | | | | /* save current ldb setting for fb notifier */ | | ldb->setting[setting_idx].active = true; | | ldb->setting[setting_idx].ipu = setting->dev_id; | | ldb->setting[setting_idx].di = setting->disp_id; | | return ret; | | } | | | | static int ldb_disp_setup(struct mxc_dispdrv_handle *disp, struct fb_info *fbi) <----+ | { | uint32_t reg, val; | uint32_t pixel_clk, rounded_pixel_clk; | struct clk *ldb_clk_parent; | struct ldb_data *ldb = mxc_dispdrv_getdata(disp); | int setting_idx, di; | | setting_idx = find_ldb_setting(ldb, fbi); | if (setting_idx < 0) | return setting_idx; | | di = ldb->setting[setting_idx].di; | | /* restore channel mode setting */ | val = readl(ldb->control_reg); | val |= ldb->setting[setting_idx].ch_val; | writel(val, ldb->control_reg); | dev_dbg(&ldb->pdev->dev, "LDB setup, control reg:0x%x\n", | readl(ldb->control_reg)); | //設置vsync信號的極性 /* vsync setup */ | reg = readl(ldb->control_reg); | if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) { | if (di == 0) | reg = (reg & ~LDB_DI0_VS_POL_MASK) | | LDB_DI0_VS_POL_ACT_HIGH; | else | reg = (reg & ~LDB_DI1_VS_POL_MASK) | | LDB_DI1_VS_POL_ACT_HIGH; | } else { | if (di == 0) | reg = (reg & ~LDB_DI0_VS_POL_MASK) | | LDB_DI0_VS_POL_ACT_LOW; | else | reg = (reg & ~LDB_DI1_VS_POL_MASK) | | LDB_DI1_VS_POL_ACT_LOW; | } | writel(reg, ldb->control_reg); | | /* clk setup */ | //時鐘關閉 | if (ldb->setting[setting_idx].clk_en) | clk_disable(ldb->setting[setting_idx].ldb_di_clk); | pixel_clk = (PICOS2KHZ(fbi->var.pixclock)) * 1000UL; | ldb_clk_parent = clk_get_parent(ldb->setting[setting_idx].ldb_di_clk); | if ((ldb->mode == LDB_SPL_DI0) || (ldb->mode == LDB_SPL_DI1)) | clk_set_rate(ldb_clk_parent, pixel_clk * 7 / 2); | else | clk_set_rate(ldb_clk_parent, pixel_clk * 7); | rounded_pixel_clk = clk_round_rate(ldb->setting[setting_idx].ldb_di_clk, | pixel_clk); | clk_set_rate(ldb->setting[setting_idx].ldb_di_clk, rounded_pixel_clk); | //時鐘使能 | clk_enable(ldb->setting[setting_idx].ldb_di_clk); | if (!ldb->setting[setting_idx].clk_en) | ldb->setting[setting_idx].clk_en = true; | | return 0; | } | | #define LVDS_MUX_CTL_WIDTH 2 | #define LVDS_MUX_CTL_MASK 3 | #define LVDS0_MUX_CTL_OFFS 6 | #define LVDS1_MUX_CTL_OFFS 8 | #define LVDS0_MUX_CTL_MASK (LVDS_MUX_CTL_MASK << 6) | #define LVDS1_MUX_CTL_MASK (LVDS_MUX_CTL_MASK << 8) | #define ROUTE_IPU_DI(ipu, di) (((ipu << 1) | di) & LVDS_MUX_CTL_MASK) | static int ldb_ipu_ldb_route(int ipu, int di, struct ldb_data *ldb) <-----------+ { uint32_t reg; int channel; int shift; int mode = ldb->mode; //IOMUXC_GPR3 reg = readl(ldb->gpr3_reg); if (mode < LDB_SIN0) { reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK); reg |= (ROUTE_IPU_DI(ipu, di) << LVDS0_MUX_CTL_OFFS) | (ROUTE_IPU_DI(ipu, di) << LVDS1_MUX_CTL_OFFS); dev_dbg(&ldb->pdev->dev, "Dual/Split mode both channels route to IPU%d-DI%d\n", ipu, di); } else if ((mode == LDB_SIN0) || (mode == LDB_SIN1)) { reg &= ~(LVDS0_MUX_CTL_MASK | LVDS1_MUX_CTL_MASK); //選擇lvds源 channel = mode - LDB_SIN0; shift = LVDS0_MUX_CTL_OFFS + channel * LVDS_MUX_CTL_WIDTH; reg |= ROUTE_IPU_DI(ipu, di) << shift; dev_dbg(&ldb->pdev->dev, "Single mode channel %d route to IPU%d-DI%d\n", channel, ipu, di); } else { static bool first = true; if (first) { if (mode == LDB_SEP0) { reg &= ~LVDS0_MUX_CTL_MASK; channel = 0; } else { reg &= ~LVDS1_MUX_CTL_MASK; channel = 1; } first = false; } else { if (mode == LDB_SEP0) { reg &= ~LVDS1_MUX_CTL_MASK; channel = 1; } else { reg &= ~LVDS0_MUX_CTL_MASK; channel = 0; } } shift = LVDS0_MUX_CTL_OFFS + channel * LVDS_MUX_CTL_WIDTH; reg |= ROUTE_IPU_DI(ipu, di) << shift; dev_dbg(&ldb->pdev->dev, "Separate mode channel %d route to IPU%d-DI%d\n", channel, ipu, di); } writel(reg, ldb->gpr3_reg); return 0; }
Tony Liuui
2016-8-31, Shenzhenthis