diff options
| author | Tom Rini <trini@konsulko.com> | 2026-08-11 12:57:12 -0600 |
|---|---|---|
| committer | Tom Rini <trini@konsulko.com> | 2026-08-11 14:55:40 -0600 |
| commit | 5a92645e1f9020d24d19e03fff2ae63f30080770 (patch) | |
| tree | a1e964d3d497b4c386abd64fc02218bd8d0f8d27 | |
| parent | a9263fc032105731ea87d28ceaf9d09e2c9ee018 (diff) | |
| parent | f77229cb1ca1245b9383204d421a2f2f3b233eb5 (diff) | |
| download | u-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-- | MAINTAINERS | 2 | ||||
| -rw-r--r-- | arch/arm/dts/ax3005-scm3005.dts | 4 | ||||
| -rw-r--r-- | arch/arm/dts/ax3005.dtsi | 25 | ||||
| -rw-r--r-- | configs/ax3005_scm3005_defconfig | 10 | ||||
| -rw-r--r-- | drivers/mmc/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/mmc/Makefile | 1 | ||||
| -rw-r--r-- | drivers/mmc/axiado_sdhci.c | 311 | ||||
| -rw-r--r-- | drivers/phy/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/phy/Makefile | 1 | ||||
| -rw-r--r-- | drivers/phy/phy-axiado-emmc.c | 208 |
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), +}; |
