diff --git a/Documentation/components/drivers/special/power/index.rst b/Documentation/components/drivers/special/power/index.rst index f08cf486502..7478dfa7302 100644 --- a/Documentation/components/drivers/special/power/index.rst +++ b/Documentation/components/drivers/special/power/index.rst @@ -6,4 +6,5 @@ Power-related Drivers :caption: Supported Drivers pm/index.rst + regulator.rst battery/fakegauge.rst diff --git a/Documentation/components/drivers/special/power/regulator.rst b/Documentation/components/drivers/special/power/regulator.rst new file mode 100644 index 00000000000..d92830e16a4 --- /dev/null +++ b/Documentation/components/drivers/special/power/regulator.rst @@ -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. diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 91876e23b38..bf29ccaf964 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -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 diff --git a/drivers/power/supply/regulator.c b/drivers/power/supply/regulator.c index 07011ee2499..d162712d3d7 100644 --- a/drivers/power/supply/regulator.c +++ b/drivers/power/supply/regulator.c @@ -38,6 +38,13 @@ #include #include +#ifdef CONFIG_REGULATOR_PROCFS +# include +# include +# include +# include +#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: diff --git a/include/nuttx/power/regulator.h b/include/nuttx/power/regulator.h index 84b1b5b64dc..0d1c2dc9f65 100644 --- a/include/nuttx/power/regulator.h +++ b/include/nuttx/power/regulator.h @@ -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 */