The dither block is the last of the colour blocks of the video pipeline
on some MediaTek SoCs, such as the MT8365. It reduces the pixel depth of
the stream down to the bit depth of the panel and spreads the resulting
quantization error over the neighbouring pixels.

The pipeline carries 10 bits per component and U-Boot always drives the
panel with 8, so the driver programs the block as Linux does for an
8 bpc output: the frame size, then the DITHER_5/7/15/16 settings of
mtk_dither_set_common() before enabling the engine.

The driver matches "mediatek,mt8183-disp-dither", the generic fallback
the MT8365 dither0 node carries behind "mediatek,mt8365-disp-dither", so
a single entry covers every SoC reusing that block.

Signed-off-by: Julien Stephan <[email protected]>
---
 drivers/video/mediatek/mtk_disp_dither.c | 89 ++++++++++++++++++++++++++++++++
 drivers/video/mediatek/mtk_disp_dither.h | 32 ++++++++++++
 2 files changed, 121 insertions(+)

diff --git a/drivers/video/mediatek/mtk_disp_dither.c 
b/drivers/video/mediatek/mtk_disp_dither.c
new file mode 100644
index 00000000000..51ed69df011
--- /dev/null
+++ b/drivers/video/mediatek/mtk_disp_dither.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MediaTek Video Dither support
+ *
+ * Copyright (c) 2026 BayLibre, SAS.
+ * Author: Julien Masson <[email protected]>
+ *         Julien Stephan <[email protected]>
+ */
+
+#include <dm/device.h>
+#include <fdtdec.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+
+#include "mtk_disp_comp.h"
+#include "mtk_disp_dither.h"
+
+#define DISP_REG_DITHER_EN     0x00
+#define DITHER_EN                      BIT(0)
+
+#define DISP_REG_DITHER_CFG    0x20
+#define DITHER_RELAY_MODE              BIT(0)
+#define DITHER_ENGINE_EN               BIT(1)
+
+#define DISP_REG_DITHER_SIZE   0x30
+#define DISP_REG_DITHER_SIZE_HSIZE     GENMASK(28, 16)
+#define DISP_REG_DITHER_SIZE_VSIZE     GENMASK(12, 0)
+
+#define DISP_REG_DITHER_5      0x114
+#define DISP_REG_DITHER_7      0x11c
+
+#define DISP_REG_DITHER_15     0x13c
+#define DITHER_LSB_ERR_SHIFT_R         GENMASK(30, 28)
+#define DITHER_ADD_LSHIFT_R            GENMASK(22, 20)
+#define DITHER_NEW_BIT_MODE            BIT(0)
+
+#define DISP_REG_DITHER_16     0x140
+#define DITHER_LSB_ERR_SHIFT_B         GENMASK(30, 28)
+#define DITHER_ADD_LSHIFT_B            GENMASK(22, 20)
+#define DITHER_LSB_ERR_SHIFT_G         GENMASK(14, 12)
+#define DITHER_ADD_LSHIFT_G            GENMASK(6, 4)
+
+/* the pipeline carries 10 bits per component, the panel takes 8 */
+#define MTK_MAX_BPC                    10
+#define MTK_DITHER_BPC                 8
+#define MTK_DITHER_SHIFT               (MTK_MAX_BPC - MTK_DITHER_BPC)
+
+void mtk_dither_config(struct udevice *dev, const struct display_timing 
*timing)
+{
+       u32 sz;
+
+       sz = FIELD_PREP(DISP_REG_DITHER_SIZE_HSIZE, timing->hactive.typ) |
+            FIELD_PREP(DISP_REG_DITHER_SIZE_VSIZE, timing->vactive.typ);
+
+       mtk_disp_comp_write(dev, DISP_REG_DITHER_SIZE, sz);
+       mtk_disp_comp_write(dev, DISP_REG_DITHER_CFG, DITHER_RELAY_MODE);
+
+       /* as mtk_dither_set_common() in Linux */
+       mtk_disp_comp_write(dev, DISP_REG_DITHER_5, 0);
+       mtk_disp_comp_write(dev, DISP_REG_DITHER_7, 0);
+       mtk_disp_comp_write(dev, DISP_REG_DITHER_15,
+                           FIELD_PREP(DITHER_LSB_ERR_SHIFT_R, 
MTK_DITHER_SHIFT) |
+                           FIELD_PREP(DITHER_ADD_LSHIFT_R, MTK_DITHER_SHIFT) |
+                           DITHER_NEW_BIT_MODE);
+       mtk_disp_comp_write(dev, DISP_REG_DITHER_16,
+                           FIELD_PREP(DITHER_LSB_ERR_SHIFT_B, 
MTK_DITHER_SHIFT) |
+                           FIELD_PREP(DITHER_ADD_LSHIFT_B, MTK_DITHER_SHIFT) |
+                           FIELD_PREP(DITHER_LSB_ERR_SHIFT_G, 
MTK_DITHER_SHIFT) |
+                           FIELD_PREP(DITHER_ADD_LSHIFT_G, MTK_DITHER_SHIFT));
+       mtk_disp_comp_write(dev, DISP_REG_DITHER_CFG, DITHER_ENGINE_EN);
+}
+
+void mtk_dither_start(struct udevice *dev)
+{
+       mtk_disp_comp_write(dev, DISP_REG_DITHER_EN, DITHER_EN);
+}
+
+static const struct udevice_id mtk_disp_dither_ids[] = {
+       { .compatible = "mediatek,mt8183-disp-dither" },
+       { }
+};
+
+U_BOOT_DRIVER(mtk_disp_dither) = {
+       .name      = "mtk_disp_dither",
+       .id        = UCLASS_MISC,
+       .of_match  = mtk_disp_dither_ids,
+       .probe     = mtk_disp_comp_probe,
+       .priv_auto = sizeof(struct mtk_disp_comp_priv),
+};
diff --git a/drivers/video/mediatek/mtk_disp_dither.h 
b/drivers/video/mediatek/mtk_disp_dither.h
new file mode 100644
index 00000000000..019e6c954bb
--- /dev/null
+++ b/drivers/video/mediatek/mtk_disp_dither.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026 BayLibre, SAS.
+ * Author: Julien Masson <[email protected]>
+ *         Julien Stephan <[email protected]>
+ */
+
+#ifndef _MTK_DISP_DITHER_H
+#define _MTK_DISP_DITHER_H
+
+struct display_timing;
+struct udevice;
+
+/**
+ * mtk_dither_config() - configure the dither block
+ * @dev: dither device
+ * @timing: display timing, giving the frame size
+ *
+ * Set the frame size of the dither block, and dither its 10-bit input down
+ * to 8 bits per component.
+ */
+void mtk_dither_config(struct udevice *dev, const struct display_timing 
*timing);
+
+/**
+ * mtk_dither_start() - start the dither block
+ * @dev: dither device
+ *
+ * Start the dither block.
+ */
+void mtk_dither_start(struct udevice *dev);
+
+#endif /* _MTK_DISP_DITHER_H */

-- 
2.55.0

Reply via email to