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 <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>
This commit is contained in:
SriNavmani A
2026-05-04 09:38:33 +08:00
committed by Vinod Koul
parent 1a75ecefa4
commit 9e7dfa4bcd
5 changed files with 231 additions and 0 deletions

View 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"

View File

@@ -20,6 +20,7 @@ obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
obj-$(CONFIG_GENERIC_PHY) += allwinner/ \
amlogic/ \
apple/ \
axiado/ \
broadcom/ \
cadence/ \
canaan/ \

View File

@@ -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.

View File

@@ -0,0 +1 @@
obj-$(CONFIG_PHY_AX3000_EMMC) += phy-axiado-emmc.o

View File

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