Thermal control updates for 5.19-rc1

- Add thermal library and thermal tools to encapsulate the netlink
    into event based callbacks (Daniel Lezcano, Jiapeng Chong).
 
  - Improve overheat condition handling during suspend-to-idle in the
    Intel PCH thermal driver (Zhang Rui).
 
  - Use local ops instead of global ops in devfreq_cooling (Kant Fan).
 
  - Clean up _OSC handling in int340x (Davidlohr Bueso).
 
  - Switch hisi_termal from CONFIG_PM_SLEEP guards to pm_sleep_ptr()
    (Hesham Almatary).
 
  - Add new k3 j72xx bangdap driver and the corresponding bindings
    (Keerthy).
 
  - Fix missing of_node_put() in the SC iMX driver at probe time
    (Miaoqian Lin).
 
  - Fix memory leak in __thermal_cooling_device_register() when
    device_register() fails by calling thermal_cooling_device_destroy_sysfs()
    (Yang Yingliang).
 
  - Add sc8180x and sc8280xp compatible string in the DT bindings and
    lMH support for QCom tsens driver (Bjorn Andersson).
 
  - Fix OTP Calibration Register values conforming to the documentation
    on RZ/G2L and bindings documentation for RZ/G2UL (Biju Das).
 
  - Fix type in kerneldoc description for __thermal_bind_params (Corentin
    Labbe).
 
  - Fix potential NULL dereference in sr_thermal_probe() on Broadcom
    platform (Zheng Yongjun).
 
  - Add change mode ops to the thermal-of sensor (Manaf Meethalavalappu
    Pallikunhi).
 
  - Fix non-negative value support by preventing the value to be clamp
    to zero (Stefan Wahren).
 
  - Add compatible string and DT bindings for MSM8960 tsens driver
    (Dmitry Baryshkov).
 
  - Add hwmon support for K3 driver (Massimiliano Minella).
 
  - Refactor and add multiple generations support for QCom ADC driver
    (Jishnu Prakash).
 
  - Use platform_get_irq_optional() to get the interrupt on RCar driver and
    document Document RZ/V2L bindings (Lad Prabhakar).
 
  - Remove NULL check after container_of() call from the Intel HFI
    thermal driver (Haowen Bai).
 -----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCAAwFiEE4fcc61cGeeHD/fCwgsRv/nhiVHEFAmKL3MESHHJqd0Byand5
 c29ja2kubmV0AAoJEILEb/54YlRxgzsQAKj4D0ROHLdqReYeIutS7IU6YQ+ejh3N
 krcAZvxQd8B3sVwSHHABPdsHwwfYjP4BwoHHVtha5q+5zFGBq5F3fJHsDeRxN23T
 NEOW81HBaLu81N5gNuAYg+NQcdqBR4U4IGKZfxWRyx13OMECXGtJRb/SIW/TuDYk
 AQIrqOivVEsVRtn+qp+x0rKHsj6ge+2lU1OyJagr2BDhgLrwzxl6cmNBfali1C3D
 sw4SqGwGVjEB0QDDpMbty9BsLEjNRE86kwoPh2u0K9dT9/b/P9pk1lp+pOnltspZ
 eOdwr0CtoEHw2x5tuhbO7fmvNIuf5jgSDWsCrP/xYnsozcRiwWsgyeJj4f5soreN
 kTJB8S/FH+fd7UuYqdmDIeJpDnkkGt1jIqjkDvfJNL7ffBWIe/meXKF4vBnrdlbd
 FMNQwzgc5eug07IFjOE43V1v5Hw1H1leVUwZczOMGNIhqyy0WZyQr7vzwbr16jCO
 x0hu3q3Y++5SUAUy9WzsOOrpRHa9JxUwVhZuLG7ajf+6pqPxB8kqs9CJe+sgWKju
 T4KqHbljJ2Gnkm9SngT0BdnB+AgparhXf8/8Mj0ZBQvamXw+ylNbYfG7qTZk5Ne/
 rUf4YDew6hGDqikyJBe2e0a1lLSoN4zaADC1lLYhC+Zp7m6eK0r3y3mhic0bZe1c
 RU/2eeA4kTuZ
 =3ltV
 -----END PGP SIGNATURE-----

Merge tag 'thermal-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm

Pull thermal control updates from Rafael Wysocki:
 "These add a thermal library and thermal tools to wrap the netlink
  interface into event-based callbacks, improve overheat condition
  handling during suspend-to-idle on Intel SoCs, add some new hardware
  support, fix bugs and clean up code.

  Specifics:

   - Add thermal library and thermal tools to encapsulate the netlink
     into event based callbacks (Daniel Lezcano, Jiapeng Chong).

   - Improve overheat condition handling during suspend-to-idle in the
     Intel PCH thermal driver (Zhang Rui).

   - Use local ops instead of global ops in devfreq_cooling (Kant Fan).

   - Clean up _OSC handling in int340x (Davidlohr Bueso).

   - Switch hisi_termal from CONFIG_PM_SLEEP guards to pm_sleep_ptr()
     (Hesham Almatary).

   - Add new k3 j72xx bangdap driver and the corresponding bindings
     (Keerthy).

   - Fix missing of_node_put() in the SC iMX driver at probe time
     (Miaoqian Lin).

   - Fix memory leak in __thermal_cooling_device_register()
     when device_register() fails by calling
     thermal_cooling_device_destroy_sysfs() (Yang Yingliang).

   - Add sc8180x and sc8280xp compatible string in the DT bindings and
     lMH support for QCom tsens driver (Bjorn Andersson).

   - Fix OTP Calibration Register values conforming to the documentation
     on RZ/G2L and bindings documentation for RZ/G2UL (Biju Das).

   - Fix type in kerneldoc description for __thermal_bind_params
     (Corentin Labbe).

   - Fix potential NULL dereference in sr_thermal_probe() on Broadcom
     platform (Zheng Yongjun).

   - Add change mode ops to the thermal-of sensor (Manaf Meethalavalappu
     Pallikunhi).

   - Fix non-negative value support by preventing the value to be clamp
     to zero (Stefan Wahren).

   - Add compatible string and DT bindings for MSM8960 tsens driver
     (Dmitry Baryshkov).

   - Add hwmon support for K3 driver (Massimiliano Minella).

   - Refactor and add multiple generations support for QCom ADC driver
     (Jishnu Prakash).

   - Use platform_get_irq_optional() to get the interrupt on RCar driver
     and document Document RZ/V2L bindings (Lad Prabhakar).

   - Remove NULL check after container_of() call from the Intel HFI
     thermal driver (Haowen Bai)"

* tag 'thermal-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (38 commits)
  thermal: intel: pch: improve the cooling delay log
  thermal: intel: pch: enhance overheat handling
  thermal: intel: pch: move cooling delay to suspend_noirq phase
  PM: wakeup: expose pm_wakeup_pending to modules
  thermal: k3_j72xx_bandgap: Add the bandgap driver support
  dt-bindings: thermal: k3-j72xx: Add VTM bindings documentation
  thermal/drivers/imx_sc_thermal: Fix refcount leak in imx_sc_thermal_probe
  thermal/core: Fix memory leak in __thermal_cooling_device_register()
  dt-bindings: thermal: tsens: Add sc8280xp compatible
  dt-bindings: thermal: lmh: Add Qualcomm sc8180x compatible
  thermal/drivers/qcom/lmh: Add sc8180x compatible
  thermal/drivers/rz2gl: Fix OTP Calibration Register values
  dt-bindings: thermal: rzg2l-thermal: Document RZ/G2UL bindings
  thermal: thermal_of: fix typo on __thermal_bind_params
  tools/thermal: remove unneeded semicolon
  tools/lib/thermal: remove unneeded semicolon
  thermal/drivers/broadcom: Fix potential NULL dereference in sr_thermal_probe
  tools/thermal: Add thermal daemon skeleton
  tools/thermal: Add a temperature capture tool
  tools/thermal: Add util library
  ...
This commit is contained in:
Linus Torvalds 2022-05-24 16:19:30 -07:00
commit f4fb859665
59 changed files with 4231 additions and 102 deletions

View File

@ -18,6 +18,7 @@ description:
properties:
compatible:
enum:
- qcom,sc8180x-lmh
- qcom,sdm845-lmh
- qcom,sm8150-lmh

View File

@ -10,7 +10,9 @@ maintainers:
properties:
compatible:
const: qcom,spmi-adc-tm5
enum:
- qcom,spmi-adc-tm5
- qcom,spmi-adc-tm5-gen2
reg:
maxItems: 1
@ -33,6 +35,7 @@ properties:
qcom,avg-samples:
$ref: /schemas/types.yaml#/definitions/uint32
description: Number of samples to be used for measurement.
Not applicable for Gen2 ADC_TM peripheral.
enum:
- 1
- 2
@ -45,6 +48,7 @@ properties:
$ref: /schemas/types.yaml#/definitions/uint32
description: This parameter is used to decrease ADC sampling rate.
Quicker measurements can be made by reducing decimation ratio.
Not applicable for Gen2 ADC_TM peripheral.
enum:
- 250
- 420
@ -93,6 +97,29 @@ patternProperties:
- const: 1
- enum: [ 1, 3, 4, 6, 20, 8, 10 ]
qcom,avg-samples:
$ref: /schemas/types.yaml#/definitions/uint32
description: Number of samples to be used for measurement.
This property in child node is applicable only for Gen2 ADC_TM peripheral.
enum:
- 1
- 2
- 4
- 8
- 16
default: 1
qcom,decimation:
$ref: /schemas/types.yaml#/definitions/uint32
description: This parameter is used to decrease ADC sampling rate.
Quicker measurements can be made by reducing decimation ratio.
This property in child node is applicable only for Gen2 ADC_TM peripheral.
enum:
- 85
- 340
- 1360
default: 1360
required:
- reg
- io-channels
@ -100,6 +127,31 @@ patternProperties:
additionalProperties:
false
allOf:
- if:
properties:
compatible:
contains:
const: qcom,spmi-adc-tm5
then:
patternProperties:
"^([-a-z0-9]*)@[0-7]$":
properties:
qcom,decimation: false
qcom,avg-samples: false
- if:
properties:
compatible:
contains:
const: qcom,spmi-adc-tm5-gen2
then:
properties:
qcom,avg-samples: false
qcom,decimation: false
required:
- compatible
- reg
@ -124,7 +176,7 @@ examples:
#size-cells = <0>;
#io-channel-cells = <1>;
/* Other propreties are omitted */
/* Other properties are omitted */
conn-therm@4f {
reg = <ADC5_AMUX_THM3_100K_PU>;
qcom,ratiometric;
@ -148,4 +200,58 @@ examples:
};
};
};
- |
#include <dt-bindings/iio/qcom,spmi-adc7-pmk8350.h>
#include <dt-bindings/iio/qcom,spmi-adc7-pm8350.h>
#include <dt-bindings/interrupt-controller/irq.h>
spmi_bus {
#address-cells = <1>;
#size-cells = <0>;
pmk8350_vadc: adc@3100 {
reg = <0x3100>;
compatible = "qcom,spmi-adc7";
#address-cells = <1>;
#size-cells = <0>;
#io-channel-cells = <1>;
/* Other properties are omitted */
xo-therm@44 {
reg = <PMK8350_ADC7_AMUX_THM1_100K_PU>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
conn-therm@47 {
reg = <PM8350_ADC7_AMUX_THM4_100K_PU>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
pmk8350_adc_tm: adc-tm@3400 {
compatible = "qcom,spmi-adc-tm5-gen2";
reg = <0x3400>;
interrupts = <0x0 0x34 0x0 IRQ_TYPE_EDGE_RISING>;
#thermal-sensor-cells = <1>;
#address-cells = <1>;
#size-cells = <0>;
pmk8350-xo-therm@0 {
reg = <0>;
io-channels = <&pmk8350_vadc PMK8350_ADC7_AMUX_THM1_100K_PU>;
qcom,decimation = <340>;
qcom,ratiometric;
qcom,hw-settle-time-us = <200>;
};
conn-therm@1 {
reg = <1>;
io-channels = <&pmk8350_vadc PM8350_ADC7_AMUX_THM4_100K_PU>;
qcom,avg-samples = <2>;
qcom,ratiometric;
qcom,hw-settle-time-us = <200>;
};
};
};
...

View File

@ -19,10 +19,11 @@ description: |
properties:
compatible:
oneOf:
- description: msm9860 TSENS based
- description: msm8960 TSENS based
items:
- enum:
- qcom,ipq8064-tsens
- qcom,msm8960-tsens
- description: v0.1 of TSENS
items:
@ -49,6 +50,7 @@ properties:
- qcom,sc7180-tsens
- qcom,sc7280-tsens
- qcom,sc8180x-tsens
- qcom,sc8280xp-tsens
- qcom,sdm630-tsens
- qcom,sdm845-tsens
- qcom,sm8150-tsens
@ -116,6 +118,7 @@ allOf:
- qcom,ipq8064-tsens
- qcom,mdm9607-tsens
- qcom,msm8916-tsens
- qcom,msm8960-tsens
- qcom,msm8974-tsens
- qcom,msm8976-tsens
- qcom,qcs404-tsens

View File

@ -17,7 +17,9 @@ properties:
compatible:
items:
- enum:
- renesas,r9a07g043-tsu # RZ/G2UL
- renesas,r9a07g044-tsu # RZ/G2{L,LC}
- renesas,r9a07g054-tsu # RZ/V2L
- const: renesas,rzg2l-tsu
reg:

View File

@ -0,0 +1,63 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/thermal/ti,j72xx-thermal.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Texas Instruments J72XX VTM (DTS) binding
maintainers:
- Keerthy <j-keerthy@ti.com>
properties:
compatible:
enum:
- ti,j721e-vtm
- ti,j7200-vtm
reg:
items:
- description: VTM cfg1 register space
- description: VTM cfg2 register space
- description: VTM efuse register space
power-domains:
maxItems: 1
"#thermal-sensor-cells":
const: 1
required:
- compatible
- reg
- power-domains
- "#thermal-sensor-cells"
additionalProperties: false
examples:
- |
#include <dt-bindings/soc/ti,sci_pm_domain.h>
wkup_vtm0: thermal-sensor@42040000 {
compatible = "ti,j721e-vtm";
reg = <0x42040000 0x350>,
<0x42050000 0x350>,
<0x43000300 0x10>;
power-domains = <&k3_pds 154 TI_SCI_PD_EXCLUSIVE>;
#thermal-sensor-cells = <1>;
};
mpu_thermal: mpu-thermal {
polling-delay-passive = <250>; /* milliseconds */
polling-delay = <500>; /* milliseconds */
thermal-sensors = <&wkup_vtm0 0>;
trips {
mpu_crit: mpu-crit {
temperature = <125000>; /* milliCelsius */
hysteresis = <2000>; /* milliCelsius */
type = "critical";
};
};
};
...

View File

@ -19589,6 +19589,7 @@ F: drivers/thermal/
F: include/linux/cpu_cooling.h
F: include/linux/thermal.h
F: include/uapi/linux/thermal.h
F: tools/lib/thermal/
F: tools/thermal/
THERMAL DRIVER FOR AMLOGIC SOCS

View File

@ -930,6 +930,7 @@ bool pm_wakeup_pending(void)
return ret || atomic_read(&pm_abort_suspend) > 0;
}
EXPORT_SYMBOL_GPL(pm_wakeup_pending);
void pm_system_wakeup(void)
{

View File

@ -677,6 +677,17 @@ u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio,
}
EXPORT_SYMBOL(qcom_adc_tm5_temp_volt_scale);
u16 qcom_adc_tm5_gen2_temp_res_scale(int temp)
{
int64_t resistance;
resistance = qcom_vadc_map_temp_voltage(adcmap7_100k,
ARRAY_SIZE(adcmap7_100k), temp);
return div64_s64(resistance * RATIO_MAX_ADC7, resistance + R_PU_100K);
}
EXPORT_SYMBOL(qcom_adc_tm5_gen2_temp_res_scale);
int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
unsigned int prescale_ratio,
const struct adc5_data *data,

View File

@ -28,7 +28,7 @@ thermal_sys-$(CONFIG_CPU_IDLE_THERMAL) += cpuidle_cooling.o
# devfreq cooling
thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o
obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
# platform thermal drivers
obj-y += broadcom/
obj-$(CONFIG_THERMAL_MMIO) += thermal_mmio.o

View File

@ -38,7 +38,6 @@ static int bcm2711_get_temp(void *data, int *temp)
int offset = thermal_zone_get_offset(priv->thermal);
u32 val;
int ret;
long t;
ret = regmap_read(priv->regmap, AVS_RO_TEMP_STATUS, &val);
if (ret)
@ -50,9 +49,7 @@ static int bcm2711_get_temp(void *data, int *temp)
val &= AVS_RO_TEMP_STATUS_DATA_MSK;
/* Convert a HW code to a temperature reading (millidegree celsius) */
t = slope * val + offset;
*temp = t < 0 ? 0 : t;
*temp = slope * val + offset;
return 0;
}

View File

@ -60,6 +60,9 @@ static int sr_thermal_probe(struct platform_device *pdev)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENOENT;
sr_thermal->regs = (void __iomem *)devm_memremap(&pdev->dev, res->start,
resource_size(res),
MEMREMAP_WB);

View File

@ -359,22 +359,29 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
struct device *dev = df->dev.parent;
struct devfreq_cooling_device *dfc;
struct em_perf_domain *em;
struct thermal_cooling_device_ops *ops;
char *name;
int err, num_opps;
dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
if (!dfc)
ops = kmemdup(&devfreq_cooling_ops, sizeof(*ops), GFP_KERNEL);
if (!ops)
return ERR_PTR(-ENOMEM);
dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
if (!dfc) {
err = -ENOMEM;
goto free_ops;
}
dfc->devfreq = df;
em = em_pd_get(dev);
if (em && !em_is_artificial(em)) {
dfc->em_pd = em;
devfreq_cooling_ops.get_requested_power =
ops->get_requested_power =
devfreq_cooling_get_requested_power;
devfreq_cooling_ops.state2power = devfreq_cooling_state2power;
devfreq_cooling_ops.power2state = devfreq_cooling_power2state;
ops->state2power = devfreq_cooling_state2power;
ops->power2state = devfreq_cooling_power2state;
dfc->power_ops = dfc_power;
@ -409,8 +416,7 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
if (!name)
goto remove_qos_req;
cdev = thermal_of_cooling_device_register(np, name, dfc,
&devfreq_cooling_ops);
cdev = thermal_of_cooling_device_register(np, name, dfc, ops);
kfree(name);
if (IS_ERR(cdev)) {
@ -431,6 +437,8 @@ free_table:
kfree(dfc->freq_table);
free_dfc:
kfree(dfc);
free_ops:
kfree(ops);
return ERR_PTR(err);
}
@ -512,11 +520,13 @@ EXPORT_SYMBOL_GPL(devfreq_cooling_em_register);
void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
{
struct devfreq_cooling_device *dfc;
const struct thermal_cooling_device_ops *ops;
struct device *dev;
if (IS_ERR_OR_NULL(cdev))
return;
ops = cdev->ops;
dfc = cdev->devdata;
dev = dfc->devfreq->dev.parent;
@ -527,5 +537,6 @@ void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
kfree(dfc->freq_table);
kfree(dfc);
kfree(ops);
}
EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);

View File

@ -629,7 +629,6 @@ static int hisi_thermal_remove(struct platform_device *pdev)
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int hisi_thermal_suspend(struct device *dev)
{
struct hisi_thermal_data *data = dev_get_drvdata(dev);
@ -651,15 +650,14 @@ static int hisi_thermal_resume(struct device *dev)
return ret;
}
#endif
static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
static DEFINE_SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
hisi_thermal_suspend, hisi_thermal_resume);
static struct platform_driver hisi_thermal_driver = {
.driver = {
.name = "hisi_thermal",
.pm = &hisi_thermal_pm_ops,
.pm = pm_sleep_ptr(&hisi_thermal_pm_ops),
.of_match_table = of_hisi_thermal_match,
},
.probe = hisi_thermal_probe,

View File

@ -94,8 +94,8 @@ static int imx_sc_thermal_probe(struct platform_device *pdev)
sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor) {
of_node_put(child);
of_node_put(sensor_np);
return -ENOMEM;
ret = -ENOMEM;
goto put_node;
}
ret = thermal_zone_of_get_sensor_id(child,
@ -124,7 +124,9 @@ static int imx_sc_thermal_probe(struct platform_device *pdev)
dev_warn(&pdev->dev, "failed to add hwmon sysfs attributes\n");
}
put_node:
of_node_put(sensor_np);
of_node_put(np);
return ret;
}

