Commit 50851c62 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'release' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux

Pull thermal management update from Zhang Rui:
 "Highlights:

   - Introduction of thermal policy support, together with three new
     thermal governors, including step_wise, user_space, fire_share.

   - Introduction of ST-Ericsson db8500_thermal driver and ST-Ericsson
     db8500_cpufreq_cooling driver.

   - Thermal Kconfig file and Makefile refactor.

   - Fixes for generic thermal layer, generic cpucooling, rcar thermal
     driver and Exynos thermal driver."

* 'release' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: (36 commits)
  Thermal: Fix DEFAULT_THERMAL_GOVERNOR
  Thermal: fix a NULL pointer dereference when generic thermal layer is built as a module
  thermal: rcar: add rcar_zone_to_priv() macro
  thermal: rcar: fixup the unit of temperature
  thermal: cpu cooling: allow module builds
  thermal: cpu cooling: use const parameter while registering
  Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.
  Thermal: Add ST-Ericsson DB8500 thermal driver.
  drivers/thermal/Makefile refactor
  Exynos: Add missing dependency
  Refactor drivers/thermal/Kconfig
  thermal: cpu_cooling: Make 'notify_device' static
  Thermal: Remove the cooling_cpufreq_list.
  Thermal: fix bug of counting cpu frequencies.
  Thermal: add indent for code alignment.
  thermal: rcar_thermal: remove explicitly used devm_kfree/iounap()
  thermal: user_space: Add missing static storage class specifiers
  thermal: fair_share: Add missing static storage class specifiers
  thermal: step_wise: Add missing static storage class specifiers
  Thermal: Fix oops and unlocking in thermal_sys.c
  ...
