On 9/28/26 10:08 AM, Julien Stephan wrote:
> The DSI controller is the last stage of the MT8365 and MT8366 display
> pipelines: it serialises the pixel stream coming out of the pipeline
> onto the MIPI D-PHY lanes, and carries the DCS commands the panel needs
> to come up.
>
> Add a video uclass driver bringing up that whole pipeline: power the
> D-PHY and the controller up with its D-PHY timings, ungate the
> components, route the overlay to the DSI through the mmsys, start the
> components in order, then program the video timings and the pixel stream
> format from the panel's own description, and start the video stream once
> the panel and its backlight are on. The MT8365 runs the stream through
> the colour pipeline, the MT8366 straight from the rdma, which the
> per-compatible data selects.
>
> The driver is also the MIPI DSI host the panel talks to: the panel init
> sequence goes out through the controller's command queue, in command
> mode, before the video stream is started.
>
> The driver matches "mediatek,mt8183-dsi", the generic fallback the
> MT8365 dsi0 node carries behind "mediatek,mt8365-dsi", and
> "mediatek,mt8366-dsi", which the MT8366 node carries on its own as that
> SoC is not upstream yet. Beyond the colour pipeline, the two generations
> differ in the offset of the command queues, in the mt8183-style shadow
> register control, which the MT8366 does not have, and in the command
> queue size of the long packets, which the MT8366 has to set itself
> (CMDQ_SIZE_SEL): all of it is per-compatible data.
>
> The objects both paths need move out of the HDMI object list into
> Kconfig symbols of their own: VIDEO_MEDIATEK_DISP_COMP for the component
> helpers and the mutex, selected by both paths, and VIDEO_MEDIATEK_DPI
> for the DPI, selected by the HDMI path. VIDEO_MEDIATEK_DISP_PIPELINE
> covers the mmsys routing and the display rdma of the DSI path.
>
> Signed-off-by: Julien Stephan <[email protected]>
> ---
> drivers/video/mediatek/Kconfig | 32 +
> drivers/video/mediatek/Makefile | 19 +-
> drivers/video/mediatek/mtk_dsi.c | 1220
> ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1268 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/video/mediatek/Kconfig b/drivers/video/mediatek/Kconfig
> index a10f2360391..9d779a26da5 100644
> --- a/drivers/video/mediatek/Kconfig
> +++ b/drivers/video/mediatek/Kconfig
> @@ -4,6 +4,36 @@
> #
> # Author: Julien Stephan <[email protected]>
>
> +# Display pipeline component helpers, selected by the output paths below
> +config VIDEO_MEDIATEK_DISP_COMP
> + bool
> +
> +# mmsys routing and display RDMA, used by the DSI path
> +config VIDEO_MEDIATEK_DISP_PIPELINE
> + bool
> +
> +# DPI timing generator programming, shared by the DPI and HDMI paths
> +config VIDEO_MEDIATEK_DPI
> + bool
> +
> +config VIDEO_MEDIATEK_DSI
> + bool "DSI video path for MediaTek SoCs"
> + depends on VIDEO && ARCH_MEDIATEK
> + depends on ARM64 && CLK && PHY
&& POWER_DOMAIN
> + select MISC
> + select MTK_SMI
> + select PANEL
> + select PHY_MTK_MIPI_DSI
> + select SYSCON
> + select VIDEO_MEDIATEK_DISP_COMP
> + select VIDEO_MEDIATEK_DISP_PIPELINE
> + select VIDEO_MIPI_DSI
> + help
> + Enable support for the DSI controller found on MediaTek SoCs such
> + as the MT8365 and the MT8366. The driver drives the mmsys display
> + pipeline (overlay, RDMA and, on the SoCs having one, the colour
> + pipeline) to scan out the U-Boot framebuffer to a DSI panel.
> +
> config VIDEO_MEDIATEK_HDMI
> bool "Video HDMI Support for MediaTek SoCs"
> depends on VIDEO && ARCH_MEDIATEK
> @@ -14,6 +44,8 @@ config VIDEO_MEDIATEK_HDMI
> select MTK_SMI
> select PHY_MTK_HDMI
> select SYS_I2C_DDC_MTK
> + select VIDEO_MEDIATEK_DISP_COMP
> + select VIDEO_MEDIATEK_DPI
> help
> Enable support for the HDMI TX output found on recent MediaTek
> SoCs such as the MT8188. The driver reads the EDID of the
> diff --git a/drivers/video/mediatek/Makefile b/drivers/video/mediatek/Makefile
> index 38ab13e94fd..9de9a9c78f1 100644
> --- a/drivers/video/mediatek/Makefile
> +++ b/drivers/video/mediatek/Makefile
...
> +/**
> + * struct mtk_dsi_data - per-SoC DSI data
> + * @has_color_pipeline: the DSI is fed through the color, ccorr, aal, gamma
> + * and dither blocks rather than straight by the rdma
> + * @cmdq_long_packet_ctl: the command queue size is not set automatically for
> + * long packets, CMDQ_SIZE_SEL must be set
> + * @shadow_dbg_off: offset of the shadow control, zero on the SoCs without it
> + * @vm_cmdq_off: offset of the video mode command register
> + * @host_cmdq_off: offset of the host command queue
> + */
> +struct mtk_dsi_data {
> + bool has_color_pipeline;
I've been looking ahead to other targets, and I think has_color_pipeline
doesn't actually belong here. To be more like Linux, it rather belongs on
the mmsys component. And it is not just a boolean. It should describe the
individual components in the pipline.
I.e. for DSI each conpatible would have an array (or read this from of_graph
but I think that was avoided for simplicity since we know what it is anyway?)
For example:
MT8365: ovl0, rdma0, color0, ccorr0, aal0, gamma0, dither0
MT8366/MT8189: ovl0, rdma0
MT8188: ovl0, rdma0, color0, ccorr0, aal0, gamma0, postmask0, dither0
MT8195: ovl0, rdma0, color0, ccorr0, aal0, gamma0, dither0, dsc0, merge0
MT8183: ovl0, ovl_2l0, rdma0, color0, ccorr0, aal0, gamma0, dither0
Plus HDMI and DP could have different piplines.
> + bool cmdq_long_packet_ctl;
> + u32 shadow_dbg_off;
> + u32 vm_cmdq_off;
> + u32 host_cmdq_off;
> +};
> +
...
> +static int mtk_dsi_power_on(struct mtk_dsi_priv *dsi,
> + const struct display_timing *timing)
> +{
> + struct mipi_dsi_panel_plat *plat = dev_get_plat(dsi->panel);
> + struct phy_configure_opts_mipi_dphy opts = { };
> + int ret, bit_per_pixel;
> +
> + bit_per_pixel = mipi_dsi_pixel_format_to_bpp(plat->format);
> + if (bit_per_pixel < 0) {
> + dev_err(dsi->dev, "unknown MIPI DSI format %d\n", plat->format);
> + return bit_per_pixel;
> + }
> +
> + dsi->data_rate = DIV_ROUND_UP_ULL((u64)timing->pixelclock.typ *
> + bit_per_pixel, plat->lanes);
> +
> + opts.hs_clk_rate = dsi->data_rate;
> + opts.lanes = plat->lanes;
> +
> + ret = generic_phy_configure(&dsi->phy, &opts);
> + if (ret) {
> + dev_err(dsi->dev, "failed to set the PHY to %llu Hz: %d\n",
> + dsi->data_rate, ret);
> + return ret;
> + }
> +
> + ret = generic_phy_power_on(&dsi->phy);
> + if (ret) {
> + dev_err(dsi->dev, "failed to power on the PHY: %d\n", ret);
> + return ret;
> + }
> +
> + ret = clk_enable(dsi->engine_clk);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable engine clk: %d\n", ret);
> + goto err_phy_power_off;
> + }
> +
> + ret = clk_enable(dsi->digital_clk);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable digital clk: %d\n", ret);
> + goto err_disable_engine_clk;
> + }
> +
> + ret = mtk_disp_comp_enable(dsi->mutex);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable mutex: %d\n", ret);
> + goto err_disable_digital_clk;
> + }
> +
> + ret = mtk_disp_comp_enable(dsi->ovl);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable ovl: %d\n", ret);
> + goto err_disable_mutex;
> + }
> +
> + ret = mtk_disp_comp_enable(dsi->rdma);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable rdma: %d\n", ret);
> + goto err_disable_ovl;
> + }
> +
On the subject of replacing has_color_pipeline again. This would become
something like a for loop to enable the components used by the chosen dsi0
pipline.
Such code could also be shared with HDMI and DP implementations. They would
just have a different array (or in some cases the same) of pipline members.
> + if (dsi->data->has_color_pipeline) {
> + ret = mtk_disp_comp_enable(dsi->color);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable color: %d\n", ret);
> + goto err_disable_rdma;
> + }
> +
> + ret = mtk_disp_comp_enable(dsi->ccorr);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable ccorr: %d\n", ret);
> + goto err_disable_color;
> + }
> +
> + ret = mtk_disp_comp_enable(dsi->aal);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable aal: %d\n", ret);
> + goto err_disable_ccorr;
> + }
> +
> + ret = mtk_disp_comp_enable(dsi->gamma);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable gamma: %d\n", ret);
> + goto err_disable_aal;
> + }
> +
> + ret = mtk_disp_comp_enable(dsi->dither);
> + if (ret) {
> + dev_err(dsi->dev, "failed to enable dither: %d\n", ret);
> + goto err_disable_gamma;
> + }
> + }
> +
> + mtk_dsi_enable(dsi);
> +
> + if (dsi->data->shadow_dbg_off)
> + mtk_dsi_shadow_debug(dsi);
> +
> + mtk_dsi_reset_engine(dsi);
> +
> + mtk_dsi_phy_timconfig(dsi);
> +
> + mtk_dsi_set_vm_cmd(dsi);
> +
> + mtk_dsi_set_interrupt_enable(dsi);
> +
> + return 0;
> +
...
> +static int mtk_dsi_probe(struct udevice *dev)
> +{
> + struct mtk_dsi_priv *dsi = dev_get_priv(dev);
> + struct video_uc_plat *plat = dev_get_uclass_plat(dev);
> + struct display_timing timing;
> + struct udevice *mmsys_dev;
> + struct regmap *regmap;
> + int ret;
> +
> + /* Before relocation we don't need to do anything */
> + if (!(gd->flags & GD_FLG_RELOC))
> + return 0;
> +
> + dsi->dev = dev;
> + dsi->data = (const struct mtk_dsi_data *)dev_get_driver_data(dev);
> + if (!dsi->data) {
> + dev_err(dev, "missing dsi match data\n");
> + return -EINVAL;
> + }
> +
> + dsi->base = dev_remap_addr(dev);
> + if (!dsi->base)
> + return -EINVAL;
> +
> + dsi->engine_clk = devm_clk_get(dev, "engine");
> + if (IS_ERR(dsi->engine_clk))
> + return PTR_ERR(dsi->engine_clk);
> +
> + /*
> + * The DSI host's "hs" clock is the MIPI TX PLL. U-Boot's MIPI TX driver
> + * does not register it as a clock, so it is not fetched here; its rate
> + * is set through generic_phy_configure() instead.
> + */
> + dsi->digital_clk = devm_clk_get(dev, "digital");
> + if (IS_ERR(dsi->digital_clk))
> + return PTR_ERR(dsi->digital_clk);
> +
> + ret = uclass_get_device_by_of_path(UCLASS_MISC, "ovl0", &dsi->ovl);
> + if (ret) {
> + dev_err(dev, "cannot get ovl device: %s\n", errno_str(ret));
> + return ret;
> + }
> +
> + ret = uclass_get_device_by_of_path(UCLASS_MISC, "rdma0", &dsi->rdma);
> + if (ret) {
> + dev_err(dev, "cannot get rdma device: %s\n", errno_str(ret));
> + return ret;
> + }
> +
> + if (dsi->data->has_color_pipeline) {
Obviosuly, more pipline stuff here...
> + ret = uclass_get_device_by_of_path(UCLASS_MISC, "color0",
> &dsi->color);
> + if (ret) {
> + dev_err(dev, "cannot get color device: %s\n",
> errno_str(ret));
> + return ret;
> + }
> +
> + ret = uclass_get_device_by_of_path(UCLASS_MISC, "ccorr0",
> &dsi->ccorr);
> + if (ret) {
> + dev_err(dev, "cannot get ccorr device: %s\n",
> errno_str(ret));
> + return ret;
> + }
> +
> + ret = uclass_get_device_by_of_path(UCLASS_MISC, "aal0",
> &dsi->aal);
> + if (ret) {
> + dev_err(dev, "cannot get aal device: %s\n",
> errno_str(ret));
> + return ret;
> + }
> +
> + ret = uclass_get_device_by_of_path(UCLASS_MISC, "gamma0",
> &dsi->gamma);
> + if (ret) {
> + dev_err(dev, "cannot get gamma device: %s\n",
> errno_str(ret));
> + return ret;
> + }
> +
> + ret = uclass_get_device_by_of_path(UCLASS_MISC, "dither0",
> &dsi->dither);
> + if (ret) {
> + dev_err(dev, "cannot get dither device: %s\n",
> errno_str(ret));
> + return ret;
> + }
> + }
> +
...
> + mtk_dsi_set_display_mode(dsi, &timing);
> +
> + mtk_dsi_lane_ready(dsi);
> +
> + ret = mtk_ovl_layer(dsi->ovl, plat, &timing);
> + if (ret)
> + goto err_power_off;
> +
> + mmu_set_region_dcache_behaviour(plat->base, ALIGN(plat->size,
> MMU_SECTION_SIZE),
> + DCACHE_WRITEBACK);
Do we actually need mmu_set_region_dcache_behaviour()? AFAIKT, this
DCACHE_WRITEBACK attribte is already set by default since we are using
PTE_BLOCK_MEMTYPE(MT_NORMAL) for the whole DRAM.
I think the ALIGN() would cause it to touch memory we didn't allocate too.
> + video_set_flush_dcache(dev, true);
> +
> + ret = panel_set_backlight(dsi->panel, BACKLIGHT_DEFAULT);
> + if (ret && ret != -ENOSYS) {
> + dev_err(dev, "failed to set backlight\n");
> + goto err_power_off;
> + }
> +
> + ret = panel_enable_backlight(dsi->panel);
> + if (ret) {
> + dev_err(dev, "failed to enable backlight\n");
> + goto err_power_off;
> + }
> +
> + mtk_dsi_output_enable(dsi);
> +
> + return 0;
> +
...
> +U_BOOT_DRIVER(mtk_dsi) = {
> + .name = "mtk_dsi",
> + .id = UCLASS_VIDEO,
> + .of_match = mtk_dsi_ids,
> + .probe = mtk_dsi_probe,
> + .bind = mtk_dsi_bind,
> + .priv_auto = sizeof(struct mtk_dsi_priv),
> + .flags = DM_FLAG_PRE_RELOC | DM_FLAG_LEAVE_PD_ON,
I think we should swap DM_FLAG_LEAVE_PD_ON for DM_FLAG_OS_PREPARE and add a
.remove() callback that shuts everything down. AFAICT, there is nothing in
Linux that knows how to properly take over from U-Boot here. And when I run
this on Genio 360, when Linux starts, the screen gets corrupted and slowly
fades to black. With a proper .remove() callback, then Linux works as it
should.
We would need to fix Linux first in order to make this actually work as-is.
> +};
>