drivers/power: Describe the regulators through procfs.
Some checks are pending
Build Documentation / build-html (push) Waiting to run
MemBrowse Memory Report / changes-filter (push) Waiting to run
MemBrowse Memory Report / load-targets (push) Waiting to run
MemBrowse Memory Report / identical (push) Blocked by required conditions
MemBrowse Memory Report / analyze (push) Blocked by required conditions

The regulator framework has no way out to userspace: consumers reach a
rail by name from inside the kernel, which is the right interface for
controlling one, but it leaves a board with regulators offering no way
to see what they are doing, and a newly written regulator driver cannot
be looked at without writing a consumer for it first.

Adds /proc/regulator, behind REGULATOR_PROCFS, listing every registered
regulator: its present voltage, the range it will accept, whether it is
enabled, how many consumers hold and enable it, its supply, and whether
it is always on or expected on at boot.  Lines carry the same key:value
tokens in the same order, so the file is machine parseable.  The last
two are worth reading beside the consumer count, since a rail enabled
with no consumers is expected rather than suspect when either is set.

A part usually measures more than the framework has fields for, so
struct regulator_ops_s gains an optional describe method: it writes
key:value text and the renderer appends it to that rail's line.  This is
how a driver reports what only it knows, an input voltage, an output
current, a temperature or a fault word, without the framework growing a
field per part or the driver growing procfs code of its own.  It is
called with the list mutex held and never from interrupt context, so
reading the part over a bus is allowed.

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.  Both calls can fail, and a failure
reports - rather than an errno formatted as a voltage or a rail that
looks switched on.

Reading the hardware is also why this takes the list mutex directly
rather than calling regulator_list_lock(), which additionally disables
interrupts so that callers in interrupt or idle context are safe.
Asking a regulator on a bus what it is doing means a transfer, and a
transfer waits; a task reading a file can afford to wait and an
interrupt handler cannot.

procfs_register() appends without checking for duplicates, so the entry
is claimed once for the lifetime of the system rather than whenever the
list is empty.  It also needs FS_PROCFS_REGISTER, which the option now
depends on rather than only FS_PROCFS.

Documents the framework, which had no page at all: the consumer
interface and what counted enables mean, what a driver supplies, and the
new entry.

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.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
This commit is contained in:
Justin Hammond 2026-08-04 21:17:41 +08:00 committed by Xiang Xiao
parent e29db6724c
commit 69386aa6c0
5 changed files with 523 additions and 0 deletions

View file

@ -6,4 +6,5 @@ Power-related Drivers
:caption: Supported Drivers
pm/index.rst
regulator.rst
battery/fakegauge.rst

View file

@ -0,0 +1,175 @@
==================
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:
.. code-block:: c
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()`` and ``regulator_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
================ ========================================================
``name`` The name consumers pass to ``regulator_get()``
``min_uv`` Lowest voltage the rail will accept
``max_uv`` Highest voltage the rail will accept
``supply_name`` The regulator feeding this one, if any
``boot_on`` The rail is expected to be on already at start up
``always_on`` The rail must never be switched off
``apply_uv`` Apply 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.
.. code-block:: c
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:
.. code-block:: text
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
================ ========================================================
``uv`` Present voltage in microvolts, or ``-`` if unreadable
``min``, ``max`` The range the regulator will accept
``enabled`` Whether the rail is on, or ``-`` if unreadable
``users`` Consumers currently enabling it
``opens`` Handles currently held on it
``supply`` The regulator feeding this one, or ``-``
``always_on`` Set if the rail must never be switched off
``boot_on`` Set 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:
.. code-block:: text
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.

View file

@ -59,6 +59,25 @@ config REGULATOR
if REGULATOR
config REGULATOR_PROCFS
bool "Describe the regulators through procfs"
default n
depends on FS_PROCFS && FS_PROCFS_REGISTER
---help---
Report every registered regulator through /proc/regulator: its
name, the voltage it is putting out, the range it will accept,
whether it is enabled, how many consumers hold it, its supply
and whether it is always on or enabled at boot.
The voltage is read back from the hardware rather than recalled,
so a rail left somewhere by the boot loader reads as it actually
is rather than as this software last set it.
The entry is read only. A rail is not something to move by
writing to a file: what a voltage may be is knowledge the
consumers hold, and the ordering between them is what the
framework exists to arrange.
config REGULATOR_GPIO
bool "Regulator gpio driver support"
default n

View file

