Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux

Pull thermal management update from Zhang Rui:
 "Summary:

   - of-thermal extension to allow drivers to register and use its
     functionality in a better way, without exploiting thermal core.
     From Lukasz Majewski.

   - Fix a bug in intel_soc_dts_thermal driver which calls a sleep
     function in interrupt handler.  From Maurice Petallo.

   - add a thermal UAPI header file for exporting the thermal generic
     netlink information to user-space.  From Florian Fainelli.

   - First round of refactoring in Exynos driver.  Bartlomiej and Lukasz
     are attempting to make it lean and easier to understand.

   - New thermal driver for Rockchip (rk3288), with support for DT
     thermal.  From Caesar Wang.

   - New thermal driver for Nvidia, Tegra124 SOCTHERM driver, with
     support for DT thermal.  From Mikko Perttunen.

   - New cooling device, based on common clock framework.  From Eduardo
     Valentin.

   - a couple of small fixes in thermal core framework.  From Srinivas
     Pandruvada, Javi Merino, Luis Henriques.

   - Dropping Armada A375-Z1 SoC thermal support as the chip is not in
     the market, armada folks decided to drop its support.

   - a couple of small fixes and cleanups in int340x thermal driver"

* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: (58 commits)
  thermal: provide an UAPI header file
  Thermal/int340x: Clear the error value of the last acpi_bus_get_device() call
  thermal/powerclamp: add id for braswell cpu
  thermal: Intel SoC DTS: Don't do thermal zone update inside spin_lock
  Thermal: fix platform_no_drv_owner.cocci warnings
  Thermal/int340x: avoid unnecessary pointer casting
  thermal: int3403: Delete a check before thermal_zone_device_unregister()
  thermal/int3400: export uuids
  thermal: of: Extend current of-thermal.c code to allow setting emulated temp
  thermal: of: Extend of-thermal to export table of trip points
  thermal: of: Rename struct __thermal_trip to struct thermal_trip
  thermal: of: Extend of-thermal.c to provide check if trip point is valid
  thermal: of: Extend of-thermal.c to provide number of trip points
  thermal: Fix error path in thermal_init()
  thermal: lock the thermal zone when switching governors
  thermal: core: ignore invalid trip temperature
  thermal: armada: Remove support for A375-Z1 SoC
  thermal: rockchip: add driver for thermal
  dt-bindings: document Rockchip thermal
  thermal: exynos: remove exynos_tmu_data.h include
  ...
This commit is contained in:
Linus Torvalds 2014-12-17 10:16:27 -08:00
commit 2efda9042d
34 changed files with 2788 additions and 859 deletions

View File

@ -5,17 +5,9 @@ Required properties:
- compatible: Should be set to one of the following:
marvell,armada370-thermal
marvell,armada375-thermal
marvell,armada375-z1-thermal
marvell,armada380-thermal
marvell,armadaxp-thermal
Note: As the name suggests, "marvell,armada375-z1-thermal"
applies for the SoC Z1 stepping only. On such stepping
some quirks need to be done and the register offset differs
from the one in the A0 stepping.
The operating system may auto-detect the SoC stepping and
update the compatible and register offsets at runtime.
- reg: Device's register space.
Two entries are expected, see the examples below.
The first one is required for the sensor register;

View File

@ -0,0 +1,68 @@
* Temperature Sensor ADC (TSADC) on rockchip SoCs
Required properties:
- compatible : "rockchip,rk3288-tsadc"
- reg : physical base address of the controller and length of memory mapped
region.
- interrupts : The interrupt number to the cpu. The interrupt specifier format
depends on the interrupt controller.
- clocks : Must contain an entry for each entry in clock-names.
- clock-names : Shall be "tsadc" for the converter-clock, and "apb_pclk" for
the peripheral clock.
- resets : Must contain an entry for each entry in reset-names.
See ../reset/reset.txt for details.
- reset-names : Must include the name "tsadc-apb".
- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description.
- rockchip,hw-tshut-temp : The hardware-controlled shutdown temperature value.
- rockchip,hw-tshut-mode : The hardware-controlled shutdown mode 0:CRU 1:GPIO.
- rockchip,hw-tshut-polarity : The hardware-controlled active polarity 0:LOW
1:HIGH.
Exiample:
tsadc: tsadc@ff280000 {
compatible = "rockchip,rk3288-tsadc";
reg = <0xff280000 0x100>;
interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru SCLK_TSADC>, <&cru PCLK_TSADC>;
clock-names = "tsadc", "apb_pclk";
resets = <&cru SRST_TSADC>;
reset-names = "tsadc-apb";
pinctrl-names = "default";
pinctrl-0 = <&otp_out>;
#thermal-sensor-cells = <1>;
rockchip,hw-tshut-temp = <95000>;
rockchip,hw-tshut-mode = <0>;
rockchip,hw-tshut-polarity = <0>;
};
Example: referring to thermal sensors:
thermal-zones {
cpu_thermal: cpu_thermal {
polling-delay-passive = <1000>; /* milliseconds */
polling-delay = <5000>; /* milliseconds */
/* sensor ID */
thermal-sensors = <&tsadc 1>;
trips {
cpu_alert0: cpu_alert {
temperature = <70000>; /* millicelsius */
hysteresis = <2000>; /* millicelsius */
type = "passive";
};
cpu_crit: cpu_crit {
temperature = <90000>; /* millicelsius */
hysteresis = <2000>; /* millicelsius */
type = "critical";
};
};
cooling-maps {
map0 {
trip = <&cpu_alert0>;
cooling-device =
<&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
};
};
};
};

View File

@ -0,0 +1,53 @@
Tegra124 SOCTHERM thermal management system
The SOCTHERM IP block contains thermal sensors, support for polled
or interrupt-based thermal monitoring, CPU and GPU throttling based
on temperature trip points, and handling external overcurrent
notifications. It is also used to manage emergency shutdown in an
overheating situation.
Required properties :
- compatible : "nvidia,tegra124-soctherm".
- reg : Should contain 1 entry:
- SOCTHERM register set
- interrupts : Defines the interrupt used by SOCTHERM
- clocks : Must contain an entry for each entry in clock-names.
See ../clocks/clock-bindings.txt for details.
- clock-names : Must include the following entries:
- tsensor
- soctherm
- resets : Must contain an entry for each entry in reset-names.
See ../reset/reset.txt for details.
- reset-names : Must include the following entries:
- soctherm
- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description
of this property. See <dt-bindings/thermal/tegra124-soctherm.h> for a
list of valid values when referring to thermal sensors.
Example :
soctherm@0,700e2000 {
compatible = "nvidia,tegra124-soctherm";
reg = <0x0 0x700e2000 0x0 0x1000>;
interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA124_CLK_TSENSOR>,
<&tegra_car TEGRA124_CLK_SOC_THERM>;
clock-names = "tsensor", "soctherm";
resets = <&tegra_car 78>;
reset-names = "soctherm";
#thermal-sensor-cells = <1>;
};
Example: referring to thermal sensors :
thermal-zones {
cpu {
polling-delay-passive = <1000>;
polling-delay = <1000>;
thermal-sensors =
<&soctherm TEGRA124_SOCTHERM_SENSOR_CPU>;
};
};

View File

@ -9516,6 +9516,7 @@ Q: https://patchwork.kernel.org/project/linux-pm/list/
S: Supported
F: drivers/thermal/
F: include/linux/thermal.h
F: include/uapi/linux/thermal.h
F: include/linux/cpu_cooling.h
F: Documentation/devicetree/bindings/thermal/

View File

@ -1942,4 +1942,48 @@
<&tegra_car TEGRA124_CLK_EXTERN1>;
clock-names = "pll_a", "pll_a_out0", "mclk";
};
thermal-zones {
cpu {
trips {
trip@0 {
temperature = <101000>;
hysteresis = <0>;
type = "critical";
};
};
cooling-maps {
/* There are currently no cooling maps because there are no cooling devices */
};
};
mem {
trips {
trip@0 {
temperature = <101000>;
hysteresis = <0>;
type = "critical";
};
};
cooling-maps {
/* There are currently no cooling maps because there are no cooling devices */
};
};
gpu {
trips {
trip@0 {
temperature = <101000>;
hysteresis = <0>;
type = "critical";
};
};
cooling-maps {
/* There are currently no cooling maps because there are no cooling devices */
};
};
};
};

View File

@ -4,6 +4,7 @@
#include <dt-bindings/pinctrl/pinctrl-tegra.h>
#include <dt-bindings/pinctrl/pinctrl-tegra-xusb.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/thermal/tegra124-soctherm.h>
#include "skeleton.dtsi"
@ -657,6 +658,18 @@
status = "disabled";
};
soctherm: thermal-sensor@0,700e2000 {
compatible = "nvidia,tegra124-soctherm";
reg = <0x0 0x700e2000 0x0 0x1000>;
interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA124_CLK_TSENSOR>,
<&tegra_car TEGRA124_CLK_SOC_THERM>;
clock-names = "tsensor", "soctherm";
resets = <&tegra_car 78>;
reset-names = "soctherm";
#thermal-sensor-cells = <1>;
};
ahub@0,70300000 {
compatible = "nvidia,tegra124-ahub";
reg = <0x0 0x70300000 0x0 0x200>,
@ -898,6 +911,40 @@
};
};
thermal-zones {
cpu {
polling-delay-passive = <1000>;
polling-delay = <1000>;
thermal-sensors =
<&soctherm TEGRA124_SOCTHERM_SENSOR_CPU>;
};
mem {
polling-delay-passive = <1000>;
polling-delay = <1000>;
thermal-sensors =
<&soctherm TEGRA124_SOCTHERM_SENSOR_MEM>;
};
gpu {
polling-delay-passive = <1000>;
polling-delay = <1000>;
thermal-sensors =
<&soctherm TEGRA124_SOCTHERM_SENSOR_GPU>;
};
pllx {
polling-delay-passive = <1000>;
polling-delay = <1000>;
thermal-sensors =
<&soctherm TEGRA124_SOCTHERM_SENSOR_PLLX>;
};
};
timer {
compatible = "arm,armv7-timer";
interrupts = <GIC_PPI 13

View File

@ -177,6 +177,10 @@ static struct attribute *lm75_attrs[] = {
};
ATTRIBUTE_GROUPS(lm75);
static const struct thermal_zone_of_device_ops lm75_of_thermal_ops = {
.get_temp = lm75_read_temp,
};
/*-----------------------------------------------------------------------*/
/* device probe and removal */
@ -296,10 +300,9 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
if (IS_ERR(data->hwmon_dev))
return PTR_ERR(data->hwmon_dev);
data->tz = thermal_zone_of_sensor_register(data->hwmon_dev,
0,
data->tz = thermal_zone_of_sensor_register(data->hwmon_dev, 0,
data->hwmon_dev,
lm75_read_temp, NULL);
&lm75_of_thermal_ops);
if (IS_ERR(data->tz))
data->tz = NULL;

View File

@ -486,6 +486,10 @@ static const struct attribute_group ntc_attr_group = {
.attrs = ntc_attributes,
};
static const struct thermal_zone_of_device_ops ntc_of_thermal_ops = {
.get_temp = ntc_read_temp,
};
static int ntc_thermistor_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
@ -579,7 +583,7 @@ static int ntc_thermistor_probe(struct platform_device *pdev)
pdev_id->name);
data->tz = thermal_zone_of_sensor_register(data->dev, 0, data->dev,
ntc_read_temp, NULL);
&ntc_of_thermal_ops);
if (IS_ERR(data->tz)) {
dev_dbg(&pdev->dev, "Failed to register to thermal fw.\n");
data->tz = NULL;

View File

@ -158,6 +158,10 @@ ATTRIBUTE_GROUPS(tmp102);
#define TMP102_CONFIG (TMP102_CONF_TM | TMP102_CONF_EM | TMP102_CONF_CR1)
#define TMP102_CONFIG_RD_ONLY (TMP102_CONF_R0 | TMP102_CONF_R1 | TMP102_CONF_AL)
static const struct thermal_zone_of_device_ops tmp102_of_thermal_ops = {
.get_temp = tmp102_read_temp,
};
static int tmp102_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@ -215,7 +219,7 @@ static int tmp102_probe(struct i2c_client *client,
}
tmp102->hwmon_dev = hwmon_dev;
tmp102->tz = thermal_zone_of_sensor_register(hwmon_dev, 0, hwmon_dev,
tmp102_read_temp, NULL);
&tmp102_of_thermal_ops);
if (IS_ERR(tmp102->tz))
tmp102->tz = NULL;

View File

@ -112,6 +112,18 @@ config CPU_THERMAL
If you want this support, you should say Y here.
config CLOCK_THERMAL
bool "Generic clock cooling support"
depends on COMMON_CLK
depends on PM_OPP
help
This entry implements the generic clock cooling mechanism through
frequency clipping. Typically used to cool off co-processors. The
device that is configured to use this cooling mechanism will be
controlled to reduce clock frequency whenever temperature is high.
If you want this support, you should say Y here.
config THERMAL_EMULATION
bool "Thermal emulation mode support"
help
@ -143,6 +155,16 @@ config SPEAR_THERMAL
Enable this to plug the SPEAr thermal sensor driver into the Linux
thermal framework.
config ROCKCHIP_THERMAL
tristate "Rockchip thermal driver"
depends on ARCH_ROCKCHIP
depends on RESET_CONTROLLER
help
Rockchip thermal driver provides support for Temperature sensor
ADC (TS-ADC) found on Rockchip SoCs. It supports one critical
trip point. Cpufreq is used as the cooling device and will throttle
CPUs when the Temperature crosses the passive trip point.
config RCAR_THERMAL
tristate "Renesas R-Car thermal driver"
depends on ARCH_SHMOBILE || COMPILE_TEST
@ -185,6 +207,16 @@ config ARMADA_THERMAL
Enable this option if you want to have support for thermal management
controller present in Armada 370 and Armada XP SoC.
config TEGRA_SOCTHERM
tristate "Tegra SOCTHERM thermal management"
depends on ARCH_TEGRA
help
Enable this option for integrated thermal management support on NVIDIA
Tegra124 systems-on-chip. The driver supports four thermal zones
(CPU, GPU, MEM, PLLX). Cooling devices can be bound to the thermal
zones to manage temperatures. This option is also required for the
emergency thermal reset (thermtrip) feature to function.
config DB8500_CPUFREQ_COOLING
tristate "DB8500 cpufreq cooling"
depends on ARCH_U8500

View File

@ -18,8 +18,12 @@ thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
# cpufreq cooling
thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
# clock cooling
thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o
# platform thermal drivers
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o
obj-y += samsung/
@ -34,3 +38,4 @@ obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o
obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
obj-$(CONFIG_ST_THERMAL) += st/
obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o

View File

