[PATCH v2 6/7] hwmon: Add Broadcom PMB PVTMON monitoring

Guenter Roeck linux at roeck-us.net
Sun Nov 1 19:05:56 PST 2026


On 11/1/26 14:14, Linus Walleij wrote:
> Expose the BCM6846 die temperature and seven voltage inputs through
> hwmon using the shared Processor Monitor Bus (PMB) regmap.
> 
> Register the temperature channel with the thermal framework so
> existing zero-cell device-tree thermal sensor references work.
> 
> Poll for valid samples with a bounded timeout, releasing the PMB lock
> between reads. Report temperature in millidegrees Celsius, including
> negative values, and voltages in millivolts.
> 
> Signed-off-by: Linus Walleij <linusw at kernel.org>

Acked-by: Guenter Roeck <linux at roeck-us.net>

Please let me know if you want me to take this patch through
the hwmon subsystem.

Thanks,
Guenter

> ---
>   Documentation/hwmon/bcm_pmb_pvtmon.rst |  53 ++++++++
>   Documentation/hwmon/index.rst          |   1 +
>   MAINTAINERS                            |   2 +
>   drivers/hwmon/Kconfig                  |  18 +++
>   drivers/hwmon/Makefile                 |   1 +
>   drivers/hwmon/bcm_pmb_pvtmon.c         | 232 +++++++++++++++++++++++++++++++++
>   6 files changed, 307 insertions(+)
> 
> diff --git a/Documentation/hwmon/bcm_pmb_pvtmon.rst b/Documentation/hwmon/bcm_pmb_pvtmon.rst
> new file mode 100644
> index 000000000000..7dba658bdc76
> --- /dev/null
> +++ b/Documentation/hwmon/bcm_pmb_pvtmon.rst
> @@ -0,0 +1,53 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +
> +Kernel driver bcm_pmb_pvtmon
> +============================
> +
> +Supported chips:
> +
> +  * Broadcom BCM6846 PVTMON
> +
> +    Access: Processor Monitor Bus (PMB), bus 1, device 6
> +
> +Author: Linus Walleij <linusw at kernel.org>
> +
> +Description
> +-----------
> +
> +The process, voltage and temperature monitor (PVTMON) measures die
> +temperature and seven voltage inputs. The driver uses the shared PMB MFD
> +transport and obtains its clock and reset from the sibling providers.
> +
> +With CONFIG_THERMAL_OF enabled, the temperature channel also supplies
> +device-tree thermal zones. The PVTMON node uses #thermal-sensor-cells = <0>;
> +voltage inputs do not have thermal sensor indices.
> +
> +Sysfs attributes
> +----------------
> +
> +All attributes are read-only. Temperature is reported in millidegrees
> +Celsius and voltages in millivolts, using the BCM6846 vendor conversion
> +formulas and input-divider ratios.
> +
> +================ ================================
> +Attribute        Description
> +================ ================================
> +temp1_input      Die temperature
> +in0_input        First 0.85 V input
> +in1_input        Second 0.85 V input
> +in2_input        VIN input
> +in3_input        1.0 V input
> +in4_input        1.8 V input
> +in5_input        3.3 V input
> +in6_input        Diagnostic test input
> +in0_label        V0.85_1
> +in1_label        V0.85_2
> +in2_label        VIN
> +in3_label        V1.0
> +in4_label        V1.8
> +in5_label        V3.3
> +in6_label        Vtest
> +================ ================================
> +
> +Labels follow the vendor channel names. Vtest is a diagnostic ADC input,
> +not a supply rail. The driver does not change the analog test routing.
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 199ca79621e9..5a579391cf9e 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -54,6 +54,7 @@ Hardware Monitoring Kernel Drivers
>      axiado-pwm-fan
>      axiado-tsadc
>      bcm54140
> +   bcm_pmb_pvtmon
>      bel-pfe
>      bpa-rs600
>      cgbc-hwmon
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 74f0783d512f..124d67ea1582 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5451,7 +5451,9 @@ F:	Documentation/devicetree/bindings/hwmon/brcm,bcm6846-pmb-pvtmon.yaml
>   F:	Documentation/devicetree/bindings/mfd/brcm,bcm6846-pmc.yaml
>   F:	Documentation/devicetree/bindings/reset/brcm,bcm6846-pmb-reset.yaml
>   F:	Documentation/devicetree/bindings/soc/bcm/brcm,bcm6846-pmb.yaml
> +F:	Documentation/hwmon/bcm_pmb_pvtmon.rst
>   F:	drivers/clk/bcm/clk-bcmbca-pmb.c
> +F:	drivers/hwmon/bcm_pmb_pvtmon.c
>   F:	drivers/mfd/bcmbca_pmc3_pmb.c
>   F:	drivers/reset/reset-bcmbca-pmb.c
>   F:	include/dt-bindings/clock/brcm,bcm6846-pmb.h
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 18c682e1a475..7e2bc32628d4 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -334,6 +334,24 @@ config SENSORS_AXIADO_TSADC
>   	  This driver can also be built as a module. If so, the module
>   	  will be called axiado-tsadc.
>   
> +config SENSORS_BCM_PMB_PVTMON
> +	tristate "Broadcom PMB PVTMON hardware monitoring"
> +	depends on ARCH_BCMBCA || COMPILE_TEST
> +	depends on MFD_BCMBCA_PMB
> +	depends on COMMON_CLK
> +	depends on RESET_CONTROLLER
> +	select CLK_BCMBCA_PMB
> +	select RESET_BCMBCA_PMB
> +	select REGMAP
> +	default MFD_BCMBCA_PMB
> +	help
> +	  Enable temperature and voltage monitoring through the Processor
> +	  Monitor Bus on Broadcom BCM6846. With THERMAL_OF enabled, the
> +	  temperature channel also supplies the thermal zones described in
> +	  the device tree.
> +
> +	  This driver can also be built as a module called bcm_pmb_pvtmon.
> +
>   config SENSORS_K8TEMP
>   	tristate "AMD Athlon64/FX or Opteron temperature sensor"
>   	depends on X86 && PCI
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index d4c345ed7261..edf8e8d2f345 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
>   obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
>   obj-$(CONFIG_SENSORS_AXIADO_PWM_FAN) += axiado-pwm-fan.o
>   obj-$(CONFIG_SENSORS_AXIADO_TSADC)	+= axiado-tsadc.o
> +obj-$(CONFIG_SENSORS_BCM_PMB_PVTMON) += bcm_pmb_pvtmon.o
>   obj-$(CONFIG_SENSORS_CGBC)	+= cgbc-hwmon.o
>   obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
>   obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
> diff --git a/drivers/hwmon/bcm_pmb_pvtmon.c b/drivers/hwmon/bcm_pmb_pvtmon.c
> new file mode 100644
> index 000000000000..8b95dac83994
> --- /dev/null
> +++ b/drivers/hwmon/bcm_pmb_pvtmon.c
> @@ -0,0 +1,232 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Broadcom BCM6846 process, voltage and temperature monitor (PVTMON).
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/hwmon.h>
> +#include <linux/mfd/bcmbca-pmb.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#define PVTMON_BUS		1
> +#define PVTMON_DEVICE		6
> +
> +/* PMB register offsets are in 32-bit words. */
> +#define PVTMON_CONFIG		17
> +#define PVTMON_RESET_N		BIT(0)
> +#define PVTMON_POWER_DOWN		BIT(1)
> +
> +#define PVTMON_ACQ_CONFIG		20
> +#define PVTMON_ACCUM_ENABLE	BIT(0)
> +#define PVTMON_SKIP_LENGTH	GENMASK(11, 8)
> +
> +#define PVTMON_CHANNEL_BASE	24
> +#define PVTMON_NUM_CHANNELS	8
> +#define PVTMON_VALUE		GENMASK(9, 0)
> +#define PVTMON_VALID		BIT(18)
> +#define PVTMON_SAMPLES		GENMASK(26, 24)
> +#define PVTMON_CHANNEL_ENABLE	BIT(31)
> +
> +/* Voltage labels and divider ratios follow the BCM6846 vendor definitions. */
> +static const struct {
> +	const char *label;
> +	unsigned int divisor;
> +} bcm6846_pvtmon_voltages[] = {
> +	{ "V0.85_1", 10 },
> +	{ "V0.85_2", 10 },
> +	{ "VIN", 7 },
> +	{ "V1.0", 7 },
> +	{ "V1.8", 4 },
> +	{ "V3.3", 2 },
> +	{ "Vtest", 10 },
> +};
> +
> +static int bcm_pmb_pvtmon_init(struct regmap *regmap,
> +			       struct reset_control *reset)
> +{
> +	unsigned int reg = BCMBCA_PMB_REG(PVTMON_BUS, PVTMON_DEVICE, PVTMON_CONFIG);
> +	u32 config, acq, channel;
> +	unsigned int i;
> +	int ret;
> +
> +	ret = regmap_read(regmap, reg, &config);
> +	if (ret)
> +		return ret;
> +
> +	/* Do not reset acquisition already running for the PMC firmware. */
> +	if ((config & (PVTMON_RESET_N | PVTMON_POWER_DOWN)) != PVTMON_RESET_N) {
> +		ret = regmap_update_bits(regmap, reg, PVTMON_POWER_DOWN, 0);
> +		if (ret)
> +			return ret;
> +		ret = reset_control_reset(reset);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = regmap_read(regmap, BCMBCA_PMB_REG(PVTMON_BUS, PVTMON_DEVICE,
> +						 PVTMON_ACQ_CONFIG), &acq);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < PVTMON_NUM_CHANNELS; i++) {
> +		reg = BCMBCA_PMB_REG(PVTMON_BUS, PVTMON_DEVICE, PVTMON_CHANNEL_BASE + i);
> +		ret = regmap_read(regmap, reg, &channel);
> +		if (ret)
> +			return ret;
> +
> +		if (channel & PVTMON_CHANNEL_ENABLE)
> +			continue;
> +
> +		/* Average 32 samples, matching the vendor acquisition setup. */
> +		ret = regmap_update_bits(regmap, reg, PVTMON_CHANNEL_ENABLE | PVTMON_SAMPLES,
> +					 PVTMON_CHANNEL_ENABLE | FIELD_PREP(PVTMON_SAMPLES, 5));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (!(acq & PVTMON_ACCUM_ENABLE))
> +		return regmap_update_bits(regmap,
> +					  BCMBCA_PMB_REG(PVTMON_BUS, PVTMON_DEVICE,
> +							 PVTMON_ACQ_CONFIG),
> +					  PVTMON_SKIP_LENGTH | PVTMON_ACCUM_ENABLE,
> +					  PVTMON_ACCUM_ENABLE | FIELD_PREP(PVTMON_SKIP_LENGTH, 8));
> +
> +	return 0;
> +}
> +
> +static int bcm_pmb_pvtmon_read(struct device *dev, enum hwmon_sensor_types type,
> +			       u32 attr, int channel, long *val)
> +{
> +	struct regmap *map = dev_get_drvdata(dev);
> +	unsigned int index;
> +	u32 value;
> +	int ret;
> +
> +	switch (type) {
> +	case hwmon_temp:
> +		if (attr != hwmon_temp_input)
> +			return -EOPNOTSUPP;
> +		index = 0;
> +		break;
> +	case hwmon_in:
> +		if (attr != hwmon_in_input)
> +			return -EOPNOTSUPP;
> +		index = channel + 1;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	/* Release the PMB between samples so other clients can make progress. */
> +	ret = regmap_read_poll_timeout(map,
> +				       BCMBCA_PMB_REG(PVTMON_BUS, PVTMON_DEVICE,
> +						      PVTMON_CHANNEL_BASE + index),
> +				       value, value & PVTMON_VALID, 1000, 100000);
> +	if (ret)
> +		return ret;
> +
> +	value = FIELD_GET(PVTMON_VALUE, value);
> +	if (type == hwmon_temp) {
> +		/* Signed arithmetic allows temperatures below zero Celsius. */
> +		*val = (41335000 - 49055 * (int)value) / 100;
> +	} else {
> +		/* 880 mV ADC reference, accounting for each input divider. */
> +		*val = 880 * value * 10 /
> +			(bcm6846_pvtmon_voltages[channel].divisor * 1024);
> +	}
> +
> +	return 0;
> +}
> +
> +static int bcm_pmb_pvtmon_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)
> +		return -EOPNOTSUPP;
> +
> +	*str = bcm6846_pvtmon_voltages[channel].label;
> +	return 0;
> +}
> +
> +static const struct hwmon_ops bcm_pmb_pvtmon_ops = {
> +	.visible = 0444,
> +	.read = bcm_pmb_pvtmon_read,
> +	.read_string = bcm_pmb_pvtmon_read_string,
> +};
> +
> +static const struct hwmon_channel_info * const bcm_pmb_pvtmon_info[] = {
> +	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
> +	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
> +	HWMON_CHANNEL_INFO(in,
> +			   HWMON_I_INPUT | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LABEL),
> +	NULL
> +};
> +
> +static const struct hwmon_chip_info bcm_pmb_pvtmon_chip_info = {
> +	.ops = &bcm_pmb_pvtmon_ops,
> +	.info = bcm_pmb_pvtmon_info,
> +};
> +
> +static int bcm_pmb_pvtmon_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct regmap *regmap = dev_get_regmap(dev->parent, NULL);
> +	struct device *hwmon;
> +	struct reset_control *reset;
> +	struct clk *clk;
> +	int ret;
> +
> +	if (!regmap)
> +		return -ENODEV;
> +
> +	reset = devm_reset_control_get_exclusive(dev, NULL);
> +	if (IS_ERR(reset))
> +		return dev_err_probe(dev, PTR_ERR(reset), "failed to get PVTMON reset\n");
> +
> +	clk = devm_clk_get_enabled(dev, NULL);
> +	if (IS_ERR(clk))
> +		return dev_err_probe(dev, PTR_ERR(clk), "failed to enable PVTMON clock\n");
> +
> +	ret = bcm_pmb_pvtmon_init(regmap, reset);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to initialize PVTMON\n");
> +
> +	hwmon = devm_hwmon_device_register_with_info(dev, "bcm6846_pvtmon", regmap,
> +						     &bcm_pmb_pvtmon_chip_info, NULL);
> +	if (IS_ERR(hwmon))
> +		return dev_err_probe(dev, PTR_ERR(hwmon), "failed to register hwmon device\n");
> +
> +	/* Acquisition remains enabled for the PMC firmware after unbinding. */
> +	return 0;
> +}
> +
> +static const struct of_device_id bcm_pmb_pvtmon_of_match[] = {
> +	{ .compatible = "brcm,bcm6846-pmb-pvtmon" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, bcm_pmb_pvtmon_of_match);
> +
> +static struct platform_driver bcm_pmb_pvtmon_driver = {
> +	.probe = bcm_pmb_pvtmon_probe,
> +	.driver = {
> +		.name = "bcm6846-pmb-pvtmon",
> +		.of_match_table = bcm_pmb_pvtmon_of_match,
> +	},
> +};
> +module_platform_driver(bcm_pmb_pvtmon_driver);
> +
> +MODULE_AUTHOR("Linus Walleij <linusw at kernel.org>");
> +MODULE_DESCRIPTION("Broadcom PMB PVTMON hardware monitoring driver");
> +MODULE_LICENSE("GPL");
> 




More information about the linux-arm-kernel mailing list