diff options
author | Keerthy <j-keerthy@ti.com> | 2012-02-23 16:34:01 +0800 |
---|---|---|
committer | Andy Green <andy.green@linaro.org> | 2012-06-20 10:27:13 +0800 |
commit | 4e247cc2ce488bbd38ba3b4a7459af16bc54ec2d (patch) | |
tree | b47f6a3e03a837eeb7f0cf449d35dd79ffebfdb5 | |
parent | 16ca51d201b4bd717e79b2f729fa09f50529b208 (diff) |
OMAP4460PLUS Thermal: Thermal framework support
The patch adds kernel thermal framework and on die
CPU governer support.
Signed-off-by: Dan Murphy <dmurphy@ti.com>
Signed-off-by: Moiz Sonasath <m-sonasath@ti.com>
Signed-off-by: Keerthy <j-keerthy@ti.com>
Signed-off-by: J Keerthy <j-keerthy@ti.com>
-rw-r--r-- | drivers/staging/Kconfig | 2 | ||||
-rw-r--r-- | drivers/staging/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/thermal_framework/Kconfig | 18 | ||||
-rw-r--r-- | drivers/staging/thermal_framework/Makefile | 5 | ||||
-rw-r--r-- | drivers/staging/thermal_framework/governor/Kconfig | 10 | ||||
-rw-r--r-- | drivers/staging/thermal_framework/governor/Makefile | 4 | ||||
-rw-r--r-- | drivers/staging/thermal_framework/governor/omap_die_governor.c | 510 | ||||
-rw-r--r-- | drivers/staging/thermal_framework/thermal_framework.c | 597 | ||||
-rw-r--r-- | include/linux/thermal_framework.h | 96 |
9 files changed, 1243 insertions, 0 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 97d412d9145..ed94734d90e 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -132,4 +132,6 @@ source "drivers/staging/ramster/Kconfig" source "drivers/staging/ozwpan/Kconfig" +source "drivers/staging/thermal_framework/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index ffe7d44374e..542876b26cd 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_ANDROID) += android/ obj-$(CONFIG_PHONE) += telephony/ obj-$(CONFIG_RAMSTER) += ramster/ obj-$(CONFIG_USB_WPAN_HCD) += ozwpan/ +obj-$(CONFIG_OMAP_THERMAL) += thermal_framework/ diff --git a/drivers/staging/thermal_framework/Kconfig b/drivers/staging/thermal_framework/Kconfig new file mode 100644 index 00000000000..0199e129ca1 --- /dev/null +++ b/drivers/staging/thermal_framework/Kconfig @@ -0,0 +1,18 @@ +# +# Generic Thermal Framework drivers configuration +# + +menuconfig THERMAL_FRAMEWORK + bool "Thermal Framework support" + help + This is a generic thermal framework which will be used as + a central hub for sensors, governors and cooling devices + to register and pass data to. + +config OMAP_THERMAL + bool "OMAP Thermal Framework support" + help + This is the thermal framework support for OMAP4 + processors. + +source "drivers/staging/thermal_framework/governor/Kconfig" diff --git a/drivers/staging/thermal_framework/Makefile b/drivers/staging/thermal_framework/Makefile new file mode 100644 index 00000000000..bdae4d550c9 --- /dev/null +++ b/drivers/staging/thermal_framework/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for sensor chip drivers. +# +obj-$(CONFIG_THERMAL_FRAMEWORK) += thermal_framework.o \ + governor/ diff --git a/drivers/staging/thermal_framework/governor/Kconfig b/drivers/staging/thermal_framework/governor/Kconfig new file mode 100644 index 00000000000..e5d41a584aa --- /dev/null +++ b/drivers/staging/thermal_framework/governor/Kconfig @@ -0,0 +1,10 @@ +# +# Thermal governor driver config +# + +config OMAP_DIE_GOVERNOR + bool "OMAP On Die thermal governor support" + help + This is the governor for the OMAP4 On-Die temperature sensor. + This governer will institute the policy to call specific + cooling agents. diff --git a/drivers/staging/thermal_framework/governor/Makefile b/drivers/staging/thermal_framework/governor/Makefile new file mode 100644 index 00000000000..0b5eff77ab7 --- /dev/null +++ b/drivers/staging/thermal_framework/governor/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for Thermal governor drivers. +# +obj-$(CONFIG_OMAP_DIE_GOVERNOR) += omap_die_governor.o diff --git a/drivers/staging/thermal_framework/governor/omap_die_governor.c b/drivers/staging/thermal_framework/governor/omap_die_governor.c new file mode 100644 index 00000000000..115ea373ec8 --- /dev/null +++ b/drivers/staging/thermal_framework/governor/omap_die_governor.c @@ -0,0 +1,510 @@ +/* + * drivers/thermal/omap_die_governor.c + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Dan Murphy <DMurphy@ti.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <linux/thermal_framework.h> + +/* CPU Zone information */ +#define FATAL_ZONE 5 +#define PANIC_ZONE 4 +#define ALERT_ZONE 3 +#define MONITOR_ZONE 2 +#define SAFE_ZONE 1 +#define NO_ACTION 0 +#define OMAP_FATAL_TEMP 125000 +#define OMAP_PANIC_TEMP 110000 +#define OMAP_ALERT_TEMP 100000 +#define OMAP_MONITOR_TEMP 85000 + + +/* TODO: Define this via a configurable file */ +#define HYSTERESIS_VALUE 5000 +#define NORMAL_TEMP_MONITORING_RATE 1000 +#define FAST_TEMP_MONITORING_RATE 250 + +static int omap_gradient_slope; +static int omap_gradient_const; + +struct omap_die_governor { + struct thermal_dev *temp_sensor; + void (*update_temp_thresh) (struct thermal_dev *, int min, int max); + int report_rate; + int panic_zone_reached; + int cooling_level; +}; + +static struct thermal_dev *therm_fw; +static struct omap_die_governor *omap_gov; + +static LIST_HEAD(cooling_agents); + +/** + * DOC: Introduction + * ================= + * The OMAP On-Die Temperature governor maintains the policy for the CPU + * on die temperature sensor. The main goal of the governor is to receive + * a temperature report from a thermal sensor and calculate the current + * thermal zone. Each zone will sort through a list of cooling agents + * passed in to determine the correct cooling stategy that will cool the + * device appropriately for that zone. + * + * The temperature that is reported by the temperature sensor is first + * calculated to an OMAP hot spot temperature. + * This takes care of the existing temperature gradient between + * the OMAP hot spot and the on-die temp sensor. + * Note: The "slope" parameter is multiplied by 1000 in the configuration + * file to avoid using floating values. + * Note: The "offset" is defined in milli-celsius degrees. + * + * Next the hot spot temperature is then compared to thresholds to determine + * the proper zone. + * + * There are 5 zones identified: + * + * FATAL_ZONE: This zone indicates that the on-die temperature has reached + * a point where the device needs to be rebooted and allow ROM or the bootloader + * to run to allow the device to cool. + * + * PANIC_ZONE: This zone indicates a near fatal temperature is approaching + * and should impart all neccessary cooling agent to bring the temperature + * down to an acceptable level. + * + * ALERT_ZONE: This zone indicates that die is at a level that may need more + * agressive cooling agents to keep or lower the temperature. + * + * MONITOR_ZONE: This zone is used as a monitoring zone and may or may not use + * cooling agents to hold the current temperature. + * + * SAFE_ZONE: This zone is optimal thermal zone. It allows the device to + * run at max levels without imparting any cooling agent strategies. + * + * NO_ACTION: Means just that. There was no action taken based on the current + * temperature sent in. +*/ + +/** + * omap_update_report_rate() - Updates the temperature sensor monitoring rate + * + * @new_rate: New measurement rate for the temp sensor + * + * No return + */ +static void omap_update_report_rate(struct thermal_dev *temp_sensor, + int new_rate) +{ + if (omap_gov->report_rate == -EOPNOTSUPP) { + pr_err("%s:Updating the report rate is not supported\n", + __func__); + return; + } + + if (omap_gov->report_rate != new_rate) + omap_gov->report_rate = + thermal_update_temp_rate(temp_sensor, new_rate); +} + +/* + * convert_omap_sensor_temp_to_hotspot_temp() -Convert the temperature from the + * OMAP on-die temp sensor into OMAP hot spot temperature. + * This takes care of the existing temperature gradient between + * the OMAP hot spot and the on-die temp sensor. + * + * @sensor_temp: Raw temperature reported by the OMAP die temp sensor + * + * Returns the calculated hot spot temperature for the zone calculation + */ +static signed int convert_omap_sensor_temp_to_hotspot_temp(int sensor_temp) +{ + int absolute_delta; + + absolute_delta = ((sensor_temp * omap_gradient_slope / 1000) + + omap_gradient_const); + return sensor_temp + absolute_delta; +} + +/* + * hotspot_temp_to_omap_sensor_temp() - Convert the temperature from + * the OMAP hot spot temperature into the OMAP on-die temp sensor. + * This is useful to configure the thresholds at OMAP on-die + * sensor level. This takes care of the existing temperature + * gradient between the OMAP hot spot and the on-die temp sensor. + * + * @hot_spot_temp: Hot spot temperature to the be calculated to CPU on-die + * temperature value. + * + * Returns the calculated cpu on-die temperature. + */ + +static signed hotspot_temp_to_sensor_temp(int hot_spot_temp) +{ + return ((hot_spot_temp - omap_gradient_const) * 1000) / + (1000 + omap_gradient_slope); +} + +/** + * omap_safe_zone() - THERMAL "Safe Zone" definition: + * - No constraint about Max CPU frequency + * - No constraint about CPU freq governor + * - Normal temperature monitoring rate + * + * @cooling_list: The list of cooling devices available to cool the zone + * @cpu_temp: The current adjusted CPU temperature + * + * Returns 0 on success and -ENODEV for no cooling devices available to cool + */ +static int omap_safe_zone(struct list_head *cooling_list, int cpu_temp) +{ + struct thermal_dev *cooling_dev, *tmp; + int die_temp_upper = 0; + int die_temp_lower = 0; + pr_debug("%s:hot spot temp %d\n", __func__, cpu_temp); + /* TO DO: need to build an algo to find the right cooling agent */ + list_for_each_entry_safe(cooling_dev, tmp, cooling_list, node) { + if (cooling_dev->dev_ops && + cooling_dev->dev_ops->cool_device) { + /* TO DO: Add cooling agents to a list here */ + list_add(&cooling_dev->node, &cooling_agents); + goto out; + } else { + pr_info("%s:Cannot find cool_device for %s\n", + __func__, cooling_dev->name); + } + } +out: + if (list_empty(&cooling_agents)) { + pr_err("%s: No Cooling devices registered\n", + __func__); + return -ENODEV; + } else { + omap_gov->cooling_level = 0; + thermal_cooling_set_level(&cooling_agents, + omap_gov->cooling_level); + list_del_init(&cooling_agents); + die_temp_lower = hotspot_temp_to_sensor_temp( + OMAP_MONITOR_TEMP - HYSTERESIS_VALUE); + die_temp_upper = hotspot_temp_to_sensor_temp(OMAP_MONITOR_TEMP); + thermal_update_temp_thresholds(omap_gov->temp_sensor, + die_temp_lower, die_temp_upper); + omap_update_report_rate(omap_gov->temp_sensor, + NORMAL_TEMP_MONITORING_RATE); + omap_gov->panic_zone_reached = 0; + } + + return 0; +} + +/** + * omap_monitor_zone() - Current device is in a situation that requires + * monitoring and may impose agents to keep the device + * at a steady state temperature or moderately cool the + * device. + * + * @cooling_list: The list of cooling devices available to cool the zone + * @cpu_temp: The current adjusted CPU temperature + * + * Returns 0 on success and -ENODEV for no cooling devices available to cool + */ +static int omap_monitor_zone(struct list_head *cooling_list, int cpu_temp) +{ + struct thermal_dev *cooling_dev, *tmp; + int die_temp_upper = 0; + int die_temp_lower = 0; + + pr_debug("%s:hot spot temp %d\n", __func__, cpu_temp); + /* TO DO: need to build an algo to find the right cooling agent */ + list_for_each_entry_safe(cooling_dev, tmp, cooling_list, node) { + if (cooling_dev->dev_ops && + cooling_dev->dev_ops->cool_device) { + /* TO DO: Add cooling agents to a list here */ + list_add(&cooling_dev->node, &cooling_agents); + goto out; + } else { + pr_info("%s:Cannot find cool_device for %s\n", + __func__, cooling_dev->name); + } + } +out: + if (list_empty(&cooling_agents)) { + pr_err("%s: No Cooling devices registered\n", + __func__); + return -ENODEV; + } else { + omap_gov->cooling_level = 0; + thermal_cooling_set_level(&cooling_agents, + omap_gov->cooling_level); + list_del_init(&cooling_agents); + die_temp_lower = hotspot_temp_to_sensor_temp( + OMAP_MONITOR_TEMP - HYSTERESIS_VALUE); + die_temp_upper = + hotspot_temp_to_sensor_temp(OMAP_ALERT_TEMP); + thermal_update_temp_thresholds(omap_gov->temp_sensor, + die_temp_lower, die_temp_upper); + omap_update_report_rate(omap_gov->temp_sensor, + FAST_TEMP_MONITORING_RATE); + omap_gov->panic_zone_reached = 0; + } + + return 0; +} +/** + * omap_alert_zone() - "Alert Zone" definition: + * - If the Panic Zone has never been reached, then + * - Define constraint about Max CPU frequency + * if Current frequency < Max frequency, + * then Max frequency = Current frequency + * - Else keep the constraints set previously until + * temperature falls to safe zone + * + * @cooling_list: The list of cooling devices available to cool the zone + * @cpu_temp: The current adjusted CPU temperature + * + * Returns 0 on success and -ENODEV for no cooling devices available to cool + */ +static int omap_alert_zone(struct list_head *cooling_list, int cpu_temp) +{ + struct thermal_dev *cooling_dev, *tmp; + int die_temp_upper = 0; + int die_temp_lower = 0; + + pr_debug("%s:hot spot temp %d\n", __func__, cpu_temp); + /* TO DO: need to build an algo to find the right cooling agent */ + list_for_each_entry_safe(cooling_dev, tmp, cooling_list, node) { + if (cooling_dev->dev_ops && + cooling_dev->dev_ops->cool_device) { + /* TO DO: Add cooling agents to a list here */ + list_add(&cooling_dev->node, &cooling_agents); + goto out; + } else { + pr_info("%s:Cannot find cool_device for %s\n", + __func__, cooling_dev->name); + } + } +out: + if (list_empty(&cooling_agents)) { + pr_err("%s: No Cooling devices registered\n", + __func__); + return -ENODEV; + } else { + if (omap_gov->panic_zone_reached == 0) { + /* Temperature rises and enters into alert zone */ + omap_gov->cooling_level = 0; + thermal_cooling_set_level(&cooling_agents, + omap_gov->cooling_level); + } + + list_del_init(&cooling_agents); + die_temp_lower = hotspot_temp_to_sensor_temp( + OMAP_ALERT_TEMP - HYSTERESIS_VALUE); + die_temp_upper = hotspot_temp_to_sensor_temp( + OMAP_PANIC_TEMP); + thermal_update_temp_thresholds(omap_gov->temp_sensor, + die_temp_lower, die_temp_upper); + omap_update_report_rate(omap_gov->temp_sensor, + FAST_TEMP_MONITORING_RATE); + } + + return 0; +} + +/** + * omap_panic_zone() - Force CPU frequency to a "safe frequency" + * . Force the CPU frequency to a “safe” frequency + * . Limit max CPU frequency to the “safe” frequency + * + * @cooling_list: The list of cooling devices available to cool the zone + * @cpu_temp: The current adjusted CPU temperature + * + * Returns 0 on success and -ENODEV for no cooling devices available to cool + */ +static int omap_panic_zone(struct list_head *cooling_list, int cpu_temp) +{ + struct thermal_dev *cooling_dev, *tmp; + int die_temp_upper = 0; + int die_temp_lower = 0; + + pr_debug("%s:hot spot temp %d\n", __func__, cpu_temp); + /* TO DO: need to build an algo to find the right cooling agent */ + list_for_each_entry_safe(cooling_dev, tmp, cooling_list, node) { + if (cooling_dev->dev_ops && + cooling_dev->dev_ops->cool_device) { + /* TO DO: Add cooling agents to a list here */ + list_add(&cooling_dev->node, &cooling_agents); + goto out; + } else { + pr_info("%s:Cannot find cool_device for %s\n", + __func__, cooling_dev->name); + } + } +out: + if (list_empty(&cooling_agents)) { + pr_err("%s: No Cooling devices registered\n", + __func__); + return -ENODEV; + } else { + omap_gov->cooling_level++; + omap_gov->panic_zone_reached++; + pr_debug("%s: Panic zone reached %i times\n", + __func__, omap_gov->panic_zone_reached); + thermal_cooling_set_level(&cooling_agents, + omap_gov->cooling_level); + list_del_init(&cooling_agents); + die_temp_lower = hotspot_temp_to_sensor_temp( + OMAP_PANIC_TEMP - HYSTERESIS_VALUE); + + /* Set the threshold window to below fatal. This way the + * governor can manage the thermal if the temp should rise + * while throttling. We need to be agressive with throttling + * should we reach this zone. */ + die_temp_upper = (((OMAP_FATAL_TEMP - OMAP_PANIC_TEMP) / 4) * + omap_gov->panic_zone_reached) + OMAP_PANIC_TEMP; + if (die_temp_upper >= OMAP_FATAL_TEMP) + die_temp_upper = OMAP_FATAL_TEMP; + + die_temp_upper = hotspot_temp_to_sensor_temp(die_temp_upper); + thermal_update_temp_thresholds(omap_gov->temp_sensor, + die_temp_lower, die_temp_upper); + omap_update_report_rate(omap_gov->temp_sensor, + FAST_TEMP_MONITORING_RATE); + } + + return 0; +} + +/** + * omap_fatal_zone() - Shut-down the system to ensure OMAP Junction + * temperature decreases enough + * + * @cpu_temp: The current adjusted CPU temperature + * + * No return forces a restart of the system + */ +static void omap_fatal_zone(int cpu_temp) +{ + pr_emerg("%s:FATAL ZONE (hot spot temp: %i)\n", __func__, cpu_temp); + + kernel_restart(NULL); +} + +static int omap_cpu_thermal_manager(struct list_head *cooling_list, int temp) +{ + int cpu_temp; + + cpu_temp = convert_omap_sensor_temp_to_hotspot_temp(temp); + if (cpu_temp >= OMAP_FATAL_TEMP) { + omap_fatal_zone(cpu_temp); + return FATAL_ZONE; + } else if (cpu_temp >= OMAP_PANIC_TEMP) { + omap_panic_zone(cooling_list, cpu_temp); + return PANIC_ZONE; + } else if (cpu_temp < (OMAP_PANIC_TEMP - HYSTERESIS_VALUE)) { + if (cpu_temp >= OMAP_ALERT_TEMP) { + omap_alert_zone(cooling_list, cpu_temp); + return ALERT_ZONE; + } else if (cpu_temp < (OMAP_ALERT_TEMP - HYSTERESIS_VALUE)) { + if (cpu_temp >= OMAP_MONITOR_TEMP) { + omap_monitor_zone(cooling_list, cpu_temp); + return MONITOR_ZONE; + } else { + /* + * this includes the case where : + * (OMAP_MONITOR_TEMP - HYSTERESIS_VALUE) <= T + * && T < OMAP_MONITOR_TEMP + */ + omap_safe_zone(cooling_list, cpu_temp); + return SAFE_ZONE; + } + } else { + /* + * this includes the case where : + * (OMAP_ALERT_TEMP - HYSTERESIS_VALUE) <= + * T < OMAP_ALERT_TEMP + */ + omap_monitor_zone(cooling_list, cpu_temp); + return MONITOR_ZONE; + } + } else { + /* + * this includes the case where : + * (OMAP_PANIC_TEMP - HYSTERESIS_VALUE) <= T < OMAP_PANIC_TEMP + */ + omap_alert_zone(cooling_list, cpu_temp); + return ALERT_ZONE; + } + + return NO_ACTION; + +} + +static int omap_process_cpu_temp(struct list_head *cooling_list, + struct thermal_dev *temp_sensor, + int temp) +{ + pr_debug("%s: Received temp %i\n", __func__, temp); + omap_gov->temp_sensor = temp_sensor; + return omap_cpu_thermal_manager(cooling_list, temp); +} + +static struct thermal_dev_ops omap_gov_ops = { + .process_temp = omap_process_cpu_temp, +}; + +static int __init omap_die_governor_init(void) +{ + struct thermal_dev *thermal_fw; + omap_gov = kzalloc(sizeof(struct omap_die_governor), GFP_KERNEL); + if (!omap_gov) { + pr_err("%s:Cannot allocate memory\n", __func__); + return -ENOMEM; + } + + thermal_fw = kzalloc(sizeof(struct thermal_dev), GFP_KERNEL); + if (thermal_fw) { + thermal_fw->name = "omap_ondie_governor"; + thermal_fw->domain_name = "cpu"; + thermal_fw->dev_ops = &omap_gov_ops; + thermal_governor_dev_register(thermal_fw); + therm_fw = thermal_fw; + } else { + pr_err("%s: Cannot allocate memory\n", __func__); + kfree(omap_gov); + return -ENOMEM; + } + + omap_gradient_slope = thermal_get_slope(thermal_fw); + omap_gradient_const = thermal_get_offset(thermal_fw); + + return 0; +} + +static void __exit omap_die_governor_exit(void) +{ + thermal_governor_dev_unregister(therm_fw); + kfree(therm_fw); + kfree(omap_gov); +} + +module_init(omap_die_governor_init); +module_exit(omap_die_governor_exit); + +MODULE_AUTHOR("Dan Murphy <DMurphy@ti.com>"); +MODULE_DESCRIPTION("OMAP on-die thermal governor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/thermal_framework/thermal_framework.c b/drivers/staging/thermal_framework/thermal_framework.c new file mode 100644 index 00000000000..a1368edaa43 --- /dev/null +++ b/drivers/staging/thermal_framework/thermal_framework.c @@ -0,0 +1,597 @@ +/* + * drivers/thermal/thermal_framework.c + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Dan Murphy <DMurphy@ti.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <linux/slab.h> + +#include <linux/thermal_framework.h> + +static LIST_HEAD(thermal_domain_list); +static DEFINE_MUTEX(thermal_domain_list_lock); + +static atomic_t device_count; + +/** + * DOC: Introduction + * ================= + * The Thermal Framework is designed to be a central location to link + * temperature sensor drivers, governors and cooling agents together. + * The principle is to have one temperature sensor to one governor to many + * cooling agents. This model allows the governors to impart cooling policies + * based on the available cooling agents for a specific domain. + * + * The temperature sensor device should register to the framework and report + * the temperature of the current domain for which it reports a + * temperature measurement. + * + * The governor is responsible for imparting the cooling policy for the specific + * domain. The governor will be given a list of cooling agents that it can call + * to cool the domain. + * + * The cooling agent's primary responsibility is to perform an operation on the + * device to cool the domain it is responsible for. + * + * The sensor, governor and the cooling agents are linked in the framework + * via the domain_name in the thermal_dev structure. +*/ +/** + * struct thermal_domain - Structure that contains the pointers to the + * sensor, governor and a list of cooling agents. + * @domain_name: The domain that the thermal structure represents + * @node: The list node of the thermal domain + * @temp_sensor: The domains temperature sensor thermal device pointer. + * @governor: The domain governor thermal device pointer. + * @cooling_agents: The domains list of available cooling agents + * + */ +struct thermal_domain { + const char *domain_name; + struct list_head node; + struct thermal_dev *temp_sensor; + struct thermal_dev *governor; + struct list_head cooling_agents; +}; + +/** + * thermal_cooling_set_level() - Calls a list of cooling devices to cool the + * thermal domain + * @cooling_list: A list of cooling agents to call to cool the domain + * @level: The level of cooling that the agent should perform + * + * Returns 0 for a successfull call to the cooling agent. ENODEV if a cooling + * agent does not exist. + */ +int thermal_cooling_set_level(struct list_head *cooling_list, + unsigned int level) +{ + struct thermal_dev *cooling_dev; + int ret = -ENODEV; + + list_for_each_entry(cooling_dev, cooling_list, node) { + if (cooling_dev->dev_ops && + cooling_dev->dev_ops->cool_device) { + pr_debug("%s:Found %s\n", __func__, cooling_dev->name); + cooling_dev->dev_ops->cool_device(cooling_dev, level); + ret = 0; + } else { + pr_err("%s:Cannot find cool_device for %s\n", + __func__, cooling_dev->name); + } + } + return ret; +} +EXPORT_SYMBOL_GPL(thermal_cooling_set_level); + +/** + * thermal_sensor_set_temp() - External API to allow a sensor driver to set + * the current temperature for a domain + * @tdev: The thermal device setting the temperature + * + * Returns 0 for a successfull call to a governor. ENODEV if a governor does not + * exist. + */ +int thermal_sensor_set_temp(struct thermal_dev *tdev) +{ + struct thermal_domain *thermal_domain; + int ret = -ENODEV; + + if (list_empty(&thermal_domain_list)) { + pr_debug("%s: No governors registered\n", __func__); + goto out; + } + + list_for_each_entry(thermal_domain, &thermal_domain_list, node) + if (!strcmp(thermal_domain->domain_name, tdev->domain_name)) { + if (!thermal_domain->cooling_agents.next) { + pr_debug("%s:No cooling agents for domain %s\n", + __func__, thermal_domain->domain_name); + goto out; + } + + if (thermal_domain->governor && + thermal_domain->governor->dev_ops && + thermal_domain->governor->dev_ops->process_temp) { + thermal_domain->governor->dev_ops->process_temp + (&thermal_domain->cooling_agents, + tdev, tdev->current_temp); + ret = 0; + goto out; + } else { + pr_debug("%s:Gov did not have right function\n", + __func__); + goto out; + } + + } +out: + return ret; +} +EXPORT_SYMBOL_GPL(thermal_sensor_set_temp); + +/* + * thermal_get_slope() - External API for the sensor driver to + * get the Slope + * for Hotspot temperature computation + */ +int thermal_get_slope(struct thermal_dev *tdev) +{ + struct thermal_domain *thermal_domain; + int ret = -ENODEV; + + if (list_empty(&thermal_domain_list)) + pr_info("%s: No sensors registered\n", __func__); + + list_for_each_entry(thermal_domain, &thermal_domain_list, node) + if (!strcmp(thermal_domain->domain_name, tdev->domain_name)) + goto found; + pr_err("%s: Domain not found\n", __func__); + return ret; +found: + + if (thermal_domain->temp_sensor && + thermal_domain->temp_sensor->dev_ops && + thermal_domain->temp_sensor->dev_ops->init_slope) { + return thermal_domain->temp_sensor->dev_ops->init_slope + (thermal_domain->temp_sensor); + } else { + pr_err("%s:Getting slope is not supported for domain %s\n", + __func__, thermal_domain->domain_name); + return -EINVAL; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(thermal_get_slope); + +/* + * thermal_get_slope() - External API for the sensor driver to + * get the Slope + * for Hotspot temperature computation + */ +int thermal_get_offset(struct thermal_dev *tdev) +{ + struct thermal_domain *thermal_domain; + int ret = -ENODEV; + + if (list_empty(&thermal_domain_list)) + pr_debug("%s: No sensors registered\n", __func__); + list_for_each_entry(thermal_domain, &thermal_domain_list, node) + if (!strcmp(thermal_domain->domain_name, tdev->domain_name)) + goto found; + pr_err("%s: Domain not found\n", __func__); + return ret; +found: + if (thermal_domain->temp_sensor && + thermal_domain->temp_sensor->dev_ops && + thermal_domain->temp_sensor->dev_ops->init_offset) { + return thermal_domain->temp_sensor->dev_ops->init_offset + (thermal_domain->temp_sensor); + } else { + pr_err("%s:Getting offset is not supported for domain %s\n", + __func__, thermal_domain->domain_name); + return -EINVAL; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(thermal_get_offset); + +/** + * thermal_request_temp() - Requests the thermal sensor to report it's current + * temperature to the governor. + * + * @tdev: The agent requesting the updated temperature. + * + * Returns the current temperature of the current domain. + * ENODEV if the temperature sensor does not exist. + * EOPNOTSUPP if the temperature sensor does not support this API. + */ +int thermal_request_temp(struct thermal_dev *tdev) +{ + struct thermal_domain *thermal_domain; + int ret = -ENODEV; + + if (list_empty(&thermal_domain_list)) { + pr_debug("%s: No thermal sensors registered\n", __func__); + return ret; + } + + mutex_lock(&thermal_domain_list_lock); + list_for_each_entry(thermal_domain, &thermal_domain_list, node) + if (!strcmp(thermal_domain->domain_name, tdev->domain_name)) + goto report; + mutex_unlock(&thermal_domain_list_lock); + pr_err("%s: Domain not found\n", __func__); + return ret; +report: + mutex_unlock(&thermal_domain_list_lock); + if (thermal_domain->temp_sensor && + thermal_domain->temp_sensor->dev_ops && + thermal_domain->temp_sensor->dev_ops->report_temp) { + ret = thermal_domain->temp_sensor->dev_ops->report_temp + (thermal_domain->temp_sensor); + } else { + pr_err("%s:Getting temp is not supported for domain %s\n", + __func__, thermal_domain->domain_name); + ret = -EOPNOTSUPP; + } + return ret; +} +EXPORT_SYMBOL_GPL(thermal_request_temp); + +/** + * thermal_update_temp_thresholds() - Update the temperature reporting + * thresholds on the temp sensor. + * + * @min: The minimum temperature that should trigger a temperature event. + * @max: The maximum temperature that should trigger a temperature event. + * + * EOPNOTSUPP if the temperature sensor does not support this API. + */ +int thermal_update_temp_thresholds(struct thermal_dev *temp_sensor, + int min, int max) +{ + if (temp_sensor) { + if ((temp_sensor->dev_ops) && + (temp_sensor->dev_ops->set_temp_thresh)) { + pr_debug("%s: Setting new temp thresholds to %i & %i\n", + __func__, min, max); + temp_sensor->dev_ops->set_temp_thresh(temp_sensor, + min, max); + return 0; + } else + pr_err("%s:Updating temp thresholds is not supported \ + for sensor %s\n", __func__, temp_sensor->name); + } else + pr_err("%s:Temp sensor pointer is NULL\n", __func__); + + return -EOPNOTSUPP; +} +EXPORT_SYMBOL_GPL(thermal_update_temp_thresholds); + +/** + * thermal_update_temp_rate() - Update the rate for the temp sensor to read and + * report the temperature data. + * + * @rate: The rate to read and report the data to the governor. Rate is defined + * per the temperature sensor driver + * + * Return is the current rate of the temperature acquisition. + */ +int thermal_update_temp_rate(struct thermal_dev *temp_sensor, int rate) +{ + int ret_rate = -EOPNOTSUPP; + if (temp_sensor) { + if ((temp_sensor->dev_ops) && + (temp_sensor->dev_ops->set_temp_report_rate)) { + pr_debug("%s: Setting new temp report rate to %i\n", + __func__, rate); + ret_rate = temp_sensor->dev_ops->set_temp_report_rate(temp_sensor, rate); + } + } else + pr_err("%s:Temp sensor pointer is NULL\n", __func__); + + return ret_rate; +} +EXPORT_SYMBOL_GPL(thermal_update_temp_rate); + +/** + * thermal_init_thermal_state() - Initialize the domain thermal state machine. + * Once all the thermal devices are available in the domain. + * Force the thermal sensor for the domain to report it's current + * temperature. + * + * @tdev: The thermal device that just registered + * + * Returns -ENODEV for empty lists and 0 for success + */ +static int thermal_init_thermal_state(struct thermal_dev *tdev) +{ + struct thermal_domain *domain; + + if (list_empty(&thermal_domain_list)) { + pr_err("%s: No cooling devices registered\n", __func__); + return -ENODEV; + } + + mutex_lock(&thermal_domain_list_lock); + list_for_each_entry(domain, &thermal_domain_list, node) { + if (!strcmp(domain->domain_name, tdev->domain_name)) + goto check_domain; + } + +check_domain: + mutex_unlock(&thermal_domain_list_lock); + if (domain->temp_sensor && + domain->governor && + domain->cooling_agents.next) + thermal_request_temp(tdev); + else + pr_debug("%s:Not all components registered for %s domain\n", + __func__, domain->domain_name); + + return 0; +} + +/** + * thermal_governor_dev_register() - Registration call for thermal domain governors + * + * @tdev: The thermal governor device structure. + * + * Returns 0 for a successful registration of the governor to a domain + * ENOMEM if the domain could not be created. + * + */ +int thermal_governor_dev_register(struct thermal_dev *tdev) +{ + struct thermal_domain *therm_dom; + struct thermal_domain *domain; + + tdev->index = atomic_inc_return(&device_count); + if (list_empty(&thermal_domain_list)) { + goto init_governor; + } else { + mutex_lock(&thermal_domain_list_lock); + list_for_each_entry(domain, &thermal_domain_list, node) { + if (!strcmp(domain->domain_name, tdev->domain_name)) { + pr_debug("%s:Found %s\n", __func__, + domain->domain_name); + domain->governor = tdev; + goto out; + } + } + mutex_unlock(&thermal_domain_list_lock); + } + +init_governor: + therm_dom = kzalloc(sizeof(struct thermal_domain), GFP_KERNEL); + if (!therm_dom) { + pr_err("%s:Cannot allocate domain memory\n", __func__); + return -ENOMEM; + } + + therm_dom->governor = tdev; + therm_dom->domain_name = tdev->domain_name; + pr_info("%s:Adding %s governor\n", __func__, tdev->name); + mutex_lock(&thermal_domain_list_lock); + list_add(&therm_dom->node, &thermal_domain_list); +out: + mutex_unlock(&thermal_domain_list_lock); + thermal_init_thermal_state(tdev); + + return 0; +} +EXPORT_SYMBOL_GPL(thermal_governor_dev_register); + +/** + * thermal_governor_dev_unregister() - Unregistration call for thermal domain + * governors. + * + * @tdev: The thermal governor device structure. + * + */ +void thermal_governor_dev_unregister(struct thermal_dev *tdev) +{ + struct thermal_domain *domain; + + mutex_lock(&thermal_domain_list_lock); + + list_for_each_entry(domain, &thermal_domain_list, node) { + if (!strcmp(domain->domain_name, tdev->domain_name)) { + kfree(domain->governor); + domain->governor = NULL; + } + } + + mutex_unlock(&thermal_domain_list_lock); + return; +} +EXPORT_SYMBOL_GPL(thermal_governor_dev_unregister); + +/** + * thermal_cooling_dev_register() - Registration call for cooling agents + * + * @tdev: The cooling agent device structure. + * + * Returns 0 for a successful registration of a cooling agent to a domain + * ENOMEM if the domain could not be created. + */ +int thermal_cooling_dev_register(struct thermal_dev *tdev) +{ + struct thermal_domain *therm_dom; + struct thermal_domain *domain; + + pr_debug("%s:Registering %s cooling device\n", __func__, tdev->name); + tdev->index = atomic_inc_return(&device_count); + + if (list_empty(&thermal_domain_list)) { + goto init_cooling_agent; + } else { + mutex_lock(&thermal_domain_list_lock); + list_for_each_entry(domain, &thermal_domain_list, node) { + pr_debug("%s:Found %s %s\n", __func__, + domain->domain_name, tdev->domain_name); + if (!strcmp(domain->domain_name, tdev->domain_name)) { + if (!domain->cooling_agents.next) + INIT_LIST_HEAD(&domain->cooling_agents); + list_add(&tdev->node, &domain->cooling_agents); + goto out; + } + } + mutex_unlock(&thermal_domain_list_lock); + } + +init_cooling_agent: + therm_dom = kzalloc(sizeof(struct thermal_domain), GFP_KERNEL); + if (!therm_dom) { + pr_err("%s:Cannot allocate domain memory\n", __func__); + return -ENOMEM; + } + + therm_dom->domain_name = tdev->domain_name; + INIT_LIST_HEAD(&therm_dom->cooling_agents); + list_add(&tdev->node, &therm_dom->cooling_agents); + mutex_lock(&thermal_domain_list_lock); + list_add(&therm_dom->node, &thermal_domain_list); +out: + mutex_unlock(&thermal_domain_list_lock); + thermal_init_thermal_state(tdev); + + return 0; +} +EXPORT_SYMBOL_GPL(thermal_cooling_dev_register); + +/** + * thermal_cooling_dev_unregister() - Unregistration call for cooling agents + * + * @tdev: The cooling agent device structure. + * + */ +void thermal_cooling_dev_unregister(struct thermal_dev *tdev) +{ + struct thermal_domain *domain; + + mutex_lock(&thermal_domain_list_lock); + + list_for_each_entry(domain, &thermal_domain_list, node) { + if (!strcmp(domain->domain_name, tdev->domain_name)) + list_del(domain->cooling_agents.next); + } + + mutex_unlock(&thermal_domain_list_lock); + return; +} +EXPORT_SYMBOL_GPL(thermal_cooling_dev_unregister); + +/** + * thermal_sensor_dev_register() - Registration call for temperature sensors + * + * @tdev: The temperature device structure. + * + * Returns 0 for a successful registration of the temp sensor to a domain + * ENOMEM if the domain could not be created. + */ +int thermal_sensor_dev_register(struct thermal_dev *tdev) +{ + struct thermal_domain *therm_dom; + struct thermal_domain *domain; + + tdev->index = atomic_inc_return(&device_count); + if (list_empty(&thermal_domain_list)) { + pr_debug("%s:Need to init the %s domain\n", + __func__, tdev->domain_name); + goto init_sensor; + } else { + mutex_lock(&thermal_domain_list_lock); + list_for_each_entry(domain, &thermal_domain_list, node) { + pr_debug("%s:Found %s %s\n", __func__, + domain->domain_name, tdev->domain_name); + if (!strcmp(domain->domain_name, tdev->domain_name)) { + pr_info("%s:Adding %s sensor\n", + __func__, tdev->name); + domain->temp_sensor = tdev; + goto out; + } + } + mutex_unlock(&thermal_domain_list_lock); + } + +init_sensor: + therm_dom = kzalloc(sizeof(struct thermal_domain), + GFP_KERNEL); + if (!therm_dom) { + pr_err("%s:Cannot allocate domain memory\n", + __func__); + return -ENOMEM; + } + therm_dom->temp_sensor = tdev; + therm_dom->domain_name = tdev->domain_name; + pr_debug("%s:Adding %s sensor\n", __func__, tdev->name); + mutex_lock(&thermal_domain_list_lock); + list_add(&therm_dom->node, &thermal_domain_list); +out: + mutex_unlock(&thermal_domain_list_lock); + thermal_init_thermal_state(tdev); + return 0; +} +EXPORT_SYMBOL_GPL(thermal_sensor_dev_register); + +/** + * thermal_sensor_dev_unregister() - Unregistration call for temperature sensors + * + * @tdev: The temperature device structure. + * + */ +void thermal_sensor_dev_unregister(struct thermal_dev *tdev) +{ + struct thermal_domain *domain; + + mutex_lock(&thermal_domain_list_lock); + + list_for_each_entry(domain, &thermal_domain_list, node) { + if (!strcmp(domain->domain_name, tdev->domain_name)) { + kfree(domain->temp_sensor); + domain->temp_sensor = NULL; + } + } + + mutex_unlock(&thermal_domain_list_lock); + return; +} +EXPORT_SYMBOL_GPL(thermal_sensor_dev_unregister); + +static int __init thermal_framework_init(void) +{ + return 0; +} + +static void __exit thermal_framework_exit(void) +{ + return; +} + +module_init(thermal_framework_init); +module_exit(thermal_framework_exit); + +MODULE_AUTHOR("Dan Murphy <DMurphy@ti.com>"); +MODULE_DESCRIPTION("Thermal Framework driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/thermal_framework.h b/include/linux/thermal_framework.h new file mode 100644 index 00000000000..f7a9c2783c3 --- /dev/null +++ b/include/linux/thermal_framework.h @@ -0,0 +1,96 @@ +/* + * Thermal Framework Driver + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Dan Murphy <DMurphy@ti.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ + +#ifndef __LINUX_THERMAL_FRAMEWORK_H__ +#define __LINUX_THERMAL_FRAMEWORK_H__ + +struct thermal_dev; + +/** + * struct thermal_dev_ops - Structure for device operation call backs + * @get_temp: A temp sensor call back to get the current temperature. + * temp is reported in milli degrees. + * @set_temp_thresh: Update the temperature sensor thresholds. This can be used + * to allow the sensor to only report changes when the thresholds + * have been crossed. + * @set_temp_report_rate: Update the rate at which the temperature sensor + * reports the temperature change. This API should return the +* current measurement rate that the sensor is measuring at. + * @cool_device: The cooling agent call back to process a list of cooling agents + * @process_temp: The governors call back for processing a domain temperature + * + */ +struct thermal_dev_ops { + /* Sensor call backs */ + int (*report_temp) (struct thermal_dev *); + int (*set_temp_thresh) (struct thermal_dev *temp_sensor, + int min, int max); + int (*set_temp_report_rate) (struct thermal_dev *, int rate); + int (*init_slope) (struct thermal_dev *); + int (*init_offset) (struct thermal_dev *); + /* Cooling agent call backs */ + int (*cool_device) (struct thermal_dev *, int temp); + /* Governor call backs */ + int (*process_temp) (struct list_head *cooling_list, + struct thermal_dev *temp_sensor, int temp); +}; + +/** + * struct thermal_dev - Structure for each thermal device. + * @name: The name of the device that is registering to the framework + * @domain_name: The temperature domain that the thermal device represents + * @dev: Device node + * @dev_ops: The device specific operations for the sensor, governor and cooling + * agents. + * @node: The list node of the + * @index: The index of the device created. + * @current_temp: The current temperature reported for the specific domain + * + */ +struct thermal_dev { + const char *name; + const char *domain_name; + struct device *dev; + struct thermal_dev_ops *dev_ops; + struct list_head node; + int index; + int current_temp; + int slope; + int constant_offset; + int sen_id; + +}; + +extern int thermal_update_temp_thresholds(struct thermal_dev *temp_sensor, + int min, int max); +extern int thermal_request_temp(struct thermal_dev *tdev); +extern int thermal_sensor_set_temp(struct thermal_dev *tdev); +extern int thermal_get_slope(struct thermal_dev *tdev); +extern int thermal_get_offset(struct thermal_dev *tdev); +extern int thermal_update_temp_rate(struct thermal_dev *temp_sensor, int rate); +extern int thermal_cooling_set_level(struct list_head *cooling_list, + unsigned int level); + +/* Registration and unregistration calls for the thermal devices */ +extern int thermal_sensor_dev_register(struct thermal_dev *tdev); +extern void thermal_sensor_dev_unregister(struct thermal_dev *tdev); +extern int thermal_cooling_dev_register(struct thermal_dev *tdev); +extern void thermal_cooling_dev_unregister(struct thermal_dev *tdev); +extern int thermal_governor_dev_register(struct thermal_dev *tdev); +extern void thermal_governor_dev_unregister(struct thermal_dev *tdev); + +#endif /* __LINUX_THERMAL_FRAMEWORK_H__ */ |