View File

@ -169,28 +169,25 @@ static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enab
acpi_status status;
int result = 0;
struct acpi_osc_context context = {
.uuid_str = NULL,
.uuid_str = uuid_str,
.rev = 1,
.cap.length = 8,
.cap.pointer = buf,
};
context.uuid_str = uuid_str;
buf[OSC_QUERY_DWORD] = 0;
buf[OSC_SUPPORT_DWORD] = *enable;
context.cap.pointer = buf;
status = acpi_run_osc(handle, &context);
if (ACPI_SUCCESS(status)) {
ret = *((u32 *)(context.ret.pointer + 4));
if (ret != *enable)
result = -EPERM;
kfree(context.ret.pointer);
} else
result = -EPERM;
kfree(context.ret.pointer);
return result;
}
@ -524,21 +521,18 @@ static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
obj = buffer.pointer;
if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
|| obj->package.elements[0].type != ACPI_TYPE_BUFFER) {
kfree(buffer.pointer);
return;
}
|| obj->package.elements[0].type != ACPI_TYPE_BUFFER)
goto out_free;
priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
obj->package.elements[0].buffer.length,
GFP_KERNEL);
if (!priv->data_vault) {
kfree(buffer.pointer);
return;
}
if (!priv->data_vault)
goto out_free;
bin_attr_data_vault.private = priv->data_vault;
bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
out_free:
kfree(buffer.pointer);
}

View File

@ -243,8 +243,6 @@ static void hfi_update_work_fn(struct work_struct *work)
hfi_instance = container_of(to_delayed_work(work), struct hfi_instance,
update_work);
if (!hfi_instance)
return;
update_capabilities(hfi_instance);
}

View File

@ -70,8 +70,8 @@ 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;
/* Number of iterations for cooling delay, 600 counts by default for now */
static unsigned int delay_cnt = 600;
module_param(delay_cnt, int, 0644);
MODULE_PARM_DESC(delay_cnt, "total number of iterations for time delay.");
@ -193,10 +193,11 @@ static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
return 0;
}
/* Cool the PCH when it's overheat in .suspend_noirq phase */
static int pch_wpt_suspend(struct pch_thermal_device *ptd)
{
u8 tsel;
u8 pch_delay_cnt = 1;
int pch_delay_cnt = 0;
u16 pch_thr_temp, pch_cur_temp;
/* Shutdown the thermal sensor if it is not enabled by BIOS */
@ -232,26 +233,38 @@ static int pch_wpt_suspend(struct pch_thermal_device *ptd)
* 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)
while (pch_delay_cnt < delay_cnt) {
if (pch_cur_temp < pch_thr_temp)
break;
dev_warn(&ptd->pdev->dev,
if (pm_wakeup_pending()) {
dev_warn(&ptd->pdev->dev, "Wakeup event detected, abort cooling\n");
return 0;
}
pch_delay_cnt++;
dev_dbg(&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++;
}
if (pch_cur_temp > pch_thr_temp)
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);
"CPU-PCH is hot [%dC] after %d ms delay. S0ix might fail\n",
pch_cur_temp, pch_delay_cnt * delay_timeout);
else {
if (pch_delay_cnt)
dev_info(&ptd->pdev->dev,
"CPU-PCH is cool [%dC] after %d ms delay\n",
pch_cur_temp, pch_delay_cnt * delay_timeout);
else
dev_info(&ptd->pdev->dev,
"CPU-PCH is cool [%dC]\n",
pch_cur_temp);
}
return 0;
}
@ -455,7 +468,7 @@ static void intel_pch_thermal_remove(struct pci_dev *pdev)
pci_disable_device(pdev);
}
static int intel_pch_thermal_suspend(struct device *device)
static int intel_pch_thermal_suspend_noirq(struct device *device)
{
struct pch_thermal_device *ptd = dev_get_drvdata(device);
@ -495,7 +508,7 @@ static const struct pci_device_id intel_pch_thermal_id[] = {
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
static const struct dev_pm_ops intel_pch_pm_ops = {
.suspend = intel_pch_thermal_suspend,
.suspend_noirq = intel_pch_thermal_suspend_noirq,
.resume = intel_pch_thermal_resume,
};

View File

@ -16,6 +16,8 @@
#include <linux/thermal.h>
#include <linux/types.h>
#include "thermal_hwmon.h"
#define K3_VTM_DEVINFO_PWR0_OFFSET 0x4
#define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0
#define K3_VTM_TMPSENS0_CTRL_OFFSET 0x80
@ -219,6 +221,9 @@ static int k3_bandgap_probe(struct platform_device *pdev)
ret = PTR_ERR(data[id].tzd);
goto err_alloc;
}
if (devm_thermal_add_hwmon_sysfs(data[id].tzd))
dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
}
platform_set_drvdata(pdev, bgp);

View File

