Chapter 116: PMICs and regulator framework¶
What: Power Management ICs, single-chip power solutions that replace the half-dozen discrete LDOs and buck converters around an SoC. We cover NXP PCA9450 (the i.MX-recommended PMIC for i.MX8M. Also used on some i.MX6 designs), NXP PF8200 (industrial), Rohm BD71850MWV (compact, integrated for i.MX6/8 cores). On the i.MX6ULL we walk the I²C register map of a typical PMIC, configure voltage rails for SoC + DDR + I/O via the kernel regulator framework, integrate with DVFS (Ch 51B), and measure the power savings from PMIC-coordinated voltage scaling vs always-on discrete LDOs. MCU bridge: Think of a PMIC like a programmable power-tree supervisor: it replaces discrete enables and LDO assumptions with sequenced rails the kernel can model. DDR: external DRAM that must be configured and trained before most software can run from it. PMIC: Power Management IC, a chip that sequences and regulates the board’s voltage rails.
Why: every i.MX6ULL design has 4–6 voltage rails: 3.3 V (I/O), 1.35 V (DDR3), 1.275 V (SoC core), 2.5 V (analog), 1.8 V (some I/O), 5 V (USB). Discrete LDOs work but: (a) bring-up sequencing is tricky (DDR before SoC core, etc.), (b) no central control for sleep, (c) BOM = 6+ chips. A PMIC consolidates these into 1 chip with I²C control, programmable voltages, ramp-rate control, sequencing, and per-rail enable for runtime power management. For products that ship in volume or need real sleep/wake, a PMIC isn’t optional, it’s the only practical path.
Focus: The regulator framework treats every rail as a “supply”. Drivers declare their consumer-supply relationship in the DT. The kernel computes the power-on order from the dependency graph, and waits for each rail to stabilise before letting consumers probe. The PMIC driver translates “set supply VDDARM to 1.275 V” into the right I²C writes. DVFS uses this: when cpufreq drops to 396 MHz, it calls
regulator_set_voltage(VDDARM, 1.150 V)first, saving ~30 % of core power. If you get the boot-sequence wrong, for example the kernel starts the FEC before its PHY’s 1.8 V rail is stable, you will see PHY probe failures that look random. PHY: physical-layer block or chip that converts digital MAC signals to electrical or radio signals.
116.1 Discrete vs PMIC¶
Discrete LDOs/bucks |
PMIC |
|
|---|---|---|
Chip count |
5–10 |
1 |
PCB area |
~600 mm² |
~200 mm² |
BOM cost (qty 1k) |
$4–6 |
$3–5 |
Sequencing |
external supervisor IC |
built-in, programmable |
Per-rail enable |
additional GPIOs |
I²C |
DVFS support |
software-toggling LDO enable (slow, noisy) |
I²C voltage change (fast, glitch-free) |
Sleep modes |
external GPIOs + LDOs |
per-rail sleep states + global low-power mode |
Fault detection |
per-LDO PG pin |
unified PMIC FAULT line |
PMICs are better in every dimension once a board has more than three rails or needs runtime power management. Discrete LDOs remain common only for the cheapest designs, or for battery rails where the PMIC’s ~50 µA quiescent is too high.
116.2 Anatomy of a typical PMIC (PCA9450)¶
The PCA9450 (i.MX8M-recommended. Technically over-specified for i.MX6ULL but illustrative):
4× Buck DC-DC (BUCK1: 1.0–1.65 V @ 3.5 A for VDDARM, BUCK2: same for SOC, BUCK3: 1.0–1.65 V @ 1.5 A, BUCK4: 0.6–2.1875 V)
1× Buck for DDR (BUCK5: 1.1 V / 1.35 V)
6× LDO (LDO1: 1.6/3.3 V @ 100 mA, LDO2: 1.5–3.3 V @ 250 mA, LDO3–5: similar)
I²C control + interrupt
Programmable power-up sequence
Sleep state with selectable rails on/off
Fault detection (over-current, over-temp, under-voltage)
I²C register map (~80 registers):
Reg |
Name |
Purpose |
|---|---|---|
0x01 |
DEV_ID |
chip ID (0x10 for PCA9450A) |
0x14 |
BUCK1OUT |
output voltage code |
0x15 |
BUCK1CTRL |
enable, soft-start, ramp rate |
0x18 |
BUCK2OUT |
|
0x20 |
BUCK5OUT |
DDR voltage |
0x21 |
BUCK5CTRL |
|
0x30 |
LDO1CTRL |
|
0x40 |
PWRON_DELAY |
sequencing |
0x70 |
INT_LATCH |
interrupt status |
Voltage encoding: per-buck typically Vout = 0.6 + N × 0.025 V for codes 0..63, so 1.275 V = code 27. The driver’s translation table maps mV ↔ code.
116.3 The Linux regulator framework¶
drivers/regulator/, central framework. Every rail is a struct regulator_dev. Consumers regulator_get() by supply name in DT, then regulator_enable() / regulator_disable() / regulator_set_voltage().
Hardware Driver Consumer
────── ────── ──────
PCA9450 BUCK1 ────► pca9450-regulator.c ──► CPU clock driver (DVFS)
"vddarm-supply = <&buck1>;"
PCA9450 BUCK5 ────► pca9450-regulator.c ──► DDR3 driver
"ddr-supply = <&buck5>;"
PCA9450 LDO1 ────► pca9450-regulator.c ──► Ethernet PHY
"vio-supply = <&ldo1>;"
DT:
&i2c1 {
pca9450: pmic@25 {
compatible = "nxp,pca9450a";
reg = <0x25>;
interrupt-parent = <&gpio4>;
interrupts = <22 IRQ_TYPE_LEVEL_LOW>;
regulators {
buck1: BUCK1 {
regulator-name = "VDD_ARM";
regulator-min-microvolt = <600000>;
regulator-max-microvolt = <1650000>;
regulator-boot-on;
regulator-always-on;
regulator-ramp-delay = <3125>;
};
buck5: BUCK5 {
regulator-name = "VDD_DRAM";
regulator-min-microvolt = <1100000>;
regulator-max-microvolt = <1350000>;
regulator-boot-on;
regulator-always-on;
};
ldo1: LDO1 {
regulator-name = "VDD_SNVS";
regulator-min-microvolt = <1600000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
/* ... etc ... */
};
};
};
&cpu0 {
vddarm-supply = <&buck1>;
};
&fec1 {
phy-supply = <&ldo1>;
};
After boot:
ls /sys/class/regulator/
# regulator.0 regulator.1 ...
cat /sys/class/regulator/regulator.0/name
# VDD_ARM
cat /sys/class/regulator/regulator.0/microvolts
# 1275000
Or regulator_summary (debugfs):
mount -t debugfs none /sys/kernel/debug
cat /sys/kernel/debug/regulator/regulator_summary
# regulator use open bypass voltage current min max
# regulator-dummy 0 0 0 0mV 0mA 0mV 0mV
# VDD_SNVS 2 2 0 3300mV 0mA 1600mV 3300mV
# VDD_ARM 1 1 0 1275mV 0mA 600mV 1650mV
The regulator summary makes the power tree visible: you can see exactly which consumer keeps each rail enabled.
116.4 Power-up sequencing, the most subtle bring-up trap¶
i.MX6ULL has a required power-up sequence:
VDD_SNVS (always-on RTC domain), must be first
VDD_HIGH_IN (analog supply), within 10 ms of SNVS
VDD_ARM_IN, VDD_SOC_IN, together, within 100 ms
NVCC_DRAM (1.35 V for DDR3), before any DDR access
Per-bank I/O supplies (NVCC_GPIO_*), before any GPIO use
MCU bridge: Think of Linux GPIO like the same pin set/reset block you used on STM32, but accessed through a kernel subsystem that owns numbering, direction, interrupts, and user-space exposure. GPIO: General-Purpose Input/Output, a pin controlled as a digital input, output, or interrupt source.
POR_B released (reset deassert), last
If you violate this, behavior ranges from “doesn’t boot” to “boots but crashes intermittently” to “silicon damage.” The PMIC’s programmable sequencer enforces this in hardware, set PWRON_DELAY registers per rail and the PMIC powers them in the right order at the right intervals.
At runtime, regulator_enable() walks the supply dependency graph and powers parent supplies first. Mark each rail with regulator-boot-on if the kernel inherits an already-on rail. Mark with regulator-always-on if it must never disable.
116.5 DVFS, coordinated voltage and frequency scaling¶
When cpufreq drops the CPU clock (Ch 51B), it should also drop the core voltage. The cpufreq-dt driver does this via the regulator framework:
&cpu0 {
cpu-supply = <&buck1>;
operating-points-v2 = <&cpu_opp_table>;
};
cpu_opp_table: opp-table {
compatible = "operating-points-v2";
opp-396000000 {
opp-hz = /bits/ 64 <396000000>;
opp-microvolt = <1150000>;
opp-supported-hw = <0xc>, <0x7>;
};
opp-528000000 {
opp-hz = /bits/ 64 <528000000>;
opp-microvolt = <1200000>;
};
opp-696000000 {
opp-hz = /bits/ 64 <696000000>;
opp-microvolt = <1275000>;
};
};
The OPP table tells cpufreq: “at 396 MHz, 1.15 V is sufficient.” On transition:
1. cpufreq decides to drop to 396 MHz
2. regulator_set_voltage(buck1, 1150000) - PMIC ramps down via I²C
3. After ramp + settling, write new clock divider
4. CPU now runs at 396 MHz / 1.15 V
Power saving from 1.275 V to 1.150 V:
Static (leakage) power ∝ V. ~10 % reduction.
Dynamic power ∝ V² × f. ~28 % reduction at same frequency, more if f drops too.
Combined with f drop from 696 → 396 MHz: ~60 % total power saving for “idle background” load. This is why DVFS exists.
116.6 Sleep state coordination¶
In suspend-to-RAM:
Most rails disable.
DDR rail stays on (self-refresh).
VDD_SNVS always on (so RTC + wake circuits work).
The PMIC’s SLEEP mode does this in one command from Linux suspend callback:
/* Inside i.MX6ULL suspend driver */
if (pmic->sleep_mode) {
/* PMIC enters its preconfigured sleep state */
pca9450_set_state(pmic, PCA9450_STATE_SLEEP);
/* Now SoC enters WFI; only SNVS and DDR are powered */
}
On wake: PMIC’s wake pin (typically tied to an i.MX EXTRBOOT or PMIC’s WAKE_IN) brings the PMIC back to active state, which restores all rails to their pre-sleep values, in the correct sequence. Linux resumes.
Without a PMIC, each rail needs its own GPIO with its own timing. The suspend driver grows complex. With a PMIC, suspend is one I²C transaction.
116.7 From scratch, minimal PMIC interaction over I²C¶
If you’re prototyping with a discrete-LDO board, but want to demonstrate the principles, here’s how you’d talk to a hypothetical PMIC’s BUCK1 register directly:
/* pmic_test.c */
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define PMIC_ADDR 0x25
#define REG_DEV_ID 0x00
#define REG_BUCK1_VSET 0x14
static int i2c_write(int fd, uint8_t reg, uint8_t val) {
uint8_t buf[2] = { reg, val };
return write(fd, buf, 2) == 2 ? 0 : -1;
}
static int i2c_read(int fd, uint8_t reg, uint8_t *val) {
if (write(fd, ®, 1) != 1) return -1;
return read(fd, val, 1) == 1 ? 0 : -1;
}
int main(void) {
int fd = open("/dev/i2c-0", O_RDWR);
ioctl(fd, I2C_SLAVE, PMIC_ADDR);
uint8_t id;
i2c_read(fd, REG_DEV_ID, &id);
printf("PMIC ID: 0x%02X\n", id);
/* Read current BUCK1 setting */
uint8_t v;
i2c_read(fd, REG_BUCK1_VSET, &v);
printf("BUCK1 code: 0x%02X (Vout = %.3f V)\n", v, 0.6 + v * 0.025);
/* Lower BUCK1 to 1.150 V (code 22 = 0.6 + 22*0.025) */
i2c_write(fd, REG_BUCK1_VSET, 22);
sleep(1);
i2c_read(fd, REG_BUCK1_VSET, &v);
printf("After: BUCK1 code: 0x%02X (Vout = %.3f V)\n", v, 0.6 + v * 0.025);
/* WARNING: directly writing PMIC registers bypasses the regulator framework.
* Don't do this when consumers depend on the rail (you may starve VDDARM).
* Use only for bring-up exploration. */
return 0;
}
This is for understanding. In real Linux code you go through the regulator framework via the consumer driver, never directly poke PMIC registers from an unrelated process.
116.8 Lab¶
Privilege boundary: $ means normal user. # or sudo means root and can change host or target state. After a privileged command, verify the expected device, service, or file appears before continuing. Roll back by undoing the config change or stopping the service you just enabled.
Identify PMIC. If your i.MX6ULL board has a PMIC:
i2cdetect -y 0to find it. Read DEV_ID register. Verify it matches the chip’s datasheet.DT skeleton. If your kernel isn’t using the PMIC: write a DT overlay with the PMIC node + a few rails. Rebuild kernel. Verify
regulator_summarylists them.Power measurement. Insert a shunt resistor between PSU and VDDARM rail. Measure current at 696 MHz idle, then at 396 MHz idle, then with cpufreq’s
userspacegovernor forcing each freq. Compute power.DVFS active. Set the cpufreq governor to
ondemand. Run a CPU-bound benchmark (stress-ng --cpu 1). Watch the regulator voltage change in real-time:watch -n0.1 cat /sys/class/regulator/regulator.0/microvolts
Per-rail enable test. Identify a non-critical rail (e.g., one for an unused peripheral). Manually disable it via sysfs
enableknob. Verify the peripheral indeed goes dead.
sysfs: a kernel-generated filesystem under /sys that exposes devices, drivers, and attributes.
Sleep state. Trigger suspend-to-RAM (
echo mem > /sys/power/state). Measure VDDARM rail, should be 0 V during suspend. Wake. Resume. Verify rail restored.Fault interrupt. PMICs typically have an INT pin. Configure it as a kernel IRQ. Over-current the chip (short a rail momentarily). Verify the kernel sees the fault.
MCU bridge: Think of an IRQ like an EXTI/NVIC interrupt path, except Linux splits the hard interrupt from deferred work and must share lines across drivers. IRQ: interrupt request, the signal path that tells the CPU or interrupt controller that hardware needs service.
OPP tuning. Add a custom OPP (e.g., 528 MHz at 1.1 V, slightly under the safe-spec to see what fails). Run stress tests. Record where the SoC starts to glitch (you may corrupt files. Use a scratch SD).
From-scratch I²C peek. Use
pmic_test.cto read every register. Dump them. Identify which rail is which by toggling each and observing what dies.Cold start sequencing trace. With a scope on each rail’s output, capture the power-on sequence. Verify timing matches the i.MX6ULL datasheet’s required order.
116.9 Pitfalls¶
Direct PMIC register pokes bypass safety. Lowering VDDARM below the OPP minimum mid-execution = SoC crashes immediately. Always use the regulator framework.
OPP table wrong voltage. Setting 396 MHz at 1.0 V (below spec) may “work” most of the time but crashes randomly. Match the SoC datasheet exactly.
Sleep-mode rails not configured. PMIC enters sleep. User-relevant rail (e.g., backlight) stays on, wakes everything via leakage. Configure SLEEP_REG for each rail.
No
regulator-boot-onon rails the kernel finds already on. Kernel assumes off, tries to enable, may sequence wrongly. Mark every pre-up rail.regulator-always-oneverywhere. Defeats DVFS and sleep. Use only for rails that genuinely cannot disable (DDR, RTC, etc.).I²C bus contention. PMIC, EEPROM, RTC, sensors all on i2c0. PMIC ops during heavy traffic delay → DVFS transitions slow → cpufreq stalls. Put PMIC on a dedicated I²C bus if you have one spare.
PMIC requires VDDA before I²C. Some PMICs (PCA9450) won’t ACK I²C until their internal LDOs are up. The kernel I²C probe must retry. The driver handles this. Ad-hoc test scripts may not.
OTP-programmed PMIC defaults. Some PMICs ship with vendor-specific OTP values. If you got a “datasheet default” example, your part may differ. Read all registers at boot, verify against your design.
Ramp rate too fast. Fast voltage steps cause overshoot. PMIC
regulator-ramp-delay(µV/µs) controls slew. Default may be too aggressive for sensitive consumers.WAKE_IN polarity wrong. Active-low vs active-high, wrong polarity = PMIC ignores wake. Spec mismatch.
DDR rail timing. DDR3 needs ≤1 ms from VREF to VDDQ stable. PMIC sequence must enforce this. Otherwise DDR initializes corruptly.
116.10 Going deeper¶
NXP PCA9450 Datasheet + AN12575 application note (the design guide for i.MX8 power).
NXP PF8200 Datasheet: industrial 14-rail PMIC.
Rohm BD71850MWV Datasheet: compact i.MX-focused PMIC.
drivers/regulator/: kernel framework + chip drivers.Documentation/devicetree/bindings/regulator/: DT binding docs.Documentation/power/regulator/: kernel regulator framework guide.i.MX6ULL Reference Manual ch. 11 (Power Supply Strategy): the canonical power-up sequence.
Ch 51B: runtime PM + DVFS chapter. The consumer side.
Ch 75: INA226 + current monitoring. How you measure rail consumption.
Next chapter: Chapter 117: External RTC, the last chapter of Part VII.