From f7fbf3091f4cc4133574852f655593e1613d1af0 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Tue, 29 Jul 2025 14:31:40 -0300 Subject: [PATCH 01/28] rust: regulator: remove needless &mut from member functions Regulator functions like "regulator_enable()" and "regulator_disable()" already provide their own locking through "regulator_lock_dependent()", so we can safely call the Rust API with a shared reference. This was already the case with Regulator::set_voltage() on the Rust side, but it was forgotten for Regulator::enable() and Regulator::disable(). Signed-off-by: Daniel Almeida Link: https://patch.msgid.link/20250729-regulator-send-sync-v1-1-8bcbd546b940@collabora.com Reviewed-by: Alexandre Courbot Reviewed-by: Alice Ryhl Signed-off-by: Mark Brown --- rust/kernel/regulator.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rust/kernel/regulator.rs b/rust/kernel/regulator.rs index 65f3a125348f..d56aa229e838 100644 --- a/rust/kernel/regulator.rs +++ b/rust/kernel/regulator.rs @@ -203,20 +203,20 @@ pub struct Error { /// // A fictictious probe function that obtains a regulator and sets it up. /// fn probe(dev: &Device) -> Result { /// // Obtain a reference to a (fictitious) regulator. -/// let mut regulator = Regulator::::get(dev, c_str!("vcc"))?; +/// let regulator = Regulator::::get(dev, c_str!("vcc"))?; /// /// Ok(PrivateData { regulator }) /// } /// /// // A fictictious function that indicates that the device is going to be used. -/// fn open(dev: &Device, data: &mut PrivateData) -> Result { +/// fn open(dev: &Device, data: &PrivateData) -> Result { /// // Increase the `enabled` reference count. /// data.regulator.enable()?; /// /// Ok(()) /// } /// -/// fn close(dev: &Device, data: &mut PrivateData) -> Result { +/// fn close(dev: &Device, data: &PrivateData) -> Result { /// // Decrease the `enabled` reference count. /// data.regulator.disable()?; /// @@ -289,12 +289,12 @@ impl Regulator { }) } - fn enable_internal(&mut self) -> Result { + fn enable_internal(&self) -> Result { // SAFETY: Safe as per the type invariants of `Regulator`. to_result(unsafe { bindings::regulator_enable(self.inner.as_ptr()) }) } - fn disable_internal(&mut self) -> Result { + fn disable_internal(&self) -> Result { // SAFETY: Safe as per the type invariants of `Regulator`. to_result(unsafe { bindings::regulator_disable(self.inner.as_ptr()) }) } @@ -310,7 +310,7 @@ impl Regulator { pub fn try_into_enabled(self) -> Result, Error> { // We will be transferring the ownership of our `regulator_get()` count to // `Regulator`. - let mut regulator = ManuallyDrop::new(self); + let regulator = ManuallyDrop::new(self); regulator .enable_internal() @@ -339,7 +339,7 @@ impl Regulator { pub fn try_into_disabled(self) -> Result, Error> { // We will be transferring the ownership of our `regulator_get()` count // to `Regulator`. - let mut regulator = ManuallyDrop::new(self); + let regulator = ManuallyDrop::new(self); regulator .disable_internal() @@ -366,12 +366,12 @@ impl Regulator { } /// Increases the `enabled` reference count. - pub fn enable(&mut self) -> Result { + pub fn enable(&self) -> Result { self.enable_internal() } /// Decreases the `enabled` reference count. - pub fn disable(&mut self) -> Result { + pub fn disable(&self) -> Result { self.disable_internal() } } From 9a200cbdb54349909a42b45379e792e4b39dd223 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Tue, 29 Jul 2025 14:31:41 -0300 Subject: [PATCH 02/28] rust: regulator: implement Send and Sync for Regulator Sending a &Regulator to another thread is safe, as the regulator core will properly handle the locking for us. Additionally, there are no restrictions that prevents sending a Regulator to another thread. Given these two facts, implement Send and Sync. Signed-off-by: Daniel Almeida Link: https://patch.msgid.link/20250729-regulator-send-sync-v1-2-8bcbd546b940@collabora.com Reviewed-by: Alexandre Courbot Reviewed-by: Alice Ryhl Signed-off-by: Mark Brown --- rust/kernel/regulator.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/kernel/regulator.rs b/rust/kernel/regulator.rs index d56aa229e838..704147e18bfc 100644 --- a/rust/kernel/regulator.rs +++ b/rust/kernel/regulator.rs @@ -398,6 +398,14 @@ impl Drop for Regulator { } } +// SAFETY: It is safe to send a `Regulator` across threads. In particular, a +// Regulator can be dropped from any thread. +unsafe impl Send for Regulator {} + +// SAFETY: It is safe to send a &Regulator across threads because the C side +// handles its own locking. +unsafe impl Sync for Regulator {} + /// A voltage. /// /// This type represents a voltage value in microvolts. From 82f0907931f016c04bcb992f764bd65992c7008e Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Thu, 31 Jul 2025 18:44:41 +0800 Subject: [PATCH 03/28] dt-bindings: regulator: add PF0900 regulator yaml Add device binding doc for PF0900 PMIC driver. Can not get the I2C_CRC_EN config by reading register. If the PMIC OTP_I2C_CRC_EN is enable, need to add the nxp,i2c-crc-enable property. Signed-off-by: Joy Zou Link: https://patch.msgid.link/20250731-b4-pf09-v2-v3-1-4c2659516582@nxp.com Reviewed-by: Rob Herring (Arm) Signed-off-by: Mark Brown --- .../bindings/regulator/nxp,pf0900.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/nxp,pf0900.yaml diff --git a/Documentation/devicetree/bindings/regulator/nxp,pf0900.yaml b/Documentation/devicetree/bindings/regulator/nxp,pf0900.yaml new file mode 100644 index 000000000000..8c8fc2cd4ced --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/nxp,pf0900.yaml @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/nxp,pf0900.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP PF0900 Power Management Integrated Circuit regulators + +maintainers: + - Joy Zou + +description: + The PF0900 is a power management integrated circuit (PMIC) optimized + for high performance i.MX9x based applications. It features five high + efficiency buck converters, three linear and one vaon regulators. It + provides low quiescent current in Standby and low power off Modes. + +properties: + compatible: + enum: + - nxp,pf0900 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + regulators: + type: object + additionalProperties: false + + properties: + vaon: + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + + patternProperties: + "^ldo[1-3]$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + + "^sw[1-5]$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + + nxp,i2c-crc-enable: + type: boolean + description: + The CRC enabled during register read/write. Controlled by customer + unviewable fuse bits OTP_I2C_CRC_EN. Check chip part number. + +required: + - compatible + - reg + - interrupts + - regulators + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pmic@8 { + compatible = "nxp,pf0900"; + reg = <0x08>; + interrupt-parent = <&pcal6524>; + interrupts = <89 IRQ_TYPE_LEVEL_LOW>; + nxp,i2c-crc-enable; + + regulators { + vaon { + regulator-name = "VAON"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + sw1 { + regulator-name = "SW1"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <1950>; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-max-microvolt = <650000>; + regulator-suspend-min-microvolt = <650000>; + }; + }; + + sw2 { + regulator-name = "SW2"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <1950>; + }; + + sw3 { + regulator-name = "SW3"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <1950>; + }; + + sw4 { + regulator-name = "SW4"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <1950>; + }; + + sw5 { + regulator-name = "SW5"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <1950>; + }; + + ldo1 { + regulator-name = "LDO1"; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo2 { + regulator-name = "LDO2"; + regulator-min-microvolt = <650000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo3 { + regulator-name = "LDO3"; + regulator-min-microvolt = <650000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + }; + }; + }; From 162e23657e5379f07c6404dbfbf4367cb438ea7d Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Thu, 31 Jul 2025 18:44:42 +0800 Subject: [PATCH 04/28] regulator: pf0900: Add PMIC PF0900 support The PF0900 is a power management integrated circuit (PMIC) optimized for high performance i.MX9x based applications. It features five high efficiency buck converters, three linear and one vaon regulators. It provides low quiescent current in Standby and low power off Modes. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202507221720.jxsGRfcE-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202506181134.0Hkvy7CK-lkp@intel.com/ Signed-off-by: Joy Zou Link: https://patch.msgid.link/20250731-b4-pf09-v2-v3-2-4c2659516582@nxp.com Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 8 + drivers/regulator/Makefile | 1 + drivers/regulator/pf0900-regulator.c | 975 +++++++++++++++++++++++++++ 3 files changed, 984 insertions(+) create mode 100644 drivers/regulator/pf0900-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index eaa6df1c9f80..b474b4bf63b3 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1006,6 +1006,14 @@ config REGULATOR_PCAP This driver provides support for the voltage regulators of the PCAP2 PMIC. +config REGULATOR_PF0900 + tristate "NXP PF0900/PF0901/PF09XX regulator driver" + depends on I2C + select REGMAP_I2C + help + Say y here to support the NXP PF0900/PF0901/PF09XX PMIC + regulator driver. + config REGULATOR_PF8X00 tristate "NXP PF8100/PF8121A/PF8200 regulator driver" depends on I2C && OF diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index be98b29d6675..74b029d29a8b 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -124,6 +124,7 @@ obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o obj-$(CONFIG_REGULATOR_QCOM_USB_VBUS) += qcom_usb_vbus-regulator.o obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o obj-$(CONFIG_REGULATOR_PCA9450) += pca9450-regulator.o +obj-$(CONFIG_REGULATOR_PF0900) += pf0900-regulator.o obj-$(CONFIG_REGULATOR_PF9453) += pf9453-regulator.o obj-$(CONFIG_REGULATOR_PF8X00) += pf8x00-regulator.o obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o diff --git a/drivers/regulator/pf0900-regulator.c b/drivers/regulator/pf0900-regulator.c new file mode 100644 index 000000000000..b5effee32917 --- /dev/null +++ b/drivers/regulator/pf0900-regulator.c @@ -0,0 +1,975 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2025 NXP. +// NXP PF0900 pmic driver + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum pf0900_regulators { + PF0900_SW1 = 0, + PF0900_SW2, + PF0900_SW3, + PF0900_SW4, + PF0900_SW5, + PF0900_LDO1, + PF0900_LDO2, + PF0900_LDO3, + PF0900_VAON, + PF0900_REGULATOR_CNT, +}; + +enum { + PF0900_DVS_LEVEL_RUN = 0, + PF0900_DVS_LEVEL_STANDBY, + PF0900_DVS_LEVEL_MAX, +}; + + +#define PF0900_VAON_VOLTAGE_NUM 0x03 +#define PF0900_SW_VOLTAGE_NUM 0x100 +#define PF0900_LDO_VOLTAGE_NUM 0x20 + +#define REGU_SW_CNT 0x5 +#define REGU_LDO_VAON_CNT 0x4 + +enum { + PF0900_REG_DEV_ID = 0x00, + PF0900_REG_DEV_FAM = 0x01, + PF0900_REG_REV_ID = 0x02, + PF0900_REG_PROG_ID1 = 0x03, + PF0900_REG_PROG_ID2 = 0x04, + PF0900_REG_SYSTEM_INT = 0x05, + PF0900_REG_STATUS1_INT = 0x06, + PF0900_REG_STATUS1_MSK = 0x07, + PF0900_REG_STATUS1_SNS = 0x08, + PF0900_REG_STATUS2_INT = 0x09, + PF0900_REG_STATUS2_MSK = 0x0A, + PF0900_REG_STATUS2_SNS = 0x0B, + PF0900_REG_STATUS3_INT = 0x0C, + PF0900_REG_STATUS3_MSK = 0x0D, + PF0900_REG_SW_MODE_INT = 0x0E, + PF0900_REG_SW_MODE_MSK = 0x0F, + PF0900_REG_SW_ILIM_INT = 0x10, + PF0900_REG_SW_ILIM_MSK = 0x11, + PF0900_REG_SW_ILIM_SNS = 0x12, + PF0900_REG_LDO_ILIM_INT = 0x13, + PF0900_REG_LDO_ILIM_MSK = 0x14, + PF0900_REG_LDO_ILIM_SNS = 0x15, + PF0900_REG_SW_UV_INT = 0x16, + PF0900_REG_SW_UV_MSK = 0x17, + PF0900_REG_SW_UV_SNS = 0x18, + PF0900_REG_SW_OV_INT = 0x19, + PF0900_REG_SW_OV_MSK = 0x1A, + PF0900_REG_SW_OV_SNS = 0x1B, + PF0900_REG_LDO_UV_INT = 0x1C, + PF0900_REG_LDO_UV_MSK = 0x1D, + PF0900_REG_LDO_UV_SNS = 0x1E, + PF0900_REG_LDO_OV_INT = 0x1F, + PF0900_REG_LDO_OV_MSK = 0x20, + PF0900_REG_LDO_OV_SNS = 0x21, + PF0900_REG_PWRON_INT = 0x22, + PF0900_REG_IO_INT = 0x24, + PF0900_REG_IO_MSK = 0x25, + PF0900_REG_IO_SNS = 0x26, + PF0900_REG_IOSHORT_SNS = 0x27, + PF0900_REG_ABIST_OV1 = 0x28, + PF0900_REG_ABIST_OV2 = 0x29, + PF0900_REG_ABIST_UV1 = 0x2A, + PF0900_REG_ABIST_UV2 = 0x2B, + PF0900_REG_ABIST_IO = 0x2C, + PF0900_REG_TEST_FLAGS = 0x2D, + PF0900_REG_HFAULT_FLAGS = 0x2E, + PF0900_REG_FAULT_FLAGS = 0x2F, + PF0900_REG_FS0B_CFG = 0x30, + PF0900_REG_FCCU_CFG = 0x31, + PF0900_REG_RSTB_CFG1 = 0x32, + PF0900_REG_SYSTEM_CMD = 0x33, + PF0900_REG_FS0B_CMD = 0x34, + PF0900_REG_SECURE_WR1 = 0x35, + PF0900_REG_SECURE_WR2 = 0x36, + PF0900_REG_VMON_CFG1 = 0x37, + PF0900_REG_SYS_CFG1 = 0x38, + PF0900_REG_GPO_CFG = 0x39, + PF0900_REG_GPO_CTRL = 0x3A, + PF0900_REG_PWRUP_CFG = 0x3B, + PF0900_REG_RSTB_PWRUP = 0x3C, + PF0900_REG_GPIO1_PWRUP = 0x3D, + PF0900_REG_GPIO2_PWRUP = 0x3E, + PF0900_REG_GPIO3_PWRUP = 0x3F, + PF0900_REG_GPIO4_PWRUP = 0x40, + PF0900_REG_VMON1_PWRUP = 0x41, + PF0900_REG_VMON2_PWRUP = 0x42, + PF0900_REG_SW1_PWRUP = 0x43, + PF0900_REG_SW2_PWRUP = 0x44, + PF0900_REG_SW3_PWRUP = 0x45, + PF0900_REG_SW4_PWRUP = 0x46, + PF0900_REG_SW5_PWRUP = 0x47, + PF0900_REG_LDO1_PWRUP = 0x48, + PF0900_REG_LDO2_PWRUP = 0x49, + PF0900_REG_LDO3_PWRUP = 0x4A, + PF0900_REG_VAON_PWRUP = 0x4B, + PF0900_REG_FREQ_CTRL = 0x4C, + PF0900_REG_PWRON_CFG = 0x4D, + PF0900_REG_WD_CTRL1 = 0x4E, + PF0900_REG_WD_CTRL2 = 0x4F, + PF0900_REG_WD_CFG1 = 0x50, + PF0900_REG_WD_CFG2 = 0x51, + PF0900_REG_WD_CNT1 = 0x52, + PF0900_REG_WD_CNT2 = 0x53, + PF0900_REG_FAULT_CFG = 0x54, + PF0900_REG_FAULT_CNT = 0x55, + PF0900_REG_DFS_CNT = 0x56, + PF0900_REG_AMUX_CFG = 0x57, + PF0900_REG_VMON1_RUN_CFG = 0x58, + PF0900_REG_VMON1_STBY_CFG = 0x59, + PF0900_REG_VMON1_CTRL = 0x5A, + PF0900_REG_VMON2_RUN_CFG = 0x5B, + PF0900_REG_VMON2_STBY_CFG = 0x5C, + PF0900_REG_VMON2_CTRL = 0x5D, + PF0900_REG_SW1_VRUN = 0x5E, + PF0900_REG_SW1_VSTBY = 0x5F, + PF0900_REG_SW1_MODE = 0x60, + PF0900_REG_SW1_CFG1 = 0x61, + PF0900_REG_SW1_CFG2 = 0x62, + PF0900_REG_SW2_VRUN = 0x63, + PF0900_REG_SW2_VSTBY = 0x64, + PF0900_REG_SW2_MODE = 0x65, + PF0900_REG_SW2_CFG1 = 0x66, + PF0900_REG_SW2_CFG2 = 0x67, + PF0900_REG_SW3_VRUN = 0x68, + PF0900_REG_SW3_VSTBY = 0x69, + PF0900_REG_SW3_MODE = 0x6A, + PF0900_REG_SW3_CFG1 = 0x6B, + PF0900_REG_SW3_CFG2 = 0x6C, + PF0900_REG_SW4_VRUN = 0x6D, + PF0900_REG_SW4_VSTBY = 0x6E, + PF0900_REG_SW4_MODE = 0x6F, + PF0900_REG_SW4_CFG1 = 0x70, + PF0900_REG_SW4_CFG2 = 0x71, + PF0900_REG_SW5_VRUN = 0x72, + PF0900_REG_SW5_VSTBY = 0x73, + PF0900_REG_SW5_MODE = 0x74, + PF0900_REG_SW5_CFG1 = 0x75, + PF0900_REG_SW5_CFG2 = 0x76, + PF0900_REG_LDO1_RUN = 0x77, + PF0900_REG_LDO1_STBY = 0x78, + PF0900_REG_LDO1_CFG2 = 0x79, + PF0900_REG_LDO2_RUN = 0x7A, + PF0900_REG_LDO2_STBY = 0x7B, + PF0900_REG_LDO2_CFG2 = 0x7C, + PF0900_REG_LDO3_RUN = 0x7D, + PF0900_REG_LDO3_STBY = 0x7E, + PF0900_REG_LDO3_CFG2 = 0x7F, + PF0900_REG_VAON_CFG1 = 0x80, + PF0900_REG_VAON_CFG2 = 0x81, + PF0900_REG_SYS_DIAG = 0x82, + PF0900_MAX_REGISTER, +}; + +/* PF0900 SW MODE */ +#define SW_RUN_MODE_OFF 0x00 +#define SW_RUN_MODE_PWM 0x01 +#define SW_RUN_MODE_PFM 0x02 +#define SW_STBY_MODE_OFF 0x00 +#define SW_STBY_MODE_PWM 0x04 +#define SW_STBY_MODE_PFM 0x08 + +/* PF0900 SW MODE MASK */ +#define SW_RUN_MODE_MASK GENMASK(1, 0) +#define SW_STBY_MODE_MASK GENMASK(3, 2) + +/* PF0900 SW VRUN/VSTBY MASK */ +#define PF0900_SW_VOL_MASK GENMASK(7, 0) + +/* PF0900_REG_VAON_CFG1 bits */ +#define PF0900_VAON_1P8V 0x01 + +#define PF0900_VAON_MASK GENMASK(1, 0) + +/* PF0900_REG_SWX_CFG1 MASK */ +#define PF0900_SW_DVS_MASK GENMASK(4, 3) + +/* PF0900_REG_LDO_RUN MASK */ +#define VLDO_RUN_MASK GENMASK(4, 0) +#define LDO_RUN_EN_MASK BIT(5) + +/* PF0900_REG_STATUS1_INT bits */ +#define PF0900_IRQ_PWRUP BIT(3) + +/* PF0900_REG_ILIM_INT bits */ +#define PF0900_IRQ_SW1_IL BIT(0) +#define PF0900_IRQ_SW2_IL BIT(1) +#define PF0900_IRQ_SW3_IL BIT(2) +#define PF0900_IRQ_SW4_IL BIT(3) +#define PF0900_IRQ_SW5_IL BIT(4) + +#define PF0900_IRQ_LDO1_IL BIT(0) +#define PF0900_IRQ_LDO2_IL BIT(1) +#define PF0900_IRQ_LDO3_IL BIT(2) + +/* PF0900_REG_UV_INT bits */ +#define PF0900_IRQ_SW1_UV BIT(0) +#define PF0900_IRQ_SW2_UV BIT(1) +#define PF0900_IRQ_SW3_UV BIT(2) +#define PF0900_IRQ_SW4_UV BIT(3) +#define PF0900_IRQ_SW5_UV BIT(4) + +#define PF0900_IRQ_LDO1_UV BIT(0) +#define PF0900_IRQ_LDO2_UV BIT(1) +#define PF0900_IRQ_LDO3_UV BIT(2) +#define PF0900_IRQ_VAON_UV BIT(3) + +/* PF0900_REG_OV_INT bits */ +#define PF0900_IRQ_SW1_OV BIT(0) +#define PF0900_IRQ_SW2_OV BIT(1) +#define PF0900_IRQ_SW3_OV BIT(2) +#define PF0900_IRQ_SW4_OV BIT(3) +#define PF0900_IRQ_SW5_OV BIT(4) + +#define PF0900_IRQ_LDO1_OV BIT(0) +#define PF0900_IRQ_LDO2_OV BIT(1) +#define PF0900_IRQ_LDO3_OV BIT(2) +#define PF0900_IRQ_VAON_OV BIT(3) + +struct pf0900_regulator_desc { + struct regulator_desc desc; + unsigned int suspend_enable_mask; + unsigned int suspend_voltage_reg; + unsigned int suspend_voltage_cache; +}; + +struct pf0900_drvdata { + const struct pf0900_regulator_desc *desc; + unsigned int rcnt; +}; + +struct pf0900 { + struct device *dev; + struct regmap *regmap; + const struct pf0900_drvdata *drvdata; + struct regulator_dev *rdevs[PF0900_REGULATOR_CNT]; + int irq; + unsigned short addr; + bool crc_en; +}; + +enum pf0900_regulator_type { + PF0900_SW = 0, + PF0900_LDO, +}; + +#define PF0900_REGU_IRQ(_reg, _type, _event) \ + { \ + .reg = _reg, \ + .type = _type, \ + .event = _event, \ + } + +struct pf0900_regulator_irq { + unsigned int reg; + unsigned int type; + unsigned int event; +}; + +static const struct regmap_range pf0900_range = { + .range_min = PF0900_REG_DEV_ID, + .range_max = PF0900_REG_SYS_DIAG, +}; + +static const struct regmap_access_table pf0900_volatile_regs = { + .yes_ranges = &pf0900_range, + .n_yes_ranges = 1, +}; + +static const struct regmap_config pf0900_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &pf0900_volatile_regs, + .max_register = PF0900_MAX_REGISTER - 1, + .cache_type = REGCACHE_MAPLE, +}; + +static uint8_t crc8_j1850(unsigned short addr, unsigned int reg, + unsigned int val) +{ + uint8_t crcBuf[3]; + uint8_t t_crc; + uint8_t i, j; + + crcBuf[0] = addr; + crcBuf[1] = reg; + crcBuf[2] = val; + t_crc = 0xFF; + + /* + * The CRC calculation is based on the standard CRC-8-SAE as + * defined in the SAE-J1850 specification with the following + * characteristics. + * Polynomial = 0x1D + * Initial Value = 0xFF + * The CRC byte is calculated by shifting 24-bit data through + * the CRC polynomial.The 24-bits package is built as follows: + * DEVICE_ADDR[b8] + REGISTER_ADDR [b8] +DATA[b8] + * The DEVICE_ADDR is calculated as the 7-bit slave address + * shifted left one space plus the corresponding read/write bit. + * (7Bit Address [b7] << 1 ) + R/W = DEVICE_ADDR[b8] + */ + for (i = 0; i < sizeof(crcBuf); i++) { + t_crc ^= crcBuf[i]; + for (j = 0; j < 8; j++) { + if ((t_crc & 0x80) != 0) { + t_crc <<= 1; + t_crc ^= 0x1D; + } else { + t_crc <<= 1; + } + } + } + + return t_crc; +} + +static int pf0900_regmap_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + struct pf0900 *pf0900 = dev_get_drvdata(dev); + int ret; + u8 crc; + + if (!pf0900 || !pf0900->dev) + return -EINVAL; + + if (reg >= PF0900_MAX_REGISTER) { + dev_err(pf0900->dev, "Invalid register address: 0x%x\n", reg); + return -EINVAL; + } + + if (pf0900->crc_en) { + ret = i2c_smbus_read_word_data(i2c, reg); + if (ret < 0) { + dev_err(pf0900->dev, "Read error at reg=0x%x: %d\n", reg, ret); + return ret; + } + + *val = (u16)ret; + crc = crc8_j1850(pf0900->addr << 1 | 0x1, reg, FIELD_GET(GENMASK(7, 0), *val)); + if (crc != FIELD_GET(GENMASK(15, 8), *val)) { + dev_err(pf0900->dev, "Crc check error!\n"); + return -EINVAL; + } + *val = FIELD_GET(GENMASK(7, 0), *val); + } else { + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret < 0) { + dev_err(pf0900->dev, "Read error at reg=0x%x: %d\n", reg, ret); + return ret; + } + *val = ret; + } + + return 0; +} + +static int pf0900_regmap_write(void *context, unsigned int reg, + unsigned int val) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + struct pf0900 *pf0900 = dev_get_drvdata(dev); + uint8_t data[2]; + int ret; + + if (!pf0900 || !pf0900->dev) + return -EINVAL; + + if (reg >= PF0900_MAX_REGISTER) { + dev_err(pf0900->dev, "Invalid register address: 0x%x\n", reg); + return -EINVAL; + } + + data[0] = val; + if (pf0900->crc_en) { + /* Get CRC */ + data[1] = crc8_j1850(pf0900->addr << 1, reg, data[0]); + val = FIELD_PREP(GENMASK(15, 8), data[1]) | data[0]; + ret = i2c_smbus_write_word_data(i2c, reg, val); + } else { + ret = i2c_smbus_write_byte_data(i2c, reg, data[0]); + } + + if (ret) { + dev_err(pf0900->dev, "Write reg=0x%x error!\n", reg); + return ret; + } + + return 0; +} + +static int pf0900_suspend_enable(struct regulator_dev *rdev) +{ + struct pf0900_regulator_desc *rdata = rdev_get_drvdata(rdev); + struct regmap *rmap = rdev_get_regmap(rdev); + + return regmap_update_bits(rmap, rdata->desc.enable_reg, + rdata->suspend_enable_mask, SW_STBY_MODE_PFM); +} + +static int pf0900_suspend_disable(struct regulator_dev *rdev) +{ + struct pf0900_regulator_desc *rdata = rdev_get_drvdata(rdev); + struct regmap *rmap = rdev_get_regmap(rdev); + + return regmap_update_bits(rmap, rdata->desc.enable_reg, + rdata->suspend_enable_mask, SW_STBY_MODE_OFF); +} + +static int pf0900_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct pf0900_regulator_desc *rdata = rdev_get_drvdata(rdev); + struct regmap *rmap = rdev_get_regmap(rdev); + int ret; + + if (rdata->suspend_voltage_cache == uV) + return 0; + + ret = regulator_map_voltage_iterate(rdev, uV, uV); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), "failed to map %i uV\n", uV); + return ret; + } + + dev_dbg(rdev_get_dev(rdev), "uV: %i, reg: 0x%x, msk: 0x%x, val: 0x%x\n", + uV, rdata->suspend_voltage_reg, rdata->desc.vsel_mask, ret); + ret = regmap_update_bits(rmap, rdata->suspend_voltage_reg, + rdata->desc.vsel_mask, ret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), "failed to set %i uV\n", uV); + return ret; + } + + rdata->suspend_voltage_cache = uV; + + return 0; +} + +static const struct regmap_bus pf0900_regmap_bus = { + .reg_read = pf0900_regmap_read, + .reg_write = pf0900_regmap_write, +}; + +static const struct regulator_ops pf0900_avon_regulator_ops = { + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +static const struct regulator_ops pf0900_dvs_sw_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + .set_suspend_enable = pf0900_suspend_enable, + .set_suspend_disable = pf0900_suspend_disable, + .set_suspend_voltage = pf0900_set_suspend_voltage, +}; + +static const struct regulator_ops pf0900_ldo_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + +/* + * SW1/2/3/4/5 + * SW1_DVS[1:0] SW1 DVS ramp rate setting + * 00: 15.6mV/8usec + * 01: 15.6mV/4usec + * 10: 15.6mV/2usec + * 11: 15.6mV/1usec + */ +static const unsigned int pf0900_dvs_sw_ramp_table[] = { + 1950, 3900, 7800, 15600 +}; + +/* VAON 1.8V, 3.0V, or 3.3V */ +static const int pf0900_vaon_voltages[] = { + 0, 1800000, 3000000, 3300000, +}; + +/* + * SW1 0.5V to 3.3V + * 0.5V to 1.35V (6.25mV step) + * 1.8V to 2.5V (125mV step) + * 2.8V to 3.3V (250mV step) + */ +static const struct linear_range pf0900_dvs_sw1_volts[] = { + REGULATOR_LINEAR_RANGE(0, 0x00, 0x08, 0), + REGULATOR_LINEAR_RANGE(500000, 0x09, 0x91, 6250), + REGULATOR_LINEAR_RANGE(0, 0x92, 0x9E, 0), + REGULATOR_LINEAR_RANGE(1500000, 0x9F, 0x9F, 0), + REGULATOR_LINEAR_RANGE(1800000, 0xA0, 0xD8, 12500), + REGULATOR_LINEAR_RANGE(0, 0xD9, 0xDF, 0), + REGULATOR_LINEAR_RANGE(2800000, 0xE0, 0xF4, 25000), + REGULATOR_LINEAR_RANGE(0, 0xF5, 0xFF, 0), +}; + +/* + * SW2/3/4/5 0.3V to 3.3V + * 0.45V to 1.35V (6.25mV step) + * 1.8V to 2.5V (125mV step) + * 2.8V to 3.3V (250mV step) + */ +static const struct linear_range pf0900_dvs_sw2345_volts[] = { + REGULATOR_LINEAR_RANGE(300000, 0x00, 0x00, 0), + REGULATOR_LINEAR_RANGE(450000, 0x01, 0x91, 6250), + REGULATOR_LINEAR_RANGE(0, 0x92, 0x9E, 0), + REGULATOR_LINEAR_RANGE(1500000, 0x9F, 0x9F, 0), + REGULATOR_LINEAR_RANGE(1800000, 0xA0, 0xD8, 12500), + REGULATOR_LINEAR_RANGE(0, 0xD9, 0xDF, 0), + REGULATOR_LINEAR_RANGE(2800000, 0xE0, 0xF4, 25000), + REGULATOR_LINEAR_RANGE(0, 0xF5, 0xFF, 0), +}; + +/* + * LDO1 + * 0.75V to 3.3V + */ +static const struct linear_range pf0900_ldo1_volts[] = { + REGULATOR_LINEAR_RANGE(750000, 0x00, 0x0F, 50000), + REGULATOR_LINEAR_RANGE(1800000, 0x10, 0x1F, 100000), +}; + +/* + * LDO2/3 + * 0.65V to 3.3V (50mV step) + */ +static const struct linear_range pf0900_ldo23_volts[] = { + REGULATOR_LINEAR_RANGE(650000, 0x00, 0x0D, 50000), + REGULATOR_LINEAR_RANGE(1400000, 0x0E, 0x0F, 100000), + REGULATOR_LINEAR_RANGE(1800000, 0x10, 0x1F, 100000), +}; + +static const struct pf0900_regulator_desc pf0900_regulators[] = { + { + .desc = { + .name = "sw1", + .of_match = of_match_ptr("sw1"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_SW1, + .ops = &pf0900_dvs_sw_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_SW_VOLTAGE_NUM, + .linear_ranges = pf0900_dvs_sw1_volts, + .n_linear_ranges = ARRAY_SIZE(pf0900_dvs_sw1_volts), + .vsel_reg = PF0900_REG_SW1_VRUN, + .vsel_mask = PF0900_SW_VOL_MASK, + .enable_reg = PF0900_REG_SW1_MODE, + .enable_mask = SW_RUN_MODE_MASK, + .enable_val = SW_RUN_MODE_PWM, + .ramp_reg = PF0900_REG_SW1_CFG1, + .ramp_mask = PF0900_SW_DVS_MASK, + .ramp_delay_table = pf0900_dvs_sw_ramp_table, + .n_ramp_values = ARRAY_SIZE(pf0900_dvs_sw_ramp_table), + .owner = THIS_MODULE, + }, + .suspend_enable_mask = SW_STBY_MODE_MASK, + .suspend_voltage_reg = PF0900_REG_SW1_VSTBY, + }, + { + .desc = { + .name = "sw2", + .of_match = of_match_ptr("sw2"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_SW2, + .ops = &pf0900_dvs_sw_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_SW_VOLTAGE_NUM, + .linear_ranges = pf0900_dvs_sw2345_volts, + .n_linear_ranges = ARRAY_SIZE(pf0900_dvs_sw2345_volts), + .vsel_reg = PF0900_REG_SW2_VRUN, + .vsel_mask = PF0900_SW_VOL_MASK, + .enable_reg = PF0900_REG_SW2_MODE, + .enable_mask = SW_RUN_MODE_MASK, + .enable_val = SW_RUN_MODE_PWM, + .ramp_reg = PF0900_REG_SW2_CFG1, + .ramp_mask = PF0900_SW_DVS_MASK, + .ramp_delay_table = pf0900_dvs_sw_ramp_table, + .n_ramp_values = ARRAY_SIZE(pf0900_dvs_sw_ramp_table), + .owner = THIS_MODULE, + }, + .suspend_enable_mask = SW_STBY_MODE_MASK, + .suspend_voltage_reg = PF0900_REG_SW2_VSTBY, + }, + { + .desc = { + .name = "sw3", + .of_match = of_match_ptr("sw3"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_SW3, + .ops = &pf0900_dvs_sw_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_SW_VOLTAGE_NUM, + .linear_ranges = pf0900_dvs_sw2345_volts, + .n_linear_ranges = ARRAY_SIZE(pf0900_dvs_sw2345_volts), + .vsel_reg = PF0900_REG_SW3_VRUN, + .vsel_mask = PF0900_SW_VOL_MASK, + .enable_reg = PF0900_REG_SW3_MODE, + .enable_mask = SW_RUN_MODE_MASK, + .enable_val = SW_RUN_MODE_PWM, + .ramp_reg = PF0900_REG_SW3_CFG1, + .ramp_mask = PF0900_SW_DVS_MASK, + .ramp_delay_table = pf0900_dvs_sw_ramp_table, + .n_ramp_values = ARRAY_SIZE(pf0900_dvs_sw_ramp_table), + .owner = THIS_MODULE, + }, + .suspend_enable_mask = SW_STBY_MODE_MASK, + .suspend_voltage_reg = PF0900_REG_SW3_VSTBY, + }, + { + .desc = { + .name = "sw4", + .of_match = of_match_ptr("sw4"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_SW4, + .ops = &pf0900_dvs_sw_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_SW_VOLTAGE_NUM, + .linear_ranges = pf0900_dvs_sw2345_volts, + .n_linear_ranges = ARRAY_SIZE(pf0900_dvs_sw2345_volts), + .vsel_reg = PF0900_REG_SW4_VRUN, + .vsel_mask = PF0900_SW_VOL_MASK, + .enable_reg = PF0900_REG_SW4_MODE, + .enable_mask = SW_RUN_MODE_MASK, + .enable_val = SW_RUN_MODE_PWM, + .ramp_reg = PF0900_REG_SW4_CFG1, + .ramp_mask = PF0900_SW_DVS_MASK, + .ramp_delay_table = pf0900_dvs_sw_ramp_table, + .n_ramp_values = ARRAY_SIZE(pf0900_dvs_sw_ramp_table), + .owner = THIS_MODULE, + }, + .suspend_enable_mask = SW_STBY_MODE_MASK, + .suspend_voltage_reg = PF0900_REG_SW4_VSTBY, + }, + { + .desc = { + .name = "sw5", + .of_match = of_match_ptr("sw5"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_SW5, + .ops = &pf0900_dvs_sw_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_SW_VOLTAGE_NUM, + .linear_ranges = pf0900_dvs_sw2345_volts, + .n_linear_ranges = ARRAY_SIZE(pf0900_dvs_sw2345_volts), + .vsel_reg = PF0900_REG_SW5_VRUN, + .vsel_mask = PF0900_SW_VOL_MASK, + .enable_reg = PF0900_REG_SW5_MODE, + .enable_mask = SW_RUN_MODE_MASK, + .enable_val = SW_RUN_MODE_PWM, + .ramp_reg = PF0900_REG_SW5_CFG1, + .ramp_mask = PF0900_SW_DVS_MASK, + .ramp_delay_table = pf0900_dvs_sw_ramp_table, + .n_ramp_values = ARRAY_SIZE(pf0900_dvs_sw_ramp_table), + .owner = THIS_MODULE, + }, + .suspend_enable_mask = SW_STBY_MODE_MASK, + .suspend_voltage_reg = PF0900_REG_SW5_VSTBY, + }, + { + .desc = { + .name = "ldo1", + .of_match = of_match_ptr("ldo1"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_LDO1, + .ops = &pf0900_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_LDO_VOLTAGE_NUM, + .linear_ranges = pf0900_ldo1_volts, + .n_linear_ranges = ARRAY_SIZE(pf0900_ldo1_volts), + .vsel_reg = PF0900_REG_LDO1_RUN, + .vsel_mask = VLDO_RUN_MASK, + .enable_reg = PF0900_REG_LDO1_RUN, + .enable_mask = LDO_RUN_EN_MASK, + .owner = THIS_MODULE, + }, + }, + { + .desc = { + .name = "ldo2", + .of_match = of_match_ptr("ldo2"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_LDO2, + .ops = &pf0900_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_LDO_VOLTAGE_NUM, + .linear_ranges = pf0900_ldo23_volts, + .n_linear_ranges = ARRAY_SIZE(pf0900_ldo23_volts), + .vsel_reg = PF0900_REG_LDO2_RUN, + .vsel_mask = VLDO_RUN_MASK, + .enable_reg = PF0900_REG_LDO2_RUN, + .enable_mask = LDO_RUN_EN_MASK, + .owner = THIS_MODULE, + }, + }, + { + .desc = { + .name = "ldo3", + .of_match = of_match_ptr("ldo3"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_LDO3, + .ops = &pf0900_ldo_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_LDO_VOLTAGE_NUM, + .linear_ranges = pf0900_ldo23_volts, + .n_linear_ranges = ARRAY_SIZE(pf0900_ldo23_volts), + .vsel_reg = PF0900_REG_LDO3_RUN, + .vsel_mask = VLDO_RUN_MASK, + .enable_reg = PF0900_REG_LDO3_RUN, + .enable_mask = LDO_RUN_EN_MASK, + .owner = THIS_MODULE, + }, + }, + { + .desc = { + .name = "vaon", + .of_match = of_match_ptr("vaon"), + .regulators_node = of_match_ptr("regulators"), + .id = PF0900_VAON, + .ops = &pf0900_avon_regulator_ops, + .type = REGULATOR_VOLTAGE, + .n_voltages = PF0900_VAON_VOLTAGE_NUM, + .volt_table = pf0900_vaon_voltages, + .enable_reg = PF0900_REG_VAON_CFG1, + .enable_mask = PF0900_VAON_MASK, + .enable_val = PF0900_VAON_1P8V, + .vsel_reg = PF0900_REG_VAON_CFG1, + .vsel_mask = PF0900_VAON_MASK, + .owner = THIS_MODULE, + }, + }, +}; + +struct pf0900_regulator_irq regu_irqs[] = { + PF0900_REGU_IRQ(PF0900_REG_SW_ILIM_INT, PF0900_SW, REGULATOR_ERROR_OVER_CURRENT_WARN), + PF0900_REGU_IRQ(PF0900_REG_LDO_ILIM_INT, PF0900_LDO, REGULATOR_ERROR_OVER_CURRENT_WARN), + PF0900_REGU_IRQ(PF0900_REG_SW_UV_INT, PF0900_SW, REGULATOR_ERROR_UNDER_VOLTAGE_WARN), + PF0900_REGU_IRQ(PF0900_REG_LDO_UV_INT, PF0900_LDO, REGULATOR_ERROR_UNDER_VOLTAGE_WARN), + PF0900_REGU_IRQ(PF0900_REG_SW_OV_INT, PF0900_SW, REGULATOR_ERROR_OVER_VOLTAGE_WARN), + PF0900_REGU_IRQ(PF0900_REG_LDO_OV_INT, PF0900_LDO, REGULATOR_ERROR_OVER_VOLTAGE_WARN), +}; + +static irqreturn_t pf0900_irq_handler(int irq, void *data) +{ + unsigned int val, regu, i, index; + struct pf0900 *pf0900 = data; + int ret; + + for (i = 0; i < ARRAY_SIZE(regu_irqs); i++) { + ret = regmap_read(pf0900->regmap, regu_irqs[i].reg, &val); + if (ret < 0) { + dev_err(pf0900->dev, "Failed to read %d\n", ret); + return IRQ_NONE; + } + if (val) { + ret = regmap_write_bits(pf0900->regmap, regu_irqs[i].reg, val, val); + if (ret < 0) { + dev_err(pf0900->dev, "Failed to update %d\n", ret); + return IRQ_NONE; + } + + if (regu_irqs[i].type == PF0900_SW) { + for (index = 0; index < REGU_SW_CNT; index++) { + if (val & BIT(index)) { + regu = (enum pf0900_regulators)index; + regulator_notifier_call_chain(pf0900->rdevs[regu], + regu_irqs[i].event, + NULL); + } + } + } else if (regu_irqs[i].type == PF0900_LDO) { + for (index = 0; index < REGU_LDO_VAON_CNT; index++) { + if (val & BIT(index)) { + regu = (enum pf0900_regulators)index + PF0900_LDO1; + regulator_notifier_call_chain(pf0900->rdevs[regu], + regu_irqs[i].event, + NULL); + } + } + } + } + } + + return IRQ_HANDLED; +} + +static int pf0900_i2c_probe(struct i2c_client *i2c) +{ + const struct pf0900_regulator_desc *regulator_desc; + const struct pf0900_drvdata *drvdata = NULL; + struct device_node *np = i2c->dev.of_node; + unsigned int device_id, device_fam, i; + struct regulator_config config = { }; + struct pf0900 *pf0900; + int ret; + + if (!i2c->irq) + return dev_err_probe(&i2c->dev, -EINVAL, "No IRQ configured?\n"); + + pf0900 = devm_kzalloc(&i2c->dev, sizeof(struct pf0900), GFP_KERNEL); + if (!pf0900) + return -ENOMEM; + + drvdata = device_get_match_data(&i2c->dev); + if (!drvdata) + return dev_err_probe(&i2c->dev, -EINVAL, "unable to find driver data\n"); + + regulator_desc = drvdata->desc; + pf0900->drvdata = drvdata; + pf0900->crc_en = of_property_read_bool(np, "nxp,i2c-crc-enable"); + pf0900->irq = i2c->irq; + pf0900->dev = &i2c->dev; + pf0900->addr = i2c->addr; + + dev_set_drvdata(&i2c->dev, pf0900); + + pf0900->regmap = devm_regmap_init(&i2c->dev, &pf0900_regmap_bus, &i2c->dev, + &pf0900_regmap_config); + if (IS_ERR(pf0900->regmap)) + return dev_err_probe(&i2c->dev, PTR_ERR(pf0900->regmap), + "regmap initialization failed\n"); + ret = regmap_read(pf0900->regmap, PF0900_REG_DEV_ID, &device_id); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Read device id error\n"); + + ret = regmap_read(pf0900->regmap, PF0900_REG_DEV_FAM, &device_fam); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Read device fam error\n"); + + /* Check your board and dts for match the right pmic */ + if (device_fam == 0x09 && (device_id & 0x1F) != 0x0) + return dev_err_probe(&i2c->dev, -EINVAL, "Device id(%x) mismatched\n", + device_id >> 4); + + for (i = 0; i < drvdata->rcnt; i++) { + const struct regulator_desc *desc; + const struct pf0900_regulator_desc *r; + + r = ®ulator_desc[i]; + desc = &r->desc; + config.regmap = pf0900->regmap; + config.driver_data = (void *)r; + config.dev = pf0900->dev; + + pf0900->rdevs[i] = devm_regulator_register(pf0900->dev, desc, &config); + if (IS_ERR(pf0900->rdevs[i])) + return dev_err_probe(pf0900->dev, PTR_ERR(pf0900->rdevs[i]), + "Failed to register regulator(%s)\n", desc->name); + } + + ret = devm_request_threaded_irq(pf0900->dev, pf0900->irq, NULL, + pf0900_irq_handler, + (IRQF_TRIGGER_FALLING | IRQF_ONESHOT), + "pf0900-irq", pf0900); + + if (ret != 0) + return dev_err_probe(pf0900->dev, ret, "Failed to request IRQ: %d\n", + pf0900->irq); + /* + * The PWRUP_M is unmasked by default. When the device enter in RUN state, + * it will assert the PWRUP_I interrupt and assert the INTB pin to inform + * the MCU that it has finished the power up sequence properly. + */ + ret = regmap_write_bits(pf0900->regmap, PF0900_REG_STATUS1_INT, PF0900_IRQ_PWRUP, + PF0900_IRQ_PWRUP); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Clean PWRUP_I error\n"); + + /* mask interrupt PWRUP */ + ret = regmap_update_bits(pf0900->regmap, PF0900_REG_STATUS1_MSK, PF0900_IRQ_PWRUP, + PF0900_IRQ_PWRUP); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Unmask irq error\n"); + + ret = regmap_update_bits(pf0900->regmap, PF0900_REG_SW_ILIM_MSK, PF0900_IRQ_SW1_IL | + PF0900_IRQ_SW2_IL | PF0900_IRQ_SW3_IL | PF0900_IRQ_SW4_IL | + PF0900_IRQ_SW5_IL, 0); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Unmask irq error\n"); + + ret = regmap_update_bits(pf0900->regmap, PF0900_REG_SW_UV_MSK, PF0900_IRQ_SW1_UV | + PF0900_IRQ_SW2_UV | PF0900_IRQ_SW3_UV | PF0900_IRQ_SW4_UV | + PF0900_IRQ_SW5_UV, 0); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Unmask irq error\n"); + + ret = regmap_update_bits(pf0900->regmap, PF0900_REG_SW_OV_MSK, PF0900_IRQ_SW1_OV | + PF0900_IRQ_SW2_OV | PF0900_IRQ_SW3_OV | PF0900_IRQ_SW4_OV | + PF0900_IRQ_SW5_OV, 0); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Unmask irq error\n"); + + ret = regmap_update_bits(pf0900->regmap, PF0900_REG_LDO_ILIM_MSK, PF0900_IRQ_LDO1_IL | + PF0900_IRQ_LDO2_IL | PF0900_IRQ_LDO3_IL, 0); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Unmask irq error\n"); + + ret = regmap_update_bits(pf0900->regmap, PF0900_REG_LDO_UV_MSK, PF0900_IRQ_LDO1_UV | + PF0900_IRQ_LDO2_UV | PF0900_IRQ_LDO3_UV | PF0900_IRQ_VAON_UV, 0); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Unmask irq error\n"); + + ret = regmap_update_bits(pf0900->regmap, PF0900_REG_LDO_OV_MSK, PF0900_IRQ_LDO1_OV | + PF0900_IRQ_LDO2_OV | PF0900_IRQ_LDO3_OV | PF0900_IRQ_VAON_OV, 0); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Unmask irq error\n"); + + return 0; +} + +static struct pf0900_drvdata pf0900_drvdata = { + .desc = pf0900_regulators, + .rcnt = ARRAY_SIZE(pf0900_regulators), +}; + +static const struct of_device_id pf0900_of_match[] = { + { .compatible = "nxp,pf0900", .data = &pf0900_drvdata}, + { } +}; + +MODULE_DEVICE_TABLE(of, pf0900_of_match); + +static struct i2c_driver pf0900_i2c_driver = { + .driver = { + .name = "nxp-pf0900", + .of_match_table = pf0900_of_match, + }, + .probe = pf0900_i2c_probe, +}; + +module_i2c_driver(pf0900_i2c_driver); + +MODULE_AUTHOR("Joy Zou "); +MODULE_DESCRIPTION("NXP PF0900 Power Management IC driver"); +MODULE_LICENSE("GPL"); From c6871d56b52ec177b8b8bd891fa0cbbd004b646d Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Tue, 29 Jul 2025 12:00:44 +0800 Subject: [PATCH 05/28] regulator: tps6594-regulator: Remove unneeded semicolon Remove unnecessary semicolons reported by Coccinelle/coccicheck and the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Signed-off-by: Chen Ni Link: https://patch.msgid.link/20250729040044.1851988-1-nichen@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/regulator/tps6594-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/regulator/tps6594-regulator.c b/drivers/regulator/tps6594-regulator.c index ab882daec7c5..645e83462c64 100644 --- a/drivers/regulator/tps6594-regulator.c +++ b/drivers/regulator/tps6594-regulator.c @@ -647,7 +647,7 @@ static int tps6594_regulator_probe(struct platform_device *pdev) default: dev_err(tps->dev, "unknown chip_id %lu\n", tps->chip_id); return -EINVAL; - }; + } enum { MULTI_BUCK12, From f6cc4140e161831e5796f099f5abc3af953ae2b8 Mon Sep 17 00:00:00 2001 From: Liao Yuanhong Date: Thu, 7 Aug 2025 20:39:49 +0800 Subject: [PATCH 06/28] regulator: tps6524x: Remove unnecessary memset devm_kzalloc() has already been initialized to full 0 space, there is no need to use memset() to initialize again. Signed-off-by: Liao Yuanhong Link: https://patch.msgid.link/20250807123949.495193-1-liaoyuanhong@vivo.com Signed-off-by: Mark Brown --- drivers/regulator/tps6524x-regulator.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/regulator/tps6524x-regulator.c b/drivers/regulator/tps6524x-regulator.c index 3fee7e38c68b..6beb51293e8e 100644 --- a/drivers/regulator/tps6524x-regulator.c +++ b/drivers/regulator/tps6524x-regulator.c @@ -598,7 +598,6 @@ static int pmic_probe(struct spi_device *spi) spi_set_drvdata(spi, hw); - memset(hw, 0, sizeof(struct tps6524x)); hw->dev = dev; hw->spi = spi; mutex_init(&hw->lock); From bb2441402392ef1f49563be68e8f0dcb127ac965 Mon Sep 17 00:00:00 2001 From: Dzmitry Sankouski Date: Tue, 5 Aug 2025 22:40:56 +0300 Subject: [PATCH 07/28] regulator: add s2dos05 regulator support S2DOS05 has 1 buck and 4 LDO regulators, used for powering panel/touchscreen. Signed-off-by: Dzmitry Sankouski Link: https://patch.msgid.link/20250805-starqltechn_integration_upstream-v8-1-09d8a321fafe@gmail.com Signed-off-by: Mark Brown --- MAINTAINERS | 2 +- drivers/regulator/Kconfig | 8 ++ drivers/regulator/Makefile | 1 + drivers/regulator/s2dos05-regulator.c | 165 ++++++++++++++++++++++++++ include/linux/regulator/s2dos05.h | 73 ++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 drivers/regulator/s2dos05-regulator.c create mode 100644 include/linux/regulator/s2dos05.h diff --git a/MAINTAINERS b/MAINTAINERS index fe168477caa4..f35f9b503956 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22387,7 +22387,7 @@ F: Documentation/devicetree/bindings/regulator/samsung,s2m*.yaml F: Documentation/devicetree/bindings/regulator/samsung,s5m*.yaml F: drivers/clk/clk-s2mps11.c F: drivers/mfd/sec*.[ch] -F: drivers/regulator/s2m*.c +F: drivers/regulator/s2*.c F: drivers/regulator/s5m*.c F: drivers/rtc/rtc-s5m.c F: include/linux/mfd/samsung/ diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index eaa6df1c9f80..2399c8a9a1c5 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1344,6 +1344,14 @@ config REGULATOR_RTQ2208 and two ldos. It features wide output voltage range from 0.4V to 2.05V and the capability to configure the corresponding power stages. +config REGULATOR_S2DOS05 + tristate "Samsung S2DOS05 voltage regulator" + depends on MFD_SEC_CORE || COMPILE_TEST + help + This driver provides support for the voltage regulators of the S2DOS05. + The S2DOS05 is a companion power management IC for the smart phones. + The S2DOS05 has 4 LDOs and 1 BUCK outputs. + config REGULATOR_S2MPA01 tristate "Samsung S2MPA01 voltage regulator" depends on MFD_SEC_CORE || COMPILE_TEST diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index be98b29d6675..78605b0fa0b2 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -156,6 +156,7 @@ obj-$(CONFIG_REGULATOR_RTMV20) += rtmv20-regulator.o obj-$(CONFIG_REGULATOR_RTQ2134) += rtq2134-regulator.o obj-$(CONFIG_REGULATOR_RTQ6752) += rtq6752-regulator.o obj-$(CONFIG_REGULATOR_RTQ2208) += rtq2208-regulator.o +obj-$(CONFIG_REGULATOR_S2DOS05) += s2dos05-regulator.o obj-$(CONFIG_REGULATOR_S2MPA01) += s2mpa01.o obj-$(CONFIG_REGULATOR_S2MPS11) += s2mps11.o obj-$(CONFIG_REGULATOR_S5M8767) += s5m8767.o diff --git a/drivers/regulator/s2dos05-regulator.c b/drivers/regulator/s2dos05-regulator.c new file mode 100644 index 000000000000..1463585c4565 --- /dev/null +++ b/drivers/regulator/s2dos05-regulator.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// s2dos05.c - Regulator driver for the Samsung s2dos05 +// +// Copyright (C) 2025 Dzmitry Sankouski + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct s2dos05_data { + struct regmap *regmap; + struct device *dev; +}; + +#define _BUCK(macro) S2DOS05_BUCK##macro +#define _buck_ops(num) s2dos05_ops##num +#define _LDO(macro) S2DOS05_LDO##macro +#define _REG(ctrl) S2DOS05_REG##ctrl +#define _ldo_ops(num) s2dos05_ops##num +#define _MASK(macro) S2DOS05_ENABLE_MASK##macro +#define _TIME(macro) S2DOS05_ENABLE_TIME##macro + +#define BUCK_DESC(_name, _id, _ops, m, s, v, e, em, t, a) { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .of_match = of_match_ptr(_name), \ + .of_match_full_name = true, \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = m, \ + .uV_step = s, \ + .n_voltages = S2DOS05_BUCK_N_VOLTAGES, \ + .vsel_reg = v, \ + .vsel_mask = S2DOS05_BUCK_VSEL_MASK, \ + .enable_reg = e, \ + .enable_mask = em, \ + .enable_time = t, \ + .active_discharge_off = 0, \ + .active_discharge_on = S2DOS05_BUCK_FD_MASK, \ + .active_discharge_reg = a, \ + .active_discharge_mask = S2DOS05_BUCK_FD_MASK \ +} + +#define LDO_DESC(_name, _id, _ops, m, s, v, e, em, t, a) { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .of_match = of_match_ptr(_name), \ + .of_match_full_name = true, \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = m, \ + .uV_step = s, \ + .n_voltages = S2DOS05_LDO_N_VOLTAGES, \ + .vsel_reg = v, \ + .vsel_mask = S2DOS05_LDO_VSEL_MASK, \ + .enable_reg = e, \ + .enable_mask = em, \ + .enable_time = t, \ + .active_discharge_off = 0, \ + .active_discharge_on = S2DOS05_LDO_FD_MASK, \ + .active_discharge_reg = a, \ + .active_discharge_mask = S2DOS05_LDO_FD_MASK \ +} + +static const struct regulator_ops s2dos05_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .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_active_discharge = regulator_set_active_discharge_regmap, +}; + +static const struct regulator_desc regulators[S2DOS05_REGULATOR_MAX] = { + // name, id, ops, min_uv, uV_step, vsel_reg, enable_reg + LDO_DESC("ldo1", _LDO(1), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_LDO1_CFG), + _REG(_EN), _MASK(_L1), _TIME(_LDO), _REG(_LDO1_CFG)), + LDO_DESC("ldo2", _LDO(2), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_LDO2_CFG), + _REG(_EN), _MASK(_L2), _TIME(_LDO), _REG(_LDO2_CFG)), + LDO_DESC("ldo3", _LDO(3), &_ldo_ops(), _LDO(_MIN2), + _LDO(_STEP1), _REG(_LDO3_CFG), + _REG(_EN), _MASK(_L3), _TIME(_LDO), _REG(_LDO3_CFG)), + LDO_DESC("ldo4", _LDO(4), &_ldo_ops(), _LDO(_MIN2), + _LDO(_STEP1), _REG(_LDO4_CFG), + _REG(_EN), _MASK(_L4), _TIME(_LDO), _REG(_LDO4_CFG)), + BUCK_DESC("buck", _BUCK(1), &_buck_ops(), _BUCK(_MIN1), + _BUCK(_STEP1), _REG(_BUCK_VOUT), + _REG(_EN), _MASK(_B1), _TIME(_BUCK), _REG(_BUCK_CFG)), +}; + +static int s2dos05_pmic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct s2dos05_data *s2dos05; + struct regulator_config config = { }; + unsigned int rdev_num = ARRAY_SIZE(regulators); + + s2dos05 = devm_kzalloc(dev, sizeof(*s2dos05), GFP_KERNEL); + if (!s2dos05) + return -ENOMEM; + + platform_set_drvdata(pdev, s2dos05); + + s2dos05->regmap = iodev->regmap_pmic; + s2dos05->dev = dev; + if (!dev->of_node) + dev->of_node = dev->parent->of_node; + + config.dev = dev; + config.driver_data = s2dos05; + + for (int i = 0; i < rdev_num; i++) { + struct regulator_dev *regulator; + + regulator = devm_regulator_register(&pdev->dev, + ®ulators[i], &config); + if (IS_ERR(regulator)) { + return dev_err_probe(&pdev->dev, PTR_ERR(regulator), + "regulator init failed for %d\n", i); + } + } + + return 0; +} + +static const struct platform_device_id s2dos05_pmic_id[] = { + { "s2dos05-regulator" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, s2dos05_pmic_id); + +static struct platform_driver s2dos05_platform_driver = { + .driver = { + .name = "s2dos05", + }, + .probe = s2dos05_pmic_probe, + .id_table = s2dos05_pmic_id, +}; +module_platform_driver(s2dos05_platform_driver); + +MODULE_AUTHOR("Dzmitry Sankouski "); +MODULE_DESCRIPTION("Samsung S2DOS05 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/regulator/s2dos05.h b/include/linux/regulator/s2dos05.h new file mode 100644 index 000000000000..2e89fcbce769 --- /dev/null +++ b/include/linux/regulator/s2dos05.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +// s2dos05.h +// +// Copyright (c) 2016 Samsung Electronics Co., Ltd +// http://www.samsung.com +// Copyright (C) 2024 Dzmitry Sankouski + +#ifndef __LINUX_S2DOS05_H +#define __LINUX_S2DOS05_H + +// S2DOS05 registers +// Slave Addr : 0xC0 +enum S2DOS05_reg { + S2DOS05_REG_DEV_ID, + S2DOS05_REG_TOPSYS_STAT, + S2DOS05_REG_STAT, + S2DOS05_REG_EN, + S2DOS05_REG_LDO1_CFG, + S2DOS05_REG_LDO2_CFG, + S2DOS05_REG_LDO3_CFG, + S2DOS05_REG_LDO4_CFG, + S2DOS05_REG_BUCK_CFG, + S2DOS05_REG_BUCK_VOUT, + S2DOS05_REG_IRQ_MASK = 0x0D, + S2DOS05_REG_SSD_TSD = 0x0E, + S2DOS05_REG_OCL = 0x10, + S2DOS05_REG_IRQ = 0x11 +}; + +// S2DOS05 regulator ids +enum S2DOS05_regulators { + S2DOS05_LDO1, + S2DOS05_LDO2, + S2DOS05_LDO3, + S2DOS05_LDO4, + S2DOS05_BUCK1, + S2DOS05_REG_MAX, +}; + +#define S2DOS05_IRQ_PWRMT_MASK BIT(5) +#define S2DOS05_IRQ_TSD_MASK BIT(4) +#define S2DOS05_IRQ_SSD_MASK BIT(3) +#define S2DOS05_IRQ_SCP_MASK BIT(2) +#define S2DOS05_IRQ_UVLO_MASK BIT(1) +#define S2DOS05_IRQ_OCD_MASK BIT(0) + +#define S2DOS05_BUCK_MIN1 506250 +#define S2DOS05_LDO_MIN1 1500000 +#define S2DOS05_LDO_MIN2 2700000 +#define S2DOS05_BUCK_STEP1 6250 +#define S2DOS05_LDO_STEP1 25000 +#define S2DOS05_LDO_VSEL_MASK 0x7F +#define S2DOS05_LDO_FD_MASK 0x80 +#define S2DOS05_BUCK_VSEL_MASK 0xFF +#define S2DOS05_BUCK_FD_MASK 0x08 + +#define S2DOS05_ENABLE_MASK_L1 BIT(0) +#define S2DOS05_ENABLE_MASK_L2 BIT(1) +#define S2DOS05_ENABLE_MASK_L3 BIT(2) +#define S2DOS05_ENABLE_MASK_L4 BIT(3) +#define S2DOS05_ENABLE_MASK_B1 BIT(4) + +#define S2DOS05_RAMP_DELAY 12000 + +#define S2DOS05_ENABLE_TIME_LDO 50 +#define S2DOS05_ENABLE_TIME_BUCK 350 + +#define S2DOS05_LDO_N_VOLTAGES (S2DOS05_LDO_VSEL_MASK + 1) +#define S2DOS05_BUCK_N_VOLTAGES (S2DOS05_BUCK_VSEL_MASK + 1) + +#define S2DOS05_REGULATOR_MAX (S2DOS05_REG_MAX) + +#endif // __LINUX_S2DOS05_H From a54ef14188519a0994d0264f701f5771815fa11e Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 7 Aug 2025 16:44:57 -0500 Subject: [PATCH 08/28] regulator: dt-bindings: Clean-up active-semi,act8945a duplication The active-semi,act8945a binding is documented in multiple places. The charger child node is documented in regulator/active-semi,act8945a.yaml and power/supply/active-semi,act8945a-charger.yaml. An old text binding is in mfd/act8945a.txt. Update the regulator/active-semi,act8945a.yaml with the additional descriptions and constraints from power/supply/active-semi,act8945a-charger.yaml, and then remove it and mfd/act8945a.txt. Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20250807214459.4173892-1-robh@kernel.org Signed-off-by: Mark Brown --- .../devicetree/bindings/mfd/act8945a.txt | 82 ------------------- .../supply/active-semi,act8945a-charger.yaml | 76 ----------------- .../regulator/active-semi,act8945a.yaml | 25 ++++-- 3 files changed, 19 insertions(+), 164 deletions(-) delete mode 100644 Documentation/devicetree/bindings/mfd/act8945a.txt delete mode 100644 Documentation/devicetree/bindings/power/supply/active-semi,act8945a-charger.yaml diff --git a/Documentation/devicetree/bindings/mfd/act8945a.txt b/Documentation/devicetree/bindings/mfd/act8945a.txt deleted file mode 100644 index 5ca75d888b4a..000000000000 --- a/Documentation/devicetree/bindings/mfd/act8945a.txt +++ /dev/null @@ -1,82 +0,0 @@ -Device-Tree bindings for Active-semi ACT8945A MFD driver - -Required properties: - - compatible: "active-semi,act8945a". - - reg: the I2C slave address for the ACT8945A chip - -The chip exposes two subdevices: - - a regulators: see ../regulator/act8945a-regulator.txt - - a charger: see ../power/act8945a-charger.txt - -Example: - pmic@5b { - compatible = "active-semi,act8945a"; - reg = <0x5b>; - - active-semi,vsel-high; - - regulators { - vdd_1v35_reg: REG_DCDC1 { - regulator-name = "VDD_1V35"; - regulator-min-microvolt = <1350000>; - regulator-max-microvolt = <1350000>; - regulator-always-on; - }; - - vdd_1v2_reg: REG_DCDC2 { - regulator-name = "VDD_1V2"; - regulator-min-microvolt = <1100000>; - regulator-max-microvolt = <1300000>; - regulator-always-on; - }; - - vdd_3v3_reg: REG_DCDC3 { - regulator-name = "VDD_3V3"; - regulator-min-microvolt = <3300000>; - regulator-max-microvolt = <3300000>; - regulator-always-on; - }; - - vdd_fuse_reg: REG_LDO1 { - regulator-name = "VDD_FUSE"; - regulator-min-microvolt = <2500000>; - regulator-max-microvolt = <2500000>; - regulator-always-on; - }; - - vdd_3v3_lp_reg: REG_LDO2 { - regulator-name = "VDD_3V3_LP"; - regulator-min-microvolt = <3300000>; - regulator-max-microvolt = <3300000>; - regulator-always-on; - }; - - vdd_led_reg: REG_LDO3 { - regulator-name = "VDD_LED"; - regulator-min-microvolt = <3300000>; - regulator-max-microvolt = <3300000>; - regulator-always-on; - }; - - vdd_sdhc_1v8_reg: REG_LDO4 { - regulator-name = "VDD_SDHC_1V8"; - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <1800000>; - regulator-always-on; - }; - }; - - charger { - compatible = "active-semi,act8945a-charger"; - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_charger_chglev &pinctrl_charger_lbo &pinctrl_charger_irq>; - interrupt-parent = <&pioA>; - interrupts = <45 IRQ_TYPE_LEVEL_LOW>; - - active-semi,chglev-gpios = <&pioA 12 GPIO_ACTIVE_HIGH>; - active-semi,lbo-gpios = <&pioA 72 GPIO_ACTIVE_LOW>; - active-semi,input-voltage-threshold-microvolt = <6600>; - active-semi,precondition-timeout = <40>; - active-semi,total-timeout = <3>; - }; - }; diff --git a/Documentation/devicetree/bindings/power/supply/active-semi,act8945a-charger.yaml b/Documentation/devicetree/bindings/power/supply/active-semi,act8945a-charger.yaml deleted file mode 100644 index 5220d9cb16d8..000000000000 --- a/Documentation/devicetree/bindings/power/supply/active-semi,act8945a-charger.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/power/supply/active-semi,act8945a-charger.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Active-semi ACT8945A Charger Function - -maintainers: - - Sebastian Reichel - -allOf: - - $ref: power-supply.yaml# - -properties: - compatible: - const: active-semi,act8945a-charger - - interrupts: - maxItems: 1 - - active-semi,chglev-gpios: - maxItems: 1 - description: charge current level GPIO - - active-semi,lbo-gpios: - maxItems: 1 - description: low battery voltage detect GPIO - - active-semi,input-voltage-threshold-microvolt: - description: | - Specifies the charger's input over-voltage threshold value. - Despite the name, specified values are in millivolt (mV). - Defaults to 6.6 V - enum: [ 6600, 7000, 7500, 8000 ] - - active-semi,precondition-timeout: - $ref: /schemas/types.yaml#/definitions/uint32 - description: | - Specifies the charger's PRECONDITION safety timer setting value in minutes. - If 0, it means to disable this timer. - Defaults to 40 minutes. - enum: [ 0, 40, 60, 80 ] - - active-semi,total-timeout: - $ref: /schemas/types.yaml#/definitions/uint32 - description: | - Specifies the charger's total safety timer setting value in hours; - If 0, it means to disable this timer; - Defaults to 3 hours. - enum: [ 0, 3, 4, 5 ] - -required: - - compatible - - interrupts - - active-semi,chglev-gpios - - active-semi,lbo-gpios - -additionalProperties: false - -examples: - - | - #include - #include - pmic { - charger { - compatible = "active-semi,act8945a-charger"; - interrupt-parent = <&pioA>; - interrupts = <45 IRQ_TYPE_LEVEL_LOW>; - active-semi,chglev-gpios = <&pioA 12 GPIO_ACTIVE_HIGH>; - active-semi,lbo-gpios = <&pioA 72 GPIO_ACTIVE_LOW>; - active-semi,input-voltage-threshold-microvolt = <6600>; - active-semi,precondition-timeout = <40>; - active-semi,total-timeout = <3>; - }; - }; diff --git a/Documentation/devicetree/bindings/regulator/active-semi,act8945a.yaml b/Documentation/devicetree/bindings/regulator/active-semi,act8945a.yaml index bdf3f7d34ef5..a8d579844dc7 100644 --- a/Documentation/devicetree/bindings/regulator/active-semi,act8945a.yaml +++ b/Documentation/devicetree/bindings/regulator/active-semi,act8945a.yaml @@ -91,28 +91,41 @@ properties: maxItems: 1 active-semi,chglev-gpios: - description: CGHLEV GPIO + description: charge current level GPIO maxItems: 1 active-semi,lbo-gpios: - description: LBO GPIO + description: low battery voltage detect GPIO maxItems: 1 active-semi,input-voltage-threshold-microvolt: - description: Input voltage threshold - maxItems: 1 + description: + Specifies the charger's input over-voltage threshold value. Despite + the name, specified values are in millivolt (mV). + enum: [ 6600, 7000, 7500, 8000 ] + default: 6600 active-semi,precondition-timeout: - description: Precondition timeout + description: + Specifies the charger's PRECONDITION safety timer setting value in + minutes. If 0, it means to disable this timer. + enum: [ 0, 40, 60, 80 ] + default: 40 $ref: /schemas/types.yaml#/definitions/uint32 active-semi,total-timeout: - description: Total timeout + description: + Specifies the charger's total safety timer setting value in hours; If + 0, it means to disable this timer; + enum: [ 0, 3, 4, 5 ] + default: 3 $ref: /schemas/types.yaml#/definitions/uint32 required: - compatible - interrupts + - active-semi,chglev-gpios + - active-semi,lbo-gpios additionalProperties: false From d0de6895095356f1cc2d5f9825df220f10d34cee Mon Sep 17 00:00:00 2001 From: Jeff Chang Date: Wed, 13 Aug 2025 10:08:50 +0800 Subject: [PATCH 09/28] regulator: dt-bindings: Add Richtek RT5133 Support Add bindings for Richtek RT5133 IC Controlled PMIC Signed-off-by: Jeff Chang Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20250813020910.2977555-1-jeff_chang@richtek.com Signed-off-by: Mark Brown --- .../bindings/regulator/richtek,rt5133.yaml | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/richtek,rt5133.yaml diff --git a/Documentation/devicetree/bindings/regulator/richtek,rt5133.yaml b/Documentation/devicetree/bindings/regulator/richtek,rt5133.yaml new file mode 100644 index 000000000000..d2e007fee6ba --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/richtek,rt5133.yaml @@ -0,0 +1,178 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/richtek,rt5133.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Richtek RT5133 PMIC Regulator + +maintainers: + - ShihChia Chang + +description: + The RT5133 is an integrated Power Management IC for portable devices, + featuring 8 LDOs and 3 GPOs. It allows programmable output voltages, + soft-start times, and protections via I2C. GPO operation depends on LDO1 + voltage. + +properties: + compatible: + enum: + - richtek,rt5133 + + reg: + maxItems: 1 + + enable-gpios: + maxItems: 1 + + wakeup-source: true + + interrupts: + maxItems: 1 + + gpio-controller: true + + "#gpio-cells": + const: 2 + + richtek,oc-shutdown-all: + type: boolean + description: + Controls the behavior when any LDO (Low Dropout Regulator) enters an + Over Current state. + If set to true, all LDO channels will be shut down. + If set to false, only the affected LDO channel will shut down itself. + + richtek,pgb-shutdown-all: + type: boolean + description: + Controls the behavior when any LDO enters a Power Good Bad state. + If set to true, all LDO channels will be shut down. + If set to false, only the affected LDO channel will shut down itself. + + regulators: + type: object + additionalProperties: false + + properties: + base: + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for the base regulator, which is the top-level supply for + LDO1 to LDO6. It functions merely as an on/off switch rather than + regulating voltages. If none of LDO1 to LDO6 are in use, switching + off the base will reduce the quiescent current. + + required: + - regulator-name + + patternProperties: + "^ldo([1-6])$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for single LDO regulator + + required: + - regulator-name + + "^ldo([7-8])$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + description: + Properties for single LDO regulator + + properties: + vin-supply: true + + required: + - regulator-name + - vin-supply + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pmic@18 { + compatible = "richtek,rt5133"; + reg = <0x18>; + wakeup-source; + interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>; + enable-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>; + gpio-controller; + #gpio-cells = <2>; + richtek,oc-shutdown-all; + richtek,pgb-shutdown-all; + regulators { + base { + regulator-name = "base"; + }; + pvin78: ldo1 { + regulator-name = "ldo1"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3199998>; + regulator-active-discharge = <1>; + }; + ldo2 { + regulator-name = "ldo2"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3200000>; + regulator-active-discharge = <1>; + }; + ldo3 { + regulator-name = "ldo3"; + regulator-min-microvolt = <1700000>; + regulator-max-microvolt = <3000000>; + regulator-active-discharge = <1>; + }; + ldo4 { + regulator-name = "ldo4"; + regulator-min-microvolt = <1700000>; + regulator-max-microvolt = <3000000>; + regulator-active-discharge = <1>; + }; + ldo5 { + regulator-name = "ldo5"; + regulator-min-microvolt = <1700000>; + regulator-max-microvolt = <3000000>; + regulator-active-discharge = <1>; + }; + ldo6 { + regulator-name = "ldo6"; + regulator-min-microvolt = <1700000>; + regulator-max-microvolt = <3000000>; + regulator-active-discharge = <1>; + }; + ldo7 { + regulator-name = "ldo7"; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <1200000>; + regulator-active-discharge = <1>; + vin-supply = <&pvin78>; + }; + ldo8 { + regulator-name = "ldo8"; + regulator-min-microvolt = <855000>; + regulator-max-microvolt = <1200000>; + regulator-active-discharge = <1>; + vin-supply = <&pvin78>; + }; + }; + }; + }; From 714165e1c4b0d5b8c6d095fe07f65e6e7047aaeb Mon Sep 17 00:00:00 2001 From: Jeff Chang Date: Wed, 13 Aug 2025 10:08:51 +0800 Subject: [PATCH 10/28] regulator: rt5133: Add RT5133 PMIC regulator Support RT5133 is a highly-integrated chip. It includes 8 LDOs and 3 GPOs that can be used to drive output high/low purpose. The dependency of the GPO block is internally LDO1 Voltage. Signed-off-by: Jeff Chang Link: https://patch.msgid.link/20250813020910.2977555-2-jeff_chang@richtek.com Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 12 + drivers/regulator/Makefile | 1 + drivers/regulator/rt5133-regulator.c | 642 +++++++++++++++++++++++++++ 3 files changed, 655 insertions(+) create mode 100644 drivers/regulator/rt5133-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index e511174be7bf..223a500370d0 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1248,6 +1248,18 @@ config REGULATOR_RT5120 600mV to 1395mV, per step 6.250mV. The others are all fixed voltage by external hardware circuit. +config REGULATOR_RT5133 + tristate "Richtek RT5133 PMIC Regulators" + depends on I2C && GPIOLIB && OF + select REGMAP + select CRC8 + select OF_GPIO + help + This driver adds support for RT5133 PMIC regulators. + RT5133 is an integrated chip. It includes 8 LDOs and 3 GPOs that + can be used to drive output high/low purpose. The dependency of the + GPO block is internally LDO1 Voltage. + config REGULATOR_RT5190A tristate "Richtek RT5190A PMIC" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 80f820ed22a3..54e67e1c0c67 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -147,6 +147,7 @@ obj-$(CONFIG_REGULATOR_RT4803) += rt4803.o obj-$(CONFIG_REGULATOR_RT4831) += rt4831-regulator.o obj-$(CONFIG_REGULATOR_RT5033) += rt5033-regulator.o obj-$(CONFIG_REGULATOR_RT5120) += rt5120-regulator.o +obj-$(CONFIG_REGULATOR_RT5133) += rt5133-regulator.o obj-$(CONFIG_REGULATOR_RT5190A) += rt5190a-regulator.o obj-$(CONFIG_REGULATOR_RT5739) += rt5739.o obj-$(CONFIG_REGULATOR_RT5759) += rt5759-regulator.o diff --git a/drivers/regulator/rt5133-regulator.c b/drivers/regulator/rt5133-regulator.c new file mode 100644 index 000000000000..d0f367381fbb --- /dev/null +++ b/drivers/regulator/rt5133-regulator.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Richtek Technology Corp. +// Author: ChiYuan Huang +// Author: ShihChia Chang + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RT5133_REG_CHIP_INFO 0x00 +#define RT5133_REG_RST_CTRL 0x06 +#define RT5133_REG_BASE_CTRL 0x09 +#define RT5133_REG_GPIO_CTRL 0x0B +#define RT5133_REG_BASE_EVT 0x10 +#define RT5133_REG_LDO_PGB_STAT 0x15 +#define RT5133_REG_BASE_MASK 0x16 +#define RT5133_REG_LDO_SHDN 0x19 +#define RT5133_REG_LDO_ON 0x1A +#define RT5133_REG_LDO_OFF 0x1B +#define RT5133_REG_LDO1_CTRL1 0x20 +#define RT5133_REG_LDO1_CTRL2 0x21 +#define RT5133_REG_LDO1_CTRL3 0x22 +#define RT5133_REG_LDO2_CTRL1 0x24 +#define RT5133_REG_LDO2_CTRL2 0x25 +#define RT5133_REG_LDO2_CTRL3 0x26 +#define RT5133_REG_LDO3_CTRL1 0x28 +#define RT5133_REG_LDO3_CTRL2 0x29 +#define RT5133_REG_LDO3_CTRL3 0x2A +#define RT5133_REG_LDO4_CTRL1 0x2C +#define RT5133_REG_LDO4_CTRL2 0x2D +#define RT5133_REG_LDO4_CTRL3 0x2E +#define RT5133_REG_LDO5_CTRL1 0x30 +#define RT5133_REG_LDO5_CTRL2 0x31 +#define RT5133_REG_LDO5_CTRL3 0x32 +#define RT5133_REG_LDO6_CTRL1 0x34 +#define RT5133_REG_LDO6_CTRL2 0x35 +#define RT5133_REG_LDO6_CTRL3 0x36 +#define RT5133_REG_LDO7_CTRL1 0x38 +#define RT5133_REG_LDO7_CTRL2 0x39 +#define RT5133_REG_LDO7_CTRL3 0x3A +#define RT5133_REG_LDO8_CTRL1 0x3C +#define RT5133_REG_LDO8_CTRL2 0x3D +#define RT5133_REG_LDO8_CTRL3 0x3E +#define RT5133_REG_LDO8_CTRL4 0x3F + +#define RT5133_LDO_REG_BASE(_id) (0x20 + ((_id) - 1) * 4) + +#define RT5133_VENDOR_ID_MASK GENMASK(7, 4) +#define RT5133_RESET_CODE 0xB1 + +#define RT5133_FOFF_BASE_MASK BIT(1) +#define RT5133_OCSHDN_ALL_MASK BIT(7) +#define RT5133_OCSHDN_ALL_SHIFT (7) +#define RT5133_PGBSHDN_ALL_MASK BIT(6) +#define RT5133_PGBSHDN_ALL_SHIFT (6) + +#define RT5133_OCPTSEL_MASK BIT(5) +#define RT5133_PGBPTSEL_MASK BIT(4) +#define RT5133_STBTDSEL_MASK GENMASK(1, 0) + +#define RT5133_LDO_ENABLE_MASK BIT(7) +#define RT5133_LDO_VSEL_MASK GENMASK(7, 5) +#define RT5133_LDO_AD_MASK BIT(2) +#define RT5133_LDO_SOFT_START_MASK GENMASK(1, 0) + +#define RT5133_GPIO_NR 3 + +#define RT5133_LDO_PGB_EVT_MASK GENMASK(23, 16) +#define RT5133_LDO_PGB_EVT_SHIFT 16 +#define RT5133_LDO_OC_EVT_MASK GENMASK(15, 8) +#define RT5133_LDO_OC_EVT_SHIFT 8 +#define RT5133_VREF_EVT_MASK BIT(6) +#define RT5133_BASE_EVT_MASK GENMASK(7, 0) +#define RT5133_INTR_CLR_MASK GENMASK(23, 0) +#define RT5133_INTR_BYTE_NR 3 + +#define RT5133_MAX_I2C_BLOCK_SIZE 1 + +#define RT5133_CRC8_POLYNOMIAL 0x7 + +#define RT5133_I2C_ADDR_LEN 1 +#define RT5133_PREDATA_LEN 2 +#define RT5133_I2C_CRC_LEN 1 +#define RT5133_REG_ADDR_LEN 1 +#define RT5133_I2C_DUMMY_LEN 1 + +#define I2C_ADDR_XLATE_8BIT(_addr, _rw) ((((_addr) & 0x7F) << 1) | (_rw)) + +enum { + RT5133_REGULATOR_BASE = 0, + RT5133_REGULATOR_LDO1, + RT5133_REGULATOR_LDO2, + RT5133_REGULATOR_LDO3, + RT5133_REGULATOR_LDO4, + RT5133_REGULATOR_LDO5, + RT5133_REGULATOR_LDO6, + RT5133_REGULATOR_LDO7, + RT5133_REGULATOR_LDO8, + RT5133_REGULATOR_MAX +}; + +struct chip_data { + const struct regulator_desc *regulators; + const u8 vendor_id; +}; + +struct rt5133_priv { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *enable_gpio; + struct regulator_dev *rdev[RT5133_REGULATOR_MAX]; + struct gpio_chip gc; + const struct chip_data *cdata; + unsigned int gpio_output_flag; + u8 crc8_tbls[CRC8_TABLE_SIZE]; +}; + +static const unsigned int vout_type1_tables[] = { + 1800000, 2500000, 2700000, 2800000, 2900000, 3000000, 3100000, 3200000 +}; + +static const unsigned int vout_type2_tables[] = { + 1700000, 1800000, 1900000, 2500000, 2700000, 2800000, 2900000, 3000000 +}; + +static const unsigned int vout_type3_tables[] = { + 900000, 950000, 1000000, 1050000, 1100000, 1150000, 1200000, 1800000 +}; + +static const unsigned int vout_type4_tables[] = { + 855000, 900000, 950000, 1000000, 1040000, 1090000, 1140000, 1710000 +}; + +static const struct regulator_ops rt5133_regulator_ops = { + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_active_discharge = regulator_set_active_discharge_regmap, +}; + +static const struct regulator_ops rt5133_base_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +#define RT5133_REGULATOR_DESC(_name, _node_name, vtables, _supply) \ +{\ + .name = #_name,\ + .id = RT5133_REGULATOR_##_name,\ + .of_match = of_match_ptr(#_node_name),\ + .regulators_node = of_match_ptr("regulators"),\ + .supply_name = _supply,\ + .type = REGULATOR_VOLTAGE,\ + .owner = THIS_MODULE,\ + .ops = &rt5133_regulator_ops,\ + .n_voltages = ARRAY_SIZE(vtables),\ + .volt_table = vtables,\ + .enable_reg = RT5133_REG_##_name##_CTRL1,\ + .enable_mask = RT5133_LDO_ENABLE_MASK,\ + .vsel_reg = RT5133_REG_##_name##_CTRL2,\ + .vsel_mask = RT5133_LDO_VSEL_MASK,\ + .active_discharge_reg = RT5133_REG_##_name##_CTRL3,\ + .active_discharge_mask = RT5133_LDO_AD_MASK,\ +} + +static const struct regulator_desc rt5133_regulators[] = { + /* For digital part, base current control */ + { + .name = "base", + .id = RT5133_REGULATOR_BASE, + .of_match = of_match_ptr("base"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &rt5133_base_regulator_ops, + .enable_reg = RT5133_REG_BASE_CTRL, + .enable_mask = RT5133_FOFF_BASE_MASK, + .enable_is_inverted = true, + }, + RT5133_REGULATOR_DESC(LDO1, ldo1, vout_type1_tables, "base"), + RT5133_REGULATOR_DESC(LDO2, ldo2, vout_type1_tables, "base"), + RT5133_REGULATOR_DESC(LDO3, ldo3, vout_type2_tables, "base"), + RT5133_REGULATOR_DESC(LDO4, ldo4, vout_type2_tables, "base"), + RT5133_REGULATOR_DESC(LDO5, ldo5, vout_type2_tables, "base"), + RT5133_REGULATOR_DESC(LDO6, ldo6, vout_type2_tables, "base"), + RT5133_REGULATOR_DESC(LDO7, ldo7, vout_type3_tables, "vin"), + RT5133_REGULATOR_DESC(LDO8, ldo8, vout_type3_tables, "vin"), +}; + +static const struct regulator_desc rt5133a_regulators[] = { + /* For digital part, base current control */ + { + .name = "base", + .id = RT5133_REGULATOR_BASE, + .of_match = of_match_ptr("base"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &rt5133_base_regulator_ops, + .enable_reg = RT5133_REG_BASE_CTRL, + .enable_mask = RT5133_FOFF_BASE_MASK, + .enable_is_inverted = true, + }, + RT5133_REGULATOR_DESC(LDO1, ldo1, vout_type1_tables, "base"), + RT5133_REGULATOR_DESC(LDO2, ldo2, vout_type1_tables, "base"), + RT5133_REGULATOR_DESC(LDO3, ldo3, vout_type2_tables, "base"), + RT5133_REGULATOR_DESC(LDO4, ldo4, vout_type2_tables, "base"), + RT5133_REGULATOR_DESC(LDO5, ldo5, vout_type2_tables, "base"), + RT5133_REGULATOR_DESC(LDO6, ldo6, vout_type2_tables, "base"), + RT5133_REGULATOR_DESC(LDO7, ldo7, vout_type3_tables, "vin"), + RT5133_REGULATOR_DESC(LDO8, ldo8, vout_type4_tables, "vin"), +}; + +static const struct chip_data regulator_data[] = { + { rt5133_regulators, 0x70}, + { rt5133a_regulators, 0x80}, +}; + +static int rt5133_gpio_direction_output(struct gpio_chip *gpio, + unsigned int offset, int value) +{ + struct rt5133_priv *priv = gpiochip_get_data(gpio); + + if (offset >= RT5133_GPIO_NR) + return -EINVAL; + + return regmap_update_bits(priv->regmap, RT5133_REG_GPIO_CTRL, + BIT(7 - offset) | BIT(3 - offset), + value ? BIT(7 - offset) | BIT(3 - offset) : 0); +} + +static int rt5133_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct rt5133_priv *priv = gpiochip_get_data(chip); + + return !!(priv->gpio_output_flag & BIT(offset)); +} + +static int rt5133_get_gpioen_mask(unsigned int offset, unsigned int *mask) +{ + if (offset >= RT5133_GPIO_NR) + return -EINVAL; + + *mask = (BIT(7 - offset) | BIT(3 - offset)); + + return 0; +} + +static int rt5133_gpio_set(struct gpio_chip *chip, unsigned int offset, int set_val) +{ + struct rt5133_priv *priv = gpiochip_get_data(chip); + unsigned int mask = 0, val = 0, next_flag = priv->gpio_output_flag; + int ret = 0; + + ret = rt5133_get_gpioen_mask(offset, &mask); + if (ret) { + dev_err(priv->dev, "%s get gpion en mask failed, offset(%d)\n", __func__, offset); + return ret; + } + + val = set_val ? mask : 0; + + if (set_val) + next_flag |= BIT(offset); + else + next_flag &= ~BIT(offset); + + ret = regmap_update_bits(priv->regmap, RT5133_REG_GPIO_CTRL, mask, val); + if (ret) { + dev_err(priv->dev, "Failed to set gpio [%d] val %d\n", offset, + set_val); + return ret; + } + + priv->gpio_output_flag = next_flag; + return 0; +} + +static irqreturn_t rt5133_intr_handler(int irq_number, void *data) +{ + struct rt5133_priv *priv = data; + u32 intr_evts = 0, handle_evts; + int i, ret; + + ret = regmap_bulk_read(priv->regmap, RT5133_REG_BASE_EVT, &intr_evts, + RT5133_INTR_BYTE_NR); + if (ret) { + dev_err(priv->dev, "%s, read event failed\n", __func__); + return IRQ_NONE; + } + + handle_evts = intr_evts & RT5133_BASE_EVT_MASK; + /* + * VREF_EVT is a special case, if base off + * this event will also be trigger. Skip it + */ + if (handle_evts & ~RT5133_VREF_EVT_MASK) + dev_dbg(priv->dev, "base event occurred [0x%02x]\n", + handle_evts); + + handle_evts = (intr_evts & RT5133_LDO_OC_EVT_MASK) >> + RT5133_LDO_OC_EVT_SHIFT; + + for (i = RT5133_REGULATOR_LDO1; i < RT5133_REGULATOR_MAX && handle_evts; i++) { + if (!(handle_evts & BIT(i - 1))) + continue; + regulator_notifier_call_chain(priv->rdev[i], + REGULATOR_EVENT_OVER_CURRENT, + &i); + } + + handle_evts = (intr_evts & RT5133_LDO_PGB_EVT_MASK) >> + RT5133_LDO_PGB_EVT_SHIFT; + for (i = RT5133_REGULATOR_LDO1; i < RT5133_REGULATOR_MAX && handle_evts; i++) { + if (!(handle_evts & BIT(i - 1))) + continue; + regulator_notifier_call_chain(priv->rdev[i], + REGULATOR_EVENT_FAIL, &i); + } + + ret = regmap_bulk_write(priv->regmap, RT5133_REG_BASE_EVT, &intr_evts, + RT5133_INTR_BYTE_NR); + if (ret) + dev_err(priv->dev, "%s, clear event failed\n", __func__); + + return IRQ_HANDLED; +} + +static int rt5133_enable_interrupts(int irq_no, struct rt5133_priv *priv) +{ + u32 mask = RT5133_INTR_CLR_MASK; + int ret; + + /* Force to write clear all events */ + ret = regmap_bulk_write(priv->regmap, RT5133_REG_BASE_EVT, &mask, + RT5133_INTR_BYTE_NR); + if (ret) { + dev_err(priv->dev, "Failed to clear all interrupts\n"); + return ret; + } + + /* Unmask all interrupts */ + mask = 0; + ret = regmap_bulk_write(priv->regmap, RT5133_REG_BASE_MASK, &mask, + RT5133_INTR_BYTE_NR); + if (ret) { + dev_err(priv->dev, "Failed to unmask all interrupts\n"); + return ret; + } + + return devm_request_threaded_irq(priv->dev, irq_no, NULL, + rt5133_intr_handler, IRQF_ONESHOT, + dev_name(priv->dev), priv); +} + +static int rt5133_regmap_hw_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct rt5133_priv *priv = context; + struct i2c_client *client = to_i2c_client(priv->dev); + u8 reg = *(u8 *)reg_buf, crc; + u8 *buf; + int buf_len = RT5133_PREDATA_LEN + val_size + RT5133_I2C_CRC_LEN; + int read_len, ret; + + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = I2C_ADDR_XLATE_8BIT(client->addr, I2C_SMBUS_READ); + buf[1] = reg; + + read_len = val_size + RT5133_I2C_CRC_LEN; + ret = i2c_smbus_read_i2c_block_data(client, reg, read_len, + buf + RT5133_PREDATA_LEN); + + if (ret < 0) + goto out_read_err; + + if (ret != read_len) { + ret = -EIO; + goto out_read_err; + } + + crc = crc8(priv->crc8_tbls, buf, RT5133_PREDATA_LEN + val_size, 0); + if (crc != buf[RT5133_PREDATA_LEN + val_size]) { + ret = -EIO; + goto out_read_err; + } + + memcpy(val_buf, buf + RT5133_PREDATA_LEN, val_size); + dev_dbg(priv->dev, "%s, reg = 0x%02x, data = 0x%02x\n", __func__, reg, *(u8 *)val_buf); + +out_read_err: + kfree(buf); + return (ret < 0) ? ret : 0; +} + +static int rt5133_regmap_hw_write(void *context, const void *data, size_t count) +{ + struct rt5133_priv *priv = context; + struct i2c_client *client = to_i2c_client(priv->dev); + u8 reg = *(u8 *)data, crc; + u8 *buf; + int buf_len = RT5133_I2C_ADDR_LEN + count + RT5133_I2C_CRC_LEN + + RT5133_I2C_DUMMY_LEN; + int write_len, ret; + + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = I2C_ADDR_XLATE_8BIT(client->addr, I2C_SMBUS_WRITE); + buf[1] = reg; + memcpy(buf + RT5133_PREDATA_LEN, data + RT5133_REG_ADDR_LEN, + count - RT5133_REG_ADDR_LEN); + + crc = crc8(priv->crc8_tbls, buf, RT5133_I2C_ADDR_LEN + count, 0); + buf[RT5133_I2C_ADDR_LEN + count] = crc; + + write_len = count - RT5133_REG_ADDR_LEN + RT5133_I2C_CRC_LEN + + RT5133_I2C_DUMMY_LEN; + ret = i2c_smbus_write_i2c_block_data(client, reg, write_len, + buf + RT5133_PREDATA_LEN); + + dev_dbg(priv->dev, "%s, reg = 0x%02x, data = 0x%02x\n", __func__, reg, + *(u8 *)(buf + RT5133_PREDATA_LEN)); + kfree(buf); + return ret; +} + +static const struct regmap_bus rt5133_regmap_bus = { + .read = rt5133_regmap_hw_read, + .write = rt5133_regmap_hw_write, + /* Due to crc, the block read/write length has the limit */ + .max_raw_read = RT5133_MAX_I2C_BLOCK_SIZE, + .max_raw_write = RT5133_MAX_I2C_BLOCK_SIZE, +}; + +static bool rt5133_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5133_REG_CHIP_INFO: + case RT5133_REG_BASE_EVT...RT5133_REG_LDO_PGB_STAT: + case RT5133_REG_LDO_ON...RT5133_REG_LDO_OFF: + case RT5133_REG_LDO1_CTRL1: + case RT5133_REG_LDO2_CTRL1: + case RT5133_REG_LDO3_CTRL1: + case RT5133_REG_LDO4_CTRL1: + case RT5133_REG_LDO5_CTRL1: + case RT5133_REG_LDO6_CTRL1: + case RT5133_REG_LDO7_CTRL1: + case RT5133_REG_LDO8_CTRL1: + return true; + default: + return false; + }; +} + +static const struct regmap_config rt5133_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = RT5133_REG_LDO8_CTRL4, + .cache_type = REGCACHE_FLAT, + .num_reg_defaults_raw = RT5133_REG_LDO8_CTRL4 + 1, + .volatile_reg = rt5133_is_volatile_reg, +}; + +static int rt5133_chip_reset(struct rt5133_priv *priv) +{ + int ret; + + ret = regmap_write(priv->regmap, RT5133_REG_RST_CTRL, + RT5133_RESET_CODE); + if (ret) + return ret; + + /* Wait for register reset to take effect */ + udelay(2); + + return 0; +} + +static int rt5133_validate_vendor_info(struct rt5133_priv *priv) +{ + unsigned int val = 0; + int i, ret; + + ret = regmap_read(priv->regmap, RT5133_REG_CHIP_INFO, &val); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(regulator_data); i++) { + if ((val & RT5133_VENDOR_ID_MASK) == + regulator_data[i].vendor_id){ + priv->cdata = ®ulator_data[i]; + break; + } + } + if (IS_ERR(priv->cdata)) { + dev_err(priv->dev, "Failed to find regualtor match version\n"); + return -ENODEV; + } + + return 0; +} + +static int rt5133_parse_dt(struct rt5133_priv *priv) +{ + unsigned int val = 0; + int ret = 0; + + if (!device_property_read_bool(priv->dev, "richtek,oc-shutdown-all")) + val = 0; + else + val = 1 << RT5133_OCSHDN_ALL_SHIFT; + ret = regmap_update_bits(priv->regmap, RT5133_REG_LDO_SHDN, + RT5133_OCSHDN_ALL_MASK, val); + if (ret) + return ret; + + if (!device_property_read_bool(priv->dev, "richtek,pgb-shutdown-all")) + val = 0; + else + val = 1 << RT5133_PGBSHDN_ALL_SHIFT; + return regmap_update_bits(priv->regmap, RT5133_REG_LDO_SHDN, + RT5133_PGBSHDN_ALL_MASK, val); +} + +static int rt5133_probe(struct i2c_client *i2c) +{ + struct rt5133_priv *priv; + struct regulator_config config = {0}; + int i, ret; + + priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &i2c->dev; + crc8_populate_msb(priv->crc8_tbls, RT5133_CRC8_POLYNOMIAL); + + priv->enable_gpio = devm_gpiod_get_optional(&i2c->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->enable_gpio)) + dev_err(&i2c->dev, "Failed to request HWEN gpio, check if default en=high\n"); + + priv->regmap = devm_regmap_init(&i2c->dev, &rt5133_regmap_bus, priv, + &rt5133_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&i2c->dev, "Failed to register regmap\n"); + return PTR_ERR(priv->regmap); + } + + ret = rt5133_validate_vendor_info(priv); + if (ret) { + dev_err(&i2c->dev, "Failed to check vendor info [%d]\n", ret); + return ret; + } + + ret = rt5133_chip_reset(priv); + if (ret) { + dev_err(&i2c->dev, "Failed to execute sw reset\n"); + return ret; + } + + config.dev = &i2c->dev; + config.driver_data = priv; + config.regmap = priv->regmap; + + for (i = 0; i < RT5133_REGULATOR_MAX; i++) { + priv->rdev[i] = devm_regulator_register(&i2c->dev, + priv->cdata->regulators + i, + &config); + if (IS_ERR(priv->rdev[i])) { + dev_err(&i2c->dev, + "Failed to register [%d] regulator\n", i); + return PTR_ERR(priv->rdev[i]); + } + } + + ret = rt5133_parse_dt(priv); + if (ret) { + dev_err(&i2c->dev, "%s, Failed to parse dt\n", __func__); + return ret; + } + + priv->gc.label = dev_name(&i2c->dev); + priv->gc.parent = &i2c->dev; + priv->gc.base = -1; + priv->gc.ngpio = RT5133_GPIO_NR; + priv->gc.set = rt5133_gpio_set; + priv->gc.get = rt5133_gpio_get; + priv->gc.direction_output = rt5133_gpio_direction_output; + priv->gc.can_sleep = true; + + ret = devm_gpiochip_add_data(&i2c->dev, &priv->gc, priv); + if (ret) + return ret; + + ret = rt5133_enable_interrupts(i2c->irq, priv); + if (ret) { + dev_err(&i2c->dev, "enable interrupt failed\n"); + return ret; + } + + i2c_set_clientdata(i2c, priv); + + return ret; +} + +static const struct of_device_id __maybe_unused rt5133_of_match_table[] = { + { .compatible = "richtek,rt5133", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5133_of_match_table); + +static struct i2c_driver rt5133_driver = { + .driver = { + .name = "rt5133", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = rt5133_of_match_table, + }, + .probe = rt5133_probe, +}; +module_i2c_driver(rt5133_driver); + +MODULE_DESCRIPTION("RT5133 Regulator Driver"); +MODULE_LICENSE("GPL v2"); From 37533933bfe92cd5a99ef4743f31dac62ccc8de0 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Wed, 13 Aug 2025 18:15:01 +0200 Subject: [PATCH 11/28] regulator: remove unneeded 'fast_io' parameter in regmap_config When using MMIO with regmap, fast_io is implied. No need to set it again. Signed-off-by: Wolfram Sang Link: https://patch.msgid.link/20250813161517.4746-16-wsa+renesas@sang-engineering.com Signed-off-by: Mark Brown --- drivers/regulator/qcom-refgen-regulator.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/regulator/qcom-refgen-regulator.c b/drivers/regulator/qcom-refgen-regulator.c index cfa72ce85bc8..299ac3c8c3bc 100644 --- a/drivers/regulator/qcom-refgen-regulator.c +++ b/drivers/regulator/qcom-refgen-regulator.c @@ -94,7 +94,6 @@ static const struct regmap_config qcom_refgen_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .fast_io = true, }; static int qcom_refgen_probe(struct platform_device *pdev) From 6d068f1ae2a2f713d7f21a9a602e65b3d6b6fc6d Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 14 Aug 2025 08:33:26 +0100 Subject: [PATCH 12/28] regulator: rt5133: Fix spelling mistake "regualtor" -> "regulator" There is a spelling mistake in a dev_err message. Fix it. Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20250814073326.17644-1-colin.i.king@gmail.com Signed-off-by: Mark Brown --- drivers/regulator/rt5133-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/regulator/rt5133-regulator.c b/drivers/regulator/rt5133-regulator.c index d0f367381fbb..39532618e73d 100644 --- a/drivers/regulator/rt5133-regulator.c +++ b/drivers/regulator/rt5133-regulator.c @@ -511,7 +511,7 @@ static int rt5133_validate_vendor_info(struct rt5133_priv *priv) } } if (IS_ERR(priv->cdata)) { - dev_err(priv->dev, "Failed to find regualtor match version\n"); + dev_err(priv->dev, "Failed to find regulator match version\n"); return -ENODEV; } From 550bc517e59347b3b1af7d290eac4fb1411a3d4e Mon Sep 17 00:00:00 2001 From: Qianfeng Rong Date: Sun, 17 Aug 2025 22:23:27 +0800 Subject: [PATCH 13/28] regulator: bd718x7: Use kcalloc() instead of kzalloc() Replace calls of 'devm_kzalloc(dev, count * sizeof([type]), flags)' with 'devm_kcalloc(dev, count, sizeof([type]), flags)' in setup_feedback_loop() for safer memory allocation with built-in overflow protection. Signed-off-by: Qianfeng Rong Reviewed-by: Matti Vaittinen Link: https://patch.msgid.link/20250817142327.174531-1-rongqianfeng@vivo.com Signed-off-by: Mark Brown --- drivers/regulator/bd718x7-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/regulator/bd718x7-regulator.c b/drivers/regulator/bd718x7-regulator.c index e803cc59d68a..022d98f3c32a 100644 --- a/drivers/regulator/bd718x7-regulator.c +++ b/drivers/regulator/bd718x7-regulator.c @@ -1598,7 +1598,7 @@ static int setup_feedback_loop(struct device *dev, struct device_node *np, if (desc->n_linear_ranges && desc->linear_ranges) { struct linear_range *new; - new = devm_kzalloc(dev, desc->n_linear_ranges * + new = devm_kcalloc(dev, desc->n_linear_ranges, sizeof(struct linear_range), GFP_KERNEL); if (!new) From f7f804633c91f0fbf03eefbae39eec2205191a82 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 18 Aug 2025 15:30:14 +0300 Subject: [PATCH 14/28] regulator: rt5133: Fix IS_ERR() vs NULL bug in rt5133_validate_vendor_info() The "priv->cdata" pointer isn't an error pointer; this should be a NULL check instead. Otherwise it leads to a NULL pointer dereference in the caller, rt5133_probe(). Fixes: 714165e1c4b0 ("regulator: rt5133: Add RT5133 PMIC regulator Support") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aKMc1oK-7yY4cD3K@stanley.mountain Signed-off-by: Mark Brown --- drivers/regulator/rt5133-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/regulator/rt5133-regulator.c b/drivers/regulator/rt5133-regulator.c index 39532618e73d..129b1f13c880 100644 --- a/drivers/regulator/rt5133-regulator.c +++ b/drivers/regulator/rt5133-regulator.c @@ -510,7 +510,7 @@ static int rt5133_validate_vendor_info(struct rt5133_priv *priv) break; } } - if (IS_ERR(priv->cdata)) { + if (!priv->cdata) { dev_err(priv->dev, "Failed to find regulator match version\n"); return -ENODEV; } From ec0be3cdf40b5302248f3fb27a911cc630e8b855 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Tue, 19 Aug 2025 12:25:43 +0800 Subject: [PATCH 15/28] regulator: consumer.rst: document bulk operations The current consumer documentation does not include bulk operations, providing an example of how to acquire multiple regulators by calling regulator_get() multiple times. That solution is valid and slightly simpler for a small amount of regulators, but it does not scale well. Document the bulk operations to get, enable and disable regulators. Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20250819-reg_consumer_doc-v1-1-b631fc0d35a3@gmail.com Signed-off-by: Mark Brown --- Documentation/power/regulator/consumer.rst | 30 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Documentation/power/regulator/consumer.rst b/Documentation/power/regulator/consumer.rst index 9d2416f63f6e..c01675b25a90 100644 --- a/Documentation/power/regulator/consumer.rst +++ b/Documentation/power/regulator/consumer.rst @@ -23,10 +23,18 @@ To release the regulator the consumer driver should call :: regulator_put(regulator); Consumers can be supplied by more than one regulator e.g. codec consumer with -analog and digital supplies :: +analog and digital supplies by means of bulk operations :: + + struct regulator_bulk_data supplies[2]; + + supplies[0].supply = "Vcc"; /* digital core */ + supplies[1].supply = "Avdd"; /* analog */ + + ret = regulator_bulk_get(dev, ARRAY_SIZE(supplies), supplies); + + // convenience helper to call regulator_put() on multiple regulators + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); - digital = regulator_get(dev, "Vcc"); /* digital core */ - analog = regulator_get(dev, "Avdd"); /* analog */ The regulator access functions regulator_get() and regulator_put() will usually be called in your device drivers probe() and remove() respectively. @@ -51,11 +59,21 @@ A consumer can determine if a regulator is enabled by calling:: This will return > zero when the regulator is enabled. +A set of regulators can be enabled with a single bulk operation :: + + int regulator_bulk_enable(int num_consumers, + struct regulator_bulk_data *consumers); + A consumer can disable its supply when no longer needed by calling:: int regulator_disable(regulator); +Or a number of them :: + + int regulator_bulk_disable(int num_consumers, + struct regulator_bulk_data *consumers); + NOTE: This may not disable the supply if it's shared with other consumers. The regulator will only be disabled when the enabled reference count is zero. @@ -64,11 +82,15 @@ Finally, a regulator can be forcefully disabled in the case of an emergency:: int regulator_force_disable(regulator); +This operation is also supported for multiple regulators :: + + int regulator_bulk_force_disable(int num_consumers, + struct regulator_bulk_data *consumers); + NOTE: this will immediately and forcefully shutdown the regulator output. All consumers will be powered off. - 3. Regulator Voltage Control & Status (dynamic drivers) ======================================================= From e2ab5f600bb01d3625d667d97b3eb7538e388336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Thu, 21 Aug 2025 12:07:20 +0300 Subject: [PATCH 16/28] rust: regulator: use `to_result` for error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplifies error handling by replacing the manual check of the return value with the `to_result` helper. Signed-off-by: Onur Özkan Reviewed-by: Daniel Almeida Message-ID: <20250821090720.23939-1-work@onurozkan.dev> Signed-off-by: Mark Brown --- rust/kernel/regulator.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/rust/kernel/regulator.rs b/rust/kernel/regulator.rs index 704147e18bfc..34bb24ec8d4d 100644 --- a/rust/kernel/regulator.rs +++ b/rust/kernel/regulator.rs @@ -267,11 +267,8 @@ impl Regulator { pub fn get_voltage(&self) -> Result { // SAFETY: Safe as per the type invariants of `Regulator`. let voltage = unsafe { bindings::regulator_get_voltage(self.inner.as_ptr()) }; - if voltage < 0 { - Err(kernel::error::Error::from_errno(voltage)) - } else { - Ok(Voltage::from_microvolts(voltage)) - } + + to_result(voltage).map(|()| Voltage::from_microvolts(voltage)) } fn get_internal(dev: &Device, name: &CStr) -> Result> { From 899fb38dd76dd3ede425bbaf8a96d390180a5d1c Mon Sep 17 00:00:00 2001 From: Liao Yuanhong Date: Thu, 28 Aug 2025 20:27:37 +0800 Subject: [PATCH 17/28] regulator: core: Remove redundant ternary operators For ternary operators in the form of "a ? true : false", if 'a' itself returns a boolean result, the ternary operator can be omitted. Remove redundant ternary operators to clean up the code. Signed-off-by: Liao Yuanhong Message-ID: <20250828122737.43488-1-liaoyuanhong@vivo.com> Signed-off-by: Mark Brown --- drivers/regulator/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 554d83c4af0c..dd7b10e768c0 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1586,8 +1586,8 @@ static int set_machine_constraints(struct regulator_dev *rdev) } if (rdev->constraints->active_discharge && ops->set_active_discharge) { - bool ad_state = (rdev->constraints->active_discharge == - REGULATOR_ACTIVE_DISCHARGE_ENABLE) ? true : false; + bool ad_state = rdev->constraints->active_discharge == + REGULATOR_ACTIVE_DISCHARGE_ENABLE; ret = ops->set_active_discharge(rdev, ad_state); if (ret < 0) { From 9d35d068fb138160709e04e3ee97fe29a6f8615b Mon Sep 17 00:00:00 2001 From: Qianfeng Rong Date: Fri, 29 Aug 2025 18:14:11 +0800 Subject: [PATCH 18/28] regulator: scmi: Use int type to store negative error codes Change the 'ret' variable from u32 to int to store negative error codes or zero returned by of_property_read_u32(). Storing the negative error codes in unsigned type, doesn't cause an issue at runtime but it's ugly as pants. Additionally, assigning negative error codes to unsigned type may trigger a GCC warning when the -Wsign-conversion flag is enabled. No effect on runtime. Signed-off-by: Qianfeng Rong Reviewed-by: Sudeep Holla Fixes: 0fbeae70ee7c ("regulator: add SCMI driver") Link: https://patch.msgid.link/20250829101411.625214-1-rongqianfeng@vivo.com Signed-off-by: Mark Brown --- drivers/regulator/scmi-regulator.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/scmi-regulator.c b/drivers/regulator/scmi-regulator.c index 9df726f10ad1..6d609c42e479 100644 --- a/drivers/regulator/scmi-regulator.c +++ b/drivers/regulator/scmi-regulator.c @@ -257,7 +257,8 @@ static int process_scmi_regulator_of_node(struct scmi_device *sdev, struct device_node *np, struct scmi_regulator_info *rinfo) { - u32 dom, ret; + u32 dom; + int ret; ret = of_property_read_u32(np, "reg", &dom); if (ret) From f5d15ff664a32593e0152d21ea747fb77e32ce7e Mon Sep 17 00:00:00 2001 From: Woodrow Douglass Date: Fri, 5 Sep 2025 08:39:42 -0400 Subject: [PATCH 19/28] regulator: dt-bindings: nxp,pf530x: Add NXP PF5300/PF5301/PF5302 PMICs Bindings for the pf530x series of voltage regulators Signed-off-by: Woodrow Douglass Reviewed-by: Krzysztof Kozlowski Message-ID: <20250902-pf530x-v7-1-10eb2542f944@carnegierobotics.com> Signed-off-by: Mark Brown --- .../bindings/regulator/nxp,pf5300.yaml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/nxp,pf5300.yaml diff --git a/Documentation/devicetree/bindings/regulator/nxp,pf5300.yaml b/Documentation/devicetree/bindings/regulator/nxp,pf5300.yaml new file mode 100644 index 000000000000..5b9d5d4e48d0 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/nxp,pf5300.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/nxp,pf5300.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP PF5300/PF5301/PF5302 PMIC regulators + +maintainers: + - Woodrow Douglass + +description: | + The PF5300, PF5301, and PF5302 integrate high-performance buck converters, + 12 A, 8 A, and 15 A, respectively, to power high-end automotive and industrial + processors. With adaptive voltage positioning and a high-bandwidth loop, they + offer transient regulation to minimize capacitor requirements. + +allOf: + - $ref: regulator.yaml# + +properties: + compatible: + oneOf: + - const: nxp,pf5300 + - items: + - enum: + - nxp,pf5301 + - nxp,pf5302 + - const: nxp,pf5300 + reg: + maxItems: 1 + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + regulator@28 { + compatible = "nxp,pf5302", "nxp,pf5300"; + reg = <0x28>; + + regulator-always-on; + regulator-boot-on; + regulator-max-microvolt = <1200000>; + regulator-min-microvolt = <500000>; + }; + }; From b497e1a1a2b10c4ddb28064fba229365ae03311a Mon Sep 17 00:00:00 2001 From: Woodrow Douglass Date: Fri, 5 Sep 2025 08:39:43 -0400 Subject: [PATCH 20/28] regulator: pf530x: Add a driver for the NXP PF5300 Regulator This driver allows reading some regulator settings and adjusting output voltage. It is based on information from the datasheet at https://www.nxp.com/docs/en/data-sheet/PF5300.pdf Signed-off-by: Woodrow Douglass Message-ID: <20250902-pf530x-v7-2-10eb2542f944@carnegierobotics.com> Signed-off-by: Mark Brown --- MAINTAINERS | 6 + drivers/regulator/Kconfig | 12 + drivers/regulator/Makefile | 1 + drivers/regulator/pf530x-regulator.c | 375 +++++++++++++++++++++++++++ 4 files changed, 394 insertions(+) create mode 100644 drivers/regulator/pf530x-regulator.c diff --git a/MAINTAINERS b/MAINTAINERS index fed6cd812d79..9abcd2d4c10f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18292,6 +18292,12 @@ F: Documentation/devicetree/bindings/clock/*imx* F: drivers/clk/imx/ F: include/dt-bindings/clock/*imx* +NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER +M: Woodrow Douglass +S: Maintained +F: Documentation/devicetree/bindings/regulator/nxp,pf5300.yaml +F: drivers/regulator/pf530x-regulator.c + NXP PF8100/PF8121A/PF8200 PMIC REGULATOR DEVICE DRIVER M: Jagan Teki S: Maintained diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index eaa6df1c9f80..611356bea09d 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1006,6 +1006,18 @@ config REGULATOR_PCAP This driver provides support for the voltage regulators of the PCAP2 PMIC. +config REGULATOR_PF530X + tristate "NXP PF5300/PF5301/PF5302 regulator driver" + depends on I2C && OF + select REGMAP_I2C + help + Say y here to support the regulators found on the NXP + PF5300/PF5301/PF5302 PMIC. + + Say M here if you want to support for the regulators found + on the NXP PF5300/PF5301/PF5302 PMIC. The module will be named + "pf530x-regulator". + config REGULATOR_PF8X00 tristate "NXP PF8100/PF8121A/PF8200 regulator driver" depends on I2C && OF diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index be98b29d6675..60ca55d04aef 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -125,6 +125,7 @@ obj-$(CONFIG_REGULATOR_QCOM_USB_VBUS) += qcom_usb_vbus-regulator.o obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o obj-$(CONFIG_REGULATOR_PCA9450) += pca9450-regulator.o obj-$(CONFIG_REGULATOR_PF9453) += pf9453-regulator.o +obj-$(CONFIG_REGULATOR_PF530X) += pf530x-regulator.o obj-$(CONFIG_REGULATOR_PF8X00) += pf8x00-regulator.o obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o obj-$(CONFIG_REGULATOR_PV88060) += pv88060-regulator.o diff --git a/drivers/regulator/pf530x-regulator.c b/drivers/regulator/pf530x-regulator.c new file mode 100644 index 000000000000..f789c4b6a499 --- /dev/null +++ b/drivers/regulator/pf530x-regulator.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0+ + +// documentation of this device is available at +// https://www.nxp.com/docs/en/data-sheet/PF5300.pdf + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* registers */ +#define PF530X_DEVICEID 0x00 +#define PF530X_REV 0x01 +#define PF530X_EMREV 0x02 +#define PF530X_PROGID 0x03 +#define PF530X_CONFIG1 0x04 +#define PF530X_INT_STATUS1 0x05 +#define PF530X_INT_SENSE1 0x06 +#define PF530X_INT_STATUS2 0x07 +#define PF530X_INT_SENSE2 0x08 +#define PF530X_BIST_STAT1 0x09 +#define PF530X_BIST_CTRL 0x0a +#define PF530X_STATE 0x0b +#define PF530X_STATE_CTRL 0x0c +#define PF530X_SW1_VOLT 0x0d +#define PF530X_SW1_STBY_VOLT 0x0e +#define PF530X_SW1_CTRL1 0x0f +#define PF530X_SW1_CTRL2 0x10 +#define PF530X_CLK_CTRL 0x11 +#define PF530X_SEQ_CTRL1 0x12 +#define PF530X_SEQ_CTRL2 0x13 +#define PF530X_RANDOM_CHK 0x14 +#define PF530X_RANDOM_GEN 0x15 +#define PF530X_WD_CTRL1 0x16 +#define PF530X_WD_SEED 0x17 +#define PF530X_WD_ANSWER 0x18 +#define PF530X_FLT_CNT1 0x19 +#define PF530X_FLT_CNT2 0x1a +#define PF530X_OTP_MODE 0x2f + +enum pf530x_states { + PF530X_STATE_POF, + PF530X_STATE_FUSE_LOAD, + PF530X_STATE_LP_OFF, + PF530X_STATE_SELF_TEST, + PF530X_STATE_POWER_UP, + PF530X_STATE_INIT, + PF530X_STATE_IO_RELEASE, + PF530X_STATE_RUN, + PF530X_STATE_STANDBY, + PF530X_STATE_FAULT, + PF530X_STATE_FAILSAFE, + PF530X_STATE_POWER_DOWN, + PF530X_STATE_2MS_SELFTEST_RETRY, + PF530X_STATE_OFF_DLY, +}; + +#define PF530_FAM 0x50 +enum pf530x_devid { + PF5300 = 0x3, + PF5301 = 0x4, + PF5302 = 0x5, +}; + +#define PF530x_FAM 0x50 +#define PF530x_DEVICE_FAM_MASK GENMASK(7, 4) +#define PF530x_DEVICE_ID_MASK GENMASK(3, 0) + +#define PF530x_STATE_MASK GENMASK(3, 0) +#define PF530x_STATE_RUN 0x07 +#define PF530x_STATE_STANDBY 0x08 +#define PF530x_STATE_LP_OFF 0x02 + +#define PF530X_OTP_STBY_MODE GENMASK(3, 2) +#define PF530X_OTP_RUN_MODE GENMASK(1, 0) + +#define PF530X_INT_STATUS_OV BIT(1) +#define PF530X_INT_STATUS_UV BIT(2) +#define PF530X_INT_STATUS_ILIM BIT(3) + +#define SW1_ILIM_S BIT(0) +#define VMON_UV_S BIT(1) +#define VMON_OV_S BIT(2) +#define VIN_OVLO_S BIT(3) +#define BG_ERR_S BIT(6) + +#define THERM_155_S BIT(3) +#define THERM_140_S BIT(2) +#define THERM_125_S BIT(1) +#define THERM_110_S BIT(0) + +struct pf530x_chip { + struct regmap *regmap; + struct device *dev; +}; + +static const struct regmap_config pf530x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PF530X_OTP_MODE, + .cache_type = REGCACHE_MAPLE, +}; + +static int pf530x_get_status(struct regulator_dev *rdev) +{ + unsigned int state; + int ret; + + ret = regmap_read(rdev->regmap, PF530X_INT_SENSE1, &state); + if (ret != 0) + return ret; + + if ((state & (BG_ERR_S | SW1_ILIM_S | VMON_UV_S | VMON_OV_S | VIN_OVLO_S)) + != 0) + return REGULATOR_STATUS_ERROR; + + // no errors, check if what non-error state we're in + ret = regmap_read(rdev->regmap, PF530X_STATE, &state); + if (ret != 0) + return ret; + + state &= PF530x_STATE_MASK; + + switch (state) { + case PF530x_STATE_RUN: + ret = REGULATOR_STATUS_NORMAL; + break; + case PF530x_STATE_STANDBY: + ret = REGULATOR_STATUS_STANDBY; + break; + case PF530x_STATE_LP_OFF: + ret = REGULATOR_STATUS_OFF; + break; + default: + ret = REGULATOR_STATUS_ERROR; + break; + } + return ret; +} + +static int pf530x_get_error_flags(struct regulator_dev *rdev, unsigned int *flags) +{ + unsigned int status; + int ret; + + ret = regmap_read(rdev->regmap, PF530X_INT_STATUS1, &status); + + if (ret != 0) + return ret; + + *flags = 0; + + if (status & PF530X_INT_STATUS_OV) + *flags |= REGULATOR_ERROR_OVER_VOLTAGE_WARN; + + if (status & PF530X_INT_STATUS_UV) + *flags |= REGULATOR_ERROR_UNDER_VOLTAGE; + + if (status & PF530X_INT_STATUS_ILIM) + *flags |= REGULATOR_ERROR_OVER_CURRENT; + + ret = regmap_read(rdev->regmap, PF530X_INT_SENSE2, &status); + + if (ret != 0) + return ret; + + if ((status & (THERM_155_S | + THERM_140_S | + THERM_125_S | + THERM_110_S)) != 0) + *flags |= REGULATOR_ERROR_OVER_TEMP_WARN; + + return 0; +} + +static const struct regulator_ops pf530x_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .map_voltage = regulator_map_voltage_linear_range, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .get_status = pf530x_get_status, + .get_error_flags = pf530x_get_error_flags, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, +}; + +static const struct linear_range vrange = REGULATOR_LINEAR_RANGE(500000, 0, 140, 5000); + +static const struct regulator_desc pf530x_reg_desc = { + .name = "SW1", + .ops = &pf530x_regulator_ops, + .linear_ranges = &vrange, + .n_linear_ranges = 1, + .type = REGULATOR_VOLTAGE, + .id = 0, + .owner = THIS_MODULE, + .vsel_reg = PF530X_SW1_VOLT, + .vsel_mask = 0xFF, + .bypass_reg = PF530X_SW1_CTRL2, + .bypass_mask = 0x07, + .bypass_val_on = 0x07, + .bypass_val_off = 0x00, + .enable_reg = PF530X_SW1_CTRL1, + .enable_mask = GENMASK(5, 2), + .enable_val = GENMASK(5, 2), + .disable_val = 0, +}; + +static int pf530x_identify(struct pf530x_chip *chip) +{ + unsigned int value; + u8 dev_fam, dev_id, full_layer_rev, metal_layer_rev, prog_idh, prog_idl, emrev; + const char *name = NULL; + int ret; + + ret = regmap_read(chip->regmap, PF530X_DEVICEID, &value); + if (ret) { + dev_err(chip->dev, "failed to read chip family\n"); + return ret; + } + + dev_fam = value & PF530x_DEVICE_FAM_MASK; + switch (dev_fam) { + case PF530x_FAM: + break; + default: + dev_err(chip->dev, + "Chip 0x%x is not from PF530X family\n", dev_fam); + return ret; + } + + dev_id = value & PF530x_DEVICE_ID_MASK; + switch (dev_id) { + case PF5300: + name = "PF5300"; + break; + case PF5301: + name = "PF5301"; + break; + case PF5302: + name = "PF5302"; + break; + default: + dev_err(chip->dev, "Unknown pf530x device id 0x%x\n", dev_id); + return -ENODEV; + } + + ret = regmap_read(chip->regmap, PF530X_REV, &value); + if (ret) { + dev_err(chip->dev, "failed to read chip rev\n"); + return ret; + } + + full_layer_rev = ((value & 0xF0) == 0) ? '0' : ((((value & 0xF0) >> 4) - 1) + 'A'); + metal_layer_rev = value & 0xF; + + ret = regmap_read(chip->regmap, PF530X_EMREV, &value); + if (ret) { + dev_err(chip->dev, "failed to read chip emrev register\n"); + return ret; + } + + prog_idh = (value >> 4) + 'A'; + // prog_idh skips 'O', per page 96 of the datasheet + if (prog_idh >= 'O') + prog_idh += 1; + + emrev = value & 0x7; + + ret = regmap_read(chip->regmap, PF530X_PROGID, &value); + if (ret) { + dev_err(chip->dev, "failed to read chip progid register\n"); + return ret; + } + + if (value >= 0x22) { + dev_err(chip->dev, "invalid value for progid register\n"); + return -ENODEV; + } else if (value < 10) { + prog_idl = value + '0'; + } else { + prog_idl = (value - 10) + 'A'; + // prog_idh skips 'O', per page 97 of the datasheet + if (prog_idl >= 'O') + prog_idl += 1; + } + + dev_info(chip->dev, "%s Regulator found (Rev %c%d ProgID %c%c EMREV %x).\n", + name, full_layer_rev, metal_layer_rev, prog_idh, prog_idl, emrev); + + return 0; +} + +static int pf530x_i2c_probe(struct i2c_client *client) +{ + struct regulator_config config = { NULL, }; + struct pf530x_chip *chip; + int ret; + struct regulator_dev *rdev; + struct regulator_init_data *init_data; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + + chip->regmap = devm_regmap_init_i2c(client, &pf530x_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err(&client->dev, + "regmap allocation failed with err %d\n", ret); + return ret; + } + + ret = pf530x_identify(chip); + if (ret) + return ret; + + init_data = of_get_regulator_init_data(chip->dev, chip->dev->of_node, &pf530x_reg_desc); + if (!init_data) + return -ENODATA; + + config.dev = chip->dev; + config.of_node = chip->dev->of_node; + config.regmap = chip->regmap; + config.init_data = init_data; + + // the config parameter gets copied, it's ok to pass a pointer on the stack here + rdev = devm_regulator_register(&client->dev, &pf530x_reg_desc, &config); + if (IS_ERR(rdev)) { + dev_err(&client->dev, "failed to register %s regulator\n", pf530x_reg_desc.name); + return PTR_ERR(rdev); + } + + return 0; +} + +static const struct of_device_id pf530x_dt_ids[] = { + { .compatible = "nxp,pf5300",}, + { } +}; +MODULE_DEVICE_TABLE(of, pf530x_dt_ids); + +static const struct i2c_device_id pf530x_i2c_id[] = { + { "pf5300", 0 }, + { "pf5301", 0 }, + { "pf5302", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, pf530x_i2c_id); + +static struct i2c_driver pf530x_regulator_driver = { + .id_table = pf530x_i2c_id, + .driver = { + .name = "pf530x", + .of_match_table = pf530x_dt_ids, + }, + .probe = pf530x_i2c_probe, +}; +module_i2c_driver(pf530x_regulator_driver); + +MODULE_AUTHOR("Woodrow Douglass "); +MODULE_DESCRIPTION("Regulator Driver for NXP's PF5300/PF5301/PF5302 PMIC"); +MODULE_LICENSE("GPL"); From 5bad16482c2a7e788c042d98f3e97d3b2bbc8cc5 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 5 Sep 2025 21:16:30 +0200 Subject: [PATCH 21/28] regulator: dt-bindings: rpi-panel: Split 7" Raspberry Pi 720x1280 v2 binding The 5" and 7" Raspberry Pi 720x1280 Display 2 MCU is a bit more complex than the original Display 1 ATTINY88 and the binding is also a bit more demanding. Split the binding into separate file and fill in required gpio-controller, #gpio-cells and #pwm-cells which must be present for the V2 MCU. Include mention of the 5" panel in the description of Display 2, as the 5" panel uses the same MCU. Fixes: 6d09c6e474bd ("regulator: dt-bindings: rpi-panel: Add regulator for 7" Raspberry Pi 720x1280") Signed-off-by: Marek Vasut Acked-by: Conor Dooley Message-ID: <20250905191637.147141-1-marek.vasut+renesas@mailbox.org> Signed-off-by: Mark Brown --- ...,7inch-touchscreen-panel-regulator-v2.yaml | 61 +++++++++++++++++++ ...ypi,7inch-touchscreen-panel-regulator.yaml | 7 +-- 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator-v2.yaml diff --git a/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator-v2.yaml b/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator-v2.yaml new file mode 100644 index 000000000000..37b9ed371b67 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator-v2.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/raspberrypi,7inch-touchscreen-panel-regulator-v2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: RaspberryPi 5" and 7" display V2 MCU-based regulator/backlight controller + +maintainers: + - Marek Vasut + +description: | + The RaspberryPi 5" and 7" display 2 has an MCU-based regulator, PWM + backlight and GPIO controller on the PCB, which is used to turn the + display unit on/off and control the backlight. + +allOf: + - $ref: regulator.yaml# + +properties: + compatible: + const: raspberrypi,touchscreen-panel-regulator-v2 + + reg: + maxItems: 1 + + gpio-controller: true + "#gpio-cells": + const: 2 + description: + The first cell is the pin number, and the second cell is used to + specify the gpio polarity (GPIO_ACTIVE_HIGH or GPIO_ACTIVE_LOW). + + "#pwm-cells": + const: 3 + description: See ../../pwm/pwm.yaml for description of the cell formats. + +additionalProperties: false + +required: + - compatible + - reg + - gpio-controller + - "#gpio-cells" + - "#pwm-cells" + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + regulator@45 { + compatible = "raspberrypi,touchscreen-panel-regulator-v2"; + reg = <0x45>; + gpio-controller; + #gpio-cells = <2>; + #pwm-cells = <3>; + }; + }; + +... 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 18944d39d08f..41678400e63f 100644 --- a/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/raspberrypi,7inch-touchscreen-panel-regulator.yaml @@ -12,17 +12,14 @@ maintainers: description: | The RaspberryPi 7" display has an ATTINY88-based regulator/backlight controller on the PCB, which is used to turn the display unit on/off - and control the backlight. The V2 supports 5" and 7" panels and also - offers PWM backlight control. + and control the backlight. allOf: - $ref: regulator.yaml# properties: compatible: - enum: - - raspberrypi,7inch-touchscreen-panel-regulator - - raspberrypi,touchscreen-panel-regulator-v2 + const: raspberrypi,7inch-touchscreen-panel-regulator reg: maxItems: 1 From b87ecbc54f22382ace1cf41645e8652a4ce44d52 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Wed, 10 Sep 2025 14:54:31 -0300 Subject: [PATCH 22/28] rust: regulator: remove Regulator After some experimenting and further discussion, it is starting to look like Regulator might be a footgun. It turns out that one can get the same behavior by correctly using just Regulator and Regulator, so there is no need to directly expose the manual refcounting ability of Regulator to clients. Remove it while we do not have any other users. Suggested-by: Danilo Krummrich Reviewed-by: Alice Ryhl Reviewed-by: Danilo Krummrich Reviewed-by: Alexandre Courbot Signed-off-by: Daniel Almeida Link: https://patch.msgid.link/20250910-regulator-remove-dynamic-v3-1-07af4dfa97cc@collabora.com Signed-off-by: Mark Brown --- rust/kernel/regulator.rs | 88 +--------------------------------------- 1 file changed, 1 insertion(+), 87 deletions(-) diff --git a/rust/kernel/regulator.rs b/rust/kernel/regulator.rs index 34bb24ec8d4d..5ea2307f02df 100644 --- a/rust/kernel/regulator.rs +++ b/rust/kernel/regulator.rs @@ -30,7 +30,6 @@ mod private { impl Sealed for super::Enabled {} impl Sealed for super::Disabled {} - impl Sealed for super::Dynamic {} } /// A trait representing the different states a [`Regulator`] can be in. @@ -50,13 +49,6 @@ pub struct Enabled; /// own an `enable` reference count, but the regulator may still be on. pub struct Disabled; -/// A state that models the C API. The [`Regulator`] can be either enabled or -/// disabled, and the user is in control of the reference count. This is also -/// the default state. -/// -/// Use [`Regulator::is_enabled`] to check the regulator's current state. -pub struct Dynamic; - impl RegulatorState for Enabled { const DISABLE_ON_DROP: bool = true; } @@ -65,14 +57,9 @@ impl RegulatorState for Disabled { const DISABLE_ON_DROP: bool = false; } -impl RegulatorState for Dynamic { - const DISABLE_ON_DROP: bool = false; -} - /// A trait that abstracts the ability to check if a [`Regulator`] is enabled. pub trait IsEnabled: RegulatorState {} impl IsEnabled for Disabled {} -impl IsEnabled for Dynamic {} /// An error that can occur when trying to convert a [`Regulator`] between states. pub struct Error { @@ -183,64 +170,13 @@ pub struct Error { /// } /// ``` /// -/// ## Using [`Regulator`] -/// -/// This example mimics the behavior of the C API, where the user is in -/// control of the enabled reference count. This is useful for drivers that -/// might call enable and disable to manage the `enable` reference count at -/// runtime, perhaps as a result of `open()` and `close()` calls or whatever -/// other driver-specific or subsystem-specific hooks. -/// -/// ``` -/// # use kernel::prelude::*; -/// # use kernel::c_str; -/// # use kernel::device::Device; -/// # use kernel::regulator::{Regulator, Dynamic}; -/// struct PrivateData { -/// regulator: Regulator, -/// } -/// -/// // A fictictious probe function that obtains a regulator and sets it up. -/// fn probe(dev: &Device) -> Result { -/// // Obtain a reference to a (fictitious) regulator. -/// let regulator = Regulator::::get(dev, c_str!("vcc"))?; -/// -/// Ok(PrivateData { regulator }) -/// } -/// -/// // A fictictious function that indicates that the device is going to be used. -/// fn open(dev: &Device, data: &PrivateData) -> Result { -/// // Increase the `enabled` reference count. -/// data.regulator.enable()?; -/// -/// Ok(()) -/// } -/// -/// fn close(dev: &Device, data: &PrivateData) -> Result { -/// // Decrease the `enabled` reference count. -/// data.regulator.disable()?; -/// -/// Ok(()) -/// } -/// -/// fn remove(dev: &Device, data: PrivateData) -> Result { -/// // `PrivateData` is dropped here, which will drop the -/// // `Regulator` in turn. -/// // -/// // The reference that was obtained by `regulator_get()` will be -/// // released, but it is up to the user to make sure that the number of calls -/// // to `enable()` and `disabled()` are balanced before this point. -/// Ok(()) -/// } -/// ``` -/// /// # Invariants /// /// - `inner` is a non-null wrapper over a pointer to a `struct /// regulator` obtained from [`regulator_get()`]. /// /// [`regulator_get()`]: https://docs.kernel.org/driver-api/regulator.html#c.regulator_get -pub struct Regulator +pub struct Regulator where State: RegulatorState, { @@ -351,28 +287,6 @@ impl Regulator { } } -impl Regulator { - /// Obtains a [`Regulator`] instance from the system. The current state of - /// the regulator is unknown and it is up to the user to manage the enabled - /// reference count. - /// - /// This closely mimics the behavior of the C API and can be used to - /// dynamically manage the enabled reference count at runtime. - pub fn get(dev: &Device, name: &CStr) -> Result { - Regulator::get_internal(dev, name) - } - - /// Increases the `enabled` reference count. - pub fn enable(&self) -> Result { - self.enable_internal() - } - - /// Decreases the `enabled` reference count. - pub fn disable(&self) -> Result { - self.disable_internal() - } -} - impl Regulator { /// Checks if the regulator is enabled. pub fn is_enabled(&self) -> bool { From 2e0fd4583d0efcdc260e61a22666c8368f505353 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Wed, 10 Sep 2025 14:54:32 -0300 Subject: [PATCH 23/28] rust: regulator: add devm_enable and devm_enable_optional A lot of drivers only care about enabling the regulator for as long as the underlying Device is bound. This can be easily observed due to the extensive use of `devm_regulator_get_enable` and `devm_regulator_get_enable_optional` throughout the kernel. Therefore, make this helper available in Rust. Also add an example noting how it should be the default API unless the driver needs more fine-grained control over the regulator. Suggested-by: Danilo Krummrich Reviewed-by: Boqun Feng Reviewed-by: Danilo Krummrich Reviewed-by: Alexandre Courbot Signed-off-by: Daniel Almeida Link: https://patch.msgid.link/20250910-regulator-remove-dynamic-v3-2-07af4dfa97cc@collabora.com Signed-off-by: Mark Brown --- rust/helpers/regulator.c | 10 +++++++ rust/kernel/regulator.rs | 60 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/rust/helpers/regulator.c b/rust/helpers/regulator.c index cd8b7ba648ee..11bc332443bd 100644 --- a/rust/helpers/regulator.c +++ b/rust/helpers/regulator.c @@ -40,4 +40,14 @@ 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) +{ + return devm_regulator_get_enable(dev, id); +} + +int rust_helper_devm_regulator_get_enable_optional(struct device *dev, const char *id) +{ + return devm_regulator_get_enable_optional(dev, id); +} + #endif diff --git a/rust/kernel/regulator.rs b/rust/kernel/regulator.rs index 5ea2307f02df..b55a201e5029 100644 --- a/rust/kernel/regulator.rs +++ b/rust/kernel/regulator.rs @@ -18,7 +18,7 @@ use crate::{ bindings, - device::Device, + device::{Bound, Device}, error::{from_err_ptr, to_result, Result}, prelude::*, }; @@ -69,6 +69,41 @@ pub struct Error { /// The regulator that caused the error, so that the operation may be retried. pub regulator: Regulator, } +/// Obtains and enables a [`devres`]-managed regulator for a device. +/// +/// This calls [`regulator_disable()`] and [`regulator_put()`] automatically on +/// driver detach. +/// +/// This API is identical to `devm_regulator_get_enable()`, and should be +/// preferred over the [`Regulator`] API if the caller only +/// cares about the regulator being enabled. +/// +/// [`devres`]: https://docs.kernel.org/driver-api/driver-model/devres.html +/// [`regulator_disable()`]: https://docs.kernel.org/driver-api/regulator.html#c.regulator_disable +/// [`regulator_put()`]: https://docs.kernel.org/driver-api/regulator.html#c.regulator_put +pub fn devm_enable(dev: &Device, name: &CStr) -> Result { + // SAFETY: `dev` is a valid and bound device, while `name` is a valid C + // string. + to_result(unsafe { bindings::devm_regulator_get_enable(dev.as_raw(), name.as_ptr()) }) +} + +/// Same as [`devm_enable`], but calls `devm_regulator_get_enable_optional` +/// instead. +/// +/// This obtains and enables a [`devres`]-managed regulator for a device, but +/// does not print a message nor provides a dummy if the regulator is not found. +/// +/// This calls [`regulator_disable()`] and [`regulator_put()`] automatically on +/// driver detach. +/// +/// [`devres`]: https://docs.kernel.org/driver-api/driver-model/devres.html +/// [`regulator_disable()`]: https://docs.kernel.org/driver-api/regulator.html#c.regulator_disable +/// [`regulator_put()`]: https://docs.kernel.org/driver-api/regulator.html#c.regulator_put +pub fn devm_enable_optional(dev: &Device, name: &CStr) -> Result { + // SAFETY: `dev` is a valid and bound device, while `name` is a valid C + // string. + to_result(unsafe { bindings::devm_regulator_get_enable_optional(dev.as_raw(), name.as_ptr()) }) +} /// A `struct regulator` abstraction. /// @@ -146,6 +181,29 @@ pub struct Error { /// } /// ``` /// +/// If a driver only cares about the regulator being on for as long it is bound +/// to a device, then it should use [`devm_enable`] or [`devm_enable_optional`]. +/// This should be the default use-case unless more fine-grained control over +/// the regulator's state is required. +/// +/// [`devm_enable`]: crate::regulator::devm_enable +/// [`devm_optional`]: crate::regulator::devm_enable_optional +/// +/// ``` +/// # 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"))?; +/// +/// // The regulator will be disabled and put when `dev` is unbound. +/// Ok(()) +/// } +/// ``` +/// /// ## Disabling a regulator /// /// ``` From 0f101028407605c7b7cea53946ee4d25f82cfb6c Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 14 Sep 2025 16:02:29 +0300 Subject: [PATCH 24/28] dt-bindings: regulator: document max77838 pmic The max77838 is a pmic, containing a BUCK regulator and 4 LDOs. It's primarily used in the Samsung Galaxy S7 lineup and is accessed over I2C. Document it. Signed-off-by: Ivaylo Ivanov Acked-by: Conor Dooley Reviewed-by: Igor Belwon Link: https://patch.msgid.link/20250914130230.2622030-2-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Mark Brown --- .../bindings/regulator/maxim,max77838.yaml | 68 +++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 74 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/maxim,max77838.yaml diff --git a/Documentation/devicetree/bindings/regulator/maxim,max77838.yaml b/Documentation/devicetree/bindings/regulator/maxim,max77838.yaml new file mode 100644 index 000000000000..bed36af5493d --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/maxim,max77838.yaml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/maxim,max77838.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim Integrated MAX77838 PMIC + +maintainers: + - Ivaylo Ivanov + +properties: + $nodename: + pattern: "pmic@[0-9a-f]{1,2}" + compatible: + enum: + - maxim,max77838 + + reg: + maxItems: 1 + + regulators: + type: object + $ref: regulator.yaml# + description: | + list of regulators provided by this controller, must be named + after their hardware counterparts ldo[1-4] and buck + + properties: + buck: + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + + patternProperties: + "^ldo([1-4])$": + type: object + $ref: regulator.yaml# + unevaluatedProperties: false + + additionalProperties: false + +required: + - compatible + - reg + - regulators + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pmic@60 { + compatible = "maxim,max77838"; + reg = <0x60>; + + regulators { + ldo2 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index f6206963efbf..7554c7f2570a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15069,6 +15069,12 @@ F: Documentation/devicetree/bindings/*/*max77802.yaml F: drivers/regulator/max77802-regulator.c F: include/dt-bindings/*/*max77802.h +MAXIM MAX77838 PMIC REGULATOR DEVICE DRIVER +M: Ivaylo Ivanov +L: linux-kernel@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/regulator/maxim,max77838.yaml + MAXIM MAX77976 BATTERY CHARGER M: Luca Ceresoli S: Supported From 6a1f303cba45fa3b612d5a2898b1b1b045eb74e3 Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 14 Sep 2025 16:02:30 +0300 Subject: [PATCH 25/28] regulator: max77838: add max77838 regulator driver The max77838 PMIC contains a BUCK regulator and 4 LDOs. It's primarily used in the Samsung Galaxy S7 lineup and is accessed over I2C. Signed-off-by: Ivaylo Ivanov Link: https://patch.msgid.link/20250914130230.2622030-3-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Mark Brown --- MAINTAINERS | 1 + drivers/regulator/Kconfig | 8 + drivers/regulator/Makefile | 1 + drivers/regulator/max77838-regulator.c | 221 +++++++++++++++++++++++++ 4 files changed, 231 insertions(+) create mode 100644 drivers/regulator/max77838-regulator.c diff --git a/MAINTAINERS b/MAINTAINERS index 7554c7f2570a..fcb5b2c115bd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15074,6 +15074,7 @@ M: Ivaylo Ivanov L: linux-kernel@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/regulator/maxim,max77838.yaml +F: drivers/regulator/max77838-regulator.c MAXIM MAX77976 BATTERY CHARGER M: Luca Ceresoli diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index eaa6df1c9f80..2cf119e59e4b 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -777,6 +777,14 @@ config REGULATOR_MAX77826 It includes support for control of output voltage. This regulator is found on the Samsung Galaxy S5 (klte) smartphone. +config REGULATOR_MAX77838 + tristate "Maxim 77838 regulator" + depends on REGMAP_I2C + help + This driver controls a Maxim 77838 regulator via I2C bus. + The regulator include 4 LDOs and a BUCK regulator. It's + present on the Samsung Galaxy S7 lineup of smartphones. + config REGULATOR_MC13XXX_CORE tristate diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index be98b29d6675..0622e9de6283 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_REGULATOR_MAX77686) += max77686-regulator.o obj-$(CONFIG_REGULATOR_MAX77693) += max77693-regulator.o obj-$(CONFIG_REGULATOR_MAX77802) += max77802-regulator.o obj-$(CONFIG_REGULATOR_MAX77826) += max77826-regulator.o +obj-$(CONFIG_REGULATOR_MAX77838) += max77838-regulator.o obj-$(CONFIG_REGULATOR_MAX77857) += max77857-regulator.o obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o diff --git a/drivers/regulator/max77838-regulator.c b/drivers/regulator/max77838-regulator.c new file mode 100644 index 000000000000..9faddbfd25fd --- /dev/null +++ b/drivers/regulator/max77838-regulator.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// regulator driver for Maxim MAX77838 +// +// based on max77826-regulator.c +// +// Copyright (c) 2025, Ivaylo Ivanov + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum max77838_registers { + MAX77838_REG_DEVICE_ID = 0x00, + MAX77838_REG_TOPSYS_STAT, + MAX77838_REG_STAT, + MAX77838_REG_EN, + MAX77838_REG_GPIO_PD_CTRL, + MAX77838_REG_UVLO_CFG1, + /* 0x06 - 0x0B: reserved */ + MAX77838_REG_I2C_CFG = 0x0C, + /* 0x0D - 0x0F: reserved */ + MAX77838_REG_LDO1_CFG = 0x10, + MAX77838_REG_LDO2_CFG, + MAX77838_REG_LDO3_CFG, + MAX77838_REG_LDO4_CFG, + /* 0x14 - 0x1F: reserved */ + MAX77838_REG_BUCK_CFG1 = 0x20, + MAX77838_REG_BUCK_VOUT, +}; + +enum max77838_regulators { + MAX77838_LDO1 = 0, + MAX77838_LDO2, + MAX77838_LDO3, + MAX77838_LDO4, + MAX77838_BUCK, + MAX77838_MAX_REGULATORS, +}; + +#define MAX77838_MASK_LDO 0x7f +#define MAX77838_MASK_BUCK 0xff + +#define MAX77838_LDO1_EN BIT(0) +#define MAX77838_LDO2_EN BIT(1) +#define MAX77838_LDO3_EN BIT(2) +#define MAX77838_LDO4_EN BIT(3) +#define MAX77838_BUCK_EN BIT(4) + +#define MAX77838_BUCK_AD BIT(3) +#define MAX77838_LDO_AD BIT(7) + +#define MAX77838_LDO_VOLT_MIN 600000 +#define MAX77838_LDO_VOLT_MAX 3775000 +#define MAX77838_LDO_VOLT_STEP 25000 + +#define MAX77838_BUCK_VOLT_MIN 500000 +#define MAX77838_BUCK_VOLT_MAX 2093750 +#define MAX77838_BUCK_VOLT_STEP 6250 + +#define MAX77838_VOLT_RANGE(_type) \ + ((MAX77838_ ## _type ## _VOLT_MAX - \ + MAX77838_ ## _type ## _VOLT_MIN) / \ + MAX77838_ ## _type ## _VOLT_STEP + 1) + +#define MAX77838_LDO(_id) \ + [MAX77838_LDO ## _id] = { \ + .id = MAX77838_LDO ## _id, \ + .name = "ldo"#_id, \ + .of_match = of_match_ptr("ldo"#_id), \ + .regulators_node = "regulators", \ + .ops = &max77838_regulator_ops, \ + .min_uV = MAX77838_LDO_VOLT_MIN, \ + .uV_step = MAX77838_LDO_VOLT_STEP, \ + .n_voltages = MAX77838_VOLT_RANGE(LDO), \ + .enable_reg = MAX77838_REG_EN, \ + .enable_mask = MAX77838_LDO ## _id ## _EN, \ + .vsel_reg = MAX77838_REG_LDO ## _id ## _CFG, \ + .vsel_mask = MAX77838_MASK_LDO, \ + .active_discharge_off = 0, \ + .active_discharge_on = MAX77838_LDO_AD, \ + .active_discharge_mask = MAX77838_LDO_AD, \ + .active_discharge_reg = MAX77838_REG_LDO ## _id ## _CFG, \ + .owner = THIS_MODULE, \ + } + +#define MAX77838_BUCK_DESC \ + [MAX77838_BUCK] = { \ + .id = MAX77838_BUCK, \ + .name = "buck", \ + .of_match = of_match_ptr("buck"), \ + .regulators_node = "regulators", \ + .ops = &max77838_regulator_ops, \ + .min_uV = MAX77838_BUCK_VOLT_MIN, \ + .uV_step = MAX77838_BUCK_VOLT_STEP, \ + .n_voltages = MAX77838_VOLT_RANGE(BUCK), \ + .enable_reg = MAX77838_REG_EN, \ + .enable_mask = MAX77838_BUCK_EN, \ + .vsel_reg = MAX77838_REG_BUCK_VOUT, \ + .vsel_mask = MAX77838_MASK_BUCK, \ + .active_discharge_off = 0, \ + .active_discharge_on = MAX77838_BUCK_AD, \ + .active_discharge_mask = MAX77838_BUCK_AD, \ + .active_discharge_reg = MAX77838_REG_BUCK_CFG1, \ + .owner = THIS_MODULE, \ + } + +struct max77838_regulator_info { + struct regmap *regmap; +}; + +static const struct regmap_config max77838_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77838_REG_BUCK_VOUT, +}; + +static const struct regulator_ops max77838_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .set_active_discharge = regulator_set_active_discharge_regmap, +}; + +static const struct regulator_desc max77838_regulators_desc[] = { + MAX77838_LDO(1), + MAX77838_LDO(2), + MAX77838_LDO(3), + MAX77838_LDO(4), + MAX77838_BUCK_DESC, +}; + +static int max77838_read_device_id(struct regmap *regmap, struct device *dev) +{ + unsigned int device_id; + int ret; + + ret = regmap_read(regmap, MAX77838_REG_DEVICE_ID, &device_id); + if (!ret) + dev_dbg(dev, "DEVICE_ID: 0x%x\n", device_id); + + return ret; +} + +static int max77838_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max77838_regulator_info *info; + struct regulator_config config = {}; + struct regulator_dev *rdev; + struct regmap *regmap; + int i; + + info = devm_kzalloc(dev, sizeof(struct max77838_regulator_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &max77838_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to allocate regmap!\n"); + return PTR_ERR(regmap); + } + + info->regmap = regmap; + i2c_set_clientdata(client, info); + + config.dev = dev; + config.regmap = regmap; + config.driver_data = info; + + for (i = 0; i < MAX77838_MAX_REGULATORS; i++) { + rdev = devm_regulator_register(dev, + &max77838_regulators_desc[i], + &config); + if (IS_ERR(rdev)) { + dev_err(dev, "Failed to register regulator!\n"); + return PTR_ERR(rdev); + } + } + + return max77838_read_device_id(regmap, dev); +} + +static const struct of_device_id __maybe_unused max77838_of_match[] = { + { .compatible = "maxim,max77838" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, max77838_of_match); + +static const struct i2c_device_id max77838_id[] = { + { "max77838-regulator" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, max77838_id); + +static struct i2c_driver max77838_regulator_driver = { + .driver = { + .name = "max77838", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = of_match_ptr(max77838_of_match), + }, + .probe = max77838_i2c_probe, + .id_table = max77838_id, +}; +module_i2c_driver(max77838_regulator_driver); + +MODULE_AUTHOR("Ivaylo Ivanov "); +MODULE_DESCRIPTION("MAX77838 PMIC regulator driver"); +MODULE_LICENSE("GPL"); From 8b84d712ad849172f6bbcad57534b284d942b0b5 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 25 Aug 2025 12:20:52 -0500 Subject: [PATCH 26/28] regulator: spacemit: support SpacemiT P1 regulators Add support for the regulators found in the SpacemiT P1 PMIC. This PMIC provides 6 buck converters and 12 LDO regulators. The PMIC is implemented as a multi-function device. These regulators are probed based on this driver being named in a MFD cell in the simple MFD I2C driver. Signed-off-by: Alex Elder Link: https://patch.msgid.link/20250825172057.163883-4-elder@riscstar.com Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 13 +++ drivers/regulator/Makefile | 1 + drivers/regulator/spacemit-p1.c | 157 ++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 drivers/regulator/spacemit-p1.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 929ea98e1817..5e451be51392 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1443,6 +1443,19 @@ config REGULATOR_SLG51000 The SLG51000 is seven compact and customizable low dropout regulators. +config REGULATOR_SPACEMIT_P1 + tristate "SpacemiT P1 regulators" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on I2C + select MFD_SPACEMIT_P1 + default ARCH_SPACEMIT + help + Enable support for regulators implemented by the SpacemiT P1 + power controller. The P1 implements 6 high-efficiency buck + converters and 12 programmable LDO regulators. To compile this + driver as a module, choose M here. The module will be called + "spacemit-pmic". + config REGULATOR_STM32_BOOSTER tristate "STMicroelectronics STM32 BOOSTER" depends on ARCH_STM32 || COMPILE_TEST diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 7bac19847781..76b02d12b758 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -167,6 +167,7 @@ obj-$(CONFIG_REGULATOR_S5M8767) += s5m8767.o obj-$(CONFIG_REGULATOR_SC2731) += sc2731-regulator.o obj-$(CONFIG_REGULATOR_SKY81452) += sky81452-regulator.o obj-$(CONFIG_REGULATOR_SLG51000) += slg51000-regulator.o +obj-$(CONFIG_REGULATOR_SPACEMIT_P1) += spacemit-p1.o obj-$(CONFIG_REGULATOR_STM32_BOOSTER) += stm32-booster.o obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o diff --git a/drivers/regulator/spacemit-p1.c b/drivers/regulator/spacemit-p1.c new file mode 100644 index 000000000000..d437e6738ea1 --- /dev/null +++ b/drivers/regulator/spacemit-p1.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for regulators found in the SpacemiT P1 PMIC + * + * Copyright (C) 2025 by RISCstar Solutions Corporation. All rights reserved. + * Derived from code from SpacemiT. + * Copyright (c) 2023, SPACEMIT Co., Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MOD_NAME "spacemit-p1-regulator" + +enum p1_regulator_id { + P1_BUCK1, + P1_BUCK2, + P1_BUCK3, + P1_BUCK4, + P1_BUCK5, + P1_BUCK6, + + P1_ALDO1, + P1_ALDO2, + P1_ALDO3, + P1_ALDO4, + + P1_DLDO1, + P1_DLDO2, + P1_DLDO3, + P1_DLDO4, + P1_DLDO5, + P1_DLDO6, + P1_DLDO7, +}; + +static const struct regulator_ops p1_regulator_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .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, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +/* Selector value 255 can be used to disable the buck converter on sleep */ +static const struct linear_range p1_buck_ranges[] = { + REGULATOR_LINEAR_RANGE(500000, 0, 170, 5000), + REGULATOR_LINEAR_RANGE(1375000, 171, 254, 25000), +}; + +/* Selector value 0 can be used for suspend */ +static const struct linear_range p1_ldo_ranges[] = { + REGULATOR_LINEAR_RANGE(500000, 11, 127, 25000), +}; + +/* These define the voltage selector field for buck and LDO regulators */ +#define BUCK_MASK GENMASK(7, 0) +#define LDO_MASK GENMASK(6, 0) + +#define P1_ID(_TYPE, _n) P1_ ## _TYPE ## _n +#define P1_ENABLE_REG(_off, _n) ((_off) + 3 * ((_n) - 1)) + +#define P1_REG_DESC(_TYPE, _type, _n, _s, _off, _mask, _nv, _ranges) \ + { \ + .name = #_type #_n, \ + .supply_name = _s, \ + .of_match = of_match_ptr(#_type #_n), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = P1_ID(_TYPE, _n), \ + .n_voltages = _nv, \ + .ops = &p1_regulator_ops, \ + .owner = THIS_MODULE, \ + .linear_ranges = _ranges, \ + .n_linear_ranges = ARRAY_SIZE(_ranges), \ + .vsel_reg = P1_ENABLE_REG(_off, _n) + 1, \ + .vsel_mask = _mask, \ + .enable_reg = P1_ENABLE_REG(_off, _n), \ + .enable_mask = BIT(0), \ + } + +#define P1_BUCK_DESC(_n) \ + P1_REG_DESC(BUCK, buck, _n, "vcc", 0x47, BUCK_MASK, 254, p1_buck_ranges) + +#define P1_ALDO_DESC(_n) \ + P1_REG_DESC(ALDO, aldo, _n, "vcc", 0x5b, LDO_MASK, 117, p1_ldo_ranges) + +#define P1_DLDO_DESC(_n) \ + P1_REG_DESC(DLDO, dldo, _n, "buck5", 0x67, LDO_MASK, 117, p1_ldo_ranges) + +static const struct regulator_desc p1_regulator_desc[] = { + P1_BUCK_DESC(1), + P1_BUCK_DESC(2), + P1_BUCK_DESC(3), + P1_BUCK_DESC(4), + P1_BUCK_DESC(5), + P1_BUCK_DESC(6), + + P1_ALDO_DESC(1), + P1_ALDO_DESC(2), + P1_ALDO_DESC(3), + P1_ALDO_DESC(4), + + P1_DLDO_DESC(1), + P1_DLDO_DESC(2), + P1_DLDO_DESC(3), + P1_DLDO_DESC(4), + P1_DLDO_DESC(5), + P1_DLDO_DESC(6), + P1_DLDO_DESC(7), +}; + +static int p1_regulator_probe(struct platform_device *pdev) +{ + struct regulator_config config = { }; + struct device *dev = &pdev->dev; + u32 i; + + /* + * The parent device (PMIC) owns the regmap. Since we don't + * provide one in the config structure, that one will be used. + */ + config.dev = dev->parent; + + for (i = 0; i < ARRAY_SIZE(p1_regulator_desc); i++) { + const struct regulator_desc *desc = &p1_regulator_desc[i]; + struct regulator_dev *rdev; + + rdev = devm_regulator_register(dev, desc, &config); + if (IS_ERR(rdev)) + return dev_err_probe(dev, PTR_ERR(rdev), + "error registering regulator %s\n", + desc->name); + } + + return 0; +} + +static struct platform_driver p1_regulator_driver = { + .probe = p1_regulator_probe, + .driver = { + .name = MOD_NAME, + }, +}; + +module_platform_driver(p1_regulator_driver); + +MODULE_DESCRIPTION("SpacemiT P1 regulator driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" MOD_NAME); From abe962346ef420998d47ba1c2fe591582f69e92e Mon Sep 17 00:00:00 2001 From: Igor Belwon Date: Wed, 17 Sep 2025 16:58:28 +0200 Subject: [PATCH 27/28] regulator: Fix MAX77838 selection The current entry for the MAX77838 regulator is unselectable (as it depended on a non-user-selectable config - REGMAP_I2C). Fix this by making it select the config, and not depending on it. Signed-off-by: Igor Belwon Link: https://patch.msgid.link/20250917-maxreg-kconfig-fix-v1-1-1369f88d6272@mentallysanemainliners.org Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 5e451be51392..e252bb11ae66 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -779,7 +779,8 @@ config REGULATOR_MAX77826 config REGULATOR_MAX77838 tristate "Maxim 77838 regulator" - depends on REGMAP_I2C + depends on I2C + select REGMAP_I2C help This driver controls a Maxim 77838 regulator via I2C bus. The regulator include 4 LDOs and a BUCK regulator. It's From e609438851928381e39b5393f17156955a84122a Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sun, 21 Sep 2025 10:09:17 +0300 Subject: [PATCH 28/28] regulator: dt-bindings: qcom,sdm845-refgen-regulator: document more platforms Document refgen block being present on SDM670, Lemans and QCS8300 platforms. It should be used to provide reference voltage to DSI controller. Signed-off-by: Dmitry Baryshkov Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20250921-refgen-v1-1-9d93e64133ea@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/regulator/qcom,sdm845-refgen-regulator.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/regulator/qcom,sdm845-refgen-regulator.yaml b/Documentation/devicetree/bindings/regulator/qcom,sdm845-refgen-regulator.yaml index f02f97d4fdd2..40f9223d4c27 100644 --- a/Documentation/devicetree/bindings/regulator/qcom,sdm845-refgen-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/qcom,sdm845-refgen-regulator.yaml @@ -23,11 +23,14 @@ properties: - enum: - qcom,sc7180-refgen-regulator - qcom,sc8180x-refgen-regulator + - qcom,sdm670-refgen-regulator - qcom,sm8150-refgen-regulator - const: qcom,sdm845-refgen-regulator - items: - enum: + - qcom,qcs8300-refgen-regulator + - qcom,sa8775p-refgen-regulator - qcom,sc7280-refgen-regulator - qcom,sc8280xp-refgen-regulator - qcom,sm6350-refgen-regulator