[PATCH v2 2/2] hwmon: raspberrypi: Add voltage input support
Guenter Roeck
linux at roeck-us.net
Sat May 16 16:20:40 PDT 2026
On 5/16/26 12:15, Shubham Chakraborty wrote:
> Extend the raspberrypi-hwmon driver to expose firmware-provided
> voltage measurements through the hwmon subsystem.
>
> The driver now exports the following voltage inputs:
>
> - in0_input (core)
> - in1_input (sdram_c)
> - in2_input (sdram_i)
> - in3_input (sdram_p)
>
> Voltage values returned by firmware are converted from microvolts
> to millivolts as expected by the hwmon subsystem.
>
> Update the documentation related to it.
>
> The existing undervoltage sticky alarm handling is preserved and
> associated with the first voltage channel.
>
> Tested in -
> - Raspberry Pi 3b+ (Linux raspberrypi 6.12.75+rpt-rpi-v8 #1 SMP PREEMPT
> Debian 1:6.12.75-1+rpt1 (2026-03-11) aarch64 GNU/Linux)
>
> Signed-off-by: Shubham Chakraborty <chakrabortyshubham66 at gmail.com>
> ---
> Documentation/hwmon/raspberrypi-hwmon.rst | 15 ++-
> drivers/hwmon/raspberrypi-hwmon.c | 134 +++++++++++++++++++++-
> 2 files changed, 144 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/hwmon/raspberrypi-hwmon.rst b/Documentation/hwmon/raspberrypi-hwmon.rst
> index 8038ade36490..db315184b861 100644
> --- a/Documentation/hwmon/raspberrypi-hwmon.rst
> +++ b/Documentation/hwmon/raspberrypi-hwmon.rst
> @@ -20,6 +20,17 @@ undervoltage conditions.
> Sysfs entries
> -------------
>
> -======================= ==================
> +======================= ======================================================
> +in0_input Core voltage in millivolts
> +in1_input SDRAM controller voltage in millivolts
> +in2_input SDRAM I/O voltage in millivolts
> +in3_input SDRAM PHY voltage in millivolts
> +in0_label "core"
> +in1_label "sdram_c"
> +in2_label "sdram_i"
> +in3_label "sdram_p"
> in0_lcrit_alarm Undervoltage alarm
> -======================= ==================
> +======================= ======================================================
> +
> +The voltage inputs and labels are only exposed if the firmware reports support
> +for the corresponding voltage ID.
> diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c
> index a2938881ccd2..4f96f37116f3 100644
> --- a/drivers/hwmon/raspberrypi-hwmon.c
> +++ b/drivers/hwmon/raspberrypi-hwmon.c
> @@ -5,6 +5,7 @@
> * Based on firmware/raspberrypi.c by Noralf Trønnes
> *
> * Copyright (C) 2018 Stefan Wahren <stefan.wahren at i2se.com>
> + * Copyright (C) 2026 Shubham Chakraborty <chakrabortyshubham66 at gmail.com>
> */
> #include <linux/device.h>
> #include <linux/devm-helpers.h>
> @@ -18,13 +19,26 @@
>
> #define UNDERVOLTAGE_STICKY_BIT BIT(16)
>
> +struct rpi_firmware_get_value {
> + __le32 id;
> + __le32 val;
> +} __packed;
My earlier comment is still valid: This should be defined in
the include file, and it should be query-specific, just like
struct rpi_firmware_clk_rate_request.
> +
> struct rpi_hwmon_data {
> struct device *hwmon_dev;
> struct rpi_firmware *fw;
> + u32 valid_inputs;
> u32 last_throttled;
> struct delayed_work get_values_poll_work;
> };
>
> +static const char * const rpi_hwmon_labels[] = {
> + "core",
> + "sdram_c",
> + "sdram_i",
> + "sdram_p",
> +};
> +
> static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
> {
> u32 new_uv, old_uv, value;
> @@ -56,6 +70,23 @@ static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
> hwmon_notify_event(data->hwmon_dev, hwmon_in, hwmon_in_lcrit_alarm, 0);
> }
>
> +static int rpi_firmware_get_voltage(struct rpi_hwmon_data *data, u32 id,
> + long *val)
> +{
> + struct rpi_firmware_get_value packet;
> + int ret;
> +
> + packet.id = cpu_to_le32(id);
> + packet.val = 0;
> + ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_VOLTAGE,
> + &packet, sizeof(packet));
> + if (ret)
> + return ret;
> +
> + *val = le32_to_cpu(packet.val) / 1000;
I would suggest to use DIV_ROUND_CLOSEST().
> + return 0;
> +}
> +
> static void get_values_poll(struct work_struct *work)
> {
> struct rpi_hwmon_data *data;
> @@ -77,19 +108,94 @@ static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
> {
> struct rpi_hwmon_data *data = dev_get_drvdata(dev);
>
> - *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
> + if (type == hwmon_in) {
> + switch (attr) {
> + case hwmon_in_input:
> + switch (channel) {
> + case 0:
> + return rpi_firmware_get_voltage(data,
> + RPI_FIRMWARE_VOLT_ID_CORE,
> + val);
> + case 1:
> + return rpi_firmware_get_voltage(data,
> + RPI_FIRMWARE_VOLT_ID_SDRAM_C,
> + val);
> + case 2:
> + return rpi_firmware_get_voltage(data,
> + RPI_FIRMWARE_VOLT_ID_SDRAM_I,
> + val);
> + case 3:
> + return rpi_firmware_get_voltage(data,
> + RPI_FIRMWARE_VOLT_ID_SDRAM_P,
> + val);
> + default:
> + return -EOPNOTSUPP;
With
static const int voltage_regs[] = {
RPI_FIRMWARE_VOLT_ID_CORE, RPI_FIRMWARE_VOLT_ID_SDRAM_C, RPI_FIRMWARE_VOLT_ID_SDRAM_I,
RPI_FIRMWARE_VOLT_ID_SDRAM_P };
this can be simplified to
return rpi_firmware_get_voltage(data, voltage_regs[channel];
> + }
> + case hwmon_in_lcrit_alarm:
> + if (channel == 0) {
> + *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
> + return 0;
> + }
The channel check is not really necessary.
> + return -EOPNOTSUPP;
> + default:
> + return -EOPNOTSUPP;
> + }
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static int rpi_read_string(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, const char **str)
> +{
> + if (type == hwmon_in && attr == hwmon_in_label) {
> + if (channel >= ARRAY_SIZE(rpi_hwmon_labels))
> + return -EOPNOTSUPP;
Unnecessary check.
> +
> + *str = rpi_hwmon_labels[channel];
> + return 0;
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + const struct rpi_hwmon_data *data = _data;
> +
> + if (type == hwmon_in) {
> + switch (attr) {
> + case hwmon_in_input:
> + case hwmon_in_label:
> + if (!(data->valid_inputs & BIT(channel)))
> + return 0;
> + return 0444;
> + case hwmon_in_lcrit_alarm:
> + if (channel == 0)
> + return 0444;
> + return 0;
> + default:
> + return 0;
> + }
> + }
> +
> return 0;
> }
>
> static const struct hwmon_channel_info * const rpi_info[] = {
> HWMON_CHANNEL_INFO(in,
> - HWMON_I_LCRIT_ALARM),
> + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT_ALARM,
> + HWMON_I_INPUT | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL),
> NULL
> };
>
> static const struct hwmon_ops rpi_hwmon_ops = {
> - .visible = 0444,
> + .is_visible = rpi_is_visible,
> .read = rpi_read,
> + .read_string = rpi_read_string,
> };
>
> static const struct hwmon_chip_info rpi_chip_info = {
> @@ -101,6 +207,7 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> struct rpi_hwmon_data *data;
> + long voltage;
> int ret;
>
> data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> @@ -110,6 +217,26 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
> /* Parent driver assure that firmware is correct */
> data->fw = dev_get_drvdata(dev->parent);
>
> + ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_CORE,
> + &voltage);
> + if (!ret)
> + data->valid_inputs |= BIT(0);
> +
> + ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_C,
> + &voltage);
> + if (!ret)
> + data->valid_inputs |= BIT(1);
> +
> + ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_I,
> + &voltage);
> + if (!ret)
> + data->valid_inputs |= BIT(2);
> +
> + ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_P,
> + &voltage);
> + if (!ret)
> + data->valid_inputs |= BIT(3);
> +
This can be implemented in a loop, using the above voltage_regs array.
Thanks,
Guenter
> data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "rpi_volt",
> data,
> &rpi_chip_info,
> @@ -159,6 +286,7 @@ static struct platform_driver rpi_hwmon_driver = {
> module_platform_driver(rpi_hwmon_driver);
>
> MODULE_AUTHOR("Stefan Wahren <wahrenst at gmx.net>");
> +MODULE_AUTHOR("Shubham Chakraborty <chakrabortyshubham66 at gmail.com>");
> MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver");
> MODULE_LICENSE("GPL v2");
> MODULE_ALIAS("platform:raspberrypi-hwmon");
More information about the linux-arm-kernel
mailing list