@ -38,6 +38,13 @@
#include <nuttx/power/consumer.h>
#include <nuttx/signal.h>
#ifdef CONFIG_REGULATOR_PROCFS
# include <sys/stat.h>
# include <fcntl.h>
# include <nuttx/fs/fs.h>
# include <nuttx/fs/procfs.h>
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
@ -73,6 +80,291 @@ static rmutex_t g_reg_lock = NXRMUTEX_INITIALIZER;
* Private Functions
****************************************************************************/
#ifdef CONFIG_REGULATOR_PROCFS
/****************************************************************************
* Name: regulator_procfs_open
*
* Description:
* Read only. A rail is not something to move by writing to a file: the
* consumers hold the knowledge of what a voltage may be, and the ordering
* between them is the whole point of the framework. Reporting is what
* this entry is for.
*
* Input Parameters:
* filep - The file structure to attach the open file to
* relpath - The path below /proc being opened
* oflags - Open flags; anything but read only is refused
* mode - Ignored, the entry cannot be created
*
* Returned Value:
* Zero on success, or a negated errno on failure.
*
****************************************************************************/
static int regulator_procfs_open(FAR struct file *filep,
FAR const char *relpath,
int oflags, mode_t mode)
{
FAR struct procfs_file_s *priv;
if ((oflags & O_ACCMODE) != O_RDONLY)
{
return -EACCES;
}
priv = kmm_zalloc(sizeof(struct procfs_file_s));
if (priv == NULL)
{
return -ENOMEM;
}
filep->f_priv = priv;
return OK;
}
/****************************************************************************
* Name: regulator_procfs_close
*
* Description:
* Close /proc/regulator and free what open() allocated.
*
* Input Parameters:
* filep - The open file
*
* Returned Value:
* Zero on success, or a negated errno on failure.
*
****************************************************************************/
static int regulator_procfs_close(FAR struct file *filep)
{
kmm_free(filep->f_priv);
filep->f_priv = NULL;
return OK;
}
/****************************************************************************
* Name: regulator_procfs_read
*
* Description:
* Describe every registered regulator: what it is called, what it is
* putting out, the range it will accept, whether it is on and how many
* consumers are holding it.
*
* The voltage is read from the hardware rather than remembered, so a rail
* moved by something other than this framework, which is the usual state
* of affairs at start up, is reported as it is rather than as this
* software last left it.
*
* That is why this takes the list mutex directly rather than calling
* regulator_list_lock(), which also disables interrupts for the benefit
* of callers that may run in interrupt or idle context. Asking a
* regulator on a bus what it is doing means bus traffic, and bus traffic
* waits; a reader of this file is always a task and can afford to.
*
* Input Parameters:
* filep - The open file, carrying the offset reached so far
* buffer - Where to return the text
* buflen - Size of buffer
*
* Returned Value:
* The number of bytes returned, zero at end of file, or a negated errno
* on failure.
*
****************************************************************************/
static ssize_t regulator_procfs_read(FAR struct file *filep,
FAR char *buffer, size_t buflen)
{
FAR struct regulator_dev_s *rdev;
size_t remaining = buflen;
FAR char *dest = buffer;
off_t pos = filep->f_pos;
char line[192];
char extra[48];
size_t n;
int ret;
ret = nxrmutex_lock(&g_reg_lock);
if (ret < 0)
{
return ret;
}
list_for_every_entry(&g_reg_list, rdev, struct regulator_dev_s, list)
{
FAR const struct regulator_desc_s *desc = rdev->desc;
int enabled;
int uv;
if (remaining == 0)
{
break;
}
n = snprintf(line, sizeof(line), "%-20s",
desc->name != NULL ? desc->name : "-");
/* Both of these reach the hardware and can fail; a failure reports
* - rather than an errno formatted as a voltage, or as a rail that
* is switched on.
*/
uv = _regulator_get_voltage(rdev);
if (uv >= 0)
{
n += snprintf(line + n, sizeof(line) - n, " uv:%d", uv);
}
else
{
n += snprintf(line + n, sizeof(line) - n, " uv:-");
}
n += snprintf(line + n, sizeof(line) - n, " min:%u max:%u",
desc->min_uv, desc->max_uv);
enabled = _regulator_is_enabled(rdev);
if (enabled >= 0)
{
n += snprintf(line + n, sizeof(line) - n, " enabled:%d",
enabled != 0);
}
else
{
n += snprintf(line + n, sizeof(line) - n, " enabled:-");
}
n += snprintf(line + n, sizeof(line) - n,
" users:%" PRIu32 " opens:%" PRIu32
" supply:%s always_on:%u boot_on:%u",
rdev->use_count, rdev->open_count,
desc->supply_name != NULL ? desc->supply_name : "-",
desc->always_on, desc->boot_on);
/* Whatever the driver has that the fields above cannot hold. The
* lock this runs under is the list mutex rather than the framework's
* own, so a driver reading its part over a bus is allowed to wait.
*/
extra[0] = '\0';
if (rdev->ops->describe != NULL &&
rdev->ops->describe(rdev, extra, sizeof(extra)) >= 0)
{
extra[sizeof(extra) - 1] = '\0';
if (extra[0] != '\0')
{
n += snprintf(line + n, sizeof(line) - n, " %s", extra);
}
}
n += snprintf(line + n, sizeof(line) - n, "\n");
/* snprintf() reports the length it wanted, so a line longer than
* the buffer would otherwise carry n past it.
*/
if (n >= sizeof(line))
{
n = sizeof(line) - 1;
line[n - 1] = '\n';
}
n = procfs_memcpy(line, n, dest, remaining, &pos);
dest += n;
remaining -= n;
}
nxrmutex_unlock(&g_reg_lock);
filep->f_pos += dest - buffer;
return dest - buffer;
}
/****************************************************************************
* Name: regulator_procfs_dup
*
* Description:
* Duplicate an open /proc/regulator, copying the position reached
* so that the new file continues where the old one had got to.
*
* Input Parameters:
* oldp - The open file being duplicated
* newp - The file structure to attach the duplicate to
*
* Returned Value:
* Zero on success, or a negated errno on failure.
*
****************************************************************************/
static int regulator_procfs_dup(FAR const struct file *oldp,
FAR struct file *newp)
{
FAR struct procfs_file_s *priv;
priv = kmm_zalloc(sizeof(struct procfs_file_s));
if (priv == NULL)
{
return -ENOMEM;
}
memcpy(priv, oldp->f_priv, sizeof(struct procfs_file_s));
newp->f_priv = priv;
return OK;
}
/****************************************************************************
* Name: regulator_procfs_stat
*
* Description:
* Report /proc/regulator as a read only regular file.
*
* Input Parameters:
* relpath - The path below /proc being queried
* buf - Where to return the status
*
* Returned Value:
* Zero on success, or a negated errno on failure.
*
****************************************************************************/
static int regulator_procfs_stat(FAR const char *relpath,
FAR struct stat *buf)
{
buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
buf->st_size = 0;
buf->st_blksize = 0;
buf->st_blocks = 0;
return OK;
}
static const struct procfs_operations g_regulator_procfs_ops =
{
regulator_procfs_open, /* open */
regulator_procfs_close, /* close */
regulator_procfs_read, /* read */
NULL, /* write */
NULL, /* poll */
regulator_procfs_dup, /* dup */
NULL, /* opendir */
NULL, /* closedir */
NULL, /* readdir */
NULL, /* rewinddir */
regulator_procfs_stat, /* stat */
};
static const struct procfs_entry_s g_regulator_procfs =
{
"regulator", &g_regulator_procfs_ops, PROCFS_FILE_TYPE
};
static bool g_regulator_procfs_added;
#endif /* CONFIG_REGULATOR_PROCFS */
static int _regulator_is_enabled(FAR struct regulator_dev_s *rdev)
{
if (!rdev->ops->is_enabled)
@ -1092,6 +1384,24 @@ bypass:
}
#endif
#ifdef CONFIG_REGULATOR_PROCFS
/* procfs_register() has to run before procfs is mounted, which holds
* here: regulators register during board or architecture start up. The
* first one to arrive publishes the entry for all of them.
*/
/* procfs_register() appends without checking for a duplicate, so the
* entry is claimed once for the lifetime of the system rather than
* whenever the list is empty.
*/
if (!g_regulator_procfs_added)
{
procfs_register(&g_regulator_procfs);
g_regulator_procfs_added = true;
}
#endif
list_add_tail(&g_reg_list, &rdev->list);
out:

View file

@ -93,6 +93,24 @@ struct regulator_ops_s
enum regulator_mode_e mode);
CODE int (*set_suspend_voltage)(FAR struct regulator_dev_s *, int uv);
CODE int (*resume)(FAR struct regulator_dev_s *rdev);
/* Report what this regulator can say about itself that the upper half
* has no field for: what a particular part measures or latches, such as
* its input voltage, output current, temperature or fault status.
*
* Optional. A regulator whose driver omits it is still listed with
* everything the upper half knows: its voltage, its range, whether it
* is enabled and how many consumers hold it.
*
* Write at most len bytes into extra as further key:value fields, in
* the form the renderer uses, and return OK. The renderer owns the
* line and appends this to it, so a driver needs no procfs knowledge
* of its own. Called with the framework's list lock held and not from
* interrupt context, so reading a part over a bus is allowed.
*/
CODE int (*describe)(FAR struct regulator_dev_s *rdev, FAR char *extra,
size_t len);
};
/* This structure describes the regulators capabilities */