From f23b5e079a93245c01f618022fd76d189070d190 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 13 Jul 2026 21:32:52 +0800 Subject: mmc: fix mmc_poll_for_busy() false timeout when card is ready mmc_poll_for_busy() returns a false -ETIMEDOUT if the card becomes ready on the exact iteration where timeout_ms reaches 0. The card-ready check breaks out of the loop, but then the post-loop check "if (timeout_ms <= 0)" fires and returns -ETIMEDOUT despite the card being ready. Fix by returning 0 directly when the card is ready instead of breaking out of the loop. The only exit from the loop is now the timeout path, so the post-loop code unconditionally returns -ETIMEDOUT. Reviewed-by: Tanmay Kathpalia Signed-off-by: Peng Fan --- drivers/mmc/mmc.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 2e565560656..ef7defcde71 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -325,7 +325,7 @@ int mmc_poll_for_busy(struct mmc *mmc, int timeout_ms) if ((status & MMC_STATUS_RDY_FOR_DATA) && (status & MMC_STATUS_CURR_STATE) != MMC_STATE_PRG) - break; + return 0; if (status & MMC_STATUS_MASK) { #if !defined(CONFIG_XPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) @@ -340,14 +340,10 @@ int mmc_poll_for_busy(struct mmc *mmc, int timeout_ms) udelay(1000); } - if (timeout_ms <= 0) { #if !defined(CONFIG_XPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) - log_err("Timeout waiting card ready\n"); + log_err("Timeout waiting card ready\n"); #endif - return -ETIMEDOUT; - } - - return 0; + return -ETIMEDOUT; } int mmc_set_blocklen(struct mmc *mmc, int len) -- cgit v1.3.1 From 67d06fe102712dc07e05bbdf893fc48ba73c86a6 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 13 Jul 2026 21:32:53 +0800 Subject: mmc: rpmb: fix tautological condition in RPMB_REQ_READ_DATA validation In rpmb_route_frames(), the RPMB_REQ_READ_DATA case checks "req_cnt != 1 || !req_cnt" which is tautological -- !req_cnt (req_cnt == 0) is always a subset of req_cnt != 1. The second operand is dead code. Based on the pattern of all other cases in the switch (RPMB_REQ_KEY, RPMB_REQ_WRITE_DATA, RPMB_REQ_WCOUNTER) which validate rsp_cnt, this was meant to be "req_cnt != 1 || !rsp_cnt". Without this fix, a caller could pass rsp_cnt=0 for a read request without validation. Signed-off-by: Peng Fan --- drivers/mmc/rpmb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/rpmb.c b/drivers/mmc/rpmb.c index 8bfdffd56f5..26d2262ef45 100644 --- a/drivers/mmc/rpmb.c +++ b/drivers/mmc/rpmb.c @@ -452,7 +452,7 @@ static int rpmb_route_frames(struct mmc *mmc, struct s_rpmb *req, return rpmb_route_read_req(mmc, req, req_cnt, rsp, rsp_cnt); case RPMB_REQ_READ_DATA: - if (req_cnt != 1 || !req_cnt) + if (req_cnt != 1 || !rsp_cnt) return -EINVAL; return rpmb_route_read_req(mmc, req, req_cnt, rsp, rsp_cnt); -- cgit v1.3.1 From b95ce3f0945cac05c09a1837ea2401df78d766d4 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 13 Jul 2026 21:32:54 +0800 Subject: mmc: sdhci: fix align_buffer memory leak on re-initialization sdhci_init() allocates a 512KB aligned bounce buffer for controllers with SDHCI_QUIRK_32BIT_DMA_ADDR. In the non-DM code path, sdhci_init() is registered as ops->init and called on every mmc_init(). Each call allocates a new buffer without checking or freeing the previous one, leaking 512KB per re-initialization. Fix by guarding the allocation with !host->align_buffer so it only allocates on the first call. Reviewed-by: Tanmay Kathpalia Signed-off-by: Tanmay Kathpalia Signed-off-by: Peng Fan --- drivers/mmc/sdhci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/sdhci.c b/drivers/mmc/sdhci.c index 71a6d8ea2f5..962a99cd05a 100644 --- a/drivers/mmc/sdhci.c +++ b/drivers/mmc/sdhci.c @@ -733,7 +733,8 @@ static int sdhci_init(struct mmc *mmc) */ host->force_align_buffer = true; #else - if (host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) { + if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && + !host->align_buffer) { host->align_buffer = memalign(8, 512 * 1024); if (!host->align_buffer) { log_err("Aligned buffer alloc failed\n"); -- cgit v1.3.1 From 648263b69c329d157d2a28f6b74172640e611533 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 13 Jul 2026 21:32:55 +0800 Subject: mmc: fix mmc_bwrite() ignoring host get_b_max() callback mmc_bwrite() uses mmc->cfg->b_max directly to limit per-transfer block count. The read path (mmc_bread()) correctly calls mmc_get_b_max() which dispatches to the host driver get_b_max() callback. This callback allows host drivers to enforce DMA boundary constraints based on the buffer address. Fix mmc_bwrite() to use mmc_get_b_max() to match the read path. This requires removing the static qualifier from the non-DM mmc_get_b_max() and adding its declaration to mmc_private.h. Reviewed-by: Tanmay Kathpalia Signed-off-by: Peng Fan --- drivers/mmc/mmc.c | 2 +- drivers/mmc/mmc_private.h | 4 ++++ drivers/mmc/mmc_write.c | 6 ++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index ef7defcde71..5fd12d21f92 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -490,7 +490,7 @@ static int mmc_read_blocks(struct mmc *mmc, void *dst, lbaint_t start, } #if !CONFIG_IS_ENABLED(DM_MMC) -static int mmc_get_b_max(struct mmc *mmc, void *dst, lbaint_t blkcnt) +int mmc_get_b_max(struct mmc *mmc, void *dst, lbaint_t blkcnt) { if (mmc->cfg->ops->get_b_max) return mmc->cfg->ops->get_b_max(mmc, dst, blkcnt); diff --git a/drivers/mmc/mmc_private.h b/drivers/mmc/mmc_private.h index fc45f017e5d..24e68ceb5e6 100644 --- a/drivers/mmc/mmc_private.h +++ b/drivers/mmc/mmc_private.h @@ -17,6 +17,10 @@ int mmc_poll_for_busy(struct mmc *mmc, int timeout); int mmc_set_blocklen(struct mmc *mmc, int len); +#if !CONFIG_IS_ENABLED(DM_MMC) +int mmc_get_b_max(struct mmc *mmc, void *dst, lbaint_t blkcnt); +#endif + #if CONFIG_IS_ENABLED(BLK) ulong mmc_bread(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, void *dst); diff --git a/drivers/mmc/mmc_write.c b/drivers/mmc/mmc_write.c index 928c05872ca..9cf07b4ad1f 100644 --- a/drivers/mmc/mmc_write.c +++ b/drivers/mmc/mmc_write.c @@ -228,6 +228,7 @@ ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt, int dev_num = block_dev->devnum; lbaint_t cur, blocks_todo = blkcnt; int err; + uint b_max; struct mmc *mmc = find_mmc_device(dev_num); if (!mmc) @@ -240,9 +241,10 @@ ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt, if (mmc_set_blocklen(mmc, mmc->write_bl_len)) return 0; + b_max = mmc_get_b_max(mmc, (void *)src, blkcnt); + do { - cur = (blocks_todo > mmc->cfg->b_max) ? - mmc->cfg->b_max : blocks_todo; + cur = (blocks_todo > b_max) ? b_max : blocks_todo; if (mmc_write_blocks(mmc, start, cur, src) != cur) return 0; blocks_todo -= cur; -- cgit v1.3.1 From 4f5f30e77c69ccb981978365fdabe0825f041f88 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 13 Jul 2026 21:32:56 +0800 Subject: mmc: fix mmc_deinit regression when card is at 1.8V signaling Commit 906ee6785b1c ("mmc: sd: Handle UHS-I voltage signaling without power cycle") added detection of cards already operating at 1.8V signaling via mmc_sd_card_using_v18(). This correctly handles the warm-reboot scenario in mmc_startup(). However, mmc_deinit() strips ALL UHS capabilities from the card caps and calls sd_select_mode_and_width() to downgrade the card before kernel handoff. For a card that has switched to 1.8V, uhs_en is already true (via OCR_S18R in mmc->ocr), so sd_select_mode_and_width() attempts UHS mode selection -- but no UHS modes remain in the filtered caps. The non-UHS fallbacks (SD_HS, MMC_LEGACY) are 3.3V speed modes which, when selected while signaling at 1.8V, leave the card in an inconsistent state it cannot recover from without a power cycle. Per SD Physical Layer Specification: "Once the card enters 1.8V signaling mode, the card cannot be switched to 3.3V signaling without power cycle. If the card receives CMD0, card returns to Idle state but still works with SDR12 timing." Fix by using a UHS capability mask that preserves UHS_SDR12 when the card is operating at 1.8V. SDR12 is the minimum valid UHS-I mode and is always available at 1.8V signaling per the SD specification, so the card is left in a consistent (1.8V) state at a low, safe speed. Fixes: 906ee6785b1c ("mmc: sd: Handle UHS-I voltage signaling without power cycle") Reviewed-by: Tanmay Kathpalia Signed-off-by: Peng Fan --- drivers/mmc/mmc.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 5fd12d21f92..135fd835609 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -3173,10 +3173,19 @@ int mmc_deinit(struct mmc *mmc) return 0; if (IS_SD(mmc)) { - caps_filtered = mmc->card_caps & - ~(MMC_CAP(UHS_SDR12) | MMC_CAP(UHS_SDR25) | - MMC_CAP(UHS_SDR50) | MMC_CAP(UHS_DDR50) | - MMC_CAP(UHS_SDR104)); + u32 uhs_mask = UHS_CAPS; + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + /* + * Per SD spec, once a card enters 1.8V signaling it + * cannot revert to 3.3V without a power cycle. + * If the card is operating at 1.8V, keep UHS_SDR12 + * as the minimum fallback mode. + */ + if (mmc_sd_card_using_v18(mmc)) + uhs_mask &= ~MMC_CAP(UHS_SDR12); +#endif + caps_filtered = mmc->card_caps & ~uhs_mask; return sd_select_mode_and_width(mmc, caps_filtered); } else { -- cgit v1.3.1 From 7e613de952e7e407a14d7110d6697457a53a68bc Mon Sep 17 00:00:00 2001 From: Eric Chung Date: Wed, 22 Jul 2026 23:21:16 +0800 Subject: mmc: enable CMD23 for multi-block transfers Enable the support for the CMD23 (SET_BLOCK_COUNT) command to manage multi-block read/write operations. This allows the MMC core to use CMD23 in preference to the legacy CMD18/CMD25 plus CMD12 sequence, reducing command overhead and improving I/O performance on multi-block transfers. Signed-off-by: Eric Chung Signed-off-by: Tanmay Kathpalia [ Drop MMC_CAP_CMD32 from mvebu_mmc.h ] Signed-off-by: Peng Fan --- drivers/mmc/mmc.c | 16 +++++++++++++++- drivers/mmc/mmc_write.c | 19 +++++++++++++++---- include/mmc.h | 3 +++ include/mvebu_mmc.h | 2 -- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 135fd835609..670e92ee12b 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -457,6 +457,14 @@ static int mmc_read_blocks(struct mmc *mmc, void *dst, lbaint_t start, struct mmc_cmd cmd; struct mmc_data data; + if (blkcnt > 1 && (mmc->host_caps & MMC_CAP_CMD23)) { + cmd.cmdidx = MMC_CMD_SET_BLOCK_COUNT; + cmd.cmdarg = blkcnt & 0x0000ffff; + cmd.resp_type = MMC_RSP_R1; + if (mmc_send_cmd(mmc, &cmd, NULL)) + return 0; + } + if (blkcnt > 1) cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK; else @@ -477,7 +485,7 @@ static int mmc_read_blocks(struct mmc *mmc, void *dst, lbaint_t start, if (mmc_send_cmd(mmc, &cmd, &data)) return 0; - if (blkcnt > 1) { + if (blkcnt > 1 && !(mmc->host_caps & MMC_CAP_CMD23)) { if (mmc_send_stop_transmission(mmc, false)) { #if !defined(CONFIG_XPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) log_err("mmc fail to send stop cmd\n"); @@ -1033,6 +1041,10 @@ static int mmc_get_capabilities(struct mmc *mmc) mmc->card_caps = MMC_MODE_1BIT | MMC_CAP(MMC_LEGACY); + /* CMD23(SET_BLOCK_COUNT) requires eMMC spec v3.1 or above */ + if (mmc->version < MMC_VERSION_3) + mmc->host_caps &= ~MMC_CAP_CMD23; + if (mmc_host_is_spi(mmc)) return 0; @@ -1432,6 +1444,8 @@ static int sd_get_capabilities(struct mmc *mmc) if (mmc->scr[0] & SD_DATA_4BIT) mmc->card_caps |= MMC_MODE_4BIT; + if (!(mmc->scr[0] & SD_SCR_CMD23_SUPPORT)) + mmc->host_caps &= ~MMC_CAP_CMD23; /* Version 1.0 doesn't support switching */ if (mmc->version == SD_VERSION_1_0) diff --git a/drivers/mmc/mmc_write.c b/drivers/mmc/mmc_write.c index 9cf07b4ad1f..e739720b77c 100644 --- a/drivers/mmc/mmc_write.c +++ b/drivers/mmc/mmc_write.c @@ -165,7 +165,15 @@ static ulong mmc_write_blocks(struct mmc *mmc, lbaint_t start, if (blkcnt == 0) return 0; - else if (blkcnt == 1) + if (blkcnt > 1 && mmc->host_caps & MMC_CAP_CMD23) { + cmd.cmdidx = MMC_CMD_SET_BLOCK_COUNT; + cmd.cmdarg = blkcnt & 0x0000ffff; + cmd.resp_type = MMC_RSP_R1; + if (mmc_send_cmd(mmc, &cmd, NULL)) + return 0; + } + + if (blkcnt == 1) cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK; else cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK; @@ -191,10 +199,13 @@ static ulong mmc_write_blocks(struct mmc *mmc, lbaint_t start, */ } - /* SPI multiblock writes terminate using a special - * token, not a STOP_TRANSMISSION request. + /* + * SPI multiblock writes terminate using a special token, not CMD12. + * When CMD23 was issued the card auto-terminates, so CMD12 is also + * skipped in that case. */ - if (!mmc_host_is_spi(mmc) && blkcnt > 1) { + if (!mmc_host_is_spi(mmc) && blkcnt > 1 && + !(mmc->host_caps & MMC_CAP_CMD23)) { cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; cmd.cmdarg = 0; cmd.resp_type = MMC_RSP_R1b; diff --git a/include/mmc.h b/include/mmc.h index 9509c9e9543..4bda44ea220 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -67,6 +67,7 @@ struct bd_info; #define MMC_CAP_NONREMOVABLE BIT(14) #define MMC_CAP_NEEDS_POLL BIT(15) #define MMC_CAP_CD_ACTIVE_HIGH BIT(16) +#define MMC_CAP_CMD23 BIT(17) #define MMC_MODE_8BIT BIT(30) #define MMC_MODE_4BIT BIT(29) @@ -141,6 +142,8 @@ static inline bool mmc_is_tuning_cmd(uint cmdidx) #define SD_HIGHSPEED_BUSY 0x00020000 #define SD_HIGHSPEED_SUPPORTED 0x00020000 +#define SD_SCR_CMD23_SUPPORT BIT(1) + #define UHS_SDR12_BUS_SPEED 0 #define HIGH_SPEED_BUS_SPEED 1 #define UHS_SDR25_BUS_SPEED 1 diff --git a/include/mvebu_mmc.h b/include/mvebu_mmc.h index 0f6f5c23dee..258623d5c67 100644 --- a/include/mvebu_mmc.h +++ b/include/mvebu_mmc.h @@ -253,8 +253,6 @@ #define MMC_CAP_DRIVER_TYPE_C (1 << 24) /* Host supports Driver Type D */ #define MMC_CAP_DRIVER_TYPE_D (1 << 25) -/* CMD23 supported. */ -#define MMC_CAP_CMD23 (1 << 30) /* Hardware reset */ #define MMC_CAP_HW_RESET (1 << 31) -- cgit v1.3.1 From 2eb81ddca1a8095332e76948d233bd9c74008e31 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Jul 2026 00:54:26 +0200 Subject: power: domain: renesas-r8a78000: Add SDK SCP 4.36 map The SCMI IDs did not change between SDK SCP 4.32 and SDK SCP 4.36. Add SDK SCP 4.36 as supported version and reuse SDK SCP 4.32 remap table. Signed-off-by: Marek Vasut Signed-off-by: Peng Fan --- drivers/power/domain/renesas-r8a78000-power-domain.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/power/domain/renesas-r8a78000-power-domain.c b/drivers/power/domain/renesas-r8a78000-power-domain.c index 57b3b56b2d9..62b56fd8ae0 100644 --- a/drivers/power/domain/renesas-r8a78000-power-domain.c +++ b/drivers/power/domain/renesas-r8a78000-power-domain.c @@ -166,6 +166,7 @@ struct rst_map_in { #define GEN5_SCMI_SDK_4_30 0x010c0000 #define GEN5_SCMI_SDK_4_31 0x010d0000 #define GEN5_SCMI_SDK_4_32 0x010e0000 +#define GEN5_SCMI_SDK_4_36 0x01100000 static const struct rst_map_in gen5_rst_map_dt_sdk_4_28[] = { { SCP_RESET_DOMAIN_ID_UFS0, 202 }, @@ -220,7 +221,8 @@ static int gen5_reset_of_xlate(struct reset_ctl *reset_ctl, map = gen5_rst_map_dt_sdk_4_28; map_size = ARRAY_SIZE(gen5_rst_map_dt_sdk_4_28); } else if (priv->basever == GEN5_SCMI_SDK_4_31 || - priv->basever == GEN5_SCMI_SDK_4_32) { + priv->basever == GEN5_SCMI_SDK_4_32 || + priv->basever == GEN5_SCMI_SDK_4_36) { map = gen5_rst_map_dt_sdk_4_31; map_size = ARRAY_SIZE(gen5_rst_map_dt_sdk_4_31); } else { -- cgit v1.3.1 From 4233fbe4e39a67c9d0cd6f04471ec9039f0514a5 Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Thu, 23 Jul 2026 15:21:56 +0200 Subject: power: pmic: ADD PMIC PF9453 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support NXP PMIC pf9453. Reviewed-by: Ye Li Reviewed-by: Peng Fan Signed-off-by: Joy Zou Signed-off-by: Peng Fan Rebased [1] on main and checkpatch fixed. See also [2] [1] https://patchwork.ozlabs.org/project/uboot/patch/20240930035531.27079-3-peng.fan@oss.nxp.com/ [2] https://lists.u-boot-project.org/pipermail/u-boot/2026-July/625613.html Signed-off-by: Krzysztof Drobiński --- drivers/power/pmic/Kconfig | 15 ++++ drivers/power/pmic/Makefile | 1 + drivers/power/pmic/pf9453.c | 170 ++++++++++++++++++++++++++++++++++++++++++++ include/power/pf9453.h | 71 ++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 drivers/power/pmic/pf9453.c create mode 100644 include/power/pf9453.h diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig index d45869cb30f..95687d0bdfd 100644 --- a/drivers/power/pmic/Kconfig +++ b/drivers/power/pmic/Kconfig @@ -171,6 +171,21 @@ config SPL_DM_PMIC_PCA9450 This config enables implementation of driver-model pmic uclass features for PMIC PCA9450 in SPL. The driver implements read/write operations. +config DM_PMIC_PF9453 + bool "Enable Driver Model for PMIC PF9453" + depends on DM_I2C + help + This config enables implementation of driver-model pmic uclass features + for PMIC PF9453. The driver implements read/write operations. + +config SPL_DM_PMIC_PF9453 + bool "Enable Driver Model for PMIC PF9453 in SPL" + depends on SPL_DM_PMIC + depends on SPL_DM_I2C + help + This config enables implementation of driver-model pmic uclass features + for PMIC PF9453 in SPL. The driver implements read/write operations. + config DM_PMIC_PFUZE100 bool "Enable Driver Model for PMIC PFUZE100" help diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index 0ac92084221..da9b810de90 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_$(PHASE_)DM_PMIC_BD71837) += bd71837.o obj-$(CONFIG_$(PHASE_)DM_PMIC_MP5416) += mp5416.o obj-$(CONFIG_$(PHASE_)DM_PMIC_PFUZE100) += pfuze100.o obj-$(CONFIG_$(PHASE_)DM_PMIC_PCA9450) += pca9450.o +obj-$(CONFIG_$(PHASE_)DM_PMIC_PF9453) += pf9453.o obj-$(CONFIG_PMIC_S2MPS11) += s2mps11.o obj-$(CONFIG_DM_PMIC_SANDBOX) += sandbox.o i2c_pmic_emul.o obj-$(CONFIG_PMIC_AB8500) += ab8500.o diff --git a/drivers/power/pmic/pf9453.c b/drivers/power/pmic/pf9453.c new file mode 100644 index 00000000000..71d766bce5b --- /dev/null +++ b/drivers/power/pmic/pf9453.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2024 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +static const struct pmic_child_info pmic_children_info[] = { + /* buck */ + { .prefix = "b", .driver = PF9453_REGULATOR_DRIVER}, + { .prefix = "B", .driver = PF9453_REGULATOR_DRIVER}, + /* ldo */ + { .prefix = "l", .driver = PF9453_REGULATOR_DRIVER}, + { .prefix = "L", .driver = PF9453_REGULATOR_DRIVER}, + { }, +}; + +struct pf9453_priv { + struct gpio_desc *sd_vsel_gpio; +}; + +static int pf9453_reg_count(struct udevice *dev) +{ + return PF9453_REG_NUM; +} + +static bool is_reg_protect(uint reg) +{ + switch (reg) { + case PF9453_BUCK1OUT: + case PF9453_BUCK2OUT: + case PF9453_BUCK3OUT: + case PF9453_BUCK4OUT: + case PF9453_LDO1OUT_L: + case PF9453_LDO1OUT_H: + case PF9453_LDO2OUT: + case PF9453_LDOSNVS_CFG1: + case PF9453_BUCK2OUT_MAX_LIMIT: + case PF9453_BUCK2OUT_MIN_LIMIT: + return true; + default: + return false; + } +} + +static int pf9453_write(struct udevice *dev, uint reg, const uint8_t *buff, int len) +{ + bool is_pro; + u32 val = PF9453_UNLOCK_KEY; + + is_pro = is_reg_protect(reg); + if (is_pro) { + if (dm_i2c_write(dev, PF9453_REG_LOCK, (uint8_t *)&val, 1)) { + pr_err("write error to device: %p register: %#x!\n", dev, reg); + return -EIO; + } + + if (dm_i2c_write(dev, reg, buff, len)) { + pr_err("write error to device: %p register: %#x!\n", dev, reg); + return -EIO; + } + + val = PF9453_LOCK_KEY; + if (dm_i2c_write(dev, PF9453_REG_LOCK, (uint8_t *)&val, 1)) { + pr_err("write error to device: %p register: %#x!\n", dev, reg); + return -EIO; + } + } else { + if (dm_i2c_write(dev, reg, buff, len)) { + pr_err("write error to device: %p register: %#x!\n", dev, reg); + return -EIO; + } + } + + return 0; +} + +static int pf9453_read(struct udevice *dev, uint reg, uint8_t *buff, int len) +{ + if (dm_i2c_read(dev, reg, buff, len)) { + pr_err("read error from device: %p register: %#x!\n", dev, reg); + return -EIO; + } + + return 0; +} + +static int pf9453_bind(struct udevice *dev) +{ + int children; + ofnode regulators_node; + + regulators_node = dev_read_subnode(dev, "regulators"); + if (!ofnode_valid(regulators_node)) { + debug("%s: %s regulators subnode not found!", __func__, + dev->name); + return -ENXIO; + } + + debug("%s: '%s' - found regulators subnode\n", __func__, dev->name); + + children = pmic_bind_children(dev, regulators_node, + pmic_children_info); + if (!children) + debug("%s: %s - no child found\n", __func__, dev->name); + + /* Always return success for this device */ + return 0; +} + +static int pf9453_probe(struct udevice *dev) +{ + struct pf9453_priv *priv = dev_get_priv(dev); + unsigned int reset_ctrl; + int ret = 0; + + if (CONFIG_IS_ENABLED(DM_GPIO) && CONFIG_IS_ENABLED(DM_REGULATOR_PF9453)) { + priv->sd_vsel_gpio = devm_gpiod_get_optional(dev, "sd-vsel", + GPIOD_IS_OUT | + GPIOD_IS_OUT_ACTIVE); + if (IS_ERR(priv->sd_vsel_gpio)) { + ret = PTR_ERR(priv->sd_vsel_gpio); + dev_err(dev, "Failed to request SD_VSEL GPIO: %d\n", ret); + if (ret) + return ret; + } + } + + if (ofnode_read_bool(dev_ofnode(dev), "nxp,wdog_b-warm-reset")) + reset_ctrl = PF9453_PMIC_RESET_WDOG_B_CFG_WARM; + else + reset_ctrl = PF9453_PMIC_RESET_WDOG_B_CFG_COLD; + + return pmic_clrsetbits(dev, PF9453_RESET_CTRL, + PF9453_PMIC_RESET_WDOG_B_CFG_MASK, reset_ctrl); +} + +static struct dm_pmic_ops pf9453_ops = { + .reg_count = pf9453_reg_count, + .read = pf9453_read, + .write = pf9453_write, +}; + +static const struct udevice_id pf9453_ids[] = { + { .compatible = "nxp,pf9453", .data = NXP_CHIP_TYPE_PF9453, }, + { } +}; + +U_BOOT_DRIVER(pmic_pf9453) = { + .name = "pf9453 pmic", + .id = UCLASS_PMIC, + .of_match = pf9453_ids, + .bind = pf9453_bind, + .probe = pf9453_probe, + .ops = &pf9453_ops, + .priv_auto = sizeof(struct pf9453_priv), +}; diff --git a/include/power/pf9453.h b/include/power/pf9453.h new file mode 100644 index 00000000000..f33b839cde0 --- /dev/null +++ b/include/power/pf9453.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2024 NXP + */ + +#ifndef PF9453_H_ +#define PF9453_H_ + +#define PF9453_REGULATOR_DRIVER "pf9453_regulator" + +enum { + PF9453_REG_DEV_ID = 0x00, + PF9453_OTP_VER = 0x01, + PF9453_INT1 = 0x02, + PF9453_INT1_MSK = 0x03, + PF9453_INT1_STATUS = 0x04, + PF9453_VRFLT1_INT = 0x05, + PF9453_VRFLT1_MASK = 0x06, + PF9453_PWRON_STAT = 0x07, + PF9453_RESET_CTRL = 0x08, + PF9453_SW_RST = 0x09, + PF9453_PWR_CTRL = 0x0a, + PF9453_CONFIG1 = 0x0b, + PF9453_CONFIG2 = 0x0c, + PF9453_32K_CONFIG = 0x0d, + PF9453_BUCK1CTRL = 0x10, + PF9453_BUCK1OUT = 0x11, + PF9453_BUCK2CTRL = 0x14, + PF9453_BUCK2OUT = 0x15, + PF9453_BUCK2OUT_STBY = 0x1D, + PF9453_BUCK2OUT_MAX_LIMIT = 0x1F, + PF9453_BUCK2OUT_MIN_LIMIT = 0x20, + PF9453_BUCK3CTRL = 0x21, + PF9453_BUCK3OUT = 0x22, + PF9453_BUCK4CTRL = 0x2e, + PF9453_BUCK4OUT = 0x2f, + PF9453_LDO1OUT_L = 0x36, + PF9453_LDO1CFG = 0x37, + PF9453_LDO1OUT_H = 0x38, + PF9453_LDOSNVS_CFG1 = 0x39, + PF9453_LDOSNVS_CFG2 = 0x3a, + PF9453_LDO2CFG = 0x3b, + PF9453_LDO2OUT = 0x3c, + PF9453_BUCK_POK = 0x3d, + PF9453_LSW_CTRL1 = 0x40, + PF9453_LSW_CTRL2 = 0x41, + PF9453_REG_LOCK = 0x4e, + PF9453_REG_NUM, +}; + +int power_pf9453_init(unsigned char bus, unsigned char addr); + +enum { + NXP_CHIP_TYPE_PF9453 = 0, + NXP_CHIP_TYPE_AMOUNT +}; + +#define PF9453_UNLOCK_KEY 0x5c +#define PF9453_LOCK_KEY 0x0 + +#define PF9453_EN_MODE_MASK 0x3 +#define PF9453_BUCK_RUN_MASK 0x7f +#define PF9453_LDO1_MASK 0x7f +#define PF9453_LDO2_MASK 0x3f +#define PF9453_LDOSNVS_MASK 0x7f + +#define PF9453_PMIC_RESET_WDOG_B_CFG_MASK 0xc0 +#define PF9453_PMIC_RESET_WDOG_B_CFG_WARM 0x40 +#define PF9453_PMIC_RESET_WDOG_B_CFG_COLD 0x80 + +#endif -- cgit v1.3.1 From 602726cb300992cd98ceea72e9ea08aa37a92a0c Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Thu, 23 Jul 2026 15:21:57 +0200 Subject: power: regulator: ADD PMIC PF9453 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add regulator support for NXP PMIC pf9453 for imx91 qsb. Reviewed-by: Ye Li Reviewed-by: Peng Fan Signed-off-by: Joy Zou Signed-off-by: Peng Fan Rebased [1] on main and LDO SNVS access / checkpatch fixed. See also [2] [1] https://patchwork.ozlabs.org/project/uboot/patch/20240930035531.27079-4-peng.fan@oss.nxp.com/ [2] https://lists.u-boot-project.org/pipermail/u-boot/2026-July/625613.html Signed-off-by: Krzysztof Drobiński --- drivers/power/regulator/Kconfig | 15 +++ drivers/power/regulator/Makefile | 1 + drivers/power/regulator/pf9453.c | 256 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 drivers/power/regulator/pf9453.c diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig index c3dc5d6f97b..00a25acfdf9 100644 --- a/drivers/power/regulator/Kconfig +++ b/drivers/power/regulator/Kconfig @@ -103,6 +103,21 @@ config SPL_DM_REGULATOR_PCA9450 This config enables implementation of driver-model regulator uclass features for regulators on ROHM PCA9450 in SPL. +config DM_REGULATOR_PF9453 + bool "Enable Driver Model for NXP PF9453 regulators" + depends on DM_REGULATOR && DM_PMIC_PF9453 + help + This config enables implementation of driver-model regulator uclass + features for regulators on NXP PF9453 PMICs. PF9453 contains 6 bucks + and 5 LDOS. The driver implements get/set api for value and enable. + +config SPL_DM_REGULATOR_PF9453 + bool "Enable Driver Model for NXP PF9453 regulators in SPL" + depends on DM_REGULATOR_PF9453 && SPL + help + This config enables implementation of driver-model regulator uclass + features for regulators on ROHM PF9453 in SPL. + config DM_REGULATOR_DA9063 bool "Enable Driver Model for REGULATOR DA9063" depends on DM_REGULATOR && DM_PMIC_DA9063 diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile index 3a148545387..a40c9d340d9 100644 --- a/drivers/power/regulator/Makefile +++ b/drivers/power/regulator/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_DM_REGULATOR_NPCM8XX) += npcm8xx_regulator.o obj-$(CONFIG_$(PHASE_)DM_REGULATOR_PFUZE100) += pfuze100.o obj-$(CONFIG_$(PHASE_)DM_REGULATOR_BD71837) += bd71837.o obj-$(CONFIG_$(PHASE_)DM_REGULATOR_PCA9450) += pca9450.o +obj-$(CONFIG_$(PHASE_)DM_REGULATOR_PF9453) += pf9453.o obj-$(CONFIG_$(PHASE_)REGULATOR_PWM) += pwm_regulator.o obj-$(CONFIG_$(PHASE_)DM_REGULATOR_FAN53555) += fan53555.o obj-$(CONFIG_$(PHASE_)DM_REGULATOR_COMMON) += regulator_common.o diff --git a/drivers/power/regulator/pf9453.c b/drivers/power/regulator/pf9453.c new file mode 100644 index 00000000000..6ecde09bafa --- /dev/null +++ b/drivers/power/regulator/pf9453.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NXP PF9453 regulator driver + * Copyright 2024 NXP + */ + +#include +#include +#include +#include +#include +#include +#include + +/** + * struct pf9453_vrange - describe linear range of voltages + * + * @min_volt: smallest voltage in range + * @step: how much voltage changes at each selector step + * @min_sel: smallest selector in the range + * @max_sel: maximum selector in the range + */ +struct pf9453_vrange { + unsigned int min_volt; + unsigned int step; + u8 min_sel; + u8 max_sel; +}; + +/** + * struct pf9453_plat - describe regulator control registers + * + * @name: name of the regulator. Used for matching the dt-entry + * @enable_reg: register address used to enable/disable regulator + * @enablemask: register mask used to enable/disable regulator + * @volt_reg: register address used to configure regulator voltage + * @volt_mask: register mask used to configure regulator voltage + * @ranges: pointer to ranges of regulator voltages and matching register + * values + * @numranges: number of voltage ranges pointed by ranges + */ +struct pf9453_plat { + const char *name; + u8 enable_reg; + u8 enablemask; + u8 volt_reg; + u8 volt_mask; + struct pf9453_vrange *ranges; + unsigned int numranges; +}; + +#define PCA_RANGE(_min, _vstep, _sel_low, _sel_hi) \ +{ \ + .min_volt = (_min), .step = (_vstep), \ + .min_sel = (_sel_low), .max_sel = (_sel_hi), \ +} + +#define PCA_DATA(_name, enreg, enmask, vreg, vmask, _range, _numranges) \ +{ \ + .name = (_name), .enable_reg = (enreg), .enablemask = (enmask), \ + .volt_reg = (vreg), .volt_mask = (vmask), .ranges = (_range), \ + .numranges = _numranges \ +} + +static struct pf9453_vrange pf9453_buck134_vranges[] = { + PCA_RANGE(600000, 25000, 0, 0x7f), +}; + +static struct pf9453_vrange pf9453_buck2_vranges[] = { + PCA_RANGE(600000, 12500, 0, 0x7f), +}; + +static struct pf9453_vrange pf9453_ldo1_vranges[] = { + PCA_RANGE(800000, 25000, 0x0, 0x64), +}; + +static struct pf9453_vrange pf9453_ldo2_vranges[] = { + PCA_RANGE(500000, 25000, 0x0, 0x3a), +}; + +static struct pf9453_vrange pf9453_ldosnvs_vranges[] = { + PCA_RANGE(800000, 25000, 0x0, 0x58), +}; + +static struct pf9453_plat pf9453_reg_data[] = { + PCA_DATA("BUCK1", PF9453_BUCK1CTRL, PF9453_EN_MODE_MASK, + PF9453_BUCK1OUT, PF9453_BUCK_RUN_MASK, + pf9453_buck134_vranges, ARRAY_SIZE(pf9453_buck134_vranges)), + PCA_DATA("BUCK2", PF9453_BUCK2CTRL, PF9453_EN_MODE_MASK, + PF9453_BUCK2OUT, PF9453_BUCK_RUN_MASK, + pf9453_buck2_vranges, ARRAY_SIZE(pf9453_buck2_vranges)), + PCA_DATA("BUCK3", PF9453_BUCK3CTRL, PF9453_EN_MODE_MASK, + PF9453_BUCK3OUT, PF9453_BUCK_RUN_MASK, + pf9453_buck134_vranges, ARRAY_SIZE(pf9453_buck134_vranges)), + PCA_DATA("BUCK4", PF9453_BUCK4CTRL, PF9453_EN_MODE_MASK, + PF9453_BUCK4OUT, PF9453_BUCK_RUN_MASK, + pf9453_buck134_vranges, ARRAY_SIZE(pf9453_buck134_vranges)), + /* LDOs */ + PCA_DATA("LDO1", PF9453_LDO1CFG, PF9453_EN_MODE_MASK, + PF9453_LDO1OUT_H, PF9453_LDO1_MASK, + pf9453_ldo1_vranges, ARRAY_SIZE(pf9453_ldo1_vranges)), + PCA_DATA("LDO2", PF9453_LDO2CFG, PF9453_EN_MODE_MASK, + PF9453_LDO2OUT, PF9453_LDO2_MASK, + pf9453_ldo2_vranges, ARRAY_SIZE(pf9453_ldo2_vranges)), + PCA_DATA("LDO-SNVS", PF9453_LDOSNVS_CFG2, PF9453_EN_MODE_MASK, + PF9453_LDOSNVS_CFG1, PF9453_LDOSNVS_MASK, + pf9453_ldosnvs_vranges, ARRAY_SIZE(pf9453_ldosnvs_vranges)), +}; + +static int vrange_find_value(struct pf9453_vrange *r, unsigned int sel, + unsigned int *val) +{ + if (!val || sel < r->min_sel || sel > r->max_sel) + return -EINVAL; + + *val = r->min_volt + r->step * (sel - r->min_sel); + return 0; +} + +static int vrange_find_selector(struct pf9453_vrange *r, int val, + unsigned int *sel) +{ + int ret = -EINVAL; + int num_vals = r->max_sel - r->min_sel + 1; + + if (val >= r->min_volt && + val <= r->min_volt + r->step * (num_vals - 1)) { + if (r->step) { + *sel = r->min_sel + ((val - r->min_volt) / r->step); + ret = 0; + } else { + *sel = r->min_sel; + ret = 0; + } + } + return ret; +} + +static int pf9453_get_enable(struct udevice *dev) +{ + struct pf9453_plat *plat = dev_get_plat(dev); + int val; + + val = pmic_reg_read(dev->parent, plat->enable_reg); + if (val < 0) + return val; + + return (val & plat->enablemask); +} + +static int pf9453_set_enable(struct udevice *dev, bool enable) +{ + int val = 0; + struct pf9453_plat *plat = dev_get_plat(dev); + + if (enable) + val = plat->enablemask; + + return pmic_clrsetbits(dev->parent, plat->enable_reg, plat->enablemask, + val); +} + +static int pf9453_get_value(struct udevice *dev) +{ + struct pf9453_plat *plat = dev_get_plat(dev); + unsigned int reg, tmp; + int i, ret; + + ret = pmic_reg_read(dev->parent, plat->volt_reg); + if (ret < 0) + return ret; + + reg = ret; + reg &= plat->volt_mask; + + for (i = 0; i < plat->numranges; i++) { + struct pf9453_vrange *r = &plat->ranges[i]; + + if (!vrange_find_value(r, reg, &tmp)) + return tmp; + } + + pr_err("Unknown voltage value read from pmic\n"); + + return -EINVAL; +} + +static int pf9453_set_value(struct udevice *dev, int uvolt) +{ + struct pf9453_plat *plat = dev_get_plat(dev); + unsigned int sel; + int i, found = 0; + + for (i = 0; i < plat->numranges; i++) { + struct pf9453_vrange *r = &plat->ranges[i]; + + found = !vrange_find_selector(r, uvolt, &sel); + if (found) { + unsigned int tmp; + + /* + * We require exactly the requested value to be + * supported - this can be changed later if needed + */ + found = !vrange_find_value(r, sel, &tmp); + if (found && tmp == uvolt) + break; + found = 0; + } + } + + if (!found) + return -EINVAL; + + return pmic_clrsetbits(dev->parent, plat->volt_reg, + plat->volt_mask, sel); +} + +static int pf9453_regulator_probe(struct udevice *dev) +{ + struct pf9453_plat *plat = dev_get_plat(dev); + int i, type; + + type = dev_get_driver_data(dev_get_parent(dev)); + + if (type != NXP_CHIP_TYPE_PF9453) { + debug("Unknown PMIC type\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(pf9453_reg_data); i++) { + if (strcmp(dev->name, pf9453_reg_data[i].name)) + continue; + *plat = pf9453_reg_data[i]; + return 0; + } + + pr_err("Unknown regulator '%s'\n", dev->name); + + return -ENOENT; +} + +static const struct dm_regulator_ops pf9453_regulator_ops = { + .get_value = pf9453_get_value, + .set_value = pf9453_set_value, + .get_enable = pf9453_get_enable, + .set_enable = pf9453_set_enable, +}; + +U_BOOT_DRIVER(pf9453_regulator) = { + .name = PF9453_REGULATOR_DRIVER, + .id = UCLASS_REGULATOR, + .ops = &pf9453_regulator_ops, + .probe = pf9453_regulator_probe, + .plat_auto = sizeof(struct pf9453_plat), +}; -- cgit v1.3.1