[PATCH 2/4] mmc: Add Axiado SDHCI driver

Siu Ming Tong <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
Add an SDHCI host controller driver (MMC_SDHCI_AXIADO) for the Arasan
SDHCI controller found on the Axiado AX3005 SoC. The driver builds on
the generic SDHCI framework and adds SoC-specific handling for signal
voltage switching, HS200 tuning and eMMC PHY power sequencing through
the generic PHY API.

Signed-off-by: Siu Ming Tong <[email protected]>
---
 MAINTAINERS                |   1 +
 drivers/mmc/Kconfig        |   7 +
 drivers/mmc/Makefile       |   1 +
 drivers/mmc/axiado_sdhci.c | 311 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 320 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index eece4e5ebd0..02a0c171840 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -232,6 +232,7 @@ 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
 
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..a1cf22ad4f5
--- /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),
+};

-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.