644 lines
16 KiB
C

/*
* Copyright (c) 2011-2013, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/gfp.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/thermal.h>
#include <linux/platform_data/tegra_thermal.h>
#include <linux/platform_data/tegra_edp.h>
#include <linux/tegra-dvfs.h>
#include <linux/tegra-soc.h>
#include <linux/clk-provider.h>
#include <linux/clk/tegra124-dfll.h>
#include "tegra_soctherm.h"
static const unsigned long t124_default_soctherm_clk_rate = 51000000;
static const unsigned long t124_default_tsensor_clk_rate = 400000;
static const s32 t124_nominal_calib_cp = 25;
static const s32 t124_nominal_calib_ft = 105;
static const struct soctherm_sensor t124_sensor_defaults = {
.tall = 16300,
.tiddq = 1,
.ten_count = 1,
.tsample = 120,
.tsamp_ate = 481,
.pdiv = 8,
.pdiv_ate = 8,
};
static struct soctherm_tsensor_pmu_data t124_pmu_data;
static struct thermal_zone_params t124_soctherm_therm_cpu_tzp = {
.governor_name = "pid_thermal_gov",
};
static bool t124_cpu_edp_trips_done;
static struct soctherm_platform_data t124_soctherm_data = {
.therm = {
[THERM_CPU] = {
.zone_enable = true,
.passive_delay = 1000,
.hotspot_offset = 6000,
.num_trips = 3,
.trips = {
{
.cdev_type = "cpu-balanced",
.trip_temp = 90000,
.trip_type = THERMAL_TRIP_PASSIVE,
.upper = THERMAL_NO_LIMIT,
.lower = THERMAL_NO_LIMIT,
},
{
.cdev_type = "tegra-heavy",
.trip_temp = 100000,
.trip_type = THERMAL_TRIP_HOT,
.upper = THERMAL_NO_LIMIT,
.lower = THERMAL_NO_LIMIT,
},
{
.cdev_type = "tegra-shutdown",
.trip_temp = 103000,
.trip_type = THERMAL_TRIP_CRITICAL,
.upper = THERMAL_NO_LIMIT,
.lower = THERMAL_NO_LIMIT,
},
},
.tzp = &t124_soctherm_therm_cpu_tzp,
},
[THERM_GPU] = {
.zone_enable = true,
.passive_delay = 1000,
.hotspot_offset = 6000,
.num_trips = 3,
.trips = {
{
.cdev_type = "gpu-balanced",
.trip_temp = 89000,
.trip_type = THERMAL_TRIP_PASSIVE,
.upper = THERMAL_NO_LIMIT,
.lower = THERMAL_NO_LIMIT,
},
{
.cdev_type = "tegra-heavy",
.trip_temp = 99000,
.trip_type = THERMAL_TRIP_HOT,
.upper = THERMAL_NO_LIMIT,
.lower = THERMAL_NO_LIMIT,
},
{
.cdev_type = "tegra-shutdown",
.trip_temp = 101000,
.trip_type = THERMAL_TRIP_CRITICAL,
.upper = THERMAL_NO_LIMIT,
.lower = THERMAL_NO_LIMIT,
},
},
.tzp = &t124_soctherm_therm_cpu_tzp,
},
[THERM_PLL] = {
.zone_enable = true,
},
},
.throttle = {
[THROTTLE_HEAVY] = {
.devs = {
[THROTTLE_DEV_CPU] = {
.enable = 1,
},
},
},
},
.tshut_pmu_trip_data = &t124_pmu_data,
};
static struct soctherm_sensor2tsensorcalib t124_tsensor_calib[] = {
[TSENSE_CPU0] = {
.tsensor = 0,
},
[TSENSE_CPU1] = {
.tsensor = 1,
},
[TSENSE_CPU2] = {
.tsensor = 2,
},
[TSENSE_CPU3] = {
.tsensor = 3,
},
[TSENSE_MEM0] = {
.tsensor = 5,
},
[TSENSE_MEM1] = {
.tsensor = 6,
},
[TSENSE_GPU] = {
.tsensor = 4,
},
[TSENSE_PLLX] = {
.tsensor = 7,
},
};
static int t124_fuse_corr_alpha[] = { /* scaled *1000000 */
[TSENSE_CPU0] = 1148300,
[TSENSE_CPU1] = 1126100,
[TSENSE_CPU2] = 1155800,
[TSENSE_CPU3] = 1134900,
[TSENSE_MEM0] = 1062700,
[TSENSE_MEM1] = 1084700,
[TSENSE_GPU] = 1084300,
[TSENSE_PLLX] = 1134500,
};
static int t124_fuse_corr_beta[] = { /* scaled *1000000 */
[TSENSE_CPU0] = -6572300,
[TSENSE_CPU1] = -5794600,
[TSENSE_CPU2] = -7462800,
[TSENSE_CPU3] = -6810800,
[TSENSE_MEM0] = -4463200,
[TSENSE_MEM1] = -5603400,
[TSENSE_GPU] = -5111900,
[TSENSE_PLLX] = -7410700,
};
static struct soctherm_fuse_calib_data t124_calib_data = {
.tsensor_calib = t124_tsensor_calib,
.fuse_corr_alpha = t124_fuse_corr_alpha,
.fuse_corr_beta = t124_fuse_corr_beta,
};
/* fuse registers used in public fuse data read API */
#define T124_FUSE_FT_REV 0x28
#define T124_FUSE_CP_REV 0x90
/* sparse realignment register */
#define T124_FUSE_SPARE_REALIGNMENT_REG_0 0x1fc
/* tsensor8_calib */
#define T124_FUSE_TSENSOR_CALIB_8 0x180
#define T124_FUSE_BASE_CP_SHIFT 0
#define T124_FUSE_BASE_CP_MASK 0x3ff
#define T124_FUSE_BASE_FT_SHIFT 10
#define T124_FUSE_BASE_FT_MASK 0x7ff
#define T124_FUSE_SHIFT_CP_SHIFT 0
#define T124_FUSE_SHIFT_CP_MASK 0x3f
#define T124_FUSE_SHIFT_CP_BITS 6
#define T124_FUSE_SHIFT_FT_SHIFT 21
#define T124_FUSE_SHIFT_FT_MASK 0x1f
#define T124_FUSE_SHIFT_FT_BITS 5
static int t124_tsensor_calib_offset[] = {
[0] = 0x098,
[1] = 0x084,
[2] = 0x088,
[3] = 0x12c,
[4] = 0x154,
[5] = 0x158,
[6] = 0x15c,
[7] = 0x160,
};
static int tegra124_fuse_get_tsensor_calib(int index, u32 *calib)
{
if (index < 0 || index >= ARRAY_SIZE(t124_tsensor_calib_offset))
return -EINVAL;
*calib = tegra30_fuse_readl(t124_tsensor_calib_offset[index]);
return 0;
}
static int fuse_cp_rev_check(void)
{
u32 rev, rev_major, rev_minor;
rev = tegra30_fuse_readl(T124_FUSE_CP_REV);
rev_minor = rev & 0x1f;
rev_major = (rev >> 5) & 0x3f;
/* CP rev < 00.4 is unsupported */
if ((rev_major == 0) && (rev_minor < 4))
return -EINVAL;
return 0;
}
static inline int fuse_ft_rev_check(void)
{
u32 rev, rev_major, rev_minor;
rev = tegra30_fuse_readl(T124_FUSE_FT_REV);
rev_minor = rev & 0x1f;
rev_major = (rev >> 5) & 0x3f;
/* FT rev < 00.5 is unsupported */
if ((rev_major == 0) && (rev_minor < 5))
return -EINVAL;
return 0;
}
static int tegra124_fuse_calib_base_get_cp(u32 *base_cp, s32 *shifted_cp)
{
s32 cp;
u32 val = tegra30_fuse_readl(T124_FUSE_TSENSOR_CALIB_8);
if (fuse_cp_rev_check() || !val)
return -EINVAL;
if (base_cp && shifted_cp) {
*base_cp = (((val) & (T124_FUSE_BASE_CP_MASK
<< T124_FUSE_BASE_CP_SHIFT))
>> T124_FUSE_BASE_CP_SHIFT);
val = tegra30_fuse_readl(T124_FUSE_SPARE_REALIGNMENT_REG_0);
cp = (((val) & (T124_FUSE_SHIFT_CP_MASK
<< T124_FUSE_SHIFT_CP_SHIFT))
>> T124_FUSE_SHIFT_CP_SHIFT);
*shifted_cp = ((s32)(cp)
<< (32 - T124_FUSE_SHIFT_CP_BITS)
>> (32 - T124_FUSE_SHIFT_CP_BITS));
}
return 0;
}
static int tegra124_fuse_calib_base_get_ft(u32 *base_ft, s32 *shifted_ft)
{
s32 ft;
u32 val = tegra30_fuse_readl(T124_FUSE_TSENSOR_CALIB_8);
if (fuse_ft_rev_check() || !val)
return -EINVAL;
if (base_ft && shifted_ft) {
*base_ft = (((val) & (T124_FUSE_BASE_FT_MASK
<< T124_FUSE_BASE_FT_SHIFT))
>> T124_FUSE_BASE_FT_SHIFT);
ft = (((val) & (T124_FUSE_SHIFT_FT_MASK
<< T124_FUSE_SHIFT_FT_SHIFT))
>> T124_FUSE_SHIFT_FT_SHIFT);
*shifted_ft = ((s32)(ft)
<< (32 - T124_FUSE_SHIFT_FT_BITS)
>> (32 - T124_FUSE_SHIFT_FT_BITS));
}
return 0;
}
static int tegra124_soctherm_fuse_read_calib_base(void)
{
s32 calib_cp, calib_ft;
u32 fuse_calib_base_cp, fuse_calib_base_ft;
if (tegra124_fuse_calib_base_get_cp(&fuse_calib_base_cp, &calib_cp) ||
tegra124_fuse_calib_base_get_ft(&fuse_calib_base_ft, &calib_ft)) {
return -EINVAL;
}
t124_calib_data.fuse_calib_base_cp = fuse_calib_base_cp;
t124_calib_data.fuse_calib_base_ft = fuse_calib_base_ft;
/* use HI precision to calculate: use fuse_temp in 0.5C */
t124_calib_data.actual_temp_cp = 2 * t124_nominal_calib_cp + calib_cp;
t124_calib_data.actual_temp_ft = 2 * t124_nominal_calib_ft + calib_ft;
return 0;
}
static int tegra124_soctherm_fuse_get_tsensor_calib(void)
{
u32 value;
int i, sensor, ret;
for (i = 0; i < TSENSE_SIZE; i++) {
sensor = t124_calib_data.tsensor_calib[i].tsensor;
ret = tegra124_fuse_get_tsensor_calib(sensor, &value);
if (ret < 0)
return ret;
t124_calib_data.tsensor_calib[i].calib_val = value;
}
return 0;
}
static int tegra124_soctherm_parse_cdev(struct device_node *soctherm_dn,
char *property_name,
const char **cdev_cap_type,
const char **cdev_floor_type,
struct platform_device **pdev)
{
struct device_node *dn_cdev, *dn;
const __be32 *prop;
int ret = 0;
dn_cdev = NULL;
dn = NULL;
prop = of_get_property(soctherm_dn, property_name, NULL);
if (!prop) {
ret = -EINVAL;
goto error;
}
dn_cdev = of_find_node_by_phandle(be32_to_cpup(prop));
if (!dn_cdev) {
ret = -ENOENT;
goto error;
}
*cdev_cap_type = of_get_property(dn_cdev, "cdev-cap-type", NULL);
*cdev_floor_type = of_get_property(dn_cdev, "cdev-floor-type", NULL);
if (IS_ERR_OR_NULL(*cdev_floor_type) &&
IS_ERR_OR_NULL(*cdev_cap_type)) {
ret = -EINVAL;
goto error;
}
prop = of_get_property(dn_cdev, "act-dev", NULL);
if (!prop) {
ret = -EINVAL;
goto error;
}
dn = of_find_node_by_phandle(be32_to_cpup(prop));
if (!dn) {
ret = -ENOENT;
goto error;
}
*pdev = of_find_device_by_node(dn);
if (!*pdev) {
ret = -ENOENT;
goto error;
}
error:
if (dn)
of_node_put(dn);
if (dn_cdev)
of_node_put(dn_cdev);
return ret;
}
static int tegra124_soctherm_parse_dfll_cdev(struct device_node *soctherm_dn,
struct platform_device *pdev,
struct thermal_trip_info *trips,
int *num_trips)
{
struct platform_device *pdev_cdev;
struct tegra_cooling_device dfll_cdev;
int err, i, num, *trip_temp;
struct clk *cpu_dfll_clk;
const char *cdev_cap_type, *cdev_floor_type;
err = tegra124_soctherm_parse_cdev(soctherm_dn, "dfll-cdev",
&cdev_cap_type,
&cdev_floor_type,
&pdev_cdev);
if (err) {
dev_warn(&pdev->dev, "Can't find dfll cdev node\n");
return 0;
}
cpu_dfll_clk = devm_clk_get(&pdev->dev, "dfllCPU_out");
if (IS_ERR(cpu_dfll_clk)) {
dev_info(&pdev->dev, "DFLL not ready\n");
return -EPROBE_DEFER;
}
/* add floor trips of dfll cdev */
if (IS_ERR_OR_NULL(cdev_floor_type))
goto ignore_dfll_floor_cdev;
dfll_cdev.cdev_type = (char *)cdev_floor_type;
num = tegra124_dfll_count_therm_states(pdev_cdev,
TEGRA_DFLL_THERM_FLOOR);
if (num == 0)
goto ignore_dfll_floor_cdev;
dfll_cdev.trip_temperatures_num = num;
trip_temp = devm_kzalloc(&pdev->dev, sizeof(int) * num, GFP_KERNEL);
if (!trip_temp)
return -ENOMEM;
dfll_cdev.trip_temperatures = trip_temp;
for (i = 0; i < num; i++) {
int temp;
temp = tegra124_dfll_get_therm_state_temp(
pdev_cdev, TEGRA_DFLL_THERM_FLOOR, i);
if (IS_ERR_VALUE(temp)) {
devm_kfree(&pdev->dev, trip_temp);
goto ignore_dfll_floor_cdev;
}
dfll_cdev.trip_temperatures[i] = temp;
}
soctherm_add_trip_points(pdev, trips, num_trips, &dfll_cdev);
ignore_dfll_floor_cdev:
/* add cap trips of dfll cdev */
if (IS_ERR_OR_NULL(cdev_cap_type))
return 0;
dfll_cdev.cdev_type = (char *)cdev_cap_type;
num = tegra124_dfll_count_therm_states(pdev_cdev,
TEGRA_DFLL_THERM_CAP);
if (num == 0)
return 0;
dfll_cdev.trip_temperatures_num = num;
trip_temp = devm_kzalloc(&pdev->dev, sizeof(int) * num, GFP_KERNEL);
if (!trip_temp)
return -ENOMEM;
dfll_cdev.trip_temperatures = trip_temp;
for (i = 0; i < num; i++) {
int temp;
temp = tegra124_dfll_get_therm_state_temp(
pdev_cdev, TEGRA_DFLL_THERM_CAP, i);
if (IS_ERR_VALUE(temp)) {
devm_kfree(&pdev->dev, trip_temp);
return 0;
}
dfll_cdev.trip_temperatures[i] = temp;
}
soctherm_add_trip_points(pdev, trips, num_trips, &dfll_cdev);
return 0;
}
int tegra124_soctherm_init(struct device_node *soctherm_dn)
{
struct platform_device *pdev, *pdev_cdev;
struct soctherm_platform_data *pdata;
struct device_node *dn;
void __iomem *base;
struct thermal_trip_info *trips;
struct tegra_cooling_device *tegra_cdev;
int *num_trips;
const char *cdev_cap_type, *cdev_floor_type;
int err;
pdev = of_find_device_by_node(soctherm_dn);
if (!pdev) {
dev_err(&pdev->dev, "Failed to get soctherm device\n");
return -EINVAL;
}
pdata = &t124_soctherm_data;
platform_set_drvdata(pdev, pdata);
if (!t124_calib_data.fuse_calib_base_cp) {
if (tegra124_soctherm_fuse_read_calib_base() < 0) {
dev_err(&pdev->dev,
"ERROR: Improper CP or FT calib fuse.\n");
return -EINVAL;
}
if (tegra124_soctherm_fuse_get_tsensor_calib() < 0) {
dev_err(&pdev->dev,
"ERROR: Improper tsensor calib fuse.\n");
return -EINVAL;
}
pdata->fuse_calib_data = &t124_calib_data;
}
trips = pdata->therm[THERM_GPU].trips;
num_trips = &pdata->therm[THERM_GPU].num_trips;
/* add trips of gpu_cold */
tegra_cdev = tegra_get_gpu_vmin_cdev();
if (PTR_ERR(tegra_cdev) == -EPROBE_DEFER) {
dev_warn(&pdev->dev, "The gpu cdev is not ready.\n");
return PTR_ERR(tegra_cdev);
} else if (IS_ERR(tegra_cdev)) {
dev_warn(&pdev->dev, "Can't get the gpu cdev, ignore it.\n");
} else {
soctherm_add_trip_points(pdev, trips, num_trips, tegra_cdev);
}
/* add trips of gpu_scaling */
tegra_cdev = tegra_get_gpu_vts_cdev();
if (PTR_ERR(tegra_cdev) == -EPROBE_DEFER) {
dev_warn(&pdev->dev, "The gpu cdev is not ready.\n");
return PTR_ERR(tegra_cdev);
} else if (IS_ERR(tegra_cdev)) {
dev_warn(&pdev->dev, "Can't get the gpu cdev, ignore it.\n");
} else {
soctherm_add_trip_points(pdev, trips, num_trips, tegra_cdev);
}
trips = pdata->therm[THERM_CPU].trips;
num_trips = &pdata->therm[THERM_CPU].num_trips;
/* add trips of core_cold */
tegra_cdev = tegra_dvfs_get_core_vmin_cdev();
if (PTR_ERR(tegra_cdev) == -EPROBE_DEFER) {
dev_warn(&pdev->dev, "The core cdev is not ready.\n");
return PTR_ERR(tegra_cdev);
} else if (IS_ERR(tegra_cdev)) {
dev_warn(&pdev->dev, "Can't get the core cdev, ignore it.\n");
} else {
soctherm_add_trip_points(pdev, trips, num_trips, tegra_cdev);
}
/* add trips of dfll cdev */
err = tegra124_soctherm_parse_dfll_cdev(soctherm_dn, pdev,
trips, num_trips);
if (err == -EPROBE_DEFER)
return err;
/* add trips of cpu edp */
if (!t124_cpu_edp_trips_done) {
err = tegra124_soctherm_parse_cdev(soctherm_dn, "cpu-edp-cdev",
&cdev_cap_type,
&cdev_floor_type,
&pdev_cdev);
if (err || IS_ERR_OR_NULL(cdev_cap_type)) {
dev_warn(&pdev->dev, "Can't find cpu-edp node\n");
goto ignore_cpu_edp;
}
/* edp temperature margin is 7000 */
err = tegra_cpu_edp_init_trips(pdev_cdev,
trips,
num_trips,
(char *)cdev_cap_type,
7000);
if (err == -EPROBE_DEFER)
return err;
else if (err)
goto ignore_cpu_edp;
else
t124_cpu_edp_trips_done = true;
}
ignore_cpu_edp:
/* get soctherm base address */
base = of_iomap(soctherm_dn, 0);
if (!base) {
dev_err(&pdev->dev, "Can't map soctherm registers\n");
WARN_ON(1);
return -EINVAL;
}
pdata->soctherm_base = base;
/* get pmc base address */
dn = of_find_compatible_node(NULL, NULL, "nvidia,tegra124-pmc");
if (!dn) {
dev_err(&pdev->dev, "Failed to find pmc node\n");
WARN_ON(1);
return -EINVAL;
}
base = of_iomap(dn, 0);
if (!base) {
dev_err(&pdev->dev, "Can't map pmc registers\n");
WARN_ON(1);
return -EINVAL;
}
pdata->pmc_base = base;
/* get clk base address */
dn = of_find_compatible_node(NULL, NULL, "nvidia,tegra124-car");
if (!dn) {
dev_err(&pdev->dev, "Failed to find clk node\n");
WARN_ON(1);
return -EINVAL;
}
base = of_iomap(dn, 0);
if (!base) {
dev_err(&pdev->dev, "Can't map clk registers\n");
WARN_ON(1);
return -EINVAL;
}
pdata->clk_reset_base = base;
soctherm_parse_pmu_dt(pdev);
/* initialize default sensor value */
soctherm_init_sensor(pdev, &t124_sensor_defaults);
/* initialize default clock rates */
soctherm_init_clk_rate(pdev,
t124_default_soctherm_clk_rate,
t124_default_tsensor_clk_rate);
return 0;
}