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
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# 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>
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_SHIFT 0
# 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)
2017-12-22 19:14:12 +03:00
# define STATUS_POLL_PERIOD_US 1000
# define STATUS_POLL_TIMEOUT_US 100000
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-07-16 17:41:52 +03:00
int current_channel ;
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 ;
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-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
/* 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
}
2018-07-16 17:41:47 +03:00
static void armada_ap806_init ( struct platform_device * pdev ,
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 ) ;
2018-07-16 17:41:49 +03:00
reg & = ~ CONTROL1_TSEN_AVG_MASK < < CONTROL1_TSEN_AVG_SHIFT ;
reg | = 1 < < CONTROL1_TSEN_AVG_SHIFT ;
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-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 .
*/
if ( armada_wait_sensor_validity ( priv ) ) {
dev_err ( priv - > dev ,
" Temperature sensor reading not valid \n " ) ;
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 )
{
struct armada_thermal_priv * priv = thermal - > devdata ;
int ret ;
2018-07-16 17:41:54 +03:00
/* Valid check */
2018-07-16 17:41:55 +03:00
if ( armada_is_valid ( priv ) ) {
2018-07-16 17:41:54 +03:00
dev_err ( priv - > dev ,
" Temperature sensor reading not valid \n " ) ;
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 ,
} ;
static int armada_get_temp ( void * _sensor , int * temp )
{
struct armada_thermal_sensor * sensor = _sensor ;
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 ) ;
unlock_mutex :
mutex_unlock ( & priv - > update_lock ) ;
return ret ;
2018-07-16 17:41:51 +03:00
}
static struct thermal_zone_of_device_ops of_ops = {
2013-04-02 05:37:41 +04:00
. get_temp = armada_get_temp ,
} ;
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 ,
. syscon_control1_off = 0xd0 ,
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 = {
2018-07-16 17:41:47 +03:00
. init = armada_ap806_init ,
2017-12-22 19:14:08 +03:00
. is_valid_bit = BIT ( 16 ) ,
. temp_shift = 0 ,
. temp_mask = 0x3ff ,
. 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-07-16 17:41:52 +03:00
. cpu_nr = 4 ,
2017-12-22 19:14:08 +03:00
} ;
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 ,
. 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 ,
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 ,
} ,
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 ;
struct resource * res ;
void __iomem * base ;
/* First memory region points towards the status register */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2018-09-19 13:35:00 +03:00
if ( ! res )
return - EIO ;
2018-07-16 17:41:50 +03:00
/*
* Edit the resource start address and length to map over all the
* registers , instead of pointing at them one by one .
*/
res - > start - = data - > syscon_status_off ;
res - > end = res - > start + max ( data - > syscon_status_off ,
max ( data - > syscon_control0_off ,
data - > syscon_control1_off ) ) +
sizeof ( unsigned int ) - 1 ;
base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
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 */
strncpy ( priv - > zone_name , name , THERMAL_NAME_LENGTH - 1 ) ;
priv - > zone_name [ THERMAL_NAME_LENGTH - 1 ] = ' \0 ' ;
/* 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 ) ;
}
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-07-16 17:41:52 +03:00
int sensor_id ;
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 ) ;
2018-07-16 17:41:51 +03:00
tz = thermal_zone_device_register ( priv - > zone_name , 0 , 0 , priv ,
& legacy_ops , NULL , 0 , 0 ) ;
if ( IS_ERR ( tz ) ) {
dev_err ( & pdev - > dev ,
" Failed to register thermal zone device \n " ) ;
return PTR_ERR ( tz ) ;
}
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-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 ;
tz = devm_thermal_zone_of_sensor_register ( & pdev - > dev ,
sensor - > id , sensor ,
& of_ops ) ;
if ( IS_ERR ( tz ) ) {
dev_info ( & pdev - > dev , " Thermal sensor %d unavailable \n " ,
sensor_id ) ;
devm_kfree ( & pdev - > dev , sensor ) ;
continue ;
}
2013-04-02 05:37:41 +04:00
}
return 0 ;
}
static int armada_thermal_exit ( struct platform_device * pdev )
{
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
return 0 ;
}
static struct platform_driver armada_thermal_driver = {
. probe = armada_thermal_probe ,
. remove = armada_thermal_exit ,
. 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 " ) ;