前言

最近全志推出了D1处理器,搭载平头哥玄铁906 RISC-V的应用处理器。于是准备入手玩一下,买了芒果派做了基于D1s的麻雀板,D1s是D1内封64M DDR的版本,去掉了hdmi输出,号称点屏强芯,点个屏来玩玩。

先晒下小巧精致的麻雀:

全志 iot 芯片 全志d1芯片_嵌入式硬件

点屏

手上正好有块480*272的rgb屏,可以用这个小板子来点一下。想来rgb屏幕应该很好适配,dts里改几个参数就完事了,不过多次调整怎么也没法让画面正常输出,这个问题应该有不少人遇到。先直入主题说一下如何修复问题吧,后面在详细记录填坑过程。

快速上手体验

我修复的代码patch在这里 ,下载下来后分别到kernel uboot 里打上patch即可。
然后屏幕参数DTS里根据自己的屏参配置下,可以参考我的配置,主要是以下几个参数:

/* part 3 */
lcd_width = <95>; /* 随便写 */
lcd_height = <54>;/* 随便写 */
lcd_x = <480>;
lcd_y = <272>;
lcd_dclk_freq = <9>;
lcd_hbp = <40>;
lcd_ht = <525>;
lcd_hspw = <0>;
lcd_vbp = <8>;
lcd_vt = <288>;
lcd_vspw = <0>;

另外屏幕起来后加载到kernel启动会明显感觉到颜色发生了变化,在kernel 的DTS里找到dmic,并关闭

&dmic {
	pinctrl-names   = "default","sleep";
	pinctrl-0       = <&dmic_pins_a>;
	pinctrl-1       = <&dmic_pins_b>;
	status = "disable";
};

这个据网友反馈是屏引脚占用导致。

详细填坑过程记录

1.关闭uboot logo显示

为了方便调试我们先针对内核修改,把uboot点屏给他关了。
打开文件 D1\lichee\brandy-2.0\u-boot-2018\configs\sun20iw1p1_defconfig 搜索 CONFIG_DISP2_SUNXI 并注释。重新编译烧录就不会在显示uboot logo了

2.确定屏参

点RGB屏一般就是配一下几个参数的事,这些参数屏手册一般都会给出来,例如我的屏参手册

全志 iot 芯片 全志d1芯片_帧率_02


从这里可以直接对应到DTS里的几个配置项

lcd_x = <480>;
lcd_y = <272>;
lcd_dclk_freq = <9>;
lcd_hbp = <40>;
lcd_ht = <525>;

lcd_vbp = <8>;
lcd_vt = <288>;

还剩下lcd_hspw,lcd_vspw
hspw (HSYNC plus width)行同步脉宽 单位:像素时钟周期
vspw (VSYNC width)垂直同步脉宽 单位:显示一行的时间th
以水平方向为例说一下几个参数的关系:
ht = h显示区域像素 + hbp + hfb + hspw
525 = 480 + 40 + 5 + 0
由此可以确定hspw与vspw 两项的值为0,按经验来说 spw这项一般不需要额外关注。

3.查看LCD显示调试信息

但我照着手册填好后点屏画面总是糊的,调整一番参数多次尝试无果,确定参数应该是没有问题的,那只能翻手册查看调试信息排坑了。
关于D1/D1s芯片资料可以在这里获取https://whycan.com/t_6440.html 查看lcd调试指南,我们可以通过

cat /sys/class/disp/disp/attr/sys

查看显示信息

全志 iot 芯片 全志d1芯片_linux_03


DTS里配置的值算出来是60帧没有问题,分辨率也正常,但是实际跑的帧率为322,这差太多了,根据帧率计算公式:

lcd_dclk_freq * 1000000 * num_of_pixel_clk = lcd_ht * lcd_vt * fps

式子里

lcd_dclk_freq单位为Mhz

num_of_pixel_clk = 1 ,因为我们是并行RGB接口,一个时钟发送一个像素

将我们的DTS配置带入以上公式 fps确实应该是60,但实际fps为322,那么我们假设时钟可能不对,反推一下将322fps带入公式算dclk得到

525 * 288 * 322.5 = 48.762M

这个时候我们更改下DTS 按800 * 480的屏参配置

lcd_dclk_freq = <33>;
lcd_hbp = <46>;
lcd_ht = <1055>;
lcd_hspw = <0>;
lcd_vbp = <23>;
lcd_vt = <525>;
lcd_vspw = <0>;

查看显示信息

全志 iot 芯片 全志d1芯片_帧率_04


根据实际帧率计算dclk

1055 * 525 * 87.7 = 48.574M

