【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第四篇 嵌入式Linux系统移植篇-第七十二章 内核配置屏幕驱动

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"

cpp 复制代码
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;

添加如下内容:

cpp 复制代码
#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;

修改如下红字的部分,修改为如下图所示:

cpp 复制代码
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);

添加如下内容,如下图所示:

cpp 复制代码
#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

修改如下所示内容。红色部分是修改过的。

cpp 复制代码
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;
}

修改为如下图所示,红色部分是修改过的内容。

cpp 复制代码
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);
}

添加如下内容:

cpp 复制代码
#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

修改如下图所示:

cpp 复制代码
#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****,添加如下所示代码,红色部分是修改过的代码

cpp 复制代码
#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节点如下图所示:

我们只需要在这个节点下配置屏幕设备树节点,因为要支持四种屏幕,所以我们先来设置四种屏幕的宏定义,如下所示,如果我们想要编译某种屏幕,将某种屏幕的宏定义使能就好了。

cpp 复制代码
//#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处

cpp 复制代码
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处

cpp 复制代码
    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处

cpp 复制代码
   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处。

cpp 复制代码
 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节点,如下图所示:

cpp 复制代码
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中默认配置好了引脚

cpp 复制代码
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下面注释掉以下节点

cpp 复制代码
/*
	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文件,添加如下内容:

cpp 复制代码
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文件,添加如下内容:

cpp 复制代码
        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

然后按如下图所示的路径,配置上屏幕,如下图所示:

Linux 内核启动的时候可以选择显示小企鹅 logo,只要这个小企鹅 logo 显示没问题那么我们的 LCD 驱动基本就工作正常了。这个 logo显示是要配置的,不过 Linux 内核一般都会默认开启 logo 显示,但是奔着学习的目的,我们还是来看一下如何使能 Linux logo 显示。打开 Linux 内核图形化配置界面,按下路径找到对应的配置项:

cpp 复制代码
-> 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上。

cpp 复制代码
&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 {

cpp 复制代码
..................................................................................................
    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

相关推荐
虾..1 天前
Linux 软硬链接和动静态库
linux·运维·服务器
Evan芙1 天前
Linux常见的日志服务管理的常见日志服务
linux·运维·服务器
iCxhust1 天前
8255 PORTC 按键输入测试
单片机·嵌入式硬件·微机原理
hkhkhkhkh1231 天前
Linux设备节点基础知识
linux·服务器·驱动开发
HZero.chen1 天前
Linux字符串处理
linux·string
张童瑶1 天前
Linux SSH隧道代理转发及多层转发
linux·运维·ssh
汪汪队立大功1231 天前
什么是SELinux
linux
石小千1 天前
Linux安装OpenProject
linux·运维
柏木乃一1 天前
进程(2)进程概念与基本操作
linux·服务器·开发语言·性能优化·shell·进程
Lime-30901 天前
制作Ubuntu 24.04-GPU服务器测试系统盘
linux·运维·ubuntu