新路程------英飛凌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;
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
所有現在的架構就是和以前不一樣了