[RFC PATCH 4/4] mmc: renesas_sdhi: Add support for RZ/V2H(P) SoC

From: Prabhakar
Date: Wed Jun 05 2024 - 03:51:11 EST


From: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>

The SDHI/eMMC IPs found in the RZ/V2H(P) (a.k.a. r9a09g057) are very
similar to those found in R-Car Gen3. However, they are not identical,
necessitating an SoC-specific compatible string for fine-tuning driver
support.

Key features of the RZ/V2H(P) SDHI/eMMC IPs include:
- Voltage level control via the IOVS bit.
- PWEN pin support via SD_STATUS register.
- Lack of HS400 support.
- Fixed address mode operation.

sd_iovs and sd_pwen quirks are introduced for SoCs supporting this bit
to handle voltage level control and power enable via SD_STATUS register.

regulator support is added to control the volatage levels of SD pins
via sd_iovs bit in SD_STATUS register.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
---
drivers/mmc/host/renesas_sdhi.h | 7 ++
drivers/mmc/host/renesas_sdhi_core.c | 67 +++++++++++++++++--
drivers/mmc/host/renesas_sdhi_internal_dmac.c | 45 +++++++++++++
drivers/mmc/host/tmio_mmc.h | 4 ++
4 files changed, 118 insertions(+), 5 deletions(-)

diff --git a/drivers/mmc/host/renesas_sdhi.h b/drivers/mmc/host/renesas_sdhi.h
index 586f94d4dbfd..9ef4fdf44280 100644
--- a/drivers/mmc/host/renesas_sdhi.h
+++ b/drivers/mmc/host/renesas_sdhi.h
@@ -11,6 +11,8 @@

#include <linux/dmaengine.h>
#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
#include "tmio_mmc.h"

struct renesas_sdhi_scc {
@@ -49,6 +51,11 @@ struct renesas_sdhi_quirks {
bool manual_tap_correction;
bool old_info1_layout;
u32 hs400_bad_taps;
+ bool sd_iovs;
+ bool sd_pwen;
+ struct regulator_desc *rdesc;
+ const struct regmap_config *rdesc_regmap_config;
+ unsigned int rdesc_offset;
const u8 (*hs400_calib_table)[SDHI_CALIB_TABLE_MAX];
};

diff --git a/drivers/mmc/host/renesas_sdhi_core.c b/drivers/mmc/host/renesas_sdhi_core.c
index 12f4faaaf4ee..2eeea9513a23 100644
--- a/drivers/mmc/host/renesas_sdhi_core.c
+++ b/drivers/mmc/host/renesas_sdhi_core.c
@@ -248,6 +248,19 @@ static int renesas_sdhi_card_busy(struct mmc_host *mmc)
TMIO_STAT_DAT0);
}

+static void renesas_sdhi_sd_status_pwen(struct tmio_mmc_host *host, bool on)
+{
+ u32 sd_status;
+
+ sd_ctrl_read32_rep(host, CTL_SD_STATUS, &sd_status, 1);
+ if (on)
+ sd_status |= SD_STATUS_PWEN;
+ else
+ sd_status &= ~SD_STATUS_PWEN;
+
+ sd_ctrl_write32(host, CTL_SD_STATUS, sd_status);
+}
+
static int renesas_sdhi_start_signal_voltage_switch(struct mmc_host *mmc,
struct mmc_ios *ios)
{
@@ -587,6 +600,9 @@ static void renesas_sdhi_reset(struct tmio_mmc_host *host, bool preserve)
false, priv->rstc);
/* At least SDHI_VER_GEN2_SDR50 needs manual release of reset */
sd_ctrl_write16(host, CTL_RESET_SD, 0x0001);
+ if (sdhi_has_quirk(priv, sd_pwen))
+ renesas_sdhi_sd_status_pwen(host, true);
+
priv->needs_adjust_hs400 = false;
renesas_sdhi_set_clock(host, host->clk_cache);

@@ -904,6 +920,34 @@ static void renesas_sdhi_enable_dma(struct tmio_mmc_host *host, bool enable)
renesas_sdhi_sdbuf_width(host, enable ? width : 16);
}