@ -0,0 +1,566 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TI Bandgap temperature sensor driver for J72XX SoC Family
*
* Copyright (C) 2021 Texas Instruments Incorporated - http://www.ti.com/
*/
#include <linux/math.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/pm_runtime.h>
#include <linux/err.h>
#include <linux/types.h>
#include <linux/of_platform.h>
#include <linux/io.h>
#include <linux/thermal.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/slab.h>
#define K3_VTM_DEVINFO_PWR0_OFFSET 0x4
#define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0
#define K3_VTM_TMPSENS0_CTRL_OFFSET 0x300
#define K3_VTM_MISC_CTRL_OFFSET 0xc
#define K3_VTM_TMPSENS_STAT_OFFSET 0x8
#define K3_VTM_ANYMAXT_OUTRG_ALERT_EN 0x1
#define K3_VTM_MISC_CTRL2_OFFSET 0x10
#define K3_VTM_TS_STAT_DTEMP_MASK 0x3ff
#define K3_VTM_MAX_NUM_TS 8
#define K3_VTM_TMPSENS_CTRL_SOC BIT(5)
#define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6)
#define K3_VTM_TMPSENS_CTRL_CLKON_REQ BIT(7)
#define K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN BIT(11)
#define K3_VTM_CORRECTION_TEMP_CNT 3
#define MINUS40CREF 5
#define PLUS30CREF 253
#define PLUS125CREF 730
#define PLUS150CREF 940
#define TABLE_SIZE 1024
#define MAX_TEMP 123000
#define COOL_DOWN_TEMP 105000
#define FACTORS_REDUCTION 13
static int *derived_table;
static int compute_value(int index, const s64 *factors, int nr_factors,
int reduction)
{
s64 value = 0;
int i;
for (i = 0; i < nr_factors; i++)
value += factors[i] * int_pow(index, i);
return (int)div64_s64(value, int_pow(10, reduction));
}
static void init_table(int factors_size, int *table, const s64 *factors)
{
int i;
for (i = 0; i < TABLE_SIZE; i++)
table[i] = compute_value(i, factors, factors_size,
FACTORS_REDUCTION);
}
/**
* struct err_values - structure containing error/reference values
* @refs: reference error values for -40C, 30C, 125C & 150C
* @errs: Actual error values for -40C, 30C, 125C & 150C read from the efuse
*/
struct err_values {
int refs[4];
int errs[4];
};
static void create_table_segments(struct err_values *err_vals, int seg,
int *ref_table)
{
int m = 0, c, num, den, i, err, idx1, idx2, err1, err2, ref1, ref2;
if (seg == 0)
idx1 = 0;
else
idx1 = err_vals->refs[seg];
idx2 = err_vals->refs[seg + 1];
err1 = err_vals->errs[seg];
err2 = err_vals->errs[seg + 1];
ref1 = err_vals->refs[seg];
ref2 = err_vals->refs[seg + 1];
/*
* Calculate the slope with adc values read from the register
* as the y-axis param and err in adc value as x-axis param
*/
num = ref2 - ref1;
den = err2 - err1;
if (den)
m = num / den;
c = ref2 - m * err2;
/*
* Take care of divide by zero error if error values are same
* Or when the slope is 0
*/
if (den != 0 && m != 0) {
for (i = idx1; i <= idx2; i++) {
err = (i - c) / m;
if (((i + err) < 0) || ((i + err) >= TABLE_SIZE))
continue;
derived_table[i] = ref_table[i + err];
}
} else { /* Constant error take care of divide by zero */
for (i = idx1; i <= idx2; i++) {
if (((i + err1) < 0) || ((i + err1) >= TABLE_SIZE))
continue;
derived_table[i] = ref_table[i + err1];
}
}
}
static int prep_lookup_table(struct err_values *err_vals, int *ref_table)
{
int inc, i, seg;
/*
* Fill up the lookup table under 3 segments
* region -40C to +30C
* region +30C to +125C
* region +125C to +150C
*/
for (seg = 0; seg < 3; seg++)
create_table_segments(err_vals, seg, ref_table);
/* Get to the first valid temperature */
i = 0;
while (!derived_table[i])
i++;
/*
* Get to the last zero index and back fill the temperature for
* sake of continuity
*/
if (i) {
/* 300 milli celsius steps */
while (i--)
derived_table[i] = derived_table[i + 1] - 300;
/* case 0 */
derived_table[i] = derived_table[i + 1] - 300;
}
/*
* Fill the last trailing 0s which are unfilled with increments of
* 100 milli celsius till 1023 code
*/
i = TABLE_SIZE - 1;
while (!derived_table[i])
i--;
i++;
inc = 1;
while (i < TABLE_SIZE) {
derived_table[i] = derived_table[i - 1] + inc * 100;
i++;
}
return 0;
}
struct k3_thermal_data;
struct k3_j72xx_bandgap {
struct device *dev;
void __iomem *base;
void __iomem *cfg2_base;
void __iomem *fuse_base;
struct k3_thermal_data *ts_data[K3_VTM_MAX_NUM_TS];
};
/* common data structures */
struct k3_thermal_data {
struct k3_j72xx_bandgap *bgp;
u32 ctrl_offset;
u32 stat_offset;
};
static int two_cmp(int tmp, int mask)
{
tmp = ~(tmp);
tmp &= mask;
tmp += 1;
/* Return negative value */
return (0 - tmp);
}
static unsigned int vtm_get_best_value(unsigned int s0, unsigned int s1,
unsigned int s2)
{
int d01 = abs(s0 - s1);
int d02 = abs(s0 - s2);
int d12 = abs(s1 - s2);
if (d01 <= d02 && d01 <= d12)
return (s0 + s1) / 2;
if (d02 <= d01 && d02 <= d12)
return (s0 + s2) / 2;
return (s1 + s2) / 2;
}
static inline int k3_bgp_read_temp(struct k3_thermal_data *devdata,
int *temp)
{
struct k3_j72xx_bandgap *bgp;
unsigned int dtemp, s0, s1, s2;
bgp = devdata->bgp;
/*
* Errata is applicable for am654 pg 1.0 silicon/J7ES. There
* is a variation of the order for certain degree centigrade on AM654.
* Work around that by getting the average of two closest
* readings out of three readings everytime we want to
* report temperatures.
*
* Errata workaround.
*/
s0 = readl(bgp->base + devdata->stat_offset) &
K3_VTM_TS_STAT_DTEMP_MASK;
s1 = readl(bgp->base + devdata->stat_offset) &
K3_VTM_TS_STAT_DTEMP_MASK;
s2 = readl(bgp->base + devdata->stat_offset) &
K3_VTM_TS_STAT_DTEMP_MASK;
dtemp = vtm_get_best_value(s0, s1, s2);
if (dtemp < 0 || dtemp >= TABLE_SIZE)
return -EINVAL;
*temp = derived_table[dtemp];
return 0;
}
/* Get temperature callback function for thermal zone */
static int k3_thermal_get_temp(void *devdata, int *temp)
{
struct k3_thermal_data *data = devdata;
int ret = 0;
ret = k3_bgp_read_temp(data, temp);
if (ret)
return ret;
return ret;
}
static const struct thermal_zone_of_device_ops k3_of_thermal_ops = {
.get_temp = k3_thermal_get_temp,
};
static int k3_j72xx_bandgap_temp_to_adc_code(int temp)
{
int low = 0, high = TABLE_SIZE - 1, mid;
if (temp > 160000 || temp < -50000)
return -EINVAL;
/* Binary search to find the adc code */
while (low < (high - 1)) {
mid = (low + high) / 2;
if (temp <= derived_table[mid])
high = mid;
else
low = mid;
}
return mid;
}
static void get_efuse_values(int id, struct k3_thermal_data *data, int *err,
struct k3_j72xx_bandgap *bgp)
{
int i, tmp, pow;
int ct_offsets[5][K3_VTM_CORRECTION_TEMP_CNT] = {
{ 0x0, 0x8, 0x4 },
{ 0x0, 0x8, 0x4 },
{ 0x0, -1, 0x4 },
{ 0x0, 0xC, -1 },
{ 0x0, 0xc, 0x8 }
};
int ct_bm[5][K3_VTM_CORRECTION_TEMP_CNT] = {
{ 0x3f, 0x1fe000, 0x1ff },
{ 0xfc0, 0x1fe000, 0x3fe00 },
{ 0x3f000, 0x7f800000, 0x7fc0000 },
{ 0xfc0000, 0x1fe0, 0x1f800000 },
{ 0x3f000000, 0x1fe000, 0x1ff0 }
};
for (i = 0; i < 3; i++) {
/* Extract the offset value using bit-mask */
if (ct_offsets[id][i] == -1 && i == 1) {
/* 25C offset Case of Sensor 2 split between 2 regs */
tmp = (readl(bgp->fuse_base + 0x8) & 0xE0000000) >> (29);
tmp |= ((readl(bgp->fuse_base + 0xC) & 0x1F) << 3);
pow = tmp & 0x80;
} else if (ct_offsets[id][i] == -1 && i == 2) {
/* 125C Case of Sensor 3 split between 2 regs */
tmp = (readl(bgp->fuse_base + 0x4) & 0xF8000000) >> (27);
tmp |= ((readl(bgp->fuse_base + 0x8) & 0xF) << 5);
pow = tmp & 0x100;
} else {
tmp = readl(bgp->fuse_base + ct_offsets[id][i]);
tmp &= ct_bm[id][i];
tmp = tmp >> __ffs(ct_bm[id][i]);
/* Obtain the sign bit pow*/
pow = ct_bm[id][i] >> __ffs(ct_bm[id][i]);
pow += 1;
pow /= 2;
}
/* Check for negative value */
if (tmp & pow) {
/* 2's complement value */
tmp = two_cmp(tmp, ct_bm[id][i] >> __ffs(ct_bm[id][i]));
}
err[i] = tmp;
}
/* Err value for 150C is set to 0 */
err[i] = 0;
}
static void print_look_up_table(struct device *dev, int *ref_table)
{
int i;
dev_dbg(dev, "The contents of derived array\n");
dev_dbg(dev, "Code Temperature\n");
for (i = 0; i < TABLE_SIZE; i++)
dev_dbg(dev, "%d %d %d\n", i, derived_table[i], ref_table[i]);
}
struct k3_j72xx_bandgap_data {
unsigned int has_errata_i2128;
};
static int k3_j72xx_bandgap_probe(struct platform_device *pdev)
{
int ret = 0, cnt, val, id;
int high_max, low_temp;
struct resource *res;
struct device *dev = &pdev->dev;
struct k3_j72xx_bandgap *bgp;
struct k3_thermal_data *data;
int workaround_needed = 0;
const struct k3_j72xx_bandgap_data *driver_data;
struct thermal_zone_device *ti_thermal;
int *ref_table;
struct err_values err_vals;
const s64 golden_factors[] = {
-490019999999999936,
3251200000000000,
-1705800000000,
603730000,
-92627,
};
const s64 pvt_wa_factors[] = {
-415230000000000000,
3126600000000000,
-1157800000000,
};
bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL);
if (!bgp)
return -ENOMEM;
bgp->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
bgp->base = devm_ioremap_resource(dev, res);
if (IS_ERR(bgp->base))
return PTR_ERR(bgp->base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
bgp->cfg2_base = devm_ioremap_resource(dev, res);
if (IS_ERR(bgp->cfg2_base))
return PTR_ERR(bgp->cfg2_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
bgp->fuse_base = devm_ioremap_resource(dev, res);
if (IS_ERR(bgp->fuse_base))
return PTR_ERR(bgp->fuse_base);
driver_data = of_device_get_match_data(dev);
if (driver_data)
workaround_needed = driver_data->has_errata_i2128;
pm_runtime_enable(dev);
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
pm_runtime_put_noidle(dev);
pm_runtime_disable(dev);
return ret;
}
/* Get the sensor count in the VTM */
val = readl(bgp->base + K3_VTM_DEVINFO_PWR0_OFFSET);
cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK;
cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK);
data = devm_kcalloc(bgp->dev, cnt, sizeof(*data), GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto err_alloc;
}
ref_table = kzalloc(sizeof(*ref_table) * TABLE_SIZE, GFP_KERNEL);
if (!ref_table) {
ret = -ENOMEM;
goto err_alloc;
}
derived_table = devm_kzalloc(bgp->dev, sizeof(*derived_table) * TABLE_SIZE,
GFP_KERNEL);
if (!derived_table) {
ret = -ENOMEM;
goto err_alloc;
}
/* Workaround not needed if bit30/bit31 is set even for J721e */
if (workaround_needed && (readl(bgp->fuse_base + 0x0) & 0xc0000000) == 0xc0000000)
workaround_needed = false;
dev_dbg(bgp->dev, "Work around %sneeded\n",
workaround_needed ? "not " : "");
if (!workaround_needed)
init_table(5, ref_table, golden_factors);
else
init_table(3, ref_table, pvt_wa_factors);
/* Register the thermal sensors */
for (id = 0; id < cnt; id++) {
data[id].bgp = bgp;
data[id].ctrl_offset = K3_VTM_TMPSENS0_CTRL_OFFSET + id * 0x20;
data[id].stat_offset = data[id].ctrl_offset +
K3_VTM_TMPSENS_STAT_OFFSET;
if (workaround_needed) {
/* ref adc values for -40C, 30C & 125C respectively */
err_vals.refs[0] = MINUS40CREF;
err_vals.refs[1] = PLUS30CREF;
err_vals.refs[2] = PLUS125CREF;
err_vals.refs[3] = PLUS150CREF;
get_efuse_values(id, &data[id], err_vals.errs, bgp);
}
if (id == 0 && workaround_needed)
prep_lookup_table(&err_vals, ref_table);
else if (id == 0 && !workaround_needed)
memcpy(derived_table, ref_table, TABLE_SIZE * 4);
val = readl(data[id].bgp->cfg2_base + data[id].ctrl_offset);
val |= (K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN |
K3_VTM_TMPSENS_CTRL_SOC |
K3_VTM_TMPSENS_CTRL_CLRZ | BIT(4));
writel(val, data[id].bgp->cfg2_base + data[id].ctrl_offset);
bgp->ts_data[id] = &data[id];
ti_thermal =
devm_thermal_zone_of_sensor_register(bgp->dev, id,
&data[id],
&k3_of_thermal_ops);
if (IS_ERR(ti_thermal)) {
dev_err(bgp->dev, "thermal zone device is NULL\n");
ret = PTR_ERR(ti_thermal);
goto err_alloc;
}
}
/*
* Program TSHUT thresholds
* Step 1: set the thresholds to ~123C and 105C WKUP_VTM_MISC_CTRL2
* Step 2: WKUP_VTM_TMPSENS_CTRL_j set the MAXT_OUTRG_EN bit
* This is already taken care as per of init
* Step 3: WKUP_VTM_MISC_CTRL set the ANYMAXT_OUTRG_ALERT_EN bit
*/
high_max = k3_j72xx_bandgap_temp_to_adc_code(MAX_TEMP);
low_temp = k3_j72xx_bandgap_temp_to_adc_code(COOL_DOWN_TEMP);
writel((low_temp << 16) | high_max, data[0].bgp->cfg2_base +
K3_VTM_MISC_CTRL2_OFFSET);
mdelay(100);
writel(K3_VTM_ANYMAXT_OUTRG_ALERT_EN, data[0].bgp->cfg2_base +
K3_VTM_MISC_CTRL_OFFSET);
platform_set_drvdata(pdev, bgp);
print_look_up_table(dev, ref_table);
/*
* Now that the derived_table has the appropriate look up values
* Free up the ref_table
*/
kfree(ref_table);
return 0;
err_alloc:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return ret;
}
static int k3_j72xx_bandgap_remove(struct platform_device *pdev)
{
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return 0;
}
const struct k3_j72xx_bandgap_data k3_j72xx_bandgap_j721e_data = {
.has_errata_i2128 = 1,
};
const struct k3_j72xx_bandgap_data k3_j72xx_bandgap_j7200_data = {
.has_errata_i2128 = 0,
};
static const struct of_device_id of_k3_j72xx_bandgap_match[] = {
{
.compatible = "ti,j721e-vtm",
.data = &k3_j72xx_bandgap_j721e_data,
},
{
.compatible = "ti,j7200-vtm",
.data = &k3_j72xx_bandgap_j7200_data,
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, of_k3_j72xx_bandgap_match);
static struct platform_driver k3_j72xx_bandgap_sensor_driver = {
.probe = k3_j72xx_bandgap_probe,
.remove = k3_j72xx_bandgap_remove,
.driver = {
.name = "k3-j72xx-soc-thermal",
.of_match_table = of_k3_j72xx_bandgap_match,
},
};
module_platform_driver(k3_j72xx_bandgap_sensor_driver);
MODULE_DESCRIPTION("K3 bandgap temperature sensor driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>");

View File

@ -220,6 +220,7 @@ static int lmh_probe(struct platform_device *pdev)
}
static const struct of_device_id lmh_table[] = {
{ .compatible = "qcom,sc8180x-lmh", },
{ .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS},
{ .compatible = "qcom,sm8150-lmh", },
{}

View File

@ -4,7 +4,10 @@
*
* Based on original driver:
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/bitfield.h>
#include <linux/iio/adc/qcom-vadc-common.h>
#include <linux/iio/consumer.h>
@ -15,6 +18,7 @@
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/thermal.h>
#include <asm-generic/unaligned.h>
/*
* Thermal monitoring block consists of 8 (ADC_TM5_NUM_CHANNELS) channels. Each
@ -71,6 +75,60 @@
#define ADC_TM5_M_HIGH_THR_INT_EN BIT(1)
#define ADC_TM5_M_LOW_THR_INT_EN BIT(0)
#define ADC_TM_GEN2_STATUS1 0x08
#define ADC_TM_GEN2_STATUS_LOW_SET 0x09
#define ADC_TM_GEN2_STATUS_LOW_CLR 0x0a
#define ADC_TM_GEN2_STATUS_HIGH_SET 0x0b
#define ADC_TM_GEN2_STATUS_HIGH_CLR 0x0c
#define ADC_TM_GEN2_CFG_HS_SET 0x0d
#define ADC_TM_GEN2_CFG_HS_FLAG BIT(0)
#define ADC_TM_GEN2_CFG_HS_CLR 0x0e
#define ADC_TM_GEN2_SID 0x40
#define ADC_TM_GEN2_CH_CTL 0x41
#define ADC_TM_GEN2_TM_CH_SEL GENMASK(7, 5)
#define ADC_TM_GEN2_MEAS_INT_SEL GENMASK(3, 2)
#define ADC_TM_GEN2_ADC_DIG_PARAM 0x42
#define ADC_TM_GEN2_CTL_CAL_SEL GENMASK(5, 4)
#define ADC_TM_GEN2_CTL_DEC_RATIO_MASK GENMASK(3, 2)
#define ADC_TM_GEN2_FAST_AVG_CTL 0x43
#define ADC_TM_GEN2_FAST_AVG_EN BIT(7)
#define ADC_TM_GEN2_ADC_CH_SEL_CTL 0x44
#define ADC_TM_GEN2_DELAY_CTL 0x45
#define ADC_TM_GEN2_HW_SETTLE_DELAY GENMASK(3, 0)
#define ADC_TM_GEN2_EN_CTL1 0x46
#define ADC_TM_GEN2_EN BIT(7)
#define ADC_TM_GEN2_CONV_REQ 0x47
#define ADC_TM_GEN2_CONV_REQ_EN BIT(7)
#define ADC_TM_GEN2_LOW_THR0 0x49
#define ADC_TM_GEN2_LOW_THR1 0x4a
#define ADC_TM_GEN2_HIGH_THR0 0x4b
#define ADC_TM_GEN2_HIGH_THR1 0x4c
#define ADC_TM_GEN2_LOWER_MASK(n) ((n) & GENMASK(7, 0))
#define ADC_TM_GEN2_UPPER_MASK(n) (((n) & GENMASK(15, 8)) >> 8)
#define ADC_TM_GEN2_MEAS_IRQ_EN 0x4d
#define ADC_TM_GEN2_MEAS_EN BIT(7)
#define ADC_TM5_GEN2_HIGH_THR_INT_EN BIT(1)
#define ADC_TM5_GEN2_LOW_THR_INT_EN BIT(0)
#define ADC_TM_GEN2_MEAS_INT_LSB 0x50
#define ADC_TM_GEN2_MEAS_INT_MSB 0x51
#define ADC_TM_GEN2_MEAS_INT_MODE 0x52
#define ADC_TM_GEN2_Mn_DATA0(n) ((n * 2) + 0xa0)
#define ADC_TM_GEN2_Mn_DATA1(n) ((n * 2) + 0xa1)
#define ADC_TM_GEN2_DATA_SHIFT 8
enum adc5_timer_select {
ADC5_TIMER_SEL_1 = 0,
ADC5_TIMER_SEL_2,
@ -78,11 +136,11 @@ enum adc5_timer_select {
ADC5_TIMER_SEL_NONE,
};
struct adc_tm5_data {
const u32 full_scale_code_volt;
unsigned int *decimation;
unsigned int *hw_settle;
bool is_hc;
enum adc5_gen {
ADC_TM5,
ADC_TM_HC,
ADC_TM5_GEN2,
ADC_TM5_MAX
};
enum adc_tm5_cal_method {
@ -91,7 +149,28 @@ enum adc_tm5_cal_method {
ADC_TM5_ABSOLUTE_CAL
};
enum adc_tm_gen2_time_select {
MEAS_INT_50MS = 0,
MEAS_INT_100MS,
MEAS_INT_1S,
MEAS_INT_SET,
MEAS_INT_NONE,
};
struct adc_tm5_chip;
struct adc_tm5_channel;
struct adc_tm5_data {
const u32 full_scale_code_volt;
unsigned int *decimation;
unsigned int *hw_settle;
int (*disable_channel)(struct adc_tm5_channel *channel);
int (*configure)(struct adc_tm5_channel *channel, int low, int high);
irqreturn_t (*isr)(int irq, void *data);
int (*init)(struct adc_tm5_chip *chip);
char *irq_name;
int gen;
};
/**
* struct adc_tm5_channel - ADC Thermal Monitoring channel data.
@ -101,6 +180,12 @@ struct adc_tm5_chip;
* @prescale: channel scaling performed on the input signal.
* @hw_settle_time: the time between AMUX being configured and the
* start of conversion.
* @decimation: sampling rate supported for the channel.
* @avg_samples: ability to provide single result from the ADC
* that is an average of multiple measurements.
* @high_thr_en: channel upper voltage threshold enable state.
* @low_thr_en: channel lower voltage threshold enable state.
* @meas_en: recurring measurement enable state
* @iio: IIO channel instance used by this channel.
* @chip: ADC TM chip instance.
* @tzd: thermal zone device used by this channel.
@ -111,6 +196,11 @@ struct adc_tm5_channel {
enum adc_tm5_cal_method cal_method;
unsigned int prescale;
unsigned int hw_settle_time;
unsigned int decimation; /* For Gen2 ADC_TM */
unsigned int avg_samples; /* For Gen2 ADC_TM */
bool high_thr_en; /* For Gen2 ADC_TM */
bool low_thr_en; /* For Gen2 ADC_TM */
bool meas_en; /* For Gen2 ADC_TM */
struct iio_channel *iio;
struct adc_tm5_chip *chip;
struct thermal_zone_device *tzd;
@ -124,9 +214,15 @@ struct adc_tm5_channel {
* @channels: array of ADC TM channel data.
* @nchannels: amount of channels defined/allocated
* @decimation: sampling rate supported for the channel.
* Applies to all channels, used only on Gen1 ADC_TM.
* @avg_samples: ability to provide single result from the ADC
* that is an average of multiple measurements.
* that is an average of multiple measurements. Applies to all
* channels, used only on Gen1 ADC_TM.
* @base: base address of TM registers.
* @adc_mutex_lock: ADC_TM mutex lock, used only on Gen2 ADC_TM.
* It is used to ensure only one ADC channel configuration
* is done at a time using the shared set of configuration
* registers.
*/
struct adc_tm5_chip {
struct regmap *regmap;
@ -137,22 +233,7 @@ struct adc_tm5_chip {
unsigned int decimation;
unsigned int avg_samples;
u16 base;
};
static const struct adc_tm5_data adc_tm5_data_pmic = {
.full_scale_code_volt = 0x70e4,
.decimation = (unsigned int []) { 250, 420, 840 },
.hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
1000, 2000, 4000, 8000, 16000, 32000,
64000, 128000 },
};
static const struct adc_tm5_data adc_tm_hc_data_pmic = {
.full_scale_code_volt = 0x70e4,
.decimation = (unsigned int []) { 256, 512, 1024 },
.hw_settle = (unsigned int []) { 0, 100, 200, 300, 400, 500, 600, 700,
1000, 2000, 4000, 6000, 8000, 10000 },
.is_hc = true,
struct mutex adc_mutex_lock;
};
static int adc_tm5_read(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len)
@ -219,6 +300,61 @@ static irqreturn_t adc_tm5_isr(int irq, void *data)
return IRQ_HANDLED;
}
static irqreturn_t adc_tm5_gen2_isr(int irq, void *data)
{
struct adc_tm5_chip *chip = data;
u8 status_low, status_high;
int ret, i;
ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low));
if (ret) {
dev_err(chip->dev, "read status_low failed: %d\n", ret);
return IRQ_HANDLED;
}
ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high));
if (ret) {
dev_err(chip->dev, "read status_high failed: %d\n", ret);
return IRQ_HANDLED;
}
ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low));
if (ret < 0) {
dev_err(chip->dev, "clear status low failed with %d\n", ret);
return IRQ_HANDLED;
}
ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high));
if (ret < 0) {
dev_err(chip->dev, "clear status high failed with %d\n", ret);
return IRQ_HANDLED;
}
for (i = 0; i < chip->nchannels; i++) {
bool upper_set = false, lower_set = false;
unsigned int ch = chip->channels[i].channel;
/* No TZD, we warned at the boot time */
if (!chip->channels[i].tzd)
continue;
if (!chip->channels[i].meas_en)
continue;
lower_set = (status_low & BIT(ch)) &&
(chip->channels[i].low_thr_en);
upper_set = (status_high & BIT(ch)) &&
(chip->channels[i].high_thr_en);
if (upper_set || lower_set)
thermal_zone_device_update(chip->channels[i].tzd,
THERMAL_EVENT_UNSPECIFIED);
}
return IRQ_HANDLED;
}
static int adc_tm5_get_temp(void *data, int *temp)
{
struct adc_tm5_channel *channel = data;
@ -249,6 +385,104 @@ static int adc_tm5_disable_channel(struct adc_tm5_channel *channel)
0);
}
#define ADC_TM_GEN2_POLL_DELAY_MIN_US 100
#define ADC_TM_GEN2_POLL_DELAY_MAX_US 110
#define ADC_TM_GEN2_POLL_RETRY_COUNT 3
static int32_t adc_tm5_gen2_conv_req(struct adc_tm5_chip *chip)
{
int ret;
u8 data;
unsigned int count;
data = ADC_TM_GEN2_EN;
ret = adc_tm5_write(chip, ADC_TM_GEN2_EN_CTL1, &data, 1);
if (ret < 0) {
dev_err(chip->dev, "adc-tm enable failed with %d\n", ret);
return ret;
}
data = ADC_TM_GEN2_CFG_HS_FLAG;
ret = adc_tm5_write(chip, ADC_TM_GEN2_CFG_HS_SET, &data, 1);
if (ret < 0) {
dev_err(chip->dev, "adc-tm handshake failed with %d\n", ret);
return ret;
}
data = ADC_TM_GEN2_CONV_REQ_EN;
ret = adc_tm5_write(chip, ADC_TM_GEN2_CONV_REQ, &data, 1);
if (ret < 0) {
dev_err(chip->dev, "adc-tm request conversion failed with %d\n", ret);
return ret;
}
/*
* SW sets a handshake bit and waits for PBS to clear it
* before the next conversion request can be queued.
*/
for (count = 0; count < ADC_TM_GEN2_POLL_RETRY_COUNT; count++) {
ret = adc_tm5_read(chip, ADC_TM_GEN2_CFG_HS_SET, &data, sizeof(data));
if (ret < 0) {
dev_err(chip->dev, "adc-tm read failed with %d\n", ret);
return ret;
}
if (!(data & ADC_TM_GEN2_CFG_HS_FLAG))
return ret;
usleep_range(ADC_TM_GEN2_POLL_DELAY_MIN_US,
ADC_TM_GEN2_POLL_DELAY_MAX_US);
}
dev_err(chip->dev, "adc-tm conversion request handshake timed out\n");
return -ETIMEDOUT;
}
static int adc_tm5_gen2_disable_channel(struct adc_tm5_channel *channel)
{
struct adc_tm5_chip *chip = channel->chip;
int ret;
u8 val;
mutex_lock(&chip->adc_mutex_lock);
channel->meas_en = false;
channel->high_thr_en = false;
channel->low_thr_en = false;
ret = adc_tm5_read(chip, ADC_TM_GEN2_CH_CTL, &val, sizeof(val));
if (ret < 0) {
dev_err(chip->dev, "adc-tm block read failed with %d\n", ret);
goto disable_fail;
}
val &= ~ADC_TM_GEN2_TM_CH_SEL;
val |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel);
ret = adc_tm5_write(chip, ADC_TM_GEN2_CH_CTL, &val, 1);
if (ret < 0) {
dev_err(chip->dev, "adc-tm channel disable failed with %d\n", ret);
goto disable_fail;
}
val = 0;
ret = adc_tm5_write(chip, ADC_TM_GEN2_MEAS_IRQ_EN, &val, 1);
if (ret < 0) {
dev_err(chip->dev, "adc-tm interrupt disable failed with %d\n", ret);
goto disable_fail;
}
ret = adc_tm5_gen2_conv_req(channel->chip);
if (ret < 0)
dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret);
disable_fail:
mutex_unlock(&chip->adc_mutex_lock);
return ret;
}
static int adc_tm5_enable(struct adc_tm5_chip *chip)
{
int ret;
@ -291,8 +525,7 @@ static int adc_tm5_configure(struct adc_tm5_channel *channel, int low, int high)
u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
chip->data->full_scale_code_volt, high);
buf[1] = adc_code & 0xff;
buf[2] = adc_code >> 8;
put_unaligned_le16(adc_code, &buf[1]);
buf[7] |= ADC_TM5_M_LOW_THR_INT_EN;
} else {
buf[7] &= ~ADC_TM5_M_LOW_THR_INT_EN;
@ -303,8 +536,7 @@ static int adc_tm5_configure(struct adc_tm5_channel *channel, int low, int high)
u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
chip->data->full_scale_code_volt, low);
buf[3] = adc_code & 0xff;
buf[4] = adc_code >> 8;
put_unaligned_le16(adc_code, &buf[3]);
buf[7] |= ADC_TM5_M_HIGH_THR_INT_EN;
} else {
buf[7] &= ~ADC_TM5_M_HIGH_THR_INT_EN;
@ -329,6 +561,82 @@ static int adc_tm5_configure(struct adc_tm5_channel *channel, int low, int high)
return adc_tm5_enable(chip);
}
static int adc_tm5_gen2_configure(struct adc_tm5_channel *channel, int low, int high)
{
struct adc_tm5_chip *chip = channel->chip;
int ret;
u8 buf[14];
u16 adc_code;
mutex_lock(&chip->adc_mutex_lock);
channel->meas_en = true;
ret = adc_tm5_read(chip, ADC_TM_GEN2_SID, buf, sizeof(buf));
if (ret < 0) {
dev_err(chip->dev, "adc-tm block read failed with %d\n", ret);
goto config_fail;
}
/* Set SID from virtual channel number */
buf[0] = channel->adc_channel >> 8;
/* Set TM channel number used and measurement interval */
buf[1] &= ~ADC_TM_GEN2_TM_CH_SEL;
buf[1] |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel);
buf[1] &= ~ADC_TM_GEN2_MEAS_INT_SEL;
buf[1] |= FIELD_PREP(ADC_TM_GEN2_MEAS_INT_SEL, MEAS_INT_1S);
buf[2] &= ~ADC_TM_GEN2_CTL_DEC_RATIO_MASK;
buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_DEC_RATIO_MASK, channel->decimation);
buf[2] &= ~ADC_TM_GEN2_CTL_CAL_SEL;
buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_CAL_SEL, channel->cal_method);
buf[3] = channel->avg_samples | ADC_TM_GEN2_FAST_AVG_EN;
buf[4] = channel->adc_channel & 0xff;
buf[5] = channel->hw_settle_time & ADC_TM_GEN2_HW_SETTLE_DELAY;
/* High temperature corresponds to low voltage threshold */
if (high != INT_MAX) {
channel->low_thr_en = true;
adc_code = qcom_adc_tm5_gen2_temp_res_scale(high);
put_unaligned_le16(adc_code, &buf[9]);
} else {
channel->low_thr_en = false;
}
/* Low temperature corresponds to high voltage threshold */
if (low != -INT_MAX) {
channel->high_thr_en = true;
adc_code = qcom_adc_tm5_gen2_temp_res_scale(low);
put_unaligned_le16(adc_code, &buf[11]);
} else {
channel->high_thr_en = false;
}
buf[13] = ADC_TM_GEN2_MEAS_EN;
if (channel->high_thr_en)
buf[13] |= ADC_TM5_GEN2_HIGH_THR_INT_EN;
if (channel->low_thr_en)
buf[13] |= ADC_TM5_GEN2_LOW_THR_INT_EN;
ret = adc_tm5_write(chip, ADC_TM_GEN2_SID, buf, sizeof(buf));
if (ret) {
dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret);
goto config_fail;
}
ret = adc_tm5_gen2_conv_req(channel->chip);
if (ret < 0)
dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret);
config_fail:
mutex_unlock(&chip->adc_mutex_lock);
return ret;
}
static int adc_tm5_set_trips(void *data, int low, int high)
{
struct adc_tm5_channel *channel = data;
@ -343,14 +651,14 @@ static int adc_tm5_set_trips(void *data, int low, int high)
channel->channel, low, high);
if (high == INT_MAX && low <= -INT_MAX)
ret = adc_tm5_disable_channel(channel);
ret = chip->data->disable_channel(channel);
else
ret = adc_tm5_configure(channel, low, high);
ret = chip->data->configure(channel, low, high);
return ret;
}
static struct thermal_zone_of_device_ops adc_tm5_ops = {
static struct thermal_zone_of_device_ops adc_tm5_thermal_ops = {
.get_temp = adc_tm5_get_temp,
.set_trips = adc_tm5_set_trips,
};
@ -366,7 +674,7 @@ static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm)
tzd = devm_thermal_zone_of_sensor_register(adc_tm->dev,
adc_tm->channels[i].channel,
&adc_tm->channels[i],
&adc_tm5_ops);
&adc_tm5_thermal_ops);
if (IS_ERR(tzd)) {
if (PTR_ERR(tzd) == -ENODEV) {
dev_warn(adc_tm->dev, "thermal sensor on channel %d is not used\n",
@ -442,12 +750,37 @@ static int adc_tm5_init(struct adc_tm5_chip *chip)
return ret;
}
static int adc_tm5_gen2_init(struct adc_tm5_chip *chip)
{
u8 channels_available;
int ret;
unsigned int i;
ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM,
&channels_available, sizeof(channels_available));
if (ret) {
dev_err(chip->dev, "read failed for BTM channels\n");
return ret;
}
for (i = 0; i < chip->nchannels; i++) {
if (chip->channels[i].channel >= channels_available) {
dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
return -EINVAL;
}
}
mutex_init(&chip->adc_mutex_lock);
return ret;
}
static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
struct adc_tm5_channel *channel,
struct device_node *node)
{
const char *name = node->name;
u32 chan, value, varr[2];
u32 chan, value, adc_channel, varr[2];
int ret;
struct device *dev = adc_tm->dev;
struct of_phandle_args args;
@ -477,7 +810,16 @@ static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
}
of_node_put(args.np);
if (args.args_count != 1 || args.args[0] >= ADC5_MAX_CHANNEL) {
if (args.args_count != 1) {
dev_err(dev, "%s: invalid args count for ADC channel %d\n", name, chan);
return -EINVAL;
}
adc_channel = args.args[0];
if (adc_tm->data->gen == ADC_TM5_GEN2)
adc_channel &= 0xff;
if (adc_channel >= ADC5_MAX_CHANNEL) {
dev_err(dev, "%s: invalid ADC channel number %d\n", name, chan);
return -EINVAL;
}
@ -523,9 +865,76 @@ static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
else
channel->cal_method = ADC_TM5_ABSOLUTE_CAL;
if (adc_tm->data->gen == ADC_TM5_GEN2) {
ret = of_property_read_u32(node, "qcom,decimation", &value);
if (!ret) {
ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation);
if (ret < 0) {
dev_err(dev, "invalid decimation %d\n", value);
return ret;
}
channel->decimation = ret;
} else {
channel->decimation = ADC5_DECIMATION_DEFAULT;
}
ret = of_property_read_u32(node, "qcom,avg-samples", &value);
if (!ret) {
ret = qcom_adc5_avg_samples_from_dt(value);
if (ret < 0) {
dev_err(dev, "invalid avg-samples %d\n", value);
return ret;
}
channel->avg_samples = ret;
} else {
channel->avg_samples = VADC_DEF_AVG_SAMPLES;
}
}
return 0;
}
static const struct adc_tm5_data adc_tm5_data_pmic = {
.full_scale_code_volt = 0x70e4,
.decimation = (unsigned int []) { 250, 420, 840 },
.hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
1000, 2000, 4000, 8000, 16000, 32000,
64000, 128000 },
.disable_channel = adc_tm5_disable_channel,
.configure = adc_tm5_configure,
.isr = adc_tm5_isr,
.init = adc_tm5_init,
.irq_name = "pm-adc-tm5",
.gen = ADC_TM5,
};
static const struct adc_tm5_data adc_tm_hc_data_pmic = {
.full_scale_code_volt = 0x70e4,
.decimation = (unsigned int []) { 256, 512, 1024 },
.hw_settle = (unsigned int []) { 0, 100, 200, 300, 400, 500, 600, 700,
1000, 2000, 4000, 6000, 8000, 10000 },
.disable_channel = adc_tm5_disable_channel,
.configure = adc_tm5_configure,
.isr = adc_tm5_isr,
.init = adc_tm_hc_init,
.irq_name = "pm-adc-tm5",
.gen = ADC_TM_HC,
};
static const struct adc_tm5_data adc_tm5_gen2_data_pmic = {
.full_scale_code_volt = 0x70e4,
.decimation = (unsigned int []) { 85, 340, 1360 },
.hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
1000, 2000, 4000, 8000, 16000, 32000,
64000, 128000 },
.disable_channel = adc_tm5_gen2_disable_channel,
.configure = adc_tm5_gen2_configure,
.isr = adc_tm5_gen2_isr,
.init = adc_tm5_gen2_init,
.irq_name = "pm-adc-tm5-gen2",
.gen = ADC_TM5_GEN2,
};
static int adc_tm5_get_dt_data(struct adc_tm5_chip *adc_tm, struct device_node *node)
{
struct adc_tm5_channel *channels;
@ -623,10 +1032,7 @@ static int adc_tm5_probe(struct platform_device *pdev)
return ret;
}
if (adc_tm->data->is_hc)
ret = adc_tm_hc_init(adc_tm);
else
ret = adc_tm5_init(adc_tm);
ret = adc_tm->data->init(adc_tm);
if (ret) {
dev_err(dev, "adc-tm init failed\n");
return ret;
@ -638,8 +1044,8 @@ static int adc_tm5_probe(struct platform_device *pdev)
return ret;
}
return devm_request_threaded_irq(dev, irq, NULL, adc_tm5_isr,
IRQF_ONESHOT, "pm-adc-tm5", adc_tm);
return devm_request_threaded_irq(dev, irq, NULL, adc_tm->data->isr,
IRQF_ONESHOT, adc_tm->data->irq_name, adc_tm);
}
static const struct of_device_id adc_tm5_match_table[] = {
@ -651,6 +1057,10 @@ static const struct of_device_id adc_tm5_match_table[] = {
.compatible = "qcom,spmi-adc-tm-hc",
.data = &adc_tm_hc_data_pmic,
},
{
.compatible = "qcom,spmi-adc-tm5-gen2",
.data = &adc_tm5_gen2_data_pmic,
},
{ }
};
MODULE_DEVICE_TABLE(of, adc_tm5_match_table);

View File

@ -979,6 +979,9 @@ static const struct of_device_id tsens_table[] = {
}, {
.compatible = "qcom,msm8939-tsens",
.data = &data_8939,
}, {
.compatible = "qcom,msm8960-tsens",
.data = &data_8960,
}, {
.compatible = "qcom,msm8974-tsens",
.data = &data_8974,

View File

@ -445,7 +445,7 @@ static int rcar_thermal_probe(struct platform_device *pdev)
struct rcar_thermal_common *common;
struct rcar_thermal_priv *priv;
struct device *dev = &pdev->dev;
struct resource *res, *irq;
struct resource *res;
const struct rcar_thermal_chip *chip = of_device_get_match_data(dev);
int mres = 0;
int i;
@ -467,9 +467,16 @@ static int rcar_thermal_probe(struct platform_device *pdev)
pm_runtime_get_sync(dev);
for (i = 0; i < chip->nirqs; i++) {
irq = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if (!irq)
continue;
int irq;
ret = platform_get_irq_optional(pdev, i);
if (ret < 0 && ret != -ENXIO)
goto error_unregister;
if (ret > 0)
irq = ret;
else
break;
if (!common->base) {
/*
* platform has IRQ support.
@ -487,7 +494,7 @@ static int rcar_thermal_probe(struct platform_device *pdev)
idle = 0; /* polling delay is not needed */
}
ret = devm_request_irq(dev, irq->start, rcar_thermal_irq,
ret = devm_request_irq(dev, irq, rcar_thermal_irq,
IRQF_SHARED, dev_name(dev), common);
if (ret) {
dev_err(dev, "irq request failed\n ");

View File

@ -32,6 +32,8 @@
#define TSU_SS 0x10
#define OTPTSUTRIM_REG(n) (0x18 + ((n) * 0x4))
#define OTPTSUTRIM_EN_MASK BIT(31)
#define OTPTSUTRIM_MASK GENMASK(11, 0)
/* Sensor Mode Register(TSU_SM) */
#define TSU_SM_EN_TS BIT(0)
@ -183,11 +185,15 @@ static int rzg2l_thermal_probe(struct platform_device *pdev)
pm_runtime_get_sync(dev);
priv->calib0 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0));
if (!priv->calib0)
if (priv->calib0 & OTPTSUTRIM_EN_MASK)
priv->calib0 &= OTPTSUTRIM_MASK;
else
priv->calib0 = SW_CALIB0_VAL;
priv->calib1 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(1));
if (!priv->calib1)
if (priv->calib1 & OTPTSUTRIM_EN_MASK)
priv->calib1 &= OTPTSUTRIM_MASK;
else
priv->calib1 = SW_CALIB1_VAL;
platform_set_drvdata(pdev, priv);

View File

@ -947,6 +947,7 @@ __thermal_cooling_device_register(struct device_node *np,
return cdev;
out_kfree_type:
thermal_cooling_device_destroy_sysfs(cdev);
kfree(cdev->type);
put_device(&cdev->device);
cdev = NULL;

View File

@ -35,7 +35,7 @@ struct __thermal_cooling_bind_param {
};
/**
* struct __thermal_bind_param - a match between trip and cooling device
* struct __thermal_bind_params - a match between trip and cooling device
* @tcbp: a pointer to an array of cooling devices
* @count: number of elements in array
* @trip_id: the trip point index
@ -203,6 +203,14 @@ static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip,
return data->ops->get_trend(data->sensor_data, trip, trend);
}
static int of_thermal_change_mode(struct thermal_zone_device *tz,
enum thermal_device_mode mode)
{
struct __thermal_zone *data = tz->devdata;
return data->ops->change_mode(data->sensor_data, mode);
}
static int of_thermal_bind(struct thermal_zone_device *thermal,
struct thermal_cooling_device *cdev)
{
@ -408,6 +416,9 @@ thermal_zone_of_add_sensor(struct device_node *zone,
if (ops->set_emul_temp)
tzd->ops->set_emul_temp = of_thermal_set_emul_temp;
if (ops->change_mode)
tzd->ops->change_mode = of_thermal_change_mode;
mutex_unlock(&tzd->lock);
return tzd;
@ -569,6 +580,7 @@ void thermal_zone_of_sensor_unregister(struct device *dev,
tzd->ops->get_temp = NULL;
tzd->ops->get_trend = NULL;
tzd->ops->set_emul_temp = NULL;
tzd->ops->change_mode = NULL;
tz->ops = NULL;
tz->sensor_data = NULL;

View File

@ -152,6 +152,8 @@ int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio,
u32 full_scale_code_volt, int temp);
u16 qcom_adc_tm5_gen2_temp_res_scale(int temp);
int qcom_adc5_prescaling_from_dt(u32 num, u32 den);
int qcom_adc5_hw_settle_time_from_dt(u32 value, const unsigned int *hw_settle);

View File

@ -299,6 +299,8 @@ struct thermal_zone_params {
* temperature.
* @set_trip_temp: a pointer to a function that sets the trip temperature on
* hardware.
* @change_mode: a pointer to a function that notifies the thermal zone
* mode change.
*/
struct thermal_zone_of_device_ops {
int (*get_temp)(void *, int *);
@ -306,6 +308,7 @@ struct thermal_zone_of_device_ops {
int (*set_trips)(void *, int, int);
int (*set_emul_temp)(void *, int);
int (*set_trip_temp)(void *, int, int);
int (*change_mode) (void *, enum thermal_device_mode);
};
/* Function declarations */

View File

@ -32,6 +32,9 @@ help:
@echo ' bootconfig - boot config tool'
@echo ' spi - spi tools'
@echo ' tmon - thermal monitoring and tuning tool'
@echo ' thermometer - temperature capture tool'
@echo ' thermal-engine - thermal monitoring tool'
@echo ' thermal - thermal library'
@echo ' tracing - misc tracing tools'
@echo ' turbostat - Intel CPU idle stats and freq reporting tool'
@echo ' usb - USB testing tools'
@ -89,12 +92,21 @@ perf: FORCE
selftests: FORCE
$(call descend,testing/$@)
thermal: FORCE
$(call descend,lib/$@)
turbostat x86_energy_perf_policy intel-speed-select: FORCE
$(call descend,power/x86/$@)
tmon: FORCE
$(call descend,thermal/$@)
thermometer: FORCE
$(call descend,thermal/$@)
thermal-engine: FORCE thermal
$(call descend,thermal/$@)
freefall: FORCE
$(call descend,laptop/$@)
@ -105,7 +117,7 @@ all: acpi cgroup counter cpupower gpio hv firewire \
perf selftests bootconfig spi turbostat usb \
virtio vm bpf x86_energy_perf_policy \
tmon freefall iio objtool kvm_stat wmi \
pci debugging tracing
pci debugging tracing thermal thermometer thermal-engine
acpi_install:
$(call descend,power/$(@:_install=),install)
@ -119,12 +131,21 @@ cgroup_install counter_install firewire_install gpio_install hv_install iio_inst
selftests_install:
$(call descend,testing/$(@:_install=),install)
thermal_install:
$(call descend,lib/$(@:_install=),install)
turbostat_install x86_energy_perf_policy_install intel-speed-select_install:
$(call descend,power/x86/$(@:_install=),install)
tmon_install:
$(call descend,thermal/$(@:_install=),install)
thermometer_install:
$(call descend,thermal/$(@:_install=),install)
thermal-engine_install:
$(call descend,thermal/$(@:_install=),install)
freefall_install:
$(call descend,laptop/$(@:_install=),install)
@ -137,7 +158,7 @@ install: acpi_install cgroup_install counter_install cpupower_install gpio_insta
virtio_install vm_install bpf_install x86_energy_perf_policy_install \
tmon_install freefall_install objtool_install kvm_stat_install \
wmi_install pci_install debugging_install intel-speed-select_install \
tracing_install
tracing_install thermometer_install thermal-engine_install
acpi_clean:
$(call descend,power/acpi,clean)
@ -164,9 +185,18 @@ perf_clean:
selftests_clean:
$(call descend,testing/$(@:_clean=),clean)
thermal_clean:
$(call descend,lib/thermal,clean)
turbostat_clean x86_energy_perf_policy_clean intel-speed-select_clean:
$(call descend,power/x86/$(@:_clean=),clean)
thermometer_clean:
$(call descend,thermal/thermometer,clean)
thermal-engine_clean:
$(call descend,thermal/thermal-engine,clean)
tmon_clean:
$(call descend,thermal/tmon,clean)
@ -181,6 +211,6 @@ clean: acpi_clean cgroup_clean counter_clean cpupower_clean hv_clean firewire_cl
vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
freefall_clean build_clean libbpf_clean libsubcmd_clean \
gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \
intel-speed-select_clean tracing_clean
intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean
.PHONY: FORCE

2
tools/lib/thermal/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
libthermal.so*
libthermal.pc

5
tools/lib/thermal/Build Normal file
View File

@ -0,0 +1,5 @@
libthermal-y += commands.o
libthermal-y += events.o
libthermal-y += thermal_nl.o
libthermal-y += sampling.o
libthermal-y += thermal.o

165
tools/lib/thermal/Makefile Normal file
View File

@ -0,0 +1,165 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
# Most of this file is copied from tools/lib/perf/Makefile
LIBTHERMAL_VERSION = 0
LIBTHERMAL_PATCHLEVEL = 0
LIBTHERMAL_EXTRAVERSION = 1
MAKEFLAGS += --no-print-directory
ifeq ($(srctree),)
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
# $(info Determined 'srctree' to be $(srctree))
endif
INSTALL = install
# Use DESTDIR for installing into a different root directory.
# This is useful for building a package. The program will be
# installed in this directory as if it was the root directory.
# Then the build tool can move it later.
DESTDIR ?=
DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))'
include $(srctree)/tools/scripts/Makefile.include
include $(srctree)/tools/scripts/Makefile.arch
ifeq ($(LP64), 1)
libdir_relative = lib64
else
libdir_relative = lib
endif
prefix ?=
libdir = $(prefix)/$(libdir_relative)
# Shell quotes
libdir_SQ = $(subst ','\'',$(libdir))
libdir_relative_SQ = $(subst ','\'',$(libdir_relative))
ifeq ("$(origin V)", "command line")
VERBOSE = $(V)
endif
ifndef VERBOSE
VERBOSE = 0
endif
ifeq ($(VERBOSE),1)
Q =
else
Q = @
endif
# Set compile option CFLAGS
ifdef EXTRA_CFLAGS
CFLAGS := $(EXTRA_CFLAGS)
else
CFLAGS := -g -Wall
endif
INCLUDES = \
-I/usr/include/libnl3 \
-I$(srctree)/tools/lib/thermal/include \
-I$(srctree)/tools/lib/ \
-I$(srctree)/tools/include \
-I$(srctree)/tools/arch/$(SRCARCH)/include/ \
-I$(srctree)/tools/arch/$(SRCARCH)/include/uapi \
-I$(srctree)/tools/include/uapi
# Append required CFLAGS
override CFLAGS += $(EXTRA_WARNINGS)
override CFLAGS += -Werror -Wall
override CFLAGS += -fPIC
override CFLAGS += $(INCLUDES)
override CFLAGS += -fvisibility=hidden
override CFGLAS += -Wl,-L.
override CFGLAS += -Wl,-lthermal
all:
export srctree OUTPUT CC LD CFLAGS V
export DESTDIR DESTDIR_SQ
include $(srctree)/tools/build/Makefile.include
VERSION_SCRIPT := libthermal.map
PATCHLEVEL = $(LIBTHERMAL_PATCHLEVEL)
EXTRAVERSION = $(LIBTHERMAL_EXTRAVERSION)
VERSION = $(LIBTHERMAL_VERSION).$(LIBTHERMAL_PATCHLEVEL).$(LIBTHERMAL_EXTRAVERSION)
LIBTHERMAL_SO := $(OUTPUT)libthermal.so.$(VERSION)
LIBTHERMAL_A := $(OUTPUT)libthermal.a
LIBTHERMAL_IN := $(OUTPUT)libthermal-in.o
LIBTHERMAL_PC := $(OUTPUT)libthermal.pc
LIBTHERMAL_ALL := $(LIBTHERMAL_A) $(OUTPUT)libthermal.so*
THERMAL_UAPI := include/uapi/linux/thermal.h
$(THERMAL_UAPI): FORCE
ln -sf $(srctree)/$@ $(srctree)/tools/$@
$(LIBTHERMAL_IN): FORCE
$(Q)$(MAKE) $(build)=libthermal
$(LIBTHERMAL_A): $(LIBTHERMAL_IN)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIBTHERMAL_IN)
$(LIBTHERMAL_SO): $(LIBTHERMAL_IN)
$(QUIET_LINK)$(CC) --shared -Wl,-soname,libthermal.so \
-Wl,--version-script=$(VERSION_SCRIPT) $^ -o $@
@ln -sf $(@F) $(OUTPUT)libthermal.so
@ln -sf $(@F) $(OUTPUT)libthermal.so.$(LIBTHERMAL_VERSION)
libs: $(THERMAL_UAPI) $(LIBTHERMAL_A) $(LIBTHERMAL_SO) $(LIBTHERMAL_PC)
all: fixdep
$(Q)$(MAKE) libs
clean:
$(call QUIET_CLEAN, libthermal) $(RM) $(LIBTHERMAL_A) \
*.o *~ *.a *.so *.so.$(VERSION) *.so.$(LIBTHERMAL_VERSION) .*.d .*.cmd LIBTHERMAL-CFLAGS $(LIBTHERMAL_PC)
$(LIBTHERMAL_PC):
$(QUIET_GEN)sed -e "s|@PREFIX@|$(prefix)|" \
-e "s|@LIBDIR@|$(libdir_SQ)|" \
-e "s|@VERSION@|$(VERSION)|" \
< libthermal.pc.template > $@
define do_install_mkdir
if [ ! -d '$(DESTDIR_SQ)$1' ]; then \
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \
fi
endef
define do_install
if [ ! -d '$(DESTDIR_SQ)$2' ]; then \
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \
fi; \
$(INSTALL) $1 $(if $3,-m $3,) '$(DESTDIR_SQ)$2'
endef
install_lib: libs
$(call QUIET_INSTALL, $(LIBTHERMAL_ALL)) \
$(call do_install_mkdir,$(libdir_SQ)); \
cp -fpR $(LIBTHERMAL_ALL) $(DESTDIR)$(libdir_SQ)
install_headers:
$(call QUIET_INSTALL, headers) \
$(call do_install,include/thermal.h,$(prefix)/include/thermal,644); \
install_pkgconfig: $(LIBTHERMAL_PC)
$(call QUIET_INSTALL, $(LIBTHERMAL_PC)) \
$(call do_install,$(LIBTHERMAL_PC),$(libdir_SQ)/pkgconfig,644)
install_doc:
$(Q)$(MAKE) -C Documentation install-man install-html install-examples
install: install_lib install_headers install_pkgconfig
FORCE:
.PHONY: all install clean FORCE

