summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2026-08-11 12:57:12 -0600
committerTom Rini <trini@konsulko.com>2026-08-11 14:55:40 -0600
commit5a92645e1f9020d24d19e03fff2ae63f30080770 (patch)
treea1e964d3d497b4c386abd64fc02218bd8d0f8d27
parenta9263fc032105731ea87d28ceaf9d09e2c9ee018 (diff)
parentf77229cb1ca1245b9383204d421a2f2f3b233eb5 (diff)
downloadu-boot-5a92645e1f9020d24d19e03fff2ae63f30080770.tar.gz
u-boot-5a92645e1f9020d24d19e03fff2ae63f30080770.zip
Merge patch series "Add eMMC support for Axiado SCM3005 board"
Siu Ming Tong <smtong@axiado.com> says: This series adds eMMC support for the Axiado SCM3005 board, based on the AX3005 SoC, which uses an Arasan SDHCI controller paired with a dedicated eMMC PHY. The series is split as follows: - Patch 1 adds the eMMC PHY driver (PHY_AXIADO_EMMC). The PHY provides signal conditioning and DLL functionality required for high-speed operation with data rates beyond 52MHz, and is based on the Arasan PHY. - Patch 2 adds the SDHCI host controller driver (MMC_SDHCI_AXIADO) for the Arasan controller. It builds on the generic SDHCI framework and adds SoC-specific signal voltage switching, HS200 tuning and eMMC PHY power sequencing through the generic PHY API. - Patch 3 adds the device tree nodes for the SDHCI controller, the eMMC PHY and their supporting fixed clock (clk_xin) to ax3005.dtsi, and enables the SDHCI node in ax3005-scm3005.dts. - Patch 4 enables the new drivers in ax3005_scm3005_defconfig, stores the environment in MMC and bumps CONFIG_SYS_MALLOC_LEN to 0x400000 to accommodate MMC operations. Tested on the Axiado SCM3005 board: the eMMC is detected and enumerated, and read/write access through the MMC subsystem works, including high-speed modes exercised by the PHY tuning path. Link: https://lore.kernel.org/r/20260731-dev-scm3005-v1-0-30bea90ac5f7@axiado.com
-rw-r--r--MAINTAINERS2
-rw-r--r--arch/arm/dts/ax3005-scm3005.dts4
-rw-r--r--arch/arm/dts/ax3005.dtsi25
-rw-r--r--configs/ax3005_scm3005_defconfig10
-rw-r--r--drivers/mmc/Kconfig7
-rw-r--r--drivers/mmc/Makefile1
-rw-r--r--drivers/mmc/axiado_sdhci.c311
-rw-r--r--drivers/phy/Kconfig9
-rw-r--r--drivers/phy/Makefile1
-rw-r--r--drivers/phy/phy-axiado-emmc.c208
10 files changed, 576 insertions, 2 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 00ba119d223..acaba95ed03 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -232,6 +232,8 @@ F: arch/arm/dts/ax3005*
F: arch/arm/mach-axiado/
F: board/axiado/scm3005/
F: configs/ax3005_scm3005_defconfig
+F: drivers/mmc/axiado_sdhci.c
+F: drivers/phy/phy-axiado-emmc.c
F: include/configs/ax3005-scm3005.h
ARM BROADCOM BCM283X / BCM27XX
diff --git a/arch/arm/dts/ax3005-scm3005.dts b/arch/arm/dts/ax3005-scm3005.dts
index b684602176c..3ef60b0b36c 100644
--- a/arch/arm/dts/ax3005-scm3005.dts
+++ b/arch/arm/dts/ax3005-scm3005.dts
@@ -26,3 +26,7 @@
&uart3 {
status = "okay";
};
+
+&sdhci0 {
+ status = "okay";
+};
diff --git a/arch/arm/dts/ax3005.dtsi b/arch/arm/dts/ax3005.dtsi
index 6df2e4a821c..7c84675169e 100644
--- a/arch/arm/dts/ax3005.dtsi
+++ b/arch/arm/dts/ax3005.dtsi
@@ -64,6 +64,12 @@
clock-frequency = <125000000>;
bootph-pre-reloc;
};
+
+ clk_xin: clk_xin {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <200000000>;
+ };
};
soc: soc {
@@ -96,5 +102,24 @@
bootph-pre-reloc;
status = "disabled";
};
+
+ emmc_phy: emmc-phy@33301c00 {
+ compatible = "axiado,ax3005-emmc-phy";
+ reg = <0x00 0x33301c00 0x00 0x100>;
+ #phy-cells = <0>;
+ };
+
+ sdhci0: mmc0@46000000 {
+ compatible = "axiado,ax3005-sdhci";
+ reg = <0x0 0x46000000 0x00 0x1000>;
+ clock-names = "clk_xin";
+ clocks = <&clk_xin>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
+ bus-width = <8>;
+ phys = <&emmc_phy>;
+ phy-names = "phy_arasan";
+ status = "disabled";
+ };
};
};
diff --git a/configs/ax3005_scm3005_defconfig b/configs/ax3005_scm3005_defconfig
index 5dd72d87220..0aa1ec85c36 100644
--- a/configs/ax3005_scm3005_defconfig
+++ b/configs/ax3005_scm3005_defconfig
@@ -5,7 +5,7 @@ CONFIG_TEXT_BASE=0x80000000
CONFIG_SYS_MONITOR_BASE=0x80000000
CONFIG_SYS_LOAD_ADDR=0x80100000
CONFIG_SYS_MALLOC_F_LEN=0x8000
-CONFIG_SYS_MALLOC_LEN=0x20000
+CONFIG_SYS_MALLOC_LEN=0x400000
CONFIG_SYS_BOOTM_LEN=0x20000000
CONFIG_ENV_SIZE=0x10000
CONFIG_ENV_OFFSET=0x5000
@@ -58,12 +58,18 @@ CONFIG_OF_CONTROL=y
CONFIG_OF_SEPARATE=y
CONFIG_DEFAULT_DEVICE_TREE="ax3005-scm3005"
CONFIG_MULTI_DTB_FIT=y
-# CONFIG_ENV_IS_IN_MMC is not set
+CONFIG_ENV_IS_IN_MMC=y
+CONFIG_ENV_OVERWRITE=y
CONFIG_DM_MMC=y
# CONFIG_SUPPORT_EMMC_BOOT is not set
CONFIG_MMC_IO_VOLTAGE=y
CONFIG_MMC_UHS_SUPPORT=y
CONFIG_MMC_HS400_SUPPORT=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_ADMA=y
+CONFIG_MMC_SDHCI_AXIADO=y
+CONFIG_PHY=y
+CONFIG_PHY_AXIADO_EMMC=y
CONFIG_TARGET_SCM3005=y
CONFIG_CLK=y
CONFIG_LZMA=y
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index f9f7aa5cf97..4ee56465c0f 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -114,6 +114,13 @@ config MMC_QUIRKS
are enabled by default, other may require additional flags or are
enabled by the host driver.
+config MMC_SDHCI_AXIADO
+ bool "Axiado eMMC sdhci driver support"
+ depends on MMC
+ help
+ Enable this to support the Axiado eMMC sdhci found in the Ax3005 SoC.
+ This sdhci provides functionality for interface with sdhci driver.
+
config SYS_MMC_MAX_BLK_COUNT
int "Block count limit"
default 65535
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index a23336d7d8d..f4a40ee10d5 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_NEXELL_DWMMC) += nexell_dw_mmc.o
obj-$(CONFIG_MMC_SDHCI) += sdhci.o
obj-$(CONFIG_MMC_SDHCI_ASPEED) += aspeed_sdhci.o
obj-$(CONFIG_MMC_SDHCI_ATMEL) += atmel_sdhci.o
+obj-$(CONFIG_MMC_SDHCI_AXIADO) += axiado_sdhci.o
obj-$(CONFIG_MMC_SDHCI_BCM2835) += bcm2835_sdhci.o
obj-$(CONFIG_MMC_SDHCI_BCMSTB) += bcmstb_sdhci.o
obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o
diff --git a/drivers/mmc/axiado_sdhci.c b/drivers/mmc/axiado_sdhci.c
new file mode 100644
index 00000000000..3f7777ab0ba
--- /dev/null
+++ b/drivers/mmc/axiado_sdhci.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2021-26 Axiado Corporation (or its affiliates).
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <fdtdec.h>
+#include "mmc_private.h"
+#include <dm/device.h>
+#include <linux/err.h>
+#include <linux/libfdt.h>
+#include <malloc.h>
+#include <sdhci.h>
+#include <generic-phy.h>
+#include <dm/device_compat.h>
+#include <linux/delay.h>
+
+#define SDHCI_HOST_CTRL2 0x3E
+#define SDHCI_CTRL_VDD_180 0x0008
+#define SDHCI_CTRL_EXEC_TUNING 0x0040
+#define SDHCI_TUNING_LOOP_COUNT 40
+#define EMMC_MIN_FREQ 400000
+#define EMMC_MAX_FREQ 200000000
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct axiado_sdhci_plat {
+ struct mmc_config cfg;
+ struct mmc mmc;
+ unsigned int f_max;
+ struct phy phy;
+ bool phy_is_on;
+};
+
+struct axiado_sdhci_priv {
+ struct sdhci_host *host;
+};
+
+static void axiado_sdhci_set_control_reg(struct sdhci_host *host)
+{
+ struct mmc *mmc = (struct mmc *)host->mmc;
+ u32 reg;
+
+ switch (mmc->signal_voltage) {
+ case MMC_SIGNAL_VOLTAGE_180:
+ reg = sdhci_readw(host, SDHCI_HOST_CTRL2);
+ reg |= SDHCI_CTRL_VDD_180; /* Set 1.8V mode */
+ sdhci_writew(host, reg, SDHCI_HOST_CTRL2);
+ break;
+
+ case MMC_SIGNAL_VOLTAGE_330:
+ reg = sdhci_readw(host, SDHCI_HOST_CTRL2);
+ reg &= ~SDHCI_CTRL_VDD_180; /* Clear 1.8V mode (3.3V) */
+ sdhci_writew(host, reg, SDHCI_HOST_CTRL2);
+ break;
+
+ default:
+ debug("axiado_sdhci: Unsupported voltage: %d\n",
+ mmc->signal_voltage);
+ break;
+ }
+
+ sdhci_set_uhs_timing(host);
+}
+
+static int axiado_sdhci_set_ios_post(struct sdhci_host *host)
+{
+ struct udevice *dev = host->mmc->dev;
+ struct axiado_sdhci_plat *plat = dev_get_plat(dev);
+ unsigned int speed = host->mmc->clock;
+ u16 clk;
+
+ /* Reset SD Clock Enable */
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ /* Power off PHY */
+ if (plat->phy_is_on) {
+ debug("Powering off eMMC PHY\n");
+ generic_phy_power_off(&plat->phy);
+ plat->phy_is_on = false;
+ }
+
+ /* Restart SD Clock */
+ sdhci_set_clock(host->mmc, speed);
+
+ /* Power on PHY if required */
+ if (speed > EMMC_MIN_FREQ) {
+ debug("Speed is high enough, powering on PHY...\n");
+ generic_phy_power_on(&plat->phy);
+ plat->phy_is_on = true;
+ }
+
+ return 0;
+}
+
+static int axiado_sdhci_execute_tuning(struct mmc *mmc, u8 opcode)
+{
+ struct mmc_cmd cmd;
+ struct mmc_data data;
+ u32 ctrl;
+ struct sdhci_host *host;
+ char tuning_loop_counter = SDHCI_TUNING_LOOP_COUNT;
+
+ host = mmc->priv;
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CTRL2);
+ ctrl |= SDHCI_CTRL_EXEC_TUNING;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CTRL2);
+
+ sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE);
+ sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_SIGNAL_ENABLE);
+
+ do {
+ cmd.cmdidx = opcode;
+ cmd.resp_type = MMC_RSP_R1;
+ cmd.cmdarg = 0;
+
+ data.blocksize = 64;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+
+ if (tuning_loop_counter-- == 0)
+ break;
+
+ if (cmd.cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200 &&
+ mmc->bus_width == 8)
+ data.blocksize = 128;
+
+ sdhci_writew(host,
+ SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, data.blocksize),
+ SDHCI_BLOCK_SIZE);
+ sdhci_writew(host, data.blocks, SDHCI_BLOCK_COUNT);
+ sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);
+
+ mmc_send_cmd(mmc, &cmd, NULL);
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CTRL2);
+
+ if (cmd.cmdidx == MMC_CMD_SEND_TUNING_BLOCK)
+ udelay(1);
+
+ } while (ctrl & SDHCI_CTRL_EXEC_TUNING);
+
+ if (tuning_loop_counter < 0) {
+ ctrl &= ~SDHCI_CTRL_TUNED_CLK;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CTRL2);
+ }
+
+ if (!(ctrl & SDHCI_CTRL_TUNED_CLK)) {
+ printf("%s: Tuning failed\n", __func__);
+ return -1;
+ }
+
+ /* Enable only interrupts served by the SD controller */
+ sdhci_writel(host, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK,
+ SDHCI_INT_ENABLE);
+ /* Mask all sdhci interrupt sources */
+ sdhci_writel(host, 0x0, SDHCI_SIGNAL_ENABLE);
+
+ return 0;
+}
+
+static struct sdhci_ops axiado_ops = {
+ .set_ios_post = &axiado_sdhci_set_ios_post,
+ .platform_execute_tuning = &axiado_sdhci_execute_tuning,
+ .set_control_reg = &axiado_sdhci_set_control_reg,
+};
+
+static int axiado_sdhci_probe(struct udevice *dev)
+{
+ struct axiado_sdhci_plat *plat = dev_get_plat(dev);
+ struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
+ struct axiado_sdhci_priv *priv = dev_get_priv(dev);
+ struct sdhci_host *host;
+ unsigned long clock = 0;
+ int ret;
+ struct clk clk;
+
+ host = priv->host;
+ /* Get clock frequency */
+ ret = clk_get_by_index(dev, 0, &clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to get clock\n");
+ return ret;
+ }
+
+ clock = clk_get_rate(&clk);
+ if (IS_ERR_VALUE(clock)) {
+ dev_err(dev, "failed to get rate\n");
+ return clock;
+ }
+
+ ret = clk_enable(&clk);
+ /* U-Boot commonly uses ENOSYS for "function not implemented" */
+ if (ret && ret != -ENOSYS) {
+ dev_err(dev, "failed to enable clock\n");
+ return ret;
+ }
+
+ if (device_is_compatible(dev, "axiado,ax3005-sdhci")) {
+ /* Get and init phy */
+ ret = generic_phy_get_by_name(dev, "phy_arasan", &plat->phy);
+ if (ret) {
+ dev_err(dev, "can't get phy from DT\n");
+ return ret;
+ }
+
+ ret = generic_phy_init(&plat->phy);
+ if (ret) {
+ dev_err(dev, "couldn't initialize MMC PHY\n");
+ return ret;
+ }
+
+ plat->phy_is_on = false;
+ }
+
+ host->ops = &axiado_ops;
+ host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD;
+
+ host->max_clk = clock;
+ host->bus_width = dev_read_u32_default(dev, "bus-width", 4);
+
+ if (host->bus_width == 8)
+ host->host_caps |= MMC_MODE_8BIT;
+ else if (host->bus_width == 4)
+ host->host_caps |= MMC_MODE_4BIT;
+
+ host->mmc = &plat->mmc;
+ host->mmc->dev = dev;
+ host->mmc->priv = host;
+
+ ret = sdhci_setup_cfg(&plat->cfg, host, plat->f_max, EMMC_MIN_FREQ);
+ if (ret)
+ goto err;
+ upriv->mmc = host->mmc;
+
+ ret = sdhci_probe(dev);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ clk_disable(&clk);
+ return ret;
+}
+
+static int axiado_sdhci_ofdata_to_platdata(struct udevice *dev)
+{
+ struct axiado_sdhci_plat *plat = dev_get_plat(dev);
+ struct axiado_sdhci_priv *priv = dev_get_priv(dev);
+
+ priv->host = calloc(1, sizeof(struct sdhci_host));
+ if (!priv->host) {
+ dev_err(dev, "sdhci_host malloc failed\n");
+ return -ENOMEM;
+ }
+
+ priv->host->name = dev->name;
+
+ priv->host->ioaddr = dev_read_addr_ptr(dev);
+ if (!priv->host->ioaddr) {
+ dev_err(dev, "failed to get I/O address\n");
+ return -EINVAL;
+ }
+
+ plat->f_max = dev_read_u32_default(dev, "mmc-clk", EMMC_MAX_FREQ);
+
+ return 0;
+}
+
+static int axiado_sdhci_bind(struct udevice *dev)
+{
+ struct axiado_sdhci_plat *plat = dev_get_plat(dev);
+
+ return sdhci_bind(dev, &plat->mmc, &plat->cfg);
+}
+
+static const struct udevice_id axiado_sdhci_ids[] = {
+ { .compatible = "axiado,ax3005-sdhci" },
+ {}
+};
+
+static int axiado_sdhci_remove(struct udevice *dev)
+{
+ struct axiado_sdhci_plat *plat = dev_get_plat(dev);
+
+ if (device_is_compatible(dev, "axiado,ax3005-sdhci")) {
+ if (plat->phy_is_on)
+ generic_phy_power_off(&plat->phy);
+ generic_phy_exit(&plat->phy);
+ }
+
+ return 0;
+}
+
+U_BOOT_DRIVER(axiado_sdhci_drv) = {
+ .name = "axiado_sdhci",
+ .id = UCLASS_MMC,
+ .of_match = axiado_sdhci_ids,
+ .of_to_plat = axiado_sdhci_ofdata_to_platdata,
+ .ops = &sdhci_ops,
+ .bind = axiado_sdhci_bind,
+ .probe = axiado_sdhci_probe,
+ .remove = axiado_sdhci_remove,
+ .priv_auto = sizeof(struct axiado_sdhci_priv),
+ .plat_auto = sizeof(struct axiado_sdhci_plat),
+};
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 89d84df96ae..16eaa67fd02 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -81,6 +81,15 @@ config APPLE_ATCPHY
This is a dummy driver since the PHY is initialized
sufficiently by previous stage firmware.
+config PHY_AXIADO_EMMC
+ bool "Axiado eMMC PHY driver support"
+ depends on PHY
+ imply REGMAP
+ help
+ Enable this to support the Axiado eMMC PHY found in the Ax3005 SoC.
+ This PHY provides signal conditioning and DLL functionality for
+ high-speed eMMC operations with data rates beyond 52MHz.
+
config BCM6318_USBH_PHY
bool "BCM6318 USBH PHY support"
depends on PHY && ARCH_BMIPS
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index e46c362878d..7ad0bd59407 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_OMAP_USB2_PHY) += omap-usb2-phy.o
obj-$(CONFIG_KEYSTONE_USB_PHY) += keystone-usb-phy.o
obj-$(CONFIG_MT7620_USB_PHY) += mt7620-usb-phy.o
obj-$(CONFIG_MT76X8_USB_PHY) += mt76x8-usb-phy.o
+obj-$(CONFIG_PHY_AXIADO_EMMC) += phy-axiado-emmc.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
diff --git a/drivers/phy/phy-axiado-emmc.c b/drivers/phy/phy-axiado-emmc.c
new file mode 100644
index 00000000000..a151557a0ec
--- /dev/null
+++ b/drivers/phy/phy-axiado-emmc.c
@@ -0,0 +1,208 @@
+// 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 <dm.h>
+#include <generic-phy.h>
+#include <regmap.h>
+#include <time.h>
+#include <dm/device.h>
+#include <dm/device_compat.h>
+
+/* Axiado eMMC PHY configuration registers */
+#define CAP_REG_IN_S1_LSB 0x00
+#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
+
+/* PHY Control 1 Register bits */
+#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)
+
+/* PHY Control 2 Register bits */
+#define OTAPDLY_EN BIT(11)
+
+/* PHY Control 3 Register bits */
+#define SEL_DLY_RXCLK BIT(18)
+#define SEL_DLY_TXCLK BIT(19)
+
+/* Status Register bits */
+#define CALDONE_MASK 0x40
+#define DLL_RDY_MASK 0x1
+
+/* Clock buffer bits */
+#define MAX_CLK_BUF0 BIT(20)
+#define MAX_CLK_BUF1 BIT(21)
+#define MAX_CLK_BUF2 BIT(22)
+
+/* Other constants */
+#define CLK_MULTIPLIER 0xC008E
+#define LOOP_TIMEOUT 3000
+#define TIMEOUT_DELAY 100
+
+struct axiado_emmc_phy {
+ struct regmap *regmap;
+};
+
+static int axiado_emmc_phy_init(struct phy *phy)
+{
+ u32 val;
+ unsigned long timeout;
+ struct axiado_emmc_phy *ax_phy = dev_get_priv(phy->dev);
+
+ regmap_read(ax_phy->regmap, PHY_CTRL_1, &val);
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_1, RETB_ENBL | RTRIM_EN,
+ RETB_ENBL | RTRIM_EN);
+
+ regmap_read(ax_phy->regmap, PHY_CTRL_3, &val);
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_3, PDB_ENBL, PDB_ENBL);
+
+ /* Wait max 3000 ms */
+ timeout = get_timer(0);
+
+ while (1) {
+ regmap_read(ax_phy->regmap, STATUS, &val);
+ if (val & CALDONE_MASK)
+ break;
+
+ if (get_timer(timeout) > LOOP_TIMEOUT) {
+ dev_err(phy->dev,
+ "eMMC-PHY: CALDONE_MASK bit not cleared.\n");
+ return -ETIMEDOUT;
+ }
+ udelay(TIMEOUT_DELAY);
+ }
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_1, REN_CMD_EN | PU_CMD_EN,
+ REN_CMD_EN | PU_CMD_EN);
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_2, REN_STRB, REN_STRB);
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_3,
+ MAX_CLK_BUF0 | MAX_CLK_BUF1 | MAX_CLK_BUF2,
+ MAX_CLK_BUF0 | MAX_CLK_BUF1 | MAX_CLK_BUF2);
+
+ regmap_write(ax_phy->regmap, CAP_REG_IN_S1_MSB, CLK_MULTIPLIER);
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_3,
+ SEL_DLY_RXCLK | SEL_DLY_TXCLK,
+ SEL_DLY_RXCLK | SEL_DLY_TXCLK);
+
+ return 0;
+}
+
+static int axiado_emmc_phy_power_on(struct phy *phy)
+{
+ struct axiado_emmc_phy *ax_phy = dev_get_priv(phy->dev);
+ u32 val;
+ unsigned long timeout;
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_1, RETB_ENBL, RETB_ENBL);
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_3, PDB_ENBL, PDB_ENBL);
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_2, OTAPDLY_EN | (0xF << 7),
+ OTAPDLY_EN | ((0x2) << 7));
+
+ /* Read back to ensure write is completed */
+ regmap_read(ax_phy->regmap, PHY_CTRL_2, &val);
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_1, DLL_ENBL | (0xF << 22),
+ DLL_ENBL | ((0x8) << 22));
+
+ regmap_write(ax_phy->regmap, STATUS, 0x00);
+
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_3, (0x1 << 25),
+ (0x1 << 25));
+
+ /* Wait max 3000 ms */
+ timeout = get_timer(0);
+
+ while (1) {
+ regmap_read(ax_phy->regmap, STATUS, &val);
+ if (val & DLL_RDY_MASK)
+ break;
+
+ if (get_timer(timeout) > LOOP_TIMEOUT) {
+ dev_err(phy->dev,
+ "eMMC-PHY: DLL_RDY_MASK bit not cleared.\n");
+ return -ETIMEDOUT;
+ }
+ udelay(TIMEOUT_DELAY);
+ }
+ return 0;
+}
+
+static int axiado_emmc_phy_power_off(struct phy *phy)
+{
+ struct axiado_emmc_phy *ax_phy = dev_get_priv(phy->dev);
+
+ /* Disable DLL */
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_1, DLL_ENBL, 0);
+
+ return 0;
+}
+
+static int axiado_emmc_phy_exit(struct phy *phy)
+{
+ struct axiado_emmc_phy *ax_phy = dev_get_priv(phy->dev);
+
+ /* Disable all PHY features */
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_1,
+ DLL_ENBL | RETB_ENBL | RTRIM_EN, 0);
+ regmap_update_bits(ax_phy->regmap, PHY_CTRL_3, PDB_ENBL, 0);
+
+ 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,
+ .exit = axiado_emmc_phy_exit,
+};
+
+static int axiado_emmc_phy_probe(struct udevice *dev)
+{
+ struct axiado_emmc_phy *ax_phy = dev_get_priv(dev);
+ int ret;
+
+ ret = regmap_init_mem(dev_ofnode(dev), &ax_phy->regmap);
+ if (ret) {
+ dev_err(dev, "Failed to init regmap: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id axiado_emmc_phy_ids[] = {
+ { .compatible = "axiado,ax3005-emmc-phy" },
+ {}
+};
+
+U_BOOT_DRIVER(axiado_emmc_phy) = {
+ .name = "axiado_emmc_phy",
+ .id = UCLASS_PHY,
+ .of_match = axiado_emmc_phy_ids,
+ .ops = &axiado_emmc_phy_ops,
+ .probe = axiado_emmc_phy_probe,
+ .priv_auto = sizeof(struct axiado_emmc_phy),
+};