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