Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
Pull thermal management updates from Zhang Rui: "We have a lot of SOC changes and a few thermal core fixes this time. The biggest change is about exynos thermal driver restructure. The patch set adds TMU (Thermal management Unit) driver support for exynos5440 platform. There are 3 instances of the TMU controllers so necessary cleanup/re-structure is done to handle multiple thermal zone. The next biggest change is the introduction of the imx thermal driver. It adds the imx thermal support using Temperature Monitor (TEMPMON) block found on some Freescale i.MX SoCs. The driver uses syscon regmap interface to access TEMPMON control registers and calibration data, and supports cpufreq as the cooling device. Highlights: - restructure exynos thermal driver. - introduce new imx thermal driver. - fix a bug in thermal core, which powers on the fans unexpectedly after resume from suspend" * 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: (46 commits) drivers: thermal: add check when unregistering cpu cooling thermal: thermal_core: allow binding with limits on bind_params drivers: thermal: make usage of CONFIG_THERMAL_HWMON optional drivers: thermal: parent virtual hwmon with thermal zone thermal: hwmon: move hwmon support to single file thermal: exynos: Clean up non-DT remnants thermal: exynos: Fix potential NULL pointer dereference thermal: exynos: Fix typos in Kconfig thermal: ti-soc-thermal: Ensure to compute thermal trend thermal: ti-soc-thermal: Set the bandgap mask counter delay value thermal: ti-soc-thermal: Initialize counter_delay field for TI DRA752 sensors thermal: step_wise: return instance->target by default thermal: step_wise: cdev only needs update on a new target state Thermal/cpu_cooling: Return directly for the cpu out of allowed_cpus in the cpufreq_thermal_notifier() thermal: exynos_tmu: fix wrong error check for mapped memory thermal: imx: implement thermal alarm interrupt handling thermal: imx: dynamic passive and SoC specific critical trip points Documentation: thermal: Explain the exynos thermal driver model ARM: dts: thermal: exynos: Add documentation for Exynos SoC thermal bindings thermal: exynos: Support for TMU regulator defined at device tree ...
This commit is contained in:
commit
b9b42eeb88
55
Documentation/devicetree/bindings/thermal/exynos-thermal.txt
Normal file
55
Documentation/devicetree/bindings/thermal/exynos-thermal.txt
Normal file
|
@ -0,0 +1,55 @@
|
|||
* Exynos Thermal Management Unit (TMU)
|
||||
|
||||
** Required properties:
|
||||
|
||||
- compatible : One of the following:
|
||||
"samsung,exynos4412-tmu"
|
||||
"samsung,exynos4210-tmu"
|
||||
"samsung,exynos5250-tmu"
|
||||
"samsung,exynos5440-tmu"
|
||||
- interrupt-parent : The phandle for the interrupt controller
|
||||
- reg : Address range of the thermal registers. For soc's which has multiple
|
||||
instances of TMU and some registers are shared across all TMU's like
|
||||
interrupt related then 2 set of register has to supplied. First set
|
||||
belongs to each instance of TMU and second set belongs to common TMU
|
||||
registers.
|
||||
- interrupts : Should contain interrupt for thermal system
|
||||
- clocks : The main clock for TMU device
|
||||
- clock-names : Thermal system clock name
|
||||
- vtmu-supply: This entry is optional and provides the regulator node supplying
|
||||
voltage to TMU. If needed this entry can be placed inside
|
||||
board/platform specific dts file.
|
||||
|
||||
Example 1):
|
||||
|
||||
tmu@100C0000 {
|
||||
compatible = "samsung,exynos4412-tmu";
|
||||
interrupt-parent = <&combiner>;
|
||||
reg = <0x100C0000 0x100>;
|
||||
interrupts = <2 4>;
|
||||
clocks = <&clock 383>;
|
||||
clock-names = "tmu_apbif";
|
||||
status = "disabled";
|
||||
vtmu-supply = <&tmu_regulator_node>;
|
||||
};
|
||||
|
||||
Example 2):
|
||||
|
||||
tmuctrl_0: tmuctrl@160118 {
|
||||
compatible = "samsung,exynos5440-tmu";
|
||||
reg = <0x160118 0x230>, <0x160368 0x10>;
|
||||
interrupts = <0 58 0>;
|
||||
clocks = <&clock 21>;
|
||||
clock-names = "tmu_apbif";
|
||||
};
|
||||
|
||||
Note: For multi-instance tmu each instance should have an alias correctly
|
||||
numbered in "aliases" node.
|
||||
|
||||
Example:
|
||||
|
||||
aliases {
|
||||
tmuctrl0 = &tmuctrl_0;
|
||||
tmuctrl1 = &tmuctrl_1;
|
||||
tmuctrl2 = &tmuctrl_2;
|
||||
};
|
17
Documentation/devicetree/bindings/thermal/imx-thermal.txt
Normal file
17
Documentation/devicetree/bindings/thermal/imx-thermal.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs
|
||||
|
||||
Required properties:
|
||||
- compatible : "fsl,imx6q-thermal"
|
||||
- fsl,tempmon : phandle pointer to system controller that contains TEMPMON
|
||||
control registers, e.g. ANATOP on imx6q.
|
||||
- fsl,tempmon-data : phandle pointer to fuse controller that contains TEMPMON
|
||||
calibration data, e.g. OCOTP on imx6q. The details about calibration data
|
||||
can be found in SoC Reference Manual.
|
||||
|
||||
Example:
|
||||
|
||||
tempmon {
|
||||
compatible = "fsl,imx6q-tempmon";
|
||||
fsl,tempmon = <&anatop>;
|
||||
fsl,tempmon-data = <&ocotp>;
|
||||
};
|
|
@ -1,17 +1,17 @@
|
|||
Kernel driver exynos4_tmu
|
||||
Kernel driver exynos_tmu
|
||||
=================
|
||||
|
||||
Supported chips:
|
||||
* ARM SAMSUNG EXYNOS4 series of SoC
|
||||
Prefix: 'exynos4-tmu'
|
||||
* ARM SAMSUNG EXYNOS4, EXYNOS5 series of SoC
|
||||
Datasheet: Not publicly available
|
||||
|
||||
Authors: Donggeun Kim <dg77.kim@samsung.com>
|
||||
Authors: Amit Daniel <amit.daniel@samsung.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
TMU controller Description:
|
||||
---------------------------
|
||||
|
||||
This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
|
||||
This driver allows to read temperature inside SAMSUNG EXYNOS4/5 series of SoC.
|
||||
|
||||
The chip only exposes the measured 8-bit temperature code value
|
||||
through a register.
|
||||
|
@ -34,9 +34,9 @@ The three equations are:
|
|||
TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
|
||||
Temperature code measured at 85 degree Celsius which is unchanged
|
||||
|
||||
TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
|
||||
TMU(Thermal Management Unit) in EXYNOS4/5 generates interrupt
|
||||
when temperature exceeds pre-defined levels.
|
||||
The maximum number of configurable threshold is four.
|
||||
The maximum number of configurable threshold is five.
|
||||
The threshold levels are defined as follows:
|
||||
Level_0: current temperature > trigger_level_0 + threshold
|
||||
Level_1: current temperature > trigger_level_1 + threshold
|
||||
|
@ -47,6 +47,31 @@ The threshold levels are defined as follows:
|
|||
through the corresponding registers.
|
||||
|
||||
When an interrupt occurs, this driver notify kernel thermal framework
|
||||
with the function exynos4_report_trigger.
|
||||
with the function exynos_report_trigger.
|
||||
Although an interrupt condition for level_0 can be set,
|
||||
it can be used to synchronize the cooling action.
|
||||
|
||||
TMU driver description:
|
||||
-----------------------
|
||||
|
||||
The exynos thermal driver is structured as,
|
||||
|
||||
Kernel Core thermal framework
|
||||
(thermal_core.c, step_wise.c, cpu_cooling.c)
|
||||
^
|
||||
|
|
||||
|
|
||||
TMU configuration data -------> TMU Driver <------> Exynos Core thermal wrapper
|
||||
(exynos_tmu_data.c) (exynos_tmu.c) (exynos_thermal_common.c)
|
||||
(exynos_tmu_data.h) (exynos_tmu.h) (exynos_thermal_common.h)
|
||||
|
||||
a) TMU configuration data: This consist of TMU register offsets/bitfields
|
||||
described through structure exynos_tmu_registers. Also several
|
||||
other platform data (struct exynos_tmu_platform_data) members
|
||||
are used to configure the TMU.
|
||||
b) TMU driver: This component initialises the TMU controller and sets different
|
||||
thresholds. It invokes core thermal implementation with the call
|
||||
exynos_report_trigger.
|
||||
c) Exynos Core thermal wrapper: This provides 3 wrapper function to use the
|
||||
Kernel core thermal framework. They are exynos_unregister_thermal,
|
||||
exynos_register_thermal and exynos_report_trigger.
|
||||
|
|
|
@ -134,6 +134,13 @@ temperature) and throttle appropriate devices.
|
|||
this thermal zone and cdev, for a particular trip point.
|
||||
If nth bit is set, then the cdev and thermal zone are bound
|
||||
for trip point n.
|
||||
.limits: This is an array of cooling state limits. Must have exactly
|
||||
2 * thermal_zone.number_of_trip_points. It is an array consisting
|
||||
of tuples <lower-state upper-state> of state limits. Each trip
|
||||
will be associated with one state limit tuple when binding.
|
||||
A NULL pointer means <THERMAL_NO_LIMITS THERMAL_NO_LIMITS>
|
||||
on all trips. These limits are used when binding a cdev to a
|
||||
trip point.
|
||||
.match: This call back returns success(0) if the 'tz and cdev' need to
|
||||
be bound, as per platform data.
|
||||
1.4.2 struct thermal_zone_params
|
||||
|
@ -142,6 +149,11 @@ temperature) and throttle appropriate devices.
|
|||
This is an optional feature where some platforms can choose not to
|
||||
provide this data.
|
||||
.governor_name: Name of the thermal governor used for this zone
|
||||
.no_hwmon: a boolean to indicate if the thermal to hwmon sysfs interface
|
||||
is required. when no_hwmon == false, a hwmon sysfs interface
|
||||
will be created. when no_hwmon == true, nothing will be done.
|
||||
In case the thermal_zone_params is NULL, the hwmon interface
|
||||
will be created (for backward compatibility).
|
||||
.num_tbps: Number of thermal_bind_params entries for this zone
|
||||
.tbp: thermal_bind_params entries
|
||||
|
||||
|
|
|
@ -17,8 +17,17 @@ if THERMAL
|
|||
|
||||
config THERMAL_HWMON
|
||||
bool
|
||||
prompt "Expose thermal sensors as hwmon device"
|
||||
depends on HWMON=y || HWMON=THERMAL
|
||||
default y
|
||||
help
|
||||
In case a sensor is registered with the thermal
|
||||
framework, this option will also register it
|
||||
as a hwmon. The sensor will then have the common
|
||||
hwmon sysfs interface.
|
||||
|
||||
Say 'Y' here if you want all thermal sensors to
|
||||
have hwmon sysfs interface too.
|
||||
|
||||
choice
|
||||
prompt "Default Thermal governor"
|
||||
|
@ -91,6 +100,17 @@ config THERMAL_EMULATION
|
|||
because userland can easily disable the thermal policy by simply
|
||||
flooding this sysfs node with low temperature values.
|
||||
|
||||
config IMX_THERMAL
|
||||
tristate "Temperature sensor driver for Freescale i.MX SoCs"
|
||||
depends on CPU_THERMAL
|
||||
depends on MFD_SYSCON
|
||||
depends on OF
|
||||
help
|
||||
Support for Temperature Monitor (TEMPMON) found on Freescale i.MX SoCs.
|
||||
It supports one critical trip point and one passive trip point. The
|
||||
cpufreq is used as the cooling device to throttle CPUs when the
|
||||
passive trip is crossed.
|
||||
|
||||
config SPEAR_THERMAL
|
||||
bool "SPEAr thermal sensor driver"
|
||||
depends on PLAT_SPEAR
|
||||
|
@ -114,14 +134,6 @@ config KIRKWOOD_THERMAL
|
|||
Support for the Kirkwood thermal sensor driver into the Linux thermal
|
||||
framework. Only kirkwood 88F6282 and 88F6283 have this sensor.
|
||||
|
||||
config EXYNOS_THERMAL
|
||||
tristate "Temperature sensor on Samsung EXYNOS"
|
||||
depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5)
|
||||
depends on CPU_THERMAL
|
||||
help
|
||||
If you say yes here you get support for TMU (Thermal Management
|
||||
Unit) on SAMSUNG EXYNOS series of SoC.
|
||||
|
||||
config DOVE_THERMAL
|
||||
tristate "Temperature sensor on Marvell Dove SoCs"
|
||||
depends on ARCH_DOVE
|
||||
|
@ -184,4 +196,9 @@ menu "Texas Instruments thermal drivers"
|
|||
source "drivers/thermal/ti-soc-thermal/Kconfig"
|
||||
endmenu
|
||||
|
||||
menu "Samsung thermal drivers"
|
||||
depends on PLAT_SAMSUNG
|
||||
source "drivers/thermal/samsung/Kconfig"
|
||||
endmenu
|
||||
|
||||
endif
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
obj-$(CONFIG_THERMAL) += thermal_sys.o
|
||||
thermal_sys-y += thermal_core.o
|
||||
|
||||
# interface to/from other layers providing sensors
|
||||
thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o
|
||||
|
||||
# governors
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o
|
||||
|
@ -17,10 +20,11 @@ thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
|
|||
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
|
||||
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
|
||||
obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o
|
||||
obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
|
||||
obj-y += samsung/
|
||||
obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o
|
||||
obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o
|
||||
obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o
|
||||
obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
|
||||
obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o
|
||||
obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
|
||||
obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o
|
||||
|
|
|
@ -322,6 +322,8 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb,
|
|||
|
||||
if (cpumask_test_cpu(policy->cpu, ¬ify_device->allowed_cpus))
|
||||
max_freq = notify_device->cpufreq_val;
|
||||
else
|
||||
return 0;
|
||||
|
||||
/* Never exceed user_policy.max */
|
||||
if (max_freq > policy->user_policy.max)
|
||||
|
@ -496,8 +498,12 @@ EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
|
|||
*/
|
||||
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata;
|
||||
struct cpufreq_cooling_device *cpufreq_dev;
|
||||
|
||||
if (!cdev)
|
||||
return;
|
||||
|
||||
cpufreq_dev = cdev->devdata;
|
||||
mutex_lock(&cooling_cpufreq_lock);
|
||||
cpufreq_dev_count--;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
541
drivers/thermal/imx_thermal.c
Normal file
541
drivers/thermal/imx_thermal.c
Normal file
|
@ -0,0 +1,541 @@
|
|||
/*
|
||||
* Copyright 2013 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define REG_SET 0x4
|
||||
#define REG_CLR 0x8
|
||||
#define REG_TOG 0xc
|
||||
|
||||
#define MISC0 0x0150
|
||||
#define MISC0_REFTOP_SELBIASOFF (1 << 3)
|
||||
|
||||
#define TEMPSENSE0 0x0180
|
||||
#define TEMPSENSE0_ALARM_VALUE_SHIFT 20
|
||||
#define TEMPSENSE0_ALARM_VALUE_MASK (0xfff << TEMPSENSE0_ALARM_VALUE_SHIFT)
|
||||
#define TEMPSENSE0_TEMP_CNT_SHIFT 8
|
||||
#define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT)
|
||||
#define TEMPSENSE0_FINISHED (1 << 2)
|
||||
#define TEMPSENSE0_MEASURE_TEMP (1 << 1)
|
||||
#define TEMPSENSE0_POWER_DOWN (1 << 0)
|
||||
|
||||
#define TEMPSENSE1 0x0190
|
||||
#define TEMPSENSE1_MEASURE_FREQ 0xffff
|
||||
|
||||
#define OCOTP_ANA1 0x04e0
|
||||
|
||||
/* The driver supports 1 passive trip point and 1 critical trip point */
|
||||
enum imx_thermal_trip {
|
||||
IMX_TRIP_PASSIVE,
|
||||
IMX_TRIP_CRITICAL,
|
||||
IMX_TRIP_NUM,
|
||||
};
|
||||
|
||||
/*
|
||||
* It defines the temperature in millicelsius for passive trip point
|
||||
* that will trigger cooling action when crossed.
|
||||
*/
|
||||
#define IMX_TEMP_PASSIVE 85000
|
||||
|
||||
#define IMX_POLLING_DELAY 2000 /* millisecond */
|
||||
#define IMX_PASSIVE_DELAY 1000
|
||||
|
||||
struct imx_thermal_data {
|
||||
struct thermal_zone_device *tz;
|
||||
struct thermal_cooling_device *cdev;
|
||||
enum thermal_device_mode mode;
|
||||
struct regmap *tempmon;
|
||||
int c1, c2; /* See formula in imx_get_sensor_data() */
|
||||
unsigned long temp_passive;
|
||||
unsigned long temp_critical;
|
||||
unsigned long alarm_temp;
|
||||
unsigned long last_temp;
|
||||
bool irq_enabled;
|
||||
int irq;
|
||||
};
|
||||
|
||||
static void imx_set_alarm_temp(struct imx_thermal_data *data,
|
||||
signed long alarm_temp)
|
||||
{
|
||||
struct regmap *map = data->tempmon;
|
||||
int alarm_value;
|
||||
|
||||
data->alarm_temp = alarm_temp;
|
||||
alarm_value = (alarm_temp - data->c2) / data->c1;
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_ALARM_VALUE_MASK);
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, alarm_value <<
|
||||
TEMPSENSE0_ALARM_VALUE_SHIFT);
|
||||
}
|
||||
|
||||
static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
|
||||
{
|
||||
struct imx_thermal_data *data = tz->devdata;
|
||||
struct regmap *map = data->tempmon;
|
||||
unsigned int n_meas;
|
||||
bool wait;
|
||||
u32 val;
|
||||
|
||||
if (data->mode == THERMAL_DEVICE_ENABLED) {
|
||||
/* Check if a measurement is currently in progress */
|
||||
regmap_read(map, TEMPSENSE0, &val);
|
||||
wait = !(val & TEMPSENSE0_FINISHED);
|
||||
} else {
|
||||
/*
|
||||
* Every time we measure the temperature, we will power on the
|
||||
* temperature sensor, enable measurements, take a reading,
|
||||
* disable measurements, power off the temperature sensor.
|
||||
*/
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
|
||||
|
||||
wait = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* According to the temp sensor designers, it may require up to ~17us
|
||||
* to complete a measurement.
|
||||
*/
|
||||
if (wait)
|
||||
usleep_range(20, 50);
|
||||
|
||||
regmap_read(map, TEMPSENSE0, &val);
|
||||
|
||||
if (data->mode != THERMAL_DEVICE_ENABLED) {
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
|
||||
}
|
||||
|
||||
if ((val & TEMPSENSE0_FINISHED) == 0) {
|
||||
dev_dbg(&tz->device, "temp measurement never finished\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT;
|
||||
|
||||
/* See imx_get_sensor_data() for formula derivation */
|
||||
*temp = data->c2 + data->c1 * n_meas;
|
||||
|
||||
/* Update alarm value to next higher trip point */
|
||||
if (data->alarm_temp == data->temp_passive && *temp >= data->temp_passive)
|
||||
imx_set_alarm_temp(data, data->temp_critical);
|
||||
if (data->alarm_temp == data->temp_critical && *temp < data->temp_passive) {
|
||||
imx_set_alarm_temp(data, data->temp_passive);
|
||||
dev_dbg(&tz->device, "thermal alarm off: T < %lu\n",
|
||||
data->alarm_temp / 1000);
|
||||
}
|
||||
|
||||
if (*temp != data->last_temp) {
|
||||
dev_dbg(&tz->device, "millicelsius: %ld\n", *temp);
|
||||
data->last_temp = *temp;
|
||||
}
|
||||
|
||||
/* Reenable alarm IRQ if temperature below alarm temperature */
|
||||
if (!data->irq_enabled && *temp < data->alarm_temp) {
|
||||
data->irq_enabled = true;
|
||||
enable_irq(data->irq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_get_mode(struct thermal_zone_device *tz,
|
||||
enum thermal_device_mode *mode)
|
||||
{
|
||||
struct imx_thermal_data *data = tz->devdata;
|
||||
|
||||
*mode = data->mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_set_mode(struct thermal_zone_device *tz,
|
||||
enum thermal_device_mode mode)
|
||||
{
|
||||
struct imx_thermal_data *data = tz->devdata;
|
||||
struct regmap *map = data->tempmon;
|
||||
|
||||
if (mode == THERMAL_DEVICE_ENABLED) {
|
||||
tz->polling_delay = IMX_POLLING_DELAY;
|
||||
tz->passive_delay = IMX_PASSIVE_DELAY;
|
||||
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
|
||||
|
||||
if (!data->irq_enabled) {
|
||||
data->irq_enabled = true;
|
||||
enable_irq(data->irq);
|
||||
}
|
||||
} else {
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
|
||||
|
||||
tz->polling_delay = 0;
|
||||
tz->passive_delay = 0;
|
||||
|
||||
if (data->irq_enabled) {
|
||||
disable_irq(data->irq);
|
||||
data->irq_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
data->mode = mode;
|
||||
thermal_zone_device_update(tz);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
|
||||
enum thermal_trip_type *type)
|
||||
{
|
||||
*type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE :
|
||||
THERMAL_TRIP_CRITICAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_get_crit_temp(struct thermal_zone_device *tz,
|
||||
unsigned long *temp)
|
||||
{
|
||||
struct imx_thermal_data *data = tz->devdata;
|
||||
|
||||
*temp = data->temp_critical;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
|
||||
unsigned long *temp)
|
||||
{
|
||||
struct imx_thermal_data *data = tz->devdata;
|
||||
|
||||
*temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive :
|
||||
data->temp_critical;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
|
||||
unsigned long temp)
|
||||
{
|
||||
struct imx_thermal_data *data = tz->devdata;
|
||||
|
||||
if (trip == IMX_TRIP_CRITICAL)
|
||||
return -EPERM;
|
||||
|
||||
if (temp > IMX_TEMP_PASSIVE)
|
||||
return -EINVAL;
|
||||
|
||||
data->temp_passive = temp;
|
||||
|
||||
imx_set_alarm_temp(data, temp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_bind(struct thermal_zone_device *tz,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
|
||||
THERMAL_NO_LIMIT,
|
||||
THERMAL_NO_LIMIT);
|
||||
if (ret) {
|
||||
dev_err(&tz->device,
|
||||
"binding zone %s with cdev %s failed:%d\n",
|
||||
tz->type, cdev->type, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_unbind(struct thermal_zone_device *tz,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
|
||||
if (ret) {
|
||||
dev_err(&tz->device,
|
||||
"unbinding zone %s with cdev %s failed:%d\n",
|
||||
tz->type, cdev->type, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_device_ops imx_tz_ops = {
|
||||
.bind = imx_bind,
|
||||
.unbind = imx_unbind,
|
||||
.get_temp = imx_get_temp,
|
||||
.get_mode = imx_get_mode,
|
||||
.set_mode = imx_set_mode,
|
||||
.get_trip_type = imx_get_trip_type,
|
||||
.get_trip_temp = imx_get_trip_temp,
|
||||
.get_crit_temp = imx_get_crit_temp,
|
||||
.set_trip_temp = imx_set_trip_temp,
|
||||
};
|
||||
|
||||
static int imx_get_sensor_data(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_thermal_data *data = platform_get_drvdata(pdev);
|
||||
struct regmap *map;
|
||||
int t1, t2, n1, n2;
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
|
||||
"fsl,tempmon-data");
|
||||
if (IS_ERR(map)) {
|
||||
ret = PTR_ERR(map);
|
||||
dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_read(map, OCOTP_ANA1, &val);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (val == 0 || val == ~0) {
|
||||
dev_err(&pdev->dev, "invalid sensor calibration data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sensor data layout:
|
||||
* [31:20] - sensor value @ 25C
|
||||
* [19:8] - sensor value of hot
|
||||
* [7:0] - hot temperature value
|
||||
*/
|
||||
n1 = val >> 20;
|
||||
n2 = (val & 0xfff00) >> 8;
|
||||
t2 = val & 0xff;
|
||||
t1 = 25; /* t1 always 25C */
|
||||
|
||||
/*
|
||||
* Derived from linear interpolation,
|
||||
* Tmeas = T2 + (Nmeas - N2) * (T1 - T2) / (N1 - N2)
|
||||
* We want to reduce this down to the minimum computation necessary
|
||||
* for each temperature read. Also, we want Tmeas in millicelsius
|
||||
* and we don't want to lose precision from integer division. So...
|
||||
* milli_Tmeas = 1000 * T2 + 1000 * (Nmeas - N2) * (T1 - T2) / (N1 - N2)
|
||||
* Let constant c1 = 1000 * (T1 - T2) / (N1 - N2)
|
||||
* milli_Tmeas = (1000 * T2) + c1 * (Nmeas - N2)
|
||||
* milli_Tmeas = (1000 * T2) + (c1 * Nmeas) - (c1 * N2)
|
||||
* Let constant c2 = (1000 * T2) - (c1 * N2)
|
||||
* milli_Tmeas = c2 + (c1 * Nmeas)
|
||||
*/
|
||||
data->c1 = 1000 * (t1 - t2) / (n1 - n2);
|
||||
data->c2 = 1000 * t2 - data->c1 * n2;
|
||||
|
||||
/*
|
||||
* Set the default passive cooling trip point to 20 °C below the
|
||||
* maximum die temperature. Can be changed from userspace.
|
||||
*/
|
||||
data->temp_passive = 1000 * (t2 - 20);
|
||||
|
||||
/*
|
||||
* The maximum die temperature is t2, let's give 5 °C cushion
|
||||
* for noise and possible temperature rise between measurements.
|
||||
*/
|
||||
data->temp_critical = 1000 * (t2 - 5);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
|
||||
{
|
||||
struct imx_thermal_data *data = dev;
|
||||
|
||||
disable_irq_nosync(irq);
|
||||
data->irq_enabled = false;
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
|
||||
{
|
||||
struct imx_thermal_data *data = dev;
|
||||
|
||||
dev_dbg(&data->tz->device, "THERMAL ALARM: T > %lu\n",
|
||||
data->alarm_temp / 1000);
|
||||
|
||||
thermal_zone_device_update(data->tz);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int imx_thermal_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_thermal_data *data;
|
||||
struct cpumask clip_cpus;
|
||||
struct regmap *map;
|
||||
int measure_freq;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
|
||||
if (IS_ERR(map)) {
|
||||
ret = PTR_ERR(map);
|
||||
dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
data->tempmon = map;
|
||||
|
||||
data->irq = platform_get_irq(pdev, 0);
|
||||
if (data->irq < 0)
|
||||
return data->irq;
|
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, data->irq,
|
||||
imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
|
||||
0, "imx_thermal", data);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
ret = imx_get_sensor_data(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get sensor data\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Make sure sensor is in known good state for measurements */
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
|
||||
regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ);
|
||||
regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF);
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
|
||||
|
||||
cpumask_set_cpu(0, &clip_cpus);
|
||||
data->cdev = cpufreq_cooling_register(&clip_cpus);
|
||||
if (IS_ERR(data->cdev)) {
|
||||
ret = PTR_ERR(data->cdev);
|
||||
dev_err(&pdev->dev,
|
||||
"failed to register cpufreq cooling device: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->tz = thermal_zone_device_register("imx_thermal_zone",
|
||||
IMX_TRIP_NUM,
|
||||
BIT(IMX_TRIP_PASSIVE), data,
|
||||
&imx_tz_ops, NULL,
|
||||
IMX_PASSIVE_DELAY,
|
||||
IMX_POLLING_DELAY);
|
||||
if (IS_ERR(data->tz)) {
|
||||
ret = PTR_ERR(data->tz);
|
||||
dev_err(&pdev->dev,
|
||||
"failed to register thermal zone device %d\n", ret);
|
||||
cpufreq_cooling_unregister(data->cdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Enable measurements at ~ 10 Hz */
|
||||
regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ);
|
||||
measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
|
||||
regmap_write(map, TEMPSENSE1 + REG_SET, measure_freq);
|
||||
imx_set_alarm_temp(data, data->temp_passive);
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
|
||||
|
||||
data->irq_enabled = true;
|
||||
data->mode = THERMAL_DEVICE_ENABLED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_thermal_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_thermal_data *data = platform_get_drvdata(pdev);
|
||||
struct regmap *map = data->tempmon;
|
||||
|
||||
/* Disable measurements */
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
|
||||
|
||||
thermal_zone_device_unregister(data->tz);
|
||||
cpufreq_cooling_unregister(data->cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int imx_thermal_suspend(struct device *dev)
|
||||
{
|
||||
struct imx_thermal_data *data = dev_get_drvdata(dev);
|
||||
struct regmap *map = data->tempmon;
|
||||
u32 val;
|
||||
|
||||
regmap_read(map, TEMPSENSE0, &val);
|
||||
if ((val & TEMPSENSE0_POWER_DOWN) == 0) {
|
||||
/*
|
||||
* If a measurement is taking place, wait for a long enough
|
||||
* time for it to finish, and then check again. If it still
|
||||
* does not finish, something must go wrong.
|
||||
*/
|
||||
udelay(50);
|
||||
regmap_read(map, TEMPSENSE0, &val);
|
||||
if ((val & TEMPSENSE0_POWER_DOWN) == 0)
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_thermal_resume(struct device *dev)
|
||||
{
|
||||
/* Nothing to do for now */
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
|
||||
imx_thermal_suspend, imx_thermal_resume);
|
||||
|
||||
static const struct of_device_id of_imx_thermal_match[] = {
|
||||
{ .compatible = "fsl,imx6q-tempmon", },
|
||||
{ /* end */ }
|
||||
};
|
||||
|
||||
static struct platform_driver imx_thermal = {
|
||||
.driver = {
|
||||
.name = "imx_thermal",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &imx_thermal_pm_ops,
|
||||
.of_match_table = of_imx_thermal_match,
|
||||
},
|
||||
.probe = imx_thermal_probe,
|
||||
.remove = imx_thermal_remove,
|
||||
};
|
||||
module_platform_driver(imx_thermal);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-thermal");
|
18
drivers/thermal/samsung/Kconfig
Normal file
18
drivers/thermal/samsung/Kconfig
Normal file
|
@ -0,0 +1,18 @@
|
|||
config EXYNOS_THERMAL
|
||||
tristate "Exynos thermal management unit driver"
|
||||
depends on ARCH_HAS_BANDGAP && OF
|
||||
help
|
||||
If you say yes here you get support for the TMU (Thermal Management
|
||||
Unit) driver for SAMSUNG EXYNOS series of SoCs. This driver initialises
|
||||
the TMU, reports temperature and handles cooling action if defined.
|
||||
This driver uses the Exynos core thermal APIs and TMU configuration
|
||||
data from the supported SoCs.
|
||||
|
||||
config EXYNOS_THERMAL_CORE
|
||||
bool "Core thermal framework support for EXYNOS SOCs"
|
||||
depends on EXYNOS_THERMAL
|
||||
help
|
||||
If you say yes here you get support for EXYNOS TMU
|
||||
(Thermal Management Unit) common registration/unregistration
|
||||
functions to the core thermal layer and also to use the generic
|
||||
CPU cooling APIs.
|
7
drivers/thermal/samsung/Makefile
Normal file
7
drivers/thermal/samsung/Makefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Samsung thermal specific Makefile
|
||||
#
|
||||
obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
|
||||
exynos_thermal-y := exynos_tmu.o
|
||||
exynos_thermal-y += exynos_tmu_data.o
|
||||
exynos_thermal-$(CONFIG_EXYNOS_THERMAL_CORE) += exynos_thermal_common.o
|
432
drivers/thermal/samsung/exynos_thermal_common.c
Normal file
432
drivers/thermal/samsung/exynos_thermal_common.c
Normal file
|
@ -0,0 +1,432 @@
|
|||
/*
|
||||
* exynos_thermal_common.c - Samsung EXYNOS common thermal file
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#include "exynos_thermal_common.h"
|
||||
|
||||
struct exynos_thermal_zone {
|
||||
enum thermal_device_mode mode;
|
||||
struct thermal_zone_device *therm_dev;
|
||||
struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
|
||||
unsigned int cool_dev_size;
|
||||
struct platform_device *exynos4_dev;
|
||||
struct thermal_sensor_conf *sensor_conf;
|
||||
bool bind;
|
||||
};
|
||||
|
||||
/* Get mode callback functions for thermal zone */
|
||||
static int exynos_get_mode(struct thermal_zone_device *thermal,
|
||||
enum thermal_device_mode *mode)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
if (th_zone)
|
||||
*mode = th_zone->mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set mode callback functions for thermal zone */
|
||||
static int exynos_set_mode(struct thermal_zone_device *thermal,
|
||||
enum thermal_device_mode mode)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
if (!th_zone) {
|
||||
dev_err(&thermal->device,
|
||||
"thermal zone not registered\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mutex_lock(&thermal->lock);
|
||||
|
||||
if (mode == THERMAL_DEVICE_ENABLED &&
|
||||
!th_zone->sensor_conf->trip_data.trigger_falling)
|
||||
thermal->polling_delay = IDLE_INTERVAL;
|
||||
else
|
||||
thermal->polling_delay = 0;
|
||||
|
||||
mutex_unlock(&thermal->lock);
|
||||
|
||||
th_zone->mode = mode;
|
||||
thermal_zone_device_update(thermal);
|
||||
dev_dbg(th_zone->sensor_conf->dev,
|
||||
"thermal polling set for duration=%d msec\n",
|
||||
thermal->polling_delay);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Get trip type callback functions for thermal zone */
|
||||
static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip,
|
||||
enum thermal_trip_type *type)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
int max_trip = th_zone->sensor_conf->trip_data.trip_count;
|
||||
int trip_type;
|
||||
|
||||
if (trip < 0 || trip >= max_trip)
|
||||
return -EINVAL;
|
||||
|
||||
trip_type = th_zone->sensor_conf->trip_data.trip_type[trip];
|
||||
|
||||
if (trip_type == SW_TRIP)
|
||||
*type = THERMAL_TRIP_CRITICAL;
|
||||
else if (trip_type == THROTTLE_ACTIVE)
|
||||
*type = THERMAL_TRIP_ACTIVE;
|
||||
else if (trip_type == THROTTLE_PASSIVE)
|
||||
*type = THERMAL_TRIP_PASSIVE;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get trip temperature callback functions for thermal zone */
|
||||
static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip,
|
||||
unsigned long *temp)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
int max_trip = th_zone->sensor_conf->trip_data.trip_count;
|
||||
|
||||
if (trip < 0 || trip >= max_trip)
|
||||
return -EINVAL;
|
||||
|
||||
*temp = th_zone->sensor_conf->trip_data.trip_val[trip];
|
||||
/* convert the temperature into millicelsius */
|
||||
*temp = *temp * MCELSIUS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get critical temperature callback functions for thermal zone */
|
||||
static int exynos_get_crit_temp(struct thermal_zone_device *thermal,
|
||||
unsigned long *temp)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
int max_trip = th_zone->sensor_conf->trip_data.trip_count;
|
||||
/* Get the temp of highest trip*/
|
||||
return exynos_get_trip_temp(thermal, max_trip - 1, temp);
|
||||
}
|
||||
|
||||
/* Bind callback functions for thermal zone */
|
||||
static int exynos_bind(struct thermal_zone_device *thermal,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{
|
||||
int ret = 0, i, tab_size, level;
|
||||
struct freq_clip_table *tab_ptr, *clip_data;
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
struct thermal_sensor_conf *data = th_zone->sensor_conf;
|
||||
|
||||
tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data;
|
||||
tab_size = data->cooling_data.freq_clip_count;
|
||||
|
||||
if (tab_ptr == NULL || tab_size == 0)
|
||||
return 0;
|
||||
|
||||
/* find the cooling device registered*/
|
||||
for (i = 0; i < th_zone->cool_dev_size; i++)
|
||||
if (cdev == th_zone->cool_dev[i])
|
||||
break;
|
||||
|
||||
/* No matching cooling device */
|
||||
if (i == th_zone->cool_dev_size)
|
||||
return 0;
|
||||
|
||||
/* Bind the thermal zone to the cpufreq cooling device */
|
||||
for (i = 0; i < tab_size; i++) {
|
||||
clip_data = (struct freq_clip_table *)&(tab_ptr[i]);
|
||||
level = cpufreq_cooling_get_level(0, clip_data->freq_clip_max);
|
||||
if (level == THERMAL_CSTATE_INVALID)
|
||||
return 0;
|
||||
switch (GET_ZONE(i)) {
|
||||
case MONITOR_ZONE:
|
||||
case WARN_ZONE:
|
||||
if (thermal_zone_bind_cooling_device(thermal, i, cdev,
|
||||
level, 0)) {
|
||||
dev_err(data->dev,
|
||||
"error unbinding cdev inst=%d\n", i);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
th_zone->bind = true;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Unbind callback functions for thermal zone */
|
||||
static int exynos_unbind(struct thermal_zone_device *thermal,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{
|
||||
int ret = 0, i, tab_size;
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
struct thermal_sensor_conf *data = th_zone->sensor_conf;
|
||||
|
||||
if (th_zone->bind == false)
|
||||
return 0;
|
||||
|
||||
tab_size = data->cooling_data.freq_clip_count;
|
||||
|
||||
if (tab_size == 0)
|
||||
return 0;
|
||||
|
||||
/* find the cooling device registered*/
|
||||
for (i = 0; i < th_zone->cool_dev_size; i++)
|
||||
if (cdev == th_zone->cool_dev[i])
|
||||
break;
|
||||
|
||||
/* No matching cooling device */
|
||||
if (i == th_zone->cool_dev_size)
|
||||
return 0;
|
||||
|
||||
/* Bind the thermal zone to the cpufreq cooling device */
|
||||
for (i = 0; i < tab_size; i++) {
|
||||
switch (GET_ZONE(i)) {
|
||||
case MONITOR_ZONE:
|
||||
case WARN_ZONE:
|
||||
if (thermal_zone_unbind_cooling_device(thermal, i,
|
||||
cdev)) {
|
||||
dev_err(data->dev,
|
||||
"error unbinding cdev inst=%d\n", i);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
th_zone->bind = false;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get temperature callback functions for thermal zone */
|
||||
static int exynos_get_temp(struct thermal_zone_device *thermal,
|
||||
unsigned long *temp)
|
||||
{
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
void *data;
|
||||
|
||||
if (!th_zone->sensor_conf) {
|
||||
dev_err(&thermal->device,
|
||||
"Temperature sensor not initialised\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
data = th_zone->sensor_conf->driver_data;
|
||||
*temp = th_zone->sensor_conf->read_temperature(data);
|
||||
/* convert the temperature into millicelsius */
|
||||
*temp = *temp * MCELSIUS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get temperature callback functions for thermal zone */
|
||||
static int exynos_set_emul_temp(struct thermal_zone_device *thermal,
|
||||
unsigned long temp)
|
||||
{
|
||||
void *data;
|
||||
int ret = -EINVAL;
|
||||
struct exynos_thermal_zone *th_zone = thermal->devdata;
|
||||
|
||||
if (!th_zone->sensor_conf) {
|
||||
dev_err(&thermal->device,
|
||||
"Temperature sensor not initialised\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
data = th_zone->sensor_conf->driver_data;
|
||||
if (th_zone->sensor_conf->write_emul_temp)
|
||||
ret = th_zone->sensor_conf->write_emul_temp(data, temp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the temperature trend */
|
||||
static int exynos_get_trend(struct thermal_zone_device *thermal,
|
||||
int trip, enum thermal_trend *trend)
|
||||
{
|
||||
int ret;
|
||||
unsigned long trip_temp;
|
||||
|
||||
ret = exynos_get_trip_temp(thermal, trip, &trip_temp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (thermal->temperature >= trip_temp)
|
||||
*trend = THERMAL_TREND_RAISE_FULL;
|
||||
else
|
||||
*trend = THERMAL_TREND_DROP_FULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* Operation callback functions for thermal zone */
|
||||
static struct thermal_zone_device_ops const exynos_dev_ops = {
|
||||
.bind = exynos_bind,
|
||||
.unbind = exynos_unbind,
|
||||
.get_temp = exynos_get_temp,
|
||||
.set_emul_temp = exynos_set_emul_temp,
|
||||
.get_trend = exynos_get_trend,
|
||||
.get_mode = exynos_get_mode,
|
||||
.set_mode = exynos_set_mode,
|
||||
.get_trip_type = exynos_get_trip_type,
|
||||
.get_trip_temp = exynos_get_trip_temp,
|
||||
.get_crit_temp = exynos_get_crit_temp,
|
||||
};
|
||||
|
||||
/*
|
||||
* This function may be called from interrupt based temperature sensor
|
||||
* when threshold is changed.
|
||||
*/
|
||||
void exynos_report_trigger(struct thermal_sensor_conf *conf)
|
||||
{
|
||||
unsigned int i;
|
||||
char data[10];
|
||||
char *envp[] = { data, NULL };
|
||||
struct exynos_thermal_zone *th_zone;
|
||||
|
||||
if (!conf || !conf->pzone_data) {
|
||||
pr_err("Invalid temperature sensor configuration data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
th_zone = conf->pzone_data;
|
||||
if (th_zone->therm_dev)
|
||||
return;
|
||||
|
||||
if (th_zone->bind == false) {
|
||||
for (i = 0; i < th_zone->cool_dev_size; i++) {
|
||||
if (!th_zone->cool_dev[i])
|
||||
continue;
|
||||
exynos_bind(th_zone->therm_dev,
|
||||
th_zone->cool_dev[i]);
|
||||
}
|
||||
}
|
||||
|
||||
thermal_zone_device_update(th_zone->therm_dev);
|
||||
|
||||
mutex_lock(&th_zone->therm_dev->lock);
|
||||
/* Find the level for which trip happened */
|
||||
for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
|
||||
if (th_zone->therm_dev->last_temperature <
|
||||
th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS)
|
||||
break;
|
||||
}
|
||||
|
||||
if (th_zone->mode == THERMAL_DEVICE_ENABLED &&
|
||||
!th_zone->sensor_conf->trip_data.trigger_falling) {
|
||||
if (i > 0)
|
||||
th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
|
||||
else
|
||||
th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
|
||||
}
|
||||
|
||||
snprintf(data, sizeof(data), "%u", i);
|
||||
kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
|
||||
mutex_unlock(&th_zone->therm_dev->lock);
|
||||
}
|
||||
|
||||
/* Register with the in-kernel thermal management */
|
||||
int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
|
||||
{
|
||||
int ret;
|
||||
struct cpumask mask_val;
|
||||
struct exynos_thermal_zone *th_zone;
|
||||
|
||||
if (!sensor_conf || !sensor_conf->read_temperature) {
|
||||
pr_err("Temperature sensor not initialised\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
th_zone = devm_kzalloc(sensor_conf->dev,
|
||||
sizeof(struct exynos_thermal_zone), GFP_KERNEL);
|
||||
if (!th_zone)
|
||||
return -ENOMEM;
|
||||
|
||||
th_zone->sensor_conf = sensor_conf;
|
||||
/*
|
||||
* TODO: 1) Handle multiple cooling devices in a thermal zone
|
||||
* 2) Add a flag/name in cooling info to map to specific
|
||||
* sensor
|
||||
*/
|
||||
if (sensor_conf->cooling_data.freq_clip_count > 0) {
|
||||
cpumask_set_cpu(0, &mask_val);
|
||||
th_zone->cool_dev[th_zone->cool_dev_size] =
|
||||
cpufreq_cooling_register(&mask_val);
|
||||
if (IS_ERR(th_zone->cool_dev[th_zone->cool_dev_size])) {
|
||||
dev_err(sensor_conf->dev,
|
||||
"Failed to register cpufreq cooling device\n");
|
||||
ret = -EINVAL;
|
||||
goto err_unregister;
|
||||
}
|
||||
th_zone->cool_dev_size++;
|
||||
}
|
||||
|
||||
th_zone->therm_dev = thermal_zone_device_register(
|
||||
sensor_conf->name, sensor_conf->trip_data.trip_count,
|
||||
0, th_zone, &exynos_dev_ops, NULL, 0,
|
||||
sensor_conf->trip_data.trigger_falling ? 0 :
|
||||
IDLE_INTERVAL);
|
||||
|
||||
if (IS_ERR(th_zone->therm_dev)) {
|
||||
dev_err(sensor_conf->dev,
|
||||
"Failed to register thermal zone device\n");
|
||||
ret = PTR_ERR(th_zone->therm_dev);
|
||||
goto err_unregister;
|
||||
}
|
||||
th_zone->mode = THERMAL_DEVICE_ENABLED;
|
||||
sensor_conf->pzone_data = th_zone;
|
||||
|
||||
dev_info(sensor_conf->dev,
|
||||
"Exynos: Thermal zone(%s) registered\n", sensor_conf->name);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
exynos_unregister_thermal(sensor_conf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Un-Register with the in-kernel thermal management */
|
||||
void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf)
|
||||
{
|
||||
int i;
|
||||
struct exynos_thermal_zone *th_zone;
|
||||
|
||||
if (!sensor_conf || !sensor_conf->pzone_data) {
|
||||
pr_err("Invalid temperature sensor configuration data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
th_zone = sensor_conf->pzone_data;
|
||||
|
||||
if (th_zone->therm_dev)
|
||||
thermal_zone_device_unregister(th_zone->therm_dev);
|
||||
|
||||
for (i = 0; i < th_zone->cool_dev_size; i++) {
|
||||
if (th_zone->cool_dev[i])
|
||||
cpufreq_cooling_unregister(th_zone->cool_dev[i]);
|
||||
}
|
||||
|
||||
dev_info(sensor_conf->dev,
|
||||
"Exynos: Kernel Thermal management unregistered\n");
|
||||
}
|
107
drivers/thermal/samsung/exynos_thermal_common.h
Normal file
107
drivers/thermal/samsung/exynos_thermal_common.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* exynos_thermal_common.h - Samsung EXYNOS common header file
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _EXYNOS_THERMAL_COMMON_H
|
||||
#define _EXYNOS_THERMAL_COMMON_H
|
||||
|
||||
/* In-kernel thermal framework related macros & definations */
|
||||
#define SENSOR_NAME_LEN 16
|
||||
#define MAX_TRIP_COUNT 8
|
||||
#define MAX_COOLING_DEVICE 4
|
||||
#define MAX_THRESHOLD_LEVS 5
|
||||
|
||||
#define ACTIVE_INTERVAL 500
|
||||
#define IDLE_INTERVAL 10000
|
||||
#define MCELSIUS 1000
|
||||
|
||||
/* CPU Zone information */
|
||||
#define PANIC_ZONE 4
|
||||
#define WARN_ZONE 3
|
||||
#define MONITOR_ZONE 2
|
||||
#define SAFE_ZONE 1
|
||||
|
||||
#define GET_ZONE(trip) (trip + 2)
|
||||
#define GET_TRIP(zone) (zone - 2)
|
||||
|
||||
enum trigger_type {
|
||||
THROTTLE_ACTIVE = 1,
|
||||
THROTTLE_PASSIVE,
|
||||
SW_TRIP,
|
||||
HW_TRIP,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct freq_clip_table
|
||||
* @freq_clip_max: maximum frequency allowed for this cooling state.
|
||||
* @temp_level: Temperature level at which the temperature clipping will
|
||||
* happen.
|
||||
* @mask_val: cpumask of the allowed cpu's where the clipping will take place.
|
||||
*
|
||||
* This structure is required to be filled and passed to the
|
||||
* cpufreq_cooling_unregister function.
|
||||
*/
|
||||
struct freq_clip_table {
|
||||
unsigned int freq_clip_max;
|
||||
unsigned int temp_level;
|
||||
const struct cpumask *mask_val;
|
||||
};
|
||||
|
||||
struct thermal_trip_point_conf {
|
||||
int trip_val[MAX_TRIP_COUNT];
|
||||
int trip_type[MAX_TRIP_COUNT];
|
||||
int trip_count;
|
||||
unsigned char trigger_falling;
|
||||
};
|
||||
|
||||
struct thermal_cooling_conf {
|
||||
struct freq_clip_table freq_data[MAX_TRIP_COUNT];
|
||||
int freq_clip_count;
|
||||
};
|
||||
|
||||
struct thermal_sensor_conf {
|
||||
char name[SENSOR_NAME_LEN];
|
||||
int (*read_temperature)(void *data);
|
||||
int (*write_emul_temp)(void *drv_data, unsigned long temp);
|
||||
struct thermal_trip_point_conf trip_data;
|
||||
struct thermal_cooling_conf cooling_data;
|
||||
void *driver_data;
|
||||
void *pzone_data;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
/*Functions used exynos based thermal sensor driver*/
|
||||
#ifdef CONFIG_EXYNOS_THERMAL_CORE
|
||||
void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf);
|
||||
int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf);
|
||||
void exynos_report_trigger(struct thermal_sensor_conf *sensor_conf);
|
||||
#else
|
||||
static inline void
|
||||
exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) { return; }
|
||||
|
||||
static inline int
|
||||
exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) { return 0; }
|
||||
|
||||
static inline void
|
||||
exynos_report_trigger(struct thermal_sensor_conf *sensor_conf) { return; }
|
||||
|
||||
#endif /* CONFIG_EXYNOS_THERMAL_CORE */
|
||||
#endif /* _EXYNOS_THERMAL_COMMON_H */
|
762
drivers/thermal/samsung/exynos_tmu.c
Normal file
762
drivers/thermal/samsung/exynos_tmu.c
Normal file
|
@ -0,0 +1,762 @@
|
|||
/*
|
||||
* exynos_tmu.c - Samsung EXYNOS TMU (Thermal Management Unit)
|
||||
*
|
||||
* Copyright (C) 2011 Samsung Electronics
|
||||
* Donggeun Kim <dg77.kim@samsung.com>
|
||||
* Amit Daniel Kachhap <amit.kachhap@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include "exynos_thermal_common.h"
|
||||
#include "exynos_tmu.h"
|
||||
#include "exynos_tmu_data.h"
|
||||
|
||||
/**
|
||||
* struct exynos_tmu_data : A structure to hold the private data of the TMU
|
||||
driver
|
||||
* @id: identifier of the one instance of the TMU controller.
|
||||
* @pdata: pointer to the tmu platform/configuration data
|
||||
* @base: base address of the single instance of the TMU controller.
|
||||
* @base_common: base address of the common registers of the TMU controller.
|
||||
* @irq: irq number of the TMU controller.
|
||||
* @soc: id of the SOC type.
|
||||
* @irq_work: pointer to the irq work structure.
|
||||
* @lock: lock to implement synchronization.
|
||||
* @clk: pointer to the clock structure.
|
||||
* @temp_error1: fused value of the first point trim.
|
||||
* @temp_error2: fused value of the second point trim.
|
||||
* @regulator: pointer to the TMU regulator structure.
|
||||
* @reg_conf: pointer to structure to register with core thermal.
|
||||
*/
|
||||
struct exynos_tmu_data {
|
||||
int id;
|
||||
struct exynos_tmu_platform_data *pdata;
|
||||
void __iomem *base;
|
||||
void __iomem *base_common;
|
||||
int irq;
|
||||
enum soc_type soc;
|
||||
struct work_struct irq_work;
|
||||
struct mutex lock;
|
||||
struct clk *clk;
|
||||
u8 temp_error1, temp_error2;
|
||||
struct regulator *regulator;
|
||||
struct thermal_sensor_conf *reg_conf;
|
||||
};
|
||||
|
||||
/*
|
||||
* TMU treats temperature as a mapped temperature code.
|
||||
* The temperature is converted differently depending on the calibration type.
|
||||
*/
|
||||
static int temp_to_code(struct exynos_tmu_data *data, u8 temp)
|
||||
{
|
||||
struct exynos_tmu_platform_data *pdata = data->pdata;
|
||||
int temp_code;
|
||||
|
||||
if (pdata->cal_mode == HW_MODE)
|
||||
return temp;
|
||||
|
||||
if (data->soc == SOC_ARCH_EXYNOS4210)
|
||||
/* temp should range between 25 and 125 */
|
||||
if (temp < 25 || temp > 125) {
|
||||
temp_code = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (pdata->cal_type) {
|
||||
case TYPE_TWO_POINT_TRIMMING:
|
||||
temp_code = (temp - pdata->first_point_trim) *
|
||||
(data->temp_error2 - data->temp_error1) /
|
||||
(pdata->second_point_trim - pdata->first_point_trim) +
|
||||
data->temp_error1;
|
||||
break;
|
||||
case TYPE_ONE_POINT_TRIMMING:
|
||||
temp_code = temp + data->temp_error1 - pdata->first_point_trim;
|
||||
break;
|
||||
default:
|
||||
temp_code = temp + pdata->default_temp_offset;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return temp_code;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate a temperature value from a temperature code.
|
||||
* The unit of the temperature is degree Celsius.
|
||||
*/
|
||||
static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code)
|
||||
{
|
||||
struct exynos_tmu_platform_data *pdata = data->pdata;
|
||||
int temp;
|
||||
|
||||
if (pdata->cal_mode == HW_MODE)
|
||||
return temp_code;
|
||||
|
||||
if (data->soc == SOC_ARCH_EXYNOS4210)
|
||||
/* temp_code should range between 75 and 175 */
|
||||
if (temp_code < 75 || temp_code > 175) {
|
||||
temp = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (pdata->cal_type) {
|
||||
case TYPE_TWO_POINT_TRIMMING:
|
||||
temp = (temp_code - data->temp_error1) *
|
||||
(pdata->second_point_trim - pdata->first_point_trim) /
|
||||
(data->temp_error2 - data->temp_error1) +
|
||||
pdata->first_point_trim;
|
||||
break;
|
||||
case TYPE_ONE_POINT_TRIMMING:
|
||||
temp = temp_code - data->temp_error1 + pdata->first_point_trim;
|
||||
break;
|
||||
default:
|
||||
temp = temp_code - pdata->default_temp_offset;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return temp;
|
||||
}
|
||||
|
||||
static int exynos_tmu_initialize(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
||||
struct exynos_tmu_platform_data *pdata = data->pdata;
|
||||
const struct exynos_tmu_registers *reg = pdata->registers;
|
||||
unsigned int status, trim_info = 0, con;
|
||||
unsigned int rising_threshold = 0, falling_threshold = 0;
|
||||
int ret = 0, threshold_code, i, trigger_levs = 0;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
if (TMU_SUPPORTS(pdata, READY_STATUS)) {
|
||||
status = readb(data->base + reg->tmu_status);
|
||||
if (!status) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (TMU_SUPPORTS(pdata, TRIM_RELOAD))
|
||||
__raw_writel(1, data->base + reg->triminfo_ctrl);
|
||||
|
||||
if (pdata->cal_mode == HW_MODE)
|
||||
goto skip_calib_data;
|
||||
|
||||
/* Save trimming info in order to perform calibration */
|
||||
if (data->soc == SOC_ARCH_EXYNOS5440) {
|
||||
/*
|
||||
* For exynos5440 soc triminfo value is swapped between TMU0 and
|
||||
* TMU2, so the below logic is needed.
|
||||
*/
|
||||
switch (data->id) {
|
||||
case 0:
|
||||
trim_info = readl(data->base +
|
||||
EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data);
|
||||
break;
|
||||
case 1:
|
||||
trim_info = readl(data->base + reg->triminfo_data);
|
||||
break;
|
||||
case 2:
|
||||
trim_info = readl(data->base -
|
||||
EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data);
|
||||
}
|
||||
} else {
|
||||
trim_info = readl(data->base + reg->triminfo_data);
|
||||
}
|
||||
data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK;
|
||||
data->temp_error2 = ((trim_info >> reg->triminfo_85_shift) &
|
||||
EXYNOS_TMU_TEMP_MASK);
|
||||
|
||||
if (!data->temp_error1 ||
|
||||
(pdata->min_efuse_value > data->temp_error1) ||
|
||||
(data->temp_error1 > pdata->max_efuse_value))
|
||||
data->temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK;
|
||||
|
||||
if (!data->temp_error2)
|
||||
data->temp_error2 =
|
||||
(pdata->efuse_value >> reg->triminfo_85_shift) &
|
||||
EXYNOS_TMU_TEMP_MASK;
|
||||
|
||||
skip_calib_data:
|
||||
if (pdata->max_trigger_level > MAX_THRESHOLD_LEVS) {
|
||||
dev_err(&pdev->dev, "Invalid max trigger level\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < pdata->max_trigger_level; i++) {
|
||||
if (!pdata->trigger_levels[i])
|
||||
continue;
|
||||
|
||||
if ((pdata->trigger_type[i] == HW_TRIP) &&
|
||||
(!pdata->trigger_levels[pdata->max_trigger_level - 1])) {
|
||||
dev_err(&pdev->dev, "Invalid hw trigger level\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Count trigger levels except the HW trip*/
|
||||
if (!(pdata->trigger_type[i] == HW_TRIP))
|
||||
trigger_levs++;
|
||||
}
|
||||
|
||||
if (data->soc == SOC_ARCH_EXYNOS4210) {
|
||||
/* Write temperature code for threshold */
|
||||
threshold_code = temp_to_code(data, pdata->threshold);
|
||||
if (threshold_code < 0) {
|
||||
ret = threshold_code;
|
||||
goto out;
|
||||
}
|
||||
writeb(threshold_code,
|
||||
data->base + reg->threshold_temp);
|
||||
for (i = 0; i < trigger_levs; i++)
|
||||
writeb(pdata->trigger_levels[i], data->base +
|
||||
reg->threshold_th0 + i * sizeof(reg->threshold_th0));
|
||||
|
||||
writel(reg->inten_rise_mask, data->base + reg->tmu_intclear);
|
||||
} else {
|
||||
/* Write temperature code for rising and falling threshold */
|
||||
for (i = 0;
|
||||
i < trigger_levs && i < EXYNOS_MAX_TRIGGER_PER_REG; i++) {
|
||||
threshold_code = temp_to_code(data,
|
||||
pdata->trigger_levels[i]);
|
||||
if (threshold_code < 0) {
|
||||
ret = threshold_code;
|
||||
goto out;
|
||||
}
|
||||
rising_threshold |= threshold_code << 8 * i;
|
||||
if (pdata->threshold_falling) {
|
||||
threshold_code = temp_to_code(data,
|
||||
pdata->trigger_levels[i] -
|
||||
pdata->threshold_falling);
|
||||
if (threshold_code > 0)
|
||||
falling_threshold |=
|
||||
threshold_code << 8 * i;
|
||||
}
|
||||
}
|
||||
|
||||
writel(rising_threshold,
|
||||
data->base + reg->threshold_th0);
|
||||
writel(falling_threshold,
|
||||
data->base + reg->threshold_th1);
|
||||
|
||||
writel((reg->inten_rise_mask << reg->inten_rise_shift) |
|
||||
(reg->inten_fall_mask << reg->inten_fall_shift),
|
||||
data->base + reg->tmu_intclear);
|
||||
|
||||
/* if last threshold limit is also present */
|
||||
i = pdata->max_trigger_level - 1;
|
||||
if (pdata->trigger_levels[i] &&
|
||||
(pdata->trigger_type[i] == HW_TRIP)) {
|
||||
threshold_code = temp_to_code(data,
|
||||
pdata->trigger_levels[i]);
|
||||
if (threshold_code < 0) {
|
||||
ret = threshold_code;
|
||||
goto out;
|
||||
}
|
||||
if (i == EXYNOS_MAX_TRIGGER_PER_REG - 1) {
|
||||
/* 1-4 level to be assigned in th0 reg */
|
||||
rising_threshold |= threshold_code << 8 * i;
|
||||
writel(rising_threshold,
|
||||
data->base + reg->threshold_th0);
|
||||
} else if (i == EXYNOS_MAX_TRIGGER_PER_REG) {
|
||||
/* 5th level to be assigned in th2 reg */
|
||||
rising_threshold =
|
||||
threshold_code << reg->threshold_th3_l0_shift;
|
||||
writel(rising_threshold,
|
||||
data->base + reg->threshold_th2);
|
||||
}
|
||||
con = readl(data->base + reg->tmu_ctrl);
|
||||
con |= (1 << reg->therm_trip_en_shift);
|
||||
writel(con, data->base + reg->tmu_ctrl);
|
||||
}
|
||||
}
|
||||
/*Clear the PMIN in the common TMU register*/
|
||||
if (reg->tmu_pmin && !data->id)
|
||||
writel(0, data->base_common + reg->tmu_pmin);
|
||||
out:
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void exynos_tmu_control(struct platform_device *pdev, bool on)
|
||||
{
|
||||
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
||||
struct exynos_tmu_platform_data *pdata = data->pdata;
|
||||
const struct exynos_tmu_registers *reg = pdata->registers;
|
||||
unsigned int con, interrupt_en, cal_val;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
con = readl(data->base + reg->tmu_ctrl);
|
||||
|
||||
if (pdata->reference_voltage) {
|
||||
con &= ~(reg->buf_vref_sel_mask << reg->buf_vref_sel_shift);
|
||||
con |= pdata->reference_voltage << reg->buf_vref_sel_shift;
|
||||
}
|
||||
|
||||
if (pdata->gain) {
|
||||
con &= ~(reg->buf_slope_sel_mask << reg->buf_slope_sel_shift);
|
||||
con |= (pdata->gain << reg->buf_slope_sel_shift);
|
||||
}
|
||||
|
||||
if (pdata->noise_cancel_mode) {
|
||||
con &= ~(reg->therm_trip_mode_mask <<
|
||||
reg->therm_trip_mode_shift);
|
||||
con |= (pdata->noise_cancel_mode << reg->therm_trip_mode_shift);
|
||||
}
|
||||
|
||||
if (pdata->cal_mode == HW_MODE) {
|
||||
con &= ~(reg->calib_mode_mask << reg->calib_mode_shift);
|
||||
cal_val = 0;
|
||||
switch (pdata->cal_type) {
|
||||
case TYPE_TWO_POINT_TRIMMING:
|
||||
cal_val = 3;
|
||||
break;
|
||||
case TYPE_ONE_POINT_TRIMMING_85:
|
||||
cal_val = 2;
|
||||
break;
|
||||
case TYPE_ONE_POINT_TRIMMING_25:
|
||||
cal_val = 1;
|
||||
break;
|
||||
case TYPE_NONE:
|
||||
break;
|
||||
default:
|
||||
dev_err(&pdev->dev, "Invalid calibration type, using none\n");
|
||||
}
|
||||
con |= cal_val << reg->calib_mode_shift;
|
||||
}
|
||||
|
||||
if (on) {
|
||||
con |= (1 << reg->core_en_shift);
|
||||
interrupt_en =
|
||||
pdata->trigger_enable[3] << reg->inten_rise3_shift |
|
||||
pdata->trigger_enable[2] << reg->inten_rise2_shift |
|
||||
pdata->trigger_enable[1] << reg->inten_rise1_shift |
|
||||
pdata->trigger_enable[0] << reg->inten_rise0_shift;
|
||||
if (TMU_SUPPORTS(pdata, FALLING_TRIP))
|
||||
interrupt_en |=
|
||||
interrupt_en << reg->inten_fall0_shift;
|
||||
} else {
|
||||
con &= ~(1 << reg->core_en_shift);
|
||||
interrupt_en = 0; /* Disable all interrupts */
|
||||
}
|
||||
writel(interrupt_en, data->base + reg->tmu_inten);
|
||||
writel(con, data->base + reg->tmu_ctrl);
|
||||
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
}
|
||||
|
||||
static int exynos_tmu_read(struct exynos_tmu_data *data)
|
||||
{
|
||||
struct exynos_tmu_platform_data *pdata = data->pdata;
|
||||
const struct exynos_tmu_registers *reg = pdata->registers;
|
||||
u8 temp_code;
|
||||
int temp;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
temp_code = readb(data->base + reg->tmu_cur_temp);
|
||||
temp = code_to_temp(data, temp_code);
|
||||
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_THERMAL_EMULATION
|
||||
static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
|
||||
{
|
||||
struct exynos_tmu_data *data = drv_data;
|
||||
struct exynos_tmu_platform_data *pdata = data->pdata;
|
||||
const struct exynos_tmu_registers *reg = pdata->registers;
|
||||
unsigned int val;
|
||||
int ret = -EINVAL;
|
||||
|
||||
if (!TMU_SUPPORTS(pdata, EMULATION))
|
||||
goto out;
|
||||
|
||||
if (temp && temp < MCELSIUS)
|
||||
goto out;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
val = readl(data->base + reg->emul_con);
|
||||
|
||||
if (temp) {
|
||||
temp /= MCELSIUS;
|
||||
|
||||
if (TMU_SUPPORTS(pdata, EMUL_TIME)) {
|
||||
val &= ~(EXYNOS_EMUL_TIME_MASK << reg->emul_time_shift);
|
||||
val |= (EXYNOS_EMUL_TIME << reg->emul_time_shift);
|
||||
}
|
||||
val &= ~(EXYNOS_EMUL_DATA_MASK << reg->emul_temp_shift);
|
||||
val |= (temp_to_code(data, temp) << reg->emul_temp_shift) |
|
||||
EXYNOS_EMUL_ENABLE;
|
||||
} else {
|
||||
val &= ~EXYNOS_EMUL_ENABLE;
|
||||
}
|
||||
|
||||
writel(val, data->base + reg->emul_con);
|
||||
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
|
||||
{ return -EINVAL; }
|
||||
#endif/*CONFIG_THERMAL_EMULATION*/
|
||||
|
||||
static void exynos_tmu_work(struct work_struct *work)
|
||||
{
|
||||
struct exynos_tmu_data *data = container_of(work,
|
||||
struct exynos_tmu_data, irq_work);
|
||||
struct exynos_tmu_platform_data *pdata = data->pdata;
|
||||
const struct exynos_tmu_registers *reg = pdata->registers;
|
||||
unsigned int val_irq, val_type;
|
||||
|
||||
/* Find which sensor generated this interrupt */
|
||||
if (reg->tmu_irqstatus) {
|
||||
val_type = readl(data->base_common + reg->tmu_irqstatus);
|
||||
if (!((val_type >> data->id) & 0x1))
|
||||
goto out;
|
||||
}
|
||||
|
||||
exynos_report_trigger(data->reg_conf);
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
/* TODO: take action based on particular interrupt */
|
||||
val_irq = readl(data->base + reg->tmu_intstat);
|
||||
/* clear the interrupts */
|
||||
writel(val_irq, data->base + reg->tmu_intclear);
|
||||
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
out:
|
||||
enable_irq(data->irq);
|
||||
}
|
||||
|
||||
static irqreturn_t exynos_tmu_irq(int irq, void *id)
|
||||
{
|
||||
struct exynos_tmu_data *data = id;
|
||||
|
||||
disable_irq_nosync(irq);
|
||||
schedule_work(&data->irq_work);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct of_device_id exynos_tmu_match[] = {
|
||||
{
|
||||
.compatible = "samsung,exynos4210-tmu",
|
||||
.data = (void *)EXYNOS4210_TMU_DRV_DATA,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,exynos4412-tmu",
|
||||
.data = (void *)EXYNOS5250_TMU_DRV_DATA,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,exynos5250-tmu",
|
||||
.data = (void *)EXYNOS5250_TMU_DRV_DATA,
|
||||
},
|
||||
{
|
||||
.compatible = "samsung,exynos5440-tmu",
|
||||
.data = (void *)EXYNOS5440_TMU_DRV_DATA,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, exynos_tmu_match);
|
||||
|
||||
static inline struct exynos_tmu_platform_data *exynos_get_driver_data(
|
||||
struct platform_device *pdev, int id)
|
||||
{
|
||||
struct exynos_tmu_init_data *data_table;
|
||||
struct exynos_tmu_platform_data *tmu_data;
|
||||
const struct of_device_id *match;
|
||||
|
||||
match = of_match_node(exynos_tmu_match, pdev->dev.of_node);
|
||||
if (!match)
|
||||
return NULL;
|
||||
data_table = (struct exynos_tmu_init_data *) match->data;
|
||||
if (!data_table || id >= data_table->tmu_count)
|
||||
return NULL;
|
||||
tmu_data = data_table->tmu_data;
|
||||
return (struct exynos_tmu_platform_data *) (tmu_data + id);
|
||||
}
|
||||
|
||||
static int exynos_map_dt_data(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
||||
struct exynos_tmu_platform_data *pdata;
|
||||
struct resource res;
|
||||
int ret;
|
||||
|
||||
if (!data || !pdev->dev.of_node)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Try enabling the regulator if found
|
||||
* TODO: Add regulator as an SOC feature, so that regulator enable
|
||||
* is a compulsory call.
|
||||
*/
|
||||
data->regulator = devm_regulator_get(&pdev->dev, "vtmu");
|
||||
if (!IS_ERR(data->regulator)) {
|
||||
ret = regulator_enable(data->regulator);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable vtmu\n");
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
dev_info(&pdev->dev, "Regulator node (vtmu) not found\n");
|
||||
}
|
||||
|
||||
data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl");
|
||||
if (data->id < 0)
|
||||
data->id = 0;
|
||||
|
||||
data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
|
||||
if (data->irq <= 0) {
|
||||
dev_err(&pdev->dev, "failed to get IRQ\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (of_address_to_resource(pdev->dev.of_node, 0, &res)) {
|
||||
dev_err(&pdev->dev, "failed to get Resource 0\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
|
||||
if (!data->base) {
|
||||
dev_err(&pdev->dev, "Failed to ioremap memory\n");
|
||||
return -EADDRNOTAVAIL;
|
||||
}
|
||||
|
||||
pdata = exynos_get_driver_data(pdev, data->id);
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "No platform init data supplied.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
data->pdata = pdata;
|
||||
/*
|
||||
* Check if the TMU shares some registers and then try to map the
|
||||
* memory of common registers.
|
||||
*/
|
||||
if (!TMU_SUPPORTS(pdata, SHARED_MEMORY))
|
||||
return 0;
|
||||
|
||||
if (of_address_to_resource(pdev->dev.of_node, 1, &res)) {
|
||||
dev_err(&pdev->dev, "failed to get Resource 1\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
data->base_common = devm_ioremap(&pdev->dev, res.start,
|
||||
resource_size(&res));
|
||||
if (!data->base_common) {
|
||||
dev_err(&pdev->dev, "Failed to ioremap memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_tmu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_tmu_data *data;
|
||||
struct exynos_tmu_platform_data *pdata;
|
||||
struct thermal_sensor_conf *sensor_conf;
|
||||
int ret, i;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),
|
||||
GFP_KERNEL);
|
||||
if (!data) {
|
||||
dev_err(&pdev->dev, "Failed to allocate driver structure\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
mutex_init(&data->lock);
|
||||
|
||||
ret = exynos_map_dt_data(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pdata = data->pdata;
|
||||
|
||||
INIT_WORK(&data->irq_work, exynos_tmu_work);
|
||||
|
||||
data->clk = devm_clk_get(&pdev->dev, "tmu_apbif");
|
||||
if (IS_ERR(data->clk)) {
|
||||
dev_err(&pdev->dev, "Failed to get clock\n");
|
||||
return PTR_ERR(data->clk);
|
||||
}
|
||||
|
||||
ret = clk_prepare(data->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (pdata->type == SOC_ARCH_EXYNOS ||
|
||||
pdata->type == SOC_ARCH_EXYNOS4210 ||
|
||||
pdata->type == SOC_ARCH_EXYNOS5440)
|
||||
data->soc = pdata->type;
|
||||
else {
|
||||
ret = -EINVAL;
|
||||
dev_err(&pdev->dev, "Platform not supported\n");
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
ret = exynos_tmu_initialize(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to initialize TMU\n");
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
exynos_tmu_control(pdev, true);
|
||||
|
||||
/* Allocate a structure to register with the exynos core thermal */
|
||||
sensor_conf = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct thermal_sensor_conf), GFP_KERNEL);
|
||||
if (!sensor_conf) {
|
||||
dev_err(&pdev->dev, "Failed to allocate registration struct\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_clk;
|
||||
}
|
||||
sprintf(sensor_conf->name, "therm_zone%d", data->id);
|
||||
sensor_conf->read_temperature = (int (*)(void *))exynos_tmu_read;
|
||||
sensor_conf->write_emul_temp =
|
||||
(int (*)(void *, unsigned long))exynos_tmu_set_emulation;
|
||||
sensor_conf->driver_data = data;
|
||||
sensor_conf->trip_data.trip_count = pdata->trigger_enable[0] +
|
||||
pdata->trigger_enable[1] + pdata->trigger_enable[2]+
|
||||
pdata->trigger_enable[3];
|
||||
|
||||
for (i = 0; i < sensor_conf->trip_data.trip_count; i++) {
|
||||
sensor_conf->trip_data.trip_val[i] =
|
||||
pdata->threshold + pdata->trigger_levels[i];
|
||||
sensor_conf->trip_data.trip_type[i] =
|
||||
pdata->trigger_type[i];
|
||||
}
|
||||
|
||||
sensor_conf->trip_data.trigger_falling = pdata->threshold_falling;
|
||||
|
||||
sensor_conf->cooling_data.freq_clip_count = pdata->freq_tab_count;
|
||||
for (i = 0; i < pdata->freq_tab_count; i++) {
|
||||
sensor_conf->cooling_data.freq_data[i].freq_clip_max =
|
||||
pdata->freq_tab[i].freq_clip_max;
|
||||
sensor_conf->cooling_data.freq_data[i].temp_level =
|
||||
pdata->freq_tab[i].temp_level;
|
||||
}
|
||||
sensor_conf->dev = &pdev->dev;
|
||||
/* Register the sensor with thermal management interface */
|
||||
ret = exynos_register_thermal(sensor_conf);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to register thermal interface\n");
|
||||
goto err_clk;
|
||||
}
|
||||
data->reg_conf = sensor_conf;
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_clk:
|
||||
clk_unprepare(data->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos_tmu_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
exynos_tmu_control(pdev, false);
|
||||
|
||||
exynos_unregister_thermal(data->reg_conf);
|
||||
|
||||
clk_unprepare(data->clk);
|
||||
|
||||
if (!IS_ERR(data->regulator))
|
||||
regulator_disable(data->regulator);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int exynos_tmu_suspend(struct device *dev)
|
||||
{
|
||||
exynos_tmu_control(to_platform_device(dev), false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_tmu_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
|
||||
exynos_tmu_initialize(pdev);
|
||||
exynos_tmu_control(pdev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(exynos_tmu_pm,
|
||||
exynos_tmu_suspend, exynos_tmu_resume);
|
||||
#define EXYNOS_TMU_PM (&exynos_tmu_pm)
|
||||
#else
|
||||
#define EXYNOS_TMU_PM NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver exynos_tmu_driver = {
|
||||
.driver = {
|
||||
.name = "exynos-tmu",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = EXYNOS_TMU_PM,
|
||||
.of_match_table = exynos_tmu_match,
|
||||
},
|
||||
.probe = exynos_tmu_probe,
|
||||
.remove = exynos_tmu_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(exynos_tmu_driver);
|
||||
|
||||
MODULE_DESCRIPTION("EXYNOS TMU Driver");
|
||||
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:exynos-tmu");
|
311
drivers/thermal/samsung/exynos_tmu.h
Normal file
311
drivers/thermal/samsung/exynos_tmu.h
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* exynos_tmu.h - Samsung EXYNOS TMU (Thermal Management Unit)
|
||||
*
|
||||
* Copyright (C) 2011 Samsung Electronics
|
||||
* Donggeun Kim <dg77.kim@samsung.com>
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _EXYNOS_TMU_H
|
||||
#define _EXYNOS_TMU_H
|
||||
#include <linux/cpu_cooling.h>
|
||||
|
||||
#include "exynos_thermal_common.h"
|
||||
|
||||
enum calibration_type {
|
||||
TYPE_ONE_POINT_TRIMMING,
|
||||
TYPE_ONE_POINT_TRIMMING_25,
|
||||
TYPE_ONE_POINT_TRIMMING_85,
|
||||
TYPE_TWO_POINT_TRIMMING,
|
||||
TYPE_NONE,
|
||||
};
|
||||
|
||||
enum calibration_mode {
|
||||
SW_MODE,
|
||||
HW_MODE,
|
||||
};
|
||||
|
||||
enum soc_type {
|
||||
SOC_ARCH_EXYNOS4210 = 1,
|
||||
SOC_ARCH_EXYNOS,
|
||||
SOC_ARCH_EXYNOS5440,
|
||||
};
|
||||
|
||||
/**
|
||||
* EXYNOS TMU supported features.
|
||||
* TMU_SUPPORT_EMULATION - This features is used to set user defined
|
||||
* temperature to the TMU controller.
|
||||
* TMU_SUPPORT_MULTI_INST - This features denotes that the soc
|
||||
* has many instances of TMU.
|
||||
* TMU_SUPPORT_TRIM_RELOAD - This features shows that trimming can
|
||||
* be reloaded.
|
||||
* TMU_SUPPORT_FALLING_TRIP - This features shows that interrupt can
|
||||
* be registered for falling trips also.
|
||||
* TMU_SUPPORT_READY_STATUS - This feature tells that the TMU current
|
||||
* state(active/idle) can be checked.
|
||||
* TMU_SUPPORT_EMUL_TIME - This features allows to set next temp emulation
|
||||
* sample time.
|
||||
* TMU_SUPPORT_SHARED_MEMORY - This feature tells that the different TMU
|
||||
* sensors shares some common registers.
|
||||
* TMU_SUPPORT - macro to compare the above features with the supplied.
|
||||
*/
|
||||
#define TMU_SUPPORT_EMULATION BIT(0)
|
||||
#define TMU_SUPPORT_MULTI_INST BIT(1)
|
||||
#define TMU_SUPPORT_TRIM_RELOAD BIT(2)
|
||||
#define TMU_SUPPORT_FALLING_TRIP BIT(3)
|
||||
#define TMU_SUPPORT_READY_STATUS BIT(4)
|
||||
#define TMU_SUPPORT_EMUL_TIME BIT(5)
|
||||
#define TMU_SUPPORT_SHARED_MEMORY BIT(6)
|
||||
|
||||
#define TMU_SUPPORTS(a, b) (a->features & TMU_SUPPORT_ ## b)
|
||||
|
||||
/**
|
||||
* struct exynos_tmu_register - register descriptors to access registers and
|
||||
* bitfields. The register validity, offsets and bitfield values may vary
|
||||
* slightly across different exynos SOC's.
|
||||
* @triminfo_data: register containing 2 pont trimming data
|
||||
* @triminfo_25_shift: shift bit of the 25 C trim value in triminfo_data reg.
|
||||
* @triminfo_85_shift: shift bit of the 85 C trim value in triminfo_data reg.
|
||||
* @triminfo_ctrl: trim info controller register.
|
||||
* @triminfo_reload_shift: shift of triminfo reload enable bit in triminfo_ctrl
|
||||
reg.
|
||||
* @tmu_ctrl: TMU main controller register.
|
||||
* @buf_vref_sel_shift: shift bits of reference voltage in tmu_ctrl register.
|
||||
* @buf_vref_sel_mask: mask bits of reference voltage in tmu_ctrl register.
|
||||
* @therm_trip_mode_shift: shift bits of tripping mode in tmu_ctrl register.
|
||||
* @therm_trip_mode_mask: mask bits of tripping mode in tmu_ctrl register.
|
||||
* @therm_trip_en_shift: shift bits of tripping enable in tmu_ctrl register.
|
||||
* @buf_slope_sel_shift: shift bits of amplifier gain value in tmu_ctrl
|
||||
register.
|
||||
* @buf_slope_sel_mask: mask bits of amplifier gain value in tmu_ctrl register.
|
||||
* @calib_mode_shift: shift bits of calibration mode value in tmu_ctrl
|
||||
register.
|
||||
* @calib_mode_mask: mask bits of calibration mode value in tmu_ctrl
|
||||
register.
|
||||
* @therm_trip_tq_en_shift: shift bits of thermal trip enable by TQ pin in
|
||||
tmu_ctrl register.
|
||||
* @core_en_shift: shift bits of TMU core enable bit in tmu_ctrl register.
|
||||
* @tmu_status: register drescribing the TMU status.
|
||||
* @tmu_cur_temp: register containing the current temperature of the TMU.
|
||||
* @tmu_cur_temp_shift: shift bits of current temp value in tmu_cur_temp
|
||||
register.
|
||||
* @threshold_temp: register containing the base threshold level.
|
||||
* @threshold_th0: Register containing first set of rising levels.
|
||||
* @threshold_th0_l0_shift: shift bits of level0 threshold temperature.
|
||||
* @threshold_th0_l1_shift: shift bits of level1 threshold temperature.
|
||||
* @threshold_th0_l2_shift: shift bits of level2 threshold temperature.
|
||||
* @threshold_th0_l3_shift: shift bits of level3 threshold temperature.
|
||||
* @threshold_th1: Register containing second set of rising levels.
|
||||
* @threshold_th1_l0_shift: shift bits of level0 threshold temperature.
|
||||
* @threshold_th1_l1_shift: shift bits of level1 threshold temperature.
|
||||
* @threshold_th1_l2_shift: shift bits of level2 threshold temperature.
|
||||
* @threshold_th1_l3_shift: shift bits of level3 threshold temperature.
|
||||
* @threshold_th2: Register containing third set of rising levels.
|
||||
* @threshold_th2_l0_shift: shift bits of level0 threshold temperature.
|
||||
* @threshold_th3: Register containing fourth set of rising levels.
|
||||
* @threshold_th3_l0_shift: shift bits of level0 threshold temperature.
|
||||
* @tmu_inten: register containing the different threshold interrupt
|
||||
enable bits.
|
||||
* @inten_rise_shift: shift bits of all rising interrupt bits.
|
||||
* @inten_rise_mask: mask bits of all rising interrupt bits.
|
||||
* @inten_fall_shift: shift bits of all rising interrupt bits.
|
||||
* @inten_fall_mask: mask bits of all rising interrupt bits.
|
||||
* @inten_rise0_shift: shift bits of rising 0 interrupt bits.
|
||||
* @inten_rise1_shift: shift bits of rising 1 interrupt bits.
|
||||
* @inten_rise2_shift: shift bits of rising 2 interrupt bits.
|
||||
* @inten_rise3_shift: shift bits of rising 3 interrupt bits.
|
||||
* @inten_fall0_shift: shift bits of falling 0 interrupt bits.
|
||||
* @inten_fall1_shift: shift bits of falling 1 interrupt bits.
|
||||
* @inten_fall2_shift: shift bits of falling 2 interrupt bits.
|
||||
* @inten_fall3_shift: shift bits of falling 3 interrupt bits.
|
||||
* @tmu_intstat: Register containing the interrupt status values.
|
||||
* @tmu_intclear: Register for clearing the raised interrupt status.
|
||||
* @emul_con: TMU emulation controller register.
|
||||
* @emul_temp_shift: shift bits of emulation temperature.
|
||||
* @emul_time_shift: shift bits of emulation time.
|
||||
* @emul_time_mask: mask bits of emulation time.
|
||||
* @tmu_irqstatus: register to find which TMU generated interrupts.
|
||||
* @tmu_pmin: register to get/set the Pmin value.
|
||||
*/
|
||||
struct exynos_tmu_registers {
|
||||
u32 triminfo_data;
|
||||
u32 triminfo_25_shift;
|
||||
u32 triminfo_85_shift;
|
||||
|
||||
u32 triminfo_ctrl;
|
||||
u32 triminfo_reload_shift;
|
||||
|
||||
u32 tmu_ctrl;
|
||||
u32 buf_vref_sel_shift;
|
||||
u32 buf_vref_sel_mask;
|
||||
u32 therm_trip_mode_shift;
|
||||
u32 therm_trip_mode_mask;
|
||||
u32 therm_trip_en_shift;
|
||||
u32 buf_slope_sel_shift;
|
||||
u32 buf_slope_sel_mask;
|
||||
u32 calib_mode_shift;
|
||||
u32 calib_mode_mask;
|
||||
u32 therm_trip_tq_en_shift;
|
||||
u32 core_en_shift;
|
||||
|
||||
u32 tmu_status;
|
||||
|
||||
u32 tmu_cur_temp;
|
||||
u32 tmu_cur_temp_shift;
|
||||
|
||||
u32 threshold_temp;
|
||||
|
||||
u32 threshold_th0;
|
||||
u32 threshold_th0_l0_shift;
|
||||
u32 threshold_th0_l1_shift;
|
||||
u32 threshold_th0_l2_shift;
|
||||
u32 threshold_th0_l3_shift;
|
||||
|
||||
u32 threshold_th1;
|
||||
u32 threshold_th1_l0_shift;
|
||||
u32 threshold_th1_l1_shift;
|
||||
u32 threshold_th1_l2_shift;
|
||||
u32 threshold_th1_l3_shift;
|
||||
|
||||
u32 threshold_th2;
|
||||
u32 threshold_th2_l0_shift;
|
||||
|
||||
u32 threshold_th3;
|
||||
u32 threshold_th3_l0_shift;
|
||||
|
||||
u32 tmu_inten;
|
||||
u32 inten_rise_shift;
|
||||
u32 inten_rise_mask;
|
||||
u32 inten_fall_shift;
|
||||
u32 inten_fall_mask;
|
||||
u32 inten_rise0_shift;
|
||||
u32 inten_rise1_shift;
|
||||
u32 inten_rise2_shift;
|
||||
u32 inten_rise3_shift;
|
||||
u32 inten_fall0_shift;
|
||||
u32 inten_fall1_shift;
|
||||
u32 inten_fall2_shift;
|
||||
u32 inten_fall3_shift;
|
||||
|
||||
u32 tmu_intstat;
|
||||
|
||||
u32 tmu_intclear;
|
||||
|
||||
u32 emul_con;
|
||||
u32 emul_temp_shift;
|
||||
u32 emul_time_shift;
|
||||
u32 emul_time_mask;
|
||||
|
||||
u32 tmu_irqstatus;
|
||||
u32 tmu_pmin;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos_tmu_platform_data
|
||||
* @threshold: basic temperature for generating interrupt
|
||||
* 25 <= threshold <= 125 [unit: degree Celsius]
|
||||
* @threshold_falling: differntial value for setting threshold
|
||||
* of temperature falling interrupt.
|
||||
* @trigger_levels: array for each interrupt levels
|
||||
* [unit: degree Celsius]
|
||||
* 0: temperature for trigger_level0 interrupt
|
||||
* condition for trigger_level0 interrupt:
|
||||
* current temperature > threshold + trigger_levels[0]
|
||||
* 1: temperature for trigger_level1 interrupt
|
||||
* condition for trigger_level1 interrupt:
|
||||
* current temperature > threshold + trigger_levels[1]
|
||||
* 2: temperature for trigger_level2 interrupt
|
||||
* condition for trigger_level2 interrupt:
|
||||
* current temperature > threshold + trigger_levels[2]
|
||||
* 3: temperature for trigger_level3 interrupt
|
||||
* condition for trigger_level3 interrupt:
|
||||
* current temperature > threshold + trigger_levels[3]
|
||||
* @trigger_type: defines the type of trigger. Possible values are,
|
||||
* THROTTLE_ACTIVE trigger type
|
||||
* THROTTLE_PASSIVE trigger type
|
||||
* SW_TRIP trigger type
|
||||
* HW_TRIP
|
||||
* @trigger_enable[]: array to denote which trigger levels are enabled.
|
||||
* 1 = enable trigger_level[] interrupt,
|
||||
* 0 = disable trigger_level[] interrupt
|
||||
* @max_trigger_level: max trigger level supported by the TMU
|
||||
* @gain: gain of amplifier in the positive-TC generator block
|
||||
* 0 <= gain <= 15
|
||||
* @reference_voltage: reference voltage of amplifier
|
||||
* in the positive-TC generator block
|
||||
* 0 <= reference_voltage <= 31
|
||||
* @noise_cancel_mode: noise cancellation mode
|
||||
* 000, 100, 101, 110 and 111 can be different modes
|
||||
* @type: determines the type of SOC
|
||||
* @efuse_value: platform defined fuse value
|
||||
* @min_efuse_value: minimum valid trimming data
|
||||
* @max_efuse_value: maximum valid trimming data
|
||||
* @first_point_trim: temp value of the first point trimming
|
||||
* @second_point_trim: temp value of the second point trimming
|
||||
* @default_temp_offset: default temperature offset in case of no trimming
|
||||
* @cal_type: calibration type for temperature
|
||||
* @cal_mode: calibration mode for temperature
|
||||
* @freq_clip_table: Table representing frequency reduction percentage.
|
||||
* @freq_tab_count: Count of the above table as frequency reduction may
|
||||
* applicable to only some of the trigger levels.
|
||||
* @registers: Pointer to structure containing all the TMU controller registers
|
||||
* and bitfields shifts and masks.
|
||||
* @features: a bitfield value indicating the features supported in SOC like
|
||||
* emulation, multi instance etc
|
||||
*
|
||||
* This structure is required for configuration of exynos_tmu driver.
|
||||
*/
|
||||
struct exynos_tmu_platform_data {
|
||||
u8 threshold;
|
||||
u8 threshold_falling;
|
||||
u8 trigger_levels[MAX_TRIP_COUNT];
|
||||
enum trigger_type trigger_type[MAX_TRIP_COUNT];
|
||||
bool trigger_enable[MAX_TRIP_COUNT];
|
||||
u8 max_trigger_level;
|
||||
u8 gain;
|
||||
u8 reference_voltage;
|
||||
u8 noise_cancel_mode;
|
||||
|
||||
u32 efuse_value;
|
||||
u32 min_efuse_value;
|
||||
u32 max_efuse_value;
|
||||
u8 first_point_trim;
|
||||
u8 second_point_trim;
|
||||
u8 default_temp_offset;
|
||||
|
||||
enum calibration_type cal_type;
|
||||
enum calibration_mode cal_mode;
|
||||
enum soc_type type;
|
||||
struct freq_clip_table freq_tab[4];
|
||||
unsigned int freq_tab_count;
|
||||
const struct exynos_tmu_registers *registers;
|
||||
unsigned int features;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos_tmu_init_data
|
||||
* @tmu_count: number of TMU instances.
|
||||
* @tmu_data: platform data of all TMU instances.
|
||||
* This structure is required to store data for multi-instance exynos tmu
|
||||
* driver.
|
||||
*/
|
||||
struct exynos_tmu_init_data {
|
||||
int tmu_count;
|
||||
struct exynos_tmu_platform_data tmu_data[];
|
||||
};
|
||||
|
||||
#endif /* _EXYNOS_TMU_H */
|
250
drivers/thermal/samsung/exynos_tmu_data.c
Normal file
250
drivers/thermal/samsung/exynos_tmu_data.c
Normal file
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* exynos_tmu_data.c - Samsung EXYNOS tmu data file
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "exynos_thermal_common.h"
|
||||
#include "exynos_tmu.h"
|
||||
#include "exynos_tmu_data.h"
|
||||
|
||||
#if defined(CONFIG_CPU_EXYNOS4210)
|
||||
static const struct exynos_tmu_registers exynos4210_tmu_registers = {
|
||||
.triminfo_data = EXYNOS_TMU_REG_TRIMINFO,
|
||||
.triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT,
|
||||
.triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT,
|
||||
.tmu_ctrl = EXYNOS_TMU_REG_CONTROL,
|
||||
.buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT,
|
||||
.buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK,
|
||||
.buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT,
|
||||
.buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK,
|
||||
.core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT,
|
||||
.tmu_status = EXYNOS_TMU_REG_STATUS,
|
||||
.tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP,
|
||||
.threshold_temp = EXYNOS4210_TMU_REG_THRESHOLD_TEMP,
|
||||
.threshold_th0 = EXYNOS4210_TMU_REG_TRIG_LEVEL0,
|
||||
.tmu_inten = EXYNOS_TMU_REG_INTEN,
|
||||
.inten_rise_mask = EXYNOS4210_TMU_TRIG_LEVEL_MASK,
|
||||
.inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT,
|
||||
.inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT,
|
||||
.inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT,
|
||||
.inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT,
|
||||
.tmu_intstat = EXYNOS_TMU_REG_INTSTAT,
|
||||
.tmu_intclear = EXYNOS_TMU_REG_INTCLEAR,
|
||||
};
|
||||
|
||||
struct exynos_tmu_init_data const exynos4210_default_tmu_data = {
|
||||
.tmu_data = {
|
||||
{
|
||||
.threshold = 80,
|
||||
.trigger_levels[0] = 5,
|
||||
.trigger_levels[1] = 20,
|
||||
.trigger_levels[2] = 30,
|
||||
.trigger_enable[0] = true,
|
||||
.trigger_enable[1] = true,
|
||||
.trigger_enable[2] = true,
|
||||
.trigger_enable[3] = false,
|
||||
.trigger_type[0] = THROTTLE_ACTIVE,
|
||||
.trigger_type[1] = THROTTLE_ACTIVE,
|
||||
.trigger_type[2] = SW_TRIP,
|
||||
.max_trigger_level = 4,
|
||||
.gain = 15,
|
||||
.reference_voltage = 7,
|
||||
.cal_type = TYPE_ONE_POINT_TRIMMING,
|
||||
.min_efuse_value = 40,
|
||||
.max_efuse_value = 100,
|
||||
.first_point_trim = 25,
|
||||
.second_point_trim = 85,
|
||||
.default_temp_offset = 50,
|
||||
.freq_tab[0] = {
|
||||
.freq_clip_max = 800 * 1000,
|
||||
.temp_level = 85,
|
||||
},
|
||||
.freq_tab[1] = {
|
||||
.freq_clip_max = 200 * 1000,
|
||||
.temp_level = 100,
|
||||
},
|
||||
.freq_tab_count = 2,
|
||||
.type = SOC_ARCH_EXYNOS4210,
|
||||
.registers = &exynos4210_tmu_registers,
|
||||
.features = TMU_SUPPORT_READY_STATUS,
|
||||
},
|
||||
},
|
||||
.tmu_count = 1,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412)
|
||||
static const struct exynos_tmu_registers exynos5250_tmu_registers = {
|
||||
.triminfo_data = EXYNOS_TMU_REG_TRIMINFO,
|
||||
.triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT,
|
||||
.triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT,
|
||||
.triminfo_ctrl = EXYNOS_TMU_TRIMINFO_CON,
|
||||
.triminfo_reload_shift = EXYNOS_TRIMINFO_RELOAD_SHIFT,
|
||||
.tmu_ctrl = EXYNOS_TMU_REG_CONTROL,
|
||||
.buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT,
|
||||
.buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK,
|
||||
.therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT,
|
||||
.therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK,
|
||||
.therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT,
|
||||
.buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT,
|
||||
.buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK,
|
||||
.core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT,
|
||||
.tmu_status = EXYNOS_TMU_REG_STATUS,
|
||||
.tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP,
|
||||
.threshold_th0 = EXYNOS_THD_TEMP_RISE,
|
||||
.threshold_th1 = EXYNOS_THD_TEMP_FALL,
|
||||
.tmu_inten = EXYNOS_TMU_REG_INTEN,
|
||||
.inten_rise_mask = EXYNOS_TMU_RISE_INT_MASK,
|
||||
.inten_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT,
|
||||
.inten_fall_mask = EXYNOS_TMU_FALL_INT_MASK,
|
||||
.inten_fall_shift = EXYNOS_TMU_FALL_INT_SHIFT,
|
||||
.inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT,
|
||||
.inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT,
|
||||
.inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT,
|
||||
.inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT,
|
||||
.inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT,
|
||||
.tmu_intstat = EXYNOS_TMU_REG_INTSTAT,
|
||||
.tmu_intclear = EXYNOS_TMU_REG_INTCLEAR,
|
||||
.emul_con = EXYNOS_EMUL_CON,
|
||||
.emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT,
|
||||
.emul_time_shift = EXYNOS_EMUL_TIME_SHIFT,
|
||||
.emul_time_mask = EXYNOS_EMUL_TIME_MASK,
|
||||
};
|
||||
|
||||
#define EXYNOS5250_TMU_DATA \
|
||||
.threshold_falling = 10, \
|
||||
.trigger_levels[0] = 85, \
|
||||
.trigger_levels[1] = 103, \
|
||||
.trigger_levels[2] = 110, \
|
||||
.trigger_levels[3] = 120, \
|
||||
.trigger_enable[0] = true, \
|
||||
.trigger_enable[1] = true, \
|
||||
.trigger_enable[2] = true, \
|
||||
.trigger_enable[3] = false, \
|
||||
.trigger_type[0] = THROTTLE_ACTIVE, \
|
||||
.trigger_type[1] = THROTTLE_ACTIVE, \
|
||||
.trigger_type[2] = SW_TRIP, \
|
||||
.trigger_type[3] = HW_TRIP, \
|
||||
.max_trigger_level = 4, \
|
||||
.gain = 8, \
|
||||
.reference_voltage = 16, \
|
||||
.noise_cancel_mode = 4, \
|
||||
.cal_type = TYPE_ONE_POINT_TRIMMING, \
|
||||
.efuse_value = 55, \
|
||||
.min_efuse_value = 40, \
|
||||
.max_efuse_value = 100, \
|
||||
.first_point_trim = 25, \
|
||||
.second_point_trim = 85, \
|
||||
.default_temp_offset = 50, \
|
||||
.freq_tab[0] = { \
|
||||
.freq_clip_max = 800 * 1000, \
|
||||
.temp_level = 85, \
|
||||
}, \
|
||||
.freq_tab[1] = { \
|
||||
.freq_clip_max = 200 * 1000, \
|
||||
.temp_level = 103, \
|
||||
}, \
|
||||
.freq_tab_count = 2, \
|
||||
.type = SOC_ARCH_EXYNOS, \
|
||||
.registers = &exynos5250_tmu_registers, \
|
||||
.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \
|
||||
TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \
|
||||
TMU_SUPPORT_EMUL_TIME)
|
||||
|
||||
struct exynos_tmu_init_data const exynos5250_default_tmu_data = {
|
||||
.tmu_data = {
|
||||
{ EXYNOS5250_TMU_DATA },
|
||||
},
|
||||
.tmu_count = 1,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS5440)
|
||||
static const struct exynos_tmu_registers exynos5440_tmu_registers = {
|
||||
.triminfo_data = EXYNOS5440_TMU_S0_7_TRIM,
|
||||
.triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT,
|
||||
.triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT,
|
||||
.tmu_ctrl = EXYNOS5440_TMU_S0_7_CTRL,
|
||||
.buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT,
|
||||
.buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK,
|
||||
.therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT,
|
||||
.therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK,
|
||||
.therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT,
|
||||
.buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT,
|
||||
.buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK,
|
||||
.calib_mode_shift = EXYNOS_TMU_CALIB_MODE_SHIFT,
|
||||
.calib_mode_mask = EXYNOS_TMU_CALIB_MODE_MASK,
|
||||
.core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT,
|
||||
.tmu_status = EXYNOS5440_TMU_S0_7_STATUS,
|
||||
.tmu_cur_temp = EXYNOS5440_TMU_S0_7_TEMP,
|
||||
.threshold_th0 = EXYNOS5440_TMU_S0_7_TH0,
|
||||
.threshold_th1 = EXYNOS5440_TMU_S0_7_TH1,
|
||||
.threshold_th2 = EXYNOS5440_TMU_S0_7_TH2,
|
||||
.threshold_th3_l0_shift = EXYNOS5440_TMU_TH_RISE4_SHIFT,
|
||||
.tmu_inten = EXYNOS5440_TMU_S0_7_IRQEN,
|
||||
.inten_rise_mask = EXYNOS5440_TMU_RISE_INT_MASK,
|
||||
.inten_rise_shift = EXYNOS5440_TMU_RISE_INT_SHIFT,
|
||||
.inten_fall_mask = EXYNOS5440_TMU_FALL_INT_MASK,
|
||||
.inten_fall_shift = EXYNOS5440_TMU_FALL_INT_SHIFT,
|
||||
.inten_rise0_shift = EXYNOS5440_TMU_INTEN_RISE0_SHIFT,
|
||||
.inten_rise1_shift = EXYNOS5440_TMU_INTEN_RISE1_SHIFT,
|
||||
.inten_rise2_shift = EXYNOS5440_TMU_INTEN_RISE2_SHIFT,
|
||||
.inten_rise3_shift = EXYNOS5440_TMU_INTEN_RISE3_SHIFT,
|
||||
.inten_fall0_shift = EXYNOS5440_TMU_INTEN_FALL0_SHIFT,
|
||||
.tmu_intstat = EXYNOS5440_TMU_S0_7_IRQ,
|
||||
.tmu_intclear = EXYNOS5440_TMU_S0_7_IRQ,
|
||||
.tmu_irqstatus = EXYNOS5440_TMU_IRQ_STATUS,
|
||||
.emul_con = EXYNOS5440_TMU_S0_7_DEBUG,
|
||||
.emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT,
|
||||
.tmu_pmin = EXYNOS5440_TMU_PMIN,
|
||||
};
|
||||
|
||||
#define EXYNOS5440_TMU_DATA \
|
||||
.trigger_levels[0] = 100, \
|
||||
.trigger_levels[4] = 105, \
|
||||
.trigger_enable[0] = 1, \
|
||||
.trigger_type[0] = SW_TRIP, \
|
||||
.trigger_type[4] = HW_TRIP, \
|
||||
.max_trigger_level = 5, \
|
||||
.gain = 5, \
|
||||
.reference_voltage = 16, \
|
||||
.noise_cancel_mode = 4, \
|
||||
.cal_type = TYPE_ONE_POINT_TRIMMING, \
|
||||
.cal_mode = 0, \
|
||||
.efuse_value = 0x5b2d, \
|
||||
.min_efuse_value = 16, \
|
||||
.max_efuse_value = 76, \
|
||||
.first_point_trim = 25, \
|
||||
.second_point_trim = 70, \
|
||||
.default_temp_offset = 25, \
|
||||
.type = SOC_ARCH_EXYNOS5440, \
|
||||
.registers = &exynos5440_tmu_registers, \
|
||||
.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_FALLING_TRIP | \
|
||||
TMU_SUPPORT_MULTI_INST | TMU_SUPPORT_SHARED_MEMORY),
|
||||
|
||||
struct exynos_tmu_init_data const exynos5440_default_tmu_data = {
|
||||
.tmu_data = {
|
||||
{ EXYNOS5440_TMU_DATA } ,
|
||||
{ EXYNOS5440_TMU_DATA } ,
|
||||
{ EXYNOS5440_TMU_DATA } ,
|
||||
},
|
||||
.tmu_count = 3,
|
||||
};
|
||||
#endif
|
155
drivers/thermal/samsung/exynos_tmu_data.h
Normal file
155
drivers/thermal/samsung/exynos_tmu_data.h
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* exynos_tmu_data.h - Samsung EXYNOS tmu data header file
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics
|
||||
* Amit Daniel Kachhap <amit.daniel@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _EXYNOS_TMU_DATA_H
|
||||
#define _EXYNOS_TMU_DATA_H
|
||||
|
||||
/* Exynos generic registers */
|
||||
#define EXYNOS_TMU_REG_TRIMINFO 0x0
|
||||
#define EXYNOS_TMU_REG_CONTROL 0x20
|
||||
#define EXYNOS_TMU_REG_STATUS 0x28
|
||||
#define EXYNOS_TMU_REG_CURRENT_TEMP 0x40
|
||||
#define EXYNOS_TMU_REG_INTEN 0x70
|
||||
#define EXYNOS_TMU_REG_INTSTAT 0x74
|
||||
#define EXYNOS_TMU_REG_INTCLEAR 0x78
|
||||
|
||||
#define EXYNOS_TMU_TEMP_MASK 0xff
|
||||
#define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24
|
||||
#define EXYNOS_TMU_REF_VOLTAGE_MASK 0x1f
|
||||
#define EXYNOS_TMU_BUF_SLOPE_SEL_MASK 0xf
|
||||
#define EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT 8
|
||||
#define EXYNOS_TMU_CORE_EN_SHIFT 0
|
||||
|
||||
/* Exynos4210 specific registers */
|
||||
#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44
|
||||
#define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50
|
||||
#define EXYNOS4210_TMU_REG_TRIG_LEVEL1 0x54
|
||||
#define EXYNOS4210_TMU_REG_TRIG_LEVEL2 0x58
|
||||
#define EXYNOS4210_TMU_REG_TRIG_LEVEL3 0x5C
|
||||
#define EXYNOS4210_TMU_REG_PAST_TEMP0 0x60
|
||||
#define EXYNOS4210_TMU_REG_PAST_TEMP1 0x64
|
||||
#define EXYNOS4210_TMU_REG_PAST_TEMP2 0x68
|
||||
#define EXYNOS4210_TMU_REG_PAST_TEMP3 0x6C
|
||||
|
||||
#define EXYNOS4210_TMU_TRIG_LEVEL0_MASK 0x1
|
||||
#define EXYNOS4210_TMU_TRIG_LEVEL1_MASK 0x10
|
||||
#define EXYNOS4210_TMU_TRIG_LEVEL2_MASK 0x100
|
||||
#define EXYNOS4210_TMU_TRIG_LEVEL3_MASK 0x1000
|
||||
#define EXYNOS4210_TMU_TRIG_LEVEL_MASK 0x1111
|
||||
#define EXYNOS4210_TMU_INTCLEAR_VAL 0x1111
|
||||
|
||||
/* Exynos5250 and Exynos4412 specific registers */
|
||||
#define EXYNOS_TMU_TRIMINFO_CON 0x14
|
||||
#define EXYNOS_THD_TEMP_RISE 0x50
|
||||
#define EXYNOS_THD_TEMP_FALL 0x54
|
||||
#define EXYNOS_EMUL_CON 0x80
|
||||
|
||||
#define EXYNOS_TRIMINFO_RELOAD_SHIFT 1
|
||||
#define EXYNOS_TRIMINFO_25_SHIFT 0
|
||||
#define EXYNOS_TRIMINFO_85_SHIFT 8
|
||||
#define EXYNOS_TMU_RISE_INT_MASK 0x111
|
||||
#define EXYNOS_TMU_RISE_INT_SHIFT 0
|
||||
#define EXYNOS_TMU_FALL_INT_MASK 0x111
|
||||
#define EXYNOS_TMU_FALL_INT_SHIFT 12
|
||||
#define EXYNOS_TMU_CLEAR_RISE_INT 0x111
|
||||
#define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 12)
|
||||
#define EXYNOS_TMU_TRIP_MODE_SHIFT 13
|
||||
#define EXYNOS_TMU_TRIP_MODE_MASK 0x7
|
||||
#define EXYNOS_TMU_THERM_TRIP_EN_SHIFT 12
|
||||
#define EXYNOS_TMU_CALIB_MODE_SHIFT 4
|
||||
#define EXYNOS_TMU_CALIB_MODE_MASK 0x3
|
||||
|
||||
#define EXYNOS_TMU_INTEN_RISE0_SHIFT 0
|
||||
#define EXYNOS_TMU_INTEN_RISE1_SHIFT 4
|
||||
#define EXYNOS_TMU_INTEN_RISE2_SHIFT 8
|
||||
#define EXYNOS_TMU_INTEN_RISE3_SHIFT 12
|
||||
#define EXYNOS_TMU_INTEN_FALL0_SHIFT 16
|
||||
#define EXYNOS_TMU_INTEN_FALL1_SHIFT 20
|
||||
#define EXYNOS_TMU_INTEN_FALL2_SHIFT 24
|
||||
|
||||
#define EXYNOS_EMUL_TIME 0x57F0
|
||||
#define EXYNOS_EMUL_TIME_MASK 0xffff
|
||||
#define EXYNOS_EMUL_TIME_SHIFT 16
|
||||
#define EXYNOS_EMUL_DATA_SHIFT 8
|
||||
#define EXYNOS_EMUL_DATA_MASK 0xFF
|
||||
#define EXYNOS_EMUL_ENABLE 0x1
|
||||
|
||||
#define EXYNOS_MAX_TRIGGER_PER_REG 4
|
||||
|
||||
/*exynos5440 specific registers*/
|
||||
#define EXYNOS5440_TMU_S0_7_TRIM 0x000
|
||||
#define EXYNOS5440_TMU_S0_7_CTRL 0x020
|
||||
#define EXYNOS5440_TMU_S0_7_DEBUG 0x040
|
||||
#define EXYNOS5440_TMU_S0_7_STATUS 0x060
|
||||
#define EXYNOS5440_TMU_S0_7_TEMP 0x0f0
|
||||
#define EXYNOS5440_TMU_S0_7_TH0 0x110
|
||||
#define EXYNOS5440_TMU_S0_7_TH1 0x130
|
||||
#define EXYNOS5440_TMU_S0_7_TH2 0x150
|
||||
#define EXYNOS5440_TMU_S0_7_EVTEN 0x1F0
|
||||
#define EXYNOS5440_TMU_S0_7_IRQEN 0x210
|
||||
#define EXYNOS5440_TMU_S0_7_IRQ 0x230
|
||||
/* exynos5440 common registers */
|
||||
#define EXYNOS5440_TMU_IRQ_STATUS 0x000
|
||||
#define EXYNOS5440_TMU_PMIN 0x004
|
||||
#define EXYNOS5440_TMU_TEMP 0x008
|
||||
|
||||
#define EXYNOS5440_TMU_RISE_INT_MASK 0xf
|
||||
#define EXYNOS5440_TMU_RISE_INT_SHIFT 0
|
||||
#define EXYNOS5440_TMU_FALL_INT_MASK 0xf
|
||||
#define EXYNOS5440_TMU_FALL_INT_SHIFT 4
|
||||
#define EXYNOS5440_TMU_INTEN_RISE0_SHIFT 0
|
||||
#define EXYNOS5440_TMU_INTEN_RISE1_SHIFT 1
|
||||
#define EXYNOS5440_TMU_INTEN_RISE2_SHIFT 2
|
||||
#define EXYNOS5440_TMU_INTEN_RISE3_SHIFT 3
|
||||
#define EXYNOS5440_TMU_INTEN_FALL0_SHIFT 4
|
||||
#define EXYNOS5440_TMU_INTEN_FALL1_SHIFT 5
|
||||
#define EXYNOS5440_TMU_INTEN_FALL2_SHIFT 6
|
||||
#define EXYNOS5440_TMU_INTEN_FALL3_SHIFT 7
|
||||
#define EXYNOS5440_TMU_TH_RISE0_SHIFT 0
|
||||
#define EXYNOS5440_TMU_TH_RISE1_SHIFT 8
|
||||
#define EXYNOS5440_TMU_TH_RISE2_SHIFT 16
|
||||
#define EXYNOS5440_TMU_TH_RISE3_SHIFT 24
|
||||
#define EXYNOS5440_TMU_TH_RISE4_SHIFT 24
|
||||
#define EXYNOS5440_EFUSE_SWAP_OFFSET 8
|
||||
|
||||
#if defined(CONFIG_CPU_EXYNOS4210)
|
||||
extern struct exynos_tmu_init_data const exynos4210_default_tmu_data;
|
||||
#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS4210_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if (defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412))
|
||||
extern struct exynos_tmu_init_data const exynos5250_default_tmu_data;
|
||||
#define EXYNOS5250_TMU_DRV_DATA (&exynos5250_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS5250_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_EXYNOS5440)
|
||||
extern struct exynos_tmu_init_data const exynos5440_default_tmu_data;
|
||||
#define EXYNOS5440_TMU_DRV_DATA (&exynos5440_default_tmu_data)
|
||||
#else
|
||||
#define EXYNOS5440_TMU_DRV_DATA (NULL)
|
||||
#endif
|
||||
|
||||
#endif /*_EXYNOS_TMU_DATA_H*/
|
|
@ -51,44 +51,51 @@ static unsigned long get_target_state(struct thermal_instance *instance,
|
|||
{
|
||||
struct thermal_cooling_device *cdev = instance->cdev;
|
||||
unsigned long cur_state;
|
||||
unsigned long next_target;
|
||||
|
||||
/*
|
||||
* We keep this instance the way it is by default.
|
||||
* Otherwise, we use the current state of the
|
||||
* cdev in use to determine the next_target.
|
||||
*/
|
||||
cdev->ops->get_cur_state(cdev, &cur_state);
|
||||
next_target = instance->target;
|
||||
|
||||
switch (trend) {
|
||||
case THERMAL_TREND_RAISING:
|
||||
if (throttle) {
|
||||
cur_state = cur_state < instance->upper ?
|
||||
next_target = cur_state < instance->upper ?
|
||||
(cur_state + 1) : instance->upper;
|
||||
if (cur_state < instance->lower)
|
||||
cur_state = instance->lower;
|
||||
if (next_target < instance->lower)
|
||||
next_target = instance->lower;
|
||||
}
|
||||
break;
|
||||
case THERMAL_TREND_RAISE_FULL:
|
||||
if (throttle)
|
||||
cur_state = instance->upper;
|
||||
next_target = instance->upper;
|
||||
break;
|
||||
case THERMAL_TREND_DROPPING:
|
||||
if (cur_state == instance->lower) {
|
||||
if (!throttle)
|
||||
cur_state = -1;
|
||||
next_target = THERMAL_NO_TARGET;
|
||||
} else {
|
||||
cur_state -= 1;
|
||||
if (cur_state > instance->upper)
|
||||
cur_state = instance->upper;
|
||||
next_target = cur_state - 1;
|
||||
if (next_target > instance->upper)
|
||||
next_target = instance->upper;
|
||||
}
|
||||
break;
|
||||
case THERMAL_TREND_DROP_FULL:
|
||||
if (cur_state == instance->lower) {
|
||||
if (!throttle)
|
||||
cur_state = -1;
|
||||
next_target = THERMAL_NO_TARGET;
|
||||
} else
|
||||
cur_state = instance->lower;
|
||||
next_target = instance->lower;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return cur_state;
|
||||
return next_target;
|
||||
}
|
||||
|
||||
static void update_passive_instance(struct thermal_zone_device *tz,
|
||||
|
@ -133,6 +140,9 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
|
|||
old_target = instance->target;
|
||||
instance->target = get_target_state(instance, trend, throttle);
|
||||
|
||||
if (old_target == instance->target)
|
||||
continue;
|
||||
|
||||
/* Activate a passive thermal instance */
|
||||
if (old_target == THERMAL_NO_TARGET &&
|
||||
instance->target != THERMAL_NO_TARGET)
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <net/genetlink.h>
|
||||
|
||||
#include "thermal_core.h"
|
||||
#include "thermal_hwmon.h"
|
||||
|
||||
MODULE_AUTHOR("Zhang Rui");
|
||||
MODULE_DESCRIPTION("Generic thermal management sysfs support");
|
||||
|
@ -201,14 +202,23 @@ static void print_bind_err_msg(struct thermal_zone_device *tz,
|
|||
}
|
||||
|
||||
static void __bind(struct thermal_zone_device *tz, int mask,
|
||||
struct thermal_cooling_device *cdev)
|
||||
struct thermal_cooling_device *cdev,
|
||||
unsigned long *limits)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < tz->trips; i++) {
|
||||
if (mask & (1 << i)) {
|
||||
unsigned long upper, lower;
|
||||
|
||||
upper = THERMAL_NO_LIMIT;
|
||||
lower = THERMAL_NO_LIMIT;
|
||||
if (limits) {
|
||||
lower = limits[i * 2];
|
||||
upper = limits[i * 2 + 1];
|
||||
}
|
||||
ret = thermal_zone_bind_cooling_device(tz, i, cdev,
|
||||
THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
|
||||
upper, lower);
|
||||
if (ret)
|
||||
print_bind_err_msg(tz, cdev, ret);
|
||||
}
|
||||
|
@ -253,7 +263,8 @@ static void bind_cdev(struct thermal_cooling_device *cdev)
|
|||
if (tzp->tbp[i].match(pos, cdev))
|
||||
continue;
|
||||
tzp->tbp[i].cdev = cdev;
|
||||
__bind(pos, tzp->tbp[i].trip_mask, cdev);
|
||||
__bind(pos, tzp->tbp[i].trip_mask, cdev,
|
||||
tzp->tbp[i].binding_limits);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,7 +302,8 @@ static void bind_tz(struct thermal_zone_device *tz)
|
|||
if (tzp->tbp[i].match(tz, pos))
|
||||
continue;
|
||||
tzp->tbp[i].cdev = pos;
|
||||
__bind(tz, tzp->tbp[i].trip_mask, pos);
|
||||
__bind(tz, tzp->tbp[i].trip_mask, pos,
|
||||
tzp->tbp[i].binding_limits);
|
||||
}
|
||||
}
|
||||
exit:
|
||||
|
@ -859,260 +871,6 @@ thermal_cooling_device_trip_point_show(struct device *dev,
|
|||
|
||||
/* Device management */
|
||||
|
||||
#if defined(CONFIG_THERMAL_HWMON)
|
||||
|
||||
/* hwmon sys I/F */
|
||||
#include <linux/hwmon.h>
|
||||
|
||||
/* thermal zone devices with the same type share one hwmon device */
|
||||
struct thermal_hwmon_device {
|
||||
char type[THERMAL_NAME_LENGTH];
|
||||
struct device *device;
|
||||
int count;
|
||||
struct list_head tz_list;
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
struct thermal_hwmon_attr {
|
||||
struct device_attribute attr;
|
||||
char name[16];
|
||||
};
|
||||
|
||||
/* one temperature input for each thermal zone */
|
||||
struct thermal_hwmon_temp {
|
||||
struct list_head hwmon_node;
|
||||
struct thermal_zone_device *tz;
|
||||
struct thermal_hwmon_attr temp_input; /* hwmon sys attr */
|
||||
struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */
|
||||
};
|
||||
|
||||
static LIST_HEAD(thermal_hwmon_list);
|
||||
|
||||
static ssize_t
|
||||
name_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%s\n", hwmon->type);
|
||||
}
|
||||
static DEVICE_ATTR(name, 0444, name_show, NULL);
|
||||
|
||||
static ssize_t
|
||||
temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
long temperature;
|
||||
int ret;
|
||||
struct thermal_hwmon_attr *hwmon_attr
|
||||
= container_of(attr, struct thermal_hwmon_attr, attr);
|
||||
struct thermal_hwmon_temp *temp
|
||||
= container_of(hwmon_attr, struct thermal_hwmon_temp,
|
||||
temp_input);
|
||||
struct thermal_zone_device *tz = temp->tz;
|
||||
|
||||
ret = thermal_zone_get_temp(tz, &temperature);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%ld\n", temperature);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
temp_crit_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct thermal_hwmon_attr *hwmon_attr
|
||||
= container_of(attr, struct thermal_hwmon_attr, attr);
|
||||
struct thermal_hwmon_temp *temp
|
||||
= container_of(hwmon_attr, struct thermal_hwmon_temp,
|
||||
temp_crit);
|
||||
struct thermal_zone_device *tz = temp->tz;
|
||||
long temperature;
|
||||
int ret;
|
||||
|
||||
ret = tz->ops->get_trip_temp(tz, 0, &temperature);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%ld\n", temperature);
|
||||
}
|
||||
|
||||
|
||||
static struct thermal_hwmon_device *
|
||||
thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
|
||||
{
|
||||
struct thermal_hwmon_device *hwmon;
|
||||
|
||||
mutex_lock(&thermal_list_lock);
|
||||
list_for_each_entry(hwmon, &thermal_hwmon_list, node)
|
||||
if (!strcmp(hwmon->type, tz->type)) {
|
||||
mutex_unlock(&thermal_list_lock);
|
||||
return hwmon;
|
||||
}
|
||||
mutex_unlock(&thermal_list_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the temperature input matching a given thermal zone */
|
||||
static struct thermal_hwmon_temp *
|
||||
thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
|
||||
const struct thermal_zone_device *tz)
|
||||
{
|
||||
struct thermal_hwmon_temp *temp;
|
||||
|
||||
mutex_lock(&thermal_list_lock);
|
||||
list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
|
||||
if (temp->tz == tz) {
|
||||
mutex_unlock(&thermal_list_lock);
|
||||
return temp;
|
||||
}
|
||||
mutex_unlock(&thermal_list_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
{
|
||||
struct thermal_hwmon_device *hwmon;
|
||||
struct thermal_hwmon_temp *temp;
|
||||
int new_hwmon_device = 1;
|
||||
int result;
|
||||
|
||||
hwmon = thermal_hwmon_lookup_by_type(tz);
|
||||
if (hwmon) {
|
||||
new_hwmon_device = 0;
|
||||
goto register_sys_interface;
|
||||
}
|
||||
|
||||
hwmon = kzalloc(sizeof(struct thermal_hwmon_device), GFP_KERNEL);
|
||||
if (!hwmon)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&hwmon->tz_list);
|
||||
strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
|
||||
hwmon->device = hwmon_device_register(NULL);
|
||||
if (IS_ERR(hwmon->device)) {
|
||||
result = PTR_ERR(hwmon->device);
|
||||
goto free_mem;
|
||||
}
|
||||
dev_set_drvdata(hwmon->device, hwmon);
|
||||
result = device_create_file(hwmon->device, &dev_attr_name);
|
||||
if (result)
|
||||
goto free_mem;
|
||||
|
||||
register_sys_interface:
|
||||
temp = kzalloc(sizeof(struct thermal_hwmon_temp), GFP_KERNEL);
|
||||
if (!temp) {
|
||||
result = -ENOMEM;
|
||||
goto unregister_name;
|
||||
}
|
||||
|
||||
temp->tz = tz;
|
||||
hwmon->count++;
|
||||
|
||||
snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
|
||||
"temp%d_input", hwmon->count);
|
||||
temp->temp_input.attr.attr.name = temp->temp_input.name;
|
||||
temp->temp_input.attr.attr.mode = 0444;
|
||||
temp->temp_input.attr.show = temp_input_show;
|
||||
sysfs_attr_init(&temp->temp_input.attr.attr);
|
||||
result = device_create_file(hwmon->device, &temp->temp_input.attr);
|
||||
if (result)
|
||||
goto free_temp_mem;
|
||||
|
||||
if (tz->ops->get_crit_temp) {
|
||||
unsigned long temperature;
|
||||
if (!tz->ops->get_crit_temp(tz, &temperature)) {
|
||||
snprintf(temp->temp_crit.name,
|
||||
sizeof(temp->temp_crit.name),
|
||||
"temp%d_crit", hwmon->count);
|
||||
temp->temp_crit.attr.attr.name = temp->temp_crit.name;
|
||||
temp->temp_crit.attr.attr.mode = 0444;
|
||||
temp->temp_crit.attr.show = temp_crit_show;
|
||||
sysfs_attr_init(&temp->temp_crit.attr.attr);
|
||||
result = device_create_file(hwmon->device,
|
||||
&temp->temp_crit.attr);
|
||||
if (result)
|
||||
goto unregister_input;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&thermal_list_lock);
|
||||
if (new_hwmon_device)
|
||||
list_add_tail(&hwmon->node, &thermal_hwmon_list);
|
||||
list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
|
||||
mutex_unlock(&thermal_list_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_input:
|
||||
device_remove_file(hwmon->device, &temp->temp_input.attr);
|
||||
free_temp_mem:
|
||||
kfree(temp);
|
||||
unregister_name:
|
||||
if (new_hwmon_device) {
|
||||
device_remove_file(hwmon->device, &dev_attr_name);
|
||||
hwmon_device_unregister(hwmon->device);
|
||||
}
|
||||
free_mem:
|
||||
if (new_hwmon_device)
|
||||
kfree(hwmon);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
{
|
||||
struct thermal_hwmon_device *hwmon;
|
||||
struct thermal_hwmon_temp *temp;
|
||||
|
||||
hwmon = thermal_hwmon_lookup_by_type(tz);
|
||||
if (unlikely(!hwmon)) {
|
||||
/* Should never happen... */
|
||||
dev_dbg(&tz->device, "hwmon device lookup failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
temp = thermal_hwmon_lookup_temp(hwmon, tz);
|
||||
if (unlikely(!temp)) {
|
||||
/* Should never happen... */
|
||||
dev_dbg(&tz->device, "temperature input lookup failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
device_remove_file(hwmon->device, &temp->temp_input.attr);
|
||||
if (tz->ops->get_crit_temp)
|
||||
device_remove_file(hwmon->device, &temp->temp_crit.attr);
|
||||
|
||||
mutex_lock(&thermal_list_lock);
|
||||
list_del(&temp->hwmon_node);
|
||||
kfree(temp);
|
||||
if (!list_empty(&hwmon->tz_list)) {
|
||||
mutex_unlock(&thermal_list_lock);
|
||||
return;
|
||||
}
|
||||
list_del(&hwmon->node);
|
||||
mutex_unlock(&thermal_list_lock);
|
||||
|
||||
device_remove_file(hwmon->device, &dev_attr_name);
|
||||
hwmon_device_unregister(hwmon->device);
|
||||
kfree(hwmon);
|
||||
}
|
||||
#else
|
||||
static int
|
||||
thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone
|
||||
* @tz: pointer to struct thermal_zone_device
|
||||
|
@ -1715,9 +1473,11 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
|
|||
|
||||
mutex_unlock(&thermal_governor_lock);
|
||||
|
||||
result = thermal_add_hwmon_sysfs(tz);
|
||||
if (result)
|
||||
goto unregister;
|
||||
if (!tz->tzp || !tz->tzp->no_hwmon) {
|
||||
result = thermal_add_hwmon_sysfs(tz);
|
||||
if (result)
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
mutex_lock(&thermal_list_lock);
|
||||
list_add_tail(&tz->node, &thermal_tz_list);
|
||||
|
|
269
drivers/thermal/thermal_hwmon.c
Normal file
269
drivers/thermal/thermal_hwmon.c
Normal file
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* thermal_hwmon.c - Generic Thermal Management hwmon support.
|
||||
*
|
||||
* Code based on Intel thermal_core.c. Copyrights of the original code:
|
||||
* Copyright (C) 2008 Intel Corp
|
||||
* Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
|
||||
* Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
|
||||
*
|
||||
* Copyright (C) 2013 Texas Instruments
|
||||
* Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include "thermal_hwmon.h"
|
||||
|
||||
/* hwmon sys I/F */
|
||||
/* thermal zone devices with the same type share one hwmon device */
|
||||
struct thermal_hwmon_device {
|
||||
char type[THERMAL_NAME_LENGTH];
|
||||
struct device *device;
|
||||
int count;
|
||||
struct list_head tz_list;
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
struct thermal_hwmon_attr {
|
||||
struct device_attribute attr;
|
||||
char name[16];
|
||||
};
|
||||
|
||||
/* one temperature input for each thermal zone */
|
||||
struct thermal_hwmon_temp {
|
||||
struct list_head hwmon_node;
|
||||
struct thermal_zone_device *tz;
|
||||
struct thermal_hwmon_attr temp_input; /* hwmon sys attr */
|
||||
struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */
|
||||
};
|
||||
|
||||
static LIST_HEAD(thermal_hwmon_list);
|
||||
|
||||
static DEFINE_MUTEX(thermal_hwmon_list_lock);
|
||||
|
||||
static ssize_t
|
||||
name_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%s\n", hwmon->type);
|
||||
}
|
||||
static DEVICE_ATTR(name, 0444, name_show, NULL);
|
||||
|
||||
static ssize_t
|
||||
temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
long temperature;
|
||||
int ret;
|
||||
struct thermal_hwmon_attr *hwmon_attr
|
||||
= container_of(attr, struct thermal_hwmon_attr, attr);
|
||||
struct thermal_hwmon_temp *temp
|
||||
= container_of(hwmon_attr, struct thermal_hwmon_temp,
|
||||
temp_input);
|
||||
struct thermal_zone_device *tz = temp->tz;
|
||||
|
||||
ret = thermal_zone_get_temp(tz, &temperature);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%ld\n", temperature);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct thermal_hwmon_attr *hwmon_attr
|
||||
= container_of(attr, struct thermal_hwmon_attr, attr);
|
||||
struct thermal_hwmon_temp *temp
|
||||
= container_of(hwmon_attr, struct thermal_hwmon_temp,
|
||||
temp_crit);
|
||||
struct thermal_zone_device *tz = temp->tz;
|
||||
long temperature;
|
||||
int ret;
|
||||
|
||||
ret = tz->ops->get_trip_temp(tz, 0, &temperature);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%ld\n", temperature);
|
||||
}
|
||||
|
||||
|
||||
static struct thermal_hwmon_device *
|
||||
thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
|
||||
{
|
||||
struct thermal_hwmon_device *hwmon;
|
||||
|
||||
mutex_lock(&thermal_hwmon_list_lock);
|
||||
list_for_each_entry(hwmon, &thermal_hwmon_list, node)
|
||||
if (!strcmp(hwmon->type, tz->type)) {
|
||||
mutex_unlock(&thermal_hwmon_list_lock);
|
||||
return hwmon;
|
||||
}
|
||||
mutex_unlock(&thermal_hwmon_list_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the temperature input matching a given thermal zone */
|
||||
static struct thermal_hwmon_temp *
|
||||
thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
|
||||
const struct thermal_zone_device *tz)
|
||||
{
|
||||
struct thermal_hwmon_temp *temp;
|
||||
|
||||
mutex_lock(&thermal_hwmon_list_lock);
|
||||
list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
|
||||
if (temp->tz == tz) {
|
||||
mutex_unlock(&thermal_hwmon_list_lock);
|
||||
return temp;
|
||||
}
|
||||
mutex_unlock(&thermal_hwmon_list_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
{
|
||||
struct thermal_hwmon_device *hwmon;
|
||||
struct thermal_hwmon_temp *temp;
|
||||
int new_hwmon_device = 1;
|
||||
int result;
|
||||
|
||||
hwmon = thermal_hwmon_lookup_by_type(tz);
|
||||
if (hwmon) {
|
||||
new_hwmon_device = 0;
|
||||
goto register_sys_interface;
|
||||
}
|
||||
|
||||
hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
|
||||
if (!hwmon)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&hwmon->tz_list);
|
||||
strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
|
||||
hwmon->device = hwmon_device_register(&tz->device);
|
||||
if (IS_ERR(hwmon->device)) {
|
||||
result = PTR_ERR(hwmon->device);
|
||||
goto free_mem;
|
||||
}
|
||||
dev_set_drvdata(hwmon->device, hwmon);
|
||||
result = device_create_file(hwmon->device, &dev_attr_name);
|
||||
if (result)
|
||||
goto free_mem;
|
||||
|
||||
register_sys_interface:
|
||||
temp = kzalloc(sizeof(*temp), GFP_KERNEL);
|
||||
if (!temp) {
|
||||
result = -ENOMEM;
|
||||
goto unregister_name;
|
||||
}
|
||||
|
||||
temp->tz = tz;
|
||||
hwmon->count++;
|
||||
|
||||
snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
|
||||
"temp%d_input", hwmon->count);
|
||||
temp->temp_input.attr.attr.name = temp->temp_input.name;
|
||||
temp->temp_input.attr.attr.mode = 0444;
|
||||
temp->temp_input.attr.show = temp_input_show;
|
||||
sysfs_attr_init(&temp->temp_input.attr.attr);
|
||||
result = device_create_file(hwmon->device, &temp->temp_input.attr);
|
||||
if (result)
|
||||
goto free_temp_mem;
|
||||
|
||||
if (tz->ops->get_crit_temp) {
|
||||
unsigned long temperature;
|
||||
if (!tz->ops->get_crit_temp(tz, &temperature)) {
|
||||
snprintf(temp->temp_crit.name,
|
||||
sizeof(temp->temp_crit.name),
|
||||
"temp%d_crit", hwmon->count);
|
||||
temp->temp_crit.attr.attr.name = temp->temp_crit.name;
|
||||
temp->temp_crit.attr.attr.mode = 0444;
|
||||
temp->temp_crit.attr.show = temp_crit_show;
|
||||
sysfs_attr_init(&temp->temp_crit.attr.attr);
|
||||
result = device_create_file(hwmon->device,
|
||||
&temp->temp_crit.attr);
|
||||
if (result)
|
||||
goto unregister_input;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&thermal_hwmon_list_lock);
|
||||
if (new_hwmon_device)
|
||||
list_add_tail(&hwmon->node, &thermal_hwmon_list);
|
||||
list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
|
||||
mutex_unlock(&thermal_hwmon_list_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_input:
|
||||
device_remove_file(hwmon->device, &temp->temp_input.attr);
|
||||
free_temp_mem:
|
||||
kfree(temp);
|
||||
unregister_name:
|
||||
if (new_hwmon_device) {
|
||||
device_remove_file(hwmon->device, &dev_attr_name);
|
||||
hwmon_device_unregister(hwmon->device);
|
||||
}
|
||||
free_mem:
|
||||
if (new_hwmon_device)
|
||||
kfree(hwmon);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
{
|
||||
struct thermal_hwmon_device *hwmon;
|
||||
struct thermal_hwmon_temp *temp;
|
||||
|
||||
hwmon = thermal_hwmon_lookup_by_type(tz);
|
||||
if (unlikely(!hwmon)) {
|
||||
/* Should never happen... */
|
||||
dev_dbg(&tz->device, "hwmon device lookup failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
temp = thermal_hwmon_lookup_temp(hwmon, tz);
|
||||
if (unlikely(!temp)) {
|
||||
/* Should never happen... */
|
||||
dev_dbg(&tz->device, "temperature input lookup failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
device_remove_file(hwmon->device, &temp->temp_input.attr);
|
||||
if (tz->ops->get_crit_temp)
|
||||
device_remove_file(hwmon->device, &temp->temp_crit.attr);
|
||||
|
||||
mutex_lock(&thermal_hwmon_list_lock);
|
||||
list_del(&temp->hwmon_node);
|
||||
kfree(temp);
|
||||
if (!list_empty(&hwmon->tz_list)) {
|
||||
mutex_unlock(&thermal_hwmon_list_lock);
|
||||
return;
|
||||
}
|
||||
list_del(&hwmon->node);
|
||||
mutex_unlock(&thermal_hwmon_list_lock);
|
||||
|
||||
device_remove_file(hwmon->device, &dev_attr_name);
|
||||
hwmon_device_unregister(hwmon->device);
|
||||
kfree(hwmon);
|
||||
}
|
49
drivers/thermal/thermal_hwmon.h
Normal file
49
drivers/thermal/thermal_hwmon.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* thermal_hwmon.h - Generic Thermal Management hwmon support.
|
||||
*
|
||||
* Code based on Intel thermal_core.c. Copyrights of the original code:
|
||||
* Copyright (C) 2008 Intel Corp
|
||||
* Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
|
||||
* Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
|
||||
*
|
||||
* Copyright (C) 2013 Texas Instruments
|
||||
* Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
#ifndef __THERMAL_HWMON_H__
|
||||
#define __THERMAL_HWMON_H__
|
||||
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#ifdef CONFIG_THERMAL_HWMON
|
||||
int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz);
|
||||
void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz);
|
||||
#else
|
||||
static int
|
||||
thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __THERMAL_HWMON_H__ */
|
|
@ -42,6 +42,7 @@ dra752_core_temp_sensor_registers = {
|
|||
.mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_CORE_MASK,
|
||||
.mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_CORE_MASK,
|
||||
.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK,
|
||||
.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
|
||||
.mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_CORE_MASK,
|
||||
.mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_CORE_MASK,
|
||||
.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_CORE_MASK,
|
||||
|
@ -77,6 +78,7 @@ dra752_iva_temp_sensor_registers = {
|
|||
.mask_hot_mask = DRA752_BANDGAP_CTRL_2_MASK_HOT_IVA_MASK,
|
||||
.mask_cold_mask = DRA752_BANDGAP_CTRL_2_MASK_COLD_IVA_MASK,
|
||||
.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK,
|
||||
.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
|
||||
.mask_freeze_mask = DRA752_BANDGAP_CTRL_2_FREEZE_IVA_MASK,
|
||||
.mask_clear_mask = DRA752_BANDGAP_CTRL_2_CLEAR_IVA_MASK,
|
||||
.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_2_CLEAR_ACCUM_IVA_MASK,
|
||||
|
@ -112,6 +114,7 @@ dra752_mpu_temp_sensor_registers = {
|
|||
.mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_MPU_MASK,
|
||||
.mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_MPU_MASK,
|
||||
.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK,
|
||||
.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
|
||||
.mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_MPU_MASK,
|
||||
.mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_MPU_MASK,
|
||||
.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_MPU_MASK,
|
||||
|
@ -147,6 +150,7 @@ dra752_dspeve_temp_sensor_registers = {
|
|||
.mask_hot_mask = DRA752_BANDGAP_CTRL_2_MASK_HOT_DSPEVE_MASK,
|
||||
.mask_cold_mask = DRA752_BANDGAP_CTRL_2_MASK_COLD_DSPEVE_MASK,
|
||||
.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK,
|
||||
.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
|
||||
.mask_freeze_mask = DRA752_BANDGAP_CTRL_2_FREEZE_DSPEVE_MASK,
|
||||
.mask_clear_mask = DRA752_BANDGAP_CTRL_2_CLEAR_DSPEVE_MASK,
|
||||
.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_2_CLEAR_ACCUM_DSPEVE_MASK,
|
||||
|
@ -182,6 +186,7 @@ dra752_gpu_temp_sensor_registers = {
|
|||
.mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_GPU_MASK,
|
||||
.mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_GPU_MASK,
|
||||
.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK,
|
||||
.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
|
||||
.mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_GPU_MASK,
|
||||
.mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_GPU_MASK,
|
||||
.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_GPU_MASK,
|
||||
|
|
|
@ -1020,9 +1020,13 @@ int ti_bandgap_get_trend(struct ti_bandgap *bgp, int id, int *trend)
|
|||
|
||||
/* Fetch the update interval */
|
||||
ret = ti_bandgap_read_update_interval(bgp, id, &interval);
|
||||
if (ret || !interval)
|
||||
if (ret)
|
||||
goto unfreeze;
|
||||
|
||||
/* Set the interval to 1 ms if bandgap counter delay is not set */
|
||||
if (interval == 0)
|
||||
interval = 1;
|
||||
|
||||
*trend = (t1 - t2) / interval;
|
||||
|
||||
dev_dbg(bgp->dev, "The temperatures are t1 = %d and t2 = %d and trend =%d\n",
|
||||
|
|
|
@ -174,6 +174,9 @@ static int ti_thermal_set_mode(struct thermal_zone_device *thermal,
|
|||
enum thermal_device_mode mode)
|
||||
{
|
||||
struct ti_thermal_data *data = thermal->devdata;
|
||||
struct ti_bandgap *bgp;
|
||||
|
||||
bgp = data->bgp;
|
||||
|
||||
if (!data->ti_thermal) {
|
||||
dev_notice(&thermal->device, "thermal zone not registered\n");
|
||||
|
@ -190,6 +193,8 @@ static int ti_thermal_set_mode(struct thermal_zone_device *thermal,
|
|||
mutex_unlock(&data->ti_thermal->lock);
|
||||
|
||||
data->mode = mode;
|
||||
ti_bandgap_write_update_interval(bgp, data->sensor_id,
|
||||
data->ti_thermal->polling_delay);
|
||||
thermal_zone_device_update(data->ti_thermal);
|
||||
dev_dbg(&thermal->device, "thermal polling set for duration=%d msec\n",
|
||||
data->ti_thermal->polling_delay);
|
||||
|
@ -313,6 +318,8 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id,
|
|||
}
|
||||
data->ti_thermal->polling_delay = FAST_TEMP_MONITORING_RATE;
|
||||
ti_bandgap_set_sensor_data(bgp, id, data);
|
||||
ti_bandgap_write_update_interval(bgp, data->sensor_id,
|
||||
data->ti_thermal->polling_delay);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* exynos_thermal.h - Samsung EXYNOS TMU (Thermal Management Unit)
|
||||
*
|
||||
* Copyright (C) 2011 Samsung Electronics
|
||||
* Donggeun Kim <dg77.kim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_EXYNOS_THERMAL_H
|
||||
#define _LINUX_EXYNOS_THERMAL_H
|
||||
#include <linux/cpu_cooling.h>
|
||||
|
||||
enum calibration_type {
|
||||
TYPE_ONE_POINT_TRIMMING,
|
||||
TYPE_TWO_POINT_TRIMMING,
|
||||
TYPE_NONE,
|
||||
};
|
||||
|
||||
enum soc_type {
|
||||
SOC_ARCH_EXYNOS4210 = 1,
|
||||
SOC_ARCH_EXYNOS,
|
||||
};
|
||||
/**
|
||||
* struct freq_clip_table
|
||||
* @freq_clip_max: maximum frequency allowed for this cooling state.
|
||||
* @temp_level: Temperature level at which the temperature clipping will
|
||||
* happen.
|
||||
* @mask_val: cpumask of the allowed cpu's where the clipping will take place.
|
||||
*
|
||||
* This structure is required to be filled and passed to the
|
||||
* cpufreq_cooling_unregister function.
|
||||
*/
|
||||
struct freq_clip_table {
|
||||
unsigned int freq_clip_max;
|
||||
unsigned int temp_level;
|
||||
const struct cpumask *mask_val;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos_tmu_platform_data
|
||||
* @threshold: basic temperature for generating interrupt
|
||||
* 25 <= threshold <= 125 [unit: degree Celsius]
|
||||
* @threshold_falling: differntial value for setting threshold
|
||||
* of temperature falling interrupt.
|
||||
* @trigger_levels: array for each interrupt levels
|
||||
* [unit: degree Celsius]
|
||||
* 0: temperature for trigger_level0 interrupt
|
||||
* condition for trigger_level0 interrupt:
|
||||
* current temperature > threshold + trigger_levels[0]
|
||||
* 1: temperature for trigger_level1 interrupt
|
||||
* condition for trigger_level1 interrupt:
|
||||
* current temperature > threshold + trigger_levels[1]
|
||||
* 2: temperature for trigger_level2 interrupt
|
||||
* condition for trigger_level2 interrupt:
|
||||
* current temperature > threshold + trigger_levels[2]
|
||||
* 3: temperature for trigger_level3 interrupt
|
||||
* condition for trigger_level3 interrupt:
|
||||
* current temperature > threshold + trigger_levels[3]
|
||||
* @trigger_level0_en:
|
||||
* 1 = enable trigger_level0 interrupt,
|
||||
* 0 = disable trigger_level0 interrupt
|
||||
* @trigger_level1_en:
|
||||
* 1 = enable trigger_level1 interrupt,
|
||||
* 0 = disable trigger_level1 interrupt
|
||||
* @trigger_level2_en:
|
||||
* 1 = enable trigger_level2 interrupt,
|
||||
* 0 = disable trigger_level2 interrupt
|
||||
* @trigger_level3_en:
|
||||
* 1 = enable trigger_level3 interrupt,
|
||||
* 0 = disable trigger_level3 interrupt
|
||||
* @gain: gain of amplifier in the positive-TC generator block
|
||||
* 0 <= gain <= 15
|
||||
* @reference_voltage: reference voltage of amplifier
|
||||
* in the positive-TC generator block
|
||||
* 0 <= reference_voltage <= 31
|
||||
* @noise_cancel_mode: noise cancellation mode
|
||||
* 000, 100, 101, 110 and 111 can be different modes
|
||||
* @type: determines the type of SOC
|
||||
* @efuse_value: platform defined fuse value
|
||||
* @cal_type: calibration type for temperature
|
||||
* @freq_clip_table: Table representing frequency reduction percentage.
|
||||
* @freq_tab_count: Count of the above table as frequency reduction may
|
||||
* applicable to only some of the trigger levels.
|
||||
*
|
||||
* This structure is required for configuration of exynos_tmu driver.
|
||||
*/
|
||||
struct exynos_tmu_platform_data {
|
||||
u8 threshold;
|
||||
u8 threshold_falling;
|
||||
u8 trigger_levels[4];
|
||||
bool trigger_level0_en;
|
||||
bool trigger_level1_en;
|
||||
bool trigger_level2_en;
|
||||
bool trigger_level3_en;
|
||||
|
||||
u8 gain;
|
||||
u8 reference_voltage;
|
||||
u8 noise_cancel_mode;
|
||||
u32 efuse_value;
|
||||
|
||||
enum calibration_type cal_type;
|
||||
enum soc_type type;
|
||||
struct freq_clip_table freq_tab[4];
|
||||
unsigned int freq_tab_count;
|
||||
};
|
||||
#endif /* _LINUX_EXYNOS_THERMAL_H */
|
|
@ -207,6 +207,16 @@ struct thermal_bind_params {
|
|||
* See Documentation/thermal/sysfs-api.txt for more information.
|
||||
*/
|
||||
int trip_mask;
|
||||
|
||||
/*
|
||||
* This is an array of cooling state limits. Must have exactly
|
||||
* 2 * thermal_zone.number_of_trip_points. It is an array consisting
|
||||
* of tuples <lower-state upper-state> of state limits. Each trip
|
||||
* will be associated with one state limit tuple when binding.
|
||||
* A NULL pointer means <THERMAL_NO_LIMITS THERMAL_NO_LIMITS>
|
||||
* on all trips.
|
||||
*/
|
||||
unsigned long *binding_limits;
|
||||
int (*match) (struct thermal_zone_device *tz,
|
||||
struct thermal_cooling_device *cdev);
|
||||
};
|
||||
|
@ -214,6 +224,14 @@ struct thermal_bind_params {
|
|||
/* Structure to define Thermal Zone parameters */
|
||||
struct thermal_zone_params {
|
||||
char governor_name[THERMAL_NAME_LENGTH];
|
||||
|
||||
/*
|
||||
* a boolean to indicate if the thermal to hwmon sysfs interface
|
||||
* is required. when no_hwmon == false, a hwmon sysfs interface
|
||||
* will be created. when no_hwmon == true, nothing will be done
|
||||
*/
|
||||
bool no_hwmon;
|
||||
|
||||
int num_tbps; /* Number of tbp entries */
|
||||
struct thermal_bind_params *tbp;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue