2012-10-16 11:45:55

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH 0/5] Fix thermal bugs and Upstream ST-Ericsson thermal driver

From: "hongbo.zhang" <[email protected]>

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

--
1.7.11.3


2012-10-16 11:46:25

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH 1/5] Thermal: do bind operation after thermal zone or cooling device register returns.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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);
--
1.7.11.3

2012-10-16 11:46:35

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH 2/5] Thermal: add indent for code alignment.

From: "hongbo.zhang" <[email protected]>

Signed-off-by: hongbo.zhang <[email protected]>
---
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);
--
1.7.11.3

2012-10-16 11:46:45

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH 3/5] Thermal: fix empty list checking method.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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);
--
1.7.11.3

2012-10-16 11:46:54

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH 4/5] Thermal: make sure cpufreq cooling register after cpufreq driver

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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++;

--
1.7.11.3

2012-10-16 11:47:05

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _DB8500_THERMAL_H_
+#define _DB8500_THERMAL_H_
+
+#include <linux/thermal.h>
+
+#define COOLING_DEV_MAX 8
+
+struct db8500_trip_point {
+ unsigned long temp;
+ enum thermal_trip_type type;
+ char *cooling_dev_name[COOLING_DEV_MAX];
+};
+
+struct db8500_thsens_platform_data {
+ struct db8500_trip_point *trip_points;
+ int num_trips;
+};
+
+#endif /* _DB8500_THERMAL_H_ */
--
1.7.11.3

2012-10-17 14:21:14

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH 2/5] Thermal: add indent for code alignment.

On 16 October 2012 17:14, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>

People always like to see a commit log here :)

> Signed-off-by: hongbo.zhang <[email protected]>
> ---
> 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 <[email protected]>

2012-10-17 14:24:03

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH 3/5] Thermal: fix empty list checking method.

On 16 October 2012 17:14, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> ---
> 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 <[email protected]>

2012-10-17 14:36:14

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH 4/5] Thermal: make sure cpufreq cooling register after cpufreq driver

On 16 October 2012 17:14, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> ---
> 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

2012-10-17 15:23:19

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

On 16 October 2012 17:14, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> ---
> 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 <[email protected]>
> + *
> + * 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 <[email protected]>");
> +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 <[email protected]>
> + *
> + * 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

2012-10-17 16:58:37

by Joe Perches

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

On Wed, 2012-10-17 at 20:53 +0530, Viresh Kumar wrote:
> On 16 October 2012 17:14, hongbo.zhang <[email protected]> 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)

2012-10-17 17:02:40

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

On 17 October 2012 22:28, Joe Perches <[email protected]> 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

2012-10-18 07:35:26

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

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 <[email protected]> wrote:
> On 16 October 2012 17:14, hongbo.zhang <[email protected]> wrote:
>> From: "hongbo.zhang" <[email protected]>
>>
>> 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 <[email protected]>
>> ---
>> 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 <[email protected]>
>> + *
>> + * 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 <[email protected]>");
>> +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 <[email protected]>
>> + *
>> + * 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

2012-10-18 08:07:57

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

On 18 October 2012 13:05, Hongbo Zhang <[email protected]> wrote:
> On 17 October 2012 23:23, Viresh Kumar <[email protected]> wrote:
>> On 16 October 2012 17:14, hongbo.zhang <[email protected]> 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

2012-10-18 10:45:05

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

On 18 October 2012 16:07, Viresh Kumar <[email protected]> wrote:
> On 18 October 2012 13:05, Hongbo Zhang <[email protected]> wrote:
>> On 17 October 2012 23:23, Viresh Kumar <[email protected]> wrote:
>>> On 16 October 2012 17:14, hongbo.zhang <[email protected]> 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

2012-10-18 18:08:28

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

On Wed, Oct 17, 2012 at 8:53 PM, Viresh Kumar <[email protected]> wrote:
> On 16 October 2012 17:14, hongbo.zhang <[email protected]> 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

2012-10-21 10:03:33

by Francesco Lavra

[permalink] [raw]
Subject: Re: [PATCH 1/5] Thermal: do bind operation after thermal zone or cooling device register returns.

Hi,

On 10/16/2012 01:44 PM, hongbo.zhang wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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

2012-10-21 14:59:27

by Francesco Lavra

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

Hi Hongbo,

