Merge tag 'thermal-v5.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/therma...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 11 Sep 2021 16:20:57 +0000 (09:20 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 11 Sep 2021 16:20:57 +0000 (09:20 -0700)
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

21 files changed:
Documentation/devicetree/bindings/thermal/qcom-lmh.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/thermal/thermal-zones.yaml
MAINTAINERS
drivers/firmware/qcom_scm.c
drivers/firmware/qcom_scm.h
drivers/thermal/intel/int340x_thermal/int3400_thermal.c
drivers/thermal/intel/intel_powerclamp.c
drivers/thermal/intel/intel_tcc_cooling.c
drivers/thermal/qcom/Kconfig
drivers/thermal/qcom/Makefile
drivers/thermal/qcom/lmh.c [new file with mode: 0644]
drivers/thermal/qcom/qcom-spmi-adc-tm5.c
drivers/thermal/rcar_gen3_thermal.c
drivers/thermal/samsung/exynos_tmu.c
drivers/thermal/tegra/Kconfig
drivers/thermal/tegra/Makefile
drivers/thermal/tegra/soctherm.c
drivers/thermal/tegra/tegra30-tsensor.c [new file with mode: 0644]
include/linux/qcom_scm.h
include/linux/thermal.h
tools/thermal/tmon/Makefile

diff --git a/Documentation/devicetree/bindings/thermal/qcom-lmh.yaml b/Documentation/devicetree/bindings/thermal/qcom-lmh.yaml
new file mode 100644 (file)
index 0000000..289e9a8
--- /dev/null
@@ -0,0 +1,82 @@
+# 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>;
+    };
index 164f715..a07de5e 100644 (file)
@@ -215,7 +215,7 @@ patternProperties:
       - polling-delay
       - polling-delay-passive
       - thermal-sensors
-      - trips
+
     additionalProperties: false
 
 additionalProperties: false
index 0590fe1..947c0cd 100644 (file)
@@ -18566,6 +18566,7 @@ F:      drivers/thermal/
 F:     include/linux/cpu_cooling.h
 F:     include/linux/thermal.h
 F:     include/uapi/linux/thermal.h
+F:     tools/thermal/
 
 THERMAL DRIVER FOR AMLOGIC SOCS
 M:     Guillaume La Roque <glaroque@baylibre.com>
index ced1964..2ee97ba 100644 (file)
@@ -1147,6 +1147,64 @@ int qcom_scm_qsmmu500_wait_safe_toggle(bool en)
 }
 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)
 {
        struct device_node *tcsr;
index 632fe31..d92156c 100644 (file)
@@ -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_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_SMMU_CONFIG_ERRATA1           0x03
 #define QCOM_SCM_SMMU_CONFIG_ERRATA1_CLIENT_ALL        0x02
index 823354a..19926be 100644 (file)
@@ -108,9 +108,12 @@ static struct attribute *imok_attr[] = {
        NULL
 };
 
+static const struct attribute_group imok_attribute_group = {
+       .attrs = imok_attr,
+};
+
 static const struct attribute_group data_attribute_group = {
        .bin_attrs = data_attributes,
-       .attrs = imok_attr,
 };
 
 static ssize_t available_uuids_show(struct device *dev,
@@ -522,6 +525,12 @@ static int int3400_thermal_probe(struct platform_device *pdev)
        if (result)
                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) {
                result = sysfs_create_group(&pdev->dev.kobj,
                                            &data_attribute_group);
@@ -545,6 +554,8 @@ free_sysfs:
        }
 free_uuid:
        sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
+free_imok:
+       sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
 free_rel_misc:
        if (!priv->rel_misc_dev_res)
                acpi_thermal_rel_misc_device_remove(priv->adev->handle);
@@ -573,6 +584,7 @@ static int int3400_thermal_remove(struct platform_device *pdev)
        if (priv->data_vault)
                sysfs_remove_group(&pdev->dev.kobj, &data_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);
        kfree(priv->data_vault);
        kfree(priv->trts);
index b0eb5ec..a5b58ea 100644 (file)
@@ -528,7 +528,7 @@ static int start_power_clamp(void)
 
        set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1);
        /* prevent cpu hotplug */
-       get_online_cpus();
+       cpus_read_lock();
 
        /* prefer BSP */
        control_cpu = 0;
@@ -542,7 +542,7 @@ static int start_power_clamp(void)
        for_each_online_cpu(cpu) {
                start_power_clamp_worker(cpu);
        }
-       put_online_cpus();
+       cpus_read_unlock();
 
        return 0;
 }
index 8ec10d5..cd80c7d 100644 (file)
@@ -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_L, NULL),
        X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, NULL),
+       X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, NULL),
+       X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, NULL),
        {}
 };
 
index 8d5ac2d..7d942f7 100644 (file)
@@ -31,3 +31,13 @@ config QCOM_SPMI_TEMP_ALARM
          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
          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.
index 252ea7d..0fa2512 100644 (file)
@@ -5,3 +5,4 @@ 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
diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c
new file mode 100644 (file)
index 0000000..eafa752
--- /dev/null
@@ -0,0 +1,232 @@
+// 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");
index 232fd0b..8494cc0 100644 (file)
@@ -359,6 +359,12 @@ static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm)
                                                           &adc_tm->channels[i],
                                                           &adc_tm5_ops);
                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",
                                adc_tm->channels[i].channel, PTR_ERR(tzd));
                        return PTR_ERR(tzd);
