本文主要是介绍新路程------英飞凌imx6的lvds驱动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
最近拿到一块开发版,打算在lvds上做些小修改,之前也接触过一点驱动,但是现在的驱动框架看起来和之前的有点差异。
关于lcd的参数信息请参考这篇文章 http://blog.csdn.net/longxiaowu/article/details/24319933
lvds的驱动在framebuffer驱动之下,也就是上层应用只知道有个framebuffer设备也就是dev/fb,而至于下面的显示输出用vga也好hdmi也好还是lvds也好是不关心的。
lvds驱动干的事也挺简单,就是把fb_info这个机构体填充好而已,但是现在并没有在驱动中直接搞一个fb_info的结构体而是在framebuffer驱动中已经申请好了,ldb.c中拿过来
填充一下而已。
代码如下
static int ldb_disp_init(struct mxc_dispdrv_handle *disp,struct mxc_dispdrv_setting *setting) //这个setting中有个fb_info结构体
{int ret = 0, i;struct ldb_data *ldb = mxc_dispdrv_getdata(disp); //lvds自身的结构体struct fsl_mxc_ldb_platform_data *plat_data = ldb->pdev->dev.platform_data;struct resource *res;uint32_t base_addr;uint32_t reg, setting_idx;uint32_t ch_mask = 0, ch_val = 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";int lvds_channel = 0;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);/* refrence resistor select */reg &= ~LDB_BGREF_RMODE_MASK;if (plat_data->ext_ref)reg |= LDB_BGREF_RMODE_EXT;elsereg |= LDB_BGREF_RMODE_INT;/* 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);if (bits_per_pixel(setting->if_fmt) == 24)reg |= LDB_DATA_WIDTH_CH0_24 | LDB_DATA_WIDTH_CH1_24;elsereg |= LDB_DATA_WIDTH_CH0_18 | LDB_DATA_WIDTH_CH1_18;if (g_ldb_mode)ldb->mode = g_ldb_mode;elseldb->mode = plat_data->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;}} 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;}}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;elsereg |= 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;elsereg |= LDB_CH1_MODE_EN_TO_DI1;ch_mask = LDB_CH1_MODE_MASK;ch_val = reg & LDB_CH1_MODE_MASK;} else { /* separate mode*/setting->disp_id = plat_data->disp_id;/* first output is LVDS0 or LVDS1 */if (ldb->mode == LDB_SEP0)lvds_channel = 0;elselvds_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;elsereg |= 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;elsereg &= ~LDB_DATA_WIDTH_CH0_24;} else {if (lvds_channel == 0)reg &= ~LDB_DATA_WIDTH_CH1_18;elsereg &= ~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;elseldb_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;} //在此之前都是设置一些register的参数而已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); //填充fb_videomode,填充fb_var_screeninfoif (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); //把选中的mode加入到list中去break;}}/* 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;
}
到此结束,一个fb_fix_screeninfo没有看到在哪里被赋值,还有就是没有看到framebuffer_register
其实framebuffer_register是在mxc_ipuv3_fb.c中
fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); //初始化fb_info结构体, 其中fb->var.active为FB_ACTIVATE_NOW
ret = mxcfb_option_setup(pdev, fbi); //从cmdline获取fb设置,如果没有,就默认使用board中tek_fb_data里的值。
fb_get_options //拿之前video=mxcfb0获取的值和mxcfbx做比较,如果相匹配,然后在解析对应后面的参数。x是当前对应的fb number号,所以这样就会一一对应。如果后面带:off字样,表示不使用此路通道。
ret = mxcfb_dispdrv_init(pdev, fbi);就是这里调用了ldb.c里的init
mxc_dispdrv_gethandle -> //根据传进来的disp_dev name在dispdrv_list中匹配获取对应的driver handle,这里是获取的是ldb的handle,它的注册是在ldb_probe()的mxc_dispdrv_register实现的,它将自己添加到了dispdrv_list。
entry->drv->init -> //mxc_dispdrv.c 调用对应driver的init函数,这里就是ldb driver对应的init了。
ldb_disp_init -> ldb.c
fb_find_mode //寻找最合适的LCD参数
mxcfb_register ->
register_framebuffer //注册fb
所有现在的架构就是和以前不一样了这篇关于新路程------英飞凌imx6的lvds驱动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!