Hi,
Le 11/10/2025 à 21:10, Igor Belwon a écrit :
This UFS M-PHY driver can be used on recent MediaTek SoCs as the
primary PHY for the UFS controller.
Signed-off-by: Igor Belwon <[email protected]>
---
drivers/phy/Kconfig | 10 +++
drivers/phy/Makefile | 1 +
drivers/phy/phy-mtk-ufs.c | 190 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 201 insertions(+)
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index
d36a5f00ef83e3bd6f216181df1a70c848bb11d4..46d02931f3534a1c396e79ab26ed1e03f86398ec
100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -281,6 +281,16 @@ config PHY_MTK_TPHY
multi-ports is first version, otherwise is second veriosn,
so you can easily distinguish them by banks layout.
+config PHY_MTK_UFS
+ tristate "MediaTek UFS M-PHY driver"
+ depends on ARCH_MEDIATEK
+ depends on PHY
+ help
+ Support for UFS M-PHY on MediaTek chipsets.
+ Enable this to provide vendor-specific probing,
+ initialization, power on and power off flow of
+ specified M-PHYs.
+
config PHY_NPCM_USB
bool "Nuvoton NPCM USB PHY support"
depends on PHY
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index
98c1ef8683b7354ccb43426f32ba4dd2f2fcf6c6..8c1bcf72908135cf840d88caeee72bfd4b9270c5
100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_MT76X8_USB_PHY) += mt76x8-usb-phy.o
obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o
obj-$(CONFIG_PHY_EXYNOS_USBDRD) += phy-exynos-usbdrd.o
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
+obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o
obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o
obj-$(CONFIG_PHY_IMX8MQ_USB) += phy-imx8mq-usb.o
obj-$(CONFIG_PHY_IMX8M_PCIE) += phy-imx8m-pcie.o
diff --git a/drivers/phy/phy-mtk-ufs.c b/drivers/phy/phy-mtk-ufs.c
new file mode 100644
index
0000000000000000000000000000000000000000..1eda3df858d820fd6f080cac9d58492ce98f19ee
--- /dev/null
+++ b/drivers/phy/phy-mtk-ufs.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 MediaTek Inc.
+ * Author: Stanley Chu <[email protected]>
+ *
+ * Copyright (c) 2025, Igor Belwon <[email protected]>
+ */
+
+#include "dm/ofnode.h"
+#include "dm/read.h"
+#include <clk.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+
+#include <dt-bindings/phy/phy.h>
+
+/* mphy register and offsets */
+#define MP_GLB_DIG_8C 0x008C
+#define FRC_PLL_ISO_EN BIT(8)
+#define PLL_ISO_EN BIT(9)
+#define FRC_FRC_PWR_ON BIT(10)
+#define PLL_PWR_ON BIT(11)
+
+#define MP_LN_DIG_RX_9C 0xA09C
+#define FSM_DIFZ_FRC BIT(18)
+
+#define MP_LN_DIG_RX_AC 0xA0AC
+#define FRC_RX_SQ_EN BIT(0)
+#define RX_SQ_EN BIT(1)
+
+#define MP_LN_RX_44 0xB044
+#define FRC_CDR_PWR_ON BIT(17)
+#define CDR_PWR_ON BIT(18)
+#define FRC_CDR_ISO_EN BIT(19)
+#define CDR_ISO_EN BIT(20)
+
+#define UFSPHY_CLKS_CNT 2
+
+struct mtk_ufs_phy {
+ struct udevice *dev;
+ void __iomem *mmio;
+
+ struct clk *unipro_clk;
+ struct clk *mp_clk;
+};
+
+static void ufs_mtk_phy_set_active(struct mtk_ufs_phy *phy)
+{
+ /* release DA_MP_PLL_PWR_ON */
+ setbits_le32(phy->mmio + MP_GLB_DIG_8C, PLL_PWR_ON);
+ clrbits_le32(phy->mmio + MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
+
+ /* release DA_MP_PLL_ISO_EN */
+ clrbits_le32(phy->mmio + MP_GLB_DIG_8C, PLL_ISO_EN);
+ clrbits_le32(phy->mmio + MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
+
+ /* release DA_MP_CDR_PWR_ON */
+ setbits_le32(phy->mmio + MP_LN_RX_44, CDR_PWR_ON);
+ clrbits_le32(phy->mmio + MP_LN_RX_44, FRC_CDR_PWR_ON);
+
+ /* release DA_MP_CDR_ISO_EN */
+ clrbits_le32(phy->mmio + MP_LN_RX_44, CDR_ISO_EN);
+ clrbits_le32(phy->mmio + MP_LN_RX_44, FRC_CDR_ISO_EN);
+
+ /* release DA_MP_RX0_SQ_EN */
+ setbits_le32(phy->mmio + MP_LN_DIG_RX_AC, RX_SQ_EN);
+ clrbits_le32(phy->mmio + MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
+
+ /* delay 1us to wait DIFZ stable */
+ udelay(1);
+
+ /* release DIFZ */
+ clrbits_le32(phy->mmio + MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
+}
+
+static int mtk_phy_power_on(struct phy *phy)
+{
+ struct mtk_ufs_phy *ufs_phy = dev_get_priv(phy->dev);
+ int ret;
+
+ ret = clk_enable(ufs_phy->mp_clk);
+ if (ret < 0) {
+ dev_err(phy->dev, "failed to enable mp_clk\n");
+ return ret;
+ }
+
+ ret = clk_enable(ufs_phy->unipro_clk);
+ if (ret < 0) {
+ dev_err(phy->dev, "failed to enable unipro_clk %d\n", ret);
+ clk_disable(ufs_phy->unipro_clk);
+ return ret;
+ }
+
+ ufs_mtk_phy_set_active(ufs_phy);
+
+ return 0;
+}
+
+static int mtk_phy_power_off(struct phy *phy)
+{
+ struct mtk_ufs_phy *ufs_phy = dev_get_priv(phy->dev);
+
+ /* Set PHY to Deep Hibernate mode */
+ setbits_le32(ufs_phy->mmio + MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
+
+ /* force DA_MP_RX0_SQ_EN */
+ setbits_le32(ufs_phy->mmio + MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
+ clrbits_le32(ufs_phy->mmio + MP_LN_DIG_RX_AC, RX_SQ_EN);
+
+ /* force DA_MP_CDR_ISO_EN */
+ setbits_le32(ufs_phy->mmio + MP_LN_RX_44, FRC_CDR_ISO_EN);
+ setbits_le32(ufs_phy->mmio + MP_LN_RX_44, CDR_ISO_EN);
+
+ /* force DA_MP_CDR_PWR_ON */
+ setbits_le32(ufs_phy->mmio + MP_LN_RX_44, FRC_CDR_PWR_ON);
+ clrbits_le32(ufs_phy->mmio + MP_LN_RX_44, CDR_PWR_ON);
+
+ /* force DA_MP_PLL_ISO_EN */
+ setbits_le32(ufs_phy->mmio + MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
+ setbits_le32(ufs_phy->mmio + MP_GLB_DIG_8C, PLL_ISO_EN);
+
+ /* force DA_MP_PLL_PWR_ON */
+ setbits_le32(ufs_phy->mmio + MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
+ clrbits_le32(ufs_phy->mmio + MP_GLB_DIG_8C, PLL_PWR_ON);
+
+ return 0;
+}
+
+static const struct phy_ops mtk_ufs_phy_ops = {
+ .power_on = mtk_phy_power_on,
+ .power_off = mtk_phy_power_off,
+};
+
+static int mtk_ufs_phy_probe(struct udevice *dev)
+{
+ struct mtk_ufs_phy *phy = dev_get_priv(dev);
+ fdt_addr_t addr;
+ int ret;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
The above devm_kzalloc() should be dropped, as it overwrites "phy",
which is already allocated by the priv_auto mechanism.
This eventually causes all sorts of error as you then fill in this newly
allocated structure, but all other functions use the priv pointer, which
contains only NULL values.
Best regards,
Arnaud
+
+ addr = dev_read_addr(dev);
+ if (addr == FDT_ADDR_T_NONE)
+ return -ENOMEM;
+
+ phy->dev = dev;
+ phy->mmio = map_sysmem(addr, 0);
+
+ phy->mp_clk = devm_clk_get(dev, "mp");
+ if (IS_ERR(phy->mp_clk)) {
+ ret = PTR_ERR(phy->mp_clk);
+ dev_err(dev, "Failed to get mp clock (ret=%d)\n", ret);
+ return ret;
+ }
+
+ phy->unipro_clk = devm_clk_get(dev, "unipro");
+ if (IS_ERR(phy->unipro_clk)) {
+ ret = PTR_ERR(phy->unipro_clk);
+ dev_err(dev, "Failed to get unipro clock (ret=%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id mtk_ufs_phy_id_table[] = {
+ {.compatible = "mediatek,mt8183-ufsphy"},
+ {},
+};
+
+U_BOOT_DRIVER(mtk_ufs_phy) = {
+ .name = "mtk-ufs_phy",
+ .id = UCLASS_PHY,
+ .of_match = mtk_ufs_phy_id_table,
+ .ops = &mtk_ufs_phy_ops,
+ .probe = mtk_ufs_phy_probe,
+ .priv_auto = sizeof(struct mtk_ufs_phy),
+};