blob: ab2c92ad0d0596d208781a4ca1fbf84173ebc205 [file] [log] [blame]
/*
* NVIDIA Tegra SOC - temperature sensor driver
*
* Copyright (C) 2011-2012 NVIDIA Corporation
*
* 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/slab.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/hwmon.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/err.h>
#include <linux/spinlock.h>
#include <linux/hwmon-sysfs.h>
#include <linux/hwmon.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/clk/tegra.h>
#include <mach/tsensor.h>
#include <mach/tegra_fuse.h>
/* HACK: These need to come from DT */
#include "../../arch/arm/mach-tegra/iomap.h"
/* macro to enable tsensor hw reset */
/* FIXME: till tsensor temperature is reliable this should be 0 */
#define ENABLE_TSENSOR_HW_RESET 1
/* tsensor instance used for temperature calculation */
#define TSENSOR_FUSE_REV1 8
#define TSENSOR_FUSE_REV2 21
/* version where tsensor temperature reading is accurate */
#define STABLE_TSENSOR_FUSE_REV TSENSOR_FUSE_REV2
/* We have multiple tsensor instances with following registers */
#define SENSOR_CFG0 0x40
#define SENSOR_CFG1 0x48
#define SENSOR_CFG2 0x4c
#define SENSOR_STATUS0 0x58
#define SENSOR_TS_STATUS1 0x5c
#define SENSOR_TS_STATUS2 0x60
/* interrupt mask in tsensor status register */
#define TSENSOR_SENSOR_X_STATUS0_0_INTR_MASK (1 << 8)
#define SENSOR_CFG0_M_MASK 0xffff
#define SENSOR_CFG0_M_SHIFT 8
#define SENSOR_CFG0_N_MASK 0xff
#define SENSOR_CFG0_N_SHIFT 24
#define SENSOR_CFG0_RST_INTR_SHIFT 6
#define SENSOR_CFG0_HW_DIV2_INTR_SHIFT 5
#define SENSOR_CFG0_OVERFLOW_INTR 4
#define SENSOR_CFG0_DVFS_INTR_SHIFT 3
#define SENSOR_CFG0_RST_ENABLE_SHIFT 2
#define SENSOR_CFG0_HW_DIV2_ENABLE_SHIFT 1
#define SENSOR_CFG0_STOP_SHIFT 0
#define SENSOR_CFG_X_TH_X_MASK 0xffff
#define SENSOR_CFG1_TH2_SHIFT 16
#define SENSOR_CFG1_TH1_SHIFT 0
#define SENSOR_CFG2_TH3_SHIFT 0
#define SENSOR_CFG2_TH0_SHIFT 16
#define SENSOR_STATUS_AVG_VALID_SHIFT 10
#define SENSOR_STATUS_CURR_VALID_SHIFT 9
#define STATE_MASK 0x7
#define STATUS0_STATE_SHIFT 0
#define STATUS0_PREV_STATE_SHIFT 4
#define LOCAL_STR_SIZE1 60
#define MAX_STR_LINE 100
#define MAX_TSENSOR_LOOP1 (1000 * 2)
#define TSENSOR_COUNTER_TOLERANCE 100
#define SENSOR_CTRL_RST_SHIFT 1
#define RST_SRC_MASK 0x7
#define RST_SRC_SENSOR 2
#define CCLK_G_BURST_POLICY_REG_REL_OFFSET 0x368
#define TSENSOR_SLOWDOWN_BIT 23
/* macros used for temperature calculations */
/* assumed get_temperature_int and get_temperature_fraction
* calculate up to 6 decimal places. print temperature
* in code assumes 6 decimal place formatting */
#define get_temperature_int(X) ((X) / 1000000)
#define get_temperature_fraction(X) (((int)(abs(X))) % 1000000)
#define get_temperature_round(X) DIV_ROUND_CLOSEST(X, 1000000)
#define MILLICELSIUS_TO_CELSIUS(i) ((i) / 1000)
#define CELSIUS_TO_MILLICELSIUS(i) ((i) * 1000)
#define TSENSOR_MILLI_CELSIUS(x) \
DIV_ROUND_CLOSEST((x), 1000)
#define get_ts_state(data) tsensor_get_reg_field(data,\
((data->instance << 16) | SENSOR_STATUS0), \
STATUS0_STATE_SHIFT, STATE_MASK)
/* tsensor states */
enum ts_state {
TS_INVALID = 0,
TS_LEVEL0,
TS_LEVEL1,
TS_LEVEL2,
TS_LEVEL3,
TS_OVERFLOW,
TS_MAX_STATE = TS_OVERFLOW
};
enum {
/* temperature is sensed from 2 points on tegra */
TSENSOR_COUNT = 2,
TSENSOR_INSTANCE1 = 0,
TSENSOR_INSTANCE2 = 1,
/* divide by 2 temperature threshold */
DIV2_CELSIUS_TEMP_THRESHOLD_DEFAULT = 70,
/* reset chip temperature threshold */
RESET_CELSIUS_TEMP_THRESHOLD_DEFAULT = 75,
/* tsensor frequency in Hz for clk src CLK_M and divisor=24 */
DEFAULT_TSENSOR_CLK_HZ = 500000,
DEFAULT_TSENSOR_N = 255,
DEFAULT_TSENSOR_M = 12500,
/* tsensor instance offset */
TSENSOR_INSTANCE_OFFSET = 0x40,
MIN_THRESHOLD = 0x0,
MAX_THRESHOLD = 0xffff,
DEFAULT_THRESHOLD_TH0 = MAX_THRESHOLD,
DEFAULT_THRESHOLD_TH1 = MAX_THRESHOLD,
DEFAULT_THRESHOLD_TH2 = MAX_THRESHOLD,
DEFAULT_THRESHOLD_TH3 = MAX_THRESHOLD,
};
/* constants used to implement sysfs interface */
enum tsensor_params {
TSENSOR_PARAM_TH1 = 0,
TSENSOR_PARAM_TH2,
TSENSOR_PARAM_TH3,
TSENSOR_TEMPERATURE,
TSENSOR_STATE,
TSENSOR_LIMITS,
};
enum tsensor_thresholds {
TSENSOR_TH0 = 0,
TSENSOR_TH1,
TSENSOR_TH2,
TSENSOR_TH3
};
/*
* For each registered chip, we need to keep some data in memory.
* The structure is dynamically allocated.
*/
struct tegra_tsensor_data {
struct tegra_tsensor_platform_data plat_data;
struct delayed_work work;
struct workqueue_struct *workqueue;
struct mutex mutex;
struct device *hwmon_dev;
spinlock_t tsensor_lock;
struct clk *dev_clk;
/* tsensor register space */
void __iomem *base;
unsigned long phys;
unsigned long phys_end;
/* pmc register space */
void __iomem *pmc_rst_base;
unsigned long pmc_phys;
unsigned long pmc_phys_end;
/* clk register space */
void __iomem *clk_rst_base;
int irq;
/* save configuration before suspend and restore after resume */
unsigned int config0[TSENSOR_COUNT];
unsigned int config1[TSENSOR_COUNT];
unsigned int config2[TSENSOR_COUNT];
/* temperature readings from instance tsensor - 0/1 */
unsigned int instance;
s64 A_e_minus12;
int B_e_minus6;
unsigned int fuse_T1;
unsigned int fuse_F1;
unsigned int fuse_T2;
unsigned int fuse_F2;
/* Quadratic fit coefficients: m=-0.003512 n=1.528943 p=-11.1 */
int m_e_minus6;
int n_e_minus6;
int p_e_minus2;
long current_hi_limit;
long current_lo_limit;
bool is_edp_supported;
struct thermal_zone_device *thz;
};
enum {
TSENSOR_COEFF_SET1 = 0,
TSENSOR_COEFF_SET2,
TSENSOR_COEFF_END
};
struct tegra_tsensor_coeff {
int e_minus6_m;
int e_minus6_n;
int e_minus2_p;
};
static struct tegra_tsensor_coeff coeff_table[] = {
/* Quadratic fit coefficients: m=-0.002775 n=1.338811 p=-7.30 */
[TSENSOR_COEFF_SET1] = {
-2775,
1338811,
-730
},
/* Quadratic fit coefficients: m=-0.003512 n=1.528943 p=-11.1 */
[TSENSOR_COEFF_SET2] = {
-3512,
1528943,
-1110
}
/* FIXME: add tsensor coefficients after chip characterization */
};
/* pTemperature returned in 100 * Celsius */
static int tsensor_count_2_temp(struct tegra_tsensor_data *data,
unsigned int count, int *p_temperature);
static unsigned int tsensor_get_threshold_counter(
struct tegra_tsensor_data *data, int temp);
/* tsensor register access functions */
static void tsensor_writel(struct tegra_tsensor_data *data, u32 val,
unsigned long reg)
{
unsigned int reg_offset = reg & 0xffff;
unsigned char inst = (reg >> 16) & 0xffff;
writel(val, data->base + (inst * TSENSOR_INSTANCE_OFFSET) +
reg_offset);
return;
}
static unsigned int tsensor_readl(struct tegra_tsensor_data *data,
unsigned long reg)
{
unsigned int reg_offset = reg & 0xffff;
unsigned char inst = (reg >> 16) & 0xffff;
return readl(data->base +
(inst * TSENSOR_INSTANCE_OFFSET) + reg_offset);
}
static unsigned int tsensor_get_reg_field(
struct tegra_tsensor_data *data, unsigned int reg,
unsigned int shift, unsigned int mask)
{
unsigned int reg_val;
reg_val = tsensor_readl(data, reg);
return (reg_val & (mask << shift)) >> shift;
}
static int tsensor_set_reg_field(
struct tegra_tsensor_data *data, unsigned int value,
unsigned int reg, unsigned int shift, unsigned int mask)
{
unsigned int reg_val;
unsigned int rd_val;
reg_val = tsensor_readl(data, reg);
reg_val &= ~(mask << shift);
reg_val |= ((value & mask) << shift);
tsensor_writel(data, reg_val, reg);
rd_val = tsensor_readl(data, reg);
if (rd_val == reg_val)
return 0;
else
return -EINVAL;
}
/* enable argument is true to enable reset, false disables pmc reset */
static void pmc_rst_enable(struct tegra_tsensor_data *data, bool enable)
{
unsigned int val;
/* mapped first pmc reg is SENSOR_CTRL */
val = readl(data->pmc_rst_base);
if (enable)
val |= (1 << SENSOR_CTRL_RST_SHIFT);
else
val &= ~(1 << SENSOR_CTRL_RST_SHIFT);
writel(val, data->pmc_rst_base);
}
/* true returned when pmc reset source is tsensor */
static bool pmc_check_rst_sensor(struct tegra_tsensor_data *data)
{
unsigned int val;
unsigned char src;
val = readl(data->pmc_rst_base + 4);
src = (unsigned char)(val & RST_SRC_MASK);
if (src == RST_SRC_SENSOR)
return true;
else
return false;
}
/*
* function to get chip revision specific tsensor coefficients
* obtained after chip characterization
*/
static void get_chip_tsensor_coeff(struct tegra_tsensor_data *data)
{
unsigned short coeff_index;
coeff_index = TSENSOR_COEFF_SET1;
if (data->instance == TSENSOR_INSTANCE1)
coeff_index = TSENSOR_COEFF_SET2;
data->m_e_minus6 = coeff_table[coeff_index].e_minus6_m;
data->n_e_minus6 = coeff_table[coeff_index].e_minus6_n;
data->p_e_minus2 = coeff_table[coeff_index].e_minus2_p;
pr_info("tsensor coeff: m=%d*10^-6,n=%d*10^-6,p=%d*10^-2\n",
data->m_e_minus6, data->n_e_minus6, data->p_e_minus2);
}
/* tsensor counter read function */
static int tsensor_read_counter(
struct tegra_tsensor_data *data,
unsigned int *p_counter)
{
unsigned int status_reg;
unsigned int config0;
int iter_count = 0;
const int max_loop = 50;
do {
config0 = tsensor_readl(data, ((data->instance << 16) |
SENSOR_CFG0));
if (config0 & (1 << SENSOR_CFG0_STOP_SHIFT)) {
dev_dbg(data->hwmon_dev, "Error: tsensor "
"counter read with STOP bit not supported\n");
*p_counter = 0;
return 0;
}
status_reg = tsensor_readl(data,
(data->instance << 16) | SENSOR_STATUS0);
if (status_reg & (1 <<
SENSOR_STATUS_CURR_VALID_SHIFT)) {
*p_counter = tsensor_readl(data, (data->instance
<< 16) | SENSOR_TS_STATUS1);
break;
}
if (!(iter_count % 10))
dev_dbg(data->hwmon_dev, "retry %d\n", iter_count);
msleep(21);
iter_count++;
} while (iter_count < max_loop);
if (iter_count == max_loop)
return -ENODEV;
return 0;
}
/* tsensor threshold print function */
static void dump_threshold(struct tegra_tsensor_data *data)
{
unsigned int TH_2_1, TH_0_3;
unsigned int curr_avg;
int err;
TH_2_1 = tsensor_readl(data, (data->instance << 16) | SENSOR_CFG1);
TH_0_3 = tsensor_readl(data, (data->instance << 16) | SENSOR_CFG2);
dev_dbg(data->hwmon_dev, "Tsensor: TH_2_1=0x%x, "
"TH_0_3=0x%x\n", TH_2_1, TH_0_3);
err = tsensor_read_counter(data, &curr_avg);
if (err < 0)
pr_err("Error: tsensor counter read, "
"err=%d\n", err);
else
dev_dbg(data->hwmon_dev, "Tsensor: "
"curr_avg=0x%x\n", curr_avg);
}
static int tsensor_get_temperature(
struct tegra_tsensor_data *data,
int *pTemp, unsigned int *pCounter)
{
int err = 0;
unsigned int curr_avg;
err = tsensor_read_counter(data, &curr_avg);
if (err < 0)
goto error;
*pCounter = ((curr_avg & 0xFFFF0000) >> 16);
err = tsensor_count_2_temp(data, *pCounter, pTemp);
error:
return err;
}
static ssize_t tsensor_show_state(struct device *dev,
struct device_attribute *da, char *buf)
{
int state;
struct tegra_tsensor_data *data = dev_get_drvdata(dev);
state = get_ts_state(data);
return snprintf(buf, 50, "%d\n", state);
}
static ssize_t tsensor_show_limits(struct device *dev,
struct device_attribute *da, char *buf)
{
struct tegra_tsensor_data *data = dev_get_drvdata(dev);
return snprintf(buf, 50, "%ld %ld\n",
data->current_lo_limit, data->current_hi_limit);
}
/* tsensor temperature show function */
static ssize_t tsensor_show_counters(struct device *dev,
struct device_attribute *da, char *buf)
{
unsigned int curr_avg;
char err_str[] = "error-sysfs-counter-read\n";
char fixed_str[MAX_STR_LINE];
struct tegra_tsensor_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
int err;
int temp;
if (attr->index == TSENSOR_TEMPERATURE) {
/* use current counter value to calculate temperature */
err = tsensor_read_counter(data, &curr_avg);
if (err < 0)
goto error;
err = tsensor_count_2_temp(data,
((curr_avg & 0xFFFF0000) >> 16), &temp);
if (err < 0)
goto error;
dev_vdbg(data->hwmon_dev, "%s has curr_avg=0x%x, "
"temp0=%d\n", __func__, curr_avg, temp);
snprintf(buf, (((LOCAL_STR_SIZE1 << 1) + 3) +
strlen(fixed_str)),
"%d.%06dC %#x\n",
get_temperature_int(temp),
get_temperature_fraction(temp),
((curr_avg & 0xFFFF0000) >> 16));
}
return strlen(buf);
error:
return snprintf(buf, strlen(err_str), "%s", err_str);
}
/* utility function to check hw clock divide by 2 condition */
static bool cclkg_check_hwdiv2_sensor(struct tegra_tsensor_data *data)
{
unsigned int val;
val = readl(IO_ADDRESS(TEGRA_CLK_RESET_BASE +
CCLK_G_BURST_POLICY_REG_REL_OFFSET));
if ((1 << TSENSOR_SLOWDOWN_BIT) & val) {
dev_err(data->hwmon_dev, "Warning: ***** tsensor "
"slowdown bit detected\n");
return true;
} else {
return false;
}
}
/*
* function with table to return register, field shift and mask
* values for supported parameters
*/
static int get_param_values(
struct tegra_tsensor_data *data, unsigned int indx,
unsigned int *p_reg, unsigned int *p_sft, unsigned int *p_msk,
char *info, size_t info_len)
{
switch (indx) {
case TSENSOR_PARAM_TH1:
*p_reg = ((data->instance << 16) | SENSOR_CFG1);
*p_sft = SENSOR_CFG1_TH1_SHIFT;
*p_msk = SENSOR_CFG_X_TH_X_MASK;
snprintf(info, info_len, "TH1[%d]: ",
data->instance);
break;
case TSENSOR_PARAM_TH2:
*p_reg = ((data->instance << 16) | SENSOR_CFG1);
*p_sft = SENSOR_CFG1_TH2_SHIFT;
*p_msk = SENSOR_CFG_X_TH_X_MASK;
snprintf(info, info_len, "TH2[%d]: ",
data->instance);
break;
case TSENSOR_PARAM_TH3:
*p_reg = ((data->instance << 16) | SENSOR_CFG2);
*p_sft = SENSOR_CFG2_TH3_SHIFT;
*p_msk = SENSOR_CFG_X_TH_X_MASK;
snprintf(info, info_len, "TH3[%d]: ",
data->instance);
break;
default:
return -ENOENT;
}
return 0;
}
/* tsensor driver sysfs show function */
static ssize_t show_tsensor_param(struct device *dev,
struct device_attribute *da,
char *buf)
{
unsigned int val;
struct tegra_tsensor_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
unsigned int reg;
unsigned int sft;
unsigned int msk;
int err;
int temp;
char info[LOCAL_STR_SIZE1];
err = get_param_values(data, attr->index, &reg, &sft, &msk,
info, sizeof(info));
if (err < 0)
goto labelErr;
val = tsensor_get_reg_field(data, reg, sft, msk);
if (val == MAX_THRESHOLD)
snprintf(buf, PAGE_SIZE, "%s un-initialized threshold\n", info);
else {
err = tsensor_count_2_temp(data, val, &temp);
if (err != 0)
goto labelErr;
snprintf(buf, PAGE_SIZE, "%s threshold: %d.%06d Celsius\n",
info, get_temperature_int(temp),
get_temperature_fraction(temp));
}
return strlen(buf);
labelErr:
snprintf(buf, PAGE_SIZE, "ERROR:");
return strlen(buf);
}
/* tsensor driver sysfs store function */
static ssize_t set_tsensor_param(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
{
int num;
struct tegra_tsensor_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
unsigned int reg;
unsigned int sft;
unsigned int msk;
int err;
unsigned int counter;
unsigned int val;
char info[LOCAL_STR_SIZE1];
if (kstrtoint(buf, 0, &num)) {
dev_err(dev, "file: %s, line=%d return %s()\n",
__FILE__, __LINE__, __func__);
return -EINVAL;
}
counter = tsensor_get_threshold_counter(data, num);
err = get_param_values(data, attr->index, &reg, &sft, &msk,
info, sizeof(info));
if (err < 0)
goto labelErr;
err = tsensor_set_reg_field(data, counter, reg, sft, msk);
if (err < 0)
goto labelErr;
/* TH2 clk divide check */
if (attr->index == TSENSOR_PARAM_TH2) {
msleep(21);
(void)cclkg_check_hwdiv2_sensor(data);
}
val = tsensor_get_reg_field(data, reg, sft, msk);
dev_dbg(dev, "%s 0x%x\n", info, val);
return count;
labelErr:
dev_err(dev, "file: %s, line=%d, %s(), error=0x%x\n", __FILE__,
__LINE__, __func__, err);
return 0;
}
static struct sensor_device_attribute tsensor_nodes[] = {
SENSOR_ATTR(tsensor_TH1, S_IRUGO | S_IWUSR,
show_tsensor_param, set_tsensor_param, TSENSOR_PARAM_TH1),
SENSOR_ATTR(tsensor_TH2, S_IRUGO | S_IWUSR,
show_tsensor_param, set_tsensor_param, TSENSOR_PARAM_TH2),
SENSOR_ATTR(tsensor_TH3, S_IRUGO | S_IWUSR,
show_tsensor_param, set_tsensor_param, TSENSOR_PARAM_TH3),
SENSOR_ATTR(tsensor_temperature, S_IRUGO | S_IWUSR,
tsensor_show_counters, NULL, TSENSOR_TEMPERATURE),
SENSOR_ATTR(tsensor_state, S_IRUGO | S_IWUSR,
tsensor_show_state, NULL, TSENSOR_STATE),
SENSOR_ATTR(tsensor_limits, S_IRUGO | S_IWUSR,
tsensor_show_limits, NULL, TSENSOR_LIMITS),
};
int tsensor_thermal_get_temp(struct tegra_tsensor_data *data,
long *milli_temp)
{
int counter, temp, err;
int temp_state, ts_state;
err = tsensor_get_temperature(data,
&temp,
&counter);
if (err)
return err;
/* temperature is in milli-Celsius */
temp = TSENSOR_MILLI_CELSIUS(temp);
mutex_lock(&data->mutex);
/* This section of logic is done in order to make sure that
* the temperature read corresponds to the current hw state.
* If it is not, return the nearest temperature
*/
if ((data->current_lo_limit != 0) ||
(data->current_hi_limit)) {
if (temp <= data->current_lo_limit)
temp_state = TS_LEVEL0;
else if (temp < data->current_hi_limit)
temp_state = TS_LEVEL1;
else
temp_state = TS_LEVEL2;
ts_state = get_ts_state(data);
if (ts_state != temp_state) {
switch (ts_state) {
case TS_LEVEL0:
temp = data->current_lo_limit - 1;
break;
case TS_LEVEL1:
if (temp_state == TS_LEVEL0)
temp = data->current_lo_limit + 1;
else
temp = data->current_hi_limit - 1;
break;
case TS_LEVEL2:
temp = data->current_hi_limit + 1;
break;
}
}
}
mutex_unlock(&data->mutex);
*milli_temp = temp;
return 0;
}
/* tsensor driver interrupt handler */
static irqreturn_t tegra_tsensor_isr(int irq, void *arg_data)
{
struct tegra_tsensor_data *data =
(struct tegra_tsensor_data *)arg_data;
unsigned long flags;
unsigned int val;
int new_state;
spin_lock_irqsave(&data->tsensor_lock, flags);
val = tsensor_readl(data, (data->instance << 16) | SENSOR_STATUS0);
if (val & TSENSOR_SENSOR_X_STATUS0_0_INTR_MASK) {
new_state = get_ts_state(data);
/* counter overflow check */
if (new_state == TS_OVERFLOW)
dev_err(data->hwmon_dev, "Warning: "
"***** OVERFLOW tsensor\n");
/* We only care if we go above hi or below low thresholds */
if (data->is_edp_supported && new_state != TS_LEVEL1)
queue_delayed_work(data->workqueue, &data->work, 0);
}
tsensor_writel(data, val, (data->instance << 16) | SENSOR_STATUS0);
spin_unlock_irqrestore(&data->tsensor_lock, flags);
return IRQ_HANDLED;
}
/*
* function to read fuse registers and give - T1, T2, F1 and F2
*/
static int read_tsensor_fuse_regs(struct tegra_tsensor_data *data)
{
unsigned int reg1;
unsigned int T1 = 0, T2 = 0;
unsigned int spare_bits;
int err;
/* read tsensor calibration register */
/*
* High (~90 DegC) Temperature Calibration value (upper 16 bits of
* FUSE_TSENSOR_CALIB_0) - F2
* Low (~25 deg C) Temperature Calibration value (lower 16 bits of
* FUSE_TSENSOR_CALIB_0) - F1
*/
err = tegra_fuse_get_tsensor_calibration_data(&reg1);
if (err)
goto errLabel;
data->fuse_F1 = reg1 & 0xFFFF;
data->fuse_F2 = (reg1 >> 16) & 0xFFFF;
err = tegra_fuse_get_tsensor_spare_bits(&spare_bits);
if (err) {
pr_err("tsensor spare bit fuse read error=%d\n", err);
goto errLabel;
}
/*
* FUSE_TJ_ADT_LOWT = T1, FUSE_TJ_ADJ = T2
*/
/*
* Low temp is:
* FUSE_TJ_ADT_LOWT = bits [20:14] or’ed with bits [27:21]
*/
T1 = ((spare_bits >> 14) & 0x7F) |
((spare_bits >> 21) & 0x7F);
dev_vdbg(data->hwmon_dev, "Tsensor low temp (T1) fuse :\n");
/*
* High temp is:
* FUSE_TJ_ADJ = bits [6:0] or’ed with bits [13:7]
*/
dev_vdbg(data->hwmon_dev, "Tsensor low temp (T2) fuse :\n");
T2 = (spare_bits & 0x7F) | ((spare_bits >> 7) & 0x7F);
pr_info("Tsensor fuse calibration F1=%d(%#x), F2=%d(%#x), T1=%d, T2=%d\n",
data->fuse_F1, data->fuse_F1,
data->fuse_F2, data->fuse_F2, T1, T2);
data->fuse_T1 = T1;
data->fuse_T2 = T2;
return 0;
errLabel:
return err;
}
/* function to calculate interim temperature */
static int calc_interim_temp(struct tegra_tsensor_data *data,
unsigned int counter, s64 *p_interim_temp)
{
s64 val1_64;
s64 val2;
u32 temp_rem;
bool is_neg;
u32 divisor;
/*
* T-int = A * Counter + B
* (Counter is the sensor frequency output)
*/
if ((data->fuse_F2 - data->fuse_F1) <= (data->fuse_T2 -
data->fuse_T1)) {
dev_err(data->hwmon_dev, "Error: F2=%d, F1=%d "
"difference unexpectedly low. "
"Aborting temperature processing\n", data->fuse_F2,
data->fuse_F1);
return -EINVAL;
} else {
/* expression modified after assuming s_A is 10^6 times,
* s_B is 10^2 times and want end result to be 10^2 times
* actual value
*/
val1_64 = (data->A_e_minus12 * counter);
dev_dbg(data->hwmon_dev, "A_e_-12*counter=%lld\n", val1_64);
val2 = (s64)data->B_e_minus6 * 1000000ULL;
dev_dbg(data->hwmon_dev, "B_e_-12=%lld\n", val2);
val2 += val1_64;
dev_dbg(data->hwmon_dev, "A_counter+B=%lld\n", val2);
is_neg = false;
if (val2 < 0) {
is_neg = true;
val2 *= -1;
}
divisor = 1000000;
temp_rem = do_div(val2, divisor);
if (temp_rem > (divisor >> 1))
val2++;
if (is_neg)
val2 *= -1;
*p_interim_temp = val2;
dev_dbg(data->hwmon_dev, "counter=%d, interim_temp=%lld\n",
counter, *p_interim_temp);
}
return 0;
}
/*
* function to calculate final temperature, given
* interim temperature
*/
static void calc_final_temp(struct tegra_tsensor_data *data,
s64 interim_temp, int *p_final_temp)
{
s64 temp1_64, temp2_64, temp_64, temp1_64_rem;
u32 temp_rem_32;
u32 divisor;
u64 divisor_64;
bool is_neg;
/*
* T-final = m * T-int ^2 + n * T-int + p
* m = -0.002775
* n = 1.338811
* p = -7.3
*/
temp1_64 = (interim_temp * interim_temp);
/* always positive as squaring value */
/* losing accuracy here */
divisor = 10000;
/* temp1_64 contains quotient and returns remainder */
temp_rem_32 = do_div(temp1_64, divisor);
if (temp_rem_32 > (divisor >> 1))
temp1_64++;
temp1_64 *= (s64)data->m_e_minus6;
dev_dbg(data->hwmon_dev, "m_T-interim^2_e^14=%lld\n", temp1_64);
temp1_64_rem = (s64)data->m_e_minus6 * (s64)temp_rem_32;
is_neg = false;
if (temp1_64_rem < 0) {
is_neg = true;
temp1_64_rem *= -1;
}
temp_rem_32 = do_div(temp1_64_rem, divisor);
if (temp_rem_32 > (divisor >> 1))
temp1_64_rem++;
if (is_neg)
temp1_64_rem *= -1;
/* temp1_64 is m * t-int * t-int * 10^14 */
temp2_64 = (s64)data->n_e_minus6 * interim_temp * 100;
dev_dbg(data->hwmon_dev, "n_T-interim_e^14=%lld\n", temp2_64);
/* temp2_64 is n * t-int * 10^14 */
temp_64 = ((s64)data->p_e_minus2 * (s64)1000000000000ULL);
/* temp_64 is n * 10^14 */
temp_64 += temp1_64 + temp2_64 + temp1_64_rem;
is_neg = false;
if (temp_64 < 0) {
is_neg = true;
temp_64 *= -1;
}
divisor_64 = 100000000ULL;
temp_rem_32 = do_div(temp_64, divisor_64);
if (temp_rem_32 > (divisor_64 >> 1))
temp_64++;
if (is_neg)
temp_64 *= -1;
/* temperature * 10^14 / 10^8 */
/* get LS decimal digit rounding */
*p_final_temp = (s32)temp_64;
dev_dbg(data->hwmon_dev, "T-final stage4=%d\n", *p_final_temp);
}
/*
* Function to compute constants A and B needed for temperature
* calculation
* A = (T2-T1) / (F2-F1)
* B = T1 – A * F1
*/
static int tsensor_get_const_AB(struct tegra_tsensor_data *data)
{
int err;
s64 temp_val1, temp_val2;
u32 temp_rem;
bool is_neg;
u32 divisor;
/*
* 1. Find fusing registers for 25C (T1, F1) and 90C (T2, F2);
*/
err = read_tsensor_fuse_regs(data);
if (err) {
dev_err(data->hwmon_dev, "Fuse register read required "
"for internal tsensor returns err=%d\n", err);
return err;
}
if (data->fuse_F2 != data->fuse_F1) {
if ((data->fuse_F2 - data->fuse_F1) <= (data->fuse_T2 -
data->fuse_T1)) {
dev_err(data->hwmon_dev, "Error: F2=%d, "
"F1=%d, difference"
" unexpectedly low. Aborting temperature"
"computation\n", data->fuse_F2, data->fuse_F1);
return -EINVAL;
} else {
temp_val1 = (s64)(data->fuse_T2 - data->fuse_T1) *
1000000000000ULL;
/* temp_val1 always positive as fuse_T2 > fuse_T1 */
temp_rem = do_div(temp_val1, (data->fuse_F2 -
data->fuse_F1));
data->A_e_minus12 = temp_val1;
temp_val2 = (s64)(data->fuse_T1 * 1000000000000ULL);
temp_val2 -= (data->A_e_minus12 * data->fuse_F1);
is_neg = false;
if (temp_val2 < 0) {
is_neg = true;
temp_val2 *= -1;
}
divisor = 1000000;
temp_rem = do_div(temp_val2, divisor);
if (temp_rem > (divisor >> 1))
temp_val2++;
if (is_neg)
temp_val2 *= -1;
data->B_e_minus6 = (s32)temp_val2;
/* B is 10^6 times now */
}
}
dev_info(data->hwmon_dev, "A_e_minus12 = %lld\n", data->A_e_minus12);
dev_info(data->hwmon_dev, "B_e_minus6 = %d\n", data->B_e_minus6);
return 0;
}
/*
* function calculates expected temperature corresponding to
* given tsensor counter value
* Value returned is 100 times calculated temperature since the
* calculations are using fixed point arithmetic instead of floating point
*/
static int tsensor_count_2_temp(struct tegra_tsensor_data *data,
unsigned int count, int *p_temperature)
{
s64 interim_temp;
int err;
/*
*
* 2. Calculate interim temperature:
*/
err = calc_interim_temp(data, count, &interim_temp);
if (err < 0) {
dev_err(data->hwmon_dev, "tsensor: cannot read temperature\n");
*p_temperature = -1;
return err;
}
/*
*
* 3. Calculate final temperature:
*/
calc_final_temp(data, interim_temp, p_temperature);
/* logs counter -> temperature conversion */
dev_dbg(data->hwmon_dev, "tsensor: counter=0x%x, interim "
"temp*10^6=%lld, Final temp=%d.%06d\n",
count, interim_temp,
get_temperature_int(*p_temperature),
get_temperature_fraction(*p_temperature));
return 0;
}
/*
* function to solve quadratic roots of equation
* used to get counter corresponding to given temperature
*/
static void get_quadratic_roots(struct tegra_tsensor_data *data,
int temp, unsigned int *p_counter1,
unsigned int *p_counter2)
{
/*
* Equation to solve:
* m * A^2 * Counter^2 +
* A * (2 * m * B + n) * Counter +
* (m * B^2 + n * B + p - Temperature) = 0
To calculate root - assume
b = A * (2 * m * B + n)
a = m * A^2
c = ((m * B^2) + n * B + p - temp)
root1 = (-b + sqrt(b^2 - (4*a*c))) / (2 * a)
root2 = (-b - sqrt(b^2 - (4*a*c))) / (2 * a)
sqrt(k) = sqrt(k * 10^6) / sqrt(10^6)
Roots are :
(-(2*m*B+n)+sqrt(((2*m*B+n)^2-4*m(m*B^2+n*B+p-temp))))/(2*m*A)
and
(-(2*m*B+n)-sqrt(((2*m*B+n)^2-4*m(m*B^2+n*B+p-temp))))/(2*m*A)
After simplify ((2*m*B+n)^2-4*m(m*B^2+n*B+p-temp)),
Roots are:
(-(2*m*B+n)+sqrt((n^2-4*m(p-temp))))/(2*m*A)
and
(-(2*m*B+n)-sqrt((n^2-4*m(p-temp))))/(2*m*A)
*/
int v_e_minus6_2mB_n;
int v_e_minus6_4m_p_minusTemp;
int v_e_minus6_n2;
int v_e_minus6_b2_minus4ac;
int v_e_minus6_sqrt_b2_minus4ac;
s64 v_e_minus12_2mA;
int root1, root2;
int temp_rem;
bool is_neg;
s64 temp_64;
dev_dbg(data->hwmon_dev, "m_e-6=%d,n_e-6=%d,p_e-2=%d,A_e-6=%lld,"
"B_e-2=%d\n", data->m_e_minus6, data->n_e_minus6,
data->p_e_minus2, data->A_e_minus12, data->B_e_minus6);
temp_64 = (2ULL * (s64)data->m_e_minus6 * (s64)data->B_e_minus6);
is_neg = false;
if (temp_64 < 0) {
is_neg = true;
temp_64 *= -1;
}
temp_rem = do_div(temp_64, 1000000);
if (is_neg)
temp_64 *= -1;
v_e_minus6_2mB_n = (s32)temp_64 + data->n_e_minus6;
/* computed 2mB + n */
temp_64 = ((s64)data->m_e_minus6 * (s64)data->A_e_minus12);
temp_64 *= 2;
is_neg = false;
if (temp_64 < 0) {
temp_64 *= -1;
is_neg = true;
}
temp_rem = do_div(temp_64, 1000000);
if (is_neg)
temp_64 *= -1;
v_e_minus12_2mA = temp_64;
/* computed 2mA */
temp_64 = ((s64)data->n_e_minus6 * (s64)data->n_e_minus6);
/* squaring give positive value */
temp_rem = do_div(temp_64, 1000000);
v_e_minus6_n2 = (s32)temp_64;
/* computed n^2 */
v_e_minus6_4m_p_minusTemp = data->p_e_minus2 - (temp * 100);
v_e_minus6_4m_p_minusTemp *= 4 * data->m_e_minus6;
v_e_minus6_4m_p_minusTemp = DIV_ROUND_CLOSEST(
v_e_minus6_4m_p_minusTemp,100);
/* computed 4m*(p-T)*/
v_e_minus6_b2_minus4ac = (v_e_minus6_n2 - v_e_minus6_4m_p_minusTemp);
/* To preserve 1 decimal digits for sqrt(v_e_minus6_b2_minus4ac),
Make it 100 times, so
v_e_minus6_sqrt_b2_minus4ac=(int_sqrt(v_e_minus6_b2_minus4ac *100)*10^6)
/sqrt(10^6 * 100)
To avoid overflow,Simplify it to be:
v_e_minus6_sqrt_b2_minus4ac =(int_sqrt(v_e_minus6_b2_minus4ac *100)*100)
*/
v_e_minus6_sqrt_b2_minus4ac = (int_sqrt(v_e_minus6_b2_minus4ac * 100)
* 100);
dev_dbg(data->hwmon_dev, "A_e_minus12=%lld, B_e_minus6=%d, "
"m_e_minus6=%d, n_e_minus6=%d, p_e_minus2=%d, "
"temp=%d\n", data->A_e_minus12, data->B_e_minus6,
data->m_e_minus6,
data->n_e_minus6, data->p_e_minus2, (int)temp);
dev_dbg(data->hwmon_dev, "2mB_n=%d, 2mA=%lld, 4m_p_minusTemp=%d,"
"b2_minus4ac=%d\n", v_e_minus6_2mB_n,
v_e_minus12_2mA, v_e_minus6_4m_p_minusTemp,
v_e_minus6_b2_minus4ac);
temp_64=(s64)(-v_e_minus6_2mB_n - v_e_minus6_sqrt_b2_minus4ac) * 1000000;
root1=(s32)div64_s64(temp_64, v_e_minus12_2mA);
temp_64=(s64)(-v_e_minus6_2mB_n + v_e_minus6_sqrt_b2_minus4ac) * 1000000;
root2=(s32)div64_s64(temp_64, v_e_minus12_2mA);
dev_dbg(data->hwmon_dev, "new expr: temp=%d, root1=%d, root2=%d\n",
temp, root1, root2);
*p_counter1 = root1;
*p_counter2 = root2;
/* we find that root2 is more appropriate root */
/* logs temperature -> counter conversion */
dev_dbg(data->hwmon_dev, "temperature=%d, counter1=%#x, "
"counter2=%#x\n", temp, *p_counter1, *p_counter2);
}
/*
* function returns tsensor expected counter corresponding to input
* temperature in degree Celsius.
* e.g. for temperature of 35C, temp=35
*/
static void tsensor_temp_2_count(struct tegra_tsensor_data *data,
int temp,
unsigned int *p_counter1,
unsigned int *p_counter2)
{
dev_dbg(data->hwmon_dev, "Trying to calculate counter"
" for requested temperature"
" threshold=%d\n", temp);
/*
* calculate the constants needed to get roots of
* following quadratic eqn:
* m * A^2 * Counter^2 +
* A * (2 * m * B + n) * Counter +
* (m * B^2 + n * B + p - Temperature) = 0
*/
get_quadratic_roots(data, temp, p_counter1, p_counter2);
/*
* checked at current temperature=35 the counter=11418
* for 50 deg temperature: counter1=22731, counter2=11817
* at 35 deg temperature: counter1=23137, counter2=11411
* hence, for above values we are assuming counter2 has
* the correct value
*/
}
/*
* function to compare computed and expected values with
* certain tolerance setting hard coded here
*/
static bool cmp_counter(
struct tegra_tsensor_data *data,
unsigned int actual, unsigned int exp)
{
unsigned int smaller;
unsigned int larger;
smaller = (actual > exp) ? exp : actual;
larger = (smaller == actual) ? exp : actual;
if ((larger - smaller) > TSENSOR_COUNTER_TOLERANCE) {
dev_dbg(data->hwmon_dev, "actual=%d, exp=%d, larger=%d, "
"smaller=%d, tolerance=%d\n", actual, exp, larger, smaller,
TSENSOR_COUNTER_TOLERANCE);
return false;
}
return true;
}
/* function to print chart of counter to temperature values -
* It uses F1, F2, T1, T2 and start data gives reading
* for temperature in between the range
*/
static void print_counter_2_temperature_table(
struct tegra_tsensor_data *data)
{
int i;
unsigned int start_counter, end_counter;
unsigned int diff;
int temperature;
const unsigned int num_readings = 40;
unsigned int index = 0;
dev_dbg(data->hwmon_dev, "***Counter and Temperature chart **********\n");
start_counter = data->fuse_F1;
end_counter = data->fuse_F2;
diff = (end_counter - start_counter) / num_readings;
/* We want to take num_readings counter values in between
and try to report corresponding temperature */
for (i = start_counter; i <= (end_counter + diff);
i += diff) {
tsensor_count_2_temp(data, i, &temperature);
dev_dbg(data->hwmon_dev, "[%d]: Counter=%#x, temperature=%d.%06dC\n",
++index, i, get_temperature_int(temperature),
get_temperature_fraction(temperature));
}
dev_dbg(data->hwmon_dev, "\n\n");
tsensor_count_2_temp(data, end_counter, &temperature);
dev_dbg(data->hwmon_dev, "[%d]: Counter=%#x, temperature=%d.%06dC\n",
++index, end_counter, get_temperature_int(temperature),
get_temperature_fraction(temperature));
}
static bool temp_matched(int given_temp, int calc_temp)
{
const int temp_diff_max = 4;
int diff;
diff = given_temp - calc_temp;
if (diff < 0)
diff *= -1;
if (diff > temp_diff_max)
return false;
else
return true;
}
/* function to print chart of temperature to counter values */
static void print_temperature_2_counter_table(
struct tegra_tsensor_data *data)
{
int i;
int min = -25;
int max = 120;
unsigned int counter1, counter2;
int temperature;
dev_dbg(data->hwmon_dev, "Temperature and counter1 and "
"counter2 chart **********\n");
for (i = min; i <= max; i++) {
tsensor_temp_2_count(data, i,
&counter1, &counter2);
dev_dbg(data->hwmon_dev, "temperature=%d, "
"counter1=0x%x, counter2=0x%x\n",
i, counter1, counter2);
/* verify the counter2 to temperature conversion */
tsensor_count_2_temp(data, counter2, &temperature);
dev_dbg(data->hwmon_dev, "Given temp=%d: counter2=%d, conv temp=%d.%06d\n",
i, counter2, get_temperature_int(temperature),
get_temperature_fraction(temperature));
if (!temp_matched(i, get_temperature_round(temperature)))
dev_dbg(data->hwmon_dev, "tsensor temp to counter to temp conversion failed for temp=%d\n",
i);
}
dev_dbg(data->hwmon_dev, "\n\n");
}
static void dump_a_tsensor_reg(struct tegra_tsensor_data *data,
unsigned int addr)
{
dev_dbg(data->hwmon_dev, "tsensor[%d][0x%x]: 0x%x\n", (addr >> 16),
addr & 0xFFFF, tsensor_readl(data, addr));
}
static void dump_tsensor_regs(struct tegra_tsensor_data *data)
{
int i;
for (i = 0; i < TSENSOR_COUNT; i++) {
/* if STOP bit is set skip this check */
dump_a_tsensor_reg(data, ((i << 16) | SENSOR_CFG0));
dump_a_tsensor_reg(data, ((i << 16) | SENSOR_CFG1));
dump_a_tsensor_reg(data, ((i << 16) | SENSOR_CFG2));
dump_a_tsensor_reg(data, ((i << 16) | SENSOR_STATUS0));
dump_a_tsensor_reg(data, ((i << 16) | SENSOR_TS_STATUS1));
dump_a_tsensor_reg(data, ((i << 16) | SENSOR_TS_STATUS2));
dump_a_tsensor_reg(data, ((i << 16) | 0x0));
dump_a_tsensor_reg(data, ((i << 16) | 0x44));
dump_a_tsensor_reg(data, ((i << 16) | 0x50));
dump_a_tsensor_reg(data, ((i << 16) | 0x54));
dump_a_tsensor_reg(data, ((i << 16) | 0x64));
dump_a_tsensor_reg(data, ((i << 16) | 0x68));
}
}
/*
* function to test if conversion of counter to temperature
* and vice-versa is working
*/
static int test_temperature_algo(struct tegra_tsensor_data *data)
{
unsigned int actual_counter;
unsigned int curr_avg;
unsigned int counter1, counter2;
int T1;
int err = 0;
bool result1, result2;
bool result = false;
/* read actual counter */
err = tsensor_read_counter(data, &curr_avg);
if (err < 0) {
pr_err("Error: tsensor0 counter read, err=%d\n", err);
goto endLabel;
}
actual_counter = ((curr_avg & 0xFFFF0000) >> 16);
dev_dbg(data->hwmon_dev, "counter read=0x%x\n", actual_counter);
/* calculate temperature */
err = tsensor_count_2_temp(data, actual_counter, &T1);
dev_dbg(data->hwmon_dev, "%s actual counter=0x%x, calculated "
"temperature=%d.%06d\n", __func__,
actual_counter, get_temperature_int(T1),
get_temperature_fraction(T1));
if (err < 0) {
pr_err("Error: calculate temperature step\n");
goto endLabel;
}
/* calculate counter corresponding to read temperature */
tsensor_temp_2_count(data, get_temperature_round(T1),
&counter1, &counter2);
dev_dbg(data->hwmon_dev, "given temperature=%d, counter1=0x%x,"
" counter2=0x%x\n",
get_temperature_round(T1), counter1, counter2);
err = tsensor_count_2_temp(data, actual_counter, &T1);
dev_dbg(data->hwmon_dev, "%s 2nd time actual counter=0x%x, "
"calculated temperature=%d.%d\n", __func__,
actual_counter, get_temperature_int(T1),
get_temperature_fraction(T1));
if (err < 0) {
pr_err("Error: calculate temperature step\n");
goto endLabel;
}
/* compare counter calculated with actual original counter */
result1 = cmp_counter(data, actual_counter, counter1);
result2 = cmp_counter(data, actual_counter, counter2);
if (result1) {
dev_dbg(data->hwmon_dev, "counter1 matches: actual=%d,"
" calc=%d\n", actual_counter, counter1);
result = true;
}
if (result2) {
dev_dbg(data->hwmon_dev, "counter2 matches: actual=%d,"
" calc=%d\n", actual_counter, counter2);
result = true;
}
if (!result) {
pr_info("NO Match: actual=%d,"
" calc counter2=%d, counter1=%d\n", actual_counter,
counter2, counter1);
err = -EIO;
}
endLabel:
return err;
}
/* tsensor threshold temperature to threshold counter conversion function */
static unsigned int tsensor_get_threshold_counter(
struct tegra_tsensor_data *data,
int temp_threshold)
{
unsigned int counter1, counter2;
unsigned int counter;
if (temp_threshold < 0)
return MAX_THRESHOLD;
tsensor_temp_2_count(data, temp_threshold, &counter1, &counter2);
counter = counter2;
return counter;
}
/* tsensor temperature threshold setup function */
static void tsensor_threshold_setup(struct tegra_tsensor_data *data,
unsigned char index)
{
unsigned long config0;
unsigned char i = index;
unsigned int th2_count = DEFAULT_THRESHOLD_TH2;
unsigned int th3_count = DEFAULT_THRESHOLD_TH3;
unsigned int th1_count = DEFAULT_THRESHOLD_TH1;
int th0_diff = 0;
dev_dbg(data->hwmon_dev, "started tsensor_threshold_setup %d\n",
index);
config0 = tsensor_readl(data, ((i << 16) | SENSOR_CFG0));
dev_dbg(data->hwmon_dev, "before threshold program TH dump:\n");
dump_threshold(data);
dev_dbg(data->hwmon_dev, "th3=0x%x, th2=0x%x, th1=0x%x, th0=0x%x\n",
th3_count, th2_count, th1_count, th0_diff);
config0 = (((th2_count & SENSOR_CFG_X_TH_X_MASK)
<< SENSOR_CFG1_TH2_SHIFT) |
((th1_count & SENSOR_CFG_X_TH_X_MASK) <<
SENSOR_CFG1_TH1_SHIFT));
tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG1));
config0 = (((th0_diff & SENSOR_CFG_X_TH_X_MASK)
<< SENSOR_CFG2_TH0_SHIFT) |
((th3_count & SENSOR_CFG_X_TH_X_MASK) <<
SENSOR_CFG2_TH3_SHIFT));
tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG2));
dev_dbg(data->hwmon_dev, "after threshold program TH dump:\n");
dump_threshold(data);
}
/* tsensor config programming function */
static int tsensor_config_setup(struct tegra_tsensor_data *data)
{
unsigned int config0;
unsigned int i;
int err = 0;
for (i = 0; i < TSENSOR_COUNT; i++) {
/*
* Pre-read setup:
* Set M and N values
* Enable HW features HW_FREQ_DIV_EN, THERMAL_RST_EN
*/
config0 = tsensor_readl(data, ((i << 16) | SENSOR_CFG0));
config0 &= ~((SENSOR_CFG0_M_MASK << SENSOR_CFG0_M_SHIFT) |
(SENSOR_CFG0_N_MASK << SENSOR_CFG0_N_SHIFT) |
(1 << SENSOR_CFG0_OVERFLOW_INTR) |
(1 << SENSOR_CFG0_RST_INTR_SHIFT) |
(1 << SENSOR_CFG0_DVFS_INTR_SHIFT) |
(1 << SENSOR_CFG0_HW_DIV2_INTR_SHIFT) |
(1 << SENSOR_CFG0_RST_ENABLE_SHIFT) |
(1 << SENSOR_CFG0_HW_DIV2_ENABLE_SHIFT)
);
/* Set STOP bit */
/* Set M and N values */
/* Enable HW features HW_FREQ_DIV_EN, THERMAL_RST_EN */
config0 |= (
((DEFAULT_TSENSOR_M & SENSOR_CFG0_M_MASK) <<
SENSOR_CFG0_M_SHIFT) |
((DEFAULT_TSENSOR_N & SENSOR_CFG0_N_MASK) <<
SENSOR_CFG0_N_SHIFT) |
(1 << SENSOR_CFG0_OVERFLOW_INTR) |
(1 << SENSOR_CFG0_DVFS_INTR_SHIFT) |
(1 << SENSOR_CFG0_HW_DIV2_INTR_SHIFT) |
#if ENABLE_TSENSOR_HW_RESET
(1 << SENSOR_CFG0_RST_ENABLE_SHIFT) |
#endif
(1 << SENSOR_CFG0_STOP_SHIFT));
tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG0));
tsensor_threshold_setup(data, i);
}
/* Disable sensor stop bit */
config0 = tsensor_readl(data, (data->instance << 16) | SENSOR_CFG0);
config0 &= ~(1 << SENSOR_CFG0_STOP_SHIFT);
tsensor_writel(data, config0, (data->instance << 16) | SENSOR_CFG0);
/* initialize tsensor chip coefficients */
get_chip_tsensor_coeff(data);
return err;
}
/* function to enable tsensor clock */
static int tsensor_clk_enable(
struct tegra_tsensor_data *data,
bool enable)
{
int err = 0;
unsigned long rate;
struct clk *clk_m;
if (enable) {
clk_prepare_enable(data->dev_clk);
rate = clk_get_rate(data->dev_clk);
clk_m = clk_get_sys(NULL, "clk_m");
if (clk_get_parent(data->dev_clk) != clk_m) {
err = clk_set_parent(data->dev_clk, clk_m);
if (err < 0)
goto fail;
}
rate = DEFAULT_TSENSOR_CLK_HZ;
if (rate != clk_get_rate(clk_m)) {
err = clk_set_rate(data->dev_clk, rate);
if (err < 0)
goto fail;
}
} else {
clk_disable_unprepare(data->dev_clk);
clk_put(data->dev_clk);
}
fail:
return err;
}
/*
* function to set counter threshold corresponding to
* given temperature
*/
static void tsensor_set_limits(
struct tegra_tsensor_data *data,
int temp,
int threshold_index)
{
unsigned int th_count;
unsigned int config;
unsigned short sft, offset;
unsigned int th1_count;
th_count = tsensor_get_threshold_counter(data, temp);
dev_dbg(data->hwmon_dev, "%s : input temp=%d, counter=0x%x\n", __func__,
temp, th_count);
switch (threshold_index) {
case TSENSOR_TH0:
sft = 16;
offset = SENSOR_CFG2;
/* assumed TH1 set before TH0, else we program
* TH0 as TH1 which means hysteresis will be
* same as TH1. Also, caller expected to pass
* (TH1 - hysteresis) as temp argument for this case */
th1_count = tsensor_readl(data,
((data->instance << 16) |
SENSOR_CFG1));
th_count = (th1_count > th_count) ?
(th1_count - th_count) :
th1_count;
break;
case TSENSOR_TH1:
default:
sft = 0;
offset = SENSOR_CFG1;
break;
case TSENSOR_TH2:
sft = 16;
offset = SENSOR_CFG1;
break;
case TSENSOR_TH3:
sft = 0;
offset = SENSOR_CFG2;
break;
}
config = tsensor_readl(data, ((data->instance << 16) | offset));
dev_dbg(data->hwmon_dev, "%s: old config=0x%x, sft=%d, offset=0x%x\n",
__func__, config, sft, offset);
config &= ~(SENSOR_CFG_X_TH_X_MASK << sft);
config |= ((th_count & SENSOR_CFG_X_TH_X_MASK) << sft);
dev_dbg(data->hwmon_dev, "new config=0x%x\n", config);
tsensor_writel(data, config, ((data->instance << 16) | offset));
}
static int tsensor_within_limits(struct tegra_tsensor_data *data)
{
int ts_state = get_ts_state(data);
return (ts_state == TS_LEVEL1);
}
#ifdef CONFIG_THERMAL
static int tsensor_thermal_set_limits(struct tegra_tsensor_data *data,
long lo_limit_milli,
long hi_limit_milli)
{
long lo_limit = MILLICELSIUS_TO_CELSIUS(lo_limit_milli);
long hi_limit = MILLICELSIUS_TO_CELSIUS(hi_limit_milli);
int i, j, hi_limit_first;
if (lo_limit_milli == hi_limit_milli)
return -EINVAL;
mutex_lock(&data->mutex);
if (data->current_lo_limit == lo_limit_milli &&
data->current_hi_limit == hi_limit_milli) {
goto done;
}
/* If going up, change hi limit first. If going down, change lo
limit first */
hi_limit_first = hi_limit_milli > data->current_hi_limit;
for (i = 0; i < 2; i++) {
j = (i + hi_limit_first) % 2;
switch (j) {
case 0:
tsensor_set_limits(data, hi_limit, TSENSOR_TH2);
data->current_hi_limit = hi_limit_milli;
break;
case 1:
tsensor_set_limits(data, lo_limit, TSENSOR_TH1);
data->current_lo_limit = lo_limit_milli;
break;
}
}
done:
mutex_unlock(&data->mutex);
return 0;
}
static void tsensor_update(struct tegra_tsensor_data *data)
{
struct thermal_zone_device *thz = data->thz;
long temp, trip_temp, low_temp = 0, high_temp = 120000;
int count;
if (!thz)
return;
if (!thz->passive)
thermal_zone_device_update(thz);
thz->ops->get_temp(thz, &temp);
for (count = 0; count < thz->trips; count++) {
thz->ops->get_trip_temp(thz, count, &trip_temp);
if ((trip_temp >= temp) && (trip_temp < high_temp))
high_temp = trip_temp;
if ((trip_temp < temp) && (trip_temp > low_temp))
low_temp = trip_temp;
}
tsensor_thermal_set_limits(data, low_temp, high_temp);
}
#else
static void tsensor_update(struct tegra_tsensor_data *data)
{
}
#endif
static void tsensor_work_func(struct work_struct *work)
{
struct tegra_tsensor_data *data = container_of(to_delayed_work(work),
struct tegra_tsensor_data, work);
if (!tsensor_within_limits(data)) {
tsensor_update(data);
if (!tsensor_within_limits(data))
dev_dbg(data->hwmon_dev,
"repeated work queueing state=%d\n",
get_ts_state(data));
queue_delayed_work(data->workqueue, &data->work,
HZ * DEFAULT_TSENSOR_M /
DEFAULT_TSENSOR_CLK_HZ);
}
}
/*
* This function enables the tsensor using default configuration
* 1. We would need some configuration APIs to calibrate
* the tsensor counters to right temperature
* 2. hardware triggered divide cpu clock by 2 as well pmu reset is enabled
* implementation. No software actions are enabled at this point
*/
static int tegra_tsensor_setup(struct platform_device *pdev)
{
struct tegra_tsensor_data *data = platform_get_drvdata(pdev);
struct resource *r;
int err = 0;
struct tegra_tsensor_platform_data *tsensor_data;
unsigned int reg;
data->dev_clk = clk_get(&pdev->dev, NULL);
if ((!data->dev_clk) || ((int)data->dev_clk == -(ENOENT))) {
dev_err(&pdev->dev, "Couldn't get the clock\n");
err = PTR_ERR(data->dev_clk);
goto fail;
}
/* Enable tsensor clock */
err = tsensor_clk_enable(data, true);
if (err < 0)
goto err_irq;
/* Reset tsensor */
dev_dbg(&pdev->dev, "before tsensor reset %s\n", __func__);
tegra_periph_reset_assert(data->dev_clk);
udelay(100);
tegra_periph_reset_deassert(data->dev_clk);
udelay(100);
dev_dbg(&pdev->dev, "before tsensor chk pmc reset %s\n",
__func__);
/* Check for previous resets in pmc */
if (pmc_check_rst_sensor(data)) {
dev_err(data->hwmon_dev, "Warning: ***** Last PMC "
"Reset source: tsensor detected\n");
}
dev_dbg(&pdev->dev, "before tsensor pmc reset enable %s\n",
__func__);
/* Enable the sensor reset in PMC */
pmc_rst_enable(data, true);
dev_dbg(&pdev->dev, "before tsensor get platform data %s\n",
__func__);
dev_dbg(&pdev->dev, "tsensor platform_data=0x%x\n",
(unsigned int)pdev->dev.platform_data);
tsensor_data = pdev->dev.platform_data;
/* register interrupt */
r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!r) {
dev_err(&pdev->dev, "Failed to get IRQ\n");
err = -ENXIO;
goto err_irq;
}
data->irq = r->start;
err = request_irq(data->irq, tegra_tsensor_isr,
IRQF_DISABLED, pdev->name, data);
if (err < 0) {
dev_err(&pdev->dev, "Failed to register IRQ\n");
goto err_irq;
}
dev_dbg(&pdev->dev, "tsensor platform_data=0x%x\n",
(unsigned int)pdev->dev.platform_data);
dev_dbg(&pdev->dev, "before tsensor_config_setup\n");
err = tsensor_config_setup(data);
if (err) {
dev_err(&pdev->dev, "[%s,line=%d]: tsensor counters dead!\n",
__func__, __LINE__);
goto err_setup;
}
dev_dbg(&pdev->dev, "before tsensor_get_const_AB\n");
/* calculate constants needed for temperature conversion */
err = tsensor_get_const_AB(data);
if (err < 0) {
dev_err(&pdev->dev, "Failed to extract temperature\n"
"const\n");
goto err_setup;
}
/* test if counter-to-temperature and temperature-to-counter
* are matching */
err = test_temperature_algo(data);
if (err) {
dev_err(&pdev->dev, "Error: read temperature\n"
"algorithm broken\n");
goto err_setup;
}
print_temperature_2_counter_table(data);
print_counter_2_temperature_table(data);
/* EDP and throttling support using tsensor enabled
* based on fuse revision */
err = tegra_fuse_get_revision(&reg);
if (err)
goto err_setup;
data->is_edp_supported = (reg >= STABLE_TSENSOR_FUSE_REV);
if (data->is_edp_supported) {
data->workqueue = create_singlethread_workqueue("tsensor");
INIT_DELAYED_WORK(&data->work, tsensor_work_func);
}
return 0;
err_setup:
free_irq(data->irq, data);
err_irq:
tsensor_clk_enable(data, false);
fail:
dev_err(&pdev->dev, "%s error=%d returned\n", __func__, err);
return err;
}
#ifdef CONFIG_THERMAL
static int tsensor_get_temp(struct thermal_zone_device *thz,
unsigned long *temp)
{
struct tegra_tsensor_data *data = thz->devdata;
return tsensor_thermal_get_temp(data, temp);
}
static int tsensor_bind(struct thermal_zone_device *thz,
struct thermal_cooling_device *cdev)
{
int i;
struct tegra_tsensor_data *data = thz->devdata;
if (cdev == data->plat_data.passive.cdev)
return thermal_zone_bind_cooling_device(thz, 0, cdev,
THERMAL_NO_LIMIT,
THERMAL_NO_LIMIT);
for (i = 0; data->plat_data.active[i].cdev; i++)
if (cdev == data->plat_data.active[i].cdev)
return thermal_zone_bind_cooling_device(thz, i+1, cdev,
THERMAL_NO_LIMIT,
THERMAL_NO_LIMIT);
return 0;
}
static int tsensor_unbind(struct thermal_zone_device *thz,
struct thermal_cooling_device *cdev)
{
int i;
struct tegra_tsensor_data *data = thz->devdata;
if (cdev == data->plat_data.passive.cdev)
return thermal_zone_unbind_cooling_device(thz, 0, cdev);
for (i = 0; data->plat_data.active[i].cdev; i++)
if (cdev == data->plat_data.active[i].cdev)
return thermal_zone_unbind_cooling_device(thz, i+1,
cdev);
return 0;
}
static int tsensor_get_trip_temp(struct thermal_zone_device *thz,
int trip,
unsigned long *temp)
{
struct tegra_tsensor_data *data = thz->devdata;
if (trip == 0)
*temp = data->plat_data.passive.trip_temp;
else
*temp = data->plat_data.active[trip-1].trip_temp;
return 0;
}
static int tsensor_get_trip_type(struct thermal_zone_device *thz,
int trip,
enum thermal_trip_type *type)
{
*type = (trip == 0) ? THERMAL_TRIP_PASSIVE : THERMAL_TRIP_ACTIVE;
return 0;
}
static struct thermal_zone_device_ops tsensor_ops = {
.get_temp = tsensor_get_temp,
.bind = tsensor_bind,
.unbind = tsensor_unbind,
.get_trip_type = tsensor_get_trip_type,
.get_trip_temp = tsensor_get_trip_temp,
};
#endif
static int tegra_tsensor_probe(struct platform_device *pdev)
{
struct tegra_tsensor_data *data;
struct resource *r;
int err;
unsigned int reg;
u8 i;
struct tegra_tsensor_platform_data *tsensor_data;
#ifdef CONFIG_THERMAL
int num_trips = 0;
#endif
data = kzalloc(sizeof(struct tegra_tsensor_data), GFP_KERNEL);
if (!data) {
dev_err(&pdev->dev, "[%s,line=%d]: Failed to allocate "
"memory\n", __func__, __LINE__);
err = -ENOMEM;
goto exit;
}
mutex_init(&data->mutex);
platform_set_drvdata(pdev, data);
/* Register sysfs hooks */
for (i = 0; i < ARRAY_SIZE(tsensor_nodes); i++) {
err = device_create_file(&pdev->dev,
&tsensor_nodes[i].dev_attr);
if (err) {
dev_err(&pdev->dev, "device_create_file failed.\n");
goto err0;
}
}
data->hwmon_dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(data->hwmon_dev)) {
err = PTR_ERR(data->hwmon_dev);
goto err1;
}
dev_set_drvdata(data->hwmon_dev, data);
spin_lock_init(&data->tsensor_lock);
/* map tsensor register space */
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (r == NULL) {
dev_err(&pdev->dev, "[%s,line=%d]: Failed to get io "
"resource\n", __func__, __LINE__);
err = -ENODEV;
goto err2;
}
if (!request_mem_region(r->start, (r->end - r->start) + 1,
dev_name(&pdev->dev))) {
dev_err(&pdev->dev, "[%s,line=%d]: Error mem busy\n",
__func__, __LINE__);
err = -EBUSY;
goto err2;
}
data->phys = r->start;
data->phys_end = r->end;
data->base = ioremap(r->start, r->end - r->start + 1);
if (!data->base) {
dev_err(&pdev->dev, "[%s, line=%d]: can't ioremap "
"tsensor iomem\n", __FILE__, __LINE__);
err = -ENOMEM;
goto err3;
}
/* map pmc rst_status register */
r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (r == NULL) {
dev_err(&pdev->dev, "[%s,line=%d]: Failed to get io "
"resource\n", __func__, __LINE__);
err = -ENODEV;
goto err4;
}
if (!request_mem_region(r->start, (r->end - r->start) + 1,
dev_name(&pdev->dev))) {
dev_err(&pdev->dev, "[%s, line=%d]: Error mem busy\n",
__func__, __LINE__);
err = -EBUSY;
goto err4;
}
data->pmc_phys = r->start;
data->pmc_phys_end = r->end;
data->pmc_rst_base = ioremap(r->start, r->end - r->start + 1);
if (!data->pmc_rst_base) {
dev_err(&pdev->dev, "[%s, line=%d]: can't ioremap "
"pmc iomem\n", __FILE__, __LINE__);
err = -ENOMEM;
goto err5;
}
/* fuse revisions less than TSENSOR_FUSE_REV1
bypass tsensor driver init */
/* tsensor active instance decided based on fuse revision */
err = tegra_fuse_get_revision(&reg);
if (err)
goto err6;
/* check for higher revision done first */
/* instance 0 is used for fuse revision TSENSOR_FUSE_REV2 onwards */
if (reg >= TSENSOR_FUSE_REV2)
data->instance = TSENSOR_INSTANCE1;
/* instance 1 is used for fuse revision TSENSOR_FUSE_REV1 till
TSENSOR_FUSE_REV2 */
else if (reg >= TSENSOR_FUSE_REV1)
data->instance = TSENSOR_INSTANCE2;
pr_info("tsensor active instance=%d\n", data->instance);
/* tegra tsensor - setup and init */
err = tegra_tsensor_setup(pdev);
if (err)
goto err6;
dump_tsensor_regs(data);
dev_dbg(&pdev->dev, "end tegra_tsensor_probe\n");
tsensor_data = pdev->dev.platform_data;
memcpy(&data->plat_data, tsensor_data,
sizeof(struct tegra_tsensor_platform_data));
tsensor_set_limits(data, tsensor_data->shutdown_temp, TSENSOR_TH3);
#ifdef CONFIG_THERMAL
if (tsensor_data->passive.cdev)
num_trips++;
for (i = 0; tsensor_data->active[i].cdev; i++)
num_trips++;
data->thz = thermal_zone_device_register("tsensor",
num_trips,
0x0,
data,
&tsensor_ops,
NULL,
tsensor_data->passive.passive_delay,
0);
if (IS_ERR_OR_NULL(data->thz))
goto err6;
tsensor_update(data);
#endif
return 0;
err6:
iounmap(data->pmc_rst_base);
err5:
release_mem_region(data->pmc_phys, (data->pmc_phys_end -
data->pmc_phys) + 1);
err4:
iounmap(data->base);
err3:
release_mem_region(data->phys, (data->phys_end -
data->phys) + 1);
err2:
hwmon_device_unregister(data->hwmon_dev);
err1:
for (i = 0; i < ARRAY_SIZE(tsensor_nodes); i++)
device_remove_file(&pdev->dev, &tsensor_nodes[i].dev_attr);
err0:
kfree(data);
exit:
dev_err(&pdev->dev, "%s error=%d returned\n", __func__, err);
return err;
}
static int tegra_tsensor_remove(struct platform_device *pdev)
{
struct tegra_tsensor_data *data = platform_get_drvdata(pdev);
u8 i;
hwmon_device_unregister(data->hwmon_dev);
for (i = 0; i < ARRAY_SIZE(tsensor_nodes); i++)
device_remove_file(&pdev->dev, &tsensor_nodes[i].dev_attr);
if (data->is_edp_supported) {
cancel_delayed_work_sync(&data->work);
destroy_workqueue(data->workqueue);
data->workqueue = NULL;
}
free_irq(data->irq, data);
iounmap(data->pmc_rst_base);
release_mem_region(data->pmc_phys, (data->pmc_phys_end -
data->pmc_phys) + 1);
iounmap(data->base);
release_mem_region(data->phys, (data->phys_end -
data->phys) + 1);
kfree(data);
return 0;
}
#ifdef CONFIG_PM
static void save_tsensor_regs(struct tegra_tsensor_data *data)
{
int i;
for (i = 0; i < TSENSOR_COUNT; i++) {
data->config0[i] = tsensor_readl(data,
((i << 16) | SENSOR_CFG0));
data->config1[i] = tsensor_readl(data,
((i << 16) | SENSOR_CFG1));
data->config2[i] = tsensor_readl(data,
((i << 16) | SENSOR_CFG2));
}
}
static void restore_tsensor_regs(struct tegra_tsensor_data *data)
{
int i;
for (i = 0; i < TSENSOR_COUNT; i++) {
tsensor_writel(data, data->config0[i],
((i << 16) | SENSOR_CFG0));
tsensor_writel(data, data->config1[i],
((i << 16) | SENSOR_CFG1));
tsensor_writel(data, data->config2[i],
((i << 16) | SENSOR_CFG2));
}
}
static int tsensor_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct tegra_tsensor_data *data = platform_get_drvdata(pdev);
unsigned int config0;
disable_irq(data->irq);
cancel_delayed_work_sync(&data->work);
/* set STOP bit, else OVERFLOW interrupt seen in LP1 */
config0 = tsensor_readl(data, ((data->instance << 16) | SENSOR_CFG0));
config0 |= (1 << SENSOR_CFG0_STOP_SHIFT);
tsensor_writel(data, config0, ((data->instance << 16) | SENSOR_CFG0));
/* save current settings before suspend, when STOP bit is set */
save_tsensor_regs(data);
tsensor_clk_enable(data, false);
return 0;
}
static int tsensor_resume(struct platform_device *pdev)
{
struct tegra_tsensor_data *data = platform_get_drvdata(pdev);
unsigned int config0;
tsensor_clk_enable(data, true);
/* restore current settings before suspend, no need
* to clear STOP bit */
restore_tsensor_regs(data);
/* clear STOP bit, after restoring regs */
config0 = tsensor_readl(data, ((data->instance << 16) | SENSOR_CFG0));
config0 &= ~(1 << SENSOR_CFG0_STOP_SHIFT);
tsensor_writel(data, config0, ((data->instance << 16) | SENSOR_CFG0));
if (data->is_edp_supported)
schedule_delayed_work(&data->work, 0);
enable_irq(data->irq);
return 0;
}
#endif
static struct platform_driver tegra_tsensor_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "tegra-tsensor",
},
.probe = tegra_tsensor_probe,
.remove = tegra_tsensor_remove,
#ifdef CONFIG_PM
.suspend = tsensor_suspend,
.resume = tsensor_resume,
#endif
};
static int __init tegra_tsensor_init(void)
{
return platform_driver_register(&tegra_tsensor_driver);
}
module_init(tegra_tsensor_init);
static void __exit tegra_tsensor_exit(void)
{
platform_driver_unregister(&tegra_tsensor_driver);
}
module_exit(tegra_tsensor_exit);
MODULE_AUTHOR("nvidia");
MODULE_DESCRIPTION("Nvidia Tegra Temperature Sensor driver");
MODULE_LICENSE("GPL");