2019-12-19 20:28:17 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Thermal sensor driver for Allwinner SOC
* Copyright ( C ) 2019 Yangtao Li
*
* Based on the work of Icenowy Zheng < icenowy @ aosc . io >
* Based on the work of Ondrej Jirman < megous @ megous . com >
* Based on the work of Josef Gajdusek < atx @ atx . name >
*/
# include <linux/clk.h>
# include <linux/device.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/nvmem-consumer.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/reset.h>
# include <linux/slab.h>
# include <linux/thermal.h>
2019-12-28 20:19:04 +03:00
# include "thermal_hwmon.h"
2019-12-19 20:28:17 +03:00
# define MAX_SENSOR_NUM 4
# define FT_TEMP_MASK GENMASK(11, 0)
# define TEMP_CALIB_MASK GENMASK(11, 0)
# define CALIBRATE_DEFAULT 0x800
# define SUN8I_THS_CTRL0 0x00
# define SUN8I_THS_CTRL2 0x40
# define SUN8I_THS_IC 0x44
# define SUN8I_THS_IS 0x48
# define SUN8I_THS_MFC 0x70
# define SUN8I_THS_TEMP_CALIB 0x74
# define SUN8I_THS_TEMP_DATA 0x80
# define SUN50I_THS_CTRL0 0x00
# define SUN50I_H6_THS_ENABLE 0x04
# define SUN50I_H6_THS_PC 0x08
# define SUN50I_H6_THS_DIC 0x10
# define SUN50I_H6_THS_DIS 0x20
# define SUN50I_H6_THS_MFC 0x30
# define SUN50I_H6_THS_TEMP_CALIB 0xa0
# define SUN50I_H6_THS_TEMP_DATA 0xc0
# define SUN8I_THS_CTRL0_T_ACQ0(x) (GENMASK(15, 0) & (x))
# define SUN8I_THS_CTRL2_T_ACQ1(x) ((GENMASK(15, 0) & (x)) << 16)
# define SUN8I_THS_DATA_IRQ_STS(x) BIT(x + 8)
# define SUN50I_THS_CTRL0_T_ACQ(x) ((GENMASK(15, 0) & (x)) << 16)
# define SUN50I_THS_FILTER_EN BIT(2)
# define SUN50I_THS_FILTER_TYPE(x) (GENMASK(1, 0) & (x))
# define SUN50I_H6_THS_PC_TEMP_PERIOD(x) ((GENMASK(19, 0) & (x)) << 12)
# define SUN50I_H6_THS_DATA_IRQ_STS(x) BIT(x)
/* millidegree celsius */
struct tsensor {
struct ths_device * tmdev ;
struct thermal_zone_device * tzd ;
int id ;
} ;
struct ths_thermal_chip {
bool has_mod_clk ;
bool has_bus_clk_reset ;
int sensor_num ;
int offset ;
int scale ;
int ft_deviation ;
int temp_data_base ;
int ( * calibrate ) ( struct ths_device * tmdev ,
u16 * caldata , int callen ) ;
int ( * init ) ( struct ths_device * tmdev ) ;
int ( * irq_ack ) ( struct ths_device * tmdev ) ;
int ( * calc_temp ) ( struct ths_device * tmdev ,
int id , int reg ) ;
} ;
struct ths_device {
const struct ths_thermal_chip * chip ;
struct device * dev ;
struct regmap * regmap ;
struct reset_control * reset ;
struct clk * bus_clk ;
struct clk * mod_clk ;
struct tsensor sensor [ MAX_SENSOR_NUM ] ;
} ;
/* Temp Unit: millidegree Celsius */
static int sun8i_ths_calc_temp ( struct ths_device * tmdev ,
int id , int reg )
{
return tmdev - > chip - > offset - ( reg * tmdev - > chip - > scale / 10 ) ;
}
static int sun50i_h5_calc_temp ( struct ths_device * tmdev ,
int id , int reg )
{
if ( reg > = 0x500 )
return - 1191 * reg / 10 + 223000 ;
else if ( ! id )
return - 1452 * reg / 10 + 259000 ;
else
return - 1590 * reg / 10 + 276000 ;
}
static int sun8i_ths_get_temp ( void * data , int * temp )
{
struct tsensor * s = data ;
struct ths_device * tmdev = s - > tmdev ;
int val = 0 ;
regmap_read ( tmdev - > regmap , tmdev - > chip - > temp_data_base +
0x4 * s - > id , & val ) ;
/* ths have no data yet */
if ( ! val )
return - EAGAIN ;
* temp = tmdev - > chip - > calc_temp ( tmdev , s - > id , val ) ;
/*
* According to the original sdk , there are some platforms ( rarely )
* that add a fixed offset value after calculating the temperature
* value . We can ' t simply put it on the formula for calculating the
* temperature above , because the formula for calculating the
* temperature above is also used when the sensor is calibrated . If
* do this , the correct calibration formula is hard to know .
*/
* temp + = tmdev - > chip - > ft_deviation ;
return 0 ;
}
static const struct thermal_zone_of_device_ops ths_ops = {
. get_temp = sun8i_ths_get_temp ,
} ;
static const struct regmap_config config = {
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
. fast_io = true ,
. max_register = 0xfc ,
} ;
static int sun8i_h3_irq_ack ( struct ths_device * tmdev )
{
int i , state , ret = 0 ;
regmap_read ( tmdev - > regmap , SUN8I_THS_IS , & state ) ;
for ( i = 0 ; i < tmdev - > chip - > sensor_num ; i + + ) {
if ( state & SUN8I_THS_DATA_IRQ_STS ( i ) ) {
regmap_write ( tmdev - > regmap , SUN8I_THS_IS ,
SUN8I_THS_DATA_IRQ_STS ( i ) ) ;
ret | = BIT ( i ) ;
}
}
return ret ;
}
static int sun50i_h6_irq_ack ( struct ths_device * tmdev )
{
int i , state , ret = 0 ;
regmap_read ( tmdev - > regmap , SUN50I_H6_THS_DIS , & state ) ;
for ( i = 0 ; i < tmdev - > chip - > sensor_num ; i + + ) {
if ( state & SUN50I_H6_THS_DATA_IRQ_STS ( i ) ) {
regmap_write ( tmdev - > regmap , SUN50I_H6_THS_DIS ,
SUN50I_H6_THS_DATA_IRQ_STS ( i ) ) ;
ret | = BIT ( i ) ;
}
}
return ret ;
}
static irqreturn_t sun8i_irq_thread ( int irq , void * data )
{
struct ths_device * tmdev = data ;
int i , state ;
state = tmdev - > chip - > irq_ack ( tmdev ) ;
for ( i = 0 ; i < tmdev - > chip - > sensor_num ; i + + ) {
if ( state & BIT ( i ) )
thermal_zone_device_update ( tmdev - > sensor [ i ] . tzd ,
THERMAL_EVENT_UNSPECIFIED ) ;
}
return IRQ_HANDLED ;
}
static int sun8i_h3_ths_calibrate ( struct ths_device * tmdev ,
u16 * caldata , int callen )
{
int i ;
if ( ! caldata [ 0 ] | | callen < 2 * tmdev - > chip - > sensor_num )
return - EINVAL ;
for ( i = 0 ; i < tmdev - > chip - > sensor_num ; i + + ) {
int offset = ( i % 2 ) < < 4 ;
regmap_update_bits ( tmdev - > regmap ,
SUN8I_THS_TEMP_CALIB + ( 4 * ( i > > 1 ) ) ,
0xfff < < offset ,
caldata [ i ] < < offset ) ;
}
return 0 ;
}
static int sun50i_h6_ths_calibrate ( struct ths_device * tmdev ,
u16 * caldata , int callen )
{
struct device * dev = tmdev - > dev ;
int i , ft_temp ;
if ( ! caldata [ 0 ] | | callen < 2 + 2 * tmdev - > chip - > sensor_num )
return - EINVAL ;
/*
* efuse layout :
*
* 0 11 16 32
* + - - - - - - - + - - - - - - - + - - - - - - - +
* | temp | | sensor0 | sensor1 |
* + - - - - - - - + - - - - - - - + - - - - - - - +
*
* The calibration data on the H6 is the ambient temperature and
* sensor values that are filled during the factory test stage .
*
* The unit of stored FT temperature is 0.1 degreee celusis .
*
* We need to calculate a delta between measured and caluclated
* register values and this will become a calibration offset .
*/
ft_temp = ( caldata [ 0 ] & FT_TEMP_MASK ) * 100 ;
for ( i = 0 ; i < tmdev - > chip - > sensor_num ; i + + ) {
2020-07-24 10:11:42 +03:00
int sensor_reg = caldata [ i + 1 ] & TEMP_CALIB_MASK ;
2019-12-19 20:28:17 +03:00
int cdata , offset ;
int sensor_temp = tmdev - > chip - > calc_temp ( tmdev , i , sensor_reg ) ;
/*
* Calibration data is CALIBRATE_DEFAULT - ( calculated
* temperature from sensor reading at factory temperature
* minus actual factory temperature ) * 14.88 ( scale from
* temperature to register values )
*/
cdata = CALIBRATE_DEFAULT -
( ( sensor_temp - ft_temp ) * 10 / tmdev - > chip - > scale ) ;
if ( cdata & ~ TEMP_CALIB_MASK ) {
/*
* Calibration value more than 12 - bit , but calibration
* register is 12 - bit . In this case , ths hardware can
* still work without calibration , although the data
* won ' t be so accurate .
*/
dev_warn ( dev , " sensor%d is not calibrated. \n " , i ) ;
continue ;
}
offset = ( i % 2 ) * 16 ;
regmap_update_bits ( tmdev - > regmap ,
SUN50I_H6_THS_TEMP_CALIB + ( i / 2 * 4 ) ,
0xfff < < offset ,
cdata < < offset ) ;
}
return 0 ;
}
static int sun8i_ths_calibrate ( struct ths_device * tmdev )
{
struct nvmem_cell * calcell ;
struct device * dev = tmdev - > dev ;
u16 * caldata ;
size_t callen ;
int ret = 0 ;
calcell = devm_nvmem_cell_get ( dev , " calibration " ) ;
if ( IS_ERR ( calcell ) ) {
if ( PTR_ERR ( calcell ) = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
/*
* Even if the external calibration data stored in sid is
* not accessible , the THS hardware can still work , although
* the data won ' t be so accurate .
*
* The default value of calibration register is 0x800 for
* every sensor , and the calibration value is usually 0x7 xx
* or 0x8 xx , so they won ' t be away from the default value
* for a lot .
*
* So here we do not return error if the calibartion data is
* not available , except the probe needs deferring .
*/
goto out ;
}
caldata = nvmem_cell_read ( calcell , & callen ) ;
if ( IS_ERR ( caldata ) ) {
ret = PTR_ERR ( caldata ) ;
goto out ;
}
tmdev - > chip - > calibrate ( tmdev , caldata , callen ) ;
kfree ( caldata ) ;
out :
return ret ;
}
static int sun8i_ths_resource_init ( struct ths_device * tmdev )
{
struct device * dev = tmdev - > dev ;
struct platform_device * pdev = to_platform_device ( dev ) ;
void __iomem * base ;
int ret ;
base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
tmdev - > regmap = devm_regmap_init_mmio ( dev , base , & config ) ;
if ( IS_ERR ( tmdev - > regmap ) )
return PTR_ERR ( tmdev - > regmap ) ;
if ( tmdev - > chip - > has_bus_clk_reset ) {
2020-01-12 20:13:18 +03:00
tmdev - > reset = devm_reset_control_get ( dev , NULL ) ;
2019-12-19 20:28:17 +03:00
if ( IS_ERR ( tmdev - > reset ) )
return PTR_ERR ( tmdev - > reset ) ;
tmdev - > bus_clk = devm_clk_get ( & pdev - > dev , " bus " ) ;
if ( IS_ERR ( tmdev - > bus_clk ) )
return PTR_ERR ( tmdev - > bus_clk ) ;
}
if ( tmdev - > chip - > has_mod_clk ) {
tmdev - > mod_clk = devm_clk_get ( & pdev - > dev , " mod " ) ;
if ( IS_ERR ( tmdev - > mod_clk ) )
return PTR_ERR ( tmdev - > mod_clk ) ;
}
ret = reset_control_deassert ( tmdev - > reset ) ;
if ( ret )
return ret ;
ret = clk_prepare_enable ( tmdev - > bus_clk ) ;
if ( ret )
goto assert_reset ;
ret = clk_set_rate ( tmdev - > mod_clk , 24000000 ) ;
if ( ret )
goto bus_disable ;
ret = clk_prepare_enable ( tmdev - > mod_clk ) ;
if ( ret )
goto bus_disable ;
ret = sun8i_ths_calibrate ( tmdev ) ;
if ( ret )
goto mod_disable ;
return 0 ;
mod_disable :
clk_disable_unprepare ( tmdev - > mod_clk ) ;
bus_disable :
clk_disable_unprepare ( tmdev - > bus_clk ) ;
assert_reset :
reset_control_assert ( tmdev - > reset ) ;
return ret ;
}
static int sun8i_h3_thermal_init ( struct ths_device * tmdev )
{
int val ;
/* average over 4 samples */
regmap_write ( tmdev - > regmap , SUN8I_THS_MFC ,
SUN50I_THS_FILTER_EN |
SUN50I_THS_FILTER_TYPE ( 1 ) ) ;
/*
* clkin = 24 MHz
* filter_samples = 4
* period = 0.25 s
*
* x = period * clkin / 4096 / filter_samples - 1
* = 365
*/
val = GENMASK ( 7 + tmdev - > chip - > sensor_num , 8 ) ;
regmap_write ( tmdev - > regmap , SUN8I_THS_IC ,
SUN50I_H6_THS_PC_TEMP_PERIOD ( 365 ) | val ) ;
/*
* T_acq = 20u s
* clkin = 24 MHz
*
* x = T_acq * clkin - 1
* = 479
*/
regmap_write ( tmdev - > regmap , SUN8I_THS_CTRL0 ,
SUN8I_THS_CTRL0_T_ACQ0 ( 479 ) ) ;
val = GENMASK ( tmdev - > chip - > sensor_num - 1 , 0 ) ;
regmap_write ( tmdev - > regmap , SUN8I_THS_CTRL2 ,
SUN8I_THS_CTRL2_T_ACQ1 ( 479 ) | val ) ;
return 0 ;
}
/*
* Without this undocummented value , the returned temperatures would
* be higher than real ones by about 20 C .
*/
# define SUN50I_H6_CTRL0_UNK 0x0000002f
static int sun50i_h6_thermal_init ( struct ths_device * tmdev )
{
int val ;
/*
* T_acq = 20u s
* clkin = 24 MHz
*
* x = T_acq * clkin - 1
* = 479
*/
regmap_write ( tmdev - > regmap , SUN50I_THS_CTRL0 ,
SUN50I_H6_CTRL0_UNK | SUN50I_THS_CTRL0_T_ACQ ( 479 ) ) ;
/* average over 4 samples */
regmap_write ( tmdev - > regmap , SUN50I_H6_THS_MFC ,
SUN50I_THS_FILTER_EN |
SUN50I_THS_FILTER_TYPE ( 1 ) ) ;
/*
* clkin = 24 MHz
* filter_samples = 4
* period = 0.25 s
*
* x = period * clkin / 4096 / filter_samples - 1
* = 365
*/
regmap_write ( tmdev - > regmap , SUN50I_H6_THS_PC ,
SUN50I_H6_THS_PC_TEMP_PERIOD ( 365 ) ) ;
/* enable sensor */
val = GENMASK ( tmdev - > chip - > sensor_num - 1 , 0 ) ;
regmap_write ( tmdev - > regmap , SUN50I_H6_THS_ENABLE , val ) ;
/* thermal data interrupt enable */
val = GENMASK ( tmdev - > chip - > sensor_num - 1 , 0 ) ;
regmap_write ( tmdev - > regmap , SUN50I_H6_THS_DIC , val ) ;
return 0 ;
}
static int sun8i_ths_register ( struct ths_device * tmdev )
{
int i ;
for ( i = 0 ; i < tmdev - > chip - > sensor_num ; i + + ) {
tmdev - > sensor [ i ] . tmdev = tmdev ;
tmdev - > sensor [ i ] . id = i ;
tmdev - > sensor [ i ] . tzd =
devm_thermal_zone_of_sensor_register ( tmdev - > dev ,
i ,
& tmdev - > sensor [ i ] ,
& ths_ops ) ;
if ( IS_ERR ( tmdev - > sensor [ i ] . tzd ) )
return PTR_ERR ( tmdev - > sensor [ i ] . tzd ) ;
2019-12-28 20:19:04 +03:00
if ( devm_thermal_add_hwmon_sysfs ( tmdev - > sensor [ i ] . tzd ) )
dev_warn ( tmdev - > dev ,
" Failed to add hwmon sysfs attributes \n " ) ;
2019-12-19 20:28:17 +03:00
}
return 0 ;
}
static int sun8i_ths_probe ( struct platform_device * pdev )
{
struct ths_device * tmdev ;
struct device * dev = & pdev - > dev ;
int ret , irq ;
tmdev = devm_kzalloc ( dev , sizeof ( * tmdev ) , GFP_KERNEL ) ;
if ( ! tmdev )
return - ENOMEM ;
tmdev - > dev = dev ;
tmdev - > chip = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! tmdev - > chip )
return - EINVAL ;
platform_set_drvdata ( pdev , tmdev ) ;
ret = sun8i_ths_resource_init ( tmdev ) ;
if ( ret )
return ret ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 )
return irq ;
ret = tmdev - > chip - > init ( tmdev ) ;
if ( ret )
return ret ;
ret = sun8i_ths_register ( tmdev ) ;
if ( ret )
return ret ;
/*
* Avoid entering the interrupt handler , the thermal device is not
* registered yet , we deffer the registration of the interrupt to
* the end .
*/
ret = devm_request_threaded_irq ( dev , irq , NULL ,
sun8i_irq_thread ,
IRQF_ONESHOT , " ths " , tmdev ) ;
if ( ret )
return ret ;
return 0 ;
}
static int sun8i_ths_remove ( struct platform_device * pdev )
{
struct ths_device * tmdev = platform_get_drvdata ( pdev ) ;
clk_disable_unprepare ( tmdev - > mod_clk ) ;
clk_disable_unprepare ( tmdev - > bus_clk ) ;
reset_control_assert ( tmdev - > reset ) ;
return 0 ;
}
static const struct ths_thermal_chip sun8i_a83t_ths = {
. sensor_num = 3 ,
. scale = 705 ,
. offset = 191668 ,
. temp_data_base = SUN8I_THS_TEMP_DATA ,
. calibrate = sun8i_h3_ths_calibrate ,
. init = sun8i_h3_thermal_init ,
. irq_ack = sun8i_h3_irq_ack ,
. calc_temp = sun8i_ths_calc_temp ,
} ;
static const struct ths_thermal_chip sun8i_h3_ths = {
. sensor_num = 1 ,
. scale = 1211 ,
. offset = 217000 ,
. has_mod_clk = true ,
. has_bus_clk_reset = true ,
. temp_data_base = SUN8I_THS_TEMP_DATA ,
. calibrate = sun8i_h3_ths_calibrate ,
. init = sun8i_h3_thermal_init ,
. irq_ack = sun8i_h3_irq_ack ,
. calc_temp = sun8i_ths_calc_temp ,
} ;
static const struct ths_thermal_chip sun8i_r40_ths = {
2020-01-06 20:46:38 +03:00
. sensor_num = 2 ,
2019-12-19 20:28:17 +03:00
. offset = 251086 ,
. scale = 1130 ,
. has_mod_clk = true ,
. has_bus_clk_reset = true ,
. temp_data_base = SUN8I_THS_TEMP_DATA ,
. calibrate = sun8i_h3_ths_calibrate ,
. init = sun8i_h3_thermal_init ,
. irq_ack = sun8i_h3_irq_ack ,
. calc_temp = sun8i_ths_calc_temp ,
} ;
static const struct ths_thermal_chip sun50i_a64_ths = {
. sensor_num = 3 ,
. offset = 260890 ,
. scale = 1170 ,
. has_mod_clk = true ,
. has_bus_clk_reset = true ,
. temp_data_base = SUN8I_THS_TEMP_DATA ,
. calibrate = sun8i_h3_ths_calibrate ,
. init = sun8i_h3_thermal_init ,
. irq_ack = sun8i_h3_irq_ack ,
. calc_temp = sun8i_ths_calc_temp ,
} ;
2020-07-24 10:11:43 +03:00
static const struct ths_thermal_chip sun50i_a100_ths = {
. sensor_num = 3 ,
. has_bus_clk_reset = true ,
. ft_deviation = 8000 ,
. offset = 187744 ,
. scale = 672 ,
. temp_data_base = SUN50I_H6_THS_TEMP_DATA ,
. calibrate = sun50i_h6_ths_calibrate ,
. init = sun50i_h6_thermal_init ,
. irq_ack = sun50i_h6_irq_ack ,
. calc_temp = sun8i_ths_calc_temp ,
} ;
2019-12-19 20:28:17 +03:00
static const struct ths_thermal_chip sun50i_h5_ths = {
. sensor_num = 2 ,
. has_mod_clk = true ,
. has_bus_clk_reset = true ,
. temp_data_base = SUN8I_THS_TEMP_DATA ,
. calibrate = sun8i_h3_ths_calibrate ,
. init = sun8i_h3_thermal_init ,
. irq_ack = sun8i_h3_irq_ack ,
. calc_temp = sun50i_h5_calc_temp ,
} ;
static const struct ths_thermal_chip sun50i_h6_ths = {
. sensor_num = 2 ,
. has_bus_clk_reset = true ,
. ft_deviation = 7000 ,
. offset = 187744 ,
. scale = 672 ,
. temp_data_base = SUN50I_H6_THS_TEMP_DATA ,
. calibrate = sun50i_h6_ths_calibrate ,
. init = sun50i_h6_thermal_init ,
. irq_ack = sun50i_h6_irq_ack ,
. calc_temp = sun8i_ths_calc_temp ,
} ;
static const struct of_device_id of_ths_match [ ] = {
{ . compatible = " allwinner,sun8i-a83t-ths " , . data = & sun8i_a83t_ths } ,
{ . compatible = " allwinner,sun8i-h3-ths " , . data = & sun8i_h3_ths } ,
{ . compatible = " allwinner,sun8i-r40-ths " , . data = & sun8i_r40_ths } ,
{ . compatible = " allwinner,sun50i-a64-ths " , . data = & sun50i_a64_ths } ,
2020-07-24 10:11:43 +03:00
{ . compatible = " allwinner,sun50i-a100-ths " , . data = & sun50i_a100_ths } ,
2019-12-19 20:28:17 +03:00
{ . compatible = " allwinner,sun50i-h5-ths " , . data = & sun50i_h5_ths } ,
{ . compatible = " allwinner,sun50i-h6-ths " , . data = & sun50i_h6_ths } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , of_ths_match ) ;
static struct platform_driver ths_driver = {
. probe = sun8i_ths_probe ,
. remove = sun8i_ths_remove ,
. driver = {
. name = " sun8i-thermal " ,
. of_match_table = of_ths_match ,
} ,
} ;
module_platform_driver ( ths_driver ) ;
MODULE_DESCRIPTION ( " Thermal sensor driver for Allwinner SOC " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;