mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-24 04:50:53 +07:00
Merge branch 'thermal/next' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux
Pull thermal management updates from Zhang Rui: - Fix a deadlock regression in thermal core framework, which was introduced in 5.3 (Wei Wang) - Initialize thermal control framework earlier to enable thermal mitigation during boot (Amit Kucheria) - Convert the Intelligent Power Allocator (IPA) thermal governor to follow the generic PM_EM instead of its own Energy Model (Quentin Perret) - Introduce a new Amlogic soc thermal driver (Guillaume La Roque) - Add interrupt support for tsens thermal driver (Amit Kucheria) - Add support for MSM8956/8976 in tsens thermal driver (AngeloGioacchino Del Regno) - Add support for r8a774b1 in rcar thermal driver (Biju Das) - Add support for Thermal Monitor Unit v2 in qoriq thermal driver (Yuantian Tang) - Some other fixes/cleanups on thermal core framework and soc thermal drivers (Colin Ian King, Daniel Lezcano, Hsin-Yi Wang, Tian Tao) * 'thermal/next' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux: (32 commits) thermal: Fix deadlock in thermal thermal_zone_device_check thermal: cpu_cooling: Migrate to using the EM framework thermal: cpu_cooling: Make the power-related code depend on IPA PM / EM: Declare EM data types unconditionally arm64: defconfig: Enable CONFIG_ENERGY_MODEL drivers: thermal: tsens: fix potential integer overflow on multiply thermal: cpu_cooling: Reorder the header file thermal: cpu_cooling: Remove pointless dependency on CONFIG_OF thermal: no need to set .owner when using module_platform_driver thermal: qcom: tsens-v1: Fix kfree of a non-pointer value cpufreq: qcom-hw: Move driver initialization earlier clk: qcom: Initialize clock drivers earlier cpufreq: Initialize cpufreq-dt driver earlier cpufreq: Initialize the governors in core_initcall thermal: Initialize thermal subsystem earlier thermal: Remove netlink support dt: thermal: tsens: Document compatible for MSM8976/56 thermal: qcom: tsens-v1: Add support for MSM8956 and MSM8976 MAINTAINERS: add entry for Amlogic Thermal driver thermal: amlogic: Add thermal driver to support G12 SoCs ...
This commit is contained in:
commit
fb3da48a86
@ -0,0 +1,54 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/thermal/amlogic,thermal.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Amlogic Thermal
|
||||
|
||||
maintainers:
|
||||
- Guillaume La Roque <glaroque@baylibre.com>
|
||||
|
||||
description: Binding for Amlogic Thermal
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- amlogic,g12a-cpu-thermal
|
||||
- amlogic,g12a-ddr-thermal
|
||||
- const: amlogic,g12a-thermal
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
amlogic,ao-secure:
|
||||
description: phandle to the ao-secure syscon
|
||||
$ref: '/schemas/types.yaml#/definitions/phandle'
|
||||
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- amlogic,ao-secure
|
||||
|
||||
examples:
|
||||
- |
|
||||
cpu_temp: temperature-sensor@ff634800 {
|
||||
compatible = "amlogic,g12a-cpu-thermal",
|
||||
"amlogic,g12a-thermal";
|
||||
reg = <0xff634800 0x50>;
|
||||
interrupts = <0x0 0x24 0x0>;
|
||||
clocks = <&clk 164>;
|
||||
#thermal-sensor-cells = <0>;
|
||||
amlogic,ao-secure = <&sec_AO>;
|
||||
};
|
||||
...
|
@ -1,55 +0,0 @@
|
||||
* QCOM SoC Temperature Sensor (TSENS)
|
||||
|
||||
Required properties:
|
||||
- compatible:
|
||||
Must be one of the following:
|
||||
- "qcom,msm8916-tsens" (MSM8916)
|
||||
- "qcom,msm8974-tsens" (MSM8974)
|
||||
- "qcom,msm8996-tsens" (MSM8996)
|
||||
- "qcom,qcs404-tsens", "qcom,tsens-v1" (QCS404)
|
||||
- "qcom,msm8998-tsens", "qcom,tsens-v2" (MSM8998)
|
||||
- "qcom,sdm845-tsens", "qcom,tsens-v2" (SDM845)
|
||||
The generic "qcom,tsens-v2" property must be used as a fallback for any SoC
|
||||
with version 2 of the TSENS IP. MSM8996 is the only exception because the
|
||||
generic property did not exist when support was added.
|
||||
Similarly, the generic "qcom,tsens-v1" property must be used as a fallback for
|
||||
any SoC with version 1 of the TSENS IP.
|
||||
|
||||
- reg: Address range of the thermal registers.
|
||||
New platforms containing v2.x.y of the TSENS IP must specify the SROT and TM
|
||||
register spaces separately, with order being TM before SROT.
|
||||
See Example 2, below.
|
||||
|
||||
- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description.
|
||||
- #qcom,sensors: Number of sensors in tsens block
|
||||
- Refer to Documentation/devicetree/bindings/nvmem/nvmem.txt to know how to specify
|
||||
nvmem cells
|
||||
|
||||
Example 1 (legacy support before a fallback tsens-v2 property was introduced):
|
||||
tsens: thermal-sensor@900000 {
|
||||
compatible = "qcom,msm8916-tsens";
|
||||
reg = <0x4a8000 0x2000>;
|
||||
nvmem-cells = <&tsens_caldata>, <&tsens_calsel>;
|
||||
nvmem-cell-names = "caldata", "calsel";
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
||||
Example 2 (for any platform containing v2 of the TSENS IP):
|
||||
tsens0: thermal-sensor@c263000 {
|
||||
compatible = "qcom,sdm845-tsens", "qcom,tsens-v2";
|
||||
reg = <0xc263000 0x1ff>, /* TM */
|
||||
<0xc222000 0x1ff>; /* SROT */
|
||||
#qcom,sensors = <13>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
||||
Example 3 (for any platform containing v1 of the TSENS IP):
|
||||
tsens: thermal-sensor@4a9000 {
|
||||
compatible = "qcom,qcs404-tsens", "qcom,tsens-v1";
|
||||
reg = <0x004a9000 0x1000>, /* TM */
|
||||
<0x004a8000 0x1000>; /* SROT */
|
||||
nvmem-cells = <&tsens_caldata>;
|
||||
nvmem-cell-names = "calib";
|
||||
#qcom,sensors = <10>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
170
Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
Normal file
170
Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
Normal file
@ -0,0 +1,170 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR MIT)
|
||||
# Copyright 2019 Linaro Ltd.
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/thermal/qcom-tsens.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: QCOM SoC Temperature Sensor (TSENS)
|
||||
|
||||
maintainers:
|
||||
- Amit Kucheria <amit.kucheria@linaro.org>
|
||||
|
||||
description: |
|
||||
QCOM SoCs have TSENS IP to allow temperature measurement. There are currently
|
||||
three distinct major versions of the IP that is supported by a single driver.
|
||||
The IP versions are named v0.1, v1 and v2 in the driver, where v0.1 captures
|
||||
everything before v1 when there was no versioning information.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- description: v0.1 of TSENS
|
||||
items:
|
||||
- enum:
|
||||
- qcom,msm8916-tsens
|
||||
- qcom,msm8974-tsens
|
||||
- const: qcom,tsens-v0_1
|
||||
|
||||
- description: v1 of TSENS
|
||||
items:
|
||||
- enum:
|
||||
- qcom,msm8976-tsens
|
||||
- qcom,qcs404-tsens
|
||||
- const: qcom,tsens-v1
|
||||
|
||||
- description: v2 of TSENS
|
||||
items:
|
||||
- enum:
|
||||
- qcom,msm8996-tsens
|
||||
- qcom,msm8998-tsens
|
||||
- qcom,sdm845-tsens
|
||||
- const: qcom,tsens-v2
|
||||
|
||||
reg:
|
||||
maxItems: 2
|
||||
items:
|
||||
- description: TM registers
|
||||
- description: SROT registers
|
||||
|
||||
nvmem-cells:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
description:
|
||||
Reference to an nvmem node for the calibration data
|
||||
|
||||
nvmem-cells-names:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
items:
|
||||
- enum:
|
||||
- caldata
|
||||
- calsel
|
||||
|
||||
"#qcom,sensors":
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32
|
||||
- minimum: 1
|
||||
- maximum: 16
|
||||
description:
|
||||
Number of sensors enabled on this platform
|
||||
|
||||
"#thermal-sensor-cells":
|
||||
const: 1
|
||||
description:
|
||||
Number of cells required to uniquely identify the thermal sensors. Since
|
||||
we have multiple sensors this is set to 1
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,msm8916-tsens
|
||||
- qcom,msm8974-tsens
|
||||
- qcom,msm8976-tsens
|
||||
- qcom,qcs404-tsens
|
||||
- qcom,tsens-v0_1
|
||||
- qcom,tsens-v1
|
||||
then:
|
||||
properties:
|
||||
interrupts:
|
||||
items:
|
||||
- description: Combined interrupt if upper or lower threshold crossed
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: uplow
|
||||
|
||||
else:
|
||||
properties:
|
||||
interrupts:
|
||||
items:
|
||||
- description: Combined interrupt if upper or lower threshold crossed
|
||||
- description: Interrupt if critical threshold crossed
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: uplow
|
||||
- const: critical
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- "#qcom,sensors"
|
||||
- interrupts
|
||||
- interrupt-names
|
||||
- "#thermal-sensor-cells"
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
// Example 1 (legacy: for pre v1 IP):
|
||||
tsens1: thermal-sensor@900000 {
|
||||
compatible = "qcom,msm8916-tsens", "qcom,tsens-v0_1";
|
||||
reg = <0x4a9000 0x1000>, /* TM */
|
||||
<0x4a8000 0x1000>; /* SROT */
|
||||
|
||||
nvmem-cells = <&tsens_caldata>, <&tsens_calsel>;
|
||||
nvmem-cell-names = "caldata", "calsel";
|
||||
|
||||
interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "uplow";
|
||||
|
||||
#qcom,sensors = <5>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
// Example 2 (for any platform containing v1 of the TSENS IP):
|
||||
tsens2: thermal-sensor@4a9000 {
|
||||
compatible = "qcom,qcs404-tsens", "qcom,tsens-v1";
|
||||
reg = <0x004a9000 0x1000>, /* TM */
|
||||
<0x004a8000 0x1000>; /* SROT */
|
||||
|
||||
nvmem-cells = <&tsens_caldata>;
|
||||
nvmem-cell-names = "calib";
|
||||
|
||||
interrupts = <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "uplow";
|
||||
|
||||
#qcom,sensors = <10>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
// Example 3 (for any platform containing v2 of the TSENS IP):
|
||||
tsens3: thermal-sensor@c263000 {
|
||||
compatible = "qcom,sdm845-tsens", "qcom,tsens-v2";
|
||||
reg = <0xc263000 0x1ff>,
|
||||
<0xc222000 0x1ff>;
|
||||
|
||||
interrupts = <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "uplow", "critical";
|
||||
|
||||
#qcom,sensors = <13>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
...
|
@ -8,6 +8,7 @@ Required properties:
|
||||
- compatible : "renesas,<soctype>-thermal",
|
||||
Examples with soctypes are:
|
||||
- "renesas,r8a774a1-thermal" (RZ/G2M)
|
||||
- "renesas,r8a774b1-thermal" (RZ/G2N)
|
||||
- "renesas,r8a7795-thermal" (R-Car H3)
|
||||
- "renesas,r8a7796-thermal" (R-Car M3-W)
|
||||
- "renesas,r8a77965-thermal" (R-Car M3-N)
|
||||
|
@ -725,24 +725,10 @@ method, the sys I/F structure will be built like this::
|
||||
|---temp1_input: 37000
|
||||
|---temp1_crit: 100000
|
||||
|
||||
4. Event Notification
|
||||
4. Export Symbol APIs
|
||||
=====================
|
||||
|
||||
The framework includes a simple notification mechanism, in the form of a
|
||||
netlink event. Netlink socket initialization is done during the _init_
|
||||
of the framework. Drivers which intend to use the notification mechanism
|
||||
just need to call thermal_generate_netlink_event() with two arguments viz
|
||||
(originator, event). The originator is a pointer to struct thermal_zone_device
|
||||
from where the event has been originated. An integer which represents the
|
||||
thermal zone device will be used in the message to identify the zone. The
|
||||
event will be one of:{THERMAL_AUX0, THERMAL_AUX1, THERMAL_CRITICAL,
|
||||
THERMAL_DEV_FAULT}. Notification can be sent when the current temperature
|
||||
crosses any of the configured thresholds.
|
||||
|
||||
5. Export Symbol APIs
|
||||
=====================
|
||||
|
||||
5.1. get_tz_trend
|
||||
4.1. get_tz_trend
|
||||
-----------------
|
||||
|
||||
This function returns the trend of a thermal zone, i.e the rate of change
|
||||
@ -751,14 +737,14 @@ are supposed to implement the callback. If they don't, the thermal
|
||||
framework calculated the trend by comparing the previous and the current
|
||||
temperature values.
|
||||
|
||||
5.2. get_thermal_instance
|
||||
4.2. get_thermal_instance
|
||||
-------------------------
|
||||
|
||||
This function returns the thermal_instance corresponding to a given
|
||||
{thermal_zone, cooling_device, trip_point} combination. Returns NULL
|
||||
if such an instance does not exist.
|
||||
|
||||
5.3. thermal_notify_framework
|
||||
4.3. thermal_notify_framework
|
||||
-----------------------------
|
||||
|
||||
This function handles the trip events from sensor drivers. It starts
|
||||
@ -768,14 +754,14 @@ and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
|
||||
The throttling policy is based on the configured platform data; if no
|
||||
platform data is provided, this uses the step_wise throttling policy.
|
||||
|
||||
5.4. thermal_cdev_update
|
||||
4.4. thermal_cdev_update
|
||||
------------------------
|
||||
|
||||
This function serves as an arbitrator to set the state of a cooling
|
||||
device. It sets the cooling device to the deepest cooling state if
|
||||
possible.
|
||||
|
||||
6. thermal_emergency_poweroff
|
||||
5. thermal_emergency_poweroff
|
||||
=============================
|
||||
|
||||
On an event of critical trip temperature crossing. Thermal framework
|
||||
|
10
MAINTAINERS
10
MAINTAINERS
@ -13688,6 +13688,7 @@ L: linux-pm@vger.kernel.org
|
||||
L: linux-arm-msm@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/thermal/qcom/
|
||||
F: Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
|
||||
|
||||
QUALCOMM VENUS VIDEO ACCELERATOR DRIVER
|
||||
M: Stanimir Varbanov <stanimir.varbanov@linaro.org>
|
||||
@ -16310,6 +16311,15 @@ F: Documentation/driver-api/thermal/cpu-cooling-api.rst
|
||||
F: drivers/thermal/cpu_cooling.c
|
||||
F: include/linux/cpu_cooling.h
|
||||
|
||||
THERMAL DRIVER FOR AMLOGIC SOCS
|
||||
M: Guillaume La Roque <glaroque@baylibre.com>
|
||||
L: linux-pm@vger.kernel.org
|
||||
L: linux-amlogic@lists.infradead.org
|
||||
W: http://linux-meson.com/
|
||||
S: Supported
|
||||
F: drivers/thermal/amlogic_thermal.c
|
||||
F: Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml
|
||||
|
||||
THINKPAD ACPI EXTRAS DRIVER
|
||||
M: Henrique de Moraes Holschuh <ibm-acpi@hmh.eng.br>
|
||||
L: ibm-acpi-devel@lists.sourceforge.net
|
||||
|
@ -71,6 +71,7 @@ CONFIG_COMPAT=y
|
||||
CONFIG_RANDOMIZE_BASE=y
|
||||
CONFIG_HIBERNATION=y
|
||||
CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y
|
||||
CONFIG_ENERGY_MODEL=y
|
||||
CONFIG_ARM_CPUIDLE=y
|
||||
CONFIG_ARM_PSCI_CPUIDLE=y
|
||||
CONFIG_CPU_FREQ=y
|
||||
|
@ -500,7 +500,7 @@ static int __init clk_rpmh_init(void)
|
||||
{
|
||||
return platform_driver_register(&clk_rpmh_driver);
|
||||
}
|
||||
subsys_initcall(clk_rpmh_init);
|
||||
core_initcall(clk_rpmh_init);
|
||||
|
||||
static void __exit clk_rpmh_exit(void)
|
||||
{
|
||||
|
@ -2855,7 +2855,7 @@ static int __init gcc_qcs404_init(void)
|
||||
{
|
||||
return platform_driver_register(&gcc_qcs404_driver);
|
||||
}
|
||||
subsys_initcall(gcc_qcs404_init);
|
||||
core_initcall(gcc_qcs404_init);
|
||||
|
||||
static void __exit gcc_qcs404_exit(void)
|
||||
{
|
||||
|
@ -3628,7 +3628,7 @@ static int __init gcc_sdm845_init(void)
|
||||
{
|
||||
return platform_driver_register(&gcc_sdm845_driver);
|
||||
}
|
||||
subsys_initcall(gcc_sdm845_init);
|
||||
core_initcall(gcc_sdm845_init);
|
||||
|
||||
static void __exit gcc_sdm845_exit(void)
|
||||
{
|
||||
|
@ -180,4 +180,4 @@ static int __init cpufreq_dt_platdev_init(void)
|
||||
-1, data,
|
||||
sizeof(struct cpufreq_dt_platform_data)));
|
||||
}
|
||||
device_initcall(cpufreq_dt_platdev_init);
|
||||
core_initcall(cpufreq_dt_platdev_init);
|
||||
|
@ -346,7 +346,7 @@ struct cpufreq_governor *cpufreq_default_governor(void)
|
||||
return CPU_FREQ_GOV_CONSERVATIVE;
|
||||
}
|
||||
|
||||
fs_initcall(cpufreq_gov_dbs_init);
|
||||
core_initcall(cpufreq_gov_dbs_init);
|
||||
#else
|
||||
module_init(cpufreq_gov_dbs_init);
|
||||
#endif
|
||||
|
@ -483,7 +483,7 @@ struct cpufreq_governor *cpufreq_default_governor(void)
|
||||
return CPU_FREQ_GOV_ONDEMAND;
|
||||
}
|
||||
|
||||
fs_initcall(cpufreq_gov_dbs_init);
|
||||
core_initcall(cpufreq_gov_dbs_init);
|
||||
#else
|
||||
module_init(cpufreq_gov_dbs_init);
|
||||
#endif
|
||||
|
@ -50,5 +50,5 @@ MODULE_AUTHOR("Dominik Brodowski <linux@brodo.de>");
|
||||
MODULE_DESCRIPTION("CPUfreq policy governor 'performance'");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
fs_initcall(cpufreq_gov_performance_init);
|
||||
core_initcall(cpufreq_gov_performance_init);
|
||||
module_exit(cpufreq_gov_performance_exit);
|
||||
|
@ -43,7 +43,7 @@ struct cpufreq_governor *cpufreq_default_governor(void)
|
||||
return &cpufreq_gov_powersave;
|
||||
}
|
||||
|
||||
fs_initcall(cpufreq_gov_powersave_init);
|
||||
core_initcall(cpufreq_gov_powersave_init);
|
||||
#else
|
||||
module_init(cpufreq_gov_powersave_init);
|
||||
#endif
|
||||
|
@ -147,7 +147,7 @@ struct cpufreq_governor *cpufreq_default_governor(void)
|
||||
return &cpufreq_gov_userspace;
|
||||
}
|
||||
|
||||
fs_initcall(cpufreq_gov_userspace_init);
|
||||
core_initcall(cpufreq_gov_userspace_init);
|
||||
#else
|
||||
module_init(cpufreq_gov_userspace_init);
|
||||
#endif
|
||||
|
@ -334,7 +334,7 @@ static int __init qcom_cpufreq_hw_init(void)
|
||||
{
|
||||
return platform_driver_register(&qcom_cpufreq_hw_driver);
|
||||
}
|
||||
device_initcall(qcom_cpufreq_hw_init);
|
||||
postcore_initcall(qcom_cpufreq_hw_init);
|
||||
|
||||
static void __exit qcom_cpufreq_hw_exit(void)
|
||||
{
|
||||
|
@ -144,6 +144,7 @@ config THERMAL_GOV_USER_SPACE
|
||||
|
||||
config THERMAL_GOV_POWER_ALLOCATOR
|
||||
bool "Power allocator thermal governor"
|
||||
depends on ENERGY_MODEL
|
||||
help
|
||||
Enable this to manage platform thermals by dynamically
|
||||
allocating and limiting power to devices.
|
||||
@ -348,6 +349,17 @@ config MTK_THERMAL
|
||||
Enable this option if you want to have support for thermal management
|
||||
controller present in Mediatek SoCs
|
||||
|
||||
config AMLOGIC_THERMAL
|
||||
tristate "Amlogic Thermal Support"
|
||||
default ARCH_MESON
|
||||
depends on OF && ARCH_MESON
|
||||
help
|
||||
If you say yes here you get support for Amlogic Thermal
|
||||
for G12 SoC Family.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called amlogic_thermal.
|
||||
|
||||
menu "Intel thermal drivers"
|
||||
depends on X86 || X86_INTEL_QUARK || COMPILE_TEST
|
||||
source "drivers/thermal/intel/Kconfig"
|
||||
|
@ -54,3 +54,4 @@ obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
|
||||
obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
|
||||
obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o
|
||||
obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o
|
||||
obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o
|
||||
|
333
drivers/thermal/amlogic_thermal.c
Normal file
333
drivers/thermal/amlogic_thermal.c
Normal file
@ -0,0 +1,333 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Amlogic Thermal Sensor Driver
|
||||
*
|
||||
* Copyright (C) 2017 Huan Biao <huan.biao@amlogic.com>
|
||||
* Copyright (C) 2019 Guillaume La Roque <glaroque@baylibre.com>
|
||||
*
|
||||
* Register value to celsius temperature formulas:
|
||||
* Read_Val m * U
|
||||
* U = ---------, Uptat = ---------
|
||||
* 2^16 1 + n * U
|
||||
*
|
||||
* Temperature = A * ( Uptat + u_efuse / 2^16 )- B
|
||||
*
|
||||
* A B m n : calibration parameters
|
||||
* u_efuse : fused calibration value, it's a signed 16 bits value
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#include "thermal_core.h"
|
||||
|
||||
#define TSENSOR_CFG_REG1 0x4
|
||||
#define TSENSOR_CFG_REG1_RSET_VBG BIT(12)
|
||||
#define TSENSOR_CFG_REG1_RSET_ADC BIT(11)
|
||||
#define TSENSOR_CFG_REG1_VCM_EN BIT(10)
|
||||
#define TSENSOR_CFG_REG1_VBG_EN BIT(9)
|
||||
#define TSENSOR_CFG_REG1_OUT_CTL BIT(6)
|
||||
#define TSENSOR_CFG_REG1_FILTER_EN BIT(5)
|
||||
#define TSENSOR_CFG_REG1_DEM_EN BIT(3)
|
||||
#define TSENSOR_CFG_REG1_CH_SEL GENMASK(1, 0)
|
||||
#define TSENSOR_CFG_REG1_ENABLE \
|
||||
(TSENSOR_CFG_REG1_FILTER_EN | \
|
||||
TSENSOR_CFG_REG1_VCM_EN | \
|
||||
TSENSOR_CFG_REG1_VBG_EN | \
|
||||
TSENSOR_CFG_REG1_DEM_EN | \
|
||||
TSENSOR_CFG_REG1_CH_SEL)
|
||||
|
||||
#define TSENSOR_STAT0 0x40
|
||||
|
||||
#define TSENSOR_STAT9 0x64
|
||||
|
||||
#define TSENSOR_READ_TEMP_MASK GENMASK(15, 0)
|
||||
#define TSENSOR_TEMP_MASK GENMASK(11, 0)
|
||||
|
||||
#define TSENSOR_TRIM_SIGN_MASK BIT(15)
|
||||
#define TSENSOR_TRIM_TEMP_MASK GENMASK(14, 0)
|
||||
#define TSENSOR_TRIM_VERSION_MASK GENMASK(31, 24)
|
||||
|
||||
#define TSENSOR_TRIM_VERSION(_version) \
|
||||
FIELD_GET(TSENSOR_TRIM_VERSION_MASK, _version)
|
||||
|
||||
#define TSENSOR_TRIM_CALIB_VALID_MASK (GENMASK(3, 2) | BIT(7))
|
||||
|
||||
#define TSENSOR_CALIB_OFFSET 1
|
||||
#define TSENSOR_CALIB_SHIFT 4
|
||||
|
||||
/**
|
||||
* struct amlogic_thermal_soc_calib_data
|
||||
* @A, B, m, n: calibration parameters
|
||||
* This structure is required for configuration of amlogic thermal driver.
|
||||
*/
|
||||
struct amlogic_thermal_soc_calib_data {
|
||||
int A;
|
||||
int B;
|
||||
int m;
|
||||
int n;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct amlogic_thermal_data
|
||||
* @u_efuse_off: register offset to read fused calibration value
|
||||
* @calibration_parameters: calibration parameters structure pointer
|
||||
* @regmap_config: regmap config for the device
|
||||
* This structure is required for configuration of amlogic thermal driver.
|
||||
*/
|
||||
struct amlogic_thermal_data {
|
||||
int u_efuse_off;
|
||||
const struct amlogic_thermal_soc_calib_data *calibration_parameters;
|
||||
const struct regmap_config *regmap_config;
|
||||
};
|
||||
|
||||
struct amlogic_thermal {
|
||||
struct platform_device *pdev;
|
||||
const struct amlogic_thermal_data *data;
|
||||
struct regmap *regmap;
|
||||
struct regmap *sec_ao_map;
|
||||
struct clk *clk;
|
||||
struct thermal_zone_device *tzd;
|
||||
u32 trim_info;
|
||||
};
|
||||
|
||||
/*
|
||||
* Calculate a temperature value from a temperature code.
|
||||
* The unit of the temperature is degree milliCelsius.
|
||||
*/
|
||||
static int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata,
|
||||
int temp_code)
|
||||
{
|
||||
const struct amlogic_thermal_soc_calib_data *param =
|
||||
pdata->data->calibration_parameters;
|
||||
int temp;
|
||||
s64 factor, Uptat, uefuse;
|
||||
|
||||
uefuse = pdata->trim_info & TSENSOR_TRIM_SIGN_MASK ?
|
||||
~(pdata->trim_info & TSENSOR_TRIM_TEMP_MASK) + 1 :
|
||||
(pdata->trim_info & TSENSOR_TRIM_TEMP_MASK);
|
||||
|
||||
factor = param->n * temp_code;
|
||||
factor = div_s64(factor, 100);
|
||||
|
||||
Uptat = temp_code * param->m;
|
||||
Uptat = div_s64(Uptat, 100);
|
||||
Uptat = Uptat * BIT(16);
|
||||
Uptat = div_s64(Uptat, BIT(16) + factor);
|
||||
|
||||
temp = (Uptat + uefuse) * param->A;
|
||||
temp = div_s64(temp, BIT(16));
|
||||
temp = (temp - param->B) * 100;
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
static int amlogic_thermal_initialize(struct amlogic_thermal *pdata)
|
||||
{
|
||||
int ret = 0;
|
||||
int ver;
|
||||
|
||||
regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off,
|
||||
&pdata->trim_info);
|
||||
|
||||
ver = TSENSOR_TRIM_VERSION(pdata->trim_info);
|
||||
|
||||
if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) {
|
||||
ret = -EINVAL;
|
||||
dev_err(&pdata->pdev->dev,
|
||||
"tsensor thermal calibration not supported: 0x%x!\n",
|
||||
ver);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int amlogic_thermal_enable(struct amlogic_thermal *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(data->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regmap_update_bits(data->regmap, TSENSOR_CFG_REG1,
|
||||
TSENSOR_CFG_REG1_ENABLE, TSENSOR_CFG_REG1_ENABLE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amlogic_thermal_disable(struct amlogic_thermal *data)
|
||||
{
|
||||
regmap_update_bits(data->regmap, TSENSOR_CFG_REG1,
|
||||
TSENSOR_CFG_REG1_ENABLE, 0);
|
||||
clk_disable_unprepare(data->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amlogic_thermal_get_temp(void *data, int *temp)
|
||||
{
|
||||
unsigned int tval;
|
||||
struct amlogic_thermal *pdata = data;
|
||||
|
||||
if (!data)
|
||||
return -EINVAL;
|
||||
|
||||
regmap_read(pdata->regmap, TSENSOR_STAT0, &tval);
|
||||
*temp =
|
||||
amlogic_thermal_code_to_millicelsius(pdata,
|
||||
tval & TSENSOR_READ_TEMP_MASK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_of_device_ops amlogic_thermal_ops = {
|
||||
.get_temp = amlogic_thermal_get_temp,
|
||||
};
|
||||
|
||||
static const struct regmap_config amlogic_thermal_regmap_config_g12a = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.max_register = TSENSOR_STAT9,
|
||||
};
|
||||
|
||||
static const struct amlogic_thermal_soc_calib_data amlogic_thermal_g12a = {
|
||||
.A = 9411,
|
||||
.B = 3159,
|
||||
.m = 424,
|
||||
.n = 324,
|
||||
};
|
||||
|
||||
static const struct amlogic_thermal_data amlogic_thermal_g12a_cpu_param = {
|
||||
.u_efuse_off = 0x128,
|
||||
.calibration_parameters = &amlogic_thermal_g12a,
|
||||
.regmap_config = &amlogic_thermal_regmap_config_g12a,
|
||||
};
|
||||
|
||||
static const struct amlogic_thermal_data amlogic_thermal_g12a_ddr_param = {
|
||||
.u_efuse_off = 0xf0,
|
||||
.calibration_parameters = &amlogic_thermal_g12a,
|
||||
.regmap_config = &amlogic_thermal_regmap_config_g12a,
|
||||
};
|
||||
|
||||
static const struct of_device_id of_amlogic_thermal_match[] = {
|
||||
{
|
||||
.compatible = "amlogic,g12a-ddr-thermal",
|
||||
.data = &amlogic_thermal_g12a_ddr_param,
|
||||
},
|
||||
{
|
||||
.compatible = "amlogic,g12a-cpu-thermal",
|
||||
.data = &amlogic_thermal_g12a_cpu_param,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_amlogic_thermal_match);
|
||||
|
||||
static int amlogic_thermal_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct amlogic_thermal *pdata;
|
||||
struct device *dev = &pdev->dev;
|
||||
void __iomem *base;
|
||||
int ret;
|
||||
|
||||
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
pdata->data = of_device_get_match_data(dev);
|
||||
pdata->pdev = pdev;
|
||||
platform_set_drvdata(pdev, pdata);
|
||||
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(base)) {
|
||||
dev_err(dev, "failed to get io address\n");
|
||||
return PTR_ERR(base);
|
||||
}
|
||||
|
||||
pdata->regmap = devm_regmap_init_mmio(dev, base,
|
||||
pdata->data->regmap_config);
|
||||
if (IS_ERR(pdata->regmap))
|
||||
return PTR_ERR(pdata->regmap);
|
||||
|
||||
pdata->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(pdata->clk)) {
|
||||
if (PTR_ERR(pdata->clk) != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to get clock\n");
|
||||
return PTR_ERR(pdata->clk);
|
||||
}
|
||||
|
||||
pdata->sec_ao_map = syscon_regmap_lookup_by_phandle
|
||||
(pdev->dev.of_node, "amlogic,ao-secure");
|
||||
if (IS_ERR(pdata->sec_ao_map)) {
|
||||
dev_err(dev, "syscon regmap lookup failed.\n");
|
||||
return PTR_ERR(pdata->sec_ao_map);
|
||||
}
|
||||
|
||||
pdata->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
|
||||
0,
|
||||
pdata,
|
||||
&amlogic_thermal_ops);
|
||||
if (IS_ERR(pdata->tzd)) {
|
||||
ret = PTR_ERR(pdata->tzd);
|
||||
dev_err(dev, "Failed to register tsensor: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = amlogic_thermal_initialize(pdata);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = amlogic_thermal_enable(pdata);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int amlogic_thermal_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct amlogic_thermal *data = platform_get_drvdata(pdev);
|
||||
|
||||
return amlogic_thermal_disable(data);
|
||||
}
|
||||
|
||||
static int __maybe_unused amlogic_thermal_suspend(struct device *dev)
|
||||
{
|
||||
struct amlogic_thermal *data = dev_get_drvdata(dev);
|
||||
|
||||
return amlogic_thermal_disable(data);
|
||||
}
|
||||
|
||||
static int __maybe_unused amlogic_thermal_resume(struct device *dev)
|
||||
{
|
||||
struct amlogic_thermal *data = dev_get_drvdata(dev);
|
||||
|
||||
return amlogic_thermal_enable(data);
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(amlogic_thermal_pm_ops,
|
||||
amlogic_thermal_suspend, amlogic_thermal_resume);
|
||||
|
||||
static struct platform_driver amlogic_thermal_driver = {
|
||||
.driver = {
|
||||
.name = "amlogic_thermal",
|
||||
.pm = &amlogic_thermal_pm_ops,
|
||||
.of_match_table = of_amlogic_thermal_match,
|
||||
},
|
||||
.probe = amlogic_thermal_probe,
|
||||
.remove = amlogic_thermal_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(amlogic_thermal_driver);
|
||||
|
||||
MODULE_AUTHOR("Guillaume La Roque <glaroque@baylibre.com>");
|
||||
MODULE_DESCRIPTION("Amlogic thermal driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -20,6 +20,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/energy_model.h>
|
||||
|
||||
#include <trace/events/thermal.h>
|
||||
|
||||
@ -37,19 +38,6 @@
|
||||
* ...
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct freq_table - frequency table along with power entries
|
||||
* @frequency: frequency in KHz
|
||||
* @power: power in mW
|
||||
*
|
||||
* This structure is built when the cooling device registers and helps
|
||||
* in translating frequency to power and vice versa.
|
||||
*/
|
||||
struct freq_table {
|
||||
u32 frequency;
|
||||
u32 power;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct time_in_idle - Idle time stats
|
||||
* @time: previous reading of the absolute time that this cpu was idle
|
||||
@ -69,7 +57,7 @@ struct time_in_idle {
|
||||
* cooling devices.
|
||||
* @max_level: maximum cooling level. One less than total number of valid
|
||||
* cpufreq frequencies.
|
||||
* @freq_table: Freq table in descending order of frequencies
|
||||
* @em: Reference on the Energy Model of the device
|
||||
* @cdev: thermal_cooling_device pointer to keep track of the
|
||||
* registered cooling device.
|
||||
* @policy: cpufreq policy.
|
||||
@ -84,7 +72,7 @@ struct cpufreq_cooling_device {
|
||||
u32 last_load;
|
||||
unsigned int cpufreq_state;
|
||||
unsigned int max_level;
|
||||
struct freq_table *freq_table; /* In descending order */
|
||||
struct em_perf_domain *em;
|
||||
struct cpufreq_policy *policy;
|
||||
struct list_head node;
|
||||
struct time_in_idle *idle_time;
|
||||
@ -95,8 +83,7 @@ static DEFINE_IDA(cpufreq_ida);
|
||||
static DEFINE_MUTEX(cooling_list_lock);
|
||||
static LIST_HEAD(cpufreq_cdev_list);
|
||||
|
||||
/* Below code defines functions to be used for cpufreq as cooling device */
|
||||
|
||||
#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
|
||||
/**
|
||||
* get_level: Find the level for a particular frequency
|
||||
* @cpufreq_cdev: cpufreq_cdev for which the property is required
|
||||
@ -107,114 +94,40 @@ static LIST_HEAD(cpufreq_cdev_list);
|
||||
static unsigned long get_level(struct cpufreq_cooling_device *cpufreq_cdev,
|
||||
unsigned int freq)
|
||||
{
|
||||
struct freq_table *freq_table = cpufreq_cdev->freq_table;
|
||||
unsigned long level;
|
||||
int i;
|
||||
|
||||
for (level = 1; level <= cpufreq_cdev->max_level; level++)
|
||||
if (freq > freq_table[level].frequency)
|
||||
for (i = cpufreq_cdev->max_level - 1; i >= 0; i--) {
|
||||
if (freq > cpufreq_cdev->em->table[i].frequency)
|
||||
break;
|
||||
|
||||
return level - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* update_freq_table() - Update the freq table with power numbers
|
||||
* @cpufreq_cdev: the cpufreq cooling device in which to update the table
|
||||
* @capacitance: dynamic power coefficient for these cpus
|
||||
*
|
||||
* Update the freq table with power numbers. This table will be used in
|
||||
* cpu_power_to_freq() and cpu_freq_to_power() to convert between power and
|
||||
* frequency efficiently. Power is stored in mW, frequency in KHz. The
|
||||
* resulting table is in descending order.
|
||||
*
|
||||
* Return: 0 on success, -EINVAL if there are no OPPs for any CPUs,
|
||||
* or -ENOMEM if we run out of memory.
|
||||
*/
|
||||
static int update_freq_table(struct cpufreq_cooling_device *cpufreq_cdev,
|
||||
u32 capacitance)
|
||||
{
|
||||
struct freq_table *freq_table = cpufreq_cdev->freq_table;
|
||||
struct dev_pm_opp *opp;
|
||||
struct device *dev = NULL;
|
||||
int num_opps = 0, cpu = cpufreq_cdev->policy->cpu, i;
|
||||
|
||||
dev = get_cpu_device(cpu);
|
||||
if (unlikely(!dev)) {
|
||||
pr_warn("No cpu device for cpu %d\n", cpu);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
num_opps = dev_pm_opp_get_opp_count(dev);
|
||||
if (num_opps < 0)
|
||||
return num_opps;
|
||||
|
||||
/*
|
||||
* The cpufreq table is also built from the OPP table and so the count
|
||||
* should match.
|
||||
*/
|
||||
if (num_opps != cpufreq_cdev->max_level + 1) {
|
||||
dev_warn(dev, "Number of OPPs not matching with max_levels\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i <= cpufreq_cdev->max_level; i++) {
|
||||
unsigned long freq = freq_table[i].frequency * 1000;
|
||||
u32 freq_mhz = freq_table[i].frequency / 1000;
|
||||
u64 power;
|
||||
u32 voltage_mv;
|
||||
|
||||
/*
|
||||
* Find ceil frequency as 'freq' may be slightly lower than OPP
|
||||
* freq due to truncation while converting to kHz.
|
||||
*/
|
||||
opp = dev_pm_opp_find_freq_ceil(dev, &freq);
|
||||
if (IS_ERR(opp)) {
|
||||
dev_err(dev, "failed to get opp for %lu frequency\n",
|
||||
freq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
voltage_mv = dev_pm_opp_get_voltage(opp) / 1000;
|
||||
dev_pm_opp_put(opp);
|
||||
|
||||
/*
|
||||
* Do the multiplication with MHz and millivolt so as
|
||||
* to not overflow.
|
||||
*/
|
||||
power = (u64)capacitance * freq_mhz * voltage_mv * voltage_mv;
|
||||
do_div(power, 1000000000);
|
||||
|
||||
/* power is stored in mW */
|
||||
freq_table[i].power = power;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return cpufreq_cdev->max_level - i - 1;
|
||||
}
|
||||
|
||||
static u32 cpu_freq_to_power(struct cpufreq_cooling_device *cpufreq_cdev,
|
||||
u32 freq)
|
||||
{
|
||||
int i;
|
||||
struct freq_table *freq_table = cpufreq_cdev->freq_table;
|
||||
|
||||
for (i = 1; i <= cpufreq_cdev->max_level; i++)
|
||||
if (freq > freq_table[i].frequency)
|
||||
for (i = cpufreq_cdev->max_level - 1; i >= 0; i--) {
|
||||
if (freq > cpufreq_cdev->em->table[i].frequency)
|
||||
break;
|
||||
}
|
||||
|
||||
return freq_table[i - 1].power;
|
||||
return cpufreq_cdev->em->table[i + 1].power;
|
||||
}
|
||||
|
||||
static u32 cpu_power_to_freq(struct cpufreq_cooling_device *cpufreq_cdev,
|
||||
u32 power)
|
||||
{
|
||||
int i;
|
||||
struct freq_table *freq_table = cpufreq_cdev->freq_table;
|
||||
|
||||
for (i = 1; i <= cpufreq_cdev->max_level; i++)
|
||||
if (power > freq_table[i].power)
|
||||
for (i = cpufreq_cdev->max_level - 1; i >= 0; i--) {
|
||||
if (power > cpufreq_cdev->em->table[i].power)
|
||||
break;
|
||||
}
|
||||
|
||||
return freq_table[i - 1].frequency;
|
||||
return cpufreq_cdev->em->table[i + 1].frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,76 +178,6 @@ static u32 get_dynamic_power(struct cpufreq_cooling_device *cpufreq_cdev,
|
||||
return (raw_cpu_power * cpufreq_cdev->last_load) / 100;
|
||||
}
|
||||
|
||||
/* cpufreq cooling device callback functions are defined below */
|
||||
|
||||
/**
|
||||
* cpufreq_get_max_state - callback function to get the max cooling state.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
* @state: fill this variable with the max cooling state.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the cpufreq
|
||||
* max cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
|
||||
|
||||
*state = cpufreq_cdev->max_level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_get_cur_state - callback function to get the current cooling state.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
* @state: fill this variable with the current cooling state.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the cpufreq
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
|
||||
|
||||
*state = cpufreq_cdev->cpufreq_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_set_cur_state - callback function to set the current cooling state.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
* @state: set this variable to the current cooling state.
|
||||
*
|
||||
* Callback for the thermal cooling device to change the cpufreq
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
|
||||
|
||||
/* Request state should be less than max_level */
|
||||
if (WARN_ON(state > cpufreq_cdev->max_level))
|
||||
return -EINVAL;
|
||||
|
||||
/* Check if the old cooling action is same as new cooling action */
|
||||
if (cpufreq_cdev->cpufreq_state == state)
|
||||
return 0;
|
||||
|
||||
cpufreq_cdev->cpufreq_state = state;
|
||||
|
||||
return freq_qos_update_request(&cpufreq_cdev->qos_req,
|
||||
cpufreq_cdev->freq_table[state].frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_get_requested_power() - get the current power
|
||||
* @cdev: &thermal_cooling_device pointer
|
||||
@ -425,7 +268,7 @@ static int cpufreq_state2power(struct thermal_cooling_device *cdev,
|
||||
struct thermal_zone_device *tz,
|
||||
unsigned long state, u32 *power)
|
||||
{
|
||||
unsigned int freq, num_cpus;
|
||||
unsigned int freq, num_cpus, idx;
|
||||
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
|
||||
|
||||
/* Request state should be less than max_level */
|
||||
@ -434,7 +277,8 @@ static int cpufreq_state2power(struct thermal_cooling_device *cdev,
|
||||
|
||||
num_cpus = cpumask_weight(cpufreq_cdev->policy->cpus);
|
||||
|
||||
freq = cpufreq_cdev->freq_table[state].frequency;
|
||||
idx = cpufreq_cdev->max_level - state;
|
||||
freq = cpufreq_cdev->em->table[idx].frequency;
|
||||
*power = cpu_freq_to_power(cpufreq_cdev, freq) * num_cpus;
|
||||
|
||||
return 0;
|
||||
@ -479,43 +323,142 @@ static int cpufreq_power2state(struct thermal_cooling_device *cdev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool em_is_sane(struct cpufreq_cooling_device *cpufreq_cdev,
|
||||
struct em_perf_domain *em) {
|
||||
struct cpufreq_policy *policy;
|
||||
unsigned int nr_levels;
|
||||
|
||||
if (!em)
|
||||
return false;
|
||||
|
||||
policy = cpufreq_cdev->policy;
|
||||
if (!cpumask_equal(policy->related_cpus, to_cpumask(em->cpus))) {
|
||||
pr_err("The span of pd %*pbl is misaligned with cpufreq policy %*pbl\n",
|
||||
cpumask_pr_args(to_cpumask(em->cpus)),
|
||||
cpumask_pr_args(policy->related_cpus));
|
||||
return false;
|
||||
}
|
||||
|
||||
nr_levels = cpufreq_cdev->max_level + 1;
|
||||
if (em->nr_cap_states != nr_levels) {
|
||||
pr_err("The number of cap states in pd %*pbl (%u) doesn't match the number of cooling levels (%u)\n",
|
||||
cpumask_pr_args(to_cpumask(em->cpus)),
|
||||
em->nr_cap_states, nr_levels);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif /* CONFIG_THERMAL_GOV_POWER_ALLOCATOR */
|
||||
|
||||
static unsigned int get_state_freq(struct cpufreq_cooling_device *cpufreq_cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct cpufreq_policy *policy;
|
||||
unsigned long idx;
|
||||
|
||||
#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
|
||||
/* Use the Energy Model table if available */
|
||||
if (cpufreq_cdev->em) {
|
||||
idx = cpufreq_cdev->max_level - state;
|
||||
return cpufreq_cdev->em->table[idx].frequency;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Otherwise, fallback on the CPUFreq table */
|
||||
policy = cpufreq_cdev->policy;
|
||||
if (policy->freq_table_sorted == CPUFREQ_TABLE_SORTED_ASCENDING)
|
||||
idx = cpufreq_cdev->max_level - state;
|
||||
else
|
||||
idx = state;
|
||||
|
||||
return policy->freq_table[idx].frequency;
|
||||
}
|
||||
|
||||
/* cpufreq cooling device callback functions are defined below */
|
||||
|
||||
/**
|
||||
* cpufreq_get_max_state - callback function to get the max cooling state.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
* @state: fill this variable with the max cooling state.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the cpufreq
|
||||
* max cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
|
||||
|
||||
*state = cpufreq_cdev->max_level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_get_cur_state - callback function to get the current cooling state.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
* @state: fill this variable with the current cooling state.
|
||||
*
|
||||
* Callback for the thermal cooling device to return the cpufreq
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
|
||||
|
||||
*state = cpufreq_cdev->cpufreq_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpufreq_set_cur_state - callback function to set the current cooling state.
|
||||
* @cdev: thermal cooling device pointer.
|
||||
* @state: set this variable to the current cooling state.
|
||||
*
|
||||
* Callback for the thermal cooling device to change the cpufreq
|
||||
* current cooling state.
|
||||
*
|
||||
* Return: 0 on success, an error code otherwise.
|
||||
*/
|
||||
static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
|
||||
|
||||
/* Request state should be less than max_level */
|
||||
if (WARN_ON(state > cpufreq_cdev->max_level))
|
||||
return -EINVAL;
|
||||
|
||||
/* Check if the old cooling action is same as new cooling action */
|
||||
if (cpufreq_cdev->cpufreq_state == state)
|
||||
return 0;
|
||||
|
||||
cpufreq_cdev->cpufreq_state = state;
|
||||
|
||||
return freq_qos_update_request(&cpufreq_cdev->qos_req,
|
||||
get_state_freq(cpufreq_cdev, state));
|
||||
}
|
||||
|
||||
/* Bind cpufreq callbacks to thermal cooling device ops */
|
||||
|
||||
static struct thermal_cooling_device_ops cpufreq_cooling_ops = {
|
||||
.get_max_state = cpufreq_get_max_state,
|
||||
.get_cur_state = cpufreq_get_cur_state,
|
||||
.set_cur_state = cpufreq_set_cur_state,
|
||||
};
|
||||
|
||||
static struct thermal_cooling_device_ops cpufreq_power_cooling_ops = {
|
||||
.get_max_state = cpufreq_get_max_state,
|
||||
.get_cur_state = cpufreq_get_cur_state,
|
||||
.set_cur_state = cpufreq_set_cur_state,
|
||||
.get_requested_power = cpufreq_get_requested_power,
|
||||
.state2power = cpufreq_state2power,
|
||||
.power2state = cpufreq_power2state,
|
||||
};
|
||||
|
||||
static unsigned int find_next_max(struct cpufreq_frequency_table *table,
|
||||
unsigned int prev_max)
|
||||
{
|
||||
struct cpufreq_frequency_table *pos;
|
||||
unsigned int max = 0;
|
||||
|
||||
cpufreq_for_each_valid_entry(pos, table) {
|
||||
if (pos->frequency > max && pos->frequency < prev_max)
|
||||
max = pos->frequency;
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* __cpufreq_cooling_register - helper function to create cpufreq cooling device
|
||||
* @np: a valid struct device_node to the cooling device device tree node
|
||||
* @policy: cpufreq policy
|
||||
* Normally this should be same as cpufreq policy->related_cpus.
|
||||
* @capacitance: dynamic power coefficient for these cpus
|
||||
* @em: Energy Model of the cpufreq policy
|
||||
*
|
||||
* This interface function registers the cpufreq cooling device with the name
|
||||
* "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
|
||||
@ -527,12 +470,13 @@ static unsigned int find_next_max(struct cpufreq_frequency_table *table,
|
||||
*/
|
||||
static struct thermal_cooling_device *
|
||||
__cpufreq_cooling_register(struct device_node *np,
|
||||
struct cpufreq_policy *policy, u32 capacitance)
|
||||
struct cpufreq_policy *policy,
|
||||
struct em_perf_domain *em)
|
||||
{
|
||||
struct thermal_cooling_device *cdev;
|
||||
struct cpufreq_cooling_device *cpufreq_cdev;
|
||||
char dev_name[THERMAL_NAME_LENGTH];
|
||||
unsigned int freq, i, num_cpus;
|
||||
unsigned int i, num_cpus;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
struct thermal_cooling_device_ops *cooling_ops;
|
||||
@ -573,51 +517,36 @@ __cpufreq_cooling_register(struct device_node *np,
|
||||
/* max_level is an index, not a counter */
|
||||
cpufreq_cdev->max_level = i - 1;
|
||||
|
||||
cpufreq_cdev->freq_table = kmalloc_array(i,
|
||||
sizeof(*cpufreq_cdev->freq_table),
|
||||
GFP_KERNEL);
|
||||
if (!cpufreq_cdev->freq_table) {
|
||||
cdev = ERR_PTR(-ENOMEM);
|
||||
goto free_idle_time;
|
||||
}
|
||||
|
||||
ret = ida_simple_get(&cpufreq_ida, 0, 0, GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
cdev = ERR_PTR(ret);
|
||||
goto free_table;
|
||||
goto free_idle_time;
|
||||
}
|
||||
cpufreq_cdev->id = ret;
|
||||
|
||||
snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",
|
||||
cpufreq_cdev->id);
|
||||
|
||||
/* Fill freq-table in descending order of frequencies */
|
||||
for (i = 0, freq = -1; i <= cpufreq_cdev->max_level; i++) {
|
||||
freq = find_next_max(policy->freq_table, freq);
|
||||
cpufreq_cdev->freq_table[i].frequency = freq;
|
||||
cooling_ops = &cpufreq_cooling_ops;
|
||||
|
||||
/* Warn for duplicate entries */
|
||||
if (!freq)
|
||||
pr_warn("%s: table has duplicate entries\n", __func__);
|
||||
else
|
||||
pr_debug("%s: freq:%u KHz\n", __func__, freq);
|
||||
}
|
||||
|
||||
if (capacitance) {
|
||||
ret = update_freq_table(cpufreq_cdev, capacitance);
|
||||
if (ret) {
|
||||
cdev = ERR_PTR(ret);
|
||||
goto remove_ida;
|
||||
}
|
||||
|
||||
cooling_ops = &cpufreq_power_cooling_ops;
|
||||
} else {
|
||||
cooling_ops = &cpufreq_cooling_ops;
|
||||
#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
|
||||
if (em_is_sane(cpufreq_cdev, em)) {
|
||||
cpufreq_cdev->em = em;
|
||||
cooling_ops->get_requested_power = cpufreq_get_requested_power;
|
||||
cooling_ops->state2power = cpufreq_state2power;
|
||||
cooling_ops->power2state = cpufreq_power2state;
|
||||
} else
|
||||
#endif
|
||||
if (policy->freq_table_sorted == CPUFREQ_TABLE_UNSORTED) {
|
||||
pr_err("%s: unsorted frequency tables are not supported\n",
|
||||
__func__);
|
||||
cdev = ERR_PTR(-EINVAL);
|
||||
goto remove_ida;
|
||||
}
|
||||
|
||||
ret = freq_qos_add_request(&policy->constraints,
|
||||
&cpufreq_cdev->qos_req, FREQ_QOS_MAX,
|
||||
cpufreq_cdev->freq_table[0].frequency);
|
||||
get_state_freq(cpufreq_cdev, 0));
|
||||
if (ret < 0) {
|
||||
pr_err("%s: Failed to add freq constraint (%d)\n", __func__,
|
||||
ret);
|
||||
@ -640,8 +569,6 @@ __cpufreq_cooling_register(struct device_node *np,
|
||||
freq_qos_remove_request(&cpufreq_cdev->qos_req);
|
||||
remove_ida:
|
||||
ida_simple_remove(&cpufreq_ida, cpufreq_cdev->id);
|
||||
free_table:
|
||||
kfree(cpufreq_cdev->freq_table);
|
||||
free_idle_time:
|
||||
kfree(cpufreq_cdev->idle_time);
|
||||
free_cdev:
|
||||
@ -663,7 +590,7 @@ __cpufreq_cooling_register(struct device_node *np,
|
||||
struct thermal_cooling_device *
|
||||
cpufreq_cooling_register(struct cpufreq_policy *policy)
|
||||
{
|
||||
return __cpufreq_cooling_register(NULL, policy, 0);
|
||||
return __cpufreq_cooling_register(NULL, policy, NULL);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
|
||||
|
||||
@ -691,7 +618,6 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct device_node *np = of_get_cpu_node(policy->cpu, NULL);
|
||||
struct thermal_cooling_device *cdev = NULL;
|
||||
u32 capacitance = 0;
|
||||
|
||||
if (!np) {
|
||||
pr_err("cpu_cooling: OF node not available for cpu%d\n",
|
||||
@ -700,10 +626,9 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy)
|
||||
}
|
||||
|
||||
if (of_find_property(np, "#cooling-cells", NULL)) {
|
||||
of_property_read_u32(np, "dynamic-power-coefficient",
|
||||
&capacitance);
|
||||
struct em_perf_domain *em = em_cpu_get(policy->cpu);
|
||||
|
||||
cdev = __cpufreq_cooling_register(np, policy, capacitance);
|
||||
cdev = __cpufreq_cooling_register(np, policy, em);
|
||||
if (IS_ERR(cdev)) {
|
||||
pr_err("cpu_cooling: cpu%d failed to register as cooling device: %ld\n",
|
||||
policy->cpu, PTR_ERR(cdev));
|
||||
@ -739,7 +664,6 @@ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
|
||||
freq_qos_remove_request(&cpufreq_cdev->qos_req);
|
||||
ida_simple_remove(&cpufreq_ida, cpufreq_cdev->id);
|
||||
kfree(cpufreq_cdev->idle_time);
|
||||
kfree(cpufreq_cdev->freq_table);
|
||||
kfree(cpufreq_cdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpufreq_cooling_unregister);
|
||||
|
@ -245,11 +245,11 @@ static inline int code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
|
||||
return adc_code * slope + offset;
|
||||
}
|
||||
|
||||
static int get_temp_8960(struct tsens_priv *priv, int id, int *temp)
|
||||
static int get_temp_8960(struct tsens_sensor *s, int *temp)
|
||||
{
|
||||
int ret;
|
||||
u32 code, trdy;
|
||||
const struct tsens_sensor *s = &priv->sensor[id];
|
||||
struct tsens_priv *priv = s->priv;
|
||||
unsigned long timeout;
|
||||
|
||||
timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
@ -12,6 +13,31 @@
|
||||
#include <linux/regmap.h>
|
||||
#include "tsens.h"
|
||||
|
||||
/**
|
||||
* struct tsens_irq_data - IRQ status and temperature violations
|
||||
* @up_viol: upper threshold violated
|
||||
* @up_thresh: upper threshold temperature value
|
||||
* @up_irq_mask: mask register for upper threshold irqs
|
||||
* @up_irq_clear: clear register for uppper threshold irqs
|
||||
* @low_viol: lower threshold violated
|
||||
* @low_thresh: lower threshold temperature value
|
||||
* @low_irq_mask: mask register for lower threshold irqs
|
||||
* @low_irq_clear: clear register for lower threshold irqs
|
||||
*
|
||||
* Structure containing data about temperature threshold settings and
|
||||
* irq status if they were violated.
|
||||
*/
|
||||
struct tsens_irq_data {
|
||||
u32 up_viol;
|
||||
int up_thresh;
|
||||
u32 up_irq_mask;
|
||||
u32 up_irq_clear;
|
||||
u32 low_viol;
|
||||
int low_thresh;
|
||||
u32 low_irq_mask;
|
||||
u32 low_irq_clear;
|
||||
};
|
||||
|
||||
char *qfprom_read(struct device *dev, const char *cname)
|
||||
{
|
||||
struct nvmem_cell *cell;
|
||||
@ -42,8 +68,8 @@ void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
dev_dbg(priv->dev,
|
||||
"sensor%d - data_point1:%#x data_point2:%#x\n",
|
||||
i, p1[i], p2[i]);
|
||||
"%s: sensor%d - data_point1:%#x data_point2:%#x\n",
|
||||
__func__, i, p1[i], p2[i]);
|
||||
|
||||
priv->sensor[i].slope = SLOPE_DEFAULT;
|
||||
if (mode == TWO_PT_CALIB) {
|
||||
@ -60,10 +86,18 @@ void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
|
||||
priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
|
||||
(CAL_DEGC_PT1 *
|
||||
priv->sensor[i].slope);
|
||||
dev_dbg(priv->dev, "offset:%d\n", priv->sensor[i].offset);
|
||||
dev_dbg(priv->dev, "%s: offset:%d\n", __func__, priv->sensor[i].offset);
|
||||
}
|
||||
}
|
||||
|
||||
static inline u32 degc_to_code(int degc, const struct tsens_sensor *s)
|
||||
{
|
||||
u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR);
|
||||
|
||||
pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc);
|
||||
return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE);
|
||||
}
|
||||
|
||||
static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
|
||||
{
|
||||
int degc, num, den;
|
||||
@ -83,12 +117,353 @@ static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
|
||||
return degc;
|
||||
}
|
||||
|
||||
int get_temp_tsens_valid(struct tsens_priv *priv, int i, int *temp)
|
||||
/**
|
||||
* tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
|
||||
* @s: Pointer to sensor struct
|
||||
* @field: Index into regmap_field array pointing to temperature data
|
||||
*
|
||||
* This function handles temperature returned in ADC code or deciCelsius
|
||||
* depending on IP version.
|
||||
*
|
||||
* Return: Temperature in milliCelsius on success, a negative errno will
|
||||
* be returned in error cases
|
||||
*/
|
||||
static int tsens_hw_to_mC(struct tsens_sensor *s, int field)
|
||||
{
|
||||
struct tsens_sensor *s = &priv->sensor[i];
|
||||
u32 temp_idx = LAST_TEMP_0 + s->hw_id;
|
||||
u32 valid_idx = VALID_0 + s->hw_id;
|
||||
u32 last_temp = 0, valid, mask;
|
||||
struct tsens_priv *priv = s->priv;
|
||||
u32 resolution;
|
||||
u32 temp = 0;
|
||||
int ret;
|
||||
|
||||
resolution = priv->fields[LAST_TEMP_0].msb -
|
||||
priv->fields[LAST_TEMP_0].lsb;
|
||||
|
||||
ret = regmap_field_read(priv->rf[field], &temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Convert temperature from ADC code to milliCelsius */
|
||||
if (priv->feat->adc)
|
||||
return code_to_degc(temp, s) * 1000;
|
||||
|
||||
/* deciCelsius -> milliCelsius along with sign extension */
|
||||
return sign_extend32(temp, resolution) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_mC_to_hw - Convert temperature to hardware register value
|
||||
* @s: Pointer to sensor struct
|
||||
* @temp: temperature in milliCelsius to be programmed to hardware
|
||||
*
|
||||
* This function outputs the value to be written to hardware in ADC code
|
||||
* or deciCelsius depending on IP version.
|
||||
*
|
||||
* Return: ADC code or temperature in deciCelsius.
|
||||
*/
|
||||
static int tsens_mC_to_hw(struct tsens_sensor *s, int temp)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
|
||||
/* milliC to adc code */
|
||||
if (priv->feat->adc)
|
||||
return degc_to_code(temp / 1000, s);
|
||||
|
||||
/* milliC to deciC */
|
||||
return temp / 100;
|
||||
}
|
||||
|
||||
static inline enum tsens_ver tsens_version(struct tsens_priv *priv)
|
||||
{
|
||||
return priv->feat->ver_major;
|
||||
}
|
||||
|
||||
static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
u32 index = 0;
|
||||
|
||||
switch (irq_type) {
|
||||
case UPPER:
|
||||
index = UP_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case LOWER:
|
||||
index = LOW_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
}
|
||||
regmap_field_write(priv->rf[index], enable ? 0 : 1);
|
||||
}
|
||||
|
||||
static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
u32 index_mask = 0, index_clear = 0;
|
||||
|
||||
/*
|
||||
* To enable the interrupt flag for a sensor:
|
||||
* - clear the mask bit
|
||||
* To disable the interrupt flag for a sensor:
|
||||
* - Mask further interrupts for this sensor
|
||||
* - Write 1 followed by 0 to clear the interrupt
|
||||
*/
|
||||
switch (irq_type) {
|
||||
case UPPER:
|
||||
index_mask = UP_INT_MASK_0 + hw_id;
|
||||
index_clear = UP_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case LOWER:
|
||||
index_mask = LOW_INT_MASK_0 + hw_id;
|
||||
index_clear = LOW_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
regmap_field_write(priv->rf[index_mask], 0);
|
||||
} else {
|
||||
regmap_field_write(priv->rf[index_mask], 1);
|
||||
regmap_field_write(priv->rf[index_clear], 1);
|
||||
regmap_field_write(priv->rf[index_clear], 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_set_interrupt - Set state of an interrupt
|
||||
* @priv: Pointer to tsens controller private data
|
||||
* @hw_id: Hardware ID aka. sensor number
|
||||
* @irq_type: irq_type from enum tsens_irq_type
|
||||
* @enable: false = disable, true = enable
|
||||
*
|
||||
* Call IP-specific function to set state of an interrupt
|
||||
*
|
||||
* Return: void
|
||||
*/
|
||||
static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
|
||||
irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
|
||||
enable ? "en" : "dis");
|
||||
if (tsens_version(priv) > VER_1_X)
|
||||
tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
|
||||
else
|
||||
tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
|
||||
* @priv: Pointer to tsens controller private data
|
||||
* @hw_id: Hardware ID aka. sensor number
|
||||
* @d: Pointer to irq state data
|
||||
*
|
||||
* Return: 0 if threshold was not violated, 1 if it was violated and negative
|
||||
* errno in case of errors
|
||||
*/
|
||||
static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
|
||||
struct tsens_irq_data *d)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (d->up_viol || d->low_viol)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
|
||||
struct tsens_sensor *s, struct tsens_irq_data *d)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (tsens_version(priv) > VER_1_X) {
|
||||
ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
/* No mask register on older TSENS */
|
||||
d->up_irq_mask = 0;
|
||||
d->low_irq_mask = 0;
|
||||
}
|
||||
|
||||
d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
|
||||
d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
|
||||
|
||||
dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u) | clr(%u|%u) | mask(%u|%u)\n",
|
||||
hw_id, __func__, (d->up_viol || d->low_viol) ? "(V)" : "",
|
||||
d->low_viol, d->up_viol, d->low_irq_clear, d->up_irq_clear,
|
||||
d->low_irq_mask, d->up_irq_mask);
|
||||
dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d)\n", hw_id, __func__,
|
||||
(d->up_viol || d->low_viol) ? "(violation)" : "",
|
||||
d->low_thresh, d->up_thresh);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
|
||||
{
|
||||
if (ver > VER_1_X)
|
||||
return mask & (1 << hw_id);
|
||||
|
||||
/* v1, v0.1 don't have a irq mask register */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_irq_thread - Threaded interrupt handler for uplow interrupts
|
||||
* @irq: irq number
|
||||
* @data: tsens controller private data
|
||||
*
|
||||
* Check all sensors to find ones that violated their threshold limits. If the
|
||||
* temperature is still outside the limits, call thermal_zone_device_update() to
|
||||
* update the thresholds, else re-enable the interrupts.
|
||||
*
|
||||
* The level-triggered interrupt might deassert if the temperature returned to
|
||||
* within the threshold limits by the time the handler got scheduled. We
|
||||
* consider the irq to have been handled in that case.
|
||||
*
|
||||
* Return: IRQ_HANDLED
|
||||
*/
|
||||
irqreturn_t tsens_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct tsens_priv *priv = data;
|
||||
struct tsens_irq_data d;
|
||||
bool enable = true, disable = false;
|
||||
unsigned long flags;
|
||||
int temp, ret, i;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
bool trigger = false;
|
||||
struct tsens_sensor *s = &priv->sensor[i];
|
||||
u32 hw_id = s->hw_id;
|
||||
|
||||
if (IS_ERR(priv->sensor[i].tzd))
|
||||
continue;
|
||||
if (!tsens_threshold_violated(priv, hw_id, &d))
|
||||
continue;
|
||||
ret = get_temp_tsens_valid(s, &temp);
|
||||
if (ret) {
|
||||
dev_err(priv->dev, "[%u] %s: error reading sensor\n", hw_id, __func__);
|
||||
continue;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&priv->ul_lock, flags);
|
||||
|
||||
tsens_read_irq_state(priv, hw_id, s, &d);
|
||||
|
||||
if (d.up_viol &&
|
||||
!masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) {
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, disable);
|
||||
if (d.up_thresh > temp) {
|
||||
dev_dbg(priv->dev, "[%u] %s: re-arm upper\n",
|
||||
priv->sensor[i].hw_id, __func__);
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, enable);
|
||||
} else {
|
||||
trigger = true;
|
||||
/* Keep irq masked */
|
||||
}
|
||||
} else if (d.low_viol &&
|
||||
!masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) {
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, disable);
|
||||
if (d.low_thresh < temp) {
|
||||
dev_dbg(priv->dev, "[%u] %s: re-arm low\n",
|
||||
priv->sensor[i].hw_id, __func__);
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, enable);
|
||||
} else {
|
||||
trigger = true;
|
||||
/* Keep irq masked */
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&priv->ul_lock, flags);
|
||||
|
||||
if (trigger) {
|
||||
dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
|
||||
hw_id, __func__, temp);
|
||||
thermal_zone_device_update(priv->sensor[i].tzd,
|
||||
THERMAL_EVENT_UNSPECIFIED);
|
||||
} else {
|
||||
dev_dbg(priv->dev, "[%u] %s: no violation: %d\n",
|
||||
hw_id, __func__, temp);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int tsens_set_trips(void *_sensor, int low, int high)
|
||||
{
|
||||
struct tsens_sensor *s = _sensor;
|
||||
struct tsens_priv *priv = s->priv;
|
||||
struct device *dev = priv->dev;
|
||||
struct tsens_irq_data d;
|
||||
unsigned long flags;
|
||||
int high_val, low_val, cl_high, cl_low;
|
||||
u32 hw_id = s->hw_id;
|
||||
|
||||
dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
|
||||
hw_id, __func__, low, high);
|
||||
|
||||
cl_high = clamp_val(high, -40000, 120000);
|
||||
cl_low = clamp_val(low, -40000, 120000);
|
||||
|
||||
high_val = tsens_mC_to_hw(s, cl_high);
|
||||
low_val = tsens_mC_to_hw(s, cl_low);
|
||||
|
||||
spin_lock_irqsave(&priv->ul_lock, flags);
|
||||
|
||||
tsens_read_irq_state(priv, hw_id, s, &d);
|
||||
|
||||
/* Write the new thresholds and clear the status */
|
||||
regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
|
||||
regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, true);
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, true);
|
||||
|
||||
spin_unlock_irqrestore(&priv->ul_lock, flags);
|
||||
|
||||
dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
|
||||
s->hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tsens_enable_irq(struct tsens_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
int val = tsens_version(priv) > VER_1_X ? 7 : 1;
|
||||
|
||||
ret = regmap_field_write(priv->rf[INT_EN], val);
|
||||
if (ret < 0)
|
||||
dev_err(priv->dev, "%s: failed to enable interrupts\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void tsens_disable_irq(struct tsens_priv *priv)
|
||||
{
|
||||
regmap_field_write(priv->rf[INT_EN], 0);
|
||||
}
|
||||
|
||||
int get_temp_tsens_valid(struct tsens_sensor *s, int *temp)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
int hw_id = s->hw_id;
|
||||
u32 temp_idx = LAST_TEMP_0 + hw_id;
|
||||
u32 valid_idx = VALID_0 + hw_id;
|
||||
u32 valid;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[valid_idx], &valid);
|
||||
@ -106,29 +481,18 @@ int get_temp_tsens_valid(struct tsens_priv *priv, int i, int *temp)
|
||||
}
|
||||
|
||||
/* Valid bit is set, OK to read the temperature */
|
||||
ret = regmap_field_read(priv->rf[temp_idx], &last_temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (priv->feat->adc) {
|
||||
/* Convert temperature from ADC code to milliCelsius */
|
||||
*temp = code_to_degc(last_temp, s) * 1000;
|
||||
} else {
|
||||
mask = GENMASK(priv->fields[LAST_TEMP_0].msb,
|
||||
priv->fields[LAST_TEMP_0].lsb);
|
||||
/* Convert temperature from deciCelsius to milliCelsius */
|
||||
*temp = sign_extend32(last_temp, fls(mask) - 1) * 100;
|
||||
}
|
||||
*temp = tsens_hw_to_mC(s, temp_idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_temp_common(struct tsens_priv *priv, int i, int *temp)
|
||||
int get_temp_common(struct tsens_sensor *s, int *temp)
|
||||
{
|
||||
struct tsens_sensor *s = &priv->sensor[i];
|
||||
struct tsens_priv *priv = s->priv;
|
||||
int hw_id = s->hw_id;
|
||||
int last_temp = 0, ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[LAST_TEMP_0 + s->hw_id], &last_temp);
|
||||
ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -137,6 +501,77 @@ int get_temp_common(struct tsens_priv *priv, int i, int *temp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int dbg_sensors_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct platform_device *pdev = s->private;
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
seq_printf(s, "max: %2d\nnum: %2d\n\n",
|
||||
priv->feat->max_sensors, priv->num_sensors);
|
||||
|
||||
seq_puts(s, " id slope offset\n--------------------------\n");
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id,
|
||||
priv->sensor[i].slope, priv->sensor[i].offset);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dbg_version_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct platform_device *pdev = s->private;
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
u32 maj_ver, min_ver, step_ver;
|
||||
int ret;
|
||||
|
||||
if (tsens_version(priv) > VER_0_1) {
|
||||
ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[VER_STEP], &step_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver);
|
||||
} else {
|
||||
seq_puts(s, "0.1.0\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(dbg_version);
|
||||
DEFINE_SHOW_ATTRIBUTE(dbg_sensors);
|
||||
|
||||
static void tsens_debug_init(struct platform_device *pdev)
|
||||
{
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
struct dentry *root, *file;
|
||||
|
||||
root = debugfs_lookup("tsens", NULL);
|
||||
if (!root)
|
||||
priv->debug_root = debugfs_create_dir("tsens", NULL);
|
||||
else
|
||||
priv->debug_root = root;
|
||||
|
||||
file = debugfs_lookup("version", priv->debug_root);
|
||||
if (!file)
|
||||
debugfs_create_file("version", 0444, priv->debug_root,
|
||||
pdev, &dbg_version_fops);
|
||||
|
||||
/* A directory for each instance of the TSENS IP */
|
||||
priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
|
||||
debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
|
||||
}
|
||||
#else
|
||||
static inline void tsens_debug_init(struct platform_device *pdev) {}
|
||||
#endif
|
||||
|
||||
static const struct regmap_config tsens_config = {
|
||||
.name = "tm",
|
||||
.reg_bits = 32,
|
||||
@ -197,6 +632,15 @@ int __init init_common(struct tsens_priv *priv)
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
if (tsens_version(priv) > VER_0_1) {
|
||||
for (i = VER_MAJOR; i <= VER_STEP; i++) {
|
||||
priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map,
|
||||
priv->fields[i]);
|
||||
if (IS_ERR(priv->rf[i]))
|
||||
return PTR_ERR(priv->rf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
|
||||
priv->fields[TSENS_EN]);
|
||||
if (IS_ERR(priv->rf[TSENS_EN])) {
|
||||
@ -207,7 +651,7 @@ int __init init_common(struct tsens_priv *priv)
|
||||
if (ret)
|
||||
goto err_put_device;
|
||||
if (!enabled) {
|
||||
dev_err(dev, "tsens device is not enabled\n");
|
||||
dev_err(dev, "%s: device not enabled\n", __func__);
|
||||
ret = -ENODEV;
|
||||
goto err_put_device;
|
||||
}
|
||||
@ -218,24 +662,31 @@ int __init init_common(struct tsens_priv *priv)
|
||||
ret = PTR_ERR(priv->rf[SENSOR_EN]);
|
||||
goto err_put_device;
|
||||
}
|
||||
/* now alloc regmap_fields in tm_map */
|
||||
for (i = 0, j = LAST_TEMP_0; i < priv->feat->max_sensors; i++, j++) {
|
||||
priv->rf[j] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[j]);
|
||||
if (IS_ERR(priv->rf[j])) {
|
||||
ret = PTR_ERR(priv->rf[j]);
|
||||
goto err_put_device;
|
||||
}
|
||||
}
|
||||
for (i = 0, j = VALID_0; i < priv->feat->max_sensors; i++, j++) {
|
||||
priv->rf[j] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[j]);
|
||||
if (IS_ERR(priv->rf[j])) {
|
||||
ret = PTR_ERR(priv->rf[j]);
|
||||
goto err_put_device;
|
||||
priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[INT_EN]);
|
||||
if (IS_ERR(priv->rf[INT_EN])) {
|
||||
ret = PTR_ERR(priv->rf[INT_EN]);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
/* This loop might need changes if enum regfield_ids is reordered */
|
||||
for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
|
||||
for (i = 0; i < priv->feat->max_sensors; i++) {
|
||||
int idx = j + i;
|
||||
|
||||
priv->rf[idx] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[idx]);
|
||||
if (IS_ERR(priv->rf[idx])) {
|
||||
ret = PTR_ERR(priv->rf[idx]);
|
||||
goto err_put_device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock_init(&priv->ul_lock);
|
||||
tsens_enable_irq(priv);
|
||||
tsens_debug_init(op);
|
||||
|
||||
return 0;
|
||||
|
||||
err_put_device:
|
||||
|
@ -347,9 +347,20 @@ static const struct reg_field tsens_v0_1_regfields[MAX_REGFIELDS] = {
|
||||
/* INTERRUPT ENABLE */
|
||||
[INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 0),
|
||||
|
||||
/* UPPER/LOWER TEMPERATURE THRESHOLDS */
|
||||
REG_FIELD_FOR_EACH_SENSOR11(LOW_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 0, 9),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(UP_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 10, 19),
|
||||
|
||||
/* UPPER/LOWER INTERRUPTS [CLEAR/STATUS] */
|
||||
REG_FIELD_FOR_EACH_SENSOR11(LOW_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 20, 20),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(UP_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 21, 21),
|
||||
|
||||
/* NO CRITICAL INTERRUPT SUPPORT on v0.1 */
|
||||
|
||||
/* Sn_STATUS */
|
||||
REG_FIELD_FOR_EACH_SENSOR11(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 9),
|
||||
/* No VALID field on v0.1 */
|
||||
/* xxx_STATUS bits: 1 == threshold violated */
|
||||
REG_FIELD_FOR_EACH_SENSOR11(MIN_STATUS, TM_Sn_STATUS_OFF, 10, 10),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(LOWER_STATUS, TM_Sn_STATUS_OFF, 11, 11),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(UPPER_STATUS, TM_Sn_STATUS_OFF, 12, 12),
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include "tsens.h"
|
||||
|
||||
/* ----- SROT ------ */
|
||||
@ -17,6 +18,70 @@
|
||||
#define TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF 0x0004
|
||||
#define TM_Sn_STATUS_OFF 0x0044
|
||||
#define TM_TRDY_OFF 0x0084
|
||||
#define TM_HIGH_LOW_INT_STATUS_OFF 0x0088
|
||||
#define TM_HIGH_LOW_Sn_INT_THRESHOLD_OFF 0x0090
|
||||
|
||||
/* eeprom layout data for msm8956/76 (v1) */
|
||||
#define MSM8976_BASE0_MASK 0xff
|
||||
#define MSM8976_BASE1_MASK 0xff
|
||||
#define MSM8976_BASE1_SHIFT 8
|
||||
|
||||
#define MSM8976_S0_P1_MASK 0x3f00
|
||||
#define MSM8976_S1_P1_MASK 0x3f00000
|
||||
#define MSM8976_S2_P1_MASK 0x3f
|
||||
#define MSM8976_S3_P1_MASK 0x3f000
|
||||
#define MSM8976_S4_P1_MASK 0x3f00
|
||||
#define MSM8976_S5_P1_MASK 0x3f00000
|
||||
#define MSM8976_S6_P1_MASK 0x3f
|
||||
#define MSM8976_S7_P1_MASK 0x3f000
|
||||
#define MSM8976_S8_P1_MASK 0x1f8
|
||||
#define MSM8976_S9_P1_MASK 0x1f8000
|
||||
#define MSM8976_S10_P1_MASK 0xf8000000
|
||||
#define MSM8976_S10_P1_MASK_1 0x1
|
||||
|
||||
#define MSM8976_S0_P2_MASK 0xfc000
|
||||
#define MSM8976_S1_P2_MASK 0xfc000000
|
||||
#define MSM8976_S2_P2_MASK 0xfc0
|
||||
#define MSM8976_S3_P2_MASK 0xfc0000
|
||||
#define MSM8976_S4_P2_MASK 0xfc000
|
||||
#define MSM8976_S5_P2_MASK 0xfc000000
|
||||
#define MSM8976_S6_P2_MASK 0xfc0
|
||||
#define MSM8976_S7_P2_MASK 0xfc0000
|
||||
#define MSM8976_S8_P2_MASK 0x7e00
|
||||
#define MSM8976_S9_P2_MASK 0x7e00000
|
||||
#define MSM8976_S10_P2_MASK 0x7e
|
||||
|
||||
#define MSM8976_S0_P1_SHIFT 8
|
||||
#define MSM8976_S1_P1_SHIFT 20
|
||||
#define MSM8976_S2_P1_SHIFT 0
|
||||
#define MSM8976_S3_P1_SHIFT 12
|
||||
#define MSM8976_S4_P1_SHIFT 8
|
||||
#define MSM8976_S5_P1_SHIFT 20
|
||||
#define MSM8976_S6_P1_SHIFT 0
|
||||
#define MSM8976_S7_P1_SHIFT 12
|
||||
#define MSM8976_S8_P1_SHIFT 3
|
||||
#define MSM8976_S9_P1_SHIFT 15
|
||||
#define MSM8976_S10_P1_SHIFT 27
|
||||
#define MSM8976_S10_P1_SHIFT_1 0
|
||||
|
||||
#define MSM8976_S0_P2_SHIFT 14
|
||||
#define MSM8976_S1_P2_SHIFT 26
|
||||
#define MSM8976_S2_P2_SHIFT 6
|
||||
#define MSM8976_S3_P2_SHIFT 18
|
||||
#define MSM8976_S4_P2_SHIFT 14
|
||||
#define MSM8976_S5_P2_SHIFT 26
|
||||
#define MSM8976_S6_P2_SHIFT 6
|
||||
#define MSM8976_S7_P2_SHIFT 18
|
||||
#define MSM8976_S8_P2_SHIFT 9
|
||||
#define MSM8976_S9_P2_SHIFT 21
|
||||
#define MSM8976_S10_P2_SHIFT 1
|
||||
|
||||
#define MSM8976_CAL_SEL_MASK 0x3
|
||||
|
||||
#define MSM8976_CAL_DEGC_PT1 30
|
||||
#define MSM8976_CAL_DEGC_PT2 120
|
||||
#define MSM8976_SLOPE_FACTOR 1000
|
||||
#define MSM8976_SLOPE_DEFAULT 3200
|
||||
|
||||
/* eeprom layout data for qcs404/405 (v1) */
|
||||
#define BASE0_MASK 0x000007f8
|
||||
@ -77,6 +142,30 @@
|
||||
#define CAL_SEL_MASK 7
|
||||
#define CAL_SEL_SHIFT 0
|
||||
|
||||
static void compute_intercept_slope_8976(struct tsens_priv *priv,
|
||||
u32 *p1, u32 *p2, u32 mode)
|
||||
{
|
||||
int i;
|
||||
|
||||
priv->sensor[0].slope = 3313;
|
||||
priv->sensor[1].slope = 3275;
|
||||
priv->sensor[2].slope = 3320;
|
||||
priv->sensor[3].slope = 3246;
|
||||
priv->sensor[4].slope = 3279;
|
||||
priv->sensor[5].slope = 3257;
|
||||
priv->sensor[6].slope = 3234;
|
||||
priv->sensor[7].slope = 3269;
|
||||
priv->sensor[8].slope = 3255;
|
||||
priv->sensor[9].slope = 3239;
|
||||
priv->sensor[10].slope = 3286;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
priv->sensor[i].offset = (p1[i] * MSM8976_SLOPE_FACTOR) -
|
||||
(MSM8976_CAL_DEGC_PT1 *
|
||||
priv->sensor[i].slope);
|
||||
}
|
||||
}
|
||||
|
||||
static int calibrate_v1(struct tsens_priv *priv)
|
||||
{
|
||||
u32 base0 = 0, base1 = 0;
|
||||
@ -143,7 +232,72 @@ static int calibrate_v1(struct tsens_priv *priv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* v1.x: qcs404,405 */
|
||||
static int calibrate_8976(struct tsens_priv *priv)
|
||||
{
|
||||
int base0 = 0, base1 = 0, i;
|
||||
u32 p1[11], p2[11];
|
||||
int mode = 0, tmp = 0;
|
||||
u32 *qfprom_cdata;
|
||||
|
||||
qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
|
||||
if (IS_ERR(qfprom_cdata))
|
||||
return PTR_ERR(qfprom_cdata);
|
||||
|
||||
mode = (qfprom_cdata[4] & MSM8976_CAL_SEL_MASK);
|
||||
dev_dbg(priv->dev, "calibration mode is %d\n", mode);
|
||||
|
||||
switch (mode) {
|
||||
case TWO_PT_CALIB:
|
||||
base1 = (qfprom_cdata[2] & MSM8976_BASE1_MASK) >> MSM8976_BASE1_SHIFT;
|
||||
p2[0] = (qfprom_cdata[0] & MSM8976_S0_P2_MASK) >> MSM8976_S0_P2_SHIFT;
|
||||
p2[1] = (qfprom_cdata[0] & MSM8976_S1_P2_MASK) >> MSM8976_S1_P2_SHIFT;
|
||||
p2[2] = (qfprom_cdata[1] & MSM8976_S2_P2_MASK) >> MSM8976_S2_P2_SHIFT;
|
||||
p2[3] = (qfprom_cdata[1] & MSM8976_S3_P2_MASK) >> MSM8976_S3_P2_SHIFT;
|
||||
p2[4] = (qfprom_cdata[2] & MSM8976_S4_P2_MASK) >> MSM8976_S4_P2_SHIFT;
|
||||
p2[5] = (qfprom_cdata[2] & MSM8976_S5_P2_MASK) >> MSM8976_S5_P2_SHIFT;
|
||||
p2[6] = (qfprom_cdata[3] & MSM8976_S6_P2_MASK) >> MSM8976_S6_P2_SHIFT;
|
||||
p2[7] = (qfprom_cdata[3] & MSM8976_S7_P2_MASK) >> MSM8976_S7_P2_SHIFT;
|
||||
p2[8] = (qfprom_cdata[4] & MSM8976_S8_P2_MASK) >> MSM8976_S8_P2_SHIFT;
|
||||
p2[9] = (qfprom_cdata[4] & MSM8976_S9_P2_MASK) >> MSM8976_S9_P2_SHIFT;
|
||||
p2[10] = (qfprom_cdata[5] & MSM8976_S10_P2_MASK) >> MSM8976_S10_P2_SHIFT;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++)
|
||||
p2[i] = ((base1 + p2[i]) << 2);
|
||||
/* Fall through */
|
||||
case ONE_PT_CALIB2:
|
||||
base0 = qfprom_cdata[0] & MSM8976_BASE0_MASK;
|
||||
p1[0] = (qfprom_cdata[0] & MSM8976_S0_P1_MASK) >> MSM8976_S0_P1_SHIFT;
|
||||
p1[1] = (qfprom_cdata[0] & MSM8976_S1_P1_MASK) >> MSM8976_S1_P1_SHIFT;
|
||||
p1[2] = (qfprom_cdata[1] & MSM8976_S2_P1_MASK) >> MSM8976_S2_P1_SHIFT;
|
||||
p1[3] = (qfprom_cdata[1] & MSM8976_S3_P1_MASK) >> MSM8976_S3_P1_SHIFT;
|
||||
p1[4] = (qfprom_cdata[2] & MSM8976_S4_P1_MASK) >> MSM8976_S4_P1_SHIFT;
|
||||
p1[5] = (qfprom_cdata[2] & MSM8976_S5_P1_MASK) >> MSM8976_S5_P1_SHIFT;
|
||||
p1[6] = (qfprom_cdata[3] & MSM8976_S6_P1_MASK) >> MSM8976_S6_P1_SHIFT;
|
||||
p1[7] = (qfprom_cdata[3] & MSM8976_S7_P1_MASK) >> MSM8976_S7_P1_SHIFT;
|
||||
p1[8] = (qfprom_cdata[4] & MSM8976_S8_P1_MASK) >> MSM8976_S8_P1_SHIFT;
|
||||
p1[9] = (qfprom_cdata[4] & MSM8976_S9_P1_MASK) >> MSM8976_S9_P1_SHIFT;
|
||||
p1[10] = (qfprom_cdata[4] & MSM8976_S10_P1_MASK) >> MSM8976_S10_P1_SHIFT;
|
||||
tmp = (qfprom_cdata[5] & MSM8976_S10_P1_MASK_1) << MSM8976_S10_P1_SHIFT_1;
|
||||
p1[10] |= tmp;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++)
|
||||
p1[i] = (((base0) + p1[i]) << 2);
|
||||
break;
|
||||
default:
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
p1[i] = 500;
|
||||
p2[i] = 780;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
compute_intercept_slope_8976(priv, p1, p2, mode);
|
||||
kfree(qfprom_cdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* v1.x: msm8956,8976,qcs404,405 */
|
||||
|
||||
static const struct tsens_features tsens_v1_feat = {
|
||||
.ver_major = VER_1_X,
|
||||
@ -168,9 +322,36 @@ static const struct reg_field tsens_v1_regfields[MAX_REGFIELDS] = {
|
||||
/* INTERRUPT ENABLE */
|
||||
[INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 0),
|
||||
|
||||
/* UPPER/LOWER TEMPERATURE THRESHOLDS */
|
||||
REG_FIELD_FOR_EACH_SENSOR11(LOW_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 0, 9),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(UP_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 10, 19),
|
||||
|
||||
/* UPPER/LOWER INTERRUPTS [CLEAR/STATUS] */
|
||||
REG_FIELD_FOR_EACH_SENSOR11(LOW_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 20, 20),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(UP_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 21, 21),
|
||||
[LOW_INT_STATUS_0] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 0, 0),
|
||||
[LOW_INT_STATUS_1] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 1, 1),
|
||||
[LOW_INT_STATUS_2] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 2, 2),
|
||||
[LOW_INT_STATUS_3] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 3, 3),
|
||||
[LOW_INT_STATUS_4] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 4, 4),
|
||||
[LOW_INT_STATUS_5] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 5, 5),
|
||||
[LOW_INT_STATUS_6] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 6, 6),
|
||||
[LOW_INT_STATUS_7] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 7, 7),
|
||||
[UP_INT_STATUS_0] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 8, 8),
|
||||
[UP_INT_STATUS_1] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 9, 9),
|
||||
[UP_INT_STATUS_2] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 10, 10),
|
||||
[UP_INT_STATUS_3] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 11, 11),
|
||||
[UP_INT_STATUS_4] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 12, 12),
|
||||
[UP_INT_STATUS_5] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 13, 13),
|
||||
[UP_INT_STATUS_6] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 14, 14),
|
||||
[UP_INT_STATUS_7] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 15, 15),
|
||||
|
||||
/* NO CRITICAL INTERRUPT SUPPORT on v1 */
|
||||
|
||||
/* Sn_STATUS */
|
||||
REG_FIELD_FOR_EACH_SENSOR11(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 9),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(VALID, TM_Sn_STATUS_OFF, 14, 14),
|
||||
/* xxx_STATUS bits: 1 == threshold violated */
|
||||
REG_FIELD_FOR_EACH_SENSOR11(MIN_STATUS, TM_Sn_STATUS_OFF, 10, 10),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(LOWER_STATUS, TM_Sn_STATUS_OFF, 11, 11),
|
||||
REG_FIELD_FOR_EACH_SENSOR11(UPPER_STATUS, TM_Sn_STATUS_OFF, 12, 12),
|
||||
@ -192,3 +373,18 @@ const struct tsens_plat_data data_tsens_v1 = {
|
||||
.feat = &tsens_v1_feat,
|
||||
.fields = tsens_v1_regfields,
|
||||
};
|
||||
|
||||
static const struct tsens_ops ops_8976 = {
|
||||
.init = init_common,
|
||||
.calibrate = calibrate_8976,
|
||||
.get_temp = get_temp_tsens_valid,
|
||||
};
|
||||
|
||||
/* Valid for both MSM8956 and MSM8976. Sensor ID 3 is unused. */
|
||||
const struct tsens_plat_data data_8976 = {
|
||||
.num_sensors = 11,
|
||||
.ops = &ops_8976,
|
||||
.hw_ids = (unsigned int[]){0, 1, 2, 4, 5, 6, 7, 8, 9, 10},
|
||||
.feat = &tsens_v1_feat,
|
||||
.fields = tsens_v1_regfields,
|
||||
};
|
||||
|
@ -50,9 +50,22 @@ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
|
||||
/* v2 has separate enables for UPPER/LOWER/CRITICAL interrupts */
|
||||
[INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 2),
|
||||
|
||||
/* TEMPERATURE THRESHOLDS */
|
||||
REG_FIELD_FOR_EACH_SENSOR16(LOW_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 0, 11),
|
||||
REG_FIELD_FOR_EACH_SENSOR16(UP_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 12, 23),
|
||||
|
||||
/* INTERRUPTS [CLEAR/STATUS/MASK] */
|
||||
REG_FIELD_SPLIT_BITS_0_15(LOW_INT_STATUS, TM_UPPER_LOWER_INT_STATUS_OFF),
|
||||
REG_FIELD_SPLIT_BITS_0_15(LOW_INT_CLEAR, TM_UPPER_LOWER_INT_CLEAR_OFF),
|
||||
REG_FIELD_SPLIT_BITS_0_15(LOW_INT_MASK, TM_UPPER_LOWER_INT_MASK_OFF),
|
||||
REG_FIELD_SPLIT_BITS_16_31(UP_INT_STATUS, TM_UPPER_LOWER_INT_STATUS_OFF),
|
||||
REG_FIELD_SPLIT_BITS_16_31(UP_INT_CLEAR, TM_UPPER_LOWER_INT_CLEAR_OFF),
|
||||
REG_FIELD_SPLIT_BITS_16_31(UP_INT_MASK, TM_UPPER_LOWER_INT_MASK_OFF),
|
||||
|
||||
/* Sn_STATUS */
|
||||
REG_FIELD_FOR_EACH_SENSOR16(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 11),
|
||||
REG_FIELD_FOR_EACH_SENSOR16(VALID, TM_Sn_STATUS_OFF, 21, 21),
|
||||
/* xxx_STATUS bits: 1 == threshold violated */
|
||||
REG_FIELD_FOR_EACH_SENSOR16(MIN_STATUS, TM_Sn_STATUS_OFF, 16, 16),
|
||||
REG_FIELD_FOR_EACH_SENSOR16(LOWER_STATUS, TM_Sn_STATUS_OFF, 17, 17),
|
||||
REG_FIELD_FOR_EACH_SENSOR16(UPPER_STATUS, TM_Sn_STATUS_OFF, 18, 18),
|
||||
|
@ -3,9 +3,11 @@
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/slab.h>
|
||||
@ -14,19 +16,19 @@
|
||||
|
||||
static int tsens_get_temp(void *data, int *temp)
|
||||
{
|
||||
const struct tsens_sensor *s = data;
|
||||
struct tsens_sensor *s = data;
|
||||
struct tsens_priv *priv = s->priv;
|
||||
|
||||
return priv->ops->get_temp(priv, s->id, temp);
|
||||
return priv->ops->get_temp(s, temp);
|
||||
}
|
||||
|
||||
static int tsens_get_trend(void *data, int trip, enum thermal_trend *trend)
|
||||
{
|
||||
const struct tsens_sensor *s = data;
|
||||
struct tsens_sensor *s = data;
|
||||
struct tsens_priv *priv = s->priv;
|
||||
|
||||
if (priv->ops->get_trend)
|
||||
return priv->ops->get_trend(priv, s->id, trend);
|
||||
return priv->ops->get_trend(s, trend);
|
||||
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
@ -60,6 +62,9 @@ static const struct of_device_id tsens_table[] = {
|
||||
}, {
|
||||
.compatible = "qcom,msm8974-tsens",
|
||||
.data = &data_8974,
|
||||
}, {
|
||||
.compatible = "qcom,msm8976-tsens",
|
||||
.data = &data_8976,
|
||||
}, {
|
||||
.compatible = "qcom,msm8996-tsens",
|
||||
.data = &data_8996,
|
||||
@ -77,17 +82,18 @@ MODULE_DEVICE_TABLE(of, tsens_table);
|
||||
static const struct thermal_zone_of_device_ops tsens_of_ops = {
|
||||
.get_temp = tsens_get_temp,
|
||||
.get_trend = tsens_get_trend,
|
||||
.set_trips = tsens_set_trips,
|
||||
};
|
||||
|
||||
static int tsens_register(struct tsens_priv *priv)
|
||||
{
|
||||
int i;
|
||||
int i, ret, irq;
|
||||
struct thermal_zone_device *tzd;
|
||||
struct platform_device *pdev;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
priv->sensor[i].priv = priv;
|
||||
priv->sensor[i].id = i;
|
||||
tzd = devm_thermal_zone_of_sensor_register(priv->dev, i,
|
||||
tzd = devm_thermal_zone_of_sensor_register(priv->dev, priv->sensor[i].hw_id,
|
||||
&priv->sensor[i],
|
||||
&tsens_of_ops);
|
||||
if (IS_ERR(tzd))
|
||||
@ -96,7 +102,31 @@ static int tsens_register(struct tsens_priv *priv)
|
||||
if (priv->ops->enable)
|
||||
priv->ops->enable(priv, i);
|
||||
}
|
||||
return 0;
|
||||
|
||||
pdev = of_find_device_by_node(priv->dev->of_node);
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
irq = platform_get_irq_byname(pdev, "uplow");
|
||||
if (irq < 0) {
|
||||
ret = irq;
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, irq,
|
||||
NULL, tsens_irq_thread,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
dev_name(&pdev->dev), priv);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s: failed to get irq\n", __func__);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
enable_irq_wake(irq);
|
||||
|
||||
err_put_device:
|
||||
put_device(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tsens_probe(struct platform_device *pdev)
|
||||
@ -128,7 +158,7 @@ static int tsens_probe(struct platform_device *pdev)
|
||||
of_property_read_u32(np, "#qcom,sensors", &num_sensors);
|
||||
|
||||
if (num_sensors <= 0) {
|
||||
dev_err(dev, "invalid number of sensors\n");
|
||||
dev_err(dev, "%s: invalid number of sensors\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -150,12 +180,14 @@ static int tsens_probe(struct platform_device *pdev)
|
||||
priv->feat = data->feat;
|
||||
priv->fields = data->fields;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
if (!priv->ops || !priv->ops->init || !priv->ops->get_temp)
|
||||
return -EINVAL;
|
||||
|
||||
ret = priv->ops->init(priv);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "tsens init failed\n");
|
||||
dev_err(dev, "%s: init failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -163,22 +195,20 @@ static int tsens_probe(struct platform_device *pdev)
|
||||
ret = priv->ops->calibrate(priv);
|
||||
if (ret < 0) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "tsens calibration failed\n");
|
||||
dev_err(dev, "%s: calibration failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = tsens_register(priv);
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
return ret;
|
||||
return tsens_register(priv);
|
||||
}
|
||||
|
||||
static int tsens_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
debugfs_remove_recursive(priv->debug_root);
|
||||
tsens_disable_irq(priv);
|
||||
if (priv->ops->disable)
|
||||
priv->ops->disable(priv);
|
||||
|
||||
|
@ -13,8 +13,10 @@
|
||||
#define CAL_DEGC_PT2 120
|
||||
#define SLOPE_FACTOR 1000
|
||||
#define SLOPE_DEFAULT 3200
|
||||
#define THRESHOLD_MAX_ADC_CODE 0x3ff
|
||||
#define THRESHOLD_MIN_ADC_CODE 0x0
|
||||
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
@ -27,12 +29,16 @@ enum tsens_ver {
|
||||
VER_2_X,
|
||||
};
|
||||
|
||||
enum tsens_irq_type {
|
||||
LOWER,
|
||||
UPPER,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tsens_sensor - data for each sensor connected to the tsens device
|
||||
* @priv: tsens device instance that this sensor is connected to
|
||||
* @tzd: pointer to the thermal zone that this sensor is in
|
||||
* @offset: offset of temperature adjustment curve
|
||||
* @id: Sensor ID
|
||||
* @hw_id: HW ID can be used in case of platform-specific IDs
|
||||
* @slope: slope of temperature adjustment curve
|
||||
* @status: 8960-specific variable to track 8960 and 8660 status register offset
|
||||
@ -41,7 +47,6 @@ struct tsens_sensor {
|
||||
struct tsens_priv *priv;
|
||||
struct thermal_zone_device *tzd;
|
||||
int offset;
|
||||
unsigned int id;
|
||||
unsigned int hw_id;
|
||||
int slope;
|
||||
u32 status;
|
||||
@ -62,13 +67,13 @@ struct tsens_ops {
|
||||
/* mandatory callbacks */
|
||||
int (*init)(struct tsens_priv *priv);
|
||||
int (*calibrate)(struct tsens_priv *priv);
|
||||
int (*get_temp)(struct tsens_priv *priv, int i, int *temp);
|
||||
int (*get_temp)(struct tsens_sensor *s, int *temp);
|
||||
/* optional callbacks */
|
||||
int (*enable)(struct tsens_priv *priv, int i);
|
||||
void (*disable)(struct tsens_priv *priv);
|
||||
int (*suspend)(struct tsens_priv *priv);
|
||||
int (*resume)(struct tsens_priv *priv);
|
||||
int (*get_trend)(struct tsens_priv *priv, int i, enum thermal_trend *trend);
|
||||
int (*get_trend)(struct tsens_sensor *s, enum thermal_trend *trend);
|
||||
};
|
||||
|
||||
#define REG_FIELD_FOR_EACH_SENSOR11(_name, _offset, _startbit, _stopbit) \
|
||||
@ -102,22 +107,66 @@ struct tsens_ops {
|
||||
[_name##_##14] = REG_FIELD(_offset + 56, _startbit, _stopbit), \
|
||||
[_name##_##15] = REG_FIELD(_offset + 60, _startbit, _stopbit)
|
||||
|
||||
/* reg_field IDs to use as an index into an array */
|
||||
#define REG_FIELD_SPLIT_BITS_0_15(_name, _offset) \
|
||||
[_name##_##0] = REG_FIELD(_offset, 0, 0), \
|
||||
[_name##_##1] = REG_FIELD(_offset, 1, 1), \
|
||||
[_name##_##2] = REG_FIELD(_offset, 2, 2), \
|
||||
[_name##_##3] = REG_FIELD(_offset, 3, 3), \
|
||||
[_name##_##4] = REG_FIELD(_offset, 4, 4), \
|
||||
[_name##_##5] = REG_FIELD(_offset, 5, 5), \
|
||||
[_name##_##6] = REG_FIELD(_offset, 6, 6), \
|
||||
[_name##_##7] = REG_FIELD(_offset, 7, 7), \
|
||||
[_name##_##8] = REG_FIELD(_offset, 8, 8), \
|
||||
[_name##_##9] = REG_FIELD(_offset, 9, 9), \
|
||||
[_name##_##10] = REG_FIELD(_offset, 10, 10), \
|
||||
[_name##_##11] = REG_FIELD(_offset, 11, 11), \
|
||||
[_name##_##12] = REG_FIELD(_offset, 12, 12), \
|
||||
[_name##_##13] = REG_FIELD(_offset, 13, 13), \
|
||||
[_name##_##14] = REG_FIELD(_offset, 14, 14), \
|
||||
[_name##_##15] = REG_FIELD(_offset, 15, 15)
|
||||
|
||||
#define REG_FIELD_SPLIT_BITS_16_31(_name, _offset) \
|
||||
[_name##_##0] = REG_FIELD(_offset, 16, 16), \
|
||||
[_name##_##1] = REG_FIELD(_offset, 17, 17), \
|
||||
[_name##_##2] = REG_FIELD(_offset, 18, 18), \
|
||||
[_name##_##3] = REG_FIELD(_offset, 19, 19), \
|
||||
[_name##_##4] = REG_FIELD(_offset, 20, 20), \
|
||||
[_name##_##5] = REG_FIELD(_offset, 21, 21), \
|
||||
[_name##_##6] = REG_FIELD(_offset, 22, 22), \
|
||||
[_name##_##7] = REG_FIELD(_offset, 23, 23), \
|
||||
[_name##_##8] = REG_FIELD(_offset, 24, 24), \
|
||||
[_name##_##9] = REG_FIELD(_offset, 25, 25), \
|
||||
[_name##_##10] = REG_FIELD(_offset, 26, 26), \
|
||||
[_name##_##11] = REG_FIELD(_offset, 27, 27), \
|
||||
[_name##_##12] = REG_FIELD(_offset, 28, 28), \
|
||||
[_name##_##13] = REG_FIELD(_offset, 29, 29), \
|
||||
[_name##_##14] = REG_FIELD(_offset, 30, 30), \
|
||||
[_name##_##15] = REG_FIELD(_offset, 31, 31)
|
||||
|
||||
/*
|
||||
* reg_field IDs to use as an index into an array
|
||||
* If you change the order of the entries, check the devm_regmap_field_alloc()
|
||||
* calls in init_common()
|
||||
*/
|
||||
enum regfield_ids {
|
||||
/* ----- SROT ------ */
|
||||
/* HW_VER */
|
||||
VER_MAJOR = 0,
|
||||
VER_MAJOR,
|
||||
VER_MINOR,
|
||||
VER_STEP,
|
||||
/* CTRL_OFFSET */
|
||||
TSENS_EN = 3,
|
||||
TSENS_EN,
|
||||
TSENS_SW_RST,
|
||||
SENSOR_EN,
|
||||
CODE_OR_TEMP,
|
||||
|
||||
/* ----- TM ------ */
|
||||
/* TRDY */
|
||||
TRDY,
|
||||
/* INTERRUPT ENABLE */
|
||||
INT_EN, /* v2+ has separate enables for crit, upper and lower irq */
|
||||
/* STATUS */
|
||||
LAST_TEMP_0 = 7, /* Last temperature reading */
|
||||
LAST_TEMP_0, /* Last temperature reading */
|
||||
LAST_TEMP_1,
|
||||
LAST_TEMP_2,
|
||||
LAST_TEMP_3,
|
||||
@ -133,7 +182,7 @@ enum regfield_ids {
|
||||
LAST_TEMP_13,
|
||||
LAST_TEMP_14,
|
||||
LAST_TEMP_15,
|
||||
VALID_0 = 23, /* VALID reading or not */
|
||||
VALID_0, /* VALID reading or not */
|
||||
VALID_1,
|
||||
VALID_2,
|
||||
VALID_3,
|
||||
@ -149,6 +198,182 @@ enum regfield_ids {
|
||||
VALID_13,
|
||||
VALID_14,
|
||||
VALID_15,
|
||||
LOWER_STATUS_0, /* LOWER threshold violated */
|
||||
LOWER_STATUS_1,
|
||||
LOWER_STATUS_2,
|
||||
LOWER_STATUS_3,
|
||||
LOWER_STATUS_4,
|
||||
LOWER_STATUS_5,
|
||||
LOWER_STATUS_6,
|
||||
LOWER_STATUS_7,
|
||||
LOWER_STATUS_8,
|
||||
LOWER_STATUS_9,
|
||||
LOWER_STATUS_10,
|
||||
LOWER_STATUS_11,
|
||||
LOWER_STATUS_12,
|
||||
LOWER_STATUS_13,
|
||||
LOWER_STATUS_14,
|
||||
LOWER_STATUS_15,
|
||||
LOW_INT_STATUS_0, /* LOWER interrupt status */
|
||||
LOW_INT_STATUS_1,
|
||||
LOW_INT_STATUS_2,
|
||||
LOW_INT_STATUS_3,
|
||||
LOW_INT_STATUS_4,
|
||||
LOW_INT_STATUS_5,
|
||||
LOW_INT_STATUS_6,
|
||||
LOW_INT_STATUS_7,
|
||||
LOW_INT_STATUS_8,
|
||||
LOW_INT_STATUS_9,
|
||||
LOW_INT_STATUS_10,
|
||||
LOW_INT_STATUS_11,
|
||||
LOW_INT_STATUS_12,
|
||||
LOW_INT_STATUS_13,
|
||||
LOW_INT_STATUS_14,
|
||||
LOW_INT_STATUS_15,
|
||||
LOW_INT_CLEAR_0, /* LOWER interrupt clear */
|
||||
LOW_INT_CLEAR_1,
|
||||
LOW_INT_CLEAR_2,
|
||||
LOW_INT_CLEAR_3,
|
||||
LOW_INT_CLEAR_4,
|
||||
LOW_INT_CLEAR_5,
|
||||
LOW_INT_CLEAR_6,
|
||||
LOW_INT_CLEAR_7,
|
||||
LOW_INT_CLEAR_8,
|
||||
LOW_INT_CLEAR_9,
|
||||
LOW_INT_CLEAR_10,
|
||||
LOW_INT_CLEAR_11,
|
||||
LOW_INT_CLEAR_12,
|
||||
LOW_INT_CLEAR_13,
|
||||
LOW_INT_CLEAR_14,
|
||||
LOW_INT_CLEAR_15,
|
||||
LOW_INT_MASK_0, /* LOWER interrupt mask */
|
||||
LOW_INT_MASK_1,
|
||||
LOW_INT_MASK_2,
|
||||
LOW_INT_MASK_3,
|
||||
LOW_INT_MASK_4,
|
||||
LOW_INT_MASK_5,
|
||||
LOW_INT_MASK_6,
|
||||
LOW_INT_MASK_7,
|
||||
LOW_INT_MASK_8,
|
||||
LOW_INT_MASK_9,
|
||||
LOW_INT_MASK_10,
|
||||
LOW_INT_MASK_11,
|
||||
LOW_INT_MASK_12,
|
||||
LOW_INT_MASK_13,
|
||||
LOW_INT_MASK_14,
|
||||
LOW_INT_MASK_15,
|
||||
LOW_THRESH_0, /* LOWER threshold values */
|
||||
LOW_THRESH_1,
|
||||
LOW_THRESH_2,
|
||||
LOW_THRESH_3,
|
||||
LOW_THRESH_4,
|
||||
LOW_THRESH_5,
|
||||
LOW_THRESH_6,
|
||||
LOW_THRESH_7,
|
||||
LOW_THRESH_8,
|
||||
LOW_THRESH_9,
|
||||
LOW_THRESH_10,
|
||||
LOW_THRESH_11,
|
||||
LOW_THRESH_12,
|
||||
LOW_THRESH_13,
|
||||
LOW_THRESH_14,
|
||||
LOW_THRESH_15,
|
||||
UPPER_STATUS_0, /* UPPER threshold violated */
|
||||
UPPER_STATUS_1,
|
||||
UPPER_STATUS_2,
|
||||
UPPER_STATUS_3,
|
||||
UPPER_STATUS_4,
|
||||
UPPER_STATUS_5,
|
||||
UPPER_STATUS_6,
|
||||
UPPER_STATUS_7,
|
||||
UPPER_STATUS_8,
|
||||
UPPER_STATUS_9,
|
||||
UPPER_STATUS_10,
|
||||
UPPER_STATUS_11,
|
||||
UPPER_STATUS_12,
|
||||
UPPER_STATUS_13,
|
||||
UPPER_STATUS_14,
|
||||
UPPER_STATUS_15,
|
||||
UP_INT_STATUS_0, /* UPPER interrupt status */
|
||||
UP_INT_STATUS_1,
|
||||
UP_INT_STATUS_2,
|
||||
UP_INT_STATUS_3,
|
||||
UP_INT_STATUS_4,
|
||||
UP_INT_STATUS_5,
|
||||
UP_INT_STATUS_6,
|
||||
UP_INT_STATUS_7,
|
||||
UP_INT_STATUS_8,
|
||||
UP_INT_STATUS_9,
|
||||
UP_INT_STATUS_10,
|
||||
UP_INT_STATUS_11,
|
||||
UP_INT_STATUS_12,
|
||||
UP_INT_STATUS_13,
|
||||
UP_INT_STATUS_14,
|
||||
UP_INT_STATUS_15,
|
||||
UP_INT_CLEAR_0, /* UPPER interrupt clear */
|
||||
UP_INT_CLEAR_1,
|
||||
UP_INT_CLEAR_2,
|
||||
UP_INT_CLEAR_3,
|
||||
UP_INT_CLEAR_4,
|
||||
UP_INT_CLEAR_5,
|
||||
UP_INT_CLEAR_6,
|
||||
UP_INT_CLEAR_7,
|
||||
UP_INT_CLEAR_8,
|
||||
UP_INT_CLEAR_9,
|
||||
UP_INT_CLEAR_10,
|
||||
UP_INT_CLEAR_11,
|
||||
UP_INT_CLEAR_12,
|
||||
UP_INT_CLEAR_13,
|
||||
UP_INT_CLEAR_14,
|
||||
UP_INT_CLEAR_15,
|
||||
UP_INT_MASK_0, /* UPPER interrupt mask */
|
||||
UP_INT_MASK_1,
|
||||
UP_INT_MASK_2,
|
||||
UP_INT_MASK_3,
|
||||
UP_INT_MASK_4,
|
||||
UP_INT_MASK_5,
|
||||
UP_INT_MASK_6,
|
||||
UP_INT_MASK_7,
|
||||
UP_INT_MASK_8,
|
||||
UP_INT_MASK_9,
|
||||
UP_INT_MASK_10,
|
||||
UP_INT_MASK_11,
|
||||
UP_INT_MASK_12,
|
||||
UP_INT_MASK_13,
|
||||
UP_INT_MASK_14,
|
||||
UP_INT_MASK_15,
|
||||
UP_THRESH_0, /* UPPER threshold values */
|
||||
UP_THRESH_1,
|
||||
UP_THRESH_2,
|
||||
UP_THRESH_3,
|
||||
UP_THRESH_4,
|
||||
UP_THRESH_5,
|
||||
UP_THRESH_6,
|
||||
UP_THRESH_7,
|
||||
UP_THRESH_8,
|
||||
UP_THRESH_9,
|
||||
UP_THRESH_10,
|
||||
UP_THRESH_11,
|
||||
UP_THRESH_12,
|
||||
UP_THRESH_13,
|
||||
UP_THRESH_14,
|
||||
UP_THRESH_15,
|
||||
CRITICAL_STATUS_0, /* CRITICAL threshold violated */
|
||||
CRITICAL_STATUS_1,
|
||||
CRITICAL_STATUS_2,
|
||||
CRITICAL_STATUS_3,
|
||||
CRITICAL_STATUS_4,
|
||||
CRITICAL_STATUS_5,
|
||||
CRITICAL_STATUS_6,
|
||||
CRITICAL_STATUS_7,
|
||||
CRITICAL_STATUS_8,
|
||||
CRITICAL_STATUS_9,
|
||||
CRITICAL_STATUS_10,
|
||||
CRITICAL_STATUS_11,
|
||||
CRITICAL_STATUS_12,
|
||||
CRITICAL_STATUS_13,
|
||||
CRITICAL_STATUS_14,
|
||||
CRITICAL_STATUS_15,
|
||||
MIN_STATUS_0, /* MIN threshold violated */
|
||||
MIN_STATUS_1,
|
||||
MIN_STATUS_2,
|
||||
@ -181,61 +406,6 @@ enum regfield_ids {
|
||||
MAX_STATUS_13,
|
||||
MAX_STATUS_14,
|
||||
MAX_STATUS_15,
|
||||
LOWER_STATUS_0, /* LOWER threshold violated */
|
||||
LOWER_STATUS_1,
|
||||
LOWER_STATUS_2,
|
||||
LOWER_STATUS_3,
|
||||
LOWER_STATUS_4,
|
||||
LOWER_STATUS_5,
|
||||
LOWER_STATUS_6,
|
||||
LOWER_STATUS_7,
|
||||
LOWER_STATUS_8,
|
||||
LOWER_STATUS_9,
|
||||
LOWER_STATUS_10,
|
||||
LOWER_STATUS_11,
|
||||
LOWER_STATUS_12,
|
||||
LOWER_STATUS_13,
|
||||
LOWER_STATUS_14,
|
||||
LOWER_STATUS_15,
|
||||
UPPER_STATUS_0, /* UPPER threshold violated */
|
||||
UPPER_STATUS_1,
|
||||
UPPER_STATUS_2,
|
||||
UPPER_STATUS_3,
|
||||
UPPER_STATUS_4,
|
||||
UPPER_STATUS_5,
|
||||
UPPER_STATUS_6,
|
||||
UPPER_STATUS_7,
|
||||
UPPER_STATUS_8,
|
||||
UPPER_STATUS_9,
|
||||
UPPER_STATUS_10,
|
||||
UPPER_STATUS_11,
|
||||
UPPER_STATUS_12,
|
||||
UPPER_STATUS_13,
|
||||
UPPER_STATUS_14,
|
||||
UPPER_STATUS_15,
|
||||
CRITICAL_STATUS_0, /* CRITICAL threshold violated */
|
||||
CRITICAL_STATUS_1,
|
||||
CRITICAL_STATUS_2,
|
||||
CRITICAL_STATUS_3,
|
||||
CRITICAL_STATUS_4,
|
||||
CRITICAL_STATUS_5,
|
||||
CRITICAL_STATUS_6,
|
||||
CRITICAL_STATUS_7,
|
||||
CRITICAL_STATUS_8,
|
||||
CRITICAL_STATUS_9,
|
||||
CRITICAL_STATUS_10,
|
||||
CRITICAL_STATUS_11,
|
||||
CRITICAL_STATUS_12,
|
||||
CRITICAL_STATUS_13,
|
||||
CRITICAL_STATUS_14,
|
||||
CRITICAL_STATUS_15,
|
||||
/* TRDY */
|
||||
TRDY,
|
||||
/* INTERRUPT ENABLE */
|
||||
INT_EN, /* Pre-V1, V1.x */
|
||||
LOW_INT_EN, /* V2.x */
|
||||
UP_INT_EN, /* V2.x */
|
||||
CRIT_INT_EN, /* V2.x */
|
||||
|
||||
/* Keep last */
|
||||
MAX_REGFIELDS
|
||||
@ -295,6 +465,8 @@ struct tsens_context {
|
||||
* @feat: features of the IP
|
||||
* @fields: bitfield locations
|
||||
* @ops: pointer to list of callbacks supported by this device
|
||||
* @debug_root: pointer to debugfs dentry for all tsens
|
||||
* @debug: pointer to debugfs dentry for tsens controller
|
||||
* @sensor: list of sensors attached to this device
|
||||
*/
|
||||
struct tsens_priv {
|
||||
@ -303,19 +475,31 @@ struct tsens_priv {
|
||||
struct regmap *tm_map;
|
||||
struct regmap *srot_map;
|
||||
u32 tm_offset;
|
||||
|
||||
/* lock for upper/lower threshold interrupts */
|
||||
spinlock_t ul_lock;
|
||||
|
||||
struct regmap_field *rf[MAX_REGFIELDS];
|
||||
struct tsens_context ctx;
|
||||
const struct tsens_features *feat;
|
||||
const struct reg_field *fields;
|
||||
const struct tsens_ops *ops;
|
||||
|
||||
struct dentry *debug_root;
|
||||
struct dentry *debug;
|
||||
|
||||
struct tsens_sensor sensor[0];
|
||||
};
|
||||
|
||||
char *qfprom_read(struct device *dev, const char *cname);
|
||||
void compute_intercept_slope(struct tsens_priv *priv, u32 *pt1, u32 *pt2, u32 mode);
|
||||
int init_common(struct tsens_priv *priv);
|
||||
int get_temp_tsens_valid(struct tsens_priv *priv, int i, int *temp);
|
||||
int get_temp_common(struct tsens_priv *priv, int i, int *temp);
|
||||
int get_temp_tsens_valid(struct tsens_sensor *s, int *temp);
|
||||
int get_temp_common(struct tsens_sensor *s, int *temp);
|
||||
int tsens_enable_irq(struct tsens_priv *priv);
|
||||
void tsens_disable_irq(struct tsens_priv *priv);
|
||||
int tsens_set_trips(void *_sensor, int low, int high);
|
||||
irqreturn_t tsens_irq_thread(int irq, void *data);
|
||||
|
||||
/* TSENS target */
|
||||
extern const struct tsens_plat_data data_8960;
|
||||
@ -324,7 +508,7 @@ extern const struct tsens_plat_data data_8960;
|
||||
extern const struct tsens_plat_data data_8916, data_8974;
|
||||
|
||||
/* TSENS v1 targets */
|
||||
extern const struct tsens_plat_data data_tsens_v1;
|
||||
extern const struct tsens_plat_data data_tsens_v1, data_8976;
|
||||
|
||||
/* TSENS v2 targets */
|
||||
extern const struct tsens_plat_data data_8996, data_tsens_v2;
|
||||
|
@ -13,7 +13,16 @@
|
||||
|
||||
#include "thermal_core.h"
|
||||
|
||||
#define SITES_MAX 16
|
||||
#define SITES_MAX 16
|
||||
#define TMR_DISABLE 0x0
|
||||
#define TMR_ME 0x80000000
|
||||
#define TMR_ALPF 0x0c000000
|
||||
#define TMR_ALPF_V2 0x03000000
|
||||
#define TMTMIR_DEFAULT 0x0000000f
|
||||
#define TIER_DISABLE 0x0
|
||||
#define TEUMR0_V2 0x51009c00
|
||||
#define TMU_VER1 0x1
|
||||
#define TMU_VER2 0x2
|
||||
|
||||
/*
|
||||
* QorIQ TMU Registers
|
||||
@ -24,17 +33,12 @@ struct qoriq_tmu_site_regs {
|
||||
u8 res0[0x8];
|
||||
};
|
||||
|
||||
struct qoriq_tmu_regs {
|
||||
struct qoriq_tmu_regs_v1 {
|
||||
u32 tmr; /* Mode Register */
|
||||
#define TMR_DISABLE 0x0
|
||||
#define TMR_ME 0x80000000
|
||||
#define TMR_ALPF 0x0c000000
|
||||
u32 tsr; /* Status Register */
|
||||
u32 tmtmir; /* Temperature measurement interval Register */
|
||||
#define TMTMIR_DEFAULT 0x0000000f
|
||||
u8 res0[0x14];
|
||||
u32 tier; /* Interrupt Enable Register */
|
||||
#define TIER_DISABLE 0x0
|
||||
u32 tidr; /* Interrupt Detect Register */
|
||||
u32 tiscr; /* Interrupt Site Capture Register */
|
||||
u32 ticscr; /* Interrupt Critical Site Capture Register */
|
||||
@ -54,10 +58,50 @@ struct qoriq_tmu_regs {
|
||||
u32 ipbrr0; /* IP Block Revision Register 0 */
|
||||
u32 ipbrr1; /* IP Block Revision Register 1 */
|
||||
u8 res6[0x310];
|
||||
u32 ttr0cr; /* Temperature Range 0 Control Register */
|
||||
u32 ttr1cr; /* Temperature Range 1 Control Register */
|
||||
u32 ttr2cr; /* Temperature Range 2 Control Register */
|
||||
u32 ttr3cr; /* Temperature Range 3 Control Register */
|
||||
u32 ttrcr[4]; /* Temperature Range Control Register */
|
||||
};
|
||||
|
||||
struct qoriq_tmu_regs_v2 {
|
||||
u32 tmr; /* Mode Register */
|
||||
u32 tsr; /* Status Register */
|
||||
u32 tmsr; /* monitor site register */
|
||||
u32 tmtmir; /* Temperature measurement interval Register */
|
||||
u8 res0[0x10];
|
||||
u32 tier; /* Interrupt Enable Register */
|
||||
u32 tidr; /* Interrupt Detect Register */
|
||||
u8 res1[0x8];
|
||||
u32 tiiscr; /* interrupt immediate site capture register */
|
||||
u32 tiascr; /* interrupt average site capture register */
|
||||
u32 ticscr; /* Interrupt Critical Site Capture Register */
|
||||
u32 res2;
|
||||
u32 tmhtcr; /* monitor high temperature capture register */
|
||||
u32 tmltcr; /* monitor low temperature capture register */
|
||||
u32 tmrtrcr; /* monitor rising temperature rate capture register */
|
||||
u32 tmftrcr; /* monitor falling temperature rate capture register */
|
||||
u32 tmhtitr; /* High Temperature Immediate Threshold */
|
||||
u32 tmhtatr; /* High Temperature Average Threshold */
|
||||
u32 tmhtactr; /* High Temperature Average Crit Threshold */
|
||||
u32 res3;
|
||||
u32 tmltitr; /* monitor low temperature immediate threshold */
|
||||
u32 tmltatr; /* monitor low temperature average threshold register */
|
||||
u32 tmltactr; /* monitor low temperature average critical threshold */
|
||||
u32 res4;
|
||||
u32 tmrtrctr; /* monitor rising temperature rate critical threshold */
|
||||
u32 tmftrctr; /* monitor falling temperature rate critical threshold*/
|
||||
u8 res5[0x8];
|
||||
u32 ttcfgr; /* Temperature Configuration Register */
|
||||
u32 tscfgr; /* Sensor Configuration Register */
|
||||
u8 res6[0x78];
|
||||
struct qoriq_tmu_site_regs site[SITES_MAX];
|
||||
u8 res7[0x9f8];
|
||||
u32 ipbrr0; /* IP Block Revision Register 0 */
|
||||
u32 ipbrr1; /* IP Block Revision Register 1 */
|
||||
u8 res8[0x300];
|
||||
u32 teumr0;
|
||||
u32 teumr1;
|
||||
u32 teumr2;
|
||||
u32 res9;
|
||||
u32 ttrcr[4]; /* Temperature Range Control Register */
|
||||
};
|
||||
|
||||
struct qoriq_tmu_data;
|
||||
@ -72,7 +116,9 @@ struct qoriq_sensor {
|
||||
};
|
||||
|
||||
struct qoriq_tmu_data {
|
||||
struct qoriq_tmu_regs __iomem *regs;
|
||||
int ver;
|
||||
struct qoriq_tmu_regs_v1 __iomem *regs;
|
||||
struct qoriq_tmu_regs_v2 __iomem *regs_v2;
|
||||
struct clk *clk;
|
||||
bool little_endian;
|
||||
struct qoriq_sensor *sensor[SITES_MAX];
|
||||
@ -132,12 +178,23 @@ static int qoriq_tmu_register_tmu_zone(struct platform_device *pdev)
|
||||
return PTR_ERR(qdata->sensor[id]->tzd);
|
||||
}
|
||||
|
||||
sites |= 0x1 << (15 - id);
|
||||
if (qdata->ver == TMU_VER1)
|
||||
sites |= 0x1 << (15 - id);
|
||||
else
|
||||
sites |= 0x1 << id;
|
||||
}
|
||||
|
||||
/* Enable monitoring */
|
||||
if (sites != 0)
|
||||
tmu_write(qdata, sites | TMR_ME | TMR_ALPF, &qdata->regs->tmr);
|
||||
if (sites != 0) {
|
||||
if (qdata->ver == TMU_VER1) {
|
||||
tmu_write(qdata, sites | TMR_ME | TMR_ALPF,
|
||||
&qdata->regs->tmr);
|
||||
} else {
|
||||
tmu_write(qdata, sites, &qdata->regs_v2->tmsr);
|
||||
tmu_write(qdata, TMR_ME | TMR_ALPF_V2,
|
||||
&qdata->regs_v2->tmr);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -150,16 +207,21 @@ static int qoriq_tmu_calibration(struct platform_device *pdev)
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct qoriq_tmu_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
if (of_property_read_u32_array(np, "fsl,tmu-range", range, 4)) {
|
||||
dev_err(&pdev->dev, "missing calibration range.\n");
|
||||
return -ENODEV;
|
||||
len = of_property_count_u32_elems(np, "fsl,tmu-range");
|
||||
if (len < 0 || len > 4) {
|
||||
dev_err(&pdev->dev, "invalid range data.\n");
|
||||
return len;
|
||||
}
|
||||
|
||||
val = of_property_read_u32_array(np, "fsl,tmu-range", range, len);
|
||||
if (val != 0) {
|
||||
dev_err(&pdev->dev, "failed to read range data.\n");
|
||||
return val;
|
||||
}
|
||||
|
||||
/* Init temperature range registers */
|
||||
tmu_write(data, range[0], &data->regs->ttr0cr);
|
||||
tmu_write(data, range[1], &data->regs->ttr1cr);
|
||||
tmu_write(data, range[2], &data->regs->ttr2cr);
|
||||
tmu_write(data, range[3], &data->regs->ttr3cr);
|
||||
for (i = 0; i < len; i++)
|
||||
tmu_write(data, range[i], &data->regs->ttrcr[i]);
|
||||
|
||||
calibration = of_get_property(np, "fsl,tmu-calibration", &len);
|
||||
if (calibration == NULL || len % 8) {
|
||||
@ -183,7 +245,12 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
|
||||
tmu_write(data, TIER_DISABLE, &data->regs->tier);
|
||||
|
||||
/* Set update_interval */
|
||||
tmu_write(data, TMTMIR_DEFAULT, &data->regs->tmtmir);
|
||||
if (data->ver == TMU_VER1) {
|
||||
tmu_write(data, TMTMIR_DEFAULT, &data->regs->tmtmir);
|
||||
} else {
|
||||
tmu_write(data, TMTMIR_DEFAULT, &data->regs_v2->tmtmir);
|
||||
tmu_write(data, TEUMR0_V2, &data->regs_v2->teumr0);
|
||||
}
|
||||
|
||||
/* Disable monitoring */
|
||||
tmu_write(data, TMR_DISABLE, &data->regs->tmr);
|
||||
@ -192,6 +259,7 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
|
||||
static int qoriq_tmu_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
u32 ver;
|
||||
struct qoriq_tmu_data *data;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
|
||||
@ -220,6 +288,12 @@ static int qoriq_tmu_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* version register offset at: 0xbf8 on both v1 and v2 */
|
||||
ver = tmu_read(data, &data->regs->ipbrr0);
|
||||
data->ver = (ver >> 8) & 0xff;
|
||||
if (data->ver == TMU_VER2)
|
||||
data->regs_v2 = (void __iomem *)data->regs;
|
||||
|
||||
qoriq_tmu_init_device(data); /* TMU initialization */
|
||||
|
||||
ret = qoriq_tmu_calibration(pdev); /* TMU calibration */
|
||||
|
@ -314,6 +314,10 @@ static const struct of_device_id rcar_gen3_thermal_dt_ids[] = {
|
||||
.compatible = "renesas,r8a774a1-thermal",
|
||||
.data = &rcar_gen3_ths_tj_1_m3_w,
|
||||
},
|
||||
{
|
||||
.compatible = "renesas,r8a774b1-thermal",
|
||||
.data = &rcar_gen3_ths_tj_1,
|
||||
},
|
||||
{
|
||||
.compatible = "renesas,r8a7795-thermal",
|
||||
.data = &rcar_gen3_ths_tj_1,
|
||||
|
@ -134,7 +134,8 @@ static int gadc_thermal_probe(struct platform_device *pdev)
|
||||
gti->channel = devm_iio_channel_get(&pdev->dev, "sensor-channel");
|
||||
if (IS_ERR(gti->channel)) {
|
||||
ret = PTR_ERR(gti->channel);
|
||||
dev_err(&pdev->dev, "IIO channel not found: %d\n", ret);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "IIO channel not found: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -142,8 +143,10 @@ static int gadc_thermal_probe(struct platform_device *pdev)
|
||||
&gadc_thermal_ops);
|
||||
if (IS_ERR(gti->tz_dev)) {
|
||||
ret = PTR_ERR(gti->tz_dev);
|
||||
dev_err(&pdev->dev, "Thermal zone sensor register failed: %d\n",
|
||||
ret);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"Thermal zone sensor register failed: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,6 @@
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/of.h>
|
||||
#include <net/netlink.h>
|
||||
#include <net/genetlink.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
@ -304,7 +302,7 @@ static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
|
||||
&tz->poll_queue,
|
||||
msecs_to_jiffies(delay));
|
||||
else
|
||||
cancel_delayed_work_sync(&tz->poll_queue);
|
||||
cancel_delayed_work(&tz->poll_queue);
|
||||
}
|
||||
|
||||
static void monitor_thermal_zone(struct thermal_zone_device *tz)
|
||||
@ -1414,7 +1412,7 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
|
||||
|
||||
mutex_unlock(&thermal_list_lock);
|
||||
|
||||
thermal_zone_device_set_polling(tz, 0);
|
||||
cancel_delayed_work_sync(&tz->poll_queue);
|
||||
|
||||
thermal_set_governor(tz, NULL);
|
||||
|
||||
@ -1464,97 +1462,6 @@ struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
|
||||
|
||||
#ifdef CONFIG_NET
|
||||
static const struct genl_multicast_group thermal_event_mcgrps[] = {
|
||||
{ .name = THERMAL_GENL_MCAST_GROUP_NAME, },
|
||||
};
|
||||
|
||||
static struct genl_family thermal_event_genl_family __ro_after_init = {
|
||||
.module = THIS_MODULE,
|
||||
.name = THERMAL_GENL_FAMILY_NAME,
|
||||
.version = THERMAL_GENL_VERSION,
|
||||
.maxattr = THERMAL_GENL_ATTR_MAX,
|
||||
.mcgrps = thermal_event_mcgrps,
|
||||
.n_mcgrps = ARRAY_SIZE(thermal_event_mcgrps),
|
||||
};
|
||||
|
||||
int thermal_generate_netlink_event(struct thermal_zone_device *tz,
|
||||
enum events event)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct nlattr *attr;
|
||||
struct thermal_genl_event *thermal_event;
|
||||
void *msg_header;
|
||||
int size;
|
||||
int result;
|
||||
static unsigned int thermal_event_seqnum;
|
||||
|
||||
if (!tz)
|
||||
return -EINVAL;
|
||||
|
||||
/* allocate memory */
|
||||
size = nla_total_size(sizeof(struct thermal_genl_event)) +
|
||||
nla_total_size(0);
|
||||
|
||||
skb = genlmsg_new(size, GFP_ATOMIC);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
|
||||
/* add the genetlink message header */
|
||||
msg_header = genlmsg_put(skb, 0, thermal_event_seqnum++,
|
||||
&thermal_event_genl_family, 0,
|
||||
THERMAL_GENL_CMD_EVENT);
|
||||
if (!msg_header) {
|
||||
nlmsg_free(skb);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* fill the data */
|
||||
attr = nla_reserve(skb, THERMAL_GENL_ATTR_EVENT,
|
||||
sizeof(struct thermal_genl_event));
|
||||
|
||||
if (!attr) {
|
||||
nlmsg_free(skb);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
thermal_event = nla_data(attr);
|
||||
if (!thermal_event) {
|
||||
nlmsg_free(skb);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(thermal_event, 0, sizeof(struct thermal_genl_event));
|
||||
|
||||
thermal_event->orig = tz->id;
|
||||
thermal_event->event = event;
|
||||
|
||||
/* send multicast genetlink message */
|
||||
genlmsg_end(skb, msg_header);
|
||||
|
||||
result = genlmsg_multicast(&thermal_event_genl_family, skb, 0,
|
||||
0, GFP_ATOMIC);
|
||||
if (result)
|
||||
dev_err(&tz->device, "Failed to send netlink event:%d", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(thermal_generate_netlink_event);
|
||||
|
||||
static int __init genetlink_init(void)
|
||||
{
|
||||
return genl_register_family(&thermal_event_genl_family);
|
||||
}
|
||||
|
||||
static void genetlink_exit(void)
|
||||
{
|
||||
genl_unregister_family(&thermal_event_genl_family);
|
||||
}
|
||||
#else /* !CONFIG_NET */
|
||||
static inline int genetlink_init(void) { return 0; }
|
||||
static inline void genetlink_exit(void) {}
|
||||
#endif /* !CONFIG_NET */
|
||||
|
||||
static int thermal_pm_notify(struct notifier_block *nb,
|
||||
unsigned long mode, void *_unused)
|
||||
{
|
||||
@ -1607,13 +1514,9 @@ static int __init thermal_init(void)
|
||||
if (result)
|
||||
goto unregister_governors;
|
||||
|
||||
result = genetlink_init();
|
||||
if (result)
|
||||
goto unregister_class;
|
||||
|
||||
result = of_parse_thermal_zones();
|
||||
if (result)
|
||||
goto exit_netlink;
|
||||
goto unregister_class;
|
||||
|
||||
result = register_pm_notifier(&thermal_pm_nb);
|
||||
if (result)
|
||||
@ -1622,8 +1525,6 @@ static int __init thermal_init(void)
|
||||
|
||||
return 0;
|
||||
|
||||
exit_netlink:
|
||||
genetlink_exit();
|
||||
unregister_class:
|
||||
class_unregister(&thermal_class);
|
||||
unregister_governors:
|
||||
@ -1636,4 +1537,4 @@ static int __init thermal_init(void)
|
||||
mutex_destroy(&poweroff_lock);
|
||||
return result;
|
||||
}
|
||||
fs_initcall(thermal_init);
|
||||
core_initcall(thermal_init);
|
||||
|
@ -110,7 +110,6 @@ static struct platform_driver thermal_mmio_driver = {
|
||||
.probe = thermal_mmio_probe,
|
||||
.driver = {
|
||||
.name = "thermal-mmio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(thermal_mmio_id_table),
|
||||
},
|
||||
};
|
||||
|
@ -33,6 +33,13 @@ cpufreq_cooling_register(struct cpufreq_policy *policy);
|
||||
*/
|
||||
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
|
||||
|
||||
/**
|
||||
* of_cpufreq_cooling_register - create cpufreq cooling device based on DT.
|
||||
* @policy: cpufreq policy.
|
||||
*/
|
||||
struct thermal_cooling_device *
|
||||
of_cpufreq_cooling_register(struct cpufreq_policy *policy);
|
||||
|
||||
#else /* !CONFIG_CPU_THERMAL */
|
||||
static inline struct thermal_cooling_device *
|
||||
cpufreq_cooling_register(struct cpufreq_policy *policy)
|
||||
@ -45,21 +52,12 @@ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif /* CONFIG_CPU_THERMAL */
|
||||
|
||||
#if defined(CONFIG_THERMAL_OF) && defined(CONFIG_CPU_THERMAL)
|
||||
/**
|
||||
* of_cpufreq_cooling_register - create cpufreq cooling device based on DT.
|
||||
* @policy: cpufreq policy.
|
||||
*/
|
||||
struct thermal_cooling_device *
|
||||
of_cpufreq_cooling_register(struct cpufreq_policy *policy);
|
||||
#else
|
||||
static inline struct thermal_cooling_device *
|
||||
of_cpufreq_cooling_register(struct cpufreq_policy *policy)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif /* defined(CONFIG_THERMAL_OF) && defined(CONFIG_CPU_THERMAL) */
|
||||
#endif /* CONFIG_CPU_THERMAL */
|
||||
|
||||
#endif /* __CPU_COOLING_H__ */
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include <linux/sched/topology.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#ifdef CONFIG_ENERGY_MODEL
|
||||
/**
|
||||
* em_cap_state - Capacity state of a performance domain
|
||||
* @frequency: The CPU frequency in KHz, for consistency with CPUFreq
|
||||
@ -40,6 +39,7 @@ struct em_perf_domain {
|
||||
unsigned long cpus[0];
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ENERGY_MODEL
|
||||
#define EM_CPU_MAX_POWER 0xFFFF
|
||||
|
||||
struct em_data_callback {
|
||||
@ -160,7 +160,6 @@ static inline int em_pd_nr_cap_states(struct em_perf_domain *pd)
|
||||
}
|
||||
|
||||
#else
|
||||
struct em_perf_domain {};
|
||||
struct em_data_callback {};
|
||||
#define EM_DATA_CB(_active_power_cb) { }
|
||||
|
||||
|
@ -544,15 +544,4 @@ static inline void thermal_notify_framework(struct thermal_zone_device *tz,
|
||||
{ }
|
||||
#endif /* CONFIG_THERMAL */
|
||||
|
||||
#if defined(CONFIG_NET) && IS_ENABLED(CONFIG_THERMAL)
|
||||
extern int thermal_generate_netlink_event(struct thermal_zone_device *tz,
|
||||
enum events event);
|
||||
#else
|
||||
static inline int thermal_generate_netlink_event(struct thermal_zone_device *tz,
|
||||
enum events event)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __THERMAL_H__ */
|
||||
|
@ -915,7 +915,7 @@ static int __init sugov_register(void)
|
||||
{
|
||||
return cpufreq_register_governor(&schedutil_gov);
|
||||
}
|
||||
fs_initcall(sugov_register);
|
||||
core_initcall(sugov_register);
|
||||
|
||||
#ifdef CONFIG_ENERGY_MODEL
|
||||
extern bool sched_energy_update;
|
||||
|
Loading…
Reference in New Issue
Block a user