2019-05-29 17:17:56 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2013-04-02 05:37:41 +04:00
/*
2017-12-22 19:14:10 +03:00
* Marvell EBU Armada SoCs thermal sensor driver
2013-04-02 05:37:41 +04:00
*
* Copyright ( C ) 2013 Marvell
*/
# include <linux/device.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/of.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/platform_device.h>
# include <linux/of_device.h>
# include <linux/thermal.h>
2017-12-22 19:14:12 +03:00
# include <linux/iopoll.h>
2018-07-16 17:41:50 +03:00
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
2018-12-12 12:36:40 +03:00
# include <linux/interrupt.h>
2013-04-02 05:37:41 +04:00
/* Thermal Manager Control and Status Register */
# define PMU_TDC0_SW_RST_MASK (0x1 << 1)
# define PMU_TM_DISABLE_OFFS 0
# define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS)
# define PMU_TDC0_REF_CAL_CNT_OFFS 11
# define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS)
# define PMU_TDC0_OTF_CAL_MASK (0x1 << 30)
# define PMU_TDC0_START_CAL_MASK (0x1 << 25)
2014-05-06 20:59:50 +04:00
# define A375_UNIT_CONTROL_SHIFT 27
# define A375_UNIT_CONTROL_MASK 0x7
# define A375_READOUT_INVERT BIT(15)
# define A375_HW_RESETn BIT(8)
2017-12-22 19:14:11 +03:00
/* Errata fields */
# define CONTROL0_TSEN_TC_TRIM_MASK 0x7
# define CONTROL0_TSEN_TC_TRIM_VAL 0x3
2017-12-22 19:14:08 +03:00
# define CONTROL0_TSEN_START BIT(0)
# define CONTROL0_TSEN_RESET BIT(1)
# define CONTROL0_TSEN_ENABLE BIT(2)
2018-07-16 17:41:49 +03:00
# define CONTROL0_TSEN_AVG_BYPASS BIT(6)
2018-07-16 17:41:52 +03:00
# define CONTROL0_TSEN_CHAN_SHIFT 13
# define CONTROL0_TSEN_CHAN_MASK 0xF
2018-07-16 17:41:49 +03:00
# define CONTROL0_TSEN_OSR_SHIFT 24
# define CONTROL0_TSEN_OSR_MAX 0x3
2018-07-16 17:41:52 +03:00
# define CONTROL0_TSEN_MODE_SHIFT 30
# define CONTROL0_TSEN_MODE_EXTERNAL 0x2
# define CONTROL0_TSEN_MODE_MASK 0x3
2017-12-22 19:14:08 +03:00
2018-07-16 17:41:49 +03:00
# define CONTROL1_TSEN_AVG_MASK 0x7
2017-12-22 19:14:09 +03:00
# define CONTROL1_EXT_TSEN_SW_RESET BIT(7)
# define CONTROL1_EXT_TSEN_HW_RESETn BIT(8)
2018-12-12 12:36:40 +03:00
# define CONTROL1_TSEN_INT_EN BIT(25)
# define CONTROL1_TSEN_SELECT_OFF 21
# define CONTROL1_TSEN_SELECT_MASK 0x3
2017-12-22 19:14:09 +03:00
2017-12-22 19:14:12 +03:00
# define STATUS_POLL_PERIOD_US 1000
# define STATUS_POLL_TIMEOUT_US 100000
2018-12-12 12:36:40 +03:00
# define OVERHEAT_INT_POLL_DELAY_MS 1000
2017-12-22 19:14:12 +03:00
2014-05-06 20:59:45 +04:00
struct armada_thermal_data ;
2013-04-02 05:37:41 +04:00
/* Marvell EBU Thermal Sensor Dev Structure */
struct armada_thermal_priv {
2018-07-16 17:41:51 +03:00
struct device * dev ;
2018-07-16 17:41:50 +03:00
struct regmap * syscon ;
2018-07-16 17:41:44 +03:00
char zone_name [ THERMAL_NAME_LENGTH ] ;
2018-07-16 17:41:52 +03:00
/* serialize temperature reads/updates */
struct mutex update_lock ;
2014-05-06 20:59:45 +04:00
struct armada_thermal_data * data ;
2018-12-12 12:36:40 +03:00
struct thermal_zone_device * overheat_sensor ;
int interrupt_source ;
2018-07-16 17:41:52 +03:00
int current_channel ;
2018-12-12 12:36:40 +03:00
long current_threshold ;
long current_hysteresis ;
2013-04-02 05:37:41 +04:00
} ;
2014-05-06 20:59:45 +04:00
struct armada_thermal_data {
2018-07-16 17:41:47 +03:00
/* Initialize the thermal IC */
void ( * init ) ( struct platform_device * pdev ,
struct armada_thermal_priv * priv ) ;
2013-04-02 05:37:41 +04:00
2017-09-14 18:06:57 +03:00
/* Formula coeficients: temp = (b - m * reg) / div */
2017-12-22 19:14:08 +03:00
s64 coef_b ;
s64 coef_m ;
u32 coef_div ;
2014-05-06 20:59:49 +04:00
bool inverted ;
2017-12-22 19:14:08 +03:00
bool signed_sample ;
2014-05-06 20:59:47 +04:00
/* Register shift and mask to access the sensor temperature */
unsigned int temp_shift ;
unsigned int temp_mask ;
2018-12-12 12:36:40 +03:00
unsigned int thresh_shift ;
unsigned int hyst_shift ;
unsigned int hyst_mask ;
2017-12-22 19:14:05 +03:00
u32 is_valid_bit ;
2018-07-16 17:41:50 +03:00
/* Syscon access */
unsigned int syscon_control0_off ;
unsigned int syscon_control1_off ;
unsigned int syscon_status_off ;
2018-12-12 12:36:40 +03:00
unsigned int dfx_irq_cause_off ;
unsigned int dfx_irq_mask_off ;
unsigned int dfx_overheat_irq ;
unsigned int dfx_server_irq_mask_off ;
unsigned int dfx_server_irq_en ;
2018-07-16 17:41:52 +03:00
/* One sensor is in the thermal IC, the others are in the CPUs if any */
unsigned int cpu_nr ;
2013-04-02 05:37:41 +04:00
} ;
2018-07-16 17:41:51 +03:00
struct armada_drvdata {
enum drvtype {
LEGACY ,
SYSCON
} type ;
union {
struct armada_thermal_priv * priv ;
struct thermal_zone_device * tz ;
} data ;
} ;
/*
* struct armada_thermal_sensor - hold the information of one thermal sensor
* @ thermal : pointer to the local private structure
* @ tzd : pointer to the thermal zone device
2018-07-16 17:41:52 +03:00
* @ id : identifier of the thermal sensor
2018-07-16 17:41:51 +03:00
*/
struct armada_thermal_sensor {
struct armada_thermal_priv * priv ;
2018-07-16 17:41:52 +03:00
int id ;
2018-07-16 17:41:51 +03:00
} ;
2018-07-16 17:41:47 +03:00
static void armadaxp_init ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
2013-04-02 05:37:41 +04:00
{
2018-07-16 17:41:50 +03:00
struct armada_thermal_data * data = priv - > data ;
2017-12-22 19:14:06 +03:00
u32 reg ;
2013-04-02 05:37:41 +04:00
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_control1_off , & reg ) ;
2013-04-02 05:37:41 +04:00
reg | = PMU_TDC0_OTF_CAL_MASK ;
/* Reference calibration value */
reg & = ~ PMU_TDC0_REF_CAL_CNT_MASK ;
reg | = ( 0xf1 < < PMU_TDC0_REF_CAL_CNT_OFFS ) ;
/* Reset the sensor */
2018-07-16 17:41:45 +03:00
reg | = PMU_TDC0_SW_RST_MASK ;
2013-04-02 05:37:41 +04:00
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
2013-04-02 05:37:41 +04:00
2019-12-09 21:57:23 +03:00
reg & = ~ PMU_TDC0_SW_RST_MASK ;
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
2013-04-02 05:37:41 +04:00
/* Enable the sensor */
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_status_off , & reg ) ;
2013-04-02 05:37:41 +04:00
reg & = ~ PMU_TM_DISABLE_MASK ;
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_status_off , reg ) ;
2013-04-02 05:37:41 +04:00
}
2018-07-16 17:41:47 +03:00
static void armada370_init ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
2013-04-02 05:37:41 +04:00
{
2018-07-16 17:41:50 +03:00
struct armada_thermal_data * data = priv - > data ;
2017-12-22 19:14:06 +03:00
u32 reg ;
2013-04-02 05:37:41 +04:00
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_control1_off , & reg ) ;
2013-04-02 05:37:41 +04:00
reg | = PMU_TDC0_OTF_CAL_MASK ;
/* Reference calibration value */
reg & = ~ PMU_TDC0_REF_CAL_CNT_MASK ;
reg | = ( 0xf1 < < PMU_TDC0_REF_CAL_CNT_OFFS ) ;
2018-07-16 17:41:50 +03:00
/* Reset the sensor */
2013-04-02 05:37:41 +04:00
reg & = ~ PMU_TDC0_START_CAL_MASK ;
2018-07-16 17:41:45 +03:00
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
2013-04-02 05:37:41 +04:00
2017-12-22 19:14:04 +03:00
msleep ( 10 ) ;
2013-04-02 05:37:41 +04:00
}
2018-07-16 17:41:47 +03:00
static void armada375_init ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
2014-05-06 20:59:50 +04:00
{
2018-07-16 17:41:50 +03:00
struct armada_thermal_data * data = priv - > data ;
2017-12-22 19:14:06 +03:00
u32 reg ;
2014-05-06 20:59:50 +04:00
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_control1_off , & reg ) ;
2014-05-06 20:59:50 +04:00
reg & = ~ ( A375_UNIT_CONTROL_MASK < < A375_UNIT_CONTROL_SHIFT ) ;
reg & = ~ A375_READOUT_INVERT ;
reg & = ~ A375_HW_RESETn ;
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
2014-05-06 20:59:50 +04:00
2017-12-22 19:14:04 +03:00
msleep ( 20 ) ;
2014-05-06 20:59:50 +04:00
reg | = A375_HW_RESETn ;
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
2017-12-22 19:14:04 +03:00
msleep ( 50 ) ;
2014-05-06 20:59:50 +04:00
}
2018-07-16 17:41:52 +03:00
static int armada_wait_sensor_validity ( struct armada_thermal_priv * priv )
2017-12-22 19:14:12 +03:00
{
u32 reg ;
2018-07-16 17:41:52 +03:00
return regmap_read_poll_timeout ( priv - > syscon ,
priv - > data - > syscon_status_off , reg ,
reg & priv - > data - > is_valid_bit ,
STATUS_POLL_PERIOD_US ,
STATUS_POLL_TIMEOUT_US ) ;
2017-12-22 19:14:12 +03:00
}
2018-07-16 17:41:47 +03:00
static void armada380_init ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
2014-05-06 20:59:51 +04:00
{
2018-07-16 17:41:50 +03:00
struct armada_thermal_data * data = priv - > data ;
u32 reg ;
2014-05-06 20:59:51 +04:00
2017-12-22 19:14:09 +03:00
/* Disable the HW/SW reset */
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_control1_off , & reg ) ;
2017-12-22 19:14:09 +03:00
reg | = CONTROL1_EXT_TSEN_HW_RESETn ;
reg & = ~ CONTROL1_EXT_TSEN_SW_RESET ;
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
2017-12-22 19:14:11 +03:00
/* Set Tsen Tc Trim to correct default value (errata #132698) */
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_control0_off , & reg ) ;
reg & = ~ CONTROL0_TSEN_TC_TRIM_MASK ;
reg | = CONTROL0_TSEN_TC_TRIM_VAL ;
regmap_write ( priv - > syscon , data - > syscon_control0_off , reg ) ;
2014-05-06 20:59:51 +04:00
}
2023-06-16 14:50:42 +03:00
static void armada_ap80x_init ( struct platform_device * pdev ,
2018-07-16 17:41:47 +03:00
struct armada_thermal_priv * priv )
2017-12-22 19:14:08 +03:00
{
2018-07-16 17:41:50 +03:00
struct armada_thermal_data * data = priv - > data ;
2017-12-22 19:14:08 +03:00
u32 reg ;
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_control0_off , & reg ) ;
2017-12-22 19:14:08 +03:00
reg & = ~ CONTROL0_TSEN_RESET ;
reg | = CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE ;
2018-07-16 17:41:49 +03:00
/* Sample every ~2ms */
reg | = CONTROL0_TSEN_OSR_MAX < < CONTROL0_TSEN_OSR_SHIFT ;
/* Enable average (2 samples by default) */
reg & = ~ CONTROL0_TSEN_AVG_BYPASS ;
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_control0_off , reg ) ;
2017-12-22 19:14:08 +03:00
}
2018-07-16 17:41:48 +03:00
static void armada_cp110_init ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
{
2018-07-16 17:41:50 +03:00
struct armada_thermal_data * data = priv - > data ;
2018-07-16 17:41:49 +03:00
u32 reg ;
2018-07-16 17:41:48 +03:00
armada380_init ( pdev , priv ) ;
2018-07-16 17:41:49 +03:00
/* Sample every ~2ms */
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_control0_off , & reg ) ;
2018-07-16 17:41:49 +03:00
reg | = CONTROL0_TSEN_OSR_MAX < < CONTROL0_TSEN_OSR_SHIFT ;
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_control0_off , reg ) ;
2018-07-16 17:41:49 +03:00
/* Average the output value over 2^1 = 2 samples */
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , data - > syscon_control1_off , & reg ) ;
2019-06-13 21:49:23 +03:00
reg & = ~ CONTROL1_TSEN_AVG_MASK ;
reg | = 1 ;
2018-07-16 17:41:50 +03:00
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
2018-07-16 17:41:48 +03:00
}
2013-04-02 05:37:41 +04:00
static bool armada_is_valid ( struct armada_thermal_priv * priv )
{
2018-07-16 17:41:50 +03:00
u32 reg ;
2018-07-16 17:41:55 +03:00
if ( ! priv - > data - > is_valid_bit )
return true ;
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , priv - > data - > syscon_status_off , & reg ) ;
2013-04-02 05:37:41 +04:00
2017-12-22 19:14:05 +03:00
return reg & priv - > data - > is_valid_bit ;
2013-04-02 05:37:41 +04:00
}
2018-12-12 12:36:40 +03:00
static void armada_enable_overheat_interrupt ( struct armada_thermal_priv * priv )
{
struct armada_thermal_data * data = priv - > data ;
u32 reg ;
/* Clear DFX temperature IRQ cause */
regmap_read ( priv - > syscon , data - > dfx_irq_cause_off , & reg ) ;
/* Enable DFX Temperature IRQ */
regmap_read ( priv - > syscon , data - > dfx_irq_mask_off , & reg ) ;
reg | = data - > dfx_overheat_irq ;
regmap_write ( priv - > syscon , data - > dfx_irq_mask_off , reg ) ;
/* Enable DFX server IRQ */
regmap_read ( priv - > syscon , data - > dfx_server_irq_mask_off , & reg ) ;
reg | = data - > dfx_server_irq_en ;
regmap_write ( priv - > syscon , data - > dfx_server_irq_mask_off , reg ) ;
/* Enable overheat interrupt */
regmap_read ( priv - > syscon , data - > syscon_control1_off , & reg ) ;
reg | = CONTROL1_TSEN_INT_EN ;
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
}
static void __maybe_unused
armada_disable_overheat_interrupt ( struct armada_thermal_priv * priv )
{
struct armada_thermal_data * data = priv - > data ;
u32 reg ;
regmap_read ( priv - > syscon , data - > syscon_control1_off , & reg ) ;
reg & = ~ CONTROL1_TSEN_INT_EN ;
regmap_write ( priv - > syscon , data - > syscon_control1_off , reg ) ;
}
2018-07-16 17:41:52 +03:00
/* There is currently no board with more than one sensor per channel */
static int armada_select_channel ( struct armada_thermal_priv * priv , int channel )
{
struct armada_thermal_data * data = priv - > data ;
u32 ctrl0 ;
if ( channel < 0 | | channel > priv - > data - > cpu_nr )
return - EINVAL ;
if ( priv - > current_channel = = channel )
return 0 ;
/* Stop the measurements */
regmap_read ( priv - > syscon , data - > syscon_control0_off , & ctrl0 ) ;
ctrl0 & = ~ CONTROL0_TSEN_START ;
regmap_write ( priv - > syscon , data - > syscon_control0_off , ctrl0 ) ;
/* Reset the mode, internal sensor will be automatically selected */
ctrl0 & = ~ ( CONTROL0_TSEN_MODE_MASK < < CONTROL0_TSEN_MODE_SHIFT ) ;
/* Other channels are external and should be selected accordingly */
if ( channel ) {
/* Change the mode to external */
ctrl0 | = CONTROL0_TSEN_MODE_EXTERNAL < <
CONTROL0_TSEN_MODE_SHIFT ;
/* Select the sensor */
ctrl0 & = ~ ( CONTROL0_TSEN_CHAN_MASK < < CONTROL0_TSEN_CHAN_SHIFT ) ;
ctrl0 | = ( channel - 1 ) < < CONTROL0_TSEN_CHAN_SHIFT ;
}
/* Actually set the mode/channel */
regmap_write ( priv - > syscon , data - > syscon_control0_off , ctrl0 ) ;
priv - > current_channel = channel ;
/* Re-start the measurements */
ctrl0 | = CONTROL0_TSEN_START ;
regmap_write ( priv - > syscon , data - > syscon_control0_off , ctrl0 ) ;
/*
* The IP has a latency of ~ 15 ms , so after updating the selected source ,
* we must absolutely wait for the sensor validity bit to ensure we read
* actual data .
*/
2023-03-01 23:14:34 +03:00
if ( armada_wait_sensor_validity ( priv ) )
2018-07-16 17:41:52 +03:00
return - EIO ;
return 0 ;
}
2018-07-16 17:41:51 +03:00
static int armada_read_sensor ( struct armada_thermal_priv * priv , int * temp )
2013-04-02 05:37:41 +04:00
{
2017-12-22 19:14:08 +03:00
u32 reg , div ;
s64 sample , b , m ;
2013-04-02 05:37:41 +04:00
2018-07-16 17:41:50 +03:00
regmap_read ( priv - > syscon , priv - > data - > syscon_status_off , & reg ) ;
2014-05-06 20:59:47 +04:00
reg = ( reg > > priv - > data - > temp_shift ) & priv - > data - > temp_mask ;
2017-12-22 19:14:08 +03:00
if ( priv - > data - > signed_sample )
/* The most significant bit is the sign bit */
sample = sign_extend32 ( reg , fls ( priv - > data - > temp_mask ) - 1 ) ;
else
sample = reg ;
2014-05-06 20:59:46 +04:00
/* Get formula coeficients */
b = priv - > data - > coef_b ;
m = priv - > data - > coef_m ;
div = priv - > data - > coef_div ;
2014-05-06 20:59:49 +04:00
if ( priv - > data - > inverted )
2017-12-22 19:14:08 +03:00
* temp = div_s64 ( ( m * sample ) - b , div ) ;
2014-05-06 20:59:49 +04:00
else
2017-12-22 19:14:08 +03:00
* temp = div_s64 ( b - ( m * sample ) , div ) ;
2013-04-02 05:37:41 +04:00
return 0 ;
}
2018-07-16 17:41:51 +03:00
static int armada_get_temp_legacy ( struct thermal_zone_device * thermal ,
int * temp )
{
2023-03-01 23:14:30 +03:00
struct armada_thermal_priv * priv = thermal_zone_device_priv ( thermal ) ;
2018-07-16 17:41:51 +03:00
int ret ;
2018-07-16 17:41:54 +03:00
/* Valid check */
2023-03-01 23:14:34 +03:00
if ( ! armada_is_valid ( priv ) )
2018-07-16 17:41:54 +03:00
return - EIO ;
2018-07-16 17:41:51 +03:00
/* Do the actual reading */
ret = armada_read_sensor ( priv , temp ) ;
return ret ;
}
static struct thermal_zone_device_ops legacy_ops = {
. get_temp = armada_get_temp_legacy ,
} ;
2022-08-05 01:43:30 +03:00
static int armada_get_temp ( struct thermal_zone_device * tz , int * temp )
2018-07-16 17:41:51 +03:00
{
2023-03-01 23:14:30 +03:00
struct armada_thermal_sensor * sensor = thermal_zone_device_priv ( tz ) ;
2018-07-16 17:41:51 +03:00
struct armada_thermal_priv * priv = sensor - > priv ;
2018-07-16 17:41:52 +03:00
int ret ;
mutex_lock ( & priv - > update_lock ) ;
/* Select the desired channel */
ret = armada_select_channel ( priv , sensor - > id ) ;
if ( ret )
goto unlock_mutex ;
2018-07-16 17:41:51 +03:00
/* Do the actual reading */
2018-07-16 17:41:52 +03:00
ret = armada_read_sensor ( priv , temp ) ;
2018-12-12 12:36:40 +03:00
if ( ret )
goto unlock_mutex ;
/*
* Select back the interrupt source channel from which a potential
* critical trip point has been set .
*/
ret = armada_select_channel ( priv , priv - > interrupt_source ) ;
2018-07-16 17:41:52 +03:00
unlock_mutex :
mutex_unlock ( & priv - > update_lock ) ;
return ret ;
2018-07-16 17:41:51 +03:00
}
2022-08-05 01:43:30 +03:00
static const struct thermal_zone_device_ops of_ops = {
2013-04-02 05:37:41 +04:00
. get_temp = armada_get_temp ,
} ;
2018-12-12 12:36:40 +03:00
static unsigned int armada_mc_to_reg_temp ( struct armada_thermal_data * data ,
unsigned int temp_mc )
{
s64 b = data - > coef_b ;
s64 m = data - > coef_m ;
s64 div = data - > coef_div ;
unsigned int sample ;
if ( data - > inverted )
sample = div_s64 ( ( ( temp_mc * div ) + b ) , m ) ;
else
sample = div_s64 ( ( b - ( temp_mc * div ) ) , m ) ;
return sample & data - > temp_mask ;
}
/*
* The documentation states :
* high / low watermark = threshold + / - 0.4761 * 2 ^ ( hysteresis + 2 )
* which is the mathematical derivation for :
* 0x0 < = > 1.9 ° C , 0x1 < = > 3.8 ° C , 0x2 < = > 7.6 ° C , 0x3 < = > 15.2 ° C
*/
static unsigned int hyst_levels_mc [ ] = { 1900 , 3800 , 7600 , 15200 } ;
static unsigned int armada_mc_to_reg_hyst ( struct armada_thermal_data * data ,
unsigned int hyst_mc )
{
int i ;
/*
* We will always take the smallest possible hysteresis to avoid risking
* the hardware integrity by enlarging the threshold by + 8 ° C in the
* worst case .
*/
for ( i = ARRAY_SIZE ( hyst_levels_mc ) - 1 ; i > 0 ; i - - )
if ( hyst_mc > = hyst_levels_mc [ i ] )
break ;
return i & data - > hyst_mask ;
}
static void armada_set_overheat_thresholds ( struct armada_thermal_priv * priv ,
int thresh_mc , int hyst_mc )
{
struct armada_thermal_data * data = priv - > data ;
unsigned int threshold = armada_mc_to_reg_temp ( data , thresh_mc ) ;
unsigned int hysteresis = armada_mc_to_reg_hyst ( data , hyst_mc ) ;
u32 ctrl1 ;
regmap_read ( priv - > syscon , data - > syscon_control1_off , & ctrl1 ) ;
/* Set Threshold */
if ( thresh_mc > = 0 ) {
ctrl1 & = ~ ( data - > temp_mask < < data - > thresh_shift ) ;
ctrl1 | = threshold < < data - > thresh_shift ;
priv - > current_threshold = thresh_mc ;
}
/* Set Hysteresis */
if ( hyst_mc > = 0 ) {
ctrl1 & = ~ ( data - > hyst_mask < < data - > hyst_shift ) ;
ctrl1 | = hysteresis < < data - > hyst_shift ;
priv - > current_hysteresis = hyst_mc ;
}
regmap_write ( priv - > syscon , data - > syscon_control1_off , ctrl1 ) ;
}
static irqreturn_t armada_overheat_isr ( int irq , void * blob )
{
/*
* Disable the IRQ and continue in thread context ( thermal core
* notification and temperature monitoring ) .
*/
disable_irq_nosync ( irq ) ;
return IRQ_WAKE_THREAD ;
}
static irqreturn_t armada_overheat_isr_thread ( int irq , void * blob )
{
struct armada_thermal_priv * priv = blob ;
int low_threshold = priv - > current_threshold - priv - > current_hysteresis ;
int temperature ;
u32 dummy ;
int ret ;
/* Notify the core in thread context */
thermal_zone_device_update ( priv - > overheat_sensor ,
THERMAL_EVENT_UNSPECIFIED ) ;
/*
* The overheat interrupt must be cleared by reading the DFX interrupt
* cause _after_ the temperature has fallen down to the low threshold .
* Otherwise future interrupts might not be served .
*/
do {
msleep ( OVERHEAT_INT_POLL_DELAY_MS ) ;
mutex_lock ( & priv - > update_lock ) ;
ret = armada_read_sensor ( priv , & temperature ) ;
mutex_unlock ( & priv - > update_lock ) ;
if ( ret )
goto enable_irq ;
} while ( temperature > = low_threshold ) ;
regmap_read ( priv - > syscon , priv - > data - > dfx_irq_cause_off , & dummy ) ;
/* Notify the thermal core that the temperature is acceptable again */
thermal_zone_device_update ( priv - > overheat_sensor ,
THERMAL_EVENT_UNSPECIFIED ) ;
enable_irq :
enable_irq ( irq ) ;
return IRQ_HANDLED ;
}
2014-05-06 20:59:45 +04:00
static const struct armada_thermal_data armadaxp_data = {
2018-07-16 17:41:47 +03:00
. init = armadaxp_init ,
2014-05-06 20:59:47 +04:00
. temp_shift = 10 ,
. temp_mask = 0x1ff ,
2017-12-22 19:14:08 +03:00
. coef_b = 3153000000ULL ,
. coef_m = 10000000ULL ,
2014-05-06 20:59:46 +04:00
. coef_div = 13825 ,
2018-07-16 17:41:50 +03:00
. syscon_status_off = 0xb0 ,
2019-12-09 21:55:51 +03:00
. syscon_control1_off = 0x2d0 ,
2013-04-02 05:37:41 +04:00
} ;
2014-05-06 20:59:45 +04:00
static const struct armada_thermal_data armada370_data = {
2018-07-16 17:41:47 +03:00
. init = armada370_init ,
2017-12-22 19:14:05 +03:00
. is_valid_bit = BIT ( 9 ) ,
2014-05-06 20:59:47 +04:00
. temp_shift = 10 ,
. temp_mask = 0x1ff ,
2017-12-22 19:14:08 +03:00
. coef_b = 3153000000ULL ,
. coef_m = 10000000ULL ,
2014-05-06 20:59:46 +04:00
. coef_div = 13825 ,
2018-07-16 17:41:50 +03:00
. syscon_status_off = 0x0 ,
. syscon_control1_off = 0x4 ,
2013-04-02 05:37:41 +04:00
} ;
2014-05-06 20:59:50 +04:00
static const struct armada_thermal_data armada375_data = {
2018-07-16 17:41:47 +03:00
. init = armada375_init ,
2017-12-22 19:14:05 +03:00
. is_valid_bit = BIT ( 10 ) ,
2014-05-06 20:59:50 +04:00
. temp_shift = 0 ,
. temp_mask = 0x1ff ,
2017-12-22 19:14:08 +03:00
. coef_b = 3171900000ULL ,
. coef_m = 10000000ULL ,
2014-05-06 20:59:50 +04:00
. coef_div = 13616 ,
2018-07-16 17:41:50 +03:00
. syscon_status_off = 0x78 ,
. syscon_control0_off = 0x7c ,
. syscon_control1_off = 0x80 ,
2014-05-06 20:59:50 +04:00
} ;
2014-05-06 20:59:51 +04:00
static const struct armada_thermal_data armada380_data = {
2018-07-16 17:41:47 +03:00
. init = armada380_init ,
2017-12-22 19:14:05 +03:00
. is_valid_bit = BIT ( 10 ) ,
2014-05-06 20:59:51 +04:00
. temp_shift = 0 ,
. temp_mask = 0x3ff ,
2017-12-22 19:14:08 +03:00
. coef_b = 1172499100ULL ,
. coef_m = 2000096ULL ,
2015-08-06 19:03:49 +03:00
. coef_div = 4201 ,
2014-05-06 20:59:51 +04:00
. inverted = true ,
2018-07-16 17:41:50 +03:00
. syscon_control0_off = 0x70 ,
. syscon_control1_off = 0x74 ,
. syscon_status_off = 0x78 ,
2014-05-06 20:59:51 +04:00
} ;
2017-12-22 19:14:08 +03:00
static const struct armada_thermal_data armada_ap806_data = {
2023-06-16 14:50:42 +03:00
. init = armada_ap80x_init ,
2017-12-22 19:14:08 +03:00
. is_valid_bit = BIT ( 16 ) ,
. temp_shift = 0 ,
. temp_mask = 0x3ff ,
2018-12-12 12:36:40 +03:00
. thresh_shift = 3 ,
. hyst_shift = 19 ,
. hyst_mask = 0x3 ,
2017-12-22 19:14:08 +03:00
. coef_b = - 150000LL ,
. coef_m = 423ULL ,
. coef_div = 1 ,
. inverted = true ,
. signed_sample = true ,
2018-07-16 17:41:50 +03:00
. syscon_control0_off = 0x84 ,
. syscon_control1_off = 0x88 ,
. syscon_status_off = 0x8C ,
2018-12-12 12:36:40 +03:00
. dfx_irq_cause_off = 0x108 ,
. dfx_irq_mask_off = 0x10C ,
. dfx_overheat_irq = BIT ( 22 ) ,
. dfx_server_irq_mask_off = 0x104 ,
. dfx_server_irq_en = BIT ( 1 ) ,
2018-07-16 17:41:52 +03:00
. cpu_nr = 4 ,
2017-12-22 19:14:08 +03:00
} ;
2023-06-16 14:50:42 +03:00
static const struct armada_thermal_data armada_ap807_data = {
. init = armada_ap80x_init ,
. is_valid_bit = BIT ( 16 ) ,
. temp_shift = 0 ,
. temp_mask = 0x3ff ,
. thresh_shift = 3 ,
. hyst_shift = 19 ,
. hyst_mask = 0x3 ,
. coef_b = - 128900LL ,
. coef_m = 394ULL ,
. coef_div = 1 ,
. inverted = true ,
. signed_sample = true ,
. syscon_control0_off = 0x84 ,
. syscon_control1_off = 0x88 ,
. syscon_status_off = 0x8C ,
. dfx_irq_cause_off = 0x108 ,
. dfx_irq_mask_off = 0x10C ,
. dfx_overheat_irq = BIT ( 22 ) ,
. dfx_server_irq_mask_off = 0x104 ,
. dfx_server_irq_en = BIT ( 1 ) ,
. cpu_nr = 4 ,
} ;
2017-12-22 19:14:09 +03:00
static const struct armada_thermal_data armada_cp110_data = {
2018-07-16 17:41:48 +03:00
. init = armada_cp110_init ,
2017-12-22 19:14:09 +03:00
. is_valid_bit = BIT ( 10 ) ,
. temp_shift = 0 ,
. temp_mask = 0x3ff ,
2018-12-12 12:36:40 +03:00
. thresh_shift = 16 ,
. hyst_shift = 26 ,
. hyst_mask = 0x3 ,
2017-12-22 19:14:09 +03:00
. coef_b = 1172499100ULL ,
. coef_m = 2000096ULL ,
. coef_div = 4201 ,
. inverted = true ,
2018-07-16 17:41:50 +03:00
. syscon_control0_off = 0x70 ,
. syscon_control1_off = 0x74 ,
. syscon_status_off = 0x78 ,
2018-12-12 12:36:40 +03:00
. dfx_irq_cause_off = 0x108 ,
. dfx_irq_mask_off = 0x10C ,
. dfx_overheat_irq = BIT ( 20 ) ,
. dfx_server_irq_mask_off = 0x104 ,
. dfx_server_irq_en = BIT ( 1 ) ,
2017-12-22 19:14:09 +03:00
} ;
2013-04-02 05:37:41 +04:00
static const struct of_device_id armada_thermal_id_table [ ] = {
{
. compatible = " marvell,armadaxp-thermal " ,
2014-05-06 20:59:45 +04:00
. data = & armadaxp_data ,
2013-04-02 05:37:41 +04:00
} ,
{
. compatible = " marvell,armada370-thermal " ,
2014-05-06 20:59:45 +04:00
. data = & armada370_data ,
2013-04-02 05:37:41 +04:00
} ,
2014-05-06 20:59:50 +04:00
{
. compatible = " marvell,armada375-thermal " ,
. data = & armada375_data ,
} ,
2014-05-06 20:59:51 +04:00
{
. compatible = " marvell,armada380-thermal " ,
. data = & armada380_data ,
} ,
2017-12-22 19:14:08 +03:00
{
. compatible = " marvell,armada-ap806-thermal " ,
. data = & armada_ap806_data ,
} ,
2023-06-16 14:50:42 +03:00
{
. compatible = " marvell,armada-ap807-thermal " ,
. data = & armada_ap807_data ,
} ,
2017-12-22 19:14:09 +03:00
{
. compatible = " marvell,armada-cp110-thermal " ,
. data = & armada_cp110_data ,
} ,
2013-04-02 05:37:41 +04:00
{
/* sentinel */
} ,
} ;
MODULE_DEVICE_TABLE ( of , armada_thermal_id_table ) ;
2018-07-16 17:41:50 +03:00
static const struct regmap_config armada_thermal_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. fast_io = true ,
} ;
static int armada_thermal_probe_legacy ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
{
struct armada_thermal_data * data = priv - > data ;
void __iomem * base ;
/* First memory region points towards the status register */
2023-01-18 11:34:37 +03:00
base = devm_platform_get_and_ioremap_resource ( pdev , 0 , NULL ) ;
2018-07-16 17:41:50 +03:00
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
2018-11-09 20:01:05 +03:00
/*
* Fix up from the old individual DT register specification to
* cover all the registers . We do this by adjusting the ioremap ( )
* result , which should be fine as ioremap ( ) deals with pages .
* However , validate that we do not cross a page boundary while
* making this adjustment .
*/
if ( ( ( unsigned long ) base & ~ PAGE_MASK ) < data - > syscon_status_off )
return - EINVAL ;
base - = data - > syscon_status_off ;
2018-07-16 17:41:50 +03:00
priv - > syscon = devm_regmap_init_mmio ( & pdev - > dev , base ,
& armada_thermal_regmap_config ) ;
2018-11-13 17:13:45 +03:00
return PTR_ERR_OR_ZERO ( priv - > syscon ) ;
2018-07-16 17:41:50 +03:00
}
static int armada_thermal_probe_syscon ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
{
priv - > syscon = syscon_node_to_regmap ( pdev - > dev . parent - > of_node ) ;
2018-11-13 17:13:45 +03:00
return PTR_ERR_OR_ZERO ( priv - > syscon ) ;
2018-07-16 17:41:50 +03:00
}
2018-07-16 17:41:44 +03:00
static void armada_set_sane_name ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
{
const char * name = dev_name ( & pdev - > dev ) ;
char * insane_char ;
if ( strlen ( name ) > THERMAL_NAME_LENGTH ) {
/*
* When inside a system controller , the device name has the
* form : f06f8000 . system - controller : ap - thermal so stripping
* after the ' : ' should give us a shorter but meaningful name .
*/
name = strrchr ( name , ' : ' ) ;
if ( ! name )
name = " armada_thermal " ;
else
name + + ;
}
/* Save the name locally */
2022-12-28 04:45:49 +03:00
strscpy ( priv - > zone_name , name , THERMAL_NAME_LENGTH ) ;
2018-07-16 17:41:44 +03:00
/* Then check there are no '-' or hwmon core will complain */
do {
insane_char = strpbrk ( priv - > zone_name , " - " ) ;
if ( insane_char )
* insane_char = ' _ ' ;
} while ( insane_char ) ;
}
2018-12-12 12:36:40 +03:00
/*
* The IP can manage to trigger interrupts on overheat situation from all the
* sensors . However , the interrupt source changes along with the last selected
* source ( ie . the last read sensor ) , which is an inconsistent behavior . Avoid
* possible glitches by always selecting back only one channel ( arbitrarily : the
* first in the DT which has a critical trip point ) . We also disable sensor
* switch during overheat situations .
*/
static int armada_configure_overheat_int ( struct armada_thermal_priv * priv ,
struct thermal_zone_device * tz ,
int sensor_id )
{
/* Retrieve the critical trip point to enable the overheat interrupt */
2023-01-19 01:26:10 +03:00
int temperature ;
2018-12-12 12:36:40 +03:00
int ret ;
2023-01-19 01:26:10 +03:00
ret = thermal_zone_get_crit_temp ( tz , & temperature ) ;
if ( ret )
return ret ;
2018-12-12 12:36:40 +03:00
2023-01-19 01:26:10 +03:00
ret = armada_select_channel ( priv , sensor_id ) ;
if ( ret )
return ret ;
2018-12-12 12:36:40 +03:00
2023-01-19 01:26:10 +03:00
/*
* A critical temperature does not have a hysteresis
*/
armada_set_overheat_thresholds ( priv , temperature , 0 ) ;
priv - > overheat_sensor = tz ;
priv - > interrupt_source = sensor_id ;
armada_enable_overheat_interrupt ( priv ) ;
2022-10-03 12:25:47 +03:00
2023-01-19 01:26:10 +03:00
return 0 ;
2018-12-12 12:36:40 +03:00
}
2013-04-02 05:37:41 +04:00
static int armada_thermal_probe ( struct platform_device * pdev )
{
2018-07-16 17:41:51 +03:00
struct thermal_zone_device * tz ;
2018-07-16 17:41:52 +03:00
struct armada_thermal_sensor * sensor ;
2018-07-16 17:41:51 +03:00
struct armada_drvdata * drvdata ;
2013-04-02 05:37:41 +04:00
const struct of_device_id * match ;
struct armada_thermal_priv * priv ;
2018-12-12 12:36:40 +03:00
int sensor_id , irq ;
2018-07-16 17:41:50 +03:00
int ret ;
2013-04-02 05:37:41 +04:00
match = of_match_device ( armada_thermal_id_table , & pdev - > dev ) ;
if ( ! match )
return - ENODEV ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2018-07-16 17:41:51 +03:00
drvdata = devm_kzalloc ( & pdev - > dev , sizeof ( * drvdata ) , GFP_KERNEL ) ;
2018-07-30 10:07:03 +03:00
if ( ! drvdata )
2018-07-16 17:41:51 +03:00
return - ENOMEM ;
2017-12-22 19:14:06 +03:00
2018-07-16 17:41:51 +03:00
priv - > dev = & pdev - > dev ;
priv - > data = ( struct armada_thermal_data * ) match - > data ;
2018-07-16 17:41:44 +03:00
2018-07-16 17:41:52 +03:00
mutex_init ( & priv - > update_lock ) ;
2017-12-22 19:14:06 +03:00
/*
* Legacy DT bindings only described " control1 " register ( also referred
2018-07-16 17:41:50 +03:00
* as " control MSB " on old documentation ) . Then , bindings moved to cover
2017-12-22 19:14:06 +03:00
* " control0/control LSB " and " control1/control MSB " registers within
2018-07-16 17:41:50 +03:00
* the same resource , which was then of size 8 instead of 4.
*
* The logic of defining sporadic registers is broken . For instance , it
* blocked the addition of the overheat interrupt feature that needed
* another resource somewhere else in the same memory area . One solution
* is to define an overall system controller and put the thermal node
* into it , which requires the use of regmaps across all the driver .
2017-12-22 19:14:06 +03:00
*/
2018-07-16 17:41:51 +03:00
if ( IS_ERR ( syscon_node_to_regmap ( pdev - > dev . parent - > of_node ) ) ) {
/* Ensure device name is correct for the thermal core */
armada_set_sane_name ( pdev , priv ) ;
2018-07-16 17:41:50 +03:00
ret = armada_thermal_probe_legacy ( pdev , priv ) ;
2018-07-16 17:41:51 +03:00
if ( ret )
return ret ;
2018-07-16 17:41:50 +03:00
2018-07-16 17:41:51 +03:00
priv - > data - > init ( pdev , priv ) ;
2018-07-16 17:41:53 +03:00
/* Wait the sensors to be valid */
armada_wait_sensor_validity ( priv ) ;
2023-08-30 19:14:57 +03:00
tz = thermal_tripless_zone_device_register ( priv - > zone_name ,
priv , & legacy_ops ,
NULL ) ;
2018-07-16 17:41:51 +03:00
if ( IS_ERR ( tz ) ) {
dev_err ( & pdev - > dev ,
" Failed to register thermal zone device \n " ) ;
return PTR_ERR ( tz ) ;
}
2020-06-29 15:29:22 +03:00
ret = thermal_zone_device_enable ( tz ) ;
if ( ret ) {
thermal_zone_device_unregister ( tz ) ;
return ret ;
}
2018-07-16 17:41:51 +03:00
drvdata - > type = LEGACY ;
drvdata - > data . tz = tz ;
platform_set_drvdata ( pdev , drvdata ) ;
return 0 ;
}
ret = armada_thermal_probe_syscon ( pdev , priv ) ;
2018-07-16 17:41:50 +03:00
if ( ret )
return ret ;
2017-12-22 19:14:06 +03:00
2018-07-16 17:41:52 +03:00
priv - > current_channel = - 1 ;
2018-07-16 17:41:47 +03:00
priv - > data - > init ( pdev , priv ) ;
2018-07-16 17:41:51 +03:00
drvdata - > type = SYSCON ;
drvdata - > data . priv = priv ;
platform_set_drvdata ( pdev , drvdata ) ;
2013-04-02 05:37:41 +04:00
2018-12-12 12:36:40 +03:00
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq = = - EPROBE_DEFER )
return irq ;
/* The overheat interrupt feature is not mandatory */
if ( irq > 0 ) {
ret = devm_request_threaded_irq ( & pdev - > dev , irq ,
armada_overheat_isr ,
armada_overheat_isr_thread ,
0 , NULL , priv ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Cannot request threaded IRQ %d \n " ,
irq ) ;
return ret ;
}
}
2018-07-16 17:41:52 +03:00
/*
* There is one channel for the IC and one per CPU ( if any ) , each
* channel has one sensor .
*/
for ( sensor_id = 0 ; sensor_id < = priv - > data - > cpu_nr ; sensor_id + + ) {
sensor = devm_kzalloc ( & pdev - > dev ,
sizeof ( struct armada_thermal_sensor ) ,
GFP_KERNEL ) ;
if ( ! sensor )
return - ENOMEM ;
/* Register the sensor */
sensor - > priv = priv ;
sensor - > id = sensor_id ;
2022-08-05 01:43:30 +03:00
tz = devm_thermal_of_zone_register ( & pdev - > dev ,
sensor - > id , sensor ,
& of_ops ) ;
2018-07-16 17:41:52 +03:00
if ( IS_ERR ( tz ) ) {
dev_info ( & pdev - > dev , " Thermal sensor %d unavailable \n " ,
sensor_id ) ;
devm_kfree ( & pdev - > dev , sensor ) ;
continue ;
}
2018-12-12 12:36:40 +03:00
/*
* The first channel that has a critical trip point registered
* in the DT will serve as interrupt source . Others possible
* critical trip points will simply be ignored by the driver .
*/
if ( irq > 0 & & ! priv - > overheat_sensor )
armada_configure_overheat_int ( priv , tz , sensor - > id ) ;
2013-04-02 05:37:41 +04:00
}
2018-12-12 12:36:40 +03:00
/* Just complain if no overheat interrupt was set up */
if ( ! priv - > overheat_sensor )
dev_warn ( & pdev - > dev , " Overheat interrupt not available \n " ) ;
2013-04-02 05:37:41 +04:00
return 0 ;
}
2023-09-27 22:37:07 +03:00
static void armada_thermal_exit ( struct platform_device * pdev )
2013-04-02 05:37:41 +04:00
{
2018-07-16 17:41:51 +03:00
struct armada_drvdata * drvdata = platform_get_drvdata ( pdev ) ;
2013-04-02 05:37:41 +04:00
2018-07-16 17:41:51 +03:00
if ( drvdata - > type = = LEGACY )
thermal_zone_device_unregister ( drvdata - > data . tz ) ;
2013-04-02 05:37:41 +04:00
}
static struct platform_driver armada_thermal_driver = {
. probe = armada_thermal_probe ,
2023-09-27 22:37:07 +03:00
. remove_new = armada_thermal_exit ,
2013-04-02 05:37:41 +04:00
. driver = {
. name = " armada_thermal " ,
2013-05-16 14:28:08 +04:00
. of_match_table = armada_thermal_id_table ,
2013-04-02 05:37:41 +04:00
} ,
} ;
module_platform_driver ( armada_thermal_driver ) ;
MODULE_AUTHOR ( " Ezequiel Garcia <ezequiel.garcia@free-electrons.com> " ) ;
2017-12-22 19:14:10 +03:00
MODULE_DESCRIPTION ( " Marvell EBU Armada SoCs thermal driver " ) ;
2013-04-02 05:37:41 +04:00
MODULE_LICENSE ( " GPL v2 " ) ;