From: "hongbo.zhang" hongbo.zhang@linaro.com
Hi all, This patch set is to upstream ST-Ericsson thermal driver and fix some bugs of thermal layer at the same time. All of these patches are based on v3.7-rc1.
[PATCH 1/5] Thermal: do bind operation after thermal zone or cooling device register returns.
In the previous bind function, cdev->get_max_state(cdev, &max_state) is called before the registration function finishes, but at this moment, the parameter cdev at thermal driver layer isn't ready--it will get ready only after its registration, so the the get_max_state callback cannot tell the max_state according to the cdev input. This problem can be fixed by separating the bind operation out of registration and doing it when registration completely finished.
There is no such problem with the current exynos thermal driver because it regsters cooling device before thermal zone device. As a generic thermal layer any sequence should be supported, thermal zone first or cooling device first, this will be also helpful to add/remove cooling device dynamically.
[PATCH 2/5] Thermal: add indent for code alignment. [PATCH 3/5] Thermal: fix empty list checking method. [PATCH 4/5] Thermal: make sure cpufreq cooling register after cpufreq driver
Bug fix for generic cpufreq cooling layer as described in the commit logs.
[PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.
This patch is to add ST-Ericsson into the latest kervel version.
hongbo.zhang (5): Thermal: do bind operation after thermal zone or cooling device register returns. Thermal: add indent for code alignment. Thermal: fix empty list checking method. Thermal: make sure cpufreq cooling register after cpufreq driver Thermal: Add ST-Ericsson db8500 thermal dirver.
arch/arm/boot/dts/dbx5x0.dtsi | 11 + arch/arm/configs/u8500_defconfig | 4 + arch/arm/mach-ux500/board-mop500.c | 73 ++++ drivers/thermal/Kconfig | 20 ++ drivers/thermal/Makefile | 2 + drivers/thermal/cpu_cooling.c | 19 +- drivers/thermal/db8500_cpufreq_cooling.c | 118 +++++++ drivers/thermal/db8500_thermal.c | 507 +++++++++++++++++++++++++++ drivers/thermal/thermal_sys.c | 86 +++-- include/linux/platform_data/db8500_thermal.h | 39 +++ 10 files changed, 847 insertions(+), 32 deletions(-) create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
From: "hongbo.zhang" hongbo.zhang@linaro.com
In the previous bind function, cdev->get_max_state(cdev, &max_state) is called before the registration function finishes, but at this moment, the parameter cdev at thermal driver layer isn't ready--it will get ready only after its registration, so the the get_max_state callback cannot tell the max_state according to the cdev input. This problem can be fixed by separating the bind operation out of registration and doing it when registration completely finished.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- drivers/thermal/thermal_sys.c | 86 +++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 28 deletions(-)
diff --git a/drivers/thermal/thermal_sys.c b/drivers/thermal/thermal_sys.c index 9ee42ca..dd3d024 100644 --- a/drivers/thermal/thermal_sys.c +++ b/drivers/thermal/thermal_sys.c @@ -70,6 +70,8 @@ static LIST_HEAD(thermal_tz_list); static LIST_HEAD(thermal_cdev_list); static DEFINE_MUTEX(thermal_list_lock);
+static struct work_struct thermal_bind; + static int get_idr(struct idr *idr, struct mutex *lock, int *id) { int err; @@ -777,7 +779,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, dev->lower = lower; dev->target = THERMAL_NO_TARGET;
- result = get_idr(&tz->idr, &tz->lock, &dev->id); + result = get_idr(&tz->idr, NULL, &dev->id); if (result) goto free_mem;
@@ -796,7 +798,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, if (result) goto remove_symbol_link;
- mutex_lock(&tz->lock); + /* tz->lock should have been locked outside this function */ mutex_lock(&cdev->lock); list_for_each_entry(pos, &tz->thermal_instances, tz_node) if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { @@ -808,7 +810,6 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, list_add_tail(&dev->cdev_node, &cdev->thermal_instances); } mutex_unlock(&cdev->lock); - mutex_unlock(&tz->lock);
if (!result) return 0; @@ -895,7 +896,6 @@ thermal_cooling_device_register(char *type, void *devdata, const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; - struct thermal_zone_device *pos; int result;
if (type && strlen(type) >= THERMAL_NAME_LENGTH) @@ -947,16 +947,10 @@ thermal_cooling_device_register(char *type, void *devdata,
mutex_lock(&thermal_list_lock); list_add(&cdev->node, &thermal_cdev_list); - list_for_each_entry(pos, &thermal_tz_list, node) { - if (!pos->ops->bind) - continue; - result = pos->ops->bind(pos, cdev); - if (result) - break; - - } mutex_unlock(&thermal_list_lock);
+ schedule_work(&thermal_bind); + if (!result) return cdev;
@@ -1141,19 +1135,13 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz,
return; } -/** - * thermal_zone_device_update - force an update of a thermal zone's state - * @ttz: the thermal zone to update - */
-void thermal_zone_device_update(struct thermal_zone_device *tz) +void __thermal_zone_device_update(struct thermal_zone_device *tz) { int count, ret = 0; long temp, trip_temp; enum thermal_trip_type trip_type;
- mutex_lock(&tz->lock); - if (tz->ops->get_temp(tz, &temp)) { /* get_temp failed - retry it later */ pr_warn("failed to read out thermal zone %d\n", tz->id); @@ -1206,10 +1194,56 @@ leave: thermal_zone_device_set_polling(tz, tz->polling_delay); else thermal_zone_device_set_polling(tz, 0); +} + +/** + * thermal_zone_device_update - force an update of a thermal zone's state + * @tz: the thermal zone to update + */ +void thermal_zone_device_update(struct thermal_zone_device *tz) +{ + mutex_lock(&tz->lock); + + __thermal_zone_device_update(tz); + mutex_unlock(&tz->lock); } EXPORT_SYMBOL(thermal_zone_device_update);
+static void thermal_zone_do_bind_work(struct work_struct *work) +{ + struct thermal_instance *instance; + struct thermal_zone_device *tz; + struct thermal_cooling_device *cdev; + + mutex_lock(&thermal_list_lock); + + list_for_each_entry(tz, &thermal_tz_list, node) + list_for_each_entry(cdev, &thermal_cdev_list, node) { + + mutex_lock(&tz->lock); + + if (list_empty(&tz->thermal_instances) + && tz->ops->bind) { + tz->ops->bind(tz, cdev); + __thermal_zone_device_update(tz); + mutex_unlock(&tz->lock); + break; + } + + list_for_each_entry(instance, &tz->thermal_instances, + tz_node) + if (instance->cdev != cdev && tz->ops->bind) { + tz->ops->bind(tz, cdev); + __thermal_zone_device_update(tz); + } + + mutex_unlock(&tz->lock); + } + + mutex_unlock(&thermal_list_lock); +} + /** * create_trip_attrs - create attributes for trip points * @tz: the thermal zone device @@ -1335,7 +1369,6 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, int passive_delay, int polling_delay) { struct thermal_zone_device *tz; - struct thermal_cooling_device *pos; enum thermal_trip_type trip_type; int result; int count; @@ -1419,17 +1452,12 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
mutex_lock(&thermal_list_lock); list_add_tail(&tz->node, &thermal_tz_list); - if (ops->bind) - list_for_each_entry(pos, &thermal_cdev_list, node) { - result = ops->bind(tz, pos); - if (result) - break; - } mutex_unlock(&thermal_list_lock);
- INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check); + if (ops->bind) + schedule_work(&thermal_bind);
- thermal_zone_device_update(tz); + INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);
if (!result) return tz; @@ -1588,6 +1616,7 @@ static int __init thermal_init(void) { int result = 0;
+ INIT_WORK(&thermal_bind, thermal_zone_do_bind_work); result = class_register(&thermal_class); if (result) { idr_destroy(&thermal_tz_idr); @@ -1601,6 +1630,7 @@ static int __init thermal_init(void)
static void __exit thermal_exit(void) { + cancel_work_sync(&thermal_bind); class_unregister(&thermal_class); idr_destroy(&thermal_tz_idr); idr_destroy(&thermal_cdev_idr);
Hi,
On 10/16/2012 01:44 PM, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
In the previous bind function, cdev->get_max_state(cdev, &max_state) is called before the registration function finishes, but at this moment, the parameter cdev at thermal driver layer isn't ready--it will get ready only after its registration, so the the get_max_state callback cannot tell the max_state according to the cdev input. This problem can be fixed by separating the bind operation out of registration and doing it when registration completely finished.
When thermal_cooling_device_register() is called, the thermal framework assumes the cooling device is "ready", i.e. all of its ops callbacks return meaningful results. If the cooling device is not ready at this point, then this is a bug in the code that registers it. Specifically, the faulty code in your case is in the cpufreq cooling implementation, where the cooling device is registered before being added to the internal list of cpufreq cooling devices. So, IMHO the fix is needed there.
-- Francesco
Hi Francesco, I found out more points about this issue.
[1] cdev should be ready when get_max_state callback be called, otherwise parameter cdev is useless, imagine there may be cases that get_max_state call back is shared by more than one cooling devices of same kind, like this: common_get_max_state(*cdev, *state) { if (cdev == cdev1) *state = 3; else if (cdev == cdev) *state = 5; else }
[2] In my cpufreq cooling case(in fact cdev is not used to calculate max_state), the cooling_cpufreq_list should be ready when get_max_state call back is called. In this patch I defer the binding when registration finished, but this is not perfect now, see this routine in cpufreq_cooling_register:
thermal_cooling_device_register; at this time: thermal_bind_work -> get_max_state -> get NULL cooling_cpufreq_list and then: list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list) This is due to we cannot know exactly when the bind work is executed. (and this can be fixed by moving mutex_lock(&cooling_cpufreq_lock) aheadof thermal_cooling_device_register and other corresponding modifications, but I found another way as below)
[3] Root cause of this problem is calling get_max_state in register -> bind routine. Better solution is to add another parameter in cooling device register function, also add a max_state member in struct cdev, like: thermal_cooling_device_register(char *type, void *devdata, const struct thermal_cooling_device_ops *ops, unsigned long max_state) and then in the bind function: if(cdev->max_state) max_state = cdev->max_state; else cdev->get_max_state(cdev, &max_state)
It is common sense that the cooling driver should know its cooling max_state(ability) before registration, and it can be offered when register. I think this way doesn't change both thermal and cooling layer much, it is more clear. I will update this patch soon.
On 21 October 2012 18:05, Francesco Lavra francescolavra.fl@gmail.com wrote:
Hi,
On 10/16/2012 01:44 PM, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
In the previous bind function, cdev->get_max_state(cdev, &max_state) is called before the registration function finishes, but at this moment, the parameter cdev at thermal driver layer isn't ready--it will get ready only after its registration, so the the get_max_state callback cannot tell the max_state according to the cdev input. This problem can be fixed by separating the bind operation out of registration and doing it when registration completely finished.
When thermal_cooling_device_register() is called, the thermal framework assumes the cooling device is "ready", i.e. all of its ops callbacks return meaningful results. If the cooling device is not ready at this point, then this is a bug in the code that registers it. Specifically, the faulty code in your case is in the cpufreq cooling implementation, where the cooling device is registered before being added to the internal list of cpufreq cooling devices. So, IMHO the fix is needed there.
-- Francesco
Hi, On 10/23/2012 10:23 AM, Hongbo Zhang wrote:
Hi Francesco, I found out more points about this issue.
[1] cdev should be ready when get_max_state callback be called, otherwise parameter cdev is useless, imagine there may be cases that get_max_state call back is shared by more than one cooling devices of same kind, like this: common_get_max_state(*cdev, *state) { if (cdev == cdev1) *state = 3; else if (cdev == cdev) *state = 5; else }
[2] In my cpufreq cooling case(in fact cdev is not used to calculate max_state), the cooling_cpufreq_list should be ready when get_max_state call back is called. In this patch I defer the binding when registration finished, but this is not perfect now, see this routine in cpufreq_cooling_register:
thermal_cooling_device_register; at this time: thermal_bind_work -> get_max_state -> get NULL cooling_cpufreq_list and then: list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list) This is due to we cannot know exactly when the bind work is executed. (and this can be fixed by moving mutex_lock(&cooling_cpufreq_lock) aheadof thermal_cooling_device_register and other corresponding modifications, but I found another way as below)
[3] Root cause of this problem is calling get_max_state in register -> bind routine. Better solution is to add another parameter in cooling device register function, also add a max_state member in struct cdev, like: thermal_cooling_device_register(char *type, void *devdata, const struct thermal_cooling_device_ops *ops, unsigned long max_state) and then in the bind function: if(cdev->max_state) max_state = cdev->max_state; else cdev->get_max_state(cdev, &max_state)
It is common sense that the cooling driver should know its cooling max_state(ability) before registration, and it can be offered when register. I think this way doesn't change both thermal and cooling layer much, it is more clear. I will update this patch soon.
I still believe the thermal layer doesn't need any change to work around this problem, and I still believe that when a cooling device is being registered, all of its ops should be fully functional. The problem with the cpufreq cooling device driver is that its callbacks use the internal list of devices to retrieve the struct cpufreq_cooling_device instance corresponding to a given struct thermal_cooling_device. This is not necessary, because the struct thermal_cooling_device has a private data pointer (devdata) which in this case is exactly a reference to the struct cpufreq_cooling_device instance the callbacks are looking for. In fact, I think the cooling_cpufreq_list is not necessary at all, and should be removed from the cpufreq cooling driver. So the 3 callbacks, instead of iterating through the device list, should have something like: struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; That would do the trick.
-- Francesco
On 24 October 2012 06:13, Francesco Lavra francescolavra.fl@gmail.com wrote:
Hi, On 10/23/2012 10:23 AM, Hongbo Zhang wrote:
Hi Francesco, I found out more points about this issue.
[1] cdev should be ready when get_max_state callback be called, otherwise parameter cdev is useless, imagine there may be cases that get_max_state call back is shared by more than one cooling devices of same kind, like this: common_get_max_state(*cdev, *state) { if (cdev == cdev1) *state = 3; else if (cdev == cdev) *state = 5; else }
[2] In my cpufreq cooling case(in fact cdev is not used to calculate max_state), the cooling_cpufreq_list should be ready when get_max_state call back is called. In this patch I defer the binding when registration finished, but this is not perfect now, see this routine in cpufreq_cooling_register:
thermal_cooling_device_register; at this time: thermal_bind_work -> get_max_state -> get NULL cooling_cpufreq_list and then: list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list) This is due to we cannot know exactly when the bind work is executed. (and this can be fixed by moving mutex_lock(&cooling_cpufreq_lock) aheadof thermal_cooling_device_register and other corresponding modifications, but I found another way as below)
[3] Root cause of this problem is calling get_max_state in register -> bind routine. Better solution is to add another parameter in cooling device register function, also add a max_state member in struct cdev, like: thermal_cooling_device_register(char *type, void *devdata, const struct thermal_cooling_device_ops *ops, unsigned long max_state) and then in the bind function: if(cdev->max_state) max_state = cdev->max_state; else cdev->get_max_state(cdev, &max_state)
It is common sense that the cooling driver should know its cooling max_state(ability) before registration, and it can be offered when register. I think this way doesn't change both thermal and cooling layer much, it is more clear. I will update this patch soon.
I still believe the thermal layer doesn't need any change to work around this problem, and I still believe that when a cooling device is being registered, all of its ops should be fully functional. The problem with the cpufreq cooling device driver is that its callbacks use the internal list of devices to retrieve the struct cpufreq_cooling_device instance corresponding to a given struct thermal_cooling_device. This is not necessary, because the struct thermal_cooling_device has a private data pointer (devdata) which in this case is exactly a reference to the struct cpufreq_cooling_device instance the callbacks are looking for. In fact, I think the cooling_cpufreq_list is not necessary at all, and should be removed from the cpufreq cooling driver. So the 3 callbacks, instead of iterating through the device list, should have something like: struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; That would do the trick.
Hi Francesco, When I found out this issue, I was hesitating to select the best solution among several ideas. It is clear now after talk with you, I will send patch for cpufreq cooling layer. Thank you.
-- Francesco
From: "hongbo.zhang" hongbo.zhang@linaro.com
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- drivers/thermal/cpu_cooling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index cc1c930..b6b4c2a 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -369,7 +369,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (min != policy.cpuinfo.min_freq || max != policy.cpuinfo.max_freq) return ERR_PTR(-EINVAL); -} + } } cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device), GFP_KERNEL);
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
People always like to see a commit log here :)
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
drivers/thermal/cpu_cooling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index cc1c930..b6b4c2a 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -369,7 +369,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (min != policy.cpuinfo.min_freq || max != policy.cpuinfo.max_freq) return ERR_PTR(-EINVAL); -}
}
Apart from that:
Reviewed-by: Viresh Kumar viresh.kumar@linaro.org
From: "hongbo.zhang" hongbo.zhang@linaro.com
Is is not reliable to check the list entry pointer after list_for_each_entry loop, list_empty should be used instead.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- drivers/thermal/cpu_cooling.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index b6b4c2a..d196230 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -247,12 +247,13 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, struct cpufreq_frequency_table *table;
mutex_lock(&cooling_cpufreq_lock); + if (list_empty(&cooling_cpufreq_list)) + goto return_get_max_state; + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { if (cpufreq_device && cpufreq_device->cool_dev == cdev) break; } - if (cpufreq_device == NULL) - goto return_get_max_state;
maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr);
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
Is is not reliable to check the list entry pointer after list_for_each_entry loop, list_empty should be used instead.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
drivers/thermal/cpu_cooling.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index b6b4c2a..d196230 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -247,12 +247,13 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, struct cpufreq_frequency_table *table;
mutex_lock(&cooling_cpufreq_lock);
if (list_empty(&cooling_cpufreq_list))
goto return_get_max_state;
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { if (cpufreq_device && cpufreq_device->cool_dev == cdev) break; }
if (cpufreq_device == NULL)
goto return_get_max_state;
I am surprised, why is it written like this in the first place :)
Reviewed-by: Viresh Kumar viresh.kumar@linaro.org
From: "hongbo.zhang" hongbo.zhang@linaro.com
The cpufreq works as a cooling device, so the cooling layer should check and wait until the cpufreq driver is initialized.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- drivers/thermal/cpu_cooling.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index d196230..01aba58 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -29,6 +29,7 @@ #include <linux/slab.h> #include <linux/cpu.h> #include <linux/cpu_cooling.h> +#include <linux/delay.h>
/** * struct cpufreq_cooling_device @@ -352,9 +353,18 @@ struct thermal_cooling_device *cpufreq_cooling_register( struct cpufreq_cooling_device *cpufreq_dev = NULL; unsigned int cpufreq_dev_count = 0, min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; - int ret = 0, i; + int ret = 0, to = 1000, i; struct cpufreq_policy policy;
+ /* make sure cpufreq driver is initialized */ + while (!cpufreq_frequency_get_table(0) && --to) + mdelay(10); + + if (!to) { + pr_err("No cpufreq driver act as cooling device.\n"); + return ERR_PTR(-ENOSYS); + } + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) cpufreq_dev_count++;
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
The cpufreq works as a cooling device, so the cooling layer should check and wait until the cpufreq driver is initialized.
Idea is good.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
drivers/thermal/cpu_cooling.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index d196230..01aba58 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -29,6 +29,7 @@ #include <linux/slab.h> #include <linux/cpu.h> #include <linux/cpu_cooling.h> +#include <linux/delay.h>
Would be better if we add them alphabetically. That makes there maintenance easier. I know the list is already mismanaged. :)
If you can add another patch here to fix that, would be good.
/**
- struct cpufreq_cooling_device
@@ -352,9 +353,18 @@ struct thermal_cooling_device *cpufreq_cooling_register( struct cpufreq_cooling_device *cpufreq_dev = NULL; unsigned int cpufreq_dev_count = 0, min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH];
int ret = 0, i;
int ret = 0, to = 1000, i; struct cpufreq_policy policy;
/* make sure cpufreq driver is initialized */
while (!cpufreq_frequency_get_table(0) && --to)
mdelay(10);
if (!to) {
pr_err("No cpufreq driver act as cooling device.\n");
return ERR_PTR(-ENOSYS);
}
I understand that you want cpufreq to be there before this thing, but i didn't like the idea here. There should be something else like returning -EPROBE_DEFER, so that driver can be pinged again.
-- viresh
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- arch/arm/boot/dts/dbx5x0.dtsi | 11 + arch/arm/configs/u8500_defconfig | 4 + arch/arm/mach-ux500/board-mop500.c | 73 ++++ drivers/thermal/Kconfig | 20 ++ drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 118 +++++++ drivers/thermal/db8500_thermal.c | 507 +++++++++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 39 +++ 8 files changed, 774 insertions(+) create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi index 748ba7a..795d7ee 100644 --- a/arch/arm/boot/dts/dbx5x0.dtsi +++ b/arch/arm/boot/dts/dbx5x0.dtsi @@ -174,6 +174,10 @@ compatible = "stericsson,nmk_pinctrl"; };
+ cpufreq-cooling { + compatible = "stericsson,db8500-cpufreq-cooling"; + }; + usb@a03e0000 { compatible = "stericsson,db8500-musb", "mentor,musb"; @@ -203,6 +207,13 @@ reg = <0x80157450 0xC>; };
+ thermal@801573c0 { + compatible = "stericsson,db8500-thermal"; + reg = <0x801573c0 0x40>; + interrupts = <21 0x4>, <22 0x4>; + interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH"; + }; + db8500-prcmu-regulators { compatible = "stericsson,db8500-prcmu-regulator";
diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index cc5e7a8..34918c4 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 416d436..5bbd3b5 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -33,6 +33,8 @@ #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> #include <linux/leds.h> @@ -229,6 +231,71 @@ static struct ab8500_platform_data ab8500_platdata = { };
/* + * Thermal Sensor + */ + +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-0", + }, + }, + [2] = { + .temp = 80000, + .type = THERMAL_TRIP_ACTIVE, + .cooling_dev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + [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, + }, +}; + +static struct platform_device u8500_cpufreq_cooling_device = { + .name = "db8500-cpufreq-cooling", +}; + +/* * TPS61052 */
@@ -583,6 +650,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_key_dev, &snowball_sbnet_dev, &snowball_gpio_en_3v3_regulator_dev, + &u8500_thsens_device, + &u8500_cpufreq_cooling_device, };
static void __init mop500_init_machine(void) @@ -765,6 +834,10 @@ struct of_dev_auxdata u8500_auxdata_lookup[] __initdata = { "ux500-msp-i2s.2", &msp2_platform_data), OF_DEV_AUXDATA("stericsson,ux500-msp-i2s", 0x80125000, "ux500-msp-i2s.3", &msp3_platform_data), + OF_DEV_AUXDATA("stericsson,db8500-thermal", 0x801573c0, + "db8500-thermal", &db8500_thsens_data), + OF_DEV_AUXDATA("stericsson,db8500-cpufreq-cooling", 0, + "db8500-cpufreq-cooling", NULL), {}, };
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index edfd67d..6607cba 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -30,6 +30,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL + bool "DB8500 thermal management" + depends on THERMAL + default y + help + Adds DB8500 thermal management implementation according to the thermal + management framework. A thermal zone with several trip points will be + created. Cooling devices can be bound to the trip points to cool this + thermal zone if trip points reached. + +config DB8500_CPUFREQ_COOLING + tristate "DB8500 cpufreq cooling" + depends on CPU_THERMAL + default y + help + Adds DB8500 cpufreq cooling devices, and these cooling devices can be + bound to thermal zone trip points. When a trip point reached, the + bound cpufreq cooling device turns active to set CPU frequency low to + cool down the CPU. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..bb065d4 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,118 @@ +/* + * 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> +#include <linux/err.h> + +static LIST_HEAD(db8500_cpufreq_cdev_list); + +struct db8500_cpufreq_cdev { + struct thermal_cooling_device *cdev; + struct list_head node; +}; + +static int __devinit db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{ + struct db8500_cpufreq_cdev *cooling_devs; + struct cpumask mask_val; + + cooling_devs = devm_kzalloc(&pdev->dev, + sizeof(struct db8500_cpufreq_cdev), GFP_KERNEL); + if (!cooling_devs) + return -ENOMEM; + + cpumask_set_cpu(0, &mask_val); + cooling_devs->cdev = cpufreq_cooling_register(&mask_val); + + if (IS_ERR(cooling_devs->cdev)) { + pr_err("Failed to register cpufreq cooling device\n"); + return PTR_ERR(cooling_devs->cdev); + } + + list_add_tail(&cooling_devs->node, &db8500_cpufreq_cdev_list); + pr_info("Cooling device registered: %s\n", + cooling_devs->cdev->type); + + return 0; +} + +static int __devexit db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{ + struct db8500_cpufreq_cdev *cooling_devs; + + list_for_each_entry(cooling_devs, &db8500_cpufreq_cdev_list, node) + cpufreq_cooling_unregister(cooling_devs->cdev); + + return 0; +} + +static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return -ENOSYS; +} + +static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{ + return -ENOSYS; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = { + { .compatible = "stericsson,db8500-cpufreq-cooling" }, + {}, +}; +#else +#define db8500_cpufreq_cooling_match NULL +#endif + +static struct platform_driver db8500_cpufreq_cooling_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-cpufreq-cooling", + .of_match_table = db8500_cpufreq_cooling_match, + }, + .probe = db8500_cpufreq_cooling_probe, + .suspend = db8500_cpufreq_cooling_suspend, + .resume = db8500_cpufreq_cooling_resume, + .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 100644 index 0000000..34dcc52 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,507 @@ +/* + * 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/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; + struct mutex th_lock; + struct platform_device *thsens_pdev; + struct work_struct therm_work; + struct db8500_thsens_platform_data *trip_tab; + enum thermal_device_mode mode; + enum thermal_trend trend; + unsigned long cur_temp_pseudo; + unsigned int cur_index; + int low_irq; + int high_irq; +}; + +/* Callback to bind cooling device to 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; + char *cdev_name; + unsigned long max_state, upper, lower; + int i, j, ret; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + ptrips = pzone->trip_tab; + + if (!cdev->type) + return -EINVAL; + + ret = -ENODEV; + for (i = 0; i < ptrips->num_trips; i++) + for (j = 0; j < COOLING_DEV_MAX; j++) { + cdev_name = ptrips->trip_points[i].cooling_dev_name[j]; + if (!cdev_name) + continue; + + if (strcmp(cdev_name, cdev->type)) + continue; + + cdev->ops->get_max_state(cdev, &max_state); + upper = (i > max_state) ? max_state : i; + lower = (i > max_state) ? max_state : i; + + ret = thermal_zone_bind_cooling_device(thermal, i, + cdev, upper, lower); + if (ret) + pr_err("Error binding cooling device.\n"); + else + pr_info("Cdev %s bound.\n", cdev->type); + } + + return ret; +} + +/* Callback to unbind cooling device from 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; + char *cdev_name; + int i, j, ret; + + pzone = (struct db8500_thermal_zone *)thermal->devdata; + ptrips = pzone->trip_tab; + + if (!cdev->type) + return -EINVAL; + + ret = -ENODEV; + for (i = 0; i < ptrips->num_trips; i++) + for (j = 0; j < COOLING_DEV_MAX; j++) { + cdev_name = ptrips->trip_points[i].cooling_dev_name[j]; + if (!cdev_name) + continue; + + if (strcmp(cdev_name, cdev->type)) + continue; + + ret = thermal_zone_unbind_cooling_device( + thermal, i, cdev); + if (ret) + pr_err("Error unbinding cooling device.\n"); + else + pr_info("Cdev %s unbound.\n", cdev->type); + } + + return ret; +} + +/* Callback to get current temperature */ +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; + + /* TODO: There is no PRCMU interface to get temperature data currently, + so a pseudo temperature is returned , it works for the thermal framework + and this will be fixed when the PRCMU interface is available */ + *temp = pzone->cur_temp_pseudo; + + return 0; +} + +/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + struct db8500_thermal_zone *pzone; + pzone = (struct db8500_thermal_zone *)thermal->devdata; + + *trend = pzone->trend; + + return 0; +} + +/* Callback to get thermal zone mode */ +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; +} + +/* Callback to set thermal zone mode */ +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); + + pzone->mode = mode; + + if (mode == THERMAL_DEVICE_ENABLED) + schedule_work(&pzone->therm_work); + + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to get trip point type */ +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->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *type = ptrips->trip_points[trip].type; + + return 0; +} + +/* Callback to get trip point temperature */ +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->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *temp = ptrips->trip_points[trip].temp; + + return 0; +} + +/* Callback to get critical trip point temperature */ +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->trip_tab; + + 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_trend = db8500_sys_get_trend, + .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; + unsigned int idx; + + ptrips = pzone->trip_tab; + idx = pzone->cur_index; + if (unlikely(idx == 0)) + /* Meaningless for thermal management, ignoring it */ + return IRQ_HANDLED; + + if (idx == 1) { + next_high = ptrips->trip_points[0].temp; + next_low = PRCMU_DEFAULT_LOW_TEMP; + } else { + next_high = ptrips->trip_points[idx-1].temp; + next_low = ptrips->trip_points[idx-2].temp; + } + + pzone->cur_index -= 1; + pzone->cur_temp_pseudo = (next_high + next_low)/2; + + prcmu_stop_temp_sense(); + prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); + prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + + pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low); + + pzone->trend = THERMAL_TREND_DROPPING; + 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; + unsigned int idx; + + ptrips = pzone->trip_tab; + idx = pzone->cur_index; + + if (idx < ptrips->num_trips - 1) { + next_high = ptrips->trip_points[idx+1].temp; + next_low = ptrips->trip_points[idx].temp; + + pzone->cur_index += 1; + pzone->cur_temp_pseudo = (next_high + next_low)/2; + + prcmu_stop_temp_sense(); + prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); + prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + + pr_debug("PRCMU set max %ld, min %ld\n", next_high, next_low); + } + + if (idx == ptrips->num_trips - 1) + pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1; + + pzone->trend = THERMAL_TREND_RAISING; + 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; + + pzone = devm_kzalloc(&pdev->dev, + 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"); + return low_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL, + prcmu_low_irq_handler, + IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", pzone); + if (ret < 0) { + pr_err("Failed to allocate temp low irq.\n"); + return ret; + } + + high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (high_irq < 0) { + pr_err("Get IRQ_HOTMON_HIGH failed.\n"); + return high_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL, + prcmu_high_irq_handler, + IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", pzone); + if (ret < 0) { + pr_err("Failed to allocate temp high irq.\n"); + return ret; + } + + pzone->low_irq = low_irq; + pzone->high_irq = high_irq; + + pzone->mode = THERMAL_DEVICE_DISABLED; + + mutex_init(&pzone->th_lock); + + INIT_WORK(&pzone->therm_work, db8500_thermal_work); + + ptrips = pdev->dev.platform_data; + pzone->trip_tab = ptrips; + + pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone", + ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0); + + if (IS_ERR(pzone->therm_dev)) { + pr_err("Failed to register thermal zone device\n"); + return PTR_ERR(pzone->therm_dev); + } + + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + prcmu_stop_temp_sense(); + prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000)); + prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + + pzone->cur_index = 0; + pzone->cur_temp_pseudo = (dft_low + dft_high)/2; + pzone->trend = THERMAL_TREND_STABLE; + pzone->mode = THERMAL_DEVICE_ENABLED; + + platform_set_drvdata(pdev, pzone); + + return 0; +} + +static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone; + pzone = platform_get_drvdata(pdev); + + cancel_work_sync(&pzone->therm_work); + + if (pzone->therm_dev) + thermal_zone_device_unregister(pzone->therm_dev); + + return 0; +} + +static int db8500_thermal_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct db8500_thermal_zone *pzone; + pzone = platform_get_drvdata(pdev); + + flush_work_sync(&pzone->therm_work); + prcmu_stop_temp_sense(); + + return 0; +} + +static int db8500_thermal_resume(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone; + struct db8500_thsens_platform_data *ptrips; + unsigned long dft_low, dft_high; + + pzone = platform_get_drvdata(pdev); + ptrips = pzone->trip_tab; + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000)); + prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = { + { .compatible = "stericsson,db8500-thermal" }, + {}, +}; +#else +#define db8500_thermal_match NULL +#endif + +static struct platform_driver db8500_thermal_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-thermal", + .of_match_table = db8500_thermal_match, + }, + .probe = db8500_thermal_probe, + .suspend = db8500_thermal_suspend, + .resume = db8500_thermal_resume, + .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_ */
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
I am not sure if you have entered a "ENTER" command after each line (to make it 80 columns aligned) or vim did it for you.
There is a very good way by which you can do it automatically.
Select all lines in vim and press gq.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
arch/arm/boot/dts/dbx5x0.dtsi | 11 + arch/arm/configs/u8500_defconfig | 4 + arch/arm/mach-ux500/board-mop500.c | 73 ++++ drivers/thermal/Kconfig | 20 ++ drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 118 +++++++ drivers/thermal/db8500_thermal.c | 507 +++++++++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 39 +++
It would be better to split platform and driver parts into separate patches.
diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi index 748ba7a..795d7ee 100644 --- a/arch/arm/boot/dts/dbx5x0.dtsi +++ b/arch/arm/boot/dts/dbx5x0.dtsi @@ -174,6 +174,10 @@ compatible = "stericsson,nmk_pinctrl"; };
cpufreq-cooling {
compatible = "stericsson,db8500-cpufreq-cooling";
};
usb@a03e0000 { compatible = "stericsson,db8500-musb", "mentor,musb";
@@ -203,6 +207,13 @@ reg = <0x80157450 0xC>; };
thermal@801573c0 {
compatible = "stericsson,db8500-thermal";
reg = <0x801573c0 0x40>;
interrupts = <21 0x4>, <22 0x4>;
interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
};
It is considered better to mark devices disabled in dtsi files and actually mark them OK or Okay in board's dts file.
diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c @@ -33,6 +33,8 @@ #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> #include <linux/leds.h> @@ -229,6 +231,71 @@ static struct ab8500_platform_data ab8500_platdata = { };
/*
- Thermal Sensor
- */
+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",
If i am not wrong length of cooling_dev_name can't be greater than 8
},
},
[1] = {
.temp = 75000,
.type = THERMAL_TRIP_ACTIVE,
.cooling_dev_name = {
[0] = "thermal-cpufreq-0",
},
},
[2] = {
.temp = 80000,
.type = THERMAL_TRIP_ACTIVE,
.cooling_dev_name = {
[0] = "thermal-cpufreq-0",
},
},
[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,
},
+};
+static struct platform_device u8500_cpufreq_cooling_device = {
.name = "db8500-cpufreq-cooling",
+};
+/*
- TPS61052
*/
@@ -583,6 +650,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_key_dev, &snowball_sbnet_dev, &snowball_gpio_en_3v3_regulator_dev,
&u8500_thsens_device,
&u8500_cpufreq_cooling_device,
};
static void __init mop500_init_machine(void) @@ -765,6 +834,10 @@ struct of_dev_auxdata u8500_auxdata_lookup[] __initdata = { "ux500-msp-i2s.2", &msp2_platform_data), OF_DEV_AUXDATA("stericsson,ux500-msp-i2s", 0x80125000, "ux500-msp-i2s.3", &msp3_platform_data),
OF_DEV_AUXDATA("stericsson,db8500-thermal", 0x801573c0,
"db8500-thermal", &db8500_thsens_data),
OF_DEV_AUXDATA("stericsson,db8500-cpufreq-cooling", 0,
"db8500-cpufreq-cooling", NULL), {},
};
Because i am not well aware of this file, May i know what are we doing here. Are we supporting two boards here? one with DT other without it?
Whatever the case, at-least we should pass data via DT for u8500_auxdata_lookup[]. As you are adding the driver for the first time here, it must be able to parse data via DT.
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index edfd67d..6607cba 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -30,6 +30,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL
bool "DB8500 thermal management"
depends on THERMAL
default y
help
Adds DB8500 thermal management implementation according to the thermal
management framework. A thermal zone with several trip points will be
created. Cooling devices can be bound to the trip points to cool this
thermal zone if trip points reached.
+config DB8500_CPUFREQ_COOLING
tristate "DB8500 cpufreq cooling"
depends on CPU_THERMAL
Shouldn't this depend on DB8500_THERMAL instead?
default y
help
Adds DB8500 cpufreq cooling devices, and these cooling devices can be
bound to thermal zone trip points. When a trip point reached, the
bound cpufreq cooling device turns active to set CPU frequency low to
cool down the CPU.
config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c
+/*
- 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> +#include <linux/err.h>
should be in alphabetical order
+static LIST_HEAD(db8500_cpufreq_cdev_list);
+struct db8500_cpufreq_cdev {
struct thermal_cooling_device *cdev;
struct list_head node;
+};
+static int __devinit db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{
struct db8500_cpufreq_cdev *cooling_devs;
cooling_dev would be more appropriate?
struct cpumask mask_val;
cooling_devs = devm_kzalloc(&pdev->dev,
sizeof(struct db8500_cpufreq_cdev), GFP_KERNEL);
sizeof(*cooling_devs)
if (!cooling_devs)
return -ENOMEM;
cpumask_set_cpu(0, &mask_val);
cooling_devs->cdev = cpufreq_cooling_register(&mask_val);
if (IS_ERR(cooling_devs->cdev)) {
IS_ERR_OR_NULL?
pr_err("Failed to register cpufreq cooling device\n");
dev_err?
return PTR_ERR(cooling_devs->cdev);
}
list_add_tail(&cooling_devs->node, &db8500_cpufreq_cdev_list);
pr_info("Cooling device registered: %s\n",
dev_info?
cooling_devs->cdev->type);
return 0;
+}
+static int __devexit db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{
struct db8500_cpufreq_cdev *cooling_devs;
cooling_dev?
list_for_each_entry(cooling_devs, &db8500_cpufreq_cdev_list, node)
cpufreq_cooling_unregister(cooling_devs->cdev);
If there are multiple calls to probe, then there must be multiple calls to remove also. Why do you remove everything for the first device here?
return 0;
+}
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
return -ENOSYS;
+}
Do you need these? Wouldn't it be same if you don't define them?
+#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = {
{ .compatible = "stericsson,db8500-cpufreq-cooling" },
{},
+}; +#else +#define db8500_cpufreq_cooling_match NULL +#endif
+static struct platform_driver db8500_cpufreq_cooling_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-cpufreq-cooling",
.of_match_table = db8500_cpufreq_cooling_match,
},
.probe = db8500_cpufreq_cooling_probe,
__devinit
.suspend = db8500_cpufreq_cooling_suspend,
.resume = db8500_cpufreq_cooling_resume,
.remove = __devexit_p(db8500_cpufreq_cooling_remove),
__devexit
+};
+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 100644 index 0000000..34dcc52 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,507 @@ +/*
- 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/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>
alphabetical order :)
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0
Can we align macro values with tabs.. makes it more readable.
+struct db8500_thermal_zone {
struct thermal_zone_device *therm_dev;
struct mutex th_lock;
struct platform_device *thsens_pdev;
struct work_struct therm_work;
struct db8500_thsens_platform_data *trip_tab;
enum thermal_device_mode mode;
enum thermal_trend trend;
unsigned long cur_temp_pseudo;
unsigned int cur_index;
int low_irq;
int high_irq;
+};
+/* Callback to bind cooling device to 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;
char *cdev_name;
unsigned long max_state, upper, lower;
int i, j, ret;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
should work without cast.
ptrips = pzone->trip_tab;
if (!cdev->type)
return -EINVAL;
ret = -ENODEV;
would be better to merge with definition of ret
for (i = 0; i < ptrips->num_trips; i++)
for (j = 0; j < COOLING_DEV_MAX; j++) {
cdev_name = ptrips->trip_points[i].cooling_dev_name[j];
if (!cdev_name)
continue;
if (strcmp(cdev_name, cdev->type))
continue;
cdev->ops->get_max_state(cdev, &max_state);
upper = (i > max_state) ? max_state : i;
lower = (i > max_state) ? max_state : i;
ret = thermal_zone_bind_cooling_device(thermal, i,
cdev, upper, lower);
if (ret)
pr_err("Error binding cooling device.\n");
else
pr_info("Cdev %s bound.\n", cdev->type);
dev_info, dev_err?
}
return ret;
+}
+/* Callback to unbind cooling device from 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;
char *cdev_name;
int i, j, ret;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
cast not required.
ptrips = pzone->trip_tab;
if (!cdev->type)
return -EINVAL;
ret = -ENODEV;
same.
for (i = 0; i < ptrips->num_trips; i++)
for (j = 0; j < COOLING_DEV_MAX; j++) {
cdev_name = ptrips->trip_points[i].cooling_dev_name[j];
if (!cdev_name)
continue;
if (strcmp(cdev_name, cdev->type))
continue;
ret = thermal_zone_unbind_cooling_device(
thermal, i, cdev);
if (ret)
pr_err("Error unbinding cooling device.\n");
else
pr_info("Cdev %s unbound.\n", cdev->type);
}
return ret;
+}
Can you try to take common part of above two routines into another one? They are pretty much similar.
+/* Callback to get current temperature */ +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;
cast not required. Also merge above two lines.
/* TODO: There is no PRCMU interface to get temperature data currently,
so a pseudo temperature is returned , it works for the thermal framework
and this will be fixed when the PRCMU interface is available */
Should Comment style be like: /* * ... */
*temp = pzone->cur_temp_pseudo;
return 0;
+}
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
struct db8500_thermal_zone *pzone;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
:(
*trend = pzone->trend;
return 0;
+}
+/* Callback to get thermal zone mode */ +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;
check everywhere :)
mutex_lock(&pzone->th_lock);
*mode = pzone->mode;
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to set thermal zone mode */ +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");
dev_err
return 0;
}
mutex_lock(&pzone->th_lock);
pzone->mode = mode;
if (mode == THERMAL_DEVICE_ENABLED)
schedule_work(&pzone->therm_work);
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to get trip point type */ +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->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*type = ptrips->trip_points[trip].type;
return 0;
+}
+/* Callback to get trip point temperature */ +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->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*temp = ptrips->trip_points[trip].temp;
return 0;
+}
+/* Callback to get critical trip point temperature */ +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->trip_tab;
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_trend = db8500_sys_get_trend,
.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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
} else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
}
pzone->cur_index -= 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low);
pzone->trend = THERMAL_TREND_DROPPING;
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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
pzone->cur_index += 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, min %ld\n", next_high, next_low);
please check all these too.. dev_debug
}
if (idx == ptrips->num_trips - 1)
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
pzone->trend = THERMAL_TREND_RAISING;
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;
pzone = devm_kzalloc(&pdev->dev,
sizeof(struct db8500_thermal_zone), GFP_KERNEL);
sizeof(*pzone)
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");
return low_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", pzone);
can you try these lines with gq i suggested earlier?
if (ret < 0) {
pr_err("Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
pr_err("Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", pzone);
if (ret < 0) {
pr_err("Failed to allocate temp high irq.\n");
return ret;
}
pzone->low_irq = low_irq;
pzone->high_irq = high_irq;
pzone->mode = THERMAL_DEVICE_DISABLED;
mutex_init(&pzone->th_lock);
INIT_WORK(&pzone->therm_work, db8500_thermal_work);
ptrips = pdev->dev.platform_data;
pzone->trip_tab = ptrips;
pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
if (IS_ERR(pzone->therm_dev)) {
IS_ERR_OR_NULL?
pr_err("Failed to register thermal zone device\n");
dev_err
return PTR_ERR(pzone->therm_dev);
}
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pzone->cur_index = 0;
pzone->cur_temp_pseudo = (dft_low + dft_high)/2;
pzone->trend = THERMAL_TREND_STABLE;
pzone->mode = THERMAL_DEVICE_ENABLED;
platform_set_drvdata(pdev, pzone);
return 0;
+}
+static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
cancel_work_sync(&pzone->therm_work);
if (pzone->therm_dev)
Can this be false? If you were not able to register a thermal_zone dev then you return error from probe and so remove wouldn't be called.
thermal_zone_device_unregister(pzone->therm_dev);
return 0;
+}
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
flush_work_sync(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
struct db8500_thsens_platform_data *ptrips;
unsigned long dft_low, dft_high;
pzone = platform_get_drvdata(pdev);
ptrips = pzone->trip_tab;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
return 0;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = {
{ .compatible = "stericsson,db8500-thermal" },
{},
+}; +#else +#define db8500_thermal_match NULL +#endif
+static struct platform_driver db8500_thermal_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-thermal",
.of_match_table = db8500_thermal_match,
},
.probe = db8500_thermal_probe,
__devinit
.suspend = db8500_thermal_suspend,
.resume = db8500_thermal_resume,
.remove = __devexit_p(db8500_thermal_remove),
__devexit
+};
+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);
Use module_platform_driver().
-- viresh
On Wed, 2012-10-17 at 20:53 +0530, Viresh Kumar wrote:
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
[]
diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c
[]
+#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h> +#include <linux/err.h>
should be in alphabetical order
There's no agreed kernel convention here. Some prefer christmas tree (shortest to longest length)
On 17 October 2012 22:28, Joe Perches joe@perches.com wrote:
+#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h> +#include <linux/err.h>
should be in alphabetical order
There's no agreed kernel convention here. Some prefer christmas tree (shortest to longest length)
:)
I have seen a number of times this happening, because the list isn't in alphabetical order people aren't able to easily read if an #include ... is already there or not.
And so one header file is included multiple times. Because git diff only shows few lines above and below a change, even people can't catch it in reviews. That's why they must always be in alphabetical order.
-- viresh
Viresh, thanks a lot for all of your comments. I accept them _by_default_ if no comment from me under them.
On 17 October 2012 23:23, Viresh Kumar viresh.kumar@linaro.org wrote:
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
I am not sure if you have entered a "ENTER" command after each line (to make it 80 columns aligned) or vim did it for you.
There is a very good way by which you can do it automatically.
Select all lines in vim and press gq.
Thanks, this is a new and cool command for me. I really used ENTER before :(
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
arch/arm/boot/dts/dbx5x0.dtsi | 11 + arch/arm/configs/u8500_defconfig | 4 + arch/arm/mach-ux500/board-mop500.c | 73 ++++ drivers/thermal/Kconfig | 20 ++ drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 118 +++++++ drivers/thermal/db8500_thermal.c | 507 +++++++++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 39 +++
It would be better to split platform and driver parts into separate patches.
only board-mop500.c is platform part in this case, is it true?
diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi index 748ba7a..795d7ee 100644 --- a/arch/arm/boot/dts/dbx5x0.dtsi +++ b/arch/arm/boot/dts/dbx5x0.dtsi @@ -174,6 +174,10 @@ compatible = "stericsson,nmk_pinctrl"; };
cpufreq-cooling {
compatible = "stericsson,db8500-cpufreq-cooling";
};
usb@a03e0000 { compatible = "stericsson,db8500-musb", "mentor,musb";
@@ -203,6 +207,13 @@ reg = <0x80157450 0xC>; };
thermal@801573c0 {
compatible = "stericsson,db8500-thermal";
reg = <0x801573c0 0x40>;
interrupts = <21 0x4>, <22 0x4>;
interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
};
It is considered better to mark devices disabled in dtsi files and actually mark them OK or Okay in board's dts file.
diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c @@ -33,6 +33,8 @@ #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> #include <linux/leds.h> @@ -229,6 +231,71 @@ static struct ab8500_platform_data ab8500_platdata = { };
/*
- Thermal Sensor
- */
+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",
If i am not wrong length of cooling_dev_name can't be greater than 8
You are wrong this time, it is 20 #define THERMAL_NAME_LENGTH 20
},
},
[1] = {
.temp = 75000,
.type = THERMAL_TRIP_ACTIVE,
.cooling_dev_name = {
[0] = "thermal-cpufreq-0",
},
},
[2] = {
.temp = 80000,
.type = THERMAL_TRIP_ACTIVE,
.cooling_dev_name = {
[0] = "thermal-cpufreq-0",
},
},
[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,
},
+};
+static struct platform_device u8500_cpufreq_cooling_device = {
.name = "db8500-cpufreq-cooling",
+};
+/*
- TPS61052
*/
@@ -583,6 +650,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_key_dev, &snowball_sbnet_dev, &snowball_gpio_en_3v3_regulator_dev,
&u8500_thsens_device,
&u8500_cpufreq_cooling_device,
};
static void __init mop500_init_machine(void) @@ -765,6 +834,10 @@ struct of_dev_auxdata u8500_auxdata_lookup[] __initdata = { "ux500-msp-i2s.2", &msp2_platform_data), OF_DEV_AUXDATA("stericsson,ux500-msp-i2s", 0x80125000, "ux500-msp-i2s.3", &msp3_platform_data),
OF_DEV_AUXDATA("stericsson,db8500-thermal", 0x801573c0,
"db8500-thermal", &db8500_thsens_data),
OF_DEV_AUXDATA("stericsson,db8500-cpufreq-cooling", 0,
"db8500-cpufreq-cooling", NULL), {},
};
Because i am not well aware of this file, May i know what are we doing here. Are we supporting two boards here? one with DT other without it?
Because DT is not totally supported in all drivers on Snowball, DT is disabled by default. Doing this is to make thermal driver work what ever DT is enabled or not. There are other cases like this, I just follow the current patten. The corresponding maintainer will clean up this part at last.
Whatever the case, at-least we should pass data via DT for u8500_auxdata_lookup[]. As you are adding the driver for the first time here, it must be able to parse data via DT.
Yes, for "db8500-thermal", data &db8500_thsens_data is parsed via DT but there is really no data for "db8500-cpufreq-cooling".
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index edfd67d..6607cba 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -30,6 +30,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL
bool "DB8500 thermal management"
depends on THERMAL
default y
help
Adds DB8500 thermal management implementation according to the thermal
management framework. A thermal zone with several trip points will be
created. Cooling devices can be bound to the trip points to cool this
thermal zone if trip points reached.
+config DB8500_CPUFREQ_COOLING
tristate "DB8500 cpufreq cooling"
depends on CPU_THERMAL
Shouldn't this depend on DB8500_THERMAL instead?
No, the designed policy is that the cooling device can be added or removed dynamically, cooling device and thermal zone device are separated, they will be bound when match. it does depend on CPU_THERMAL. in another patch from Amit, CPU_THERMAL depends on THERMAL && CPU_FREQ
default y
help
Adds DB8500 cpufreq cooling devices, and these cooling devices can be
bound to thermal zone trip points. When a trip point reached, the
bound cpufreq cooling device turns active to set CPU frequency low to
cool down the CPU.
config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c
+/*
- 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> +#include <linux/err.h>
should be in alphabetical order
+static LIST_HEAD(db8500_cpufreq_cdev_list);
+struct db8500_cpufreq_cdev {
struct thermal_cooling_device *cdev;
struct list_head node;
+};
+static int __devinit db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{
struct db8500_cpufreq_cdev *cooling_devs;
cooling_dev would be more appropriate?
Accept. There is a historic reason, it was an array before, but the generic cooling layer changed, so no array here any more, but I forgot renaming it to cooling_devs.
struct cpumask mask_val;
cooling_devs = devm_kzalloc(&pdev->dev,
sizeof(struct db8500_cpufreq_cdev), GFP_KERNEL);
sizeof(*cooling_devs)
if (!cooling_devs)
return -ENOMEM;
cpumask_set_cpu(0, &mask_val);
cooling_devs->cdev = cpufreq_cooling_register(&mask_val);
if (IS_ERR(cooling_devs->cdev)) {
IS_ERR_OR_NULL?
pr_err("Failed to register cpufreq cooling device\n");
dev_err?
Accept dev_* too. There is also some reason, pr_* is used because I try to use uniform style with db8500_thermal.c where pr_* is also used. The reason pr_* is used there is that I found it is a bit redundant to get device pointer which is only used once as input parameter of dev_*. I will use dev_* it is preferred in drivers anyway.
return PTR_ERR(cooling_devs->cdev);
}
list_add_tail(&cooling_devs->node, &db8500_cpufreq_cdev_list);
pr_info("Cooling device registered: %s\n",
dev_info?
cooling_devs->cdev->type);
return 0;
+}
+static int __devexit db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{
struct db8500_cpufreq_cdev *cooling_devs;
cooling_dev?
list_for_each_entry(cooling_devs, &db8500_cpufreq_cdev_list, node)
cpufreq_cooling_unregister(cooling_devs->cdev);
If there are multiple calls to probe, then there must be multiple calls to remove also. Why do you remove everything for the first device here?
OK, there is defect here. I will check the input *pdev, unregister it and remove it from list.
return 0;
+}
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
return -ENOSYS;
+}
Do you need these? Wouldn't it be same if you don't define them?
There were not such functions before, I added them after reading Documentation/SubmittingDrivers. Is the document too old? should I follow it?
+#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = {
{ .compatible = "stericsson,db8500-cpufreq-cooling" },
{},
+}; +#else +#define db8500_cpufreq_cooling_match NULL +#endif
+static struct platform_driver db8500_cpufreq_cooling_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-cpufreq-cooling",
.of_match_table = db8500_cpufreq_cooling_match,
},
.probe = db8500_cpufreq_cooling_probe,
__devinit
.suspend = db8500_cpufreq_cooling_suspend,
.resume = db8500_cpufreq_cooling_resume,
.remove = __devexit_p(db8500_cpufreq_cooling_remove),
__devexit
+};
+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 100644 index 0000000..34dcc52 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,507 @@ +/*
- 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/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>
alphabetical order :)
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0
Can we align macro values with tabs.. makes it more readable.
+struct db8500_thermal_zone {
struct thermal_zone_device *therm_dev;
struct mutex th_lock;
struct platform_device *thsens_pdev;
struct work_struct therm_work;
struct db8500_thsens_platform_data *trip_tab;
enum thermal_device_mode mode;
enum thermal_trend trend;
unsigned long cur_temp_pseudo;
unsigned int cur_index;
int low_irq;
int high_irq;
+};
+/* Callback to bind cooling device to 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;
char *cdev_name;
unsigned long max_state, upper, lower;
int i, j, ret;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
should work without cast.
ptrips = pzone->trip_tab;
if (!cdev->type)
return -EINVAL;
ret = -ENODEV;
would be better to merge with definition of ret
for (i = 0; i < ptrips->num_trips; i++)
for (j = 0; j < COOLING_DEV_MAX; j++) {
cdev_name = ptrips->trip_points[i].cooling_dev_name[j];
if (!cdev_name)
continue;
if (strcmp(cdev_name, cdev->type))
continue;
cdev->ops->get_max_state(cdev, &max_state);
upper = (i > max_state) ? max_state : i;
lower = (i > max_state) ? max_state : i;
ret = thermal_zone_bind_cooling_device(thermal, i,
cdev, upper, lower);
if (ret)
pr_err("Error binding cooling device.\n");
else
pr_info("Cdev %s bound.\n", cdev->type);
dev_info, dev_err?
}
return ret;
+}
+/* Callback to unbind cooling device from 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;
char *cdev_name;
int i, j, ret;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
cast not required.
ptrips = pzone->trip_tab;
if (!cdev->type)
return -EINVAL;
ret = -ENODEV;
same.
for (i = 0; i < ptrips->num_trips; i++)
for (j = 0; j < COOLING_DEV_MAX; j++) {
cdev_name = ptrips->trip_points[i].cooling_dev_name[j];
if (!cdev_name)
continue;
if (strcmp(cdev_name, cdev->type))
continue;
ret = thermal_zone_unbind_cooling_device(
thermal, i, cdev);
if (ret)
pr_err("Error unbinding cooling device.\n");
else
pr_info("Cdev %s unbound.\n", cdev->type);
}
return ret;
+}
Can you try to take common part of above two routines into another one? They are pretty much similar.
+/* Callback to get current temperature */ +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;
cast not required. Also merge above two lines.
/* TODO: There is no PRCMU interface to get temperature data currently,
so a pseudo temperature is returned , it works for the thermal framework
and this will be fixed when the PRCMU interface is available */
Should Comment style be like: /*
- ...
*/
*temp = pzone->cur_temp_pseudo;
return 0;
+}
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
struct db8500_thermal_zone *pzone;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
:(
*trend = pzone->trend;
return 0;
+}
+/* Callback to get thermal zone mode */ +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;
check everywhere :)
mutex_lock(&pzone->th_lock);
*mode = pzone->mode;
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to set thermal zone mode */ +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");
dev_err
return 0;
}
mutex_lock(&pzone->th_lock);
pzone->mode = mode;
if (mode == THERMAL_DEVICE_ENABLED)
schedule_work(&pzone->therm_work);
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to get trip point type */ +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->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*type = ptrips->trip_points[trip].type;
return 0;
+}
+/* Callback to get trip point temperature */ +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->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*temp = ptrips->trip_points[trip].temp;
return 0;
+}
+/* Callback to get critical trip point temperature */ +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->trip_tab;
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_trend = db8500_sys_get_trend,
.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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
} else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
}
pzone->cur_index -= 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low);
pzone->trend = THERMAL_TREND_DROPPING;
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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
pzone->cur_index += 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, min %ld\n", next_high, next_low);
please check all these too.. dev_debug
}
if (idx == ptrips->num_trips - 1)
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
pzone->trend = THERMAL_TREND_RAISING;
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;
pzone = devm_kzalloc(&pdev->dev,
sizeof(struct db8500_thermal_zone), GFP_KERNEL);
sizeof(*pzone)
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");
return low_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", pzone);
can you try these lines with gq i suggested earlier?
if (ret < 0) {
pr_err("Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
pr_err("Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", pzone);
if (ret < 0) {
pr_err("Failed to allocate temp high irq.\n");
return ret;
}
pzone->low_irq = low_irq;
pzone->high_irq = high_irq;
pzone->mode = THERMAL_DEVICE_DISABLED;
mutex_init(&pzone->th_lock);
INIT_WORK(&pzone->therm_work, db8500_thermal_work);
ptrips = pdev->dev.platform_data;
pzone->trip_tab = ptrips;
pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
if (IS_ERR(pzone->therm_dev)) {
IS_ERR_OR_NULL?
pr_err("Failed to register thermal zone device\n");
dev_err
return PTR_ERR(pzone->therm_dev);
}
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pzone->cur_index = 0;
pzone->cur_temp_pseudo = (dft_low + dft_high)/2;
pzone->trend = THERMAL_TREND_STABLE;
pzone->mode = THERMAL_DEVICE_ENABLED;
platform_set_drvdata(pdev, pzone);
return 0;
+}
+static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
cancel_work_sync(&pzone->therm_work);
if (pzone->therm_dev)
Can this be false? If you were not able to register a thermal_zone dev then you return error from probe and so remove wouldn't be called.
thermal_zone_device_unregister(pzone->therm_dev);
return 0;
+}
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
flush_work_sync(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
struct db8500_thsens_platform_data *ptrips;
unsigned long dft_low, dft_high;
pzone = platform_get_drvdata(pdev);
ptrips = pzone->trip_tab;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
return 0;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = {
{ .compatible = "stericsson,db8500-thermal" },
{},
+}; +#else +#define db8500_thermal_match NULL +#endif
+static struct platform_driver db8500_thermal_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-thermal",
.of_match_table = db8500_thermal_match,
},
.probe = db8500_thermal_probe,
__devinit
.suspend = db8500_thermal_suspend,
.resume = db8500_thermal_resume,
.remove = __devexit_p(db8500_thermal_remove),
__devexit
+};
+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);
Use module_platform_driver().
-- viresh
On 18 October 2012 13:05, Hongbo Zhang hongbo.zhang@linaro.org wrote:
On 17 October 2012 23:23, Viresh Kumar viresh.kumar@linaro.org wrote:
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
+static struct db8500_trip_point db8500_trips_table[] = {
[0] = {
.temp = 70000,
.type = THERMAL_TRIP_ACTIVE,
.cooling_dev_name = {
[0] = "thermal-cpufreq-0",
If i am not wrong length of cooling_dev_name can't be greater than 8
You are wrong this time, it is 20 #define THERMAL_NAME_LENGTH 20
Ahh.. Its the array size fixed to 8.. not length of each element within.
static void __init mop500_init_machine(void) @@ -765,6 +834,10 @@ struct of_dev_auxdata u8500_auxdata_lookup[] __initdata = { "ux500-msp-i2s.2", &msp2_platform_data), OF_DEV_AUXDATA("stericsson,ux500-msp-i2s", 0x80125000, "ux500-msp-i2s.3", &msp3_platform_data),
OF_DEV_AUXDATA("stericsson,db8500-thermal", 0x801573c0,
"db8500-thermal", &db8500_thsens_data),
OF_DEV_AUXDATA("stericsson,db8500-cpufreq-cooling", 0,
"db8500-cpufreq-cooling", NULL), {},
};
Whatever the case, at-least we should pass data via DT for u8500_auxdata_lookup[]. As you are adding the driver for the first time here, it must be able to parse data via DT.
Yes, for "db8500-thermal", data &db8500_thsens_data is parsed via DT but there is really no data for "db8500-cpufreq-cooling".
You didn't get my point. You are not parsing pdata via DT here, but setting pdata of device node created due to DT.
What i am saying is, you must put in code in thermal driver, which will actually parse data from DT and create a pdata structure at run time. And there you would be required to add another file in Documentation with bindings for this driver.
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
return -ENOSYS;
+}
Do you need these? Wouldn't it be same if you don't define them?
There were not such functions before, I added them after reading Documentation/SubmittingDrivers. Is the document too old? should I follow it?
Probably the document is correct and i am not :)
-- viresh
On 18 October 2012 16:07, Viresh Kumar viresh.kumar@linaro.org wrote:
On 18 October 2012 13:05, Hongbo Zhang hongbo.zhang@linaro.org wrote:
On 17 October 2012 23:23, Viresh Kumar viresh.kumar@linaro.org wrote:
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
+static struct db8500_trip_point db8500_trips_table[] = {
[0] = {
.temp = 70000,
.type = THERMAL_TRIP_ACTIVE,
.cooling_dev_name = {
[0] = "thermal-cpufreq-0",
If i am not wrong length of cooling_dev_name can't be greater than 8
You are wrong this time, it is 20 #define THERMAL_NAME_LENGTH 20
Ahh.. Its the array size fixed to 8.. not length of each element within.
static void __init mop500_init_machine(void) @@ -765,6 +834,10 @@ struct of_dev_auxdata u8500_auxdata_lookup[] __initdata = { "ux500-msp-i2s.2", &msp2_platform_data), OF_DEV_AUXDATA("stericsson,ux500-msp-i2s", 0x80125000, "ux500-msp-i2s.3", &msp3_platform_data),
OF_DEV_AUXDATA("stericsson,db8500-thermal", 0x801573c0,
"db8500-thermal", &db8500_thsens_data),
OF_DEV_AUXDATA("stericsson,db8500-cpufreq-cooling", 0,
"db8500-cpufreq-cooling", NULL), {},
};
Whatever the case, at-least we should pass data via DT for u8500_auxdata_lookup[]. As you are adding the driver for the first time here, it must be able to parse data via DT.
Yes, for "db8500-thermal", data &db8500_thsens_data is parsed via DT but there is really no data for "db8500-cpufreq-cooling".
You didn't get my point. You are not parsing pdata via DT here, but setting pdata of device node created due to DT.
What i am saying is, you must put in code in thermal driver, which will actually parse data from DT and create a pdata structure at run time. And there you would be required to add another file in Documentation with bindings for this driver.
Get the idea. I will update the DT related codes and sent it out again. Thanks.
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
return -ENOSYS;
+}
Do you need these? Wouldn't it be same if you don't define them?
There were not such functions before, I added them after reading Documentation/SubmittingDrivers. Is the document too old? should I follow it?
Probably the document is correct and i am not :)
-- viresh
On Wed, Oct 17, 2012 at 8:53 PM, Viresh Kumar viresh.kumar@linaro.org wrote:
On 16 October 2012 17:14, hongbo.zhang hongbo.zhang@linaro.org wrote:
+static int __devinit db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{
struct db8500_cpufreq_cdev *cooling_devs;
Hi Hongbo,
I saw somebody saying this in another thread:
__devinit &__devexit will go away sometime soon, so please don't use it in new code.
commit 45f035a (only in linux-next I think) has some details.
-- viresh
Hi Hongbo,
On 10/16/2012 01:44 PM, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
[...]
diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..bb065d4 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,118 @@ +/*
- 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
Your e-mail address is misspelled :)
[...]
diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c new file mode 100644 index 0000000..34dcc52 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,507 @@ +/*
- db8500_thermal.c - db8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hognbo.zhang@stericsson.com
Misspelled address
- 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/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;
- struct mutex th_lock;
- struct platform_device *thsens_pdev;
This member is set in db8500_thermal_probe(), but is never used. I would suggest removing it.
- struct work_struct therm_work;
- struct db8500_thsens_platform_data *trip_tab;
- enum thermal_device_mode mode;
- enum thermal_trend trend;
- unsigned long cur_temp_pseudo;
- unsigned int cur_index;
- int low_irq;
- int high_irq;
Same story as thsens_pdev, low_irq and high_irq are set in db8500_thermal_probe(), but are never used. Should be removed.
+};
+/* Callback to bind cooling device to 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;
- char *cdev_name;
- unsigned long max_state, upper, lower;
- int i, j, ret;
- pzone = (struct db8500_thermal_zone *)thermal->devdata;
- ptrips = pzone->trip_tab;
- if (!cdev->type)
return -EINVAL;
cdev->type is an array, not a simple pointer, so it cannot be NULL.
- ret = -ENODEV;
- for (i = 0; i < ptrips->num_trips; i++)
for (j = 0; j < COOLING_DEV_MAX; j++) {
cdev_name = ptrips->trip_points[i].cooling_dev_name[j];
if (!cdev_name)
continue;
if (strcmp(cdev_name, cdev->type))
continue;
cdev->ops->get_max_state(cdev, &max_state);
upper = (i > max_state) ? max_state : i;
lower = (i > max_state) ? max_state : i;
You may want to merge these two lines: upper = lower = ...
ret = thermal_zone_bind_cooling_device(thermal, i,
cdev, upper, lower);
if (ret)
pr_err("Error binding cooling device.\n");
else
pr_info("Cdev %s bound.\n", cdev->type);
}
- return ret;
+}
+/* Callback to unbind cooling device from 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;
- char *cdev_name;
- int i, j, ret;
- pzone = (struct db8500_thermal_zone *)thermal->devdata;
- ptrips = pzone->trip_tab;
- if (!cdev->type)
return -EINVAL;
cdev->type cannot be NULL.
- ret = -ENODEV;
- for (i = 0; i < ptrips->num_trips; i++)
for (j = 0; j < COOLING_DEV_MAX; j++) {
cdev_name = ptrips->trip_points[i].cooling_dev_name[j];
if (!cdev_name)
continue;
if (strcmp(cdev_name, cdev->type))
continue;
ret = thermal_zone_unbind_cooling_device(
thermal, i, cdev);
if (ret)
pr_err("Error unbinding cooling device.\n");
else
pr_info("Cdev %s unbound.\n", cdev->type);
}
- return ret;
+}
+/* Callback to get current temperature */ +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;
- /* TODO: There is no PRCMU interface to get temperature data currently,
- so a pseudo temperature is returned , it works for the thermal framework
- and this will be fixed when the PRCMU interface is available */
- *temp = pzone->cur_temp_pseudo;
- return 0;
+}
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
- struct db8500_thermal_zone *pzone;
- pzone = (struct db8500_thermal_zone *)thermal->devdata;
- *trend = pzone->trend;
- return 0;
+}
+/* Callback to get thermal zone mode */ +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;
+}
+/* Callback to set thermal zone mode */ +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;
- }
If this function is called, you are sure the thermal zone has been registered.
- mutex_lock(&pzone->th_lock);
- pzone->mode = mode;
- if (mode == THERMAL_DEVICE_ENABLED)
schedule_work(&pzone->therm_work);
- mutex_unlock(&pzone->th_lock);
- return 0;
+}
+/* Callback to get trip point type */ +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->trip_tab;
- if (trip >= ptrips->num_trips)
return -EINVAL;
- *type = ptrips->trip_points[trip].type;
- return 0;
+}
+/* Callback to get trip point temperature */ +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->trip_tab;
- if (trip >= ptrips->num_trips)
return -EINVAL;
- *temp = ptrips->trip_points[trip].temp;
- return 0;
+}
+/* Callback to get critical trip point temperature */ +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->trip_tab;
- 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_trend = db8500_sys_get_trend,
- .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;
- unsigned int idx;
- ptrips = pzone->trip_tab;
- idx = pzone->cur_index;
- if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
- if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
- } else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
- }
- pzone->cur_index -= 1;
- pzone->cur_temp_pseudo = (next_high + next_low)/2;
- prcmu_stop_temp_sense();
- prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
- prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
- pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low);
- pzone->trend = THERMAL_TREND_DROPPING;
- 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;
- unsigned int idx;
- ptrips = pzone->trip_tab;
- idx = pzone->cur_index;
- if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
pzone->cur_index += 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, min %ld\n", next_high, next_low);
- }
- if (idx == ptrips->num_trips - 1)
} else if ()
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
- pzone->trend = THERMAL_TREND_RAISING;
- 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;
- pzone = devm_kzalloc(&pdev->dev,
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");
return low_irq;
- }
- ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", pzone);
- if (ret < 0) {
pr_err("Failed to allocate temp low irq.\n");
return ret;
- }
- high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
- if (high_irq < 0) {
pr_err("Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
- }
- ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", pzone);
- if (ret < 0) {
pr_err("Failed to allocate temp high irq.\n");
return ret;
- }
- pzone->low_irq = low_irq;
- pzone->high_irq = high_irq;
- pzone->mode = THERMAL_DEVICE_DISABLED;
- mutex_init(&pzone->th_lock);
- INIT_WORK(&pzone->therm_work, db8500_thermal_work);
- ptrips = pdev->dev.platform_data;
- pzone->trip_tab = ptrips;
- pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
- if (IS_ERR(pzone->therm_dev)) {
pr_err("Failed to register thermal zone device\n");
return PTR_ERR(pzone->therm_dev);
- }
- dft_low = PRCMU_DEFAULT_LOW_TEMP;
- dft_high = ptrips->trip_points[0].temp;
- prcmu_stop_temp_sense();
- prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
- prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
- pzone->cur_index = 0;
- pzone->cur_temp_pseudo = (dft_low + dft_high)/2;
- pzone->trend = THERMAL_TREND_STABLE;
All the stuff from prcmu_stop_temp_sense() up to this line can race with the irq handlers, I would suggest doing it before requesting the irqs.
- pzone->mode = THERMAL_DEVICE_ENABLED;
Shouldn't this be protected by pzone->th_lock? Otherwise it should be set before the thermal zone is registered.
- platform_set_drvdata(pdev, pzone);
- return 0;
+}
+static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{
- struct db8500_thermal_zone *pzone;
- pzone = platform_get_drvdata(pdev);
- cancel_work_sync(&pzone->therm_work);
- if (pzone->therm_dev)
thermal_zone_device_unregister(pzone->therm_dev);
- return 0;
+}
mutex_destroy() should be called on pzone->th_lock
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
- struct db8500_thermal_zone *pzone;
- pzone = platform_get_drvdata(pdev);
- flush_work_sync(&pzone->therm_work);
- prcmu_stop_temp_sense();
- return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) +{
- struct db8500_thermal_zone *pzone;
- struct db8500_thsens_platform_data *ptrips;
- unsigned long dft_low, dft_high;
- pzone = platform_get_drvdata(pdev);
- ptrips = pzone->trip_tab;
- dft_low = PRCMU_DEFAULT_LOW_TEMP;
- dft_high = ptrips->trip_points[0].temp;
- prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
- prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
Shouldn't cur_index and cur_temp_pseudo be updated as well? You may want to define a helper function with all the code shared by irq handlers (both high and low), probe and resume.
- return 0;
+}
[...]
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
Misspelled address
-- Francesco
On 21 October 2012 23:01, Francesco Lavra francescolavra.fl@gmail.com wrote:
Hi Hongbo,
Hi Francesco, Thanks for your review, I will accept all the comments except the ones I have some comments under them.
On 10/16/2012 01:44 PM, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
[...]
diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..bb065d4 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,118 @@ +/*
- 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
Your e-mail address is misspelled :)
[...]
diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c new file mode 100644 index 0000000..34dcc52 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,507 @@ +/*
- db8500_thermal.c - db8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hognbo.zhang@stericsson.com
Misspelled address
- 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/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;
struct mutex th_lock;
struct platform_device *thsens_pdev;
This member is set in db8500_thermal_probe(), but is never used. I would suggest removing it.
struct work_struct therm_work;
struct db8500_thsens_platform_data *trip_tab;
enum thermal_device_mode mode;
enum thermal_trend trend;
unsigned long cur_temp_pseudo;
unsigned int cur_index;
int low_irq;
int high_irq;
Same story as thsens_pdev, low_irq and high_irq are set in db8500_thermal_probe(), but are never used. Should be removed.
+};
+/* Callback to bind cooling device to 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;
char *cdev_name;
unsigned long max_state, upper, lower;
int i, j, ret;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
ptrips = pzone->trip_tab;
if (!cdev->type)
return -EINVAL;
cdev->type is an array, not a simple pointer, so it cannot be NULL.
ret = -ENODEV;
for (i = 0; i < ptrips->num_trips; i++)
for (j = 0; j < COOLING_DEV_MAX; j++) {
cdev_name = ptrips->trip_points[i].cooling_dev_name[j];
if (!cdev_name)
continue;
if (strcmp(cdev_name, cdev->type))
continue;
cdev->ops->get_max_state(cdev, &max_state);
upper = (i > max_state) ? max_state : i;
lower = (i > max_state) ? max_state : i;
You may want to merge these two lines: upper = lower = ...
ret = thermal_zone_bind_cooling_device(thermal, i,
cdev, upper, lower);
if (ret)
pr_err("Error binding cooling device.\n");
else
pr_info("Cdev %s bound.\n", cdev->type);
}
return ret;
+}
+/* Callback to unbind cooling device from 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;
char *cdev_name;
int i, j, ret;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
ptrips = pzone->trip_tab;
if (!cdev->type)
return -EINVAL;
cdev->type cannot be NULL.
ret = -ENODEV;
for (i = 0; i < ptrips->num_trips; i++)
for (j = 0; j < COOLING_DEV_MAX; j++) {
cdev_name = ptrips->trip_points[i].cooling_dev_name[j];
if (!cdev_name)
continue;
if (strcmp(cdev_name, cdev->type))
continue;
ret = thermal_zone_unbind_cooling_device(
thermal, i, cdev);
if (ret)
pr_err("Error unbinding cooling device.\n");
else
pr_info("Cdev %s unbound.\n", cdev->type);
}
return ret;
+}
+/* Callback to get current temperature */ +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;
/* TODO: There is no PRCMU interface to get temperature data currently,
so a pseudo temperature is returned , it works for the thermal framework
and this will be fixed when the PRCMU interface is available */
*temp = pzone->cur_temp_pseudo;
return 0;
+}
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
struct db8500_thermal_zone *pzone;
pzone = (struct db8500_thermal_zone *)thermal->devdata;
*trend = pzone->trend;
return 0;
+}
+/* Callback to get thermal zone mode */ +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;
+}
+/* Callback to set thermal zone mode */ +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;
}
If this function is called, you are sure the thermal zone has been registered.
mutex_lock(&pzone->th_lock);
pzone->mode = mode;
if (mode == THERMAL_DEVICE_ENABLED)
schedule_work(&pzone->therm_work);
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to get trip point type */ +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->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*type = ptrips->trip_points[trip].type;
return 0;
+}
+/* Callback to get trip point temperature */ +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->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*temp = ptrips->trip_points[trip].temp;
return 0;
+}
+/* Callback to get critical trip point temperature */ +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->trip_tab;
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_trend = db8500_sys_get_trend,
.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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
} else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
}
pzone->cur_index -= 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low);
pzone->trend = THERMAL_TREND_DROPPING;
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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
pzone->cur_index += 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, min %ld\n", next_high, next_low);
}
if (idx == ptrips->num_trips - 1)
} else if ()
There is no else condition here, because it it the highest critical trip point, system will be shut down in thermal_work. But I'd like to add some comments here to state this situation.
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
pzone->trend = THERMAL_TREND_RAISING;
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;
pzone = devm_kzalloc(&pdev->dev,
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");
return low_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", pzone);
if (ret < 0) {
pr_err("Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
pr_err("Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", pzone);
if (ret < 0) {
pr_err("Failed to allocate temp high irq.\n");
return ret;
}
pzone->low_irq = low_irq;
pzone->high_irq = high_irq;
pzone->mode = THERMAL_DEVICE_DISABLED;
mutex_init(&pzone->th_lock);
INIT_WORK(&pzone->therm_work, db8500_thermal_work);
ptrips = pdev->dev.platform_data;
pzone->trip_tab = ptrips;
pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
if (IS_ERR(pzone->therm_dev)) {
pr_err("Failed to register thermal zone device\n");
return PTR_ERR(pzone->therm_dev);
}
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pzone->cur_index = 0;
pzone->cur_temp_pseudo = (dft_low + dft_high)/2;
pzone->trend = THERMAL_TREND_STABLE;
All the stuff from prcmu_stop_temp_sense() up to this line can race with the irq handlers, I would suggest doing it before requesting the irqs.
pzone->mode = THERMAL_DEVICE_ENABLED;
Shouldn't this be protected by pzone->th_lock? Otherwise it should be set before the thermal zone is registered.
platform_set_drvdata(pdev, pzone);
return 0;
+}
+static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
cancel_work_sync(&pzone->therm_work);
if (pzone->therm_dev)
thermal_zone_device_unregister(pzone->therm_dev);
return 0;
+}
mutex_destroy() should be called on pzone->th_lock
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
flush_work_sync(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
struct db8500_thsens_platform_data *ptrips;
unsigned long dft_low, dft_high;
pzone = platform_get_drvdata(pdev);
ptrips = pzone->trip_tab;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
Shouldn't cur_index and cur_temp_pseudo be updated as well? You may want to define a helper function with all the code shared by irq handlers (both high and low), probe and resume.
No, they cannot be update because we don't know the actual current temp[1] after short or long time suspend, everything goes as beginning. If a helper function is introduced, it can be only used in probe and resume I think, different and a bit complicated algorithm in irq handlers. [1] due to lack of corresponding interface, search "TODO" in this file to get more explanation.
return 0;
+}
[...]
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
Misspelled address
-- Francesco
On 10/22/2012 02:02 PM, Hongbo Zhang wrote: [...]
+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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
} else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
}
pzone->cur_index -= 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low);
pzone->trend = THERMAL_TREND_DROPPING;
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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
pzone->cur_index += 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, min %ld\n", next_high, next_low);
}
if (idx == ptrips->num_trips - 1)
} else if ()
There is no else condition here, because it it the highest critical trip point, system will be shut down in thermal_work. But I'd like to add some comments here to state this situation.
I didn't mean adding a new else condition, what I meant is that if the first condition (idx < ptrips->num_trips - 1) is verified, then there is no need to check for the second condition (idx == ptrips->num_trips - 1). So I was thinking of changing the code to:
if (idx < ptrips->num_trips - 1) ... else if (idx == ptrips->num_trips - 1) ...
Sorry if I wasn't clear.
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
pzone->trend = THERMAL_TREND_RAISING;
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;
pzone = devm_kzalloc(&pdev->dev,
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");
return low_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", pzone);
if (ret < 0) {
pr_err("Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
pr_err("Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", pzone);
if (ret < 0) {
pr_err("Failed to allocate temp high irq.\n");
return ret;
}
pzone->low_irq = low_irq;
pzone->high_irq = high_irq;
pzone->mode = THERMAL_DEVICE_DISABLED;
mutex_init(&pzone->th_lock);
INIT_WORK(&pzone->therm_work, db8500_thermal_work);
ptrips = pdev->dev.platform_data;
pzone->trip_tab = ptrips;
pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
if (IS_ERR(pzone->therm_dev)) {
pr_err("Failed to register thermal zone device\n");
return PTR_ERR(pzone->therm_dev);
}
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pzone->cur_index = 0;
pzone->cur_temp_pseudo = (dft_low + dft_high)/2;
pzone->trend = THERMAL_TREND_STABLE;
All the stuff from prcmu_stop_temp_sense() up to this line can race with the irq handlers, I would suggest doing it before requesting the irqs.
pzone->mode = THERMAL_DEVICE_ENABLED;
Shouldn't this be protected by pzone->th_lock? Otherwise it should be set before the thermal zone is registered.
platform_set_drvdata(pdev, pzone);
return 0;
+}
+static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
cancel_work_sync(&pzone->therm_work);
if (pzone->therm_dev)
thermal_zone_device_unregister(pzone->therm_dev);
return 0;
+}
mutex_destroy() should be called on pzone->th_lock
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
flush_work_sync(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
struct db8500_thsens_platform_data *ptrips;
unsigned long dft_low, dft_high;
pzone = platform_get_drvdata(pdev);
ptrips = pzone->trip_tab;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
Shouldn't cur_index and cur_temp_pseudo be updated as well? You may want to define a helper function with all the code shared by irq handlers (both high and low), probe and resume.
No, they cannot be update because we don't know the actual current temp[1] after short or long time suspend, everything goes as beginning.
That's what I wanted to say, if everything is reset to a default value, then cur_index and cur_temp should be reset too, as it's done in the probe function. Otherwise you may have a current pseudo-temp and a current index unrelated to how the hotmon is configured.
If a helper function is introduced, it can be only used in probe and resume I think, different and a bit complicated algorithm in irq handlers.
I was thinking about a function which takes the new index and the new low and high parameters, and updates cur_index and cur_pseudo_temp and does the prcmu stuff. It seems to me this is (or should be) common to all the 4 functions.
[1] due to lack of corresponding interface, search "TODO" in this file to get more explanation.
-- Francesco
On 23 October 2012 02:51, Francesco Lavra francescolavra.fl@gmail.com wrote:
On 10/22/2012 02:02 PM, Hongbo Zhang wrote: [...]
+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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
} else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
}
pzone->cur_index -= 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, set min %ld\n", next_high, next_low);
pzone->trend = THERMAL_TREND_DROPPING;
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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
pzone->cur_index += 1;
pzone->cur_temp_pseudo = (next_high + next_low)/2;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pr_debug("PRCMU set max %ld, min %ld\n", next_high, next_low);
}
if (idx == ptrips->num_trips - 1)
} else if ()
There is no else condition here, because it it the highest critical trip point, system will be shut down in thermal_work. But I'd like to add some comments here to state this situation.
I didn't mean adding a new else condition, what I meant is that if the first condition (idx < ptrips->num_trips - 1) is verified, then there is no need to check for the second condition (idx == ptrips->num_trips - 1). So I was thinking of changing the code to:
if (idx < ptrips->num_trips - 1) ... else if (idx == ptrips->num_trips - 1) ...
Sorry if I wasn't clear.
Got it, thanks.
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
pzone->trend = THERMAL_TREND_RAISING;
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;
pzone = devm_kzalloc(&pdev->dev,
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");
return low_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", pzone);
if (ret < 0) {
pr_err("Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
pr_err("Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler,
IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", pzone);
if (ret < 0) {
pr_err("Failed to allocate temp high irq.\n");
return ret;
}
pzone->low_irq = low_irq;
pzone->high_irq = high_irq;
pzone->mode = THERMAL_DEVICE_DISABLED;
mutex_init(&pzone->th_lock);
INIT_WORK(&pzone->therm_work, db8500_thermal_work);
ptrips = pdev->dev.platform_data;
pzone->trip_tab = ptrips;
pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
if (IS_ERR(pzone->therm_dev)) {
pr_err("Failed to register thermal zone device\n");
return PTR_ERR(pzone->therm_dev);
}
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
pzone->cur_index = 0;
pzone->cur_temp_pseudo = (dft_low + dft_high)/2;
pzone->trend = THERMAL_TREND_STABLE;
All the stuff from prcmu_stop_temp_sense() up to this line can race with the irq handlers, I would suggest doing it before requesting the irqs.
pzone->mode = THERMAL_DEVICE_ENABLED;
Shouldn't this be protected by pzone->th_lock? Otherwise it should be set before the thermal zone is registered.
platform_set_drvdata(pdev, pzone);
return 0;
+}
+static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
cancel_work_sync(&pzone->therm_work);
if (pzone->therm_dev)
thermal_zone_device_unregister(pzone->therm_dev);
return 0;
+}
mutex_destroy() should be called on pzone->th_lock
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
flush_work_sync(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
struct db8500_thsens_platform_data *ptrips;
unsigned long dft_low, dft_high;
pzone = platform_get_drvdata(pdev);
ptrips = pzone->trip_tab;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
prcmu_config_hotmon((u8)(dft_low/1000), (u8)(dft_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
Shouldn't cur_index and cur_temp_pseudo be updated as well? You may want to define a helper function with all the code shared by irq handlers (both high and low), probe and resume.
No, they cannot be update because we don't know the actual current temp[1] after short or long time suspend, everything goes as beginning.
That's what I wanted to say, if everything is reset to a default value, then cur_index and cur_temp should be reset too, as it's done in the probe function. Otherwise you may have a current pseudo-temp and a current index unrelated to how the hotmon is configured.
Yes, right.
If a helper function is introduced, it can be only used in probe and resume I think, different and a bit complicated algorithm in irq handlers.
I was thinking about a function which takes the new index and the new low and high parameters, and updates cur_index and cur_pseudo_temp and does the prcmu stuff. It seems to me this is (or should be) common to all the 4 functions.
I misunderstood this helper will do all the updates, but algorithm of cur_index is different everywhere. If cur_index is calculated outside this new helper function, it works. I will do that.
[1] due to lack of corresponding interface, search "TODO" in this file to get more explanation.
-- Francesco
From: "hongbo.zhang" hongbo.zhang@linaro.com
V1->V2 Changes:
DB8500 thermal dirver: Accept comments from Francesco Lavra and Viresh Kumar, and split platform and driver parts into separate patches.
Thermal layer: Cancel the patch for deferring bind due to new patch for generic cpu cooling layer to fix this issue.
CPU cooling layer: New patch "Remove the cooling_cpufreq_list" added, thus old patch to fix empty list checking is also removed.
hongbo.zhang (6): Thermal: add indent for code alignment. Thermal: make sure cpufreq cooling register after cpufreq driver Thermal: fix bug of counting cpu frequencies. Thermal: Remove the cooling_cpufreq_list. Thermal: Add ST-Ericsson DB8500 thermal dirver. Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.
.../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ arch/arm/boot/dts/dbx5x0.dtsi | 14 + arch/arm/boot/dts/snowball.dts | 31 ++ arch/arm/configs/u8500_defconfig | 4 + arch/arm/mach-ux500/board-mop500.c | 64 +++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/cpu_cooling.c | 95 +--- drivers/thermal/db8500_cpufreq_cooling.c | 123 +++++ drivers/thermal/db8500_thermal.c | 557 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 39 ++ 11 files changed, 920 insertions(+), 69 deletions(-) create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
From: "hongbo.zhang" hongbo.zhang@linaro.com
The curly bracket should be aligned with corresponding if else statements.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/thermal/cpu_cooling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index cc1c930..b6b4c2a 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -369,7 +369,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (min != policy.cpuinfo.min_freq || max != policy.cpuinfo.max_freq) return ERR_PTR(-EINVAL); -} + } } cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device), GFP_KERNEL);
From: "hongbo.zhang" hongbo.zhang@linaro.com
The cpufreq works as a cooling device, so the cooling layer should check if the cpufreq driver is initialized or not.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- drivers/thermal/cpu_cooling.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index b6b4c2a..7519a0b 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -354,6 +354,10 @@ struct thermal_cooling_device *cpufreq_cooling_register( int ret = 0, i; struct cpufreq_policy policy;
+ /* make sure cpufreq driver has been initialized */ + if (!cpufreq_frequency_get_table(cpumask_any(clip_cpus))) + return ERR_PTR(-EPROBE_DEFER); + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) cpufreq_dev_count++;
On 24 October 2012 17:28, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
The cpufreq works as a cooling device, so the cooling layer should check if the cpufreq driver is initialized or not.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
drivers/thermal/cpu_cooling.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index b6b4c2a..7519a0b 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -354,6 +354,10 @@ struct thermal_cooling_device *cpufreq_cooling_register( int ret = 0, i; struct cpufreq_policy policy;
/* make sure cpufreq driver has been initialized */
if (!cpufreq_frequency_get_table(cpumask_any(clip_cpus)))
return ERR_PTR(-EPROBE_DEFER);
Hi Hongbo,
I am not against this change but this might cause unnecessary delay in probe thread. I also thought about it but have not put this restriction. Actually you can put a check in platform_bind for this condition and defer the binding till the time actual throttling starts. So basically only after throttling cpufreq_table is needed. (See my implementation exynos_thermal.c).
Thanks, Amit Daniel
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) cpufreq_dev_count++;
-- 1.7.11.3
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
On 29 October 2012 12:42, Amit Kachhap amit.kachhap@linaro.org wrote:
On 24 October 2012 17:28, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
The cpufreq works as a cooling device, so the cooling layer should check if the cpufreq driver is initialized or not.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
drivers/thermal/cpu_cooling.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index b6b4c2a..7519a0b 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -354,6 +354,10 @@ struct thermal_cooling_device *cpufreq_cooling_register( int ret = 0, i; struct cpufreq_policy policy;
/* make sure cpufreq driver has been initialized */
if (!cpufreq_frequency_get_table(cpumask_any(clip_cpus)))
return ERR_PTR(-EPROBE_DEFER);
Hi Hongbo,
I am not against this change but this might cause unnecessary delay in probe thread. I also thought about it but have not put this restriction. Actually you can put a check in platform_bind for this condition and defer the binding till the time actual throttling starts. So basically only after throttling cpufreq_table is needed. (See my implementation exynos_thermal.c).
In fact, this piece of checking code was in my db8500_cpufreq_cooling_probe() before, I will move it back there again, and the ST-E's policy is separating cooling devs and thermal zone, so cannot be in binding function on my platform, only in probe function instead.
Thanks, Amit Daniel
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) cpufreq_dev_count++;
-- 1.7.11.3
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
From: "hongbo.zhang" hongbo.zhang@linaro.com
In the while loop for counting cpu frequencies, if table[i].frequency equals CPUFREQ_ENTRY_INVALID, index i won't be increased, so this leads to an endless loop, what's more the index i cannot be referred as cpu frequencies number if there is CPUFREQ_ENTRY_INVALID case.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- drivers/thermal/cpu_cooling.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 7519a0b..415b041 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -245,6 +245,7 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, struct cpumask *maskPtr; unsigned int cpu; struct cpufreq_frequency_table *table; + unsigned long count = 0;
mutex_lock(&cooling_cpufreq_lock); list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { @@ -263,13 +264,14 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, goto return_get_max_state; }
- while (table[i].frequency != CPUFREQ_TABLE_END) { + for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { if (table[i].frequency == CPUFREQ_ENTRY_INVALID) continue; - i++; + count++; } - if (i > 0) { - *state = --i; + + if (count > 0) { + *state = --count; ret = 0; }
On 24 October 2012 17:28, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
In the while loop for counting cpu frequencies, if table[i].frequency equals CPUFREQ_ENTRY_INVALID, index i won't be increased, so this leads to an endless loop, what's more the index i cannot be referred as cpu frequencies number if there is CPUFREQ_ENTRY_INVALID case.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
Good one.
Reviewed-by: Viresh Kumar viresh.kumar@linaro.org
On 24 October 2012 19:04, Viresh Kumar viresh.kumar@linaro.org wrote:
On 24 October 2012 17:28, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
In the while loop for counting cpu frequencies, if table[i].frequency equals CPUFREQ_ENTRY_INVALID, index i won't be increased, so this leads to an endless loop, what's more the index i cannot be referred as cpu frequencies number if there is CPUFREQ_ENTRY_INVALID case.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
Good one.
Reviewed-by: Viresh Kumar viresh.kumar@linaro.org
Changes looks fine. Adding thermal maintainer(Rui Zhang) in the mail list. Reviewed-by: Amit Daniel Kachhap amit.kachhap@linaro.org
Thanks, Amit Daniel
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
From: "hongbo.zhang" hongbo.zhang@linaro.com
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- drivers/thermal/cpu_cooling.c | 81 +++++++++---------------------------------- 1 file changed, 16 insertions(+), 65 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 415b041..cc80d29 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock);
-static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count;
/* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -241,20 +242,12 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { int ret = -EINVAL, i = 0; - struct cpufreq_cooling_device *cpufreq_device; + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; struct cpumask *maskPtr; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0;
- mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) - break; - } - if (cpufreq_device == NULL) - goto return_get_max_state; - maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu); @@ -276,7 +269,6 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, }
return_get_max_state: - mutex_unlock(&cooling_cpufreq_lock); return ret; }
@@ -288,20 +280,10 @@ return_get_max_state: static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { - int ret = -EINVAL; - struct cpufreq_cooling_device *cpufreq_device; - - mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) { - *state = cpufreq_device->cpufreq_state; - ret = 0; - break; - } - } - mutex_unlock(&cooling_cpufreq_lock); + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- return ret; + *state = cpufreq_device->cpufreq_state; + return 0; }
/** @@ -312,22 +294,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { - int ret = -EINVAL; - struct cpufreq_cooling_device *cpufreq_device; - - mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) { - ret = 0; - break; - } - } - if (!ret) - ret = cpufreq_apply_cooling(cpufreq_device, state); + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- mutex_unlock(&cooling_cpufreq_lock); - - return ret; + return cpufreq_apply_cooling(cpufreq_device, state); }
/* Bind cpufreq callbacks to thermal cooling device ops */ @@ -351,7 +320,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL; - unsigned int cpufreq_dev_count = 0, min = 0, max = 0; + unsigned int min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; int ret = 0, i; struct cpufreq_policy policy; @@ -360,9 +329,6 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (!cpufreq_frequency_get_table(cpumask_any(clip_cpus))) return ERR_PTR(-EPROBE_DEFER);
- list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) - cpufreq_dev_count++; - /*Verify that all the clip cpus have same freq_min, freq_max limit*/ for_each_cpu(i, clip_cpus) { /*continue if cpufreq policy not found and not return error*/ @@ -384,9 +350,6 @@ struct thermal_cooling_device *cpufreq_cooling_register(
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
- if (cpufreq_dev_count == 0) - mutex_init(&cooling_cpufreq_lock); - ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); if (ret) { kfree(cpufreq_dev); @@ -405,12 +368,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cpufreq_state = 0; mutex_lock(&cooling_cpufreq_lock); - list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
/* Register the notifier for first cpufreq cooling device */ if (cpufreq_dev_count == 0) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); + cpufreq_dev_count++;
mutex_unlock(&cooling_cpufreq_lock); return cool_dev; @@ -423,33 +386,21 @@ EXPORT_SYMBOL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) { - struct cpufreq_cooling_device *cpufreq_dev = NULL; - unsigned int cpufreq_dev_count = 0; + struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) { - if (cpufreq_dev && cpufreq_dev->cool_dev == cdev) - break; - cpufreq_dev_count++; - } - - if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) { - mutex_unlock(&cooling_cpufreq_lock); - return; - } + thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
- list_del(&cpufreq_dev->node); + mutex_lock(&cooling_cpufreq_lock); + cpufreq_dev_count--;
/* Unregister the notifier for the last cpufreq cooling device */ - if (cpufreq_dev_count == 1) { + if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock); - thermal_cooling_device_unregister(cpufreq_dev->cool_dev); + release_idr(&cpufreq_idr, cpufreq_dev->id); - if (cpufreq_dev_count == 1) - mutex_destroy(&cooling_cpufreq_lock); kfree(cpufreq_dev); } EXPORT_SYMBOL(cpufreq_cooling_unregister);
Hi, Hongbo Zhang wrote:
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Signed-off-by: hongbo.zhang <hongbo.zhang at linaro.com>
drivers/thermal/cpu_cooling.c | 81 +++++++++---------------------------------- 1 file changed, 16 insertions(+), 65 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 415b041..cc80d29 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock); -static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count; /* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -241,20 +242,12 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { int ret = -EINVAL, i = 0;
- struct cpufreq_cooling_device *cpufreq_device;
- struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; struct cpumask *maskPtr; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0;
- mutex_lock(&cooling_cpufreq_lock);
- list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev)
break;
- }
- if (cpufreq_device == NULL)
goto return_get_max_state;
- maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu);
@@ -276,7 +269,6 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, } return_get_max_state:
- mutex_unlock(&cooling_cpufreq_lock); return ret;
Since there is no mutex locking/unlocking anymore, I'd say the goto label should be removed.
[...]
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) {
- struct cpufreq_cooling_device *cpufreq_dev = NULL;
- unsigned int cpufreq_dev_count = 0;
- struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock);
- list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
break;
cpufreq_dev_count++;
- }
- if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
mutex_unlock(&cooling_cpufreq_lock);
return;
- }
- thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
- list_del(&cpufreq_dev->node);
- mutex_lock(&cooling_cpufreq_lock);
- cpufreq_dev_count--;
/* Unregister the notifier for the last cpufreq cooling device */
- if (cpufreq_dev_count == 1) {
- if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock);
- thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
Why did you move the call to thermal_cooling_device_unregister() from here? I don't see any reason for moving it.
- release_idr(&cpufreq_idr, cpufreq_dev->id);
- if (cpufreq_dev_count == 1)
kfree(cpufreq_dev);mutex_destroy(&cooling_cpufreq_lock);
} EXPORT_SYMBOL(cpufreq_cooling_unregister); -- 1.7.11.3
-- Francesco
On 26 October 2012 03:14, Francesco Lavra francescolavra.fl@gmail.com wrote:
Hi, Hongbo Zhang wrote:
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Signed-off-by: hongbo.zhang <hongbo.zhang at linaro.com>
drivers/thermal/cpu_cooling.c | 81 +++++++++---------------------------------- 1 file changed, 16 insertions(+), 65 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 415b041..cc80d29 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock);
-static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count;
/* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -241,20 +242,12 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { int ret = -EINVAL, i = 0;
struct cpufreq_cooling_device *cpufreq_device;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; struct cpumask *maskPtr; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev)
break;
}
if (cpufreq_device == NULL)
goto return_get_max_state;
maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu);
@@ -276,7 +269,6 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, }
return_get_max_state:
mutex_unlock(&cooling_cpufreq_lock); return ret;
Since there is no mutex locking/unlocking anymore, I'd say the goto label should be removed.
Good.
[...]
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) {
struct cpufreq_cooling_device *cpufreq_dev = NULL;
unsigned int cpufreq_dev_count = 0;
struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
break;
cpufreq_dev_count++;
}
if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
mutex_unlock(&cooling_cpufreq_lock);
return;
}
thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
list_del(&cpufreq_dev->node);
mutex_lock(&cooling_cpufreq_lock);
cpufreq_dev_count--; /* Unregister the notifier for the last cpufreq cooling device */
if (cpufreq_dev_count == 1) {
if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock);
thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
Why did you move the call to thermal_cooling_device_unregister() from here? I don't see any reason for moving it.
In common sense, usually unregister first and then count--; But here it should be opposite sequence of cpufreq_cooling_register, will update it.
release_idr(&cpufreq_idr, cpufreq_dev->id);
if (cpufreq_dev_count == 1)
mutex_destroy(&cooling_cpufreq_lock); kfree(cpufreq_dev);
} EXPORT_SYMBOL(cpufreq_cooling_unregister); -- 1.7.11.3
-- Francesco
From: "hongbo.zhang" hongbo.zhang@linaro.com
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- drivers/thermal/cpu_cooling.c | 91 +++++++++---------------------------------- 1 file changed, 19 insertions(+), 72 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 415b041..2ffd12c 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock);
-static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count;
/* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -240,28 +241,18 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - int ret = -EINVAL, i = 0; - struct cpufreq_cooling_device *cpufreq_device; - struct cpumask *maskPtr; + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; + struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0; + int i = 0;
- mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) - break; - } - if (cpufreq_device == NULL) - goto return_get_max_state; - - maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu); if (!table) { *state = 0; - ret = 0; - goto return_get_max_state; + return 0; }
for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { @@ -272,12 +263,10 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
if (count > 0) { *state = --count; - ret = 0; + return 0; }
-return_get_max_state: - mutex_unlock(&cooling_cpufreq_lock); - return ret; + return -EINVAL; }
/** @@ -288,20 +277,10 @@ return_get_max_state: static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { - int ret = -EINVAL; - struct cpufreq_cooling_device *cpufreq_device; + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) { - *state = cpufreq_device->cpufreq_state; - ret = 0; - break; - } - } - mutex_unlock(&cooling_cpufreq_lock); - - return ret; + *state = cpufreq_device->cpufreq_state; + return 0; }
/** @@ -312,22 +291,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { - int ret = -EINVAL; - struct cpufreq_cooling_device *cpufreq_device; + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) { - ret = 0; - break; - } - } - if (!ret) - ret = cpufreq_apply_cooling(cpufreq_device, state); - - mutex_unlock(&cooling_cpufreq_lock); - - return ret; + return cpufreq_apply_cooling(cpufreq_device, state); }
/* Bind cpufreq callbacks to thermal cooling device ops */ @@ -351,7 +317,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL; - unsigned int cpufreq_dev_count = 0, min = 0, max = 0; + unsigned int min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; int ret = 0, i; struct cpufreq_policy policy; @@ -360,9 +326,6 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (!cpufreq_frequency_get_table(cpumask_any(clip_cpus))) return ERR_PTR(-EPROBE_DEFER);
- list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) - cpufreq_dev_count++; - /*Verify that all the clip cpus have same freq_min, freq_max limit*/ for_each_cpu(i, clip_cpus) { /*continue if cpufreq policy not found and not return error*/ @@ -384,9 +347,6 @@ struct thermal_cooling_device *cpufreq_cooling_register(
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
- if (cpufreq_dev_count == 0) - mutex_init(&cooling_cpufreq_lock); - ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); if (ret) { kfree(cpufreq_dev); @@ -405,12 +365,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cpufreq_state = 0; mutex_lock(&cooling_cpufreq_lock); - list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
/* Register the notifier for first cpufreq cooling device */ if (cpufreq_dev_count == 0) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); + cpufreq_dev_count++;
mutex_unlock(&cooling_cpufreq_lock); return cool_dev; @@ -423,33 +383,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) { - struct cpufreq_cooling_device *cpufreq_dev = NULL; - unsigned int cpufreq_dev_count = 0; + struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata;
mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) { - if (cpufreq_dev && cpufreq_dev->cool_dev == cdev) - break; - cpufreq_dev_count++; - } - - if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) { - mutex_unlock(&cooling_cpufreq_lock); - return; - } - - list_del(&cpufreq_dev->node); + cpufreq_dev_count--;
/* Unregister the notifier for the last cpufreq cooling device */ - if (cpufreq_dev_count == 1) { + if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock); + thermal_cooling_device_unregister(cpufreq_dev->cool_dev); release_idr(&cpufreq_idr, cpufreq_dev->id); - if (cpufreq_dev_count == 1) - mutex_destroy(&cooling_cpufreq_lock); kfree(cpufreq_dev); } EXPORT_SYMBOL(cpufreq_cooling_unregister);
On Fri, 26 Oct 2012 15:09:05 +0800, Hongbo Zhang wrote:
From: "hongbo.zhang" <hongbo.zhang at linaro.com>
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Signed-off-by: hongbo.zhang <hongbo.zhang at linaro.com>
FWIW, Reviewed-by: Francesco Lavra francescolavra.fl@gmail.com
On 26 October 2012 12:39, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Hi,
Removing this list seems fine as most of frequency checks are moved inside generic thermal layer. Some minor review comments below,
Reviewed-by: Amit Daniel Kachhap amit.kachhap@linaro.org
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
drivers/thermal/cpu_cooling.c | 91 +++++++++---------------------------------- 1 file changed, 19 insertions(+), 72 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 415b041..2ffd12c 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock);
-static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count;
/* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -240,28 +241,18 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) {
int ret = -EINVAL, i = 0;
struct cpufreq_cooling_device *cpufreq_device;
struct cpumask *maskPtr;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
check cdev is not null.
struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0;
int i = 0;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev)
break;
}
if (cpufreq_device == NULL)
goto return_get_max_state;
maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu); if (!table) { *state = 0;
ret = 0;
goto return_get_max_state;
return 0; } for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
@@ -272,12 +263,10 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
if (count > 0) { *state = --count;
ret = 0;
return 0; }
-return_get_max_state:
mutex_unlock(&cooling_cpufreq_lock);
return ret;
return -EINVAL;
}
/** @@ -288,20 +277,10 @@ return_get_max_state: static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) {
int ret = -EINVAL;
struct cpufreq_cooling_device *cpufreq_device;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
*state = cpufreq_device->cpufreq_state;
ret = 0;
break;
}
}
mutex_unlock(&cooling_cpufreq_lock);
return ret;
*state = cpufreq_device->cpufreq_state;
return 0;
}
/** @@ -312,22 +291,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) {
int ret = -EINVAL;
struct cpufreq_cooling_device *cpufreq_device;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
check cdev is not null
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
ret = 0;
break;
}
}
if (!ret)
ret = cpufreq_apply_cooling(cpufreq_device, state);
mutex_unlock(&cooling_cpufreq_lock);
return ret;
return cpufreq_apply_cooling(cpufreq_device, state);
}
/* Bind cpufreq callbacks to thermal cooling device ops */ @@ -351,7 +317,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL;
unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
unsigned int min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; int ret = 0, i; struct cpufreq_policy policy;
@@ -360,9 +326,6 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (!cpufreq_frequency_get_table(cpumask_any(clip_cpus))) return ERR_PTR(-EPROBE_DEFER);
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
cpufreq_dev_count++;
/*Verify that all the clip cpus have same freq_min, freq_max limit*/ for_each_cpu(i, clip_cpus) { /*continue if cpufreq policy not found and not return error*/
@@ -384,9 +347,6 @@ struct thermal_cooling_device *cpufreq_cooling_register(
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
if (cpufreq_dev_count == 0)
mutex_init(&cooling_cpufreq_lock);
ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); if (ret) { kfree(cpufreq_dev);
@@ -405,12 +365,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cpufreq_state = 0; mutex_lock(&cooling_cpufreq_lock);
list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list); /* Register the notifier for first cpufreq cooling device */ if (cpufreq_dev_count == 0) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER);
cpufreq_dev_count++; mutex_unlock(&cooling_cpufreq_lock); return cool_dev;
@@ -423,33 +383,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) {
struct cpufreq_cooling_device *cpufreq_dev = NULL;
unsigned int cpufreq_dev_count = 0;
struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata; mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
break;
cpufreq_dev_count++;
}
if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
mutex_unlock(&cooling_cpufreq_lock);
return;
}
list_del(&cpufreq_dev->node);
cpufreq_dev_count--; /* Unregister the notifier for the last cpufreq cooling device */
if (cpufreq_dev_count == 1) {
if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock);
thermal_cooling_device_unregister(cpufreq_dev->cool_dev); release_idr(&cpufreq_idr, cpufreq_dev->id);
if (cpufreq_dev_count == 1)
mutex_destroy(&cooling_cpufreq_lock); kfree(cpufreq_dev);
} EXPORT_SYMBOL(cpufreq_cooling_unregister); -- 1.7.11.3
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
On 30 October 2012 09:03, Amit Kachhap amit.kachhap@linaro.org wrote:
On 26 October 2012 12:39, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Hi,
Removing this list seems fine as most of frequency checks are moved inside generic thermal layer. Some minor review comments below,
Reviewed-by: Amit Daniel Kachhap amit.kachhap@linaro.org
Thanks.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
drivers/thermal/cpu_cooling.c | 91 +++++++++---------------------------------- 1 file changed, 19 insertions(+), 72 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 415b041..2ffd12c 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock);
-static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count;
/* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -240,28 +241,18 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) {
int ret = -EINVAL, i = 0;
struct cpufreq_cooling_device *cpufreq_device;
struct cpumask *maskPtr;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
check cdev is not null.
struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0;
int i = 0;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev)
break;
}
if (cpufreq_device == NULL)
goto return_get_max_state;
maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu); if (!table) { *state = 0;
ret = 0;
goto return_get_max_state;
return 0; } for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
@@ -272,12 +263,10 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
if (count > 0) { *state = --count;
ret = 0;
return 0; }
-return_get_max_state:
mutex_unlock(&cooling_cpufreq_lock);
return ret;
return -EINVAL;
}
/** @@ -288,20 +277,10 @@ return_get_max_state: static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) {
int ret = -EINVAL;
struct cpufreq_cooling_device *cpufreq_device;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
*state = cpufreq_device->cpufreq_state;
ret = 0;
break;
}
}
mutex_unlock(&cooling_cpufreq_lock);
return ret;
*state = cpufreq_device->cpufreq_state;
return 0;
}
/** @@ -312,22 +291,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) {
int ret = -EINVAL;
struct cpufreq_cooling_device *cpufreq_device;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
check cdev is not null
Such values are used in all the three static cpufreq_***_state callback functions, when these functions are called, cdev->devdata should have been set in the registration function, and cannot be null, no body can call these function before registration, so I think there is no need to add such a check here.
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
ret = 0;
break;
}
}
if (!ret)
ret = cpufreq_apply_cooling(cpufreq_device, state);
mutex_unlock(&cooling_cpufreq_lock);
return ret;
return cpufreq_apply_cooling(cpufreq_device, state);
}
/* Bind cpufreq callbacks to thermal cooling device ops */ @@ -351,7 +317,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL;
unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
unsigned int min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; int ret = 0, i; struct cpufreq_policy policy;
@@ -360,9 +326,6 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (!cpufreq_frequency_get_table(cpumask_any(clip_cpus))) return ERR_PTR(-EPROBE_DEFER);
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
cpufreq_dev_count++;
/*Verify that all the clip cpus have same freq_min, freq_max limit*/ for_each_cpu(i, clip_cpus) { /*continue if cpufreq policy not found and not return error*/
@@ -384,9 +347,6 @@ struct thermal_cooling_device *cpufreq_cooling_register(
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
if (cpufreq_dev_count == 0)
mutex_init(&cooling_cpufreq_lock);
ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); if (ret) { kfree(cpufreq_dev);
@@ -405,12 +365,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cpufreq_state = 0; mutex_lock(&cooling_cpufreq_lock);
list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list); /* Register the notifier for first cpufreq cooling device */ if (cpufreq_dev_count == 0) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER);
cpufreq_dev_count++; mutex_unlock(&cooling_cpufreq_lock); return cool_dev;
@@ -423,33 +383,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) {
struct cpufreq_cooling_device *cpufreq_dev = NULL;
unsigned int cpufreq_dev_count = 0;
struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata; mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
break;
cpufreq_dev_count++;
}
if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
mutex_unlock(&cooling_cpufreq_lock);
return;
}
list_del(&cpufreq_dev->node);
cpufreq_dev_count--; /* Unregister the notifier for the last cpufreq cooling device */
if (cpufreq_dev_count == 1) {
if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock);
thermal_cooling_device_unregister(cpufreq_dev->cool_dev); release_idr(&cpufreq_idr, cpufreq_dev->id);
if (cpufreq_dev_count == 1)
mutex_destroy(&cooling_cpufreq_lock); kfree(cpufreq_dev);
} EXPORT_SYMBOL(cpufreq_cooling_unregister); -- 1.7.11.3
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Viresh Kumar viresh.kumar@linaro.org Reviewed-by: Francesco Lavra francescolavra.fl@gmail.com --- arch/arm/configs/u8500_defconfig | 4 + drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 123 ++++++ drivers/thermal/db8500_thermal.c | 557 +++++++++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 39 ++ 6 files changed, 745 insertions(+) create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 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 cc5e7a8..34918c4 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/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e1cb6bd..54c8fd0 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -31,6 +31,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL + bool "DB8500 thermal management" + depends on THERMAL + default y + help + Adds DB8500 thermal management implementation according to the thermal + management framework. A thermal zone with several trip points will be + created. Cooling devices can be bound to the trip points to cool this + thermal zone if trip points reached. + +config DB8500_CPUFREQ_COOLING + tristate "DB8500 cpufreq cooling" + depends on CPU_THERMAL + default y + help + Adds DB8500 cpufreq cooling devices, and these cooling devices can be + bound to thermal zone trip points. When a trip point reached, the + bound cpufreq cooling device turns active to set CPU frequency low to + cool down the CPU. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..e4eddfd --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,123 @@ +/* + * 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@linaro.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/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +static LIST_HEAD(db8500_cpufreq_cdev_list); + +struct db8500_cpufreq_cdev { + struct thermal_cooling_device *cdev; + struct list_head node; +}; + +static int __devinit db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{ + struct db8500_cpufreq_cdev *cooling_dev; + struct cpumask mask_val; + + cooling_dev = devm_kzalloc(&pdev->dev, + sizeof(*cooling_dev), GFP_KERNEL); + if (!cooling_dev) + return -ENOMEM; + + cpumask_set_cpu(0, &mask_val); + cooling_dev->cdev = cpufreq_cooling_register(&mask_val); + + if (IS_ERR_OR_NULL(cooling_dev->cdev)) { + dev_err(&pdev->dev, + "Failed to register cpufreq cooling device\n"); + return PTR_ERR(cooling_dev->cdev); + } + + pdev->dev.platform_data = cooling_dev->cdev; + list_add_tail(&cooling_dev->node, &db8500_cpufreq_cdev_list); + dev_info(&pdev->dev, "Cooling device registered: %s\n", + cooling_dev->cdev->type); + + return 0; +} + +static int __devexit db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{ + struct db8500_cpufreq_cdev *cooling_dev; + + list_for_each_entry(cooling_dev, &db8500_cpufreq_cdev_list, node) + if (cooling_dev->cdev == pdev->dev.platform_data) { + cpufreq_cooling_unregister(cooling_dev->cdev); + list_del(&cooling_dev->node); + } + + return 0; +} + +static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return -ENOSYS; +} + +static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{ + return -ENOSYS; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = { + { .compatible = "stericsson,db8500-cpufreq-cooling" }, + {}, +}; +#else +#define db8500_cpufreq_cooling_match NULL +#endif + +static struct platform_driver db8500_cpufreq_cooling_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-cpufreq-cooling", + .of_match_table = db8500_cpufreq_cooling_match, + }, + .probe = db8500_cpufreq_cooling_probe, + .suspend = db8500_cpufreq_cooling_suspend, + .resume = db8500_cpufreq_cooling_resume, + .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 100644 index 0000000..52b814d --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,557 @@ +/* + * db8500_thermal.c - db8500 Thermal Management Implementation + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang hognbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/interrupt.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/db8500_thermal.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0 + +struct db8500_thermal_zone { + struct thermal_zone_device *therm_dev; + struct mutex th_lock; + struct work_struct therm_work; + struct db8500_thsens_platform_data *trip_tab; + enum thermal_device_mode mode; + enum thermal_trend trend; + unsigned long cur_temp_pseudo; + unsigned int cur_index; +}; + +/* Local function to check if thermal zone matches cooling devices */ +static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev, + struct db8500_trip_point *trip_points) +{ + int i; + char *cdev_name; + + if (!strlen(cdev->type)) + return -EINVAL; + + for (i = 0; i < COOLING_DEV_MAX; i++) { + cdev_name = trip_points->cdev_name[i]; + if (!strlen(cdev_name)) + continue; + if (!strcmp(cdev_name, cdev->type)) + return 0; + } + + return -ENODEV; +} + +/* Callback to bind cooling device to 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; + unsigned long max_state, upper, lower; + int i, ret = -EINVAL; + + pzone = thermal->devdata; + ptrips = pzone->trip_tab; + + for (i = 0; i < ptrips->num_trips; i++) { + if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) + continue; + + cdev->ops->get_max_state(cdev, &max_state); + lower = upper = (i > max_state) ? max_state : i; + + ret = thermal_zone_bind_cooling_device(thermal, i, + cdev, upper, lower); + + dev_info(&cdev->device, "%s bind to %d: %d-%s\n", + cdev->type, i, ret, ret ? "fail" : "succeed"); + } + + return ret; +} + +/* Callback to unbind cooling device from 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, ret = -EINVAL; + + pzone = thermal->devdata; + ptrips = pzone->trip_tab; + + for (i = 0; i < ptrips->num_trips; i++) { + if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) + continue; + + ret = thermal_zone_unbind_cooling_device(thermal, i, cdev); + + dev_info(&cdev->device, "%s unbind: %s\n", + cdev->type, ret ? "fail" : "succeed"); + } + + return ret; +} + +/* Callback to get current temperature */ +static int db8500_sys_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + /* + * TODO: There is no PRCMU interface to get temperature data currently, + * so a pseudo temperature is returned , it works for thermal framework + * and this will be fixed when the PRCMU interface is available. + */ + *temp = pzone->cur_temp_pseudo; + + return 0; +} + +/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + *trend = pzone->trend; + + return 0; +} + +/* Callback to get thermal zone mode */ +static int db8500_sys_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + mutex_lock(&pzone->th_lock); + *mode = pzone->mode; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to set thermal zone mode */ +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 = thermal->devdata; + pthdev = pzone->therm_dev; + + mutex_lock(&pzone->th_lock); + + pzone->mode = mode; + if (mode == THERMAL_DEVICE_ENABLED) + schedule_work(&pzone->therm_work); + + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to get trip point type */ +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 = thermal->devdata; + ptrips = pzone->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *type = ptrips->trip_points[trip].type; + + return 0; +} + +/* Callback to get trip point temperature */ +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 = thermal->devdata; + ptrips = pzone->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *temp = ptrips->trip_points[trip].temp; + + return 0; +} + +/* Callback to get critical trip point temperature */ +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 = thermal->devdata; + ptrips = pzone->trip_tab; + + 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_trend = db8500_sys_get_trend, + .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 void db8500_thermal_update_config(struct db8500_thermal_zone *pzone, + unsigned int idx, enum thermal_trend trend, + unsigned long next_low, unsigned long next_high) +{ + pzone->cur_index = idx; + pzone->cur_temp_pseudo = (next_low + next_high)/2; + pzone->trend = trend; + + prcmu_stop_temp_sense(); + prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); + prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); +} + +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; + unsigned int idx; + + ptrips = pzone->trip_tab; + idx = pzone->cur_index; + if (unlikely(idx == 0)) + /* Meaningless for thermal management, ignoring it */ + return IRQ_HANDLED; + + if (idx == 1) { + next_high = ptrips->trip_points[0].temp; + next_low = PRCMU_DEFAULT_LOW_TEMP; + } else { + next_high = ptrips->trip_points[idx-1].temp; + next_low = ptrips->trip_points[idx-2].temp; + } + idx -= 1; + + db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING, + next_low, next_high); + + dev_dbg(&pzone->therm_dev->device, + "PRCMU set max %ld, 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; + unsigned int idx; + + ptrips = pzone->trip_tab; + idx = pzone->cur_index; + + if (idx < ptrips->num_trips - 1) { + next_high = ptrips->trip_points[idx+1].temp; + next_low = ptrips->trip_points[idx].temp; + idx += 1; + + db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING, + next_low, next_high); + + dev_dbg(&pzone->therm_dev->device, + "PRCMU set max %ld, min %ld\n", next_high, next_low); + } + + else if (idx == ptrips->num_trips - 1) + pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1; + + 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) + return; + + thermal_zone_device_update(pzone->therm_dev); + dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); +} + +#ifdef CONFIG_OF +static struct db8500_thsens_platform_data* + db8500_thermal_parse_dt(struct platform_device *pdev) +{ + struct db8500_thsens_platform_data *ptrips; + struct device_node *np = pdev->dev.of_node; + char prop_name[32]; + const char *tmp_str; + u32 tmp_data; + int i, j; + + if (!np) { + dev_err(&pdev->dev, "Missing device tree data\n"); + return NULL; + } + + ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL); + if (!ptrips) + return NULL; + + if (of_property_read_u32(np, "num-trips", &tmp_data)) + goto err_parse_dt; + + ptrips->num_trips = tmp_data; + + for (i = 0; i < ptrips->num_trips; i++) { + sprintf(prop_name, "trip%d-temp", i); + if (of_property_read_u32(np, prop_name, &tmp_data)) + goto err_parse_dt; + + ptrips->trip_points[i].temp = tmp_data; + + sprintf(prop_name, "trip%d-type", i); + if (of_property_read_string(np, prop_name, &tmp_str)) + goto err_parse_dt; + + if (!strcmp(tmp_str, "active")) + ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE; + else if (!strcmp(tmp_str, "passive")) + ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE; + else if (!strcmp(tmp_str, "hot")) + ptrips->trip_points[i].type = THERMAL_TRIP_HOT; + else if (!strcmp(tmp_str, "critical")) + ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL; + else + goto err_parse_dt; + + sprintf(prop_name, "trip%d-cdev-num", i); + if (of_property_read_u32(np, prop_name, &tmp_data)) + goto err_parse_dt; + + for (j = 0; j < tmp_data; j++) { + sprintf(prop_name, "trip%d-cdev-name%d", i, j); + if (of_property_read_string(np, prop_name, &tmp_str)) + goto err_parse_dt; + strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str); + } + } + return ptrips; + +err_parse_dt: + dev_err(&pdev->dev, "Parsing device tree data error.\n"); + return NULL; +} +#else +static struct db8500_thsens_platform_data* + db8500_thermal_parse_dt(struct platform_device *pdev) +{ + return NULL; +} +#endif + +static int __devinit db8500_thermal_probe(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = NULL; + struct db8500_thsens_platform_data *ptrips = NULL; + int low_irq, high_irq, ret = 0; + unsigned long dft_low, dft_high; + + pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL); + if (!pzone) + return -ENOMEM; + + ptrips = db8500_thermal_parse_dt(pdev); + if (!ptrips) + ptrips = dev_get_platdata(&pdev->dev); + + if (!ptrips) + return -EINVAL; + + mutex_init(&pzone->th_lock); + mutex_lock(&pzone->th_lock); + + pzone->mode = THERMAL_DEVICE_DISABLED; + pzone->trip_tab = ptrips; + + pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone", + ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0); + + if (IS_ERR_OR_NULL(pzone->therm_dev)) { + dev_err(&pdev->dev, "Register thermal zone device failed.\n"); + return PTR_ERR(pzone->therm_dev); + } + dev_info(&pdev->dev, "Thermal zone device registered.\n"); + + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, + dft_low, dft_high); + + INIT_WORK(&pzone->therm_work, db8500_thermal_work); + + low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (low_irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n"); + return low_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL, + prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "dbx500_temp_low", pzone); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate temp low irq.\n"); + return ret; + } + + high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (high_irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n"); + return high_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL, + prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "dbx500_temp_high", pzone); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate temp high irq.\n"); + return ret; + } + + platform_set_drvdata(pdev, pzone); + pzone->mode = THERMAL_DEVICE_ENABLED; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone; + pzone = platform_get_drvdata(pdev); + + cancel_work_sync(&pzone->therm_work); + mutex_destroy(&pzone->th_lock); + thermal_zone_device_unregister(pzone->therm_dev); + + return 0; +} + +static int db8500_thermal_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct db8500_thermal_zone *pzone; + pzone = platform_get_drvdata(pdev); + + flush_work_sync(&pzone->therm_work); + prcmu_stop_temp_sense(); + + return 0; +} + +static int db8500_thermal_resume(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone; + struct db8500_thsens_platform_data *ptrips; + unsigned long dft_low, dft_high; + + pzone = platform_get_drvdata(pdev); + ptrips = pzone->trip_tab; + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, + dft_low, dft_high); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = { + { .compatible = "stericsson,db8500-thermal" }, + {}, +}; +#else +#define db8500_thermal_match NULL +#endif + +static struct platform_driver db8500_thermal_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-thermal", + .of_match_table = db8500_thermal_match, + }, + .probe = db8500_thermal_probe, + .suspend = db8500_thermal_suspend, + .resume = db8500_thermal_resume, + .remove = __devexit_p(db8500_thermal_remove), +}; + +module_platform_driver(db8500_thermal_driver); + +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..8825cd9 --- /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@linaro.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 cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH]; +}; + +struct db8500_thsens_platform_data { + struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS]; + int num_trips; +}; + +#endif /* _DB8500_THERMAL_H_ */
On 24 October 2012 17:28, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Viresh Kumar viresh.kumar@linaro.org Reviewed-by: Francesco Lavra francescolavra.fl@gmail.com
You can't add these lines, until somebody has replied you with them in earlier mails.
They don't show that somebody has put effort in reviewing them, but that current patch looks Ok to these guys.
arch/arm/configs/u8500_defconfig | 4 +
This is considered as platform part. So it must be part of next patch.
drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 123 ++++++ drivers/thermal/db8500_thermal.c | 557 +++++++++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 39 ++ 6 files changed, 745 insertions(+) create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 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 cc5e7a8..34918c4 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/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e1cb6bd..54c8fd0 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -31,6 +31,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL
bool "DB8500 thermal management"
depends on THERMAL
default y
help
Adds DB8500 thermal management implementation according to the thermal
management framework. A thermal zone with several trip points will be
created. Cooling devices can be bound to the trip points to cool this
thermal zone if trip points reached.
+config DB8500_CPUFREQ_COOLING
tristate "DB8500 cpufreq cooling"
depends on CPU_THERMAL
default y
help
Adds DB8500 cpufreq cooling devices, and these cooling devices can be
bound to thermal zone trip points. When a trip point reached, the
bound cpufreq cooling device turns active to set CPU frequency low to
cool down the CPU.
config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..e4eddfd --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,123 @@ +/*
- 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@linaro.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.
remove extra blank line.
- */
+#include <linux/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h>
+static LIST_HEAD(db8500_cpufreq_cdev_list);
+struct db8500_cpufreq_cdev {
struct thermal_cooling_device *cdev;
struct list_head node;
+};
+static int __devinit db8500_cpufreq_cooling_probe(struct platform_device *pdev)
As said earlier, don't use __devinit, exit...
+{
struct db8500_cpufreq_cdev *cooling_dev;
struct cpumask mask_val;
cooling_dev = devm_kzalloc(&pdev->dev,
sizeof(*cooling_dev), GFP_KERNEL);
Align this too with 'gq'
if (!cooling_dev)
return -ENOMEM;
cpumask_set_cpu(0, &mask_val);
cooling_dev->cdev = cpufreq_cooling_register(&mask_val);
if (IS_ERR_OR_NULL(cooling_dev->cdev)) {
dev_err(&pdev->dev,
"Failed to register cpufreq cooling device\n");
return PTR_ERR(cooling_dev->cdev);
}
pdev->dev.platform_data = cooling_dev->cdev;
Use platform_set_drvdata() and platform_get_drvdata()
list_add_tail(&cooling_dev->node, &db8500_cpufreq_cdev_list);
dev_info(&pdev->dev, "Cooling device registered: %s\n",
cooling_dev->cdev->type);
return 0;
+}
+static int __devexit db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{
struct db8500_cpufreq_cdev *cooling_dev;
list_for_each_entry(cooling_dev, &db8500_cpufreq_cdev_list, node)
if (cooling_dev->cdev == pdev->dev.platform_data) {
Use platform_get_drvdata()
cpufreq_cooling_unregister(cooling_dev->cdev);
list_del(&cooling_dev->node);
}
return 0;
+}
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
return -ENOSYS;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = {
{ .compatible = "stericsson,db8500-cpufreq-cooling" },
{},
+}; +#else +#define db8500_cpufreq_cooling_match NULL +#endif
+static struct platform_driver db8500_cpufreq_cooling_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-cpufreq-cooling",
.of_match_table = db8500_cpufreq_cooling_match,
},
.probe = db8500_cpufreq_cooling_probe,
.suspend = db8500_cpufreq_cooling_suspend,
.resume = db8500_cpufreq_cooling_resume,
.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 100644 index 0000000..52b814d --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,557 @@ +/*
- db8500_thermal.c - db8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hognbo.zhang@linaro.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.
remove extra blank line.
- */
+#include <linux/cpu_cooling.h> +#include <linux/interrupt.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/db8500_thermal.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h>
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0
+struct db8500_thermal_zone {
struct thermal_zone_device *therm_dev;
struct mutex th_lock;
struct work_struct therm_work;
struct db8500_thsens_platform_data *trip_tab;
enum thermal_device_mode mode;
enum thermal_trend trend;
unsigned long cur_temp_pseudo;
unsigned int cur_index;
+};
+/* Local function to check if thermal zone matches cooling devices */ +static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
struct db8500_trip_point *trip_points)
+{
int i;
char *cdev_name;
if (!strlen(cdev->type))
return -EINVAL;
for (i = 0; i < COOLING_DEV_MAX; i++) {
cdev_name = trip_points->cdev_name[i];
if (!strlen(cdev_name))
continue;
Even if you don't have this strlen(), below strcmp will skip null strings.
if (!strcmp(cdev_name, cdev->type))
return 0;
}
return -ENODEV;
+}
+/* Callback to bind cooling device to 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;
unsigned long max_state, upper, lower;
int i, ret = -EINVAL;
pzone = thermal->devdata;
ptrips = pzone->trip_tab;
Do this with definition of these variables.
for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
continue;
cdev->ops->get_max_state(cdev, &max_state);
lower = upper = (i > max_state) ? max_state : i;
ret = thermal_zone_bind_cooling_device(thermal, i,
cdev, upper, lower);
dev_info(&cdev->device, "%s bind to %d: %d-%s\n",
cdev->type, i, ret, ret ? "fail" : "succeed");
}
return ret;
+}
+/* Callback to unbind cooling device from 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, ret = -EINVAL;
pzone = thermal->devdata;
ptrips = pzone->trip_tab;
ditto
for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
continue;
ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
dev_info(&cdev->device, "%s unbind: %s\n",
cdev->type, ret ? "fail" : "succeed");
}
return ret;
+}
+/* Callback to get current temperature */ +static int db8500_sys_get_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
/*
* TODO: There is no PRCMU interface to get temperature data currently,
* so a pseudo temperature is returned , it works for thermal framework
* and this will be fixed when the PRCMU interface is available.
*/
*temp = pzone->cur_temp_pseudo;
return 0;
+}
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
*trend = pzone->trend;
return 0;
Can make it return void.
+}
+/* Callback to get thermal zone mode */ +static int db8500_sys_get_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode *mode)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
mutex_lock(&pzone->th_lock);
*mode = pzone->mode;
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to set thermal zone mode */ +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 = thermal->devdata;
pthdev = pzone->therm_dev;
ditto
mutex_lock(&pzone->th_lock);
pzone->mode = mode;
if (mode == THERMAL_DEVICE_ENABLED)
schedule_work(&pzone->therm_work);
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to get trip point type */ +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 = thermal->devdata;
ptrips = pzone->trip_tab;
ditto
do it everywhere
if (trip >= ptrips->num_trips)
return -EINVAL;
*type = ptrips->trip_points[trip].type;
return 0;
+}
+/* Callback to get trip point temperature */ +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 = thermal->devdata;
ptrips = pzone->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*temp = ptrips->trip_points[trip].temp;
return 0;
+}
+/* Callback to get critical trip point temperature */ +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 = thermal->devdata;
ptrips = pzone->trip_tab;
for (i = (ptrips->num_trips - 1); i > 0; i--) {
don't need extra () here. It would be followed by a ";"
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_trend = db8500_sys_get_trend,
.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 void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
unsigned int idx, enum thermal_trend trend,
unsigned long next_low, unsigned long next_high)
+{
pzone->cur_index = idx;
pzone->cur_temp_pseudo = (next_low + next_high)/2;
pzone->trend = trend;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
+}
+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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
} else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
}
idx -= 1;
db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
next_low, next_high);
dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, 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;
unsigned int idx;
ptrips = pzone->trip_tab;
idx = pzone->cur_index;
if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
idx += 1;
db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING,
next_low, next_high);
dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
}
else if (idx == ptrips->num_trips - 1)
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
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)
return;
thermal_zone_device_update(pzone->therm_dev);
dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n");
+}
+#ifdef CONFIG_OF +static struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev)
+{
struct db8500_thsens_platform_data *ptrips;
struct device_node *np = pdev->dev.of_node;
char prop_name[32];
const char *tmp_str;
u32 tmp_data;
int i, j;
if (!np) {
dev_err(&pdev->dev, "Missing device tree data\n");
return NULL;
}
ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
if (!ptrips)
return NULL;
if (of_property_read_u32(np, "num-trips", &tmp_data))
goto err_parse_dt;
ptrips->num_trips = tmp_data;
for (i = 0; i < ptrips->num_trips; i++) {
sprintf(prop_name, "trip%d-temp", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
ptrips->trip_points[i].temp = tmp_data;
sprintf(prop_name, "trip%d-type", i);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (!strcmp(tmp_str, "active"))
ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
else if (!strcmp(tmp_str, "passive"))
ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
else if (!strcmp(tmp_str, "hot"))
ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
else if (!strcmp(tmp_str, "critical"))
ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
else
goto err_parse_dt;
sprintf(prop_name, "trip%d-cdev-num", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
for (j = 0; j < tmp_data; j++) {
sprintf(prop_name, "trip%d-cdev-name%d", i, j);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
}
}
return ptrips;
+err_parse_dt:
dev_err(&pdev->dev, "Parsing device tree data error.\n");
return NULL;
+} +#else +static struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev)
mark it inline here.
+{
return NULL;
+} +#endif
+static int __devinit db8500_thermal_probe(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone = NULL;
struct db8500_thsens_platform_data *ptrips = NULL;
int low_irq, high_irq, ret = 0;
unsigned long dft_low, dft_high;
pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
if (!pzone)
return -ENOMEM;
ptrips = db8500_thermal_parse_dt(pdev);
This is what u have in this routine at the very first line:
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n");
So, you will end up printing this line for every non-DT case. Not good. What u can do is, give preference to normal pdata here.
if (!ptrips)
ptrips = dev_get_platdata(&pdev->dev);
if (!ptrips)
In case we have a DT case, you will run this if statement twice :(
return -EINVAL;
move pzone = devm_kzalloc, here after verifying pdata is there. so that you don't allocate things for error cases.
mutex_init(&pzone->th_lock);
mutex_lock(&pzone->th_lock);
pzone->mode = THERMAL_DEVICE_DISABLED;
pzone->trip_tab = ptrips;
pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
if (IS_ERR_OR_NULL(pzone->therm_dev)) {
dev_err(&pdev->dev, "Register thermal zone device failed.\n");
return PTR_ERR(pzone->therm_dev);
}
dev_info(&pdev->dev, "Thermal zone device registered.\n");
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
INIT_WORK(&pzone->therm_work, db8500_thermal_work);
low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
if (low_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
return low_irq;
Don't you want to do thermal_zone_device_unregister here? Please check the sequence of stuff in this routine.
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
why threaded irq?
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_low", pzone);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
don't want to free resources?
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_high", pzone);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
return ret;
}
platform_set_drvdata(pdev, pzone);
pzone->mode = THERMAL_DEVICE_ENABLED;
mutex_unlock(&pzone->th_lock);
return 0;
+}
+static int __devexit db8500_thermal_remove(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
cancel_work_sync(&pzone->therm_work);
mutex_destroy(&pzone->th_lock);
thermal_zone_device_unregister(pzone->therm_dev);
The first thing you must do here is unregister... Then cancel work and mutex destroy. It has to be opposite of probe.
return 0;
+}
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct db8500_thermal_zone *pzone;
pzone = platform_get_drvdata(pdev);
flush_work_sync(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone;
struct db8500_thsens_platform_data *ptrips;
unsigned long dft_low, dft_high;
pzone = platform_get_drvdata(pdev);
ptrips = pzone->trip_tab;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
return 0;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = {
{ .compatible = "stericsson,db8500-thermal" },
{},
+}; +#else +#define db8500_thermal_match NULL +#endif
+static struct platform_driver db8500_thermal_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-thermal",
.of_match_table = db8500_thermal_match,
},
.probe = db8500_thermal_probe,
.suspend = db8500_thermal_suspend,
.resume = db8500_thermal_resume,
.remove = __devexit_p(db8500_thermal_remove),
+};
+module_platform_driver(db8500_thermal_driver);
+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..8825cd9 --- /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@linaro.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 cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH];
+};
+struct db8500_thsens_platform_data {
struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS];
int num_trips;
+};
+#endif /* _DB8500_THERMAL_H_ */
[...]
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
*trend = pzone->trend;
return 0;
Can make it return void.
No, it is callback of thermal layer, prototype it to return int.
[...]
+static int __devinit db8500_thermal_probe(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone = NULL;
struct db8500_thsens_platform_data *ptrips = NULL;
int low_irq, high_irq, ret = 0;
unsigned long dft_low, dft_high;
pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
if (!pzone)
return -ENOMEM;
ptrips = db8500_thermal_parse_dt(pdev);
This is what u have in this routine at the very first line:
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n");
So, you will end up printing this line for every non-DT case. Not good. What u can do is, give preference to normal pdata here.
I moved this if(!np) into parse_dt function, no problem again. (in fact have already done this, but it is missed in this sending)
if (!ptrips)
ptrips = dev_get_platdata(&pdev->dev);
if (!ptrips)
[...]
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
why threaded irq?
In fact PRCMU firmware is polling the thermal sensor, and if it meets threshold, the PRCMU will write this event into share memory (shared between PRCMU and ARM) and trigger an interrupt to ARM. There may be other events passed via share memory, so it is better to handle this kind of irq as fast as possible(it is always the policy), and threaded irq satisfies this case better then the traditional one.
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_low", pzone);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
don't want to free resources?
devm_* is used to allocate resources, so no need to free them manually.
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_high", pzone);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
return ret;
}
[...]
On 25 October 2012 13:56, Hongbo Zhang hongbo.zhang@linaro.org wrote:
While replying to mails, don't remove lines like above. They help identifying who wrote what.
[...]
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
For example, you can't tell who wrote this line...
+static int __devinit db8500_thermal_probe(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone = NULL;
struct db8500_thsens_platform_data *ptrips = NULL;
int low_irq, high_irq, ret = 0;
unsigned long dft_low, dft_high;
pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
if (!pzone)
return -ENOMEM;
ptrips = db8500_thermal_parse_dt(pdev);
This is what u have in this routine at the very first line:
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n");
So, you will end up printing this line for every non-DT case. Not good. What u can do is, give preference to normal pdata here.
I moved this if(!np) into parse_dt function, no problem again. (in fact have already done this, but it is missed in this sending)
Sorry couldn't get your point. :( Can you share diff of latest code in the same mail thread?
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
why threaded irq?
In fact PRCMU firmware is polling the thermal sensor, and if it meets threshold, the PRCMU will write this event into share memory (shared between PRCMU and ARM) and trigger an interrupt to ARM.
There may be other events passed via share memory, so it is better to handle this kind of irq as fast as possible(it is always the policy), and threaded irq satisfies this case better then the traditional one.
Its been long that i prepared for an interview, but i believe purpose of threaded irq is something else.
There can be two use cases of request_irq() - We don't want to sleep from interrupt handler, because we don't need to sleep for reading hardware's register. And so handler must be called from interrupt context. We use normal request_irq() here. This is the fastest one.
- We have to sleep from some part of interrupt handler, because we don't have peripherals register on AMBA bus. But we have it on SPI or I2C bus, where read/ write to SPI/I2C can potentially sleep. So, we want handler to execute from process context and so use request_threaded_irq(), i.e. handler will be called from a thread. This will surely be slow.
Now in threaded irq case, we can give two handlers, one that must be called from interrupt context and other that must be called from process context. Both will be called one by one.
Sorry if i am wrong in my theory :( @Amit: Am i correct??
Now, the same question again. Are you sure you want threaded irq here.
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_low", pzone);
On 25 October 2012 16:41, Viresh Kumar viresh.kumar@linaro.org wrote:
On 25 October 2012 13:56, Hongbo Zhang hongbo.zhang@linaro.org wrote:
While replying to mails, don't remove lines like above. They help identifying who wrote what.
[...]
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
For example, you can't tell who wrote this line...
+static int __devinit db8500_thermal_probe(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone = NULL;
struct db8500_thsens_platform_data *ptrips = NULL;
int low_irq, high_irq, ret = 0;
unsigned long dft_low, dft_high;
pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
if (!pzone)
return -ENOMEM;
ptrips = db8500_thermal_parse_dt(pdev);
This is what u have in this routine at the very first line:
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n");
So, you will end up printing this line for every non-DT case. Not good. What u can do is, give preference to normal pdata here.
I moved this if(!np) into parse_dt function, no problem again. (in fact have already done this, but it is missed in this sending)
Sorry couldn't get your point. :( Can you share diff of latest code in the same mail thread?
Just paste my current pieces of codes here:
static struct db8500_thsens_platform_data* db8500_thermal_parse_dt(struct platform_device *pdev) { struct db8500_thsens_platform_data *ptrips; struct device_node *np = pdev->dev.of_node; char prop_name[32]; const char *tmp_str; u32 tmp_data; int i, j;
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n"); return NULL; } ...... }
static int db8500_thermal_probe(struct platform_device *pdev) { struct db8500_thermal_zone *pzone = NULL; struct db8500_thsens_platform_data *ptrips = NULL; int low_irq, high_irq, ret = 0; unsigned long dft_low, dft_high;
ptrips = db8500_thermal_parse_dt(pdev); if (!ptrips) ptrips = dev_get_platdata(&pdev->dev);
if (!ptrips) return -EINVAL;
pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL); if (!pzone) return -ENOMEM; ...... }
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
why threaded irq?
In fact PRCMU firmware is polling the thermal sensor, and if it meets threshold, the PRCMU will write this event into share memory (shared between PRCMU and ARM) and trigger an interrupt to ARM.
There may be other events passed via share memory, so it is better to handle this kind of irq as fast as possible(it is always the policy), and threaded irq satisfies this case better then the traditional one.
Its been long that i prepared for an interview, but i believe purpose of threaded irq is something else.
There can be two use cases of request_irq()
We don't want to sleep from interrupt handler, because we don't need to sleep for reading hardware's register. And so handler must be called from interrupt context. We use normal request_irq() here. This is the fastest one.
We have to sleep from some part of interrupt handler, because we don't have peripherals register on AMBA bus. But we have it on SPI or I2C bus,
where read/ write to SPI/I2C can potentially sleep. So, we want handler to execute from process context and so use request_threaded_irq(), i.e. handler will be called from a thread. This will surely be slow.
Now in threaded irq case, we can give two handlers, one that must be called from interrupt context and other that must be called from process context. Both will be called one by one.
Understand your points.
Sorry if i am wrong in my theory :( @Amit: Am i correct??
Now, the same question again. Are you sure you want threaded irq here.
I just saw that all the PRCMU and ab8500 related irqs use request_threaded_irq only difference is that I use devm_request_threaded_irq
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_low", pzone);
On 25 October 2012 15:03, Hongbo Zhang hongbo.zhang@linaro.org wrote:
On 25 October 2012 16:41, Viresh Kumar viresh.kumar@linaro.org wrote:
Just paste my current pieces of codes here:
static struct db8500_thsens_platform_data* db8500_thermal_parse_dt(struct platform_device *pdev) {
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n");
}
static int db8500_thermal_probe(struct platform_device *pdev) {
ptrips = db8500_thermal_parse_dt(pdev); if (!ptrips) ptrips = dev_get_platdata(&pdev->dev);
So, the above code still has the flaw i pointed out. It will print "Missing device tree data", while booting for non-DT case.
What i would suggest you is:
static int db8500_thermal_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node;
if (np) ptrips = db8500_thermal_parse_dt(pdev); else ptrips = dev_get_platdata(&pdev->dev);
if (!ptrips) explode!!
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
I just saw that all the PRCMU and ab8500 related irqs use request_threaded_irq only difference is that I use devm_request_threaded_irq
See, i started this threaded_irq thread is to make sure you know exactly what you are doing. Others are doing it doesn't mean you should do it too.. :)
You must dig in a bit to see why is it required for your case? If earlier code related to PRCMU and db8500 is correct, then i am sure you need to sleep from your handler.
-- viresh
On 25 October 2012 17:42, Viresh Kumar viresh.kumar@linaro.org wrote:
On 25 October 2012 15:03, Hongbo Zhang hongbo.zhang@linaro.org wrote:
On 25 October 2012 16:41, Viresh Kumar viresh.kumar@linaro.org wrote:
Just paste my current pieces of codes here:
static struct db8500_thsens_platform_data* db8500_thermal_parse_dt(struct platform_device *pdev) {
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n");
}
static int db8500_thermal_probe(struct platform_device *pdev) {
ptrips = db8500_thermal_parse_dt(pdev); if (!ptrips) ptrips = dev_get_platdata(&pdev->dev);
So, the above code still has the flaw i pointed out. It will print "Missing device tree data", while booting for non-DT case.
What i would suggest you is:
static int db8500_thermal_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node;
if (np) ptrips = db8500_thermal_parse_dt(pdev); else ptrips = dev_get_platdata(&pdev->dev); if (!ptrips) explode!!
This seems neat.
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
I just saw that all the PRCMU and ab8500 related irqs use request_threaded_irq only difference is that I use devm_request_threaded_irq
See, i started this threaded_irq thread is to make sure you know exactly what you are doing. Others are doing it doesn't mean you should do it too.. :)
You must dig in a bit to see why is it required for your case? If earlier code related to PRCMU and db8500 is correct, then i am sure you need to sleep from your handler.
-- viresh
On 25 October 2012 17:33, Hongbo Zhang hongbo.zhang@linaro.org wrote:
On 25 October 2012 16:41, Viresh Kumar viresh.kumar@linaro.org wrote:
On 25 October 2012 13:56, Hongbo Zhang hongbo.zhang@linaro.org wrote:
While replying to mails, don't remove lines like above. They help identifying who wrote what.
[...]
+/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
For example, you can't tell who wrote this line...
+static int __devinit db8500_thermal_probe(struct platform_device *pdev) +{
struct db8500_thermal_zone *pzone = NULL;
struct db8500_thsens_platform_data *ptrips = NULL;
int low_irq, high_irq, ret = 0;
unsigned long dft_low, dft_high;
pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
if (!pzone)
return -ENOMEM;
ptrips = db8500_thermal_parse_dt(pdev);
This is what u have in this routine at the very first line:
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n");
So, you will end up printing this line for every non-DT case. Not good. What u can do is, give preference to normal pdata here.
I moved this if(!np) into parse_dt function, no problem again. (in fact have already done this, but it is missed in this sending)
Sorry couldn't get your point. :( Can you share diff of latest code in the same mail thread?
Just paste my current pieces of codes here:
static struct db8500_thsens_platform_data* db8500_thermal_parse_dt(struct platform_device *pdev) { struct db8500_thsens_platform_data *ptrips; struct device_node *np = pdev->dev.of_node; char prop_name[32]; const char *tmp_str; u32 tmp_data; int i, j;
if (!np) { dev_err(&pdev->dev, "Missing device tree data\n"); return NULL; } ......
}
static int db8500_thermal_probe(struct platform_device *pdev) { struct db8500_thermal_zone *pzone = NULL; struct db8500_thsens_platform_data *ptrips = NULL; int low_irq, high_irq, ret = 0; unsigned long dft_low, dft_high;
ptrips = db8500_thermal_parse_dt(pdev); if (!ptrips) ptrips = dev_get_platdata(&pdev->dev); if (!ptrips) return -EINVAL; pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL); if (!pzone) return -ENOMEM; ......
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
why threaded irq?
In fact PRCMU firmware is polling the thermal sensor, and if it meets threshold, the PRCMU will write this event into share memory (shared between PRCMU and ARM) and trigger an interrupt to ARM.
There may be other events passed via share memory, so it is better to handle this kind of irq as fast as possible(it is always the policy), and threaded irq satisfies this case better then the traditional one.
Its been long that i prepared for an interview, but i believe purpose of threaded irq is something else.
There can be two use cases of request_irq()
We don't want to sleep from interrupt handler, because we don't need to sleep for reading hardware's register. And so handler must be called from interrupt context. We use normal request_irq() here. This is the fastest one.
We have to sleep from some part of interrupt handler, because we don't have peripherals register on AMBA bus. But we have it on SPI or I2C bus,
where read/ write to SPI/I2C can potentially sleep. So, we want handler to execute from process context and so use request_threaded_irq(), i.e. handler will be called from a thread. This will surely be slow.
Now in threaded irq case, we can give two handlers, one that must be called from interrupt context and other that must be called from process context. Both will be called one by one.
Understand your points.
Verish, see the codes in include/linux/interrupt.h: static inline int __must_check devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) { return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags, devname, dev_id); } devm_request_irq is devm_request_threaded_irq
Sorry if i am wrong in my theory :( @Amit: Am i correct??
Now, the same question again. Are you sure you want threaded irq here.
I just saw that all the PRCMU and ab8500 related irqs use request_threaded_irq only difference is that I use devm_request_threaded_irq
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_low", pzone);
On 25 October 2012 15:26, Hongbo Zhang hongbo.zhang@linaro.org wrote:
Verish, see the codes in include/linux/interrupt.h:
s/verish/viresh :)
static inline int __must_check devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) { return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags, devname, dev_id); } devm_request_irq is devm_request_threaded_irq
See carefully what's happening here.
All interrupt types have a common irq_desc type in kernel. This has few pointers for every interrupt line: - List of handlers to call from interrupt context - handlers to call from process context via a thread.
So, the internal implementation is exactly same... The only difference is which pointer should be called in.
the devm_request_threaded_irq() called from devm_request_irq() has following params handler: For irq to be called from interrupt context (param 3) NULL: For irq to be called from process context. (param 4)
So, that means normal request_irq type only.
In your case, you have passed interrupt context pointer as NULL and process context one as handler. So that's an issue.
-- viresh
On 25 October 2012 15:34, Viresh Kumar viresh.kumar@linaro.org wrote:
On 25 October 2012 15:26, Hongbo Zhang hongbo.zhang@linaro.org wrote:
This is what your prcmu driver's routines are doing:
int db8500_prcmu_config_hotmon(u8 low, u8 high) { ... wait_for_completion(&mb4_transfer.work); ... return 0; }
This is why others in STE have used threaded_irqs... Because the routine you guys call from interrupt handlers actually sleeps.
So, they can't be called from interrupt context.
I wanted you to knew this :)
Its okay now, you need to use threaded irq only and you can't use normal request_irq(). Its not that you want to make things fast that's why you used threaded irqs... If you try to sleep from interrupt context (i.e. if you have registered your handler with request_irq()), you will see a kernel crash.
-- viresh
On 25 October 2012 18:11, Viresh Kumar viresh.kumar@linaro.org wrote:
On 25 October 2012 15:34, Viresh Kumar viresh.kumar@linaro.org wrote:
On 25 October 2012 15:26, Hongbo Zhang hongbo.zhang@linaro.org wrote:
This is what your prcmu driver's routines are doing:
int db8500_prcmu_config_hotmon(u8 low, u8 high) { ... wait_for_completion(&mb4_transfer.work); ... return 0; }
This is why others in STE have used threaded_irqs... Because the routine you guys call from interrupt handlers actually sleeps.
I should find this for you, but you find it for me :( Thanks a lot.
So, they can't be called from interrupt context.
I wanted you to knew this :)
Its okay now, you need to use threaded irq only and you can't use normal request_irq(). Its not that you want to make things fast that's why you used threaded irqs... If you try to sleep from interrupt context (i.e. if you have registered your handler with request_irq()), you will see a kernel crash.
-- viresh
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- .../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 121 +++++ drivers/thermal/db8500_thermal.c | 523 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 38 ++ 6 files changed, 744 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/Documentation/devicetree/bindings/thermal/db8500-thermal.txt b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt new file mode 100644 index 0000000..abe08d7 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal + +** Thermal node properties: + +- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated form PRCMU; +- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH"; +- num-trips : number of total trip points; +- tripN-temp : temperature of trip point N, should be in ascending order; +- tripN-type : type of trip point N, should be one of "active" "passive" "hot" "critical"; +- tripN-cdev-num : number of the cooling devices which can be bound to trip point N; +- tripN-cdev-nameM : name of the No. M cooling device of trip point N; + +Usually the num-trips and tripN-*** are separated in board related dts files. + +Example: +thermal@801573c0 { + compatible = "stericsson,db8500-thermal"; + reg = <0x801573c0 0x40>; + interrupts = <21 0x4>, <22 0x4>; + interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH"; + + num-trips = <3>; + + trip0-temp = <70000>; + trip0-type = "active"; + trip0-cdev-num = <1>; + trip0-cdev-name0 = "thermal-cpufreq-0"; + + trip1-temp = <75000>; + trip1-type = "active"; + trip1-cdev-num = <2>; + trip1-cdev-name0 = "thermal-cpufreq-0"; + trip1-cdev-name1 = "thermal-fan"; + + trip2-temp = <85000>; + trip2-type = "critical"; + trip2-cdev-num = <0>; +} diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index edfd67d..6607cba 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -30,6 +30,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL + bool "DB8500 thermal management" + depends on THERMAL + default y + help + Adds DB8500 thermal management implementation according to the thermal + management framework. A thermal zone with several trip points will be + created. Cooling devices can be bound to the trip points to cool this + thermal zone if trip points reached. + +config DB8500_CPUFREQ_COOLING + tristate "DB8500 cpufreq cooling" + depends on CPU_THERMAL + default y + help + Adds DB8500 cpufreq cooling devices, and these cooling devices can be + bound to thermal zone trip points. When a trip point reached, the + bound cpufreq cooling device turns active to set CPU frequency low to + cool down the CPU. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..f6a8d24 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,121 @@ +/* + * 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@linaro.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/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +static LIST_HEAD(db8500_cpufreq_cdev_list); + +struct db8500_cpufreq_cdev { + struct thermal_cooling_device *cdev; + struct list_head node; +}; + +static int db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{ + struct db8500_cpufreq_cdev *cooling_dev; + struct cpumask mask_val; + + cooling_dev = devm_kzalloc(&pdev->dev, sizeof(*cooling_dev), + GFP_KERNEL); + if (!cooling_dev) + return -ENOMEM; + + cpumask_set_cpu(0, &mask_val); + cooling_dev->cdev = cpufreq_cooling_register(&mask_val); + + if (IS_ERR_OR_NULL(cooling_dev->cdev)) { + dev_err(&pdev->dev, "Failed to register cooling device\n"); + return PTR_ERR(cooling_dev->cdev); + } + + platform_set_drvdata(pdev, cooling_dev->cdev); + list_add_tail(&cooling_dev->node, &db8500_cpufreq_cdev_list); + dev_info(&pdev->dev, "Cooling device registered: %s\n", + cooling_dev->cdev->type); + + return 0; +} + +static int db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{ + struct db8500_cpufreq_cdev *cooling_dev; + + list_for_each_entry(cooling_dev, &db8500_cpufreq_cdev_list, node) + if (cooling_dev->cdev == platform_get_drvdata(pdev)) { + cpufreq_cooling_unregister(cooling_dev->cdev); + list_del(&cooling_dev->node); + } + + return 0; +} + +static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return -ENOSYS; +} + +static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{ + return -ENOSYS; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = { + { .compatible = "stericsson,db8500-cpufreq-cooling" }, + {}, +}; +#else +#define db8500_cpufreq_cooling_match NULL +#endif + +static struct platform_driver db8500_cpufreq_cooling_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-cpufreq-cooling", + .of_match_table = db8500_cpufreq_cooling_match, + }, + .probe = db8500_cpufreq_cooling_probe, + .suspend = db8500_cpufreq_cooling_suspend, + .resume = db8500_cpufreq_cooling_resume, + .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 100644 index 0000000..d12bf9b --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,523 @@ +/* + * db8500_thermal.c - db8500 Thermal Management Implementation + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang hognbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/interrupt.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/db8500_thermal.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0 + +struct db8500_thermal_zone { + struct thermal_zone_device *therm_dev; + struct mutex th_lock; + struct work_struct therm_work; + struct db8500_thsens_platform_data *trip_tab; + enum thermal_device_mode mode; + enum thermal_trend trend; + unsigned long cur_temp_pseudo; + unsigned int cur_index; +}; + +/* Local function to check if thermal zone matches cooling devices */ +static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev, + struct db8500_trip_point *trip_points) +{ + int i; + char *cdev_name; + + if (!strlen(cdev->type)) + return -EINVAL; + + for (i = 0; i < COOLING_DEV_MAX; i++) { + cdev_name = trip_points->cdev_name[i]; + if (!strcmp(cdev_name, cdev->type)) + return 0; + } + + return -ENODEV; +} + +/* Callback to bind cooling device to thermal zone */ +static int db8500_cdev_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + unsigned long max_state, upper, lower; + int i, ret = -EINVAL; + + for (i = 0; i < ptrips->num_trips; i++) { + if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) + continue; + + cdev->ops->get_max_state(cdev, &max_state); + lower = upper = (i > max_state) ? max_state : i; + + ret = thermal_zone_bind_cooling_device(thermal, i, cdev, + upper, lower); + + dev_info(&cdev->device, "%s bind to %d: %d-%s\n", + cdev->type, i, ret, ret ? "fail" : "succeed"); + } + + return ret; +} + +/* Callback to unbind cooling device from thermal zone */ +static int db8500_cdev_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + int i, ret = -EINVAL; + + for (i = 0; i < ptrips->num_trips; i++) { + if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) + continue; + + ret = thermal_zone_unbind_cooling_device(thermal, i, cdev); + + dev_info(&cdev->device, "%s unbind: %s\n", + cdev->type, ret ? "fail" : "succeed"); + } + + return ret; +} + +/* Callback to get current temperature */ +static int db8500_sys_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + /* + * TODO: There is no PRCMU interface to get temperature data currently, + * so a pseudo temperature is returned , it works for thermal framework + * and this will be fixed when the PRCMU interface is available. + */ + *temp = pzone->cur_temp_pseudo; + + return 0; +} + +/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + *trend = pzone->trend; + + return 0; +} + +/* Callback to get thermal zone mode */ +static int db8500_sys_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + mutex_lock(&pzone->th_lock); + *mode = pzone->mode; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to set thermal zone mode */ +static int db8500_sys_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + mutex_lock(&pzone->th_lock); + + pzone->mode = mode; + if (mode == THERMAL_DEVICE_ENABLED) + schedule_work(&pzone->therm_work); + + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to get trip point type */ +static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *type = ptrips->trip_points[trip].type; + + return 0; +} + +/* Callback to get trip point temperature */ +static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *temp = ptrips->trip_points[trip].temp; + + return 0; +} + +/* Callback to get critical trip point temperature */ +static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + int i; + + 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_trend = db8500_sys_get_trend, + .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 void db8500_thermal_update_config(struct db8500_thermal_zone *pzone, + unsigned int idx, enum thermal_trend trend, + unsigned long next_low, unsigned long next_high) +{ + pzone->cur_index = idx; + pzone->cur_temp_pseudo = (next_low + next_high)/2; + pzone->trend = trend; + + prcmu_stop_temp_sense(); + prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); + prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); +} + +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 = pzone->trip_tab; + unsigned int idx = pzone->cur_index; + unsigned long next_low, next_high; + + if (unlikely(idx == 0)) + /* Meaningless for thermal management, ignoring it */ + return IRQ_HANDLED; + + if (idx == 1) { + next_high = ptrips->trip_points[0].temp; + next_low = PRCMU_DEFAULT_LOW_TEMP; + } else { + next_high = ptrips->trip_points[idx-1].temp; + next_low = ptrips->trip_points[idx-2].temp; + } + idx -= 1; + + db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING, + next_low, next_high); + + dev_dbg(&pzone->therm_dev->device, + "PRCMU set max %ld, 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 = pzone->trip_tab; + unsigned int idx = pzone->cur_index; + unsigned long next_low, next_high; + + if (idx < ptrips->num_trips - 1) { + next_high = ptrips->trip_points[idx+1].temp; + next_low = ptrips->trip_points[idx].temp; + idx += 1; + + db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING, + next_low, next_high); + + dev_dbg(&pzone->therm_dev->device, + "PRCMU set max %ld, min %ld\n", next_high, next_low); + } + + else if (idx == ptrips->num_trips - 1) + pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1; + + 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) + return; + + thermal_zone_device_update(pzone->therm_dev); + dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); +} + +#ifdef CONFIG_OF +static struct db8500_thsens_platform_data* + db8500_thermal_parse_dt(struct platform_device *pdev) +{ + struct db8500_thsens_platform_data *ptrips; + struct device_node *np = pdev->dev.of_node; + char prop_name[32]; + const char *tmp_str; + u32 tmp_data; + int i, j; + + ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL); + if (!ptrips) + return NULL; + + if (of_property_read_u32(np, "num-trips", &tmp_data)) + goto err_parse_dt; + + ptrips->num_trips = tmp_data; + + for (i = 0; i < ptrips->num_trips; i++) { + sprintf(prop_name, "trip%d-temp", i); + if (of_property_read_u32(np, prop_name, &tmp_data)) + goto err_parse_dt; + + ptrips->trip_points[i].temp = tmp_data; + + sprintf(prop_name, "trip%d-type", i); + if (of_property_read_string(np, prop_name, &tmp_str)) + goto err_parse_dt; + + if (!strcmp(tmp_str, "active")) + ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE; + else if (!strcmp(tmp_str, "passive")) + ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE; + else if (!strcmp(tmp_str, "hot")) + ptrips->trip_points[i].type = THERMAL_TRIP_HOT; + else if (!strcmp(tmp_str, "critical")) + ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL; + else + goto err_parse_dt; + + sprintf(prop_name, "trip%d-cdev-num", i); + if (of_property_read_u32(np, prop_name, &tmp_data)) + goto err_parse_dt; + + for (j = 0; j < tmp_data; j++) { + sprintf(prop_name, "trip%d-cdev-name%d", i, j); + if (of_property_read_string(np, prop_name, &tmp_str)) + goto err_parse_dt; + strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str); + } + } + return ptrips; + +err_parse_dt: + dev_err(&pdev->dev, "Parsing device tree data error.\n"); + return NULL; +} +#else +static inline struct db8500_thsens_platform_data* + db8500_thermal_parse_dt(struct platform_device *pdev) +{ + return NULL; +} +#endif + +static int db8500_thermal_probe(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = NULL; + struct db8500_thsens_platform_data *ptrips = NULL; + struct device_node *np = pdev->dev.of_node; + int low_irq, high_irq, ret = 0; + unsigned long dft_low, dft_high; + + if (np) + ptrips = db8500_thermal_parse_dt(pdev); + else + ptrips = dev_get_platdata(&pdev->dev); + + if (!ptrips) + return -EINVAL; + + pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL); + if (!pzone) + return -ENOMEM; + + mutex_init(&pzone->th_lock); + mutex_lock(&pzone->th_lock); + + pzone->mode = THERMAL_DEVICE_DISABLED; + pzone->trip_tab = ptrips; + + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, + dft_low, dft_high); + + INIT_WORK(&pzone->therm_work, db8500_thermal_work); + + low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (low_irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n"); + return low_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL, + prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "dbx500_temp_low", pzone); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate temp low irq.\n"); + return ret; + } + + high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (high_irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n"); + return high_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL, + prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "dbx500_temp_high", pzone); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate temp high irq.\n"); + return ret; + } + + pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone", + ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0); + + if (IS_ERR_OR_NULL(pzone->therm_dev)) { + dev_err(&pdev->dev, "Register thermal zone device failed.\n"); + return PTR_ERR(pzone->therm_dev); + } + dev_info(&pdev->dev, "Thermal zone device registered.\n"); + + platform_set_drvdata(pdev, pzone); + pzone->mode = THERMAL_DEVICE_ENABLED; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +static int db8500_thermal_remove(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + + thermal_zone_device_unregister(pzone->therm_dev); + cancel_work_sync(&pzone->therm_work); + mutex_destroy(&pzone->th_lock); + + return 0; +} + +static int db8500_thermal_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + + flush_work_sync(&pzone->therm_work); + prcmu_stop_temp_sense(); + + return 0; +} + +static int db8500_thermal_resume(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + unsigned long dft_low, dft_high; + + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, + dft_low, dft_high); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = { + { .compatible = "stericsson,db8500-thermal" }, + {}, +}; +#else +#define db8500_thermal_match NULL +#endif + +static struct platform_driver db8500_thermal_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-thermal", + .of_match_table = db8500_thermal_match, + }, + .probe = db8500_thermal_probe, + .suspend = db8500_thermal_suspend, + .resume = db8500_thermal_resume, + .remove = __devexit_p(db8500_thermal_remove), +}; + +module_platform_driver(db8500_thermal_driver); + +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..aad98f8 --- /dev/null +++ b/include/linux/platform_data/db8500_thermal.h @@ -0,0 +1,38 @@ +/* + * db8500_thermal.h - db8500 Thermal Management Implementation + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang hognbo.zhang@linaro.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 cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH]; +}; + +struct db8500_thsens_platform_data { + struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS]; + int num_trips; +}; + +#endif /* _DB8500_THERMAL_H_ */
On Thu Oct 25 11:13:59 2012, Hongbo Zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
.../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 121 +++++ drivers/thermal/db8500_thermal.c | 523 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 38 ++ 6 files changed, 744 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/Documentation/devicetree/bindings/thermal/db8500-thermal.txt b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt new file mode 100644 index 0000000..abe08d7 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal
+** Thermal node properties:
+- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated form PRCMU;
s/form/from
[...]
diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..f6a8d24 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,121 @@ +/*
- 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@linaro.com
Why do you keep writing an incorrect email address? :) I'm referring to the spelling "hognbo" instead of "hongbo".
- 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/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h>
+static LIST_HEAD(db8500_cpufreq_cdev_list);
+struct db8500_cpufreq_cdev {
- struct thermal_cooling_device *cdev;
- struct list_head node;
+};
+static int db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{
- struct db8500_cpufreq_cdev *cooling_dev;
- struct cpumask mask_val;
- cooling_dev = devm_kzalloc(&pdev->dev, sizeof(*cooling_dev),
GFP_KERNEL);
- if (!cooling_dev)
return -ENOMEM;
- cpumask_set_cpu(0, &mask_val);
- cooling_dev->cdev = cpufreq_cooling_register(&mask_val);
- if (IS_ERR_OR_NULL(cooling_dev->cdev)) {
dev_err(&pdev->dev, "Failed to register cooling device\n");
return PTR_ERR(cooling_dev->cdev);
- }
- platform_set_drvdata(pdev, cooling_dev->cdev);
- list_add_tail(&cooling_dev->node, &db8500_cpufreq_cdev_list);
- dev_info(&pdev->dev, "Cooling device registered: %s\n",
cooling_dev->cdev->type);
- return 0;
+}
+static int db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{
- struct db8500_cpufreq_cdev *cooling_dev;
- list_for_each_entry(cooling_dev, &db8500_cpufreq_cdev_list, node)
if (cooling_dev->cdev == platform_get_drvdata(pdev)) {
cpufreq_cooling_unregister(cooling_dev->cdev);
list_del(&cooling_dev->node);
}
- return 0;
+}
There is no need to keep an internal list of cooling devices, and the struct db8500_cpufreq_cdev definition is not needed either. In probe() you can simply call platform_set_drvdata() with the pointer returned by cpufreq_cooling_register(); conversely, in remove() you can simply call cpufreq_cooling_unregister() with the pointer returned by platform_get_drvdata().
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
- return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
- return -ENOSYS;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = {
- { .compatible = "stericsson,db8500-cpufreq-cooling" },
- {},
+}; +#else +#define db8500_cpufreq_cooling_match NULL +#endif
+static struct platform_driver db8500_cpufreq_cooling_driver = {
- .driver = {
.owner = THIS_MODULE,
.name = "db8500-cpufreq-cooling",
.of_match_table = db8500_cpufreq_cooling_match,
- },
- .probe = db8500_cpufreq_cooling_probe,
- .suspend = db8500_cpufreq_cooling_suspend,
- .resume = db8500_cpufreq_cooling_resume,
- .remove = __devexit_p(db8500_cpufreq_cooling_remove),
Since db8500_cpufreq_cooling_remove is not __devexit anymore, __devexit_p() should be removed.
+};
+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 100644 index 0000000..d12bf9b --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,523 @@ +/*
- db8500_thermal.c - db8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hognbo.zhang@linaro.com
s/hognbo/hongbo
[...]
+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 = pzone->trip_tab;
- unsigned int idx = pzone->cur_index;
- unsigned long next_low, next_high;
- if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
idx += 1;
db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING,
next_low, next_high);
dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
- }
- else if (idx == ptrips->num_trips - 1)
As per kernel coding style, the else clause should be in the same line as the closing brace of the if block: } else if ()
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
- 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)
return;
- thermal_zone_device_update(pzone->therm_dev);
- dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n");
+}
+#ifdef CONFIG_OF +static struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev)
+{
- struct db8500_thsens_platform_data *ptrips;
- struct device_node *np = pdev->dev.of_node;
- char prop_name[32];
- const char *tmp_str;
- u32 tmp_data;
- int i, j;
- ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
- if (!ptrips)
return NULL;
- if (of_property_read_u32(np, "num-trips", &tmp_data))
goto err_parse_dt;
- ptrips->num_trips = tmp_data;
There should be a check against tmp_data exceeding the size of the trip_points array.
- for (i = 0; i < ptrips->num_trips; i++) {
sprintf(prop_name, "trip%d-temp", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
ptrips->trip_points[i].temp = tmp_data;
sprintf(prop_name, "trip%d-type", i);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (!strcmp(tmp_str, "active"))
ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
else if (!strcmp(tmp_str, "passive"))
ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
else if (!strcmp(tmp_str, "hot"))
ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
else if (!strcmp(tmp_str, "critical"))
ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
else
goto err_parse_dt;
sprintf(prop_name, "trip%d-cdev-num", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
for (j = 0; j < tmp_data; j++) {
sprintf(prop_name, "trip%d-cdev-name%d", i, j);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
You should check that tmp_data doesn't exceed the size of the cdev_name array, and that the length of the tmp_str string doesn't exceed the size of each element of the cdev_name array.
[...]
+static struct platform_driver db8500_thermal_driver = {
- .driver = {
.owner = THIS_MODULE,
.name = "db8500-thermal",
.of_match_table = db8500_thermal_match,
- },
- .probe = db8500_thermal_probe,
- .suspend = db8500_thermal_suspend,
- .resume = db8500_thermal_resume,
- .remove = __devexit_p(db8500_thermal_remove),
__devexit_p() should be removed.
+};
+module_platform_driver(db8500_thermal_driver);
+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..aad98f8 --- /dev/null +++ b/include/linux/platform_data/db8500_thermal.h @@ -0,0 +1,38 @@ +/*
- db8500_thermal.h - db8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hognbo.zhang@linaro.com
s/hognbo/hongbo
PS: When re-sending the patch, I'd suggest you re-send the entire patch series as V3.
Thanks, Francesco
From: "hongbo.zhang" hongbo.zhang@linaro.com
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- .../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++++++++++++++ arch/arm/boot/dts/dbx5x0.dtsi | 14 +++++ arch/arm/boot/dts/snowball.dts | 31 +++++++++++ arch/arm/mach-ux500/board-mop500.c | 64 ++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt
diff --git a/Documentation/devicetree/bindings/thermal/db8500-thermal.txt b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt new file mode 100644 index 0000000..80d53e6 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal + +** Thermal node properties: + +- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated form PRCMU; +- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH"; +- num-trips : number of total trip points; +- tripN-temp : temperature of trip point N; +- tripN-type : type of trip point N, should be one of "active" "passive" "hot" "critical"; +- tripN-cdev-num : number of the cooling devices which can be bound to trip point N; +- tripN-cdev-nameM : name of the No. M cooling device of trip point N; + +Usually the num-trips and tripN-*** are seperated in board related dts files. + +Example: +thermal@801573c0 { + compatible = "stericsson,db8500-thermal"; + reg = <0x801573c0 0x40>; + interrupts = <21 0x4>, <22 0x4>; + interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH"; + + num-trips = <3>; + + trip0-temp = <70000>; + trip0-type = "active"; + trip0-cdev-num = <1>; + trip0-cdev-name0 = "thermal-cpufreq-0"; + + trip1-temp = <75000>; + trip1-type = "active"; + trip1-cdev-num = <2>; + trip1-cdev-name0 = "thermal-cpufreq-0"; + trip1-cdev-name1 = "thermal-fan"; + + trip2-temp = <85000>; + trip2-type = "critical"; + trip2-cdev-num = <0>; +} diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi index 748ba7a..949edc2 100644 --- a/arch/arm/boot/dts/dbx5x0.dtsi +++ b/arch/arm/boot/dts/dbx5x0.dtsi @@ -203,6 +203,14 @@ reg = <0x80157450 0xC>; };
+ thermal@801573c0 { + compatible = "stericsson,db8500-thermal"; + reg = <0x801573c0 0x40>; + interrupts = <21 0x4>, <22 0x4>; + interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH"; + status = "disabled"; + }; + db8500-prcmu-regulators { compatible = "stericsson,db8500-prcmu-regulator";
@@ -645,5 +653,11 @@ ranges = <0 0x50000000 0x4000000>; status = "disabled"; }; + + cpufreq-cooling { + compatible = "stericsson,db8500-cpufreq-cooling"; + status = "disabled"; + }; + }; }; diff --git a/arch/arm/boot/dts/snowball.dts b/arch/arm/boot/dts/snowball.dts index 702c0ba..c6f85f0 100644 --- a/arch/arm/boot/dts/snowball.dts +++ b/arch/arm/boot/dts/snowball.dts @@ -99,6 +99,33 @@ status = "okay"; };
+ prcmu@80157000 { + thermal@801573c0 { + num-trips = <4>; + + trip0-temp = <70000>; + trip0-type = "active"; + trip0-cdev-num = <1>; + trip0-cdev-name0 = "thermal-cpufreq-0"; + + trip1-temp = <75000>; + trip1-type = "active"; + trip1-cdev-num = <1>; + trip1-cdev-name0 = "thermal-cpufreq-0"; + + trip2-temp = <80000>; + trip2-type = "active"; + trip2-cdev-num = <1>; + trip2-cdev-name0 = "thermal-cpufreq-0"; + + trip3-temp = <85000>; + trip3-type = "critical"; + trip3-cdev-num = <0>; + + status = "okay"; + }; + }; + external-bus@50000000 { status = "okay";
@@ -183,5 +210,9 @@ reg = <0x33>; }; }; + + cpufreq-cooling { + status = "okay"; + }; }; }; diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 416d436..b03216b 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -16,6 +16,7 @@ #include <linux/io.h> #include <linux/i2c.h> #include <linux/platform_data/i2c-nomadik.h> +#include <linux/platform_data/db8500_thermal.h> #include <linux/gpio.h> #include <linux/amba/bus.h> #include <linux/amba/pl022.h> @@ -229,6 +230,67 @@ static struct ab8500_platform_data ab8500_platdata = { };
/* + * Thermal Sensor + */ + +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_thsens_platform_data db8500_thsens_data = { + .trip_points[0] = { + .temp = 70000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[1] = { + .temp = 75000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[2] = { + .temp = 80000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[3] = { + .temp = 85000, + .type = THERMAL_TRIP_CRITICAL, + }, + .num_trips = 4, +}; + +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, + }, +}; + +static struct platform_device u8500_cpufreq_cooling_device = { + .name = "db8500-cpufreq-cooling", +}; + +/* * TPS61052 */
@@ -583,6 +645,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_key_dev, &snowball_sbnet_dev, &snowball_gpio_en_3v3_regulator_dev, + &u8500_thsens_device, + &u8500_cpufreq_cooling_device, };
static void __init mop500_init_machine(void)
On Wed, 2012-10-24 at 19:58 +0800, hongbo.zhang wrote:
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Just a trivial note:
diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c
@@ -229,6 +230,67 @@ static struct ab8500_platform_data ab8500_platdata = { }; /*
- Thermal Sensor
- */
+static struct resource db8500_thsens_resources[] = {
should there be a const in any of these?
+static struct db8500_thsens_platform_data db8500_thsens_data = {
[]
+static struct platform_device u8500_thsens_device = {
[]
+static struct platform_device u8500_cpufreq_cooling_device = {
On 24 October 2012 22:32, Joe Perches joe@perches.com wrote:
On Wed, 2012-10-24 at 19:58 +0800, hongbo.zhang wrote:
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Just a trivial note:
diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c
@@ -229,6 +230,67 @@ static struct ab8500_platform_data ab8500_platdata = { };
/*
- Thermal Sensor
- */
+static struct resource db8500_thsens_resources[] = {
should there be a const in any of these?
There will be warnings from gcc: warning: initialization discards 'const' qualifier from pointer target type [enabled by default]
+static struct db8500_thsens_platform_data db8500_thsens_data = {
[]
+static struct platform_device u8500_thsens_device = {
[]
+static struct platform_device u8500_cpufreq_cooling_device = {
On 24 October 2012 17:28, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
.../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++++++++++++++
It must be included in patch 5/6
arch/arm/boot/dts/dbx5x0.dtsi | 14 +++++ arch/arm/boot/dts/snowball.dts | 31 +++++++++++ arch/arm/mach-ux500/board-mop500.c | 64 ++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt
diff --git a/Documentation/devicetree/bindings/thermal/db8500-thermal.txt b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt new file mode 100644 index 0000000..80d53e6 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal
+** Thermal node properties:
+- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated form PRCMU; +- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH"; +- num-trips : number of total trip points; +- tripN-temp : temperature of trip point N; +- tripN-type : type of trip point N, should be one of "active" "passive" "hot" "critical"; +- tripN-cdev-num : number of the cooling devices which can be bound to trip point N; +- tripN-cdev-nameM : name of the No. M cooling device of trip point N;
+Usually the num-trips and tripN-*** are seperated in board related dts files.
s/seperated/separated
+Example: +thermal@801573c0 {
compatible = "stericsson,db8500-thermal";
reg = <0x801573c0 0x40>;
interrupts = <21 0x4>, <22 0x4>;
interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
num-trips = <3>;
trip0-temp = <70000>;
trip0-type = "active";
trip0-cdev-num = <1>;
trip0-cdev-name0 = "thermal-cpufreq-0";
trip1-temp = <75000>;
trip1-type = "active";
trip1-cdev-num = <2>;
trip1-cdev-name0 = "thermal-cpufreq-0";
trip1-cdev-name1 = "thermal-fan";
trip2-temp = <85000>;
trip2-type = "critical";
trip2-cdev-num = <0>;
+} diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi index 748ba7a..949edc2 100644 --- a/arch/arm/boot/dts/dbx5x0.dtsi +++ b/arch/arm/boot/dts/dbx5x0.dtsi @@ -203,6 +203,14 @@ reg = <0x80157450 0xC>; };
thermal@801573c0 {
compatible = "stericsson,db8500-thermal";
reg = <0x801573c0 0x40>;
interrupts = <21 0x4>, <22 0x4>;
interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
status = "disabled";
};
db8500-prcmu-regulators { compatible = "stericsson,db8500-prcmu-regulator";
@@ -645,5 +653,11 @@ ranges = <0 0x50000000 0x4000000>; status = "disabled"; };
cpufreq-cooling {
compatible = "stericsson,db8500-cpufreq-cooling";
status = "disabled";
};
};
}; diff --git a/arch/arm/boot/dts/snowball.dts b/arch/arm/boot/dts/snowball.dts index 702c0ba..c6f85f0 100644 --- a/arch/arm/boot/dts/snowball.dts +++ b/arch/arm/boot/dts/snowball.dts @@ -99,6 +99,33 @@ status = "okay"; };
prcmu@80157000 {
thermal@801573c0 {
num-trips = <4>;
trip0-temp = <70000>;
trip0-type = "active";
trip0-cdev-num = <1>;
trip0-cdev-name0 = "thermal-cpufreq-0";
trip1-temp = <75000>;
trip1-type = "active";
trip1-cdev-num = <1>;
trip1-cdev-name0 = "thermal-cpufreq-0";
trip2-temp = <80000>;
trip2-type = "active";
trip2-cdev-num = <1>;
trip2-cdev-name0 = "thermal-cpufreq-0";
trip3-temp = <85000>;
trip3-type = "critical";
trip3-cdev-num = <0>;
status = "okay";
};
};
external-bus@50000000 { status = "okay";
@@ -183,5 +210,9 @@ reg = <0x33>; }; };
cpufreq-cooling {
status = "okay";
}; };
}; diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 416d436..b03216b 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -16,6 +16,7 @@ #include <linux/io.h> #include <linux/i2c.h> #include <linux/platform_data/i2c-nomadik.h> +#include <linux/platform_data/db8500_thermal.h> #include <linux/gpio.h> #include <linux/amba/bus.h> #include <linux/amba/pl022.h> @@ -229,6 +230,67 @@ static struct ab8500_platform_data ab8500_platdata = { };
/*
- Thermal Sensor
- */
+static struct resource db8500_thsens_resources[] = {
{
.name = "IRQ_HOTMON_LOW",
.start = IRQ_PRCMU_HOTMON_LOW,
.end = IRQ_PRCMU_HOTMON_LOW,
.flags = IORESOURCE_IRQ,
},
{
I prefer }, {
.name = "IRQ_HOTMON_HIGH",
.start = IRQ_PRCMU_HOTMON_HIGH,
.end = IRQ_PRCMU_HOTMON_HIGH,
.flags = IORESOURCE_IRQ,
},
+};
+static struct db8500_thsens_platform_data db8500_thsens_data = {
.trip_points[0] = {
.temp = 70000,
.type = THERMAL_TRIP_ACTIVE,
.cdev_name = {
[0] = "thermal-cpufreq-0",
},
},
.trip_points[1] = {
.temp = 75000,
.type = THERMAL_TRIP_ACTIVE,
.cdev_name = {
[0] = "thermal-cpufreq-0",
},
},
.trip_points[2] = {
.temp = 80000,
.type = THERMAL_TRIP_ACTIVE,
.cdev_name = {
[0] = "thermal-cpufreq-0",
},
},
.trip_points[3] = {
.temp = 85000,
.type = THERMAL_TRIP_CRITICAL,
},
.num_trips = 4,
+};
+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,
},
+};
+static struct platform_device u8500_cpufreq_cooling_device = {
.name = "db8500-cpufreq-cooling",
+};
+/*
- TPS61052
*/
@@ -583,6 +645,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_key_dev, &snowball_sbnet_dev, &snowball_gpio_en_3v3_regulator_dev,
&u8500_thsens_device,
&u8500_cpufreq_cooling_device,
};
static void __init mop500_init_machine(void)
1.7.11.3
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
[...]
/*
- Thermal Sensor
- */
+static struct resource db8500_thsens_resources[] = {
{
.name = "IRQ_HOTMON_LOW",
.start = IRQ_PRCMU_HOTMON_LOW,
.end = IRQ_PRCMU_HOTMON_LOW,
.flags = IORESOURCE_IRQ,
},
{
I prefer }, {
I just follow the style of all the other definitions, let's keep a uniform style.
.name = "IRQ_HOTMON_HIGH",
.start = IRQ_PRCMU_HOTMON_HIGH,
.end = IRQ_PRCMU_HOTMON_HIGH,
.flags = IORESOURCE_IRQ,
},
+};
[...]
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
From: "hongbo.zhang" hongbo.zhang@linaro.com
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- arch/arm/boot/dts/dbx5x0.dtsi | 14 +++++++++ arch/arm/boot/dts/snowball.dts | 31 ++++++++++++++++++ arch/arm/configs/u8500_defconfig | 4 +++ arch/arm/mach-ux500/board-mop500.c | 64 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+)
diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi index 748ba7a..949edc2 100644 --- a/arch/arm/boot/dts/dbx5x0.dtsi +++ b/arch/arm/boot/dts/dbx5x0.dtsi @@ -203,6 +203,14 @@ reg = <0x80157450 0xC>; };
+ thermal@801573c0 { + compatible = "stericsson,db8500-thermal"; + reg = <0x801573c0 0x40>; + interrupts = <21 0x4>, <22 0x4>; + interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH"; + status = "disabled"; + }; + db8500-prcmu-regulators { compatible = "stericsson,db8500-prcmu-regulator";
@@ -645,5 +653,11 @@ ranges = <0 0x50000000 0x4000000>; status = "disabled"; }; + + cpufreq-cooling { + compatible = "stericsson,db8500-cpufreq-cooling"; + status = "disabled"; + }; + }; }; diff --git a/arch/arm/boot/dts/snowball.dts b/arch/arm/boot/dts/snowball.dts index fce4a00..787d992 100644 --- a/arch/arm/boot/dts/snowball.dts +++ b/arch/arm/boot/dts/snowball.dts @@ -91,6 +91,33 @@ status = "okay"; };
+ prcmu@80157000 { + thermal@801573c0 { + num-trips = <4>; + + trip0-temp = <70000>; + trip0-type = "active"; + trip0-cdev-num = <1>; + trip0-cdev-name0 = "thermal-cpufreq-0"; + + trip1-temp = <75000>; + trip1-type = "active"; + trip1-cdev-num = <1>; + trip1-cdev-name0 = "thermal-cpufreq-0"; + + trip2-temp = <80000>; + trip2-type = "active"; + trip2-cdev-num = <1>; + trip2-cdev-name0 = "thermal-cpufreq-0"; + + trip3-temp = <85000>; + trip3-type = "critical"; + trip3-cdev-num = <0>; + + status = "okay"; + }; + }; + external-bus@50000000 { status = "okay";
@@ -176,5 +203,9 @@ reg = <0x33>; }; }; + + cpufreq-cooling { + status = "okay"; + }; }; }; diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index cc5e7a8..34918c4 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 a534d88..71f2a9c 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -16,6 +16,7 @@ #include <linux/io.h> #include <linux/i2c.h> #include <linux/platform_data/i2c-nomadik.h> +#include <linux/platform_data/db8500_thermal.h> #include <linux/gpio.h> #include <linux/amba/bus.h> #include <linux/amba/pl022.h> @@ -212,6 +213,67 @@ static struct ab8500_platform_data ab8500_platdata = { };
/* + * Thermal Sensor + */ + +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_thsens_platform_data db8500_thsens_data = { + .trip_points[0] = { + .temp = 70000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[1] = { + .temp = 75000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[2] = { + .temp = 80000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[3] = { + .temp = 85000, + .type = THERMAL_TRIP_CRITICAL, + }, + .num_trips = 4, +}; + +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, + }, +}; + +static struct platform_device u8500_cpufreq_cooling_device = { + .name = "db8500-cpufreq-cooling", +}; + +/* * TPS61052 */
@@ -586,6 +648,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_led_dev, &snowball_key_dev, &snowball_sbnet_dev, + &u8500_thsens_device, + &u8500_cpufreq_cooling_device, };
static void __init mop500_init_machine(void)
From: "hongbo.zhang" hongbo.zhang@linaro.com
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- arch/arm/boot/dts/dbx5x0.dtsi | 14 +++++++++ arch/arm/boot/dts/snowball.dts | 31 ++++++++++++++++++ arch/arm/configs/u8500_defconfig | 4 +++ arch/arm/mach-ux500/board-mop500.c | 64 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+)
diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi index 748ba7a..949edc2 100644 --- a/arch/arm/boot/dts/dbx5x0.dtsi +++ b/arch/arm/boot/dts/dbx5x0.dtsi @@ -203,6 +203,14 @@ reg = <0x80157450 0xC>; };
+ thermal@801573c0 { + compatible = "stericsson,db8500-thermal"; + reg = <0x801573c0 0x40>; + interrupts = <21 0x4>, <22 0x4>; + interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH"; + status = "disabled"; + }; + db8500-prcmu-regulators { compatible = "stericsson,db8500-prcmu-regulator";
@@ -645,5 +653,11 @@ ranges = <0 0x50000000 0x4000000>; status = "disabled"; }; + + cpufreq-cooling { + compatible = "stericsson,db8500-cpufreq-cooling"; + status = "disabled"; + }; + }; }; diff --git a/arch/arm/boot/dts/snowball.dts b/arch/arm/boot/dts/snowball.dts index 702c0ba..c6f85f0 100644 --- a/arch/arm/boot/dts/snowball.dts +++ b/arch/arm/boot/dts/snowball.dts @@ -99,6 +99,33 @@ status = "okay"; };
+ prcmu@80157000 { + thermal@801573c0 { + num-trips = <4>; + + trip0-temp = <70000>; + trip0-type = "active"; + trip0-cdev-num = <1>; + trip0-cdev-name0 = "thermal-cpufreq-0"; + + trip1-temp = <75000>; + trip1-type = "active"; + trip1-cdev-num = <1>; + trip1-cdev-name0 = "thermal-cpufreq-0"; + + trip2-temp = <80000>; + trip2-type = "active"; + trip2-cdev-num = <1>; + trip2-cdev-name0 = "thermal-cpufreq-0"; + + trip3-temp = <85000>; + trip3-type = "critical"; + trip3-cdev-num = <0>; + + status = "okay"; + }; + }; + external-bus@50000000 { status = "okay";
@@ -183,5 +210,9 @@ reg = <0x33>; }; }; + + cpufreq-cooling { + status = "okay"; + }; }; }; diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index cc5e7a8..34918c4 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 416d436..b03216b 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -16,6 +16,7 @@ #include <linux/io.h> #include <linux/i2c.h> #include <linux/platform_data/i2c-nomadik.h> +#include <linux/platform_data/db8500_thermal.h> #include <linux/gpio.h> #include <linux/amba/bus.h> #include <linux/amba/pl022.h> @@ -229,6 +230,67 @@ static struct ab8500_platform_data ab8500_platdata = { };
/* + * Thermal Sensor + */ + +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_thsens_platform_data db8500_thsens_data = { + .trip_points[0] = { + .temp = 70000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[1] = { + .temp = 75000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[2] = { + .temp = 80000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[3] = { + .temp = 85000, + .type = THERMAL_TRIP_CRITICAL, + }, + .num_trips = 4, +}; + +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, + }, +}; + +static struct platform_device u8500_cpufreq_cooling_device = { + .name = "db8500-cpufreq-cooling", +}; + +/* * TPS61052 */
@@ -583,6 +645,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_key_dev, &snowball_sbnet_dev, &snowball_gpio_en_3v3_regulator_dev, + &u8500_thsens_device, + &u8500_cpufreq_cooling_device, };
static void __init mop500_init_machine(void)
From: "hongbo.zhang" hongbo.zhang@linaro.com
V2->V3 Changes:
1. Moved the previous "[PATCH V2 2/6] Thermal: make sure cpufreq cooling register after cpufreq driver" from generic cpu cooling layer to ST-Ericsson driver, thus only 5 patches in total in V3 patch set.
2. Update ST-Ericsson thermal driver due to review comments from Viresh Kumar and Francesco Lavra.
hongbo.zhang (5): Thermal: add indent for code alignment. Thermal: fix bug of counting cpu frequencies. Thermal: Remove the cooling_cpufreq_list. Thermal: Add ST-Ericsson DB8500 thermal driver. Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.
.../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ arch/arm/boot/dts/dbx5x0.dtsi | 14 + arch/arm/boot/dts/snowball.dts | 31 ++ arch/arm/configs/u8500_defconfig | 4 + arch/arm/mach-ux500/board-mop500.c | 64 +++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/cpu_cooling.c | 103 +--- drivers/thermal/db8500_cpufreq_cooling.c | 108 +++++ drivers/thermal/db8500_thermal.c | 531 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 38 ++ 11 files changed, 878 insertions(+), 77 deletions(-) create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
From: "hongbo.zhang" hongbo.zhang@linaro.com
The curly bracket should be aligned with corresponding if else statements.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/thermal/cpu_cooling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index cc1c930..b6b4c2a 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -369,7 +369,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (min != policy.cpuinfo.min_freq || max != policy.cpuinfo.max_freq) return ERR_PTR(-EINVAL); -} + } } cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device), GFP_KERNEL);
On Tue, 2012-10-30 at 17:48 +0100, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
The curly bracket should be aligned with corresponding if else statements.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Viresh Kumar viresh.kumar@linaro.org
applied to thermal-next.
thanks, rui
drivers/thermal/cpu_cooling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index cc1c930..b6b4c2a 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -369,7 +369,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (min != policy.cpuinfo.min_freq || max != policy.cpuinfo.max_freq) return ERR_PTR(-EINVAL); -}
} cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device), GFP_KERNEL);}
From: "hongbo.zhang" hongbo.zhang@linaro.com
In the while loop for counting cpu frequencies, if table[i].frequency equals CPUFREQ_ENTRY_INVALID, index i won't be increased, so this leads to an endless loop, what's more the index i cannot be referred as cpu frequencies number if there is CPUFREQ_ENTRY_INVALID case.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Viresh Kumar viresh.kumar@linaro.org Reviewed-by: Amit Daniel Kachhap amit.kachhap@linaro.org --- drivers/thermal/cpu_cooling.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index b6b4c2a..bfd62b7 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -245,6 +245,7 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, struct cpumask *maskPtr; unsigned int cpu; struct cpufreq_frequency_table *table; + unsigned long count = 0;
mutex_lock(&cooling_cpufreq_lock); list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { @@ -263,13 +264,14 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, goto return_get_max_state; }
- while (table[i].frequency != CPUFREQ_TABLE_END) { + for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { if (table[i].frequency == CPUFREQ_ENTRY_INVALID) continue; - i++; + count++; } - if (i > 0) { - *state = --i; + + if (count > 0) { + *state = --count; ret = 0; }
On Tue, 2012-10-30 at 17:48 +0100, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
In the while loop for counting cpu frequencies, if table[i].frequency equals CPUFREQ_ENTRY_INVALID, index i won't be increased, so this leads to an endless loop, what's more the index i cannot be referred as cpu frequencies number if there is CPUFREQ_ENTRY_INVALID case.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Viresh Kumar viresh.kumar@linaro.org Reviewed-by: Amit Daniel Kachhap amit.kachhap@linaro.org
applied to thermal-next.
thanks, rui
drivers/thermal/cpu_cooling.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index b6b4c2a..bfd62b7 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -245,6 +245,7 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, struct cpumask *maskPtr; unsigned int cpu; struct cpufreq_frequency_table *table;
- unsigned long count = 0;
mutex_lock(&cooling_cpufreq_lock); list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { @@ -263,13 +264,14 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, goto return_get_max_state; }
- while (table[i].frequency != CPUFREQ_TABLE_END) {
- for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { if (table[i].frequency == CPUFREQ_ENTRY_INVALID) continue;
i++;
}count++;
- if (i > 0) {
*state = --i;
- if (count > 0) {
ret = 0; }*state = --count;
From: "hongbo.zhang" hongbo.zhang@linaro.com
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Francesco Lavra francescolavra.fl@gmail.com Reviewed-by: Amit Daniel Kachhap amit.kachhap@linaro.org --- drivers/thermal/cpu_cooling.c | 91 +++++++++---------------------------------- 1 file changed, 19 insertions(+), 72 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index bfd62b7..392d57d 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock);
-static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count;
/* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -240,28 +241,18 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - int ret = -EINVAL, i = 0; - struct cpufreq_cooling_device *cpufreq_device; - struct cpumask *maskPtr; + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; + struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0; + int i = 0;
- mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) - break; - } - if (cpufreq_device == NULL) - goto return_get_max_state; - - maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu); if (!table) { *state = 0; - ret = 0; - goto return_get_max_state; + return 0; }
for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { @@ -272,12 +263,10 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
if (count > 0) { *state = --count; - ret = 0; + return 0; }
-return_get_max_state: - mutex_unlock(&cooling_cpufreq_lock); - return ret; + return -EINVAL; }
/** @@ -288,20 +277,10 @@ return_get_max_state: static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { - int ret = -EINVAL; - struct cpufreq_cooling_device *cpufreq_device; + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) { - *state = cpufreq_device->cpufreq_state; - ret = 0; - break; - } - } - mutex_unlock(&cooling_cpufreq_lock); - - return ret; + *state = cpufreq_device->cpufreq_state; + return 0; }
/** @@ -312,22 +291,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { - int ret = -EINVAL; - struct cpufreq_cooling_device *cpufreq_device; + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) { - ret = 0; - break; - } - } - if (!ret) - ret = cpufreq_apply_cooling(cpufreq_device, state); - - mutex_unlock(&cooling_cpufreq_lock); - - return ret; + return cpufreq_apply_cooling(cpufreq_device, state); }
/* Bind cpufreq callbacks to thermal cooling device ops */ @@ -351,14 +317,11 @@ struct thermal_cooling_device *cpufreq_cooling_register( { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL; - unsigned int cpufreq_dev_count = 0, min = 0, max = 0; + unsigned int min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; int ret = 0, i; struct cpufreq_policy policy;
- list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) - cpufreq_dev_count++; - /*Verify that all the clip cpus have same freq_min, freq_max limit*/ for_each_cpu(i, clip_cpus) { /*continue if cpufreq policy not found and not return error*/ @@ -380,9 +343,6 @@ struct thermal_cooling_device *cpufreq_cooling_register(
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
- if (cpufreq_dev_count == 0) - mutex_init(&cooling_cpufreq_lock); - ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); if (ret) { kfree(cpufreq_dev); @@ -401,12 +361,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cpufreq_state = 0; mutex_lock(&cooling_cpufreq_lock); - list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
/* Register the notifier for first cpufreq cooling device */ if (cpufreq_dev_count == 0) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); + cpufreq_dev_count++;
mutex_unlock(&cooling_cpufreq_lock); return cool_dev; @@ -419,33 +379,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) { - struct cpufreq_cooling_device *cpufreq_dev = NULL; - unsigned int cpufreq_dev_count = 0; + struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata;
mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) { - if (cpufreq_dev && cpufreq_dev->cool_dev == cdev) - break; - cpufreq_dev_count++; - } - - if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) { - mutex_unlock(&cooling_cpufreq_lock); - return; - } - - list_del(&cpufreq_dev->node); + cpufreq_dev_count--;
/* Unregister the notifier for the last cpufreq cooling device */ - if (cpufreq_dev_count == 1) { + if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock); + thermal_cooling_device_unregister(cpufreq_dev->cool_dev); release_idr(&cpufreq_idr, cpufreq_dev->id); - if (cpufreq_dev_count == 1) - mutex_destroy(&cooling_cpufreq_lock); kfree(cpufreq_dev); } EXPORT_SYMBOL(cpufreq_cooling_unregister);
On Tue, 2012-10-30 at 17:48 +0100, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Francesco Lavra francescolavra.fl@gmail.com Reviewed-by: Amit Daniel Kachhap amit.kachhap@linaro.org
applied to thermal-next.
thanks, rui
drivers/thermal/cpu_cooling.c | 91 +++++++++---------------------------------- 1 file changed, 19 insertions(+), 72 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index bfd62b7..392d57d 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock); -static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count; /* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -240,28 +241,18 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) {
- int ret = -EINVAL, i = 0;
- struct cpufreq_cooling_device *cpufreq_device;
- struct cpumask *maskPtr;
- struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0;
- int i = 0;
- mutex_lock(&cooling_cpufreq_lock);
- list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev)
break;
- }
- if (cpufreq_device == NULL)
goto return_get_max_state;
- maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu); if (!table) { *state = 0;
ret = 0;
goto return_get_max_state;
}return 0;
for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { @@ -272,12 +263,10 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, if (count > 0) { *state = --count;
ret = 0;
}return 0;
-return_get_max_state:
- mutex_unlock(&cooling_cpufreq_lock);
- return ret;
- return -EINVAL;
} /** @@ -288,20 +277,10 @@ return_get_max_state: static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) {
- int ret = -EINVAL;
- struct cpufreq_cooling_device *cpufreq_device;
- struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock);
- list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
*state = cpufreq_device->cpufreq_state;
ret = 0;
break;
}
- }
- mutex_unlock(&cooling_cpufreq_lock);
- return ret;
- *state = cpufreq_device->cpufreq_state;
- return 0;
} /** @@ -312,22 +291,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) {
- int ret = -EINVAL;
- struct cpufreq_cooling_device *cpufreq_device;
- struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock);
- list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
ret = 0;
break;
}
- }
- if (!ret)
ret = cpufreq_apply_cooling(cpufreq_device, state);
- mutex_unlock(&cooling_cpufreq_lock);
- return ret;
- return cpufreq_apply_cooling(cpufreq_device, state);
} /* Bind cpufreq callbacks to thermal cooling device ops */ @@ -351,14 +317,11 @@ struct thermal_cooling_device *cpufreq_cooling_register( { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL;
- unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
- unsigned int min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; int ret = 0, i; struct cpufreq_policy policy;
- list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
cpufreq_dev_count++;
- /*Verify that all the clip cpus have same freq_min, freq_max limit*/ for_each_cpu(i, clip_cpus) { /*continue if cpufreq policy not found and not return error*/
@@ -380,9 +343,6 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
- if (cpufreq_dev_count == 0)
mutex_init(&cooling_cpufreq_lock);
- ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); if (ret) { kfree(cpufreq_dev);
@@ -401,12 +361,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cpufreq_state = 0; mutex_lock(&cooling_cpufreq_lock);
- list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
/* Register the notifier for first cpufreq cooling device */ if (cpufreq_dev_count == 0) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER);
- cpufreq_dev_count++;
mutex_unlock(&cooling_cpufreq_lock); return cool_dev; @@ -419,33 +379,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) {
- struct cpufreq_cooling_device *cpufreq_dev = NULL;
- unsigned int cpufreq_dev_count = 0;
- struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata;
mutex_lock(&cooling_cpufreq_lock);
- list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
break;
cpufreq_dev_count++;
- }
- if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
mutex_unlock(&cooling_cpufreq_lock);
return;
- }
- list_del(&cpufreq_dev->node);
- cpufreq_dev_count--;
/* Unregister the notifier for the last cpufreq cooling device */
- if (cpufreq_dev_count == 1) {
- if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock);
- thermal_cooling_device_unregister(cpufreq_dev->cool_dev); release_idr(&cpufreq_idr, cpufreq_dev->id);
- if (cpufreq_dev_count == 1)
kfree(cpufreq_dev);mutex_destroy(&cooling_cpufreq_lock);
} EXPORT_SYMBOL(cpufreq_cooling_unregister);
On 7 November 2012 14:54, Zhang Rui rui.zhang@intel.com wrote:
On Tue, 2012-10-30 at 17:48 +0100, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
Problem of using this list is that the cpufreq_get_max_state callback will be called when register cooling device by thermal_cooling_device_register, but this list isn't ready at this moment. What's more, there is no need to maintain such a list, we can get cpufreq_cooling_device instance by the private thermal_cooling_device.devdata.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com Reviewed-by: Francesco Lavra francescolavra.fl@gmail.com Reviewed-by: Amit Daniel Kachhap amit.kachhap@linaro.org
applied to thermal-next.
Thanks. I have sent the other updated 4/5 5/5 patches with Reviewed-by added in a new thread, please have a look there.
thanks, rui
drivers/thermal/cpu_cooling.c | 91 +++++++++---------------------------------- 1 file changed, 19 insertions(+), 72 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index bfd62b7..392d57d 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,8 +58,9 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock);
-static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count;
/* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL @@ -240,28 +241,18 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) {
int ret = -EINVAL, i = 0;
struct cpufreq_cooling_device *cpufreq_device;
struct cpumask *maskPtr;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; unsigned int cpu; struct cpufreq_frequency_table *table; unsigned long count = 0;
int i = 0;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev)
break;
}
if (cpufreq_device == NULL)
goto return_get_max_state;
maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu); if (!table) { *state = 0;
ret = 0;
goto return_get_max_state;
return 0; } for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
@@ -272,12 +263,10 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
if (count > 0) { *state = --count;
ret = 0;
return 0; }
-return_get_max_state:
mutex_unlock(&cooling_cpufreq_lock);
return ret;
return -EINVAL;
}
/** @@ -288,20 +277,10 @@ return_get_max_state: static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) {
int ret = -EINVAL;
struct cpufreq_cooling_device *cpufreq_device;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
*state = cpufreq_device->cpufreq_state;
ret = 0;
break;
}
}
mutex_unlock(&cooling_cpufreq_lock);
return ret;
*state = cpufreq_device->cpufreq_state;
return 0;
}
/** @@ -312,22 +291,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) {
int ret = -EINVAL;
struct cpufreq_cooling_device *cpufreq_device;
struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
ret = 0;
break;
}
}
if (!ret)
ret = cpufreq_apply_cooling(cpufreq_device, state);
mutex_unlock(&cooling_cpufreq_lock);
return ret;
return cpufreq_apply_cooling(cpufreq_device, state);
}
/* Bind cpufreq callbacks to thermal cooling device ops */ @@ -351,14 +317,11 @@ struct thermal_cooling_device *cpufreq_cooling_register( { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL;
unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
unsigned int min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; int ret = 0, i; struct cpufreq_policy policy;
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
cpufreq_dev_count++;
/*Verify that all the clip cpus have same freq_min, freq_max limit*/ for_each_cpu(i, clip_cpus) { /*continue if cpufreq policy not found and not return error*/
@@ -380,9 +343,6 @@ struct thermal_cooling_device *cpufreq_cooling_register(
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
if (cpufreq_dev_count == 0)
mutex_init(&cooling_cpufreq_lock);
ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); if (ret) { kfree(cpufreq_dev);
@@ -401,12 +361,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cpufreq_state = 0; mutex_lock(&cooling_cpufreq_lock);
list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list); /* Register the notifier for first cpufreq cooling device */ if (cpufreq_dev_count == 0) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER);
cpufreq_dev_count++; mutex_unlock(&cooling_cpufreq_lock); return cool_dev;
@@ -419,33 +379,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) {
struct cpufreq_cooling_device *cpufreq_dev = NULL;
unsigned int cpufreq_dev_count = 0;
struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata; mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
break;
cpufreq_dev_count++;
}
if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
mutex_unlock(&cooling_cpufreq_lock);
return;
}
list_del(&cpufreq_dev->node);
cpufreq_dev_count--; /* Unregister the notifier for the last cpufreq cooling device */
if (cpufreq_dev_count == 1) {
if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock);
thermal_cooling_device_unregister(cpufreq_dev->cool_dev); release_idr(&cpufreq_idr, cpufreq_dev->id);
if (cpufreq_dev_count == 1)
mutex_destroy(&cooling_cpufreq_lock); kfree(cpufreq_dev);
} EXPORT_SYMBOL(cpufreq_cooling_unregister);
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- .../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 108 +++++ drivers/thermal/db8500_thermal.c | 531 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 38 ++ 6 files changed, 739 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/Documentation/devicetree/bindings/thermal/db8500-thermal.txt b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt new file mode 100644 index 0000000..cab6916 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal + +** Thermal node properties: + +- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated from PRCMU; +- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH"; +- num-trips : number of total trip points; +- tripN-temp : temperature of trip point N, should be in ascending order; +- tripN-type : type of trip point N, should be one of "active" "passive" "hot" "critical"; +- tripN-cdev-num : number of the cooling devices which can be bound to trip point N; +- tripN-cdev-nameM : name of the No. M cooling device of trip point N; + +Usually the num-trips and tripN-*** are separated in board related dts files. + +Example: +thermal@801573c0 { + compatible = "stericsson,db8500-thermal"; + reg = <0x801573c0 0x40>; + interrupts = <21 0x4>, <22 0x4>; + interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH"; + + num-trips = <3>; + + trip0-temp = <70000>; + trip0-type = "active"; + trip0-cdev-num = <1>; + trip0-cdev-name0 = "thermal-cpufreq-0"; + + trip1-temp = <75000>; + trip1-type = "active"; + trip1-cdev-num = <2>; + trip1-cdev-name0 = "thermal-cpufreq-0"; + trip1-cdev-name1 = "thermal-fan"; + + trip2-temp = <85000>; + trip2-type = "critical"; + trip2-cdev-num = <0>; +} diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e1cb6bd..54c8fd0 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -31,6 +31,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL + bool "DB8500 thermal management" + depends on THERMAL + default y + help + Adds DB8500 thermal management implementation according to the thermal + management framework. A thermal zone with several trip points will be + created. Cooling devices can be bound to the trip points to cool this + thermal zone if trip points reached. + +config DB8500_CPUFREQ_COOLING + tristate "DB8500 cpufreq cooling" + depends on CPU_THERMAL + default y + help + Adds DB8500 cpufreq cooling devices, and these cooling devices can be + bound to thermal zone trip points. When a trip point reached, the + bound cpufreq cooling device turns active to set CPU frequency low to + cool down the CPU. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..4cf8e72 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,108 @@ +/* + * db8500_cpufreq_cooling.c - DB8500 cpufreq works as cooling device. + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang hongbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +static int db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{ + struct thermal_cooling_device *cdev; + struct cpumask mask_val; + + /* make sure cpufreq driver has been initialized */ + if (!cpufreq_frequency_get_table(0)) + return -EPROBE_DEFER; + + cpumask_set_cpu(0, &mask_val); + cdev = cpufreq_cooling_register(&mask_val); + + if (IS_ERR_OR_NULL(cdev)) { + dev_err(&pdev->dev, "Failed to register cooling device\n"); + return PTR_ERR(cdev); + } + + platform_set_drvdata(pdev, cdev); + + dev_info(&pdev->dev, "Cooling device registered: %s\n", cdev->type); + + return 0; +} + +static int db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{ + struct thermal_cooling_device *cdev = platform_get_drvdata(pdev); + + cpufreq_cooling_unregister(cdev); + + return 0; +} + +static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return -ENOSYS; +} + +static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{ + return -ENOSYS; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = { + { .compatible = "stericsson,db8500-cpufreq-cooling" }, + {}, +}; +#else +#define db8500_cpufreq_cooling_match NULL +#endif + +static struct platform_driver db8500_cpufreq_cooling_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-cpufreq-cooling", + .of_match_table = db8500_cpufreq_cooling_match, + }, + .probe = db8500_cpufreq_cooling_probe, + .suspend = db8500_cpufreq_cooling_suspend, + .resume = db8500_cpufreq_cooling_resume, + .remove = 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 100644 index 0000000..023f7b4 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,531 @@ +/* + * db8500_thermal.c - DB8500 Thermal Management Implementation + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang hongbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/interrupt.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/db8500_thermal.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0 + +struct db8500_thermal_zone { + struct thermal_zone_device *therm_dev; + struct mutex th_lock; + struct work_struct therm_work; + struct db8500_thsens_platform_data *trip_tab; + enum thermal_device_mode mode; + enum thermal_trend trend; + unsigned long cur_temp_pseudo; + unsigned int cur_index; +}; + +/* Local function to check if thermal zone matches cooling devices */ +static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev, + struct db8500_trip_point *trip_points) +{ + int i; + char *cdev_name; + + if (!strlen(cdev->type)) + return -EINVAL; + + for (i = 0; i < COOLING_DEV_MAX; i++) { + cdev_name = trip_points->cdev_name[i]; + if (!strcmp(cdev_name, cdev->type)) + return 0; + } + + return -ENODEV; +} + +/* Callback to bind cooling device to thermal zone */ +static int db8500_cdev_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + unsigned long max_state, upper, lower; + int i, ret = -EINVAL; + + for (i = 0; i < ptrips->num_trips; i++) { + if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) + continue; + + cdev->ops->get_max_state(cdev, &max_state); + lower = upper = (i > max_state) ? max_state : i; + + ret = thermal_zone_bind_cooling_device(thermal, i, cdev, + upper, lower); + + dev_info(&cdev->device, "%s bind to %d: %d-%s\n", + cdev->type, i, ret, ret ? "fail" : "succeed"); + } + + return ret; +} + +/* Callback to unbind cooling device from thermal zone */ +static int db8500_cdev_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + int i, ret = -EINVAL; + + for (i = 0; i < ptrips->num_trips; i++) { + if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) + continue; + + ret = thermal_zone_unbind_cooling_device(thermal, i, cdev); + + dev_info(&cdev->device, "%s unbind: %s\n", + cdev->type, ret ? "fail" : "succeed"); + } + + return ret; +} + +/* Callback to get current temperature */ +static int db8500_sys_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + /* + * TODO: There is no PRCMU interface to get temperature data currently, + * so a pseudo temperature is returned , it works for thermal framework + * and this will be fixed when the PRCMU interface is available. + */ + *temp = pzone->cur_temp_pseudo; + + return 0; +} + +/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + *trend = pzone->trend; + + return 0; +} + +/* Callback to get thermal zone mode */ +static int db8500_sys_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + mutex_lock(&pzone->th_lock); + *mode = pzone->mode; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to set thermal zone mode */ +static int db8500_sys_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + mutex_lock(&pzone->th_lock); + + pzone->mode = mode; + if (mode == THERMAL_DEVICE_ENABLED) + schedule_work(&pzone->therm_work); + + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to get trip point type */ +static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *type = ptrips->trip_points[trip].type; + + return 0; +} + +/* Callback to get trip point temperature */ +static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *temp = ptrips->trip_points[trip].temp; + + return 0; +} + +/* Callback to get critical trip point temperature */ +static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + int i; + + 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_trend = db8500_sys_get_trend, + .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 void db8500_thermal_update_config(struct db8500_thermal_zone *pzone, + unsigned int idx, enum thermal_trend trend, + unsigned long next_low, unsigned long next_high) +{ + pzone->cur_index = idx; + pzone->cur_temp_pseudo = (next_low + next_high)/2; + pzone->trend = trend; + + prcmu_stop_temp_sense(); + prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); + prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); +} + +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 = pzone->trip_tab; + unsigned int idx = pzone->cur_index; + unsigned long next_low, next_high; + + if (unlikely(idx == 0)) + /* Meaningless for thermal management, ignoring it */ + return IRQ_HANDLED; + + if (idx == 1) { + next_high = ptrips->trip_points[0].temp; + next_low = PRCMU_DEFAULT_LOW_TEMP; + } else { + next_high = ptrips->trip_points[idx-1].temp; + next_low = ptrips->trip_points[idx-2].temp; + } + idx -= 1; + + db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING, + next_low, next_high); + + dev_dbg(&pzone->therm_dev->device, + "PRCMU set max %ld, 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 = pzone->trip_tab; + unsigned int idx = pzone->cur_index; + unsigned long next_low, next_high; + + if (idx < ptrips->num_trips - 1) { + next_high = ptrips->trip_points[idx+1].temp; + next_low = ptrips->trip_points[idx].temp; + idx += 1; + + db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING, + next_low, next_high); + + dev_dbg(&pzone->therm_dev->device, + "PRCMU set max %ld, min %ld\n", next_high, next_low); + } else if (idx == ptrips->num_trips - 1) + pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1; + + 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) + return; + + thermal_zone_device_update(pzone->therm_dev); + dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); +} + +#ifdef CONFIG_OF +static struct db8500_thsens_platform_data* + db8500_thermal_parse_dt(struct platform_device *pdev) +{ + struct db8500_thsens_platform_data *ptrips; + struct device_node *np = pdev->dev.of_node; + char prop_name[32]; + const char *tmp_str; + u32 tmp_data; + int i, j; + + ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL); + if (!ptrips) + return NULL; + + if (of_property_read_u32(np, "num-trips", &tmp_data)) + goto err_parse_dt; + + if (tmp_data > THERMAL_MAX_TRIPS) + goto err_parse_dt; + + ptrips->num_trips = tmp_data; + + for (i = 0; i < ptrips->num_trips; i++) { + sprintf(prop_name, "trip%d-temp", i); + if (of_property_read_u32(np, prop_name, &tmp_data)) + goto err_parse_dt; + + ptrips->trip_points[i].temp = tmp_data; + + sprintf(prop_name, "trip%d-type", i); + if (of_property_read_string(np, prop_name, &tmp_str)) + goto err_parse_dt; + + if (!strcmp(tmp_str, "active")) + ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE; + else if (!strcmp(tmp_str, "passive")) + ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE; + else if (!strcmp(tmp_str, "hot")) + ptrips->trip_points[i].type = THERMAL_TRIP_HOT; + else if (!strcmp(tmp_str, "critical")) + ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL; + else + goto err_parse_dt; + + sprintf(prop_name, "trip%d-cdev-num", i); + if (of_property_read_u32(np, prop_name, &tmp_data)) + goto err_parse_dt; + + if (tmp_data > COOLING_DEV_MAX) + goto err_parse_dt; + + for (j = 0; j < tmp_data; j++) { + sprintf(prop_name, "trip%d-cdev-name%d", i, j); + if (of_property_read_string(np, prop_name, &tmp_str)) + goto err_parse_dt; + + if (strlen(tmp_str) > THERMAL_NAME_LENGTH) + goto err_parse_dt; + + strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str); + } + } + return ptrips; + +err_parse_dt: + dev_err(&pdev->dev, "Parsing device tree data error.\n"); + return NULL; +} +#else +static inline struct db8500_thsens_platform_data* + db8500_thermal_parse_dt(struct platform_device *pdev) +{ + return NULL; +} +#endif + +static int db8500_thermal_probe(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = NULL; + struct db8500_thsens_platform_data *ptrips = NULL; + struct device_node *np = pdev->dev.of_node; + int low_irq, high_irq, ret = 0; + unsigned long dft_low, dft_high; + + if (np) + ptrips = db8500_thermal_parse_dt(pdev); + else + ptrips = dev_get_platdata(&pdev->dev); + + if (!ptrips) + return -EINVAL; + + pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL); + if (!pzone) + return -ENOMEM; + + mutex_init(&pzone->th_lock); + mutex_lock(&pzone->th_lock); + + pzone->mode = THERMAL_DEVICE_DISABLED; + pzone->trip_tab = ptrips; + + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, + dft_low, dft_high); + + INIT_WORK(&pzone->therm_work, db8500_thermal_work); + + low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (low_irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n"); + return low_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL, + prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "dbx500_temp_low", pzone); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate temp low irq.\n"); + return ret; + } + + high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (high_irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n"); + return high_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL, + prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "dbx500_temp_high", pzone); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate temp high irq.\n"); + return ret; + } + + pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone", + ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0); + + if (IS_ERR_OR_NULL(pzone->therm_dev)) { + dev_err(&pdev->dev, "Register thermal zone device failed.\n"); + return PTR_ERR(pzone->therm_dev); + } + dev_info(&pdev->dev, "Thermal zone device registered.\n"); + + platform_set_drvdata(pdev, pzone); + pzone->mode = THERMAL_DEVICE_ENABLED; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +static int db8500_thermal_remove(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + + thermal_zone_device_unregister(pzone->therm_dev); + cancel_work_sync(&pzone->therm_work); + mutex_destroy(&pzone->th_lock); + + return 0; +} + +static int db8500_thermal_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + + flush_work_sync(&pzone->therm_work); + prcmu_stop_temp_sense(); + + return 0; +} + +static int db8500_thermal_resume(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + unsigned long dft_low, dft_high; + + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, + dft_low, dft_high); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = { + { .compatible = "stericsson,db8500-thermal" }, + {}, +}; +#else +#define db8500_thermal_match NULL +#endif + +static struct platform_driver db8500_thermal_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-thermal", + .of_match_table = db8500_thermal_match, + }, + .probe = db8500_thermal_probe, + .suspend = db8500_thermal_suspend, + .resume = db8500_thermal_resume, + .remove = db8500_thermal_remove, +}; + +module_platform_driver(db8500_thermal_driver); + +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..3bf6090 --- /dev/null +++ b/include/linux/platform_data/db8500_thermal.h @@ -0,0 +1,38 @@ +/* + * db8500_thermal.h - DB8500 Thermal Management Implementation + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang hongbo.zhang@linaro.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 cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH]; +}; + +struct db8500_thsens_platform_data { + struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS]; + int num_trips; +}; + +#endif /* _DB8500_THERMAL_H_ */
Sorry for late comments :(
On 30 October 2012 22:19, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A
s/diver/driver
thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
.../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 108 +++++ drivers/thermal/db8500_thermal.c | 531 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 38 ++ 6 files changed, 739 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/Documentation/devicetree/bindings/thermal/db8500-thermal.txt b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt new file mode 100644 index 0000000..cab6916 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal
+** Thermal node properties:
+- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated from PRCMU; +- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH";
Just mention here that below properties are optional or required.
+- num-trips : number of total trip points; +- tripN-temp : temperature of trip point N, should be in ascending order; +- tripN-type : type of trip point N, should be one of "active" "passive" "hot" "critical"; +- tripN-cdev-num : number of the cooling devices which can be bound to trip point N; +- tripN-cdev-nameM : name of the No. M cooling device of trip point N;
diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c
+static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
struct db8500_trip_point *trip_points)
+{
int i;
char *cdev_name;
if (!strlen(cdev->type))
return -EINVAL;
for (i = 0; i < COOLING_DEV_MAX; i++) {
cdev_name = trip_points->cdev_name[i];
if (!strcmp(cdev_name, cdev->type))
You can actually remove cdev_name variable. and use if (!strcmp(trip_points->cdev_name[i], cdev->type))
return 0;
}
return -ENODEV;
+}
+#ifdef CONFIG_OF +static struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev)
+{
for (j = 0; j < tmp_data; j++) {
sprintf(prop_name, "trip%d-cdev-name%d", i, j);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (strlen(tmp_str) > THERMAL_NAME_LENGTH)
goto err_parse_dt;
strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
want to check if it is copied or not??
}
}
return ptrips;
+err_parse_dt:
dev_err(&pdev->dev, "Parsing device tree data error.\n");
return NULL;
+}
After these please add my:
Reviewed-by: Viresh Kumar viresh.kumar@linaro.org
-----Original Message----- From: hongbo.zhang [mailto:hongbo.zhang@linaro.org] Sent: Wednesday, October 31, 2012 12:49 AM To: linaro-dev@lists.linaro.org; linux-kernel@vger.kernel.org; linux- pm@vger.kernel.org; Zhang, Rui; amit.kachhap@linaro.org Cc: patches@linaro.org; linaro-kernel@lists.linaro.org; STEricsson_nomadik_linux@list.st.com; kernel@igloocommunity.org; hongbo.zhang Subject: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver. Importance: High
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
.../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 108 +++++ drivers/thermal/db8500_thermal.c | 531 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 38 ++ 6 files changed, 739 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500- thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/Documentation/devicetree/bindings/thermal/db8500- thermal.txt b/Documentation/devicetree/bindings/thermal/db8500- thermal.txt new file mode 100644 index 0000000..cab6916 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal
+** Thermal node properties:
+- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated from PRCMU; +- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH"; +- num-trips : number of total trip points; +- tripN-temp : temperature of trip point N, should be in ascending +order; +- tripN-type : type of trip point N, should be one of "active" +"passive" "hot" "critical"; +- tripN-cdev-num : number of the cooling devices which can be bound to +trip point N; +- tripN-cdev-nameM : name of the No. M cooling device of trip point N;
+Usually the num-trips and tripN-*** are separated in board related dts files.
+Example: +thermal@801573c0 {
- compatible = "stericsson,db8500-thermal";
- reg = <0x801573c0 0x40>;
- interrupts = <21 0x4>, <22 0x4>;
- interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
- num-trips = <3>;
- trip0-temp = <70000>;
- trip0-type = "active";
- trip0-cdev-num = <1>;
- trip0-cdev-name0 = "thermal-cpufreq-0";
- trip1-temp = <75000>;
- trip1-type = "active";
- trip1-cdev-num = <2>;
- trip1-cdev-name0 = "thermal-cpufreq-0";
- trip1-cdev-name1 = "thermal-fan";
- trip2-temp = <85000>;
- trip2-type = "critical";
- trip2-cdev-num = <0>;
+} diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e1cb6bd..54c8fd0 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -31,6 +31,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL
- bool "DB8500 thermal management"
- depends on THERMAL
- default y
- help
Adds DB8500 thermal management implementation according to the
thermal
management framework. A thermal zone with several trip points
will be
created. Cooling devices can be bound to the trip points to
cool this
thermal zone if trip points reached.
+config DB8500_CPUFREQ_COOLING
- tristate "DB8500 cpufreq cooling"
- depends on CPU_THERMAL
- default y
- help
Adds DB8500 cpufreq cooling devices, and these cooling devices
can be
bound to thermal zone trip points. When a trip point reached,
the
bound cpufreq cooling device turns active to set CPU frequency
low to
cool down the CPU.
config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..4cf8e72 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,108 @@ +/*
- db8500_cpufreq_cooling.c - DB8500 cpufreq works as cooling device.
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h>
+static int db8500_cpufreq_cooling_probe(struct platform_device *pdev) {
- struct thermal_cooling_device *cdev;
- struct cpumask mask_val;
- /* make sure cpufreq driver has been initialized */
- if (!cpufreq_frequency_get_table(0))
return -EPROBE_DEFER;
- cpumask_set_cpu(0, &mask_val);
- cdev = cpufreq_cooling_register(&mask_val);
- if (IS_ERR_OR_NULL(cdev)) {
dev_err(&pdev->dev, "Failed to register cooling device\n");
return PTR_ERR(cdev);
- }
- platform_set_drvdata(pdev, cdev);
- dev_info(&pdev->dev, "Cooling device registered: %s\n", cdev-
type);
- return 0;
+}
+static int db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{
- struct thermal_cooling_device *cdev = platform_get_drvdata(pdev);
- cpufreq_cooling_unregister(cdev);
- return 0;
+}
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
- return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
- return -ENOSYS;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = {
- { .compatible = "stericsson,db8500-cpufreq-cooling" },
- {},
+}; +#else +#define db8500_cpufreq_cooling_match NULL #endif
+static struct platform_driver db8500_cpufreq_cooling_driver = {
- .driver = {
.owner = THIS_MODULE,
.name = "db8500-cpufreq-cooling",
.of_match_table = db8500_cpufreq_cooling_match,
- },
- .probe = db8500_cpufreq_cooling_probe,
- .suspend = db8500_cpufreq_cooling_suspend,
- .resume = db8500_cpufreq_cooling_resume,
- .remove = 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 100644 index 0000000..023f7b4 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,531 @@ +/*
- db8500_thermal.c - DB8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/interrupt.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/db8500_thermal.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h>
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0
+struct db8500_thermal_zone {
- struct thermal_zone_device *therm_dev;
- struct mutex th_lock;
- struct work_struct therm_work;
- struct db8500_thsens_platform_data *trip_tab;
- enum thermal_device_mode mode;
- enum thermal_trend trend;
- unsigned long cur_temp_pseudo;
- unsigned int cur_index;
+};
+/* Local function to check if thermal zone matches cooling devices */ +static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
struct db8500_trip_point *trip_points) {
- int i;
- char *cdev_name;
- if (!strlen(cdev->type))
return -EINVAL;
- for (i = 0; i < COOLING_DEV_MAX; i++) {
cdev_name = trip_points->cdev_name[i];
if (!strcmp(cdev_name, cdev->type))
return 0;
- }
- return -ENODEV;
+}
+/* Callback to bind cooling device to thermal zone */ static int +db8500_cdev_bind(struct thermal_zone_device *thermal,
struct thermal_cooling_device *cdev)
+{
- struct db8500_thermal_zone *pzone = thermal->devdata;
- struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
- unsigned long max_state, upper, lower;
- int i, ret = -EINVAL;
- for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips-
trip_points[i]))
continue;
cdev->ops->get_max_state(cdev, &max_state);
Why not moving this line out of the loop so that you only need to call it once.
lower = upper = (i > max_state) ? max_state : i;
What does this mean? Say cooling device 0 matches trip point 3, and you want to use cooling state 3 only, for trip point 3? No matter how many cooling states the cooling device supports?
Thanks, rui
ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
upper, lower);
dev_info(&cdev->device, "%s bind to %d: %d-%s\n",
cdev->type, i, ret, ret ? "fail" : "succeed");
- }
- return ret;
+}
+/* Callback to unbind cooling device from thermal zone */ static int +db8500_cdev_unbind(struct thermal_zone_device *thermal,
struct thermal_cooling_device *cdev)
+{
- struct db8500_thermal_zone *pzone = thermal->devdata;
- struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
- int i, ret = -EINVAL;
- for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips-
trip_points[i]))
continue;
ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
dev_info(&cdev->device, "%s unbind: %s\n",
cdev->type, ret ? "fail" : "succeed");
- }
- return ret;
+}
+/* Callback to get current temperature */ static int +db8500_sys_get_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
+{
- struct db8500_thermal_zone *pzone = thermal->devdata;
- /*
* TODO: There is no PRCMU interface to get temperature data
currently,
* so a pseudo temperature is returned , it works for thermal
framework
* and this will be fixed when the PRCMU interface is available.
*/
- *temp = pzone->cur_temp_pseudo;
- return 0;
+}
+/* Callback to get temperature changing trend */ static int +db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
- struct db8500_thermal_zone *pzone = thermal->devdata;
- *trend = pzone->trend;
- return 0;
+}
+/* Callback to get thermal zone mode */ static int +db8500_sys_get_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode *mode)
+{
- struct db8500_thermal_zone *pzone = thermal->devdata;
- mutex_lock(&pzone->th_lock);
- *mode = pzone->mode;
- mutex_unlock(&pzone->th_lock);
- return 0;
+}
+/* Callback to set thermal zone mode */ static int +db8500_sys_set_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode mode)
+{
- struct db8500_thermal_zone *pzone = thermal->devdata;
- mutex_lock(&pzone->th_lock);
- pzone->mode = mode;
- if (mode == THERMAL_DEVICE_ENABLED)
schedule_work(&pzone->therm_work);
- mutex_unlock(&pzone->th_lock);
- return 0;
+}
+/* Callback to get trip point type */ +static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
int trip, enum thermal_trip_type *type) {
- struct db8500_thermal_zone *pzone = thermal->devdata;
- struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
- if (trip >= ptrips->num_trips)
return -EINVAL;
- *type = ptrips->trip_points[trip].type;
- return 0;
+}
+/* Callback to get trip point temperature */ static int +db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
int trip, unsigned long *temp)
+{
- struct db8500_thermal_zone *pzone = thermal->devdata;
- struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
- if (trip >= ptrips->num_trips)
return -EINVAL;
- *temp = ptrips->trip_points[trip].temp;
- return 0;
+}
+/* Callback to get critical trip point temperature */ static int +db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
+{
- struct db8500_thermal_zone *pzone = thermal->devdata;
- struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
- int i;
- 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_trend = db8500_sys_get_trend,
- .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 void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
unsigned int idx, enum thermal_trend trend,
unsigned long next_low, unsigned long next_high) {
- pzone->cur_index = idx;
- pzone->cur_temp_pseudo = (next_low + next_high)/2;
- pzone->trend = trend;
- prcmu_stop_temp_sense();
- prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
- prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
+}
+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 = pzone->trip_tab;
- unsigned int idx = pzone->cur_index;
- unsigned long next_low, next_high;
- if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
- if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
- } else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
- }
- idx -= 1;
- db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
next_low, next_high);
- dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, 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 = pzone->trip_tab;
- unsigned int idx = pzone->cur_index;
- unsigned long next_low, next_high;
- if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
idx += 1;
db8500_thermal_update_config(pzone, idx,
THERMAL_TREND_RAISING,
next_low, next_high);
dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
- } else if (idx == ptrips->num_trips - 1)
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
- 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)
return;
- thermal_zone_device_update(pzone->therm_dev);
- dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); }
+#ifdef CONFIG_OF +static struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev) {
- struct db8500_thsens_platform_data *ptrips;
- struct device_node *np = pdev->dev.of_node;
- char prop_name[32];
- const char *tmp_str;
- u32 tmp_data;
- int i, j;
- ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
- if (!ptrips)
return NULL;
- if (of_property_read_u32(np, "num-trips", &tmp_data))
goto err_parse_dt;
- if (tmp_data > THERMAL_MAX_TRIPS)
goto err_parse_dt;
- ptrips->num_trips = tmp_data;
- for (i = 0; i < ptrips->num_trips; i++) {
sprintf(prop_name, "trip%d-temp", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
ptrips->trip_points[i].temp = tmp_data;
sprintf(prop_name, "trip%d-type", i);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (!strcmp(tmp_str, "active"))
ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
else if (!strcmp(tmp_str, "passive"))
ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
else if (!strcmp(tmp_str, "hot"))
ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
else if (!strcmp(tmp_str, "critical"))
ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
else
goto err_parse_dt;
sprintf(prop_name, "trip%d-cdev-num", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
if (tmp_data > COOLING_DEV_MAX)
goto err_parse_dt;
for (j = 0; j < tmp_data; j++) {
sprintf(prop_name, "trip%d-cdev-name%d", i, j);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (strlen(tmp_str) > THERMAL_NAME_LENGTH)
goto err_parse_dt;
strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
}
- }
- return ptrips;
+err_parse_dt:
- dev_err(&pdev->dev, "Parsing device tree data error.\n");
- return NULL;
+} +#else +static inline struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev) {
- return NULL;
+} +#endif
+static int db8500_thermal_probe(struct platform_device *pdev) {
- struct db8500_thermal_zone *pzone = NULL;
- struct db8500_thsens_platform_data *ptrips = NULL;
- struct device_node *np = pdev->dev.of_node;
- int low_irq, high_irq, ret = 0;
- unsigned long dft_low, dft_high;
- if (np)
ptrips = db8500_thermal_parse_dt(pdev);
- else
ptrips = dev_get_platdata(&pdev->dev);
- if (!ptrips)
return -EINVAL;
- pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
- if (!pzone)
return -ENOMEM;
- mutex_init(&pzone->th_lock);
- mutex_lock(&pzone->th_lock);
- pzone->mode = THERMAL_DEVICE_DISABLED;
- pzone->trip_tab = ptrips;
- dft_low = PRCMU_DEFAULT_LOW_TEMP;
- dft_high = ptrips->trip_points[0].temp;
- db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
- INIT_WORK(&pzone->therm_work, db8500_thermal_work);
- low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
- if (low_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
return low_irq;
- }
- ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_low", pzone);
- if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
return ret;
- }
- high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
- if (high_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
- }
- ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_high", pzone);
- if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
return ret;
- }
- pzone->therm_dev =
thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
- if (IS_ERR_OR_NULL(pzone->therm_dev)) {
dev_err(&pdev->dev, "Register thermal zone device
failed.\n");
return PTR_ERR(pzone->therm_dev);
- }
- dev_info(&pdev->dev, "Thermal zone device registered.\n");
- platform_set_drvdata(pdev, pzone);
- pzone->mode = THERMAL_DEVICE_ENABLED;
- mutex_unlock(&pzone->th_lock);
- return 0;
+}
+static int db8500_thermal_remove(struct platform_device *pdev) {
- struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
- thermal_zone_device_unregister(pzone->therm_dev);
- cancel_work_sync(&pzone->therm_work);
- mutex_destroy(&pzone->th_lock);
- return 0;
+}
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
- struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
- flush_work_sync(&pzone->therm_work);
- prcmu_stop_temp_sense();
- return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) {
- struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
- struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
- unsigned long dft_low, dft_high;
- dft_low = PRCMU_DEFAULT_LOW_TEMP;
- dft_high = ptrips->trip_points[0].temp;
- db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
- return 0;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = {
- { .compatible = "stericsson,db8500-thermal" },
- {},
+}; +#else +#define db8500_thermal_match NULL +#endif
+static struct platform_driver db8500_thermal_driver = {
- .driver = {
.owner = THIS_MODULE,
.name = "db8500-thermal",
.of_match_table = db8500_thermal_match,
- },
- .probe = db8500_thermal_probe,
- .suspend = db8500_thermal_suspend,
- .resume = db8500_thermal_resume,
- .remove = db8500_thermal_remove,
+};
+module_platform_driver(db8500_thermal_driver);
+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..3bf6090 --- /dev/null +++ b/include/linux/platform_data/db8500_thermal.h @@ -0,0 +1,38 @@ +/*
- db8500_thermal.h - DB8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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 cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH];
+};
+struct db8500_thsens_platform_data {
- struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS];
- int num_trips;
+};
+#endif /* _DB8500_THERMAL_H_ */
1.7.11.3
On 1 November 2012 09:52, Zhang, Rui rui.zhang@intel.com wrote:
-----Original Message----- From: hongbo.zhang [mailto:hongbo.zhang@linaro.org] Sent: Wednesday, October 31, 2012 12:49 AM To: linaro-dev@lists.linaro.org; linux-kernel@vger.kernel.org; linux- pm@vger.kernel.org; Zhang, Rui; amit.kachhap@linaro.org Cc: patches@linaro.org; linaro-kernel@lists.linaro.org; STEricsson_nomadik_linux@list.st.com; kernel@igloocommunity.org; hongbo.zhang Subject: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver. Importance: High
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
.../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 108 +++++ drivers/thermal/db8500_thermal.c | 531 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 38 ++ 6 files changed, 739 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500- thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/Documentation/devicetree/bindings/thermal/db8500- thermal.txt b/Documentation/devicetree/bindings/thermal/db8500- thermal.txt new file mode 100644 index 0000000..cab6916 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal
+** Thermal node properties:
+- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated from PRCMU; +- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH"; +- num-trips : number of total trip points; +- tripN-temp : temperature of trip point N, should be in ascending +order; +- tripN-type : type of trip point N, should be one of "active" +"passive" "hot" "critical"; +- tripN-cdev-num : number of the cooling devices which can be bound to +trip point N; +- tripN-cdev-nameM : name of the No. M cooling device of trip point N;
+Usually the num-trips and tripN-*** are separated in board related dts files.
+Example: +thermal@801573c0 {
compatible = "stericsson,db8500-thermal";
reg = <0x801573c0 0x40>;
interrupts = <21 0x4>, <22 0x4>;
interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
num-trips = <3>;
trip0-temp = <70000>;
trip0-type = "active";
trip0-cdev-num = <1>;
trip0-cdev-name0 = "thermal-cpufreq-0";
trip1-temp = <75000>;
trip1-type = "active";
trip1-cdev-num = <2>;
trip1-cdev-name0 = "thermal-cpufreq-0";
trip1-cdev-name1 = "thermal-fan";
trip2-temp = <85000>;
trip2-type = "critical";
trip2-cdev-num = <0>;
+} diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e1cb6bd..54c8fd0 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -31,6 +31,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL
bool "DB8500 thermal management"
depends on THERMAL
default y
help
Adds DB8500 thermal management implementation according to the
thermal
management framework. A thermal zone with several trip points
will be
created. Cooling devices can be bound to the trip points to
cool this
thermal zone if trip points reached.
+config DB8500_CPUFREQ_COOLING
tristate "DB8500 cpufreq cooling"
depends on CPU_THERMAL
default y
help
Adds DB8500 cpufreq cooling devices, and these cooling devices
can be
bound to thermal zone trip points. When a trip point reached,
the
bound cpufreq cooling device turns active to set CPU frequency
low to
cool down the CPU.
config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..4cf8e72 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,108 @@ +/*
- db8500_cpufreq_cooling.c - DB8500 cpufreq works as cooling device.
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h>
+static int db8500_cpufreq_cooling_probe(struct platform_device *pdev) {
struct thermal_cooling_device *cdev;
struct cpumask mask_val;
/* make sure cpufreq driver has been initialized */
if (!cpufreq_frequency_get_table(0))
return -EPROBE_DEFER;
cpumask_set_cpu(0, &mask_val);
cdev = cpufreq_cooling_register(&mask_val);
if (IS_ERR_OR_NULL(cdev)) {
dev_err(&pdev->dev, "Failed to register cooling device\n");
return PTR_ERR(cdev);
}
platform_set_drvdata(pdev, cdev);
dev_info(&pdev->dev, "Cooling device registered: %s\n", cdev-
type);
return 0;
+}
+static int db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{
struct thermal_cooling_device *cdev = platform_get_drvdata(pdev);
cpufreq_cooling_unregister(cdev);
return 0;
+}
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
return -ENOSYS;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = {
{ .compatible = "stericsson,db8500-cpufreq-cooling" },
{},
+}; +#else +#define db8500_cpufreq_cooling_match NULL #endif
+static struct platform_driver db8500_cpufreq_cooling_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-cpufreq-cooling",
.of_match_table = db8500_cpufreq_cooling_match,
},
.probe = db8500_cpufreq_cooling_probe,
.suspend = db8500_cpufreq_cooling_suspend,
.resume = db8500_cpufreq_cooling_resume,
.remove = 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 100644 index 0000000..023f7b4 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,531 @@ +/*
- db8500_thermal.c - DB8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/interrupt.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/db8500_thermal.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h>
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0
+struct db8500_thermal_zone {
struct thermal_zone_device *therm_dev;
struct mutex th_lock;
struct work_struct therm_work;
struct db8500_thsens_platform_data *trip_tab;
enum thermal_device_mode mode;
enum thermal_trend trend;
unsigned long cur_temp_pseudo;
unsigned int cur_index;
+};
+/* Local function to check if thermal zone matches cooling devices */ +static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
struct db8500_trip_point *trip_points) {
int i;
char *cdev_name;
if (!strlen(cdev->type))
return -EINVAL;
for (i = 0; i < COOLING_DEV_MAX; i++) {
cdev_name = trip_points->cdev_name[i];
if (!strcmp(cdev_name, cdev->type))
return 0;
}
return -ENODEV;
+}
+/* Callback to bind cooling device to thermal zone */ static int +db8500_cdev_bind(struct thermal_zone_device *thermal,
struct thermal_cooling_device *cdev)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
unsigned long max_state, upper, lower;
int i, ret = -EINVAL;
for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips-
trip_points[i]))
continue;
cdev->ops->get_max_state(cdev, &max_state);
Why not moving this line out of the loop so that you only need to call it once.
Good.
lower = upper = (i > max_state) ? max_state : i;
What does this mean? Say cooling device 0 matches trip point 3, and you want to use cooling state 3 only, for trip point 3? No matter how many cooling states the cooling device supports?
I would like to change lower = upper - 1 > 0 ? upper - 1 : 0; More states for one trip point is meaningless on my platform, because thermal framework polling thread isn't used, a PRCMU firmware high/low interrupts are used to trigger thermal update instead(currently platform limitation), thus thermal update is called only once when trip point is crossed up/down. More states for one trip points can be used on our SOC serials in future when this limitation is removed.
Thanks, rui
ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
upper, lower);
dev_info(&cdev->device, "%s bind to %d: %d-%s\n",
cdev->type, i, ret, ret ? "fail" : "succeed");
}
return ret;
+}
+/* Callback to unbind cooling device from thermal zone */ static int +db8500_cdev_unbind(struct thermal_zone_device *thermal,
struct thermal_cooling_device *cdev)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
int i, ret = -EINVAL;
for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips-
trip_points[i]))
continue;
ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
dev_info(&cdev->device, "%s unbind: %s\n",
cdev->type, ret ? "fail" : "succeed");
}
return ret;
+}
+/* Callback to get current temperature */ static int +db8500_sys_get_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
/*
* TODO: There is no PRCMU interface to get temperature data
currently,
* so a pseudo temperature is returned , it works for thermal
framework
* and this will be fixed when the PRCMU interface is available.
*/
*temp = pzone->cur_temp_pseudo;
return 0;
+}
+/* Callback to get temperature changing trend */ static int +db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
*trend = pzone->trend;
return 0;
+}
+/* Callback to get thermal zone mode */ static int +db8500_sys_get_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode *mode)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
mutex_lock(&pzone->th_lock);
*mode = pzone->mode;
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to set thermal zone mode */ static int +db8500_sys_set_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode mode)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
mutex_lock(&pzone->th_lock);
pzone->mode = mode;
if (mode == THERMAL_DEVICE_ENABLED)
schedule_work(&pzone->therm_work);
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to get trip point type */ +static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
int trip, enum thermal_trip_type *type) {
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*type = ptrips->trip_points[trip].type;
return 0;
+}
+/* Callback to get trip point temperature */ static int +db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
int trip, unsigned long *temp)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*temp = ptrips->trip_points[trip].temp;
return 0;
+}
+/* Callback to get critical trip point temperature */ static int +db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
int i;
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_trend = db8500_sys_get_trend,
.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 void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
unsigned int idx, enum thermal_trend trend,
unsigned long next_low, unsigned long next_high) {
pzone->cur_index = idx;
pzone->cur_temp_pseudo = (next_low + next_high)/2;
pzone->trend = trend;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
+}
+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 = pzone->trip_tab;
unsigned int idx = pzone->cur_index;
unsigned long next_low, next_high;
if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
} else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
}
idx -= 1;
db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
next_low, next_high);
dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, 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 = pzone->trip_tab;
unsigned int idx = pzone->cur_index;
unsigned long next_low, next_high;
if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
idx += 1;
db8500_thermal_update_config(pzone, idx,
THERMAL_TREND_RAISING,
next_low, next_high);
dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
} else if (idx == ptrips->num_trips - 1)
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
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)
return;
thermal_zone_device_update(pzone->therm_dev);
dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); }
+#ifdef CONFIG_OF +static struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev) {
struct db8500_thsens_platform_data *ptrips;
struct device_node *np = pdev->dev.of_node;
char prop_name[32];
const char *tmp_str;
u32 tmp_data;
int i, j;
ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
if (!ptrips)
return NULL;
if (of_property_read_u32(np, "num-trips", &tmp_data))
goto err_parse_dt;
if (tmp_data > THERMAL_MAX_TRIPS)
goto err_parse_dt;
ptrips->num_trips = tmp_data;
for (i = 0; i < ptrips->num_trips; i++) {
sprintf(prop_name, "trip%d-temp", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
ptrips->trip_points[i].temp = tmp_data;
sprintf(prop_name, "trip%d-type", i);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (!strcmp(tmp_str, "active"))
ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
else if (!strcmp(tmp_str, "passive"))
ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
else if (!strcmp(tmp_str, "hot"))
ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
else if (!strcmp(tmp_str, "critical"))
ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
else
goto err_parse_dt;
sprintf(prop_name, "trip%d-cdev-num", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
if (tmp_data > COOLING_DEV_MAX)
goto err_parse_dt;
for (j = 0; j < tmp_data; j++) {
sprintf(prop_name, "trip%d-cdev-name%d", i, j);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (strlen(tmp_str) > THERMAL_NAME_LENGTH)
goto err_parse_dt;
strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
}
}
return ptrips;
+err_parse_dt:
dev_err(&pdev->dev, "Parsing device tree data error.\n");
return NULL;
+} +#else +static inline struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev) {
return NULL;
+} +#endif
+static int db8500_thermal_probe(struct platform_device *pdev) {
struct db8500_thermal_zone *pzone = NULL;
struct db8500_thsens_platform_data *ptrips = NULL;
struct device_node *np = pdev->dev.of_node;
int low_irq, high_irq, ret = 0;
unsigned long dft_low, dft_high;
if (np)
ptrips = db8500_thermal_parse_dt(pdev);
else
ptrips = dev_get_platdata(&pdev->dev);
if (!ptrips)
return -EINVAL;
pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
if (!pzone)
return -ENOMEM;
mutex_init(&pzone->th_lock);
mutex_lock(&pzone->th_lock);
pzone->mode = THERMAL_DEVICE_DISABLED;
pzone->trip_tab = ptrips;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
INIT_WORK(&pzone->therm_work, db8500_thermal_work);
low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
if (low_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
return low_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_low", pzone);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_high", pzone);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
return ret;
}
pzone->therm_dev =
thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
if (IS_ERR_OR_NULL(pzone->therm_dev)) {
dev_err(&pdev->dev, "Register thermal zone device
failed.\n");
return PTR_ERR(pzone->therm_dev);
}
dev_info(&pdev->dev, "Thermal zone device registered.\n");
platform_set_drvdata(pdev, pzone);
pzone->mode = THERMAL_DEVICE_ENABLED;
mutex_unlock(&pzone->th_lock);
return 0;
+}
+static int db8500_thermal_remove(struct platform_device *pdev) {
struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
thermal_zone_device_unregister(pzone->therm_dev);
cancel_work_sync(&pzone->therm_work);
mutex_destroy(&pzone->th_lock);
return 0;
+}
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
flush_work_sync(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) {
struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
unsigned long dft_low, dft_high;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
return 0;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = {
{ .compatible = "stericsson,db8500-thermal" },
{},
+}; +#else +#define db8500_thermal_match NULL +#endif
+static struct platform_driver db8500_thermal_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-thermal",
.of_match_table = db8500_thermal_match,
},
.probe = db8500_thermal_probe,
.suspend = db8500_thermal_suspend,
.resume = db8500_thermal_resume,
.remove = db8500_thermal_remove,
+};
+module_platform_driver(db8500_thermal_driver);
+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..3bf6090 --- /dev/null +++ b/include/linux/platform_data/db8500_thermal.h @@ -0,0 +1,38 @@ +/*
- db8500_thermal.h - DB8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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 cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH];
+};
+struct db8500_thsens_platform_data {
struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS];
int num_trips;
+};
+#endif /* _DB8500_THERMAL_H_ */
1.7.11.3
On 6 November 2012 18:17, Hongbo Zhang hongbo.zhang@linaro.org wrote:
On 1 November 2012 09:52, Zhang, Rui rui.zhang@intel.com wrote:
-----Original Message----- From: hongbo.zhang [mailto:hongbo.zhang@linaro.org] Sent: Wednesday, October 31, 2012 12:49 AM To: linaro-dev@lists.linaro.org; linux-kernel@vger.kernel.org; linux- pm@vger.kernel.org; Zhang, Rui; amit.kachhap@linaro.org Cc: patches@linaro.org; linaro-kernel@lists.linaro.org; STEricsson_nomadik_linux@list.st.com; kernel@igloocommunity.org; hongbo.zhang Subject: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver. Importance: High
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
.../devicetree/bindings/thermal/db8500-thermal.txt | 40 ++ drivers/thermal/Kconfig | 20 + drivers/thermal/Makefile | 2 + drivers/thermal/db8500_cpufreq_cooling.c | 108 +++++ drivers/thermal/db8500_thermal.c | 531 +++++++++++++++++++++ include/linux/platform_data/db8500_thermal.h | 38 ++ 6 files changed, 739 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/db8500- thermal.txt create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c create mode 100644 drivers/thermal/db8500_thermal.c create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/Documentation/devicetree/bindings/thermal/db8500- thermal.txt b/Documentation/devicetree/bindings/thermal/db8500- thermal.txt new file mode 100644 index 0000000..cab6916 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt @@ -0,0 +1,40 @@ +* ST-Ericsson DB8500 Thermal
+** Thermal node properties:
+- compatible : "stericsson,db8500-thermal"; +- reg : address range of the thermal sensor registers; +- interrupts : interrupts generated from PRCMU; +- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH"; +- num-trips : number of total trip points; +- tripN-temp : temperature of trip point N, should be in ascending +order; +- tripN-type : type of trip point N, should be one of "active" +"passive" "hot" "critical"; +- tripN-cdev-num : number of the cooling devices which can be bound to +trip point N; +- tripN-cdev-nameM : name of the No. M cooling device of trip point N;
+Usually the num-trips and tripN-*** are separated in board related dts files.
+Example: +thermal@801573c0 {
compatible = "stericsson,db8500-thermal";
reg = <0x801573c0 0x40>;
interrupts = <21 0x4>, <22 0x4>;
interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
num-trips = <3>;
trip0-temp = <70000>;
trip0-type = "active";
trip0-cdev-num = <1>;
trip0-cdev-name0 = "thermal-cpufreq-0";
trip1-temp = <75000>;
trip1-type = "active";
trip1-cdev-num = <2>;
trip1-cdev-name0 = "thermal-cpufreq-0";
trip1-cdev-name1 = "thermal-fan";
trip2-temp = <85000>;
trip2-type = "critical";
trip2-cdev-num = <0>;
+} diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e1cb6bd..54c8fd0 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -31,6 +31,26 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here.
+config DB8500_THERMAL
bool "DB8500 thermal management"
depends on THERMAL
default y
help
Adds DB8500 thermal management implementation according to the
thermal
management framework. A thermal zone with several trip points
will be
created. Cooling devices can be bound to the trip points to
cool this
thermal zone if trip points reached.
+config DB8500_CPUFREQ_COOLING
tristate "DB8500 cpufreq cooling"
depends on CPU_THERMAL
default y
help
Adds DB8500 cpufreq cooling devices, and these cooling devices
can be
bound to thermal zone trip points. When a trip point reached,
the
bound cpufreq cooling device turns active to set CPU frequency
low to
cool down the CPU.
config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550d..c7a8dab 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 0000000..4cf8e72 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,108 @@ +/*
- db8500_cpufreq_cooling.c - DB8500 cpufreq works as cooling device.
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h>
+static int db8500_cpufreq_cooling_probe(struct platform_device *pdev) {
struct thermal_cooling_device *cdev;
struct cpumask mask_val;
/* make sure cpufreq driver has been initialized */
if (!cpufreq_frequency_get_table(0))
return -EPROBE_DEFER;
cpumask_set_cpu(0, &mask_val);
cdev = cpufreq_cooling_register(&mask_val);
if (IS_ERR_OR_NULL(cdev)) {
dev_err(&pdev->dev, "Failed to register cooling device\n");
return PTR_ERR(cdev);
}
platform_set_drvdata(pdev, cdev);
dev_info(&pdev->dev, "Cooling device registered: %s\n", cdev-
type);
return 0;
+}
+static int db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{
struct thermal_cooling_device *cdev = platform_get_drvdata(pdev);
cpufreq_cooling_unregister(cdev);
return 0;
+}
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
+{
return -ENOSYS;
+}
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{
return -ENOSYS;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = {
{ .compatible = "stericsson,db8500-cpufreq-cooling" },
{},
+}; +#else +#define db8500_cpufreq_cooling_match NULL #endif
+static struct platform_driver db8500_cpufreq_cooling_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-cpufreq-cooling",
.of_match_table = db8500_cpufreq_cooling_match,
},
.probe = db8500_cpufreq_cooling_probe,
.suspend = db8500_cpufreq_cooling_suspend,
.resume = db8500_cpufreq_cooling_resume,
.remove = 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 100644 index 0000000..023f7b4 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,531 @@ +/*
- db8500_thermal.c - DB8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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/cpu_cooling.h> +#include <linux/interrupt.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/db8500_thermal.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h>
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0
+struct db8500_thermal_zone {
struct thermal_zone_device *therm_dev;
struct mutex th_lock;
struct work_struct therm_work;
struct db8500_thsens_platform_data *trip_tab;
enum thermal_device_mode mode;
enum thermal_trend trend;
unsigned long cur_temp_pseudo;
unsigned int cur_index;
+};
+/* Local function to check if thermal zone matches cooling devices */ +static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
struct db8500_trip_point *trip_points) {
int i;
char *cdev_name;
if (!strlen(cdev->type))
return -EINVAL;
for (i = 0; i < COOLING_DEV_MAX; i++) {
cdev_name = trip_points->cdev_name[i];
if (!strcmp(cdev_name, cdev->type))
return 0;
}
return -ENODEV;
+}
+/* Callback to bind cooling device to thermal zone */ static int +db8500_cdev_bind(struct thermal_zone_device *thermal,
struct thermal_cooling_device *cdev)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
unsigned long max_state, upper, lower;
int i, ret = -EINVAL;
for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips-
trip_points[i]))
continue;
cdev->ops->get_max_state(cdev, &max_state);
Why not moving this line out of the loop so that you only need to call it once.
Good.
lower = upper = (i > max_state) ? max_state : i;
What does this mean? Say cooling device 0 matches trip point 3, and you want to use cooling state 3 only, for trip point 3? No matter how many cooling states the cooling device supports?
I would like to change lower = upper - 1 > 0 ? upper - 1 : 0; More states for one trip point is meaningless on my platform, because thermal framework polling thread isn't used, a PRCMU firmware high/low interrupts are used to trigger thermal update instead(currently platform limitation), thus thermal update is called only once when trip point is crossed up/down. More states for one trip points can be used on our SOC serials in future when this limitation is removed.
Sorry for update: callback this "lower = upper - 1 > 0 ? upper - 1 : 0;" current setting works as explained above, this is platform specific.
Thanks, rui
ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
upper, lower);
dev_info(&cdev->device, "%s bind to %d: %d-%s\n",
cdev->type, i, ret, ret ? "fail" : "succeed");
}
return ret;
+}
+/* Callback to unbind cooling device from thermal zone */ static int +db8500_cdev_unbind(struct thermal_zone_device *thermal,
struct thermal_cooling_device *cdev)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
int i, ret = -EINVAL;
for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips-
trip_points[i]))
continue;
ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
dev_info(&cdev->device, "%s unbind: %s\n",
cdev->type, ret ? "fail" : "succeed");
}
return ret;
+}
+/* Callback to get current temperature */ static int +db8500_sys_get_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
/*
* TODO: There is no PRCMU interface to get temperature data
currently,
* so a pseudo temperature is returned , it works for thermal
framework
* and this will be fixed when the PRCMU interface is available.
*/
*temp = pzone->cur_temp_pseudo;
return 0;
+}
+/* Callback to get temperature changing trend */ static int +db8500_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
*trend = pzone->trend;
return 0;
+}
+/* Callback to get thermal zone mode */ static int +db8500_sys_get_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode *mode)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
mutex_lock(&pzone->th_lock);
*mode = pzone->mode;
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to set thermal zone mode */ static int +db8500_sys_set_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode mode)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
mutex_lock(&pzone->th_lock);
pzone->mode = mode;
if (mode == THERMAL_DEVICE_ENABLED)
schedule_work(&pzone->therm_work);
mutex_unlock(&pzone->th_lock);
return 0;
+}
+/* Callback to get trip point type */ +static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
int trip, enum thermal_trip_type *type) {
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*type = ptrips->trip_points[trip].type;
return 0;
+}
+/* Callback to get trip point temperature */ static int +db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
int trip, unsigned long *temp)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
if (trip >= ptrips->num_trips)
return -EINVAL;
*temp = ptrips->trip_points[trip].temp;
return 0;
+}
+/* Callback to get critical trip point temperature */ static int +db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
+{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
int i;
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_trend = db8500_sys_get_trend,
.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 void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
unsigned int idx, enum thermal_trend trend,
unsigned long next_low, unsigned long next_high) {
pzone->cur_index = idx;
pzone->cur_temp_pseudo = (next_low + next_high)/2;
pzone->trend = trend;
prcmu_stop_temp_sense();
prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
+}
+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 = pzone->trip_tab;
unsigned int idx = pzone->cur_index;
unsigned long next_low, next_high;
if (unlikely(idx == 0))
/* Meaningless for thermal management, ignoring it */
return IRQ_HANDLED;
if (idx == 1) {
next_high = ptrips->trip_points[0].temp;
next_low = PRCMU_DEFAULT_LOW_TEMP;
} else {
next_high = ptrips->trip_points[idx-1].temp;
next_low = ptrips->trip_points[idx-2].temp;
}
idx -= 1;
db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
next_low, next_high);
dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, 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 = pzone->trip_tab;
unsigned int idx = pzone->cur_index;
unsigned long next_low, next_high;
if (idx < ptrips->num_trips - 1) {
next_high = ptrips->trip_points[idx+1].temp;
next_low = ptrips->trip_points[idx].temp;
idx += 1;
db8500_thermal_update_config(pzone, idx,
THERMAL_TREND_RAISING,
next_low, next_high);
dev_dbg(&pzone->therm_dev->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
} else if (idx == ptrips->num_trips - 1)
pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
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)
return;
thermal_zone_device_update(pzone->therm_dev);
dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); }
+#ifdef CONFIG_OF +static struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev) {
struct db8500_thsens_platform_data *ptrips;
struct device_node *np = pdev->dev.of_node;
char prop_name[32];
const char *tmp_str;
u32 tmp_data;
int i, j;
ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
if (!ptrips)
return NULL;
if (of_property_read_u32(np, "num-trips", &tmp_data))
goto err_parse_dt;
if (tmp_data > THERMAL_MAX_TRIPS)
goto err_parse_dt;
ptrips->num_trips = tmp_data;
for (i = 0; i < ptrips->num_trips; i++) {
sprintf(prop_name, "trip%d-temp", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
ptrips->trip_points[i].temp = tmp_data;
sprintf(prop_name, "trip%d-type", i);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (!strcmp(tmp_str, "active"))
ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
else if (!strcmp(tmp_str, "passive"))
ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
else if (!strcmp(tmp_str, "hot"))
ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
else if (!strcmp(tmp_str, "critical"))
ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
else
goto err_parse_dt;
sprintf(prop_name, "trip%d-cdev-num", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
if (tmp_data > COOLING_DEV_MAX)
goto err_parse_dt;
for (j = 0; j < tmp_data; j++) {
sprintf(prop_name, "trip%d-cdev-name%d", i, j);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (strlen(tmp_str) > THERMAL_NAME_LENGTH)
goto err_parse_dt;
strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
}
}
return ptrips;
+err_parse_dt:
dev_err(&pdev->dev, "Parsing device tree data error.\n");
return NULL;
+} +#else +static inline struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev) {
return NULL;
+} +#endif
+static int db8500_thermal_probe(struct platform_device *pdev) {
struct db8500_thermal_zone *pzone = NULL;
struct db8500_thsens_platform_data *ptrips = NULL;
struct device_node *np = pdev->dev.of_node;
int low_irq, high_irq, ret = 0;
unsigned long dft_low, dft_high;
if (np)
ptrips = db8500_thermal_parse_dt(pdev);
else
ptrips = dev_get_platdata(&pdev->dev);
if (!ptrips)
return -EINVAL;
pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
if (!pzone)
return -ENOMEM;
mutex_init(&pzone->th_lock);
mutex_lock(&pzone->th_lock);
pzone->mode = THERMAL_DEVICE_DISABLED;
pzone->trip_tab = ptrips;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
INIT_WORK(&pzone->therm_work, db8500_thermal_work);
low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
if (low_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
return low_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_low", pzone);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
return ret;
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
if (high_irq < 0) {
dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
return high_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
"dbx500_temp_high", pzone);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
return ret;
}
pzone->therm_dev =
thermal_zone_device_register("db8500_thermal_zone",
ptrips->num_trips, 0, pzone, &thdev_ops, 0, 0);
if (IS_ERR_OR_NULL(pzone->therm_dev)) {
dev_err(&pdev->dev, "Register thermal zone device
failed.\n");
return PTR_ERR(pzone->therm_dev);
}
dev_info(&pdev->dev, "Thermal zone device registered.\n");
platform_set_drvdata(pdev, pzone);
pzone->mode = THERMAL_DEVICE_ENABLED;
mutex_unlock(&pzone->th_lock);
return 0;
+}
+static int db8500_thermal_remove(struct platform_device *pdev) {
struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
thermal_zone_device_unregister(pzone->therm_dev);
cancel_work_sync(&pzone->therm_work);
mutex_destroy(&pzone->th_lock);
return 0;
+}
+static int db8500_thermal_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
flush_work_sync(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
+}
+static int db8500_thermal_resume(struct platform_device *pdev) {
struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
unsigned long dft_low, dft_high;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
return 0;
+}
+#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = {
{ .compatible = "stericsson,db8500-thermal" },
{},
+}; +#else +#define db8500_thermal_match NULL +#endif
+static struct platform_driver db8500_thermal_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-thermal",
.of_match_table = db8500_thermal_match,
},
.probe = db8500_thermal_probe,
.suspend = db8500_thermal_suspend,
.resume = db8500_thermal_resume,
.remove = db8500_thermal_remove,
+};
+module_platform_driver(db8500_thermal_driver);
+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..3bf6090 --- /dev/null +++ b/include/linux/platform_data/db8500_thermal.h @@ -0,0 +1,38 @@ +/*
- db8500_thermal.h - DB8500 Thermal Management Implementation
- Copyright (C) 2012 ST-Ericsson
- Copyright (C) 2012 Linaro Ltd.
- Author: Hongbo Zhang hongbo.zhang@linaro.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 cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH];
+};
+struct db8500_thsens_platform_data {
struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS];
int num_trips;
+};
+#endif /* _DB8500_THERMAL_H_ */
1.7.11.3
Hi,
On 10/30/2012 05:49 PM, hongbo.zhang wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
This diver is based on the thermal management framework in thermal_sys.c. A thermal zone device is created with the trip points to which cooling devices can be bound, the current cooling device is cpufreq, e.g. CPU frequency is clipped down to cool the CPU, and other cooling devices can be added and bound to the trip points dynamically. The platform specific PRCMU interrupts are used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
[...]
+static struct db8500_thsens_platform_data*
db8500_thermal_parse_dt(struct platform_device *pdev)
+{
- struct db8500_thsens_platform_data *ptrips;
- struct device_node *np = pdev->dev.of_node;
- char prop_name[32];
- const char *tmp_str;
- u32 tmp_data;
- int i, j;
- ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
- if (!ptrips)
return NULL;
- if (of_property_read_u32(np, "num-trips", &tmp_data))
goto err_parse_dt;
- if (tmp_data > THERMAL_MAX_TRIPS)
goto err_parse_dt;
- ptrips->num_trips = tmp_data;
- for (i = 0; i < ptrips->num_trips; i++) {
sprintf(prop_name, "trip%d-temp", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
ptrips->trip_points[i].temp = tmp_data;
sprintf(prop_name, "trip%d-type", i);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (!strcmp(tmp_str, "active"))
ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
else if (!strcmp(tmp_str, "passive"))
ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
else if (!strcmp(tmp_str, "hot"))
ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
else if (!strcmp(tmp_str, "critical"))
ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
else
goto err_parse_dt;
sprintf(prop_name, "trip%d-cdev-num", i);
if (of_property_read_u32(np, prop_name, &tmp_data))
goto err_parse_dt;
if (tmp_data > COOLING_DEV_MAX)
goto err_parse_dt;
for (j = 0; j < tmp_data; j++) {
sprintf(prop_name, "trip%d-cdev-name%d", i, j);
if (of_property_read_string(np, prop_name, &tmp_str))
goto err_parse_dt;
if (strlen(tmp_str) > THERMAL_NAME_LENGTH)
goto err_parse_dt;
strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
If strlen(tmp_str) equals THERMAL_NAME_LENGTH, strcpy() will go past the size of the destination array.
After the above is fixed, you can add my: Reviewed-by: Francesco Lavra francescolavra.fl@gmail.com
If you re-send a new version of the patch series, I suggest you do so in a new thread.
Thanks, Francesco
From: "hongbo.zhang" hongbo.zhang@linaro.com
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com --- arch/arm/boot/dts/dbx5x0.dtsi | 14 +++++++++ arch/arm/boot/dts/snowball.dts | 31 ++++++++++++++++++ arch/arm/configs/u8500_defconfig | 4 +++ arch/arm/mach-ux500/board-mop500.c | 64 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+)
diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi index 4b0e0ca..731086b 100644 --- a/arch/arm/boot/dts/dbx5x0.dtsi +++ b/arch/arm/boot/dts/dbx5x0.dtsi @@ -203,6 +203,14 @@ reg = <0x80157450 0xC>; };
+ thermal@801573c0 { + compatible = "stericsson,db8500-thermal"; + reg = <0x801573c0 0x40>; + interrupts = <21 0x4>, <22 0x4>; + interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH"; + status = "disabled"; + }; + db8500-prcmu-regulators { compatible = "stericsson,db8500-prcmu-regulator";
@@ -660,5 +668,11 @@ ranges = <0 0x50000000 0x4000000>; status = "disabled"; }; + + cpufreq-cooling { + compatible = "stericsson,db8500-cpufreq-cooling"; + status = "disabled"; + }; + }; }; diff --git a/arch/arm/boot/dts/snowball.dts b/arch/arm/boot/dts/snowball.dts index 702c0ba..c6f85f0 100644 --- a/arch/arm/boot/dts/snowball.dts +++ b/arch/arm/boot/dts/snowball.dts @@ -99,6 +99,33 @@ status = "okay"; };
+ prcmu@80157000 { + thermal@801573c0 { + num-trips = <4>; + + trip0-temp = <70000>; + trip0-type = "active"; + trip0-cdev-num = <1>; + trip0-cdev-name0 = "thermal-cpufreq-0"; + + trip1-temp = <75000>; + trip1-type = "active"; + trip1-cdev-num = <1>; + trip1-cdev-name0 = "thermal-cpufreq-0"; + + trip2-temp = <80000>; + trip2-type = "active"; + trip2-cdev-num = <1>; + trip2-cdev-name0 = "thermal-cpufreq-0"; + + trip3-temp = <85000>; + trip3-type = "critical"; + trip3-cdev-num = <0>; + + status = "okay"; + }; + }; + external-bus@50000000 { status = "okay";
@@ -183,5 +210,9 @@ reg = <0x33>; }; }; + + cpufreq-cooling { + status = "okay"; + }; }; }; diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index cc5e7a8..34918c4 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 416d436..b03216b 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -16,6 +16,7 @@ #include <linux/io.h> #include <linux/i2c.h> #include <linux/platform_data/i2c-nomadik.h> +#include <linux/platform_data/db8500_thermal.h> #include <linux/gpio.h> #include <linux/amba/bus.h> #include <linux/amba/pl022.h> @@ -229,6 +230,67 @@ static struct ab8500_platform_data ab8500_platdata = { };
/* + * Thermal Sensor + */ + +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_thsens_platform_data db8500_thsens_data = { + .trip_points[0] = { + .temp = 70000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[1] = { + .temp = 75000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[2] = { + .temp = 80000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[3] = { + .temp = 85000, + .type = THERMAL_TRIP_CRITICAL, + }, + .num_trips = 4, +}; + +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, + }, +}; + +static struct platform_device u8500_cpufreq_cooling_device = { + .name = "db8500-cpufreq-cooling", +}; + +/* * TPS61052 */
@@ -583,6 +645,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { &snowball_key_dev, &snowball_sbnet_dev, &snowball_gpio_en_3v3_regulator_dev, + &u8500_thsens_device, + &u8500_cpufreq_cooling_device, };
static void __init mop500_init_machine(void)
On Tue, Oct 30, 2012 at 10:19 PM, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
Just a minor comment below.
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
Reviewed-by: Viresh Kumar viresh.kumar@linaro.org
arch/arm/boot/dts/dbx5x0.dtsi | 14 +++++++++ arch/arm/boot/dts/snowball.dts | 31 ++++++++++++++++++ arch/arm/configs/u8500_defconfig | 4 +++ arch/arm/mach-ux500/board-mop500.c | 64 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+)
diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index cc5e7a8..34918c4 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
Have you entered these manually?? Or used make savedefconfig?
On 31 October 2012 10:18, viresh kumar viresh.kumar@linaro.org wrote:
On Tue, Oct 30, 2012 at 10:19 PM, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
Just a minor comment below.
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver, also adds the platform data to support the old fashion.
Signed-off-by: hongbo.zhang hongbo.zhang@linaro.com
Reviewed-by: Viresh Kumar viresh.kumar@linaro.org
arch/arm/boot/dts/dbx5x0.dtsi | 14 +++++++++ arch/arm/boot/dts/snowball.dts | 31 ++++++++++++++++++ arch/arm/configs/u8500_defconfig | 4 +++ arch/arm/mach-ux500/board-mop500.c | 64 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+)
diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index cc5e7a8..34918c4 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
Have you entered these manually?? Or used make savedefconfig?
Yes these are added manually, make savedefconfig should be better, but it seems the original defconfig file are not well generated by savedefconfig, this file will be changed too much if I use make savedefconfig. If this is an issue, I will inform the maintainer to update this. After consideration, for this time, I will use savedefconfig to find right places for my configs, discarding the other dis-order ones.
On Tue, Oct 30, 2012 at 10:18 PM, hongbo.zhang hongbo.zhang@linaro.org wrote:
From: "hongbo.zhang" hongbo.zhang@linaro.com
V2->V3 Changes:
- Update ST-Ericsson thermal driver due to review comments from Viresh Kumar
and Francesco Lavra.
You expect people, who want to know what has changed, to go and check our comments? That never happens and so above statement from you must have been more descriptive, detailing all the fixes you have done.
-- viresh