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