2018-07-28 22:17:51 +03:00
// SPDX-License-Identifier: GPL-2.0
//
// Copyright 2016 Freescale Semiconductor, Inc.
2016-06-30 06:08:38 +03:00
2019-07-30 05:21:22 +03:00
# include <linux/clk.h>
2016-06-30 06:08:38 +03:00
# include <linux/err.h>
# include <linux/io.h>
2020-03-11 08:07:32 +03:00
# include <linux/module.h>
2016-06-30 06:08:38 +03:00
# include <linux/of.h>
2020-03-11 08:07:32 +03:00
# include <linux/platform_device.h>
2019-12-10 19:41:49 +03:00
# include <linux/regmap.h>
# include <linux/sizes.h>
2016-06-30 06:08:38 +03:00
# include <linux/thermal.h>
2020-05-26 09:02:12 +03:00
# include <linux/units.h>
2016-06-30 06:08:38 +03:00
# include "thermal_core.h"
2019-12-10 19:41:53 +03:00
# include "thermal_hwmon.h"
2016-06-30 06:08:38 +03:00
2019-10-11 05:05:34 +03:00
# define SITES_MAX 16
# define TMR_DISABLE 0x0
# define TMR_ME 0x80000000
# define TMR_ALPF 0x0c000000
# define TMR_ALPF_V2 0x03000000
# define TMTMIR_DEFAULT 0x0000000f
# define TIER_DISABLE 0x0
# define TEUMR0_V2 0x51009c00
2020-05-26 09:02:12 +03:00
# define TMSARA_V2 0xe
2019-10-11 05:05:34 +03:00
# define TMU_VER1 0x1
# define TMU_VER2 0x2
2016-06-30 06:08:38 +03:00
2019-12-10 19:41:49 +03:00
# define REGS_TMR 0x000 /* Mode Register */
# define TMR_DISABLE 0x0
# define TMR_ME 0x80000000
# define TMR_ALPF 0x0c000000
2019-12-10 19:41:50 +03:00
# define TMR_MSITE_ALL GENMASK(15, 0)
2016-06-30 06:08:38 +03:00
2019-12-10 19:41:49 +03:00
# define REGS_TMTMIR 0x008 /* Temperature measurement interval Register */
# define TMTMIR_DEFAULT 0x0000000f
2019-10-11 05:05:34 +03:00
2019-12-10 19:41:49 +03:00
# define REGS_V2_TMSR 0x008 /* monitor site register */
# define REGS_V2_TMTMIR 0x00c /* Temperature measurement interval Register */
# define REGS_TIER 0x020 /* Interrupt Enable Register */
# define TIER_DISABLE 0x0
# define REGS_TTCFGR 0x080 /* Temperature Configuration Register */
# define REGS_TSCFGR 0x084 /* Sensor Configuration Register */
# define REGS_TRITSR(n) (0x100 + 16 * (n)) / * Immediate Temperature
* Site Register
*/
2019-12-10 19:41:51 +03:00
# define TRITSR_V BIT(31)
2020-05-26 09:02:12 +03:00
# define REGS_V2_TMSAR(n) (0x304 + 16 * (n)) / * TMU monitoring
* site adjustment register
*/
2019-12-10 19:41:49 +03:00
# define REGS_TTRnCR(n) (0xf10 + 4 * (n)) / * Temperature Range n
* Control Register
*/
# define REGS_IPBRR(n) (0xbf8 + 4 * (n)) / * IP Block Revision
* Register n
*/
# define REGS_V2_TEUMR(n) (0xf00 + 4 * (n))
2016-06-30 06:08:38 +03:00
/*
* Thermal zone data
*/
2019-01-18 08:39:40 +03:00
struct qoriq_sensor {
int id ;
} ;
2016-06-30 06:08:38 +03:00
struct qoriq_tmu_data {
2019-10-11 05:05:34 +03:00
int ver ;
2019-12-10 19:41:49 +03:00
struct regmap * regmap ;
2019-07-30 05:21:22 +03:00
struct clk * clk ;
2019-12-10 19:41:45 +03:00
struct qoriq_sensor sensor [ SITES_MAX ] ;
2016-06-30 06:08:38 +03:00
} ;
2019-12-10 19:41:45 +03:00
static struct qoriq_tmu_data * qoriq_sensor_to_data ( struct qoriq_sensor * s )
{
return container_of ( s , struct qoriq_tmu_data , sensor [ s - > id ] ) ;
}
2022-08-05 01:43:35 +03:00
static int tmu_get_temp ( struct thermal_zone_device * tz , int * temp )
2016-06-30 06:08:38 +03:00
{
2022-08-05 01:43:35 +03:00
struct qoriq_sensor * qsensor = tz - > devdata ;
2019-12-10 19:41:45 +03:00
struct qoriq_tmu_data * qdata = qoriq_sensor_to_data ( qsensor ) ;
2016-06-30 06:08:38 +03:00
u32 val ;
2019-12-10 19:41:51 +03:00
/*
* REGS_TRITSR ( id ) has the following layout :
*
2020-05-26 09:02:12 +03:00
* For TMU Rev1 :
2019-12-10 19:41:51 +03:00
* 31 . . . 7 6 5 4 3 2 1 0
* V TEMP
*
* Where V bit signifies if the measurement is ready and is
* within sensor range . TEMP is an 8 bit value representing
2020-05-26 09:02:12 +03:00
* temperature in Celsius .
* For TMU Rev2 :
* 31 . . . 8 7 6 5 4 3 2 1 0
* V TEMP
*
* Where V bit signifies if the measurement is ready and is
* within sensor range . TEMP is an 9 bit value representing
* temperature in KelVin .
2019-12-10 19:41:51 +03:00
*/
if ( regmap_read_poll_timeout ( qdata - > regmap ,
REGS_TRITSR ( qsensor - > id ) ,
val ,
val & TRITSR_V ,
USEC_PER_MSEC ,
10 * USEC_PER_MSEC ) )
return - ENODATA ;
2016-06-30 06:08:38 +03:00
2020-05-26 09:02:12 +03:00
if ( qdata - > ver = = TMU_VER1 )
* temp = ( val & GENMASK ( 7 , 0 ) ) * MILLIDEGREE_PER_DEGREE ;
else
* temp = kelvin_to_millicelsius ( val & GENMASK ( 8 , 0 ) ) ;
2016-06-30 06:08:38 +03:00
return 0 ;
}
2022-08-05 01:43:35 +03:00
static const struct thermal_zone_device_ops tmu_tz_ops = {
2019-01-18 08:39:40 +03:00
. get_temp = tmu_get_temp ,
} ;
2016-06-30 06:08:38 +03:00
2019-12-10 19:41:46 +03:00
static int qoriq_tmu_register_tmu_zone ( struct device * dev ,
struct qoriq_tmu_data * qdata )
2019-01-18 08:39:40 +03:00
{
2019-12-10 19:41:50 +03:00
int id ;
if ( qdata - > ver = = TMU_VER1 ) {
regmap_write ( qdata - > regmap , REGS_TMR ,
TMR_MSITE_ALL | TMR_ME | TMR_ALPF ) ;
} else {
regmap_write ( qdata - > regmap , REGS_V2_TMSR , TMR_MSITE_ALL ) ;
regmap_write ( qdata - > regmap , REGS_TMR , TMR_ME | TMR_ALPF_V2 ) ;
}
2019-01-18 08:39:40 +03:00
for ( id = 0 ; id < SITES_MAX ; id + + ) {
2019-12-10 19:41:43 +03:00
struct thermal_zone_device * tzd ;
2019-12-10 19:41:45 +03:00
struct qoriq_sensor * sensor = & qdata - > sensor [ id ] ;
2019-12-10 19:41:43 +03:00
int ret ;
2019-12-10 19:41:44 +03:00
sensor - > id = id ;
2019-12-10 19:41:43 +03:00
2022-08-05 01:43:35 +03:00
tzd = devm_thermal_of_zone_register ( dev , id ,
sensor ,
& tmu_tz_ops ) ;
2019-12-10 19:41:43 +03:00
ret = PTR_ERR_OR_ZERO ( tzd ) ;
if ( ret ) {
if ( ret = = - ENODEV )
2019-01-18 08:39:40 +03:00
continue ;
2016-06-30 06:08:38 +03:00
2019-12-10 19:41:50 +03:00
regmap_write ( qdata - > regmap , REGS_TMR , TMR_DISABLE ) ;
return ret ;
2019-10-11 05:05:34 +03:00
}
2019-12-10 19:41:53 +03:00
if ( devm_thermal_add_hwmon_sysfs ( tzd ) )
dev_warn ( dev ,
" Failed to add hwmon sysfs attributes \n " ) ;
2019-10-11 05:05:34 +03:00
}
2016-06-30 06:08:38 +03:00
2019-01-18 08:39:40 +03:00
return 0 ;
2016-06-30 06:08:38 +03:00
}
2019-12-10 19:41:47 +03:00
static int qoriq_tmu_calibration ( struct device * dev ,
struct qoriq_tmu_data * data )
2016-06-30 06:08:38 +03:00
{
int i , val , len ;
u32 range [ 4 ] ;
const u32 * calibration ;
2019-12-10 19:41:47 +03:00
struct device_node * np = dev - > of_node ;
2016-06-30 06:08:38 +03:00
2019-10-11 05:05:34 +03:00
len = of_property_count_u32_elems ( np , " fsl,tmu-range " ) ;
if ( len < 0 | | len > 4 ) {
2019-12-10 19:41:47 +03:00
dev_err ( dev , " invalid range data. \n " ) ;
2019-10-11 05:05:34 +03:00
return len ;
}
val = of_property_read_u32_array ( np , " fsl,tmu-range " , range , len ) ;
if ( val ! = 0 ) {
2019-12-10 19:41:47 +03:00
dev_err ( dev , " failed to read range data. \n " ) ;
2019-10-11 05:05:34 +03:00
return val ;
2016-06-30 06:08:38 +03:00
}
/* Init temperature range registers */
2019-10-11 05:05:34 +03:00
for ( i = 0 ; i < len ; i + + )
2019-12-10 19:41:49 +03:00
regmap_write ( data - > regmap , REGS_TTRnCR ( i ) , range [ i ] ) ;
2016-06-30 06:08:38 +03:00
calibration = of_get_property ( np , " fsl,tmu-calibration " , & len ) ;
if ( calibration = = NULL | | len % 8 ) {
2019-12-10 19:41:47 +03:00
dev_err ( dev , " invalid calibration data. \n " ) ;
2016-06-30 06:08:38 +03:00
return - ENODEV ;
}
for ( i = 0 ; i < len ; i + = 8 , calibration + = 2 ) {
val = of_read_number ( calibration , 1 ) ;
2019-12-10 19:41:49 +03:00
regmap_write ( data - > regmap , REGS_TTCFGR , val ) ;
2016-06-30 06:08:38 +03:00
val = of_read_number ( calibration + 1 , 1 ) ;
2019-12-10 19:41:49 +03:00
regmap_write ( data - > regmap , REGS_TSCFGR , val ) ;
2016-06-30 06:08:38 +03:00
}
return 0 ;
}
static void qoriq_tmu_init_device ( struct qoriq_tmu_data * data )
{
2020-05-26 09:02:12 +03:00
int i ;
2016-06-30 06:08:38 +03:00
/* Disable interrupt, using polling instead */
2019-12-10 19:41:49 +03:00
regmap_write ( data - > regmap , REGS_TIER , TIER_DISABLE ) ;
2016-06-30 06:08:38 +03:00
/* Set update_interval */
2019-12-10 19:41:49 +03:00
2019-10-11 05:05:34 +03:00
if ( data - > ver = = TMU_VER1 ) {
2019-12-10 19:41:49 +03:00
regmap_write ( data - > regmap , REGS_TMTMIR , TMTMIR_DEFAULT ) ;
2019-10-11 05:05:34 +03:00
} else {
2019-12-10 19:41:49 +03:00
regmap_write ( data - > regmap , REGS_V2_TMTMIR , TMTMIR_DEFAULT ) ;
regmap_write ( data - > regmap , REGS_V2_TEUMR ( 0 ) , TEUMR0_V2 ) ;
2020-05-26 09:02:12 +03:00
for ( i = 0 ; i < SITES_MAX ; i + + )
regmap_write ( data - > regmap , REGS_V2_TMSAR ( i ) , TMSARA_V2 ) ;
2019-10-11 05:05:34 +03:00
}
2016-06-30 06:08:38 +03:00
/* Disable monitoring */
2019-12-10 19:41:49 +03:00
regmap_write ( data - > regmap , REGS_TMR , TMR_DISABLE ) ;
2016-06-30 06:08:38 +03:00
}
2019-12-10 19:41:49 +03:00
static const struct regmap_range qoriq_yes_ranges [ ] = {
regmap_reg_range ( REGS_TMR , REGS_TSCFGR ) ,
regmap_reg_range ( REGS_TTRnCR ( 0 ) , REGS_TTRnCR ( 3 ) ) ,
regmap_reg_range ( REGS_V2_TEUMR ( 0 ) , REGS_V2_TEUMR ( 2 ) ) ,
2020-05-26 09:02:12 +03:00
regmap_reg_range ( REGS_V2_TMSAR ( 0 ) , REGS_V2_TMSAR ( 15 ) ) ,
2019-12-10 19:41:49 +03:00
regmap_reg_range ( REGS_IPBRR ( 0 ) , REGS_IPBRR ( 1 ) ) ,
/* Read only registers below */
regmap_reg_range ( REGS_TRITSR ( 0 ) , REGS_TRITSR ( 15 ) ) ,
} ;
static const struct regmap_access_table qoriq_wr_table = {
. yes_ranges = qoriq_yes_ranges ,
. n_yes_ranges = ARRAY_SIZE ( qoriq_yes_ranges ) - 1 ,
} ;
static const struct regmap_access_table qoriq_rd_table = {
. yes_ranges = qoriq_yes_ranges ,
. n_yes_ranges = ARRAY_SIZE ( qoriq_yes_ranges ) ,
} ;
2020-03-11 08:07:31 +03:00
static void qoriq_tmu_action ( void * p )
{
struct qoriq_tmu_data * data = p ;
regmap_write ( data - > regmap , REGS_TMR , TMR_DISABLE ) ;
clk_disable_unprepare ( data - > clk ) ;
}
2016-06-30 06:08:38 +03:00
static int qoriq_tmu_probe ( struct platform_device * pdev )
{
int ret ;
2019-10-11 05:05:34 +03:00
u32 ver ;
2016-06-30 06:08:38 +03:00
struct qoriq_tmu_data * data ;
struct device_node * np = pdev - > dev . of_node ;
2019-12-10 19:41:42 +03:00
struct device * dev = & pdev - > dev ;
2019-12-10 19:41:49 +03:00
const bool little_endian = of_property_read_bool ( np , " little-endian " ) ;
const enum regmap_endian format_endian =
little_endian ? REGMAP_ENDIAN_LITTLE : REGMAP_ENDIAN_BIG ;
const struct regmap_config regmap_config = {
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
. rd_table = & qoriq_rd_table ,
. wr_table = & qoriq_wr_table ,
. val_format_endian = format_endian ,
. max_register = SZ_4K ,
} ;
void __iomem * base ;
2016-06-30 06:08:38 +03:00
2019-12-10 19:41:42 +03:00
data = devm_kzalloc ( dev , sizeof ( struct qoriq_tmu_data ) ,
2016-06-30 06:08:38 +03:00
GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2019-12-10 19:41:49 +03:00
base = devm_platform_ioremap_resource ( pdev , 0 ) ;
ret = PTR_ERR_OR_ZERO ( base ) ;
if ( ret ) {
2019-12-10 19:41:42 +03:00
dev_err ( dev , " Failed to get memory region \n " ) ;
2019-12-10 19:41:49 +03:00
return ret ;
}
data - > regmap = devm_regmap_init_mmio ( dev , base , & regmap_config ) ;
ret = PTR_ERR_OR_ZERO ( data - > regmap ) ;
if ( ret ) {
dev_err ( dev , " Failed to init regmap (%d) \n " , ret ) ;
return ret ;
2016-06-30 06:08:38 +03:00
}
2019-12-10 19:41:42 +03:00
data - > clk = devm_clk_get_optional ( dev , NULL ) ;
2019-07-30 05:21:22 +03:00
if ( IS_ERR ( data - > clk ) )
return PTR_ERR ( data - > clk ) ;
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret ) {
2019-12-10 19:41:42 +03:00
dev_err ( dev , " Failed to enable clock \n " ) ;
2019-07-30 05:21:22 +03:00
return ret ;
}
2020-03-11 08:07:31 +03:00
ret = devm_add_action_or_reset ( dev , qoriq_tmu_action , data ) ;
if ( ret )
return ret ;
2019-10-11 05:05:34 +03:00
/* version register offset at: 0xbf8 on both v1 and v2 */
2019-12-10 19:41:49 +03:00
ret = regmap_read ( data - > regmap , REGS_IPBRR ( 0 ) , & ver ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to read IP block version \n " ) ;
return ret ;
}
2019-10-11 05:05:34 +03:00
data - > ver = ( ver > > 8 ) & 0xff ;
2016-06-30 06:08:38 +03:00
qoriq_tmu_init_device ( data ) ; /* TMU initialization */
2019-12-10 19:41:47 +03:00
ret = qoriq_tmu_calibration ( dev , data ) ; /* TMU calibration */
2016-06-30 06:08:38 +03:00
if ( ret < 0 )
2020-03-11 08:07:31 +03:00
return ret ;
2016-06-30 06:08:38 +03:00
2019-12-10 19:41:46 +03:00
ret = qoriq_tmu_register_tmu_zone ( dev , data ) ;
2019-01-18 08:39:40 +03:00
if ( ret < 0 ) {
2019-12-10 19:41:42 +03:00
dev_err ( dev , " Failed to register sensors \n " ) ;
2020-03-11 08:07:31 +03:00
return ret ;
2016-06-30 06:08:38 +03:00
}
2019-12-10 19:41:47 +03:00
platform_set_drvdata ( pdev , data ) ;
2016-06-30 06:08:38 +03:00
return 0 ;
}
2019-07-30 05:21:25 +03:00
static int __maybe_unused qoriq_tmu_suspend ( struct device * dev )
2016-06-30 06:08:38 +03:00
{
struct qoriq_tmu_data * data = dev_get_drvdata ( dev ) ;
2019-12-10 19:41:49 +03:00
int ret ;
2016-06-30 06:08:38 +03:00
2019-12-10 19:41:49 +03:00
ret = regmap_update_bits ( data - > regmap , REGS_TMR , TMR_ME , 0 ) ;
if ( ret )
return ret ;
2016-06-30 06:08:38 +03:00
2019-07-30 05:21:22 +03:00
clk_disable_unprepare ( data - > clk ) ;
2016-06-30 06:08:38 +03:00
return 0 ;
}
2019-07-30 05:21:25 +03:00
static int __maybe_unused qoriq_tmu_resume ( struct device * dev )
2016-06-30 06:08:38 +03:00
{
2019-07-30 05:21:22 +03:00
int ret ;
2016-06-30 06:08:38 +03:00
struct qoriq_tmu_data * data = dev_get_drvdata ( dev ) ;
2019-07-30 05:21:22 +03:00
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret )
return ret ;
2016-06-30 06:08:38 +03:00
/* Enable monitoring */
2019-12-10 19:41:49 +03:00
return regmap_update_bits ( data - > regmap , REGS_TMR , TMR_ME , TMR_ME ) ;
2016-06-30 06:08:38 +03:00
}
static SIMPLE_DEV_PM_OPS ( qoriq_tmu_pm_ops ,
qoriq_tmu_suspend , qoriq_tmu_resume ) ;
static const struct of_device_id qoriq_tmu_match [ ] = {
{ . compatible = " fsl,qoriq-tmu " , } ,
2018-08-30 05:14:46 +03:00
{ . compatible = " fsl,imx8mq-tmu " , } ,
2016-06-30 06:08:38 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , qoriq_tmu_match ) ;
static struct platform_driver qoriq_tmu = {
. driver = {
. name = " qoriq_thermal " ,
. pm = & qoriq_tmu_pm_ops ,
. of_match_table = qoriq_tmu_match ,
} ,
. probe = qoriq_tmu_probe ,
} ;
module_platform_driver ( qoriq_tmu ) ;
MODULE_AUTHOR ( " Jia Hongtao <hongtao.jia@nxp.com> " ) ;
MODULE_DESCRIPTION ( " QorIQ Thermal Monitoring Unit driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;