2013-06-24 10:30:44 +04:00
/*
* Copyright 2013 Freescale Semiconductor , Inc .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*/
# include <linux/cpu_cooling.h>
# include <linux/cpufreq.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/init.h>
2013-08-01 20:33:12 +04:00
# include <linux/interrupt.h>
2013-06-24 10:30:44 +04:00
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# include <linux/thermal.h>
# include <linux/types.h>
# define REG_SET 0x4
# define REG_CLR 0x8
# define REG_TOG 0xc
# define MISC0 0x0150
# define MISC0_REFTOP_SELBIASOFF (1 << 3)
# define TEMPSENSE0 0x0180
2013-08-01 20:33:12 +04:00
# define TEMPSENSE0_ALARM_VALUE_SHIFT 20
# define TEMPSENSE0_ALARM_VALUE_MASK (0xfff << TEMPSENSE0_ALARM_VALUE_SHIFT)
2013-06-24 10:30:44 +04:00
# define TEMPSENSE0_TEMP_CNT_SHIFT 8
# define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT)
# define TEMPSENSE0_FINISHED (1 << 2)
# define TEMPSENSE0_MEASURE_TEMP (1 << 1)
# define TEMPSENSE0_POWER_DOWN (1 << 0)
# define TEMPSENSE1 0x0190
# define TEMPSENSE1_MEASURE_FREQ 0xffff
# define OCOTP_ANA1 0x04e0
/* The driver supports 1 passive trip point and 1 critical trip point */
enum imx_thermal_trip {
IMX_TRIP_PASSIVE ,
IMX_TRIP_CRITICAL ,
IMX_TRIP_NUM ,
} ;
/*
* It defines the temperature in millicelsius for passive trip point
* that will trigger cooling action when crossed .
*/
# define IMX_TEMP_PASSIVE 85000
# define IMX_POLLING_DELAY 2000 /* millisecond */
# define IMX_PASSIVE_DELAY 1000
struct imx_thermal_data {
struct thermal_zone_device * tz ;
struct thermal_cooling_device * cdev ;
enum thermal_device_mode mode ;
struct regmap * tempmon ;
int c1 , c2 ; /* See formula in imx_get_sensor_data() */
2013-08-01 20:33:11 +04:00
unsigned long temp_passive ;
unsigned long temp_critical ;
2013-08-01 20:33:12 +04:00
unsigned long alarm_temp ;
unsigned long last_temp ;
bool irq_enabled ;
int irq ;
2013-06-24 10:30:44 +04:00
} ;
2013-08-01 20:33:12 +04:00
static void imx_set_alarm_temp ( struct imx_thermal_data * data ,
signed long alarm_temp )
{
struct regmap * map = data - > tempmon ;
int alarm_value ;
data - > alarm_temp = alarm_temp ;
alarm_value = ( alarm_temp - data - > c2 ) / data - > c1 ;
regmap_write ( map , TEMPSENSE0 + REG_CLR , TEMPSENSE0_ALARM_VALUE_MASK ) ;
regmap_write ( map , TEMPSENSE0 + REG_SET , alarm_value < <
TEMPSENSE0_ALARM_VALUE_SHIFT ) ;
}
2013-06-24 10:30:44 +04:00
static int imx_get_temp ( struct thermal_zone_device * tz , unsigned long * temp )
{
struct imx_thermal_data * data = tz - > devdata ;
struct regmap * map = data - > tempmon ;
unsigned int n_meas ;
2013-08-01 20:33:12 +04:00
bool wait ;
2013-06-24 10:30:44 +04:00
u32 val ;
2013-08-01 20:33:12 +04:00
if ( data - > mode = = THERMAL_DEVICE_ENABLED ) {
/* Check if a measurement is currently in progress */
regmap_read ( map , TEMPSENSE0 , & val ) ;
wait = ! ( val & TEMPSENSE0_FINISHED ) ;
} else {
/*
* Every time we measure the temperature , we will power on the
* temperature sensor , enable measurements , take a reading ,
* disable measurements , power off the temperature sensor .
*/
regmap_write ( map , TEMPSENSE0 + REG_CLR , TEMPSENSE0_POWER_DOWN ) ;
regmap_write ( map , TEMPSENSE0 + REG_SET , TEMPSENSE0_MEASURE_TEMP ) ;
wait = true ;
}
2013-06-24 10:30:44 +04:00
/*
* According to the temp sensor designers , it may require up to ~ 17u s
* to complete a measurement .
*/
2013-08-01 20:33:12 +04:00
if ( wait )
usleep_range ( 20 , 50 ) ;
2013-06-24 10:30:44 +04:00
regmap_read ( map , TEMPSENSE0 , & val ) ;
2013-08-01 20:33:12 +04:00
if ( data - > mode ! = THERMAL_DEVICE_ENABLED ) {
regmap_write ( map , TEMPSENSE0 + REG_CLR , TEMPSENSE0_MEASURE_TEMP ) ;
regmap_write ( map , TEMPSENSE0 + REG_SET , TEMPSENSE0_POWER_DOWN ) ;
}
2013-06-24 10:30:44 +04:00
if ( ( val & TEMPSENSE0_FINISHED ) = = 0 ) {
dev_dbg ( & tz - > device , " temp measurement never finished \n " ) ;
return - EAGAIN ;
}
n_meas = ( val & TEMPSENSE0_TEMP_CNT_MASK ) > > TEMPSENSE0_TEMP_CNT_SHIFT ;
/* See imx_get_sensor_data() for formula derivation */
* temp = data - > c2 + data - > c1 * n_meas ;
2013-08-01 20:33:12 +04:00
/* Update alarm value to next higher trip point */
if ( data - > alarm_temp = = data - > temp_passive & & * temp > = data - > temp_passive )
imx_set_alarm_temp ( data , data - > temp_critical ) ;
if ( data - > alarm_temp = = data - > temp_critical & & * temp < data - > temp_passive ) {
imx_set_alarm_temp ( data , data - > temp_passive ) ;
dev_dbg ( & tz - > device , " thermal alarm off: T < %lu \n " ,
data - > alarm_temp / 1000 ) ;
}
if ( * temp ! = data - > last_temp ) {
2013-06-24 10:30:44 +04:00
dev_dbg ( & tz - > device , " millicelsius: %ld \n " , * temp ) ;
2013-08-01 20:33:12 +04:00
data - > last_temp = * temp ;
}
/* Reenable alarm IRQ if temperature below alarm temperature */
if ( ! data - > irq_enabled & & * temp < data - > alarm_temp ) {
data - > irq_enabled = true ;
enable_irq ( data - > irq ) ;
2013-06-24 10:30:44 +04:00
}
return 0 ;
}
static int imx_get_mode ( struct thermal_zone_device * tz ,
enum thermal_device_mode * mode )
{
struct imx_thermal_data * data = tz - > devdata ;
* mode = data - > mode ;
return 0 ;
}
static int imx_set_mode ( struct thermal_zone_device * tz ,
enum thermal_device_mode mode )
{
struct imx_thermal_data * data = tz - > devdata ;
2013-08-01 20:33:12 +04:00
struct regmap * map = data - > tempmon ;
2013-06-24 10:30:44 +04:00
if ( mode = = THERMAL_DEVICE_ENABLED ) {
tz - > polling_delay = IMX_POLLING_DELAY ;
tz - > passive_delay = IMX_PASSIVE_DELAY ;
2013-08-01 20:33:12 +04:00
regmap_write ( map , TEMPSENSE0 + REG_CLR , TEMPSENSE0_POWER_DOWN ) ;
regmap_write ( map , TEMPSENSE0 + REG_SET , TEMPSENSE0_MEASURE_TEMP ) ;
if ( ! data - > irq_enabled ) {
data - > irq_enabled = true ;
enable_irq ( data - > irq ) ;
}
2013-06-24 10:30:44 +04:00
} else {
2013-08-01 20:33:12 +04:00
regmap_write ( map , TEMPSENSE0 + REG_CLR , TEMPSENSE0_MEASURE_TEMP ) ;
regmap_write ( map , TEMPSENSE0 + REG_SET , TEMPSENSE0_POWER_DOWN ) ;
2013-06-24 10:30:44 +04:00
tz - > polling_delay = 0 ;
tz - > passive_delay = 0 ;
2013-08-01 20:33:12 +04:00
if ( data - > irq_enabled ) {
disable_irq ( data - > irq ) ;
data - > irq_enabled = false ;
}
2013-06-24 10:30:44 +04:00
}
data - > mode = mode ;
thermal_zone_device_update ( tz ) ;
return 0 ;
}
static int imx_get_trip_type ( struct thermal_zone_device * tz , int trip ,
enum thermal_trip_type * type )
{
* type = ( trip = = IMX_TRIP_PASSIVE ) ? THERMAL_TRIP_PASSIVE :
THERMAL_TRIP_CRITICAL ;
return 0 ;
}
static int imx_get_crit_temp ( struct thermal_zone_device * tz ,
unsigned long * temp )
{
2013-08-01 20:33:11 +04:00
struct imx_thermal_data * data = tz - > devdata ;
* temp = data - > temp_critical ;
2013-06-24 10:30:44 +04:00
return 0 ;
}
static int imx_get_trip_temp ( struct thermal_zone_device * tz , int trip ,
unsigned long * temp )
{
2013-08-01 20:33:11 +04:00
struct imx_thermal_data * data = tz - > devdata ;
* temp = ( trip = = IMX_TRIP_PASSIVE ) ? data - > temp_passive :
data - > temp_critical ;
return 0 ;
}
static int imx_set_trip_temp ( struct thermal_zone_device * tz , int trip ,
unsigned long temp )
{
struct imx_thermal_data * data = tz - > devdata ;
if ( trip = = IMX_TRIP_CRITICAL )
return - EPERM ;
if ( temp > IMX_TEMP_PASSIVE )
return - EINVAL ;
data - > temp_passive = temp ;
2013-08-01 20:33:12 +04:00
imx_set_alarm_temp ( data , temp ) ;
2013-06-24 10:30:44 +04:00
return 0 ;
}
static int imx_bind ( struct thermal_zone_device * tz ,
struct thermal_cooling_device * cdev )
{
int ret ;
ret = thermal_zone_bind_cooling_device ( tz , IMX_TRIP_PASSIVE , cdev ,
THERMAL_NO_LIMIT ,
THERMAL_NO_LIMIT ) ;
if ( ret ) {
dev_err ( & tz - > device ,
" binding zone %s with cdev %s failed:%d \n " ,
tz - > type , cdev - > type , ret ) ;
return ret ;
}
return 0 ;
}
static int imx_unbind ( struct thermal_zone_device * tz ,
struct thermal_cooling_device * cdev )
{
int ret ;
ret = thermal_zone_unbind_cooling_device ( tz , IMX_TRIP_PASSIVE , cdev ) ;
if ( ret ) {
dev_err ( & tz - > device ,
" unbinding zone %s with cdev %s failed:%d \n " ,
tz - > type , cdev - > type , ret ) ;
return ret ;
}
return 0 ;
}
static const struct thermal_zone_device_ops imx_tz_ops = {
. bind = imx_bind ,
. unbind = imx_unbind ,
. get_temp = imx_get_temp ,
. get_mode = imx_get_mode ,
. set_mode = imx_set_mode ,
. get_trip_type = imx_get_trip_type ,
. get_trip_temp = imx_get_trip_temp ,
. get_crit_temp = imx_get_crit_temp ,
2013-08-01 20:33:11 +04:00
. set_trip_temp = imx_set_trip_temp ,
2013-06-24 10:30:44 +04:00
} ;
static int imx_get_sensor_data ( struct platform_device * pdev )
{
struct imx_thermal_data * data = platform_get_drvdata ( pdev ) ;
struct regmap * map ;
int t1 , t2 , n1 , n2 ;
int ret ;
u32 val ;
map = syscon_regmap_lookup_by_phandle ( pdev - > dev . of_node ,
" fsl,tempmon-data " ) ;
if ( IS_ERR ( map ) ) {
ret = PTR_ERR ( map ) ;
dev_err ( & pdev - > dev , " failed to get sensor regmap: %d \n " , ret ) ;
return ret ;
}
ret = regmap_read ( map , OCOTP_ANA1 , & val ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to read sensor data: %d \n " , ret ) ;
return ret ;
}
if ( val = = 0 | | val = = ~ 0 ) {
dev_err ( & pdev - > dev , " invalid sensor calibration data \n " ) ;
return - EINVAL ;
}
/*
* Sensor data layout :
* [ 31 : 20 ] - sensor value @ 25 C
* [ 19 : 8 ] - sensor value of hot
* [ 7 : 0 ] - hot temperature value
*/
n1 = val > > 20 ;
n2 = ( val & 0xfff00 ) > > 8 ;
t2 = val & 0xff ;
t1 = 25 ; /* t1 always 25C */
/*
* Derived from linear interpolation ,
* Tmeas = T2 + ( Nmeas - N2 ) * ( T1 - T2 ) / ( N1 - N2 )
* We want to reduce this down to the minimum computation necessary
* for each temperature read . Also , we want Tmeas in millicelsius
* and we don ' t want to lose precision from integer division . So . . .
* milli_Tmeas = 1000 * T2 + 1000 * ( Nmeas - N2 ) * ( T1 - T2 ) / ( N1 - N2 )
* Let constant c1 = 1000 * ( T1 - T2 ) / ( N1 - N2 )
* milli_Tmeas = ( 1000 * T2 ) + c1 * ( Nmeas - N2 )
* milli_Tmeas = ( 1000 * T2 ) + ( c1 * Nmeas ) - ( c1 * N2 )
* Let constant c2 = ( 1000 * T2 ) - ( c1 * N2 )
* milli_Tmeas = c2 + ( c1 * Nmeas )
*/
data - > c1 = 1000 * ( t1 - t2 ) / ( n1 - n2 ) ;
data - > c2 = 1000 * t2 - data - > c1 * n2 ;
2013-08-01 20:33:11 +04:00
/*
* Set the default passive cooling trip point to 20 ° C below the
* maximum die temperature . Can be changed from userspace .
*/
data - > temp_passive = 1000 * ( t2 - 20 ) ;
/*
* The maximum die temperature is t2 , let ' s give 5 ° C cushion
* for noise and possible temperature rise between measurements .
*/
data - > temp_critical = 1000 * ( t2 - 5 ) ;
2013-06-24 10:30:44 +04:00
return 0 ;
}
2013-08-01 20:33:12 +04:00
static irqreturn_t imx_thermal_alarm_irq ( int irq , void * dev )
{
struct imx_thermal_data * data = dev ;
disable_irq_nosync ( irq ) ;
data - > irq_enabled = false ;
return IRQ_WAKE_THREAD ;
}
static irqreturn_t imx_thermal_alarm_irq_thread ( int irq , void * dev )
{
struct imx_thermal_data * data = dev ;
dev_dbg ( & data - > tz - > device , " THERMAL ALARM: T > %lu \n " ,
data - > alarm_temp / 1000 ) ;
thermal_zone_device_update ( data - > tz ) ;
return IRQ_HANDLED ;
}
2013-06-24 10:30:44 +04:00
static int imx_thermal_probe ( struct platform_device * pdev )
{
struct imx_thermal_data * data ;
struct cpumask clip_cpus ;
struct regmap * map ;
2013-08-01 20:33:12 +04:00
int measure_freq ;
2013-06-24 10:30:44 +04:00
int ret ;
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
map = syscon_regmap_lookup_by_phandle ( pdev - > dev . of_node , " fsl,tempmon " ) ;
if ( IS_ERR ( map ) ) {
ret = PTR_ERR ( map ) ;
dev_err ( & pdev - > dev , " failed to get tempmon regmap: %d \n " , ret ) ;
return ret ;
}
data - > tempmon = map ;
2013-08-01 20:33:12 +04:00
data - > irq = platform_get_irq ( pdev , 0 ) ;
if ( data - > irq < 0 )
return data - > irq ;
ret = devm_request_threaded_irq ( & pdev - > dev , data - > irq ,
imx_thermal_alarm_irq , imx_thermal_alarm_irq_thread ,
0 , " imx_thermal " , data ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to request alarm irq: %d \n " , ret ) ;
return ret ;
}
2013-06-24 10:30:44 +04:00
platform_set_drvdata ( pdev , data ) ;
ret = imx_get_sensor_data ( pdev ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to get sensor data \n " ) ;
return ret ;
}
/* Make sure sensor is in known good state for measurements */
regmap_write ( map , TEMPSENSE0 + REG_CLR , TEMPSENSE0_POWER_DOWN ) ;
regmap_write ( map , TEMPSENSE0 + REG_CLR , TEMPSENSE0_MEASURE_TEMP ) ;
regmap_write ( map , TEMPSENSE1 + REG_CLR , TEMPSENSE1_MEASURE_FREQ ) ;
regmap_write ( map , MISC0 + REG_SET , MISC0_REFTOP_SELBIASOFF ) ;
regmap_write ( map , TEMPSENSE0 + REG_SET , TEMPSENSE0_POWER_DOWN ) ;
cpumask_set_cpu ( 0 , & clip_cpus ) ;
data - > cdev = cpufreq_cooling_register ( & clip_cpus ) ;
if ( IS_ERR ( data - > cdev ) ) {
ret = PTR_ERR ( data - > cdev ) ;
dev_err ( & pdev - > dev ,
" failed to register cpufreq cooling device: %d \n " , ret ) ;
return ret ;
}
data - > tz = thermal_zone_device_register ( " imx_thermal_zone " ,
2013-08-01 20:33:11 +04:00
IMX_TRIP_NUM ,
BIT ( IMX_TRIP_PASSIVE ) , data ,
2013-06-24 10:30:44 +04:00
& imx_tz_ops , NULL ,
IMX_PASSIVE_DELAY ,
IMX_POLLING_DELAY ) ;
if ( IS_ERR ( data - > tz ) ) {
ret = PTR_ERR ( data - > tz ) ;
dev_err ( & pdev - > dev ,
" failed to register thermal zone device %d \n " , ret ) ;
cpufreq_cooling_unregister ( data - > cdev ) ;
return ret ;
}
2013-08-01 20:33:12 +04:00
/* Enable measurements at ~ 10 Hz */
regmap_write ( map , TEMPSENSE1 + REG_CLR , TEMPSENSE1_MEASURE_FREQ ) ;
measure_freq = DIV_ROUND_UP ( 32768 , 10 ) ; /* 10 Hz */
regmap_write ( map , TEMPSENSE1 + REG_SET , measure_freq ) ;
imx_set_alarm_temp ( data , data - > temp_passive ) ;
regmap_write ( map , TEMPSENSE0 + REG_CLR , TEMPSENSE0_POWER_DOWN ) ;
regmap_write ( map , TEMPSENSE0 + REG_SET , TEMPSENSE0_MEASURE_TEMP ) ;
data - > irq_enabled = true ;
2013-06-24 10:30:44 +04:00
data - > mode = THERMAL_DEVICE_ENABLED ;
return 0 ;
}
static int imx_thermal_remove ( struct platform_device * pdev )
{
struct imx_thermal_data * data = platform_get_drvdata ( pdev ) ;
2013-08-01 20:33:12 +04:00
struct regmap * map = data - > tempmon ;
/* Disable measurements */
regmap_write ( map , TEMPSENSE0 + REG_SET , TEMPSENSE0_POWER_DOWN ) ;
2013-06-24 10:30:44 +04:00
thermal_zone_device_unregister ( data - > tz ) ;
cpufreq_cooling_unregister ( data - > cdev ) ;
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int imx_thermal_suspend ( struct device * dev )
{
struct imx_thermal_data * data = dev_get_drvdata ( dev ) ;
struct regmap * map = data - > tempmon ;
u32 val ;
regmap_read ( map , TEMPSENSE0 , & val ) ;
if ( ( val & TEMPSENSE0_POWER_DOWN ) = = 0 ) {
/*
* If a measurement is taking place , wait for a long enough
* time for it to finish , and then check again . If it still
* does not finish , something must go wrong .
*/
udelay ( 50 ) ;
regmap_read ( map , TEMPSENSE0 , & val ) ;
if ( ( val & TEMPSENSE0_POWER_DOWN ) = = 0 )
return - ETIMEDOUT ;
}
return 0 ;
}
static int imx_thermal_resume ( struct device * dev )
{
/* Nothing to do for now */
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( imx_thermal_pm_ops ,
imx_thermal_suspend , imx_thermal_resume ) ;
static const struct of_device_id of_imx_thermal_match [ ] = {
{ . compatible = " fsl,imx6q-tempmon " , } ,
{ /* end */ }
} ;
static struct platform_driver imx_thermal = {
. driver = {
. name = " imx_thermal " ,
. owner = THIS_MODULE ,
. pm = & imx_thermal_pm_ops ,
. of_match_table = of_imx_thermal_match ,
} ,
. probe = imx_thermal_probe ,
. remove = imx_thermal_remove ,
} ;
module_platform_driver ( imx_thermal ) ;
MODULE_AUTHOR ( " Freescale Semiconductor, Inc. " ) ;
MODULE_DESCRIPTION ( " Thermal driver for Freescale i.MX SoCs " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:imx-thermal " ) ;