blob: 2fb75e9d917642f127a1cede7bc607eb8a91c5ea [file] [log] [blame]
/*
*
* MNH Thermal Driver
* Copyright (c) 2016, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
*/
/* #define DEBUG */
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/thermal.h>
#include <linux/timer.h>
#include "mnh-efuse.h"
#include "mnh-hwio.h"
#include "mnh-hwio-bases.h"
#include "mnh-hwio-scu.h"
#include "mnh-sm.h"
#include "mnh-thermal.h"
#define DEVICE_NAME "mnh_thermal"
/* PVT operation mode configuration */
static struct mnh_thermal_op_param mnh_op_mode_table[] = {
{0, 0}, /* temperature: vsample b0, psample b00 */
{1, 0}, /* voltage: vsample b1, psample b00 */
{0, 1}, /* LVT: vsample b0, psample b01 */
{0, 2}, /* HVT: vsample b0, psample b10 */
{0, 3} /* SVT: vsample b0, psample b11 */
};
/* PVT dts trim efuse addresses */
static const struct mnh_efuse_addr mnh_thermal_trim_addr[] = {
/* id0, ipu1 */
{
.row1 = 2,
.row2 = 3,
.bit_high = 9,
.bit_low = 0,
},
/* id1, ipu2 */
{
.row1 = 2,
.row2 = 3,
.bit_high = 19,
.bit_low = 10,
},
/* id2, cpu */
{
.row1 = 2,
.row2 = 3,
.bit_high = 29,
.bit_low = 20,
},
/* id3, lpddr */
{
.row1 = 0,
.row2 = 1,
.bit_high = 9,
.bit_low = 0,
}
};
/**
* This checks mnh sm state to see mnh is out of reset
* Return: 0 if mnh is out of reset, an error code otherwise.
*/
static int check_mnh_hw_init(void)
{
int state = mnh_sm_get_state();
if ((state == MNH_STATE_OFF) || (state == MNH_STATE_SUSPEND))
return -EIO;
return 0;
}
static void config_dts_trim(struct mnh_thermal_device *mnh_dev)
{
uint32_t i, value;
uint32_t slope, offset;
for (i = 0; i < MNH_NUM_PVT_SENSORS; i++) {
value = mnh_efuse_read(&mnh_thermal_trim_addr[i]);
/* Make sure code is 10bit */
value = value & 0x3ff;
/* Calculate slope and offset */
slope = ((unsigned)value >> API_BITS_OFFSET) &
API_BITS_SLOPE_MASK;
if (slope >= 16)
slope = slope - 32;
offset = value & API_BITS_OFFSET_MASK;
if (offset >= 16)
offset = offset - 32;
dev_dbg(mnh_dev->dev, "%s: pvt%d efuse:0x%x, s:%d, o:%d\n",
__func__, i, value, slope, offset);
mnh_dev->sensors[i]->slope = slope;
mnh_dev->sensors[i]->offset = offset;
}
}
/**
* Configure 1.2MHz clock to PVT sensor and wait for 2usecs
* until the clock is ticking if enabled
*/
static void config_pvt_clk_en(struct mnh_thermal_device *mnh_dev, bool clk_en)
{
dev_dbg(mnh_dev->dev, "%s: enable %d\n", __func__, clk_en);
HW_OUTf(mnh_dev->regs, SCU, PERIPH_CLK_CTRL, PVT_CLKEN, clk_en);
udelay(2);
}
/**
* Check pvt clock is enabled
* Return: 0 if pvt clk is off, 1 if pvt clk is on
*/
static uint32_t read_pvt_clk_en(struct mnh_thermal_device *mnh_dev)
{
uint32_t val = HW_INf(mnh_dev->regs, SCU, PERIPH_CLK_CTRL, PVT_CLKEN);
return val;
}
/*
* Caculate PVT_DATA output to millicelsius.
*
* Step #1 : Calculate Code to DegC using ideal equation.
* DegC= API_Poly_N4 * Code^4 + API_Poly_N3 * Code^3 + API_Poly_N2 * Code^2
* + API_Poly_N1 * Code^1 + API_Poly_N0
* Step #2 : Calculate Error using slope and offset.
* Error= Slope * DegC + Offset
* Step #3 : Calculate Final_temp by removing the above error from DegC
* Final_Temp = DegC - Error
*
* Note: kernel doesn't support floaing point calculation, so convert all the
* predefined floating number to 64bit number. All variables and predefined
* constants with an underscore "_" carry an implicit E-15 factor.
*/
static int calculate_temp(struct mnh_thermal_device *mnh_dev, int code,
int slope, int offset)
{
long n1_, n2_, n3_, n4_, n0_, n01234_, final_temp_, error_;
int temp_md;
dev_dbg(mnh_dev->dev, "%s: pvt data:%d, slope:%d, offset:%d\n",
__func__, code, slope, offset);
/* Make sure code is 10bit */
code = code & 0x3FF;
/* Calculate degree Celcius value
* n4: 55bits
* n3: 57bits
* n2: 55bits
* n1: 56bits
* n0: 59bits
*/
n4_ = (long)code*code*code*code * API_POLY_N4;
n3_ = (long)code*code*code * API_POLY_N3 * N3_E15_MULTIPLIER;
n2_ = (long)code*code * API_POLY_N2 * N2_E15_MULTIPLIER;
n1_ = (long)code * API_POLY_N1 * N1_E15_MULTIPLIER;
n0_ = API_POLY_N0 * N0_E15_MULTIPLIER;
n01234_ = n4_ + n3_ + n2_ + n1_ + n0_;
/* slope = trim_slope * API_RES_SLOPE
* offset = trim_offset * API_RES_OFFSET
* error = slope * degC + offset
* n01234_ is 59bit, max slope is 5bit, which may overflow
* when multiplied without first down-scaling.
*/
error_ = n01234_ / RES_SLOPE_DIVIDER * slope * API_RES_SLOPE
+ (long)offset * RES_OFFSET_E15_MULTIPLIER * API_RES_OFFSET;
final_temp_ = n01234_ - error_;
temp_md = (final_temp_ + N12_ROUNDING_NUM) / N12_DIVIDER;
dev_dbg(mnh_dev->dev, "%s: deg:%ld, err:%ld, temp:%ld\n",
__func__, n01234_, error_, final_temp_);
return temp_md;
}
/* Read raw temperature data from sensors
* Enable 1.2MHz Clock to PVT Sensor
* Wait for 2 msecs for the clock to PVT sensor to start ticking
* Program VSAMPLE/PSAMPLE
* Enable PVT sensor access
* Wait for conversion cycle time or PVT_DATA.DATAVALID or
* wait for SCU interrupt
* Read PVT_DATA[x].DATA_OUT[9:0]
* Disable PVT sensor access
* Reset PVT_DATA.DATAVALID
* Disable 1.2MHz clock to PVT sensor
*/
static int mnh_thermal_get_data(void *data, int *data_out)
{
struct mnh_thermal_sensor *sensor = (struct mnh_thermal_sensor *)data;
struct mnh_thermal_device *mnh_dev = sensor->mnh_dev;
u32 val = 0;
unsigned long timeout = jiffies + msecs_to_jiffies(500);
u32 op_mode, psample, vsample;
uint32_t mnh_scu_base = mnh_dev->regs;
uint32_t id = sensor->id;
op_mode = THERMAL_OPMODE_TEMP;
psample = mnh_op_mode_table[op_mode].psample;
vsample = mnh_op_mode_table[op_mode].vsample;
dev_dbg(mnh_dev->dev, "%s: pvt op:%d, vs:%d, ps:%d\n",
__func__, op_mode, vsample, psample);
/* Enable PVT clock */
if (!read_pvt_clk_en(mnh_dev))
config_pvt_clk_en(mnh_dev, 1);
/* Program VSAMPLE/PSAMPLE for temperature evaulation */
HW_OUTxf(mnh_scu_base, SCU, PVT_CONTROL, id, PSAMPLE, psample);
HW_OUTxf(mnh_scu_base, SCU, PVT_CONTROL, id, VSAMPLE, vsample);
/* Enable PVT sensor access */
HW_OUTxf(mnh_scu_base, SCU, PVT_CONTROL, id, ENA, 1);
/* Wait until PVT data conversion is finished in
* temperature and voltage mode.
* Conversion time: 376 PVT_SENSOR_CLK cycles
*/
do {
val = HW_INxf(mnh_scu_base, SCU, PVT_DATA, id, DATAVALID);
if (val == 1)
break;
udelay(mnh_dev->wait_time_us);
} while (time_before(jiffies, timeout));
/* Read DATA_OUT from sensor */
val = HW_INxf(mnh_scu_base, SCU, PVT_DATA, id, DATA);
/* Disable PVT sensor access */
HW_OUTxf(mnh_scu_base, SCU, PVT_CONTROL, id, ENA, 0);
/* Reset DATAVALID bit */
HW_OUTxf(mnh_scu_base, SCU, PVT_DATA, id, DATAVALID, 1);
*data_out = val;
return 0;
}
/*
* This will read raw_temp_code and convert to DecC temperature.
*/
static int mnh_thermal_get_temp(void *data, int *temp_out)
{
struct mnh_thermal_sensor *sensor = (struct mnh_thermal_sensor *)data;
struct mnh_thermal_device *mnh_dev = sensor->mnh_dev;
int temp_raw = 0;
uint32_t id = sensor->id;
if (check_mnh_hw_init())
return -EIO;
/* read dts trim data if it is not configured yet */
if (!mnh_dev->mnh_trim_init_done) {
config_dts_trim(mnh_dev);
mnh_dev->mnh_trim_init_done = 1;
}
/* Get raw temp code */
mnh_thermal_get_data(data, &temp_raw);
/* Convert raw code to milli DecC */
*temp_out = calculate_temp(mnh_dev, temp_raw,
mnh_dev->sensors[id]->slope, mnh_dev->sensors[id]->offset);
return 0;
}
static int mnh_get_cur_state(struct thermal_cooling_device *cooling_dev,
unsigned long *state)
{
struct mnh_thermal_device *mnh_dev = cooling_dev->devdata;
if (!mnh_dev)
return -EINVAL;
*state = mnh_dev->throttle_state;
return 0;
}
static int mnh_get_max_state(struct thermal_cooling_device *cooling_dev,
unsigned long *state)
{
struct mnh_thermal_device *mnh_dev = cooling_dev->devdata;
if (!mnh_dev)
return -EINVAL;
*state = MNH_MAX_THROTTLE_STATE;
return 0;
}
static int mnh_set_cur_state(struct thermal_cooling_device *cooling_dev,
unsigned long state)
{
struct mnh_thermal_device *mnh_dev = cooling_dev->devdata;
if (!mnh_dev)
return -EINVAL;
if (state > MNH_MAX_THROTTLE_STATE)
state = MNH_MAX_THROTTLE_STATE;
mnh_dev->throttle_state = state;
return 0;
}
static const struct thermal_zone_of_device_ops mnh_of_thermal_ops = {
.get_temp = mnh_thermal_get_temp,
};
static const struct thermal_cooling_device_ops mnh_cooling_ops = {
.get_max_state = mnh_get_max_state,
.get_cur_state = mnh_get_cur_state,
.set_cur_state = mnh_set_cur_state,
};
static int mnh_thermal_probe(struct platform_device *pdev)
{
struct mnh_thermal_device *mnh_dev;
unsigned int i;
int err;
dev_dbg(&pdev->dev, "%s: enter\n", __func__);
mnh_dev = devm_kzalloc(&pdev->dev, sizeof(*mnh_dev),
GFP_KERNEL);
if (!mnh_dev)
return -ENOMEM;
mnh_dev->pdev = pdev;
mnh_dev->dev = &pdev->dev;
mnh_dev->regs = HWIO_SCU_BASE_ADDR;
mnh_dev->wait_time_us = PVT_WAIT_US(MNH_PRECISION);
platform_set_drvdata(pdev, mnh_dev);
/* Register cooling device */
mnh_dev->cooling_dev = thermal_of_cooling_device_register(
dev_of_node(mnh_dev->dev), "mnh", mnh_dev,
&mnh_cooling_ops);
if (IS_ERR(mnh_dev->cooling_dev)) {
err = PTR_ERR(mnh_dev->cooling_dev);
dev_err(mnh_dev->dev, "%s: failed to register cooling device: %d\n",
__func__, err);
return err;
}
/* Initialize thermctl sensors */
for (i = 0; i < ARRAY_SIZE(mnh_dev->sensors); ++i) {
struct mnh_thermal_sensor *sensor =
devm_kzalloc(&pdev->dev, sizeof(*sensor),
GFP_KERNEL);
sensor->mnh_dev = mnh_dev;
sensor->id = i;
sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev, i,
sensor, &mnh_of_thermal_ops);
if (IS_ERR(sensor->tzd)) {
err = PTR_ERR(sensor->tzd);
dev_err(mnh_dev->dev, "%s: failed to register sensor: %d\n",
__func__, err);
goto unregister_sensors;
}
mnh_dev->sensors[i] = sensor;
}
dev_info(&pdev->dev, "%s: initialized\n", __func__);
return 0;
unregister_sensors:
while (i--)
thermal_zone_of_sensor_unregister(&pdev->dev,
mnh_dev->sensors[i]->tzd);
thermal_cooling_device_unregister(mnh_dev->cooling_dev);
return err;
}
static int mnh_thermal_remove(struct platform_device *pdev)
{
struct mnh_thermal_device *mnh_dev = platform_get_drvdata(pdev);
unsigned int i;
for (i = 0; i < ARRAY_SIZE(mnh_dev->sensors); ++i) {
thermal_zone_of_sensor_unregister(&pdev->dev,
mnh_dev->sensors[i]->tzd);
}
thermal_cooling_device_unregister(mnh_dev->cooling_dev);
return 0;
}
/*
* of_device_id structure
*/
static const struct of_device_id mnh_thermal_of_match[] = {
{ .compatible = "intel,mnh_thermal" },
{ }
};
MODULE_DEVICE_TABLE(of, mnh_thermal_of_match);
/*
* Platform driver structure
*/
static struct platform_driver mnh_thermal_driver = {
.probe = mnh_thermal_probe,
.remove = mnh_thermal_remove,
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
.of_match_table = mnh_thermal_of_match,
},
};
module_platform_driver(mnh_thermal_driver);
MODULE_DESCRIPTION("MonetteHill Thermal Driver");
MODULE_LICENSE("GPL");