2011-09-07 18:49:08 +09:00
/*
2012-08-16 17:11:41 +05:30
* exynos_thermal . c - Samsung EXYNOS TMU ( Thermal Management Unit )
2011-09-07 18:49:08 +09:00
*
* Copyright ( C ) 2011 Samsung Electronics
* Donggeun Kim < dg77 . kim @ samsung . com >
2012-08-16 17:11:41 +05:30
* Amit Daniel Kachhap < amit . kachhap @ linaro . org >
2011-09-07 18:49:08 +09:00
*
* 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/workqueue.h>
# include <linux/sysfs.h>
# include <linux/kobject.h>
# include <linux/io.h>
# include <linux/mutex.h>
2012-08-16 17:11:41 +05:30
# include <linux/platform_data/exynos_thermal.h>
2012-08-16 17:11:43 +05:30
# include <linux/thermal.h>
# include <linux/cpufreq.h>
# include <linux/cpu_cooling.h>
2012-08-16 17:11:42 +05:30
# include <linux/of.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_TRIM_TEMP_MASK 0xff
# define EXYNOS_TMU_GAIN_SHIFT 8
# define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24
# define EXYNOS_TMU_CORE_ON 3
# define EXYNOS_TMU_CORE_OFF 2
# define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET 50
/* Exynos4210 specific registers */
# define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44
# define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50
# define EXYNOS4210_TMU_REG_TRIG_LEVEL1 0x54
# define EXYNOS4210_TMU_REG_TRIG_LEVEL2 0x58
# define EXYNOS4210_TMU_REG_TRIG_LEVEL3 0x5C
# define EXYNOS4210_TMU_REG_PAST_TEMP0 0x60
# define EXYNOS4210_TMU_REG_PAST_TEMP1 0x64
# define EXYNOS4210_TMU_REG_PAST_TEMP2 0x68
# define EXYNOS4210_TMU_REG_PAST_TEMP3 0x6C
# define EXYNOS4210_TMU_TRIG_LEVEL0_MASK 0x1
# define EXYNOS4210_TMU_TRIG_LEVEL1_MASK 0x10
# define EXYNOS4210_TMU_TRIG_LEVEL2_MASK 0x100
# define EXYNOS4210_TMU_TRIG_LEVEL3_MASK 0x1000
# define EXYNOS4210_TMU_INTCLEAR_VAL 0x1111
/* Exynos5250 and Exynos4412 specific registers */
# define EXYNOS_TMU_TRIMINFO_CON 0x14
# define EXYNOS_THD_TEMP_RISE 0x50
# define EXYNOS_THD_TEMP_FALL 0x54
# define EXYNOS_EMUL_CON 0x80
# define EXYNOS_TRIMINFO_RELOAD 0x1
# define EXYNOS_TMU_CLEAR_RISE_INT 0x111
2013-01-17 01:42:18 +00:00
# define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 12)
2012-08-16 17:11:42 +05:30
# define EXYNOS_MUX_ADDR_VALUE 6
# define EXYNOS_MUX_ADDR_SHIFT 20
# define EXYNOS_TMU_TRIP_MODE_SHIFT 13
# define EFUSE_MIN_VALUE 40
# define EFUSE_MAX_VALUE 100
/* In-kernel thermal framework related macros & definations */
# define SENSOR_NAME_LEN 16
# define MAX_TRIP_COUNT 8
# define MAX_COOLING_DEVICE 4
2013-02-08 01:13:06 +00:00
# define MAX_THRESHOLD_LEVS 4
2012-08-16 17:11:42 +05:30
# define ACTIVE_INTERVAL 500
# define IDLE_INTERVAL 10000
2012-08-16 17:11:43 +05:30
# define MCELSIUS 1000
2012-08-16 17:11:42 +05:30
2013-02-11 03:54:23 +00:00
# ifdef CONFIG_THERMAL_EMULATION
2012-11-21 13:31:01 +09:00
# define EXYNOS_EMUL_TIME 0x57F0
# define EXYNOS_EMUL_TIME_SHIFT 16
# define EXYNOS_EMUL_DATA_SHIFT 8
# define EXYNOS_EMUL_DATA_MASK 0xFF
# define EXYNOS_EMUL_ENABLE 0x1
2013-02-11 03:54:23 +00:00
# endif /* CONFIG_THERMAL_EMULATION */
2012-11-21 13:31:01 +09:00
2012-08-16 17:11:42 +05:30
/* CPU Zone information */
# define PANIC_ZONE 4
# define WARN_ZONE 3
# define MONITOR_ZONE 2
# define SAFE_ZONE 1
# define GET_ZONE(trip) (trip + 2)
# define GET_TRIP(zone) (zone - 2)
2012-08-16 17:11:43 +05:30
# define EXYNOS_ZONE_COUNT 3
2012-08-16 17:11:42 +05:30
struct exynos_tmu_data {
struct exynos_tmu_platform_data * pdata ;
2011-09-07 18:49:08 +09:00
struct resource * mem ;
void __iomem * base ;
int irq ;
2012-08-16 17:11:42 +05:30
enum soc_type soc ;
2011-09-07 18:49:08 +09:00
struct work_struct irq_work ;
struct mutex lock ;
struct clk * clk ;
u8 temp_error1 , temp_error2 ;
} ;
2012-08-16 17:11:43 +05:30
struct thermal_trip_point_conf {
int trip_val [ MAX_TRIP_COUNT ] ;
int trip_count ;
2013-02-08 01:13:06 +00:00
u8 trigger_falling ;
2012-08-16 17:11:43 +05:30
} ;
struct thermal_cooling_conf {
struct freq_clip_table freq_data [ MAX_TRIP_COUNT ] ;
int freq_clip_count ;
} ;
struct thermal_sensor_conf {
char name [ SENSOR_NAME_LEN ] ;
int ( * read_temperature ) ( void * data ) ;
2013-02-11 03:54:23 +00:00
int ( * write_emul_temp ) ( void * drv_data , unsigned long temp ) ;
2012-08-16 17:11:43 +05:30
struct thermal_trip_point_conf trip_data ;
struct thermal_cooling_conf cooling_data ;
void * private_data ;
} ;
struct exynos_thermal_zone {
enum thermal_device_mode mode ;
struct thermal_zone_device * therm_dev ;
struct thermal_cooling_device * cool_dev [ MAX_COOLING_DEVICE ] ;
unsigned int cool_dev_size ;
struct platform_device * exynos4_dev ;
struct thermal_sensor_conf * sensor_conf ;
bool bind ;
} ;
static struct exynos_thermal_zone * th_zone ;
static void exynos_unregister_thermal ( void ) ;
static int exynos_register_thermal ( struct thermal_sensor_conf * sensor_conf ) ;
/* Get mode callback functions for thermal zone */
static int exynos_get_mode ( struct thermal_zone_device * thermal ,
enum thermal_device_mode * mode )
{
if ( th_zone )
* mode = th_zone - > mode ;
return 0 ;
}
/* Set mode callback functions for thermal zone */
static int exynos_set_mode ( struct thermal_zone_device * thermal ,
enum thermal_device_mode mode )
{
if ( ! th_zone - > therm_dev ) {
pr_notice ( " thermal zone not registered \n " ) ;
return 0 ;
}
mutex_lock ( & th_zone - > therm_dev - > lock ) ;
2013-02-08 01:13:06 +00:00
if ( mode = = THERMAL_DEVICE_ENABLED & &
! th_zone - > sensor_conf - > trip_data . trigger_falling )
2012-08-16 17:11:43 +05:30
th_zone - > therm_dev - > polling_delay = IDLE_INTERVAL ;
else
th_zone - > therm_dev - > polling_delay = 0 ;
mutex_unlock ( & th_zone - > therm_dev - > lock ) ;
th_zone - > mode = mode ;
thermal_zone_device_update ( th_zone - > therm_dev ) ;
pr_info ( " thermal polling set for duration=%d msec \n " ,
th_zone - > therm_dev - > polling_delay ) ;
return 0 ;
}
/* Get trip type callback functions for thermal zone */
static int exynos_get_trip_type ( struct thermal_zone_device * thermal , int trip ,
enum thermal_trip_type * type )
{
switch ( GET_ZONE ( trip ) ) {
case MONITOR_ZONE :
case WARN_ZONE :
* type = THERMAL_TRIP_ACTIVE ;
break ;
case PANIC_ZONE :
* type = THERMAL_TRIP_CRITICAL ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
/* Get trip temperature callback functions for thermal zone */
static int exynos_get_trip_temp ( struct thermal_zone_device * thermal , int trip ,
unsigned long * temp )
{
if ( trip < GET_TRIP ( MONITOR_ZONE ) | | trip > GET_TRIP ( PANIC_ZONE ) )
return - EINVAL ;
* temp = th_zone - > sensor_conf - > trip_data . trip_val [ trip ] ;
/* convert the temperature into millicelsius */
* temp = * temp * MCELSIUS ;
return 0 ;
}
/* Get critical temperature callback functions for thermal zone */
static int exynos_get_crit_temp ( struct thermal_zone_device * thermal ,
unsigned long * temp )
{
int ret ;
/* Panic zone */
ret = exynos_get_trip_temp ( thermal , GET_TRIP ( PANIC_ZONE ) , temp ) ;
return ret ;
}
/* Bind callback functions for thermal zone */
static int exynos_bind ( struct thermal_zone_device * thermal ,
struct thermal_cooling_device * cdev )
{
int ret = 0 , i , tab_size , level ;
struct freq_clip_table * tab_ptr , * clip_data ;
struct thermal_sensor_conf * data = th_zone - > sensor_conf ;
tab_ptr = ( struct freq_clip_table * ) data - > cooling_data . freq_data ;
tab_size = data - > cooling_data . freq_clip_count ;
if ( tab_ptr = = NULL | | tab_size = = 0 )
return - EINVAL ;
/* find the cooling device registered*/
for ( i = 0 ; i < th_zone - > cool_dev_size ; i + + )
if ( cdev = = th_zone - > cool_dev [ i ] )
break ;
/* No matching cooling device */
if ( i = = th_zone - > cool_dev_size )
return 0 ;
/* Bind the thermal zone to the cpufreq cooling device */
for ( i = 0 ; i < tab_size ; i + + ) {
clip_data = ( struct freq_clip_table * ) & ( tab_ptr [ i ] ) ;
2013-02-08 14:52:06 +08:00
level = cpufreq_cooling_get_level ( 0 , clip_data - > freq_clip_max ) ;
if ( level = = THERMAL_CSTATE_INVALID )
2012-08-16 17:11:43 +05:30
return 0 ;
switch ( GET_ZONE ( i ) ) {
case MONITOR_ZONE :
case WARN_ZONE :
if ( thermal_zone_bind_cooling_device ( thermal , i , cdev ,
2013-02-08 01:13:07 +00:00
level , 0 ) ) {
2012-08-16 17:11:43 +05:30
pr_err ( " error binding cdev inst %d \n " , i ) ;
ret = - EINVAL ;
}
th_zone - > bind = true ;
break ;
default :
ret = - EINVAL ;
}
}
return ret ;
}
/* Unbind callback functions for thermal zone */
static int exynos_unbind ( struct thermal_zone_device * thermal ,
struct thermal_cooling_device * cdev )
{
int ret = 0 , i , tab_size ;
struct thermal_sensor_conf * data = th_zone - > sensor_conf ;
if ( th_zone - > bind = = false )
return 0 ;
tab_size = data - > cooling_data . freq_clip_count ;
if ( tab_size = = 0 )
return - EINVAL ;
/* find the cooling device registered*/
for ( i = 0 ; i < th_zone - > cool_dev_size ; i + + )
if ( cdev = = th_zone - > cool_dev [ i ] )
break ;
/* No matching cooling device */
if ( i = = th_zone - > cool_dev_size )
return 0 ;
/* Bind the thermal zone to the cpufreq cooling device */
for ( i = 0 ; i < tab_size ; i + + ) {
switch ( GET_ZONE ( i ) ) {
case MONITOR_ZONE :
case WARN_ZONE :
if ( thermal_zone_unbind_cooling_device ( thermal , i ,
cdev ) ) {
pr_err ( " error unbinding cdev inst=%d \n " , i ) ;
ret = - EINVAL ;
}
th_zone - > bind = false ;
break ;
default :
ret = - EINVAL ;
}
}
return ret ;
}
/* Get temperature callback functions for thermal zone */
static int exynos_get_temp ( struct thermal_zone_device * thermal ,
unsigned long * temp )
{
void * data ;
if ( ! th_zone - > sensor_conf ) {
pr_info ( " Temperature sensor not initialised \n " ) ;
return - EINVAL ;
}
data = th_zone - > sensor_conf - > private_data ;
* temp = th_zone - > sensor_conf - > read_temperature ( data ) ;
/* convert the temperature into millicelsius */
* temp = * temp * MCELSIUS ;
return 0 ;
}
2013-02-11 03:54:23 +00:00
/* Get temperature callback functions for thermal zone */
static int exynos_set_emul_temp ( struct thermal_zone_device * thermal ,
unsigned long temp )
{
void * data ;
int ret = - EINVAL ;
if ( ! th_zone - > sensor_conf ) {
pr_info ( " Temperature sensor not initialised \n " ) ;
return - EINVAL ;
}
data = th_zone - > sensor_conf - > private_data ;
if ( th_zone - > sensor_conf - > write_emul_temp )
ret = th_zone - > sensor_conf - > write_emul_temp ( data , temp ) ;
return ret ;
}
2012-08-16 17:11:43 +05:30
/* Get the temperature trend */
static int exynos_get_trend ( struct thermal_zone_device * thermal ,
int trip , enum thermal_trend * trend )
{
2013-01-17 01:42:18 +00:00
int ret ;
unsigned long trip_temp ;
ret = exynos_get_trip_temp ( thermal , trip , & trip_temp ) ;
if ( ret < 0 )
return ret ;
if ( thermal - > temperature > = trip_temp )
2013-02-08 01:13:07 +00:00
* trend = THERMAL_TREND_RAISE_FULL ;
2012-08-16 17:11:43 +05:30
else
2013-02-08 01:13:07 +00:00
* trend = THERMAL_TREND_DROP_FULL ;
2012-08-16 17:11:43 +05:30
return 0 ;
}
/* Operation callback functions for thermal zone */
static struct thermal_zone_device_ops const exynos_dev_ops = {
. bind = exynos_bind ,
. unbind = exynos_unbind ,
. get_temp = exynos_get_temp ,
2013-02-11 03:54:23 +00:00
. set_emul_temp = exynos_set_emul_temp ,
2012-08-16 17:11:43 +05:30
. get_trend = exynos_get_trend ,
. get_mode = exynos_get_mode ,
. set_mode = exynos_set_mode ,
. get_trip_type = exynos_get_trip_type ,
. get_trip_temp = exynos_get_trip_temp ,
. get_crit_temp = exynos_get_crit_temp ,
} ;
/*
* This function may be called from interrupt based temperature sensor
* when threshold is changed .
*/
static void exynos_report_trigger ( void )
{
unsigned int i ;
char data [ 10 ] ;
char * envp [ ] = { data , NULL } ;
if ( ! th_zone | | ! th_zone - > therm_dev )
return ;
if ( th_zone - > bind = = false ) {
for ( i = 0 ; i < th_zone - > cool_dev_size ; i + + ) {
if ( ! th_zone - > cool_dev [ i ] )
continue ;
exynos_bind ( th_zone - > therm_dev ,
th_zone - > cool_dev [ i ] ) ;
}
}
thermal_zone_device_update ( th_zone - > therm_dev ) ;
mutex_lock ( & th_zone - > therm_dev - > lock ) ;
/* Find the level for which trip happened */
for ( i = 0 ; i < th_zone - > sensor_conf - > trip_data . trip_count ; i + + ) {
if ( th_zone - > therm_dev - > last_temperature <
th_zone - > sensor_conf - > trip_data . trip_val [ i ] * MCELSIUS )
break ;
}
2013-02-08 01:13:06 +00:00
if ( th_zone - > mode = = THERMAL_DEVICE_ENABLED & &
! th_zone - > sensor_conf - > trip_data . trigger_falling ) {
2012-08-16 17:11:43 +05:30
if ( i > 0 )
th_zone - > therm_dev - > polling_delay = ACTIVE_INTERVAL ;
else
th_zone - > therm_dev - > polling_delay = IDLE_INTERVAL ;
}
snprintf ( data , sizeof ( data ) , " %u " , i ) ;
kobject_uevent_env ( & th_zone - > therm_dev - > device . kobj , KOBJ_CHANGE , envp ) ;
mutex_unlock ( & th_zone - > therm_dev - > lock ) ;
}
/* Register with the in-kernel thermal management */
static int exynos_register_thermal ( struct thermal_sensor_conf * sensor_conf )
{
int ret ;
struct cpumask mask_val ;
if ( ! sensor_conf | | ! sensor_conf - > read_temperature ) {
pr_err ( " Temperature sensor not initialised \n " ) ;
return - EINVAL ;
}
th_zone = kzalloc ( sizeof ( struct exynos_thermal_zone ) , GFP_KERNEL ) ;
if ( ! th_zone )
return - ENOMEM ;
th_zone - > sensor_conf = sensor_conf ;
cpumask_set_cpu ( 0 , & mask_val ) ;
th_zone - > cool_dev [ 0 ] = cpufreq_cooling_register ( & mask_val ) ;
if ( IS_ERR ( th_zone - > cool_dev [ 0 ] ) ) {
pr_err ( " Failed to register cpufreq cooling device \n " ) ;
ret = - EINVAL ;
goto err_unregister ;
}
th_zone - > cool_dev_size + + ;
th_zone - > therm_dev = thermal_zone_device_register ( sensor_conf - > name ,
2012-09-18 11:04:56 +05:30
EXYNOS_ZONE_COUNT , 0 , NULL , & exynos_dev_ops , NULL , 0 ,
2013-02-08 01:13:06 +00:00
sensor_conf - > trip_data . trigger_falling ?
0 : IDLE_INTERVAL ) ;
2012-08-16 17:11:43 +05:30
if ( IS_ERR ( th_zone - > therm_dev ) ) {
pr_err ( " Failed to register thermal zone device \n " ) ;
2013-03-04 16:52:48 +00:00
ret = PTR_ERR ( th_zone - > therm_dev ) ;
2012-08-16 17:11:43 +05:30
goto err_unregister ;
}
th_zone - > mode = THERMAL_DEVICE_ENABLED ;
pr_info ( " Exynos: Kernel Thermal management registered \n " ) ;
return 0 ;
err_unregister :
exynos_unregister_thermal ( ) ;
return ret ;
}
/* Un-Register with the in-kernel thermal management */
static void exynos_unregister_thermal ( void )
{
int i ;
2012-09-27 16:20:38 +05:30
if ( ! th_zone )
return ;
if ( th_zone - > therm_dev )
2012-08-16 17:11:43 +05:30
thermal_zone_device_unregister ( th_zone - > therm_dev ) ;
for ( i = 0 ; i < th_zone - > cool_dev_size ; i + + ) {
2012-09-27 16:20:38 +05:30
if ( th_zone - > cool_dev [ i ] )
2012-08-16 17:11:43 +05:30
cpufreq_cooling_unregister ( th_zone - > cool_dev [ i ] ) ;
}
kfree ( th_zone ) ;
pr_info ( " Exynos: Kernel Thermal management unregistered \n " ) ;
}
2011-09-07 18:49:08 +09:00
/*
* TMU treats temperature as a mapped temperature code .
* The temperature is converted differently depending on the calibration type .
*/
2012-08-16 17:11:42 +05:30
static int temp_to_code ( struct exynos_tmu_data * data , u8 temp )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
struct exynos_tmu_platform_data * pdata = data - > pdata ;
2011-09-07 18:49:08 +09:00
int temp_code ;
2012-08-16 17:11:42 +05:30
if ( data - > soc = = SOC_ARCH_EXYNOS4210 )
/* temp should range between 25 and 125 */
if ( temp < 25 | | temp > 125 ) {
temp_code = - EINVAL ;
goto out ;
}
2011-09-07 18:49:08 +09:00
switch ( pdata - > cal_type ) {
case TYPE_TWO_POINT_TRIMMING :
temp_code = ( temp - 25 ) *
( data - > temp_error2 - data - > temp_error1 ) /
( 85 - 25 ) + data - > temp_error1 ;
break ;
case TYPE_ONE_POINT_TRIMMING :
temp_code = temp + data - > temp_error1 - 25 ;
break ;
default :
2012-08-16 17:11:42 +05:30
temp_code = temp + EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET ;
2011-09-07 18:49:08 +09:00
break ;
}
out :
return temp_code ;
}
/*
* Calculate a temperature value from a temperature code .
* The unit of the temperature is degree Celsius .
*/
2012-08-16 17:11:42 +05:30
static int code_to_temp ( struct exynos_tmu_data * data , u8 temp_code )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
struct exynos_tmu_platform_data * pdata = data - > pdata ;
2011-09-07 18:49:08 +09:00
int temp ;
2012-08-16 17:11:42 +05:30
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 ;
}
2011-09-07 18:49:08 +09:00
switch ( pdata - > cal_type ) {
case TYPE_TWO_POINT_TRIMMING :
temp = ( temp_code - data - > temp_error1 ) * ( 85 - 25 ) /
( data - > temp_error2 - data - > temp_error1 ) + 25 ;
break ;
case TYPE_ONE_POINT_TRIMMING :
temp = temp_code - data - > temp_error1 + 25 ;
break ;
default :
2012-08-16 17:11:42 +05:30
temp = temp_code - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET ;
2011-09-07 18:49:08 +09:00
break ;
}
out :
return temp ;
}
2012-08-16 17:11:42 +05:30
static int exynos_tmu_initialize ( struct platform_device * pdev )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
struct exynos_tmu_data * data = platform_get_drvdata ( pdev ) ;
struct exynos_tmu_platform_data * pdata = data - > pdata ;
2013-02-08 01:13:06 +00:00
unsigned int status , trim_info ;
unsigned int rising_threshold = 0 , falling_threshold = 0 ;
int ret = 0 , threshold_code , i , trigger_levs = 0 ;
2011-09-07 18:49:08 +09:00
mutex_lock ( & data - > lock ) ;
clk_enable ( data - > clk ) ;
2012-08-16 17:11:42 +05:30
status = readb ( data - > base + EXYNOS_TMU_REG_STATUS ) ;
2011-09-07 18:49:08 +09:00
if ( ! status ) {
ret = - EBUSY ;
goto out ;
}
2012-08-16 17:11:42 +05:30
if ( data - > soc = = SOC_ARCH_EXYNOS ) {
__raw_writel ( EXYNOS_TRIMINFO_RELOAD ,
data - > base + EXYNOS_TMU_TRIMINFO_CON ) ;
}
2011-09-07 18:49:08 +09:00
/* Save trimming info in order to perform calibration */
2012-08-16 17:11:42 +05:30
trim_info = readl ( data - > base + EXYNOS_TMU_REG_TRIMINFO ) ;
data - > temp_error1 = trim_info & EXYNOS_TMU_TRIM_TEMP_MASK ;
data - > temp_error2 = ( ( trim_info > > 8 ) & EXYNOS_TMU_TRIM_TEMP_MASK ) ;
if ( ( EFUSE_MIN_VALUE > data - > temp_error1 ) | |
( data - > temp_error1 > EFUSE_MAX_VALUE ) | |
( data - > temp_error2 ! = 0 ) )
data - > temp_error1 = pdata - > efuse_value ;
2013-02-08 01:13:06 +00:00
/* Count trigger levels to be enabled */
for ( i = 0 ; i < MAX_THRESHOLD_LEVS ; i + + )
if ( pdata - > trigger_levels [ i ] )
trigger_levs + + ;
2012-08-16 17:11:42 +05:30
if ( data - > soc = = SOC_ARCH_EXYNOS4210 ) {
/* Write temperature code for threshold */
threshold_code = temp_to_code ( data , pdata - > threshold ) ;
if ( threshold_code < 0 ) {
ret = threshold_code ;
goto out ;
}
writeb ( threshold_code ,
data - > base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP ) ;
2013-02-08 01:13:06 +00:00
for ( i = 0 ; i < trigger_levs ; i + + )
writeb ( pdata - > trigger_levels [ i ] ,
data - > base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4 ) ;
2012-08-16 17:11:42 +05:30
writel ( EXYNOS4210_TMU_INTCLEAR_VAL ,
data - > base + EXYNOS_TMU_REG_INTCLEAR ) ;
} else if ( data - > soc = = SOC_ARCH_EXYNOS ) {
2013-02-08 01:13:06 +00:00
/* Write temperature code for rising and falling threshold */
for ( i = 0 ; i < trigger_levs ; i + + ) {
threshold_code = temp_to_code ( data ,
pdata - > trigger_levels [ i ] ) ;
if ( threshold_code < 0 ) {
ret = threshold_code ;
goto out ;
}
rising_threshold | = threshold_code < < 8 * i ;
if ( pdata - > threshold_falling ) {
threshold_code = temp_to_code ( data ,
pdata - > trigger_levels [ i ] -
pdata - > threshold_falling ) ;
if ( threshold_code > 0 )
falling_threshold | =
threshold_code < < 8 * i ;
}
2012-08-16 17:11:42 +05:30
}
writel ( rising_threshold ,
data - > base + EXYNOS_THD_TEMP_RISE ) ;
2013-02-08 01:13:06 +00:00
writel ( falling_threshold ,
data - > base + EXYNOS_THD_TEMP_FALL ) ;
2012-08-16 17:11:42 +05:30
2013-02-08 01:13:06 +00:00
writel ( EXYNOS_TMU_CLEAR_RISE_INT | EXYNOS_TMU_CLEAR_FALL_INT ,
2012-08-16 17:11:42 +05:30
data - > base + EXYNOS_TMU_REG_INTCLEAR ) ;
2011-09-07 18:49:08 +09:00
}
out :
clk_disable ( data - > clk ) ;
mutex_unlock ( & data - > lock ) ;
return ret ;
}
2012-08-16 17:11:42 +05:30
static void exynos_tmu_control ( struct platform_device * pdev , bool on )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
struct exynos_tmu_data * data = platform_get_drvdata ( pdev ) ;
struct exynos_tmu_platform_data * pdata = data - > pdata ;
2011-09-07 18:49:08 +09:00
unsigned int con , interrupt_en ;
mutex_lock ( & data - > lock ) ;
clk_enable ( data - > clk ) ;
2012-08-16 17:11:42 +05:30
con = pdata - > reference_voltage < < EXYNOS_TMU_REF_VOLTAGE_SHIFT |
pdata - > gain < < EXYNOS_TMU_GAIN_SHIFT ;
if ( data - > soc = = SOC_ARCH_EXYNOS ) {
con | = pdata - > noise_cancel_mode < < EXYNOS_TMU_TRIP_MODE_SHIFT ;
con | = ( EXYNOS_MUX_ADDR_VALUE < < EXYNOS_MUX_ADDR_SHIFT ) ;
}
2011-09-07 18:49:08 +09:00
if ( on ) {
2012-08-16 17:11:42 +05:30
con | = EXYNOS_TMU_CORE_ON ;
2011-09-07 18:49:08 +09:00
interrupt_en = pdata - > trigger_level3_en < < 12 |
pdata - > trigger_level2_en < < 8 |
pdata - > trigger_level1_en < < 4 |
pdata - > trigger_level0_en ;
2013-02-08 01:13:06 +00:00
if ( pdata - > threshold_falling )
interrupt_en | = interrupt_en < < 16 ;
2011-09-07 18:49:08 +09:00
} else {
2012-08-16 17:11:42 +05:30
con | = EXYNOS_TMU_CORE_OFF ;
2011-09-07 18:49:08 +09:00
interrupt_en = 0 ; /* Disable all interrupts */
}
2012-08-16 17:11:42 +05:30
writel ( interrupt_en , data - > base + EXYNOS_TMU_REG_INTEN ) ;
writel ( con , data - > base + EXYNOS_TMU_REG_CONTROL ) ;
2011-09-07 18:49:08 +09:00
clk_disable ( data - > clk ) ;
mutex_unlock ( & data - > lock ) ;
}
2012-08-16 17:11:42 +05:30
static int exynos_tmu_read ( struct exynos_tmu_data * data )
2011-09-07 18:49:08 +09:00
{
u8 temp_code ;
int temp ;
mutex_lock ( & data - > lock ) ;
clk_enable ( data - > clk ) ;
2012-08-16 17:11:42 +05:30
temp_code = readb ( data - > base + EXYNOS_TMU_REG_CURRENT_TEMP ) ;
2011-09-07 18:49:08 +09:00
temp = code_to_temp ( data , temp_code ) ;
clk_disable ( data - > clk ) ;
mutex_unlock ( & data - > lock ) ;
return temp ;
}
2013-02-11 03:54:23 +00:00
# ifdef CONFIG_THERMAL_EMULATION
static int exynos_tmu_set_emulation ( void * drv_data , unsigned long temp )
{
struct exynos_tmu_data * data = drv_data ;
unsigned int reg ;
int ret = - EINVAL ;
if ( data - > soc = = SOC_ARCH_EXYNOS4210 )
goto out ;
if ( temp & & temp < MCELSIUS )
goto out ;
mutex_lock ( & data - > lock ) ;
clk_enable ( data - > clk ) ;
reg = readl ( data - > base + EXYNOS_EMUL_CON ) ;
if ( temp ) {
temp / = MCELSIUS ;
reg = ( EXYNOS_EMUL_TIME < < EXYNOS_EMUL_TIME_SHIFT ) |
( temp_to_code ( data , temp )
< < EXYNOS_EMUL_DATA_SHIFT ) | EXYNOS_EMUL_ENABLE ;
} else {
reg & = ~ EXYNOS_EMUL_ENABLE ;
}
writel ( reg , data - > base + EXYNOS_EMUL_CON ) ;
clk_disable ( data - > clk ) ;
mutex_unlock ( & data - > lock ) ;
return 0 ;
out :
return ret ;
}
# else
static int exynos_tmu_set_emulation ( void * drv_data , unsigned long temp )
{ return - EINVAL ; }
# endif /*CONFIG_THERMAL_EMULATION*/
2012-08-16 17:11:42 +05:30
static void exynos_tmu_work ( struct work_struct * work )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
struct exynos_tmu_data * data = container_of ( work ,
struct exynos_tmu_data , irq_work ) ;
2011-09-07 18:49:08 +09:00
2013-01-17 01:42:18 +00:00
exynos_report_trigger ( ) ;
2011-09-07 18:49:08 +09:00
mutex_lock ( & data - > lock ) ;
clk_enable ( data - > clk ) ;
2012-08-16 17:11:42 +05:30
if ( data - > soc = = SOC_ARCH_EXYNOS )
2013-02-08 01:13:06 +00:00
writel ( EXYNOS_TMU_CLEAR_RISE_INT |
EXYNOS_TMU_CLEAR_FALL_INT ,
2012-08-16 17:11:42 +05:30
data - > base + EXYNOS_TMU_REG_INTCLEAR ) ;
else
writel ( EXYNOS4210_TMU_INTCLEAR_VAL ,
data - > base + EXYNOS_TMU_REG_INTCLEAR ) ;
2011-09-07 18:49:08 +09:00
clk_disable ( data - > clk ) ;
mutex_unlock ( & data - > lock ) ;
2013-01-17 01:42:18 +00:00
2012-08-16 17:11:42 +05:30
enable_irq ( data - > irq ) ;
2011-09-07 18:49:08 +09:00
}
2012-08-16 17:11:42 +05:30
static irqreturn_t exynos_tmu_irq ( int irq , void * id )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
struct exynos_tmu_data * data = id ;
2011-09-07 18:49:08 +09:00
disable_irq_nosync ( irq ) ;
schedule_work ( & data - > irq_work ) ;
return IRQ_HANDLED ;
}
2012-08-16 17:11:43 +05:30
static struct thermal_sensor_conf exynos_sensor_conf = {
. name = " exynos-therm " ,
. read_temperature = ( int ( * ) ( void * ) ) exynos_tmu_read ,
2013-02-11 03:54:23 +00:00
. write_emul_temp = exynos_tmu_set_emulation ,
2012-08-16 17:11:44 +05:30
} ;
# if defined(CONFIG_CPU_EXYNOS4210)
static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = {
. threshold = 80 ,
. trigger_levels [ 0 ] = 5 ,
. trigger_levels [ 1 ] = 20 ,
. trigger_levels [ 2 ] = 30 ,
. trigger_level0_en = 1 ,
. trigger_level1_en = 1 ,
. trigger_level2_en = 1 ,
. trigger_level3_en = 0 ,
. gain = 15 ,
. reference_voltage = 7 ,
. cal_type = TYPE_ONE_POINT_TRIMMING ,
. freq_tab [ 0 ] = {
. freq_clip_max = 800 * 1000 ,
. temp_level = 85 ,
} ,
. freq_tab [ 1 ] = {
. freq_clip_max = 200 * 1000 ,
. temp_level = 100 ,
} ,
. freq_tab_count = 2 ,
. type = SOC_ARCH_EXYNOS4210 ,
} ;
# define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data)
# else
# define EXYNOS4210_TMU_DRV_DATA (NULL)
# endif
2013-06-19 01:31:50 +09:00
# if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412) || \
defined ( CONFIG_SOC_EXYNOS4212 )
2012-08-16 17:11:44 +05:30
static struct exynos_tmu_platform_data const exynos_default_tmu_data = {
2013-02-08 01:13:06 +00:00
. threshold_falling = 10 ,
2012-08-16 17:11:44 +05:30
. trigger_levels [ 0 ] = 85 ,
. trigger_levels [ 1 ] = 103 ,
. trigger_levels [ 2 ] = 110 ,
. trigger_level0_en = 1 ,
. trigger_level1_en = 1 ,
. trigger_level2_en = 1 ,
. trigger_level3_en = 0 ,
. gain = 8 ,
. reference_voltage = 16 ,
. noise_cancel_mode = 4 ,
. cal_type = TYPE_ONE_POINT_TRIMMING ,
. efuse_value = 55 ,
. freq_tab [ 0 ] = {
. freq_clip_max = 800 * 1000 ,
. temp_level = 85 ,
} ,
. freq_tab [ 1 ] = {
. freq_clip_max = 200 * 1000 ,
. temp_level = 103 ,
} ,
. freq_tab_count = 2 ,
. type = SOC_ARCH_EXYNOS ,
} ;
# define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data)
# else
# define EXYNOS_TMU_DRV_DATA (NULL)
# endif
# ifdef CONFIG_OF
static const struct of_device_id exynos_tmu_match [ ] = {
{
. compatible = " samsung,exynos4210-tmu " ,
. data = ( void * ) EXYNOS4210_TMU_DRV_DATA ,
} ,
2013-04-18 11:37:59 +00:00
{
. compatible = " samsung,exynos4412-tmu " ,
. data = ( void * ) EXYNOS_TMU_DRV_DATA ,
} ,
2012-08-16 17:11:44 +05:30
{
. compatible = " samsung,exynos5250-tmu " ,
. data = ( void * ) EXYNOS_TMU_DRV_DATA ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , exynos_tmu_match ) ;
# endif
static struct platform_device_id exynos_tmu_driver_ids [ ] = {
{
. name = " exynos4210-tmu " ,
. driver_data = ( kernel_ulong_t ) EXYNOS4210_TMU_DRV_DATA ,
} ,
{
. name = " exynos5250-tmu " ,
. driver_data = ( kernel_ulong_t ) EXYNOS_TMU_DRV_DATA ,
} ,
{ } ,
} ;
2012-10-23 14:54:42 +08:00
MODULE_DEVICE_TABLE ( platform , exynos_tmu_driver_ids ) ;
2012-08-16 17:11:44 +05:30
static inline struct exynos_tmu_platform_data * exynos_get_driver_data (
struct platform_device * pdev )
{
# ifdef CONFIG_OF
if ( pdev - > dev . of_node ) {
const struct of_device_id * match ;
match = of_match_node ( exynos_tmu_match , pdev - > dev . of_node ) ;
if ( ! match )
return NULL ;
return ( struct exynos_tmu_platform_data * ) match - > data ;
}
# endif
return ( struct exynos_tmu_platform_data * )
platform_get_device_id ( pdev ) - > driver_data ;
2012-08-16 17:11:43 +05:30
}
2012-11-21 13:31:01 +09:00
2012-12-21 13:15:52 -08:00
static int exynos_tmu_probe ( struct platform_device * pdev )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
struct exynos_tmu_data * data ;
struct exynos_tmu_platform_data * pdata = pdev - > dev . platform_data ;
2012-08-16 17:11:43 +05:30
int ret , i ;
2011-09-07 18:49:08 +09:00
2012-08-16 17:11:44 +05:30
if ( ! pdata )
pdata = exynos_get_driver_data ( pdev ) ;
2011-09-07 18:49:08 +09:00
if ( ! pdata ) {
dev_err ( & pdev - > dev , " No platform init data supplied. \n " ) ;
return - ENODEV ;
}
2012-08-16 05:41:45 -06:00
data = devm_kzalloc ( & pdev - > dev , sizeof ( struct exynos_tmu_data ) ,
GFP_KERNEL ) ;
2011-09-07 18:49:08 +09:00
if ( ! data ) {
dev_err ( & pdev - > dev , " Failed to allocate driver structure \n " ) ;
return - ENOMEM ;
}
data - > irq = platform_get_irq ( pdev , 0 ) ;
if ( data - > irq < 0 ) {
dev_err ( & pdev - > dev , " Failed to get platform irq \n " ) ;
2012-08-16 05:41:45 -06:00
return data - > irq ;
2011-09-07 18:49:08 +09:00
}
2012-08-16 17:11:42 +05:30
INIT_WORK ( & data - > irq_work , exynos_tmu_work ) ;
2011-09-07 18:49:08 +09:00
data - > mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-01-21 11:09:20 +01:00
data - > base = devm_ioremap_resource ( & pdev - > dev , data - > mem ) ;
if ( IS_ERR ( data - > base ) )
return PTR_ERR ( data - > base ) ;
2011-09-07 18:49:08 +09:00
2012-08-16 05:41:45 -06:00
ret = devm_request_irq ( & pdev - > dev , data - > irq , exynos_tmu_irq ,
2012-08-16 17:11:42 +05:30
IRQF_TRIGGER_RISING , " exynos-tmu " , data ) ;
2011-09-07 18:49:08 +09:00
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to request irq: %d \n " , data - > irq ) ;
2012-08-16 05:41:45 -06:00
return ret ;
2011-09-07 18:49:08 +09:00
}
2013-04-18 11:37:58 +00:00
data - > clk = devm_clk_get ( & pdev - > dev , " tmu_apbif " ) ;
2011-09-07 18:49:08 +09:00
if ( IS_ERR ( data - > clk ) ) {
dev_err ( & pdev - > dev , " Failed to get clock \n " ) ;
2012-08-16 05:41:45 -06:00
return PTR_ERR ( data - > clk ) ;
2011-09-07 18:49:08 +09:00
}
2013-04-18 11:37:58 +00:00
ret = clk_prepare ( data - > clk ) ;
if ( ret )
return ret ;
2012-08-16 17:11:42 +05:30
if ( pdata - > type = = SOC_ARCH_EXYNOS | |
pdata - > type = = SOC_ARCH_EXYNOS4210 )
data - > soc = pdata - > type ;
else {
ret = - EINVAL ;
dev_err ( & pdev - > dev , " Platform not supported \n " ) ;
goto err_clk ;
}
2011-09-07 18:49:08 +09:00
data - > pdata = pdata ;
platform_set_drvdata ( pdev , data ) ;
mutex_init ( & data - > lock ) ;
2012-08-16 17:11:42 +05:30
ret = exynos_tmu_initialize ( pdev ) ;
2011-09-07 18:49:08 +09:00
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to initialize TMU \n " ) ;
goto err_clk ;
}
2012-08-16 17:11:42 +05:30
exynos_tmu_control ( pdev , true ) ;
2011-09-07 18:49:08 +09:00
2012-08-16 17:11:43 +05:30
/* Register the sensor with thermal management interface */
( & exynos_sensor_conf ) - > private_data = data ;
exynos_sensor_conf . trip_data . trip_count = pdata - > trigger_level0_en +
pdata - > trigger_level1_en + pdata - > trigger_level2_en +
pdata - > trigger_level3_en ;
for ( i = 0 ; i < exynos_sensor_conf . trip_data . trip_count ; i + + )
exynos_sensor_conf . trip_data . trip_val [ i ] =
pdata - > threshold + pdata - > trigger_levels [ i ] ;
2013-02-08 01:13:06 +00:00
exynos_sensor_conf . trip_data . trigger_falling = pdata - > threshold_falling ;
2012-08-16 17:11:43 +05:30
exynos_sensor_conf . cooling_data . freq_clip_count =
pdata - > freq_tab_count ;
for ( i = 0 ; i < pdata - > freq_tab_count ; i + + ) {
exynos_sensor_conf . cooling_data . freq_data [ i ] . freq_clip_max =
pdata - > freq_tab [ i ] . freq_clip_max ;
exynos_sensor_conf . cooling_data . freq_data [ i ] . temp_level =
pdata - > freq_tab [ i ] . temp_level ;
}
ret = exynos_register_thermal ( & exynos_sensor_conf ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register thermal interface \n " ) ;
goto err_clk ;
}
2012-11-21 13:31:01 +09:00
2011-09-07 18:49:08 +09:00
return 0 ;
err_clk :
2013-04-18 11:37:58 +00:00
clk_unprepare ( data - > clk ) ;
2011-09-07 18:49:08 +09:00
return ret ;
}
2012-12-21 13:15:52 -08:00
static int exynos_tmu_remove ( struct platform_device * pdev )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
struct exynos_tmu_data * data = platform_get_drvdata ( pdev ) ;
2011-09-07 18:49:08 +09:00
2012-08-16 17:11:42 +05:30
exynos_tmu_control ( pdev , false ) ;
2011-09-07 18:49:08 +09:00
2012-08-16 17:11:43 +05:30
exynos_unregister_thermal ( ) ;
2013-04-18 11:37:58 +00:00
clk_unprepare ( data - > clk ) ;
2011-09-07 18:49:08 +09:00
return 0 ;
}
2012-07-08 21:48:15 +02:00
# ifdef CONFIG_PM_SLEEP
2012-08-16 17:11:42 +05:30
static int exynos_tmu_suspend ( struct device * dev )
2011-09-07 18:49:08 +09:00
{
2012-08-16 17:11:42 +05:30
exynos_tmu_control ( to_platform_device ( dev ) , false ) ;
2011-09-07 18:49:08 +09:00
return 0 ;
}
2012-08-16 17:11:42 +05:30
static int exynos_tmu_resume ( struct device * dev )
2011-09-07 18:49:08 +09:00
{
2012-07-08 21:48:15 +02:00
struct platform_device * pdev = to_platform_device ( dev ) ;
2012-08-16 17:11:42 +05:30
exynos_tmu_initialize ( pdev ) ;
exynos_tmu_control ( pdev , true ) ;
2011-09-07 18:49:08 +09:00
return 0 ;
}
2012-07-08 21:48:15 +02:00
2012-08-16 17:11:42 +05:30
static SIMPLE_DEV_PM_OPS ( exynos_tmu_pm ,
exynos_tmu_suspend , exynos_tmu_resume ) ;
# define EXYNOS_TMU_PM (&exynos_tmu_pm)
2011-09-07 18:49:08 +09:00
# else
2012-08-16 17:11:42 +05:30
# define EXYNOS_TMU_PM NULL
2011-09-07 18:49:08 +09:00
# endif
2012-08-16 17:11:42 +05:30
static struct platform_driver exynos_tmu_driver = {
2011-09-07 18:49:08 +09:00
. driver = {
2012-08-16 17:11:42 +05:30
. name = " exynos-tmu " ,
2011-09-07 18:49:08 +09:00
. owner = THIS_MODULE ,
2012-08-16 17:11:42 +05:30
. pm = EXYNOS_TMU_PM ,
2012-12-12 15:54:24 +05:30
. of_match_table = of_match_ptr ( exynos_tmu_match ) ,
2011-09-07 18:49:08 +09:00
} ,
2012-08-16 17:11:42 +05:30
. probe = exynos_tmu_probe ,
2012-12-21 13:15:52 -08:00
. remove = exynos_tmu_remove ,
2012-08-16 17:11:44 +05:30
. id_table = exynos_tmu_driver_ids ,
2011-09-07 18:49:08 +09:00
} ;
2012-08-16 17:11:42 +05:30
module_platform_driver ( exynos_tmu_driver ) ;
2011-09-07 18:49:08 +09:00
2012-08-16 17:11:42 +05:30
MODULE_DESCRIPTION ( " EXYNOS TMU Driver " ) ;
2011-09-07 18:49:08 +09:00
MODULE_AUTHOR ( " Donggeun Kim <dg77.kim@samsung.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
2012-08-16 17:11:42 +05:30
MODULE_ALIAS ( " platform:exynos-tmu " ) ;