2019-10-04 12:01:09 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Amlogic Thermal Sensor Driver
*
* Copyright ( C ) 2017 Huan Biao < huan . biao @ amlogic . com >
* Copyright ( C ) 2019 Guillaume La Roque < glaroque @ baylibre . com >
*
* Register value to celsius temperature formulas :
* Read_Val m * U
* U = - - - - - - - - - , Uptat = - - - - - - - - -
* 2 ^ 16 1 + n * U
*
* Temperature = A * ( Uptat + u_efuse / 2 ^ 16 ) - B
*
* A B m n : calibration parameters
* u_efuse : fused calibration value , it ' s a signed 16 bits value
*/
# include <linux/bitfield.h>
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/thermal.h>
# include "thermal_core.h"
2020-11-15 22:06:58 +03:00
# include "thermal_hwmon.h"
2019-10-04 12:01:09 +03:00
# define TSENSOR_CFG_REG1 0x4
# define TSENSOR_CFG_REG1_RSET_VBG BIT(12)
# define TSENSOR_CFG_REG1_RSET_ADC BIT(11)
# define TSENSOR_CFG_REG1_VCM_EN BIT(10)
# define TSENSOR_CFG_REG1_VBG_EN BIT(9)
# define TSENSOR_CFG_REG1_OUT_CTL BIT(6)
# define TSENSOR_CFG_REG1_FILTER_EN BIT(5)
# define TSENSOR_CFG_REG1_DEM_EN BIT(3)
# define TSENSOR_CFG_REG1_CH_SEL GENMASK(1, 0)
# define TSENSOR_CFG_REG1_ENABLE \
( TSENSOR_CFG_REG1_FILTER_EN | \
TSENSOR_CFG_REG1_VCM_EN | \
TSENSOR_CFG_REG1_VBG_EN | \
TSENSOR_CFG_REG1_DEM_EN | \
TSENSOR_CFG_REG1_CH_SEL )
# define TSENSOR_STAT0 0x40
# define TSENSOR_STAT9 0x64
# define TSENSOR_READ_TEMP_MASK GENMASK(15, 0)
# define TSENSOR_TEMP_MASK GENMASK(11, 0)
# define TSENSOR_TRIM_SIGN_MASK BIT(15)
# define TSENSOR_TRIM_TEMP_MASK GENMASK(14, 0)
# define TSENSOR_TRIM_VERSION_MASK GENMASK(31, 24)
# define TSENSOR_TRIM_VERSION(_version) \
FIELD_GET ( TSENSOR_TRIM_VERSION_MASK , _version )
# define TSENSOR_TRIM_CALIB_VALID_MASK (GENMASK(3, 2) | BIT(7))
# define TSENSOR_CALIB_OFFSET 1
# define TSENSOR_CALIB_SHIFT 4
/**
* struct amlogic_thermal_soc_calib_data
2019-11-20 18:45:19 +03:00
* @ A : calibration parameters
* @ B : calibration parameters
* @ m : calibration parameters
* @ n : calibration parameters
*
2019-10-04 12:01:09 +03:00
* This structure is required for configuration of amlogic thermal driver .
*/
struct amlogic_thermal_soc_calib_data {
int A ;
int B ;
int m ;
int n ;
} ;
/**
* struct amlogic_thermal_data
* @ u_efuse_off : register offset to read fused calibration value
* @ calibration_parameters : calibration parameters structure pointer
* @ regmap_config : regmap config for the device
* This structure is required for configuration of amlogic thermal driver .
*/
struct amlogic_thermal_data {
int u_efuse_off ;
const struct amlogic_thermal_soc_calib_data * calibration_parameters ;
const struct regmap_config * regmap_config ;
} ;
struct amlogic_thermal {
struct platform_device * pdev ;
const struct amlogic_thermal_data * data ;
struct regmap * regmap ;
struct regmap * sec_ao_map ;
struct clk * clk ;
struct thermal_zone_device * tzd ;
u32 trim_info ;
} ;
/*
* Calculate a temperature value from a temperature code .
* The unit of the temperature is degree milliCelsius .
*/
static int amlogic_thermal_code_to_millicelsius ( struct amlogic_thermal * pdata ,
int temp_code )
{
const struct amlogic_thermal_soc_calib_data * param =
pdata - > data - > calibration_parameters ;
int temp ;
s64 factor , Uptat , uefuse ;
uefuse = pdata - > trim_info & TSENSOR_TRIM_SIGN_MASK ?
~ ( pdata - > trim_info & TSENSOR_TRIM_TEMP_MASK ) + 1 :
( pdata - > trim_info & TSENSOR_TRIM_TEMP_MASK ) ;
factor = param - > n * temp_code ;
factor = div_s64 ( factor , 100 ) ;
Uptat = temp_code * param - > m ;
Uptat = div_s64 ( Uptat , 100 ) ;
Uptat = Uptat * BIT ( 16 ) ;
Uptat = div_s64 ( Uptat , BIT ( 16 ) + factor ) ;
temp = ( Uptat + uefuse ) * param - > A ;
temp = div_s64 ( temp , BIT ( 16 ) ) ;
temp = ( temp - param - > B ) * 100 ;
return temp ;
}
static int amlogic_thermal_initialize ( struct amlogic_thermal * pdata )
{
int ret = 0 ;
int ver ;
regmap_read ( pdata - > sec_ao_map , pdata - > data - > u_efuse_off ,
& pdata - > trim_info ) ;
ver = TSENSOR_TRIM_VERSION ( pdata - > trim_info ) ;
if ( ( ver & TSENSOR_TRIM_CALIB_VALID_MASK ) = = 0 ) {
ret = - EINVAL ;
dev_err ( & pdata - > pdev - > dev ,
" tsensor thermal calibration not supported: 0x%x! \n " ,
ver ) ;
}
return ret ;
}
static int amlogic_thermal_enable ( struct amlogic_thermal * data )
{
int ret ;
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret )
return ret ;
regmap_update_bits ( data - > regmap , TSENSOR_CFG_REG1 ,
TSENSOR_CFG_REG1_ENABLE , TSENSOR_CFG_REG1_ENABLE ) ;
return 0 ;
}
static int amlogic_thermal_disable ( struct amlogic_thermal * data )
{
regmap_update_bits ( data - > regmap , TSENSOR_CFG_REG1 ,
TSENSOR_CFG_REG1_ENABLE , 0 ) ;
clk_disable_unprepare ( data - > clk ) ;
return 0 ;
}
static int amlogic_thermal_get_temp ( void * data , int * temp )
{
unsigned int tval ;
struct amlogic_thermal * pdata = data ;
if ( ! data )
return - EINVAL ;
regmap_read ( pdata - > regmap , TSENSOR_STAT0 , & tval ) ;
* temp =
amlogic_thermal_code_to_millicelsius ( pdata ,
tval & TSENSOR_READ_TEMP_MASK ) ;
return 0 ;
}
static const struct thermal_zone_of_device_ops amlogic_thermal_ops = {
. get_temp = amlogic_thermal_get_temp ,
} ;
static const struct regmap_config amlogic_thermal_regmap_config_g12a = {
. reg_bits = 8 ,
. val_bits = 32 ,
. reg_stride = 4 ,
. max_register = TSENSOR_STAT9 ,
} ;
static const struct amlogic_thermal_soc_calib_data amlogic_thermal_g12a = {
. A = 9411 ,
. B = 3159 ,
. m = 424 ,
. n = 324 ,
} ;
static const struct amlogic_thermal_data amlogic_thermal_g12a_cpu_param = {
. u_efuse_off = 0x128 ,
. calibration_parameters = & amlogic_thermal_g12a ,
. regmap_config = & amlogic_thermal_regmap_config_g12a ,
} ;
static const struct amlogic_thermal_data amlogic_thermal_g12a_ddr_param = {
. u_efuse_off = 0xf0 ,
. calibration_parameters = & amlogic_thermal_g12a ,
. regmap_config = & amlogic_thermal_regmap_config_g12a ,
} ;
static const struct of_device_id of_amlogic_thermal_match [ ] = {
{
. compatible = " amlogic,g12a-ddr-thermal " ,
. data = & amlogic_thermal_g12a_ddr_param ,
} ,
{
. compatible = " amlogic,g12a-cpu-thermal " ,
. data = & amlogic_thermal_g12a_cpu_param ,
} ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , of_amlogic_thermal_match ) ;
static int amlogic_thermal_probe ( struct platform_device * pdev )
{
struct amlogic_thermal * pdata ;
struct device * dev = & pdev - > dev ;
void __iomem * base ;
int ret ;
pdata = devm_kzalloc ( dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
pdata - > data = of_device_get_match_data ( dev ) ;
pdata - > pdev = pdev ;
platform_set_drvdata ( pdev , pdata ) ;
base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2021-02-22 09:11:05 +03:00
if ( IS_ERR ( base ) )
2019-10-04 12:01:09 +03:00
return PTR_ERR ( base ) ;
pdata - > regmap = devm_regmap_init_mmio ( dev , base ,
pdata - > data - > regmap_config ) ;
if ( IS_ERR ( pdata - > regmap ) )
return PTR_ERR ( pdata - > regmap ) ;
pdata - > clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( pdata - > clk ) ) {
if ( PTR_ERR ( pdata - > clk ) ! = - EPROBE_DEFER )
dev_err ( dev , " failed to get clock \n " ) ;
return PTR_ERR ( pdata - > clk ) ;
}
pdata - > sec_ao_map = syscon_regmap_lookup_by_phandle
( pdev - > dev . of_node , " amlogic,ao-secure " ) ;
if ( IS_ERR ( pdata - > sec_ao_map ) ) {
dev_err ( dev , " syscon regmap lookup failed. \n " ) ;
return PTR_ERR ( pdata - > sec_ao_map ) ;
}
pdata - > tzd = devm_thermal_zone_of_sensor_register ( & pdev - > dev ,
0 ,
pdata ,
& amlogic_thermal_ops ) ;
if ( IS_ERR ( pdata - > tzd ) ) {
ret = PTR_ERR ( pdata - > tzd ) ;
dev_err ( dev , " Failed to register tsensor: %d \n " , ret ) ;
return ret ;
}
2020-11-15 22:06:58 +03:00
if ( devm_thermal_add_hwmon_sysfs ( pdata - > tzd ) )
dev_warn ( & pdev - > dev , " Failed to add hwmon sysfs attributes \n " ) ;
2019-10-04 12:01:09 +03:00
ret = amlogic_thermal_initialize ( pdata ) ;
if ( ret )
return ret ;
ret = amlogic_thermal_enable ( pdata ) ;
return ret ;
}
static int amlogic_thermal_remove ( struct platform_device * pdev )
{
struct amlogic_thermal * data = platform_get_drvdata ( pdev ) ;
return amlogic_thermal_disable ( data ) ;
}
static int __maybe_unused amlogic_thermal_suspend ( struct device * dev )
{
struct amlogic_thermal * data = dev_get_drvdata ( dev ) ;
return amlogic_thermal_disable ( data ) ;
}
static int __maybe_unused amlogic_thermal_resume ( struct device * dev )
{
struct amlogic_thermal * data = dev_get_drvdata ( dev ) ;
return amlogic_thermal_enable ( data ) ;
}
static SIMPLE_DEV_PM_OPS ( amlogic_thermal_pm_ops ,
amlogic_thermal_suspend , amlogic_thermal_resume ) ;
static struct platform_driver amlogic_thermal_driver = {
. driver = {
. name = " amlogic_thermal " ,
. pm = & amlogic_thermal_pm_ops ,
. of_match_table = of_amlogic_thermal_match ,
} ,
. probe = amlogic_thermal_probe ,
. remove = amlogic_thermal_remove ,
} ;
module_platform_driver ( amlogic_thermal_driver ) ;
MODULE_AUTHOR ( " Guillaume La Roque <glaroque@baylibre.com> " ) ;
MODULE_DESCRIPTION ( " Amlogic thermal driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;