Regulator Drivers
A regulator is a power rail whose voltage or on/off state software can control: a PMIC output, a switching converter, or a load switch driven by a GPIO. Drivers rarely want to own one outright, because a rail usually feeds more than one peripheral, so the framework arbitrates between the consumers of a rail rather than letting each drive it directly.
include/nuttx/power/consumer.h declares what a consumer uses;
include/nuttx/power/regulator.h declares what a regulator driver
implements. CONFIG_REGULATOR builds the framework.
Consumer interface
A consumer looks a regulator up by name, holds it while it needs the rail, and puts it back:
FAR struct regulator_s *reg;
reg = regulator_get("vdd-sensor");
if (reg == NULL)
{
return -ENODEV;
}
regulator_set_voltage(reg, 1800000, 1800000); /* microvolts */
regulator_enable(reg);
...
regulator_disable(reg);
regulator_put(reg);
regulator_get()Look up a regulator by the name its driver registered, and return a handle on it. Each handle is one consumer’s claim.
regulator_enable()andregulator_disable()Ask for the rail to be on or off. These are counted: the rail is switched on for the first consumer that enables it and off once the last one has disabled it, so a driver need not know who else is using it.
regulator_set_voltage()Ask for a voltage, as a range in microvolts. The framework intersects the ranges every consumer has asked for, so a rail shared between a part that needs 1.8 V and one that tolerates 1.7 V to 1.9 V settles where both are satisfied.
regulator_get_voltage()Read the rail’s present voltage, in microvolts.
regulator_is_enabled()Report whether the rail is on.
regulator_put()Give the handle back and drop this consumer’s claim.
Because the framework counts consumers, a driver should hold a rail for exactly as long as it needs it, and should not assume that disabling it turns anything off.
Writing a regulator driver
A driver describes its regulator with a struct regulator_desc_s, provides
a struct regulator_ops_s, and calls regulator_register(). The
descriptor carries the rail’s name, the voltage range it will accept, and how
it behaves at start up:
Member
Meaning
nameThe name consumers pass to
regulator_get()
min_uvLowest voltage the rail will accept
max_uvHighest voltage the rail will accept
supply_nameThe regulator feeding this one, if any
boot_onThe rail is expected to be on already at start up
always_onThe rail must never be switched off
apply_uvApply a voltage during initialisation
The remaining members describe the hardware to the generic helpers: the register and mask for voltage selection and for enabling, the step size and count for a linear rail, and the ramp and enable delays the framework waits out after a change.
describe is the one operation that exists only for reporting. A part
often measures more than the framework has fields for, and this is where that
goes: write key:value text and the /proc/regulator renderer appends it
to the rail’s line. It is called with the list mutex held and never from
interrupt context, so reading the part over a bus is allowed.
static int mychip_describe(FAR struct regulator_dev_s *rdev,
FAR char *extra, size_t len)
{
FAR struct mychip_dev_s *priv = rdev->priv;
snprintf(extra, len, "vin:%d iout:%d temp:%d",
priv->vin_uv, priv->iout_ua, priv->temp_mc);
return OK;
}
The operations are all optional; a regulator with no is_enabled is treated
as always on, and one with no get_voltage reports the first entry of its
voltage list. A GPIO controlled load switch needs only enable,
disable and is_enabled.
regulator_unregister() removes a regulator; its consumers must have put
their handles back first.
/proc/regulator
The framework has no character device: consumers reach a rail from inside the
kernel, which is the right interface for controlling one but leaves no way to
see what the rails are doing. CONFIG_REGULATOR_PROCFS adds
/proc/regulator, listing every registered regulator:
vdd-cpux uv:900000 min:800000 max:900000 enabled:1 users:1 opens:1 supply:- always_on:1 boot_on:1
vdd-sensor uv:1800000 min:1700000 max:1900000 enabled:0 users:0 opens:0 supply:vsys always_on:0 boot_on:0
Every line carries the same key:value tokens in the same order, so the
file can be parsed as well as read:
Token
Meaning
uvPresent voltage in microvolts, or
-if unreadable
min,maxThe range the regulator will accept
enabledWhether the rail is on, or
-if unreadable
usersConsumers currently enabling it
opensHandles currently held on it
supplyThe regulator feeding this one, or
-
always_onSet if the rail must never be switched off
boot_onSet if the rail is expected on at start up
A driver implementing describe adds its own key:value fields after
those, so a part that measures its input voltage, output current or
temperature reports them on the same line:
npu_vdd uv:800000 min:600000 max:1100000 enabled:1 users:1 opens:1 supply:- always_on:1 boot_on:0 vin:12043750 iout:3200000 temp:47500 status:0x0000
always_on and boot_on are worth reading beside users: a rail that
is enabled with no consumers is expected rather than suspect when either is
set.
The voltage and the enabled state are read back from the hardware rather than recalled, so a rail the boot loader set and nothing has touched since reads as it actually is. For a regulator on a bus that means a transfer per rail per read, which is why the entry takes the list mutex rather than the framework’s own lock: that one also disables interrupts, and a bus transfer waits.
The entry is read only. What voltage a rail may be is knowledge its consumers hold, and arranging the order between them is what the framework is for, so moving one from a shell would step around the part that matters.
The option depends on FS_PROCFS_REGISTER and is off by default.
Remote regulators
CONFIG_REGULATOR_RPMSG lets a consumer on one core use a regulator owned
by another, over rpmsg. The core that owns the hardware runs
regulator_rpmsg_server_init(); consumers use the ordinary interface either
way.