diff options
author | Rocky Hao <rocky.hao@rock-chips.com> | 2017-12-06 10:53:29 +0800 |
---|---|---|
committer | Tao Huang <huangtao@rock-chips.com> | 2017-12-13 18:39:59 +0800 |
commit | 2954d5a6dbb875dd8189d0d56b61b30dfeffda3f (patch) | |
tree | f0f2c0120f40174db6cb2d7283c35f0787b49b64 /drivers/thermal | |
parent | 0c3d5875c307d46394d3f0ea7f7b154828a31f8f (diff) |
thermal: rockchip: add virtual tsadc support for rk3126
rk previous SOCs such as rk3126 have no tsadc module, so a virtual tsadc is
implemented to control the thermal problem.
the virtual tsadc is designed on considering 2 factors, one is heating
modules' heating time and the working frequences, the other one is current
leval monitored by coulometer.
Change-Id: I0c7d8b952004d4f7918a41c925c50d38aaa65673
Signed-off-by: Rocky Hao <rocky.hao@rock-chips.com>
Diffstat (limited to 'drivers/thermal')
-rw-r--r-- | drivers/thermal/Kconfig | 9 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/rk_virtual_thermal.c | 889 |
3 files changed, 899 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 580104b1063b..5cb853a17a60 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -212,6 +212,15 @@ config ROCKCHIP_THERMAL trip point. Cpufreq is used as the cooling device and will throttle CPUs when the Temperature crosses the passive trip point. +config RK_VIRTUAL_THERMAL + tristate "rk_virtual thermal driver" + depends on ROCKCHIP_THERMAL + help + Rk virtual thermal driver provides virtual temperature support for + SOCs without tsadc module. It supports one critical trip point. + Cpufreq is used as the cooling device and will throttle CPUs when + the Temperature crosses the passive trip point. + config RK3368_THERMAL tristate "rk3368 thermal driver legacy" depends on ROCKCHIP_THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 73eef831c536..cc2cc3e97f35 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o obj-$(CONFIG_RK3368_THERMAL) += rk3368_thermal.o +obj-$(CONFIG_RK_VIRTUAL_THERMAL) += rk_virtual_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o obj-y += samsung/ diff --git a/drivers/thermal/rk_virtual_thermal.c b/drivers/thermal/rk_virtual_thermal.c new file mode 100644 index 000000000000..529ec656175f --- /dev/null +++ b/drivers/thermal/rk_virtual_thermal.c @@ -0,0 +1,889 @@ +/* + * rk virtual tsadc driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd + * Author: Rocky Hao <rocky.hao@rock-chips.com> + * + * 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. + * + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/timer.h> +#include <linux/nvmem-consumer.h> +#include <linux/backlight.h> +#include <linux/cpufreq.h> +#include <linux/power_supply.h> +#include <linux/clk-provider.h> +#include <dt-bindings/clock/rk3128-cru.h> + +#define GPU_TEMP_COMPENSION (6000) +#define VPU_TEMP_COMPENSION (3000) + +#define LOWEST_TEMP (-273000) + +#define BASE (1024) +#define BASE_SHIFT (10) +#define START_DEBOUNCE_COUNT (100) +#define HIGHER_DEBOUNCE_TEMP (30000) +#define LOWER_DEBOUNCE_TEMP (15000) + +#define LEAKAGE_INVALID (0xff) +/*20ms as the unit, 60000 * 20ms = 20mins */ +#define TEMP_STABLE_TIME (60000) + +#define MINIMAL_DISCHARGE_CURRENT (-200000) +#define LOWEST_WORKING_TEMP (-40000) + +static unsigned int logout; +module_param(logout, int, 0644); +MODULE_PARM_DESC(logout, "switch to control logout or not"); + +struct temp_frequency_entry { + unsigned int frequency; + s32 time2temp[2]; + int time_bound; + s32 time2temp2[2]; + int min_temp; + int stable_temp; + + s32 temp2time[2]; + int temp_bound; + s32 temp2time2[2]; +}; + +static const struct temp_frequency_entry rk3126_table[] = { + {400000, {18, 446167,}, 6000, {2, 541167,}, 44616, 69000, {555, -23865}, + 56000, {5000, -272785},}, + {816000, {18, 496167,}, 6000, {2, 591167,}, 49616, 74000, {555, -26640}, + 61000, {5000, -297785},}, + {912000, {21, 525167,}, 6000, {2, 639167,}, 52516, 80000, {476, -25007}, + 65000, {5000, -319067},}, + {1008000, {22, 563500,}, 6000, {3, 677500,}, 56350, 100000, + {454, -25613}, 70000, {3333, -227143},}, + {1104000, {33, 570000,}, 6000, {5, 738000,}, 57000, 109000, + {303, -17272}, 77000, {2000, -147941},}, + {1200000, {35, 620167,}, 6000, {5, 800167,}, 61016, 113000, + {285, -17719}, 83000, {2000, -160064},}, + {CPUFREQ_TABLE_END, {0, 0,}, 0, {0, 0,}, 0, 0, {0, 0,}, 0, {0, 0,} }, +}; + +struct thermal_tuning_info { + int load_slope; + int load_intercept; + + int lkg_slope; + int lkg_intercept; + + int cur_slope; + int cur_intercept; + + int bn_slope; + int bn_intercept; + int bn_offsite; + + int vpu_slope; + int gpu_slope; + const struct temp_frequency_entry *map_entries; + + int vpu_ajust; + int gpu_ajust; + + int fusing_step; +}; + +static const struct thermal_tuning_info rk3126_tuning_info = { + .load_slope = 102, + .load_intercept = 61800, + + .lkg_slope = 107, + .lkg_intercept = 4713, + + .cur_slope = 42, + .cur_intercept = 32661, + + .bn_slope = 1517, + .bn_intercept = 199353, + .bn_offsite = 262000, + + .vpu_slope = 5, + .gpu_slope = 5, + + .map_entries = rk3126_table, + + .vpu_ajust = GPU_TEMP_COMPENSION, + .gpu_ajust = VPU_TEMP_COMPENSION, + + .fusing_step = 2, +}; + +struct virtual_thermal_data { + struct platform_device *pdev; + struct device *dev; + struct thermal_zone_device *tzd; + struct power_supply *psy_bat; + struct power_supply *psy_usb; + struct power_supply *psy_ac; + struct cpufreq_freqs current_freq; + const struct temp_frequency_entry *temp_freq; + int cmp_lkg_temp; + int sigma_time_20ms; + struct kobject virtual_thermal_kobj; + struct thermal_tuning_info *tuning_info; +}; + +static struct platform_device *platform_dev; + +static int get_temp_by_freq_time(unsigned int freq, int time_20ms) +{ + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + const struct temp_frequency_entry *table = ctx->tuning_info->map_entries; + + int i = 0; + int milli_deg = 0; + + for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) { + if (freq < table[i].frequency) { + ctx->temp_freq = &table[i]; + break; + } + } + if (table[i].frequency == CPUFREQ_TABLE_END) + ctx->temp_freq = &table[i - 1]; + + if (time_20ms > TEMP_STABLE_TIME) + return ctx->temp_freq->stable_temp; + + if (time_20ms < ctx->temp_freq->time_bound) + milli_deg = + time_20ms * ctx->temp_freq->time2temp[0] + + ctx->temp_freq->time2temp[1]; + else + milli_deg = + time_20ms * ctx->temp_freq->time2temp2[0] + + ctx->temp_freq->time2temp2[1]; + + if (logout) + dev_info(&platform_dev->dev, "current freq: %u stable_temp: %d milli_deg %d\n", + freq, ctx->temp_freq->stable_temp, milli_deg / 10); + + return milli_deg / 10; +} + +static int get_time_by_temp(int milli_deg) +{ + int time_20ms = 0; + int deg = milli_deg / 1000; + + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + if (milli_deg > ctx->temp_freq->stable_temp) + return TEMP_STABLE_TIME; + + if (milli_deg < ctx->temp_freq->temp_bound) { + time_20ms = + deg * ctx->temp_freq->temp2time[0] + + ctx->temp_freq->temp2time[1]; + } else { + time_20ms = + deg * ctx->temp_freq->temp2time2[0] + + ctx->temp_freq->temp2time2[1]; + } + + if (logout) + dev_info(&platform_dev->dev, "estimate time %d, by milli_deg %d\n", + time_20ms, milli_deg); + + return max(time_20ms, 0); +} + +static u32 get_load(int cpu, int cpu_idx) +{ + static u64 time_in_idle[NR_CPUS] = { 0 }; + static u64 time_in_idle_timestamp[NR_CPUS] = { 0 }; + + u32 load; + u64 now, now_idle, delta_time, delta_idle; + + now_idle = get_cpu_idle_time(cpu, &now, 0); + delta_idle = now_idle - time_in_idle[cpu_idx]; + delta_time = now - time_in_idle_timestamp[cpu_idx]; + + if (delta_time <= delta_idle) + load = 0; + else + load = div64_u64(100 * (delta_time - delta_idle), delta_time); + + time_in_idle[cpu_idx] = now_idle; + time_in_idle_timestamp[cpu_idx] = now; + + return load; +} + +static int get_all_load(void) +{ + u32 total_load = 0; + int cpu; + int i = 0; + + for_each_online_cpu(cpu) { + u32 load; + + load = get_load(cpu, i); + total_load += load; + if (logout) + dev_info(&platform_dev->dev, "cpu %d, load %d\n", i, + load); + + i++; + } + if (logout) + dev_info(&platform_dev->dev, "total cpu load %d\n", total_load); + + return total_load; +} + +static int predict_normal_temp(int milli_deg) +{ + int cov_q = 18; + int cov_r = 542; + + int gain; + int temp_mid; + int temp_now; + int prob_mid; + int prob_now; + static int temp_last = 50000; + static int prob_last = 20; + static int bounding_cnt; + + if (bounding_cnt++ > START_DEBOUNCE_COUNT) { + bounding_cnt = START_DEBOUNCE_COUNT; + if (milli_deg - temp_last > HIGHER_DEBOUNCE_TEMP) + milli_deg = temp_last + HIGHER_DEBOUNCE_TEMP / 3; + if (temp_last - milli_deg > LOWER_DEBOUNCE_TEMP) + milli_deg = temp_last - LOWER_DEBOUNCE_TEMP / 3; + } + + temp_mid = temp_last; + prob_mid = prob_last + cov_q; + gain = (prob_mid * BASE) / (prob_mid + cov_r); + + temp_now = temp_mid + (gain * (milli_deg - temp_mid) >> BASE_SHIFT); + prob_now = ((BASE - gain) * prob_mid) >> BASE_SHIFT; + + prob_last = prob_now; + temp_last = temp_now; + + return temp_last; +} + +static int predict_cur_temp(int milli_cur_temp) +{ + int cov_q = 18; + int cov_r = 542; + + int gain; + int temp_mid; + int temp_now; + int prob_mid; + int prob_now; + static int cur_last = 50000; + static int prob_last = 20; + static int bounding_cnt; + + if (bounding_cnt++ > START_DEBOUNCE_COUNT) { + bounding_cnt = START_DEBOUNCE_COUNT; + if (milli_cur_temp - cur_last > HIGHER_DEBOUNCE_TEMP) + milli_cur_temp = cur_last + HIGHER_DEBOUNCE_TEMP / 3; + if (cur_last - milli_cur_temp > LOWER_DEBOUNCE_TEMP) + milli_cur_temp = cur_last - LOWER_DEBOUNCE_TEMP / 3; + } + + temp_mid = cur_last; + prob_mid = prob_last + cov_q; + gain = (prob_mid * BASE) / (prob_mid + cov_r); + + temp_now = + temp_mid + (gain * (milli_cur_temp - temp_mid) >> BASE_SHIFT); + prob_now = ((BASE - gain) * prob_mid) >> BASE_SHIFT; + + prob_last = prob_now; + cur_last = temp_now; + + return cur_last; +} + +static void update_counting_time(void) +{ + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + static ktime_t delta_last; + ktime_t delta; + unsigned long long duration; + ktime_t timestamp = ktime_get(); + + delta = ktime_sub(timestamp, delta_last); + duration = (unsigned long long)ktime_to_ns(delta) >> 20; + delta_last = timestamp; + + if (duration < TEMP_STABLE_TIME) + ctx->sigma_time_20ms += div64_u64(duration, 20); + else + ctx->sigma_time_20ms = 0; + + if (logout) + dev_info(&platform_dev->dev, "sigma heating time %d\n", + ctx->sigma_time_20ms); +} + +static s64 update_working_time_for_gpu_vpu(void) +{ + static ktime_t last_timestamp; + ktime_t delta; + s64 duration; + ktime_t timestamp = ktime_get(); + + delta = ktime_sub(timestamp, last_timestamp); + duration = (long long)ktime_to_ns(delta) >> 20; + last_timestamp = timestamp; + duration = div64_s64(duration, 20); + return duration; +} + +static struct clk *clk_get_by_name(const char *clk_name) +{ + const char *name; + struct clk *clk; + struct device_node *np; + struct of_phandle_args clkspec; + int i; + + np = of_find_node_by_name(NULL, "clock-controller"); + clkspec.np = np; + clkspec.args_count = 1; + for (i = 1; i < CLK_NR_CLKS; i++) { + clkspec.args[0] = i; + clk = of_clk_get_from_provider(&clkspec); + if (IS_ERR_OR_NULL(clk)) + continue; + name = __clk_get_name(clk); + if (strlen(name) != strlen(clk_name)) + continue; + if (!strncmp(name, clk_name, strlen(clk_name))) + break; + } + + if (i == CLK_NR_CLKS) + clk = NULL; + + return clk; +} + +static int get_actual_brightness(void) +{ + struct backlight_device *bd; + + struct device_node *np; + int brightness; + + np = of_find_node_by_name(NULL, "backlight"); + if (!np) + return 0; + bd = of_find_backlight_by_node(np); + + if (!bd) + return 0; + + mutex_lock(&bd->ops_lock); + if (bd->ops && bd->ops->get_brightness) + brightness = bd->ops->get_brightness(bd); + else + brightness = bd->props.brightness; + + mutex_unlock(&bd->ops_lock); + + return brightness; +} + +static int compensate_brightness(int cur) +{ + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + int slope = ctx->tuning_info->bn_slope; + int intercept = ctx->tuning_info->bn_slope; + int offsite = ctx->tuning_info->bn_offsite; + + int brightness; + int cur_ajust = 0; + + brightness = get_actual_brightness(); + + if (brightness == 0) + cur_ajust = cur - offsite; + else if (brightness > 0) + cur_ajust = cur - intercept + brightness * slope; + + if (logout) + dev_info(&platform_dev->dev, "brightness %d cur %d cur_ajust %d\n", + brightness, cur, cur_ajust); + + return cur_ajust; +} + +static int rockchip_get_efuse_value(struct device_node *np, char *porp_name, + int *value) +{ + struct nvmem_cell *cell; + unsigned char *buf; + size_t len; + + cell = of_nvmem_cell_get(np, porp_name); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = (unsigned char *)nvmem_cell_read(cell, &len); + + nvmem_cell_put(cell); + + if (IS_ERR(buf)) + return PTR_ERR(buf); + + if (buf[0] == LEAKAGE_INVALID) { + kfree(buf); + return -EINVAL; + } + + *value = buf[0]; + + kfree(buf); + + return 0; +} + +static int ajust_temp_on_gpu_vpu(int temp) +{ + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + int vpu_slope = ctx->tuning_info->vpu_slope; + int gpu_slope = ctx->tuning_info->gpu_slope; + int vpu_ajust = ctx->tuning_info->vpu_ajust; + int gpu_ajust = ctx->tuning_info->gpu_ajust; + + int delta_gpu_temp = 0; + int delta_vpu_temp = 0; + int gpu_enabled = 0; + int vpu_enabled = 0; + struct clk *clk; + int delta; + static int sigma_vpu_20ms; + static int sigma_gpu_20ms; + + delta = (int)update_working_time_for_gpu_vpu(); + clk = clk_get_by_name("aclk_gpu"); + + if (!IS_ERR(clk) && __clk_is_enabled(clk)) { + gpu_enabled = 1; + sigma_gpu_20ms -= delta; + sigma_gpu_20ms = max(sigma_gpu_20ms, 0); + } else { + sigma_gpu_20ms += delta; + } + + clk = clk_get_by_name("aclk_vdpu"); + + if (!IS_ERR(clk) && __clk_is_enabled(clk)) { + vpu_enabled = 1; + sigma_vpu_20ms -= delta; + sigma_vpu_20ms = max(sigma_vpu_20ms, 0); + + } else { + sigma_vpu_20ms += delta; + } + + delta_gpu_temp = sigma_gpu_20ms * gpu_slope; + delta_vpu_temp = sigma_vpu_20ms * vpu_slope; + + if (delta_gpu_temp > gpu_ajust) { + delta_gpu_temp = gpu_ajust; + sigma_gpu_20ms = gpu_ajust / gpu_slope; + } + + if (delta_vpu_temp > vpu_ajust) { + delta_vpu_temp = vpu_ajust; + sigma_vpu_20ms = vpu_ajust / vpu_slope; + } + + if (logout) + dev_info(&platform_dev->dev, "temp %d delta_vpu_temp %d delta_vpu_temp %d\n", + temp, delta_vpu_temp, delta_vpu_temp); + + temp = temp - delta_gpu_temp - delta_vpu_temp; + + return temp; +} + +static int ps_get_cur_current(struct power_supply *psy, int *power_cur) +{ + union power_supply_propval val; + int ret; + + ret = psy->desc->get_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, &val); + if (!ret) + *power_cur = val.intval; + + return ret; +} + +static int map_temp_from_current(int cur) +{ + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + int slope = ctx->tuning_info->cur_slope; + int intercept = ctx->tuning_info->cur_intercept; + + int milli_degree = cur * slope + intercept; + + milli_degree = predict_cur_temp(milli_degree); + return milli_degree; +} + +static int get_temp_by_current(void) +{ + int cur = 0; + int temp = LOWEST_TEMP; + int ret = -1; + + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + if (ctx->psy_bat) + ret = ps_get_cur_current(ctx->psy_bat, &cur); + + if (ret) + return temp; + + cur = compensate_brightness(cur); + + if (cur < MINIMAL_DISCHARGE_CURRENT) { + cur = -cur; + temp = map_temp_from_current(cur / 1000); + } + + return temp; +} + +static int ajudt_temp_by_load(int temp_delta) +{ + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + int slope = ctx->tuning_info->load_slope; + int intercept = ctx->tuning_info->load_intercept; + + int load_rate; + int total_load = 0; + int temp_delta_ajust; + + total_load = get_all_load(); + + load_rate = (total_load * slope + intercept) / 1000; + + load_rate = min(load_rate, 100); + + if (temp_delta > 0) + temp_delta_ajust = temp_delta * load_rate / 100; + else + temp_delta_ajust = temp_delta * 100 / load_rate; + + if (logout) + dev_info(&platform_dev->dev, "temp_delta %d load_rate %d temp_delta_ajust %d\n", + temp_delta, load_rate, temp_delta_ajust); + + return temp_delta_ajust; +} + +static int is_charger_pluged_in(void) +{ + union power_supply_propval val; + int ret = 0; + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + struct power_supply *psy_usb = ctx->psy_usb; + struct power_supply *psy_ac = ctx->psy_ac; + + if (psy_usb && psy_usb->desc && psy_usb->desc->get_property) { + ret = psy_usb->desc->get_property(psy_usb, + POWER_SUPPLY_PROP_ONLINE, + &val); + if (!ret && val.intval) + return 1; + } + if (psy_ac && psy_ac->desc && psy_ac->desc->get_property) { + ret = psy_ac->desc->get_property(psy_ac, + POWER_SUPPLY_PROP_ONLINE, + &val); + if (!ret && val.intval) + return 1; + } + return 0; +} + +static int estimate_temp_internal(void) +{ + int temp = 0; + static int last_temp = LOWEST_TEMP; + int temp_delta; + + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + struct cpufreq_freqs *current_freq = &ctx->current_freq; + + update_counting_time(); + + temp = get_temp_by_freq_time(current_freq->new, ctx->sigma_time_20ms); + + temp = ajust_temp_on_gpu_vpu(temp); + + if (last_temp == LOWEST_TEMP) + temp_delta = 0; + else + temp_delta = temp - last_temp; + + temp_delta = ajudt_temp_by_load(temp_delta); + + if (last_temp != LOWEST_TEMP) + temp = last_temp + temp_delta; + + last_temp = temp; + + temp = clamp(temp, ctx->temp_freq->min_temp, ctx->temp_freq->stable_temp); + + temp += ctx->cmp_lkg_temp; + + temp = predict_normal_temp(temp); + + ctx->sigma_time_20ms = get_time_by_temp(temp); + + if (logout) + dev_info(&platform_dev->dev, "Temp1 %d cmp_lkg_temp %d sigma %d\n", + temp, ctx->cmp_lkg_temp, ctx->sigma_time_20ms); + + if (!is_charger_pluged_in()) { + int temp_from_current = 0; + int fusion_diff = 0; + int fusing_step = ctx->tuning_info->fusing_step; + + temp_from_current = get_temp_by_current(); + if (temp_from_current > LOWEST_WORKING_TEMP) { + fusion_diff = temp_from_current - temp; + temp = temp + fusion_diff / fusing_step; + ctx->sigma_time_20ms = get_time_by_temp(temp); + if (logout) + dev_info(&platform_dev->dev, "Temp2 %d temp_from_current %d sigma %d\n", + temp, temp_from_current, + ctx->sigma_time_20ms); + } + } + return temp; +} + +static int virtual_thermal_set_trips(void *_sensor, int low, int high) +{ + return 0; +} + +static int virtual_thermal_get_temp(void *_sensor, int *out_temp) +{ + *out_temp = estimate_temp_internal(); + return 0; +} + +static const struct thermal_zone_of_device_ops virtual_of_thermal_ops = { + .get_temp = virtual_thermal_get_temp, + .set_trips = virtual_thermal_set_trips, +}; + +static const struct of_device_id of_virtual_thermal_match[] = { + { + .compatible = "rockchip,rk3126-tsadc-virtual", + .data = (void *)&rk3126_tuning_info, + }, + + { /* end */ }, +}; + +MODULE_DEVICE_TABLE(of, of_virtual_thermal_match); + +static int temp_interactive_notifier(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct cpufreq_freqs *freq = data; + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + if (!ctx) + return 0; + if (val == CPUFREQ_POSTCHANGE) { + ctx->current_freq.new = freq->new; + ctx->current_freq.old = freq->old; + } + return 0; +} + +static struct notifier_block temp_notifier_block = { + .notifier_call = temp_interactive_notifier, +}; + +static int compensate_leakage(int lkg) +{ + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + int slope = ctx->tuning_info->lkg_slope; + int intercept = ctx->tuning_info->lkg_slope; + + int milli_degree = 0; + + if (lkg == 0) + milli_degree = 0; + else + milli_degree = slope * lkg - intercept; + + return milli_degree; +} + +void dump_virtual_temperature(void) +{ + struct virtual_thermal_data *ctx = platform_get_drvdata(platform_dev); + + struct thermal_zone_device *tz = ctx->tzd; + + if (tz->temperature != THERMAL_TEMP_INVALID) + dev_warn(&platform_dev->dev, "virtual temperature(%d C)\n", + tz->temperature / 1000); +} +EXPORT_SYMBOL_GPL(dump_virtual_temperature); + +static int virtual_thermal_panic(struct notifier_block *this, + unsigned long ev, void *ptr) +{ + dump_virtual_temperature(); + return NOTIFY_DONE; +} + +static struct notifier_block virtual_thermal_panic_block = { + .notifier_call = virtual_thermal_panic, +}; + +static int virtual_thermal_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int ret; + int leakage = 0; + struct virtual_thermal_data *ctx; + + const struct of_device_id *match; + + match = of_match_node(of_virtual_thermal_match, np); + if (!match) + return -ENXIO; + + ctx = devm_kzalloc(&pdev->dev, sizeof(struct virtual_thermal_data), + GFP_KERNEL); + + ctx->pdev = pdev; + ctx->dev = &pdev->dev; + platform_set_drvdata(pdev, ctx); + + platform_dev = pdev; + + ctx->tuning_info = (struct thermal_tuning_info *)match->data; + if (!ctx->tuning_info) { + dev_err(&pdev->dev, + "failed to allocate memory for tuning info.\n"); + return -EINVAL; + } + + ret = rockchip_get_efuse_value(np, "cpu_leakage", &leakage); + if (!ret) + dev_info(&pdev->dev, "leakage=%d\n", leakage); + + ctx->cmp_lkg_temp = compensate_leakage(leakage); + + ctx->psy_bat = power_supply_get_by_name("battery"); + ctx->psy_usb = power_supply_get_by_name("usb"); + ctx->psy_ac = power_supply_get_by_name("ac"); + + ret = cpufreq_register_notifier(&temp_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + if (ret) { + dev_err(&pdev->dev, "failed to register cpufreq notifier: %d\n", + ret); + return ret; + } + + ctx->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, + NULL, + &virtual_of_thermal_ops); + if (IS_ERR(ctx->tzd)) { + ret = PTR_ERR(ctx->tzd); + dev_err(&pdev->dev, "failed to register sensor 0: %d\n", ret); + goto err_unreg_cpufreq_notifier; + } + + ret = atomic_notifier_chain_register(&panic_notifier_list, + &virtual_thermal_panic_block); + if (ret) { + dev_err(&pdev->dev, "failed to register panic notifier: %d\n", + ret); + goto err_unreg_cpufreq_notifier; + } + + dev_info(&pdev->dev, "virtual tsadc probed successfully\n"); + + return 0; + +err_unreg_cpufreq_notifier: + cpufreq_unregister_notifier(&temp_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + return ret; +} + +static int virtual_thermal_remove(struct platform_device *pdev) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, + &virtual_thermal_panic_block); + cpufreq_unregister_notifier(&temp_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + return 0; +} + +static struct platform_driver virtual_thermal_driver = { + .driver = { + .name = "virtual-thermal", + .of_match_table = of_virtual_thermal_match, + }, + .probe = virtual_thermal_probe, + .remove = virtual_thermal_remove, +}; + +static int __init virtual_thermal_init_driver(void) +{ + return platform_driver_register(&virtual_thermal_driver); +} + +late_initcall(virtual_thermal_init_driver); + +MODULE_DESCRIPTION("ROCKCHIP THERMAL Driver"); +MODULE_AUTHOR("Rockchip, Inc."); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:virtual-thermal"); |