對國產板子有陰影?這些軟硬體開源的ARM開發板可以學習Linux驅動開發
為了點亮一塊MIPI螢幕,我們除了要了解MIPI DSI的工作原理之外,大前提要了解整個MIPI DSI圖顯系統的組成,更需要清楚點亮一塊MIPI螢幕需要做哪些事情。
///插播一條:我自己在今年年初錄製了一套還比較系統的入門單片機教程,想要的同學找我拿就行了免費的,私信我就可以哦~點我頭像黑色字體加我地球呺也能領取哦。最近比較閒,帶做畢設,帶學生參加省級或以上比賽///
正文開始:
1. MIPI DSI圖顯系統組成
MIPI圖顯系統的硬體組成如下圖表示:
MIPI DSI圖顯系統組成
圖顯處理器通過DPI接口將像素數據傳輸到MIPI DSI Host,MIPI D-PHY作為顯示屏和DSI Host之間由物理媒介,將編碼後的像素數據發送到MIPI顯示屏。
對於MIPI DSI顯示屏而言,無需MIPI信號轉換的稱之為panel,內部有數據信號轉換橋片的稱之為bridge。
整個MIPI圖顯系統除了基本的像素數據信號外,為了使整個顯示系統能夠正常工作,還包含其他與顯示屏相關的控制信號,包括顯示屏內部IC配置、顯示屏背光配置、顯示屏的復位和上電配置。
對於點亮一塊MIPI螢幕而言,重中之重是要正確的配置顯示屏參數,配置方式主要有如下3種:
·I2C或SPI等總線配置
·顯示屏內部集成的MCU完成配置
·MIPI DSI DCS初始化序列
通過PWM來實現MIPI螢幕的背光控制,使用GPIO完成顯示屏的復位、上電的控制。
在設備樹中定義MIPI DSI圖顯系統的聯結關係。
以RK3399為例,其提供了兩路MIPI DSI通道,分別是dsi@ff960000和dsi1: dsi@ff968000,代表MIPI DSI host。
2050 dsi: dsi@ff960000 {
2051 compatible = "rockchip,rk3399-mipi-dsi";
2052 reg = ;
...
2083 dsi1: dsi@ff968000 {
2084 compatible = "rockchip,rk3399-mipi-dsi";
2085 reg = ;
...
RK3399晶片使用了Synopsy的DPHY。控制器與DPHY之間的關係如下圖所示:
MIPI DSI設備樹結點中有一個信息同MIPI顯示密切相關,那就是時鐘信息。可以看出MIPI DSI需要三路時鐘,分別是ref、pclk、phy_cfg。
2050 dsi: dsi@ff960000 {
2051 compatible = "rockchip,rk3399-mipi-dsi";
...
2054 clocks = , ,
2055 ;
2056 clock-names = "ref", "pclk", "phy_cfg";
pclk是MIPI DSI host的APB時鐘,用於配置MIPI DSI host寄存器以及中斷等。ref和phy_cfg是MIPI DPHY所需時鐘。這兩路時鐘由MIPI DSI host提供。其中ref時鐘用於MIPI DPHY內部PLL產生主機側的串行發送時鐘。phy_cfg是在配置MIPI DPHY時使用。
4知識體系搭建
MIPI DSI同圖顯控制器vop之間在邏輯層面上的聯結關係如下:
# MIPI DSI Host
dsi_in_vopb: endpoint@0 {
reg = ;
remote-endpoint = ;
};
dsi_in_vopl: endpoint@1 {
reg = ;
remote-endpoint = ;
};
# VOP
vopb_out_dsi: endpoint@1 {
reg = ;
remote-endpoint = ;
};
vopb_out_dsi1: endpoint@4 {
reg = ;
remote-endpoint = ;
};
「後文所涉及到的代碼部分,均基於DRM架構實現」
2.關鍵數據結構
請注意以下幾個關鍵的數據結構,後文的MIPI DSI初始化以及MIPI顯示系統初始化均以它們為核心進行展開,包括各數據結構的例化工作和創建各數據結構之間的聯結關係。
「struct mipi_dsi_host」
struct mipi_dsi_host {
struct device *dev;
const struct mipi_dsi_host_ops *ops;
struct list_head list;
};
該數據結構由DRM MIPI DSI提供,用以描述MIPI DSI Host,包括Host的驅動設備、Host提供的功能函數(後文會介紹具體功能),Host鍊表的設備註冊、管理。
「struct mipi_dsi_device」
struct mipi_dsi_device {
struct mipi_dsi_host *host;
struct device dev;
char name[DSI_DEV_NAME_SIZE];
unsigned int channel;
unsigned int lanes;
enum mipi_dsi_pixel_format format;
unsigned long mode_flags;
};
該數據結構由DRM MIPI DSI提供,用以描述DSI設備信息,主要包括DSI設備的lane數量(最多4lane)、像素格式(由Host決定,如RGB888、RGB565等)。
「struct mipi_dsi_host_ops」
struct mipi_dsi_host_ops {
int (*attach)(struct mipi_dsi_host *host,
struct mipi_dsi_device *dsi);
int (*detach)(struct mipi_dsi_host *host,
struct mipi_dsi_device *dsi);
ssize_t (*transfer)(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg);
};
該數據結構由DRM MIPI DSI提供,用以描述DSI Host所能提供的功能函數,包括DSI Host和顯示屏之間創建聯結關係需要使用的attach、通過DSI Host配置顯示屏的transfer。
3. MIPI DSI軟體架構
基於DRM的圖顯系統中,MIPI DSI子系統主要由DSI CORE與PANEL CORE組成,二者內建連接關係後註冊到DRM CORE系統。
MIPI DSI軟體架構
在DRM架構下,提供了drm_mipi_dsi.c、drm_panel.c以及drm_bridge.c三個核心文件。用戶的MIPI DSI驅動以drm_mipi_dsi.c為核心進行代碼編寫,例如RK3399的MIPI驅動dw-mipi-dsi.c。各顯示屏廠商的驅動代碼基於後兩者進行編寫,例如較為廣泛使用的panel-simple.c。
4. MIPI圖顯系統初始化
從第一章內容來看,點亮MIPI DSI螢幕並能正常的顯示圖像,在DRM架構下需完成如下初始化工作:
.MIPI DSI Host初始化
.MIPI DSI D-PHY初始化
.MIPI DSI螢幕初始化
.構建MIPI DSI各模塊間的聯結關係
為了實現以上初始化工作,我們需要在設備樹DTS文件中約定各種初始化參數,包括各個模塊的功能參數以及各個模塊之間的聯結關係,在驅動代碼中配置各IP及外設硬體,並基於DRM架構註冊出各組件,從軟體層面勾勒出MIPI DSI的數據流路徑。
4.1 MIPI DSI初始化
RK3399使用數據結構struct dw_mipi_dsi描述MIPI DSI設備,該數據結構也是整個MIPI DSI圖顯系統的核心,囊括了系統中的各個組件。
struct dw_mipi_dsi {
struct drm_encoder encoder;
struct drm_connector connector;
struct drm_bridge *bridge;
struct device_node *client;
struct mipi_dsi_host dsi_host;
struct mipi_dphy dphy;
struct drm_panel *panel;
struct device *dev;
struct regmap *grf;
struct reset_control *rst;
struct regmap *regmap;
struct clk *pclk;
struct clk *h2p_clk;
int irq;
struct dw_mipi_dsi *master;
struct dw_mipi_dsi *slave;
struct mutex mutex;
bool prepared;
unsigned int id;
unsigned long mode_flags;
unsigned int lane_mbps; /* per lane */
u32 channel;
u32 lanes;
u32 format;
struct drm_display_mode mode;
const struct dw_mipi_dsi_plat_data *pdata;
};
「DRM架構相關」
基於DRM KMS架構的MIPI DSI圖顯系統也同樣遵循其設定的組件規則,MIPI Host屬於DRM encoder,D-PHY和PANEL部分屬於DRM connector。在實際使用過程種你可能發現這樣一個現象,就是電路板並沒有連接MIPI螢幕,但是DRM connector的連接狀態依然是connected,這是因為MIPI DSI無法真實的檢測到物理連接關係,通過軟體定義DRM connector和encoder之間的連接關係,當一切都初始化成功的時候,connector就處於connected狀態了。
struct drm_encoder encoder;
struct drm_connector connector;
struct drm_bridge *bridge;
struct drm_panel *panel;
「MIPI DSI主體」
這裡所說的主體主要包括MIPI DSI Host、D-PHY、bridge或pannel。不僅需要表示MIPI DSI圖顯系統的關鍵組成模塊,也需要定義彼此之間硬體與軟體層面的連接關係。
struct mipi_dsi_host dsi_host;
struct mipi_dphy dphy;
struct drm_bridge *bridge;
struct drm_panel *panel;
「MIPI DSI參數」
我們關心的MIPI DSI參數主要包括物理硬體定義、時鐘頻率、復位控制等。而物理硬體定義又包括lane數目、通道數、lane速率等。
unsigned long mode_flags;
unsigned int lane_mbps; /* per lane */
u32 channel;
u32 lanes;
u32 format;
「顯示參數」
MIPI DSI圖顯系統不像HDMI、VGA那樣可以通過EDID獲取到顯示參數,也同樣不支持熱插拔操作。其顯示參數如解析度均是在顯示屏初始化時註冊到drm_display_mode中,當我們將MIPI DSI註冊進DRM系統中的時候,直接解析drm_display_mode數據結構獲取顯示參數。
struct drm_display_mode mode;
const struct dw_mipi_dsi_plat_data *pdata;
介紹了MIPI DSI關鍵數據結構之後,接下來我們看看MIPI DSI的初始化流程是什麼樣的。
概況來講,MIPI DSI Host初始化包括兩部分內容:初始化MIPI DSI Host/D-PHY功能、構建MIPI DSI與D-PHY、顯示屏之間的聯結關係。關於Host/D-PHY的配置細節,因為每一家IP的實現不盡相同,故本篇文章不做具體分析。
初始化流程如下圖所示:
MIPI DSI初始化流程
「初始化流程解析」
同所有設備驅動一樣,在驅動代碼的開始處解析設備樹,這裡只需要解析MIPI DSI結點即可。SoC廠商會實現這部分設備樹的定義,我們只需要關心時鐘相關的選項即可。
MIPI DSI host和D-PHY之間的初始化存在天生的聯繫,可以基於通用phy代碼架構進行D-PHY初始化,也可以在Host代碼進行D-PHY初始化。
在整個的Host初始化流程中,非常關鍵的一步是注冊Host ops,其存在的意義是為panel或bridge提供Host控制的一種機制。例如panel通過.attach聯結Host,通過.transfer控制DSI Host發送螢幕初始化序列的DCS包。
基於DRM架構的MIPI DSI圖顯系統,必須注冊encoder和connector兩個組件,這屬於常規操作,同HDMI、VGA、LVDS等圖顯系統一樣。此處我們需要重點關注encoder的.enable,該函數會完成MIPI DSI Host、D-PHY的功能配置。如下圖所示:
Host與D-PHY功能初始化
以上所有Host、D-PHY相關初始化均成功之後,構建Host與panel(bridge)之間的聯結關係:
int drm_panel_attach(struct drm_panel *panel, struct drm_connector *connector)
{
if (panel->connector)
return -EBUSY;
panel->connector = connector;
panel->drm = connector->dev;
return 0;
}
4.2 PANEL初始化
本文只分析panel的初始化流程,對於bridge的初始化不做過多闡述。從原理上來講,二者區別不大。在嵌入式領域,使用最多的是panel類型的螢幕。基於DRM架構,有專門的panel驅動,如下所示:
rk@ubuntu:~/OK3399-linux-release/kernel$ ls drivers/gpu/drm/panel/
Makefile panel-samsung-ld9040.c panel-sharp-lq101r1sx01.c panel-simple.o
Kconfig panel-lg-lg4573.c panel-samsung-s6e8aa0.c panel-simple.c
對於大多數的MIPI DSI顯示屏,我們都可以基於panel-simple.c編寫代碼,在其中例化進我們的螢幕配置。當然,LCD、LVDS的panel驅動也在這個文件中,要按需編寫。
當我們拿到一塊MIPI DSI顯示屏之後,首先需要確定其硬體連接關係,查看是否需要通過GPIO控制上下電、確定螢幕參數配置方式、PWM背光調節等。
通過GPIO上下電的控制方式並不多見,若存在這種需求,則需要在設備樹中配置pinctrl子系統。
螢幕參數的配置方式比較靈活,在前面已經介紹過。
PWM背光調節功能是MIPI DSI螢幕的必備項,但這部分並不難,我們需要做的是掛載相應的PWM背光碟機動即可。
對於panel的初始化,其流程如下圖所示:
panel初始化流程
「初始化流程解析」初始化的第一步是解析MIPI DSI螢幕的參數,包括上電參數、背光控制、lane數量、圖顯時序參數等等。這個是需要我們自己在設備樹文件中補充定義的,根據自己手中的螢幕量身定製。
&dsi {
panel@0{
status = "okay";
compatible ="simple-panel-dsi";
reg = ;
backlight = ;
re-delay-ms = ;
...
dsi,flags =
MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_EOT_PACKET)>;
dsi,format = ;
dsi,lanes = ;
display-timings {
native-mode = ;
timing1: timing1 {
clock-frequency = ;
...
pixelclk-active = ;
};
};
};
};
若需要通過MIPI DSI DCS配置螢幕,則還需要定義init-sequence初始化序列,通過DCS配置螢幕時,需要註冊出panel prepare函數,當MIPI DSI Host使能時回調到它,然通過MIPI DSI host的.transfer完成DCS數據的發送。例如panel_simple_prepare()函數:
static int panel_simple_prepare(struct drm_panel *panel)
{
struct panel_simple *p = to_panel_simple(panel);
int err;
...
if (p->on_cmds) {
if (p->dsi)
err = panel_simple_dsi_send_cmds(p, p->on_cmds);
else if (p->cmd_type == CMD_TYPE_SPI)
err = panel_simple_spi_send_cmds(p, p->on_cmds);
if (err)
dev_err(p->dev, "failed to send on cmds\n");
}
...
}
通過panel_simple_dsi_send_cmds()調用DCS發送函數:
static int panel_simple_dsi_send_cmds(struct panel_simple *panel,
struct panel_cmds *cmds)
{
...
switch (cmd->dchdr.dtype) {
case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
case MIPI_DSI_GENERIC_LONG_WRITE:
err = mipi_dsi_generic_write(dsi, cmd->payload,
cmd->dchdr.dlen);
break;
case MIPI_DSI_DCS_SHORT_WRITE:
case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
case MIPI_DSI_DCS_LONG_WRITE:
err = mipi_dsi_dcs_write_buffer(dsi, cmd->payload,
cmd->dchdr.dlen);
break;
default:
return -EINVAL;
}
...
return 0;
}
最後調用到MIPI DSI Host的ops函數:
static ssize_t mipi_dsi_device_transfer(struct mipi_dsi_device *dsi,
struct mipi_dsi_msg *msg)
{
const struct mipi_dsi_host_ops *ops = dsi->host->ops;
if (!ops || !ops->transfer)
return -ENOSYS;
if (dsi->mode_flags & MIPI_DSI_MODE_LPM)
msg->flags |= MIPI_DSI_MSG_USE_LPM;
return ops->transfer(dsi->host, msg);
}
當以上具體的配置工作正常結束之後,更新panel-list鍊表以使得DRM架構下可以找到對應的panel組件。