由此可以大概能猜到,dclk确实是48M,而dts的对dclk的设置应该是不生效的。

查看下lcd相关的时钟

cat /sys/kernel/debug/clk/clk_summary
cat /sys/kernel/debug/clk/clk_summary | grep tcon

全志 iot 芯片 全志d1芯片_linux_05


这里的时钟具体含义与dclk的关系暂时不得而知,可以先留意一下,后面再来对应。

4.通过修改寄存器尝试解决问题

既然猜到了dclk有问题,那么可以去查下寄存器手册找到该值的地址,确定下问题。

最终查到的dclk寄存器描述如下:

全志 iot 芯片 全志d1芯片_帧率_06


dclk寄存器基地址:

全志 iot 芯片 全志d1芯片_全志 iot 芯片_07


dclk地址为:0x05461044

用devmem工具读一下寄存器的值 devmem 0x05461044

全志 iot 芯片 全志d1芯片_全志 iot 芯片_08


根据寄存器的描述:Tdclk = Tsclk/DCLKDIV。 Tsclk是个啥我们也不清楚,但我们可以用已知量先反推计算下:

Tsclk = Tdclk * DCLKDIV = 48 * 6 = 288 Mhz

看到这个数字你想到了什么,前面查看时钟时 tcon-lcd0 的值不就是这个吗。

现在知道了Tsclk = 288M ,要想让我们的dclk为9M,那就得改DCLKDIV为32。

把我们的设备烧录回480 * 272的DTS配置,写寄存器 devmem 0x05461044 32 0xF0000020 写完后立马看到屏幕显示正常了,确定是这个问题无疑。
DTS设置不生效就要去驱动源码里查问题了。

5.梳理屏幕显示驱动

DTS里可知 lcd_driver_name = “default_lcd”

我们可以通过搜索"default_lcd"来找屏驱动文件,找到了drivers/video/fbdev/sunxi/disp2/disp/lcd/default_panel.c,查看文件发现只有上下电时序,初始化序列相关,没有我们需要的,那直接按解析参数来搜索好了。

进入到内核目录,执行 grep -rn "lcd_dclk_freq"

全志 iot 芯片 全志d1芯片_嵌入式硬件_09


找到文件drivers/video/fbdev/sunxi/disp2/disp/de/disp_lcd.c

lcd 显示初始化使能主要有这个函数来完成:disp_lcd_fake_enable,梳理出其中主要关注的调用

disp_lcd_fake_enable(...)
{
...//省略若干代码
	lcd_clk_enable(lcd); //该函数通过调用 lcd_clk_config 来配置lcd相关时钟
	ret = cal_real_frame_period(lcd); //通过调用do_div(temp, lcd->timings.dclk_rate_set); 设置了 dclk_rate_set
...
}

先来看下 lcd_clk_config 函数,时钟配置的代码在这里:

lcd_clk_config(...)
{
...//省略若干代码
	disp_al_lcd_get_clk_info(lcd->hwdev_index, &clk_info,
				 &lcdp->panel_info);
	dclk_rate = lcdp->panel_info.lcd_dclk_freq * 1000000;	/* Mhz -> hz */
	if (lcdp->panel_info.lcd_if == LCD_IF_DSI) {
		lcd_rate = dclk_rate * clk_info.dsi_div;
		pll_rate = lcd_rate * clk_info.lcd_div;
	} else {
		lcd_rate = dclk_rate * clk_info.tcon_div;
		pll_rate = lcd_rate * clk_info.lcd_div;
	}
...
	dclk_rate_set = lcd_rate_set / clk_info.tcon_div;
	if ((pll_rate_set != pll_rate) || (lcd_rate_set != lcd_rate)
	    || (dclk_rate_set != dclk_rate)) {
		DE_WRN
		    ("disp %d, clk: pll(%ld),clk(%ld),dclk(%ld) dsi_rate(%ld)\n     clk real:pll(%ld),clk(%ld),dclk(%ld) dsi_rate(%ld)\n",
		     lcd->disp, pll_rate, lcd_rate, dclk_rate, dsi_rate,
		     pll_rate_set, lcd_rate_set, dclk_rate_set, dsi_rate_set);
	}

	return 0;
}

dclk_rate 就是我们DTS传入的9Mhz参数,通过该值计算得到两个时钟 lcd_rate 与 pll_rate ,根据代码可以得知
pll_rate = lcd_rate = dclk_rate * tcon_div;

pll_rate、 lcd_rate、 dclk_rate 这些都是我们根据DTS得到的值,实际设置值为 pll_rate_set、 lcd_rate_set、dclk_rate_set。这个函数末尾已经加了打印,当计算值与实际设置值不一致时就会打印,查看下启动打印有没有:

全志 iot 芯片 全志d1芯片_嵌入式硬件_10


可以看到上面设置没有生效,pll_rate实际是288M,加下打印

DE_WRN("lyrdebug start\n");
	if (lcdp->clk_parent) {
		DE_WRN("lyrdebug set pll_rate_set(%ld)\n",pll_rate);
		clk_set_rate(lcdp->clk_parent, pll_rate);
		pll_rate_set = clk_get_rate(lcdp->clk_parent);
		DE_WRN("lyrdebug get pll_rate_set(%ld)\n",pll_rate_set);
	}

	if (clk_info.lcd_div)
		lcd_rate_set = pll_rate_set / clk_info.lcd_div;
	else
		lcd_rate_set = pll_rate_set;
	DE_WRN("lyrdebug get lcd_rate_set-1-(%ld)\n",lcd_rate_set);
	clk_set_rate(lcdp->clk_tcon_lcd, lcd_rate_set);
	lcd_rate_set = clk_get_rate(lcdp->clk_tcon_lcd);
	DE_WRN("lyrdebug get lcd_rate_set-2-(%ld)\n",lcd_rate_set);
	DE_WRN("lyrdebug end\n");

查看打印:

全志 iot 芯片 全志d1芯片_寄存器_11


可以得知设置pll_rate这步不生效,那么解决问题就有两个路线了,

  1. 继续追查clk_set_rate(lcdp->clk_parent, pll_rate)为何不生效,至于具体原因那就要去查手册pll_rate相关的属性注意项了
  2. 保持pll_rate为288M,更改DCLK_DIV的值来使得最终给到dclk的值为实际值。

这里我们就懒得再去追查pll_rate设置不生效原因了,我们先假设pll_rate最小值为288M或者他只能设置为288M。

然后找到DCLK_DIV设置的地方,去修改。

通过上面代码分析可以知道pll_rate 与 dclk_rate 的关系:

pll_rate = lcd_rate = dclk_rate * tcon_div

那么这个tcon_div应当就是DCLK_DIV了

tcon_div的值来自函数disp_al_lcd_get_clk_info 搜索这个函数,找到源码:

全志 iot 芯片 全志d1芯片_全志 iot 芯片_12


可以看到很多结果,哪个才是我们真正使用的源文件呢,这里有个小技巧,通过.o文件来反推,找到了该函数位于文件:

drivers/video/fbdev/sunxi/disp2/disp/de/lowlevel_v2x/disp_al.c

打开文件找到函数disp_al_lcd_get_clk_info

...
static struct lcd_clk_info clk_tbl[] = {
	{LCD_IF_HV, 6, 1, 1, 0},
	{LCD_IF_CPU, 12, 1, 1, 0},
	{LCD_IF_LVDS, 7, 1, 1, 0},
#if defined(DSI_VERSION_40)
	{LCD_IF_DSI, 4, 1, 4, 150000000},
#else
	{LCD_IF_DSI, 4, 1, 4, 0},
#endif /*endif DSI_VERSION_40*/
	{LCD_IF_VDPO, 4, 1, 1, 0},
};
...
disp_al_lcd_get_clk_info(...)
{
...
	for (i = 0; i < sizeof(clk_tbl) / sizeof(struct lcd_clk_info); i++) {
		if (clk_tbl[i].lcd_if == panel->lcd_if) {
			tcon_div = clk_tbl[i].tcon_div;
			lcd_div = clk_tbl[i].lcd_div;
			dsi_div = clk_tbl[i].dsi_div;
			dsi_rate = clk_tbl[i].dsi_rate;
			find = 1;
			break;
		}
	}
...
}

从这里就知道了tcon_div值来自clk_tbl[]中,默认为6,这与我们之前寄存器查看得到值是对得上的。

6.动手修改

上面已经找到了tcon_div值设置的位置,就在这下面添加代码好了

if(panel->lcd_dclk_freq < 48 && panel->lcd_dclk_freq >= 3){
		tcon_div = 288/panel->lcd_dclk_freq;
	}

意思是当DTS中设置的dclk在3到48M之间时自动计算tcon_div的值,要满足tcon_div小于128。

kernel修改完毕后编译测试显示正常,来看下显示调试信息

全志 iot 芯片 全志d1芯片_linux_13


可以看到实际帧率与设置帧率完全一致了。

Uboot的修改也是同样的道理,UBoot显示驱动代码路径在D1\lichee\brandy-2.0\u-boot-2018\drivers\video\sunxi\disp2\disp\de\

完成

最后晒一下屏幕显示结果

全志 iot 芯片 全志d1芯片_全志 iot 芯片_14