@ -35,10 +35,6 @@
#define PMU_TDC0_OTF_CAL_MASK (0x1 << 30)
#define PMU_TDC0_START_CAL_MASK (0x1 << 25)
#define A375_Z1_CAL_RESET_LSB 0x8011e214
#define A375_Z1_CAL_RESET_MSB 0x30a88019
#define A375_Z1_WORKAROUND_BIT BIT(9)
#define A375_UNIT_CONTROL_SHIFT 27
#define A375_UNIT_CONTROL_MASK 0x7
#define A375_READOUT_INVERT BIT(15)
@ -124,24 +120,12 @@ static void armada375_init_sensor(struct platform_device *pdev,
struct armada_thermal_priv *priv)
{
unsigned long reg;
bool quirk_needed =
!!of_device_is_compatible(pdev->dev.of_node,
"marvell,armada375-z1-thermal");
if (quirk_needed) {
/* Ensure these registers have the default (reset) values */
writel(A375_Z1_CAL_RESET_LSB, priv->control);
writel(A375_Z1_CAL_RESET_MSB, priv->control + 0x4);
}
reg = readl(priv->control + 4);
reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT);
reg &= ~A375_READOUT_INVERT;
reg &= ~A375_HW_RESETn;
if (quirk_needed)
reg |= A375_Z1_WORKAROUND_BIT;
writel(reg, priv->control + 4);
mdelay(20);
@ -259,10 +243,6 @@ static const struct of_device_id armada_thermal_id_table[] = {
.compatible = "marvell,armada375-thermal",
.data = &armada375_data,
},
{
.compatible = "marvell,armada375-z1-thermal",
.data = &armada375_data,
},
{
.compatible = "marvell,armada380-thermal",
.data = &armada380_data,

View File

@ -0,0 +1,485 @@
/*
* drivers/thermal/clock_cooling.c
*
* Copyright (C) 2014 Eduardo Valentin <edubezval@gmail.com>
*
* Copyright (C) 2013 Texas Instruments Inc.
* Contact: Eduardo Valentin <eduardo.valentin@ti.com>
*
* Highly based on cpu_cooling.c.
* Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
* Copyright (C) 2012 Amit Daniel <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; 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.
*/
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/idr.h>
#include <linux/mutex.h>
#include <linux/pm_opp.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include <linux/clock_cooling.h>
/**
* struct clock_cooling_device - data for cooling device with clock
* @id: unique integer value corresponding to each clock_cooling_device
* registered.
* @dev: struct device pointer to the device being used to cool off using
* clock frequencies.
* @cdev: thermal_cooling_device pointer to keep track of the
* registered cooling device.
* @clk_rate_change_nb: reference to notifier block used to receive clock
* rate changes.
* @freq_table: frequency table used to keep track of available frequencies.
* @clock_state: integer value representing the current state of clock
* cooling devices.
* @clock_val: integer value representing the absolute value of the clipped
* frequency.
* @clk: struct clk reference used to enforce clock limits.
* @lock: mutex lock to protect this struct.
*
* This structure is required for keeping information of each
* clock_cooling_device registered. In order to prevent corruption of this a
* mutex @lock is used.
*/
struct clock_cooling_device {
int id;
struct device *dev;
struct thermal_cooling_device *cdev;
struct notifier_block clk_rate_change_nb;
struct cpufreq_frequency_table *freq_table;
unsigned long clock_state;
unsigned long clock_val;
struct clk *clk;
struct mutex lock; /* lock to protect the content of this struct */
};
#define to_clock_cooling_device(x) \
container_of(x, struct clock_cooling_device, clk_rate_change_nb)
static DEFINE_IDR(clock_idr);
static DEFINE_MUTEX(cooling_clock_lock);
/**
* clock_cooling_get_idr - function to get an unique id.
* @id: int * value generated by this function.
*
* This function will populate @id with an unique
* id, using the idr API.
*
* Return: 0 on success, an error code on failure.
*/
static int clock_cooling_get_idr(int *id)
{
int ret;
mutex_lock(&cooling_clock_lock);
ret = idr_alloc(&clock_idr, NULL, 0, 0, GFP_KERNEL);
mutex_unlock(&cooling_clock_lock);
if (unlikely(ret < 0))
return ret;
*id = ret;
return 0;
}
/**
* release_idr - function to free the unique id.
* @id: int value representing the unique id.
*/
static void release_idr(int id)
{
mutex_lock(&cooling_clock_lock);
idr_remove(&clock_idr, id);
mutex_unlock(&cooling_clock_lock);
}
/* Below code defines functions to be used for clock as cooling device */
enum clock_cooling_property {
GET_LEVEL,
GET_FREQ,
GET_MAXL,
};
/**
* clock_cooling_get_property - fetch a property of interest for a give cpu.
* @ccdev: clock cooling device reference
* @input: query parameter
* @output: query return
* @property: type of query (frequency, level, max level)
*
* This is the common function to
* 1. get maximum clock cooling states
* 2. translate frequency to cooling state
* 3. translate cooling state to frequency
* Note that the code may be not in good shape
* but it is written in this way in order to:
* a) reduce duplicate code as most of the code can be shared.
* b) make sure the logic is consistent when translating between
* cooling states and frequencies.
*
* Return: 0 on success, -EINVAL when invalid parameters are passed.
*/
static int clock_cooling_get_property(struct clock_cooling_device *ccdev,
unsigned long input,
unsigned long *output,
enum clock_cooling_property property)
{
int i;
unsigned long max_level = 0, level = 0;
unsigned int freq = CPUFREQ_ENTRY_INVALID;
int descend = -1;
struct cpufreq_frequency_table *pos, *table = ccdev->freq_table;
if (!output)
return -EINVAL;
if (!table)
return -EINVAL;
cpufreq_for_each_valid_entry(pos, table) {
/* ignore duplicate entry */
if (freq == pos->frequency)
continue;
/* get the frequency order */
if (freq != CPUFREQ_ENTRY_INVALID && descend == -1)
descend = freq > pos->frequency;
freq = pos->frequency;
max_level++;
}
/* No valid cpu frequency entry */
if (max_level == 0)
return -EINVAL;
/* max_level is an index, not a counter */
max_level--;
/* get max level */
if (property == GET_MAXL) {
*output = max_level;
return 0;
}
if (property == GET_FREQ)
level = descend ? input : (max_level - input);
i = 0;
cpufreq_for_each_valid_entry(pos, table) {
/* ignore duplicate entry */
if (freq == pos->frequency)
continue;
/* now we have a valid frequency entry */
freq = pos->frequency;
if (property == GET_LEVEL && (unsigned int)input == freq) {
/* get level by frequency */
*output = descend ? i : (max_level - i);
return 0;
}
if (property == GET_FREQ && level == i) {
/* get frequency by level */
*output = freq;
return 0;
}
i++;
}
return -EINVAL;
}
/**
* clock_cooling_get_level - return the cooling level of given clock cooling.
* @cdev: reference of a thermal cooling device of used as clock cooling device
* @freq: the frequency of interest
*
* This function will match the cooling level corresponding to the
* requested @freq and return it.
*
* Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
* otherwise.
*/
unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev,
unsigned long freq)
{
struct clock_cooling_device *ccdev = cdev->devdata;
unsigned long val;
if (clock_cooling_get_property(ccdev, (unsigned long)freq, &val,
GET_LEVEL))
return THERMAL_CSTATE_INVALID;
return val;
}
EXPORT_SYMBOL_GPL(clock_cooling_get_level);
/**
* clock_cooling_get_frequency - get the absolute value of frequency from level.
* @ccdev: clock cooling device reference
* @level: cooling level
*
* This function matches cooling level with frequency. Based on a cooling level
* of frequency, equals cooling state of cpu cooling device, it will return
* the corresponding frequency.
* e.g level=0 --> 1st MAX FREQ, level=1 ---> 2nd MAX FREQ, .... etc
*
* Return: 0 on error, the corresponding frequency otherwise.
*/
static unsigned long
clock_cooling_get_frequency(struct clock_cooling_device *ccdev,
unsigned long level)
{
int ret = 0;
unsigned long freq;
ret = clock_cooling_get_property(ccdev, level, &freq, GET_FREQ);
if (ret)
return 0;
return freq;
}
/**
* clock_cooling_apply - function to apply frequency clipping.
* @ccdev: clock_cooling_device pointer containing frequency clipping data.
* @cooling_state: value of the cooling state.
*
* Function used to make sure the clock layer is aware of current thermal
* limits. The limits are applied by updating the clock rate in case it is
* higher than the corresponding frequency based on the requested cooling_state.
*
* Return: 0 on success, an error code otherwise (-EINVAL in case wrong
* cooling state).
*/
static int clock_cooling_apply(struct clock_cooling_device *ccdev,
unsigned long cooling_state)
{
unsigned long clip_freq, cur_freq;
int ret = 0;
/* Here we write the clipping */
/* Check if the old cooling action is same as new cooling action */
if (ccdev->clock_state == cooling_state)
return 0;
clip_freq = clock_cooling_get_frequency(ccdev, cooling_state);
if (!clip_freq)
return -EINVAL;
cur_freq = clk_get_rate(ccdev->clk);
mutex_lock(&ccdev->lock);
ccdev->clock_state = cooling_state;
ccdev->clock_val = clip_freq;
/* enforce clock level */
if (cur_freq > clip_freq)
ret = clk_set_rate(ccdev->clk, clip_freq);
mutex_unlock(&ccdev->lock);
return ret;
}
/**
* clock_cooling_clock_notifier - notifier callback on clock rate changes.
* @nb: struct notifier_block * with callback info.
* @event: value showing clock event for which this function invoked.
* @data: callback-specific data
*
* Callback to hijack the notification on clock transition.
* Every time there is a clock change, we intercept all pre change events
* and block the transition in case the new rate infringes thermal limits.
*
* Return: NOTIFY_DONE (success) or NOTIFY_BAD (new_rate > thermal limit).
*/
static int clock_cooling_clock_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct clk_notifier_data *ndata = data;
struct clock_cooling_device *ccdev = to_clock_cooling_device(nb);
switch (event) {
case PRE_RATE_CHANGE:
/*
* checks on current state
* TODO: current method is not best we can find as it
* allows possibly voltage transitions, in case DVFS
* layer is also hijacking clock pre notifications.
*/
if (ndata->new_rate > ccdev->clock_val)
return NOTIFY_BAD;
/* fall through */
case POST_RATE_CHANGE:
case ABORT_RATE_CHANGE:
default:
return NOTIFY_DONE;
}
}
/* clock cooling device thermal callback functions are defined below */
/**
* clock_cooling_get_max_state - callback function to get the max cooling state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the max cooling state.
*
* Callback for the thermal cooling device to return the clock
* max cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int clock_cooling_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct clock_cooling_device *ccdev = cdev->devdata;
unsigned long count = 0;
int ret;
ret = clock_cooling_get_property(ccdev, 0, &count, GET_MAXL);
if (!ret)
*state = count;
return ret;
}
/**
* clock_cooling_get_cur_state - function to get the current cooling state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the current cooling state.
*
* Callback for the thermal cooling device to return the clock
* current cooling state.
*
* Return: 0 (success)
*/
static int clock_cooling_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct clock_cooling_device *ccdev = cdev->devdata;
*state = ccdev->clock_state;
return 0;
}
/**
* clock_cooling_set_cur_state - function to set the current cooling state.
* @cdev: thermal cooling device pointer.
* @state: set this variable to the current cooling state.
*
* Callback for the thermal cooling device to change the clock cooling
* current cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int clock_cooling_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct clock_cooling_device *clock_device = cdev->devdata;
return clock_cooling_apply(clock_device, state);
}
/* Bind clock callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const clock_cooling_ops = {
.get_max_state = clock_cooling_get_max_state,
.get_cur_state = clock_cooling_get_cur_state,
.set_cur_state = clock_cooling_set_cur_state,
};
/**
* clock_cooling_register - function to create clock cooling device.
* @dev: struct device pointer to the device used as clock cooling device.
* @clock_name: string containing the clock used as cooling mechanism.
*
* This interface function registers the clock cooling device with the name
* "thermal-clock-%x". The cooling device is based on clock frequencies.
* The struct device is assumed to be capable of DVFS transitions.
* The OPP layer is used to fetch and fill the available frequencies for
* the referred device. The ordered frequency table is used to control
* the clock cooling device cooling states and to limit clock transitions
* based on the cooling state requested by the thermal framework.
*
* Return: a valid struct thermal_cooling_device pointer on success,
* on failure, it returns a corresponding ERR_PTR().
*/
struct thermal_cooling_device *
clock_cooling_register(struct device *dev, const char *clock_name)
{
struct thermal_cooling_device *cdev;
struct clock_cooling_device *ccdev = NULL;
char dev_name[THERMAL_NAME_LENGTH];
int ret = 0;
ccdev = devm_kzalloc(dev, sizeof(*ccdev), GFP_KERNEL);
if (!ccdev)
return ERR_PTR(-ENOMEM);
ccdev->dev = dev;
ccdev->clk = devm_clk_get(dev, clock_name);
if (IS_ERR(ccdev->clk))
return ERR_CAST(ccdev->clk);
ret = clock_cooling_get_idr(&ccdev->id);
if (ret)
return ERR_PTR(-EINVAL);
snprintf(dev_name, sizeof(dev_name), "thermal-clock-%d", ccdev->id);
cdev = thermal_cooling_device_register(dev_name, ccdev,
&clock_cooling_ops);
if (IS_ERR(cdev)) {
release_idr(ccdev->id);
return ERR_PTR(-EINVAL);
}
ccdev->cdev = cdev;
ccdev->clk_rate_change_nb.notifier_call = clock_cooling_clock_notifier;
/* Assuming someone has already filled the opp table for this device */
ret = dev_pm_opp_init_cpufreq_table(dev, &ccdev->freq_table);
if (ret) {
release_idr(ccdev->id);
return ERR_PTR(ret);
}
ccdev->clock_state = 0;
ccdev->clock_val = clock_cooling_get_frequency(ccdev, 0);
clk_notifier_register(ccdev->clk, &ccdev->clk_rate_change_nb);
return cdev;
}
EXPORT_SYMBOL_GPL(clock_cooling_register);
/**
* clock_cooling_unregister - function to remove clock cooling device.
* @cdev: thermal cooling device pointer.
*
* This interface function unregisters the "thermal-clock-%x" cooling device.
*/
void clock_cooling_unregister(struct thermal_cooling_device *cdev)
{
struct clock_cooling_device *ccdev;
if (!cdev)
return;
ccdev = cdev->devdata;
clk_notifier_unregister(ccdev->clk, &ccdev->clk_rate_change_nb);
dev_pm_opp_free_cpufreq_table(ccdev->dev, &ccdev->freq_table);
thermal_cooling_device_unregister(ccdev->cdev);
release_idr(ccdev->id);
}
EXPORT_SYMBOL_GPL(clock_cooling_unregister);

View File

@ -131,6 +131,8 @@ int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp,
pr_warn("Failed to get target ACPI device\n");
}
result = 0;
*trtp = trts;
/* don't count bad entries */
*trt_count -= nr_bad_entries;
@ -317,21 +319,21 @@ static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd,
{
int ret = 0;
unsigned long length = 0;
unsigned long count = 0;
int count = 0;
char __user *arg = (void __user *)__arg;
struct trt *trts;
struct art *arts;
switch (cmd) {
case ACPI_THERMAL_GET_TRT_COUNT:
ret = acpi_parse_trt(acpi_thermal_rel_handle, (int *)&count,
ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,
&trts, false);
kfree(trts);
if (!ret)
return put_user(count, (unsigned long __user *)__arg);
return ret;
case ACPI_THERMAL_GET_TRT_LEN:
ret = acpi_parse_trt(acpi_thermal_rel_handle, (int *)&count,
ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,
&trts, false);
kfree(trts);
length = count * sizeof(union trt_object);
@ -341,14 +343,14 @@ static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd,
case ACPI_THERMAL_GET_TRT:
return fill_trt(arg);
case ACPI_THERMAL_GET_ART_COUNT:
ret = acpi_parse_art(acpi_thermal_rel_handle, (int *)&count,
ret = acpi_parse_art(acpi_thermal_rel_handle, &count,
&arts, false);
kfree(arts);
if (!ret)
return put_user(count, (unsigned long __user *)__arg);
return ret;
case ACPI_THERMAL_GET_ART_LEN:
ret = acpi_parse_art(acpi_thermal_rel_handle, (int *)&count,
ret = acpi_parse_art(acpi_thermal_rel_handle, &count,
&arts, false);
kfree(arts);
length = count * sizeof(union art_object);

View File

@ -43,6 +43,74 @@ struct int3400_thermal_priv {
struct trt *trts;
u8 uuid_bitmap;
int rel_misc_dev_res;
int current_uuid_index;
};
static ssize_t available_uuids_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
int i;
int length = 0;
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
if (priv->uuid_bitmap & (1 << i))
if (PAGE_SIZE - length > 0)
length += snprintf(&buf[length],
PAGE_SIZE - length,
"%s\n",
int3400_thermal_uuids[i]);
}
return length;
}
static ssize_t current_uuid_show(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
if (priv->uuid_bitmap & (1 << priv->current_uuid_index))
return sprintf(buf, "%s\n",
int3400_thermal_uuids[priv->current_uuid_index]);
else
return sprintf(buf, "INVALID\n");
}
static ssize_t current_uuid_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
int i;
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
if ((priv->uuid_bitmap & (1 << i)) &&
!(strncmp(buf, int3400_thermal_uuids[i],
sizeof(int3400_thermal_uuids[i]) - 1))) {
priv->current_uuid_index = i;
return count;
}
}
return -EINVAL;
}
static DEVICE_ATTR(current_uuid, 0644, current_uuid_show, current_uuid_store);
static DEVICE_ATTR_RO(available_uuids);
static struct attribute *uuid_attrs[] = {
&dev_attr_available_uuids.attr,
&dev_attr_current_uuid.attr,
NULL
};
static struct attribute_group uuid_attribute_group = {
.attrs = uuid_attrs,
.name = "uuids"
};
static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)
@ -160,9 +228,9 @@ static int int3400_thermal_set_mode(struct thermal_zone_device *thermal,
if (enable != priv->mode) {
priv->mode = enable;
/* currently, only PASSIVE COOLING is supported */
result = int3400_thermal_run_osc(priv->adev->handle,
INT3400_THERMAL_PASSIVE_1, enable);
priv->current_uuid_index,
enable);
}
return result;
}
@ -223,7 +291,14 @@ static int int3400_thermal_probe(struct platform_device *pdev)
priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
priv->adev->handle);
result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);
if (result)
goto free_zone;
return 0;
free_zone:
thermal_zone_device_unregister(priv->thermal);
free_trt:
kfree(priv->trts);
free_art:
@ -240,6 +315,7 @@ static int int3400_thermal_remove(struct platform_device *pdev)
if (!priv->rel_misc_dev_res)
acpi_thermal_rel_misc_device_remove(priv->adev->handle);
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
thermal_zone_device_unregister(priv->thermal);
kfree(priv->trts);
kfree(priv->arts);

