2015-05-20 19:16:37 +08: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"
2017-10-19 19:05:49 +02:00
# define TEMP0_LAG (0x0)
2015-05-20 19:16:37 +08:00
# define TEMP0_TH (0x4)
# define TEMP0_RST_TH (0x8)
# define TEMP0_CFG (0xC)
2017-10-19 19:05:50 +02:00
# define TEMP0_CFG_SS_MSK (0xF000)
# define TEMP0_CFG_HDAK_MSK (0x30)
2015-05-20 19:16:37 +08:00
# define TEMP0_EN (0x10)
# define TEMP0_INT_EN (0x14)
# define TEMP0_INT_CLR (0x18)
# define TEMP0_RST_MSK (0x1C)
# define TEMP0_VALUE (0x28)
2017-10-19 19:05:46 +02:00
# define HISI_TEMP_BASE (-60000)
2015-05-20 19:16:37 +08:00
# define HISI_TEMP_RESET (100000)
2017-10-19 19:05:46 +02:00
# define HISI_TEMP_STEP (784)
2015-05-20 19:16:37 +08:00
# define HISI_MAX_SENSORS 4
2017-10-19 19:05:44 +02:00
# define HISI_DEFAULT_SENSOR 2
2015-05-20 19:16:37 +08:00
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 ;
2017-10-19 19:05:44 +02:00
struct hisi_thermal_sensor sensors ;
int irq ;
2015-05-20 19:16:37 +08:00
bool irq_enabled ;
void __iomem * regs ;
} ;
2017-10-19 19:05:46 +02:00
/*
* The temperature computation on the tsensor is as follow :
* Unit : millidegree Celsius
* Step : 255 / 200 ( 0.7843 )
* Temperature base : - 60 ° C
*
* The register is programmed in temperature steps , every step is 784
* millidegree and begins at - 60 000 m ° C
*
* The temperature from the steps :
*
* Temp = TempBase + ( steps x 784 )
*
* and the steps from the temperature :
*
* steps = ( Temp - TempBase ) / 784
*
*/
static inline int hisi_thermal_step_to_temp ( int step )
2015-05-20 19:16:37 +08:00
{
2017-10-19 19:05:46 +02:00
return HISI_TEMP_BASE + ( step * HISI_TEMP_STEP ) ;
2015-05-20 19:16:37 +08:00
}
2017-10-19 19:05:46 +02:00
static inline long hisi_thermal_temp_to_step ( long temp )
2015-05-20 19:16:37 +08:00
{
2017-10-19 19:05:46 +02:00
return ( temp - HISI_TEMP_BASE ) / HISI_TEMP_STEP ;
2015-05-20 19:16:37 +08:00
}
2017-10-19 19:05:47 +02:00
static inline long hisi_thermal_round_temp ( int temp )
{
return hisi_thermal_step_to_temp (
hisi_thermal_temp_to_step ( temp ) ) ;
}
2017-10-19 19:05:49 +02:00
static inline void hisi_thermal_set_lag ( void __iomem * addr , int value )
{
writel ( value , addr + TEMP0_LAG ) ;
}
static inline void hisi_thermal_alarm_clear ( void __iomem * addr , int value )
{
writel ( value , addr + TEMP0_INT_CLR ) ;
}
static inline void hisi_thermal_alarm_enable ( void __iomem * addr , int value )
{
writel ( value , addr + TEMP0_INT_EN ) ;
}
static inline void hisi_thermal_alarm_set ( void __iomem * addr , int temp )
{
writel ( hisi_thermal_temp_to_step ( temp ) | 0x0FFFFFF00 , addr + TEMP0_TH ) ;
}
static inline void hisi_thermal_reset_set ( void __iomem * addr , int temp )
{
writel ( hisi_thermal_temp_to_step ( temp ) , addr + TEMP0_RST_TH ) ;
}
static inline void hisi_thermal_reset_enable ( void __iomem * addr , int value )
{
writel ( value , addr + TEMP0_RST_MSK ) ;
}
static inline void hisi_thermal_enable ( void __iomem * addr , int value )
{
writel ( value , addr + TEMP0_EN ) ;
}
2017-10-19 19:05:50 +02:00
static inline int hisi_thermal_get_temperature ( void __iomem * addr )
2017-10-19 19:05:49 +02:00
{
2017-10-19 19:05:50 +02:00
return hisi_thermal_step_to_temp ( readl ( addr + TEMP0_VALUE ) ) ;
2017-10-19 19:05:49 +02:00
}
2017-10-19 19:05:50 +02:00
/*
* Temperature configuration register - Sensor selection
*
* Bits [ 19 : 12 ]
*
* 0x0 : local sensor ( default )
* 0x1 : remote sensor 1 ( ACPU cluster 1 )
* 0x2 : remote sensor 2 ( ACPU cluster 0 )
* 0x3 : remote sensor 3 ( G3D )
*/
static inline void hisi_thermal_sensor_select ( void __iomem * addr , int sensor )
2017-10-19 19:05:49 +02:00
{
2017-10-19 19:05:50 +02:00
writel ( ( readl ( addr + TEMP0_CFG ) & ~ TEMP0_CFG_SS_MSK ) |
( sensor < < 12 ) , addr + TEMP0_CFG ) ;
2017-10-19 19:05:49 +02:00
}
2017-10-19 19:05:50 +02:00
/*
* Temperature configuration register - Hdak conversion polling interval
*
* Bits [ 5 : 4 ]
*
* 0x0 : 0.768 ms
* 0x1 : 6.144 ms
* 0x2 : 49.152 ms
* 0x3 : 393.216 ms
*/
2017-10-19 19:05:49 +02:00
static inline void hisi_thermal_hdak_set ( void __iomem * addr , int value )
{
2017-10-19 19:05:50 +02:00
writel ( ( readl ( addr + TEMP0_CFG ) & ~ TEMP0_CFG_HDAK_MSK ) |
( value < < 4 ) , addr + TEMP0_CFG ) ;
2017-10-19 19:05:49 +02:00
}
2015-05-20 19:16:37 +08: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 */
2017-10-19 19:05:49 +02:00
hisi_thermal_alarm_enable ( data - > regs , 0 ) ;
hisi_thermal_alarm_clear ( data - > regs , 1 ) ;
2015-05-20 19:16:37 +08:00
/* disable module firstly */
2017-10-19 19:05:49 +02:00
hisi_thermal_enable ( data - > regs , 0 ) ;
2015-05-20 19:16:37 +08:00
/* select sensor id */
2017-10-19 19:05:49 +02:00
hisi_thermal_sensor_select ( data - > regs , sensor - > id ) ;
2015-05-20 19:16:37 +08:00
/* enable module */
2017-10-19 19:05:49 +02:00
hisi_thermal_enable ( data - > regs , 1 ) ;
2015-05-20 19:16:37 +08:00
usleep_range ( 3000 , 5000 ) ;
2017-10-19 19:05:49 +02:00
val = hisi_thermal_get_temperature ( data - > regs ) ;
2015-05-20 19:16:37 +08:00
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 ) ;
2017-10-19 19:05:44 +02:00
sensor = & data - > sensors ;
2015-05-20 19:16:37 +08:00
/* setting the hdak time */
2017-10-19 19:05:49 +02:00
hisi_thermal_hdak_set ( data - > regs , 0 ) ;
2015-05-20 19:16:37 +08:00
/* disable module firstly */
2017-10-19 19:05:49 +02:00
hisi_thermal_reset_enable ( data - > regs , 0 ) ;
hisi_thermal_enable ( data - > regs , 0 ) ;
2015-05-20 19:16:37 +08:00
/* select sensor id */
2017-10-19 19:05:49 +02:00
hisi_thermal_sensor_select ( data - > regs , sensor - > id ) ;
2015-05-20 19:16:37 +08:00
/* enable for interrupt */
2017-10-19 19:05:49 +02:00
hisi_thermal_alarm_set ( data - > regs , sensor - > thres_temp ) ;
2015-05-20 19:16:37 +08:00
2017-10-19 19:05:49 +02:00
hisi_thermal_reset_set ( data - > regs , HISI_TEMP_RESET ) ;
2015-05-20 19:16:37 +08:00
/* enable module */
2017-10-19 19:05:49 +02:00
hisi_thermal_reset_enable ( data - > regs , 1 ) ;
hisi_thermal_enable ( data - > regs , 1 ) ;
2015-05-20 19:16:37 +08:00
2017-10-19 19:05:49 +02:00
hisi_thermal_alarm_clear ( data - > regs , 0 ) ;
hisi_thermal_alarm_enable ( data - > regs , 1 ) ;
2015-05-20 19:16:37 +08:00
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 */
2017-10-19 19:05:49 +02:00
hisi_thermal_enable ( data - > regs , 0 ) ;
hisi_thermal_alarm_enable ( data - > regs , 0 ) ;
hisi_thermal_reset_enable ( data - > regs , 0 ) ;
2015-05-20 19:16:37 +08:00
mutex_unlock ( & data - > thermal_lock ) ;
}
2015-07-24 08:12:54 +02:00
static int hisi_thermal_get_temp ( void * _sensor , int * temp )
2015-05-20 19:16:37 +08:00
{
struct hisi_thermal_sensor * sensor = _sensor ;
struct hisi_thermal_data * data = sensor - > thermal ;
* temp = hisi_thermal_get_sensor_temp ( data , sensor ) ;
2015-07-24 08:12:54 +02:00
dev_dbg ( & data - > pdev - > dev , " id=%d, irq=%d, temp=%d, thres=%d \n " ,
2015-05-20 19:16:37 +08: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 ;
}
2017-10-19 19:05:44 +02:00
if ( * temp < sensor - > thres_temp ) {
2015-05-20 19:16:37 +08:00
data - > irq_enabled = true ;
hisi_thermal_enable_bind_irq_sensor ( data ) ;
enable_irq ( data - > irq ) ;
}
return 0 ;
}
2017-08-08 17:08:55 +02:00
static const struct thermal_zone_of_device_ops hisi_of_thermal_ops = {
2015-05-20 19:16:37 +08:00
. 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 ;
2017-10-19 19:05:48 +02:00
struct hisi_thermal_sensor * sensor = & data - > sensors ;
2015-05-20 19:16:37 +08:00
dev_crit ( & data - > pdev - > dev , " THERMAL ALARM: T > %d \n " ,
2017-10-19 19:05:47 +02:00
sensor - > thres_temp ) ;
2015-05-20 19:16:37 +08:00
2017-10-19 19:05:44 +02:00
thermal_zone_device_update ( data - > sensors . tzd ,
THERMAL_EVENT_UNSPECIFIED ) ;
2015-05-20 19:16:37 +08: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-09 13:07:13 -08:00
sensor - > tzd = devm_thermal_zone_of_sensor_register ( & pdev - > dev ,
sensor - > id , sensor , & hisi_of_thermal_ops ) ;
2015-05-20 19:16:37 +08:00
if ( IS_ERR ( sensor - > tzd ) ) {
ret = PTR_ERR ( sensor - > tzd ) ;
2016-03-29 19:27:12 +08:00
sensor - > tzd = NULL ;
2015-05-20 19:16:37 +08: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 ) {
2017-10-19 19:05:47 +02:00
sensor - > thres_temp = hisi_thermal_round_temp ( trip [ i ] . temperature ) ;
2015-05-20 19:16:37 +08:00
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 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 ;
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 19:27:13 +08:00
hisi_thermal_enable_bind_irq_sensor ( data ) ;
2017-10-19 19:05:43 +02:00
data - > irq_enabled = true ;
2016-03-29 19:27:13 +08:00
2017-10-19 19:05:44 +02:00
ret = hisi_thermal_register_sensor ( pdev , data ,
& data - > sensors ,
HISI_DEFAULT_SENSOR ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to register thermal sensor: %d \n " ,
ret ) ;
return ret ;
2015-05-20 19:16:37 +08:00
}
2017-10-19 19:05:44 +02:00
hisi_thermal_toggle_sensor ( & data - > sensors , true ) ;
2017-10-19 19:05:45 +02:00
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 ;
}
2017-10-19 19:05:43 +02:00
enable_irq ( data - > irq ) ;
2015-05-20 19:16:37 +08:00
return 0 ;
}
static int hisi_thermal_remove ( struct platform_device * pdev )
{
struct hisi_thermal_data * data = platform_get_drvdata ( pdev ) ;
2017-10-19 19:05:44 +02:00
struct hisi_thermal_sensor * sensor = & data - > sensors ;
2015-05-20 19:16:37 +08:00
2017-10-19 19:05:44 +02:00
hisi_thermal_toggle_sensor ( sensor , false ) ;
2015-05-20 19:16:37 +08:00
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 15:04:46 +05:30
int ret ;
2015-05-20 19:16:37 +08:00
2017-06-06 15:04:46 +05:30
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret )
return ret ;
2015-05-20 19:16:37 +08: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 " ) ;