2018-09-12 15:22:47 +05:30
// SPDX-License-Identifier: GPL-2.0
2016-05-05 14:21:39 +05:30
/*
* Copyright ( c ) 2015 , The Linux Foundation . All rights reserved .
2020-04-29 23:44:17 +05:30
* Copyright ( c ) 2019 , 2020 , Linaro Ltd .
2016-05-05 14:21:39 +05:30
*/
2019-11-01 00:07:28 +05:30
# include <linux/debugfs.h>
2016-05-05 14:21:39 +05:30
# include <linux/err.h>
2020-04-29 23:44:17 +05:30
# include <linux/io.h>
2016-05-05 14:21:39 +05:30
# include <linux/module.h>
2020-04-29 23:44:17 +05:30
# include <linux/nvmem-consumer.h>
2016-05-05 14:21:39 +05:30
# include <linux/of.h>
2020-04-29 23:44:17 +05:30
# include <linux/of_address.h>
2019-11-01 00:07:39 +05:30
# include <linux/of_platform.h>
2021-04-20 20:33:37 +02:00
# include <linux/mfd/syscon.h>
2016-05-05 14:21:39 +05:30
# include <linux/platform_device.h>
# include <linux/pm.h>
2020-04-29 23:44:17 +05:30
# include <linux/regmap.h>
2016-05-05 14:21:39 +05:30
# include <linux/slab.h>
2024-03-28 10:32:30 +05:30
# include <linux/suspend.h>
2016-05-05 14:21:39 +05:30
# include <linux/thermal.h>
2022-01-29 21:07:50 +03:00
# include "../thermal_hwmon.h"
2016-05-05 14:21:39 +05:30
# include "tsens.h"
2020-04-29 23:44:17 +05:30
/**
* struct tsens_irq_data - IRQ status and temperature violations
* @ up_viol : upper threshold violated
* @ up_thresh : upper threshold temperature value
* @ up_irq_mask : mask register for upper threshold irqs
* @ up_irq_clear : clear register for uppper threshold irqs
* @ low_viol : lower threshold violated
* @ low_thresh : lower threshold temperature value
* @ low_irq_mask : mask register for lower threshold irqs
* @ low_irq_clear : clear register for lower threshold irqs
* @ crit_viol : critical threshold violated
* @ crit_thresh : critical threshold temperature value
* @ crit_irq_mask : mask register for critical threshold irqs
* @ crit_irq_clear : clear register for critical threshold irqs
*
* Structure containing data about temperature threshold settings and
* irq status if they were violated .
*/
struct tsens_irq_data {
u32 up_viol ;
int up_thresh ;
u32 up_irq_mask ;
u32 up_irq_clear ;
u32 low_viol ;
int low_thresh ;
u32 low_irq_mask ;
u32 low_irq_clear ;
u32 crit_viol ;
u32 crit_thresh ;
u32 crit_irq_mask ;
u32 crit_irq_clear ;
} ;
char * qfprom_read ( struct device * dev , const char * cname )
{
struct nvmem_cell * cell ;
ssize_t data ;
char * ret ;
cell = nvmem_cell_get ( dev , cname ) ;
if ( IS_ERR ( cell ) )
return ERR_CAST ( cell ) ;
ret = nvmem_cell_read ( cell , & data ) ;
nvmem_cell_put ( cell ) ;
return ret ;
}
2023-01-01 21:40:24 +02:00
int tsens_read_calibration ( struct tsens_priv * priv , int shift , u32 * p1 , u32 * p2 , bool backup )
2023-01-01 21:40:23 +02:00
{
u32 mode ;
u32 base1 , base2 ;
2023-01-01 21:40:24 +02:00
char name [ ] = " sXX_pY_backup " ; /* s10_p1_backup */
2023-01-01 21:40:23 +02:00
int i , ret ;
if ( priv - > num_sensors > MAX_SENSORS )
return - EINVAL ;
2023-01-01 21:40:24 +02:00
ret = snprintf ( name , sizeof ( name ) , " mode%s " , backup ? " _backup " : " " ) ;
if ( ret < 0 )
return ret ;
ret = nvmem_cell_read_variable_le_u32 ( priv - > dev , name , & mode ) ;
2023-01-01 21:40:23 +02:00
if ( ret = = - ENOENT )
dev_warn ( priv - > dev , " Please migrate to separate nvmem cells for calibration data \n " ) ;
if ( ret < 0 )
return ret ;
dev_dbg ( priv - > dev , " calibration mode is %d \n " , mode ) ;
2023-01-01 21:40:24 +02:00
ret = snprintf ( name , sizeof ( name ) , " base1%s " , backup ? " _backup " : " " ) ;
if ( ret < 0 )
return ret ;
ret = nvmem_cell_read_variable_le_u32 ( priv - > dev , name , & base1 ) ;
if ( ret < 0 )
return ret ;
ret = snprintf ( name , sizeof ( name ) , " base2%s " , backup ? " _backup " : " " ) ;
2023-01-01 21:40:23 +02:00
if ( ret < 0 )
return ret ;
2023-01-01 21:40:24 +02:00
ret = nvmem_cell_read_variable_le_u32 ( priv - > dev , name , & base2 ) ;
2023-01-01 21:40:23 +02:00
if ( ret < 0 )
return ret ;
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
2023-01-01 21:40:24 +02:00
ret = snprintf ( name , sizeof ( name ) , " s%d_p1%s " , priv - > sensor [ i ] . hw_id ,
backup ? " _backup " : " " ) ;
2023-01-01 21:40:23 +02:00
if ( ret < 0 )
return ret ;
ret = nvmem_cell_read_variable_le_u32 ( priv - > dev , name , & p1 [ i ] ) ;
if ( ret )
return ret ;
2023-01-01 21:40:24 +02:00
ret = snprintf ( name , sizeof ( name ) , " s%d_p2%s " , priv - > sensor [ i ] . hw_id ,
backup ? " _backup " : " " ) ;
2023-01-01 21:40:23 +02:00
if ( ret < 0 )
return ret ;
ret = nvmem_cell_read_variable_le_u32 ( priv - > dev , name , & p2 [ i ] ) ;
if ( ret )
return ret ;
}
switch ( mode ) {
case ONE_PT_CALIB :
for ( i = 0 ; i < priv - > num_sensors ; i + + )
p1 [ i ] = p1 [ i ] + ( base1 < < shift ) ;
break ;
case TWO_PT_CALIB :
2023-06-07 12:47:46 +02:00
case TWO_PT_CALIB_NO_OFFSET :
2023-01-01 21:40:23 +02:00
for ( i = 0 ; i < priv - > num_sensors ; i + + )
p2 [ i ] = ( p2 [ i ] + base2 ) < < shift ;
fallthrough ;
case ONE_PT_CALIB2 :
2023-06-07 12:47:46 +02:00
case ONE_PT_CALIB2_NO_OFFSET :
2023-01-01 21:40:23 +02:00
for ( i = 0 ; i < priv - > num_sensors ; i + + )
p1 [ i ] = ( p1 [ i ] + base1 ) < < shift ;
break ;
default :
dev_dbg ( priv - > dev , " calibrationless mode \n " ) ;
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
p1 [ i ] = 500 ;
p2 [ i ] = 780 ;
}
}
2023-06-07 12:47:46 +02:00
/* Apply calibration offset workaround except for _NO_OFFSET modes */
switch ( mode ) {
case TWO_PT_CALIB :
for ( i = 0 ; i < priv - > num_sensors ; i + + )
p2 [ i ] + = priv - > sensor [ i ] . p2_calib_offset ;
fallthrough ;
case ONE_PT_CALIB2 :
for ( i = 0 ; i < priv - > num_sensors ; i + + )
p1 [ i ] + = priv - > sensor [ i ] . p1_calib_offset ;
break ;
}
2023-01-01 21:40:24 +02:00
return mode ;
}
int tsens_calibrate_nvmem ( struct tsens_priv * priv , int shift )
{
u32 p1 [ MAX_SENSORS ] , p2 [ MAX_SENSORS ] ;
int mode ;
mode = tsens_read_calibration ( priv , shift , p1 , p2 , false ) ;
if ( mode < 0 )
return mode ;
2023-01-01 21:40:23 +02:00
compute_intercept_slope ( priv , p1 , p2 , mode ) ;
return 0 ;
}
int tsens_calibrate_common ( struct tsens_priv * priv )
{
return tsens_calibrate_nvmem ( priv , 2 ) ;
}
2023-01-01 21:40:25 +02:00
static u32 tsens_read_cell ( const struct tsens_single_value * cell , u8 len , u32 * data0 , u32 * data1 )
{
u32 val ;
u32 * data = cell - > blob ? data1 : data0 ;
if ( cell - > shift + len < = 32 ) {
val = data [ cell - > idx ] > > cell - > shift ;
} else {
u8 part = 32 - cell - > shift ;
val = data [ cell - > idx ] > > cell - > shift ;
val | = data [ cell - > idx + 1 ] < < part ;
}
return val & ( ( 1 < < len ) - 1 ) ;
}
int tsens_read_calibration_legacy ( struct tsens_priv * priv ,
const struct tsens_legacy_calibration_format * format ,
u32 * p1 , u32 * p2 ,
u32 * cdata0 , u32 * cdata1 )
{
u32 mode , invalid ;
u32 base1 , base2 ;
int i ;
mode = tsens_read_cell ( & format - > mode , 2 , cdata0 , cdata1 ) ;
invalid = tsens_read_cell ( & format - > invalid , 1 , cdata0 , cdata1 ) ;
if ( invalid )
mode = NO_PT_CALIB ;
dev_dbg ( priv - > dev , " calibration mode is %d \n " , mode ) ;
base1 = tsens_read_cell ( & format - > base [ 0 ] , format - > base_len , cdata0 , cdata1 ) ;
base2 = tsens_read_cell ( & format - > base [ 1 ] , format - > base_len , cdata0 , cdata1 ) ;
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
p1 [ i ] = tsens_read_cell ( & format - > sp [ i ] [ 0 ] , format - > sp_len , cdata0 , cdata1 ) ;
p2 [ i ] = tsens_read_cell ( & format - > sp [ i ] [ 1 ] , format - > sp_len , cdata0 , cdata1 ) ;
}
switch ( mode ) {
case ONE_PT_CALIB :
for ( i = 0 ; i < priv - > num_sensors ; i + + )
p1 [ i ] = p1 [ i ] + ( base1 < < format - > base_shift ) ;
break ;
case TWO_PT_CALIB :
for ( i = 0 ; i < priv - > num_sensors ; i + + )
p2 [ i ] = ( p2 [ i ] + base2 ) < < format - > base_shift ;
fallthrough ;
case ONE_PT_CALIB2 :
for ( i = 0 ; i < priv - > num_sensors ; i + + )
p1 [ i ] = ( p1 [ i ] + base1 ) < < format - > base_shift ;
break ;
default :
dev_dbg ( priv - > dev , " calibrationless mode \n " ) ;
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
p1 [ i ] = 500 ;
p2 [ i ] = 780 ;
}
}
return mode ;
}
2020-04-29 23:44:17 +05:30
/*
* Use this function on devices where slope and offset calculations
* depend on calibration data read from qfprom . On others the slope
* and offset values are derived from tz - > tzp - > slope and tz - > tzp - > offset
* resp .
*/
void compute_intercept_slope ( struct tsens_priv * priv , u32 * p1 ,
u32 * p2 , u32 mode )
{
int i ;
int num , den ;
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
dev_dbg ( priv - > dev ,
" %s: sensor%d - data_point1:%#x data_point2:%#x \n " ,
2024-04-11 14:40:21 +03:00
__func__ , i , p1 [ i ] , p2 ? p2 [ i ] : 0 ) ;
2020-04-29 23:44:17 +05:30
2021-04-20 20:33:35 +02:00
if ( ! priv - > sensor [ i ] . slope )
priv - > sensor [ i ] . slope = SLOPE_DEFAULT ;
2023-06-07 12:47:46 +02:00
if ( mode = = TWO_PT_CALIB | | mode = = TWO_PT_CALIB_NO_OFFSET ) {
2020-04-29 23:44:17 +05:30
/*
* slope ( m ) = adc_code2 - adc_code1 ( y2 - y1 ) /
* temp_120_degc - temp_30_degc ( x2 - x1 )
*/
num = p2 [ i ] - p1 [ i ] ;
num * = SLOPE_FACTOR ;
den = CAL_DEGC_PT2 - CAL_DEGC_PT1 ;
priv - > sensor [ i ] . slope = num / den ;
}
priv - > sensor [ i ] . offset = ( p1 [ i ] * SLOPE_FACTOR ) -
( CAL_DEGC_PT1 *
priv - > sensor [ i ] . slope ) ;
dev_dbg ( priv - > dev , " %s: offset:%d \n " , __func__ ,
priv - > sensor [ i ] . offset ) ;
}
}
static inline u32 degc_to_code ( int degc , const struct tsens_sensor * s )
{
u64 code = div_u64 ( ( ( u64 ) degc * s - > slope + s - > offset ) , SLOPE_FACTOR ) ;
pr_debug ( " %s: raw_code: 0x%llx, degc:%d \n " , __func__ , code , degc ) ;
return clamp_val ( code , THRESHOLD_MIN_ADC_CODE , THRESHOLD_MAX_ADC_CODE ) ;
}
static inline int code_to_degc ( u32 adc_code , const struct tsens_sensor * s )
{
int degc , num , den ;
num = ( adc_code * SLOPE_FACTOR ) - s - > offset ;
den = s - > slope ;
if ( num > 0 )
degc = num + ( den / 2 ) ;
else if ( num < 0 )
degc = num - ( den / 2 ) ;
else
degc = num ;
degc / = den ;
return degc ;
}
/**
* tsens_hw_to_mC - Return sign - extended temperature in mCelsius .
* @ s : Pointer to sensor struct
* @ field : Index into regmap_field array pointing to temperature data
*
* This function handles temperature returned in ADC code or deciCelsius
* depending on IP version .
*
* Return : Temperature in milliCelsius on success , a negative errno will
* be returned in error cases
*/
static int tsens_hw_to_mC ( const struct tsens_sensor * s , int field )
{
struct tsens_priv * priv = s - > priv ;
u32 resolution ;
u32 temp = 0 ;
int ret ;
resolution = priv - > fields [ LAST_TEMP_0 ] . msb -
priv - > fields [ LAST_TEMP_0 ] . lsb ;
ret = regmap_field_read ( priv - > rf [ field ] , & temp ) ;
if ( ret )
return ret ;
/* Convert temperature from ADC code to milliCelsius */
if ( priv - > feat - > adc )
return code_to_degc ( temp , s ) * 1000 ;
/* deciCelsius -> milliCelsius along with sign extension */
return sign_extend32 ( temp , resolution ) * 100 ;
}
/**
* tsens_mC_to_hw - Convert temperature to hardware register value
* @ s : Pointer to sensor struct
* @ temp : temperature in milliCelsius to be programmed to hardware
*
* This function outputs the value to be written to hardware in ADC code
* or deciCelsius depending on IP version .
*
* Return : ADC code or temperature in deciCelsius .
*/
static int tsens_mC_to_hw ( const struct tsens_sensor * s , int temp )
{
struct tsens_priv * priv = s - > priv ;
/* milliC to adc code */
if ( priv - > feat - > adc )
return degc_to_code ( temp / 1000 , s ) ;
/* milliC to deciC */
return temp / 100 ;
}
static inline enum tsens_ver tsens_version ( struct tsens_priv * priv )
{
return priv - > feat - > ver_major ;
}
static void tsens_set_interrupt_v1 ( struct tsens_priv * priv , u32 hw_id ,
enum tsens_irq_type irq_type , bool enable )
{
u32 index = 0 ;
switch ( irq_type ) {
case UPPER :
index = UP_INT_CLEAR_0 + hw_id ;
break ;
case LOWER :
index = LOW_INT_CLEAR_0 + hw_id ;
break ;
case CRITICAL :
/* No critical interrupts before v2 */
return ;
}
regmap_field_write ( priv - > rf [ index ] , enable ? 0 : 1 ) ;
}
static void tsens_set_interrupt_v2 ( struct tsens_priv * priv , u32 hw_id ,
enum tsens_irq_type irq_type , bool enable )
{
u32 index_mask = 0 , index_clear = 0 ;
/*
* To enable the interrupt flag for a sensor :
* - clear the mask bit
* To disable the interrupt flag for a sensor :
* - Mask further interrupts for this sensor
* - Write 1 followed by 0 to clear the interrupt
*/
switch ( irq_type ) {
case UPPER :
index_mask = UP_INT_MASK_0 + hw_id ;
index_clear = UP_INT_CLEAR_0 + hw_id ;
break ;
case LOWER :
index_mask = LOW_INT_MASK_0 + hw_id ;
index_clear = LOW_INT_CLEAR_0 + hw_id ;
break ;
case CRITICAL :
index_mask = CRIT_INT_MASK_0 + hw_id ;
index_clear = CRIT_INT_CLEAR_0 + hw_id ;
break ;
}
if ( enable ) {
regmap_field_write ( priv - > rf [ index_mask ] , 0 ) ;
} else {
regmap_field_write ( priv - > rf [ index_mask ] , 1 ) ;
regmap_field_write ( priv - > rf [ index_clear ] , 1 ) ;
regmap_field_write ( priv - > rf [ index_clear ] , 0 ) ;
}
}
/**
* tsens_set_interrupt - Set state of an interrupt
* @ priv : Pointer to tsens controller private data
* @ hw_id : Hardware ID aka . sensor number
* @ irq_type : irq_type from enum tsens_irq_type
* @ enable : false = disable , true = enable
*
* Call IP - specific function to set state of an interrupt
*
* Return : void
*/
static void tsens_set_interrupt ( struct tsens_priv * priv , u32 hw_id ,
enum tsens_irq_type irq_type , bool enable )
{
dev_dbg ( priv - > dev , " [%u] %s: %s -> %s \n " , hw_id , __func__ ,
irq_type ? ( ( irq_type = = 1 ) ? " UP " : " CRITICAL " ) : " LOW " ,
enable ? " en " : " dis " ) ;
if ( tsens_version ( priv ) > VER_1_X )
tsens_set_interrupt_v2 ( priv , hw_id , irq_type , enable ) ;
else
tsens_set_interrupt_v1 ( priv , hw_id , irq_type , enable ) ;
}
/**
* tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
* @ priv : Pointer to tsens controller private data
* @ hw_id : Hardware ID aka . sensor number
* @ d : Pointer to irq state data
*
* Return : 0 if threshold was not violated , 1 if it was violated and negative
* errno in case of errors
*/
static int tsens_threshold_violated ( struct tsens_priv * priv , u32 hw_id ,
struct tsens_irq_data * d )
{
int ret ;
ret = regmap_field_read ( priv - > rf [ UPPER_STATUS_0 + hw_id ] , & d - > up_viol ) ;
if ( ret )
return ret ;
ret = regmap_field_read ( priv - > rf [ LOWER_STATUS_0 + hw_id ] , & d - > low_viol ) ;
if ( ret )
return ret ;
if ( priv - > feat - > crit_int ) {
ret = regmap_field_read ( priv - > rf [ CRITICAL_STATUS_0 + hw_id ] ,
& d - > crit_viol ) ;
if ( ret )
return ret ;
}
if ( d - > up_viol | | d - > low_viol | | d - > crit_viol )
return 1 ;
return 0 ;
}
static int tsens_read_irq_state ( struct tsens_priv * priv , u32 hw_id ,
const struct tsens_sensor * s ,
struct tsens_irq_data * d )
{
int ret ;
ret = regmap_field_read ( priv - > rf [ UP_INT_CLEAR_0 + hw_id ] , & d - > up_irq_clear ) ;
if ( ret )
return ret ;
ret = regmap_field_read ( priv - > rf [ LOW_INT_CLEAR_0 + hw_id ] , & d - > low_irq_clear ) ;
if ( ret )
return ret ;
if ( tsens_version ( priv ) > VER_1_X ) {
ret = regmap_field_read ( priv - > rf [ UP_INT_MASK_0 + hw_id ] , & d - > up_irq_mask ) ;
if ( ret )
return ret ;
ret = regmap_field_read ( priv - > rf [ LOW_INT_MASK_0 + hw_id ] , & d - > low_irq_mask ) ;
if ( ret )
return ret ;
ret = regmap_field_read ( priv - > rf [ CRIT_INT_CLEAR_0 + hw_id ] ,
& d - > crit_irq_clear ) ;
if ( ret )
return ret ;
ret = regmap_field_read ( priv - > rf [ CRIT_INT_MASK_0 + hw_id ] ,
& d - > crit_irq_mask ) ;
if ( ret )
return ret ;
d - > crit_thresh = tsens_hw_to_mC ( s , CRIT_THRESH_0 + hw_id ) ;
} else {
/* No mask register on older TSENS */
d - > up_irq_mask = 0 ;
d - > low_irq_mask = 0 ;
d - > crit_irq_clear = 0 ;
d - > crit_irq_mask = 0 ;
d - > crit_thresh = 0 ;
}
d - > up_thresh = tsens_hw_to_mC ( s , UP_THRESH_0 + hw_id ) ;
d - > low_thresh = tsens_hw_to_mC ( s , LOW_THRESH_0 + hw_id ) ;
dev_dbg ( priv - > dev , " [%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u) \n " ,
hw_id , __func__ ,
( d - > up_viol | | d - > low_viol | | d - > crit_viol ) ? " (V) " : " " ,
d - > low_viol , d - > up_viol , d - > crit_viol ,
d - > low_irq_clear , d - > up_irq_clear , d - > crit_irq_clear ,
d - > low_irq_mask , d - > up_irq_mask , d - > crit_irq_mask ) ;
dev_dbg ( priv - > dev , " [%u] %s%s: thresh: (%d:%d:%d) \n " , hw_id , __func__ ,
( d - > up_viol | | d - > low_viol | | d - > crit_viol ) ? " (V) " : " " ,
d - > low_thresh , d - > up_thresh , d - > crit_thresh ) ;
return 0 ;
}
static inline u32 masked_irq ( u32 hw_id , u32 mask , enum tsens_ver ver )
{
if ( ver > VER_1_X )
return mask & ( 1 < < hw_id ) ;
/* v1, v0.1 don't have a irq mask register */
return 0 ;
}
/**
* tsens_critical_irq_thread ( ) - Threaded handler for critical interrupts
* @ irq : irq number
* @ data : tsens controller private data
*
* Check FSM watchdog bark status and clear if needed .
* Check all sensors to find ones that violated their critical threshold limits .
* Clear and then re - enable the interrupt .
*
* The level - triggered interrupt might deassert if the temperature returned to
* within the threshold limits by the time the handler got scheduled . We
* consider the irq to have been handled in that case .
*
* Return : IRQ_HANDLED
*/
2020-05-27 16:38:46 +05:30
static irqreturn_t tsens_critical_irq_thread ( int irq , void * data )
2020-04-29 23:44:17 +05:30
{
struct tsens_priv * priv = data ;
struct tsens_irq_data d ;
int temp , ret , i ;
u32 wdog_status , wdog_count ;
if ( priv - > feat - > has_watchdog ) {
ret = regmap_field_read ( priv - > rf [ WDOG_BARK_STATUS ] ,
& wdog_status ) ;
if ( ret )
return ret ;
if ( wdog_status ) {
/* Clear WDOG interrupt */
regmap_field_write ( priv - > rf [ WDOG_BARK_CLEAR ] , 1 ) ;
regmap_field_write ( priv - > rf [ WDOG_BARK_CLEAR ] , 0 ) ;
ret = regmap_field_read ( priv - > rf [ WDOG_BARK_COUNT ] ,
& wdog_count ) ;
if ( ret )
return ret ;
if ( wdog_count )
dev_dbg ( priv - > dev , " %s: watchdog count: %d \n " ,
__func__ , wdog_count ) ;
/* Fall through to handle critical interrupts if any */
}
}
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
const struct tsens_sensor * s = & priv - > sensor [ i ] ;
u32 hw_id = s - > hw_id ;
2021-09-07 23:25:42 +02:00
if ( ! s - > tzd )
2020-04-29 23:44:17 +05:30
continue ;
if ( ! tsens_threshold_violated ( priv , hw_id , & d ) )
continue ;
ret = get_temp_tsens_valid ( s , & temp ) ;
if ( ret ) {
dev_err ( priv - > dev , " [%u] %s: error reading sensor \n " ,
hw_id , __func__ ) ;
continue ;
}
tsens_read_irq_state ( priv , hw_id , s , & d ) ;
if ( d . crit_viol & &
! masked_irq ( hw_id , d . crit_irq_mask , tsens_version ( priv ) ) ) {
/* Mask critical interrupts, unused on Linux */
tsens_set_interrupt ( priv , hw_id , CRITICAL , false ) ;
}
}
return IRQ_HANDLED ;
}
/**
* tsens_irq_thread - Threaded interrupt handler for uplow interrupts
* @ irq : irq number
* @ data : tsens controller private data
*
* Check all sensors to find ones that violated their threshold limits . If the
* temperature is still outside the limits , call thermal_zone_device_update ( ) to
* update the thresholds , else re - enable the interrupts .
*
* The level - triggered interrupt might deassert if the temperature returned to
* within the threshold limits by the time the handler got scheduled . We
* consider the irq to have been handled in that case .
*
* Return : IRQ_HANDLED
*/
2020-05-27 16:38:46 +05:30
static irqreturn_t tsens_irq_thread ( int irq , void * data )
2020-04-29 23:44:17 +05:30
{
struct tsens_priv * priv = data ;
struct tsens_irq_data d ;
2023-01-16 11:19:54 +01:00
int i ;
2020-04-29 23:44:17 +05:30
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
const struct tsens_sensor * s = & priv - > sensor [ i ] ;
u32 hw_id = s - > hw_id ;
2021-09-07 23:25:42 +02:00
if ( ! s - > tzd )
2020-04-29 23:44:17 +05:30
continue ;
if ( ! tsens_threshold_violated ( priv , hw_id , & d ) )
continue ;
2023-01-16 11:19:54 +01:00
thermal_zone_device_update ( s - > tzd , THERMAL_EVENT_UNSPECIFIED ) ;
2021-04-20 20:33:37 +02:00
if ( tsens_version ( priv ) < VER_0_1 ) {
/* Constraint: There is only 1 interrupt control register for all
* 11 temperature sensor . So monitoring more than 1 sensor based
* on interrupts will yield inconsistent result . To overcome this
* issue we will monitor only sensor 0 which is the master sensor .
*/
break ;
}
2020-04-29 23:44:17 +05:30
}
return IRQ_HANDLED ;
}
2022-08-19 00:02:42 +02:00
/**
* tsens_combined_irq_thread ( ) - Threaded interrupt handler for combined interrupts
* @ irq : irq number
* @ data : tsens controller private data
*
* Handle the combined interrupt as if it were 2 separate interrupts , so call the
* critical handler first and then the up / low one .
*
* Return : IRQ_HANDLED
*/
static irqreturn_t tsens_combined_irq_thread ( int irq , void * data )
{
irqreturn_t ret ;
ret = tsens_critical_irq_thread ( irq , data ) ;
if ( ret ! = IRQ_HANDLED )
return ret ;
return tsens_irq_thread ( irq , data ) ;
}
2022-08-05 00:43:27 +02:00
static int tsens_set_trips ( struct thermal_zone_device * tz , int low , int high )
2020-04-29 23:44:17 +05:30
{
2023-03-01 21:14:30 +01:00
struct tsens_sensor * s = thermal_zone_device_priv ( tz ) ;
2020-04-29 23:44:17 +05:30
struct tsens_priv * priv = s - > priv ;
struct device * dev = priv - > dev ;
struct tsens_irq_data d ;
unsigned long flags ;
int high_val , low_val , cl_high , cl_low ;
u32 hw_id = s - > hw_id ;
2021-04-20 20:33:37 +02:00
if ( tsens_version ( priv ) < VER_0_1 ) {
/* Pre v0.1 IP had a single register for each type of interrupt
* and thresholds
*/
hw_id = 0 ;
}
2020-04-29 23:44:17 +05:30
dev_dbg ( dev , " [%u] %s: proposed thresholds: (%d:%d) \n " ,
hw_id , __func__ , low , high ) ;
2022-08-19 00:02:43 +02:00
cl_high = clamp_val ( high , priv - > feat - > trip_min_temp , priv - > feat - > trip_max_temp ) ;
cl_low = clamp_val ( low , priv - > feat - > trip_min_temp , priv - > feat - > trip_max_temp ) ;
2020-04-29 23:44:17 +05:30
high_val = tsens_mC_to_hw ( s , cl_high ) ;
low_val = tsens_mC_to_hw ( s , cl_low ) ;
spin_lock_irqsave ( & priv - > ul_lock , flags ) ;
tsens_read_irq_state ( priv , hw_id , s , & d ) ;
/* Write the new thresholds and clear the status */
regmap_field_write ( priv - > rf [ LOW_THRESH_0 + hw_id ] , low_val ) ;
regmap_field_write ( priv - > rf [ UP_THRESH_0 + hw_id ] , high_val ) ;
tsens_set_interrupt ( priv , hw_id , LOWER , true ) ;
tsens_set_interrupt ( priv , hw_id , UPPER , true ) ;
spin_unlock_irqrestore ( & priv - > ul_lock , flags ) ;
dev_dbg ( dev , " [%u] %s: (%d:%d)->(%d:%d) \n " ,
hw_id , __func__ , d . low_thresh , d . up_thresh , cl_low , cl_high ) ;
return 0 ;
}
2020-05-27 16:38:46 +05:30
static int tsens_enable_irq ( struct tsens_priv * priv )
2020-04-29 23:44:17 +05:30
{
int ret ;
int val = tsens_version ( priv ) > VER_1_X ? 7 : 1 ;
ret = regmap_field_write ( priv - > rf [ INT_EN ] , val ) ;
if ( ret < 0 )
dev_err ( priv - > dev , " %s: failed to enable interrupts \n " ,
__func__ ) ;
return ret ;
}
2020-05-27 16:38:46 +05:30
static void tsens_disable_irq ( struct tsens_priv * priv )
2020-04-29 23:44:17 +05:30
{
regmap_field_write ( priv - > rf [ INT_EN ] , 0 ) ;
}
int get_temp_tsens_valid ( const struct tsens_sensor * s , int * temp )
{
struct tsens_priv * priv = s - > priv ;
int hw_id = s - > hw_id ;
u32 temp_idx = LAST_TEMP_0 + hw_id ;
u32 valid_idx = VALID_0 + hw_id ;
u32 valid ;
int ret ;
2021-04-20 20:33:37 +02:00
/* VER_0 doesn't have VALID bit */
2021-10-07 19:28:59 +02:00
if ( tsens_version ( priv ) = = VER_0 )
goto get_temp ;
/* Valid bit is 0 for 6 AHB clock cycles.
* At 19.2 MHz , 1 AHB clock is ~ 60 ns .
* We should enter this loop very , very rarely .
* Wait 1 us since it ' s the min of poll_timeout macro .
* Old value was 400 ns .
*/
ret = regmap_field_read_poll_timeout ( priv - > rf [ valid_idx ] , valid ,
valid , 1 , 20 * USEC_PER_MSEC ) ;
if ( ret )
return ret ;
2020-04-29 23:44:17 +05:30
2021-10-07 19:28:59 +02:00
get_temp :
2020-04-29 23:44:17 +05:30
/* Valid bit is set, OK to read the temperature */
* temp = tsens_hw_to_mC ( s , temp_idx ) ;
return 0 ;
}
int get_temp_common ( const struct tsens_sensor * s , int * temp )
{
struct tsens_priv * priv = s - > priv ;
int hw_id = s - > hw_id ;
2021-04-20 20:33:37 +02:00
int last_temp = 0 , ret , trdy ;
unsigned long timeout ;
2020-04-29 23:44:17 +05:30
2021-04-20 20:33:37 +02:00
timeout = jiffies + usecs_to_jiffies ( TIMEOUT_US ) ;
do {
if ( tsens_version ( priv ) = = VER_0 ) {
ret = regmap_field_read ( priv - > rf [ TRDY ] , & trdy ) ;
if ( ret )
return ret ;
if ( ! trdy )
continue ;
}
2020-04-29 23:44:17 +05:30
2021-04-20 20:33:37 +02:00
ret = regmap_field_read ( priv - > rf [ LAST_TEMP_0 + hw_id ] , & last_temp ) ;
if ( ret )
return ret ;
2020-04-29 23:44:17 +05:30
2021-04-20 20:33:37 +02:00
* temp = code_to_degc ( last_temp , s ) * 1000 ;
return 0 ;
} while ( time_before ( jiffies , timeout ) ) ;
return - ETIMEDOUT ;
2020-04-29 23:44:17 +05:30
}
# ifdef CONFIG_DEBUG_FS
static int dbg_sensors_show ( struct seq_file * s , void * data )
{
struct platform_device * pdev = s - > private ;
struct tsens_priv * priv = platform_get_drvdata ( pdev ) ;
int i ;
seq_printf ( s , " max: %2d \n num: %2d \n \n " ,
priv - > feat - > max_sensors , priv - > num_sensors ) ;
seq_puts ( s , " id slope offset \n -------------------------- \n " ) ;
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
seq_printf ( s , " %8d %8d %8d \n " , priv - > sensor [ i ] . hw_id ,
priv - > sensor [ i ] . slope , priv - > sensor [ i ] . offset ) ;
}
return 0 ;
}
static int dbg_version_show ( struct seq_file * s , void * data )
{
struct platform_device * pdev = s - > private ;
struct tsens_priv * priv = platform_get_drvdata ( pdev ) ;
u32 maj_ver , min_ver , step_ver ;
int ret ;
if ( tsens_version ( priv ) > VER_0_1 ) {
ret = regmap_field_read ( priv - > rf [ VER_MAJOR ] , & maj_ver ) ;
if ( ret )
return ret ;
ret = regmap_field_read ( priv - > rf [ VER_MINOR ] , & min_ver ) ;
if ( ret )
return ret ;
ret = regmap_field_read ( priv - > rf [ VER_STEP ] , & step_ver ) ;
if ( ret )
return ret ;
seq_printf ( s , " %d.%d.%d \n " , maj_ver , min_ver , step_ver ) ;
} else {
2022-10-22 14:56:56 +02:00
seq_printf ( s , " 0.%d.0 \n " , priv - > feat - > ver_major ) ;
2020-04-29 23:44:17 +05:30
}
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE ( dbg_version ) ;
DEFINE_SHOW_ATTRIBUTE ( dbg_sensors ) ;
static void tsens_debug_init ( struct platform_device * pdev )
{
struct tsens_priv * priv = platform_get_drvdata ( pdev ) ;
2022-10-22 14:56:57 +02:00
priv - > debug_root = debugfs_lookup ( " tsens " , NULL ) ;
if ( ! priv - > debug_root )
2020-04-29 23:44:17 +05:30
priv - > debug_root = debugfs_create_dir ( " tsens " , NULL ) ;
/* A directory for each instance of the TSENS IP */
priv - > debug = debugfs_create_dir ( dev_name ( & pdev - > dev ) , priv - > debug_root ) ;
2022-10-22 14:56:57 +02:00
debugfs_create_file ( " version " , 0444 , priv - > debug , pdev , & dbg_version_fops ) ;
2020-04-29 23:44:17 +05:30
debugfs_create_file ( " sensors " , 0444 , priv - > debug , pdev , & dbg_sensors_fops ) ;
}
# else
static inline void tsens_debug_init ( struct platform_device * pdev ) { }
# endif
static const struct regmap_config tsens_config = {
. name = " tm " ,
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
} ;
static const struct regmap_config tsens_srot_config = {
. name = " srot " ,
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
} ;
int __init init_common ( struct tsens_priv * priv )
{
void __iomem * tm_base , * srot_base ;
struct device * dev = priv - > dev ;
u32 ver_minor ;
struct resource * res ;
u32 enabled ;
int ret , i , j ;
struct platform_device * op = of_find_device_by_node ( priv - > dev - > of_node ) ;
if ( ! op )
return - EINVAL ;
if ( op - > num_resources > 1 ) {
/* DT with separate SROT and TM address space */
priv - > tm_offset = 0 ;
res = platform_get_resource ( op , IORESOURCE_MEM , 1 ) ;
srot_base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( srot_base ) ) {
ret = PTR_ERR ( srot_base ) ;
goto err_put_device ;
}
priv - > srot_map = devm_regmap_init_mmio ( dev , srot_base ,
& tsens_srot_config ) ;
if ( IS_ERR ( priv - > srot_map ) ) {
ret = PTR_ERR ( priv - > srot_map ) ;
goto err_put_device ;
}
} else {
/* old DTs where SROT and TM were in a contiguous 2K block */
priv - > tm_offset = 0x1000 ;
}
2021-04-20 20:33:37 +02:00
if ( tsens_version ( priv ) > = VER_0_1 ) {
res = platform_get_resource ( op , IORESOURCE_MEM , 0 ) ;
tm_base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( tm_base ) ) {
ret = PTR_ERR ( tm_base ) ;
goto err_put_device ;
}
priv - > tm_map = devm_regmap_init_mmio ( dev , tm_base , & tsens_config ) ;
} else { /* VER_0 share the same gcc regs using a syscon */
struct device * parent = priv - > dev - > parent ;
if ( parent )
priv - > tm_map = syscon_node_to_regmap ( parent - > of_node ) ;
2020-04-29 23:44:17 +05:30
}
2021-04-20 20:33:37 +02:00
if ( IS_ERR_OR_NULL ( priv - > tm_map ) ) {
if ( ! priv - > tm_map )
ret = - ENODEV ;
else
ret = PTR_ERR ( priv - > tm_map ) ;
2020-04-29 23:44:17 +05:30
goto err_put_device ;
}
2021-04-20 20:33:37 +02:00
/* VER_0 have only tm_map */
if ( ! priv - > srot_map )
priv - > srot_map = priv - > tm_map ;
2020-04-29 23:44:17 +05:30
if ( tsens_version ( priv ) > VER_0_1 ) {
for ( i = VER_MAJOR ; i < = VER_STEP ; i + + ) {
priv - > rf [ i ] = devm_regmap_field_alloc ( dev , priv - > srot_map ,
priv - > fields [ i ] ) ;
2021-04-04 20:54:31 +08:00
if ( IS_ERR ( priv - > rf [ i ] ) ) {
ret = PTR_ERR ( priv - > rf [ i ] ) ;
goto err_put_device ;
}
2020-04-29 23:44:17 +05:30
}
ret = regmap_field_read ( priv - > rf [ VER_MINOR ] , & ver_minor ) ;
if ( ret )
goto err_put_device ;
}
priv - > rf [ TSENS_EN ] = devm_regmap_field_alloc ( dev , priv - > srot_map ,
priv - > fields [ TSENS_EN ] ) ;
if ( IS_ERR ( priv - > rf [ TSENS_EN ] ) ) {
ret = PTR_ERR ( priv - > rf [ TSENS_EN ] ) ;
goto err_put_device ;
}
2021-04-20 20:33:37 +02:00
/* in VER_0 TSENS need to be explicitly enabled */
if ( tsens_version ( priv ) = = VER_0 )
regmap_field_write ( priv - > rf [ TSENS_EN ] , 1 ) ;
2020-04-29 23:44:17 +05:30
ret = regmap_field_read ( priv - > rf [ TSENS_EN ] , & enabled ) ;
if ( ret )
goto err_put_device ;
if ( ! enabled ) {
dev_err ( dev , " %s: device not enabled \n " , __func__ ) ;
ret = - ENODEV ;
goto err_put_device ;
}
priv - > rf [ SENSOR_EN ] = devm_regmap_field_alloc ( dev , priv - > srot_map ,
priv - > fields [ SENSOR_EN ] ) ;
if ( IS_ERR ( priv - > rf [ SENSOR_EN ] ) ) {
ret = PTR_ERR ( priv - > rf [ SENSOR_EN ] ) ;
goto err_put_device ;
}
priv - > rf [ INT_EN ] = devm_regmap_field_alloc ( dev , priv - > tm_map ,
priv - > fields [ INT_EN ] ) ;
if ( IS_ERR ( priv - > rf [ INT_EN ] ) ) {
ret = PTR_ERR ( priv - > rf [ INT_EN ] ) ;
goto err_put_device ;
}
2021-04-20 20:33:37 +02:00
priv - > rf [ TSENS_SW_RST ] =
devm_regmap_field_alloc ( dev , priv - > srot_map , priv - > fields [ TSENS_SW_RST ] ) ;
if ( IS_ERR ( priv - > rf [ TSENS_SW_RST ] ) ) {
ret = PTR_ERR ( priv - > rf [ TSENS_SW_RST ] ) ;
goto err_put_device ;
}
priv - > rf [ TRDY ] = devm_regmap_field_alloc ( dev , priv - > tm_map , priv - > fields [ TRDY ] ) ;
if ( IS_ERR ( priv - > rf [ TRDY ] ) ) {
ret = PTR_ERR ( priv - > rf [ TRDY ] ) ;
goto err_put_device ;
}
2020-04-29 23:44:17 +05:30
/* This loop might need changes if enum regfield_ids is reordered */
for ( j = LAST_TEMP_0 ; j < = UP_THRESH_15 ; j + = 16 ) {
for ( i = 0 ; i < priv - > feat - > max_sensors ; i + + ) {
int idx = j + i ;
priv - > rf [ idx ] = devm_regmap_field_alloc ( dev ,
priv - > tm_map ,
priv - > fields [ idx ] ) ;
if ( IS_ERR ( priv - > rf [ idx ] ) ) {
ret = PTR_ERR ( priv - > rf [ idx ] ) ;
goto err_put_device ;
}
}
}
2021-04-20 20:33:37 +02:00
if ( priv - > feat - > crit_int | | tsens_version ( priv ) < VER_0_1 ) {
2020-04-29 23:44:17 +05:30
/* Loop might need changes if enum regfield_ids is reordered */
for ( j = CRITICAL_STATUS_0 ; j < = CRIT_THRESH_15 ; j + = 16 ) {
for ( i = 0 ; i < priv - > feat - > max_sensors ; i + + ) {
int idx = j + i ;
priv - > rf [ idx ] =
devm_regmap_field_alloc ( dev ,
priv - > tm_map ,
priv - > fields [ idx ] ) ;
if ( IS_ERR ( priv - > rf [ idx ] ) ) {
ret = PTR_ERR ( priv - > rf [ idx ] ) ;
goto err_put_device ;
}
}
}
}
if ( tsens_version ( priv ) > VER_1_X & & ver_minor > 2 ) {
/* Watchdog is present only on v2.3+ */
priv - > feat - > has_watchdog = 1 ;
for ( i = WDOG_BARK_STATUS ; i < = CC_MON_MASK ; i + + ) {
priv - > rf [ i ] = devm_regmap_field_alloc ( dev , priv - > tm_map ,
priv - > fields [ i ] ) ;
if ( IS_ERR ( priv - > rf [ i ] ) ) {
ret = PTR_ERR ( priv - > rf [ i ] ) ;
goto err_put_device ;
}
}
/*
* Watchdog is already enabled , unmask the bark .
* Disable cycle completion monitoring
*/
regmap_field_write ( priv - > rf [ WDOG_BARK_MASK ] , 0 ) ;
regmap_field_write ( priv - > rf [ CC_MON_MASK ] , 1 ) ;
}
spin_lock_init ( & priv - > ul_lock ) ;
2021-04-20 20:33:37 +02:00
/* VER_0 interrupt doesn't need to be enabled */
if ( tsens_version ( priv ) > = VER_0_1 )
tsens_enable_irq ( priv ) ;
2020-04-29 23:44:17 +05:30
err_put_device :
put_device ( & op - > dev ) ;
return ret ;
}
2022-08-05 00:43:27 +02:00
static int tsens_get_temp ( struct thermal_zone_device * tz , int * temp )
2016-05-05 14:21:39 +05:30
{
2023-03-01 21:14:30 +01:00
struct tsens_sensor * s = thermal_zone_device_priv ( tz ) ;
2019-03-20 18:47:44 +05:30
struct tsens_priv * priv = s - > priv ;
2016-05-05 14:21:39 +05:30
2019-11-01 00:07:25 +05:30
return priv - > ops - > get_temp ( s , temp ) ;
2016-05-05 14:21:39 +05:30
}
2016-07-04 15:12:28 +02:00
static int __maybe_unused tsens_suspend ( struct device * dev )
2016-05-05 14:21:39 +05:30
{
2019-03-20 18:47:44 +05:30
struct tsens_priv * priv = dev_get_drvdata ( dev ) ;
2016-05-05 14:21:39 +05:30
2019-03-20 18:47:44 +05:30
if ( priv - > ops & & priv - > ops - > suspend )
return priv - > ops - > suspend ( priv ) ;
2016-05-05 14:21:39 +05:30
return 0 ;
}
2016-07-04 15:12:28 +02:00
static int __maybe_unused tsens_resume ( struct device * dev )
2016-05-05 14:21:39 +05:30
{
2019-03-20 18:47:44 +05:30
struct tsens_priv * priv = dev_get_drvdata ( dev ) ;
2016-05-05 14:21:39 +05:30
2019-03-20 18:47:44 +05:30
if ( priv - > ops & & priv - > ops - > resume )
return priv - > ops - > resume ( priv ) ;
2016-05-05 14:21:39 +05:30
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( tsens_pm_ops , tsens_suspend , tsens_resume ) ;
static const struct of_device_id tsens_table [ ] = {
{
2021-04-20 20:33:42 +02:00
. compatible = " qcom,ipq8064-tsens " ,
. data = & data_8960 ,
2022-08-19 00:02:44 +02:00
} , {
. compatible = " qcom,ipq8074-tsens " ,
. data = & data_ipq8074 ,
2021-04-20 20:33:42 +02:00
} , {
2021-03-19 23:08:02 +01:00
. compatible = " qcom,mdm9607-tsens " ,
. data = & data_9607 ,
2023-05-07 23:12:21 +03:00
} , {
. compatible = " qcom,msm8226-tsens " ,
. data = & data_8226 ,
2023-06-07 12:47:49 +02:00
} , {
. compatible = " qcom,msm8909-tsens " ,
. data = & data_8909 ,
2021-03-19 23:08:02 +01:00
} , {
2016-05-05 14:21:39 +05:30
. compatible = " qcom,msm8916-tsens " ,
2016-05-05 14:21:40 +05:30
. data = & data_8916 ,
2020-06-29 22:49:26 +08:00
} , {
. compatible = " qcom,msm8939-tsens " ,
. data = & data_8939 ,
2023-01-01 21:40:20 +02:00
} , {
. compatible = " qcom,msm8956-tsens " ,
. data = & data_8956 ,
2022-04-06 03:26:46 +03:00
} , {
. compatible = " qcom,msm8960-tsens " ,
. data = & data_8960 ,
2016-05-05 14:21:39 +05:30
} , {
. compatible = " qcom,msm8974-tsens " ,
2016-05-05 14:21:41 +05:30
. data = & data_8974 ,
2019-10-05 12:41:31 +02:00
} , {
. compatible = " qcom,msm8976-tsens " ,
. data = & data_8976 ,
2016-05-05 14:21:44 +05:30
} , {
. compatible = " qcom,msm8996-tsens " ,
. data = & data_8996 ,
2019-03-20 18:48:00 +05:30
} , {
. compatible = " qcom,tsens-v1 " ,
. data = & data_tsens_v1 ,
2018-07-18 12:13:12 +05:30
} , {
. compatible = " qcom,tsens-v2 " ,
. data = & data_tsens_v2 ,
2016-05-05 14:21:39 +05:30
} ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , tsens_table ) ;
2022-08-05 00:43:27 +02:00
static const struct thermal_zone_device_ops tsens_of_ops = {
2016-05-05 14:21:39 +05:30
. get_temp = tsens_get_temp ,
2019-11-01 00:07:39 +05:30
. set_trips = tsens_set_trips ,
2016-05-05 14:21:39 +05:30
} ;
2020-03-12 18:07:02 +05:30
static int tsens_register_irq ( struct tsens_priv * priv , char * irqname ,
irq_handler_t thread_fn )
{
struct platform_device * pdev ;
int ret , irq ;
pdev = of_find_device_by_node ( priv - > dev - > of_node ) ;
if ( ! pdev )
return - ENODEV ;
irq = platform_get_irq_byname ( pdev , irqname ) ;
if ( irq < 0 ) {
ret = irq ;
/* For old DTs with no IRQ defined */
if ( irq = = - ENXIO )
ret = 0 ;
} else {
2021-04-20 20:33:37 +02:00
/* VER_0 interrupt is TRIGGER_RISING, VER_0_1 and up is ONESHOT */
if ( tsens_version ( priv ) = = VER_0 )
ret = devm_request_threaded_irq ( & pdev - > dev , irq ,
thread_fn , NULL ,
IRQF_TRIGGER_RISING ,
dev_name ( & pdev - > dev ) ,
priv ) ;
else
ret = devm_request_threaded_irq ( & pdev - > dev , irq , NULL ,
thread_fn , IRQF_ONESHOT ,
dev_name ( & pdev - > dev ) ,
priv ) ;
2020-03-12 18:07:02 +05:30
if ( ret )
dev_err ( & pdev - > dev , " %s: failed to get irq \n " ,
__func__ ) ;
else
enable_irq_wake ( irq ) ;
}
put_device ( & pdev - > dev ) ;
return ret ;
}
2024-03-28 10:32:30 +05:30
# ifdef CONFIG_SUSPEND
static int tsens_reinit ( struct tsens_priv * priv )
{
if ( tsens_version ( priv ) > = VER_2_X ) {
/*
* Re - enable the watchdog , unmask the bark .
* Disable cycle completion monitoring
*/
if ( priv - > feat - > has_watchdog ) {
regmap_field_write ( priv - > rf [ WDOG_BARK_MASK ] , 0 ) ;
regmap_field_write ( priv - > rf [ CC_MON_MASK ] , 1 ) ;
}
/* Re-enable interrupts */
tsens_enable_irq ( priv ) ;
}
return 0 ;
}
int tsens_resume_common ( struct tsens_priv * priv )
{
if ( pm_suspend_target_state = = PM_SUSPEND_MEM )
tsens_reinit ( priv ) ;
return 0 ;
}
# endif /* !CONFIG_SUSPEND */
2019-03-20 18:47:44 +05:30
static int tsens_register ( struct tsens_priv * priv )
2016-05-05 14:21:39 +05:30
{
2020-03-12 18:07:02 +05:30
int i , ret ;
2016-05-05 14:21:39 +05:30
struct thermal_zone_device * tzd ;
2019-03-20 18:47:44 +05:30
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
priv - > sensor [ i ] . priv = priv ;
2022-08-05 00:43:27 +02:00
tzd = devm_thermal_of_zone_register ( priv - > dev , priv - > sensor [ i ] . hw_id ,
& priv - > sensor [ i ] ,
& tsens_of_ops ) ;
2016-05-05 14:21:39 +05:30
if ( IS_ERR ( tzd ) )
continue ;
2019-03-20 18:47:44 +05:30
priv - > sensor [ i ] . tzd = tzd ;
if ( priv - > ops - > enable )
priv - > ops - > enable ( priv , i ) ;
2022-01-29 21:07:50 +03:00
2023-06-20 17:07:30 +08:00
devm_thermal_add_hwmon_sysfs ( priv - > dev , tzd ) ;
2016-05-05 14:21:39 +05:30
}
2019-11-01 00:07:39 +05:30
2021-04-20 20:33:37 +02:00
/* VER_0 require to set MIN and MAX THRESH
* These 2 regs are set using the :
* - CRIT_THRESH_0 for MAX THRESH hardcoded to 120 ° C
* - CRIT_THRESH_1 for MIN THRESH hardcoded to 0 ° C
*/
if ( tsens_version ( priv ) < VER_0_1 ) {
regmap_field_write ( priv - > rf [ CRIT_THRESH_0 ] ,
tsens_mC_to_hw ( priv - > sensor , 120000 ) ) ;
regmap_field_write ( priv - > rf [ CRIT_THRESH_1 ] ,
tsens_mC_to_hw ( priv - > sensor , 0 ) ) ;
}
2022-08-19 00:02:42 +02:00
if ( priv - > feat - > combo_int ) {
ret = tsens_register_irq ( priv , " combined " ,
tsens_combined_irq_thread ) ;
} else {
ret = tsens_register_irq ( priv , " uplow " , tsens_irq_thread ) ;
if ( ret < 0 )
return ret ;
2019-11-01 00:07:39 +05:30
2022-08-19 00:02:42 +02:00
if ( priv - > feat - > crit_int )
ret = tsens_register_irq ( priv , " critical " ,
tsens_critical_irq_thread ) ;
}
2019-11-01 00:07:39 +05:30
return ret ;
2016-05-05 14:21:39 +05:30
}
static int tsens_probe ( struct platform_device * pdev )
{
int ret , i ;
struct device * dev ;
struct device_node * np ;
2019-03-20 18:47:44 +05:30
struct tsens_priv * priv ;
2019-03-20 18:47:42 +05:30
const struct tsens_plat_data * data ;
2016-05-05 14:21:39 +05:30
const struct of_device_id * id ;
2018-05-07 16:53:39 -07:00
u32 num_sensors ;
2016-05-05 14:21:39 +05:30
if ( pdev - > dev . of_node )
dev = & pdev - > dev ;
else
dev = pdev - > dev . parent ;
np = dev - > of_node ;
id = of_match_node ( tsens_table , np ) ;
2016-05-05 14:21:43 +05:30
if ( id )
data = id - > data ;
else
data = & data_8960 ;
2016-05-05 14:21:39 +05:30
2018-05-07 16:53:39 -07:00
num_sensors = data - > num_sensors ;
if ( np )
of_property_read_u32 ( np , " #qcom,sensors " , & num_sensors ) ;
if ( num_sensors < = 0 ) {
2019-11-01 00:07:27 +05:30
dev_err ( dev , " %s: invalid number of sensors \n " , __func__ ) ;
2016-05-05 14:21:39 +05:30
return - EINVAL ;
}
2019-03-20 18:47:44 +05:30
priv = devm_kzalloc ( dev ,
struct_size ( priv , sensor , num_sensors ) ,
treewide: Use struct_size() for devm_kmalloc() and friends
Replaces open-coded struct size calculations with struct_size() for
devm_*, f2fs_*, and sock_* allocations. Automatically generated (and
manually adjusted) from the following Coccinelle script:
// Direct reference to struct field.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(*VAR->ELEMENT), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// mr = kzalloc(sizeof(*mr) + m * sizeof(mr->map[0]), GFP_KERNEL);
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(VAR->ELEMENT[0]), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// Same pattern, but can't trivially locate the trailing element name,
// or variable name.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
expression SOMETHING, COUNT, ELEMENT;
@@
- alloc(HANDLE, sizeof(SOMETHING) + COUNT * sizeof(ELEMENT), GFP)
+ alloc(HANDLE, CHECKME_struct_size(&SOMETHING, ELEMENT, COUNT), GFP)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-05-08 16:08:53 -07:00
GFP_KERNEL ) ;
2019-03-20 18:47:44 +05:30
if ( ! priv )
2016-05-05 14:21:39 +05:30
return - ENOMEM ;
2019-03-20 18:47:44 +05:30
priv - > dev = dev ;
priv - > num_sensors = num_sensors ;
priv - > ops = data - > ops ;
for ( i = 0 ; i < priv - > num_sensors ; i + + ) {
2016-05-05 14:21:39 +05:30
if ( data - > hw_ids )
2019-03-20 18:47:44 +05:30
priv - > sensor [ i ] . hw_id = data - > hw_ids [ i ] ;
2016-05-05 14:21:39 +05:30
else
2019-03-20 18:47:44 +05:30
priv - > sensor [ i ] . hw_id = i ;
2016-05-05 14:21:39 +05:30
}
2019-03-20 18:47:50 +05:30
priv - > feat = data - > feat ;
priv - > fields = data - > fields ;
2016-05-05 14:21:39 +05:30
2019-11-01 00:07:26 +05:30
platform_set_drvdata ( pdev , priv ) ;
2019-03-20 18:47:44 +05:30
if ( ! priv - > ops | | ! priv - > ops - > init | | ! priv - > ops - > get_temp )
2016-05-05 14:21:39 +05:30
return - EINVAL ;
2019-03-20 18:47:44 +05:30
ret = priv - > ops - > init ( priv ) ;
2016-05-05 14:21:39 +05:30
if ( ret < 0 ) {
2019-11-01 00:07:27 +05:30
dev_err ( dev , " %s: init failed \n " , __func__ ) ;
2016-05-05 14:21:39 +05:30
return ret ;
}
2019-03-20 18:47:44 +05:30
if ( priv - > ops - > calibrate ) {
ret = priv - > ops - > calibrate ( priv ) ;
2016-05-05 14:21:39 +05:30
if ( ret < 0 ) {
2019-03-20 18:47:52 +05:30
if ( ret ! = - EPROBE_DEFER )
2019-11-01 00:07:27 +05:30
dev_err ( dev , " %s: calibration failed \n " , __func__ ) ;
2016-05-05 14:21:39 +05:30
return ret ;
}
}
2022-10-22 14:56:55 +02:00
ret = tsens_register ( priv ) ;
if ( ! ret )
tsens_debug_init ( pdev ) ;
return ret ;
2016-05-05 14:21:39 +05:30
}
2023-09-27 21:37:24 +02:00
static void tsens_remove ( struct platform_device * pdev )
2016-05-05 14:21:39 +05:30
{
2019-03-20 18:47:44 +05:30
struct tsens_priv * priv = platform_get_drvdata ( pdev ) ;
2016-05-05 14:21:39 +05:30
2019-11-01 00:07:28 +05:30
debugfs_remove_recursive ( priv - > debug_root ) ;
2019-11-01 00:07:39 +05:30
tsens_disable_irq ( priv ) ;
2019-03-20 18:47:44 +05:30
if ( priv - > ops - > disable )
priv - > ops - > disable ( priv ) ;
2016-05-05 14:21:39 +05:30
}
static struct platform_driver tsens_driver = {
. probe = tsens_probe ,
2023-09-27 21:37:24 +02:00
. remove_new = tsens_remove ,
2016-05-05 14:21:39 +05:30
. driver = {
. name = " qcom-tsens " ,
. pm = & tsens_pm_ops ,
. of_match_table = tsens_table ,
} ,
} ;
module_platform_driver ( tsens_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " QCOM Temperature Sensor driver " ) ;
MODULE_ALIAS ( " platform:qcom-tsens " ) ;