View File

@ -0,0 +1,349 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thermal.h>
#include "thermal_nl.h"
static struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {
/* Thermal zone */
[THERMAL_GENL_ATTR_TZ] = { .type = NLA_NESTED },
[THERMAL_GENL_ATTR_TZ_ID] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_TZ_TEMP] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_TZ_TRIP] = { .type = NLA_NESTED },
[THERMAL_GENL_ATTR_TZ_TRIP_ID] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_TZ_TRIP_TEMP] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_TZ_TRIP_TYPE] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_TZ_TRIP_HYST] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_TZ_MODE] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_TZ_NAME] = { .type = NLA_STRING },
/* Governor(s) */
[THERMAL_GENL_ATTR_TZ_GOV] = { .type = NLA_NESTED },
[THERMAL_GENL_ATTR_TZ_GOV_NAME] = { .type = NLA_STRING },
/* Cooling devices */
[THERMAL_GENL_ATTR_CDEV] = { .type = NLA_NESTED },
[THERMAL_GENL_ATTR_CDEV_ID] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_CDEV_CUR_STATE] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_CDEV_MAX_STATE] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_CDEV_NAME] = { .type = NLA_STRING },
};
static int parse_tz_get(struct genl_info *info, struct thermal_zone **tz)
{
struct nlattr *attr;
struct thermal_zone *__tz = NULL;
size_t size = 0;
int rem;
nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ], rem) {
if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_ID) {
size++;
__tz = realloc(__tz, sizeof(*__tz) * (size + 2));
if (!__tz)
return THERMAL_ERROR;
__tz[size - 1].id = nla_get_u32(attr);
}
if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_NAME)
nla_strlcpy(__tz[size - 1].name, attr,
THERMAL_NAME_LENGTH);
}
if (__tz)
__tz[size].id = -1;
*tz = __tz;
return THERMAL_SUCCESS;
}
static int parse_cdev_get(struct genl_info *info, struct thermal_cdev **cdev)
{
struct nlattr *attr;
struct thermal_cdev *__cdev = NULL;
size_t size = 0;
int rem;
nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_CDEV], rem) {
if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_ID) {
size++;
__cdev = realloc(__cdev, sizeof(*__cdev) * (size + 2));
if (!__cdev)
return THERMAL_ERROR;
__cdev[size - 1].id = nla_get_u32(attr);
}
if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_NAME) {
nla_strlcpy(__cdev[size - 1].name, attr,
THERMAL_NAME_LENGTH);
}
if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_CUR_STATE)
__cdev[size - 1].cur_state = nla_get_u32(attr);
if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_MAX_STATE)
__cdev[size - 1].max_state = nla_get_u32(attr);
}
if (__cdev)
__cdev[size].id = -1;
*cdev = __cdev;
return THERMAL_SUCCESS;
}
static int parse_tz_get_trip(struct genl_info *info, struct thermal_zone *tz)
{
struct nlattr *attr;
struct thermal_trip *__tt = NULL;
size_t size = 0;
int rem;
nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ_TRIP], rem) {
if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_ID) {
size++;
__tt = realloc(__tt, sizeof(*__tt) * (size + 2));
if (!__tt)
return THERMAL_ERROR;
__tt[size - 1].id = nla_get_u32(attr);
}
if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TYPE)
__tt[size - 1].type = nla_get_u32(attr);
if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TEMP)
__tt[size - 1].temp = nla_get_u32(attr);
if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_HYST)
__tt[size - 1].hyst = nla_get_u32(attr);
}
if (__tt)
__tt[size].id = -1;
tz->trip = __tt;
return THERMAL_SUCCESS;
}
static int parse_tz_get_temp(struct genl_info *info, struct thermal_zone *tz)
{
int id = -1;
if (info->attrs[THERMAL_GENL_ATTR_TZ_ID])
id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]);
if (tz->id != id)
return THERMAL_ERROR;
if (info->attrs[THERMAL_GENL_ATTR_TZ_TEMP])
tz->temp = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_TEMP]);
return THERMAL_SUCCESS;
}
static int parse_tz_get_gov(struct genl_info *info, struct thermal_zone *tz)
{
int id = -1;
if (info->attrs[THERMAL_GENL_ATTR_TZ_ID])
id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]);
if (tz->id != id)
return THERMAL_ERROR;
if (info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME]) {
nla_strlcpy(tz->governor,
info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME],
THERMAL_NAME_LENGTH);
}
return THERMAL_SUCCESS;
}
static int handle_netlink(struct nl_cache_ops *unused,
struct genl_cmd *cmd,
struct genl_info *info, void *arg)
{
int ret;
switch (cmd->c_id) {
case THERMAL_GENL_CMD_TZ_GET_ID:
ret = parse_tz_get(info, arg);
break;
case THERMAL_GENL_CMD_CDEV_GET:
ret = parse_cdev_get(info, arg);
break;
case THERMAL_GENL_CMD_TZ_GET_TEMP:
ret = parse_tz_get_temp(info, arg);
break;
case THERMAL_GENL_CMD_TZ_GET_TRIP:
ret = parse_tz_get_trip(info, arg);
break;
case THERMAL_GENL_CMD_TZ_GET_GOV:
ret = parse_tz_get_gov(info, arg);
break;
default:
return THERMAL_ERROR;
}
return ret;
}
static struct genl_cmd thermal_cmds[] = {
{
.c_id = THERMAL_GENL_CMD_TZ_GET_ID,
.c_name = (char *)"List thermal zones",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
{
.c_id = THERMAL_GENL_CMD_TZ_GET_GOV,
.c_name = (char *)"Get governor",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
{
.c_id = THERMAL_GENL_CMD_TZ_GET_TEMP,
.c_name = (char *)"Get thermal zone temperature",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
{
.c_id = THERMAL_GENL_CMD_TZ_GET_TRIP,
.c_name = (char *)"Get thermal zone trip points",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
{
.c_id = THERMAL_GENL_CMD_CDEV_GET,
.c_name = (char *)"Get cooling devices",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
};
static struct genl_ops thermal_cmd_ops = {
.o_name = (char *)"thermal",
.o_cmds = thermal_cmds,
.o_ncmds = ARRAY_SIZE(thermal_cmds),
};
static thermal_error_t thermal_genl_auto(struct thermal_handler *th, int id, int cmd,
int flags, void *arg)
{
struct nl_msg *msg;
void *hdr;
msg = nlmsg_alloc();
if (!msg)
return THERMAL_ERROR;
hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, thermal_cmd_ops.o_id,
0, flags, cmd, THERMAL_GENL_VERSION);
if (!hdr)
return THERMAL_ERROR;
if (id >= 0 && nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id))
return THERMAL_ERROR;
if (nl_send_msg(th->sk_cmd, th->cb_cmd, msg, genl_handle_msg, arg))
return THERMAL_ERROR;
nlmsg_free(msg);
return THERMAL_SUCCESS;
}
thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th, struct thermal_zone **tz)
{
return thermal_genl_auto(th, -1, THERMAL_GENL_CMD_TZ_GET_ID,
NLM_F_DUMP | NLM_F_ACK, tz);
}
thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th, struct thermal_cdev **tc)
{
return thermal_genl_auto(th, -1, THERMAL_GENL_CMD_CDEV_GET,
NLM_F_DUMP | NLM_F_ACK, tc);
}
thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th, struct thermal_zone *tz)
{
return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_TRIP,
0, tz);
}
thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th, struct thermal_zone *tz)
{
return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_GOV, 0, tz);
}
thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th, struct thermal_zone *tz)
{
return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_TEMP, 0, tz);
}
thermal_error_t thermal_cmd_exit(struct thermal_handler *th)
{
if (genl_unregister_family(&thermal_cmd_ops))
return THERMAL_ERROR;
nl_thermal_disconnect(th->sk_cmd, th->cb_cmd);
return THERMAL_SUCCESS;
}
thermal_error_t thermal_cmd_init(struct thermal_handler *th)
{
int ret;
int family;
if (nl_thermal_connect(&th->sk_cmd, &th->cb_cmd))
return THERMAL_ERROR;
ret = genl_register_family(&thermal_cmd_ops);
if (ret)
return THERMAL_ERROR;
ret = genl_ops_resolve(th->sk_cmd, &thermal_cmd_ops);
if (ret)
return THERMAL_ERROR;
family = genl_ctrl_resolve(th->sk_cmd, "nlctrl");
if (family != GENL_ID_CTRL)
return THERMAL_ERROR;
return THERMAL_SUCCESS;
}

164
tools/lib/thermal/events.c Normal file
View File

@ -0,0 +1,164 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <linux/netlink.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thermal.h>
#include "thermal_nl.h"
/*
* Optimization: fill this array to tell which event we do want to pay
* attention to. That happens at init time with the ops
* structure. Each ops will enable the event and the general handler
* will be able to discard the event if there is not ops associated
* with it.
*/
static int enabled_ops[__THERMAL_GENL_EVENT_MAX];
static int handle_thermal_event(struct nl_msg *n, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(n);
struct genlmsghdr *genlhdr = genlmsg_hdr(nlh);
struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1];
struct thermal_handler_param *thp = arg;
struct thermal_events_ops *ops = &thp->th->ops->events;
genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL);
arg = thp->arg;
/*
* This is an event we don't care of, bail out.
*/
if (!enabled_ops[genlhdr->cmd])
return THERMAL_SUCCESS;
switch (genlhdr->cmd) {
case THERMAL_GENL_EVENT_TZ_CREATE:
return ops->tz_create(nla_get_string(attrs[THERMAL_GENL_ATTR_TZ_NAME]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
case THERMAL_GENL_EVENT_TZ_DELETE:
return ops->tz_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
case THERMAL_GENL_EVENT_TZ_ENABLE:
return ops->tz_enable(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
case THERMAL_GENL_EVENT_TZ_DISABLE:
return ops->tz_disable(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
case THERMAL_GENL_EVENT_TZ_TRIP_CHANGE:
return ops->trip_change(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]), arg);
case THERMAL_GENL_EVENT_TZ_TRIP_ADD:
return ops->trip_add(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]), arg);
case THERMAL_GENL_EVENT_TZ_TRIP_DELETE:
return ops->trip_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), arg);
case THERMAL_GENL_EVENT_TZ_TRIP_UP:
return ops->trip_high(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg);
case THERMAL_GENL_EVENT_TZ_TRIP_DOWN:
return ops->trip_low(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg);
case THERMAL_GENL_EVENT_CDEV_ADD:
return ops->cdev_add(nla_get_string(attrs[THERMAL_GENL_ATTR_CDEV_NAME]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_MAX_STATE]), arg);
case THERMAL_GENL_EVENT_CDEV_DELETE:
return ops->cdev_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]), arg);
case THERMAL_GENL_EVENT_CDEV_STATE_UPDATE:
return ops->cdev_update(nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_CUR_STATE]), arg);
case THERMAL_GENL_EVENT_TZ_GOV_CHANGE:
return ops->gov_change(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_string(attrs[THERMAL_GENL_ATTR_GOV_NAME]), arg);
default:
return -1;
}
}
static void thermal_events_ops_init(struct thermal_events_ops *ops)
{
enabled_ops[THERMAL_GENL_EVENT_TZ_CREATE] = !!ops->tz_create;
enabled_ops[THERMAL_GENL_EVENT_TZ_DELETE] = !!ops->tz_delete;
enabled_ops[THERMAL_GENL_EVENT_TZ_DISABLE] = !!ops->tz_disable;
enabled_ops[THERMAL_GENL_EVENT_TZ_ENABLE] = !!ops->tz_enable;
enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_UP] = !!ops->trip_high;
enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_DOWN] = !!ops->trip_low;
enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_CHANGE] = !!ops->trip_change;
enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_ADD] = !!ops->trip_add;
enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_DELETE] = !!ops->trip_delete;
enabled_ops[THERMAL_GENL_EVENT_CDEV_ADD] = !!ops->cdev_add;
enabled_ops[THERMAL_GENL_EVENT_CDEV_DELETE] = !!ops->cdev_delete;
enabled_ops[THERMAL_GENL_EVENT_CDEV_STATE_UPDATE] = !!ops->cdev_update;
enabled_ops[THERMAL_GENL_EVENT_TZ_GOV_CHANGE] = !!ops->gov_change;
}
thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg)
{
struct thermal_handler_param thp = { .th = th, .arg = arg };
if (!th)
return THERMAL_ERROR;
if (nl_cb_set(th->cb_event, NL_CB_VALID, NL_CB_CUSTOM,
handle_thermal_event, &thp))
return THERMAL_ERROR;
return nl_recvmsgs(th->sk_event, th->cb_event);
}
int thermal_events_fd(struct thermal_handler *th)
{
if (!th)
return -1;
return nl_socket_get_fd(th->sk_event);
}
thermal_error_t thermal_events_exit(struct thermal_handler *th)
{
if (nl_unsubscribe_thermal(th->sk_event, th->cb_event,
THERMAL_GENL_EVENT_GROUP_NAME))
return THERMAL_ERROR;
nl_thermal_disconnect(th->sk_event, th->cb_event);
return THERMAL_SUCCESS;
}
thermal_error_t thermal_events_init(struct thermal_handler *th)
{
thermal_events_ops_init(&th->ops->events);
if (nl_thermal_connect(&th->sk_event, &th->cb_event))
return THERMAL_ERROR;
if (nl_subscribe_thermal(th->sk_event, th->cb_event,
THERMAL_GENL_EVENT_GROUP_NAME))
return THERMAL_ERROR;
return THERMAL_SUCCESS;
}

