]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
phy: axiado: add Axiado eMMC PHY driver
authorSriNavmani A <srinavmani@axiado.com>
Mon, 4 May 2026 01:38:33 +0000 (09:38 +0800)
committerVinod Koul <vkoul@kernel.org>
Sun, 10 May 2026 10:59:55 +0000 (16:29 +0530)
It provides the required configurations for Axiado eMMC PHY driver for
HS200 mode.

Signed-off-by: SriNavmani A <srinavmani@axiado.com>
Co-developed-by: Prasad Bolisetty <pbolisetty@axiado.com>
Signed-off-by: Prasad Bolisetty <pbolisetty@axiado.com>
Signed-off-by: Tzu-Hao Wei <twei@axiado.com>
Link: https://patch.msgid.link/20260504-axiado-ax3000-add-emmc-phy-driver-support-v3-2-3ab7eb45b0c5@axiado.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/phy/Kconfig
drivers/phy/Makefile
drivers/phy/axiado/Kconfig [new file with mode: 0644]
drivers/phy/axiado/Makefile [new file with mode: 0644]
drivers/phy/axiado/phy-axiado-emmc.c [new file with mode: 0644]

index 227b9a4c612e8c7a2ba7374e5ffcd9d9cb22810a..872a7f02e1c4d23ec7921f6a27ba1170cdf3fa8e 100644 (file)
@@ -136,6 +136,7 @@ config PHY_XGENE
 source "drivers/phy/allwinner/Kconfig"
 source "drivers/phy/amlogic/Kconfig"
 source "drivers/phy/apple/Kconfig"
+source "drivers/phy/axiado/Kconfig"
 source "drivers/phy/broadcom/Kconfig"
 source "drivers/phy/cadence/Kconfig"
 source "drivers/phy/canaan/Kconfig"
index f49d83f00a3d877df998c596fca68ea653ee7a0d..ae756fea473b70b1217c6b51c9fa132a3ec0e097 100644 (file)
@@ -20,6 +20,7 @@ obj-$(CONFIG_PHY_XGENE)                       += phy-xgene.o
 obj-$(CONFIG_GENERIC_PHY)              += allwinner/   \
                                           amlogic/     \
                                           apple/       \
+                                          axiado/      \
                                           broadcom/    \
                                           cadence/     \
                                           canaan/      \
