Commit dd470387 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'thermal-v5.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux

Pull thermal updates from Daniel Lezcano:

 - Add the tegra3 thermal sensor and fix the compilation testing on
   tegra by adding a dependency on ARCH_TEGRA along with COMPILE_TEST
   (Dmitry Osipenko)

 - Fix the error code for the exynos when devm_get_clk() fails (Dan
   Carpenter)

 - Add the TCC cooling support for AlderLake platform (Sumeet Pawnikar)

 - Add support for hardware trip points for the rcar gen3 thermal driver
   and store TSC id as unsigned int (Niklas Söderlund)

 - Replace the deprecated CPU-hotplug functions get_online_cpus() and
   put_online_cpus (Sebastian Andrzej Siewior)

 - Add the thermal tools directory in the MAINTAINERS file (Daniel
   Lezcano)

 - Fix the Makefile and the cross compilation flags for the userspace
   'tmon' tool (Rolf Eike Beer)

 - Allow to use the IMOK independently from the GDDV on Int340x (Sumeet
   Pawnikar)

 - Fix the stub thermal_cooling_device_register() function prototype
   which does not match the real function (Arnd Bergmann)

 - Make the thermal trip point optional in the DT bindings (Maxime
   Ripard)

 - Fix a typo in a comment in the core code (Geert Uytterhoeven)

 - Reduce the verbosity of the trace in the SoC thermal tegra driver
   (Dmitry Osipenko)

 - Add the support for the LMh (Limit Management hardware) driver on the
   QCom platforms (Thara Gopinath)

 - Allow processing of HWP interrupt by adding a weak function in the
   Intel driver (Srinivas Pandruvada)

 - Prevent an abort of the sensor probe is a channel is not used
   (Matthias Kaehlcke)

* tag 'thermal-v5.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux:
  thermal/drivers/qcom/spmi-adc-tm5: Don't abort probing if a sensor is not used
  thermal/drivers/intel: Allow processing of HWP interrupt
  dt-bindings: thermal: Add dt binding for QCOM LMh
  thermal/drivers/qcom: Add support for LMh driver
  firmware: qcom_scm: Introduce SCM calls to access LMh
  thermal/drivers/tegra-soctherm: Silence message about clamped temperature
  thermal: Spelling s/scallbacks/callbacks/
  dt-bindings: thermal: Make trips node optional
  thermal/core: Fix thermal_cooling_device_register() prototype
  thermal/drivers/int340x: Use IMOK independently
  tools/thermal/tmon: Add cross compiling support
  thermal/tools/tmon: Improve the Makefile
  MAINTAINERS: Add missing userspace thermal tools to the thermal section
  thermal/drivers/intel_powerclamp: Replace deprecated CPU-hotplug functions.
  thermal/drivers/rcar_gen3_thermal: Store TSC id as unsigned int
  thermal/drivers/rcar_gen3_thermal: Add support for hardware trip points
  drivers/thermal/intel: Add TCC cooling support for AlderLake platform
  thermal/drivers/exynos: Fix an error code in exynos_tmu_probe()
  thermal/drivers/tegra: Correct compile-testing of drivers
  thermal/drivers/tegra: Add driver for Tegra30 thermal sensor
