2015-04-15 05:08:15 +03:00
/*
* intel_quark_dts_thermal . c
*
* This file is provided under a dual BSD / GPLv2 license . When using or
* redistributing this file , you may do so under either license .
*
* GPL LICENSE SUMMARY
*
* Copyright ( c ) 2015 Intel Corporation .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation .
*
* 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 .
*
* Contact Information :
* Ong Boon Leong < boon . leong . ong @ intel . com >
* Intel Malaysia , Penang
*
* BSD LICENSE
*
* Copyright ( c ) 2015 Intel Corporation .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
*
* * Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* * Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in
* the documentation and / or other materials provided with the
* distribution .
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* " AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT
* LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* Quark DTS thermal driver is implemented by referencing
* intel_soc_dts_thermal . c .
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/thermal.h>
# include <asm/cpu_device_id.h>
# include <asm/iosf_mbi.h>
# define X86_FAMILY_QUARK 0x5
# define X86_MODEL_QUARK_X1000 0x9
/* DTS reset is programmed via QRK_MBI_UNIT_SOC */
# define QRK_DTS_REG_OFFSET_RESET 0x34
# define QRK_DTS_RESET_BIT BIT(0)
/* DTS enable is programmed via QRK_MBI_UNIT_RMU */
# define QRK_DTS_REG_OFFSET_ENABLE 0xB0
# define QRK_DTS_ENABLE_BIT BIT(15)
/* Temperature Register is read via QRK_MBI_UNIT_RMU */
# define QRK_DTS_REG_OFFSET_TEMP 0xB1
# define QRK_DTS_MASK_TEMP 0xFF
# define QRK_DTS_OFFSET_TEMP 0
# define QRK_DTS_OFFSET_REL_TEMP 16
# define QRK_DTS_TEMP_BASE 50
/* Programmable Trip Point Register is configured via QRK_MBI_UNIT_RMU */
# define QRK_DTS_REG_OFFSET_PTPS 0xB2
# define QRK_DTS_MASK_TP_THRES 0xFF
# define QRK_DTS_SHIFT_TP 8
# define QRK_DTS_ID_TP_CRITICAL 0
# define QRK_DTS_SAFE_TP_THRES 105
/* Thermal Sensor Register Lock */
# define QRK_DTS_REG_OFFSET_LOCK 0x71
# define QRK_DTS_LOCK_BIT BIT(5)
/* Quark DTS has 2 trip points: hot & catastrophic */
# define QRK_MAX_DTS_TRIPS 2
/* If DTS not locked, all trip points are configurable */
# define QRK_DTS_WR_MASK_SET 0x3
/* If DTS locked, all trip points are not configurable */
# define QRK_DTS_WR_MASK_CLR 0
# define DEFAULT_POLL_DELAY 2000
struct soc_sensor_entry {
bool locked ;
u32 store_ptps ;
u32 store_dts_enable ;
enum thermal_device_mode mode ;
struct thermal_zone_device * tzone ;
} ;
static struct soc_sensor_entry * soc_dts ;
static int polling_delay = DEFAULT_POLL_DELAY ;
module_param ( polling_delay , int , 0644 ) ;
MODULE_PARM_DESC ( polling_delay ,
" Polling interval for checking trip points (in milliseconds) " ) ;
static DEFINE_MUTEX ( dts_update_mutex ) ;
static int soc_dts_enable ( struct thermal_zone_device * tzd )
{
u32 out ;
struct soc_sensor_entry * aux_entry = tzd - > devdata ;
int ret ;
2015-11-11 20:59:29 +03:00
ret = iosf_mbi_read ( QRK_MBI_UNIT_RMU , MBI_REG_READ ,
QRK_DTS_REG_OFFSET_ENABLE , & out ) ;
2015-04-15 05:08:15 +03:00
if ( ret )
return ret ;
if ( out & QRK_DTS_ENABLE_BIT ) {
aux_entry - > mode = THERMAL_DEVICE_ENABLED ;
return 0 ;
}
if ( ! aux_entry - > locked ) {
out | = QRK_DTS_ENABLE_BIT ;
2015-11-11 20:59:29 +03:00
ret = iosf_mbi_write ( QRK_MBI_UNIT_RMU , MBI_REG_WRITE ,
QRK_DTS_REG_OFFSET_ENABLE , out ) ;
2015-04-15 05:08:15 +03:00
if ( ret )
return ret ;
aux_entry - > mode = THERMAL_DEVICE_ENABLED ;
} else {
aux_entry - > mode = THERMAL_DEVICE_DISABLED ;
pr_info ( " DTS is locked. Cannot enable DTS \n " ) ;
ret = - EPERM ;
}
return ret ;
}
static int soc_dts_disable ( struct thermal_zone_device * tzd )
{
u32 out ;
struct soc_sensor_entry * aux_entry = tzd - > devdata ;
int ret ;
2015-11-11 20:59:29 +03:00
ret = iosf_mbi_read ( QRK_MBI_UNIT_RMU , MBI_REG_READ ,
QRK_DTS_REG_OFFSET_ENABLE , & out ) ;
2015-04-15 05:08:15 +03:00
if ( ret )
return ret ;
if ( ! ( out & QRK_DTS_ENABLE_BIT ) ) {
aux_entry - > mode = THERMAL_DEVICE_DISABLED ;
return 0 ;
}
if ( ! aux_entry - > locked ) {
out & = ~ QRK_DTS_ENABLE_BIT ;
2015-11-11 20:59:29 +03:00
ret = iosf_mbi_write ( QRK_MBI_UNIT_RMU , MBI_REG_WRITE ,
QRK_DTS_REG_OFFSET_ENABLE , out ) ;
2015-04-15 05:08:15 +03:00
if ( ret )
return ret ;
aux_entry - > mode = THERMAL_DEVICE_DISABLED ;
} else {
aux_entry - > mode = THERMAL_DEVICE_ENABLED ;
pr_info ( " DTS is locked. Cannot disable DTS \n " ) ;
ret = - EPERM ;
}
return ret ;
}
2015-07-24 09:12:54 +03:00
static int _get_trip_temp ( int trip , int * temp )
2015-04-15 05:08:15 +03:00
{
int status ;
u32 out ;
mutex_lock ( & dts_update_mutex ) ;
2015-11-11 20:59:29 +03:00
status = iosf_mbi_read ( QRK_MBI_UNIT_RMU , MBI_REG_READ ,
QRK_DTS_REG_OFFSET_PTPS , & out ) ;
2015-04-15 05:08:15 +03:00
mutex_unlock ( & dts_update_mutex ) ;
if ( status )
return status ;
/*
* Thermal Sensor Programmable Trip Point Register has 8 - bit
* fields for critical ( catastrophic ) and hot set trip point
* thresholds . The threshold value is always offset by its
* temperature base ( 50 degree Celsius ) .
*/
* temp = ( out > > ( trip * QRK_DTS_SHIFT_TP ) ) & QRK_DTS_MASK_TP_THRES ;
* temp - = QRK_DTS_TEMP_BASE ;
return 0 ;
}
static inline int sys_get_trip_temp ( struct thermal_zone_device * tzd ,
2015-07-24 09:12:54 +03:00
int trip , int * temp )
2015-04-15 05:08:15 +03:00
{
return _get_trip_temp ( trip , temp ) ;
}
2015-07-24 09:12:54 +03:00
static inline int sys_get_crit_temp ( struct thermal_zone_device * tzd , int * temp )
2015-04-15 05:08:15 +03:00
{
return _get_trip_temp ( QRK_DTS_ID_TP_CRITICAL , temp ) ;
}
static int update_trip_temp ( struct soc_sensor_entry * aux_entry ,
2015-07-24 09:12:54 +03:00
int trip , int temp )
2015-04-15 05:08:15 +03:00
{
u32 out ;
u32 temp_out ;
u32 store_ptps ;
int ret ;
mutex_lock ( & dts_update_mutex ) ;
if ( aux_entry - > locked ) {
ret = - EPERM ;
goto failed ;
}
2015-11-11 20:59:29 +03:00
ret = iosf_mbi_read ( QRK_MBI_UNIT_RMU , MBI_REG_READ ,
QRK_DTS_REG_OFFSET_PTPS , & store_ptps ) ;
2015-04-15 05:08:15 +03:00
if ( ret )
goto failed ;
/*
* Protection against unsafe trip point thresdhold value .
* As Quark X1000 data - sheet does not provide any recommendation
* regarding the safe trip point threshold value to use , we choose
* the safe value according to the threshold value set by UEFI BIOS .
*/
if ( temp > QRK_DTS_SAFE_TP_THRES )
temp = QRK_DTS_SAFE_TP_THRES ;
/*
* Thermal Sensor Programmable Trip Point Register has 8 - bit
* fields for critical ( catastrophic ) and hot set trip point
* thresholds . The threshold value is always offset by its
* temperature base ( 50 degree Celsius ) .
*/
temp_out = temp + QRK_DTS_TEMP_BASE ;
out = ( store_ptps & ~ ( QRK_DTS_MASK_TP_THRES < <
( trip * QRK_DTS_SHIFT_TP ) ) ) ;
out | = ( temp_out & QRK_DTS_MASK_TP_THRES ) < <
( trip * QRK_DTS_SHIFT_TP ) ;
2015-11-11 20:59:29 +03:00
ret = iosf_mbi_write ( QRK_MBI_UNIT_RMU , MBI_REG_WRITE ,
QRK_DTS_REG_OFFSET_PTPS , out ) ;
2015-04-15 05:08:15 +03:00
failed :
mutex_unlock ( & dts_update_mutex ) ;
return ret ;
}
static inline int sys_set_trip_temp ( struct thermal_zone_device * tzd , int trip ,
2015-07-24 09:12:54 +03:00
int temp )
2015-04-15 05:08:15 +03:00
{
return update_trip_temp ( tzd - > devdata , trip , temp ) ;
}
static int sys_get_trip_type ( struct thermal_zone_device * thermal ,
int trip , enum thermal_trip_type * type )
{
if ( trip )
* type = THERMAL_TRIP_HOT ;
else
* type = THERMAL_TRIP_CRITICAL ;
return 0 ;
}
static int sys_get_curr_temp ( struct thermal_zone_device * tzd ,
2015-07-24 09:12:54 +03:00
int * temp )
2015-04-15 05:08:15 +03:00
{
u32 out ;
int ret ;
mutex_lock ( & dts_update_mutex ) ;
2015-11-11 20:59:29 +03:00
ret = iosf_mbi_read ( QRK_MBI_UNIT_RMU , MBI_REG_READ ,
QRK_DTS_REG_OFFSET_TEMP , & out ) ;
2015-04-15 05:08:15 +03:00
mutex_unlock ( & dts_update_mutex ) ;
if ( ret )
return ret ;
/*
* Thermal Sensor Temperature Register has 8 - bit field
* for temperature value ( offset by temperature base
* 50 degree Celsius ) .
*/
out = ( out > > QRK_DTS_OFFSET_TEMP ) & QRK_DTS_MASK_TEMP ;
* temp = out - QRK_DTS_TEMP_BASE ;
return 0 ;
}
static int sys_get_mode ( struct thermal_zone_device * tzd ,
enum thermal_device_mode * mode )
{
struct soc_sensor_entry * aux_entry = tzd - > devdata ;
* mode = aux_entry - > mode ;
return 0 ;
}
static int sys_set_mode ( struct thermal_zone_device * tzd ,
enum thermal_device_mode mode )
{
int ret ;
mutex_lock ( & dts_update_mutex ) ;
if ( mode = = THERMAL_DEVICE_ENABLED )
ret = soc_dts_enable ( tzd ) ;
else
ret = soc_dts_disable ( tzd ) ;
mutex_unlock ( & dts_update_mutex ) ;
return ret ;
}
static struct thermal_zone_device_ops tzone_ops = {
. get_temp = sys_get_curr_temp ,
. get_trip_temp = sys_get_trip_temp ,
. get_trip_type = sys_get_trip_type ,
. set_trip_temp = sys_set_trip_temp ,
. get_crit_temp = sys_get_crit_temp ,
. get_mode = sys_get_mode ,
. set_mode = sys_set_mode ,
} ;
static void free_soc_dts ( struct soc_sensor_entry * aux_entry )
{
if ( aux_entry ) {
if ( ! aux_entry - > locked ) {
mutex_lock ( & dts_update_mutex ) ;
2015-11-11 20:59:29 +03:00
iosf_mbi_write ( QRK_MBI_UNIT_RMU , MBI_REG_WRITE ,
QRK_DTS_REG_OFFSET_ENABLE ,
aux_entry - > store_dts_enable ) ;
2015-04-15 05:08:15 +03:00
2015-11-11 20:59:29 +03:00
iosf_mbi_write ( QRK_MBI_UNIT_RMU , MBI_REG_WRITE ,
QRK_DTS_REG_OFFSET_PTPS ,
aux_entry - > store_ptps ) ;
2015-04-15 05:08:15 +03:00
mutex_unlock ( & dts_update_mutex ) ;
}
thermal_zone_device_unregister ( aux_entry - > tzone ) ;
kfree ( aux_entry ) ;
}
}
static struct soc_sensor_entry * alloc_soc_dts ( void )
{
struct soc_sensor_entry * aux_entry ;
int err ;
u32 out ;
int wr_mask ;
aux_entry = kzalloc ( sizeof ( * aux_entry ) , GFP_KERNEL ) ;
if ( ! aux_entry ) {
err = - ENOMEM ;
return ERR_PTR ( - ENOMEM ) ;
}
/* Check if DTS register is locked */
2015-11-11 20:59:29 +03:00
err = iosf_mbi_read ( QRK_MBI_UNIT_RMU , MBI_REG_READ ,
QRK_DTS_REG_OFFSET_LOCK , & out ) ;
2015-04-15 05:08:15 +03:00
if ( err )
goto err_ret ;
if ( out & QRK_DTS_LOCK_BIT ) {
aux_entry - > locked = true ;
wr_mask = QRK_DTS_WR_MASK_CLR ;
} else {
aux_entry - > locked = false ;
wr_mask = QRK_DTS_WR_MASK_SET ;
}
/* Store DTS default state if DTS registers are not locked */
if ( ! aux_entry - > locked ) {
/* Store DTS default enable for restore on exit */
2015-11-11 20:59:29 +03:00
err = iosf_mbi_read ( QRK_MBI_UNIT_RMU , MBI_REG_READ ,
QRK_DTS_REG_OFFSET_ENABLE ,
& aux_entry - > store_dts_enable ) ;
2015-04-15 05:08:15 +03:00
if ( err )
goto err_ret ;
/* Store DTS default PTPS register for restore on exit */
2015-11-11 20:59:29 +03:00
err = iosf_mbi_read ( QRK_MBI_UNIT_RMU , MBI_REG_READ ,
QRK_DTS_REG_OFFSET_PTPS ,
& aux_entry - > store_ptps ) ;
2015-04-15 05:08:15 +03:00
if ( err )
goto err_ret ;
}
aux_entry - > tzone = thermal_zone_device_register ( " quark_dts " ,
QRK_MAX_DTS_TRIPS ,
wr_mask ,
aux_entry , & tzone_ops , NULL , 0 , polling_delay ) ;
if ( IS_ERR ( aux_entry - > tzone ) ) {
err = PTR_ERR ( aux_entry - > tzone ) ;
goto err_ret ;
}
mutex_lock ( & dts_update_mutex ) ;
err = soc_dts_enable ( aux_entry - > tzone ) ;
mutex_unlock ( & dts_update_mutex ) ;
if ( err )
goto err_aux_status ;
return aux_entry ;
err_aux_status :
thermal_zone_device_unregister ( aux_entry - > tzone ) ;
err_ret :
kfree ( aux_entry ) ;
return ERR_PTR ( err ) ;
}
static const struct x86_cpu_id qrk_thermal_ids [ ] __initconst = {
{ X86_VENDOR_INTEL , X86_FAMILY_QUARK , X86_MODEL_QUARK_X1000 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( x86cpu , qrk_thermal_ids ) ;
static int __init intel_quark_thermal_init ( void )
{
int err = 0 ;
if ( ! x86_match_cpu ( qrk_thermal_ids ) | | ! iosf_mbi_available ( ) )
return - ENODEV ;
soc_dts = alloc_soc_dts ( ) ;
if ( IS_ERR ( soc_dts ) ) {
err = PTR_ERR ( soc_dts ) ;
goto err_free ;
}
return 0 ;
err_free :
free_soc_dts ( soc_dts ) ;
return err ;
}
static void __exit intel_quark_thermal_exit ( void )
{
free_soc_dts ( soc_dts ) ;
}
module_init ( intel_quark_thermal_init )
module_exit ( intel_quark_thermal_exit )
MODULE_DESCRIPTION ( " Intel Quark DTS Thermal Driver " ) ;
MODULE_AUTHOR ( " Ong Boon Leong <boon.leong.ong@intel.com> " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;