From: "hongbo.zhang" hongbo.zhang@stericsson.com
--- arch/arm/configs/u8500_defconfig | 2 ++ drivers/power/Makefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 889d73a..4dc11da 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -99,6 +99,8 @@ CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT2_FS_SECURITY=y CONFIG_EXT3_FS=y +CONFIG_EXT4_FS=y +CONFIG_LBDAF=y CONFIG_VFAT_FS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b6b2434..e642e1c 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -34,7 +34,7 @@ obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o -obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o +#obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
From: "hongbo.zhang" hongbo.zhang@stericsson.com
--- 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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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 hognbo.zhang@stericsson.com + * + * 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h> + +#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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com + * + * 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h> + +#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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com + * + * 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 <linux/thermal.h> + +#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_ */
[PATCH 1/2] make v3.4-rc3 run on snowball: this patch can be omitted. but, [PATCH 2/2] db8500 thermal dirver: any comments/suggestions are welcomed.
On 20 June 2012 21:04, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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 hognbo.zhang@stericsson.com
- 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h>
+#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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h>
+#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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/thermal.h>
+#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_ */
1.7.10
On 20 June 2012 18:34, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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 hognbo.zhang@stericsson.com
- 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h>
+#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;
Can these globals be removed through a private data structure?
+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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h>
+#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;
This global can be removed using platform_set_drvdata().
+/* 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;
The thermal framework takes cooling decisions based on current temperature in thermal_zone_device_update(). Returning average temperature might force thermal framework to fail. Given the lack of interface to read current temp, it may be reasonable to return trip high/low temp depending on last low/high irq received.
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);
Should mutex be acquired after if condition?
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;
trip points table is being accessed in most of functions. May be worth having its pointer in driver private data itself?
+ 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++) {
Would it be good idea to store trip table index instead of cur_high & cur_low?
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);
is there a possibily of pzone getting freed in some other place to have if checks?
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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/thermal.h>
+#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_ */
1.7.10
Please run scripts/checkpatch.pl on this patch and fix formatting errors
and warnings.
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
On 4 July 2012 17:56, Rajagopal Venkat rajagopal.venkat@linaro.org wrote:
On 20 June 2012 18:34, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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 hognbo.zhang@stericsson.com
- 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h>
+#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;
Can these globals be removed through a private data structure?
Yes, I don't like these globals either.
+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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h>
+#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;
This global can be removed using platform_set_drvdata().
Yes.
+/* 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;
The thermal framework takes cooling decisions based on current temperature in thermal_zone_device_update(). Returning average temperature might force thermal framework to fail. Given the lack of interface to read current temp, it may be reasonable to return trip high/low temp depending on last low/high irq received.
Cannot understand why my method caused thermal framework to fail. I think when PRCMU interrupt comes, the real temperature is min-1 or max+1, not min or max itself, right?
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);
Should mutex be acquired after if condition?
What is wrong? I think it is OK.
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;
trip points table is being accessed in most of functions. May be worth having its pointer in driver private data itself?
I had this pointer previously, but was afraid of being challenged "why use additional pointer, this pointer is just in paltform_data". Since other person agree this, I will use a pointer again.
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++) {
Would it be good idea to store trip table index instead of cur_high & cur_low?
I have ever thought about storing table index, cur_high & cur_low are used is to return average temp quickly. Anyway storing index is good idea whatever temp is returned.
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);
is there a possibily of pzone getting freed in some other place to have if checks?
I will fixed this by Linus's suggestions.
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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/thermal.h>
+#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_ */
1.7.10
Please run scripts/checkpatch.pl on this patch and fix formatting errors
and warnings.
Yes sure.
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
On 5 July 2012 09:51, Hongbo Zhang hongbo.zhang@linaro.org wrote:
On 4 July 2012 17:56, Rajagopal Venkat rajagopal.venkat@linaro.orgwrote:
On 20 June 2012 18:34, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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 hognbo.zhang@stericsson.com
- 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h>
+#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;
Can these globals be removed through a private data structure?
Yes, I don't like these globals either.
+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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h>
+#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;
This global can be removed using platform_set_drvdata().
Yes.
+/* 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;
The thermal framework takes cooling decisions based on current temperature in thermal_zone_device_update(). Returning average temperature might force thermal framework to fail. Given the lack of interface to read current temp, it may be reasonable to return trip high/low temp depending on last low/high irq received.
Cannot understand why my method caused thermal framework to fail. I think when PRCMU interrupt comes, the real temperature is min-1 or max+1, not min or max itself, right?
Yes. Agree. I see that cur_high and cur_low are updated to next trip point before calling thermal_zone_device_update(). I take back this comment.
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);
Should mutex be acquired after if condition?
What is wrong? I think it is OK.
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;
trip points table is being accessed in most of functions. May be worth having its pointer in driver private data itself?
I had this pointer previously, but was afraid of being challenged "why use additional pointer, this pointer is just in paltform_data". Since other person agree this, I will use a pointer again.
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++) {
Would it be good idea to store trip table index instead of cur_high & cur_low?
I have ever thought about storing table index, cur_high & cur_low are used is to return average temp quickly. Anyway storing index is good idea whatever temp is returned.
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);
is there a possibily of pzone getting freed in some other place to have if checks?
I will fixed this by Linus's suggestions.
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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/thermal.h>
+#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_ */
1.7.10
Please run scripts/checkpatch.pl on this patch and fix formatting
errors and warnings.
Yes sure.
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
On Wed, Jun 20, 2012 at 3:04 PM, hongbo.zhang hongbo.zhang@linaro.org wrote:
/*
- Thermal Sensor
- */
+#ifdef CONFIG_DB8500_THERMAL
Please don't #ifdef the platform data like this, Documentation/CodingStyle dislikes #ifdefs unless needed and this just saves a very marginal piece of code in footprint, and will compile just as well even if the driver is not selected.
+static struct resource db8500_thsens_resources[] = {
+#ifdef CONFIG_DB8500_CPUFREQ_COOLING
Don't #ifdef this either.
Then you will need to add device tree bindings for all this platform data sooner or later. Please check with Lee Jones if uncertain.
@@ -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,
};
As you can see the above will break compilation when the drivers are not selected if you don't remove the #ifdefs.
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); }
This looks like a generic patch to cpu_cooling, please break it out and send this separately to the thermal maintainer and discuss it already today.
+#include <linux/compiler.h>
Why is this include needed? (Just curious.)
+/* 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);
So what happens if this work is scheduled and you suspend before it executes?
I think you need a suspend/resume hook to clean that up? Probably also in remove().
+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;
+}
This also triggers a work that can be inhibited by suspend/resume or remove.
schedule_work(&pzone->therm_work);
etc.
+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);
Use devm_kzalloc().
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);
Use devm_request_threaded_irq().
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);
Use devm_request_threaded_irq()
if (ret < 0) {
pr_err("Failed to allocate temp high irq.\n");
goto exit_irq;
}
(...)
+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);
None of this is needed if you use the devm_* functions, they are garbage collecting.
+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);
And...
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);
None of this either.
diff --git a/include/linux/platform_data/db8500_thermal.h b/include/linux/platform_data/db8500_thermal.h
Thanks for using the proper platform data dir!
Yours, Linus Walleij
On 5 July 2012 07:20, Linus Walleij linus.walleij@linaro.org wrote:
On Wed, Jun 20, 2012 at 3:04 PM, hongbo.zhang hongbo.zhang@linaro.org wrote:
/*
- Thermal Sensor
- */
+#ifdef CONFIG_DB8500_THERMAL
Please don't #ifdef the platform data like this, Documentation/CodingStyle dislikes #ifdefs unless needed and this just saves a very marginal piece of code in footprint, and will compile just as well even if the driver is not selected.
Yes I will remove #ifdef
+static struct resource db8500_thsens_resources[] = {
+#ifdef CONFIG_DB8500_CPUFREQ_COOLING
Don't #ifdef this either.
Then you will need to add device tree bindings for all this platform data sooner or later. Please check with Lee Jones if uncertain.
OK I will check device tree codes and check with him if any doubt.
@@ -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,
};
As you can see the above will break compilation when the drivers are not selected if you don't remove the #ifdefs.
This is a silly mistake I made :(
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); }
This looks like a generic patch to cpu_cooling, please break it out and send this separately to the thermal maintainer and discuss it already today.
Yes this should be a separate patch.
+#include <linux/compiler.h>
Why is this include needed? (Just curious.)
This is due to some obsolete debuging codes, I forgot to remove this header file.
+/* 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);
So what happens if this work is scheduled and you suspend before it executes?
I think you need a suspend/resume hook to clean that up? Probably also in remove().
Yes I will consider suspend/resume functions.
+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;
+}
This also triggers a work that can be inhibited by suspend/resume or remove.
schedule_work(&pzone->therm_work);
etc.
+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);
Use devm_kzalloc().
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);
Use devm_request_threaded_irq().
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);
Use devm_request_threaded_irq()
if (ret < 0) {
pr_err("Failed to allocate temp high irq.\n");
goto exit_irq;
}
(...)
+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);
None of this is needed if you use the devm_* functions, they are garbage collecting.
Good suggestion of using devm_* functions.
+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);
And...
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);
None of this either.
diff --git a/include/linux/platform_data/db8500_thermal.h
b/include/linux/platform_data/db8500_thermal.h
Thanks for using the proper platform data dir!
Thanks for all of you comments.
Yours, Linus Walleij
On 20 June 2012 15:04, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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) {
please make a separate patch for this modification
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 hognbo.zhang@stericsson.com
- 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h>
+#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);
cpumask_of(0) is false because both cpu are linked for their frequency use cpu_present_mask instead
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));
could you dynamically allocate db8500_cool_dev and db8500_clip_table based on the count result that has been done in cpufreq_table_create function
more generally speaking, could you prevent using "global" variable ?
db8500_cool_devs, db8500_clip_table, num_freqs and num_cdevs should be linked with your driver and not a static variabale
/* 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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h>
+#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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/thermal.h>
+#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_ */
1.7.10
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
I sent the mail before finishing the review. additional one will come soon
On 5 July 2012 16:13, Vincent Guittot vincent.guittot@linaro.org wrote:
On 20 June 2012 15:04, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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) {
please make a separate patch for this modification
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 hognbo.zhang@stericsson.com
- 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h>
+#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);
cpumask_of(0) is false because both cpu are linked for their frequency use cpu_present_mask instead
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));
could you dynamically allocate db8500_cool_dev and db8500_clip_table based on the count result that has been done in cpufreq_table_create function
more generally speaking, could you prevent using "global" variable ?
db8500_cool_devs, db8500_clip_table, num_freqs and num_cdevs should be linked with your driver and not a static variabale
/* 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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h>
+#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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/thermal.h>
+#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_ */
1.7.10
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
On 5 July 2012 22:13, Vincent Guittot vincent.guittot@linaro.org wrote:
On 20 June 2012 15:04, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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) {
please make a separate patch for this modification
OK, it should be a separate patch.
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 hognbo.zhang@stericsson.com
- 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h>
+#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);
cpumask_of(0) is false because both cpu are linked for their frequency use cpu_present_mask instead
OK, thanks.
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));
could you dynamically allocate db8500_cool_dev and db8500_clip_table based on the count result that has been done in cpufreq_table_create function
more generally speaking, could you prevent using "global" variable ?
db8500_cool_devs, db8500_clip_table, num_freqs and num_cdevs should be linked with your driver and not a static variabale
Globals and static arrays are not good. I will improve this.
/* 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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h>
+#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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/thermal.h>
+#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_ */
1.7.10
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
On 20 June 2012 15:04, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
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 <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> +#include <linux/platform_data/db8500_thermal.h>
#include <linux/of.h> #include <linux/of_platform.h> @@ -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 hognbo.zhang@stericsson.com
- 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 <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h>
+#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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/cpu_cooling.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/platform_data/db8500_thermal.h>
+#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;
th_zone is only used in db8500_thermal_remove function. Use platform_set_drvdata instead
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 hongbo.zhang@stericsson.com"); +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 hognbo.zhang@stericsson.com
- 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 <linux/thermal.h>
+#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_ */
1.7.10
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
Please omit this patch, I am currently working on v3.4-rc3, I will rebase to the latest version soon.
On 20 June 2012 21:04, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
arch/arm/configs/u8500_defconfig | 2 ++ drivers/power/Makefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 889d73a..4dc11da 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -99,6 +99,8 @@ CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT2_FS_SECURITY=y CONFIG_EXT3_FS=y +CONFIG_EXT4_FS=y +CONFIG_LBDAF=y CONFIG_VFAT_FS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b6b2434..e642e1c 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -34,7 +34,7 @@ obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o -obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o +#obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o -- 1.7.10
[PATCH 1/2] make v3.4-rc3 run on snowball: this patch can be omitted. but, [PATCH 2/2] db8500 thermal dirver: any comments/suggestions are welcomed.
On 20 June 2012 21:14, Hongbo Zhang hongbo.zhang@linaro.org wrote:
Please omit this patch, I am currently working on v3.4-rc3, I will rebase to the latest version soon.
On 20 June 2012 21:04, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@stericsson.com
arch/arm/configs/u8500_defconfig | 2 ++ drivers/power/Makefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 889d73a..4dc11da 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -99,6 +99,8 @@ CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT2_FS_SECURITY=y CONFIG_EXT3_FS=y +CONFIG_EXT4_FS=y +CONFIG_LBDAF=y CONFIG_VFAT_FS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b6b2434..e642e1c 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -34,7 +34,7 @@ obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o -obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o +#obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o -- 1.7.10