最近拿到一块开发版,打算在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
所有现在的架构就是和以前不一样了