+static int renesas_sdhi_internal_dmac_register_regulator(struct platform_device *pdev,
+ const struct renesas_sdhi_quirks *quirks)
+{
+ struct tmio_mmc_host *host = platform_get_drvdata(pdev);
+ struct renesas_sdhi *priv = host_to_priv(host);
+ struct regulator_config rcfg = {
+ .dev = &pdev->dev,
+ .driver_data = priv,
+ };
+ struct regulator_dev *rdev;
+ const char *devname;
+
+ devname = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s-vqmmc-regulator",
+ dev_name(&pdev->dev));
+ if (!devname)
+ return -ENOMEM;
+
+ quirks->rdesc->name = devname;
+ rcfg.regmap = devm_regmap_init_mmio(&pdev->dev, host->ctl + quirks->rdesc_offset,
+ quirks->rdesc_regmap_config);
+ if (IS_ERR(rcfg.regmap))
+ return PTR_ERR(rcfg.regmap);
+
+ rdev = devm_regulator_register(&pdev->dev, quirks->rdesc, &rcfg);
+
+ return PTR_ERR_OR_ZERO(rdev);
+}
+
int renesas_sdhi_probe(struct platform_device *pdev,
const struct tmio_mmc_dma_ops *dma_ops,
const struct renesas_sdhi_of_data *of_data,
@@ -1051,6 +1095,15 @@ int renesas_sdhi_probe(struct platform_device *pdev,
if (ret)
goto efree;

+ if (sdhi_has_quirk(priv, sd_iovs)) {
+ ret = renesas_sdhi_internal_dmac_register_regulator(pdev, quirks);
+ if (ret)
+ goto efree;
+ }
+
+ if (sdhi_has_quirk(priv, sd_pwen))
+ renesas_sdhi_sd_status_pwen(host, true);
+
ver = sd_ctrl_read16(host, CTL_VERSION);
/* GEN2_SDR104 is first known SDHI to use 32bit block count */
if (ver < SDHI_VER_GEN2_SDR104 && mmc_data->max_blk_count > U16_MAX)
@@ -1110,26 +1163,26 @@ int renesas_sdhi_probe(struct platform_device *pdev,
num_irqs = platform_irq_count(pdev);
if (num_irqs < 0) {
ret = num_irqs;
- goto eirq;
+ goto epwen;
}

/* There must be at least one IRQ source */
if (!num_irqs) {
ret = -ENXIO;
- goto eirq;
+ goto epwen;
}

for (i = 0; i < num_irqs; i++) {
irq = platform_get_irq(pdev, i);
if (irq < 0) {
ret = irq;
- goto eirq;
+ goto epwen;
}

ret = devm_request_irq(&pdev->dev, irq, tmio_mmc_irq, 0,
dev_name(&pdev->dev), host);
if (ret)
- goto eirq;
+ goto epwen;
}

ret = tmio_mmc_host_probe(host);
@@ -1141,7 +1194,9 @@ int renesas_sdhi_probe(struct platform_device *pdev,

return ret;

-eirq:
+epwen:
+ if (sdhi_has_quirk(priv, sd_pwen))
+ renesas_sdhi_sd_status_pwen(host, false);
tmio_mmc_host_remove(host);
edisclk:
renesas_sdhi_clk_disable(host);
@@ -1157,6 +1212,8 @@ void renesas_sdhi_remove(struct platform_device *pdev)
struct tmio_mmc_host *host = platform_get_drvdata(pdev);

tmio_mmc_host_remove(host);
+ if (sdhi_has_quirk(host_to_priv(host), sd_pwen))
+ renesas_sdhi_sd_status_pwen(host, false);
renesas_sdhi_clk_disable(host);
tmio_mmc_host_free(host);
}
diff --git a/drivers/mmc/host/renesas_sdhi_internal_dmac.c b/drivers/mmc/host/renesas_sdhi_internal_dmac.c
index 422fa63a2e99..f824d167bb09 100644
--- a/drivers/mmc/host/renesas_sdhi_internal_dmac.c
+++ b/drivers/mmc/host/renesas_sdhi_internal_dmac.c
@@ -215,6 +215,45 @@ static const struct renesas_sdhi_quirks sdhi_quirks_rzg2l = {
.hs400_disabled = true,
};

+static const unsigned int r9a09g057_vqmmc_voltages[] = {
+ 3300000, 1800000,
+};
+
+static const struct regulator_ops r9a09g057_regulator_voltage_ops = {
+ .list_voltage = regulator_list_voltage_table,
+ .map_voltage = regulator_map_voltage_descend,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+};
+
+static struct regulator_desc r9a09g057_vqmmc_regulator = {
+ .of_match = of_match_ptr("vqmmc-r9a09g057-regulator"),
+ .owner = THIS_MODULE,
+ .type = REGULATOR_VOLTAGE,
+ .ops = &r9a09g057_regulator_voltage_ops,
+ .volt_table = r9a09g057_vqmmc_voltages,
+ .n_voltages = ARRAY_SIZE(r9a09g057_vqmmc_voltages),
+ .vsel_mask = 0x10000,
+ .vsel_reg = 0,
+};
+
+static const struct regmap_config r9a09g057_vqmmc_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 1,
+};
+
+static const struct renesas_sdhi_quirks sdhi_quirks_r9a09g057 = {
+ .fixed_addr_mode = true,
+ .hs400_disabled = true,
+ .sd_iovs = true,
+ .sd_pwen = true,
+ .rdesc = &r9a09g057_vqmmc_regulator,
+ .rdesc_regmap_config = &r9a09g057_vqmmc_regmap_config,
+ .rdesc_offset = 0x3C8,
+};
+
/*
* Note for r8a7796 / r8a774a1: we can't distinguish ES1.1 and 1.2 as of now.
* So, we want to treat them equally and only have a match for ES1.2 to enforce
@@ -260,6 +299,11 @@ static const struct renesas_sdhi_of_data_with_quirks of_rzg2l_compatible = {
.quirks = &sdhi_quirks_rzg2l,
};

+static const struct renesas_sdhi_of_data_with_quirks of_r9a09g057_compatible = {
+ .of_data = &of_data_rcar_gen3,
+ .quirks = &sdhi_quirks_r9a09g057,
+};
+
static const struct renesas_sdhi_of_data_with_quirks of_rcar_gen3_compatible = {
.of_data = &of_data_rcar_gen3,
};
@@ -284,6 +328,7 @@ static const struct of_device_id renesas_sdhi_internal_dmac_of_match[] = {
{ .compatible = "renesas,sdhi-r8a77990", .data = &of_r8a77990_compatible, },
{ .compatible = "renesas,sdhi-r8a77995", .data = &of_rcar_gen3_nohs400_compatible, },
{ .compatible = "renesas,sdhi-r9a09g011", .data = &of_rzg2l_compatible, },
+ { .compatible = "renesas,sdhi-r9a09g057", .data = &of_r9a09g057_compatible, },
{ .compatible = "renesas,rzg2l-sdhi", .data = &of_rzg2l_compatible, },
{ .compatible = "renesas,rcar-gen3-sdhi", .data = &of_rcar_gen3_compatible, },
{ .compatible = "renesas,rcar-gen4-sdhi", .data = &of_rcar_gen3_compatible, },
diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h
index de56e6534aea..d03aedf61aa3 100644
--- a/drivers/mmc/host/tmio_mmc.h
+++ b/drivers/mmc/host/tmio_mmc.h
@@ -43,6 +43,7 @@
#define CTL_RESET_SD 0xe0
#define CTL_VERSION 0xe2
#define CTL_SDIF_MODE 0xe6 /* only known on R-Car 2+ */
+#define CTL_SD_STATUS 0xf2 /* only known on RZ/G2L and RZ/V2H(P) */

/* Definitions for values the CTL_STOP_INTERNAL_ACTION register can take */
#define TMIO_STOP_STP BIT(0)
@@ -102,6 +103,9 @@
/* Definitions for values the CTL_SDIF_MODE register can take */
#define SDIF_MODE_HS400 BIT(0) /* only known on R-Car 2+ */

+/* Definitions for values the CTL_SD_STATUS register can take */
+#define SD_STATUS_PWEN BIT(0) /* only known on RZ/V2H(P) */
+
/* Define some IRQ masks */
/* This is the mask used at reset by the chip */
#define TMIO_MASK_ALL 0x837f031d
--
2.34.1