- "mediatek,mt2712-thermal" : For MT2712 family of SoCs
- "mediatek,mt7622-thermal" : For MT7622 SoC
- "mediatek,mt8183-thermal" : For MT8183 family of SoCs
+ - "mediatek,mt8516-thermal", "mediatek,mt2701-thermal : For MT8516 family of SoCs
- reg: Address range of the thermal controller
- interrupts: IRQ for the thermal controller
- clocks, clock-names: Clocks needed for the thermal controller. required
clocks are:
"therm": Main clock needed for register access
"auxadc": The AUXADC clock
-- resets: Reference to the reset controller controlling the thermal controller.
- mediatek,auxadc: A phandle to the AUXADC which the thermal controller uses
- mediatek,apmixedsys: A phandle to the APMIXEDSYS controller.
- #thermal-sensor-cells : Should be 0. See Documentation/devicetree/bindings/thermal/thermal-sensor.yaml for a description.
Optional properties:
+- resets: Reference to the reset controller controlling the thermal controller.
- nvmem-cells: A phandle to the calibration data provided by a nvmem device. If
unspecified default values shall be used.
- nvmem-cell-names: Should be "calibration-data"
- renesas,r8a77961-thermal # R-Car M3-W+
- renesas,r8a77965-thermal # R-Car M3-N
- renesas,r8a77980-thermal # R-Car V3H
+ - renesas,r8a779a0-thermal # R-Car V3U
+
reg:
minItems: 2
- maxItems: 3
+ maxItems: 4
items:
- description: TSC1 registers
- description: TSC2 registers
- description: TSC3 registers
+ - description: TSC4 registers
interrupts:
items:
required:
- compatible
- reg
- - interrupts
- clocks
- power-domains
- resets
- "#thermal-sensor-cells"
+if:
+ not:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - renesas,r8a779a0-thermal
+then:
+ required:
+ - interrupts
+
additionalProperties: false
examples:
"#thermal-sensor-cells":
const: 0
-if:
- properties:
- compatible:
- contains:
- enum:
- - renesas,thermal-r8a73a4 # R-Mobile APE6
- - renesas,thermal-r8a7779 # R-Car H1
-then:
- required:
- - compatible
- - reg
-else:
- required:
- - compatible
- - reg
- - interrupts
- - clocks
- - power-domains
- - resets
+required:
+ - compatible
+ - reg
+
+allOf:
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - renesas,thermal-r8a73a4 # R-Mobile APE6
+ - renesas,thermal-r8a7779 # R-Car H1
+ then:
+ required:
+ - resets
+ - '#thermal-sensor-cells'
+
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: renesas,thermal-r8a7779 # R-Car H1
+ then:
+ required:
+ - interrupts
+ - clocks
+ - power-domains
additionalProperties: false
The amount of time spent by the cooling device in various cooling
states. The output will have "<state> <time>" pair in each line, which
will mean this cooling device spent <time> msec of time at <state>.
- Output will have one line for each of the supported states. usertime
- units here is 10mS (similar to other time exported in /proc).
+ Output will have one line for each of the supported states.
RO, Required
}
pfdevfreq->devfreq = devfreq;
- cooling = of_devfreq_cooling_register(dev->of_node, devfreq);
+ cooling = devfreq_cooling_em_register(devfreq, NULL);
if (IS_ERR(cooling))
DRM_DEV_INFO(dev, "Failed to register cooling device\n");
else
module_param(kernelmode, uint, 0);
MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off");
-module_param(interval, uint, 0600);
-MODULE_PARM_DESC(interval, "Polling interval of temperature check");
module_param(fanon, uint, 0600);
MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
module_param(fanoff, uint, 0600);
}
if (verbose)
pr_notice("interval changed to: %d\n", interval);
- thermal->polling_delay = interval*1000;
+
+ if (thermal)
+ thermal->polling_delay = interval*1000;
+
prev_interval = interval;
}
}
{
int temp, err = 0;
- acerhdf_check_param(thermal);
-
err = acerhdf_get_temp(&temp);
if (err)
return err;
module_init(acerhdf_init);
module_exit(acerhdf_exit);
+
+static int interval_set_uint(const char *val, const struct kernel_param *kp)
+{
+ int ret;
+
+ ret = param_set_uint(val, kp);
+ if (ret)
+ return ret;
+
+ acerhdf_check_param(thz_dev);
+
+ return 0;
+}
+
+static const struct kernel_param_ops interval_ops = {
+ .set = interval_set_uint,
+ .get = param_get_uint,
+};
+
+module_param_cb(interval, &interval_ops, &interval, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperature check");
#include <linux/thermal.h>
#include "thermal_core.h"
+#include "thermal_hwmon.h"
#define TSENSOR_CFG_REG1 0x4
#define TSENSOR_CFG_REG1_RSET_VBG BIT(12)
return ret;
}
+ if (devm_thermal_add_hwmon_sysfs(pdata->tzd))
+ dev_warn(&pdev->dev, "Failed to add hwmon sysfs attributes\n");
+
ret = amlogic_thermal_initialize(pdata);
if (ret)
return ret;
priv->thermal = thermal;
thermal->tzp->no_hwmon = false;
- ret = thermal_add_hwmon_sysfs(thermal);
- if (ret)
- return ret;
-
- return 0;
+ return thermal_add_hwmon_sysfs(thermal);
}
static struct platform_driver bcm2711_thermal_driver = {
if (cpufreq_cdev->cpufreq_state == state)
return 0;
- cpufreq_cdev->cpufreq_state = state;
-
frequency = get_state_freq(cpufreq_cdev, state);
ret = freq_qos_update_request(&cpufreq_cdev->qos_req, frequency);
-
if (ret > 0) {
+ cpufreq_cdev->cpufreq_state = state;
cpus = cpufreq_cdev->policy->cpus;
max_capacity = arch_scale_cpu_capacity(cpumask_first(cpus));
capacity = frequency * max_capacity;
#include <linux/devfreq.h>
#include <linux/devfreq_cooling.h>
+#include <linux/energy_model.h>
#include <linux/export.h>
#include <linux/idr.h>
#include <linux/slab.h>
* @cdev: Pointer to associated thermal cooling device.
* @devfreq: Pointer to associated devfreq device.
* @cooling_state: Current cooling state.
- * @power_table: Pointer to table with maximum power draw for each
- * cooling state. State is the index into the table, and
- * the power is in mW.
* @freq_table: Pointer to a table with the frequencies sorted in descending
* order. You can index the table by cooling device state
- * @freq_table_size: Size of the @freq_table and @power_table
- * @power_ops: Pointer to devfreq_cooling_power, used to generate the
- * @power_table.
+ * @max_state: It is the last index, that is, one less than the number of the
+ * OPPs
+ * @power_ops: Pointer to devfreq_cooling_power, a more precised model.
* @res_util: Resource utilization scaling factor for the power.
* It is multiplied by 100 to minimize the error. It is used
* for estimation of the power budget instead of using
- * 'utilization' (which is 'busy_time / 'total_time').
- * The 'res_util' range is from 100 to (power_table[state] * 100)
- * for the corresponding 'state'.
+ * 'utilization' (which is 'busy_time' / 'total_time').
+ * The 'res_util' range is from 100 to power * 100 for the
+ * corresponding 'state'.
* @capped_state: index to cooling state with in dynamic power budget
* @req_max_freq: PM QoS request for limiting the maximum frequency
* of the devfreq device.
+ * @em_pd: Energy Model for the associated Devfreq device
*/
struct devfreq_cooling_device {
int id;
struct thermal_cooling_device *cdev;
struct devfreq *devfreq;
unsigned long cooling_state;
- u32 *power_table;
u32 *freq_table;
- size_t freq_table_size;
+ size_t max_state;
struct devfreq_cooling_power *power_ops;
u32 res_util;
int capped_state;
struct dev_pm_qos_request req_max_freq;
+ struct em_perf_domain *em_pd;
};
static int devfreq_cooling_get_max_state(struct thermal_cooling_device *cdev,
{
struct devfreq_cooling_device *dfc = cdev->devdata;
- *state = dfc->freq_table_size - 1;
+ *state = dfc->max_state;
return 0;
}
struct devfreq *df = dfc->devfreq;
struct device *dev = df->dev.parent;
unsigned long freq;
+ int perf_idx;
if (state == dfc->cooling_state)
return 0;
dev_dbg(dev, "Setting cooling state %lu\n", state);
- if (state >= dfc->freq_table_size)
+ if (state > dfc->max_state)
return -EINVAL;
- freq = dfc->freq_table[state];
+ if (dfc->em_pd) {
+ perf_idx = dfc->max_state - state;
+ freq = dfc->em_pd->table[perf_idx].frequency * 1000;
+ } else {
+ freq = dfc->freq_table[state];
+ }
dev_pm_qos_update_request(&dfc->req_max_freq,
DIV_ROUND_UP(freq, HZ_PER_KHZ));
}
/**
- * freq_get_state() - get the cooling state corresponding to a frequency
- * @dfc: Pointer to devfreq cooling device
- * @freq: frequency in Hz
+ * get_perf_idx() - get the performance index corresponding to a frequency
+ * @em_pd: Pointer to device's Energy Model
+ * @freq: frequency in kHz
*
- * Return: the cooling state associated with the @freq, or
- * THERMAL_CSTATE_INVALID if it wasn't found.
+ * Return: the performance index associated with the @freq, or
+ * -EINVAL if it wasn't found.
*/
-static unsigned long
-freq_get_state(struct devfreq_cooling_device *dfc, unsigned long freq)
+static int get_perf_idx(struct em_perf_domain *em_pd, unsigned long freq)
{
int i;
- for (i = 0; i < dfc->freq_table_size; i++) {
- if (dfc->freq_table[i] == freq)
+ for (i = 0; i < em_pd->nr_perf_states; i++) {
+ if (em_pd->table[i].frequency == freq)
return i;
}
- return THERMAL_CSTATE_INVALID;
+ return -EINVAL;
}
static unsigned long get_voltage(struct devfreq *df, unsigned long freq)
return voltage;
}
-/**
- * get_static_power() - calculate the static power
- * @dfc: Pointer to devfreq cooling device
- * @freq: Frequency in Hz
- *
- * Calculate the static power in milliwatts using the supplied
- * get_static_power(). The current voltage is calculated using the
- * OPP library. If no get_static_power() was supplied, assume the
- * static power is negligible.
- */
-static unsigned long
-get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq)
+static void _normalize_load(struct devfreq_dev_status *status)
{
- struct devfreq *df = dfc->devfreq;
- unsigned long voltage;
-
- if (!dfc->power_ops->get_static_power)
- return 0;
-
- voltage = get_voltage(df, freq);
-
- if (voltage == 0)
- return 0;
-
- return dfc->power_ops->get_static_power(df, voltage);
-}
-
-/**
- * get_dynamic_power - calculate the dynamic power
- * @dfc: Pointer to devfreq cooling device
- * @freq: Frequency in Hz
- * @voltage: Voltage in millivolts
- *
- * Calculate the dynamic power in milliwatts consumed by the device at
- * frequency @freq and voltage @voltage. If the get_dynamic_power()
- * was supplied as part of the devfreq_cooling_power struct, then that
- * function is used. Otherwise, a simple power model (Pdyn = Coeff *
- * Voltage^2 * Frequency) is used.
- */
-static unsigned long
-get_dynamic_power(struct devfreq_cooling_device *dfc, unsigned long freq,
- unsigned long voltage)
-{
- u64 power;
- u32 freq_mhz;
- struct devfreq_cooling_power *dfc_power = dfc->power_ops;
-
- if (dfc_power->get_dynamic_power)
- return dfc_power->get_dynamic_power(dfc->devfreq, freq,
- voltage);
-
- freq_mhz = freq / 1000000;
- power = (u64)dfc_power->dyn_power_coeff * freq_mhz * voltage * voltage;
- do_div(power, 1000000000);
-
- return power;
-}
+ if (status->total_time > 0xfffff) {
+ status->total_time >>= 10;
+ status->busy_time >>= 10;
+ }
+ status->busy_time <<= 10;
+ status->busy_time /= status->total_time ? : 1;
-static inline unsigned long get_total_power(struct devfreq_cooling_device *dfc,
- unsigned long freq,
- unsigned long voltage)
-{
- return get_static_power(dfc, freq) + get_dynamic_power(dfc, freq,
- voltage);
+ status->busy_time = status->busy_time ? : 1;
+ status->total_time = 1024;
}
-
static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cdev,
u32 *power)
{
struct devfreq_cooling_device *dfc = cdev->devdata;
struct devfreq *df = dfc->devfreq;
- struct devfreq_dev_status *status = &df->last_status;
+ struct devfreq_dev_status status;
unsigned long state;
- unsigned long freq = status->current_frequency;
+ unsigned long freq;
unsigned long voltage;
- u32 dyn_power = 0;
- u32 static_power = 0;
- int res;
-
- state = freq_get_state(dfc, freq);
- if (state == THERMAL_CSTATE_INVALID) {
- res = -EAGAIN;
- goto fail;
- }
+ int res, perf_idx;
+
+ mutex_lock(&df->lock);
+ status = df->last_status;
+ mutex_unlock(&df->lock);
- if (dfc->power_ops->get_real_power) {
+ freq = status.current_frequency;
+
+ if (dfc->power_ops && dfc->power_ops->get_real_power) {
voltage = get_voltage(df, freq);
if (voltage == 0) {
res = -EINVAL;
res = dfc->power_ops->get_real_power(df, power, freq, voltage);
if (!res) {
state = dfc->capped_state;
- dfc->res_util = dfc->power_table[state];
+ dfc->res_util = dfc->em_pd->table[state].power;
dfc->res_util *= SCALE_ERROR_MITIGATION;
if (*power > 1)
goto fail;
}
} else {
- dyn_power = dfc->power_table[state];
+ /* Energy Model frequencies are in kHz */
+ perf_idx = get_perf_idx(dfc->em_pd, freq / 1000);
+ if (perf_idx < 0) {
+ res = -EAGAIN;
+ goto fail;
+ }
- /* Scale dynamic power for utilization */
- dyn_power *= status->busy_time;
- dyn_power /= status->total_time;
- /* Get static power */
- static_power = get_static_power(dfc, freq);
+ _normalize_load(&status);
- *power = dyn_power + static_power;
+ /* Scale power for utilization */
+ *power = dfc->em_pd->table[perf_idx].power;
+ *power *= status.busy_time;
+ *power >>= 10;
}
- trace_thermal_power_devfreq_get_power(cdev, status, freq, dyn_power,
- static_power, *power);
+ trace_thermal_power_devfreq_get_power(cdev, &status, freq, *power);
return 0;
fail:
}
static int devfreq_cooling_state2power(struct thermal_cooling_device *cdev,
- unsigned long state,
- u32 *power)
+ unsigned long state, u32 *power)
{
struct devfreq_cooling_device *dfc = cdev->devdata;
- unsigned long freq;
- u32 static_power;
+ int perf_idx;
- if (state >= dfc->freq_table_size)
+ if (state > dfc->max_state)
return -EINVAL;
- freq = dfc->freq_table[state];
- static_power = get_static_power(dfc, freq);
+ perf_idx = dfc->max_state - state;
+ *power = dfc->em_pd->table[perf_idx].power;
- *power = dfc->power_table[state] + static_power;
return 0;
}
{
struct devfreq_cooling_device *dfc = cdev->devdata;
struct devfreq *df = dfc->devfreq;
- struct devfreq_dev_status *status = &df->last_status;
- unsigned long freq = status->current_frequency;
- unsigned long busy_time;
- s32 dyn_power;
- u32 static_power;
+ struct devfreq_dev_status status;
+ unsigned long freq;
s32 est_power;
int i;
- if (dfc->power_ops->get_real_power) {
+ mutex_lock(&df->lock);
+ status = df->last_status;
+ mutex_unlock(&df->lock);
+
+ freq = status.current_frequency;
+
+ if (dfc->power_ops && dfc->power_ops->get_real_power) {
/* Scale for resource utilization */
est_power = power * dfc->res_util;
est_power /= SCALE_ERROR_MITIGATION;
} else {
- static_power = get_static_power(dfc, freq);
-
- dyn_power = power - static_power;
- dyn_power = dyn_power > 0 ? dyn_power : 0;
-
/* Scale dynamic power for utilization */
- busy_time = status->busy_time ?: 1;
- est_power = (dyn_power * status->total_time) / busy_time;
+ _normalize_load(&status);
+ est_power = power << 10;
+ est_power /= status.busy_time;
}
/*
* Find the first cooling state that is within the power
- * budget for dynamic power.
+ * budget. The EM power table is sorted ascending.
*/
- for (i = 0; i < dfc->freq_table_size - 1; i++)
- if (est_power >= dfc->power_table[i])
+ for (i = dfc->max_state; i > 0; i--)
+ if (est_power >= dfc->em_pd->table[i].power)
break;
- *state = i;
- dfc->capped_state = i;
+ *state = dfc->max_state - i;
+ dfc->capped_state = *state;
+
trace_thermal_power_devfreq_limit(cdev, freq, *state, power);
return 0;
}
};
/**
- * devfreq_cooling_gen_tables() - Generate power and freq tables.
- * @dfc: Pointer to devfreq cooling device.
- *
- * Generate power and frequency tables: the power table hold the
- * device's maximum power usage at each cooling state (OPP). The
- * static and dynamic power using the appropriate voltage and
- * frequency for the state, is acquired from the struct
- * devfreq_cooling_power, and summed to make the maximum power draw.
+ * devfreq_cooling_gen_tables() - Generate frequency table.
+ * @dfc: Pointer to devfreq cooling device.
+ * @num_opps: Number of OPPs
*
- * The frequency table holds the frequencies in descending order.
- * That way its indexed by cooling device state.
- *
- * The tables are malloced, and pointers put in dfc. They must be
- * freed when unregistering the devfreq cooling device.
+ * Generate frequency table which holds the frequencies in descending
+ * order. That way its indexed by cooling device state. This is for
+ * compatibility with drivers which do not register Energy Model.
*
* Return: 0 on success, negative error code on failure.
*/
-static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc)
+static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc,
+ int num_opps)
{
struct devfreq *df = dfc->devfreq;
struct device *dev = df->dev.parent;
- int ret, num_opps;
unsigned long freq;
- u32 *power_table = NULL;
- u32 *freq_table;
int i;
- num_opps = dev_pm_opp_get_opp_count(dev);
-
- if (dfc->power_ops) {
- power_table = kcalloc(num_opps, sizeof(*power_table),
- GFP_KERNEL);
- if (!power_table)
- return -ENOMEM;
- }
-
- freq_table = kcalloc(num_opps, sizeof(*freq_table),
+ dfc->freq_table = kcalloc(num_opps, sizeof(*dfc->freq_table),
GFP_KERNEL);
- if (!freq_table) {
- ret = -ENOMEM;
- goto free_power_table;
- }
+ if (!dfc->freq_table)
+ return -ENOMEM;
for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) {
- unsigned long power, voltage;
struct dev_pm_opp *opp;
opp = dev_pm_opp_find_freq_floor(dev, &freq);
if (IS_ERR(opp)) {
- ret = PTR_ERR(opp);
- goto free_tables;
+ kfree(dfc->freq_table);
+ return PTR_ERR(opp);
}
- voltage = dev_pm_opp_get_voltage(opp) / 1000; /* mV */
dev_pm_opp_put(opp);
-
- if (dfc->power_ops) {
- if (dfc->power_ops->get_real_power)
- power = get_total_power(dfc, freq, voltage);
- else
- power = get_dynamic_power(dfc, freq, voltage);
-
- dev_dbg(dev, "Power table: %lu MHz @ %lu mV: %lu = %lu mW\n",
- freq / 1000000, voltage, power, power);
-
- power_table[i] = power;
- }
-
- freq_table[i] = freq;
+ dfc->freq_table[i] = freq;
}
- if (dfc->power_ops)
- dfc->power_table = power_table;
-
- dfc->freq_table = freq_table;
- dfc->freq_table_size = num_opps;
-
return 0;
-
-free_tables:
- kfree(freq_table);
-free_power_table:
- kfree(power_table);
-
- return ret;
}
/**
struct devfreq_cooling_power *dfc_power)
{
struct thermal_cooling_device *cdev;
+ struct device *dev = df->dev.parent;
struct devfreq_cooling_device *dfc;
char dev_name[THERMAL_NAME_LENGTH];
- int err;
+ int err, num_opps;
dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
if (!dfc)
dfc->devfreq = df;
- if (dfc_power) {
- dfc->power_ops = dfc_power;
-
+ dfc->em_pd = em_pd_get(dev);
+ if (dfc->em_pd) {
devfreq_cooling_ops.get_requested_power =
devfreq_cooling_get_requested_power;
devfreq_cooling_ops.state2power = devfreq_cooling_state2power;
devfreq_cooling_ops.power2state = devfreq_cooling_power2state;
+
+ dfc->power_ops = dfc_power;
+
+ num_opps = em_pd_nr_perf_states(dfc->em_pd);
+ } else {
+ /* Backward compatibility for drivers which do not use IPA */
+ dev_dbg(dev, "missing EM for cooling device\n");
+
+ num_opps = dev_pm_opp_get_opp_count(dev);
+
+ err = devfreq_cooling_gen_tables(dfc, num_opps);
+ if (err)
+ goto free_dfc;
}
- err = devfreq_cooling_gen_tables(dfc);
- if (err)
+ if (num_opps <= 0) {
+ err = -EINVAL;
goto free_dfc;
+ }
+
+ /* max_state is an index, not a counter */
+ dfc->max_state = num_opps - 1;
- err = dev_pm_qos_add_request(df->dev.parent, &dfc->req_max_freq,
+ err = dev_pm_qos_add_request(dev, &dfc->req_max_freq,
DEV_PM_QOS_MAX_FREQUENCY,
PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
if (err < 0)
- goto free_tables;
+ goto free_table;
err = ida_simple_get(&devfreq_ida, 0, 0, GFP_KERNEL);
if (err < 0)
goto remove_qos_req;
+
dfc->id = err;
snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", dfc->id);
&devfreq_cooling_ops);
if (IS_ERR(cdev)) {
err = PTR_ERR(cdev);
- dev_err(df->dev.parent,
+ dev_err(dev,
"Failed to register devfreq cooling device (%d)\n",
err);
goto release_ida;
release_ida:
ida_simple_remove(&devfreq_ida, dfc->id);
-
remove_qos_req:
dev_pm_qos_remove_request(&dfc->req_max_freq);
-
-free_tables:
- kfree(dfc->power_table);
+free_table:
kfree(dfc->freq_table);
free_dfc:
kfree(dfc);
EXPORT_SYMBOL_GPL(devfreq_cooling_register);
/**
+ * devfreq_cooling_em_register_power() - Register devfreq cooling device with
+ * power information and automatically register Energy Model (EM)
+ * @df: Pointer to devfreq device.
+ * @dfc_power: Pointer to devfreq_cooling_power.
+ *
+ * Register a devfreq cooling device and automatically register EM. The
+ * available OPPs must be registered for the device.
+ *
+ * If @dfc_power is provided, the cooling device is registered with the
+ * power extensions. It is using the simple Energy Model which requires
+ * "dynamic-power-coefficient" a devicetree property. To not break drivers
+ * which miss that DT property, the function won't bail out when the EM
+ * registration failed. The cooling device will be registered if everything
+ * else is OK.
+ */
+struct thermal_cooling_device *
+devfreq_cooling_em_register(struct devfreq *df,
+ struct devfreq_cooling_power *dfc_power)
+{
+ struct thermal_cooling_device *cdev;
+ struct device *dev;
+ int ret;
+
+ if (IS_ERR_OR_NULL(df))
+ return ERR_PTR(-EINVAL);
+
+ dev = df->dev.parent;
+
+ ret = dev_pm_opp_of_register_em(dev, NULL);
+ if (ret)
+ dev_dbg(dev, "Unable to register EM for devfreq cooling device (%d)\n",
+ ret);
+
+ cdev = of_devfreq_cooling_register_power(dev->of_node, df, dfc_power);
+
+ if (IS_ERR_OR_NULL(cdev))
+ em_dev_unregister_perf_domain(dev);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(devfreq_cooling_em_register);
+
+/**
* devfreq_cooling_unregister() - Unregister devfreq cooling device.
* @cdev: Pointer to devfreq cooling device to unregister.
+ *
+ * Unregisters devfreq cooling device and related Energy Model if it was
+ * present.
*/
void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
{
struct devfreq_cooling_device *dfc;
+ struct device *dev;
- if (!cdev)
+ if (IS_ERR_OR_NULL(cdev))
return;
dfc = cdev->devdata;
+ dev = dfc->devfreq->dev.parent;
thermal_cooling_device_unregister(dfc->cdev);
ida_simple_remove(&devfreq_ida, dfc->id);
dev_pm_qos_remove_request(&dfc->req_max_freq);
- kfree(dfc->power_table);
- kfree(dfc->freq_table);
+ em_dev_unregister_perf_domain(dev);
+
+ kfree(dfc->freq_table);
kfree(dfc);
}
EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);
* @trip_max_desired_temperature: last passive trip point of the thermal
* zone. The temperature we are
* controlling for.
+ * @sustainable_power: Sustainable power (heat) that this thermal zone can
+ * dissipate
*/
struct power_allocator_params {
bool allocated_tzp;
s32 prev_err;
int trip_switch_on;
int trip_max_desired_temperature;
+ u32 sustainable_power;
};
/**
if (instance->trip != params->trip_max_desired_temperature)
continue;
- if (power_actor_get_min_power(cdev, &min_power))
+ if (!cdev_is_power_actor(cdev))
+ continue;
+
+ if (cdev->ops->state2power(cdev, instance->upper, &min_power))
continue;
sustainable_power += min_power;
* @sustainable_power: sustainable power for the thermal zone
* @trip_switch_on: trip point number for the switch on temperature
* @control_temp: target temperature for the power allocator governor
- * @force: whether to force the update of the constants
*
* This function is used to update the estimation of the PID
* controller constants in struct thermal_zone_parameters.
- * Sustainable power is provided in case it was estimated. The
- * estimated sustainable_power should not be stored in the
- * thermal_zone_parameters so it has to be passed explicitly to this
- * function.
- *
- * If @force is not set, the values in the thermal zone's parameters
- * are preserved if they are not zero. If @force is set, the values
- * in thermal zone's parameters are overwritten.
*/
static void estimate_pid_constants(struct thermal_zone_device *tz,
u32 sustainable_power, int trip_switch_on,
- int control_temp, bool force)
+ int control_temp)
{
int ret;
int switch_on_temp;
u32 temperature_threshold;
+ s32 k_i;
ret = tz->ops->get_trip_temp(tz, trip_switch_on, &switch_on_temp);
if (ret)
if (!temperature_threshold)
return;
- if (!tz->tzp->k_po || force)
- tz->tzp->k_po = int_to_frac(sustainable_power) /
- temperature_threshold;
+ tz->tzp->k_po = int_to_frac(sustainable_power) /
+ temperature_threshold;
+
+ tz->tzp->k_pu = int_to_frac(2 * sustainable_power) /
+ temperature_threshold;
- if (!tz->tzp->k_pu || force)
- tz->tzp->k_pu = int_to_frac(2 * sustainable_power) /
- temperature_threshold;
+ k_i = tz->tzp->k_pu / 10;
+ tz->tzp->k_i = k_i > 0 ? k_i : 1;
- if (!tz->tzp->k_i || force)
- tz->tzp->k_i = int_to_frac(10) / 1000;
/*
* The default for k_d and integral_cutoff is 0, so we can
* leave them as they are.
}
/**
+ * get_sustainable_power() - Get the right sustainable power
+ * @tz: thermal zone for which to estimate the constants
+ * @params: parameters for the power allocator governor
+ * @control_temp: target temperature for the power allocator governor
+ *
+ * This function is used for getting the proper sustainable power value based
+ * on variables which might be updated by the user sysfs interface. If that
+ * happen the new value is going to be estimated and updated. It is also used
+ * after thermal zone binding, where the initial values where set to 0.
+ */
+static u32 get_sustainable_power(struct thermal_zone_device *tz,
+ struct power_allocator_params *params,
+ int control_temp)
+{
+ u32 sustainable_power;
+
+ if (!tz->tzp->sustainable_power)
+ sustainable_power = estimate_sustainable_power(tz);
+ else
+ sustainable_power = tz->tzp->sustainable_power;
+
+ /* Check if it's init value 0 or there was update via sysfs */
+ if (sustainable_power != params->sustainable_power) {
+ estimate_pid_constants(tz, sustainable_power,
+ params->trip_switch_on, control_temp);
+
+ /* Do the estimation only once and make available in sysfs */
+ tz->tzp->sustainable_power = sustainable_power;
+ params->sustainable_power = sustainable_power;
+ }
+
+ return sustainable_power;
+}
+
+/**
* pid_controller() - PID controller
* @tz: thermal zone we are operating in
* @control_temp: the target temperature in millicelsius
max_power_frac = int_to_frac(max_allocatable_power);
- if (tz->tzp->sustainable_power) {
- sustainable_power = tz->tzp->sustainable_power;
- } else {
- sustainable_power = estimate_sustainable_power(tz);
- estimate_pid_constants(tz, sustainable_power,
- params->trip_switch_on, control_temp,
- true);
- }
+ sustainable_power = get_sustainable_power(tz, params, control_temp);
err = control_temp - tz->temperature;
err = int_to_frac(err);
}
/**
+ * power_actor_set_power() - limit the maximum power a cooling device consumes
+ * @cdev: pointer to &thermal_cooling_device
+ * @instance: thermal instance to update
+ * @power: the power in milliwatts
+ *
+ * Set the cooling device to consume at most @power milliwatts. The limit is
+ * expected to be a cap at the maximum power consumption.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device does not
+ * implement the power actor API or -E* for other failures.
+ */
+static int
+power_actor_set_power(struct thermal_cooling_device *cdev,
+ struct thermal_instance *instance, u32 power)
+{
+ unsigned long state;
+ int ret;
+
+ ret = cdev->ops->power2state(cdev, power, &state);
+ if (ret)
+ return ret;
+
+ instance->target = clamp_val(state, instance->lower, instance->upper);
+ mutex_lock(&cdev->lock);
+ cdev->updated = false;
+ mutex_unlock(&cdev->lock);
+ thermal_cdev_update(cdev);
+
+ return 0;
+}
+
+/**
* divvy_up_power() - divvy the allocated power between the actors
* @req_power: each actor's requested power
* @max_power: each actor's maximum available power
weighted_req_power[i] = frac_to_int(weight * req_power[i]);
- if (power_actor_get_max_power(cdev, &max_power[i]))
+ if (cdev->ops->state2power(cdev, instance->lower,
+ &max_power[i]))
continue;
total_req_power += req_power[i];
if (!ret)
estimate_pid_constants(tz, tz->tzp->sustainable_power,
params->trip_switch_on,
- control_temp, false);
+ control_temp);
}
reset_pid_controller(params);
&tmu->sensors[i],
&tmu_tz_ops);
if (IS_ERR(tmu->sensors[i].tzd)) {
+ ret = PTR_ERR(tmu->sensors[i].tzd);
dev_err(&pdev->dev,
"failed to register thermal zone sensor[%d]: %d\n",
i, ret);
- return PTR_ERR(tmu->sensors[i].tzd);
+ goto disable_clk;
}
tmu->sensors[i].hw_id = i;
}
imx8mm_tmu_enable(tmu, true);
return 0;
+
+disable_clk:
+ clk_disable_unprepare(tmu->clk);
+ return ret;
}
static int imx8mm_tmu_remove(struct platform_device *pdev)
select ACPI_THERMAL_REL
select ACPI_FAN
select INTEL_SOC_DTS_IOSF_CORE
+ select PROC_THERMAL_MMIO_RAPL if X86_64 && POWERCAP
help
Newer laptops and tablets that use ACPI may have thermal sensors and
other devices with thermal control capabilities outside the core
power consumed by display device.
config PROC_THERMAL_MMIO_RAPL
- bool
- depends on 64BIT
- depends on POWERCAP
+ tristate
select INTEL_RAPL_CORE
- default y
endif
obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o
+obj-$(CONFIG_PROC_THERMAL_MMIO_RAPL) += processor_thermal_rapl.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_rfim.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_mbox.o
obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o
obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o
#include <linux/acpi.h>
#include <linux/thermal.h>
#include <linux/cpuhotplug.h>
-#include <linux/intel_rapl.h>
#include "int340x_thermal_zone.h"
+#include "processor_thermal_device.h"
#include "../intel_soc_dts_iosf.h"
-/* Broadwell-U/HSB thermal reporting device */
-#define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603
-#define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03
-
-/* Skylake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903
-
-/* CannonLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_CNL_THERMAL 0x5a03
-#define PCI_DEVICE_ID_PROC_CFL_THERMAL 0x3E83
-
-/* Braswell thermal reporting device */
-#define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC
-
-/* Broxton thermal reporting device */
-#define PCI_DEVICE_ID_PROC_BXT0_THERMAL 0x0A8C
-#define PCI_DEVICE_ID_PROC_BXT1_THERMAL 0x1A8C
-#define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C
-#define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C
-
-/* GeminiLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_GLK_THERMAL 0x318C
-
-/* IceLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_ICL_THERMAL 0x8a03
-
-/* JasperLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_JSL_THERMAL 0x4E03
-
-/* TigerLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_TGL_THERMAL 0x9A03
-
#define DRV_NAME "proc_thermal"
-struct power_config {
- u32 index;
- u32 min_uw;
- u32 max_uw;
- u32 tmin_us;
- u32 tmax_us;
- u32 step_uw;
-};
-
-struct proc_thermal_device {
- struct device *dev;
- struct acpi_device *adev;
- struct power_config power_limits[2];
- struct int34x_thermal_zone *int340x_zone;
- struct intel_soc_dts_sensors *soc_dts;
- void __iomem *mmio_base;
-};
-
enum proc_thermal_emum_mode_type {
PROC_THERMAL_NONE,
PROC_THERMAL_PCI,
PROC_THERMAL_PLATFORM_DEV
};
-struct rapl_mmio_regs {
- u64 reg_unit;
- u64 regs[RAPL_DOMAIN_MAX][RAPL_DOMAIN_REG_MAX];
- int limits[RAPL_DOMAIN_MAX];
-};
-
/*
* We can have only one type of enumeration, PCI or Platform,
* not both. So we don't need instance specific data.
return IRQ_HANDLED;
}
-#ifdef CONFIG_PROC_THERMAL_MMIO_RAPL
-
#define MCHBAR 0
-/* RAPL Support via MMIO interface */
-static struct rapl_if_priv rapl_mmio_priv;
-
-static int rapl_mmio_cpu_online(unsigned int cpu)
+static int proc_thermal_set_mmio_base(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv)
{
- struct rapl_package *rp;
-
- /* mmio rapl supports package 0 only for now */
- if (topology_physical_package_id(cpu))
- return 0;
+ int ret;
- rp = rapl_find_package_domain(cpu, &rapl_mmio_priv);
- if (!rp) {
- rp = rapl_add_package(cpu, &rapl_mmio_priv);
- if (IS_ERR(rp))
- return PTR_ERR(rp);
+ ret = pcim_iomap_regions(pdev, 1 << MCHBAR, DRV_NAME);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot reserve PCI memory region\n");
+ return -ENOMEM;
}
- cpumask_set_cpu(cpu, &rp->cpumask);
- return 0;
-}
-
-static int rapl_mmio_cpu_down_prep(unsigned int cpu)
-{
- struct rapl_package *rp;
- int lead_cpu;
-
- rp = rapl_find_package_domain(cpu, &rapl_mmio_priv);
- if (!rp)
- return 0;
-
- cpumask_clear_cpu(cpu, &rp->cpumask);
- lead_cpu = cpumask_first(&rp->cpumask);
- if (lead_cpu >= nr_cpu_ids)
- rapl_remove_package(rp);
- else if (rp->lead_cpu == cpu)
- rp->lead_cpu = lead_cpu;
- return 0;
-}
-
-static int rapl_mmio_read_raw(int cpu, struct reg_action *ra)
-{
- if (!ra->reg)
- return -EINVAL;
- ra->value = readq((void __iomem *)ra->reg);
- ra->value &= ra->mask;
- return 0;
-}
-
-static int rapl_mmio_write_raw(int cpu, struct reg_action *ra)
-{
- u64 val;
-
- if (!ra->reg)
- return -EINVAL;
+ proc_priv->mmio_base = pcim_iomap_table(pdev)[MCHBAR];
- val = readq((void __iomem *)ra->reg);
- val &= ~ra->mask;
- val |= ra->value;
- writeq(val, (void __iomem *)ra->reg);
return 0;
}
-static int proc_thermal_rapl_add(struct pci_dev *pdev,
+static int proc_thermal_mmio_add(struct pci_dev *pdev,
struct proc_thermal_device *proc_priv,
- struct rapl_mmio_regs *rapl_regs)
+ kernel_ulong_t feature_mask)
{
- enum rapl_domain_reg_id reg;
- enum rapl_domain_type domain;
int ret;
- if (!rapl_regs)
- return 0;
+ proc_priv->mmio_feature_mask = feature_mask;
- ret = pcim_iomap_regions(pdev, 1 << MCHBAR, DRV_NAME);
- if (ret) {
- dev_err(&pdev->dev, "cannot reserve PCI memory region\n");
- return -ENOMEM;
+ if (feature_mask) {
+ ret = proc_thermal_set_mmio_base(pdev, proc_priv);
+ if (ret)
+ return ret;
}
- proc_priv->mmio_base = pcim_iomap_table(pdev)[MCHBAR];
-
- for (domain = RAPL_DOMAIN_PACKAGE; domain < RAPL_DOMAIN_MAX; domain++) {
- for (reg = RAPL_DOMAIN_REG_LIMIT; reg < RAPL_DOMAIN_REG_MAX; reg++)
- if (rapl_regs->regs[domain][reg])
- rapl_mmio_priv.regs[domain][reg] =
- (u64)proc_priv->mmio_base +
- rapl_regs->regs[domain][reg];
- rapl_mmio_priv.limits[domain] = rapl_regs->limits[domain];
+ if (feature_mask & PROC_THERMAL_FEATURE_RAPL) {
+ ret = proc_thermal_rapl_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add RAPL MMIO interface\n");
+ return ret;
+ }
}
- rapl_mmio_priv.reg_unit = (u64)proc_priv->mmio_base + rapl_regs->reg_unit;
-
- rapl_mmio_priv.read_raw = rapl_mmio_read_raw;
- rapl_mmio_priv.write_raw = rapl_mmio_write_raw;
- rapl_mmio_priv.control_type = powercap_register_control_type(NULL, "intel-rapl-mmio", NULL);
- if (IS_ERR(rapl_mmio_priv.control_type)) {
- pr_debug("failed to register powercap control_type.\n");
- return PTR_ERR(rapl_mmio_priv.control_type);
+ if (feature_mask & PROC_THERMAL_FEATURE_FIVR ||
+ feature_mask & PROC_THERMAL_FEATURE_DVFS) {
+ ret = proc_thermal_rfim_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add RFIM interface\n");
+ goto err_rem_rapl;
+ }
}
- ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "powercap/rapl:online",
- rapl_mmio_cpu_online, rapl_mmio_cpu_down_prep);
- if (ret < 0) {
- powercap_unregister_control_type(rapl_mmio_priv.control_type);
- rapl_mmio_priv.control_type = NULL;
- return ret;
+ if (feature_mask & PROC_THERMAL_FEATURE_MBOX) {
+ ret = proc_thermal_mbox_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add MBOX interface\n");
+ goto err_rem_rfim;
+ }
}
- rapl_mmio_priv.pcap_rapl_online = ret;
return 0;
-}
-static void proc_thermal_rapl_remove(void)
-{
- if (IS_ERR_OR_NULL(rapl_mmio_priv.control_type))
- return;
+err_rem_rfim:
+ proc_thermal_rfim_remove(pdev);
+err_rem_rapl:
+ proc_thermal_rapl_remove();
- cpuhp_remove_state(rapl_mmio_priv.pcap_rapl_online);
- powercap_unregister_control_type(rapl_mmio_priv.control_type);
+ return ret;
}
-static const struct rapl_mmio_regs rapl_mmio_hsw = {
- .reg_unit = 0x5938,
- .regs[RAPL_DOMAIN_PACKAGE] = { 0x59a0, 0x593c, 0x58f0, 0, 0x5930},
- .regs[RAPL_DOMAIN_DRAM] = { 0x58e0, 0x58e8, 0x58ec, 0, 0},
- .limits[RAPL_DOMAIN_PACKAGE] = 2,
- .limits[RAPL_DOMAIN_DRAM] = 2,
-};
+static void proc_thermal_mmio_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
-#else
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_RAPL)
+ proc_thermal_rapl_remove();
-static int proc_thermal_rapl_add(struct pci_dev *pdev,
- struct proc_thermal_device *proc_priv,
- struct rapl_mmio_regs *rapl_regs)
-{
- return 0;
-}
-static void proc_thermal_rapl_remove(void) {}
-static const struct rapl_mmio_regs rapl_mmio_hsw;
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR ||
+ proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS)
+ proc_thermal_rfim_remove(pdev);
-#endif /* CONFIG_MMIO_RAPL */
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_MBOX)
+ proc_thermal_mbox_remove(pdev);
+}
static int proc_thermal_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
if (ret)
return ret;
- ret = proc_thermal_rapl_add(pdev, proc_priv,
- (struct rapl_mmio_regs *)id->driver_data);
- if (ret) {
- dev_err(&pdev->dev, "failed to add RAPL MMIO interface\n");
- proc_thermal_remove(proc_priv);
- return ret;
- }
-
pci_set_drvdata(pdev, proc_priv);
proc_thermal_emum_mode = PROC_THERMAL_PCI;
- if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) {
+ if (pdev->device == PCI_DEVICE_ID_INTEL_BSW_THERMAL) {
/*
* Enumerate additional DTS sensors available via IOSF.
* But we are not treating as a failure condition, if
return ret;
ret = sysfs_create_group(&pdev->dev.kobj, &power_limit_attribute_group);
- if (ret)
+ if (ret) {
sysfs_remove_file(&pdev->dev.kobj, &dev_attr_tcc_offset_degree_celsius.attr);
+ return ret;
+ }
- return ret;
+ ret = proc_thermal_mmio_add(pdev, proc_priv, id->driver_data);
+ if (ret) {
+ proc_thermal_remove(proc_priv);
+ return ret;
+ }
+
+ return 0;
}
static void proc_thermal_pci_remove(struct pci_dev *pdev)
pci_disable_msi(pdev);
}
}
- proc_thermal_rapl_remove();
+
+ proc_thermal_mmio_remove(pdev);
proc_thermal_remove(proc_priv);
}
static SIMPLE_DEV_PM_OPS(proc_thermal_pm, NULL, proc_thermal_resume);
static const struct pci_device_id proc_thermal_pci_ids[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL),
- .driver_data = (kernel_ulong_t)&rapl_mmio_hsw, },
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT0_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT1_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTX_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTP_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CNL_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CFL_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_GLK_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_ICL_THERMAL),
- .driver_data = (kernel_ulong_t)&rapl_mmio_hsw, },
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_JSL_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_TGL_THERMAL),
- .driver_data = (kernel_ulong_t)&rapl_mmio_hsw, },
- { 0, },
+ { PCI_DEVICE_DATA(INTEL, ADL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_MBOX) },
+ { PCI_DEVICE_DATA(INTEL, BDW_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BSW_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXT0_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXT1_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXTX_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXTP_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, CNL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, CFL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, GLK_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, HSB_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, ICL_THERMAL, PROC_THERMAL_FEATURE_RAPL) },
+ { PCI_DEVICE_DATA(INTEL, JSL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, SKL_THERMAL, PROC_THERMAL_FEATURE_RAPL) },
+ { PCI_DEVICE_DATA(INTEL, TGL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_MBOX) },
+ { },
};
MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * processor_thermal_device.h
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __PROCESSOR_THERMAL_DEVICE_H__
+#define __PROCESSOR_THERMAL_DEVICE_H__
+
+#include <linux/intel_rapl.h>
+
+#define PCI_DEVICE_ID_INTEL_ADL_THERMAL 0x461d
+#define PCI_DEVICE_ID_INTEL_BDW_THERMAL 0x1603
+#define PCI_DEVICE_ID_INTEL_BSW_THERMAL 0x22DC
+
+#define PCI_DEVICE_ID_INTEL_BXT0_THERMAL 0x0A8C
+#define PCI_DEVICE_ID_INTEL_BXT1_THERMAL 0x1A8C
+#define PCI_DEVICE_ID_INTEL_BXTX_THERMAL 0x4A8C
+#define PCI_DEVICE_ID_INTEL_BXTP_THERMAL 0x5A8C
+
+#define PCI_DEVICE_ID_INTEL_CNL_THERMAL 0x5a03
+#define PCI_DEVICE_ID_INTEL_CFL_THERMAL 0x3E83
+#define PCI_DEVICE_ID_INTEL_GLK_THERMAL 0x318C
+#define PCI_DEVICE_ID_INTEL_HSB_THERMAL 0x0A03
+#define PCI_DEVICE_ID_INTEL_ICL_THERMAL 0x8a03
+#define PCI_DEVICE_ID_INTEL_JSL_THERMAL 0x4E03
+#define PCI_DEVICE_ID_INTEL_SKL_THERMAL 0x1903
+#define PCI_DEVICE_ID_INTEL_TGL_THERMAL 0x9A03
+
+struct power_config {
+ u32 index;
+ u32 min_uw;
+ u32 max_uw;
+ u32 tmin_us;
+ u32 tmax_us;
+ u32 step_uw;
+};
+
+struct proc_thermal_device {
+ struct device *dev;
+ struct acpi_device *adev;
+ struct power_config power_limits[2];
+ struct int34x_thermal_zone *int340x_zone;
+ struct intel_soc_dts_sensors *soc_dts;
+ u32 mmio_feature_mask;
+ void __iomem *mmio_base;
+};
+
+struct rapl_mmio_regs {
+ u64 reg_unit;
+ u64 regs[RAPL_DOMAIN_MAX][RAPL_DOMAIN_REG_MAX];
+ int limits[RAPL_DOMAIN_MAX];
+};
+
+#define PROC_THERMAL_FEATURE_NONE 0x00
+#define PROC_THERMAL_FEATURE_RAPL 0x01
+#define PROC_THERMAL_FEATURE_FIVR 0x02
+#define PROC_THERMAL_FEATURE_DVFS 0x04
+#define PROC_THERMAL_FEATURE_MBOX 0x08
+
+#if IS_ENABLED(CONFIG_PROC_THERMAL_MMIO_RAPL)
+int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_rapl_remove(void);
+#else
+static int __maybe_unused proc_thermal_rapl_add(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv)
+{
+ return 0;
+}
+
+static void __maybe_unused proc_thermal_rapl_remove(void)
+{
+}
+#endif
+
+int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_rfim_remove(struct pci_dev *pdev);
+
+int proc_thermal_mbox_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_mbox_remove(struct pci_dev *pdev);
+
+#endif
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device mailbox driver for Workload type hints
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+#define MBOX_CMD_WORKLOAD_TYPE_READ 0x0E
+#define MBOX_CMD_WORKLOAD_TYPE_WRITE 0x0F
+
+#define MBOX_OFFSET_DATA 0x5810
+#define MBOX_OFFSET_INTERFACE 0x5818
+
+#define MBOX_BUSY_BIT 31
+#define MBOX_RETRY_COUNT 100
+
+#define MBOX_DATA_BIT_VALID 31
+#define MBOX_DATA_BIT_AC_DC 30
+
+static DEFINE_MUTEX(mbox_lock);
+
+static int send_mbox_cmd(struct pci_dev *pdev, u8 cmd_id, u32 cmd_data, u8 *cmd_resp)
+{
+ struct proc_thermal_device *proc_priv;
+ u32 retries, data;
+ int ret;
+
+ mutex_lock(&mbox_lock);
+ proc_priv = pci_get_drvdata(pdev);
+
+ /* Poll for rb bit == 0 */
+ retries = MBOX_RETRY_COUNT;
+ do {
+ data = readl((void __iomem *) (proc_priv->mmio_base + MBOX_OFFSET_INTERFACE));
+ if (data & BIT_ULL(MBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ continue;
+ }
+ ret = 0;
+ break;
+ } while (--retries);
+
+ if (ret)
+ goto unlock_mbox;
+
+ if (cmd_id == MBOX_CMD_WORKLOAD_TYPE_WRITE)
+ writel(cmd_data, (void __iomem *) ((proc_priv->mmio_base + MBOX_OFFSET_DATA)));
+
+ /* Write command register */
+ data = BIT_ULL(MBOX_BUSY_BIT) | cmd_id;
+ writel(data, (void __iomem *) ((proc_priv->mmio_base + MBOX_OFFSET_INTERFACE)));
+
+ /* Poll for rb bit == 0 */
+ retries = MBOX_RETRY_COUNT;
+ do {
+ data = readl((void __iomem *) (proc_priv->mmio_base + MBOX_OFFSET_INTERFACE));
+ if (data & BIT_ULL(MBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ continue;
+ }
+
+ if (data) {
+ ret = -ENXIO;
+ goto unlock_mbox;
+ }
+
+ if (cmd_id == MBOX_CMD_WORKLOAD_TYPE_READ) {
+ data = readl((void __iomem *) (proc_priv->mmio_base + MBOX_OFFSET_DATA));
+ *cmd_resp = data & 0xff;
+ }
+
+ ret = 0;
+ break;
+ } while (--retries);
+
+unlock_mbox:
+ mutex_unlock(&mbox_lock);
+ return ret;
+}
+
+/* List of workload types */
+static const char * const workload_types[] = {
+ "none",
+ "idle",
+ "semi_active",
+ "bursty",
+ "sustained",
+ "battery_life",
+ NULL
+};
+
+
+static ssize_t workload_available_types_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i = 0;
+ int ret = 0;
+
+ while (workload_types[i] != NULL)
+ ret += sprintf(&buf[ret], "%s ", workload_types[i++]);
+
+ ret += sprintf(&buf[ret], "\n");
+
+ return ret;
+}
+
+static DEVICE_ATTR_RO(workload_available_types);
+
+static ssize_t workload_type_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ char str_preference[15];
+ u32 data = 0;
+ ssize_t ret;
+
+ ret = sscanf(buf, "%14s", str_preference);
+ if (ret != 1)
+ return -EINVAL;
+
+ ret = match_string(workload_types, -1, str_preference);
+ if (ret < 0)
+ return ret;
+
+ ret &= 0xff;
+
+ if (ret)
+ data = BIT(MBOX_DATA_BIT_VALID) | BIT(MBOX_DATA_BIT_AC_DC);
+
+ data |= ret;
+
+ ret = send_mbox_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_WRITE, data, NULL);
+ if (ret)
+ return false;
+
+ return count;
+}
+
+static ssize_t workload_type_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u8 cmd_resp;
+ int ret;
+
+ ret = send_mbox_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, 0, &cmd_resp);
+ if (ret)
+ return false;
+
+ cmd_resp &= 0xff;
+
+ if (cmd_resp > ARRAY_SIZE(workload_types) - 1)
+ return -EINVAL;
+
+ return sprintf(buf, "%s\n", workload_types[cmd_resp]);
+}
+
+static DEVICE_ATTR_RW(workload_type);
+
+static struct attribute *workload_req_attrs[] = {
+ &dev_attr_workload_available_types.attr,
+ &dev_attr_workload_type.attr,
+ NULL
+};
+
+static const struct attribute_group workload_req_attribute_group = {
+ .attrs = workload_req_attrs,
+ .name = "workload_request"
+};
+
+
+
+static bool workload_req_created;
+
+int proc_thermal_mbox_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ u8 cmd_resp;
+ int ret;
+
+ /* Check if there is a mailbox support, if fails return success */
+ ret = send_mbox_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, 0, &cmd_resp);
+ if (ret)
+ return 0;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &workload_req_attribute_group);
+ if (ret)
+ return ret;
+
+ workload_req_created = true;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_mbox_add);
+
+void proc_thermal_mbox_remove(struct pci_dev *pdev)
+{
+ if (workload_req_created)
+ sysfs_remove_group(&pdev->dev.kobj, &workload_req_attribute_group);
+
+ workload_req_created = false;
+
+}
+EXPORT_SYMBOL_GPL(proc_thermal_mbox_remove);
+
+MODULE_LICENSE("GPL v2");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device RFIM control
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+static struct rapl_if_priv rapl_mmio_priv;
+
+static const struct rapl_mmio_regs rapl_mmio_default = {
+ .reg_unit = 0x5938,
+ .regs[RAPL_DOMAIN_PACKAGE] = { 0x59a0, 0x593c, 0x58f0, 0, 0x5930},
+ .regs[RAPL_DOMAIN_DRAM] = { 0x58e0, 0x58e8, 0x58ec, 0, 0},
+ .limits[RAPL_DOMAIN_PACKAGE] = 2,
+ .limits[RAPL_DOMAIN_DRAM] = 2,
+};
+
+static int rapl_mmio_cpu_online(unsigned int cpu)
+{
+ struct rapl_package *rp;
+
+ /* mmio rapl supports package 0 only for now */
+ if (topology_physical_package_id(cpu))
+ return 0;
+
+ rp = rapl_find_package_domain(cpu, &rapl_mmio_priv);
+ if (!rp) {
+ rp = rapl_add_package(cpu, &rapl_mmio_priv);
+ if (IS_ERR(rp))
+ return PTR_ERR(rp);
+ }
+ cpumask_set_cpu(cpu, &rp->cpumask);
+ return 0;
+}
+
+static int rapl_mmio_cpu_down_prep(unsigned int cpu)
+{
+ struct rapl_package *rp;
+ int lead_cpu;
+
+ rp = rapl_find_package_domain(cpu, &rapl_mmio_priv);
+ if (!rp)
+ return 0;
+
+ cpumask_clear_cpu(cpu, &rp->cpumask);
+ lead_cpu = cpumask_first(&rp->cpumask);
+ if (lead_cpu >= nr_cpu_ids)
+ rapl_remove_package(rp);
+ else if (rp->lead_cpu == cpu)
+ rp->lead_cpu = lead_cpu;
+ return 0;
+}
+
+static int rapl_mmio_read_raw(int cpu, struct reg_action *ra)
+{
+ if (!ra->reg)
+ return -EINVAL;
+
+ ra->value = readq((void __iomem *)ra->reg);
+ ra->value &= ra->mask;
+ return 0;
+}
+
+static int rapl_mmio_write_raw(int cpu, struct reg_action *ra)
+{
+ u64 val;
+
+ if (!ra->reg)
+ return -EINVAL;
+
+ val = readq((void __iomem *)ra->reg);
+ val &= ~ra->mask;
+ val |= ra->value;
+ writeq(val, (void __iomem *)ra->reg);
+ return 0;
+}
+
+int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ const struct rapl_mmio_regs *rapl_regs = &rapl_mmio_default;
+ enum rapl_domain_reg_id reg;
+ enum rapl_domain_type domain;
+ int ret;
+
+ if (!rapl_regs)
+ return 0;
+
+ for (domain = RAPL_DOMAIN_PACKAGE; domain < RAPL_DOMAIN_MAX; domain++) {
+ for (reg = RAPL_DOMAIN_REG_LIMIT; reg < RAPL_DOMAIN_REG_MAX; reg++)
+ if (rapl_regs->regs[domain][reg])
+ rapl_mmio_priv.regs[domain][reg] =
+ (u64)proc_priv->mmio_base +
+ rapl_regs->regs[domain][reg];
+ rapl_mmio_priv.limits[domain] = rapl_regs->limits[domain];
+ }
+ rapl_mmio_priv.reg_unit = (u64)proc_priv->mmio_base + rapl_regs->reg_unit;
+
+ rapl_mmio_priv.read_raw = rapl_mmio_read_raw;
+ rapl_mmio_priv.write_raw = rapl_mmio_write_raw;
+
+ rapl_mmio_priv.control_type = powercap_register_control_type(NULL, "intel-rapl-mmio", NULL);
+ if (IS_ERR(rapl_mmio_priv.control_type)) {
+ pr_debug("failed to register powercap control_type.\n");
+ return PTR_ERR(rapl_mmio_priv.control_type);
+ }
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "powercap/rapl:online",
+ rapl_mmio_cpu_online, rapl_mmio_cpu_down_prep);
+ if (ret < 0) {
+ powercap_unregister_control_type(rapl_mmio_priv.control_type);
+ rapl_mmio_priv.control_type = NULL;
+ return ret;
+ }
+ rapl_mmio_priv.pcap_rapl_online = ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rapl_add);
+
+void proc_thermal_rapl_remove(void)
+{
+ if (IS_ERR_OR_NULL(rapl_mmio_priv.control_type))
+ return;
+
+ cpuhp_remove_state(rapl_mmio_priv.pcap_rapl_online);
+ powercap_unregister_control_type(rapl_mmio_priv.control_type);
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rapl_remove);
+
+MODULE_LICENSE("GPL v2");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device RFIM control
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+struct mmio_reg {
+ int read_only;
+ u32 offset;
+ int bits;
+ u16 mask;
+ u16 shift;
+};
+
+/* These will represent sysfs attribute names */
+static const char * const fivr_strings[] = {
+ "vco_ref_code_lo",
+ "vco_ref_code_hi",
+ "spread_spectrum_pct",
+ "spread_spectrum_clk_enable",
+ "rfi_vco_ref_code",
+ "fivr_fffc_rev",
+ NULL
+};
+
+static const struct mmio_reg tgl_fivr_mmio_regs[] = {
+ { 0, 0x5A18, 3, 0x7, 12}, /* vco_ref_code_lo */
+ { 0, 0x5A18, 8, 0xFF, 16}, /* vco_ref_code_hi */
+ { 0, 0x5A08, 8, 0xFF, 0}, /* spread_spectrum_pct */
+ { 0, 0x5A08, 1, 0x1, 8}, /* spread_spectrum_clk_enable */
+ { 1, 0x5A10, 12, 0xFFF, 0}, /* rfi_vco_ref_code */
+ { 1, 0x5A14, 2, 0x3, 1}, /* fivr_fffc_rev */
+};
+
+/* These will represent sysfs attribute names */
+static const char * const dvfs_strings[] = {
+ "rfi_restriction_run_busy",
+ "rfi_restriction_err_code",
+ "rfi_restriction_data_rate",
+ "rfi_restriction_data_rate_base",
+ "ddr_data_rate_point_0",
+ "ddr_data_rate_point_1",
+ "ddr_data_rate_point_2",
+ "ddr_data_rate_point_3",
+ "rfi_disable",
+ NULL
+};
+
+static const struct mmio_reg adl_dvfs_mmio_regs[] = {
+ { 0, 0x5A38, 1, 0x1, 31}, /* rfi_restriction_run_busy */
+ { 0, 0x5A38, 7, 0x7F, 24}, /* rfi_restriction_err_code */
+ { 0, 0x5A38, 8, 0xFF, 16}, /* rfi_restriction_data_rate */
+ { 0, 0x5A38, 16, 0xFFFF, 0}, /* rfi_restriction_data_rate_base */
+ { 0, 0x5A30, 10, 0x3FF, 0}, /* ddr_data_rate_point_0 */
+ { 0, 0x5A30, 10, 0x3FF, 10}, /* ddr_data_rate_point_1 */
+ { 0, 0x5A30, 10, 0x3FF, 20}, /* ddr_data_rate_point_2 */
+ { 0, 0x5A30, 10, 0x3FF, 30}, /* ddr_data_rate_point_3 */
+ { 0, 0x5A40, 1, 0x1, 0}, /* rfi_disable */
+};
+
+#define RFIM_SHOW(suffix, table)\
+static ssize_t suffix##_show(struct device *dev,\
+ struct device_attribute *attr,\
+ char *buf)\
+{\
+ struct proc_thermal_device *proc_priv;\
+ struct pci_dev *pdev = to_pci_dev(dev);\
+ const struct mmio_reg *mmio_regs;\
+ const char **match_strs;\
+ u32 reg_val;\
+ int ret;\
+\
+ proc_priv = pci_get_drvdata(pdev);\
+ if (table) {\
+ match_strs = (const char **)dvfs_strings;\
+ mmio_regs = adl_dvfs_mmio_regs;\
+ } else { \
+ match_strs = (const char **)fivr_strings;\
+ mmio_regs = tgl_fivr_mmio_regs;\
+ } \
+ \
+ ret = match_string(match_strs, -1, attr->attr.name);\
+ if (ret < 0)\
+ return ret;\
+ reg_val = readl((void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
+ ret = (reg_val >> mmio_regs[ret].shift) & mmio_regs[ret].mask;\
+ return sprintf(buf, "%u\n", ret);\
+}
+
+#define RFIM_STORE(suffix, table)\
+static ssize_t suffix##_store(struct device *dev,\
+ struct device_attribute *attr,\
+ const char *buf, size_t count)\
+{\
+ struct proc_thermal_device *proc_priv;\
+ struct pci_dev *pdev = to_pci_dev(dev);\
+ unsigned int input;\
+ const char **match_strs;\
+ const struct mmio_reg *mmio_regs;\
+ int ret, err;\
+ u32 reg_val;\
+ u32 mask;\
+\
+ proc_priv = pci_get_drvdata(pdev);\
+ if (table) {\
+ match_strs = (const char **)dvfs_strings;\
+ mmio_regs = adl_dvfs_mmio_regs;\
+ } else { \
+ match_strs = (const char **)fivr_strings;\
+ mmio_regs = tgl_fivr_mmio_regs;\
+ } \
+ \
+ ret = match_string(match_strs, -1, attr->attr.name);\
+ if (ret < 0)\
+ return ret;\
+ if (mmio_regs[ret].read_only)\
+ return -EPERM;\
+ err = kstrtouint(buf, 10, &input);\
+ if (err)\
+ return err;\
+ mask = GENMASK(mmio_regs[ret].shift + mmio_regs[ret].bits - 1, mmio_regs[ret].shift);\
+ reg_val = readl((void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
+ reg_val &= ~mask;\
+ reg_val |= (input << mmio_regs[ret].shift);\
+ writel(reg_val, (void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
+ return count;\
+}
+
+RFIM_SHOW(vco_ref_code_lo, 0)
+RFIM_SHOW(vco_ref_code_hi, 0)
+RFIM_SHOW(spread_spectrum_pct, 0)
+RFIM_SHOW(spread_spectrum_clk_enable, 0)
+RFIM_SHOW(rfi_vco_ref_code, 0)
+RFIM_SHOW(fivr_fffc_rev, 0)
+
+RFIM_STORE(vco_ref_code_lo, 0)
+RFIM_STORE(vco_ref_code_hi, 0)
+RFIM_STORE(spread_spectrum_pct, 0)
+RFIM_STORE(spread_spectrum_clk_enable, 0)
+RFIM_STORE(rfi_vco_ref_code, 0)
+RFIM_STORE(fivr_fffc_rev, 0)
+
+static DEVICE_ATTR_RW(vco_ref_code_lo);
+static DEVICE_ATTR_RW(vco_ref_code_hi);
+static DEVICE_ATTR_RW(spread_spectrum_pct);
+static DEVICE_ATTR_RW(spread_spectrum_clk_enable);
+static DEVICE_ATTR_RW(rfi_vco_ref_code);
+static DEVICE_ATTR_RW(fivr_fffc_rev);
+
+static struct attribute *fivr_attrs[] = {
+ &dev_attr_vco_ref_code_lo.attr,
+ &dev_attr_vco_ref_code_hi.attr,
+ &dev_attr_spread_spectrum_pct.attr,
+ &dev_attr_spread_spectrum_clk_enable.attr,
+ &dev_attr_rfi_vco_ref_code.attr,
+ &dev_attr_fivr_fffc_rev.attr,
+ NULL
+};
+
+static const struct attribute_group fivr_attribute_group = {
+ .attrs = fivr_attrs,
+ .name = "fivr"
+};
+
+RFIM_SHOW(rfi_restriction_run_busy, 1)
+RFIM_SHOW(rfi_restriction_err_code, 1)
+RFIM_SHOW(rfi_restriction_data_rate, 1)
+RFIM_SHOW(ddr_data_rate_point_0, 1)
+RFIM_SHOW(ddr_data_rate_point_1, 1)
+RFIM_SHOW(ddr_data_rate_point_2, 1)
+RFIM_SHOW(ddr_data_rate_point_3, 1)
+RFIM_SHOW(rfi_disable, 1)
+
+RFIM_STORE(rfi_restriction_run_busy, 1)
+RFIM_STORE(rfi_restriction_err_code, 1)
+RFIM_STORE(rfi_restriction_data_rate, 1)
+RFIM_STORE(rfi_disable, 1)
+
+static DEVICE_ATTR_RW(rfi_restriction_run_busy);
+static DEVICE_ATTR_RW(rfi_restriction_err_code);
+static DEVICE_ATTR_RW(rfi_restriction_data_rate);
+static DEVICE_ATTR_RO(ddr_data_rate_point_0);
+static DEVICE_ATTR_RO(ddr_data_rate_point_1);
+static DEVICE_ATTR_RO(ddr_data_rate_point_2);
+static DEVICE_ATTR_RO(ddr_data_rate_point_3);
+static DEVICE_ATTR_RW(rfi_disable);
+
+static struct attribute *dvfs_attrs[] = {
+ &dev_attr_rfi_restriction_run_busy.attr,
+ &dev_attr_rfi_restriction_err_code.attr,
+ &dev_attr_rfi_restriction_data_rate.attr,
+ &dev_attr_ddr_data_rate_point_0.attr,
+ &dev_attr_ddr_data_rate_point_1.attr,
+ &dev_attr_ddr_data_rate_point_2.attr,
+ &dev_attr_ddr_data_rate_point_3.attr,
+ &dev_attr_rfi_disable.attr,
+ NULL
+};
+
+static const struct attribute_group dvfs_attribute_group = {
+ .attrs = dvfs_attrs,
+ .name = "dvfs"
+};
+
+int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ int ret;
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) {
+ ret = sysfs_create_group(&pdev->dev.kobj, &fivr_attribute_group);
+ if (ret)
+ return ret;
+ }
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS) {
+ ret = sysfs_create_group(&pdev->dev.kobj, &dvfs_attribute_group);
+ if (ret && proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) {
+ sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rfim_add);
+
+void proc_thermal_rfim_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR)
+ sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS)
+ sysfs_remove_group(&pdev->dev.kobj, &dvfs_attribute_group);
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rfim_remove);
+
+MODULE_LICENSE("GPL v2");
* Tushar Dave <tushar.n.dave@intel.com>
*/
+#include <linux/acpi.h>
+#include <linux/delay.h>
#include <linux/module.h>
-#include <linux/types.h>
#include <linux/init.h>
#include <linux/pci.h>
-#include <linux/acpi.h>
+#include <linux/pm.h>
+#include <linux/suspend.h>
#include <linux/thermal.h>
+#include <linux/types.h>
#include <linux/units.h>
-#include <linux/pm.h>
/* Intel PCH thermal Device IDs */
#define PCH_THERMAL_DID_HSW_1 0x9C24 /* Haswell PCH */
#define PCH_THERMAL_DID_CNL_H 0xA379 /* CNL-H PCH */
#define PCH_THERMAL_DID_CNL_LP 0x02F9 /* CNL-LP PCH */
#define PCH_THERMAL_DID_CML_H 0X06F9 /* CML-H PCH */
+#define PCH_THERMAL_DID_LWB 0xA1B1 /* Lewisburg PCH */
/* Wildcat Point-LP PCH Thermal registers */
#define WPT_TEMP 0x0000 /* Temperature */
#define WPT_TSREL 0x0A /* Thermal Sensor Report Enable and Lock */
#define WPT_TSMIC 0x0C /* Thermal Sensor SMI Control */
#define WPT_CTT 0x0010 /* Catastrophic Trip Point */
+#define WPT_TSPM 0x001C /* Thermal Sensor Power Management */
#define WPT_TAHV 0x0014 /* Thermal Alert High Value */
#define WPT_TALV 0x0018 /* Thermal Alert Low Value */
#define WPT_TL 0x00000040 /* Throttle Value */
#define WPT_TL_T1L 0x1ff00000 /* T1 Level */
#define WPT_TL_TTEN 0x20000000 /* TT Enable */
+/* Resolution of 1/2 degree C and an offset of -50C */
+#define PCH_TEMP_OFFSET (-50)
+#define GET_WPT_TEMP(x) ((x) * MILLIDEGREE_PER_DEGREE / 2 + WPT_TEMP_OFFSET)
+#define WPT_TEMP_OFFSET (PCH_TEMP_OFFSET * MILLIDEGREE_PER_DEGREE)
+#define GET_PCH_TEMP(x) (((x) / 2) + PCH_TEMP_OFFSET)
+
+/* Amount of time for each cooling delay, 100ms by default for now */
+static unsigned int delay_timeout = 100;
+module_param(delay_timeout, int, 0644);
+MODULE_PARM_DESC(delay_timeout, "amount of time delay for each iteration.");
+
+/* Number of iterations for cooling delay, 10 counts by default for now */
+static unsigned int delay_cnt = 10;
+module_param(delay_cnt, int, 0644);
+MODULE_PARM_DESC(delay_cnt, "total number of iterations for time delay.");
+
static char driver_name[] = "Intel PCH thermal driver";
struct pch_thermal_device {
trip_temp = readw(ptd->hw_base + WPT_CTT);
trip_temp &= 0x1FF;
if (trip_temp) {
- /* Resolution of 1/2 degree C and an offset of -50C */
- ptd->crt_temp = trip_temp * 1000 / 2 - 50000;
+ ptd->crt_temp = GET_WPT_TEMP(trip_temp);
ptd->crt_trip_id = 0;
++(*nr_trips);
}
trip_temp = readw(ptd->hw_base + WPT_PHL);
trip_temp &= 0x1FF;
if (trip_temp) {
- /* Resolution of 1/2 degree C and an offset of -50C */
- ptd->hot_temp = trip_temp * 1000 / 2 - 50000;
+ ptd->hot_temp = GET_WPT_TEMP(trip_temp);
ptd->hot_trip_id = *nr_trips;
++(*nr_trips);
}
static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
{
- u16 wpt_temp;
-
- wpt_temp = WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP);
-
- /* Resolution of 1/2 degree C and an offset of -50C */
- *temp = (wpt_temp * 1000 / 2 - 50000);
+ *temp = GET_WPT_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
return 0;
}
static int pch_wpt_suspend(struct pch_thermal_device *ptd)
{
u8 tsel;
+ u8 pch_delay_cnt = 1;
+ u16 pch_thr_temp, pch_cur_temp;
- if (ptd->bios_enabled)
+ /* Shutdown the thermal sensor if it is not enabled by BIOS */
+ if (!ptd->bios_enabled) {
+ tsel = readb(ptd->hw_base + WPT_TSEL);
+ writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL);
return 0;
+ }
- tsel = readb(ptd->hw_base + WPT_TSEL);
+ /* Do not check temperature if it is not a S0ix capable platform */
+#ifdef CONFIG_ACPI
+ if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0))
+ return 0;
+#else
+ return 0;
+#endif
+
+ /* Do not check temperature if it is not s2idle */
+ if (pm_suspend_via_firmware())
+ return 0;
+
+ /* Get the PCH temperature threshold value */
+ pch_thr_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TSPM));
+
+ /* Get the PCH current temperature value */
+ pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
+
+ /*
+ * If current PCH temperature is higher than configured PCH threshold
+ * value, run some delay loop with sleep to let the current temperature
+ * go down below the threshold value which helps to allow system enter
+ * lower power S0ix suspend state. Even after delay loop if PCH current
+ * temperature stays above threshold, notify the warning message
+ * which helps to indentify the reason why S0ix entry was rejected.
+ */
+ while (pch_delay_cnt <= delay_cnt) {
+ if (pch_cur_temp <= pch_thr_temp)
+ break;
+
+ dev_warn(&ptd->pdev->dev,
+ "CPU-PCH current temp [%dC] higher than the threshold temp [%dC], sleep %d times for %d ms duration\n",
+ pch_cur_temp, pch_thr_temp, pch_delay_cnt, delay_timeout);
+ msleep(delay_timeout);
+ /* Read the PCH current temperature for next cycle. */
+ pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
+ pch_delay_cnt++;
+ }
- writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL);
+ if (pch_cur_temp > pch_thr_temp)
+ dev_warn(&ptd->pdev->dev,
+ "CPU-PCH is hot [%dC] even after delay, continue to suspend. S0ix might fail\n",
+ pch_cur_temp);
+ else
+ dev_info(&ptd->pdev->dev,
+ "CPU-PCH is cool [%dC], continue to suspend\n", pch_cur_temp);
return 0;
}
board_skl,
board_cnl,
board_cml,
+ board_lwb,
};
static const struct board_info {
[board_cml] = {
.name = "pch_cometlake",
.ops = &pch_dev_ops_wpt,
- }
+ },
+ [board_lwb] = {
+ .name = "pch_lewisburg",
+ .ops = &pch_dev_ops_wpt,
+ },
};
static int intel_pch_thermal_probe(struct pci_dev *pdev,
.driver_data = board_cnl, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CML_H),
.driver_data = board_cml, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_LWB),
+ .driver_data = board_lwb, },
{ 0, },
};
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
return -EINVAL;
}
- ret = device_reset(&pdev->dev);
+ ret = device_reset_optional(&pdev->dev);
if (ret)
return ret;
#define MCELSIUS(temp) ((temp) * 1000)
#define GEN3_FUSE_MASK 0xFFF
-#define TSC_MAX_NUM 3
+#define TSC_MAX_NUM 4
/* default THCODE values if FUSEs are missing */
static const int thcodes[TSC_MAX_NUM][3] = {
{ 3397, 2800, 2221 },
{ 3393, 2795, 2216 },
{ 3389, 2805, 2237 },
+ { 3415, 2694, 2195 },
};
/* Structure for thermal temperature calculation */
return 0;
}
-static int rcar_gen3_thermal_mcelsius_to_temp(struct rcar_gen3_thermal_tsc *tsc,
- int mcelsius)
-{
- int celsius, val;
-
- celsius = DIV_ROUND_CLOSEST(mcelsius, 1000);
- if (celsius <= INT_FIXPT(tsc->tj_t))
- val = celsius * tsc->coef.a1 + tsc->coef.b1;
- else
- val = celsius * tsc->coef.a2 + tsc->coef.b2;
-
- return INT_FIXPT(val);
-}
-
-static int rcar_gen3_thermal_update_range(struct rcar_gen3_thermal_tsc *tsc)
-{
- int temperature, low, high;
-
- rcar_gen3_thermal_get_temp(tsc, &temperature);
-
- low = temperature - MCELSIUS(1);
- high = temperature + MCELSIUS(1);
-
- rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1,
- rcar_gen3_thermal_mcelsius_to_temp(tsc, low));
-
- rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2,
- rcar_gen3_thermal_mcelsius_to_temp(tsc, high));
-
- return 0;
-}
-
static const struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = {
.get_temp = rcar_gen3_thermal_get_temp,
};
-static void rcar_thermal_irq_set(struct rcar_gen3_thermal_priv *priv, bool on)
-{
- unsigned int i;
- u32 val = on ? IRQ_TEMPD1 | IRQ_TEMP2 : 0;
-
- for (i = 0; i < priv->num_tscs; i++)
- rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQMSK, val);
-}
-
-static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data)
-{
- struct rcar_gen3_thermal_priv *priv = data;
- u32 status;
- int i;
-
- for (i = 0; i < priv->num_tscs; i++) {
- status = rcar_gen3_thermal_read(priv->tscs[i], REG_GEN3_IRQSTR);
- rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQSTR, 0);
- if (status) {
- rcar_gen3_thermal_update_range(priv->tscs[i]);
- thermal_zone_device_update(priv->tscs[i]->zone,
- THERMAL_EVENT_UNSPECIFIED);
- }
- }
-
- return IRQ_HANDLED;
-}
-
static const struct soc_device_attribute r8a7795es1[] = {
{ .soc_id = "r8a7795", .revision = "ES1.*" },
{ /* sentinel */ }
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F);
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
- rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2);
rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR,
CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN);
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0);
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
- rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2);
reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR);
reg_val |= THCTR_THSST;
.compatible = "renesas,r8a77980-thermal",
.data = &rcar_gen3_ths_tj_1,
},
+ {
+ .compatible = "renesas,r8a779a0-thermal",
+ .data = &rcar_gen3_ths_tj_1,
+ },
{},
};
MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids);
static int rcar_gen3_thermal_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
-
- rcar_thermal_irq_set(priv, false);
pm_runtime_put(dev);
pm_runtime_disable(dev);
const int *rcar_gen3_ths_tj_1 = of_device_get_match_data(dev);
struct resource *res;
struct thermal_zone_device *zone;
- int ret, irq, i;
- char *irqname;
+ int ret, i;
/* default values if FUSEs are missing */
/* TODO: Read values from hardware on supported platforms */
platform_set_drvdata(pdev, priv);
- /*
- * Request 2 (of the 3 possible) IRQs, the driver only needs to
- * to trigger on the low and high trip points of the current
- * temp window at this point.
- */
- for (i = 0; i < 2; i++) {
- irq = platform_get_irq(pdev, i);
- if (irq < 0)
- return irq;
-
- irqname = devm_kasprintf(dev, GFP_KERNEL, "%s:ch%d",
- dev_name(dev), i);
- if (!irqname)
- return -ENOMEM;
-
- ret = devm_request_threaded_irq(dev, irq, NULL,
- rcar_gen3_thermal_irq,
- IRQF_ONESHOT, irqname, priv);
- if (ret)
- return ret;
- }
-
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
if (ret < 0)
goto error_unregister;
- rcar_gen3_thermal_update_range(tsc);
-
dev_info(dev, "TSC%d: Loaded %d trip points\n", i, ret);
}
goto error_unregister;
}
- rcar_thermal_irq_set(priv, true);
-
return 0;
error_unregister:
return ret;
}
-static int __maybe_unused rcar_gen3_thermal_suspend(struct device *dev)
-{
- struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
-
- rcar_thermal_irq_set(priv, false);
-
- return 0;
-}
-
static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
{
struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
priv->thermal_init(tsc);
- rcar_gen3_thermal_update_range(tsc);
}
- rcar_thermal_irq_set(priv, true);
-
return 0;
}
-static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, rcar_gen3_thermal_suspend,
+static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, NULL,
rcar_gen3_thermal_resume);
static struct platform_driver rcar_gen3_thermal_driver = {
return 0;
}
-static int rcar_thermal_notify(struct thermal_zone_device *zone,
- int trip, enum thermal_trip_type type)
-{
- struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
- struct device *dev = rcar_priv_to_dev(priv);
-
- switch (type) {
- case THERMAL_TRIP_CRITICAL:
- /* FIXME */
- dev_warn(dev, "Thermal reached to critical temperature\n");
- break;
- default:
- break;
- }
-
- return 0;
-}
-
static const struct thermal_zone_of_device_ops rcar_thermal_zone_of_ops = {
.get_temp = rcar_thermal_of_get_temp,
};
.get_temp = rcar_thermal_get_temp,
.get_trip_type = rcar_thermal_get_trip_type,
.get_trip_temp = rcar_thermal_get_trip_temp,
- .notify = rcar_thermal_notify,
};
/*
{
struct rcar_thermal_common *common = data;
struct rcar_thermal_priv *priv;
- unsigned long flags;
u32 status, mask;
- spin_lock_irqsave(&common->lock, flags);
+ spin_lock(&common->lock);
mask = rcar_thermal_common_read(common, INTMSK);
status = rcar_thermal_common_read(common, STR);
rcar_thermal_common_write(common, STR, 0x000F0F0F & mask);
- spin_unlock_irqrestore(&common->lock, flags);
+ spin_unlock(&common->lock);
status = status & ~mask;
* Based on the work of Josef Gajdusek <atx@atx.name>
*/
+#include <linux/bitmap.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/interrupt.h>
int (*calibrate)(struct ths_device *tmdev,
u16 *caldata, int callen);
int (*init)(struct ths_device *tmdev);
- int (*irq_ack)(struct ths_device *tmdev);
+ unsigned long (*irq_ack)(struct ths_device *tmdev);
int (*calc_temp)(struct ths_device *tmdev,
int id, int reg);
};
.max_register = 0xfc,
};
-static int sun8i_h3_irq_ack(struct ths_device *tmdev)
+static unsigned long sun8i_h3_irq_ack(struct ths_device *tmdev)
{
- int i, state, ret = 0;
+ unsigned long irq_bitmap = 0;
+ int i, state;
regmap_read(tmdev->regmap, SUN8I_THS_IS, &state);
if (state & SUN8I_THS_DATA_IRQ_STS(i)) {
regmap_write(tmdev->regmap, SUN8I_THS_IS,
SUN8I_THS_DATA_IRQ_STS(i));
- ret |= BIT(i);
+ bitmap_set(&irq_bitmap, i, 1);
}
}
- return ret;
+ return irq_bitmap;
}
-static int sun50i_h6_irq_ack(struct ths_device *tmdev)
+static unsigned long sun50i_h6_irq_ack(struct ths_device *tmdev)
{
- int i, state, ret = 0;
+ unsigned long irq_bitmap = 0;
+ int i, state;
regmap_read(tmdev->regmap, SUN50I_H6_THS_DIS, &state);
if (state & SUN50I_H6_THS_DATA_IRQ_STS(i)) {
regmap_write(tmdev->regmap, SUN50I_H6_THS_DIS,
SUN50I_H6_THS_DATA_IRQ_STS(i));
- ret |= BIT(i);
+ bitmap_set(&irq_bitmap, i, 1);
}
}
- return ret;
+ return irq_bitmap;
}
static irqreturn_t sun8i_irq_thread(int irq, void *data)
{
struct ths_device *tmdev = data;
- int i, state;
-
- state = tmdev->chip->irq_ack(tmdev);
+ unsigned long irq_bitmap = tmdev->chip->irq_ack(tmdev);
+ int i;
- for (i = 0; i < tmdev->chip->sensor_num; i++) {
- if (state & BIT(i))
- thermal_zone_device_update(tmdev->sensor[i].tzd,
- THERMAL_EVENT_UNSPECIFIED);
+ for_each_set_bit(i, &irq_bitmap, tmdev->chip->sensor_num) {
+ thermal_zone_device_update(tmdev->sensor[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
}
return IRQ_HANDLED;
msecs_to_jiffies(poweroff_delay_ms));
}
+void thermal_zone_device_critical(struct thermal_zone_device *tz)
+{
+ dev_emerg(&tz->device, "%s: critical temperature reached, "
+ "shutting down\n", tz->type);
+
+ mutex_lock(&poweroff_lock);
+ if (!power_off_triggered) {
+ /*
+ * Queue a backup emergency shutdown in the event of
+ * orderly_poweroff failure
+ */
+ thermal_emergency_poweroff();
+ orderly_poweroff(true);
+ power_off_triggered = true;
+ }
+ mutex_unlock(&poweroff_lock);
+}
+EXPORT_SYMBOL(thermal_zone_device_critical);
+
static void handle_critical_trips(struct thermal_zone_device *tz,
int trip, enum thermal_trip_type trip_type)
{
if (tz->ops->notify)
tz->ops->notify(tz, trip, trip_type);
- if (trip_type == THERMAL_TRIP_CRITICAL) {
- dev_emerg(&tz->device,
- "critical temperature reached (%d C), shutting down\n",
- tz->temperature / 1000);
- mutex_lock(&poweroff_lock);
- if (!power_off_triggered) {
- /*
- * Queue a backup emergency shutdown in the event of
- * orderly_poweroff failure
- */
- thermal_emergency_poweroff();
- orderly_poweroff(true);
- power_off_triggered = true;
- }
- mutex_unlock(&poweroff_lock);
- }
+ if (trip_type == THERMAL_TRIP_HOT && tz->ops->hot)
+ tz->ops->hot(tz);
+ else if (trip_type == THERMAL_TRIP_CRITICAL)
+ tz->ops->critical(tz);
}
static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
if (atomic_read(&in_suspend))
return;
- if (!tz->ops->get_temp)
+ if (WARN_ONCE(!tz->ops->get_temp, "'%s' must not be called without "
+ "'get_temp' ops set\n", __func__))
return;
update_temperature(tz);
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
}
-/*
- * Power actor section: interface to power actors to estimate power
- *
- * Set of functions used to interact to cooling devices that know
- * how to estimate their devices power consumption.
- */
-
-/**
- * power_actor_get_max_power() - get the maximum power that a cdev can consume
- * @cdev: pointer to &thermal_cooling_device
- * @max_power: pointer in which to store the maximum power
- *
- * Calculate the maximum power consumption in milliwats that the
- * cooling device can currently consume and store it in @max_power.
- *
- * Return: 0 on success, -EINVAL if @cdev doesn't support the
- * power_actor API or -E* on other error.
- */
-int power_actor_get_max_power(struct thermal_cooling_device *cdev,
- u32 *max_power)
-{
- if (!cdev_is_power_actor(cdev))
- return -EINVAL;
-
- return cdev->ops->state2power(cdev, 0, max_power);
-}
-
-/**
- * power_actor_get_min_power() - get the mainimum power that a cdev can consume
- * @cdev: pointer to &thermal_cooling_device
- * @min_power: pointer in which to store the minimum power
- *
- * Calculate the minimum power consumption in milliwatts that the
- * cooling device can currently consume and store it in @min_power.
- *
- * Return: 0 on success, -EINVAL if @cdev doesn't support the
- * power_actor API or -E* on other error.
- */
-int power_actor_get_min_power(struct thermal_cooling_device *cdev,
- u32 *min_power)
-{
- unsigned long max_state;
- int ret;
-
- if (!cdev_is_power_actor(cdev))
- return -EINVAL;
-
- ret = cdev->ops->get_max_state(cdev, &max_state);
- if (ret)
- return ret;
-
- return cdev->ops->state2power(cdev, max_state, min_power);
-}
-
-/**
- * power_actor_set_power() - limit the maximum power a cooling device consumes
- * @cdev: pointer to &thermal_cooling_device
- * @instance: thermal instance to update
- * @power: the power in milliwatts
- *
- * Set the cooling device to consume at most @power milliwatts. The limit is
- * expected to be a cap at the maximum power consumption.
- *
- * Return: 0 on success, -EINVAL if the cooling device does not
- * implement the power actor API or -E* for other failures.
- */
-int power_actor_set_power(struct thermal_cooling_device *cdev,
- struct thermal_instance *instance, u32 power)
-{
- unsigned long state;
- int ret;
-
- if (!cdev_is_power_actor(cdev))
- return -EINVAL;
-
- ret = cdev->ops->power2state(cdev, power, &state);
- if (ret)
- return ret;
-
- instance->target = state;
- mutex_lock(&cdev->lock);
- cdev->updated = false;
- mutex_unlock(&cdev->lock);
- thermal_cdev_update(cdev);
-
- return 0;
-}
-
void thermal_zone_device_rebind_exception(struct thermal_zone_device *tz,
const char *cdev_type, size_t size)
{
tz->id = id;
strlcpy(tz->type, type, sizeof(tz->type));
+
+ if (!ops->critical)
+ ops->critical = thermal_zone_device_critical;
+
tz->ops = ops;
tz->tzp = tzp;
tz->device.class = &thermal_class;
goto release_device;
for (count = 0; count < trips; count++) {
- if (tz->ops->get_trip_type(tz, count, &trip_type))
- set_bit(count, &tz->trips_disabled);
- if (tz->ops->get_trip_temp(tz, count, &trip_temp))
- set_bit(count, &tz->trips_disabled);
- /* Check for bogus trip points */
- if (trip_temp == 0)
+ if (tz->ops->get_trip_type(tz, count, &trip_type) ||
+ tz->ops->get_trip_temp(tz, count, &trip_temp) ||
+ !trip_temp)
set_bit(count, &tz->trips_disabled);
}
cdev->ops->power2state;
}
-int power_actor_get_max_power(struct thermal_cooling_device *cdev,
- u32 *max_power);
-int power_actor_get_min_power(struct thermal_cooling_device *cdev,
- u32 *min_power);
-int power_actor_set_power(struct thermal_cooling_device *cdev,
- struct thermal_instance *ti, u32 power);
/**
* struct thermal_trip - representation of a point in temperature domain
* @np: pointer to struct device_node that this trip point was created from
if (new_hwmon_device)
hwmon_device_unregister(hwmon->device);
free_mem:
- if (new_hwmon_device)
- kfree(hwmon);
+ kfree(hwmon);
return result;
}
NULL,
};
-static struct attribute_group thermal_zone_attribute_group = {
+static const struct attribute_group thermal_zone_attribute_group = {
.attrs = thermal_zone_dev_attrs,
};
NULL,
};
-static struct attribute_group thermal_zone_mode_attribute_group = {
+static const struct attribute_group thermal_zone_mode_attribute_group = {
.attrs = thermal_zone_mode_attrs,
};
return 0;
}
-static struct attribute_group thermal_zone_passive_attribute_group = {
+static const struct attribute_group thermal_zone_passive_attribute_group = {
.attrs = thermal_zone_passive_attrs,
.is_visible = thermal_zone_passive_is_visible,
};
/**
* struct devfreq_cooling_power - Devfreq cooling power ops
- * @get_static_power: Take voltage, in mV, and return the static power
- * in mW. If NULL, the static power is assumed
- * to be 0.
- * @get_dynamic_power: Take voltage, in mV, and frequency, in HZ, and
- * return the dynamic power draw in mW. If NULL,
- * a simple power model is used.
- * @dyn_power_coeff: Coefficient for the simple dynamic power model in
- * mW/(MHz mV mV).
- * If get_dynamic_power() is NULL, then the
- * dynamic power is calculated as
- * @dyn_power_coeff * frequency * voltage^2
* @get_real_power: When this is set, the framework uses it to ask the
* device driver for the actual power.
* Some devices have more sophisticated methods
* max total (static + dynamic) power value for each OPP.
*/
struct devfreq_cooling_power {
- unsigned long (*get_static_power)(struct devfreq *devfreq,
- unsigned long voltage);
- unsigned long (*get_dynamic_power)(struct devfreq *devfreq,
- unsigned long freq,
- unsigned long voltage);
int (*get_real_power)(struct devfreq *df, u32 *power,
unsigned long freq, unsigned long voltage);
- unsigned long dyn_power_coeff;
};
#ifdef CONFIG_DEVFREQ_THERMAL
of_devfreq_cooling_register(struct device_node *np, struct devfreq *df);
struct thermal_cooling_device *devfreq_cooling_register(struct devfreq *df);
void devfreq_cooling_unregister(struct thermal_cooling_device *dfc);
+struct thermal_cooling_device *
+devfreq_cooling_em_register(struct devfreq *df,
+ struct devfreq_cooling_power *dfc_power);
#else /* !CONFIG_DEVFREQ_THERMAL */
return ERR_PTR(-EINVAL);
}
+static inline struct thermal_cooling_device *
+devfreq_cooling_em_register(struct devfreq *df,
+ struct devfreq_cooling_power *dfc_power)
+{
+ return ERR_PTR(-EINVAL);
+}
+
static inline void
devfreq_cooling_unregister(struct thermal_cooling_device *dfc)
{
enum thermal_trend *);
int (*notify) (struct thermal_zone_device *, int,
enum thermal_trip_type);
+ void (*hot)(struct thermal_zone_device *);
+ void (*critical)(struct thermal_zone_device *);
};
struct thermal_cooling_device_ops {
void thermal_notify_framework(struct thermal_zone_device *, int);
int thermal_zone_device_enable(struct thermal_zone_device *tz);
int thermal_zone_device_disable(struct thermal_zone_device *tz);
+void thermal_zone_device_critical(struct thermal_zone_device *tz);
#else
static inline struct thermal_zone_device *thermal_zone_device_register(
const char *type, int trips, int mask, void *devdata,
TRACE_EVENT(thermal_power_devfreq_get_power,
TP_PROTO(struct thermal_cooling_device *cdev,
struct devfreq_dev_status *status, unsigned long freq,
- u32 dynamic_power, u32 static_power, u32 power),
+ u32 power),
- TP_ARGS(cdev, status, freq, dynamic_power, static_power, power),
+ TP_ARGS(cdev, status, freq, power),
TP_STRUCT__entry(
__string(type, cdev->type )
__field(unsigned long, freq )
- __field(u32, load )
- __field(u32, dynamic_power )
- __field(u32, static_power )
+ __field(u32, busy_time)
+ __field(u32, total_time)
__field(u32, power)
),
TP_fast_assign(
__assign_str(type, cdev->type);
__entry->freq = freq;
- __entry->load = (100 * status->busy_time) / status->total_time;
- __entry->dynamic_power = dynamic_power;
- __entry->static_power = static_power;
+ __entry->busy_time = status->busy_time;
+ __entry->total_time = status->total_time;
__entry->power = power;
),
- TP_printk("type=%s freq=%lu load=%u dynamic_power=%u static_power=%u power=%u",
+ TP_printk("type=%s freq=%lu load=%u power=%u",
__get_str(type), __entry->freq,
- __entry->load, __entry->dynamic_power, __entry->static_power,
+ __entry->total_time == 0 ? 0 :
+ (100 * __entry->busy_time) / __entry->total_time,
__entry->power)
);