Add a new driver for the MIPI DSI PHY. The driver supports MT8183
compatible SoCs. The driver is base on the corresponding kernel driver
commit 2ec9c210c1f9 ("phy: mediatek: Add support for MT8196 MIPI DSI PHY")

Few notes about the port from kernel:
- tested on Genio-350 which does not have nvmem-cells, so keep rt_code[]
  zeroed and fallback to 0x210 has kernel does when no cell available.
  Can be implemented later.
- PLL is not registered as a clock provider:
    - configure() = determine_rate+set_rate
    - power_on() = clk_prepare_enable
  configure() takes a struct phy_configure_opts_mipi_dphy and uses its
  hs_clk_rate member, which carries the per-lane data rate the kernel
  would have set on the "hs" clock.

Signed-off-by: Julien Stephan <[email protected]>
---
 drivers/phy/mediatek/Kconfig                   |  11 ++
 drivers/phy/mediatek/Makefile                  |   1 +
 drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c | 175 +++++++++++++++++++++++++
 drivers/phy/mediatek/phy-mtk-mipi-dsi.c        | 144 ++++++++++++++++++++
 drivers/phy/mediatek/phy-mtk-mipi-dsi.h        |  45 +++++++
 5 files changed, 376 insertions(+)

diff --git a/drivers/phy/mediatek/Kconfig b/drivers/phy/mediatek/Kconfig
index 29a46ea9350..f4049afc43f 100644
--- a/drivers/phy/mediatek/Kconfig
+++ b/drivers/phy/mediatek/Kconfig
@@ -11,6 +11,17 @@ config PHY_MTK_HDMI
          MT8188 and MT8195. The PHY provides the TMDS links and the pixel
          clock PLL to the HDMI TX controller.
 
+config PHY_MTK_MIPI_DSI
+       bool "MediaTek MIPI-DSI PHY Driver"
+       depends on PHY
+       depends on ARCH_MEDIATEK
+       depends on ARM64
+       depends on CLK
+       help
+         Support for the MIPI TX D-PHY found on MediaTek SoCs such as the
+         MT8183 and MT8365. The PHY provides the D-PHY lanes and the byte
+         clock PLL to the DSI host controller.
+
 config PHY_MTK_TPHY
        bool "MediaTek T-PHY Driver"
        depends on PHY
diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile
index 00ab10a238e..6dedec70edc 100644
--- a/drivers/phy/mediatek/Makefile
+++ b/drivers/phy/mediatek/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 obj-$(CONFIG_PHY_MTK_HDMI) += phy-mtk-hdmi.o phy-mtk-hdmi-mt8195.o
+obj-$(CONFIG_PHY_MTK_MIPI_DSI) += phy-mtk-mipi-dsi.o phy-mtk-mipi-dsi-mt8183.o
 obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
 obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o
 obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o
diff --git a/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c 
b/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c
new file mode 100644
index 00000000000..1fce03fcf45
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ * Copyright (c) 2026 BayLibre, SAS
+ * Author: Jitao Shi <[email protected]>
+ */
+
+#include <dm/device_compat.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+
+#include "phy-mtk-io.h"
+#include "phy-mtk-mipi-dsi.h"
+
+#define MIPITX_LANE_CON                0x000c
+#define RG_DSI_BG_LPF_EN               BIT(6)
+#define RG_DSI_BG_CORE_EN              BIT(7)
+#define RG_DSI_PAD_TIEL_SEL            BIT(8)
+
+#define MIPITX_VOLTAGE_SEL     0x0010
+#define RG_DSI_HSTX_LDO_REF_SEL                GENMASK(9, 6)
+
+#define MIPITX_PLL_PWR         0x0028
+#define AD_DSI_PLL_SDM_PWR_ON          BIT(0)
+#define AD_DSI_PLL_SDM_ISO_EN          BIT(1)
+
+#define MIPITX_PLL_CON0                0x002c
+
+#define MIPITX_PLL_CON1                0x0030
+#define RG_DSI_PLL_EN                  BIT(4)
+#define RG_DSI_PLL_POSDIV              GENMASK(10, 8)
+
+#define MIPITX_PLL_CON4                0x003c
+#define RG_DSI_PLL_IBIAS               GENMASK(11, 10)
+
+#define MIPITX_D2P_RTCODE      0x0100
+#define MIPITX_D2_SW_CTL_EN    0x0144
+#define MIPITX_D0_SW_CTL_EN    0x0244
+
+#define MIPITX_CK_CKMODE_EN    0x0328
+#define DSI_CK_CKMODE_EN               BIT(0)
+
+#define MIPITX_CK_SW_CTL_EN    0x0344
+#define MIPITX_D1_SW_CTL_EN    0x0444
+#define MIPITX_D3_SW_CTL_EN    0x0544
+#define DSI_SW_CTL_EN                  BIT(0)
+
+/* Reference clock feeding the PLL, fixed at 26MHz on all supported SoCs. */
+#define MIPITX_PLL_REF_RATE    26000000
+
+#define MIPITX_DATA_RATE_MIN   125000000ULL
+#define MIPITX_DATA_RATE_MAX   1600000000ULL
+
+static u64 mtk_mipi_tx_pll_determine_rate(struct mtk_mipi_tx *mipi_tx, u64 
rate)
+{
+       return clamp_val(rate, MIPITX_DATA_RATE_MIN, MIPITX_DATA_RATE_MAX);
+}
+
+static int mtk_mipi_tx_pll_enable(struct mtk_mipi_tx *mipi_tx)
+{
+       void __iomem *base = mipi_tx->regs;
+       unsigned int txdiv, txdiv0;
+       u64 pcw;
+
+       dev_dbg(mipi_tx->dev, "enable: %llu bps\n", mipi_tx->data_rate);
+
+       if (mipi_tx->data_rate >= 2000000000) {
+               txdiv = 1;
+               txdiv0 = 0;
+       } else if (mipi_tx->data_rate >= 1000000000) {
+               txdiv = 2;
+               txdiv0 = 1;
+       } else if (mipi_tx->data_rate >= 500000000) {
+               txdiv = 4;
+               txdiv0 = 2;
+       } else if (mipi_tx->data_rate > 250000000) {
+               txdiv = 8;
+               txdiv0 = 3;
+       } else if (mipi_tx->data_rate >= 125000000) {
+               txdiv = 16;
+               txdiv0 = 4;
+       } else {
+               return -EINVAL;
+       }
+
+       mtk_phy_clear_bits(base + MIPITX_PLL_CON4, RG_DSI_PLL_IBIAS);
+
+       mtk_phy_set_bits(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON);
+       mtk_phy_clear_bits(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
+       udelay(1);
+       mtk_phy_clear_bits(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN);
+       pcw = div_u64(mipi_tx->data_rate * txdiv << 24, MIPITX_PLL_REF_RATE);
+       writel(pcw, base + MIPITX_PLL_CON0);
+       mtk_phy_update_field(base + MIPITX_PLL_CON1, RG_DSI_PLL_POSDIV, txdiv0);
+       mtk_phy_set_bits(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
+
+       return 0;
+}
+
+static void mtk_mipi_tx_pll_disable(struct mtk_mipi_tx *mipi_tx)
+{
+       void __iomem *base = mipi_tx->regs;
+
+       mtk_phy_clear_bits(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
+
+       mtk_phy_set_bits(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN);
+       mtk_phy_clear_bits(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON);
+}
+
+static void mtk_mipi_tx_config_calibration_data(struct mtk_mipi_tx *mipi_tx)
+{
+       int i, j;
+
+       for (i = 0; i < 5; i++) {
+               if ((mipi_tx->rt_code[i] & 0x1f) == 0)
+                       mipi_tx->rt_code[i] |= 0x10;
+
+               if ((mipi_tx->rt_code[i] >> 5 & 0x1f) == 0)
+                       mipi_tx->rt_code[i] |= 0x10 << 5;
+
+               for (j = 0; j < 10; j++)
+                       mtk_phy_update_bits(mipi_tx->regs +
+                               MIPITX_D2P_RTCODE * (i + 1) + j * 4,
+                               1, mipi_tx->rt_code[i] >> j & 1);
+       }
+}
+
+static void mtk_mipi_tx_power_on_signal(struct mtk_mipi_tx *mipi_tx)
+{
+       void __iomem *base = mipi_tx->regs;
+
+       /* BG_LPF_EN / BG_CORE_EN */
+       writel(RG_DSI_PAD_TIEL_SEL | RG_DSI_BG_CORE_EN, base + MIPITX_LANE_CON);
+       udelay(30);
+       writel(RG_DSI_BG_CORE_EN | RG_DSI_BG_LPF_EN, base + MIPITX_LANE_CON);
+
+       /* Switch OFF each Lane */
+       mtk_phy_clear_bits(base + MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN);
+       mtk_phy_clear_bits(base + MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN);
+       mtk_phy_clear_bits(base + MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN);
+       mtk_phy_clear_bits(base + MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN);
+       mtk_phy_clear_bits(base + MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN);
+
+       mtk_phy_update_field(base + MIPITX_VOLTAGE_SEL, RG_DSI_HSTX_LDO_REF_SEL,
+                            (mipi_tx->mipitx_drive - 3000) / 200);
+
+       mtk_mipi_tx_config_calibration_data(mipi_tx);
+
+       mtk_phy_set_bits(base + MIPITX_CK_CKMODE_EN, DSI_CK_CKMODE_EN);
+}
+
+static void mtk_mipi_tx_power_off_signal(struct mtk_mipi_tx *mipi_tx)
+{
+       void __iomem *base = mipi_tx->regs;
+
+       /* Switch ON each Lane */
+       mtk_phy_set_bits(base + MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN);
+       mtk_phy_set_bits(base + MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN);
+       mtk_phy_set_bits(base + MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN);
+       mtk_phy_set_bits(base + MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN);
+       mtk_phy_set_bits(base + MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN);
+
+       writel(RG_DSI_PAD_TIEL_SEL | RG_DSI_BG_CORE_EN, base + MIPITX_LANE_CON);
+       writel(RG_DSI_PAD_TIEL_SEL, base + MIPITX_LANE_CON);
+}
+
+const struct mtk_mipitx_data mt8183_mipitx_data = {
+       .pll_determine_rate = mtk_mipi_tx_pll_determine_rate,
+       .pll_enable = mtk_mipi_tx_pll_enable,
+       .pll_disable = mtk_mipi_tx_pll_disable,
+       .mipi_tx_enable_signal = mtk_mipi_tx_power_on_signal,
+       .mipi_tx_disable_signal = mtk_mipi_tx_power_off_signal,
+};
diff --git a/drivers/phy/mediatek/phy-mtk-mipi-dsi.c 
b/drivers/phy/mediatek/phy-mtk-mipi-dsi.c
new file mode 100644
index 00000000000..918636a8df1
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-mipi-dsi.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Copyright (c) 2026 BayLibre, SAS
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <generic-phy.h>
+#include <linux/kernel.h>
+#include <phy-mipi-dphy.h>
+
+#include "phy-mtk-mipi-dsi.h"
+
+#define MIPITX_DRIVE_DEFAULT_UA                4600
+#define MIPITX_DRIVE_MIN_UA            3000
+#define MIPITX_DRIVE_MAX_UA            6000
+
+static int mtk_mipi_tx_power_on(struct phy *phy)
+{
+       struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
+       int ret;
+
+       if (!mipi_tx->data_rate) {
+               dev_err(phy->dev, "No data rate set, configure the PHY 
first\n");
+               return -EINVAL;
+       }
+
+       /* Power up core and enable PLL */
+       ret = mipi_tx->driver_data->pll_enable(mipi_tx);
+       if (ret)
+               return ret;
+
+       /* Enable DSI Lane LDO outputs, disable pad tie low */
+       mipi_tx->driver_data->mipi_tx_enable_signal(mipi_tx);
+
+       return 0;
+}
+
+static int mtk_mipi_tx_power_off(struct phy *phy)
+{
+       struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
+
+       /* Enable pad tie low, disable DSI Lane LDO outputs */
+       mipi_tx->driver_data->mipi_tx_disable_signal(mipi_tx);
+
+       /* Disable PLL and power down core */
+       mipi_tx->driver_data->pll_disable(mipi_tx);
+
+       return 0;
+}
+
+/*
+ * Equivalent of the kernel PLL clock's determine_rate and set_rate ops: clamp
+ * the requested data rate to what the PLL can lock to and latch it, the
+ * hardware is programmed when the PHY is powered on.
+ */
+static int mtk_mipi_tx_configure(struct phy *phy, void *params)
+{
+       struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
+       struct phy_configure_opts_mipi_dphy *opts = params;
+
+       if (!opts)
+               return -EINVAL;
+
+       mipi_tx->data_rate =
+               mipi_tx->driver_data->pll_determine_rate(mipi_tx,
+                                                        opts->hs_clk_rate);
+
+       return 0;
+}
+
+static int mtk_mipi_tx_probe(struct udevice *dev)
+{
+       struct mtk_mipi_tx *mipi_tx = dev_get_priv(dev);
+       int ret;
+
+       mipi_tx->dev = dev;
+       mipi_tx->driver_data =
+               (const struct mtk_mipitx_data *)dev_get_driver_data(dev);
+       if (!mipi_tx->driver_data)
+               return -ENODEV;
+
+       mipi_tx->regs = dev_remap_addr(dev);
+       if (!mipi_tx->regs)
+               return -EINVAL;
+
+       ret = clk_get_by_index(dev, 0, &mipi_tx->ref_clk);
+       if (ret) {
+               dev_err(dev, "Failed to get reference clock: %d\n", ret);
+               return ret;
+       }
+
+       ret = clk_enable(&mipi_tx->ref_clk);
+       if (ret) {
+               dev_err(dev, "Failed to enable reference clock: %d\n", ret);
+               return ret;
+       }
+
+       mipi_tx->mipitx_drive = dev_read_u32_default(dev, 
"drive-strength-microamp",
+                                                    MIPITX_DRIVE_DEFAULT_UA);
+       if (mipi_tx->mipitx_drive > MIPITX_DRIVE_MAX_UA ||
+           mipi_tx->mipitx_drive < MIPITX_DRIVE_MIN_UA) {
+               dev_warn(dev, "drive-strength-microamp is invalid %u, not in %u 
~ %u\n",
+                        mipi_tx->mipitx_drive, MIPITX_DRIVE_MIN_UA,
+                        MIPITX_DRIVE_MAX_UA);
+               mipi_tx->mipitx_drive = clamp_val(mipi_tx->mipitx_drive,
+                                                 MIPITX_DRIVE_MIN_UA,
+                                                 MIPITX_DRIVE_MAX_UA);
+       }
+
+       /*
+        * The kernel reads the per-lane impedance trim codes from the
+        * "calibration-data" nvmem cell. No MediaTek board describes that cell
+        * in its device tree yet, so rt_code[] is left zeroed here and the
+        * SoC layer substitutes the same mid-scale default the kernel uses
+        * when the cell is absent.
+        */
+
+       return 0;
+}
+
+static const struct phy_ops mtk_mipi_tx_phy_ops = {
+       .power_on       = mtk_mipi_tx_power_on,
+       .power_off      = mtk_mipi_tx_power_off,
+       .configure      = mtk_mipi_tx_configure,
+};
+
+static const struct udevice_id mtk_mipi_tx_ids[] = {
+       { .compatible = "mediatek,mt8183-mipi-tx",
+         .data = (ulong)&mt8183_mipitx_data,
+       },
+       { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(mtk_mipi_tx) = {
+       .name           = "mtk-mipi-tx",
+       .id             = UCLASS_PHY,
+       .of_match       = mtk_mipi_tx_ids,
+       .ops            = &mtk_mipi_tx_phy_ops,
+       .probe          = mtk_mipi_tx_probe,
+       .priv_auto      = sizeof(struct mtk_mipi_tx),
+};
diff --git a/drivers/phy/mediatek/phy-mtk-mipi-dsi.h 
b/drivers/phy/mediatek/phy-mtk-mipi-dsi.h
new file mode 100644
index 00000000000..96a93e5ba5f
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-mipi-dsi.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ * Copyright (c) 2026 BayLibre, SAS
+ * Author: Jitao Shi <[email protected]>
+ */
+
+#ifndef _PHY_MTK_MIPI_DSI_H
+#define _PHY_MTK_MIPI_DSI_H
+
+#include <clk.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+
+struct mtk_mipi_tx;
+struct udevice;
+
+/*
+ * The Linux driver models the MIPI TX PLL as a clock provider (struct clk_hw /
+ * struct clk_ops) fed to the DSI host as its "hs" clock. U-Boot has no 
consumer
+ * for that clock, so the PLL clk_ops are folded into the callbacks below
+ * (keeping the kernel determine_rate/enable/disable naming) and are driven
+ * directly from the PHY ops.
+ */
+struct mtk_mipitx_data {
+       u64 (*pll_determine_rate)(struct mtk_mipi_tx *mipi_tx, u64 rate);
+       int (*pll_enable)(struct mtk_mipi_tx *mipi_tx);
+       void (*pll_disable)(struct mtk_mipi_tx *mipi_tx);
+       void (*mipi_tx_enable_signal)(struct mtk_mipi_tx *mipi_tx);
+       void (*mipi_tx_disable_signal)(struct mtk_mipi_tx *mipi_tx);
+};
+
+struct mtk_mipi_tx {
+       struct udevice *dev;
+       void __iomem *regs;
+       struct clk ref_clk;
+       u64 data_rate;
+       u32 mipitx_drive;
+       u32 rt_code[5];
+       const struct mtk_mipitx_data *driver_data;
+};
+
+extern const struct mtk_mipitx_data mt8183_mipitx_data;
+
+#endif /* _PHY_MTK_MIPI_DSI_H */

-- 
2.55.0

Reply via email to