index fdf16aa..85228d3 100644 (file)
@@ -84,7 +84,7 @@ struct rcar_gen3_thermal_tsc {
        struct thermal_zone_device *zone;
        struct equation_coefs coef;
        int tj_t;
-       int id; /* thermal channel id */
+       unsigned int id; /* thermal channel id */
 };
 
 struct rcar_gen3_thermal_priv {
@@ -190,10 +190,64 @@ static int rcar_gen3_thermal_get_temp(void *devdata, int *temp)
        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,
+       .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[] = {
        { .soc_id = "r8a7795", .revision = "ES1.*" },
        { /* sentinel */ }
@@ -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_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,
                                CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN);
@@ -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_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 |= THCTR_THSST;
@@ -303,6 +363,34 @@ static void rcar_gen3_hwmon_action(void *data)
        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)
 {
        struct rcar_gen3_thermal_priv *priv;
@@ -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);
        struct resource *res;
        struct thermal_zone_device *zone;
-       int ret, i;
+       unsigned int i;
+       int ret;
 
        /* default values if FUSEs are missing */
        /* TODO: Read values from hardware on supported platforms */
@@ -326,6 +415,9 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
 
        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_get_sync(dev);
 
@@ -351,9 +443,6 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
 
                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,
                                                            &rcar_gen3_tz_of_ops);
                if (IS_ERR(zone)) {
@@ -363,6 +452,9 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
                }
                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;
                ret = thermal_add_hwmon_sysfs(tsc->zone);
                if (ret)
@@ -376,7 +468,7 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
                if (ret < 0)
                        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;
@@ -401,8 +493,12 @@ static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
 
        for (i = 0; i < priv->num_tscs; i++) {
                struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
+               struct thermal_zone_device *zone = tsc->zone;
 
                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;
index e9a90bc..f4ab4c5 100644 (file)
@@ -1073,6 +1073,7 @@ static int exynos_tmu_probe(struct platform_device *pdev)
                data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
                if (IS_ERR(data->sclk)) {
                        dev_err(&pdev->dev, "Failed to get sclk\n");
+                       ret = PTR_ERR(data->sclk);
                        goto err_clk;
                } else {
                        ret = clk_prepare_enable(data->sclk);
index 46c2215..cfa41d8 100644 (file)
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 menu "NVIDIA Tegra thermal drivers"
-depends on ARCH_TEGRA
+depends on ARCH_TEGRA || COMPILE_TEST
 
 config TEGRA_SOCTHERM
        tristate "Tegra SOCTHERM thermal management"
@@ -18,4 +18,11 @@ config TEGRA_BPMP_THERMAL
          Enable this option for support for sensing system temperature of NVIDIA
          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
index 0f2b66e..eb27d19 100644 (file)
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_TEGRA_SOCTHERM)           += tegra-soctherm.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-$(CONFIG_ARCH_TEGRA_124_SOC)    += tegra124-soctherm.o
index 8e303e9..210325f 100644 (file)
@@ -450,8 +450,8 @@ static int enforce_temp_range(struct device *dev, int trip_temp)
 
        temp = clamp_val(trip_temp, min_low_temp, max_high_temp);
        if (temp != trip_temp)
-               dev_info(dev, "soctherm: trip temperature %d forced to %d\n",
-                        trip_temp, temp);
+               dev_dbg(dev, "soctherm: trip temperature %d forced to %d\n",
+                       trip_temp, temp);
        return temp;
 }
 
diff --git a/drivers/thermal/tegra/tegra30-tsensor.c b/drivers/thermal/tegra/tegra30-tsensor.c
new file mode 100644 (file)
index 0000000..9b6b693
--- /dev/null
@@ -0,0 +1,673 @@
+// 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");
index 0165824..c0475d1 100644 (file)
@@ -109,6 +109,12 @@ extern int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt,
                             u32 *resp);
 
 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
 
 #include <linux/errno.h>
@@ -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)
                { 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
index d296f3b..c314893 100644 (file)
@@ -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:
  * @get_temp: a pointer to a function that reads the sensor temperature.
@@ -404,12 +404,13 @@ static inline void thermal_zone_device_unregister(
        struct thermal_zone_device *tz)
 { }
 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)
 { return ERR_PTR(-ENODEV); }
 static inline struct thermal_cooling_device *
 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); }
 static inline struct thermal_cooling_device *
 devm_thermal_of_cooling_device_register(struct device *dev,
index 9db867d..f9c52b7 100644 (file)
@@ -10,10 +10,9 @@ override CFLAGS+= $(call cc-option,-O3,-O1) ${WARNFLAGS}
 # Add "-fstack-protector" only if toolchain supports it.
 override CFLAGS+= $(call cc-option,-fstack-protector-strong)
 CC?= $(CROSS_COMPILE)gcc
-PKG_CONFIG?= pkg-config
+PKG_CONFIG?= $(CROSS_COMPILE)pkg-config
 
 override CFLAGS+=-D VERSION=\"$(VERSION)\"
-LDFLAGS+=
 TARGET=tmon
 
 INSTALL_PROGRAM=install -m 755 -p
@@ -33,7 +32,6 @@ override CFLAGS += $(shell $(PKG_CONFIG) --cflags $(STATIC) panelw ncursesw 2> /
                     $(PKG_CONFIG) --cflags $(STATIC) panel ncurses 2> /dev/null)
 
 OBJS = tmon.o tui.o sysfs.o pid.o
-OBJS +=
 
 tmon: $(OBJS) Makefile tmon.h
        $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS)  -o $(TARGET) $(TMON_LIBS)
@@ -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
 
 install:
-       - mkdir -p $(INSTALL_ROOT)/$(BINDIR)
-       - $(INSTALL_PROGRAM) "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
+       - $(INSTALL_PROGRAM) -D "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
 
 uninstall:
        $(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
 
 clean:
-       find . -name "*.o" | xargs $(DEL_FILE)
-       rm -f $(TARGET)
+       rm -f $(TARGET) $(OBJS)
 
 dist:
        git tag v$(VERSION)