前言
最近全志推出了D1处理器,搭载平头哥玄铁906 RISC-V的应用处理器。于是准备入手玩一下,买了芒果派做了基于D1s的麻雀板,D1s是D1内封64M DDR的版本,去掉了hdmi输出,号称点屏强芯,点个屏来玩玩。
先晒下小巧精致的麻雀:
点屏
手上正好有块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屏一般就是配一下几个参数的事,这些参数屏手册一般都会给出来,例如我的屏参手册
从这里可以直接对应到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
查看显示信息
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>;
查看显示信息
根据实际帧率计算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
这里的时钟具体含义与dclk的关系暂时不得而知,可以先留意一下,后面再来对应。
4.通过修改寄存器尝试解决问题
既然猜到了dclk有问题,那么可以去查下寄存器手册找到该值的地址,确定下问题。
最终查到的dclk寄存器描述如下:
dclk寄存器基地址:
dclk地址为:0x05461044
用devmem工具读一下寄存器的值 devmem 0x05461044
根据寄存器的描述: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"
找到文件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。这个函数末尾已经加了打印,当计算值与实际设置值不一致时就会打印,查看下启动打印有没有:
可以看到上面设置没有生效,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");
查看打印:
可以得知设置pll_rate这步不生效,那么解决问题就有两个路线了,
- 继续追查clk_set_rate(lcdp->clk_parent, pll_rate)为何不生效,至于具体原因那就要去查手册pll_rate相关的属性注意项了
- 保持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
搜索这个函数,找到源码:
可以看到很多结果,哪个才是我们真正使用的源文件呢,这里有个小技巧,通过.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修改完毕后编译测试显示正常,来看下显示调试信息
可以看到实际帧率与设置帧率完全一致了。
Uboot的修改也是同样的道理,UBoot显示驱动代码路径在D1\lichee\brandy-2.0\u-boot-2018\drivers\video\sunxi\disp2\disp\de\
完成
最后晒一下屏幕显示结果