On 10/16/2012 01:44 PM, hongbo.zhang wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>

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 <[email protected]>

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 <[email protected]>

Misspelled address

--
Francesco

2012-10-22 12:02:10

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

On 21 October 2012 23:01, Francesco Lavra <[email protected]> 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" <[email protected]>
>>
>> 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 <[email protected]>
>
> 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 <[email protected]>
>
> 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 <[email protected]>
>
> Misspelled address
>
> --
> Francesco

2012-10-22 18:49:19

by Francesco Lavra

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

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

2012-10-23 08:23:46

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH 1/5] Thermal: do bind operation after thermal zone or cooling device register returns.

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 <[email protected]> wrote:
> Hi,
>
> On 10/16/2012 01:44 PM, hongbo.zhang wrote:
>> From: "hongbo.zhang" <[email protected]>
>>
>> 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

2012-10-23 22:11:43

by Francesco Lavra

[permalink] [raw]
Subject: Re: [PATCH 1/5] Thermal: do bind operation after thermal zone or cooling device register returns.

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

2012-10-24 02:37:07

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH 1/5] Thermal: do bind operation after thermal zone or cooling device register returns.

On 24 October 2012 06:13, Francesco Lavra <[email protected]> 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

2012-10-24 04:40:22

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH 5/5] Thermal: Add ST-Ericsson db8500 thermal dirver.

On 23 October 2012 02:51, Francesco Lavra <[email protected]> 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

2012-10-24 11:58:56

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 0/6] Fix thermal bugs and Upstream ST-Ericsson thermal driver

From: "hongbo.zhang" <[email protected]>

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

--
1.7.11.3

2012-10-24 11:59:05

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 1/6] Thermal: add indent for code alignment.

From: "hongbo.zhang" <[email protected]>

The curly bracket should be aligned with corresponding if else statements.

Signed-off-by: hongbo.zhang <[email protected]>
Reviewed-by: Viresh Kumar <[email protected]>
---
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);
--
1.7.11.3

2012-10-24 11:59:12

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 2/6] Thermal: make sure cpufreq cooling register after cpufreq driver

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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++;

--
1.7.11.3

2012-10-24 11:59:26

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 4/6] Thermal: Remove the cooling_cpufreq_list.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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);
--
1.7.11.3

2012-10-24 11:59:36

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
Reviewed-by: Viresh Kumar <[email protected]>
Reviewed-by: Francesco Lavra <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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

2012-10-24 11:59:43

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 6/6] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
.../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)
--
1.7.11.3

2012-10-24 11:59:21

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 3/6] Thermal: fix bug of counting cpu frequencies.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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;
}

--
1.7.11.3

2012-10-24 13:34:46

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V2 3/6] Thermal: fix bug of counting cpu frequencies.

On 24 October 2012 17:28, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>

Good one.

Reviewed-by: Viresh Kumar <[email protected]>

2012-10-24 14:32:08

by Joe Perches

[permalink] [raw]
Subject: Re: [PATCH V2 6/6] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

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 = {

2012-10-24 14:38:47

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 24 October 2012 17:28, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> Reviewed-by: Viresh Kumar <[email protected]>
> Reviewed-by: Francesco Lavra <[email protected]>

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 <[email protected]>
> + *
> + * 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 <[email protected]>");
> +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 <[email protected]>
> + *
> + * 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 <[email protected]>");
> +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 <[email protected]>
> + *
> + * 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_ */

2012-10-24 14:47:14

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V2 6/6] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

On 24 October 2012 17:28, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> ---
> .../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
> [email protected]
> http://lists.linaro.org/mailman/listinfo/linaro-dev

2012-10-25 03:46:23

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 6/6] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

On 24 October 2012 22:32, Joe Perches <[email protected]> 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 = {
>
>

2012-10-25 03:49:51

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 6/6] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

