2013-04-02 05:37:41 +04:00
/*
* Marvell Armada 370 / XP thermal sensor driver
*
* 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>
# define THERMAL_VALID_MASK 0x1
/* 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)
2014-05-06 20:59:51 +04:00
# define A380_HW_RESET BIT(8)
2014-05-06 20:59:50 +04:00
2014-05-06 20:59:45 +04:00
struct armada_thermal_data ;
2013-04-02 05:37:41 +04:00
/* Marvell EBU Thermal Sensor Dev Structure */
struct armada_thermal_priv {
void __iomem * sensor ;
void __iomem * control ;
2014-05-06 20:59:45 +04:00
struct armada_thermal_data * data ;
2013-04-02 05:37:41 +04:00
} ;
2014-05-06 20:59:45 +04:00
struct armada_thermal_data {
2013-04-02 05:37:41 +04:00
/* Initialize the sensor */
2014-05-06 20:59:48 +04:00
void ( * init_sensor ) ( struct platform_device * pdev ,
struct armada_thermal_priv * ) ;
2013-04-02 05:37:41 +04:00
/* Test for a valid sensor value (optional) */
bool ( * is_valid ) ( struct armada_thermal_priv * ) ;
2014-05-06 20:59:46 +04:00
/* Formula coeficients: temp = (b + m * reg) / div */
unsigned long coef_b ;
unsigned long coef_m ;
unsigned long coef_div ;
2014-05-06 20:59:49 +04:00
bool inverted ;
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 ;
unsigned int is_valid_shift ;
2013-04-02 05:37:41 +04:00
} ;
2014-05-06 20:59:48 +04:00
static void armadaxp_init_sensor ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
2013-04-02 05:37:41 +04:00
{
unsigned long reg ;
reg = readl_relaxed ( priv - > control ) ;
reg | = PMU_TDC0_OTF_CAL_MASK ;
writel ( reg , priv - > control ) ;
/* Reference calibration value */
reg & = ~ PMU_TDC0_REF_CAL_CNT_MASK ;
reg | = ( 0xf1 < < PMU_TDC0_REF_CAL_CNT_OFFS ) ;
writel ( reg , priv - > control ) ;
/* Reset the sensor */
reg = readl_relaxed ( priv - > control ) ;
writel ( ( reg | PMU_TDC0_SW_RST_MASK ) , priv - > control ) ;
writel ( reg , priv - > control ) ;
/* Enable the sensor */
reg = readl_relaxed ( priv - > sensor ) ;
reg & = ~ PMU_TM_DISABLE_MASK ;
writel ( reg , priv - > sensor ) ;
}
2014-05-06 20:59:48 +04:00
static void armada370_init_sensor ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
2013-04-02 05:37:41 +04:00
{
unsigned long reg ;
reg = readl_relaxed ( priv - > control ) ;
reg | = PMU_TDC0_OTF_CAL_MASK ;
writel ( reg , priv - > control ) ;
/* Reference calibration value */
reg & = ~ PMU_TDC0_REF_CAL_CNT_MASK ;
reg | = ( 0xf1 < < PMU_TDC0_REF_CAL_CNT_OFFS ) ;
writel ( reg , priv - > control ) ;
reg & = ~ PMU_TDC0_START_CAL_MASK ;
writel ( reg , priv - > control ) ;
mdelay ( 10 ) ;
}
2014-05-06 20:59:50 +04:00
static void armada375_init_sensor ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
{
unsigned long reg ;
reg = readl ( priv - > control + 4 ) ;
reg & = ~ ( A375_UNIT_CONTROL_MASK < < A375_UNIT_CONTROL_SHIFT ) ;
reg & = ~ A375_READOUT_INVERT ;
reg & = ~ A375_HW_RESETn ;
writel ( reg , priv - > control + 4 ) ;
mdelay ( 20 ) ;
reg | = A375_HW_RESETn ;
writel ( reg , priv - > control + 4 ) ;
mdelay ( 50 ) ;
}
2014-05-06 20:59:51 +04:00
static void armada380_init_sensor ( struct platform_device * pdev ,
struct armada_thermal_priv * priv )
{
unsigned long reg = readl_relaxed ( priv - > control ) ;
/* Reset hardware once */
if ( ! ( reg & A380_HW_RESET ) ) {
reg | = A380_HW_RESET ;
writel ( reg , priv - > control ) ;
mdelay ( 10 ) ;
}
}
2013-04-02 05:37:41 +04:00
static bool armada_is_valid ( struct armada_thermal_priv * priv )
{
unsigned long reg = readl_relaxed ( priv - > sensor ) ;
2014-05-06 20:59:47 +04:00
return ( reg > > priv - > data - > is_valid_shift ) & THERMAL_VALID_MASK ;
2013-04-02 05:37:41 +04:00
}
static int armada_get_temp ( struct thermal_zone_device * thermal ,
2015-07-24 09:12:54 +03:00
int * temp )
2013-04-02 05:37:41 +04:00
{
struct armada_thermal_priv * priv = thermal - > devdata ;
unsigned long reg ;
2014-05-06 20:59:46 +04:00
unsigned long m , b , div ;
2013-04-02 05:37:41 +04:00
/* Valid check */
2014-05-06 20:59:45 +04:00
if ( priv - > data - > is_valid & & ! priv - > data - > is_valid ( priv ) ) {
2013-04-02 05:37:41 +04:00
dev_err ( & thermal - > device ,
" Temperature sensor reading not valid \n " ) ;
return - EIO ;
}
reg = readl_relaxed ( priv - > sensor ) ;
2014-05-06 20:59:47 +04:00
reg = ( reg > > priv - > data - > temp_shift ) & priv - > data - > temp_mask ;
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 )
* temp = ( ( m * reg ) - b ) / div ;
else
* temp = ( b - ( m * reg ) ) / div ;
2013-04-02 05:37:41 +04:00
return 0 ;
}
static struct thermal_zone_device_ops ops = {
. get_temp = armada_get_temp ,
} ;
2014-05-06 20:59:45 +04:00
static const struct armada_thermal_data armadaxp_data = {
2013-04-02 05:37:41 +04:00
. init_sensor = armadaxp_init_sensor ,
2014-05-06 20:59:47 +04:00
. temp_shift = 10 ,
. temp_mask = 0x1ff ,
2014-05-06 20:59:46 +04:00
. coef_b = 3153000000UL ,
. coef_m = 10000000UL ,
. coef_div = 13825 ,
2013-04-02 05:37:41 +04:00
} ;
2014-05-06 20:59:45 +04:00
static const struct armada_thermal_data armada370_data = {
2013-04-02 05:37:41 +04:00
. is_valid = armada_is_valid ,
. init_sensor = armada370_init_sensor ,
2014-05-06 20:59:47 +04:00
. is_valid_shift = 9 ,
. temp_shift = 10 ,
. temp_mask = 0x1ff ,
2014-05-06 20:59:46 +04:00
. coef_b = 3153000000UL ,
. coef_m = 10000000UL ,
. coef_div = 13825 ,
2013-04-02 05:37:41 +04:00
} ;
2014-05-06 20:59:50 +04:00
static const struct armada_thermal_data armada375_data = {
. is_valid = armada_is_valid ,
. init_sensor = armada375_init_sensor ,
. is_valid_shift = 10 ,
. temp_shift = 0 ,
. temp_mask = 0x1ff ,
. coef_b = 3171900000UL ,
. coef_m = 10000000UL ,
. coef_div = 13616 ,
} ;
2014-05-06 20:59:51 +04:00
static const struct armada_thermal_data armada380_data = {
. is_valid = armada_is_valid ,
. init_sensor = armada380_init_sensor ,
. is_valid_shift = 10 ,
. temp_shift = 0 ,
. temp_mask = 0x3ff ,
2015-04-15 20:08:08 +03:00
. coef_b = 2931108200UL ,
. coef_m = 5000000UL ,
. coef_div = 10502 ,
2014-05-06 20:59:51 +04:00
. inverted = true ,
} ;
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 ,
} ,
2013-04-02 05:37:41 +04:00
{
/* sentinel */
} ,
} ;
MODULE_DEVICE_TABLE ( of , armada_thermal_id_table ) ;
static int armada_thermal_probe ( struct platform_device * pdev )
{
struct thermal_zone_device * thermal ;
const struct of_device_id * match ;
struct armada_thermal_priv * priv ;
struct resource * res ;
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 ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
priv - > sensor = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( priv - > sensor ) )
return PTR_ERR ( priv - > sensor ) ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 1 ) ;
priv - > control = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( priv - > control ) )
return PTR_ERR ( priv - > control ) ;
2014-05-06 20:59:45 +04:00
priv - > data = ( struct armada_thermal_data * ) match - > data ;
2014-05-06 20:59:48 +04:00
priv - > data - > init_sensor ( pdev , priv ) ;
2013-04-02 05:37:41 +04:00
thermal = thermal_zone_device_register ( " armada_thermal " , 0 , 0 ,
priv , & ops , NULL , 0 , 0 ) ;
if ( IS_ERR ( thermal ) ) {
dev_err ( & pdev - > dev ,
" Failed to register thermal zone device \n " ) ;
return PTR_ERR ( thermal ) ;
}
platform_set_drvdata ( pdev , thermal ) ;
return 0 ;
}
static int armada_thermal_exit ( struct platform_device * pdev )
{
struct thermal_zone_device * armada_thermal =
platform_get_drvdata ( pdev ) ;
thermal_zone_device_unregister ( armada_thermal ) ;
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> " ) ;
MODULE_DESCRIPTION ( " Armada 370/XP thermal driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;