2015-05-20 14:16:37 +03:00
/*
* Hisilicon thermal sensor driver
*
* Copyright ( c ) 2014 - 2015 Hisilicon Limited .
* Copyright ( c ) 2014 - 2015 Linaro Limited .
*
* Xinwei Kong < kong . kongxinwei @ hisilicon . com >
* Leo Yan < leo . yan @ linaro . org >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/cpufreq.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/io.h>
# include "thermal_core.h"
# define TEMP0_TH (0x4)
# define TEMP0_RST_TH (0x8)
# define TEMP0_CFG (0xC)
# define TEMP0_EN (0x10)
# define TEMP0_INT_EN (0x14)
# define TEMP0_INT_CLR (0x18)
# define TEMP0_RST_MSK (0x1C)
# define TEMP0_VALUE (0x28)
# define HISI_TEMP_BASE (-60)
# define HISI_TEMP_RESET (100000)
# define HISI_MAX_SENSORS 4
struct hisi_thermal_sensor {
struct hisi_thermal_data * thermal ;
struct thermal_zone_device * tzd ;
long sensor_temp ;
uint32_t id ;
uint32_t thres_temp ;
} ;
struct hisi_thermal_data {
struct mutex thermal_lock ; /* protects register data */
struct platform_device * pdev ;
struct clk * clk ;
struct hisi_thermal_sensor sensors [ HISI_MAX_SENSORS ] ;
int irq , irq_bind_sensor ;
bool irq_enabled ;
void __iomem * regs ;
} ;
/* in millicelsius */
static inline int _step_to_temp ( int step )
{
/*
* Every step equals ( 1 * 200 ) / 255 celsius , and finally
* need convert to millicelsius .
*/
2016-03-29 14:23:32 +03:00
return ( HISI_TEMP_BASE * 1000 + ( step * 200000 / 255 ) ) ;
2015-05-20 14:16:37 +03:00
}
static inline long _temp_to_step ( long temp )
{
2016-03-29 14:23:32 +03:00
return ( ( temp - HISI_TEMP_BASE * 1000 ) * 255 ) / 200000 ;
2015-05-20 14:16:37 +03:00
}
static long hisi_thermal_get_sensor_temp ( struct hisi_thermal_data * data ,
struct hisi_thermal_sensor * sensor )
{
long val ;
mutex_lock ( & data - > thermal_lock ) ;
/* disable interrupt */
writel ( 0x0 , data - > regs + TEMP0_INT_EN ) ;
writel ( 0x1 , data - > regs + TEMP0_INT_CLR ) ;
/* disable module firstly */
writel ( 0x0 , data - > regs + TEMP0_EN ) ;
/* select sensor id */
writel ( ( sensor - > id < < 12 ) , data - > regs + TEMP0_CFG ) ;
/* enable module */
writel ( 0x1 , data - > regs + TEMP0_EN ) ;
usleep_range ( 3000 , 5000 ) ;
val = readl ( data - > regs + TEMP0_VALUE ) ;
val = _step_to_temp ( val ) ;
mutex_unlock ( & data - > thermal_lock ) ;
return val ;
}
static void hisi_thermal_enable_bind_irq_sensor
( struct hisi_thermal_data * data )
{
struct hisi_thermal_sensor * sensor ;
mutex_lock ( & data - > thermal_lock ) ;
sensor = & data - > sensors [ data - > irq_bind_sensor ] ;
/* setting the hdak time */
writel ( 0x0 , data - > regs + TEMP0_CFG ) ;
/* disable module firstly */
writel ( 0x0 , data - > regs + TEMP0_RST_MSK ) ;
writel ( 0x0 , data - > regs + TEMP0_EN ) ;
/* select sensor id */
writel ( ( sensor - > id < < 12 ) , data - > regs + TEMP0_CFG ) ;
/* enable for interrupt */
writel ( _temp_to_step ( sensor - > thres_temp ) | 0x0FFFFFF00 ,
data - > regs + TEMP0_TH ) ;
writel ( _temp_to_step ( HISI_TEMP_RESET ) , data - > regs + TEMP0_RST_TH ) ;
/* enable module */
writel ( 0x1 , data - > regs + TEMP0_RST_MSK ) ;
writel ( 0x1 , data - > regs + TEMP0_EN ) ;
writel ( 0x0 , data - > regs + TEMP0_INT_CLR ) ;
writel ( 0x1 , data - > regs + TEMP0_INT_EN ) ;
usleep_range ( 3000 , 5000 ) ;
mutex_unlock ( & data - > thermal_lock ) ;
}
static void hisi_thermal_disable_sensor ( struct hisi_thermal_data * data )
{
mutex_lock ( & data - > thermal_lock ) ;
/* disable sensor module */
writel ( 0x0 , data - > regs + TEMP0_INT_EN ) ;
writel ( 0x0 , data - > regs + TEMP0_RST_MSK ) ;
writel ( 0x0 , data - > regs + TEMP0_EN ) ;
mutex_unlock ( & data - > thermal_lock ) ;
}
2015-07-24 09:12:54 +03:00
static int hisi_thermal_get_temp ( void * _sensor , int * temp )
2015-05-20 14:16:37 +03:00
{
struct hisi_thermal_sensor * sensor = _sensor ;
struct hisi_thermal_data * data = sensor - > thermal ;
2016-03-29 14:27:12 +03:00
int sensor_id = - 1 , i ;
2015-05-20 14:16:37 +03:00
long max_temp = 0 ;
* temp = hisi_thermal_get_sensor_temp ( data , sensor ) ;
sensor - > sensor_temp = * temp ;
for ( i = 0 ; i < HISI_MAX_SENSORS ; i + + ) {
2016-03-29 14:27:12 +03:00
if ( ! data - > sensors [ i ] . tzd )
continue ;
2015-05-20 14:16:37 +03:00
if ( data - > sensors [ i ] . sensor_temp > = max_temp ) {
max_temp = data - > sensors [ i ] . sensor_temp ;
sensor_id = i ;
}
}
2016-03-29 14:27:12 +03:00
/* If no sensor has been enabled, then skip to enable irq */
if ( sensor_id = = - 1 )
return 0 ;
2015-05-20 14:16:37 +03:00
mutex_lock ( & data - > thermal_lock ) ;
data - > irq_bind_sensor = sensor_id ;
mutex_unlock ( & data - > thermal_lock ) ;
2015-07-24 09:12:54 +03:00
dev_dbg ( & data - > pdev - > dev , " id=%d, irq=%d, temp=%d, thres=%d \n " ,
2015-05-20 14:16:37 +03:00
sensor - > id , data - > irq_enabled , * temp , sensor - > thres_temp ) ;
/*
* Bind irq to sensor for two cases :
* Reenable alarm IRQ if temperature below threshold ;
* if irq has been enabled , always set it ;
*/
if ( data - > irq_enabled ) {
hisi_thermal_enable_bind_irq_sensor ( data ) ;
return 0 ;
}
if ( max_temp < sensor - > thres_temp ) {
data - > irq_enabled = true ;
hisi_thermal_enable_bind_irq_sensor ( data ) ;
enable_irq ( data - > irq ) ;
}
return 0 ;
}
static struct thermal_zone_of_device_ops hisi_of_thermal_ops = {
. get_temp = hisi_thermal_get_temp ,
} ;
static irqreturn_t hisi_thermal_alarm_irq ( int irq , void * dev )
{
struct hisi_thermal_data * data = dev ;
disable_irq_nosync ( irq ) ;
data - > irq_enabled = false ;
return IRQ_WAKE_THREAD ;
}
static irqreturn_t hisi_thermal_alarm_irq_thread ( int irq , void * dev )
{
struct hisi_thermal_data * data = dev ;
struct hisi_thermal_sensor * sensor ;
int i ;
mutex_lock ( & data - > thermal_lock ) ;
sensor = & data - > sensors [ data - > irq_bind_sensor ] ;
dev_crit ( & data - > pdev - > dev , " THERMAL ALARM: T > %d \n " ,
sensor - > thres_temp / 1000 ) ;
mutex_unlock ( & data - > thermal_lock ) ;
2016-03-29 14:27:12 +03:00
for ( i = 0 ; i < HISI_MAX_SENSORS ; i + + ) {
if ( ! data - > sensors [ i ] . tzd )
continue ;
2016-08-27 02:21:16 +03:00
thermal_zone_device_update ( data - > sensors [ i ] . tzd ,
THERMAL_EVENT_UNSPECIFIED ) ;
2016-03-29 14:27:12 +03:00
}
2015-05-20 14:16:37 +03:00
return IRQ_HANDLED ;
}
static int hisi_thermal_register_sensor ( struct platform_device * pdev ,
struct hisi_thermal_data * data ,
struct hisi_thermal_sensor * sensor ,
int index )
{
int ret , i ;
const struct thermal_trip * trip ;
sensor - > id = index ;
sensor - > thermal = data ;
2016-03-10 00:07:13 +03:00
sensor - > tzd = devm_thermal_zone_of_sensor_register ( & pdev - > dev ,
sensor - > id , sensor , & hisi_of_thermal_ops ) ;
2015-05-20 14:16:37 +03:00
if ( IS_ERR ( sensor - > tzd ) ) {
ret = PTR_ERR ( sensor - > tzd ) ;
2016-03-29 14:27:12 +03:00
sensor - > tzd = NULL ;
2015-05-20 14:16:37 +03:00
dev_err ( & pdev - > dev , " failed to register sensor id %d: %d \n " ,
sensor - > id , ret ) ;
return ret ;
}
trip = of_thermal_get_trip_points ( sensor - > tzd ) ;
for ( i = 0 ; i < of_thermal_get_ntrips ( sensor - > tzd ) ; i + + ) {
if ( trip [ i ] . type = = THERMAL_TRIP_PASSIVE ) {
sensor - > thres_temp = trip [ i ] . temperature ;
break ;
}
}
return 0 ;
}
static const struct of_device_id of_hisi_thermal_match [ ] = {
{ . compatible = " hisilicon,tsensor " } ,
{ /* end */ }
} ;
MODULE_DEVICE_TABLE ( of , of_hisi_thermal_match ) ;
static void hisi_thermal_toggle_sensor ( struct hisi_thermal_sensor * sensor ,
bool on )
{
struct thermal_zone_device * tzd = sensor - > tzd ;
tzd - > ops - > set_mode ( tzd ,
on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED ) ;
}
static int hisi_thermal_probe ( struct platform_device * pdev )
{
struct hisi_thermal_data * data ;
struct resource * res ;
int i ;
int ret ;
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
mutex_init ( & data - > thermal_lock ) ;
data - > pdev = pdev ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
data - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( data - > regs ) ) {
dev_err ( & pdev - > dev , " failed to get io address \n " ) ;
return PTR_ERR ( data - > regs ) ;
}
data - > irq = platform_get_irq ( pdev , 0 ) ;
if ( data - > irq < 0 )
return data - > irq ;
ret = devm_request_threaded_irq ( & pdev - > dev , data - > irq ,
hisi_thermal_alarm_irq ,
hisi_thermal_alarm_irq_thread ,
0 , " hisi_thermal " , data ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to request alarm irq: %d \n " , ret ) ;
return ret ;
}
platform_set_drvdata ( pdev , data ) ;
data - > clk = devm_clk_get ( & pdev - > dev , " thermal_clk " ) ;
if ( IS_ERR ( data - > clk ) ) {
ret = PTR_ERR ( data - > clk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( & pdev - > dev ,
" failed to get thermal clk: %d \n " , ret ) ;
return ret ;
}
/* enable clock for thermal */
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to enable thermal clk: %d \n " , ret ) ;
return ret ;
}
2016-03-29 14:27:13 +03:00
hisi_thermal_enable_bind_irq_sensor ( data ) ;
irq_get_irqchip_state ( data - > irq , IRQCHIP_STATE_MASKED ,
& data - > irq_enabled ) ;
2015-05-20 14:16:37 +03:00
for ( i = 0 ; i < HISI_MAX_SENSORS ; + + i ) {
ret = hisi_thermal_register_sensor ( pdev , data ,
& data - > sensors [ i ] , i ) ;
2016-03-29 14:27:12 +03:00
if ( ret )
2015-05-20 14:16:37 +03:00
dev_err ( & pdev - > dev ,
" failed to register thermal sensor: %d \n " , ret ) ;
2016-03-29 14:27:12 +03:00
else
hisi_thermal_toggle_sensor ( & data - > sensors [ i ] , true ) ;
2015-05-20 14:16:37 +03:00
}
return 0 ;
}
static int hisi_thermal_remove ( struct platform_device * pdev )
{
struct hisi_thermal_data * data = platform_get_drvdata ( pdev ) ;
int i ;
for ( i = 0 ; i < HISI_MAX_SENSORS ; i + + ) {
struct hisi_thermal_sensor * sensor = & data - > sensors [ i ] ;
2016-03-29 14:27:12 +03:00
if ( ! sensor - > tzd )
continue ;
2015-05-20 14:16:37 +03:00
hisi_thermal_toggle_sensor ( sensor , false ) ;
}
hisi_thermal_disable_sensor ( data ) ;
clk_disable_unprepare ( data - > clk ) ;
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int hisi_thermal_suspend ( struct device * dev )
{
struct hisi_thermal_data * data = dev_get_drvdata ( dev ) ;
hisi_thermal_disable_sensor ( data ) ;
data - > irq_enabled = false ;
clk_disable_unprepare ( data - > clk ) ;
return 0 ;
}
static int hisi_thermal_resume ( struct device * dev )
{
struct hisi_thermal_data * data = dev_get_drvdata ( dev ) ;
2017-06-06 12:34:46 +03:00
int ret ;
2015-05-20 14:16:37 +03:00
2017-06-06 12:34:46 +03:00
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret )
return ret ;
2015-05-20 14:16:37 +03:00
data - > irq_enabled = true ;
hisi_thermal_enable_bind_irq_sensor ( data ) ;
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( hisi_thermal_pm_ops ,
hisi_thermal_suspend , hisi_thermal_resume ) ;
static struct platform_driver hisi_thermal_driver = {
. driver = {
. name = " hisi_thermal " ,
. pm = & hisi_thermal_pm_ops ,
. of_match_table = of_hisi_thermal_match ,
} ,
. probe = hisi_thermal_probe ,
. remove = hisi_thermal_remove ,
} ;
module_platform_driver ( hisi_thermal_driver ) ;
MODULE_AUTHOR ( " Xinwei Kong <kong.kongxinwei@hisilicon.com> " ) ;
MODULE_AUTHOR ( " Leo Yan <leo.yan@linaro.org> " ) ;
MODULE_DESCRIPTION ( " Hisilicon thermal driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;