diff --git a/drivers/phy/axiado/Kconfig b/drivers/phy/axiado/Kconfig
new file mode 100644 (file)
index 0000000..d159e03
--- /dev/null
@@ -0,0 +1,11 @@
+#
+# PHY drivers for Axiado platforms
+#
+
+config PHY_AX3000_EMMC
+       tristate "Axiado eMMC PHY driver"
+       depends on OF && (ARCH_AXIADO || COMPILE_TEST)
+       select GENERIC_PHY
+       help
+         Enables this to support for the AX3000 EMMC PHY driver.
+         If unsure, say N.
diff --git a/drivers/phy/axiado/Makefile b/drivers/phy/axiado/Makefile
new file mode 100644 (file)
index 0000000..1e2b1ba
--- /dev/null
@@ -0,0 +1 @@
+obj-$(CONFIG_PHY_AX3000_EMMC)          += phy-axiado-emmc.o
diff --git a/drivers/phy/axiado/phy-axiado-emmc.c b/drivers/phy/axiado/phy-axiado-emmc.c
new file mode 100644 (file)
index 0000000..e0e2174
--- /dev/null
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Axiado eMMC PHY driver
+ *
+ * Copyright (C) 2017 Arasan Chip Systems Inc.
+ * Copyright (C) 2022-2026 Axiado Corporation (or its affiliates).
+ *
+ * Based on Arasan Driver (sdhci-pci-arasan.c)
+ * sdhci-pci-arasan.c - Driver for Arasan PCI Controller with integrated phy.
+ */
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* Arasan eMMC 5.1 - PHY configuration registers */
+#define CAP_REG_IN_S1_MSB              0x04
+#define PHY_CTRL_1                     0x38
+#define PHY_CTRL_2                     0x3c
+#define PHY_CTRL_3                     0x40
+#define STATUS                         0x50
+
+#define DLL_ENBL       BIT(26)
+#define RTRIM_EN       BIT(21)
+#define PDB_ENBL       BIT(23)
+#define RETB_ENBL      BIT(1)
+
+#define REN_STRB       BIT(27)
+#define REN_CMD_EN     GENMASK(20, 12)
+
+/* Pull-UP Enable on CMD Line */
+#define PU_CMD_EN      GENMASK(11, 3)
+
+/* Selection value for the optimum delay from 1-32 output tap lines */
+#define OTAP_DLY       0x02
+/* DLL charge pump current trim default [1000] */
+#define DLL_TRM_ICP    0x08
+/* Select the frequency range of DLL Operation */
+#define FRQ_SEL        0x01
+
+#define OTAP_SEL_MASK          GENMASK(10, 7)
+#define DLL_TRM_MASK           GENMASK(25, 22)
+#define DLL_FRQSEL_MASK                GENMASK(27, 25)
+
+#define OTAP_SEL(x)            (FIELD_PREP(OTAP_SEL_MASK, x) | OTAPDLY_EN)
+#define DLL_TRM(x)             (FIELD_PREP(DLL_TRM_MASK, x) | DLL_ENBL)
+#define DLL_FRQSEL(x)  FIELD_PREP(DLL_FRQSEL_MASK, x)
+
+#define OTAPDLY_EN     BIT(11)
+
+#define SEL_DLY_RXCLK  BIT(18)
+#define SEL_DLY_TXCLK  BIT(19)
+
+#define CALDONE_MASK   0x40
+#define DLL_RDY_MASK   0x1
+#define MAX_CLK_BUF0   BIT(20)
+#define MAX_CLK_BUF1   BIT(21)
+#define MAX_CLK_BUF2   BIT(22)
+
+#define CLK_MULTIPLIER 0xc008e
+#define POLL_TIMEOUT_MS        3000
+#define POLL_DELAY_US  100
+
+struct axiado_emmc_phy {
+       void __iomem *reg_base;
+       struct device *dev;
+};
+
+static int axiado_emmc_phy_init(struct phy *phy)
+{
+       struct axiado_emmc_phy *ax_phy = phy_get_drvdata(phy);
+       struct device *dev = ax_phy->dev;
+       u32 val;
+       int ret;
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_1);
+       writel(val | RETB_ENBL | RTRIM_EN, ax_phy->reg_base + PHY_CTRL_1);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_3);
+       writel(val | PDB_ENBL, ax_phy->reg_base + PHY_CTRL_3);
+
+       ret = readl_poll_timeout(ax_phy->reg_base + STATUS, val,
+                                val & CALDONE_MASK, POLL_DELAY_US,
+                                POLL_TIMEOUT_MS * 1000);
+       if (ret) {
+               dev_err(dev, "PHY calibration timeout\n");
+               return ret;
+       }
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_1);
+       writel(val | REN_CMD_EN | PU_CMD_EN, ax_phy->reg_base + PHY_CTRL_1);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_2);
+       writel(val | REN_STRB, ax_phy->reg_base + PHY_CTRL_2);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_3);
+       writel(val | MAX_CLK_BUF0 | MAX_CLK_BUF1 | MAX_CLK_BUF2,
+              ax_phy->reg_base + PHY_CTRL_3);
+
+       writel(CLK_MULTIPLIER, ax_phy->reg_base + CAP_REG_IN_S1_MSB);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_3);
+       writel(val | SEL_DLY_RXCLK | SEL_DLY_TXCLK,
+              ax_phy->reg_base + PHY_CTRL_3);
+
+       return 0;
+}
+
+static int axiado_emmc_phy_power_on(struct phy *phy)
+{
+       struct axiado_emmc_phy *ax_phy = phy_get_drvdata(phy);
+       struct device *dev = ax_phy->dev;
+       u32 val;
+       int ret;
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_1);
+       writel(val | RETB_ENBL, ax_phy->reg_base + PHY_CTRL_1);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_3);
+       writel(val | PDB_ENBL, ax_phy->reg_base + PHY_CTRL_3);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_2);
+       writel(val | OTAP_SEL(OTAP_DLY), ax_phy->reg_base + PHY_CTRL_2);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_1);
+       writel(val | DLL_TRM(DLL_TRM_ICP), ax_phy->reg_base + PHY_CTRL_1);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_3);
+       writel(val | DLL_FRQSEL(FRQ_SEL), ax_phy->reg_base + PHY_CTRL_3);
+
+       ret = read_poll_timeout(readl, val, val & DLL_RDY_MASK, POLL_DELAY_US,
+                               POLL_TIMEOUT_MS * 1000, false,
+                               ax_phy->reg_base + STATUS);
+       if (ret) {
+               dev_err(dev, "DLL ready timeout\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int axiado_emmc_phy_power_off(struct phy *phy)
+{
+       struct axiado_emmc_phy *ax_phy = phy_get_drvdata(phy);
+       u32 val;
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_1);
+       val &= ~(DLL_TRM_MASK | DLL_ENBL);
+       writel(val, ax_phy->reg_base + PHY_CTRL_1);
+
+       val = readl(ax_phy->reg_base + PHY_CTRL_3);
+       val &= ~(DLL_FRQSEL_MASK | PDB_ENBL);
+       writel(val, ax_phy->reg_base + PHY_CTRL_3);
+
+       return 0;
+}
+
+static const struct phy_ops axiado_emmc_phy_ops = {
+       .init = axiado_emmc_phy_init,
+       .power_on = axiado_emmc_phy_power_on,
+       .power_off = axiado_emmc_phy_power_off,
+       .owner = THIS_MODULE,
+};
+
+static const struct of_device_id axiado_emmc_phy_of_match[] = {
+       { .compatible = "axiado,ax3000-emmc-phy" },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, axiado_emmc_phy_of_match);
+
+static int axiado_emmc_phy_probe(struct platform_device *pdev)
+{
+       struct axiado_emmc_phy *ax_phy;
+       struct phy_provider *phy_provider;
+       struct device *dev = &pdev->dev;
+       struct phy *generic_phy;
+
+       if (!dev->of_node)
+               return -ENODEV;
+
+       ax_phy = devm_kzalloc(dev, sizeof(*ax_phy), GFP_KERNEL);
+       if (!ax_phy)
+               return -ENOMEM;
+
+       ax_phy->reg_base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(ax_phy->reg_base))
+               return PTR_ERR(ax_phy->reg_base);
+
+       ax_phy->dev = dev;
+
+       generic_phy = devm_phy_create(dev, dev->of_node, &axiado_emmc_phy_ops);
+       if (IS_ERR(generic_phy))
+               return dev_err_probe(dev, PTR_ERR(generic_phy),
+                                    "failed to create PHY\n");
+
+       phy_set_drvdata(generic_phy, ax_phy);
+       phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+       return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver axiado_emmc_phy_driver = {
+       .probe = axiado_emmc_phy_probe,
+       .driver = {
+               .name = "axiado-emmc-phy",
+               .of_match_table = axiado_emmc_phy_of_match,
+       },
+};
+module_platform_driver(axiado_emmc_phy_driver);
+
+MODULE_DESCRIPTION("AX3000 eMMC PHY Driver");
+MODULE_AUTHOR("Axiado Corporation");
+MODULE_LICENSE("GPL");