From eee20da4b7d0716a8a3b6994d8eee25631aca37e Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Mon, 9 Feb 2026 16:24:38 +0530 Subject: [PATCH 1/6] FROMLIST: thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Add support for ADC_TM part of PMIC5 Gen3. This is an auxiliary driver under the Gen3 ADC driver, which implements the threshold setting and interrupt generating functionalities of QCOM ADC_TM drivers, used to support thermal trip points. Reviewed-by: Jonathan Cameron Link: https://lore.kernel.org/all/20260209105438.596339-5-jishnu.prakash@oss.qualcomm.com/ Signed-off-by: Jishnu Prakash --- drivers/thermal/qcom/Kconfig | 9 + drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 507 ++++++++++++++++++ 3 files changed, 517 insertions(+) create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a6bb01082ec69..1acb11e4ac800 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -21,6 +21,15 @@ 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_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 0fa2512042e78..828d9e7bc7970 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_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.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-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c new file mode 100644 index 0000000000000..fde9b073f4826 --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_hwmon.h" + +struct device; +struct adc_tm5_gen3_chip; + +/** + * struct adc_tm5_gen3_channel_props - ADC_TM channel structure + * @timer: time period of recurring TM measurement. + * @tm_chan_index: TM channel number used (ranging from 1-7). + * @sdam_index: SDAM on which this TM channel lies. + * @common_props: structure with common ADC channel properties. + * @high_thr_en: TM high threshold crossing detection enabled. + * @low_thr_en: TM low threshold crossing detection enabled. + * @chip: ADC TM device. + * @tzd: pointer to thermal device corresponding to TM channel. + * @last_temp: last temperature that caused threshold violation, + * or a thermal TM channel. + * @last_temp_set: indicates if last_temp is stored. + */ +struct adc_tm5_gen3_channel_props { + unsigned int timer; + unsigned int tm_chan_index; + unsigned int sdam_index; + struct adc5_channel_common_prop common_props; + bool high_thr_en; + bool low_thr_en; + struct adc_tm5_gen3_chip *chip; + struct thermal_zone_device *tzd; + int last_temp; + bool last_temp_set; +}; + +/** + * 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. + * @nchannels: number of TM channels allocated + * @dev: SPMI ADC5 Gen3 device. + * @tm_handler_work: handler for TM interrupt for threshold violation. + */ +struct adc_tm5_gen3_chip { + struct adc5_device_data *dev_data; + struct adc_tm5_gen3_channel_props *chan_props; + unsigned int nchannels; + struct device *dev; + struct work_struct tm_handler_work; +}; + +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) { + dev_err(adc_tm5->dev, "adc irq %d not associated with an sdam\n", + irq); + return IRQ_HANDLED; + } + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1, + &status, sizeof(status)); + if (ret) { + dev_err(adc_tm5->dev, "adc read status1 failed with %d\n", ret); + return IRQ_HANDLED; + } + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + dev_err_ratelimited(adc_tm5->dev, + "Unexpected conversion fault, status:%#x\n", + status); + 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) { + dev_err(adc_tm5->dev, "adc read TM status failed with %d\n", ret); + return IRQ_HANDLED; + } + + if (tm_status[0] || tm_status[1]) + schedule_work(&adc_tm5->tm_handler_work); + + dev_dbg(adc_tm5->dev, "Interrupt status:%#x, high:%#x, low:%#x\n", + status, tm_status[0], tm_status[1]); + + return IRQ_HANDLED; +} + +static int adc5_gen3_tm_status_check(struct adc_tm5_gen3_chip *adc_tm5, + int sdam_index, u8 *tm_status, u8 *buf) +{ + int ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS, + tm_status, 2); + if (ret) { + dev_err(adc_tm5->dev, "adc read TM status failed with %d\n", ret); + return ret; + } + + ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS_CLR, + tm_status, 2); + if (ret) { + dev_err(adc_tm5->dev, "adc status clear conv_req failed with %d\n", + ret); + return ret; + } + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_CH_DATA0(0), + buf, 16); + if (ret) + dev_err(adc_tm5->dev, "adc read data failed with %d\n", ret); + + return ret; +} + +static void tm_handler_work(struct work_struct *work) +{ + struct adc_tm5_gen3_chip *adc_tm5 = container_of(work, struct adc_tm5_gen3_chip, + tm_handler_work); + int sdam_index = -1; + u8 tm_status[2] = { }; + u8 buf[16] = { }; + + 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; + int ret, temp; + u16 code; + + scoped_guard(adc5_gen3, adc_tm5) { + if (chan_prop->sdam_index != sdam_index) { + sdam_index = chan_prop->sdam_index; + ret = adc5_gen3_tm_status_check(adc_tm5, sdam_index, + tm_status, buf); + if (ret) + return; + } + + 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; + + code = get_unaligned_le16(&buf[2 * offset]); + dev_dbg(adc_tm5->dev, "ADC_TM threshold code:%#x\n", code); + + ret = adc5_gen3_therm_code_to_temp(adc_tm5->dev, + &chan_prop->common_props, + code, &temp); + if (ret) { + dev_err(adc_tm5->dev, + "Invalid temperature reading, ret = %d, code=%#x\n", + ret, code); + continue; + } + + chan_prop->last_temp = temp; + chan_prop->last_temp_set = true; + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED); + } +} + +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; + + if (prop->last_temp_set) { + pr_debug("last_temp: %d\n", prop->last_temp); + prop->last_temp_set = false; + *temp = prop->last_temp; + return 0; + } + + 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; + + 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)); +} + +#define ADC_TM5_GEN3_CONFIG_REGS 12 + +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] &= ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK; + buf[4] |= 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); + + /* 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]); + } + + /* 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] = 0; + if (prop->high_thr_en) + buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN; + if (prop->low_thr_en) + buf[7] |= ADC5_GEN3_LOW_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); + if (high_temp == INT_MAX && low_temp == -INT_MAX) + return adc_tm5_gen3_disable_channel(prop); + + 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_info(adc_tm5->dev, + "thermal sensor on channel %d is not used\n", + channel); + continue; + } + return dev_err_probe(adc_tm5->dev, PTR_ERR(tzd), + "Error registering TZ zone:%ld for channel:%d\n", + PTR_ERR(tzd), channel); + } + 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_clear_work(void *data) +{ + struct adc_tm5_gen3_chip *adc_tm5 = data; + + cancel_work_sync(&adc_tm5->tm_handler_work); +} + +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 void adctm_event_handler(struct auxiliary_device *adev) +{ + struct adc_tm5_gen3_chip *adc_tm5 = auxiliary_get_drvdata(adev); + + schedule_work(&adc_tm5->tm_handler_work); +} + +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++) { + 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 = (i + 1) / 8; + adc_tm5->chan_props[i].tm_chan_index = (i + 1) % 8; + adc_tm5->chan_props[i].chip = adc_tm5; + } + + INIT_WORK(&adc_tm5->tm_handler_work, tm_handler_work); + + /* + * Skipping first SDAM IRQ as it is requested in parent driver. + * If there is a TM violation on that IRQ, the parent driver calls + * the notifier (adctm_event_handler) exposed from this driver to handle it. + */ + for (int i = 1; i < adc_tm5->dev_data->num_sdams; i++) { + ret = devm_request_threaded_irq(dev, + adc_tm5->dev_data->base[i].irq, + NULL, adctm5_gen3_isr, IRQF_ONESHOT, + adc_tm5->dev_data->base[i].irq_name, + adc_tm5); + if (ret < 0) + return ret; + } + + /* + * This drvdata is only used in the function (adctm_event_handler) + * called by parent ADC driver in case of TM violation on the first SDAM. + */ + auxiliary_set_drvdata(aux_dev, adc_tm5); + + adc5_gen3_register_tm_event_notifier(dev, adctm_event_handler); + + /* + * This is to cancel any instances of tm_handler_work scheduled by + * TM interrupt, at the time of module removal. + */ + ret = devm_add_action(dev, adc5_gen3_clear_work, adc_tm5); + if (ret) + return ret; + + ret = adc_tm5_register_tzd(adc_tm5); + if (ret) + return ret; + + /* This is to disable all ADC_TM channels in case of probe failure. */ + + return devm_add_action(dev, adc5_gen3_disable, adc_tm5); +} + +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"); From 0ce73133945c15ce77f08151f682b00557217455 Mon Sep 17 00:00:00 2001 From: Raj Aryan Date: Mon, 20 Apr 2026 10:27:00 +0530 Subject: [PATCH 2/6] FROMLIST: soc: qcom: spmi-pmic: add SUBTYPEs for Glymur/Kaanapali/SM8750 PMICs On Glymur, Kaanapali, and SM8750, PMIC info is not being properly populated in qcom_socinfo. Its shows `unknown` as PMIC subtypes are not updated in the socinfo. root@glymur-crd:/sys/kernel/debug/qcom_socinfo# cat pmic_model unknown (92) root@glymur-crd:/sys/kernel/debug/qcom_socinfo# cat pmic_model_array unknown (92) unknown (93) unknown (98) unknown (98) unknown (97) unknown (97) unknown (96) unknown (96) Update the SUBTYPE info for PMICs present on Glymur,Kaanapali and SM8750 boards, to fix this issue. Also, there are some PMIC subtypes present in the socinfo but not present in the spmi header file, add these entries to keep both definitions aligned. Link: https://lore.kernel.org/all/20260507-fury-v1-1-d24e4bb5b774@qti.qualcomm.com/ Signed-off-by: Raj Aryan --- drivers/soc/qcom/socinfo.c | 8 ++++++++ include/soc/qcom/qcom-spmi-pmic.h | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 8ffd903ebddbb..76dc6499ad679 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -188,7 +188,15 @@ static const char *const pmic_models[] = { [80] = "PM7550", [82] = "PMC8380", [83] = "SMB2360", + [86] = "PM8750B", + [87] = "PMD8028", [91] = "PMIV0108", + [92] = "PMK8850", + [93] = "PMH0101", + [95] = "SMB2370", + [96] = "PMH0104", + [97] = "PMH0110", + [98] = "PMCX0102", }; struct socinfo_params { diff --git a/include/soc/qcom/qcom-spmi-pmic.h b/include/soc/qcom/qcom-spmi-pmic.h index 2cf9e2d8cd55f..997fa18d70fe8 100644 --- a/include/soc/qcom/qcom-spmi-pmic.h +++ b/include/soc/qcom/qcom-spmi-pmic.h @@ -50,9 +50,22 @@ #define PMR735B_SUBTYPE 0x34 #define PM6350_SUBTYPE 0x36 #define PM4125_SUBTYPE 0x37 +#define PM8010_SUBTYPE 0x41 +#define PM8550VS_SUBTYPE 0x45 +#define PM8550VE_SUBTYPE 0x46 +#define PMR735D_SUBTYPE 0x48 +#define PM8550_SUBTYPE 0x49 +#define PMK8550_SUBTYPE 0x4a #define PMM8650AU_SUBTYPE 0x4e #define PMM8650AU_PSAIL_SUBTYPE 0x4f - +#define PM8750B_SUBTYPE 0x56 +#define PMD8028_SUBTYPE 0x57 +#define PMK8850_SUBTYPE 0x5c +#define PMH0101_SUBTYPE 0x5d +#define SMB2370_SUBTYPE 0x5f +#define PMH0104_SUBTYPE 0x60 +#define PMH0110_SUBTYPE 0x61 +#define PMCX0102_SUBTYPE 0x62 #define PMI8998_FAB_ID_SMIC 0x11 #define PMI8998_FAB_ID_GF 0x30 From 7be177d33316d709da51dce6c5f86bf30331d840 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Thu, 30 Apr 2026 14:28:56 +0530 Subject: [PATCH 3/6] FROMLIST: arm64: dts: qcom: Add header file for ADC5 Gen3 channel macros Add macro definitions for virtual channels (combination of ADC channel number and PMIC SID number), to be used in devicetree by clients of ADC5 GEN3 device and in the "reg" property of ADC channels. Link: https://lore.kernel.org/all/20260430-adc5_gen3_dt-v1-1-ab2bb40fd490@oss.qualcomm.com/ Signed-off-by: Jishnu Prakash --- arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h | 88 +++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h diff --git a/arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h b/arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h new file mode 100644 index 0000000000000..aa8e54d7e786a --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcom-adc5-gen3.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __DTS_ARM64_QCOM_ADC5_GEN3_H__ +#define __DTS_ARM64_QCOM_ADC5_GEN3_H__ + +/* ADC channels for PMIC5 Gen3 */ + +#define VIRT_CHAN(sid, chan) ((sid) << 8 | (chan)) + +#define ADC5_GEN3_REF_GND(sid) VIRT_CHAN(sid, 0x00) +#define ADC5_GEN3_1P25VREF(sid) VIRT_CHAN(sid, 0x01) +#define ADC5_GEN3_VREF_VADC(sid) VIRT_CHAN(sid, 0x02) +#define ADC5_GEN3_DIE_TEMP(sid) VIRT_CHAN(sid, 0x03) + +#define ADC5_GEN3_AMUX1_THM(sid) VIRT_CHAN(sid, 0x04) +#define ADC5_GEN3_AMUX2_THM(sid) VIRT_CHAN(sid, 0x05) +#define ADC5_GEN3_AMUX3_THM(sid) VIRT_CHAN(sid, 0x06) +#define ADC5_GEN3_AMUX4_THM(sid) VIRT_CHAN(sid, 0x07) +#define ADC5_GEN3_AMUX5_THM(sid) VIRT_CHAN(sid, 0x08) +#define ADC5_GEN3_AMUX6_THM(sid) VIRT_CHAN(sid, 0x09) +#define ADC5_GEN3_AMUX1_GPIO(sid) VIRT_CHAN(sid, 0x0a) +#define ADC5_GEN3_AMUX2_GPIO(sid) VIRT_CHAN(sid, 0x0b) +#define ADC5_GEN3_AMUX3_GPIO(sid) VIRT_CHAN(sid, 0x0c) +#define ADC5_GEN3_AMUX4_GPIO(sid) VIRT_CHAN(sid, 0x0d) + +#define ADC5_GEN3_CHG_TEMP(sid) VIRT_CHAN(sid, 0x10) +#define ADC5_GEN3_USB_SNS_V_16(sid) VIRT_CHAN(sid, 0x11) +#define ADC5_GEN3_VIN_DIV16_MUX(sid) VIRT_CHAN(sid, 0x12) +#define ADC5_GEN3_VREF_BAT_THERM(sid) VIRT_CHAN(sid, 0x15) +#define ADC5_GEN3_IIN_FB(sid) VIRT_CHAN(sid, 0x17) +#define ADC5_GEN3_TEMP_ALARM_LITE(sid) VIRT_CHAN(sid, 0x18) +#define ADC5_GEN3_IIN_SMB(sid) VIRT_CHAN(sid, 0x19) +#define ADC5_GEN3_ICHG_SMB(sid) VIRT_CHAN(sid, 0x1b) +#define ADC5_GEN3_ICHG_FB(sid) VIRT_CHAN(sid, 0xa1) + +/* 30k pull-up */ +#define ADC5_GEN3_AMUX1_THM_30K_PU(sid) VIRT_CHAN(sid, 0x24) +#define ADC5_GEN3_AMUX2_THM_30K_PU(sid) VIRT_CHAN(sid, 0x25) +#define ADC5_GEN3_AMUX3_THM_30K_PU(sid) VIRT_CHAN(sid, 0x26) +#define ADC5_GEN3_AMUX4_THM_30K_PU(sid) VIRT_CHAN(sid, 0x27) +#define ADC5_GEN3_AMUX5_THM_30K_PU(sid) VIRT_CHAN(sid, 0x28) +#define ADC5_GEN3_AMUX6_THM_30K_PU(sid) VIRT_CHAN(sid, 0x29) +#define ADC5_GEN3_AMUX1_GPIO_30K_PU(sid) VIRT_CHAN(sid, 0x2a) +#define ADC5_GEN3_AMUX2_GPIO_30K_PU(sid) VIRT_CHAN(sid, 0x2b) +#define ADC5_GEN3_AMUX3_GPIO_30K_PU(sid) VIRT_CHAN(sid, 0x2c) +#define ADC5_GEN3_AMUX4_GPIO_30K_PU(sid) VIRT_CHAN(sid, 0x2d) + +/* 100k pull-up */ +#define ADC5_GEN3_AMUX1_THM_100K_PU(sid) VIRT_CHAN(sid, 0x44) +#define ADC5_GEN3_AMUX2_THM_100K_PU(sid) VIRT_CHAN(sid, 0x45) +#define ADC5_GEN3_AMUX3_THM_100K_PU(sid) VIRT_CHAN(sid, 0x46) +#define ADC5_GEN3_AMUX4_THM_100K_PU(sid) VIRT_CHAN(sid, 0x47) +#define ADC5_GEN3_AMUX5_THM_100K_PU(sid) VIRT_CHAN(sid, 0x48) +#define ADC5_GEN3_AMUX6_THM_100K_PU(sid) VIRT_CHAN(sid, 0x49) +#define ADC5_GEN3_AMUX1_GPIO_100K_PU(sid) VIRT_CHAN(sid, 0x4a) +#define ADC5_GEN3_AMUX2_GPIO_100K_PU(sid) VIRT_CHAN(sid, 0x4b) +#define ADC5_GEN3_AMUX3_GPIO_100K_PU(sid) VIRT_CHAN(sid, 0x4c) +#define ADC5_GEN3_AMUX4_GPIO_100K_PU(sid) VIRT_CHAN(sid, 0x4d) + +/* 400k pull-up */ +#define ADC5_GEN3_AMUX1_THM_400K_PU(sid) VIRT_CHAN(sid, 0x64) +#define ADC5_GEN3_AMUX2_THM_400K_PU(sid) VIRT_CHAN(sid, 0x65) +#define ADC5_GEN3_AMUX3_THM_400K_PU(sid) VIRT_CHAN(sid, 0x66) +#define ADC5_GEN3_AMUX4_THM_400K_PU(sid) VIRT_CHAN(sid, 0x67) +#define ADC5_GEN3_AMUX5_THM_400K_PU(sid) VIRT_CHAN(sid, 0x68) +#define ADC5_GEN3_AMUX6_THM_400K_PU(sid) VIRT_CHAN(sid, 0x69) +#define ADC5_GEN3_AMUX1_GPIO_400K_PU(sid) VIRT_CHAN(sid, 0x6a) +#define ADC5_GEN3_AMUX2_GPIO_400K_PU(sid) VIRT_CHAN(sid, 0x6b) +#define ADC5_GEN3_AMUX3_GPIO_400K_PU(sid) VIRT_CHAN(sid, 0x6c) +#define ADC5_GEN3_AMUX4_GPIO_400K_PU(sid) VIRT_CHAN(sid, 0x6d) + +/* 1/3 Divider */ +#define ADC5_GEN3_AMUX1_GPIO_DIV3(sid) VIRT_CHAN(sid, 0x8a) +#define ADC5_GEN3_AMUX2_GPIO_DIV3(sid) VIRT_CHAN(sid, 0x8b) +#define ADC5_GEN3_AMUX3_GPIO_DIV3(sid) VIRT_CHAN(sid, 0x8c) +#define ADC5_GEN3_AMUX4_GPIO_DIV3(sid) VIRT_CHAN(sid, 0x8d) + +#define ADC5_GEN3_VPH_PWR(sid) VIRT_CHAN(sid, 0x8e) +#define ADC5_GEN3_VBAT_SNS_QBG(sid) VIRT_CHAN(sid, 0x8f) + +#define ADC5_GEN3_VBAT_SNS_CHGR(sid) VIRT_CHAN(sid, 0x94) +#define ADC5_GEN3_VBAT_2S_MID_QBG(sid) VIRT_CHAN(sid, 0x96) +#define ADC5_GEN3_VBAT_2S_MID_CHGR(sid) VIRT_CHAN(sid, 0x9d) + +#endif /* __DTS_ARM64_QCOM_ADC5_GEN3_H__ */ From 3843ab50c3f31b575d4fcc780e387159d2cca3dc Mon Sep 17 00:00:00 2001 From: Ayyagari Ushasreevalli Date: Thu, 30 Apr 2026 14:28:57 +0530 Subject: [PATCH 4/6] FROMLIST: arm64: dts: qcom: lemans-pmics: Add ADC support for PMM8654au Add ADC nodes for the four PMM8654au PMICs (pmm8654au_0 through pmm8654au_3) on the Lemans platform. Each ADC node exposes the following ADC channels: - DIE_TEMP: PMIC die temperature channel - VPH_PWR: Battery/supply voltage channel Also add the io-channels and io-channel-names properties under the temp-alarm nodes so that they can get temperature reading from the ADC die_temp channels. Link: https://lore.kernel.org/all/20260430-adc5_gen3_dt-v1-2-ab2bb40fd490@oss.qualcomm.com/ Signed-off-by: Ayyagari Ushasreevalli Signed-off-by: Jishnu Prakash --- arch/arm64/boot/dts/qcom/lemans-pmics.dtsi | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans-pmics.dtsi b/arch/arm64/boot/dts/qcom/lemans-pmics.dtsi index 341119fc82440..6caec3e4df4bb 100644 --- a/arch/arm64/boot/dts/qcom/lemans-pmics.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans-pmics.dtsi @@ -5,6 +5,7 @@ #include #include +#include "qcom-adc5-gen3.h" / { thermal-zones { @@ -110,6 +111,8 @@ reg = <0xa00>; interrupts-extended = <&spmi_bus 0x0 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmm8654au_0_adc ADC5_GEN3_DIE_TEMP(0)>; + io-channel-names = "thermal"; }; pmm8654au_0_pon: pon@1200 { @@ -141,6 +144,27 @@ interrupts = <0x0 0x62 0x1 IRQ_TYPE_EDGE_RISING>; }; + pmm8654au_0_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x0 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@3 { + reg = ; + label = "pmm8654au_0_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@8e { + reg = ; + label = "pmm8654au_0_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; + }; + pmm8654au_0_gpios: gpio@8800 { compatible = "qcom,pmm8654au-gpio", "qcom,spmi-gpio"; reg = <0x8800>; @@ -176,6 +200,29 @@ reg = <0xa00>; interrupts-extended = <&spmi_bus 0x2 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmm8654au_1_adc ADC5_GEN3_DIE_TEMP(2)>; + io-channel-names = "thermal"; + }; + + pmm8654au_1_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x2 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@203 { + reg = ; + label = "pmm8654au_1_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@28e { + reg = ; + label = "pmm8654au_1_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; }; pmm8654au_1_gpios: gpio@8800 { @@ -200,6 +247,29 @@ reg = <0xa00>; interrupts-extended = <&spmi_bus 0x4 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmm8654au_2_adc ADC5_GEN3_DIE_TEMP(4)>; + io-channel-names = "thermal"; + }; + + pmm8654au_2_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x4 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@403 { + reg = ; + label = "pmm8654au_2_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@48e { + reg = ; + label = "pmm8654au_2_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; }; pmm8654au_2_gpios: gpio@8800 { @@ -224,6 +294,29 @@ reg = <0xa00>; interrupts-extended = <&spmi_bus 0x6 0xa 0x0 IRQ_TYPE_EDGE_BOTH>; #thermal-sensor-cells = <0>; + io-channels = <&pmm8654au_3_adc ADC5_GEN3_DIE_TEMP(6)>; + io-channel-names = "thermal"; + }; + + pmm8654au_3_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x6 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@603 { + reg = ; + label = "pmm8654au_3_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@68e { + reg = ; + label = "pmm8654au_3_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; }; pmm8654au_3_gpios: gpio@8800 { From ee32a8c23d8ea363b214127b66f1cb16a4432953 Mon Sep 17 00:00:00 2001 From: Ayyagari Ushasreevalli Date: Thu, 30 Apr 2026 14:28:58 +0530 Subject: [PATCH 5/6] FROMLIST: arm64: dts: qcom: monaco-pmics: Add ADC support for PMM8620AU Add ADC nodes for PMM8620AU PMIC instances (SID 0 and SID 2) present on the Monaco platform. Each ADC node exposes the following ADC channels: - DIE_TEMP: PMIC die temperature channel - VPH_PWR: Battery/supply voltage channel Link: https://lore.kernel.org/all/20260430-adc5_gen3_dt-v1-3-ab2bb40fd490@oss.qualcomm.com/ Signed-off-by: Ayyagari Ushasreevalli Signed-off-by: Jishnu Prakash --- arch/arm64/boot/dts/qcom/monaco-pmics.dtsi | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi b/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi index e990d7367719b..232bcb942b54c 100644 --- a/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi +++ b/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi @@ -5,6 +5,7 @@ #include #include +#include "qcom-adc5-gen3.h" &spmi_bus { pmm8620au_0: pmic@0 { @@ -20,6 +21,27 @@ interrupts = <0x0 0x62 0x1 IRQ_TYPE_EDGE_RISING>; }; + pmm8620au_0_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x0 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@3 { + reg = ; + label = "pmm8620au_0_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@8e { + reg = ; + label = "pmm8620au_0_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; + }; + pmm8620au_0_gpios: gpio@8800 { compatible = "qcom,pmm8654au-gpio", "qcom,spmi-gpio"; reg = <0x8800>; @@ -37,6 +59,27 @@ #address-cells = <1>; #size-cells = <0>; + pmm8650au_1_adc: adc@8000 { + compatible = "qcom,spmi-adc5-gen3"; + reg = <0x8000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <0x2 0x80 0x1 IRQ_TYPE_EDGE_RISING>; + #io-channel-cells = <1>; + + channel@203 { + reg = ; + label = "pmm8650au_1_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@28e { + reg = ; + label = "pmm8650au_1_vph_pwr"; + qcom,pre-scaling = <1 3>; + }; + }; + pmm8650au_1_gpios: gpio@8800 { compatible = "qcom,pmm8654au-gpio", "qcom,spmi-gpio"; reg = <0x8800>; From 8269b21e091790f9a6240617a0cc3b5a8a4527d6 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Mon, 25 May 2026 14:32:02 +0530 Subject: [PATCH 6/6] ucsi wakelock Signed-off-by: Jishnu Prakash --- drivers/usb/typec/ucsi/ucsi_glink.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index 12e07b9fe6228..7e2ed888d75e4 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,15 @@ #include #include "ucsi.h" +/* + * Wakeup timeout covering two async hops: + * 1. pmic_glink_ucsi_notify() work runs ucsi_notify_common() + * 2. ucsi_handle_connector_change() work runs and notifies USB + * ucsi_handle_connector_change() involves GLINK round-trips with up to 5s + * timeouts, so 10s gives sufficient margin for the full chain to complete. + */ +#define UCSI_GLINK_WAKEUP_TIMEOUT_MS 200 + #define PMIC_GLINK_MAX_PORTS 3 #define UCSI_BUF_V1_SIZE (UCSI_MESSAGE_OUT + (UCSI_MESSAGE_OUT - UCSI_MESSAGE_IN)) @@ -342,6 +352,8 @@ static void pmic_glink_ucsi_callback(const void *data, size_t len, void *priv) pmic_glink_ucsi_write_ack(ucsi, data, len); break; case UC_UCSI_USBC_NOTIFY_IND: +// pm_wakeup_event(ucsi->dev, UCSI_GLINK_WAKEUP_TIMEOUT_MS); + pm_wakeup_hard_event(ucsi->dev); schedule_work(&ucsi->notify_work); break; } @@ -401,6 +413,8 @@ static int pmic_glink_ucsi_probe(struct auxiliary_device *adev, ucsi->dev = dev; dev_set_drvdata(dev, ucsi); + device_init_wakeup(dev, true); + INIT_WORK(&ucsi->notify_work, pmic_glink_ucsi_notify); INIT_WORK(&ucsi->register_work, pmic_glink_ucsi_register); init_completion(&ucsi->read_ack);