2020-02-29 06:44:20 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020 NXP .
*
* Author : Anson Huang < Anson . Huang @ nxp . com >
*/
2020-03-20 06:26:30 +03:00
# include <linux/bitfield.h>
2020-02-29 06:44:20 +03:00
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/module.h>
2022-12-02 19:23:53 +03:00
# include <linux/nvmem-consumer.h>
2020-02-29 06:44:20 +03:00
# include <linux/of.h>
# include <linux/platform_device.h>
2022-12-02 19:23:53 +03:00
# include <linux/slab.h>
2020-02-29 06:44:20 +03:00
# include <linux/thermal.h>
2022-07-26 15:23:31 +03:00
# include "thermal_hwmon.h"
2020-02-29 06:44:20 +03:00
# define TER 0x0 /* TMU enable */
2020-03-20 06:26:30 +03:00
# define TPS 0x4
2020-02-29 06:44:20 +03:00
# define TRITSR 0x20 /* TMU immediate temp */
2022-12-02 19:23:53 +03:00
/* TMU calibration data registers */
# define TASR 0x28
# define TASR_BUF_SLOPE_MASK GENMASK(19, 16)
# define TASR_BUF_VREF_MASK GENMASK(4, 0) /* TMU_V1 */
# define TASR_BUF_VERF_SEL_MASK GENMASK(1, 0) /* TMU_V2 */
# define TCALIV(n) (0x30 + ((n) * 4))
# define TCALIV_EN BIT(31)
# define TCALIV_HR_MASK GENMASK(23, 16) /* TMU_V1 */
# define TCALIV_RT_MASK GENMASK(7, 0) /* TMU_V1 */
# define TCALIV_SNSR105C_MASK GENMASK(27, 16) /* TMU_V2 */
# define TCALIV_SNSR25C_MASK GENMASK(11, 0) /* TMU_V2 */
# define TRIM 0x3c
# define TRIM_BJT_CUR_MASK GENMASK(23, 20)
# define TRIM_BGR_MASK GENMASK(31, 28)
# define TRIM_VLSB_MASK GENMASK(15, 12)
# define TRIM_EN_CH BIT(7)
2020-02-29 06:44:20 +03:00
2021-11-22 14:42:25 +03:00
# define TER_ADC_PD BIT(30)
2020-02-29 06:44:20 +03:00
# define TER_EN BIT(31)
2022-10-14 11:16:20 +03:00
# define TRITSR_TEMP0_VAL_MASK GENMASK(7, 0)
# define TRITSR_TEMP1_VAL_MASK GENMASK(23, 16)
2020-02-29 06:44:20 +03:00
2020-03-20 06:26:30 +03:00
# define PROBE_SEL_ALL GENMASK(31, 30)
2020-02-29 06:44:20 +03:00
2020-03-20 06:26:30 +03:00
# define probe_status_offset(x) (30 + x)
# define SIGN_BIT BIT(7)
# define TEMP_VAL_MASK GENMASK(6, 0)
2022-12-02 19:23:53 +03:00
/* TMU OCOTP calibration data bitfields */
# define ANA0_EN BIT(25)
# define ANA0_BUF_VREF_MASK GENMASK(24, 20)
# define ANA0_BUF_SLOPE_MASK GENMASK(19, 16)
# define ANA0_HR_MASK GENMASK(15, 8)
# define ANA0_RT_MASK GENMASK(7, 0)
# define TRIM2_VLSB_MASK GENMASK(23, 20)
# define TRIM2_BGR_MASK GENMASK(19, 16)
# define TRIM2_BJT_CUR_MASK GENMASK(15, 12)
# define TRIM2_BUF_SLOP_SEL_MASK GENMASK(11, 8)
# define TRIM2_BUF_VERF_SEL_MASK GENMASK(7, 6)
# define TRIM3_TCA25_0_LSB_MASK GENMASK(31, 28)
# define TRIM3_TCA40_0_MASK GENMASK(27, 16)
# define TRIM4_TCA40_1_MASK GENMASK(31, 20)
# define TRIM4_TCA105_0_MASK GENMASK(19, 8)
# define TRIM4_TCA25_0_MSB_MASK GENMASK(7, 0)
# define TRIM5_TCA105_1_MASK GENMASK(23, 12)
# define TRIM5_TCA25_1_MASK GENMASK(11, 0)
2020-03-20 06:26:30 +03:00
# define VER1_TEMP_LOW_LIMIT 10000
# define VER2_TEMP_LOW_LIMIT -40000
# define VER2_TEMP_HIGH_LIMIT 125000
# define TMU_VER1 0x1
# define TMU_VER2 0x2
struct thermal_soc_data {
u32 num_sensors ;
u32 version ;
2023-09-17 11:34:43 +03:00
int ( * get_temp ) ( void * data , int * temp ) ;
2020-03-20 06:26:30 +03:00
} ;
struct tmu_sensor {
struct imx8mm_tmu * priv ;
u32 hw_id ;
2020-02-29 06:44:20 +03:00
struct thermal_zone_device * tzd ;
2020-03-20 06:26:30 +03:00
} ;
struct imx8mm_tmu {
2020-02-29 06:44:20 +03:00
void __iomem * base ;
struct clk * clk ;
2020-03-20 06:26:30 +03:00
const struct thermal_soc_data * socdata ;
2020-05-07 22:25:17 +03:00
struct tmu_sensor sensors [ ] ;
2020-02-29 06:44:20 +03:00
} ;
2020-03-20 06:26:30 +03:00
static int imx8mm_tmu_get_temp ( void * data , int * temp )
2020-02-29 06:44:20 +03:00
{
2020-03-20 06:26:30 +03:00
struct tmu_sensor * sensor = data ;
struct imx8mm_tmu * tmu = sensor - > priv ;
2020-02-29 06:44:20 +03:00
u32 val ;
2020-03-20 06:26:30 +03:00
val = readl_relaxed ( tmu - > base + TRITSR ) & TRITSR_TEMP0_VAL_MASK ;
2022-10-14 10:35:07 +03:00
/*
* Do not validate against the V bit ( bit 31 ) due to errata
* ERR051272 : TMU : Bit 31 of registers TMU_TSCR / TMU_TRITSR / TMU_TRATSR invalid
*/
2020-03-20 06:26:30 +03:00
* temp = val * 1000 ;
2022-10-14 10:35:07 +03:00
if ( * temp < VER1_TEMP_LOW_LIMIT | | * temp > VER2_TEMP_HIGH_LIMIT )
2020-02-29 06:44:20 +03:00
return - EAGAIN ;
2020-03-20 06:26:30 +03:00
return 0 ;
}
static int imx8mp_tmu_get_temp ( void * data , int * temp )
{
struct tmu_sensor * sensor = data ;
struct imx8mm_tmu * tmu = sensor - > priv ;
2020-03-23 17:19:16 +03:00
unsigned long val ;
2020-03-20 06:26:30 +03:00
bool ready ;
2020-03-23 17:19:16 +03:00
val = readl_relaxed ( tmu - > base + TRITSR ) ;
ready = test_bit ( probe_status_offset ( sensor - > hw_id ) , & val ) ;
2020-03-20 06:26:30 +03:00
if ( ! ready )
return - EAGAIN ;
val = sensor - > hw_id ? FIELD_GET ( TRITSR_TEMP1_VAL_MASK , val ) :
FIELD_GET ( TRITSR_TEMP0_VAL_MASK , val ) ;
if ( val & SIGN_BIT ) /* negative */
val = ( ~ ( val & TEMP_VAL_MASK ) + 1 ) ;
2020-02-29 06:44:20 +03:00
* temp = val * 1000 ;
2020-03-20 06:26:30 +03:00
if ( * temp < VER2_TEMP_LOW_LIMIT | | * temp > VER2_TEMP_HIGH_LIMIT )
return - EAGAIN ;
2020-02-29 06:44:20 +03:00
return 0 ;
}
2022-08-05 01:43:32 +03:00
static int tmu_get_temp ( struct thermal_zone_device * tz , int * temp )
2020-03-20 06:26:30 +03:00
{
2023-03-01 23:14:30 +03:00
struct tmu_sensor * sensor = thermal_zone_device_priv ( tz ) ;
2020-03-20 06:26:30 +03:00
struct imx8mm_tmu * tmu = sensor - > priv ;
2022-08-05 01:43:32 +03:00
return tmu - > socdata - > get_temp ( sensor , temp ) ;
2020-03-20 06:26:30 +03:00
}
2022-08-05 01:43:32 +03:00
static const struct thermal_zone_device_ops tmu_tz_ops = {
2020-02-29 06:44:20 +03:00
. get_temp = tmu_get_temp ,
} ;
2020-03-20 06:26:30 +03:00
static void imx8mm_tmu_enable ( struct imx8mm_tmu * tmu , bool enable )
{
u32 val ;
val = readl_relaxed ( tmu - > base + TER ) ;
val = enable ? ( val | TER_EN ) : ( val & ~ TER_EN ) ;
2021-11-22 14:42:25 +03:00
if ( tmu - > socdata - > version = = TMU_VER2 )
val = enable ? ( val & ~ TER_ADC_PD ) : ( val | TER_ADC_PD ) ;
2020-03-20 06:26:30 +03:00
writel_relaxed ( val , tmu - > base + TER ) ;
}
static void imx8mm_tmu_probe_sel_all ( struct imx8mm_tmu * tmu )
{
u32 val ;
val = readl_relaxed ( tmu - > base + TPS ) ;
val | = PROBE_SEL_ALL ;
writel_relaxed ( val , tmu - > base + TPS ) ;
}
2022-12-02 19:23:53 +03:00
static int imx8mm_tmu_probe_set_calib_v1 ( struct platform_device * pdev ,
struct imx8mm_tmu * tmu )
{
struct device * dev = & pdev - > dev ;
u32 ana0 ;
int ret ;
ret = nvmem_cell_read_u32 ( & pdev - > dev , " calib " , & ana0 ) ;
2023-07-08 14:26:46 +03:00
if ( ret )
return dev_err_probe ( dev , ret , " Failed to read OCOTP nvmem cell \n " ) ;
2022-12-02 19:23:53 +03:00
writel ( FIELD_PREP ( TASR_BUF_VREF_MASK ,
FIELD_GET ( ANA0_BUF_VREF_MASK , ana0 ) ) |
FIELD_PREP ( TASR_BUF_SLOPE_MASK ,
FIELD_GET ( ANA0_BUF_SLOPE_MASK , ana0 ) ) ,
tmu - > base + TASR ) ;
writel ( FIELD_PREP ( TCALIV_RT_MASK , FIELD_GET ( ANA0_RT_MASK , ana0 ) ) |
FIELD_PREP ( TCALIV_HR_MASK , FIELD_GET ( ANA0_HR_MASK , ana0 ) ) |
( ( ana0 & ANA0_EN ) ? TCALIV_EN : 0 ) ,
tmu - > base + TCALIV ( 0 ) ) ;
return 0 ;
}
static int imx8mm_tmu_probe_set_calib_v2 ( struct platform_device * pdev ,
struct imx8mm_tmu * tmu )
{
struct device * dev = & pdev - > dev ;
struct nvmem_cell * cell ;
u32 trim [ 4 ] = { 0 } ;
size_t len ;
void * buf ;
cell = nvmem_cell_get ( dev , " calib " ) ;
if ( IS_ERR ( cell ) )
return PTR_ERR ( cell ) ;
buf = nvmem_cell_read ( cell , & len ) ;
nvmem_cell_put ( cell ) ;
if ( IS_ERR ( buf ) )
return PTR_ERR ( buf ) ;
memcpy ( trim , buf , min ( len , sizeof ( trim ) ) ) ;
kfree ( buf ) ;
if ( len ! = 16 ) {
dev_err ( dev ,
" OCOTP nvmem cell length is %zu, must be 16. \n " , len ) ;
return - EINVAL ;
}
/* Blank sample hardware */
if ( ! trim [ 0 ] & & ! trim [ 1 ] & & ! trim [ 2 ] & & ! trim [ 3 ] ) {
/* Use a default 25C binary codes */
writel ( FIELD_PREP ( TCALIV_SNSR25C_MASK , 0x63c ) ,
tmu - > base + TCALIV ( 0 ) ) ;
writel ( FIELD_PREP ( TCALIV_SNSR25C_MASK , 0x63c ) ,
tmu - > base + TCALIV ( 1 ) ) ;
return 0 ;
}
writel ( FIELD_PREP ( TASR_BUF_VERF_SEL_MASK ,
FIELD_GET ( TRIM2_BUF_VERF_SEL_MASK , trim [ 0 ] ) ) |
FIELD_PREP ( TASR_BUF_SLOPE_MASK ,
FIELD_GET ( TRIM2_BUF_SLOP_SEL_MASK , trim [ 0 ] ) ) ,
tmu - > base + TASR ) ;
writel ( FIELD_PREP ( TRIM_BJT_CUR_MASK ,
FIELD_GET ( TRIM2_BJT_CUR_MASK , trim [ 0 ] ) ) |
FIELD_PREP ( TRIM_BGR_MASK , FIELD_GET ( TRIM2_BGR_MASK , trim [ 0 ] ) ) |
FIELD_PREP ( TRIM_VLSB_MASK , FIELD_GET ( TRIM2_VLSB_MASK , trim [ 0 ] ) ) |
TRIM_EN_CH ,
tmu - > base + TRIM ) ;
writel ( FIELD_PREP ( TCALIV_SNSR25C_MASK ,
FIELD_GET ( TRIM3_TCA25_0_LSB_MASK , trim [ 1 ] ) |
( FIELD_GET ( TRIM4_TCA25_0_MSB_MASK , trim [ 2 ] ) < < 4 ) ) |
FIELD_PREP ( TCALIV_SNSR105C_MASK ,
FIELD_GET ( TRIM4_TCA105_0_MASK , trim [ 2 ] ) ) ,
tmu - > base + TCALIV ( 0 ) ) ;
writel ( FIELD_PREP ( TCALIV_SNSR25C_MASK ,
FIELD_GET ( TRIM5_TCA25_1_MASK , trim [ 3 ] ) ) |
FIELD_PREP ( TCALIV_SNSR105C_MASK ,
FIELD_GET ( TRIM5_TCA105_1_MASK , trim [ 3 ] ) ) ,
tmu - > base + TCALIV ( 1 ) ) ;
writel ( FIELD_PREP ( TCALIV_SNSR25C_MASK ,
FIELD_GET ( TRIM3_TCA40_0_MASK , trim [ 1 ] ) ) |
FIELD_PREP ( TCALIV_SNSR105C_MASK ,
FIELD_GET ( TRIM4_TCA40_1_MASK , trim [ 2 ] ) ) ,
tmu - > base + TCALIV ( 2 ) ) ;
return 0 ;
}
static int imx8mm_tmu_probe_set_calib ( struct platform_device * pdev ,
struct imx8mm_tmu * tmu )
{
struct device * dev = & pdev - > dev ;
/*
* Lack of calibration data OCOTP reference is not considered
* fatal to retain compatibility with old DTs . It is however
* strongly recommended to update such old DTs to get correct
* temperature compensation values for each SoC .
*/
2023-03-10 17:47:26 +03:00
if ( ! of_property_present ( pdev - > dev . of_node , " nvmem-cells " ) ) {
2022-12-02 19:23:53 +03:00
dev_warn ( dev ,
" No OCOTP nvmem reference found, SoC-specific calibration not loaded. Please update your DT. \n " ) ;
return 0 ;
}
if ( tmu - > socdata - > version = = TMU_VER1 )
return imx8mm_tmu_probe_set_calib_v1 ( pdev , tmu ) ;
return imx8mm_tmu_probe_set_calib_v2 ( pdev , tmu ) ;
}
2020-02-29 06:44:20 +03:00
static int imx8mm_tmu_probe ( struct platform_device * pdev )
{
2020-03-20 06:26:30 +03:00
const struct thermal_soc_data * data ;
2020-02-29 06:44:20 +03:00
struct imx8mm_tmu * tmu ;
int ret ;
2020-03-20 06:26:30 +03:00
int i ;
data = of_device_get_match_data ( & pdev - > dev ) ;
2020-02-29 06:44:20 +03:00
2020-03-20 06:26:30 +03:00
tmu = devm_kzalloc ( & pdev - > dev , struct_size ( tmu , sensors ,
data - > num_sensors ) , GFP_KERNEL ) ;
2020-02-29 06:44:20 +03:00
if ( ! tmu )
return - ENOMEM ;
2020-03-20 06:26:30 +03:00
tmu - > socdata = data ;
2020-02-29 06:44:20 +03:00
tmu - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( tmu - > base ) )
return PTR_ERR ( tmu - > base ) ;
tmu - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
2020-08-11 09:59:45 +03:00
if ( IS_ERR ( tmu - > clk ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( tmu - > clk ) ,
" failed to get tmu clock \n " ) ;
2020-02-29 06:44:20 +03:00
ret = clk_prepare_enable ( tmu - > clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to enable tmu clock: %d \n " , ret ) ;
return ret ;
}
2020-03-20 06:26:30 +03:00
/* disable the monitor during initialization */
imx8mm_tmu_enable ( tmu , false ) ;
for ( i = 0 ; i < data - > num_sensors ; i + + ) {
tmu - > sensors [ i ] . priv = tmu ;
tmu - > sensors [ i ] . tzd =
2022-08-05 01:43:32 +03:00
devm_thermal_of_zone_register ( & pdev - > dev , i ,
& tmu - > sensors [ i ] ,
& tmu_tz_ops ) ;
2020-03-20 06:26:30 +03:00
if ( IS_ERR ( tmu - > sensors [ i ] . tzd ) ) {
2020-12-03 02:24:47 +03:00
ret = PTR_ERR ( tmu - > sensors [ i ] . tzd ) ;
2020-03-20 06:26:30 +03:00
dev_err ( & pdev - > dev ,
" failed to register thermal zone sensor[%d]: %d \n " ,
i , ret ) ;
2020-12-03 02:24:48 +03:00
goto disable_clk ;
2020-03-20 06:26:30 +03:00
}
tmu - > sensors [ i ] . hw_id = i ;
2022-07-26 15:23:31 +03:00
2023-06-20 12:07:25 +03:00
devm_thermal_add_hwmon_sysfs ( & pdev - > dev , tmu - > sensors [ i ] . tzd ) ;
2020-02-29 06:44:20 +03:00
}
platform_set_drvdata ( pdev , tmu ) ;
2022-12-02 19:23:53 +03:00
ret = imx8mm_tmu_probe_set_calib ( pdev , tmu ) ;
if ( ret )
goto disable_clk ;
2020-03-20 06:26:30 +03:00
/* enable all the probes for V2 TMU */
if ( tmu - > socdata - > version = = TMU_VER2 )
imx8mm_tmu_probe_sel_all ( tmu ) ;
2020-02-29 06:44:20 +03:00
/* enable the monitor */
2020-03-20 06:26:30 +03:00
imx8mm_tmu_enable ( tmu , true ) ;
2020-02-29 06:44:20 +03:00
return 0 ;
2020-12-03 02:24:48 +03:00
disable_clk :
clk_disable_unprepare ( tmu - > clk ) ;
return ret ;
2020-02-29 06:44:20 +03:00
}
2023-09-27 22:37:13 +03:00
static void imx8mm_tmu_remove ( struct platform_device * pdev )
2020-02-29 06:44:20 +03:00
{
struct imx8mm_tmu * tmu = platform_get_drvdata ( pdev ) ;
/* disable TMU */
2020-03-20 06:26:30 +03:00
imx8mm_tmu_enable ( tmu , false ) ;
2020-02-29 06:44:20 +03:00
clk_disable_unprepare ( tmu - > clk ) ;
platform_set_drvdata ( pdev , NULL ) ;
}
2020-03-20 06:26:30 +03:00
static struct thermal_soc_data imx8mm_tmu_data = {
. num_sensors = 1 ,
. version = TMU_VER1 ,
. get_temp = imx8mm_tmu_get_temp ,
} ;
static struct thermal_soc_data imx8mp_tmu_data = {
. num_sensors = 2 ,
. version = TMU_VER2 ,
. get_temp = imx8mp_tmu_get_temp ,
} ;
2020-02-29 06:44:20 +03:00
static const struct of_device_id imx8mm_tmu_table [ ] = {
2020-03-20 06:26:30 +03:00
{ . compatible = " fsl,imx8mm-tmu " , . data = & imx8mm_tmu_data , } ,
{ . compatible = " fsl,imx8mp-tmu " , . data = & imx8mp_tmu_data , } ,
2020-02-29 06:44:20 +03:00
{ } ,
} ;
2020-06-17 10:47:54 +03:00
MODULE_DEVICE_TABLE ( of , imx8mm_tmu_table ) ;
2020-02-29 06:44:20 +03:00
static struct platform_driver imx8mm_tmu = {
. driver = {
. name = " i.mx8mm_thermal " ,
. of_match_table = imx8mm_tmu_table ,
} ,
. probe = imx8mm_tmu_probe ,
2023-09-27 22:37:13 +03:00
. remove_new = imx8mm_tmu_remove ,
2020-02-29 06:44:20 +03:00
} ;
module_platform_driver ( imx8mm_tmu ) ;
MODULE_AUTHOR ( " Anson Huang <Anson.Huang@nxp.com> " ) ;
MODULE_DESCRIPTION ( " i.MX8MM Thermal Monitor Unit driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;