View File

@ -0,0 +1,142 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __LIBTHERMAL_H
#define __LIBTHERMAL_H
#include <linux/thermal.h>
#ifndef LIBTHERMAL_API
#define LIBTHERMAL_API __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct thermal_sampling_ops {
int (*tz_temp)(int tz_id, int temp, void *arg);
};
struct thermal_events_ops {
int (*tz_create)(const char *name, int tz_id, void *arg);
int (*tz_delete)(int tz_id, void *arg);
int (*tz_enable)(int tz_id, void *arg);
int (*tz_disable)(int tz_id, void *arg);
int (*trip_high)(int tz_id, int trip_id, int temp, void *arg);
int (*trip_low)(int tz_id, int trip_id, int temp, void *arg);
int (*trip_add)(int tz_id, int trip_id, int type, int temp, int hyst, void *arg);
int (*trip_change)(int tz_id, int trip_id, int type, int temp, int hyst, void *arg);
int (*trip_delete)(int tz_id, int trip_id, void *arg);
int (*cdev_add)(const char *name, int cdev_id, int max_state, void *arg);
int (*cdev_delete)(int cdev_id, void *arg);
int (*cdev_update)(int cdev_id, int cur_state, void *arg);
int (*gov_change)(int tz_id, const char *gov_name, void *arg);
};
struct thermal_ops {
struct thermal_sampling_ops sampling;
struct thermal_events_ops events;
};
struct thermal_trip {
int id;
int type;
int temp;
int hyst;
};
struct thermal_zone {
int id;
int temp;
char name[THERMAL_NAME_LENGTH];
char governor[THERMAL_NAME_LENGTH];
struct thermal_trip *trip;
};
struct thermal_cdev {
int id;
char name[THERMAL_NAME_LENGTH];
int max_state;
int min_state;
int cur_state;
};
typedef enum {
THERMAL_ERROR = -1,
THERMAL_SUCCESS = 0,
} thermal_error_t;
struct thermal_handler;
typedef int (*cb_tz_t)(struct thermal_zone *, void *);
typedef int (*cb_tt_t)(struct thermal_trip *, void *);
typedef int (*cb_tc_t)(struct thermal_cdev *, void *);
LIBTHERMAL_API int for_each_thermal_zone(struct thermal_zone *tz, cb_tz_t cb, void *arg);
LIBTHERMAL_API int for_each_thermal_trip(struct thermal_trip *tt, cb_tt_t cb, void *arg);
LIBTHERMAL_API int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg);
LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz,
const char *name);
LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_id(struct thermal_zone *tz, int id);
LIBTHERMAL_API struct thermal_zone *thermal_zone_discover(struct thermal_handler *th);
LIBTHERMAL_API struct thermal_handler *thermal_init(struct thermal_ops *ops);
LIBTHERMAL_API void thermal_exit(struct thermal_handler *th);
/*
* Netlink thermal events
*/
LIBTHERMAL_API thermal_error_t thermal_events_exit(struct thermal_handler *th);
LIBTHERMAL_API thermal_error_t thermal_events_init(struct thermal_handler *th);
LIBTHERMAL_API thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg);
LIBTHERMAL_API int thermal_events_fd(struct thermal_handler *th);
/*
* Netlink thermal commands
*/
LIBTHERMAL_API thermal_error_t thermal_cmd_exit(struct thermal_handler *th);
LIBTHERMAL_API thermal_error_t thermal_cmd_init(struct thermal_handler *th);
LIBTHERMAL_API thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th,
struct thermal_zone **tz);
LIBTHERMAL_API thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th,
struct thermal_cdev **tc);
LIBTHERMAL_API thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th,
struct thermal_zone *tz);
LIBTHERMAL_API thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th,
struct thermal_zone *tz);
LIBTHERMAL_API thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th,
struct thermal_zone *tz);
/*
* Netlink thermal samples
*/
LIBTHERMAL_API thermal_error_t thermal_sampling_exit(struct thermal_handler *th);
LIBTHERMAL_API thermal_error_t thermal_sampling_init(struct thermal_handler *th);
LIBTHERMAL_API thermal_error_t thermal_sampling_handle(struct thermal_handler *th, void *arg);
LIBTHERMAL_API int thermal_sampling_fd(struct thermal_handler *th);
#endif /* __LIBTHERMAL_H */
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,25 @@
LIBTHERMAL_0.0.1 {
global:
thermal_init;
for_each_thermal_zone;
for_each_thermal_trip;
for_each_thermal_cdev;
thermal_zone_find_by_name;
thermal_zone_find_by_id;
thermal_zone_discover;
thermal_init;
thermal_events_init;
thermal_events_handle;
thermal_events_fd;
thermal_cmd_init;
thermal_cmd_get_tz;
thermal_cmd_get_cdev;
thermal_cmd_get_trip;
thermal_cmd_get_governor;
thermal_cmd_get_temp;
thermal_sampling_init;
thermal_sampling_handle;
thermal_sampling_fd;
local:
*;
};

View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
prefix=@PREFIX@
libdir=@LIBDIR@
includedir=${prefix}/include
Name: libthermal
Description: thermal library
Requires: libnl-3.0 libnl-genl-3.0
Version: @VERSION@
Libs: -L${libdir} -lnl-genl-3 -lnl-3
Cflags: -I${includedir} -I{include}/libnl3

View File

@ -0,0 +1,75 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thermal.h>
#include "thermal_nl.h"
static int handle_thermal_sample(struct nl_msg *n, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(n);
struct genlmsghdr *genlhdr = genlmsg_hdr(nlh);
struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1];
struct thermal_handler_param *thp = arg;
struct thermal_handler *th = thp->th;
genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL);
switch (genlhdr->cmd) {
case THERMAL_GENL_SAMPLING_TEMP:
return th->ops->sampling.tz_temp(
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg);
default:
return THERMAL_ERROR;
}
}
thermal_error_t thermal_sampling_handle(struct thermal_handler *th, void *arg)
{
struct thermal_handler_param thp = { .th = th, .arg = arg };
if (!th)
return THERMAL_ERROR;
if (nl_cb_set(th->cb_sampling, NL_CB_VALID, NL_CB_CUSTOM,
handle_thermal_sample, &thp))
return THERMAL_ERROR;
return nl_recvmsgs(th->sk_sampling, th->cb_sampling);
}
int thermal_sampling_fd(struct thermal_handler *th)
{
if (!th)
return -1;
return nl_socket_get_fd(th->sk_sampling);
}
thermal_error_t thermal_sampling_exit(struct thermal_handler *th)
{
if (nl_unsubscribe_thermal(th->sk_sampling, th->cb_sampling,
THERMAL_GENL_EVENT_GROUP_NAME))
return THERMAL_ERROR;
nl_thermal_disconnect(th->sk_sampling, th->cb_sampling);
return THERMAL_SUCCESS;
}
thermal_error_t thermal_sampling_init(struct thermal_handler *th)
{
if (nl_thermal_connect(&th->sk_sampling, &th->cb_sampling))
return THERMAL_ERROR;
if (nl_subscribe_thermal(th->sk_sampling, th->cb_sampling,
THERMAL_GENL_SAMPLING_GROUP_NAME))
return THERMAL_ERROR;
return THERMAL_SUCCESS;
}

135
tools/lib/thermal/thermal.c Normal file
View File

@ -0,0 +1,135 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdio.h>
#include <thermal.h>
#include "thermal_nl.h"
int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg)
{
int i, ret = 0;
if (!cdev)
return 0;
for (i = 0; cdev[i].id != -1; i++)
ret |= cb(&cdev[i], arg);
return ret;
}
int for_each_thermal_trip(struct thermal_trip *tt, cb_tt_t cb, void *arg)
{
int i, ret = 0;
if (!tt)
return 0;
for (i = 0; tt[i].id != -1; i++)
ret |= cb(&tt[i], arg);
return ret;
}
int for_each_thermal_zone(struct thermal_zone *tz, cb_tz_t cb, void *arg)
{
int i, ret = 0;
if (!tz)
return 0;
for (i = 0; tz[i].id != -1; i++)
ret |= cb(&tz[i], arg);
return ret;
}
struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz,
const char *name)
{
int i;
if (!tz || !name)
return NULL;
for (i = 0; tz[i].id != -1; i++) {
if (!strcmp(tz[i].name, name))
return &tz[i];
}
return NULL;
}
struct thermal_zone *thermal_zone_find_by_id(struct thermal_zone *tz, int id)
{
int i;
if (!tz || id < 0)
return NULL;
for (i = 0; tz[i].id != -1; i++) {
if (tz[i].id == id)
return &tz[i];
}
return NULL;
}
static int __thermal_zone_discover(struct thermal_zone *tz, void *th)
{
if (thermal_cmd_get_trip(th, tz) < 0)
return -1;
if (thermal_cmd_get_governor(th, tz))
return -1;
return 0;
}
struct thermal_zone *thermal_zone_discover(struct thermal_handler *th)
{
struct thermal_zone *tz;
if (thermal_cmd_get_tz(th, &tz) < 0)
return NULL;
if (for_each_thermal_zone(tz, __thermal_zone_discover, th))
return NULL;
return tz;
}
void thermal_exit(struct thermal_handler *th)
{
thermal_cmd_exit(th);
thermal_events_exit(th);
thermal_sampling_exit(th);
free(th);
}
struct thermal_handler *thermal_init(struct thermal_ops *ops)
{
struct thermal_handler *th;
th = malloc(sizeof(*th));
if (!th)
return NULL;
th->ops = ops;
if (thermal_events_init(th))
goto out_free;
if (thermal_sampling_init(th))
goto out_free;
if (thermal_cmd_init(th))
goto out_free;
return th;
out_free:
free(th);
return NULL;
}

View File

@ -0,0 +1,215 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thermal.h>
#include "thermal_nl.h"
struct handler_args {
const char *group;
int id;
};
static __thread int err;
static __thread int done;
static int nl_seq_check_handler(struct nl_msg *msg, void *arg)
{
return NL_OK;
}
static int nl_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *nl_err,
void *arg)
{
int *ret = arg;
if (ret)
*ret = nl_err->error;
return NL_STOP;
}
static int nl_finish_handler(struct nl_msg *msg, void *arg)
{
int *ret = arg;
if (ret)
*ret = 1;
return NL_OK;
}
static int nl_ack_handler(struct nl_msg *msg, void *arg)
{
int *ret = arg;
if (ret)
*ret = 1;
return NL_OK;
}
int nl_send_msg(struct nl_sock *sock, struct nl_cb *cb, struct nl_msg *msg,
int (*rx_handler)(struct nl_msg *, void *), void *data)
{
if (!rx_handler)
return THERMAL_ERROR;
err = nl_send_auto_complete(sock, msg);
if (err < 0)
return err;
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, rx_handler, data);
err = done = 0;
while (err == 0 && done == 0)
nl_recvmsgs(sock, cb);
return err;
}
static int nl_family_handler(struct nl_msg *msg, void *arg)
{
struct handler_args *grp = arg;
struct nlattr *tb[CTRL_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *mcgrp;
int rem_mcgrp;
nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[CTRL_ATTR_MCAST_GROUPS])
return THERMAL_ERROR;
nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
nla_data(mcgrp), nla_len(mcgrp), NULL);
if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
!tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
continue;
if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
grp->group,
nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
continue;
grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
break;
}
return THERMAL_SUCCESS;
}
static int nl_get_multicast_id(struct nl_sock *sock, struct nl_cb *cb,
const char *family, const char *group)
{
struct nl_msg *msg;
int ret = 0, ctrlid;
struct handler_args grp = {
.group = group,
.id = -ENOENT,
};
msg = nlmsg_alloc();
if (!msg)
return THERMAL_ERROR;
ctrlid = genl_ctrl_resolve(sock, "nlctrl");
genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, family);
ret = nl_send_msg(sock, cb, msg, nl_family_handler, &grp);
if (ret)
goto nla_put_failure;
ret = grp.id;
nla_put_failure:
nlmsg_free(msg);
return ret;
}
int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb)
{
struct nl_cb *cb;
struct nl_sock *sock;
cb = nl_cb_alloc(NL_CB_DEFAULT);
if (!cb)
return THERMAL_ERROR;
sock = nl_socket_alloc();
if (!sock)
goto out_cb_free;
if (genl_connect(sock))
goto out_socket_free;
if (nl_cb_err(cb, NL_CB_CUSTOM, nl_error_handler, &err) ||
nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl_finish_handler, &done) ||
nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_ack_handler, &done) ||
nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check_handler, &done))
return THERMAL_ERROR;
*nl_sock = sock;
*nl_cb = cb;
return THERMAL_SUCCESS;
out_socket_free:
nl_socket_free(sock);
out_cb_free:
nl_cb_put(cb);
return THERMAL_ERROR;
}
void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb)
{
nl_close(nl_sock);
nl_socket_free(nl_sock);
nl_cb_put(nl_cb);
}
int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb,
const char *group)
{
int mcid;
mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME,
group);
if (mcid < 0)
return THERMAL_ERROR;
if (nl_socket_drop_membership(nl_sock, mcid))
return THERMAL_ERROR;
return THERMAL_SUCCESS;
}
int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb,
const char *group)
{
int mcid;
mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME,
group);
if (mcid < 0)
return THERMAL_ERROR;
if (nl_socket_add_membership(nl_sock, mcid))
return THERMAL_ERROR;
return THERMAL_SUCCESS;
}

View File

@ -0,0 +1,46 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_H
#define __THERMAL_H
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/mngt.h>
#include <netlink/genl/ctrl.h>
struct thermal_handler {
int done;
int error;
struct thermal_ops *ops;
struct nl_msg *msg;
struct nl_sock *sk_event;
struct nl_sock *sk_sampling;
struct nl_sock *sk_cmd;
struct nl_cb *cb_cmd;
struct nl_cb *cb_event;
struct nl_cb *cb_sampling;
};
struct thermal_handler_param {
struct thermal_handler *th;
void *arg;
};
/*
* Low level netlink
*/
extern int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb,
const char *group);
extern int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb,
const char *group);
extern int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb);
extern void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb);
extern int nl_send_msg(struct nl_sock *sock, struct nl_cb *nl_cb, struct nl_msg *msg,
int (*rx_handler)(struct nl_msg *, void *),
void *data);
#endif /* __THERMAL_H */

3
tools/thermal/lib/Build Normal file
View File

@ -0,0 +1,3 @@
libthermal_tools-y += mainloop.o
libthermal_tools-y += log.o
libthermal_tools-y += uptimeofday.o

158
tools/thermal/lib/Makefile Normal file
View File

