From 82f4c1948a4f951d7d501757a31c6a10eccd37ae Mon Sep 17 00:00:00 2001 From: Fenglin Wu Date: Mon, 8 Jun 2026 18:00:59 -0700 Subject: [PATCH 01/17] spmi: pmic-arb: return 0 in spmi_pmic_arb_register_buses() on success The "ret" could be returned with an uninitialized value. Fix it. Fixes: 979987371739 ("spmi: pmic-arb: Add multi bus support") Signed-off-by: Fenglin Wu Reviewed-by: Konrad Dybcio Signed-off-by: Stephen Boyd --- drivers/spmi/spmi-pmic-arb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 2e2cb4774103..402ffeaff8e2 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -2060,7 +2060,7 @@ static int spmi_pmic_arb_register_buses(struct spmi_pmic_arb *pmic_arb, } } - return ret; + return 0; } static void spmi_pmic_arb_deregister_buses(struct spmi_pmic_arb *pmic_arb) From 36bb69cf0fdddbd40961818409849aad17ea01eb Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Mon, 17 Aug 2026 22:26:45 +0300 Subject: [PATCH 02/17] MAINTAINERS: Add Qualcomm SPMI controller entry Add an entry for the Qualcomm SPMI controller (PMIC Arbiter) driver and its device tree bindings. List Abel Vesa and David Collins as maintainers. Signed-off-by: Abel Vesa Acked-by: David Collins Signed-off-by: Stephen Boyd --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..90de9c6aea0e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22613,6 +22613,14 @@ F: Documentation/networking/device_drivers/cellular/qualcomm/rmnet.rst F: drivers/net/ethernet/qualcomm/rmnet/ F: include/linux/if_rmnet.h +QUALCOMM SPMI CONTROLLER (PMIC ARBITER) DRIVER +M: Abel Vesa +M: David Collins +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/spmi/qcom,*spmi-pmic-arb*.yaml +F: drivers/spmi/spmi-pmic-arb.c + QUALCOMM TEE (QCOMTEE) DRIVER M: Amirreza Zarrabi L: linux-arm-msm@vger.kernel.org From 84c72fc05f73df3473b3bfac89c0a7eae765513a Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 1 Jul 2026 14:19:08 +0200 Subject: [PATCH 03/17] spmi: mtk-pmif: Add workaround for FSM lockup/error in read operation The SPMI PMIF in some SoCs like MT8196 is affected by a hardware issue that makes the first read operation on each SID to fail as the FSM locks up after sending the read command, and this happens at least in the following conditions: - At boot, after bootloader handoff; and - At every suspend->resume cycle. This critical bug may produce unwanted issues in regulator drivers which may fail to set voltages, making the platform to end up in a undervoltage or, a bit more critically, an overvoltage condition, producing instability or ... worse. In order to work around this issue, add a retry mechanism into the pmif_spmi_read_cmd() callback which will resend the read command up to 3 times in the following conditions: - Software Interface returns an error; or - Software Interface never gets in WFVLDCLR state. As to avoid uselessly blocking for too much time, only during the read-retry loop, the maximum SWINF polling time for each trial is reduced to PMIF_TIMEOUT_US / 2: this was tested on multiple SoCs and seems to always be enough, as when any read succeeds it will always take less than 40ms. In any case, this is still allowing 1.5 times the previous maximum polling time, reaching a total maximum (for 3 retries) of 150ms. Worst case testing that I performed saw a third retry only 3 times out of ~100 reboots. Fixes: 1f5be2d7f743 ("spmi: mtk-pmif: Add support for MT8196 SPMI Controller") Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/spmi/spmi-mtk-pmif.c | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c index 1048420b5afb..61c916edaec4 100644 --- a/drivers/spmi/spmi-mtk-pmif.c +++ b/drivers/spmi/spmi-mtk-pmif.c @@ -21,6 +21,7 @@ #define SWINF_WFVLDCLR 0x06 #define GET_SWINF(x) (((x) >> 1) & 0x7) +#define GET_SWINFERR(x) (((x) >> 18) & 0x1) #define PMIF_CMD_REG_0 0 #define PMIF_CMD_REG 1 @@ -349,6 +350,7 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, struct pmif *arb = to_mtk_pmif(ctrl); struct ch_reg *inf_reg; int ret; + u8 retry = 0; u32 data, cmd; unsigned long flags; @@ -385,17 +387,35 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, return ret; } - /* Send the command. */ cmd = (opc << 30) | (sid << 24) | ((len - 1) << 16) | addr; - pmif_writel(arb, pbus, cmd, inf_reg->ch_send); + do { + /* Send the command. */ + pmif_writel(arb, pbus, cmd, inf_reg->ch_send); + + /* + * Wait for Software Interface FSM state to be WFVLDCLR or to + * return an error. + * + * If this is WFVLDCLR, read the data and clear the valid flag; + * If error or timeout, retry for a maximum of 3 times as a + * workaround for an hardware issue. + */ + ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], + data, + GET_SWINF(data) == SWINF_WFVLDCLR || + GET_SWINFERR(data), + PMIF_DELAY_US, PMIF_TIMEOUT_US / 2); + if (ret < 0) + continue; + + if (GET_SWINFERR(data)) { + ret = -EIO; + continue; + } + + break; + } while (++retry < 3); - /* - * Wait for Software Interface FSM state to be WFVLDCLR, - * read the data and clear the valid flag. - */ - ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], - data, GET_SWINF(data) == SWINF_WFVLDCLR, - PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_WFVLDCLR\n"); From f7aa9de4f2b197cdd704cdc476901b68ee128433 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:15 +0200 Subject: [PATCH 04/17] spmi: Fix potential use-after-free by grabbing of_node reference As noticed by Sashiko during a review run of an unrelated patch, in of_spmi_register_devices(), for_each_available_child_of_node() is used to loop through children, and to also assign a node to a newly created SPMI child device. Problem is that the refcount is dropped at every iteration so, in the specific case of DT overlays, a use-after-free may occur when an overlay is dynamically unloaded! To resolve this, increase the of_node refcount when assigning (in function of_spmi_register_devices) and release the reference in spmi_device_remove(). Fixes: bc32bbd04011 ("spmi: Set fwnode for spmi devices") Cc: stable@vger.kernel.org Reported-by: Sashiko Bot Closes: https://sashiko.dev/#/patchset/20260608100949.36309-1-angelogioacchino.delregno@collabora.com?part=2 Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/spmi/spmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index 57b7c0cb4240..f6d2f4bddfcd 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -24,6 +24,7 @@ static void spmi_dev_release(struct device *dev) { struct spmi_device *sdev = to_spmi_device(dev); + of_node_put(dev->of_node); kfree(sdev); } @@ -517,7 +518,7 @@ static void of_spmi_register_devices(struct spmi_controller *ctrl) if (!sdev) continue; - device_set_node(&sdev->dev, of_fwnode_handle(node)); + device_set_node(&sdev->dev, of_fwnode_handle(of_node_get(node))); sdev->usid = (u8)reg[0]; err = spmi_device_add(sdev); From 91363e6bcd543eed5d22fe2e1cb5fdffa1c78958 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:16 +0200 Subject: [PATCH 05/17] spmi: Remove redundant dev_name() print in spmi_device_add() Function spmi_device_add() uses dev_{dbg,err}() for respectively debug and error prints, and passes the same device pointer as both the dev_{dbg,err}() parameters and to a dev_name() that is part of the actual message. This means that the device name gets printed twice! Remove the redundant dev_name() from the messages. Acked-by: Stephen Boyd Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/spmi/spmi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index f6d2f4bddfcd..7cd81394c925 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -69,12 +69,11 @@ int spmi_device_add(struct spmi_device *sdev) err = device_add(&sdev->dev); if (err < 0) { - dev_err(&sdev->dev, "Can't add %s, status %d\n", - dev_name(&sdev->dev), err); + dev_err(&sdev->dev, "Can't add device, status %d\n", err); goto err_device_add; } - dev_dbg(&sdev->dev, "device %s registered\n", dev_name(&sdev->dev)); + dev_dbg(&sdev->dev, "device registered\n"); err_device_add: return err; From 33cc7810a0ae1f8d57dbec88e5a2843060457550 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:17 +0200 Subject: [PATCH 06/17] spmi: Print error status with %pe format Instead of printing just a number, use the %pe format for error status, increasing readability of error prints. Acked-by: Stephen Boyd Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/spmi/spmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index 7cd81394c925..ec4389dfae28 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -69,7 +69,7 @@ int spmi_device_add(struct spmi_device *sdev) err = device_add(&sdev->dev); if (err < 0) { - dev_err(&sdev->dev, "Can't add device, status %d\n", err); + dev_err(&sdev->dev, "Can't add device, status %pe\n", ERR_PTR(err)); goto err_device_add; } @@ -494,8 +494,8 @@ static void of_spmi_register_devices(struct spmi_controller *ctrl) err = of_property_read_u32_array(node, "reg", reg, 2); if (err) { dev_err(&ctrl->dev, - "node %pOF err (%d) does not have 'reg' property\n", - node, err); + "node %pOF err (%pe) does not have 'reg' property\n", + node, ERR_PTR(err)); continue; } @@ -523,7 +523,7 @@ static void of_spmi_register_devices(struct spmi_controller *ctrl) err = spmi_device_add(sdev); if (err) { dev_err(&sdev->dev, - "failure adding device. status %d\n", err); + "failure adding device. status %pe\n", ERR_PTR(err)); spmi_device_put(sdev); } } From 91396478267ec6e9ec73a62de2e5c56a2d963ad2 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:18 +0200 Subject: [PATCH 07/17] spmi: Remove unneeded goto in spmi_device_add() error path If any error happens during device_add() just return inside of the conditional, as the goto path doesn't do anything else if not just returning. While at it, to improve readability, also change this function to explicitly return 0 (for success) at the end. Acked-by: Stephen Boyd Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Abel Vesa Signed-off-by: Stephen Boyd --- drivers/spmi/spmi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index ec4389dfae28..ba4a980a35eb 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -70,13 +70,11 @@ int spmi_device_add(struct spmi_device *sdev) err = device_add(&sdev->dev); if (err < 0) { dev_err(&sdev->dev, "Can't add device, status %pe\n", ERR_PTR(err)); - goto err_device_add; + return err; } dev_dbg(&sdev->dev, "device registered\n"); - -err_device_add: - return err; + return 0; } EXPORT_SYMBOL_GPL(spmi_device_add); From 6e19e5d699f37e01f4e6c7b25f17cd603195d27c Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:19 +0200 Subject: [PATCH 08/17] spmi: Implement spmi_subdevice_alloc_and_add() and devm variant Some devices connected over the SPMI bus may be big, in the sense that those may be a complex of devices managed by a single chip over the SPMI bus, reachable through a single SID. Add new functions aimed at managing sub-devices of a SPMI device spmi_subdevice_alloc_and_add() and a spmi_subdevice_remove() for adding a new subdevice and removing it respectively, and also add their devm_* variants. The need for such functions comes from the existence of those complex Power Management ICs (PMICs), which feature one or many sub-devices, in some cases with these being even addressable on the chip in form of SPMI register ranges. Examples of those devices can be found in both Qualcomm platforms with their PMICs having PON, RTC, SDAM, GPIO controller, and other sub-devices, and in newer MediaTek platforms showing similar HW features and a similar layout with those also having many subdevs. Also, instead of generally exporting symbols, export them with a new "SPMI" namespace: all users will have to import this namespace to make use of the newly introduced exports. Reviewed-by: Jonathan Cameron Tested-by: Neil Armstrong # on SM8650-QRD Acked-by: Stephen Boyd Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/spmi/spmi-devres.c | 24 ++++++++++++ drivers/spmi/spmi.c | 78 ++++++++++++++++++++++++++++++++++++++ include/linux/spmi.h | 16 ++++++++ 3 files changed, 118 insertions(+) diff --git a/drivers/spmi/spmi-devres.c b/drivers/spmi/spmi-devres.c index 62c4b3f24d06..c3e889fe1b6e 100644 --- a/drivers/spmi/spmi-devres.c +++ b/drivers/spmi/spmi-devres.c @@ -60,5 +60,29 @@ int devm_spmi_controller_add(struct device *parent, struct spmi_controller *ctrl } EXPORT_SYMBOL_GPL(devm_spmi_controller_add); +static void devm_spmi_subdevice_remove(void *sub_sdev) +{ + spmi_subdevice_remove(sub_sdev); +} + +struct spmi_subdevice *devm_spmi_subdevice_alloc_and_add(struct device *dev, + struct spmi_device *sparent) +{ + struct spmi_subdevice *sub_sdev; + int ret; + + sub_sdev = spmi_subdevice_alloc_and_add(sparent); + if (IS_ERR(sub_sdev)) + return sub_sdev; + + ret = devm_add_action_or_reset(dev, devm_spmi_subdevice_remove, sub_sdev); + if (ret) + return ERR_PTR(ret); + + return sub_sdev; +} +EXPORT_SYMBOL_NS_GPL(devm_spmi_subdevice_alloc_and_add, "SPMI"); + MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("SPMI devres helpers"); +MODULE_IMPORT_NS("SPMI"); diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index ba4a980a35eb..bafb4c3e6c17 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -19,6 +19,7 @@ static bool is_registered; static DEFINE_IDA(ctrl_ida); +static DEFINE_IDA(spmi_subdevice_ida); static void spmi_dev_release(struct device *dev) { @@ -32,6 +33,19 @@ static const struct device_type spmi_dev_type = { .release = spmi_dev_release, }; +static void spmi_subdev_release(struct device *dev) +{ + struct spmi_device *sdev = to_spmi_device(dev); + struct spmi_subdevice *sub_sdev = container_of(sdev, struct spmi_subdevice, sdev); + + ida_free(&spmi_subdevice_ida, sub_sdev->devid); + kfree(sub_sdev); +} + +static const struct device_type spmi_subdev_type = { + .release = spmi_subdev_release, +}; + static void spmi_ctrl_release(struct device *dev) { struct spmi_controller *ctrl = to_spmi_controller(dev); @@ -88,6 +102,18 @@ void spmi_device_remove(struct spmi_device *sdev) } EXPORT_SYMBOL_GPL(spmi_device_remove); +/** + * spmi_subdevice_remove() - Remove an SPMI subdevice + * @sub_sdev: spmi_device to be removed + */ +void spmi_subdevice_remove(struct spmi_subdevice *sub_sdev) +{ + struct spmi_device *sdev = &sub_sdev->sdev; + + device_unregister(&sdev->dev); +} +EXPORT_SYMBOL_NS_GPL(spmi_subdevice_remove, "SPMI"); + static inline int spmi_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid) { @@ -429,6 +455,58 @@ struct spmi_device *spmi_device_alloc(struct spmi_controller *ctrl) } EXPORT_SYMBOL_GPL(spmi_device_alloc); +/** + * spmi_subdevice_alloc_and_add(): Allocate and add a new SPMI sub-device + * @sparent: SPMI parent device with previously registered SPMI controller + * + * Returns: + * Pointer to newly allocated SPMI sub-device for success or error pointer. + */ +struct spmi_subdevice *spmi_subdevice_alloc_and_add(struct spmi_device *sparent) +{ + struct spmi_subdevice *sub_sdev; + struct spmi_device *sdev; + int ret; + + sub_sdev = kzalloc_obj(*sub_sdev); + if (!sub_sdev) + return ERR_PTR(-ENOMEM); + + sdev = &sub_sdev->sdev; + sdev->ctrl = sparent->ctrl; + sdev->usid = sparent->usid; + + ret = ida_alloc(&spmi_subdevice_ida, GFP_KERNEL); + if (ret < 0) { + kfree(sub_sdev); + return ERR_PTR(ret); + } + sub_sdev->devid = ret; + + device_initialize(&sdev->dev); + sdev->dev.parent = &sparent->dev; + sdev->dev.bus = &spmi_bus_type; + sdev->dev.type = &spmi_subdev_type; + + ret = dev_set_name(&sdev->dev, "%u-%02x.%u.auto", + sdev->ctrl->nr, sdev->usid, sub_sdev->devid); + if (ret) + goto err_put_dev; + + ret = device_add(&sdev->dev); + if (ret) { + dev_err(&sdev->dev, "Can't add device, status %pe\n", ERR_PTR(ret)); + goto err_put_dev; + } + + return sub_sdev; + +err_put_dev: + put_device(&sdev->dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_NS_GPL(spmi_subdevice_alloc_and_add, "SPMI"); + /** * spmi_controller_alloc() - Allocate a new SPMI controller * @parent: parent device diff --git a/include/linux/spmi.h b/include/linux/spmi.h index 4eb9564a7fb3..a78a8924b2ac 100644 --- a/include/linux/spmi.h +++ b/include/linux/spmi.h @@ -69,6 +69,22 @@ int spmi_device_add(struct spmi_device *sdev); void spmi_device_remove(struct spmi_device *sdev); +/** + * struct spmi_subdevice - Basic representation of an SPMI sub-device + * @sdev: Sub-device representation of an SPMI device + * @devid: Platform Device ID of an SPMI sub-device + */ +struct spmi_subdevice { + struct spmi_device sdev; + unsigned int devid; +}; + +struct spmi_subdevice *spmi_subdevice_alloc_and_add(struct spmi_device *sparent); +void spmi_subdevice_remove(struct spmi_subdevice *sdev); + +struct spmi_subdevice *devm_spmi_subdevice_alloc_and_add(struct device *dev, + struct spmi_device *sparent); + /** * struct spmi_controller - interface to the SPMI master controller * @dev: Driver model representation of the device. From 939e80cef9a757ca3a08cd1c8665726837405c0f Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:20 +0200 Subject: [PATCH 09/17] spmi: Add helper to get a parent SPMI device Drivers that register a SPMI sub-device as a platform_driver are not guaranteed to have a SPMI device as their parent: add a new helper `spmi_get_parent_spmi_device()` that takes a struct device and checks if there's a parent, and if that parent is actually a SPMI device by checking if its device type matches with SPMI: if so, returns a pointer to the relative struct spmi_device without incrementing any refcount. As a note, in the specific case of using this helper to retrieve a SPMI subdevice's parent, the spmi_subdevice_alloc_and_add() function does actually call device_add(), which will already take care of raising the refcount of the associated parent device. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Abel Vesa Signed-off-by: Stephen Boyd --- drivers/spmi/spmi.c | 23 +++++++++++++++++++++++ include/linux/spmi.h | 1 + 2 files changed, 24 insertions(+) diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index bafb4c3e6c17..ec97f12046f5 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -431,6 +431,29 @@ struct spmi_device *spmi_find_device_by_of_node(struct device_node *np) } EXPORT_SYMBOL_GPL(spmi_find_device_by_of_node); +/** + * spmi_get_parent_spmi_device() - get the parent SPMI device from current dev + * @dev: pointer to a subdevice on SPMI bus + * + * Checks if the passed device is a child of an SPMI device and returns a + * handle to the parent SPMI device without incrementing any refcount. + * + * Return: Handle to the parent SPMI device or NULL + */ +struct spmi_device *spmi_get_parent_spmi_device(struct device *dev) +{ + struct device *parent; + + if (dev && dev->parent) { + parent = dev->parent; + + if (parent->type == &spmi_dev_type) + return to_spmi_device(parent); + } + return NULL; +} +EXPORT_SYMBOL_NS_GPL(spmi_get_parent_spmi_device, "SPMI"); + /** * spmi_device_alloc() - Allocate a new SPMI device * @ctrl: associated controller diff --git a/include/linux/spmi.h b/include/linux/spmi.h index a78a8924b2ac..4daebc980501 100644 --- a/include/linux/spmi.h +++ b/include/linux/spmi.h @@ -189,6 +189,7 @@ static inline void spmi_driver_unregister(struct spmi_driver *sdrv) struct device_node; struct spmi_device *spmi_find_device_by_of_node(struct device_node *np); +struct spmi_device *spmi_get_parent_spmi_device(struct device *dev); int spmi_register_read(struct spmi_device *sdev, u8 addr, u8 *buf); int spmi_ext_register_read(struct spmi_device *sdev, u8 addr, u8 *buf, size_t len); From 1e048fe2908c59c5f0cacd61e4be30d5bbc45f0e Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:21 +0200 Subject: [PATCH 10/17] nvmem: qcom-spmi-sdam: Migrate to devm_spmi_subdevice_alloc_and_add() Some Qualcomm PMICs integrate a SDAM device, internally located in a specific address range reachable through SPMI communication. Instead of using the parent SPMI device (the main PMIC) as a kind of syscon in this driver, register a new SPMI sub-device for SDAM and initialize its own regmap with this sub-device's specific base address, retrieved from the devicetree. This allows to stop manually adding the register base address to every R/W call in this driver, as this can be, and is now, handled by the regmap API instead. Reviewed-by: Konrad Dybcio Tested-by: Neil Armstrong # on SM8650-QRD Acked-by: Srinivas Kandagatla Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/nvmem/Kconfig | 1 + drivers/nvmem/qcom-spmi-sdam.c | 41 ++++++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index c36c2a4c2a0b..a12e912ec17f 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -468,6 +468,7 @@ config NVMEM_SNVS_LPGPR config NVMEM_SPMI_SDAM tristate "SPMI SDAM Support" depends on SPMI + select REGMAP_SPMI help This driver supports the Shared Direct Access Memory Module on Qualcomm Technologies, Inc. PMICs. It provides the clients diff --git a/drivers/nvmem/qcom-spmi-sdam.c b/drivers/nvmem/qcom-spmi-sdam.c index 4f1cca6eab71..2aec95dff05d 100644 --- a/drivers/nvmem/qcom-spmi-sdam.c +++ b/drivers/nvmem/qcom-spmi-sdam.c @@ -9,6 +9,7 @@ #include #include #include +#include #define SDAM_MEM_START 0x40 #define REGISTER_MAP_ID 0x40 @@ -20,7 +21,6 @@ struct sdam_chip { struct regmap *regmap; struct nvmem_config sdam_config; - unsigned int base; unsigned int size; }; @@ -73,7 +73,7 @@ static int sdam_read(void *priv, unsigned int offset, void *val, return -EINVAL; } - rc = regmap_bulk_read(sdam->regmap, sdam->base + offset, val, bytes); + rc = regmap_bulk_read(sdam->regmap, offset, val, bytes); if (rc < 0) dev_err(dev, "Failed to read SDAM offset %#x len=%zd, rc=%d\n", offset, bytes, rc); @@ -100,7 +100,7 @@ static int sdam_write(void *priv, unsigned int offset, void *val, return -EINVAL; } - rc = regmap_bulk_write(sdam->regmap, sdam->base + offset, val, bytes); + rc = regmap_bulk_write(sdam->regmap, offset, val, bytes); if (rc < 0) dev_err(dev, "Failed to write SDAM offset %#x len=%zd, rc=%d\n", offset, bytes, rc); @@ -110,8 +110,17 @@ static int sdam_write(void *priv, unsigned int offset, void *val, static int sdam_probe(struct platform_device *pdev) { + struct regmap_config sdam_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; struct sdam_chip *sdam; struct nvmem_device *nvmem; + struct spmi_device *sparent; + struct spmi_subdevice *sub_sdev; + struct device *dev = &pdev->dev; unsigned int val; int rc; @@ -119,19 +128,26 @@ static int sdam_probe(struct platform_device *pdev) if (!sdam) return -ENOMEM; - sdam->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!sdam->regmap) { - dev_err(&pdev->dev, "Failed to get regmap handle\n"); - return -ENXIO; - } + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) + return -ENODEV; - rc = of_property_read_u32(pdev->dev.of_node, "reg", &sdam->base); + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + + rc = device_property_read_u32(dev, "reg", &sdam_regmap_config.reg_base); if (rc < 0) { - dev_err(&pdev->dev, "Failed to get SDAM base, rc=%d\n", rc); + dev_err(dev, "Failed to get SDAM base, rc=%d\n", rc); return -EINVAL; } - rc = regmap_read(sdam->regmap, sdam->base + SDAM_SIZE, &val); + sdam->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &sdam_regmap_config); + if (IS_ERR(sdam->regmap)) + return dev_err_probe(dev, PTR_ERR(sdam->regmap), + "Failed to get regmap handle\n"); + + rc = regmap_read(sdam->regmap, SDAM_SIZE, &val); if (rc < 0) { dev_err(&pdev->dev, "Failed to read SDAM_SIZE rc=%d\n", rc); return -EINVAL; @@ -159,7 +175,7 @@ static int sdam_probe(struct platform_device *pdev) } dev_dbg(&pdev->dev, "SDAM base=%#x size=%u registered successfully\n", - sdam->base, sdam->size); + sdam_regmap_config.reg_base, sdam->size); return 0; } @@ -181,3 +197,4 @@ module_platform_driver(sdam_driver); MODULE_DESCRIPTION("QCOM SPMI SDAM driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SPMI"); From c3e4860e172b11ef57316f78142e3e3da374e523 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:22 +0200 Subject: [PATCH 11/17] power: reset: qcom-pon: Migrate to devm_spmi_subdevice_alloc_and_add() Some Qualcomm PMICs integrates a Power On device supporting pwrkey and resin along with the Android reboot reason action identifier. Instead of using the parent SPMI device (the main PMIC) as a kind of syscon in this driver, register a new SPMI sub-device for PON and initialize its own regmap with this sub-device's specific base address, retrieved from the devicetree. This allows to stop manually adding the register base address to every R/W call in this driver, as this can be, and is now, handled by the regmap API instead. Reviewed-by: Sebastian Reichel Tested-by: Neil Armstrong # on SM8650-QRD Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/power/reset/qcom-pon.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c index 7e108982a582..cd7ba680b841 100644 --- a/drivers/power/reset/qcom-pon.c +++ b/drivers/power/reset/qcom-pon.c @@ -11,6 +11,7 @@ #include #include #include +#include #define PON_SOFT_RB_SPARE 0x8f @@ -22,7 +23,6 @@ struct qcom_pon { struct device *dev; struct regmap *regmap; - u32 baseaddr; struct reboot_mode_driver reboot_mode; long reason_shift; }; @@ -35,7 +35,7 @@ static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot, int ret; ret = regmap_update_bits(pon->regmap, - pon->baseaddr + PON_SOFT_RB_SPARE, + PON_SOFT_RB_SPARE, GENMASK(7, pon->reason_shift), magic << pon->reason_shift); if (ret < 0) @@ -46,6 +46,15 @@ static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot, static int qcom_pon_probe(struct platform_device *pdev) { + struct regmap_config qcom_pon_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; + struct device *dev = &pdev->dev; + struct spmi_subdevice *sub_sdev; + struct spmi_device *sparent; struct qcom_pon *pon; long reason_shift; int error; @@ -56,17 +65,22 @@ static int qcom_pon_probe(struct platform_device *pdev) pon->dev = &pdev->dev; - pon->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!pon->regmap) { - dev_err(&pdev->dev, "failed to locate regmap\n"); + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) return -ENODEV; - } - error = of_property_read_u32(pdev->dev.of_node, "reg", - &pon->baseaddr); + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + + error = device_property_read_u32(dev, "reg", &qcom_pon_regmap_config.reg_base); if (error) return error; + pon->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &qcom_pon_regmap_config); + if (IS_ERR(pon->regmap)) + return PTR_ERR(pon->regmap); + reason_shift = (long)of_device_get_match_data(&pdev->dev); if (reason_shift != NO_REASON_SHIFT) { @@ -106,3 +120,4 @@ module_platform_driver(qcom_pon_driver); MODULE_DESCRIPTION("Qualcomm Power On driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SPMI"); From 1b6603de9a71908ead619c6e8a397cac0e6625c3 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:23 +0200 Subject: [PATCH 12/17] phy: qualcomm: eusb2-repeater: Migrate to devm_spmi_subdevice_alloc_and_add() Some Qualcomm PMICs integrate an USB Repeater device, used to convert between eUSB2 and USB 2.0 signaling levels, reachable in a specific address range over SPMI. Instead of using the parent SPMI device (the main PMIC) as a kind of syscon in this driver, register a new SPMI sub-device for EUSB2 and initialize its own regmap with this sub-device's specific base address, retrieved from the devicetree. This allows to stop manually adding the register base address to every R/W call in this driver, as this can be, and is now, handled by the regmap API instead. Tested-by: Neil Armstrong # on SM8650-QRD Reviewed-by: Abel Vesa Acked-by: Vinod Koul Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/phy/qualcomm/Kconfig | 2 + .../phy/qualcomm/phy-qcom-eusb2-repeater.c | 52 ++++++++++++------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index d910a5d1a1ac..c5e260a4c7f8 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -138,7 +138,9 @@ config PHY_QCOM_QUSB2 config PHY_QCOM_EUSB2_REPEATER tristate "Qualcomm PMIC eUSB2 Repeater Driver" depends on OF && (ARCH_QCOM || COMPILE_TEST) + depends on SPMI select GENERIC_PHY + select REGMAP_SPMI help Enable support for the USB high-speed eUSB2 repeater on Qualcomm PMICs. The repeater is paired with a Synopsys or M31 eUSB2 Phy diff --git a/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c b/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c index efeec4709a15..89bc1c89fc85 100644 --- a/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c +++ b/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c @@ -9,6 +9,7 @@ #include #include #include +#include /* eUSB2 status registers */ #define EUSB2_RPTR_STATUS 0x08 @@ -66,7 +67,6 @@ struct eusb2_repeater { struct phy *phy; struct regulator_bulk_data *vregs; const struct eusb2_repeater_cfg *cfg; - u32 base; enum phy_mode mode; }; @@ -143,7 +143,6 @@ static int eusb2_repeater_init(struct phy *phy) struct eusb2_repeater *rptr = phy_get_drvdata(phy); struct device_node *np = rptr->dev->of_node; struct regmap *regmap = rptr->regmap; - u32 base = rptr->base; u32 poll_val; s32 dt_val; int ret; @@ -154,37 +153,37 @@ static int eusb2_repeater_init(struct phy *phy) if (ret) return ret; - regmap_write(regmap, base + EUSB2_EN_CTL1, EUSB2_RPTR_EN); + regmap_write(regmap, EUSB2_EN_CTL1, EUSB2_RPTR_EN); /* Write registers from init table */ for (int i = 0; i < rptr->cfg->init_tbl_num; i++) - regmap_write(regmap, base + rptr->cfg->init_tbl[i].reg, + regmap_write(regmap, rptr->cfg->init_tbl[i].reg, rptr->cfg->init_tbl[i].value); /* Override registers from devicetree values */ if (!of_property_read_u8(np, "qcom,tune-usb2-preem", &val)) - regmap_write(regmap, base + EUSB2_TUNE_USB2_PREEM, val); + regmap_write(regmap, EUSB2_TUNE_USB2_PREEM, val); if (!of_property_read_u8(np, "qcom,tune-usb2-disc-thres", &val)) - regmap_write(regmap, base + EUSB2_TUNE_HSDISC, val); + regmap_write(regmap, EUSB2_TUNE_HSDISC, val); if (!of_property_read_u8(np, "qcom,tune-usb2-amplitude", &val)) - regmap_write(regmap, base + EUSB2_TUNE_IUSB2, val); + regmap_write(regmap, EUSB2_TUNE_IUSB2, val); if (!of_property_read_u8(np, "qcom,tune-res-fsdif", &val)) - regmap_write(regmap, base + EUSB2_TUNE_RES_FSDIF, val); + regmap_write(regmap, EUSB2_TUNE_RES_FSDIF, val); if (!of_property_read_s32(np, "qcom,squelch-detector-bp", &dt_val)) { for (i = 0; i < ARRAY_SIZE(squelch_detector); i++) { if (squelch_detector[i] == dt_val) { - regmap_write(regmap, base + EUSB2_TUNE_SQUELCH_U, i); + regmap_write(regmap, EUSB2_TUNE_SQUELCH_U, i); break; } } } /* Wait for status OK */ - ret = regmap_read_poll_timeout(regmap, base + EUSB2_RPTR_STATUS, poll_val, + ret = regmap_read_poll_timeout(regmap, EUSB2_RPTR_STATUS, poll_val, poll_val & RPTR_OK, 10, 5); if (ret) dev_err(rptr->dev, "initialization timed-out\n"); @@ -197,7 +196,6 @@ static int eusb2_repeater_set_mode(struct phy *phy, { struct eusb2_repeater *rptr = phy_get_drvdata(phy); struct regmap *regmap = rptr->regmap; - u32 base = rptr->base; switch (mode) { case PHY_MODE_USB_HOST: @@ -206,8 +204,8 @@ static int eusb2_repeater_set_mode(struct phy *phy, * per eUSB 1.2 Spec. Below implement software workaround until * PHY and controller is fixing seen observation. */ - regmap_write(regmap, base + EUSB2_FORCE_EN_5, F_CLK_19P2M_EN); - regmap_write(regmap, base + EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN); + regmap_write(regmap, EUSB2_FORCE_EN_5, F_CLK_19P2M_EN); + regmap_write(regmap, EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN); break; case PHY_MODE_USB_DEVICE: /* @@ -216,8 +214,8 @@ static int eusb2_repeater_set_mode(struct phy *phy, * repeater doesn't clear previous value due to shared * regulators (say host <-> device mode switch). */ - regmap_write(regmap, base + EUSB2_FORCE_EN_5, 0); - regmap_write(regmap, base + EUSB2_FORCE_VAL_5, 0); + regmap_write(regmap, EUSB2_FORCE_EN_5, 0); + regmap_write(regmap, EUSB2_FORCE_VAL_5, 0); break; default: return -EINVAL; @@ -242,11 +240,18 @@ static const struct phy_ops eusb2_repeater_ops = { static int eusb2_repeater_probe(struct platform_device *pdev) { + struct regmap_config eusb2_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; + struct spmi_device *sparent; struct eusb2_repeater *rptr; + struct spmi_subdevice *sub_sdev; struct device *dev = &pdev->dev; struct phy_provider *phy_provider; struct device_node *np = dev->of_node; - u32 res; int ret; rptr = devm_kzalloc(dev, sizeof(*rptr), GFP_KERNEL); @@ -260,15 +265,21 @@ static int eusb2_repeater_probe(struct platform_device *pdev) if (!rptr->cfg) return -EINVAL; - rptr->regmap = dev_get_regmap(dev->parent, NULL); - if (!rptr->regmap) + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) return -ENODEV; - ret = of_property_read_u32(np, "reg", &res); + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + + ret = device_property_read_u32(dev, "reg", &eusb2_regmap_config.reg_base); if (ret < 0) return ret; - rptr->base = res; + rptr->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &eusb2_regmap_config); + if (IS_ERR(rptr->regmap)) + return PTR_ERR(rptr->regmap); ret = eusb2_repeater_init_vregs(rptr); if (ret < 0) { @@ -335,3 +346,4 @@ module_platform_driver(eusb2_repeater_driver); MODULE_DESCRIPTION("Qualcomm PMIC eUSB2 Repeater driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("SPMI"); From 27a2dca1241cb4a485cec844185d20b42370ed76 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:24 +0200 Subject: [PATCH 13/17] misc: qcom-coincell: Migrate to devm_spmi_subdevice_alloc_and_add() Some Qualcomm PMICs integrate a charger for coincells, usually powering an RTC when external (or main battery) power is missing. Instead of using the parent SPMI device (the main PMIC) as a kind of syscon in this driver, register a new SPMI sub-device and initialize its own regmap with this sub-device's specific base address, retrieved from the devicetree. This allows to stop manually adding the register base address to every R/W call in this driver, as this can be, and is now, handled by the regmap API instead. Acked-by: Greg Kroah-Hartman Tested-by: Neil Armstrong # on SM8650-QRD Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/misc/Kconfig | 2 ++ drivers/misc/qcom-coincell.c | 45 +++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 7364931dad3a..e7d6b79dc6a0 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -256,6 +256,8 @@ config HP_ILO config QCOM_COINCELL tristate "Qualcomm coincell charger support" depends on MFD_SPMI_PMIC || COMPILE_TEST + depends on SPMI + select REGMAP_SPMI help This driver supports the coincell block found inside of Qualcomm PMICs. The coincell charger provides a means to diff --git a/drivers/misc/qcom-coincell.c b/drivers/misc/qcom-coincell.c index b58bd34fc471..8da3736652c3 100644 --- a/drivers/misc/qcom-coincell.c +++ b/drivers/misc/qcom-coincell.c @@ -9,11 +9,11 @@ #include #include #include +#include struct qcom_coincell { struct device *dev; struct regmap *regmap; - u32 base_addr; }; #define QCOM_COINCELL_REG_RSET 0x44 @@ -35,7 +35,7 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset, /* if disabling, just do that and skip other operations */ if (!enable) return regmap_write(chgr->regmap, - chgr->base_addr + QCOM_COINCELL_REG_ENABLE, 0); + QCOM_COINCELL_REG_ENABLE, 0); /* find index for current-limiting resistor */ for (i = 0; i < ARRAY_SIZE(qcom_rset_map); i++) @@ -58,7 +58,7 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset, } rc = regmap_write(chgr->regmap, - chgr->base_addr + QCOM_COINCELL_REG_RSET, i); + QCOM_COINCELL_REG_RSET, i); if (rc) { /* * This is mainly to flag a bad base_addr (reg) from dts. @@ -71,19 +71,28 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset, } rc = regmap_write(chgr->regmap, - chgr->base_addr + QCOM_COINCELL_REG_VSET, j); + QCOM_COINCELL_REG_VSET, j); if (rc) return rc; /* set 'enable' register */ return regmap_write(chgr->regmap, - chgr->base_addr + QCOM_COINCELL_REG_ENABLE, + QCOM_COINCELL_REG_ENABLE, QCOM_COINCELL_ENABLE); } static int qcom_coincell_probe(struct platform_device *pdev) { - struct device_node *node = pdev->dev.of_node; + struct regmap_config qcom_coincell_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct spmi_subdevice *sub_sdev; + struct spmi_device *sparent; struct qcom_coincell chgr; u32 rset = 0; u32 vset = 0; @@ -92,16 +101,25 @@ static int qcom_coincell_probe(struct platform_device *pdev) chgr.dev = &pdev->dev; - chgr.regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!chgr.regmap) { - dev_err(chgr.dev, "Unable to get regmap\n"); - return -EINVAL; - } - - rc = of_property_read_u32(node, "reg", &chgr.base_addr); + rc = device_property_read_u32(dev, "reg", &qcom_coincell_regmap_config.reg_base); if (rc) return rc; + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) + return -ENODEV; + + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + + chgr.regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, + &qcom_coincell_regmap_config); + if (IS_ERR(chgr.regmap)) { + dev_err(chgr.dev, "Unable to get regmap\n"); + return PTR_ERR(chgr.regmap); + } + enable = !of_property_read_bool(node, "qcom,charger-disable"); if (enable) { @@ -142,3 +160,4 @@ module_platform_driver(qcom_coincell_driver); MODULE_DESCRIPTION("Qualcomm PMIC coincell charger driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SPMI"); From 6507e23579d4c8241b0fa33df6a4f330a3f0174f Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:25 +0200 Subject: [PATCH 14/17] iio: adc: qcom-spmi-iadc: Migrate to devm_spmi_subdevice_alloc_and_add() Some Qualcomm PMICs integrate an Current ADC device, reachable in a specific address range over SPMI. Instead of using the parent SPMI device (the main PMIC) as a kind of syscon in this driver, register a new SPMI sub-device and initialize its own regmap with this sub-device's specific base address, retrieved from the devicetree. This allows to stop manually adding the register base address to every R/W call in this driver, as this can be, and is now, handled by the regmap API instead. Reviewed-by: Jonathan Cameron Tested-by: Neil Armstrong # on SM8650-QRD Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/iio/adc/qcom-spmi-iadc.c | 33 ++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-iadc.c b/drivers/iio/adc/qcom-spmi-iadc.c index 0ec3a0c4b1de..21e35df82193 100644 --- a/drivers/iio/adc/qcom-spmi-iadc.c +++ b/drivers/iio/adc/qcom-spmi-iadc.c @@ -16,6 +16,7 @@ #include #include #include +#include /* IADC register and bit definition */ #define IADC_REVISION2 0x1 @@ -94,7 +95,6 @@ * struct iadc_chip - IADC Current ADC device structure. * @regmap: regmap for register read/write. * @dev: This device pointer. - * @base: base offset for the ADC peripheral. * @rsense: Values of the internal and external sense resister in micro Ohms. * @poll_eoc: Poll for end of conversion instead of waiting for IRQ. * @offset: Raw offset values for the internal and external channels. @@ -105,7 +105,6 @@ struct iadc_chip { struct regmap *regmap; struct device *dev; - u16 base; bool poll_eoc; u32 rsense[2]; u16 offset[2]; @@ -119,7 +118,7 @@ static int iadc_read(struct iadc_chip *iadc, u16 offset, u8 *data) unsigned int val; int ret; - ret = regmap_read(iadc->regmap, iadc->base + offset, &val); + ret = regmap_read(iadc->regmap, offset, &val); if (ret < 0) return ret; @@ -129,7 +128,7 @@ static int iadc_read(struct iadc_chip *iadc, u16 offset, u8 *data) static int iadc_write(struct iadc_chip *iadc, u16 offset, u8 data) { - return regmap_write(iadc->regmap, iadc->base + offset, data); + return regmap_write(iadc->regmap, offset, data); } static int iadc_reset(struct iadc_chip *iadc) @@ -270,7 +269,7 @@ static int iadc_poll_wait_eoc(struct iadc_chip *iadc, unsigned int interval_us) static int iadc_read_result(struct iadc_chip *iadc, u16 *data) { - return regmap_bulk_read(iadc->regmap, iadc->base + IADC_DATA, data, 2); + return regmap_bulk_read(iadc->regmap, IADC_DATA, data, 2); } static int iadc_do_conversion(struct iadc_chip *iadc, int chan, u16 *data) @@ -488,12 +487,19 @@ static void iadc_disable_irq_wake(void *data) static int iadc_probe(struct platform_device *pdev) { + struct regmap_config iadc_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; struct device_node *node = pdev->dev.of_node; struct device *dev = &pdev->dev; + struct spmi_subdevice *sub_sdev; + struct spmi_device *sparent; struct iio_dev *indio_dev; struct iadc_chip *iadc; int ret, irq_eoc; - u32 res; indio_dev = devm_iio_device_alloc(dev, sizeof(*iadc)); if (!indio_dev) @@ -502,18 +508,24 @@ static int iadc_probe(struct platform_device *pdev) iadc = iio_priv(indio_dev); iadc->dev = dev; - iadc->regmap = dev_get_regmap(dev->parent, NULL); - if (!iadc->regmap) + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) return -ENODEV; + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + init_completion(&iadc->complete); mutex_init(&iadc->lock); - ret = of_property_read_u32(node, "reg", &res); + ret = device_property_read_u32(dev, "reg", &iadc_regmap_config.reg_base); if (ret < 0) return -ENODEV; - iadc->base = res; + iadc->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &iadc_regmap_config); + if (IS_ERR(iadc->regmap)) + return PTR_ERR(iadc->regmap); ret = iadc_version_check(iadc); if (ret < 0) @@ -596,3 +608,4 @@ MODULE_ALIAS("platform:qcom-spmi-iadc"); MODULE_DESCRIPTION("Qualcomm SPMI PMIC current ADC driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Ivan T. Ivanov "); +MODULE_IMPORT_NS("SPMI"); From c8f1aa63942f5e06cbecf3390fd57c92a4f44dd5 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 11:36:26 +0200 Subject: [PATCH 15/17] iio: adc: qcom-spmi-iadc: Remove regmap R/W wrapper functions This driver doesn't need to add any register base address to any regmap call anymore since it was migrated to register as a SPMI subdevice with its own regmap reg_base, which makes the regmap API to automatically add such base address internally. Since the iadc_{read,write,read_result}() functions now only do call regmap_{read,write,bulk_read}() and nothing else, simplify the driver by removing them and by calling regmap APIs directly. Reviewed-by: Jonathan Cameron Tested-by: Neil Armstrong # on SM8650-QRD Reviewed-by: Andy Shevchenko Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd --- drivers/iio/adc/qcom-spmi-iadc.c | 89 ++++++++++++-------------------- 1 file changed, 33 insertions(+), 56 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-iadc.c b/drivers/iio/adc/qcom-spmi-iadc.c index 21e35df82193..196525ba0b4b 100644 --- a/drivers/iio/adc/qcom-spmi-iadc.c +++ b/drivers/iio/adc/qcom-spmi-iadc.c @@ -113,77 +113,59 @@ struct iadc_chip { struct completion complete; }; -static int iadc_read(struct iadc_chip *iadc, u16 offset, u8 *data) -{ - unsigned int val; - int ret; - - ret = regmap_read(iadc->regmap, offset, &val); - if (ret < 0) - return ret; - - *data = val; - return 0; -} - -static int iadc_write(struct iadc_chip *iadc, u16 offset, u8 data) -{ - return regmap_write(iadc->regmap, offset, data); -} - static int iadc_reset(struct iadc_chip *iadc) { - u8 data; + u32 data; int ret; - ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); + ret = regmap_write(iadc->regmap, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); if (ret < 0) return ret; - ret = iadc_read(iadc, IADC_PERH_RESET_CTL3, &data); + ret = regmap_read(iadc->regmap, IADC_PERH_RESET_CTL3, &data); if (ret < 0) return ret; - ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); + ret = regmap_write(iadc->regmap, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); if (ret < 0) return ret; data |= IADC_FOLLOW_WARM_RB; - return iadc_write(iadc, IADC_PERH_RESET_CTL3, data); + return regmap_write(iadc->regmap, IADC_PERH_RESET_CTL3, data); } static int iadc_set_state(struct iadc_chip *iadc, bool state) { - return iadc_write(iadc, IADC_EN_CTL1, state ? IADC_EN_CTL1_SET : 0); + return regmap_write(iadc->regmap, IADC_EN_CTL1, state ? IADC_EN_CTL1_SET : 0); } static void iadc_status_show(struct iadc_chip *iadc) { - u8 mode, sta1, chan, dig, en, req; + u32 mode, sta1, chan, dig, en, req; int ret; - ret = iadc_read(iadc, IADC_MODE_CTL, &mode); + ret = regmap_read(iadc->regmap, IADC_MODE_CTL, &mode); if (ret < 0) return; - ret = iadc_read(iadc, IADC_DIG_PARAM, &dig); + ret = regmap_read(iadc->regmap, IADC_DIG_PARAM, &dig); if (ret < 0) return; - ret = iadc_read(iadc, IADC_CH_SEL_CTL, &chan); + ret = regmap_read(iadc->regmap, IADC_CH_SEL_CTL, &chan); if (ret < 0) return; - ret = iadc_read(iadc, IADC_CONV_REQ, &req); + ret = regmap_read(iadc->regmap, IADC_CONV_REQ, &req); if (ret < 0) return; - ret = iadc_read(iadc, IADC_STATUS1, &sta1); + ret = regmap_read(iadc->regmap, IADC_STATUS1, &sta1); if (ret < 0) return; - ret = iadc_read(iadc, IADC_EN_CTL1, &en); + ret = regmap_read(iadc->regmap, IADC_EN_CTL1, &en); if (ret < 0) return; @@ -199,34 +181,34 @@ static int iadc_configure(struct iadc_chip *iadc, int channel) /* Mode selection */ mode = (IADC_OP_MODE_NORMAL << IADC_OP_MODE_SHIFT) | IADC_TRIM_EN; - ret = iadc_write(iadc, IADC_MODE_CTL, mode); + ret = regmap_write(iadc->regmap, IADC_MODE_CTL, mode); if (ret < 0) return ret; /* Channel selection */ - ret = iadc_write(iadc, IADC_CH_SEL_CTL, channel); + ret = regmap_write(iadc->regmap, IADC_CH_SEL_CTL, channel); if (ret < 0) return ret; /* Digital parameter setup */ decim = IADC_DEF_DECIMATION << IADC_DIG_DEC_RATIO_SEL_SHIFT; - ret = iadc_write(iadc, IADC_DIG_PARAM, decim); + ret = regmap_write(iadc->regmap, IADC_DIG_PARAM, decim); if (ret < 0) return ret; /* HW settle time delay */ - ret = iadc_write(iadc, IADC_HW_SETTLE_DELAY, IADC_DEF_HW_SETTLE_TIME); + ret = regmap_write(iadc->regmap, IADC_HW_SETTLE_DELAY, IADC_DEF_HW_SETTLE_TIME); if (ret < 0) return ret; - ret = iadc_write(iadc, IADC_FAST_AVG_CTL, IADC_DEF_AVG_SAMPLES); + ret = regmap_write(iadc->regmap, IADC_FAST_AVG_CTL, IADC_DEF_AVG_SAMPLES); if (ret < 0) return ret; if (IADC_DEF_AVG_SAMPLES) - ret = iadc_write(iadc, IADC_FAST_AVG_EN, IADC_FAST_AVG_EN_SET); + ret = regmap_write(iadc->regmap, IADC_FAST_AVG_EN, IADC_FAST_AVG_EN_SET); else - ret = iadc_write(iadc, IADC_FAST_AVG_EN, 0); + ret = regmap_write(iadc->regmap, IADC_FAST_AVG_EN, 0); if (ret < 0) return ret; @@ -239,19 +221,19 @@ static int iadc_configure(struct iadc_chip *iadc, int channel) return ret; /* Request conversion */ - return iadc_write(iadc, IADC_CONV_REQ, IADC_CONV_REQ_SET); + return regmap_write(iadc->regmap, IADC_CONV_REQ, IADC_CONV_REQ_SET); } static int iadc_poll_wait_eoc(struct iadc_chip *iadc, unsigned int interval_us) { unsigned int count, retry; + u32 sta1; int ret; - u8 sta1; retry = interval_us / IADC_CONV_TIME_MIN_US; for (count = 0; count < retry; count++) { - ret = iadc_read(iadc, IADC_STATUS1, &sta1); + ret = regmap_read(iadc->regmap, IADC_STATUS1, &sta1); if (ret < 0) return ret; @@ -267,11 +249,6 @@ static int iadc_poll_wait_eoc(struct iadc_chip *iadc, unsigned int interval_us) return -ETIMEDOUT; } -static int iadc_read_result(struct iadc_chip *iadc, u16 *data) -{ - return regmap_bulk_read(iadc->regmap, IADC_DATA, data, 2); -} - static int iadc_do_conversion(struct iadc_chip *iadc, int chan, u16 *data) { unsigned int wait; @@ -296,7 +273,7 @@ static int iadc_do_conversion(struct iadc_chip *iadc, int chan, u16 *data) } if (!ret) - ret = iadc_read_result(iadc, data); + ret = regmap_bulk_read(iadc->regmap, IADC_DATA, data, sizeof(*data)); exit: iadc_set_state(iadc, false); if (ret < 0) @@ -392,33 +369,33 @@ static int iadc_update_offset(struct iadc_chip *iadc) static int iadc_version_check(struct iadc_chip *iadc) { - u8 val; + u32 val; int ret; - ret = iadc_read(iadc, IADC_PERPH_TYPE, &val); + ret = regmap_read(iadc->regmap, IADC_PERPH_TYPE, &val); if (ret < 0) return ret; if (val < IADC_PERPH_TYPE_ADC) { - dev_err(iadc->dev, "%d is not ADC\n", val); + dev_err(iadc->dev, "%u is not ADC\n", val); return -EINVAL; } - ret = iadc_read(iadc, IADC_PERPH_SUBTYPE, &val); + ret = regmap_read(iadc->regmap, IADC_PERPH_SUBTYPE, &val); if (ret < 0) return ret; if (val < IADC_PERPH_SUBTYPE_IADC) { - dev_err(iadc->dev, "%d is not IADC\n", val); + dev_err(iadc->dev, "%u is not IADC\n", val); return -EINVAL; } - ret = iadc_read(iadc, IADC_REVISION2, &val); + ret = regmap_read(iadc->regmap, IADC_REVISION2, &val); if (ret < 0) return ret; if (val < IADC_REVISION2_SUPPORTED_IADC) { - dev_err(iadc->dev, "revision %d not supported\n", val); + dev_err(iadc->dev, "revision %u not supported\n", val); return -EINVAL; } @@ -428,7 +405,7 @@ static int iadc_version_check(struct iadc_chip *iadc) static int iadc_rsense_read(struct iadc_chip *iadc, struct device_node *node) { int ret, sign, int_sense; - u8 deviation; + u32 deviation; ret = of_property_read_u32(node, "qcom,external-resistor-micro-ohms", &iadc->rsense[IADC_EXT_RSENSE]); @@ -440,7 +417,7 @@ static int iadc_rsense_read(struct iadc_chip *iadc, struct device_node *node) return -EINVAL; } - ret = iadc_read(iadc, IADC_NOMINAL_RSENSE, &deviation); + ret = regmap_read(iadc->regmap, IADC_NOMINAL_RSENSE, &deviation); if (ret < 0) return ret; From 636c4c026f47f0a7a009b1e39e310afca640a047 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 15:50:37 +0000 Subject: [PATCH 16/17] spmi: mtk-pmif: Fix clock reference leak in mtk_spmi_bus_probe() of_clk_get_by_name() returns a reference that must be released with clk_put(). If the lookup of a later clock fails, the function returns without releasing the clocks that were already obtained. Put those clocks before returning. Fixes: 078117963b2c ("spmi: mtk-pmif: Add multi-bus support for SPMI 2.0") Signed-off-by: Wentao Liang Reviewed-by: Abel Vesa Signed-off-by: Stephen Boyd --- drivers/spmi/spmi-mtk-pmif.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c index 61c916edaec4..833c2b30d09f 100644 --- a/drivers/spmi/spmi-mtk-pmif.c +++ b/drivers/spmi/spmi-mtk-pmif.c @@ -746,9 +746,11 @@ static int mtk_spmi_bus_probe(struct platform_device *pdev, for (i = 0; i < pbus->nclks; i++) { pbus->clks[i].id = pmif_clock_names[i]; pbus->clks[i].clk = of_clk_get_by_name(node, pbus->clks[i].id); - if (IS_ERR(pbus->clks[i].clk)) + if (IS_ERR(pbus->clks[i].clk)) { + clk_bulk_put(i, pbus->clks); return dev_err_probe(&pdev->dev, PTR_ERR(pbus->clks[i].clk), "Failed to get clocks\n"); + } } err = clk_bulk_prepare_enable(pbus->nclks, pbus->clks); From 8f70c729e68c3e02ccca6d13c733175489fdc069 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Thu, 8 Oct 2026 23:57:45 +0300 Subject: [PATCH 17/17] MAINTAINERS: Add Abel as SPMI co-maintainer Add myself as a co-maintainer for the SPMI subsystem to help with patch review and handling. Signed-off-by: Abel Vesa Signed-off-by: Stephen Boyd --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 90de9c6aea0e..eaeb60c1e1c2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25844,6 +25844,7 @@ F: tools/spi/ SPMI SUBSYSTEM M: Stephen Boyd +M: Abel Vesa L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/sboyd/spmi.git