parents 765092e4 70ee251d
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright 2021 Linaro Ltd.
%YAML 1.2
---
$id: http://devicetree.org/schemas/thermal/qcom-lmh.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Qualcomm Limits Management Hardware(LMh)
maintainers:
- Thara Gopinath <thara.gopinath@linaro.org>
description:
Limits Management Hardware(LMh) is a hardware infrastructure on some
Qualcomm SoCs that can enforce temperature and current limits as
programmed by software for certain IPs like CPU.
properties:
compatible:
enum:
- qcom,sdm845-lmh
reg:
items:
- description: core registers
interrupts:
maxItems: 1
'#interrupt-cells':
const: 1
interrupt-controller: true
cpus:
description:
phandle of the first cpu in the LMh cluster
$ref: /schemas/types.yaml#/definitions/phandle
qcom,lmh-temp-arm-millicelsius:
description:
An integer expressing temperature threshold at which the LMh thermal
FSM is engaged.
qcom,lmh-temp-low-millicelsius:
description:
An integer expressing temperature threshold at which the state machine
will attempt to remove frequency throttling.
qcom,lmh-temp-high-millicelsius:
description:
An integer expressing temperature threshold at which the state machine
will attempt to throttle the frequency.
required:
- compatible
- reg
- interrupts
- '#interrupt-cells'
- interrupt-controller
- cpus
- qcom,lmh-temp-arm-millicelsius
- qcom,lmh-temp-low-millicelsius
- qcom,lmh-temp-high-millicelsius
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
lmh@17d70800 {
compatible = "qcom,sdm845-lmh";
reg = <0x17d70800 0x400>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
cpus = <&CPU4>;
qcom,lmh-temp-arm-millicelsius = <65000>;
qcom,lmh-temp-low-millicelsius = <94500>;
qcom,lmh-temp-high-millicelsius = <95000>;
interrupt-controller;
#interrupt-cells = <1>;
};
...@@ -215,7 +215,7 @@ patternProperties: ...@@ -215,7 +215,7 @@ patternProperties:
- polling-delay - polling-delay
- polling-delay-passive - polling-delay-passive
- thermal-sensors - thermal-sensors
- trips
additionalProperties: false additionalProperties: false
additionalProperties: false additionalProperties: false
......
...@@ -18566,6 +18566,7 @@ F: drivers/thermal/ ...@@ -18566,6 +18566,7 @@ F: drivers/thermal/
F: include/linux/cpu_cooling.h F: include/linux/cpu_cooling.h
F: include/linux/thermal.h F: include/linux/thermal.h
F: include/uapi/linux/thermal.h F: include/uapi/linux/thermal.h
F: tools/thermal/
THERMAL DRIVER FOR AMLOGIC SOCS THERMAL DRIVER FOR AMLOGIC SOCS
M: Guillaume La Roque <glaroque@baylibre.com> M: Guillaume La Roque <glaroque@baylibre.com>
......
...@@ -1147,6 +1147,64 @@ int qcom_scm_qsmmu500_wait_safe_toggle(bool en) ...@@ -1147,6 +1147,64 @@ int qcom_scm_qsmmu500_wait_safe_toggle(bool en)
} }
EXPORT_SYMBOL(qcom_scm_qsmmu500_wait_safe_toggle); EXPORT_SYMBOL(qcom_scm_qsmmu500_wait_safe_toggle);
bool qcom_scm_lmh_dcvsh_available(void)
{
return __qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_LMH, QCOM_SCM_LMH_LIMIT_DCVSH);
}
EXPORT_SYMBOL(qcom_scm_lmh_dcvsh_available);
int qcom_scm_lmh_profile_change(u32 profile_id)
{
struct qcom_scm_desc desc = {
.svc = QCOM_SCM_SVC_LMH,
.cmd = QCOM_SCM_LMH_LIMIT_PROFILE_CHANGE,
.arginfo = QCOM_SCM_ARGS(1, QCOM_SCM_VAL),
.args[0] = profile_id,
.owner = ARM_SMCCC_OWNER_SIP,
};
return qcom_scm_call(__scm->dev, &desc, NULL);
}
EXPORT_SYMBOL(qcom_scm_lmh_profile_change);
int qcom_scm_lmh_dcvsh(u32 payload_fn, u32 payload_reg, u32 payload_val,
u64 limit_node, u32 node_id, u64 version)
{
dma_addr_t payload_phys;
u32 *payload_buf;
int ret, payload_size = 5 * sizeof(u32);
struct qcom_scm_desc desc = {
.svc = QCOM_SCM_SVC_LMH,
.cmd = QCOM_SCM_LMH_LIMIT_DCVSH,
.arginfo = QCOM_SCM_ARGS(5, QCOM_SCM_RO, QCOM_SCM_VAL, QCOM_SCM_VAL,
QCOM_SCM_VAL, QCOM_SCM_VAL),
.args[1] = payload_size,
.args[2] = limit_node,
.args[3] = node_id,
.args[4] = version,
.owner = ARM_SMCCC_OWNER_SIP,
};
payload_buf = dma_alloc_coherent(__scm->dev, payload_size, &payload_phys, GFP_KERNEL);
if (!payload_buf)
return -ENOMEM;
payload_buf[0] = payload_fn;
payload_buf[1] = 0;
payload_buf[2] = payload_reg;
payload_buf[3] = 1;
payload_buf[4] = payload_val;
desc.args[0] = payload_phys;
ret = qcom_scm_call(__scm->dev, &desc, NULL);
dma_free_coherent(__scm->dev, payload_size, payload_buf, payload_phys);
return ret;
}
EXPORT_SYMBOL(qcom_scm_lmh_dcvsh);
static int qcom_scm_find_dload_address(struct device *dev, u64 *addr) static int qcom_scm_find_dload_address(struct device *dev, u64 *addr)
{ {
struct device_node *tcsr; struct device_node *tcsr;
......
...@@ -114,6 +114,10 @@ extern int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc, ...@@ -114,6 +114,10 @@ extern int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc,
#define QCOM_SCM_SVC_HDCP 0x11 #define QCOM_SCM_SVC_HDCP 0x11
#define QCOM_SCM_HDCP_INVOKE 0x01 #define QCOM_SCM_HDCP_INVOKE 0x01
#define QCOM_SCM_SVC_LMH 0x13
#define QCOM_SCM_LMH_LIMIT_PROFILE_CHANGE 0x01
#define QCOM_SCM_LMH_LIMIT_DCVSH 0x10
#define QCOM_SCM_SVC_SMMU_PROGRAM 0x15 #define QCOM_SCM_SVC_SMMU_PROGRAM 0x15
#define QCOM_SCM_SMMU_CONFIG_ERRATA1 0x03 #define QCOM_SCM_SMMU_CONFIG_ERRATA1 0x03
#define QCOM_SCM_SMMU_CONFIG_ERRATA1_CLIENT_ALL 0x02 #define QCOM_SCM_SMMU_CONFIG_ERRATA1_CLIENT_ALL 0x02
......
...@@ -108,9 +108,12 @@ static struct attribute *imok_attr[] = { ...@@ -108,9 +108,12 @@ static struct attribute *imok_attr[] = {
NULL NULL
}; };
static const struct attribute_group imok_attribute_group = {
.attrs = imok_attr,
};
static const struct attribute_group data_attribute_group = { static const struct attribute_group data_attribute_group = {
.bin_attrs = data_attributes, .bin_attrs = data_attributes,
.attrs = imok_attr,
}; };
static ssize_t available_uuids_show(struct device *dev, static ssize_t available_uuids_show(struct device *dev,
...@@ -522,6 +525,12 @@ static int int3400_thermal_probe(struct platform_device *pdev) ...@@ -522,6 +525,12 @@ static int int3400_thermal_probe(struct platform_device *pdev)
if (result) if (result)
goto free_rel_misc; goto free_rel_misc;
if (acpi_has_method(priv->adev->handle, "IMOK")) {
result = sysfs_create_group(&pdev->dev.kobj, &imok_attribute_group);
if (result)
goto free_imok;
}
if (priv->data_vault) { if (priv->data_vault) {
result = sysfs_create_group(&pdev->dev.kobj, result = sysfs_create_group(&pdev->dev.kobj,
&data_attribute_group); &data_attribute_group);
...@@ -545,6 +554,8 @@ static int int3400_thermal_probe(struct platform_device *pdev) ...@@ -545,6 +554,8 @@ static int int3400_thermal_probe(struct platform_device *pdev)
} }
free_uuid: free_uuid:
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
free_imok:
sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
free_rel_misc: free_rel_misc:
if (!priv->rel_misc_dev_res) if (!priv->rel_misc_dev_res)
acpi_thermal_rel_misc_device_remove(priv->adev->handle); acpi_thermal_rel_misc_device_remove(priv->adev->handle);
...@@ -573,6 +584,7 @@ static int int3400_thermal_remove(struct platform_device *pdev) ...@@ -573,6 +584,7 @@ static int int3400_thermal_remove(struct platform_device *pdev)
if (priv->data_vault) if (priv->data_vault)
sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group); sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
thermal_zone_device_unregister(priv->thermal); thermal_zone_device_unregister(priv->thermal);
kfree(priv->data_vault); kfree(priv->data_vault);
kfree(priv->trts); kfree(priv->trts);
......
...@@ -528,7 +528,7 @@ static int start_power_clamp(void) ...@@ -528,7 +528,7 @@ static int start_power_clamp(void)
set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1); set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1);
/* prevent cpu hotplug */ /* prevent cpu hotplug */
get_online_cpus(); cpus_read_lock();
/* prefer BSP */ /* prefer BSP */
control_cpu = 0; control_cpu = 0;
...@@ -542,7 +542,7 @@ static int start_power_clamp(void) ...@@ -542,7 +542,7 @@ static int start_power_clamp(void)
for_each_online_cpu(cpu) { for_each_online_cpu(cpu) {
start_power_clamp_worker(cpu); start_power_clamp_worker(cpu);
} }
put_online_cpus(); cpus_read_unlock();
return 0; return 0;
} }
......
...@@ -79,6 +79,8 @@ static const struct x86_cpu_id tcc_ids[] __initconst = { ...@@ -79,6 +79,8 @@ static const struct x86_cpu_id tcc_ids[] __initconst = {
X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, NULL), X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, NULL),
X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, NULL), X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, NULL),
X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, NULL), X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, NULL),
X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, NULL),
X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, NULL),
{} {}
}; };
......
...@@ -31,3 +31,13 @@ config QCOM_SPMI_TEMP_ALARM ...@@ -31,3 +31,13 @@ config QCOM_SPMI_TEMP_ALARM
trip points. The temperature reported by the thermal sensor reflects the trip points. The temperature reported by the thermal sensor reflects the
real time die temperature if an ADC is present or an estimate of the real time die temperature if an ADC is present or an estimate of the
temperature based upon the over temperature stage value. temperature based upon the over temperature stage value.
config QCOM_LMH
tristate "Qualcomm Limits Management Hardware"
depends on ARCH_QCOM
help
This enables initialization of Qualcomm limits management
hardware(LMh). LMh allows for hardware-enforced mitigation for cpus based on
input from temperature and current sensors. On many newer Qualcomm SoCs
LMh is configured in the firmware and this feature need not be enabled.
However, on certain SoCs like sdm845 LMh has to be configured from kernel.
...@@ -5,3 +5,4 @@ qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ ...@@ -5,3 +5,4 @@ qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
tsens-8960.o tsens-8960.o
obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.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_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
obj-$(CONFIG_QCOM_LMH) += lmh.o
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021, Linaro Limited. All rights reserved.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/qcom_scm.h>
#define LMH_NODE_DCVS 0x44435653
#define LMH_CLUSTER0_NODE_ID 0x6370302D
#define LMH_CLUSTER1_NODE_ID 0x6370312D
#define LMH_SUB_FN_THERMAL 0x54484D4C
#define LMH_SUB_FN_CRNT 0x43524E54
#define LMH_SUB_FN_REL 0x52454C00
#define LMH_SUB_FN_BCL 0x42434C00
#define LMH_ALGO_MODE_ENABLE 0x454E424C
#define LMH_TH_HI_THRESHOLD 0x48494748
#define LMH_TH_LOW_THRESHOLD 0x4C4F5700
#define LMH_TH_ARM_THRESHOLD 0x41524D00
#define LMH_REG_DCVS_INTR_CLR 0x8
struct lmh_hw_data {
void __iomem *base;
struct irq_domain *domain;
int irq;
};
static irqreturn_t lmh_handle_irq(int hw_irq, void *data)
{
struct lmh_hw_data *lmh_data = data;
int irq = irq_find_mapping(lmh_data->domain, 0);
/* Call the cpufreq driver to handle the interrupt */
if (irq)
generic_handle_irq(irq);
return 0;
}
static void lmh_enable_interrupt(struct irq_data *d)
{
struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
/* Clear the existing interrupt */
writel(0xff, lmh_data->base + LMH_REG_DCVS_INTR_CLR);
enable_irq(lmh_data->irq);
}
static void lmh_disable_interrupt(struct irq_data *d)
{
struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
disable_irq_nosync(lmh_data->irq);
}
static struct irq_chip lmh_irq_chip = {
.name = "lmh",
.irq_enable = lmh_enable_interrupt,
.irq_disable = lmh_disable_interrupt
};
static int lmh_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
struct lmh_hw_data *lmh_data = d->host_data;
irq_set_chip_and_handler(irq, &lmh_irq_chip, handle_simple_irq);
irq_set_chip_data(irq, lmh_data);
return 0;
}
static const struct irq_domain_ops lmh_irq_ops = {
.map = lmh_irq_map,
.xlate = irq_domain_xlate_onecell,
};
static int lmh_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *cpu_node;
struct lmh_hw_data *lmh_data;
int temp_low, temp_high, temp_arm, cpu_id, ret;
u32 node_id;
lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL);
if (!lmh_data)
return -ENOMEM;
lmh_data->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(lmh_data->base))
return PTR_ERR(lmh_data->base);
cpu_node = of_parse_phandle(np, "cpus", 0);
if (!cpu_node)
return -EINVAL;
cpu_id = of_cpu_node_to_id(cpu_node);
of_node_put(cpu_node);
ret = of_property_read_u32(np, "qcom,lmh-temp-high-millicelsius", &temp_high);
if (ret) {
dev_err(dev, "missing qcom,lmh-temp-high-millicelsius property\n");
return ret;
}
ret = of_property_read_u32(np, "qcom,lmh-temp-low-millicelsius", &temp_low);
if (ret) {
dev_err(dev, "missing qcom,lmh-temp-low-millicelsius property\n");
return ret;
}
ret = of_property_read_u32(np, "qcom,lmh-temp-arm-millicelsius", &temp_arm);
if (ret) {
dev_err(dev, "missing qcom,lmh-temp-arm-millicelsius property\n");
return ret;
}
/*
* Only sdm845 has lmh hardware currently enabled from hlos. If this is needed
* for other platforms, revisit this to check if the <cpu-id, node-id> should be part
* of a dt match table.
*/
if (cpu_id == 0) {
node_id = LMH_CLUSTER0_NODE_ID;
} else if (cpu_id == 4) {
node_id = LMH_CLUSTER1_NODE_ID;
} else {
dev_err(dev, "Wrong CPU id associated with LMh node\n");
return -EINVAL;
}
if (!qcom_scm_lmh_dcvsh_available())
return -EINVAL;
ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1,
LMH_NODE_DCVS, node_id, 0);
if (ret)
dev_err(dev, "Error %d enabling current subfunction\n", ret);
ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1,
LMH_NODE_DCVS, node_id, 0);
if (ret)
dev_err(dev, "Error %d enabling reliability subfunction\n", ret);
ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1,
LMH_NODE_DCVS, node_id, 0);
if (ret)
dev_err(dev, "Error %d enabling BCL subfunction\n", ret);
ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1,
LMH_NODE_DCVS, node_id, 0);
if (ret) {
dev_err(dev, "Error %d enabling thermal subfunction\n", ret);
return ret;
}
ret = qcom_scm_lmh_profile_change(0x1);
if (ret) {
dev_err(dev, "Error %d changing profile\n", ret);
return ret;
}
/* Set default thermal trips */
ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, temp_arm,
LMH_NODE_DCVS, node_id, 0);
if (ret) {
dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
return ret;
}
ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, temp_high,
LMH_NODE_DCVS, node_id, 0);
if (ret) {
dev_err(dev, "Error setting thermal HI threshold%d\n", ret);
return ret;
}
ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, temp_low,
LMH_NODE_DCVS, node_id, 0);
if (ret) {
dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
return ret;
}
lmh_data->irq = platform_get_irq(pdev, 0);
lmh_data->domain = irq_domain_add_linear(np, 1, &lmh_irq_ops, lmh_data);
if (!lmh_data->domain) {
dev_err(dev, "Error adding irq_domain\n");
return -EINVAL;
}
/* Disable the irq and let cpufreq enable it when ready to handle the interrupt */
irq_set_status_flags(lmh_data->irq, IRQ_NOAUTOEN);
ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq,
IRQF_ONESHOT | IRQF_NO_SUSPEND,
"lmh-irq", lmh_data);
if (ret) {
dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq);
irq_domain_remove(lmh_data->domain);
return ret;
}
return 0;
}
static const struct of_device_id lmh_table[] = {
{ .compatible = "qcom,sdm845-lmh", },
{}
};
MODULE_DEVICE_TABLE(of, lmh_table);
static struct platform_driver lmh_driver = {
.probe = lmh_probe,
.driver = {
.name = "qcom-lmh",
.of_match_table = lmh_table,
.suppress_bind_attrs = true,
},
};
module_platform_driver(lmh_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("QCOM LMh driver");
...@@ -359,6 +359,12 @@ static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm) ...@@ -359,6 +359,12 @@ static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm)
&adc_tm->channels[i], &adc_tm->channels[i],
&adc_tm5_ops); &adc_tm5_ops);
if (IS_ERR(tzd)) { if (IS_ERR(tzd)) {
if (PTR_ERR(tzd) == -ENODEV) {
dev_warn(adc_tm->dev, "thermal sensor on channel %d is not used\n",
adc_tm->channels[i].channel);
continue;
}
dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n", dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n",
adc_tm->channels[i].channel, PTR_ERR(tzd)); adc_tm->channels[i].channel, PTR_ERR(tzd));
return PTR_ERR(tzd); return PTR_ERR(tzd);
......
...@@ -84,7 +84,7 @@ struct rcar_gen3_thermal_tsc { ...@@ -84,7 +84,7 @@ struct rcar_gen3_thermal_tsc {
struct thermal_zone_device *zone; struct thermal_zone_device *zone;
struct equation_coefs coef; struct equation_coefs coef;
int tj_t; int tj_t;
int id; /* thermal channel id */ unsigned int id; /* thermal channel id */
}; };
struct rcar_gen3_thermal_priv { struct rcar_gen3_thermal_priv {
...@@ -190,10 +190,64 @@ static int rcar_gen3_thermal_get_temp(void *devdata, int *temp) ...@@ -190,10 +190,64 @@ static int rcar_gen3_thermal_get_temp(void *devdata, int *temp)
return 0; return 0;
} }
static const struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = { static int rcar_gen3_thermal_mcelsius_to_temp(struct rcar_gen3_thermal_tsc *tsc,
int mcelsius)
{
int celsius, val;
celsius = DIV_ROUND_CLOSEST(mcelsius, 1000);
if (celsius <= INT_FIXPT(tsc->tj_t))
val = celsius * tsc->coef.a1 + tsc->coef.b1;
else
val = celsius * tsc->coef.a2 + tsc->coef.b2;
return INT_FIXPT(val);
}
static int rcar_gen3_thermal_set_trips(void *devdata, int low, int high)
{
struct rcar_gen3_thermal_tsc *tsc = devdata;
u32 irqmsk = 0;
if (low != -INT_MAX) {
irqmsk |= IRQ_TEMPD1;
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1,
rcar_gen3_thermal_mcelsius_to_temp(tsc, low));
}
if (high != INT_MAX) {
irqmsk |= IRQ_TEMP2;
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2,
rcar_gen3_thermal_mcelsius_to_temp(tsc, high));
}
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, irqmsk);
return 0;
}
static struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = {
.get_temp = rcar_gen3_thermal_get_temp, .get_temp = rcar_gen3_thermal_get_temp,
.set_trips = rcar_gen3_thermal_set_trips,
}; };
static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data)
{
struct rcar_gen3_thermal_priv *priv = data;
unsigned int i;
u32 status;
for (i = 0; i < priv->num_tscs; i++) {
status = rcar_gen3_thermal_read(priv->tscs[i], REG_GEN3_IRQSTR);
rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQSTR, 0);
if (status)
thermal_zone_device_update(priv->tscs[i]->zone,
THERMAL_EVENT_UNSPECIFIED);
}
return IRQ_HANDLED;
}
static const struct soc_device_attribute r8a7795es1[] = { static const struct soc_device_attribute r8a7795es1[] = {
{ .soc_id = "r8a7795", .revision = "ES1.*" }, { .soc_id = "r8a7795", .revision = "ES1.*" },
{ /* sentinel */ } { /* sentinel */ }
...@@ -210,6 +264,9 @@ static void rcar_gen3_thermal_init_r8a7795es1(struct rcar_gen3_thermal_tsc *tsc) ...@@ -210,6 +264,9 @@ static void rcar_gen3_thermal_init_r8a7795es1(struct rcar_gen3_thermal_tsc *tsc)
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F); rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F);
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0); rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
if (tsc->zone->ops->set_trips)
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN,
IRQ_TEMPD1 | IRQ_TEMP2);
rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR,
CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN); CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN);
...@@ -235,6 +292,9 @@ static void rcar_gen3_thermal_init(struct rcar_gen3_thermal_tsc *tsc) ...@@ -235,6 +292,9 @@ static void rcar_gen3_thermal_init(struct rcar_gen3_thermal_tsc *tsc)
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0); rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0);
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0); rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
if (tsc->zone->ops->set_trips)
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN,
IRQ_TEMPD1 | IRQ_TEMP2);
reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR); reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR);
reg_val |= THCTR_THSST; reg_val |= THCTR_THSST;
...@@ -303,6 +363,34 @@ static void rcar_gen3_hwmon_action(void *data) ...@@ -303,6 +363,34 @@ static void rcar_gen3_hwmon_action(void *data)
thermal_remove_hwmon_sysfs(zone); thermal_remove_hwmon_sysfs(zone);
} }
static int rcar_gen3_thermal_request_irqs(struct rcar_gen3_thermal_priv *priv,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
unsigned int i;
char *irqname;
int ret, irq;
for (i = 0; i < 2; i++) {
irq = platform_get_irq_optional(pdev, i);
if (irq < 0)
return irq;
irqname = devm_kasprintf(dev, GFP_KERNEL, "%s:ch%d",
dev_name(dev), i);
if (!irqname)
return -ENOMEM;
ret = devm_request_threaded_irq(dev, irq, NULL,
rcar_gen3_thermal_irq,
IRQF_ONESHOT, irqname, priv);
if (ret)
return ret;
}
return 0;
}
static int rcar_gen3_thermal_probe(struct platform_device *pdev) static int rcar_gen3_thermal_probe(struct platform_device *pdev)
{ {
struct rcar_gen3_thermal_priv *priv; struct rcar_gen3_thermal_priv *priv;
...@@ -310,7 +398,8 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) ...@@ -310,7 +398,8 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
const int *ths_tj_1 = of_device_get_match_data(dev); const int *ths_tj_1 = of_device_get_match_data(dev);
struct resource *res; struct resource *res;
struct thermal_zone_device *zone; struct thermal_zone_device *zone;
int ret, i; unsigned int i;
int ret;
/* default values if FUSEs are missing */ /* default values if FUSEs are missing */
/* TODO: Read values from hardware on supported platforms */ /* TODO: Read values from hardware on supported platforms */
...@@ -326,6 +415,9 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) ...@@ -326,6 +415,9 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, priv); platform_set_drvdata(pdev, priv);
if (rcar_gen3_thermal_request_irqs(priv, pdev))
rcar_gen3_tz_of_ops.set_trips = NULL;
pm_runtime_enable(dev); pm_runtime_enable(dev);
pm_runtime_get_sync(dev); pm_runtime_get_sync(dev);
...@@ -351,9 +443,6 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) ...@@ -351,9 +443,6 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
priv->tscs[i] = tsc; priv->tscs[i] = tsc;
priv->thermal_init(tsc);
rcar_gen3_thermal_calc_coefs(tsc, ptat, thcodes[i], *ths_tj_1);
zone = devm_thermal_zone_of_sensor_register(dev, i, tsc, zone = devm_thermal_zone_of_sensor_register(dev, i, tsc,
&rcar_gen3_tz_of_ops); &rcar_gen3_tz_of_ops);
if (IS_ERR(zone)) { if (IS_ERR(zone)) {
...@@ -363,6 +452,9 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) ...@@ -363,6 +452,9 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
} }
tsc->zone = zone; tsc->zone = zone;
priv->thermal_init(tsc);
rcar_gen3_thermal_calc_coefs(tsc, ptat, thcodes[i], *ths_tj_1);
tsc->zone->tzp->no_hwmon = false; tsc->zone->tzp->no_hwmon = false;
ret = thermal_add_hwmon_sysfs(tsc->zone); ret = thermal_add_hwmon_sysfs(tsc->zone);
if (ret) if (ret)
...@@ -376,7 +468,7 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) ...@@ -376,7 +468,7 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
if (ret < 0) if (ret < 0)
goto error_unregister; goto error_unregister;
dev_info(dev, "TSC%d: Loaded %d trip points\n", i, ret); dev_info(dev, "TSC%u: Loaded %d trip points\n", i, ret);
} }
priv->num_tscs = i; priv->num_tscs = i;
...@@ -401,8 +493,12 @@ static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev) ...@@ -401,8 +493,12 @@ static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
for (i = 0; i < priv->num_tscs; i++) { for (i = 0; i < priv->num_tscs; i++) {
struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i]; struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
struct thermal_zone_device *zone = tsc->zone;
priv->thermal_init(tsc); priv->thermal_init(tsc);
if (zone->ops->set_trips)
rcar_gen3_thermal_set_trips(tsc, zone->prev_low_trip,
zone->prev_high_trip);
} }
return 0; return 0;
......
...@@ -1073,6 +1073,7 @@ static int exynos_tmu_probe(struct platform_device *pdev) ...@@ -1073,6 +1073,7 @@ static int exynos_tmu_probe(struct platform_device *pdev)
data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk"); data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
if (IS_ERR(data->sclk)) { if (IS_ERR(data->sclk)) {
dev_err(&pdev->dev, "Failed to get sclk\n"); dev_err(&pdev->dev, "Failed to get sclk\n");
ret = PTR_ERR(data->sclk);
goto err_clk; goto err_clk;
} else { } else {
ret = clk_prepare_enable(data->sclk); ret = clk_prepare_enable(data->sclk);
......
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
menu "NVIDIA Tegra thermal drivers" menu "NVIDIA Tegra thermal drivers"
depends on ARCH_TEGRA depends on ARCH_TEGRA || COMPILE_TEST
config TEGRA_SOCTHERM config TEGRA_SOCTHERM
tristate "Tegra SOCTHERM thermal management" tristate "Tegra SOCTHERM thermal management"
...@@ -18,4 +18,11 @@ config TEGRA_BPMP_THERMAL ...@@ -18,4 +18,11 @@ config TEGRA_BPMP_THERMAL
Enable this option for support for sensing system temperature of NVIDIA Enable this option for support for sensing system temperature of NVIDIA
Tegra systems-on-chip with the BPMP coprocessor (Tegra186). Tegra systems-on-chip with the BPMP coprocessor (Tegra186).
config TEGRA30_TSENSOR
tristate "Tegra30 Thermal Sensor"
depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
help
Enable this option to support thermal management of NVIDIA Tegra30
system-on-chip.
endmenu endmenu
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TEGRA_SOCTHERM) += tegra-soctherm.o obj-$(CONFIG_TEGRA_SOCTHERM) += tegra-soctherm.o
obj-$(CONFIG_TEGRA_BPMP_THERMAL) += tegra-bpmp-thermal.o obj-$(CONFIG_TEGRA_BPMP_THERMAL) += tegra-bpmp-thermal.o
obj-$(CONFIG_TEGRA30_TSENSOR) += tegra30-tsensor.o
tegra-soctherm-y := soctherm.o soctherm-fuse.o tegra-soctherm-y := soctherm.o soctherm-fuse.o
tegra-soctherm-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124-soctherm.o tegra-soctherm-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124-soctherm.o
......
...@@ -450,7 +450,7 @@ static int enforce_temp_range(struct device *dev, int trip_temp) ...@@ -450,7 +450,7 @@ static int enforce_temp_range(struct device *dev, int trip_temp)
temp = clamp_val(trip_temp, min_low_temp, max_high_temp); temp = clamp_val(trip_temp, min_low_temp, max_high_temp);
if (temp != trip_temp) if (temp != trip_temp)
dev_info(dev, "soctherm: trip temperature %d forced to %d\n", dev_dbg(dev, "soctherm: trip temperature %d forced to %d\n",
trip_temp, temp); trip_temp, temp);
return temp; return temp;
} }
......
// SPDX-License-Identifier: GPL-2.0
/*
* Tegra30 SoC Thermal Sensor driver
*
* Based on downstream HWMON driver from NVIDIA.
* Copyright (C) 2011 NVIDIA Corporation
*
* Author: Dmitry Osipenko <digetx@gmail.com>
* Copyright (C) 2021 GRATE-DRIVER project
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include <linux/types.h>
#include <soc/tegra/fuse.h>
#include "../thermal_core.h"
#include "../thermal_hwmon.h"
#define TSENSOR_SENSOR0_CONFIG0 0x0
#define TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP BIT(0)
#define TSENSOR_SENSOR0_CONFIG0_HW_FREQ_DIV_EN BIT(1)
#define TSENSOR_SENSOR0_CONFIG0_THERMAL_RST_EN BIT(2)
#define TSENSOR_SENSOR0_CONFIG0_DVFS_EN BIT(3)
#define TSENSOR_SENSOR0_CONFIG0_INTR_OVERFLOW_EN BIT(4)
#define TSENSOR_SENSOR0_CONFIG0_INTR_HW_FREQ_DIV_EN BIT(5)
#define TSENSOR_SENSOR0_CONFIG0_INTR_THERMAL_RST_EN BIT(6)
#define TSENSOR_SENSOR0_CONFIG0_M GENMASK(23, 8)
#define TSENSOR_SENSOR0_CONFIG0_N GENMASK(31, 24)
#define TSENSOR_SENSOR0_CONFIG1 0x8
#define TSENSOR_SENSOR0_CONFIG1_TH1 GENMASK(15, 0)
#define TSENSOR_SENSOR0_CONFIG1_TH2 GENMASK(31, 16)
#define TSENSOR_SENSOR0_CONFIG2 0xc
#define TSENSOR_SENSOR0_CONFIG2_TH3 GENMASK(15, 0)
#define TSENSOR_SENSOR0_STATUS0 0x18
#define TSENSOR_SENSOR0_STATUS0_STATE GENMASK(2, 0)
#define TSENSOR_SENSOR0_STATUS0_INTR BIT(8)
#define TSENSOR_SENSOR0_STATUS0_CURRENT_VALID BIT(9)
#define TSENSOR_SENSOR0_TS_STATUS1 0x1c
#define TSENSOR_SENSOR0_TS_STATUS1_CURRENT_COUNT GENMASK(31, 16)
#define TEGRA30_FUSE_TEST_PROG_VER 0x28
#define TEGRA30_FUSE_TSENSOR_CALIB 0x98
#define TEGRA30_FUSE_TSENSOR_CALIB_LOW GENMASK(15, 0)
#define TEGRA30_FUSE_TSENSOR_CALIB_HIGH GENMASK(31, 16)
#define TEGRA30_FUSE_SPARE_BIT 0x144
struct tegra_tsensor;
struct tegra_tsensor_calibration_data {
int a, b, m, n, p, r;
};
struct tegra_tsensor_channel {
void __iomem *regs;
unsigned int id;
struct tegra_tsensor *ts;
struct thermal_zone_device *tzd;
};
struct tegra_tsensor {
void __iomem *regs;
bool swap_channels;
struct clk *clk;
struct device *dev;
struct reset_control *rst;
struct tegra_tsensor_channel ch[2];
struct tegra_tsensor_calibration_data calib;
};
static int tegra_tsensor_hw_enable(const struct tegra_tsensor *ts)
{
u32 val;
int err;
err = reset_control_assert(ts->rst);
if (err) {
dev_err(ts->dev, "failed to assert hardware reset: %d\n", err);
return err;
}
err = clk_prepare_enable(ts->clk);
if (err) {
dev_err(ts->dev, "failed to enable clock: %d\n", err);
return err;
}
fsleep(1000);
err = reset_control_deassert(ts->rst);
if (err) {
dev_err(ts->dev, "failed to deassert hardware reset: %d\n", err);
goto disable_clk;
}
/*
* Sensors are enabled after reset by default, but not gauging
* until clock counter is programmed.
*
* M: number of reference clock pulses after which every
* temperature / voltage measurement is made
*
* N: number of reference clock counts for which the counter runs
*/
val = FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_M, 12500);
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_N, 255);
/* apply the same configuration to both channels */
writel_relaxed(val, ts->regs + 0x40 + TSENSOR_SENSOR0_CONFIG0);
writel_relaxed(val, ts->regs + 0x80 + TSENSOR_SENSOR0_CONFIG0);
return 0;
disable_clk:
clk_disable_unprepare(ts->clk);
return err;
}
static int tegra_tsensor_hw_disable(const struct tegra_tsensor *ts)
{
int err;
err = reset_control_assert(ts->rst);
if (err) {
dev_err(ts->dev, "failed to assert hardware reset: %d\n", err);
return err;
}
clk_disable_unprepare(ts->clk);
return 0;
}
static void devm_tegra_tsensor_hw_disable(void *data)
{
const struct tegra_tsensor *ts = data;
tegra_tsensor_hw_disable(ts);
}
static int tegra_tsensor_get_temp(void *data, int *temp)
{
const struct tegra_tsensor_channel *tsc = data;
const struct tegra_tsensor *ts = tsc->ts;
int err, c1, c2, c3, c4, counter;
u32 val;
/*
* Counter will be invalid if hardware is misprogrammed or not enough
* time passed since the time when sensor was enabled.
*/
err = readl_relaxed_poll_timeout(tsc->regs + TSENSOR_SENSOR0_STATUS0, val,
val & TSENSOR_SENSOR0_STATUS0_CURRENT_VALID,
21 * USEC_PER_MSEC,
21 * USEC_PER_MSEC * 50);
if (err) {
dev_err_once(ts->dev, "ch%u: counter invalid\n", tsc->id);
return err;
}
val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_TS_STATUS1);
counter = FIELD_GET(TSENSOR_SENSOR0_TS_STATUS1_CURRENT_COUNT, val);
/*
* This shouldn't happen with a valid counter status, nevertheless
* lets verify the value since it's in a separate (from status)
* register.
*/
if (counter == 0xffff) {
dev_err_once(ts->dev, "ch%u: counter overflow\n", tsc->id);
return -EINVAL;
}
/*
* temperature = a * counter + b
* temperature = m * (temperature ^ 2) + n * temperature + p
*/
c1 = DIV_ROUND_CLOSEST(ts->calib.a * counter + ts->calib.b, 1000000);
c1 = c1 ?: 1;
c2 = DIV_ROUND_CLOSEST(ts->calib.p, c1);
c3 = c1 * ts->calib.m;
c4 = ts->calib.n;
*temp = DIV_ROUND_CLOSEST(c1 * (c2 + c3 + c4), 1000);
return 0;
}
static int tegra_tsensor_temp_to_counter(const struct tegra_tsensor *ts, int temp)
{
int c1, c2;
c1 = DIV_ROUND_CLOSEST(ts->calib.p - temp * 1000, ts->calib.m);
c2 = -ts->calib.r - int_sqrt(ts->calib.r * ts->calib.r - c1);
return DIV_ROUND_CLOSEST(c2 * 1000000 - ts->calib.b, ts->calib.a);
}
static int tegra_tsensor_set_trips(void *data, int low, int high)
{
const struct tegra_tsensor_channel *tsc = data;
const struct tegra_tsensor *ts = tsc->ts;
u32 val;
/*
* TSENSOR doesn't trigger interrupt on the "low" temperature breach,
* hence bail out if high temperature is unspecified.
*/
if (high == INT_MAX)
return 0;
val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG1);
val &= ~TSENSOR_SENSOR0_CONFIG1_TH1;
high = tegra_tsensor_temp_to_counter(ts, high);
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG1_TH1, high);
writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG1);
return 0;
}
static const struct thermal_zone_of_device_ops ops = {
.get_temp = tegra_tsensor_get_temp,
.set_trips = tegra_tsensor_set_trips,
};
static bool
tegra_tsensor_handle_channel_interrupt(const struct tegra_tsensor *ts,
unsigned int id)
{
const struct tegra_tsensor_channel *tsc = &ts->ch[id];
u32 val;
val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_STATUS0);
writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_STATUS0);
if (FIELD_GET(TSENSOR_SENSOR0_STATUS0_STATE, val) == 5)
dev_err_ratelimited(ts->dev, "ch%u: counter overflowed\n", id);
if (!FIELD_GET(TSENSOR_SENSOR0_STATUS0_INTR, val))
return false;
thermal_zone_device_update(tsc->tzd, THERMAL_EVENT_UNSPECIFIED);
return true;
}
static irqreturn_t tegra_tsensor_isr(int irq, void *data)
{
const struct tegra_tsensor *ts = data;
bool handled = false;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(ts->ch); i++)
handled |= tegra_tsensor_handle_channel_interrupt(ts, i);
return handled ? IRQ_HANDLED : IRQ_NONE;
}
static int tegra_tsensor_disable_hw_channel(const struct tegra_tsensor *ts,
unsigned int id)
{
const struct tegra_tsensor_channel *tsc = &ts->ch[id];
struct thermal_zone_device *tzd = tsc->tzd;
u32 val;
int err;
if (!tzd)
goto stop_channel;
err = thermal_zone_device_disable(tzd);
if (err) {
dev_err(ts->dev, "ch%u: failed to disable zone: %d\n", id, err);
return err;
}
stop_channel:
/* stop channel gracefully */
val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP, 1);
writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
return 0;
}
static void tegra_tsensor_get_hw_channel_trips(struct thermal_zone_device *tzd,
int *hot_trip, int *crit_trip)
{
unsigned int i;
/*
* 90C is the maximal critical temperature of all Tegra30 SoC variants,
* use it for the default trip if unspecified in a device-tree.
*/
*hot_trip = 85000;
*crit_trip = 90000;
for (i = 0; i < tzd->trips; i++) {
enum thermal_trip_type type;
int trip_temp;
tzd->ops->get_trip_temp(tzd, i, &trip_temp);
tzd->ops->get_trip_type(tzd, i, &type);
if (type == THERMAL_TRIP_HOT)
*hot_trip = trip_temp;
if (type == THERMAL_TRIP_CRITICAL)
*crit_trip = trip_temp;
}
/* clamp hardware trips to the calibration limits */
*hot_trip = clamp(*hot_trip, 25000, 90000);
/*
* Kernel will perform a normal system shut down if it will
* see that critical temperature is breached, hence set the
* hardware limit by 5C higher in order to allow system to
* shut down gracefully before sending signal to the Power
* Management controller.
*/
*crit_trip = clamp(*crit_trip + 5000, 25000, 90000);
}
static int tegra_tsensor_enable_hw_channel(const struct tegra_tsensor *ts,
unsigned int id)
{
const struct tegra_tsensor_channel *tsc = &ts->ch[id];
struct thermal_zone_device *tzd = tsc->tzd;
int err, hot_trip = 0, crit_trip = 0;
u32 val;
if (!tzd) {
val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
val &= ~TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP;
writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
return 0;
}
tegra_tsensor_get_hw_channel_trips(tzd, &hot_trip, &crit_trip);
/* prevent potential racing with tegra_tsensor_set_trips() */
mutex_lock(&tzd->lock);
dev_info_once(ts->dev, "ch%u: PMC emergency shutdown trip set to %dC\n",
id, DIV_ROUND_CLOSEST(crit_trip, 1000));
hot_trip = tegra_tsensor_temp_to_counter(ts, hot_trip);
crit_trip = tegra_tsensor_temp_to_counter(ts, crit_trip);
/* program LEVEL2 counter threshold */
val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG1);
val &= ~TSENSOR_SENSOR0_CONFIG1_TH2;
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG1_TH2, hot_trip);
writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG1);
/* program LEVEL3 counter threshold */
val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG2);
val &= ~TSENSOR_SENSOR0_CONFIG2_TH3;
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG2_TH3, crit_trip);
writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG2);
/*
* Enable sensor, emergency shutdown, interrupts for level 1/2/3
* breaches and counter overflow condition.
*
* Disable DIV2 throttle for now since we need to figure out how
* to integrate it properly with the thermal framework.
*
* Thermal levels supported by hardware:
*
* Level 0 = cold
* Level 1 = passive cooling (cpufreq DVFS)
* Level 2 = passive cooling assisted by hardware (DIV2)
* Level 3 = emergency shutdown assisted by hardware (PMC)
*/
val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
val &= ~TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP;
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_DVFS_EN, 1);
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_HW_FREQ_DIV_EN, 0);
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_THERMAL_RST_EN, 1);
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_OVERFLOW_EN, 1);
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_HW_FREQ_DIV_EN, 1);
val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_THERMAL_RST_EN, 1);
writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
mutex_unlock(&tzd->lock);
err = thermal_zone_device_enable(tzd);
if (err) {
dev_err(ts->dev, "ch%u: failed to enable zone: %d\n", id, err);
return err;
}
return 0;
}
static bool tegra_tsensor_fuse_read_spare(unsigned int spare)
{
u32 val = 0;
tegra_fuse_readl(TEGRA30_FUSE_SPARE_BIT + spare * 4, &val);
return !!val;
}
static int tegra_tsensor_nvmem_setup(struct tegra_tsensor *ts)
{
u32 i, ate_ver = 0, cal = 0, t1_25C = 0, t2_90C = 0;
int err, c1_25C, c2_90C;
err = tegra_fuse_readl(TEGRA30_FUSE_TEST_PROG_VER, &ate_ver);
if (err) {
dev_err_probe(ts->dev, err, "failed to get ATE version\n");
return err;
}
if (ate_ver < 8) {
dev_info(ts->dev, "unsupported ATE version: %u\n", ate_ver);
return -ENODEV;
}
/*
* We have two TSENSOR channels in a two different spots on SoC.
* Second channel provides more accurate data on older SoC versions,
* use it as a primary channel.
*/
if (ate_ver <= 21) {
dev_info_once(ts->dev,
"older ATE version detected, channels remapped\n");
ts->swap_channels = true;
}
err = tegra_fuse_readl(TEGRA30_FUSE_TSENSOR_CALIB, &cal);
if (err) {
dev_err(ts->dev, "failed to get calibration data: %d\n", err);
return err;
}
/* get calibrated counter values for 25C/90C thresholds */
c1_25C = FIELD_GET(TEGRA30_FUSE_TSENSOR_CALIB_LOW, cal);
c2_90C = FIELD_GET(TEGRA30_FUSE_TSENSOR_CALIB_HIGH, cal);
/* and calibrated temperatures corresponding to the counter values */
for (i = 0; i < 7; i++) {
t1_25C |= tegra_tsensor_fuse_read_spare(14 + i) << i;
t1_25C |= tegra_tsensor_fuse_read_spare(21 + i) << i;
t2_90C |= tegra_tsensor_fuse_read_spare(0 + i) << i;
t2_90C |= tegra_tsensor_fuse_read_spare(7 + i) << i;
}
if (c2_90C - c1_25C <= t2_90C - t1_25C) {
dev_err(ts->dev, "invalid calibration data: %d %d %u %u\n",
c2_90C, c1_25C, t2_90C, t1_25C);
return -EINVAL;
}
/* all calibration coefficients are premultiplied by 1000000 */
ts->calib.a = DIV_ROUND_CLOSEST((t2_90C - t1_25C) * 1000000,
(c2_90C - c1_25C));
ts->calib.b = t1_25C * 1000000 - ts->calib.a * c1_25C;
if (tegra_sku_info.revision == TEGRA_REVISION_A01) {
ts->calib.m = -2775;
ts->calib.n = 1338811;
ts->calib.p = -7300000;
} else {
ts->calib.m = -3512;
ts->calib.n = 1528943;
ts->calib.p = -11100000;
}
/* except the coefficient of a reduced quadratic equation */
ts->calib.r = DIV_ROUND_CLOSEST(ts->calib.n, ts->calib.m * 2);
dev_info_once(ts->dev,
"calibration: %d %d %u %u ATE ver: %u SoC rev: %u\n",
c2_90C, c1_25C, t2_90C, t1_25C, ate_ver,
tegra_sku_info.revision);
return 0;
}
static int tegra_tsensor_register_channel(struct tegra_tsensor *ts,
unsigned int id)
{
struct tegra_tsensor_channel *tsc = &ts->ch[id];
unsigned int hw_id = ts->swap_channels ? !id : id;
tsc->ts = ts;
tsc->id = id;
tsc->regs = ts->regs + 0x40 * (hw_id + 1);
tsc->tzd = devm_thermal_zone_of_sensor_register(ts->dev, id, tsc, &ops);
if (IS_ERR(tsc->tzd)) {
if (PTR_ERR(tsc->tzd) != -ENODEV)
return dev_err_probe(ts->dev, PTR_ERR(tsc->tzd),
"failed to register thermal zone\n");
/*
* It's okay if sensor isn't assigned to any thermal zone
* in a device-tree.
*/
tsc->tzd = NULL;
return 0;
}
if (devm_thermal_add_hwmon_sysfs(tsc->tzd))
dev_warn(ts->dev, "failed to add hwmon sysfs attributes\n");
return 0;
}
static int tegra_tsensor_probe(struct platform_device *pdev)
{
struct tegra_tsensor *ts;
unsigned int i;
int err, irq;
ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL);
if (!ts)
return -ENOMEM;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ts->dev = &pdev->dev;
platform_set_drvdata(pdev, ts);
ts->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ts->regs))
return PTR_ERR(ts->regs);
ts->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(ts->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(ts->clk),
"failed to get clock\n");
ts->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (IS_ERR(ts->rst))
return dev_err_probe(&pdev->dev, PTR_ERR(ts->rst),
"failed to get reset control\n");
err = tegra_tsensor_nvmem_setup(ts);
if (err)
return err;
err = tegra_tsensor_hw_enable(ts);
if (err)
return err;
err = devm_add_action_or_reset(&pdev->dev,
devm_tegra_tsensor_hw_disable,
ts);
if (err)
return err;
for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
err = tegra_tsensor_register_channel(ts, i);
if (err)
return err;
}
err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
tegra_tsensor_isr, IRQF_ONESHOT,
"tegra_tsensor", ts);
if (err)
return dev_err_probe(&pdev->dev, err,
"failed to request interrupt\n");
for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
err = tegra_tsensor_enable_hw_channel(ts, i);
if (err)
return err;
}
return 0;
}
static int __maybe_unused tegra_tsensor_suspend(struct device *dev)
{
struct tegra_tsensor *ts = dev_get_drvdata(dev);
unsigned int i;
int err;
for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
err = tegra_tsensor_disable_hw_channel(ts, i);
if (err)
goto enable_channel;
}
err = tegra_tsensor_hw_disable(ts);
if (err)
goto enable_channel;
return 0;
enable_channel:
while (i--)
tegra_tsensor_enable_hw_channel(ts, i);
return err;
}
static int __maybe_unused tegra_tsensor_resume(struct device *dev)
{
struct tegra_tsensor *ts = dev_get_drvdata(dev);
unsigned int i;
int err;
err = tegra_tsensor_hw_enable(ts);
if (err)
return err;
for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
err = tegra_tsensor_enable_hw_channel(ts, i);
if (err)
return err;
}
return 0;
}
static const struct dev_pm_ops tegra_tsensor_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_tsensor_suspend,
tegra_tsensor_resume)
};
static const struct of_device_id tegra_tsensor_of_match[] = {
{ .compatible = "nvidia,tegra30-tsensor", },
{},
};
MODULE_DEVICE_TABLE(of, tegra_tsensor_of_match);
static struct platform_driver tegra_tsensor_driver = {
.probe = tegra_tsensor_probe,
.driver = {
.name = "tegra30-tsensor",
.of_match_table = tegra_tsensor_of_match,
.pm = &tegra_tsensor_pm_ops,
},
};
module_platform_driver(tegra_tsensor_driver);
MODULE_DESCRIPTION("NVIDIA Tegra30 Thermal Sensor driver");
MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
MODULE_LICENSE("GPL");
...@@ -109,6 +109,12 @@ extern int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, ...@@ -109,6 +109,12 @@ extern int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt,
u32 *resp); u32 *resp);
extern int qcom_scm_qsmmu500_wait_safe_toggle(bool en); extern int qcom_scm_qsmmu500_wait_safe_toggle(bool en);
extern int qcom_scm_lmh_dcvsh(u32 payload_fn, u32 payload_reg, u32 payload_val,
u64 limit_node, u32 node_id, u64 version);
extern int qcom_scm_lmh_profile_change(u32 profile_id);
extern bool qcom_scm_lmh_dcvsh_available(void);
#else #else
#include <linux/errno.h> #include <linux/errno.h>
...@@ -170,5 +176,13 @@ static inline int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, ...@@ -170,5 +176,13 @@ static inline int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt,
static inline int qcom_scm_qsmmu500_wait_safe_toggle(bool en) static inline int qcom_scm_qsmmu500_wait_safe_toggle(bool en)
{ return -ENODEV; } { return -ENODEV; }
static inline int qcom_scm_lmh_dcvsh(u32 payload_fn, u32 payload_reg, u32 payload_val,
u64 limit_node, u32 node_id, u64 version)
{ return -ENODEV; }
static inline int qcom_scm_lmh_profile_change(u32 profile_id) { return -ENODEV; }
static inline bool qcom_scm_lmh_dcvsh_available(void) { return -ENODEV; }
#endif #endif
#endif #endif
...@@ -285,7 +285,7 @@ struct thermal_zone_params { ...@@ -285,7 +285,7 @@ struct thermal_zone_params {
}; };
/** /**
* struct thermal_zone_of_device_ops - scallbacks for handling DT based zones * struct thermal_zone_of_device_ops - callbacks for handling DT based zones
* *
* Mandatory: * Mandatory:
* @get_temp: a pointer to a function that reads the sensor temperature. * @get_temp: a pointer to a function that reads the sensor temperature.
...@@ -404,12 +404,13 @@ static inline void thermal_zone_device_unregister( ...@@ -404,12 +404,13 @@ static inline void thermal_zone_device_unregister(
struct thermal_zone_device *tz) struct thermal_zone_device *tz)
{ } { }
static inline struct thermal_cooling_device * static inline struct thermal_cooling_device *
thermal_cooling_device_register(char *type, void *devdata, thermal_cooling_device_register(const char *type, void *devdata,
const struct thermal_cooling_device_ops *ops) const struct thermal_cooling_device_ops *ops)
{ return ERR_PTR(-ENODEV); } { return ERR_PTR(-ENODEV); }
static inline struct thermal_cooling_device * static inline struct thermal_cooling_device *
thermal_of_cooling_device_register(struct device_node *np, thermal_of_cooling_device_register(struct device_node *np,
char *type, void *devdata, const struct thermal_cooling_device_ops *ops) const char *type, void *devdata,
const struct thermal_cooling_device_ops *ops)
{ return ERR_PTR(-ENODEV); } { return ERR_PTR(-ENODEV); }
static inline struct thermal_cooling_device * static inline struct thermal_cooling_device *
devm_thermal_of_cooling_device_register(struct device *dev, devm_thermal_of_cooling_device_register(struct device *dev,
......
...@@ -10,10 +10,9 @@ override CFLAGS+= $(call cc-option,-O3,-O1) ${WARNFLAGS} ...@@ -10,10 +10,9 @@ override CFLAGS+= $(call cc-option,-O3,-O1) ${WARNFLAGS}
# Add "-fstack-protector" only if toolchain supports it. # Add "-fstack-protector" only if toolchain supports it.
override CFLAGS+= $(call cc-option,-fstack-protector-strong) override CFLAGS+= $(call cc-option,-fstack-protector-strong)
CC?= $(CROSS_COMPILE)gcc CC?= $(CROSS_COMPILE)gcc
PKG_CONFIG?= pkg-config PKG_CONFIG?= $(CROSS_COMPILE)pkg-config
override CFLAGS+=-D VERSION=\"$(VERSION)\" override CFLAGS+=-D VERSION=\"$(VERSION)\"
LDFLAGS+=
TARGET=tmon TARGET=tmon
INSTALL_PROGRAM=install -m 755 -p INSTALL_PROGRAM=install -m 755 -p
...@@ -33,7 +32,6 @@ override CFLAGS += $(shell $(PKG_CONFIG) --cflags $(STATIC) panelw ncursesw 2> / ...@@ -33,7 +32,6 @@ override CFLAGS += $(shell $(PKG_CONFIG) --cflags $(STATIC) panelw ncursesw 2> /
$(PKG_CONFIG) --cflags $(STATIC) panel ncurses 2> /dev/null) $(PKG_CONFIG) --cflags $(STATIC) panel ncurses 2> /dev/null)
OBJS = tmon.o tui.o sysfs.o pid.o OBJS = tmon.o tui.o sysfs.o pid.o
OBJS +=
tmon: $(OBJS) Makefile tmon.h tmon: $(OBJS) Makefile tmon.h
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(TARGET) $(TMON_LIBS) $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(TARGET) $(TMON_LIBS)
...@@ -42,15 +40,13 @@ valgrind: tmon ...@@ -42,15 +40,13 @@ valgrind: tmon
sudo valgrind -v --track-origins=yes --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes ./$(TARGET) 1> /dev/null sudo valgrind -v --track-origins=yes --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes ./$(TARGET) 1> /dev/null
install: install:
- mkdir -p $(INSTALL_ROOT)/$(BINDIR) - $(INSTALL_PROGRAM) -D "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
- $(INSTALL_PROGRAM) "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
uninstall: uninstall:
$(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" $(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
clean: clean:
find . -name "*.o" | xargs $(DEL_FILE) rm -f $(TARGET) $(OBJS)
rm -f $(TARGET)
dist: dist:
git tag v$(VERSION) git tag v$(VERSION)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment