diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-26 14:21:30 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-26 14:21:30 -0700 |
| commit | b81e34131907d2c243c51117a4aff8dbe22ccc9c (patch) | |
| tree | 3ca8f72beba189f5d6e78e0b699a0ce581d359b1 /drivers/thermal/qcom | |
| parent | 85671b807f82dae7a88ccfc74e33a8374219841c (diff) | |
| parent | 79a57e48822a88f082a4dadb366abaf3c0988b5d (diff) | |
Merge tag 'thermal-7.3-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
Pull more thermal control updates from Rafael Wysocki:
"This mostly consists of assorted updates of thermal drivers, including
new hardware support (Airoha AN7583, Qualcomm Master BandGap thermal
monitor, QCom PMIC5 Gen3 ADC), but it also includes two reverts of
recent cosmetic thermal core updates that went against driver core
plans to eliminate class_create():
- Fix missing bitfield include headers in Armada and QCom SPM BMG
drivers (Daniel Lezcano)
- Fix missed file when manually applying a change after a conflict
resolution for the QCom SPMI ADC TM5 Gen3 (Daniel Lezcano)
- Move thermal_zone_device_enable() to the right place in order to
prevent calling it if the thermal zone registration failed (Dan
Carpenter)
- Improve bitfield manipulations on Armada (Bryan B. Lima)
- Remove unneeded 'fast_io' on Sun8i and Armada (Wolfram Sang)
- Fix wrong boundary when clamping the low values in the set_trips()
callback and fix wrong mask when setting the temperature interval
on Airoha (Christian Marangi)
- Make use of the regmap API to support Airoha AN7583 (Christian
Marangi)
- Fix adc_tm5_get_temp() return check value on the QCom SPMI ADC
sensor (Rakesh Kota)
- Fix unbalanced clock enablement when the resume fails on the iMX
driver (Can Peng)
- Add Qualcomm Master BandGap thermal monitor support (Satya Priya
Kakitapalli)
- Add Maili Temperature bindings compatible (Haritha S K)
- Add a devm action to clean hardware interrupts, sampling, and
control registers on Spacemit K1 (Pei Xiao)
- Fix trivial typo in a thermal OF code comment (Marek Vasut)
- Remove unnecessary print on Qcom SPMI ADC driver when a call to
devm_request_threaded_irq() fails as this one already prints a
message (Jishnu Prakash)
- Add support for QCom PMIC5 Gen3 ADC by using auxiliary driver and
shared interrupt with the IIO driver (Jishnu Prakash)
- Make resets optional on MT8196 and add the corresponding property
in the DT bindings (AngeloGioacchino Del Regno)
- Fix clock staying enabled on failing resume operation on Qoriq (Can
Peng)
- Fix wrong closing brace position in thermal library header (Andreas
Haufler)
- Fix low and high trip point validation by moving the check after
the clamp on the spacemit driver (surendra)
- Remove redundant error messages on IRQ request failure (Pan Chuang)
- Add IIO_CONSUMER namespace import to the qcom-spmi-mbg-tm thermal
driver to avoid modpost warnings that would appear after merging
the iio tree against the thermal updates (Nathan Chancellor)
- Revert two recent cosmetic updates of the thermal core conflicting
with driver core plans to eliminate class_create() (Rafael
Wysocki)"
* tag 'thermal-7.3-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (32 commits)
thermal/drivers/qcom-spmi-mbg-tm: Add module namespace import for IIO_CONSUMER
Revert "thermal/core: Allocate the thermal class dynamically"
Revert "thermal/core: Use the thermal class pointer as init guard"
thermal/drivers/armada: Fix missing bitfields include
thermal/drivers/qcom/spm mbg tm: Fix missing bitfield header
thermal/drivers/qcom: Fix missing spmi adc tm5 gen3 file
thermal/drivers: Remove redundant error messages on IRQ request failure
thermal/drivers/spacemit: Validate clamped trip thresholds
tools/lib/thermal: Fix misplaced extern "C" closing brace
thermal/drivers/qoriq: Disable clock on resume failure
thermal/drivers/mediatek/lvts_thermal: Make reset optional for MT8196
dt-bindings: thermal: mediatek: Make resets optional for MT8196
thermal/drivers/qcom: add support for PMIC5 Gen3 ADC thermal monitoring
iio: adc: qcom-spmi-adc5-gen3: Share SDAM0 IRQ with ADC_TM auxiliary driver
iio: adc: qcom-spmi-adc5-gen3: Remove an unnecessary print
thermal/of: Fix trivial enabled typo
thermal/drivers/spacemit/k1: Add shutdown action and reorder registration order
dt-bindings: thermal: qcom-tsens: Document the Maili Temperature Sensor
thermal/drivers/qcom: Add support for Qualcomm MBG thermal monitoring
dt-bindings: thermal: Add Qualcomm MBG thermal monitor support
...
Diffstat (limited to 'drivers/thermal/qcom')
| -rw-r--r-- | drivers/thermal/qcom/Kconfig | 23 | ||||
| -rw-r--r-- | drivers/thermal/qcom/Makefile | 2 | ||||
| -rw-r--r-- | drivers/thermal/qcom/lmh.c | 1 | ||||
| -rw-r--r-- | drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 434 | ||||
| -rw-r--r-- | drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 3 | ||||
| -rw-r--r-- | drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 257 | ||||
| -rw-r--r-- | drivers/thermal/qcom/tsens.c | 5 |
7 files changed, 717 insertions, 8 deletions
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a6bb01082ec6..a8cf7e258201 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -21,6 +21,29 @@ 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 Qualcomm PMIC MBG (Master Bandgap) thermal monitor. + + The MBG block monitors PMIC die temperature in hardware and raises an + interrupt when the programmed threshold is crossed. + + Temperature is read from the associated ADC channel, and threshold + interrupts are forwarded to the thermal framework as trip-violation + events. Current support handles a single LVL1 upper (hot) trip. + +config QCOM_SPMI_ADC_TM5_GEN3 + tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3" + depends on QCOM_SPMI_ADC5_GEN3 + help + This enables the auxiliary thermal driver for the ADC5 Gen3 thermal + monitoring device. It shows up as a thermal zone with multiple trip points. + Thermal client sets threshold temperature for both warm and cool and + gets updated when a threshold is reached. + 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..937ba0fe2801 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -4,5 +4,7 @@ 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_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.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/lmh.c b/drivers/thermal/qcom/lmh.c index 3d072b7a4a6d..99396b93eff5 100644 --- a/drivers/thermal/qcom/lmh.c +++ b/drivers/thermal/qcom/lmh.c @@ -223,7 +223,6 @@ static int lmh_probe(struct platform_device *pdev) IRQF_NO_THREAD | IRQF_NO_SUSPEND, "lmh-irq", lmh_data); if (ret) { - dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq); irq_domain_remove(lmh_data->domain); return ret; } diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c new file mode 100644 index 000000000000..9cf552d36868 --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/auxiliary_bus.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/device/devres.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/iio/adc/qcom-adc5-gen3-common.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/thermal.h> +#include <linux/types.h> +#include <linux/unaligned.h> + +#include "../thermal_hwmon.h" + +#define ADC_TM5_GEN3_CONFIG_REGS 12 + +struct device; +struct adc_tm5_gen3_chip; + +/** + * struct adc_tm5_gen3_channel_props - ADC_TM channel structure + * @common_props: structure with common ADC channel properties. + * @chip: ADC TM device. + * @tzd: pointer to thermal device corresponding to TM channel. + * @sdam_index: SDAM on which this TM channel lies. + * @timer: time period of recurring TM measurement. + * @tm_chan_index: TM channel number used. + * @high_thr_en: TM high threshold crossing detection enabled. + * @low_thr_en: TM low threshold crossing detection enabled. + */ +struct adc_tm5_gen3_channel_props { + struct adc5_channel_common_prop common_props; + struct adc_tm5_gen3_chip *chip; + struct thermal_zone_device *tzd; + unsigned int sdam_index; + unsigned int timer; + unsigned int tm_chan_index; + bool high_thr_en; + bool low_thr_en; +}; + +/** + * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure + * @dev_data: Top-level ADC device data. + * @chan_props: Array of ADC_TM channel structures. + * @dev: SPMI ADC5 Gen3 device. + * @nchannels: number of TM channels allocated + */ +struct adc_tm5_gen3_chip { + struct adc5_device_data *dev_data; + struct adc_tm5_gen3_channel_props *chan_props; + struct device *dev; + unsigned int nchannels; +}; + +DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *, + adc5_gen3_mutex_lock(_T->dev), adc5_gen3_mutex_unlock(_T->dev)) + +static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq) +{ + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + if (adc_tm5->dev_data->base[i].irq == irq) + return i; + } + return -ENOENT; +} + +static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + int ret, sdam_num; + u8 tm_status[2]; + u8 status, val; + + sdam_num = get_sdam_from_irq(adc_tm5, irq); + if (sdam_num < 0) + return IRQ_NONE; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1, + &status, sizeof(status)); + if (ret) + return IRQ_NONE; + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + val = ADC5_GEN3_CONV_ERR_CLR_REQ; + adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num, + ADC5_GEN3_CONV_ERR_CLR, &val, 1); + return IRQ_HANDLED; + } + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + if (tm_status[0] || tm_status[1]) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static irqreturn_t adctm5_gen3_isr_thread(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + u8 tm_status[2]; + int sdam_index; + + sdam_index = get_sdam_from_irq(adc_tm5, irq); + if (sdam_index < 0) + return IRQ_NONE; + + scoped_guard(adc5_gen3, adc_tm5) { + int ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, tm_status, + sizeof(tm_status)); + if (ret) + return IRQ_NONE; + } + + for (int i = 0; i < adc_tm5->nchannels; i++) { + struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i]; + int offset = chan_prop->tm_chan_index; + bool upper_set, lower_set; + + if (chan_prop->sdam_index != sdam_index) + continue; + + upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en); + lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en); + + if (!(upper_set || lower_set)) + continue; + + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED); + } + + return IRQ_HANDLED; +} + +static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props, temp); +} + +static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + int ret; + u8 val; + + prop->high_thr_en = false; + prop->low_thr_en = false; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret) + return ret; + + val = BIT(prop->tm_chan_index); + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_LOW_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + val = MEAS_INT_DISABLE; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TIMER_SEL, &val, sizeof(val)); + if (ret) + return ret; + + /* To indicate there is an actual conversion request */ + val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_PERPH_CH, &val, sizeof(val)); + if (ret) + return ret; + + val = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &val, sizeof(val)); +} + +static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + u8 buf[ADC_TM5_GEN3_CONFIG_REGS]; + u8 conv_req; + u16 adc_code; + int ret; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret < 0) + return ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* Write SID */ + buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid); + + /* Select TM channel and indicate there is an actual conversion request */ + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(&prop->common_props, &buf[3]); + + /* Update fast average sample value */ + buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK, + prop->common_props.avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[5] = prop->common_props.channel; + + /* Select HW settle delay for channel */ + buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK, + prop->common_props.hw_settle_time_us); + + buf[7] = 0; + + /* High temperature corresponds to low voltage threshold */ + prop->low_thr_en = (high_temp != INT_MAX); + if (prop->low_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp); + put_unaligned_le16(adc_code, &buf[8]); + buf[7] |= ADC5_GEN3_LOW_THR_INT_EN; + } + + /* Low temperature corresponds to high voltage threshold */ + prop->high_thr_en = (low_temp != -INT_MAX); + if (prop->high_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp); + put_unaligned_le16(adc_code, &buf[10]); + buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN; + } + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID, + buf, sizeof(buf)); + if (ret < 0) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req)); +} + +static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n", + prop->common_props.label, low_temp, high_temp); + + guard(adc5_gen3)(adc_tm5); + + return adc_tm5_gen3_configure(prop, low_temp, high_temp); +} + +static const struct thermal_zone_device_ops adc_tm_ops = { + .get_temp = adc_tm5_gen3_get_temp, + .set_trips = adc_tm5_gen3_set_trip_temp, +}; + +static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5) +{ + struct thermal_zone_device *tzd; + unsigned int channel; + int ret; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props); + tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel, + &adc_tm5->chan_props[i], + &adc_tm_ops); + if (IS_ERR(tzd)) { + if (PTR_ERR(tzd) == -ENODEV) { + dev_dbg(adc_tm5->dev, + "thermal sensor on channel %d is not used\n", + channel); + continue; + } + return PTR_ERR(tzd); + } + adc_tm5->chan_props[i].tzd = tzd; + ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd); + if (ret) + return ret; + } + + return 0; +} + +static void adc5_gen3_disable(void *data) +{ + struct adc_tm5_gen3_chip *adc_tm5 = data; + + guard(adc5_gen3)(adc_tm5); + + /* Disable all available TM channels */ + for (int i = 0; i < adc_tm5->nchannels; i++) + adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]); +} + +static int adc_tm5_probe(struct auxiliary_device *aux_dev, + const struct auxiliary_device_id *id) +{ + struct adc_tm5_gen3_chip *adc_tm5; + struct tm5_aux_dev_wrapper *aux_dev_wrapper; + struct device *dev = &aux_dev->dev; + int ret; + + adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL); + if (!adc_tm5) + return -ENOMEM; + + aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper, aux_dev); + + adc_tm5->dev = dev; + adc_tm5->dev_data = aux_dev_wrapper->dev_data; + adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels; + adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels, + sizeof(*adc_tm5->chan_props), GFP_KERNEL); + if (!adc_tm5->chan_props) + return -ENOMEM; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + /* + * Since the first channel of the first SDAM is reserved for + * immediate ADC conversions, TM channel count must start from + * the channel just after it. The variable tm_count is used to + * calculate SDAM and TM channel index on that SDAM correctly + * for each TM channel. + */ + int tm_count = i + 1; + + adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i]; + adc_tm5->chan_props[i].timer = MEAS_INT_1S; + adc_tm5->chan_props[i].sdam_index = tm_count / 8; + adc_tm5->chan_props[i].tm_chan_index = tm_count % 8; + adc_tm5->chan_props[i].chip = adc_tm5; + } + + /* + * ADC_TM channels are enabled in the loop in adc_tm5_register_tzd() as + * part of the set_trips calls during thermal zone registration. This + * action is to disable them all in case of probe failure. + */ + ret = devm_add_action(dev, adc5_gen3_disable, adc_tm5); + if (ret) + return ret; + + ret = adc_tm5_register_tzd(adc_tm5); + if (ret) + return ret; + + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + u32 irq_flags = IRQF_ONESHOT; + + /* + * First SDAM's interrupt is shared between main ADC driver and + * auxiliary TM driver, so its flags must include IRQF_SHARED. + * This is not needed for other SDAMs as they will be used only + * for TM functionality. + */ + if (i == 0) + irq_flags |= IRQF_SHARED; + + ret = devm_request_threaded_irq(dev, + adc_tm5->dev_data->base[i].irq, + adctm5_gen3_isr, + adctm5_gen3_isr_thread, + irq_flags, + adc_tm5->dev_data->base[i].irq_name, + adc_tm5); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = { + { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table); + +static struct auxiliary_driver adctm5gen3_auxiliary_driver = { + .id_table = adctm5_auxiliary_id_table, + .probe = adc_tm5_probe, +}; +module_auxiliary_driver(adctm5gen3_auxiliary_driver); + +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3"); diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c index bb6222c8cc5f..af72db6299cd 100644 --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c @@ -369,9 +369,6 @@ static int adc_tm5_get_temp(struct thermal_zone_device *tz, int *temp) if (ret < 0) return ret; - if (ret != IIO_VAL_INT) - return -EINVAL; - return 0; } 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..a05bb444627a --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include <linux/bitfield.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/thermal.h> +#include <linux/iio/consumer.h> + +#define MBG_TEMP_MON2_FAULT_STATUS 0x50 + +#define MON_FAULT_STATUS_MASK GENMASK(7, 4) +#define MON_FAULT_LVL1_UPR 0x5 + +#define MON2_LVL1_UP_THRESH 0x59 + +#define MBG_TEMP_MON2_MISC_CFG 0x5f +#define MON2_UP_THRESH_EN BIT(1) + +#define MBG_TEMP_STEP_MV 8 +#define MBG_TEMP_DEFAULT_TEMP_MV 600 +#define MBG_TEMP_CONSTANT 1000 +#define MBG_MIN_TRIP_TEMP 25000 +#define MBG_MAX_SUPPORTED_TEMP 160000 + +/** + * struct mbg_tm_chip - MBG thermal monitor device data. + * @map: regmap for accessing MBG thermal registers. + * @dev: mbg_tm_chip device. + * @tz_dev: thermal zone device registered with the thermal framework. + * @lock: mbg_tm_chip lock for set trip temperature. + * @base: base register offset for this MBG instance + * @irq: interrupt line used to signal threshold events + * @last_temp: last measured temperature. + * @last_thres_crossed: indicates whether the last interrupt crossed a threshold + * @adc: IIO ADC channel used for temperature sensing + */ +struct mbg_tm_chip { + struct regmap *map; + struct device *dev; + struct thermal_zone_device *tz_dev; + struct mutex lock; + unsigned int base; + int irq; + int last_temp; + bool last_thres_crossed; + struct iio_channel *adc; +}; + +/** + * struct mbg_map_table - temperature to voltage mapping entry + * @min_temp: minimum temperature supported by this mapping entry + * @vtemp0: reference voltage or ADC code corresponding to the temperature + * @tc: temperature coefficient used for conversion calculations + */ +struct mbg_map_table { + int min_temp; + int vtemp0; + int tc; +}; + +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; + + 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"); + } + + 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"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 6e3714ecab1d..b5ec70201e2f 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -1258,10 +1258,7 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname, dev_name(&pdev->dev), priv); - if (ret) - dev_err(&pdev->dev, "%s: failed to get irq\n", - __func__); - else + if (!ret) *irq_num = irq; } |
