From patchwork Wed Oct 26 11:36:52 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Amit Daniel Kachhap X-Patchwork-Id: 4832 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 6705723E0B for ; Wed, 26 Oct 2011 11:37:14 +0000 (UTC) Received: from mail-fx0-f52.google.com (mail-fx0-f52.google.com [209.85.161.52]) by fiordland.canonical.com (Postfix) with ESMTP id 5A148A182D5 for ; Wed, 26 Oct 2011 11:37:14 +0000 (UTC) Received: by mail-fx0-f52.google.com with SMTP id n26so2021542faa.11 for ; Wed, 26 Oct 2011 04:37:14 -0700 (PDT) Received: by 10.223.77.71 with SMTP id f7mr58456354fak.33.1319629034248; Wed, 26 Oct 2011 04:37:14 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.152.1.71 with SMTP id 7cs9553lak; Wed, 26 Oct 2011 04:37:14 -0700 (PDT) Received: by 10.229.83.132 with SMTP id f4mr4124138qcl.148.1319629032985; Wed, 26 Oct 2011 04:37:12 -0700 (PDT) Received: from mail-qw0-f50.google.com (mail-qw0-f50.google.com [209.85.216.50]) by mx.google.com with ESMTPS id y11si359905qct.147.2011.10.26.04.37.12 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 26 Oct 2011 04:37:12 -0700 (PDT) Received-SPF: pass (google.com: domain of amitdanielk@gmail.com designates 209.85.216.50 as permitted sender) client-ip=209.85.216.50; Authentication-Results: mx.google.com; spf=pass (google.com: domain of amitdanielk@gmail.com designates 209.85.216.50 as permitted sender) smtp.mail=amitdanielk@gmail.com; dkim=pass (test mode) header.i=@gmail.com Received: by qadz3 with SMTP id z3so1581143qad.37 for ; Wed, 26 Oct 2011 04:37:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=sender:from:to:cc:subject:date:message-id:x-mailer:in-reply-to :references; bh=Vc8m8Al5A1YBSo+FEr2zJxlZVQowK67jQloJIp/X4lU=; b=Zf89WBN5QGB2kwTvK13rhTYYQsLtt+z6I+kJ2jVSI9ZSqu6Qyr1YCrAMBnSWQCPMa7 zBpOBzmcTyxfzk7GhwSXD87egjgYwz+oWRA1FJH0as9MM3lOH6UghWh8ZO7wcqOjGZwh zhOHpcHW4OEUzN3Z+ibB3OiL5clnpkLkJgFRc= Received: by 10.68.12.199 with SMTP id a7mr284485pbc.58.1319629032258; Wed, 26 Oct 2011 04:37:12 -0700 (PDT) Received: from localhost.localdomain ([115.113.119.130]) by mx.google.com with ESMTPS id 3sm5064379pbx.14.2011.10.26.04.37.10 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 26 Oct 2011 04:37:11 -0700 (PDT) Sender: amit kachhap From: Amit Daniel Kachhap To: linaro-dev@lists.linaro.org Cc: amit.kachhap@linaro.org, patches@linaro.org Subject: [RFC PATCH 4/4] ARM: Exynos4: Add thermal interface support for linux thermal layer Date: Wed, 26 Oct 2011 17:06:52 +0530 Message-Id: <1319629012-21205-4-git-send-email-amit.kachhap@linaro.org> X-Mailer: git-send-email 1.7.1 In-Reply-To: <1319628954-21177-1-git-send-email-amit.kachhap@linaro.org> References: <1319628954-21177-1-git-send-email-amit.kachhap@linaro.org> This codes uses the generic linux thermal layer and creates a bridge between temperature sensors, linux thermal framework and cooling devices for samsung exynos platform. This layer recieves or monitor the temperature from the sensor and informs the generic thermal layer. It also provides the handlers for the cooling devices. Signed-off-by: Amit Daniel Kachhap --- .../mach-exynos4/include/mach/thermal_interface.h | 26 ++ drivers/staging/thermal_exynos4/Kconfig | 12 + drivers/staging/thermal_exynos4/Makefile | 5 + .../staging/thermal_exynos4/thermal_interface.c | 382 ++++++++++++++++++++ 4 files changed, 425 insertions(+), 0 deletions(-) create mode 100644 arch/arm/mach-exynos4/include/mach/thermal_interface.h create mode 100644 drivers/staging/thermal_exynos4/Kconfig create mode 100644 drivers/staging/thermal_exynos4/Makefile create mode 100644 drivers/staging/thermal_exynos4/thermal_interface.c diff --git a/arch/arm/mach-exynos4/include/mach/thermal_interface.h b/arch/arm/mach-exynos4/include/mach/thermal_interface.h new file mode 100644 index 0000000..b228033 --- /dev/null +++ b/arch/arm/mach-exynos4/include/mach/thermal_interface.h @@ -0,0 +1,26 @@ +/* linux/drivers/staging/thermal_exynos4/thermal_interface.h + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#ifndef THERMAL_INTERFACE_H +#define THERMAL_INTERFACE_H +/* CPU Zone information */ +#define PANIC_ZONE 4 +#define WARN_ZONE 3 +#define MONITOR_ZONE 2 +#define SAFE_ZONE 1 +#define NO_ACTION 0 + +#define EXYNOS_PANIC_TEMP 110 +#define EXYNOS_WARN_TEMP 100 +#define EXYNOS_MONITOR_TEMP 85 + +extern void exynos4_report_trigger(void); +extern int (*exynos4_read_sensor)(int *temp); +#endif diff --git a/drivers/staging/thermal_exynos4/Kconfig b/drivers/staging/thermal_exynos4/Kconfig new file mode 100644 index 0000000..85c6f5d --- /dev/null +++ b/drivers/staging/thermal_exynos4/Kconfig @@ -0,0 +1,12 @@ +# +# Generic Thermal Framework drivers configuration +# + +menuconfig SAMSUNG_THERMAL_INTERFACE + bool "Samsung Thermal support" + help + This is a samsung thermal interface which will be used as + a link between sensors and cooling devices with linux thermal + framework. + +source "drivers/staging/thermal_exynos4/sensor/Kconfig" diff --git a/drivers/staging/thermal_exynos4/Makefile b/drivers/staging/thermal_exynos4/Makefile new file mode 100644 index 0000000..daf393c --- /dev/null +++ b/drivers/staging/thermal_exynos4/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for sensor chip drivers. +# +obj-$(CONFIG_SAMSUNG_THERMAL_INTERFACE) += thermal_interface.o \ + sensor/ diff --git a/drivers/staging/thermal_exynos4/thermal_interface.c b/drivers/staging/thermal_exynos4/thermal_interface.c new file mode 100644 index 0000000..da15f13 --- /dev/null +++ b/drivers/staging/thermal_exynos4/thermal_interface.c @@ -0,0 +1,382 @@ +/* linux/drivers/staging/thermal_exynos4/thermal_interface.c + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#define pr_fmt(fmt) "exynos4: " fmt + +#include +#include +#include +#include +#include +#include +#include + +#define INITIAL_COOLING_LEVEL 0 +#define MAX_COOLING_LEVEL 2 + +static unsigned int idle_interval = 50; +static unsigned int active_interval = 1; +static unsigned int verbose; +static int exynos4_thermal_level; +static struct thermal_zone_device *therm_dev; +static struct thermal_cooling_device *cool_dev; +static struct platform_device *exynos4_dev; +int (*exynos4_read_sensor)(int *); + +static int exynos4_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + if (!exynos4_read_sensor) { + pr_info("Temperature sensor not initialised\n"); + *mode = THERMAL_DEVICE_DISABLED; + } else + *mode = THERMAL_DEVICE_ENABLED; + return 0; +} + +/* + * set operation mode; + * enabled: the thermal layer of the kernel takes care about + * the temperature. + * disabled: temperature sensor is not enabled. + */ +static int exynos4_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + if (!therm_dev) { + pr_notice("thermal zone not registered\n"); + return 0; + } + if (mode == THERMAL_DEVICE_ENABLED) + therm_dev->polling_delay = active_interval*1000; + else + therm_dev->polling_delay = idle_interval*1000; + + thermal_zone_device_update(therm_dev); + pr_info("thermal polling set for duration=%d sec\n", + therm_dev->polling_delay/1000); + return 0; +} + +/*This may be called from interrupt based temperature sensor*/ +void exynos4_report_trigger(void) +{ + therm_dev->polling_delay = active_interval*1000; + thermal_zone_device_update(therm_dev); +} + +static int exynos4_get_trip_type(struct thermal_zone_device *thermal, int trip, + enum thermal_trip_type *type) +{ + if (trip == 0 || trip == 1) + *type = THERMAL_TRIP_ACTIVE; + else if (trip == 2) + *type = THERMAL_TRIP_CRITICAL; + else + return -EINVAL; + + return 0; +} + +static int exynos4_get_trip_temp(struct thermal_zone_device *thermal, int trip, + unsigned long *temp) +{ + if (trip == 0) + *temp = EXYNOS_MONITOR_TEMP; + else if (trip == 1) + *temp = EXYNOS_WARN_TEMP; + else if (trip == 2) + *temp = EXYNOS_PANIC_TEMP; + else + return -EINVAL; + + return 0; +} + +static int exynos4_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temperature) +{ + *temperature = EXYNOS_PANIC_TEMP; + return 0; +} + +static int exynos4_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + /* if the cooling device is the one from exynos4 bind it */ + if (cdev != cool_dev) + return 0; + if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) { + pr_err("error binding cooling dev\n"); + return -EINVAL; + } + + return 0; +} + +static int exynos4_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + if (cdev != cool_dev) + return 0; + if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) { + pr_err("error unbinding cooling dev\n"); + return -EINVAL; + } + return 0; +} + +static int exynos4_get_temp(struct thermal_zone_device *thermal, + unsigned long *t) +{ + int temp, err = 0; + if (!exynos4_read_sensor) { + pr_info("Temperature sensor not initialised\n"); + return -EINVAL; + } + + err = exynos4_read_sensor(&temp); + if (err) + return err; + + if (verbose) + pr_notice("temp %d\n", temp); + + *t = temp; + return 0; +} + +/* bind callback functions to thermalzone */ +static struct thermal_zone_device_ops exynos4_dev_ops = { + .bind = exynos4_bind, + .unbind = exynos4_unbind, + .get_temp = exynos4_get_temp, + .get_mode = exynos4_get_mode, + .set_mode = exynos4_set_mode, + .get_trip_type = exynos4_get_trip_type, + .get_trip_temp = exynos4_get_trip_temp, + .get_crit_temp = exynos4_get_crit_temp, +}; + +/*Below codes defines functions to be used for cpufreq as cooling device*/ +static bool is_cpufreq_valid(void) +{ + struct cpufreq_policy policy; + if (!cpufreq_get_policy(&policy, 0)) + return true; + return false; +} + +static int exynos4_cpufreq_apply_cooling(int cooling_level) +{ + if (verbose) + pr_info("%s: %d cooling_level=%d\n", __func__, __LINE__, + cooling_level); + if (!is_cpufreq_valid()) + return 0; + exynos4_thermal_level = cooling_level; + cpufreq_update_policy(0); + if (cooling_level == INITIAL_COOLING_LEVEL) + therm_dev->polling_delay = idle_interval*1000; + return 0; +} + +static int exynos4_thermal_cpufreq_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_policy *policy = data; + unsigned long max_freq = 0; + if (event != CPUFREQ_ADJUST) + return 0; + + max_freq = + (policy->cpuinfo.max_freq * (100 - (exynos4_thermal_level*48))) / 100; + cpufreq_verify_within_limits(policy, 0, max_freq); + + return 0; +} + +static struct notifier_block exynos4_thermal_cpufreq_notifier_block = { + .notifier_call = exynos4_thermal_cpufreq_notifier, +}; + +/* + * cooling device callback functions + */ +static int exynos4_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = MAX_COOLING_LEVEL; + return 0; +} + +static int exynos4_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = exynos4_thermal_level; + return 0; +} + +static int exynos4_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + int zone, err = 0; + unsigned long cur_temp; + + err = exynos4_get_temp(therm_dev, &cur_temp); + if (err) { + pr_err("error reading temperature\n"); + return -EINVAL; + } + + if (cur_temp < EXYNOS_MONITOR_TEMP) + zone = SAFE_ZONE; + else if (cur_temp >= EXYNOS_MONITOR_TEMP && cur_temp < EXYNOS_WARN_TEMP) + zone = MONITOR_ZONE; + else if (cur_temp >= EXYNOS_WARN_TEMP) + zone = WARN_ZONE; + + if ((zone == SAFE_ZONE) && (state == INITIAL_COOLING_LEVEL)) + exynos4_cpufreq_apply_cooling(INITIAL_COOLING_LEVEL); + else if ((zone == MONITOR_ZONE) && (state > INITIAL_COOLING_LEVEL)) + exynos4_cpufreq_apply_cooling(INITIAL_COOLING_LEVEL + 1); + else if ((zone == WARN_ZONE) && (state > INITIAL_COOLING_LEVEL)) + exynos4_cpufreq_apply_cooling(INITIAL_COOLING_LEVEL + 2); + + return 0; +} + +/* bind cpufreq callbacks to cpufreq cooling device */ +static struct thermal_cooling_device_ops exynos4_cooling_ops = { + .get_max_state = exynos4_get_max_state, + .get_cur_state = exynos4_get_cur_state, + .set_cur_state = exynos4_set_cur_state, +}; + +static int __devinit exynos4_probe(struct platform_device *device) +{ + return 0; +} + +static int exynos4_remove(struct platform_device *device) +{ + return 0; +} + +static struct platform_driver exynos4_driver = { + .driver = { + .name = "exynos4", + .owner = THIS_MODULE, + }, + .probe = exynos4_probe, + .remove = exynos4_remove, +}; + +static int exynos4_register_platform(void) +{ + int err = 0; + + err = platform_driver_register(&exynos4_driver); + if (err) + return err; + + exynos4_dev = platform_device_alloc("exynos4", -1); + if (!exynos4_dev) { + err = -ENOMEM; + goto err_device_alloc; + } + err = platform_device_add(exynos4_dev); + if (err) + goto err_device_add; + + return 0; + +err_device_add: + platform_device_put(exynos4_dev); +err_device_alloc: + platform_driver_unregister(&exynos4_driver); + return err; +} + +static void exynos4_unregister_platform(void) +{ + platform_device_unregister(exynos4_dev); + platform_driver_unregister(&exynos4_driver); +} + +static int exynos4_register_thermal(void) +{ + cool_dev = thermal_cooling_device_register("exynos4-cpufreq", NULL, + &exynos4_cooling_ops); + + if (IS_ERR(cool_dev)) + return -EINVAL; + + therm_dev = thermal_zone_device_register("exynos4-therm", 3, NULL, + &exynos4_dev_ops, 0, 0, 0, + 1000); + if (IS_ERR(therm_dev)) + return -EINVAL; + + exynos4_set_mode(therm_dev, THERMAL_DEVICE_DISABLED); + + cpufreq_register_notifier(&exynos4_thermal_cpufreq_notifier_block, + CPUFREQ_POLICY_NOTIFIER); + + return 0; +} + +static void exynos4_unregister_thermal(void) +{ + if (cool_dev) { + thermal_cooling_device_unregister(cool_dev); + cool_dev = NULL; + } + + if (therm_dev) { + thermal_zone_device_unregister(therm_dev); + therm_dev = NULL; + } +} + +static int __init exynos4_init(void) +{ + int err = 0; + + err = exynos4_register_platform(); + if (err) + goto out_err; + + err = exynos4_register_thermal(); + if (err) + goto err_unreg; + + return 0; + +err_unreg: + exynos4_unregister_thermal(); + exynos4_unregister_platform(); + +out_err: + return err; +} + +static void __exit exynos4_exit(void) +{ + exynos4_unregister_thermal(); + exynos4_unregister_platform(); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Amit Daniel "); +MODULE_DESCRIPTION("Exynos4 thermal monitor driver"); + +module_init(exynos4_init); +module_exit(exynos4_exit);