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");
>>
>