From 05a0fe8e43c876ffd2befb5a406d3baf3179b9fe Mon Sep 17 00:00:00 2001 From: Joan Na Date: Sun, 7 Dec 2025 12:29:06 +0900 Subject: regulator: dt-bindings: Add MAX77675 regulator Add device tree binding YAML schema for the Maxim MAX77675 PMIC regulator. Signed-off-by: Joan Na Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20251207032907.4850-2-joan.na@analog.com Signed-off-by: Mark Brown --- .../bindings/regulator/adi,max77675.yaml | 184 +++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/adi,max77675.yaml diff --git a/Documentation/devicetree/bindings/regulator/adi,max77675.yaml b/Documentation/devicetree/bindings/regulator/adi,max77675.yaml new file mode 100644 index 000000000000..c138e61380a4 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/adi,max77675.yaml @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/adi,max77675.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX77675 PMIC Regulator + +maintainers: + - Joan Na + +description: + The MAX77675 is a Power Management IC providing four switching buck + regulators (SBB0–SBB3) accessible via I2C. It supports configuration + of output voltages and enable controls for each regulator. + +allOf: + - $ref: /schemas/input/input.yaml + - $ref: /schemas/pinctrl/pincfg-node.yaml + +properties: + compatible: + const: adi,max77675 + + reg: + maxItems: 1 + + reset-time-sec: + description: Manual reset time in seconds + enum: [4, 8, 12, 16] + default: 4 + + bias-disable: + type: boolean + description: Disable internal pull-up for EN pin + + input-debounce: + description: Debounce time for the enable pin, in microseconds + items: + - enum: [100, 30000] + default: 100 + + adi,en-mode: + description: | + Enable mode configuration. + The debounce time set by 'input-debounce' applies to + both push-button and slide-switch modes. + "push-button" - A long press triggers power-on or power-down + "slide-switch" - Low : powers on, High : powers down + "logic" - Low : powers on, High : powers down (no debounce time) + $ref: /schemas/types.yaml#/definitions/string + enum: [push-button, slide-switch, logic] + default: slide-switch + + adi,voltage-change-latency-us: + description: + Specifies the delay (in microseconds) between an output voltage change + request and the start of the SBB voltage ramp. + enum: [10, 100] + default: 100 + + adi,drv-sbb-strength: + description: | + SIMO Buck-Boost Drive Strength Trim. + Controls the drive strength of the SIMO regulator's power MOSFETs. + This setting affects switching speed, impacting power efficiency and EMI. + "max" – Maximum drive strength (~0.6 ns transition time) + "high" – High drive strength (~1.2 ns transition time) + "low" – Low drive strength (~1.8 ns transition time) + "min" – Minimum drive strength (~8 ns transition time) + $ref: /schemas/types.yaml#/definitions/string + enum: [max, high, low, min] + default: max + + adi,dvs-slew-rate-mv-per-us: + description: + Dynamic rising slew rate for output voltage transitions, in mV/μs. + This setting is only used when 'adi,fixed-slew-rate' is not present. + enum: [5, 10] + default: 5 + + adi,bias-low-power-request: + type: boolean + description: Request low-power bias mode + + adi,simo-ldo-always-on: + type: boolean + description: Set internal LDO to always supply 1.8V + + regulators: + type: object + description: Regulator child nodes + patternProperties: + "^sbb[0-3]$": + type: object + $ref: regulator.yaml# + properties: + adi,fps-slot: + description: | + FPS (Flexible Power Sequencer) slot selection. + The Flexible Power Sequencer allows resources to power up under + hardware or software control. Additionally, each resource can + power up independently or among a group of other regulators with + adjustable power-up and power-down slots. + "slot0" - Assign to FPS Slot 0 + "slot1" - Assign to FPS Slot 1 + "slot2" - Assign to FPS Slot 2 + "slot3" - Assign to FPS Slot 3 + "default" - Use the default FPS slot value stored in register + $ref: /schemas/types.yaml#/definitions/string + enum: [slot0, slot1, slot2, slot3, default] + default: default + + adi,fixed-slew-rate: + type: boolean + description: + When this property is present, the device uses a constant 2 mV/μs + slew rate and ignores any dynamic slew rate configuration. + When absent, the device uses the dynamic slew rate specified + by 'adi,dvs-slew-rate-mv-per-us' + + unevaluatedProperties: false + +required: + - compatible + - reg + - regulators + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + max77675: pmic@44 { + compatible = "adi,max77675"; + reg = <0x44>; + + reset-time-sec = <4>; + input-debounce = <100>; + + adi,en-mode = "slide-switch"; + adi,voltage-change-latency-us = <100>; + adi,drv-sbb-strength = "max"; + adi,dvs-slew-rate-mv-per-us = <5>; + + regulators { + sbb0: sbb0 { + regulator-name = "sbb0"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <5500000>; + adi,fps-slot = "default"; + adi,fixed-slew-rate; + }; + + sbb1: sbb1 { + regulator-name = "sbb1"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <5500000>; + adi,fps-slot = "default"; + adi,fixed-slew-rate; + }; + + sbb2: sbb2 { + regulator-name = "sbb2"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <5500000>; + adi,fps-slot = "default"; + adi,fixed-slew-rate; + }; + + sbb3: sbb3 { + regulator-name = "sbb3"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <5500000>; + adi,fps-slot = "default"; + adi,fixed-slew-rate; + }; + }; + }; + }; + -- cgit v1.2.3 From 9e92c559d49d6fb903af17a31a469aac51b1766d Mon Sep 17 00:00:00 2001 From: Joan Na Date: Sun, 7 Dec 2025 12:29:07 +0900 Subject: regulator: max77675: Add MAX77675 regulator driver Add support for the Maxim Integrated MAX77675 PMIC regulator. The MAX77675 is a compact, highly efficient SIMO (Single Inductor Multiple Output) power management IC that provides four programmable buck-boost switching regulators with only one inductor. It supports up to 700mA total output current and operates from a single-cell Li-ion battery. An integrated power-up sequencer and I2C interface allow flexible startup configuration and runtime control. Signed-off-by: Joan Na Link: https://patch.msgid.link/20251207032907.4850-3-joan.na@analog.com Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/max77675-regulator.c | 1056 ++++++++++++++++++++++++++++++++ 3 files changed, 1066 insertions(+) create mode 100644 drivers/regulator/max77675-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index d2335276cce5..98966ed36f8e 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -659,6 +659,15 @@ config REGULATOR_MAX77650 Semiconductor. This device has a SIMO with three independent power rails and an LDO. +config REGULATOR_MAX77675 + tristate "Maxim MAX77675 regulator driver" + depends on I2C && OF + select REGMAP_I2C + help + This driver controls the Maxim MAX77675 power regulator via I2C. + It supports four programmable buck-boost outputs. + Say Y here to enable the regulator driver + config REGULATOR_MAX77857 tristate "ADI MAX77857/MAX77831 regulator support" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 1beba1493241..1836fedfeb5d 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -79,6 +79,7 @@ obj-$(CONFIG_REGULATOR_MAX77503) += max77503-regulator.o obj-$(CONFIG_REGULATOR_MAX77541) += max77541-regulator.o obj-$(CONFIG_REGULATOR_MAX77620) += max77620-regulator.o obj-$(CONFIG_REGULATOR_MAX77650) += max77650-regulator.o +obj-$(CONFIG_REGULATOR_MAX77675) += max77675-regulator.o obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o obj-$(CONFIG_REGULATOR_MAX8893) += max8893.o diff --git a/drivers/regulator/max77675-regulator.c b/drivers/regulator/max77675-regulator.c new file mode 100644 index 000000000000..af3eb7174875 --- /dev/null +++ b/drivers/regulator/max77675-regulator.c @@ -0,0 +1,1056 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Analog Devices, Inc. + * ADI regulator driver for MAX77675. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register Addresses */ +#define MAX77675_REG_CNFG_GLBL_A 0x00 +#define MAX77675_REG_CNFG_GLBL_B 0x01 +#define MAX77675_REG_INT_GLBL 0x02 +#define MAX77675_REG_INTM_GLBL 0x03 +#define MAX77675_REG_STAT_GLBL 0x04 +#define MAX77675_REG_ERCF_GLBL 0x05 +#define MAX77675_REG_CID 0x06 +#define MAX77675_REG_CNFG_SBB_TOP_A 0x07 +#define MAX77675_REG_CNFG_SBB0_A 0x08 +#define MAX77675_REG_CNFG_SBB0_B 0x09 +#define MAX77675_REG_CNFG_SBB1_A 0x0A +#define MAX77675_REG_CNFG_SBB1_B 0x0B +#define MAX77675_REG_CNFG_SBB2_A 0x0C +#define MAX77675_REG_CNFG_SBB2_B 0x0D +#define MAX77675_REG_CNFG_SBB3_A 0x0E +#define MAX77675_REG_CNFG_SBB3_B 0x0F +#define MAX77675_REG_CNFG_SBB_TOP_B 0x10 + +/* CNFG_GLBL_A (0x00) bit masks and shifts */ +#define MAX77675_MRT_MASK GENMASK(7, 6) /* Manual Reset Time (bits 7:6) */ +#define MAX77675_MRT_SHIFT 6 +#define MAX77675_PU_DIS_BIT BIT(5) /* Pullup Disable (bit 5) */ +#define MAX77675_PU_DIS_SHIFT 5 +#define MAX77675_BIAS_LPM_BIT BIT(4) /* Bias Low Power Mode (bit 4) */ +#define MAX77675_BIAS_LPM_SHIFT 4 +#define MAX77675_SIMO_CH_DIS_BIT BIT(3) /* SIMO Internal Channel Disable (bit 3) */ +#define MAX77675_SIMO_CH_DIS_SHIFT 3 +#define MAX77675_EN_MODE_MASK GENMASK(2, 1) /* nEN Mode (bits 2:1) */ +#define MAX77675_EN_MODE_SHIFT 1 +#define MAX77675_DBEN_EN_BIT BIT(0) /* Debounce Enable (bit 0) */ +#define MAX77675_DBEN_EN_SHIFT 0 + +/* CNFG_GLBL_B (0x01) */ +#define MAX77675_SFT_CTRL_MASK GENMASK(2, 0) /* Soft Start Control */ +#define MAX77675_SFT_CTRL_SHIFT 0 + +/* INT_GLBL (0x02) bit bits and shifts */ +#define MAX77675_INT_SBB3_F_BIT BIT(7) +#define MAX77675_INT_SBB3_F_SHIFT 7 +#define MAX77675_INT_SBB2_F_BIT BIT(6) +#define MAX77675_INT_SBB2_F_SHIFT 6 +#define MAX77675_INT_SBB1_F_BIT BIT(5) +#define MAX77675_INT_SBB1_F_SHIFT 5 +#define MAX77675_INT_SBB0_F_BIT BIT(4) +#define MAX77675_INT_SBB0_F_SHIFT 4 +#define MAX77675_INT_TJAL2_R_BIT BIT(3) +#define MAX77675_INT_TJAL2_R_SHIFT 3 +#define MAX77675_INT_TJAL1_R_BIT BIT(2) +#define MAX77675_INT_TJAL1_R_SHIFT 2 +#define MAX77675_INT_EN_R_BIT BIT(1) +#define MAX77675_INT_EN_R_SHIFT 1 +#define MAX77675_INT_EN_F_BIT BIT(0) +#define MAX77675_INT_EN_F_SHIFT 0 + +/* INTM_GLBL (0x03) bits and shifts */ +#define MAX77675_INTM_SBB3_F_BIT BIT(7) +#define MAX77675_INTM_SBB3_F_SHIFT 7 +#define MAX77675_INTM_SBB2_F_BIT BIT(6) +#define MAX77675_INTM_SBB2_F_SHIFT 6 +#define MAX77675_INTM_SBB1_F_BIT BIT(5) +#define MAX77675_INTM_SBB1_F_SHIFT 5 +#define MAX77675_INTM_SBB0_F_BIT BIT(4) +#define MAX77675_INTM_SBB0_F_SHIFT 4 +#define MAX77675_INTM_TJAL2_R_BIT BIT(3) +#define MAX77675_INTM_TJAL2_R_SHIFT 3 +#define MAX77675_INTM_TJAL1_R_BIT BIT(2) +#define MAX77675_INTM_TJAL1_R_SHIFT 2 +#define MAX77675_INTM_EN_R_BIT BIT(1) +#define MAX77675_INTM_EN_R_SHIFT 1 +#define MAX77675_INTM_EN_F_BIT BIT(0) +#define MAX77675_INTM_EN_F_SHIFT 0 + +/* STAT_GLBL (0x04) bits and shifts */ +#define MAX77675_STAT_SBB3_S_BIT BIT(7) +#define MAX77675_STAT_SBB3_S_SHIFT 7 +#define MAX77675_STAT_SBB2_S_BIT BIT(6) +#define MAX77675_STAT_SBB2_S_SHIFT 6 +#define MAX77675_STAT_SBB1_S_BIT BIT(5) +#define MAX77675_STAT_SBB1_S_SHIFT 5 +#define MAX77675_STAT_SBB0_S_BIT BIT(4) +#define MAX77675_STAT_SBB0_S_SHIFT 4 +#define MAX77675_STAT_TJAL2_S_BIT BIT(2) +#define MAX77675_STAT_TJAL2_S_SHIFT 2 +#define MAX77675_STAT_TJAL1_S_BIT BIT(1) +#define MAX77675_STAT_TJAL1_S_SHIFT 1 +#define MAX77675_STAT_STAT_EN_BIT BIT(0) +#define MAX77675_STAT_STAT_EN_SHIFT 0 + +#define MAX77675_STAT_STAT_EN_BIT BIT(0) +#define MAX77675_STAT_STAT_EN_SHIFT 0 + +/* ERCFLAG (0x05) bits and shifts */ +#define MAX77675_SFT_CRST_F_BIT BIT(5) /* Software Cold Reset Flag */ +#define MAX77675_SFT_CRST_F_SHIFT 5 +#define MAX77675_SFT_OFF_F_BIT BIT(4) /* Software Off Flag */ +#define MAX77675_SFT_OFF_F_SHIFT 4 +#define MAX77675_MRST_BIT BIT(3) /* Manual Reset Timer Flag */ +#define MAX77675_MRST_SHIFT 3 +#define MAX77675_UVLO_BIT BIT(2) /* Undervoltage Lockout Flag */ +#define MAX77675_UVLO_SHIFT 2 +#define MAX77675_OVLO_BIT BIT(1) /* Overvoltage Lockout Flag */ +#define MAX77675_OVLO_SHIFT 1 +#define MAX77675_TOVLD_BIT BIT(0) /* Thermal Overload Flag */ +#define MAX77675_TOVLD_SHIFT 0 + +/* CID (0x06) bits and shifts */ +#define MAX77675_CID_MASK GENMASK(4, 0) /* Chip Identification Code mask */ +#define MAX77675_CID_SHIFT 0 /* Starts at bit 0 */ + +/* CNFG_SBB_TOP_A (0x07) bits and shifts */ +#define MAX77675_STEP_SZ_SBB3_BIT BIT(5) +#define MAX77675_STEP_SZ_SBB3_SHIFT 5 +#define MAX77675_STEP_SZ_SBB2_BIT BIT(4) +#define MAX77675_STEP_SZ_SBB2_SHIFT 4 +#define MAX77675_STEP_SZ_SBB1_BIT BIT(3) +#define MAX77675_STEP_SZ_SBB1_SHIFT 3 +#define MAX77675_STEP_SZ_SBB0_BIT BIT(2) +#define MAX77675_STEP_SZ_SBB0_SHIFT 2 +#define MAX77675_DRV_SBB_MASK GENMASK(1, 0) +#define MAX77675_DRV_SBB_SHIFT 0 + +/* CNFG_SBB0_A (0x08) bits and shifts */ +#define MAX77675_TV_SBB0_MASK GENMASK(7, 0) +#define MAX77675_TV_SBB0_SHIFT 0 + +/* CNFG_SBB0_B (0x09) bits and shifts */ +#define MAX77675_ADE_SBB0_BIT BIT(3) +#define MAX77675_ADE_SBB0_SHIFT 3 +#define MAX77675_EN_SBB0_MASK GENMASK(2, 0) +#define MAX77675_EN_SBB0_SHIFT 0 + +/* CNFG_SBB1_A (0x0A) bits and shifts */ +#define MAX77675_TV_SBB1_MASK GENMASK(7, 0) +#define MAX77675_TV_SBB1_SHIFT 0 + +/* CNFG_SBB1_B (0x0B) bits and shifts */ +#define MAX77675_ADE_SBB1_BIT BIT(3) +#define MAX77675_ADE_SBB1_SHIFT 3 +#define MAX77675_EN_SBB1_MASK GENMASK(2, 0) +#define MAX77675_EN_SBB1_SHIFT 0 + +/* CNFG_SBB2_A (0x0C) bits and shifts */ +#define MAX77675_TV_SBB2_MASK GENMASK(7, 0) +#define MAX77675_TV_SBB2_SHIFT 0 + +/* CNFG_SBB2_B (0x0D) bits and shifts */ +#define MAX77675_ADE_SBB2_BIT BIT(3) +#define MAX77675_ADE_SBB2_SHIFT 3 +#define MAX77675_EN_SBB2_MASK GENMASK(2, 0) +#define MAX77675_EN_SBB2_SHIFT 0 + +/* CNFG_SBB3_A (0x0E) bits and shifts */ +#define MAX77675_TV_SBB3_MASK GENMASK(7, 0) +#define MAX77675_TV_SBB3_SHIFT 0 + +/* CNFG_SBB3_B (0x0F) bits and shifts */ +#define MAX77675_ADE_SBB3_BIT BIT(3) +#define MAX77675_ADE_SBB3_SHIFT 3 +#define MAX77675_EN_SBB3_MASK GENMASK(2, 0) +#define MAX77675_EN_SBB3_SHIFT 0 + +#define MAX77675_EN_SBB_MASK GENMASK(2, 0) + +/* CNFG_SBB_TOP_B (0x10) bits and shifts */ +#define MAX77675_DVS_SLEW_BIT BIT(5) +#define MAX77675_DVS_SLEW_SHIFT 5 +#define MAX77675_LAT_MODE_BIT BIT(4) +#define MAX77675_LAT_MODE_SHIFT 4 +#define MAX77675_SR_SBB3_BIT BIT(3) +#define MAX77675_SR_SBB3_SHIFT 3 +#define MAX77675_SR_SBB2_BIT BIT(2) +#define MAX77675_SR_SBB2_SHIFT 2 +#define MAX77675_SR_SBB1_BIT BIT(1) +#define MAX77675_SR_SBB1_SHIFT 1 +#define MAX77675_SR_SBB0_BIT BIT(0) +#define MAX77675_SR_SBB0_SHIFT 0 + +#define MAX77675_MAX_REGISTER 0x10 + +/* Common minimum voltage (in microvolts) */ +#define MAX77675_MIN_UV 500000 // 500 mV + +/* Voltage step configuration for 25mV mode */ +#define MAX77675_STEP_25MV 25000 // Step size: 25 mV +#define MAX77675_MAX_UV_25MV 5500000 // Max voltage: 5.5 V +#define MAX77675_NUM_LEVELS_25MV 201 // levels = (5500mV - 500mV) / 25mV + 1 + +/* Voltage step configuration for 12.5mV mode */ +#define MAX77675_STEP_12_5MV 12500 // Step size: 12.5 mV +#define MAX77675_MAX_UV_12_5MV 3687500 // Max voltage: 3.6875 V +#define MAX77675_NUM_LEVELS_12_5MV 255 // levels = (3687.5mV - 500mV) / 12.5mV + 1 + +#define MAX77675_ENABLE_OFF 0x04 +#define MAX77675_ENABLE_ON 0x06 + +#define MAX77675_REGULATOR_AD_OFF 0x00 +#define MAX77675_REGULATOR_AD_ON BIT(3) + +/* FPS source */ +#define MAX77675_FPS_SLOT_0 0x0 +#define MAX77675_FPS_SLOT_1 0x1 +#define MAX77675_FPS_SLOT_2 0x2 +#define MAX77675_FPS_SLOT_3 0x3 +#define MAX77675_FPS_DEF 0x4 + +/* nEN Manual Reset Time Configuration (MRT) */ +#define MAX77675_MRT_4S 0x0 +#define MAX77675_MRT_8S 0x1 +#define MAX77675_MRT_12S 0x2 +#define MAX77675_MRT_16S 0x3 + +/* nEN Mode Configuration */ +#define MAX77675_EN_PUSH_BUTTON 0x0 +#define MAX77675_EN_SLIDE_SWITCH 0x1 +#define MAX77675_EN_LOGIC 0x2 + +/* Debounce Timer Enable (DBEN_nEN) */ +#define MAX77675_DBEN_100US 0x0 +#define MAX77675_DBEN_30000US 0x1 + +/* Rising slew rate control for SBB0 when ramping up */ +#define MAX77675_SR_2MV_PER_US 0x0 // 2 mV/us +#define MAX77675_SR_USE_DVS 0x1 // Use DVS slew rate setting (adi,dvs-slew-rate) + +/* Latency Mode */ +#define MAX77675_HIGH_LATENCY_MODE 0x0 // High latency, low quiescent current (~100us) +#define MAX77675_LOW_LATENCY_MODE 0x1 // Low latency, high quiescent current (~10us) + +/* Dynamic Voltage Scaling (DVS) Slew Rate */ +#define MAX77675_DVS_SLEW_5MV_PER_US 0x0 // 5 mV/us +#define MAX77675_DVS_SLEW_10MV_PER_US 0x1 // 10 mV/us + +/* SIMO Buck-Boost Drive Strength (All Channels) */ +#define MAX77675_DRV_SBB_STRENGTH_MAX 0x0 // Maximum drive strength (~0.6 ns transition time) +#define MAX77675_DRV_SBB_STRENGTH_HIGH 0x1 // High drive strength (~1.2 ns transition time) +#define MAX77675_DRV_SBB_STRENGTH_LOW 0x2 // Low drive strength (~1.8 ns transition time) +#define MAX77675_DRV_SBB_STRENGTH_MIN 0x3 // Minimum drive strength (~8 ns transition time) + +/* Regulator ID enumeration */ +enum max77675_regulator_id { + MAX77675_ID_SBB0 = 0, + MAX77675_ID_SBB1, + MAX77675_ID_SBB2, + MAX77675_ID_SBB3, + MAX77675_ID_NUM_MAX, +}; + +struct max77675_regulator_sbb_setting { + u8 fps_slot; + bool fixed_slew_rate; +}; + +struct max77675_config { + u8 en_mode; + u8 voltage_change_latency; + u8 drv_sbb_strength; + u8 dvs_slew_rate; + u8 debounce_time; + u8 manual_reset_time; + bool en_pullup_disable; + bool bias_low_power_request; + bool simo_ldo_always_on; +}; + +struct max77675_regulator { + struct device *dev; + struct regmap *regmap; + struct max77675_config config; + struct max77675_regulator_sbb_setting sbb_setting[MAX77675_ID_NUM_MAX]; +}; + +static int max77675_regulator_get_fps_src(struct max77675_regulator *maxreg, int id) +{ + unsigned int reg_addr; + unsigned int val; + int ret; + + switch (id) { + case MAX77675_ID_SBB0: + reg_addr = MAX77675_REG_CNFG_SBB0_B; + break; + case MAX77675_ID_SBB1: + reg_addr = MAX77675_REG_CNFG_SBB1_B; + break; + case MAX77675_ID_SBB2: + reg_addr = MAX77675_REG_CNFG_SBB2_B; + break; + case MAX77675_ID_SBB3: + reg_addr = MAX77675_REG_CNFG_SBB3_B; + break; + default: + dev_err(maxreg->dev, "Invalid regulator id: %d\n", id); + return -EINVAL; + } + + ret = regmap_read(maxreg->regmap, reg_addr, &val); + if (ret < 0) { + dev_err(maxreg->dev, "Failed to read FPS source (reg 0x%02x): %d\n", + reg_addr, ret); + return ret; + } + + return FIELD_GET(MAX77675_EN_SBB_MASK, val); +} + +static int max77675_regulator_set_fps_src(struct max77675_regulator *maxreg, int id, u8 fps_src) +{ + unsigned int reg_addr; + + switch (id) { + case MAX77675_ID_SBB0: + reg_addr = MAX77675_REG_CNFG_SBB0_B; + break; + case MAX77675_ID_SBB1: + reg_addr = MAX77675_REG_CNFG_SBB1_B; + break; + case MAX77675_ID_SBB2: + reg_addr = MAX77675_REG_CNFG_SBB2_B; + break; + case MAX77675_ID_SBB3: + reg_addr = MAX77675_REG_CNFG_SBB3_B; + break; + default: + dev_err(maxreg->dev, "Invalid regulator id: %d\n", id); + return -EINVAL; + } + + return regmap_update_bits(maxreg->regmap, reg_addr, MAX77675_EN_SBB_MASK, fps_src); +} + +static int max77675_set_sbb_slew_rate_fixed(struct max77675_regulator *maxreg, int id, bool fixed) +{ + u8 mask, value; + u8 slew_src_ctrl_bit = fixed ? 0 : 1; + + switch (id) { + case MAX77675_ID_SBB0: + mask = MAX77675_SR_SBB0_BIT; + value = FIELD_PREP(MAX77675_SR_SBB0_BIT, slew_src_ctrl_bit); + break; + + case MAX77675_ID_SBB1: + mask = MAX77675_SR_SBB1_BIT; + value = FIELD_PREP(MAX77675_SR_SBB1_BIT, slew_src_ctrl_bit); + break; + + case MAX77675_ID_SBB2: + mask = MAX77675_SR_SBB2_BIT; + value = FIELD_PREP(MAX77675_SR_SBB2_BIT, slew_src_ctrl_bit); + break; + + case MAX77675_ID_SBB3: + mask = MAX77675_SR_SBB3_BIT; + value = FIELD_PREP(MAX77675_SR_SBB3_BIT, slew_src_ctrl_bit); + break; + + default: + return -EINVAL; + } + + return regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_SBB_TOP_B, mask, value); +} + +static int max77675_init_regulator(struct max77675_regulator *maxreg, int id) +{ + struct max77675_regulator_sbb_setting *sbb_setting = &maxreg->sbb_setting[id]; + int ret; + + if (sbb_setting->fps_slot == MAX77675_FPS_DEF) { + ret = max77675_regulator_get_fps_src(maxreg, id); + if (ret < 0) + return ret; + + sbb_setting->fps_slot = ret; + } else { + ret = max77675_regulator_set_fps_src(maxreg, id, sbb_setting->fps_slot); + if (ret < 0) + return ret; + } + + ret = max77675_set_sbb_slew_rate_fixed(maxreg, id, sbb_setting->fixed_slew_rate); + if (ret < 0) + return ret; + + return 0; +} + +static int max77675_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct max77675_regulator *maxreg = config->driver_data; + struct max77675_regulator_sbb_setting *sbb_setting = &maxreg->sbb_setting[desc->id]; + static const char * const fps_slots[] = { "slot0", "slot1", "slot2", "slot3", "default" }; + const char *fps_str; + int slot; + + /* Parse FPS slot from DT */ + if (of_property_read_string(np, "adi,fps-slot", &fps_str)) { + /* Property not set, use default */ + sbb_setting->fps_slot = MAX77675_FPS_DEF; + } else { + /* Match string to index */ + slot = match_string(fps_slots, ARRAY_SIZE(fps_slots), fps_str); + if (slot < 0) { + dev_dbg(maxreg->dev, "Invalid fps-slot '%s', using default\n", fps_str); + sbb_setting->fps_slot = MAX77675_FPS_DEF; + } else { + sbb_setting->fps_slot = slot; + } + } + + /* Parse slew rate control source */ + sbb_setting->fixed_slew_rate = of_property_read_bool(np, "adi,fixed-slew-rate"); + + /* Apply parsed configuration */ + return max77675_init_regulator(maxreg, desc->id); +} + +static int max77675_get_error_flags(struct regulator_dev *rdev, unsigned int *flags) +{ + struct max77675_regulator *maxreg = rdev_get_drvdata(rdev); + unsigned int int_flags; + int id = rdev_get_id(rdev); + int ret; + + ret = regmap_read(maxreg->regmap, MAX77675_REG_INT_GLBL, &int_flags); + if (ret) { + dev_err(maxreg->dev, "Failed to read INT_GLBL: %d\n", ret); + return ret; + } + + *flags = 0; + + switch (id) { + case MAX77675_ID_SBB0: + if (int_flags & MAX77675_INT_SBB0_F_BIT) + *flags |= REGULATOR_ERROR_FAIL; + break; + case MAX77675_ID_SBB1: + if (int_flags & MAX77675_INT_SBB1_F_BIT) + *flags |= REGULATOR_ERROR_FAIL; + break; + case MAX77675_ID_SBB2: + if (int_flags & MAX77675_INT_SBB2_F_BIT) + *flags |= REGULATOR_ERROR_FAIL; + break; + case MAX77675_ID_SBB3: + if (int_flags & MAX77675_INT_SBB3_F_BIT) + *flags |= REGULATOR_ERROR_FAIL; + break; + default: + dev_warn(maxreg->dev, "Unsupported regulator ID: %d\n", id); + break; + } + + if (int_flags & MAX77675_INT_TJAL2_R_BIT) { + /* TJAL2 interrupt: Over-temperature condition (above 120 degree) */ + *flags |= REGULATOR_ERROR_OVER_TEMP; + } + + return 0; +} + +static const struct regulator_ops max77675_regulator_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .map_voltage = regulator_map_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_active_discharge = regulator_set_active_discharge_regmap, + .get_error_flags = max77675_get_error_flags, +}; + +static struct regulator_desc max77675_regulators[MAX77675_ID_NUM_MAX] = { + { + .name = "sbb0", + .of_match = of_match_ptr("sbb0"), + .regulators_node = of_match_ptr("regulators"), + .of_parse_cb = max77675_of_parse_cb, + .id = MAX77675_ID_SBB0, + .ops = &max77675_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = MAX77675_NUM_LEVELS_25MV, + .min_uV = MAX77675_MIN_UV, + .uV_step = MAX77675_STEP_25MV, + .vsel_reg = MAX77675_REG_CNFG_SBB0_A, + .vsel_mask = MAX77675_TV_SBB0_MASK, + .enable_reg = MAX77675_REG_CNFG_SBB0_B, + .enable_mask = MAX77675_EN_SBB0_MASK, + .enable_val = MAX77675_ENABLE_ON, + .disable_val = MAX77675_ENABLE_OFF, + .active_discharge_off = MAX77675_REGULATOR_AD_OFF, + .active_discharge_on = MAX77675_REGULATOR_AD_ON, + .active_discharge_mask = MAX77675_ADE_SBB0_BIT, + .active_discharge_reg = MAX77675_REG_CNFG_SBB0_B, + }, + { + .name = "sbb1", + .of_match = of_match_ptr("sbb1"), + .regulators_node = of_match_ptr("regulators"), + .of_parse_cb = max77675_of_parse_cb, + .id = MAX77675_ID_SBB1, + .ops = &max77675_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = MAX77675_NUM_LEVELS_25MV, + .min_uV = MAX77675_MIN_UV, + .uV_step = MAX77675_STEP_25MV, + .vsel_reg = MAX77675_REG_CNFG_SBB1_A, + .vsel_mask = MAX77675_TV_SBB1_MASK, + .enable_reg = MAX77675_REG_CNFG_SBB1_B, + .enable_mask = MAX77675_EN_SBB1_MASK, + .enable_val = MAX77675_ENABLE_ON, + .disable_val = MAX77675_ENABLE_OFF, + .active_discharge_off = MAX77675_REGULATOR_AD_OFF, + .active_discharge_on = MAX77675_REGULATOR_AD_ON, + .active_discharge_mask = MAX77675_ADE_SBB1_BIT, + .active_discharge_reg = MAX77675_REG_CNFG_SBB1_B, + }, + { + .name = "sbb2", + .of_match = of_match_ptr("sbb2"), + .regulators_node = of_match_ptr("regulators"), + .of_parse_cb = max77675_of_parse_cb, + .id = MAX77675_ID_SBB2, + .ops = &max77675_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = MAX77675_NUM_LEVELS_25MV, + .min_uV = MAX77675_MIN_UV, + .uV_step = MAX77675_STEP_25MV, + .vsel_reg = MAX77675_REG_CNFG_SBB2_A, + .vsel_mask = MAX77675_TV_SBB2_MASK, + .enable_reg = MAX77675_REG_CNFG_SBB2_B, + .enable_mask = MAX77675_EN_SBB2_MASK, + .enable_val = MAX77675_ENABLE_ON, + .disable_val = MAX77675_ENABLE_OFF, + .active_discharge_off = MAX77675_REGULATOR_AD_OFF, + .active_discharge_on = MAX77675_REGULATOR_AD_ON, + .active_discharge_mask = MAX77675_ADE_SBB2_BIT, + .active_discharge_reg = MAX77675_REG_CNFG_SBB2_B, + }, + { + .name = "sbb3", + .of_match = of_match_ptr("sbb3"), + .regulators_node = of_match_ptr("regulators"), + .of_parse_cb = max77675_of_parse_cb, + .id = MAX77675_ID_SBB3, + .ops = &max77675_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = MAX77675_NUM_LEVELS_25MV, + .min_uV = MAX77675_MIN_UV, + .uV_step = MAX77675_STEP_25MV, + .vsel_reg = MAX77675_REG_CNFG_SBB3_A, + .vsel_mask = MAX77675_TV_SBB3_MASK, + .enable_reg = MAX77675_REG_CNFG_SBB3_B, + .enable_mask = MAX77675_EN_SBB3_MASK, + .enable_val = MAX77675_ENABLE_ON, + .disable_val = MAX77675_ENABLE_OFF, + .active_discharge_off = MAX77675_REGULATOR_AD_OFF, + .active_discharge_on = MAX77675_REGULATOR_AD_ON, + .active_discharge_mask = MAX77675_ADE_SBB3_BIT, + .active_discharge_reg = MAX77675_REG_CNFG_SBB3_B, + }, +}; + +static bool max77675_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX77675_REG_CNFG_GLBL_B: + /* This register can be updated by an internal state machine */ + case MAX77675_REG_INT_GLBL: + case MAX77675_REG_STAT_GLBL: + case MAX77675_REG_ERCF_GLBL: + return true; + default: + return false; + } +} + +static const struct regmap_config max77675_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77675_MAX_REGISTER, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = max77675_volatile_reg, +}; + +static int max77675_apply_config(struct max77675_regulator *maxreg) +{ + const struct max77675_config *cfg = &maxreg->config; + int ret; + + /* Set EN pin mode */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_GLBL_A, + MAX77675_EN_MODE_MASK, + FIELD_PREP(MAX77675_EN_MODE_MASK, cfg->en_mode)); + if (ret) { + dev_err(maxreg->dev, "Failed to set EN mode: %d\n", ret); + return ret; + } + + /* Set the latency between output voltage change and SBBx voltage ramp start */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_SBB_TOP_B, + MAX77675_LAT_MODE_BIT, + FIELD_PREP(MAX77675_LAT_MODE_BIT, cfg->voltage_change_latency)); + if (ret) { + dev_err(maxreg->dev, "Failed to set latency mode: %d\n", ret); + return ret; + } + + /* Set drive strength */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_SBB_TOP_A, + MAX77675_DRV_SBB_MASK, + FIELD_PREP(MAX77675_DRV_SBB_MASK, cfg->drv_sbb_strength)); + if (ret) { + dev_err(maxreg->dev, "Failed to set drive strength: %d\n", ret); + return ret; + } + + /* Set DVS slew rate */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_SBB_TOP_B, + MAX77675_DVS_SLEW_BIT, + FIELD_PREP(MAX77675_DVS_SLEW_BIT, cfg->dvs_slew_rate)); + if (ret) { + dev_err(maxreg->dev, "Failed to set DVS slew rate: %d\n", ret); + return ret; + } + + /* Set debounce time for EN pin */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_GLBL_A, + MAX77675_DBEN_EN_BIT, + FIELD_PREP(MAX77675_DBEN_EN_BIT, cfg->debounce_time)); + if (ret) { + dev_err(maxreg->dev, "Failed to set EN debounce time: %d\n", ret); + return ret; + } + + /* Set manual reset time (MRT) for EN pin */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_GLBL_A, + MAX77675_MRT_MASK, + FIELD_PREP(MAX77675_MRT_MASK, cfg->manual_reset_time)); + if (ret) { + dev_err(maxreg->dev, "Failed to set manual reset time: %d\n", ret); + return ret; + } + + /* Enable or disable internal pull-up resistor on EN pin */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_GLBL_A, + MAX77675_PU_DIS_BIT, + FIELD_PREP(MAX77675_PU_DIS_BIT, cfg->en_pullup_disable)); + if (ret) { + dev_err(maxreg->dev, "Failed to set EN pull-up disable: %d\n", ret); + return ret; + } + + /* Request main bias to enter low-power mode */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_GLBL_A, + MAX77675_BIAS_LPM_BIT, + FIELD_PREP(MAX77675_BIAS_LPM_BIT, cfg->bias_low_power_request)); + if (ret) { + dev_err(maxreg->dev, "Failed to set bias low-power request: %d\n", ret); + return ret; + } + + /* Force SIMO internal LDO to always supply 1.8V */ + ret = regmap_update_bits(maxreg->regmap, MAX77675_REG_CNFG_GLBL_A, + MAX77675_SIMO_CH_DIS_BIT, + FIELD_PREP(MAX77675_SIMO_CH_DIS_BIT, cfg->simo_ldo_always_on)); + if (ret) { + dev_err(maxreg->dev, "Failed to set SIMO internal LDO always-on: %d\n", ret); + return ret; + } + + return 0; +} + +static int max77675_parse_en_mode(struct device *dev, + struct device_node *np, + u8 *en_mode) +{ + static const char * const en_modes[] = {"push-button", "slide-switch", "logic"}; + const char *str; + int index; + + *en_mode = MAX77675_EN_SLIDE_SWITCH; + + if (of_property_read_string(np, "adi,en-mode", &str)) + return 0; + + index = match_string(en_modes, ARRAY_SIZE(en_modes), str); + if (index < 0) { + dev_err(dev, "Invalid 'adi,en-mode' value '%s'\n", str); + return -EINVAL; + } + + *en_mode = index; + + return 0; +} + +static int max77675_parse_voltage_change_latency(struct device *dev, + struct device_node *np, + u8 *latency_mode) +{ + u32 val; + + *latency_mode = MAX77675_HIGH_LATENCY_MODE; + + if (!of_property_read_u32(np, "adi,voltage-change-latency-us", &val)) { + switch (val) { + case 10: + *latency_mode = MAX77675_LOW_LATENCY_MODE; + break; + case 100: + *latency_mode = MAX77675_HIGH_LATENCY_MODE; + break; + default: + dev_err(dev, "Invalid voltage-change-latency-us value: %u\n", val); + return -EINVAL; + } + } + + return 0; +} + +static int max77675_parse_manual_reset_time(struct device *dev, + struct device_node *np, + u8 *reset_time) +{ + u32 val; + + *reset_time = MAX77675_MRT_4S; + + if (!of_property_read_u32(np, "reset-time-sec", &val)) { + switch (val) { + case 4: + *reset_time = MAX77675_MRT_4S; + break; + case 8: + *reset_time = MAX77675_MRT_8S; + break; + case 12: + *reset_time = MAX77675_MRT_12S; + break; + case 16: + *reset_time = MAX77675_MRT_16S; + break; + default: + dev_err(dev, "Invalid reset-time-sec value: %u\n", val); + return -EINVAL; + } + } + + return 0; +} + +static int max77675_parse_dvs_slew_rate(struct device *dev, struct device_node *np, u8 *slew_rate) +{ + u32 val; + + /* Set default: 5 mV/us */ + *slew_rate = MAX77675_DVS_SLEW_5MV_PER_US; + + if (!of_property_read_u32(np, "adi,dvs-slew-rate-mv-per-us", &val)) { + switch (val) { + case 5: + *slew_rate = MAX77675_DVS_SLEW_5MV_PER_US; + break; + case 10: + *slew_rate = MAX77675_DVS_SLEW_10MV_PER_US; + break; + default: + dev_err(dev, "Invalid dvs-slew-rate-mv-per-us value: %u\n", val); + return -EINVAL; + } + } + + return 0; +} + +static int max77675_parse_drv_sbb_strength(struct device *dev, struct device_node *np, u8 *strength) +{ + static const char * const strength_names[] = {"max", "high", "low", "min"}; + const char *str; + int index; + + /* Set default: maximum drive strength */ + *strength = MAX77675_DRV_SBB_STRENGTH_MAX; + + if (of_property_read_string(np, "adi,drv-sbb-strength", &str)) + return 0; + + index = match_string(strength_names, ARRAY_SIZE(strength_names), str); + if (index < 0) { + dev_err(dev, "Invalid 'adi,drv-sbb-strength' value: '%s'\n", str); + return -EINVAL; + } + + *strength = index; + + return 0; +} + +static int max77675_parse_debounce_time_us(struct device *dev, + struct device_node *np, + u8 *debounce_time) +{ + u32 val; + + *debounce_time = MAX77675_DBEN_100US; + + if (!of_property_read_u32(np, "input-debounce", &val)) { + switch (val) { + case 100: + *debounce_time = MAX77675_DBEN_100US; + break; + case 30000: + *debounce_time = MAX77675_DBEN_30000US; + break; + default: + dev_err(dev, "Invalid input-debounce value: %u\n", val); + return -EINVAL; + } + } + + return 0; +} + +static int max77675_parse_config(struct max77675_regulator *maxreg) +{ + struct device_node *np = maxreg->dev->of_node; + struct max77675_config *cfg = &maxreg->config; + int ret; + + /* EN pin mode */ + ret = max77675_parse_en_mode(maxreg->dev, np, &cfg->en_mode); + if (ret < 0) + return ret; + + /* voltage change latency */ + ret = max77675_parse_voltage_change_latency(maxreg->dev, np, &cfg->voltage_change_latency); + if (ret < 0) + return ret; + + /* drive strength */ + ret = max77675_parse_drv_sbb_strength(maxreg->dev, np, &cfg->drv_sbb_strength); + if (ret < 0) + return ret; + + /* dvs slew rate */ + ret = max77675_parse_dvs_slew_rate(maxreg->dev, np, &cfg->dvs_slew_rate); + if (ret < 0) + return ret; + + /* Debounce time for EN pin */ + ret = max77675_parse_debounce_time_us(maxreg->dev, np, &cfg->debounce_time); + if (ret < 0) + return ret; + + /* Manual reset time for EN pin */ + ret = max77675_parse_manual_reset_time(maxreg->dev, np, &cfg->manual_reset_time); + if (ret < 0) + return ret; + + /* Disable internal pull-up resistor on EN pin */ + cfg->en_pullup_disable = of_property_read_bool(np, "bias-disable"); + + /* Request low-power mode for main bias */ + cfg->bias_low_power_request = of_property_read_bool(np, "adi,bias-low-power-request"); + + /* Force internal LDO to always supply 1.8V */ + cfg->simo_ldo_always_on = of_property_read_bool(np, "adi,simo-ldo-always-on"); + + return ret; +} + +static int max77675_init_event(struct max77675_regulator *maxreg) +{ + unsigned int ercflag, int_glbl; + int ret; + + ret = regmap_read(maxreg->regmap, MAX77675_REG_ERCF_GLBL, &ercflag); + if (ret) { + dev_err(maxreg->dev, "Failed to read CID register: %d\n", ret); + return ret; + } + + ret = regmap_read(maxreg->regmap, MAX77675_REG_INT_GLBL, &int_glbl); + if (ret) { + dev_err(maxreg->dev, "Failed to read INT_GLBL register: %d\n", ret); + return ret; + } + + if (ercflag & MAX77675_SFT_CRST_F_BIT) + dev_dbg(maxreg->dev, "Software Cold Reset Flag is set\n"); + + if (ercflag & MAX77675_SFT_OFF_F_BIT) + dev_dbg(maxreg->dev, "Software Off Flag is set\n"); + + if (ercflag & MAX77675_MRST_BIT) + dev_dbg(maxreg->dev, "Manual Reset Timer Flag is set\n"); + + if (ercflag & MAX77675_UVLO_BIT) + dev_dbg(maxreg->dev, "Undervoltage Lockout Flag is set\n"); + + if (ercflag & MAX77675_OVLO_BIT) + dev_dbg(maxreg->dev, "Overvoltage Lockout Flag is set\n"); + + if (ercflag & MAX77675_TOVLD_BIT) + dev_dbg(maxreg->dev, "Thermal Overload Flag is set\n"); + + if (int_glbl & MAX77675_INT_SBB3_F_BIT) + dev_dbg(maxreg->dev, "SBB3 Channel Fault Interrupt occurred\n"); + + if (int_glbl & MAX77675_INT_SBB2_F_BIT) + dev_dbg(maxreg->dev, "SBB2 Channel Fault Interrupt occurred\n"); + + if (int_glbl & MAX77675_INT_SBB1_F_BIT) + dev_dbg(maxreg->dev, "SBB1 Channel Fault Interrupt occurred\n"); + + if (int_glbl & MAX77675_INT_SBB0_F_BIT) + dev_dbg(maxreg->dev, "SBB0 Channel Fault Interrupt occurred\n"); + + if (int_glbl & MAX77675_INT_TJAL2_R_BIT) + dev_dbg(maxreg->dev, "Thermal Alarm 2 Rising Interrupt occurred\n"); + + if (int_glbl & MAX77675_INT_TJAL1_R_BIT) + dev_dbg(maxreg->dev, "Thermal Alarm 1 Rising Interrupt occurred\n"); + + if (int_glbl & MAX77675_INT_EN_R_BIT) + dev_dbg(maxreg->dev, "nEN Rising Edge Interrupt occurred\n"); + + if (int_glbl & MAX77675_INT_EN_F_BIT) + dev_dbg(maxreg->dev, "nEN Falling Edge Interrupt occurred\n"); + + return 0; +} + +static int max77675_regulator_probe(struct i2c_client *client) +{ + struct max77675_regulator *maxreg; + struct regulator_config config = {}; + int i, ret; + + maxreg = devm_kzalloc(&client->dev, sizeof(*maxreg), GFP_KERNEL); + if (!maxreg) + return -ENOMEM; + + maxreg->dev = &client->dev; + + maxreg->regmap = devm_regmap_init_i2c(client, &max77675_regmap_config); + if (IS_ERR(maxreg->regmap)) + return dev_err_probe(maxreg->dev, + PTR_ERR(maxreg->regmap), + "Failed to init regmap\n"); + + ret = max77675_init_event(maxreg); + if (ret < 0) + return dev_err_probe(maxreg->dev, ret, "Failed to init event\n"); + + ret = max77675_parse_config(maxreg); + if (ret < 0) + return dev_err_probe(maxreg->dev, ret, "Failed to parse config\n"); + + ret = max77675_apply_config(maxreg); + if (ret < 0) + return dev_err_probe(maxreg->dev, ret, "Failed to apply config\n"); + + config.dev = &client->dev; + config.regmap = maxreg->regmap; + config.driver_data = maxreg; + + struct device_node *regulators_np __free(device_node) = + of_get_child_by_name(client->dev.of_node, "regulators"); + if (!regulators_np) { + dev_err(maxreg->dev, "No 'regulators' subnode found in DT\n"); + return -EINVAL; + } + + for (i = 0; i < MAX77675_ID_NUM_MAX; i++) { + const struct regulator_desc *desc = &max77675_regulators[i]; + struct regulator_dev *rdev; + + struct device_node *child_np __free(device_node) = + of_get_child_by_name(regulators_np, desc->name); + if (!child_np) { + dev_warn(maxreg->dev, "No DT node for regulator %s\n", desc->name); + continue; + } + + config.of_node = child_np; + + rdev = devm_regulator_register(&client->dev, desc, &config); + if (IS_ERR(rdev)) { + return dev_err_probe(maxreg->dev, PTR_ERR(rdev), + "Failed to register regulator %d (%s)\n", + i, desc->name); + } + } + + return 0; +} + +static const struct i2c_device_id max77675_i2c_id[] = { + { "max77675", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77675_i2c_id); + +static const struct of_device_id __maybe_unused max77675_of_match[] = { + { .compatible = "adi,max77675", }, + { } +}; +MODULE_DEVICE_TABLE(of, max77675_of_match); + +static struct i2c_driver max77675_regulator_driver = { + .driver = { + .name = "max77675", + .of_match_table = of_match_ptr(max77675_of_match), + }, + .probe = max77675_regulator_probe, + .id_table = max77675_i2c_id, +}; + +module_i2c_driver(max77675_regulator_driver); + +MODULE_DESCRIPTION("MAX77675 Regulator Driver"); +MODULE_AUTHOR("Joan Na "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 03d281f384768610bf90697bce9e35d3d596de77 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:59 +0000 Subject: rust: regulator: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251202-define-rust-helper-v1-35-a2e13cbc17a6@google.com Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Signed-off-by: Mark Brown --- rust/helpers/regulator.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/rust/helpers/regulator.c b/rust/helpers/regulator.c index 11bc332443bd..9ec5237f449b 100644 --- a/rust/helpers/regulator.c +++ b/rust/helpers/regulator.c @@ -4,48 +4,52 @@ #ifndef CONFIG_REGULATOR -void rust_helper_regulator_put(struct regulator *regulator) +__rust_helper void rust_helper_regulator_put(struct regulator *regulator) { regulator_put(regulator); } -int rust_helper_regulator_set_voltage(struct regulator *regulator, int min_uV, - int max_uV) +__rust_helper int rust_helper_regulator_set_voltage(struct regulator *regulator, + int min_uV, int max_uV) { return regulator_set_voltage(regulator, min_uV, max_uV); } -int rust_helper_regulator_get_voltage(struct regulator *regulator) +__rust_helper int rust_helper_regulator_get_voltage(struct regulator *regulator) { return regulator_get_voltage(regulator); } -struct regulator *rust_helper_regulator_get(struct device *dev, const char *id) +__rust_helper struct regulator *rust_helper_regulator_get(struct device *dev, + const char *id) { return regulator_get(dev, id); } -int rust_helper_regulator_enable(struct regulator *regulator) +__rust_helper int rust_helper_regulator_enable(struct regulator *regulator) { return regulator_enable(regulator); } -int rust_helper_regulator_disable(struct regulator *regulator) +__rust_helper int rust_helper_regulator_disable(struct regulator *regulator) { return regulator_disable(regulator); } -int rust_helper_regulator_is_enabled(struct regulator *regulator) +__rust_helper int rust_helper_regulator_is_enabled(struct regulator *regulator) { return regulator_is_enabled(regulator); } -int rust_helper_devm_regulator_get_enable(struct device *dev, const char *id) +__rust_helper int rust_helper_devm_regulator_get_enable(struct device *dev, + const char *id) { return devm_regulator_get_enable(dev, id); } -int rust_helper_devm_regulator_get_enable_optional(struct device *dev, const char *id) +__rust_helper int +rust_helper_devm_regulator_get_enable_optional(struct device *dev, + const char *id) { return devm_regulator_get_enable_optional(dev, id); } -- cgit v1.2.3 From e5eb5638d632cf1180454acf16391ea9450e6295 Mon Sep 17 00:00:00 2001 From: ChiYuan Huang Date: Fri, 19 Dec 2025 14:36:19 +0800 Subject: regulator: dt-bindings: rt5739: Add compatible for rt8092 Append rt8092 compatible in rt5739 document. Compared to rt5739, RT8092 can offer up to 4A output current. Signed-off-by: ChiYuan Huang Link: https://patch.msgid.link/9b67b2d2b4268d356f41ae2d0c3202e7813ea6b1.1766125676.git.cy_huang@richtek.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/richtek,rt5739.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/regulator/richtek,rt5739.yaml b/Documentation/devicetree/bindings/regulator/richtek,rt5739.yaml index e95e046e9ed6..983f4c1ce380 100644 --- a/Documentation/devicetree/bindings/regulator/richtek,rt5739.yaml +++ b/Documentation/devicetree/bindings/regulator/richtek,rt5739.yaml @@ -15,6 +15,10 @@ description: | supply of 2.5V to 5.5V. It can provide up to 3.5A continuous current capability at over 80% high efficiency. + The RT8092 is similar type buck converter. Compared to RT5739, it can offer + up to 4A output current and more output voltage range to meet the application + on most mobile products. + allOf: - $ref: regulator.yaml# @@ -23,6 +27,7 @@ properties: enum: - richtek,rt5733 - richtek,rt5739 + - richtek,rt8092 reg: maxItems: 1 -- cgit v1.2.3 From 32a708ba5db50cf928a1f1b2039ceef33de2c286 Mon Sep 17 00:00:00 2001 From: ChiYuan Huang Date: Fri, 19 Dec 2025 14:36:20 +0800 Subject: regulator: Add rt8092 support RT8092 is a 3MHz 4A efficiency step-down converter with I2C control interface. It can support wide output range from 0.7 to 5.5V, based on the voltage bank selection. Signed-off-by: ChiYuan Huang Link: https://patch.msgid.link/d2293c513f396f86e54f9b806fd0195b57db7aae.1766125676.git.cy_huang@richtek.com Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 9 ++ drivers/regulator/Makefile | 1 + drivers/regulator/rt8092.c | 313 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 drivers/regulator/rt8092.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index d2335276cce5..ca68d5e3c13c 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1394,6 +1394,15 @@ config REGULATOR_RT6245 It can support up to 14A output current and adjustable output voltage from 0.4375V to 1.3875V, per step 12.5mV. +config REGULATOR_RT8092 + tristate "Richtek RT8092 voltage regulator" + depends on I2C + select REGMAP_I2C + help + The RT8092 is a peak-current mode PWM step-down DC/DC converter with + I2C control interface. It is capable of delivering 4A continuing + current over a wide input range from 2.5V to 5.5V. + config REGULATOR_RTQ2134 tristate "Richtek RTQ2134 SubPMIC Regulator" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 1beba1493241..5757a450d135 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -161,6 +161,7 @@ obj-$(CONFIG_REGULATOR_RT5759) += rt5759-regulator.o obj-$(CONFIG_REGULATOR_RT6160) += rt6160-regulator.o obj-$(CONFIG_REGULATOR_RT6190) += rt6190-regulator.o obj-$(CONFIG_REGULATOR_RT6245) += rt6245-regulator.o +obj-$(CONFIG_REGULATOR_RT8092) += rt8092.o obj-$(CONFIG_REGULATOR_RTMV20) += rtmv20-regulator.o obj-$(CONFIG_REGULATOR_RTQ2134) += rtq2134-regulator.o obj-$(CONFIG_REGULATOR_RTQ6752) += rtq6752-regulator.o diff --git a/drivers/regulator/rt8092.c b/drivers/regulator/rt8092.c new file mode 100644 index 000000000000..558bd04a2090 --- /dev/null +++ b/drivers/regulator/rt8092.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (c) 2025 Richtek Technology Corp. +// +// Author: ChiYuan Huang + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RT8092_REG_MNTRPT 0x00 +#define RT8092_REG_VOUTH 0x10 +#define RT8092_REG_VOUTL 0x11 +#define RT8092_REG_PWMMODE 0x14 +#define RT8092_REG_EVENT 0x18 +#define RT8092_REG_VBANKH 0x1C +#define RT8092_REG_VBANKL 0x1D +#define RT8092_REG_VBOUND 0x1E + +#define RT8092_TSDEVT_MASK BIT(7) +#define RT8092_PGEVT_MASK BIT(0) +#define RT8092_VSEL_MASK GENMASK(6, 0) +#define RT8092_VOUTEN_MASK BIT(7) +#define RT8092_FPWML_MASK BIT(7) +#define RT8092_FPWMH_MASK BIT(6) +#define RT8092_OCPEVT_MASK BIT(7) +#define RT8092_SCPEVT_MASK BIT(4) +#define RT8092_VINUVEVT_MASK BIT(1) +#define RT8092_VBANK_MASK GENMASK(1, 0) + +#define RT8092_MODE_AUTO 0 +#define RT8092_MODE_FPWM 1 +#define RT8092_VOUT_BASEUV 303125 +#define RT8092_VOUT_STEPUV 3125 +#define RT8092_VOUT_MINSEL 15 +#define RT8092_NUM_VOLTS 128 +#define RT8092_INITSS_US 400 + +static int rt8092_get_vbank_index(struct regmap *regmap, bool vsel_high, unsigned int *vbank_idx) +{ + unsigned int vbank_reg = vsel_high ? RT8092_REG_VBANKH : RT8092_REG_VBANKL; + unsigned int index; + int ret; + + ret = regmap_read(regmap, vbank_reg, &index); + if (ret) + return ret; + + *vbank_idx = FIELD_GET(RT8092_VBANK_MASK, index); + return 0; +} + +static int rt8092_set_operating_mode(struct regulator_dev *rdev, unsigned int mode) +{ + const struct regulator_desc *desc = rdev->desc; + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int mode_mask, mode_val; + + mode_mask = desc->vsel_reg == RT8092_REG_VOUTH ? RT8092_FPWMH_MASK : RT8092_FPWML_MASK; + + switch (mode) { + case REGULATOR_MODE_FAST: + mode_val = mode_mask; + break; + case REGULATOR_MODE_NORMAL: + mode_val = 0; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(regmap, RT8092_REG_PWMMODE, mode_mask, mode_val); +} + +static unsigned int rt8092_get_operating_mode(struct regulator_dev *rdev) +{ + const struct regulator_desc *desc = rdev->desc; + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int mode_mask, mode_val; + int ret; + + mode_mask = desc->vsel_reg == RT8092_REG_VOUTH ? RT8092_FPWMH_MASK : RT8092_FPWML_MASK; + + ret = regmap_read(regmap, RT8092_REG_PWMMODE, &mode_val); + if (ret) + return REGULATOR_MODE_INVALID; + + return mode_val & mode_mask ? REGULATOR_MODE_FAST : REGULATOR_MODE_NORMAL; +} + +static int rt8092_get_error_flags(struct regulator_dev *rdev, unsigned int *flags) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int mntrpt, evtrpt, events = 0; + int ret; + + ret = regmap_read(regmap, RT8092_REG_MNTRPT, &mntrpt); + if (ret) + return ret; + + ret = regmap_read(regmap, RT8092_REG_EVENT, &evtrpt); + if (ret) + return ret; + + if (!(mntrpt & RT8092_PGEVT_MASK) || evtrpt & RT8092_VINUVEVT_MASK) + events |= REGULATOR_ERROR_UNDER_VOLTAGE; + + if (mntrpt & RT8092_TSDEVT_MASK) + events |= REGULATOR_ERROR_OVER_TEMP; + + if (evtrpt & RT8092_OCPEVT_MASK) + events |= REGULATOR_ERROR_OVER_CURRENT; + + if (evtrpt & RT8092_SCPEVT_MASK) + events |= REGULATOR_ERROR_FAIL; + + *flags = events; + return 0; +} + + +static int rt8092_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + const struct regulator_desc *desc = rdev->desc; + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int vsel_reg, vsel_val, vbank_idx; + bool vsel_high; + int ret; + + vsel_reg = desc->vsel_reg == RT8092_REG_VOUTH ? RT8092_REG_VOUTL : RT8092_REG_VOUTH; + vsel_high = desc->vsel_reg == RT8092_REG_VOUTH; + + ret = rt8092_get_vbank_index(regmap, vsel_high, &vbank_idx); + if (ret) + return ret; + + /* VOUT = (BASEUV + STEPUV * VSEL) * 2^vbank_idx */ + uV >>= vbank_idx; + if (uV < RT8092_VOUT_BASEUV) + return -EINVAL; + + vsel_val = (uV - RT8092_VOUT_BASEUV) / RT8092_VOUT_STEPUV; + if (vsel_val < RT8092_VOUT_MINSEL || vsel_val >= RT8092_NUM_VOLTS) + return -EINVAL; + + return regmap_update_bits(regmap, vsel_reg, RT8092_VSEL_MASK, vsel_val); +} + +static int rt8092_set_suspend_enable(struct regulator_dev *rdev) +{ + const struct regulator_desc *desc = rdev->desc; + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int enable_reg; + + enable_reg = desc->vsel_reg == RT8092_REG_VOUTH ? RT8092_REG_VOUTL : RT8092_REG_VOUTH; + return regmap_set_bits(regmap, enable_reg, RT8092_VOUTEN_MASK); +} + +static int rt8092_set_suspend_disable(struct regulator_dev *rdev) +{ + const struct regulator_desc *desc = rdev->desc; + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int enable_reg; + + enable_reg = desc->vsel_reg == RT8092_REG_VOUTH ? RT8092_REG_VOUTL : RT8092_REG_VOUTH; + return regmap_clear_bits(regmap, enable_reg, RT8092_VOUTEN_MASK); +} + +static int rt8092_set_suspend_mode(struct regulator_dev *rdev, unsigned int mode) +{ + const struct regulator_desc *desc = rdev->desc; + struct regmap *regmap = rdev_get_regmap(rdev); + unsigned int mode_mask, mode_val; + + mode_mask = desc->vsel_reg == RT8092_REG_VOUTH ? RT8092_FPWML_MASK : RT8092_FPWMH_MASK; + + switch (mode) { + case REGULATOR_MODE_FAST: + mode_val = mode_mask; + break; + case REGULATOR_MODE_NORMAL: + mode_val = 0; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(regmap, RT8092_REG_PWMMODE, mode_mask, mode_val); +} + +static const struct regulator_ops rt8092_regulator_ops = { + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_mode = rt8092_set_operating_mode, + .get_mode = rt8092_get_operating_mode, + .get_error_flags = rt8092_get_error_flags, + .set_suspend_voltage = rt8092_set_suspend_voltage, + .set_suspend_enable = rt8092_set_suspend_enable, + .set_suspend_disable = rt8092_set_suspend_disable, + .set_suspend_mode = rt8092_set_suspend_mode, +}; + +static unsigned int rt8092_of_map_mode(unsigned int mode) +{ + switch (mode) { + case RT8092_MODE_AUTO: + return REGULATOR_MODE_NORMAL; + case RT8092_MODE_FPWM: + return REGULATOR_MODE_FAST; + default: + return REGULATOR_MODE_INVALID; + } +} + +static const struct regmap_config rt8092_regmap_cfg = { + .name = "rt8092", + .reg_bits = 8, + .val_bits = 8, + .max_register = RT8092_REG_VBOUND, +}; + +static int rt8092_probe(struct i2c_client *i2c) +{ + unsigned int vbank_idx, min_uV, step_uV; + struct regulator_config cfg = {}; + struct device *dev = &i2c->dev; + struct regulator_desc *desc; + struct regulator_dev *rdev; + struct gpio_desc *enable; + struct regmap *regmap; + bool vsel_high; + int ret; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(enable)) + return dev_err_probe(dev, PTR_ERR(enable), "Failed get 'enable' gpio\n"); + + regmap = devm_regmap_init_i2c(i2c, &rt8092_regmap_cfg); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n"); + + vsel_high = device_property_read_bool(dev, "richtek,vsel-active-high"); + + ret = rt8092_get_vbank_index(regmap, vsel_high, &vbank_idx); + if (ret) + return dev_err_probe(dev, ret, "Failed to get VOUT bank index\n"); + + /* + * step VOUT = STEP_UV * 2^vbank_idx + * min VOUT = (BASEUV + STEPUV * VMIN_SEL) * 2^vbank_idx + */ + step_uV = RT8092_VOUT_STEPUV << vbank_idx; + min_uV = (RT8092_VOUT_BASEUV + RT8092_VOUT_STEPUV * RT8092_VOUT_MINSEL) << vbank_idx; + + desc->name = "rt8092"; + desc->owner = THIS_MODULE; + desc->type = REGULATOR_VOLTAGE; + desc->ops = &rt8092_regulator_ops; + desc->n_voltages = RT8092_NUM_VOLTS; + desc->min_uV = min_uV; + desc->uV_step = step_uV; + desc->linear_min_sel = RT8092_VOUT_MINSEL; + desc->enable_reg = desc->vsel_reg = vsel_high ? RT8092_REG_VOUTH : RT8092_REG_VOUTL; + desc->vsel_mask = RT8092_VSEL_MASK; + desc->enable_mask = RT8092_VOUTEN_MASK; + desc->enable_time = RT8092_INITSS_US; + desc->of_map_mode = rt8092_of_map_mode; + + cfg.dev = dev; + cfg.of_node = dev_of_node(dev); + cfg.init_data = of_get_regulator_init_data(dev, dev_of_node(dev), desc); + + rdev = devm_regulator_register(dev, desc, &cfg); + if (IS_ERR(rdev)) + return dev_err_probe(dev, PTR_ERR(rdev), "Failed to register regulator\n"); + + return 0; +} + +static const struct of_device_id rt8092_device_tables[] = { + { .compatible = "richtek,rt8092" }, + {} +}; +MODULE_DEVICE_TABLE(of, rt8092_device_tables); + +static struct i2c_driver rt8092_driver = { + .driver = { + .name = "rt8092", + .of_match_table = rt8092_device_tables, + }, + .probe = rt8092_probe, +}; +module_i2c_driver(rt8092_driver); + +MODULE_AUTHOR("ChiYuan Huang "); +MODULE_DESCRIPTION("Richtek RT8092 Regulator Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From b0655377aa5a410df02d89170c20141a1a5bbc28 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:15:58 +0100 Subject: rust: regulator: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-regulator-v1-1-430e3d517025@gmail.com Signed-off-by: Mark Brown --- rust/kernel/regulator.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/rust/kernel/regulator.rs b/rust/kernel/regulator.rs index 2c44827ad0b7..4f7837c7e53a 100644 --- a/rust/kernel/regulator.rs +++ b/rust/kernel/regulator.rs @@ -122,12 +122,11 @@ pub fn devm_enable_optional(dev: &Device, name: &CStr) -> Result { /// /// ``` /// # use kernel::prelude::*; -/// # use kernel::c_str; /// # use kernel::device::Device; /// # use kernel::regulator::{Voltage, Regulator, Disabled, Enabled}; /// fn enable(dev: &Device, min_voltage: Voltage, max_voltage: Voltage) -> Result { /// // Obtain a reference to a (fictitious) regulator. -/// let regulator: Regulator = Regulator::::get(dev, c_str!("vcc"))?; +/// let regulator: Regulator = Regulator::::get(dev, c"vcc")?; /// /// // The voltage can be set before enabling the regulator if needed, e.g.: /// regulator.set_voltage(min_voltage, max_voltage)?; @@ -166,12 +165,11 @@ pub fn devm_enable_optional(dev: &Device, name: &CStr) -> Result { /// /// ``` /// # use kernel::prelude::*; -/// # use kernel::c_str; /// # use kernel::device::Device; /// # use kernel::regulator::{Voltage, Regulator, Enabled}; /// fn enable(dev: &Device) -> Result { /// // Obtain a reference to a (fictitious) regulator and enable it. -/// let regulator: Regulator = Regulator::::get(dev, c_str!("vcc"))?; +/// let regulator: Regulator = Regulator::::get(dev, c"vcc")?; /// /// // Dropping an enabled regulator will disable it. The refcount will be /// // decremented. @@ -193,13 +191,12 @@ pub fn devm_enable_optional(dev: &Device, name: &CStr) -> Result { /// /// ``` /// # use kernel::prelude::*; -/// # use kernel::c_str; /// # use kernel::device::{Bound, Device}; /// # use kernel::regulator; /// fn enable(dev: &Device) -> Result { /// // Obtain a reference to a (fictitious) regulator and enable it. This /// // call only returns whether the operation succeeded. -/// regulator::devm_enable(dev, c_str!("vcc"))?; +/// regulator::devm_enable(dev, c"vcc")?; /// /// // The regulator will be disabled and put when `dev` is unbound. /// Ok(()) -- cgit v1.2.3 From 96e7a88d32de2554a5d7e54e87eb03d445dd6924 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 Jan 2026 08:38:37 +0000 Subject: regulator: core: update two debug messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) In print_constraints_debug(), the power budget is printed as: lldo2: 450 <--> 1300 mV at 900 mV 2147483647 mW budge, enabled (note the missing t in budget). This is because there is a --count just below the call to scnprintf(), to make space for the comma. All similar calls to scnprintf() above add an extra space to the format string to allow for that, but this one doesn't, so the last character t is stripped instead. Update the format string to fix the message. 2) Add the name of the supply to the failure message printed when the supply can not be resolved when debug messages are enabled to help with debug. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-1-1a25dc968e60@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 4b6182cde859..a723bd00e171 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1183,7 +1183,7 @@ static void print_constraints_debug(struct regulator_dev *rdev) count += scnprintf(buf + count, len - count, "standby "); if (constraints->pw_budget_mW) - count += scnprintf(buf + count, len - count, "%d mW budget", + count += scnprintf(buf + count, len - count, "%d mW budget ", constraints->pw_budget_mW); if (!count) @@ -5697,7 +5697,8 @@ static int regulator_register_resolve_supply(struct device *dev, void *data) struct regulator_dev *rdev = dev_to_rdev(dev); if (regulator_resolve_supply(rdev)) - rdev_dbg(rdev, "unable to resolve supply\n"); + rdev_dbg(rdev, "unable to resolve supply '%s'\n", + rdev->supply_name); return 0; } -- cgit v1.2.3 From 497330b203d2c59c5ff3fa4c34d14494d7203bc3 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 Jan 2026 08:38:38 +0000 Subject: regulator: core: fix locking in regulator_resolve_supply() error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If late enabling of a supply regulator fails in regulator_resolve_supply(), the code currently triggers a lockdep warning: WARNING: drivers/regulator/core.c:2649 at _regulator_put+0x80/0xa0, CPU#6: kworker/u32:4/596 ... Call trace: _regulator_put+0x80/0xa0 (P) regulator_resolve_supply+0x7cc/0xbe0 regulator_register_resolve_supply+0x28/0xb8 as the regulator_list_mutex must be held when calling _regulator_put(). To solve this, simply switch to using regulator_put(). While at it, we should also make sure that no concurrent access happens to our rdev while we clear out the supply pointer. Add appropriate locking to ensure that. While the code in question will be removed altogether in a follow-up commit, I believe it is still beneficial to have this corrected before removal for future reference. Fixes: 36a1f1b6ddc6 ("regulator: core: Fix memory leak in regulator_resolve_supply()") Fixes: 8e5356a73604 ("regulator: core: Clear the supply pointer if enabling fails") Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-2-1a25dc968e60@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/core.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index a723bd00e171..48c091de68d8 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -2285,8 +2285,16 @@ static int regulator_resolve_supply(struct regulator_dev *rdev) if (rdev->use_count) { ret = regulator_enable(rdev->supply); if (ret < 0) { - _regulator_put(rdev->supply); + struct regulator *supply; + + regulator_lock_two(rdev, rdev->supply->rdev, &ww_ctx); + + supply = rdev->supply; rdev->supply = NULL; + + regulator_unlock_two(rdev, supply->rdev, &ww_ctx); + + regulator_put(supply); goto out; } } -- cgit v1.2.3 From 86a8eeb0e913f4b6a55dabba5122098d4e805e55 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 Jan 2026 08:38:39 +0000 Subject: regulator: core: move supply check earlier in set_machine_constraints() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 98e48cd9283d ("regulator: core: resolve supply for boot-on/always-on regulators"), set_machine_constraints() can return -EPROBE_DEFER very late, after it has done a lot of work and configuration of the regulator. This means that configuration will happen multiple times for no benefit in that case. Furthermore, this can lead to timing-dependent voltage glitches as mentioned e.g. in commit 8a866d527ac0 ("regulator: core: Resolve supply name earlier to prevent double-init"). We can know that it's going to fail very early, in particular before going through the complete regulator configuration by moving some code around a little. Do so to avoid re-configuring the regulator multiple times, also avoiding the voltage glitches if we can. Fixes: 98e48cd9283d ("regulator: core: resolve supply for boot-on/always-on regulators") Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-3-1a25dc968e60@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/core.c | 55 ++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 48c091de68d8..9ce0eef1dcfc 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1444,6 +1444,33 @@ static int set_machine_constraints(struct regulator_dev *rdev) int ret = 0; const struct regulator_ops *ops = rdev->desc->ops; + /* + * If there is no mechanism for controlling the regulator then + * flag it as always_on so we don't end up duplicating checks + * for this so much. Note that we could control the state of + * a supply to control the output on a regulator that has no + * direct control. + */ + if (!rdev->ena_pin && !ops->enable) { + if (rdev->supply_name && !rdev->supply) + return -EPROBE_DEFER; + + if (rdev->supply) + rdev->constraints->always_on = + rdev->supply->rdev->constraints->always_on; + else + rdev->constraints->always_on = true; + } + + /* + * If we want to enable this regulator, make sure that we know the + * supplying regulator. + */ + if (rdev->constraints->always_on || rdev->constraints->boot_on) { + if (rdev->supply_name && !rdev->supply) + return -EPROBE_DEFER; + } + ret = machine_constraints_voltage(rdev, rdev->constraints); if (ret != 0) return ret; @@ -1609,37 +1636,15 @@ static int set_machine_constraints(struct regulator_dev *rdev) } } - /* - * If there is no mechanism for controlling the regulator then - * flag it as always_on so we don't end up duplicating checks - * for this so much. Note that we could control the state of - * a supply to control the output on a regulator that has no - * direct control. - */ - if (!rdev->ena_pin && !ops->enable) { - if (rdev->supply_name && !rdev->supply) - return -EPROBE_DEFER; - - if (rdev->supply) - rdev->constraints->always_on = - rdev->supply->rdev->constraints->always_on; - else - rdev->constraints->always_on = true; - } - /* If the constraints say the regulator should be on at this point * and we have control then make sure it is enabled. */ if (rdev->constraints->always_on || rdev->constraints->boot_on) { bool supply_enabled = false; - /* If we want to enable this regulator, make sure that we know - * the supplying regulator. - */ - if (rdev->supply_name && !rdev->supply) - return -EPROBE_DEFER; - - /* If supplying regulator has already been enabled, + /* We have ensured a potential supply has been resolved above. + * + * If supplying regulator has already been enabled, * it's not intended to have use_count increment * when rdev is only boot-on. */ -- cgit v1.2.3 From 4f3323b752bfcc185c98ce4fb841cca8b700a7c0 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 Jan 2026 08:38:40 +0000 Subject: regulator: core: streamline supply resolution for always-on/boot-on regulators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For always-on and boot-on regulators, regulator_register() is currently trying to anticipate the requirement to resolve a supply early. Unfortunately, this code executes too early, before we have potentially updated the regulator's always_on constraint as part of set_machine_constraints(), causing it to miss cases. Rather than trying to hack it more, just defer to the outcome of set_machine_constraints(). The latter returns early (without doing any regulator initialisation) with -EPROBE_DEFER as of commit 'regulator: core: move supply check earlier in set_machine_constraints()' and is therefore safe to call multiple times to determine if supplies need to be resolved early. Commit 8a866d527ac0 ("regulator: core: Resolve supply name earlier to prevent double-init") (later updated by commit 520fb178212d ("regulator: core: Fix regulator supply registration with sysfs")) added these tests originally to avoid calling set_machine_constraints() multiple times to try to avoid voltage glitches due to all the regulator initialisation happening each time. This isn't an issue anymore as per above. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-4-1a25dc968e60@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/core.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 9ce0eef1dcfc..08bdb1e4175e 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -5937,7 +5937,6 @@ regulator_register(struct device *dev, bool dangling_cfg_gpiod = false; bool dangling_of_gpiod = false; int ret, i; - bool resolved_early = false; if (cfg == NULL) return ERR_PTR(-EINVAL); @@ -6075,17 +6074,6 @@ regulator_register(struct device *dev, goto wash; } - if ((rdev->supply_name && !rdev->supply) && - (rdev->constraints->always_on || - rdev->constraints->boot_on)) { - ret = regulator_resolve_supply(rdev); - if (ret) - rdev_dbg(rdev, "unable to resolve supply early: %pe\n", - ERR_PTR(ret)); - - resolved_early = true; - } - if (config->ena_gpiod) { ret = regulator_ena_gpio_request(rdev, config); if (ret != 0) { @@ -6099,9 +6087,10 @@ regulator_register(struct device *dev, } ret = set_machine_constraints(rdev); - if (ret == -EPROBE_DEFER && !resolved_early) { - /* Regulator might be in bypass mode and so needs its supply - * to set the constraints + if (ret == -EPROBE_DEFER) { + /* Regulator might be in bypass mode or an always-on or boot-on + * regulator and so needs its supply to set the constraints or + * for enable. */ /* FIXME: this currently triggers a chicken-and-egg problem * when creating -SUPPLY symlink in sysfs to a regulator -- cgit v1.2.3 From bdbdc4b398254597c9b6ac279f336750996bc0a6 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 Jan 2026 08:38:41 +0000 Subject: regulator: core: remove dead code in regulator_resolve_supply() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 98e48cd9283d ("regulator: core: resolve supply for boot-on/always-on regulators") we require that a regulator's supply has been resolved before enabling the regulator. Furthermore, regulator_get() also fails if the supply hasn't been resolved yet (preventing consumers from enabling a regulator without its supply known). In combination this means that regulator_resolve_supply() now always runs before the regulator has been enabled via set_machine_constraints(). The code here was meant to run after enabling the regulator in case the supply hadn't been resolved at that time and can therefore never execute anymore since that commit. Remove it. No functional change intended. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-5-1a25dc968e60@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/core.c | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 08bdb1e4175e..fd8da369c052 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -2282,28 +2282,6 @@ static int regulator_resolve_supply(struct regulator_dev *rdev) /* rdev->supply was created in set_supply() */ link_and_create_debugfs(rdev->supply, r, &rdev->dev); - /* - * In set_machine_constraints() we may have turned this regulator on - * but we couldn't propagate to the supply if it hadn't been resolved - * yet. Do it now. - */ - if (rdev->use_count) { - ret = regulator_enable(rdev->supply); - if (ret < 0) { - struct regulator *supply; - - regulator_lock_two(rdev, rdev->supply->rdev, &ww_ctx); - - supply = rdev->supply; - rdev->supply = NULL; - - regulator_unlock_two(rdev, supply->rdev, &ww_ctx); - - regulator_put(supply); - goto out; - } - } - out: return ret; } -- cgit v1.2.3 From e23c0a59dabae9166bbea26fc05d08e7d9e900b7 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 Jan 2026 08:38:42 +0000 Subject: regulator: core: don't ignore errors from event forwarding setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Receiving and forwarding critical supply events seems like they're important information and we shouldn't ignore errors occurring during registration for such events. With this change the supply is unset on event registration failure, allowing us to potentially retry another time. Fixes: 433e294c3c5b ("regulator: core: forward undervoltage events downstream by default") Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-6-1a25dc968e60@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/core.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index fd8da369c052..86dbee3ffda0 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -2273,10 +2273,21 @@ static int regulator_resolve_supply(struct regulator_dev *rdev) * under-voltage. */ ret = register_regulator_event_forwarding(rdev); - if (ret < 0) + if (ret < 0) { + struct regulator *supply; + rdev_warn(rdev, "Failed to register event forwarding: %pe\n", ERR_PTR(ret)); + supply = rdev->supply; + rdev->supply = NULL; + + regulator_unlock_two(rdev, supply->rdev, &ww_ctx); + + regulator_put(supply); + goto out; + } + regulator_unlock_two(rdev, r, &ww_ctx); /* rdev->supply was created in set_supply() */ -- cgit v1.2.3 From 304f5784e97281f18ef4bed574cbe5fcf6ce2f2e Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 Jan 2026 08:38:43 +0000 Subject: regulator: core: reresolve unresolved supplies when available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a regulator A and its supply B are provided by different devices, the driver implementing B might be last to probe (with A still pending resolution of its supply B). While we try to resolve all pending supplies for all regulators (including A) during regulator_register() of B via regulator_register_resolve_supply(), supply resolution will still not work for A as the driver for B hasn't finished binding to the PMIC device corresponding to B at that stage yet. The regulator core explicitly only allows supplies from other devices to be used once the relevant driver has fully bound, mainly to avoid having to deal with cases where B itself might -EPROBE_DEFER. In this case, A's supply will only be resolved as part of the core's regulator_init_complete_work_function(), which currently is scheduled to run after 30s. This was added as a work-around in commit 3827b64dba27 ("regulator: core: Resolve supplies before disabling unused regulators") to cover this situation. There are two problems with that approach: * it potentially runs long after all our consumers have probed * an upcoming change will allow regulator_register() to complete successfully even when required supplies (e.g. due to always-on or boot-on) are missing at register time, deferring full configuration of the regulator (and usability by consumers, i.e. usually consumer probe) until the supply becomes available. Resolving supplies in the late work func can therefore make it impossible for consumers to probe at all, as the driver core will not know to reprobe consumers when supplies have resolved. We could schedule an earlier work to try to resolve supplies sooner, but that'd be racy as consumers of A might try to probe before A's supply gets fully resolved via this extra work. Instead, add a very simple regulator bus and add a dummy device with a corresponding driver to it for each regulator that is missing its supply during regulator_register(). This way, the driver core will call our bus' probe whenever a new (regulator) device was successfully bound, allowing us to retry resolving the supply during (our bus) probe and to bind this dummy device if successful. In turn this means the driver core will see a newly bound device and retry probing of all pending consumers, if any. With that in place, we can avoid walking the full list of all known regulators to try resolve missing supplies during regulator_register(), as the driver core will invoke the bus probe for regulators that are still pending their supplies. We can also drop the code trying to resolve supplies one last time before unused regulators get disabled, as all supplies should have resolved at that point in time, and if they haven't then there's no point in trying again, as the outcome won't change. Note: We can not reuse the existing struct device created for each rail, as a device can not be part of a class and a bus simultaneously. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-7-1a25dc968e60@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/core.c | 121 +++++++++++++++++++++++++++++++-------- include/linux/regulator/driver.h | 1 + 2 files changed, 98 insertions(+), 24 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 86dbee3ffda0..08e92b1ba2dc 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -44,6 +44,8 @@ static LIST_HEAD(regulator_supply_alias_list); static LIST_HEAD(regulator_coupler_list); static bool has_full_constraints; +static const struct bus_type regulator_bus; + static struct dentry *debugfs_root; /* @@ -5694,17 +5696,6 @@ static void rdev_init_debugfs(struct regulator_dev *rdev) &rdev->bypass_count); } -static int regulator_register_resolve_supply(struct device *dev, void *data) -{ - struct regulator_dev *rdev = dev_to_rdev(dev); - - if (regulator_resolve_supply(rdev)) - rdev_dbg(rdev, "unable to resolve supply '%s'\n", - rdev->supply_name); - - return 0; -} - int regulator_coupler_register(struct regulator_coupler *coupler) { mutex_lock(®ulator_list_mutex); @@ -5923,6 +5914,7 @@ regulator_register(struct device *dev, struct regulator_config *config = NULL; static atomic_t regulator_no = ATOMIC_INIT(-1); struct regulator_dev *rdev; + bool tried_supply_resolve = false; bool dangling_cfg_gpiod = false; bool dangling_of_gpiod = false; int ret, i; @@ -6093,6 +6085,7 @@ regulator_register(struct device *dev, else rdev_dbg(rdev, "unable to resolve supply early: %pe\n", ERR_PTR(ret)); + tried_supply_resolve = true; } if (ret < 0) goto wash; @@ -6124,6 +6117,37 @@ regulator_register(struct device *dev, if (ret != 0) goto unset_supplies; + if (!tried_supply_resolve) { + /* + * As an optimisation, try to resolve our supply (if any) now to + * avoid adding the bus device. Errors are not fatal at this + * stage, we'll simply try again later. + */ + ret = regulator_resolve_supply(rdev); + if (ret) + rdev_dbg(rdev, + "unable to resolve supply (ignoring): %pe\n", + ERR_PTR(ret)); + } + + /* + * If we have a supply but couldn't resolve it yet, register a device + * with our bus, so that the bus probe gets called whenever any new + * driver binds, allowing us to retry matching supplies and which then + * triggers (re)probe of consumers if successful. + */ + if (rdev->supply_name && !rdev->supply) { + device_initialize(&rdev->bdev); + rdev->bdev.bus = ®ulator_bus; + rdev->bdev.parent = &rdev->dev; + device_set_pm_not_required(&rdev->dev); + dev_set_name(&rdev->bdev, "%s.bdev", dev_name(&rdev->dev)); + + ret = device_add(&rdev->bdev); + if (ret) + goto del_cdev_and_bdev; + } + rdev_init_debugfs(rdev); /* try to resolve regulators coupling since a new one was registered */ @@ -6131,12 +6155,13 @@ regulator_register(struct device *dev, regulator_resolve_coupling(rdev); mutex_unlock(®ulator_list_mutex); - /* try to resolve regulators supply since a new one was registered */ - class_for_each_device(®ulator_class, NULL, NULL, - regulator_register_resolve_supply); kfree(config); return rdev; +del_cdev_and_bdev: + if (rdev->bdev.bus == ®ulator_bus) + put_device(&rdev->bdev); + device_del(&rdev->dev); unset_supplies: mutex_lock(®ulator_list_mutex); unset_regulator_supplies(rdev); @@ -6189,6 +6214,9 @@ void regulator_unregister(struct regulator_dev *rdev) unset_regulator_supplies(rdev); list_del(&rdev->list); regulator_ena_gpio_free(rdev); + if (rdev->bdev.bus == ®ulator_bus) + /* only if the device was added in the first place */ + device_unregister(&rdev->bdev); device_unregister(&rdev->dev); mutex_unlock(®ulator_list_mutex); @@ -6269,6 +6297,45 @@ const struct class regulator_class = { .pm = ®ulator_pm_ops, #endif }; + +#define bdev_to_rdev(__bdev) container_of_const(__bdev, struct regulator_dev, bdev) + +static int regulator_bus_match(struct device *bdev, + const struct device_driver *drv) +{ + /* Match always succeeds, we only have one driver */ + return 1; +} + +static int regulator_bus_probe(struct device *bdev) +{ + struct regulator_dev *rdev = bdev_to_rdev(bdev); + int ret; + + ret = regulator_resolve_supply(rdev); + if (ret) + rdev_dbg(rdev, + "unable to resolve supply or constraints '%s': %pe\n", + rdev->supply_name, ERR_PTR(ret)); + else + rdev_dbg(rdev, "resolved supply '%s'\n", rdev->supply_name); + + return ret; +} + +static const struct bus_type regulator_bus = { + .name = "regulator", + .match = regulator_bus_match, + .probe = regulator_bus_probe, +}; + +static struct device_driver regulator_bus_driver = { + .name = "regulator-bus-drv", + .bus = ®ulator_bus, + .suppress_bind_attrs = true, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, +}; + /** * regulator_has_full_constraints - the system has fully specified constraints * @@ -6602,7 +6669,17 @@ static int __init regulator_init(void) { int ret; + ret = bus_register(®ulator_bus); + if (ret) + return ret; + ret = class_register(®ulator_class); + if (ret) + goto err_class; + + ret = driver_register(®ulator_bus_driver); + if (ret) + goto err_driver; debugfs_root = debugfs_create_dir("regulator", NULL); if (IS_ERR(debugfs_root)) @@ -6619,6 +6696,12 @@ static int __init regulator_init(void) regulator_coupler_register(&generic_regulator_coupler); + return 0; + +err_driver: + class_unregister(®ulator_class); +err_class: + bus_unregister(®ulator_bus); return ret; } @@ -6679,16 +6762,6 @@ __setup("regulator_ignore_unused", regulator_ignore_unused_setup); static void regulator_init_complete_work_function(struct work_struct *work) { - /* - * Regulators may had failed to resolve their input supplies - * when were registered, either because the input supply was - * not registered yet or because its parent device was not - * bound yet. So attempt to resolve the input supplies for - * pending regulators before trying to disable unused ones. - */ - class_for_each_device(®ulator_class, NULL, NULL, - regulator_register_resolve_supply); - /* * For debugging purposes, it may be useful to prevent unused * regulators from being disabled. diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index 978cf593b662..d38353f2b56f 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -635,6 +635,7 @@ struct regulator_dev { int ref_cnt; struct module *owner; struct device dev; + struct device bdev; struct regulation_constraints *constraints; struct regulator *supply; /* for tree */ const char *supply_name; -- cgit v1.2.3 From 8d38423d9dea7353a8a54a3ab2e0d0aa04ed34d0 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 Jan 2026 08:38:44 +0000 Subject: regulator: core: don't fail regulator_register() with missing required supply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 98e48cd9283d ("regulator: core: resolve supply for boot-on/always-on regulators"), the regulator core returns -EPROBE_DEFER if a supply can not be resolved at regulator_register() time due to set_machine_constraints() requiring that supply (e.g. because of always-on or boot-on). In some hardware designs, multiple PMICs are used where individual rails of each act as supplies for rails of the other, and vice-versa. In such a design no PMIC driver can probe when registering one top- level regulator device (as is common practice for almost all regulator drivers in Linux) since that commit. Supplies are only considered when their driver has fully bound, but because in a design like the above two drivers / devices depend on each other, neither will have fully bound while the other probes. The Google Pixel 6 and 6 Pro (oriole and raven) are examples of such a design. One way to make this work would be to register each rail as an individual device, rather than just one top-level regulator device. Then, fw-devlink and Linux' driver core could do their usual handling of deferred device probe as each rail would be probed individually. This approach was dismissed in [1] as each regulator driver would have to take care of this itself. Alternatively, we can change the regulator core to not fail regulator_register() if a rail's required supply can not be resolved while keeping the intended change from above mentioned commit, and instead retry whenever a new rail is registered. This commit implements such an approach: If set_machine_constraints() requests probe deferral, regulator_register() still succeeds and we retry setting constraints as part of regulator_resolve_supply(). We still do not enable the regulator or allow consumers to use it until constraints have been set (including resolution of the supply) to prevent enabling of a regulator before its supply. With this change, we keep track of regulators with missing required supplies and can therefore try to resolve them again and try to set the constraints again once more regulators become available. Care has to be taken to not allow consumers to use regulators that haven't had their constraints set yet. regulator_get() ensures that and now returns -EPROBE_DEFER in that case. The implementation is straight-forward, thanks to our newly introduced regulator-bus. Locking in regulator_resolve_supply() has to be done carefully, as a combination of regulator_(un)lock_two() and regulator_(un)lock_dependent() is needed. The reason is that set_machine_constraints() might call regulator_enable() which needs rdev and all its dependents locked, but everything else requires to only have rdev and its supply locked. Link: https://lore.kernel.org/all/aRn_-o-vie_QoDXD@sirena.co.uk/ [1] Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-8-1a25dc968e60@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/core.c | 148 +++++++++++++++++++++++++++++++-------- include/linux/regulator/driver.h | 1 + 2 files changed, 119 insertions(+), 30 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 08e92b1ba2dc..8c2fd20edd50 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -98,6 +98,7 @@ struct regulator_event_work { unsigned long event; }; +static int _regulator_enable(struct regulator *regulator); static int _regulator_is_enabled(struct regulator_dev *rdev); static int _regulator_disable(struct regulator *regulator); static int _regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags); @@ -1432,6 +1433,7 @@ static int handle_notify_limits(struct regulator_dev *rdev, /** * set_machine_constraints - sets regulator constraints * @rdev: regulator source + * @is_locked: whether or not this is called with locks held already * * Allows platform initialisation code to define and constrain * regulator circuits e.g. valid voltage/current ranges, etc. NOTE: @@ -1441,7 +1443,8 @@ static int handle_notify_limits(struct regulator_dev *rdev, * * Return: 0 on success or a negative error number on failure. */ -static int set_machine_constraints(struct regulator_dev *rdev) +static int set_machine_constraints(struct regulator_dev *rdev, + bool is_locked) { int ret = 0; const struct regulator_ops *ops = rdev->desc->ops; @@ -1653,7 +1656,9 @@ static int set_machine_constraints(struct regulator_dev *rdev) if (rdev->supply && (rdev->constraints->always_on || !regulator_is_enabled(rdev->supply))) { - ret = regulator_enable(rdev->supply); + ret = (is_locked + ? _regulator_enable(rdev->supply) + : regulator_enable(rdev->supply)); if (ret < 0) { _regulator_put(rdev->supply); rdev->supply = NULL; @@ -1781,6 +1786,15 @@ static int register_regulator_event_forwarding(struct regulator_dev *rdev) return 0; } +static void unregister_regulator_event_forwarding(struct regulator_dev *rdev) +{ + if (!rdev->supply_fwd_nb.notifier_call) + return; + + regulator_unregister_notifier(rdev->supply, &rdev->supply_fwd_nb); + rdev->supply_fwd_nb.notifier_call = NULL; +} + /** * set_supply - set regulator supply regulator * @rdev: regulator (locked) @@ -2169,6 +2183,8 @@ static int regulator_resolve_supply(struct regulator_dev *rdev) struct regulator_dev *r; struct device *dev = rdev->dev.parent; struct ww_acquire_ctx ww_ctx; + struct regulator *supply; + bool do_final_setup; int ret = 0; /* No supply to resolve? */ @@ -2176,7 +2192,7 @@ static int regulator_resolve_supply(struct regulator_dev *rdev) return 0; /* Supply already resolved? (fast-path without locking contention) */ - if (rdev->supply) + if (rdev->supply && !rdev->constraints_pending) return 0; /* first do a dt based lookup on the node described in the virtual @@ -2257,46 +2273,115 @@ static int regulator_resolve_supply(struct regulator_dev *rdev) /* Supply just resolved by a concurrent task? */ if (rdev->supply) { + /* Constraints might still be pending due to concurrency. */ + bool done = !rdev->constraints_pending; + + supply = rdev->supply; + regulator_unlock_two(rdev, r, &ww_ctx); put_device(&r->dev); - goto out; - } - ret = set_supply(rdev, r); - if (ret < 0) { + /* + * Supply resolved by concurrent task, and constraints set as + * well (or not required): fast path. + */ + if (done) + goto out; + + do_final_setup = false; + } else { + ret = set_supply(rdev, r); + if (ret < 0) { + regulator_unlock_two(rdev, r, &ww_ctx); + put_device(&r->dev); + goto out; + } + + supply = rdev->supply; + + /* + * Automatically register for event forwarding from the new + * supply. This creates the downstream propagation link for + * events like under-voltage. + */ + ret = register_regulator_event_forwarding(rdev); + if (ret < 0) { + rdev_warn(rdev, + "Failed to register event forwarding: %pe\n", + ERR_PTR(ret)); + + goto unset_supply; + } + regulator_unlock_two(rdev, r, &ww_ctx); - put_device(&r->dev); - goto out; + + do_final_setup = true; } /* - * Automatically register for event forwarding from the new supply. - * This creates the downstream propagation link for events like - * under-voltage. + * Now that we have the supply, we can retry setting the machine + * constraints, if necessary. */ - ret = register_regulator_event_forwarding(rdev); - if (ret < 0) { - struct regulator *supply; - - rdev_warn(rdev, "Failed to register event forwarding: %pe\n", - ERR_PTR(ret)); - - supply = rdev->supply; - rdev->supply = NULL; + regulator_lock_dependent(rdev, &ww_ctx); + if (rdev->constraints_pending) { + if (!rdev->supply) { + /* + * Supply could have been released by another task that + * failed to set the constraints or event forwarding. + */ + regulator_unlock_dependent(rdev, &ww_ctx); + ret = -EPROBE_DEFER; + goto out; + } - regulator_unlock_two(rdev, supply->rdev, &ww_ctx); + ret = set_machine_constraints(rdev, true); + if (ret < 0) { + regulator_unlock_dependent(rdev, &ww_ctx); + + rdev_warn(rdev, + "Failed to set machine constraints: %pe\n", + ERR_PTR(ret)); + + regulator_lock_two(rdev, r, &ww_ctx); + + if (supply != rdev->supply) { + /* + * Supply could have been released by another + * task that got here before us. If it did, it + * will have released 'supply' (i.e. the + * previous rdev->supply) and we shouldn't do + * that again via unset_supply. + */ + regulator_unlock_two(rdev, r, &ww_ctx); + goto out; + } - regulator_put(supply); - goto out; + unregister_regulator_event_forwarding(rdev); + rdev->constraints_pending = true; + goto unset_supply; + } + rdev->constraints_pending = false; } + regulator_unlock_dependent(rdev, &ww_ctx); - regulator_unlock_two(rdev, r, &ww_ctx); + if (!do_final_setup) + goto out; /* rdev->supply was created in set_supply() */ - link_and_create_debugfs(rdev->supply, r, &rdev->dev); + link_and_create_debugfs(rdev->supply, rdev->supply->rdev, &rdev->dev); out: return ret; + +unset_supply: + lockdep_assert_held_once(&rdev->mutex.base); + lockdep_assert_held_once(&r->mutex.base); + rdev->supply = NULL; + regulator_unlock_two(rdev, supply->rdev, &ww_ctx); + + regulator_put(supply); + + return ret; } /* common pre-checks for regulator requests */ @@ -6067,7 +6152,7 @@ regulator_register(struct device *dev, dangling_of_gpiod = false; } - ret = set_machine_constraints(rdev); + ret = set_machine_constraints(rdev, false); if (ret == -EPROBE_DEFER) { /* Regulator might be in bypass mode or an always-on or boot-on * regulator and so needs its supply to set the constraints or @@ -6081,14 +6166,17 @@ regulator_register(struct device *dev, rdev->supply_name); ret = regulator_resolve_supply(rdev); if (!ret) - ret = set_machine_constraints(rdev); + ret = set_machine_constraints(rdev, false); else rdev_dbg(rdev, "unable to resolve supply early: %pe\n", ERR_PTR(ret)); tried_supply_resolve = true; } - if (ret < 0) - goto wash; + if (ret < 0) { + if (ret != -EPROBE_DEFER) + goto wash; + rdev->constraints_pending = true; + } ret = regulator_init_coupling(rdev); if (ret < 0) diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index d38353f2b56f..09f3b67638f9 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -650,6 +650,7 @@ struct regulator_dev { struct regulator_enable_gpio *ena_pin; unsigned int ena_gpio_state:1; + unsigned int constraints_pending:1; unsigned int is_switch:1; /* time when this regulator was disabled last time */ -- cgit v1.2.3 From da1456e435ae84852bda484cd4d60f47228d52fc Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Fri, 2 Jan 2026 11:13:56 +0100 Subject: regulator: dt-bindings: Document TI TPS65185 Document the TPS65185. GPIO names are same as in the datasheet except for the PWRUP pad which is described as "enable". That pin is optional because the rising edge corresponds to setting one register bit and falling edge to another register bit. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Andreas Kemnade Link: https://patch.msgid.link/20260102-tps65185-submit-v3-1-23bda35772f2@kemnade.info Signed-off-by: Mark Brown --- .../devicetree/bindings/regulator/ti,tps65185.yaml | 96 ++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/ti,tps65185.yaml diff --git a/Documentation/devicetree/bindings/regulator/ti,tps65185.yaml b/Documentation/devicetree/bindings/regulator/ti,tps65185.yaml new file mode 100644 index 000000000000..af0f638b80bc --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/ti,tps65185.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/ti,tps65185.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI TPS65185 Power Management Integrated Circuit + +maintainers: + - Andreas Kemnade + +description: + TPS65185 is a Power Management IC to provide Power for EPDs with one 3.3V + switch, 2 symmetric LDOs behind 2 DC/DC converters, and one unsymmetric + regulator for a compensation voltage. + +properties: + compatible: + const: ti,tps65185 + + reg: + maxItems: 1 + + enable-gpios: + description: + PWRUP pin + maxItems: 1 + + pwr-good-gpios: + maxItems: 1 + + vcom-ctrl-gpios: + maxItems: 1 + + wakeup-gpios: + maxItems: 1 + + vin-supply: true + + interrupts: + maxItems: 1 + + regulators: + type: object + additionalProperties: false + patternProperties: + "^(vcom|vposneg|v3p3)$": + unevaluatedProperties: false + type: object + $ref: /schemas/regulator/regulator.yaml + +required: + - compatible + - reg + - pwr-good-gpios + - vin-supply + +additionalProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pmic@18 { + compatible = "ti,tps65185"; + reg = <0x18>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_tps65185_gpio>; + pwr-good-gpios = <&gpio2 7 GPIO_ACTIVE_HIGH>; + vcom-ctrl-gpios = <&gpio2 9 GPIO_ACTIVE_HIGH>; + enable-gpios = <&gpio2 8 GPIO_ACTIVE_HIGH>; + wakeup-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>; + vin-supply = <&epdc_pmic_supply>; + interrupts-extended = <&gpio2 0 IRQ_TYPE_LEVEL_LOW>; + + regulators { + vcom { + regulator-name = "vcom"; + }; + + vposneg { + regulator-name = "vposneg"; + regulator-min-microvolt = <15000000>; + regulator-max-microvolt = <15000000>; + }; + + v3p3 { + regulator-name = "v3p3"; + }; + }; + }; + }; -- cgit v1.2.3 From b0fc1e7701940d12ea2c41f386aa552bc4cc3629 Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Fri, 2 Jan 2026 11:13:57 +0100 Subject: regulator: Add TPS65185 driver Add a driver for the TPS65185 regulator. Implement handling of the various gpio pins. Because the PWRUP (=enable) pin functionality can be achieved by just using two bits instead, just ensure that it is set to a stable value. Implement the pair of symmetric LDOs as a single regulator because they share a single voltage set register. As the VCOM regulator sits behind that machinery, just define that one as a supply. For simplicity, just add the temperature sensor (depending on external NTC) directly. There is a mechanism to measure some kick-back voltage during a defined EPD operation, to calibrate the VCOM voltage setting and store that non-volatile in the chip to be the power up default setup. That is not implemented yet in the driver, but that also means that there is a non-factory default value in these registers after power-up. Tested-by: Josua Mayer Signed-off-by: Andreas Kemnade Link: https://patch.msgid.link/20260102-tps65185-submit-v3-2-23bda35772f2@kemnade.info Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 11 ++ drivers/regulator/Makefile | 1 + drivers/regulator/tps65185.c | 454 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 466 insertions(+) create mode 100644 drivers/regulator/tps65185.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index d2335276cce5..5c539782e48d 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1690,6 +1690,17 @@ config REGULATOR_TPS65132 This driver supports TPS65132 single inductor - dual output power supply specifically designed for display panels. +config REGULATOR_TPS65185 + tristate "TI TPS65185 EPD regulator" + depends on I2C + select REGMAP_I2C + help + This driver supports the TPS65185 voltage regulator chip + which is used to provide power to Electronic Paper Displays + so it is found in E-Book readers. + If HWWON is enabled, it also provides temperature measurement. + + config REGULATOR_TPS65217 tristate "TI TPS65217 Power regulators" depends on MFD_TPS65217 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 1beba1493241..240de94cd432 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -192,6 +192,7 @@ obj-$(CONFIG_REGULATOR_TPS65023) += tps65023-regulator.o obj-$(CONFIG_REGULATOR_TPS6507X) += tps6507x-regulator.o obj-$(CONFIG_REGULATOR_TPS65086) += tps65086-regulator.o obj-$(CONFIG_REGULATOR_TPS65090) += tps65090-regulator.o +obj-$(CONFIG_REGULATOR_TPS65185) += tps65185.o obj-$(CONFIG_REGULATOR_TPS65217) += tps65217-regulator.o obj-$(CONFIG_REGULATOR_TPS65218) += tps65218-regulator.o obj-$(CONFIG_REGULATOR_TPS65219) += tps65219-regulator.o diff --git a/drivers/regulator/tps65185.c b/drivers/regulator/tps65185.c new file mode 100644 index 000000000000..3286c9ab33d0 --- /dev/null +++ b/drivers/regulator/tps65185.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2025 Andreas Kemnade + +/* Datasheet: https://www.ti.com/lit/gpn/tps65185 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TPS65185_REG_TMST_VALUE 0 +#define TPS65185_REG_ENABLE 1 +#define TPS65185_REG_VADJ 2 +#define TPS65185_REG_VCOM1 3 +#define TPS65185_REG_VCOM2 4 +#define TPS65185_REG_INT_EN1 5 +#define TPS65185_REG_INT_EN2 6 +#define TPS65185_REG_INT1 7 +#define TPS65185_REG_INT2 8 +#define TPS65185_REG_TMST1 0xd +#define TPS65185_REG_TMST2 0xe +#define TPS65185_REG_PG 0xf +#define TPS65185_REG_REVID 0x10 + +#define TPS65185_READ_THERM BIT(7) +#define TPS65185_CONV_END BIT(5) + +#define TPS65185_ENABLE_ACTIVE BIT(7) +#define TPS65185_ENABLE_STANDBY BIT(6) + +#define PGOOD_TIMEOUT_MSECS 200 + +struct tps65185_data { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *pgood_gpio; + struct gpio_desc *pwrup_gpio; + struct gpio_desc *vcom_ctrl_gpio; + struct gpio_desc *wakeup_gpio; + struct completion pgood_completion; + int pgood_irq; + struct completion tmst_completion; +}; + +static const struct hwmon_channel_info *tps65185_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static int tps65185_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *temp) +{ + struct tps65185_data *data = dev_get_drvdata(dev); + unsigned int val; + int ret; + + reinit_completion(&data->tmst_completion); + /* start acquisition */ + regmap_update_bits(data->regmap, TPS65185_REG_TMST1, + TPS65185_READ_THERM, TPS65185_READ_THERM); + wait_for_completion_timeout(&data->tmst_completion, + msecs_to_jiffies(PGOOD_TIMEOUT_MSECS)); + ret = regmap_read(data->regmap, TPS65185_REG_TMST1, &val); + if (!(val & TPS65185_CONV_END)) + return -ETIMEDOUT; + + ret = regmap_read(data->regmap, TPS65185_REG_TMST_VALUE, &val); + if (ret) + return ret; + + *temp = (s8)val * 1000; + + return 0; +} + +static umode_t tps65185_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static const struct hwmon_ops tps65185_hwmon_ops = { + .is_visible = tps65185_hwmon_is_visible, + .read = tps65185_hwmon_read, +}; + +static const struct hwmon_chip_info tps65185_chip_info = { + .ops = &tps65185_hwmon_ops, + .info = tps65185_info, +}; + +static bool tps65185_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS65185_REG_TMST_VALUE: + case TPS65185_REG_ENABLE: + case TPS65185_REG_VCOM2: + case TPS65185_REG_INT1: + case TPS65185_REG_INT2: + case TPS65185_REG_TMST1: + return true; + default: + return false; + } +} + +static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x10, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = tps65185_volatile_reg, +}; + +static const struct regulator_ops tps65185_v3p3ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static int tps65185_check_powergood(struct regulator_dev *rdev) +{ + struct tps65185_data *data = rdev_get_drvdata(rdev); + + return gpiod_get_value_cansleep(data->pgood_gpio); +} + +static int tps65185_vposneg_get_voltage_sel(struct regulator_dev *rdev) +{ + int ret; + + ret = regulator_get_voltage_sel_regmap(rdev); + if (ret < 0) + return ret; + + /* highest value is lowest voltage */ + return 6 - ret; +} + +static int tps65185_vposneg_set_voltage_sel(struct regulator_dev *rdev, unsigned int selector) +{ + return regulator_set_voltage_sel_regmap(rdev, 6 - selector); +} + +static irqreturn_t pgood_handler(int irq, void *dev_id) +{ + struct tps65185_data *data = dev_id; + + complete(&data->pgood_completion); + + return IRQ_HANDLED; +} + +static int tps65185_vposneg_enable(struct regulator_dev *rdev) +{ + struct tps65185_data *data = rdev_get_drvdata(rdev); + int ret; + + reinit_completion(&data->pgood_completion); + if (data->pwrup_gpio) + ret = gpiod_set_value_cansleep(data->pwrup_gpio, 1); + else + ret = regmap_update_bits(data->regmap, TPS65185_REG_ENABLE, + TPS65185_ENABLE_ACTIVE, + TPS65185_ENABLE_ACTIVE); + + if (ret) + return ret; + + dev_dbg(data->dev, "turning on..."); + wait_for_completion_timeout(&data->pgood_completion, + msecs_to_jiffies(PGOOD_TIMEOUT_MSECS)); + dev_dbg(data->dev, "turned on"); + if (gpiod_get_value_cansleep(data->pgood_gpio) != 1) + return -ETIMEDOUT; + + return 0; +} + +static int tps65185_vposneg_disable(struct regulator_dev *rdev) +{ + struct tps65185_data *data = rdev_get_drvdata(rdev); + int ret; + + if (data->pwrup_gpio) + ret = gpiod_set_value_cansleep(data->pwrup_gpio, 0); + else + ret = regmap_update_bits(data->regmap, TPS65185_REG_ENABLE, + TPS65185_ENABLE_STANDBY, + TPS65185_ENABLE_STANDBY); + + return ret; +} + +static int tps65185_vcom_set_voltage_sel(struct regulator_dev *rdev, unsigned int selector) +{ + struct tps65185_data *data = rdev_get_drvdata(rdev); + int ret; + + ret = regmap_update_bits(data->regmap, TPS65185_REG_VCOM2, BIT(0), selector >> 8); + if (ret < 0) + return ret; + + return regmap_write(data->regmap, TPS65185_REG_VCOM1, selector & 0xFF); +} + +static int tps65185_vcom_get_voltage_sel(struct regulator_dev *rdev) +{ + struct tps65185_data *data = rdev_get_drvdata(rdev); + int ret; + unsigned int sel, sel2; + + ret = regmap_read(data->regmap, TPS65185_REG_VCOM1, &sel); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, TPS65185_REG_VCOM2, &sel2); + if (ret < 0) + return ret; + + if (sel2 & BIT(0)) + sel |= 0x100; + + return sel; +} + +static const struct regulator_ops tps65185_vcom_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_voltage_sel = tps65185_vcom_set_voltage_sel, + .get_voltage_sel = tps65185_vcom_get_voltage_sel, +}; + +static const struct regulator_ops tps65185_vposneg_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .enable = tps65185_vposneg_enable, + .disable = tps65185_vposneg_disable, + .is_enabled = tps65185_check_powergood, + .set_voltage_sel = tps65185_vposneg_set_voltage_sel, + .get_voltage_sel = tps65185_vposneg_get_voltage_sel, +}; + +static const struct regulator_desc regulators[] = { + { + .name = "v3p3", + .of_match = of_match_ptr("v3p3"), + .regulators_node = of_match_ptr("regulators"), + .id = 0, + .ops = &tps65185_v3p3ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = TPS65185_REG_ENABLE, + .enable_mask = BIT(5), + .n_voltages = 1, + .min_uV = 3300000, + }, + { + .name = "vposneg", + .of_match = of_match_ptr("vposneg"), + .regulators_node = of_match_ptr("regulators"), + .id = 1, + .ops = &tps65185_vposneg_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = 4, + .vsel_reg = TPS65185_REG_VADJ, + .vsel_mask = 0x7, + .min_uV = 14250000, + .uV_step = 250000, + } +}; + +static const struct regulator_desc vcom_regulator_desc = { + .name = "vcom", + .of_match = of_match_ptr("vcom"), + .regulators_node = of_match_ptr("regulators"), + .supply_name = "vposneg", + .id = 2, + .ops = &tps65185_vcom_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = 511, + .min_uV = 0, + .uV_step = 10000, +}; + +static irqreturn_t tps65185_irq_thread(int irq, void *dev_id) +{ + struct tps65185_data *data = dev_id; + unsigned int int_status_1, int_status_2; + int ret; + + /* read both status to have irq cleared */ + ret = regmap_read(data->regmap, TPS65185_REG_INT1, &int_status_1); + if (ret) + return IRQ_NONE; + + ret = regmap_read(data->regmap, TPS65185_REG_INT2, &int_status_2); + if (ret) + return IRQ_NONE; + + if (int_status_2 & BIT(0)) + complete(&data->tmst_completion); + + dev_dbg(data->dev, "irq status %02x %02x\n", int_status_1, int_status_2); + + if (int_status_1 || int_status_2) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static int tps65185_probe(struct i2c_client *client) +{ + struct tps65185_data *data; + struct regulator_config config = { }; + struct regulator_dev *rdev; + int ret = 0; + int i; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + data->regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(data->regmap), + "failed to allocate regmap!\n"); + + data->pgood_gpio = devm_gpiod_get(&client->dev, "pwr-good", GPIOD_IN); + if (IS_ERR(data->pgood_gpio)) + return dev_err_probe(&client->dev, + PTR_ERR(data->pgood_gpio), + "failed to get power good gpio\n"); + + data->pgood_irq = gpiod_to_irq(data->pgood_gpio); + if (data->pgood_irq < 0) + return data->pgood_irq; + + data->pwrup_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(data->pwrup_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(data->pwrup_gpio), + "failed to get pwrup gpio\n"); + + data->wakeup_gpio = devm_gpiod_get_optional(&client->dev, "wakeup", GPIOD_OUT_HIGH); + if (IS_ERR(data->wakeup_gpio)) + return dev_err_probe(&client->dev, + PTR_ERR(data->wakeup_gpio), + "failed to get wakeup gpio\n"); + + data->vcom_ctrl_gpio = devm_gpiod_get_optional(&client->dev, "vcom-ctrl", GPIOD_OUT_LOW); + if (IS_ERR(data->vcom_ctrl_gpio)) + return dev_err_probe(&client->dev, + PTR_ERR(data->vcom_ctrl_gpio), + "failed to get vcm ctrl gpio\n"); + + ret = devm_regulator_get_enable(&client->dev, "vin"); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to get vin regulator\n"); + + data->dev = &client->dev; + i2c_set_clientdata(client, data); + + init_completion(&data->pgood_completion); + init_completion(&data->tmst_completion); + + ret = devm_request_threaded_irq(&client->dev, data->pgood_irq, NULL, + pgood_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "PGOOD", data); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to request power good irq\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tps65185_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "tps65185", data); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to request irq\n"); + } + + ret = regmap_update_bits(data->regmap, TPS65185_REG_INT_EN2, BIT(0), BIT(0)); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to enable temp irq\n"); + + config.driver_data = data; + config.dev = &client->dev; + config.regmap = data->regmap; + + for (i = 0; i < ARRAY_SIZE(regulators); i++) { + rdev = devm_regulator_register(&client->dev, ®ulators[i], + &config); + if (IS_ERR(rdev)) + return dev_err_probe(&client->dev, PTR_ERR(rdev), + "failed to register %s regulator\n", + regulators[i].name); + } + + config.ena_gpiod = data->vcom_ctrl_gpio; + rdev = devm_regulator_register(&client->dev, &vcom_regulator_desc, &config); + if (IS_ERR(rdev)) + return dev_err_probe(&client->dev, PTR_ERR(rdev), + "failed to register vcom regulator\n"); + + if (IS_REACHABLE(CONFIG_HWMON)) { + struct device *hwmon_dev; + + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, "tps65185", data, + &tps65185_chip_info, NULL); + if (IS_ERR(hwmon_dev)) + dev_notice(&client->dev, "failed to register hwmon\n"); + } + + return 0; +} + +static const struct of_device_id tps65185_dt_ids[] = { + { + .compatible = "ti,tps65185", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, tps65185_dt_ids); + +static struct i2c_driver tps65185_i2c_driver = { + .driver = { + .name = "tps65185", + .of_match_table = tps65185_dt_ids, + }, + .probe = tps65185_probe, +}; + +module_i2c_driver(tps65185_i2c_driver); + +/* Module information */ +MODULE_DESCRIPTION("TPS65185 regulator driver"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 09dc08b396c954820f119e1ab0c7d72333c18323 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Mon, 12 Jan 2026 15:49:09 +0000 Subject: regulator: dummy, make dummy_regulator_driver static When converting to faux_device the dummy_regulator_driver was made non-static however it isn't exported or defined anywhere outside the file it is in. Make it static to avoid the following sparse warning: drivers/regulator/dummy.c:59:24: warning: symbol 'dummy_regulator_driver' was not declared. Should it be static? Fixes: dcd2a9a5550ef556c8 ("regulator: dummy: convert to use the faux device interface") Signed-off-by: Ben Dooks Link: https://patch.msgid.link/20260112154909.601987-1-ben.dooks@codethink.co.uk Signed-off-by: Mark Brown --- drivers/regulator/dummy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/regulator/dummy.c b/drivers/regulator/dummy.c index e5197ec7234d..c3e416fd3c3e 100644 --- a/drivers/regulator/dummy.c +++ b/drivers/regulator/dummy.c @@ -56,7 +56,7 @@ static int dummy_regulator_probe(struct faux_device *fdev) return 0; } -struct faux_device_ops dummy_regulator_driver = { +static struct faux_device_ops dummy_regulator_driver = { .probe = dummy_regulator_probe, }; -- cgit v1.2.3 From de9f1b1583aecb246b659effb03f2456604fab64 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 13 Jan 2026 11:59:57 +0100 Subject: regulator: dt-bindings: mediatek,mt6331: Add missing ldo-vio28 vreg The MT6331 has a "ldo-vio28" regulator but this was missing in the list: add it to resolve a dtbs_check warning. Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260113110000.36953-4-angelogioacchino.delregno@collabora.com Signed-off-by: Mark Brown --- .../devicetree/bindings/regulator/mediatek,mt6331-regulator.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/regulator/mediatek,mt6331-regulator.yaml b/Documentation/devicetree/bindings/regulator/mediatek,mt6331-regulator.yaml index c654acf13768..eb16e53cb5bf 100644 --- a/Documentation/devicetree/bindings/regulator/mediatek,mt6331-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/mediatek,mt6331-regulator.yaml @@ -40,13 +40,13 @@ patternProperties: unevaluatedProperties: false - "^ldo-v(dig18|emc33|ibr|mc|mch|mipi|rtc|sim1|sim2|sram|usb10)$": + "^ldo-v(dig18|emc33|ibr|io28|mc|mch|mipi|rtc|sim1|sim2|sram|usb10)$": type: object $ref: regulator.yaml# properties: regulator-name: - pattern: "^v(dig18|emc33|ibr|mc|mch|mipi|rtc|sim1|sim2|sram|usb)$" + pattern: "^v(dig18|emc33|ibr|io28|mc|mch|mipi|rtc|sim1|sim2|sram|usb)$" unevaluatedProperties: false -- cgit v1.2.3 From 62b04225e99a5d1c71c5c73d2aa6618bc2c0738f Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Wed, 7 Jan 2026 22:36:25 +0100 Subject: regulator: dt-bindings: rpi-panel: Mark 7" Raspberry Pi as GPIO controller Mark the Raspberry Pi 7" Display 1 ATTINY based regulator as GPIO controller, because the hardware behaves that way in addition to being a regulator. Add fixed gpio-cells as well. Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260107213638.505319-1-marex@nabladev.com Signed-off-by: Mark Brown --- .../regulator/raspberrypi,7inch-touchscreen-panel-regulator.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator.yaml b/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator.yaml index 41678400e63f..6c23f18a32c6 100644 --- a/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator.yaml @@ -24,6 +24,11 @@ properties: reg: maxItems: 1 + gpio-controller: true + + "#gpio-cells": + const: 2 + additionalProperties: false required: -- cgit v1.2.3 From 20c4701b75a3d6ce09d61e17125aefe77e7eb333 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 19 Jan 2026 11:48:49 +0800 Subject: dt-bindings: regulator: mark regulator-suspend-microvolt as deprecated The Documentation/devicetree/bindings/regulator/regulator.yaml already states in its description that regulator-suspend-microvolt is deprecated, but the schema did not formally mark it as such. Add the `deprecated: true` annotation to regulator-suspend-microvolt so that this is enforced at the schema level. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260119-regulator-binding-v1-1-e55d33b4c3e3@nxp.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/regulator.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/regulator/regulator.yaml b/Documentation/devicetree/bindings/regulator/regulator.yaml index 77573bcb6b79..042e56396399 100644 --- a/Documentation/devicetree/bindings/regulator/regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/regulator.yaml @@ -274,6 +274,7 @@ patternProperties: suspend. This property is now deprecated, instead setting voltage for suspend mode via the API which regulator driver provides is recommended. + deprecated: true regulator-changeable-in-suspend: description: whether the default voltage and the regulator on/off -- cgit v1.2.3 From 153ae5c52b7063ac0926926d0cc9b53ee9d7fed2 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Tue, 13 Jan 2026 14:03:11 +0000 Subject: mfd: sec: Add rtc alarm IRQ as platform device resource MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By adding the RTC alarm IRQ to the MFD cell as a resource, the child driver (rtc) can simply query that IRQ, instead of having a lookup table itself. This change therefore allows the child driver to be simplified with regards to determining the alarm IRQ. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260113-s5m-alarm-v3-1-855a19db1277@linaro.org Signed-off-by: Lee Jones --- drivers/mfd/sec-common.c | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/drivers/mfd/sec-common.c b/drivers/mfd/sec-common.c index 42d55e70e34c..77370db52a7b 100644 --- a/drivers/mfd/sec-common.c +++ b/drivers/mfd/sec-common.c @@ -23,9 +23,13 @@ #include #include "sec-core.h" +static const struct resource s5m8767_rtc_resources[] = { + DEFINE_RES_IRQ_NAMED(S5M8767_IRQ_RTCA1, "alarm"), +}; + static const struct mfd_cell s5m8767_devs[] = { MFD_CELL_NAME("s5m8767-pmic"), - MFD_CELL_NAME("s5m-rtc"), + MFD_CELL_RES("s5m-rtc", s5m8767_rtc_resources), MFD_CELL_OF("s5m8767-clk", NULL, NULL, 0, 0, "samsung,s5m8767-clk"), }; @@ -33,50 +37,66 @@ static const struct mfd_cell s2dos05_devs[] = { MFD_CELL_NAME("s2dos05-regulator"), }; +static const struct resource s2mpg10_rtc_resources[] = { + DEFINE_RES_IRQ_NAMED(S2MPG10_IRQ_RTCA0, "alarm"), +}; + static const struct mfd_cell s2mpg10_devs[] = { MFD_CELL_NAME("s2mpg10-meter"), MFD_CELL_NAME("s2mpg10-regulator"), - MFD_CELL_NAME("s2mpg10-rtc"), + MFD_CELL_RES("s2mpg10-rtc", s2mpg10_rtc_resources), MFD_CELL_OF("s2mpg10-clk", NULL, NULL, 0, 0, "samsung,s2mpg10-clk"), MFD_CELL_OF("s2mpg10-gpio", NULL, NULL, 0, 0, "samsung,s2mpg10-gpio"), }; +static const struct resource s2mps11_rtc_resources[] = { + DEFINE_RES_IRQ_NAMED(S2MPS11_IRQ_RTCA0, "alarm"), +}; + static const struct mfd_cell s2mps11_devs[] = { MFD_CELL_NAME("s2mps11-regulator"), - MFD_CELL_NAME("s2mps14-rtc"), + MFD_CELL_RES("s2mps14-rtc", s2mps11_rtc_resources), MFD_CELL_OF("s2mps11-clk", NULL, NULL, 0, 0, "samsung,s2mps11-clk"), }; +static const struct resource s2mps14_rtc_resources[] = { + DEFINE_RES_IRQ_NAMED(S2MPS14_IRQ_RTCA0, "alarm"), +}; + static const struct mfd_cell s2mps13_devs[] = { MFD_CELL_NAME("s2mps13-regulator"), - MFD_CELL_NAME("s2mps13-rtc"), + MFD_CELL_RES("s2mps13-rtc", s2mps14_rtc_resources), MFD_CELL_OF("s2mps13-clk", NULL, NULL, 0, 0, "samsung,s2mps13-clk"), }; static const struct mfd_cell s2mps14_devs[] = { MFD_CELL_NAME("s2mps14-regulator"), - MFD_CELL_NAME("s2mps14-rtc"), + MFD_CELL_RES("s2mps14-rtc", s2mps14_rtc_resources), MFD_CELL_OF("s2mps14-clk", NULL, NULL, 0, 0, "samsung,s2mps14-clk"), }; static const struct mfd_cell s2mps15_devs[] = { MFD_CELL_NAME("s2mps15-regulator"), - MFD_CELL_NAME("s2mps15-rtc"), + MFD_CELL_RES("s2mps15-rtc", s2mps14_rtc_resources), MFD_CELL_OF("s2mps13-clk", NULL, NULL, 0, 0, "samsung,s2mps13-clk"), }; static const struct mfd_cell s2mpa01_devs[] = { MFD_CELL_NAME("s2mpa01-pmic"), - MFD_CELL_NAME("s2mps14-rtc"), + MFD_CELL_RES("s2mps14-rtc", s2mps14_rtc_resources), }; static const struct mfd_cell s2mpu02_devs[] = { MFD_CELL_NAME("s2mpu02-regulator"), }; +static const struct resource s2mpu05_rtc_resources[] = { + DEFINE_RES_IRQ_NAMED(S2MPU05_IRQ_RTCA0, "alarm"), +}; + static const struct mfd_cell s2mpu05_devs[] = { MFD_CELL_NAME("s2mpu05-regulator"), - MFD_CELL_NAME("s2mps15-rtc"), + MFD_CELL_RES("s2mps15-rtc", s2mpu05_rtc_resources), }; static void sec_pmic_dump_rev(struct sec_pmic_dev *sec_pmic) @@ -220,7 +240,7 @@ int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq, sec_pmic->device_type); } ret = devm_mfd_add_devices(sec_pmic->dev, -1, sec_devs, num_sec_devs, - NULL, 0, NULL); + NULL, 0, regmap_irq_get_domain(sec_pmic->irq_data)); if (ret) return ret; -- cgit v1.2.3 From c70aee3dd85482c67720eb642d59ebbb9433faa5 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Tue, 13 Jan 2026 14:03:12 +0000 Subject: rtc: s5m: query platform device IRQ resource for alarm IRQ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The core driver now exposes the alarm IRQ as a resource, so we can drop the lookup from here to simplify the code and make adding support for additional variants easier in this driver. Signed-off-by: André Draszik Acked-by: Alexandre Belloni Link: https://patch.msgid.link/20260113-s5m-alarm-v3-2-855a19db1277@linaro.org Signed-off-by: Lee Jones --- drivers/rtc/rtc-s5m.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/rtc/rtc-s5m.c b/drivers/rtc/rtc-s5m.c index a7220b4d0e8d..c6ed5a4ca8a0 100644 --- a/drivers/rtc/rtc-s5m.c +++ b/drivers/rtc/rtc-s5m.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -683,22 +682,18 @@ static int s5m_rtc_probe(struct platform_device *pdev) case S2MPS15X: regmap_cfg = &s2mps14_rtc_regmap_config; info->regs = &s2mps15_rtc_regs; - alarm_irq = S2MPS14_IRQ_RTCA0; break; case S2MPS14X: regmap_cfg = &s2mps14_rtc_regmap_config; info->regs = &s2mps14_rtc_regs; - alarm_irq = S2MPS14_IRQ_RTCA0; break; case S2MPS13X: regmap_cfg = &s2mps14_rtc_regmap_config; info->regs = &s2mps13_rtc_regs; - alarm_irq = S2MPS14_IRQ_RTCA0; break; case S5M8767X: regmap_cfg = &s5m_rtc_regmap_config; info->regs = &s5m_rtc_regs; - alarm_irq = S5M8767_IRQ_RTCA1; break; default: return dev_err_probe(&pdev->dev, -ENODEV, @@ -719,7 +714,6 @@ static int s5m_rtc_probe(struct platform_device *pdev) "Failed to allocate regmap\n"); } else if (device_type == S2MPG10) { info->regs = &s2mpg10_rtc_regs; - alarm_irq = S2MPG10_IRQ_RTCA0; } else { return dev_err_probe(&pdev->dev, -ENODEV, "Unsupported device type %d\n", @@ -730,13 +724,14 @@ static int s5m_rtc_probe(struct platform_device *pdev) info->s5m87xx = s5m87xx; info->device_type = device_type; - if (s5m87xx->irq_data) { - info->irq = regmap_irq_get_virq(s5m87xx->irq_data, alarm_irq); - if (info->irq <= 0) - return dev_err_probe(&pdev->dev, -EINVAL, - "Failed to get virtual IRQ %d\n", - alarm_irq); - } + alarm_irq = platform_get_irq_byname_optional(pdev, "alarm"); + if (alarm_irq > 0) + info->irq = alarm_irq; + else if (alarm_irq == -ENXIO) + info->irq = 0; + else + return dev_err_probe(&pdev->dev, alarm_irq ? : -EINVAL, + "IRQ 'alarm' not found\n"); platform_set_drvdata(pdev, info); -- cgit v1.2.3 From b31583a1a9ab32923734ceb5fc95e536dfacccf7 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Tue, 13 Jan 2026 14:03:13 +0000 Subject: mfd: sec: Drop now unused struct sec_pmic_dev::irq_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was used only to allow the s5m RTC driver to deal with the alarm IRQ. That driver now uses a different approach to acquire that IRQ, and ::irq_data doesn't need to be kept around anymore. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260113-s5m-alarm-v3-3-855a19db1277@linaro.org Signed-off-by: Lee Jones --- drivers/mfd/sec-common.c | 9 +++--- drivers/mfd/sec-core.h | 2 +- drivers/mfd/sec-irq.c | 64 ++++++++++++++++++---------------------- include/linux/mfd/samsung/core.h | 1 - 4 files changed, 35 insertions(+), 41 deletions(-) diff --git a/drivers/mfd/sec-common.c b/drivers/mfd/sec-common.c index 77370db52a7b..0021f9ae8484 100644 --- a/drivers/mfd/sec-common.c +++ b/drivers/mfd/sec-common.c @@ -163,6 +163,7 @@ sec_pmic_parse_dt_pdata(struct device *dev) int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq, struct regmap *regmap, struct i2c_client *client) { + struct regmap_irq_chip_data *irq_data; struct sec_platform_data *pdata; const struct mfd_cell *sec_devs; struct sec_pmic_dev *sec_pmic; @@ -187,9 +188,9 @@ int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq, sec_pmic->pdata = pdata; - ret = sec_irq_init(sec_pmic); - if (ret) - return ret; + irq_data = sec_irq_init(sec_pmic); + if (IS_ERR(irq_data)) + return PTR_ERR(irq_data); pm_runtime_set_active(sec_pmic->dev); @@ -240,7 +241,7 @@ int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq, sec_pmic->device_type); } ret = devm_mfd_add_devices(sec_pmic->dev, -1, sec_devs, num_sec_devs, - NULL, 0, regmap_irq_get_domain(sec_pmic->irq_data)); + NULL, 0, regmap_irq_get_domain(irq_data)); if (ret) return ret; diff --git a/drivers/mfd/sec-core.h b/drivers/mfd/sec-core.h index 92c7558ab8b0..8d85c70c2326 100644 --- a/drivers/mfd/sec-core.h +++ b/drivers/mfd/sec-core.h @@ -18,6 +18,6 @@ int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq, struct regmap *regmap, struct i2c_client *client); void sec_pmic_shutdown(struct device *dev); -int sec_irq_init(struct sec_pmic_dev *sec_pmic); +struct regmap_irq_chip_data *sec_irq_init(struct sec_pmic_dev *sec_pmic); #endif /* __SEC_CORE_INT_H */ diff --git a/drivers/mfd/sec-irq.c b/drivers/mfd/sec-irq.c index 74ac70002d1f..e0dd122e8fe5 100644 --- a/drivers/mfd/sec-irq.c +++ b/drivers/mfd/sec-irq.c @@ -268,26 +268,28 @@ static const struct regmap_irq_chip s5m8767_irq_chip = { .ack_base = S5M8767_REG_INT1, }; -static int s2mpg1x_add_chained_irq_chip(struct device *dev, struct regmap *regmap, int pirq, - struct regmap_irq_chip_data *parent, - const struct regmap_irq_chip *chip, - struct regmap_irq_chip_data **data) +static struct regmap_irq_chip_data * +s2mpg1x_add_chained_pmic(struct sec_pmic_dev *sec_pmic, int pirq, + struct regmap_irq_chip_data *parent, const struct regmap_irq_chip *chip) { + struct device *dev = sec_pmic->dev; + struct regmap_irq_chip_data *data; int irq, ret; irq = regmap_irq_get_virq(parent, pirq); if (irq < 0) - return dev_err_probe(dev, irq, "Failed to get parent vIRQ(%d) for chip %s\n", pirq, - chip->name); + return dev_err_ptr_probe(dev, irq, "Failed to get parent vIRQ(%d) for chip %s\n", + pirq, chip->name); - ret = devm_regmap_add_irq_chip(dev, regmap, irq, IRQF_ONESHOT | IRQF_SHARED, 0, chip, data); + ret = devm_regmap_add_irq_chip(dev, sec_pmic->regmap_pmic, irq, + IRQF_ONESHOT | IRQF_SHARED, 0, chip, &data); if (ret) - return dev_err_probe(dev, ret, "Failed to add %s IRQ chip\n", chip->name); + return dev_err_ptr_probe(dev, ret, "Failed to add %s IRQ chip\n", chip->name); - return 0; + return data; } -static int sec_irq_init_s2mpg1x(struct sec_pmic_dev *sec_pmic) +static struct regmap_irq_chip_data *sec_irq_init_s2mpg1x(struct sec_pmic_dev *sec_pmic) { const struct regmap_irq_chip *irq_chip, *chained_irq_chip; struct regmap_irq_chip_data *irq_data; @@ -302,27 +304,27 @@ static int sec_irq_init_s2mpg1x(struct sec_pmic_dev *sec_pmic) chained_pirq = S2MPG10_COMMON_IRQ_PMIC; break; default: - return dev_err_probe(sec_pmic->dev, -EINVAL, "Unsupported device type %d\n", - sec_pmic->device_type); + return dev_err_ptr_probe(sec_pmic->dev, -EINVAL, "Unsupported device type %d\n", + sec_pmic->device_type); } regmap_common = dev_get_regmap(sec_pmic->dev, "common"); if (!regmap_common) - return dev_err_probe(sec_pmic->dev, -EINVAL, "No 'common' regmap %d\n", - sec_pmic->device_type); + return dev_err_ptr_probe(sec_pmic->dev, -EINVAL, "No 'common' regmap %d\n", + sec_pmic->device_type); ret = devm_regmap_add_irq_chip(sec_pmic->dev, regmap_common, sec_pmic->irq, IRQF_ONESHOT, 0, irq_chip, &irq_data); if (ret) - return dev_err_probe(sec_pmic->dev, ret, "Failed to add %s IRQ chip\n", - irq_chip->name); + return dev_err_ptr_probe(sec_pmic->dev, ret, "Failed to add %s IRQ chip\n", + irq_chip->name); - return s2mpg1x_add_chained_irq_chip(sec_pmic->dev, sec_pmic->regmap_pmic, chained_pirq, - irq_data, chained_irq_chip, &sec_pmic->irq_data); + return s2mpg1x_add_chained_pmic(sec_pmic, chained_pirq, irq_data, chained_irq_chip); } -int sec_irq_init(struct sec_pmic_dev *sec_pmic) +struct regmap_irq_chip_data *sec_irq_init(struct sec_pmic_dev *sec_pmic) { + struct regmap_irq_chip_data *sec_irq_chip_data; const struct regmap_irq_chip *sec_irq_chip; int ret; @@ -331,7 +333,7 @@ int sec_irq_init(struct sec_pmic_dev *sec_pmic) sec_irq_chip = &s5m8767_irq_chip; break; case S2DOS05: - return 0; + return NULL; case S2MPA01: sec_irq_chip = &s2mps14_irq_chip; break; @@ -356,30 +358,22 @@ int sec_irq_init(struct sec_pmic_dev *sec_pmic) sec_irq_chip = &s2mpu05_irq_chip; break; default: - return dev_err_probe(sec_pmic->dev, -EINVAL, - "Unsupported device type %d\n", - sec_pmic->device_type); + return dev_err_ptr_probe(sec_pmic->dev, -EINVAL, "Unsupported device type %d\n", + sec_pmic->device_type); } if (!sec_pmic->irq) { dev_warn(sec_pmic->dev, "No interrupt specified, no interrupts\n"); - return 0; + return NULL; } ret = devm_regmap_add_irq_chip(sec_pmic->dev, sec_pmic->regmap_pmic, sec_pmic->irq, IRQF_ONESHOT, - 0, sec_irq_chip, &sec_pmic->irq_data); + 0, sec_irq_chip, &sec_irq_chip_data); if (ret) - return dev_err_probe(sec_pmic->dev, ret, - "Failed to add %s IRQ chip\n", - sec_irq_chip->name); + return dev_err_ptr_probe(sec_pmic->dev, ret, "Failed to add %s IRQ chip\n", + sec_irq_chip->name); - /* - * The rtc-s5m driver requests S2MPS14_IRQ_RTCA0 also for S2MPS11 - * so the interrupt number must be consistent. - */ - BUILD_BUG_ON(((enum s2mps14_irq)S2MPS11_IRQ_RTCA0) != S2MPS14_IRQ_RTCA0); - - return 0; + return sec_irq_chip_data; } diff --git a/include/linux/mfd/samsung/core.h b/include/linux/mfd/samsung/core.h index d785e101fe79..c7c3c8cd8d5f 100644 --- a/include/linux/mfd/samsung/core.h +++ b/include/linux/mfd/samsung/core.h @@ -69,7 +69,6 @@ struct sec_pmic_dev { int device_type; int irq; - struct regmap_irq_chip_data *irq_data; }; struct sec_platform_data { -- cgit v1.2.3 From 189ccdc7e8a9b5634b99ad0052749ac4c5442f89 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:31 +0000 Subject: dt-bindings: mfd: samsung,s2mps11: Split s2mpg10-pmic into separate file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The samsung,s2mpg10-pmic binding is going to acquire various additional properties. To avoid making the common samsung,s2mps11 binding file too complicated due to additional nesting, split s2mpg10 out into its own file. As a side-effect, the oneOf for the interrupts is not required anymore, as the required: node is at the top-level now. Signed-off-by: André Draszik Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-4-3b1f9831fffd@linaro.org Signed-off-by: Lee Jones --- .../bindings/mfd/samsung,s2mpg10-pmic.yaml | 69 ++++++++++++++++++++++ .../devicetree/bindings/mfd/samsung,s2mps11.yaml | 29 +-------- 2 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 Documentation/devicetree/bindings/mfd/samsung,s2mpg10-pmic.yaml diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mpg10-pmic.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mpg10-pmic.yaml new file mode 100644 index 000000000000..6475cd1d2d15 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/samsung,s2mpg10-pmic.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/samsung,s2mpg10-pmic.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung S2MPG10 Power Management IC + +maintainers: + - André Draszik + +description: | + This is part of the device tree bindings for the S2MPG family of Power + Management IC (PMIC). + + The Samsung S2MPG10 is a Power Management IC for mobile applications with buck + converters, various LDOs, power meters, RTC, clock outputs, and additional + GPIO interfaces. + +properties: + compatible: + const: samsung,s2mpg10-pmic + + clocks: + $ref: /schemas/clock/samsung,s2mps11.yaml + description: + Child node describing clock provider. + + interrupts: + maxItems: 1 + + regulators: + type: object + description: + List of child nodes that specify the regulators. + + system-power-controller: true + + wakeup-source: true + +required: + - compatible + - interrupts + - regulators + +additionalProperties: false + +examples: + - | + #include + #include + + pmic { + compatible = "samsung,s2mpg10-pmic"; + interrupts-extended = <&gpa0 6 IRQ_TYPE_LEVEL_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&pmic_int>; + system-power-controller; + wakeup-source; + + clocks { + compatible = "samsung,s2mpg10-clk"; + #clock-cells = <1>; + clock-output-names = "rtc32k_ap", "peri32k1", "peri32k2"; + }; + + regulators { + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml index 31d544a9c05c..ac5d0c149796 100644 --- a/Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml +++ b/Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml @@ -20,7 +20,6 @@ description: | properties: compatible: enum: - - samsung,s2mpg10-pmic - samsung,s2mps11-pmic - samsung,s2mps13-pmic - samsung,s2mps14-pmic @@ -59,42 +58,16 @@ properties: reset (setting buck voltages to default values). type: boolean - system-power-controller: true - wakeup-source: true required: - compatible + - reg - regulators additionalProperties: false allOf: - - if: - properties: - compatible: - contains: - const: samsung,s2mpg10-pmic - then: - properties: - reg: false - samsung,s2mps11-acokb-ground: false - samsung,s2mps11-wrstbi-ground: false - - # oneOf is required, because dtschema's fixups.py doesn't handle this - # nesting here. Its special treatment to allow either interrupt property - # when only one is specified in the binding works at the top level only. - oneOf: - - required: [interrupts] - - required: [interrupts-extended] - - else: - properties: - system-power-controller: false - - required: - - reg - - if: properties: compatible: -- cgit v1.2.3 From b356595f8bf4d22646e7800f6b85f63d42de1f31 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:32 +0000 Subject: dt-bindings: mfd: samsung,s2mpg10-pmic: Link to its regulators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the regulators node to link to the correct and expected samsung,s2mpg10-regulators binding, in order to describe the regulators available on this PMIC. Additionally, describe the supply inputs of the regulator rails, with the supply names matching the datasheet. While at it, update the description and example slightly. Note: S2MPG10 is typically used as the main-PMIC together with an S2MPG11 PMIC in a main/sub configuration, hence the datasheet and the binding both suffix the supplies with an 'm'. Signed-off-by: André Draszik Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-5-3b1f9831fffd@linaro.org Signed-off-by: Lee Jones --- .../bindings/mfd/samsung,s2mpg10-pmic.yaml | 57 ++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mpg10-pmic.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mpg10-pmic.yaml index 6475cd1d2d15..0ea1a440b983 100644 --- a/Documentation/devicetree/bindings/mfd/samsung,s2mpg10-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/samsung,s2mpg10-pmic.yaml @@ -10,12 +10,13 @@ maintainers: - André Draszik description: | - This is part of the device tree bindings for the S2MPG family of Power - Management IC (PMIC). + This is part of the device tree bindings for the S2MPG10 Power Management IC + (PMIC). The Samsung S2MPG10 is a Power Management IC for mobile applications with buck converters, various LDOs, power meters, RTC, clock outputs, and additional - GPIO interfaces. + GPIO interfaces and is typically complemented by S2MPG10 PMIC in a main/sub + configuration as the main PMIC. properties: compatible: @@ -31,6 +32,7 @@ properties: regulators: type: object + $ref: /schemas/regulator/samsung,s2mpg10-regulator.yaml description: List of child nodes that specify the regulators. @@ -38,6 +40,32 @@ properties: wakeup-source: true +patternProperties: + "^vinb([1-9]|10)m-supply$": + description: + Phandle to the power supply for each buck rail of this PMIC. There is a + 1:1 mapping of supply to rail, e.g. vinb1m-supply supplies buck1m. + + "^vinl([1-9]|1[0-5])m-supply$": + description: | + Phandle to the power supply for one or multiple LDO rails of this PMIC. + The mapping of supply to rail(s) is as follows: + vinl1m - ldo13m + vinl2m - ldo15m + vinl3m - ldo1m, ldo5m, ldo7m + vinl4m - ldo3m, ldo8m + vinl5m - ldo16m + vinl6m - ldo17m + vinl7m - ldo6m, ldo11m, ldo24m, ldo28m + vinl8m - ldo12m + vinl9m - ldo2m, ldo4m + vinl10m - ldo9m, ldo14m, ldo18m, 19m, ldo20m, ldo25m + vinl11m - ldo23m, ldo31m + vinl12m - ldo29m + vinl13m - ldo30m + vinl14m - ldo21m + vinl15m - ldo10m, ldo22m, ldo26m, ldo27m + required: - compatible - interrupts @@ -49,6 +77,7 @@ examples: - | #include #include + #include pmic { compatible = "samsung,s2mpg10-pmic"; @@ -58,6 +87,8 @@ examples: system-power-controller; wakeup-source; + vinl3m-supply = <&buck8m>; + clocks { compatible = "samsung,s2mpg10-clk"; #clock-cells = <1>; @@ -65,5 +96,25 @@ examples: }; regulators { + buck8m { + regulator-name = "vdd_mif"; + regulator-min-microvolt = <450000>; + regulator-max-microvolt = <1300000>; + regulator-ramp-delay = <6250>; + }; + + ldo1m { + regulator-name = "vdd_ldo1"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <1300000>; + }; + + ldo20m { + regulator-name = "vdd_dmics"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <1300000>; + regulator-always-on; + samsung,ext-control = ; + }; }; }; -- cgit v1.2.3 From bfacd34f8f34edc70c5c7a5fea46fd3c9ec35a5c Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:33 +0000 Subject: dt-bindings: mfd: Add samsung,s2mpg11-pmic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Samsung S2MPG11 PMIC is similar to the existing S2MPG10 PMIC supported by this binding, but still differs enough from it to justify a separate binding. It is a Power Management IC for mobile applications with buck converters, various LDOs, power meters, NTC thermistor inputs, and additional GPIO interfaces and typically complements an S2MPG10 PMIC in a main/sub configuration as the sub-PMIC. Like S2MPG10, communication is via the Samsung ACPM firmware and it therefore needs to be a child of the ACPM firmware node. Add the PMIC, the regulators node, and the supply inputs of the regulator rails, with the supply names matching the datasheet. Note: S2MPG11 is typically used as the sub-PMIC together with an S2MPG10 PMIC in a main/sub configuration, hence the datasheet and the binding both suffix the supplies with an 's'. Signed-off-by: André Draszik Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-6-3b1f9831fffd@linaro.org Signed-off-by: Lee Jones --- .../bindings/mfd/samsung,s2mpg11-pmic.yaml | 88 ++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/samsung,s2mpg11-pmic.yaml diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mpg11-pmic.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mpg11-pmic.yaml new file mode 100644 index 000000000000..62cedbbd9d8c --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/samsung,s2mpg11-pmic.yaml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/samsung,s2mpg11-pmic.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung S2MPG11 Power Management IC + +maintainers: + - André Draszik + +description: | + This is part of the device tree bindings for the S2MPG11 Power Management IC + (PMIC). + + The Samsung S2MPG11 is a Power Management IC for mobile applications with buck + converters, various LDOs, power meters, NTC thermistor inputs, and additional + GPIO interfaces and typically complements an S2MPG10 PMIC in a main/sub + configuration as the sub-PMIC. + +properties: + compatible: + const: samsung,s2mpg11-pmic + + interrupts: + maxItems: 1 + + regulators: + type: object + $ref: /schemas/regulator/samsung,s2mpg11-regulator.yaml + description: + List of child nodes that specify the regulators. + + wakeup-source: true + +patternProperties: + "^vinb(([1-9]|10)s|[abd])-supply$": + description: + Phandle to the power supply for each buck rail of this PMIC. There is a + 1:1 mapping of numbered supply to rail, e.g. vinb1s-supply supplies + buck1s. The remaining mapping is as follows + vinba - bucka + vinbb - buck boost + vinbd - buckd + + "^vinl[1-6]s-supply$": + description: | + Phandle to the power supply for one or multiple LDO rails of this PMIC. + The mapping of supply to rail(s) is as follows + vinl1s - ldo1s, ldo2s + vinl2s - ldo8s, ldo9s + vinl3s - ldo3s, ldo5s, ldo7s, ldo15s + vinl4s - ldo10s, ldo11s, ldo12s, ldo14s + vinl5s - ldo4s, ldo6s + vinl6s - ldo13s + +required: + - compatible + - interrupts + - regulators + +additionalProperties: false + +examples: + - | + #include + #include + #include + + pmic { + compatible = "samsung,s2mpg11-pmic"; + interrupts-extended = <&gpa0 7 IRQ_TYPE_LEVEL_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&pmic_int>; + wakeup-source; + + vinl1s-supply = <&buck8m>; + vinl2s-supply = <&buck6s>; + + regulators { + buckd { + regulator-name = "vcc_ufs"; + regulator-ramp-delay = <6250>; + enable-gpios = <&gpp0 1 GPIO_ACTIVE_HIGH>; + samsung,ext-control = ; + }; + }; + }; -- cgit v1.2.3 From fa72a842734272e295e6804df75131acde2d6e2d Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:35 +0000 Subject: mfd: sec: s2mpg10: Reorder regulators for better probe performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bucks can reasonably be supplies for LDOs, but not the other way around. Since rail registration is going to be ordered by 'enum s2mpg10_regulators', it makes sense to specify bucks first, so that during LDO registration it is more likely that the corresponding supply is known already. This can improve probe speed, as no unnecessary deferrals and retries are required anymore. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-8-3b1f9831fffd@linaro.org Signed-off-by: Lee Jones --- include/linux/mfd/samsung/s2mpg10.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/linux/mfd/samsung/s2mpg10.h b/include/linux/mfd/samsung/s2mpg10.h index 9f5919b89a3c..aec248c51f36 100644 --- a/include/linux/mfd/samsung/s2mpg10.h +++ b/include/linux/mfd/samsung/s2mpg10.h @@ -407,6 +407,16 @@ enum s2mpg10_meter_reg { /* S2MPG10 regulator IDs */ enum s2mpg10_regulators { + S2MPG10_BUCK1, + S2MPG10_BUCK2, + S2MPG10_BUCK3, + S2MPG10_BUCK4, + S2MPG10_BUCK5, + S2MPG10_BUCK6, + S2MPG10_BUCK7, + S2MPG10_BUCK8, + S2MPG10_BUCK9, + S2MPG10_BUCK10, S2MPG10_LDO1, S2MPG10_LDO2, S2MPG10_LDO3, @@ -438,16 +448,6 @@ enum s2mpg10_regulators { S2MPG10_LDO29, S2MPG10_LDO30, S2MPG10_LDO31, - S2MPG10_BUCK1, - S2MPG10_BUCK2, - S2MPG10_BUCK3, - S2MPG10_BUCK4, - S2MPG10_BUCK5, - S2MPG10_BUCK6, - S2MPG10_BUCK7, - S2MPG10_BUCK8, - S2MPG10_BUCK9, - S2MPG10_BUCK10, S2MPG10_REGULATOR_MAX, }; -- cgit v1.2.3 From 3a17ba6557e28d5d99b7e3cad31f22ad28a36cc2 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:36 +0000 Subject: mfd: sec: Add support for S2MPG11 PMIC via ACPM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for Samsung's S2MPG11 PMIC, which is a Power Management IC for mobile applications with buck converters, various LDOs, power meters, NTC thermistor inputs, and additional GPIO interfaces. It typically complements an S2MPG10 PMIC in a main/sub configuration as the sub-PMIC. Like S2MPG10, communication is not via I2C, but via the Samsung ACPM firmware. While at it, we can also switch to asynchronous probe, which helps with probe performance, as the drivers for s2mpg10 and s2mpg11 can probe in parallel. Note: The firmware uses the ACPM channel ID and the Speedy channel ID to select the PMIC address. Since these are firmware properties, they can not be retrieved from DT, but instead are deducted from the compatible for now. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-9-3b1f9831fffd@linaro.org Signed-off-by: Lee Jones --- drivers/mfd/sec-acpm.c | 171 +++++++++++++- drivers/mfd/sec-common.c | 17 +- drivers/mfd/sec-irq.c | 86 ++++++- include/linux/mfd/samsung/core.h | 1 + include/linux/mfd/samsung/irq.h | 105 +++++++++ include/linux/mfd/samsung/s2mpg11.h | 434 ++++++++++++++++++++++++++++++++++++ 6 files changed, 808 insertions(+), 6 deletions(-) create mode 100644 include/linux/mfd/samsung/s2mpg11.h diff --git a/drivers/mfd/sec-acpm.c b/drivers/mfd/sec-acpm.c index 36622069a788..537ea65685bf 100644 --- a/drivers/mfd/sec-acpm.c +++ b/drivers/mfd/sec-acpm.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -216,6 +217,155 @@ static const struct regmap_config s2mpg10_regmap_config_meter = { .cache_type = REGCACHE_FLAT, }; +static const struct regmap_range s2mpg11_common_registers[] = { + regmap_reg_range(0x00, 0x02), /* CHIP_ID_S, INT, INT_MASK */ + regmap_reg_range(0x0a, 0x0c), /* Speedy control */ + regmap_reg_range(0x1a, 0x27), /* Debug */ +}; + +static const struct regmap_range s2mpg11_common_ro_registers[] = { + regmap_reg_range(0x00, 0x01), /* CHIP_ID_S, INT */ + regmap_reg_range(0x25, 0x27), /* Debug */ +}; + +static const struct regmap_range s2mpg11_common_nonvolatile_registers[] = { + regmap_reg_range(0x00, 0x00), /* CHIP_ID_S */ + regmap_reg_range(0x02, 0x02), /* INT_MASK */ + regmap_reg_range(0x0a, 0x0c), /* Speedy control */ +}; + +static const struct regmap_range s2mpg11_common_precious_registers[] = { + regmap_reg_range(0x01, 0x01), /* INT */ +}; + +static const struct regmap_access_table s2mpg11_common_wr_table = { + .yes_ranges = s2mpg11_common_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_common_registers), + .no_ranges = s2mpg11_common_ro_registers, + .n_no_ranges = ARRAY_SIZE(s2mpg11_common_ro_registers), +}; + +static const struct regmap_access_table s2mpg11_common_rd_table = { + .yes_ranges = s2mpg11_common_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_common_registers), +}; + +static const struct regmap_access_table s2mpg11_common_volatile_table = { + .no_ranges = s2mpg11_common_nonvolatile_registers, + .n_no_ranges = ARRAY_SIZE(s2mpg11_common_nonvolatile_registers), +}; + +static const struct regmap_access_table s2mpg11_common_precious_table = { + .yes_ranges = s2mpg11_common_precious_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_common_precious_registers), +}; + +static const struct regmap_config s2mpg11_regmap_config_common = { + .name = "common", + .reg_bits = ACPM_ADDR_BITS, + .val_bits = 8, + .max_register = S2MPG11_COMMON_SPD_DEBUG4, + .wr_table = &s2mpg11_common_wr_table, + .rd_table = &s2mpg11_common_rd_table, + .volatile_table = &s2mpg11_common_volatile_table, + .precious_table = &s2mpg11_common_precious_table, + .num_reg_defaults_raw = S2MPG11_COMMON_SPD_DEBUG4 + 1, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_range s2mpg11_pmic_registers[] = { + regmap_reg_range(0x00, 0x5a), /* All PMIC registers */ + regmap_reg_range(0x5c, 0xb7), /* All PMIC registers */ +}; + +static const struct regmap_range s2mpg11_pmic_ro_registers[] = { + regmap_reg_range(0x00, 0x05), /* INTx */ + regmap_reg_range(0x0c, 0x0d), /* STATUS OFFSRC */ + regmap_reg_range(0x98, 0x98), /* GPIO input */ +}; + +static const struct regmap_range s2mpg11_pmic_nonvolatile_registers[] = { + regmap_reg_range(0x06, 0x0b), /* INTxM */ +}; + +static const struct regmap_range s2mpg11_pmic_precious_registers[] = { + regmap_reg_range(0x00, 0x05), /* INTx */ +}; + +static const struct regmap_access_table s2mpg11_pmic_wr_table = { + .yes_ranges = s2mpg11_pmic_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_pmic_registers), + .no_ranges = s2mpg11_pmic_ro_registers, + .n_no_ranges = ARRAY_SIZE(s2mpg11_pmic_ro_registers), +}; + +static const struct regmap_access_table s2mpg11_pmic_rd_table = { + .yes_ranges = s2mpg11_pmic_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_pmic_registers), +}; + +static const struct regmap_access_table s2mpg11_pmic_volatile_table = { + .no_ranges = s2mpg11_pmic_nonvolatile_registers, + .n_no_ranges = ARRAY_SIZE(s2mpg11_pmic_nonvolatile_registers), +}; + +static const struct regmap_access_table s2mpg11_pmic_precious_table = { + .yes_ranges = s2mpg11_pmic_precious_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_pmic_precious_registers), +}; + +static const struct regmap_config s2mpg11_regmap_config_pmic = { + .name = "pmic", + .reg_bits = ACPM_ADDR_BITS, + .val_bits = 8, + .max_register = S2MPG11_PMIC_LDO_SENSE2, + .wr_table = &s2mpg11_pmic_wr_table, + .rd_table = &s2mpg11_pmic_rd_table, + .volatile_table = &s2mpg11_pmic_volatile_table, + .precious_table = &s2mpg11_pmic_precious_table, + .num_reg_defaults_raw = S2MPG11_PMIC_LDO_SENSE2 + 1, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_range s2mpg11_meter_registers[] = { + regmap_reg_range(0x00, 0x3e), /* Meter config */ + regmap_reg_range(0x40, 0x8a), /* Meter data */ + regmap_reg_range(0x8d, 0x9c), /* Meter data */ +}; + +static const struct regmap_range s2mpg11_meter_ro_registers[] = { + regmap_reg_range(0x40, 0x9c), /* Meter data */ +}; + +static const struct regmap_access_table s2mpg11_meter_wr_table = { + .yes_ranges = s2mpg11_meter_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_meter_registers), + .no_ranges = s2mpg11_meter_ro_registers, + .n_no_ranges = ARRAY_SIZE(s2mpg11_meter_ro_registers), +}; + +static const struct regmap_access_table s2mpg11_meter_rd_table = { + .yes_ranges = s2mpg11_meter_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_meter_registers), +}; + +static const struct regmap_access_table s2mpg11_meter_volatile_table = { + .yes_ranges = s2mpg11_meter_ro_registers, + .n_yes_ranges = ARRAY_SIZE(s2mpg11_meter_ro_registers), +}; + +static const struct regmap_config s2mpg11_regmap_config_meter = { + .name = "meter", + .reg_bits = ACPM_ADDR_BITS, + .val_bits = 8, + .max_register = S2MPG11_METER_LPF_DATA_NTC7_2, + .wr_table = &s2mpg11_meter_wr_table, + .rd_table = &s2mpg11_meter_rd_table, + .volatile_table = &s2mpg11_meter_volatile_table, + .num_reg_defaults_raw = S2MPG11_METER_LPF_DATA_NTC7_2 + 1, + .cache_type = REGCACHE_FLAT, +}; + struct sec_pmic_acpm_shared_bus_context { const struct acpm_handle *acpm; unsigned int acpm_chan_id; @@ -364,10 +514,12 @@ static int sec_pmic_acpm_probe(struct platform_device *pdev) if (IS_ERR(regmap_pmic)) return PTR_ERR(regmap_pmic); - regmap = sec_pmic_acpm_regmap_init(dev, shared_ctx, SEC_PMIC_ACPM_ACCESSTYPE_RTC, - pdata->regmap_cfg_rtc, true); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); + if (pdata->regmap_cfg_rtc) { + regmap = sec_pmic_acpm_regmap_init(dev, shared_ctx, SEC_PMIC_ACPM_ACCESSTYPE_RTC, + pdata->regmap_cfg_rtc, true); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + } regmap = sec_pmic_acpm_regmap_init(dev, shared_ctx, SEC_PMIC_ACPM_ACCESSTYPE_METER, pdata->regmap_cfg_meter, true); @@ -399,8 +551,18 @@ static const struct sec_pmic_acpm_platform_data s2mpg10_data = { .regmap_cfg_meter = &s2mpg10_regmap_config_meter, }; +static const struct sec_pmic_acpm_platform_data s2mpg11_data = { + .device_type = S2MPG11, + .acpm_chan_id = 2, + .speedy_channel = 1, + .regmap_cfg_common = &s2mpg11_regmap_config_common, + .regmap_cfg_pmic = &s2mpg11_regmap_config_pmic, + .regmap_cfg_meter = &s2mpg11_regmap_config_meter, +}; + static const struct of_device_id sec_pmic_acpm_of_match[] = { { .compatible = "samsung,s2mpg10-pmic", .data = &s2mpg10_data, }, + { .compatible = "samsung,s2mpg11-pmic", .data = &s2mpg11_data, }, { }, }; MODULE_DEVICE_TABLE(of, sec_pmic_acpm_of_match); @@ -408,6 +570,7 @@ MODULE_DEVICE_TABLE(of, sec_pmic_acpm_of_match); static struct platform_driver sec_pmic_acpm_driver = { .driver = { .name = "sec-pmic-acpm", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, .pm = pm_sleep_ptr(&sec_pmic_pm_ops), .of_match_table = sec_pmic_acpm_of_match, }, diff --git a/drivers/mfd/sec-common.c b/drivers/mfd/sec-common.c index 0021f9ae8484..bd8b5f968689 100644 --- a/drivers/mfd/sec-common.c +++ b/drivers/mfd/sec-common.c @@ -49,6 +49,12 @@ static const struct mfd_cell s2mpg10_devs[] = { MFD_CELL_OF("s2mpg10-gpio", NULL, NULL, 0, 0, "samsung,s2mpg10-gpio"), }; +static const struct mfd_cell s2mpg11_devs[] = { + MFD_CELL_NAME("s2mpg11-meter"), + MFD_CELL_NAME("s2mpg11-regulator"), + MFD_CELL_OF("s2mpg11-gpio", NULL, NULL, 0, 0, "samsung,s2mpg11-gpio"), +}; + static const struct resource s2mps11_rtc_resources[] = { DEFINE_RES_IRQ_NAMED(S2MPS11_IRQ_RTCA0, "alarm"), }; @@ -104,8 +110,13 @@ static void sec_pmic_dump_rev(struct sec_pmic_dev *sec_pmic) unsigned int val; /* For s2mpg1x, the revision is in a different regmap */ - if (sec_pmic->device_type == S2MPG10) + switch (sec_pmic->device_type) { + case S2MPG10: + case S2MPG11: return; + default: + break; + } /* For each device type, the REG_ID is always the first register */ if (!regmap_read(sec_pmic->regmap_pmic, S2MPS11_REG_ID, &val)) @@ -211,6 +222,10 @@ int sec_pmic_probe(struct device *dev, int device_type, unsigned int irq, sec_devs = s2mpg10_devs; num_sec_devs = ARRAY_SIZE(s2mpg10_devs); break; + case S2MPG11: + sec_devs = s2mpg11_devs; + num_sec_devs = ARRAY_SIZE(s2mpg11_devs); + break; case S2MPS11X: sec_devs = s2mps11_devs; num_sec_devs = ARRAY_SIZE(s2mps11_devs); diff --git a/drivers/mfd/sec-irq.c b/drivers/mfd/sec-irq.c index e0dd122e8fe5..ee722fa35f3b 100644 --- a/drivers/mfd/sec-irq.c +++ b/drivers/mfd/sec-irq.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,64 @@ static const struct regmap_irq s2mpg10_pmic_irqs[] = { REGMAP_IRQ_REG(S2MPG10_IRQ_PWR_WARN_CH7, 5, S2MPG10_IRQ_PWR_WARN_CH7_MASK), }; +static const struct regmap_irq s2mpg11_irqs[] = { + REGMAP_IRQ_REG(S2MPG11_COMMON_IRQ_PMIC, 0, S2MPG11_COMMON_INT_SRC_PMIC), + /* No documentation or other reference for remaining bits */ + REGMAP_IRQ_REG(S2MPG11_COMMON_IRQ_UNUSED, 0, GENMASK(7, 1)), +}; + +static const struct regmap_irq s2mpg11_pmic_irqs[] = { + REGMAP_IRQ_REG(S2MPG11_IRQ_PWRONF, 0, S2MPG11_IRQ_PWRONF_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PWRONR, 0, S2MPG11_IRQ_PWRONR_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PIF_TIMEOUT_MIF, 0, S2MPG11_IRQ_PIF_TIMEOUT_MIF_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PIF_TIMEOUTS, 0, S2MPG11_IRQ_PIF_TIMEOUTS_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_WTSR, 0, S2MPG11_IRQ_WTSR_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_SPD_ABNORMAL_STOP, 0, S2MPG11_IRQ_SPD_ABNORMAL_STOP_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_SPD_PARITY_ERR, 0, S2MPG11_IRQ_SPD_PARITY_ERR_MASK), + + REGMAP_IRQ_REG(S2MPG11_IRQ_140C, 1, S2MPG11_IRQ_INT140C_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_120C, 1, S2MPG11_IRQ_INT120C_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_TSD, 1, S2MPG11_IRQ_TSD_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_WRST, 1, S2MPG11_IRQ_WRST_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_CYCLE_DONE, 1, S2MPG11_IRQ_NTC_CYCLE_DONE_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PMETER_OVERF, 1, S2MPG11_IRQ_PMETER_OVERF_MASK), + + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B1S, 2, S2MPG11_IRQ_OCP_B1S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B2S, 2, S2MPG11_IRQ_OCP_B2S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B3S, 2, S2MPG11_IRQ_OCP_B3S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B4S, 2, S2MPG11_IRQ_OCP_B4S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B5S, 2, S2MPG11_IRQ_OCP_B5S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B6S, 2, S2MPG11_IRQ_OCP_B6S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B7S, 2, S2MPG11_IRQ_OCP_B7S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B8S, 2, S2MPG11_IRQ_OCP_B8S_MASK), + + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B9S, 3, S2MPG11_IRQ_OCP_B9S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_B10S, 3, S2MPG11_IRQ_OCP_B10S_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_BDS, 3, S2MPG11_IRQ_OCP_BDS_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_BAS, 3, S2MPG11_IRQ_OCP_BAS_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_OCP_BBS, 3, S2MPG11_IRQ_OCP_BBS_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_WLWP_ACC, 3, S2MPG11_IRQ_WLWP_ACC_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_SPD_SRP_PKT_RST, 3, S2MPG11_IRQ_SPD_SRP_PKT_RST_MASK), + + REGMAP_IRQ_REG(S2MPG11_IRQ_PWR_WARN_CH0, 4, S2MPG11_IRQ_PWR_WARN_CH0_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PWR_WARN_CH1, 4, S2MPG11_IRQ_PWR_WARN_CH1_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PWR_WARN_CH2, 4, S2MPG11_IRQ_PWR_WARN_CH2_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PWR_WARN_CH3, 4, S2MPG11_IRQ_PWR_WARN_CH3_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PWR_WARN_CH4, 4, S2MPG11_IRQ_PWR_WARN_CH4_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PWR_WARN_CH5, 4, S2MPG11_IRQ_PWR_WARN_CH5_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PWR_WARN_CH6, 4, S2MPG11_IRQ_PWR_WARN_CH6_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_PWR_WARN_CH7, 4, S2MPG11_IRQ_PWR_WARN_CH7_MASK), + + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_WARN_CH0, 5, S2MPG11_IRQ_NTC_WARN_CH0_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_WARN_CH1, 5, S2MPG11_IRQ_NTC_WARN_CH1_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_WARN_CH2, 5, S2MPG11_IRQ_NTC_WARN_CH2_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_WARN_CH3, 5, S2MPG11_IRQ_NTC_WARN_CH3_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_WARN_CH4, 5, S2MPG11_IRQ_NTC_WARN_CH4_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_WARN_CH5, 5, S2MPG11_IRQ_NTC_WARN_CH5_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_WARN_CH6, 5, S2MPG11_IRQ_NTC_WARN_CH6_MASK), + REGMAP_IRQ_REG(S2MPG11_IRQ_NTC_WARN_CH7, 5, S2MPG11_IRQ_NTC_WARN_CH7_MASK), +}; + static const struct regmap_irq s2mps11_irqs[] = { REGMAP_IRQ_REG(S2MPS11_IRQ_PWRONF, 0, S2MPS11_IRQ_PWRONF_MASK), REGMAP_IRQ_REG(S2MPS11_IRQ_PWRONR, 0, S2MPS11_IRQ_PWRONR_MASK), @@ -186,7 +245,7 @@ static const struct regmap_irq s5m8767_irqs[] = { REGMAP_IRQ_REG(S5M8767_IRQ_WTSR, 2, S5M8767_IRQ_WTSR_MASK), }; -/* All S2MPG10 interrupt sources are read-only and don't require clearing */ +/* All S2MPG1x interrupt sources are read-only and don't require clearing */ static const struct regmap_irq_chip s2mpg10_irq_chip = { .name = "s2mpg10", .status_base = S2MPG10_COMMON_INT, @@ -205,6 +264,25 @@ static const struct regmap_irq_chip s2mpg10_irq_chip_pmic = { .num_irqs = ARRAY_SIZE(s2mpg10_pmic_irqs), }; +static const struct regmap_irq_chip s2mpg11_irq_chip = { + .name = "s2mpg11", + .status_base = S2MPG11_COMMON_INT, + .mask_base = S2MPG11_COMMON_INT_MASK, + .num_regs = 1, + .irqs = s2mpg11_irqs, + .num_irqs = ARRAY_SIZE(s2mpg11_irqs), +}; + +static const struct regmap_irq_chip s2mpg11_irq_chip_pmic = { + .name = "s2mpg11-pmic", + .domain_suffix = "pmic", + .status_base = S2MPG11_PMIC_INT1, + .mask_base = S2MPG11_PMIC_INT1M, + .num_regs = 6, + .irqs = s2mpg11_pmic_irqs, + .num_irqs = ARRAY_SIZE(s2mpg11_pmic_irqs), +}; + static const struct regmap_irq_chip s2mps11_irq_chip = { .name = "s2mps11", .irqs = s2mps11_irqs, @@ -303,6 +381,11 @@ static struct regmap_irq_chip_data *sec_irq_init_s2mpg1x(struct sec_pmic_dev *se chained_irq_chip = &s2mpg10_irq_chip_pmic; chained_pirq = S2MPG10_COMMON_IRQ_PMIC; break; + case S2MPG11: + irq_chip = &s2mpg11_irq_chip; + chained_irq_chip = &s2mpg11_irq_chip_pmic; + chained_pirq = S2MPG11_COMMON_IRQ_PMIC; + break; default: return dev_err_ptr_probe(sec_pmic->dev, -EINVAL, "Unsupported device type %d\n", sec_pmic->device_type); @@ -338,6 +421,7 @@ struct regmap_irq_chip_data *sec_irq_init(struct sec_pmic_dev *sec_pmic) sec_irq_chip = &s2mps14_irq_chip; break; case S2MPG10: + case S2MPG11: return sec_irq_init_s2mpg1x(sec_pmic); case S2MPS11X: sec_irq_chip = &s2mps11_irq_chip; diff --git a/include/linux/mfd/samsung/core.h b/include/linux/mfd/samsung/core.h index c7c3c8cd8d5f..4480c631110a 100644 --- a/include/linux/mfd/samsung/core.h +++ b/include/linux/mfd/samsung/core.h @@ -40,6 +40,7 @@ enum sec_device_type { S2DOS05, S2MPA01, S2MPG10, + S2MPG11, S2MPS11X, S2MPS13X, S2MPS14X, diff --git a/include/linux/mfd/samsung/irq.h b/include/linux/mfd/samsung/irq.h index 8402a5f8e18a..6eab95de6fa8 100644 --- a/include/linux/mfd/samsung/irq.h +++ b/include/linux/mfd/samsung/irq.h @@ -166,6 +166,111 @@ enum s2mpg10_irq { S2MPG10_IRQ_NR, }; +enum s2mpg11_common_irq { + /* Top-level (common) block */ + S2MPG11_COMMON_IRQ_PMIC, + S2MPG11_COMMON_IRQ_UNUSED, +}; + +enum s2mpg11_irq { + /* PMIC */ + S2MPG11_IRQ_PWRONF, + S2MPG11_IRQ_PWRONR, + S2MPG11_IRQ_PIF_TIMEOUT_MIF, + S2MPG11_IRQ_PIF_TIMEOUTS, + S2MPG11_IRQ_WTSR, + S2MPG11_IRQ_SPD_ABNORMAL_STOP, + S2MPG11_IRQ_SPD_PARITY_ERR, +#define S2MPG11_IRQ_PWRONF_MASK BIT(0) +#define S2MPG11_IRQ_PWRONR_MASK BIT(1) +#define S2MPG11_IRQ_PIF_TIMEOUT_MIF_MASK BIT(3) +#define S2MPG11_IRQ_PIF_TIMEOUTS_MASK BIT(4) +#define S2MPG11_IRQ_WTSR_MASK BIT(5) +#define S2MPG11_IRQ_SPD_ABNORMAL_STOP_MASK BIT(6) +#define S2MPG11_IRQ_SPD_PARITY_ERR_MASK BIT(7) + + S2MPG11_IRQ_140C, + S2MPG11_IRQ_120C, + S2MPG11_IRQ_TSD, + S2MPG11_IRQ_WRST, + S2MPG11_IRQ_NTC_CYCLE_DONE, + S2MPG11_IRQ_PMETER_OVERF, +#define S2MPG11_IRQ_INT140C_MASK BIT(0) +#define S2MPG11_IRQ_INT120C_MASK BIT(1) +#define S2MPG11_IRQ_TSD_MASK BIT(2) +#define S2MPG11_IRQ_WRST_MASK BIT(5) +#define S2MPG11_IRQ_NTC_CYCLE_DONE_MASK BIT(6) +#define S2MPG11_IRQ_PMETER_OVERF_MASK BIT(7) + + S2MPG11_IRQ_OCP_B1S, + S2MPG11_IRQ_OCP_B2S, + S2MPG11_IRQ_OCP_B3S, + S2MPG11_IRQ_OCP_B4S, + S2MPG11_IRQ_OCP_B5S, + S2MPG11_IRQ_OCP_B6S, + S2MPG11_IRQ_OCP_B7S, + S2MPG11_IRQ_OCP_B8S, +#define S2MPG11_IRQ_OCP_B1S_MASK BIT(0) +#define S2MPG11_IRQ_OCP_B2S_MASK BIT(1) +#define S2MPG11_IRQ_OCP_B3S_MASK BIT(2) +#define S2MPG11_IRQ_OCP_B4S_MASK BIT(3) +#define S2MPG11_IRQ_OCP_B5S_MASK BIT(4) +#define S2MPG11_IRQ_OCP_B6S_MASK BIT(5) +#define S2MPG11_IRQ_OCP_B7S_MASK BIT(6) +#define S2MPG11_IRQ_OCP_B8S_MASK BIT(7) + + S2MPG11_IRQ_OCP_B9S, + S2MPG11_IRQ_OCP_B10S, + S2MPG11_IRQ_OCP_BDS, + S2MPG11_IRQ_OCP_BAS, + S2MPG11_IRQ_OCP_BBS, + S2MPG11_IRQ_WLWP_ACC, + S2MPG11_IRQ_SPD_SRP_PKT_RST, +#define S2MPG11_IRQ_OCP_B9S_MASK BIT(0) +#define S2MPG11_IRQ_OCP_B10S_MASK BIT(1) +#define S2MPG11_IRQ_OCP_BDS_MASK BIT(2) +#define S2MPG11_IRQ_OCP_BAS_MASK BIT(3) +#define S2MPG11_IRQ_OCP_BBS_MASK BIT(4) +#define S2MPG11_IRQ_WLWP_ACC_MASK BIT(5) +#define S2MPG11_IRQ_SPD_SRP_PKT_RST_MASK BIT(7) + + S2MPG11_IRQ_PWR_WARN_CH0, + S2MPG11_IRQ_PWR_WARN_CH1, + S2MPG11_IRQ_PWR_WARN_CH2, + S2MPG11_IRQ_PWR_WARN_CH3, + S2MPG11_IRQ_PWR_WARN_CH4, + S2MPG11_IRQ_PWR_WARN_CH5, + S2MPG11_IRQ_PWR_WARN_CH6, + S2MPG11_IRQ_PWR_WARN_CH7, +#define S2MPG11_IRQ_PWR_WARN_CH0_MASK BIT(0) +#define S2MPG11_IRQ_PWR_WARN_CH1_MASK BIT(1) +#define S2MPG11_IRQ_PWR_WARN_CH2_MASK BIT(2) +#define S2MPG11_IRQ_PWR_WARN_CH3_MASK BIT(3) +#define S2MPG11_IRQ_PWR_WARN_CH4_MASK BIT(4) +#define S2MPG11_IRQ_PWR_WARN_CH5_MASK BIT(5) +#define S2MPG11_IRQ_PWR_WARN_CH6_MASK BIT(6) +#define S2MPG11_IRQ_PWR_WARN_CH7_MASK BIT(7) + + S2MPG11_IRQ_NTC_WARN_CH0, + S2MPG11_IRQ_NTC_WARN_CH1, + S2MPG11_IRQ_NTC_WARN_CH2, + S2MPG11_IRQ_NTC_WARN_CH3, + S2MPG11_IRQ_NTC_WARN_CH4, + S2MPG11_IRQ_NTC_WARN_CH5, + S2MPG11_IRQ_NTC_WARN_CH6, + S2MPG11_IRQ_NTC_WARN_CH7, +#define S2MPG11_IRQ_NTC_WARN_CH0_MASK BIT(0) +#define S2MPG11_IRQ_NTC_WARN_CH1_MASK BIT(1) +#define S2MPG11_IRQ_NTC_WARN_CH2_MASK BIT(2) +#define S2MPG11_IRQ_NTC_WARN_CH3_MASK BIT(3) +#define S2MPG11_IRQ_NTC_WARN_CH4_MASK BIT(4) +#define S2MPG11_IRQ_NTC_WARN_CH5_MASK BIT(5) +#define S2MPG11_IRQ_NTC_WARN_CH6_MASK BIT(6) +#define S2MPG11_IRQ_NTC_WARN_CH7_MASK BIT(7) + + S2MPG11_IRQ_NR, +}; + enum s2mps11_irq { S2MPS11_IRQ_PWRONF, S2MPS11_IRQ_PWRONR, diff --git a/include/linux/mfd/samsung/s2mpg11.h b/include/linux/mfd/samsung/s2mpg11.h new file mode 100644 index 000000000000..66daa3bafa6e --- /dev/null +++ b/include/linux/mfd/samsung/s2mpg11.h @@ -0,0 +1,434 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2015 Samsung Electronics + * Copyright 2020 Google Inc + * Copyright 2025 Linaro Ltd. + */ + +#ifndef __LINUX_MFD_S2MPG11_H +#define __LINUX_MFD_S2MPG11_H + +/* Common registers (type 0x000) */ +enum s2mpg11_common_reg { + S2MPG11_COMMON_CHIPID, + S2MPG11_COMMON_INT, + S2MPG11_COMMON_INT_MASK, + S2MPG11_COMMON_SPD_CTRL1 = 0x0a, + S2MPG11_COMMON_SPD_CTRL2, + S2MPG11_COMMON_SPD_CTRL3, + S2MPG11_COMMON_MON1SEL = 0x1a, + S2MPG11_COMMON_MON2SEL, + S2MPG11_COMMON_MONR, + S2MPG11_COMMON_DEBUG_CTRL1, + S2MPG11_COMMON_DEBUG_CTRL2, + S2MPG11_COMMON_DEBUG_CTRL3, + S2MPG11_COMMON_DEBUG_CTRL4, + S2MPG11_COMMON_DEBUG_CTRL5, + S2MPG11_COMMON_DEBUG_CTRL6, + S2MPG11_COMMON_TEST_MODE1, + S2MPG11_COMMON_SPD_DEBUG1, + S2MPG11_COMMON_SPD_DEBUG2, + S2MPG11_COMMON_SPD_DEBUG3, + S2MPG11_COMMON_SPD_DEBUG4, +}; + +/* For S2MPG11_COMMON_INT and S2MPG11_COMMON_INT_MASK */ +#define S2MPG11_COMMON_INT_SRC GENMASK(2, 0) +#define S2MPG11_COMMON_INT_SRC_PMIC BIT(0) + +/* PMIC registers (type 0x100) */ +enum s2mpg11_pmic_reg { + S2MPG11_PMIC_INT1, + S2MPG11_PMIC_INT2, + S2MPG11_PMIC_INT3, + S2MPG11_PMIC_INT4, + S2MPG11_PMIC_INT5, + S2MPG11_PMIC_INT6, + S2MPG11_PMIC_INT1M, + S2MPG11_PMIC_INT2M, + S2MPG11_PMIC_INT3M, + S2MPG11_PMIC_INT4M, + S2MPG11_PMIC_INT5M, + S2MPG11_PMIC_INT6M, + S2MPG11_PMIC_STATUS1, + S2MPG11_PMIC_OFFSRC, + S2MPG11_PMIC_COMMON_CTRL1, + S2MPG11_PMIC_COMMON_CTRL2, + S2MPG11_PMIC_COMMON_CTRL3, + S2MPG11_PMIC_MIMICKING_CTRL, + S2MPG11_PMIC_B1S_CTRL, + S2MPG11_PMIC_B1S_OUT1, + S2MPG11_PMIC_B1S_OUT2, + S2MPG11_PMIC_B2S_CTRL, + S2MPG11_PMIC_B2S_OUT1, + S2MPG11_PMIC_B2S_OUT2, + S2MPG11_PMIC_B3S_CTRL, + S2MPG11_PMIC_B3S_OUT1, + S2MPG11_PMIC_B3S_OUT2, + S2MPG11_PMIC_B4S_CTRL, + S2MPG11_PMIC_B4S_OUT, + S2MPG11_PMIC_B5S_CTRL, + S2MPG11_PMIC_B5S_OUT, + S2MPG11_PMIC_B6S_CTRL, + S2MPG11_PMIC_B6S_OUT1, + S2MPG11_PMIC_B6S_OUT2, + S2MPG11_PMIC_B7S_CTRL, + S2MPG11_PMIC_B7S_OUT1, + S2MPG11_PMIC_B7S_OUT2, + S2MPG11_PMIC_B8S_CTRL, + S2MPG11_PMIC_B8S_OUT1, + S2MPG11_PMIC_B8S_OUT2, + S2MPG11_PMIC_B9S_CTRL, + S2MPG11_PMIC_B9S_OUT1, + S2MPG11_PMIC_B9S_OUT2, + S2MPG11_PMIC_B10S_CTRL, + S2MPG11_PMIC_B10S_OUT, + S2MPG11_PMIC_BUCKD_CTRL, + S2MPG11_PMIC_BUCKD_OUT, + S2MPG11_PMIC_BUCKA_CTRL, + S2MPG11_PMIC_BUCKA_OUT, + S2MPG11_PMIC_BB_CTRL, + S2MPG11_PMIC_BB_OUT1, + S2MPG11_PMIC_BB_OUT2, + S2MPG11_PMIC_BUCK1S_USONIC, + S2MPG11_PMIC_BUCK2S_USONIC, + S2MPG11_PMIC_BUCK3S_USONIC, + S2MPG11_PMIC_BUCK4S_USONIC, + S2MPG11_PMIC_BUCK5S_USONIC, + S2MPG11_PMIC_BUCK6S_USONIC, + S2MPG11_PMIC_BUCK7S_USONIC, + S2MPG11_PMIC_BUCK8S_USONIC, + S2MPG11_PMIC_BUCK9S_USONIC, + S2MPG11_PMIC_BUCK10S_USONIC, + S2MPG11_PMIC_BUCKD_USONIC, + S2MPG11_PMIC_BUCKA_USONIC, + S2MPG11_PMIC_BB_USONIC, + S2MPG11_PMIC_L1S_CTRL1, + S2MPG11_PMIC_L1S_CTRL2, + S2MPG11_PMIC_L2S_CTRL1, + S2MPG11_PMIC_L2S_CTRL2, + S2MPG11_PMIC_L3S_CTRL, + S2MPG11_PMIC_L4S_CTRL, + S2MPG11_PMIC_L5S_CTRL, + S2MPG11_PMIC_L6S_CTRL, + S2MPG11_PMIC_L7S_CTRL, + S2MPG11_PMIC_L8S_CTRL, + S2MPG11_PMIC_L9S_CTRL, + S2MPG11_PMIC_L10S_CTRL, + S2MPG11_PMIC_L11S_CTRL, + S2MPG11_PMIC_L12S_CTRL, + S2MPG11_PMIC_L13S_CTRL, + S2MPG11_PMIC_L14S_CTRL, + S2MPG11_PMIC_L15S_CTRL, + S2MPG11_PMIC_LDO_CTRL1, + S2MPG11_PMIC_LDO_DSCH1, + S2MPG11_PMIC_LDO_DSCH2, + S2MPG11_PMIC_DVS_RAMP1, + S2MPG11_PMIC_DVS_RAMP2, + S2MPG11_PMIC_DVS_RAMP3, + S2MPG11_PMIC_DVS_RAMP4, + S2MPG11_PMIC_DVS_RAMP5, + S2MPG11_PMIC_DVS_RAMP6, + /* Nothing @ 0x5a */ + S2MPG11_PMIC_DVS_SYNC_CTRL1 = 0x5c, + S2MPG11_PMIC_DVS_SYNC_CTRL2, + S2MPG11_PMIC_OFF_CTRL1, + S2MPG11_PMIC_OFF_CTRL2, + S2MPG11_PMIC_OFF_CTRL3, + S2MPG11_PMIC_SEQ_CTRL1, + S2MPG11_PMIC_SEQ_CTRL2, + S2MPG11_PMIC_SEQ_CTRL3, + S2MPG11_PMIC_SEQ_CTRL4, + S2MPG11_PMIC_SEQ_CTRL5, + S2MPG11_PMIC_SEQ_CTRL6, + S2MPG11_PMIC_SEQ_CTRL7, + S2MPG11_PMIC_SEQ_CTRL8, + S2MPG11_PMIC_SEQ_CTRL9, + S2MPG11_PMIC_SEQ_CTRL10, + S2MPG11_PMIC_SEQ_CTRL11, + S2MPG11_PMIC_SEQ_CTRL12, + S2MPG11_PMIC_SEQ_CTRL13, + S2MPG11_PMIC_SEQ_CTRL14, + S2MPG11_PMIC_SEQ_CTRL15, + S2MPG11_PMIC_SEQ_CTRL16, + S2MPG11_PMIC_SEQ_CTRL17, + S2MPG11_PMIC_SEQ_CTRL18, + S2MPG11_PMIC_SEQ_CTRL19, + S2MPG11_PMIC_SEQ_CTRL20, + S2MPG11_PMIC_SEQ_CTRL21, + S2MPG11_PMIC_SEQ_CTRL22, + S2MPG11_PMIC_SEQ_CTRL23, + S2MPG11_PMIC_SEQ_CTRL24, + S2MPG11_PMIC_SEQ_CTRL25, + S2MPG11_PMIC_SEQ_CTRL26, + S2MPG11_PMIC_SEQ_CTRL27, + S2MPG11_PMIC_OFF_SEQ_CTRL1, + S2MPG11_PMIC_OFF_SEQ_CTRL2, + S2MPG11_PMIC_OFF_SEQ_CTRL3, + S2MPG11_PMIC_OFF_SEQ_CTRL4, + S2MPG11_PMIC_OFF_SEQ_CTRL5, + S2MPG11_PMIC_OFF_SEQ_CTRL6, + S2MPG11_PMIC_OFF_SEQ_CTRL7, + S2MPG11_PMIC_OFF_SEQ_CTRL8, + S2MPG11_PMIC_OFF_SEQ_CTRL9, + S2MPG11_PMIC_OFF_SEQ_CTRL10, + S2MPG11_PMIC_OFF_SEQ_CTRL11, + S2MPG11_PMIC_OFF_SEQ_CTRL12, + S2MPG11_PMIC_OFF_SEQ_CTRL13, + S2MPG11_PMIC_OFF_SEQ_CTRL14, + S2MPG11_PMIC_OFF_SEQ_CTRL15, + S2MPG11_PMIC_OFF_SEQ_CTRL16, + S2MPG11_PMIC_OFF_SEQ_CTRL17, + S2MPG11_PMIC_PCTRLSEL1, + S2MPG11_PMIC_PCTRLSEL2, + S2MPG11_PMIC_PCTRLSEL3, + S2MPG11_PMIC_PCTRLSEL4, + S2MPG11_PMIC_PCTRLSEL5, + S2MPG11_PMIC_PCTRLSEL6, + S2MPG11_PMIC_DCTRLSEL1, + S2MPG11_PMIC_DCTRLSEL2, + S2MPG11_PMIC_DCTRLSEL3, + S2MPG11_PMIC_DCTRLSEL4, + S2MPG11_PMIC_DCTRLSEL5, + S2MPG11_PMIC_GPIO_CTRL1, + S2MPG11_PMIC_GPIO_CTRL2, + S2MPG11_PMIC_GPIO_CTRL3, + S2MPG11_PMIC_GPIO_CTRL4, + S2MPG11_PMIC_GPIO_CTRL5, + S2MPG11_PMIC_GPIO_CTRL6, + S2MPG11_PMIC_GPIO_CTRL7, + S2MPG11_PMIC_B2S_OCP_WARN, + S2MPG11_PMIC_B2S_OCP_WARN_X, + S2MPG11_PMIC_B2S_OCP_WARN_Y, + S2MPG11_PMIC_B2S_OCP_WARN_Z, + S2MPG11_PMIC_B2S_SOFT_OCP_WARN, + S2MPG11_PMIC_B2S_SOFT_OCP_WARN_X, + S2MPG11_PMIC_B2S_SOFT_OCP_WARN_Y, + S2MPG11_PMIC_B2S_SOFT_OCP_WARN_Z, + S2MPG11_PMIC_BUCK_OCP_EN1, + S2MPG11_PMIC_BUCK_OCP_EN2, + S2MPG11_PMIC_BUCK_OCP_PD_EN1, + S2MPG11_PMIC_BUCK_OCP_PD_EN2, + S2MPG11_PMIC_BUCK_OCP_CTRL1, + S2MPG11_PMIC_BUCK_OCP_CTRL2, + S2MPG11_PMIC_BUCK_OCP_CTRL3, + S2MPG11_PMIC_BUCK_OCP_CTRL4, + S2MPG11_PMIC_BUCK_OCP_CTRL5, + S2MPG11_PMIC_BUCK_OCP_CTRL6, + S2MPG11_PMIC_BUCK_OCP_CTRL7, + S2MPG11_PMIC_PIF_CTRL, + S2MPG11_PMIC_BUCK_HR_MODE1, + S2MPG11_PMIC_BUCK_HR_MODE2, + S2MPG11_PMIC_FAULTOUT_CTRL, + S2MPG11_PMIC_LDO_SENSE1, + S2MPG11_PMIC_LDO_SENSE2, +}; + +/* For S2MPG11_PMIC_PCTRLSELx */ +#define S2MPG11_PCTRLSEL_PWREN 0x1 /* PWREN pin */ +#define S2MPG11_PCTRLSEL_PWREN_TRG 0x2 /* PWREN_TRG bit in MIMICKING_CTRL */ +#define S2MPG11_PCTRLSEL_PWREN_MIF 0x3 /* PWREN_MIF pin */ +#define S2MPG11_PCTRLSEL_PWREN_MIF_TRG 0x4 /* PWREN_MIF_TRG bit in MIMICKING_CTRL */ +#define S2MPG11_PCTRLSEL_AP_ACTIVE_N 0x5 /* ~AP_ACTIVE_N pin */ +#define S2MPG11_PCTRLSEL_AP_ACTIVE_N_TRG 0x6 /* ~AP_ACTIVE_N_TRG bit in MIMICKING_CTRL */ +#define S2MPG11_PCTRLSEL_G3D_EN 0x7 /* G3D_EN pin */ +#define S2MPG11_PCTRLSEL_G3D_EN2 0x8 /* G3D_EN & ~AP_ACTIVE_N pins */ +#define S2MPG11_PCTRLSEL_AOC_VDD 0x9 /* AOC_VDD pin */ +#define S2MPG11_PCTRLSEL_AOC_RET 0xa /* AOC_RET pin */ +#define S2MPG11_PCTRLSEL_UFS_EN 0xb /* UFS_EN pin */ +#define S2MPG11_PCTRLSEL_LDO13S_EN 0xc /* VLDO13S_EN pin */ + +/* Meter registers (type 0xa00) */ +enum s2mpg11_meter_reg { + S2MPG11_METER_CTRL1, + S2MPG11_METER_CTRL2, + S2MPG11_METER_CTRL3, + S2MPG11_METER_CTRL4, + S2MPG11_METER_CTRL5, + S2MPG11_METER_BUCKEN1, + S2MPG11_METER_BUCKEN2, + S2MPG11_METER_MUXSEL0, + S2MPG11_METER_MUXSEL1, + S2MPG11_METER_MUXSEL2, + S2MPG11_METER_MUXSEL3, + S2MPG11_METER_MUXSEL4, + S2MPG11_METER_MUXSEL5, + S2MPG11_METER_MUXSEL6, + S2MPG11_METER_MUXSEL7, + S2MPG11_METER_LPF_C0_0, + S2MPG11_METER_LPF_C0_1, + S2MPG11_METER_LPF_C0_2, + S2MPG11_METER_LPF_C0_3, + S2MPG11_METER_LPF_C0_4, + S2MPG11_METER_LPF_C0_5, + S2MPG11_METER_LPF_C0_6, + S2MPG11_METER_LPF_C0_7, + S2MPG11_METER_NTC_LPF_C0_0, + S2MPG11_METER_NTC_LPF_C0_1, + S2MPG11_METER_NTC_LPF_C0_2, + S2MPG11_METER_NTC_LPF_C0_3, + S2MPG11_METER_NTC_LPF_C0_4, + S2MPG11_METER_NTC_LPF_C0_5, + S2MPG11_METER_NTC_LPF_C0_6, + S2MPG11_METER_NTC_LPF_C0_7, + S2MPG11_METER_PWR_WARN0, + S2MPG11_METER_PWR_WARN1, + S2MPG11_METER_PWR_WARN2, + S2MPG11_METER_PWR_WARN3, + S2MPG11_METER_PWR_WARN4, + S2MPG11_METER_PWR_WARN5, + S2MPG11_METER_PWR_WARN6, + S2MPG11_METER_PWR_WARN7, + S2MPG11_METER_NTC_L_WARN0, + S2MPG11_METER_NTC_L_WARN1, + S2MPG11_METER_NTC_L_WARN2, + S2MPG11_METER_NTC_L_WARN3, + S2MPG11_METER_NTC_L_WARN4, + S2MPG11_METER_NTC_L_WARN5, + S2MPG11_METER_NTC_L_WARN6, + S2MPG11_METER_NTC_L_WARN7, + S2MPG11_METER_NTC_H_WARN0, + S2MPG11_METER_NTC_H_WARN1, + S2MPG11_METER_NTC_H_WARN2, + S2MPG11_METER_NTC_H_WARN3, + S2MPG11_METER_NTC_H_WARN4, + S2MPG11_METER_NTC_H_WARN5, + S2MPG11_METER_NTC_H_WARN6, + S2MPG11_METER_NTC_H_WARN7, + S2MPG11_METER_PWR_HYS1, + S2MPG11_METER_PWR_HYS2, + S2MPG11_METER_PWR_HYS3, + S2MPG11_METER_PWR_HYS4, + S2MPG11_METER_NTC_HYS1, + S2MPG11_METER_NTC_HYS2, + S2MPG11_METER_NTC_HYS3, + S2MPG11_METER_NTC_HYS4, + /* Nothing @ 0x3f */ + S2MPG11_METER_ACC_DATA_CH0_1 = 0x40, + S2MPG11_METER_ACC_DATA_CH0_2, + S2MPG11_METER_ACC_DATA_CH0_3, + S2MPG11_METER_ACC_DATA_CH0_4, + S2MPG11_METER_ACC_DATA_CH0_5, + S2MPG11_METER_ACC_DATA_CH0_6, + S2MPG11_METER_ACC_DATA_CH1_1, + S2MPG11_METER_ACC_DATA_CH1_2, + S2MPG11_METER_ACC_DATA_CH1_3, + S2MPG11_METER_ACC_DATA_CH1_4, + S2MPG11_METER_ACC_DATA_CH1_5, + S2MPG11_METER_ACC_DATA_CH1_6, + S2MPG11_METER_ACC_DATA_CH2_1, + S2MPG11_METER_ACC_DATA_CH2_2, + S2MPG11_METER_ACC_DATA_CH2_3, + S2MPG11_METER_ACC_DATA_CH2_4, + S2MPG11_METER_ACC_DATA_CH2_5, + S2MPG11_METER_ACC_DATA_CH2_6, + S2MPG11_METER_ACC_DATA_CH3_1, + S2MPG11_METER_ACC_DATA_CH3_2, + S2MPG11_METER_ACC_DATA_CH3_3, + S2MPG11_METER_ACC_DATA_CH3_4, + S2MPG11_METER_ACC_DATA_CH3_5, + S2MPG11_METER_ACC_DATA_CH3_6, + S2MPG11_METER_ACC_DATA_CH4_1, + S2MPG11_METER_ACC_DATA_CH4_2, + S2MPG11_METER_ACC_DATA_CH4_3, + S2MPG11_METER_ACC_DATA_CH4_4, + S2MPG11_METER_ACC_DATA_CH4_5, + S2MPG11_METER_ACC_DATA_CH4_6, + S2MPG11_METER_ACC_DATA_CH5_1, + S2MPG11_METER_ACC_DATA_CH5_2, + S2MPG11_METER_ACC_DATA_CH5_3, + S2MPG11_METER_ACC_DATA_CH5_4, + S2MPG11_METER_ACC_DATA_CH5_5, + S2MPG11_METER_ACC_DATA_CH5_6, + S2MPG11_METER_ACC_DATA_CH6_1, + S2MPG11_METER_ACC_DATA_CH6_2, + S2MPG11_METER_ACC_DATA_CH6_3, + S2MPG11_METER_ACC_DATA_CH6_4, + S2MPG11_METER_ACC_DATA_CH6_5, + S2MPG11_METER_ACC_DATA_CH6_6, + S2MPG11_METER_ACC_DATA_CH7_1, + S2MPG11_METER_ACC_DATA_CH7_2, + S2MPG11_METER_ACC_DATA_CH7_3, + S2MPG11_METER_ACC_DATA_CH7_4, + S2MPG11_METER_ACC_DATA_CH7_5, + S2MPG11_METER_ACC_DATA_CH7_6, + S2MPG11_METER_ACC_COUNT_1, + S2MPG11_METER_ACC_COUNT_2, + S2MPG11_METER_ACC_COUNT_3, + S2MPG11_METER_LPF_DATA_CH0_1, + S2MPG11_METER_LPF_DATA_CH0_2, + S2MPG11_METER_LPF_DATA_CH0_3, + S2MPG11_METER_LPF_DATA_CH1_1, + S2MPG11_METER_LPF_DATA_CH1_2, + S2MPG11_METER_LPF_DATA_CH1_3, + S2MPG11_METER_LPF_DATA_CH2_1, + S2MPG11_METER_LPF_DATA_CH2_2, + S2MPG11_METER_LPF_DATA_CH2_3, + S2MPG11_METER_LPF_DATA_CH3_1, + S2MPG11_METER_LPF_DATA_CH3_2, + S2MPG11_METER_LPF_DATA_CH3_3, + S2MPG11_METER_LPF_DATA_CH4_1, + S2MPG11_METER_LPF_DATA_CH4_2, + S2MPG11_METER_LPF_DATA_CH4_3, + S2MPG11_METER_LPF_DATA_CH5_1, + S2MPG11_METER_LPF_DATA_CH5_2, + S2MPG11_METER_LPF_DATA_CH5_3, + S2MPG11_METER_LPF_DATA_CH6_1, + S2MPG11_METER_LPF_DATA_CH6_2, + S2MPG11_METER_LPF_DATA_CH6_3, + S2MPG11_METER_LPF_DATA_CH7_1, + S2MPG11_METER_LPF_DATA_CH7_2, + S2MPG11_METER_LPF_DATA_CH7_3, + /* Nothing @ 0x8b 0x8c */ + S2MPG11_METER_LPF_DATA_NTC0_1 = 0x8d, + S2MPG11_METER_LPF_DATA_NTC0_2, + S2MPG11_METER_LPF_DATA_NTC1_1, + S2MPG11_METER_LPF_DATA_NTC1_2, + S2MPG11_METER_LPF_DATA_NTC2_1, + S2MPG11_METER_LPF_DATA_NTC2_2, + S2MPG11_METER_LPF_DATA_NTC3_1, + S2MPG11_METER_LPF_DATA_NTC3_2, + S2MPG11_METER_LPF_DATA_NTC4_1, + S2MPG11_METER_LPF_DATA_NTC4_2, + S2MPG11_METER_LPF_DATA_NTC5_1, + S2MPG11_METER_LPF_DATA_NTC5_2, + S2MPG11_METER_LPF_DATA_NTC6_1, + S2MPG11_METER_LPF_DATA_NTC6_2, + S2MPG11_METER_LPF_DATA_NTC7_1, + S2MPG11_METER_LPF_DATA_NTC7_2, +}; + +/* S2MPG11 regulator IDs */ +enum s2mpg11_regulators { + S2MPG11_BUCKBOOST, + S2MPG11_BUCK1, + S2MPG11_BUCK2, + S2MPG11_BUCK3, + S2MPG11_BUCK4, + S2MPG11_BUCK5, + S2MPG11_BUCK6, + S2MPG11_BUCK7, + S2MPG11_BUCK8, + S2MPG11_BUCK9, + S2MPG11_BUCK10, + S2MPG11_BUCKD, + S2MPG11_BUCKA, + S2MPG11_LDO1, + S2MPG11_LDO2, + S2MPG11_LDO3, + S2MPG11_LDO4, + S2MPG11_LDO5, + S2MPG11_LDO6, + S2MPG11_LDO7, + S2MPG11_LDO8, + S2MPG11_LDO9, + S2MPG11_LDO10, + S2MPG11_LDO11, + S2MPG11_LDO12, + S2MPG11_LDO13, + S2MPG11_LDO14, + S2MPG11_LDO15, + S2MPG11_REGULATOR_MAX, +}; + +#endif /* __LINUX_MFD_S2MPG11_H */ -- cgit v1.2.3 From e4691f356b2c24467eadc8b20f267e9e046f74cd Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:28 +0000 Subject: dt-bindings: firmware: google,gs101-acpm-ipc: convert regulators to lowercase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using lowercase for the buck and ldo nodenames is preferred, as evidenced e.g. in [1]. Convert the example here to lowercase before we add any bindings describing the s2mpg1x regulators that will enforce the spelling. Link: https://lore.kernel.org/all/20250223-mysterious-infrared-civet-e5bcbf@krzk-bin/ [1] Acked-by: Rob Herring (Arm) Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-1-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml b/Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml index d3bca6088d12..4a1e3e3c0505 100644 --- a/Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml +++ b/Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml @@ -75,7 +75,7 @@ examples: interrupts-extended = <&gpa0 6 IRQ_TYPE_LEVEL_LOW>; regulators { - LDO1 { + ldo1m { regulator-name = "vdd_ldo1"; regulator-min-microvolt = <700000>; regulator-max-microvolt = <1300000>; @@ -84,7 +84,7 @@ examples: // ... - BUCK1 { + buck8m { regulator-name = "vdd_mif"; regulator-min-microvolt = <450000>; regulator-max-microvolt = <1300000>; -- cgit v1.2.3 From 71bc6adae4102550717a8eeaa21d3f76f5149ac6 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:29 +0000 Subject: regulator: dt-bindings: add s2mpg10-pmic regulators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The S2MPG10 PMIC is a Power Management IC for mobile applications with buck converters, various LDOs, power meters, RTC, clock outputs, and additional GPIO interfaces. It has 10 buck and 31 LDO rails. Several of these can either be controlled via software (register writes) or via external signals, in particular by: * one out of several input pins connected to a main processor's: * GPIO pins * other pins that are e.g. firmware- or power-domain-controlled without explicit driver intervention * a combination of input pins and register writes. Control via input pins allows PMIC rails to be controlled by firmware, e.g. during standby/suspend, or as part of power domain handling where otherwise that would not be possible. Additionally toggling a pin is faster than register writes, and it also allows the PMIC to ensure that any necessary timing requirements between rails are respected automatically if multiple rails are to be enabled or disabled quasi simultaneously. While external control via input pins appears to exist on other versions of this PMIC, there is more flexibility in this version, in particular there is a selection of input pins to choose from for each rail (which must therefore be configured accordingly if in use), whereas other versions don't have this flexibility. Add documentation related to the regulator (buck & ldo) parts like devicetree definitions, regulator naming patterns, and additional properties. S2MPG10 is typically used as the main-PMIC together with an S2MPG11 PMIC in a main/sub configuration, hence the datasheet and the binding both suffix the rails with an 'm'. Reviewed-by: Krzysztof Kozlowski Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-2-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- .../regulator/samsung,s2mpg10-regulator.yaml | 158 +++++++++++++++++++++ MAINTAINERS | 1 + .../regulator/samsung,s2mpg10-regulator.h | 39 +++++ 3 files changed, 198 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/samsung,s2mpg10-regulator.yaml create mode 100644 include/dt-bindings/regulator/samsung,s2mpg10-regulator.h diff --git a/Documentation/devicetree/bindings/regulator/samsung,s2mpg10-regulator.yaml b/Documentation/devicetree/bindings/regulator/samsung,s2mpg10-regulator.yaml new file mode 100644 index 000000000000..7252f94b3a8f --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/samsung,s2mpg10-regulator.yaml @@ -0,0 +1,158 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/samsung,s2mpg10-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung S2MPG10 Power Management IC regulators + +maintainers: + - André Draszik + +description: | + This is part of the device tree bindings for the S2MG10 Power Management IC + (PMIC). + + The S2MPG10 PMIC provides 10 buck and 31 LDO regulators. + + See also Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml for + additional information and example. + +properties: + # 1 LDO with possible (but limited) external control + ldo20m: + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for a single LDO regulator. + + allOf: + - $ref: "#/$defs/s2mpg10-ext-control" + + properties: + regulator-ramp-delay: false + + samsung,ext-control: + minimum: 11 + +patternProperties: + # 10 bucks + "^buck([1-9]|10)m$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for a single buck regulator. + + allOf: + - $ref: "#/$defs/s2mpg10-ext-control" + + properties: + regulator-ramp-delay: + enum: [6250, 12500, 25000] + default: 6250 + + samsung,ext-control: + maximum: 10 + + # 12 standard LDOs + "^ldo(2[1-9]?|3[0-1])m$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for single LDO regulator. + + properties: + regulator-ramp-delay: false + + # 12 LDOs with possible external control + "^ldo([3-689]|1[046-9])m$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for a single LDO regulator. + + allOf: + - $ref: "#/$defs/s2mpg10-ext-control" + + properties: + regulator-ramp-delay: false + + samsung,ext-control: + maximum: 10 + + # 6 LDOs with ramp support, 5 out of those with possible external control + "^ldo(1[1235]?|7)m$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for a single LDO regulator. + + allOf: + - $ref: "#/$defs/s2mpg10-ext-control" + + properties: + regulator-ramp-delay: + enum: [6250, 12500] + default: 6250 + + samsung,ext-control: + maximum: 10 + +$defs: + s2mpg10-ext-control: + properties: + samsung,ext-control: + description: | + These rails can be controlled via one of several possible external + (hardware) signals. If so, this property configures the signal the PMIC + should monitor. For S2MPG10 rails where external control is possible other + than ldo20m, the following values generally corresponding to the + respective on-chip pin are valid: + - 0 # S2MPG10_EXTCTRL_PWREN - PWREN pin + - 1 # S2MPG10_EXTCTRL_PWREN_MIF - PWREN_MIF pin + - 2 # S2MPG10_EXTCTRL_AP_ACTIVE_N - ~AP_ACTIVE_N pin + - 3 # S2MPG10_EXTCTRL_CPUCL1_EN - CPUCL1_EN pin + - 4 # S2MPG10_EXTCTRL_CPUCL1_EN2 - CPUCL1_EN & PWREN pins + - 5 # S2MPG10_EXTCTRL_CPUCL2_EN - CPUCL2_EN pin + - 6 # S2MPG10_EXTCTRL_CPUCL2_EN2 - CPUCL2_E2 & PWREN pins + - 7 # S2MPG10_EXTCTRL_TPU_EN - TPU_EN pin + - 8 # S2MPG10_EXTCTRL_TPU_EN2 - TPU_EN & ~AP_ACTIVE_N pins + - 9 # S2MPG10_EXTCTRL_TCXO_ON - TCXO_ON pin + - 10 # S2MPG10_EXTCTRL_TCXO_ON2 - TCXO_ON & ~AP_ACTIVE_N pins + + For S2MPG10 ldo20m, the following values are valid + - 11 # S2MPG10_EXTCTRL_LDO20M_EN2 - VLDO20M_EN & LDO20M_SFR + - 12 # S2MPG10_EXTCTRL_LDO20M_EN - VLDO20M_EN pin + + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 12 + + enable-gpios: + description: + For rails where external control is done via a GPIO, this optional + property describes the GPIO line used. + + dependentRequired: + enable-gpios: [ "samsung,ext-control" ] + +allOf: + # Bucks 8, 9, and LDO 1 can not be controlled externally - above definition + # allows it and we deny it here. This approach reduces repetition. + - if: + anyOf: + - required: [buck8m] + - required: [buck9m] + - required: [ldo1m] + then: + patternProperties: + "^(buck[8-9]|ldo1)m$": + properties: + samsung,ext-control: false + +additionalProperties: false diff --git a/MAINTAINERS b/MAINTAINERS index 5b11839cba9d..877c1cddb576 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23239,6 +23239,7 @@ F: drivers/mfd/sec*.[ch] F: drivers/regulator/s2*.c F: drivers/regulator/s5m*.c F: drivers/rtc/rtc-s5m.c +F: include/dt-bindings/regulator/samsung,s2m*.h F: include/linux/mfd/samsung/ SAMSUNG S3C24XX/S3C64XX SOC SERIES CAMIF DRIVER diff --git a/include/dt-bindings/regulator/samsung,s2mpg10-regulator.h b/include/dt-bindings/regulator/samsung,s2mpg10-regulator.h new file mode 100644 index 000000000000..4a6bf13442f5 --- /dev/null +++ b/include/dt-bindings/regulator/samsung,s2mpg10-regulator.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright 2021 Google LLC + * Copyright 2025 Linaro Ltd. + * + * Device Tree binding constants for the Samsung S2MPG1x PMIC regulators + */ + +#ifndef _DT_BINDINGS_REGULATOR_SAMSUNG_S2MPG10_H +#define _DT_BINDINGS_REGULATOR_SAMSUNG_S2MPG10_H + +/* + * Several regulators may be controlled via external signals instead of via + * software. These constants describe the possible signals for such regulators + * and generally correspond to the respecitve on-chip pins. + * + * S2MPG10 regulators supporting these are: + * - buck1m .. buck7m buck10m + * - ldo3m .. ldo19m + * + * ldo20m supports external control, but using a different set of control + * signals. + */ +#define S2MPG10_EXTCTRL_PWREN 0 /* PWREN pin */ +#define S2MPG10_EXTCTRL_PWREN_MIF 1 /* PWREN_MIF pin */ +#define S2MPG10_EXTCTRL_AP_ACTIVE_N 2 /* ~AP_ACTIVE_N pin */ +#define S2MPG10_EXTCTRL_CPUCL1_EN 3 /* CPUCL1_EN pin */ +#define S2MPG10_EXTCTRL_CPUCL1_EN2 4 /* CPUCL1_EN & PWREN pins */ +#define S2MPG10_EXTCTRL_CPUCL2_EN 5 /* CPUCL2_EN pin */ +#define S2MPG10_EXTCTRL_CPUCL2_EN2 6 /* CPUCL2_E2 & PWREN pins */ +#define S2MPG10_EXTCTRL_TPU_EN 7 /* TPU_EN pin */ +#define S2MPG10_EXTCTRL_TPU_EN2 8 /* TPU_EN & ~AP_ACTIVE_N pins */ +#define S2MPG10_EXTCTRL_TCXO_ON 9 /* TCXO_ON pin */ +#define S2MPG10_EXTCTRL_TCXO_ON2 10 /* TCXO_ON & ~AP_ACTIVE_N pins */ + +#define S2MPG10_EXTCTRL_LDO20M_EN2 11 /* VLDO20M_EN & LDO20M_SFR */ +#define S2MPG10_EXTCTRL_LDO20M_EN 12 /* VLDO20M_EN pin */ + +#endif /* _DT_BINDINGS_REGULATOR_SAMSUNG_S2MPG10_H */ -- cgit v1.2.3 From 030158c0528d1cbfbe9eebed09bad604f6135734 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:30 +0000 Subject: regulator: dt-bindings: add s2mpg11-pmic regulators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The S2MPG11 PMIC is a Power Management IC for mobile applications with buck converters, various LDOs, power meters, NTC thermistor inputs, and additional GPIO interfaces. It typically complements an S2MPG10 PMIC in a main/sub configuration as the sub-PMIC. S2MPG11 has 12 buck, 1 buck-boost, and 15 LDO rails. Several of these can either be controlled via software (register writes) or via external signals, in particular by: * one out of several input pins connected to a main processor's: * GPIO pins * other pins that are e.g. firmware- or power-domain-controlled without explicit driver intervention * a combination of input pins and register writes. Control via input pins allows PMIC rails to be controlled by firmware, e.g. during standby/suspend, or as part of power domain handling where otherwise that would not be possible. Additionally toggling a pin is faster than register writes, and it also allows the PMIC to ensure that any necessary timing requirements between rails are respected automatically if multiple rails are to be enabled or disabled quasi simultaneously. While external control via input pins appears to exist on other versions of this PMIC, there is more flexibility in this version, in particular there is a selection of input pins to choose from for each rail (which must therefore be configured accordingly if in use), whereas other versions don't have this flexibility. Add documentation related to the regulator (buck & ldo) parts like devicetree definitions, regulator naming patterns, and additional properties. Since S2MPG11 is typically used as the sub-PMIC together with an S2MPG10 as the main-PMIC, the datasheet and the binding both suffix the rails with an 's'. Reviewed-by: Krzysztof Kozlowski Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-3-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- .../regulator/samsung,s2mpg11-regulator.yaml | 136 +++++++++++++++++++++ .../regulator/samsung,s2mpg10-regulator.h | 14 +++ 2 files changed, 150 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/samsung,s2mpg11-regulator.yaml diff --git a/Documentation/devicetree/bindings/regulator/samsung,s2mpg11-regulator.yaml b/Documentation/devicetree/bindings/regulator/samsung,s2mpg11-regulator.yaml new file mode 100644 index 000000000000..119386325d1b --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/samsung,s2mpg11-regulator.yaml @@ -0,0 +1,136 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/samsung,s2mpg11-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung S2MPG11 Power Management IC regulators + +maintainers: + - André Draszik + +description: | + This is part of the device tree bindings for the S2MG11 Power Management IC + (PMIC). + + The S2MPG11 PMIC provides 12 buck, 1 buck-boost, and 15 LDO regulators. + + See also Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml for + additional information and example. + +properties: + buckboost: + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for the buck-boost regulator. + + properties: + regulator-ramp-delay: false + +patternProperties: + # 12 bucks + "^buck(([1-9]|10)s|[ad])$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for a single buck regulator. + + allOf: + - $ref: "#/$defs/s2mpg11-ext-control" + + properties: + regulator-ramp-delay: + enum: [6250, 12500, 25000] + default: 6250 + + # 11 standard LDOs + "^ldo([3-79]|1[01245])s$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for a single LDO regulator. + + properties: + regulator-ramp-delay: false + + # 2 LDOs with possible external control + "^ldo(8|13)s$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for single LDO regulator. + + allOf: + - $ref: "#/$defs/s2mpg11-ext-control" + + properties: + regulator-ramp-delay: false + + # 2 LDOs with ramp support and possible external control + "^ldo[12]s$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for a single LDO regulator. + + allOf: + - $ref: "#/$defs/s2mpg11-ext-control" + + properties: + regulator-ramp-delay: + enum: [6250, 12500] + default: 6250 + +$defs: + s2mpg11-ext-control: + properties: + samsung,ext-control: + description: | + These rails can be controlled via one of several possible external + (hardware) signals. If so, this property configures the signal the PMIC + should monitor. The following values generally corresponding to the + respective on-chip pin are valid: + - 0 # S2MPG11_EXTCTRL_PWREN - PWREN pin + - 1 # S2MPG11_EXTCTRL_PWREN_MIF - PWREN_MIF pin + - 2 # S2MPG11_EXTCTRL_AP_ACTIVE_N - ~AP_ACTIVE_N pin + - 3 # S2MPG11_EXTCTRL_G3D_EN - G3D_EN pin + - 4 # S2MPG11_EXTCTRL_G3D_EN2 - G3D_EN & ~AP_ACTIVE_N pins + - 5 # S2MPG11_EXTCTRL_AOC_VDD - AOC_VDD pin + - 6 # S2MPG11_EXTCTRL_AOC_RET - AOC_RET pin + - 7 # S2MPG11_EXTCTRL_UFS_EN - UFS_EN pin + - 8 # S2MPG11_EXTCTRL_LDO13S_EN - VLDO13S_EN pin + + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 8 + + enable-gpios: + description: + For rails where external control is done via a GPIO, this optional + property describes the GPIO line used. + + dependentRequired: + enable-gpios: [ "samsung,ext-control" ] + +allOf: + # Bucks 4, 6, 7 and 10 can not be controlled externally - above definition + # allows it and we deny it here. This approach reduces repetition. + - if: + anyOf: + - required: [buck4s] + - required: [buck6s] + - required: [buck7s] + - required: [buck10s] + then: + patternProperties: + "^buck([467]|10)s$": + properties: + samsung,ext-control: false + +additionalProperties: false diff --git a/include/dt-bindings/regulator/samsung,s2mpg10-regulator.h b/include/dt-bindings/regulator/samsung,s2mpg10-regulator.h index 4a6bf13442f5..d9c16bba4d85 100644 --- a/include/dt-bindings/regulator/samsung,s2mpg10-regulator.h +++ b/include/dt-bindings/regulator/samsung,s2mpg10-regulator.h @@ -20,6 +20,10 @@ * * ldo20m supports external control, but using a different set of control * signals. + * + * S2MPG11 regulators supporting these are: + * - buck1s .. buck3s buck5s buck8s buck9s bucka buckd + * - ldo1s ldo2s ldo8s ldo13s */ #define S2MPG10_EXTCTRL_PWREN 0 /* PWREN pin */ #define S2MPG10_EXTCTRL_PWREN_MIF 1 /* PWREN_MIF pin */ @@ -36,4 +40,14 @@ #define S2MPG10_EXTCTRL_LDO20M_EN2 11 /* VLDO20M_EN & LDO20M_SFR */ #define S2MPG10_EXTCTRL_LDO20M_EN 12 /* VLDO20M_EN pin */ +#define S2MPG11_EXTCTRL_PWREN 0 /* PWREN pin */ +#define S2MPG11_EXTCTRL_PWREN_MIF 1 /* PWREN_MIF pin */ +#define S2MPG11_EXTCTRL_AP_ACTIVE_N 2 /* ~AP_ACTIVE_N pin */ +#define S2MPG11_EXTCTRL_G3D_EN 3 /* G3D_EN pin */ +#define S2MPG11_EXTCTRL_G3D_EN2 4 /* G3D_EN & ~AP_ACTIVE_N pins */ +#define S2MPG11_EXTCTRL_AOC_VDD 5 /* AOC_VDD pin */ +#define S2MPG11_EXTCTRL_AOC_RET 6 /* AOC_RET pin */ +#define S2MPG11_EXTCTRL_UFS_EN 7 /* UFS_EN pin */ +#define S2MPG11_EXTCTRL_LDO13S_EN 8 /* VLDO13S_EN pin */ + #endif /* _DT_BINDINGS_REGULATOR_SAMSUNG_S2MPG10_H */ -- cgit v1.2.3 From 7d33c0a4c6a3356db2b2f599820baf75d3753d44 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:37 +0000 Subject: regulator: add REGULATOR_LINEAR_VRANGE macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit REGULATOR_LINEAR_VRANGE is similar to REGULATOR_LINEAR_RANGE, but allows a more natural declaration of a voltage range for a regulator, in that it expects the minimum and maximum values as voltages rather than as selectors. Using voltages arguably makes this macro easier to use by drivers and code using it can become easier to read compared to REGULATOR_LINEAR_RANGE. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-10-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- include/linux/regulator/driver.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index 978cf593b662..977755db64c6 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -53,6 +53,11 @@ enum regulator_detection_severity { #define REGULATOR_LINEAR_RANGE(_min_uV, _min_sel, _max_sel, _step_uV) \ LINEAR_RANGE(_min_uV, _min_sel, _max_sel, _step_uV) +/* Initialize struct linear_range using voltages, not selectors */ +#define REGULATOR_LINEAR_VRANGE(_offs_uV, _min_uV, _max_uV, _step_uV) \ + LINEAR_RANGE(_min_uV, ((_min_uV) - (_offs_uV)) / (_step_uV), \ + ((_max_uV) - (_offs_uV)) / (_step_uV), _step_uV) + /** * struct regulator_ops - regulator operations. * -- cgit v1.2.3 From 0809d3dcc0dd8f597adbcd4d881063eb1b437987 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:38 +0000 Subject: regulator: s2mps11: drop two needless variable initialisations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initialisations being removed are needless, as both variables are being assigned values unconditionally further down. Additionally, doing this eager init here might lead to preventing the compiler from issuing a warning if a future code change actually forgets to assign a useful value in some code path. Reviewed-by: Krzysztof Kozlowski Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-11-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 04ae9c6150bd..1f51fbc6c7b6 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -1207,8 +1207,8 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent); struct regulator_config config = { }; struct s2mps11_info *s2mps11; - unsigned int rdev_num = 0; - int i, ret = 0; + unsigned int rdev_num; + int i, ret; const struct regulator_desc *regulators; s2mps11 = devm_kzalloc(&pdev->dev, sizeof(struct s2mps11_info), -- cgit v1.2.3 From 6430d65d7b74712e9ff60e270687d66265dad6f2 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:39 +0000 Subject: regulator: s2mps11: use dev_err_probe() where appropriate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev_err_probe() exists to simplify code and harmonise error messages, there's no reason not to use it here. While at it, harmonise some error messages to add regulator name and ID like in other messages in this driver, and update messages to be more similar to other child-drivers of this PMIC (e.g. RTC). Reviewed-by: Krzysztof Kozlowski Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-12-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 1f51fbc6c7b6..30586e9884bf 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -1249,9 +1249,9 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mpu05_regulators)); break; default: - dev_err(&pdev->dev, "Invalid device type: %u\n", - s2mps11->dev_type); - return -EINVAL; + return dev_err_probe(&pdev->dev, -ENODEV, + "Unsupported device type %d\n", + s2mps11->dev_type); } s2mps11->ext_control_gpiod = devm_kcalloc(&pdev->dev, rdev_num, @@ -1290,21 +1290,20 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) devm_gpiod_unhinge(&pdev->dev, config.ena_gpiod); regulator = devm_regulator_register(&pdev->dev, ®ulators[i], &config); - if (IS_ERR(regulator)) { - dev_err(&pdev->dev, "regulator init failed for %d\n", - i); - return PTR_ERR(regulator); - } + if (IS_ERR(regulator)) + return dev_err_probe(&pdev->dev, PTR_ERR(regulator), + "regulator init failed for %d/%s\n", + regulators[i].id, + regulators[i].name); if (config.ena_gpiod) { ret = s2mps14_pmic_enable_ext_control(s2mps11, - regulator); - if (ret < 0) { - dev_err(&pdev->dev, - "failed to enable GPIO control over %s: %d\n", - regulator->desc->name, ret); - return ret; - } + regulator); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "failed to enable GPIO control over %d/%s\n", + regulator->desc->id, + regulator->desc->name); } } -- cgit v1.2.3 From 223cefd021fa6ef5687159836871907aa3084fe2 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:40 +0000 Subject: regulator: s2mps11: place constants on right side of comparison tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For the lines being changed, checkpatch reports: WARNING: Comparisons should place the constant on the right side of the test Update the code accordingly. Reviewed-by: Krzysztof Kozlowski Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-13-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 30586e9884bf..8a36ab67b73e 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -1221,32 +1221,32 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) case S2MPS11X: rdev_num = ARRAY_SIZE(s2mps11_regulators); regulators = s2mps11_regulators; - BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mps11_regulators)); + BUILD_BUG_ON(ARRAY_SIZE(s2mps11_regulators) > S2MPS_REGULATOR_MAX); break; case S2MPS13X: rdev_num = ARRAY_SIZE(s2mps13_regulators); regulators = s2mps13_regulators; - BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mps13_regulators)); + BUILD_BUG_ON(ARRAY_SIZE(s2mps13_regulators) > S2MPS_REGULATOR_MAX); break; case S2MPS14X: rdev_num = ARRAY_SIZE(s2mps14_regulators); regulators = s2mps14_regulators; - BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mps14_regulators)); + BUILD_BUG_ON(ARRAY_SIZE(s2mps14_regulators) > S2MPS_REGULATOR_MAX); break; case S2MPS15X: rdev_num = ARRAY_SIZE(s2mps15_regulators); regulators = s2mps15_regulators; - BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mps15_regulators)); + BUILD_BUG_ON(ARRAY_SIZE(s2mps15_regulators) > S2MPS_REGULATOR_MAX); break; case S2MPU02: rdev_num = ARRAY_SIZE(s2mpu02_regulators); regulators = s2mpu02_regulators; - BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mpu02_regulators)); + BUILD_BUG_ON(ARRAY_SIZE(s2mpu02_regulators) > S2MPS_REGULATOR_MAX); break; case S2MPU05: rdev_num = ARRAY_SIZE(s2mpu05_regulators); regulators = s2mpu05_regulators; - BUILD_BUG_ON(S2MPS_REGULATOR_MAX < ARRAY_SIZE(s2mpu05_regulators)); + BUILD_BUG_ON(ARRAY_SIZE(s2mpu05_regulators) > S2MPS_REGULATOR_MAX); break; default: return dev_err_probe(&pdev->dev, -ENODEV, -- cgit v1.2.3 From 5b3c95739d674794730fbf3c678206f302609d27 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:41 +0000 Subject: regulator: s2mps11: update node parsing (allow -supply properties) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For the upcoming S2MPG10 and S2MPG11 support, we need to be able to parse -supply properties in the PMIC's DT node. This currently doesn't work, because the code here currently points the regulator core at each individual regulator sub-node, and therefore the regulator core is unable to find the -supply properties. Update the code to simply let the regulator core handle all the parsing by adding the ::of_match and ::regulators_node members to all existing regulator descriptions, by adding ::of_parse_cb() to those regulators which support the vendor-specific samsung,ext-control-gpios to parse it (S2MPS14), and by dropping the explicit call to of_regulator_match(). Configuring the PMIC to respect the external control GPIOs via s2mps14_pmic_enable_ext_control() is left outside ::of_parse_cb() because the regulator core ignores errors other than -EPROBE_DEFER from that callback, while the code currently fails probe on register write errors and I believe it should stay that way. The driver can now avoid the devm_gpiod_unhinge() dance due to simpler error handling of GPIO descriptor acquisition. This change also has the advantage of reducing runtime memory consumption by quite a bit as the driver doesn't need to allocate a 'struct of_regulator_match' and a 'struct gpio_desc *' for each regulator for all PMICs as the regulator core does that. This saves 40+8 bytes on arm64 for each individual regulator on all supported PMICs (even on non-S2MPS14 due to currently unnecessarily allocating the extra memory unconditionally). With the upcoming S2MPG10 and S2MPG11 support, this amounts to 1640+328 and 1120+224 bytes respectively. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-14-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 192 ++++++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 8a36ab67b73e..88e21c90832a 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -40,12 +40,6 @@ struct s2mps11_info { * the suspend mode was enabled. */ DECLARE_BITMAP(suspend_state, S2MPS_REGULATOR_MAX); - - /* - * Array (size: number of regulators) with GPIO-s for external - * sleep control. - */ - struct gpio_desc **ext_control_gpiod; }; static int get_ramp_delay(int ramp_delay) @@ -244,7 +238,7 @@ static int s2mps11_regulator_enable(struct regulator_dev *rdev) case S2MPS14X: if (test_bit(rdev_id, s2mps11->suspend_state)) val = S2MPS14_ENABLE_SUSPEND; - else if (s2mps11->ext_control_gpiod[rdev_id]) + else if (rdev->ena_pin) val = S2MPS14_ENABLE_EXT_CONTROL; else val = rdev->desc->enable_mask; @@ -334,6 +328,58 @@ static int s2mps11_regulator_set_suspend_disable(struct regulator_dev *rdev) rdev->desc->enable_mask, state); } +static int s2mps11_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + const struct s2mps11_info *s2mps11 = config->driver_data; + struct gpio_desc *ena_gpiod; + int ret; + + if (s2mps11->dev_type == S2MPS14X) + switch (desc->id) { + case S2MPS14_LDO10: + case S2MPS14_LDO11: + case S2MPS14_LDO12: + break; + + default: + return 0; + } + else + return 0; + + ena_gpiod = fwnode_gpiod_get_index(of_fwnode_handle(np), + "samsung,ext-control", 0, + GPIOD_OUT_HIGH | + GPIOD_FLAGS_BIT_NONEXCLUSIVE, + "s2mps11-regulator"); + if (IS_ERR(ena_gpiod)) { + ret = PTR_ERR(ena_gpiod); + + /* Ignore all errors except probe defer. */ + if (ret == -EPROBE_DEFER) + return ret; + + if (ret == -ENOENT) + dev_info(config->dev, + "No entry for control GPIO for %d/%s in node %pOF\n", + desc->id, desc->name, np); + else + dev_warn_probe(config->dev, ret, + "Failed to get control GPIO for %d/%s in node %pOF\n", + desc->id, desc->name, np); + return 0; + } + + dev_info(config->dev, "Using GPIO for ext-control over %d/%s\n", + desc->id, desc->name); + + config->ena_gpiod = ena_gpiod; + + return 0; +} + static const struct regulator_ops s2mps11_ldo_ops = { .list_voltage = regulator_list_voltage_linear, .map_voltage = regulator_map_voltage_linear, @@ -362,6 +408,8 @@ static const struct regulator_ops s2mps11_buck_ops = { #define regulator_desc_s2mps11_ldo(num, step) { \ .name = "LDO"#num, \ .id = S2MPS11_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps11_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -378,6 +426,8 @@ static const struct regulator_ops s2mps11_buck_ops = { #define regulator_desc_s2mps11_buck1_4(num) { \ .name = "BUCK"#num, \ .id = S2MPS11_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps11_buck_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -395,6 +445,8 @@ static const struct regulator_ops s2mps11_buck_ops = { #define regulator_desc_s2mps11_buck5 { \ .name = "BUCK5", \ .id = S2MPS11_BUCK5, \ + .of_match = of_match_ptr("BUCK5"), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps11_buck_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -412,6 +464,8 @@ static const struct regulator_ops s2mps11_buck_ops = { #define regulator_desc_s2mps11_buck67810(num, min, step, min_sel, voltages) { \ .name = "BUCK"#num, \ .id = S2MPS11_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps11_buck_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -429,6 +483,8 @@ static const struct regulator_ops s2mps11_buck_ops = { #define regulator_desc_s2mps11_buck9 { \ .name = "BUCK9", \ .id = S2MPS11_BUCK9, \ + .of_match = of_match_ptr("BUCK9"), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps11_buck_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -502,6 +558,8 @@ static const struct regulator_ops s2mps14_reg_ops; #define regulator_desc_s2mps13_ldo(num, min, step, min_sel) { \ .name = "LDO"#num, \ .id = S2MPS13_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps14_reg_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -518,6 +576,8 @@ static const struct regulator_ops s2mps14_reg_ops; #define regulator_desc_s2mps13_buck(num, min, step, min_sel) { \ .name = "BUCK"#num, \ .id = S2MPS13_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps14_reg_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -535,6 +595,8 @@ static const struct regulator_ops s2mps14_reg_ops; #define regulator_desc_s2mps13_buck7(num, min, step, min_sel) { \ .name = "BUCK"#num, \ .id = S2MPS13_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps14_reg_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -552,6 +614,8 @@ static const struct regulator_ops s2mps14_reg_ops; #define regulator_desc_s2mps13_buck8_10(num, min, step, min_sel) { \ .name = "BUCK"#num, \ .id = S2MPS13_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps14_reg_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -634,6 +698,9 @@ static const struct regulator_ops s2mps14_reg_ops = { #define regulator_desc_s2mps14_ldo(num, min, step) { \ .name = "LDO"#num, \ .id = S2MPS14_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .of_parse_cb = s2mps11_of_parse_cb, \ .ops = &s2mps14_reg_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -649,6 +716,9 @@ static const struct regulator_ops s2mps14_reg_ops = { #define regulator_desc_s2mps14_buck(num, min, step, min_sel) { \ .name = "BUCK"#num, \ .id = S2MPS14_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .of_parse_cb = s2mps11_of_parse_cb, \ .ops = &s2mps14_reg_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -725,6 +795,8 @@ static const struct regulator_ops s2mps15_reg_buck_ops = { #define regulator_desc_s2mps15_ldo(num, range) { \ .name = "LDO"#num, \ .id = S2MPS15_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps15_reg_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -740,6 +812,8 @@ static const struct regulator_ops s2mps15_reg_buck_ops = { #define regulator_desc_s2mps15_buck(num, range) { \ .name = "BUCK"#num, \ .id = S2MPS15_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mps15_reg_buck_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -835,60 +909,6 @@ static int s2mps14_pmic_enable_ext_control(struct s2mps11_info *s2mps11, rdev->desc->enable_mask, S2MPS14_ENABLE_EXT_CONTROL); } -static void s2mps14_pmic_dt_parse_ext_control_gpio(struct platform_device *pdev, - struct of_regulator_match *rdata, struct s2mps11_info *s2mps11) -{ - struct gpio_desc **gpio = s2mps11->ext_control_gpiod; - unsigned int i; - unsigned int valid_regulators[3] = { S2MPS14_LDO10, S2MPS14_LDO11, - S2MPS14_LDO12 }; - - for (i = 0; i < ARRAY_SIZE(valid_regulators); i++) { - unsigned int reg = valid_regulators[i]; - - if (!rdata[reg].init_data || !rdata[reg].of_node) - continue; - - gpio[reg] = devm_fwnode_gpiod_get(&pdev->dev, - of_fwnode_handle(rdata[reg].of_node), - "samsung,ext-control", - GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_NONEXCLUSIVE, - "s2mps11-regulator"); - if (PTR_ERR(gpio[reg]) == -ENOENT) - gpio[reg] = NULL; - else if (IS_ERR(gpio[reg])) { - dev_err(&pdev->dev, "Failed to get control GPIO for %d/%s\n", - reg, rdata[reg].name); - gpio[reg] = NULL; - continue; - } - if (gpio[reg]) - dev_dbg(&pdev->dev, "Using GPIO for ext-control over %d/%s\n", - reg, rdata[reg].name); - } -} - -static int s2mps11_pmic_dt_parse(struct platform_device *pdev, - struct of_regulator_match *rdata, struct s2mps11_info *s2mps11, - unsigned int rdev_num) -{ - struct device_node *reg_np; - - reg_np = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); - if (!reg_np) { - dev_err(&pdev->dev, "could not find regulators sub-node\n"); - return -EINVAL; - } - - of_regulator_match(&pdev->dev, reg_np, rdata, rdev_num); - if (s2mps11->dev_type == S2MPS14X) - s2mps14_pmic_dt_parse_ext_control_gpio(pdev, rdata, s2mps11); - - of_node_put(reg_np); - - return 0; -} - static int s2mpu02_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) { unsigned int ramp_val, ramp_shift, ramp_reg; @@ -946,6 +966,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_ldo1(num) { \ .name = "LDO"#num, \ .id = S2MPU02_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -961,6 +983,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_ldo2(num) { \ .name = "LDO"#num, \ .id = S2MPU02_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -976,6 +1000,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_ldo3(num) { \ .name = "LDO"#num, \ .id = S2MPU02_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -991,6 +1017,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_ldo4(num) { \ .name = "LDO"#num, \ .id = S2MPU02_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -1006,6 +1034,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_ldo5(num) { \ .name = "LDO"#num, \ .id = S2MPU02_LDO##num, \ + .of_match = of_match_ptr("LDO"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -1022,6 +1052,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_buck1234(num) { \ .name = "BUCK"#num, \ .id = S2MPU02_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_buck_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -1038,6 +1070,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_buck5(num) { \ .name = "BUCK"#num, \ .id = S2MPU02_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -1054,6 +1088,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_buck6(num) { \ .name = "BUCK"#num, \ .id = S2MPU02_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -1070,6 +1106,8 @@ static const struct regulator_ops s2mpu02_buck_ops = { #define regulator_desc_s2mpu02_buck7(num) { \ .name = "BUCK"#num, \ .id = S2MPU02_BUCK##num, \ + .of_match = of_match_ptr("BUCK"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -1125,6 +1163,8 @@ static const struct regulator_desc s2mpu02_regulators[] = { #define regulator_desc_s2mpu05_ldo_reg(num, min, step, reg) { \ .name = "ldo"#num, \ .id = S2MPU05_LDO##num, \ + .of_match = of_match_ptr("ldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_ldo_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -1156,6 +1196,8 @@ static const struct regulator_desc s2mpu02_regulators[] = { #define regulator_desc_s2mpu05_buck(num, which) { \ .name = "buck"#num, \ .id = S2MPU05_BUCK##num, \ + .of_match = of_match_ptr("buck"#num), \ + .regulators_node = of_match_ptr("regulators"), \ .ops = &s2mpu02_buck_ops, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ @@ -1254,22 +1296,7 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) s2mps11->dev_type); } - s2mps11->ext_control_gpiod = devm_kcalloc(&pdev->dev, rdev_num, - sizeof(*s2mps11->ext_control_gpiod), GFP_KERNEL); - if (!s2mps11->ext_control_gpiod) - return -ENOMEM; - - struct of_regulator_match *rdata __free(kfree) = - kcalloc(rdev_num, sizeof(*rdata), GFP_KERNEL); - if (!rdata) - return -ENOMEM; - - for (i = 0; i < rdev_num; i++) - rdata[i].name = regulators[i].name; - - ret = s2mps11_pmic_dt_parse(pdev, rdata, s2mps11, rdev_num); - if (ret) - return ret; + device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); platform_set_drvdata(pdev, s2mps11); @@ -1279,15 +1306,6 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) for (i = 0; i < rdev_num; i++) { struct regulator_dev *regulator; - config.init_data = rdata[i].init_data; - config.of_node = rdata[i].of_node; - config.ena_gpiod = s2mps11->ext_control_gpiod[i]; - /* - * Hand the GPIO descriptor management over to the regulator - * core, remove it from devres management. - */ - if (config.ena_gpiod) - devm_gpiod_unhinge(&pdev->dev, config.ena_gpiod); regulator = devm_regulator_register(&pdev->dev, ®ulators[i], &config); if (IS_ERR(regulator)) @@ -1296,7 +1314,7 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) regulators[i].id, regulators[i].name); - if (config.ena_gpiod) { + if (regulator->ena_pin) { ret = s2mps14_pmic_enable_ext_control(s2mps11, regulator); if (ret < 0) -- cgit v1.2.3 From 0042c880e43c54c9bf19c24a72e54eeea37995e3 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:42 +0000 Subject: regulator: s2mps11: refactor handling of external rail control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor s2mps14_pmic_enable_ext_control() and s2mps11_of_parse_cb() slightly as a preparation for adding S2MPG10 and S2MPG11 support, as both of those PMICs also support control of rails via GPIOs. This also includes the following to avoid further updates in follow-up commits: * On S2MPG10 and S2MPG11, external rail control can be via GPIO or via non-GPIO signals, hence passing a GPIO is allowed to be optional. This avoids inappropriate verbose driver messages. * Prepare to allow use of standard DT property name 'enable-gpios' for newer platforms instead of vendor-specific 'samsung,ext-control'. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-15-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 100 +++++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 33 deletions(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 88e21c90832a..7f4db6673b43 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -328,29 +328,15 @@ static int s2mps11_regulator_set_suspend_disable(struct regulator_dev *rdev) rdev->desc->enable_mask, state); } -static int s2mps11_of_parse_cb(struct device_node *np, - const struct regulator_desc *desc, - struct regulator_config *config) +static int s2mps11_of_parse_gpiod(struct device_node *np, + const char *con_id, bool optional, + const struct regulator_desc *desc, + struct regulator_config *config) { - const struct s2mps11_info *s2mps11 = config->driver_data; struct gpio_desc *ena_gpiod; int ret; - if (s2mps11->dev_type == S2MPS14X) - switch (desc->id) { - case S2MPS14_LDO10: - case S2MPS14_LDO11: - case S2MPS14_LDO12: - break; - - default: - return 0; - } - else - return 0; - - ena_gpiod = fwnode_gpiod_get_index(of_fwnode_handle(np), - "samsung,ext-control", 0, + ena_gpiod = fwnode_gpiod_get_index(of_fwnode_handle(np), con_id, 0, GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_NONEXCLUSIVE, "s2mps11-regulator"); @@ -361,14 +347,19 @@ static int s2mps11_of_parse_cb(struct device_node *np, if (ret == -EPROBE_DEFER) return ret; - if (ret == -ENOENT) + if (ret == -ENOENT) { + if (optional) + return 0; + dev_info(config->dev, "No entry for control GPIO for %d/%s in node %pOF\n", desc->id, desc->name, np); - else + } else { dev_warn_probe(config->dev, ret, "Failed to get control GPIO for %d/%s in node %pOF\n", desc->id, desc->name, np); + } + return 0; } @@ -380,6 +371,29 @@ static int s2mps11_of_parse_cb(struct device_node *np, return 0; } +static int s2mps11_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + const struct s2mps11_info *s2mps11 = config->driver_data; + + if (s2mps11->dev_type == S2MPS14X) + switch (desc->id) { + case S2MPS14_LDO10: + case S2MPS14_LDO11: + case S2MPS14_LDO12: + break; + + default: + return 0; + } + else + return 0; + + return s2mps11_of_parse_gpiod(np, "samsung,ext-control", false, desc, + config); +} + static const struct regulator_ops s2mps11_ldo_ops = { .list_voltage = regulator_list_voltage_linear, .map_voltage = regulator_map_voltage_linear, @@ -903,10 +917,16 @@ static const struct regulator_desc s2mps15_regulators[] = { }; static int s2mps14_pmic_enable_ext_control(struct s2mps11_info *s2mps11, - struct regulator_dev *rdev) + struct regulator_dev *rdev) { - return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, - rdev->desc->enable_mask, S2MPS14_ENABLE_EXT_CONTROL); + int ret = regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, + S2MPS14_ENABLE_EXT_CONTROL); + if (ret < 0) + return dev_err_probe(rdev_get_dev(rdev), ret, + "failed to enable GPIO control over %d/%s\n", + rdev->desc->id, rdev->desc->name); + return 0; } static int s2mpu02_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay) @@ -1244,6 +1264,26 @@ static const struct regulator_desc s2mpu05_regulators[] = { regulator_desc_s2mpu05_buck45(5), }; +static int s2mps11_handle_ext_control(struct s2mps11_info *s2mps11, + struct regulator_dev *rdev) +{ + int ret; + + switch (s2mps11->dev_type) { + case S2MPS14X: + if (!rdev->ena_pin) + return 0; + + ret = s2mps14_pmic_enable_ext_control(s2mps11, rdev); + break; + + default: + return 0; + } + + return ret; +} + static int s2mps11_pmic_probe(struct platform_device *pdev) { struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent); @@ -1314,15 +1354,9 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) regulators[i].id, regulators[i].name); - if (regulator->ena_pin) { - ret = s2mps14_pmic_enable_ext_control(s2mps11, - regulator); - if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "failed to enable GPIO control over %d/%s\n", - regulator->desc->id, - regulator->desc->name); - } + ret = s2mps11_handle_ext_control(s2mps11, regulator); + if (ret < 0) + return ret; } return 0; -- cgit v1.2.3 From a2b8b9f33ce30ab51b33b52dc52e55d6930b9a02 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:43 +0000 Subject: regulator: s2mps11: add S2MPG10 regulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The S2MPG10 PMIC is a Power Management IC for mobile applications with buck converters, various LDOs, power meters, RTC, clock outputs, and additional GPIO interfaces. It has 10 buck and 31 LDO rails. Several of these can either be controlled via software (register writes) or via external signals, in particular by: * one out of several input pins connected to a main processor's: * GPIO pins * other pins that are e.g. firmware- or power-domain-controlled without explicit driver intervention * a combination of input pins and register writes. Control via input pins allows PMIC rails to be controlled by firmware, e.g. during standby/suspend, or as part of power domain handling where otherwise that would not be possible. Additionally toggling a pin is faster than register writes, and it also allows the PMIC to ensure that any necessary timing requirements between rails are respected automatically if multiple rails are to be enabled or disabled quasi simultaneously. This commit implements support for all these rails and control combinations. Additional data needs to be stored for each regulator, e.g. the input pin for external control, or a rail-specific ramp-rate for when enabling a buck-rail. Therefore, probe() is updated slightly to make that possible. Note1: For an externally controlled rail, the regulator_ops provide an empty ::enable() and no ::disable() implementations, even though Linux can not enable the rail and one might think ::enable could be NULL. Without ops->enable(), the regulator core will assume enabling such a rail failed, though, and in turn never add a reference to its parent (supplier) rail. Once a different (Linux-controlled) sibling (consumer) rail on that same parent rail gets disabled, the parent gets disabled (cutting power to the externally controlled rail although it should stay on), and the system will misbehave. Note2: While external control via input pins appears to exist on other versions of this PMIC, there is more flexibility in this version, in particular there is a selection of input pins to choose from for each rail (which must therefore be configured accordingly if in use), whereas other versions don't have this flexibility. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-16-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 587 +++++++++++++++++++++++++++++++++++- include/linux/mfd/samsung/s2mpg10.h | 24 ++ 2 files changed, 608 insertions(+), 3 deletions(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 7f4db6673b43..0b6b28ce6465 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -3,6 +3,7 @@ // Copyright (c) 2012-2014 Samsung Electronics Co., Ltd // http://www.samsung.com +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +25,11 @@ #include #include +enum { + S2MPG10_REGULATOR_OPS_STD, + S2MPG10_REGULATOR_OPS_EXTCONTROL, +}; + /* The highest number of possible regulators for supported devices. */ #define S2MPS_REGULATOR_MAX S2MPS13_REGULATOR_MAX struct s2mps11_info { @@ -42,6 +49,21 @@ struct s2mps11_info { DECLARE_BITMAP(suspend_state, S2MPS_REGULATOR_MAX); }; +#define to_s2mpg10_regulator_desc(x) container_of((x), struct s2mpg10_regulator_desc, desc) + +struct s2mpg10_regulator_desc { + struct regulator_desc desc; + + /* Ramp rate during enable, valid for bucks only. */ + unsigned int enable_ramp_rate; + + /* Registers for external control of rail. */ + unsigned int pctrlsel_reg; + unsigned int pctrlsel_mask; + /* Populated from DT. */ + unsigned int pctrlsel_val; +}; + static int get_ramp_delay(int ramp_delay) { unsigned char cnt = 0; @@ -394,6 +416,530 @@ static int s2mps11_of_parse_cb(struct device_node *np, config); } +static int s2mpg10_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + const struct s2mps11_info *s2mps11 = config->driver_data; + struct s2mpg10_regulator_desc *s2mpg10_desc = to_s2mpg10_regulator_desc(desc); + static const u32 ext_control_s2mpg10[] = { + [S2MPG10_EXTCTRL_PWREN] = S2MPG10_PCTRLSEL_PWREN, + [S2MPG10_EXTCTRL_PWREN_MIF] = S2MPG10_PCTRLSEL_PWREN_MIF, + [S2MPG10_EXTCTRL_AP_ACTIVE_N] = S2MPG10_PCTRLSEL_AP_ACTIVE_N, + [S2MPG10_EXTCTRL_CPUCL1_EN] = S2MPG10_PCTRLSEL_CPUCL1_EN, + [S2MPG10_EXTCTRL_CPUCL1_EN2] = S2MPG10_PCTRLSEL_CPUCL1_EN2, + [S2MPG10_EXTCTRL_CPUCL2_EN] = S2MPG10_PCTRLSEL_CPUCL2_EN, + [S2MPG10_EXTCTRL_CPUCL2_EN2] = S2MPG10_PCTRLSEL_CPUCL2_EN2, + [S2MPG10_EXTCTRL_TPU_EN] = S2MPG10_PCTRLSEL_TPU_EN, + [S2MPG10_EXTCTRL_TPU_EN2] = S2MPG10_PCTRLSEL_TPU_EN2, + [S2MPG10_EXTCTRL_TCXO_ON] = S2MPG10_PCTRLSEL_TCXO_ON, + [S2MPG10_EXTCTRL_TCXO_ON2] = S2MPG10_PCTRLSEL_TCXO_ON2, + [S2MPG10_EXTCTRL_LDO20M_EN2] = S2MPG10_PCTRLSEL_LDO20M_EN2, + [S2MPG10_EXTCTRL_LDO20M_EN] = S2MPG10_PCTRLSEL_LDO20M_EN, + }; + u32 ext_control; + + if (s2mps11->dev_type != S2MPG10) + return 0; + + if (of_property_read_u32(np, "samsung,ext-control", &ext_control)) + return 0; + + switch (s2mps11->dev_type) { + case S2MPG10: + switch (desc->id) { + case S2MPG10_BUCK1 ... S2MPG10_BUCK7: + case S2MPG10_BUCK10: + case S2MPG10_LDO3 ... S2MPG10_LDO19: + if (ext_control > S2MPG10_EXTCTRL_TCXO_ON2) + return -EINVAL; + break; + + case S2MPG10_LDO20: + if (ext_control < S2MPG10_EXTCTRL_LDO20M_EN2 || + ext_control > S2MPG10_EXTCTRL_LDO20M_EN) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + if (ext_control > ARRAY_SIZE(ext_control_s2mpg10)) + return -EINVAL; + ext_control = ext_control_s2mpg10[ext_control]; + break; + + default: + return -EINVAL; + } + + /* + * If the regulator should be configured for external control, then: + * 1) the PCTRLSELx register needs to be set accordingly + * 2) regulator_desc::enable_val needs to be: + * a) updated and + * b) written to the hardware + * 3) we switch to the ::ops that provide an empty ::enable() and no + * ::disable() implementations + * + * Points 1) and 2b) will be handled in _probe(), after + * devm_regulator_register() returns, so that we can properly act on + * failures, since the regulator core ignores most return values from + * this parse callback. + */ + s2mpg10_desc->pctrlsel_val = ext_control; + s2mpg10_desc->pctrlsel_val <<= (ffs(s2mpg10_desc->pctrlsel_mask) - 1); + + s2mpg10_desc->desc.enable_val = S2MPG10_PMIC_CTRL_ENABLE_EXT; + s2mpg10_desc->desc.enable_val <<= (ffs(desc->enable_mask) - 1); + + ++s2mpg10_desc->desc.ops; + + return s2mps11_of_parse_gpiod(np, "enable", true, desc, config); +} + +static int s2mpg10_enable_ext_control(struct s2mps11_info *s2mps11, + struct regulator_dev *rdev) +{ + const struct s2mpg10_regulator_desc *s2mpg10_desc; + int ret; + + switch (s2mps11->dev_type) { + case S2MPG10: + s2mpg10_desc = to_s2mpg10_regulator_desc(rdev->desc); + break; + + default: + return 0; + } + + ret = regmap_update_bits(rdev_get_regmap(rdev), + s2mpg10_desc->pctrlsel_reg, + s2mpg10_desc->pctrlsel_mask, + s2mpg10_desc->pctrlsel_val); + if (ret) + return dev_err_probe(rdev_get_dev(rdev), ret, + "failed to configure pctrlsel for %s\n", + rdev->desc->name); + + /* + * When using external control, the enable bit of the regulator still + * needs to be set. The actual state will still be determined by the + * external signal. + */ + ret = regulator_enable_regmap(rdev); + if (ret) + return dev_err_probe(rdev_get_dev(rdev), ret, + "failed to enable regulator %s\n", + rdev->desc->name); + + return 0; +} + +static int s2mpg10_regulator_enable_nop(struct regulator_dev *rdev) +{ + /* + * We need to provide this, otherwise the regulator core's enable on + * this regulator will return a failure and subsequently disable our + * parent regulator. + */ + return 0; +} + +static int s2mpg10_regulator_buck_enable_time(struct regulator_dev *rdev) +{ + const struct s2mpg10_regulator_desc * const s2mpg10_desc = + to_s2mpg10_regulator_desc(rdev->desc); + const struct regulator_ops * const ops = rdev->desc->ops; + int vsel, curr_uV; + + vsel = ops->get_voltage_sel(rdev); + if (vsel < 0) + return vsel; + + curr_uV = ops->list_voltage(rdev, vsel); + if (curr_uV < 0) + return curr_uV; + + return (rdev->desc->enable_time + + DIV_ROUND_UP(curr_uV, s2mpg10_desc->enable_ramp_rate)); +} + +static int s2mpg10_regulator_buck_set_voltage_time(struct regulator_dev *rdev, + int old_uV, int new_uV) +{ + unsigned int ramp_reg, ramp_sel, ramp_rate; + int ret; + + if (old_uV == new_uV) + return 0; + + ramp_reg = rdev->desc->ramp_reg; + if (old_uV > new_uV) + /* The downwards ramp is at a different offset. */ + ramp_reg += S2MPG10_PMIC_DVS_RAMP4 - S2MPG10_PMIC_DVS_RAMP1; + + ret = regmap_read(rdev->regmap, ramp_reg, &ramp_sel); + if (ret) + return ret; + + ramp_sel &= rdev->desc->ramp_mask; + ramp_sel >>= ffs(rdev->desc->ramp_mask) - 1; + if (ramp_sel >= rdev->desc->n_ramp_values || + !rdev->desc->ramp_delay_table) + return -EINVAL; + + ramp_rate = rdev->desc->ramp_delay_table[ramp_sel]; + + return DIV_ROUND_UP(abs(new_uV - old_uV), ramp_rate); +} + +/* + * We assign both, ::set_voltage_time() and ::set_voltage_time_sel(), because + * only if the latter is != NULL, the regulator core will call neither during + * DVS if the regulator is disabled. If the latter is NULL, the core always + * calls the ::set_voltage_time() callback, which would give incorrect results + * if the regulator is off. + * At the same time, we do need ::set_voltage_time() due to differing upwards + * and downwards ramps and we can not make that code dependent on the regulator + * enable state, as that would break regulator_set_voltage_time() which + * expects a correct result no matter the enable state. + */ +static const struct regulator_ops s2mpg10_reg_buck_ops[] = { + [S2MPG10_REGULATOR_OPS_STD] = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .enable_time = s2mpg10_regulator_buck_enable_time, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time = s2mpg10_regulator_buck_set_voltage_time, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + }, + [S2MPG10_REGULATOR_OPS_EXTCONTROL] = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .enable = s2mpg10_regulator_enable_nop, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time = s2mpg10_regulator_buck_set_voltage_time, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + } +}; + +#define s2mpg10_buck_to_ramp_mask(n) (GENMASK(1, 0) << (((n) % 4) * 2)) + +/* + * The ramp_delay during enable is fixed (12.5mV/μs), while the ramp during + * DVS can be adjusted. Linux can adjust the ramp delay via DT, in which case + * the regulator core will modify the regulator's constraints and call our + * .set_ramp_delay() which updates the DVS ramp in ramp_reg. + * For enable, our .enable_time() unconditionally uses enable_ramp_rate + * (12.5mV/μs) while our ::set_voltage_time() takes the value in ramp_reg + * into account. + */ +#define regulator_desc_s2mpg10_buck(_num, _vrange, _r_reg) { \ + .name = "buck"#_num "m", \ + .supply_name = "vinb"#_num "m", \ + .of_match = of_match_ptr("buck"#_num "m"), \ + .regulators_node = of_match_ptr("regulators"), \ + .of_parse_cb = s2mpg10_of_parse_cb, \ + .id = S2MPG10_BUCK##_num, \ + .ops = &s2mpg10_reg_buck_ops[0], \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .linear_ranges = _vrange, \ + .n_linear_ranges = ARRAY_SIZE(_vrange), \ + .n_voltages = _vrange##_count, \ + .vsel_reg = S2MPG10_PMIC_B##_num##M_OUT1, \ + .vsel_mask = 0xff, \ + .enable_reg = S2MPG10_PMIC_B##_num##M_CTRL, \ + .enable_mask = GENMASK(7, 6), \ + .ramp_reg = S2MPG10_PMIC_##_r_reg, \ + .ramp_mask = s2mpg10_buck_to_ramp_mask(S2MPG10_BUCK##_num \ + - S2MPG10_BUCK1), \ + .ramp_delay_table = s2mpg10_buck_ramp_table, \ + .n_ramp_values = ARRAY_SIZE(s2mpg10_buck_ramp_table), \ + .enable_time = 30, /* + V/enable_ramp_rate */ \ +} + +#define s2mpg10_regulator_desc_buck_cm(_num, _vrange, _r_reg) \ + .desc = regulator_desc_s2mpg10_buck(_num, _vrange, _r_reg), \ + .enable_ramp_rate = 12500 + +#define s2mpg10_regulator_desc_buck_gpio(_num, _vrange, _r_reg, \ + _pc_reg, _pc_mask) \ + [S2MPG10_BUCK##_num] = { \ + s2mpg10_regulator_desc_buck_cm(_num, _vrange, _r_reg), \ + .pctrlsel_reg = S2MPG10_PMIC_##_pc_reg, \ + .pctrlsel_mask = _pc_mask, \ + } + +#define s2mpg10_regulator_desc_buck(_num, _vrange, _r_reg) \ + [S2MPG10_BUCK##_num] = { \ + s2mpg10_regulator_desc_buck_cm(_num, _vrange, _r_reg), \ + } + +/* ops for S2MPG1x LDO regulators without ramp control */ +static const struct regulator_ops s2mpg10_reg_ldo_ops[] = { + [S2MPG10_REGULATOR_OPS_STD] = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + }, + [S2MPG10_REGULATOR_OPS_EXTCONTROL] = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .enable = s2mpg10_regulator_enable_nop, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + } +}; + +/* ops for S2MPG1x LDO regulators that have ramp control */ +static const struct regulator_ops s2mpg10_reg_ldo_ramp_ops[] = { + [S2MPG10_REGULATOR_OPS_STD] = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + }, + [S2MPG10_REGULATOR_OPS_EXTCONTROL] = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .enable = s2mpg10_regulator_enable_nop, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + } +}; + +#define regulator_desc_s2mpg10_ldo_cmn(_num, _supply, _ops, _vrange, \ + _vsel_reg_sfx, _vsel_mask, _en_reg, _en_mask, \ + _ramp_delay, _r_reg, _r_mask, _r_table, _r_table_sz) { \ + .name = "ldo"#_num "m", \ + .supply_name = _supply, \ + .of_match = of_match_ptr("ldo"#_num "m"), \ + .regulators_node = of_match_ptr("regulators"), \ + .of_parse_cb = s2mpg10_of_parse_cb, \ + .id = S2MPG10_LDO##_num, \ + .ops = &(_ops)[0], \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .linear_ranges = _vrange, \ + .n_linear_ranges = ARRAY_SIZE(_vrange), \ + .n_voltages = _vrange##_count, \ + .vsel_reg = S2MPG10_PMIC_L##_num##M_##_vsel_reg_sfx, \ + .vsel_mask = _vsel_mask, \ + .enable_reg = S2MPG10_PMIC_##_en_reg, \ + .enable_mask = _en_mask, \ + .ramp_delay = _ramp_delay, \ + .ramp_reg = _r_reg, \ + .ramp_mask = _r_mask, \ + .ramp_delay_table = _r_table, \ + .n_ramp_values = _r_table_sz, \ + .enable_time = 130, /* startup 20+-10 + ramp 30..100μs */ \ +} + +#define s2mpg10_regulator_desc_ldo_cmn(_num, _supply, _ops, _vrange, \ + _vsel_reg_sfx, _vsel_mask, _en_reg, _en_mask, \ + _ramp_delay, _r_reg, _r_mask, _r_table, _r_table_sz, \ + _pc_reg, _pc_mask) \ + [S2MPG10_LDO##_num] = { \ + .desc = regulator_desc_s2mpg10_ldo_cmn(_num, _supply, \ + _ops, \ + _vrange, _vsel_reg_sfx, _vsel_mask, \ + _en_reg, _en_mask, \ + _ramp_delay, _r_reg, _r_mask, _r_table, \ + _r_table_sz), \ + .pctrlsel_reg = _pc_reg, \ + .pctrlsel_mask = _pc_mask, \ + } + +/* standard LDO via LxM_CTRL */ +#define s2mpg10_regulator_desc_ldo(_num, _supply, _vrange) \ + s2mpg10_regulator_desc_ldo_cmn(_num, _supply, \ + s2mpg10_reg_ldo_ops, _vrange, CTRL, GENMASK(5, 0), \ + L##_num##M_CTRL, BIT(7), \ + 0, 0, 0, NULL, 0, \ + 0, 0) + +/* standard LDO but possibly GPIO controlled */ +#define s2mpg10_regulator_desc_ldo_gpio(_num, _supply, _vrange, \ + _pc_reg, _pc_mask) \ + s2mpg10_regulator_desc_ldo_cmn(_num, _supply, \ + s2mpg10_reg_ldo_ops, _vrange, CTRL, GENMASK(5, 0), \ + L##_num##M_CTRL, GENMASK(7, 6), \ + 0, 0, 0, NULL, 0, \ + S2MPG10_PMIC_##_pc_reg, _pc_mask) + +/* LDO with ramp support and possibly GPIO controlled */ +#define s2mpg10_regulator_desc_ldo_ramp(_num, _supply, _vrange, \ + _en_mask, _r_reg, _pc_reg, _pc_mask) \ + s2mpg10_regulator_desc_ldo_cmn(_num, _supply, \ + s2mpg10_reg_ldo_ramp_ops, _vrange, CTRL1, GENMASK(6, 0), \ + LDO_CTRL2, _en_mask, \ + 6250, S2MPG10_PMIC_##_r_reg, GENMASK(1, 0), \ + s2mpg10_ldo_ramp_table, \ + ARRAY_SIZE(s2mpg10_ldo_ramp_table), \ + S2MPG10_PMIC_##_pc_reg, _pc_mask) + +#define S2MPG10_VOLTAGE_RANGE(_prefix, _idx, _offs_uV, _min_uV, \ + _max_uV, _step_uV) \ +static const struct linear_range _prefix##_vranges##_idx[] = { \ + REGULATOR_LINEAR_VRANGE(_offs_uV, _min_uV, _max_uV, _step_uV) \ +}; \ +static const unsigned int _prefix##_vranges##_idx##_count = \ + ((((_max_uV) - (_offs_uV)) / (_step_uV)) + 1) + +/* voltage range for s2mpg10 BUCK 1, 2, 3, 4, 5, 7, 8, 9, 10 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_buck, 1, 200000, 450000, 1300000, STEP_6_25_MV); + +/* voltage range for s2mpg10 BUCK 6 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_buck, 6, 200000, 450000, 1350000, STEP_6_25_MV); + +static const unsigned int s2mpg10_buck_ramp_table[] = { + 6250, 12500, 25000 +}; + +/* voltage range for s2mpg10 LDO 1, 11, 12 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_ldo, 1, 300000, 700000, 1300000, STEP_12_5_MV); + +/* voltage range for s2mpg10 LDO 2, 4, 9, 14, 18, 19, 20, 23, 25, 29, 30, 31 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_ldo, 2, 700000, 1600000, 1950000, STEP_25_MV); + +/* voltage range for s2mpg10 LDO 3, 5, 6, 8, 16, 17, 24, 28 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_ldo, 3, 725000, 725000, 1300000, STEP_12_5_MV); + +/* voltage range for s2mpg10 LDO 7 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_ldo, 7, 300000, 450000, 1300000, STEP_12_5_MV); + +/* voltage range for s2mpg10 LDO 13, 15 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_ldo, 13, 300000, 450000, 950000, STEP_12_5_MV); + +/* voltage range for s2mpg10 LDO 10 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_ldo, 10, 1800000, 1800000, 3350000, STEP_25_MV); + +/* voltage range for s2mpg10 LDO 21, 22, 26, 27 */ +S2MPG10_VOLTAGE_RANGE(s2mpg10_ldo, 21, 1800000, 2500000, 3300000, STEP_25_MV); + +/* possible ramp values for s2mpg10 LDO 1, 7, 11, 12, 13, 15 */ +static const unsigned int s2mpg10_ldo_ramp_table[] = { + 6250, 12500 +}; + +static const struct s2mpg10_regulator_desc s2mpg10_regulators[] = { + s2mpg10_regulator_desc_buck_gpio(1, s2mpg10_buck_vranges1, DVS_RAMP1, + PCTRLSEL1, GENMASK(3, 0)), + s2mpg10_regulator_desc_buck_gpio(2, s2mpg10_buck_vranges1, DVS_RAMP1, + PCTRLSEL1, GENMASK(7, 4)), + s2mpg10_regulator_desc_buck_gpio(3, s2mpg10_buck_vranges1, DVS_RAMP1, + PCTRLSEL2, GENMASK(3, 0)), + s2mpg10_regulator_desc_buck_gpio(4, s2mpg10_buck_vranges1, DVS_RAMP1, + PCTRLSEL2, GENMASK(7, 4)), + s2mpg10_regulator_desc_buck_gpio(5, s2mpg10_buck_vranges1, DVS_RAMP2, + PCTRLSEL3, GENMASK(3, 0)), + s2mpg10_regulator_desc_buck_gpio(6, s2mpg10_buck_vranges6, DVS_RAMP2, + PCTRLSEL3, GENMASK(7, 4)), + s2mpg10_regulator_desc_buck_gpio(7, s2mpg10_buck_vranges1, DVS_RAMP2, + PCTRLSEL4, GENMASK(3, 0)), + s2mpg10_regulator_desc_buck(8, s2mpg10_buck_vranges1, DVS_RAMP2), + s2mpg10_regulator_desc_buck(9, s2mpg10_buck_vranges1, DVS_RAMP3), + s2mpg10_regulator_desc_buck_gpio(10, s2mpg10_buck_vranges1, DVS_RAMP3, + PCTRLSEL4, GENMASK(7, 4)), + /* + * Standard LDO via LxM_CTRL but non-standard (greater) V-range and with + * ramp support. + */ + s2mpg10_regulator_desc_ldo_cmn(1, "vinl3m", s2mpg10_reg_ldo_ramp_ops, + s2mpg10_ldo_vranges1, + CTRL, GENMASK(6, 0), + L1M_CTRL, BIT(7), + 6250, S2MPG10_PMIC_DVS_RAMP6, + GENMASK(5, 4), s2mpg10_ldo_ramp_table, + ARRAY_SIZE(s2mpg10_ldo_ramp_table), + 0, 0), + s2mpg10_regulator_desc_ldo(2, "vinl9m", s2mpg10_ldo_vranges2), + s2mpg10_regulator_desc_ldo_gpio(3, "vinl4m", s2mpg10_ldo_vranges3, + PCTRLSEL5, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_gpio(4, "vinl9m", s2mpg10_ldo_vranges2, + PCTRLSEL5, GENMASK(7, 4)), + s2mpg10_regulator_desc_ldo_gpio(5, "vinl3m", s2mpg10_ldo_vranges3, + PCTRLSEL6, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_gpio(6, "vinl7m", s2mpg10_ldo_vranges3, + PCTRLSEL6, GENMASK(7, 4)), + /* + * Ramp support, possibly GPIO controlled, non-standard (greater) V- + * range and enable reg & mask. + */ + s2mpg10_regulator_desc_ldo_cmn(7, "vinl3m", s2mpg10_reg_ldo_ramp_ops, + s2mpg10_ldo_vranges7, + CTRL, GENMASK(6, 0), + LDO_CTRL1, GENMASK(4, 3), + 6250, S2MPG10_PMIC_DVS_RAMP6, + GENMASK(7, 6), s2mpg10_ldo_ramp_table, + ARRAY_SIZE(s2mpg10_ldo_ramp_table), + S2MPG10_PMIC_PCTRLSEL7, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_gpio(8, "vinl4m", s2mpg10_ldo_vranges3, + PCTRLSEL7, GENMASK(7, 4)), + s2mpg10_regulator_desc_ldo_gpio(9, "vinl10m", s2mpg10_ldo_vranges2, + PCTRLSEL8, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_gpio(10, "vinl15m", s2mpg10_ldo_vranges10, + PCTRLSEL8, GENMASK(7, 4)), + s2mpg10_regulator_desc_ldo_ramp(11, "vinl7m", s2mpg10_ldo_vranges1, + GENMASK(1, 0), DVS_SYNC_CTRL3, + PCTRLSEL9, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_ramp(12, "vinl8m", s2mpg10_ldo_vranges1, + GENMASK(3, 2), DVS_SYNC_CTRL4, + PCTRLSEL9, GENMASK(7, 4)), + s2mpg10_regulator_desc_ldo_ramp(13, "vinl1m", s2mpg10_ldo_vranges13, + GENMASK(5, 4), DVS_SYNC_CTRL5, + PCTRLSEL10, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_gpio(14, "vinl10m", s2mpg10_ldo_vranges2, + PCTRLSEL10, GENMASK(7, 4)), + s2mpg10_regulator_desc_ldo_ramp(15, "vinl2m", s2mpg10_ldo_vranges13, + GENMASK(7, 6), DVS_SYNC_CTRL6, + PCTRLSEL11, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_gpio(16, "vinl5m", s2mpg10_ldo_vranges3, + PCTRLSEL11, GENMASK(7, 4)), + s2mpg10_regulator_desc_ldo_gpio(17, "vinl6m", s2mpg10_ldo_vranges3, + PCTRLSEL12, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_gpio(18, "vinl10m", s2mpg10_ldo_vranges2, + PCTRLSEL12, GENMASK(7, 4)), + s2mpg10_regulator_desc_ldo_gpio(19, "vinl10m", s2mpg10_ldo_vranges2, + PCTRLSEL13, GENMASK(3, 0)), + s2mpg10_regulator_desc_ldo_gpio(20, "vinl10m", s2mpg10_ldo_vranges2, + PCTRLSEL13, GENMASK(7, 4)), + s2mpg10_regulator_desc_ldo(21, "vinl14m", s2mpg10_ldo_vranges21), + s2mpg10_regulator_desc_ldo(22, "vinl15m", s2mpg10_ldo_vranges21), + s2mpg10_regulator_desc_ldo(23, "vinl11m", s2mpg10_ldo_vranges2), + s2mpg10_regulator_desc_ldo(24, "vinl7m", s2mpg10_ldo_vranges3), + s2mpg10_regulator_desc_ldo(25, "vinl10m", s2mpg10_ldo_vranges2), + s2mpg10_regulator_desc_ldo(26, "vinl15m", s2mpg10_ldo_vranges21), + s2mpg10_regulator_desc_ldo(27, "vinl15m", s2mpg10_ldo_vranges21), + s2mpg10_regulator_desc_ldo(28, "vinl7m", s2mpg10_ldo_vranges3), + s2mpg10_regulator_desc_ldo(29, "vinl12m", s2mpg10_ldo_vranges2), + s2mpg10_regulator_desc_ldo(30, "vinl13m", s2mpg10_ldo_vranges2), + s2mpg10_regulator_desc_ldo(31, "vinl11m", s2mpg10_ldo_vranges2) +}; + static const struct regulator_ops s2mps11_ldo_ops = { .list_voltage = regulator_list_voltage_linear, .map_voltage = regulator_map_voltage_linear, @@ -1277,6 +1823,18 @@ static int s2mps11_handle_ext_control(struct s2mps11_info *s2mps11, ret = s2mps14_pmic_enable_ext_control(s2mps11, rdev); break; + case S2MPG10: + /* + * If desc.enable_val is != 0, then external control was + * requested. We can not test s2mpg10_desc::ext_control, + * because 0 is a valid value. + */ + if (!rdev->desc->enable_val) + return 0; + + ret = s2mpg10_enable_ext_control(s2mps11, rdev); + break; + default: return 0; } @@ -1292,6 +1850,7 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) unsigned int rdev_num; int i, ret; const struct regulator_desc *regulators; + const struct s2mpg10_regulator_desc *s2mpg1x_regulators = NULL; s2mps11 = devm_kzalloc(&pdev->dev, sizeof(struct s2mps11_info), GFP_KERNEL); @@ -1300,6 +1859,11 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) s2mps11->dev_type = platform_get_device_id(pdev)->driver_data; switch (s2mps11->dev_type) { + case S2MPG10: + rdev_num = ARRAY_SIZE(s2mpg10_regulators); + s2mpg1x_regulators = s2mpg10_regulators; + BUILD_BUG_ON(ARRAY_SIZE(s2mpg10_regulators) > S2MPS_REGULATOR_MAX); + break; case S2MPS11X: rdev_num = ARRAY_SIZE(s2mps11_regulators); regulators = s2mps11_regulators; @@ -1336,6 +1900,17 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) s2mps11->dev_type); } + if (s2mpg1x_regulators) { + size_t regulators_sz = rdev_num * sizeof(*s2mpg1x_regulators); + + s2mpg1x_regulators = devm_kmemdup(&pdev->dev, + s2mpg1x_regulators, + regulators_sz, + GFP_KERNEL); + if (!s2mpg1x_regulators) + return -ENOMEM; + } + device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); platform_set_drvdata(pdev, s2mps11); @@ -1344,15 +1919,20 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) config.regmap = iodev->regmap_pmic; config.driver_data = s2mps11; for (i = 0; i < rdev_num; i++) { + const struct regulator_desc *rdesc; struct regulator_dev *regulator; + if (s2mpg1x_regulators) + rdesc = &s2mpg1x_regulators[i].desc; + else + rdesc = ®ulators[i]; + regulator = devm_regulator_register(&pdev->dev, - ®ulators[i], &config); + rdesc, &config); if (IS_ERR(regulator)) return dev_err_probe(&pdev->dev, PTR_ERR(regulator), "regulator init failed for %d/%s\n", - regulators[i].id, - regulators[i].name); + rdesc->id, rdesc->name); ret = s2mps11_handle_ext_control(s2mps11, regulator); if (ret < 0) @@ -1363,6 +1943,7 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) } static const struct platform_device_id s2mps11_pmic_id[] = { + { "s2mpg10-regulator", S2MPG10}, { "s2mps11-regulator", S2MPS11X}, { "s2mps13-regulator", S2MPS13X}, { "s2mps14-regulator", S2MPS14X}, diff --git a/include/linux/mfd/samsung/s2mpg10.h b/include/linux/mfd/samsung/s2mpg10.h index aec248c51f36..8e5cf21cbd5a 100644 --- a/include/linux/mfd/samsung/s2mpg10.h +++ b/include/linux/mfd/samsung/s2mpg10.h @@ -290,6 +290,30 @@ enum s2mpg10_pmic_reg { S2MPG10_PMIC_LDO_SENSE4, }; +/* Rail controlled externally, based on PCTRLSELx */ +#define S2MPG10_PMIC_CTRL_ENABLE_EXT BIT(0) + +/* For S2MPG10_PMIC_PCTRLSELx */ +#define S2MPG10_PCTRLSEL_PWREN 0x1 /* PWREN pin */ +#define S2MPG10_PCTRLSEL_PWREN_TRG 0x2 /* PWREN_TRG bit in MIMICKING_CTRL */ +#define S2MPG10_PCTRLSEL_PWREN_MIF 0x3 /* PWREN_MIF pin */ +#define S2MPG10_PCTRLSEL_PWREN_MIF_TRG 0x4 /* PWREN_MIF_TRG bit in MIMICKING_CTRL */ +#define S2MPG10_PCTRLSEL_AP_ACTIVE_N 0x5 /* ~AP_ACTIVE_N pin */ +#define S2MPG10_PCTRLSEL_AP_ACTIVE_N_TRG 0x6 /* ~AP_ACTIVE_N_TRG bit in MIMICKING_CTRL */ +#define S2MPG10_PCTRLSEL_CPUCL1_EN 0x7 /* CPUCL1_EN pin */ +#define S2MPG10_PCTRLSEL_CPUCL1_EN2 0x8 /* CPUCL1_EN & PWREN pins */ +#define S2MPG10_PCTRLSEL_CPUCL2_EN 0x9 /* CPUCL2_EN pin */ +#define S2MPG10_PCTRLSEL_CPUCL2_EN2 0xa /* CPUCL2_E2 & PWREN pins */ +#define S2MPG10_PCTRLSEL_TPU_EN 0xb /* TPU_EN pin */ +#define S2MPG10_PCTRLSEL_TPU_EN2 0xc /* TPU_EN & ~AP_ACTIVE_N pins */ +#define S2MPG10_PCTRLSEL_TCXO_ON 0xd /* TCXO_ON pin */ +#define S2MPG10_PCTRLSEL_TCXO_ON2 0xe /* TCXO_ON & ~AP_ACTIVE_N pins */ + +/* For S2MPG10_PMIC_PCTRLSELx of LDO20M */ +#define S2MPG10_PCTRLSEL_LDO20M_EN2 0x1 /* VLDO20M_EN & LDO20M_SFR */ +#define S2MPG10_PCTRLSEL_LDO20M_EN 0x2 /* VLDO20M_EN pin */ +#define S2MPG10_PCTRLSEL_LDO20M_SFR 0x3 /* LDO20M_SFR bit in LDO_CTRL1 register */ + /* Meter registers (type 0xa00) */ enum s2mpg10_meter_reg { S2MPG10_METER_CTRL1, -- cgit v1.2.3 From 8f23cfbe4463c3de2e552aed106e179c0c932b6e Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:44 +0000 Subject: regulator: s2mps11: refactor S2MPG10 ::set_voltage_time() for S2MPG11 reuse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming S2MPG11 support needs a similar, but different version of ::set_voltage_time(). For S2MPG10, the downwards and upwards ramps for a rail are at different offsets at the same bit positions, while for S2MPG11 the ramps are at the same offset at different bit positions. Refactor the existing version slightly to allow reuse. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-17-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 0b6b28ce6465..5e3584060547 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -566,26 +566,23 @@ static int s2mpg10_regulator_buck_enable_time(struct regulator_dev *rdev) + DIV_ROUND_UP(curr_uV, s2mpg10_desc->enable_ramp_rate)); } -static int s2mpg10_regulator_buck_set_voltage_time(struct regulator_dev *rdev, - int old_uV, int new_uV) +static int s2mpg1x_regulator_buck_set_voltage_time(struct regulator_dev *rdev, + int old_uV, int new_uV, + unsigned int ramp_reg, + unsigned int ramp_mask) { - unsigned int ramp_reg, ramp_sel, ramp_rate; + unsigned int ramp_sel, ramp_rate; int ret; if (old_uV == new_uV) return 0; - ramp_reg = rdev->desc->ramp_reg; - if (old_uV > new_uV) - /* The downwards ramp is at a different offset. */ - ramp_reg += S2MPG10_PMIC_DVS_RAMP4 - S2MPG10_PMIC_DVS_RAMP1; - ret = regmap_read(rdev->regmap, ramp_reg, &ramp_sel); if (ret) return ret; - ramp_sel &= rdev->desc->ramp_mask; - ramp_sel >>= ffs(rdev->desc->ramp_mask) - 1; + ramp_sel &= ramp_mask; + ramp_sel >>= ffs(ramp_mask) - 1; if (ramp_sel >= rdev->desc->n_ramp_values || !rdev->desc->ramp_delay_table) return -EINVAL; @@ -595,6 +592,21 @@ static int s2mpg10_regulator_buck_set_voltage_time(struct regulator_dev *rdev, return DIV_ROUND_UP(abs(new_uV - old_uV), ramp_rate); } +static int s2mpg10_regulator_buck_set_voltage_time(struct regulator_dev *rdev, + int old_uV, int new_uV) +{ + unsigned int ramp_reg; + + ramp_reg = rdev->desc->ramp_reg; + if (old_uV > new_uV) + /* The downwards ramp is at a different offset. */ + ramp_reg += S2MPG10_PMIC_DVS_RAMP4 - S2MPG10_PMIC_DVS_RAMP1; + + return s2mpg1x_regulator_buck_set_voltage_time(rdev, old_uV, new_uV, + ramp_reg, + rdev->desc->ramp_mask); +} + /* * We assign both, ::set_voltage_time() and ::set_voltage_time_sel(), because * only if the latter is != NULL, the regulator core will call neither during -- cgit v1.2.3 From 102dd11fc98261675a0664de1466616d7dad8d91 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:45 +0000 Subject: regulator: s2mps11: refactor S2MPG10 regulator macros for S2MPG11 reuse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rails in the S2MPG11 share a very similar set of properties with S2MPG10 with slight differences. Update the existing macros to allow reuse by the upcoming S2MPG11 driver. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-18-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 69 +++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 5e3584060547..c75ee0bd3437 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -655,31 +655,44 @@ static const struct regulator_ops s2mpg10_reg_buck_ops[] = { * (12.5mV/μs) while our ::set_voltage_time() takes the value in ramp_reg * into account. */ -#define regulator_desc_s2mpg10_buck(_num, _vrange, _r_reg) { \ - .name = "buck"#_num "m", \ - .supply_name = "vinb"#_num "m", \ - .of_match = of_match_ptr("buck"#_num "m"), \ +#define regulator_desc_s2mpg1x_buck_cmn(_name, _id, _supply, _ops, \ + _vrange, _vsel_reg, _vsel_mask, _en_reg, _en_mask, \ + _r_reg, _r_mask, _r_table, _r_table_sz, \ + _en_time) { \ + .name = "buck" _name, \ + .supply_name = _supply, \ + .of_match = of_match_ptr("buck" _name), \ .regulators_node = of_match_ptr("regulators"), \ .of_parse_cb = s2mpg10_of_parse_cb, \ - .id = S2MPG10_BUCK##_num, \ - .ops = &s2mpg10_reg_buck_ops[0], \ + .id = _id, \ + .ops = &(_ops)[0], \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ .linear_ranges = _vrange, \ .n_linear_ranges = ARRAY_SIZE(_vrange), \ .n_voltages = _vrange##_count, \ - .vsel_reg = S2MPG10_PMIC_B##_num##M_OUT1, \ - .vsel_mask = 0xff, \ - .enable_reg = S2MPG10_PMIC_B##_num##M_CTRL, \ - .enable_mask = GENMASK(7, 6), \ - .ramp_reg = S2MPG10_PMIC_##_r_reg, \ - .ramp_mask = s2mpg10_buck_to_ramp_mask(S2MPG10_BUCK##_num \ - - S2MPG10_BUCK1), \ - .ramp_delay_table = s2mpg10_buck_ramp_table, \ - .n_ramp_values = ARRAY_SIZE(s2mpg10_buck_ramp_table), \ - .enable_time = 30, /* + V/enable_ramp_rate */ \ + .vsel_reg = _vsel_reg, \ + .vsel_mask = _vsel_mask, \ + .enable_reg = _en_reg, \ + .enable_mask = _en_mask, \ + .ramp_reg = _r_reg, \ + .ramp_mask = _r_mask, \ + .ramp_delay_table = _r_table, \ + .n_ramp_values = _r_table_sz, \ + .enable_time = _en_time, /* + V/enable_ramp_rate */ \ } +#define regulator_desc_s2mpg10_buck(_num, _vrange, _r_reg) \ + regulator_desc_s2mpg1x_buck_cmn(#_num "m", S2MPG10_BUCK##_num, \ + "vinb"#_num "m", s2mpg10_reg_buck_ops, _vrange, \ + S2MPG10_PMIC_B##_num##M_OUT1, GENMASK(7, 0), \ + S2MPG10_PMIC_B##_num##M_CTRL, GENMASK(7, 6), \ + S2MPG10_PMIC_##_r_reg, \ + s2mpg10_buck_to_ramp_mask(S2MPG10_BUCK##_num \ + - S2MPG10_BUCK1), \ + s2mpg10_buck_ramp_table, \ + ARRAY_SIZE(s2mpg10_buck_ramp_table), 30) + #define s2mpg10_regulator_desc_buck_cm(_num, _vrange, _r_reg) \ .desc = regulator_desc_s2mpg10_buck(_num, _vrange, _r_reg), \ .enable_ramp_rate = 12500 @@ -743,24 +756,24 @@ static const struct regulator_ops s2mpg10_reg_ldo_ramp_ops[] = { } }; -#define regulator_desc_s2mpg10_ldo_cmn(_num, _supply, _ops, _vrange, \ - _vsel_reg_sfx, _vsel_mask, _en_reg, _en_mask, \ +#define regulator_desc_s2mpg1x_ldo_cmn(_name, _id, _supply, _ops, \ + _vrange, _vsel_reg, _vsel_mask, _en_reg, _en_mask, \ _ramp_delay, _r_reg, _r_mask, _r_table, _r_table_sz) { \ - .name = "ldo"#_num "m", \ + .name = "ldo" _name, \ .supply_name = _supply, \ - .of_match = of_match_ptr("ldo"#_num "m"), \ + .of_match = of_match_ptr("ldo" _name), \ .regulators_node = of_match_ptr("regulators"), \ .of_parse_cb = s2mpg10_of_parse_cb, \ - .id = S2MPG10_LDO##_num, \ + .id = _id, \ .ops = &(_ops)[0], \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ .linear_ranges = _vrange, \ .n_linear_ranges = ARRAY_SIZE(_vrange), \ .n_voltages = _vrange##_count, \ - .vsel_reg = S2MPG10_PMIC_L##_num##M_##_vsel_reg_sfx, \ + .vsel_reg = _vsel_reg, \ .vsel_mask = _vsel_mask, \ - .enable_reg = S2MPG10_PMIC_##_en_reg, \ + .enable_reg = _en_reg, \ .enable_mask = _en_mask, \ .ramp_delay = _ramp_delay, \ .ramp_reg = _r_reg, \ @@ -775,10 +788,12 @@ static const struct regulator_ops s2mpg10_reg_ldo_ramp_ops[] = { _ramp_delay, _r_reg, _r_mask, _r_table, _r_table_sz, \ _pc_reg, _pc_mask) \ [S2MPG10_LDO##_num] = { \ - .desc = regulator_desc_s2mpg10_ldo_cmn(_num, _supply, \ - _ops, \ - _vrange, _vsel_reg_sfx, _vsel_mask, \ - _en_reg, _en_mask, \ + .desc = regulator_desc_s2mpg1x_ldo_cmn(#_num "m", \ + S2MPG10_LDO##_num, _supply, _ops, \ + _vrange, \ + S2MPG10_PMIC_L##_num##M_##_vsel_reg_sfx, \ + _vsel_mask, \ + S2MPG10_PMIC_##_en_reg, _en_mask, \ _ramp_delay, _r_reg, _r_mask, _r_table, \ _r_table_sz), \ .pctrlsel_reg = _pc_reg, \ -- cgit v1.2.3 From 979dd8da76eb98b212f4e8cafc3c4019cfa3d93d Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:46 +0000 Subject: regulator: s2mps11: add S2MPG11 regulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The S2MPG11 PMIC is a Power Management IC for mobile applications with buck converters, various LDOs, power meters, and additional GPIO interfaces. It typically complements an S2MPG10 PMIC in a main/sub configuration as the sub-PMIC. It has 12 buck, 1 buck-boost, and 15 LDO rails. Several of these can either be controlled via software (register writes) or via external signals, in particular by: * input pins connected to a main processor's: * GPIO pins * other pins that are e.g. firmware- or power-domain-controlled without explicit driver intervention * a combination of input pins and register writes. Control via input pins allows PMIC rails to be controlled by firmware, e.g. during standby/suspend or as part of power domain handling where otherwise that would not be possible. Additionally toggling a pin is faster than register writes, and it also allows the PMIC to ensure that any necessary timing requirements between rails are respected automatically if multiple rails are to be enabled or disabled quasi simultaneously. This commit implements support for all these rails and control combination. Note1: For an externally controlled rail, the regulator_ops provide an empty ::enable() and no ::disable() implementations, even though Linux can not enable the rail and one might think ::enable could be NULL. Without ops->enable(), the regulator core will assume enabling such a rail failed, though, and in turn never add a reference to its parent (supplier) rail. Once a different (Linux-controlled) sibling (consumer) rail on that same parent rail gets disabled, the parent gets disabled (cutting power to the externally controlled rail although it should stay on), and the system will misbehave. Note2: While external control via input pins appears to exist on other versions of this PMIC, there is more flexibility in this version, in particular there is a selection of input pins to choose from for each rail (which must therefore be configured accordingly if in use), whereas other versions don't have this flexibility. Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-19-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 302 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 301 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index c75ee0bd3437..4a9d70947f17 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -437,9 +438,20 @@ static int s2mpg10_of_parse_cb(struct device_node *np, [S2MPG10_EXTCTRL_LDO20M_EN2] = S2MPG10_PCTRLSEL_LDO20M_EN2, [S2MPG10_EXTCTRL_LDO20M_EN] = S2MPG10_PCTRLSEL_LDO20M_EN, }; + static const u32 ext_control_s2mpg11[] = { + [S2MPG11_EXTCTRL_PWREN] = S2MPG10_PCTRLSEL_PWREN, + [S2MPG11_EXTCTRL_PWREN_MIF] = S2MPG10_PCTRLSEL_PWREN_MIF, + [S2MPG11_EXTCTRL_AP_ACTIVE_N] = S2MPG10_PCTRLSEL_AP_ACTIVE_N, + [S2MPG11_EXTCTRL_G3D_EN] = S2MPG10_PCTRLSEL_CPUCL1_EN, + [S2MPG11_EXTCTRL_G3D_EN2] = S2MPG10_PCTRLSEL_CPUCL1_EN2, + [S2MPG11_EXTCTRL_AOC_VDD] = S2MPG10_PCTRLSEL_CPUCL2_EN, + [S2MPG11_EXTCTRL_AOC_RET] = S2MPG10_PCTRLSEL_CPUCL2_EN2, + [S2MPG11_EXTCTRL_UFS_EN] = S2MPG10_PCTRLSEL_TPU_EN, + [S2MPG11_EXTCTRL_LDO13S_EN] = S2MPG10_PCTRLSEL_TPU_EN2, + }; u32 ext_control; - if (s2mps11->dev_type != S2MPG10) + if (s2mps11->dev_type != S2MPG10 && s2mps11->dev_type != S2MPG11) return 0; if (of_property_read_u32(np, "samsung,ext-control", &ext_control)) @@ -470,6 +482,31 @@ static int s2mpg10_of_parse_cb(struct device_node *np, ext_control = ext_control_s2mpg10[ext_control]; break; + case S2MPG11: + switch (desc->id) { + case S2MPG11_BUCK1 ... S2MPG11_BUCK3: + case S2MPG11_BUCK5: + case S2MPG11_BUCK8: + case S2MPG11_BUCK9: + case S2MPG11_BUCKD: + case S2MPG11_BUCKA: + case S2MPG11_LDO1: + case S2MPG11_LDO2: + case S2MPG11_LDO8: + case S2MPG11_LDO13: + if (ext_control > S2MPG11_EXTCTRL_LDO13S_EN) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + if (ext_control > ARRAY_SIZE(ext_control_s2mpg11)) + return -EINVAL; + ext_control = ext_control_s2mpg11[ext_control]; + break; + default: return -EINVAL; } @@ -507,6 +544,7 @@ static int s2mpg10_enable_ext_control(struct s2mps11_info *s2mps11, switch (s2mps11->dev_type) { case S2MPG10: + case S2MPG11: s2mpg10_desc = to_s2mpg10_regulator_desc(rdev->desc); break; @@ -607,6 +645,21 @@ static int s2mpg10_regulator_buck_set_voltage_time(struct regulator_dev *rdev, rdev->desc->ramp_mask); } +static int s2mpg11_regulator_buck_set_voltage_time(struct regulator_dev *rdev, + int old_uV, int new_uV) +{ + unsigned int ramp_mask; + + ramp_mask = rdev->desc->ramp_mask; + if (old_uV > new_uV) + /* The downwards mask is at a different position. */ + ramp_mask >>= 2; + + return s2mpg1x_regulator_buck_set_voltage_time(rdev, old_uV, new_uV, + rdev->desc->ramp_reg, + ramp_mask); +} + /* * We assign both, ::set_voltage_time() and ::set_voltage_time_sel(), because * only if the latter is != NULL, the regulator core will call neither during @@ -967,6 +1020,246 @@ static const struct s2mpg10_regulator_desc s2mpg10_regulators[] = { s2mpg10_regulator_desc_ldo(31, "vinl11m", s2mpg10_ldo_vranges2) }; +static const struct regulator_ops s2mpg11_reg_buck_ops[] = { + [S2MPG10_REGULATOR_OPS_STD] = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time = s2mpg11_regulator_buck_set_voltage_time, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable_time = s2mpg10_regulator_buck_enable_time, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + }, + [S2MPG10_REGULATOR_OPS_EXTCONTROL] = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .enable = s2mpg10_regulator_enable_nop, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_voltage_time = s2mpg11_regulator_buck_set_voltage_time, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable_time = s2mpg10_regulator_buck_enable_time, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + } +}; + +#define s2mpg11_buck_to_ramp_mask(n) (GENMASK(3, 2) << (((n) % 2) * 4)) + +#define regulator_desc_s2mpg11_buckx(_name, _id, _supply, _vrange, \ + _vsel_reg, _en_reg, _en_mask, _r_reg) \ + regulator_desc_s2mpg1x_buck_cmn(_name, _id, _supply, \ + s2mpg11_reg_buck_ops, _vrange, \ + S2MPG11_PMIC_##_vsel_reg, GENMASK(7, 0), \ + S2MPG11_PMIC_##_en_reg, _en_mask, \ + S2MPG11_PMIC_##_r_reg, \ + s2mpg11_buck_to_ramp_mask(_id - S2MPG11_BUCK1), \ + s2mpg10_buck_ramp_table, \ + ARRAY_SIZE(s2mpg10_buck_ramp_table), 30) + +#define s2mpg11_regulator_desc_buck_xm(_num, _vrange, _vsel_reg_sfx, \ + _en_mask, _r_reg, _en_rrate) \ + .desc = regulator_desc_s2mpg11_buckx(#_num"s", \ + S2MPG11_BUCK##_num, "vinb"#_num"s", \ + _vrange, \ + B##_num##S_##_vsel_reg_sfx, \ + B##_num##S_CTRL, _en_mask, \ + _r_reg), \ + .enable_ramp_rate = _en_rrate + +#define s2mpg11_regulator_desc_buck_cm(_num, _vrange, _vsel_reg_sfx, \ + _en_mask, _r_reg) \ + [S2MPG11_BUCK##_num] = { \ + s2mpg11_regulator_desc_buck_xm(_num, _vrange, \ + _vsel_reg_sfx, _en_mask, _r_reg, 12500), \ + } + +#define s2mpg11_regulator_desc_buckn_cm_gpio(_num, _vrange, \ + _vsel_reg_sfx, _en_mask, _r_reg, _pc_reg, _pc_mask) \ + [S2MPG11_BUCK##_num] = { \ + s2mpg11_regulator_desc_buck_xm(_num, _vrange, \ + _vsel_reg_sfx, _en_mask, _r_reg, 12500), \ + .pctrlsel_reg = S2MPG11_PMIC_##_pc_reg, \ + .pctrlsel_mask = _pc_mask, \ + } + +#define s2mpg11_regulator_desc_buck_vm(_num, _vrange, _vsel_reg_sfx, \ + _en_mask, _r_reg) \ + [S2MPG11_BUCK##_num] = { \ + s2mpg11_regulator_desc_buck_xm(_num, _vrange, \ + _vsel_reg_sfx, _en_mask, _r_reg, 25000), \ + } + +#define s2mpg11_regulator_desc_bucka(_num, _num_lower, _r_reg, \ + _pc_reg, _pc_mask) \ + [S2MPG11_BUCK##_num] = { \ + .desc = regulator_desc_s2mpg11_buckx(#_num_lower, \ + S2MPG11_BUCK##_num, "vinb"#_num_lower, \ + s2mpg11_buck_vranges##_num_lower, \ + BUCK##_num##_OUT, \ + BUCK##_num##_CTRL, GENMASK(7, 6), \ + _r_reg), \ + .enable_ramp_rate = 25000, \ + .pctrlsel_reg = S2MPG11_PMIC_##_pc_reg, \ + .pctrlsel_mask = _pc_mask, \ + } + +#define s2mpg11_regulator_desc_buckboost() \ + [S2MPG11_BUCKBOOST] = { \ + .desc = regulator_desc_s2mpg1x_buck_cmn("boost", \ + S2MPG11_BUCKBOOST, "vinbb", \ + s2mpg10_reg_ldo_ops, \ + s2mpg11_buck_vrangesboost, \ + S2MPG11_PMIC_BB_OUT1, GENMASK(6, 0), \ + S2MPG11_PMIC_BB_CTRL, BIT(7), \ + 0, 0, NULL, 0, 35), \ + .enable_ramp_rate = 17500, \ + } + +#define s2mpg11_regulator_desc_ldo_cmn(_num, _supply, _ops, \ + _vrange, _vsel_reg_sfx, _vsel_mask, _en_reg, _en_mask, \ + _ramp_delay, _r_reg, _r_mask, _r_table, _r_table_sz, \ + _pc_reg, _pc_mask) \ + [S2MPG11_LDO##_num] = { \ + .desc = regulator_desc_s2mpg1x_ldo_cmn(#_num "s", \ + S2MPG11_LDO##_num, _supply, _ops, \ + _vrange, \ + S2MPG11_PMIC_L##_num##S_##_vsel_reg_sfx, \ + _vsel_mask, \ + S2MPG11_PMIC_##_en_reg, _en_mask, \ + _ramp_delay, _r_reg, _r_mask, _r_table, \ + _r_table_sz), \ + .pctrlsel_reg = _pc_reg, \ + .pctrlsel_mask = _pc_mask, \ + } + +/* standard LDO via LxM_CTRL */ +#define s2mpg11_regulator_desc_ldo(_num, _supply, _vrange) \ + s2mpg11_regulator_desc_ldo_cmn(_num, _supply, \ + s2mpg10_reg_ldo_ops, _vrange, CTRL, GENMASK(5, 0), \ + L##_num##S_CTRL, BIT(7), \ + 0, 0, 0, NULL, 0, \ + 0, 0) + +/* standard LDO but possibly GPIO controlled */ +#define s2mpg11_regulator_desc_ldo_gpio(_num, _supply, _vrange, \ + _pc_reg, _pc_mask) \ + s2mpg11_regulator_desc_ldo_cmn(_num, _supply, \ + s2mpg10_reg_ldo_ops, _vrange, CTRL, GENMASK(5, 0), \ + L##_num##S_CTRL, GENMASK(7, 6), \ + 0, 0, 0, NULL, 0, \ + S2MPG11_PMIC_##_pc_reg, _pc_mask) + +/* LDO with ramp support and possibly GPIO controlled */ +#define s2mpg11_regulator_desc_ldo_ramp(_num, _supply, _vrange, \ + _en_mask, _r_reg, _pc_reg, _pc_mask) \ + s2mpg11_regulator_desc_ldo_cmn(_num, _supply, \ + s2mpg10_reg_ldo_ramp_ops, _vrange, CTRL1, GENMASK(6, 0), \ + LDO_CTRL1, _en_mask, \ + 6250, S2MPG11_PMIC_##_r_reg, GENMASK(1, 0), \ + s2mpg10_ldo_ramp_table, \ + ARRAY_SIZE(s2mpg10_ldo_ramp_table), \ + S2MPG11_PMIC_##_pc_reg, _pc_mask) + +/* voltage range for s2mpg11 BUCK 1, 2, 3, 4, 8, 9, 10 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_buck, 1, 200000, 450000, 1300000, STEP_6_25_MV); + +/* voltage range for s2mpg11 BUCK 5 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_buck, 5, 200000, 400000, 1300000, STEP_6_25_MV); + +/* voltage range for s2mpg11 BUCK 6 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_buck, 6, 200000, 1000000, 1500000, STEP_6_25_MV); + +/* voltage range for s2mpg11 BUCK 7 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_buck, 7, 600000, 1500000, 2200000, STEP_12_5_MV); + +/* voltage range for s2mpg11 BUCK D */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_buck, d, 600000, 2400000, 3300000, STEP_12_5_MV); + +/* voltage range for s2mpg11 BUCK A */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_buck, a, 600000, 1700000, 2100000, STEP_12_5_MV); + +/* voltage range for s2mpg11 BUCK BOOST */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_buck, boost, + 2600000, 3000000, 3600000, STEP_12_5_MV); + +/* voltage range for s2mpg11 LDO 1, 2 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_ldo, 1, 300000, 450000, 950000, STEP_12_5_MV); + +/* voltage range for s2mpg11 LDO 3, 7, 10, 11, 12, 14, 15 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_ldo, 3, 700000, 1600000, 1950000, STEP_25_MV); + +/* voltage range for s2mpg11 LDO 4, 6 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_ldo, 4, 1800000, 2500000, 3300000, STEP_25_MV); + +/* voltage range for s2mpg11 LDO 5 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_ldo, 5, 1600000, 1600000, 1950000, STEP_12_5_MV); + +/* voltage range for s2mpg11 LDO 8 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_ldo, 8, 979600, 1130400, 1281200, 5800); + +/* voltage range for s2mpg11 LDO 9 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_ldo, 9, 725000, 725000, 1300000, STEP_12_5_MV); + +/* voltage range for s2mpg11 LDO 13 */ +S2MPG10_VOLTAGE_RANGE(s2mpg11_ldo, 13, 1800000, 1800000, 3350000, STEP_25_MV); + +static const struct s2mpg10_regulator_desc s2mpg11_regulators[] = { + s2mpg11_regulator_desc_buckboost(), + s2mpg11_regulator_desc_buckn_cm_gpio(1, s2mpg11_buck_vranges1, + OUT1, GENMASK(7, 6), DVS_RAMP1, + PCTRLSEL1, GENMASK(3, 0)), + s2mpg11_regulator_desc_buckn_cm_gpio(2, s2mpg11_buck_vranges1, + OUT1, GENMASK(7, 6), DVS_RAMP1, + PCTRLSEL1, GENMASK(7, 4)), + s2mpg11_regulator_desc_buckn_cm_gpio(3, s2mpg11_buck_vranges1, + OUT1, GENMASK(7, 6), DVS_RAMP2, + PCTRLSEL2, GENMASK(3, 0)), + s2mpg11_regulator_desc_buck_cm(4, s2mpg11_buck_vranges1, + OUT, BIT(7), DVS_RAMP2), + s2mpg11_regulator_desc_buckn_cm_gpio(5, s2mpg11_buck_vranges5, + OUT, GENMASK(7, 6), DVS_RAMP3, + PCTRLSEL2, GENMASK(7, 4)), + s2mpg11_regulator_desc_buck_cm(6, s2mpg11_buck_vranges6, + OUT1, BIT(7), DVS_RAMP3), + s2mpg11_regulator_desc_buck_vm(7, s2mpg11_buck_vranges7, + OUT1, BIT(7), DVS_RAMP4), + s2mpg11_regulator_desc_buckn_cm_gpio(8, s2mpg11_buck_vranges1, + OUT1, GENMASK(7, 6), DVS_RAMP4, + PCTRLSEL3, GENMASK(3, 0)), + s2mpg11_regulator_desc_buckn_cm_gpio(9, s2mpg11_buck_vranges1, + OUT1, GENMASK(7, 6), DVS_RAMP5, + PCTRLSEL3, GENMASK(7, 4)), + s2mpg11_regulator_desc_buck_cm(10, s2mpg11_buck_vranges1, + OUT, BIT(7), DVS_RAMP5), + s2mpg11_regulator_desc_bucka(D, d, DVS_RAMP6, PCTRLSEL4, GENMASK(3, 0)), + s2mpg11_regulator_desc_bucka(A, a, DVS_RAMP6, PCTRLSEL4, GENMASK(7, 4)), + s2mpg11_regulator_desc_ldo_ramp(1, "vinl1s", s2mpg11_ldo_vranges1, + GENMASK(5, 4), DVS_SYNC_CTRL1, + PCTRLSEL5, GENMASK(3, 0)), + s2mpg11_regulator_desc_ldo_ramp(2, "vinl1s", s2mpg11_ldo_vranges1, + GENMASK(7, 6), DVS_SYNC_CTRL2, + PCTRLSEL5, GENMASK(7, 4)), + s2mpg11_regulator_desc_ldo(3, "vinl3s", s2mpg11_ldo_vranges3), + s2mpg11_regulator_desc_ldo(4, "vinl5s", s2mpg11_ldo_vranges4), + s2mpg11_regulator_desc_ldo(5, "vinl3s", s2mpg11_ldo_vranges5), + s2mpg11_regulator_desc_ldo(6, "vinl5s", s2mpg11_ldo_vranges4), + s2mpg11_regulator_desc_ldo(7, "vinl3s", s2mpg11_ldo_vranges3), + s2mpg11_regulator_desc_ldo_gpio(8, "vinl2s", s2mpg11_ldo_vranges8, + PCTRLSEL6, GENMASK(3, 0)), + s2mpg11_regulator_desc_ldo(9, "vinl2s", s2mpg11_ldo_vranges9), + s2mpg11_regulator_desc_ldo(10, "vinl4s", s2mpg11_ldo_vranges3), + s2mpg11_regulator_desc_ldo(11, "vinl4s", s2mpg11_ldo_vranges3), + s2mpg11_regulator_desc_ldo(12, "vinl4s", s2mpg11_ldo_vranges3), + s2mpg11_regulator_desc_ldo_gpio(13, "vinl6s", s2mpg11_ldo_vranges13, + PCTRLSEL6, GENMASK(7, 4)), + s2mpg11_regulator_desc_ldo(14, "vinl4s", s2mpg11_ldo_vranges3), + s2mpg11_regulator_desc_ldo(15, "vinl3s", s2mpg11_ldo_vranges3) +}; + static const struct regulator_ops s2mps11_ldo_ops = { .list_voltage = regulator_list_voltage_linear, .map_voltage = regulator_map_voltage_linear, @@ -1851,6 +2144,7 @@ static int s2mps11_handle_ext_control(struct s2mps11_info *s2mps11, break; case S2MPG10: + case S2MPG11: /* * If desc.enable_val is != 0, then external control was * requested. We can not test s2mpg10_desc::ext_control, @@ -1891,6 +2185,11 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) s2mpg1x_regulators = s2mpg10_regulators; BUILD_BUG_ON(ARRAY_SIZE(s2mpg10_regulators) > S2MPS_REGULATOR_MAX); break; + case S2MPG11: + rdev_num = ARRAY_SIZE(s2mpg11_regulators); + s2mpg1x_regulators = s2mpg11_regulators; + BUILD_BUG_ON(ARRAY_SIZE(s2mpg11_regulators) > S2MPS_REGULATOR_MAX); + break; case S2MPS11X: rdev_num = ARRAY_SIZE(s2mps11_regulators); regulators = s2mps11_regulators; @@ -1971,6 +2270,7 @@ static int s2mps11_pmic_probe(struct platform_device *pdev) static const struct platform_device_id s2mps11_pmic_id[] = { { "s2mpg10-regulator", S2MPG10}, + { "s2mpg11-regulator", S2MPG11}, { "s2mps11-regulator", S2MPS11X}, { "s2mps13-regulator", S2MPS13X}, { "s2mps14-regulator", S2MPS14X}, -- cgit v1.2.3 From fe8429a2717fc01082502b0adf680a50b230eff7 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Thu, 22 Jan 2026 15:43:47 +0000 Subject: regulator: s2mps11: more descriptive gpio consumer name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, GPIOs claimed by this driver for external rail control all show up with "s2mps11-regulator" as consumer, which is not very informative. Switch to using the regulator name via desc->name instead, using the device name as fallback. Reviewed-by: Bartosz Golaszewski Signed-off-by: André Draszik Link: https://patch.msgid.link/20260122-s2mpg1x-regulators-v7-20-3b1f9831fffd@linaro.org Signed-off-by: Mark Brown --- drivers/regulator/s2mps11.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/s2mps11.c b/drivers/regulator/s2mps11.c index 4a9d70947f17..2d5510acd078 100644 --- a/drivers/regulator/s2mps11.c +++ b/drivers/regulator/s2mps11.c @@ -362,7 +362,8 @@ static int s2mps11_of_parse_gpiod(struct device_node *np, ena_gpiod = fwnode_gpiod_get_index(of_fwnode_handle(np), con_id, 0, GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_NONEXCLUSIVE, - "s2mps11-regulator"); + desc->name + ? : dev_name(config->dev)); if (IS_ERR(ena_gpiod)) { ret = PTR_ERR(ena_gpiod); -- cgit v1.2.3