@ -0,0 +1,158 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
# Most of this file is copied from tools/lib/perf/Makefile
LIBTHERMAL_TOOLS_VERSION = 0
LIBTHERMAL_TOOLS_PATCHLEVEL = 0
LIBTHERMAL_TOOLS_EXTRAVERSION = 1
MAKEFLAGS += --no-print-directory
ifeq ($(srctree),)
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
# $(info Determined 'srctree' to be $(srctree))
endif
INSTALL = install
# Use DESTDIR for installing into a different root directory.
# This is useful for building a package. The program will be
# installed in this directory as if it was the root directory.
# Then the build tool can move it later.
DESTDIR ?=
DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))'
include $(srctree)/tools/scripts/Makefile.include
include $(srctree)/tools/scripts/Makefile.arch
ifeq ($(LP64), 1)
libdir_relative = lib64
else
libdir_relative = lib
endif
prefix ?=
libdir = $(prefix)/$(libdir_relative)
# Shell quotes
libdir_SQ = $(subst ','\'',$(libdir))
libdir_relative_SQ = $(subst ','\'',$(libdir_relative))
ifeq ("$(origin V)", "command line")
VERBOSE = $(V)
endif
ifndef VERBOSE
VERBOSE = 0
endif
ifeq ($(VERBOSE),1)
Q =
else
Q = @
endif
# Set compile option CFLAGS
ifdef EXTRA_CFLAGS
CFLAGS := $(EXTRA_CFLAGS)
else
CFLAGS := -g -Wall
endif
INCLUDES = \
-I/usr/include/libnl3 \
-I$(srctree)/tools/lib/thermal/include \
-I$(srctree)/tools/lib/ \
-I$(srctree)/tools/include \
-I$(srctree)/tools/arch/$(SRCARCH)/include/ \
-I$(srctree)/tools/arch/$(SRCARCH)/include/uapi \
-I$(srctree)/tools/include/uapi
# Append required CFLAGS
override CFLAGS += $(EXTRA_WARNINGS)
override CFLAGS += -Werror -Wall
override CFLAGS += -fPIC
override CFLAGS += $(INCLUDES)
override CFGLAS += -Wl,-L.
override CFGLAS += -Wl,-lthermal
all:
export srctree OUTPUT CC LD CFLAGS V
export DESTDIR DESTDIR_SQ
include $(srctree)/tools/build/Makefile.include
PATCHLEVEL = $(LIBTHERMAL_TOOLS_PATCHLEVEL)
EXTRAVERSION = $(LIBTHERMAL_TOOLS_EXTRAVERSION)
VERSION = $(LIBTHERMAL_TOOLS_VERSION).$(LIBTHERMAL_TOOLS_PATCHLEVEL).$(LIBTHERMAL_TOOLS_EXTRAVERSION)
LIBTHERMAL_TOOLS_SO := $(OUTPUT)libthermal_tools.so.$(VERSION)
LIBTHERMAL_TOOLS_A := $(OUTPUT)libthermal_tools.a
LIBTHERMAL_TOOLS_IN := $(OUTPUT)libthermal_tools-in.o
LIBTHERMAL_TOOLS_PC := $(OUTPUT)libthermal_tools.pc
LIBTHERMAL_TOOLS_ALL := $(LIBTHERMAL_TOOLS_A) $(OUTPUT)libthermal_tools.so*
$(LIBTHERMAL_TOOLS_IN): FORCE
$(Q)$(MAKE) $(build)=libthermal_tools
$(LIBTHERMAL_TOOLS_A): $(LIBTHERMAL_TOOLS_IN)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIBTHERMAL_TOOLS_IN)
$(LIBTHERMAL_TOOLS_SO): $(LIBTHERMAL_TOOLS_IN)
$(QUIET_LINK)$(CC) --shared -Wl,-soname,libthermal_tools.so $^ -o $@
@ln -sf $(@F) $(OUTPUT)libthermal_tools.so
@ln -sf $(@F) $(OUTPUT)libthermal_tools.so.$(LIBTHERMAL_TOOLS_VERSION)
libs: $(LIBTHERMAL_TOOLS_A) $(LIBTHERMAL_TOOLS_SO) $(LIBTHERMAL_TOOLS_PC)
all: fixdep
$(Q)$(MAKE) libs
clean:
$(call QUIET_CLEAN, libthermal_tools) $(RM) $(LIBTHERMAL_TOOLS_A) \
*.o *~ *.a *.so *.so.$(VERSION) *.so.$(LIBTHERMAL_TOOLS_VERSION) .*.d .*.cmd LIBTHERMAL_TOOLS-CFLAGS $(LIBTHERMAL_TOOLS_PC)
$(LIBTHERMAL_TOOLS_PC):
$(QUIET_GEN)sed -e "s|@PREFIX@|$(prefix)|" \
-e "s|@LIBDIR@|$(libdir_SQ)|" \
-e "s|@VERSION@|$(VERSION)|" \
< libthermal_tools.pc.template > $@
define do_install_mkdir
if [ ! -d '$(DESTDIR_SQ)$1' ]; then \
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \
fi
endef
define do_install
if [ ! -d '$(DESTDIR_SQ)$2' ]; then \
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \
fi; \
$(INSTALL) $1 $(if $3,-m $3,) '$(DESTDIR_SQ)$2'
endef
install_lib: libs
$(call QUIET_INSTALL, $(LIBTHERMAL_TOOLS_ALL)) \
$(call do_install_mkdir,$(libdir_SQ)); \
cp -fpR $(LIBTHERMAL_TOOLS_ALL) $(DESTDIR)$(libdir_SQ)
install_headers:
$(call QUIET_INSTALL, headers) \
$(call do_install,include/thermal.h,$(prefix)/include/thermal,644); \
install_pkgconfig: $(LIBTHERMAL_TOOLS_PC)
$(call QUIET_INSTALL, $(LIBTHERMAL_TOOLS_PC)) \
$(call do_install,$(LIBTHERMAL_TOOLS_PC),$(libdir_SQ)/pkgconfig,644)
install_doc:
$(Q)$(MAKE) -C Documentation install-man install-html install-examples
#install: install_lib install_headers install_pkgconfig install_doc
install: install_lib install_headers install_pkgconfig
FORCE:
.PHONY: all install clean FORCE

View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
prefix=@PREFIX@
libdir=@LIBDIR@
includedir=${prefix}/include
Name: libthermal
Description: thermal library
Requires: libnl-3.0 libnl-genl-3.0
Version: @VERSION@
Libs: -L${libdir} -lnl-genl-3 -lnl-3
Cflags: -I${includedir} -I{include}/libnl3

77
tools/thermal/lib/log.c Normal file
View File

@ -0,0 +1,77 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include "log.h"
static const char *__ident = "unknown";
static int __options;
static const char * const loglvl[] = {
[LOG_DEBUG] = "DEBUG",
[LOG_INFO] = "INFO",
[LOG_NOTICE] = "NOTICE",
[LOG_WARNING] = "WARN",
[LOG_ERR] = "ERROR",
[LOG_CRIT] = "CRITICAL",
[LOG_ALERT] = "ALERT",
[LOG_EMERG] = "EMERG",
};
int log_str2level(const char *lvl)
{
int i;
for (i = 0; i < sizeof(loglvl) / sizeof(loglvl[LOG_DEBUG]); i++)
if (!strcmp(lvl, loglvl[i]))
return i;
return LOG_DEBUG;
}
extern void logit(int level, const char *format, ...)
{
va_list args;
va_start(args, format);
if (__options & TO_SYSLOG)
vsyslog(level, format, args);
if (__options & TO_STDERR)
vfprintf(stderr, format, args);
if (__options & TO_STDOUT)
vfprintf(stdout, format, args);
va_end(args);
}
int log_init(int level, const char *ident, int options)
{
if (!options)
return -1;
if (level > LOG_DEBUG)
return -1;
if (!ident)
return -1;
__ident = ident;
__options = options;
if (options & TO_SYSLOG) {
openlog(__ident, options | LOG_NDELAY, LOG_USER);
setlogmask(LOG_UPTO(level));
}
return 0;
}
void log_exit(void)
{
closelog();
}

31
tools/thermal/lib/log.h Normal file
View File

@ -0,0 +1,31 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_TOOLS_LOG_H
#define __THERMAL_TOOLS_LOG_H
#include <syslog.h>
#ifndef __maybe_unused
#define __maybe_unused __attribute__((__unused__))
#endif
#define TO_SYSLOG 0x1
#define TO_STDOUT 0x2
#define TO_STDERR 0x4
extern void logit(int level, const char *format, ...);
#define DEBUG(fmt, ...) logit(LOG_DEBUG, "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
#define INFO(fmt, ...) logit(LOG_INFO, fmt, ##__VA_ARGS__)
#define NOTICE(fmt, ...) logit(LOG_NOTICE, fmt, ##__VA_ARGS__)
#define WARN(fmt, ...) logit(LOG_WARNING, fmt, ##__VA_ARGS__)
#define ERROR(fmt, ...) logit(LOG_ERR, fmt, ##__VA_ARGS__)
#define CRITICAL(fmt, ...) logit(LOG_CRIT, fmt, ##__VA_ARGS__)
#define ALERT(fmt, ...) logit(LOG_ALERT, fmt, ##__VA_ARGS__)
#define EMERG(fmt, ...) logit(LOG_EMERG, fmt, ##__VA_ARGS__)
int log_init(int level, const char *ident, int options);
int log_str2level(const char *lvl);
void log_exit(void);
#endif

View File

@ -0,0 +1,120 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/epoll.h>
#include "mainloop.h"
#include "log.h"
static int epfd = -1;
static unsigned short nrhandler;
static sig_atomic_t exit_mainloop;
struct mainloop_data {
mainloop_callback_t cb;
void *data;
int fd;
};
static struct mainloop_data **mds;
#define MAX_EVENTS 10
int mainloop(unsigned int timeout)
{
int i, nfds;
struct epoll_event events[MAX_EVENTS];
struct mainloop_data *md;
if (epfd < 0)
return -1;
for (;;) {
nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
if (exit_mainloop || !nfds)
return 0;
if (nfds < 0) {
if (errno == EINTR)
continue;
return -1;
}
for (i = 0; i < nfds; i++) {
md = events[i].data.ptr;
if (md->cb(md->fd, md->data) > 0)
return 0;
}
}
}
int mainloop_add(int fd, mainloop_callback_t cb, void *data)
{
struct epoll_event ev = {
.events = EPOLLIN,
};
struct mainloop_data *md;
if (fd >= nrhandler) {
mds = realloc(mds, sizeof(*mds) * (fd + 1));
if (!mds)
return -1;
nrhandler = fd + 1;
}
md = malloc(sizeof(*md));
if (!md)
return -1;
md->data = data;
md->cb = cb;
md->fd = fd;
mds[fd] = md;
ev.data.ptr = md;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
free(md);
return -1;
}
return 0;
}
int mainloop_del(int fd)
{
if (fd >= nrhandler)
return -1;
if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) < 0)
return -1;
free(mds[fd]);
return 0;
}
int mainloop_init(void)
{
epfd = epoll_create(2);
if (epfd < 0)
return -1;
return 0;
}
void mainloop_exit(void)
{
exit_mainloop = 1;
}
void mainloop_fini(void)
{
close(epfd);
}

View File

@ -0,0 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_TOOLS_MAINLOOP_H
#define __THERMAL_TOOLS_MAINLOOP_H
typedef int (*mainloop_callback_t)(int fd, void *data);
extern int mainloop(unsigned int timeout);
extern int mainloop_add(int fd, mainloop_callback_t cb, void *data);
extern int mainloop_del(int fd);
extern void mainloop_exit(void);
extern int mainloop_init(void);
extern void mainloop_fini(void);
#endif

View File

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_TOOLS
#define __THERMAL_TOOLS
#include "log.h"
#include "mainloop.h"
#include "uptimeofday.h"
#endif

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdio.h>
#include <sys/time.h>
#include <linux/sysinfo.h>
#include "thermal-tools.h"
static unsigned long __offset;
static struct timeval __tv;
int uptimeofday_init(void)
{
struct sysinfo info;
if (sysinfo(&info))
return -1;
gettimeofday(&__tv, NULL);
__offset = __tv.tv_sec - info.uptime;
return 0;
}
unsigned long getuptimeofday_ms(void)
{
gettimeofday(&__tv, NULL);
return ((__tv.tv_sec - __offset) * 1000) + (__tv.tv_usec / 1000);
}
struct timespec msec_to_timespec(int msec)
{
struct timespec tv = {
.tv_sec = (msec / 1000),
.tv_nsec = (msec % 1000) * 1000000,
};
return tv;
}

View File

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_TOOLS_UPTIMEOFDAY_H
#define __THERMAL_TOOLS_UPTIMEOFDAY_H
#include <sys/sysinfo.h>
#include <sys/time.h>
int uptimeofday_init(void);
unsigned long getuptimeofday_ms(void);
struct timespec msec_to_timespec(int msec);
#endif

View File

@ -0,0 +1 @@
thermal-engine-y += thermal-engine.o

View File

@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for thermal tools
ifeq ($(srctree),)
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
# $(info Determined 'srctree' to be $(srctree))
endif
CFLAGS = -Wall -Wextra
CFLAGS += -I$(srctree)/tools/thermal/lib
CFLAGS += -I$(srctree)/tools/lib/thermal/include
LDFLAGS = -L$(srctree)/tools/thermal/lib
LDFLAGS += -L$(srctree)/tools/lib/thermal
LDFLAGS += -lthermal_tools
LDFLAGS += -lthermal
LDFLAGS += -lconfig
LDFLAGS += -lnl-genl-3 -lnl-3
VERSION = 0.0.1
all: thermal-engine
%: %.c
$(CC) $(CFLAGS) -D VERSION=\"$(VERSION)\" -o $@ $^ $(LDFLAGS)
clean:
$(RM) thermal-engine

View File

@ -0,0 +1,341 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Thermal monitoring tool based on the thermal netlink events.
*
* Copyright (C) 2022 Linaro Ltd.
*
* Author: Daniel Lezcano <daniel.lezcano@kernel.org>
*/
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <thermal.h>
#include "thermal-tools.h"
struct options {
int loglevel;
int logopt;
int interactive;
int daemonize;
};
struct thermal_data {
struct thermal_zone *tz;
struct thermal_handler *th;
};
static int show_trip(struct thermal_trip *tt, __maybe_unused void *arg)
{
INFO("trip id=%d, type=%d, temp=%d, hyst=%d\n",
tt->id, tt->type, tt->temp, tt->hyst);
return 0;
}
static int show_temp(struct thermal_zone *tz, __maybe_unused void *arg)
{
thermal_cmd_get_temp(arg, tz);
INFO("temperature: %d\n", tz->temp);
return 0;
}
static int show_governor(struct thermal_zone *tz, __maybe_unused void *arg)
{
thermal_cmd_get_governor(arg, tz);
INFO("governor: '%s'\n", tz->governor);
return 0;
}
static int show_tz(struct thermal_zone *tz, __maybe_unused void *arg)
{
INFO("thermal zone '%s', id=%d\n", tz->name, tz->id);
for_each_thermal_trip(tz->trip, show_trip, NULL);
show_temp(tz, arg);
show_governor(tz, arg);
return 0;
}
static int tz_create(const char *name, int tz_id, __maybe_unused void *arg)
{
INFO("Thermal zone '%s'/%d created\n", name, tz_id);
return 0;
}
static int tz_delete(int tz_id, __maybe_unused void *arg)
{
INFO("Thermal zone %d deleted\n", tz_id);
return 0;
}
static int tz_disable(int tz_id, void *arg)
{
struct thermal_data *td = arg;
struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
INFO("Thermal zone %d ('%s') disabled\n", tz_id, tz->name);
return 0;
}
static int tz_enable(int tz_id, void *arg)
{
struct thermal_data *td = arg;
struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
INFO("Thermal zone %d ('%s') enabled\n", tz_id, tz->name);
return 0;
}
static int trip_high(int tz_id, int trip_id, int temp, void *arg)
{
struct thermal_data *td = arg;
struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
INFO("Thermal zone %d ('%s'): trip point %d crossed way up with %d °C\n",
tz_id, tz->name, trip_id, temp);
return 0;
}
static int trip_low(int tz_id, int trip_id, int temp, void *arg)
{
struct thermal_data *td = arg;
struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
INFO("Thermal zone %d ('%s'): trip point %d crossed way down with %d °C\n",
tz_id, tz->name, trip_id, temp);
return 0;
}
static int trip_add(int tz_id, int trip_id, int type, int temp, int hyst, __maybe_unused void *arg)
{
INFO("Trip point added %d: id=%d, type=%d, temp=%d, hyst=%d\n",
tz_id, trip_id, type, temp, hyst);
return 0;
}
static int trip_delete(int tz_id, int trip_id, __maybe_unused void *arg)
{
INFO("Trip point deleted %d: id=%d\n", tz_id, trip_id);
return 0;
}
static int trip_change(int tz_id, int trip_id, int type, int temp,
int hyst, __maybe_unused void *arg)
{
struct thermal_data *td = arg;
struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
INFO("Trip point changed %d: id=%d, type=%d, temp=%d, hyst=%d\n",
tz_id, trip_id, type, temp, hyst);
tz->trip[trip_id].type = type;
tz->trip[trip_id].temp = temp;
tz->trip[trip_id].hyst = hyst;
return 0;
}
static int cdev_add(const char *name, int cdev_id, int max_state, __maybe_unused void *arg)
{
INFO("Cooling device '%s'/%d (max state=%d) added\n", name, cdev_id, max_state);
return 0;
}
static int cdev_delete(int cdev_id, __maybe_unused void *arg)
{
INFO("Cooling device %d deleted", cdev_id);
return 0;
}
static int cdev_update(int cdev_id, int cur_state, __maybe_unused void *arg)
{
INFO("cdev:%d state:%d\n", cdev_id, cur_state);
return 0;
}
static int gov_change(int tz_id, const char *name, __maybe_unused void *arg)
{
struct thermal_data *td = arg;
struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
INFO("%s: governor changed %s -> %s\n", tz->name, tz->governor, name);
strcpy(tz->governor, name);
return 0;
}
static struct thermal_ops ops = {
.events.tz_create = tz_create,
.events.tz_delete = tz_delete,
.events.tz_disable = tz_disable,
.events.tz_enable = tz_enable,
.events.trip_high = trip_high,
.events.trip_low = trip_low,
.events.trip_add = trip_add,
.events.trip_delete = trip_delete,
.events.trip_change = trip_change,
.events.cdev_add = cdev_add,
.events.cdev_delete = cdev_delete,
.events.cdev_update = cdev_update,
.events.gov_change = gov_change
};
static int thermal_event(__maybe_unused int fd, __maybe_unused void *arg)
{
struct thermal_data *td = arg;
return thermal_events_handle(td->th, td);
}
static void usage(const char *cmd)
{
printf("%s : A thermal monitoring engine based on notifications\n", cmd);
printf("Usage: %s [options]\n", cmd);
printf("\t-h, --help\t\tthis help\n");
printf("\t-d, --daemonize\n");
printf("\t-l <level>, --loglevel <level>\tlog level: ");
printf("DEBUG, INFO, NOTICE, WARN, ERROR\n");
printf("\t-s, --syslog\t\toutput to syslog\n");
printf("\n");
exit(0);
}
static int options_init(int argc, char *argv[], struct options *options)
{
int opt;
struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "daemonize", no_argument, NULL, 'd' },
{ "syslog", no_argument, NULL, 's' },
{ "loglevel", required_argument, NULL, 'l' },
{ 0, 0, 0, 0 }
};
while (1) {
int optindex = 0;
opt = getopt_long(argc, argv, "l:dhs", long_options, &optindex);
if (opt == -1)
break;
switch (opt) {
case 'l':
options->loglevel = log_str2level(optarg);
break;
case 'd':
options->daemonize = 1;
break;
case 's':
options->logopt = TO_SYSLOG;
break;
case 'h':
usage(basename(argv[0]));
break;
default: /* '?' */
return -1;
}
}
return 0;
}
enum {
THERMAL_ENGINE_SUCCESS = 0,
THERMAL_ENGINE_OPTION_ERROR,
THERMAL_ENGINE_DAEMON_ERROR,
THERMAL_ENGINE_LOG_ERROR,
THERMAL_ENGINE_THERMAL_ERROR,
THERMAL_ENGINE_MAINLOOP_ERROR,
};
int main(int argc, char *argv[])
{
struct thermal_data td;
struct options options = {
.loglevel = LOG_INFO,
.logopt = TO_STDOUT,
};
if (options_init(argc, argv, &options)) {
ERROR("Usage: %s --help\n", argv[0]);
return THERMAL_ENGINE_OPTION_ERROR;
}
if (options.daemonize && daemon(0, 0)) {
ERROR("Failed to daemonize: %p\n");
return THERMAL_ENGINE_DAEMON_ERROR;
}
if (log_init(options.loglevel, basename(argv[0]), options.logopt)) {
ERROR("Failed to initialize logging facility\n");
return THERMAL_ENGINE_LOG_ERROR;
}
td.th = thermal_init(&ops);
if (!td.th) {
ERROR("Failed to initialize the thermal library\n");
return THERMAL_ENGINE_THERMAL_ERROR;
}
td.tz = thermal_zone_discover(td.th);
if (!td.tz) {
ERROR("No thermal zone available\n");
return THERMAL_ENGINE_THERMAL_ERROR;
}
for_each_thermal_zone(td.tz, show_tz, td.th);
if (mainloop_init()) {
ERROR("Failed to initialize the mainloop\n");
return THERMAL_ENGINE_MAINLOOP_ERROR;
}
if (mainloop_add(thermal_events_fd(td.th), thermal_event, &td)) {
ERROR("Failed to setup the mainloop\n");
return THERMAL_ENGINE_MAINLOOP_ERROR;
}
INFO("Waiting for thermal events ...\n");
if (mainloop(-1)) {
ERROR("Mainloop failed\n");
return THERMAL_ENGINE_MAINLOOP_ERROR;
}
return THERMAL_ENGINE_SUCCESS;
}

