]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
hwmon: (raspberrypi) Add voltage input support
authorShubham Chakraborty <chakrabortyshubham66@gmail.com>
Sun, 17 May 2026 08:04:44 +0000 (13:34 +0530)
committerGuenter Roeck <linux@roeck-us.net>
Tue, 9 Jun 2026 15:23:00 +0000 (08:23 -0700)
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@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
Link: https://lore.kernel.org/r/20260517080445.103962-3-chakrabortyshubham66@gmail.com
[groeck: Added missing empty line after declaration]
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/raspberrypi-hwmon.rst
drivers/hwmon/raspberrypi-hwmon.c

index 8038ade36490a072bf96789c362e1d8fa39161e5..db315184b8616d2fb4762073121648bb6f25034f 100644 (file)
@@ -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.
index a2938881ccd2f74b80857fcab3dd98d376826f67..a458a858b0ac5850e90854c81e620aaf0147545e 100644 (file)
@@ -5,6 +5,7 @@
  * Based on firmware/raspberrypi.c by Noralf Trønnes
  *
  * Copyright (C) 2018 Stefan Wahren <stefan.wahren@i2se.com>
+ * Copyright (C) 2026 Shubham Chakraborty <chakrabortyshubham66@gmail.com>
  */
 #include <linux/device.h>
 #include <linux/devm-helpers.h>
 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 +65,22 @@ 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_voltage_request packet =
+               RPI_FIRMWARE_GET_VOLTAGE_REQUEST(id);
+       int ret;
+
+       ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_VOLTAGE,
+                                   &packet, sizeof(packet));
+       if (ret)
+               return ret;
+
+       *val = le32_to_cpu(packet.value) / 1000;
+       return 0;
+}
+
 static void get_values_poll(struct work_struct *work)
 {
        struct rpi_hwmon_data *data;
@@ -77,19 +102,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;
+                       }
+               case hwmon_in_lcrit_alarm:
+                       if (channel == 0) {
+                               *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
+                               return 0;
+                       }
+                       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;
+
+               *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 +201,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 +211,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);
+
        data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "rpi_volt",
                                                               data,
                                                               &rpi_chip_info,
@@ -159,6 +280,7 @@ static struct platform_driver rpi_hwmon_driver = {
 module_platform_driver(rpi_hwmon_driver);
 
 MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
+MODULE_AUTHOR("Shubham Chakraborty <chakrabortyshubham66@gmail.com>");
 MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver");
 MODULE_LICENSE("GPL v2");
 MODULE_ALIAS("platform:raspberrypi-hwmon");