From 240423ee6b8870a6618bcf70917540e4aaaa14ea Mon Sep 17 00:00:00 2001 From: Rashid Zafar Date: Fri, 4 Nov 2022 15:31:46 -0700 Subject: [PATCH 01/17] drivers: thermal: Add a snapshot of bcl soc driver Add a snapshot of bcl soc driver from msm-5.10 of commit 4911990d5e6d ("drivers: thermal: bcl_soc: Remove QTI_THERMAL dependency"). Updates: - Change GPL v2 to GPL Change-Id: I39f087d54a2be1e12756481ca05279a28b781bd5 Signed-off-by: Manaf Meethalavalappu Pallikunhi Signed-off-by: Rashid Zafar --- drivers/thermal/qcom/Kconfig | 11 ++ drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/bcl_soc.c | 188 +++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 drivers/thermal/qcom/bcl_soc.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a412e77fdbf8..d8e404a226a2 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -62,3 +62,14 @@ config QTI_BCL_PMIC5 voltage sensors with the thermal core framework and can take threshold input and notify the thermal core when the threshold is reached. + +config QTI_BCL_SOC_DRIVER + tristate "QTI Battery state of charge sensor driver" + depends on THERMAL && POWER_SUPPLY + help + This driver registers battery state of charge as a sensor with + thermal framework. This sensor can monitor for state of charge + thresholds and notify the thermal framework when the thresholds + are reached and cleared. This will help to monitor and apply any + mitigation when state of charge goes below a certain threshold. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index c4df3c9694ce..5dffcc7ecc77 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o obj-$(CONFIG_QTI_CPU_PAUSE_COOLING_DEVICE) += thermal_pause.o obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o +obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o diff --git a/drivers/thermal/qcom/bcl_soc.c b/drivers/thermal/qcom/bcl_soc.c new file mode 100644 index 000000000000..a8dbfd3868d7 --- /dev/null +++ b/drivers/thermal/qcom/bcl_soc.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BCL_DRIVER_NAME "bcl_soc_peripheral" + +struct bcl_device { + struct device *dev; + struct notifier_block psy_nb; + struct work_struct soc_eval_work; + long trip_temp; + int trip_val; + struct mutex state_trans_lock; + bool irq_enabled; + struct thermal_zone_device *tz_dev; + struct thermal_zone_of_device_ops ops; +}; + +static struct bcl_device *bcl_perph; + +static int bcl_set_soc(void *data, int low, int high) +{ + if (high == bcl_perph->trip_temp) + return 0; + + mutex_lock(&bcl_perph->state_trans_lock); + pr_debug("socd threshold:%d\n", high); + bcl_perph->trip_temp = high; + if (high == INT_MAX) { + bcl_perph->irq_enabled = false; + goto unlock_and_exit; + } + bcl_perph->irq_enabled = true; + schedule_work(&bcl_perph->soc_eval_work); + +unlock_and_exit: + mutex_unlock(&bcl_perph->state_trans_lock); + return 0; +} + +static int bcl_read_soc(void *data, int *val) +{ + static struct power_supply *batt_psy; + union power_supply_propval ret = {0,}; + int err = 0; + + *val = 0; + if (!batt_psy) + batt_psy = power_supply_get_by_name("battery"); + if (batt_psy) { + err = power_supply_get_property(batt_psy, + POWER_SUPPLY_PROP_CAPACITY, &ret); + if (err) { + pr_err("battery percentage read error:%d\n", + err); + return err; + } + *val = 100 - ret.intval; + } + pr_debug("soc:%d\n", *val); + + return err; +} + +static void bcl_evaluate_soc(struct work_struct *work) +{ + int battery_depletion; + + if (!bcl_perph->tz_dev) + return; + + if (bcl_read_soc(NULL, &battery_depletion)) + return; + + mutex_lock(&bcl_perph->state_trans_lock); + if (!bcl_perph->irq_enabled) + goto eval_exit; + if (battery_depletion < bcl_perph->trip_temp) + goto eval_exit; + + bcl_perph->trip_val = battery_depletion; + mutex_unlock(&bcl_perph->state_trans_lock); + thermal_zone_device_update(bcl_perph->tz_dev, + THERMAL_TRIP_VIOLATED); + + return; +eval_exit: + mutex_unlock(&bcl_perph->state_trans_lock); +} + +static int battery_supply_callback(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct power_supply *psy = data; + + if (strcmp(psy->desc->name, "battery")) + return NOTIFY_OK; + schedule_work(&bcl_perph->soc_eval_work); + + return NOTIFY_OK; +} + +static int bcl_soc_remove(struct platform_device *pdev) +{ + power_supply_unreg_notifier(&bcl_perph->psy_nb); + flush_work(&bcl_perph->soc_eval_work); + if (bcl_perph->tz_dev) + thermal_zone_of_sensor_unregister(&pdev->dev, + bcl_perph->tz_dev); + + return 0; +} + +static int bcl_soc_probe(struct platform_device *pdev) +{ + int ret = 0; + + bcl_perph = devm_kzalloc(&pdev->dev, sizeof(*bcl_perph), GFP_KERNEL); + if (!bcl_perph) + return -ENOMEM; + + mutex_init(&bcl_perph->state_trans_lock); + bcl_perph->dev = &pdev->dev; + bcl_perph->ops.get_temp = bcl_read_soc; + bcl_perph->ops.set_trips = bcl_set_soc; + INIT_WORK(&bcl_perph->soc_eval_work, bcl_evaluate_soc); + bcl_perph->psy_nb.notifier_call = battery_supply_callback; + ret = power_supply_reg_notifier(&bcl_perph->psy_nb); + if (ret < 0) { + pr_err("soc notifier registration error. defer. err:%d\n", + ret); + ret = -EPROBE_DEFER; + goto bcl_soc_probe_exit; + } + bcl_perph->tz_dev = thermal_zone_of_sensor_register(&pdev->dev, + 0, bcl_perph, &bcl_perph->ops); + if (IS_ERR(bcl_perph->tz_dev)) { + pr_err("soc TZ register failed. err:%ld\n", + PTR_ERR(bcl_perph->tz_dev)); + ret = PTR_ERR(bcl_perph->tz_dev); + bcl_perph->tz_dev = NULL; + goto bcl_soc_probe_exit; + } + thermal_zone_device_update(bcl_perph->tz_dev, THERMAL_DEVICE_UP); + schedule_work(&bcl_perph->soc_eval_work); + + dev_set_drvdata(&pdev->dev, bcl_perph); + + return 0; + +bcl_soc_probe_exit: + bcl_soc_remove(pdev); + return ret; +} + +static const struct of_device_id bcl_match[] = { + { + .compatible = "qcom,msm-bcl-soc", + }, + {}, +}; + +static struct platform_driver bcl_driver = { + .probe = bcl_soc_probe, + .remove = bcl_soc_remove, + .driver = { + .name = BCL_DRIVER_NAME, + .of_match_table = bcl_match, + }, +}; + +module_platform_driver(bcl_driver); +MODULE_LICENSE("GPL"); From 70627fa6e2fc4876a242bbac507066bf43f77f85 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Thu, 16 Dec 2021 23:23:39 +0530 Subject: [PATCH 02/17] drivers: thermal: Add ipc logging support for tsens driver Add ipc logging support to tsens driver. It enables two separate ipc logging circular buffer for tsens. The first one dumps different temperature sensor read value whenever there is a read request. The second one dumps different sensor thresholds, interrupt status etc. whenever there is tsens interrupt trigger or set trip call from thermal framework. Change-Id: Ib7964571547c222838b1fa45a19b3bdb7b837116 Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/thermal/qcom/tsens.c | 29 +++++++++++++++++++++++++---- drivers/thermal/qcom/tsens.h | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index e49f58e83513..0c83e99b6874 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2015, The Linux Foundation. All rights reserved. * Copyright (c) 2019, 2020, Linaro Ltd. + * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. */ #include @@ -264,7 +265,7 @@ static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id, static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id, enum tsens_irq_type irq_type, bool enable) { - dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__, + TSENS_DBG_1(priv, "[%u] %s: %s -> %s\n", hw_id, __func__, irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW", enable ? "en" : "dis"); if (tsens_version(priv) > VER_1_X) @@ -348,7 +349,7 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id); d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id); - dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n", + TSENS_DBG_1(priv, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n", hw_id, __func__, (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "", d->low_viol, d->up_viol, d->crit_viol, @@ -407,7 +408,7 @@ static irqreturn_t tsens_critical_irq_thread(int irq, void *data) if (ret) return ret; if (wdog_count) - dev_dbg(priv->dev, "%s: watchdog count: %d\n", + TSENS_DBG_1(priv, "%s: watchdog count: %d\n", __func__, wdog_count); /* Fall through to handle critical interrupts if any */ @@ -528,6 +529,7 @@ static irqreturn_t tsens_irq_thread(int irq, void *data) break; } } + TSENS_DBG_1(priv, "%s: irq[%d] exit", __func__, irq); return IRQ_HANDLED; } @@ -570,7 +572,7 @@ static int tsens_set_trips(void *_sensor, int low, int high) spin_unlock_irqrestore(&priv->ul_lock, flags); - dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n", + TSENS_DBG_1(priv, "[%u] %s: (%d:%d)->(%d:%d)\n", hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high); return 0; @@ -622,6 +624,8 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp) /* Valid bit is set, OK to read the temperature */ *temp = tsens_hw_to_mC(s, temp_idx); + TSENS_DBG(priv, "Sensor_id: %d temp: %d", hw_id, *temp); + return 0; } @@ -648,6 +652,8 @@ int get_temp_common(const struct tsens_sensor *s, int *temp) *temp = code_to_degc(last_temp, s) * 1000; + TSENS_DBG(priv, "Sensor_id: %d temp: %d", hw_id, *temp); + return 0; } while (time_before(jiffies, timeout)); @@ -705,6 +711,7 @@ static void tsens_debug_init(struct platform_device *pdev) { struct tsens_priv *priv = platform_get_drvdata(pdev); struct dentry *root, *file; + char tsens_name[32]; root = debugfs_lookup("tsens", NULL); if (!root) @@ -720,6 +727,20 @@ static void tsens_debug_init(struct platform_device *pdev) /* A directory for each instance of the TSENS IP */ priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root); debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops); + + /* Enable TSENS IPC logging context */ + snprintf(tsens_name, sizeof(tsens_name), "%s_0", dev_name(&pdev->dev)); + priv->ipc_log = ipc_log_context_create(IPC_LOGPAGES, tsens_name, 0); + if (!priv->ipc_log) + dev_err(&pdev->dev, "%s: unable to create IPC Logging for %s\n", + __func__, tsens_name); + + snprintf(tsens_name, sizeof(tsens_name), "%s_1", dev_name(&pdev->dev)); + priv->ipc_log1 = ipc_log_context_create(IPC_LOGPAGES, tsens_name, 0); + if (!priv->ipc_log1) + dev_err(&pdev->dev, "%s: unable to create IPC Logging for %s\n", + __func__, tsens_name); + } #else static inline void tsens_debug_init(struct platform_device *pdev) {} diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index ba05c8233356..e7266f1dae4d 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. */ #ifndef __QCOM_TSENS_H__ @@ -21,6 +22,7 @@ #include #include #include +#include struct tsens_priv; @@ -145,6 +147,25 @@ struct tsens_ops { [_name##_##14] = REG_FIELD(_offset, 30, 30), \ [_name##_##15] = REG_FIELD(_offset, 31, 31) +#define IPC_LOGPAGES 10 +#define TSENS_DBG(dev, msg, args...) do { \ + pr_debug("%s:" msg, __func__, args); \ + if ((dev) && (dev)->ipc_log) { \ + ipc_log_string((dev)->ipc_log, \ + "%s: " msg " [%s]\n", \ + __func__, args, current->comm); \ + } \ + } while (0) + +#define TSENS_DBG_1(priv, msg, args...) do { \ + dev_dbg((priv)->dev, "%s:" msg, __func__, args); \ + if ((priv) && (priv)->ipc_log1) { \ + ipc_log_string((priv)->ipc_log1, \ + "%s: " msg " [%s]\n", \ + __func__, args, current->comm); \ + } \ + } while (0) + /* * reg_field IDs to use as an index into an array * If you change the order of the entries, check the devm_regmap_field_alloc() @@ -551,6 +572,8 @@ struct tsens_context { * @ops: pointer to list of callbacks supported by this device * @debug_root: pointer to debugfs dentry for all tsens * @debug: pointer to debugfs dentry for tsens controller + * @ipc_log: pointer for first ipc log context id + * @ipc_log1: pointer for second ipc log context id * @sensor: list of sensors attached to this device */ struct tsens_priv { @@ -571,6 +594,8 @@ struct tsens_priv { struct dentry *debug_root; struct dentry *debug; + void *ipc_log; + void *ipc_log1; struct tsens_sensor sensor[]; }; From 8f25da0261cdd424780f6dc485549b91c537f701 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Thu, 16 Dec 2021 15:49:42 +0530 Subject: [PATCH 03/17] drivers: thermal: tsens: Cache the trip temperature Cache the trip temperature for the thermal framework to read. Once the trip occurs the temperature at which the trip or trip clear is cached till the thermal framework sets a new trip value. This will ensure that the thermal framework will read the exact temperature as the driver and it cannot go out of sync. Change-Id: Ie44ee0e1e7e4acf3a08f47f1204e6d2ab1a8100f Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/thermal/qcom/tsens.c | 11 ++++++++++- drivers/thermal/qcom/tsens.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 0c83e99b6874..c3c53350d842 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -466,7 +466,7 @@ static irqreturn_t tsens_irq_thread(int irq, void *data) for (i = 0; i < priv->num_sensors; i++) { bool trigger = false; - const struct tsens_sensor *s = &priv->sensor[i]; + struct tsens_sensor *s = &priv->sensor[i]; u32 hw_id = s->hw_id; if (!s->tzd) @@ -511,6 +511,7 @@ static irqreturn_t tsens_irq_thread(int irq, void *data) spin_unlock_irqrestore(&priv->ul_lock, flags); if (trigger) { + s->cached_temp = temp; dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n", hw_id, __func__, temp); thermal_zone_device_update(s->tzd, @@ -564,6 +565,7 @@ static int tsens_set_trips(void *_sensor, int low, int high) tsens_read_irq_state(priv, hw_id, s, &d); + s->cached_temp = INT_MIN; /* Write the new thresholds and clear the status */ regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val); regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val); @@ -605,6 +607,11 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp) u32 valid; int ret; + if (s->cached_temp != INT_MIN) { + *temp = s->cached_temp; + goto dump_and_exit; + } + /* VER_0 doesn't have VALID bit */ if (tsens_version(priv) == VER_0) goto get_temp; @@ -624,6 +631,7 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp) /* Valid bit is set, OK to read the temperature */ *temp = tsens_hw_to_mC(s, temp_idx); +dump_and_exit: TSENS_DBG(priv, "Sensor_id: %d temp: %d", hw_id, *temp); return 0; @@ -1150,6 +1158,7 @@ static int tsens_probe(struct platform_device *pdev) priv->sensor[i].hw_id = data->hw_ids[i]; else priv->sensor[i].hw_id = i; + priv->sensor[i].cached_temp = INT_MIN; } priv->feat = data->feat; priv->fields = data->fields; diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index e7266f1dae4d..f47b80adf572 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -56,6 +56,7 @@ struct tsens_sensor { unsigned int hw_id; int slope; u32 status; + int cached_temp; }; /** From c3aaeca366b7b001cb2a6f4934fd7c877baecfc2 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Thu, 16 Dec 2021 15:51:52 +0530 Subject: [PATCH 04/17] drivers: thermal: tsens: reset cached temp value just after notification The tsens driver caches last read temperature during tsens interrupt violation and notifies framework so that framework will use this cached temperature value instead of new register read. This cached temperature variable will be reset once set trip API gets called for the same sensor. But some reason if set trip API is not called for the same sensor during notification, this cached temperature variable won't reset. It leads to a case where if next tsens violation happens for same sensor, interrupt handler will read this temperature and re-arm previous threshold without notifying thermal framework. It can cause spurious interrupt scenario for tsens. Reset cached temperature value just after thermal framework notification in tsens irq handler. Change-Id: I9707bf5fd245df404122f137b586e4a286be2eee Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/thermal/qcom/tsens.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index c3c53350d842..93020c395357 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -529,6 +529,7 @@ static irqreturn_t tsens_irq_thread(int irq, void *data) */ break; } + s->cached_temp = INT_MIN; } TSENS_DBG_1(priv, "%s: irq[%d] exit", __func__, irq); @@ -565,7 +566,6 @@ static int tsens_set_trips(void *_sensor, int low, int high) tsens_read_irq_state(priv, hw_id, s, &d); - s->cached_temp = INT_MIN; /* Write the new thresholds and clear the status */ regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val); regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val); From c9b28312797d5f1a777c12b9c1a7219217f97b9b Mon Sep 17 00:00:00 2001 From: Rashid Zafar Date: Fri, 4 Nov 2022 15:33:04 -0700 Subject: [PATCH 05/17] driver: thermal: pe_sensor: Add a snapshot of policy engine driver Add a snapshot of policy engine driver from msm-5.10 as of commit 2883589c93f8 ("drivers: thermal: pe_sensor: Add support for trend estimation"). Updates: - Change GPL v2 license to GPL Change-Id: I403d7e26fb31d882c493071ed9826b4861ec87cd Signed-off-by: Manaf Meethalavalappu Pallikunhi Signed-off-by: Rashid Zafar --- drivers/thermal/qcom/Kconfig | 9 ++ drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/policy_engine.c | 216 +++++++++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 drivers/thermal/qcom/policy_engine.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index d8e404a226a2..bc8189d09eba 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -73,3 +73,12 @@ config QTI_BCL_SOC_DRIVER are reached and cleared. This will help to monitor and apply any mitigation when state of charge goes below a certain threshold. +config QTI_POLICY_ENGINE_SENSOR + tristate "QTI Policy Engine Sensor device" + depends on THERMAL + help + This enables the QTI Policy Engine sensor device. This driver will + register the policy engine recommendation with thermal framework as + a sensor. This will enable to provide configuration to mitigate + cooling devices when a recommendation is sent from hardware. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 5dffcc7ecc77..4f5540bd724c 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_QCOM_LMH) += lmh.o obj-$(CONFIG_QTI_CPU_PAUSE_COOLING_DEVICE) += thermal_pause.o obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o +obj-$(CONFIG_QTI_POLICY_ENGINE_SENSOR) += policy_engine.o diff --git a/drivers/thermal/qcom/policy_engine.c b/drivers/thermal/qcom/policy_engine.c new file mode 100644 index 000000000000..edd128242428 --- /dev/null +++ b/drivers/thermal/qcom/policy_engine.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PE_SENS_DRIVER "policy-engine-sensor" +#define PE_INT_ENABLE_OFFSET 0x530 +#define PE_STATUS_OFFSET 0x590 +#define PE_INT_STATUS_OFFSET 0x620 +#define PE_INT_STATUS1_OFFSET 0x630 +#define PE_INTR_CFG 0x11000 +#define PE_INTR_CLEAR 0x11111 +#define PE_STS_CLEAR 0xFFFF +#define PE_READ_MITIGATION_IDX(val) ((val >> 16) & 0x1F) + +struct pe_sensor_data { + struct device *dev; + struct thermal_zone_device *tz_dev; + int32_t high_thresh; + int32_t low_thresh; + int32_t irq_num; + void __iomem *regmap; + struct mutex mutex; +}; + +static int pe_sensor_get_trend(void *data, int trip, enum thermal_trend *trend) +{ + struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data; + struct thermal_zone_device *tz = pe_sens->tz_dev; + int value, last_value; + + if (!tz) + return -EINVAL; + + value = READ_ONCE(tz->temperature); + last_value = READ_ONCE(tz->last_temperature); + + if (!value) + *trend = THERMAL_TREND_DROPPING; + else if (value > last_value) + *trend = THERMAL_TREND_RAISING; + else if (value < last_value) + *trend = THERMAL_TREND_DROPPING; + else + *trend = THERMAL_TREND_STABLE; + + return 0; +} + +static int fetch_mitigation_table_idx(struct pe_sensor_data *pe_sens, int *temp) +{ + u32 data = 0; + + data = readl_relaxed(pe_sens->regmap + PE_STATUS_OFFSET); + *temp = PE_READ_MITIGATION_IDX(data); + dev_dbg(pe_sens->dev, "PE data:%d index:%d\n", data, *temp); + + return 0; +} + +static int pe_sensor_read(void *data, int *temp) +{ + struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data; + + return fetch_mitigation_table_idx(pe_sens, temp); +} + +static int pe_sensor_set_trips(void *data, int low, int high) +{ + struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data; + + mutex_lock(&pe_sens->mutex); + if (pe_sens->high_thresh == high && + pe_sens->low_thresh == low) + goto unlock_exit; + pe_sens->high_thresh = high; + pe_sens->low_thresh = low; + dev_dbg(pe_sens->dev, "PE rail set trip. high:%d low:%d\n", + high, low); + +unlock_exit: + mutex_unlock(&pe_sens->mutex); + return 0; +} + +static struct thermal_zone_of_device_ops pe_sensor_ops = { + .get_temp = pe_sensor_read, + .set_trips = pe_sensor_set_trips, + .get_trend = pe_sensor_get_trend, +}; + +static irqreturn_t pe_handle_irq(int irq, void *data) +{ + struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data; + int val = 0, ret = 0; + + writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET); + writel_relaxed(PE_STS_CLEAR, pe_sens->regmap + PE_INT_STATUS1_OFFSET); + + ret = fetch_mitigation_table_idx(pe_sens, &val); + if (ret) + return IRQ_HANDLED; + + mutex_lock(&pe_sens->mutex); + dev_dbg(pe_sens->dev, "Policy Engine interrupt fired value:%d\n", val); + if (pe_sens->tz_dev && (val >= pe_sens->high_thresh || + val <= pe_sens->low_thresh)) { + mutex_unlock(&pe_sens->mutex); + thermal_zone_device_update(pe_sens->tz_dev, + THERMAL_TRIP_VIOLATED); + } else + mutex_unlock(&pe_sens->mutex); + + return IRQ_HANDLED; +} + +static int pe_sens_device_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret = 0; + struct pe_sensor_data *pe_sens; + struct resource *res; + + pe_sens = devm_kzalloc(dev, sizeof(*pe_sens), GFP_KERNEL); + if (!pe_sens) + return -ENOMEM; + pe_sens->dev = dev; + pe_sens->high_thresh = INT_MAX; + pe_sens->low_thresh = INT_MIN; + mutex_init(&pe_sens->mutex); + + dev_set_drvdata(dev, pe_sens); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Couldn't get MEM resource\n"); + return -EINVAL; + } + dev_dbg(dev, "pe@0x%x size:%d\n", res->start, + resource_size(res)); + + pe_sens->regmap = devm_ioremap_resource(dev, res); + if (!pe_sens->regmap) { + dev_err(dev, "Couldn't get regmap\n"); + return -EINVAL; + } + + pe_sens->irq_num = platform_get_irq(pdev, 0); + if (pe_sens->irq_num < 0) { + dev_err(dev, "Couldn't get irq number\n"); + return pe_sens->irq_num; + } + pe_sens->tz_dev = devm_thermal_zone_of_sensor_register( + dev, 0, pe_sens, &pe_sensor_ops); + if (IS_ERR_OR_NULL(pe_sens->tz_dev)) { + ret = PTR_ERR(pe_sens->tz_dev); + if (ret != -ENODEV) + dev_err(dev, "sensor register failed. ret:%d\n", ret); + pe_sens->tz_dev = NULL; + return ret; + } + writel_relaxed(PE_INTR_CFG, pe_sens->regmap + PE_INT_ENABLE_OFFSET); + writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET); + writel_relaxed(PE_STS_CLEAR, pe_sens->regmap + PE_INT_STATUS1_OFFSET); + ret = devm_request_threaded_irq(dev, pe_sens->irq_num, NULL, + pe_handle_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + dev_name(dev), pe_sens); + if (ret) { + dev_err(dev, "Couldn't get irq registered\n"); + thermal_zone_of_sensor_unregister(pe_sens->dev, + pe_sens->tz_dev); + return ret; + } + enable_irq_wake(pe_sens->irq_num); + + dev_dbg(dev, "PE sensor register success\n"); + + return 0; +} + +static int pe_sens_device_remove(struct platform_device *pdev) +{ + struct pe_sensor_data *pe_sens = + (struct pe_sensor_data *)dev_get_drvdata(&pdev->dev); + + thermal_zone_of_sensor_unregister(pe_sens->dev, pe_sens->tz_dev); + + return 0; +} + +static const struct of_device_id pe_sens_device_match[] = { + {.compatible = "qcom,policy-engine"}, + {} +}; + +static struct platform_driver pe_sens_device_driver = { + .probe = pe_sens_device_probe, + .remove = pe_sens_device_remove, + .driver = { + .name = PE_SENS_DRIVER, + .of_match_table = pe_sens_device_match, + }, +}; + +module_platform_driver(pe_sens_device_driver); +MODULE_LICENSE("GPL"); From ec5bc9a94b6dbc3034d75aba0adb7d8b31bb4750 Mon Sep 17 00:00:00 2001 From: Rashid Zafar Date: Fri, 4 Nov 2022 15:33:37 -0700 Subject: [PATCH 06/17] drivers: thermal: qmi_sensor: Add snapshot of QMI sensor V2 driver This is a snapshot of the thermal framework from msm-5.10 as of commit f5a9cf8e4b17 ("drivers: qmi_sensor: Re-configure qmi sensor trip on subsystem restart"). Updates: - Change GPL v2 license to GPL Change-Id: If2681995379274a5d9d641d0dce2b4f9ec07edb6 Signed-off-by: Manaf Meethalavalappu Pallikunhi Signed-off-by: Rashid Zafar --- drivers/thermal/qcom/Kconfig | 10 + drivers/thermal/qcom/Makefile | 3 + drivers/thermal/qcom/qmi_sensors.h | 147 ++++ drivers/thermal/qcom/qmi_sensors_v2.c | 654 ++++++++++++++++++ .../thermal/qcom/thermal_sensor_service_v02.c | 286 ++++++++ .../thermal/qcom/thermal_sensor_service_v02.h | 98 +++ 6 files changed, 1198 insertions(+) create mode 100644 drivers/thermal/qcom/qmi_sensors.h create mode 100644 drivers/thermal/qcom/qmi_sensors_v2.c create mode 100644 drivers/thermal/qcom/thermal_sensor_service_v02.c create mode 100644 drivers/thermal/qcom/thermal_sensor_service_v02.h diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index bc8189d09eba..494fa9956531 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -82,3 +82,13 @@ config QTI_POLICY_ENGINE_SENSOR a sensor. This will enable to provide configuration to mitigate cooling devices when a recommendation is sent from hardware. +config QTI_QMI_SENSOR_V2 + tristate "QTI QMI TS V2 sensor driver" + depends on THERMAL_OF + select QCOM_QMI_HELPERS + help + This enables to list the QTI remote subsystem temperature sensors + with QMI TS V2 interface. This driver can read the temperature of + the remote sensor. These sensors can take thresholds and notify the + thermal framework when the threshold is reached. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 4f5540bd724c..5d5182621e2e 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -10,3 +10,6 @@ obj-$(CONFIG_QTI_CPU_PAUSE_COOLING_DEVICE) += thermal_pause.o obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o obj-$(CONFIG_QTI_POLICY_ENGINE_SENSOR) += policy_engine.o + +obj-$(CONFIG_QTI_QMI_SENSOR_V2) += qti_qmi_sensor_v2.o +qti_qmi_sensor_v2-y += thermal_sensor_service_v02.o qmi_sensors_v2.o diff --git a/drivers/thermal/qcom/qmi_sensors.h b/drivers/thermal/qcom/qmi_sensors.h new file mode 100644 index 000000000000..776c18d81052 --- /dev/null +++ b/drivers/thermal/qcom/qmi_sensors.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __QMI_SENSORS_H__ +#define __QMI_SENSORS_H__ + +#define QMI_CLIENT_NAME_LENGTH 40 + +enum qmi_ts_sensor { + QMI_TS_PA, + QMI_TS_PA_1, + QMI_TS_PA_2, + QMI_TS_QFE_PA_0, + QMI_TS_QFE_WTR_0, + QMI_TS_MODEM_MODEM, + QMI_TS_MMW_0, + QMI_TS_MMW_1, + QMI_TS_MMW_2, + QMI_TS_MMW_3, + QMI_TS_MODEM_SKIN, + QMI_TS_QFE_PA_MDM, + QMI_TS_QFE_PA_WTR, + QMI_TS_STREAMER_0, + QMI_TS_MOD_MMW_0, + QMI_TS_MOD_MMW_1, + QMI_TS_MOD_MMW_2, + QMI_TS_MOD_MMW_3, + QMI_TS_RET_PA_0, + QMI_TS_WTR_PA_0, + QMI_TS_WTR_PA_1, + QMI_TS_WTR_PA_2, + QMI_TS_WTR_PA_3, + QMI_SYS_THERM1, + QMI_SYS_THERM2, + QMI_TS_TSENS_1, + QMI_TS_MMW_PA1, + QMI_TS_MMW_PA2, + QMI_TS_MMW_PA3, + QMI_TS_SDR_MMW, + QMI_TS_QTM_THERM, + QMI_TS_BCL_WARN, + QMI_TS_SDR0_PA0, + QMI_TS_SDR0_PA1, + QMI_TS_SDR0_PA2, + QMI_TS_SDR0_PA3, + QMI_TS_SDR0_PA4, + QMI_TS_SDR0_PA5, + QMI_TS_SDR0, + QMI_TS_SDR1_PA0, + QMI_TS_SDR1_PA1, + QMI_TS_SDR1_PA2, + QMI_TS_SDR1_PA3, + QMI_TS_SDR1_PA4, + QMI_TS_SDR1_PA5, + QMI_TS_SDR1, + QMI_TS_MMW0, + QMI_TS_MMW1, + QMI_TS_MMW2, + QMI_TS_MMW3, + QMI_TS_MMW_IFIC0, + QMI_TS_SUB1_MODEM_CFG, + QMI_TS_SUB1_LTE_CC, + QMI_TS_SUB1_MCG_FR1_CC, + QMI_TS_SUB1_MCG_FR2_CC, + QMI_TS_SUB1_SCG_FR1_CC, + QMI_TS_SUB1_SCG_FR2_CC, + QMI_TS_SUB2_MODEM_CFG, + QMI_TS_SUB2_LTE_CC, + QMI_TS_SUB2_MCG_FR1_CC, + QMI_TS_SUB2_MCG_FR2_CC, + QMI_TS_SUB2_SCG_FR1_CC, + QMI_TS_SUB2_SCG_FR2_CC, + QMI_TS_NSP_ISENSE_TRIM, + QMI_TS_MAX_NR +}; + +static char sensor_clients[QMI_TS_MAX_NR][QMI_CLIENT_NAME_LENGTH] = { + {"pa"}, + {"pa_1"}, + {"pa_2"}, + {"qfe_pa0"}, + {"qfe_wtr0"}, + {"modem_tsens"}, + {"qfe_mmw0"}, + {"qfe_mmw1"}, + {"qfe_mmw2"}, + {"qfe_mmw3"}, + {"xo_therm"}, + {"qfe_pa_mdm"}, + {"qfe_pa_wtr"}, + {"qfe_mmw_streamer0"}, + {"qfe_mmw0_mod"}, + {"qfe_mmw1_mod"}, + {"qfe_mmw2_mod"}, + {"qfe_mmw3_mod"}, + {"qfe_ret_pa0"}, + {"qfe_wtr_pa0"}, + {"qfe_wtr_pa1"}, + {"qfe_wtr_pa2"}, + {"qfe_wtr_pa3"}, + {"sys_therm1"}, + {"sys_therm2"}, + {"modem_tsens1"}, + {"mmw_pa1"}, + {"mmw_pa2"}, + {"mmw_pa3"}, + {"sdr_mmw_therm"}, + {"qtm_therm"}, + {"modem_bcl_warn"}, + {"sdr0_pa0"}, + {"sdr0_pa1"}, + {"sdr0_pa2"}, + {"sdr0_pa3"}, + {"sdr0_pa4"}, + {"sdr0_pa5"}, + {"sdr0"}, + {"sdr1_pa0"}, + {"sdr1_pa1"}, + {"sdr1_pa2"}, + {"sdr1_pa3"}, + {"sdr1_pa4"}, + {"sdr1_pa5"}, + {"sdr1"}, + {"mmw0"}, + {"mmw1"}, + {"mmw2"}, + {"mmw3"}, + {"mmw_ific0"}, + {"sub1_modem_cfg"}, + {"sub1_lte_cc"}, + {"sub1_mcg_fr1_cc"}, + {"sub1_mcg_fr2_cc"}, + {"sub1_scg_fr1_cc"}, + {"sub1_scg_fr2_cc"}, + {"sub2_modem_cfg"}, + {"sub2_lte_cc"}, + {"sub2_mcg_fr1_cc"}, + {"sub2_mcg_fr2_cc"}, + {"sub2_scg_fr1_cc"}, + {"sub2_scg_fr2_cc"}, + {"isense_trim"}, +}; + +#endif /* __QMI_SENSORS_H__ */ diff --git a/drivers/thermal/qcom/qmi_sensors_v2.c b/drivers/thermal/qcom/qmi_sensors_v2.c new file mode 100644 index 000000000000..3c2ec59c137b --- /dev/null +++ b/drivers/thermal/qcom/qmi_sensors_v2.c @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "thermal_sensor_service_v02.h" +#include "qmi_sensors.h" + +#define QMI_SENS_DRIVER "qmi-therm-sensors-v2" +#define QMI_TS_RESP_TOUT msecs_to_jiffies(100) + +struct qmi_sensor { + struct device *dev; + char qmi_name[QMI_CLIENT_NAME_LENGTH]; + bool connection_active; + struct list_head ts_node; + struct thermal_zone_device *tz_dev; + int32_t last_reading; + int32_t high_thresh; + int32_t low_thresh; + struct qmi_ts_instance *ts; + enum qmi_ts_sensor sens_type; + struct work_struct therm_notify_work; +}; + +struct qmi_ts_instance { + struct device *dev; + struct qmi_handle handle; + struct mutex mutex; + uint32_t inst_id; + struct list_head ts_sensor_list; + struct work_struct svc_arrive_work; +}; + +static struct qmi_ts_instance *ts_instances; +static int ts_inst_cnt; +static atomic_t in_suspend; + +static int qmi_sensor_pm_notify(struct notifier_block *nb, + unsigned long mode, void *_unused) +{ + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_RESTORE_PREPARE: + case PM_SUSPEND_PREPARE: + atomic_set(&in_suspend, 1); + break; + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + atomic_set(&in_suspend, 0); + break; + default: + break; + } + return 0; +} + +static struct notifier_block qmi_sensor_pm_nb = { + .notifier_call = qmi_sensor_pm_notify, +}; + +static void qmi_ts_thresh_notify(struct work_struct *work) +{ + struct qmi_sensor *qmi_sens = container_of(work, + struct qmi_sensor, + therm_notify_work); + + thermal_zone_device_update(qmi_sens->tz_dev, THERMAL_TRIP_VIOLATED); +}; + +static void qmi_ts_update_temperature(struct qmi_ts_instance *ts, + const struct ts_temp_report_ind_msg_v02 *ind_msg, + uint8_t notify) +{ + struct qmi_sensor *qmi_sens; + + list_for_each_entry(qmi_sens, &ts->ts_sensor_list, + ts_node) { + if ((strncasecmp(qmi_sens->qmi_name, + ind_msg->sensor_id.sensor_id, + QMI_TS_SENSOR_ID_LENGTH_MAX_V02))) + continue; + + qmi_sens->last_reading = ind_msg->temp; + + pr_debug("sensor:%s temperature:%d\n", + qmi_sens->qmi_name, qmi_sens->last_reading); + if (!qmi_sens->tz_dev) + return; + if (notify && + (qmi_sens->last_reading >= qmi_sens->high_thresh || + qmi_sens->last_reading <= qmi_sens->low_thresh)) { + pr_debug("Sensor:%s Notify. temp:%d\n", + ind_msg->sensor_id.sensor_id, + qmi_sens->last_reading); + queue_work(system_highpri_wq, + &qmi_sens->therm_notify_work); + } + return; + } +} + +void qmi_ts_ind_cb_v02(struct qmi_handle *qmi, struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *decoded) +{ + const struct ts_temp_report_ind_msg_v02 *ind_msg = decoded; + uint8_t notify = 0; + struct qmi_ts_instance *ts = container_of(qmi, struct qmi_ts_instance, + handle); + + if (!txn) { + pr_err("Invalid transaction\n"); + return; + } + + if ((ind_msg->report_type != QMI_TS_TEMP_REPORT_CURRENT_TEMP_V02) || + ind_msg->seq_num_valid) + notify = 1; + + if (ind_msg->temp_valid) + qmi_ts_update_temperature(ts, ind_msg, notify); + else + pr_err("Error invalid temperature field.\n"); +} + +static int qmi_ts_send_request(struct qmi_ts_instance *ts, int msg_id, + size_t len, struct qmi_elem_info *req_ei, + struct qmi_elem_info *resp_ei, + void *req, void *resp, + struct qmi_response_type_v01 *resp_data) +{ + int ret = 0; + struct qmi_txn txn; + + mutex_lock(&ts->mutex); + ret = qmi_txn_init(&ts->handle, &txn, resp_ei, resp); + if (ret < 0) { + pr_err("qmi txn init failed for msg id:0x%x ret:%d\n", + msg_id, ret); + goto qmi_ts_send_exit; + } + + ret = qmi_send_request(&ts->handle, NULL, &txn, msg_id, len, + req_ei, req); + if (ret < 0) { + pr_err("qmi txn send failed for msg id:0x%x ret:%d\n", + msg_id, ret); + qmi_txn_cancel(&txn); + goto qmi_ts_send_exit; + } + + ret = qmi_txn_wait(&txn, QMI_TS_RESP_TOUT); + if (ret < 0) { + pr_err("qmi txn wait failed for msg id:0x%x ret:%d\n", + msg_id, ret); + goto qmi_ts_send_exit; + } + if (resp_data->result != QMI_RESULT_SUCCESS_V01) { + ret = resp_data->result; + pr_err("qmi NOT success for msg id:0x%x ret:%d\n", + msg_id, ret); + goto qmi_ts_send_exit; + } + ret = 0; + +qmi_ts_send_exit: + mutex_unlock(&ts->mutex); + return ret; +} + +static int qmi_ts_request(struct qmi_sensor *qmi_sens, + bool send_current_temp_report) +{ + int ret = 0; + struct ts_register_notification_temp_resp_msg_v02 resp; + struct ts_register_notification_temp_req_msg_v02 req; + struct qmi_ts_instance *ts = qmi_sens->ts; + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + strscpy(req.sensor_id.sensor_id, qmi_sens->qmi_name, + QMI_TS_SENSOR_ID_LENGTH_MAX_V02); + req.seq_num = 0; + if (send_current_temp_report) { + req.send_current_temp_report = 1; + req.seq_num_valid = true; + } else { + req.seq_num_valid = false; + req.temp_threshold_high_valid = + qmi_sens->high_thresh != INT_MAX; + req.temp_threshold_high = qmi_sens->high_thresh; + req.temp_threshold_low_valid = + qmi_sens->low_thresh != (-INT_MAX); + req.temp_threshold_low = qmi_sens->low_thresh; + pr_debug("sensor:%s high_valid:%d high:%d low_valid:%d low:%d\n", + req.sensor_id.sensor_id, req.temp_threshold_high_valid, + req.temp_threshold_high, req.temp_threshold_low_valid, + req.temp_threshold_low); + } + + ret = qmi_ts_send_request(ts, QMI_TS_REGISTER_NOTIFICATION_TEMP_REQ_V02, + TS_REGISTER_NOTIFICATION_TEMP_REQ_MSG_V02_MAX_MSG_LEN, + ts_register_notification_temp_req_msg_v02_ei, + ts_register_notification_temp_resp_msg_v02_ei, + &req, &resp, &resp.resp); + if (ret < 0) + pr_err("qmi ts txn failed for %s ret:%d\n", + qmi_sens->qmi_name, ret); + + return ret; +} + +static int qmi_sensor_read(void *data, int *temp) +{ + struct qmi_sensor *qmi_sens = (struct qmi_sensor *)data; + + if (qmi_sens->connection_active && !atomic_read(&in_suspend)) + qmi_ts_request(qmi_sens, true); + *temp = qmi_sens->last_reading; + + return 0; +} + +static int qmi_sensor_set_trips(void *data, int low, int high) +{ + struct qmi_sensor *qmi_sens = (struct qmi_sensor *)data; + int ret = 0; + + if (qmi_sens->high_thresh == high && + qmi_sens->low_thresh == low) + return ret; + qmi_sens->high_thresh = high; + qmi_sens->low_thresh = low; + if (!qmi_sens->connection_active) + return ret; + ret = qmi_ts_request(qmi_sens, false); + if (ret < 0) + pr_err("Sensor:%s set high trip:%d low trip:%d error%d\n", + qmi_sens->qmi_name, + qmi_sens->high_thresh, + qmi_sens->low_thresh, + ret); + + return ret; +} + +static struct thermal_zone_of_device_ops qmi_sensor_ops = { + .get_temp = qmi_sensor_read, + .set_trips = qmi_sensor_set_trips, +}; + +static struct qmi_msg_handler handlers[] = { + { + .type = QMI_INDICATION, + .msg_id = QMI_TS_TEMP_REPORT_IND_V02, + .ei = ts_temp_report_ind_msg_v02_ei, + .decoded_size = sizeof(struct ts_temp_report_ind_msg_v02), + .fn = qmi_ts_ind_cb_v02 + }, + {} +}; + +static int qmi_register_sensor_device(struct qmi_sensor *qmi_sens) +{ + int ret = 0; + + qmi_sens->tz_dev = thermal_zone_of_sensor_register( + qmi_sens->dev, + qmi_sens->sens_type + qmi_sens->ts->inst_id, + qmi_sens, &qmi_sensor_ops); + if (IS_ERR(qmi_sens->tz_dev)) { + ret = PTR_ERR(qmi_sens->tz_dev); + if (ret != -ENODEV) + pr_err("sensor register failed for %s, ret:%d\n", + qmi_sens->qmi_name, ret); + qmi_sens->tz_dev = NULL; + return ret; + } + pr_debug("Sensor register success for %s\n", qmi_sens->qmi_name); + + return 0; +} + +static int verify_sensor_and_register(struct qmi_ts_instance *ts) +{ + struct ts_get_sensor_list_req_msg_v02 list_req; + struct ts_get_sensor_list_resp_msg_v02 *list_resp; + struct ts_get_total_num_of_sensors_req_msg_v02 tot_req; + struct ts_get_total_num_of_sensors_resp_msg_v02 tot_resp; + int ret = 0, i, j, sensor_list_itr; + + memset(&tot_resp, 0, sizeof(tot_resp)); + memset(&tot_req, 0, sizeof(tot_req)); + /* size of list_resp is very high, use heap memory rather than stack */ + list_resp = kzalloc(sizeof(*list_resp), GFP_KERNEL); + if (!list_resp) + return -ENOMEM; + + ret = qmi_ts_send_request(ts, QMI_TS_GET_TOTAL_NUM_OF_SENSORS_REQ_V02, + TS_GET_TOTAL_NUM_OF_SENSORS_REQ_MSG_V02_MAX_MSG_LEN, + ts_get_total_num_of_sensors_req_msg_v02_ei, + ts_get_total_num_of_sensors_resp_msg_v02_ei, + &tot_req, &tot_resp, &tot_resp.resp); + if (ret < 0) { + pr_err("qmi ts txn failed, inst_id:%d ret:%d\n", + ts->inst_id, ret); + goto reg_exit; + } + + sensor_list_itr = (tot_resp.total_num_sensors / + QMI_TS_SENSOR_LIST_MAX_V02 + + (tot_resp.total_num_sensors % + QMI_TS_SENSOR_LIST_MAX_V02 ? 1 : 0)); + + pr_debug("Total QMI sensors cnt:%d list iteration cnt:%d inst_id:%d\n", + tot_resp.total_num_sensors, sensor_list_itr, ts->inst_id); + + for (i = 0; i < sensor_list_itr; i++) { + memset(&list_req, 0, sizeof(list_req)); + memset(list_resp, 0, sizeof(*list_resp)); + + list_req.list_index = i; + + ret = qmi_ts_send_request(ts, QMI_TS_GET_SENSOR_LIST_REQ_V02, + TS_GET_SENSOR_LIST_REQ_MSG_V02_MAX_MSG_LEN, + ts_get_sensor_list_req_msg_v02_ei, + ts_get_sensor_list_resp_msg_v02_ei, + &list_req, list_resp, &list_resp->resp); + if (ret < 0) { + pr_err("qmi ts txn failed, inst_id:%d itr:%d ret:%d\n", + ts->inst_id, i, ret); + goto reg_exit; + } + + for (j = 0; j < list_resp->sensor_list_len; j++) { + struct qmi_sensor *qmi_sens = NULL; + + pr_debug("QMI TS sensor[%d][%d]: %s\n", i, j, + list_resp->sensor_list[j].sensor_id); + + list_for_each_entry(qmi_sens, &ts->ts_sensor_list, + ts_node) { + if ((strncasecmp(qmi_sens->qmi_name, + list_resp->sensor_list[j].sensor_id, + QMI_TS_SENSOR_ID_LENGTH_MAX_V02))) + continue; + + qmi_sens->connection_active = true; + /* + * Send a temperature request notification. + */ + qmi_ts_request(qmi_sens, true); + if (!qmi_sens->tz_dev) { + ret = qmi_register_sensor_device(qmi_sens); + } else { + ret = qmi_ts_request(qmi_sens, false); + if (ret) + pr_err( + "sensor:%s trips:%d %d err%d\n", + qmi_sens->tz_dev->type, + qmi_sens->high_thresh, + qmi_sens->low_thresh, + ret); + } + break; + } + } + } + +reg_exit: + kfree(list_resp); + return ret; +} + +static void qmi_ts_svc_arrive(struct work_struct *work) +{ + struct qmi_ts_instance *ts = container_of(work, + struct qmi_ts_instance, + svc_arrive_work); + + verify_sensor_and_register(ts); +} + +static void thermal_qmi_net_reset(struct qmi_handle *qmi) +{ + struct qmi_ts_instance *ts = container_of(qmi, + struct qmi_ts_instance, + handle); + struct qmi_sensor *qmi_sens = NULL; + int ret; + + pr_debug("reset QMI server\n"); + list_for_each_entry(qmi_sens, &ts->ts_sensor_list, + ts_node) { + if (!qmi_sens->connection_active) + continue; + qmi_ts_request(qmi_sens, true); + ret = qmi_ts_request(qmi_sens, false); + if (ret) + pr_err("Sensor:%s set high trip:%d low trip:%d err%d\n", + qmi_sens->tz_dev->type, + qmi_sens->high_thresh, + qmi_sens->low_thresh, + ret); + } +} + +static void thermal_qmi_del_server(struct qmi_handle *qmi, + struct qmi_service *service) +{ + struct qmi_ts_instance *ts = container_of(qmi, + struct qmi_ts_instance, + handle); + struct qmi_sensor *qmi_sens = NULL; + + pr_debug("QMI server deleted\n"); + list_for_each_entry(qmi_sens, &ts->ts_sensor_list, ts_node) + qmi_sens->connection_active = false; +} + +static int thermal_qmi_new_server(struct qmi_handle *qmi, + struct qmi_service *service) +{ + struct qmi_ts_instance *ts = container_of(qmi, + struct qmi_ts_instance, + handle); + struct sockaddr_qrtr sq = {AF_QIPCRTR, service->node, service->port}; + + mutex_lock(&ts->mutex); + pr_debug("new QMI server is up, connecting..\n"); + kernel_connect(qmi->sock, (struct sockaddr *)&sq, sizeof(sq), 0); + mutex_unlock(&ts->mutex); + queue_work(system_highpri_wq, &ts->svc_arrive_work); + + return 0; +} + +static struct qmi_ops thermal_qmi_event_ops = { + .new_server = thermal_qmi_new_server, + .del_server = thermal_qmi_del_server, + .net_reset = thermal_qmi_net_reset, +}; + +static void qmi_ts_cleanup(void) +{ + struct qmi_ts_instance *ts; + struct qmi_sensor *qmi_sens, *c_next; + int idx = 0; + + for (; idx < ts_inst_cnt; idx++) { + ts = &ts_instances[idx]; + mutex_lock(&ts->mutex); + list_for_each_entry_safe(qmi_sens, c_next, + &ts->ts_sensor_list, ts_node) { + qmi_sens->connection_active = false; + if (qmi_sens->tz_dev) { + thermal_zone_of_sensor_unregister( + qmi_sens->dev, qmi_sens->tz_dev); + qmi_sens->tz_dev = NULL; + } + + list_del(&qmi_sens->ts_node); + } + qmi_handle_release(&ts->handle); + + mutex_unlock(&ts->mutex); + } + ts_inst_cnt = 0; +} + +static int of_get_qmi_ts_platform_data(struct device *dev) +{ + int ret = 0, i = 0, idx = 0; + struct device_node *np = dev->of_node; + struct device_node *subsys_np = NULL; + struct qmi_ts_instance *ts; + struct qmi_sensor *qmi_sens; + int sens_name_max = 0, sens_idx = 0, subsys_cnt = 0; + + subsys_cnt = of_get_available_child_count(np); + if (!subsys_cnt) { + dev_err(dev, "No child node to process\n"); + return -EFAULT; + } + + ts = devm_kcalloc(dev, subsys_cnt, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + for_each_available_child_of_node(np, subsys_np) { + if (idx >= subsys_cnt) + break; + + ret = of_property_read_u32(subsys_np, "qcom,instance-id", + &ts[idx].inst_id); + if (ret) { + dev_err(dev, "error reading qcom,insance-id. ret:%d\n", + ret); + goto data_fetch_err; + } + + ts[idx].dev = dev; + mutex_init(&ts[idx].mutex); + INIT_LIST_HEAD(&ts[idx].ts_sensor_list); + INIT_WORK(&ts[idx].svc_arrive_work, qmi_ts_svc_arrive); + + sens_name_max = of_property_count_strings(subsys_np, + "qcom,qmi-sensor-names"); + if (sens_name_max <= 0) { + dev_err(dev, "Invalid or no sensor. err:%d\n", + sens_name_max); + ret = -EINVAL; + goto data_fetch_err; + } + + for (sens_idx = 0; sens_idx < sens_name_max; sens_idx++) { + const char *qmi_name; + + qmi_sens = devm_kzalloc(dev, sizeof(*qmi_sens), + GFP_KERNEL); + if (!qmi_sens) { + ret = -ENOMEM; + goto data_fetch_err; + } + + of_property_read_string_index(subsys_np, + "qcom,qmi-sensor-names", sens_idx, + &qmi_name); + strscpy(qmi_sens->qmi_name, qmi_name, + QMI_CLIENT_NAME_LENGTH); + /* Check for supported qmi sensors */ + for (i = 0; i < QMI_TS_MAX_NR; i++) { + if (!strcmp(sensor_clients[i], + qmi_sens->qmi_name)) + break; + } + + if (i >= QMI_TS_MAX_NR) { + dev_err(dev, "Unknown sensor:%s\n", + qmi_sens->qmi_name); + ret = -EINVAL; + goto data_fetch_err; + } + dev_dbg(dev, "QMI sensor:%s available\n", qmi_name); + qmi_sens->sens_type = i; + qmi_sens->ts = &ts[idx]; + qmi_sens->dev = dev; + qmi_sens->last_reading = 0; + qmi_sens->high_thresh = INT_MAX; + qmi_sens->low_thresh = -INT_MAX; + INIT_WORK(&qmi_sens->therm_notify_work, + qmi_ts_thresh_notify); + list_add(&qmi_sens->ts_node, &ts[idx].ts_sensor_list); + } + idx++; + } + ts_instances = ts; + ts_inst_cnt = subsys_cnt; + + return 0; +data_fetch_err: + of_node_put(subsys_np); + return ret; +} + +static int qmi_sens_device_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret = 0, idx = 0; + struct qmi_ts_instance *ts; + + ret = of_get_qmi_ts_platform_data(dev); + if (ret) + goto probe_err; + + if (!ts_instances || !ts_inst_cnt) { + dev_err(dev, "Empty ts instances\n"); + return -EINVAL; + } + + for (; idx < ts_inst_cnt; idx++) { + ts = &ts_instances[idx]; + if (list_empty(&ts->ts_sensor_list)) { + ret = -ENODEV; + goto probe_err; + } + ret = qmi_handle_init(&ts->handle, + TS_GET_SENSOR_LIST_RESP_MSG_V02_MAX_MSG_LEN, + &thermal_qmi_event_ops, handlers); + if (ret < 0) { + dev_err(dev, "QMI[0x%x] handle init failed. err:%d\n", + ts->inst_id, ret); + ts_inst_cnt = idx; + ret = -EPROBE_DEFER; + goto probe_err; + } + ret = qmi_add_lookup(&ts->handle, TS_SERVICE_ID_V02, + TS_SERVICE_VERS_V02, ts->inst_id); + if (ret < 0) { + dev_err(dev, "QMI register failed for 0x%x, ret:%d\n", + ts->inst_id, ret); + ret = -EPROBE_DEFER; + goto probe_err; + } + } + atomic_set(&in_suspend, 0); + register_pm_notifier(&qmi_sensor_pm_nb); + return 0; + +probe_err: + qmi_ts_cleanup(); + return ret; +} + +static int qmi_sens_device_remove(struct platform_device *pdev) +{ + qmi_ts_cleanup(); + unregister_pm_notifier(&qmi_sensor_pm_nb); + + return 0; +} + +static const struct of_device_id qmi_sens_device_match[] = { + {.compatible = "qcom,qmi-sensors"}, + {} +}; + +static struct platform_driver qmi_sens_device_driver = { + .probe = qmi_sens_device_probe, + .remove = qmi_sens_device_remove, + .driver = { + .name = QMI_SENS_DRIVER, + .of_match_table = qmi_sens_device_match, + }, +}; + +module_platform_driver(qmi_sens_device_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/qcom/thermal_sensor_service_v02.c b/drivers/thermal/qcom/thermal_sensor_service_v02.c new file mode 100644 index 000000000000..2d341b2fe773 --- /dev/null +++ b/drivers/thermal/qcom/thermal_sensor_service_v02.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include "thermal_sensor_service_v02.h" + +static struct qmi_elem_info ts_sensor_type_v02_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_TS_SENSOR_ID_LENGTH_MAX_V02 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct ts_sensor_type_v02, + sensor_id), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info ts_get_total_num_of_sensors_req_msg_v02_ei[] = { + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info ts_get_total_num_of_sensors_resp_msg_v02_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct ts_get_total_num_of_sensors_resp_msg_v02, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct ts_get_total_num_of_sensors_resp_msg_v02, + total_num_sensors), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info ts_get_sensor_list_req_msg_v02_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct ts_get_sensor_list_req_msg_v02, + list_index), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info ts_get_sensor_list_resp_msg_v02_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct ts_get_sensor_list_resp_msg_v02, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ts_get_sensor_list_resp_msg_v02, + sensor_list_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ts_get_sensor_list_resp_msg_v02, + sensor_list_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_TS_SENSOR_LIST_MAX_V02, + .elem_size = sizeof(struct ts_sensor_type_v02), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ts_get_sensor_list_resp_msg_v02, + sensor_list), + .ei_array = ts_sensor_type_v02_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info ts_register_notification_temp_req_msg_v02_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct ts_sensor_type_v02), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct ts_register_notification_temp_req_msg_v02, + sensor_id), + .ei_array = ts_sensor_type_v02_ei, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct ts_register_notification_temp_req_msg_v02, + send_current_temp_report), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ts_register_notification_temp_req_msg_v02, + temp_threshold_high_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(int), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ts_register_notification_temp_req_msg_v02, + temp_threshold_high), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct ts_register_notification_temp_req_msg_v02, + temp_threshold_low_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(int), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct ts_register_notification_temp_req_msg_v02, + temp_threshold_low), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct ts_register_notification_temp_req_msg_v02, + seq_num_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct ts_register_notification_temp_req_msg_v02, + seq_num), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info ts_register_notification_temp_resp_msg_v02_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct ts_register_notification_temp_resp_msg_v02, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info ts_temp_report_ind_msg_v02_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct ts_sensor_type_v02), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct ts_temp_report_ind_msg_v02, + sensor_id), + .ei_array = ts_sensor_type_v02_ei, + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum ts_temp_report_type_enum_v02), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct ts_temp_report_ind_msg_v02, + report_type), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ts_temp_report_ind_msg_v02, + temp_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(int), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ts_temp_report_ind_msg_v02, + temp), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct ts_temp_report_ind_msg_v02, + seq_num_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct ts_temp_report_ind_msg_v02, + seq_num), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + diff --git a/drivers/thermal/qcom/thermal_sensor_service_v02.h b/drivers/thermal/qcom/thermal_sensor_service_v02.h new file mode 100644 index 000000000000..b5cf7b6fcc8a --- /dev/null +++ b/drivers/thermal/qcom/thermal_sensor_service_v02.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef THERMAL_SENSOR_SERVICE_V02_H +#define THERMAL_SENSOR_SERVICE_V02_H + +#define TS_SERVICE_ID_V02 0x17 +#define TS_SERVICE_VERS_V02 0x02 + +#define QMI_TS_GET_SUPPORTED_MSGS_RESP_V02 0x001E +#define QMI_TS_GET_SENSOR_LIST_RESP_V02 0x0031 +#define QMI_TS_GET_SUPPORTED_MSGS_REQ_V02 0x001E +#define QMI_TS_GET_TOTAL_NUM_OF_SENSORS_RESP_V02 0x0030 +#define QMI_TS_REGISTER_NOTIFICATION_TEMP_REQ_V02 0x0032 +#define QMI_TS_REGISTER_NOTIFICATION_TEMP_RESP_V02 0x0032 +#define QMI_TS_GET_TOTAL_NUM_OF_SENSORS_REQ_V02 0x0030 +#define QMI_TS_GET_SUPPORTED_FIELDS_RESP_V02 0x001F +#define QMI_TS_GET_SENSOR_LIST_REQ_V02 0x0031 +#define QMI_TS_TEMP_REPORT_IND_V02 0x0033 +#define QMI_TS_GET_SUPPORTED_FIELDS_REQ_V02 0x001F + +#define QMI_TS_SENSOR_ID_LENGTH_MAX_V02 32 +#define QMI_TS_SENSOR_LIST_MAX_V02 96 + +struct ts_sensor_type_v02 { + char sensor_id[QMI_TS_SENSOR_ID_LENGTH_MAX_V02 + 1]; +}; + +struct ts_get_total_num_of_sensors_req_msg_v02 { + char placeholder; +}; +#define TS_GET_TOTAL_NUM_OF_SENSORS_REQ_MSG_V02_MAX_MSG_LEN 0 +extern struct qmi_elem_info ts_get_total_num_of_sensors_req_msg_v02_ei[]; + +struct ts_get_total_num_of_sensors_resp_msg_v02 { + struct qmi_response_type_v01 resp; + u32 total_num_sensors; +}; +#define TS_GET_TOTAL_NUM_OF_SENSORS_RESP_MSG_V02_MAX_MSG_LEN 14 +extern struct qmi_elem_info ts_get_total_num_of_sensors_resp_msg_v02_ei[]; + +struct ts_get_sensor_list_req_msg_v02 { + u32 list_index; +}; +#define TS_GET_SENSOR_LIST_REQ_MSG_V02_MAX_MSG_LEN 7 +extern struct qmi_elem_info ts_get_sensor_list_req_msg_v02_ei[]; + +struct ts_get_sensor_list_resp_msg_v02 { + struct qmi_response_type_v01 resp; + u8 sensor_list_valid; + u32 sensor_list_len; + struct ts_sensor_type_v02 sensor_list[QMI_TS_SENSOR_LIST_MAX_V02]; +}; +#define TS_GET_SENSOR_LIST_RESP_MSG_V02_MAX_MSG_LEN 3179 +extern struct qmi_elem_info ts_get_sensor_list_resp_msg_v02_ei[]; + +struct ts_register_notification_temp_req_msg_v02 { + struct ts_sensor_type_v02 sensor_id; + u8 send_current_temp_report; + u8 temp_threshold_high_valid; + int temp_threshold_high; + u8 temp_threshold_low_valid; + int temp_threshold_low; + u8 seq_num_valid; + u32 seq_num; +}; +#define TS_REGISTER_NOTIFICATION_TEMP_REQ_MSG_V02_MAX_MSG_LEN 61 +extern struct qmi_elem_info ts_register_notification_temp_req_msg_v02_ei[]; + +struct ts_register_notification_temp_resp_msg_v02 { + struct qmi_response_type_v01 resp; +}; +#define TS_REGISTER_NOTIFICATION_TEMP_RESP_MSG_V02_MAX_MSG_LEN 7 +extern struct qmi_elem_info ts_register_notification_temp_resp_msg_v02_ei[]; + +enum ts_temp_report_type_enum_v02 { + TS_TEMP_REPORT_TYPE_ENUM_MIN_VAL_V02 = INT_MIN, + QMI_TS_TEMP_REPORT_CURRENT_TEMP_V02 = 0, + QMI_TS_TEMP_REPORT_THRESHOLD_HIGH_V02 = 1, + QMI_TS_TEMP_REPORT_THRESHOLD_LOW_V02 = 2, + TS_TEMP_REPORT_TYPE_ENUM_MAX_VAL_V02 = INT_MAX, +}; + +struct ts_temp_report_ind_msg_v02 { + struct ts_sensor_type_v02 sensor_id; + enum ts_temp_report_type_enum_v02 report_type; + u8 temp_valid; + int temp; + u8 seq_num_valid; + u32 seq_num; +}; +#define TS_TEMP_REPORT_IND_MSG_V02_MAX_MSG_LEN 57 +extern struct qmi_elem_info ts_temp_report_ind_msg_v02_ei[]; + +#endif From b28c171a2a3471e4808383026161ae76b39f305a Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 30 Mar 2022 01:03:02 +0530 Subject: [PATCH 07/17] driver: thermal: qcom: clear all mitigation when thermal zone is disabled Whenever a thermal zone is in trip violated state, there is a chance that the same thermal zone mode can be disabled either via thermal core API or via thermal zone sysfs. But thermal core doesn't guarantee that it will clear all mitigation on disabling thermal zone. Monitor thermal zone mode_change callback in different sensor drivers and add support to clear all mitigation on thermal zone disablement. Change-Id: I82720be5b1b9016d218f9a1c39315da794a2a330 Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/thermal/qcom/bcl_pmic5.c | 3 ++ drivers/thermal/qcom/bcl_soc.c | 6 ++- drivers/thermal/qcom/policy_engine.c | 4 ++ drivers/thermal/qcom/qmi_sensors_v2.c | 5 +- drivers/thermal/qcom/thermal_zone_internal.h | 50 ++++++++++++++++++++ drivers/thermal/qcom/tsens.c | 8 +++- 6 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 drivers/thermal/qcom/thermal_zone_internal.h diff --git a/drivers/thermal/qcom/bcl_pmic5.c b/drivers/thermal/qcom/bcl_pmic5.c index da2cf3478d48..2efe7dd7c1fa 100644 --- a/drivers/thermal/qcom/bcl_pmic5.c +++ b/drivers/thermal/qcom/bcl_pmic5.c @@ -22,6 +22,7 @@ #include #include #include +#include "thermal_zone_internal.h" #define BCL_DRIVER_NAME "bcl_pmic5" #define BCL_MONITOR_EN 0x46 @@ -799,6 +800,7 @@ static void bcl_lvl_init(struct platform_device *pdev, return; } thermal_zone_device_update(lbat->tz_dev, THERMAL_DEVICE_UP); + qti_update_tz_ops(lbat->tz_dev, true); } static void bcl_probe_lvls(struct platform_device *pdev, @@ -856,6 +858,7 @@ static int bcl_remove(struct platform_device *pdev) continue; thermal_zone_of_sensor_unregister(&pdev->dev, bcl_perph->param[i].tz_dev); + qti_update_tz_ops(bcl_perph->param[i].tz_dev, false); } return 0; diff --git a/drivers/thermal/qcom/bcl_soc.c b/drivers/thermal/qcom/bcl_soc.c index a8dbfd3868d7..d3caa67f7808 100644 --- a/drivers/thermal/qcom/bcl_soc.c +++ b/drivers/thermal/qcom/bcl_soc.c @@ -16,6 +16,7 @@ #include #include #include +#include "thermal_zone_internal.h" #define BCL_DRIVER_NAME "bcl_soc_peripheral" @@ -119,9 +120,11 @@ static int bcl_soc_remove(struct platform_device *pdev) { power_supply_unreg_notifier(&bcl_perph->psy_nb); flush_work(&bcl_perph->soc_eval_work); - if (bcl_perph->tz_dev) + if (bcl_perph->tz_dev) { thermal_zone_of_sensor_unregister(&pdev->dev, bcl_perph->tz_dev); + qti_update_tz_ops(bcl_perph->tz_dev, false); + } return 0; } @@ -156,6 +159,7 @@ static int bcl_soc_probe(struct platform_device *pdev) bcl_perph->tz_dev = NULL; goto bcl_soc_probe_exit; } + qti_update_tz_ops(bcl_perph->tz_dev, true); thermal_zone_device_update(bcl_perph->tz_dev, THERMAL_DEVICE_UP); schedule_work(&bcl_perph->soc_eval_work); diff --git a/drivers/thermal/qcom/policy_engine.c b/drivers/thermal/qcom/policy_engine.c index edd128242428..bb2b0f7fdbfa 100644 --- a/drivers/thermal/qcom/policy_engine.c +++ b/drivers/thermal/qcom/policy_engine.c @@ -12,6 +12,7 @@ #include #include #include +#include "thermal_zone_internal.h" #define PE_SENS_DRIVER "policy-engine-sensor" #define PE_INT_ENABLE_OFFSET 0x530 @@ -168,6 +169,8 @@ static int pe_sens_device_probe(struct platform_device *pdev) pe_sens->tz_dev = NULL; return ret; } + qti_update_tz_ops(pe_sens->tz_dev, true); + writel_relaxed(PE_INTR_CFG, pe_sens->regmap + PE_INT_ENABLE_OFFSET); writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET); writel_relaxed(PE_STS_CLEAR, pe_sens->regmap + PE_INT_STATUS1_OFFSET); @@ -194,6 +197,7 @@ static int pe_sens_device_remove(struct platform_device *pdev) (struct pe_sensor_data *)dev_get_drvdata(&pdev->dev); thermal_zone_of_sensor_unregister(pe_sens->dev, pe_sens->tz_dev); + qti_update_tz_ops(pe_sens->tz_dev, false); return 0; } diff --git a/drivers/thermal/qcom/qmi_sensors_v2.c b/drivers/thermal/qcom/qmi_sensors_v2.c index 3c2ec59c137b..e5918f721332 100644 --- a/drivers/thermal/qcom/qmi_sensors_v2.c +++ b/drivers/thermal/qcom/qmi_sensors_v2.c @@ -19,6 +19,7 @@ #include "thermal_sensor_service_v02.h" #include "qmi_sensors.h" +#include "thermal_zone_internal.h" #define QMI_SENS_DRIVER "qmi-therm-sensors-v2" #define QMI_TS_RESP_TOUT msecs_to_jiffies(100) @@ -292,8 +293,9 @@ static int qmi_register_sensor_device(struct qmi_sensor *qmi_sens) qmi_sens->tz_dev = NULL; return ret; } - pr_debug("Sensor register success for %s\n", qmi_sens->qmi_name); + qti_update_tz_ops(qmi_sens->tz_dev, true); + pr_debug("Sensor register success for %s\n", qmi_sens->qmi_name); return 0; } @@ -472,6 +474,7 @@ static void qmi_ts_cleanup(void) if (qmi_sens->tz_dev) { thermal_zone_of_sensor_unregister( qmi_sens->dev, qmi_sens->tz_dev); + qti_update_tz_ops(qmi_sens->tz_dev, false); qmi_sens->tz_dev = NULL; } diff --git a/drivers/thermal/qcom/thermal_zone_internal.h b/drivers/thermal/qcom/thermal_zone_internal.h new file mode 100644 index 000000000000..a037c2a167b0 --- /dev/null +++ b/drivers/thermal/qcom/thermal_zone_internal.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __QTI_THERMAL_ZONE_INTERNAL_H +#define __QTI_THERMAL_ZONE_INTERNAL_H + +#include +#include "../thermal_core.h" + +/* Generic helpers for thermal zone -> change_mode ops */ +static inline int qti_tz_change_mode(struct thermal_zone_device *tz, + enum thermal_device_mode mode) +{ + struct thermal_instance *instance; + + tz->passive = 0; + tz->temperature = THERMAL_TEMP_INVALID; + tz->prev_low_trip = -INT_MAX; + tz->prev_high_trip = INT_MAX; + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + instance->initialized = false; + if (mode == THERMAL_DEVICE_DISABLED) { + instance->target = THERMAL_NO_TARGET; + instance->cdev->updated = false; + thermal_cdev_update(instance->cdev); + } + } + + return 0; +} + +static inline int qti_update_tz_ops(struct thermal_zone_device *tz, bool enable) +{ + if (!tz || !tz->ops) + return -EINVAL; + + mutex_lock(&tz->lock); + if (enable) { + if (!tz->ops->change_mode) + tz->ops->change_mode = qti_tz_change_mode; + } else { + tz->ops->change_mode = NULL; + } + mutex_unlock(&tz->lock); + return 0; +} + +#endif // __QTI_THERMAL_ZONE_INTERNAL_H diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 93020c395357..70cfad1ecfe5 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -2,7 +2,7 @@ /* * Copyright (c) 2015, The Linux Foundation. All rights reserved. * Copyright (c) 2019, 2020, Linaro Ltd. - * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. */ #include @@ -21,6 +21,7 @@ #include #include "../thermal_hwmon.h" #include "tsens.h" +#include "thermal_zone_internal.h" /** * struct tsens_irq_data - IRQ status and temperature violations @@ -1085,6 +1086,7 @@ static int tsens_register(struct tsens_priv *priv) if (devm_thermal_add_hwmon_sysfs(tzd)) dev_warn(priv->dev, "Failed to add hwmon sysfs attributes\n"); + qti_update_tz_ops(tzd, true); } /* VER_0 require to set MIN and MAX THRESH @@ -1189,12 +1191,16 @@ static int tsens_probe(struct platform_device *pdev) static int tsens_remove(struct platform_device *pdev) { struct tsens_priv *priv = platform_get_drvdata(pdev); + int i; debugfs_remove_recursive(priv->debug_root); tsens_disable_irq(priv); if (priv->ops->disable) priv->ops->disable(priv); + for (i = 0; i < priv->num_sensors; i++) + if (priv->sensor[i].tzd) + qti_update_tz_ops(priv->sensor[i].tzd, false); return 0; } From c4de8507982e294e6e25a2812c913096153e76cb Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Fri, 27 May 2022 22:58:25 +0530 Subject: [PATCH 08/17] driver: thermal: bcl-soc: Add support for trend estimation Add support for trend estimation callback. Since BCL soc update is happening in software worker thread and soc stays longer duration in same state compared to thermal sensor. It leads to a case where before handling thermal zone update completely on threshold update, there is a chance that another thermal zone update context can happen in parallel due to userspace clients update or due to polling thread context. This causes the trend stays stable for longer duration. This will not help in mitigating using step wise algorithm. Adding this trend estimation callback will return a raising trend during bcl soc trip high violation, a dropping trend during trip low vilation and fallback to default core trend estimation for otherwise. This will help step wise algorithm to determine the next mitigation actions. Change-Id: Ib59eadbb497cf5775bda4888e0d93b3378d46798 Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/thermal/qcom/bcl_soc.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/thermal/qcom/bcl_soc.c b/drivers/thermal/qcom/bcl_soc.c index d3caa67f7808..eeab5b29776e 100644 --- a/drivers/thermal/qcom/bcl_soc.c +++ b/drivers/thermal/qcom/bcl_soc.c @@ -34,6 +34,39 @@ struct bcl_device { static struct bcl_device *bcl_perph; +static int bcl_soc_get_trend(void *data, int trip, + enum thermal_trend *trend) +{ + struct bcl_device *bcl_perph = data; + struct thermal_zone_device *tz = bcl_perph->tz_dev; + int trip_temp = 0, trip_hyst = 0, temp, ret; + + if (!tz) + return -EINVAL; + + ret = tz->ops->get_trip_temp(tz, trip, &trip_temp); + if (ret) + return ret; + + if (tz->ops->get_trip_hyst) { + ret = tz->ops->get_trip_hyst(tz, trip, &trip_hyst); + if (ret) + return ret; + } + temp = READ_ONCE(tz->temperature); + + if (temp >= trip_temp) + *trend = THERMAL_TREND_RAISING; + else if (!trip_hyst && temp < trip_temp) + *trend = THERMAL_TREND_DROPPING; + else if (temp <= (trip_temp - trip_hyst)) + *trend = THERMAL_TREND_DROPPING; + else + *trend = THERMAL_TREND_STABLE; + + return 0; +} + static int bcl_set_soc(void *data, int low, int high) { if (high == bcl_perph->trip_temp) @@ -141,6 +174,7 @@ static int bcl_soc_probe(struct platform_device *pdev) bcl_perph->dev = &pdev->dev; bcl_perph->ops.get_temp = bcl_read_soc; bcl_perph->ops.set_trips = bcl_set_soc; + bcl_perph->ops.get_trend = bcl_soc_get_trend; INIT_WORK(&bcl_perph->soc_eval_work, bcl_evaluate_soc); bcl_perph->psy_nb.notifier_call = battery_supply_callback; ret = power_supply_reg_notifier(&bcl_perph->psy_nb); From c1d32883d28c438c7f64d9a3da81fa01637b2e0a Mon Sep 17 00:00:00 2001 From: Rashid Zafar Date: Fri, 4 Nov 2022 15:34:21 -0700 Subject: [PATCH 09/17] drivers: thermal: qmi_cooling: Add qmi cooling driver snapshot This is a snapshot taken of qmi_cooling driver from msm-5.15 commit e40c031f04e1 ("drivers: thermal: qmi_cooling: Add support for extended device list"). Updates: - Change GPL v2 license to GPL - Added qmi cooling driver description to kernel config - Updated Makefile Change-Id: Ia4a95cc2a85348d27691c4e52513e996dd1a5990 Signed-off-by: Rashid Zafar --- drivers/thermal/qcom/Kconfig | 11 + drivers/thermal/qcom/Makefile | 5 +- drivers/thermal/qcom/qmi_cooling.c | 621 ++++++++++++++++++ .../thermal_mitigation_device_service_v01.c | 386 +++++++++++ .../thermal_mitigation_device_service_v01.h | 126 ++++ 5 files changed, 1148 insertions(+), 1 deletion(-) create mode 100644 drivers/thermal/qcom/qmi_cooling.c create mode 100644 drivers/thermal/qcom/thermal_mitigation_device_service_v01.c create mode 100644 drivers/thermal/qcom/thermal_mitigation_device_service_v01.h diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 494fa9956531..b6374f8c8b33 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -82,6 +82,17 @@ config QTI_POLICY_ENGINE_SENSOR a sensor. This will enable to provide configuration to mitigate cooling devices when a recommendation is sent from hardware. +config QTI_QMI_COOLING_DEVICE + tristate "QTI QMI cooling devices" + depends on QCOM_QMI_HELPERS && THERMAL + help + This enables the QTI remote subsystem cooling devices. These cooling + devices will be used by QTI chipset to place various remote + subsystem mitigations like remote processor passive mitigation, + remote subsystem voltage restriction at low temperatures etc. + The QMI cooling device will interface with remote subsystem + using QTI QMI interface. + config QTI_QMI_SENSOR_V2 tristate "QTI QMI TS V2 sensor driver" depends on THERMAL_OF diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 5d5182621e2e..75ac74d2beb2 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only 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_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o @@ -11,5 +11,8 @@ obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o obj-$(CONFIG_QTI_POLICY_ENGINE_SENSOR) += policy_engine.o +obj-$(CONFIG_QTI_QMI_COOLING_DEVICE) += qti_qmi_cdev.o +qti_qmi_cdev-y += thermal_mitigation_device_service_v01.o qmi_cooling.o + obj-$(CONFIG_QTI_QMI_SENSOR_V2) += qti_qmi_sensor_v2.o qti_qmi_sensor_v2-y += thermal_sensor_service_v02.o qmi_sensors_v2.o diff --git a/drivers/thermal/qcom/qmi_cooling.c b/drivers/thermal/qcom/qmi_cooling.c new file mode 100644 index 000000000000..46e81de4dd7c --- /dev/null +++ b/drivers/thermal/qcom/qmi_cooling.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "thermal_mitigation_device_service_v01.h" + +#define QMI_CDEV_DRIVER "qmi-cooling-device" +#define QMI_TMD_RESP_TOUT msecs_to_jiffies(100) +#define QMI_CLIENT_NAME_LENGTH 40 + +struct qmi_cooling_device { + struct device_node *np; + char cdev_name[THERMAL_NAME_LENGTH]; + char qmi_name[QMI_CLIENT_NAME_LENGTH]; + bool connection_active; + struct list_head qmi_node; + struct thermal_cooling_device *cdev; + unsigned int mtgn_state; + unsigned int max_level; + struct qmi_tmd_instance *tmd; +}; + +struct qmi_tmd_instance { + struct device *dev; + struct qmi_handle handle; + struct mutex mutex; + uint32_t inst_id; + struct list_head tmd_cdev_list; + struct work_struct svc_arrive_work; +}; + +static struct qmi_tmd_instance *tmd_instances; +static int tmd_inst_cnt; + +static char device_clients[][QMI_CLIENT_NAME_LENGTH] = { + {"pa"}, + {"pa_fr1"}, + {"cx_vdd_limit"}, + {"modem"}, + {"modem_current"}, + {"modem_skin"}, + {"modem_bw"}, + {"modem_bw_backoff"}, + {"vbatt_low"}, + {"charge_state"}, + {"mmw0"}, + {"mmw1"}, + {"mmw2"}, + {"mmw3"}, + {"mmw_skin0"}, + {"mmw_skin1"}, + {"mmw_skin2"}, + {"mmw_skin3"}, + {"wlan"}, + {"wlan_bw"}, + {"mmw_skin0_dsc"}, + {"mmw_skin1_dsc"}, + {"mmw_skin2_dsc"}, + {"mmw_skin3_dsc"}, + {"modem_skin_lte_dsc"}, + {"modem_skin_nr_dsc"}, + {"pa_dsc"}, + {"pa_fr1_dsc"}, + {"cdsp_sw"}, + {"cdsp_hw"}, + {"cpuv_restriction_cold"}, + {"cpr_cold"}, + {"modem_lte_dsc"}, + {"modem_nr_dsc"}, + {"modem_nr_scg_dsc"}, + {"sdr0_lte_dsc"}, + {"sdr1_lte_dsc"}, + {"sdr0_nr_dsc"}, + {"sdr1_nr_dsc"}, + {"pa_lte_sdr0_dsc"}, + {"pa_lte_sdr1_dsc"}, + {"pa_nr_sdr0_dsc"}, + {"pa_nr_sdr1_dsc"}, + {"pa_nr_sdr0_scg_dsc"}, + {"pa_nr_sdr1_scg_dsc"}, + {"mmw0_dsc"}, + {"mmw1_dsc"}, + {"mmw2_dsc"}, + {"mmw3_dsc"}, + {"mmw_ific_dsc"}, + {"modem_lte_sub1_dsc"}, + {"modem_nr_sub1_dsc"}, + {"modem_nr_scg_sub1_dsc"}, + {"sdr0_lte_sub1_dsc"}, + {"sdr1_lte_sub1_dsc"}, + {"sdr0_nr_sub1_dsc"}, + {"sdr1_nr_sub1_dsc"}, + {"pa_lte_sdr0_sub1_dsc"}, + {"pa_lte_sdr1_sub1_dsc"}, + {"pa_nr_sdr0_sub1_dsc"}, + {"pa_nr_sdr1_sub1_dsc"}, + {"pa_nr_sdr0_scg_sub1_dsc"}, + {"pa_nr_sdr1_scg_sub1_dsc"}, + {"mmw0_sub1_dsc"}, + {"mmw1_sub1_dsc"}, + {"mmw2_sub1_dsc"}, + {"mmw3_sub1_dsc"}, + {"mmw_ific_sub1_dsc"}, +}; + +static int qmi_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct qmi_cooling_device *qmi_cdev = cdev->devdata; + + if (!qmi_cdev) + return -EINVAL; + + *state = qmi_cdev->max_level; + + return 0; +} + +static int qmi_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct qmi_cooling_device *qmi_cdev = cdev->devdata; + + if (!qmi_cdev) + return -EINVAL; + + *state = qmi_cdev->mtgn_state; + + return 0; +} + +static int qmi_tmd_send_state_request(struct qmi_cooling_device *qmi_cdev, + uint8_t state) +{ + int ret = 0; + struct tmd_set_mitigation_level_req_msg_v01 req; + struct tmd_set_mitigation_level_resp_msg_v01 tmd_resp; + struct qmi_tmd_instance *tmd = qmi_cdev->tmd; + struct qmi_txn txn; + + memset(&req, 0, sizeof(req)); + memset(&tmd_resp, 0, sizeof(tmd_resp)); + + strscpy(req.mitigation_dev_id.mitigation_dev_id, qmi_cdev->qmi_name, + QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01); + req.mitigation_level = state; + + mutex_lock(&tmd->mutex); + + ret = qmi_txn_init(&tmd->handle, &txn, + tmd_set_mitigation_level_resp_msg_v01_ei, &tmd_resp); + if (ret < 0) { + pr_err("qmi set state:%d txn init failed for %s ret:%d\n", + state, qmi_cdev->cdev_name, ret); + goto qmi_send_exit; + } + + ret = qmi_send_request(&tmd->handle, NULL, &txn, + QMI_TMD_SET_MITIGATION_LEVEL_REQ_V01, + TMD_SET_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN, + tmd_set_mitigation_level_req_msg_v01_ei, &req); + if (ret < 0) { + pr_err("qmi set state:%d txn send failed for %s ret:%d\n", + state, qmi_cdev->cdev_name, ret); + qmi_txn_cancel(&txn); + goto qmi_send_exit; + } + + ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TOUT); + if (ret < 0) { + pr_err("qmi set state:%d txn wait failed for %s ret:%d\n", + state, qmi_cdev->cdev_name, ret); + goto qmi_send_exit; + } + if (tmd_resp.resp.result != QMI_RESULT_SUCCESS_V01) { + ret = tmd_resp.resp.result; + pr_err("qmi set state:%d NOT success for %s ret:%d\n", + state, qmi_cdev->cdev_name, ret); + goto qmi_send_exit; + } + ret = 0; + pr_debug("Requested qmi state:%d for %s\n", state, qmi_cdev->cdev_name); + +qmi_send_exit: + mutex_unlock(&tmd->mutex); + return ret; +} + +static int qmi_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct qmi_cooling_device *qmi_cdev = cdev->devdata; + int ret = 0; + + if (!qmi_cdev) + return -EINVAL; + + if (state > qmi_cdev->max_level) + return -EINVAL; + + if (qmi_cdev->mtgn_state == state) + return 0; + + /* save it and return if server exit */ + if (!qmi_cdev->connection_active) { + qmi_cdev->mtgn_state = state; + pr_debug("Pending request:%ld for %s\n", state, + qmi_cdev->cdev_name); + return 0; + } + + /* It is best effort to save state even if QMI fail */ + ret = qmi_tmd_send_state_request(qmi_cdev, (uint8_t)state); + + qmi_cdev->mtgn_state = state; + + return ret; +} + +static struct thermal_cooling_device_ops qmi_device_ops = { + .get_max_state = qmi_get_max_state, + .get_cur_state = qmi_get_cur_state, + .set_cur_state = qmi_set_cur_state, +}; + +static int qmi_register_cooling_device(struct qmi_cooling_device *qmi_cdev) +{ + qmi_cdev->cdev = thermal_of_cooling_device_register( + qmi_cdev->np, + qmi_cdev->cdev_name, + qmi_cdev, + &qmi_device_ops); + if (IS_ERR(qmi_cdev->cdev)) { + pr_err("Cooling register failed for %s, ret:%ld\n", + qmi_cdev->cdev_name, PTR_ERR(qmi_cdev->cdev)); + return PTR_ERR(qmi_cdev->cdev); + } + pr_debug("Cooling register success for %s\n", qmi_cdev->cdev_name); + + return 0; +} + +static int verify_devices_and_register(struct qmi_tmd_instance *tmd) +{ + struct tmd_get_mitigation_device_list_req_msg_v01 req; + struct tmd_get_mitigation_device_list_resp_msg_v01 *tmd_resp; + int ret = 0, i; + struct qmi_txn txn; + + memset(&req, 0, sizeof(req)); + /* size of tmd_resp is very high, use heap memory rather than stack */ + tmd_resp = kzalloc(sizeof(*tmd_resp), GFP_KERNEL); + if (!tmd_resp) + return -ENOMEM; + + mutex_lock(&tmd->mutex); + ret = qmi_txn_init(&tmd->handle, &txn, + tmd_get_mitigation_device_list_resp_msg_v01_ei, tmd_resp); + if (ret < 0) { + pr_err("Transaction Init error for inst_id:0x%x ret:%d\n", + tmd->inst_id, ret); + goto reg_exit; + } + + ret = qmi_send_request(&tmd->handle, NULL, &txn, + QMI_TMD_GET_MITIGATION_DEVICE_LIST_REQ_V01, + TMD_GET_MITIGATION_DEVICE_LIST_REQ_MSG_V01_MAX_MSG_LEN, + tmd_get_mitigation_device_list_req_msg_v01_ei, + &req); + if (ret < 0) { + qmi_txn_cancel(&txn); + goto reg_exit; + } + + ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TOUT); + if (ret < 0) { + pr_err("Transaction wait error for inst_id:0x%x ret:%d\n", + tmd->inst_id, ret); + goto reg_exit; + } + if (tmd_resp->resp.result != QMI_RESULT_SUCCESS_V01) { + ret = tmd_resp->resp.result; + pr_err("Get device list NOT success for inst_id:0x%x ret:%d\n", + tmd->inst_id, ret); + goto reg_exit; + } + mutex_unlock(&tmd->mutex); + + for (i = 0; i < tmd_resp->mitigation_device_list_len; i++) { + struct qmi_cooling_device *qmi_cdev = NULL; + + list_for_each_entry(qmi_cdev, &tmd->tmd_cdev_list, + qmi_node) { + struct tmd_mitigation_dev_list_type_v01 *device = + &tmd_resp->mitigation_device_list[i]; + + if ((strncasecmp(qmi_cdev->qmi_name, + device->mitigation_dev_id.mitigation_dev_id, + QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01))) + continue; + + qmi_cdev->connection_active = true; + qmi_cdev->max_level = device->max_mitigation_level; + /* + * It is better to set current state + * initially or during restart + */ + qmi_tmd_send_state_request(qmi_cdev, + qmi_cdev->mtgn_state); + if (!qmi_cdev->cdev) + ret = qmi_register_cooling_device(qmi_cdev); + break; + } + } + + for (i = 0; tmd_resp->mitigation_device_list_ext01_valid && + i < tmd_resp->mitigation_device_list_ext01_len; i++) { + struct qmi_cooling_device *qmi_cdev = NULL; + + list_for_each_entry(qmi_cdev, &tmd->tmd_cdev_list, + qmi_node) { + struct tmd_mitigation_dev_list_type_v01 *device = + &tmd_resp->mitigation_device_list_ext01[i]; + + if ((strncasecmp(qmi_cdev->qmi_name, + device->mitigation_dev_id.mitigation_dev_id, + QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01))) + continue; + + qmi_cdev->connection_active = true; + qmi_cdev->max_level = device->max_mitigation_level; + /* + * It is better to set current state + * initially or during restart + */ + qmi_tmd_send_state_request(qmi_cdev, + qmi_cdev->mtgn_state); + if (!qmi_cdev->cdev) + ret = qmi_register_cooling_device(qmi_cdev); + break; + } + } + + kfree(tmd_resp); + return ret; + +reg_exit: + mutex_unlock(&tmd->mutex); + kfree(tmd_resp); + + return ret; +} + +static void qmi_tmd_svc_arrive(struct work_struct *work) +{ + struct qmi_tmd_instance *tmd = container_of(work, + struct qmi_tmd_instance, + svc_arrive_work); + + verify_devices_and_register(tmd); +} + +static void thermal_qmi_net_reset(struct qmi_handle *qmi) +{ + struct qmi_tmd_instance *tmd = container_of(qmi, + struct qmi_tmd_instance, + handle); + struct qmi_cooling_device *qmi_cdev = NULL; + + list_for_each_entry(qmi_cdev, &tmd->tmd_cdev_list, + qmi_node) { + if (qmi_cdev->connection_active) + qmi_tmd_send_state_request(qmi_cdev, + qmi_cdev->mtgn_state); + } +} + +static void thermal_qmi_del_server(struct qmi_handle *qmi, + struct qmi_service *service) +{ + struct qmi_tmd_instance *tmd = container_of(qmi, + struct qmi_tmd_instance, + handle); + struct qmi_cooling_device *qmi_cdev = NULL; + + list_for_each_entry(qmi_cdev, &tmd->tmd_cdev_list, qmi_node) + qmi_cdev->connection_active = false; +} + +static int thermal_qmi_new_server(struct qmi_handle *qmi, + struct qmi_service *service) +{ + struct qmi_tmd_instance *tmd = container_of(qmi, + struct qmi_tmd_instance, + handle); + struct sockaddr_qrtr sq = {AF_QIPCRTR, service->node, service->port}; + + mutex_lock(&tmd->mutex); + kernel_connect(qmi->sock, (struct sockaddr *)&sq, sizeof(sq), 0); + mutex_unlock(&tmd->mutex); + queue_work(system_highpri_wq, &tmd->svc_arrive_work); + + return 0; +} + +static struct qmi_ops thermal_qmi_event_ops = { + .new_server = thermal_qmi_new_server, + .del_server = thermal_qmi_del_server, + .net_reset = thermal_qmi_net_reset, +}; + +static void qmi_tmd_cleanup(void) +{ + int idx = 0; + struct qmi_tmd_instance *tmd = tmd_instances; + struct qmi_cooling_device *qmi_cdev, *c_next; + + for (; idx < tmd_inst_cnt; idx++) { + mutex_lock(&tmd[idx].mutex); + list_for_each_entry_safe(qmi_cdev, c_next, + &tmd[idx].tmd_cdev_list, qmi_node) { + qmi_cdev->connection_active = false; + if (qmi_cdev->cdev) + thermal_cooling_device_unregister( + qmi_cdev->cdev); + + list_del(&qmi_cdev->qmi_node); + } + qmi_handle_release(&tmd[idx].handle); + + mutex_unlock(&tmd[idx].mutex); + } +} + +static int of_get_qmi_tmd_platform_data(struct device *dev) +{ + int ret = 0, idx = 0, i = 0, subsys_cnt = 0; + struct device_node *np = dev->of_node; + struct device_node *subsys_np = NULL, *cdev_np = NULL; + struct qmi_tmd_instance *tmd; + struct qmi_cooling_device *qmi_cdev; + + subsys_cnt = of_get_available_child_count(np); + if (!subsys_cnt) { + dev_err(dev, "No child node to process\n"); + return -EFAULT; + } + + tmd = devm_kcalloc(dev, subsys_cnt, sizeof(*tmd), GFP_KERNEL); + if (!tmd) + return -ENOMEM; + + for_each_available_child_of_node(np, subsys_np) { + if (idx >= subsys_cnt) { + of_node_put(subsys_np); + break; + } + + ret = of_property_read_u32(subsys_np, "qcom,instance-id", + &tmd[idx].inst_id); + if (ret) { + dev_err(dev, "error reading qcom,insance-id. ret:%d\n", + ret); + goto data_subsys_error; + } + + tmd[idx].dev = dev; + mutex_init(&tmd[idx].mutex); + INIT_LIST_HEAD(&tmd[idx].tmd_cdev_list); + INIT_WORK(&tmd[idx].svc_arrive_work, qmi_tmd_svc_arrive); + + for_each_available_child_of_node(subsys_np, cdev_np) { + const char *qmi_name; + + qmi_cdev = devm_kzalloc(dev, sizeof(*qmi_cdev), + GFP_KERNEL); + if (!qmi_cdev) { + ret = -ENOMEM; + goto data_error; + } + + strscpy(qmi_cdev->cdev_name, cdev_np->name, + THERMAL_NAME_LENGTH); + + if (!of_property_read_string(cdev_np, + "qcom,qmi-dev-name", + &qmi_name)) { + strscpy(qmi_cdev->qmi_name, qmi_name, + QMI_CLIENT_NAME_LENGTH); + } else { + dev_err(dev, "Fail to parse dev name for %s\n", + cdev_np->name); + break; + } + /* Check for supported qmi dev*/ + for (i = 0; i < ARRAY_SIZE(device_clients); i++) { + if (strcmp(device_clients[i], + qmi_cdev->qmi_name) == 0) + break; + } + + if (i >= ARRAY_SIZE(device_clients)) { + dev_err(dev, "Not supported dev name for %s\n", + cdev_np->name); + break; + } + qmi_cdev->tmd = &tmd[idx]; + qmi_cdev->np = cdev_np; + qmi_cdev->mtgn_state = 0; + list_add(&qmi_cdev->qmi_node, &tmd[idx].tmd_cdev_list); + } + idx++; + } + of_node_put(np); + tmd_instances = tmd; + tmd_inst_cnt = subsys_cnt; + + return 0; +data_error: + of_node_put(cdev_np); +data_subsys_error: + of_node_put(subsys_np); + of_node_put(np); + + return ret; +} + +static int qmi_device_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret = 0, idx = 0; + + ret = of_get_qmi_tmd_platform_data(dev); + if (ret) + goto probe_err; + + if (!tmd_instances || !tmd_inst_cnt) { + dev_err(dev, "Empty tmd instances\n"); + return -EINVAL; + } + + for (; idx < tmd_inst_cnt; idx++) { + struct qmi_tmd_instance *tmd = &tmd_instances[idx]; + + if (list_empty(&tmd->tmd_cdev_list)) + continue; + + ret = qmi_handle_init(&tmd->handle, + TMD_GET_MITIGATION_DEVICE_LIST_RESP_MSG_V01_MAX_MSG_LEN, + &thermal_qmi_event_ops, NULL); + if (ret < 0) { + dev_err(dev, "QMI[0x%x] handle init failed. err:%d\n", + tmd->inst_id, ret); + tmd_inst_cnt = idx; + goto probe_err; + } + ret = qmi_add_lookup(&tmd->handle, TMD_SERVICE_ID_V01, + TMD_SERVICE_VERS_V01, + tmd->inst_id); + if (ret < 0) { + dev_err(dev, "QMI register failed for 0x%x, ret:%d\n", + tmd->inst_id, ret); + goto probe_err; + } + } + + return 0; + +probe_err: + qmi_tmd_cleanup(); + return ret; +} + +static int qmi_device_remove(struct platform_device *pdev) +{ + qmi_tmd_cleanup(); + + return 0; +} + +static const struct of_device_id qmi_device_match[] = { + {.compatible = "qcom,qmi-cooling-devices"}, + {} +}; + +static struct platform_driver qmi_device_driver = { + .probe = qmi_device_probe, + .remove = qmi_device_remove, + .driver = { + .name = QMI_CDEV_DRIVER, + .of_match_table = qmi_device_match, + }, +}; + +static int __init qmi_device_init(void) +{ + return platform_driver_register(&qmi_device_driver); +} +module_init(qmi_device_init); + +static void __exit qmi_device_exit(void) +{ + platform_driver_unregister(&qmi_device_driver); +} +module_exit(qmi_device_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("QTI QMI cooling device driver"); diff --git a/drivers/thermal/qcom/thermal_mitigation_device_service_v01.c b/drivers/thermal/qcom/thermal_mitigation_device_service_v01.c new file mode 100644 index 000000000000..96fd8bac3ebb --- /dev/null +++ b/drivers/thermal/qcom/thermal_mitigation_device_service_v01.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include + +#include "thermal_mitigation_device_service_v01.h" + +static struct qmi_elem_info tmd_mitigation_dev_id_type_v01_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01 + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof( + struct tmd_mitigation_dev_id_type_v01, + mitigation_dev_id), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct qmi_elem_info tmd_mitigation_dev_list_type_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof( + struct tmd_mitigation_dev_list_type_v01, + mitigation_dev_id), + .ei_array = tmd_mitigation_dev_id_type_v01_ei, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof( + struct tmd_mitigation_dev_list_type_v01, + max_mitigation_level), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info tmd_get_mitigation_device_list_req_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info tmd_get_mitigation_device_list_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct tmd_get_mitigation_device_list_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct tmd_get_mitigation_device_list_resp_msg_v01, + mitigation_device_list_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct tmd_get_mitigation_device_list_resp_msg_v01, + mitigation_device_list_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_TMD_MITIGATION_DEV_LIST_MAX_V01, + .elem_size = sizeof( + struct tmd_mitigation_dev_list_type_v01), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct tmd_get_mitigation_device_list_resp_msg_v01, + mitigation_device_list), + .ei_array = tmd_mitigation_dev_list_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct tmd_get_mitigation_device_list_resp_msg_v01, + mitigation_device_list_ext01_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct tmd_get_mitigation_device_list_resp_msg_v01, + mitigation_device_list_ext01_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_TMD_MITIGATION_DEV_LIST_EXT01_MAX_V01, + .elem_size = sizeof( + struct tmd_mitigation_dev_list_type_v01), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct tmd_get_mitigation_device_list_resp_msg_v01, + mitigation_device_list_ext01), + .ei_array = tmd_mitigation_dev_list_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info tmd_set_mitigation_level_req_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof( + struct tmd_set_mitigation_level_req_msg_v01, + mitigation_dev_id), + .ei_array = tmd_mitigation_dev_id_type_v01_ei, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct tmd_set_mitigation_level_req_msg_v01, + mitigation_level), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info tmd_set_mitigation_level_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct tmd_set_mitigation_level_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info tmd_get_mitigation_level_req_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof( + struct tmd_get_mitigation_level_req_msg_v01, + mitigation_device), + .ei_array = tmd_mitigation_dev_id_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info tmd_get_mitigation_level_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct tmd_get_mitigation_level_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct tmd_get_mitigation_level_resp_msg_v01, + current_mitigation_level_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct tmd_get_mitigation_level_resp_msg_v01, + current_mitigation_level), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct tmd_get_mitigation_level_resp_msg_v01, + requested_mitigation_level_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct tmd_get_mitigation_level_resp_msg_v01, + requested_mitigation_level), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info + tmd_register_notification_mitigation_level_req_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof( + struct tmd_register_notification_mitigation_level_req_msg_v01, + mitigation_device), + .ei_array = tmd_mitigation_dev_id_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info + tmd_register_notification_mitigation_level_resp_msg_v01_ei[] + = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct tmd_register_notification_mitigation_level_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info + tmd_deregister_notification_mitigation_level_req_msg_v01_ei[] + = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + tmd_deregister_notification_mitigation_level_req_msg_v01, + mitigation_device), + .ei_array = tmd_mitigation_dev_id_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info + tmd_deregister_notification_mitigation_level_resp_msg_v01_ei[] + = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + tmd_deregister_notification_mitigation_level_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct qmi_elem_info tmd_mitigation_level_report_ind_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof( + struct tmd_mitigation_level_report_ind_msg_v01, + mitigation_device), + .ei_array = tmd_mitigation_dev_id_type_v01_ei, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct tmd_mitigation_level_report_ind_msg_v01, + current_mitigation_level), + }, + { + .data_type = QMI_EOTI, + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + diff --git a/drivers/thermal/qcom/thermal_mitigation_device_service_v01.h b/drivers/thermal/qcom/thermal_mitigation_device_service_v01.h new file mode 100644 index 000000000000..9e9a3dcc45cf --- /dev/null +++ b/drivers/thermal/qcom/thermal_mitigation_device_service_v01.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef THERMAL_MITIGATION_DEVICE_SERVICE_V01_H +#define THERMAL_MITIGATION_DEVICE_SERVICE_V01_H + +#define TMD_SERVICE_ID_V01 0x18 +#define TMD_SERVICE_VERS_V01 0x01 + +#define QMI_TMD_GET_MITIGATION_DEVICE_LIST_RESP_V01 0x0020 +#define QMI_TMD_GET_MITIGATION_LEVEL_REQ_V01 0x0022 +#define QMI_TMD_GET_SUPPORTED_MSGS_REQ_V01 0x001E +#define QMI_TMD_SET_MITIGATION_LEVEL_REQ_V01 0x0021 +#define QMI_TMD_REGISTER_NOTIFICATION_MITIGATION_LEVEL_RESP_V01 0x0023 +#define QMI_TMD_GET_SUPPORTED_MSGS_RESP_V01 0x001E +#define QMI_TMD_SET_MITIGATION_LEVEL_RESP_V01 0x0021 +#define QMI_TMD_DEREGISTER_NOTIFICATION_MITIGATION_LEVEL_RESP_V01 0x0024 +#define QMI_TMD_MITIGATION_LEVEL_REPORT_IND_V01 0x0025 +#define QMI_TMD_GET_MITIGATION_LEVEL_RESP_V01 0x0022 +#define QMI_TMD_GET_SUPPORTED_FIELDS_REQ_V01 0x001F +#define QMI_TMD_GET_MITIGATION_DEVICE_LIST_REQ_V01 0x0020 +#define QMI_TMD_REGISTER_NOTIFICATION_MITIGATION_LEVEL_REQ_V01 0x0023 +#define QMI_TMD_DEREGISTER_NOTIFICATION_MITIGATION_LEVEL_REQ_V01 0x0024 +#define QMI_TMD_GET_SUPPORTED_FIELDS_RESP_V01 0x001F + +#define QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01 32 +#define QMI_TMD_MITIGATION_DEV_LIST_MAX_V01 32 +#define QMI_TMD_MITIGATION_DEV_LIST_EXT01_MAX_V01 64 + +struct tmd_mitigation_dev_id_type_v01 { + char mitigation_dev_id[QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01 + 1]; +}; + +struct tmd_mitigation_dev_list_type_v01 { + struct tmd_mitigation_dev_id_type_v01 mitigation_dev_id; + uint8_t max_mitigation_level; +}; + +struct tmd_get_mitigation_device_list_req_msg_v01 { + char placeholder; +}; +#define TMD_GET_MITIGATION_DEVICE_LIST_REQ_MSG_V01_MAX_MSG_LEN 0 +extern struct qmi_elem_info tmd_get_mitigation_device_list_req_msg_v01_ei[]; + +struct tmd_get_mitigation_device_list_resp_msg_v01 { + struct qmi_response_type_v01 resp; + uint8_t mitigation_device_list_valid; + uint32_t mitigation_device_list_len; + struct tmd_mitigation_dev_list_type_v01 + mitigation_device_list[QMI_TMD_MITIGATION_DEV_LIST_MAX_V01]; + uint8_t mitigation_device_list_ext01_valid; + uint32_t mitigation_device_list_ext01_len; + struct tmd_mitigation_dev_list_type_v01 + mitigation_device_list_ext01[QMI_TMD_MITIGATION_DEV_LIST_EXT01_MAX_V01]; +}; +#define TMD_GET_MITIGATION_DEVICE_LIST_RESP_MSG_V01_MAX_MSG_LEN 3279 +extern struct qmi_elem_info tmd_get_mitigation_device_list_resp_msg_v01_ei[]; + +struct tmd_set_mitigation_level_req_msg_v01 { + struct tmd_mitigation_dev_id_type_v01 mitigation_dev_id; + uint8_t mitigation_level; +}; +#define TMD_SET_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 40 +extern struct qmi_elem_info tmd_set_mitigation_level_req_msg_v01_ei[]; + +struct tmd_set_mitigation_level_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; +#define TMD_SET_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct qmi_elem_info tmd_set_mitigation_level_resp_msg_v01_ei[]; + +struct tmd_get_mitigation_level_req_msg_v01 { + struct tmd_mitigation_dev_id_type_v01 mitigation_device; +}; +#define TMD_GET_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 36 +extern struct qmi_elem_info tmd_get_mitigation_level_req_msg_v01_ei[]; + +struct tmd_get_mitigation_level_resp_msg_v01 { + struct qmi_response_type_v01 resp; + uint8_t current_mitigation_level_valid; + uint8_t current_mitigation_level; + uint8_t requested_mitigation_level_valid; + uint8_t requested_mitigation_level; +}; +#define TMD_GET_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 15 +extern struct qmi_elem_info tmd_get_mitigation_level_resp_msg_v01_ei[]; + +struct tmd_register_notification_mitigation_level_req_msg_v01 { + struct tmd_mitigation_dev_id_type_v01 mitigation_device; +}; +#define TMD_REGISTER_NOTIFICATION_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 36 +extern struct qmi_elem_info + tmd_register_notification_mitigation_level_req_msg_v01_ei[]; + +struct tmd_register_notification_mitigation_level_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; +#define TMD_REGISTER_NOTIFICATION_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct qmi_elem_info + tmd_register_notification_mitigation_level_resp_msg_v01_ei[]; + +struct tmd_deregister_notification_mitigation_level_req_msg_v01 { + struct tmd_mitigation_dev_id_type_v01 mitigation_device; +}; +#define TMD_DEREGISTER_NOTIFICATION_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 36 +extern struct qmi_elem_info + tmd_deregister_notification_mitigation_level_req_msg_v01_ei[]; + +struct tmd_deregister_notification_mitigation_level_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; +#define TMD_DEREGISTER_NOTIFICATION_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct qmi_elem_info + tmd_deregister_notification_mitigation_level_resp_msg_v01_ei[]; + +struct tmd_mitigation_level_report_ind_msg_v01 { + struct tmd_mitigation_dev_id_type_v01 mitigation_device; + uint8_t current_mitigation_level; +}; +#define TMD_MITIGATION_LEVEL_REPORT_IND_MSG_V01_MAX_MSG_LEN 40 +extern struct qmi_elem_info tmd_mitigation_level_report_ind_msg_v01_ei[]; + +#endif From bffc3e6a201b04b72c578872bfc3be7bc61d64f9 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 16 Feb 2022 16:09:45 +0530 Subject: [PATCH 10/17] drivers: thermal: qmi_cooling: update cooling device name length The thermal framework doesn't have any cooling device name length limit check. So use the same qmi device name length for qmi cooling device as well so that both qmi device name and thermal framework cooling device name will be used the same name. Change-Id: I373107c5eb17852dd98d0fa787839e65284690fa Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/thermal/qcom/qmi_cooling.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/qcom/qmi_cooling.c b/drivers/thermal/qcom/qmi_cooling.c index 46e81de4dd7c..4911ff58c99a 100644 --- a/drivers/thermal/qcom/qmi_cooling.c +++ b/drivers/thermal/qcom/qmi_cooling.c @@ -19,12 +19,11 @@ #define QMI_CDEV_DRIVER "qmi-cooling-device" #define QMI_TMD_RESP_TOUT msecs_to_jiffies(100) -#define QMI_CLIENT_NAME_LENGTH 40 struct qmi_cooling_device { struct device_node *np; - char cdev_name[THERMAL_NAME_LENGTH]; - char qmi_name[QMI_CLIENT_NAME_LENGTH]; + char cdev_name[QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01]; + char qmi_name[QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01]; bool connection_active; struct list_head qmi_node; struct thermal_cooling_device *cdev; @@ -45,7 +44,7 @@ struct qmi_tmd_instance { static struct qmi_tmd_instance *tmd_instances; static int tmd_inst_cnt; -static char device_clients[][QMI_CLIENT_NAME_LENGTH] = { +static char device_clients[][QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01] = { {"pa"}, {"pa_fr1"}, {"cx_vdd_limit"}, @@ -493,13 +492,13 @@ static int of_get_qmi_tmd_platform_data(struct device *dev) } strscpy(qmi_cdev->cdev_name, cdev_np->name, - THERMAL_NAME_LENGTH); + QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01); if (!of_property_read_string(cdev_np, "qcom,qmi-dev-name", &qmi_name)) { strscpy(qmi_cdev->qmi_name, qmi_name, - QMI_CLIENT_NAME_LENGTH); + QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01); } else { dev_err(dev, "Fail to parse dev name for %s\n", cdev_np->name); From 4b31fa9c520c5ce051822b5f8f9d4a85b9e19195 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Fri, 11 Mar 2022 11:10:52 +0530 Subject: [PATCH 11/17] drivers: thermal: Add support for new sdr scg modem qmi cooling devices Add support for new sdr scg modem qmi cooling devices to QMI cooling device driver. These cooling devices can be used to mitigate modem subsystem at high temperature via qmi interface. Change-Id: I6503e75d53dc0456dd90401a6a1af02c10fa4e41 Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/thermal/qcom/qmi_cooling.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/thermal/qcom/qmi_cooling.c b/drivers/thermal/qcom/qmi_cooling.c index 4911ff58c99a..2ab23347a753 100644 --- a/drivers/thermal/qcom/qmi_cooling.c +++ b/drivers/thermal/qcom/qmi_cooling.c @@ -84,6 +84,8 @@ static char device_clients[][QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01] = { {"sdr1_lte_dsc"}, {"sdr0_nr_dsc"}, {"sdr1_nr_dsc"}, + {"sdr0_nr_scg_dsc"}, + {"sdr1_nr_scg_dsc"}, {"pa_lte_sdr0_dsc"}, {"pa_lte_sdr1_dsc"}, {"pa_nr_sdr0_dsc"}, From 6034c5188ba8f354c21594582ed951a1be8d578e Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Thu, 10 Feb 2022 16:08:51 +0530 Subject: [PATCH 12/17] drivers: thermal: qmi_sensor: Add modem epm sensors to QMI TS Add support for modem epm sensors to QMI TS sensors list. It enables apps to monitor these sensors from thermal framework. Change-Id: Ibf80aefa002f8a3cc85ac23b337069103fe2245e Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/thermal/qcom/qmi_sensors.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/thermal/qcom/qmi_sensors.h b/drivers/thermal/qcom/qmi_sensors.h index 776c18d81052..657f38a88e81 100644 --- a/drivers/thermal/qcom/qmi_sensors.h +++ b/drivers/thermal/qcom/qmi_sensors.h @@ -74,6 +74,14 @@ enum qmi_ts_sensor { QMI_TS_SUB2_SCG_FR1_CC, QMI_TS_SUB2_SCG_FR2_CC, QMI_TS_NSP_ISENSE_TRIM, + QMI_TS_EPM0, + QMI_TS_EPM1, + QMI_TS_EPM2, + QMI_TS_EPM3, + QMI_TS_EPM4, + QMI_TS_EPM5, + QMI_TS_EPM6, + QMI_TS_EPM7, QMI_TS_MAX_NR }; @@ -142,6 +150,14 @@ static char sensor_clients[QMI_TS_MAX_NR][QMI_CLIENT_NAME_LENGTH] = { {"sub2_scg_fr1_cc"}, {"sub2_scg_fr2_cc"}, {"isense_trim"}, + {"epm0"}, + {"epm1"}, + {"epm2"}, + {"epm3"}, + {"epm4"}, + {"epm5"}, + {"epm6"}, + {"epm7"}, }; #endif /* __QMI_SENSORS_H__ */ From 7d40460e376268e1d5852045b226f86b8769da8c Mon Sep 17 00:00:00 2001 From: Priyansh Jain Date: Wed, 22 Jun 2022 17:47:59 +0530 Subject: [PATCH 13/17] drivers: thermal: qmi_sensor: Add new sdr thermistors Add new sdr thermistors support. The support includes for the qmi sensors sdr0_pa, sdr1_pa. Change-Id: If6019170095d55e41c617be14865f5079390430b Signed-off-by: Priyansh Jain --- drivers/thermal/qcom/qmi_sensors.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/thermal/qcom/qmi_sensors.h b/drivers/thermal/qcom/qmi_sensors.h index 657f38a88e81..eed7a55e2c70 100644 --- a/drivers/thermal/qcom/qmi_sensors.h +++ b/drivers/thermal/qcom/qmi_sensors.h @@ -82,6 +82,8 @@ enum qmi_ts_sensor { QMI_TS_EPM5, QMI_TS_EPM6, QMI_TS_EPM7, + QMI_TS_SDR0_PA, + QMI_TS_SDR1_PA, QMI_TS_MAX_NR }; @@ -158,6 +160,8 @@ static char sensor_clients[QMI_TS_MAX_NR][QMI_CLIENT_NAME_LENGTH] = { {"epm5"}, {"epm6"}, {"epm7"}, + {"sdr0_pa"}, + {"sdr1_pa"}, }; #endif /* __QMI_SENSORS_H__ */ From 195402de210a5a3c962a434c8a679ca9efe80887 Mon Sep 17 00:00:00 2001 From: Rashid Zafar Date: Fri, 4 Nov 2022 15:34:48 -0700 Subject: [PATCH 14/17] drivers: thermal: cpu_voltage: Add snapshot of cpu_voltage cooling driver This is a snapshot of the cpu_voltage cooling device driver from msm-5.10 as of commit 07b06da60fbb ("drivers: thermal: cpu_voltage: Update the cooling device naming"). Updates: - Change GPL v2 license to GPL Change-Id: I6ffa3773970a279d90e813b3ff88e0afe414308f Signed-off-by: Manaf Meethalavalappu Pallikunhi Signed-off-by: Rashid Zafar --- drivers/thermal/qcom/Kconfig | 10 + drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/cpu_voltage_cooling.c | 367 +++++++++++++++++++++ 3 files changed, 378 insertions(+) create mode 100644 drivers/thermal/qcom/cpu_voltage_cooling.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index b6374f8c8b33..0e17a4ec58c0 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -103,3 +103,13 @@ config QTI_QMI_SENSOR_V2 the remote sensor. These sensors can take thresholds and notify the thermal framework when the threshold is reached. +config QTI_CPU_VOLTAGE_COOLING_DEVICE + tristate "QTI CPU VOLTAGE cooling devices" + depends on CPU_FREQ && THERMAL + help + This enables the QTI CPU Voltage cooling devices. This cooling + device will allow the CPUs with different frequency plan in a + cluster to be mitigated together based on the voltages. This will + decrease or increase the voltages in a cluster based on thermal + conditions. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 75ac74d2beb2..81c7a278bc2b 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -16,3 +16,4 @@ qti_qmi_cdev-y += thermal_mitigation_device_service_v01.o qmi_cooling.o obj-$(CONFIG_QTI_QMI_SENSOR_V2) += qti_qmi_sensor_v2.o qti_qmi_sensor_v2-y += thermal_sensor_service_v02.o qmi_sensors_v2.o +obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o diff --git a/drivers/thermal/qcom/cpu_voltage_cooling.c b/drivers/thermal/qcom/cpu_voltage_cooling.c new file mode 100644 index 000000000000..2d5cbd3a7b4e --- /dev/null +++ b/drivers/thermal/qcom/cpu_voltage_cooling.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CPU_MAP_CT 2 +#define CC_CDEV_DRIVER "CPU-voltage-cdev" + +struct limits_freq_table { + unsigned long frequency; + unsigned long volt; +}; + +struct limits_freq_map { + unsigned long frequency[CPU_MAP_CT]; +}; + +struct cc_limits_data { + struct list_head node; + int map_freq_ct; + int thermal_state; + int cpu_map[CPU_MAP_CT]; + struct limits_freq_map *map_freq; + struct freq_qos_request cc_qos_req[CPU_MAP_CT]; + char cdev_name[THERMAL_NAME_LENGTH]; + struct thermal_cooling_device *cdev; +}; + +static DEFINE_MUTEX(cc_list_lock); +static LIST_HEAD(cc_cdev_list); + +static int cc_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct cc_limits_data *cc_cdev = cdev->devdata; + int idx = 0, ret = 0; + + if (state > cc_cdev->map_freq_ct) + return -EINVAL; + + if (state == cc_cdev->thermal_state) + return 0; + + cc_cdev->thermal_state = state; + + for (idx = 0; idx < CPU_MAP_CT; idx++) { + pr_debug("Mitigate CPU:%d to freq:%lu\n", cc_cdev->cpu_map[idx], + cc_cdev->map_freq[state].frequency[idx]); + ret = freq_qos_update_request(&cc_cdev->cc_qos_req[idx], + cc_cdev->map_freq[state].frequency[idx]); + if (ret < 0) + return ret; + } + return 0; +} + +static int cc_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct cc_limits_data *cc_cdev = cdev->devdata; + + *state = cc_cdev->thermal_state; + + return 0; +} + +static int cc_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct cc_limits_data *cc_cdev = cdev->devdata; + + *state = cc_cdev->map_freq_ct; + + return 0; +} + +static struct thermal_cooling_device_ops cc_cooling_ops = { + .get_max_state = cc_get_max_state, + .get_cur_state = cc_get_cur_state, + .set_cur_state = cc_set_cur_state, +}; + +static int fetch_opp_table(struct device *dev, + struct limits_freq_table **freq_table_inp) +{ + int idx = 0, max_opp_ct; + struct limits_freq_table *freq_table = NULL; + struct dev_pm_opp *opp; + unsigned long freq = 0; + + max_opp_ct = dev_pm_opp_get_opp_count(dev); + if (max_opp_ct <= 0) + return max_opp_ct; + + freq_table = kcalloc(max_opp_ct, sizeof(*freq_table), GFP_KERNEL); + if (!freq_table) + return -ENOMEM; + + for (; idx < max_opp_ct; idx++, freq++) { + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + if (IS_ERR(opp)) { + pr_err("Error fetching freq\n"); + goto fetch_err_exit; + } + freq_table[idx].frequency = freq / 1000; //MHz + freq_table[idx].volt = dev_pm_opp_get_voltage(opp) / 1000; //mV + pr_debug("%d: freq:%lu Mhz volt:%lu mv\n", idx, + freq_table[idx].frequency, + freq_table[idx].volt); + dev_pm_opp_put(opp); + } + *freq_table_inp = freq_table; + + return max_opp_ct; +fetch_err_exit: + kfree(freq_table); + return -EINVAL; +} + +static int build_unified_table(struct cc_limits_data *cc_cdev, + struct limits_freq_table **table, int *table_ct, + int *cpu, int cpu_ct) +{ + struct limits_freq_map *freq_map = NULL; + int idx = 0, idy = 0, idz = 0, min_idx = 0, max_v = 0, max_idx = 0; + + for (idx = 0; idx < cpu_ct; idx++) { + int table_v = table[idx][table_ct[idx] - 1].volt; + + if ((table_v > max_v) || (table_v == max_v && + table_ct[idx] > table_ct[max_idx])) { + max_v = table_v; + max_idx = idx; + } + } + + cc_cdev->thermal_state = 0; + cc_cdev->map_freq_ct = table_ct[max_idx] - 1; + min_idx = !max_idx; + cc_cdev->cpu_map[0] = cpu[max_idx]; + cc_cdev->cpu_map[1] = cpu[min_idx]; + freq_map = kcalloc(table_ct[max_idx], sizeof(*freq_map), GFP_KERNEL); + if (!freq_map) + return -ENOMEM; + pr_info("CPU1:%d CPU2:%d\n", cc_cdev->cpu_map[0], cc_cdev->cpu_map[1]); + for (idx = table_ct[max_idx] - 1, idy = table_ct[min_idx] - 1, idz = 0; + idx >= 0 && idz < table_ct[max_idx]; idx--, idz++) { + int volt = table[max_idx][idx].volt; + + freq_map[idz].frequency[0] = table[max_idx][idx].frequency; + for (; idy >= 0 ; idy--) { + if (table[min_idx][idy].volt <= volt) + break; + } + if (idy < 0) + idy = 0; + freq_map[idz].frequency[1] = table[min_idx][idy].frequency; + pr_info("freq1:%u freq2:%u\n", freq_map[idz].frequency[0], + freq_map[idz].frequency[1]); + } + + cc_cdev->map_freq = freq_map; + return 0; +} + +static struct cc_limits_data *opp_init(int *cpus) +{ + int cpu1, cpu2; + struct device *cpu1_dev, *cpu2_dev; + struct limits_freq_table *cpu1_freq_table, *cpu2_freq_table; + struct limits_freq_table *cpu_freq_table[CPU_MAP_CT]; + int table_ct[CPU_MAP_CT], ret = 0; + struct cc_limits_data *cc_cdev = NULL; + + cpu1 = cpus[0]; + cpu2 = cpus[1]; + cpu1_dev = get_cpu_device(cpu1); + if (!cpu1_dev) { + pr_err("couldn't find cpu:%d\n", cpu1); + return ERR_PTR(-ENODEV); + } + cpu2_dev = get_cpu_device(cpu2); + if (!cpu2_dev) { + pr_err("couldn't find cpu:%d\n", cpu2); + return ERR_PTR(-ENODEV); + } + table_ct[0] = fetch_opp_table(cpu1_dev, &cpu1_freq_table); + if (table_ct[0] <= 0) + goto opp_err_exit; + + table_ct[1] = fetch_opp_table(cpu2_dev, &cpu2_freq_table); + if (table_ct[1] <= 0) + goto opp_err_exit; + + cc_cdev = kzalloc(sizeof(*cc_cdev), GFP_KERNEL); + if (!cc_cdev) + goto opp_err_exit; + cpu_freq_table[0] = cpu1_freq_table; + cpu_freq_table[1] = cpu2_freq_table; + ret = build_unified_table(cc_cdev, cpu_freq_table, table_ct, cpus, + CPU_MAP_CT); + if (ret < 0) + goto opp_err_exit; + + kfree(cpu1_freq_table); + kfree(cpu2_freq_table); + return cc_cdev; +opp_err_exit: + kfree(cpu1_freq_table); + kfree(cpu2_freq_table); + if (cc_cdev) { + kfree(cc_cdev->map_freq); + kfree(cc_cdev); + } + + return ERR_PTR(-EPROBE_DEFER); +} + +static int cc_init(struct device_node *np, int *cpus) +{ + struct cc_limits_data *cc_cdev; + int idx = 0, ret = 0; + struct cpufreq_policy *policy; + + mutex_lock(&cc_list_lock); + list_for_each_entry(cc_cdev, &cc_cdev_list, node) { + if ((cpus[0] == cc_cdev->cpu_map[0] && + cpus[1] == cc_cdev->cpu_map[1]) || + (cpus[0] == cc_cdev->cpu_map[1] && + cpus[1] == cc_cdev->cpu_map[0])) { + mutex_unlock(&cc_list_lock); + return 0; + } + } + policy = cpufreq_cpu_get(cpus[0]); + if (!policy) { + pr_err("No policy for CPU:%d. Defer.\n", cpus[0]); + mutex_unlock(&cc_list_lock); + return -EPROBE_DEFER; + } + if (cpumask_test_cpu(cpus[1], policy->related_cpus)) { + pr_err("CPUs:%d %d are related.\n", cpus[0], cpus[1]); + cpufreq_cpu_put(policy); + mutex_unlock(&cc_list_lock); + return -EINVAL; + } + cpufreq_cpu_put(policy); + + cc_cdev = opp_init(cpus); + if (IS_ERR(cc_cdev)) { + ret = PTR_ERR(cc_cdev); + mutex_unlock(&cc_list_lock); + return ret; + } + for (idx = 0; idx < CPU_MAP_CT; idx++) { + policy = cpufreq_cpu_get(cc_cdev->cpu_map[idx]); + if (!policy) { + pr_err("No policy for CPU:%d\n", cc_cdev->cpu_map[idx]); + ret = -ENODEV; + goto cc_err_exit; + } + ret = freq_qos_add_request(&policy->constraints, + &cc_cdev->cc_qos_req[idx], FREQ_QOS_MAX, + cc_cdev->map_freq[0].frequency[idx]); + cpufreq_cpu_put(policy); + if (ret < 0) { + pr_err("CPU%d Failed to add freq constraint (%d)\n", + cc_cdev->cpu_map[idx], ret); + goto cc_err_exit; + } + } + snprintf(cc_cdev->cdev_name, THERMAL_NAME_LENGTH, + "thermal-cluster-%d-%d", cpus[0], cpus[1]); + cc_cdev->cdev = thermal_of_cooling_device_register( + np, cc_cdev->cdev_name, cc_cdev, + &cc_cooling_ops); + list_add(&cc_cdev->node, &cc_cdev_list); + mutex_unlock(&cc_list_lock); + + return 0; +cc_err_exit: + mutex_unlock(&cc_list_lock); + for (idx = 0; idx < CPU_MAP_CT; idx++) + freq_qos_remove_request(&cc_cdev->cc_qos_req[idx]); + kfree(cc_cdev->map_freq); + kfree(cc_cdev); + + return ret; +} + +static int cc_cooling_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *dev_phandle, *subsys_np = NULL; + struct device *cpu_dev; + int ret = 0, idx = 0, cpu; + u32 cpu_map[CPU_MAP_CT]; + + for_each_available_child_of_node(np, subsys_np) { + for (idx = 0; idx < CPU_MAP_CT; idx++) { + dev_phandle = of_parse_phandle(subsys_np, "qcom,cpus", + idx); + for_each_possible_cpu(cpu) { + cpu_dev = get_cpu_device(cpu); + if (cpu_dev && cpu_dev->of_node == + dev_phandle) { + cpu_map[idx] = cpu; + break; + } + } + } + ret = cc_init(subsys_np, cpu_map); + } + + return ret; +} + +static int cc_cooling_remove(struct platform_device *pdev) +{ + struct cc_limits_data *cc_cdev, *cc_next; + int idx = 0; + + mutex_lock(&cc_list_lock); + list_for_each_entry_safe(cc_cdev, cc_next, &cc_cdev_list, node) { + if (cc_cdev->cdev) + thermal_cooling_device_unregister(cc_cdev->cdev); + + list_del(&cc_cdev->node); + for (idx = 0; idx < CPU_MAP_CT; idx++) + freq_qos_remove_request(&cc_cdev->cc_qos_req[idx]); + kfree(cc_cdev->map_freq); + kfree(cc_cdev); + } + mutex_unlock(&cc_list_lock); + return 0; +} + +static const struct of_device_id cc_cooling_device_match[] = { + {.compatible = "qcom,cc-cooling-devices"}, + {} +}; + +static struct platform_driver cc_cooling_driver = { + .probe = cc_cooling_probe, + .remove = cc_cooling_remove, + .driver = { + .name = CC_CDEV_DRIVER, + .of_match_table = cc_cooling_device_match, + }, +}; + +module_platform_driver(cc_cooling_driver); +MODULE_DESCRIPTION("CPU Voltage cooling device driver"); +MODULE_LICENSE("GPL"); From ba64c62171b16785895d5a44498067809f7f1bb8 Mon Sep 17 00:00:00 2001 From: Rashid Zafar Date: Fri, 4 Nov 2022 15:35:15 -0700 Subject: [PATCH 15/17] drivers: thermal: qcom: Add a snapshot of cpu_hotplug cooling device driver Add a snapshot of cpu hotplug cooling device driver from msm-5.10 as of commit 3ea553e19355 ("drivers: thermal: cpu_hotplug_cdev: Use work queue to execute mitigation"). Updates: - Change GPL v2 license to GPL Change-Id: I7881ee4b2817c943bd15699d68d73f738bd3711c Signed-off-by: Manaf Meethalavalappu Pallikunhi Signed-off-by: Rashid Zafar --- drivers/thermal/qcom/Kconfig | 9 + drivers/thermal/qcom/Makefile | 2 + drivers/thermal/qcom/cpu_hotplug.c | 292 +++++++++++++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 drivers/thermal/qcom/cpu_hotplug.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 0e17a4ec58c0..358d584b2d65 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -113,3 +113,12 @@ config QTI_CPU_VOLTAGE_COOLING_DEVICE decrease or increase the voltages in a cluster based on thermal conditions. +config QTI_CPU_HOTPLUG_COOLING_DEVICE + tristate "QTI CPU Hotplug cooling devices" + depends on THERMAL && HOTPLUG_CPU + help + This enables the QTI CPU Hotplug cooling devices. These cooling + devices will be used by QTI chipset to hotplug a CPU to achieve + thermal cooling. CPU Hotplug will be done after core isolation, + to prevent any process from waking the mitigated CPU. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 81c7a278bc2b..76e215ee5989 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -16,4 +16,6 @@ qti_qmi_cdev-y += thermal_mitigation_device_service_v01.o qmi_cooling.o obj-$(CONFIG_QTI_QMI_SENSOR_V2) += qti_qmi_sensor_v2.o qti_qmi_sensor_v2-y += thermal_sensor_service_v02.o qmi_sensors_v2.o + obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o +obj-$(CONFIG_QTI_CPU_HOTPLUG_COOLING_DEVICE) += cpu_hotplug.o diff --git a/drivers/thermal/qcom/cpu_hotplug.c b/drivers/thermal/qcom/cpu_hotplug.c new file mode 100644 index 000000000000..3dbeea7903b4 --- /dev/null +++ b/drivers/thermal/qcom/cpu_hotplug.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#include + +#define CPU_HOTPLUG_LEVEL 1 + +struct cpu_hot_cdev { + struct list_head node; + int cpu_id; + bool cpu_hot_state; + bool cpu_cur_state; + struct thermal_cooling_device *cdev; + struct device_node *np; + struct work_struct reg_work; + struct work_struct exec_work; +}; +static enum cpuhp_state cpu_hp_online; +static DEFINE_MUTEX(cpu_hot_lock); +static LIST_HEAD(cpu_hot_cdev_list); + +static int cpu_hot_hp_online(unsigned int online_cpu) +{ + struct cpu_hot_cdev *cpu_hot_cdev; + int ret = 0; + + mutex_lock(&cpu_hot_lock); + list_for_each_entry(cpu_hot_cdev, &cpu_hot_cdev_list, node) { + if (online_cpu != cpu_hot_cdev->cpu_id) + continue; + + if (cpu_hot_cdev->cdev) { + if (cpu_hot_cdev->cpu_hot_state) { + pr_debug("Offline CPU:%d\n", + online_cpu); + ret = NOTIFY_BAD; + } + } else + queue_work(system_highpri_wq, &cpu_hot_cdev->reg_work); + + break; + } + mutex_unlock(&cpu_hot_lock); + + return ret; +} + +/** + * cpu_hot_set_cur_state - callback function to set the current cooling + * state. + * @cdev: thermal cooling device pointer. + * @state: set this variable to the current cooling state. + * + * Callback for the thermal cooling device to change the cpu hotplug + * current cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int cpu_hot_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct cpu_hot_cdev *cpu_hot_cdev = cdev->devdata; + + if (cpu_hot_cdev->cpu_id == -1) + return -ENODEV; + + /* Request state should be less than max_level */ + if (state > CPU_HOTPLUG_LEVEL) + return -EINVAL; + + state = !!state; + /* Check if the old cooling action is same as new cooling action */ + if (cpu_hot_cdev->cpu_hot_state == state) + return 0; + + mutex_lock(&cpu_hot_lock); + cpu_hot_cdev->cpu_hot_state = state; + mutex_unlock(&cpu_hot_lock); + queue_work(system_highpri_wq, &cpu_hot_cdev->exec_work); + + return 0; +} + +/** + * cpu_hot_get_cur_state - callback function to get the current cooling + * state. + * @cdev: thermal cooling device pointer. + * @state: fill this variable with the current cooling state. + * + * Callback for the thermal cooling device to return the cpu hotplug + * current cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int cpu_hot_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct cpu_hot_cdev *cpu_hot_cdev = cdev->devdata; + + mutex_lock(&cpu_hot_lock); + *state = (cpu_hot_cdev->cpu_hot_state) ? + CPU_HOTPLUG_LEVEL : 0; + mutex_unlock(&cpu_hot_lock); + + return 0; +} + +/** + * cpu_hot_get_max_state - callback function to get the max cooling state. + * @cdev: thermal cooling device pointer. + * @state: fill this variable with the max cooling state. + * + * Callback for the thermal cooling device to return the cpu + * hotplug max cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int cpu_hot_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = CPU_HOTPLUG_LEVEL; + return 0; +} + +static struct thermal_cooling_device_ops cpu_hot_cooling_ops = { + .get_max_state = cpu_hot_get_max_state, + .get_cur_state = cpu_hot_get_cur_state, + .set_cur_state = cpu_hot_set_cur_state, +}; + +static void cpu_hot_execute_cdev(struct work_struct *work) +{ + struct cpu_hot_cdev *cpu_hot_cdev = + container_of(work, struct cpu_hot_cdev, exec_work); + int ret = 0, cpu = 0; + + mutex_lock(&cpu_hot_lock); + cpu = cpu_hot_cdev->cpu_id; + if (cpu_hot_cdev->cpu_hot_state == CPU_HOTPLUG_LEVEL) { + if (!cpu_hot_cdev->cpu_cur_state) + goto unlock_exit; + mutex_unlock(&cpu_hot_lock); + ret = remove_cpu(cpu); + mutex_lock(&cpu_hot_lock); + if (ret < 0) + pr_err("CPU:%d offline error:%d\n", cpu, ret); + else + cpu_hot_cdev->cpu_cur_state = false; + } else { + if (cpu_hot_cdev->cpu_cur_state) + goto unlock_exit; + mutex_unlock(&cpu_hot_lock); + ret = add_cpu(cpu); + mutex_lock(&cpu_hot_lock); + if (ret) + pr_err("CPU:%d online error:%d\n", cpu, ret); + else + cpu_hot_cdev->cpu_cur_state = true; + } +unlock_exit: + mutex_unlock(&cpu_hot_lock); +} + +static void cpu_hot_register_cdev(struct work_struct *work) +{ + struct cpu_hot_cdev *cpu_hot_cdev = + container_of(work, struct cpu_hot_cdev, reg_work); + char cdev_name[THERMAL_NAME_LENGTH] = ""; + int ret = 0; + + snprintf(cdev_name, THERMAL_NAME_LENGTH, "cpu-hotplug%d", + cpu_hot_cdev->cpu_id); + + cpu_hot_cdev->cdev = thermal_of_cooling_device_register( + cpu_hot_cdev->np, + cdev_name, + cpu_hot_cdev, + &cpu_hot_cooling_ops); + if (IS_ERR(cpu_hot_cdev->cdev)) { + ret = PTR_ERR(cpu_hot_cdev->cdev); + pr_err("Cooling register failed for %s, ret:%d\n", + cdev_name, ret); + cpu_hot_cdev->cdev = NULL; + return; + } + pr_debug("Cooling device [%s] registered.\n", cdev_name); +} + +static int cpu_hot_probe(struct platform_device *pdev) +{ + int ret = 0, cpu = 0; + struct device_node *dev_phandle, *subsys_np = NULL; + struct device *cpu_dev; + struct cpu_hot_cdev *cpu_hot_cdev = NULL; + struct device_node *np = pdev->dev.of_node; + + INIT_LIST_HEAD(&cpu_hot_cdev_list); + for_each_available_child_of_node(np, subsys_np) { + cpu_hot_cdev = devm_kzalloc(&pdev->dev, + sizeof(*cpu_hot_cdev), GFP_KERNEL); + if (!cpu_hot_cdev) { + of_node_put(subsys_np); + return -ENOMEM; + } + cpu_hot_cdev->cpu_id = -1; + cpu_hot_cdev->cpu_hot_state = false; + cpu_hot_cdev->cpu_cur_state = true; + cpu_hot_cdev->cdev = NULL; + cpu_hot_cdev->np = subsys_np; + + dev_phandle = of_parse_phandle(subsys_np, "qcom,cpu", 0); + for_each_possible_cpu(cpu) { + cpu_dev = get_cpu_device(cpu); + if (cpu_dev && cpu_dev->of_node == dev_phandle) { + cpu_hot_cdev->cpu_id = cpu; + break; + } + } + if (cpu_hot_cdev->cpu_id == -1) { + dev_err(&pdev->dev, "Invalid CPU phandle\n"); + continue; + } + INIT_WORK(&cpu_hot_cdev->reg_work, + cpu_hot_register_cdev); + INIT_WORK(&cpu_hot_cdev->exec_work, + cpu_hot_execute_cdev); + list_add(&cpu_hot_cdev->node, &cpu_hot_cdev_list); + } + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cpu-hotplug/cdev:online", + cpu_hot_hp_online, NULL); + if (ret < 0) + return ret; + cpu_hp_online = ret; + + return 0; +} + +static int cpu_hot_remove(struct platform_device *pdev) +{ + struct cpu_hot_cdev *cpu_hot_cdev = NULL, *next = NULL; + int ret = 0; + + if (cpu_hp_online) { + cpuhp_remove_state_nocalls(cpu_hp_online); + cpu_hp_online = 0; + } + + mutex_lock(&cpu_hot_lock); + list_for_each_entry_safe(cpu_hot_cdev, next, &cpu_hot_cdev_list, node) { + if (!cpu_hot_cdev->cdev) + goto loop_skip; + thermal_cooling_device_unregister(cpu_hot_cdev->cdev); + if (cpu_hot_cdev->cpu_hot_state) { + cpu_hot_cdev->cpu_hot_state = false; + ret = add_cpu(cpu_hot_cdev->cpu_id); + if (ret) + pr_err("CPU:%d online error:%d\n", + cpu_hot_cdev->cpu_id, ret); + } +loop_skip: + list_del(&cpu_hot_cdev->node); + } + mutex_unlock(&cpu_hot_lock); + + return 0; +} + +static const struct of_device_id cpu_hot_match[] = { + { .compatible = "qcom,cpu-hotplug", }, + {}, +}; + +static struct platform_driver cpu_hot_driver = { + .probe = cpu_hot_probe, + .remove = cpu_hot_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = cpu_hot_match, + }, +}; +module_platform_driver(cpu_hot_driver); +MODULE_DESCRIPTION("CPU Hotplug cooling device driver"); +MODULE_LICENSE("GPL"); From 83cf3de09112a1294092737a48ac8a1036070822 Mon Sep 17 00:00:00 2001 From: Rashid Zafar Date: Fri, 4 Nov 2022 15:35:34 -0700 Subject: [PATCH 16/17] drivers: thermal: ddr-cdev: Add snapshot of ddr-cdev cooling driver This is a snapshot of the ddr cooling device driver from msm-5.10 as of commit 36bd801b76c2 ("drivers: thermal: ddr-cdev: Add DDR bus width property support"). Add support to get DDR frequency table from its own devicetree. Updates: - Change GPL v2 to GPL Change-Id: If506dc7182033dce2a93863f736553c2b42437a3 Signed-off-by: Manaf Meethalavalappu Pallikunhi Signed-off-by: Rashid Zafar --- drivers/thermal/qcom/Kconfig | 9 ++ drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/ddr_cdev.c | 229 ++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 drivers/thermal/qcom/ddr_cdev.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 358d584b2d65..a4118ffe4d96 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -122,3 +122,12 @@ config QTI_CPU_HOTPLUG_COOLING_DEVICE thermal cooling. CPU Hotplug will be done after core isolation, to prevent any process from waking the mitigated CPU. +config QTI_DDR_COOLING_DEVICE + tristate "QTI DDR cooling devices" + depends on THERMAL && INTERCONNECT + help + This enables the QTI DDR cooling devices. These cooling + devices will be used by QTI chipset to place a DDR state request + to meet the performance requirement under thermally constrained + conditions. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 76e215ee5989..12772bcadbbf 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -19,3 +19,4 @@ qti_qmi_sensor_v2-y += thermal_sensor_service_v02.o qmi_sensors_v2.o obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o obj-$(CONFIG_QTI_CPU_HOTPLUG_COOLING_DEVICE) += cpu_hotplug.o +obj-$(CONFIG_QTI_DDR_COOLING_DEVICE) += ddr_cdev.o diff --git a/drivers/thermal/qcom/ddr_cdev.c b/drivers/thermal/qcom/ddr_cdev.c new file mode 100644 index 000000000000..6a4a79f66465 --- /dev/null +++ b/drivers/thermal/qcom/ddr_cdev.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include + +#define DDR_CDEV_NAME "ddr-cdev" + +struct ddr_cdev { + uint32_t cur_state; + uint32_t max_state; + struct thermal_cooling_device *cdev; + struct icc_path *icc_path; + struct device *dev; + uint32_t *freq_table; + uint32_t freq_table_size; +}; + +/** + * ddr_set_cur_state - callback function to set the ddr state. + * @cdev: thermal cooling device pointer. + * @state: set this variable to the current cooling state. + * + * Callback for the thermal cooling device to change the + * DDR state. + * + * Return: 0 on success, an error code otherwise. + */ +static int ddr_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct ddr_cdev *ddr_cdev = cdev->devdata; + int ret = 0; + + /* Request state should be less than max_level */ + if (state > ddr_cdev->max_state) + return -EINVAL; + + /* Check if the old cooling action is same as new cooling action */ + if (ddr_cdev->cur_state == state) + return 0; + + ret = icc_set_bw(ddr_cdev->icc_path, 0, ddr_cdev->freq_table[state]); + if (ret < 0) { + dev_err(ddr_cdev->dev, "Error placing DDR freq%u. err:%d\n", + ddr_cdev->freq_table[state], ret); + return ret; + } + + ddr_cdev->cur_state = state; + + return ret; +} + +/** + * ddr_get_cur_state - callback function to get the current cooling + * state. + * @cdev: thermal cooling device pointer. + * @state: fill this variable with the current cooling state. + * + * Callback for the thermal cooling device to return the + * current DDR state request. + * + * Return: 0 on success, an error code otherwise. + */ +static int ddr_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct ddr_cdev *ddr_cdev = cdev->devdata; + *state = ddr_cdev->cur_state; + + return 0; +} + +/** + * ddr_get_max_state - callback function to get the max cooling state. + * @cdev: thermal cooling device pointer. + * @state: fill this variable with the max cooling state. + * + * Callback for the thermal cooling device to return the DDR + * max cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int ddr_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct ddr_cdev *ddr_cdev = cdev->devdata; + *state = ddr_cdev->max_state; + + return 0; +} + +static struct thermal_cooling_device_ops ddr_cdev_ops = { + .get_max_state = ddr_get_max_state, + .get_cur_state = ddr_get_cur_state, + .set_cur_state = ddr_set_cur_state, +}; + +static int ddr_cdev_probe(struct platform_device *pdev) +{ + int ret = 0, opp_ct = 0, bus_width = 1, idx = 0; + struct ddr_cdev *ddr_cdev = NULL; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + uint32_t *freq_table = NULL; + char cdev_name[THERMAL_NAME_LENGTH] = DDR_CDEV_NAME; + + ddr_cdev = devm_kzalloc(dev, sizeof(*ddr_cdev), GFP_KERNEL); + if (!ddr_cdev) + return -ENOMEM; + ddr_cdev->icc_path = of_icc_get(dev, NULL); + if (IS_ERR(ddr_cdev->icc_path)) { + ret = PTR_ERR(ddr_cdev->icc_path); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Unable to register icc path: %d\n", + ret); + return ret; + } + + if (!of_find_property(np, "qcom,freq-table", &opp_ct)) { + dev_err(dev, "No DDR frequency entries\n"); + ret = -ENODEV; + goto err_exit; + } + + if (opp_ct <= 0) { + dev_err(dev, "No DDR frequency\n"); + ret = -ENODEV; + goto err_exit; + } + opp_ct = opp_ct / sizeof(*freq_table); + + /* Add one more entry for 0 or no DDR BW vote */ + opp_ct++; + + freq_table = devm_kcalloc(dev, opp_ct, sizeof(*freq_table), GFP_KERNEL); + if (!freq_table) { + ret = -ENOMEM; + goto err_exit; + } + freq_table[0] = 0; + + ret = of_property_read_u32_array(np, "qcom,freq-table", + &freq_table[1], opp_ct - 1); + if (ret < 0) { + dev_err(dev, "DDR frequency read error:%d\n", ret); + goto err_exit; + } + + ddr_cdev->freq_table = freq_table; + ddr_cdev->freq_table_size = opp_ct; + ddr_cdev->cur_state = 0; + ddr_cdev->max_state = opp_ct - 1; + ddr_cdev->dev = dev; + + ret = of_property_read_u32(np, "qcom,bus-width", &bus_width); + if (ret < 0) { + dev_err(dev, "DDR bus width read error:%d\n", ret); + goto err_exit; + } + + for (idx = 0; idx < opp_ct; idx++) + ddr_cdev->freq_table[idx] *= bus_width; + + ret = icc_set_bw(ddr_cdev->icc_path, 0, freq_table[0]); + if (ret < 0) { + dev_err(dev, "Error placing DDR freq request. err:%d\n", + ret); + goto err_exit; + } + + ddr_cdev->cdev = thermal_of_cooling_device_register(np, cdev_name, + ddr_cdev, &ddr_cdev_ops); + if (IS_ERR(ddr_cdev->cdev)) { + ret = PTR_ERR(ddr_cdev->cdev); + dev_err(dev, "Cdev register failed for %s, ret:%d\n", + cdev_name, ret); + goto err_exit; + } + dev_dbg(dev, "Cooling device [%s] registered.\n", cdev_name); + dev_set_drvdata(dev, ddr_cdev); + + return 0; +err_exit: + icc_put(ddr_cdev->icc_path); + + return ret; +} + +static int ddr_cdev_remove(struct platform_device *pdev) +{ + struct ddr_cdev *ddr_cdev = + (struct ddr_cdev *)dev_get_drvdata(&pdev->dev); + + if (ddr_cdev->cdev) { + thermal_cooling_device_unregister(ddr_cdev->cdev); + ddr_cdev->cdev = NULL; + } + if (ddr_cdev->icc_path) { + icc_set_bw(ddr_cdev->icc_path, 0, ddr_cdev->freq_table[0]); + icc_put(ddr_cdev->icc_path); + ddr_cdev->icc_path = NULL; + } + + return 0; +} + +static const struct of_device_id ddr_cdev_match[] = { + { .compatible = "qcom,ddr-cooling-device", }, + {}, +}; + +static struct platform_driver ddr_cdev_driver = { + .probe = ddr_cdev_probe, + .remove = ddr_cdev_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ddr_cdev_match, + }, +}; +module_platform_driver(ddr_cdev_driver); +MODULE_LICENSE("GPL"); From 9408ecee8255e7335b215ee384eed6649813ea54 Mon Sep 17 00:00:00 2001 From: Rashid Zafar Date: Fri, 4 Nov 2022 15:43:52 -0700 Subject: [PATCH 17/17] driver: thermal: userspace_cdev: Add a snapshot of userspace cooling driver Add a snapshot of qti userspace cooling driver from msm-5.10 as of commit 90be510a996d ("drivers: userspace_cdev: Add new userspace cooling device"). Updates: - Change GPL v2 license to GPL Change-Id: I57e07b76bf12a587a167bc4220b45c0d7fd29e8a Signed-off-by: Manaf Meethalavalappu Pallikunhi Signed-off-by: Rashid Zafar --- drivers/thermal/qcom/Kconfig | 9 ++ drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/qti_userspace_cdev.c | 188 ++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 drivers/thermal/qcom/qti_userspace_cdev.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a4118ffe4d96..ea9fbf657ff7 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -131,3 +131,12 @@ config QTI_DDR_COOLING_DEVICE to meet the performance requirement under thermally constrained conditions. +config QTI_USERSPACE_CDEV + tristate "QTI Userspace cooling device" + depends on THERMAL_OF + help + This enables the QTI userspace cooling device, which will help + send the mitigation notification via thermal framework netlink + socket, so that userspace cooling device can perform a mitigation + action. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 12772bcadbbf..51714d52a030 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -20,3 +20,4 @@ qti_qmi_sensor_v2-y += thermal_sensor_service_v02.o qmi_sensors_v2.o obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o obj-$(CONFIG_QTI_CPU_HOTPLUG_COOLING_DEVICE) += cpu_hotplug.o obj-$(CONFIG_QTI_DDR_COOLING_DEVICE) += ddr_cdev.o +obj-$(CONFIG_QTI_USERSPACE_CDEV) += qti_userspace_cdev.o diff --git a/drivers/thermal/qcom/qti_userspace_cdev.c b/drivers/thermal/qcom/qti_userspace_cdev.c new file mode 100644 index 000000000000..e1b583155ad6 --- /dev/null +++ b/drivers/thermal/qcom/qti_userspace_cdev.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include + +#define USERSPACE_CDEV_DRIVER "userspace-cdev" + +struct userspace_cdev { + struct device_node *np; + char cdev_name[THERMAL_NAME_LENGTH]; + struct thermal_cooling_device *cdev; + unsigned int cur_level; + unsigned int max_level; +}; + +static struct userspace_cdev *cdev_instances; +static int inst_cnt; + +static int userspace_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct userspace_cdev *usr_cdev = cdev->devdata; + + if (!usr_cdev) + return -EINVAL; + *state = usr_cdev->max_level; + + return 0; +} + +static int userspace_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct userspace_cdev *usr_cdev = cdev->devdata; + + if (!usr_cdev) + return -EINVAL; + *state = usr_cdev->cur_level; + + return 0; +} + +static int userspace_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct userspace_cdev *usr_cdev = cdev->devdata; + + if (!usr_cdev) + return -EINVAL; + + if (state > usr_cdev->max_level) + return -EINVAL; + + if (usr_cdev->cur_level == state) + return 0; + usr_cdev->cur_level = state; + + return 0; +} + +static struct thermal_cooling_device_ops userspace_cdev_ops = { + .get_max_state = userspace_get_max_state, + .get_cur_state = userspace_get_cur_state, + .set_cur_state = userspace_set_cur_state, +}; + +static void userspace_cdev_cleanup(void) +{ + int idx = 0; + struct userspace_cdev *usr_cdev = cdev_instances; + + for (; idx < inst_cnt; idx++) { + if (usr_cdev[idx].cdev) + thermal_cooling_device_unregister( + usr_cdev[idx].cdev); + usr_cdev[idx].cdev = NULL; + } + inst_cnt = 0; +} + +static int userspace_device_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret = 0, idx = 0, subsys_cnt = 0; + struct device_node *np = dev->of_node, *subsys_np = NULL; + + subsys_cnt = of_get_available_child_count(np); + if (!subsys_cnt) { + dev_err(dev, "No child node to process\n"); + return -EFAULT; + } + cdev_instances = devm_kcalloc(dev, subsys_cnt, sizeof(*cdev_instances), + GFP_KERNEL); + if (!cdev_instances) + return -ENOMEM; + + inst_cnt = subsys_cnt; + for_each_available_child_of_node(np, subsys_np) { + if (idx >= subsys_cnt) { + of_node_put(subsys_np); + break; + } + + ret = of_property_read_u32(subsys_np, "qcom,max-level", + &cdev_instances[idx].max_level); + if (ret) { + dev_err(dev, "error reading qcom,max-level. ret:%d\n", + ret); + goto probe_error; + } + + cdev_instances[idx].np = subsys_np; + strscpy(cdev_instances[idx].cdev_name, subsys_np->name, + THERMAL_NAME_LENGTH); + + cdev_instances[idx].cdev = thermal_of_cooling_device_register( + subsys_np, + cdev_instances[idx].cdev_name, + &cdev_instances[idx], + &userspace_cdev_ops); + if (IS_ERR(cdev_instances[idx].cdev)) { + dev_err(dev, "Error registering cdev:%s err:%d\n", + cdev_instances[idx].cdev_name, + PTR_ERR(cdev_instances[idx].cdev)); + cdev_instances[idx].cdev = NULL; + goto probe_error; + } + dev_info(dev, "cdev:%s lvl:%d registered\n", + cdev_instances[idx].cdev_name, + cdev_instances[idx].max_level); + idx++; + } + of_node_put(np); + + return 0; +probe_error: + of_node_put(subsys_np); + of_node_put(np); + inst_cnt = idx; + userspace_cdev_cleanup(); + return ret; +} + +static int userspace_device_remove(struct platform_device *pdev) +{ + userspace_cdev_cleanup(); + + return 0; +} + +static const struct of_device_id userspace_device_match[] = { + {.compatible = "qcom,userspace-cooling-devices"}, + {} +}; + +static struct platform_driver userspace_cdev_driver = { + .probe = userspace_device_probe, + .remove = userspace_device_remove, + .driver = { + .name = USERSPACE_CDEV_DRIVER, + .of_match_table = userspace_device_match, + }, +}; + +static int __init userspace_cdev_init(void) +{ + return platform_driver_register(&userspace_cdev_driver); +} +module_init(userspace_cdev_init); + +static void __exit userspace_cdev_exit(void) +{ + platform_driver_unregister(&userspace_cdev_driver); +} +module_exit(userspace_cdev_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Userspace cooling device driver");