View File

@ -293,8 +293,7 @@ static int int3403_sensor_add(struct int3403_priv *priv)
return 0;
err_free_obj:
if (obj->tzone)
thermal_zone_device_unregister(obj->tzone);
thermal_zone_device_unregister(obj->tzone);
return result;
}
@ -471,7 +470,6 @@ static struct platform_driver int3403_driver = {
.remove = int3403_remove,
.driver = {
.name = "int3403 thermal",
.owner = THIS_MODULE,
.acpi_match_table = int3403_device_ids,
},
};

View File

@ -689,6 +689,7 @@ static const struct x86_cpu_id intel_powerclamp_ids[] = {
{ X86_VENDOR_INTEL, 6, 0x3f},
{ X86_VENDOR_INTEL, 6, 0x45},
{ X86_VENDOR_INTEL, 6, 0x46},
{ X86_VENDOR_INTEL, 6, 0x4c},
{}
};
MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);

View File

@ -360,6 +360,9 @@ static void proc_thermal_interrupt(void)
u32 sticky_out;
int status;
u32 ptmc_out;
unsigned long flags;
spin_lock_irqsave(&intr_notify_lock, flags);
/* Clear APIC interrupt */
status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
@ -378,21 +381,20 @@ static void proc_thermal_interrupt(void)
/* reset sticky bit */
status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
SOC_DTS_OFFSET_PTTSS, sticky_out);
spin_unlock_irqrestore(&intr_notify_lock, flags);
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
pr_debug("TZD update for zone %d\n", i);
thermal_zone_device_update(soc_dts[i]->tzone);
}
}
} else
spin_unlock_irqrestore(&intr_notify_lock, flags);
}
static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data)
{
unsigned long flags;
spin_lock_irqsave(&intr_notify_lock, flags);
proc_thermal_interrupt();
spin_unlock_irqrestore(&intr_notify_lock, flags);
pr_debug("proc_thermal_interrupt\n");
return IRQ_HANDLED;

View File

@ -30,26 +30,12 @@
#include <linux/err.h>
#include <linux/export.h>
#include <linux/string.h>
#include <linux/thermal.h>
#include "thermal_core.h"
/*** Private data structures to represent thermal device tree data ***/
/**
* struct __thermal_trip - representation of a point in temperature domain
* @np: pointer to struct device_node that this trip point was created from
* @temperature: temperature value in miliCelsius
* @hysteresis: relative hysteresis in miliCelsius
* @type: trip point type
*/
struct __thermal_trip {
struct device_node *np;
unsigned long int temperature;
unsigned long int hysteresis;
enum thermal_trip_type type;
};
/**
* struct __thermal_bind_param - a match between trip and cooling device
* @cooling_device: a pointer to identify the referred cooling device
@ -77,8 +63,7 @@ struct __thermal_bind_params {
* @num_tbps: number of thermal bind params
* @tbps: an array of thermal bind params (0..num_tbps - 1)
* @sensor_data: sensor private data used while reading temperature and trend
* @get_temp: sensor callback to read temperature
* @get_trend: sensor callback to read temperature trend
* @ops: set of callbacks to handle the thermal zone based on DT
*/
struct __thermal_zone {
@ -88,7 +73,7 @@ struct __thermal_zone {
/* trip data */
int ntrips;
struct __thermal_trip *trips;
struct thermal_trip *trips;
/* cooling binding data */
int num_tbps;
@ -96,8 +81,7 @@ struct __thermal_zone {
/* sensor interface */
void *sensor_data;
int (*get_temp)(void *, long *);
int (*get_trend)(void *, long *);
const struct thermal_zone_of_device_ops *ops;
};
/*** DT thermal zone device callbacks ***/
@ -107,10 +91,96 @@ static int of_thermal_get_temp(struct thermal_zone_device *tz,
{
struct __thermal_zone *data = tz->devdata;
if (!data->get_temp)
if (!data->ops->get_temp)
return -EINVAL;
return data->get_temp(data->sensor_data, temp);
return data->ops->get_temp(data->sensor_data, temp);
}
/**
* of_thermal_get_ntrips - function to export number of available trip
* points.
* @tz: pointer to a thermal zone
*
* This function is a globally visible wrapper to get number of trip points
* stored in the local struct __thermal_zone
*
* Return: number of available trip points, -ENODEV when data not available
*/
int of_thermal_get_ntrips(struct thermal_zone_device *tz)
{
struct __thermal_zone *data = tz->devdata;
if (!data || IS_ERR(data))
return -ENODEV;
return data->ntrips;
}
EXPORT_SYMBOL_GPL(of_thermal_get_ntrips);
/**
* of_thermal_is_trip_valid - function to check if trip point is valid
*
* @tz: pointer to a thermal zone
* @trip: trip point to evaluate
*
* This function is responsible for checking if passed trip point is valid
*
* Return: true if trip point is valid, false otherwise
*/
bool of_thermal_is_trip_valid(struct thermal_zone_device *tz, int trip)
{
struct __thermal_zone *data = tz->devdata;
if (!data || trip >= data->ntrips || trip < 0)
return false;
return true;
}
EXPORT_SYMBOL_GPL(of_thermal_is_trip_valid);
/**
* of_thermal_get_trip_points - function to get access to a globally exported
* trip points
*
* @tz: pointer to a thermal zone
*
* This function provides a pointer to trip points table
*
* Return: pointer to trip points table, NULL otherwise
*/
const struct thermal_trip * const
of_thermal_get_trip_points(struct thermal_zone_device *tz)
{
struct __thermal_zone *data = tz->devdata;
if (!data)
return NULL;
return data->trips;
}
EXPORT_SYMBOL_GPL(of_thermal_get_trip_points);
/**
* of_thermal_set_emul_temp - function to set emulated temperature
*
* @tz: pointer to a thermal zone
* @temp: temperature to set
*
* This function gives the ability to set emulated value of temperature,
* which is handy for debugging
*
* Return: zero on success, error code otherwise
*/
static int of_thermal_set_emul_temp(struct thermal_zone_device *tz,
unsigned long temp)
{
struct __thermal_zone *data = tz->devdata;
if (!data->ops || !data->ops->set_emul_temp)
return -EINVAL;
return data->ops->set_emul_temp(data->sensor_data, temp);
}
static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip,
@ -120,10 +190,10 @@ static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip,
long dev_trend;
int r;
if (!data->get_trend)
if (!data->ops->get_trend)
return -EINVAL;
r = data->get_trend(data->sensor_data, &dev_trend);
r = data->ops->get_trend(data->sensor_data, &dev_trend);
if (r)
return r;
@ -324,8 +394,7 @@ static struct thermal_zone_device_ops of_thermal_ops = {
static struct thermal_zone_device *
thermal_zone_of_add_sensor(struct device_node *zone,
struct device_node *sensor, void *data,
int (*get_temp)(void *, long *),
int (*get_trend)(void *, long *))
const struct thermal_zone_of_device_ops *ops)
{
struct thermal_zone_device *tzd;
struct __thermal_zone *tz;
@ -336,13 +405,16 @@ thermal_zone_of_add_sensor(struct device_node *zone,
tz = tzd->devdata;
if (!ops)
return ERR_PTR(-EINVAL);
mutex_lock(&tzd->lock);
tz->get_temp = get_temp;
tz->get_trend = get_trend;
tz->ops = ops;
tz->sensor_data = data;
tzd->ops->get_temp = of_thermal_get_temp;
tzd->ops->get_trend = of_thermal_get_trend;
tzd->ops->set_emul_temp = of_thermal_set_emul_temp;
mutex_unlock(&tzd->lock);
return tzd;
@ -356,8 +428,7 @@ thermal_zone_of_add_sensor(struct device_node *zone,
* than one sensors
* @data: a private pointer (owned by the caller) that will be passed
* back, when a temperature reading is needed.
* @get_temp: a pointer to a function that reads the sensor temperature.
* @get_trend: a pointer to a function that reads the sensor temperature trend.
* @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp.
*
* This function will search the list of thermal zones described in device
* tree and look for the zone that refer to the sensor device pointed by
@ -382,9 +453,8 @@ thermal_zone_of_add_sensor(struct device_node *zone,
* check the return value with help of IS_ERR() helper.
*/
struct thermal_zone_device *
thermal_zone_of_sensor_register(struct device *dev, int sensor_id,
void *data, int (*get_temp)(void *, long *),
int (*get_trend)(void *, long *))
thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data,
const struct thermal_zone_of_device_ops *ops)
{
struct device_node *np, *child, *sensor_np;
struct thermal_zone_device *tzd = ERR_PTR(-ENODEV);
@ -426,9 +496,7 @@ thermal_zone_of_sensor_register(struct device *dev, int sensor_id,
if (sensor_specs.np == sensor_np && id == sensor_id) {
tzd = thermal_zone_of_add_sensor(child, sensor_np,
data,
get_temp,
get_trend);
data, ops);
of_node_put(sensor_specs.np);
of_node_put(child);
goto exit;
@ -475,9 +543,9 @@ void thermal_zone_of_sensor_unregister(struct device *dev,
mutex_lock(&tzd->lock);
tzd->ops->get_temp = NULL;
tzd->ops->get_trend = NULL;
tzd->ops->set_emul_temp = NULL;
tz->get_temp = NULL;
tz->get_trend = NULL;
tz->ops = NULL;
tz->sensor_data = NULL;
mutex_unlock(&tzd->lock);
}
@ -501,7 +569,7 @@ EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister);
*/
static int thermal_of_populate_bind_params(struct device_node *np,
struct __thermal_bind_params *__tbp,
struct __thermal_trip *trips,
struct thermal_trip *trips,
int ntrips)
{
struct of_phandle_args cooling_spec;
@ -604,7 +672,7 @@ static int thermal_of_get_trip_type(struct device_node *np,
* Return: 0 on success, proper error code otherwise
*/
static int thermal_of_populate_trip(struct device_node *np,
struct __thermal_trip *trip)
struct thermal_trip *trip)
{
int prop;
int ret;

View File

@ -0,0 +1,693 @@
/*
* Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.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/reset.h>
#include <linux/thermal.h>
/**
* If the temperature over a period of time High,
* the resulting TSHUT gave CRU module,let it reset the entire chip,
* or via GPIO give PMIC.
*/
enum tshut_mode {
TSHUT_MODE_CRU = 0,
TSHUT_MODE_GPIO,
};
/**
* the system Temperature Sensors tshut(tshut) polarity
* the bit 8 is tshut polarity.
* 0: low active, 1: high active
*/
enum tshut_polarity {
TSHUT_LOW_ACTIVE = 0,
TSHUT_HIGH_ACTIVE,
};
/**
* The system has three Temperature Sensors. channel 0 is reserved,
* channel 1 is for CPU, and channel 2 is for GPU.
*/
enum sensor_id {
SENSOR_CPU = 1,
SENSOR_GPU,
};
struct rockchip_tsadc_chip {
/* The hardware-controlled tshut property */
long tshut_temp;
enum tshut_mode tshut_mode;
enum tshut_polarity tshut_polarity;
/* Chip-wide methods */
void (*initialize)(void __iomem *reg, enum tshut_polarity p);
void (*irq_ack)(void __iomem *reg);
void (*control)(void __iomem *reg, bool on);
/* Per-sensor methods */
int (*get_temp)(int chn, void __iomem *reg, long *temp);
void (*set_tshut_temp)(int chn, void __iomem *reg, long temp);
void (*set_tshut_mode)(int chn, void __iomem *reg, enum tshut_mode m);
};
struct rockchip_thermal_sensor {
struct rockchip_thermal_data *thermal;
struct thermal_zone_device *tzd;
enum sensor_id id;
};
#define NUM_SENSORS 2 /* Ignore unused sensor 0 */
struct rockchip_thermal_data {
const struct rockchip_tsadc_chip *chip;
struct platform_device *pdev;
struct reset_control *reset;
struct rockchip_thermal_sensor sensors[NUM_SENSORS];
struct clk *clk;
struct clk *pclk;
void __iomem *regs;
long tshut_temp;
enum tshut_mode tshut_mode;
enum tshut_polarity tshut_polarity;
};
/* TSADC V2 Sensor info define: */
#define TSADCV2_AUTO_CON 0x04
#define TSADCV2_INT_EN 0x08
#define TSADCV2_INT_PD 0x0c
#define TSADCV2_DATA(chn) (0x20 + (chn) * 0x04)
#define TSADCV2_COMP_SHUT(chn) (0x40 + (chn) * 0x04)
#define TSADCV2_HIGHT_INT_DEBOUNCE 0x60
#define TSADCV2_HIGHT_TSHUT_DEBOUNCE 0x64
#define TSADCV2_AUTO_PERIOD 0x68
#define TSADCV2_AUTO_PERIOD_HT 0x6c
#define TSADCV2_AUTO_EN BIT(0)
#define TSADCV2_AUTO_DISABLE ~BIT(0)
#define TSADCV2_AUTO_SRC_EN(chn) BIT(4 + (chn))
#define TSADCV2_AUTO_TSHUT_POLARITY_HIGH BIT(8)
#define TSADCV2_AUTO_TSHUT_POLARITY_LOW ~BIT(8)
#define TSADCV2_INT_SRC_EN(chn) BIT(chn)
#define TSADCV2_SHUT_2GPIO_SRC_EN(chn) BIT(4 + (chn))
#define TSADCV2_SHUT_2CRU_SRC_EN(chn) BIT(8 + (chn))
#define TSADCV2_INT_PD_CLEAR ~BIT(8)
#define TSADCV2_DATA_MASK 0xfff
#define TSADCV2_HIGHT_INT_DEBOUNCE_COUNT 4
#define TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT 4
#define TSADCV2_AUTO_PERIOD_TIME 250 /* msec */
#define TSADCV2_AUTO_PERIOD_HT_TIME 50 /* msec */
struct tsadc_table {
unsigned long code;
long temp;
};
static const struct tsadc_table v2_code_table[] = {
{TSADCV2_DATA_MASK, -40000},
{3800, -40000},
{3792, -35000},
{3783, -30000},
{3774, -25000},
{3765, -20000},
{3756, -15000},
{3747, -10000},
{3737, -5000},
{3728, 0},
{3718, 5000},
{3708, 10000},
{3698, 15000},
{3688, 20000},
{3678, 25000},
{3667, 30000},
{3656, 35000},
{3645, 40000},
{3634, 45000},
{3623, 50000},
{3611, 55000},
{3600, 60000},
{3588, 65000},
{3575, 70000},
{3563, 75000},
{3550, 80000},
{3537, 85000},
{3524, 90000},
{3510, 95000},
{3496, 100000},
{3482, 105000},
{3467, 110000},
{3452, 115000},
{3437, 120000},
{3421, 125000},
{0, 125000},
};
static u32 rk_tsadcv2_temp_to_code(long temp)
{
int high, low, mid;
low = 0;
high = ARRAY_SIZE(v2_code_table) - 1;
mid = (high + low) / 2;
if (temp < v2_code_table[low].temp || temp > v2_code_table[high].temp)
return 0;
while (low <= high) {
if (temp == v2_code_table[mid].temp)
return v2_code_table[mid].code;
else if (temp < v2_code_table[mid].temp)
high = mid - 1;
else
low = mid + 1;
mid = (low + high) / 2;
}
return 0;
}
static long rk_tsadcv2_code_to_temp(u32 code)
{
int high, low, mid;
low = 0;
high = ARRAY_SIZE(v2_code_table) - 1;
mid = (high + low) / 2;
if (code > v2_code_table[low].code || code < v2_code_table[high].code)
return 125000; /* No code available, return max temperature */
while (low <= high) {
if (code >= v2_code_table[mid].code && code <
v2_code_table[mid - 1].code)
return v2_code_table[mid].temp;
else if (code < v2_code_table[mid].code)
low = mid + 1;
else
high = mid - 1;
mid = (low + high) / 2;
}
return 125000;
}
/**
* rk_tsadcv2_initialize - initialize TASDC Controller
* (1) Set TSADCV2_AUTO_PERIOD, configure the interleave between
* every two accessing of TSADC in normal operation.
* (2) Set TSADCV2_AUTO_PERIOD_HT, configure the interleave between
* every two accessing of TSADC after the temperature is higher
* than COM_SHUT or COM_INT.
* (3) Set TSADCV2_HIGH_INT_DEBOUNCE and TSADC_HIGHT_TSHUT_DEBOUNCE,
* if the temperature is higher than COMP_INT or COMP_SHUT for
* "debounce" times, TSADC controller will generate interrupt or TSHUT.
*/
static void rk_tsadcv2_initialize(void __iomem *regs,
enum tshut_polarity tshut_polarity)
{
if (tshut_polarity == TSHUT_HIGH_ACTIVE)
writel_relaxed(0 | (TSADCV2_AUTO_TSHUT_POLARITY_HIGH),
regs + TSADCV2_AUTO_CON);
else
writel_relaxed(0 | (TSADCV2_AUTO_TSHUT_POLARITY_LOW),
regs + TSADCV2_AUTO_CON);
writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, regs + TSADCV2_AUTO_PERIOD);
writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT,
regs + TSADCV2_HIGHT_INT_DEBOUNCE);
writel_relaxed(TSADCV2_AUTO_PERIOD_HT_TIME,
regs + TSADCV2_AUTO_PERIOD_HT);
writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE);
}
static void rk_tsadcv2_irq_ack(void __iomem *regs)
{
u32 val;
val = readl_relaxed(regs + TSADCV2_INT_PD);
writel_relaxed(val & TSADCV2_INT_PD_CLEAR, regs + TSADCV2_INT_PD);
}
static void rk_tsadcv2_control(void __iomem *regs, bool enable)
{
u32 val;
val = readl_relaxed(regs + TSADCV2_AUTO_CON);
if (enable)
val |= TSADCV2_AUTO_EN;
else
val &= ~TSADCV2_AUTO_EN;
writel_relaxed(val, regs + TSADCV2_AUTO_CON);
}
static int rk_tsadcv2_get_temp(int chn, void __iomem *regs, long *temp)
{
u32 val;
/* the A/D value of the channel last conversion need some time */
val = readl_relaxed(regs + TSADCV2_DATA(chn));
if (val == 0)
return -EAGAIN;
*temp = rk_tsadcv2_code_to_temp(val);
return 0;
}
static void rk_tsadcv2_tshut_temp(int chn, void __iomem *regs, long temp)
{
u32 tshut_value, val;
tshut_value = rk_tsadcv2_temp_to_code(temp);
writel_relaxed(tshut_value, regs + TSADCV2_COMP_SHUT(chn));
/* TSHUT will be valid */
val = readl_relaxed(regs + TSADCV2_AUTO_CON);
writel_relaxed(val | TSADCV2_AUTO_SRC_EN(chn), regs + TSADCV2_AUTO_CON);
}
static void rk_tsadcv2_tshut_mode(int chn, void __iomem *regs,
enum tshut_mode mode)
{
u32 val;
val = readl_relaxed(regs + TSADCV2_INT_EN);
if (mode == TSHUT_MODE_GPIO) {
val &= ~TSADCV2_SHUT_2CRU_SRC_EN(chn);
val |= TSADCV2_SHUT_2GPIO_SRC_EN(chn);
} else {
val &= ~TSADCV2_SHUT_2GPIO_SRC_EN(chn);
val |= TSADCV2_SHUT_2CRU_SRC_EN(chn);
}
writel_relaxed(val, regs + TSADCV2_INT_EN);
}
static const struct rockchip_tsadc_chip rk3288_tsadc_data = {
.tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
.tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
.tshut_temp = 95000,
.initialize = rk_tsadcv2_initialize,
.irq_ack = rk_tsadcv2_irq_ack,
.control = rk_tsadcv2_control,
.get_temp = rk_tsadcv2_get_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
};
static const struct of_device_id of_rockchip_thermal_match[] = {
{
.compatible = "rockchip,rk3288-tsadc",
.data = (void *)&rk3288_tsadc_data,
},
{ /* end */ },
};
MODULE_DEVICE_TABLE(of, of_rockchip_thermal_match);
static void
rockchip_thermal_toggle_sensor(struct rockchip_thermal_sensor *sensor, bool on)
{
struct thermal_zone_device *tzd = sensor->tzd;
tzd->ops->set_mode(tzd,
on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED);
}
static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev)
{
struct rockchip_thermal_data *thermal = dev;
int i;
dev_dbg(&thermal->pdev->dev, "thermal alarm\n");
thermal->chip->irq_ack(thermal->regs);
for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++)
thermal_zone_device_update(thermal->sensors[i].tzd);
return IRQ_HANDLED;
}
static int rockchip_thermal_get_temp(void *_sensor, long *out_temp)
{
struct rockchip_thermal_sensor *sensor = _sensor;
struct rockchip_thermal_data *thermal = sensor->thermal;
const struct rockchip_tsadc_chip *tsadc = sensor->thermal->chip;
int retval;
retval = tsadc->get_temp(sensor->id, thermal->regs, out_temp);
dev_dbg(&thermal->pdev->dev, "sensor %d - temp: %ld, retval: %d\n",
sensor->id, *out_temp, retval);
return retval;
}
static const struct thermal_zone_of_device_ops rockchip_of_thermal_ops = {
.get_temp = rockchip_thermal_get_temp,
};
static int rockchip_configure_from_dt(struct device *dev,
struct device_node *np,
struct rockchip_thermal_data *thermal)
{
u32 shut_temp, tshut_mode, tshut_polarity;
if (of_property_read_u32(np, "rockchip,hw-tshut-temp", &shut_temp)) {
dev_warn(dev,
"Missing tshut temp property, using default %ld\n",
thermal->chip->tshut_temp);
thermal->tshut_temp = thermal->chip->tshut_temp;
} else {
thermal->tshut_temp = shut_temp;
}
if (thermal->tshut_temp > INT_MAX) {
dev_err(dev, "Invalid tshut temperature specified: %ld\n",
thermal->tshut_temp);
return -ERANGE;
}
if (of_property_read_u32(np, "rockchip,hw-tshut-mode", &tshut_mode)) {
dev_warn(dev,
"Missing tshut mode property, using default (%s)\n",
thermal->chip->tshut_mode == TSHUT_MODE_GPIO ?
"gpio" : "cru");
thermal->tshut_mode = thermal->chip->tshut_mode;
} else {
thermal->tshut_mode = tshut_mode;
}
if (thermal->tshut_mode > 1) {
dev_err(dev, "Invalid tshut mode specified: %d\n",
thermal->tshut_mode);
return -EINVAL;
}
if (of_property_read_u32(np, "rockchip,hw-tshut-polarity",
&tshut_polarity)) {
dev_warn(dev,
"Missing tshut-polarity property, using default (%s)\n",
thermal->chip->tshut_polarity == TSHUT_LOW_ACTIVE ?
"low" : "high");
thermal->tshut_polarity = thermal->chip->tshut_polarity;
} else {
thermal->tshut_polarity = tshut_polarity;
}
if (thermal->tshut_polarity > 1) {
dev_err(dev, "Invalid tshut-polarity specified: %d\n",
thermal->tshut_polarity);
return -EINVAL;
}
return 0;
}
static int
rockchip_thermal_register_sensor(struct platform_device *pdev,
struct rockchip_thermal_data *thermal,
struct rockchip_thermal_sensor *sensor,
enum sensor_id id)
{
const struct rockchip_tsadc_chip *tsadc = thermal->chip;
int error;
tsadc->set_tshut_mode(id, thermal->regs, thermal->tshut_mode);
tsadc->set_tshut_temp(id, thermal->regs, thermal->tshut_temp);
sensor->thermal = thermal;
sensor->id = id;
sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev, id, sensor,
&rockchip_of_thermal_ops);
if (IS_ERR(sensor->tzd)) {
error = PTR_ERR(sensor->tzd);
dev_err(&pdev->dev, "failed to register sensor %d: %d\n",
id, error);
return error;
}
return 0;
}
/*
* Reset TSADC Controller, reset all tsadc registers.
*/
static void rockchip_thermal_reset_controller(struct reset_control *reset)
{
reset_control_assert(reset);
usleep_range(10, 20);
reset_control_deassert(reset);
}
static int rockchip_thermal_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct rockchip_thermal_data *thermal;
const struct of_device_id *match;
struct resource *res;
int irq;
int i;
int error;
match = of_match_node(of_rockchip_thermal_match, np);
if (!match)
return -ENXIO;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq resource?\n");
return -EINVAL;
}
thermal = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_thermal_data),
GFP_KERNEL);
if (!thermal)
return -ENOMEM;
thermal->pdev = pdev;
thermal->chip = (const struct rockchip_tsadc_chip *)match->data;
if (!thermal->chip)
return -EINVAL;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
thermal->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(thermal->regs))
return PTR_ERR(thermal->regs);
thermal->reset = devm_reset_control_get(&pdev->dev, "tsadc-apb");
if (IS_ERR(thermal->reset)) {
error = PTR_ERR(thermal->reset);
dev_err(&pdev->dev, "failed to get tsadc reset: %d\n", error);
return error;
}
thermal->clk = devm_clk_get(&pdev->dev, "tsadc");
if (IS_ERR(thermal->clk)) {
error = PTR_ERR(thermal->clk);
dev_err(&pdev->dev, "failed to get tsadc clock: %d\n", error);
return error;
}
thermal->pclk = devm_clk_get(&pdev->dev, "apb_pclk");
if (IS_ERR(thermal->pclk)) {
error = PTR_ERR(thermal->clk);
dev_err(&pdev->dev, "failed to get apb_pclk clock: %d\n",
error);
return error;
}
error = clk_prepare_enable(thermal->clk);
if (error) {
dev_err(&pdev->dev, "failed to enable converter clock: %d\n",
error);
return error;
}
error = clk_prepare_enable(thermal->pclk);
if (error) {
dev_err(&pdev->dev, "failed to enable pclk: %d\n", error);
goto err_disable_clk;
}
rockchip_thermal_reset_controller(thermal->reset);
error = rockchip_configure_from_dt(&pdev->dev, np, thermal);
if (error) {
dev_err(&pdev->dev, "failed to parse device tree data: %d\n",
error);
goto err_disable_pclk;
}
thermal->chip->initialize(thermal->regs, thermal->tshut_polarity);
error = rockchip_thermal_register_sensor(pdev, thermal,
&thermal->sensors[0],
SENSOR_CPU);
if (error) {
dev_err(&pdev->dev,
"failed to register CPU thermal sensor: %d\n", error);
goto err_disable_pclk;
}
error = rockchip_thermal_register_sensor(pdev, thermal,
&thermal->sensors[1],
SENSOR_GPU);
if (error) {
dev_err(&pdev->dev,
"failed to register GPU thermal sensor: %d\n", error);
goto err_unregister_cpu_sensor;
}
error = devm_request_threaded_irq(&pdev->dev, irq, NULL,
&rockchip_thermal_alarm_irq_thread,
IRQF_ONESHOT,
"rockchip_thermal", thermal);
if (error) {
dev_err(&pdev->dev,
"failed to request tsadc irq: %d\n", error);
goto err_unregister_gpu_sensor;
}
thermal->chip->control(thermal->regs, true);
for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++)
rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
platform_set_drvdata(pdev, thermal);
return 0;
err_unregister_gpu_sensor:
thermal_zone_of_sensor_unregister(&pdev->dev, thermal->sensors[1].tzd);
err_unregister_cpu_sensor:
thermal_zone_of_sensor_unregister(&pdev->dev, thermal->sensors[0].tzd);
err_disable_pclk:
clk_disable_unprepare(thermal->pclk);
err_disable_clk:
clk_disable_unprepare(thermal->clk);
return error;
}
static int rockchip_thermal_remove(struct platform_device *pdev)
{
struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
int i;
for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++) {
struct rockchip_thermal_sensor *sensor = &thermal->sensors[i];
rockchip_thermal_toggle_sensor(sensor, false);
thermal_zone_of_sensor_unregister(&pdev->dev, sensor->tzd);
}
thermal->chip->control(thermal->regs, false);
clk_disable_unprepare(thermal->pclk);
clk_disable_unprepare(thermal->clk);
return 0;
}
static int __maybe_unused rockchip_thermal_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
int i;
for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++)
rockchip_thermal_toggle_sensor(&thermal->sensors[i], false);
thermal->chip->control(thermal->regs, false);
clk_disable(thermal->pclk);
clk_disable(thermal->clk);
return 0;
}
static int __maybe_unused rockchip_thermal_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
int i;
int error;
error = clk_enable(thermal->clk);
if (error)
return error;
error = clk_enable(thermal->pclk);
if (error)
return error;
rockchip_thermal_reset_controller(thermal->reset);
thermal->chip->initialize(thermal->regs, thermal->tshut_polarity);
for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++) {
enum sensor_id id = thermal->sensors[i].id;
thermal->chip->set_tshut_mode(id, thermal->regs,
thermal->tshut_mode);
thermal->chip->set_tshut_temp(id, thermal->regs,
thermal->tshut_temp);
}
thermal->chip->control(thermal->regs, true);
for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++)
rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
return 0;
}
static SIMPLE_DEV_PM_OPS(rockchip_thermal_pm_ops,
rockchip_thermal_suspend, rockchip_thermal_resume);
static struct platform_driver rockchip_thermal_driver = {
.driver = {
.name = "rockchip-thermal",
.owner = THIS_MODULE,
.pm = &rockchip_thermal_pm_ops,
.of_match_table = of_rockchip_thermal_match,
},
.probe = rockchip_thermal_probe,
.remove = rockchip_thermal_remove,
};
module_platform_driver(rockchip_thermal_driver);
MODULE_DESCRIPTION("ROCKCHIP THERMAL Driver");
MODULE_AUTHOR("Rockchip, Inc.");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:rockchip-thermal");

