如果有问题,请加QQ群 891339868 进行交流
最近一段时间在荔枝派zero上调试simplefb进行显示视频和图片,经过一段时间的研究,基本上搞清楚了simplefb参数的设置、uboot和kernel之间关于参数的传递流程,在这里记录一下,以备查阅。
simplefb使能和参数设置的流程:
1、在uboot中设置参数:
下面具体看一下在uboot中simplefb参数的设置,如下图所示:
具体的设置路径为:ARM architecture ---> Enable graphical uboot console on HDMI, LCD or VGA ---> LCD panel timing details
2、内核设备树中设置simplefb的相关节点:
具体的设置方式如下图所示:
这里面需要注意几点:一是节点名称,必须是framebuffer;二是compatible字段必须包含allwinner,simple-framebuffer;三是allwinner,pipeline字段对于全志的V3S等内部的显示模块de2的平台,必须是de0-lcd0。具体原因下面再说。我看到网上有人说,必须simplefb的设备树节点必须要放在chosen节点下面,其实不需要。
3、uboot读取内核设备树后,找到和simplefb相关的节点,根据预先设置的参数信息进行修改。
具体的修改流程需要对uboot的源码进行解析,首先看driver/video/sunxi_display.c中的void *video_hw_init(void)函数,这个函数是对参数的设置:
void *video_hw_init(void)
{
static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
const struct ctfb_res_modes *mode;
struct ctfb_res_modes custom;
const char *options;
#ifdef CONFIG_VIDEO_HDMI
int ret, hpd, hpd_delay, edid;
#endif
int i, overscan_offset, overscan_x, overscan_y;
unsigned int fb_dma_addr;
char mon[16];
char *lcd_mode = CONFIG_VIDEO_LCD_MODE;
memset(&sunxi_display, 0, sizeof(struct sunxi_display));
video_get_ctfb_res_modes(RES_MODE_1024x768, 24, &mode,
&sunxi_display.depth, &options);
#ifdef CONFIG_VIDEO_HDMI
hpd = video_get_option_int(options, "hpd", 1);
hpd_delay = video_get_option_int(options, "hpd_delay", 500);
edid = video_get_option_int(options, "edid", 1);
#endif
overscan_x = video_get_option_int(options, "overscan_x", -1);
overscan_y = video_get_option_int(options, "overscan_y", -1);
sunxi_display.monitor = sunxi_get_default_mon(true);
video_get_option_string(options, "monitor", mon, sizeof(mon),
sunxi_get_mon_desc(sunxi_display.monitor));
for (i = 0; i <= SUNXI_MONITOR_LAST; i++) {
if (strcmp(mon, sunxi_get_mon_desc(i)) == 0) {
sunxi_display.monitor = i;
break;
}
}
if (i > SUNXI_MONITOR_LAST)
printf("Unknown monitor: '%s', falling back to '%s'\n",
mon, sunxi_get_mon_desc(sunxi_display.monitor));
#ifdef CONFIG_VIDEO_HDMI
/* If HDMI/DVI is selected do HPD & EDID, and handle fallback */
if (sunxi_display.monitor == sunxi_monitor_dvi ||
sunxi_display.monitor == sunxi_monitor_hdmi) {
/* Always call hdp_detect, as it also enables clocks, etc. */
ret = sunxi_hdmi_hpd_detect(hpd_delay);
if (ret) {
printf("HDMI connected: ");
if (edid && sunxi_hdmi_edid_get_mode(&custom) == 0)
mode = &custom;
} else if (hpd) {
sunxi_hdmi_shutdown();
sunxi_display.monitor = sunxi_get_default_mon(false);
} /* else continue with hdmi/dvi without a cable connected */
}
#endif
switch (sunxi_display.monitor) {
case sunxi_monitor_none:
return NULL;
case sunxi_monitor_dvi:
case sunxi_monitor_hdmi:
if (!sunxi_has_hdmi()) {
printf("HDMI/DVI not supported on this board\n");
sunxi_display.monitor = sunxi_monitor_none;
return NULL;
}
break;
case sunxi_monitor_lcd:
if (!sunxi_has_lcd()) {
printf("LCD not supported on this board\n");
sunxi_display.monitor = sunxi_monitor_none;
return NULL;
}
sunxi_display.depth = video_get_params(&custom, lcd_mode);
mode = &custom;
break;
case sunxi_monitor_vga:
if (!sunxi_has_vga()) {
printf("VGA not supported on this board\n");
sunxi_display.monitor = sunxi_monitor_none;
return NULL;
}
sunxi_display.depth = 18;
break;
case sunxi_monitor_composite_pal:
case sunxi_monitor_composite_ntsc:
case sunxi_monitor_composite_pal_m:
case sunxi_monitor_composite_pal_nc:
if (!sunxi_has_composite()) {
printf("Composite video not supported on this board\n");
sunxi_display.monitor = sunxi_monitor_none;
return NULL;
}
if (sunxi_display.monitor == sunxi_monitor_composite_pal ||
sunxi_display.monitor == sunxi_monitor_composite_pal_nc)
mode = &composite_video_modes[0];
else
mode = &composite_video_modes[1];
sunxi_display.depth = 24;
break;
}
/* Yes these defaults are quite high, overscan on composite sucks... */
if (overscan_x == -1)
overscan_x = sunxi_is_composite() ? 32 : 0;
if (overscan_y == -1)
overscan_y = sunxi_is_composite() ? 20 : 0;
sunxi_display.fb_size =
(mode->xres * mode->yres * 4 + 0xfff) & ~0xfff;
overscan_offset = (overscan_y * mode->xres + overscan_x) * 4;
/* We want to keep the fb_base for simplefb page aligned, where as
* the sunxi dma engines will happily accept an unaligned address. */
if (overscan_offset)
sunxi_display.fb_size += 0x1000;
if (sunxi_display.fb_size > CONFIG_SUNXI_MAX_FB_SIZE) {
printf("Error need %dkB for fb, but only %dkB is reserved\n",
sunxi_display.fb_size >> 10,
CONFIG_SUNXI_MAX_FB_SIZE >> 10);
return NULL;
}
printf("Setting up a %dx%d%s %s console (overscan %dx%d)\n",
mode->xres, mode->yres,
(mode->vmode == FB_VMODE_INTERLACED) ? "i" : "",
sunxi_get_mon_desc(sunxi_display.monitor),
overscan_x, overscan_y);
gd->fb_base = gd->bd->bi_dram[0].start +
gd->bd->bi_dram[0].size - sunxi_display.fb_size;
sunxi_engines_init();
fb_dma_addr = gd->fb_base - CONFIG_SYS_SDRAM_BASE;
sunxi_display.fb_addr = gd->fb_base;
if (overscan_offset) {
fb_dma_addr += 0x1000 - (overscan_offset & 0xfff);
sunxi_display.fb_addr += (overscan_offset + 0xfff) & ~0xfff;
memset((void *)gd->fb_base, 0, sunxi_display.fb_size);
flush_cache(gd->fb_base, sunxi_display.fb_size);
}
sunxi_mode_set(mode, fb_dma_addr);
/*
* These are the only members of this structure that are used. All the
* others are driver specific. The pitch is stored in plnSizeX.
*/
graphic_device->frameAdrs = sunxi_display.fb_addr;
graphic_device->gdfIndex = GDF_32BIT_X888RGB;
graphic_device->gdfBytesPP = 4;
graphic_device->winSizeX = mode->xres - 2 * overscan_x;
graphic_device->winSizeY = mode->yres - 2 * overscan_y;
graphic_device->plnSizeX = mode->xres * graphic_device->gdfBytesPP;
return graphic_device;
}
这个函数设置了做了很多工作,可是对于咱们设置的lcd参数,只有video_get_params(&custom, lcd_mode)这个函数最关键,这个函数就是去读取在上面第一步中配置的lcd的时序的接口,这个函数的具体路径是driver/video/videomodes.c,进入这个函数,可以详细看一下:
int video_get_params (struct ctfb_res_modes *pPar, char *penv)
{
char *p, *s, *val_s;
int i = 0;
int bpp;
int mode;
/* first search for the environment containing the real param string */
s = penv;
if ((p = getenv (s)) != NULL)
s = p;
/*
* in case of the bootargs line, we have to start
* after "video=ctfb:"
*/
i = video_search_param (s, "video=ctfb:");
if (i >= 0) {
s += i;
s += strlen ("video=ctfb:");
}
/* search for mode as a default value */
p = s;
mode = 0; /* default */
while ((i = video_get_param_len (p, ',')) != 0) {
GET_OPTION ("mode:", mode)
p += i;
if (*p != 0)
p++; /* skip ',' */
}
if (mode >= RES_MODES_COUNT)
mode = 0;
*pPar = res_mode_init[mode]; /* copy default values */
bpp = 24 - ((mode % 3) * 8);
p = s; /* restart */
while ((i = video_get_param_len (p, ',')) != 0) {
GET_OPTION ("x:", pPar->xres)
GET_OPTION ("y:", pPar->yres)
GET_OPTION ("refresh:", pPar->refresh)
GET_OPTION ("le:", pPar->left_margin)
GET_OPTION ("ri:", pPar->right_margin)
GET_OPTION ("up:", pPar->upper_margin)
GET_OPTION ("lo:", pPar->lower_margin)
GET_OPTION ("hs:", pPar->hsync_len)
GET_OPTION ("vs:", pPar->vsync_len)
GET_OPTION ("sync:", pPar->sync)
GET_OPTION ("vmode:", pPar->vmode)
GET_OPTION ("pclk:", pPar->pixclock)
GET_OPTION ("pclk_khz:", pPar->pixclock_khz)
GET_OPTION ("depth:", bpp)
p += i;
if (*p != 0)
p++; /* skip ',' */
}
return bpp;
}
从最后几行的while循环中,可以看到分别去解析在uboot中设置的参数的各个字段,病保存到先关的数据结构中。
参数初始化后,再回到原来的sunxi_display.c中,看一下这个文件中的sunxi_simplefb_setup(void *blob)这个函数:
/*
* Simplefb support.
*/
#if defined(CONFIG_OF_BOARD_SETUP) && defined(CONFIG_VIDEO_DT_SIMPLEFB)
int sunxi_simplefb_setup(void *blob)
{
static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
int offset, ret;
u64 start, size;
const char *pipeline = NULL;
#ifdef CONFIG_MACH_SUN4I
#define PIPELINE_PREFIX "de_fe0-de_be0-"
#elif defined CONFIG_SUNXI_DE2
#define PIPELINE_PREFIX "de0-"
#else
#define PIPELINE_PREFIX "de_be0-"
#endif
switch (sunxi_display.monitor) {
case sunxi_monitor_none:
return 0;
case sunxi_monitor_dvi:
case sunxi_monitor_hdmi:
pipeline = PIPELINE_PREFIX "lcd0-hdmi";
break;
case sunxi_monitor_lcd:
pipeline = PIPELINE_PREFIX "lcd0";
break;
case sunxi_monitor_vga:
#ifdef CONFIG_VIDEO_VGA
pipeline = PIPELINE_PREFIX "lcd0-tve0";
#elif defined CONFIG_VIDEO_VGA_VIA_LCD
pipeline = PIPELINE_PREFIX "lcd0";
#endif
break;
case sunxi_monitor_composite_pal:
case sunxi_monitor_composite_ntsc:
case sunxi_monitor_composite_pal_m:
case sunxi_monitor_composite_pal_nc:
pipeline = PIPELINE_PREFIX "de_be0-lcd0-tve0";
break;
}
/* Find a prefilled simpefb node, matching out pipeline config */
offset = fdt_node_offset_by_compatible(blob, -1,
"allwinner,simple-framebuffer");
while (offset >= 0) {
ret = fdt_stringlist_search(blob, offset, "allwinner,pipeline",
pipeline);
if (ret == 0)
break;
offset = fdt_node_offset_by_compatible(blob, offset,
"allwinner,simple-framebuffer");
}
if (offset < 0) {
eprintf("Cannot setup simplefb: node not found\n");
return 0; /* Keep older kernels working */
}
/*
* Do not report the framebuffer as free RAM to the OS, note we cannot
* use fdt_add_mem_rsv() here, because then it is still seen as RAM,
* and e.g. Linux refuses to iomap RAM on ARM, see:
* linux/arch/arm/mm/ioremap.c around line 301.
*/
start = gd->bd->bi_dram[0].start;
size = gd->bd->bi_dram[0].size - sunxi_display.fb_size;
ret = fdt_fixup_memory_banks(blob, &start, &size, 1);
if (ret) {
eprintf("Cannot setup simplefb: Error reserving memory\n");
return ret;
}
ret = fdt_setup_simplefb_node(blob, offset, sunxi_display.fb_addr,
graphic_device->winSizeX, graphic_device->winSizeY,
graphic_device->plnSizeX, "x8r8g8b8");
if (ret)
eprintf("Cannot setup simplefb: Error setting properties\n");
return ret;
}
#endif /* CONFIG_OF_BOARD_SETUP && CONFIG_VIDEO_DT_SIMPLEFB */
这个函数的前半部分就是在匹配内核设备树中simplefb节点中相关的字段,从代码中可以很清晰的看到,对于sun8i的v3s平台,pipeline字段是de0-lcd0,compatible字段是allwinner,simple-framebuffer,这就是上面为什么说必须是这两个字符串。这个函数的后半部分主要是对内核设备树的修改,增加simplefb的显示的长、宽、高等参数的设置,进入到fdt_setup_simplefb_node()这个函数,可以看到具体的设置流程:
/**
* fdt_setup_simplefb_node - Fill and enable a simplefb node
*
* @fdt: ptr to device tree
* @node: offset of the simplefb node
* @base_address: framebuffer base address
* @width: width in pixels
* @height: height in pixels
* @stride: bytes per line
* @format: pixel format string
*
* Convenience function to fill and enable a simplefb node.
*/
int fdt_setup_simplefb_node(void *fdt, int node, u64 base_address, u32 width,
u32 height, u32 stride, const char *format)
{
char name[32];
fdt32_t cells[4];
int i, addrc, sizec, ret;
of_bus_default_count_cells(fdt, fdt_parent_offset(fdt, node),
&addrc, &sizec);
i = 0;
if (addrc == 2)
cells[i++] = cpu_to_fdt32(base_address >> 32);
cells[i++] = cpu_to_fdt32(base_address);
if (sizec == 2)
cells[i++] = 0;
cells[i++] = cpu_to_fdt32(height * stride);
ret = fdt_setprop(fdt, node, "reg", cells, sizeof(cells[0]) * i);
if (ret < 0)
return ret;
snprintf(name, sizeof(name), "framebuffer@%" PRIx64, base_address);
ret = fdt_set_name(fdt, node, name);
if (ret < 0)
return ret;
ret = fdt_setprop_u32(fdt, node, "width", width);
if (ret < 0)
return ret;
ret = fdt_setprop_u32(fdt, node, "height", height);
if (ret < 0)
return ret;
ret = fdt_setprop_u32(fdt, node, "stride", stride);
if (ret < 0)
return ret;
ret = fdt_setprop_string(fdt, node, "format", format);
if (ret < 0)
return ret;
ret = fdt_setprop_string(fdt, node, "status", "okay");
if (ret < 0)
return ret;
return 0;
}
有一点儿需要关注一下,就是在最后设置状态时,默认的是"okay",这就是为啥在设备树中设置的simplefb的状态是不是使能,最终都是使能的状态。至此,在uboot中设置lcd的参数,uboot对内核的设备树进行修改,将参数传递设备树的流程就完成了,下一步等到内核启动时,就可以对uboot修改后的设备树进行解析并启动相关的驱动。
好了,今天就记录到此。