From patchwork Wed Jun 20 13:04:27 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hongbo Zhang X-Patchwork-Id: 9505 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 C7BAA23F2A for ; Wed, 20 Jun 2012 13:05:45 +0000 (UTC) Received: from mail-yx0-f180.google.com (mail-yx0-f180.google.com [209.85.213.180]) by fiordland.canonical.com (Postfix) with ESMTP id 6B5D6A187F6 for ; Wed, 20 Jun 2012 13:05:45 +0000 (UTC) Received: by yenq6 with SMTP id q6so6168683yen.11 for ; Wed, 20 Jun 2012 06:05:45 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf:from:to:cc :subject:date:message-id:x-mailer:in-reply-to:references :x-gm-message-state; bh=Mc0U6Cj7f4vcgrafzQgwXle5sUocT3I8ps3TX7csjzU=; b=X617uA4iYXt6fZNA2JcP0Sl7uO+HvEWitTPkCaEUYAt4rXY1IpJsHW2/J7h4C0f8zu mFUVM+cxZh7uJZpGDaNeSzR98N+m0LYaRg1vIEQscQHayqrnRDh3QFKOBNh8+jOhg5Xw X5DF108ylLEzo4j9LdrdoynqF4G/QxwsKZXJuMVla9rPSC5Vjr2ZoiWbYRj6g4RPdZA8 1IZbJvWz15mh2/PtEfWS9hSJOH6lDJISIJ//8JCXrarW75vBnOtUI3swuOpxHZ0ltdqp Yofe/l+A5WuP/wPZx7TyS0BPGVQEwBObEmcpIW1mCNvbsrataWEehtgvEEyRNsa27KaM Vj1Q== Received: by 10.50.195.234 with SMTP id ih10mr4473057igc.0.1340197544495; Wed, 20 Jun 2012 06:05:44 -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.231.24.148 with SMTP id v20csp178906ibb; Wed, 20 Jun 2012 06:05:43 -0700 (PDT) Received: by 10.68.225.201 with SMTP id rm9mr76453678pbc.71.1340197542863; Wed, 20 Jun 2012 06:05:42 -0700 (PDT) Received: from mail-pz0-f50.google.com (mail-pz0-f50.google.com [209.85.210.50]) by mx.google.com with ESMTPS id rn8si195835pbc.217.2012.06.20.06.05.42 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 20 Jun 2012 06:05:42 -0700 (PDT) Received-SPF: neutral (google.com: 209.85.210.50 is neither permitted nor denied by best guess record for domain of hongbo.zhang@linaro.org) client-ip=209.85.210.50; Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.210.50 is neither permitted nor denied by best guess record for domain of hongbo.zhang@linaro.org) smtp.mail=hongbo.zhang@linaro.org Received: by mail-pz0-f50.google.com with SMTP id h15so11275093dan.37 for ; Wed, 20 Jun 2012 06:05:42 -0700 (PDT) Received: by 10.68.222.106 with SMTP id ql10mr5262780pbc.80.1340197542557; Wed, 20 Jun 2012 06:05:42 -0700 (PDT) Received: from stebjsxu0148.bjs.st.com ([124.127.135.94]) by mx.google.com with ESMTPS id rd7sm31856288pbc.70.2012.06.20.06.05.34 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 20 Jun 2012 06:05:41 -0700 (PDT) From: "hongbo.zhang" To: linaro-dev@lists.linaro.org Cc: patches@linaro.org, "hongbo.zhang" Subject: [PATCH 2/2] db8500 thermal dirver Date: Wed, 20 Jun 2012 21:04:27 +0800 Message-Id: <1340197467-16867-2-git-send-email-hongbo.zhang@linaro.com> X-Mailer: git-send-email 1.7.10 In-Reply-To: <1340197467-16867-1-git-send-email-hongbo.zhang@linaro.com> References: <1340197467-16867-1-git-send-email-hongbo.zhang@linaro.com> X-Gm-Message-State: ALoCoQkqVf6x1MMsNYfXv04KOqnCRGTdCZx84PnSYVY08b3jnNoG8soJ+mcNk7j/ZaB+koA1CO6d From: "hongbo.zhang" --- arch/arm/configs/u8500_defconfig | 4 + arch/arm/mach-ux500/board-mop500.c | 71 ++++ drivers/thermal/Kconfig | 16 + drivers/thermal/Makefile | 4 +- drivers/thermal/cpu_cooling.c | 3 +- drivers/thermal/db8500_cpufreq_cooling.c | 159 +++++++++ drivers/thermal/db8500_thermal.c | 445 ++++++++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 39 +++ 8 files changed, 738 insertions(+), 3 deletions(-) create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100755 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 4dc11da..ad6e7ab 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -118,3 +118,7 @@ CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_INFO=y # CONFIG_FTRACE is not set CONFIG_DEBUG_USER=y +CONFIG_THERMAL=y +CONFIG_CPU_THERMAL=y +CONFIG_DB8500_THERMAL=y +CONFIG_DB8500_CPUFREQ_COOLING=y diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 77d03c1..0c95bce 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -215,6 +216,74 @@ struct platform_device ab8500_device = { }; /* + * Thermal Sensor + */ + +#ifdef CONFIG_DB8500_THERMAL +static struct resource db8500_thsens_resources[] = { + { + .name = "IRQ_HOTMON_LOW", + .start = IRQ_PRCMU_HOTMON_LOW, + .end = IRQ_PRCMU_HOTMON_LOW, + .flags = IORESOURCE_IRQ, + }, + { + .name = "IRQ_HOTMON_HIGH", + .start = IRQ_PRCMU_HOTMON_HIGH, + .end = IRQ_PRCMU_HOTMON_HIGH, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct db8500_trip_point db8500_trips_table[] = { + [0] = { + .temp = 70000, + .type = THERMAL_TRIP_ACTIVE, + .cooling_dev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + [1] = { + .temp = 75000, + .type = THERMAL_TRIP_ACTIVE, + .cooling_dev_name = { + [0] = "thermal-cpufreq-1", + }, + }, + [2] = { + .temp = 80000, + .type = THERMAL_TRIP_ACTIVE, + .cooling_dev_name = { + [0] = "thermal-cpufreq-2", + }, + }, + [3] = { + .temp = 85000, + .type = THERMAL_TRIP_CRITICAL, + }, +}; + +static struct db8500_thsens_platform_data db8500_thsens_data = { + .trip_points = db8500_trips_table, + .num_trips = ARRAY_SIZE(db8500_trips_table), +}; + +static struct platform_device u8500_thsens_device = { + .name = "db8500_thermal", + .resource = db8500_thsens_resources, + .num_resources = ARRAY_SIZE(db8500_thsens_resources), + .dev = { + .platform_data = &db8500_thsens_data, + }, +}; +#endif + +#ifdef CONFIG_DB8500_CPUFREQ_COOLING +static struct platform_device u8500_cpufreq_cooling_device = { + .name = "db8500_cpufreq_cooling", +}; +#endif +/* * TPS61052 */ @@ -607,6 +676,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_key_dev, &snowball_sbnet_dev, &ab8500_device, + &u8500_thsens_device, + &u8500_cpufreq_cooling_device, }; static void __init mop500_init_machine(void) diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index d9c529f..eeabe01 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -30,6 +30,22 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y or M here. +config DB8500_THERMAL + tristate "db8500 thermal management" + depends on THERMAL + default y + help + Adds DB8500 thermal management implementation according to the thermal + management framework. + +config DB8500_CPUFREQ_COOLING + tristate "db8500 cpufreq cooling" + depends on CPU_THERMAL + default y + help + Adds DB8500 cpufreq cooling devices, and these cooling devicesd can be + binded to thermal zone device trip points. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 30c456c..d146456 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -3,5 +3,7 @@ # obj-$(CONFIG_THERMAL) += thermal_sys.o -obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o +obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) +=db8500_cpufreq_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index c40d9a0..a8aa10f 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -399,8 +399,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( /*Verify that all the entries of freq_clip_table are present*/ for (i = 0; i < tab_size; i++) { clip_tab = ((struct freq_clip_table *)&tab_ptr[i]); - if (!clip_tab->freq_clip_max || !clip_tab->mask_val - || !clip_tab->temp_level) { + if (!clip_tab->freq_clip_max || !clip_tab->mask_val) { kfree(cpufreq_dev); return ERR_PTR(-EINVAL); } diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..0dc2fcb --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,159 @@ +/* + * db8500_cpufreq_cooling.c - db8500 cpufreq works as cooling device. + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 +#include +#include +#include +#include + +#define CPU_FREQS_MAX 8 +#define CPU_CDEVS_MAX 8 /* should equal or larger than CPU_FREQS_MAX */ + +static struct freq_clip_table db8500_clip_table[CPU_FREQS_MAX]; +static struct thermal_cooling_device *db8500_cool_devs[CPU_CDEVS_MAX]; + +static int num_freqs; +static int num_cdevs; + +static int cpufreq_table_create(void) +{ + struct cpufreq_frequency_table *table; + unsigned int freq_scratch[CPU_FREQS_MAX]; + unsigned int temp; + int i, j, count = 0; + + table = cpufreq_frequency_get_table(0); + if (!table) + return -EINVAL; + + /* Check number of frequencies */ + for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { + if (table[i].frequency == CPUFREQ_ENTRY_INVALID) + continue; + count++; + } + + if(unlikely(count > CPU_FREQS_MAX)) { + pr_err("CPU_FREQS_MAX is not large enough.\n"); + return -EINVAL; + } + num_freqs = count; + + /* Save frequencies */ + count= 0; + memset(freq_scratch, 0, sizeof(freq_scratch)); + for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { + if (table[i].frequency == CPUFREQ_ENTRY_INVALID) + continue; + freq_scratch[count] = table[i].frequency; + count++; + } + + /* Descending order frequencies */ + for (i = 0; i <= num_freqs - 2; i++) + for (j = i + 1; j <= num_freqs - 1; j++) + if (freq_scratch[i] < freq_scratch[j]) { + temp = freq_scratch[i]; + freq_scratch[i] = freq_scratch[j]; + freq_scratch[j] = temp; + } + + /* Create freq clip table */ + memset(db8500_clip_table, 0, sizeof(db8500_clip_table)); + for (i = 0; i < num_freqs; i++) { + db8500_clip_table[i].freq_clip_max = freq_scratch[i]; + db8500_clip_table[i].mask_val = cpumask_of(0); + pr_info("db8500_clip_table %d: %d\n",i, db8500_clip_table[i].freq_clip_max); + } + + return 0; +} + + +static int __devinit db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{ + struct freq_clip_table *clip_data; + int i, count = 0; + + if (cpufreq_table_create()) + return -EINVAL; + + memset(db8500_cool_devs, 0, sizeof(db8500_cool_devs)); + + /* Create one cooling device for each clip frequency */ + for (i = 0; i < num_freqs; i++) { + clip_data = &(db8500_clip_table[i]); + db8500_cool_devs[i] = cpufreq_cooling_register(clip_data, 1); + if (!db8500_cool_devs[i]) { + pr_err("Failed to register cpufreq cooling device\n"); + goto exit; + } + count++; + pr_info("Cooling device regestered: %s\n", db8500_cool_devs[i]->type); + } + + num_cdevs = count; + return 0; + +exit: + for (i = 0; i < count; i++) + cpufreq_cooling_unregister(db8500_cool_devs[i]); + + return -EINVAL; +} + +static int __devexit db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < num_cdevs; i++) + cpufreq_cooling_unregister(db8500_cool_devs[i]); + + return 0; +} + +/* No actions required in suspend/resume, so lack of them */ +static struct platform_driver db8500_cpufreq_cooling_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500_cpufreq_cooling", + }, + .probe = db8500_cpufreq_cooling_probe, + .remove = __devexit_p(db8500_cpufreq_cooling_remove), +}; + +static int __init db8500_cpufreq_cooling_init(void) +{ + return platform_driver_register(&db8500_cpufreq_cooling_driver); +} + +static void __exit db8500_cpufreq_cooling_exit(void) +{ + platform_driver_unregister(&db8500_cpufreq_cooling_driver); +} + +/* Should be later than db8500_cpufreq_register() */ +late_initcall(db8500_cpufreq_cooling_init); +module_exit(db8500_cpufreq_cooling_exit); + +MODULE_AUTHOR("Hongbo Zhang "); +MODULE_DESCRIPTION("db8500 cpufreq cooling driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c new file mode 100755 index 0000000..7e85969 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,445 @@ +/* + * db8500_thermal.c - db8500 Thermal Management Implementation + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0 + +struct db8500_thermal_zone { + struct thermal_zone_device *therm_dev; + enum thermal_device_mode mode; + struct mutex th_lock; + struct platform_device *thsens_pdev; + struct work_struct therm_work; + unsigned long cur_low; + unsigned long cur_high; + int low_irq; + int high_irq; +}; + +static struct db8500_thermal_zone *th_zone = NULL; + +/* Bind callback functions for thermal zone */ +static int db8500_cdev_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct db8500_thermal_zone *pzone; + struct db8500_thsens_platform_data *ptrips; + int i, j, ret; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + ptrips = pzone->thsens_pdev->dev.platform_data; + + for (i = 0; i < ptrips->num_trips; i++) + for (j = 0; j < COOLING_DEV_MAX; j++) + if (ptrips->trip_points[i].cooling_dev_name[j] && cdev->type) + if (strcmp(ptrips->trip_points[i].cooling_dev_name[j], cdev->type) == 0) { + ret = thermal_zone_bind_cooling_device(thermal, i, cdev); + if (ret) + pr_err("Error binding cooling device.\n"); + else + pr_info("Cooling device %s binded to trip point %d.\n", cdev->type, i); + } + + schedule_work(&pzone->therm_work); + return 0; +} + +/* Unbind callback functions for thermal zone */ +static int db8500_cdev_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct db8500_thermal_zone *pzone; + struct db8500_thsens_platform_data *ptrips; + int i, j, ret; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + ptrips = pzone->thsens_pdev->dev.platform_data; + + for (i = 0; i < ptrips->num_trips; i++) + for (j = 0; j < COOLING_DEV_MAX; j++) + if (ptrips->trip_points[i].cooling_dev_name[j] && cdev->type) + if (strcmp(ptrips->trip_points[i].cooling_dev_name[j], cdev->type) == 0) { + ret = thermal_zone_unbind_cooling_device(thermal, i, cdev); + if (ret) + pr_err("Error unbinding cooling device.\n"); + else + pr_info("Cooling device %s unbinded from trip point %d.\n", cdev->type, i); + } + + return 0; +} + +/* Get temperature callback functions for thermal zone */ +static int db8500_sys_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + + /* There is no PRCMU interface to get temperature data currently, + so just returns temperature between two trip points, it works for the thermal framework + and this will be fixed when the PRCMU interface to get temperature is available */ + *temp = (pzone->cur_high + pzone->cur_low)/2; + + return 0; +} + +/* Get mode callback functions for thermal zone */ +static int db8500_sys_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct db8500_thermal_zone *pzone; + pzone = (struct db8500_thermal_zone *)thermal->devdata; + + mutex_lock(&pzone->th_lock); + *mode = pzone->mode; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Set mode callback functions for thermal zone */ +static int db8500_sys_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct db8500_thermal_zone *pzone; + struct thermal_zone_device *pthdev; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + pthdev = pzone->therm_dev; + + if (!pthdev) { + pr_err("Thermal zone not registered.\n"); + return 0; + } + + mutex_lock(&pzone->th_lock); + if (mode == THERMAL_DEVICE_ENABLED) { + if (pzone->mode == THERMAL_DEVICE_DISABLED) + pr_info("Thermal function started.\n"); + else + pr_warning("Thermal function already started.\n"); + } else { + if (pzone->mode == THERMAL_DEVICE_ENABLED) + pr_info("Thermal function stoped.\n"); + else + pr_warning("Thermal function already stoped.\n"); + } + pzone->mode = mode; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Get trip type callback function for thermal zone */ +static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal, int trip, + enum thermal_trip_type *type) +{ + struct db8500_thermal_zone *pzone; + struct db8500_thsens_platform_data *ptrips; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + ptrips = pzone->thsens_pdev->dev.platform_data; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *type = ptrips->trip_points[trip].type; + + return 0; +} + +/* Get trip temperature callback function for thermal zone */ +static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal, int trip, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone; + struct db8500_thsens_platform_data *ptrips; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + ptrips = pzone->thsens_pdev->dev.platform_data; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *temp = ptrips->trip_points[trip].temp; + + return 0; +} + +/* Get critical temperature callback function for thermal zone */ +static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone; + struct db8500_thsens_platform_data *ptrips; + int i; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + ptrips = pzone->thsens_pdev->dev.platform_data; + + for (i = (ptrips->num_trips - 1) ; i > 0 ; i--) { + if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) { + *temp = ptrips->trip_points[i].temp; + return 0; + } + } + + return -EINVAL; +} + +static struct thermal_zone_device_ops thdev_ops = { + .bind = db8500_cdev_bind, + .unbind = db8500_cdev_unbind, + .get_temp = db8500_sys_get_temp, + .get_mode = db8500_sys_get_mode, + .set_mode = db8500_sys_set_mode, + .get_trip_type = db8500_sys_get_trip_type, + .get_trip_temp = db8500_sys_get_trip_temp, + .get_crit_temp = db8500_sys_get_crit_temp, +}; + +static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data) +{ + struct db8500_thermal_zone *pzone = irq_data; + struct db8500_thsens_platform_data *ptrips; + unsigned long next_low, next_high; + int i; + + ptrips = pzone->thsens_pdev->dev.platform_data; + + for(i = 0; i < ptrips->num_trips; i++) { + if (pzone->cur_high == ptrips->trip_points[i].temp) + break; + } + + if (i <= 1) { + next_high = ptrips->trip_points[0].temp; + next_low = PRCMU_DEFAULT_LOW_TEMP; + } else { + next_high = ptrips->trip_points[i-1].temp; + next_low = ptrips->trip_points[i-2].temp; + } + + (void) prcmu_stop_temp_sense(); + (void) prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); + (void) prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + + pzone->cur_high = next_high; + pzone->cur_low = next_low; + pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low); + + schedule_work(&pzone->therm_work); + + return IRQ_HANDLED; +} + +static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data) +{ + struct db8500_thermal_zone *pzone = irq_data; + struct db8500_thsens_platform_data *ptrips; + unsigned long next_low, next_high; + int i; + + ptrips = pzone->thsens_pdev->dev.platform_data; + + for(i = 0; i < ptrips->num_trips; i++) { + if (pzone->cur_high == ptrips->trip_points[i].temp) + break; + } + + /* not likely over critial trip temp, e.g. i < ptrips->num_trips-1 here */ + next_high = ptrips->trip_points[i+1].temp; + next_low = ptrips->trip_points[i].temp; + + (void) prcmu_stop_temp_sense(); + (void) prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); + (void) prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + + pzone->cur_high = next_high; + pzone->cur_low = next_low; + pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low); + + schedule_work(&pzone->therm_work); + + return IRQ_HANDLED; +} + +static void db8500_thermal_work(struct work_struct *work) +{ + enum thermal_device_mode cur_mode; + struct db8500_thermal_zone *pzone; + + pzone = container_of(work, struct db8500_thermal_zone, therm_work); + + mutex_lock(&pzone->th_lock); + cur_mode = pzone->mode; + mutex_unlock(&pzone->th_lock); + + if (cur_mode == THERMAL_DEVICE_DISABLED) { + pr_warn("Warning: thermal function disabled.\n"); + return; + } + + thermal_zone_device_update(pzone->therm_dev); + pr_debug("db8500_thermal_work finished. \n"); +} + +static int __devinit db8500_thermal_probe(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = NULL; + struct db8500_thsens_platform_data *ptrips; + int low_irq, high_irq, ret = 0; + unsigned long dft_low, dft_high; + + pr_info("Function db8500_thermal_probe.\n"); + + pzone = kzalloc(sizeof(struct db8500_thermal_zone), GFP_KERNEL); + if (!pzone) + return ENOMEM; + + pzone->thsens_pdev = pdev; + + low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (low_irq < 0) { + pr_err("Get IRQ_HOTMON_LOW failed.\n"); + goto exit_irq; + } + + ret = request_threaded_irq(low_irq, NULL, prcmu_low_irq_handler, + IRQF_NO_SUSPEND, "dbx500_temp_low", pzone); + if (ret < 0) { + pr_err("Failed to allocate temp low irq.\n"); + goto exit_irq; + } + + high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (high_irq < 0) { + pr_err("Get IRQ_HOTMON_HIGH failed.\n"); + goto exit_irq; + } + + ret = request_threaded_irq(high_irq, NULL, prcmu_high_irq_handler, + IRQF_NO_SUSPEND, "dbx500_temp_high", pzone); + if (ret < 0) { + pr_err("Failed to allocate temp high irq.\n"); + goto exit_irq; + } + + pzone->low_irq = low_irq; + pzone->high_irq = high_irq; + + ptrips = (struct db8500_thsens_platform_data *)pdev->dev.platform_data; + + pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone", + ptrips->num_trips, pzone, &thdev_ops, 0, 0, 0, 0); + + if (IS_ERR(pzone->therm_dev)) { + pr_err("Failed to register thermal zone device\n"); + ret = -EINVAL; + goto exit_th; + } + + mutex_init(&pzone->th_lock); + + INIT_WORK(&pzone->therm_work, db8500_thermal_work); + + /* PRCMU initialize */ + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = (ptrips->trip_points[0].temp); + + (void) prcmu_stop_temp_sense(); + (void) prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000)); + (void) prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + + pzone->cur_low = dft_low; + pzone->cur_high = dft_high; + pzone->mode = THERMAL_DEVICE_ENABLED; + + th_zone = pzone; + return 0; + +exit_th: + if (pzone->therm_dev) + thermal_zone_device_unregister(pzone->therm_dev); + +exit_irq: + if (pzone->low_irq > 0) + free_irq(pzone->low_irq, pzone); + if (pzone->low_irq > 0) + free_irq(pzone->high_irq, pzone); + + kfree(pzone); + return ret; +} + +static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = th_zone; + + if (pzone && pzone->therm_dev) + thermal_zone_device_unregister(pzone->therm_dev); + if (pzone && pzone->low_irq) + free_irq(pzone->low_irq, pzone); + if (pzone && pzone->high_irq) + free_irq(pzone->high_irq, pzone); + if (pzone) + kfree(pzone); + + return 0; +} + +/* No actions required in suspend/resume, so lack of them */ +static struct platform_driver db8500_thermal_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500_thermal" + }, + .probe = db8500_thermal_probe, + .remove = __devexit_p(db8500_thermal_remove), +}; + +static int __init db8500_thermal_init(void) +{ + return platform_driver_register(&db8500_thermal_driver); +} + +static void __exit db8500_thermal_exit(void) +{ + platform_driver_unregister(&db8500_thermal_driver); +} + +module_init(db8500_thermal_init); +module_exit(db8500_thermal_exit); + +MODULE_AUTHOR("Hongbo Zhang "); +MODULE_DESCRIPTION("db8500 thermal driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/db8500_thermal.h b/include/linux/platform_data/db8500_thermal.h new file mode 100644 index 0000000..0b6d164 --- /dev/null +++ b/include/linux/platform_data/db8500_thermal.h @@ -0,0 +1,39 @@ +/* + * db8500_thermal.h - db8500 Thermal Management Implementation + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 _DB8500_THERMAL_H_ +#define _DB8500_THERMAL_H_ + +#include + +#define COOLING_DEV_MAX 8 + +struct db8500_trip_point { + unsigned long temp; + enum thermal_trip_type type; + char *cooling_dev_name[COOLING_DEV_MAX]; +}; + +struct db8500_thsens_platform_data { + struct db8500_trip_point *trip_points; + int num_trips; +}; + +#endif /* _DB8500_THERMAL_H_ */