|
@@ -21,23 +21,15 @@
|
|
|
*
|
|
|
*/
|
|
|
|
|
|
-#include <linux/module.h>
|
|
|
-#include <linux/err.h>
|
|
|
-#include <linux/kernel.h>
|
|
|
-#include <linux/slab.h>
|
|
|
-#include <linux/platform_device.h>
|
|
|
-#include <linux/interrupt.h>
|
|
|
#include <linux/clk.h>
|
|
|
-#include <linux/workqueue.h>
|
|
|
-#include <linux/sysfs.h>
|
|
|
-#include <linux/kobject.h>
|
|
|
#include <linux/io.h>
|
|
|
-#include <linux/mutex.h>
|
|
|
-#include <linux/platform_data/exynos_thermal.h>
|
|
|
-#include <linux/thermal.h>
|
|
|
-#include <linux/cpufreq.h>
|
|
|
-#include <linux/cpu_cooling.h>
|
|
|
+#include <linux/interrupt.h>
|
|
|
+#include <linux/module.h>
|
|
|
#include <linux/of.h>
|
|
|
+#include <linux/platform_device.h>
|
|
|
+#include <linux/platform_data/exynos_thermal.h>
|
|
|
+
|
|
|
+#include "exynos_thermal_common.h"
|
|
|
|
|
|
/* Exynos generic registers */
|
|
|
#define EXYNOS_TMU_REG_TRIMINFO 0x0
|
|
@@ -88,16 +80,6 @@
|
|
|
#define EFUSE_MIN_VALUE 40
|
|
|
#define EFUSE_MAX_VALUE 100
|
|
|
|
|
|
-/* In-kernel thermal framework related macros & definations */
|
|
|
-#define SENSOR_NAME_LEN 16
|
|
|
-#define MAX_TRIP_COUNT 8
|
|
|
-#define MAX_COOLING_DEVICE 4
|
|
|
-#define MAX_THRESHOLD_LEVS 4
|
|
|
-
|
|
|
-#define ACTIVE_INTERVAL 500
|
|
|
-#define IDLE_INTERVAL 10000
|
|
|
-#define MCELSIUS 1000
|
|
|
-
|
|
|
#ifdef CONFIG_THERMAL_EMULATION
|
|
|
#define EXYNOS_EMUL_TIME 0x57F0
|
|
|
#define EXYNOS_EMUL_TIME_SHIFT 16
|
|
@@ -106,17 +88,6 @@
|
|
|
#define EXYNOS_EMUL_ENABLE 0x1
|
|
|
#endif /* CONFIG_THERMAL_EMULATION */
|
|
|
|
|
|
-/* CPU Zone information */
|
|
|
-#define PANIC_ZONE 4
|
|
|
-#define WARN_ZONE 3
|
|
|
-#define MONITOR_ZONE 2
|
|
|
-#define SAFE_ZONE 1
|
|
|
-
|
|
|
-#define GET_ZONE(trip) (trip + 2)
|
|
|
-#define GET_TRIP(zone) (zone - 2)
|
|
|
-
|
|
|
-#define EXYNOS_ZONE_COUNT 3
|
|
|
-
|
|
|
struct exynos_tmu_data {
|
|
|
struct exynos_tmu_platform_data *pdata;
|
|
|
struct resource *mem;
|
|
@@ -129,384 +100,6 @@ struct exynos_tmu_data {
|
|
|
u8 temp_error1, temp_error2;
|
|
|
};
|
|
|
|
|
|
-struct thermal_trip_point_conf {
|
|
|
- int trip_val[MAX_TRIP_COUNT];
|
|
|
- int trip_count;
|
|
|
- u8 trigger_falling;
|
|
|
-};
|
|
|
-
|
|
|
-struct thermal_cooling_conf {
|
|
|
- struct freq_clip_table freq_data[MAX_TRIP_COUNT];
|
|
|
- int freq_clip_count;
|
|
|
-};
|
|
|
-
|
|
|
-struct thermal_sensor_conf {
|
|
|
- char name[SENSOR_NAME_LEN];
|
|
|
- int (*read_temperature)(void *data);
|
|
|
- int (*write_emul_temp)(void *drv_data, unsigned long temp);
|
|
|
- struct thermal_trip_point_conf trip_data;
|
|
|
- struct thermal_cooling_conf cooling_data;
|
|
|
- void *private_data;
|
|
|
-};
|
|
|
-
|
|
|
-struct exynos_thermal_zone {
|
|
|
- enum thermal_device_mode mode;
|
|
|
- struct thermal_zone_device *therm_dev;
|
|
|
- struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
|
|
|
- unsigned int cool_dev_size;
|
|
|
- struct platform_device *exynos4_dev;
|
|
|
- struct thermal_sensor_conf *sensor_conf;
|
|
|
- bool bind;
|
|
|
-};
|
|
|
-
|
|
|
-static struct exynos_thermal_zone *th_zone;
|
|
|
-static void exynos_unregister_thermal(void);
|
|
|
-static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf);
|
|
|
-
|
|
|
-/* Get mode callback functions for thermal zone */
|
|
|
-static int exynos_get_mode(struct thermal_zone_device *thermal,
|
|
|
- enum thermal_device_mode *mode)
|
|
|
-{
|
|
|
- if (th_zone)
|
|
|
- *mode = th_zone->mode;
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-/* Set mode callback functions for thermal zone */
|
|
|
-static int exynos_set_mode(struct thermal_zone_device *thermal,
|
|
|
- enum thermal_device_mode mode)
|
|
|
-{
|
|
|
- if (!th_zone->therm_dev) {
|
|
|
- pr_notice("thermal zone not registered\n");
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- mutex_lock(&th_zone->therm_dev->lock);
|
|
|
-
|
|
|
- if (mode == THERMAL_DEVICE_ENABLED &&
|
|
|
- !th_zone->sensor_conf->trip_data.trigger_falling)
|
|
|
- th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
|
|
|
- else
|
|
|
- th_zone->therm_dev->polling_delay = 0;
|
|
|
-
|
|
|
- mutex_unlock(&th_zone->therm_dev->lock);
|
|
|
-
|
|
|
- th_zone->mode = mode;
|
|
|
- thermal_zone_device_update(th_zone->therm_dev);
|
|
|
- pr_info("thermal polling set for duration=%d msec\n",
|
|
|
- th_zone->therm_dev->polling_delay);
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-/* Get trip type callback functions for thermal zone */
|
|
|
-static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip,
|
|
|
- enum thermal_trip_type *type)
|
|
|
-{
|
|
|
- switch (GET_ZONE(trip)) {
|
|
|
- case MONITOR_ZONE:
|
|
|
- case WARN_ZONE:
|
|
|
- *type = THERMAL_TRIP_ACTIVE;
|
|
|
- break;
|
|
|
- case PANIC_ZONE:
|
|
|
- *type = THERMAL_TRIP_CRITICAL;
|
|
|
- break;
|
|
|
- default:
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-/* Get trip temperature callback functions for thermal zone */
|
|
|
-static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip,
|
|
|
- unsigned long *temp)
|
|
|
-{
|
|
|
- if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE))
|
|
|
- return -EINVAL;
|
|
|
-
|
|
|
- *temp = th_zone->sensor_conf->trip_data.trip_val[trip];
|
|
|
- /* convert the temperature into millicelsius */
|
|
|
- *temp = *temp * MCELSIUS;
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-/* Get critical temperature callback functions for thermal zone */
|
|
|
-static int exynos_get_crit_temp(struct thermal_zone_device *thermal,
|
|
|
- unsigned long *temp)
|
|
|
-{
|
|
|
- int ret;
|
|
|
- /* Panic zone */
|
|
|
- ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp);
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-/* Bind callback functions for thermal zone */
|
|
|
-static int exynos_bind(struct thermal_zone_device *thermal,
|
|
|
- struct thermal_cooling_device *cdev)
|
|
|
-{
|
|
|
- int ret = 0, i, tab_size, level;
|
|
|
- struct freq_clip_table *tab_ptr, *clip_data;
|
|
|
- struct thermal_sensor_conf *data = th_zone->sensor_conf;
|
|
|
-
|
|
|
- tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data;
|
|
|
- tab_size = data->cooling_data.freq_clip_count;
|
|
|
-
|
|
|
- if (tab_ptr == NULL || tab_size == 0)
|
|
|
- return -EINVAL;
|
|
|
-
|
|
|
- /* find the cooling device registered*/
|
|
|
- for (i = 0; i < th_zone->cool_dev_size; i++)
|
|
|
- if (cdev == th_zone->cool_dev[i])
|
|
|
- break;
|
|
|
-
|
|
|
- /* No matching cooling device */
|
|
|
- if (i == th_zone->cool_dev_size)
|
|
|
- return 0;
|
|
|
-
|
|
|
- /* Bind the thermal zone to the cpufreq cooling device */
|
|
|
- for (i = 0; i < tab_size; i++) {
|
|
|
- clip_data = (struct freq_clip_table *)&(tab_ptr[i]);
|
|
|
- level = cpufreq_cooling_get_level(0, clip_data->freq_clip_max);
|
|
|
- if (level == THERMAL_CSTATE_INVALID)
|
|
|
- return 0;
|
|
|
- switch (GET_ZONE(i)) {
|
|
|
- case MONITOR_ZONE:
|
|
|
- case WARN_ZONE:
|
|
|
- if (thermal_zone_bind_cooling_device(thermal, i, cdev,
|
|
|
- level, 0)) {
|
|
|
- pr_err("error binding cdev inst %d\n", i);
|
|
|
- ret = -EINVAL;
|
|
|
- }
|
|
|
- th_zone->bind = true;
|
|
|
- break;
|
|
|
- default:
|
|
|
- ret = -EINVAL;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-/* Unbind callback functions for thermal zone */
|
|
|
-static int exynos_unbind(struct thermal_zone_device *thermal,
|
|
|
- struct thermal_cooling_device *cdev)
|
|
|
-{
|
|
|
- int ret = 0, i, tab_size;
|
|
|
- struct thermal_sensor_conf *data = th_zone->sensor_conf;
|
|
|
-
|
|
|
- if (th_zone->bind == false)
|
|
|
- return 0;
|
|
|
-
|
|
|
- tab_size = data->cooling_data.freq_clip_count;
|
|
|
-
|
|
|
- if (tab_size == 0)
|
|
|
- return -EINVAL;
|
|
|
-
|
|
|
- /* find the cooling device registered*/
|
|
|
- for (i = 0; i < th_zone->cool_dev_size; i++)
|
|
|
- if (cdev == th_zone->cool_dev[i])
|
|
|
- break;
|
|
|
-
|
|
|
- /* No matching cooling device */
|
|
|
- if (i == th_zone->cool_dev_size)
|
|
|
- return 0;
|
|
|
-
|
|
|
- /* Bind the thermal zone to the cpufreq cooling device */
|
|
|
- for (i = 0; i < tab_size; i++) {
|
|
|
- switch (GET_ZONE(i)) {
|
|
|
- case MONITOR_ZONE:
|
|
|
- case WARN_ZONE:
|
|
|
- if (thermal_zone_unbind_cooling_device(thermal, i,
|
|
|
- cdev)) {
|
|
|
- pr_err("error unbinding cdev inst=%d\n", i);
|
|
|
- ret = -EINVAL;
|
|
|
- }
|
|
|
- th_zone->bind = false;
|
|
|
- break;
|
|
|
- default:
|
|
|
- ret = -EINVAL;
|
|
|
- }
|
|
|
- }
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-/* Get temperature callback functions for thermal zone */
|
|
|
-static int exynos_get_temp(struct thermal_zone_device *thermal,
|
|
|
- unsigned long *temp)
|
|
|
-{
|
|
|
- void *data;
|
|
|
-
|
|
|
- if (!th_zone->sensor_conf) {
|
|
|
- pr_info("Temperature sensor not initialised\n");
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
- data = th_zone->sensor_conf->private_data;
|
|
|
- *temp = th_zone->sensor_conf->read_temperature(data);
|
|
|
- /* convert the temperature into millicelsius */
|
|
|
- *temp = *temp * MCELSIUS;
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-/* Get temperature callback functions for thermal zone */
|
|
|
-static int exynos_set_emul_temp(struct thermal_zone_device *thermal,
|
|
|
- unsigned long temp)
|
|
|
-{
|
|
|
- void *data;
|
|
|
- int ret = -EINVAL;
|
|
|
-
|
|
|
- if (!th_zone->sensor_conf) {
|
|
|
- pr_info("Temperature sensor not initialised\n");
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
- data = th_zone->sensor_conf->private_data;
|
|
|
- if (th_zone->sensor_conf->write_emul_temp)
|
|
|
- ret = th_zone->sensor_conf->write_emul_temp(data, temp);
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-/* Get the temperature trend */
|
|
|
-static int exynos_get_trend(struct thermal_zone_device *thermal,
|
|
|
- int trip, enum thermal_trend *trend)
|
|
|
-{
|
|
|
- int ret;
|
|
|
- unsigned long trip_temp;
|
|
|
-
|
|
|
- ret = exynos_get_trip_temp(thermal, trip, &trip_temp);
|
|
|
- if (ret < 0)
|
|
|
- return ret;
|
|
|
-
|
|
|
- if (thermal->temperature >= trip_temp)
|
|
|
- *trend = THERMAL_TREND_RAISE_FULL;
|
|
|
- else
|
|
|
- *trend = THERMAL_TREND_DROP_FULL;
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-/* Operation callback functions for thermal zone */
|
|
|
-static struct thermal_zone_device_ops const exynos_dev_ops = {
|
|
|
- .bind = exynos_bind,
|
|
|
- .unbind = exynos_unbind,
|
|
|
- .get_temp = exynos_get_temp,
|
|
|
- .set_emul_temp = exynos_set_emul_temp,
|
|
|
- .get_trend = exynos_get_trend,
|
|
|
- .get_mode = exynos_get_mode,
|
|
|
- .set_mode = exynos_set_mode,
|
|
|
- .get_trip_type = exynos_get_trip_type,
|
|
|
- .get_trip_temp = exynos_get_trip_temp,
|
|
|
- .get_crit_temp = exynos_get_crit_temp,
|
|
|
-};
|
|
|
-
|
|
|
-/*
|
|
|
- * This function may be called from interrupt based temperature sensor
|
|
|
- * when threshold is changed.
|
|
|
- */
|
|
|
-static void exynos_report_trigger(void)
|
|
|
-{
|
|
|
- unsigned int i;
|
|
|
- char data[10];
|
|
|
- char *envp[] = { data, NULL };
|
|
|
-
|
|
|
- if (!th_zone || !th_zone->therm_dev)
|
|
|
- return;
|
|
|
- if (th_zone->bind == false) {
|
|
|
- for (i = 0; i < th_zone->cool_dev_size; i++) {
|
|
|
- if (!th_zone->cool_dev[i])
|
|
|
- continue;
|
|
|
- exynos_bind(th_zone->therm_dev,
|
|
|
- th_zone->cool_dev[i]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- thermal_zone_device_update(th_zone->therm_dev);
|
|
|
-
|
|
|
- mutex_lock(&th_zone->therm_dev->lock);
|
|
|
- /* Find the level for which trip happened */
|
|
|
- for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
|
|
|
- if (th_zone->therm_dev->last_temperature <
|
|
|
- th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS)
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (th_zone->mode == THERMAL_DEVICE_ENABLED &&
|
|
|
- !th_zone->sensor_conf->trip_data.trigger_falling) {
|
|
|
- if (i > 0)
|
|
|
- th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
|
|
|
- else
|
|
|
- th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
|
|
|
- }
|
|
|
-
|
|
|
- snprintf(data, sizeof(data), "%u", i);
|
|
|
- kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
|
|
|
- mutex_unlock(&th_zone->therm_dev->lock);
|
|
|
-}
|
|
|
-
|
|
|
-/* Register with the in-kernel thermal management */
|
|
|
-static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
|
|
|
-{
|
|
|
- int ret;
|
|
|
- struct cpumask mask_val;
|
|
|
-
|
|
|
- if (!sensor_conf || !sensor_conf->read_temperature) {
|
|
|
- pr_err("Temperature sensor not initialised\n");
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
-
|
|
|
- th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL);
|
|
|
- if (!th_zone)
|
|
|
- return -ENOMEM;
|
|
|
-
|
|
|
- th_zone->sensor_conf = sensor_conf;
|
|
|
- cpumask_set_cpu(0, &mask_val);
|
|
|
- th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val);
|
|
|
- if (IS_ERR(th_zone->cool_dev[0])) {
|
|
|
- pr_err("Failed to register cpufreq cooling device\n");
|
|
|
- ret = -EINVAL;
|
|
|
- goto err_unregister;
|
|
|
- }
|
|
|
- th_zone->cool_dev_size++;
|
|
|
-
|
|
|
- th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
|
|
|
- EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, NULL, 0,
|
|
|
- sensor_conf->trip_data.trigger_falling ?
|
|
|
- 0 : IDLE_INTERVAL);
|
|
|
-
|
|
|
- if (IS_ERR(th_zone->therm_dev)) {
|
|
|
- pr_err("Failed to register thermal zone device\n");
|
|
|
- ret = PTR_ERR(th_zone->therm_dev);
|
|
|
- goto err_unregister;
|
|
|
- }
|
|
|
- th_zone->mode = THERMAL_DEVICE_ENABLED;
|
|
|
-
|
|
|
- pr_info("Exynos: Kernel Thermal management registered\n");
|
|
|
-
|
|
|
- return 0;
|
|
|
-
|
|
|
-err_unregister:
|
|
|
- exynos_unregister_thermal();
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-/* Un-Register with the in-kernel thermal management */
|
|
|
-static void exynos_unregister_thermal(void)
|
|
|
-{
|
|
|
- int i;
|
|
|
-
|
|
|
- if (!th_zone)
|
|
|
- return;
|
|
|
-
|
|
|
- if (th_zone->therm_dev)
|
|
|
- thermal_zone_device_unregister(th_zone->therm_dev);
|
|
|
-
|
|
|
- for (i = 0; i < th_zone->cool_dev_size; i++) {
|
|
|
- if (th_zone->cool_dev[i])
|
|
|
- cpufreq_cooling_unregister(th_zone->cool_dev[i]);
|
|
|
- }
|
|
|
-
|
|
|
- kfree(th_zone);
|
|
|
- pr_info("Exynos: Kernel Thermal management unregistered\n");
|
|
|
-}
|
|
|
-
|
|
|
/*
|
|
|
* TMU treats temperature as a mapped temperature code.
|
|
|
* The temperature is converted differently depending on the calibration type.
|