新路程------英飞凌imx6的lvds驱动

时间:2022-10-11 23:37:19

  最近拿到一块开发版,打算在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;
		else
			reg |= 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;
		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;

		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;
			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*/
			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;
	} //在此之前都是设置一些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_screeninfo
	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);  //把选中的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

所有现在的架构就是和以前不一样了