2019-05-27 09:55:06 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2012-11-15 14:56:42 +04:00
/*
* db8500_thermal . c - DB8500 Thermal Management Implementation
*
* Copyright ( C ) 2012 ST - Ericsson
2019-08-28 16:03:20 +03:00
* Copyright ( C ) 2012 - 2019 Linaro Ltd .
2012-11-15 14:56:42 +04:00
*
2019-08-28 16:03:20 +03:00
* Authors : Hongbo Zhang , Linus Walleij
2012-11-15 14:56:42 +04:00
*/
# include <linux/cpu_cooling.h>
# include <linux/interrupt.h>
# include <linux/mfd/dbx500-prcmu.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/thermal.h>
# define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
# define PRCMU_DEFAULT_LOW_TEMP 0
2019-08-28 16:03:18 +03:00
2019-08-28 16:03:20 +03:00
/**
* db8500_thermal_points - the interpolation points that trigger
* interrupts
*/
static const unsigned long db8500_thermal_points [ ] = {
15000 ,
20000 ,
25000 ,
30000 ,
35000 ,
40000 ,
45000 ,
50000 ,
55000 ,
60000 ,
65000 ,
70000 ,
75000 ,
80000 ,
/*
* This is where things start to get really bad for the
* SoC and the thermal zones should be set up to trigger
* critical temperature at 85000 mC so we don ' t get above
* this point .
*/
85000 ,
90000 ,
95000 ,
100000 ,
2019-08-28 16:03:18 +03:00
} ;
2012-11-15 14:56:42 +04:00
struct db8500_thermal_zone {
2019-08-28 16:03:20 +03:00
struct thermal_zone_device * tz ;
2012-11-15 14:56:42 +04:00
enum thermal_trend trend ;
2019-08-28 16:03:20 +03:00
unsigned long interpolated_temp ;
2012-11-15 14:56:42 +04:00
unsigned int cur_index ;
} ;
/* Callback to get current temperature */
2019-08-28 16:03:20 +03:00
static int db8500_thermal_get_temp ( void * data , int * temp )
2012-11-15 14:56:42 +04:00
{
2019-08-28 16:03:20 +03:00
struct db8500_thermal_zone * th = data ;
2012-11-15 14:56:42 +04:00
/*
* TODO : There is no PRCMU interface to get temperature data currently ,
* so a pseudo temperature is returned , it works for thermal framework
* and this will be fixed when the PRCMU interface is available .
*/
2019-08-28 16:03:20 +03:00
* temp = th - > interpolated_temp ;
2012-11-15 14:56:42 +04:00
return 0 ;
}
/* Callback to get temperature changing trend */
2019-08-28 16:03:20 +03:00
static int db8500_thermal_get_trend ( void * data , int trip , enum thermal_trend * trend )
2012-11-15 14:56:42 +04:00
{
2019-08-28 16:03:20 +03:00
struct db8500_thermal_zone * th = data ;
2012-11-15 14:56:42 +04:00
2019-08-28 16:03:20 +03:00
* trend = th - > trend ;
2012-11-15 14:56:42 +04:00
return 0 ;
}
2019-08-28 16:03:20 +03:00
static struct thermal_zone_of_device_ops thdev_ops = {
. get_temp = db8500_thermal_get_temp ,
. get_trend = db8500_thermal_get_trend ,
2012-11-15 14:56:42 +04:00
} ;
2019-08-28 16:03:20 +03:00
static void db8500_thermal_update_config ( struct db8500_thermal_zone * th ,
unsigned int idx ,
enum thermal_trend trend ,
unsigned long next_low ,
unsigned long next_high )
2012-11-15 14:56:42 +04:00
{
prcmu_stop_temp_sense ( ) ;
2019-08-28 16:03:20 +03:00
th - > cur_index = idx ;
th - > interpolated_temp = ( next_low + next_high ) / 2 ;
th - > trend = trend ;
2012-11-15 14:56:42 +04:00
2019-08-28 16:03:20 +03:00
/*
* The PRCMU accept absolute temperatures in celsius so divide
* down the millicelsius with 1000
*/
2012-11-15 14:56:42 +04:00
prcmu_config_hotmon ( ( u8 ) ( next_low / 1000 ) , ( u8 ) ( next_high / 1000 ) ) ;
prcmu_start_temp_sense ( PRCMU_DEFAULT_MEASURE_TIME ) ;
}
static irqreturn_t prcmu_low_irq_handler ( int irq , void * irq_data )
{
2019-08-28 16:03:20 +03:00
struct db8500_thermal_zone * th = irq_data ;
unsigned int idx = th - > cur_index ;
2012-11-15 14:56:42 +04:00
unsigned long next_low , next_high ;
2019-08-28 16:03:20 +03:00
if ( idx = = 0 )
2012-11-15 14:56:42 +04:00
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED ;
if ( idx = = 1 ) {
2019-08-28 16:03:20 +03:00
next_high = db8500_thermal_points [ 0 ] ;
2012-11-15 14:56:42 +04:00
next_low = PRCMU_DEFAULT_LOW_TEMP ;
} else {
2019-08-28 16:03:20 +03:00
next_high = db8500_thermal_points [ idx - 1 ] ;
next_low = db8500_thermal_points [ idx - 2 ] ;
2012-11-15 14:56:42 +04:00
}
idx - = 1 ;
2019-08-28 16:03:20 +03:00
db8500_thermal_update_config ( th , idx , THERMAL_TREND_DROPPING ,
next_low , next_high ) ;
dev_dbg ( & th - > tz - > device ,
2012-11-15 14:56:42 +04:00
" PRCMU set max %ld, min %ld \n " , next_high , next_low ) ;
2019-08-28 16:03:20 +03:00
thermal_zone_device_update ( th - > tz , THERMAL_EVENT_UNSPECIFIED ) ;
2012-11-15 14:56:42 +04:00
return IRQ_HANDLED ;
}
static irqreturn_t prcmu_high_irq_handler ( int irq , void * irq_data )
{
2019-08-28 16:03:20 +03:00
struct db8500_thermal_zone * th = irq_data ;
unsigned int idx = th - > cur_index ;
2012-11-15 14:56:42 +04:00
unsigned long next_low , next_high ;
2019-08-28 16:03:20 +03:00
int num_points = ARRAY_SIZE ( db8500_thermal_points ) ;
2012-11-15 14:56:42 +04:00
2019-08-28 16:03:20 +03:00
if ( idx < num_points - 1 ) {
next_high = db8500_thermal_points [ idx + 1 ] ;
next_low = db8500_thermal_points [ idx ] ;
2012-11-15 14:56:42 +04:00
idx + = 1 ;
2019-08-28 16:03:20 +03:00
db8500_thermal_update_config ( th , idx , THERMAL_TREND_RAISING ,
next_low , next_high ) ;
2012-11-15 14:56:42 +04:00
2019-11-19 10:46:50 +03:00
dev_dbg ( & th - > tz - > device ,
" PRCMU set max %ld, min %ld \n " , next_high , next_low ) ;
2019-08-28 16:03:20 +03:00
} else if ( idx = = num_points - 1 )
/* So we roof out 1 degree over the max point */
th - > interpolated_temp = db8500_thermal_points [ idx ] + 1 ;
2012-11-15 14:56:42 +04:00
2019-08-28 16:03:20 +03:00
thermal_zone_device_update ( th - > tz , THERMAL_EVENT_UNSPECIFIED ) ;
2012-11-15 14:56:42 +04:00
return IRQ_HANDLED ;
}
static int db8500_thermal_probe ( struct platform_device * pdev )
{
2019-08-28 16:03:20 +03:00
struct db8500_thermal_zone * th = NULL ;
2019-08-28 16:03:19 +03:00
struct device * dev = & pdev - > dev ;
2012-11-15 14:56:42 +04:00
int low_irq , high_irq , ret = 0 ;
2019-08-28 16:03:20 +03:00
th = devm_kzalloc ( dev , sizeof ( * th ) , GFP_KERNEL ) ;
if ( ! th )
2012-11-15 14:56:42 +04:00
return - ENOMEM ;
low_irq = platform_get_irq_byname ( pdev , " IRQ_HOTMON_LOW " ) ;
if ( low_irq < 0 ) {
2019-08-28 16:03:20 +03:00
dev_err ( dev , " Get IRQ_HOTMON_LOW failed \n " ) ;
return low_irq ;
2012-11-15 14:56:42 +04:00
}
2019-08-28 16:03:19 +03:00
ret = devm_request_threaded_irq ( dev , low_irq , NULL ,
2012-11-15 14:56:42 +04:00
prcmu_low_irq_handler , IRQF_NO_SUSPEND | IRQF_ONESHOT ,
2019-08-28 16:03:20 +03:00
" dbx500_temp_low " , th ) ;
2012-11-15 14:56:42 +04:00
if ( ret < 0 ) {
2019-08-28 16:03:20 +03:00
dev_err ( dev , " failed to allocate temp low irq \n " ) ;
return ret ;
2012-11-15 14:56:42 +04:00
}
high_irq = platform_get_irq_byname ( pdev , " IRQ_HOTMON_HIGH " ) ;
if ( high_irq < 0 ) {
2019-08-28 16:03:20 +03:00
dev_err ( dev , " Get IRQ_HOTMON_HIGH failed \n " ) ;
return high_irq ;
2012-11-15 14:56:42 +04:00
}
2019-08-28 16:03:19 +03:00
ret = devm_request_threaded_irq ( dev , high_irq , NULL ,
2012-11-15 14:56:42 +04:00
prcmu_high_irq_handler , IRQF_NO_SUSPEND | IRQF_ONESHOT ,
2019-08-28 16:03:20 +03:00
" dbx500_temp_high " , th ) ;
2012-11-15 14:56:42 +04:00
if ( ret < 0 ) {
2019-08-28 16:03:20 +03:00
dev_err ( dev , " failed to allocate temp high irq \n " ) ;
return ret ;
2012-11-15 14:56:42 +04:00
}
2019-08-28 16:03:20 +03:00
/* register of thermal sensor and get info from DT */
th - > tz = devm_thermal_zone_of_sensor_register ( dev , 0 , th , & thdev_ops ) ;
if ( IS_ERR ( th - > tz ) ) {
dev_err ( dev , " register thermal zone sensor failed \n " ) ;
return PTR_ERR ( th - > tz ) ;
2012-11-15 14:56:42 +04:00
}
2019-08-28 16:03:20 +03:00
dev_info ( dev , " thermal zone sensor registered \n " ) ;
2012-11-15 14:56:42 +04:00
2019-08-28 16:03:20 +03:00
/* Start measuring at the lowest point */
db8500_thermal_update_config ( th , 0 , THERMAL_TREND_STABLE ,
PRCMU_DEFAULT_LOW_TEMP ,
db8500_thermal_points [ 0 ] ) ;
2012-11-15 14:56:42 +04:00
2019-08-28 16:03:20 +03:00
platform_set_drvdata ( pdev , th ) ;
2012-11-15 14:56:42 +04:00
return 0 ;
}
static int db8500_thermal_suspend ( struct platform_device * pdev ,
pm_message_t state )
{
prcmu_stop_temp_sense ( ) ;
return 0 ;
}
static int db8500_thermal_resume ( struct platform_device * pdev )
{
2019-08-28 16:03:20 +03:00
struct db8500_thermal_zone * th = platform_get_drvdata ( pdev ) ;
2012-11-15 14:56:42 +04:00
2019-08-28 16:03:20 +03:00
/* Resume and start measuring at the lowest point */
db8500_thermal_update_config ( th , 0 , THERMAL_TREND_STABLE ,
PRCMU_DEFAULT_LOW_TEMP ,
db8500_thermal_points [ 0 ] ) ;
2012-11-15 14:56:42 +04:00
return 0 ;
}
static const struct of_device_id db8500_thermal_match [ ] = {
{ . compatible = " stericsson,db8500-thermal " } ,
{ } ,
} ;
2016-10-14 17:35:02 +03:00
MODULE_DEVICE_TABLE ( of , db8500_thermal_match ) ;
2012-11-15 14:56:42 +04:00
static struct platform_driver db8500_thermal_driver = {
. driver = {
. name = " db8500-thermal " ,
2012-12-19 12:50:58 +04:00
. of_match_table = of_match_ptr ( db8500_thermal_match ) ,
2012-11-15 14:56:42 +04:00
} ,
. probe = db8500_thermal_probe ,
. suspend = db8500_thermal_suspend ,
. resume = db8500_thermal_resume ,
} ;
module_platform_driver ( db8500_thermal_driver ) ;
MODULE_AUTHOR ( " Hongbo Zhang <hongbo.zhang@stericsson.com> " ) ;
MODULE_DESCRIPTION ( " DB8500 thermal driver " ) ;
MODULE_LICENSE ( " GPL " ) ;