Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions Documentation/hwmon/raspberrypi-hwmon.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
128 changes: 125 additions & 3 deletions drivers/hwmon/raspberrypi-hwmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -21,10 +22,18 @@
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;
Expand Down Expand Up @@ -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;
Comment on lines +68 to +73

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;
Expand All @@ -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;
Comment on lines +128 to +131
}
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 = {
Expand All @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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");
Loading