blob: 4549dd96c29cac2a558f21587126af58255146bc [file] [log] [blame]
/*
* gpadc_thermal.c - thermistor thermal management
*
* Author: Feng Hong <hongfeng@marvell.com>
* Yi Zhang <yizhang@marvell.com>
* Copyright: (C) 2014 Marvell International Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/thermal.h>
#include <linux/cpufreq.h>
#include <linux/cpu_cooling.h>
#include <linux/of.h>
#include <linux/mfd/88pm80x.h>
/*
* -40 ~ 125: step -> 1C
* resistor value multiplied by 1000
*/
static int temp_table[] = {
/* [-40, -36]*/
1747919, 1631671, 1523950, 1424075, 1331424,
1245427, 1165564, 1091357, 1022369, 958201,
/* [-30, -26]*/
898485, 842883, 791087, 742813, 697798,
655802, 616604, 580001, 545804, 513842,
/* [-20, -16]*/
483953, 455992, 429821, 405317, 382362,
360849, 340680, 321762, 304010, 287346,
/* [-10, -6]*/
271697, 256994, 243176, 230183, 217962,
206463, 195624, 185419, 175807, 166751,
/* [0, 4]*/
158214, 150165, 142572, 135408, 128645,
122259, 116227, 110527, 105139, 100045,
/* [10, 14]*/
95226, 90667, 86351, 82266, 78396,
74730, 71255, 67962, 64838, 61875,
/* [20, 24]*/
59064, 56396, 53862, 51456, 49171,
47000, 44936, 42973, 41107, 39332,
/* [30, 34]*/
37643, 36035, 34504, 33046, 31657,
30333, 29073, 27871, 26726, 25633,
/* [40, 44]*/
24590, 23596, 22646, 21740, 20874,
20047, 19258, 18503, 17781, 17092,
/* [50, 54]*/
16432, 15801, 15198, 14620, 14067,
13538, 13031, 12546, 12081, 11636,
/* [60, 64]*/
11209, 10800, 10409, 10033, 9673,
9327, 8996, 8678, 8372, 8079,
/* [70, 74]*/
7797, 7526, 7266, 7016, 6775,
6544, 6322, 6109, 5904, 5707,
/* [80, 84]*/
5517, 5335, 5160, 4992, 4829,
4673, 4522, 4377, 4236, 4101,
/* [90, 94]*/
3971, 3846, 3725, 3608, 3496,
3387, 3283, 3183, 3086, 2992,
/* [100, 104]*/
2901, 2814, 2730, 2648, 2570,
2494, 2420, 2349, 2280, 2213,
/* [110, 114]*/
2149, 2087, 2027, 1969, 1913,
1859, 1807, 1757, 1708, 1661,
/* [120, 124]*/
1615, 1570, 1527, 1485, 1445,
};
enum trip_points {
TRIP_0,
TRIP_1,
TRIP_2,
TRIP_3,
TRIP_POINTS_NUM,
TRIP_POINTS_ACTIVE_NUM = TRIP_POINTS_NUM - 1,
};
struct uevent_msg_priv {
int cur_s;
int last_s;
};
struct gpadc_thermal_device {
struct thermal_zone_device *therm_adc;
int temp_adc;
struct uevent_msg_priv msg_s[TRIP_POINTS_ACTIVE_NUM];
int hit_trip_cnt[TRIP_POINTS_NUM];
};
static struct gpadc_thermal_device adc_dev;
static int adc_trips_temp[TRIP_POINTS_NUM] = {
40000, /* TRIP_0 */
50000, /* TRIP_1 */
55000, /* TRIP_2 */
100000, /* TRIP_3, set critical 100C for don't enable it currently */
};
static int hit_trip_status_get(struct device *dev,
struct device_attribute *attr, char *buf)
{
int i;
int ret = 0;
for (i = 0; i < TRIP_POINTS_NUM; i++) {
ret += sprintf(buf + ret, "trip %d: %d hits\n",
adc_trips_temp[i],
adc_dev.hit_trip_cnt[i]);
}
return ret;
}
static DEVICE_ATTR(hit_trip_status, 0444, hit_trip_status_get, NULL);
static struct attribute *thermal_attrs[] = {
&dev_attr_hit_trip_status.attr,
NULL,
};
static struct attribute_group thermal_attr_grp = {
.attrs = thermal_attrs,
};
static void gpadc_set_interval(int ms)
{
(adc_dev.therm_adc)->polling_delay = ms;
}
/* step: the temperature step */
static int get_thermal_temp(int gp_id, unsigned int step, int lowest)
{
int temp, tbat, volt, i;
unsigned int bias_current[5] = {31, 61, 16, 11, 6};
/*
*1) set bias as 31uA firstly for room temperature,
*2) check the voltage whether it's in [0.3V, 1.25V];
* if yes, tbat = tbat/31; break;
* else
* set bias as 61uA/16uA/11uA/6uA...
* for lower or higher temperature;
*/
for (i = 0; i < ARRAY_SIZE(bias_current); i++) {
volt = extern_get_gpadc_bias_volt(gp_id, bias_current[i]);
if ((volt > 300) && (volt < 1250)) {
volt *= 1000;
tbat = volt / bias_current[i];
break;
}
}
/* report the fake value 25C */
if (i == ARRAY_SIZE(bias_current)) {
pr_debug("thermal fake raw temp is 25C\n");
temp = 25;
return temp;
}
pr_debug("%s: tbat = %dKohm, i = %d\n", __func__, tbat, i);
for (i = 0; i < ARRAY_SIZE(temp_table); i++) {
if (tbat >= temp_table[i]) {
temp = lowest + i * step;
break;
}
}
/* max temperature */
if (i == ARRAY_SIZE(temp_table))
temp = temp_table[i - 1];
pr_debug("raw temperature is %dC\n", temp);
return temp;
}
static int gpadc_sys_get_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
{
int ret = 0;
struct gpadc_thermal_device *t_dev = &adc_dev;
char *temp_info[3] = { "TYPE=thsens_adc", "TEMP=10000", NULL };
int mon_interval;
t_dev->temp_adc = get_thermal_temp(3, 1, -40);
*temp = t_dev->temp_adc * 1000;
if (t_dev->therm_adc) {
if (t_dev->temp_adc >= adc_trips_temp[TRIP_2]) {
t_dev->hit_trip_cnt[TRIP_2]++;
t_dev->msg_s[TRIP_2].cur_s = 1;
t_dev->msg_s[TRIP_1].cur_s = 1;
t_dev->msg_s[TRIP_0].cur_s = 1;
mon_interval = 2000;
} else if ((t_dev->temp_adc >= adc_trips_temp[TRIP_1]) &&
(t_dev->temp_adc < adc_trips_temp[TRIP_2])) {
t_dev->hit_trip_cnt[TRIP_1]++;
t_dev->msg_s[TRIP_2].cur_s = 0;
t_dev->msg_s[TRIP_1].cur_s = 1;
t_dev->msg_s[TRIP_0].cur_s = 1;
mon_interval = 3000;
} else if ((t_dev->temp_adc >= adc_trips_temp[TRIP_0]) &&
(t_dev->temp_adc < adc_trips_temp[TRIP_1])) {
t_dev->hit_trip_cnt[TRIP_0]++;
t_dev->msg_s[TRIP_2].cur_s = 0;
t_dev->msg_s[TRIP_1].cur_s = 0;
t_dev->msg_s[TRIP_0].cur_s = 1;
mon_interval = 4000;
} else {
t_dev->msg_s[TRIP_2].cur_s = 0;
t_dev->msg_s[TRIP_1].cur_s = 0;
t_dev->msg_s[TRIP_0].cur_s = 0;
mon_interval = 5000;
}
if ((t_dev->msg_s[TRIP_2].cur_s !=
t_dev->msg_s[TRIP_2].last_s) ||
(t_dev->msg_s[TRIP_1].cur_s !=
t_dev->msg_s[TRIP_1].last_s) ||
(t_dev->msg_s[TRIP_0].cur_s !=
t_dev->msg_s[TRIP_0].last_s)) {
gpadc_set_interval(mon_interval);
t_dev->msg_s[TRIP_2].last_s =
t_dev->msg_s[TRIP_2].cur_s;
t_dev->msg_s[TRIP_1].last_s =
t_dev->msg_s[TRIP_1].cur_s;
t_dev->msg_s[TRIP_0].last_s =
t_dev->msg_s[TRIP_0].cur_s;
pr_info("board adc thermal %dC\n",
t_dev->temp_adc / 1000);
sprintf(temp_info[1], "TEMP=%d", t_dev->temp_adc);
/* TODO notify user for trip point cross */
/*
kobject_uevent_env(&((t_dev->therm_adc)->
device.kobj), KOBJ_CHANGE, temp_info);
*/
}
}
return ret;
}
static int gpadc_sys_get_trip_type(struct thermal_zone_device *thermal,
int trip, enum thermal_trip_type *type)
{
if ((trip >= 0) && (trip < TRIP_POINTS_ACTIVE_NUM))
*type = THERMAL_TRIP_ACTIVE;
else if (TRIP_POINTS_ACTIVE_NUM == trip)
*type = THERMAL_TRIP_CRITICAL;
else
*type = (enum thermal_trip_type)(-1);
return 0;
}
static int gpadc_sys_get_trip_temp(struct thermal_zone_device *thermal,
int trip, unsigned long *temp)
{
if ((trip >= 0) && (trip < TRIP_POINTS_NUM))
*temp = adc_trips_temp[trip];
else
*temp = -1;
return 0;
}
static int gpadc_sys_set_trip_temp(struct thermal_zone_device *thermal,
int trip, unsigned long temp)
{
if ((trip >= 0) && (trip < TRIP_POINTS_NUM))
adc_trips_temp[trip] = temp;
return 0;
}
static int gpadc_sys_get_crit_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
{
return adc_trips_temp[TRIP_POINTS_NUM - 1];
}
static struct thermal_zone_device_ops adc_thermal_ops = {
.get_temp = gpadc_sys_get_temp,
.get_trip_type = gpadc_sys_get_trip_type,
.get_trip_temp = gpadc_sys_get_trip_temp,
.set_trip_temp = gpadc_sys_set_trip_temp,
.get_crit_temp = gpadc_sys_get_crit_temp,
};
#ifdef CONFIG_PM_SLEEP
static int thermal_suspend(struct device *dev)
{
return 0;
}
static int thermal_resume(struct device *dev)
{
return 0;
}
static SIMPLE_DEV_PM_OPS(thermal_pm_ops,
thermal_suspend, thermal_resume);
#define PXA_TMU_PM (&thermal_pm_ops)
#else
#define PXA_TMU_PM NULL
#endif
static int gpadc_register_thermal(void)
{
/*struct cpumask mask_val;*/
int i, trip_w_mask = 0;
int tmp = 0;
for (i = 0; i < TRIP_POINTS_NUM; i++)
trip_w_mask |= (1 << i);
adc_dev.therm_adc = thermal_zone_device_register("thsens_adc",
TRIP_POINTS_NUM, trip_w_mask, NULL,
&adc_thermal_ops, NULL, 0, 5000);
if (IS_ERR(adc_dev.therm_adc)) {
pr_err("Failed to register board thermal zone device\n");
return PTR_ERR(adc_dev.therm_adc);
}
tmp = sysfs_create_group(&((adc_dev.therm_adc->device).kobj),
&thermal_attr_grp);
if (tmp < 0)
pr_err("Failed to register private adc thermal interface\n");
return 0;
}
static int gpadc_thermal_probe(struct platform_device *pdev)
{
/*
* init thermal framework
* all of initialization has been done in mfd driver;
* so currently, we need to make sure this driver is later
* than PMIC driver;
*/
return gpadc_register_thermal();
}
static int gpadc_thermal_remove(struct platform_device *pdev)
{
if (adc_dev.therm_adc)
thermal_zone_device_unregister(adc_dev.therm_adc);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id gpadc_tmu_match[] = {
{ .compatible = "marvell,gpadc-thermal", },
{},
};
MODULE_DEVICE_TABLE(of, gpadc_tmu_match);
#endif
static struct platform_driver gpadc_thermal_driver = {
.driver = {
.name = "gpadc-thermal",
.pm = PXA_TMU_PM,
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(gpadc_tmu_match),
#endif
},
.probe = gpadc_thermal_probe,
.remove = gpadc_thermal_remove,
};
module_platform_driver(gpadc_thermal_driver);
MODULE_AUTHOR("Marvell Semiconductor");
MODULE_DESCRIPTION("GPADC thermal driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:gpadc-thermal");