[...]
>> /*
>> + * 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
>> [email protected]
>> http://lists.linaro.org/mailman/listinfo/linaro-dev

2012-10-25 08:26:58

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

[...]
>> +/* 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;
>> + }
>> +
[...]

2012-10-25 08:41:32

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 25 October 2012 13:56, Hongbo Zhang <[email protected]> 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);

2012-10-25 09:33:12

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 25 October 2012 16:41, Viresh Kumar <[email protected]> wrote:
> On 25 October 2012 13:56, Hongbo Zhang <[email protected]> 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);

2012-10-25 09:42:17

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 25 October 2012 15:03, Hongbo Zhang <[email protected]> wrote:
> On 25 October 2012 16:41, Viresh Kumar <[email protected]> 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

2012-10-25 09:56:11

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 25 October 2012 17:33, Hongbo Zhang <[email protected]> wrote:
> On 25 October 2012 16:41, Viresh Kumar <[email protected]> wrote:
>> On 25 October 2012 13:56, Hongbo Zhang <[email protected]> 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);

2012-10-25 10:04:11

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 25 October 2012 15:26, Hongbo Zhang <[email protected]> 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

2012-10-25 10:11:57

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 25 October 2012 15:34, Viresh Kumar <[email protected]> wrote:
> On 25 October 2012 15:26, Hongbo Zhang <[email protected]> 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

2012-10-25 10:43:58

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 25 October 2012 17:42, Viresh Kumar <[email protected]> wrote:
> On 25 October 2012 15:03, Hongbo Zhang <[email protected]> wrote:
>> On 25 October 2012 16:41, Viresh Kumar <[email protected]> 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

2012-10-25 10:45:38

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal dirver.

On 25 October 2012 18:11, Viresh Kumar <[email protected]> wrote:
> On 25 October 2012 15:34, Viresh Kumar <[email protected]> wrote:
>> On 25 October 2012 15:26, Hongbo Zhang <[email protected]> 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

2012-10-25 11:14:56

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal driver.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
.../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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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

2012-10-25 11:16:17

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 6/6] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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)
--
1.7.11.3

2012-10-25 11:39:55

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 6/6] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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)
--
1.7.11.3

2012-10-25 19:12:29

by Francesco Lavra

[permalink] [raw]
Subject: Re: [PATCH V2 4/6] Thermal: Remove the cooling_cpufreq_list

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)
> - mutex_destroy(&cooling_cpufreq_lock);
> kfree(cpufreq_dev);
> }
> EXPORT_SYMBOL(cpufreq_cooling_unregister);
> --
> 1.7.11.3

--
Francesco

2012-10-26 02:59:10

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 4/6] Thermal: Remove the cooling_cpufreq_list

On 26 October 2012 03:14, Francesco Lavra <[email protected]> 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

2012-10-26 07:09:25

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V2 4/6] Thermal: Remove the cooling_cpufreq_list.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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);
--
1.7.11.3

2012-10-27 06:37:11

by Francesco Lavra

[permalink] [raw]
Subject: Re: [PATCH V2 4/6] Thermal: Remove the cooling_cpufreq_list.

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 <[email protected]>

2012-10-27 10:51:06

by Francesco Lavra

[permalink] [raw]
Subject: Re: [PATCH V2 5/6] Thermal: Add ST-Ericsson DB8500 thermal driver.

On Thu Oct 25 11:13:59 2012, Hongbo Zhang wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> ---
> .../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 <[email protected]>

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 <[email protected]>");
> +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 <[email protected]>

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 <[email protected]>");
> +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 <[email protected]>

s/hognbo/hongbo

PS: When re-sending the patch, I'd suggest you re-send the entire patch
series as V3.

Thanks,
Francesco

2012-10-29 11:42:39

by Amit Kachhap

[permalink] [raw]
Subject: Re: [PATCH V2 2/6] Thermal: make sure cpufreq cooling register after cpufreq driver

On 24 October 2012 17:28, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> ---
> 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
> [email protected]
> http://lists.linaro.org/mailman/listinfo/linaro-dev

2012-10-29 11:54:13

by Amit Kachhap

[permalink] [raw]
Subject: Re: [PATCH V2 3/6] Thermal: fix bug of counting cpu frequencies.

On 24 October 2012 19:04, Viresh Kumar <[email protected]> wrote:
> On 24 October 2012 17:28, hongbo.zhang <[email protected]> wrote:
>> From: "hongbo.zhang" <[email protected]>
>>
>> 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 <[email protected]>
>
> Good one.
>
> Reviewed-by: Viresh Kumar <[email protected]>
Changes looks fine. Adding thermal maintainer(Rui Zhang) in the mail list.
Reviewed-by: Amit Daniel Kachhap <[email protected]>

Thanks,
Amit Daniel
>
> _______________________________________________
> linaro-dev mailing list
> [email protected]
> http://lists.linaro.org/mailman/listinfo/linaro-dev

2012-10-30 08:03:28

by Amit Kachhap

[permalink] [raw]
Subject: Re: [PATCH V2 4/6] Thermal: Remove the cooling_cpufreq_list.

On 26 October 2012 12:39, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
>
> Signed-off-by: hongbo.zhang <[email protected]>
> ---
> 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
> [email protected]
> http://lists.linaro.org/mailman/listinfo/linaro-dev

2012-10-30 08:53:08

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 4/6] Thermal: Remove the cooling_cpufreq_list.

On 30 October 2012 09:03, Amit Kachhap <[email protected]> wrote:
> On 26 October 2012 12:39, hongbo.zhang <[email protected]> wrote:
>> From: "hongbo.zhang" <[email protected]>
>>
>> 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 <[email protected]>
Thanks.
>>
>> Signed-off-by: hongbo.zhang <[email protected]>
>> ---
>> 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
>> [email protected]
>> http://lists.linaro.org/mailman/listinfo/linaro-dev

2012-10-30 08:59:26

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V2 2/6] Thermal: make sure cpufreq cooling register after cpufreq driver

On 29 October 2012 12:42, Amit Kachhap <[email protected]> wrote:
> On 24 October 2012 17:28, hongbo.zhang <[email protected]> wrote:
>> From: "hongbo.zhang" <[email protected]>
>>
>> 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 <[email protected]>
>> ---
>> 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
>> [email protected]
>> http://lists.linaro.org/mailman/listinfo/linaro-dev

2012-10-30 16:49:51

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V3 0/5] Fix thermal bugs and Upstream ST-Ericsson thermal driver

From: "hongbo.zhang" <[email protected]>

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

--
1.7.11.3

2012-10-30 16:49:57

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V3 1/5] Thermal: add indent for code alignment.

From: "hongbo.zhang" <[email protected]>

The curly bracket should be aligned with corresponding if else statements.

Signed-off-by: hongbo.zhang <[email protected]>
Reviewed-by: Viresh Kumar <[email protected]>
---
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);
--
1.7.11.3

2012-10-30 16:50:08

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
.../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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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

2012-10-30 16:50:03

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V3 2/5] Thermal: fix bug of counting cpu frequencies.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
Reviewed-by: Viresh Kumar <[email protected]>
Reviewed-by: Amit Daniel Kachhap <[email protected]>
---
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;
}

--
1.7.11.3

2012-10-30 16:50:31

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V3 5/5] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
---
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)
--
1.7.11.3

2012-10-30 16:50:57

by Hongbo Zhang

[permalink] [raw]
Subject: [PATCH V3 3/5] Thermal: Remove the cooling_cpufreq_list.

From: "hongbo.zhang" <[email protected]>

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 <[email protected]>
Reviewed-by: Francesco Lavra <[email protected]>
Reviewed-by: Amit Daniel Kachhap <[email protected]>
---
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);
--
1.7.11.3

2012-10-31 02:08:22

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V3 0/5] Fix thermal bugs and Upstream ST-Ericsson thermal driver

On Tue, Oct 30, 2012 at 10:18 PM, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> V2->V3 Changes:
>
> 2. 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

2012-10-31 02:18:18

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V3 5/5] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

On Tue, Oct 30, 2012 at 10:19 PM, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>

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 <[email protected]>

Reviewed-by: Viresh Kumar <[email protected]>

> ---
> 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?

2012-10-31 02:33:57

by Viresh Kumar

[permalink] [raw]
Subject: Re: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver.

Sorry for late comments :(

On 30 October 2012 22:19, hongbo.zhang <[email protected]> wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> ---
> .../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 <[email protected]>

2012-11-01 14:46:31

by Francesco Lavra

[permalink] [raw]
Subject: Re: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver.

Hi,

On 10/30/2012 05:49 PM, hongbo.zhang wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>

[...]
> +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 <[email protected]>

If you re-send a new version of the patch series, I suggest you do so in
a new thread.

Thanks,
Francesco

2012-11-06 07:25:51

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V3 5/5] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.

On 31 October 2012 10:18, viresh kumar <[email protected]> wrote:
> On Tue, Oct 30, 2012 at 10:19 PM, hongbo.zhang <[email protected]> wrote:
>> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
>
> Reviewed-by: Viresh Kumar <[email protected]>
>
>> ---
>> 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.

2012-11-06 10:17:37

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver.

On 1 November 2012 09:52, Zhang, Rui <[email protected]> wrote:
>
>
>> -----Original Message-----
>> From: hongbo.zhang [mailto:[email protected]]
>> Sent: Wednesday, October 31, 2012 12:49 AM
>> To: [email protected]; [email protected]; linux-
>> [email protected]; Zhang, Rui; [email protected]
>> Cc: [email protected]; [email protected];
>> [email protected]; [email protected];
>> hongbo.zhang
>> Subject: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver.
>> Importance: High
>>
>> From: "hongbo.zhang" <[email protected]>
>>
>> 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 <[email protected]>
>> ---
>> .../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 <[email protected]>
>> + *
>> + * 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 <[email protected]>");
>> +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 <[email protected]>
>> + *
>> + * 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 <[email protected]>");
>> +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 <[email protected]>
>> + *
>> + * 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
>

2012-11-06 10:30:15

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver.

On 6 November 2012 18:17, Hongbo Zhang <[email protected]> wrote:
> On 1 November 2012 09:52, Zhang, Rui <[email protected]> wrote:
>>
>>
>>> -----Original Message-----
>>> From: hongbo.zhang [mailto:[email protected]]
>>> Sent: Wednesday, October 31, 2012 12:49 AM
>>> To: [email protected]; [email protected]; linux-
>>> [email protected]; Zhang, Rui; [email protected]
>>> Cc: [email protected]; [email protected];
>>> [email protected]; [email protected];
>>> hongbo.zhang
>>> Subject: [PATCH V3 4/5] Thermal: Add ST-Ericsson DB8500 thermal driver.
>>> Importance: High
>>>
>>> From: "hongbo.zhang" <[email protected]>
>>>
>>> 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 <[email protected]>
>>> ---
>>> .../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 <[email protected]>
>>> + *
>>> + * 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 <[email protected]>");
>>> +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 <[email protected]>
>>> + *
>>> + * 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 <[email protected]>");
>>> +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 <[email protected]>
>>> + *
>>> + * 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
>>

2012-11-07 06:54:23

by Zhang, Rui

[permalink] [raw]
Subject: Re: [PATCH V3 1/5] Thermal: add indent for code alignment.

On Tue, 2012-10-30 at 17:48 +0100, hongbo.zhang wrote:
> From: "hongbo.zhang" <[email protected]>
>
> The curly bracket should be aligned with corresponding if else statements.
>
> Signed-off-by: hongbo.zhang <[email protected]>
> Reviewed-by: Viresh Kumar <[email protected]>

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);

2012-11-07 06:54:42

by Zhang, Rui

[permalink] [raw]
Subject: Re: [PATCH V3 2/5] Thermal: fix bug of counting cpu frequencies.

On Tue, 2012-10-30 at 17:48 +0100, hongbo.zhang wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> Reviewed-by: Viresh Kumar <[email protected]>
> Reviewed-by: Amit Daniel Kachhap <[email protected]>

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) {
> + *state = --count;
> ret = 0;
> }
>

2012-11-07 06:55:04

by Zhang, Rui

[permalink] [raw]
Subject: Re: [PATCH V3 3/5] Thermal: Remove the cooling_cpufreq_list.

On Tue, 2012-10-30 at 17:48 +0100, hongbo.zhang wrote:
> From: "hongbo.zhang" <[email protected]>
>
> 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 <[email protected]>
> Reviewed-by: Francesco Lavra <[email protected]>
> Reviewed-by: Amit Daniel Kachhap <[email protected]>

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)
> - mutex_destroy(&cooling_cpufreq_lock);
> kfree(cpufreq_dev);
> }
> EXPORT_SYMBOL(cpufreq_cooling_unregister);

2012-11-09 11:54:12

by Hongbo Zhang

[permalink] [raw]
Subject: Re: [PATCH V3 3/5] Thermal: Remove the cooling_cpufreq_list.

On 7 November 2012 14:54, Zhang Rui <[email protected]> wrote:
> On Tue, 2012-10-30 at 17:48 +0100, hongbo.zhang wrote:
>> From: "hongbo.zhang" <[email protected]>
>>
>> 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 <[email protected]>
>> Reviewed-by: Francesco Lavra <[email protected]>
>> Reviewed-by: Amit Daniel Kachhap <[email protected]>
>
> 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);
>
>