From 5d23810c3c6cea0326b48f5ef9faca6a8fe3ff5a Mon Sep 17 00:00:00 2001 From: Lukas Timmermann Date: Tue, 14 Oct 2025 13:40:32 +0200 Subject: mmc: exynos_dw_mmc: Add compatible string for Exynos5250 This driver got successfully tested with an upstream device tree and an Exynos5250. The board in question is samsung-manta (Google Nexus 10) which we are getting ready for upstream. For the u-boot port was just this additional compatible string needed. Signed-off-by: Lukas Timmermann Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/mmc/exynos_dw_mmc.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c index 12e37cb4b78..0597cd7f0c7 100644 --- a/drivers/mmc/exynos_dw_mmc.c +++ b/drivers/mmc/exynos_dw_mmc.c @@ -378,6 +378,9 @@ static const struct udevice_id exynos_dwmmc_ids[] = { }, { .compatible = "samsung,exynos5420-dw-mshc", .data = (ulong)&exynos5_drv_data, + }, { + .compatible = "samsung,exynos5250-dw-mshc", + .data = (ulong)&exynos5_drv_data, }, { .compatible = "samsung,exynos-dwmmc", .data = (ulong)&exynos5_drv_data, -- cgit v1.3.1 From 02d7aa88214e73aef81474f8b7d85272d94239a0 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:06 +0530 Subject: mmc: dw_mmc: export dwmci_send_cmd() and dwmci_set_ios() These commands are required by struct dm_mmc_ops. Any platform specific driver may use some or all of the functions in their own ops. Make them accessible by moving the prototype to the dwmmc.h header. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/mmc/dw_mmc.c | 6 +++--- include/dwmmc.h | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c index a51494380ce..c6a18a5427a 100644 --- a/drivers/mmc/dw_mmc.c +++ b/drivers/mmc/dw_mmc.c @@ -482,8 +482,8 @@ static int dwmci_send_cmd_common(struct dwmci_host *host, struct mmc_cmd *cmd, } #ifdef CONFIG_DM_MMC -static int dwmci_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, - struct mmc_data *data) +int dwmci_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) { struct mmc *mmc = mmc_get_mmc_dev(dev); #else @@ -585,7 +585,7 @@ static int dwmci_setup_bus(struct dwmci_host *host, u32 freq) } #ifdef CONFIG_DM_MMC -static int dwmci_set_ios(struct udevice *dev) +int dwmci_set_ios(struct udevice *dev) { struct mmc *mmc = mmc_get_mmc_dev(dev); #else diff --git a/include/dwmmc.h b/include/dwmmc.h index 87ca127cd6c..639a2d28e78 100644 --- a/include/dwmmc.h +++ b/include/dwmmc.h @@ -334,6 +334,9 @@ int add_dwmci(struct dwmci_host *host, u32 max_clk, u32 min_clk); #ifdef CONFIG_DM_MMC /* Export the operations to drivers */ int dwmci_probe(struct udevice *dev); +int dwmci_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data); +int dwmci_set_ios(struct udevice *dev); extern const struct dm_mmc_ops dm_dwmci_ops; #endif -- cgit v1.3.1 From 3a5b187b5c03eff681d4e41823313dfa28dfe845 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:07 +0530 Subject: mmc: dw_mmc: do not skip dwmci_setup_bus() for same non-zero clock frequency In dwmci_setup_bus(), if the requested frequency is equal to the current frequency, the function is returned, assuming no changes are required in associated registers. On certain SD cards, skipping in such situations may result in a timeout errors during MMC initialization. Due to the lack of documentation, the cause is unknown, but removing said check seems to fix the issue. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/mmc/dw_mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c index c6a18a5427a..130a5bb57f3 100644 --- a/drivers/mmc/dw_mmc.c +++ b/drivers/mmc/dw_mmc.c @@ -554,7 +554,7 @@ static int dwmci_setup_bus(struct dwmci_host *host, u32 freq) unsigned long sclk; int ret; - if (freq == host->clock || freq == 0) + if (!freq) return 0; /* -- cgit v1.3.1 From ae4601959202f4ea21c8a1aa42c5d2d894b03d57 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:08 +0530 Subject: mmc: dw_mmc: properly address command completion in dwmci_control_clken() The current implementation polls for the DWMCI_CMD register, for the DWMCI_CMD_START bit to turn off, which indicates that the command has been completed. The problem with this approach is that it doesn't address the DWMCI_INTMSK_CDONE bit in the interrupt register, DWMCI_RINTSTS. As a result, subsequent commands result in timeout errors. Re-implement the waiting logic by polling for said interrupt status bit and setting it low if raised. Signed-off-by: Kaustabh Chakraborty Signed-off-by: Peng Fan --- drivers/mmc/dw_mmc.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c index 130a5bb57f3..1aa992c352c 100644 --- a/drivers/mmc/dw_mmc.c +++ b/drivers/mmc/dw_mmc.c @@ -507,20 +507,21 @@ static int dwmci_control_clken(struct dwmci_host *host, bool on) { const u32 val = on ? DWMCI_CLKEN_ENABLE | DWMCI_CLKEN_LOW_PWR : 0; const u32 cmd_only_clk = DWMCI_CMD_PRV_DAT_WAIT | DWMCI_CMD_UPD_CLK; - int timeout = 10000; - u32 status; + int i, timeout = 10000; + u32 mask; dwmci_writel(host, DWMCI_CLKENA, val); /* Inform CIU */ dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_START | cmd_only_clk); - do { - status = dwmci_readl(host, DWMCI_CMD); - if (timeout-- < 0) { - debug("%s: Timeout!\n", __func__); - return -ETIMEDOUT; + + for (i = 0; i < timeout; i++) { + mask = dwmci_readl(host, DWMCI_RINTSTS); + if (mask & DWMCI_INTMSK_CDONE) { + dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_CDONE); + break; } - } while (status & DWMCI_CMD_START); + } return 0; } -- cgit v1.3.1 From e6b66e9f338fe9c8a6bc498399e736f7ec9deb10 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:09 +0530 Subject: mmc: dw_mmc: add voltage switch command flag During a voltage switch command (CMD11, opcode: SD_CMD_SWITCH_UHS18V), certain hosts tend to stop responding to subsequent commands. This is addressed by introducing an additional command flag, DWMCI_CMD_VOLT_SWITCH. The associated interrupt bit is defined as DWMCI_INTMSK_VOLTSW. This is set high when a voltage switch is issued, this needs to be waited for and set to low. Implement the same in the timeout loop. Do note that since DWMCI_INTMSK_VOLTSW shares the same bit as DWMCI_INTMSK_HTO (bit 10), the interrupt bit needs to be polled for only if the volt switch command is issued. DWMCI_CMD_VOLT_SWITCH also needs to be set for subsequent clken commands after the volt switch. To ensure this, add a boolean member in the host private struct (herein named volt_switching), which informs if the last command issued was for volt switching or not. Signed-off-by: Kaustabh Chakraborty Signed-off-by: Peng Fan --- drivers/mmc/dw_mmc.c | 15 +++++++++++++-- include/dwmmc.h | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c index 1aa992c352c..94b6641c44c 100644 --- a/drivers/mmc/dw_mmc.c +++ b/drivers/mmc/dw_mmc.c @@ -419,6 +419,10 @@ static int dwmci_send_cmd_common(struct dwmci_host *host, struct mmc_cmd *cmd, if (cmd->resp_type & MMC_RSP_CRC) flags |= DWMCI_CMD_CHECK_CRC; + host->volt_switching = (cmd->cmdidx == SD_CMD_SWITCH_UHS18V); + if (host->volt_switching) + flags |= DWMCI_CMD_VOLT_SWITCH; + flags |= cmd->cmdidx | DWMCI_CMD_START | DWMCI_CMD_USE_HOLD_REG; debug("Sending CMD%d\n", cmd->cmdidx); @@ -427,6 +431,10 @@ static int dwmci_send_cmd_common(struct dwmci_host *host, struct mmc_cmd *cmd, for (i = 0; i < retry; i++) { mask = dwmci_readl(host, DWMCI_RINTSTS); + if (host->volt_switching && (mask & DWMCI_INTMSK_VOLTSW)) { + dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_VOLTSW); + break; + } if (mask & DWMCI_INTMSK_CDONE) { if (!data) dwmci_writel(host, DWMCI_RINTSTS, mask); @@ -508,12 +516,15 @@ static int dwmci_control_clken(struct dwmci_host *host, bool on) const u32 val = on ? DWMCI_CLKEN_ENABLE | DWMCI_CLKEN_LOW_PWR : 0; const u32 cmd_only_clk = DWMCI_CMD_PRV_DAT_WAIT | DWMCI_CMD_UPD_CLK; int i, timeout = 10000; - u32 mask; + u32 flags, mask; dwmci_writel(host, DWMCI_CLKENA, val); /* Inform CIU */ - dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_START | cmd_only_clk); + flags = DWMCI_CMD_START | cmd_only_clk; + if (host->volt_switching) + flags |= DWMCI_CMD_VOLT_SWITCH; + dwmci_writel(host, DWMCI_CMD, flags); for (i = 0; i < timeout; i++) { mask = dwmci_readl(host, DWMCI_RINTSTS); diff --git a/include/dwmmc.h b/include/dwmmc.h index 639a2d28e78..47e3220985e 100644 --- a/include/dwmmc.h +++ b/include/dwmmc.h @@ -72,6 +72,7 @@ #define DWMCI_INTMSK_RTO BIT(8) #define DWMCI_INTMSK_DRTO BIT(9) #define DWMCI_INTMSK_HTO BIT(10) +#define DWMCI_INTMSK_VOLTSW BIT(10) /* overlap! */ #define DWMCI_INTMSK_FRUN BIT(11) #define DWMCI_INTMSK_HLE BIT(12) #define DWMCI_INTMSK_SBE BIT(13) @@ -104,6 +105,7 @@ #define DWMCI_CMD_ABORT_STOP BIT(14) #define DWMCI_CMD_PRV_DAT_WAIT BIT(13) #define DWMCI_CMD_UPD_CLK BIT(21) +#define DWMCI_CMD_VOLT_SWITCH BIT(28) #define DWMCI_CMD_USE_HOLD_REG BIT(29) #define DWMCI_CMD_START BIT(31) @@ -190,6 +192,7 @@ struct dwmci_idmac_regs { * @cfg: Internal MMC configuration, for !CONFIG_BLK cases * @fifo_mode: Use FIFO mode (not DMA) to read and write data * @dma_64bit_address: Whether DMA supports 64-bit address mode or not + * @volt_switching: Whether SD voltage switching is in process or not * @regs: Registers that can vary for different DW MMC block versions */ struct dwmci_host { @@ -229,6 +232,7 @@ struct dwmci_host { bool fifo_mode; bool dma_64bit_address; + bool volt_switching; const struct dwmci_idmac_regs *regs; }; -- cgit v1.3.1 From 0b75109b6aafd1e796bc8cee0953cd6c5ba77610 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:10 +0530 Subject: mmc: dw_mmc: return error for invalid voltage setting In certain cases, the VQMMC regulator may not support certain voltages. For instance, a VQMMC regulator which supports only up to 2.7V will not accept 3.3V as an argument. This is unaccounted for, and thus the driver incorrectly assumes that the voltage is set successfully. Fetch the return value in a variable and return if it's non-zero. (-ENOSYS is exempted as it implies that the voltage adjustment functionality as a whole isn't supported). Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/mmc/dw_mmc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c index 94b6641c44c..9b143f9931e 100644 --- a/drivers/mmc/dw_mmc.c +++ b/drivers/mmc/dw_mmc.c @@ -649,9 +649,11 @@ static int dwmci_set_ios(struct mmc *mmc) return ret; if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) - regulator_set_value(mmc->vqmmc_supply, 1800000); + ret = regulator_set_value(mmc->vqmmc_supply, 1800000); else - regulator_set_value(mmc->vqmmc_supply, 3300000); + ret = regulator_set_value(mmc->vqmmc_supply, 3300000); + if (ret && ret != -ENOSYS) + return ret; ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, true); if (ret) -- cgit v1.3.1 From 0f425edd10f30361ad52af466e36ffe718af1ea5 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:11 +0530 Subject: mmc: enable/disable VQMMC regulator only during MMC power cycle Disrupting the regulator voltage during ios configuration messes with the MMC initialization sequence. Move the VQMMC regulator enable/disable functions to the MMC power cycle function, similar to how its done for the VMMC regulator. Signed-off-by: Kaustabh Chakraborty Signed-off-by: Peng Fan --- drivers/mmc/dw_mmc.c | 8 -------- drivers/mmc/mmc.c | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c index 9b143f9931e..f3c0cc5cd8e 100644 --- a/drivers/mmc/dw_mmc.c +++ b/drivers/mmc/dw_mmc.c @@ -644,20 +644,12 @@ static int dwmci_set_ios(struct mmc *mmc) if (mmc->vqmmc_supply) { int ret; - ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, false); - if (ret) - return ret; - if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) ret = regulator_set_value(mmc->vqmmc_supply, 1800000); else ret = regulator_set_value(mmc->vqmmc_supply, 3300000); if (ret && ret != -ENOSYS) return ret; - - ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, true); - if (ret) - return ret; } #endif diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index ec61ed92e86..0f07955a32e 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -2844,6 +2844,16 @@ static int mmc_power_on(struct mmc *mmc) return ret; } } + + if (mmc->vqmmc_supply) { + int ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, + true); + + if (ret && ret != -ENOSYS) { + printf("Error enabling VQMMC supply : %d\n", ret); + return ret; + } + } #endif return 0; } @@ -2861,6 +2871,16 @@ static int mmc_power_off(struct mmc *mmc) return ret; } } + + if (mmc->vqmmc_supply) { + int ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, + false); + + if (ret && ret != -ENOSYS) { + pr_debug("Error disabling VQMMC supply : %d\n", ret); + return ret; + } + } #endif return 0; } -- cgit v1.3.1 From bbe3b9fa09220c97b09c0d6e96a0d416865389da Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:12 +0530 Subject: mmc: exynos_dw_mmc: add support for MMC HS200 and HS400 modes MMC HS200 and HS400 modes are already supported by the Exynos DW-MMC driver in mainline Linux. Using that as reference, add support in the U-Boot driver. The maximum frequency was capped to 50000000, increase it to 200000000, which is the required frequency for HS200/HS400. Moreover, add MMC_MODE_HS200 and MMC_MODE_HS400 to host capailities. These changes allow both host and card to recognize support for HS200/HS400. This change also includes a new ops function, namely execute_tuning. Implementing it would mean that we can no longer rely on the default ops provided by dw_mmc.c, thus a new ops instance is created with proper fields. The execute_tuning function is modeled after the one available in the Linux driver. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/mmc/exynos_dw_mmc.c | 88 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c index 0597cd7f0c7..66d2b8bdafd 100644 --- a/drivers/mmc/exynos_dw_mmc.c +++ b/drivers/mmc/exynos_dw_mmc.c @@ -18,13 +18,18 @@ #include #define DWMMC_MAX_CH_NUM 4 -#define DWMMC_MAX_FREQ 52000000 +#define DWMMC_MAX_FREQ 200000000 #define DWMMC_MIN_FREQ 400000 #define DWMMC_MMC0_SDR_TIMING_VAL 0x03030001 #define DWMMC_MMC2_SDR_TIMING_VAL 0x03020001 #define EXYNOS4412_FIXED_CIU_CLK_DIV 4 +/* CLKSEL register defines */ +#define CLKSEL_CCLK_SAMPLE(x) (((x) & 7) << 0) +#define CLKSEL_UP_SAMPLE(x, y) (((x) & ~CLKSEL_CCLK_SAMPLE(7)) |\ + CLKSEL_CCLK_SAMPLE(y)) + /* Quirks */ #define DWMCI_QUIRK_DISABLE_SMU BIT(0) @@ -166,8 +171,9 @@ static unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq) u8 clk_div; int err; - /* Should be double rate for DDR mode */ - if (host->mmc->selected_mode == MMC_DDR_52 && host->mmc->bus_width == 8) + /* Should be double rate for DDR or HS mode */ + if ((host->mmc->selected_mode == MMC_DDR_52 && host->mmc->bus_width == 8) || + host->mmc->selected_mode == MMC_HS_400) freq *= 2; clk_div = exynos_dwmmc_get_ciu_div(host); @@ -282,6 +288,72 @@ static int exynos_dwmmc_of_to_plat(struct udevice *dev) return 0; } +static int exynos_dwmmc_get_best_clksmpl(u8 candidates) +{ + u8 i; + + for (i = 0; i < 8; i++) { + candidates = (candidates >> 1) | (candidates << 7); /* ror */ + if ((candidates & 0xc7) == 0xc7) + return i; + } + + for (i = 0; i < 8; i++) { + candidates = (candidates >> 1) | (candidates << 7); /* ror */ + if ((candidates & 0x83) == 0x83) + return i; + } + + /* + * If no valid clock sample values are found, use the first + * canditate bit for clock sample value. + */ + for (i = 0; i < 8; i++) { + candidates = (candidates >> 1) | (candidates << 7); /* ror */ + if ((candidates & 0x1) == 0x1) + return i; + } + + return -EIO; +} + +static int exynos_dwmmc_execute_tuning(struct udevice *dev, u32 opcode) +{ + struct dwmci_exynos_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + struct mmc *mmc = mmc_get_mmc_dev(dev); + u8 start_smpl, smpl, candidates = 0; + u32 clksel; + int ret; + + clksel = dwmci_readl(host, priv->chip->clksel); + start_smpl = CLKSEL_CCLK_SAMPLE(clksel); + + do { + dwmci_writel(host, DWMCI_TMOUT, ~0); + + /* move to the next clksmpl */ + smpl = (clksel + 1) & 0x7; + clksel = CLKSEL_UP_SAMPLE(clksel, smpl); + dwmci_writel(host, priv->chip->clksel, clksel); + + if (!mmc_send_tuning(mmc, opcode)) + candidates |= (1 << smpl); + + } while (start_smpl != smpl); + + ret = exynos_dwmmc_get_best_clksmpl(candidates); + if (ret < 0) { + printf("DWMMC%d: No candidates for clksmpl\n", host->dev_index); + return ret; + } + + dwmci_writel(host, priv->chip->clksel, + CLKSEL_UP_SAMPLE(clksel, ret)); + + return 0; +} + static int exynos_dwmmc_probe(struct udevice *dev) { struct exynos_mmc_plat *plat = dev_get_plat(dev); @@ -321,7 +393,7 @@ static int exynos_dwmmc_probe(struct udevice *dev) host->name = dev->name; host->board_init = exynos_dwmci_board_init; - host->caps = MMC_MODE_DDR_52MHz; + host->caps = MMC_MODE_DDR_52MHz | MMC_MODE_HS200 | MMC_MODE_HS400; host->clksel = exynos_dwmci_clksel; host->get_mmc_clk = exynos_dwmci_get_clk; @@ -391,6 +463,12 @@ static const struct udevice_id exynos_dwmmc_ids[] = { { } }; +struct dm_mmc_ops exynos_dwmmc_ops = { + .send_cmd = dwmci_send_cmd, + .set_ios = dwmci_set_ios, + .execute_tuning = exynos_dwmmc_execute_tuning, +}; + U_BOOT_DRIVER(exynos_dwmmc_drv) = { .name = "exynos_dwmmc", .id = UCLASS_MMC, @@ -398,7 +476,7 @@ U_BOOT_DRIVER(exynos_dwmmc_drv) = { .of_to_plat = exynos_dwmmc_of_to_plat, .bind = exynos_dwmmc_bind, .probe = exynos_dwmmc_probe, - .ops = &dm_dwmci_ops, + .ops = &exynos_dwmmc_ops, .priv_auto = sizeof(struct dwmci_exynos_priv_data), .plat_auto = sizeof(struct exynos_mmc_plat), }; -- cgit v1.3.1 From bc707c576c4c926a98e57030861af4c8d1596862 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:13 +0530 Subject: mmc: exynos_dw_mmc: add support for SD UHS mode SD UHS mode is already supported by the Exynos DW-MMC driver in mainline Linux. Using that as reference, add support in the U-Boot driver. The maximum frequency was capped to 200000000, increase it to 208000000, which is the required frequency for UHS_SDR104, which has the highest frequency of all UHS modes. Moreover, add UHS_CAPS to host capailities. These changes allow both host and card to recognize support for all UHS modes. SDR104, SDR50, and DDR50 have their own CLKSEL timing values, which requires the CIU div value to be set in bits 18:16. Move the function exynos_dwmci_clksel() below exynos_dwmmc_get_ciu_div() so that the latter is accessible from the former, and add cases for said timing modes. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/mmc/exynos_dw_mmc.c | 48 ++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c index 66d2b8bdafd..7e7b472411c 100644 --- a/drivers/mmc/exynos_dw_mmc.c +++ b/drivers/mmc/exynos_dw_mmc.c @@ -18,7 +18,7 @@ #include #define DWMMC_MAX_CH_NUM 4 -#define DWMMC_MAX_FREQ 200000000 +#define DWMMC_MAX_FREQ 208000000 #define DWMMC_MIN_FREQ 400000 #define DWMMC_MMC0_SDR_TIMING_VAL 0x03030001 #define DWMMC_MMC2_SDR_TIMING_VAL 0x03020001 @@ -126,22 +126,6 @@ static int exynos_dwmmc_set_sclk(struct dwmci_host *host, unsigned long rate) return 0; } -/* Configure CLKSEL register with chosen timing values */ -static int exynos_dwmci_clksel(struct dwmci_host *host) -{ - struct dwmci_exynos_priv_data *priv = exynos_dwmmc_get_priv(host); - u32 timing; - - if (host->mmc->selected_mode == MMC_DDR_52) - timing = priv->ddr_timing; - else - timing = priv->sdr_timing; - - dwmci_writel(host, priv->chip->clksel, timing); - - return 0; -} - /** * exynos_dwmmc_get_ciu_div - Get internal clock divider value * @host: MMC controller object @@ -165,6 +149,33 @@ static u8 exynos_dwmmc_get_ciu_div(struct dwmci_host *host) & DWMCI_DIVRATIO_MASK) + 1; } +/* Configure CLKSEL register with chosen timing values */ +static int exynos_dwmci_clksel(struct dwmci_host *host) +{ + struct dwmci_exynos_priv_data *priv = exynos_dwmmc_get_priv(host); + u8 clk_div = exynos_dwmmc_get_ciu_div(host) - 1; + u32 timing; + + switch (host->mmc->selected_mode) { + case MMC_DDR_52: + timing = priv->ddr_timing; + break; + case UHS_SDR104: + case UHS_SDR50: + timing = (priv->sdr_timing & 0xfff8ffff) | (clk_div << 16); + break; + case UHS_DDR50: + timing = (priv->ddr_timing & 0xfff8ffff) | (clk_div << 16); + break; + default: + timing = priv->sdr_timing; + } + + dwmci_writel(host, priv->chip->clksel, timing); + + return 0; +} + static unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq) { unsigned long sclk; @@ -393,7 +404,8 @@ static int exynos_dwmmc_probe(struct udevice *dev) host->name = dev->name; host->board_init = exynos_dwmci_board_init; - host->caps = MMC_MODE_DDR_52MHz | MMC_MODE_HS200 | MMC_MODE_HS400; + host->caps = MMC_MODE_DDR_52MHz | MMC_MODE_HS200 | MMC_MODE_HS400 | + UHS_CAPS; host->clksel = exynos_dwmci_clksel; host->get_mmc_clk = exynos_dwmci_get_clk; -- cgit v1.3.1 From 07d2aa783cd50ede621ba6e51b3017b38791f478 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:54:14 +0530 Subject: mmc: exynos_dw_mmc: add compatible for exynos7870-dw-mshc-smu Exynos7870 is documented in upstream dt-schema. Add it in the U-Boot driver. Note that here it seems that Exynos7 DW MMC is perfectly compatible with Exynos7870 DW MMC. It's not always true, especially in SDIO cards where data from a 64-bit FIFO is read in two 32-bit halves [1]. Since SDIO isn't used or implemented here, it's oblivious. But upstream's schema considers that quirk, so that compatible is followed. Link: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=7cbe799ac10f [1] Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/mmc/exynos_dw_mmc.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c index 7e7b472411c..b6660fb2cd5 100644 --- a/drivers/mmc/exynos_dw_mmc.c +++ b/drivers/mmc/exynos_dw_mmc.c @@ -471,6 +471,9 @@ static const struct udevice_id exynos_dwmmc_ids[] = { }, { .compatible = "samsung,exynos7-dw-mshc-smu", .data = (ulong)&exynos7_smu_drv_data, + }, { + .compatible = "samsung,exynos7870-dw-mshc-smu", + .data = (ulong)&exynos7_smu_drv_data, }, { } }; -- cgit v1.3.1 From 86f793120ca62f3599e7907005a3cb8302e55fda Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:58:16 +0530 Subject: power: pmic: s2mps11: change the probe function to bind The probe function, s2mps11_probe() is responsible for binding its PMIC children. The driver doesn't have any functionality directly, but has sub-devices which are parts of the device. Therefore, this should be a bind function. This is the case in the Samsung S5M8767 PMIC driver. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/power/pmic/s2mps11.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/power/pmic/s2mps11.c b/drivers/power/pmic/s2mps11.c index 6e819579030..23c96da3fdf 100644 --- a/drivers/power/pmic/s2mps11.c +++ b/drivers/power/pmic/s2mps11.c @@ -47,7 +47,7 @@ static int s2mps11_read(struct udevice *dev, uint reg, uint8_t *buff, int len) return ret; } -static int s2mps11_probe(struct udevice *dev) +static int s2mps11_bind(struct udevice *dev) { ofnode regulators_node; int children; @@ -84,5 +84,5 @@ U_BOOT_DRIVER(pmic_s2mps11) = { .id = UCLASS_PMIC, .of_match = s2mps11_ids, .ops = &s2mps11_ops, - .probe = s2mps11_probe, + .bind = s2mps11_bind, }; -- cgit v1.3.1 From 73a85502bd2a4373d82f638b1b4c3e1f79e394e3 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:58:17 +0530 Subject: power: pmic: s2mps11: add support for allowing multiple device variants There are multiple PMICs by Samsung which are similar in architecture (register layout, interface, etc.) and is possible to be driven by a single driver. Variant specific code and data should be managed properly in the driver. And an enum which describes all supported variants. Pass the enum as the device driver data. Introduce a switch-case block on the enum for any variant specific code. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/power/pmic/s2mps11.c | 21 ++++++++++++++++++--- include/power/s2mps11.h | 5 +++++ 2 files changed, 23 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/pmic/s2mps11.c b/drivers/power/pmic/s2mps11.c index 23c96da3fdf..51c98afb17b 100644 --- a/drivers/power/pmic/s2mps11.c +++ b/drivers/power/pmic/s2mps11.c @@ -13,7 +13,7 @@ #include #include -static const struct pmic_child_info pmic_children_info[] = { +static const struct pmic_child_info s2mps11_pmic_children_info[] = { { .prefix = S2MPS11_OF_LDO_PREFIX, .driver = S2MPS11_LDO_DRIVER }, { .prefix = S2MPS11_OF_BUCK_PREFIX, .driver = S2MPS11_BUCK_DRIVER }, { }, @@ -21,7 +21,12 @@ static const struct pmic_child_info pmic_children_info[] = { static int s2mps11_reg_count(struct udevice *dev) { - return S2MPS11_REG_COUNT; + switch (dev_get_driver_data(dev)) { + case VARIANT_S2MPS11: + return S2MPS11_REG_COUNT; + default: + return -EINVAL; + } } static int s2mps11_write(struct udevice *dev, uint reg, const uint8_t *buff, @@ -51,6 +56,7 @@ static int s2mps11_bind(struct udevice *dev) { ofnode regulators_node; int children; + const struct pmic_child_info *pmic_children_info; regulators_node = dev_read_subnode(dev, "regulators"); if (!ofnode_valid(regulators_node)) { @@ -61,6 +67,15 @@ static int s2mps11_bind(struct udevice *dev) debug("%s: '%s' - found regulators subnode\n", __func__, dev->name); + switch (dev_get_driver_data(dev)) { + case VARIANT_S2MPS11: + pmic_children_info = s2mps11_pmic_children_info; + break; + default: + debug("%s: unknown device type\n", __func__); + return -EINVAL; + } + children = pmic_bind_children(dev, regulators_node, pmic_children_info); if (!children) debug("%s: %s - no child found\n", __func__, dev->name); @@ -75,7 +90,7 @@ static struct dm_pmic_ops s2mps11_ops = { }; static const struct udevice_id s2mps11_ids[] = { - { .compatible = "samsung,s2mps11-pmic" }, + { .compatible = "samsung,s2mps11-pmic", .data = VARIANT_S2MPS11 }, { } }; diff --git a/include/power/s2mps11.h b/include/power/s2mps11.h index 22b38fff703..c08bea5a516 100644 --- a/include/power/s2mps11.h +++ b/include/power/s2mps11.h @@ -161,4 +161,9 @@ enum { OP_ON, }; +enum { + VARIANT_NONE, + VARIANT_S2MPS11, +}; + #endif -- cgit v1.3.1 From 2d49a7e75ce176b07288397742fb9a72d6caf134 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:58:18 +0530 Subject: power: regulator: s2mps11: declaratively define LDOs and BUCKs In the Linux kernel driver, all information related to LDO and BUCK regulators are stored in descriptive arrays. This also allows multiple variants to be supported by the same driver. Define a struct sec_regulator_desc which holds all values required by a regulator. Create an array of said struct containing all regulators. The descriptors are designed to follow a style similar to what's seen in the Linux driver, so comparing one with the other is simple. In functions such as s2mps11_{buck,ldo}_{val,mode} these values are to be used, make necessary modifications to pull them from the descriptors. Since multiple variants have varying descriptors, select them from within a switch-case block. Functions s2mps11_{buck,ldo}_{volt2hex,hex2volt} and arrays s2mps11_buck_{ctrl,out} are phased out as the calculations are now hardcoded in descriptors, thusly, it reduces clutter and enhances readability. Two macros in s2mps11.h, S2MPS11_LDO_NUM and S2MPS11_BUCK_NUM are removed as they are no longer being used. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/power/regulator/s2mps11_regulator.c | 364 +++++++++++++++------------- include/power/s2mps11.h | 17 +- 2 files changed, 205 insertions(+), 176 deletions(-) (limited to 'drivers') diff --git a/drivers/power/regulator/s2mps11_regulator.c b/drivers/power/regulator/s2mps11_regulator.c index 96de55065fe..db981df53ae 100644 --- a/drivers/power/regulator/s2mps11_regulator.c +++ b/drivers/power/regulator/s2mps11_regulator.c @@ -13,6 +13,112 @@ #include #include +#define regulator_desc_s2mps11_buck(num, mask, min, step, max_hex) \ + [num] = { \ + .mode_reg = S2MPS11_REG_B##num##CTRL1, \ + .mode_mask = S2MPS11_BUCK_MODE_MASK << S2MPS11_BUCK_MODE_SHIFT, \ + .volt_reg = S2MPS11_REG_B##num##CTRL2, \ + .volt_mask = mask, \ + .volt_min = min, \ + .volt_step = step, \ + .volt_max_hex = max_hex, \ + } + +#define regulator_desc_s2mps11_buck1_2_3_4_6(num) \ + regulator_desc_s2mps11_buck(num, S2MPS11_BUCK_VOLT_MASK, \ + S2MPS11_BUCK_UV_MIN, \ + S2MPS11_BUCK_LSTEP, \ + S2MPS11_BUCK_VOLT_MAX_HEX) + +#define regulator_desc_s2mps11_buck5 \ + regulator_desc_s2mps11_buck(5, S2MPS11_BUCK_VOLT_MASK, \ + S2MPS11_BUCK_UV_MIN, \ + S2MPS11_BUCK_LSTEP, \ + S2MPS11_BUCK5_VOLT_MAX_HEX) + +#define regulator_desc_s2mps11_buck7_8_10(num) \ + regulator_desc_s2mps11_buck(num, S2MPS11_BUCK_VOLT_MASK, \ + S2MPS11_BUCK_UV_HMIN, \ + S2MPS11_BUCK_HSTEP, \ + S2MPS11_BUCK7_8_10_VOLT_MAX_HEX) + +#define regulator_desc_s2mps11_buck9 \ + regulator_desc_s2mps11_buck(9, S2MPS11_BUCK9_VOLT_MASK, \ + S2MPS11_BUCK_UV_MIN, \ + S2MPS11_BUCK9_STEP, \ + S2MPS11_BUCK9_VOLT_MAX_HEX) + +static const struct sec_regulator_desc s2mps11_buck_desc[] = { + regulator_desc_s2mps11_buck1_2_3_4_6(1), + regulator_desc_s2mps11_buck1_2_3_4_6(2), + regulator_desc_s2mps11_buck1_2_3_4_6(3), + regulator_desc_s2mps11_buck1_2_3_4_6(4), + regulator_desc_s2mps11_buck5, + regulator_desc_s2mps11_buck1_2_3_4_6(6), + regulator_desc_s2mps11_buck7_8_10(7), + regulator_desc_s2mps11_buck7_8_10(8), + regulator_desc_s2mps11_buck9, + regulator_desc_s2mps11_buck7_8_10(10), +}; + +#define regulator_desc_s2mps11_ldo(num, step) \ + [num] = { \ + .mode_reg = S2MPS11_REG_L##num##CTRL, \ + .mode_mask = S2MPS11_LDO_MODE_MASK << S2MPS11_LDO_MODE_SHIFT, \ + .volt_reg = S2MPS11_REG_L##num##CTRL, \ + .volt_mask = S2MPS11_LDO_VOLT_MASK, \ + .volt_min = S2MPS11_LDO_UV_MIN, \ + .volt_step = step, \ + .volt_max_hex = S2MPS11_LDO_VOLT_MAX_HEX \ + } + +#define regulator_desc_s2mps11_ldo_type1(num) \ + regulator_desc_s2mps11_ldo(num, S2MPS11_LDO_STEP) + +#define regulator_desc_s2mps11_ldo_type2(num) \ + regulator_desc_s2mps11_ldo(num, S2MPS11_LDO_STEP * 2) + +static const struct sec_regulator_desc s2mps11_ldo_desc[] = { + regulator_desc_s2mps11_ldo_type1(1), + regulator_desc_s2mps11_ldo_type2(2), + regulator_desc_s2mps11_ldo_type2(3), + regulator_desc_s2mps11_ldo_type2(4), + regulator_desc_s2mps11_ldo_type2(5), + regulator_desc_s2mps11_ldo_type1(6), + regulator_desc_s2mps11_ldo_type2(7), + regulator_desc_s2mps11_ldo_type2(8), + regulator_desc_s2mps11_ldo_type2(9), + regulator_desc_s2mps11_ldo_type2(10), + regulator_desc_s2mps11_ldo_type1(11), + regulator_desc_s2mps11_ldo_type2(12), + regulator_desc_s2mps11_ldo_type2(13), + regulator_desc_s2mps11_ldo_type2(14), + regulator_desc_s2mps11_ldo_type2(15), + regulator_desc_s2mps11_ldo_type2(16), + regulator_desc_s2mps11_ldo_type2(17), + regulator_desc_s2mps11_ldo_type2(18), + regulator_desc_s2mps11_ldo_type2(19), + regulator_desc_s2mps11_ldo_type2(20), + regulator_desc_s2mps11_ldo_type2(21), + regulator_desc_s2mps11_ldo_type1(22), + regulator_desc_s2mps11_ldo_type1(23), + regulator_desc_s2mps11_ldo_type2(24), + regulator_desc_s2mps11_ldo_type2(25), + regulator_desc_s2mps11_ldo_type2(26), + regulator_desc_s2mps11_ldo_type1(27), + regulator_desc_s2mps11_ldo_type2(28), + regulator_desc_s2mps11_ldo_type2(29), + regulator_desc_s2mps11_ldo_type2(30), + regulator_desc_s2mps11_ldo_type2(31), + regulator_desc_s2mps11_ldo_type2(32), + regulator_desc_s2mps11_ldo_type2(33), + regulator_desc_s2mps11_ldo_type2(34), + regulator_desc_s2mps11_ldo_type1(35), + regulator_desc_s2mps11_ldo_type2(36), + regulator_desc_s2mps11_ldo_type2(37), + regulator_desc_s2mps11_ldo_type2(38), +}; + #define MODE(_id, _val, _name) { \ .id = _id, \ .register_value = _val, \ @@ -33,94 +139,37 @@ static struct dm_regulator_mode s2mps11_ldo_modes[] = { MODE(OP_ON, S2MPS11_LDO_MODE_ON, "ON"), }; -static const char s2mps11_buck_ctrl[] = { - 0xff, 0x25, 0x27, 0x29, 0x2b, 0x2d, 0x33, 0x35, 0x37, 0x39, 0x3b -}; - -static const char s2mps11_buck_out[] = { - 0xff, 0x26, 0x28, 0x2a, 0x2c, 0x2f, 0x34, 0x36, 0x38, 0x3a, 0x3c -}; - -static int s2mps11_buck_hex2volt(int buck, int hex) +static const ulong s2mps11_get_variant(struct udevice *dev) { - unsigned int uV = 0; - - if (hex < 0) - goto bad; - - switch (buck) { - case 7: - case 8: - case 10: - if (hex > S2MPS11_BUCK7_8_10_VOLT_MAX_HEX) - goto bad; + struct udevice *parent = dev_get_parent(dev); - uV = hex * S2MPS11_BUCK_HSTEP + S2MPS11_BUCK_UV_HMIN; - break; - case 9: - if (hex > S2MPS11_BUCK9_VOLT_MAX_HEX) - goto bad; - uV = hex * S2MPS11_BUCK9_STEP * 2 + S2MPS11_BUCK9_UV_MIN; - break; - default: - if (buck == 5 && hex > S2MPS11_BUCK5_VOLT_MAX_HEX) - goto bad; - else if (buck != 5 && hex > S2MPS11_BUCK_VOLT_MAX_HEX) - goto bad; - - uV = hex * S2MPS11_BUCK_LSTEP + S2MPS11_BUCK_UV_MIN; - break; + if (!parent) { + pr_err("Parent is non-existent, this shouldn't happen!\n"); + return VARIANT_NONE; } - return uV; -bad: - pr_err("Value: %#x is wrong for BUCK%d", hex, buck); - return -EINVAL; + return dev_get_driver_data(parent); } -static int s2mps11_buck_volt2hex(int buck, int uV) +static int s2mps11_buck_val(struct udevice *dev, int op, int *uV) { - int hex; - - switch (buck) { - case 7: - case 8: - case 10: - hex = (uV - S2MPS11_BUCK_UV_HMIN) / S2MPS11_BUCK_HSTEP; - if (hex > S2MPS11_BUCK7_8_10_VOLT_MAX_HEX) - goto bad; + const struct sec_regulator_desc *buck_desc; + int num_bucks, hex, buck, ret; + u32 addr; + u8 val; - break; - case 9: - hex = (uV - S2MPS11_BUCK9_UV_MIN) / S2MPS11_BUCK9_STEP; - if (hex > S2MPS11_BUCK9_VOLT_MAX_HEX) - goto bad; + switch (s2mps11_get_variant(dev)) { + case VARIANT_S2MPS11: + buck_desc = s2mps11_buck_desc; + num_bucks = ARRAY_SIZE(s2mps11_buck_desc); break; default: - hex = (uV - S2MPS11_BUCK_UV_MIN) / S2MPS11_BUCK_LSTEP; - if (buck == 5 && hex > S2MPS11_BUCK5_VOLT_MAX_HEX) - goto bad; - else if (buck != 5 && hex > S2MPS11_BUCK_VOLT_MAX_HEX) - goto bad; - break; - }; - - if (hex >= 0) - return hex; - -bad: - pr_err("Value: %d uV is wrong for BUCK%d", uV, buck); - return -EINVAL; -} - -static int s2mps11_buck_val(struct udevice *dev, int op, int *uV) -{ - int hex, buck, ret; - u32 mask, addr; - u8 val; + pr_err("Unknown device type\n"); + return -EINVAL; + } buck = dev->driver_data; - if (buck < 1 || buck > S2MPS11_BUCK_NUM) { + if (buck < 1 || buck > num_bucks) { pr_err("Wrong buck number: %d\n", buck); return -EINVAL; } @@ -128,35 +177,25 @@ static int s2mps11_buck_val(struct udevice *dev, int op, int *uV) if (op == PMIC_OP_GET) *uV = 0; - addr = s2mps11_buck_out[buck]; - - switch (buck) { - case 9: - mask = S2MPS11_BUCK9_VOLT_MASK; - break; - default: - mask = S2MPS11_BUCK_VOLT_MASK; - break; - } + addr = buck_desc[buck].volt_reg; ret = pmic_read(dev->parent, addr, &val, 1); if (ret) return ret; if (op == PMIC_OP_GET) { - val &= mask; - ret = s2mps11_buck_hex2volt(buck, val); - if (ret < 0) - return ret; - *uV = ret; + val &= buck_desc[buck].volt_mask; + *uV = val * buck_desc[buck].volt_step + buck_desc[buck].volt_min; return 0; } - hex = s2mps11_buck_volt2hex(buck, *uV); - if (hex < 0) - return hex; + hex = (*uV - buck_desc[buck].volt_min) / buck_desc[buck].volt_step; + if (hex > buck_desc[buck].volt_max_hex) { + pr_err("Value: %d uV is wrong for LDO%d\n", *uV, buck); + return -EINVAL; + } - val &= ~mask; + val &= ~buck_desc[buck].volt_mask; val |= hex; ret = pmic_write(dev->parent, addr, &val, 1); @@ -165,24 +204,35 @@ static int s2mps11_buck_val(struct udevice *dev, int op, int *uV) static int s2mps11_buck_mode(struct udevice *dev, int op, int *opmode) { + const struct sec_regulator_desc *buck_desc; unsigned int addr, mode; unsigned char val; - int buck, ret; + int num_bucks, buck, ret; + + switch (s2mps11_get_variant(dev)) { + case VARIANT_S2MPS11: + buck_desc = s2mps11_buck_desc; + num_bucks = ARRAY_SIZE(s2mps11_buck_desc); + break; + default: + pr_err("Unknown device type\n"); + return -EINVAL; + } buck = dev->driver_data; - if (buck < 1 || buck > S2MPS11_BUCK_NUM) { + if (buck < 1 || buck > num_bucks) { pr_err("Wrong buck number: %d\n", buck); return -EINVAL; } - addr = s2mps11_buck_ctrl[buck]; + addr = buck_desc[buck].mode_reg; ret = pmic_read(dev->parent, addr, &val, 1); if (ret) return ret; if (op == PMIC_OP_GET) { - val &= (S2MPS11_BUCK_MODE_MASK << S2MPS11_BUCK_MODE_SHIFT); + val &= buck_desc[buck].mode_mask; switch (val) { case S2MPS11_BUCK_MODE_OFF: *opmode = OP_OFF; @@ -214,7 +264,7 @@ static int s2mps11_buck_mode(struct udevice *dev, int op, int *opmode) return -EINVAL; } - val &= ~(S2MPS11_BUCK_MODE_MASK << S2MPS11_BUCK_MODE_SHIFT); + val &= ~buck_desc[buck].mode_mask; val |= mode; ret = pmic_write(dev->parent, addr, &val, 1); @@ -331,95 +381,51 @@ U_BOOT_DRIVER(s2mps11_buck) = { .probe = s2mps11_buck_probe, }; -static int s2mps11_ldo_hex2volt(int ldo, int hex) +static int s2mps11_ldo_val(struct udevice *dev, int op, int *uV) { - unsigned int uV = 0; - - if (hex > S2MPS11_LDO_VOLT_MAX_HEX) { - pr_err("Value: %#x is wrong for LDO%d", hex, ldo); - return -EINVAL; - } - - switch (ldo) { - case 1: - case 6: - case 11: - case 22: - case 23: - case 27: - case 35: - uV = hex * S2MPS11_LDO_STEP + S2MPS11_LDO_UV_MIN; - break; - default: - uV = hex * S2MPS11_LDO_STEP * 2 + S2MPS11_LDO_UV_MIN; - break; - } - - return uV; -} + const struct sec_regulator_desc *ldo_desc; + unsigned int addr; + unsigned char val; + int num_ldos, hex, ldo, ret; -static int s2mps11_ldo_volt2hex(int ldo, int uV) -{ - int hex = 0; - - switch (ldo) { - case 1: - case 6: - case 11: - case 22: - case 23: - case 27: - case 35: - hex = (uV - S2MPS11_LDO_UV_MIN) / S2MPS11_LDO_STEP; + switch (s2mps11_get_variant(dev)) { + case VARIANT_S2MPS11: + ldo_desc = s2mps11_ldo_desc; + num_ldos = ARRAY_SIZE(s2mps11_ldo_desc); break; default: - hex = (uV - S2MPS11_LDO_UV_MIN) / (S2MPS11_LDO_STEP * 2); - break; + pr_err("Unknown device type\n"); + return -EINVAL; } - if (hex >= 0 && hex <= S2MPS11_LDO_VOLT_MAX_HEX) - return hex; - - pr_err("Value: %d uV is wrong for LDO%d", uV, ldo); - return -EINVAL; - - return 0; -} - -static int s2mps11_ldo_val(struct udevice *dev, int op, int *uV) -{ - unsigned int addr; - unsigned char val; - int hex, ldo, ret; - ldo = dev->driver_data; - if (ldo < 1 || ldo > S2MPS11_LDO_NUM) { + if (ldo < 1 || ldo > num_ldos) { pr_err("Wrong ldo number: %d\n", ldo); return -EINVAL; } - addr = S2MPS11_REG_L1CTRL + ldo - 1; + addr = ldo_desc[ldo].volt_reg; + + if (op == PMIC_OP_GET) + *uV = 0; ret = pmic_read(dev->parent, addr, &val, 1); if (ret) return ret; if (op == PMIC_OP_GET) { - *uV = 0; - val &= S2MPS11_LDO_VOLT_MASK; - ret = s2mps11_ldo_hex2volt(ldo, val); - if (ret < 0) - return ret; - - *uV = ret; + val &= ldo_desc[ldo].volt_mask; + *uV = val * ldo_desc[ldo].volt_step + ldo_desc[ldo].volt_min; return 0; } - hex = s2mps11_ldo_volt2hex(ldo, *uV); - if (hex < 0) - return hex; + hex = (*uV - ldo_desc[ldo].volt_min) / ldo_desc[ldo].volt_step; + if (hex > ldo_desc[ldo].volt_max_hex) { + pr_err("Value: %d uV is wrong for LDO%d\n", *uV, ldo); + return -EINVAL; + } - val &= ~S2MPS11_LDO_VOLT_MASK; + val &= ~ldo_desc[ldo].volt_mask; val |= hex; ret = pmic_write(dev->parent, addr, &val, 1); @@ -428,23 +434,35 @@ static int s2mps11_ldo_val(struct udevice *dev, int op, int *uV) static int s2mps11_ldo_mode(struct udevice *dev, int op, int *opmode) { + const struct sec_regulator_desc *ldo_desc; unsigned int addr, mode; unsigned char val; - int ldo, ret; + int num_ldos, ldo, ret; + + switch (s2mps11_get_variant(dev)) { + case VARIANT_S2MPS11: + ldo_desc = s2mps11_ldo_desc; + num_ldos = ARRAY_SIZE(s2mps11_ldo_desc); + break; + default: + pr_err("Unknown device type\n"); + return -EINVAL; + } ldo = dev->driver_data; - if (ldo < 1 || ldo > S2MPS11_LDO_NUM) { + if (ldo < 1 || ldo > num_ldos) { pr_err("Wrong ldo number: %d\n", ldo); return -EINVAL; } - addr = S2MPS11_REG_L1CTRL + ldo - 1; + + addr = ldo_desc[ldo].mode_reg; ret = pmic_read(dev->parent, addr, &val, 1); if (ret) return ret; if (op == PMIC_OP_GET) { - val &= (S2MPS11_LDO_MODE_MASK << S2MPS11_LDO_MODE_SHIFT); + val &= ldo_desc[ldo].mode_mask; switch (val) { case S2MPS11_LDO_MODE_OFF: *opmode = OP_OFF; @@ -482,7 +500,7 @@ static int s2mps11_ldo_mode(struct udevice *dev, int op, int *opmode) return -EINVAL; } - val &= ~(S2MPS11_LDO_MODE_MASK << S2MPS11_LDO_MODE_SHIFT); + val &= ~ldo_desc[ldo].mode_mask; val |= mode; ret = pmic_write(dev->parent, addr, &val, 1); diff --git a/include/power/s2mps11.h b/include/power/s2mps11.h index c08bea5a516..dfbb5f1c165 100644 --- a/include/power/s2mps11.h +++ b/include/power/s2mps11.h @@ -106,9 +106,6 @@ enum s2mps11_reg { #define S2MPS11_LDO26_ENABLE 0xec -#define S2MPS11_LDO_NUM 26 -#define S2MPS11_BUCK_NUM 10 - /* Driver name */ #define S2MPS11_BUCK_DRIVER "s2mps11_buck" #define S2MPS11_OF_BUCK_PREFIX "BUCK" @@ -153,6 +150,20 @@ enum s2mps11_reg { #define S2MPS11_LDO_MODE_STANDBY_LPM (0x2 << 6) #define S2MPS11_LDO_MODE_ON (0x3 << 6) +struct sec_regulator_desc { + /* regulator mode control */ + unsigned int mode_reg; + unsigned int mode_mask; + + /* regulator voltage control */ + unsigned int volt_reg; + unsigned int volt_mask; + + unsigned int volt_min; + unsigned int volt_step; + unsigned int volt_max_hex; +}; + enum { OP_OFF = 0, OP_LPM, -- cgit v1.3.1 From 3d4143ac3aefe54ad77151378f44df94de7e10da Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:58:19 +0530 Subject: power: regulator: s2mps11: declaratively get/set regulator mode The functions s2mps11_{buck,ldo}_mode use the s2mps11_{buck,ldo}_modes arrays directly in order to extract the mode of a certain register. This approach does not allow similar devices of other variants (which may support a different set of modes) to work with the same driver. Instead of using these arrays hardcoded, extract them from the device's uclass platform data. Now the responsibility of setting these arrays properly is done by functions s2mps11_{buck,ldo}_probe, by implementing a switch-case block which can support modes of multiple variants if and when added. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/power/regulator/s2mps11_regulator.c | 132 +++++++++++++--------------- 1 file changed, 59 insertions(+), 73 deletions(-) (limited to 'drivers') diff --git a/drivers/power/regulator/s2mps11_regulator.c b/drivers/power/regulator/s2mps11_regulator.c index db981df53ae..17a0a4d7ce6 100644 --- a/drivers/power/regulator/s2mps11_regulator.c +++ b/drivers/power/regulator/s2mps11_regulator.c @@ -204,10 +204,11 @@ static int s2mps11_buck_val(struct udevice *dev, int op, int *uV) static int s2mps11_buck_mode(struct udevice *dev, int op, int *opmode) { + struct dm_regulator_uclass_plat *uc_pdata = dev_get_uclass_plat(dev); const struct sec_regulator_desc *buck_desc; unsigned int addr, mode; unsigned char val; - int num_bucks, buck, ret; + int num_bucks, buck, ret, i; switch (s2mps11_get_variant(dev)) { case VARIANT_S2MPS11: @@ -233,42 +234,29 @@ static int s2mps11_buck_mode(struct udevice *dev, int op, int *opmode) if (op == PMIC_OP_GET) { val &= buck_desc[buck].mode_mask; - switch (val) { - case S2MPS11_BUCK_MODE_OFF: - *opmode = OP_OFF; - break; - case S2MPS11_BUCK_MODE_STANDBY: - *opmode = OP_STANDBY; - break; - case S2MPS11_BUCK_MODE_ON: - *opmode = OP_ON; - break; - default: - return -EINVAL; + for (i = 0; i < uc_pdata->mode_count; i++) { + if (uc_pdata->mode[i].register_value != val) + continue; + + *opmode = uc_pdata->mode[i].id; + return 0; } - return 0; - } - switch (*opmode) { - case OP_OFF: - mode = S2MPS11_BUCK_MODE_OFF; - break; - case OP_STANDBY: - mode = S2MPS11_BUCK_MODE_STANDBY; - break; - case OP_ON: - mode = S2MPS11_BUCK_MODE_ON; - break; - default: - pr_err("Wrong mode: %d for buck: %d\n", *opmode, buck); return -EINVAL; } - val &= ~buck_desc[buck].mode_mask; - val |= mode; - ret = pmic_write(dev->parent, addr, &val, 1); + for (i = 0; i < uc_pdata->mode_count; i++) { + if (uc_pdata->mode[i].id != *opmode) + continue; - return ret; + mode = uc_pdata->mode[i].register_value; + val &= ~buck_desc[buck].mode_mask; + val |= mode; + return pmic_write(dev->parent, addr, &val, 1); + } + + pr_err("Wrong mode: %d for buck: %d\n", *opmode, buck); + return -EINVAL; } static int s2mps11_buck_enable(struct udevice *dev, int op, bool *enable) @@ -357,10 +345,17 @@ static int s2mps11_buck_probe(struct udevice *dev) struct dm_regulator_uclass_plat *uc_pdata; uc_pdata = dev_get_uclass_plat(dev); - uc_pdata->type = REGULATOR_TYPE_BUCK; - uc_pdata->mode = s2mps11_buck_modes; - uc_pdata->mode_count = ARRAY_SIZE(s2mps11_buck_modes); + + switch (s2mps11_get_variant(dev)) { + case VARIANT_S2MPS11: + uc_pdata->mode = s2mps11_buck_modes; + uc_pdata->mode_count = ARRAY_SIZE(s2mps11_buck_modes); + break; + default: + pr_err("Unknown device type\n"); + return -EINVAL; + } return 0; } @@ -434,10 +429,11 @@ static int s2mps11_ldo_val(struct udevice *dev, int op, int *uV) static int s2mps11_ldo_mode(struct udevice *dev, int op, int *opmode) { + struct dm_regulator_uclass_plat *uc_pdata = dev_get_uclass_plat(dev); const struct sec_regulator_desc *ldo_desc; unsigned int addr, mode; unsigned char val; - int num_ldos, ldo, ret; + int num_ldos, ldo, ret, i; switch (s2mps11_get_variant(dev)) { case VARIANT_S2MPS11: @@ -463,48 +459,30 @@ static int s2mps11_ldo_mode(struct udevice *dev, int op, int *opmode) if (op == PMIC_OP_GET) { val &= ldo_desc[ldo].mode_mask; - switch (val) { - case S2MPS11_LDO_MODE_OFF: - *opmode = OP_OFF; - break; - case S2MPS11_LDO_MODE_STANDBY: - *opmode = OP_STANDBY; - break; - case S2MPS11_LDO_MODE_STANDBY_LPM: - *opmode = OP_STANDBY_LPM; - break; - case S2MPS11_LDO_MODE_ON: - *opmode = OP_ON; - break; - default: - return -EINVAL; + + for (i = 0; i < uc_pdata->mode_count; i++) { + if (uc_pdata->mode[i].register_value != val) + continue; + + *opmode = uc_pdata->mode[i].id; + return 0; } - return 0; - } - switch (*opmode) { - case OP_OFF: - mode = S2MPS11_LDO_MODE_OFF; - break; - case OP_STANDBY: - mode = S2MPS11_LDO_MODE_STANDBY; - break; - case OP_STANDBY_LPM: - mode = S2MPS11_LDO_MODE_STANDBY_LPM; - break; - case OP_ON: - mode = S2MPS11_LDO_MODE_ON; - break; - default: - pr_err("Wrong mode: %d for ldo: %d\n", *opmode, ldo); return -EINVAL; } - val &= ~ldo_desc[ldo].mode_mask; - val |= mode; - ret = pmic_write(dev->parent, addr, &val, 1); + for (i = 0; i < uc_pdata->mode_count; i++) { + if (uc_pdata->mode[i].id != *opmode) + continue; - return ret; + mode = uc_pdata->mode[i].register_value; + val &= ~ldo_desc[ldo].mode_mask; + val |= mode; + return pmic_write(dev->parent, addr, &val, 1); + } + + pr_err("Wrong mode: %d for ldo: %d\n", *opmode, ldo); + return -EINVAL; } static int s2mps11_ldo_enable(struct udevice *dev, int op, bool *enable) @@ -602,8 +580,16 @@ static int s2mps11_ldo_probe(struct udevice *dev) uc_pdata = dev_get_uclass_plat(dev); uc_pdata->type = REGULATOR_TYPE_LDO; - uc_pdata->mode = s2mps11_ldo_modes; - uc_pdata->mode_count = ARRAY_SIZE(s2mps11_ldo_modes); + + switch (s2mps11_get_variant(dev)) { + case VARIANT_S2MPS11: + uc_pdata->mode = s2mps11_ldo_modes; + uc_pdata->mode_count = ARRAY_SIZE(s2mps11_ldo_modes); + break; + default: + pr_err("Unknown device type\n"); + return -EINVAL; + } return 0; } -- cgit v1.3.1 From defe12f306e5ca80d06a92796564f1fbef906181 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:58:20 +0530 Subject: power: regulator: s2mps11: add support for S2MPU05 PMIC Samsung's S2MPU05 PMIC is used by Exynos7870 SoC. It has 5 buck and 38 LDO regulators. Add support for this device variant in the driver. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/power/regulator/s2mps11_regulator.c | 110 +++++++++++++++++++++++++++ include/power/s2mps11.h | 112 ++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) (limited to 'drivers') diff --git a/drivers/power/regulator/s2mps11_regulator.c b/drivers/power/regulator/s2mps11_regulator.c index 17a0a4d7ce6..4b4353af639 100644 --- a/drivers/power/regulator/s2mps11_regulator.c +++ b/drivers/power/regulator/s2mps11_regulator.c @@ -119,6 +119,87 @@ static const struct sec_regulator_desc s2mps11_ldo_desc[] = { regulator_desc_s2mps11_ldo_type2(38), }; +#define regulator_desc_s2mpu05_buck(num, which) \ + [num] = { \ + .mode_reg = S2MPU05_REG_B##num##CTRL1, \ + .mode_mask = S2MPS11_BUCK_MODE_MASK << S2MPS11_BUCK_MODE_SHIFT, \ + .volt_reg = S2MPU05_REG_B##num##CTRL2, \ + .volt_mask = S2MPS11_BUCK_VOLT_MASK, \ + .volt_min = S2MPU05_BUCK_MIN##which, \ + .volt_step = S2MPU05_BUCK_STEP##which, \ + .volt_max_hex = S2MPS11_BUCK_VOLT_MASK, \ + } + +#define regulator_desc_s2mpu05_buck1_2_3(num) \ + regulator_desc_s2mpu05_buck(num, 1) + +#define regulator_desc_s2mpu05_buck4_5(num) \ + regulator_desc_s2mpu05_buck(num, 2) + +static const struct sec_regulator_desc s2mpu05_buck_desc[] = { + regulator_desc_s2mpu05_buck1_2_3(1), + regulator_desc_s2mpu05_buck1_2_3(2), + regulator_desc_s2mpu05_buck1_2_3(3), + regulator_desc_s2mpu05_buck4_5(4), + regulator_desc_s2mpu05_buck4_5(5), +}; + +#define regulator_desc_s2mpu05_ldo(num, reg, min, step) \ + [num] = { \ + .mode_reg = S2MPU05_REG_L##num##reg, \ + .mode_mask = S2MPS11_LDO_MODE_MASK << S2MPS11_LDO_MODE_SHIFT, \ + .volt_reg = S2MPU05_REG_L##num##reg, \ + .volt_mask = S2MPS11_LDO_VOLT_MASK, \ + .volt_min = min, \ + .volt_step = step, \ + .volt_max_hex = S2MPS11_LDO_VOLT_MAX_HEX, \ + } + +#define regulator_desc_s2mpu05_ldo_type1(num) \ + regulator_desc_s2mpu05_ldo(num, CTRL, S2MPU05_LDO_MIN1, \ + S2MPU05_LDO_STEP1) + +#define regulator_desc_s2mpu05_ldo_type2(num) \ + regulator_desc_s2mpu05_ldo(num, CTRL, S2MPU05_LDO_MIN1, \ + S2MPU05_LDO_STEP2) + +#define regulator_desc_s2mpu05_ldo_type3(num) \ + regulator_desc_s2mpu05_ldo(num, CTRL, S2MPU05_LDO_MIN2, \ + S2MPU05_LDO_STEP2) + +#define regulator_desc_s2mpu05_ldo_type4(num) \ + regulator_desc_s2mpu05_ldo(num, CTRL, S2MPU05_LDO_MIN3, \ + S2MPU05_LDO_STEP2) + +#define regulator_desc_s2mpu05_ldo_type5(num) \ + regulator_desc_s2mpu05_ldo(num, CTRL1, S2MPU05_LDO_MIN3, \ + S2MPU05_LDO_STEP2) + +static const struct sec_regulator_desc s2mpu05_ldo_desc[] = { + regulator_desc_s2mpu05_ldo_type4(1), + regulator_desc_s2mpu05_ldo_type3(2), + regulator_desc_s2mpu05_ldo_type2(3), + regulator_desc_s2mpu05_ldo_type1(4), + regulator_desc_s2mpu05_ldo_type1(5), + regulator_desc_s2mpu05_ldo_type1(6), + regulator_desc_s2mpu05_ldo_type2(7), + regulator_desc_s2mpu05_ldo_type3(8), + regulator_desc_s2mpu05_ldo_type5(9), + regulator_desc_s2mpu05_ldo_type4(10), + /* LDOs 11-24 are used for CP. They aren't documented. */ + regulator_desc_s2mpu05_ldo_type2(25), + regulator_desc_s2mpu05_ldo_type3(26), + regulator_desc_s2mpu05_ldo_type2(27), + regulator_desc_s2mpu05_ldo_type3(28), + regulator_desc_s2mpu05_ldo_type3(29), + regulator_desc_s2mpu05_ldo_type2(30), + regulator_desc_s2mpu05_ldo_type3(31), + regulator_desc_s2mpu05_ldo_type3(32), + regulator_desc_s2mpu05_ldo_type3(33), + regulator_desc_s2mpu05_ldo_type3(34), + regulator_desc_s2mpu05_ldo_type3(35), +}; + #define MODE(_id, _val, _name) { \ .id = _id, \ .register_value = _val, \ @@ -139,6 +220,11 @@ static struct dm_regulator_mode s2mps11_ldo_modes[] = { MODE(OP_ON, S2MPS11_LDO_MODE_ON, "ON"), }; +static struct dm_regulator_mode s2mpu05_regulator_modes[] = { + MODE(OP_OFF, S2MPS11_LDO_MODE_OFF, "OFF"), + MODE(OP_ON, S2MPS11_LDO_MODE_ON, "ON"), +}; + static const ulong s2mps11_get_variant(struct udevice *dev) { struct udevice *parent = dev_get_parent(dev); @@ -163,6 +249,10 @@ static int s2mps11_buck_val(struct udevice *dev, int op, int *uV) buck_desc = s2mps11_buck_desc; num_bucks = ARRAY_SIZE(s2mps11_buck_desc); break; + case VARIANT_S2MPU05: + buck_desc = s2mpu05_buck_desc; + num_bucks = ARRAY_SIZE(s2mpu05_buck_desc); + break; default: pr_err("Unknown device type\n"); return -EINVAL; @@ -215,6 +305,10 @@ static int s2mps11_buck_mode(struct udevice *dev, int op, int *opmode) buck_desc = s2mps11_buck_desc; num_bucks = ARRAY_SIZE(s2mps11_buck_desc); break; + case VARIANT_S2MPU05: + buck_desc = s2mpu05_buck_desc; + num_bucks = ARRAY_SIZE(s2mpu05_buck_desc); + break; default: pr_err("Unknown device type\n"); return -EINVAL; @@ -352,6 +446,10 @@ static int s2mps11_buck_probe(struct udevice *dev) uc_pdata->mode = s2mps11_buck_modes; uc_pdata->mode_count = ARRAY_SIZE(s2mps11_buck_modes); break; + case VARIANT_S2MPU05: + uc_pdata->mode = s2mpu05_regulator_modes; + uc_pdata->mode_count = ARRAY_SIZE(s2mpu05_regulator_modes); + break; default: pr_err("Unknown device type\n"); return -EINVAL; @@ -388,6 +486,10 @@ static int s2mps11_ldo_val(struct udevice *dev, int op, int *uV) ldo_desc = s2mps11_ldo_desc; num_ldos = ARRAY_SIZE(s2mps11_ldo_desc); break; + case VARIANT_S2MPU05: + ldo_desc = s2mpu05_ldo_desc; + num_ldos = ARRAY_SIZE(s2mpu05_ldo_desc); + break; default: pr_err("Unknown device type\n"); return -EINVAL; @@ -440,6 +542,10 @@ static int s2mps11_ldo_mode(struct udevice *dev, int op, int *opmode) ldo_desc = s2mps11_ldo_desc; num_ldos = ARRAY_SIZE(s2mps11_ldo_desc); break; + case VARIANT_S2MPU05: + ldo_desc = s2mpu05_ldo_desc; + num_ldos = ARRAY_SIZE(s2mpu05_ldo_desc); + break; default: pr_err("Unknown device type\n"); return -EINVAL; @@ -586,6 +692,10 @@ static int s2mps11_ldo_probe(struct udevice *dev) uc_pdata->mode = s2mps11_ldo_modes; uc_pdata->mode_count = ARRAY_SIZE(s2mps11_ldo_modes); break; + case VARIANT_S2MPU05: + uc_pdata->mode = s2mpu05_regulator_modes; + uc_pdata->mode_count = ARRAY_SIZE(s2mpu05_regulator_modes); + break; default: pr_err("Unknown device type\n"); return -EINVAL; diff --git a/include/power/s2mps11.h b/include/power/s2mps11.h index dfbb5f1c165..51eb79bdde1 100644 --- a/include/power/s2mps11.h +++ b/include/power/s2mps11.h @@ -150,6 +150,117 @@ enum s2mps11_reg { #define S2MPS11_LDO_MODE_STANDBY_LPM (0x2 << 6) #define S2MPS11_LDO_MODE_ON (0x3 << 6) +enum s2mpu05_reg { + S2MPU05_REG_ID, + S2MPU05_REG_INT1, + S2MPU05_REG_INT2, + S2MPU05_REG_INT3, + S2MPU05_REG_INT1M, + S2MPU05_REG_INT2M, + S2MPU05_REG_INT3M, + S2MPU05_REG_ST1, + S2MPU05_REG_ST2, + S2MPU05_REG_PWRONSRC, + S2MPU05_REG_OFFSRC, + S2MPU05_REG_BU_CHG, + S2MPU05_REG_RTC_BUF, + S2MPU05_REG_CTRL1, + S2MPU05_REG_CTRL2, + S2MPU05_REG_ETC_TEST, + S2MPU05_REG_OTP_ADRL, + S2MPU05_REG_OTP_ADRH, + S2MPU05_REG_OTP_DATA, + S2MPU05_REG_MON1SEL, + S2MPU05_REG_MON2SEL, + S2MPU05_REG_CTRL3, + S2MPU05_REG_ETC_OTP, + S2MPU05_REG_UVLO, + S2MPU05_REG_TIME_CTRL1, + S2MPU05_REG_TIME_CTRL2, + S2MPU05_REG_B1CTRL1, + S2MPU05_REG_B1CTRL2, + S2MPU05_REG_B2CTRL1, + S2MPU05_REG_B2CTRL2, + S2MPU05_REG_B2CTRL3, + S2MPU05_REG_B2CTRL4, + S2MPU05_REG_B3CTRL1, + S2MPU05_REG_B3CTRL2, + S2MPU05_REG_B3CTRL3, + S2MPU05_REG_B4CTRL1, + S2MPU05_REG_B4CTRL2, + S2MPU05_REG_B5CTRL1, + S2MPU05_REG_B5CTRL2, + S2MPU05_REG_BUCK_RAMP, + S2MPU05_REG_LDO_DVS1, + S2MPU05_REG_LDO_DVS9, + S2MPU05_REG_LDO_DVS10, + S2MPU05_REG_L1CTRL, + S2MPU05_REG_L2CTRL, + S2MPU05_REG_L3CTRL, + S2MPU05_REG_L4CTRL, + S2MPU05_REG_L5CTRL, + S2MPU05_REG_L6CTRL, + S2MPU05_REG_L7CTRL, + S2MPU05_REG_L8CTRL, + S2MPU05_REG_L9CTRL1, + S2MPU05_REG_L9CTRL2, + S2MPU05_REG_L10CTRL, + S2MPU05_REG_L11CTRL1, + S2MPU05_REG_L11CTRL2, + S2MPU05_REG_L12CTRL, + S2MPU05_REG_L13CTRL, + S2MPU05_REG_L14CTRL, + S2MPU05_REG_L15CTRL, + S2MPU05_REG_L16CTRL, + S2MPU05_REG_L17CTRL1, + S2MPU05_REG_L17CTRL2, + S2MPU05_REG_L18CTRL1, + S2MPU05_REG_L18CTRL2, + S2MPU05_REG_L19CTRL, + S2MPU05_REG_L20CTRL, + S2MPU05_REG_L21CTRL, + S2MPU05_REG_L22CTRL, + S2MPU05_REG_L23CTRL, + S2MPU05_REG_L24CTRL, + S2MPU05_REG_L25CTRL, + S2MPU05_REG_L26CTRL, + S2MPU05_REG_L27CTRL, + S2MPU05_REG_L28CTRL, + S2MPU05_REG_L29CTRL, + S2MPU05_REG_L30CTRL, + S2MPU05_REG_L31CTRL, + S2MPU05_REG_L32CTRL, + S2MPU05_REG_L33CTRL, + S2MPU05_REG_L34CTRL, + S2MPU05_REG_L35CTRL, + S2MPU05_REG_LDO_DSCH1, + S2MPU05_REG_LDO_DSCH2, + S2MPU05_REG_LDO_DSCH3, + S2MPU05_REG_LDO_DSCH4, + S2MPU05_REG_LDO_DSCH5, + S2MPU05_REG_LDO_CTRL1, + S2MPU05_REG_LDO_CTRL2, + S2MPU05_REG_TCXO_CTRL, + S2MPU05_REG_SELMIF, + S2MPU05_REG_COUNT, +}; + +#define S2MPU05_OF_BUCK_PREFIX "buck" +#define S2MPU05_OF_LDO_PREFIX "ldo" + +/* BUCK */ +#define S2MPU05_BUCK_MIN1 400000 +#define S2MPU05_BUCK_MIN2 600000 +#define S2MPU05_BUCK_STEP1 6250 +#define S2MPU05_BUCK_STEP2 12500 + +/* LDO */ +#define S2MPU05_LDO_MIN1 800000 +#define S2MPU05_LDO_MIN2 1800000 +#define S2MPU05_LDO_MIN3 400000 +#define S2MPU05_LDO_STEP1 12500 +#define S2MPU05_LDO_STEP2 25000 + struct sec_regulator_desc { /* regulator mode control */ unsigned int mode_reg; @@ -175,6 +286,7 @@ enum { enum { VARIANT_NONE, VARIANT_S2MPS11, + VARIANT_S2MPU05, }; #endif -- cgit v1.3.1 From bf8f1a984d66865a065634e2468f2879dfb98d96 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Fri, 17 Oct 2025 20:58:21 +0530 Subject: power: pmic: s2mps11: add support for Samsung S2MPU05 PMIC Samsung S2MPU05 PMIC is used in devices with the Exynos7870 SoC, it houses voltage regulators and an RTC module. Add support for this device variant in the driver, which also binds the corresponding voltage regulator driver. Signed-off-by: Kaustabh Chakraborty Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/power/pmic/s2mps11.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/power/pmic/s2mps11.c b/drivers/power/pmic/s2mps11.c index 51c98afb17b..5cf9d34ffaf 100644 --- a/drivers/power/pmic/s2mps11.c +++ b/drivers/power/pmic/s2mps11.c @@ -19,11 +19,19 @@ static const struct pmic_child_info s2mps11_pmic_children_info[] = { { }, }; +static const struct pmic_child_info s2mpu05_pmic_children_info[] = { + { .prefix = S2MPU05_OF_LDO_PREFIX, .driver = S2MPS11_LDO_DRIVER }, + { .prefix = S2MPU05_OF_BUCK_PREFIX, .driver = S2MPS11_BUCK_DRIVER }, + { }, +}; + static int s2mps11_reg_count(struct udevice *dev) { switch (dev_get_driver_data(dev)) { case VARIANT_S2MPS11: return S2MPS11_REG_COUNT; + case VARIANT_S2MPU05: + return S2MPU05_REG_COUNT; default: return -EINVAL; } @@ -71,6 +79,9 @@ static int s2mps11_bind(struct udevice *dev) case VARIANT_S2MPS11: pmic_children_info = s2mps11_pmic_children_info; break; + case VARIANT_S2MPU05: + pmic_children_info = s2mpu05_pmic_children_info; + break; default: debug("%s: unknown device type\n", __func__); return -EINVAL; @@ -91,6 +102,7 @@ static struct dm_pmic_ops s2mps11_ops = { static const struct udevice_id s2mps11_ids[] = { { .compatible = "samsung,s2mps11-pmic", .data = VARIANT_S2MPS11 }, + { .compatible = "samsung,s2mpu05-pmic", .data = VARIANT_S2MPU05 }, { } }; -- cgit v1.3.1 From 906ee6785b1cb5a51375b5a613a7cc56acced8c8 Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Tue, 21 Oct 2025 13:45:26 -0700 Subject: mmc: sd: Handle UHS-I voltage signaling without power cycle Some boards have SD card connectors where the power rail cannot be switched off by the driver. However there are various circumstances when a card might be re-initialized, such as after system resume, warm re-boot, or error handling. However, a UHS card will continue to use 1.8V signaling unless it is power cycled. If the card has not been power cycled, it may still be using 1.8V signaling. According to the SD spec., the Bus Speed Mode (function group 1) bits 2 to 4 are zero if the card is initialized at 3.3V signal level. Thus they can be used to determine if the card has already switched to 1.8V signaling. Detect that situation and try to initialize a UHS-I (1.8V) transfer mode. Signed-off-by: Tanmay Kathpalia Signed-off-by: Peng Fan --- drivers/mmc/mmc.c | 55 +++++++++++++++++++++++++++++++++++++++++++++---------- include/mmc.h | 3 +++ 2 files changed, 48 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 0f07955a32e..bf82c515600 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -643,6 +643,19 @@ static int mmc_switch_voltage(struct mmc *mmc, int signal_voltage) return 0; } + +static bool mmc_sd_card_using_v18(struct mmc *mmc) +{ + /* + * According to the SD spec., the Bus Speed Mode (function group 1) bits + * 2 to 4 are zero if the card is initialized at 3.3V signal level. Thus + * they can be used to determine if the card has already switched to + * 1.8V signaling. + */ + bool volt = mmc->sd3_bus_mode & + (SD_MODE_UHS_SDR50 | SD_MODE_UHS_SDR104 | SD_MODE_UHS_DDR50); + return volt; +} #endif static int sd_send_op_cond(struct mmc *mmc, bool uhs_en) @@ -1369,9 +1382,6 @@ static int sd_get_capabilities(struct mmc *mmc) ALLOC_CACHE_ALIGN_BUFFER(__be32, switch_status, 16); struct mmc_data data; int timeout; -#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) - u32 sd3_bus_mode; -#endif mmc->card_caps = MMC_MODE_1BIT | MMC_CAP(MMC_LEGACY); @@ -1451,16 +1461,16 @@ static int sd_get_capabilities(struct mmc *mmc) if (mmc->version < SD_VERSION_3) return 0; - sd3_bus_mode = __be32_to_cpu(switch_status[3]) >> 16 & 0x1f; - if (sd3_bus_mode & SD_MODE_UHS_SDR104) + mmc->sd3_bus_mode = __be32_to_cpu(switch_status[3]) >> 16 & 0x1f; + if (mmc->sd3_bus_mode & SD_MODE_UHS_SDR104) mmc->card_caps |= MMC_CAP(UHS_SDR104); - if (sd3_bus_mode & SD_MODE_UHS_SDR50) + if (mmc->sd3_bus_mode & SD_MODE_UHS_SDR50) mmc->card_caps |= MMC_CAP(UHS_SDR50); - if (sd3_bus_mode & SD_MODE_UHS_SDR25) + if (mmc->sd3_bus_mode & SD_MODE_UHS_SDR25) mmc->card_caps |= MMC_CAP(UHS_SDR25); - if (sd3_bus_mode & SD_MODE_UHS_SDR12) + if (mmc->sd3_bus_mode & SD_MODE_UHS_SDR12) mmc->card_caps |= MMC_CAP(UHS_SDR12); - if (sd3_bus_mode & SD_MODE_UHS_DDR50) + if (mmc->sd3_bus_mode & SD_MODE_UHS_DDR50) mmc->card_caps |= MMC_CAP(UHS_DDR50); #endif @@ -1830,7 +1840,11 @@ static int sd_select_mode_and_width(struct mmc *mmc, uint card_caps) uint widths[] = {MMC_MODE_4BIT, MMC_MODE_1BIT}; const struct mode_width_tuning *mwt; #if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) - bool uhs_en = (mmc->ocr & OCR_S18R) ? true : false; + /* + * Enable UHS mode if the card advertises 1.8V support (S18R in OCR) + * or is already operating at 1.8V signaling. + */ + bool uhs_en = (mmc->ocr & OCR_S18R) || mmc_sd_card_using_v18(mmc); #else bool uhs_en = false; #endif @@ -2701,6 +2715,27 @@ static int mmc_startup(struct mmc *mmc) err = sd_get_capabilities(mmc); if (err) return err; + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + /* + * If the card has already switched to 1.8V signaling, then + * set the signal voltage to 1.8V. + */ + if (mmc_sd_card_using_v18(mmc)) { + /* + * During a signal voltage level switch, the clock must be gated + * for 5 ms according to the SD spec. + */ + mmc_set_clock(mmc, mmc->clock, MMC_CLK_DISABLE); + err = mmc_set_signal_voltage(mmc, MMC_SIGNAL_VOLTAGE_180); + if (err) + return err; + /* Keep clock gated for at least 10 ms, though spec only says 5 ms */ + mdelay(10); + mmc_set_clock(mmc, mmc->clock, MMC_CLK_ENABLE); + } +#endif + err = sd_select_mode_and_width(mmc, mmc->card_caps); } else { err = mmc_get_capabilities(mmc); diff --git a/include/mmc.h b/include/mmc.h index c6b2ab4a29f..51d3f2f8dd5 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -759,6 +759,9 @@ struct mmc { #endif u8 *ext_csd; u32 cardtype; /* cardtype read from the MMC */ +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + u32 sd3_bus_mode; /* Supported UHS-I bus speed modes */ +#endif enum mmc_voltage current_voltage; enum bus_mode selected_mode; /* mode currently used */ enum bus_mode best_mode; /* best mode is the supported mode with the -- cgit v1.3.1 From d805a18ec60acdb0c2f28b56d336d026bad3c44e Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Wed, 22 Oct 2025 22:00:17 -0700 Subject: power: regulator: Remove duplicate include of regulator_common.h Remove duplicate #include "regulator_common.h" statements from regulator driver files. Signed-off-by: Tanmay Kathpalia Reviewed-by: Peng Fan Signed-off-by: Peng Fan --- drivers/power/regulator/fixed.c | 1 - drivers/power/regulator/gpio-regulator.c | 1 - drivers/power/regulator/regulator_common.c | 1 - 3 files changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/regulator/fixed.c b/drivers/power/regulator/fixed.c index 996da41546a..1dd137f493e 100644 --- a/drivers/power/regulator/fixed.c +++ b/drivers/power/regulator/fixed.c @@ -13,7 +13,6 @@ #include #include #include -#include "regulator_common.h" #include "regulator_common.h" diff --git a/drivers/power/regulator/gpio-regulator.c b/drivers/power/regulator/gpio-regulator.c index 38b22535c3d..787f8170234 100644 --- a/drivers/power/regulator/gpio-regulator.c +++ b/drivers/power/regulator/gpio-regulator.c @@ -12,7 +12,6 @@ #include #include #include -#include "regulator_common.h" #include "regulator_common.h" diff --git a/drivers/power/regulator/regulator_common.c b/drivers/power/regulator/regulator_common.c index c80f10c3aa3..3ed713ce501 100644 --- a/drivers/power/regulator/regulator_common.c +++ b/drivers/power/regulator/regulator_common.c @@ -9,7 +9,6 @@ #include #include #include -#include "regulator_common.h" #include "regulator_common.h" -- cgit v1.3.1 From e16018f6b22d36f9f6b9f881175547457c06dae3 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 23 Oct 2025 21:11:04 +0800 Subject: mmc: exynos_dw_mmc: guard execute_tuning Guard execute_tuning with MMC_SUPPORTS_TUNING, otherwise there will be build failure: drivers/mmc/exynos_dw_mmc.c:484:10: error: 'struct dm_mmc_ops' has no member named 'execute_tuning' .execute_tuning = exynos_dwmmc_execute_tuning, ^~~~~~~~~~~~~~ Cc: Kaustabh Chakraborty Acked-by: Kaustabh Chakraborty Signed-off-by: Peng Fan --- drivers/mmc/exynos_dw_mmc.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/exynos_dw_mmc.c b/drivers/mmc/exynos_dw_mmc.c index b6660fb2cd5..b230e9dbbf8 100644 --- a/drivers/mmc/exynos_dw_mmc.c +++ b/drivers/mmc/exynos_dw_mmc.c @@ -299,6 +299,7 @@ static int exynos_dwmmc_of_to_plat(struct udevice *dev) return 0; } +#if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING) static int exynos_dwmmc_get_best_clksmpl(u8 candidates) { u8 i; @@ -364,6 +365,7 @@ static int exynos_dwmmc_execute_tuning(struct udevice *dev, u32 opcode) return 0; } +#endif static int exynos_dwmmc_probe(struct udevice *dev) { @@ -481,7 +483,9 @@ static const struct udevice_id exynos_dwmmc_ids[] = { struct dm_mmc_ops exynos_dwmmc_ops = { .send_cmd = dwmci_send_cmd, .set_ios = dwmci_set_ios, +#if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING) .execute_tuning = exynos_dwmmc_execute_tuning, +#endif }; U_BOOT_DRIVER(exynos_dwmmc_drv) = { -- cgit v1.3.1