From: SriNavmani A Date: Mon, 4 May 2026 01:38:33 +0000 (+0800) Subject: phy: axiado: add Axiado eMMC PHY driver X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=9e7dfa4bcd4e2c3541c4ee954ea5e66edab94d3f;p=thirdparty%2Fkernel%2Flinux.git phy: axiado: add Axiado eMMC PHY driver It provides the required configurations for Axiado eMMC PHY driver for HS200 mode. Signed-off-by: SriNavmani A Co-developed-by: Prasad Bolisetty Signed-off-by: Prasad Bolisetty Signed-off-by: Tzu-Hao Wei Link: https://patch.msgid.link/20260504-axiado-ax3000-add-emmc-phy-driver-support-v3-2-3ab7eb45b0c5@axiado.com Signed-off-by: Vinod Koul --- diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 227b9a4c612e8..872a7f02e1c4d 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -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" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index f49d83f00a3d8..ae756fea473b7 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -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 index 0000000000000..d159e03453459 --- /dev/null +++ b/drivers/phy/axiado/Kconfig @@ -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 index 0000000000000..1e2b1ba016092 --- /dev/null +++ b/drivers/phy/axiado/Makefile @@ -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 index 0000000000000..e0e2174776ad0 --- /dev/null +++ b/drivers/phy/axiado/phy-axiado-emmc.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +/* 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");