Merge branch 'thermal-intel'
Merge thermal control changes related to Intel platforms for 6.3-rc1: - Rework ACPI helper functions for thermal control to retrieve a trip point temperature instead of initializing a trip point objetc (Rafael Wysocki). - Clean up and improve the int340x thermal driver ((Rafael Wysocki). - Simplify and clean up the intel_pch thermal driver ((Rafael Wysocki). - Fix the Intel powerclamp thermal driver and make it use the common idle injection framework (Srinivas Pandruvada). - Add two module parameters, cpumask and max_idle, to the Intel powerclamp thermal driver to allow it to affect only a specific subset of CPUs instead of all of them (Srinivas Pandruvada). - Make the Intel quark_dts thermal driver Use generic trip point objects instead of its own trip point representation (Daniel Lezcano). - Add toctree entry for thermal documents and fix two issues in the Intel powerclamp driver documentation (Bagas Sanjaya). * thermal-intel: (25 commits) Documentation: powerclamp: Fix numbered lists formatting Documentation: powerclamp: Escape wildcard in cpumask description Documentation: admin-guide: Add toctree entry for thermal docs thermal: intel: powerclamp: Add two module parameters Documentation: admin-guide: Move intel_powerclamp documentation thermal: intel: powerclamp: Fix duration module parameter thermal: intel: powerclamp: Return last requested state as cur_state thermal: intel: quark_dts: Use generic trip points thermal: intel: powerclamp: Use powercap idle-inject feature powercap: idle_inject: Add update callback powercap: idle_inject: Export symbols thermal: intel: powerclamp: Fix cur_state for multi package system thermal: intel: intel_pch: Drop struct board_info thermal: intel: intel_pch: Rename board ID symbols thermal: intel: intel_pch: Fold suspend and resume routines into their callers thermal: intel: intel_pch: Fold two functions into their callers thermal: intel: intel_pch: Eliminate device operations object thermal: intel: intel_pch: Rename device operations callbacks thermal: intel: intel_pch: Eliminate redundant return pointers thermal: intel: intel_pch: Make pch_wpt_add_acpi_psv_trip() return int ...
This commit is contained in:
Коммит
badf1f9050
|
@ -116,6 +116,7 @@ configure specific aspects of kernel behavior to your liking.
|
||||||
svga
|
svga
|
||||||
syscall-user-dispatch
|
syscall-user-dispatch
|
||||||
sysrq
|
sysrq
|
||||||
|
thermal/index
|
||||||
thunderbolt
|
thunderbolt
|
||||||
ufs
|
ufs
|
||||||
unicode
|
unicode
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
=================
|
||||||
|
Thermal Subsystem
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
intel_powerclamp
|
|
@ -26,6 +26,8 @@ By:
|
||||||
- Generic Thermal Layer (sysfs)
|
- Generic Thermal Layer (sysfs)
|
||||||
- Kernel APIs (TBD)
|
- Kernel APIs (TBD)
|
||||||
|
|
||||||
|
(*) Module Parameters
|
||||||
|
|
||||||
INTRODUCTION
|
INTRODUCTION
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -153,13 +155,15 @@ b) determine the amount of compensation needed at each target ratio
|
||||||
Compensation to each target ratio consists of two parts:
|
Compensation to each target ratio consists of two parts:
|
||||||
|
|
||||||
a) steady state error compensation
|
a) steady state error compensation
|
||||||
This is to offset the error occurring when the system can
|
|
||||||
enter idle without extra wakeups (such as external interrupts).
|
This is to offset the error occurring when the system can
|
||||||
|
enter idle without extra wakeups (such as external interrupts).
|
||||||
|
|
||||||
b) dynamic error compensation
|
b) dynamic error compensation
|
||||||
When an excessive amount of wakeups occurs during idle, an
|
|
||||||
additional idle ratio can be added to quiet interrupts, by
|
When an excessive amount of wakeups occurs during idle, an
|
||||||
slowing down CPU activities.
|
additional idle ratio can be added to quiet interrupts, by
|
||||||
|
slowing down CPU activities.
|
||||||
|
|
||||||
A debugfs file is provided for the user to examine compensation
|
A debugfs file is provided for the user to examine compensation
|
||||||
progress and results, such as on a Westmere system::
|
progress and results, such as on a Westmere system::
|
||||||
|
@ -281,6 +285,7 @@ cur_state returns value -1 instead of 0 which is to avoid confusing
|
||||||
100% busy state with the disabled state.
|
100% busy state with the disabled state.
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
||||||
|
|
||||||
- To inject 25% idle time::
|
- To inject 25% idle time::
|
||||||
|
|
||||||
$ sudo sh -c "echo 25 > /sys/class/thermal/cooling_device80/cur_state
|
$ sudo sh -c "echo 25 > /sys/class/thermal/cooling_device80/cur_state
|
||||||
|
@ -318,3 +323,23 @@ device, a PID based userspace thermal controller can manage to
|
||||||
control CPU temperature effectively, when no other thermal influence
|
control CPU temperature effectively, when no other thermal influence
|
||||||
is added. For example, a UltraBook user can compile the kernel under
|
is added. For example, a UltraBook user can compile the kernel under
|
||||||
certain temperature (below most active trip points).
|
certain temperature (below most active trip points).
|
||||||
|
|
||||||
|
Module Parameters
|
||||||
|
=================
|
||||||
|
|
||||||
|
``cpumask`` (RW)
|
||||||
|
A bit mask of CPUs to inject idle. The format of the bitmask is same as
|
||||||
|
used in other subsystems like in /proc/irq/\*/smp_affinity. The mask is
|
||||||
|
comma separated 32 bit groups. Each CPU is one bit. For example for a 256
|
||||||
|
CPU system the full mask is:
|
||||||
|
ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff
|
||||||
|
|
||||||
|
The rightmost mask is for CPU 0-32.
|
||||||
|
|
||||||
|
``max_idle`` (RW)
|
||||||
|
Maximum injected idle time to the total CPU time ratio in percent range
|
||||||
|
from 1 to 100. Even if the cooling device max_state is always 100 (100%),
|
||||||
|
this parameter allows to add a max idle percent limit. The default is 50,
|
||||||
|
to match the current implementation of powerclamp driver. Also doesn't
|
||||||
|
allow value more than 75, if the cpumask includes every CPU present in
|
||||||
|
the system.
|
|
@ -14,7 +14,6 @@ Thermal
|
||||||
|
|
||||||
exynos_thermal
|
exynos_thermal
|
||||||
exynos_thermal_emulation
|
exynos_thermal_emulation
|
||||||
intel_powerclamp
|
|
||||||
nouveau_thermal
|
nouveau_thermal
|
||||||
x86_pkg_temperature_thermal
|
x86_pkg_temperature_thermal
|
||||||
intel_dptf
|
intel_dptf
|
||||||
|
|
|
@ -20707,6 +20707,7 @@ S: Supported
|
||||||
Q: https://patchwork.kernel.org/project/linux-pm/list/
|
Q: https://patchwork.kernel.org/project/linux-pm/list/
|
||||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git thermal
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git thermal
|
||||||
F: Documentation/ABI/testing/sysfs-class-thermal
|
F: Documentation/ABI/testing/sysfs-class-thermal
|
||||||
|
F: Documentation/admin-guide/thermal/
|
||||||
F: Documentation/devicetree/bindings/thermal/
|
F: Documentation/devicetree/bindings/thermal/
|
||||||
F: Documentation/driver-api/thermal/
|
F: Documentation/driver-api/thermal/
|
||||||
F: drivers/thermal/
|
F: drivers/thermal/
|
||||||
|
|
|
@ -63,13 +63,29 @@ struct idle_inject_thread {
|
||||||
* @idle_duration_us: duration of CPU idle time to inject
|
* @idle_duration_us: duration of CPU idle time to inject
|
||||||
* @run_duration_us: duration of CPU run time to allow
|
* @run_duration_us: duration of CPU run time to allow
|
||||||
* @latency_us: max allowed latency
|
* @latency_us: max allowed latency
|
||||||
|
* @update: Optional callback deciding whether or not to skip idle
|
||||||
|
* injection in the given cycle.
|
||||||
* @cpumask: mask of CPUs affected by idle injection
|
* @cpumask: mask of CPUs affected by idle injection
|
||||||
|
*
|
||||||
|
* This structure is used to define per instance idle inject device data. Each
|
||||||
|
* instance has an idle duration, a run duration and mask of CPUs to inject
|
||||||
|
* idle.
|
||||||
|
*
|
||||||
|
* Actual CPU idle time is injected by calling kernel scheduler interface
|
||||||
|
* play_idle_precise(). There is one optional callback that can be registered
|
||||||
|
* by calling idle_inject_register_full():
|
||||||
|
*
|
||||||
|
* update() - This callback is invoked just before waking up CPUs to inject
|
||||||
|
* idle. If it returns false, CPUs are not woken up to inject idle in the given
|
||||||
|
* cycle. It also allows the caller to readjust the idle and run duration by
|
||||||
|
* calling idle_inject_set_duration() for the next cycle.
|
||||||
*/
|
*/
|
||||||
struct idle_inject_device {
|
struct idle_inject_device {
|
||||||
struct hrtimer timer;
|
struct hrtimer timer;
|
||||||
unsigned int idle_duration_us;
|
unsigned int idle_duration_us;
|
||||||
unsigned int run_duration_us;
|
unsigned int run_duration_us;
|
||||||
unsigned int latency_us;
|
unsigned int latency_us;
|
||||||
|
bool (*update)(void);
|
||||||
unsigned long cpumask[];
|
unsigned long cpumask[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -111,11 +127,12 @@ static enum hrtimer_restart idle_inject_timer_fn(struct hrtimer *timer)
|
||||||
struct idle_inject_device *ii_dev =
|
struct idle_inject_device *ii_dev =
|
||||||
container_of(timer, struct idle_inject_device, timer);
|
container_of(timer, struct idle_inject_device, timer);
|
||||||
|
|
||||||
|
if (!ii_dev->update || (ii_dev->update && ii_dev->update()))
|
||||||
|
idle_inject_wakeup(ii_dev);
|
||||||
|
|
||||||
duration_us = READ_ONCE(ii_dev->run_duration_us);
|
duration_us = READ_ONCE(ii_dev->run_duration_us);
|
||||||
duration_us += READ_ONCE(ii_dev->idle_duration_us);
|
duration_us += READ_ONCE(ii_dev->idle_duration_us);
|
||||||
|
|
||||||
idle_inject_wakeup(ii_dev);
|
|
||||||
|
|
||||||
hrtimer_forward_now(timer, ns_to_ktime(duration_us * NSEC_PER_USEC));
|
hrtimer_forward_now(timer, ns_to_ktime(duration_us * NSEC_PER_USEC));
|
||||||
|
|
||||||
return HRTIMER_RESTART;
|
return HRTIMER_RESTART;
|
||||||
|
@ -160,6 +177,7 @@ void idle_inject_set_duration(struct idle_inject_device *ii_dev,
|
||||||
WRITE_ONCE(ii_dev->idle_duration_us, idle_duration_us);
|
WRITE_ONCE(ii_dev->idle_duration_us, idle_duration_us);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(idle_inject_set_duration, IDLE_INJECT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* idle_inject_get_duration - idle and run duration retrieval helper
|
* idle_inject_get_duration - idle and run duration retrieval helper
|
||||||
|
@ -174,6 +192,7 @@ void idle_inject_get_duration(struct idle_inject_device *ii_dev,
|
||||||
*run_duration_us = READ_ONCE(ii_dev->run_duration_us);
|
*run_duration_us = READ_ONCE(ii_dev->run_duration_us);
|
||||||
*idle_duration_us = READ_ONCE(ii_dev->idle_duration_us);
|
*idle_duration_us = READ_ONCE(ii_dev->idle_duration_us);
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(idle_inject_get_duration, IDLE_INJECT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* idle_inject_set_latency - set the maximum latency allowed
|
* idle_inject_set_latency - set the maximum latency allowed
|
||||||
|
@ -185,6 +204,7 @@ void idle_inject_set_latency(struct idle_inject_device *ii_dev,
|
||||||
{
|
{
|
||||||
WRITE_ONCE(ii_dev->latency_us, latency_us);
|
WRITE_ONCE(ii_dev->latency_us, latency_us);
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(idle_inject_set_latency, IDLE_INJECT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* idle_inject_start - start idle injections
|
* idle_inject_start - start idle injections
|
||||||
|
@ -216,6 +236,7 @@ int idle_inject_start(struct idle_inject_device *ii_dev)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(idle_inject_start, IDLE_INJECT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* idle_inject_stop - stops idle injections
|
* idle_inject_stop - stops idle injections
|
||||||
|
@ -262,6 +283,7 @@ void idle_inject_stop(struct idle_inject_device *ii_dev)
|
||||||
|
|
||||||
cpu_hotplug_enable();
|
cpu_hotplug_enable();
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(idle_inject_stop, IDLE_INJECT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* idle_inject_setup - prepare the current task for idle injection
|
* idle_inject_setup - prepare the current task for idle injection
|
||||||
|
@ -290,17 +312,22 @@ static int idle_inject_should_run(unsigned int cpu)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* idle_inject_register - initialize idle injection on a set of CPUs
|
* idle_inject_register_full - initialize idle injection on a set of CPUs
|
||||||
* @cpumask: CPUs to be affected by idle injection
|
* @cpumask: CPUs to be affected by idle injection
|
||||||
|
* @update: This callback is called just before waking up CPUs to inject
|
||||||
|
* idle
|
||||||
*
|
*
|
||||||
* This function creates an idle injection control device structure for the
|
* This function creates an idle injection control device structure for the
|
||||||
* given set of CPUs and initializes the timer associated with it. It does not
|
* given set of CPUs and initializes the timer associated with it. This
|
||||||
* start any injection cycles.
|
* function also allows to register update()callback.
|
||||||
|
* It does not start any injection cycles.
|
||||||
*
|
*
|
||||||
* Return: NULL if memory allocation fails, idle injection control device
|
* Return: NULL if memory allocation fails, idle injection control device
|
||||||
* pointer on success.
|
* pointer on success.
|
||||||
*/
|
*/
|
||||||
struct idle_inject_device *idle_inject_register(struct cpumask *cpumask)
|
|
||||||
|
struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask,
|
||||||
|
bool (*update)(void))
|
||||||
{
|
{
|
||||||
struct idle_inject_device *ii_dev;
|
struct idle_inject_device *ii_dev;
|
||||||
int cpu, cpu_rb;
|
int cpu, cpu_rb;
|
||||||
|
@ -313,6 +340,7 @@ struct idle_inject_device *idle_inject_register(struct cpumask *cpumask)
|
||||||
hrtimer_init(&ii_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
hrtimer_init(&ii_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||||
ii_dev->timer.function = idle_inject_timer_fn;
|
ii_dev->timer.function = idle_inject_timer_fn;
|
||||||
ii_dev->latency_us = UINT_MAX;
|
ii_dev->latency_us = UINT_MAX;
|
||||||
|
ii_dev->update = update;
|
||||||
|
|
||||||
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) {
|
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) {
|
||||||
|
|
||||||
|
@ -337,6 +365,24 @@ out_rollback:
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(idle_inject_register_full, IDLE_INJECT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* idle_inject_register - initialize idle injection on a set of CPUs
|
||||||
|
* @cpumask: CPUs to be affected by idle injection
|
||||||
|
*
|
||||||
|
* This function creates an idle injection control device structure for the
|
||||||
|
* given set of CPUs and initializes the timer associated with it. It does not
|
||||||
|
* start any injection cycles.
|
||||||
|
*
|
||||||
|
* Return: NULL if memory allocation fails, idle injection control device
|
||||||
|
* pointer on success.
|
||||||
|
*/
|
||||||
|
struct idle_inject_device *idle_inject_register(struct cpumask *cpumask)
|
||||||
|
{
|
||||||
|
return idle_inject_register_full(cpumask, NULL);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(idle_inject_register, IDLE_INJECT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* idle_inject_unregister - unregister idle injection control device
|
* idle_inject_unregister - unregister idle injection control device
|
||||||
|
@ -357,6 +403,7 @@ void idle_inject_unregister(struct idle_inject_device *ii_dev)
|
||||||
|
|
||||||
kfree(ii_dev);
|
kfree(ii_dev);
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(idle_inject_unregister, IDLE_INJECT);
|
||||||
|
|
||||||
static struct smp_hotplug_thread idle_inject_threads = {
|
static struct smp_hotplug_thread idle_inject_threads = {
|
||||||
.store = &idle_inject_thread.tsk,
|
.store = &idle_inject_thread.tsk,
|
||||||
|
|
|
@ -3,6 +3,9 @@ config INTEL_POWERCLAMP
|
||||||
tristate "Intel PowerClamp idle injection driver"
|
tristate "Intel PowerClamp idle injection driver"
|
||||||
depends on X86
|
depends on X86
|
||||||
depends on CPU_SUP_INTEL
|
depends on CPU_SUP_INTEL
|
||||||
|
depends on CPU_IDLE
|
||||||
|
select POWERCAP
|
||||||
|
select IDLE_INJECT
|
||||||
help
|
help
|
||||||
Enable this to enable Intel PowerClamp idle injection driver. This
|
Enable this to enable Intel PowerClamp idle injection driver. This
|
||||||
enforce idle time which results in more package C-state residency. The
|
enforce idle time which results in more package C-state residency. The
|
||||||
|
|
|
@ -29,24 +29,27 @@ static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone,
|
||||||
if (conv_temp < 0)
|
if (conv_temp < 0)
|
||||||
return conv_temp;
|
return conv_temp;
|
||||||
|
|
||||||
*temp = (unsigned long)conv_temp * 10;
|
*temp = conv_temp * 10;
|
||||||
} else
|
} else {
|
||||||
/* _TMP returns the temperature in tenths of degrees Kelvin */
|
/* _TMP returns the temperature in tenths of degrees Kelvin */
|
||||||
*temp = deci_kelvin_to_millicelsius(tmp);
|
*temp = deci_kelvin_to_millicelsius(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
|
static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
|
||||||
int trip, int temp)
|
int trip, int temp)
|
||||||
{
|
{
|
||||||
struct int34x_thermal_zone *d = zone->devdata;
|
struct int34x_thermal_zone *d = zone->devdata;
|
||||||
|
char name[] = {'P', 'A', 'T', '0' + trip, '\0'};
|
||||||
acpi_status status;
|
acpi_status status;
|
||||||
char name[10];
|
|
||||||
|
|
||||||
snprintf(name, sizeof(name), "PAT%d", trip);
|
if (trip > 9)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
status = acpi_execute_simple_method(d->adev->handle, name,
|
status = acpi_execute_simple_method(d->adev->handle, name,
|
||||||
millicelsius_to_deci_kelvin(temp));
|
millicelsius_to_deci_kelvin(temp));
|
||||||
if (ACPI_FAILURE(status))
|
if (ACPI_FAILURE(status))
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
||||||
|
@ -70,24 +73,34 @@ static int int340x_thermal_read_trips(struct acpi_device *zone_adev,
|
||||||
{
|
{
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
|
||||||
ret = thermal_acpi_trip_critical(zone_adev, &zone_trips[trip_cnt]);
|
ret = thermal_acpi_critical_trip_temp(zone_adev,
|
||||||
if (!ret)
|
&zone_trips[trip_cnt].temperature);
|
||||||
|
if (!ret) {
|
||||||
|
zone_trips[trip_cnt].type = THERMAL_TRIP_CRITICAL;
|
||||||
trip_cnt++;
|
trip_cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
ret = thermal_acpi_trip_hot(zone_adev, &zone_trips[trip_cnt]);
|
ret = thermal_acpi_hot_trip_temp(zone_adev,
|
||||||
if (!ret)
|
&zone_trips[trip_cnt].temperature);
|
||||||
|
if (!ret) {
|
||||||
|
zone_trips[trip_cnt].type = THERMAL_TRIP_HOT;
|
||||||
trip_cnt++;
|
trip_cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
ret = thermal_acpi_trip_passive(zone_adev, &zone_trips[trip_cnt]);
|
ret = thermal_acpi_passive_trip_temp(zone_adev,
|
||||||
if (!ret)
|
&zone_trips[trip_cnt].temperature);
|
||||||
|
if (!ret) {
|
||||||
|
zone_trips[trip_cnt].type = THERMAL_TRIP_PASSIVE;
|
||||||
trip_cnt++;
|
trip_cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
|
for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
|
||||||
|
ret = thermal_acpi_active_trip_temp(zone_adev, i,
|
||||||
ret = thermal_acpi_trip_active(zone_adev, i, &zone_trips[trip_cnt]);
|
&zone_trips[trip_cnt].temperature);
|
||||||
if (ret)
|
if (ret)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
zone_trips[trip_cnt].type = THERMAL_TRIP_ACTIVE;
|
||||||
trip_cnt++;
|
trip_cnt++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +115,7 @@ static struct thermal_zone_params int340x_thermal_params = {
|
||||||
struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
|
struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
|
||||||
int (*get_temp) (struct thermal_zone_device *, int *))
|
int (*get_temp) (struct thermal_zone_device *, int *))
|
||||||
{
|
{
|
||||||
struct int34x_thermal_zone *int34x_thermal_zone;
|
struct int34x_thermal_zone *int34x_zone;
|
||||||
struct thermal_trip *zone_trips;
|
struct thermal_trip *zone_trips;
|
||||||
unsigned long long trip_cnt = 0;
|
unsigned long long trip_cnt = 0;
|
||||||
unsigned long long hyst;
|
unsigned long long hyst;
|
||||||
|
@ -110,26 +123,25 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
|
||||||
acpi_status status;
|
acpi_status status;
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
|
||||||
int34x_thermal_zone = kzalloc(sizeof(*int34x_thermal_zone),
|
int34x_zone = kzalloc(sizeof(*int34x_zone), GFP_KERNEL);
|
||||||
GFP_KERNEL);
|
if (!int34x_zone)
|
||||||
if (!int34x_thermal_zone)
|
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
int34x_thermal_zone->adev = adev;
|
int34x_zone->adev = adev;
|
||||||
|
|
||||||
int34x_thermal_zone->ops = kmemdup(&int340x_thermal_zone_ops,
|
int34x_zone->ops = kmemdup(&int340x_thermal_zone_ops,
|
||||||
sizeof(int340x_thermal_zone_ops), GFP_KERNEL);
|
sizeof(int340x_thermal_zone_ops), GFP_KERNEL);
|
||||||
if (!int34x_thermal_zone->ops) {
|
if (!int34x_zone->ops) {
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err_ops_alloc;
|
goto err_ops_alloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_temp)
|
if (get_temp)
|
||||||
int34x_thermal_zone->ops->get_temp = get_temp;
|
int34x_zone->ops->get_temp = get_temp;
|
||||||
|
|
||||||
status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt);
|
status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt);
|
||||||
if (!ACPI_FAILURE(status)) {
|
if (ACPI_SUCCESS(status)) {
|
||||||
int34x_thermal_zone->aux_trip_nr = trip_cnt;
|
int34x_zone->aux_trip_nr = trip_cnt;
|
||||||
trip_mask = BIT(trip_cnt) - 1;
|
trip_mask = BIT(trip_cnt) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,49 +168,47 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
|
||||||
for (i = 0; i < trip_cnt; ++i)
|
for (i = 0; i < trip_cnt; ++i)
|
||||||
zone_trips[i].hysteresis = hyst;
|
zone_trips[i].hysteresis = hyst;
|
||||||
|
|
||||||
int34x_thermal_zone->trips = zone_trips;
|
int34x_zone->trips = zone_trips;
|
||||||
|
|
||||||
int34x_thermal_zone->lpat_table = acpi_lpat_get_conversion_table(
|
int34x_zone->lpat_table = acpi_lpat_get_conversion_table(adev->handle);
|
||||||
adev->handle);
|
|
||||||
|
|
||||||
int34x_thermal_zone->zone = thermal_zone_device_register_with_trips(
|
int34x_zone->zone = thermal_zone_device_register_with_trips(
|
||||||
acpi_device_bid(adev),
|
acpi_device_bid(adev),
|
||||||
zone_trips, trip_cnt,
|
zone_trips, trip_cnt,
|
||||||
trip_mask, int34x_thermal_zone,
|
trip_mask, int34x_zone,
|
||||||
int34x_thermal_zone->ops,
|
int34x_zone->ops,
|
||||||
&int340x_thermal_params,
|
&int340x_thermal_params,
|
||||||
0, 0);
|
0, 0);
|
||||||
if (IS_ERR(int34x_thermal_zone->zone)) {
|
if (IS_ERR(int34x_zone->zone)) {
|
||||||
ret = PTR_ERR(int34x_thermal_zone->zone);
|
ret = PTR_ERR(int34x_zone->zone);
|
||||||
goto err_thermal_zone;
|
goto err_thermal_zone;
|
||||||
}
|
}
|
||||||
ret = thermal_zone_device_enable(int34x_thermal_zone->zone);
|
ret = thermal_zone_device_enable(int34x_zone->zone);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err_enable;
|
goto err_enable;
|
||||||
|
|
||||||
return int34x_thermal_zone;
|
return int34x_zone;
|
||||||
|
|
||||||
err_enable:
|
err_enable:
|
||||||
thermal_zone_device_unregister(int34x_thermal_zone->zone);
|
thermal_zone_device_unregister(int34x_zone->zone);
|
||||||
err_thermal_zone:
|
err_thermal_zone:
|
||||||
kfree(int34x_thermal_zone->trips);
|
kfree(int34x_zone->trips);
|
||||||
acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
|
acpi_lpat_free_conversion_table(int34x_zone->lpat_table);
|
||||||
err_trips_alloc:
|
err_trips_alloc:
|
||||||
kfree(int34x_thermal_zone->ops);
|
kfree(int34x_zone->ops);
|
||||||
err_ops_alloc:
|
err_ops_alloc:
|
||||||
kfree(int34x_thermal_zone);
|
kfree(int34x_zone);
|
||||||
return ERR_PTR(ret);
|
return ERR_PTR(ret);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(int340x_thermal_zone_add);
|
EXPORT_SYMBOL_GPL(int340x_thermal_zone_add);
|
||||||
|
|
||||||
void int340x_thermal_zone_remove(struct int34x_thermal_zone
|
void int340x_thermal_zone_remove(struct int34x_thermal_zone *int34x_zone)
|
||||||
*int34x_thermal_zone)
|
|
||||||
{
|
{
|
||||||
thermal_zone_device_unregister(int34x_thermal_zone->zone);
|
thermal_zone_device_unregister(int34x_zone->zone);
|
||||||
acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
|
acpi_lpat_free_conversion_table(int34x_zone->lpat_table);
|
||||||
kfree(int34x_thermal_zone->trips);
|
kfree(int34x_zone->trips);
|
||||||
kfree(int34x_thermal_zone->ops);
|
kfree(int34x_zone->ops);
|
||||||
kfree(int34x_thermal_zone);
|
kfree(int34x_zone);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove);
|
EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove);
|
||||||
|
|
||||||
|
@ -213,22 +223,21 @@ void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone)
|
||||||
mutex_lock(&int34x_zone->zone->lock);
|
mutex_lock(&int34x_zone->zone->lock);
|
||||||
|
|
||||||
for (i = int34x_zone->aux_trip_nr; i < trip_cnt; i++) {
|
for (i = int34x_zone->aux_trip_nr; i < trip_cnt; i++) {
|
||||||
struct thermal_trip trip;
|
int temp, err;
|
||||||
int err;
|
|
||||||
|
|
||||||
switch (zone_trips[i].type) {
|
switch (zone_trips[i].type) {
|
||||||
case THERMAL_TRIP_CRITICAL:
|
case THERMAL_TRIP_CRITICAL:
|
||||||
err = thermal_acpi_trip_critical(zone_adev, &trip);
|
err = thermal_acpi_critical_trip_temp(zone_adev, &temp);
|
||||||
break;
|
break;
|
||||||
case THERMAL_TRIP_HOT:
|
case THERMAL_TRIP_HOT:
|
||||||
err = thermal_acpi_trip_hot(zone_adev, &trip);
|
err = thermal_acpi_hot_trip_temp(zone_adev, &temp);
|
||||||
break;
|
break;
|
||||||
case THERMAL_TRIP_PASSIVE:
|
case THERMAL_TRIP_PASSIVE:
|
||||||
err = thermal_acpi_trip_passive(zone_adev, &trip);
|
err = thermal_acpi_passive_trip_temp(zone_adev, &temp);
|
||||||
break;
|
break;
|
||||||
case THERMAL_TRIP_ACTIVE:
|
case THERMAL_TRIP_ACTIVE:
|
||||||
err = thermal_acpi_trip_active(zone_adev, act_trip_nr++,
|
err = thermal_acpi_active_trip_temp(zone_adev, act_trip_nr++,
|
||||||
&trip);
|
&temp);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
err = -ENODEV;
|
err = -ENODEV;
|
||||||
|
@ -238,7 +247,7 @@ void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
zone_trips[i].temperature = trip.temperature;
|
zone_trips[i].temperature = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&int34x_zone->zone->lock);
|
mutex_unlock(&int34x_zone->zone->lock);
|
||||||
|
|
|
@ -82,7 +82,6 @@ static char driver_name[] = "Intel PCH thermal driver";
|
||||||
|
|
||||||
struct pch_thermal_device {
|
struct pch_thermal_device {
|
||||||
void __iomem *hw_base;
|
void __iomem *hw_base;
|
||||||
const struct pch_dev_ops *ops;
|
|
||||||
struct pci_dev *pdev;
|
struct pci_dev *pdev;
|
||||||
struct thermal_zone_device *tzd;
|
struct thermal_zone_device *tzd;
|
||||||
struct thermal_trip trips[PCH_MAX_TRIPS];
|
struct thermal_trip trips[PCH_MAX_TRIPS];
|
||||||
|
@ -90,42 +89,107 @@ struct pch_thermal_device {
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_ACPI
|
#ifdef CONFIG_ACPI
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* On some platforms, there is a companion ACPI device, which adds
|
* On some platforms, there is a companion ACPI device, which adds
|
||||||
* passive trip temperature using _PSV method. There is no specific
|
* passive trip temperature using _PSV method. There is no specific
|
||||||
* passive temperature setting in MMIO interface of this PCI device.
|
* passive temperature setting in MMIO interface of this PCI device.
|
||||||
*/
|
*/
|
||||||
static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
|
static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, int trip)
|
||||||
int *nr_trips)
|
|
||||||
{
|
{
|
||||||
struct acpi_device *adev;
|
struct acpi_device *adev;
|
||||||
int ret;
|
int temp;
|
||||||
|
|
||||||
adev = ACPI_COMPANION(&ptd->pdev->dev);
|
adev = ACPI_COMPANION(&ptd->pdev->dev);
|
||||||
if (!adev)
|
if (!adev)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
ret = thermal_acpi_trip_passive(adev, &ptd->trips[*nr_trips]);
|
if (thermal_acpi_passive_trip_temp(adev, &temp) || temp <= 0)
|
||||||
if (ret || ptd->trips[*nr_trips].temperature <= 0)
|
return 0;
|
||||||
return;
|
|
||||||
|
|
||||||
++(*nr_trips);
|
ptd->trips[trip].type = THERMAL_TRIP_PASSIVE;
|
||||||
|
ptd->trips[trip].temperature = temp;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
|
static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, int trip)
|
||||||
int *nr_trips)
|
|
||||||
{
|
{
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int pch_wpt_init(struct pch_thermal_device *ptd, int *nr_trips)
|
static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp)
|
||||||
{
|
{
|
||||||
u8 tsel;
|
struct pch_thermal_device *ptd = tzd->devdata;
|
||||||
u16 trip_temp;
|
|
||||||
|
|
||||||
*nr_trips = 0;
|
*temp = GET_WPT_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pch_critical(struct thermal_zone_device *tzd)
|
||||||
|
{
|
||||||
|
dev_dbg(&tzd->device, "%s: critical temperature reached\n", tzd->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct thermal_zone_device_ops tzd_ops = {
|
||||||
|
.get_temp = pch_thermal_get_temp,
|
||||||
|
.critical = pch_critical,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum pch_board_ids {
|
||||||
|
PCH_BOARD_HSW = 0,
|
||||||
|
PCH_BOARD_WPT,
|
||||||
|
PCH_BOARD_SKL,
|
||||||
|
PCH_BOARD_CNL,
|
||||||
|
PCH_BOARD_CML,
|
||||||
|
PCH_BOARD_LWB,
|
||||||
|
PCH_BOARD_WBG,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *board_names[] = {
|
||||||
|
[PCH_BOARD_HSW] = "pch_haswell",
|
||||||
|
[PCH_BOARD_WPT] = "pch_wildcat_point",
|
||||||
|
[PCH_BOARD_SKL] = "pch_skylake",
|
||||||
|
[PCH_BOARD_CNL] = "pch_cannonlake",
|
||||||
|
[PCH_BOARD_CML] = "pch_cometlake",
|
||||||
|
[PCH_BOARD_LWB] = "pch_lewisburg",
|
||||||
|
[PCH_BOARD_WBG] = "pch_wellsburg",
|
||||||
|
};
|
||||||
|
|
||||||
|
static int intel_pch_thermal_probe(struct pci_dev *pdev,
|
||||||
|
const struct pci_device_id *id)
|
||||||
|
{
|
||||||
|
enum pch_board_ids board_id = id->driver_data;
|
||||||
|
struct pch_thermal_device *ptd;
|
||||||
|
int nr_trips = 0;
|
||||||
|
u16 trip_temp;
|
||||||
|
u8 tsel;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
|
||||||
|
if (!ptd)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
pci_set_drvdata(pdev, ptd);
|
||||||
|
ptd->pdev = pdev;
|
||||||
|
|
||||||
|
err = pci_enable_device(pdev);
|
||||||
|
if (err) {
|
||||||
|
dev_err(&pdev->dev, "failed to enable pci device\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pci_request_regions(pdev, driver_name);
|
||||||
|
if (err) {
|
||||||
|
dev_err(&pdev->dev, "failed to request pci region\n");
|
||||||
|
goto error_disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptd->hw_base = pci_ioremap_bar(pdev, 0);
|
||||||
|
if (!ptd->hw_base) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
dev_err(&pdev->dev, "failed to map mem base\n");
|
||||||
|
goto error_release;
|
||||||
|
}
|
||||||
|
|
||||||
/* Check if BIOS has already enabled thermal sensor */
|
/* Check if BIOS has already enabled thermal sensor */
|
||||||
if (WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL)) {
|
if (WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL)) {
|
||||||
|
@ -140,50 +204,79 @@ static int pch_wpt_init(struct pch_thermal_device *ptd, int *nr_trips)
|
||||||
*/
|
*/
|
||||||
if (tsel & WPT_TSEL_PLDB) {
|
if (tsel & WPT_TSEL_PLDB) {
|
||||||
dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
|
dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
|
||||||
return -ENODEV;
|
err = -ENODEV;
|
||||||
|
goto error_cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeb(tsel|WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL);
|
writeb(tsel|WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL);
|
||||||
if (!(WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL))) {
|
if (!(WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL))) {
|
||||||
dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
|
dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
|
||||||
return -ENODEV;
|
err = -ENODEV;
|
||||||
|
goto error_cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
read_trips:
|
read_trips:
|
||||||
trip_temp = readw(ptd->hw_base + WPT_CTT);
|
trip_temp = readw(ptd->hw_base + WPT_CTT);
|
||||||
trip_temp &= 0x1FF;
|
trip_temp &= 0x1FF;
|
||||||
if (trip_temp) {
|
if (trip_temp) {
|
||||||
ptd->trips[*nr_trips].temperature = GET_WPT_TEMP(trip_temp);
|
ptd->trips[nr_trips].temperature = GET_WPT_TEMP(trip_temp);
|
||||||
ptd->trips[*nr_trips].type = THERMAL_TRIP_CRITICAL;
|
ptd->trips[nr_trips++].type = THERMAL_TRIP_CRITICAL;
|
||||||
++(*nr_trips);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trip_temp = readw(ptd->hw_base + WPT_PHL);
|
trip_temp = readw(ptd->hw_base + WPT_PHL);
|
||||||
trip_temp &= 0x1FF;
|
trip_temp &= 0x1FF;
|
||||||
if (trip_temp) {
|
if (trip_temp) {
|
||||||
ptd->trips[*nr_trips].temperature = GET_WPT_TEMP(trip_temp);
|
ptd->trips[nr_trips].temperature = GET_WPT_TEMP(trip_temp);
|
||||||
ptd->trips[*nr_trips].type = THERMAL_TRIP_HOT;
|
ptd->trips[nr_trips++].type = THERMAL_TRIP_HOT;
|
||||||
++(*nr_trips);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pch_wpt_add_acpi_psv_trip(ptd, nr_trips);
|
nr_trips += pch_wpt_add_acpi_psv_trip(ptd, nr_trips);
|
||||||
|
|
||||||
|
ptd->tzd = thermal_zone_device_register_with_trips(board_names[board_id],
|
||||||
|
ptd->trips, nr_trips,
|
||||||
|
0, ptd, &tzd_ops,
|
||||||
|
NULL, 0, 0);
|
||||||
|
if (IS_ERR(ptd->tzd)) {
|
||||||
|
dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
|
||||||
|
board_names[board_id]);
|
||||||
|
err = PTR_ERR(ptd->tzd);
|
||||||
|
goto error_cleanup;
|
||||||
|
}
|
||||||
|
err = thermal_zone_device_enable(ptd->tzd);
|
||||||
|
if (err)
|
||||||
|
goto err_unregister;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_unregister:
|
||||||
|
thermal_zone_device_unregister(ptd->tzd);
|
||||||
|
error_cleanup:
|
||||||
|
iounmap(ptd->hw_base);
|
||||||
|
error_release:
|
||||||
|
pci_release_regions(pdev);
|
||||||
|
error_disable:
|
||||||
|
pci_disable_device(pdev);
|
||||||
|
dev_err(&pdev->dev, "pci device failed to probe\n");
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
|
static void intel_pch_thermal_remove(struct pci_dev *pdev)
|
||||||
{
|
{
|
||||||
*temp = GET_WPT_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
|
struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
|
||||||
|
|
||||||
return 0;
|
thermal_zone_device_unregister(ptd->tzd);
|
||||||
|
iounmap(ptd->hw_base);
|
||||||
|
pci_set_drvdata(pdev, NULL);
|
||||||
|
pci_release_regions(pdev);
|
||||||
|
pci_disable_device(pdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cool the PCH when it's overheat in .suspend_noirq phase */
|
static int intel_pch_thermal_suspend_noirq(struct device *device)
|
||||||
static int pch_wpt_suspend(struct pch_thermal_device *ptd)
|
|
||||||
{
|
{
|
||||||
u8 tsel;
|
struct pch_thermal_device *ptd = dev_get_drvdata(device);
|
||||||
int pch_delay_cnt = 0;
|
|
||||||
u16 pch_thr_temp, pch_cur_temp;
|
u16 pch_thr_temp, pch_cur_temp;
|
||||||
|
int pch_delay_cnt = 0;
|
||||||
|
u8 tsel;
|
||||||
|
|
||||||
/* Shutdown the thermal sensor if it is not enabled by BIOS */
|
/* Shutdown the thermal sensor if it is not enabled by BIOS */
|
||||||
if (!ptd->bios_enabled) {
|
if (!ptd->bios_enabled) {
|
||||||
|
@ -246,8 +339,9 @@ static int pch_wpt_suspend(struct pch_thermal_device *ptd)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pch_wpt_resume(struct pch_thermal_device *ptd)
|
static int intel_pch_thermal_resume(struct device *device)
|
||||||
{
|
{
|
||||||
|
struct pch_thermal_device *ptd = dev_get_drvdata(device);
|
||||||
u8 tsel;
|
u8 tsel;
|
||||||
|
|
||||||
if (ptd->bios_enabled)
|
if (ptd->bios_enabled)
|
||||||
|
@ -260,199 +354,29 @@ static int pch_wpt_resume(struct pch_thermal_device *ptd)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct pch_dev_ops {
|
|
||||||
int (*hw_init)(struct pch_thermal_device *ptd, int *nr_trips);
|
|
||||||
int (*get_temp)(struct pch_thermal_device *ptd, int *temp);
|
|
||||||
int (*suspend)(struct pch_thermal_device *ptd);
|
|
||||||
int (*resume)(struct pch_thermal_device *ptd);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* dev ops for Wildcat Point */
|
|
||||||
static const struct pch_dev_ops pch_dev_ops_wpt = {
|
|
||||||
.hw_init = pch_wpt_init,
|
|
||||||
.get_temp = pch_wpt_get_temp,
|
|
||||||
.suspend = pch_wpt_suspend,
|
|
||||||
.resume = pch_wpt_resume,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp)
|
|
||||||
{
|
|
||||||
struct pch_thermal_device *ptd = tzd->devdata;
|
|
||||||
|
|
||||||
return ptd->ops->get_temp(ptd, temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pch_critical(struct thermal_zone_device *tzd)
|
|
||||||
{
|
|
||||||
dev_dbg(&tzd->device, "%s: critical temperature reached\n", tzd->type);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct thermal_zone_device_ops tzd_ops = {
|
|
||||||
.get_temp = pch_thermal_get_temp,
|
|
||||||
.critical = pch_critical,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum board_ids {
|
|
||||||
board_hsw,
|
|
||||||
board_wpt,
|
|
||||||
board_skl,
|
|
||||||
board_cnl,
|
|
||||||
board_cml,
|
|
||||||
board_lwb,
|
|
||||||
board_wbg,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct board_info {
|
|
||||||
const char *name;
|
|
||||||
const struct pch_dev_ops *ops;
|
|
||||||
} board_info[] = {
|
|
||||||
[board_hsw] = {
|
|
||||||
.name = "pch_haswell",
|
|
||||||
.ops = &pch_dev_ops_wpt,
|
|
||||||
},
|
|
||||||
[board_wpt] = {
|
|
||||||
.name = "pch_wildcat_point",
|
|
||||||
.ops = &pch_dev_ops_wpt,
|
|
||||||
},
|
|
||||||
[board_skl] = {
|
|
||||||
.name = "pch_skylake",
|
|
||||||
.ops = &pch_dev_ops_wpt,
|
|
||||||
},
|
|
||||||
[board_cnl] = {
|
|
||||||
.name = "pch_cannonlake",
|
|
||||||
.ops = &pch_dev_ops_wpt,
|
|
||||||
},
|
|
||||||
[board_cml] = {
|
|
||||||
.name = "pch_cometlake",
|
|
||||||
.ops = &pch_dev_ops_wpt,
|
|
||||||
},
|
|
||||||
[board_lwb] = {
|
|
||||||
.name = "pch_lewisburg",
|
|
||||||
.ops = &pch_dev_ops_wpt,
|
|
||||||
},
|
|
||||||
[board_wbg] = {
|
|
||||||
.name = "pch_wellsburg",
|
|
||||||
.ops = &pch_dev_ops_wpt,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int intel_pch_thermal_probe(struct pci_dev *pdev,
|
|
||||||
const struct pci_device_id *id)
|
|
||||||
{
|
|
||||||
enum board_ids board_id = id->driver_data;
|
|
||||||
const struct board_info *bi = &board_info[board_id];
|
|
||||||
struct pch_thermal_device *ptd;
|
|
||||||
int err;
|
|
||||||
int nr_trips;
|
|
||||||
|
|
||||||
ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
|
|
||||||
if (!ptd)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
ptd->ops = bi->ops;
|
|
||||||
|
|
||||||
pci_set_drvdata(pdev, ptd);
|
|
||||||
ptd->pdev = pdev;
|
|
||||||
|
|
||||||
err = pci_enable_device(pdev);
|
|
||||||
if (err) {
|
|
||||||
dev_err(&pdev->dev, "failed to enable pci device\n");
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = pci_request_regions(pdev, driver_name);
|
|
||||||
if (err) {
|
|
||||||
dev_err(&pdev->dev, "failed to request pci region\n");
|
|
||||||
goto error_disable;
|
|
||||||
}
|
|
||||||
|
|
||||||
ptd->hw_base = pci_ioremap_bar(pdev, 0);
|
|
||||||
if (!ptd->hw_base) {
|
|
||||||
err = -ENOMEM;
|
|
||||||
dev_err(&pdev->dev, "failed to map mem base\n");
|
|
||||||
goto error_release;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ptd->ops->hw_init(ptd, &nr_trips);
|
|
||||||
if (err)
|
|
||||||
goto error_cleanup;
|
|
||||||
|
|
||||||
ptd->tzd = thermal_zone_device_register_with_trips(bi->name, ptd->trips,
|
|
||||||
nr_trips, 0, ptd,
|
|
||||||
&tzd_ops, NULL, 0, 0);
|
|
||||||
if (IS_ERR(ptd->tzd)) {
|
|
||||||
dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
|
|
||||||
bi->name);
|
|
||||||
err = PTR_ERR(ptd->tzd);
|
|
||||||
goto error_cleanup;
|
|
||||||
}
|
|
||||||
err = thermal_zone_device_enable(ptd->tzd);
|
|
||||||
if (err)
|
|
||||||
goto err_unregister;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err_unregister:
|
|
||||||
thermal_zone_device_unregister(ptd->tzd);
|
|
||||||
error_cleanup:
|
|
||||||
iounmap(ptd->hw_base);
|
|
||||||
error_release:
|
|
||||||
pci_release_regions(pdev);
|
|
||||||
error_disable:
|
|
||||||
pci_disable_device(pdev);
|
|
||||||
dev_err(&pdev->dev, "pci device failed to probe\n");
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void intel_pch_thermal_remove(struct pci_dev *pdev)
|
|
||||||
{
|
|
||||||
struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
|
|
||||||
|
|
||||||
thermal_zone_device_unregister(ptd->tzd);
|
|
||||||
iounmap(ptd->hw_base);
|
|
||||||
pci_set_drvdata(pdev, NULL);
|
|
||||||
pci_release_regions(pdev);
|
|
||||||
pci_disable_device(pdev);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int intel_pch_thermal_suspend_noirq(struct device *device)
|
|
||||||
{
|
|
||||||
struct pch_thermal_device *ptd = dev_get_drvdata(device);
|
|
||||||
|
|
||||||
return ptd->ops->suspend(ptd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int intel_pch_thermal_resume(struct device *device)
|
|
||||||
{
|
|
||||||
struct pch_thermal_device *ptd = dev_get_drvdata(device);
|
|
||||||
|
|
||||||
return ptd->ops->resume(ptd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct pci_device_id intel_pch_thermal_id[] = {
|
static const struct pci_device_id intel_pch_thermal_id[] = {
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1),
|
||||||
.driver_data = board_hsw, },
|
.driver_data = PCH_BOARD_HSW, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2),
|
||||||
.driver_data = board_hsw, },
|
.driver_data = PCH_BOARD_HSW, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT),
|
||||||
.driver_data = board_wpt, },
|
.driver_data = PCH_BOARD_WPT, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL),
|
||||||
.driver_data = board_skl, },
|
.driver_data = PCH_BOARD_SKL, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H),
|
||||||
.driver_data = board_skl, },
|
.driver_data = PCH_BOARD_SKL, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL),
|
||||||
.driver_data = board_cnl, },
|
.driver_data = PCH_BOARD_CNL, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H),
|
||||||
.driver_data = board_cnl, },
|
.driver_data = PCH_BOARD_CNL, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_LP),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_LP),
|
||||||
.driver_data = board_cnl, },
|
.driver_data = PCH_BOARD_CNL, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CML_H),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CML_H),
|
||||||
.driver_data = board_cml, },
|
.driver_data = PCH_BOARD_CML, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_LWB),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_LWB),
|
||||||
.driver_data = board_lwb, },
|
.driver_data = PCH_BOARD_LWB, },
|
||||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WBG),
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WBG),
|
||||||
.driver_data = board_wbg, },
|
.driver_data = PCH_BOARD_WBG, },
|
||||||
{ 0, },
|
{ 0, },
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
|
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/*
|
/*
|
||||||
* intel_powerclamp.c - package c-state idle injection
|
* intel_powerclamp.c - package c-state idle injection
|
||||||
*
|
*
|
||||||
* Copyright (c) 2012, Intel Corporation.
|
* Copyright (c) 2012-2023, Intel Corporation.
|
||||||
*
|
*
|
||||||
* Authors:
|
* Authors:
|
||||||
* Arjan van de Ven <arjan@linux.intel.com>
|
* Arjan van de Ven <arjan@linux.intel.com>
|
||||||
|
@ -27,23 +27,17 @@
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/kthread.h>
|
|
||||||
#include <linux/cpu.h>
|
#include <linux/cpu.h>
|
||||||
#include <linux/thermal.h>
|
#include <linux/thermal.h>
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/tick.h>
|
|
||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
#include <linux/seq_file.h>
|
#include <linux/seq_file.h>
|
||||||
#include <linux/sched/rt.h>
|
#include <linux/idle_inject.h>
|
||||||
#include <uapi/linux/sched/types.h>
|
|
||||||
|
|
||||||
#include <asm/nmi.h>
|
|
||||||
#include <asm/msr.h>
|
#include <asm/msr.h>
|
||||||
#include <asm/mwait.h>
|
#include <asm/mwait.h>
|
||||||
#include <asm/cpu_device_id.h>
|
#include <asm/cpu_device_id.h>
|
||||||
#include <asm/hardirq.h>
|
|
||||||
|
|
||||||
#define MAX_TARGET_RATIO (50U)
|
#define MAX_TARGET_RATIO (100U)
|
||||||
/* For each undisturbed clamping period (no extra wake ups during idle time),
|
/* For each undisturbed clamping period (no extra wake ups during idle time),
|
||||||
* we increment the confidence counter for the given target ratio.
|
* we increment the confidence counter for the given target ratio.
|
||||||
* CONFIDENCE_OK defines the level where runtime calibration results are
|
* CONFIDENCE_OK defines the level where runtime calibration results are
|
||||||
|
@ -57,37 +51,30 @@
|
||||||
|
|
||||||
static unsigned int target_mwait;
|
static unsigned int target_mwait;
|
||||||
static struct dentry *debug_dir;
|
static struct dentry *debug_dir;
|
||||||
|
static bool poll_pkg_cstate_enable;
|
||||||
|
|
||||||
/* user selected target */
|
/* Idle ratio observed using package C-state counters */
|
||||||
static unsigned int set_target_ratio;
|
|
||||||
static unsigned int current_ratio;
|
static unsigned int current_ratio;
|
||||||
|
|
||||||
|
/* Skip the idle injection till set to true */
|
||||||
static bool should_skip;
|
static bool should_skip;
|
||||||
|
|
||||||
static unsigned int control_cpu; /* The cpu assigned to collect stat and update
|
struct powerclamp_data {
|
||||||
* control parameters. default to BSP but BSP
|
|
||||||
* can be offlined.
|
|
||||||
*/
|
|
||||||
static bool clamping;
|
|
||||||
|
|
||||||
struct powerclamp_worker_data {
|
|
||||||
struct kthread_worker *worker;
|
|
||||||
struct kthread_work balancing_work;
|
|
||||||
struct kthread_delayed_work idle_injection_work;
|
|
||||||
unsigned int cpu;
|
unsigned int cpu;
|
||||||
unsigned int count;
|
unsigned int count;
|
||||||
unsigned int guard;
|
unsigned int guard;
|
||||||
unsigned int window_size_now;
|
unsigned int window_size_now;
|
||||||
unsigned int target_ratio;
|
unsigned int target_ratio;
|
||||||
unsigned int duration_jiffies;
|
|
||||||
bool clamping;
|
bool clamping;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct powerclamp_worker_data __percpu *worker_data;
|
static struct powerclamp_data powerclamp_data;
|
||||||
static struct thermal_cooling_device *cooling_dev;
|
|
||||||
static unsigned long *cpu_clamping_mask; /* bit map for tracking per cpu
|
|
||||||
* clamping kthread worker
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
static struct thermal_cooling_device *cooling_dev;
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(powerclamp_lock);
|
||||||
|
|
||||||
|
/* This duration is in microseconds */
|
||||||
static unsigned int duration;
|
static unsigned int duration;
|
||||||
static unsigned int pkg_cstate_ratio_cur;
|
static unsigned int pkg_cstate_ratio_cur;
|
||||||
static unsigned int window_size;
|
static unsigned int window_size;
|
||||||
|
@ -104,24 +91,170 @@ static int duration_set(const char *arg, const struct kernel_param *kp)
|
||||||
pr_err("Out of recommended range %lu, between 6-25ms\n",
|
pr_err("Out of recommended range %lu, between 6-25ms\n",
|
||||||
new_duration);
|
new_duration);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
duration = clamp(new_duration, 6ul, 25ul);
|
mutex_lock(&powerclamp_lock);
|
||||||
smp_mb();
|
duration = clamp(new_duration, 6ul, 25ul) * 1000;
|
||||||
|
mutex_unlock(&powerclamp_lock);
|
||||||
exit:
|
exit:
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int duration_get(char *buf, const struct kernel_param *kp)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&powerclamp_lock);
|
||||||
|
ret = sysfs_emit(buf, "%d\n", duration / 1000);
|
||||||
|
mutex_unlock(&powerclamp_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct kernel_param_ops duration_ops = {
|
static const struct kernel_param_ops duration_ops = {
|
||||||
.set = duration_set,
|
.set = duration_set,
|
||||||
|
.get = duration_get,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_param_cb(duration, &duration_ops, NULL, 0644);
|
||||||
|
MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec.");
|
||||||
|
|
||||||
|
#define DEFAULT_MAX_IDLE 50
|
||||||
|
#define MAX_ALL_CPU_IDLE 75
|
||||||
|
|
||||||
|
static u8 max_idle = DEFAULT_MAX_IDLE;
|
||||||
|
|
||||||
|
static cpumask_var_t idle_injection_cpu_mask;
|
||||||
|
|
||||||
|
static int allocate_copy_idle_injection_mask(const struct cpumask *copy_mask)
|
||||||
|
{
|
||||||
|
if (cpumask_available(idle_injection_cpu_mask))
|
||||||
|
goto copy_mask;
|
||||||
|
|
||||||
|
/* This mask is allocated only one time and freed during module exit */
|
||||||
|
if (!alloc_cpumask_var(&idle_injection_cpu_mask, GFP_KERNEL))
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
copy_mask:
|
||||||
|
cpumask_copy(idle_injection_cpu_mask, copy_mask);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return true if the cpumask and idle percent combination is invalid */
|
||||||
|
static bool check_invalid(cpumask_var_t mask, u8 idle)
|
||||||
|
{
|
||||||
|
if (cpumask_equal(cpu_present_mask, mask) && idle > MAX_ALL_CPU_IDLE)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cpumask_set(const char *arg, const struct kernel_param *kp)
|
||||||
|
{
|
||||||
|
cpumask_var_t new_mask;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&powerclamp_lock);
|
||||||
|
|
||||||
|
/* Can't set mask when cooling device is in use */
|
||||||
|
if (powerclamp_data.clamping) {
|
||||||
|
ret = -EAGAIN;
|
||||||
|
goto skip_cpumask_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = alloc_cpumask_var(&new_mask, GFP_KERNEL);
|
||||||
|
if (!ret)
|
||||||
|
goto skip_cpumask_set;
|
||||||
|
|
||||||
|
ret = bitmap_parse(arg, strlen(arg), cpumask_bits(new_mask),
|
||||||
|
nr_cpumask_bits);
|
||||||
|
if (ret)
|
||||||
|
goto free_cpumask_set;
|
||||||
|
|
||||||
|
if (cpumask_empty(new_mask) || check_invalid(new_mask, max_idle)) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto free_cpumask_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When module parameters are passed from kernel command line
|
||||||
|
* during insmod, the module parameter callback is called
|
||||||
|
* before powerclamp_init(), so we can't assume that some
|
||||||
|
* cpumask can be allocated and copied before here. Also
|
||||||
|
* in this case this cpumask is used as the default mask.
|
||||||
|
*/
|
||||||
|
ret = allocate_copy_idle_injection_mask(new_mask);
|
||||||
|
|
||||||
|
free_cpumask_set:
|
||||||
|
free_cpumask_var(new_mask);
|
||||||
|
skip_cpumask_set:
|
||||||
|
mutex_unlock(&powerclamp_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cpumask_get(char *buf, const struct kernel_param *kp)
|
||||||
|
{
|
||||||
|
if (!cpumask_available(idle_injection_cpu_mask))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
return bitmap_print_to_pagebuf(false, buf, cpumask_bits(idle_injection_cpu_mask),
|
||||||
|
nr_cpumask_bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct kernel_param_ops cpumask_ops = {
|
||||||
|
.set = cpumask_set,
|
||||||
|
.get = cpumask_get,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_param_cb(cpumask, &cpumask_ops, NULL, 0644);
|
||||||
|
MODULE_PARM_DESC(cpumask, "Mask of CPUs to use for idle injection.");
|
||||||
|
|
||||||
|
static int max_idle_set(const char *arg, const struct kernel_param *kp)
|
||||||
|
{
|
||||||
|
u8 new_max_idle;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
mutex_lock(&powerclamp_lock);
|
||||||
|
|
||||||
|
/* Can't set mask when cooling device is in use */
|
||||||
|
if (powerclamp_data.clamping) {
|
||||||
|
ret = -EAGAIN;
|
||||||
|
goto skip_limit_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = kstrtou8(arg, 10, &new_max_idle);
|
||||||
|
if (ret)
|
||||||
|
goto skip_limit_set;
|
||||||
|
|
||||||
|
if (new_max_idle > MAX_TARGET_RATIO) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto skip_limit_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_invalid(idle_injection_cpu_mask, new_max_idle)) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto skip_limit_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
max_idle = new_max_idle;
|
||||||
|
|
||||||
|
skip_limit_set:
|
||||||
|
mutex_unlock(&powerclamp_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct kernel_param_ops max_idle_ops = {
|
||||||
|
.set = max_idle_set,
|
||||||
.get = param_get_int,
|
.get = param_get_int,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module_param_cb(max_idle, &max_idle_ops, &max_idle, 0644);
|
||||||
module_param_cb(duration, &duration_ops, &duration, 0644);
|
MODULE_PARM_DESC(max_idle, "maximum injected idle time to the total CPU time ratio in percent range:1-100");
|
||||||
MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec.");
|
|
||||||
|
|
||||||
struct powerclamp_calibration_data {
|
struct powerclamp_calibration_data {
|
||||||
unsigned long confidence; /* used for calibration, basically a counter
|
unsigned long confidence; /* used for calibration, basically a counter
|
||||||
|
@ -261,6 +394,9 @@ static unsigned int get_compensation(int ratio)
|
||||||
{
|
{
|
||||||
unsigned int comp = 0;
|
unsigned int comp = 0;
|
||||||
|
|
||||||
|
if (!poll_pkg_cstate_enable)
|
||||||
|
return 0;
|
||||||
|
|
||||||
/* we only use compensation if all adjacent ones are good */
|
/* we only use compensation if all adjacent ones are good */
|
||||||
if (ratio == 1 &&
|
if (ratio == 1 &&
|
||||||
cal_data[ratio].confidence >= CONFIDENCE_OK &&
|
cal_data[ratio].confidence >= CONFIDENCE_OK &&
|
||||||
|
@ -302,7 +438,7 @@ static void adjust_compensation(int target_ratio, unsigned int win)
|
||||||
if (d->confidence >= CONFIDENCE_OK)
|
if (d->confidence >= CONFIDENCE_OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
delta = set_target_ratio - current_ratio;
|
delta = powerclamp_data.target_ratio - current_ratio;
|
||||||
/* filter out bad data */
|
/* filter out bad data */
|
||||||
if (delta >= 0 && delta <= (1+target_ratio/10)) {
|
if (delta >= 0 && delta <= (1+target_ratio/10)) {
|
||||||
if (d->steady_comp)
|
if (d->steady_comp)
|
||||||
|
@ -341,82 +477,39 @@ static bool powerclamp_adjust_controls(unsigned int target_ratio,
|
||||||
adjust_compensation(target_ratio, win);
|
adjust_compensation(target_ratio, win);
|
||||||
|
|
||||||
/* if we are above target+guard, skip */
|
/* if we are above target+guard, skip */
|
||||||
return set_target_ratio + guard <= current_ratio;
|
return powerclamp_data.target_ratio + guard <= current_ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clamp_balancing_func(struct kthread_work *work)
|
/*
|
||||||
|
* This function calculates runtime from the current target ratio.
|
||||||
|
* This function gets called under powerclamp_lock.
|
||||||
|
*/
|
||||||
|
static unsigned int get_run_time(void)
|
||||||
{
|
{
|
||||||
struct powerclamp_worker_data *w_data;
|
|
||||||
int sleeptime;
|
|
||||||
unsigned long target_jiffies;
|
|
||||||
unsigned int compensated_ratio;
|
unsigned int compensated_ratio;
|
||||||
int interval; /* jiffies to sleep for each attempt */
|
unsigned int runtime;
|
||||||
|
|
||||||
w_data = container_of(work, struct powerclamp_worker_data,
|
|
||||||
balancing_work);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* make sure user selected ratio does not take effect until
|
* make sure user selected ratio does not take effect until
|
||||||
* the next round. adjust target_ratio if user has changed
|
* the next round. adjust target_ratio if user has changed
|
||||||
* target such that we can converge quickly.
|
* target such that we can converge quickly.
|
||||||
*/
|
*/
|
||||||
w_data->target_ratio = READ_ONCE(set_target_ratio);
|
powerclamp_data.guard = 1 + powerclamp_data.target_ratio / 20;
|
||||||
w_data->guard = 1 + w_data->target_ratio / 20;
|
powerclamp_data.window_size_now = window_size;
|
||||||
w_data->window_size_now = window_size;
|
|
||||||
w_data->duration_jiffies = msecs_to_jiffies(duration);
|
|
||||||
w_data->count++;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* systems may have different ability to enter package level
|
* systems may have different ability to enter package level
|
||||||
* c-states, thus we need to compensate the injected idle ratio
|
* c-states, thus we need to compensate the injected idle ratio
|
||||||
* to achieve the actual target reported by the HW.
|
* to achieve the actual target reported by the HW.
|
||||||
*/
|
*/
|
||||||
compensated_ratio = w_data->target_ratio +
|
compensated_ratio = powerclamp_data.target_ratio +
|
||||||
get_compensation(w_data->target_ratio);
|
get_compensation(powerclamp_data.target_ratio);
|
||||||
if (compensated_ratio <= 0)
|
if (compensated_ratio <= 0)
|
||||||
compensated_ratio = 1;
|
compensated_ratio = 1;
|
||||||
interval = w_data->duration_jiffies * 100 / compensated_ratio;
|
|
||||||
|
|
||||||
/* align idle time */
|
runtime = duration * 100 / compensated_ratio - duration;
|
||||||
target_jiffies = roundup(jiffies, interval);
|
|
||||||
sleeptime = target_jiffies - jiffies;
|
|
||||||
if (sleeptime <= 0)
|
|
||||||
sleeptime = 1;
|
|
||||||
|
|
||||||
if (clamping && w_data->clamping && cpu_online(w_data->cpu))
|
return runtime;
|
||||||
kthread_queue_delayed_work(w_data->worker,
|
|
||||||
&w_data->idle_injection_work,
|
|
||||||
sleeptime);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void clamp_idle_injection_func(struct kthread_work *work)
|
|
||||||
{
|
|
||||||
struct powerclamp_worker_data *w_data;
|
|
||||||
|
|
||||||
w_data = container_of(work, struct powerclamp_worker_data,
|
|
||||||
idle_injection_work.work);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* only elected controlling cpu can collect stats and update
|
|
||||||
* control parameters.
|
|
||||||
*/
|
|
||||||
if (w_data->cpu == control_cpu &&
|
|
||||||
!(w_data->count % w_data->window_size_now)) {
|
|
||||||
should_skip =
|
|
||||||
powerclamp_adjust_controls(w_data->target_ratio,
|
|
||||||
w_data->guard,
|
|
||||||
w_data->window_size_now);
|
|
||||||
smp_mb();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (should_skip)
|
|
||||||
goto balance;
|
|
||||||
|
|
||||||
play_idle(jiffies_to_usecs(w_data->duration_jiffies));
|
|
||||||
|
|
||||||
balance:
|
|
||||||
if (clamping && w_data->clamping && cpu_online(w_data->cpu))
|
|
||||||
kthread_queue_work(w_data->worker, &w_data->balancing_work);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -452,128 +545,131 @@ static void poll_pkg_cstate(struct work_struct *dummy)
|
||||||
msr_last = msr_now;
|
msr_last = msr_now;
|
||||||
tsc_last = tsc_now;
|
tsc_last = tsc_now;
|
||||||
|
|
||||||
if (true == clamping)
|
mutex_lock(&powerclamp_lock);
|
||||||
|
if (powerclamp_data.clamping)
|
||||||
schedule_delayed_work(&poll_pkg_cstate_work, HZ);
|
schedule_delayed_work(&poll_pkg_cstate_work, HZ);
|
||||||
|
mutex_unlock(&powerclamp_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void start_power_clamp_worker(unsigned long cpu)
|
static struct idle_inject_device *ii_dev;
|
||||||
{
|
|
||||||
struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu);
|
|
||||||
struct kthread_worker *worker;
|
|
||||||
|
|
||||||
worker = kthread_create_worker_on_cpu(cpu, 0, "kidle_inj/%ld", cpu);
|
/*
|
||||||
if (IS_ERR(worker))
|
* This function is called from idle injection core on timer expiry
|
||||||
|
* for the run duration. This allows powerclamp to readjust or skip
|
||||||
|
* injecting idle for this cycle.
|
||||||
|
*/
|
||||||
|
static bool idle_inject_update(void)
|
||||||
|
{
|
||||||
|
bool update = false;
|
||||||
|
|
||||||
|
/* We can't sleep in this callback */
|
||||||
|
if (!mutex_trylock(&powerclamp_lock))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!(powerclamp_data.count % powerclamp_data.window_size_now)) {
|
||||||
|
|
||||||
|
should_skip = powerclamp_adjust_controls(powerclamp_data.target_ratio,
|
||||||
|
powerclamp_data.guard,
|
||||||
|
powerclamp_data.window_size_now);
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
unsigned int runtime = get_run_time();
|
||||||
|
|
||||||
|
idle_inject_set_duration(ii_dev, runtime, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
powerclamp_data.count++;
|
||||||
|
|
||||||
|
mutex_unlock(&powerclamp_lock);
|
||||||
|
|
||||||
|
if (should_skip)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function starts idle injection by calling idle_inject_start() */
|
||||||
|
static void trigger_idle_injection(void)
|
||||||
|
{
|
||||||
|
unsigned int runtime = get_run_time();
|
||||||
|
|
||||||
|
idle_inject_set_duration(ii_dev, runtime, duration);
|
||||||
|
idle_inject_start(ii_dev);
|
||||||
|
powerclamp_data.clamping = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called from start_power_clamp() to register
|
||||||
|
* CPUS with powercap idle injection register and set default
|
||||||
|
* idle duration and latency.
|
||||||
|
*/
|
||||||
|
static int powerclamp_idle_injection_register(void)
|
||||||
|
{
|
||||||
|
poll_pkg_cstate_enable = false;
|
||||||
|
if (cpumask_equal(cpu_present_mask, idle_injection_cpu_mask)) {
|
||||||
|
ii_dev = idle_inject_register_full(idle_injection_cpu_mask, idle_inject_update);
|
||||||
|
if (topology_max_packages() == 1 && topology_max_die_per_package() == 1)
|
||||||
|
poll_pkg_cstate_enable = true;
|
||||||
|
} else {
|
||||||
|
ii_dev = idle_inject_register(idle_injection_cpu_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ii_dev) {
|
||||||
|
pr_err("powerclamp: idle_inject_register failed\n");
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
idle_inject_set_duration(ii_dev, TICK_USEC, duration);
|
||||||
|
idle_inject_set_latency(ii_dev, UINT_MAX);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called from end_power_clamp() to stop idle injection
|
||||||
|
* and unregister CPUS from powercap idle injection core.
|
||||||
|
*/
|
||||||
|
static void remove_idle_injection(void)
|
||||||
|
{
|
||||||
|
if (!powerclamp_data.clamping)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
w_data->worker = worker;
|
powerclamp_data.clamping = false;
|
||||||
w_data->count = 0;
|
idle_inject_stop(ii_dev);
|
||||||
w_data->cpu = cpu;
|
|
||||||
w_data->clamping = true;
|
|
||||||
set_bit(cpu, cpu_clamping_mask);
|
|
||||||
sched_set_fifo(worker->task);
|
|
||||||
kthread_init_work(&w_data->balancing_work, clamp_balancing_func);
|
|
||||||
kthread_init_delayed_work(&w_data->idle_injection_work,
|
|
||||||
clamp_idle_injection_func);
|
|
||||||
kthread_queue_work(w_data->worker, &w_data->balancing_work);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void stop_power_clamp_worker(unsigned long cpu)
|
|
||||||
{
|
|
||||||
struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu);
|
|
||||||
|
|
||||||
if (!w_data->worker)
|
|
||||||
return;
|
|
||||||
|
|
||||||
w_data->clamping = false;
|
|
||||||
/*
|
|
||||||
* Make sure that all works that get queued after this point see
|
|
||||||
* the clamping disabled. The counter part is not needed because
|
|
||||||
* there is an implicit memory barrier when the queued work
|
|
||||||
* is proceed.
|
|
||||||
*/
|
|
||||||
smp_wmb();
|
|
||||||
kthread_cancel_work_sync(&w_data->balancing_work);
|
|
||||||
kthread_cancel_delayed_work_sync(&w_data->idle_injection_work);
|
|
||||||
/*
|
|
||||||
* The balancing work still might be queued here because
|
|
||||||
* the handling of the "clapming" variable, cancel, and queue
|
|
||||||
* operations are not synchronized via a lock. But it is not
|
|
||||||
* a big deal. The balancing work is fast and destroy kthread
|
|
||||||
* will wait for it.
|
|
||||||
*/
|
|
||||||
clear_bit(w_data->cpu, cpu_clamping_mask);
|
|
||||||
kthread_destroy_worker(w_data->worker);
|
|
||||||
|
|
||||||
w_data->worker = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called when user change the cooling device
|
||||||
|
* state from zero to some other value.
|
||||||
|
*/
|
||||||
static int start_power_clamp(void)
|
static int start_power_clamp(void)
|
||||||
{
|
{
|
||||||
unsigned long cpu;
|
int ret;
|
||||||
|
|
||||||
set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1);
|
ret = powerclamp_idle_injection_register();
|
||||||
/* prevent cpu hotplug */
|
if (!ret) {
|
||||||
cpus_read_lock();
|
trigger_idle_injection();
|
||||||
|
if (poll_pkg_cstate_enable)
|
||||||
/* prefer BSP */
|
schedule_delayed_work(&poll_pkg_cstate_work, 0);
|
||||||
control_cpu = cpumask_first(cpu_online_mask);
|
|
||||||
|
|
||||||
clamping = true;
|
|
||||||
schedule_delayed_work(&poll_pkg_cstate_work, 0);
|
|
||||||
|
|
||||||
/* start one kthread worker per online cpu */
|
|
||||||
for_each_online_cpu(cpu) {
|
|
||||||
start_power_clamp_worker(cpu);
|
|
||||||
}
|
}
|
||||||
cpus_read_unlock();
|
|
||||||
|
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called when user change the cooling device
|
||||||
|
* state from non zero value zero.
|
||||||
|
*/
|
||||||
static void end_power_clamp(void)
|
static void end_power_clamp(void)
|
||||||
{
|
{
|
||||||
int i;
|
if (powerclamp_data.clamping) {
|
||||||
|
remove_idle_injection();
|
||||||
/*
|
idle_inject_unregister(ii_dev);
|
||||||
* Block requeuing in all the kthread workers. They will flush and
|
|
||||||
* stop faster.
|
|
||||||
*/
|
|
||||||
clamping = false;
|
|
||||||
for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) {
|
|
||||||
pr_debug("clamping worker for cpu %d alive, destroy\n", i);
|
|
||||||
stop_power_clamp_worker(i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int powerclamp_cpu_online(unsigned int cpu)
|
|
||||||
{
|
|
||||||
if (clamping == false)
|
|
||||||
return 0;
|
|
||||||
start_power_clamp_worker(cpu);
|
|
||||||
/* prefer BSP as controlling CPU */
|
|
||||||
if (cpu == 0) {
|
|
||||||
control_cpu = 0;
|
|
||||||
smp_mb();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int powerclamp_cpu_predown(unsigned int cpu)
|
|
||||||
{
|
|
||||||
if (clamping == false)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
stop_power_clamp_worker(cpu);
|
|
||||||
if (cpu != control_cpu)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
control_cpu = cpumask_first(cpu_online_mask);
|
|
||||||
if (control_cpu == cpu)
|
|
||||||
control_cpu = cpumask_next(cpu, cpu_online_mask);
|
|
||||||
smp_mb();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int powerclamp_get_max_state(struct thermal_cooling_device *cdev,
|
static int powerclamp_get_max_state(struct thermal_cooling_device *cdev,
|
||||||
unsigned long *state)
|
unsigned long *state)
|
||||||
{
|
{
|
||||||
|
@ -585,11 +681,9 @@ static int powerclamp_get_max_state(struct thermal_cooling_device *cdev,
|
||||||
static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev,
|
static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev,
|
||||||
unsigned long *state)
|
unsigned long *state)
|
||||||
{
|
{
|
||||||
if (true == clamping)
|
mutex_lock(&powerclamp_lock);
|
||||||
*state = pkg_cstate_ratio_cur;
|
*state = powerclamp_data.target_ratio;
|
||||||
else
|
mutex_unlock(&powerclamp_lock);
|
||||||
/* to save power, do not poll idle ratio while not clamping */
|
|
||||||
*state = -1; /* indicates invalid state */
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -599,24 +693,32 @@ static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev,
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
mutex_lock(&powerclamp_lock);
|
||||||
|
|
||||||
new_target_ratio = clamp(new_target_ratio, 0UL,
|
new_target_ratio = clamp(new_target_ratio, 0UL,
|
||||||
(unsigned long) (MAX_TARGET_RATIO-1));
|
(unsigned long) (max_idle - 1));
|
||||||
if (set_target_ratio == 0 && new_target_ratio > 0) {
|
if (!powerclamp_data.target_ratio && new_target_ratio > 0) {
|
||||||
pr_info("Start idle injection to reduce power\n");
|
pr_info("Start idle injection to reduce power\n");
|
||||||
set_target_ratio = new_target_ratio;
|
powerclamp_data.target_ratio = new_target_ratio;
|
||||||
ret = start_power_clamp();
|
ret = start_power_clamp();
|
||||||
|
if (ret)
|
||||||
|
powerclamp_data.target_ratio = 0;
|
||||||
goto exit_set;
|
goto exit_set;
|
||||||
} else if (set_target_ratio > 0 && new_target_ratio == 0) {
|
} else if (powerclamp_data.target_ratio > 0 && new_target_ratio == 0) {
|
||||||
pr_info("Stop forced idle injection\n");
|
pr_info("Stop forced idle injection\n");
|
||||||
end_power_clamp();
|
end_power_clamp();
|
||||||
set_target_ratio = 0;
|
powerclamp_data.target_ratio = 0;
|
||||||
} else /* adjust currently running */ {
|
} else /* adjust currently running */ {
|
||||||
set_target_ratio = new_target_ratio;
|
unsigned int runtime;
|
||||||
/* make new set_target_ratio visible to other cpus */
|
|
||||||
smp_mb();
|
powerclamp_data.target_ratio = new_target_ratio;
|
||||||
|
runtime = get_run_time();
|
||||||
|
idle_inject_set_duration(ii_dev, runtime, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_set:
|
exit_set:
|
||||||
|
mutex_unlock(&powerclamp_lock);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,7 +759,6 @@ static int powerclamp_debug_show(struct seq_file *m, void *unused)
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
seq_printf(m, "controlling cpu: %d\n", control_cpu);
|
|
||||||
seq_printf(m, "pct confidence steady dynamic (compensation)\n");
|
seq_printf(m, "pct confidence steady dynamic (compensation)\n");
|
||||||
for (i = 0; i < MAX_TARGET_RATIO; i++) {
|
for (i = 0; i < MAX_TARGET_RATIO; i++) {
|
||||||
seq_printf(m, "%d\t%lu\t%lu\t%lu\n",
|
seq_printf(m, "%d\t%lu\t%lu\t%lu\n",
|
||||||
|
@ -680,75 +781,57 @@ static inline void powerclamp_create_debug_files(void)
|
||||||
&powerclamp_debug_fops);
|
&powerclamp_debug_fops);
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum cpuhp_state hp_state;
|
|
||||||
|
|
||||||
static int __init powerclamp_init(void)
|
static int __init powerclamp_init(void)
|
||||||
{
|
{
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
cpu_clamping_mask = bitmap_zalloc(num_possible_cpus(), GFP_KERNEL);
|
|
||||||
if (!cpu_clamping_mask)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
/* probe cpu features and ids here */
|
/* probe cpu features and ids here */
|
||||||
retval = powerclamp_probe();
|
retval = powerclamp_probe();
|
||||||
if (retval)
|
if (retval)
|
||||||
goto exit_free;
|
return retval;
|
||||||
|
|
||||||
|
mutex_lock(&powerclamp_lock);
|
||||||
|
retval = allocate_copy_idle_injection_mask(cpu_present_mask);
|
||||||
|
mutex_unlock(&powerclamp_lock);
|
||||||
|
|
||||||
|
if (retval)
|
||||||
|
return retval;
|
||||||
|
|
||||||
/* set default limit, maybe adjusted during runtime based on feedback */
|
/* set default limit, maybe adjusted during runtime based on feedback */
|
||||||
window_size = 2;
|
window_size = 2;
|
||||||
retval = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
|
|
||||||
"thermal/intel_powerclamp:online",
|
|
||||||
powerclamp_cpu_online,
|
|
||||||
powerclamp_cpu_predown);
|
|
||||||
if (retval < 0)
|
|
||||||
goto exit_free;
|
|
||||||
|
|
||||||
hp_state = retval;
|
|
||||||
|
|
||||||
worker_data = alloc_percpu(struct powerclamp_worker_data);
|
|
||||||
if (!worker_data) {
|
|
||||||
retval = -ENOMEM;
|
|
||||||
goto exit_unregister;
|
|
||||||
}
|
|
||||||
|
|
||||||
cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL,
|
cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL,
|
||||||
&powerclamp_cooling_ops);
|
&powerclamp_cooling_ops);
|
||||||
if (IS_ERR(cooling_dev)) {
|
if (IS_ERR(cooling_dev))
|
||||||
retval = -ENODEV;
|
return -ENODEV;
|
||||||
goto exit_free_thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!duration)
|
if (!duration)
|
||||||
duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES);
|
duration = jiffies_to_usecs(DEFAULT_DURATION_JIFFIES);
|
||||||
|
|
||||||
powerclamp_create_debug_files();
|
powerclamp_create_debug_files();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
exit_free_thread:
|
|
||||||
free_percpu(worker_data);
|
|
||||||
exit_unregister:
|
|
||||||
cpuhp_remove_state_nocalls(hp_state);
|
|
||||||
exit_free:
|
|
||||||
bitmap_free(cpu_clamping_mask);
|
|
||||||
return retval;
|
|
||||||
}
|
}
|
||||||
module_init(powerclamp_init);
|
module_init(powerclamp_init);
|
||||||
|
|
||||||
static void __exit powerclamp_exit(void)
|
static void __exit powerclamp_exit(void)
|
||||||
{
|
{
|
||||||
|
mutex_lock(&powerclamp_lock);
|
||||||
end_power_clamp();
|
end_power_clamp();
|
||||||
cpuhp_remove_state_nocalls(hp_state);
|
mutex_unlock(&powerclamp_lock);
|
||||||
free_percpu(worker_data);
|
|
||||||
thermal_cooling_device_unregister(cooling_dev);
|
thermal_cooling_device_unregister(cooling_dev);
|
||||||
bitmap_free(cpu_clamping_mask);
|
|
||||||
|
|
||||||
cancel_delayed_work_sync(&poll_pkg_cstate_work);
|
cancel_delayed_work_sync(&poll_pkg_cstate_work);
|
||||||
debugfs_remove_recursive(debug_dir);
|
debugfs_remove_recursive(debug_dir);
|
||||||
|
|
||||||
|
if (cpumask_available(idle_injection_cpu_mask))
|
||||||
|
free_cpumask_var(idle_injection_cpu_mask);
|
||||||
}
|
}
|
||||||
module_exit(powerclamp_exit);
|
module_exit(powerclamp_exit);
|
||||||
|
|
||||||
|
MODULE_IMPORT_NS(IDLE_INJECT);
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
|
MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
|
||||||
MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
|
MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
#define QRK_DTS_MASK_TP_THRES 0xFF
|
#define QRK_DTS_MASK_TP_THRES 0xFF
|
||||||
#define QRK_DTS_SHIFT_TP 8
|
#define QRK_DTS_SHIFT_TP 8
|
||||||
#define QRK_DTS_ID_TP_CRITICAL 0
|
#define QRK_DTS_ID_TP_CRITICAL 0
|
||||||
|
#define QRK_DTS_ID_TP_HOT 1
|
||||||
#define QRK_DTS_SAFE_TP_THRES 105
|
#define QRK_DTS_SAFE_TP_THRES 105
|
||||||
|
|
||||||
/* Thermal Sensor Register Lock */
|
/* Thermal Sensor Register Lock */
|
||||||
|
@ -104,6 +105,7 @@ struct soc_sensor_entry {
|
||||||
u32 store_ptps;
|
u32 store_ptps;
|
||||||
u32 store_dts_enable;
|
u32 store_dts_enable;
|
||||||
struct thermal_zone_device *tzone;
|
struct thermal_zone_device *tzone;
|
||||||
|
struct thermal_trip trips[QRK_MAX_DTS_TRIPS];
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct soc_sensor_entry *soc_dts;
|
static struct soc_sensor_entry *soc_dts;
|
||||||
|
@ -172,9 +174,9 @@ static int soc_dts_disable(struct thermal_zone_device *tzd)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _get_trip_temp(int trip, int *temp)
|
static int get_trip_temp(int trip)
|
||||||
{
|
{
|
||||||
int status;
|
int status, temp;
|
||||||
u32 out;
|
u32 out;
|
||||||
|
|
||||||
mutex_lock(&dts_update_mutex);
|
mutex_lock(&dts_update_mutex);
|
||||||
|
@ -183,7 +185,7 @@ static int _get_trip_temp(int trip, int *temp)
|
||||||
mutex_unlock(&dts_update_mutex);
|
mutex_unlock(&dts_update_mutex);
|
||||||
|
|
||||||
if (status)
|
if (status)
|
||||||
return status;
|
return THERMAL_TEMP_INVALID;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Thermal Sensor Programmable Trip Point Register has 8-bit
|
* Thermal Sensor Programmable Trip Point Register has 8-bit
|
||||||
|
@ -191,21 +193,10 @@ static int _get_trip_temp(int trip, int *temp)
|
||||||
* thresholds. The threshold value is always offset by its
|
* thresholds. The threshold value is always offset by its
|
||||||
* temperature base (50 degree Celsius).
|
* temperature base (50 degree Celsius).
|
||||||
*/
|
*/
|
||||||
*temp = (out >> (trip * QRK_DTS_SHIFT_TP)) & QRK_DTS_MASK_TP_THRES;
|
temp = (out >> (trip * QRK_DTS_SHIFT_TP)) & QRK_DTS_MASK_TP_THRES;
|
||||||
*temp -= QRK_DTS_TEMP_BASE;
|
temp -= QRK_DTS_TEMP_BASE;
|
||||||
|
|
||||||
return 0;
|
return temp;
|
||||||
}
|
|
||||||
|
|
||||||
static inline int sys_get_trip_temp(struct thermal_zone_device *tzd,
|
|
||||||
int trip, int *temp)
|
|
||||||
{
|
|
||||||
return _get_trip_temp(trip, temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int sys_get_crit_temp(struct thermal_zone_device *tzd, int *temp)
|
|
||||||
{
|
|
||||||
return _get_trip_temp(QRK_DTS_ID_TP_CRITICAL, temp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int update_trip_temp(struct soc_sensor_entry *aux_entry,
|
static int update_trip_temp(struct soc_sensor_entry *aux_entry,
|
||||||
|
@ -262,17 +253,6 @@ static inline int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
|
||||||
return update_trip_temp(tzd->devdata, trip, temp);
|
return update_trip_temp(tzd->devdata, trip, temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sys_get_trip_type(struct thermal_zone_device *thermal,
|
|
||||||
int trip, enum thermal_trip_type *type)
|
|
||||||
{
|
|
||||||
if (trip)
|
|
||||||
*type = THERMAL_TRIP_HOT;
|
|
||||||
else
|
|
||||||
*type = THERMAL_TRIP_CRITICAL;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int sys_get_curr_temp(struct thermal_zone_device *tzd,
|
static int sys_get_curr_temp(struct thermal_zone_device *tzd,
|
||||||
int *temp)
|
int *temp)
|
||||||
{
|
{
|
||||||
|
@ -315,10 +295,7 @@ static int sys_change_mode(struct thermal_zone_device *tzd,
|
||||||
|
|
||||||
static struct thermal_zone_device_ops tzone_ops = {
|
static struct thermal_zone_device_ops tzone_ops = {
|
||||||
.get_temp = sys_get_curr_temp,
|
.get_temp = sys_get_curr_temp,
|
||||||
.get_trip_temp = sys_get_trip_temp,
|
|
||||||
.get_trip_type = sys_get_trip_type,
|
|
||||||
.set_trip_temp = sys_set_trip_temp,
|
.set_trip_temp = sys_set_trip_temp,
|
||||||
.get_crit_temp = sys_get_crit_temp,
|
|
||||||
.change_mode = sys_change_mode,
|
.change_mode = sys_change_mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -385,10 +362,18 @@ static struct soc_sensor_entry *alloc_soc_dts(void)
|
||||||
goto err_ret;
|
goto err_ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
aux_entry->tzone = thermal_zone_device_register("quark_dts",
|
aux_entry->trips[QRK_DTS_ID_TP_CRITICAL].temperature = get_trip_temp(QRK_DTS_ID_TP_CRITICAL);
|
||||||
QRK_MAX_DTS_TRIPS,
|
aux_entry->trips[QRK_DTS_ID_TP_CRITICAL].type = THERMAL_TRIP_CRITICAL;
|
||||||
wr_mask,
|
|
||||||
aux_entry, &tzone_ops, NULL, 0, polling_delay);
|
aux_entry->trips[QRK_DTS_ID_TP_HOT].temperature = get_trip_temp(QRK_DTS_ID_TP_HOT);
|
||||||
|
aux_entry->trips[QRK_DTS_ID_TP_HOT].type = THERMAL_TRIP_HOT;
|
||||||
|
|
||||||
|
aux_entry->tzone = thermal_zone_device_register_with_trips("quark_dts",
|
||||||
|
aux_entry->trips,
|
||||||
|
QRK_MAX_DTS_TRIPS,
|
||||||
|
wr_mask,
|
||||||
|
aux_entry, &tzone_ops,
|
||||||
|
NULL, 0, polling_delay);
|
||||||
if (IS_ERR(aux_entry->tzone)) {
|
if (IS_ERR(aux_entry->tzone)) {
|
||||||
err = PTR_ERR(aux_entry->tzone);
|
err = PTR_ERR(aux_entry->tzone);
|
||||||
goto err_ret;
|
goto err_ret;
|
||||||
|
|
|
@ -21,42 +21,11 @@
|
||||||
#define TEMP_MIN_DECIK 2180
|
#define TEMP_MIN_DECIK 2180
|
||||||
#define TEMP_MAX_DECIK 4480
|
#define TEMP_MAX_DECIK 4480
|
||||||
|
|
||||||
static int thermal_acpi_trip_init(struct acpi_device *adev,
|
static int thermal_acpi_trip_temp(struct acpi_device *adev, char *obj_name,
|
||||||
enum thermal_trip_type type, int id,
|
int *ret_temp)
|
||||||
struct thermal_trip *trip)
|
|
||||||
{
|
{
|
||||||
unsigned long long temp;
|
unsigned long long temp;
|
||||||
acpi_status status;
|
acpi_status status;
|
||||||
char obj_name[5];
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case THERMAL_TRIP_ACTIVE:
|
|
||||||
if (id < 0 || id > 9)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
obj_name[1] = 'A';
|
|
||||||
obj_name[2] = 'C';
|
|
||||||
obj_name[3] = '0' + id;
|
|
||||||
break;
|
|
||||||
case THERMAL_TRIP_PASSIVE:
|
|
||||||
obj_name[1] = 'P';
|
|
||||||
obj_name[2] = 'S';
|
|
||||||
obj_name[3] = 'V';
|
|
||||||
break;
|
|
||||||
case THERMAL_TRIP_HOT:
|
|
||||||
obj_name[1] = 'H';
|
|
||||||
obj_name[2] = 'O';
|
|
||||||
obj_name[3] = 'T';
|
|
||||||
break;
|
|
||||||
case THERMAL_TRIP_CRITICAL:
|
|
||||||
obj_name[1] = 'C';
|
|
||||||
obj_name[2] = 'R';
|
|
||||||
obj_name[3] = 'T';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj_name[0] = '_';
|
|
||||||
obj_name[4] = '\0';
|
|
||||||
|
|
||||||
status = acpi_evaluate_integer(adev->handle, obj_name, NULL, &temp);
|
status = acpi_evaluate_integer(adev->handle, obj_name, NULL, &temp);
|
||||||
if (ACPI_FAILURE(status)) {
|
if (ACPI_FAILURE(status)) {
|
||||||
|
@ -65,87 +34,84 @@ static int thermal_acpi_trip_init(struct acpi_device *adev,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (temp >= TEMP_MIN_DECIK && temp <= TEMP_MAX_DECIK) {
|
if (temp >= TEMP_MIN_DECIK && temp <= TEMP_MAX_DECIK) {
|
||||||
trip->temperature = deci_kelvin_to_millicelsius(temp);
|
*ret_temp = deci_kelvin_to_millicelsius(temp);
|
||||||
} else {
|
} else {
|
||||||
acpi_handle_debug(adev->handle, "%s result %llu out of range\n",
|
acpi_handle_debug(adev->handle, "%s result %llu out of range\n",
|
||||||
obj_name, temp);
|
obj_name, temp);
|
||||||
trip->temperature = THERMAL_TEMP_INVALID;
|
*ret_temp = THERMAL_TEMP_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
trip->hysteresis = 0;
|
|
||||||
trip->type = type;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* thermal_acpi_trip_active - Get the specified active trip point
|
* thermal_acpi_active_trip_temp - Retrieve active trip point temperature
|
||||||
* @adev: Thermal zone ACPI device object to get the description from.
|
* @adev: Target thermal zone ACPI device object.
|
||||||
* @id: Active cooling level (0 - 9).
|
* @id: Active cooling level (0 - 9).
|
||||||
* @trip: Trip point structure to be populated on success.
|
* @ret_temp: Address to store the retrieved temperature value on success.
|
||||||
*
|
*
|
||||||
* Evaluate the _ACx object for the thermal zone represented by @adev to obtain
|
* Evaluate the _ACx object for the thermal zone represented by @adev to obtain
|
||||||
* the temperature of the active cooling trip point corresponding to the active
|
* the temperature of the active cooling trip point corresponding to the active
|
||||||
* cooling level given by @id and initialize @trip as an active trip point using
|
* cooling level given by @id.
|
||||||
* that temperature value.
|
|
||||||
*
|
*
|
||||||
* Return 0 on success or a negative error value on failure.
|
* Return 0 on success or a negative error value on failure.
|
||||||
*/
|
*/
|
||||||
int thermal_acpi_trip_active(struct acpi_device *adev, int id,
|
int thermal_acpi_active_trip_temp(struct acpi_device *adev, int id, int *ret_temp)
|
||||||
struct thermal_trip *trip)
|
|
||||||
{
|
{
|
||||||
return thermal_acpi_trip_init(adev, THERMAL_TRIP_ACTIVE, id, trip);
|
char obj_name[] = {'_', 'A', 'C', '0' + id, '\0'};
|
||||||
|
|
||||||
|
if (id < 0 || id > 9)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return thermal_acpi_trip_temp(adev, obj_name, ret_temp);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(thermal_acpi_trip_active);
|
EXPORT_SYMBOL_GPL(thermal_acpi_active_trip_temp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* thermal_acpi_trip_passive - Get the passive trip point
|
* thermal_acpi_passive_trip_temp - Retrieve passive trip point temperature
|
||||||
* @adev: Thermal zone ACPI device object to get the description from.
|
* @adev: Target thermal zone ACPI device object.
|
||||||
* @trip: Trip point structure to be populated on success.
|
* @ret_temp: Address to store the retrieved temperature value on success.
|
||||||
*
|
*
|
||||||
* Evaluate the _PSV object for the thermal zone represented by @adev to obtain
|
* Evaluate the _PSV object for the thermal zone represented by @adev to obtain
|
||||||
* the temperature of the passive cooling trip point and initialize @trip as a
|
* the temperature of the passive cooling trip point.
|
||||||
* passive trip point using that temperature value.
|
|
||||||
*
|
*
|
||||||
* Return 0 on success or -ENODATA on failure.
|
* Return 0 on success or -ENODATA on failure.
|
||||||
*/
|
*/
|
||||||
int thermal_acpi_trip_passive(struct acpi_device *adev, struct thermal_trip *trip)
|
int thermal_acpi_passive_trip_temp(struct acpi_device *adev, int *ret_temp)
|
||||||
{
|
{
|
||||||
return thermal_acpi_trip_init(adev, THERMAL_TRIP_PASSIVE, INT_MAX, trip);
|
return thermal_acpi_trip_temp(adev, "_PSV", ret_temp);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(thermal_acpi_trip_passive);
|
EXPORT_SYMBOL_GPL(thermal_acpi_passive_trip_temp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* thermal_acpi_trip_hot - Get the near critical trip point
|
* thermal_acpi_hot_trip_temp - Retrieve hot trip point temperature
|
||||||
* @adev: the ACPI device to get the description from.
|
* @adev: Target thermal zone ACPI device object.
|
||||||
* @trip: a &struct thermal_trip to be filled if the function succeed.
|
* @ret_temp: Address to store the retrieved temperature value on success.
|
||||||
*
|
*
|
||||||
* Evaluate the _HOT object for the thermal zone represented by @adev to obtain
|
* Evaluate the _HOT object for the thermal zone represented by @adev to obtain
|
||||||
* the temperature of the trip point at which the system is expected to be put
|
* the temperature of the trip point at which the system is expected to be put
|
||||||
* into the S4 sleep state and initialize @trip as a hot trip point using that
|
* into the S4 sleep state.
|
||||||
* temperature value.
|
|
||||||
*
|
*
|
||||||
* Return 0 on success or -ENODATA on failure.
|
* Return 0 on success or -ENODATA on failure.
|
||||||
*/
|
*/
|
||||||
int thermal_acpi_trip_hot(struct acpi_device *adev, struct thermal_trip *trip)
|
int thermal_acpi_hot_trip_temp(struct acpi_device *adev, int *ret_temp)
|
||||||
{
|
{
|
||||||
return thermal_acpi_trip_init(adev, THERMAL_TRIP_HOT, INT_MAX, trip);
|
return thermal_acpi_trip_temp(adev, "_HOT", ret_temp);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(thermal_acpi_trip_hot);
|
EXPORT_SYMBOL_GPL(thermal_acpi_hot_trip_temp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* thermal_acpi_trip_critical - Get the critical trip point
|
* thermal_acpi_critical_trip_temp - Retrieve critical trip point temperature
|
||||||
* @adev: the ACPI device to get the description from.
|
* @adev: Target thermal zone ACPI device object.
|
||||||
* @trip: a &struct thermal_trip to be filled if the function succeed.
|
* @ret_temp: Address to store the retrieved temperature value on success.
|
||||||
*
|
*
|
||||||
* Evaluate the _CRT object for the thermal zone represented by @adev to obtain
|
* Evaluate the _CRT object for the thermal zone represented by @adev to obtain
|
||||||
* the temperature of the critical cooling trip point and initialize @trip as a
|
* the temperature of the critical cooling trip point.
|
||||||
* critical trip point using that temperature value.
|
|
||||||
*
|
*
|
||||||
* Return 0 on success or -ENODATA on failure.
|
* Return 0 on success or -ENODATA on failure.
|
||||||
*/
|
*/
|
||||||
int thermal_acpi_trip_critical(struct acpi_device *adev, struct thermal_trip *trip)
|
int thermal_acpi_critical_trip_temp(struct acpi_device *adev, int *ret_temp)
|
||||||
{
|
{
|
||||||
return thermal_acpi_trip_init(adev, THERMAL_TRIP_CRITICAL, INT_MAX, trip);
|
return thermal_acpi_trip_temp(adev, "_CRT", ret_temp);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(thermal_acpi_trip_critical);
|
EXPORT_SYMBOL_GPL(thermal_acpi_critical_trip_temp);
|
||||||
|
|
|
@ -13,6 +13,9 @@ struct idle_inject_device;
|
||||||
|
|
||||||
struct idle_inject_device *idle_inject_register(struct cpumask *cpumask);
|
struct idle_inject_device *idle_inject_register(struct cpumask *cpumask);
|
||||||
|
|
||||||
|
struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask,
|
||||||
|
bool (*update)(void));
|
||||||
|
|
||||||
void idle_inject_unregister(struct idle_inject_device *ii_dev);
|
void idle_inject_unregister(struct idle_inject_device *ii_dev);
|
||||||
|
|
||||||
int idle_inject_start(struct idle_inject_device *ii_dev);
|
int idle_inject_start(struct idle_inject_device *ii_dev);
|
||||||
|
|
|
@ -347,11 +347,10 @@ int thermal_zone_get_num_trips(struct thermal_zone_device *tz);
|
||||||
int thermal_zone_get_crit_temp(struct thermal_zone_device *tz, int *temp);
|
int thermal_zone_get_crit_temp(struct thermal_zone_device *tz, int *temp);
|
||||||
|
|
||||||
#ifdef CONFIG_THERMAL_ACPI
|
#ifdef CONFIG_THERMAL_ACPI
|
||||||
int thermal_acpi_trip_active(struct acpi_device *adev, int id,
|
int thermal_acpi_active_trip_temp(struct acpi_device *adev, int id, int *ret_temp);
|
||||||
struct thermal_trip *trip);
|
int thermal_acpi_passive_trip_temp(struct acpi_device *adev, int *ret_temp);
|
||||||
int thermal_acpi_trip_passive(struct acpi_device *adev, struct thermal_trip *trip);
|
int thermal_acpi_hot_trip_temp(struct acpi_device *adev, int *ret_temp);
|
||||||
int thermal_acpi_trip_hot(struct acpi_device *adev, struct thermal_trip *trip);
|
int thermal_acpi_critical_trip_temp(struct acpi_device *adev, int *ret_temp);
|
||||||
int thermal_acpi_trip_critical(struct acpi_device *adev, struct thermal_trip *trip);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_THERMAL
|
#ifdef CONFIG_THERMAL
|
||||||
|
|
Загрузка…
Ссылка в новой задаче