View File

@ -27,7 +27,6 @@
#define SENSOR_NAME_LEN 16
#define MAX_TRIP_COUNT 8
#define MAX_COOLING_DEVICE 4
#define MAX_TRIMINFO_CTRL_REG 2
#define ACTIVE_INTERVAL 500
#define IDLE_INTERVAL 10000

View File

@ -33,7 +33,87 @@
#include "exynos_thermal_common.h"
#include "exynos_tmu.h"
#include "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
/* Exynos3250 specific registers */
#define EXYNOS_TMU_TRIMINFO_CON1 0x10
/* Exynos4210 specific registers */
#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44
#define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50
/* Exynos5250, Exynos4412, Exynos3250 specific registers */
#define EXYNOS_TMU_TRIMINFO_CON2 0x14
#define EXYNOS_THD_TEMP_RISE 0x50
#define EXYNOS_THD_TEMP_FALL 0x54
#define EXYNOS_EMUL_CON 0x80
#define EXYNOS_TRIMINFO_RELOAD_ENABLE 1
#define EXYNOS_TRIMINFO_25_SHIFT 0
#define EXYNOS_TRIMINFO_85_SHIFT 8
#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_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_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
/* Exynos5260 specific */
#define EXYNOS5260_TMU_REG_INTEN 0xC0
#define EXYNOS5260_TMU_REG_INTSTAT 0xC4
#define EXYNOS5260_TMU_REG_INTCLEAR 0xC8
#define EXYNOS5260_EMUL_CON 0x100
/* Exynos4412 specific */
#define EXYNOS4412_MUX_ADDR_VALUE 6
#define EXYNOS4412_MUX_ADDR_SHIFT 20
/*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_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_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_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_TH_RISE4_SHIFT 24
#define EXYNOS5440_EFUSE_SWAP_OFFSET 8
/**
* struct exynos_tmu_data : A structure to hold the private data of the TMU
@ -52,6 +132,11 @@
* @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.
* @tmu_initialize: SoC specific TMU initialization method
* @tmu_control: SoC specific TMU control method
* @tmu_read: SoC specific TMU temperature read method
* @tmu_set_emulation: SoC specific TMU emulation setting method
* @tmu_clear_irqs: SoC specific TMU interrupts clearing method
*/
struct exynos_tmu_data {
int id;
@ -66,6 +151,12 @@ struct exynos_tmu_data {
u8 temp_error1, temp_error2;
struct regulator *regulator;
struct thermal_sensor_conf *reg_conf;
int (*tmu_initialize)(struct platform_device *pdev);
void (*tmu_control)(struct platform_device *pdev, bool on);
int (*tmu_read)(struct exynos_tmu_data *data);
void (*tmu_set_emulation)(struct exynos_tmu_data *data,
unsigned long temp);
void (*tmu_clear_irqs)(struct exynos_tmu_data *data);
};
/*
@ -122,83 +213,10 @@ static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code)
return temp;
}
static void exynos_tmu_clear_irqs(struct exynos_tmu_data *data)
static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info)
{
const struct exynos_tmu_registers *reg = data->pdata->registers;
unsigned int val_irq;
val_irq = readl(data->base + reg->tmu_intstat);
/*
* Clear the interrupts. Please note that the documentation for
* Exynos3250, Exynos4412, Exynos5250 and Exynos5260 incorrectly
* states that INTCLEAR register has a different placing of bits
* responsible for FALL IRQs than INTSTAT register. Exynos5420
* and Exynos5440 documentation is correct (Exynos4210 doesn't
* support FALL IRQs at all).
*/
writel(val_irq, data->base + reg->tmu_intclear);
}
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, ctrl;
unsigned int rising_threshold = 0, falling_threshold = 0;
int ret = 0, threshold_code, i;
mutex_lock(&data->lock);
clk_enable(data->clk);
if (!IS_ERR(data->clk_sec))
clk_enable(data->clk_sec);
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)) {
for (i = 0; i < reg->triminfo_ctrl_count; i++) {
if (pdata->triminfo_reload[i]) {
ctrl = readl(data->base +
reg->triminfo_ctrl[i]);
ctrl |= pdata->triminfo_reload[i];
writel(ctrl, data->base +
reg->triminfo_ctrl[i]);
}
}
}
/* 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 {
/* On exynos5420 the triminfo register is in the shared space */
if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO)
trim_info = readl(data->base_second +
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 >> EXYNOS_TRIMINFO_85_SHIFT) &
EXYNOS_TMU_TEMP_MASK);
@ -212,69 +230,37 @@ static int exynos_tmu_initialize(struct platform_device *pdev)
data->temp_error2 =
(pdata->efuse_value >> EXYNOS_TRIMINFO_85_SHIFT) &
EXYNOS_TMU_TEMP_MASK;
}
rising_threshold = readl(data->base + reg->threshold_th0);
static u32 get_th_reg(struct exynos_tmu_data *data, u32 threshold, bool falling)
{
struct exynos_tmu_platform_data *pdata = data->pdata;
int i;
if (data->soc == SOC_ARCH_EXYNOS4210) {
/* Write temperature code for threshold */
threshold_code = temp_to_code(data, pdata->threshold);
writeb(threshold_code,
data->base + reg->threshold_temp);
for (i = 0; i < pdata->non_hw_trigger_levels; i++)
writeb(pdata->trigger_levels[i], data->base +
reg->threshold_th0 + i * sizeof(reg->threshold_th0));
for (i = 0; i < pdata->non_hw_trigger_levels; i++) {
u8 temp = pdata->trigger_levels[i];
exynos_tmu_clear_irqs(data);
} else {
/* Write temperature code for rising and falling threshold */
for (i = 0; i < pdata->non_hw_trigger_levels; i++) {
threshold_code = temp_to_code(data,
pdata->trigger_levels[i]);
rising_threshold &= ~(0xff << 8 * i);
rising_threshold |= threshold_code << 8 * i;
if (pdata->threshold_falling) {
threshold_code = temp_to_code(data,
pdata->trigger_levels[i] -
pdata->threshold_falling);
falling_threshold |= threshold_code << 8 * i;
}
}
if (falling)
temp -= pdata->threshold_falling;
else
threshold &= ~(0xff << 8 * i);
writel(rising_threshold,
data->base + reg->threshold_th0);
writel(falling_threshold,
data->base + reg->threshold_th1);
exynos_tmu_clear_irqs(data);
/* 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 (i == EXYNOS_MAX_TRIGGER_PER_REG - 1) {
/* 1-4 level to be assigned in th0 reg */
rising_threshold &= ~(0xff << 8 * i);
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);
}
threshold |= temp_to_code(data, temp) << 8 * i;
}
/*Clear the PMIN in the common TMU register*/
if (reg->tmu_pmin && !data->id)
writel(0, data->base_second + reg->tmu_pmin);
out:
return threshold;
}
static int exynos_tmu_initialize(struct platform_device *pdev)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
int ret;
mutex_lock(&data->lock);
clk_enable(data->clk);
if (!IS_ERR(data->clk_sec))
clk_enable(data->clk_sec);
ret = data->tmu_initialize(pdev);
clk_disable(data->clk);
mutex_unlock(&data->lock);
if (!IS_ERR(data->clk_sec))
@ -283,20 +269,13 @@ out:
return ret;
}
static void exynos_tmu_control(struct platform_device *pdev, bool on)
static u32 get_con_reg(struct exynos_tmu_data *data, u32 con)
{
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;
mutex_lock(&data->lock);
clk_enable(data->clk);
con = readl(data->base + reg->tmu_ctrl);
if (pdata->test_mux)
con |= (pdata->test_mux << reg->test_mux_addr_shift);
if (data->soc == SOC_ARCH_EXYNOS4412 ||
data->soc == SOC_ARCH_EXYNOS3250)
con |= (EXYNOS4412_MUX_ADDR_VALUE << EXYNOS4412_MUX_ADDR_SHIFT);
con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << EXYNOS_TMU_REF_VOLTAGE_SHIFT);
con |= pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT;
@ -305,69 +284,279 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on)
con |= (pdata->gain << EXYNOS_TMU_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);
con &= ~(EXYNOS_TMU_TRIP_MODE_MASK << EXYNOS_TMU_TRIP_MODE_SHIFT);
con |= (pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT);
}
return con;
}
static void exynos_tmu_control(struct platform_device *pdev, bool on)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
mutex_lock(&data->lock);
clk_enable(data->clk);
data->tmu_control(pdev, on);
clk_disable(data->clk);
mutex_unlock(&data->lock);
}
static int exynos4210_tmu_initialize(struct platform_device *pdev)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
struct exynos_tmu_platform_data *pdata = data->pdata;
unsigned int status;
int ret = 0, threshold_code, i;
status = readb(data->base + EXYNOS_TMU_REG_STATUS);
if (!status) {
ret = -EBUSY;
goto out;
}
sanitize_temp_error(data, readl(data->base + EXYNOS_TMU_REG_TRIMINFO));
/* Write temperature code for threshold */
threshold_code = temp_to_code(data, pdata->threshold);
writeb(threshold_code, data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
for (i = 0; i < pdata->non_hw_trigger_levels; i++)
writeb(pdata->trigger_levels[i], data->base +
EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4);
data->tmu_clear_irqs(data);
out:
return ret;
}
static int exynos4412_tmu_initialize(struct platform_device *pdev)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
struct exynos_tmu_platform_data *pdata = data->pdata;
unsigned int status, trim_info, con, ctrl, rising_threshold;
int ret = 0, threshold_code, i;
status = readb(data->base + EXYNOS_TMU_REG_STATUS);
if (!status) {
ret = -EBUSY;
goto out;
}
if (data->soc == SOC_ARCH_EXYNOS3250 ||
data->soc == SOC_ARCH_EXYNOS4412 ||
data->soc == SOC_ARCH_EXYNOS5250) {
if (data->soc == SOC_ARCH_EXYNOS3250) {
ctrl = readl(data->base + EXYNOS_TMU_TRIMINFO_CON1);
ctrl |= EXYNOS_TRIMINFO_RELOAD_ENABLE;
writel(ctrl, data->base + EXYNOS_TMU_TRIMINFO_CON1);
}
ctrl = readl(data->base + EXYNOS_TMU_TRIMINFO_CON2);
ctrl |= EXYNOS_TRIMINFO_RELOAD_ENABLE;
writel(ctrl, data->base + EXYNOS_TMU_TRIMINFO_CON2);
}
/* On exynos5420 the triminfo register is in the shared space */
if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO)
trim_info = readl(data->base_second + EXYNOS_TMU_REG_TRIMINFO);
else
trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
sanitize_temp_error(data, trim_info);
/* Write temperature code for rising and falling threshold */
rising_threshold = readl(data->base + EXYNOS_THD_TEMP_RISE);
rising_threshold = get_th_reg(data, rising_threshold, false);
writel(rising_threshold, data->base + EXYNOS_THD_TEMP_RISE);
writel(get_th_reg(data, 0, true), data->base + EXYNOS_THD_TEMP_FALL);
data->tmu_clear_irqs(data);
/* 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]);
/* 1-4 level to be assigned in th0 reg */
rising_threshold &= ~(0xff << 8 * i);
rising_threshold |= threshold_code << 8 * i;
writel(rising_threshold, data->base + EXYNOS_THD_TEMP_RISE);
con = readl(data->base + EXYNOS_TMU_REG_CONTROL);
con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT);
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
}
out:
return ret;
}
static int exynos5440_tmu_initialize(struct platform_device *pdev)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
struct exynos_tmu_platform_data *pdata = data->pdata;
unsigned int trim_info = 0, con, rising_threshold;
int ret = 0, threshold_code, i;
/*
* 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 +
EXYNOS5440_TMU_S0_7_TRIM);
break;
case 1:
trim_info = readl(data->base + EXYNOS5440_TMU_S0_7_TRIM);
break;
case 2:
trim_info = readl(data->base - EXYNOS5440_EFUSE_SWAP_OFFSET +
EXYNOS5440_TMU_S0_7_TRIM);
}
sanitize_temp_error(data, trim_info);
/* Write temperature code for rising and falling threshold */
rising_threshold = readl(data->base + EXYNOS5440_TMU_S0_7_TH0);
rising_threshold = get_th_reg(data, rising_threshold, false);
writel(rising_threshold, data->base + EXYNOS5440_TMU_S0_7_TH0);
writel(0, data->base + EXYNOS5440_TMU_S0_7_TH1);
data->tmu_clear_irqs(data);
/* 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]);
/* 5th level to be assigned in th2 reg */
rising_threshold =
threshold_code << EXYNOS5440_TMU_TH_RISE4_SHIFT;
writel(rising_threshold, data->base + EXYNOS5440_TMU_S0_7_TH2);
con = readl(data->base + EXYNOS5440_TMU_S0_7_CTRL);
con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT);
writel(con, data->base + EXYNOS5440_TMU_S0_7_CTRL);
}
/* Clear the PMIN in the common TMU register */
if (!data->id)
writel(0, data->base_second + EXYNOS5440_TMU_PMIN);
return ret;
}
static void exynos4210_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;
unsigned int con, interrupt_en;
con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
if (on) {
con |= (1 << EXYNOS_TMU_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))
pdata->trigger_enable[3] << EXYNOS_TMU_INTEN_RISE3_SHIFT |
pdata->trigger_enable[2] << EXYNOS_TMU_INTEN_RISE2_SHIFT |
pdata->trigger_enable[1] << EXYNOS_TMU_INTEN_RISE1_SHIFT |
pdata->trigger_enable[0] << EXYNOS_TMU_INTEN_RISE0_SHIFT;
if (data->soc != SOC_ARCH_EXYNOS4210)
interrupt_en |=
interrupt_en << reg->inten_fall0_shift;
interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
} else {
con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
interrupt_en = 0; /* Disable all interrupts */
}
writel(interrupt_en, data->base + reg->tmu_inten);
writel(con, data->base + reg->tmu_ctrl);
writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN);
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
}
clk_disable(data->clk);
mutex_unlock(&data->lock);
static void exynos5440_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;
unsigned int con, interrupt_en;
con = get_con_reg(data, readl(data->base + EXYNOS5440_TMU_S0_7_CTRL));
if (on) {
con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
interrupt_en =
pdata->trigger_enable[3] << EXYNOS5440_TMU_INTEN_RISE3_SHIFT |
pdata->trigger_enable[2] << EXYNOS5440_TMU_INTEN_RISE2_SHIFT |
pdata->trigger_enable[1] << EXYNOS5440_TMU_INTEN_RISE1_SHIFT |
pdata->trigger_enable[0] << EXYNOS5440_TMU_INTEN_RISE0_SHIFT;
interrupt_en |= interrupt_en << EXYNOS5440_TMU_INTEN_FALL0_SHIFT;
} else {
con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
interrupt_en = 0; /* Disable all interrupts */
}
writel(interrupt_en, data->base + EXYNOS5440_TMU_S0_7_IRQEN);
writel(con, data->base + EXYNOS5440_TMU_S0_7_CTRL);
}
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;
int ret;
mutex_lock(&data->lock);
clk_enable(data->clk);
temp_code = readb(data->base + reg->tmu_cur_temp);
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;
}
temp = code_to_temp(data, temp_code);
out:
ret = data->tmu_read(data);
if (ret >= 0)
ret = code_to_temp(data, ret);
clk_disable(data->clk);
mutex_unlock(&data->lock);
return temp;
return ret;
}
#ifdef CONFIG_THERMAL_EMULATION
static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val,
unsigned long temp)
{
if (temp) {
temp /= MCELSIUS;
if (data->soc != SOC_ARCH_EXYNOS5440) {
val &= ~(EXYNOS_EMUL_TIME_MASK << EXYNOS_EMUL_TIME_SHIFT);
val |= (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT);
}
val &= ~(EXYNOS_EMUL_DATA_MASK << EXYNOS_EMUL_DATA_SHIFT);
val |= (temp_to_code(data, temp) << EXYNOS_EMUL_DATA_SHIFT) |
EXYNOS_EMUL_ENABLE;
} else {
val &= ~EXYNOS_EMUL_ENABLE;
}
return val;
}
static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
unsigned long temp)
{
unsigned int val;
u32 emul_con;
if (data->soc == SOC_ARCH_EXYNOS5260)
emul_con = EXYNOS5260_EMUL_CON;
else
emul_con = EXYNOS_EMUL_CON;
val = readl(data->base + emul_con);
val = get_emul_con_reg(data, val, temp);
writel(val, data->base + emul_con);
}
static void exynos5440_tmu_set_emulation(struct exynos_tmu_data *data,
unsigned long temp)
{
unsigned int val;
val = readl(data->base + EXYNOS5440_TMU_S0_7_DEBUG);
val = get_emul_con_reg(data, val, temp);
writel(val, data->base + EXYNOS5440_TMU_S0_7_DEBUG);
}
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))
if (data->soc == SOC_ARCH_EXYNOS4210)
goto out;
if (temp && temp < MCELSIUS)
@ -375,25 +564,7 @@ static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
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);
data->tmu_set_emulation(data, temp);
clk_disable(data->clk);
mutex_unlock(&data->lock);
return 0;
@ -401,23 +572,41 @@ out:
return ret;
}
#else
#define exynos4412_tmu_set_emulation NULL
#define exynos5440_tmu_set_emulation NULL
static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
{ return -EINVAL; }
#endif/*CONFIG_THERMAL_EMULATION*/
static int exynos4210_tmu_read(struct exynos_tmu_data *data)
{
int ret = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
/* "temp_code" should range between 75 and 175 */
return (ret < 75 || ret > 175) ? -ENODATA : ret;
}
static int exynos4412_tmu_read(struct exynos_tmu_data *data)
{
return readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
}
static int exynos5440_tmu_read(struct exynos_tmu_data *data)
{
return readb(data->base + EXYNOS5440_TMU_S0_7_TEMP);
}
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_type;
if (!IS_ERR(data->clk_sec))
clk_enable(data->clk_sec);
/* Find which sensor generated this interrupt */
if (reg->tmu_irqstatus) {
val_type = readl(data->base_second + reg->tmu_irqstatus);
if (data->soc == SOC_ARCH_EXYNOS5440) {
val_type = readl(data->base_second + EXYNOS5440_TMU_IRQ_STATUS);
if (!((val_type >> data->id) & 0x1))
goto out;
}
@ -429,7 +618,7 @@ static void exynos_tmu_work(struct work_struct *work)
clk_enable(data->clk);
/* TODO: take action based on particular interrupt */
exynos_tmu_clear_irqs(data);
data->tmu_clear_irqs(data);
clk_disable(data->clk);
mutex_unlock(&data->lock);
@ -437,6 +626,40 @@ out:
enable_irq(data->irq);
}
static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
{
unsigned int val_irq;
u32 tmu_intstat, tmu_intclear;
if (data->soc == SOC_ARCH_EXYNOS5260) {
tmu_intstat = EXYNOS5260_TMU_REG_INTSTAT;
tmu_intclear = EXYNOS5260_TMU_REG_INTCLEAR;
} else {
tmu_intstat = EXYNOS_TMU_REG_INTSTAT;
tmu_intclear = EXYNOS_TMU_REG_INTCLEAR;
}
val_irq = readl(data->base + tmu_intstat);
/*
* Clear the interrupts. Please note that the documentation for
* Exynos3250, Exynos4412, Exynos5250 and Exynos5260 incorrectly
* states that INTCLEAR register has a different placing of bits
* responsible for FALL IRQs than INTSTAT register. Exynos5420
* and Exynos5440 documentation is correct (Exynos4210 doesn't
* support FALL IRQs at all).
*/
writel(val_irq, data->base + tmu_intclear);
}
static void exynos5440_tmu_clear_irqs(struct exynos_tmu_data *data)
{
unsigned int val_irq;
val_irq = readl(data->base + EXYNOS5440_TMU_S0_7_IRQ);
/* clear the interrupts */
writel(val_irq, data->base + EXYNOS5440_TMU_S0_7_IRQ);
}
static irqreturn_t exynos_tmu_irq(int irq, void *id)
{
struct exynos_tmu_data *data = id;
@ -450,35 +673,35 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
static const struct of_device_id exynos_tmu_match[] = {
{
.compatible = "samsung,exynos3250-tmu",
.data = (void *)EXYNOS3250_TMU_DRV_DATA,
.data = &exynos3250_default_tmu_data,
},
{
.compatible = "samsung,exynos4210-tmu",
.data = (void *)EXYNOS4210_TMU_DRV_DATA,
.data = &exynos4210_default_tmu_data,
},
{
.compatible = "samsung,exynos4412-tmu",
.data = (void *)EXYNOS4412_TMU_DRV_DATA,
.data = &exynos4412_default_tmu_data,
},
{
.compatible = "samsung,exynos5250-tmu",
.data = (void *)EXYNOS5250_TMU_DRV_DATA,
.data = &exynos5250_default_tmu_data,
},
{
.compatible = "samsung,exynos5260-tmu",
.data = (void *)EXYNOS5260_TMU_DRV_DATA,
.data = &exynos5260_default_tmu_data,
},
{
.compatible = "samsung,exynos5420-tmu",
.data = (void *)EXYNOS5420_TMU_DRV_DATA,
.data = &exynos5420_default_tmu_data,
},
{
.compatible = "samsung,exynos5420-tmu-ext-triminfo",
.data = (void *)EXYNOS5420_TMU_DRV_DATA,
.data = &exynos5420_default_tmu_data,
},
{
.compatible = "samsung,exynos5440-tmu",
.data = (void *)EXYNOS5440_TMU_DRV_DATA,
.data = &exynos5440_default_tmu_data,
},
{},
};
@ -553,12 +776,47 @@ static int exynos_map_dt_data(struct platform_device *pdev)
dev_err(&pdev->dev, "No platform init data supplied.\n");
return -ENODEV;
}
data->pdata = pdata;
data->soc = pdata->type;
switch (data->soc) {
case SOC_ARCH_EXYNOS4210:
data->tmu_initialize = exynos4210_tmu_initialize;
data->tmu_control = exynos4210_tmu_control;
data->tmu_read = exynos4210_tmu_read;
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
break;
case SOC_ARCH_EXYNOS3250:
case SOC_ARCH_EXYNOS4412:
case SOC_ARCH_EXYNOS5250:
case SOC_ARCH_EXYNOS5260:
case SOC_ARCH_EXYNOS5420:
case SOC_ARCH_EXYNOS5420_TRIMINFO:
data->tmu_initialize = exynos4412_tmu_initialize;
data->tmu_control = exynos4210_tmu_control;
data->tmu_read = exynos4412_tmu_read;
data->tmu_set_emulation = exynos4412_tmu_set_emulation;
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
break;
case SOC_ARCH_EXYNOS5440:
data->tmu_initialize = exynos5440_tmu_initialize;
data->tmu_control = exynos5440_tmu_control;
data->tmu_read = exynos5440_tmu_read;
data->tmu_set_emulation = exynos5440_tmu_set_emulation;
data->tmu_clear_irqs = exynos5440_tmu_clear_irqs;
break;
default:
dev_err(&pdev->dev, "Platform not supported\n");
return -EINVAL;
}
/*
* Check if the TMU shares some registers and then try to map the
* memory of common registers.
*/
if (!TMU_SUPPORTS(pdata, ADDRESS_MULTIPLE))
if (data->soc != SOC_ARCH_EXYNOS5420_TRIMINFO &&
data->soc != SOC_ARCH_EXYNOS5440)
return 0;
if (of_address_to_resource(pdev->dev.of_node, 1, &res)) {
@ -625,20 +883,6 @@ static int exynos_tmu_probe(struct platform_device *pdev)
goto err_clk_sec;
}
if (pdata->type == SOC_ARCH_EXYNOS3250 ||
pdata->type == SOC_ARCH_EXYNOS4210 ||
pdata->type == SOC_ARCH_EXYNOS4412 ||
pdata->type == SOC_ARCH_EXYNOS5250 ||
pdata->type == SOC_ARCH_EXYNOS5260 ||
pdata->type == SOC_ARCH_EXYNOS5420_TRIMINFO ||
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");

View File

@ -40,114 +40,11 @@ enum soc_type {
SOC_ARCH_EXYNOS4412,
SOC_ARCH_EXYNOS5250,
SOC_ARCH_EXYNOS5260,
SOC_ARCH_EXYNOS5420,
SOC_ARCH_EXYNOS5420_TRIMINFO,
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_ADDRESS_MULTIPLE - 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_ADDRESS_MULTIPLE 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_ctrl: trim info controller register.
* @triminfo_ctrl_count: the number of trim info controller register.
* @tmu_ctrl: TMU main controller register.
* @test_mux_addr_shift: shift bits of test mux address.
* @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.
* @tmu_status: register drescribing the TMU status.
* @tmu_cur_temp: register containing the current temperature of the TMU.
* @threshold_temp: register containing the base threshold level.
* @threshold_th0: Register containing first set of rising levels.
* @threshold_th1: Register containing second set of rising levels.
* @threshold_th2: Register containing third 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_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.
* @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.
* @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_ctrl[MAX_TRIMINFO_CTRL_REG];
u32 triminfo_ctrl_count;
u32 tmu_ctrl;
u32 test_mux_addr_shift;
u32 therm_trip_mode_shift;
u32 therm_trip_mode_mask;
u32 therm_trip_en_shift;
u32 tmu_status;
u32 tmu_cur_temp;
u32 threshold_temp;
u32 threshold_th0;
u32 threshold_th1;
u32 threshold_th2;
u32 threshold_th3_l0_shift;
u32 tmu_inten;
u32 inten_rise0_shift;
u32 inten_rise1_shift;
u32 inten_rise2_shift;
u32 inten_rise3_shift;
u32 inten_fall0_shift;
u32 tmu_intstat;
u32 tmu_intclear;
u32 emul_con;
u32 emul_temp_shift;
u32 emul_time_shift;
u32 tmu_irqstatus;
u32 tmu_pmin;
};
/**
* struct exynos_tmu_platform_data
* @threshold: basic temperature for generating interrupt
@ -192,16 +89,10 @@ struct exynos_tmu_registers {
* @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
* @test_mux; information if SoC supports test MUX
* @triminfo_reload: reload value to read TRIMINFO register
* @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.
* @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.
*/
@ -223,15 +114,11 @@ struct exynos_tmu_platform_data {
u8 first_point_trim;
u8 second_point_trim;
u8 default_temp_offset;
u8 test_mux;
u8 triminfo_reload[MAX_TRIMINFO_CTRL_REG];
enum calibration_type cal_type;
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;
};
/**
@ -246,4 +133,12 @@ struct exynos_tmu_init_data {
struct exynos_tmu_platform_data tmu_data[];
};
extern struct exynos_tmu_init_data const exynos3250_default_tmu_data;
extern struct exynos_tmu_init_data const exynos4210_default_tmu_data;
extern struct exynos_tmu_init_data const exynos4412_default_tmu_data;
extern struct exynos_tmu_init_data const exynos5250_default_tmu_data;
extern struct exynos_tmu_init_data const exynos5260_default_tmu_data;
extern struct exynos_tmu_init_data const exynos5420_default_tmu_data;
extern struct exynos_tmu_init_data const exynos5440_default_tmu_data;
#endif /* _EXYNOS_TMU_H */

View File

@ -22,24 +22,6 @@
#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,
.tmu_ctrl = EXYNOS_TMU_REG_CONTROL,
.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_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 = {
@ -75,40 +57,10 @@ struct exynos_tmu_init_data const exynos4210_default_tmu_data = {
},
.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_EXYNOS3250)
static const struct exynos_tmu_registers exynos3250_tmu_registers = {
.triminfo_data = EXYNOS_TMU_REG_TRIMINFO,
.triminfo_ctrl[0] = EXYNOS_TMU_TRIMINFO_CON1,
.triminfo_ctrl[1] = EXYNOS_TMU_TRIMINFO_CON2,
.triminfo_ctrl_count = 2,
.tmu_ctrl = EXYNOS_TMU_REG_CONTROL,
.test_mux_addr_shift = EXYNOS4412_MUX_ADDR_SHIFT,
.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,
.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_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT,
.inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT,
.inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_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,
};
#define EXYNOS3250_TMU_DATA \
.threshold_falling = 10, \
@ -144,54 +96,17 @@ static const struct exynos_tmu_registers exynos3250_tmu_registers = {
.freq_clip_max = 400 * 1000, \
.temp_level = 95, \
}, \
.freq_tab_count = 2, \
.triminfo_reload[0] = EXYNOS_TRIMINFO_RELOAD_ENABLE, \
.triminfo_reload[1] = EXYNOS_TRIMINFO_RELOAD_ENABLE, \
.registers = &exynos3250_tmu_registers, \
.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \
TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \
TMU_SUPPORT_EMUL_TIME)
#endif
.freq_tab_count = 2
#if defined(CONFIG_SOC_EXYNOS3250)
struct exynos_tmu_init_data const exynos3250_default_tmu_data = {
.tmu_data = {
{
EXYNOS3250_TMU_DATA,
.type = SOC_ARCH_EXYNOS3250,
.test_mux = EXYNOS4412_MUX_ADDR_VALUE,
},
},
.tmu_count = 1,
};
#endif
#if defined(CONFIG_SOC_EXYNOS4412) || defined(CONFIG_SOC_EXYNOS5250)
static const struct exynos_tmu_registers exynos4412_tmu_registers = {
.triminfo_data = EXYNOS_TMU_REG_TRIMINFO,
.triminfo_ctrl[0] = EXYNOS_TMU_TRIMINFO_CON2,
.triminfo_ctrl_count = 1,
.tmu_ctrl = EXYNOS_TMU_REG_CONTROL,
.test_mux_addr_shift = EXYNOS4412_MUX_ADDR_SHIFT,
.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,
.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_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,
};
#define EXYNOS4412_TMU_DATA \
.threshold_falling = 10, \
@ -227,28 +142,18 @@ static const struct exynos_tmu_registers exynos4412_tmu_registers = {
.freq_clip_max = 400 * 1000, \
.temp_level = 95, \
}, \
.freq_tab_count = 2, \
.triminfo_reload[0] = EXYNOS_TRIMINFO_RELOAD_ENABLE, \
.registers = &exynos4412_tmu_registers, \
.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \
TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \
TMU_SUPPORT_EMUL_TIME)
#endif
.freq_tab_count = 2
#if defined(CONFIG_SOC_EXYNOS4412)
struct exynos_tmu_init_data const exynos4412_default_tmu_data = {
.tmu_data = {
{
EXYNOS4412_TMU_DATA,
.type = SOC_ARCH_EXYNOS4412,
.test_mux = EXYNOS4412_MUX_ADDR_VALUE,
},
},
.tmu_count = 1,
};
#endif
#if defined(CONFIG_SOC_EXYNOS5250)
struct exynos_tmu_init_data const exynos5250_default_tmu_data = {
.tmu_data = {
{
@ -258,31 +163,6 @@ struct exynos_tmu_init_data const exynos5250_default_tmu_data = {
},
.tmu_count = 1,
};
#endif
#if defined(CONFIG_SOC_EXYNOS5260)
static const struct exynos_tmu_registers exynos5260_tmu_registers = {
.triminfo_data = EXYNOS_TMU_REG_TRIMINFO,
.tmu_ctrl = EXYNOS_TMU_REG_CONTROL,
.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,
.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 = EXYNOS5260_TMU_REG_INTEN,
.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 = EXYNOS5260_TMU_REG_INTSTAT,
.tmu_intclear = EXYNOS5260_TMU_REG_INTCLEAR,
.emul_con = EXYNOS5260_EMUL_CON,
.emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT,
.emul_time_shift = EXYNOS_EMUL_TIME_SHIFT,
};
#define __EXYNOS5260_TMU_DATA \
.threshold_falling = 10, \
@ -319,13 +199,10 @@ static const struct exynos_tmu_registers exynos5260_tmu_registers = {
.temp_level = 103, \
}, \
.freq_tab_count = 2, \
.registers = &exynos5260_tmu_registers, \
#define EXYNOS5260_TMU_DATA \
__EXYNOS5260_TMU_DATA \
.type = SOC_ARCH_EXYNOS5260, \
.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_FALLING_TRIP | \
TMU_SUPPORT_READY_STATUS | TMU_SUPPORT_EMUL_TIME)
.type = SOC_ARCH_EXYNOS5260
struct exynos_tmu_init_data const exynos5260_default_tmu_data = {
.tmu_data = {
@ -337,82 +214,14 @@ struct exynos_tmu_init_data const exynos5260_default_tmu_data = {
},
.tmu_count = 5,
};
#endif
#if defined(CONFIG_SOC_EXYNOS5420)
static const struct exynos_tmu_registers exynos5420_tmu_registers = {
.triminfo_data = EXYNOS_TMU_REG_TRIMINFO,
.tmu_ctrl = EXYNOS_TMU_REG_CONTROL,
.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,
.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_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 Not availble in exynos5420 */
.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,
};
#define __EXYNOS5420_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, \
.non_hw_trigger_levels = 3, \
.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, \
.registers = &exynos5420_tmu_registers, \
#define EXYNOS5420_TMU_DATA \
__EXYNOS5420_TMU_DATA \
.type = SOC_ARCH_EXYNOS5250, \
.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_FALLING_TRIP | \
TMU_SUPPORT_READY_STATUS | TMU_SUPPORT_EMUL_TIME)
__EXYNOS5260_TMU_DATA \
.type = SOC_ARCH_EXYNOS5420
#define EXYNOS5420_TMU_DATA_SHARED \
__EXYNOS5420_TMU_DATA \
.type = SOC_ARCH_EXYNOS5420_TRIMINFO, \
.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_FALLING_TRIP | \
TMU_SUPPORT_READY_STATUS | TMU_SUPPORT_EMUL_TIME | \
TMU_SUPPORT_ADDRESS_MULTIPLE)
__EXYNOS5260_TMU_DATA \
.type = SOC_ARCH_EXYNOS5420_TRIMINFO
struct exynos_tmu_init_data const exynos5420_default_tmu_data = {
.tmu_data = {
@ -424,34 +233,6 @@ struct exynos_tmu_init_data const exynos5420_default_tmu_data = {
},
.tmu_count = 5,
};
#endif
#if defined(CONFIG_SOC_EXYNOS5440)
static const struct exynos_tmu_registers exynos5440_tmu_registers = {
.triminfo_data = EXYNOS5440_TMU_S0_7_TRIM,
.tmu_ctrl = EXYNOS5440_TMU_S0_7_CTRL,
.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,
.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_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, \
@ -471,10 +252,7 @@ static const struct exynos_tmu_registers exynos5440_tmu_registers = {
.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_ADDRESS_MULTIPLE),
.type = SOC_ARCH_EXYNOS5440
struct exynos_tmu_init_data const exynos5440_default_tmu_data = {
.tmu_data = {
@ -484,4 +262,3 @@ struct exynos_tmu_init_data const exynos5440_default_tmu_data = {
},
.tmu_count = 3,
};
#endif

View File

@ -1,159 +0,0 @@
/*
* 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
/* Exynos3250 specific registers */
#define EXYNOS_TMU_TRIMINFO_CON1 0x10
/* Exynos4210 specific registers */
#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44
#define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50
/* Exynos5250, Exynos4412, Exynos3250 specific registers */
#define EXYNOS_TMU_TRIMINFO_CON2 0x14
#define EXYNOS_THD_TEMP_RISE 0x50
#define EXYNOS_THD_TEMP_FALL 0x54
#define EXYNOS_EMUL_CON 0x80
#define EXYNOS_TRIMINFO_RELOAD_ENABLE 1
#define EXYNOS_TRIMINFO_25_SHIFT 0
#define EXYNOS_TRIMINFO_85_SHIFT 8
#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_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_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
/* Exynos5260 specific */
#define EXYNOS5260_TMU_REG_INTEN 0xC0
#define EXYNOS5260_TMU_REG_INTSTAT 0xC4
#define EXYNOS5260_TMU_REG_INTCLEAR 0xC8
#define EXYNOS5260_EMUL_CON 0x100
/* Exynos4412 specific */
#define EXYNOS4412_MUX_ADDR_VALUE 6
#define EXYNOS4412_MUX_ADDR_SHIFT 20
/*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_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_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_TH_RISE4_SHIFT 24
#define EXYNOS5440_EFUSE_SWAP_OFFSET 8
#if defined(CONFIG_SOC_EXYNOS3250)
extern struct exynos_tmu_init_data const exynos3250_default_tmu_data;
#define EXYNOS3250_TMU_DRV_DATA (&exynos3250_default_tmu_data)
#else
#define EXYNOS3250_TMU_DRV_DATA (NULL)
#endif
#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_EXYNOS4412)
extern struct exynos_tmu_init_data const exynos4412_default_tmu_data;
#define EXYNOS4412_TMU_DRV_DATA (&exynos4412_default_tmu_data)
#else
#define EXYNOS4412_TMU_DRV_DATA (NULL)
#endif
#if defined(CONFIG_SOC_EXYNOS5250)
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_EXYNOS5260)
extern struct exynos_tmu_init_data const exynos5260_default_tmu_data;
#define EXYNOS5260_TMU_DRV_DATA (&exynos5260_default_tmu_data)
#else
#define EXYNOS5260_TMU_DRV_DATA (NULL)
#endif
#if defined(CONFIG_SOC_EXYNOS5420)
extern struct exynos_tmu_init_data const exynos5420_default_tmu_data;
#define EXYNOS5420_TMU_DRV_DATA (&exynos5420_default_tmu_data)
#else
#define EXYNOS5420_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*/

View File

@ -0,0 +1,476 @@
/*
* Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
*
* Author:
* Mikko Perttunen <mperttunen@nvidia.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/thermal.h>
#include <soc/tegra/fuse.h>
#define SENSOR_CONFIG0 0
#define SENSOR_CONFIG0_STOP BIT(0)
#define SENSOR_CONFIG0_TALL_SHIFT 8
#define SENSOR_CONFIG0_TCALC_OVER BIT(4)
#define SENSOR_CONFIG0_OVER BIT(3)
#define SENSOR_CONFIG0_CPTR_OVER BIT(2)
#define SENSOR_CONFIG1 4
#define SENSOR_CONFIG1_TSAMPLE_SHIFT 0
#define SENSOR_CONFIG1_TIDDQ_EN_SHIFT 15
#define SENSOR_CONFIG1_TEN_COUNT_SHIFT 24
#define SENSOR_CONFIG1_TEMP_ENABLE BIT(31)
#define SENSOR_CONFIG2 8
#define SENSOR_CONFIG2_THERMA_SHIFT 16
#define SENSOR_CONFIG2_THERMB_SHIFT 0
#define SENSOR_PDIV 0x1c0
#define SENSOR_PDIV_T124 0x8888
#define SENSOR_HOTSPOT_OFF 0x1c4
#define SENSOR_HOTSPOT_OFF_T124 0x00060600
#define SENSOR_TEMP1 0x1c8
#define SENSOR_TEMP2 0x1cc
#define SENSOR_TEMP_MASK 0xffff
#define READBACK_VALUE_MASK 0xff00
#define READBACK_VALUE_SHIFT 8
#define READBACK_ADD_HALF BIT(7)
#define READBACK_NEGATE BIT(1)
#define FUSE_TSENSOR8_CALIB 0x180
#define FUSE_SPARE_REALIGNMENT_REG_0 0x1fc
#define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK 0x1fff
#define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK (0x1fff << 13)
#define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT 13
#define FUSE_TSENSOR8_CALIB_CP_TS_BASE_MASK 0x3ff
#define FUSE_TSENSOR8_CALIB_FT_TS_BASE_MASK (0x7ff << 10)
#define FUSE_TSENSOR8_CALIB_FT_TS_BASE_SHIFT 10
#define FUSE_SPARE_REALIGNMENT_REG_SHIFT_CP_MASK 0x3f
#define FUSE_SPARE_REALIGNMENT_REG_SHIFT_FT_MASK (0x1f << 21)
#define FUSE_SPARE_REALIGNMENT_REG_SHIFT_FT_SHIFT 21
#define NOMINAL_CALIB_FT_T124 105
#define NOMINAL_CALIB_CP_T124 25
struct tegra_tsensor_configuration {
u32 tall, tsample, tiddq_en, ten_count, pdiv, tsample_ate, pdiv_ate;
};
struct tegra_tsensor {
const struct tegra_tsensor_configuration *config;
u32 base, calib_fuse_offset;
/* Correction values used to modify values read from calibration fuses */
s32 fuse_corr_alpha, fuse_corr_beta;
};
struct tegra_thermctl_zone {
void __iomem *reg;
unsigned int shift;
};
static const struct tegra_tsensor_configuration t124_tsensor_config = {
.tall = 16300,
.tsample = 120,
.tiddq_en = 1,
.ten_count = 1,
.pdiv = 8,
.tsample_ate = 480,
.pdiv_ate = 8
};
static const struct tegra_tsensor t124_tsensors[] = {
{
.config = &t124_tsensor_config,
.base = 0xc0,
.calib_fuse_offset = 0x098,
.fuse_corr_alpha = 1135400,
.fuse_corr_beta = -6266900,
},
{
.config = &t124_tsensor_config,
.base = 0xe0,
.calib_fuse_offset = 0x084,
.fuse_corr_alpha = 1122220,
.fuse_corr_beta = -5700700,
},
{
.config = &t124_tsensor_config,
.base = 0x100,
.calib_fuse_offset = 0x088,
.fuse_corr_alpha = 1127000,
.fuse_corr_beta = -6768200,
},
{
.config = &t124_tsensor_config,
.base = 0x120,
.calib_fuse_offset = 0x12c,
.fuse_corr_alpha = 1110900,
.fuse_corr_beta = -6232000,
},
{
.config = &t124_tsensor_config,
.base = 0x140,
.calib_fuse_offset = 0x158,
.fuse_corr_alpha = 1122300,
.fuse_corr_beta = -5936400,
},
{
.config = &t124_tsensor_config,
.base = 0x160,
.calib_fuse_offset = 0x15c,
.fuse_corr_alpha = 1145700,
.fuse_corr_beta = -7124600,
},
{
.config = &t124_tsensor_config,
.base = 0x180,
.calib_fuse_offset = 0x154,
.fuse_corr_alpha = 1120100,
.fuse_corr_beta = -6000500,
},
{
.config = &t124_tsensor_config,
.base = 0x1a0,
.calib_fuse_offset = 0x160,
.fuse_corr_alpha = 1106500,
.fuse_corr_beta = -6729300,
},
};
struct tegra_soctherm {
struct reset_control *reset;
struct clk *clock_tsensor;
struct clk *clock_soctherm;
void __iomem *regs;
struct thermal_zone_device *thermctl_tzs[4];
};
struct tsensor_shared_calibration {
u32 base_cp, base_ft;
u32 actual_temp_cp, actual_temp_ft;
};
static int calculate_shared_calibration(struct tsensor_shared_calibration *r)
{
u32 val, shifted_cp, shifted_ft;
int err;
err = tegra_fuse_readl(FUSE_TSENSOR8_CALIB, &val);
if (err)
return err;
r->base_cp = val & FUSE_TSENSOR8_CALIB_CP_TS_BASE_MASK;
r->base_ft = (val & FUSE_TSENSOR8_CALIB_FT_TS_BASE_MASK)
>> FUSE_TSENSOR8_CALIB_FT_TS_BASE_SHIFT;
val = ((val & FUSE_SPARE_REALIGNMENT_REG_SHIFT_FT_MASK)
>> FUSE_SPARE_REALIGNMENT_REG_SHIFT_FT_SHIFT);
shifted_ft = sign_extend32(val, 4);
err = tegra_fuse_readl(FUSE_SPARE_REALIGNMENT_REG_0, &val);
if (err)
return err;
shifted_cp = sign_extend32(val, 5);
r->actual_temp_cp = 2 * NOMINAL_CALIB_CP_T124 + shifted_cp;
r->actual_temp_ft = 2 * NOMINAL_CALIB_FT_T124 + shifted_ft;
return 0;
}
static s64 div64_s64_precise(s64 a, s64 b)
{
s64 r, al;
/* Scale up for increased precision division */
al = a << 16;
r = div64_s64(al * 2 + 1, 2 * b);
return r >> 16;
}
static int
calculate_tsensor_calibration(const struct tegra_tsensor *sensor,
const struct tsensor_shared_calibration *shared,
u32 *calib)
{
u32 val;
s32 actual_tsensor_ft, actual_tsensor_cp, delta_sens, delta_temp,
mult, div;
s16 therma, thermb;
s64 tmp;
int err;
err = tegra_fuse_readl(sensor->calib_fuse_offset, &val);
if (err)
return err;
actual_tsensor_cp = (shared->base_cp * 64) + sign_extend32(val, 12);
val = (val & FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK)
>> FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT;
actual_tsensor_ft = (shared->base_ft * 32) + sign_extend32(val, 12);
delta_sens = actual_tsensor_ft - actual_tsensor_cp;
delta_temp = shared->actual_temp_ft - shared->actual_temp_cp;
mult = sensor->config->pdiv * sensor->config->tsample_ate;
div = sensor->config->tsample * sensor->config->pdiv_ate;
therma = div64_s64_precise((s64) delta_temp * (1LL << 13) * mult,
(s64) delta_sens * div);
tmp = (s64)actual_tsensor_ft * shared->actual_temp_cp -
(s64)actual_tsensor_cp * shared->actual_temp_ft;
thermb = div64_s64_precise(tmp, (s64)delta_sens);
therma = div64_s64_precise((s64)therma * sensor->fuse_corr_alpha,
(s64)1000000LL);
thermb = div64_s64_precise((s64)thermb * sensor->fuse_corr_alpha +
sensor->fuse_corr_beta, (s64)1000000LL);
*calib = ((u16)therma << SENSOR_CONFIG2_THERMA_SHIFT) |
((u16)thermb << SENSOR_CONFIG2_THERMB_SHIFT);
return 0;
}
static int enable_tsensor(struct tegra_soctherm *tegra,
const struct tegra_tsensor *sensor,
const struct tsensor_shared_calibration *shared)
{
void __iomem *base = tegra->regs + sensor->base;
unsigned int val;
u32 calib;
int err;
err = calculate_tsensor_calibration(sensor, shared, &calib);
if (err)
return err;
val = sensor->config->tall << SENSOR_CONFIG0_TALL_SHIFT;
writel(val, base + SENSOR_CONFIG0);
val = (sensor->config->tsample - 1) << SENSOR_CONFIG1_TSAMPLE_SHIFT;
val |= sensor->config->tiddq_en << SENSOR_CONFIG1_TIDDQ_EN_SHIFT;
val |= sensor->config->ten_count << SENSOR_CONFIG1_TEN_COUNT_SHIFT;
val |= SENSOR_CONFIG1_TEMP_ENABLE;
writel(val, base + SENSOR_CONFIG1);
writel(calib, base + SENSOR_CONFIG2);
return 0;
}
/*
* Translate from soctherm readback format to millicelsius.
* The soctherm readback format in bits is as follows:
* TTTTTTTT H______N
* where T's contain the temperature in Celsius,
* H denotes an addition of 0.5 Celsius and N denotes negation
* of the final value.
*/
static long translate_temp(u16 val)
{
long t;
t = ((val & READBACK_VALUE_MASK) >> READBACK_VALUE_SHIFT) * 1000;
if (val & READBACK_ADD_HALF)
t += 500;
if (val & READBACK_NEGATE)
t *= -1;
return t;
}
static int tegra_thermctl_get_temp(void *data, long *out_temp)
{
struct tegra_thermctl_zone *zone = data;
u32 val;
val = (readl(zone->reg) >> zone->shift) & SENSOR_TEMP_MASK;
*out_temp = translate_temp(val);
return 0;
}
static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = {
.get_temp = tegra_thermctl_get_temp,
};
static const struct of_device_id tegra_soctherm_of_match[] = {
{ .compatible = "nvidia,tegra124-soctherm" },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_soctherm_of_match);
struct thermctl_zone_desc {
unsigned int offset;
unsigned int shift;
};
static const struct thermctl_zone_desc t124_thermctl_temp_zones[] = {
{ SENSOR_TEMP1, 16 },
{ SENSOR_TEMP2, 16 },
{ SENSOR_TEMP1, 0 },
{ SENSOR_TEMP2, 0 }
};
static int tegra_soctherm_probe(struct platform_device *pdev)
{
struct tegra_soctherm *tegra;
struct thermal_zone_device *tz;
struct tsensor_shared_calibration shared_calib;
struct resource *res;
unsigned int i;
int err;
const struct tegra_tsensor *tsensors = t124_tsensors;
tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
if (!tegra)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tegra->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(tegra->regs))
return PTR_ERR(tegra->regs);
tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm");
if (IS_ERR(tegra->reset)) {
dev_err(&pdev->dev, "can't get soctherm reset\n");
return PTR_ERR(tegra->reset);
}
tegra->clock_tsensor = devm_clk_get(&pdev->dev, "tsensor");
if (IS_ERR(tegra->clock_tsensor)) {
dev_err(&pdev->dev, "can't get tsensor clock\n");
return PTR_ERR(tegra->clock_tsensor);
}
tegra->clock_soctherm = devm_clk_get(&pdev->dev, "soctherm");
if (IS_ERR(tegra->clock_soctherm)) {
dev_err(&pdev->dev, "can't get soctherm clock\n");
return PTR_ERR(tegra->clock_soctherm);
}
reset_control_assert(tegra->reset);
err = clk_prepare_enable(tegra->clock_soctherm);
if (err)
return err;
err = clk_prepare_enable(tegra->clock_tsensor);
if (err) {
clk_disable_unprepare(tegra->clock_soctherm);
return err;
}
reset_control_deassert(tegra->reset);
/* Initialize raw sensors */
err = calculate_shared_calibration(&shared_calib);
if (err)
goto disable_clocks;
for (i = 0; i < ARRAY_SIZE(t124_tsensors); ++i) {
err = enable_tsensor(tegra, tsensors + i, &shared_calib);
if (err)
goto disable_clocks;
}
writel(SENSOR_PDIV_T124, tegra->regs + SENSOR_PDIV);
writel(SENSOR_HOTSPOT_OFF_T124, tegra->regs + SENSOR_HOTSPOT_OFF);
/* Initialize thermctl sensors */
for (i = 0; i < ARRAY_SIZE(tegra->thermctl_tzs); ++i) {
struct tegra_thermctl_zone *zone =
devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL);
if (!zone) {
err = -ENOMEM;
goto unregister_tzs;
}
zone->reg = tegra->regs + t124_thermctl_temp_zones[i].offset;
zone->shift = t124_thermctl_temp_zones[i].shift;
tz = thermal_zone_of_sensor_register(&pdev->dev, i, zone,
&tegra_of_thermal_ops);
if (IS_ERR(tz)) {
err = PTR_ERR(tz);
dev_err(&pdev->dev, "failed to register sensor: %d\n",
err);
goto unregister_tzs;
}
tegra->thermctl_tzs[i] = tz;
}
return 0;
unregister_tzs:
while (i--)
thermal_zone_of_sensor_unregister(&pdev->dev,
tegra->thermctl_tzs[i]);
disable_clocks:
clk_disable_unprepare(tegra->clock_tsensor);
clk_disable_unprepare(tegra->clock_soctherm);
return err;
}
static int tegra_soctherm_remove(struct platform_device *pdev)
{
struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
unsigned int i;
for (i = 0; i < ARRAY_SIZE(tegra->thermctl_tzs); ++i) {
thermal_zone_of_sensor_unregister(&pdev->dev,
tegra->thermctl_tzs[i]);
}
clk_disable_unprepare(tegra->clock_tsensor);
clk_disable_unprepare(tegra->clock_soctherm);
return 0;
}
static struct platform_driver tegra_soctherm_driver = {
.probe = tegra_soctherm_probe,
.remove = tegra_soctherm_remove,
.driver = {
.name = "tegra-soctherm",
.of_match_table = tegra_soctherm_of_match,
},
};
module_platform_driver(tegra_soctherm_driver);
MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
MODULE_DESCRIPTION("NVIDIA Tegra SOCTHERM thermal management driver");
MODULE_LICENSE("GPL v2");

View File

@ -368,7 +368,7 @@ static void handle_critical_trips(struct thermal_zone_device *tz,
tz->ops->get_trip_temp(tz, trip, &trip_temp);
/* If we have not crossed the trip_temp, we do not care. */
if (tz->temperature < trip_temp)
if (trip_temp <= 0 || tz->temperature < trip_temp)
return;
trace_thermal_zone_trip(tz, trip, trip_type);
@ -757,6 +757,7 @@ policy_store(struct device *dev, struct device_attribute *attr,
snprintf(name, sizeof(name), "%s", buf);
mutex_lock(&thermal_governor_lock);
mutex_lock(&tz->lock);
gov = __find_governor(strim(name));
if (!gov)
@ -766,6 +767,7 @@ policy_store(struct device *dev, struct device_attribute *attr,
ret = count;
exit:
mutex_unlock(&tz->lock);
mutex_unlock(&thermal_governor_lock);
return ret;
}
@ -1835,10 +1837,10 @@ static int __init thermal_init(void)
exit_netlink:
genetlink_exit();
unregister_governors:
thermal_unregister_governors();
unregister_class:
class_unregister(&thermal_class);
unregister_governors:
thermal_unregister_governors();
error:
idr_destroy(&thermal_tz_idr);
idr_destroy(&thermal_cdev_idr);

View File

@ -89,9 +89,27 @@ static inline void thermal_gov_user_space_unregister(void) {}
#ifdef CONFIG_THERMAL_OF
int of_parse_thermal_zones(void);
void of_thermal_destroy_zones(void);
int of_thermal_get_ntrips(struct thermal_zone_device *);
bool of_thermal_is_trip_valid(struct thermal_zone_device *, int);
const struct thermal_trip * const
of_thermal_get_trip_points(struct thermal_zone_device *);
#else
static inline int of_parse_thermal_zones(void) { return 0; }
static inline void of_thermal_destroy_zones(void) { }
static inline int of_thermal_get_ntrips(struct thermal_zone_device *tz)
{
return 0;
}
static inline bool of_thermal_is_trip_valid(struct thermal_zone_device *tz,
int trip)
{
return 0;
}
static inline const struct thermal_trip * const
of_thermal_get_trip_points(struct thermal_zone_device *tz)
{
return NULL;
}
#endif
#endif /* __THERMAL_CORE_H__ */

View File

@ -286,6 +286,11 @@ static int ti_thermal_get_crit_temp(struct thermal_zone_device *thermal,
return ti_thermal_get_trip_temp(thermal, OMAP_TRIP_NUMBER - 1, temp);
}
static const struct thermal_zone_of_device_ops ti_of_thermal_ops = {
.get_temp = __ti_thermal_get_temp,
.get_trend = __ti_thermal_get_trend,
};
static struct thermal_zone_device_ops ti_thermal_ops = {
.get_temp = ti_thermal_get_temp,
.get_trend = ti_thermal_get_trend,
@ -333,8 +338,7 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id,
/* in case this is specified by DT */
data->ti_thermal = thermal_zone_of_sensor_register(bgp->dev, id,
data, __ti_thermal_get_temp,
__ti_thermal_get_trend);
data, &ti_of_thermal_ops);
if (IS_ERR(data->ti_thermal)) {
/* Create thermal zone */
data->ti_thermal = thermal_zone_device_register(domain,

View File

@ -0,0 +1,13 @@
/*
* This header provides constants for binding nvidia,tegra124-soctherm.
*/
#ifndef _DT_BINDINGS_THERMAL_TEGRA124_SOCTHERM_H
#define _DT_BINDINGS_THERMAL_TEGRA124_SOCTHERM_H
#define TEGRA124_SOCTHERM_SENSOR_CPU 0
#define TEGRA124_SOCTHERM_SENSOR_MEM 1
#define TEGRA124_SOCTHERM_SENSOR_GPU 2
#define TEGRA124_SOCTHERM_SENSOR_PLLX 3
#endif

View File

@ -0,0 +1,65 @@
/*
* linux/include/linux/clock_cooling.h
*
* Copyright (C) 2014 Eduardo Valentin <edubezval@gmail.com>
*
* Copyright (C) 2013 Texas Instruments Inc.
* Contact: Eduardo Valentin <eduardo.valentin@ti.com>
*
* Highly based on cpu_cooling.c.
* Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
* Copyright (C) 2012 Amit Daniel <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; 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.
*/
#ifndef __CPU_COOLING_H__
#define __CPU_COOLING_H__
#include <linux/of.h>
#include <linux/thermal.h>
#include <linux/cpumask.h>
#ifdef CONFIG_CLOCK_THERMAL
/**
* clock_cooling_register - function to create clock cooling device.
* @dev: struct device pointer to the device used as clock cooling device.
* @clock_name: string containing the clock used as cooling mechanism.
*/
struct thermal_cooling_device *
clock_cooling_register(struct device *dev, const char *clock_name);
/**
* clock_cooling_unregister - function to remove clock cooling device.
* @cdev: thermal cooling device pointer.
*/
void clock_cooling_unregister(struct thermal_cooling_device *cdev);
unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev,
unsigned long freq);
#else /* !CONFIG_CLOCK_THERMAL */
static inline struct thermal_cooling_device *
clock_cooling_register(struct device *dev, const char *clock_name)
{
return NULL;
}
static inline
void clock_cooling_unregister(struct thermal_cooling_device *cdev)
{
}
static inline
unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev,
unsigned long freq)
{
return THERMAL_CSTATE_INVALID;
}
#endif /* CONFIG_CLOCK_THERMAL */
#endif /* __CPU_COOLING_H__ */

View File

@ -29,10 +29,10 @@
#include <linux/idr.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <uapi/linux/thermal.h>
#define THERMAL_TRIPS_NONE -1
#define THERMAL_MAX_TRIPS 12
#define THERMAL_NAME_LENGTH 20
/* invalid cooling state */
#define THERMAL_CSTATE_INVALID -1UL
@ -49,11 +49,6 @@
#define MILLICELSIUS_TO_DECI_KELVIN_WITH_OFFSET(t, off) (((t) / 100) + (off))
#define MILLICELSIUS_TO_DECI_KELVIN(t) MILLICELSIUS_TO_DECI_KELVIN_WITH_OFFSET(t, 2732)
/* Adding event notification support elements */
#define THERMAL_GENL_FAMILY_NAME "thermal_event"
#define THERMAL_GENL_VERSION 0x01
#define THERMAL_GENL_MCAST_GROUP_NAME "thermal_mc_grp"
/* Default Thermal Governor */
#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE)
#define DEFAULT_THERMAL_GOVERNOR "step_wise"
@ -86,30 +81,6 @@ enum thermal_trend {
THERMAL_TREND_DROP_FULL, /* apply lowest cooling action */
};
/* Events supported by Thermal Netlink */
enum events {
THERMAL_AUX0,
THERMAL_AUX1,
THERMAL_CRITICAL,
THERMAL_DEV_FAULT,
};
/* attributes of thermal_genl_family */
enum {
THERMAL_GENL_ATTR_UNSPEC,
THERMAL_GENL_ATTR_EVENT,
__THERMAL_GENL_ATTR_MAX,
};
#define THERMAL_GENL_ATTR_MAX (__THERMAL_GENL_ATTR_MAX - 1)
/* commands supported by the thermal_genl_family */
enum {
THERMAL_GENL_CMD_UNSPEC,
THERMAL_GENL_CMD_EVENT,
__THERMAL_GENL_CMD_MAX,
};
#define THERMAL_GENL_CMD_MAX (__THERMAL_GENL_CMD_MAX - 1)
struct thermal_zone_device_ops {
int (*bind) (struct thermal_zone_device *,
struct thermal_cooling_device *);
@ -289,19 +260,49 @@ struct thermal_genl_event {
enum events event;
};
/**
* struct thermal_zone_of_device_ops - scallbacks for handling DT based zones
*
* Mandatory:
* @get_temp: a pointer to a function that reads the sensor temperature.
*
* Optional:
* @get_trend: a pointer to a function that reads the sensor temperature trend.
* @set_emul_temp: a pointer to a function that sets sensor emulated
* temperature.
*/
struct thermal_zone_of_device_ops {
int (*get_temp)(void *, long *);
int (*get_trend)(void *, long *);
int (*set_emul_temp)(void *, unsigned long);
};
/**
* struct thermal_trip - representation of a point in temperature domain
* @np: pointer to struct device_node that this trip point was created from
* @temperature: temperature value in miliCelsius
* @hysteresis: relative hysteresis in miliCelsius
* @type: trip point type
*/
struct thermal_trip {
struct device_node *np;
unsigned long int temperature;
unsigned long int hysteresis;
enum thermal_trip_type type;
};
/* Function declarations */
#ifdef CONFIG_THERMAL_OF
struct thermal_zone_device *
thermal_zone_of_sensor_register(struct device *dev, int id,
void *data, int (*get_temp)(void *, long *),
int (*get_trend)(void *, long *));
thermal_zone_of_sensor_register(struct device *dev, int id, void *data,
const struct thermal_zone_of_device_ops *ops);
void thermal_zone_of_sensor_unregister(struct device *dev,
struct thermal_zone_device *tz);
#else
static inline struct thermal_zone_device *
thermal_zone_of_sensor_register(struct device *dev, int id,
void *data, int (*get_temp)(void *, long *),
int (*get_trend)(void *, long *))
thermal_zone_of_sensor_register(struct device *dev, int id, void *data,
const struct thermal_zone_of_device_ops *ops)
{
return NULL;
}

View File

@ -387,6 +387,7 @@ header-y += tcp.h
header-y += tcp_metrics.h
header-y += telephony.h
header-y += termios.h
header-y += thermal.h
header-y += time.h
header-y += times.h
header-y += timex.h

View File

@ -0,0 +1,35 @@
#ifndef _UAPI_LINUX_THERMAL_H
#define _UAPI_LINUX_THERMAL_H
#define THERMAL_NAME_LENGTH 20
/* Adding event notification support elements */
#define THERMAL_GENL_FAMILY_NAME "thermal_event"
#define THERMAL_GENL_VERSION 0x01
#define THERMAL_GENL_MCAST_GROUP_NAME "thermal_mc_grp"
/* Events supported by Thermal Netlink */
enum events {
THERMAL_AUX0,
THERMAL_AUX1,
THERMAL_CRITICAL,
THERMAL_DEV_FAULT,
};
/* attributes of thermal_genl_family */
enum {
THERMAL_GENL_ATTR_UNSPEC,
THERMAL_GENL_ATTR_EVENT,
__THERMAL_GENL_ATTR_MAX,
};
#define THERMAL_GENL_ATTR_MAX (__THERMAL_GENL_ATTR_MAX - 1)
/* commands supported by the thermal_genl_family */
enum {
THERMAL_GENL_CMD_UNSPEC,
THERMAL_GENL_CMD_EVENT,
__THERMAL_GENL_CMD_MAX,
};
#define THERMAL_GENL_CMD_MAX (__THERMAL_GENL_CMD_MAX - 1)
#endif /* _UAPI_LINUX_THERMAL_H */