Merge back earlier thermal control updates for 5.19-rc1.
This commit is contained in:
commit
388292df27
@ -18,6 +18,7 @@ description:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qcom,sc8180x-lmh
|
||||
- qcom,sdm845-lmh
|
||||
- qcom,sm8150-lmh
|
||||
|
||||
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
@ -19544,6 +19544,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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -194,12 +194,31 @@ static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enab
|
||||
return result;
|
||||
}
|
||||
|
||||
static int set_os_uuid_mask(struct int3400_thermal_priv *priv, u32 mask)
|
||||
{
|
||||
int cap = 0;
|
||||
|
||||
/*
|
||||
* Capability bits:
|
||||
* Bit 0: set to 1 to indicate DPTF is active
|
||||
* Bi1 1: set to 1 to active cooling is supported by user space daemon
|
||||
* Bit 2: set to 1 to passive cooling is supported by user space daemon
|
||||
* Bit 3: set to 1 to critical trip is handled by user space daemon
|
||||
*/
|
||||
if (mask)
|
||||
cap = (priv->os_uuid_mask << 1) | 0x01;
|
||||
|
||||
return int3400_thermal_run_osc(priv->adev->handle,
|
||||
"b23ba85d-c8b7-3542-88de-8de2ffcfd698",
|
||||
&cap);
|
||||
}
|
||||
|
||||
static ssize_t current_uuid_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
|
||||
int i;
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
|
||||
if (!strncmp(buf, int3400_thermal_uuids[i],
|
||||
@ -231,19 +250,7 @@ static ssize_t current_uuid_store(struct device *dev,
|
||||
}
|
||||
|
||||
if (priv->os_uuid_mask) {
|
||||
int cap, ret;
|
||||
|
||||
/*
|
||||
* Capability bits:
|
||||
* Bit 0: set to 1 to indicate DPTF is active
|
||||
* Bi1 1: set to 1 to active cooling is supported by user space daemon
|
||||
* Bit 2: set to 1 to passive cooling is supported by user space daemon
|
||||
* Bit 3: set to 1 to critical trip is handled by user space daemon
|
||||
*/
|
||||
cap = ((priv->os_uuid_mask << 1) | 0x01);
|
||||
ret = int3400_thermal_run_osc(priv->adev->handle,
|
||||
"b23ba85d-c8b7-3542-88de-8de2ffcfd698",
|
||||
&cap);
|
||||
ret = set_os_uuid_mask(priv, priv->os_uuid_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -469,17 +476,26 @@ static int int3400_thermal_change_mode(struct thermal_zone_device *thermal,
|
||||
if (mode != thermal->mode) {
|
||||
int enabled;
|
||||
|
||||
enabled = mode == THERMAL_DEVICE_ENABLED;
|
||||
|
||||
if (priv->os_uuid_mask) {
|
||||
if (!enabled) {
|
||||
priv->os_uuid_mask = 0;
|
||||
result = set_os_uuid_mask(priv, priv->os_uuid_mask);
|
||||
}
|
||||
goto eval_odvp;
|
||||
}
|
||||
|
||||
if (priv->current_uuid_index < 0 ||
|
||||
priv->current_uuid_index >= INT3400_THERMAL_MAXIMUM_UUID)
|
||||
return -EINVAL;
|
||||
|
||||
enabled = (mode == THERMAL_DEVICE_ENABLED);
|
||||
result = int3400_thermal_run_osc(priv->adev->handle,
|
||||
int3400_thermal_uuids[priv->current_uuid_index],
|
||||
&enabled);
|
||||
}
|
||||
|
||||
|
||||
eval_odvp:
|
||||
evaluate_odvp(priv);
|
||||
|
||||
return result;
|
||||
|
@ -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);
|
||||
|
566
drivers/thermal/k3_j72xx_bandgap.c
Normal file
566
drivers/thermal/k3_j72xx_bandgap.c
Normal 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>");
|
@ -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", },
|
||||
{}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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 ");
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -31,6 +31,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'
|
||||
@ -85,12 +88,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/$@)
|
||||
|
||||
@ -101,7 +113,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)
|
||||
@ -115,12 +127,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)
|
||||
|
||||
@ -133,7 +154,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)
|
||||
@ -160,9 +181,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)
|
||||
|
||||
@ -177,6 +207,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
2
tools/lib/thermal/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
libthermal.so*
|
||||
libthermal.pc
|
5
tools/lib/thermal/Build
Normal file
5
tools/lib/thermal/Build
Normal 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
165
tools/lib/thermal/Makefile
Normal 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
|
349
tools/lib/thermal/commands.c
Normal file
349
tools/lib/thermal/commands.c
Normal 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
164
tools/lib/thermal/events.c
Normal 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;
|
||||
}
|
142
tools/lib/thermal/include/thermal.h
Normal file
142
tools/lib/thermal/include/thermal.h
Normal 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
|
25
tools/lib/thermal/libthermal.map
Normal file
25
tools/lib/thermal/libthermal.map
Normal 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:
|
||||
*;
|
||||
};
|
12
tools/lib/thermal/libthermal.pc.template
Normal file
12
tools/lib/thermal/libthermal.pc.template
Normal 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
|
75
tools/lib/thermal/sampling.c
Normal file
75
tools/lib/thermal/sampling.c
Normal 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
135
tools/lib/thermal/thermal.c
Normal 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;
|
||||
}
|
215
tools/lib/thermal/thermal_nl.c
Normal file
215
tools/lib/thermal/thermal_nl.c
Normal 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;
|
||||
}
|
46
tools/lib/thermal/thermal_nl.h
Normal file
46
tools/lib/thermal/thermal_nl.h
Normal 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
3
tools/thermal/lib/Build
Normal 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
158
tools/thermal/lib/Makefile
Normal 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
|
12
tools/thermal/lib/libthermal_tools.pc.template
Normal file
12
tools/thermal/lib/libthermal_tools.pc.template
Normal 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
77
tools/thermal/lib/log.c
Normal 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
31
tools/thermal/lib/log.h
Normal 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
|
120
tools/thermal/lib/mainloop.c
Normal file
120
tools/thermal/lib/mainloop.c
Normal 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);
|
||||
}
|
15
tools/thermal/lib/mainloop.h
Normal file
15
tools/thermal/lib/mainloop.h
Normal 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
|
10
tools/thermal/lib/thermal-tools.h
Normal file
10
tools/thermal/lib/thermal-tools.h
Normal 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
|
40
tools/thermal/lib/uptimeofday.c
Normal file
40
tools/thermal/lib/uptimeofday.c
Normal 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;
|
||||
}
|
12
tools/thermal/lib/uptimeofday.h
Normal file
12
tools/thermal/lib/uptimeofday.h
Normal 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
|
1
tools/thermal/thermal-engine/Build
Normal file
1
tools/thermal/thermal-engine/Build
Normal file
@ -0,0 +1 @@
|
||||
thermal-engine-y += thermal-engine.o
|
28
tools/thermal/thermal-engine/Makefile
Normal file
28
tools/thermal/thermal-engine/Makefile
Normal 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
|
341
tools/thermal/thermal-engine/thermal-engine.c
Normal file
341
tools/thermal/thermal-engine/thermal-engine.c
Normal 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;
|
||||
}
|
1
tools/thermal/thermometer/Build
Normal file
1
tools/thermal/thermometer/Build
Normal file
@ -0,0 +1 @@
|
||||
thermometer-y += thermometer.o
|
26
tools/thermal/thermometer/Makefile
Normal file
26
tools/thermal/thermometer/Makefile
Normal 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)
|
92
tools/thermal/thermometer/thermometer.8
Normal file
92
tools/thermal/thermometer/thermometer.8
Normal 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>
|
572
tools/thermal/thermometer/thermometer.c
Normal file
572
tools/thermal/thermometer/thermometer.c
Normal 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;
|
||||
}
|
5
tools/thermal/thermometer/thermometer.conf
Normal file
5
tools/thermal/thermometer/thermometer.conf
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
thermal-zones = (
|
||||
{ name = "cpu[0-9]-thermal";
|
||||
polling = 100; }
|
||||
)
|
Loading…
Reference in New Issue
Block a user