i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、
【公众号】迅为电子
【粉丝群】258811263
第七十二章 内核配置屏幕驱动
屏幕驱动是非常重要的一部分,本章节我们将学习如何在上个章节的基础上配置屏幕驱动。
72.1修改屏幕驱动
i.MX8M Mini 处理器的 MIPI_DSI 最高分辨率可达 WQHD(1920x1080p60,24bpp),支持 1,2,3 或 4 个数据通道。 i.MX8MM 将 4 通道的 MIPI_DSI 通过 30pin 0.5mm 间距的 FPC 座引出(J9)。可连接迅为的 7 寸 mipi 显示屏。或者通过连接mipi转lvds转接板,然后再连接7寸lvds 屏,9.7寸lvds屏,10.1寸lvds屏。
NXP官方写好了mipi_dsi的节点,如下图所示:
/home/topeet/bsp_kernel_imx/bsp_kernel_imx/linux-imx/arch/arm64/boot/dts/freescale/fsl-imx8mm.dtsi文件的mipi_dsi节点,如下图所示:
由上图的compatible = "fsl,imx8mm-mipi-dsim"; 可以查找到匹配的驱动文件为/home/topeet/bsp_kernel_imx/bsp_kernel_imx/linux-imx/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c文件。修改此文件,如下图所示:
#include "sec_mipi_pll_1432x.h"
static const struct sec_mipi_dsim_plat_data imx8mm_mipi_dsim_plat_data = {
.version = 0x1060200,
.max_data_lanes = 4,
.max_data_rate = 1500000000ULL,
.dphy_pll = &pll_1432x,
.dphy_timing = dphy_timing_ln14lpp_v1p2,
.num_dphy_timing = ARRAY_SIZE(dphy_timing_ln14lpp_v1p2),
.dphy_timing_cmp = dphy_timing_default_cmp,
.mode_valid = NULL,
};
然后将网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\3.Linux系统移植\8.移植内核需要用到的文件\01 适配屏幕需要使用的文件”目录下的sec_mipi_pll_1432x.h文件拷贝到/home/topeet/bsp_kernel_imx/bsp_kernel_imx/linux-imx/drivers/gpu/drm/imx/目录下。
修改/home/topeet/imx8mm/linux/linux/linux-imx/drivers/gpu/drm/bridge/sec-dsim.c文件,修改如下内容:
添加如下内容:
#define MIPI_HFP_PKT_OVERHEAD 6
#define MIPI_HBP_PKT_OVERHEAD 6
#define MIPI_HSA_PKT_OVERHEAD 6
添加如下内容:
uint32_t pref_clk;
添加如下内容:
#if 1 //add by cym 20201127
static int sec_mipi_dsim_set_pref_rate(struct sec_mipi_dsim *dsim)
{
int ret;
uint32_t rate;
struct device *dev = dsim->dev;
const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
const struct sec_mipi_dsim_pll *dpll = pdata->dphy_pll;
const struct sec_mipi_dsim_range *fin_range = &dpll->fin;
ret = of_property_read_u32(dev->of_node, "pref-rate", &rate);
if (ret < 0) {
dev_dbg(dev, "no valid rate assigned for pref clock\n");
dsim->pref_clk = PHY_REF_CLK;
} else {
if (unlikely(rate < fin_range->min || rate > fin_range->max)) {
dev_warn(dev, "pref-rate get is invalid: %uKHz\n",
rate);
dsim->pref_clk = PHY_REF_CLK;
} else
dsim->pref_clk = rate;
}
set_rate:
ret = clk_set_rate(dsim->clk_pllref,
((unsigned long)dsim->pref_clk) * 1000);
if (ret) {
dev_err(dev, "failed to set pll ref clock rate\n");
return ret;
}
rate = clk_get_rate(dsim->clk_pllref) / 1000;
if (unlikely(!rate)) {
dev_err(dev, "failed to get pll ref clock rate\n");
return -EINVAL;
}
if (rate != dsim->pref_clk) {
if (unlikely(dsim->pref_clk == PHY_REF_CLK)) {
/* set default rate failed */
dev_err(dev, "no valid pll ref clock rate\n");
return -EINVAL;
}
dev_warn(dev, "invalid assigned rate for pref: %uKHz\n",
dsim->pref_clk);
dev_warn(dev, "use default pref rate instead: %uKHz\n",
PHY_REF_CLK);
dsim->pref_clk = PHY_REF_CLK;
goto set_rate;
}
return 0;
}
#endif
注释掉如下内容:
//escmode |= ESCMODE_FORCEBTA;
修改如下红字的部分,修改为如下图所示:
static void sec_mipi_dsim_set_main_mode(struct sec_mipi_dsim *dsim)
{
uint32_t bpp, hfp_wc, hbp_wc, hsa_wc, wc;
uint32_t mdresol = 0, mvporch = 0, mhporch = 0, msync = 0;
struct videomode *vmode = &dsim->vmode;
mdresol |= MDRESOL_SET_MAINVRESOL(vmode->vactive) |
MDRESOL_SET_MAINHRESOL(vmode->hactive);
dsim_write(dsim, mdresol, DSIM_MDRESOL);
mvporch |= MVPORCH_SET_MAINVBP(vmode->vback_porch) |
MVPORCH_SET_STABLEVFP(vmode->vfront_porch) |
MVPORCH_SET_CMDALLOW(0x0);
dsim_write(dsim, mvporch, DSIM_MVPORCH);
bpp = mipi_dsi_pixel_format_to_bpp(dsim->format);
/* calculate hfp & hbp word counts */
#if 0
if (dsim->panel || !dsim->hpar) {
hfp_wc = vmode->hfront_porch * (bpp >> 3);
hbp_wc = vmode->hback_porch * (bpp >> 3);
} else {
hfp_wc = dsim->hpar->hfp_wc;
hbp_wc = dsim->hpar->hbp_wc;
}
#else
if (!dsim->hpar) {
wc = DIV_ROUND_UP(vmode->hfront_porch * (bpp >> 3),
dsim->lanes);
hfp_wc = wc > MIPI_HFP_PKT_OVERHEAD ?
wc - MIPI_HFP_PKT_OVERHEAD : vmode->hfront_porch;
wc = DIV_ROUND_UP(vmode->hback_porch * (bpp >> 3),
dsim->lanes);
hbp_wc = wc > MIPI_HBP_PKT_OVERHEAD ?
wc - MIPI_HBP_PKT_OVERHEAD : vmode->hback_porch;
} else {
hfp_wc = dsim->hpar->hfp_wc;
hbp_wc = dsim->hpar->hbp_wc;
}
#endif
mhporch |= MHPORCH_SET_MAINHFP(hfp_wc) |
MHPORCH_SET_MAINHBP(hbp_wc);
dsim_write(dsim, mhporch, DSIM_MHPORCH);
/* calculate hsa word counts */
#if 0
if (dsim->panel || !dsim->hpar)
hsa_wc = vmode->hsync_len * (bpp >> 3);
else
hsa_wc = dsim->hpar->hsa_wc;
msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) |
MSYNC_SET_MAINHSA(hsa_wc);
#else
if (!dsim->hpar) {
wc = DIV_ROUND_UP(vmode->hsync_len * (bpp >> 3),
dsim->lanes);
hsa_wc = wc > MIPI_HSA_PKT_OVERHEAD ?
wc - MIPI_HSA_PKT_OVERHEAD : vmode->hsync_len;
} else
hsa_wc = dsim->hpar->hsa_wc;
msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) |
MSYNC_SET_MAINHSA(hsa_wc);
#endif
dsim_write(dsim, msync, DSIM_MSYNC);
}
修改如下图所示内容:
//esc_prescaler = DIV_ROUND_UP_ULL(byte_clk, MAX_ESC_CLK_FREQ);
esc_prescaler = DIV_ROUND_UP(byte_clk, MAX_ESC_CLK_FREQ);
添加如下内容,如下图所示:
#if 1
struct dsim_pll_pms *sec_mipi_dsim_calc_pmsk(struct sec_mipi_dsim *dsim)
{
uint32_t p, m, s;
uint32_t best_p = 0, best_m = 0, best_s = 0;
uint32_t fin, fout;
uint32_t s_pow_2, raw_s;
uint64_t mfin, pfvco, pfout, psfout;
uint32_t delta, best_delta = ~0U;
struct dsim_pll_pms *pll_pms;
struct device *dev = dsim->dev;
const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
struct sec_mipi_dsim_pll dpll = *pdata->dphy_pll;
struct sec_mipi_dsim_range *prange = &dpll.p;
struct sec_mipi_dsim_range *mrange = &dpll.m;
struct sec_mipi_dsim_range *srange = &dpll.s;
struct sec_mipi_dsim_range *krange = &dpll.k;
struct sec_mipi_dsim_range *fvco_range = &dpll.fvco;
struct sec_mipi_dsim_range *fpref_range = &dpll.fpref;
struct sec_mipi_dsim_range pr_new = *prange;
struct sec_mipi_dsim_range sr_new = *srange;
pll_pms = devm_kzalloc(dev, sizeof(*pll_pms), GFP_KERNEL);
if (!pll_pms) {
dev_err(dev, "Unable to allocate 'pll_pms'\n");
return ERR_PTR(-ENOMEM);
}
fout = dsim->bit_clk;
fin = dsim->pref_clk;
/* TODO: ignore 'k' for PMS calculation,
* only use 'p', 'm' and 's' to generate
* the requested PLL output clock.
*/
krange->min = 0;
krange->max = 0;
/* narrow 'p' range via 'Fpref' limitation:
* Fpref : [2MHz ~ 30MHz] (Fpref = Fin / p)
*/
prange->min = max(prange->min, DIV_ROUND_UP(fin, fpref_range->max));
prange->max = min(prange->max, fin / fpref_range->min);
/* narrow 'm' range via 'Fvco' limitation:
* Fvco: [1050MHz ~ 2100MHz] (Fvco = ((m + k / 65536) * Fin) / p)
* So, m = Fvco * p / Fin and Fvco > Fin;
*/
pfvco = (uint64_t)fvco_range->min * prange->min;
mrange->min = max_t(uint32_t, mrange->min,
DIV_ROUND_UP_ULL(pfvco, fin));
pfvco = (uint64_t)fvco_range->max * prange->max;
mrange->max = min_t(uint32_t, mrange->max,
DIV_ROUND_UP_ULL(pfvco, fin));
dev_dbg(dev, "p: min = %u, max = %u, "
"m: min = %u, max = %u, "
"s: min = %u, max = %u\n",
prange->min, prange->max, mrange->min,
mrange->max, srange->min, srange->max);
/* first determine 'm', then can determine 'p', last determine 's' */
for (m = mrange->min; m <= mrange->max; m++) {
/* p = m * Fin / Fvco */
mfin = (uint64_t)m * fin;
pr_new.min = max_t(uint32_t, prange->min,
DIV_ROUND_UP_ULL(mfin, fvco_range->max));
pr_new.max = min_t(uint32_t, prange->max,
(mfin / fvco_range->min));
if (pr_new.max < pr_new.min || pr_new.min < prange->min)
continue;
for (p = pr_new.min; p <= pr_new.max; p++) {
/* s = order_pow_of_two((m * Fin) / (p * Fout)) */
pfout = (uint64_t)p * fout;
raw_s = DIV_ROUND_CLOSEST_ULL(mfin, pfout);
s_pow_2 = rounddown_pow_of_two(raw_s);
sr_new.min = max_t(uint32_t, srange->min,
order_base_2(s_pow_2));
s_pow_2 = roundup_pow_of_two(DIV_ROUND_CLOSEST_ULL(mfin, pfout));
sr_new.max = min_t(uint32_t, srange->max,
order_base_2(s_pow_2));
if (sr_new.max < sr_new.min || sr_new.min < srange->min)
continue;
for (s = sr_new.min; s <= sr_new.max; s++) {
/* fout = m * Fin / (p * 2^s) */
psfout = pfout * (1 << s);
delta = abs(psfout - mfin);
if (delta < best_delta) {
best_p = p;
best_m = m;
best_s = s;
best_delta = delta;
}
}
}
}
if (best_delta == ~0U) {
devm_kfree(dev, pll_pms);
return ERR_PTR(-EINVAL);
}
pll_pms->p = best_p;
pll_pms->m = best_m;
pll_pms->s = best_s;
dev_dbg(dev, "fout = %u, fin = %u, m = %u, "
"p = %u, s = %u, best_delta = %u\n",
fout, fin, pll_pms->m, pll_pms->p, pll_pms->s, best_delta);
return pll_pms;
}
#endif
修改如下所示内容。红色部分是修改过的。
int sec_mipi_dsim_check_pll_out(void *driver_private,
const struct drm_display_mode *mode)
{
int bpp;
uint64_t pix_clk, bit_clk, ref_clk;
struct sec_mipi_dsim *dsim = driver_private;
const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
const struct dsim_hblank_par *hpar;
const struct dsim_pll_pms *pmsk;
bpp = mipi_dsi_pixel_format_to_bpp(dsim->format);
if (bpp < 0)
return -EINVAL;
#if 0
pix_clk = mode->clock * 1000;
bit_clk = DIV_ROUND_UP_ULL(pix_clk * bpp, dsim->lanes);
if (bit_clk > pdata->max_data_rate) {
dev_err(dsim->dev,
"reuest bit clk freq exceeds lane's maximum value\n");
return -EINVAL;
}
dsim->pix_clk = DIV_ROUND_UP_ULL(pix_clk, 1000);
dsim->bit_clk = DIV_ROUND_UP_ULL(bit_clk, 1000);
dsim->pms = 0x4210;
dsim->hpar = NULL;
if (dsim->panel)
return 0;
#else
pix_clk = mode->clock;
bit_clk = DIV_ROUND_UP(pix_clk * bpp, dsim->lanes);
if (bit_clk * 1000 > pdata->max_data_rate) {
dev_err(dsim->dev,
"reuest bit clk freq exceeds lane's maximum value\n");
return -EINVAL;
}
dsim->pix_clk = pix_clk;
dsim->bit_clk = bit_clk;
dsim->hpar = NULL;
pmsk = sec_mipi_dsim_calc_pmsk(dsim);
if (IS_ERR(pmsk)) {
dev_err(dsim->dev,
"failed to get pmsk for: fin = %u, fout = %u\n",
dsim->pref_clk, dsim->bit_clk);
return -EINVAL;
}
dsim->pms = PLLCTRL_SET_P(pmsk->p) |
PLLCTRL_SET_M(pmsk->m) |
PLLCTRL_SET_S(pmsk->s);
/* free 'dsim_pll_pms' structure data which is
* allocated in 'sec_mipi_dsim_calc_pmsk()'.
*/
devm_kfree(dsim->dev, (void *)pmsk);
#endif
#if 0
if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
hpar = sec_mipi_dsim_get_hblank_par(mode->name,
mode->vrefresh,
dsim->lanes);
if (!hpar)
return -EINVAL;
dsim->hpar = hpar;
pms = sec_mipi_dsim_get_pms(dsim->bit_clk);
if (WARN_ON(!pms))
return -EINVAL;
ref_clk = PHY_REF_CLK / 1000;
/* TODO: add PMS calculate and check
* Only support '1080p@60Hz' for now,
* add other modes support later
*/
dsim->pms = PLLCTRL_SET_P(pms->p) |
PLLCTRL_SET_M(pms->m) |
PLLCTRL_SET_S(pms->s);
}
#else
if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
hpar = sec_mipi_dsim_get_hblank_par(mode->name,
mode->vrefresh,
dsim->lanes);
dsim->hpar = hpar;
if (!hpar)
dev_dbg(dsim->dev, "no pre-exist hpar can be used\n");
}
#endif
return 0;
}
修改为如下图所示,红色部分是修改过的内容。
static void sec_mipi_dsim_set_main_mode(struct sec_mipi_dsim *dsim)
{
uint32_t bpp, hfp_wc, hbp_wc, hsa_wc, wc;
uint32_t mdresol = 0, mvporch = 0, mhporch = 0, msync = 0;
struct videomode *vmode = &dsim->vmode;
mdresol |= MDRESOL_SET_MAINVRESOL(vmode->vactive) |
MDRESOL_SET_MAINHRESOL(vmode->hactive);
dsim_write(dsim, mdresol, DSIM_MDRESOL);
mvporch |= MVPORCH_SET_MAINVBP(vmode->vback_porch) |
MVPORCH_SET_STABLEVFP(vmode->vfront_porch) |
MVPORCH_SET_CMDALLOW(0x0);
dsim_write(dsim, mvporch, DSIM_MVPORCH);
bpp = mipi_dsi_pixel_format_to_bpp(dsim->format);
/* calculate hfp & hbp word counts */
#if 0
if (dsim->panel || !dsim->hpar) {
hfp_wc = vmode->hfront_porch * (bpp >> 3);
hbp_wc = vmode->hback_porch * (bpp >> 3);
} else {
hfp_wc = dsim->hpar->hfp_wc;
hbp_wc = dsim->hpar->hbp_wc;
}
#else
if (!dsim->hpar) {
wc = DIV_ROUND_UP(vmode->hfront_porch * (bpp >> 3),
dsim->lanes);
hfp_wc = wc > MIPI_HFP_PKT_OVERHEAD ?
wc - MIPI_HFP_PKT_OVERHEAD : vmode->hfront_porch;
wc = DIV_ROUND_UP(vmode->hback_porch * (bpp >> 3),
dsim->lanes);
hbp_wc = wc > MIPI_HBP_PKT_OVERHEAD ?
wc - MIPI_HBP_PKT_OVERHEAD : vmode->hback_porch;
} else {
hfp_wc = dsim->hpar->hfp_wc;
hbp_wc = dsim->hpar->hbp_wc;
}
#endif
mhporch |= MHPORCH_SET_MAINHFP(hfp_wc) |
MHPORCH_SET_MAINHBP(hbp_wc);
dsim_write(dsim, mhporch, DSIM_MHPORCH);
/* calculate hsa word counts */
#if 0
if (dsim->panel || !dsim->hpar)
hsa_wc = vmode->hsync_len * (bpp >> 3);
else
hsa_wc = dsim->hpar->hsa_wc;
msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) |
MSYNC_SET_MAINHSA(hsa_wc);
#else
if (!dsim->hpar) {
wc = DIV_ROUND_UP(vmode->hsync_len * (bpp >> 3),
dsim->lanes);
hsa_wc = wc > MIPI_HSA_PKT_OVERHEAD ?
wc - MIPI_HSA_PKT_OVERHEAD : vmode->hsync_len;
} else
hsa_wc = dsim->hpar->hsa_wc;
msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) |
MSYNC_SET_MAINHSA(hsa_wc);
#endif
dsim_write(dsim, msync, DSIM_MSYNC);
}
添加如下内容:
#if 0
{
//cym
#if 0
dsim_write(dsim, 0x91f80002, DSIM_CLKCTRL);
dsim_write(dsim, 0x82570400, DSIM_MDRESOL);
dsim_write(dsim, 0x20018, DSIM_MVPORCH);
dsim_write(dsim, 0xc0072, DSIM_MHPORCH);
dsim_write(dsim, 0xc00051, DSIM_MSYNC);
dsim_write(dsim, 0x7ac7bfff, DSIM_RXFIFO);
dsim_write(dsim, 0x825904, DSIM_PLLCTRL);
dsim_write(dsim, 0x203, DSIM_PHYTIMING);
dsim_write(dsim, 0x20d0803, DSIM_PHYTIMING1);
dsim_write(dsim, 0x30305, DSIM_PHYTIMING2);
#endif
printk("DSIM_STATUS:0x%x\n", dsim_read(dsim, DSIM_STATUS));
printk("DSIM_RGB_STATUS:0x%x\n", dsim_read(dsim, DSIM_RGB_STATUS));
printk("DSIM_SWRST:0x%x\n", dsim_read(dsim, DSIM_SWRST));
printk("DSIM_CLKCTRL:0x%x\n", dsim_read(dsim, DSIM_CLKCTRL));
printk("DSIM_TIMEOUT:0x%x\n", dsim_read(dsim, DSIM_TIMEOUT));
printk("DSIM_CONFIG:0x%x\n", dsim_read(dsim, DSIM_CONFIG));
printk("DSIM_ESCMODE:0x%x\n", dsim_read(dsim, DSIM_ESCMODE));
printk("DSIM_MDRESOL:0x%x\n", dsim_read(dsim, DSIM_MDRESOL));
printk("DSIM_MVPORCH:0x%x\n", dsim_read(dsim, DSIM_MVPORCH));
printk("DSIM_MHPORCH:0x%x\n", dsim_read(dsim, DSIM_MHPORCH));
printk("DSIM_MSYNC:0x%x\n", dsim_read(dsim, DSIM_MSYNC));
printk("DSIM_SDRESOL:0x%x\n", dsim_read(dsim, DSIM_SDRESOL));
printk("DSIM_INTSRC:0x%x\n", dsim_read(dsim, DSIM_INTSRC));
printk("DSIM_INTMSK:0x%x\n", dsim_read(dsim, DSIM_INTMSK));
printk("DSIM_PKTHDR:0x%x\n", dsim_read(dsim, DSIM_PKTHDR));
printk("DSIM_PAYLOAD:0x%x\n", dsim_read(dsim, DSIM_PAYLOAD));
printk("DSIM_RXFIFO:0x%x\n", dsim_read(dsim, DSIM_RXFIFO));
printk("DSIM_FIFOTHLD:0x%x\n", dsim_read(dsim, DSIM_FIFOTHLD));
printk("DSIM_FIFOCTRL:0x%x\n", dsim_read(dsim, DSIM_FIFOCTRL));
printk("DSIM_MEMACCHR:0x%x\n", dsim_read(dsim, DSIM_MEMACCHR));
printk("DSIM_MULTI_PKT:0x%x\n", dsim_read(dsim, DSIM_MULTI_PKT));
printk("DSIM_PLLCTRL_1G:0x%x\n", dsim_read(dsim, DSIM_PLLCTRL_1G));
printk("DSIM_PLLCTRL:0x%x\n", dsim_read(dsim, DSIM_PLLCTRL));
printk("DSIM_PLLCTRL1:0x%x\n", dsim_read(dsim, DSIM_PLLCTRL1));
printk("DSIM_PLLCTRL2:0x%x\n", dsim_read(dsim, DSIM_PLLCTRL2));
printk("DSIM_PLLTMR:0x%x\n", dsim_read(dsim, DSIM_PLLTMR));
printk("DSIM_PHYTIMING:0x%x\n", dsim_read(dsim, DSIM_PHYTIMING));
printk("DSIM_PHYTIMING1:0x%x\n", dsim_read(dsim, DSIM_PHYTIMING1));
printk("DSIM_PHYTIMING2:0x%x\n", dsim_read(dsim, DSIM_PHYTIMING2));
}
#endif
修改如下图所示:
#if 0 //modify by cym 20201127
/* TODO: set pll ref clock rate to be fixed with 27MHz */
ret = clk_set_rate(dsim->clk_pllref, PHY_REF_CLK);
#else
/* set suitable rate for phy ref clock */
ret = sec_mipi_dsim_set_pref_rate(dsim);
#endif
修改/home/topeet/imx8mm/linux/linux/linux-imx/include/drm/bridge/sec_mipi_dsim.h,添加如下所示代码,红色部分是修改过的代码
#ifndef __SEC_MIPI_DSIM_H__
#define __SEC_MIPI_DSIM_H__
#include <drm/drmP.h>
#include <linux/bsearch.h>
struct sec_mipi_dsim_dphy_timing;
struct sec_mipi_dsim_pll;//add by cym 20201127
struct sec_mipi_dsim_plat_data {
uint32_t version;
uint32_t max_data_lanes;
uint64_t max_data_rate;
const struct sec_mipi_dsim_dphy_timing *dphy_timing;
uint32_t num_dphy_timing;
const struct sec_mipi_dsim_pll *dphy_pll;//add by cym 20201127
int (*dphy_timing_cmp)(const void *key, const void *elt);
enum drm_mode_status (*mode_valid)(struct drm_connector *connector,
struct drm_display_mode *mode);
};
/* add by cym 20201127 */
/* DPHY PLL structure */
struct sec_mipi_dsim_range {
uint32_t min;
uint32_t max;
};
struct sec_mipi_dsim_pll {
struct sec_mipi_dsim_range p;
struct sec_mipi_dsim_range m;
struct sec_mipi_dsim_range s;
struct sec_mipi_dsim_range k;
struct sec_mipi_dsim_range fin;
struct sec_mipi_dsim_range fpref;
struct sec_mipi_dsim_range fvco;
};
/* end add */
/* DPHY timings structure */
struct sec_mipi_dsim_dphy_timing {
uint32_t bit_clk; /* MHz */
uint32_t clk_prepare;
uint32_t clk_zero;
uint32_t clk_post;
uint32_t clk_trail;
uint32_t hs_prepare;
uint32_t hs_zero;
uint32_t hs_trail;
uint32_t lpx;
uint32_t hs_exit;
};
#define DSIM_DPHY_TIMING(bclk, cpre, czero, cpost, ctrail, \
hpre, hzero, htrail, lp, hexit) \
.bit_clk = bclk, \
.clk_prepare = cpre, \
.clk_zero = czero, \
.clk_post = cpost, \
.clk_trail = ctrail, \
.hs_prepare = hpre, \
.hs_zero = hzero, \
.hs_trail = htrail, \
.lpx = lp, \
.hs_exit = hexit
static inline int dphy_timing_default_cmp(const void *key, const void *elt)
{
const struct sec_mipi_dsim_dphy_timing *_key = key;
const struct sec_mipi_dsim_dphy_timing *_elt = elt;
/* find an element whose 'bit_clk' is equal to the
* the key's 'bit_clk' value or, the difference
* between them is less than 5.
*/
if (abs((int)(_elt->bit_clk - _key->bit_clk)) <= 5)
return 0;
if (_key->bit_clk < _elt->bit_clk)
/* search bottom half */
return 1;
else
/* search top half */
return -1;
}
int sec_mipi_dsim_check_pll_out(void *driver_private,
const struct drm_display_mode *mode);
int sec_mipi_dsim_bind(struct device *dev, struct device *master, void *data,
struct drm_encoder *encoder, struct resource *res,
int irq, const struct sec_mipi_dsim_plat_data *pdata);
void sec_mipi_dsim_unbind(struct device *dev, struct device *master, void *data);
void sec_mipi_dsim_suspend(struct device *dev);
void sec_mipi_dsim_resume(struct device *dev);
#endif
然后我们修改/home/topeet/bsp_kernel_imx/bsp_kernel_imx/linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dts设备树文件,&mipi_dsi节点如下图所示:
我们只需要在这个节点下配置屏幕设备树节点,因为要支持四种屏幕,所以我们先来设置四种屏幕的宏定义,如下所示,如果我们想要编译某种屏幕,将某种屏幕的宏定义使能就好了。
//#define LCD_TYPE_10_1 1
#define LCD_TYPE_7_0 1
//#define LCD_TYPE_9_7 1
//#define LCD_TYPE_MIPI_7_0 1
首先我们删除原来节点下的内容:
然后我们写个逻辑框架,如下所示,数字代表是我们要添加的代码,mipi 7寸屏的配置写在数字111处,依次类推。
我们先来配置mipi 7寸屏幕,以下代码填写在111处
panel@0 {/* 7.0 inch lvds screen */
/*compatible是系统用来决定绑定到设备的设备驱动的关键,在内核源码driver目录下搜索toshiba,panel-tc358775会找到对应的驱动文件*/
compatible = "toshiba,panel-tc358775"; /*匹配驱动的值*/
reg = <0>;
pinctrl-0 = <&pinctrl_mipi_dsi_en>; /*使用到的IO*/
reset-gpio = <&gpio1 8 GPIO_ACTIVE_HIGH>; /*重置GPIO*/
status = "okay"; /*状态为okay*/
panel-width-mm = <154>;
panel-height-mm = <85>;
dsi-lanes = <4>; /*显示通道设置为四通道*/
backlight = <&backlight0>; /*背光*/
client-device = <&tc358775>; /*客户端*/
display-timings {
native-mode = <&timing0>;/*时序信息*/
timing0:timing0{
clock-frequency = <70000000>;*//*LCD像素时钟,单位是Hz*/
hactive = <800>; /*LCD X轴像素个数*/
hsync-len = <10>; /*LCD hspw参数*/
hback-porch = <100>;/* important *//*LCD hbp参数*/
hfront-porch = <24>;/*LCD hfp参数*/
vactive = <1280>;/*LCD Y轴像素个数*/
vsync-len = <10>;/*LCD vspw参数*/
vback-porch = <25>;/*LCD vbp参数*/
vfront-porch = <10>;/*LCD vfp参数*/
vsync-active = <0>;/*vsync 数据线极性*/
hsync-active =<0>;/*hsync 数据线极性*/
de-active =<0>;/*de 数据线极性*/
pixelclk-active =<0>;/*clk数据线极性*/
};
};
};
上面我们已经解释了设备树配置的参数,那么屏幕的时序信息是什么意思呢?
我们把屏幕想象成一幅画,显示的过程其实就是用笔在不同的像素点画上不同的颜色,这根笔按照从左到右,从上到下的顺序画每个像素点,当画完最后一个像素点,一幅画也就画好了。我们画一个示意图,如下所示:
Hsync 是水平同步信号,也叫做行同步信号,当产生此信号的话就表示开始显示新的一行了,所以此信号都是在上图的最左边,Vsync是垂直同步信号,也叫做帧同步信号,当产生此信号的话就表示开始显示新的一帧图像了,所以此信号在上图的左上角。其实真正显示的是中间白色的部分。
显示完一行后会发出Hsync信号,然后电子枪会关闭,然后很快移动到屏幕的左边,hsync信号结束之后便可以显示新的一行数据,电子枪会打开,那么hsync信号结束到开始之间会插入一段延时,这个延时就是hbp,同理,vbp也是这样的道理,HBP HFP VBP VFP就是导致黑边的原因,这四个值的具体值得查阅LCD数据手册。
接下来我们来配置lvds 7寸屏幕,以下代码填写在222处
panel@0 {/* 7.0 inch lvds screen */
compatible = "toshiba,panel-tc358775";
reg = <0>;
pinctrl-0 = <&pinctrl_mipi_dsi_en>;
reset-gpio = <&gpio1 8 GPIO_ACTIVE_HIGH>;
status = "okay";
panel-width-mm = <154>;
panel-height-mm = <85>;
dsi-lanes = <4>;
backlight = <&backlight0>;
client-device = <&tc358775>;
display-timings {
native-mode = <&timing0>;
timing0:timing0{
clock-frequency = <70000000>;/*<70000000>;*/
hactive = <800>;
hsync-len = <10>;
hback-porch = <100>;/* important */
hfront-porch = <24>;
vactive = <1280>;
vsync-len = <10>;
vback-porch = <25>;
vfront-porch = <10>;
vsync-active = <0>;
hsync-active =<0>;
de-active =<0>;
pixelclk-active =<0>;
};
};
};
接下来我们来配置lvds 9.7寸屏幕,以下代码填写在333处
panel@1 {/* 9.7 inch screen */
compatible = "toshiba,panel-tc358775";
reg = <0>;
pinctrl-0 = <&pinctrl_mipi_dsi_en>;
reset-gpio = <&gpio1 8 GPIO_ACTIVE_HIGH>;
status = "okay";/*"disabled";*/
panel-width-mm = <154>;
panel-height-mm = <85>;
dsi-lanes = <4>;
backlight = <&backlight0>;
client-device = <&tc358775>;
display-timings {
timing {
clock-frequency = <65000000>;//<132000000>;
hactive = <1024>;
vactive = <768>;
hfront-porch = <4>;
hsync-len = <116>;
hback-porch = <20>;
vfront-porch = <2>;
vsync-len = <10>;//<12>;//<4>;
vback-porch = <20>;//<16>;//<6>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
};
};
};
接下来我们来配置lvds 10.1寸屏幕,以下代码填写在444处。
panel@0 {/* 10.1 inch screen */
compatible = "toshiba,panel-tc358775";
reg = <0>;
pinctrl-0 = <&pinctrl_mipi_dsi_en>;
reset-gpio = <&gpio1 8 GPIO_ACTIVE_HIGH>;
status = "okay";
panel-width-mm = <154>;
panel-height-mm = <85>;
dsi-lanes = <4>;
backlight = <&backlight0>;
client-device = <&tc358775>;
display-timings {
native-mode = <&timing0>;
timing0:timing0{
clock-frequency = <50000000>;
hactive = <1024>;
hsync-len = <116>;
hback-porch = <160>;
hfront-porch = <24>;
vactive = <600>;
vsync-len = <3>;
vback-porch = <24>;
vfront-porch = <2>;
vsync-active = <0>;
hsync-active =<0>;
de-active =<0>;
pixelclk-active =<0>;
};
};
};
然后在根目录下配置背光信息,添加backlight0节点,如下图所示:
backlight0: backlight@0 {
compatible = "pwm-backlight";
pwms = <&pwm1 0 50000 0>;
brightness-levels = < 0 23 23 23 23 23 23 23 23 23
23 23 23 23 23 23 23 23 23 23
23 23 23 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99
100>;
default-brightness-level = <89>;
};
然后在根目录下配置&pwm1节点,如下图所示:
&pwm1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm1>;
status = "okay";
};
然后在pinctrl中添加pwm1信息,如下图所示:
在&iomuxc中默认配置好了引脚
pinctrl_mipi_dsi_en: mipi_dsi_en {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8 0x16
>;
};
pinctrl_pwm1: pwm1 {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO01_PWM1_OUT 0x1d0
>;
};
然后再i2c2下面注释掉以下节点
/*
adv_bridge: adv7535@3d {
compatible = "adi,adv7533";
reg = <0x3d>;
adi,addr-cec = <0x3b>;
adi,dsi-lanes = <4>;
status = "okay";
port {
adv7535_from_dsim: endpoint {
remote-endpoint = <&dsim_to_adv7535>;
};
};
};
*/
然后再i2c2节点下面配置
tc358775:tc358775@0x0f{
compatible = "toshiba,tc358775";
reg = <0x0f>;
status = "okay";
};
72.2配置屏幕驱动
将网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\3.Linux系统移植\8.移植内核需要用到的文件\01 适配屏幕需要使用的文件”目录下的panel-itop_mipi_screen.c和tc358775_i2c.c和panel-tc358775.c拷贝到内核源码的
/linux-imx/drivers/gpu/drm/panel/目录下。
然后修改linux-imx/drivers/gpu/drm/panel/目录下Makefile文件,添加如下内容:
obj-$(CONFIG_DRM_PANEL_ITOP_MIPI) += panel-itop_mipi_screen.o
obj-$(CONFIG_DRM_PANEL_TC358775) += panel-tc358775.o
obj-$(CONFIG_DRM_PANEL_TC358775) += tc358775_i2c.o
然后修改linux-imx/drivers/gpu/drm/panel/目录下kconfig文件,添加如下内容:
tristate "itop mipi screen panel"
depends on OF
depends on DRM_MIPI_DSI
depends on BACKLIGHT_CLASS_DEVICE
help
Say Y here if you want to enable support for itop mipi screen panel
DSI panel.
config DRM_PANEL_TC358775
tristate "TC358775 FHD panel"
depends on OF
depends on DRM_MIPI_DSI
depends on BACKLIGHT_CLASS_DEVICE
help
Say Y here if you want to enable support for TC358775 FHD
DSI panel.
72.3编译屏幕驱动到内核
在上面小节配置完毕,保存修改,然后重新打开一个终端,输入以下命令,将默认的配置文件写入到.config文件。
make defconfig
然后输入以下命令进入menuconfig进行配置,如下所示:
export ARCH=arm64
make menuconfig
然后按如下图所示的路径,配置上屏幕,如下图所示:
72.4使能 Linux logo 显示
Linux 内核启动的时候可以选择显示小企鹅 logo,只要这个小企鹅 logo 显示没问题那么我们的 LCD 驱动基本就工作正常了。这个 logo显示是要配置的,不过 Linux 内核一般都会默认开启 logo 显示,但是奔着学习的目的,我们还是来看一下如何使能 Linux logo 显示。打开 Linux 内核图形化配置界面,按下路径找到对应的配置项:
-> Device Drivers
-> Graphics support
-> Bootup logo (LOGO [=y])
-> Standard black and white Linux logo
-> Standard 16-color Linux logo
-> Standard 224-color Linux logo
最后保存配置文件到arch/arm64/configs/defconfig文件,如下图所示:
72.5配置触摸驱动
Mipi 7寸屏幕,lvds7 寸屏幕 ,lvds9.7寸屏幕使用的触摸芯片是ft5x06, lvds 10.1寸屏幕使用的触摸芯片是gt911。
首先修改/home/topeet/bsp_kernel_imx/bsp_kernel_imx/linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dts设备树文件 ,配置触摸芯片的节点,如下图示,挂载在i2c2上。
&i2c2{
.............................................................................................
#if defined(LCD_TYPE_7_0) || defined(LCD_TYPE_9_7) || defined(LCD_TYPE_MIPI_7_0)
ft5x06_ts@38 {
compatible = "edt,edt-ft5x06";
reg = <0x38>;
pinctrl-names = "defaults";
pinctrl-0 = <&pinctrl_ft5x06_int>;
interrupt-parent = <&gpio1>;
interrupts = <15 2>;
status = "okay";
};
#elif defined(LCD_TYPE_10_1)
gt911@14 {
compatible = "goodix,gt911";
reg = <0x14>;/*<0x5d>;*//*<0x14>;*/
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ft5x06_int>;
interrupt-parent = <&gpio1>;
interrupts = <15 2>;/*<15 IRQ_TYPE_EDGE_RISING>;*/
/*synaptics,y-rotation;*/
esd-recovery-timeout-ms = <2000>;
irq-gpios = <&gpio1 15 0>;/*<&gpio1 15 GPIO_ACTIVE_HIGH>;*/
reset-gpios = <&gpio3 23 0>;/*<&gpio3 23 GPIO_ACTIVE_HIGH>;*/
status = "okay";
};
.............................................................................................
};
然后配置pinctrl信息,如下图所示:
&iomuxc {
..................................................................................................
pinctrl_ft5x06_int: ft5x06_int {
fsl,pins = <
/*MX8MM_IOMUXC_GPIO1_IO09_GPIO1_IO9 0x159*/
MX8MM_IOMUXC_GPIO1_IO15_GPIO1_IO15 0x159
MX8MM_IOMUXC_SAI5_RXD2_GPIO3_IO23 0x41
>;
};
..................................................................................................
接下来拷贝“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\3.Linux系统移植\8.移植内核需要用到的文件\01 适配屏幕需要使用的文件”目录下的edt-ft5x06.c文件拷贝到内核源码linux-imx/drivers/input/touchscreen目录下。
72.6 编译驱动进内核
在上面小节配置完毕,保存修改,然后重新打开一个终端,输入以下命令,将默认的配置文件写入到.config文件。
make defconfig
然后输入以下命令进入menuconfig进行配置,如下所示:
export ARCH=arm64
make menuconfig
然后按如下图所示的路径,配置上屏幕,如下图所示:
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT │
-> Touchscreens
最后保存配置文件到arch/arm64/configs/defconfig文件,如下图所示:
72.7设定屏幕
如果要设置 7 寸 mipi 屏幕,则进入 linux 源码路径下,编辑 itop8mm-evk.dts 设备树。
vim linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dts
将#define LCD_TYPE_MIPI_7_0 1 取消注释,其他屏幕的宏定义加上注释,如下图所示:
如果要设置 7 寸 lvds 屏幕,则进入 linux 源码路径下,编辑 itop8mm-evk.dts 设备树。
vim linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dts
将#define LCD_TYPE_7_0 1 取消注释,其他屏幕的宏定义加上注释,如下图所示:
然后编辑 panel-tc358775.c。
vim linux-imx/drivers/gpu/drm/panel/panel-tc358775.c
修改触摸文件 edt-ft5x06.c
vim linux-imx/drivers/input/touchscreen/edt-ft5x06.c
将#define CONFIG_LVDS_7_0_800x1280 1 取消注释,其他屏幕的宏定义加上注释,如下图所示:
如果要设置 9.7 寸 lvds 屏幕,则进入 linux 源码路径下,编辑 itop8mm-evk.dts 设备树。
vim linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dts
将#define LCD_TYPE_9_7 1 取消注释,其他屏幕的宏定义加上注释,如下图所示
然后编辑 panel-tc358775.c。
vim linux-imx/drivers/gpu/drm/panel/panel-tc358775.c
将#define LCD_TYPE_9_7 1 取消注释,其他屏幕的宏定义加上注释,如下图所示:
修改触摸文件 edt-ft5x06.c
vim linux-imx/drivers/input/touchscreen/edt-ft5x06.c
将#define CONFIG_LVDS_9_7_1024x768 1 取消注释,其他屏幕的宏定义加上注释,如下图所示:
如果要设置 10.1 寸 lvds 屏幕,则进入 linux 源码路径下,编辑 itop8mm-evk.dts 设备树。
vim linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dts
将#define LCD_TYPE_10_1 1 取消注释,其他屏幕的宏定义加上注释,如下图所示:
然后编辑 panel-tc358775.c。
vim linux-imx/drivers/gpu/drm/panel/panel-tc358775.c
将#define LCD_TYPE_10_1 1 取消注释,其他屏幕的宏定义加上注释,如下图所示:
然后再回到之前的终端窗口进行编译镜像。编译之后烧写镜像,系统启动后如下图所示,作者连接的是mipi屏幕,触摸显示正常。如下图所示:
输入以下命令查看触摸节点
cat /proc/bus/input/devices
然后输入以下命令,触摸屏幕会上报信息。
hexdump /dev/input/event1