Re: [PATCH v2 2/2] thermal: qcom: Add support for Qualcomm MBG thermal monitoring
From: Sachin Gupta
Date: Tue Jul 14 2026 - 07:03:04 EST
On 7/10/2026 8:44 PM, Daniel Lezcano wrote:
>
> Hi Sachin,
>
> On 7/6/26 10:14, Sachin Gupta wrote:
>> From: Satya Priya Kakitapalli <quic_skakitap@xxxxxxxxxxx>
>>
>> Add driver for the Qualcomm MBG thermal monitoring device. It monitors
>> the
>
> MBG ?
>
MBG here stands for Master Bandgap will update more info in the next
patch.
>> die temperature, and when there is a level 1 upper threshold
>> violation, it
>> receives an interrupt over spmi. The driver reads the fault status
>> register and notifies thermal accordingly.
>
> You are describing how works the thermal framework. Please explain how
> works the hardware so we can understand the submitted driver.
>
>> Signed-off-by: Satya Priya Kakitapalli <quic_skakitap@xxxxxxxxxxx>
>> Co-developed-by: Sachin Gupta <sachin.gupta@xxxxxxxxxxxxxxxx>
>> Signed-off-by: Sachin Gupta <sachin.gupta@xxxxxxxxxxxxxxxx>
>> ---
>> drivers/thermal/qcom/Kconfig | 11 ++
>> drivers/thermal/qcom/Makefile | 1 +
>> drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 256 ++++++++++++++++++++++
>> ++++++++++
>> 3 files changed, 268 insertions(+)
>>
>> diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
>> index a6bb01082ec6..f2fc24a3096f 100644
>> --- a/drivers/thermal/qcom/Kconfig
>> +++ b/drivers/thermal/qcom/Kconfig
>> @@ -21,6 +21,17 @@ config QCOM_SPMI_ADC_TM5
>> Thermal client sets threshold temperature for both warm and
>> cool and
>> gets updated when a threshold is reached.
>> +config QCOM_SPMI_MBG_TM
>> + tristate "Qualcomm SPMI PMIC MBG Temperature monitor"
>> + depends on QCOM_SPMI_ADC5_GEN3
>> + select REGMAP_SPMI
>> + help
>> + This enables a thermal driver for the MBG thermal monitoring
>> device.
>> + It shows up in sysfs as a thermal sensor with single trip point.
>> + It notifies the thermal framework when this trip is violated. The
>> + temperature reported by the thermal sensor reflects the real
>> + time die temperature through ADC channel.
>
> Explain the hardware, all sensors are showing up in sysfs and notify the
> thermal framework about thermal events
>
Sure will update in next patch.
>> config QCOM_SPMI_TEMP_ALARM
>> tristate "Qualcomm SPMI PMIC Temperature Alarm"
>> depends on OF && SPMI && IIO
>> diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/
>> Makefile
>> index 0fa2512042e7..1bec2746b98d 100644
>> --- a/drivers/thermal/qcom/Makefile
>> +++ b/drivers/thermal/qcom/Makefile
>> @@ -4,5 +4,6 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
>> qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
>> tsens-8960.o
>> obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
>> +obj-$(CONFIG_QCOM_SPMI_MBG_TM) += qcom-spmi-mbg-tm.o
>> obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
>> obj-$(CONFIG_QCOM_LMH) += lmh.o
>> diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/
>> thermal/qcom/qcom-spmi-mbg-tm.c
>> new file mode 100644
>> index 000000000000..fa2f10002253
>> --- /dev/null
>
> [ ... ]
>
>> +static const struct mbg_map_table map_table[] = {
>> + { -60000, 4337, 1967 },
>> + { -40000, 4731, 1964 },
>> + { -20000, 5124, 1957 },
>> + { 0, 5515, 1949 },
>> + { 20000, 5905, 1940 },
>> + { 40000, 6293, 1930 },
>> + { 60000, 6679, 1921 },
>> + { 80000, 7064, 1910 },
>> + { 100000, 7446, 1896 },
>> + { 120000, 7825, 1878 },
>> + { 140000, 8201, 1859 },
>> +};
>> +
>> +static int mbg_tm_get_temp(struct thermal_zone_device *tz, int *temp)
>> +{
>> + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz);
>> + int ret, milli_celsius;
>> +
>> + scoped_guard(mutex, &chip->lock) {
>> + if (chip->last_thres_crossed) {
>> + dev_dbg(chip->dev, "last_temp: %d\n", chip->last_temp);
>> + chip->last_thres_crossed = false;
>> + *temp = chip->last_temp;
>> + return 0;
>> + }
>> + }
>> +
>> + ret = iio_read_channel_processed(chip->adc, &milli_celsius);
>> + if (ret < 0) {
>> + dev_err(chip->dev, "Failed to read iio channel with %d\n", ret);
>> + return ret;
>> + }
>> +
>> + *temp = milli_celsius;
>> +
>> + return 0;
>> +}
>> +
>> +static int temp_to_vtemp_mv(int temp)
>> +{
>> + int idx, vtemp, tc = 0, t0 = 0, vtemp0 = 0;
>> +
>> + for (idx = 0; idx < ARRAY_SIZE(map_table); idx++)
>> + if (temp >= map_table[idx].min_temp &&
>> + temp < (map_table[idx].min_temp + 20000)) {
>> + tc = map_table[idx].tc;
>> + t0 = map_table[idx].min_temp;
>> + vtemp0 = map_table[idx].vtemp0;
>> + break;
>> + }
>> +
>> + /*
>> + * Formula to calculate vtemp(mV) from a given temp
>> + * vtemp = (temp - minT) * tc + vtemp0
>> + * tc, t0 and vtemp0 values are mentioned in the map_table array.
>> + */
>> + vtemp = ((temp - t0) * tc + vtemp0 * 100000) / 1000000;
>> +
>> + /* step size is 8mV */
>> + return abs(vtemp - MBG_TEMP_DEFAULT_TEMP_MV) / MBG_TEMP_STEP_MV;
>> +}
>> +
>> +static int mbg_tm_set_trip_temp(struct thermal_zone_device *tz, int
>> low_temp,
>> + int temp)
>> +{
>> + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz);
>> + int ret = 0;
>> +
>> + guard(mutex)(&chip->lock);
>> +
>> + /* The HW has a limitation that the trip set must be above 25C */
>> + if (temp > MBG_MIN_TRIP_TEMP && temp < MBG_MAX_SUPPORTED_TEMP) {
>> + ret = regmap_write(chip->map, chip->base + MON2_LVL1_UP_THRESH,
>> + temp_to_vtemp_mv(temp));
>> + if (ret < 0)
>> + return ret;
>> +
>> + ret = regmap_set_bits(chip->map, chip->base +
>> MBG_TEMP_MON2_MISC_CFG,
>> + MON2_UP_THRESH_EN);
>> + if (ret < 0)
>> + return ret;
>> + } else {
>> + dev_err(chip->dev, "Set trip b/w 25C and 160C\n");
>> + ret = regmap_clear_bits(chip->map, chip->base +
>> MBG_TEMP_MON2_MISC_CFG,
>> + MON2_UP_THRESH_EN);
>> + return -ERANGE;
>> + }
>> +
>> + /*
>> + * Configure the last_temp one degree higher, to ensure the
>> + * violated temp is returned to thermal framework when it reads
>> + * temperature for the first time after the violation happens.
>> + * This is needed to account for the inaccuracy in the conversion
>> + * formula used which leads to the thermal framework setting back
>> + * the same thresholds in case the temperature it reads does not
>> + * show violation.
>> + */
>> + chip->last_temp = temp + MBG_TEMP_CONSTANT;
>
> It is because it is inaccurate or the temperature decreased a bit after
> the interrupt fired ?
>
It is because temperature measurement in the threshold setting in MBG
peripheral is inaccurate as compared to the PMIC die temp channel whose
temperature is read in get_temp().
>> + return ret;
>> +}
>> +
>> +static const struct thermal_zone_device_ops mbg_tm_ops = {
>> + .get_temp = mbg_tm_get_temp,
>> + .set_trips = mbg_tm_set_trip_temp,
>> +};
>> +
>> +static irqreturn_t mbg_tm_isr(int irq, void *data)
>> +{
>> + struct mbg_tm_chip *chip = data;
>> + int ret, val;
>> +
>> + scoped_guard(mutex, &chip->lock) {
>> + ret = regmap_read(chip->map, chip->base +
>> MBG_TEMP_MON2_FAULT_STATUS, &val);
>> + if (ret < 0)
>> + return IRQ_HANDLED;
>> + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR)
>> + chip->last_thres_crossed = true;
>> + }
>> +
>> + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) {
>> + dev_dbg(chip->dev, "Notifying Thermal, fault status=%d\n", val);
>> + thermal_zone_device_update(chip->tz_dev, THERMAL_TRIP_VIOLATED);
>> + } else {
>> + dev_dbg(chip->dev, "Lvl1 upper threshold not violated,
>> ignoring interrupt\n");
>
> What does it mean ? Spurious interrupt ?
>
There are multiple hardware reasons for this irq can get triggered but
in software at present we are supporting interrupt handling only for
LVL1_UPR threshold.
Thanks,
Sachin
>> + }
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int mbg_tm_probe(struct platform_device *pdev)
>> +{
>> + struct mbg_tm_chip *chip;
>> + struct device_node *node = pdev->dev.of_node;
>> + u32 res;
>> + int ret;
>> +
>> + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
>> + if (!chip)
>> + return -ENOMEM;
>> +
>> + chip->dev = &pdev->dev;
>> +
>> + mutex_init(&chip->lock);
>> +
>> + chip->map = dev_get_regmap(pdev->dev.parent, NULL);
>> + if (!chip->map)
>> + return -ENXIO;
>> +
>> + ret = device_property_read_u32(chip->dev, "reg", &res);
>> + if (ret < 0)
>> + return dev_err_probe(chip->dev, ret, "Couldn't read reg
>> property\n");
>> +
>> + chip->base = res;
>> +
>> + chip->irq = platform_get_irq(pdev, 0);
>> + if (chip->irq < 0)
>> + return dev_err_probe(chip->dev, chip->irq, "Failed to get
>> irq\n");
>> +
>> + chip->adc = devm_iio_channel_get(&pdev->dev, "thermal");
>> + if (IS_ERR(chip->adc))
>> + return dev_err_probe(chip->dev, PTR_ERR(chip->adc), "Failed
>> to get adc channel\n");
>> +
>> + chip->tz_dev = devm_thermal_of_zone_register(chip->dev, 0, chip,
>> &mbg_tm_ops);
>> + if (IS_ERR(chip->tz_dev))
>> + return dev_err_probe(chip->dev, PTR_ERR(chip->tz_dev),
>> + "Failed to register sensor\n");
>> +
>> + return devm_request_threaded_irq(&pdev->dev, chip->irq, NULL,
>> mbg_tm_isr, IRQF_ONESHOT,
>> + node->name, chip);
>> +}
>> +
>> +static const struct of_device_id mbg_tm_match_table[] = {
>> + { .compatible = "qcom,pm8775-mbg-tm" },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(of, mbg_tm_match_table);
>> +
>> +static struct platform_driver mbg_tm_driver = {
>> + .driver = {
>> + .name = "qcom-spmi-mbg-tm",
>> + .of_match_table = mbg_tm_match_table,
>> + },
>> + .probe = mbg_tm_probe,
>> +};
>> +module_platform_driver(mbg_tm_driver);
>> +
>> +MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver");
>> +MODULE_LICENSE("GPL");
>>
>