// 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");