View File

@ -0,0 +1 @@
thermometer-y += thermometer.o

View File

@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for cgroup tools
ifeq ($(srctree),)
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
# $(info Determined 'srctree' to be $(srctree))
endif
CFLAGS = -Wall -Wextra
CFLAGS += -I$(srctree)/tools/thermal/lib
LDFLAGS = -L$(srctree)/tools/thermal/lib
LDFLAGS += -lthermal_tools
LDFLAGS += -lconfig
VERSION = 0.0.1
TARGET=thermometer
all: $(TARGET)
%: %.c
$(CC) $(CFLAGS) -D VERSION=\"$(VERSION)\" -o $@ $^ $(LDFLAGS)
clean:
$(RM) $(TARGET)

View File

@ -0,0 +1,92 @@
.TH THERMOMETER 8
# SPDX-License-Identifier: GPL-2.0
.SH NAME
\fBthermometer\fP - A thermal profiling tool
.SH SYNOPSIS
.ft B
.B thermometer
.RB [ options ]
.RB [ command ]
.br
.SH DESCRIPTION
\fBthermometer \fP captures the thermal zones temperature at a
specified sampling period. It is optimized to reduce as much as
possible the overhead while doing the temperature acquisition in order
to prevent disrupting the running application we may want to profile.
This low overhead also allows a high rate sampling for the temperature
which could be necessary to spot overshots and undershots.
If no configuration file is specified, then all the thermal zones will
be monitored at 4Hz, so every 250ms. A configuration file specifies
the thermal zone names and the desired sampling period. A thermal zone
name can be a regular expression to specify a group of thermal zone.
The sampling of the different thermal zones will be written into
separate files with the thermal zone name. It is possible to specify a
postfix to identify them for example for a specific scenario. The
output directory can be specified in addition.
Without any parameters, \fBthermometer \fP captures all the thermal
zone temperatures every 250ms and write to the current directory the
captured files postfixed with the current date.
If a running \fBduration\fP is specified or a \fBcommand\fP, the
capture ends at the end of the duration if the command did not
finished before. The \fBduration\fP can be specified alone as well as
the \fBcommand\fP. If none is specified, the capture will continue
indefinitively until interrupted by \fBSIGINT\fP or \fBSIGQUIT\fP.
.PP
.SS Options
.PP
The \fB-h, --help\fP option shows a short usage help
.PP
The \fB-o <dir>, --output <dir>\fP option defines the output directory to put the
sampling files
.PP
The \fB-c <config>, --config <config>\fP option specifies the configuration file to use
.PP
The \fB-d <seconds>, --duration <seconds>\fP option specifies the duration of the capture
.PP
The \fB-l <loglevel>, --loglevel <loglevel>\fP option sets the loglevel [DEBUG,INFO,NOTICE,WARN,ERROR]
.PP
The \fB-p <string>, --postfix <string>\fP option appends \fBstring\fP at the end of the capture filenames
.PP
The \fB-s, --syslog\fP option sets the output to syslog, default is \fBstdout\fP
.PP
The \fB-w, --overwrite\fP overwrites the output files if they exist
.PP
.PP
.SS "Exit status:"
.TP
0
if OK,
.TP
1
Error with the options specified as parameters
.TP
2
Error when configuring the logging facility
.TP
3
Error when configuring the time
.TP
4
Error in the initialization routine
.TP
5
Error during the runtime
.SH Capture file format
Every file contains two columns. The first one is the uptime timestamp
in order to find a point in time since the system started up if there
is any thermal event. The second one is the temperature in milli
degree. The first line contains the label of each column.
.SH AUTHOR
Daniel Lezcano <daniel.lezcano@kernel.org>

View File

@ -0,0 +1,572 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#define _GNU_SOURCE
#include <dirent.h>
#include <fcntl.h>
#include <getopt.h>
#include <regex.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/signalfd.h>
#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <linux/thermal.h>
#include <libconfig.h>
#include "thermal-tools.h"
#define CLASS_THERMAL "/sys/class/thermal"
enum {
THERMOMETER_SUCCESS = 0,
THERMOMETER_OPTION_ERROR,
THERMOMETER_LOG_ERROR,
THERMOMETER_CONFIG_ERROR,
THERMOMETER_TIME_ERROR,
THERMOMETER_INIT_ERROR,
THERMOMETER_RUNTIME_ERROR
};
struct options {
int loglvl;
int logopt;
int overwrite;
int duration;
const char *config;
char postfix[PATH_MAX];
char output[PATH_MAX];
};
struct tz_regex {
regex_t regex;
int polling;
};
struct configuration {
struct tz_regex *tz_regex;
int nr_tz_regex;
};
struct tz {
FILE *file_out;
int fd_temp;
int fd_timer;
int polling;
const char *name;
};
struct thermometer {
struct tz *tz;
int nr_tz;
};
static struct tz_regex *configuration_tz_match(const char *expr,
struct configuration *config)
{
int i;
for (i = 0; i < config->nr_tz_regex; i++) {
if (!regexec(&config->tz_regex[i].regex, expr, 0, NULL, 0))
return &config->tz_regex[i];
}
return NULL;
}
static int configuration_default_init(struct configuration *config)
{
config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) *
(config->nr_tz_regex + 1));
if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, ".*",
REG_NOSUB | REG_EXTENDED)) {
ERROR("Invalid regular expression\n");
return -1;
}
config->tz_regex[config->nr_tz_regex].polling = 250;
config->nr_tz_regex = 1;
return 0;
}
static int configuration_init(const char *path, struct configuration *config)
{
config_t cfg;
config_setting_t *tz;
int i, length;
if (path && access(path, F_OK)) {
ERROR("'%s' is not accessible\n", path);
return -1;
}
if (!path && !config->nr_tz_regex) {
INFO("No thermal zones configured, using wildcard for all of them\n");
return configuration_default_init(config);
}
config_init(&cfg);
if (!config_read_file(&cfg, path)) {
ERROR("Failed to parse %s:%d - %s\n", config_error_file(&cfg),
config_error_line(&cfg), config_error_text(&cfg));
return -1;
}
tz = config_lookup(&cfg, "thermal-zones");
if (!tz) {
ERROR("No thermal zone configured to be monitored\n");
return -1;
}
length = config_setting_length(tz);
INFO("Found %d thermal zone(s) regular expression\n", length);
for (i = 0; i < length; i++) {
config_setting_t *node;
const char *name;
int polling;
node = config_setting_get_elem(tz, i);
if (!node) {
ERROR("Missing node name '%d'\n", i);
return -1;
}
if (!config_setting_lookup_string(node, "name", &name)) {
ERROR("Thermal zone name not found\n");
return -1;
}
if (!config_setting_lookup_int(node, "polling", &polling)) {
ERROR("Polling value not found");
return -1;
}
config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) *
(config->nr_tz_regex + 1));
if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, name,
REG_NOSUB | REG_EXTENDED)) {
ERROR("Invalid regular expression '%s'\n", name);
continue;
}
config->tz_regex[config->nr_tz_regex].polling = polling;
config->nr_tz_regex++;
INFO("Thermal zone regular expression '%s' with polling %d\n",
name, polling);
}
return 0;
}
static void usage(const char *cmd)
{
printf("%s Version: %s\n", cmd, VERSION);
printf("Usage: %s [options]\n", cmd);
printf("\t-h, --help\t\tthis help\n");
printf("\t-o, --output <dir>\toutput directory for temperature capture\n");
printf("\t-c, --config <file>\tconfiguration file\n");
printf("\t-d, --duration <seconds>\tcapture duration\n");
printf("\t-l, --loglevel <level>\tlog level: ");
printf("DEBUG, INFO, NOTICE, WARN, ERROR\n");
printf("\t-p, --postfix <string>\tpostfix to be happened at the end of the files\n");
printf("\t-s, --syslog\t\toutput to syslog\n");
printf("\t-w, --overwrite\t\toverwrite the temperature capture files if they exist\n");
printf("\n");
exit(0);
}
static int options_init(int argc, char *argv[], struct options *options)
{
int opt;
time_t now = time(NULL);
struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "config", required_argument, NULL, 'c' },
{ "duration", required_argument, NULL, 'd' },
{ "loglevel", required_argument, NULL, 'l' },
{ "postfix", required_argument, NULL, 'p' },
{ "output", required_argument, NULL, 'o' },
{ "syslog", required_argument, NULL, 's' },
{ "overwrite", no_argument, NULL, 'w' },
{ 0, 0, 0, 0 }
};
strftime(options->postfix, sizeof(options->postfix),
"-%Y-%m-%d_%H:%M:%S", gmtime(&now));
while (1) {
int optindex = 0;
opt = getopt_long(argc, argv, "ho:c:d:l:p:sw", long_options, &optindex);
if (opt == -1)
break;
switch (opt) {
case 'c':
options->config = optarg;
break;
case 'd':
options->duration = atoi(optarg) * 1000;
break;
case 'l':
options->loglvl = log_str2level(optarg);
break;
case 'h':
usage(basename(argv[0]));
break;
case 'p':
strcpy(options->postfix, optarg);
break;
case 'o':
strcpy(options->output, optarg);
break;
case 's':
options->logopt = TO_SYSLOG;
break;
case 'w':
options->overwrite = 1;
break;
default: /* '?' */
ERROR("Usage: %s --help\n", argv[0]);
return -1;
}
}
return 0;
}
static int thermometer_add_tz(const char *path, const char *name, int polling,
struct thermometer *thermometer)
{
int fd;
char tz_path[PATH_MAX];
sprintf(tz_path, CLASS_THERMAL"/%s/temp", path);
fd = open(tz_path, O_RDONLY);
if (fd < 0) {
ERROR("Failed to open '%s': %m\n", tz_path);
return -1;
}
thermometer->tz = realloc(thermometer->tz,
sizeof(*thermometer->tz) * (thermometer->nr_tz + 1));
if (!thermometer->tz) {
ERROR("Failed to allocate thermometer->tz\n");
return -1;
}
thermometer->tz[thermometer->nr_tz].fd_temp = fd;
thermometer->tz[thermometer->nr_tz].name = strdup(name);
thermometer->tz[thermometer->nr_tz].polling = polling;
thermometer->nr_tz++;
INFO("Added thermal zone '%s->%s (polling:%d)'\n", path, name, polling);
return 0;
}
static int thermometer_init(struct configuration *config,
struct thermometer *thermometer)
{
DIR *dir;
struct dirent *dirent;
struct tz_regex *tz_regex;
const char *tz_dirname = "thermal_zone";
if (mainloop_init()) {
ERROR("Failed to start mainloop\n");
return -1;
}
dir = opendir(CLASS_THERMAL);
if (!dir) {
ERROR("failed to open '%s'\n", CLASS_THERMAL);
return -1;
}
while ((dirent = readdir(dir))) {
char tz_type[THERMAL_NAME_LENGTH];
char tz_path[PATH_MAX];
FILE *tz_file;
if (strncmp(dirent->d_name, tz_dirname, strlen(tz_dirname)))
continue;
sprintf(tz_path, CLASS_THERMAL"/%s/type", dirent->d_name);
tz_file = fopen(tz_path, "r");
if (!tz_file) {
ERROR("Failed to open '%s': %m", tz_path);
continue;
}
fscanf(tz_file, "%s", tz_type);
fclose(tz_file);
tz_regex = configuration_tz_match(tz_type, config);
if (!tz_regex)
continue;
if (thermometer_add_tz(dirent->d_name, tz_type,
tz_regex->polling, thermometer))
continue;
}
closedir(dir);
return 0;
}
static int timer_temperature_callback(int fd, void *arg)
{
struct tz *tz = arg;
char buf[16] = { 0 };
pread(tz->fd_temp, buf, sizeof(buf), 0);
fprintf(tz->file_out, "%ld %s", getuptimeofday_ms(), buf);
read(fd, buf, sizeof(buf));
return 0;
}
static int thermometer_start(struct thermometer *thermometer,
struct options *options)
{
struct itimerspec timer_it = { 0 };
char *path;
FILE *f;
int i;
INFO("Capturing %d thermal zone(s) temperature...\n", thermometer->nr_tz);
if (access(options->output, F_OK) && mkdir(options->output, 0700)) {
ERROR("Failed to create directory '%s'\n", options->output);
return -1;
}
for (i = 0; i < thermometer->nr_tz; i++) {
asprintf(&path, "%s/%s%s", options->output,
thermometer->tz[i].name, options->postfix);
if (!options->overwrite && !access(path, F_OK)) {
ERROR("'%s' already exists\n", path);
return -1;
}
f = fopen(path, "w");
if (!f) {
ERROR("Failed to create '%s':%m\n", path);
return -1;
}
fprintf(f, "timestamp(ms) %s(°mC)\n", thermometer->tz[i].name);
thermometer->tz[i].file_out = f;
DEBUG("Created '%s' file for thermal zone '%s'\n", path, thermometer->tz[i].name);
/*
* Create polling timer
*/
thermometer->tz[i].fd_timer = timerfd_create(CLOCK_MONOTONIC, 0);
if (thermometer->tz[i].fd_timer < 0) {
ERROR("Failed to create timer for '%s': %m\n",
thermometer->tz[i].name);
return -1;
}
DEBUG("Watching '%s' every %d ms\n",
thermometer->tz[i].name, thermometer->tz[i].polling);
timer_it.it_interval = timer_it.it_value =
msec_to_timespec(thermometer->tz[i].polling);
if (timerfd_settime(thermometer->tz[i].fd_timer, 0,
&timer_it, NULL) < 0)
return -1;
if (mainloop_add(thermometer->tz[i].fd_timer,
timer_temperature_callback,
&thermometer->tz[i]))
return -1;
}
return 0;
}
static int thermometer_execute(int argc, char *argv[], char *const envp[], pid_t *pid)
{
if (!argc)
return 0;
*pid = fork();
if (*pid < 0) {
ERROR("Failed to fork process: %m");
return -1;
}
if (!(*pid)) {
execvpe(argv[0], argv, envp);
exit(1);
}
return 0;
}
static int kill_process(__maybe_unused int fd, void *arg)
{
pid_t pid = *(pid_t *)arg;
if (kill(pid, SIGTERM))
ERROR("Failed to send SIGTERM signal to '%d': %p\n", pid);
else if (waitpid(pid, NULL, 0))
ERROR("Failed to wait pid '%d': %p\n", pid);
mainloop_exit();
return 0;
}
static int exit_mainloop(__maybe_unused int fd, __maybe_unused void *arg)
{
mainloop_exit();
return 0;
}
static int thermometer_wait(struct options *options, pid_t pid)
{
int fd;
sigset_t mask;
/*
* If there is a duration specified, we will exit the mainloop
* and gracefully close all the files which will flush the
* file system cache
*/
if (options->duration) {
struct itimerspec timer_it = { 0 };
timer_it.it_value = msec_to_timespec(options->duration);
fd = timerfd_create(CLOCK_MONOTONIC, 0);
if (fd < 0) {
ERROR("Failed to create duration timer: %m\n");
return -1;
}
if (timerfd_settime(fd, 0, &timer_it, NULL)) {
ERROR("Failed to set timer time: %m\n");
return -1;
}
if (mainloop_add(fd, pid < 0 ? exit_mainloop : kill_process, &pid)) {
ERROR("Failed to set timer exit mainloop callback\n");
return -1;
}
}
/*
* We want to catch any keyboard interrupt, as well as child
* signals if any in order to exit properly
*/
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
sigaddset(&mask, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &mask, NULL)) {
ERROR("Failed to set sigprocmask: %m\n");
return -1;
}
fd = signalfd(-1, &mask, 0);
if (fd < 0) {
ERROR("Failed to set the signalfd: %m\n");
return -1;
}
if (mainloop_add(fd, exit_mainloop, NULL)) {
ERROR("Failed to set timer exit mainloop callback\n");
return -1;
}
return mainloop(-1);
}
static int thermometer_stop(struct thermometer *thermometer)
{
int i;
INFO("Closing/flushing output files\n");
for (i = 0; i < thermometer->nr_tz; i++)
fclose(thermometer->tz[i].file_out);
return 0;
}
int main(int argc, char *argv[], char *const envp[])
{
struct options options = {
.loglvl = LOG_DEBUG,
.logopt = TO_STDOUT,
.output = ".",
};
struct configuration config = { 0 };
struct thermometer thermometer = { 0 };
pid_t pid = -1;
if (options_init(argc, argv, &options))
return THERMOMETER_OPTION_ERROR;
if (log_init(options.loglvl, argv[0], options.logopt))
return THERMOMETER_LOG_ERROR;
if (configuration_init(options.config, &config))
return THERMOMETER_CONFIG_ERROR;
if (uptimeofday_init())
return THERMOMETER_TIME_ERROR;
if (thermometer_init(&config, &thermometer))
return THERMOMETER_INIT_ERROR;
if (thermometer_start(&thermometer, &options))
return THERMOMETER_RUNTIME_ERROR;
if (thermometer_execute(argc - optind, &argv[optind], envp, &pid))
return THERMOMETER_RUNTIME_ERROR;
if (thermometer_wait(&options, pid))
return THERMOMETER_RUNTIME_ERROR;
if (thermometer_stop(&thermometer))
return THERMOMETER_RUNTIME_ERROR;
return THERMOMETER_SUCCESS;
}

View File

@ -0,0 +1,5 @@
thermal-zones = (
{ name = "cpu[0-9]-thermal";
polling = 100; }
)