Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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

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

Specifics:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+4231 -102
+1
Documentation/devicetree/bindings/thermal/qcom-lmh.yaml
··· 18 18 properties: 19 19 compatible: 20 20 enum: 21 + - qcom,sc8180x-lmh 21 22 - qcom,sdm845-lmh 22 23 - qcom,sm8150-lmh 23 24
+108 -2
Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml
··· 10 10 11 11 properties: 12 12 compatible: 13 - const: qcom,spmi-adc-tm5 13 + enum: 14 + - qcom,spmi-adc-tm5 15 + - qcom,spmi-adc-tm5-gen2 14 16 15 17 reg: 16 18 maxItems: 1 ··· 35 33 qcom,avg-samples: 36 34 $ref: /schemas/types.yaml#/definitions/uint32 37 35 description: Number of samples to be used for measurement. 36 + Not applicable for Gen2 ADC_TM peripheral. 38 37 enum: 39 38 - 1 40 39 - 2 ··· 48 45 $ref: /schemas/types.yaml#/definitions/uint32 49 46 description: This parameter is used to decrease ADC sampling rate. 50 47 Quicker measurements can be made by reducing decimation ratio. 48 + Not applicable for Gen2 ADC_TM peripheral. 51 49 enum: 52 50 - 250 53 51 - 420 ··· 97 93 - const: 1 98 94 - enum: [ 1, 3, 4, 6, 20, 8, 10 ] 99 95 96 + qcom,avg-samples: 97 + $ref: /schemas/types.yaml#/definitions/uint32 98 + description: Number of samples to be used for measurement. 99 + This property in child node is applicable only for Gen2 ADC_TM peripheral. 100 + enum: 101 + - 1 102 + - 2 103 + - 4 104 + - 8 105 + - 16 106 + default: 1 107 + 108 + qcom,decimation: 109 + $ref: /schemas/types.yaml#/definitions/uint32 110 + description: This parameter is used to decrease ADC sampling rate. 111 + Quicker measurements can be made by reducing decimation ratio. 112 + This property in child node is applicable only for Gen2 ADC_TM peripheral. 113 + enum: 114 + - 85 115 + - 340 116 + - 1360 117 + default: 1360 118 + 100 119 required: 101 120 - reg 102 121 - io-channels 103 122 104 123 additionalProperties: 105 124 false 125 + 126 + allOf: 127 + - if: 128 + properties: 129 + compatible: 130 + contains: 131 + const: qcom,spmi-adc-tm5 132 + 133 + then: 134 + patternProperties: 135 + "^([-a-z0-9]*)@[0-7]$": 136 + properties: 137 + qcom,decimation: false 138 + qcom,avg-samples: false 139 + 140 + - if: 141 + properties: 142 + compatible: 143 + contains: 144 + const: qcom,spmi-adc-tm5-gen2 145 + 146 + then: 147 + properties: 148 + qcom,avg-samples: false 149 + qcom,decimation: false 106 150 107 151 required: 108 152 - compatible ··· 176 124 #size-cells = <0>; 177 125 #io-channel-cells = <1>; 178 126 179 - /* Other propreties are omitted */ 127 + /* Other properties are omitted */ 180 128 conn-therm@4f { 181 129 reg = <ADC5_AMUX_THM3_100K_PU>; 182 130 qcom,ratiometric; ··· 195 143 conn-therm@0 { 196 144 reg = <0>; 197 145 io-channels = <&pm8150b_adc ADC5_AMUX_THM3_100K_PU>; 146 + qcom,ratiometric; 147 + qcom,hw-settle-time-us = <200>; 148 + }; 149 + }; 150 + }; 151 + 152 + - | 153 + #include <dt-bindings/iio/qcom,spmi-adc7-pmk8350.h> 154 + #include <dt-bindings/iio/qcom,spmi-adc7-pm8350.h> 155 + #include <dt-bindings/interrupt-controller/irq.h> 156 + spmi_bus { 157 + #address-cells = <1>; 158 + #size-cells = <0>; 159 + pmk8350_vadc: adc@3100 { 160 + reg = <0x3100>; 161 + compatible = "qcom,spmi-adc7"; 162 + #address-cells = <1>; 163 + #size-cells = <0>; 164 + #io-channel-cells = <1>; 165 + 166 + /* Other properties are omitted */ 167 + xo-therm@44 { 168 + reg = <PMK8350_ADC7_AMUX_THM1_100K_PU>; 169 + qcom,ratiometric; 170 + qcom,hw-settle-time = <200>; 171 + }; 172 + 173 + conn-therm@47 { 174 + reg = <PM8350_ADC7_AMUX_THM4_100K_PU>; 175 + qcom,ratiometric; 176 + qcom,hw-settle-time = <200>; 177 + }; 178 + }; 179 + 180 + pmk8350_adc_tm: adc-tm@3400 { 181 + compatible = "qcom,spmi-adc-tm5-gen2"; 182 + reg = <0x3400>; 183 + interrupts = <0x0 0x34 0x0 IRQ_TYPE_EDGE_RISING>; 184 + #thermal-sensor-cells = <1>; 185 + #address-cells = <1>; 186 + #size-cells = <0>; 187 + 188 + pmk8350-xo-therm@0 { 189 + reg = <0>; 190 + io-channels = <&pmk8350_vadc PMK8350_ADC7_AMUX_THM1_100K_PU>; 191 + qcom,decimation = <340>; 192 + qcom,ratiometric; 193 + qcom,hw-settle-time-us = <200>; 194 + }; 195 + 196 + conn-therm@1 { 197 + reg = <1>; 198 + io-channels = <&pmk8350_vadc PM8350_ADC7_AMUX_THM4_100K_PU>; 199 + qcom,avg-samples = <2>; 198 200 qcom,ratiometric; 199 201 qcom,hw-settle-time-us = <200>; 200 202 };
+4 -1
Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
··· 19 19 properties: 20 20 compatible: 21 21 oneOf: 22 - - description: msm9860 TSENS based 22 + - description: msm8960 TSENS based 23 23 items: 24 24 - enum: 25 25 - qcom,ipq8064-tsens 26 + - qcom,msm8960-tsens 26 27 27 28 - description: v0.1 of TSENS 28 29 items: ··· 50 49 - qcom,sc7180-tsens 51 50 - qcom,sc7280-tsens 52 51 - qcom,sc8180x-tsens 52 + - qcom,sc8280xp-tsens 53 53 - qcom,sdm630-tsens 54 54 - qcom,sdm845-tsens 55 55 - qcom,sm8150-tsens ··· 118 116 - qcom,ipq8064-tsens 119 117 - qcom,mdm9607-tsens 120 118 - qcom,msm8916-tsens 119 + - qcom,msm8960-tsens 121 120 - qcom,msm8974-tsens 122 121 - qcom,msm8976-tsens 123 122 - qcom,qcs404-tsens
+2
Documentation/devicetree/bindings/thermal/rzg2l-thermal.yaml
··· 17 17 compatible: 18 18 items: 19 19 - enum: 20 + - renesas,r9a07g043-tsu # RZ/G2UL 20 21 - renesas,r9a07g044-tsu # RZ/G2{L,LC} 22 + - renesas,r9a07g054-tsu # RZ/V2L 21 23 - const: renesas,rzg2l-tsu 22 24 23 25 reg:
+63
Documentation/devicetree/bindings/thermal/ti,j72xx-thermal.yaml
··· 1 + # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 + %YAML 1.2 3 + --- 4 + $id: http://devicetree.org/schemas/thermal/ti,j72xx-thermal.yaml# 5 + $schema: http://devicetree.org/meta-schemas/core.yaml# 6 + 7 + title: Texas Instruments J72XX VTM (DTS) binding 8 + 9 + maintainers: 10 + - Keerthy <j-keerthy@ti.com> 11 + 12 + properties: 13 + compatible: 14 + enum: 15 + - ti,j721e-vtm 16 + - ti,j7200-vtm 17 + 18 + reg: 19 + items: 20 + - description: VTM cfg1 register space 21 + - description: VTM cfg2 register space 22 + - description: VTM efuse register space 23 + 24 + power-domains: 25 + maxItems: 1 26 + 27 + "#thermal-sensor-cells": 28 + const: 1 29 + 30 + required: 31 + - compatible 32 + - reg 33 + - power-domains 34 + - "#thermal-sensor-cells" 35 + 36 + additionalProperties: false 37 + 38 + examples: 39 + - | 40 + #include <dt-bindings/soc/ti,sci_pm_domain.h> 41 + wkup_vtm0: thermal-sensor@42040000 { 42 + compatible = "ti,j721e-vtm"; 43 + reg = <0x42040000 0x350>, 44 + <0x42050000 0x350>, 45 + <0x43000300 0x10>; 46 + power-domains = <&k3_pds 154 TI_SCI_PD_EXCLUSIVE>; 47 + #thermal-sensor-cells = <1>; 48 + }; 49 + 50 + mpu_thermal: mpu-thermal { 51 + polling-delay-passive = <250>; /* milliseconds */ 52 + polling-delay = <500>; /* milliseconds */ 53 + thermal-sensors = <&wkup_vtm0 0>; 54 + 55 + trips { 56 + mpu_crit: mpu-crit { 57 + temperature = <125000>; /* milliCelsius */ 58 + hysteresis = <2000>; /* milliCelsius */ 59 + type = "critical"; 60 + }; 61 + }; 62 + }; 63 + ...
+1
MAINTAINERS
··· 19589 19589 F: include/linux/cpu_cooling.h 19590 19590 F: include/linux/thermal.h 19591 19591 F: include/uapi/linux/thermal.h 19592 + F: tools/lib/thermal/ 19592 19593 F: tools/thermal/ 19593 19594 19594 19595 THERMAL DRIVER FOR AMLOGIC SOCS
+1
drivers/base/power/wakeup.c
··· 930 930 931 931 return ret || atomic_read(&pm_abort_suspend) > 0; 932 932 } 933 + EXPORT_SYMBOL_GPL(pm_wakeup_pending); 933 934 934 935 void pm_system_wakeup(void) 935 936 {
+11
drivers/iio/adc/qcom-vadc-common.c
··· 677 677 } 678 678 EXPORT_SYMBOL(qcom_adc_tm5_temp_volt_scale); 679 679 680 + u16 qcom_adc_tm5_gen2_temp_res_scale(int temp) 681 + { 682 + int64_t resistance; 683 + 684 + resistance = qcom_vadc_map_temp_voltage(adcmap7_100k, 685 + ARRAY_SIZE(adcmap7_100k), temp); 686 + 687 + return div64_s64(resistance * RATIO_MAX_ADC7, resistance + R_PU_100K); 688 + } 689 + EXPORT_SYMBOL(qcom_adc_tm5_gen2_temp_res_scale); 690 + 680 691 int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype, 681 692 unsigned int prescale_ratio, 682 693 const struct adc5_data *data,
+1 -1
drivers/thermal/Makefile
··· 28 28 # devfreq cooling 29 29 thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o 30 30 31 - obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o 31 + obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o 32 32 # platform thermal drivers 33 33 obj-y += broadcom/ 34 34 obj-$(CONFIG_THERMAL_MMIO) += thermal_mmio.o
+1 -4
drivers/thermal/broadcom/bcm2711_thermal.c
··· 38 38 int offset = thermal_zone_get_offset(priv->thermal); 39 39 u32 val; 40 40 int ret; 41 - long t; 42 41 43 42 ret = regmap_read(priv->regmap, AVS_RO_TEMP_STATUS, &val); 44 43 if (ret) ··· 49 50 val &= AVS_RO_TEMP_STATUS_DATA_MSK; 50 51 51 52 /* Convert a HW code to a temperature reading (millidegree celsius) */ 52 - t = slope * val + offset; 53 - 54 - *temp = t < 0 ? 0 : t; 53 + *temp = slope * val + offset; 55 54 56 55 return 0; 57 56 }
+3
drivers/thermal/broadcom/sr-thermal.c
··· 60 60 return -ENOMEM; 61 61 62 62 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 63 + if (!res) 64 + return -ENOENT; 65 + 63 66 sr_thermal->regs = (void __iomem *)devm_memremap(&pdev->dev, res->start, 64 67 resource_size(res), 65 68 MEMREMAP_WB);
+18 -7
drivers/thermal/devfreq_cooling.c
··· 359 359 struct device *dev = df->dev.parent; 360 360 struct devfreq_cooling_device *dfc; 361 361 struct em_perf_domain *em; 362 + struct thermal_cooling_device_ops *ops; 362 363 char *name; 363 364 int err, num_opps; 364 365 365 - dfc = kzalloc(sizeof(*dfc), GFP_KERNEL); 366 - if (!dfc) 366 + ops = kmemdup(&devfreq_cooling_ops, sizeof(*ops), GFP_KERNEL); 367 + if (!ops) 367 368 return ERR_PTR(-ENOMEM); 369 + 370 + dfc = kzalloc(sizeof(*dfc), GFP_KERNEL); 371 + if (!dfc) { 372 + err = -ENOMEM; 373 + goto free_ops; 374 + } 368 375 369 376 dfc->devfreq = df; 370 377 371 378 em = em_pd_get(dev); 372 379 if (em && !em_is_artificial(em)) { 373 380 dfc->em_pd = em; 374 - devfreq_cooling_ops.get_requested_power = 381 + ops->get_requested_power = 375 382 devfreq_cooling_get_requested_power; 376 - devfreq_cooling_ops.state2power = devfreq_cooling_state2power; 377 - devfreq_cooling_ops.power2state = devfreq_cooling_power2state; 383 + ops->state2power = devfreq_cooling_state2power; 384 + ops->power2state = devfreq_cooling_power2state; 378 385 379 386 dfc->power_ops = dfc_power; 380 387 ··· 416 409 if (!name) 417 410 goto remove_qos_req; 418 411 419 - cdev = thermal_of_cooling_device_register(np, name, dfc, 420 - &devfreq_cooling_ops); 412 + cdev = thermal_of_cooling_device_register(np, name, dfc, ops); 421 413 kfree(name); 422 414 423 415 if (IS_ERR(cdev)) { ··· 437 431 kfree(dfc->freq_table); 438 432 free_dfc: 439 433 kfree(dfc); 434 + free_ops: 435 + kfree(ops); 440 436 441 437 return ERR_PTR(err); 442 438 } ··· 520 512 void devfreq_cooling_unregister(struct thermal_cooling_device *cdev) 521 513 { 522 514 struct devfreq_cooling_device *dfc; 515 + const struct thermal_cooling_device_ops *ops; 523 516 struct device *dev; 524 517 525 518 if (IS_ERR_OR_NULL(cdev)) 526 519 return; 527 520 521 + ops = cdev->ops; 528 522 dfc = cdev->devdata; 529 523 dev = dfc->devfreq->dev.parent; 530 524 ··· 537 527 538 528 kfree(dfc->freq_table); 539 529 kfree(dfc); 530 + kfree(ops); 540 531 } 541 532 EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);
+2 -4
drivers/thermal/hisi_thermal.c
··· 629 629 return 0; 630 630 } 631 631 632 - #ifdef CONFIG_PM_SLEEP 633 632 static int hisi_thermal_suspend(struct device *dev) 634 633 { 635 634 struct hisi_thermal_data *data = dev_get_drvdata(dev); ··· 650 651 651 652 return ret; 652 653 } 653 - #endif 654 654 655 - static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops, 655 + static DEFINE_SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops, 656 656 hisi_thermal_suspend, hisi_thermal_resume); 657 657 658 658 static struct platform_driver hisi_thermal_driver = { 659 659 .driver = { 660 660 .name = "hisi_thermal", 661 - .pm = &hisi_thermal_pm_ops, 661 + .pm = pm_sleep_ptr(&hisi_thermal_pm_ops), 662 662 .of_match_table = of_hisi_thermal_match, 663 663 }, 664 664 .probe = hisi_thermal_probe,
+4 -2
drivers/thermal/imx_sc_thermal.c
··· 94 94 sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL); 95 95 if (!sensor) { 96 96 of_node_put(child); 97 - of_node_put(sensor_np); 98 - return -ENOMEM; 97 + ret = -ENOMEM; 98 + goto put_node; 99 99 } 100 100 101 101 ret = thermal_zone_of_get_sensor_id(child, ··· 124 124 dev_warn(&pdev->dev, "failed to add hwmon sysfs attributes\n"); 125 125 } 126 126 127 + put_node: 127 128 of_node_put(sensor_np); 129 + of_node_put(np); 128 130 129 131 return ret; 130 132 }
+9 -15
drivers/thermal/intel/int340x_thermal/int3400_thermal.c
··· 169 169 acpi_status status; 170 170 int result = 0; 171 171 struct acpi_osc_context context = { 172 - .uuid_str = NULL, 172 + .uuid_str = uuid_str, 173 173 .rev = 1, 174 174 .cap.length = 8, 175 + .cap.pointer = buf, 175 176 }; 176 - 177 - context.uuid_str = uuid_str; 178 177 179 178 buf[OSC_QUERY_DWORD] = 0; 180 179 buf[OSC_SUPPORT_DWORD] = *enable; 181 - 182 - context.cap.pointer = buf; 183 180 184 181 status = acpi_run_osc(handle, &context); 185 182 if (ACPI_SUCCESS(status)) { 186 183 ret = *((u32 *)(context.ret.pointer + 4)); 187 184 if (ret != *enable) 188 185 result = -EPERM; 186 + 187 + kfree(context.ret.pointer); 189 188 } else 190 189 result = -EPERM; 191 - 192 - kfree(context.ret.pointer); 193 190 194 191 return result; 195 192 } ··· 521 524 522 525 obj = buffer.pointer; 523 526 if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1 524 - || obj->package.elements[0].type != ACPI_TYPE_BUFFER) { 525 - kfree(buffer.pointer); 526 - return; 527 - } 527 + || obj->package.elements[0].type != ACPI_TYPE_BUFFER) 528 + goto out_free; 528 529 529 530 priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer, 530 531 obj->package.elements[0].buffer.length, 531 532 GFP_KERNEL); 532 - if (!priv->data_vault) { 533 - kfree(buffer.pointer); 534 - return; 535 - } 533 + if (!priv->data_vault) 534 + goto out_free; 536 535 537 536 bin_attr_data_vault.private = priv->data_vault; 538 537 bin_attr_data_vault.size = obj->package.elements[0].buffer.length; 538 + out_free: 539 539 kfree(buffer.pointer); 540 540 } 541 541
-2
drivers/thermal/intel/intel_hfi.c
··· 243 243 244 244 hfi_instance = container_of(to_delayed_work(work), struct hfi_instance, 245 245 update_work); 246 - if (!hfi_instance) 247 - return; 248 246 249 247 update_capabilities(hfi_instance); 250 248 }
+28 -15
drivers/thermal/intel/intel_pch_thermal.c
··· 70 70 module_param(delay_timeout, int, 0644); 71 71 MODULE_PARM_DESC(delay_timeout, "amount of time delay for each iteration."); 72 72 73 - /* Number of iterations for cooling delay, 10 counts by default for now */ 74 - static unsigned int delay_cnt = 10; 73 + /* Number of iterations for cooling delay, 600 counts by default for now */ 74 + static unsigned int delay_cnt = 600; 75 75 module_param(delay_cnt, int, 0644); 76 76 MODULE_PARM_DESC(delay_cnt, "total number of iterations for time delay."); 77 77 ··· 193 193 return 0; 194 194 } 195 195 196 + /* Cool the PCH when it's overheat in .suspend_noirq phase */ 196 197 static int pch_wpt_suspend(struct pch_thermal_device *ptd) 197 198 { 198 199 u8 tsel; 199 - u8 pch_delay_cnt = 1; 200 + int pch_delay_cnt = 0; 200 201 u16 pch_thr_temp, pch_cur_temp; 201 202 202 203 /* Shutdown the thermal sensor if it is not enabled by BIOS */ ··· 233 232 * temperature stays above threshold, notify the warning message 234 233 * which helps to indentify the reason why S0ix entry was rejected. 235 234 */ 236 - while (pch_delay_cnt <= delay_cnt) { 237 - if (pch_cur_temp <= pch_thr_temp) 235 + while (pch_delay_cnt < delay_cnt) { 236 + if (pch_cur_temp < pch_thr_temp) 238 237 break; 239 238 240 - dev_warn(&ptd->pdev->dev, 239 + if (pm_wakeup_pending()) { 240 + dev_warn(&ptd->pdev->dev, "Wakeup event detected, abort cooling\n"); 241 + return 0; 242 + } 243 + 244 + pch_delay_cnt++; 245 + dev_dbg(&ptd->pdev->dev, 241 246 "CPU-PCH current temp [%dC] higher than the threshold temp [%dC], sleep %d times for %d ms duration\n", 242 247 pch_cur_temp, pch_thr_temp, pch_delay_cnt, delay_timeout); 243 248 msleep(delay_timeout); 244 249 /* Read the PCH current temperature for next cycle. */ 245 250 pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP)); 246 - pch_delay_cnt++; 247 251 } 248 252 249 - if (pch_cur_temp > pch_thr_temp) 253 + if (pch_cur_temp >= pch_thr_temp) 250 254 dev_warn(&ptd->pdev->dev, 251 - "CPU-PCH is hot [%dC] even after delay, continue to suspend. S0ix might fail\n", 252 - pch_cur_temp); 253 - else 254 - dev_info(&ptd->pdev->dev, 255 - "CPU-PCH is cool [%dC], continue to suspend\n", pch_cur_temp); 255 + "CPU-PCH is hot [%dC] after %d ms delay. S0ix might fail\n", 256 + pch_cur_temp, pch_delay_cnt * delay_timeout); 257 + else { 258 + if (pch_delay_cnt) 259 + dev_info(&ptd->pdev->dev, 260 + "CPU-PCH is cool [%dC] after %d ms delay\n", 261 + pch_cur_temp, pch_delay_cnt * delay_timeout); 262 + else 263 + dev_info(&ptd->pdev->dev, 264 + "CPU-PCH is cool [%dC]\n", 265 + pch_cur_temp); 266 + } 256 267 257 268 return 0; 258 269 } ··· 468 455 pci_disable_device(pdev); 469 456 } 470 457 471 - static int intel_pch_thermal_suspend(struct device *device) 458 + static int intel_pch_thermal_suspend_noirq(struct device *device) 472 459 { 473 460 struct pch_thermal_device *ptd = dev_get_drvdata(device); 474 461 ··· 508 495 MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id); 509 496 510 497 static const struct dev_pm_ops intel_pch_pm_ops = { 511 - .suspend = intel_pch_thermal_suspend, 498 + .suspend_noirq = intel_pch_thermal_suspend_noirq, 512 499 .resume = intel_pch_thermal_resume, 513 500 }; 514 501
+5
drivers/thermal/k3_bandgap.c
··· 16 16 #include <linux/thermal.h> 17 17 #include <linux/types.h> 18 18 19 + #include "thermal_hwmon.h" 20 + 19 21 #define K3_VTM_DEVINFO_PWR0_OFFSET 0x4 20 22 #define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0 21 23 #define K3_VTM_TMPSENS0_CTRL_OFFSET 0x80 ··· 221 219 ret = PTR_ERR(data[id].tzd); 222 220 goto err_alloc; 223 221 } 222 + 223 + if (devm_thermal_add_hwmon_sysfs(data[id].tzd)) 224 + dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); 224 225 } 225 226 226 227 platform_set_drvdata(pdev, bgp);
+566
drivers/thermal/k3_j72xx_bandgap.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * TI Bandgap temperature sensor driver for J72XX SoC Family 4 + * 5 + * Copyright (C) 2021 Texas Instruments Incorporated - http://www.ti.com/ 6 + */ 7 + 8 + #include <linux/math.h> 9 + #include <linux/math64.h> 10 + #include <linux/module.h> 11 + #include <linux/init.h> 12 + #include <linux/kernel.h> 13 + #include <linux/pm_runtime.h> 14 + #include <linux/err.h> 15 + #include <linux/types.h> 16 + #include <linux/of_platform.h> 17 + #include <linux/io.h> 18 + #include <linux/thermal.h> 19 + #include <linux/of.h> 20 + #include <linux/delay.h> 21 + #include <linux/slab.h> 22 + 23 + #define K3_VTM_DEVINFO_PWR0_OFFSET 0x4 24 + #define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0 25 + #define K3_VTM_TMPSENS0_CTRL_OFFSET 0x300 26 + #define K3_VTM_MISC_CTRL_OFFSET 0xc 27 + #define K3_VTM_TMPSENS_STAT_OFFSET 0x8 28 + #define K3_VTM_ANYMAXT_OUTRG_ALERT_EN 0x1 29 + #define K3_VTM_MISC_CTRL2_OFFSET 0x10 30 + #define K3_VTM_TS_STAT_DTEMP_MASK 0x3ff 31 + #define K3_VTM_MAX_NUM_TS 8 32 + #define K3_VTM_TMPSENS_CTRL_SOC BIT(5) 33 + #define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6) 34 + #define K3_VTM_TMPSENS_CTRL_CLKON_REQ BIT(7) 35 + #define K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN BIT(11) 36 + 37 + #define K3_VTM_CORRECTION_TEMP_CNT 3 38 + 39 + #define MINUS40CREF 5 40 + #define PLUS30CREF 253 41 + #define PLUS125CREF 730 42 + #define PLUS150CREF 940 43 + 44 + #define TABLE_SIZE 1024 45 + #define MAX_TEMP 123000 46 + #define COOL_DOWN_TEMP 105000 47 + 48 + #define FACTORS_REDUCTION 13 49 + static int *derived_table; 50 + 51 + static int compute_value(int index, const s64 *factors, int nr_factors, 52 + int reduction) 53 + { 54 + s64 value = 0; 55 + int i; 56 + 57 + for (i = 0; i < nr_factors; i++) 58 + value += factors[i] * int_pow(index, i); 59 + 60 + return (int)div64_s64(value, int_pow(10, reduction)); 61 + } 62 + 63 + static void init_table(int factors_size, int *table, const s64 *factors) 64 + { 65 + int i; 66 + 67 + for (i = 0; i < TABLE_SIZE; i++) 68 + table[i] = compute_value(i, factors, factors_size, 69 + FACTORS_REDUCTION); 70 + } 71 + 72 + /** 73 + * struct err_values - structure containing error/reference values 74 + * @refs: reference error values for -40C, 30C, 125C & 150C 75 + * @errs: Actual error values for -40C, 30C, 125C & 150C read from the efuse 76 + */ 77 + struct err_values { 78 + int refs[4]; 79 + int errs[4]; 80 + }; 81 + 82 + static void create_table_segments(struct err_values *err_vals, int seg, 83 + int *ref_table) 84 + { 85 + int m = 0, c, num, den, i, err, idx1, idx2, err1, err2, ref1, ref2; 86 + 87 + if (seg == 0) 88 + idx1 = 0; 89 + else 90 + idx1 = err_vals->refs[seg]; 91 + 92 + idx2 = err_vals->refs[seg + 1]; 93 + err1 = err_vals->errs[seg]; 94 + err2 = err_vals->errs[seg + 1]; 95 + ref1 = err_vals->refs[seg]; 96 + ref2 = err_vals->refs[seg + 1]; 97 + 98 + /* 99 + * Calculate the slope with adc values read from the register 100 + * as the y-axis param and err in adc value as x-axis param 101 + */ 102 + num = ref2 - ref1; 103 + den = err2 - err1; 104 + if (den) 105 + m = num / den; 106 + c = ref2 - m * err2; 107 + 108 + /* 109 + * Take care of divide by zero error if error values are same 110 + * Or when the slope is 0 111 + */ 112 + if (den != 0 && m != 0) { 113 + for (i = idx1; i <= idx2; i++) { 114 + err = (i - c) / m; 115 + if (((i + err) < 0) || ((i + err) >= TABLE_SIZE)) 116 + continue; 117 + derived_table[i] = ref_table[i + err]; 118 + } 119 + } else { /* Constant error take care of divide by zero */ 120 + for (i = idx1; i <= idx2; i++) { 121 + if (((i + err1) < 0) || ((i + err1) >= TABLE_SIZE)) 122 + continue; 123 + derived_table[i] = ref_table[i + err1]; 124 + } 125 + } 126 + } 127 + 128 + static int prep_lookup_table(struct err_values *err_vals, int *ref_table) 129 + { 130 + int inc, i, seg; 131 + 132 + /* 133 + * Fill up the lookup table under 3 segments 134 + * region -40C to +30C 135 + * region +30C to +125C 136 + * region +125C to +150C 137 + */ 138 + for (seg = 0; seg < 3; seg++) 139 + create_table_segments(err_vals, seg, ref_table); 140 + 141 + /* Get to the first valid temperature */ 142 + i = 0; 143 + while (!derived_table[i]) 144 + i++; 145 + 146 + /* 147 + * Get to the last zero index and back fill the temperature for 148 + * sake of continuity 149 + */ 150 + if (i) { 151 + /* 300 milli celsius steps */ 152 + while (i--) 153 + derived_table[i] = derived_table[i + 1] - 300; 154 + /* case 0 */ 155 + derived_table[i] = derived_table[i + 1] - 300; 156 + } 157 + 158 + /* 159 + * Fill the last trailing 0s which are unfilled with increments of 160 + * 100 milli celsius till 1023 code 161 + */ 162 + i = TABLE_SIZE - 1; 163 + while (!derived_table[i]) 164 + i--; 165 + 166 + i++; 167 + inc = 1; 168 + while (i < TABLE_SIZE) { 169 + derived_table[i] = derived_table[i - 1] + inc * 100; 170 + i++; 171 + } 172 + 173 + return 0; 174 + } 175 + 176 + struct k3_thermal_data; 177 + 178 + struct k3_j72xx_bandgap { 179 + struct device *dev; 180 + void __iomem *base; 181 + void __iomem *cfg2_base; 182 + void __iomem *fuse_base; 183 + struct k3_thermal_data *ts_data[K3_VTM_MAX_NUM_TS]; 184 + }; 185 + 186 + /* common data structures */ 187 + struct k3_thermal_data { 188 + struct k3_j72xx_bandgap *bgp; 189 + u32 ctrl_offset; 190 + u32 stat_offset; 191 + }; 192 + 193 + static int two_cmp(int tmp, int mask) 194 + { 195 + tmp = ~(tmp); 196 + tmp &= mask; 197 + tmp += 1; 198 + 199 + /* Return negative value */ 200 + return (0 - tmp); 201 + } 202 + 203 + static unsigned int vtm_get_best_value(unsigned int s0, unsigned int s1, 204 + unsigned int s2) 205 + { 206 + int d01 = abs(s0 - s1); 207 + int d02 = abs(s0 - s2); 208 + int d12 = abs(s1 - s2); 209 + 210 + if (d01 <= d02 && d01 <= d12) 211 + return (s0 + s1) / 2; 212 + 213 + if (d02 <= d01 && d02 <= d12) 214 + return (s0 + s2) / 2; 215 + 216 + return (s1 + s2) / 2; 217 + } 218 + 219 + static inline int k3_bgp_read_temp(struct k3_thermal_data *devdata, 220 + int *temp) 221 + { 222 + struct k3_j72xx_bandgap *bgp; 223 + unsigned int dtemp, s0, s1, s2; 224 + 225 + bgp = devdata->bgp; 226 + /* 227 + * Errata is applicable for am654 pg 1.0 silicon/J7ES. There 228 + * is a variation of the order for certain degree centigrade on AM654. 229 + * Work around that by getting the average of two closest 230 + * readings out of three readings everytime we want to 231 + * report temperatures. 232 + * 233 + * Errata workaround. 234 + */ 235 + s0 = readl(bgp->base + devdata->stat_offset) & 236 + K3_VTM_TS_STAT_DTEMP_MASK; 237 + s1 = readl(bgp->base + devdata->stat_offset) & 238 + K3_VTM_TS_STAT_DTEMP_MASK; 239 + s2 = readl(bgp->base + devdata->stat_offset) & 240 + K3_VTM_TS_STAT_DTEMP_MASK; 241 + dtemp = vtm_get_best_value(s0, s1, s2); 242 + 243 + if (dtemp < 0 || dtemp >= TABLE_SIZE) 244 + return -EINVAL; 245 + 246 + *temp = derived_table[dtemp]; 247 + 248 + return 0; 249 + } 250 + 251 + /* Get temperature callback function for thermal zone */ 252 + static int k3_thermal_get_temp(void *devdata, int *temp) 253 + { 254 + struct k3_thermal_data *data = devdata; 255 + int ret = 0; 256 + 257 + ret = k3_bgp_read_temp(data, temp); 258 + if (ret) 259 + return ret; 260 + 261 + return ret; 262 + } 263 + 264 + static const struct thermal_zone_of_device_ops k3_of_thermal_ops = { 265 + .get_temp = k3_thermal_get_temp, 266 + }; 267 + 268 + static int k3_j72xx_bandgap_temp_to_adc_code(int temp) 269 + { 270 + int low = 0, high = TABLE_SIZE - 1, mid; 271 + 272 + if (temp > 160000 || temp < -50000) 273 + return -EINVAL; 274 + 275 + /* Binary search to find the adc code */ 276 + while (low < (high - 1)) { 277 + mid = (low + high) / 2; 278 + if (temp <= derived_table[mid]) 279 + high = mid; 280 + else 281 + low = mid; 282 + } 283 + 284 + return mid; 285 + } 286 + 287 + static void get_efuse_values(int id, struct k3_thermal_data *data, int *err, 288 + struct k3_j72xx_bandgap *bgp) 289 + { 290 + int i, tmp, pow; 291 + int ct_offsets[5][K3_VTM_CORRECTION_TEMP_CNT] = { 292 + { 0x0, 0x8, 0x4 }, 293 + { 0x0, 0x8, 0x4 }, 294 + { 0x0, -1, 0x4 }, 295 + { 0x0, 0xC, -1 }, 296 + { 0x0, 0xc, 0x8 } 297 + }; 298 + int ct_bm[5][K3_VTM_CORRECTION_TEMP_CNT] = { 299 + { 0x3f, 0x1fe000, 0x1ff }, 300 + { 0xfc0, 0x1fe000, 0x3fe00 }, 301 + { 0x3f000, 0x7f800000, 0x7fc0000 }, 302 + { 0xfc0000, 0x1fe0, 0x1f800000 }, 303 + { 0x3f000000, 0x1fe000, 0x1ff0 } 304 + }; 305 + 306 + for (i = 0; i < 3; i++) { 307 + /* Extract the offset value using bit-mask */ 308 + if (ct_offsets[id][i] == -1 && i == 1) { 309 + /* 25C offset Case of Sensor 2 split between 2 regs */ 310 + tmp = (readl(bgp->fuse_base + 0x8) & 0xE0000000) >> (29); 311 + tmp |= ((readl(bgp->fuse_base + 0xC) & 0x1F) << 3); 312 + pow = tmp & 0x80; 313 + } else if (ct_offsets[id][i] == -1 && i == 2) { 314 + /* 125C Case of Sensor 3 split between 2 regs */ 315 + tmp = (readl(bgp->fuse_base + 0x4) & 0xF8000000) >> (27); 316 + tmp |= ((readl(bgp->fuse_base + 0x8) & 0xF) << 5); 317 + pow = tmp & 0x100; 318 + } else { 319 + tmp = readl(bgp->fuse_base + ct_offsets[id][i]); 320 + tmp &= ct_bm[id][i]; 321 + tmp = tmp >> __ffs(ct_bm[id][i]); 322 + 323 + /* Obtain the sign bit pow*/ 324 + pow = ct_bm[id][i] >> __ffs(ct_bm[id][i]); 325 + pow += 1; 326 + pow /= 2; 327 + } 328 + 329 + /* Check for negative value */ 330 + if (tmp & pow) { 331 + /* 2's complement value */ 332 + tmp = two_cmp(tmp, ct_bm[id][i] >> __ffs(ct_bm[id][i])); 333 + } 334 + err[i] = tmp; 335 + } 336 + 337 + /* Err value for 150C is set to 0 */ 338 + err[i] = 0; 339 + } 340 + 341 + static void print_look_up_table(struct device *dev, int *ref_table) 342 + { 343 + int i; 344 + 345 + dev_dbg(dev, "The contents of derived array\n"); 346 + dev_dbg(dev, "Code Temperature\n"); 347 + for (i = 0; i < TABLE_SIZE; i++) 348 + dev_dbg(dev, "%d %d %d\n", i, derived_table[i], ref_table[i]); 349 + } 350 + 351 + struct k3_j72xx_bandgap_data { 352 + unsigned int has_errata_i2128; 353 + }; 354 + 355 + static int k3_j72xx_bandgap_probe(struct platform_device *pdev) 356 + { 357 + int ret = 0, cnt, val, id; 358 + int high_max, low_temp; 359 + struct resource *res; 360 + struct device *dev = &pdev->dev; 361 + struct k3_j72xx_bandgap *bgp; 362 + struct k3_thermal_data *data; 363 + int workaround_needed = 0; 364 + const struct k3_j72xx_bandgap_data *driver_data; 365 + struct thermal_zone_device *ti_thermal; 366 + int *ref_table; 367 + struct err_values err_vals; 368 + 369 + const s64 golden_factors[] = { 370 + -490019999999999936, 371 + 3251200000000000, 372 + -1705800000000, 373 + 603730000, 374 + -92627, 375 + }; 376 + 377 + const s64 pvt_wa_factors[] = { 378 + -415230000000000000, 379 + 3126600000000000, 380 + -1157800000000, 381 + }; 382 + 383 + bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL); 384 + if (!bgp) 385 + return -ENOMEM; 386 + 387 + bgp->dev = dev; 388 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 389 + bgp->base = devm_ioremap_resource(dev, res); 390 + if (IS_ERR(bgp->base)) 391 + return PTR_ERR(bgp->base); 392 + 393 + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 394 + bgp->cfg2_base = devm_ioremap_resource(dev, res); 395 + if (IS_ERR(bgp->cfg2_base)) 396 + return PTR_ERR(bgp->cfg2_base); 397 + 398 + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); 399 + bgp->fuse_base = devm_ioremap_resource(dev, res); 400 + if (IS_ERR(bgp->fuse_base)) 401 + return PTR_ERR(bgp->fuse_base); 402 + 403 + driver_data = of_device_get_match_data(dev); 404 + if (driver_data) 405 + workaround_needed = driver_data->has_errata_i2128; 406 + 407 + pm_runtime_enable(dev); 408 + ret = pm_runtime_get_sync(dev); 409 + if (ret < 0) { 410 + pm_runtime_put_noidle(dev); 411 + pm_runtime_disable(dev); 412 + return ret; 413 + } 414 + 415 + /* Get the sensor count in the VTM */ 416 + val = readl(bgp->base + K3_VTM_DEVINFO_PWR0_OFFSET); 417 + cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK; 418 + cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK); 419 + 420 + data = devm_kcalloc(bgp->dev, cnt, sizeof(*data), GFP_KERNEL); 421 + if (!data) { 422 + ret = -ENOMEM; 423 + goto err_alloc; 424 + } 425 + 426 + ref_table = kzalloc(sizeof(*ref_table) * TABLE_SIZE, GFP_KERNEL); 427 + if (!ref_table) { 428 + ret = -ENOMEM; 429 + goto err_alloc; 430 + } 431 + 432 + derived_table = devm_kzalloc(bgp->dev, sizeof(*derived_table) * TABLE_SIZE, 433 + GFP_KERNEL); 434 + if (!derived_table) { 435 + ret = -ENOMEM; 436 + goto err_alloc; 437 + } 438 + 439 + /* Workaround not needed if bit30/bit31 is set even for J721e */ 440 + if (workaround_needed && (readl(bgp->fuse_base + 0x0) & 0xc0000000) == 0xc0000000) 441 + workaround_needed = false; 442 + 443 + dev_dbg(bgp->dev, "Work around %sneeded\n", 444 + workaround_needed ? "not " : ""); 445 + 446 + if (!workaround_needed) 447 + init_table(5, ref_table, golden_factors); 448 + else 449 + init_table(3, ref_table, pvt_wa_factors); 450 + 451 + /* Register the thermal sensors */ 452 + for (id = 0; id < cnt; id++) { 453 + data[id].bgp = bgp; 454 + data[id].ctrl_offset = K3_VTM_TMPSENS0_CTRL_OFFSET + id * 0x20; 455 + data[id].stat_offset = data[id].ctrl_offset + 456 + K3_VTM_TMPSENS_STAT_OFFSET; 457 + 458 + if (workaround_needed) { 459 + /* ref adc values for -40C, 30C & 125C respectively */ 460 + err_vals.refs[0] = MINUS40CREF; 461 + err_vals.refs[1] = PLUS30CREF; 462 + err_vals.refs[2] = PLUS125CREF; 463 + err_vals.refs[3] = PLUS150CREF; 464 + get_efuse_values(id, &data[id], err_vals.errs, bgp); 465 + } 466 + 467 + if (id == 0 && workaround_needed) 468 + prep_lookup_table(&err_vals, ref_table); 469 + else if (id == 0 && !workaround_needed) 470 + memcpy(derived_table, ref_table, TABLE_SIZE * 4); 471 + 472 + val = readl(data[id].bgp->cfg2_base + data[id].ctrl_offset); 473 + val |= (K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN | 474 + K3_VTM_TMPSENS_CTRL_SOC | 475 + K3_VTM_TMPSENS_CTRL_CLRZ | BIT(4)); 476 + writel(val, data[id].bgp->cfg2_base + data[id].ctrl_offset); 477 + 478 + bgp->ts_data[id] = &data[id]; 479 + ti_thermal = 480 + devm_thermal_zone_of_sensor_register(bgp->dev, id, 481 + &data[id], 482 + &k3_of_thermal_ops); 483 + if (IS_ERR(ti_thermal)) { 484 + dev_err(bgp->dev, "thermal zone device is NULL\n"); 485 + ret = PTR_ERR(ti_thermal); 486 + goto err_alloc; 487 + } 488 + } 489 + 490 + /* 491 + * Program TSHUT thresholds 492 + * Step 1: set the thresholds to ~123C and 105C WKUP_VTM_MISC_CTRL2 493 + * Step 2: WKUP_VTM_TMPSENS_CTRL_j set the MAXT_OUTRG_EN bit 494 + * This is already taken care as per of init 495 + * Step 3: WKUP_VTM_MISC_CTRL set the ANYMAXT_OUTRG_ALERT_EN bit 496 + */ 497 + high_max = k3_j72xx_bandgap_temp_to_adc_code(MAX_TEMP); 498 + low_temp = k3_j72xx_bandgap_temp_to_adc_code(COOL_DOWN_TEMP); 499 + 500 + writel((low_temp << 16) | high_max, data[0].bgp->cfg2_base + 501 + K3_VTM_MISC_CTRL2_OFFSET); 502 + mdelay(100); 503 + writel(K3_VTM_ANYMAXT_OUTRG_ALERT_EN, data[0].bgp->cfg2_base + 504 + K3_VTM_MISC_CTRL_OFFSET); 505 + 506 + platform_set_drvdata(pdev, bgp); 507 + 508 + print_look_up_table(dev, ref_table); 509 + /* 510 + * Now that the derived_table has the appropriate look up values 511 + * Free up the ref_table 512 + */ 513 + kfree(ref_table); 514 + 515 + return 0; 516 + 517 + err_alloc: 518 + pm_runtime_put_sync(&pdev->dev); 519 + pm_runtime_disable(&pdev->dev); 520 + 521 + return ret; 522 + } 523 + 524 + static int k3_j72xx_bandgap_remove(struct platform_device *pdev) 525 + { 526 + pm_runtime_put_sync(&pdev->dev); 527 + pm_runtime_disable(&pdev->dev); 528 + 529 + return 0; 530 + } 531 + 532 + const struct k3_j72xx_bandgap_data k3_j72xx_bandgap_j721e_data = { 533 + .has_errata_i2128 = 1, 534 + }; 535 + 536 + const struct k3_j72xx_bandgap_data k3_j72xx_bandgap_j7200_data = { 537 + .has_errata_i2128 = 0, 538 + }; 539 + 540 + static const struct of_device_id of_k3_j72xx_bandgap_match[] = { 541 + { 542 + .compatible = "ti,j721e-vtm", 543 + .data = &k3_j72xx_bandgap_j721e_data, 544 + }, 545 + { 546 + .compatible = "ti,j7200-vtm", 547 + .data = &k3_j72xx_bandgap_j7200_data, 548 + }, 549 + { /* sentinel */ }, 550 + }; 551 + MODULE_DEVICE_TABLE(of, of_k3_j72xx_bandgap_match); 552 + 553 + static struct platform_driver k3_j72xx_bandgap_sensor_driver = { 554 + .probe = k3_j72xx_bandgap_probe, 555 + .remove = k3_j72xx_bandgap_remove, 556 + .driver = { 557 + .name = "k3-j72xx-soc-thermal", 558 + .of_match_table = of_k3_j72xx_bandgap_match, 559 + }, 560 + }; 561 + 562 + module_platform_driver(k3_j72xx_bandgap_sensor_driver); 563 + 564 + MODULE_DESCRIPTION("K3 bandgap temperature sensor driver"); 565 + MODULE_LICENSE("GPL"); 566 + MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>");
+1
drivers/thermal/qcom/lmh.c
··· 220 220 } 221 221 222 222 static const struct of_device_id lmh_table[] = { 223 + { .compatible = "qcom,sc8180x-lmh", }, 223 224 { .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS}, 224 225 { .compatible = "qcom,sm8150-lmh", }, 225 226 {}
+448 -38
drivers/thermal/qcom/qcom-spmi-adc-tm5.c
··· 4 4 * 5 5 * Based on original driver: 6 6 * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. 7 + * 8 + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. 7 9 */ 10 + 8 11 #include <linux/bitfield.h> 9 12 #include <linux/iio/adc/qcom-vadc-common.h> 10 13 #include <linux/iio/consumer.h> ··· 18 15 #include <linux/platform_device.h> 19 16 #include <linux/regmap.h> 20 17 #include <linux/thermal.h> 18 + #include <asm-generic/unaligned.h> 21 19 22 20 /* 23 21 * Thermal monitoring block consists of 8 (ADC_TM5_NUM_CHANNELS) channels. Each ··· 75 71 #define ADC_TM5_M_HIGH_THR_INT_EN BIT(1) 76 72 #define ADC_TM5_M_LOW_THR_INT_EN BIT(0) 77 73 74 + #define ADC_TM_GEN2_STATUS1 0x08 75 + #define ADC_TM_GEN2_STATUS_LOW_SET 0x09 76 + #define ADC_TM_GEN2_STATUS_LOW_CLR 0x0a 77 + #define ADC_TM_GEN2_STATUS_HIGH_SET 0x0b 78 + #define ADC_TM_GEN2_STATUS_HIGH_CLR 0x0c 79 + 80 + #define ADC_TM_GEN2_CFG_HS_SET 0x0d 81 + #define ADC_TM_GEN2_CFG_HS_FLAG BIT(0) 82 + #define ADC_TM_GEN2_CFG_HS_CLR 0x0e 83 + 84 + #define ADC_TM_GEN2_SID 0x40 85 + 86 + #define ADC_TM_GEN2_CH_CTL 0x41 87 + #define ADC_TM_GEN2_TM_CH_SEL GENMASK(7, 5) 88 + #define ADC_TM_GEN2_MEAS_INT_SEL GENMASK(3, 2) 89 + 90 + #define ADC_TM_GEN2_ADC_DIG_PARAM 0x42 91 + #define ADC_TM_GEN2_CTL_CAL_SEL GENMASK(5, 4) 92 + #define ADC_TM_GEN2_CTL_DEC_RATIO_MASK GENMASK(3, 2) 93 + 94 + #define ADC_TM_GEN2_FAST_AVG_CTL 0x43 95 + #define ADC_TM_GEN2_FAST_AVG_EN BIT(7) 96 + 97 + #define ADC_TM_GEN2_ADC_CH_SEL_CTL 0x44 98 + 99 + #define ADC_TM_GEN2_DELAY_CTL 0x45 100 + #define ADC_TM_GEN2_HW_SETTLE_DELAY GENMASK(3, 0) 101 + 102 + #define ADC_TM_GEN2_EN_CTL1 0x46 103 + #define ADC_TM_GEN2_EN BIT(7) 104 + 105 + #define ADC_TM_GEN2_CONV_REQ 0x47 106 + #define ADC_TM_GEN2_CONV_REQ_EN BIT(7) 107 + 108 + #define ADC_TM_GEN2_LOW_THR0 0x49 109 + #define ADC_TM_GEN2_LOW_THR1 0x4a 110 + #define ADC_TM_GEN2_HIGH_THR0 0x4b 111 + #define ADC_TM_GEN2_HIGH_THR1 0x4c 112 + #define ADC_TM_GEN2_LOWER_MASK(n) ((n) & GENMASK(7, 0)) 113 + #define ADC_TM_GEN2_UPPER_MASK(n) (((n) & GENMASK(15, 8)) >> 8) 114 + 115 + #define ADC_TM_GEN2_MEAS_IRQ_EN 0x4d 116 + #define ADC_TM_GEN2_MEAS_EN BIT(7) 117 + #define ADC_TM5_GEN2_HIGH_THR_INT_EN BIT(1) 118 + #define ADC_TM5_GEN2_LOW_THR_INT_EN BIT(0) 119 + 120 + #define ADC_TM_GEN2_MEAS_INT_LSB 0x50 121 + #define ADC_TM_GEN2_MEAS_INT_MSB 0x51 122 + #define ADC_TM_GEN2_MEAS_INT_MODE 0x52 123 + 124 + #define ADC_TM_GEN2_Mn_DATA0(n) ((n * 2) + 0xa0) 125 + #define ADC_TM_GEN2_Mn_DATA1(n) ((n * 2) + 0xa1) 126 + #define ADC_TM_GEN2_DATA_SHIFT 8 127 + 78 128 enum adc5_timer_select { 79 129 ADC5_TIMER_SEL_1 = 0, 80 130 ADC5_TIMER_SEL_2, ··· 136 78 ADC5_TIMER_SEL_NONE, 137 79 }; 138 80 139 - struct adc_tm5_data { 140 - const u32 full_scale_code_volt; 141 - unsigned int *decimation; 142 - unsigned int *hw_settle; 143 - bool is_hc; 81 + enum adc5_gen { 82 + ADC_TM5, 83 + ADC_TM_HC, 84 + ADC_TM5_GEN2, 85 + ADC_TM5_MAX 144 86 }; 145 87 146 88 enum adc_tm5_cal_method { ··· 149 91 ADC_TM5_ABSOLUTE_CAL 150 92 }; 151 93 94 + enum adc_tm_gen2_time_select { 95 + MEAS_INT_50MS = 0, 96 + MEAS_INT_100MS, 97 + MEAS_INT_1S, 98 + MEAS_INT_SET, 99 + MEAS_INT_NONE, 100 + }; 101 + 152 102 struct adc_tm5_chip; 103 + struct adc_tm5_channel; 104 + 105 + struct adc_tm5_data { 106 + const u32 full_scale_code_volt; 107 + unsigned int *decimation; 108 + unsigned int *hw_settle; 109 + int (*disable_channel)(struct adc_tm5_channel *channel); 110 + int (*configure)(struct adc_tm5_channel *channel, int low, int high); 111 + irqreturn_t (*isr)(int irq, void *data); 112 + int (*init)(struct adc_tm5_chip *chip); 113 + char *irq_name; 114 + int gen; 115 + }; 153 116 154 117 /** 155 118 * struct adc_tm5_channel - ADC Thermal Monitoring channel data. ··· 180 101 * @prescale: channel scaling performed on the input signal. 181 102 * @hw_settle_time: the time between AMUX being configured and the 182 103 * start of conversion. 104 + * @decimation: sampling rate supported for the channel. 105 + * @avg_samples: ability to provide single result from the ADC 106 + * that is an average of multiple measurements. 107 + * @high_thr_en: channel upper voltage threshold enable state. 108 + * @low_thr_en: channel lower voltage threshold enable state. 109 + * @meas_en: recurring measurement enable state 183 110 * @iio: IIO channel instance used by this channel. 184 111 * @chip: ADC TM chip instance. 185 112 * @tzd: thermal zone device used by this channel. ··· 196 111 enum adc_tm5_cal_method cal_method; 197 112 unsigned int prescale; 198 113 unsigned int hw_settle_time; 114 + unsigned int decimation; /* For Gen2 ADC_TM */ 115 + unsigned int avg_samples; /* For Gen2 ADC_TM */ 116 + bool high_thr_en; /* For Gen2 ADC_TM */ 117 + bool low_thr_en; /* For Gen2 ADC_TM */ 118 + bool meas_en; /* For Gen2 ADC_TM */ 199 119 struct iio_channel *iio; 200 120 struct adc_tm5_chip *chip; 201 121 struct thermal_zone_device *tzd; ··· 214 124 * @channels: array of ADC TM channel data. 215 125 * @nchannels: amount of channels defined/allocated 216 126 * @decimation: sampling rate supported for the channel. 127 + * Applies to all channels, used only on Gen1 ADC_TM. 217 128 * @avg_samples: ability to provide single result from the ADC 218 - * that is an average of multiple measurements. 129 + * that is an average of multiple measurements. Applies to all 130 + * channels, used only on Gen1 ADC_TM. 219 131 * @base: base address of TM registers. 132 + * @adc_mutex_lock: ADC_TM mutex lock, used only on Gen2 ADC_TM. 133 + * It is used to ensure only one ADC channel configuration 134 + * is done at a time using the shared set of configuration 135 + * registers. 220 136 */ 221 137 struct adc_tm5_chip { 222 138 struct regmap *regmap; ··· 233 137 unsigned int decimation; 234 138 unsigned int avg_samples; 235 139 u16 base; 236 - }; 237 - 238 - static const struct adc_tm5_data adc_tm5_data_pmic = { 239 - .full_scale_code_volt = 0x70e4, 240 - .decimation = (unsigned int []) { 250, 420, 840 }, 241 - .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700, 242 - 1000, 2000, 4000, 8000, 16000, 32000, 243 - 64000, 128000 }, 244 - }; 245 - 246 - static const struct adc_tm5_data adc_tm_hc_data_pmic = { 247 - .full_scale_code_volt = 0x70e4, 248 - .decimation = (unsigned int []) { 256, 512, 1024 }, 249 - .hw_settle = (unsigned int []) { 0, 100, 200, 300, 400, 500, 600, 700, 250 - 1000, 2000, 4000, 6000, 8000, 10000 }, 251 - .is_hc = true, 140 + struct mutex adc_mutex_lock; 252 141 }; 253 142 254 143 static int adc_tm5_read(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len) ··· 300 219 return IRQ_HANDLED; 301 220 } 302 221 222 + static irqreturn_t adc_tm5_gen2_isr(int irq, void *data) 223 + { 224 + struct adc_tm5_chip *chip = data; 225 + u8 status_low, status_high; 226 + int ret, i; 227 + 228 + ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low)); 229 + if (ret) { 230 + dev_err(chip->dev, "read status_low failed: %d\n", ret); 231 + return IRQ_HANDLED; 232 + } 233 + 234 + ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high)); 235 + if (ret) { 236 + dev_err(chip->dev, "read status_high failed: %d\n", ret); 237 + return IRQ_HANDLED; 238 + } 239 + 240 + ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low)); 241 + if (ret < 0) { 242 + dev_err(chip->dev, "clear status low failed with %d\n", ret); 243 + return IRQ_HANDLED; 244 + } 245 + 246 + ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high)); 247 + if (ret < 0) { 248 + dev_err(chip->dev, "clear status high failed with %d\n", ret); 249 + return IRQ_HANDLED; 250 + } 251 + 252 + for (i = 0; i < chip->nchannels; i++) { 253 + bool upper_set = false, lower_set = false; 254 + unsigned int ch = chip->channels[i].channel; 255 + 256 + /* No TZD, we warned at the boot time */ 257 + if (!chip->channels[i].tzd) 258 + continue; 259 + 260 + if (!chip->channels[i].meas_en) 261 + continue; 262 + 263 + lower_set = (status_low & BIT(ch)) && 264 + (chip->channels[i].low_thr_en); 265 + 266 + upper_set = (status_high & BIT(ch)) && 267 + (chip->channels[i].high_thr_en); 268 + 269 + if (upper_set || lower_set) 270 + thermal_zone_device_update(chip->channels[i].tzd, 271 + THERMAL_EVENT_UNSPECIFIED); 272 + } 273 + 274 + return IRQ_HANDLED; 275 + } 276 + 303 277 static int adc_tm5_get_temp(void *data, int *temp) 304 278 { 305 279 struct adc_tm5_channel *channel = data; ··· 383 247 ADC_TM5_M_HIGH_THR_INT_EN | 384 248 ADC_TM5_M_LOW_THR_INT_EN, 385 249 0); 250 + } 251 + 252 + #define ADC_TM_GEN2_POLL_DELAY_MIN_US 100 253 + #define ADC_TM_GEN2_POLL_DELAY_MAX_US 110 254 + #define ADC_TM_GEN2_POLL_RETRY_COUNT 3 255 + 256 + static int32_t adc_tm5_gen2_conv_req(struct adc_tm5_chip *chip) 257 + { 258 + int ret; 259 + u8 data; 260 + unsigned int count; 261 + 262 + data = ADC_TM_GEN2_EN; 263 + ret = adc_tm5_write(chip, ADC_TM_GEN2_EN_CTL1, &data, 1); 264 + if (ret < 0) { 265 + dev_err(chip->dev, "adc-tm enable failed with %d\n", ret); 266 + return ret; 267 + } 268 + 269 + data = ADC_TM_GEN2_CFG_HS_FLAG; 270 + ret = adc_tm5_write(chip, ADC_TM_GEN2_CFG_HS_SET, &data, 1); 271 + if (ret < 0) { 272 + dev_err(chip->dev, "adc-tm handshake failed with %d\n", ret); 273 + return ret; 274 + } 275 + 276 + data = ADC_TM_GEN2_CONV_REQ_EN; 277 + ret = adc_tm5_write(chip, ADC_TM_GEN2_CONV_REQ, &data, 1); 278 + if (ret < 0) { 279 + dev_err(chip->dev, "adc-tm request conversion failed with %d\n", ret); 280 + return ret; 281 + } 282 + 283 + /* 284 + * SW sets a handshake bit and waits for PBS to clear it 285 + * before the next conversion request can be queued. 286 + */ 287 + 288 + for (count = 0; count < ADC_TM_GEN2_POLL_RETRY_COUNT; count++) { 289 + ret = adc_tm5_read(chip, ADC_TM_GEN2_CFG_HS_SET, &data, sizeof(data)); 290 + if (ret < 0) { 291 + dev_err(chip->dev, "adc-tm read failed with %d\n", ret); 292 + return ret; 293 + } 294 + 295 + if (!(data & ADC_TM_GEN2_CFG_HS_FLAG)) 296 + return ret; 297 + usleep_range(ADC_TM_GEN2_POLL_DELAY_MIN_US, 298 + ADC_TM_GEN2_POLL_DELAY_MAX_US); 299 + } 300 + 301 + dev_err(chip->dev, "adc-tm conversion request handshake timed out\n"); 302 + 303 + return -ETIMEDOUT; 304 + } 305 + 306 + static int adc_tm5_gen2_disable_channel(struct adc_tm5_channel *channel) 307 + { 308 + struct adc_tm5_chip *chip = channel->chip; 309 + int ret; 310 + u8 val; 311 + 312 + mutex_lock(&chip->adc_mutex_lock); 313 + 314 + channel->meas_en = false; 315 + channel->high_thr_en = false; 316 + channel->low_thr_en = false; 317 + 318 + ret = adc_tm5_read(chip, ADC_TM_GEN2_CH_CTL, &val, sizeof(val)); 319 + if (ret < 0) { 320 + dev_err(chip->dev, "adc-tm block read failed with %d\n", ret); 321 + goto disable_fail; 322 + } 323 + 324 + val &= ~ADC_TM_GEN2_TM_CH_SEL; 325 + val |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel); 326 + 327 + ret = adc_tm5_write(chip, ADC_TM_GEN2_CH_CTL, &val, 1); 328 + if (ret < 0) { 329 + dev_err(chip->dev, "adc-tm channel disable failed with %d\n", ret); 330 + goto disable_fail; 331 + } 332 + 333 + val = 0; 334 + ret = adc_tm5_write(chip, ADC_TM_GEN2_MEAS_IRQ_EN, &val, 1); 335 + if (ret < 0) { 336 + dev_err(chip->dev, "adc-tm interrupt disable failed with %d\n", ret); 337 + goto disable_fail; 338 + } 339 + 340 + 341 + ret = adc_tm5_gen2_conv_req(channel->chip); 342 + if (ret < 0) 343 + dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret); 344 + 345 + disable_fail: 346 + mutex_unlock(&chip->adc_mutex_lock); 347 + return ret; 386 348 } 387 349 388 350 static int adc_tm5_enable(struct adc_tm5_chip *chip) ··· 525 291 u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale, 526 292 chip->data->full_scale_code_volt, high); 527 293 528 - buf[1] = adc_code & 0xff; 529 - buf[2] = adc_code >> 8; 294 + put_unaligned_le16(adc_code, &buf[1]); 530 295 buf[7] |= ADC_TM5_M_LOW_THR_INT_EN; 531 296 } else { 532 297 buf[7] &= ~ADC_TM5_M_LOW_THR_INT_EN; ··· 536 303 u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale, 537 304 chip->data->full_scale_code_volt, low); 538 305 539 - buf[3] = adc_code & 0xff; 540 - buf[4] = adc_code >> 8; 306 + put_unaligned_le16(adc_code, &buf[3]); 541 307 buf[7] |= ADC_TM5_M_HIGH_THR_INT_EN; 542 308 } else { 543 309 buf[7] &= ~ADC_TM5_M_HIGH_THR_INT_EN; ··· 561 329 return adc_tm5_enable(chip); 562 330 } 563 331 332 + static int adc_tm5_gen2_configure(struct adc_tm5_channel *channel, int low, int high) 333 + { 334 + struct adc_tm5_chip *chip = channel->chip; 335 + int ret; 336 + u8 buf[14]; 337 + u16 adc_code; 338 + 339 + mutex_lock(&chip->adc_mutex_lock); 340 + 341 + channel->meas_en = true; 342 + 343 + ret = adc_tm5_read(chip, ADC_TM_GEN2_SID, buf, sizeof(buf)); 344 + if (ret < 0) { 345 + dev_err(chip->dev, "adc-tm block read failed with %d\n", ret); 346 + goto config_fail; 347 + } 348 + 349 + /* Set SID from virtual channel number */ 350 + buf[0] = channel->adc_channel >> 8; 351 + 352 + /* Set TM channel number used and measurement interval */ 353 + buf[1] &= ~ADC_TM_GEN2_TM_CH_SEL; 354 + buf[1] |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel); 355 + buf[1] &= ~ADC_TM_GEN2_MEAS_INT_SEL; 356 + buf[1] |= FIELD_PREP(ADC_TM_GEN2_MEAS_INT_SEL, MEAS_INT_1S); 357 + 358 + buf[2] &= ~ADC_TM_GEN2_CTL_DEC_RATIO_MASK; 359 + buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_DEC_RATIO_MASK, channel->decimation); 360 + buf[2] &= ~ADC_TM_GEN2_CTL_CAL_SEL; 361 + buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_CAL_SEL, channel->cal_method); 362 + 363 + buf[3] = channel->avg_samples | ADC_TM_GEN2_FAST_AVG_EN; 364 + 365 + buf[4] = channel->adc_channel & 0xff; 366 + 367 + buf[5] = channel->hw_settle_time & ADC_TM_GEN2_HW_SETTLE_DELAY; 368 + 369 + /* High temperature corresponds to low voltage threshold */ 370 + if (high != INT_MAX) { 371 + channel->low_thr_en = true; 372 + adc_code = qcom_adc_tm5_gen2_temp_res_scale(high); 373 + put_unaligned_le16(adc_code, &buf[9]); 374 + } else { 375 + channel->low_thr_en = false; 376 + } 377 + 378 + /* Low temperature corresponds to high voltage threshold */ 379 + if (low != -INT_MAX) { 380 + channel->high_thr_en = true; 381 + adc_code = qcom_adc_tm5_gen2_temp_res_scale(low); 382 + put_unaligned_le16(adc_code, &buf[11]); 383 + } else { 384 + channel->high_thr_en = false; 385 + } 386 + 387 + buf[13] = ADC_TM_GEN2_MEAS_EN; 388 + if (channel->high_thr_en) 389 + buf[13] |= ADC_TM5_GEN2_HIGH_THR_INT_EN; 390 + if (channel->low_thr_en) 391 + buf[13] |= ADC_TM5_GEN2_LOW_THR_INT_EN; 392 + 393 + ret = adc_tm5_write(chip, ADC_TM_GEN2_SID, buf, sizeof(buf)); 394 + if (ret) { 395 + dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret); 396 + goto config_fail; 397 + } 398 + 399 + ret = adc_tm5_gen2_conv_req(channel->chip); 400 + if (ret < 0) 401 + dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret); 402 + 403 + config_fail: 404 + mutex_unlock(&chip->adc_mutex_lock); 405 + return ret; 406 + } 407 + 564 408 static int adc_tm5_set_trips(void *data, int low, int high) 565 409 { 566 410 struct adc_tm5_channel *channel = data; ··· 651 343 channel->channel, low, high); 652 344 653 345 if (high == INT_MAX && low <= -INT_MAX) 654 - ret = adc_tm5_disable_channel(channel); 346 + ret = chip->data->disable_channel(channel); 655 347 else 656 - ret = adc_tm5_configure(channel, low, high); 348 + ret = chip->data->configure(channel, low, high); 657 349 658 350 return ret; 659 351 } 660 352 661 - static struct thermal_zone_of_device_ops adc_tm5_ops = { 353 + static struct thermal_zone_of_device_ops adc_tm5_thermal_ops = { 662 354 .get_temp = adc_tm5_get_temp, 663 355 .set_trips = adc_tm5_set_trips, 664 356 }; ··· 674 366 tzd = devm_thermal_zone_of_sensor_register(adc_tm->dev, 675 367 adc_tm->channels[i].channel, 676 368 &adc_tm->channels[i], 677 - &adc_tm5_ops); 369 + &adc_tm5_thermal_ops); 678 370 if (IS_ERR(tzd)) { 679 371 if (PTR_ERR(tzd) == -ENODEV) { 680 372 dev_warn(adc_tm->dev, "thermal sensor on channel %d is not used\n", ··· 750 442 return ret; 751 443 } 752 444 445 + static int adc_tm5_gen2_init(struct adc_tm5_chip *chip) 446 + { 447 + u8 channels_available; 448 + int ret; 449 + unsigned int i; 450 + 451 + ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM, 452 + &channels_available, sizeof(channels_available)); 453 + if (ret) { 454 + dev_err(chip->dev, "read failed for BTM channels\n"); 455 + return ret; 456 + } 457 + 458 + for (i = 0; i < chip->nchannels; i++) { 459 + if (chip->channels[i].channel >= channels_available) { 460 + dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel); 461 + return -EINVAL; 462 + } 463 + } 464 + 465 + mutex_init(&chip->adc_mutex_lock); 466 + 467 + return ret; 468 + } 469 + 753 470 static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm, 754 471 struct adc_tm5_channel *channel, 755 472 struct device_node *node) 756 473 { 757 474 const char *name = node->name; 758 - u32 chan, value, varr[2]; 475 + u32 chan, value, adc_channel, varr[2]; 759 476 int ret; 760 477 struct device *dev = adc_tm->dev; 761 478 struct of_phandle_args args; ··· 810 477 } 811 478 of_node_put(args.np); 812 479 813 - if (args.args_count != 1 || args.args[0] >= ADC5_MAX_CHANNEL) { 480 + if (args.args_count != 1) { 481 + dev_err(dev, "%s: invalid args count for ADC channel %d\n", name, chan); 482 + return -EINVAL; 483 + } 484 + 485 + adc_channel = args.args[0]; 486 + if (adc_tm->data->gen == ADC_TM5_GEN2) 487 + adc_channel &= 0xff; 488 + 489 + if (adc_channel >= ADC5_MAX_CHANNEL) { 814 490 dev_err(dev, "%s: invalid ADC channel number %d\n", name, chan); 815 491 return -EINVAL; 816 492 } ··· 865 523 else 866 524 channel->cal_method = ADC_TM5_ABSOLUTE_CAL; 867 525 526 + if (adc_tm->data->gen == ADC_TM5_GEN2) { 527 + ret = of_property_read_u32(node, "qcom,decimation", &value); 528 + if (!ret) { 529 + ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation); 530 + if (ret < 0) { 531 + dev_err(dev, "invalid decimation %d\n", value); 532 + return ret; 533 + } 534 + channel->decimation = ret; 535 + } else { 536 + channel->decimation = ADC5_DECIMATION_DEFAULT; 537 + } 538 + 539 + ret = of_property_read_u32(node, "qcom,avg-samples", &value); 540 + if (!ret) { 541 + ret = qcom_adc5_avg_samples_from_dt(value); 542 + if (ret < 0) { 543 + dev_err(dev, "invalid avg-samples %d\n", value); 544 + return ret; 545 + } 546 + channel->avg_samples = ret; 547 + } else { 548 + channel->avg_samples = VADC_DEF_AVG_SAMPLES; 549 + } 550 + } 551 + 868 552 return 0; 869 553 } 554 + 555 + static const struct adc_tm5_data adc_tm5_data_pmic = { 556 + .full_scale_code_volt = 0x70e4, 557 + .decimation = (unsigned int []) { 250, 420, 840 }, 558 + .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700, 559 + 1000, 2000, 4000, 8000, 16000, 32000, 560 + 64000, 128000 }, 561 + .disable_channel = adc_tm5_disable_channel, 562 + .configure = adc_tm5_configure, 563 + .isr = adc_tm5_isr, 564 + .init = adc_tm5_init, 565 + .irq_name = "pm-adc-tm5", 566 + .gen = ADC_TM5, 567 + }; 568 + 569 + static const struct adc_tm5_data adc_tm_hc_data_pmic = { 570 + .full_scale_code_volt = 0x70e4, 571 + .decimation = (unsigned int []) { 256, 512, 1024 }, 572 + .hw_settle = (unsigned int []) { 0, 100, 200, 300, 400, 500, 600, 700, 573 + 1000, 2000, 4000, 6000, 8000, 10000 }, 574 + .disable_channel = adc_tm5_disable_channel, 575 + .configure = adc_tm5_configure, 576 + .isr = adc_tm5_isr, 577 + .init = adc_tm_hc_init, 578 + .irq_name = "pm-adc-tm5", 579 + .gen = ADC_TM_HC, 580 + }; 581 + 582 + static const struct adc_tm5_data adc_tm5_gen2_data_pmic = { 583 + .full_scale_code_volt = 0x70e4, 584 + .decimation = (unsigned int []) { 85, 340, 1360 }, 585 + .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700, 586 + 1000, 2000, 4000, 8000, 16000, 32000, 587 + 64000, 128000 }, 588 + .disable_channel = adc_tm5_gen2_disable_channel, 589 + .configure = adc_tm5_gen2_configure, 590 + .isr = adc_tm5_gen2_isr, 591 + .init = adc_tm5_gen2_init, 592 + .irq_name = "pm-adc-tm5-gen2", 593 + .gen = ADC_TM5_GEN2, 594 + }; 870 595 871 596 static int adc_tm5_get_dt_data(struct adc_tm5_chip *adc_tm, struct device_node *node) 872 597 { ··· 1032 623 return ret; 1033 624 } 1034 625 1035 - if (adc_tm->data->is_hc) 1036 - ret = adc_tm_hc_init(adc_tm); 1037 - else 1038 - ret = adc_tm5_init(adc_tm); 626 + ret = adc_tm->data->init(adc_tm); 1039 627 if (ret) { 1040 628 dev_err(dev, "adc-tm init failed\n"); 1041 629 return ret; ··· 1044 638 return ret; 1045 639 } 1046 640 1047 - return devm_request_threaded_irq(dev, irq, NULL, adc_tm5_isr, 1048 - IRQF_ONESHOT, "pm-adc-tm5", adc_tm); 641 + return devm_request_threaded_irq(dev, irq, NULL, adc_tm->data->isr, 642 + IRQF_ONESHOT, adc_tm->data->irq_name, adc_tm); 1049 643 } 1050 644 1051 645 static const struct of_device_id adc_tm5_match_table[] = { ··· 1056 650 { 1057 651 .compatible = "qcom,spmi-adc-tm-hc", 1058 652 .data = &adc_tm_hc_data_pmic, 653 + }, 654 + { 655 + .compatible = "qcom,spmi-adc-tm5-gen2", 656 + .data = &adc_tm5_gen2_data_pmic, 1059 657 }, 1060 658 { } 1061 659 };
+3
drivers/thermal/qcom/tsens.c
··· 980 980 .compatible = "qcom,msm8939-tsens", 981 981 .data = &data_8939, 982 982 }, { 983 + .compatible = "qcom,msm8960-tsens", 984 + .data = &data_8960, 985 + }, { 983 986 .compatible = "qcom,msm8974-tsens", 984 987 .data = &data_8974, 985 988 }, {
+12 -5
drivers/thermal/rcar_thermal.c
··· 445 445 struct rcar_thermal_common *common; 446 446 struct rcar_thermal_priv *priv; 447 447 struct device *dev = &pdev->dev; 448 - struct resource *res, *irq; 448 + struct resource *res; 449 449 const struct rcar_thermal_chip *chip = of_device_get_match_data(dev); 450 450 int mres = 0; 451 451 int i; ··· 467 467 pm_runtime_get_sync(dev); 468 468 469 469 for (i = 0; i < chip->nirqs; i++) { 470 - irq = platform_get_resource(pdev, IORESOURCE_IRQ, i); 471 - if (!irq) 472 - continue; 470 + int irq; 471 + 472 + ret = platform_get_irq_optional(pdev, i); 473 + if (ret < 0 && ret != -ENXIO) 474 + goto error_unregister; 475 + if (ret > 0) 476 + irq = ret; 477 + else 478 + break; 479 + 473 480 if (!common->base) { 474 481 /* 475 482 * platform has IRQ support. ··· 494 487 idle = 0; /* polling delay is not needed */ 495 488 } 496 489 497 - ret = devm_request_irq(dev, irq->start, rcar_thermal_irq, 490 + ret = devm_request_irq(dev, irq, rcar_thermal_irq, 498 491 IRQF_SHARED, dev_name(dev), common); 499 492 if (ret) { 500 493 dev_err(dev, "irq request failed\n ");
+8 -2
drivers/thermal/rzg2l_thermal.c
··· 32 32 #define TSU_SS 0x10 33 33 34 34 #define OTPTSUTRIM_REG(n) (0x18 + ((n) * 0x4)) 35 + #define OTPTSUTRIM_EN_MASK BIT(31) 36 + #define OTPTSUTRIM_MASK GENMASK(11, 0) 35 37 36 38 /* Sensor Mode Register(TSU_SM) */ 37 39 #define TSU_SM_EN_TS BIT(0) ··· 185 183 pm_runtime_get_sync(dev); 186 184 187 185 priv->calib0 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0)); 188 - if (!priv->calib0) 186 + if (priv->calib0 & OTPTSUTRIM_EN_MASK) 187 + priv->calib0 &= OTPTSUTRIM_MASK; 188 + else 189 189 priv->calib0 = SW_CALIB0_VAL; 190 190 191 191 priv->calib1 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(1)); 192 - if (!priv->calib1) 192 + if (priv->calib1 & OTPTSUTRIM_EN_MASK) 193 + priv->calib1 &= OTPTSUTRIM_MASK; 194 + else 193 195 priv->calib1 = SW_CALIB1_VAL; 194 196 195 197 platform_set_drvdata(pdev, priv);
+1
drivers/thermal/thermal_core.c
··· 947 947 return cdev; 948 948 949 949 out_kfree_type: 950 + thermal_cooling_device_destroy_sysfs(cdev); 950 951 kfree(cdev->type); 951 952 put_device(&cdev->device); 952 953 cdev = NULL;
+13 -1
drivers/thermal/thermal_of.c
··· 35 35 }; 36 36 37 37 /** 38 - * struct __thermal_bind_param - a match between trip and cooling device 38 + * struct __thermal_bind_params - a match between trip and cooling device 39 39 * @tcbp: a pointer to an array of cooling devices 40 40 * @count: number of elements in array 41 41 * @trip_id: the trip point index ··· 201 201 return -EINVAL; 202 202 203 203 return data->ops->get_trend(data->sensor_data, trip, trend); 204 + } 205 + 206 + static int of_thermal_change_mode(struct thermal_zone_device *tz, 207 + enum thermal_device_mode mode) 208 + { 209 + struct __thermal_zone *data = tz->devdata; 210 + 211 + return data->ops->change_mode(data->sensor_data, mode); 204 212 } 205 213 206 214 static int of_thermal_bind(struct thermal_zone_device *thermal, ··· 416 408 if (ops->set_emul_temp) 417 409 tzd->ops->set_emul_temp = of_thermal_set_emul_temp; 418 410 411 + if (ops->change_mode) 412 + tzd->ops->change_mode = of_thermal_change_mode; 413 + 419 414 mutex_unlock(&tzd->lock); 420 415 421 416 return tzd; ··· 580 569 tzd->ops->get_temp = NULL; 581 570 tzd->ops->get_trend = NULL; 582 571 tzd->ops->set_emul_temp = NULL; 572 + tzd->ops->change_mode = NULL; 583 573 584 574 tz->ops = NULL; 585 575 tz->sensor_data = NULL;
+2
include/linux/iio/adc/qcom-vadc-common.h
··· 152 152 u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio, 153 153 u32 full_scale_code_volt, int temp); 154 154 155 + u16 qcom_adc_tm5_gen2_temp_res_scale(int temp); 156 + 155 157 int qcom_adc5_prescaling_from_dt(u32 num, u32 den); 156 158 157 159 int qcom_adc5_hw_settle_time_from_dt(u32 value, const unsigned int *hw_settle);
+3
include/linux/thermal.h
··· 299 299 * temperature. 300 300 * @set_trip_temp: a pointer to a function that sets the trip temperature on 301 301 * hardware. 302 + * @change_mode: a pointer to a function that notifies the thermal zone 303 + * mode change. 302 304 */ 303 305 struct thermal_zone_of_device_ops { 304 306 int (*get_temp)(void *, int *); ··· 308 306 int (*set_trips)(void *, int, int); 309 307 int (*set_emul_temp)(void *, int); 310 308 int (*set_trip_temp)(void *, int, int); 309 + int (*change_mode) (void *, enum thermal_device_mode); 311 310 }; 312 311 313 312 /* Function declarations */
+33 -3
tools/Makefile
··· 32 32 @echo ' bootconfig - boot config tool' 33 33 @echo ' spi - spi tools' 34 34 @echo ' tmon - thermal monitoring and tuning tool' 35 + @echo ' thermometer - temperature capture tool' 36 + @echo ' thermal-engine - thermal monitoring tool' 37 + @echo ' thermal - thermal library' 35 38 @echo ' tracing - misc tracing tools' 36 39 @echo ' turbostat - Intel CPU idle stats and freq reporting tool' 37 40 @echo ' usb - USB testing tools' ··· 92 89 selftests: FORCE 93 90 $(call descend,testing/$@) 94 91 92 + thermal: FORCE 93 + $(call descend,lib/$@) 94 + 95 95 turbostat x86_energy_perf_policy intel-speed-select: FORCE 96 96 $(call descend,power/x86/$@) 97 97 98 98 tmon: FORCE 99 + $(call descend,thermal/$@) 100 + 101 + thermometer: FORCE 102 + $(call descend,thermal/$@) 103 + 104 + thermal-engine: FORCE thermal 99 105 $(call descend,thermal/$@) 100 106 101 107 freefall: FORCE ··· 117 105 perf selftests bootconfig spi turbostat usb \ 118 106 virtio vm bpf x86_energy_perf_policy \ 119 107 tmon freefall iio objtool kvm_stat wmi \ 120 - pci debugging tracing 108 + pci debugging tracing thermal thermometer thermal-engine 121 109 122 110 acpi_install: 123 111 $(call descend,power/$(@:_install=),install) ··· 131 119 selftests_install: 132 120 $(call descend,testing/$(@:_install=),install) 133 121 122 + thermal_install: 123 + $(call descend,lib/$(@:_install=),install) 124 + 134 125 turbostat_install x86_energy_perf_policy_install intel-speed-select_install: 135 126 $(call descend,power/x86/$(@:_install=),install) 136 127 137 128 tmon_install: 129 + $(call descend,thermal/$(@:_install=),install) 130 + 131 + thermometer_install: 132 + $(call descend,thermal/$(@:_install=),install) 133 + 134 + thermal-engine_install: 138 135 $(call descend,thermal/$(@:_install=),install) 139 136 140 137 freefall_install: ··· 158 137 virtio_install vm_install bpf_install x86_energy_perf_policy_install \ 159 138 tmon_install freefall_install objtool_install kvm_stat_install \ 160 139 wmi_install pci_install debugging_install intel-speed-select_install \ 161 - tracing_install 140 + tracing_install thermometer_install thermal-engine_install 162 141 163 142 acpi_clean: 164 143 $(call descend,power/acpi,clean) ··· 185 164 selftests_clean: 186 165 $(call descend,testing/$(@:_clean=),clean) 187 166 167 + thermal_clean: 168 + $(call descend,lib/thermal,clean) 169 + 188 170 turbostat_clean x86_energy_perf_policy_clean intel-speed-select_clean: 189 171 $(call descend,power/x86/$(@:_clean=),clean) 172 + 173 + thermometer_clean: 174 + $(call descend,thermal/thermometer,clean) 175 + 176 + thermal-engine_clean: 177 + $(call descend,thermal/thermal-engine,clean) 190 178 191 179 tmon_clean: 192 180 $(call descend,thermal/tmon,clean) ··· 211 181 vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ 212 182 freefall_clean build_clean libbpf_clean libsubcmd_clean \ 213 183 gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \ 214 - intel-speed-select_clean tracing_clean 184 + intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean 215 185 216 186 .PHONY: FORCE
+2
tools/lib/thermal/.gitignore
··· 1 + libthermal.so* 2 + libthermal.pc
+5
tools/lib/thermal/Build
··· 1 + libthermal-y += commands.o 2 + libthermal-y += events.o 3 + libthermal-y += thermal_nl.o 4 + libthermal-y += sampling.o 5 + libthermal-y += thermal.o
+165
tools/lib/thermal/Makefile
··· 1 + # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 + # Most of this file is copied from tools/lib/perf/Makefile 3 + 4 + LIBTHERMAL_VERSION = 0 5 + LIBTHERMAL_PATCHLEVEL = 0 6 + LIBTHERMAL_EXTRAVERSION = 1 7 + 8 + MAKEFLAGS += --no-print-directory 9 + 10 + ifeq ($(srctree),) 11 + srctree := $(patsubst %/,%,$(dir $(CURDIR))) 12 + srctree := $(patsubst %/,%,$(dir $(srctree))) 13 + srctree := $(patsubst %/,%,$(dir $(srctree))) 14 + # $(info Determined 'srctree' to be $(srctree)) 15 + endif 16 + 17 + INSTALL = install 18 + 19 + # Use DESTDIR for installing into a different root directory. 20 + # This is useful for building a package. The program will be 21 + # installed in this directory as if it was the root directory. 22 + # Then the build tool can move it later. 23 + DESTDIR ?= 24 + DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' 25 + 26 + include $(srctree)/tools/scripts/Makefile.include 27 + include $(srctree)/tools/scripts/Makefile.arch 28 + 29 + ifeq ($(LP64), 1) 30 + libdir_relative = lib64 31 + else 32 + libdir_relative = lib 33 + endif 34 + 35 + prefix ?= 36 + libdir = $(prefix)/$(libdir_relative) 37 + 38 + # Shell quotes 39 + libdir_SQ = $(subst ','\'',$(libdir)) 40 + libdir_relative_SQ = $(subst ','\'',$(libdir_relative)) 41 + 42 + ifeq ("$(origin V)", "command line") 43 + VERBOSE = $(V) 44 + endif 45 + ifndef VERBOSE 46 + VERBOSE = 0 47 + endif 48 + 49 + ifeq ($(VERBOSE),1) 50 + Q = 51 + else 52 + Q = @ 53 + endif 54 + 55 + # Set compile option CFLAGS 56 + ifdef EXTRA_CFLAGS 57 + CFLAGS := $(EXTRA_CFLAGS) 58 + else 59 + CFLAGS := -g -Wall 60 + endif 61 + 62 + INCLUDES = \ 63 + -I/usr/include/libnl3 \ 64 + -I$(srctree)/tools/lib/thermal/include \ 65 + -I$(srctree)/tools/lib/ \ 66 + -I$(srctree)/tools/include \ 67 + -I$(srctree)/tools/arch/$(SRCARCH)/include/ \ 68 + -I$(srctree)/tools/arch/$(SRCARCH)/include/uapi \ 69 + -I$(srctree)/tools/include/uapi 70 + 71 + # Append required CFLAGS 72 + override CFLAGS += $(EXTRA_WARNINGS) 73 + override CFLAGS += -Werror -Wall 74 + override CFLAGS += -fPIC 75 + override CFLAGS += $(INCLUDES) 76 + override CFLAGS += -fvisibility=hidden 77 + override CFGLAS += -Wl,-L. 78 + override CFGLAS += -Wl,-lthermal 79 + 80 + all: 81 + 82 + export srctree OUTPUT CC LD CFLAGS V 83 + export DESTDIR DESTDIR_SQ 84 + 85 + include $(srctree)/tools/build/Makefile.include 86 + 87 + VERSION_SCRIPT := libthermal.map 88 + 89 + PATCHLEVEL = $(LIBTHERMAL_PATCHLEVEL) 90 + EXTRAVERSION = $(LIBTHERMAL_EXTRAVERSION) 91 + VERSION = $(LIBTHERMAL_VERSION).$(LIBTHERMAL_PATCHLEVEL).$(LIBTHERMAL_EXTRAVERSION) 92 + 93 + LIBTHERMAL_SO := $(OUTPUT)libthermal.so.$(VERSION) 94 + LIBTHERMAL_A := $(OUTPUT)libthermal.a 95 + LIBTHERMAL_IN := $(OUTPUT)libthermal-in.o 96 + LIBTHERMAL_PC := $(OUTPUT)libthermal.pc 97 + LIBTHERMAL_ALL := $(LIBTHERMAL_A) $(OUTPUT)libthermal.so* 98 + 99 + THERMAL_UAPI := include/uapi/linux/thermal.h 100 + 101 + $(THERMAL_UAPI): FORCE 102 + ln -sf $(srctree)/$@ $(srctree)/tools/$@ 103 + 104 + $(LIBTHERMAL_IN): FORCE 105 + $(Q)$(MAKE) $(build)=libthermal 106 + 107 + $(LIBTHERMAL_A): $(LIBTHERMAL_IN) 108 + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIBTHERMAL_IN) 109 + 110 + $(LIBTHERMAL_SO): $(LIBTHERMAL_IN) 111 + $(QUIET_LINK)$(CC) --shared -Wl,-soname,libthermal.so \ 112 + -Wl,--version-script=$(VERSION_SCRIPT) $^ -o $@ 113 + @ln -sf $(@F) $(OUTPUT)libthermal.so 114 + @ln -sf $(@F) $(OUTPUT)libthermal.so.$(LIBTHERMAL_VERSION) 115 + 116 + 117 + libs: $(THERMAL_UAPI) $(LIBTHERMAL_A) $(LIBTHERMAL_SO) $(LIBTHERMAL_PC) 118 + 119 + all: fixdep 120 + $(Q)$(MAKE) libs 121 + 122 + clean: 123 + $(call QUIET_CLEAN, libthermal) $(RM) $(LIBTHERMAL_A) \ 124 + *.o *~ *.a *.so *.so.$(VERSION) *.so.$(LIBTHERMAL_VERSION) .*.d .*.cmd LIBTHERMAL-CFLAGS $(LIBTHERMAL_PC) 125 + 126 + $(LIBTHERMAL_PC): 127 + $(QUIET_GEN)sed -e "s|@PREFIX@|$(prefix)|" \ 128 + -e "s|@LIBDIR@|$(libdir_SQ)|" \ 129 + -e "s|@VERSION@|$(VERSION)|" \ 130 + < libthermal.pc.template > $@ 131 + 132 + define do_install_mkdir 133 + if [ ! -d '$(DESTDIR_SQ)$1' ]; then \ 134 + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \ 135 + fi 136 + endef 137 + 138 + define do_install 139 + if [ ! -d '$(DESTDIR_SQ)$2' ]; then \ 140 + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \ 141 + fi; \ 142 + $(INSTALL) $1 $(if $3,-m $3,) '$(DESTDIR_SQ)$2' 143 + endef 144 + 145 + install_lib: libs 146 + $(call QUIET_INSTALL, $(LIBTHERMAL_ALL)) \ 147 + $(call do_install_mkdir,$(libdir_SQ)); \ 148 + cp -fpR $(LIBTHERMAL_ALL) $(DESTDIR)$(libdir_SQ) 149 + 150 + install_headers: 151 + $(call QUIET_INSTALL, headers) \ 152 + $(call do_install,include/thermal.h,$(prefix)/include/thermal,644); \ 153 + 154 + install_pkgconfig: $(LIBTHERMAL_PC) 155 + $(call QUIET_INSTALL, $(LIBTHERMAL_PC)) \ 156 + $(call do_install,$(LIBTHERMAL_PC),$(libdir_SQ)/pkgconfig,644) 157 + 158 + install_doc: 159 + $(Q)$(MAKE) -C Documentation install-man install-html install-examples 160 + 161 + install: install_lib install_headers install_pkgconfig 162 + 163 + FORCE: 164 + 165 + .PHONY: all install clean FORCE
+349
tools/lib/thermal/commands.c
··· 1 + // SPDX-License-Identifier: LGPL-2.1+ 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #define _GNU_SOURCE 4 + #include <errno.h> 5 + #include <stdio.h> 6 + #include <stdlib.h> 7 + #include <unistd.h> 8 + 9 + #include <thermal.h> 10 + #include "thermal_nl.h" 11 + 12 + static struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = { 13 + /* Thermal zone */ 14 + [THERMAL_GENL_ATTR_TZ] = { .type = NLA_NESTED }, 15 + [THERMAL_GENL_ATTR_TZ_ID] = { .type = NLA_U32 }, 16 + [THERMAL_GENL_ATTR_TZ_TEMP] = { .type = NLA_U32 }, 17 + [THERMAL_GENL_ATTR_TZ_TRIP] = { .type = NLA_NESTED }, 18 + [THERMAL_GENL_ATTR_TZ_TRIP_ID] = { .type = NLA_U32 }, 19 + [THERMAL_GENL_ATTR_TZ_TRIP_TEMP] = { .type = NLA_U32 }, 20 + [THERMAL_GENL_ATTR_TZ_TRIP_TYPE] = { .type = NLA_U32 }, 21 + [THERMAL_GENL_ATTR_TZ_TRIP_HYST] = { .type = NLA_U32 }, 22 + [THERMAL_GENL_ATTR_TZ_MODE] = { .type = NLA_U32 }, 23 + [THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT] = { .type = NLA_U32 }, 24 + [THERMAL_GENL_ATTR_TZ_NAME] = { .type = NLA_STRING }, 25 + 26 + /* Governor(s) */ 27 + [THERMAL_GENL_ATTR_TZ_GOV] = { .type = NLA_NESTED }, 28 + [THERMAL_GENL_ATTR_TZ_GOV_NAME] = { .type = NLA_STRING }, 29 + 30 + /* Cooling devices */ 31 + [THERMAL_GENL_ATTR_CDEV] = { .type = NLA_NESTED }, 32 + [THERMAL_GENL_ATTR_CDEV_ID] = { .type = NLA_U32 }, 33 + [THERMAL_GENL_ATTR_CDEV_CUR_STATE] = { .type = NLA_U32 }, 34 + [THERMAL_GENL_ATTR_CDEV_MAX_STATE] = { .type = NLA_U32 }, 35 + [THERMAL_GENL_ATTR_CDEV_NAME] = { .type = NLA_STRING }, 36 + }; 37 + 38 + static int parse_tz_get(struct genl_info *info, struct thermal_zone **tz) 39 + { 40 + struct nlattr *attr; 41 + struct thermal_zone *__tz = NULL; 42 + size_t size = 0; 43 + int rem; 44 + 45 + nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ], rem) { 46 + 47 + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_ID) { 48 + 49 + size++; 50 + 51 + __tz = realloc(__tz, sizeof(*__tz) * (size + 2)); 52 + if (!__tz) 53 + return THERMAL_ERROR; 54 + 55 + __tz[size - 1].id = nla_get_u32(attr); 56 + } 57 + 58 + 59 + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_NAME) 60 + nla_strlcpy(__tz[size - 1].name, attr, 61 + THERMAL_NAME_LENGTH); 62 + } 63 + 64 + if (__tz) 65 + __tz[size].id = -1; 66 + 67 + *tz = __tz; 68 + 69 + return THERMAL_SUCCESS; 70 + } 71 + 72 + static int parse_cdev_get(struct genl_info *info, struct thermal_cdev **cdev) 73 + { 74 + struct nlattr *attr; 75 + struct thermal_cdev *__cdev = NULL; 76 + size_t size = 0; 77 + int rem; 78 + 79 + nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_CDEV], rem) { 80 + 81 + if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_ID) { 82 + 83 + size++; 84 + 85 + __cdev = realloc(__cdev, sizeof(*__cdev) * (size + 2)); 86 + if (!__cdev) 87 + return THERMAL_ERROR; 88 + 89 + __cdev[size - 1].id = nla_get_u32(attr); 90 + } 91 + 92 + if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_NAME) { 93 + nla_strlcpy(__cdev[size - 1].name, attr, 94 + THERMAL_NAME_LENGTH); 95 + } 96 + 97 + if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_CUR_STATE) 98 + __cdev[size - 1].cur_state = nla_get_u32(attr); 99 + 100 + if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_MAX_STATE) 101 + __cdev[size - 1].max_state = nla_get_u32(attr); 102 + } 103 + 104 + if (__cdev) 105 + __cdev[size].id = -1; 106 + 107 + *cdev = __cdev; 108 + 109 + return THERMAL_SUCCESS; 110 + } 111 + 112 + static int parse_tz_get_trip(struct genl_info *info, struct thermal_zone *tz) 113 + { 114 + struct nlattr *attr; 115 + struct thermal_trip *__tt = NULL; 116 + size_t size = 0; 117 + int rem; 118 + 119 + nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ_TRIP], rem) { 120 + 121 + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_ID) { 122 + 123 + size++; 124 + 125 + __tt = realloc(__tt, sizeof(*__tt) * (size + 2)); 126 + if (!__tt) 127 + return THERMAL_ERROR; 128 + 129 + __tt[size - 1].id = nla_get_u32(attr); 130 + } 131 + 132 + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TYPE) 133 + __tt[size - 1].type = nla_get_u32(attr); 134 + 135 + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TEMP) 136 + __tt[size - 1].temp = nla_get_u32(attr); 137 + 138 + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_HYST) 139 + __tt[size - 1].hyst = nla_get_u32(attr); 140 + } 141 + 142 + if (__tt) 143 + __tt[size].id = -1; 144 + 145 + tz->trip = __tt; 146 + 147 + return THERMAL_SUCCESS; 148 + } 149 + 150 + static int parse_tz_get_temp(struct genl_info *info, struct thermal_zone *tz) 151 + { 152 + int id = -1; 153 + 154 + if (info->attrs[THERMAL_GENL_ATTR_TZ_ID]) 155 + id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]); 156 + 157 + if (tz->id != id) 158 + return THERMAL_ERROR; 159 + 160 + if (info->attrs[THERMAL_GENL_ATTR_TZ_TEMP]) 161 + tz->temp = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_TEMP]); 162 + 163 + return THERMAL_SUCCESS; 164 + } 165 + 166 + static int parse_tz_get_gov(struct genl_info *info, struct thermal_zone *tz) 167 + { 168 + int id = -1; 169 + 170 + if (info->attrs[THERMAL_GENL_ATTR_TZ_ID]) 171 + id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]); 172 + 173 + if (tz->id != id) 174 + return THERMAL_ERROR; 175 + 176 + if (info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME]) { 177 + nla_strlcpy(tz->governor, 178 + info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME], 179 + THERMAL_NAME_LENGTH); 180 + } 181 + 182 + return THERMAL_SUCCESS; 183 + } 184 + 185 + static int handle_netlink(struct nl_cache_ops *unused, 186 + struct genl_cmd *cmd, 187 + struct genl_info *info, void *arg) 188 + { 189 + int ret; 190 + 191 + switch (cmd->c_id) { 192 + 193 + case THERMAL_GENL_CMD_TZ_GET_ID: 194 + ret = parse_tz_get(info, arg); 195 + break; 196 + 197 + case THERMAL_GENL_CMD_CDEV_GET: 198 + ret = parse_cdev_get(info, arg); 199 + break; 200 + 201 + case THERMAL_GENL_CMD_TZ_GET_TEMP: 202 + ret = parse_tz_get_temp(info, arg); 203 + break; 204 + 205 + case THERMAL_GENL_CMD_TZ_GET_TRIP: 206 + ret = parse_tz_get_trip(info, arg); 207 + break; 208 + 209 + case THERMAL_GENL_CMD_TZ_GET_GOV: 210 + ret = parse_tz_get_gov(info, arg); 211 + break; 212 + 213 + default: 214 + return THERMAL_ERROR; 215 + } 216 + 217 + return ret; 218 + } 219 + 220 + static struct genl_cmd thermal_cmds[] = { 221 + { 222 + .c_id = THERMAL_GENL_CMD_TZ_GET_ID, 223 + .c_name = (char *)"List thermal zones", 224 + .c_msg_parser = handle_netlink, 225 + .c_maxattr = THERMAL_GENL_ATTR_MAX, 226 + .c_attr_policy = thermal_genl_policy, 227 + }, 228 + { 229 + .c_id = THERMAL_GENL_CMD_TZ_GET_GOV, 230 + .c_name = (char *)"Get governor", 231 + .c_msg_parser = handle_netlink, 232 + .c_maxattr = THERMAL_GENL_ATTR_MAX, 233 + .c_attr_policy = thermal_genl_policy, 234 + }, 235 + { 236 + .c_id = THERMAL_GENL_CMD_TZ_GET_TEMP, 237 + .c_name = (char *)"Get thermal zone temperature", 238 + .c_msg_parser = handle_netlink, 239 + .c_maxattr = THERMAL_GENL_ATTR_MAX, 240 + .c_attr_policy = thermal_genl_policy, 241 + }, 242 + { 243 + .c_id = THERMAL_GENL_CMD_TZ_GET_TRIP, 244 + .c_name = (char *)"Get thermal zone trip points", 245 + .c_msg_parser = handle_netlink, 246 + .c_maxattr = THERMAL_GENL_ATTR_MAX, 247 + .c_attr_policy = thermal_genl_policy, 248 + }, 249 + { 250 + .c_id = THERMAL_GENL_CMD_CDEV_GET, 251 + .c_name = (char *)"Get cooling devices", 252 + .c_msg_parser = handle_netlink, 253 + .c_maxattr = THERMAL_GENL_ATTR_MAX, 254 + .c_attr_policy = thermal_genl_policy, 255 + }, 256 + }; 257 + 258 + static struct genl_ops thermal_cmd_ops = { 259 + .o_name = (char *)"thermal", 260 + .o_cmds = thermal_cmds, 261 + .o_ncmds = ARRAY_SIZE(thermal_cmds), 262 + }; 263 + 264 + static thermal_error_t thermal_genl_auto(struct thermal_handler *th, int id, int cmd, 265 + int flags, void *arg) 266 + { 267 + struct nl_msg *msg; 268 + void *hdr; 269 + 270 + msg = nlmsg_alloc(); 271 + if (!msg) 272 + return THERMAL_ERROR; 273 + 274 + hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, thermal_cmd_ops.o_id, 275 + 0, flags, cmd, THERMAL_GENL_VERSION); 276 + if (!hdr) 277 + return THERMAL_ERROR; 278 + 279 + if (id >= 0 && nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id)) 280 + return THERMAL_ERROR; 281 + 282 + if (nl_send_msg(th->sk_cmd, th->cb_cmd, msg, genl_handle_msg, arg)) 283 + return THERMAL_ERROR; 284 + 285 + nlmsg_free(msg); 286 + 287 + return THERMAL_SUCCESS; 288 + } 289 + 290 + thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th, struct thermal_zone **tz) 291 + { 292 + return thermal_genl_auto(th, -1, THERMAL_GENL_CMD_TZ_GET_ID, 293 + NLM_F_DUMP | NLM_F_ACK, tz); 294 + } 295 + 296 + thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th, struct thermal_cdev **tc) 297 + { 298 + return thermal_genl_auto(th, -1, THERMAL_GENL_CMD_CDEV_GET, 299 + NLM_F_DUMP | NLM_F_ACK, tc); 300 + } 301 + 302 + thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th, struct thermal_zone *tz) 303 + { 304 + return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_TRIP, 305 + 0, tz); 306 + } 307 + 308 + thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th, struct thermal_zone *tz) 309 + { 310 + return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_GOV, 0, tz); 311 + } 312 + 313 + thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th, struct thermal_zone *tz) 314 + { 315 + return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_TEMP, 0, tz); 316 + } 317 + 318 + thermal_error_t thermal_cmd_exit(struct thermal_handler *th) 319 + { 320 + if (genl_unregister_family(&thermal_cmd_ops)) 321 + return THERMAL_ERROR; 322 + 323 + nl_thermal_disconnect(th->sk_cmd, th->cb_cmd); 324 + 325 + return THERMAL_SUCCESS; 326 + } 327 + 328 + thermal_error_t thermal_cmd_init(struct thermal_handler *th) 329 + { 330 + int ret; 331 + int family; 332 + 333 + if (nl_thermal_connect(&th->sk_cmd, &th->cb_cmd)) 334 + return THERMAL_ERROR; 335 + 336 + ret = genl_register_family(&thermal_cmd_ops); 337 + if (ret) 338 + return THERMAL_ERROR; 339 + 340 + ret = genl_ops_resolve(th->sk_cmd, &thermal_cmd_ops); 341 + if (ret) 342 + return THERMAL_ERROR; 343 + 344 + family = genl_ctrl_resolve(th->sk_cmd, "nlctrl"); 345 + if (family != GENL_ID_CTRL) 346 + return THERMAL_ERROR; 347 + 348 + return THERMAL_SUCCESS; 349 + }
+164
tools/lib/thermal/events.c
··· 1 + // SPDX-License-Identifier: LGPL-2.1+ 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #include <linux/netlink.h> 4 + #include <stdio.h> 5 + #include <stdlib.h> 6 + #include <unistd.h> 7 + 8 + 9 + #include <thermal.h> 10 + #include "thermal_nl.h" 11 + 12 + /* 13 + * Optimization: fill this array to tell which event we do want to pay 14 + * attention to. That happens at init time with the ops 15 + * structure. Each ops will enable the event and the general handler 16 + * will be able to discard the event if there is not ops associated 17 + * with it. 18 + */ 19 + static int enabled_ops[__THERMAL_GENL_EVENT_MAX]; 20 + 21 + static int handle_thermal_event(struct nl_msg *n, void *arg) 22 + { 23 + struct nlmsghdr *nlh = nlmsg_hdr(n); 24 + struct genlmsghdr *genlhdr = genlmsg_hdr(nlh); 25 + struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1]; 26 + struct thermal_handler_param *thp = arg; 27 + struct thermal_events_ops *ops = &thp->th->ops->events; 28 + 29 + genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL); 30 + 31 + arg = thp->arg; 32 + 33 + /* 34 + * This is an event we don't care of, bail out. 35 + */ 36 + if (!enabled_ops[genlhdr->cmd]) 37 + return THERMAL_SUCCESS; 38 + 39 + switch (genlhdr->cmd) { 40 + 41 + case THERMAL_GENL_EVENT_TZ_CREATE: 42 + return ops->tz_create(nla_get_string(attrs[THERMAL_GENL_ATTR_TZ_NAME]), 43 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg); 44 + 45 + case THERMAL_GENL_EVENT_TZ_DELETE: 46 + return ops->tz_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg); 47 + 48 + case THERMAL_GENL_EVENT_TZ_ENABLE: 49 + return ops->tz_enable(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg); 50 + 51 + case THERMAL_GENL_EVENT_TZ_DISABLE: 52 + return ops->tz_disable(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg); 53 + 54 + case THERMAL_GENL_EVENT_TZ_TRIP_CHANGE: 55 + return ops->trip_change(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), 56 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), 57 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]), 58 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]), 59 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]), arg); 60 + 61 + case THERMAL_GENL_EVENT_TZ_TRIP_ADD: 62 + return ops->trip_add(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), 63 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), 64 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]), 65 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]), 66 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]), arg); 67 + 68 + case THERMAL_GENL_EVENT_TZ_TRIP_DELETE: 69 + return ops->trip_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), 70 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), arg); 71 + 72 + case THERMAL_GENL_EVENT_TZ_TRIP_UP: 73 + return ops->trip_high(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), 74 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), 75 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg); 76 + 77 + case THERMAL_GENL_EVENT_TZ_TRIP_DOWN: 78 + return ops->trip_low(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), 79 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), 80 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg); 81 + 82 + case THERMAL_GENL_EVENT_CDEV_ADD: 83 + return ops->cdev_add(nla_get_string(attrs[THERMAL_GENL_ATTR_CDEV_NAME]), 84 + nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]), 85 + nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_MAX_STATE]), arg); 86 + 87 + case THERMAL_GENL_EVENT_CDEV_DELETE: 88 + return ops->cdev_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]), arg); 89 + 90 + case THERMAL_GENL_EVENT_CDEV_STATE_UPDATE: 91 + return ops->cdev_update(nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]), 92 + nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_CUR_STATE]), arg); 93 + 94 + case THERMAL_GENL_EVENT_TZ_GOV_CHANGE: 95 + return ops->gov_change(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), 96 + nla_get_string(attrs[THERMAL_GENL_ATTR_GOV_NAME]), arg); 97 + default: 98 + return -1; 99 + } 100 + } 101 + 102 + static void thermal_events_ops_init(struct thermal_events_ops *ops) 103 + { 104 + enabled_ops[THERMAL_GENL_EVENT_TZ_CREATE] = !!ops->tz_create; 105 + enabled_ops[THERMAL_GENL_EVENT_TZ_DELETE] = !!ops->tz_delete; 106 + enabled_ops[THERMAL_GENL_EVENT_TZ_DISABLE] = !!ops->tz_disable; 107 + enabled_ops[THERMAL_GENL_EVENT_TZ_ENABLE] = !!ops->tz_enable; 108 + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_UP] = !!ops->trip_high; 109 + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_DOWN] = !!ops->trip_low; 110 + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_CHANGE] = !!ops->trip_change; 111 + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_ADD] = !!ops->trip_add; 112 + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_DELETE] = !!ops->trip_delete; 113 + enabled_ops[THERMAL_GENL_EVENT_CDEV_ADD] = !!ops->cdev_add; 114 + enabled_ops[THERMAL_GENL_EVENT_CDEV_DELETE] = !!ops->cdev_delete; 115 + enabled_ops[THERMAL_GENL_EVENT_CDEV_STATE_UPDATE] = !!ops->cdev_update; 116 + enabled_ops[THERMAL_GENL_EVENT_TZ_GOV_CHANGE] = !!ops->gov_change; 117 + } 118 + 119 + thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg) 120 + { 121 + struct thermal_handler_param thp = { .th = th, .arg = arg }; 122 + 123 + if (!th) 124 + return THERMAL_ERROR; 125 + 126 + if (nl_cb_set(th->cb_event, NL_CB_VALID, NL_CB_CUSTOM, 127 + handle_thermal_event, &thp)) 128 + return THERMAL_ERROR; 129 + 130 + return nl_recvmsgs(th->sk_event, th->cb_event); 131 + } 132 + 133 + int thermal_events_fd(struct thermal_handler *th) 134 + { 135 + if (!th) 136 + return -1; 137 + 138 + return nl_socket_get_fd(th->sk_event); 139 + } 140 + 141 + thermal_error_t thermal_events_exit(struct thermal_handler *th) 142 + { 143 + if (nl_unsubscribe_thermal(th->sk_event, th->cb_event, 144 + THERMAL_GENL_EVENT_GROUP_NAME)) 145 + return THERMAL_ERROR; 146 + 147 + nl_thermal_disconnect(th->sk_event, th->cb_event); 148 + 149 + return THERMAL_SUCCESS; 150 + } 151 + 152 + thermal_error_t thermal_events_init(struct thermal_handler *th) 153 + { 154 + thermal_events_ops_init(&th->ops->events); 155 + 156 + if (nl_thermal_connect(&th->sk_event, &th->cb_event)) 157 + return THERMAL_ERROR; 158 + 159 + if (nl_subscribe_thermal(th->sk_event, th->cb_event, 160 + THERMAL_GENL_EVENT_GROUP_NAME)) 161 + return THERMAL_ERROR; 162 + 163 + return THERMAL_SUCCESS; 164 + }
+142
tools/lib/thermal/include/thermal.h
··· 1 + /* SPDX-License-Identifier: LGPL-2.1+ */ 2 + /* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */ 3 + #ifndef __LIBTHERMAL_H 4 + #define __LIBTHERMAL_H 5 + 6 + #include <linux/thermal.h> 7 + 8 + #ifndef LIBTHERMAL_API 9 + #define LIBTHERMAL_API __attribute__((visibility("default"))) 10 + #endif 11 + 12 + #ifdef __cplusplus 13 + extern "C" { 14 + #endif 15 + 16 + struct thermal_sampling_ops { 17 + int (*tz_temp)(int tz_id, int temp, void *arg); 18 + }; 19 + 20 + struct thermal_events_ops { 21 + int (*tz_create)(const char *name, int tz_id, void *arg); 22 + int (*tz_delete)(int tz_id, void *arg); 23 + int (*tz_enable)(int tz_id, void *arg); 24 + int (*tz_disable)(int tz_id, void *arg); 25 + int (*trip_high)(int tz_id, int trip_id, int temp, void *arg); 26 + int (*trip_low)(int tz_id, int trip_id, int temp, void *arg); 27 + int (*trip_add)(int tz_id, int trip_id, int type, int temp, int hyst, void *arg); 28 + int (*trip_change)(int tz_id, int trip_id, int type, int temp, int hyst, void *arg); 29 + int (*trip_delete)(int tz_id, int trip_id, void *arg); 30 + int (*cdev_add)(const char *name, int cdev_id, int max_state, void *arg); 31 + int (*cdev_delete)(int cdev_id, void *arg); 32 + int (*cdev_update)(int cdev_id, int cur_state, void *arg); 33 + int (*gov_change)(int tz_id, const char *gov_name, void *arg); 34 + }; 35 + 36 + struct thermal_ops { 37 + struct thermal_sampling_ops sampling; 38 + struct thermal_events_ops events; 39 + }; 40 + 41 + struct thermal_trip { 42 + int id; 43 + int type; 44 + int temp; 45 + int hyst; 46 + }; 47 + 48 + struct thermal_zone { 49 + int id; 50 + int temp; 51 + char name[THERMAL_NAME_LENGTH]; 52 + char governor[THERMAL_NAME_LENGTH]; 53 + struct thermal_trip *trip; 54 + }; 55 + 56 + struct thermal_cdev { 57 + int id; 58 + char name[THERMAL_NAME_LENGTH]; 59 + int max_state; 60 + int min_state; 61 + int cur_state; 62 + }; 63 + 64 + typedef enum { 65 + THERMAL_ERROR = -1, 66 + THERMAL_SUCCESS = 0, 67 + } thermal_error_t; 68 + 69 + struct thermal_handler; 70 + 71 + typedef int (*cb_tz_t)(struct thermal_zone *, void *); 72 + 73 + typedef int (*cb_tt_t)(struct thermal_trip *, void *); 74 + 75 + typedef int (*cb_tc_t)(struct thermal_cdev *, void *); 76 + 77 + LIBTHERMAL_API int for_each_thermal_zone(struct thermal_zone *tz, cb_tz_t cb, void *arg); 78 + 79 + LIBTHERMAL_API int for_each_thermal_trip(struct thermal_trip *tt, cb_tt_t cb, void *arg); 80 + 81 + LIBTHERMAL_API int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg); 82 + 83 + LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz, 84 + const char *name); 85 + 86 + LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_id(struct thermal_zone *tz, int id); 87 + 88 + LIBTHERMAL_API struct thermal_zone *thermal_zone_discover(struct thermal_handler *th); 89 + 90 + LIBTHERMAL_API struct thermal_handler *thermal_init(struct thermal_ops *ops); 91 + 92 + LIBTHERMAL_API void thermal_exit(struct thermal_handler *th); 93 + 94 + /* 95 + * Netlink thermal events 96 + */ 97 + LIBTHERMAL_API thermal_error_t thermal_events_exit(struct thermal_handler *th); 98 + 99 + LIBTHERMAL_API thermal_error_t thermal_events_init(struct thermal_handler *th); 100 + 101 + LIBTHERMAL_API thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg); 102 + 103 + LIBTHERMAL_API int thermal_events_fd(struct thermal_handler *th); 104 + 105 + /* 106 + * Netlink thermal commands 107 + */ 108 + LIBTHERMAL_API thermal_error_t thermal_cmd_exit(struct thermal_handler *th); 109 + 110 + LIBTHERMAL_API thermal_error_t thermal_cmd_init(struct thermal_handler *th); 111 + 112 + LIBTHERMAL_API thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th, 113 + struct thermal_zone **tz); 114 + 115 + LIBTHERMAL_API thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th, 116 + struct thermal_cdev **tc); 117 + 118 + LIBTHERMAL_API thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th, 119 + struct thermal_zone *tz); 120 + 121 + LIBTHERMAL_API thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th, 122 + struct thermal_zone *tz); 123 + 124 + LIBTHERMAL_API thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th, 125 + struct thermal_zone *tz); 126 + 127 + /* 128 + * Netlink thermal samples 129 + */ 130 + LIBTHERMAL_API thermal_error_t thermal_sampling_exit(struct thermal_handler *th); 131 + 132 + LIBTHERMAL_API thermal_error_t thermal_sampling_init(struct thermal_handler *th); 133 + 134 + LIBTHERMAL_API thermal_error_t thermal_sampling_handle(struct thermal_handler *th, void *arg); 135 + 136 + LIBTHERMAL_API int thermal_sampling_fd(struct thermal_handler *th); 137 + 138 + #endif /* __LIBTHERMAL_H */ 139 + 140 + #ifdef __cplusplus 141 + } 142 + #endif
+25
tools/lib/thermal/libthermal.map
··· 1 + LIBTHERMAL_0.0.1 { 2 + global: 3 + thermal_init; 4 + for_each_thermal_zone; 5 + for_each_thermal_trip; 6 + for_each_thermal_cdev; 7 + thermal_zone_find_by_name; 8 + thermal_zone_find_by_id; 9 + thermal_zone_discover; 10 + thermal_init; 11 + thermal_events_init; 12 + thermal_events_handle; 13 + thermal_events_fd; 14 + thermal_cmd_init; 15 + thermal_cmd_get_tz; 16 + thermal_cmd_get_cdev; 17 + thermal_cmd_get_trip; 18 + thermal_cmd_get_governor; 19 + thermal_cmd_get_temp; 20 + thermal_sampling_init; 21 + thermal_sampling_handle; 22 + thermal_sampling_fd; 23 + local: 24 + *; 25 + };
+12
tools/lib/thermal/libthermal.pc.template
··· 1 + # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 + 3 + prefix=@PREFIX@ 4 + libdir=@LIBDIR@ 5 + includedir=${prefix}/include 6 + 7 + Name: libthermal 8 + Description: thermal library 9 + Requires: libnl-3.0 libnl-genl-3.0 10 + Version: @VERSION@ 11 + Libs: -L${libdir} -lnl-genl-3 -lnl-3 12 + Cflags: -I${includedir} -I{include}/libnl3
+75
tools/lib/thermal/sampling.c
··· 1 + // SPDX-License-Identifier: LGPL-2.1+ 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #include <errno.h> 4 + #include <stdio.h> 5 + #include <stdlib.h> 6 + #include <unistd.h> 7 + 8 + #include <thermal.h> 9 + #include "thermal_nl.h" 10 + 11 + static int handle_thermal_sample(struct nl_msg *n, void *arg) 12 + { 13 + struct nlmsghdr *nlh = nlmsg_hdr(n); 14 + struct genlmsghdr *genlhdr = genlmsg_hdr(nlh); 15 + struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1]; 16 + struct thermal_handler_param *thp = arg; 17 + struct thermal_handler *th = thp->th; 18 + 19 + genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL); 20 + 21 + switch (genlhdr->cmd) { 22 + 23 + case THERMAL_GENL_SAMPLING_TEMP: 24 + return th->ops->sampling.tz_temp( 25 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), 26 + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg); 27 + default: 28 + return THERMAL_ERROR; 29 + } 30 + } 31 + 32 + thermal_error_t thermal_sampling_handle(struct thermal_handler *th, void *arg) 33 + { 34 + struct thermal_handler_param thp = { .th = th, .arg = arg }; 35 + 36 + if (!th) 37 + return THERMAL_ERROR; 38 + 39 + if (nl_cb_set(th->cb_sampling, NL_CB_VALID, NL_CB_CUSTOM, 40 + handle_thermal_sample, &thp)) 41 + return THERMAL_ERROR; 42 + 43 + return nl_recvmsgs(th->sk_sampling, th->cb_sampling); 44 + } 45 + 46 + int thermal_sampling_fd(struct thermal_handler *th) 47 + { 48 + if (!th) 49 + return -1; 50 + 51 + return nl_socket_get_fd(th->sk_sampling); 52 + } 53 + 54 + thermal_error_t thermal_sampling_exit(struct thermal_handler *th) 55 + { 56 + if (nl_unsubscribe_thermal(th->sk_sampling, th->cb_sampling, 57 + THERMAL_GENL_EVENT_GROUP_NAME)) 58 + return THERMAL_ERROR; 59 + 60 + nl_thermal_disconnect(th->sk_sampling, th->cb_sampling); 61 + 62 + return THERMAL_SUCCESS; 63 + } 64 + 65 + thermal_error_t thermal_sampling_init(struct thermal_handler *th) 66 + { 67 + if (nl_thermal_connect(&th->sk_sampling, &th->cb_sampling)) 68 + return THERMAL_ERROR; 69 + 70 + if (nl_subscribe_thermal(th->sk_sampling, th->cb_sampling, 71 + THERMAL_GENL_SAMPLING_GROUP_NAME)) 72 + return THERMAL_ERROR; 73 + 74 + return THERMAL_SUCCESS; 75 + }
+135
tools/lib/thermal/thermal.c
··· 1 + // SPDX-License-Identifier: LGPL-2.1+ 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #include <stdio.h> 4 + #include <thermal.h> 5 + 6 + #include "thermal_nl.h" 7 + 8 + int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg) 9 + { 10 + int i, ret = 0; 11 + 12 + if (!cdev) 13 + return 0; 14 + 15 + for (i = 0; cdev[i].id != -1; i++) 16 + ret |= cb(&cdev[i], arg); 17 + 18 + return ret; 19 + } 20 + 21 + int for_each_thermal_trip(struct thermal_trip *tt, cb_tt_t cb, void *arg) 22 + { 23 + int i, ret = 0; 24 + 25 + if (!tt) 26 + return 0; 27 + 28 + for (i = 0; tt[i].id != -1; i++) 29 + ret |= cb(&tt[i], arg); 30 + 31 + return ret; 32 + } 33 + 34 + int for_each_thermal_zone(struct thermal_zone *tz, cb_tz_t cb, void *arg) 35 + { 36 + int i, ret = 0; 37 + 38 + if (!tz) 39 + return 0; 40 + 41 + for (i = 0; tz[i].id != -1; i++) 42 + ret |= cb(&tz[i], arg); 43 + 44 + return ret; 45 + } 46 + 47 + struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz, 48 + const char *name) 49 + { 50 + int i; 51 + 52 + if (!tz || !name) 53 + return NULL; 54 + 55 + for (i = 0; tz[i].id != -1; i++) { 56 + if (!strcmp(tz[i].name, name)) 57 + return &tz[i]; 58 + } 59 + 60 + return NULL; 61 + } 62 + 63 + struct thermal_zone *thermal_zone_find_by_id(struct thermal_zone *tz, int id) 64 + { 65 + int i; 66 + 67 + if (!tz || id < 0) 68 + return NULL; 69 + 70 + for (i = 0; tz[i].id != -1; i++) { 71 + if (tz[i].id == id) 72 + return &tz[i]; 73 + } 74 + 75 + return NULL; 76 + } 77 + 78 + static int __thermal_zone_discover(struct thermal_zone *tz, void *th) 79 + { 80 + if (thermal_cmd_get_trip(th, tz) < 0) 81 + return -1; 82 + 83 + if (thermal_cmd_get_governor(th, tz)) 84 + return -1; 85 + 86 + return 0; 87 + } 88 + 89 + struct thermal_zone *thermal_zone_discover(struct thermal_handler *th) 90 + { 91 + struct thermal_zone *tz; 92 + 93 + if (thermal_cmd_get_tz(th, &tz) < 0) 94 + return NULL; 95 + 96 + if (for_each_thermal_zone(tz, __thermal_zone_discover, th)) 97 + return NULL; 98 + 99 + return tz; 100 + } 101 + 102 + void thermal_exit(struct thermal_handler *th) 103 + { 104 + thermal_cmd_exit(th); 105 + thermal_events_exit(th); 106 + thermal_sampling_exit(th); 107 + 108 + free(th); 109 + } 110 + 111 + struct thermal_handler *thermal_init(struct thermal_ops *ops) 112 + { 113 + struct thermal_handler *th; 114 + 115 + th = malloc(sizeof(*th)); 116 + if (!th) 117 + return NULL; 118 + th->ops = ops; 119 + 120 + if (thermal_events_init(th)) 121 + goto out_free; 122 + 123 + if (thermal_sampling_init(th)) 124 + goto out_free; 125 + 126 + if (thermal_cmd_init(th)) 127 + goto out_free; 128 + 129 + return th; 130 + 131 + out_free: 132 + free(th); 133 + 134 + return NULL; 135 + }
+215
tools/lib/thermal/thermal_nl.c
··· 1 + // SPDX-License-Identifier: LGPL-2.1+ 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #include <errno.h> 4 + #include <stdio.h> 5 + #include <stdlib.h> 6 + #include <unistd.h> 7 + 8 + #include <thermal.h> 9 + #include "thermal_nl.h" 10 + 11 + struct handler_args { 12 + const char *group; 13 + int id; 14 + }; 15 + 16 + static __thread int err; 17 + static __thread int done; 18 + 19 + static int nl_seq_check_handler(struct nl_msg *msg, void *arg) 20 + { 21 + return NL_OK; 22 + } 23 + 24 + static int nl_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *nl_err, 25 + void *arg) 26 + { 27 + int *ret = arg; 28 + 29 + if (ret) 30 + *ret = nl_err->error; 31 + 32 + return NL_STOP; 33 + } 34 + 35 + static int nl_finish_handler(struct nl_msg *msg, void *arg) 36 + { 37 + int *ret = arg; 38 + 39 + if (ret) 40 + *ret = 1; 41 + 42 + return NL_OK; 43 + } 44 + 45 + static int nl_ack_handler(struct nl_msg *msg, void *arg) 46 + { 47 + int *ret = arg; 48 + 49 + if (ret) 50 + *ret = 1; 51 + 52 + return NL_OK; 53 + } 54 + 55 + int nl_send_msg(struct nl_sock *sock, struct nl_cb *cb, struct nl_msg *msg, 56 + int (*rx_handler)(struct nl_msg *, void *), void *data) 57 + { 58 + if (!rx_handler) 59 + return THERMAL_ERROR; 60 + 61 + err = nl_send_auto_complete(sock, msg); 62 + if (err < 0) 63 + return err; 64 + 65 + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, rx_handler, data); 66 + 67 + err = done = 0; 68 + 69 + while (err == 0 && done == 0) 70 + nl_recvmsgs(sock, cb); 71 + 72 + return err; 73 + } 74 + 75 + static int nl_family_handler(struct nl_msg *msg, void *arg) 76 + { 77 + struct handler_args *grp = arg; 78 + struct nlattr *tb[CTRL_ATTR_MAX + 1]; 79 + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); 80 + struct nlattr *mcgrp; 81 + int rem_mcgrp; 82 + 83 + nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), 84 + genlmsg_attrlen(gnlh, 0), NULL); 85 + 86 + if (!tb[CTRL_ATTR_MCAST_GROUPS]) 87 + return THERMAL_ERROR; 88 + 89 + nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { 90 + 91 + struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; 92 + 93 + nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, 94 + nla_data(mcgrp), nla_len(mcgrp), NULL); 95 + 96 + if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || 97 + !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) 98 + continue; 99 + 100 + if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), 101 + grp->group, 102 + nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) 103 + continue; 104 + 105 + grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); 106 + 107 + break; 108 + } 109 + 110 + return THERMAL_SUCCESS; 111 + } 112 + 113 + static int nl_get_multicast_id(struct nl_sock *sock, struct nl_cb *cb, 114 + const char *family, const char *group) 115 + { 116 + struct nl_msg *msg; 117 + int ret = 0, ctrlid; 118 + struct handler_args grp = { 119 + .group = group, 120 + .id = -ENOENT, 121 + }; 122 + 123 + msg = nlmsg_alloc(); 124 + if (!msg) 125 + return THERMAL_ERROR; 126 + 127 + ctrlid = genl_ctrl_resolve(sock, "nlctrl"); 128 + 129 + genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); 130 + 131 + nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, family); 132 + 133 + ret = nl_send_msg(sock, cb, msg, nl_family_handler, &grp); 134 + if (ret) 135 + goto nla_put_failure; 136 + 137 + ret = grp.id; 138 + 139 + nla_put_failure: 140 + nlmsg_free(msg); 141 + return ret; 142 + } 143 + 144 + int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb) 145 + { 146 + struct nl_cb *cb; 147 + struct nl_sock *sock; 148 + 149 + cb = nl_cb_alloc(NL_CB_DEFAULT); 150 + if (!cb) 151 + return THERMAL_ERROR; 152 + 153 + sock = nl_socket_alloc(); 154 + if (!sock) 155 + goto out_cb_free; 156 + 157 + if (genl_connect(sock)) 158 + goto out_socket_free; 159 + 160 + if (nl_cb_err(cb, NL_CB_CUSTOM, nl_error_handler, &err) || 161 + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl_finish_handler, &done) || 162 + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_ack_handler, &done) || 163 + nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check_handler, &done)) 164 + return THERMAL_ERROR; 165 + 166 + *nl_sock = sock; 167 + *nl_cb = cb; 168 + 169 + return THERMAL_SUCCESS; 170 + 171 + out_socket_free: 172 + nl_socket_free(sock); 173 + out_cb_free: 174 + nl_cb_put(cb); 175 + return THERMAL_ERROR; 176 + } 177 + 178 + void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb) 179 + { 180 + nl_close(nl_sock); 181 + nl_socket_free(nl_sock); 182 + nl_cb_put(nl_cb); 183 + } 184 + 185 + int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, 186 + const char *group) 187 + { 188 + int mcid; 189 + 190 + mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME, 191 + group); 192 + if (mcid < 0) 193 + return THERMAL_ERROR; 194 + 195 + if (nl_socket_drop_membership(nl_sock, mcid)) 196 + return THERMAL_ERROR; 197 + 198 + return THERMAL_SUCCESS; 199 + } 200 + 201 + int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, 202 + const char *group) 203 + { 204 + int mcid; 205 + 206 + mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME, 207 + group); 208 + if (mcid < 0) 209 + return THERMAL_ERROR; 210 + 211 + if (nl_socket_add_membership(nl_sock, mcid)) 212 + return THERMAL_ERROR; 213 + 214 + return THERMAL_SUCCESS; 215 + }
+46
tools/lib/thermal/thermal_nl.h
··· 1 + /* SPDX-License-Identifier: LGPL-2.1+ */ 2 + /* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */ 3 + #ifndef __THERMAL_H 4 + #define __THERMAL_H 5 + 6 + #include <netlink/netlink.h> 7 + #include <netlink/genl/genl.h> 8 + #include <netlink/genl/mngt.h> 9 + #include <netlink/genl/ctrl.h> 10 + 11 + struct thermal_handler { 12 + int done; 13 + int error; 14 + struct thermal_ops *ops; 15 + struct nl_msg *msg; 16 + struct nl_sock *sk_event; 17 + struct nl_sock *sk_sampling; 18 + struct nl_sock *sk_cmd; 19 + struct nl_cb *cb_cmd; 20 + struct nl_cb *cb_event; 21 + struct nl_cb *cb_sampling; 22 + }; 23 + 24 + struct thermal_handler_param { 25 + struct thermal_handler *th; 26 + void *arg; 27 + }; 28 + 29 + /* 30 + * Low level netlink 31 + */ 32 + extern int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, 33 + const char *group); 34 + 35 + extern int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, 36 + const char *group); 37 + 38 + extern int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb); 39 + 40 + extern void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb); 41 + 42 + extern int nl_send_msg(struct nl_sock *sock, struct nl_cb *nl_cb, struct nl_msg *msg, 43 + int (*rx_handler)(struct nl_msg *, void *), 44 + void *data); 45 + 46 + #endif /* __THERMAL_H */
+3
tools/thermal/lib/Build
··· 1 + libthermal_tools-y += mainloop.o 2 + libthermal_tools-y += log.o 3 + libthermal_tools-y += uptimeofday.o
+158
tools/thermal/lib/Makefile
··· 1 + # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 + # Most of this file is copied from tools/lib/perf/Makefile 3 + 4 + LIBTHERMAL_TOOLS_VERSION = 0 5 + LIBTHERMAL_TOOLS_PATCHLEVEL = 0 6 + LIBTHERMAL_TOOLS_EXTRAVERSION = 1 7 + 8 + MAKEFLAGS += --no-print-directory 9 + 10 + ifeq ($(srctree),) 11 + srctree := $(patsubst %/,%,$(dir $(CURDIR))) 12 + srctree := $(patsubst %/,%,$(dir $(srctree))) 13 + srctree := $(patsubst %/,%,$(dir $(srctree))) 14 + # $(info Determined 'srctree' to be $(srctree)) 15 + endif 16 + 17 + INSTALL = install 18 + 19 + # Use DESTDIR for installing into a different root directory. 20 + # This is useful for building a package. The program will be 21 + # installed in this directory as if it was the root directory. 22 + # Then the build tool can move it later. 23 + DESTDIR ?= 24 + DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' 25 + 26 + include $(srctree)/tools/scripts/Makefile.include 27 + include $(srctree)/tools/scripts/Makefile.arch 28 + 29 + ifeq ($(LP64), 1) 30 + libdir_relative = lib64 31 + else 32 + libdir_relative = lib 33 + endif 34 + 35 + prefix ?= 36 + libdir = $(prefix)/$(libdir_relative) 37 + 38 + # Shell quotes 39 + libdir_SQ = $(subst ','\'',$(libdir)) 40 + libdir_relative_SQ = $(subst ','\'',$(libdir_relative)) 41 + 42 + ifeq ("$(origin V)", "command line") 43 + VERBOSE = $(V) 44 + endif 45 + ifndef VERBOSE 46 + VERBOSE = 0 47 + endif 48 + 49 + ifeq ($(VERBOSE),1) 50 + Q = 51 + else 52 + Q = @ 53 + endif 54 + 55 + # Set compile option CFLAGS 56 + ifdef EXTRA_CFLAGS 57 + CFLAGS := $(EXTRA_CFLAGS) 58 + else 59 + CFLAGS := -g -Wall 60 + endif 61 + 62 + INCLUDES = \ 63 + -I/usr/include/libnl3 \ 64 + -I$(srctree)/tools/lib/thermal/include \ 65 + -I$(srctree)/tools/lib/ \ 66 + -I$(srctree)/tools/include \ 67 + -I$(srctree)/tools/arch/$(SRCARCH)/include/ \ 68 + -I$(srctree)/tools/arch/$(SRCARCH)/include/uapi \ 69 + -I$(srctree)/tools/include/uapi 70 + 71 + # Append required CFLAGS 72 + override CFLAGS += $(EXTRA_WARNINGS) 73 + override CFLAGS += -Werror -Wall 74 + override CFLAGS += -fPIC 75 + override CFLAGS += $(INCLUDES) 76 + override CFGLAS += -Wl,-L. 77 + override CFGLAS += -Wl,-lthermal 78 + 79 + all: 80 + 81 + export srctree OUTPUT CC LD CFLAGS V 82 + export DESTDIR DESTDIR_SQ 83 + 84 + include $(srctree)/tools/build/Makefile.include 85 + 86 + PATCHLEVEL = $(LIBTHERMAL_TOOLS_PATCHLEVEL) 87 + EXTRAVERSION = $(LIBTHERMAL_TOOLS_EXTRAVERSION) 88 + VERSION = $(LIBTHERMAL_TOOLS_VERSION).$(LIBTHERMAL_TOOLS_PATCHLEVEL).$(LIBTHERMAL_TOOLS_EXTRAVERSION) 89 + 90 + LIBTHERMAL_TOOLS_SO := $(OUTPUT)libthermal_tools.so.$(VERSION) 91 + LIBTHERMAL_TOOLS_A := $(OUTPUT)libthermal_tools.a 92 + LIBTHERMAL_TOOLS_IN := $(OUTPUT)libthermal_tools-in.o 93 + LIBTHERMAL_TOOLS_PC := $(OUTPUT)libthermal_tools.pc 94 + 95 + LIBTHERMAL_TOOLS_ALL := $(LIBTHERMAL_TOOLS_A) $(OUTPUT)libthermal_tools.so* 96 + 97 + $(LIBTHERMAL_TOOLS_IN): FORCE 98 + $(Q)$(MAKE) $(build)=libthermal_tools 99 + 100 + $(LIBTHERMAL_TOOLS_A): $(LIBTHERMAL_TOOLS_IN) 101 + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIBTHERMAL_TOOLS_IN) 102 + 103 + $(LIBTHERMAL_TOOLS_SO): $(LIBTHERMAL_TOOLS_IN) 104 + $(QUIET_LINK)$(CC) --shared -Wl,-soname,libthermal_tools.so $^ -o $@ 105 + @ln -sf $(@F) $(OUTPUT)libthermal_tools.so 106 + @ln -sf $(@F) $(OUTPUT)libthermal_tools.so.$(LIBTHERMAL_TOOLS_VERSION) 107 + 108 + 109 + libs: $(LIBTHERMAL_TOOLS_A) $(LIBTHERMAL_TOOLS_SO) $(LIBTHERMAL_TOOLS_PC) 110 + 111 + all: fixdep 112 + $(Q)$(MAKE) libs 113 + 114 + clean: 115 + $(call QUIET_CLEAN, libthermal_tools) $(RM) $(LIBTHERMAL_TOOLS_A) \ 116 + *.o *~ *.a *.so *.so.$(VERSION) *.so.$(LIBTHERMAL_TOOLS_VERSION) .*.d .*.cmd LIBTHERMAL_TOOLS-CFLAGS $(LIBTHERMAL_TOOLS_PC) 117 + 118 + $(LIBTHERMAL_TOOLS_PC): 119 + $(QUIET_GEN)sed -e "s|@PREFIX@|$(prefix)|" \ 120 + -e "s|@LIBDIR@|$(libdir_SQ)|" \ 121 + -e "s|@VERSION@|$(VERSION)|" \ 122 + < libthermal_tools.pc.template > $@ 123 + 124 + define do_install_mkdir 125 + if [ ! -d '$(DESTDIR_SQ)$1' ]; then \ 126 + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \ 127 + fi 128 + endef 129 + 130 + define do_install 131 + if [ ! -d '$(DESTDIR_SQ)$2' ]; then \ 132 + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \ 133 + fi; \ 134 + $(INSTALL) $1 $(if $3,-m $3,) '$(DESTDIR_SQ)$2' 135 + endef 136 + 137 + install_lib: libs 138 + $(call QUIET_INSTALL, $(LIBTHERMAL_TOOLS_ALL)) \ 139 + $(call do_install_mkdir,$(libdir_SQ)); \ 140 + cp -fpR $(LIBTHERMAL_TOOLS_ALL) $(DESTDIR)$(libdir_SQ) 141 + 142 + install_headers: 143 + $(call QUIET_INSTALL, headers) \ 144 + $(call do_install,include/thermal.h,$(prefix)/include/thermal,644); \ 145 + 146 + install_pkgconfig: $(LIBTHERMAL_TOOLS_PC) 147 + $(call QUIET_INSTALL, $(LIBTHERMAL_TOOLS_PC)) \ 148 + $(call do_install,$(LIBTHERMAL_TOOLS_PC),$(libdir_SQ)/pkgconfig,644) 149 + 150 + install_doc: 151 + $(Q)$(MAKE) -C Documentation install-man install-html install-examples 152 + 153 + #install: install_lib install_headers install_pkgconfig install_doc 154 + install: install_lib install_headers install_pkgconfig 155 + 156 + FORCE: 157 + 158 + .PHONY: all install clean FORCE
+12
tools/thermal/lib/libthermal_tools.pc.template
··· 1 + # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 + 3 + prefix=@PREFIX@ 4 + libdir=@LIBDIR@ 5 + includedir=${prefix}/include 6 + 7 + Name: libthermal 8 + Description: thermal library 9 + Requires: libnl-3.0 libnl-genl-3.0 10 + Version: @VERSION@ 11 + Libs: -L${libdir} -lnl-genl-3 -lnl-3 12 + Cflags: -I${includedir} -I{include}/libnl3
+77
tools/thermal/lib/log.c
··· 1 + // SPDX-License-Identifier: LGPL-2.1+ 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #include <stdarg.h> 4 + #include <stdio.h> 5 + #include <string.h> 6 + #include <syslog.h> 7 + #include "log.h" 8 + 9 + static const char *__ident = "unknown"; 10 + static int __options; 11 + 12 + static const char * const loglvl[] = { 13 + [LOG_DEBUG] = "DEBUG", 14 + [LOG_INFO] = "INFO", 15 + [LOG_NOTICE] = "NOTICE", 16 + [LOG_WARNING] = "WARN", 17 + [LOG_ERR] = "ERROR", 18 + [LOG_CRIT] = "CRITICAL", 19 + [LOG_ALERT] = "ALERT", 20 + [LOG_EMERG] = "EMERG", 21 + }; 22 + 23 + int log_str2level(const char *lvl) 24 + { 25 + int i; 26 + 27 + for (i = 0; i < sizeof(loglvl) / sizeof(loglvl[LOG_DEBUG]); i++) 28 + if (!strcmp(lvl, loglvl[i])) 29 + return i; 30 + 31 + return LOG_DEBUG; 32 + } 33 + 34 + extern void logit(int level, const char *format, ...) 35 + { 36 + va_list args; 37 + 38 + va_start(args, format); 39 + 40 + if (__options & TO_SYSLOG) 41 + vsyslog(level, format, args); 42 + 43 + if (__options & TO_STDERR) 44 + vfprintf(stderr, format, args); 45 + 46 + if (__options & TO_STDOUT) 47 + vfprintf(stdout, format, args); 48 + 49 + va_end(args); 50 + } 51 + 52 + int log_init(int level, const char *ident, int options) 53 + { 54 + if (!options) 55 + return -1; 56 + 57 + if (level > LOG_DEBUG) 58 + return -1; 59 + 60 + if (!ident) 61 + return -1; 62 + 63 + __ident = ident; 64 + __options = options; 65 + 66 + if (options & TO_SYSLOG) { 67 + openlog(__ident, options | LOG_NDELAY, LOG_USER); 68 + setlogmask(LOG_UPTO(level)); 69 + } 70 + 71 + return 0; 72 + } 73 + 74 + void log_exit(void) 75 + { 76 + closelog(); 77 + }
+31
tools/thermal/lib/log.h
··· 1 + /* SPDX-License-Identifier: LGPL-2.1+ */ 2 + /* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */ 3 + #ifndef __THERMAL_TOOLS_LOG_H 4 + #define __THERMAL_TOOLS_LOG_H 5 + 6 + #include <syslog.h> 7 + 8 + #ifndef __maybe_unused 9 + #define __maybe_unused __attribute__((__unused__)) 10 + #endif 11 + 12 + #define TO_SYSLOG 0x1 13 + #define TO_STDOUT 0x2 14 + #define TO_STDERR 0x4 15 + 16 + extern void logit(int level, const char *format, ...); 17 + 18 + #define DEBUG(fmt, ...) logit(LOG_DEBUG, "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__) 19 + #define INFO(fmt, ...) logit(LOG_INFO, fmt, ##__VA_ARGS__) 20 + #define NOTICE(fmt, ...) logit(LOG_NOTICE, fmt, ##__VA_ARGS__) 21 + #define WARN(fmt, ...) logit(LOG_WARNING, fmt, ##__VA_ARGS__) 22 + #define ERROR(fmt, ...) logit(LOG_ERR, fmt, ##__VA_ARGS__) 23 + #define CRITICAL(fmt, ...) logit(LOG_CRIT, fmt, ##__VA_ARGS__) 24 + #define ALERT(fmt, ...) logit(LOG_ALERT, fmt, ##__VA_ARGS__) 25 + #define EMERG(fmt, ...) logit(LOG_EMERG, fmt, ##__VA_ARGS__) 26 + 27 + int log_init(int level, const char *ident, int options); 28 + int log_str2level(const char *lvl); 29 + void log_exit(void); 30 + 31 + #endif
+120
tools/thermal/lib/mainloop.c
··· 1 + // SPDX-License-Identifier: LGPL-2.1+ 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #include <stdlib.h> 4 + #include <errno.h> 5 + #include <unistd.h> 6 + #include <signal.h> 7 + #include <sys/epoll.h> 8 + #include "mainloop.h" 9 + #include "log.h" 10 + 11 + static int epfd = -1; 12 + static unsigned short nrhandler; 13 + static sig_atomic_t exit_mainloop; 14 + 15 + struct mainloop_data { 16 + mainloop_callback_t cb; 17 + void *data; 18 + int fd; 19 + }; 20 + 21 + static struct mainloop_data **mds; 22 + 23 + #define MAX_EVENTS 10 24 + 25 + int mainloop(unsigned int timeout) 26 + { 27 + int i, nfds; 28 + struct epoll_event events[MAX_EVENTS]; 29 + struct mainloop_data *md; 30 + 31 + if (epfd < 0) 32 + return -1; 33 + 34 + for (;;) { 35 + 36 + nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout); 37 + 38 + if (exit_mainloop || !nfds) 39 + return 0; 40 + 41 + if (nfds < 0) { 42 + if (errno == EINTR) 43 + continue; 44 + return -1; 45 + } 46 + 47 + for (i = 0; i < nfds; i++) { 48 + md = events[i].data.ptr; 49 + 50 + if (md->cb(md->fd, md->data) > 0) 51 + return 0; 52 + } 53 + } 54 + } 55 + 56 + int mainloop_add(int fd, mainloop_callback_t cb, void *data) 57 + { 58 + struct epoll_event ev = { 59 + .events = EPOLLIN, 60 + }; 61 + 62 + struct mainloop_data *md; 63 + 64 + if (fd >= nrhandler) { 65 + mds = realloc(mds, sizeof(*mds) * (fd + 1)); 66 + if (!mds) 67 + return -1; 68 + nrhandler = fd + 1; 69 + } 70 + 71 + md = malloc(sizeof(*md)); 72 + if (!md) 73 + return -1; 74 + 75 + md->data = data; 76 + md->cb = cb; 77 + md->fd = fd; 78 + 79 + mds[fd] = md; 80 + ev.data.ptr = md; 81 + 82 + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0) { 83 + free(md); 84 + return -1; 85 + } 86 + 87 + return 0; 88 + } 89 + 90 + int mainloop_del(int fd) 91 + { 92 + if (fd >= nrhandler) 93 + return -1; 94 + 95 + if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) < 0) 96 + return -1; 97 + 98 + free(mds[fd]); 99 + 100 + return 0; 101 + } 102 + 103 + int mainloop_init(void) 104 + { 105 + epfd = epoll_create(2); 106 + if (epfd < 0) 107 + return -1; 108 + 109 + return 0; 110 + } 111 + 112 + void mainloop_exit(void) 113 + { 114 + exit_mainloop = 1; 115 + } 116 + 117 + void mainloop_fini(void) 118 + { 119 + close(epfd); 120 + }
+15
tools/thermal/lib/mainloop.h
··· 1 + /* SPDX-License-Identifier: LGPL-2.1+ */ 2 + /* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */ 3 + #ifndef __THERMAL_TOOLS_MAINLOOP_H 4 + #define __THERMAL_TOOLS_MAINLOOP_H 5 + 6 + typedef int (*mainloop_callback_t)(int fd, void *data); 7 + 8 + extern int mainloop(unsigned int timeout); 9 + extern int mainloop_add(int fd, mainloop_callback_t cb, void *data); 10 + extern int mainloop_del(int fd); 11 + extern void mainloop_exit(void); 12 + extern int mainloop_init(void); 13 + extern void mainloop_fini(void); 14 + 15 + #endif
+10
tools/thermal/lib/thermal-tools.h
··· 1 + /* SPDX-License-Identifier: LGPL-2.1+ */ 2 + /* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */ 3 + #ifndef __THERMAL_TOOLS 4 + #define __THERMAL_TOOLS 5 + 6 + #include "log.h" 7 + #include "mainloop.h" 8 + #include "uptimeofday.h" 9 + 10 + #endif
+40
tools/thermal/lib/uptimeofday.c
··· 1 + // SPDX-License-Identifier: LGPL-2.1+ 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #include <stdio.h> 4 + #include <sys/time.h> 5 + #include <linux/sysinfo.h> 6 + #include "thermal-tools.h" 7 + 8 + static unsigned long __offset; 9 + static struct timeval __tv; 10 + 11 + int uptimeofday_init(void) 12 + { 13 + struct sysinfo info; 14 + 15 + if (sysinfo(&info)) 16 + return -1; 17 + 18 + gettimeofday(&__tv, NULL); 19 + 20 + __offset = __tv.tv_sec - info.uptime; 21 + 22 + return 0; 23 + } 24 + 25 + unsigned long getuptimeofday_ms(void) 26 + { 27 + gettimeofday(&__tv, NULL); 28 + 29 + return ((__tv.tv_sec - __offset) * 1000) + (__tv.tv_usec / 1000); 30 + } 31 + 32 + struct timespec msec_to_timespec(int msec) 33 + { 34 + struct timespec tv = { 35 + .tv_sec = (msec / 1000), 36 + .tv_nsec = (msec % 1000) * 1000000, 37 + }; 38 + 39 + return tv; 40 + }
+12
tools/thermal/lib/uptimeofday.h
··· 1 + /* SPDX-License-Identifier: LGPL-2.1+ */ 2 + /* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */ 3 + #ifndef __THERMAL_TOOLS_UPTIMEOFDAY_H 4 + #define __THERMAL_TOOLS_UPTIMEOFDAY_H 5 + #include <sys/sysinfo.h> 6 + #include <sys/time.h> 7 + 8 + int uptimeofday_init(void); 9 + unsigned long getuptimeofday_ms(void); 10 + struct timespec msec_to_timespec(int msec); 11 + 12 + #endif
+1
tools/thermal/thermal-engine/Build
··· 1 + thermal-engine-y += thermal-engine.o
+28
tools/thermal/thermal-engine/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + # Makefile for thermal tools 3 + 4 + ifeq ($(srctree),) 5 + srctree := $(patsubst %/,%,$(dir $(CURDIR))) 6 + srctree := $(patsubst %/,%,$(dir $(srctree))) 7 + srctree := $(patsubst %/,%,$(dir $(srctree))) 8 + # $(info Determined 'srctree' to be $(srctree)) 9 + endif 10 + 11 + CFLAGS = -Wall -Wextra 12 + CFLAGS += -I$(srctree)/tools/thermal/lib 13 + CFLAGS += -I$(srctree)/tools/lib/thermal/include 14 + 15 + LDFLAGS = -L$(srctree)/tools/thermal/lib 16 + LDFLAGS += -L$(srctree)/tools/lib/thermal 17 + LDFLAGS += -lthermal_tools 18 + LDFLAGS += -lthermal 19 + LDFLAGS += -lconfig 20 + LDFLAGS += -lnl-genl-3 -lnl-3 21 + 22 + VERSION = 0.0.1 23 + 24 + all: thermal-engine 25 + %: %.c 26 + $(CC) $(CFLAGS) -D VERSION=\"$(VERSION)\" -o $@ $^ $(LDFLAGS) 27 + clean: 28 + $(RM) thermal-engine
+341
tools/thermal/thermal-engine/thermal-engine.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Thermal monitoring tool based on the thermal netlink events. 4 + * 5 + * Copyright (C) 2022 Linaro Ltd. 6 + * 7 + * Author: Daniel Lezcano <daniel.lezcano@kernel.org> 8 + */ 9 + #include <errno.h> 10 + #include <fcntl.h> 11 + #include <getopt.h> 12 + #include <libgen.h> 13 + #include <limits.h> 14 + #include <stdio.h> 15 + #include <stdlib.h> 16 + #include <string.h> 17 + #include <signal.h> 18 + #include <unistd.h> 19 + 20 + #include <syslog.h> 21 + 22 + #include <sys/epoll.h> 23 + #include <sys/stat.h> 24 + #include <sys/types.h> 25 + 26 + #include <thermal.h> 27 + #include "thermal-tools.h" 28 + 29 + struct options { 30 + int loglevel; 31 + int logopt; 32 + int interactive; 33 + int daemonize; 34 + }; 35 + 36 + struct thermal_data { 37 + struct thermal_zone *tz; 38 + struct thermal_handler *th; 39 + }; 40 + 41 + static int show_trip(struct thermal_trip *tt, __maybe_unused void *arg) 42 + { 43 + INFO("trip id=%d, type=%d, temp=%d, hyst=%d\n", 44 + tt->id, tt->type, tt->temp, tt->hyst); 45 + 46 + return 0; 47 + } 48 + 49 + static int show_temp(struct thermal_zone *tz, __maybe_unused void *arg) 50 + { 51 + thermal_cmd_get_temp(arg, tz); 52 + 53 + INFO("temperature: %d\n", tz->temp); 54 + 55 + return 0; 56 + } 57 + 58 + static int show_governor(struct thermal_zone *tz, __maybe_unused void *arg) 59 + { 60 + thermal_cmd_get_governor(arg, tz); 61 + 62 + INFO("governor: '%s'\n", tz->governor); 63 + 64 + return 0; 65 + } 66 + 67 + static int show_tz(struct thermal_zone *tz, __maybe_unused void *arg) 68 + { 69 + INFO("thermal zone '%s', id=%d\n", tz->name, tz->id); 70 + 71 + for_each_thermal_trip(tz->trip, show_trip, NULL); 72 + 73 + show_temp(tz, arg); 74 + 75 + show_governor(tz, arg); 76 + 77 + return 0; 78 + } 79 + 80 + static int tz_create(const char *name, int tz_id, __maybe_unused void *arg) 81 + { 82 + INFO("Thermal zone '%s'/%d created\n", name, tz_id); 83 + 84 + return 0; 85 + } 86 + 87 + static int tz_delete(int tz_id, __maybe_unused void *arg) 88 + { 89 + INFO("Thermal zone %d deleted\n", tz_id); 90 + 91 + return 0; 92 + } 93 + 94 + static int tz_disable(int tz_id, void *arg) 95 + { 96 + struct thermal_data *td = arg; 97 + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 98 + 99 + INFO("Thermal zone %d ('%s') disabled\n", tz_id, tz->name); 100 + 101 + return 0; 102 + } 103 + 104 + static int tz_enable(int tz_id, void *arg) 105 + { 106 + struct thermal_data *td = arg; 107 + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 108 + 109 + INFO("Thermal zone %d ('%s') enabled\n", tz_id, tz->name); 110 + 111 + return 0; 112 + } 113 + 114 + static int trip_high(int tz_id, int trip_id, int temp, void *arg) 115 + { 116 + struct thermal_data *td = arg; 117 + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 118 + 119 + INFO("Thermal zone %d ('%s'): trip point %d crossed way up with %d °C\n", 120 + tz_id, tz->name, trip_id, temp); 121 + 122 + return 0; 123 + } 124 + 125 + static int trip_low(int tz_id, int trip_id, int temp, void *arg) 126 + { 127 + struct thermal_data *td = arg; 128 + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 129 + 130 + INFO("Thermal zone %d ('%s'): trip point %d crossed way down with %d °C\n", 131 + tz_id, tz->name, trip_id, temp); 132 + 133 + return 0; 134 + } 135 + 136 + static int trip_add(int tz_id, int trip_id, int type, int temp, int hyst, __maybe_unused void *arg) 137 + { 138 + INFO("Trip point added %d: id=%d, type=%d, temp=%d, hyst=%d\n", 139 + tz_id, trip_id, type, temp, hyst); 140 + 141 + return 0; 142 + } 143 + 144 + static int trip_delete(int tz_id, int trip_id, __maybe_unused void *arg) 145 + { 146 + INFO("Trip point deleted %d: id=%d\n", tz_id, trip_id); 147 + 148 + return 0; 149 + } 150 + 151 + static int trip_change(int tz_id, int trip_id, int type, int temp, 152 + int hyst, __maybe_unused void *arg) 153 + { 154 + struct thermal_data *td = arg; 155 + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 156 + 157 + INFO("Trip point changed %d: id=%d, type=%d, temp=%d, hyst=%d\n", 158 + tz_id, trip_id, type, temp, hyst); 159 + 160 + tz->trip[trip_id].type = type; 161 + tz->trip[trip_id].temp = temp; 162 + tz->trip[trip_id].hyst = hyst; 163 + 164 + return 0; 165 + } 166 + 167 + static int cdev_add(const char *name, int cdev_id, int max_state, __maybe_unused void *arg) 168 + { 169 + INFO("Cooling device '%s'/%d (max state=%d) added\n", name, cdev_id, max_state); 170 + 171 + return 0; 172 + } 173 + 174 + static int cdev_delete(int cdev_id, __maybe_unused void *arg) 175 + { 176 + INFO("Cooling device %d deleted", cdev_id); 177 + 178 + return 0; 179 + } 180 + 181 + static int cdev_update(int cdev_id, int cur_state, __maybe_unused void *arg) 182 + { 183 + INFO("cdev:%d state:%d\n", cdev_id, cur_state); 184 + 185 + return 0; 186 + } 187 + 188 + static int gov_change(int tz_id, const char *name, __maybe_unused void *arg) 189 + { 190 + struct thermal_data *td = arg; 191 + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); 192 + 193 + INFO("%s: governor changed %s -> %s\n", tz->name, tz->governor, name); 194 + 195 + strcpy(tz->governor, name); 196 + 197 + return 0; 198 + } 199 + 200 + static struct thermal_ops ops = { 201 + .events.tz_create = tz_create, 202 + .events.tz_delete = tz_delete, 203 + .events.tz_disable = tz_disable, 204 + .events.tz_enable = tz_enable, 205 + .events.trip_high = trip_high, 206 + .events.trip_low = trip_low, 207 + .events.trip_add = trip_add, 208 + .events.trip_delete = trip_delete, 209 + .events.trip_change = trip_change, 210 + .events.cdev_add = cdev_add, 211 + .events.cdev_delete = cdev_delete, 212 + .events.cdev_update = cdev_update, 213 + .events.gov_change = gov_change 214 + }; 215 + 216 + static int thermal_event(__maybe_unused int fd, __maybe_unused void *arg) 217 + { 218 + struct thermal_data *td = arg; 219 + 220 + return thermal_events_handle(td->th, td); 221 + } 222 + 223 + static void usage(const char *cmd) 224 + { 225 + printf("%s : A thermal monitoring engine based on notifications\n", cmd); 226 + printf("Usage: %s [options]\n", cmd); 227 + printf("\t-h, --help\t\tthis help\n"); 228 + printf("\t-d, --daemonize\n"); 229 + printf("\t-l <level>, --loglevel <level>\tlog level: "); 230 + printf("DEBUG, INFO, NOTICE, WARN, ERROR\n"); 231 + printf("\t-s, --syslog\t\toutput to syslog\n"); 232 + printf("\n"); 233 + exit(0); 234 + } 235 + 236 + static int options_init(int argc, char *argv[], struct options *options) 237 + { 238 + int opt; 239 + 240 + struct option long_options[] = { 241 + { "help", no_argument, NULL, 'h' }, 242 + { "daemonize", no_argument, NULL, 'd' }, 243 + { "syslog", no_argument, NULL, 's' }, 244 + { "loglevel", required_argument, NULL, 'l' }, 245 + { 0, 0, 0, 0 } 246 + }; 247 + 248 + while (1) { 249 + 250 + int optindex = 0; 251 + 252 + opt = getopt_long(argc, argv, "l:dhs", long_options, &optindex); 253 + if (opt == -1) 254 + break; 255 + 256 + switch (opt) { 257 + case 'l': 258 + options->loglevel = log_str2level(optarg); 259 + break; 260 + case 'd': 261 + options->daemonize = 1; 262 + break; 263 + case 's': 264 + options->logopt = TO_SYSLOG; 265 + break; 266 + case 'h': 267 + usage(basename(argv[0])); 268 + break; 269 + default: /* '?' */ 270 + return -1; 271 + } 272 + } 273 + 274 + return 0; 275 + } 276 + 277 + enum { 278 + THERMAL_ENGINE_SUCCESS = 0, 279 + THERMAL_ENGINE_OPTION_ERROR, 280 + THERMAL_ENGINE_DAEMON_ERROR, 281 + THERMAL_ENGINE_LOG_ERROR, 282 + THERMAL_ENGINE_THERMAL_ERROR, 283 + THERMAL_ENGINE_MAINLOOP_ERROR, 284 + }; 285 + 286 + int main(int argc, char *argv[]) 287 + { 288 + struct thermal_data td; 289 + struct options options = { 290 + .loglevel = LOG_INFO, 291 + .logopt = TO_STDOUT, 292 + }; 293 + 294 + if (options_init(argc, argv, &options)) { 295 + ERROR("Usage: %s --help\n", argv[0]); 296 + return THERMAL_ENGINE_OPTION_ERROR; 297 + } 298 + 299 + if (options.daemonize && daemon(0, 0)) { 300 + ERROR("Failed to daemonize: %p\n"); 301 + return THERMAL_ENGINE_DAEMON_ERROR; 302 + } 303 + 304 + if (log_init(options.loglevel, basename(argv[0]), options.logopt)) { 305 + ERROR("Failed to initialize logging facility\n"); 306 + return THERMAL_ENGINE_LOG_ERROR; 307 + } 308 + 309 + td.th = thermal_init(&ops); 310 + if (!td.th) { 311 + ERROR("Failed to initialize the thermal library\n"); 312 + return THERMAL_ENGINE_THERMAL_ERROR; 313 + } 314 + 315 + td.tz = thermal_zone_discover(td.th); 316 + if (!td.tz) { 317 + ERROR("No thermal zone available\n"); 318 + return THERMAL_ENGINE_THERMAL_ERROR; 319 + } 320 + 321 + for_each_thermal_zone(td.tz, show_tz, td.th); 322 + 323 + if (mainloop_init()) { 324 + ERROR("Failed to initialize the mainloop\n"); 325 + return THERMAL_ENGINE_MAINLOOP_ERROR; 326 + } 327 + 328 + if (mainloop_add(thermal_events_fd(td.th), thermal_event, &td)) { 329 + ERROR("Failed to setup the mainloop\n"); 330 + return THERMAL_ENGINE_MAINLOOP_ERROR; 331 + } 332 + 333 + INFO("Waiting for thermal events ...\n"); 334 + 335 + if (mainloop(-1)) { 336 + ERROR("Mainloop failed\n"); 337 + return THERMAL_ENGINE_MAINLOOP_ERROR; 338 + } 339 + 340 + return THERMAL_ENGINE_SUCCESS; 341 + }
+1
tools/thermal/thermometer/Build
··· 1 + thermometer-y += thermometer.o
+26
tools/thermal/thermometer/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + # Makefile for cgroup tools 3 + 4 + ifeq ($(srctree),) 5 + srctree := $(patsubst %/,%,$(dir $(CURDIR))) 6 + srctree := $(patsubst %/,%,$(dir $(srctree))) 7 + srctree := $(patsubst %/,%,$(dir $(srctree))) 8 + # $(info Determined 'srctree' to be $(srctree)) 9 + endif 10 + 11 + CFLAGS = -Wall -Wextra 12 + CFLAGS += -I$(srctree)/tools/thermal/lib 13 + 14 + LDFLAGS = -L$(srctree)/tools/thermal/lib 15 + LDFLAGS += -lthermal_tools 16 + LDFLAGS += -lconfig 17 + 18 + VERSION = 0.0.1 19 + TARGET=thermometer 20 + 21 + all: $(TARGET) 22 + %: %.c 23 + $(CC) $(CFLAGS) -D VERSION=\"$(VERSION)\" -o $@ $^ $(LDFLAGS) 24 + 25 + clean: 26 + $(RM) $(TARGET)
+92
tools/thermal/thermometer/thermometer.8
··· 1 + .TH THERMOMETER 8 2 + # SPDX-License-Identifier: GPL-2.0 3 + .SH NAME 4 + \fBthermometer\fP - A thermal profiling tool 5 + 6 + .SH SYNOPSIS 7 + .ft B 8 + .B thermometer 9 + .RB [ options ] 10 + .RB [ command ] 11 + .br 12 + .SH DESCRIPTION 13 + \fBthermometer \fP captures the thermal zones temperature at a 14 + specified sampling period. It is optimized to reduce as much as 15 + possible the overhead while doing the temperature acquisition in order 16 + to prevent disrupting the running application we may want to profile. 17 + 18 + This low overhead also allows a high rate sampling for the temperature 19 + which could be necessary to spot overshots and undershots. 20 + 21 + If no configuration file is specified, then all the thermal zones will 22 + be monitored at 4Hz, so every 250ms. A configuration file specifies 23 + the thermal zone names and the desired sampling period. A thermal zone 24 + name can be a regular expression to specify a group of thermal zone. 25 + 26 + The sampling of the different thermal zones will be written into 27 + separate files with the thermal zone name. It is possible to specify a 28 + postfix to identify them for example for a specific scenario. The 29 + output directory can be specified in addition. 30 + 31 + Without any parameters, \fBthermometer \fP captures all the thermal 32 + zone temperatures every 250ms and write to the current directory the 33 + captured files postfixed with the current date. 34 + 35 + If a running \fBduration\fP is specified or a \fBcommand\fP, the 36 + capture ends at the end of the duration if the command did not 37 + finished before. The \fBduration\fP can be specified alone as well as 38 + the \fBcommand\fP. If none is specified, the capture will continue 39 + indefinitively until interrupted by \fBSIGINT\fP or \fBSIGQUIT\fP. 40 + .PP 41 + 42 + .SS Options 43 + .PP 44 + The \fB-h, --help\fP option shows a short usage help 45 + .PP 46 + The \fB-o <dir>, --output <dir>\fP option defines the output directory to put the 47 + sampling files 48 + .PP 49 + The \fB-c <config>, --config <config>\fP option specifies the configuration file to use 50 + .PP 51 + The \fB-d <seconds>, --duration <seconds>\fP option specifies the duration of the capture 52 + .PP 53 + The \fB-l <loglevel>, --loglevel <loglevel>\fP option sets the loglevel [DEBUG,INFO,NOTICE,WARN,ERROR] 54 + .PP 55 + The \fB-p <string>, --postfix <string>\fP option appends \fBstring\fP at the end of the capture filenames 56 + .PP 57 + The \fB-s, --syslog\fP option sets the output to syslog, default is \fBstdout\fP 58 + .PP 59 + The \fB-w, --overwrite\fP overwrites the output files if they exist 60 + .PP 61 + 62 + .PP 63 + 64 + .SS "Exit status:" 65 + .TP 66 + 0 67 + if OK, 68 + .TP 69 + 1 70 + Error with the options specified as parameters 71 + .TP 72 + 2 73 + Error when configuring the logging facility 74 + .TP 75 + 3 76 + Error when configuring the time 77 + .TP 78 + 4 79 + Error in the initialization routine 80 + .TP 81 + 5 82 + Error during the runtime 83 + 84 + .SH Capture file format 85 + 86 + Every file contains two columns. The first one is the uptime timestamp 87 + in order to find a point in time since the system started up if there 88 + is any thermal event. The second one is the temperature in milli 89 + degree. The first line contains the label of each column. 90 + 91 + .SH AUTHOR 92 + Daniel Lezcano <daniel.lezcano@kernel.org>
+572
tools/thermal/thermometer/thermometer.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> 3 + #define _GNU_SOURCE 4 + #include <dirent.h> 5 + #include <fcntl.h> 6 + #include <getopt.h> 7 + #include <regex.h> 8 + #include <signal.h> 9 + #include <stdio.h> 10 + #include <stdlib.h> 11 + #include <string.h> 12 + #include <sys/stat.h> 13 + #include <sys/signalfd.h> 14 + #include <sys/timerfd.h> 15 + #include <sys/types.h> 16 + #include <sys/wait.h> 17 + #include <time.h> 18 + #include <unistd.h> 19 + #include <linux/thermal.h> 20 + 21 + #include <libconfig.h> 22 + #include "thermal-tools.h" 23 + 24 + #define CLASS_THERMAL "/sys/class/thermal" 25 + 26 + enum { 27 + THERMOMETER_SUCCESS = 0, 28 + THERMOMETER_OPTION_ERROR, 29 + THERMOMETER_LOG_ERROR, 30 + THERMOMETER_CONFIG_ERROR, 31 + THERMOMETER_TIME_ERROR, 32 + THERMOMETER_INIT_ERROR, 33 + THERMOMETER_RUNTIME_ERROR 34 + }; 35 + 36 + struct options { 37 + int loglvl; 38 + int logopt; 39 + int overwrite; 40 + int duration; 41 + const char *config; 42 + char postfix[PATH_MAX]; 43 + char output[PATH_MAX]; 44 + }; 45 + 46 + struct tz_regex { 47 + regex_t regex; 48 + int polling; 49 + }; 50 + 51 + struct configuration { 52 + struct tz_regex *tz_regex; 53 + int nr_tz_regex; 54 + 55 + }; 56 + 57 + struct tz { 58 + FILE *file_out; 59 + int fd_temp; 60 + int fd_timer; 61 + int polling; 62 + const char *name; 63 + }; 64 + 65 + struct thermometer { 66 + struct tz *tz; 67 + int nr_tz; 68 + }; 69 + 70 + static struct tz_regex *configuration_tz_match(const char *expr, 71 + struct configuration *config) 72 + { 73 + int i; 74 + 75 + for (i = 0; i < config->nr_tz_regex; i++) { 76 + 77 + if (!regexec(&config->tz_regex[i].regex, expr, 0, NULL, 0)) 78 + return &config->tz_regex[i]; 79 + } 80 + 81 + return NULL; 82 + } 83 + 84 + static int configuration_default_init(struct configuration *config) 85 + { 86 + config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) * 87 + (config->nr_tz_regex + 1)); 88 + 89 + if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, ".*", 90 + REG_NOSUB | REG_EXTENDED)) { 91 + ERROR("Invalid regular expression\n"); 92 + return -1; 93 + } 94 + 95 + config->tz_regex[config->nr_tz_regex].polling = 250; 96 + config->nr_tz_regex = 1; 97 + 98 + return 0; 99 + } 100 + 101 + static int configuration_init(const char *path, struct configuration *config) 102 + { 103 + config_t cfg; 104 + 105 + config_setting_t *tz; 106 + int i, length; 107 + 108 + if (path && access(path, F_OK)) { 109 + ERROR("'%s' is not accessible\n", path); 110 + return -1; 111 + } 112 + 113 + if (!path && !config->nr_tz_regex) { 114 + INFO("No thermal zones configured, using wildcard for all of them\n"); 115 + return configuration_default_init(config); 116 + } 117 + 118 + config_init(&cfg); 119 + 120 + if (!config_read_file(&cfg, path)) { 121 + ERROR("Failed to parse %s:%d - %s\n", config_error_file(&cfg), 122 + config_error_line(&cfg), config_error_text(&cfg)); 123 + 124 + return -1; 125 + } 126 + 127 + tz = config_lookup(&cfg, "thermal-zones"); 128 + if (!tz) { 129 + ERROR("No thermal zone configured to be monitored\n"); 130 + return -1; 131 + } 132 + 133 + length = config_setting_length(tz); 134 + 135 + INFO("Found %d thermal zone(s) regular expression\n", length); 136 + 137 + for (i = 0; i < length; i++) { 138 + 139 + config_setting_t *node; 140 + const char *name; 141 + int polling; 142 + 143 + node = config_setting_get_elem(tz, i); 144 + if (!node) { 145 + ERROR("Missing node name '%d'\n", i); 146 + return -1; 147 + } 148 + 149 + if (!config_setting_lookup_string(node, "name", &name)) { 150 + ERROR("Thermal zone name not found\n"); 151 + return -1; 152 + } 153 + 154 + if (!config_setting_lookup_int(node, "polling", &polling)) { 155 + ERROR("Polling value not found"); 156 + return -1; 157 + } 158 + 159 + config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) * 160 + (config->nr_tz_regex + 1)); 161 + 162 + if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, name, 163 + REG_NOSUB | REG_EXTENDED)) { 164 + ERROR("Invalid regular expression '%s'\n", name); 165 + continue; 166 + } 167 + 168 + config->tz_regex[config->nr_tz_regex].polling = polling; 169 + config->nr_tz_regex++; 170 + 171 + INFO("Thermal zone regular expression '%s' with polling %d\n", 172 + name, polling); 173 + } 174 + 175 + return 0; 176 + } 177 + 178 + static void usage(const char *cmd) 179 + { 180 + printf("%s Version: %s\n", cmd, VERSION); 181 + printf("Usage: %s [options]\n", cmd); 182 + printf("\t-h, --help\t\tthis help\n"); 183 + printf("\t-o, --output <dir>\toutput directory for temperature capture\n"); 184 + printf("\t-c, --config <file>\tconfiguration file\n"); 185 + printf("\t-d, --duration <seconds>\tcapture duration\n"); 186 + printf("\t-l, --loglevel <level>\tlog level: "); 187 + printf("DEBUG, INFO, NOTICE, WARN, ERROR\n"); 188 + printf("\t-p, --postfix <string>\tpostfix to be happened at the end of the files\n"); 189 + printf("\t-s, --syslog\t\toutput to syslog\n"); 190 + printf("\t-w, --overwrite\t\toverwrite the temperature capture files if they exist\n"); 191 + printf("\n"); 192 + exit(0); 193 + } 194 + 195 + static int options_init(int argc, char *argv[], struct options *options) 196 + { 197 + int opt; 198 + time_t now = time(NULL); 199 + 200 + struct option long_options[] = { 201 + { "help", no_argument, NULL, 'h' }, 202 + { "config", required_argument, NULL, 'c' }, 203 + { "duration", required_argument, NULL, 'd' }, 204 + { "loglevel", required_argument, NULL, 'l' }, 205 + { "postfix", required_argument, NULL, 'p' }, 206 + { "output", required_argument, NULL, 'o' }, 207 + { "syslog", required_argument, NULL, 's' }, 208 + { "overwrite", no_argument, NULL, 'w' }, 209 + { 0, 0, 0, 0 } 210 + }; 211 + 212 + strftime(options->postfix, sizeof(options->postfix), 213 + "-%Y-%m-%d_%H:%M:%S", gmtime(&now)); 214 + 215 + while (1) { 216 + 217 + int optindex = 0; 218 + 219 + opt = getopt_long(argc, argv, "ho:c:d:l:p:sw", long_options, &optindex); 220 + if (opt == -1) 221 + break; 222 + 223 + switch (opt) { 224 + case 'c': 225 + options->config = optarg; 226 + break; 227 + case 'd': 228 + options->duration = atoi(optarg) * 1000; 229 + break; 230 + case 'l': 231 + options->loglvl = log_str2level(optarg); 232 + break; 233 + case 'h': 234 + usage(basename(argv[0])); 235 + break; 236 + case 'p': 237 + strcpy(options->postfix, optarg); 238 + break; 239 + case 'o': 240 + strcpy(options->output, optarg); 241 + break; 242 + case 's': 243 + options->logopt = TO_SYSLOG; 244 + break; 245 + case 'w': 246 + options->overwrite = 1; 247 + break; 248 + default: /* '?' */ 249 + ERROR("Usage: %s --help\n", argv[0]); 250 + return -1; 251 + } 252 + } 253 + 254 + return 0; 255 + } 256 + 257 + static int thermometer_add_tz(const char *path, const char *name, int polling, 258 + struct thermometer *thermometer) 259 + { 260 + int fd; 261 + char tz_path[PATH_MAX]; 262 + 263 + sprintf(tz_path, CLASS_THERMAL"/%s/temp", path); 264 + 265 + fd = open(tz_path, O_RDONLY); 266 + if (fd < 0) { 267 + ERROR("Failed to open '%s': %m\n", tz_path); 268 + return -1; 269 + } 270 + 271 + thermometer->tz = realloc(thermometer->tz, 272 + sizeof(*thermometer->tz) * (thermometer->nr_tz + 1)); 273 + if (!thermometer->tz) { 274 + ERROR("Failed to allocate thermometer->tz\n"); 275 + return -1; 276 + } 277 + 278 + thermometer->tz[thermometer->nr_tz].fd_temp = fd; 279 + thermometer->tz[thermometer->nr_tz].name = strdup(name); 280 + thermometer->tz[thermometer->nr_tz].polling = polling; 281 + thermometer->nr_tz++; 282 + 283 + INFO("Added thermal zone '%s->%s (polling:%d)'\n", path, name, polling); 284 + 285 + return 0; 286 + } 287 + 288 + static int thermometer_init(struct configuration *config, 289 + struct thermometer *thermometer) 290 + { 291 + DIR *dir; 292 + struct dirent *dirent; 293 + struct tz_regex *tz_regex; 294 + const char *tz_dirname = "thermal_zone"; 295 + 296 + if (mainloop_init()) { 297 + ERROR("Failed to start mainloop\n"); 298 + return -1; 299 + } 300 + 301 + dir = opendir(CLASS_THERMAL); 302 + if (!dir) { 303 + ERROR("failed to open '%s'\n", CLASS_THERMAL); 304 + return -1; 305 + } 306 + 307 + while ((dirent = readdir(dir))) { 308 + char tz_type[THERMAL_NAME_LENGTH]; 309 + char tz_path[PATH_MAX]; 310 + FILE *tz_file; 311 + 312 + if (strncmp(dirent->d_name, tz_dirname, strlen(tz_dirname))) 313 + continue; 314 + 315 + sprintf(tz_path, CLASS_THERMAL"/%s/type", dirent->d_name); 316 + 317 + tz_file = fopen(tz_path, "r"); 318 + if (!tz_file) { 319 + ERROR("Failed to open '%s': %m", tz_path); 320 + continue; 321 + } 322 + 323 + fscanf(tz_file, "%s", tz_type); 324 + 325 + fclose(tz_file); 326 + 327 + tz_regex = configuration_tz_match(tz_type, config); 328 + if (!tz_regex) 329 + continue; 330 + 331 + if (thermometer_add_tz(dirent->d_name, tz_type, 332 + tz_regex->polling, thermometer)) 333 + continue; 334 + } 335 + 336 + closedir(dir); 337 + 338 + return 0; 339 + } 340 + 341 + static int timer_temperature_callback(int fd, void *arg) 342 + { 343 + struct tz *tz = arg; 344 + char buf[16] = { 0 }; 345 + 346 + pread(tz->fd_temp, buf, sizeof(buf), 0); 347 + 348 + fprintf(tz->file_out, "%ld %s", getuptimeofday_ms(), buf); 349 + 350 + read(fd, buf, sizeof(buf)); 351 + 352 + return 0; 353 + } 354 + 355 + static int thermometer_start(struct thermometer *thermometer, 356 + struct options *options) 357 + { 358 + struct itimerspec timer_it = { 0 }; 359 + char *path; 360 + FILE *f; 361 + int i; 362 + 363 + INFO("Capturing %d thermal zone(s) temperature...\n", thermometer->nr_tz); 364 + 365 + if (access(options->output, F_OK) && mkdir(options->output, 0700)) { 366 + ERROR("Failed to create directory '%s'\n", options->output); 367 + return -1; 368 + } 369 + 370 + for (i = 0; i < thermometer->nr_tz; i++) { 371 + 372 + asprintf(&path, "%s/%s%s", options->output, 373 + thermometer->tz[i].name, options->postfix); 374 + 375 + if (!options->overwrite && !access(path, F_OK)) { 376 + ERROR("'%s' already exists\n", path); 377 + return -1; 378 + } 379 + 380 + f = fopen(path, "w"); 381 + if (!f) { 382 + ERROR("Failed to create '%s':%m\n", path); 383 + return -1; 384 + } 385 + 386 + fprintf(f, "timestamp(ms) %s(°mC)\n", thermometer->tz[i].name); 387 + 388 + thermometer->tz[i].file_out = f; 389 + 390 + DEBUG("Created '%s' file for thermal zone '%s'\n", path, thermometer->tz[i].name); 391 + 392 + /* 393 + * Create polling timer 394 + */ 395 + thermometer->tz[i].fd_timer = timerfd_create(CLOCK_MONOTONIC, 0); 396 + if (thermometer->tz[i].fd_timer < 0) { 397 + ERROR("Failed to create timer for '%s': %m\n", 398 + thermometer->tz[i].name); 399 + return -1; 400 + } 401 + 402 + DEBUG("Watching '%s' every %d ms\n", 403 + thermometer->tz[i].name, thermometer->tz[i].polling); 404 + 405 + timer_it.it_interval = timer_it.it_value = 406 + msec_to_timespec(thermometer->tz[i].polling); 407 + 408 + if (timerfd_settime(thermometer->tz[i].fd_timer, 0, 409 + &timer_it, NULL) < 0) 410 + return -1; 411 + 412 + if (mainloop_add(thermometer->tz[i].fd_timer, 413 + timer_temperature_callback, 414 + &thermometer->tz[i])) 415 + return -1; 416 + } 417 + 418 + return 0; 419 + } 420 + 421 + static int thermometer_execute(int argc, char *argv[], char *const envp[], pid_t *pid) 422 + { 423 + if (!argc) 424 + return 0; 425 + 426 + *pid = fork(); 427 + if (*pid < 0) { 428 + ERROR("Failed to fork process: %m"); 429 + return -1; 430 + } 431 + 432 + if (!(*pid)) { 433 + execvpe(argv[0], argv, envp); 434 + exit(1); 435 + } 436 + 437 + return 0; 438 + } 439 + 440 + static int kill_process(__maybe_unused int fd, void *arg) 441 + { 442 + pid_t pid = *(pid_t *)arg; 443 + 444 + if (kill(pid, SIGTERM)) 445 + ERROR("Failed to send SIGTERM signal to '%d': %p\n", pid); 446 + else if (waitpid(pid, NULL, 0)) 447 + ERROR("Failed to wait pid '%d': %p\n", pid); 448 + 449 + mainloop_exit(); 450 + 451 + return 0; 452 + } 453 + 454 + static int exit_mainloop(__maybe_unused int fd, __maybe_unused void *arg) 455 + { 456 + mainloop_exit(); 457 + return 0; 458 + } 459 + 460 + static int thermometer_wait(struct options *options, pid_t pid) 461 + { 462 + int fd; 463 + sigset_t mask; 464 + 465 + /* 466 + * If there is a duration specified, we will exit the mainloop 467 + * and gracefully close all the files which will flush the 468 + * file system cache 469 + */ 470 + if (options->duration) { 471 + struct itimerspec timer_it = { 0 }; 472 + 473 + timer_it.it_value = msec_to_timespec(options->duration); 474 + 475 + fd = timerfd_create(CLOCK_MONOTONIC, 0); 476 + if (fd < 0) { 477 + ERROR("Failed to create duration timer: %m\n"); 478 + return -1; 479 + } 480 + 481 + if (timerfd_settime(fd, 0, &timer_it, NULL)) { 482 + ERROR("Failed to set timer time: %m\n"); 483 + return -1; 484 + } 485 + 486 + if (mainloop_add(fd, pid < 0 ? exit_mainloop : kill_process, &pid)) { 487 + ERROR("Failed to set timer exit mainloop callback\n"); 488 + return -1; 489 + } 490 + } 491 + 492 + /* 493 + * We want to catch any keyboard interrupt, as well as child 494 + * signals if any in order to exit properly 495 + */ 496 + sigemptyset(&mask); 497 + sigaddset(&mask, SIGINT); 498 + sigaddset(&mask, SIGQUIT); 499 + sigaddset(&mask, SIGCHLD); 500 + 501 + if (sigprocmask(SIG_BLOCK, &mask, NULL)) { 502 + ERROR("Failed to set sigprocmask: %m\n"); 503 + return -1; 504 + } 505 + 506 + fd = signalfd(-1, &mask, 0); 507 + if (fd < 0) { 508 + ERROR("Failed to set the signalfd: %m\n"); 509 + return -1; 510 + } 511 + 512 + if (mainloop_add(fd, exit_mainloop, NULL)) { 513 + ERROR("Failed to set timer exit mainloop callback\n"); 514 + return -1; 515 + } 516 + 517 + return mainloop(-1); 518 + } 519 + 520 + static int thermometer_stop(struct thermometer *thermometer) 521 + { 522 + int i; 523 + 524 + INFO("Closing/flushing output files\n"); 525 + 526 + for (i = 0; i < thermometer->nr_tz; i++) 527 + fclose(thermometer->tz[i].file_out); 528 + 529 + return 0; 530 + } 531 + 532 + int main(int argc, char *argv[], char *const envp[]) 533 + { 534 + struct options options = { 535 + .loglvl = LOG_DEBUG, 536 + .logopt = TO_STDOUT, 537 + .output = ".", 538 + }; 539 + struct configuration config = { 0 }; 540 + struct thermometer thermometer = { 0 }; 541 + 542 + pid_t pid = -1; 543 + 544 + if (options_init(argc, argv, &options)) 545 + return THERMOMETER_OPTION_ERROR; 546 + 547 + if (log_init(options.loglvl, argv[0], options.logopt)) 548 + return THERMOMETER_LOG_ERROR; 549 + 550 + if (configuration_init(options.config, &config)) 551 + return THERMOMETER_CONFIG_ERROR; 552 + 553 + if (uptimeofday_init()) 554 + return THERMOMETER_TIME_ERROR; 555 + 556 + if (thermometer_init(&config, &thermometer)) 557 + return THERMOMETER_INIT_ERROR; 558 + 559 + if (thermometer_start(&thermometer, &options)) 560 + return THERMOMETER_RUNTIME_ERROR; 561 + 562 + if (thermometer_execute(argc - optind, &argv[optind], envp, &pid)) 563 + return THERMOMETER_RUNTIME_ERROR; 564 + 565 + if (thermometer_wait(&options, pid)) 566 + return THERMOMETER_RUNTIME_ERROR; 567 + 568 + if (thermometer_stop(&thermometer)) 569 + return THERMOMETER_RUNTIME_ERROR; 570 + 571 + return THERMOMETER_SUCCESS; 572 + }
+5
tools/thermal/thermometer/thermometer.conf
··· 1 + 2 + thermal-zones = ( 3 + { name = "cpu[0-9]-thermal"; 4 + polling = 100; } 5 + )