2019-05-29 17:17:56 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2017-09-27 00:27:59 +03:00
/*
* Broadcom STB AVS TMON thermal sensor driver
*
* Copyright ( c ) 2015 - 2017 Broadcom
*/
# define DRV_NAME "brcmstb_thermal"
# define pr_fmt(fmt) DRV_NAME ": " fmt
# include <linux/bitops.h>
# include <linux/device.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/irqreturn.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/of_device.h>
# include <linux/thermal.h>
# define AVS_TMON_STATUS 0x00
# define AVS_TMON_STATUS_valid_msk BIT(11)
# define AVS_TMON_STATUS_data_msk GENMASK(10, 1)
# define AVS_TMON_STATUS_data_shift 1
# define AVS_TMON_EN_OVERTEMP_RESET 0x04
# define AVS_TMON_EN_OVERTEMP_RESET_msk BIT(0)
# define AVS_TMON_RESET_THRESH 0x08
# define AVS_TMON_RESET_THRESH_msk GENMASK(10, 1)
# define AVS_TMON_RESET_THRESH_shift 1
# define AVS_TMON_INT_IDLE_TIME 0x10
# define AVS_TMON_EN_TEMP_INT_SRCS 0x14
# define AVS_TMON_EN_TEMP_INT_SRCS_high BIT(1)
# define AVS_TMON_EN_TEMP_INT_SRCS_low BIT(0)
# define AVS_TMON_INT_THRESH 0x18
# define AVS_TMON_INT_THRESH_high_msk GENMASK(26, 17)
# define AVS_TMON_INT_THRESH_high_shift 17
# define AVS_TMON_INT_THRESH_low_msk GENMASK(10, 1)
# define AVS_TMON_INT_THRESH_low_shift 1
# define AVS_TMON_TEMP_INT_CODE 0x1c
# define AVS_TMON_TP_TEST_ENABLE 0x20
/* Default coefficients */
2020-01-14 22:06:02 +03:00
# define AVS_TMON_TEMP_SLOPE 487
2017-09-27 00:27:59 +03:00
# define AVS_TMON_TEMP_OFFSET 410040
/* HW related temperature constants */
# define AVS_TMON_TEMP_MAX 0x3ff
# define AVS_TMON_TEMP_MIN -88161
# define AVS_TMON_TEMP_MASK AVS_TMON_TEMP_MAX
enum avs_tmon_trip_type {
TMON_TRIP_TYPE_LOW = 0 ,
TMON_TRIP_TYPE_HIGH ,
TMON_TRIP_TYPE_RESET ,
TMON_TRIP_TYPE_MAX ,
} ;
struct avs_tmon_trip {
/* HW bit to enable the trip */
u32 enable_offs ;
u32 enable_mask ;
/* HW field to read the trip temperature */
u32 reg_offs ;
u32 reg_msk ;
int reg_shift ;
} ;
static struct avs_tmon_trip avs_tmon_trips [ ] = {
/* Trips when temperature is below threshold */
[ TMON_TRIP_TYPE_LOW ] = {
. enable_offs = AVS_TMON_EN_TEMP_INT_SRCS ,
. enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_low ,
. reg_offs = AVS_TMON_INT_THRESH ,
. reg_msk = AVS_TMON_INT_THRESH_low_msk ,
. reg_shift = AVS_TMON_INT_THRESH_low_shift ,
} ,
/* Trips when temperature is above threshold */
[ TMON_TRIP_TYPE_HIGH ] = {
. enable_offs = AVS_TMON_EN_TEMP_INT_SRCS ,
. enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_high ,
. reg_offs = AVS_TMON_INT_THRESH ,
. reg_msk = AVS_TMON_INT_THRESH_high_msk ,
. reg_shift = AVS_TMON_INT_THRESH_high_shift ,
} ,
/* Automatically resets chip when above threshold */
[ TMON_TRIP_TYPE_RESET ] = {
. enable_offs = AVS_TMON_EN_OVERTEMP_RESET ,
. enable_mask = AVS_TMON_EN_OVERTEMP_RESET_msk ,
. reg_offs = AVS_TMON_RESET_THRESH ,
. reg_msk = AVS_TMON_RESET_THRESH_msk ,
. reg_shift = AVS_TMON_RESET_THRESH_shift ,
} ,
} ;
2020-01-14 22:06:03 +03:00
struct brcmstb_thermal_params {
unsigned int offset ;
unsigned int mult ;
2020-01-14 22:06:07 +03:00
const struct thermal_zone_of_device_ops * of_ops ;
2020-01-14 22:06:03 +03:00
} ;
2017-09-27 00:27:59 +03:00
struct brcmstb_thermal_priv {
void __iomem * tmon_base ;
struct device * dev ;
struct thermal_zone_device * thermal ;
2020-01-14 22:06:03 +03:00
/* Process specific thermal parameters used for calculations */
const struct brcmstb_thermal_params * temp_params ;
2017-09-27 00:27:59 +03:00
} ;
/* Convert a HW code to a temperature reading (millidegree celsius) */
2020-01-14 22:06:03 +03:00
static inline int avs_tmon_code_to_temp ( struct brcmstb_thermal_priv * priv ,
2017-09-27 00:27:59 +03:00
u32 code )
{
2020-01-14 22:06:03 +03:00
int offset = priv - > temp_params - > offset ;
int mult = priv - > temp_params - > mult ;
return ( offset - ( int ) ( ( code & AVS_TMON_TEMP_MASK ) * mult ) ) ;
2017-09-27 00:27:59 +03:00
}
/*
* Convert a temperature value ( millidegree celsius ) to a HW code
*
* @ temp : temperature to convert
* @ low : if true , round toward the low side
*/
2020-01-14 22:06:03 +03:00
static inline u32 avs_tmon_temp_to_code ( struct brcmstb_thermal_priv * priv ,
2017-09-27 00:27:59 +03:00
int temp , bool low )
{
2020-01-14 22:06:03 +03:00
int offset = priv - > temp_params - > offset ;
int mult = priv - > temp_params - > mult ;
2017-09-27 00:27:59 +03:00
if ( temp < AVS_TMON_TEMP_MIN )
2020-01-14 22:06:02 +03:00
return AVS_TMON_TEMP_MAX ; /* Maximum code value */
2017-09-27 00:27:59 +03:00
2020-01-14 22:06:03 +03:00
if ( temp > = offset )
2017-09-27 00:27:59 +03:00
return 0 ; /* Minimum code value */
if ( low )
2020-01-14 22:06:03 +03:00
return ( u32 ) ( DIV_ROUND_UP ( offset - temp , mult ) ) ;
2017-09-27 00:27:59 +03:00
else
2020-01-14 22:06:03 +03:00
return ( u32 ) ( ( offset - temp ) / mult ) ;
2017-09-27 00:27:59 +03:00
}
static int brcmstb_get_temp ( void * data , int * temp )
{
struct brcmstb_thermal_priv * priv = data ;
u32 val ;
long t ;
val = __raw_readl ( priv - > tmon_base + AVS_TMON_STATUS ) ;
if ( ! ( val & AVS_TMON_STATUS_valid_msk ) ) {
dev_err ( priv - > dev , " reading not valid \n " ) ;
return - EIO ;
}
val = ( val & AVS_TMON_STATUS_data_msk ) > > AVS_TMON_STATUS_data_shift ;
2020-01-14 22:06:03 +03:00
t = avs_tmon_code_to_temp ( priv , val ) ;
2017-09-27 00:27:59 +03:00
if ( t < 0 )
* temp = 0 ;
else
* temp = t ;
return 0 ;
}
static void avs_tmon_trip_enable ( struct brcmstb_thermal_priv * priv ,
enum avs_tmon_trip_type type , int en )
{
struct avs_tmon_trip * trip = & avs_tmon_trips [ type ] ;
u32 val = __raw_readl ( priv - > tmon_base + trip - > enable_offs ) ;
dev_dbg ( priv - > dev , " %sable trip, type %d \n " , en ? " en " : " dis " , type ) ;
if ( en )
val | = trip - > enable_mask ;
else
val & = ~ trip - > enable_mask ;
__raw_writel ( val , priv - > tmon_base + trip - > enable_offs ) ;
}
static int avs_tmon_get_trip_temp ( struct brcmstb_thermal_priv * priv ,
enum avs_tmon_trip_type type )
{
struct avs_tmon_trip * trip = & avs_tmon_trips [ type ] ;
u32 val = __raw_readl ( priv - > tmon_base + trip - > reg_offs ) ;
val & = trip - > reg_msk ;
val > > = trip - > reg_shift ;
2020-01-14 22:06:03 +03:00
return avs_tmon_code_to_temp ( priv , val ) ;
2017-09-27 00:27:59 +03:00
}
static void avs_tmon_set_trip_temp ( struct brcmstb_thermal_priv * priv ,
enum avs_tmon_trip_type type ,
int temp )
{
struct avs_tmon_trip * trip = & avs_tmon_trips [ type ] ;
u32 val , orig ;
dev_dbg ( priv - > dev , " set temp %d to %d \n " , type , temp ) ;
/* round toward low temp for the low interrupt */
2020-01-14 22:06:03 +03:00
val = avs_tmon_temp_to_code ( priv , temp ,
2017-09-27 00:27:59 +03:00
type = = TMON_TRIP_TYPE_LOW ) ;
val < < = trip - > reg_shift ;
val & = trip - > reg_msk ;
orig = __raw_readl ( priv - > tmon_base + trip - > reg_offs ) ;
orig & = ~ trip - > reg_msk ;
orig | = val ;
__raw_writel ( orig , priv - > tmon_base + trip - > reg_offs ) ;
}
static int avs_tmon_get_intr_temp ( struct brcmstb_thermal_priv * priv )
{
u32 val ;
val = __raw_readl ( priv - > tmon_base + AVS_TMON_TEMP_INT_CODE ) ;
2020-01-14 22:06:03 +03:00
return avs_tmon_code_to_temp ( priv , val ) ;
2017-09-27 00:27:59 +03:00
}
static irqreturn_t brcmstb_tmon_irq_thread ( int irq , void * data )
{
struct brcmstb_thermal_priv * priv = data ;
int low , high , intr ;
low = avs_tmon_get_trip_temp ( priv , TMON_TRIP_TYPE_LOW ) ;
high = avs_tmon_get_trip_temp ( priv , TMON_TRIP_TYPE_HIGH ) ;
intr = avs_tmon_get_intr_temp ( priv ) ;
dev_dbg ( priv - > dev , " low/intr/high: %d/%d/%d \n " ,
low , intr , high ) ;
/* Disable high-temp until next threshold shift */
if ( intr > = high )
avs_tmon_trip_enable ( priv , TMON_TRIP_TYPE_HIGH , 0 ) ;
/* Disable low-temp until next threshold shift */
if ( intr < = low )
avs_tmon_trip_enable ( priv , TMON_TRIP_TYPE_LOW , 0 ) ;
/*
* Notify using the interrupt temperature , in case the temperature
* changes before it can next be read out
*/
thermal_zone_device_update ( priv - > thermal , intr ) ;
return IRQ_HANDLED ;
}
static int brcmstb_set_trips ( void * data , int low , int high )
{
struct brcmstb_thermal_priv * priv = data ;
dev_dbg ( priv - > dev , " set trips %d <--> %d \n " , low , high ) ;
/*
* Disable low - temp if " low " is too small . As per thermal framework
* API , we use - INT_MAX rather than INT_MIN .
*/
if ( low < = - INT_MAX ) {
avs_tmon_trip_enable ( priv , TMON_TRIP_TYPE_LOW , 0 ) ;
} else {
avs_tmon_set_trip_temp ( priv , TMON_TRIP_TYPE_LOW , low ) ;
avs_tmon_trip_enable ( priv , TMON_TRIP_TYPE_LOW , 1 ) ;
}
/* Disable high-temp if "high" is too big. */
if ( high = = INT_MAX ) {
avs_tmon_trip_enable ( priv , TMON_TRIP_TYPE_HIGH , 0 ) ;
} else {
avs_tmon_set_trip_temp ( priv , TMON_TRIP_TYPE_HIGH , high ) ;
avs_tmon_trip_enable ( priv , TMON_TRIP_TYPE_HIGH , 1 ) ;
}
return 0 ;
}
2020-01-14 22:06:07 +03:00
static const struct thermal_zone_of_device_ops brcmstb_16nm_of_ops = {
2017-09-27 00:27:59 +03:00
. get_temp = brcmstb_get_temp ,
} ;
2020-01-14 22:06:05 +03:00
static const struct brcmstb_thermal_params brcmstb_16nm_params = {
. offset = 457829 ,
. mult = 557 ,
2020-01-14 22:06:07 +03:00
. of_ops = & brcmstb_16nm_of_ops ,
} ;
static const struct thermal_zone_of_device_ops brcmstb_28nm_of_ops = {
. get_temp = brcmstb_get_temp ,
. set_trips = brcmstb_set_trips ,
2020-01-14 22:06:05 +03:00
} ;
2020-01-14 22:06:03 +03:00
static const struct brcmstb_thermal_params brcmstb_28nm_params = {
. offset = 410040 ,
. mult = 487 ,
2020-01-14 22:06:07 +03:00
. of_ops = & brcmstb_28nm_of_ops ,
2020-01-14 22:06:03 +03:00
} ;
2017-09-27 00:27:59 +03:00
static const struct of_device_id brcmstb_thermal_id_table [ ] = {
2020-01-14 22:06:05 +03:00
{ . compatible = " brcm,avs-tmon-bcm7216 " , . data = & brcmstb_16nm_params } ,
2020-01-14 22:06:03 +03:00
{ . compatible = " brcm,avs-tmon " , . data = & brcmstb_28nm_params } ,
2017-09-27 00:27:59 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , brcmstb_thermal_id_table ) ;
static int brcmstb_thermal_probe ( struct platform_device * pdev )
{
2020-01-14 22:06:07 +03:00
const struct thermal_zone_of_device_ops * of_ops ;
2017-09-27 00:27:59 +03:00
struct thermal_zone_device * thermal ;
struct brcmstb_thermal_priv * priv ;
struct resource * res ;
int irq , ret ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2020-01-14 22:06:03 +03:00
priv - > temp_params = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! priv - > temp_params )
return - EINVAL ;
2017-09-27 00:27:59 +03:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
priv - > tmon_base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( priv - > tmon_base ) )
return PTR_ERR ( priv - > tmon_base ) ;
priv - > dev = & pdev - > dev ;
platform_set_drvdata ( pdev , priv ) ;
2020-01-14 22:06:07 +03:00
of_ops = priv - > temp_params - > of_ops ;
2017-09-27 00:27:59 +03:00
2018-10-30 19:08:51 +03:00
thermal = devm_thermal_zone_of_sensor_register ( & pdev - > dev , 0 , priv ,
2020-01-14 22:06:07 +03:00
of_ops ) ;
2017-09-27 00:27:59 +03:00
if ( IS_ERR ( thermal ) ) {
ret = PTR_ERR ( thermal ) ;
dev_err ( & pdev - > dev , " could not register sensor: %d \n " , ret ) ;
return ret ;
}
priv - > thermal = thermal ;
irq = platform_get_irq ( pdev , 0 ) ;
2020-01-14 22:06:06 +03:00
if ( irq > = 0 ) {
ret = devm_request_threaded_irq ( & pdev - > dev , irq , NULL ,
brcmstb_tmon_irq_thread ,
IRQF_ONESHOT ,
DRV_NAME , priv ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " could not request IRQ: %d \n " , ret ) ;
return ret ;
}
2017-09-27 00:27:59 +03:00
}
dev_info ( & pdev - > dev , " registered AVS TMON of-sensor driver \n " ) ;
return 0 ;
}
static struct platform_driver brcmstb_thermal_driver = {
. probe = brcmstb_thermal_probe ,
. driver = {
. name = DRV_NAME ,
. of_match_table = brcmstb_thermal_id_table ,
} ,
} ;
module_platform_driver ( brcmstb_thermal_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Brian Norris " ) ;
MODULE_DESCRIPTION ( " Broadcom STB AVS TMON thermal driver " ) ;