parents 99b8f42e 1f53ef17
* 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, this is required, set it 0 if none,
if greater than 0, the following properties must be defined;
- 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, this is required if trip point N is defined, set it 0 if none,
otherwise the following cooling device names must be defined;
- 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 = <75000>;
trip0-type = "active";
trip0-cdev-num = <1>;
trip0-cdev-name0 = "thermal-cpufreq-0";
trip1-temp = <80000>;
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>;
}
...@@ -112,6 +112,29 @@ temperature) and throttle appropriate devices. ...@@ -112,6 +112,29 @@ temperature) and throttle appropriate devices.
trip: indicates which trip point the cooling devices is associated with trip: indicates which trip point the cooling devices is associated with
in this thermal zone. in this thermal zone.
1.4 Thermal Zone Parameters
1.4.1 struct thermal_bind_params
This structure defines the following parameters that are used to bind
a zone with a cooling device for a particular trip point.
.cdev: The cooling device pointer
.weight: The 'influence' of a particular cooling device on this zone.
This is on a percentage scale. The sum of all these weights
(for a particular zone) cannot exceed 100.
.trip_mask:This is a bit mask that gives the binding relation between
this thermal zone and cdev, for a particular trip point.
If nth bit is set, then the cdev and thermal zone are bound
for trip point n.
.match: This call back returns success(0) if the 'tz and cdev' need to
be bound, as per platform data.
1.4.2 struct thermal_zone_params
This structure defines the platform level parameters for a thermal zone.
This data, for each thermal zone should come from the platform layer.
This is an optional feature where some platforms can choose not to
provide this data.
.governor_name: Name of the thermal governor used for this zone
.num_tbps: Number of thermal_bind_params entries for this zone
.tbp: thermal_bind_params entries
2. sysfs attributes structure 2. sysfs attributes structure
RO read only value RO read only value
...@@ -126,6 +149,7 @@ Thermal zone device sys I/F, created once it's registered: ...@@ -126,6 +149,7 @@ Thermal zone device sys I/F, created once it's registered:
|---type: Type of the thermal zone |---type: Type of the thermal zone
|---temp: Current temperature |---temp: Current temperature
|---mode: Working mode of the thermal zone |---mode: Working mode of the thermal zone
|---policy: Thermal governor used for this zone
|---trip_point_[0-*]_temp: Trip point temperature |---trip_point_[0-*]_temp: Trip point temperature
|---trip_point_[0-*]_type: Trip point type |---trip_point_[0-*]_type: Trip point type
|---trip_point_[0-*]_hyst: Hysteresis value for this trip point |---trip_point_[0-*]_hyst: Hysteresis value for this trip point
...@@ -187,6 +211,10 @@ mode ...@@ -187,6 +211,10 @@ mode
charge of the thermal management. charge of the thermal management.
RW, Optional RW, Optional
policy
One of the various thermal governors used for a particular zone.
RW, Required
trip_point_[0-*]_temp trip_point_[0-*]_temp
The temperature above which trip point will be fired. The temperature above which trip point will be fired.
Unit: millidegree Celsius Unit: millidegree Celsius
...@@ -264,6 +292,7 @@ method, the sys I/F structure will be built like this: ...@@ -264,6 +292,7 @@ method, the sys I/F structure will be built like this:
|---type: acpitz |---type: acpitz
|---temp: 37000 |---temp: 37000
|---mode: enabled |---mode: enabled
|---policy: step_wise
|---trip_point_0_temp: 100000 |---trip_point_0_temp: 100000
|---trip_point_0_type: critical |---trip_point_0_type: critical
|---trip_point_1_temp: 80000 |---trip_point_1_temp: 80000
...@@ -305,3 +334,38 @@ to a thermal_zone_device when it registers itself with the framework. The ...@@ -305,3 +334,38 @@ to a thermal_zone_device when it registers itself with the framework. The
event will be one of:{THERMAL_AUX0, THERMAL_AUX1, THERMAL_CRITICAL, event will be one of:{THERMAL_AUX0, THERMAL_AUX1, THERMAL_CRITICAL,
THERMAL_DEV_FAULT}. Notification can be sent when the current temperature THERMAL_DEV_FAULT}. Notification can be sent when the current temperature
crosses any of the configured thresholds. crosses any of the configured thresholds.
5. Export Symbol APIs:
5.1: get_tz_trend:
This function returns the trend of a thermal zone, i.e the rate of change
of temperature of the thermal zone. Ideally, the thermal sensor drivers
are supposed to implement the callback. If they don't, the thermal
framework calculated the trend by comparing the previous and the current
temperature values.
5.2:get_thermal_instance:
This function returns the thermal_instance corresponding to a given
{thermal_zone, cooling_device, trip_point} combination. Returns NULL
if such an instance does not exist.
5.3:notify_thermal_framework:
This function handles the trip events from sensor drivers. It starts
throttling the cooling devices according to the policy configured.
For CRITICAL and HOT trip points, this notifies the respective drivers,
and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
The throttling policy is based on the configured platform data; if no
platform data is provided, this uses the step_wise throttling policy.
5.4:thermal_cdev_update:
This function serves as an arbitrator to set the state of a cooling
device. It sets the cooling device to the deepest cooling state if
possible.
5.5:thermal_register_governor:
This function lets the various thermal governors to register themselves
with the Thermal framework. At run time, depending on a zone's platform
data, a particular governor is used for throttling.
5.6:thermal_unregister_governor:
This function unregisters a governor from the thermal framework.
...@@ -203,6 +203,14 @@ prcmu-timer-4@80157450 { ...@@ -203,6 +203,14 @@ prcmu-timer-4@80157450 {
reg = <0x80157450 0xC>; 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 { db8500-prcmu-regulators {
compatible = "stericsson,db8500-prcmu-regulator"; compatible = "stericsson,db8500-prcmu-regulator";
...@@ -660,5 +668,11 @@ external-bus@50000000 { ...@@ -660,5 +668,11 @@ external-bus@50000000 {
ranges = <0 0x50000000 0x4000000>; ranges = <0 0x50000000 0x4000000>;
status = "disabled"; status = "disabled";
}; };
cpufreq-cooling {
compatible = "stericsson,db8500-cpufreq-cooling";
status = "disabled";
};
}; };
}; };
...@@ -99,6 +99,33 @@ msp3: msp@80125000 { ...@@ -99,6 +99,33 @@ msp3: msp@80125000 {
status = "okay"; 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 { external-bus@50000000 {
status = "okay"; status = "okay";
...@@ -183,5 +210,9 @@ bh1780@0x29 { ...@@ -183,5 +210,9 @@ bh1780@0x29 {
reg = <0x33>; reg = <0x33>;
}; };
}; };
cpufreq-cooling {
status = "okay";
};
}; };
}; };
...@@ -69,6 +69,8 @@ CONFIG_GPIO_TC3589X=y ...@@ -69,6 +69,8 @@ CONFIG_GPIO_TC3589X=y
CONFIG_POWER_SUPPLY=y CONFIG_POWER_SUPPLY=y
CONFIG_AB8500_BM=y CONFIG_AB8500_BM=y
CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=y CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=y
CONFIG_THERMAL=y
CONFIG_CPU_THERMAL=y
CONFIG_MFD_STMPE=y CONFIG_MFD_STMPE=y
CONFIG_MFD_TC3589X=y CONFIG_MFD_TC3589X=y
CONFIG_AB5500_CORE=y CONFIG_AB5500_CORE=y
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <linux/io.h> #include <linux/io.h>
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/platform_data/i2c-nomadik.h> #include <linux/platform_data/i2c-nomadik.h>
#include <linux/platform_data/db8500_thermal.h>
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/amba/bus.h> #include <linux/amba/bus.h>
#include <linux/amba/pl022.h> #include <linux/amba/pl022.h>
...@@ -228,6 +229,67 @@ static struct ab8500_platform_data ab8500_platdata = { ...@@ -228,6 +229,67 @@ static struct ab8500_platform_data ab8500_platdata = {
.codec = &ab8500_codec_pdata, .codec = &ab8500_codec_pdata,
}; };
/*
* 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 * TPS61052
*/ */
...@@ -583,6 +645,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = { ...@@ -583,6 +645,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = {
&snowball_key_dev, &snowball_key_dev,
&snowball_sbnet_dev, &snowball_sbnet_dev,
&snowball_gpio_en_3v3_regulator_dev, &snowball_gpio_en_3v3_regulator_dev,
&u8500_thsens_device,
&u8500_cpufreq_cooling_device,
}; };
static void __init mop500_init_machine(void) static void __init mop500_init_machine(void)
......
...@@ -900,14 +900,14 @@ static int acpi_thermal_register_thermal_zone(struct acpi_thermal *tz) ...@@ -900,14 +900,14 @@ static int acpi_thermal_register_thermal_zone(struct acpi_thermal *tz)
if (tz->trips.passive.flags.valid) if (tz->trips.passive.flags.valid)
tz->thermal_zone = tz->thermal_zone =
thermal_zone_device_register("acpitz", trips, 0, tz, thermal_zone_device_register("acpitz", trips, 0, tz,
&acpi_thermal_zone_ops, &acpi_thermal_zone_ops, NULL,
tz->trips.passive.tsp*100, tz->trips.passive.tsp*100,
tz->polling_frequency*100); tz->polling_frequency*100);
else else
tz->thermal_zone = tz->thermal_zone =
thermal_zone_device_register("acpitz", trips, 0, tz, thermal_zone_device_register("acpitz", trips, 0, tz,
&acpi_thermal_zone_ops, 0, &acpi_thermal_zone_ops, NULL,
tz->polling_frequency*100); 0, tz->polling_frequency*100);
if (IS_ERR(tz->thermal_zone)) if (IS_ERR(tz->thermal_zone))
return -ENODEV; return -ENODEV;
......
...@@ -662,7 +662,7 @@ static int acerhdf_register_thermal(void) ...@@ -662,7 +662,7 @@ static int acerhdf_register_thermal(void)
return -EINVAL; return -EINVAL;
thz_dev = thermal_zone_device_register("acerhdf", 1, 0, NULL, thz_dev = thermal_zone_device_register("acerhdf", 1, 0, NULL,
&acerhdf_dev_ops, 0, &acerhdf_dev_ops, NULL, 0,
(kernelmode) ? interval*1000 : 0); (kernelmode) ? interval*1000 : 0);
if (IS_ERR(thz_dev)) if (IS_ERR(thz_dev))
return -EINVAL; return -EINVAL;
......
...@@ -502,7 +502,7 @@ static int mid_thermal_probe(struct platform_device *pdev) ...@@ -502,7 +502,7 @@ static int mid_thermal_probe(struct platform_device *pdev)
goto err; goto err;
} }
pinfo->tzd[i] = thermal_zone_device_register(name[i], pinfo->tzd[i] = thermal_zone_device_register(name[i],
0, 0, td_info, &tzd_ops, 0, 0); 0, 0, td_info, &tzd_ops, NULL, 0, 0);
if (IS_ERR(pinfo->tzd[i])) { if (IS_ERR(pinfo->tzd[i])) {
kfree(td_info); kfree(td_info);
ret = PTR_ERR(pinfo->tzd[i]); ret = PTR_ERR(pinfo->tzd[i]);
......
...@@ -201,7 +201,7 @@ static int psy_register_thermal(struct power_supply *psy) ...@@ -201,7 +201,7 @@ static int psy_register_thermal(struct power_supply *psy)
for (i = 0; i < psy->num_properties; i++) { for (i = 0; i < psy->num_properties; i++) {
if (psy->properties[i] == POWER_SUPPLY_PROP_TEMP) { if (psy->properties[i] == POWER_SUPPLY_PROP_TEMP) {
psy->tzd = thermal_zone_device_register(psy->name, 0, 0, psy->tzd = thermal_zone_device_register(psy->name, 0, 0,
psy, &psy_tzd_ops, 0, 0); psy, &psy_tzd_ops, NULL, 0, 0);
if (IS_ERR(psy->tzd)) if (IS_ERR(psy->tzd))
return PTR_ERR(psy->tzd); return PTR_ERR(psy->tzd);
break; break;
......
...@@ -270,7 +270,7 @@ int omap_thermal_expose_sensor(struct omap_bandgap *bg_ptr, int id, ...@@ -270,7 +270,7 @@ int omap_thermal_expose_sensor(struct omap_bandgap *bg_ptr, int id,
/* Create thermal zone */ /* Create thermal zone */
data->omap_thermal = thermal_zone_device_register(domain, data->omap_thermal = thermal_zone_device_register(domain,
OMAP_TRIP_NUMBER, 0, data, &omap_thermal_ops, OMAP_TRIP_NUMBER, 0, data, &omap_thermal_ops,
FAST_TEMP_MONITORING_RATE, NULL, FAST_TEMP_MONITORING_RATE,
FAST_TEMP_MONITORING_RATE); FAST_TEMP_MONITORING_RATE);
if (IS_ERR_OR_NULL(data->omap_thermal)) { if (IS_ERR_OR_NULL(data->omap_thermal)) {
dev_err(bg_ptr->dev, "thermal zone device is NULL\n"); dev_err(bg_ptr->dev, "thermal zone device is NULL\n");
......
...@@ -13,15 +13,62 @@ menuconfig THERMAL ...@@ -13,15 +13,62 @@ menuconfig THERMAL
All platforms with ACPI thermal support can use this driver. All platforms with ACPI thermal support can use this driver.
If you want this support, you should say Y or M here. If you want this support, you should say Y or M here.
if THERMAL
config THERMAL_HWMON config THERMAL_HWMON
bool bool
depends on THERMAL
depends on HWMON=y || HWMON=THERMAL depends on HWMON=y || HWMON=THERMAL
default y default y
choice
prompt "Default Thermal governor"
default THERMAL_DEFAULT_GOV_STEP_WISE
help
This option sets which thermal governor shall be loaded at
startup. If in doubt, select 'step_wise'.
config THERMAL_DEFAULT_GOV_STEP_WISE
bool "step_wise"
select STEP_WISE
help
Use the step_wise governor as default. This throttles the
devices one step at a time.
config THERMAL_DEFAULT_GOV_FAIR_SHARE
bool "fair_share"
select FAIR_SHARE
help
Use the fair_share governor as default. This throttles the
devices based on their 'contribution' to a zone. The
contribution should be provided through platform data.
config THERMAL_DEFAULT_GOV_USER_SPACE
bool "user_space"
select USER_SPACE
help
Select this if you want to let the user space manage the
lpatform thermals.
endchoice
config FAIR_SHARE
bool "Fair-share thermal governor"
help
Enable this to manage platform thermals using fair-share governor.
config STEP_WISE
bool "Step_wise thermal governor"
help
Enable this to manage platform thermals using a simple linear
config USER_SPACE
bool "User_space thermal governor"
help
Enable this to let the user space manage the platform thermals.
config CPU_THERMAL config CPU_THERMAL
bool "generic cpu cooling support" tristate "generic cpu cooling support"
depends on THERMAL && CPU_FREQ depends on CPU_FREQ
select CPU_FREQ_TABLE select CPU_FREQ_TABLE
help help
This implements the generic cpu cooling mechanism through frequency This implements the generic cpu cooling mechanism through frequency
...@@ -33,7 +80,6 @@ config CPU_THERMAL ...@@ -33,7 +80,6 @@ config CPU_THERMAL
config SPEAR_THERMAL config SPEAR_THERMAL
bool "SPEAr thermal sensor driver" bool "SPEAr thermal sensor driver"
depends on THERMAL
depends on PLAT_SPEAR depends on PLAT_SPEAR
depends on OF depends on OF
help help
...@@ -42,7 +88,6 @@ config SPEAR_THERMAL ...@@ -42,7 +88,6 @@ config SPEAR_THERMAL
config RCAR_THERMAL config RCAR_THERMAL
tristate "Renesas R-Car thermal driver" tristate "Renesas R-Car thermal driver"
depends on THERMAL
depends on ARCH_SHMOBILE depends on ARCH_SHMOBILE
help help
Enable this to plug the R-Car thermal sensor driver into the Linux Enable this to plug the R-Car thermal sensor driver into the Linux
...@@ -50,8 +95,31 @@ config RCAR_THERMAL ...@@ -50,8 +95,31 @@ config RCAR_THERMAL
config EXYNOS_THERMAL config EXYNOS_THERMAL
tristate "Temperature sensor on Samsung EXYNOS" tristate "Temperature sensor on Samsung EXYNOS"
depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) && THERMAL depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5)
select CPU_FREQ_TABLE depends on CPU_THERMAL
help help
If you say yes here you get support for TMU (Thermal Managment If you say yes here you get support for TMU (Thermal Managment
Unit) on SAMSUNG EXYNOS series of SoC. Unit) on SAMSUNG EXYNOS series of SoC.
config DB8500_THERMAL
bool "DB8500 thermal management"
depends on ARCH_U8500
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 ARCH_U8500
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.
endif
...@@ -3,7 +3,18 @@ ...@@ -3,7 +3,18 @@
# #
obj-$(CONFIG_THERMAL) += thermal_sys.o obj-$(CONFIG_THERMAL) += thermal_sys.o
obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o # governors
obj-$(CONFIG_FAIR_SHARE) += fair_share.o
obj-$(CONFIG_STEP_WISE) += step_wise.o
obj-$(CONFIG_USER_SPACE) += user_space.o
# cpufreq cooling
obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
# platform thermal drivers
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
obj-$(CONFIG_EXYNOS_THERMAL) += exynos_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
...@@ -58,12 +58,13 @@ struct cpufreq_cooling_device { ...@@ -58,12 +58,13 @@ struct cpufreq_cooling_device {
}; };
static LIST_HEAD(cooling_cpufreq_list); static LIST_HEAD(cooling_cpufreq_list);
static DEFINE_IDR(cpufreq_idr); 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. */ /* notify_table passes value to the CPUFREQ_ADJUST callback function. */
#define NOTIFY_INVALID NULL #define NOTIFY_INVALID NULL
struct cpufreq_cooling_device *notify_device; static struct cpufreq_cooling_device *notify_device;
/** /**
* get_idr - function to get a unique id. * get_idr - function to get a unique id.
...@@ -240,42 +241,32 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, ...@@ -240,42 +241,32 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb,
static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state) unsigned long *state)
{ {
int ret = -EINVAL, i = 0; struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
struct cpufreq_cooling_device *cpufreq_device; struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
struct cpumask *maskPtr;
unsigned int cpu; unsigned int cpu;
struct cpufreq_frequency_table *table; 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); cpu = cpumask_any(maskPtr);
table = cpufreq_frequency_get_table(cpu); table = cpufreq_frequency_get_table(cpu);
if (!table) { if (!table) {
*state = 0; *state = 0;
ret = 0; return 0;
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) if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
continue; continue;
i++; count++;
} }
if (i > 0) {
*state = --i; if (count > 0) {
ret = 0; *state = --count;
return 0;
} }
return_get_max_state: return -EINVAL;
mutex_unlock(&cooling_cpufreq_lock);
return ret;
} }
/** /**
...@@ -286,20 +277,10 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, ...@@ -286,20 +277,10 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state) unsigned long *state)
{ {
int ret = -EINVAL; struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
struct cpufreq_cooling_device *cpufreq_device;
mutex_lock(&cooling_cpufreq_lock); *state = cpufreq_device->cpufreq_state;
list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { return 0;
if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
*state = cpufreq_device->cpufreq_state;
ret = 0;
break;
}
}
mutex_unlock(&cooling_cpufreq_lock);
return ret;
} }
/** /**
...@@ -310,22 +291,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, ...@@ -310,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, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state) unsigned long state)
{ {
int ret = -EINVAL; struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
struct cpufreq_cooling_device *cpufreq_device;
mutex_lock(&cooling_cpufreq_lock); return cpufreq_apply_cooling(cpufreq_device, state);
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;
} }
/* Bind cpufreq callbacks to thermal cooling device ops */ /* Bind cpufreq callbacks to thermal cooling device ops */
...@@ -345,18 +313,15 @@ static struct notifier_block thermal_cpufreq_notifier_block = { ...@@ -345,18 +313,15 @@ static struct notifier_block thermal_cpufreq_notifier_block = {
* @clip_cpus: cpumask of cpus where the frequency constraints will happen. * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
*/ */
struct thermal_cooling_device *cpufreq_cooling_register( struct thermal_cooling_device *cpufreq_cooling_register(
struct cpumask *clip_cpus) const struct cpumask *clip_cpus)
{ {
struct thermal_cooling_device *cool_dev; struct thermal_cooling_device *cool_dev;
struct cpufreq_cooling_device *cpufreq_dev = NULL; 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]; char dev_name[THERMAL_NAME_LENGTH];
int ret = 0, i; int ret = 0, i;
struct cpufreq_policy policy; 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*/ /*Verify that all the clip cpus have same freq_min, freq_max limit*/
for_each_cpu(i, clip_cpus) { for_each_cpu(i, clip_cpus) {
/*continue if cpufreq policy not found and not return error*/ /*continue if cpufreq policy not found and not return error*/
...@@ -369,7 +334,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( ...@@ -369,7 +334,7 @@ struct thermal_cooling_device *cpufreq_cooling_register(
if (min != policy.cpuinfo.min_freq || if (min != policy.cpuinfo.min_freq ||
max != policy.cpuinfo.max_freq) max != policy.cpuinfo.max_freq)
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
} }
} }
cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device), cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device),
GFP_KERNEL); GFP_KERNEL);
...@@ -378,9 +343,6 @@ struct thermal_cooling_device *cpufreq_cooling_register( ...@@ -378,9 +343,6 @@ struct thermal_cooling_device *cpufreq_cooling_register(
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus); 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); ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
if (ret) { if (ret) {
kfree(cpufreq_dev); kfree(cpufreq_dev);
...@@ -399,12 +361,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( ...@@ -399,12 +361,12 @@ struct thermal_cooling_device *cpufreq_cooling_register(
cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cool_dev = cool_dev;
cpufreq_dev->cpufreq_state = 0; cpufreq_dev->cpufreq_state = 0;
mutex_lock(&cooling_cpufreq_lock); mutex_lock(&cooling_cpufreq_lock);
list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
/* Register the notifier for first cpufreq cooling device */ /* Register the notifier for first cpufreq cooling device */
if (cpufreq_dev_count == 0) if (cpufreq_dev_count == 0)
cpufreq_register_notifier(&thermal_cpufreq_notifier_block, cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
CPUFREQ_POLICY_NOTIFIER); CPUFREQ_POLICY_NOTIFIER);
cpufreq_dev_count++;
mutex_unlock(&cooling_cpufreq_lock); mutex_unlock(&cooling_cpufreq_lock);
return cool_dev; return cool_dev;
...@@ -417,33 +379,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register); ...@@ -417,33 +379,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register);
*/ */
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
{ {
struct cpufreq_cooling_device *cpufreq_dev = NULL; struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata;
unsigned int cpufreq_dev_count = 0;
mutex_lock(&cooling_cpufreq_lock); mutex_lock(&cooling_cpufreq_lock);
list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) { cpufreq_dev_count--;
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);
/* Unregister the notifier for the last cpufreq cooling device */ /* 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_unregister_notifier(&thermal_cpufreq_notifier_block,
CPUFREQ_POLICY_NOTIFIER); CPUFREQ_POLICY_NOTIFIER);
} }
mutex_unlock(&cooling_cpufreq_lock); mutex_unlock(&cooling_cpufreq_lock);
thermal_cooling_device_unregister(cpufreq_dev->cool_dev); thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
release_idr(&cpufreq_idr, cpufreq_dev->id); release_idr(&cpufreq_idr, cpufreq_dev->id);
if (cpufreq_dev_count == 1)
mutex_destroy(&cooling_cpufreq_lock);
kfree(cpufreq_dev); kfree(cpufreq_dev);
} }
EXPORT_SYMBOL(cpufreq_cooling_unregister); EXPORT_SYMBOL(cpufreq_cooling_unregister);
/*
* db8500_cpufreq_cooling.c - DB8500 cpufreq works as cooling device.
*
* Copyright (C) 2012 ST-Ericsson
* Copyright (C) 2012 Linaro Ltd.
*
* Author: Hongbo Zhang <hongbo.zhang@linaro.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/cpu_cooling.h>
#include <linux/cpufreq.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
static int db8500_cpufreq_cooling_probe(struct platform_device *pdev)
{
struct thermal_cooling_device *cdev;
struct cpumask mask_val;
/* make sure cpufreq driver has been initialized */
if (!cpufreq_frequency_get_table(0))
return -EPROBE_DEFER;
cpumask_set_cpu(0, &mask_val);
cdev = cpufreq_cooling_register(&mask_val);
if (IS_ERR_OR_NULL(cdev)) {
dev_err(&pdev->dev, "Failed to register cooling device\n");
return PTR_ERR(cdev);
}
platform_set_drvdata(pdev, cdev);
dev_info(&pdev->dev, "Cooling device registered: %s\n", cdev->type);
return 0;
}
static int db8500_cpufreq_cooling_remove(struct platform_device *pdev)
{
struct thermal_cooling_device *cdev = platform_get_drvdata(pdev);
cpufreq_cooling_unregister(cdev);
return 0;
}
static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
pm_message_t state)
{
return -ENOSYS;
}
static int db8500_cpufreq_cooling_resume(struct platform_device *pdev)
{
return -ENOSYS;
}
#ifdef CONFIG_OF
static const struct of_device_id db8500_cpufreq_cooling_match[] = {
{ .compatible = "stericsson,db8500-cpufreq-cooling" },
{},
};
#else
#define db8500_cpufreq_cooling_match NULL
#endif
static struct platform_driver db8500_cpufreq_cooling_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-cpufreq-cooling",
.of_match_table = db8500_cpufreq_cooling_match,
},
.probe = db8500_cpufreq_cooling_probe,
.suspend = db8500_cpufreq_cooling_suspend,
.resume = db8500_cpufreq_cooling_resume,
.remove = db8500_cpufreq_cooling_remove,
};
static int __init db8500_cpufreq_cooling_init(void)
{
return platform_driver_register(&db8500_cpufreq_cooling_driver);
}
static void __exit db8500_cpufreq_cooling_exit(void)
{
platform_driver_unregister(&db8500_cpufreq_cooling_driver);
}
/* Should be later than db8500_cpufreq_register */
late_initcall(db8500_cpufreq_cooling_init);
module_exit(db8500_cpufreq_cooling_exit);
MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
MODULE_DESCRIPTION("DB8500 cpufreq cooling driver");
MODULE_LICENSE("GPL");
/*
* db8500_thermal.c - DB8500 Thermal Management Implementation
*
* Copyright (C) 2012 ST-Ericsson
* Copyright (C) 2012 Linaro Ltd.
*
* Author: Hongbo Zhang <hongbo.zhang@linaro.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/cpu_cooling.h>
#include <linux/interrupt.h>
#include <linux/mfd/dbx500-prcmu.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_data/db8500_thermal.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
#define PRCMU_DEFAULT_LOW_TEMP 0
struct db8500_thermal_zone {
struct thermal_zone_device *therm_dev;
struct mutex th_lock;
struct work_struct therm_work;
struct db8500_thsens_platform_data *trip_tab;
enum thermal_device_mode mode;
enum thermal_trend trend;
unsigned long cur_temp_pseudo;
unsigned int cur_index;
};
/* Local function to check if thermal zone matches cooling devices */
static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
struct db8500_trip_point *trip_point)
{
int i;
if (!strlen(cdev->type))
return -EINVAL;
for (i = 0; i < COOLING_DEV_MAX; i++) {
if (!strcmp(trip_point->cdev_name[i], 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;
cdev->ops->get_max_state(cdev, &max_state);
for (i = 0; i < ptrips->num_trips; i++) {
if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
continue;
upper = lower = 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 from %d: %s\n", cdev->type,
i, 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)
{
prcmu_stop_temp_sense();
pzone->cur_index = idx;
pzone->cur_temp_pseudo = (next_low + next_high)/2;
pzone->trend = trend;
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;
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, NULL, 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);
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(&pzone->therm_work);
prcmu_stop_temp_sense();
return 0;
}
static int db8500_thermal_resume(struct platform_device *pdev)
{
struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
unsigned long dft_low, dft_high;
dft_low = PRCMU_DEFAULT_LOW_TEMP;
dft_high = ptrips->trip_points[0].temp;
db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
dft_low, dft_high);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id db8500_thermal_match[] = {
{ .compatible = "stericsson,db8500-thermal" },
{},
};
#else
#define db8500_thermal_match NULL
#endif
static struct platform_driver db8500_thermal_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "db8500-thermal",
.of_match_table = db8500_thermal_match,
},
.probe = db8500_thermal_probe,
.suspend = db8500_thermal_suspend,
.resume = db8500_thermal_resume,
.remove = db8500_thermal_remove,
};
module_platform_driver(db8500_thermal_driver);
MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
MODULE_DESCRIPTION("DB8500 thermal driver");
MODULE_LICENSE("GPL");
...@@ -451,7 +451,7 @@ static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) ...@@ -451,7 +451,7 @@ static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
th_zone->cool_dev_size++; th_zone->cool_dev_size++;
th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name, th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, 0, EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, NULL, 0,
IDLE_INTERVAL); IDLE_INTERVAL);
if (IS_ERR(th_zone->therm_dev)) { if (IS_ERR(th_zone->therm_dev)) {
......
/*
* fair_share.c - A simple weight based Thermal governor
*
* Copyright (C) 2012 Intel Corp
* Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/thermal.h>
#include "thermal_core.h"
/**
* get_trip_level: - obtains the current trip level for a zone
* @tz: thermal zone device
*/
static int get_trip_level(struct thermal_zone_device *tz)
{
int count = 0;
unsigned long trip_temp;
if (tz->trips == 0 || !tz->ops->get_trip_temp)
return 0;
for (count = 0; count < tz->trips; count++) {
tz->ops->get_trip_temp(tz, count, &trip_temp);
if (tz->temperature < trip_temp)
break;
}
return count;
}
static long get_target_state(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev, int weight, int level)
{
unsigned long max_state;
cdev->ops->get_max_state(cdev, &max_state);
return (long)(weight * level * max_state) / (100 * tz->trips);
}
/**
* fair_share_throttle - throttles devices asscciated with the given zone
* @tz - thermal_zone_device
*
* Throttling Logic: This uses three parameters to calculate the new
* throttle state of the cooling devices associated with the given zone.
*
* Parameters used for Throttling:
* P1. max_state: Maximum throttle state exposed by the cooling device.
* P2. weight[i]/100:
* How 'effective' the 'i'th device is, in cooling the given zone.
* P3. cur_trip_level/max_no_of_trips:
* This describes the extent to which the devices should be throttled.
* We do not want to throttle too much when we trip a lower temperature,
* whereas the throttling is at full swing if we trip critical levels.
* (Heavily assumes the trip points are in ascending order)
* new_state of cooling device = P3 * P2 * P1
*/
static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
{
const struct thermal_zone_params *tzp;
struct thermal_cooling_device *cdev;
struct thermal_instance *instance;
int i;
int cur_trip_level = get_trip_level(tz);
if (!tz->tzp || !tz->tzp->tbp)
return -EINVAL;
tzp = tz->tzp;
for (i = 0; i < tzp->num_tbps; i++) {
if (!tzp->tbp[i].cdev)
continue;
cdev = tzp->tbp[i].cdev;
instance = get_thermal_instance(tz, cdev, trip);
if (!instance)
continue;
instance->target = get_target_state(tz, cdev,
tzp->tbp[i].weight, cur_trip_level);
instance->cdev->updated = false;
thermal_cdev_update(cdev);
}
return 0;
}
static struct thermal_governor thermal_gov_fair_share = {
.name = "fair_share",
.throttle = fair_share_throttle,
.owner = THIS_MODULE,
};
static int __init thermal_gov_fair_share_init(void)
{
return thermal_register_governor(&thermal_gov_fair_share);
}
static void __exit thermal_gov_fair_share_exit(void)
{
thermal_unregister_governor(&thermal_gov_fair_share);
}
/* This should load after thermal framework */
fs_initcall(thermal_gov_fair_share_init);
module_exit(thermal_gov_fair_share_exit);
MODULE_AUTHOR("Durgadoss R");
MODULE_DESCRIPTION("A simple weight based thermal throttling governor");
MODULE_LICENSE("GPL");
...@@ -43,6 +43,9 @@ struct rcar_thermal_priv { ...@@ -43,6 +43,9 @@ struct rcar_thermal_priv {
u32 comp; u32 comp;
}; };
#define MCELSIUS(temp) ((temp) * 1000)
#define rcar_zone_to_priv(zone) (zone->devdata)
/* /*
* basic functions * basic functions
*/ */
...@@ -96,7 +99,7 @@ static void rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg, ...@@ -96,7 +99,7 @@ static void rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg,
static int rcar_thermal_get_temp(struct thermal_zone_device *zone, static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
unsigned long *temp) unsigned long *temp)
{ {
struct rcar_thermal_priv *priv = zone->devdata; struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
int val, min, max, tmp; int val, min, max, tmp;
tmp = -200; /* default */ tmp = -200; /* default */
...@@ -169,7 +172,7 @@ static int rcar_thermal_get_temp(struct thermal_zone_device *zone, ...@@ -169,7 +172,7 @@ static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
} }
} }
*temp = tmp; *temp = MCELSIUS(tmp);
return 0; return 0;
} }
...@@ -185,7 +188,6 @@ static int rcar_thermal_probe(struct platform_device *pdev) ...@@ -185,7 +188,6 @@ static int rcar_thermal_probe(struct platform_device *pdev)
struct thermal_zone_device *zone; struct thermal_zone_device *zone;
struct rcar_thermal_priv *priv; struct rcar_thermal_priv *priv;
struct resource *res; struct resource *res;
int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) { if (!res) {
...@@ -206,16 +208,14 @@ static int rcar_thermal_probe(struct platform_device *pdev) ...@@ -206,16 +208,14 @@ static int rcar_thermal_probe(struct platform_device *pdev)
res->start, resource_size(res)); res->start, resource_size(res));
if (!priv->base) { if (!priv->base) {
dev_err(&pdev->dev, "Unable to ioremap thermal register\n"); dev_err(&pdev->dev, "Unable to ioremap thermal register\n");
ret = -ENOMEM; return -ENOMEM;
goto error_free_priv;
} }
zone = thermal_zone_device_register("rcar_thermal", 0, 0, priv, zone = thermal_zone_device_register("rcar_thermal", 0, 0, priv,
&rcar_thermal_zone_ops, 0, 0); &rcar_thermal_zone_ops, NULL, 0, 0);
if (IS_ERR(zone)) { if (IS_ERR(zone)) {
dev_err(&pdev->dev, "thermal zone device is NULL\n"); dev_err(&pdev->dev, "thermal zone device is NULL\n");
ret = PTR_ERR(zone); return PTR_ERR(zone);
goto error_iounmap;
} }
platform_set_drvdata(pdev, zone); platform_set_drvdata(pdev, zone);
...@@ -223,26 +223,15 @@ static int rcar_thermal_probe(struct platform_device *pdev) ...@@ -223,26 +223,15 @@ static int rcar_thermal_probe(struct platform_device *pdev)
dev_info(&pdev->dev, "proved\n"); dev_info(&pdev->dev, "proved\n");
return 0; return 0;
error_iounmap:
devm_iounmap(&pdev->dev, priv->base);
error_free_priv:
devm_kfree(&pdev->dev, priv);
return ret;
} }
static int rcar_thermal_remove(struct platform_device *pdev) static int rcar_thermal_remove(struct platform_device *pdev)
{ {
struct thermal_zone_device *zone = platform_get_drvdata(pdev); struct thermal_zone_device *zone = platform_get_drvdata(pdev);
struct rcar_thermal_priv *priv = zone->devdata;
thermal_zone_device_unregister(zone); thermal_zone_device_unregister(zone);
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
devm_iounmap(&pdev->dev, priv->base);
devm_kfree(&pdev->dev, priv);
return 0; return 0;
} }
......
...@@ -147,7 +147,7 @@ static int spear_thermal_probe(struct platform_device *pdev) ...@@ -147,7 +147,7 @@ static int spear_thermal_probe(struct platform_device *pdev)
writel_relaxed(stdev->flags, stdev->thermal_base); writel_relaxed(stdev->flags, stdev->thermal_base);
spear_thermal = thermal_zone_device_register("spear_thermal", 0, 0, spear_thermal = thermal_zone_device_register("spear_thermal", 0, 0,
stdev, &ops, 0, 0); stdev, &ops, NULL, 0, 0);
if (IS_ERR(spear_thermal)) { if (IS_ERR(spear_thermal)) {
dev_err(&pdev->dev, "thermal zone device is NULL\n"); dev_err(&pdev->dev, "thermal zone device is NULL\n");
ret = PTR_ERR(spear_thermal); ret = PTR_ERR(spear_thermal);
......
/*
* step_wise.c - A step-by-step Thermal throttling governor
*
* Copyright (C) 2012 Intel Corp
* Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/thermal.h>
#include "thermal_core.h"
/*
* If the temperature is higher than a trip point,
* a. if the trend is THERMAL_TREND_RAISING, use higher cooling
* state for this trip point
* b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
* state for this trip point
*/
static unsigned long get_target_state(struct thermal_instance *instance,
enum thermal_trend trend)
{
struct thermal_cooling_device *cdev = instance->cdev;
unsigned long cur_state;
cdev->ops->get_cur_state(cdev, &cur_state);
if (trend == THERMAL_TREND_RAISING) {
cur_state = cur_state < instance->upper ?
(cur_state + 1) : instance->upper;
} else if (trend == THERMAL_TREND_DROPPING) {
cur_state = cur_state > instance->lower ?
(cur_state - 1) : instance->lower;
}
return cur_state;
}
static void update_passive_instance(struct thermal_zone_device *tz,
enum thermal_trip_type type, int value)
{
/*
* If value is +1, activate a passive instance.
* If value is -1, deactivate a passive instance.
*/
if (type == THERMAL_TRIP_PASSIVE || type == THERMAL_TRIPS_NONE)
tz->passive += value;
}
static void update_instance_for_throttle(struct thermal_zone_device *tz,
int trip, enum thermal_trip_type trip_type,
enum thermal_trend trend)
{
struct thermal_instance *instance;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)
continue;
instance->target = get_target_state(instance, trend);
/* Activate a passive thermal instance */
if (instance->target == THERMAL_NO_TARGET)
update_passive_instance(tz, trip_type, 1);
instance->cdev->updated = false; /* cdev needs update */
}
}
static void update_instance_for_dethrottle(struct thermal_zone_device *tz,
int trip, enum thermal_trip_type trip_type)
{
struct thermal_instance *instance;
struct thermal_cooling_device *cdev;
unsigned long cur_state;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip ||
instance->target == THERMAL_NO_TARGET)
continue;
cdev = instance->cdev;
cdev->ops->get_cur_state(cdev, &cur_state);
instance->target = cur_state > instance->lower ?
(cur_state - 1) : THERMAL_NO_TARGET;
/* Deactivate a passive thermal instance */
if (instance->target == THERMAL_NO_TARGET)
update_passive_instance(tz, trip_type, -1);
cdev->updated = false; /* cdev needs update */
}
}
static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
{
long trip_temp;
enum thermal_trip_type trip_type;
enum thermal_trend trend;
if (trip == THERMAL_TRIPS_NONE) {
trip_temp = tz->forced_passive;
trip_type = THERMAL_TRIPS_NONE;
} else {
tz->ops->get_trip_temp(tz, trip, &trip_temp);
tz->ops->get_trip_type(tz, trip, &trip_type);
}
trend = get_tz_trend(tz, trip);
mutex_lock(&tz->lock);
if (tz->temperature >= trip_temp)
update_instance_for_throttle(tz, trip, trip_type, trend);
else
update_instance_for_dethrottle(tz, trip, trip_type);
mutex_unlock(&tz->lock);
}
/**
* step_wise_throttle - throttles devices asscciated with the given zone
* @tz - thermal_zone_device
* @trip - the trip point
* @trip_type - type of the trip point
*
* Throttling Logic: This uses the trend of the thermal zone to throttle.
* If the thermal zone is 'heating up' this throttles all the cooling
* devices associated with the zone and its particular trip point, by one
* step. If the zone is 'cooling down' it brings back the performance of
* the devices by one step.
*/
static int step_wise_throttle(struct thermal_zone_device *tz, int trip)
{
struct thermal_instance *instance;
thermal_zone_trip_update(tz, trip);
if (tz->forced_passive)
thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE);
mutex_lock(&tz->lock);
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
thermal_cdev_update(instance->cdev);
mutex_unlock(&tz->lock);
return 0;
}
static struct thermal_governor thermal_gov_step_wise = {
.name = "step_wise",
.throttle = step_wise_throttle,
.owner = THIS_MODULE,
};
static int __init thermal_gov_step_wise_init(void)
{
return thermal_register_governor(&thermal_gov_step_wise);
}
static void __exit thermal_gov_step_wise_exit(void)
{
thermal_unregister_governor(&thermal_gov_step_wise);
}
/* This should load after thermal framework */
fs_initcall(thermal_gov_step_wise_init);
module_exit(thermal_gov_step_wise_exit);
MODULE_AUTHOR("Durgadoss R");
MODULE_DESCRIPTION("A step-by-step thermal throttling governor");
MODULE_LICENSE("GPL");
/*
* thermal_core.h
*
* Copyright (C) 2012 Intel Corp
* Author: Durgadoss R <durgadoss.r@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#ifndef __THERMAL_CORE_H__
#define __THERMAL_CORE_H__
#include <linux/device.h>
#include <linux/thermal.h>
/* Initial state of a cooling device during binding */
#define THERMAL_NO_TARGET -1UL
/*
* This structure is used to describe the behavior of
* a certain cooling device on a certain trip point
* in a certain thermal zone
*/
struct thermal_instance {
int id;
char name[THERMAL_NAME_LENGTH];
struct thermal_zone_device *tz;
struct thermal_cooling_device *cdev;
int trip;
unsigned long upper; /* Highest cooling state for this trip point */
unsigned long lower; /* Lowest cooling state for this trip point */
unsigned long target; /* expected cooling state */
char attr_name[THERMAL_NAME_LENGTH];
struct device_attribute attr;
struct list_head tz_node; /* node in tz->thermal_instances */
struct list_head cdev_node; /* node in cdev->thermal_instances */
};
#endif /* __THERMAL_CORE_H__ */
...@@ -37,38 +37,98 @@ ...@@ -37,38 +37,98 @@
#include <net/netlink.h> #include <net/netlink.h>
#include <net/genetlink.h> #include <net/genetlink.h>
#include "thermal_core.h"
MODULE_AUTHOR("Zhang Rui"); MODULE_AUTHOR("Zhang Rui");
MODULE_DESCRIPTION("Generic thermal management sysfs support"); MODULE_DESCRIPTION("Generic thermal management sysfs support");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
#define THERMAL_NO_TARGET -1UL
/*
* This structure is used to describe the behavior of
* a certain cooling device on a certain trip point
* in a certain thermal zone
*/
struct thermal_instance {
int id;
char name[THERMAL_NAME_LENGTH];
struct thermal_zone_device *tz;
struct thermal_cooling_device *cdev;
int trip;
unsigned long upper; /* Highest cooling state for this trip point */
unsigned long lower; /* Lowest cooling state for this trip point */
unsigned long target; /* expected cooling state */
char attr_name[THERMAL_NAME_LENGTH];
struct device_attribute attr;
struct list_head tz_node; /* node in tz->thermal_instances */
struct list_head cdev_node; /* node in cdev->thermal_instances */
};
static DEFINE_IDR(thermal_tz_idr); static DEFINE_IDR(thermal_tz_idr);
static DEFINE_IDR(thermal_cdev_idr); static DEFINE_IDR(thermal_cdev_idr);
static DEFINE_MUTEX(thermal_idr_lock); static DEFINE_MUTEX(thermal_idr_lock);
static LIST_HEAD(thermal_tz_list); static LIST_HEAD(thermal_tz_list);
static LIST_HEAD(thermal_cdev_list); static LIST_HEAD(thermal_cdev_list);
static LIST_HEAD(thermal_governor_list);
static DEFINE_MUTEX(thermal_list_lock); static DEFINE_MUTEX(thermal_list_lock);
static DEFINE_MUTEX(thermal_governor_lock);
static struct thermal_governor *__find_governor(const char *name)
{
struct thermal_governor *pos;
list_for_each_entry(pos, &thermal_governor_list, governor_list)
if (!strnicmp(name, pos->name, THERMAL_NAME_LENGTH))
return pos;
return NULL;
}
int thermal_register_governor(struct thermal_governor *governor)
{
int err;
const char *name;
struct thermal_zone_device *pos;
if (!governor)
return -EINVAL;
mutex_lock(&thermal_governor_lock);
err = -EBUSY;
if (__find_governor(governor->name) == NULL) {
err = 0;
list_add(&governor->governor_list, &thermal_governor_list);
}
mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) {
if (pos->governor)
continue;
if (pos->tzp)
name = pos->tzp->governor_name;
else
name = DEFAULT_THERMAL_GOVERNOR;
if (!strnicmp(name, governor->name, THERMAL_NAME_LENGTH))
pos->governor = governor;
}
mutex_unlock(&thermal_list_lock);
mutex_unlock(&thermal_governor_lock);
return err;
}
EXPORT_SYMBOL_GPL(thermal_register_governor);
void thermal_unregister_governor(struct thermal_governor *governor)
{
struct thermal_zone_device *pos;
if (!governor)
return;
mutex_lock(&thermal_governor_lock);
if (__find_governor(governor->name) == NULL)
goto exit;
mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) {
if (!strnicmp(pos->governor->name, governor->name,
THERMAL_NAME_LENGTH))
pos->governor = NULL;
}
mutex_unlock(&thermal_list_lock);
list_del(&governor->governor_list);
exit:
mutex_unlock(&thermal_governor_lock);
return;
}
EXPORT_SYMBOL_GPL(thermal_unregister_governor);
static int get_idr(struct idr *idr, struct mutex *lock, int *id) static int get_idr(struct idr *idr, struct mutex *lock, int *id)
{ {
...@@ -101,6 +161,262 @@ static void release_idr(struct idr *idr, struct mutex *lock, int id) ...@@ -101,6 +161,262 @@ static void release_idr(struct idr *idr, struct mutex *lock, int id)
mutex_unlock(lock); mutex_unlock(lock);
} }
int get_tz_trend(struct thermal_zone_device *tz, int trip)
{
enum thermal_trend trend;
if (!tz->ops->get_trend || tz->ops->get_trend(tz, trip, &trend)) {
if (tz->temperature > tz->last_temperature)
trend = THERMAL_TREND_RAISING;
else if (tz->temperature < tz->last_temperature)
trend = THERMAL_TREND_DROPPING;
else
trend = THERMAL_TREND_STABLE;
}
return trend;
}
EXPORT_SYMBOL(get_tz_trend);
struct thermal_instance *get_thermal_instance(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev, int trip)
{
struct thermal_instance *pos = NULL;
struct thermal_instance *target_instance = NULL;
mutex_lock(&tz->lock);
mutex_lock(&cdev->lock);
list_for_each_entry(pos, &tz->thermal_instances, tz_node) {
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
target_instance = pos;
break;
}
}
mutex_unlock(&cdev->lock);
mutex_unlock(&tz->lock);
return target_instance;
}
EXPORT_SYMBOL(get_thermal_instance);
static void print_bind_err_msg(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev, int ret)
{
dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
tz->type, cdev->type, ret);
}
static void __bind(struct thermal_zone_device *tz, int mask,
struct thermal_cooling_device *cdev)
{
int i, ret;
for (i = 0; i < tz->trips; i++) {
if (mask & (1 << i)) {
ret = thermal_zone_bind_cooling_device(tz, i, cdev,
THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
if (ret)
print_bind_err_msg(tz, cdev, ret);
}
}
}
static void __unbind(struct thermal_zone_device *tz, int mask,
struct thermal_cooling_device *cdev)
{
int i;
for (i = 0; i < tz->trips; i++)
if (mask & (1 << i))
thermal_zone_unbind_cooling_device(tz, i, cdev);
}
static void bind_cdev(struct thermal_cooling_device *cdev)
{
int i, ret;
const struct thermal_zone_params *tzp;
struct thermal_zone_device *pos = NULL;
mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) {
if (!pos->tzp && !pos->ops->bind)
continue;
if (!pos->tzp && pos->ops->bind) {
ret = pos->ops->bind(pos, cdev);
if (ret)
print_bind_err_msg(pos, cdev, ret);
}
tzp = pos->tzp;
if (!tzp || !tzp->tbp)
continue;
for (i = 0; i < tzp->num_tbps; i++) {
if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
continue;
if (tzp->tbp[i].match(pos, cdev))
continue;
tzp->tbp[i].cdev = cdev;
__bind(pos, tzp->tbp[i].trip_mask, cdev);
}
}
mutex_unlock(&thermal_list_lock);
}
static void bind_tz(struct thermal_zone_device *tz)
{
int i, ret;
struct thermal_cooling_device *pos = NULL;
const struct thermal_zone_params *tzp = tz->tzp;
if (!tzp && !tz->ops->bind)
return;
mutex_lock(&thermal_list_lock);
/* If there is no platform data, try to use ops->bind */
if (!tzp && tz->ops->bind) {
list_for_each_entry(pos, &thermal_cdev_list, node) {
ret = tz->ops->bind(tz, pos);
if (ret)
print_bind_err_msg(tz, pos, ret);
}
goto exit;
}
if (!tzp || !tzp->tbp)
goto exit;
list_for_each_entry(pos, &thermal_cdev_list, node) {
for (i = 0; i < tzp->num_tbps; i++) {
if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
continue;
if (tzp->tbp[i].match(tz, pos))
continue;
tzp->tbp[i].cdev = pos;
__bind(tz, tzp->tbp[i].trip_mask, pos);
}
}
exit:
mutex_unlock(&thermal_list_lock);
}
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
int delay)
{
if (delay > 1000)
mod_delayed_work(system_freezable_wq, &tz->poll_queue,
round_jiffies(msecs_to_jiffies(delay)));
else if (delay)
mod_delayed_work(system_freezable_wq, &tz->poll_queue,
msecs_to_jiffies(delay));
else
cancel_delayed_work(&tz->poll_queue);
}
static void monitor_thermal_zone(struct thermal_zone_device *tz)
{
mutex_lock(&tz->lock);
if (tz->passive)
thermal_zone_device_set_polling(tz, tz->passive_delay);
else if (tz->polling_delay)
thermal_zone_device_set_polling(tz, tz->polling_delay);
else
thermal_zone_device_set_polling(tz, 0);
mutex_unlock(&tz->lock);
}
static void handle_non_critical_trips(struct thermal_zone_device *tz,
int trip, enum thermal_trip_type trip_type)
{
if (tz->governor)
tz->governor->throttle(tz, trip);
}
static void handle_critical_trips(struct thermal_zone_device *tz,
int trip, enum thermal_trip_type trip_type)
{
long trip_temp;
tz->ops->get_trip_temp(tz, trip, &trip_temp);
/* If we have not crossed the trip_temp, we do not care. */
if (tz->temperature < trip_temp)
return;
if (tz->ops->notify)
tz->ops->notify(tz, trip, trip_type);
if (trip_type == THERMAL_TRIP_CRITICAL) {
pr_emerg("Critical temperature reached(%d C),shutting down\n",
tz->temperature / 1000);
orderly_poweroff(true);
}
}
static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
{
enum thermal_trip_type type;
tz->ops->get_trip_type(tz, trip, &type);
if (type == THERMAL_TRIP_CRITICAL || type == THERMAL_TRIP_HOT)
handle_critical_trips(tz, trip, type);
else
handle_non_critical_trips(tz, trip, type);
/*
* Alright, we handled this trip successfully.
* So, start monitoring again.
*/
monitor_thermal_zone(tz);
}
static void update_temperature(struct thermal_zone_device *tz)
{
long temp;
int ret;
mutex_lock(&tz->lock);
ret = tz->ops->get_temp(tz, &temp);
if (ret) {
pr_warn("failed to read out thermal zone %d\n", tz->id);
goto exit;
}
tz->last_temperature = tz->temperature;
tz->temperature = temp;
exit:
mutex_unlock(&tz->lock);
}
void thermal_zone_device_update(struct thermal_zone_device *tz)
{
int count;
update_temperature(tz);
for (count = 0; count < tz->trips; count++)
handle_thermal_trip(tz, count);
}
EXPORT_SYMBOL(thermal_zone_device_update);
static void thermal_zone_device_check(struct work_struct *work)
{
struct thermal_zone_device *tz = container_of(work, struct
thermal_zone_device,
poll_queue.work);
thermal_zone_device_update(tz);
}
/* sys I/F for thermal zone */ /* sys I/F for thermal zone */
#define to_thermal_zone(_dev) \ #define to_thermal_zone(_dev) \
...@@ -354,10 +670,41 @@ passive_show(struct device *dev, struct device_attribute *attr, ...@@ -354,10 +670,41 @@ passive_show(struct device *dev, struct device_attribute *attr,
return sprintf(buf, "%d\n", tz->forced_passive); return sprintf(buf, "%d\n", tz->forced_passive);
} }
static ssize_t
policy_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret = -EINVAL;
struct thermal_zone_device *tz = to_thermal_zone(dev);
struct thermal_governor *gov;
mutex_lock(&thermal_governor_lock);
gov = __find_governor(buf);
if (!gov)
goto exit;
tz->governor = gov;
ret = count;
exit:
mutex_unlock(&thermal_governor_lock);
return ret;
}
static ssize_t
policy_show(struct device *dev, struct device_attribute *devattr, char *buf)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
return sprintf(buf, "%s\n", tz->governor->name);
}
static DEVICE_ATTR(type, 0444, type_show, NULL); static DEVICE_ATTR(type, 0444, type_show, NULL);
static DEVICE_ATTR(temp, 0444, temp_show, NULL); static DEVICE_ATTR(temp, 0444, temp_show, NULL);
static DEVICE_ATTR(mode, 0644, mode_show, mode_store); static DEVICE_ATTR(mode, 0644, mode_show, mode_store);
static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store); static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store);
static DEVICE_ATTR(policy, S_IRUGO | S_IWUSR, policy_show, policy_store);
/* sys I/F for cooling device */ /* sys I/F for cooling device */
#define to_cooling_device(_dev) \ #define to_cooling_device(_dev) \
...@@ -700,27 +1047,6 @@ thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) ...@@ -700,27 +1047,6 @@ thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
} }
#endif #endif
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
int delay)
{
if (delay > 1000)
mod_delayed_work(system_freezable_wq, &tz->poll_queue,
round_jiffies(msecs_to_jiffies(delay)));
else if (delay)
mod_delayed_work(system_freezable_wq, &tz->poll_queue,
msecs_to_jiffies(delay));
else
cancel_delayed_work(&tz->poll_queue);
}
static void thermal_zone_device_check(struct work_struct *work)
{
struct thermal_zone_device *tz = container_of(work, struct
thermal_zone_device,
poll_queue.work);
thermal_zone_device_update(tz);
}
/** /**
* thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone * thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone
* @tz: thermal zone device * @tz: thermal zone device
...@@ -895,7 +1221,6 @@ thermal_cooling_device_register(char *type, void *devdata, ...@@ -895,7 +1221,6 @@ thermal_cooling_device_register(char *type, void *devdata,
const struct thermal_cooling_device_ops *ops) const struct thermal_cooling_device_ops *ops)
{ {
struct thermal_cooling_device *cdev; struct thermal_cooling_device *cdev;
struct thermal_zone_device *pos;
int result; int result;
if (type && strlen(type) >= THERMAL_NAME_LENGTH) if (type && strlen(type) >= THERMAL_NAME_LENGTH)
...@@ -945,20 +1270,15 @@ thermal_cooling_device_register(char *type, void *devdata, ...@@ -945,20 +1270,15 @@ thermal_cooling_device_register(char *type, void *devdata,
if (result) if (result)
goto unregister; goto unregister;
/* Add 'this' new cdev to the global cdev list */
mutex_lock(&thermal_list_lock); mutex_lock(&thermal_list_lock);
list_add(&cdev->node, &thermal_cdev_list); 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); mutex_unlock(&thermal_list_lock);
if (!result) /* Update binding information for 'this' new cdev */
return cdev; bind_cdev(cdev);
return cdev;
unregister: unregister:
release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
...@@ -974,10 +1294,10 @@ EXPORT_SYMBOL(thermal_cooling_device_register); ...@@ -974,10 +1294,10 @@ EXPORT_SYMBOL(thermal_cooling_device_register);
* thermal_cooling_device_unregister() must be called when the device is no * thermal_cooling_device_unregister() must be called when the device is no
* longer needed. * longer needed.
*/ */
void thermal_cooling_device_unregister(struct void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
thermal_cooling_device
*cdev)
{ {
int i;
const struct thermal_zone_params *tzp;
struct thermal_zone_device *tz; struct thermal_zone_device *tz;
struct thermal_cooling_device *pos = NULL; struct thermal_cooling_device *pos = NULL;
...@@ -994,12 +1314,28 @@ void thermal_cooling_device_unregister(struct ...@@ -994,12 +1314,28 @@ void thermal_cooling_device_unregister(struct
return; return;
} }
list_del(&cdev->node); list_del(&cdev->node);
/* Unbind all thermal zones associated with 'this' cdev */
list_for_each_entry(tz, &thermal_tz_list, node) { list_for_each_entry(tz, &thermal_tz_list, node) {
if (!tz->ops->unbind) if (tz->ops->unbind) {
tz->ops->unbind(tz, cdev);
continue; continue;
tz->ops->unbind(tz, cdev); }
if (!tz->tzp || !tz->tzp->tbp)
continue;
tzp = tz->tzp;
for (i = 0; i < tzp->num_tbps; i++) {
if (tzp->tbp[i].cdev == cdev) {
__unbind(tz, tzp->tbp[i].trip_mask, cdev);
tzp->tbp[i].cdev = NULL;
}
}
} }
mutex_unlock(&thermal_list_lock); mutex_unlock(&thermal_list_lock);
if (cdev->type[0]) if (cdev->type[0])
device_remove_file(&cdev->device, &dev_attr_cdev_type); device_remove_file(&cdev->device, &dev_attr_cdev_type);
device_remove_file(&cdev->device, &dev_attr_max_state); device_remove_file(&cdev->device, &dev_attr_max_state);
...@@ -1011,7 +1347,7 @@ void thermal_cooling_device_unregister(struct ...@@ -1011,7 +1347,7 @@ void thermal_cooling_device_unregister(struct
} }
EXPORT_SYMBOL(thermal_cooling_device_unregister); EXPORT_SYMBOL(thermal_cooling_device_unregister);
static void thermal_cdev_do_update(struct thermal_cooling_device *cdev) void thermal_cdev_update(struct thermal_cooling_device *cdev)
{ {
struct thermal_instance *instance; struct thermal_instance *instance;
unsigned long target = 0; unsigned long target = 0;
...@@ -1032,183 +1368,25 @@ static void thermal_cdev_do_update(struct thermal_cooling_device *cdev) ...@@ -1032,183 +1368,25 @@ static void thermal_cdev_do_update(struct thermal_cooling_device *cdev)
cdev->ops->set_cur_state(cdev, target); cdev->ops->set_cur_state(cdev, target);
cdev->updated = true; cdev->updated = true;
} }
EXPORT_SYMBOL(thermal_cdev_update);
static void thermal_zone_do_update(struct thermal_zone_device *tz)
{
struct thermal_instance *instance;
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
thermal_cdev_do_update(instance->cdev);
}
/*
* Cooling algorithm for both active and passive cooling
*
* 1. if the temperature is higher than a trip point,
* a. if the trend is THERMAL_TREND_RAISING, use higher cooling
* state for this trip point
* b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
* state for this trip point
*
* 2. if the temperature is lower than a trip point, use lower
* cooling state for this trip point
*
* Note that this behaves the same as the previous passive cooling
* algorithm.
*/
static void thermal_zone_trip_update(struct thermal_zone_device *tz,
int trip, long temp)
{
struct thermal_instance *instance;
struct thermal_cooling_device *cdev = NULL;
unsigned long cur_state, max_state;
long trip_temp;
enum thermal_trip_type trip_type;
enum thermal_trend trend;
if (trip == THERMAL_TRIPS_NONE) {
trip_temp = tz->forced_passive;
trip_type = THERMAL_TRIPS_NONE;
} else {
tz->ops->get_trip_temp(tz, trip, &trip_temp);
tz->ops->get_trip_type(tz, trip, &trip_type);
}
if (!tz->ops->get_trend || tz->ops->get_trend(tz, trip, &trend)) {
/*
* compare the current temperature and previous temperature
* to get the thermal trend, if no special requirement
*/
if (tz->temperature > tz->last_temperature)
trend = THERMAL_TREND_RAISING;
else if (tz->temperature < tz->last_temperature)
trend = THERMAL_TREND_DROPPING;
else
trend = THERMAL_TREND_STABLE;
}
if (temp >= trip_temp) {
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)
continue;
cdev = instance->cdev;
cdev->ops->get_cur_state(cdev, &cur_state);
cdev->ops->get_max_state(cdev, &max_state);
if (trend == THERMAL_TREND_RAISING) {
cur_state = cur_state < instance->upper ?
(cur_state + 1) : instance->upper;
} else if (trend == THERMAL_TREND_DROPPING) {
cur_state = cur_state > instance->lower ?
(cur_state - 1) : instance->lower;
}
/* activate a passive thermal instance */
if ((trip_type == THERMAL_TRIP_PASSIVE ||
trip_type == THERMAL_TRIPS_NONE) &&
instance->target == THERMAL_NO_TARGET)
tz->passive++;
instance->target = cur_state;
cdev->updated = false; /* cooling device needs update */
}
} else { /* below trip */
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)
continue;
/* Do not use the inactive thermal instance */
if (instance->target == THERMAL_NO_TARGET)
continue;
cdev = instance->cdev;
cdev->ops->get_cur_state(cdev, &cur_state);
cur_state = cur_state > instance->lower ?
(cur_state - 1) : THERMAL_NO_TARGET;
/* deactivate a passive thermal instance */
if ((trip_type == THERMAL_TRIP_PASSIVE ||
trip_type == THERMAL_TRIPS_NONE) &&
cur_state == THERMAL_NO_TARGET)
tz->passive--;
instance->target = cur_state;
cdev->updated = false; /* cooling device needs update */
}
}
return;
}
/** /**
* thermal_zone_device_update - force an update of a thermal zone's state * notify_thermal_framework - Sensor drivers use this API to notify framework
* @ttz: the thermal zone to update * @tz: thermal zone device
* @trip: indicates which trip point has been crossed
*
* This function handles the trip events from sensor drivers. It starts
* throttling the cooling devices according to the policy configured.
* For CRITICAL and HOT trip points, this notifies the respective drivers,
* and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
* The throttling policy is based on the configured platform data; if no
* platform data is provided, this uses the step_wise throttling policy.
*/ */
void notify_thermal_framework(struct thermal_zone_device *tz, int trip)
void thermal_zone_device_update(struct thermal_zone_device *tz)
{ {
int count, ret = 0; handle_thermal_trip(tz, trip);
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);
goto leave;
}
tz->last_temperature = tz->temperature;
tz->temperature = temp;
for (count = 0; count < tz->trips; count++) {
tz->ops->get_trip_type(tz, count, &trip_type);
tz->ops->get_trip_temp(tz, count, &trip_temp);
switch (trip_type) {
case THERMAL_TRIP_CRITICAL:
if (temp >= trip_temp) {
if (tz->ops->notify)
ret = tz->ops->notify(tz, count,
trip_type);
if (!ret) {
pr_emerg("Critical temperature reached (%ld C), shutting down\n",
temp/1000);
orderly_poweroff(true);
}
}
break;
case THERMAL_TRIP_HOT:
if (temp >= trip_temp)
if (tz->ops->notify)
tz->ops->notify(tz, count, trip_type);
break;
case THERMAL_TRIP_ACTIVE:
thermal_zone_trip_update(tz, count, temp);
break;
case THERMAL_TRIP_PASSIVE:
if (temp >= trip_temp || tz->passive)
thermal_zone_trip_update(tz, count, temp);
break;
}
}
if (tz->forced_passive)
thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE, temp);
thermal_zone_do_update(tz);
leave:
if (tz->passive)
thermal_zone_device_set_polling(tz, tz->passive_delay);
else if (tz->polling_delay)
thermal_zone_device_set_polling(tz, tz->polling_delay);
else
thermal_zone_device_set_polling(tz, 0);
mutex_unlock(&tz->lock);
} }
EXPORT_SYMBOL(thermal_zone_device_update); EXPORT_SYMBOL(notify_thermal_framework);
/** /**
* create_trip_attrs - create attributes for trip points * create_trip_attrs - create attributes for trip points
...@@ -1320,6 +1498,7 @@ static void remove_trip_attrs(struct thermal_zone_device *tz) ...@@ -1320,6 +1498,7 @@ static void remove_trip_attrs(struct thermal_zone_device *tz)
* @mask: a bit string indicating the writeablility of trip points * @mask: a bit string indicating the writeablility of trip points
* @devdata: private device data * @devdata: private device data
* @ops: standard thermal zone device callbacks * @ops: standard thermal zone device callbacks
* @tzp: thermal zone platform parameters
* @passive_delay: number of milliseconds to wait between polls when * @passive_delay: number of milliseconds to wait between polls when
* performing passive cooling * performing passive cooling
* @polling_delay: number of milliseconds to wait between polls when checking * @polling_delay: number of milliseconds to wait between polls when checking
...@@ -1332,10 +1511,10 @@ static void remove_trip_attrs(struct thermal_zone_device *tz) ...@@ -1332,10 +1511,10 @@ static void remove_trip_attrs(struct thermal_zone_device *tz)
struct thermal_zone_device *thermal_zone_device_register(const char *type, struct thermal_zone_device *thermal_zone_device_register(const char *type,
int trips, int mask, void *devdata, int trips, int mask, void *devdata,
const struct thermal_zone_device_ops *ops, const struct thermal_zone_device_ops *ops,
const struct thermal_zone_params *tzp,
int passive_delay, int polling_delay) int passive_delay, int polling_delay)
{ {
struct thermal_zone_device *tz; struct thermal_zone_device *tz;
struct thermal_cooling_device *pos;
enum thermal_trip_type trip_type; enum thermal_trip_type trip_type;
int result; int result;
int count; int count;
...@@ -1365,6 +1544,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, ...@@ -1365,6 +1544,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
strcpy(tz->type, type ? : ""); strcpy(tz->type, type ? : "");
tz->ops = ops; tz->ops = ops;
tz->tzp = tzp;
tz->device.class = &thermal_class; tz->device.class = &thermal_class;
tz->devdata = devdata; tz->devdata = devdata;
tz->trips = trips; tz->trips = trips;
...@@ -1406,27 +1586,38 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, ...@@ -1406,27 +1586,38 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
passive = 1; passive = 1;
} }
if (!passive) if (!passive) {
result = device_create_file(&tz->device, result = device_create_file(&tz->device, &dev_attr_passive);
&dev_attr_passive); if (result)
goto unregister;
}
/* Create policy attribute */
result = device_create_file(&tz->device, &dev_attr_policy);
if (result) if (result)
goto unregister; goto unregister;
/* Update 'this' zone's governor information */
mutex_lock(&thermal_governor_lock);
if (tz->tzp)
tz->governor = __find_governor(tz->tzp->governor_name);
else
tz->governor = __find_governor(DEFAULT_THERMAL_GOVERNOR);
mutex_unlock(&thermal_governor_lock);
result = thermal_add_hwmon_sysfs(tz); result = thermal_add_hwmon_sysfs(tz);
if (result) if (result)
goto unregister; goto unregister;
mutex_lock(&thermal_list_lock); mutex_lock(&thermal_list_lock);
list_add_tail(&tz->node, &thermal_tz_list); 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); mutex_unlock(&thermal_list_lock);
/* Bind cooling devices for this zone */
bind_tz(tz);
INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check); INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);
thermal_zone_device_update(tz); thermal_zone_device_update(tz);
...@@ -1447,12 +1638,16 @@ EXPORT_SYMBOL(thermal_zone_device_register); ...@@ -1447,12 +1638,16 @@ EXPORT_SYMBOL(thermal_zone_device_register);
*/ */
void thermal_zone_device_unregister(struct thermal_zone_device *tz) void thermal_zone_device_unregister(struct thermal_zone_device *tz)
{ {
int i;
const struct thermal_zone_params *tzp;
struct thermal_cooling_device *cdev; struct thermal_cooling_device *cdev;
struct thermal_zone_device *pos = NULL; struct thermal_zone_device *pos = NULL;
if (!tz) if (!tz)
return; return;
tzp = tz->tzp;
mutex_lock(&thermal_list_lock); mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) list_for_each_entry(pos, &thermal_tz_list, node)
if (pos == tz) if (pos == tz)
...@@ -1463,9 +1658,25 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz) ...@@ -1463,9 +1658,25 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
return; return;
} }
list_del(&tz->node); list_del(&tz->node);
if (tz->ops->unbind)
list_for_each_entry(cdev, &thermal_cdev_list, node) /* Unbind all cdevs associated with 'this' thermal zone */
tz->ops->unbind(tz, cdev); list_for_each_entry(cdev, &thermal_cdev_list, node) {
if (tz->ops->unbind) {
tz->ops->unbind(tz, cdev);
continue;
}
if (!tzp || !tzp->tbp)
break;
for (i = 0; i < tzp->num_tbps; i++) {
if (tzp->tbp[i].cdev == cdev) {
__unbind(tz, tzp->tbp[i].trip_mask, cdev);
tzp->tbp[i].cdev = NULL;
}
}
}
mutex_unlock(&thermal_list_lock); mutex_unlock(&thermal_list_lock);
thermal_zone_device_set_polling(tz, 0); thermal_zone_device_set_polling(tz, 0);
...@@ -1475,7 +1686,9 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz) ...@@ -1475,7 +1686,9 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
device_remove_file(&tz->device, &dev_attr_temp); device_remove_file(&tz->device, &dev_attr_temp);
if (tz->ops->get_mode) if (tz->ops->get_mode)
device_remove_file(&tz->device, &dev_attr_mode); device_remove_file(&tz->device, &dev_attr_mode);
device_remove_file(&tz->device, &dev_attr_policy);
remove_trip_attrs(tz); remove_trip_attrs(tz);
tz->governor = NULL;
thermal_remove_hwmon_sysfs(tz); thermal_remove_hwmon_sysfs(tz);
release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
......
/*
* user_space.c - A simple user space Thermal events notifier
*
* Copyright (C) 2012 Intel Corp
* Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/thermal.h>
#include "thermal_core.h"
/**
* notify_user_space - Notifies user space about thermal events
* @tz - thermal_zone_device
*
* This function notifies the user space through UEvents.
*/
static int notify_user_space(struct thermal_zone_device *tz, int trip)
{
mutex_lock(&tz->lock);
kobject_uevent(&tz->device.kobj, KOBJ_CHANGE);
mutex_unlock(&tz->lock);
return 0;
}
static struct thermal_governor thermal_gov_user_space = {
.name = "user_space",
.throttle = notify_user_space,
.owner = THIS_MODULE,
};
static int __init thermal_gov_user_space_init(void)
{
return thermal_register_governor(&thermal_gov_user_space);
}
static void __exit thermal_gov_user_space_exit(void)
{
thermal_unregister_governor(&thermal_gov_user_space);
}
/* This should load after thermal framework */
fs_initcall(thermal_gov_user_space_init);
module_exit(thermal_gov_user_space_exit);
MODULE_AUTHOR("Durgadoss R");
MODULE_DESCRIPTION("A user space Thermal notifier");
MODULE_LICENSE("GPL");
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
#define CPUFREQ_COOLING_START 0 #define CPUFREQ_COOLING_START 0
#define CPUFREQ_COOLING_STOP 1 #define CPUFREQ_COOLING_STOP 1
#ifdef CONFIG_CPU_THERMAL #if defined(CONFIG_CPU_THERMAL) || defined(CONFIG_CPU_THERMAL_MODULE)
/** /**
* cpufreq_cooling_register - function to create cpufreq cooling device. * cpufreq_cooling_register - function to create cpufreq cooling device.
* @clip_cpus: cpumask of cpus where the frequency constraints will happen * @clip_cpus: cpumask of cpus where the frequency constraints will happen
*/ */
struct thermal_cooling_device *cpufreq_cooling_register( struct thermal_cooling_device *cpufreq_cooling_register(
struct cpumask *clip_cpus); const struct cpumask *clip_cpus);
/** /**
* cpufreq_cooling_unregister - function to remove cpufreq cooling device. * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
...@@ -44,7 +44,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( ...@@ -44,7 +44,7 @@ struct thermal_cooling_device *cpufreq_cooling_register(
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev); void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
#else /* !CONFIG_CPU_THERMAL */ #else /* !CONFIG_CPU_THERMAL */
static inline struct thermal_cooling_device *cpufreq_cooling_register( static inline struct thermal_cooling_device *cpufreq_cooling_register(
struct cpumask *clip_cpus) const struct cpumask *clip_cpus)
{ {
return NULL; return NULL;
} }
......
/*
* db8500_thermal.h - DB8500 Thermal Management Implementation
*
* Copyright (C) 2012 ST-Ericsson
* Copyright (C) 2012 Linaro Ltd.
*
* Author: Hongbo Zhang <hongbo.zhang@linaro.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _DB8500_THERMAL_H_
#define _DB8500_THERMAL_H_
#include <linux/thermal.h>
#define COOLING_DEV_MAX 8
struct db8500_trip_point {
unsigned long temp;
enum thermal_trip_type type;
char cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH];
};
struct db8500_thsens_platform_data {
struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS];
int num_trips;
};
#endif /* _DB8500_THERMAL_H_ */
...@@ -29,6 +29,32 @@ ...@@ -29,6 +29,32 @@
#include <linux/device.h> #include <linux/device.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#define THERMAL_TRIPS_NONE -1
#define THERMAL_MAX_TRIPS 12
#define THERMAL_NAME_LENGTH 20
/* No upper/lower limit requirement */
#define THERMAL_NO_LIMIT -1UL
/* Unit conversion macros */
#define KELVIN_TO_CELSIUS(t) (long)(((long)t-2732 >= 0) ? \
((long)t-2732+5)/10 : ((long)t-2732-5)/10)
#define CELSIUS_TO_KELVIN(t) ((t)*10+2732)
/* Adding event notification support elements */
#define THERMAL_GENL_FAMILY_NAME "thermal_event"
#define THERMAL_GENL_VERSION 0x01
#define THERMAL_GENL_MCAST_GROUP_NAME "thermal_mc_group"
/* Default Thermal Governor */
#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE)
#define DEFAULT_THERMAL_GOVERNOR "step_wise"
#elif defined(CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE)
#define DEFAULT_THERMAL_GOVERNOR "fair_share"
#elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE)
#define DEFAULT_THERMAL_GOVERNOR "user_space"
#endif
struct thermal_zone_device; struct thermal_zone_device;
struct thermal_cooling_device; struct thermal_cooling_device;
...@@ -50,6 +76,30 @@ enum thermal_trend { ...@@ -50,6 +76,30 @@ enum thermal_trend {
THERMAL_TREND_DROPPING, /* temperature is dropping */ THERMAL_TREND_DROPPING, /* temperature is dropping */
}; };
/* Events supported by Thermal Netlink */
enum events {
THERMAL_AUX0,
THERMAL_AUX1,
THERMAL_CRITICAL,
THERMAL_DEV_FAULT,
};
/* attributes of thermal_genl_family */
enum {
THERMAL_GENL_ATTR_UNSPEC,
THERMAL_GENL_ATTR_EVENT,
__THERMAL_GENL_ATTR_MAX,
};
#define THERMAL_GENL_ATTR_MAX (__THERMAL_GENL_ATTR_MAX - 1)
/* commands supported by the thermal_genl_family */
enum {
THERMAL_GENL_CMD_UNSPEC,
THERMAL_GENL_CMD_EVENT,
__THERMAL_GENL_CMD_MAX,
};
#define THERMAL_GENL_CMD_MAX (__THERMAL_GENL_CMD_MAX - 1)
struct thermal_zone_device_ops { struct thermal_zone_device_ops {
int (*bind) (struct thermal_zone_device *, int (*bind) (struct thermal_zone_device *,
struct thermal_cooling_device *); struct thermal_cooling_device *);
...@@ -83,11 +133,6 @@ struct thermal_cooling_device_ops { ...@@ -83,11 +133,6 @@ struct thermal_cooling_device_ops {
int (*set_cur_state) (struct thermal_cooling_device *, unsigned long); int (*set_cur_state) (struct thermal_cooling_device *, unsigned long);
}; };
#define THERMAL_NO_LIMIT -1UL /* no upper/lower limit requirement */
#define THERMAL_TRIPS_NONE -1
#define THERMAL_MAX_TRIPS 12
#define THERMAL_NAME_LENGTH 20
struct thermal_cooling_device { struct thermal_cooling_device {
int id; int id;
char type[THERMAL_NAME_LENGTH]; char type[THERMAL_NAME_LENGTH];
...@@ -100,10 +145,6 @@ struct thermal_cooling_device { ...@@ -100,10 +145,6 @@ struct thermal_cooling_device {
struct list_head node; struct list_head node;
}; };
#define KELVIN_TO_CELSIUS(t) (long)(((long)t-2732 >= 0) ? \
((long)t-2732+5)/10 : ((long)t-2732-5)/10)
#define CELSIUS_TO_KELVIN(t) ((t)*10+2732)
struct thermal_attr { struct thermal_attr {
struct device_attribute attr; struct device_attribute attr;
char name[THERMAL_NAME_LENGTH]; char name[THERMAL_NAME_LENGTH];
...@@ -125,46 +166,61 @@ struct thermal_zone_device { ...@@ -125,46 +166,61 @@ struct thermal_zone_device {
int passive; int passive;
unsigned int forced_passive; unsigned int forced_passive;
const struct thermal_zone_device_ops *ops; const struct thermal_zone_device_ops *ops;
const struct thermal_zone_params *tzp;
struct thermal_governor *governor;
struct list_head thermal_instances; struct list_head thermal_instances;
struct idr idr; struct idr idr;
struct mutex lock; /* protect thermal_instances list */ struct mutex lock; /* protect thermal_instances list */
struct list_head node; struct list_head node;
struct delayed_work poll_queue; struct delayed_work poll_queue;
}; };
/* Adding event notification support elements */
#define THERMAL_GENL_FAMILY_NAME "thermal_event"
#define THERMAL_GENL_VERSION 0x01
#define THERMAL_GENL_MCAST_GROUP_NAME "thermal_mc_group"
enum events { /* Structure that holds thermal governor information */
THERMAL_AUX0, struct thermal_governor {
THERMAL_AUX1, char name[THERMAL_NAME_LENGTH];
THERMAL_CRITICAL, int (*throttle)(struct thermal_zone_device *tz, int trip);
THERMAL_DEV_FAULT, struct list_head governor_list;
struct module *owner;
}; };
struct thermal_genl_event { /* Structure that holds binding parameters for a zone */
u32 orig; struct thermal_bind_params {
enum events event; struct thermal_cooling_device *cdev;
/*
* This is a measure of 'how effectively these devices can
* cool 'this' thermal zone. The shall be determined by platform
* characterization. This is on a 'percentage' scale.
* See Documentation/thermal/sysfs-api.txt for more information.
*/
int weight;
/*
* This is a bit mask that gives the binding relation between this
* thermal zone and cdev, for a particular trip point.
* See Documentation/thermal/sysfs-api.txt for more information.
*/
int trip_mask;
int (*match) (struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev);
}; };
/* attributes of thermal_genl_family */
enum { /* Structure to define Thermal Zone parameters */
THERMAL_GENL_ATTR_UNSPEC, struct thermal_zone_params {
THERMAL_GENL_ATTR_EVENT, char governor_name[THERMAL_NAME_LENGTH];
__THERMAL_GENL_ATTR_MAX, int num_tbps; /* Number of tbp entries */
struct thermal_bind_params *tbp;
}; };
#define THERMAL_GENL_ATTR_MAX (__THERMAL_GENL_ATTR_MAX - 1)
/* commands supported by the thermal_genl_family */ struct thermal_genl_event {
enum { u32 orig;
THERMAL_GENL_CMD_UNSPEC, enum events event;
THERMAL_GENL_CMD_EVENT,
__THERMAL_GENL_CMD_MAX,
}; };
#define THERMAL_GENL_CMD_MAX (__THERMAL_GENL_CMD_MAX - 1)
/* Function declarations */
struct thermal_zone_device *thermal_zone_device_register(const char *, int, int, struct thermal_zone_device *thermal_zone_device_register(const char *, int, int,
void *, const struct thermal_zone_device_ops *, int, int); void *, const struct thermal_zone_device_ops *,
const struct thermal_zone_params *, int, int);
void thermal_zone_device_unregister(struct thermal_zone_device *); void thermal_zone_device_unregister(struct thermal_zone_device *);
int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int, int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int,
...@@ -173,10 +229,20 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int, ...@@ -173,10 +229,20 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int,
int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int, int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int,
struct thermal_cooling_device *); struct thermal_cooling_device *);
void thermal_zone_device_update(struct thermal_zone_device *); void thermal_zone_device_update(struct thermal_zone_device *);
struct thermal_cooling_device *thermal_cooling_device_register(char *, void *, struct thermal_cooling_device *thermal_cooling_device_register(char *, void *,
const struct thermal_cooling_device_ops *); const struct thermal_cooling_device_ops *);
void thermal_cooling_device_unregister(struct thermal_cooling_device *); void thermal_cooling_device_unregister(struct thermal_cooling_device *);
int get_tz_trend(struct thermal_zone_device *, int);
struct thermal_instance *get_thermal_instance(struct thermal_zone_device *,
struct thermal_cooling_device *, int);
void thermal_cdev_update(struct thermal_cooling_device *);
void notify_thermal_framework(struct thermal_zone_device *, int);
int thermal_register_governor(struct thermal_governor *);
void thermal_unregister_governor(struct thermal_governor *);
#ifdef CONFIG_NET #ifdef CONFIG_NET
extern int thermal_generate_netlink_event(u32 orig, enum events event); extern int thermal_generate_netlink_event(u32 orig, enum events event);
#else #else
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment