From b474303ffd57e0a379ce73ca10232350f866f77b Mon Sep 17 00:00:00 2001 From: Bin Gao Date: Mon, 29 Aug 2016 09:55:04 -0700 Subject: thermal: add Intel BXT WhiskeyCove PMIC thermal driver This change adds support for Intel BXT Whiskey Cove PMIC thermal driver which is intended to handle the alert interrupts triggered upon thermal trip point cross and notify the thermal framework appropriately with the zone, temp, crossed trip and event details. Signed-off-by: Yegnesh S Iyer Signed-off-by: Bin Gao Signed-off-by: Zhang Rui diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 2d702ca..9bdd624 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -332,6 +332,16 @@ menu "ACPI INT340X thermal drivers" source drivers/thermal/int340x_thermal/Kconfig endmenu +config INTEL_BXT_PMIC_THERMAL + tristate "Intel Broxton PMIC thermal driver" + depends on X86 && INTEL_SOC_PMIC && REGMAP + help + Select this driver for Intel Broxton PMIC with ADC channels monitoring + system temperature measurements and alerts. + This driver is used for monitoring the ADC channels of PMIC and handles + the alert trip point interrupts and notifies the thermal framework with + the trip point and temperature details of the zone. + config INTEL_PCH_THERMAL tristate "Intel PCH Thermal Reporting Driver" depends on X86 && PCI diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 10b07c1..7aa7f4c 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL) += intel_quark_dts_thermal.o obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ +obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o obj-$(CONFIG_ST_THERMAL) += st/ obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/ diff --git a/drivers/thermal/intel_bxt_pmic_thermal.c b/drivers/thermal/intel_bxt_pmic_thermal.c new file mode 100644 index 0000000..4ae3e0c --- /dev/null +++ b/drivers/thermal/intel_bxt_pmic_thermal.c @@ -0,0 +1,299 @@ +/* + * Intel Broxton PMIC thermal driver + * + * Copyright (C) 2016 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BXTWC_THRM0IRQ 0x4E04 +#define BXTWC_THRM1IRQ 0x4E05 +#define BXTWC_THRM2IRQ 0x4E06 +#define BXTWC_MTHRM0IRQ 0x4E12 +#define BXTWC_MTHRM1IRQ 0x4E13 +#define BXTWC_MTHRM2IRQ 0x4E14 +#define BXTWC_STHRM0IRQ 0x4F19 +#define BXTWC_STHRM1IRQ 0x4F1A +#define BXTWC_STHRM2IRQ 0x4F1B + +struct trip_config_map { + u16 irq_reg; + u16 irq_en; + u16 evt_stat; + u8 irq_mask; + u8 irq_en_mask; + u8 evt_mask; + u8 trip_num; +}; + +struct thermal_irq_map { + char handle[20]; + int num_trips; + const struct trip_config_map *trip_config; +}; + +struct pmic_thermal_data { + const struct thermal_irq_map *maps; + int num_maps; +}; + +static const struct trip_config_map bxtwc_str0_trip_config[] = { + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x01, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x01, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x01, + .trip_num = 0 + }, + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x10, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x10, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x10, + .trip_num = 1 + } +}; + +static const struct trip_config_map bxtwc_str1_trip_config[] = { + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x02, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x02, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x02, + .trip_num = 0 + }, + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x20, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x20, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x20, + .trip_num = 1 + }, +}; + +static const struct trip_config_map bxtwc_str2_trip_config[] = { + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x04, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x04, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x04, + .trip_num = 0 + }, + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x40, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x40, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x40, + .trip_num = 1 + }, +}; + +static const struct trip_config_map bxtwc_str3_trip_config[] = { + { + .irq_reg = BXTWC_THRM2IRQ, + .irq_mask = 0x10, + .irq_en = BXTWC_MTHRM2IRQ, + .irq_en_mask = 0x10, + .evt_stat = BXTWC_STHRM2IRQ, + .evt_mask = 0x10, + .trip_num = 0 + }, +}; + +static const struct thermal_irq_map bxtwc_thermal_irq_map[] = { + { + .handle = "STR0", + .trip_config = bxtwc_str0_trip_config, + .num_trips = ARRAY_SIZE(bxtwc_str0_trip_config), + }, + { + .handle = "STR1", + .trip_config = bxtwc_str1_trip_config, + .num_trips = ARRAY_SIZE(bxtwc_str1_trip_config), + }, + { + .handle = "STR2", + .trip_config = bxtwc_str2_trip_config, + .num_trips = ARRAY_SIZE(bxtwc_str2_trip_config), + }, + { + .handle = "STR3", + .trip_config = bxtwc_str3_trip_config, + .num_trips = ARRAY_SIZE(bxtwc_str3_trip_config), + }, +}; + +static const struct pmic_thermal_data bxtwc_thermal_data = { + .maps = bxtwc_thermal_irq_map, + .num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map), +}; + +static irqreturn_t pmic_thermal_irq_handler(int irq, void *data) +{ + struct platform_device *pdev = data; + struct thermal_zone_device *tzd; + struct pmic_thermal_data *td; + struct intel_soc_pmic *pmic; + struct regmap *regmap; + u8 reg_val, mask, irq_stat, trip; + u16 reg, evt_stat_reg; + int i, j, ret; + + pmic = dev_get_drvdata(pdev->dev.parent); + regmap = pmic->regmap; + td = (struct pmic_thermal_data *) + platform_get_device_id(pdev)->driver_data; + + /* Resolve thermal irqs */ + for (i = 0; i < td->num_maps; i++) { + for (j = 0; j < td->maps[i].num_trips; j++) { + reg = td->maps[i].trip_config[j].irq_reg; + mask = td->maps[i].trip_config[j].irq_mask; + /* + * Read the irq register to resolve whether the + * interrupt was triggered for this sensor + */ + if (regmap_read(regmap, reg, &ret)) + return IRQ_HANDLED; + + reg_val = (u8)ret; + irq_stat = ((u8)ret & mask); + + if (!irq_stat) + continue; + + /* + * Read the status register to find out what + * event occurred i.e a high or a low + */ + evt_stat_reg = td->maps[i].trip_config[j].evt_stat; + if (regmap_read(regmap, evt_stat_reg, &ret)) + return IRQ_HANDLED; + + trip = td->maps[i].trip_config[j].trip_num; + tzd = thermal_zone_get_zone_by_name(td->maps[i].handle); + if (!IS_ERR(tzd)) + thermal_zone_device_update(tzd); + + /* Clear the appropriate irq */ + regmap_write(regmap, reg, reg_val & mask); + } + } + + return IRQ_HANDLED; +} + +static int pmic_thermal_probe(struct platform_device *pdev) +{ + struct regmap_irq_chip_data *regmap_irq_chip; + struct pmic_thermal_data *thermal_data; + int ret, irq, virq, i, j, pmic_irq_count; + struct intel_soc_pmic *pmic; + struct regmap *regmap; + struct device *dev; + u16 reg; + u8 mask; + + dev = &pdev->dev; + pmic = dev_get_drvdata(pdev->dev.parent); + if (!pmic) { + dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n"); + return -ENODEV; + } + + thermal_data = (struct pmic_thermal_data *) + platform_get_device_id(pdev)->driver_data; + if (!thermal_data) { + dev_err(dev, "No thermal data initialized!!\n"); + return -ENODEV; + } + + regmap = pmic->regmap; + regmap_irq_chip = pmic->irq_chip_data_level2; + + pmic_irq_count = 0; + while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) { + virq = regmap_irq_get_virq(regmap_irq_chip, irq); + if (virq < 0) { + dev_err(dev, "failed to get virq by irq %d\n", irq); + return virq; + } + + ret = devm_request_threaded_irq(&pdev->dev, virq, + NULL, pmic_thermal_irq_handler, + IRQF_ONESHOT, "pmic_thermal", pdev); + + if (ret) { + dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); + return ret; + } + pmic_irq_count++; + } + + /* Enable thermal interrupts */ + for (i = 0; i < thermal_data->num_maps; i++) { + for (j = 0; j < thermal_data->maps[i].num_trips; j++) { + reg = thermal_data->maps[i].trip_config[j].irq_en; + mask = thermal_data->maps[i].trip_config[j].irq_en_mask; + ret = regmap_update_bits(regmap, reg, mask, 0x00); + if (ret) + return ret; + } + } + + return 0; +} + +static const struct platform_device_id pmic_thermal_id_table[] = { + { + .name = "bxt_wcove_thermal", + .driver_data = (kernel_ulong_t)&bxtwc_thermal_data, + }, + {}, +}; + +static struct platform_driver pmic_thermal_driver = { + .probe = pmic_thermal_probe, + .driver = { + .name = "pmic_thermal", + }, + .id_table = pmic_thermal_id_table, +}; + +MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table); +module_platform_driver(pmic_thermal_driver); + +MODULE_AUTHOR("Yegnesh S Iyer "); +MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From 0744f1306f6b0ffc2c6bd68c1edf688b74fa8779 Mon Sep 17 00:00:00 2001 From: Hugh Kang Date: Wed, 7 Sep 2016 09:35:39 +0900 Subject: thermal: cpu_cooling: Fix wrong comment call function name The last_load is updated not cpufreq_get_actual_power() function call but cpufreq_get_requested_power() function call. Signed-off-by: Inhyuk Kang Acked-by: Viresh Kumar Acked-by: Javi Merino Acked-by: Javi Merino Signed-off-by: Zhang Rui diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index a32b417..9ce0e9e 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -74,7 +74,7 @@ struct power_table { * cpufreq frequencies. * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device. * @node: list_head to link all cpufreq_cooling_device together. - * @last_load: load measured by the latest call to cpufreq_get_actual_power() + * @last_load: load measured by the latest call to cpufreq_get_requested_power() * @time_in_idle: previous reading of the absolute time that this cpu was idle * @time_in_idle_timestamp: wall time of the last invocation of * get_cpu_idle_time_us() -- cgit v0.10.2 From 9066073c6c27994a30187abf3b674770b4088348 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 5 May 2016 14:21:39 +0530 Subject: thermal: qcom: tsens: Add a skeletal TSENS drivers TSENS is Qualcomms' thermal temperature sensor device. It supports reading temperatures from multiple thermal sensors present on various QCOM SoCs. Calibration data is generally read from a non-volatile memory (eeprom) device. Add a skeleton driver with all the necessary abstractions so a variety of qcom device families which support TSENS can add driver extensions. Also add the required device tree bindings which can be used to describe the TSENS device in DT. Signed-off-by: Rajendra Nayak Reviewed-by: Lina Iyer Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.txt b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt new file mode 100644 index 0000000..292ed89 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt @@ -0,0 +1,21 @@ +* QCOM SoC Temperature Sensor (TSENS) + +Required properties: +- compatible : + - "qcom,msm8916-tsens" : For 8916 Family of SoCs + - "qcom,msm8974-tsens" : For 8974 Family of SoCs + - "qcom,msm8996-tsens" : For 8996 Family of SoCs + +- reg: Address range of the thermal registers +- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description. +- Refer to Documentation/devicetree/bindings/nvmem/nvmem.txt to know how to specify +nvmem cells + +Example: +tsens: thermal-sensor@900000 { + compatible = "qcom,msm8916-tsens"; + reg = <0x4a8000 0x2000>; + nvmem-cells = <&tsens_caldata>, <&tsens_calsel>; + nvmem-cell-names = "caldata", "calsel"; + #thermal-sensor-cells = <1>; + }; diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 2d702ca..8303823 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -399,4 +399,9 @@ config GENERIC_ADC_THERMAL to this driver. This driver reports the temperature by reading ADC channel and converts it to temperature based on lookup table. +menu "Qualcomm thermal drivers" +depends on (ARCH_QCOM && OF) || COMPILE_TEST +source "drivers/thermal/qcom/Kconfig" +endmenu + endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 10b07c1..431771e 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o obj-$(CONFIG_ST_THERMAL) += st/ +obj-$(CONFIG_QCOM_TSENS) += qcom/ obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/ obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig new file mode 100644 index 0000000..be32e5a --- /dev/null +++ b/drivers/thermal/qcom/Kconfig @@ -0,0 +1,11 @@ +config QCOM_TSENS + tristate "Qualcomm TSENS Temperature Alarm" + depends on THERMAL + depends on QCOM_QFPROM + depends on ARCH_QCOM || COMPILE_TEST + help + This enables the thermal sysfs driver for the TSENS device. It shows + up in Sysfs as a thermal zone with multiple trip points. Disabling the + thermal zone device via the mode file results in disabling the sensor. + Also able to set threshold temperature for both hot and cold and update + when a threshold is reached. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile new file mode 100644 index 0000000..2e800c5 --- /dev/null +++ b/drivers/thermal/qcom/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o +qcom_tsens-y += tsens.o tsens-common.o diff --git a/drivers/thermal/qcom/tsens-common.c b/drivers/thermal/qcom/tsens-common.c new file mode 100644 index 0000000..4a1af15 --- /dev/null +++ b/drivers/thermal/qcom/tsens-common.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include "tsens.h" + +#define S0_ST_ADDR 0x1030 +#define SN_ADDR_OFFSET 0x4 +#define SN_ST_TEMP_MASK 0x3ff +#define CAL_DEGC_PT1 30 +#define CAL_DEGC_PT2 120 +#define SLOPE_FACTOR 1000 +#define SLOPE_DEFAULT 3200 + +char *qfprom_read(struct device *dev, const char *cname) +{ + struct nvmem_cell *cell; + ssize_t data; + char *ret; + + cell = nvmem_cell_get(dev, cname); + if (IS_ERR(cell)) + return ERR_CAST(cell); + + ret = nvmem_cell_read(cell, &data); + nvmem_cell_put(cell); + + return ret; +} + +/* + * Use this function on devices where slope and offset calculations + * depend on calibration data read from qfprom. On others the slope + * and offset values are derived from tz->tzp->slope and tz->tzp->offset + * resp. + */ +void compute_intercept_slope(struct tsens_device *tmdev, u32 *p1, + u32 *p2, u32 mode) +{ + int i; + int num, den; + + for (i = 0; i < tmdev->num_sensors; i++) { + dev_dbg(tmdev->dev, + "sensor%d - data_point1:%#x data_point2:%#x\n", + i, p1[i], p2[i]); + + tmdev->sensor[i].slope = SLOPE_DEFAULT; + if (mode == TWO_PT_CALIB) { + /* + * slope (m) = adc_code2 - adc_code1 (y2 - y1)/ + * temp_120_degc - temp_30_degc (x2 - x1) + */ + num = p2[i] - p1[i]; + num *= SLOPE_FACTOR; + den = CAL_DEGC_PT2 - CAL_DEGC_PT1; + tmdev->sensor[i].slope = num / den; + } + + tmdev->sensor[i].offset = (p1[i] * SLOPE_FACTOR) - + (CAL_DEGC_PT1 * + tmdev->sensor[i].slope); + dev_dbg(tmdev->dev, "offset:%d\n", tmdev->sensor[i].offset); + } +} + +static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) +{ + int degc, num, den; + + num = (adc_code * SLOPE_FACTOR) - s->offset; + den = s->slope; + + if (num > 0) + degc = num + (den / 2); + else if (num < 0) + degc = num - (den / 2); + else + degc = num; + + degc /= den; + + return degc; +} + +int get_temp_common(struct tsens_device *tmdev, int id, int *temp) +{ + struct tsens_sensor *s = &tmdev->sensor[id]; + u32 code; + unsigned int sensor_addr; + int last_temp = 0, ret; + + sensor_addr = S0_ST_ADDR + s->hw_id * SN_ADDR_OFFSET; + ret = regmap_read(tmdev->map, sensor_addr, &code); + if (ret) + return ret; + last_temp = code & SN_ST_TEMP_MASK; + + *temp = code_to_degc(last_temp, s) * 1000; + + return 0; +} + +static const struct regmap_config tsens_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +int __init init_common(struct tsens_device *tmdev) +{ + void __iomem *base; + + base = of_iomap(tmdev->dev->of_node, 0); + if (IS_ERR(base)) + return -EINVAL; + + tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config); + if (!tmdev->map) { + iounmap(base); + return -ENODEV; + } + + return 0; +} diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c new file mode 100644 index 0000000..17d7b55 --- /dev/null +++ b/drivers/thermal/qcom/tsens.c @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "tsens.h" + +static int tsens_get_temp(void *data, int *temp) +{ + const struct tsens_sensor *s = data; + struct tsens_device *tmdev = s->tmdev; + + return tmdev->ops->get_temp(tmdev, s->id, temp); +} + +static int tsens_get_trend(void *data, long *temp) +{ + const struct tsens_sensor *s = data; + struct tsens_device *tmdev = s->tmdev; + + if (tmdev->ops->get_trend) + return tmdev->ops->get_trend(tmdev, s->id, temp); + + return -ENOTSUPP; +} + +static int tsens_suspend(struct device *dev) +{ + struct tsens_device *tmdev = dev_get_drvdata(dev); + + if (tmdev->ops && tmdev->ops->suspend) + return tmdev->ops->suspend(tmdev); + + return 0; +} + +static int tsens_resume(struct device *dev) +{ + struct tsens_device *tmdev = dev_get_drvdata(dev); + + if (tmdev->ops && tmdev->ops->resume) + return tmdev->ops->resume(tmdev); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); + +static const struct of_device_id tsens_table[] = { + { + .compatible = "qcom,msm8916-tsens", + }, { + .compatible = "qcom,msm8974-tsens", + }, + {} +}; +MODULE_DEVICE_TABLE(of, tsens_table); + +static const struct thermal_zone_of_device_ops tsens_of_ops = { + .get_temp = tsens_get_temp, + .get_trend = tsens_get_trend, +}; + +static int tsens_register(struct tsens_device *tmdev) +{ + int i; + struct thermal_zone_device *tzd; + u32 *hw_id, n = tmdev->num_sensors; + + hw_id = devm_kcalloc(tmdev->dev, n, sizeof(u32), GFP_KERNEL); + if (!hw_id) + return -ENOMEM; + + for (i = 0; i < tmdev->num_sensors; i++) { + tmdev->sensor[i].tmdev = tmdev; + tmdev->sensor[i].id = i; + tzd = devm_thermal_zone_of_sensor_register(tmdev->dev, i, + &tmdev->sensor[i], + &tsens_of_ops); + if (IS_ERR(tzd)) + continue; + tmdev->sensor[i].tzd = tzd; + if (tmdev->ops->enable) + tmdev->ops->enable(tmdev, i); + } + return 0; +} + +static int tsens_probe(struct platform_device *pdev) +{ + int ret, i; + struct device *dev; + struct device_node *np; + struct tsens_sensor *s; + struct tsens_device *tmdev; + const struct tsens_data *data; + const struct of_device_id *id; + + if (pdev->dev.of_node) + dev = &pdev->dev; + else + dev = pdev->dev.parent; + + np = dev->of_node; + + id = of_match_node(tsens_table, np); + if (!id) + return -EINVAL; + + data = id->data; + + if (data->num_sensors <= 0) { + dev_err(dev, "invalid number of sensors\n"); + return -EINVAL; + } + + tmdev = devm_kzalloc(dev, sizeof(*tmdev) + + data->num_sensors * sizeof(*s), GFP_KERNEL); + if (!tmdev) + return -ENOMEM; + + tmdev->dev = dev; + tmdev->num_sensors = data->num_sensors; + tmdev->ops = data->ops; + for (i = 0; i < tmdev->num_sensors; i++) { + if (data->hw_ids) + tmdev->sensor[i].hw_id = data->hw_ids[i]; + else + tmdev->sensor[i].hw_id = i; + } + + if (!tmdev->ops || !tmdev->ops->init || !tmdev->ops->get_temp) + return -EINVAL; + + ret = tmdev->ops->init(tmdev); + if (ret < 0) { + dev_err(dev, "tsens init failed\n"); + return ret; + } + + if (tmdev->ops->calibrate) { + ret = tmdev->ops->calibrate(tmdev); + if (ret < 0) { + dev_err(dev, "tsens calibration failed\n"); + return ret; + } + } + + ret = tsens_register(tmdev); + + platform_set_drvdata(pdev, tmdev); + + return ret; +} + +static int tsens_remove(struct platform_device *pdev) +{ + struct tsens_device *tmdev = platform_get_drvdata(pdev); + + if (tmdev->ops->disable) + tmdev->ops->disable(tmdev); + + return 0; +} + +static struct platform_driver tsens_driver = { + .probe = tsens_probe, + .remove = tsens_remove, + .driver = { + .name = "qcom-tsens", + .pm = &tsens_pm_ops, + .of_match_table = tsens_table, + }, +}; +module_platform_driver(tsens_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("QCOM Temperature Sensor driver"); +MODULE_ALIAS("platform:qcom-tsens"); diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h new file mode 100644 index 0000000..3b1b6ae --- /dev/null +++ b/drivers/thermal/qcom/tsens.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __QCOM_TSENS_H__ +#define __QCOM_TSENS_H__ + +#define ONE_PT_CALIB 0x1 +#define ONE_PT_CALIB2 0x2 +#define TWO_PT_CALIB 0x3 + +struct tsens_device; + +struct tsens_sensor { + struct tsens_device *tmdev; + struct thermal_zone_device *tzd; + int offset; + int id; + int hw_id; + int slope; + u32 status; +}; + +/** + * struct tsens_ops - operations as supported by the tsens device + * @init: Function to initialize the tsens device + * @calibrate: Function to calibrate the tsens device + * @get_temp: Function which returns the temp in millidegC + * @enable: Function to enable (clocks/power) tsens device + * @disable: Function to disable the tsens device + * @suspend: Function to suspend the tsens device + * @resume: Function to resume the tsens device + * @get_trend: Function to get the thermal/temp trend + */ +struct tsens_ops { + /* mandatory callbacks */ + int (*init)(struct tsens_device *); + int (*calibrate)(struct tsens_device *); + int (*get_temp)(struct tsens_device *, int, int *); + /* optional callbacks */ + int (*enable)(struct tsens_device *, int); + void (*disable)(struct tsens_device *); + int (*suspend)(struct tsens_device *); + int (*resume)(struct tsens_device *); + int (*get_trend)(struct tsens_device *, int, long *); +}; + +/** + * struct tsens_data - tsens instance specific data + * @num_sensors: Max number of sensors supported by platform + * @ops: operations the tsens instance supports + * @hw_ids: Subset of sensors ids supported by platform, if not the first n + */ +struct tsens_data { + const u32 num_sensors; + const struct tsens_ops *ops; + unsigned int *hw_ids; +}; + +/* Registers to be saved/restored across a context loss */ +struct tsens_context { + int threshold; + int control; +}; + +struct tsens_device { + struct device *dev; + u32 num_sensors; + struct regmap *map; + struct regmap_field *status_field; + struct tsens_context ctx; + bool trdy; + const struct tsens_ops *ops; + struct tsens_sensor sensor[0]; +}; + +char *qfprom_read(struct device *, const char *); +void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32); +int init_common(struct tsens_device *); +int get_temp_common(struct tsens_device *, int, int *); + +#endif /* __QCOM_TSENS_H__ */ -- cgit v0.10.2 From 840a5bd3ed3fdd62456d4d26c3128ec10496555b Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 5 May 2016 14:21:40 +0530 Subject: thermal: qcom: tsens-8916: Add support for 8916 family of SoCs Add support to calibrate sensors on 8916 family and also add common functions to read temperature from sensors (This can be reused on other SoCs having similar TSENS device) The calibration data is read from eeprom using the generic nvmem framework apis. Based on the original code by Siddartha Mohanadoss and Stephen Boyd. Signed-off-by: Rajendra Nayak Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 2e800c5..05c98e4 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -1,2 +1,2 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o -qcom_tsens-y += tsens.o tsens-common.o +qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o diff --git a/drivers/thermal/qcom/tsens-8916.c b/drivers/thermal/qcom/tsens-8916.c new file mode 100644 index 0000000..beb3afa --- /dev/null +++ b/drivers/thermal/qcom/tsens-8916.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include "tsens.h" + +/* eeprom layout data for 8916 */ +#define BASE0_MASK 0x0000007f +#define BASE1_MASK 0xfe000000 +#define BASE0_SHIFT 0 +#define BASE1_SHIFT 25 + +#define S0_P1_MASK 0x00000f80 +#define S1_P1_MASK 0x003e0000 +#define S2_P1_MASK 0xf8000000 +#define S3_P1_MASK 0x000003e0 +#define S4_P1_MASK 0x000f8000 + +#define S0_P2_MASK 0x0001f000 +#define S1_P2_MASK 0x07c00000 +#define S2_P2_MASK 0x0000001f +#define S3_P2_MASK 0x00007c00 +#define S4_P2_MASK 0x01f00000 + +#define S0_P1_SHIFT 7 +#define S1_P1_SHIFT 17 +#define S2_P1_SHIFT 27 +#define S3_P1_SHIFT 5 +#define S4_P1_SHIFT 15 + +#define S0_P2_SHIFT 12 +#define S1_P2_SHIFT 22 +#define S2_P2_SHIFT 0 +#define S3_P2_SHIFT 10 +#define S4_P2_SHIFT 20 + +#define CAL_SEL_MASK 0xe0000000 +#define CAL_SEL_SHIFT 29 + +static int calibrate_8916(struct tsens_device *tmdev) +{ + int base0 = 0, base1 = 0, i; + u32 p1[5], p2[5]; + int mode = 0; + u32 *qfprom_cdata, *qfprom_csel; + + qfprom_cdata = (u32 *)qfprom_read(tmdev->dev, "calib"); + if (IS_ERR(qfprom_cdata)) + return PTR_ERR(qfprom_cdata); + + qfprom_csel = (u32 *)qfprom_read(tmdev->dev, "calib_sel"); + if (IS_ERR(qfprom_csel)) + return PTR_ERR(qfprom_csel); + + mode = (qfprom_csel[0] & CAL_SEL_MASK) >> CAL_SEL_SHIFT; + dev_dbg(tmdev->dev, "calibration mode is %d\n", mode); + + switch (mode) { + case TWO_PT_CALIB: + base1 = (qfprom_cdata[1] & BASE1_MASK) >> BASE1_SHIFT; + p2[0] = (qfprom_cdata[0] & S0_P2_MASK) >> S0_P2_SHIFT; + p2[1] = (qfprom_cdata[0] & S1_P2_MASK) >> S1_P2_SHIFT; + p2[2] = (qfprom_cdata[1] & S2_P2_MASK) >> S2_P2_SHIFT; + p2[3] = (qfprom_cdata[1] & S3_P2_MASK) >> S3_P2_SHIFT; + p2[4] = (qfprom_cdata[1] & S4_P2_MASK) >> S4_P2_SHIFT; + for (i = 0; i < tmdev->num_sensors; i++) + p2[i] = ((base1 + p2[i]) << 3); + /* Fall through */ + case ONE_PT_CALIB2: + base0 = (qfprom_cdata[0] & BASE0_MASK); + p1[0] = (qfprom_cdata[0] & S0_P1_MASK) >> S0_P1_SHIFT; + p1[1] = (qfprom_cdata[0] & S1_P1_MASK) >> S1_P1_SHIFT; + p1[2] = (qfprom_cdata[0] & S2_P1_MASK) >> S2_P1_SHIFT; + p1[3] = (qfprom_cdata[1] & S3_P1_MASK) >> S3_P1_SHIFT; + p1[4] = (qfprom_cdata[1] & S4_P1_MASK) >> S4_P1_SHIFT; + for (i = 0; i < tmdev->num_sensors; i++) + p1[i] = (((base0) + p1[i]) << 3); + break; + default: + for (i = 0; i < tmdev->num_sensors; i++) { + p1[i] = 500; + p2[i] = 780; + } + break; + } + + compute_intercept_slope(tmdev, p1, p2, mode); + + return 0; +} + +const struct tsens_ops ops_8916 = { + .init = init_common, + .calibrate = calibrate_8916, + .get_temp = get_temp_common, +}; + +const struct tsens_data data_8916 = { + .num_sensors = 5, + .ops = &ops_8916, + .hw_ids = (unsigned int []){0, 1, 2, 4, 5 }, +}; diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 17d7b55..7f3dfa1 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -65,6 +65,7 @@ static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); static const struct of_device_id tsens_table[] = { { .compatible = "qcom,msm8916-tsens", + .data = &data_8916, }, { .compatible = "qcom,msm8974-tsens", }, diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index 3b1b6ae..e767ad5 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -87,4 +87,6 @@ void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32); int init_common(struct tsens_device *); int get_temp_common(struct tsens_device *, int, int *); +extern const struct tsens_data data_8916; + #endif /* __QCOM_TSENS_H__ */ -- cgit v0.10.2 From 5e6703bd2d83548998848865cb9a9a795f31a311 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 5 May 2016 14:21:41 +0530 Subject: thermal: qcom: tsens-8974: Add support for 8974 family of SoCs Add .calibrate support for 8974 family as part of tsens_ops. Based on the original code by Siddartha Mohanadoss and Stephen Boyd. Signed-off-by: Rajendra Nayak Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 05c98e4..a471100 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -1,2 +1,2 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o -qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o +qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o diff --git a/drivers/thermal/qcom/tsens-8974.c b/drivers/thermal/qcom/tsens-8974.c new file mode 100644 index 0000000..da3ca4c --- /dev/null +++ b/drivers/thermal/qcom/tsens-8974.c @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include "tsens.h" + +/* eeprom layout data for 8974 */ +#define BASE1_MASK 0xff +#define S0_P1_MASK 0x3f00 +#define S1_P1_MASK 0xfc000 +#define S2_P1_MASK 0x3f00000 +#define S3_P1_MASK 0xfc000000 +#define S4_P1_MASK 0x3f +#define S5_P1_MASK 0xfc0 +#define S6_P1_MASK 0x3f000 +#define S7_P1_MASK 0xfc0000 +#define S8_P1_MASK 0x3f000000 +#define S8_P1_MASK_BKP 0x3f +#define S9_P1_MASK 0x3f +#define S9_P1_MASK_BKP 0xfc0 +#define S10_P1_MASK 0xfc0 +#define S10_P1_MASK_BKP 0x3f000 +#define CAL_SEL_0_1 0xc0000000 +#define CAL_SEL_2 0x40000000 +#define CAL_SEL_SHIFT 30 +#define CAL_SEL_SHIFT_2 28 + +#define S0_P1_SHIFT 8 +#define S1_P1_SHIFT 14 +#define S2_P1_SHIFT 20 +#define S3_P1_SHIFT 26 +#define S5_P1_SHIFT 6 +#define S6_P1_SHIFT 12 +#define S7_P1_SHIFT 18 +#define S8_P1_SHIFT 24 +#define S9_P1_BKP_SHIFT 6 +#define S10_P1_SHIFT 6 +#define S10_P1_BKP_SHIFT 12 + +#define BASE2_SHIFT 12 +#define BASE2_BKP_SHIFT 18 +#define S0_P2_SHIFT 20 +#define S0_P2_BKP_SHIFT 26 +#define S1_P2_SHIFT 26 +#define S2_P2_BKP_SHIFT 6 +#define S3_P2_SHIFT 6 +#define S3_P2_BKP_SHIFT 12 +#define S4_P2_SHIFT 12 +#define S4_P2_BKP_SHIFT 18 +#define S5_P2_SHIFT 18 +#define S5_P2_BKP_SHIFT 24 +#define S6_P2_SHIFT 24 +#define S7_P2_BKP_SHIFT 6 +#define S8_P2_SHIFT 6 +#define S8_P2_BKP_SHIFT 12 +#define S9_P2_SHIFT 12 +#define S9_P2_BKP_SHIFT 18 +#define S10_P2_SHIFT 18 +#define S10_P2_BKP_SHIFT 24 + +#define BASE2_MASK 0xff000 +#define BASE2_BKP_MASK 0xfc0000 +#define S0_P2_MASK 0x3f00000 +#define S0_P2_BKP_MASK 0xfc000000 +#define S1_P2_MASK 0xfc000000 +#define S1_P2_BKP_MASK 0x3f +#define S2_P2_MASK 0x3f +#define S2_P2_BKP_MASK 0xfc0 +#define S3_P2_MASK 0xfc0 +#define S3_P2_BKP_MASK 0x3f000 +#define S4_P2_MASK 0x3f000 +#define S4_P2_BKP_MASK 0xfc0000 +#define S5_P2_MASK 0xfc0000 +#define S5_P2_BKP_MASK 0x3f000000 +#define S6_P2_MASK 0x3f000000 +#define S6_P2_BKP_MASK 0x3f +#define S7_P2_MASK 0x3f +#define S7_P2_BKP_MASK 0xfc0 +#define S8_P2_MASK 0xfc0 +#define S8_P2_BKP_MASK 0x3f000 +#define S9_P2_MASK 0x3f000 +#define S9_P2_BKP_MASK 0xfc0000 +#define S10_P2_MASK 0xfc0000 +#define S10_P2_BKP_MASK 0x3f000000 + +#define BKP_SEL 0x3 +#define BKP_REDUN_SEL 0xe0000000 +#define BKP_REDUN_SHIFT 29 + +#define BIT_APPEND 0x3 + +static int calibrate_8974(struct tsens_device *tmdev) +{ + int base1 = 0, base2 = 0, i; + u32 p1[11], p2[11]; + int mode = 0; + u32 *calib, *bkp; + u32 calib_redun_sel; + + calib = (u32 *)qfprom_read(tmdev->dev, "calib"); + if (IS_ERR(calib)) + return PTR_ERR(calib); + + bkp = (u32 *)qfprom_read(tmdev->dev, "calib_backup"); + if (IS_ERR(bkp)) + return PTR_ERR(bkp); + + calib_redun_sel = bkp[1] & BKP_REDUN_SEL; + calib_redun_sel >>= BKP_REDUN_SHIFT; + + if (calib_redun_sel == BKP_SEL) { + mode = (calib[4] & CAL_SEL_0_1) >> CAL_SEL_SHIFT; + mode |= (calib[5] & CAL_SEL_2) >> CAL_SEL_SHIFT_2; + + switch (mode) { + case TWO_PT_CALIB: + base2 = (bkp[2] & BASE2_BKP_MASK) >> BASE2_BKP_SHIFT; + p2[0] = (bkp[2] & S0_P2_BKP_MASK) >> S0_P2_BKP_SHIFT; + p2[1] = (bkp[3] & S1_P2_BKP_MASK); + p2[2] = (bkp[3] & S2_P2_BKP_MASK) >> S2_P2_BKP_SHIFT; + p2[3] = (bkp[3] & S3_P2_BKP_MASK) >> S3_P2_BKP_SHIFT; + p2[4] = (bkp[3] & S4_P2_BKP_MASK) >> S4_P2_BKP_SHIFT; + p2[5] = (calib[4] & S5_P2_BKP_MASK) >> S5_P2_BKP_SHIFT; + p2[6] = (calib[5] & S6_P2_BKP_MASK); + p2[7] = (calib[5] & S7_P2_BKP_MASK) >> S7_P2_BKP_SHIFT; + p2[8] = (calib[5] & S8_P2_BKP_MASK) >> S8_P2_BKP_SHIFT; + p2[9] = (calib[5] & S9_P2_BKP_MASK) >> S9_P2_BKP_SHIFT; + p2[10] = (calib[5] & S10_P2_BKP_MASK) >> S10_P2_BKP_SHIFT; + /* Fall through */ + case ONE_PT_CALIB: + case ONE_PT_CALIB2: + base1 = bkp[0] & BASE1_MASK; + p1[0] = (bkp[0] & S0_P1_MASK) >> S0_P1_SHIFT; + p1[1] = (bkp[0] & S1_P1_MASK) >> S1_P1_SHIFT; + p1[2] = (bkp[0] & S2_P1_MASK) >> S2_P1_SHIFT; + p1[3] = (bkp[0] & S3_P1_MASK) >> S3_P1_SHIFT; + p1[4] = (bkp[1] & S4_P1_MASK); + p1[5] = (bkp[1] & S5_P1_MASK) >> S5_P1_SHIFT; + p1[6] = (bkp[1] & S6_P1_MASK) >> S6_P1_SHIFT; + p1[7] = (bkp[1] & S7_P1_MASK) >> S7_P1_SHIFT; + p1[8] = (bkp[2] & S8_P1_MASK_BKP) >> S8_P1_SHIFT; + p1[9] = (bkp[2] & S9_P1_MASK_BKP) >> S9_P1_BKP_SHIFT; + p1[10] = (bkp[2] & S10_P1_MASK_BKP) >> S10_P1_BKP_SHIFT; + break; + } + } else { + mode = (calib[1] & CAL_SEL_0_1) >> CAL_SEL_SHIFT; + mode |= (calib[3] & CAL_SEL_2) >> CAL_SEL_SHIFT_2; + + switch (mode) { + case TWO_PT_CALIB: + base2 = (calib[2] & BASE2_MASK) >> BASE2_SHIFT; + p2[0] = (calib[2] & S0_P2_MASK) >> S0_P2_SHIFT; + p2[1] = (calib[2] & S1_P2_MASK) >> S1_P2_SHIFT; + p2[2] = (calib[3] & S2_P2_MASK); + p2[3] = (calib[3] & S3_P2_MASK) >> S3_P2_SHIFT; + p2[4] = (calib[3] & S4_P2_MASK) >> S4_P2_SHIFT; + p2[5] = (calib[3] & S5_P2_MASK) >> S5_P2_SHIFT; + p2[6] = (calib[3] & S6_P2_MASK) >> S6_P2_SHIFT; + p2[7] = (calib[4] & S7_P2_MASK); + p2[8] = (calib[4] & S8_P2_MASK) >> S8_P2_SHIFT; + p2[9] = (calib[4] & S9_P2_MASK) >> S9_P2_SHIFT; + p2[10] = (calib[4] & S10_P2_MASK) >> S10_P2_SHIFT; + /* Fall through */ + case ONE_PT_CALIB: + case ONE_PT_CALIB2: + base1 = calib[0] & BASE1_MASK; + p1[0] = (calib[0] & S0_P1_MASK) >> S0_P1_SHIFT; + p1[1] = (calib[0] & S1_P1_MASK) >> S1_P1_SHIFT; + p1[2] = (calib[0] & S2_P1_MASK) >> S2_P1_SHIFT; + p1[3] = (calib[0] & S3_P1_MASK) >> S3_P1_SHIFT; + p1[4] = (calib[1] & S4_P1_MASK); + p1[5] = (calib[1] & S5_P1_MASK) >> S5_P1_SHIFT; + p1[6] = (calib[1] & S6_P1_MASK) >> S6_P1_SHIFT; + p1[7] = (calib[1] & S7_P1_MASK) >> S7_P1_SHIFT; + p1[8] = (calib[1] & S8_P1_MASK) >> S8_P1_SHIFT; + p1[9] = (calib[2] & S9_P1_MASK); + p1[10] = (calib[2] & S10_P1_MASK) >> S10_P1_SHIFT; + break; + } + } + + switch (mode) { + case ONE_PT_CALIB: + for (i = 0; i < tmdev->num_sensors; i++) + p1[i] += (base1 << 2) | BIT_APPEND; + break; + case TWO_PT_CALIB: + for (i = 0; i < tmdev->num_sensors; i++) { + p2[i] += base2; + p2[i] <<= 2; + p2[i] |= BIT_APPEND; + } + /* Fall through */ + case ONE_PT_CALIB2: + for (i = 0; i < tmdev->num_sensors; i++) { + p1[i] += base1; + p1[i] <<= 2; + p1[i] |= BIT_APPEND; + } + break; + default: + for (i = 0; i < tmdev->num_sensors; i++) + p2[i] = 780; + p1[0] = 502; + p1[1] = 509; + p1[2] = 503; + p1[3] = 509; + p1[4] = 505; + p1[5] = 509; + p1[6] = 507; + p1[7] = 510; + p1[8] = 508; + p1[9] = 509; + p1[10] = 508; + break; + } + + compute_intercept_slope(tmdev, p1, p2, mode); + + return 0; +} + +const struct tsens_ops ops_8974 = { + .init = init_common, + .calibrate = calibrate_8974, + .get_temp = get_temp_common, +}; + +const struct tsens_data data_8974 = { + .num_sensors = 11, + .ops = &ops_8974, +}; diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 7f3dfa1..5f206e3 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -68,6 +68,7 @@ static const struct of_device_id tsens_table[] = { .data = &data_8916, }, { .compatible = "qcom,msm8974-tsens", + .data = &data_8974, }, {} }; diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index e767ad5..8ab9997 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -87,6 +87,6 @@ void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32); int init_common(struct tsens_device *); int get_temp_common(struct tsens_device *, int, int *); -extern const struct tsens_data data_8916; +extern const struct tsens_data data_8916, data_8974; #endif /* __QCOM_TSENS_H__ */ -- cgit v0.10.2 From 4a7069a32c99a81950de035535b0a064dcceaeba Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 5 May 2016 14:21:42 +0530 Subject: thermal: core: export apis to get slope and offset Add apis for platform thermal drivers to query for slope and offset attributes, which might be needed for temperature calculations. Signed-off-by: Rajendra Nayak Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index efc3f3d..755476e 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -140,6 +140,18 @@ temperature) and throttle appropriate devices. Normally this function will not need to be called and the resource management code will ensure that the resource is freed. +1.1.7 int thermal_zone_get_slope(struct thermal_zone_device *tz) + + This interface is used to read the slope attribute value + for the thermal zone device, which might be useful for platform + drivers for temperature calculations. + +1.1.8 int thermal_zone_get_offset(struct thermal_zone_device *tz) + + This interface is used to read the offset attribute value + for the thermal zone device, which might be useful for platform + drivers for temperature calculations. + 1.2 thermal cooling device interface 1.2.1 struct thermal_cooling_device *thermal_cooling_device_register(char *name, void *devdata, struct thermal_cooling_device_ops *) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index e2fc616..8728cc6 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -2069,6 +2069,36 @@ exit: } EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name); +/** + * thermal_zone_get_slope - return the slope attribute of the thermal zone + * @tz: thermal zone device with the slope attribute + * + * Return: If the thermal zone device has a slope attribute, return it, else + * return 1. + */ +int thermal_zone_get_slope(struct thermal_zone_device *tz) +{ + if (tz && tz->tzp) + return tz->tzp->slope; + return 1; +} +EXPORT_SYMBOL_GPL(thermal_zone_get_slope); + +/** + * thermal_zone_get_offset - return the offset attribute of the thermal zone + * @tz: thermal zone device with the offset attribute + * + * Return: If the thermal zone device has a offset attribute, return it, else + * return 0. + */ +int thermal_zone_get_offset(struct thermal_zone_device *tz) +{ + if (tz && tz->tzp) + return tz->tzp->offset; + return 0; +} +EXPORT_SYMBOL_GPL(thermal_zone_get_offset); + #ifdef CONFIG_NET static const struct genl_multicast_group thermal_event_mcgrps[] = { { .name = THERMAL_GENL_MCAST_GROUP_NAME, }, diff --git a/include/linux/thermal.h b/include/linux/thermal.h index ee517be..707d735 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -435,6 +435,8 @@ thermal_of_cooling_device_register(struct device_node *np, char *, void *, void thermal_cooling_device_unregister(struct thermal_cooling_device *); struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name); int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp); +int thermal_zone_get_slope(struct thermal_zone_device *tz); +int thermal_zone_get_offset(struct thermal_zone_device *tz); int get_tz_trend(struct thermal_zone_device *, int); struct thermal_instance *get_thermal_instance(struct thermal_zone_device *, @@ -492,6 +494,12 @@ static inline struct thermal_zone_device *thermal_zone_get_zone_by_name( static inline int thermal_zone_get_temp( struct thermal_zone_device *tz, int *temp) { return -ENODEV; } +static inline int thermal_zone_get_slope( + struct thermal_zone_device *tz) +{ return -ENODEV; } +static inline int thermal_zone_get_offset( + struct thermal_zone_device *tz) +{ return -ENODEV; } static inline int get_tz_trend(struct thermal_zone_device *tz, int trip) { return -ENODEV; } static inline struct thermal_instance * -- cgit v0.10.2 From 20d4fd84bf524ad91e2cc3e4ab4020c27cfc0081 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 5 May 2016 14:21:43 +0530 Subject: thermal: qcom: tsens-8960: Add support for 8960 family of SoCs 8960 family of SoCs have the TSENS device as part of GCC, hence the driver probes the virtual child device created by GCC and uses the parent to extract all DT properties and reuses the GCC regmap. Also GCC/TSENS are part of a domain thats not always ON. Hence add .suspend and .resume hooks to save and restore some of the inited register context. Also 8960 family have some of the TSENS init sequence thats required to be done by the HLOS driver (some later versions of TSENS do not export these registers to non-secure world, and hence need these initializations to be done by secure bootloaders) 8660 from the same family has just one sensor and hence some register offset/layout differences which need special handling in the driver. Based on the original code from Siddartha Mohanadoss, Stephen Boyd and Narendran Rajan. Signed-off-by: Rajendra Nayak Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index a471100..f3cefd1 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -1,2 +1,2 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o -qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o +qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c new file mode 100644 index 0000000..1d60916 --- /dev/null +++ b/drivers/thermal/qcom/tsens-8960.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "tsens.h" + +#define CAL_MDEGC 30000 + +#define CONFIG_ADDR 0x3640 +#define CONFIG_ADDR_8660 0x3620 +/* CONFIG_ADDR bitmasks */ +#define CONFIG 0x9b +#define CONFIG_MASK 0xf +#define CONFIG_8660 1 +#define CONFIG_SHIFT_8660 28 +#define CONFIG_MASK_8660 (3 << CONFIG_SHIFT_8660) + +#define STATUS_CNTL_ADDR_8064 0x3660 +#define CNTL_ADDR 0x3620 +/* CNTL_ADDR bitmasks */ +#define EN BIT(0) +#define SW_RST BIT(1) +#define SENSOR0_EN BIT(3) +#define SLP_CLK_ENA BIT(26) +#define SLP_CLK_ENA_8660 BIT(24) +#define MEASURE_PERIOD 1 +#define SENSOR0_SHIFT 3 + +/* INT_STATUS_ADDR bitmasks */ +#define MIN_STATUS_MASK BIT(0) +#define LOWER_STATUS_CLR BIT(1) +#define UPPER_STATUS_CLR BIT(2) +#define MAX_STATUS_MASK BIT(3) + +#define THRESHOLD_ADDR 0x3624 +/* THRESHOLD_ADDR bitmasks */ +#define THRESHOLD_MAX_LIMIT_SHIFT 24 +#define THRESHOLD_MIN_LIMIT_SHIFT 16 +#define THRESHOLD_UPPER_LIMIT_SHIFT 8 +#define THRESHOLD_LOWER_LIMIT_SHIFT 0 + +/* Initial temperature threshold values */ +#define LOWER_LIMIT_TH 0x50 +#define UPPER_LIMIT_TH 0xdf +#define MIN_LIMIT_TH 0x0 +#define MAX_LIMIT_TH 0xff + +#define S0_STATUS_ADDR 0x3628 +#define INT_STATUS_ADDR 0x363c +#define TRDY_MASK BIT(7) +#define TIMEOUT_US 100 + +static int suspend_8960(struct tsens_device *tmdev) +{ + int ret; + unsigned int mask; + struct regmap *map = tmdev->map; + + ret = regmap_read(map, THRESHOLD_ADDR, &tmdev->ctx.threshold); + if (ret) + return ret; + + ret = regmap_read(map, CNTL_ADDR, &tmdev->ctx.control); + if (ret) + return ret; + + if (tmdev->num_sensors > 1) + mask = SLP_CLK_ENA | EN; + else + mask = SLP_CLK_ENA_8660 | EN; + + ret = regmap_update_bits(map, CNTL_ADDR, mask, 0); + if (ret) + return ret; + + return 0; +} + +static int resume_8960(struct tsens_device *tmdev) +{ + int ret; + struct regmap *map = tmdev->map; + + ret = regmap_update_bits(map, CNTL_ADDR, SW_RST, SW_RST); + if (ret) + return ret; + + /* + * Separate CONFIG restore is not needed only for 8660 as + * config is part of CTRL Addr and its restored as such + */ + if (tmdev->num_sensors > 1) { + ret = regmap_update_bits(map, CONFIG_ADDR, CONFIG_MASK, CONFIG); + if (ret) + return ret; + } + + ret = regmap_write(map, THRESHOLD_ADDR, tmdev->ctx.threshold); + if (ret) + return ret; + + ret = regmap_write(map, CNTL_ADDR, tmdev->ctx.control); + if (ret) + return ret; + + return 0; +} + +static int enable_8960(struct tsens_device *tmdev, int id) +{ + int ret; + u32 reg, mask; + + ret = regmap_read(tmdev->map, CNTL_ADDR, ®); + if (ret) + return ret; + + mask = BIT(id + SENSOR0_SHIFT); + ret = regmap_write(tmdev->map, CNTL_ADDR, reg | SW_RST); + if (ret) + return ret; + + if (tmdev->num_sensors > 1) + reg |= mask | SLP_CLK_ENA | EN; + else + reg |= mask | SLP_CLK_ENA_8660 | EN; + + ret = regmap_write(tmdev->map, CNTL_ADDR, reg); + if (ret) + return ret; + + return 0; +} + +static void disable_8960(struct tsens_device *tmdev) +{ + int ret; + u32 reg_cntl; + u32 mask; + + mask = GENMASK(tmdev->num_sensors - 1, 0); + mask <<= SENSOR0_SHIFT; + mask |= EN; + + ret = regmap_read(tmdev->map, CNTL_ADDR, ®_cntl); + if (ret) + return; + + reg_cntl &= ~mask; + + if (tmdev->num_sensors > 1) + reg_cntl &= ~SLP_CLK_ENA; + else + reg_cntl &= ~SLP_CLK_ENA_8660; + + regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); +} + +static int init_8960(struct tsens_device *tmdev) +{ + int ret, i; + u32 reg_cntl; + + tmdev->map = dev_get_regmap(tmdev->dev, NULL); + if (!tmdev->map) + return -ENODEV; + + /* + * The status registers for each sensor are discontiguous + * because some SoCs have 5 sensors while others have more + * but the control registers stay in the same place, i.e + * directly after the first 5 status registers. + */ + for (i = 0; i < tmdev->num_sensors; i++) { + if (i >= 5) + tmdev->sensor[i].status = S0_STATUS_ADDR + 40; + tmdev->sensor[i].status += i * 4; + } + + reg_cntl = SW_RST; + ret = regmap_update_bits(tmdev->map, CNTL_ADDR, SW_RST, reg_cntl); + if (ret) + return ret; + + if (tmdev->num_sensors > 1) { + reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18); + reg_cntl &= ~SW_RST; + ret = regmap_update_bits(tmdev->map, CONFIG_ADDR, + CONFIG_MASK, CONFIG); + } else { + reg_cntl |= SLP_CLK_ENA_8660 | (MEASURE_PERIOD << 16); + reg_cntl &= ~CONFIG_MASK_8660; + reg_cntl |= CONFIG_8660 << CONFIG_SHIFT_8660; + } + + reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << SENSOR0_SHIFT; + ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); + if (ret) + return ret; + + reg_cntl |= EN; + ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); + if (ret) + return ret; + + return 0; +} + +static int calibrate_8960(struct tsens_device *tmdev) +{ + int i; + char *data; + + ssize_t num_read = tmdev->num_sensors; + struct tsens_sensor *s = tmdev->sensor; + + data = qfprom_read(tmdev->dev, "calib"); + if (IS_ERR(data)) + data = qfprom_read(tmdev->dev, "calib_backup"); + if (IS_ERR(data)) + return PTR_ERR(data); + + for (i = 0; i < num_read; i++, s++) + s->offset = data[i]; + + return 0; +} + +/* Temperature on y axis and ADC-code on x-axis */ +static inline int code_to_mdegC(u32 adc_code, const struct tsens_sensor *s) +{ + int slope, offset; + + slope = thermal_zone_get_slope(s->tzd); + offset = CAL_MDEGC - slope * s->offset; + + return adc_code * slope + offset; +} + +static int get_temp_8960(struct tsens_device *tmdev, int id, int *temp) +{ + int ret; + u32 code, trdy; + const struct tsens_sensor *s = &tmdev->sensor[id]; + unsigned long timeout; + + timeout = jiffies + usecs_to_jiffies(TIMEOUT_US); + do { + ret = regmap_read(tmdev->map, INT_STATUS_ADDR, &trdy); + if (ret) + return ret; + if (!(trdy & TRDY_MASK)) + continue; + ret = regmap_read(tmdev->map, s->status, &code); + if (ret) + return ret; + *temp = code_to_mdegC(code, s); + return 0; + } while (time_before(jiffies, timeout)); + + return -ETIMEDOUT; +} + +const struct tsens_ops ops_8960 = { + .init = init_8960, + .calibrate = calibrate_8960, + .get_temp = get_temp_8960, + .enable = enable_8960, + .disable = disable_8960, + .suspend = suspend_8960, + .resume = resume_8960, +}; + +const struct tsens_data data_8960 = { + .num_sensors = 11, + .ops = &ops_8960, +}; diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 5f206e3..cef86c3 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -122,10 +122,10 @@ static int tsens_probe(struct platform_device *pdev) np = dev->of_node; id = of_match_node(tsens_table, np); - if (!id) - return -EINVAL; - - data = id->data; + if (id) + data = id->data; + else + data = &data_8960; if (data->num_sensors <= 0) { dev_err(dev, "invalid number of sensors\n"); diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index 8ab9997..42071a4 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -87,6 +87,6 @@ void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32); int init_common(struct tsens_device *); int get_temp_common(struct tsens_device *, int, int *); -extern const struct tsens_data data_8916, data_8974; +extern const struct tsens_data data_8916, data_8974, data_8960; #endif /* __QCOM_TSENS_H__ */ -- cgit v0.10.2 From d059c739aacfbd00606f1b120ceaadb79f05c7e0 Mon Sep 17 00:00:00 2001 From: Rajendra Nayak Date: Thu, 5 May 2016 14:21:44 +0530 Subject: thermal: qcom: tsens-8996: Add support for 8996 family of SoCs The TSENS controller in 8996 family of SoCs is capable of converting the ADC code outputs to real temperature values (in decidegree Celsius). It can also be programmed to provide raw ADC code, but the secure software on 8996 programs it to provide real temperatures and also does the needed calibrations. We check the valid bit to ensure valid data is read by the AHB master. And the spec recommends the below algorithm to read data 3 consecutive times, which takes care of the worst case delay taken to propagate the updated data to the register. Signed-off-by: Rajendra Nayak Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index f3cefd1..2cc2193 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -1,2 +1,2 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o -qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o +qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-8996.o diff --git a/drivers/thermal/qcom/tsens-8996.c b/drivers/thermal/qcom/tsens-8996.c new file mode 100644 index 0000000..e2e1274 --- /dev/null +++ b/drivers/thermal/qcom/tsens-8996.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "tsens.h" + +#define STATUS_OFFSET 0x10a0 +#define LAST_TEMP_MASK 0xfff +#define STATUS_VALID_BIT BIT(21) +#define CODE_SIGN_BIT BIT(11) + +static int get_temp_8996(struct tsens_device *tmdev, int id, int *temp) +{ + struct tsens_sensor *s = &tmdev->sensor[id]; + u32 code; + unsigned int sensor_addr; + int last_temp = 0, last_temp2 = 0, last_temp3 = 0, ret; + + sensor_addr = STATUS_OFFSET + s->hw_id * 4; + ret = regmap_read(tmdev->map, sensor_addr, &code); + if (ret) + return ret; + last_temp = code & LAST_TEMP_MASK; + if (code & STATUS_VALID_BIT) + goto done; + + /* Try a second time */ + ret = regmap_read(tmdev->map, sensor_addr, &code); + if (ret) + return ret; + if (code & STATUS_VALID_BIT) { + last_temp = code & LAST_TEMP_MASK; + goto done; + } else { + last_temp2 = code & LAST_TEMP_MASK; + } + + /* Try a third/last time */ + ret = regmap_read(tmdev->map, sensor_addr, &code); + if (ret) + return ret; + if (code & STATUS_VALID_BIT) { + last_temp = code & LAST_TEMP_MASK; + goto done; + } else { + last_temp3 = code & LAST_TEMP_MASK; + } + + if (last_temp == last_temp2) + last_temp = last_temp2; + else if (last_temp2 == last_temp3) + last_temp = last_temp3; +done: + /* Code sign bit is the sign extension for a negative value */ + if (last_temp & CODE_SIGN_BIT) + last_temp |= ~CODE_SIGN_BIT; + + /* Temperatures are in deciCelicius */ + *temp = last_temp * 100; + + return 0; +} + +const struct tsens_ops ops_8996 = { + .init = init_common, + .get_temp = get_temp_8996, +}; + +const struct tsens_data data_8996 = { + .num_sensors = 13, + .ops = &ops_8996, +}; diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index cef86c3..b182272 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -69,6 +69,9 @@ static const struct of_device_id tsens_table[] = { }, { .compatible = "qcom,msm8974-tsens", .data = &data_8974, + }, { + .compatible = "qcom,msm8996-tsens", + .data = &data_8996, }, {} }; diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index 42071a4..b0f8c47 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -87,6 +87,6 @@ void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32); int init_common(struct tsens_device *); int get_temp_common(struct tsens_device *, int, int *); -extern const struct tsens_data data_8916, data_8974, data_8960; +extern const struct tsens_data data_8916, data_8974, data_8960, data_8996; #endif /* __QCOM_TSENS_H__ */ -- cgit v0.10.2 From 032d4057291e9d017cf50be017cf09b169ff438f Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Fri, 1 Jul 2016 18:02:09 -0700 Subject: thermal: qcom: remove declare local symbols as static Trivial: remove the following: drivers/thermal/qcom/tsens-8916.c:103:24: warning: symbol 'ops_8916' was not declared. Should it be static? drivers/thermal/qcom/tsens-8996.c:76:24: warning: symbol 'ops_8996' was not declared. Should it be static? drivers/thermal/qcom/tsens-8974.c:235:24: warning: symbol 'ops_8974' was not declared. Should it be static? drivers/thermal/qcom/tsens-8960.c:279:24: warning: symbol 'ops_8960' was not declared. Should it be static? Cc: Zhang Rui Cc: Rajendra Nayak Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/qcom/tsens-8916.c b/drivers/thermal/qcom/tsens-8916.c index beb3afa..fdf561b 100644 --- a/drivers/thermal/qcom/tsens-8916.c +++ b/drivers/thermal/qcom/tsens-8916.c @@ -100,7 +100,7 @@ static int calibrate_8916(struct tsens_device *tmdev) return 0; } -const struct tsens_ops ops_8916 = { +static const struct tsens_ops ops_8916 = { .init = init_common, .calibrate = calibrate_8916, .get_temp = get_temp_common, diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c index 1d60916..0451277 100644 --- a/drivers/thermal/qcom/tsens-8960.c +++ b/drivers/thermal/qcom/tsens-8960.c @@ -276,7 +276,7 @@ static int get_temp_8960(struct tsens_device *tmdev, int id, int *temp) return -ETIMEDOUT; } -const struct tsens_ops ops_8960 = { +static const struct tsens_ops ops_8960 = { .init = init_8960, .calibrate = calibrate_8960, .get_temp = get_temp_8960, diff --git a/drivers/thermal/qcom/tsens-8974.c b/drivers/thermal/qcom/tsens-8974.c index da3ca4c..9baf77e 100644 --- a/drivers/thermal/qcom/tsens-8974.c +++ b/drivers/thermal/qcom/tsens-8974.c @@ -232,7 +232,7 @@ static int calibrate_8974(struct tsens_device *tmdev) return 0; } -const struct tsens_ops ops_8974 = { +static const struct tsens_ops ops_8974 = { .init = init_common, .calibrate = calibrate_8974, .get_temp = get_temp_common, diff --git a/drivers/thermal/qcom/tsens-8996.c b/drivers/thermal/qcom/tsens-8996.c index e2e1274..e1f7781 100644 --- a/drivers/thermal/qcom/tsens-8996.c +++ b/drivers/thermal/qcom/tsens-8996.c @@ -73,7 +73,7 @@ done: return 0; } -const struct tsens_ops ops_8996 = { +static const struct tsens_ops ops_8996 = { .init = init_common, .get_temp = get_temp_8996, }; -- cgit v0.10.2 From 060c034a974187e930b790957cafc5047cc30a40 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 22 Jun 2016 16:42:01 +0800 Subject: thermal: Add support for hardware-tracked trip points This adds support for hardware-tracked trip points to the device tree thermal sensor framework. The framework supports an arbitrary number of trip points. Whenever the current temperature is updated, the trip points immediately below and above the current temperature are found. A .set_trips callback is then called with the temperatures. If there is no trip point above or below the current temperature, the passed trip temperature will be -INT_MAX or INT_MAX respectively. In this callback, the driver should program the hardware such that it is notified when either of these trip points are triggered. When a trip point is triggered, the driver should call `thermal_zone_device_update' for the respective thermal zone. This will cause the trip points to be updated again. If .set_trips is not implemented, the framework behaves as before. This patch is based on an earlier version from Mikko Perttunen Signed-off-by: Sascha Hauer Signed-off-by: Caesar Wang Cc: Zhang Rui Cc: Eduardo Valentin Cc: linux-pm@vger.kernel.org Reviewed-by: Javi Merino Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index 755476e..ef473dc 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -49,6 +49,9 @@ temperature) and throttle appropriate devices. .bind: bind the thermal zone device with a thermal cooling device. .unbind: unbind the thermal zone device with a thermal cooling device. .get_temp: get the current temperature of the thermal zone. + .set_trips: set the trip points window. Whenever the current temperature + is updated, the trip points immediately below and above the + current temperature are found. .get_mode: get the current mode (enabled/disabled) of the thermal zone. - "enabled" means the kernel thermal management is enabled. - "disabled" will prevent kernel thermal driver action upon trip points @@ -95,6 +98,10 @@ temperature) and throttle appropriate devices. get_temp: a pointer to a function that reads the sensor temperature. This is mandatory callback provided by sensor driver. + set_trips: a pointer to a function that sets a + temperature window. When this window is + left the driver must inform the thermal + core via thermal_zone_device_update. get_trend: a pointer to a function that reads the sensor temperature trend. set_emul_temp: a pointer to a function that sets diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 8728cc6..f2d55e4 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -520,6 +520,56 @@ exit: } EXPORT_SYMBOL_GPL(thermal_zone_get_temp); +void thermal_zone_set_trips(struct thermal_zone_device *tz) +{ + int low = -INT_MAX; + int high = INT_MAX; + int trip_temp, hysteresis; + int i, ret; + + mutex_lock(&tz->lock); + + if (!tz->ops->set_trips || !tz->ops->get_trip_hyst) + goto exit; + + for (i = 0; i < tz->trips; i++) { + int trip_low; + + tz->ops->get_trip_temp(tz, i, &trip_temp); + tz->ops->get_trip_hyst(tz, i, &hysteresis); + + trip_low = trip_temp - hysteresis; + + if (trip_low < tz->temperature && trip_low > low) + low = trip_low; + + if (trip_temp > tz->temperature && trip_temp < high) + high = trip_temp; + } + + /* No need to change trip points */ + if (tz->prev_low_trip == low && tz->prev_high_trip == high) + goto exit; + + tz->prev_low_trip = low; + tz->prev_high_trip = high; + + dev_dbg(&tz->device, + "new temperature boundaries: %d < x < %d\n", low, high); + + /* + * Set a temperature window. When this window is left the driver + * must inform the thermal core via thermal_zone_device_update. + */ + ret = tz->ops->set_trips(tz, low, high); + if (ret) + dev_err(&tz->device, "Failed to set trips: %d\n", ret); + +exit: + mutex_unlock(&tz->lock); +} +EXPORT_SYMBOL_GPL(thermal_zone_set_trips); + static void update_temperature(struct thermal_zone_device *tz) { int temp, ret; @@ -569,6 +619,8 @@ void thermal_zone_device_update(struct thermal_zone_device *tz) update_temperature(tz); + thermal_zone_set_trips(tz); + for (count = 0; count < tz->trips; count++) handle_thermal_trip(tz, count); } @@ -754,6 +806,9 @@ trip_point_hyst_store(struct device *dev, struct device_attribute *attr, */ ret = tz->ops->set_trip_hyst(tz, trip, temperature); + if (!ret) + thermal_zone_set_trips(tz); + return ret ? ret : count; } diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 707d735..54cdfea 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -98,6 +98,7 @@ struct thermal_zone_device_ops { int (*unbind) (struct thermal_zone_device *, struct thermal_cooling_device *); int (*get_temp) (struct thermal_zone_device *, int *); + int (*set_trips) (struct thermal_zone_device *, int, int); int (*get_mode) (struct thermal_zone_device *, enum thermal_device_mode *); int (*set_mode) (struct thermal_zone_device *, @@ -168,6 +169,10 @@ struct thermal_attr { * @last_temperature: previous temperature read * @emul_temperature: emulated temperature when using CONFIG_THERMAL_EMULATION * @passive: 1 if you've crossed a passive trip point, 0 otherwise. + * @prev_low_trip: the low current temperature if you've crossed a passive + trip point. + * @prev_high_trip: the above current temperature if you've crossed a + passive trip point. * @forced_passive: If > 0, temperature at which to switch on all ACPI * processor cooling devices. Currently only used by the * step-wise governor. @@ -199,6 +204,8 @@ struct thermal_zone_device { int last_temperature; int emul_temperature; int passive; + int prev_low_trip; + int prev_high_trip; unsigned int forced_passive; atomic_t need_update; struct thermal_zone_device_ops *ops; @@ -426,6 +433,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int, int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int, struct thermal_cooling_device *); void thermal_zone_device_update(struct thermal_zone_device *); +void thermal_zone_set_trips(struct thermal_zone_device *); struct thermal_cooling_device *thermal_cooling_device_register(char *, void *, const struct thermal_cooling_device_ops *); @@ -477,6 +485,8 @@ static inline int thermal_zone_unbind_cooling_device( { return -ENODEV; } static inline void thermal_zone_device_update(struct thermal_zone_device *tz) { } +static inline void thermal_zone_set_trips(struct thermal_zone_device *tz) +{ } static inline struct thermal_cooling_device * thermal_cooling_device_register(char *type, void *devdata, const struct thermal_cooling_device_ops *ops) -- cgit v0.10.2 From 826386e73193e0b58c6d797fbbab409bc98b1d9c Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 22 Jun 2016 16:42:02 +0800 Subject: thermal: of: implement .set_trips for device tree thermal zones This patch implements .set_trips for device tree thermal zones. As the hardware-tracked trip points is supported by thermal core patch[0]. patch[0] "thermal: Add support for hardware-tracked trip points". Signed-off-by: Sascha Hauer Signed-off-by: Caesar Wang Cc: Zhang Rui Cc: Eduardo Valentin Reviewed-by: Javi Merino Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index b8e509c..2d2a06f 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -101,6 +101,17 @@ static int of_thermal_get_temp(struct thermal_zone_device *tz, return data->ops->get_temp(data->sensor_data, temp); } +static int of_thermal_set_trips(struct thermal_zone_device *tz, + int low, int high) +{ + struct __thermal_zone *data = tz->devdata; + + if (!data->ops || !data->ops->set_trips) + return -EINVAL; + + return data->ops->set_trips(data->sensor_data, low, high); +} + /** * of_thermal_get_ntrips - function to export number of available trip * points. @@ -427,6 +438,14 @@ thermal_zone_of_add_sensor(struct device_node *zone, tzd->ops->get_temp = of_thermal_get_temp; tzd->ops->get_trend = of_thermal_get_trend; + + /* + * The thermal zone core will calculate the window if they have set the + * optional set_trips pointer. + */ + if (ops->set_trips) + tzd->ops->set_trips = of_thermal_set_trips; + tzd->ops->set_emul_temp = of_thermal_set_emul_temp; mutex_unlock(&tzd->lock); diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 54cdfea..20118b9 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -340,6 +340,9 @@ struct thermal_genl_event { * * Optional: * @get_trend: a pointer to a function that reads the sensor temperature trend. + * @set_trips: a pointer to a function that sets a temperature window. When + * this window is left the driver must inform the thermal core via + * thermal_zone_device_update. * @set_emul_temp: a pointer to a function that sets sensor emulated * temperature. * @set_trip_temp: a pointer to a function that sets the trip temperature on @@ -348,6 +351,7 @@ struct thermal_genl_event { struct thermal_zone_of_device_ops { int (*get_temp)(void *, int *); int (*get_trend)(void *, long *); + int (*set_trips)(void *, int, int); int (*set_emul_temp)(void *, int); int (*set_trip_temp)(void *, int, int); }; -- cgit v0.10.2 From e78eaf45993a51e5d7120de48aa01f059ffe8d37 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 22 Jun 2016 16:42:03 +0800 Subject: thermal: streamline get_trend callbacks The .get_trend callback in struct thermal_zone_device_ops has the prototype: int (*get_trend) (struct thermal_zone_device *, int, enum thermal_trend *); whereas the .get_trend callback in struct thermal_zone_of_device_ops has: int (*get_trend)(void *, long *); Streamline both prototypes and add the trip argument to the OF callback aswell and use enum thermal_trend * instead of an integer pointer. While the OF prototype may be the better one, this should be decided at framework level and not on OF level. Signed-off-by: Sascha Hauer Signed-off-by: Caesar Wang Cc: Zhang Rui Cc: Eduardo Valentin Cc: linux-pm@vger.kernel.org Reviewed-by: Keerthy Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index 2d2a06f..20822ab 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -202,25 +202,11 @@ static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip, enum thermal_trend *trend) { struct __thermal_zone *data = tz->devdata; - long dev_trend; - int r; if (!data->ops->get_trend) return -EINVAL; - r = data->ops->get_trend(data->sensor_data, &dev_trend); - if (r) - return r; - - /* TODO: These intervals might have some thresholds, but in core code */ - if (dev_trend > 0) - *trend = THERMAL_TREND_RAISING; - else if (dev_trend < 0) - *trend = THERMAL_TREND_DROPPING; - else - *trend = THERMAL_TREND_STABLE; - - return 0; + return data->ops->get_trend(data->sensor_data, trip, trend); } static int of_thermal_bind(struct thermal_zone_device *thermal, diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index b182272..446f70b 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -29,13 +29,13 @@ static int tsens_get_temp(void *data, int *temp) return tmdev->ops->get_temp(tmdev, s->id, temp); } -static int tsens_get_trend(void *data, long *temp) +static int tsens_get_trend(void *p, int trip, enum thermal_trend *trend) { - const struct tsens_sensor *s = data; + const struct tsens_sensor *s = p; struct tsens_device *tmdev = s->tmdev; if (tmdev->ops->get_trend) - return tmdev->ops->get_trend(tmdev, s->id, temp); + return tmdev->ops->get_trend(tmdev, s->id, trend); return -ENOTSUPP; } diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index b0f8c47..911c197 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -17,6 +17,8 @@ #define ONE_PT_CALIB2 0x2 #define TWO_PT_CALIB 0x3 +#include + struct tsens_device; struct tsens_sensor { @@ -50,7 +52,7 @@ struct tsens_ops { void (*disable)(struct tsens_device *); int (*suspend)(struct tsens_device *); int (*resume)(struct tsens_device *); - int (*get_trend)(struct tsens_device *, int, long *); + int (*get_trend)(struct tsens_device *, int, enum thermal_trend *); }; /** diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 15c0a9a..4a6757c 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -239,7 +239,7 @@ static int ti_thermal_get_trip_temp(struct thermal_zone_device *thermal, return 0; } -static int __ti_thermal_get_trend(void *p, long *trend) +static int __ti_thermal_get_trend(void *p, int trip, enum thermal_trend *trend) { struct ti_thermal_data *data = p; struct ti_bandgap *bgp; @@ -252,22 +252,6 @@ static int __ti_thermal_get_trend(void *p, long *trend) if (ret) return ret; - *trend = tr; - - return 0; -} - -/* Get the temperature trend callback functions for thermal zone */ -static int ti_thermal_get_trend(struct thermal_zone_device *thermal, - int trip, enum thermal_trend *trend) -{ - int ret; - long tr; - - ret = __ti_thermal_get_trend(thermal->devdata, &tr); - if (ret) - return ret; - if (tr > 0) *trend = THERMAL_TREND_RAISING; else if (tr < 0) @@ -278,6 +262,13 @@ static int ti_thermal_get_trend(struct thermal_zone_device *thermal, return 0; } +/* Get the temperature trend callback functions for thermal zone */ +static int ti_thermal_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + return __ti_thermal_get_trend(thermal->devdata, trip, trend); +} + /* Get critical temperature callback functions for thermal zone */ static int ti_thermal_get_crit_temp(struct thermal_zone_device *thermal, int *temp) diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 20118b9..b3c16f0 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -350,7 +350,7 @@ struct thermal_genl_event { */ struct thermal_zone_of_device_ops { int (*get_temp)(void *, int *); - int (*get_trend)(void *, long *); + int (*get_trend)(void *, int, enum thermal_trend *); int (*set_trips)(void *, int, int); int (*set_emul_temp)(void *, int); int (*set_trip_temp)(void *, int, int); -- cgit v0.10.2 From 897e721037753ca5b843c309d08dfb50387cbe84 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 22 Jun 2016 16:42:04 +0800 Subject: thermal: bang-bang governor: act on lower trip boundary With interrupt driven thermal zones we pass the lower and upper temperature on which shall be acted, so in the governor we have to act on the exact lower temperature to be consistent. Otherwise an interrupt maybe generated on the exact lower temperature, but the bang bang governor does not react since The polling driven zones have to be one step cooler before the governor reacts. Signed-off-by: Sascha Hauer Signed-off-by: Caesar Wang Cc: Zhang Rui Cc: Eduardo Valentin Cc: linux-pm@vger.kernel.org Acked-by: Peter Feuerer Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/gov_bang_bang.c b/drivers/thermal/gov_bang_bang.c index bb118a1..fc5e505 100644 --- a/drivers/thermal/gov_bang_bang.c +++ b/drivers/thermal/gov_bang_bang.c @@ -65,7 +65,7 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip) if (instance->target == 0 && tz->temperature >= trip_temp) instance->target = 1; else if (instance->target == 1 && - tz->temperature < trip_temp - trip_hyst) + tz->temperature <= trip_temp - trip_hyst) instance->target = 0; dev_dbg(&instance->cdev->device, "target=%d\n", -- cgit v0.10.2 From 148485023f2bff19732366789f2badb1cc3ac95b Mon Sep 17 00:00:00 2001 From: Caesar Wang Date: Wed, 22 Jun 2016 16:42:05 +0800 Subject: thermal: rockchip: add the set_trips function Whenever the current temperature is updated, the trip points immediately below and above the current temperature are found. A sensor driver callback `set_trips' is then called with the temperatures. Lastly, The sensor will trigger the hardware high temperature interrupts to increase the sampleing rate and throttle frequency to limit the temperature rising When performing passive cooling. Signed-off-by: Caesar Wang Cc: Zhang Rui Cc: Eduardo Valentin Cc: linux-pm@vger.kernel.org Tested-by: Stephen Barber Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index 5d491f1..8175cdb 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -96,6 +96,7 @@ struct chip_tsadc_table { * @initialize: SoC special initialize tsadc controller method * @irq_ack: clear the interrupt * @get_temp: get the temperature + * @set_alarm_temp: set the high temperature interrupt * @set_tshut_temp: set the hardware-controlled shutdown temperature * @set_tshut_mode: set the hardware-controlled shutdown mode * @table: the chip-specific conversion table @@ -119,6 +120,8 @@ struct rockchip_tsadc_chip { /* Per-sensor methods */ int (*get_temp)(struct chip_tsadc_table table, int chn, void __iomem *reg, int *temp); + void (*set_alarm_temp)(struct chip_tsadc_table table, + int chn, void __iomem *reg, int temp); void (*set_tshut_temp)(struct chip_tsadc_table table, int chn, void __iomem *reg, int temp); void (*set_tshut_mode)(int chn, void __iomem *reg, enum tshut_mode m); @@ -183,6 +186,7 @@ struct rockchip_thermal_data { #define TSADCV2_INT_EN 0x08 #define TSADCV2_INT_PD 0x0c #define TSADCV2_DATA(chn) (0x20 + (chn) * 0x04) +#define TSADCV2_COMP_INT(chn) (0x30 + (chn) * 0x04) #define TSADCV2_COMP_SHUT(chn) (0x40 + (chn) * 0x04) #define TSADCV2_HIGHT_INT_DEBOUNCE 0x60 #define TSADCV2_HIGHT_TSHUT_DEBOUNCE 0x64 @@ -628,6 +632,20 @@ static int rk_tsadcv2_get_temp(struct chip_tsadc_table table, return rk_tsadcv2_code_to_temp(table, val, temp); } +static void rk_tsadcv2_alarm_temp(struct chip_tsadc_table table, + int chn, void __iomem *regs, int temp) +{ + u32 alarm_value, int_en; + + alarm_value = rk_tsadcv2_temp_to_code(table, temp); + writel_relaxed(alarm_value & table.data_mask, + regs + TSADCV2_COMP_INT(chn)); + + int_en = readl_relaxed(regs + TSADCV2_INT_EN); + int_en |= TSADCV2_INT_SRC_EN(chn); + writel_relaxed(int_en, regs + TSADCV2_INT_EN); +} + static void rk_tsadcv2_tshut_temp(struct chip_tsadc_table table, int chn, void __iomem *regs, int temp) { @@ -670,6 +688,7 @@ static const struct rockchip_tsadc_chip rk3228_tsadc_data = { .irq_ack = rk_tsadcv3_irq_ack, .control = rk_tsadcv3_control, .get_temp = rk_tsadcv2_get_temp, + .set_alarm_temp = rk_tsadcv2_alarm_temp, .set_tshut_temp = rk_tsadcv2_tshut_temp, .set_tshut_mode = rk_tsadcv2_tshut_mode, @@ -694,6 +713,7 @@ static const struct rockchip_tsadc_chip rk3288_tsadc_data = { .irq_ack = rk_tsadcv2_irq_ack, .control = rk_tsadcv2_control, .get_temp = rk_tsadcv2_get_temp, + .set_alarm_temp = rk_tsadcv2_alarm_temp, .set_tshut_temp = rk_tsadcv2_tshut_temp, .set_tshut_mode = rk_tsadcv2_tshut_mode, @@ -718,6 +738,7 @@ static const struct rockchip_tsadc_chip rk3366_tsadc_data = { .irq_ack = rk_tsadcv3_irq_ack, .control = rk_tsadcv3_control, .get_temp = rk_tsadcv2_get_temp, + .set_alarm_temp = rk_tsadcv2_alarm_temp, .set_tshut_temp = rk_tsadcv2_tshut_temp, .set_tshut_mode = rk_tsadcv2_tshut_mode, @@ -742,6 +763,7 @@ static const struct rockchip_tsadc_chip rk3368_tsadc_data = { .irq_ack = rk_tsadcv2_irq_ack, .control = rk_tsadcv2_control, .get_temp = rk_tsadcv2_get_temp, + .set_alarm_temp = rk_tsadcv2_alarm_temp, .set_tshut_temp = rk_tsadcv2_tshut_temp, .set_tshut_mode = rk_tsadcv2_tshut_mode, @@ -766,6 +788,7 @@ static const struct rockchip_tsadc_chip rk3399_tsadc_data = { .irq_ack = rk_tsadcv3_irq_ack, .control = rk_tsadcv3_control, .get_temp = rk_tsadcv2_get_temp, + .set_alarm_temp = rk_tsadcv2_alarm_temp, .set_tshut_temp = rk_tsadcv2_tshut_temp, .set_tshut_mode = rk_tsadcv2_tshut_mode, @@ -826,6 +849,21 @@ static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev) return IRQ_HANDLED; } +static int rockchip_thermal_set_trips(void *_sensor, int low, int high) +{ + struct rockchip_thermal_sensor *sensor = _sensor; + struct rockchip_thermal_data *thermal = sensor->thermal; + const struct rockchip_tsadc_chip *tsadc = thermal->chip; + + dev_dbg(&thermal->pdev->dev, "%s: sensor %d: low: %d, high %d\n", + __func__, sensor->id, low, high); + + tsadc->set_alarm_temp(tsadc->table, + sensor->id, thermal->regs, high); + + return 0; +} + static int rockchip_thermal_get_temp(void *_sensor, int *out_temp) { struct rockchip_thermal_sensor *sensor = _sensor; @@ -843,6 +881,7 @@ static int rockchip_thermal_get_temp(void *_sensor, int *out_temp) static const struct thermal_zone_of_device_ops rockchip_of_thermal_ops = { .get_temp = rockchip_thermal_get_temp, + .set_trips = rockchip_thermal_set_trips, }; static int rockchip_configure_from_dt(struct device *dev, -- cgit v0.10.2 From 466678790650a9dff6f107ca09a2f5e6480799e9 Mon Sep 17 00:00:00 2001 From: Caesar Wang Date: Wed, 22 Jun 2016 18:13:56 +0800 Subject: thermal: rockchip: fixes the period time for tsadc We should increase the period cycles to save power since the rk3399 has the high frequency for tsadc clock. Fixes commit b0d70338bca22cb14 ("thermal: rockchip: Support the RK3399 SoCs in thermal driver") Signed-off-by: Caesar Wang Cc: Zhang Rui Cc: Eduardo Valentin Cc: Heiko Stuebner Cc: linux-pm@vger.kernel.org Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index 8175cdb..2d5ba97 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -211,8 +211,11 @@ struct rockchip_thermal_data { #define TSADCV2_HIGHT_INT_DEBOUNCE_COUNT 4 #define TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT 4 -#define TSADCV2_AUTO_PERIOD_TIME 250 /* msec */ -#define TSADCV2_AUTO_PERIOD_HT_TIME 50 /* msec */ +#define TSADCV2_AUTO_PERIOD_TIME 250 /* 250ms */ +#define TSADCV2_AUTO_PERIOD_HT_TIME 50 /* 50ms */ +#define TSADCV3_AUTO_PERIOD_TIME 187500 /* 250ms */ +#define TSADCV3_AUTO_PERIOD_HT_TIME 37500 /* 50ms */ + #define TSADCV2_USER_INTER_PD_SOC 0x340 /* 13 clocks */ #define GRF_SARADC_TESTBIT 0x0e644 @@ -547,6 +550,16 @@ static void rk_tsadcv3_initialize(struct regmap *grf, void __iomem *regs, /* Set interleave value to workround ic time sync issue */ writel_relaxed(TSADCV2_USER_INTER_PD_SOC, regs + TSADCV2_USER_CON); + + writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, + regs + TSADCV2_AUTO_PERIOD); + writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT, + regs + TSADCV2_HIGHT_INT_DEBOUNCE); + writel_relaxed(TSADCV2_AUTO_PERIOD_HT_TIME, + regs + TSADCV2_AUTO_PERIOD_HT); + writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, + regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); + } else { regmap_write(grf, GRF_TSADC_TESTBIT_L, GRF_TSADC_TSEN_PD_ON); mdelay(10); @@ -555,6 +568,15 @@ static void rk_tsadcv3_initialize(struct regmap *grf, void __iomem *regs, regmap_write(grf, GRF_SARADC_TESTBIT, GRF_SARADC_TESTBIT_ON); regmap_write(grf, GRF_TSADC_TESTBIT_H, GRF_TSADC_TESTBIT_H_ON); usleep_range(90, 200); /* The spec note says at least 90 us */ + + writel_relaxed(TSADCV3_AUTO_PERIOD_TIME, + regs + TSADCV2_AUTO_PERIOD); + writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT, + regs + TSADCV2_HIGHT_INT_DEBOUNCE); + writel_relaxed(TSADCV3_AUTO_PERIOD_HT_TIME, + regs + TSADCV2_AUTO_PERIOD_HT); + writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, + regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); } if (tshut_polarity == TSHUT_HIGH_ACTIVE) @@ -563,14 +585,6 @@ static void rk_tsadcv3_initialize(struct regmap *grf, void __iomem *regs, else writel_relaxed(0U & ~TSADCV2_AUTO_TSHUT_POLARITY_HIGH, regs + TSADCV2_AUTO_CON); - - writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, regs + TSADCV2_AUTO_PERIOD); - writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT, - regs + TSADCV2_HIGHT_INT_DEBOUNCE); - writel_relaxed(TSADCV2_AUTO_PERIOD_HT_TIME, - regs + TSADCV2_AUTO_PERIOD_HT); - writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, - regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); } static void rk_tsadcv2_irq_ack(void __iomem *regs) -- cgit v0.10.2 From 1f09ba82fa4bd405f5c656c48d53e076931b4a2d Mon Sep 17 00:00:00 2001 From: Caesar Wang Date: Wed, 22 Jun 2016 18:13:57 +0800 Subject: thermal: rockchip: fixes the exception interrupts The hardware-tracked trips will set the alarm interrupt value for registers. Then when the thermal zone has no trips to be set, That make the thermal trips callback a over range value. The root cause is the rk_tsadcv2_temp_to_code() function to handle the invalid temperature range is indeed incorrect, let's fix it on now. Otherwise, the thermal alarm interrupt will be triggered all the time on some SoCs. Fox example: localhost tmp # grep thermal /proc/interrupts; sleep 5; grep thermal /proc/interrupts 23: 994830 .. GICv3 129 Level rockchip_thermal 23: 1003423 .. GICv3 129 Level rockchip_thermal Reported-by: Rocky Hao Signed-off-by: Caesar Wang Cc: Zhang Rui Cc: Eduardo Valentin Cc: Heiko Stuebner Cc: linux-pm@vger.kernel.org Signed-off-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index 2d5ba97..db5ecc5 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -401,13 +401,17 @@ static u32 rk_tsadcv2_temp_to_code(struct chip_tsadc_table table, int temp) { int high, low, mid; + u32 error = 0; low = 0; high = table.length - 1; mid = (high + low) / 2; - if (temp < table.id[low].temp || temp > table.id[high].temp) - return 0; + /* Return mask code data when the temp is over table range */ + if (temp < table.id[low].temp || temp > table.id[high].temp) { + error = table.data_mask; + goto exit; + } while (low <= high) { if (temp == table.id[mid].temp) @@ -419,7 +423,9 @@ static u32 rk_tsadcv2_temp_to_code(struct chip_tsadc_table table, mid = (low + high) / 2; } - return 0; +exit: + pr_err("Invalid the conversion, error=%d\n", error); + return error; } static int rk_tsadcv2_code_to_temp(struct chip_tsadc_table table, u32 code, @@ -651,7 +657,11 @@ static void rk_tsadcv2_alarm_temp(struct chip_tsadc_table table, { u32 alarm_value, int_en; + /* Make sure the value is valid */ alarm_value = rk_tsadcv2_temp_to_code(table, temp); + if (alarm_value == table.data_mask) + return; + writel_relaxed(alarm_value & table.data_mask, regs + TSADCV2_COMP_INT(chn)); @@ -665,7 +675,11 @@ static void rk_tsadcv2_tshut_temp(struct chip_tsadc_table table, { u32 tshut_value, val; + /* Make sure the value is valid */ tshut_value = rk_tsadcv2_temp_to_code(table, temp); + if (tshut_value == table.data_mask) + return; + writel_relaxed(tshut_value, regs + TSADCV2_COMP_SHUT(chn)); /* TSHUT will be valid */ -- cgit v0.10.2 From e2fa74880987e9d6e3f8dcbff3f3eb260936842f Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 2 Jun 2016 14:24:50 +0530 Subject: thermal: of-thermal: Fix setting of set_emul_temp hook Currently thermal zone set_emul_temp is set unconditionally with of_thermal_set_emul_temp function. Set this only if the set_emul_temp hook is provided for thermal_zone_of_device_ops. This fixes emul_temp failures on platforms for which set_emul_temp hook is not populated. Fixes: "184a4bf623f (thermal: of: Extend current of-thermal.c code to allow setting emulated temp)" Suggested-by: Eduardo Valentin Signed-off-by: Keerthy Signed-off-by: Eduardo Valentin Conflicts: drivers/thermal/of-thermal.c Signed-off-by: Zhang Rui diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index 20822ab..972b0cf 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -192,9 +192,6 @@ static int of_thermal_set_emul_temp(struct thermal_zone_device *tz, { struct __thermal_zone *data = tz->devdata; - if (!data->ops || !data->ops->set_emul_temp) - return -EINVAL; - return data->ops->set_emul_temp(data->sensor_data, temp); } @@ -432,7 +429,9 @@ thermal_zone_of_add_sensor(struct device_node *zone, if (ops->set_trips) tzd->ops->set_trips = of_thermal_set_trips; - tzd->ops->set_emul_temp = of_thermal_set_emul_temp; + if (ops->set_emul_temp) + tzd->ops->set_emul_temp = of_thermal_set_emul_temp; + mutex_unlock(&tzd->lock); return tzd; -- cgit v0.10.2 From 64a411e8042ed00057658000126fd9f2b4105bdd Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 19 Jul 2016 10:01:37 +0000 Subject: thermal: rcar-thermal: enable hwmon when thermal_zone_of_sensor_register is used rcar-thermal is supporting both thermal_zone_of_sensor_register() and thermal_zone_device_register(). But thermal_zone_of_sensor_register() doesn't enable hwmon as default. This patch enables it to keep compatibility Signed-off-by: Kuninori Morimoto Signed-off-by: Zhang Rui diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index 71a3392..4d07644 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -31,6 +31,8 @@ #include #include +#include "thermal_hwmon.h" + #define IDLE_INTERVAL 5000 #define COMMON_STR 0x00 @@ -75,6 +77,8 @@ struct rcar_thermal_priv { #define rcar_priv_to_dev(priv) ((priv)->common->dev) #define rcar_has_irq_support(priv) ((priv)->common->base) #define rcar_id_to_shift(priv) ((priv)->id * 8) +#define rcar_of_data(dev) ((unsigned long)of_device_get_match_data(dev)) +#define rcar_use_of_thermal(dev) (rcar_of_data(dev) == USE_OF_THERMAL) #define USE_OF_THERMAL 1 static const struct of_device_id rcar_thermal_dt_ids[] = { @@ -416,6 +420,8 @@ static int rcar_thermal_remove(struct platform_device *pdev) rcar_thermal_for_each_priv(priv, common) { rcar_thermal_irq_disable(priv); thermal_zone_device_unregister(priv->zone); + if (rcar_use_of_thermal(dev)) + thermal_remove_hwmon_sysfs(priv->zone); } pm_runtime_put(dev); @@ -430,7 +436,6 @@ static int rcar_thermal_probe(struct platform_device *pdev) struct rcar_thermal_priv *priv; struct device *dev = &pdev->dev; struct resource *res, *irq; - unsigned long of_data = (unsigned long)of_device_get_match_data(dev); int mres = 0; int i; int ret = -ENODEV; @@ -491,7 +496,7 @@ static int rcar_thermal_probe(struct platform_device *pdev) if (ret < 0) goto error_unregister; - if (of_data == USE_OF_THERMAL) + if (rcar_use_of_thermal(dev)) priv->zone = devm_thermal_zone_of_sensor_register( dev, i, priv, &rcar_thermal_zone_of_ops); @@ -507,6 +512,17 @@ static int rcar_thermal_probe(struct platform_device *pdev) goto error_unregister; } + if (rcar_use_of_thermal(dev)) { + /* + * thermal_zone doesn't enable hwmon as default, + * but, enable it here to keep compatible + */ + priv->zone->tzp->no_hwmon = false; + ret = thermal_add_hwmon_sysfs(priv->zone); + if (ret) + goto error_unregister; + } + rcar_thermal_irq_enable(priv); list_move_tail(&priv->list, &common->head); -- cgit v0.10.2 From 43528445f6db0e934592a0c58b5a71d6be51aa41 Mon Sep 17 00:00:00 2001 From: Jia Hongtao Date: Thu, 30 Jun 2016 11:08:38 +0800 Subject: thermal: qoriq: Add thermal management support This driver add thermal management support by enabling TMU (Thermal Monitoring Unit) on QorIQ platform. It's based on thermal of framework: - Trip points defined in device tree. - Cpufreq as cooling device registered in qoriq cpufreq driver. Signed-off-by: Jia Hongtao Signed-off-by: Zhang Rui diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 8303823..94fa9e3 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -195,6 +195,16 @@ config IMX_THERMAL cpufreq is used as the cooling device to throttle CPUs when the passive trip is crossed. +config QORIQ_THERMAL + tristate "QorIQ Thermal Monitoring Unit" + depends on THERMAL_OF + depends on HAS_IOMEM + help + Support for Thermal Monitoring Unit (TMU) found on QorIQ platforms. + It supports one critical trip point and one passive trip point. The + cpufreq is used as the cooling device to throttle CPUs when the + passive trip is crossed. + config SPEAR_THERMAL tristate "SPEAr thermal sensor driver" depends on PLAT_SPEAR || COMPILE_TEST diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 431771e..fdb4b38 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o obj-$(CONFIG_TANGO_THERMAL) += tango_thermal.o obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o +obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c new file mode 100644 index 0000000..644ba52 --- /dev/null +++ b/drivers/thermal/qoriq_thermal.c @@ -0,0 +1,328 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "thermal_core.h" + +#define SITES_MAX 16 + +/* + * QorIQ TMU Registers + */ +struct qoriq_tmu_site_regs { + u32 tritsr; /* Immediate Temperature Site Register */ + u32 tratsr; /* Average Temperature Site Register */ + u8 res0[0x8]; +}; + +struct qoriq_tmu_regs { + u32 tmr; /* Mode Register */ +#define TMR_DISABLE 0x0 +#define TMR_ME 0x80000000 +#define TMR_ALPF 0x0c000000 + u32 tsr; /* Status Register */ + u32 tmtmir; /* Temperature measurement interval Register */ +#define TMTMIR_DEFAULT 0x0000000f + u8 res0[0x14]; + u32 tier; /* Interrupt Enable Register */ +#define TIER_DISABLE 0x0 + u32 tidr; /* Interrupt Detect Register */ + u32 tiscr; /* Interrupt Site Capture Register */ + u32 ticscr; /* Interrupt Critical Site Capture Register */ + u8 res1[0x10]; + u32 tmhtcrh; /* High Temperature Capture Register */ + u32 tmhtcrl; /* Low Temperature Capture Register */ + u8 res2[0x8]; + u32 tmhtitr; /* High Temperature Immediate Threshold */ + u32 tmhtatr; /* High Temperature Average Threshold */ + u32 tmhtactr; /* High Temperature Average Crit Threshold */ + u8 res3[0x24]; + u32 ttcfgr; /* Temperature Configuration Register */ + u32 tscfgr; /* Sensor Configuration Register */ + u8 res4[0x78]; + struct qoriq_tmu_site_regs site[SITES_MAX]; + u8 res5[0x9f8]; + u32 ipbrr0; /* IP Block Revision Register 0 */ + u32 ipbrr1; /* IP Block Revision Register 1 */ + u8 res6[0x310]; + u32 ttr0cr; /* Temperature Range 0 Control Register */ + u32 ttr1cr; /* Temperature Range 1 Control Register */ + u32 ttr2cr; /* Temperature Range 2 Control Register */ + u32 ttr3cr; /* Temperature Range 3 Control Register */ +}; + +/* + * Thermal zone data + */ +struct qoriq_tmu_data { + struct thermal_zone_device *tz; + struct qoriq_tmu_regs __iomem *regs; + int sensor_id; + bool little_endian; +}; + +static void tmu_write(struct qoriq_tmu_data *p, u32 val, void __iomem *addr) +{ + if (p->little_endian) + iowrite32(val, addr); + else + iowrite32be(val, addr); +} + +static u32 tmu_read(struct qoriq_tmu_data *p, void __iomem *addr) +{ + if (p->little_endian) + return ioread32(addr); + else + return ioread32be(addr); +} + +static int tmu_get_temp(void *p, int *temp) +{ + u32 val; + struct qoriq_tmu_data *data = p; + + val = tmu_read(data, &data->regs->site[data->sensor_id].tritsr); + *temp = (val & 0xff) * 1000; + + return 0; +} + +static int qoriq_tmu_get_sensor_id(void) +{ + int ret, id; + struct of_phandle_args sensor_specs; + struct device_node *np, *sensor_np; + + np = of_find_node_by_name(NULL, "thermal-zones"); + if (!np) + return -ENODEV; + + sensor_np = of_get_next_child(np, NULL); + ret = of_parse_phandle_with_args(sensor_np, "thermal-sensors", + "#thermal-sensor-cells", + 0, &sensor_specs); + if (ret) { + of_node_put(np); + of_node_put(sensor_np); + return ret; + } + + if (sensor_specs.args_count >= 1) { + id = sensor_specs.args[0]; + WARN(sensor_specs.args_count > 1, + "%s: too many cells in sensor specifier %d\n", + sensor_specs.np->name, sensor_specs.args_count); + } else { + id = 0; + } + + of_node_put(np); + of_node_put(sensor_np); + + return id; +} + +static int qoriq_tmu_calibration(struct platform_device *pdev) +{ + int i, val, len; + u32 range[4]; + const u32 *calibration; + struct device_node *np = pdev->dev.of_node; + struct qoriq_tmu_data *data = platform_get_drvdata(pdev); + + if (of_property_read_u32_array(np, "fsl,tmu-range", range, 4)) { + dev_err(&pdev->dev, "missing calibration range.\n"); + return -ENODEV; + } + + /* Init temperature range registers */ + tmu_write(data, range[0], &data->regs->ttr0cr); + tmu_write(data, range[1], &data->regs->ttr1cr); + tmu_write(data, range[2], &data->regs->ttr2cr); + tmu_write(data, range[3], &data->regs->ttr3cr); + + calibration = of_get_property(np, "fsl,tmu-calibration", &len); + if (calibration == NULL || len % 8) { + dev_err(&pdev->dev, "invalid calibration data.\n"); + return -ENODEV; + } + + for (i = 0; i < len; i += 8, calibration += 2) { + val = of_read_number(calibration, 1); + tmu_write(data, val, &data->regs->ttcfgr); + val = of_read_number(calibration + 1, 1); + tmu_write(data, val, &data->regs->tscfgr); + } + + return 0; +} + +static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) +{ + /* Disable interrupt, using polling instead */ + tmu_write(data, TIER_DISABLE, &data->regs->tier); + + /* Set update_interval */ + tmu_write(data, TMTMIR_DEFAULT, &data->regs->tmtmir); + + /* Disable monitoring */ + tmu_write(data, TMR_DISABLE, &data->regs->tmr); +} + +static struct thermal_zone_of_device_ops tmu_tz_ops = { + .get_temp = tmu_get_temp, +}; + +static int qoriq_tmu_probe(struct platform_device *pdev) +{ + int ret; + const struct thermal_trip *trip; + struct qoriq_tmu_data *data; + struct device_node *np = pdev->dev.of_node; + u32 site = 0; + + if (!np) { + dev_err(&pdev->dev, "Device OF-Node is NULL"); + return -ENODEV; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct qoriq_tmu_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + + data->little_endian = of_property_read_bool(np, "little-endian"); + + data->sensor_id = qoriq_tmu_get_sensor_id(); + if (data->sensor_id < 0) { + dev_err(&pdev->dev, "Failed to get sensor id\n"); + ret = -ENODEV; + goto err_iomap; + } + + data->regs = of_iomap(np, 0); + if (!data->regs) { + dev_err(&pdev->dev, "Failed to get memory region\n"); + ret = -ENODEV; + goto err_iomap; + } + + qoriq_tmu_init_device(data); /* TMU initialization */ + + ret = qoriq_tmu_calibration(pdev); /* TMU calibration */ + if (ret < 0) + goto err_tmu; + + data->tz = thermal_zone_of_sensor_register(&pdev->dev, data->sensor_id, + data, &tmu_tz_ops); + if (IS_ERR(data->tz)) { + ret = PTR_ERR(data->tz); + dev_err(&pdev->dev, + "Failed to register thermal zone device %d\n", ret); + goto err_tmu; + } + + trip = of_thermal_get_trip_points(data->tz); + + /* Enable monitoring */ + site |= 0x1 << (15 - data->sensor_id); + tmu_write(data, site | TMR_ME | TMR_ALPF, &data->regs->tmr); + + return 0; + +err_tmu: + iounmap(data->regs); + +err_iomap: + platform_set_drvdata(pdev, NULL); + + return ret; +} + +static int qoriq_tmu_remove(struct platform_device *pdev) +{ + struct qoriq_tmu_data *data = platform_get_drvdata(pdev); + + thermal_zone_of_sensor_unregister(&pdev->dev, data->tz); + + /* Disable monitoring */ + tmu_write(data, TMR_DISABLE, &data->regs->tmr); + + iounmap(data->regs); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int qoriq_tmu_suspend(struct device *dev) +{ + u32 tmr; + struct qoriq_tmu_data *data = dev_get_drvdata(dev); + + /* Disable monitoring */ + tmr = tmu_read(data, &data->regs->tmr); + tmr &= ~TMR_ME; + tmu_write(data, tmr, &data->regs->tmr); + + return 0; +} + +static int qoriq_tmu_resume(struct device *dev) +{ + u32 tmr; + struct qoriq_tmu_data *data = dev_get_drvdata(dev); + + /* Enable monitoring */ + tmr = tmu_read(data, &data->regs->tmr); + tmr |= TMR_ME; + tmu_write(data, tmr, &data->regs->tmr); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops, + qoriq_tmu_suspend, qoriq_tmu_resume); + +static const struct of_device_id qoriq_tmu_match[] = { + { .compatible = "fsl,qoriq-tmu", }, + {}, +}; +MODULE_DEVICE_TABLE(of, qoriq_tmu_match); + +static struct platform_driver qoriq_tmu = { + .driver = { + .name = "qoriq_thermal", + .pm = &qoriq_tmu_pm_ops, + .of_match_table = qoriq_tmu_match, + }, + .probe = qoriq_tmu_probe, + .remove = qoriq_tmu_remove, +}; +module_platform_driver(qoriq_tmu); + +MODULE_AUTHOR("Jia Hongtao "); +MODULE_DESCRIPTION("QorIQ Thermal Monitoring Unit driver"); +MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From 5b97469a55872a30a0d53a1279a8ae8b1c68b52c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 4 Jul 2016 15:12:28 +0200 Subject: thermal: qcom: tsens-8916: mark PM functions __maybe_unused The newly added tsens-8916 driver produces warnings when CONFIG_PM is disabled: drivers/thermal/qcom/tsens.c:53:12: error: 'tsens_resume' defined but not used [-Werror=unused-function] static int tsens_resume(struct device *dev) ^~~~~~~~~~~~ drivers/thermal/qcom/tsens.c:43:12: error: 'tsens_suspend' defined but not used [-Werror=unused-function] static int tsens_suspend(struct device *dev) ^~~~~~~~~~~~~ This marks both functions __maybe_unused to let the compiler know that they might be used in other configurations, without adding ugly #ifdef logic. Signed-off-by: Arnd Bergmann Reviewed-by: Rajendra Nayak Signed-off-by: Zhang Rui diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 446f70b..3f9fe6a 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -40,7 +40,7 @@ static int tsens_get_trend(void *p, int trip, enum thermal_trend *trend) return -ENOTSUPP; } -static int tsens_suspend(struct device *dev) +static int __maybe_unused tsens_suspend(struct device *dev) { struct tsens_device *tmdev = dev_get_drvdata(dev); @@ -50,7 +50,7 @@ static int tsens_suspend(struct device *dev) return 0; } -static int tsens_resume(struct device *dev) +static int __maybe_unused tsens_resume(struct device *dev) { struct tsens_device *tmdev = dev_get_drvdata(dev); -- cgit v0.10.2 From 23f75e4893b488c41a940f730ae7760376e02426 Mon Sep 17 00:00:00 2001 From: Rocky Hao Date: Wed, 27 Jul 2016 22:10:42 +0800 Subject: thermal: rockchip: enhance the tsadc's bandgap feature for rk3399 Due to the voltage ripple, the sensing data of the tsadc is not accurate. And in this patch, the bandgap feature is enhanced to remove the voltage ripple, and then the tsadc can sense the temperature more precisely. Obsolete codes are removed as well. Signed-off-by: Rocky Hao Signed-off-by: Caesar Wang Cc: Eduardo Valentin Cc: Zhang Rui Cc: Heiko Stuebner Cc: linux-pm@vger.kernel.org Tested-by: Stephen Barber Signed-off-by: Zhang Rui diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index db5ecc5..a90423d 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -222,10 +222,10 @@ struct rockchip_thermal_data { #define GRF_TSADC_TESTBIT_L 0x0e648 #define GRF_TSADC_TESTBIT_H 0x0e64c -#define GRF_TSADC_TSEN_PD_ON (0x30003 << 0) -#define GRF_TSADC_TSEN_PD_OFF (0x30000 << 0) #define GRF_SARADC_TESTBIT_ON (0x10001 << 2) #define GRF_TSADC_TESTBIT_H_ON (0x10001 << 2) +#define GRF_TSADC_VCM_EN_L (0x10001 << 7) +#define GRF_TSADC_VCM_EN_H (0x10001 << 7) /** * struct tsadc_table - code to temperature conversion table @@ -567,9 +567,10 @@ static void rk_tsadcv3_initialize(struct regmap *grf, void __iomem *regs, regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); } else { - regmap_write(grf, GRF_TSADC_TESTBIT_L, GRF_TSADC_TSEN_PD_ON); - mdelay(10); - regmap_write(grf, GRF_TSADC_TESTBIT_L, GRF_TSADC_TSEN_PD_OFF); + /* Enable the voltage common mode feature */ + regmap_write(grf, GRF_TSADC_TESTBIT_L, GRF_TSADC_VCM_EN_L); + regmap_write(grf, GRF_TSADC_TESTBIT_H, GRF_TSADC_VCM_EN_H); + usleep_range(15, 100); /* The spec note says at least 15 us */ regmap_write(grf, GRF_SARADC_TESTBIT, GRF_SARADC_TESTBIT_ON); regmap_write(grf, GRF_TSADC_TESTBIT_H, GRF_TSADC_TESTBIT_H_ON); -- cgit v0.10.2 From 5ef62de751dcd21b3853368ab47eedcbc1cf3475 Mon Sep 17 00:00:00 2001 From: Rocky Hao Date: Wed, 27 Jul 2016 22:10:43 +0800 Subject: thermal: rockchip: optimize sensor auto accessing period In less than 10 ms, the temperature of soc will arise 10 degree. 250 ms is too big for soc tempeture control. Setting 2.5 ms will speed up temperature accessing speed but introduce no more cpu's computing overhead. We set AUTO_PERIOD_TIME and TSADCV3_AUTO_PERIOD_HT_TIME the same value, because normal temperature update speed is also our consern in IPA. Signed-off-by: Rocky Hao Signed-off-by: Caesar Wang Cc: Zhang Rui Cc: Eduardo Valentin Cc: Heiko Stuebner Cc: linux-pm@vger.kernel.org Tested-by: Stephen Barber Signed-off-by: Zhang Rui diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index a90423d..1f165c9 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -213,8 +213,8 @@ struct rockchip_thermal_data { #define TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT 4 #define TSADCV2_AUTO_PERIOD_TIME 250 /* 250ms */ #define TSADCV2_AUTO_PERIOD_HT_TIME 50 /* 50ms */ -#define TSADCV3_AUTO_PERIOD_TIME 187500 /* 250ms */ -#define TSADCV3_AUTO_PERIOD_HT_TIME 37500 /* 50ms */ +#define TSADCV3_AUTO_PERIOD_TIME 1875 /* 2.5ms */ +#define TSADCV3_AUTO_PERIOD_HT_TIME 1875 /* 2.5ms */ #define TSADCV2_USER_INTER_PD_SOC 0x340 /* 13 clocks */ -- cgit v0.10.2 From 809eb35045097bc7da56fa8bba1f6b4cd1b721ba Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sat, 30 Jul 2016 06:32:37 +0000 Subject: thermal: qcom: tsens: Fix return value check in init_common() In case of error, the function of_iomap() returns NULL pointer not ERR_PTR(). The IS_ERR() test in the return value check should be replaced with NULL test. And the function devm_regmap_init_mmio() returns ERR_PTR() and never returns NULL. The NULL test in the return value check should be replaced with IS_ERR(). Signed-off-by: Wei Yongjun Acked-by: Rajendra Nayak Signed-off-by: Zhang Rui diff --git a/drivers/thermal/qcom/tsens-common.c b/drivers/thermal/qcom/tsens-common.c index 4a1af15..b1449ad 100644 --- a/drivers/thermal/qcom/tsens-common.c +++ b/drivers/thermal/qcom/tsens-common.c @@ -128,13 +128,13 @@ int __init init_common(struct tsens_device *tmdev) void __iomem *base; base = of_iomap(tmdev->dev->of_node, 0); - if (IS_ERR(base)) + if (!base) return -EINVAL; tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config); - if (!tmdev->map) { + if (IS_ERR(tmdev->map)) { iounmap(base); - return -ENODEV; + return PTR_ERR(tmdev->map); } return 0; -- cgit v0.10.2 From eea6a364393736693b2d2c7c10b8b7d194a5ca10 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Wed, 25 May 2016 14:45:50 +0100 Subject: thermal: imx: depend on imx SoC arch Not much use unless the SoC is selected so depend on the ARCH_MXC and COMPILE_TEST like all the other thermal drivers. v2: drop extraneous OF Signed-off-by: Peter Robinson Acked-by: Shawn Guo Signed-off-by: Zhang Rui diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 94fa9e3..900d505 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -186,7 +186,7 @@ config HISI_THERMAL config IMX_THERMAL tristate "Temperature sensor driver for Freescale i.MX SoCs" - depends on CPU_THERMAL + depends on (ARCH_MXC && CPU_THERMAL) || COMPILE_TEST depends on MFD_SYSCON depends on OF help -- cgit v0.10.2 From 7b5c4a0cd48dbdcdf545eff5a214b1862a398784 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Mon, 22 Aug 2016 15:48:11 +0800 Subject: Thermal: of thermal: typo fix Signed-off-by: Zhang Rui diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index 972b0cf..ab6e526 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -600,7 +600,7 @@ static int devm_thermal_zone_of_sensor_match(struct device *dev, void *res, * Return: On success returns a valid struct thermal_zone_device, * otherwise, it returns a corresponding ERR_PTR(). Caller must * check the return value with help of IS_ERR() helper. - * Registered hermal_zone_device device will automatically be + * Registered thermal_zone_device device will automatically be * released when device is unbounded. */ struct thermal_zone_device *devm_thermal_zone_of_sensor_register( -- cgit v0.10.2 From d4b23c5c434a7af053782cc0f9eebee51ec71bb2 Mon Sep 17 00:00:00 2001 From: Bui Duc Phuc Date: Mon, 22 Aug 2016 03:19:49 +0000 Subject: thermal: rcar_thermal: don't call thermal_zone_device_unregister when USE_OF_THERMAL devm_thermal_zone_of_sensor_register() case doesn't need to call thermal_zone_device_unregister(). Otherwise, rcar-thermal can't register thermal zone again after rebind. This patch fixes it. Signed-off-by: Bui Duc Phuc Signed-off-by: Kuninori Morimoto Signed-off-by: Zhang Rui diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index 4d07644..b5c6442 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -419,9 +419,10 @@ static int rcar_thermal_remove(struct platform_device *pdev) rcar_thermal_for_each_priv(priv, common) { rcar_thermal_irq_disable(priv); - thermal_zone_device_unregister(priv->zone); if (rcar_use_of_thermal(dev)) thermal_remove_hwmon_sysfs(priv->zone); + else + thermal_zone_device_unregister(priv->zone); } pm_runtime_put(dev); -- cgit v0.10.2 From e3da1cbed64a19babe91775e21fcd8887039e711 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 22 Aug 2016 16:08:06 +0800 Subject: devfreq_cooling: no need to check state with negative number We could see that state is defined as unsigned type, so it should never be less than zero. Let' remove this check. Signed-off-by: Shawn Lin Reviewed-by: Chanwoo Choi Signed-off-by: Zhang Rui diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index 01f0015..81631b1 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -312,7 +312,7 @@ static int devfreq_cooling_state2power(struct thermal_cooling_device *cdev, unsigned long freq; u32 static_power; - if (state < 0 || state >= dfc->freq_table_size) + if (state >= dfc->freq_table_size) return -EINVAL; freq = dfc->freq_table[state]; -- cgit v0.10.2 From 9dfe1a2f9a5a582931e9ecf3efd8dac5e1f89072 Mon Sep 17 00:00:00 2001 From: Marc Gonzalez Date: Fri, 2 Sep 2016 15:17:17 +0200 Subject: thermal: tango: add resume support When this platform is suspended, firmware powers the entire SoC down, except a few hardware blocks waiting for wakeup events. There is no context to save for this particular block. Therefore, there is nothing useful for the driver to do on suspend; so we define a NULL suspend hook. On resume, the driver initializes the block exactly as is done in the probe callback. Signed-off-by: Marc Gonzalez Reviewed-by: Kevin Hilman Signed-off-by: Zhang Rui diff --git a/drivers/thermal/tango_thermal.c b/drivers/thermal/tango_thermal.c index 70e0d9f..201304a 100644 --- a/drivers/thermal/tango_thermal.c +++ b/drivers/thermal/tango_thermal.c @@ -64,6 +64,12 @@ static const struct thermal_zone_of_device_ops ops = { .get_temp = tango_get_temp, }; +static void tango_thermal_init(struct tango_thermal_priv *priv) +{ + writel(0, priv->base + TEMPSI_CFG); + writel(CMD_ON, priv->base + TEMPSI_CMD); +} + static int tango_thermal_probe(struct platform_device *pdev) { struct resource *res; @@ -79,14 +85,22 @@ static int tango_thermal_probe(struct platform_device *pdev) if (IS_ERR(priv->base)) return PTR_ERR(priv->base); + platform_set_drvdata(pdev, priv); priv->thresh_idx = IDX_MIN; - writel(0, priv->base + TEMPSI_CFG); - writel(CMD_ON, priv->base + TEMPSI_CMD); + tango_thermal_init(priv); tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &ops); return PTR_ERR_OR_ZERO(tzdev); } +static int __maybe_unused tango_thermal_resume(struct device *dev) +{ + tango_thermal_init(dev_get_drvdata(dev)); + return 0; +} + +static SIMPLE_DEV_PM_OPS(tango_thermal_pm, NULL, tango_thermal_resume); + static const struct of_device_id tango_sensor_ids[] = { { .compatible = "sigma,smp8758-thermal", @@ -99,6 +113,7 @@ static struct platform_driver tango_thermal_driver = { .driver = { .name = "tango-thermal", .of_match_table = tango_sensor_ids, + .pm = &tango_thermal_pm, }, }; -- cgit v0.10.2 From 94c2004ef530a9692ae9f417241eb44a51723c51 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Tue, 23 Aug 2016 13:50:26 +0530 Subject: thermal: max77620: Add DT binding doc for thermal driver Maxim Semiconductor MAX77620 supports alarm interrupts when its die temperature crosses 120C and 140C. These threshold temperatures are not configurable. Add DT binding document to details out the DT property related to MAX77620 thermal functionality. Signed-off-by: Laxman Dewangan Acked-by: Rob Herring Signed-off-by: Zhang Rui diff --git a/Documentation/devicetree/bindings/thermal/max77620_thermal.txt b/Documentation/devicetree/bindings/thermal/max77620_thermal.txt new file mode 100644 index 0000000..323a3b3 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/max77620_thermal.txt @@ -0,0 +1,70 @@ +Thermal driver for MAX77620 Power management IC from Maxim Semiconductor. + +Maxim Semiconductor MAX77620 supports alarm interrupts when its +die temperature crosses 120C and 140C. These threshold temperatures +are not configurable. Device does not provide the real temperature +of die other than just indicating whether temperature is above or +below threshold level. + +Required properties: +------------------- +#thermal-sensor-cells: Please refer + for more details. + The value must be 0. + +For more details, please refer generic thermal DT binding document +. + +Please refer for mfd DT binding +document for the MAX77620. + +Example: +-------- +#include +#include +... + +i2c@7000d000 { + spmic: max77620@3c { + compatible = "maxim,max77620"; + ::::: + #thermal-sensor-cells = <0>; + ::: + }; +}; + +cool_dev: cool-dev { + compatible = "cooling-dev"; + #cooling-cells = <2>; +}; + +thermal-zones { + PMIC-Die { + polling-delay = <0>; + polling-delay-passive = <0>; + thermal-sensors = <&spmic>; + + trips { + pmic_die_warn_temp_thresh: hot-die { + temperature = <120000>; + type = "hot"; + hysteresis = <0>; + }; + + pmic_die_cirt_temp_thresh: cirtical-die { + temperature = <140000>; + type = "critical"; + hysteresis = <0>; + }; + }; + + cooling-maps { + map0 { + trip = <&pmic_die_warn_temp_thresh>; + cooling-device = <&cool_dev THERMAL_NO_LIMIT + THERMAL_NO_LIMIT>; + contribution = <100>; + }; + }; + }; +}; -- cgit v0.10.2 From ec4664b3fd6d565a79eb562e4339528f74eb1cca Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Tue, 23 Aug 2016 13:50:27 +0530 Subject: thermal: max77620: Add thermal driver for reporting junction temp Maxim Semiconductor Max77620 supports alarm interrupts when its die temperature crosses 120C and 140C. These threshold temperatures are not configurable. Add thermal driver to register PMIC die temperature as thermal zone sensor and capture the die temperature warning interrupts to notifying the client. Signed-off-by: Laxman Dewangan Signed-off-by: Zhang Rui diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 900d505..38a304e 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -195,6 +195,16 @@ config IMX_THERMAL cpufreq is used as the cooling device to throttle CPUs when the passive trip is crossed. +config MAX77620_THERMAL + tristate "Temperature sensor driver for Maxim MAX77620 PMIC" + depends on MFD_MAX77620 + depends on OF + help + Support for die junction temperature warning alarm for Maxim + Semiconductor PMIC MAX77620 device. Device generates two alarm + interrupts when PMIC die temperature cross the threshold of + 120 degC and 140 degC. + config QORIQ_THERMAL tristate "QorIQ Thermal Monitoring Unit" depends on THERMAL_OF diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index fdb4b38..7c1aca6 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o obj-$(CONFIG_TANGO_THERMAL) += tango_thermal.o obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o +obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c new file mode 100644 index 0000000..8d61626 --- /dev/null +++ b/drivers/thermal/max77620_thermal.c @@ -0,0 +1,165 @@ +/* + * Junction temperature thermal driver for Maxim Max77620. + * + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. + * + * Author: Laxman Dewangan + * Mallikarjun Kasoju + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX77620_NORMAL_OPERATING_TEMP 100000 +#define MAX77620_TJALARM1_TEMP 120000 +#define MAX77620_TJALARM2_TEMP 140000 + +struct max77620_therm_info { + struct device *dev; + struct regmap *rmap; + struct thermal_zone_device *tz_device; + int irq_tjalarm1; + int irq_tjalarm2; +}; + +/** + * max77620_thermal_read_temp: Read PMIC die temperatue. + * @data: Device specific data. + * temp: Temperature in millidegrees Celsius + * + * The actual temperature of PMIC die is not available from PMIC. + * PMIC only tells the status if it has crossed or not the threshold level + * of 120degC or 140degC. + * If threshold has not been crossed then assume die temperature as 100degC + * else 120degC or 140deG based on the PMIC die temp threshold status. + * + * Return 0 on success otherwise error number to show reason of failure. + */ + +static int max77620_thermal_read_temp(void *data, int *temp) +{ + struct max77620_therm_info *mtherm = data; + unsigned int val; + int ret; + + ret = regmap_read(mtherm->rmap, MAX77620_REG_STATLBT, &val); + if (ret < 0) { + dev_err(mtherm->dev, "Failed to read STATLBT: %d\n", ret); + return ret; + } + + if (val & MAX77620_IRQ_TJALRM2_MASK) + *temp = MAX77620_TJALARM2_TEMP; + else if (val & MAX77620_IRQ_TJALRM1_MASK) + *temp = MAX77620_TJALARM1_TEMP; + else + *temp = MAX77620_NORMAL_OPERATING_TEMP; + + return 0; +} + +static const struct thermal_zone_of_device_ops max77620_thermal_ops = { + .get_temp = max77620_thermal_read_temp, +}; + +static irqreturn_t max77620_thermal_irq(int irq, void *data) +{ + struct max77620_therm_info *mtherm = data; + + if (irq == mtherm->irq_tjalarm1) + dev_warn(mtherm->dev, "Junction Temp Alarm1(120C) occurred\n"); + else if (irq == mtherm->irq_tjalarm2) + dev_crit(mtherm->dev, "Junction Temp Alarm2(140C) occurred\n"); + + thermal_zone_device_update(mtherm->tz_device); + + return IRQ_HANDLED; +} + +static int max77620_thermal_probe(struct platform_device *pdev) +{ + struct max77620_therm_info *mtherm; + int ret; + + mtherm = devm_kzalloc(&pdev->dev, sizeof(*mtherm), GFP_KERNEL); + if (!mtherm) + return -ENOMEM; + + mtherm->irq_tjalarm1 = platform_get_irq(pdev, 0); + mtherm->irq_tjalarm2 = platform_get_irq(pdev, 1); + if ((mtherm->irq_tjalarm1 < 0) || (mtherm->irq_tjalarm2 < 0)) { + dev_err(&pdev->dev, "Alarm irq number not available\n"); + return -EINVAL; + } + + pdev->dev.of_node = pdev->dev.parent->of_node; + + mtherm->dev = &pdev->dev; + mtherm->rmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!mtherm->rmap) { + dev_err(&pdev->dev, "Failed to get parent regmap\n"); + return -ENODEV; + } + + mtherm->tz_device = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, + mtherm, &max77620_thermal_ops); + if (IS_ERR(mtherm->tz_device)) { + ret = PTR_ERR(mtherm->tz_device); + dev_err(&pdev->dev, "Failed to register thermal zone: %d\n", + ret); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm1, NULL, + max77620_thermal_irq, + IRQF_ONESHOT | IRQF_SHARED, + dev_name(&pdev->dev), mtherm); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request irq1: %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm2, NULL, + max77620_thermal_irq, + IRQF_ONESHOT | IRQF_SHARED, + dev_name(&pdev->dev), mtherm); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request irq2: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, mtherm); + + return 0; +} + +static struct platform_device_id max77620_thermal_devtype[] = { + { .name = "max77620-thermal", }, + {}, +}; + +static struct platform_driver max77620_thermal_driver = { + .driver = { + .name = "max77620-thermal", + }, + .probe = max77620_thermal_probe, + .id_table = max77620_thermal_devtype, +}; + +module_platform_driver(max77620_thermal_driver); + +MODULE_DESCRIPTION("Max77620 Junction temperature Thermal driver"); +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_AUTHOR("Mallikarjun Kasoju "); +MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From 77d6e7212cd53dd93de6fab26d7ca89c8d591c07 Mon Sep 17 00:00:00 2001 From: "dawei.chien@mediatek.com" Date: Thu, 18 Aug 2016 11:50:51 +0800 Subject: dt-bindings: thermal: Add binding document for Mediatek thermal controller This adds the device tree binding documentation for the mediatek thermal controller found on Mediatek MT2701. Signed-off-by: Dawei Chien Reviewed-by: Keerthy Acked-by: Rob Herring Signed-off-by: Zhang Rui diff --git a/Documentation/devicetree/bindings/thermal/mediatek-thermal.txt b/Documentation/devicetree/bindings/thermal/mediatek-thermal.txt index 81f9a51..e2f494d 100644 --- a/Documentation/devicetree/bindings/thermal/mediatek-thermal.txt +++ b/Documentation/devicetree/bindings/thermal/mediatek-thermal.txt @@ -8,7 +8,9 @@ apmixedsys register space via AHB bus accesses, so a phandle to the APMIXEDSYS is also needed. Required properties: -- compatible: "mediatek,mt8173-thermal" +- compatible: + - "mediatek,mt8173-thermal" : For MT8173 family of SoCs + - "mediatek,mt2701-thermal" : For MT2701 family of SoCs - reg: Address range of the thermal controller - interrupts: IRQ for the thermal controller - clocks, clock-names: Clocks needed for the thermal controller. required -- cgit v0.10.2 From b7cf0053738c5491df532a625321e976eaa93b22 Mon Sep 17 00:00:00 2001 From: "dawei.chien@mediatek.com" Date: Thu, 18 Aug 2016 11:50:52 +0800 Subject: thermal: Add Mediatek thermal driver for mt2701. This patch adds support for mt2701 chip to mtk_thermal, and integrate both mt8173 and mt2701 on the same driver. MT8173 has four banks and five sensors, and MT2701 has only one bank and three sensors. Signed-off-by: Dawei Chien Signed-off-by: Zhang Rui diff --git a/drivers/thermal/mtk_thermal.c b/drivers/thermal/mtk_thermal.c index 262ab0a..7b233c7 100644 --- a/drivers/thermal/mtk_thermal.c +++ b/drivers/thermal/mtk_thermal.c @@ -2,6 +2,7 @@ * Copyright (c) 2015 MediaTek Inc. * Author: Hanyi Wu * Sascha Hauer + * Dawei Chien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +90,7 @@ #define TEMP_ADCVALIDMASK_VALID_HIGH BIT(5) #define TEMP_ADCVALIDMASK_VALID_POS(bit) (bit) +/* MT8173 thermal sensors */ #define MT8173_TS1 0 #define MT8173_TS2 1 #define MT8173_TS3 2 @@ -106,7 +109,12 @@ /* The number of sensing points per bank */ #define MT8173_NUM_SENSORS_PER_ZONE 4 -/* Layout of the fuses providing the calibration data */ +/* + * Layout of the fuses providing the calibration data + * These macros could be used for both MT8173 and MT2701. + * MT8173 has five sensors and need five VTS calibration data, + * and MT2701 has three sensors and need three VTS calibration data. + */ #define MT8173_CALIB_BUF0_VALID BIT(0) #define MT8173_CALIB_BUF1_ADC_GE(x) (((x) >> 22) & 0x3ff) #define MT8173_CALIB_BUF0_VTS_TS1(x) (((x) >> 17) & 0x1ff) @@ -117,24 +125,50 @@ #define MT8173_CALIB_BUF0_DEGC_CALI(x) (((x) >> 1) & 0x3f) #define MT8173_CALIB_BUF0_O_SLOPE(x) (((x) >> 26) & 0x3f) +/* MT2701 thermal sensors */ +#define MT2701_TS1 0 +#define MT2701_TS2 1 +#define MT2701_TSABB 2 + +/* AUXADC channel 11 is used for the temperature sensors */ +#define MT2701_TEMP_AUXADC_CHANNEL 11 + +/* The total number of temperature sensors in the MT2701 */ +#define MT2701_NUM_SENSORS 3 + #define THERMAL_NAME "mtk-thermal" +/* The number of sensing points per bank */ +#define MT2701_NUM_SENSORS_PER_ZONE 3 + struct mtk_thermal; +struct thermal_bank_cfg { + unsigned int num_sensors; + const int *sensors; +}; + struct mtk_thermal_bank { struct mtk_thermal *mt; int id; }; +struct mtk_thermal_data { + s32 num_banks; + s32 num_sensors; + s32 auxadc_channel; + const int *sensor_mux_values; + const int *msr; + const int *adcpnp; + struct thermal_bank_cfg bank_data[]; +}; + struct mtk_thermal { struct device *dev; void __iomem *thermal_base; struct clk *clk_peri_therm; struct clk *clk_auxadc; - - struct mtk_thermal_bank banks[MT8173_NUM_ZONES]; - /* lock: for getting and putting banks */ struct mutex lock; @@ -144,16 +178,44 @@ struct mtk_thermal { s32 o_slope; s32 vts[MT8173_NUM_SENSORS]; + const struct mtk_thermal_data *conf; + struct mtk_thermal_bank banks[]; }; -struct mtk_thermal_bank_cfg { - unsigned int num_sensors; - unsigned int sensors[MT8173_NUM_SENSORS_PER_ZONE]; +/* MT8173 thermal sensor data */ +const int mt8173_bank_data[MT8173_NUM_ZONES][3] = { + { MT8173_TS2, MT8173_TS3 }, + { MT8173_TS2, MT8173_TS4 }, + { MT8173_TS1, MT8173_TS2, MT8173_TSABB }, + { MT8173_TS2 }, }; -static const int sensor_mux_values[MT8173_NUM_SENSORS] = { 0, 1, 2, 3, 16 }; +const int mt8173_msr[MT8173_NUM_SENSORS_PER_ZONE] = { + TEMP_MSR0, TEMP_MSR1, TEMP_MSR2, TEMP_MSR2 +}; -/* +const int mt8173_adcpnp[MT8173_NUM_SENSORS_PER_ZONE] = { + TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2, TEMP_ADCPNP3 +}; + +const int mt8173_mux_values[MT8173_NUM_SENSORS] = { 0, 1, 2, 3, 16 }; + +/* MT2701 thermal sensor data */ +const int mt2701_bank_data[MT2701_NUM_SENSORS] = { + MT2701_TS1, MT2701_TS2, MT2701_TSABB +}; + +const int mt2701_msr[MT2701_NUM_SENSORS_PER_ZONE] = { + TEMP_MSR0, TEMP_MSR1, TEMP_MSR2 +}; + +const int mt2701_adcpnp[MT2701_NUM_SENSORS_PER_ZONE] = { + TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2 +}; + +const int mt2701_mux_values[MT2701_NUM_SENSORS] = { 0, 1, 16 }; + +/** * The MT8173 thermal controller has four banks. Each bank can read up to * four temperature sensors simultaneously. The MT8173 has a total of 5 * temperature sensors. We use each bank to measure a certain area of the @@ -166,42 +228,53 @@ static const int sensor_mux_values[MT8173_NUM_SENSORS] = { 0, 1, 2, 3, 16 }; * data, and this indeed needs the temperatures of the individual banks * for making better decisions. */ -static const struct mtk_thermal_bank_cfg bank_data[] = { - { - .num_sensors = 2, - .sensors = { MT8173_TS2, MT8173_TS3 }, - }, { - .num_sensors = 2, - .sensors = { MT8173_TS2, MT8173_TS4 }, - }, { - .num_sensors = 3, - .sensors = { MT8173_TS1, MT8173_TS2, MT8173_TSABB }, - }, { - .num_sensors = 1, - .sensors = { MT8173_TS2 }, +static const struct mtk_thermal_data mt8173_thermal_data = { + .auxadc_channel = MT8173_TEMP_AUXADC_CHANNEL, + .num_banks = MT8173_NUM_ZONES, + .num_sensors = MT8173_NUM_SENSORS, + .bank_data = { + { + .num_sensors = 2, + .sensors = mt8173_bank_data[0], + }, { + .num_sensors = 2, + .sensors = mt8173_bank_data[1], + }, { + .num_sensors = 3, + .sensors = mt8173_bank_data[2], + }, { + .num_sensors = 1, + .sensors = mt8173_bank_data[3], + }, }, + .msr = mt8173_msr, + .adcpnp = mt8173_adcpnp, + .sensor_mux_values = mt8173_mux_values, }; -struct mtk_thermal_sense_point { - int msr; - int adcpnp; -}; - -static const struct mtk_thermal_sense_point - sensing_points[MT8173_NUM_SENSORS_PER_ZONE] = { - { - .msr = TEMP_MSR0, - .adcpnp = TEMP_ADCPNP0, - }, { - .msr = TEMP_MSR1, - .adcpnp = TEMP_ADCPNP1, - }, { - .msr = TEMP_MSR2, - .adcpnp = TEMP_ADCPNP2, - }, { - .msr = TEMP_MSR3, - .adcpnp = TEMP_ADCPNP3, +/** + * The MT2701 thermal controller has one bank, which can read up to + * three temperature sensors simultaneously. The MT2701 has a total of 3 + * temperature sensors. + * + * The thermal core only gets the maximum temperature of this one bank, + * so the bank concept wouldn't be necessary here. However, the SVS (Smart + * Voltage Scaling) unit makes its decisions based on the same bank + * data. + */ +static const struct mtk_thermal_data mt2701_thermal_data = { + .auxadc_channel = MT2701_TEMP_AUXADC_CHANNEL, + .num_banks = 1, + .num_sensors = MT2701_NUM_SENSORS, + .bank_data = { + { + .num_sensors = 3, + .sensors = mt2701_bank_data, + }, }, + .msr = mt2701_msr, + .adcpnp = mt2701_adcpnp, + .sensor_mux_values = mt2701_mux_values, }; /** @@ -270,13 +343,16 @@ static void mtk_thermal_put_bank(struct mtk_thermal_bank *bank) static int mtk_thermal_bank_temperature(struct mtk_thermal_bank *bank) { struct mtk_thermal *mt = bank->mt; + const struct mtk_thermal_data *conf = mt->conf; int i, temp = INT_MIN, max = INT_MIN; u32 raw; - for (i = 0; i < bank_data[bank->id].num_sensors; i++) { - raw = readl(mt->thermal_base + sensing_points[i].msr); + for (i = 0; i < conf->bank_data[bank->id].num_sensors; i++) { + raw = readl(mt->thermal_base + conf->msr[i]); - temp = raw_to_mcelsius(mt, bank_data[bank->id].sensors[i], raw); + temp = raw_to_mcelsius(mt, + conf->bank_data[bank->id].sensors[i], + raw); /* * The first read of a sensor often contains very high bogus @@ -299,7 +375,7 @@ static int mtk_read_temp(void *data, int *temperature) int i; int tempmax = INT_MIN; - for (i = 0; i < MT8173_NUM_ZONES; i++) { + for (i = 0; i < mt->conf->num_banks; i++) { struct mtk_thermal_bank *bank = &mt->banks[i]; mtk_thermal_get_bank(bank); @@ -322,7 +398,7 @@ static void mtk_thermal_init_bank(struct mtk_thermal *mt, int num, u32 apmixed_phys_base, u32 auxadc_phys_base) { struct mtk_thermal_bank *bank = &mt->banks[num]; - const struct mtk_thermal_bank_cfg *cfg = &bank_data[num]; + const struct mtk_thermal_data *conf = mt->conf; int i; bank->id = num; @@ -368,7 +444,7 @@ static void mtk_thermal_init_bank(struct mtk_thermal *mt, int num, * this value will be stored to TEMP_PNPMUXADDR (TEMP_SPARE0) * automatically by hw */ - writel(BIT(MT8173_TEMP_AUXADC_CHANNEL), mt->thermal_base + TEMP_ADCMUX); + writel(BIT(conf->auxadc_channel), mt->thermal_base + TEMP_ADCMUX); /* AHB address for auxadc mux selection */ writel(auxadc_phys_base + AUXADC_CON1_CLR_V, @@ -379,18 +455,18 @@ static void mtk_thermal_init_bank(struct mtk_thermal *mt, int num, mt->thermal_base + TEMP_PNPMUXADDR); /* AHB value for auxadc enable */ - writel(BIT(MT8173_TEMP_AUXADC_CHANNEL), mt->thermal_base + TEMP_ADCEN); + writel(BIT(conf->auxadc_channel), mt->thermal_base + TEMP_ADCEN); /* AHB address for auxadc enable (channel 0 immediate mode selected) */ writel(auxadc_phys_base + AUXADC_CON1_SET_V, mt->thermal_base + TEMP_ADCENADDR); /* AHB address for auxadc valid bit */ - writel(auxadc_phys_base + AUXADC_DATA(MT8173_TEMP_AUXADC_CHANNEL), + writel(auxadc_phys_base + AUXADC_DATA(conf->auxadc_channel), mt->thermal_base + TEMP_ADCVALIDADDR); /* AHB address for auxadc voltage output */ - writel(auxadc_phys_base + AUXADC_DATA(MT8173_TEMP_AUXADC_CHANNEL), + writel(auxadc_phys_base + AUXADC_DATA(conf->auxadc_channel), mt->thermal_base + TEMP_ADCVOLTADDR); /* read valid & voltage are at the same register */ @@ -407,11 +483,12 @@ static void mtk_thermal_init_bank(struct mtk_thermal *mt, int num, writel(TEMP_ADCWRITECTRL_ADC_MUX_WRITE, mt->thermal_base + TEMP_ADCWRITECTRL); - for (i = 0; i < cfg->num_sensors; i++) - writel(sensor_mux_values[cfg->sensors[i]], - mt->thermal_base + sensing_points[i].adcpnp); + for (i = 0; i < conf->bank_data[num].num_sensors; i++) + writel(conf->sensor_mux_values[conf->bank_data[num].sensors[i]], + mt->thermal_base + conf->adcpnp[i]); - writel((1 << cfg->num_sensors) - 1, mt->thermal_base + TEMP_MONCTL0); + writel((1 << conf->bank_data[num].num_sensors) - 1, + mt->thermal_base + TEMP_MONCTL0); writel(TEMP_ADCWRITECTRL_ADC_PNP_WRITE | TEMP_ADCWRITECTRL_ADC_MUX_WRITE, @@ -442,7 +519,7 @@ static int mtk_thermal_get_calibration_data(struct device *dev, /* Start with default values */ mt->adc_ge = 512; - for (i = 0; i < MT8173_NUM_SENSORS; i++) + for (i = 0; i < mt->conf->num_sensors; i++) mt->vts[i] = 260; mt->degc_cali = 40; mt->o_slope = 0; @@ -486,18 +563,36 @@ out: return ret; } +static const struct of_device_id mtk_thermal_of_match[] = { + { + .compatible = "mediatek,mt8173-thermal", + .data = (void *)&mt8173_thermal_data, + }, + { + .compatible = "mediatek,mt2701-thermal", + .data = (void *)&mt2701_thermal_data, + }, { + }, +}; +MODULE_DEVICE_TABLE(of, mtk_thermal_of_match); + static int mtk_thermal_probe(struct platform_device *pdev) { int ret, i; struct device_node *auxadc, *apmixedsys, *np = pdev->dev.of_node; struct mtk_thermal *mt; struct resource *res; + const struct of_device_id *of_id; u64 auxadc_phys_base, apmixed_phys_base; mt = devm_kzalloc(&pdev->dev, sizeof(*mt), GFP_KERNEL); if (!mt) return -ENOMEM; + of_id = of_match_device(mtk_thermal_of_match, &pdev->dev); + if (of_id) + mt->conf = (const struct mtk_thermal_data *)of_id->data; + mt->clk_peri_therm = devm_clk_get(&pdev->dev, "therm"); if (IS_ERR(mt->clk_peri_therm)) return PTR_ERR(mt->clk_peri_therm); @@ -565,7 +660,7 @@ static int mtk_thermal_probe(struct platform_device *pdev) goto err_disable_clk_auxadc; } - for (i = 0; i < MT8173_NUM_ZONES; i++) + for (i = 0; i < mt->conf->num_banks; i++) mtk_thermal_init_bank(mt, i, apmixed_phys_base, auxadc_phys_base); @@ -592,13 +687,6 @@ static int mtk_thermal_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id mtk_thermal_of_match[] = { - { - .compatible = "mediatek,mt8173-thermal", - }, { - }, -}; - static struct platform_driver mtk_thermal_driver = { .probe = mtk_thermal_probe, .remove = mtk_thermal_remove, @@ -610,6 +698,7 @@ static struct platform_driver mtk_thermal_driver = { module_platform_driver(mtk_thermal_driver); +MODULE_AUTHOR("Dawei Chien "); MODULE_AUTHOR("Sascha Hauer "); MODULE_AUTHOR("Hanyi Wu "); MODULE_DESCRIPTION("Mediatek thermal driver"); -- cgit v0.10.2 From 1f6b0889d0ea03541be03ccfc8d07deadae98283 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Wed, 7 Sep 2016 17:24:52 +0800 Subject: thermal: mtk_thermal: Check return value of devm_thermal_zone_of_sensor_register devm_thermal_zone_of_sensor_register can fail, so check it's return value. Signed-off-by: Axel Lin Reviewed-by: Matthias Brugger Signed-off-by: Zhang Rui diff --git a/drivers/thermal/mtk_thermal.c b/drivers/thermal/mtk_thermal.c index 7b233c7..34169c3 100644 --- a/drivers/thermal/mtk_thermal.c +++ b/drivers/thermal/mtk_thermal.c @@ -584,6 +584,7 @@ static int mtk_thermal_probe(struct platform_device *pdev) struct resource *res; const struct of_device_id *of_id; u64 auxadc_phys_base, apmixed_phys_base; + struct thermal_zone_device *tzdev; mt = devm_kzalloc(&pdev->dev, sizeof(*mt), GFP_KERNEL); if (!mt) @@ -666,11 +667,17 @@ static int mtk_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, mt); - devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mt, - &mtk_thermal_ops); + tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mt, + &mtk_thermal_ops); + if (IS_ERR(tzdev)) { + ret = PTR_ERR(tzdev); + goto err_disable_clk_peri_therm; + } return 0; +err_disable_clk_peri_therm: + clk_disable_unprepare(mt->clk_peri_therm); err_disable_clk_auxadc: clk_disable_unprepare(mt->clk_auxadc); -- cgit v0.10.2 From 44f3d4170e00debf154e778e7068e5d78c41dd82 Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:16 +0800 Subject: of: Add bindings of hw throttle for Tegra soctherm Add HW throttle configuration sub-node for soctherm, which is used to describe the throttle event, and worked as a cooling device. The "hot" type trip in thermal zone can be bound to this cooling device, and trigger the throttle function. Signed-off-by: Wei Ni Acked-by: Rob Herring Signed-off-by: Zhang Rui diff --git a/Documentation/devicetree/bindings/thermal/nvidia,tegra124-soctherm.txt b/Documentation/devicetree/bindings/thermal/nvidia,tegra124-soctherm.txt index edebfa0..b6c0ae5 100644 --- a/Documentation/devicetree/bindings/thermal/nvidia,tegra124-soctherm.txt +++ b/Documentation/devicetree/bindings/thermal/nvidia,tegra124-soctherm.txt @@ -10,8 +10,14 @@ Required properties : - compatible : For Tegra124, must contain "nvidia,tegra124-soctherm". For Tegra132, must contain "nvidia,tegra132-soctherm". For Tegra210, must contain "nvidia,tegra210-soctherm". -- reg : Should contain 1 entry: +- reg : Should contain at least 2 entries for each entry in reg-names: - SOCTHERM register set + - Tegra CAR register set: Required for Tegra124 and Tegra210. + - CCROC register set: Required for Tegra132. +- reg-names : Should contain at least 2 entries: + - soctherm-reg + - car-reg + - ccroc-reg - interrupts : Defines the interrupt used by SOCTHERM - clocks : Must contain an entry for each entry in clock-names. See ../clocks/clock-bindings.txt for details. @@ -25,17 +31,45 @@ Required properties : - #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description of this property. See for a list of valid values when referring to thermal sensors. +- throttle-cfgs: A sub-node which is a container of configuration for each + hardware throttle events. These events can be set as cooling devices. + * throttle events: Sub-nodes must be named as "light" or "heavy". + Properties: + - nvidia,priority: Each throttles has its own throttle settings, so the + SW need to set priorities for various throttle, the HW arbiter can select + the final throttle settings. + Bigger value indicates higher priority, In general, higher priority + translates to lower target frequency. SW needs to ensure that critical + thermal alarms are given higher priority, and ensure that there is + no race if priority of two vectors is set to the same value. + The range of this value is 1~100. + - nvidia,cpu-throt-percent: This property is for Tegra124 and Tegra210. + It is the throttling depth of pulse skippers, it's the percentage + throttling. + - nvidia,cpu-throt-level: This property is only for Tegra132, it is the + level of pulse skippers, which used to throttle clock frequencies. It + indicates cpu clock throttling depth, and the depth can be programmed. + Must set as following values: + TEGRA_SOCTHERM_THROT_LEVEL_LOW, TEGRA_SOCTHERM_THROT_LEVEL_MED + TEGRA_SOCTHERM_THROT_LEVEL_HIGH, TEGRA_SOCTHERM_THROT_LEVEL_NONE + - #cooling-cells: Should be 1. This cooling device only support on/off state. + See ./thermal.txt for a description of this property. Note: - the "critical" type trip points will be set to SOC_THERM hardware as the shut down temperature. Once the temperature of this thermal zone is higher than it, the system will be shutdown or reset by hardware. +- the "hot" type trip points will be set to SOC_THERM hardware as the throttle +temperature. Once the the temperature of this thermal zone is higher +than it, it will trigger the HW throttle event. Example : soctherm@700e2000 { compatible = "nvidia,tegra124-soctherm"; - reg = <0x0 0x700e2000 0x0 0x1000>; + reg = <0x0 0x700e2000 0x0 0x600 /* SOC_THERM reg_base */ + 0x0 0x60006000 0x0 0x400 /* CAR reg_base */ + reg-names = "soctherm-reg", "car-reg"; interrupts = ; clocks = <&tegra_car TEGRA124_CLK_TSENSOR>, <&tegra_car TEGRA124_CLK_SOC_THERM>; @@ -44,6 +78,76 @@ Example : reset-names = "soctherm"; #thermal-sensor-cells = <1>; + + throttle-cfgs { + /* + * When the "heavy" cooling device triggered, + * the HW will skip cpu clock's pulse in 85% depth + */ + throttle_heavy: heavy { + nvidia,priority = <100>; + nvidia,cpu-throt-percent = <85>; + + #cooling-cells = <1>; + }; + + /* + * When the "light" cooling device triggered, + * the HW will skip cpu clock's pulse in 50% depth + */ + throttle_light: light { + nvidia,priority = <80>; + nvidia,cpu-throt-percent = <50>; + + #cooling-cells = <1>; + }; + + /* + * If these two devices are triggered in same time, the HW throttle + * arbiter will select the highest priority as the final throttle + * settings to skip cpu pulse. + */ + }; + }; + +Example: referring to Tegra132's "reg", "reg-names" and "throttle-cfgs" : + + soctherm@700e2000 { + compatible = "nvidia,tegra132-soctherm"; + reg = <0x0 0x700e2000 0x0 0x600 /* SOC_THERM reg_base */ + 0x0 0x70040000 0x0 0x200>; /* CCROC reg_base */; + reg-names = "soctherm-reg", "ccroc-reg"; + + throttle-cfgs { + /* + * When the "heavy" cooling device triggered, + * the HW will skip cpu clock's pulse in HIGH level + */ + throttle_heavy: heavy { + nvidia,priority = <100>; + nvidia,cpu-throt-level = ; + + #cooling-cells = <1>; + }; + + /* + * When the "light" cooling device triggered, + * the HW will skip cpu clock's pulse in MED level + */ + throttle_light: light { + nvidia,priority = <80>; + nvidia,cpu-throt-level = ; + + #cooling-cells = <1>; + }; + + /* + * If these two devices are triggered in same time, the HW throttle + * arbiter will select the highest priority as the final throttle + * settings to skip cpu pulse. + */ + + }; }; Example: referring to thermal sensors : @@ -62,6 +166,19 @@ Example: referring to thermal sensors : hysteresis = <1000>; type = "critical"; }; + + cpu_throttle_trip: throttle-trip { + temperature = <100000>; + hysteresis = <1000>; + type = "hot"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu_throttle_trip>; + cooling-device = <&throttle_heavy 1 1>; + }; }; }; }; -- cgit v0.10.2 From ce0dbf04f685d48ffdec64a17ebb4965f828c3ff Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:17 +0800 Subject: thermal: tegra: add hw-throttle function Tegra soctherm support HW throttle, when the soctherm snesors' temperature is above the throttle trip point, it will trigger pulse skiper to tune clocks accroding to the throttle depth. Add this function for Tegra124 and Tegra210. Since Tegra132 use different registers to configure pulse skiper, will support it in next patch. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index b865172..dc75f92 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -30,6 +30,7 @@ #include +#include "../thermal_core.h" #include "soctherm.h" #define SENSOR_CONFIG0 0 @@ -67,35 +68,179 @@ #define READBACK_ADD_HALF BIT(7) #define READBACK_NEGATE BIT(0) +/* + * THERMCTL_LEVEL0_GROUP_CPU is defined in soctherm.h + * because it will be used by tegraxxx_soctherm.c + */ +#define THERMCTL_LVL0_CPU0_EN_MASK BIT(8) +#define THERMCTL_LVL0_CPU0_CPU_THROT_MASK (0x3 << 5) +#define THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT 0x1 +#define THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY 0x2 +#define THERMCTL_LVL0_CPU0_GPU_THROT_MASK (0x3 << 3) +#define THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT 0x1 +#define THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY 0x2 +#define THERMCTL_LVL0_CPU0_MEM_THROT_MASK BIT(2) +#define THERMCTL_LVL0_CPU0_STATUS_MASK 0x3 + +#define THERMCTL_LVL0_UP_STATS 0x10 +#define THERMCTL_LVL0_DN_STATS 0x14 + +#define THERMCTL_STATS_CTL 0x94 +#define STATS_CTL_CLR_DN 0x8 +#define STATS_CTL_EN_DN 0x4 +#define STATS_CTL_CLR_UP 0x2 +#define STATS_CTL_EN_UP 0x1 + +#define THROT_GLOBAL_CFG 0x400 +#define THROT_GLOBAL_ENB_MASK BIT(0) + +#define CPU_PSKIP_STATUS 0x418 +#define XPU_PSKIP_STATUS_M_MASK (0xff << 12) +#define XPU_PSKIP_STATUS_N_MASK (0xff << 4) +#define XPU_PSKIP_STATUS_SW_OVERRIDE_MASK BIT(1) +#define XPU_PSKIP_STATUS_ENABLED_MASK BIT(0) + +#define THROT_PRIORITY_LOCK 0x424 +#define THROT_PRIORITY_LOCK_PRIORITY_MASK 0xff + +#define THROT_STATUS 0x428 +#define THROT_STATUS_BREACH_MASK BIT(12) +#define THROT_STATUS_STATE_MASK (0xff << 4) +#define THROT_STATUS_ENABLED_MASK BIT(0) + +#define THROT_PSKIP_CTRL_LITE_CPU 0x430 +#define THROT_PSKIP_CTRL_ENABLE_MASK BIT(31) +#define THROT_PSKIP_CTRL_DIVIDEND_MASK (0xff << 8) +#define THROT_PSKIP_CTRL_DIVISOR_MASK 0xff +#define THROT_PSKIP_CTRL_VECT_GPU_MASK (0x7 << 16) +#define THROT_PSKIP_CTRL_VECT_CPU_MASK (0x7 << 8) +#define THROT_PSKIP_CTRL_VECT2_CPU_MASK 0x7 + +#define THROT_VECT_NONE 0x0 /* 3'b000 */ +#define THROT_VECT_LOW 0x1 /* 3'b001 */ +#define THROT_VECT_MED 0x3 /* 3'b011 */ +#define THROT_VECT_HIGH 0x7 /* 3'b111 */ + +#define THROT_PSKIP_RAMP_LITE_CPU 0x434 +#define THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK BIT(31) +#define THROT_PSKIP_RAMP_DURATION_MASK (0xffff << 8) +#define THROT_PSKIP_RAMP_STEP_MASK 0xff + +#define THROT_PRIORITY_LITE 0x444 +#define THROT_PRIORITY_LITE_PRIO_MASK 0xff + +#define THROT_DELAY_LITE 0x448 +#define THROT_DELAY_LITE_DELAY_MASK 0xff + +/* car register offsets needed for enabling HW throttling */ +#define CAR_SUPER_CCLKG_DIVIDER 0x36c +#define CDIVG_USE_THERM_CONTROLS_MASK BIT(30) + /* get val from register(r) mask bits(m) */ #define REG_GET_MASK(r, m) (((r) & (m)) >> (ffs(m) - 1)) /* set val(v) to mask bits(m) of register(r) */ #define REG_SET_MASK(r, m, v) (((r) & ~(m)) | \ (((v) & (m >> (ffs(m) - 1))) << (ffs(m) - 1))) +/* get dividend from the depth */ +#define THROT_DEPTH_DIVIDEND(depth) ((256 * (100 - (depth)) / 100) - 1) + +/* get THROT_PSKIP_xxx offset per LIGHT/HEAVY throt and CPU/GPU dev */ +#define THROT_OFFSET 0x30 +#define THROT_PSKIP_CTRL(throt, dev) (THROT_PSKIP_CTRL_LITE_CPU + \ + (THROT_OFFSET * throt) + (8 * dev)) +#define THROT_PSKIP_RAMP(throt, dev) (THROT_PSKIP_RAMP_LITE_CPU + \ + (THROT_OFFSET * throt) + (8 * dev)) + +/* get THROT_xxx_CTRL offset per LIGHT/HEAVY throt */ +#define THROT_PRIORITY_CTRL(throt) (THROT_PRIORITY_LITE + \ + (THROT_OFFSET * throt)) +#define THROT_DELAY_CTRL(throt) (THROT_DELAY_LITE + \ + (THROT_OFFSET * throt)) + +/* get THERMCTL_LEVELx offset per CPU/GPU/MEM/TSENSE rg and LEVEL0~3 lv */ +#define THERMCTL_LVL_REGS_SIZE 0x20 +#define THERMCTL_LVL_REG(rg, lv) ((rg) + ((lv) * THERMCTL_LVL_REGS_SIZE)) + static const int min_low_temp = -127000; static const int max_high_temp = 127000; +enum soctherm_throttle_id { + THROTTLE_LIGHT = 0, + THROTTLE_HEAVY, + THROTTLE_SIZE, +}; + +enum soctherm_throttle_dev_id { + THROTTLE_DEV_CPU = 0, + THROTTLE_DEV_GPU, + THROTTLE_DEV_SIZE, +}; + +static const char *const throt_names[] = { + [THROTTLE_LIGHT] = "light", + [THROTTLE_HEAVY] = "heavy", +}; + +struct tegra_soctherm; struct tegra_thermctl_zone { void __iomem *reg; struct device *dev; + struct tegra_soctherm *ts; struct thermal_zone_device *tz; const struct tegra_tsensor_group *sg; }; +struct soctherm_throt_cfg { + const char *name; + unsigned int id; + u8 priority; + u32 cpu_throt_depth; + struct thermal_cooling_device *cdev; + bool init; +}; + struct tegra_soctherm { struct reset_control *reset; struct clk *clock_tsensor; struct clk *clock_soctherm; void __iomem *regs; - struct thermal_zone_device **thermctl_tzs; + void __iomem *clk_regs; u32 *calib; + struct thermal_zone_device **thermctl_tzs; struct tegra_soctherm_soc *soc; + struct soctherm_throt_cfg throt_cfgs[THROTTLE_SIZE]; + struct dentry *debugfs_dir; }; +/** + * clk_writel() - writes a value to a CAR register + * @ts: pointer to a struct tegra_soctherm + * @v: the value to write + * @reg: the register offset + * + * Writes @v to @reg. No return value. + */ +static inline void clk_writel(struct tegra_soctherm *ts, u32 value, u32 reg) +{ + writel(value, (ts->clk_regs + reg)); +} + +/** + * clk_readl() - reads specified register from CAR IP block + * @ts: pointer to a struct tegra_soctherm + * @reg: register address to be read + * + * Return: the value of the register + */ +static inline u32 clk_readl(struct tegra_soctherm *ts, u32 reg) +{ + return readl(ts->clk_regs + reg); +} + static void enable_tsensor(struct tegra_soctherm *tegra, unsigned int i) { const struct tegra_tsensor *sensor = &tegra->soc->tsensors[i]; @@ -150,11 +295,17 @@ static int tegra_thermctl_get_temp(void *data, int *out_temp) static int thermtrip_program(struct device *dev, const struct tegra_tsensor_group *sg, int trip_temp); +static int +throttrip_program(struct device *dev, const struct tegra_tsensor_group *sg, + struct soctherm_throt_cfg *stc, int trip_temp); +static struct soctherm_throt_cfg * +find_throttle_cfg_by_name(struct tegra_soctherm *ts, const char *name); static int tegra_thermctl_set_trip_temp(void *data, int trip, int temp) { struct tegra_thermctl_zone *zone = data; struct thermal_zone_device *tz = zone->tz; + struct tegra_soctherm *ts = zone->ts; const struct tegra_tsensor_group *sg = zone->sg; struct device *dev = zone->dev; enum thermal_trip_type type; @@ -167,10 +318,29 @@ static int tegra_thermctl_set_trip_temp(void *data, int trip, int temp) if (ret) return ret; - if (type != THERMAL_TRIP_CRITICAL) - return 0; + if (type == THERMAL_TRIP_CRITICAL) { + return thermtrip_program(dev, sg, temp); + } else if (type == THERMAL_TRIP_HOT) { + int i; + + for (i = 0; i < THROTTLE_SIZE; i++) { + struct thermal_cooling_device *cdev; + struct soctherm_throt_cfg *stc; - return thermtrip_program(dev, sg, temp); + if (!ts->throt_cfgs[i].init) + continue; + + cdev = ts->throt_cfgs[i].cdev; + if (get_thermal_instance(tz, cdev, trip)) + stc = find_throttle_cfg_by_name(ts, cdev->type); + else + continue; + + return throttrip_program(dev, sg, stc, temp); + } + } + + return 0; } static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = { @@ -238,14 +408,110 @@ static int thermtrip_program(struct device *dev, } /** + * throttrip_program() - Configures the hardware to throttle the + * pulse if a given sensor group reaches a given temperature + * @dev: ptr to the struct device for the SOC_THERM IP block + * @sg: pointer to the sensor group to set the thermtrip temperature for + * @stc: pointer to the throttle need to be triggered + * @trip_temp: the temperature in millicelsius to trigger the thermal trip at + * + * Sets the thermal trip threshold and throttle event of the given sensor + * group. If this threshold is crossed, the hardware will trigger the + * throttle. + * + * Note that, although @trip_temp is specified in millicelsius, the + * hardware is programmed in degrees Celsius. + * + * Return: 0 upon success, or %-EINVAL upon failure. + */ +static int throttrip_program(struct device *dev, + const struct tegra_tsensor_group *sg, + struct soctherm_throt_cfg *stc, + int trip_temp) +{ + struct tegra_soctherm *ts = dev_get_drvdata(dev); + int temp, cpu_throt, gpu_throt; + unsigned int throt; + u32 r, reg_off; + + if (!dev || !sg || !stc || !stc->init) + return -EINVAL; + + temp = enforce_temp_range(dev, trip_temp) / ts->soc->thresh_grain; + + /* Hardcode LIGHT on LEVEL1 and HEAVY on LEVEL2 */ + throt = stc->id; + reg_off = THERMCTL_LVL_REG(sg->thermctl_lvl0_offset, throt + 1); + + if (throt == THROTTLE_LIGHT) { + cpu_throt = THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT; + gpu_throt = THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT; + } else { + cpu_throt = THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY; + gpu_throt = THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY; + if (throt != THROTTLE_HEAVY) + dev_warn(dev, + "invalid throt id %d - assuming HEAVY", + throt); + } + + r = readl(ts->regs + reg_off); + r = REG_SET_MASK(r, sg->thermctl_lvl0_up_thresh_mask, temp); + r = REG_SET_MASK(r, sg->thermctl_lvl0_dn_thresh_mask, temp); + r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_CPU_THROT_MASK, cpu_throt); + r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_GPU_THROT_MASK, gpu_throt); + r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_EN_MASK, 1); + writel(r, ts->regs + reg_off); + + return 0; +} + +static struct soctherm_throt_cfg * +find_throttle_cfg_by_name(struct tegra_soctherm *ts, const char *name) +{ + unsigned int i; + + for (i = 0; ts->throt_cfgs[i].name; i++) + if (!strcmp(ts->throt_cfgs[i].name, name)) + return &ts->throt_cfgs[i]; + + return NULL; +} + +static int get_hot_temp(struct thermal_zone_device *tz, int *trip, int *temp) +{ + int ntrips, i, ret; + enum thermal_trip_type type; + + ntrips = of_thermal_get_ntrips(tz); + if (ntrips <= 0) + return -EINVAL; + + for (i = 0; i < ntrips; i++) { + ret = tz->ops->get_trip_type(tz, i, &type); + if (ret) + return -EINVAL; + if (type == THERMAL_TRIP_HOT) { + ret = tz->ops->get_trip_temp(tz, i, temp); + if (!ret) + *trip = i; + + return ret; + } + } + + return -EINVAL; +} + +/** * tegra_soctherm_set_hwtrips() - set HW trip point from DT data * @dev: struct device * of the SOC_THERM instance * * Configure the SOC_THERM HW trip points, setting "THERMTRIP" - * trip points , using "critical" type trip_temp from thermal - * zone. - * After they have been configured, THERMTRIP will take action - * when the configured SoC thermal sensor group reaches a + * "THROTTLE" trip points , using "critical" or "hot" type trip_temp + * from thermal zone. + * After they have been configured, THERMTRIP or THROTTLE will take + * action when the configured SoC thermal sensor group reaches a * certain temperature. * * Return: 0 upon success, or a negative error code on failure. @@ -254,19 +520,24 @@ static int thermtrip_program(struct device *dev, * THERMTRIP has been enabled successfully when a message similar to * this one appears on the serial console: * "thermtrip: will shut down when sensor group XXX reaches YYYYYY mC" + * THROTTLE has been enabled successfully when a message similar to + * this one appears on the serial console: + * ""throttrip: will throttle when sensor group XXX reaches YYYYYY mC" */ static int tegra_soctherm_set_hwtrips(struct device *dev, const struct tegra_tsensor_group *sg, struct thermal_zone_device *tz) { - int temperature; + struct tegra_soctherm *ts = dev_get_drvdata(dev); + struct soctherm_throt_cfg *stc; + int i, trip, temperature; int ret; ret = tz->ops->get_crit_temp(tz, &temperature); if (ret) { dev_warn(dev, "thermtrip: %s: missing critical temperature\n", sg->name); - return ret; + goto set_throttle; } ret = thermtrip_program(dev, sg, temperature); @@ -280,6 +551,46 @@ static int tegra_soctherm_set_hwtrips(struct device *dev, "thermtrip: will shut down when %s reaches %d mC\n", sg->name, temperature); +set_throttle: + if (ts->soc->use_ccroc) + return 0; + + ret = get_hot_temp(tz, &trip, &temperature); + if (ret) { + dev_warn(dev, "throttrip: %s: missing hot temperature\n", + sg->name); + return 0; + } + + for (i = 0; i < THROTTLE_SIZE; i++) { + struct thermal_cooling_device *cdev; + + if (!ts->throt_cfgs[i].init) + continue; + + cdev = ts->throt_cfgs[i].cdev; + if (get_thermal_instance(tz, cdev, trip)) + stc = find_throttle_cfg_by_name(ts, cdev->type); + else + continue; + + ret = throttrip_program(dev, sg, stc, temperature); + if (ret) { + dev_err(dev, "throttrip: %s: error during enable\n", + sg->name); + return ret; + } + + dev_info(dev, + "throttrip: will throttle when %s reaches %d mC\n", + sg->name, temperature); + break; + } + + if (i == THROTTLE_SIZE) + dev_warn(dev, "throttrip: %s: missing throttle cdev\n", + sg->name); + return 0; } @@ -291,7 +602,7 @@ static int regs_show(struct seq_file *s, void *data) const struct tegra_tsensor *tsensors = ts->soc->tsensors; const struct tegra_tsensor_group **ttgs = ts->soc->ttgs; u32 r, state; - int i; + int i, level; seq_puts(s, "-----TSENSE (convert HW)-----\n"); @@ -365,6 +676,84 @@ static int regs_show(struct seq_file *s, void *data) state = REG_GET_MASK(r, SENSOR_TEMP2_MEM_TEMP_MASK); seq_printf(s, " MEM(%d)\n", translate_temp(state)); + if (ts->soc->use_ccroc) + return 0; + + for (i = 0; i < ts->soc->num_ttgs; i++) { + seq_printf(s, "%s:\n", ttgs[i]->name); + for (level = 0; level < 4; level++) { + s32 v; + u32 mask; + u16 off = ttgs[i]->thermctl_lvl0_offset; + + r = readl(ts->regs + THERMCTL_LVL_REG(off, level)); + + mask = ttgs[i]->thermctl_lvl0_up_thresh_mask; + state = REG_GET_MASK(r, mask); + v = sign_extend32(state, ts->soc->bptt - 1); + v *= ts->soc->thresh_grain; + seq_printf(s, " %d: Up/Dn(%d /", level, v); + + mask = ttgs[i]->thermctl_lvl0_dn_thresh_mask; + state = REG_GET_MASK(r, mask); + v = sign_extend32(state, ts->soc->bptt - 1); + v *= ts->soc->thresh_grain; + seq_printf(s, "%d ) ", v); + + mask = THERMCTL_LVL0_CPU0_EN_MASK; + state = REG_GET_MASK(r, mask); + seq_printf(s, "En(%d) ", state); + + mask = THERMCTL_LVL0_CPU0_CPU_THROT_MASK; + state = REG_GET_MASK(r, mask); + seq_puts(s, "CPU Throt"); + if (!state) + seq_printf(s, "(%s) ", "none"); + else if (state == THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT) + seq_printf(s, "(%s) ", "L"); + else if (state == THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY) + seq_printf(s, "(%s) ", "H"); + else + seq_printf(s, "(%s) ", "H+L"); + + mask = THERMCTL_LVL0_CPU0_GPU_THROT_MASK; + state = REG_GET_MASK(r, mask); + seq_puts(s, "GPU Throt"); + if (!state) + seq_printf(s, "(%s) ", "none"); + else if (state == THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT) + seq_printf(s, "(%s) ", "L"); + else if (state == THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY) + seq_printf(s, "(%s) ", "H"); + else + seq_printf(s, "(%s) ", "H+L"); + + mask = THERMCTL_LVL0_CPU0_STATUS_MASK; + state = REG_GET_MASK(r, mask); + seq_printf(s, "Status(%s)\n", + state == 0 ? "LO" : + state == 1 ? "In" : + state == 2 ? "Res" : "HI"); + } + } + + r = readl(ts->regs + THERMCTL_STATS_CTL); + seq_printf(s, "STATS: Up(%s) Dn(%s)\n", + r & STATS_CTL_EN_UP ? "En" : "--", + r & STATS_CTL_EN_DN ? "En" : "--"); + + for (level = 0; level < 4; level++) { + u16 off; + + off = THERMCTL_LVL0_UP_STATS; + r = readl(ts->regs + THERMCTL_LVL_REG(off, level)); + seq_printf(s, " Level_%d Up(%d) ", level, r); + + off = THERMCTL_LVL0_DN_STATS; + r = readl(ts->regs + THERMCTL_LVL_REG(off, level)); + seq_printf(s, "Dn(%d)\n", r); + } + r = readl(ts->regs + THERMCTL_THERMTRIP_CTL); state = REG_GET_MASK(r, ttgs[0]->thermtrip_any_en_mask); seq_printf(s, "Thermtrip Any En(%d)\n", state); @@ -376,6 +765,27 @@ static int regs_show(struct seq_file *s, void *data) seq_printf(s, "Thresh(%d)\n", state); } + r = readl(ts->regs + THROT_GLOBAL_CFG); + seq_puts(s, "\n"); + seq_printf(s, "GLOBAL THROTTLE CONFIG: 0x%08x\n", r); + + seq_puts(s, "---------------------------------------------------\n"); + r = readl(ts->regs + THROT_STATUS); + state = REG_GET_MASK(r, THROT_STATUS_BREACH_MASK); + seq_printf(s, "THROT STATUS: breach(%d) ", state); + state = REG_GET_MASK(r, THROT_STATUS_STATE_MASK); + seq_printf(s, "state(%d) ", state); + state = REG_GET_MASK(r, THROT_STATUS_ENABLED_MASK); + seq_printf(s, "enabled(%d)\n", state); + + r = readl(ts->regs + CPU_PSKIP_STATUS); + state = REG_GET_MASK(r, XPU_PSKIP_STATUS_M_MASK); + seq_printf(s, "CPU PSKIP STATUS: M(%d) ", state); + state = REG_GET_MASK(r, XPU_PSKIP_STATUS_N_MASK); + seq_printf(s, "N(%d) ", state); + state = REG_GET_MASK(r, XPU_PSKIP_STATUS_ENABLED_MASK); + seq_printf(s, "enabled(%d)\n", state); + return 0; } @@ -449,6 +859,204 @@ static int soctherm_clk_enable(struct platform_device *pdev, bool enable) return 0; } +static int throt_get_cdev_max_state(struct thermal_cooling_device *cdev, + unsigned long *max_state) +{ + *max_state = 1; + return 0; +} + +static int throt_get_cdev_cur_state(struct thermal_cooling_device *cdev, + unsigned long *cur_state) +{ + struct tegra_soctherm *ts = cdev->devdata; + u32 r; + + r = readl(ts->regs + THROT_STATUS); + if (REG_GET_MASK(r, THROT_STATUS_STATE_MASK)) + *cur_state = 1; + else + *cur_state = 0; + + return 0; +} + +static int throt_set_cdev_state(struct thermal_cooling_device *cdev, + unsigned long cur_state) +{ + return 0; +} + +static struct thermal_cooling_device_ops throt_cooling_ops = { + .get_max_state = throt_get_cdev_max_state, + .get_cur_state = throt_get_cdev_cur_state, + .set_cur_state = throt_set_cdev_state, +}; + +/** + * soctherm_init_hw_throt_cdev() - Parse the HW throttle configurations + * and register them as cooling devices. + */ +static void soctherm_init_hw_throt_cdev(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra_soctherm *ts = dev_get_drvdata(dev); + struct device_node *np_stc, *np_stcc; + const char *name; + u32 val; + int i, r; + + for (i = 0; i < THROTTLE_SIZE; i++) { + ts->throt_cfgs[i].name = throt_names[i]; + ts->throt_cfgs[i].id = i; + ts->throt_cfgs[i].init = false; + } + + np_stc = of_get_child_by_name(dev->of_node, "throttle-cfgs"); + if (!np_stc) { + dev_info(dev, + "throttle-cfg: no throttle-cfgs - not enabling\n"); + return; + } + + for_each_child_of_node(np_stc, np_stcc) { + struct soctherm_throt_cfg *stc; + struct thermal_cooling_device *tcd; + + name = np_stcc->name; + stc = find_throttle_cfg_by_name(ts, name); + if (!stc) { + dev_err(dev, + "throttle-cfg: could not find %s\n", name); + continue; + } + + r = of_property_read_u32(np_stcc, "nvidia,priority", &val); + if (r) { + dev_info(dev, + "throttle-cfg: %s: missing priority\n", name); + continue; + } + stc->priority = val; + + r = of_property_read_u32(np_stcc, "nvidia,cpu-throt-percent", + &val); + if (r) { + dev_info(dev, + "throttle-cfg: %s: missing cpu-throt-percent\n", + name); + continue; + } + stc->cpu_throt_depth = val; + + tcd = thermal_of_cooling_device_register(np_stcc, + (char *)name, ts, + &throt_cooling_ops); + of_node_put(np_stcc); + if (IS_ERR_OR_NULL(tcd)) { + dev_err(dev, + "throttle-cfg: %s: failed to register cooling device\n", + name); + continue; + } + + stc->cdev = tcd; + stc->init = true; + } + + of_node_put(np_stc); +} + +/** + * throttlectl_cpu_mn() - program CPU pulse skipper configuration + * @throt: the LIGHT/HEAVY of throttle event id + * + * Pulse skippers are used to throttle clock frequencies. This + * function programs the pulse skippers based on @throt and platform + * data. This function is used for CPUs that have "remote" pulse + * skipper control, e.g., the CPU pulse skipper is controlled by the + * SOC_THERM IP block. (SOC_THERM is located outside the CPU + * complex.) + */ +static void throttlectl_cpu_mn(struct tegra_soctherm *ts, + enum soctherm_throttle_id throt) +{ + u32 r; + int depth; + u8 dividend; + + depth = ts->throt_cfgs[throt].cpu_throt_depth; + dividend = THROT_DEPTH_DIVIDEND(depth); + + r = readl(ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); + r = REG_SET_MASK(r, THROT_PSKIP_CTRL_ENABLE_MASK, 1); + r = REG_SET_MASK(r, THROT_PSKIP_CTRL_DIVIDEND_MASK, dividend); + r = REG_SET_MASK(r, THROT_PSKIP_CTRL_DIVISOR_MASK, 0xff); + writel(r, ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); + + r = readl(ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); + r = REG_SET_MASK(r, THROT_PSKIP_RAMP_DURATION_MASK, 0xff); + r = REG_SET_MASK(r, THROT_PSKIP_RAMP_STEP_MASK, 0xf); + writel(r, ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); +} + +/** + * soctherm_throttle_program() - programs pulse skippers' configuration + * @throt: the LIGHT/HEAVY of the throttle event id. + * + * Pulse skippers are used to throttle clock frequencies. + * This function programs the pulse skippers. + */ +static void soctherm_throttle_program(struct tegra_soctherm *ts, + enum soctherm_throttle_id throt) +{ + u32 r; + struct soctherm_throt_cfg stc = ts->throt_cfgs[throt]; + + if (!stc.init) + return; + + /* Setup PSKIP parameters */ + throttlectl_cpu_mn(ts, throt); + + r = REG_SET_MASK(0, THROT_PRIORITY_LITE_PRIO_MASK, stc.priority); + writel(r, ts->regs + THROT_PRIORITY_CTRL(throt)); + + r = REG_SET_MASK(0, THROT_DELAY_LITE_DELAY_MASK, 0); + writel(r, ts->regs + THROT_DELAY_CTRL(throt)); + + r = readl(ts->regs + THROT_PRIORITY_LOCK); + r = REG_GET_MASK(r, THROT_PRIORITY_LOCK_PRIORITY_MASK); + if (r >= stc.priority) + return; + r = REG_SET_MASK(0, THROT_PRIORITY_LOCK_PRIORITY_MASK, + stc.priority); + writel(r, ts->regs + THROT_PRIORITY_LOCK); +} + +static void tegra_soctherm_throttle(struct device *dev) +{ + struct tegra_soctherm *ts = dev_get_drvdata(dev); + u32 v; + int i; + + /* Thermal HW throttle programming */ + for (i = 0; i < THROTTLE_SIZE; i++) + soctherm_throttle_program(ts, i); + + v = REG_SET_MASK(0, THROT_GLOBAL_ENB_MASK, 1); + writel(v, ts->regs + THROT_GLOBAL_CFG); + + v = clk_readl(ts, CAR_SUPER_CCLKG_DIVIDER); + v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1); + clk_writel(ts, v, CAR_SUPER_CCLKG_DIVIDER); + + /* initialize stats collection */ + v = STATS_CTL_CLR_DN | STATS_CTL_EN_DN | + STATS_CTL_CLR_UP | STATS_CTL_EN_UP; + writel(v, ts->regs + THERMCTL_STATS_CTL); +} + static void soctherm_init(struct platform_device *pdev) { struct tegra_soctherm *tegra = platform_get_drvdata(pdev); @@ -475,6 +1083,12 @@ static void soctherm_init(struct platform_device *pdev) } writel(pdiv, tegra->regs + SENSOR_PDIV); writel(hotspot, tegra->regs + SENSOR_HOTSPOT_OFF); + + if (tegra->soc->use_ccroc) + return; + + /* Configure hw throttle */ + tegra_soctherm_throttle(&pdev->dev); } static const struct of_device_id tegra_soctherm_of_match[] = { @@ -527,10 +1141,23 @@ static int tegra_soctherm_probe(struct platform_device *pdev) tegra->soc = soc; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "soctherm-reg"); tegra->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(tegra->regs)) + if (IS_ERR(tegra->regs)) { + dev_err(&pdev->dev, "can't get soctherm registers"); return PTR_ERR(tegra->regs); + } + + if (!tegra->soc->use_ccroc) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "car-reg"); + tegra->clk_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tegra->clk_regs)) { + dev_err(&pdev->dev, "can't get car clk registers"); + return PTR_ERR(tegra->clk_regs); + } + } tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm"); if (IS_ERR(tegra->reset)) { @@ -580,6 +1207,9 @@ static int tegra_soctherm_probe(struct platform_device *pdev) if (err) return err; + if (!tegra->soc->use_ccroc) + soctherm_init_hw_throt_cdev(pdev); + soctherm_init(pdev); for (i = 0; i < soc->num_ttgs; ++i) { @@ -593,6 +1223,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev) zone->reg = tegra->regs + soc->ttgs[i]->sensor_temp_offset; zone->dev = &pdev->dev; zone->sg = soc->ttgs[i]; + zone->ts = tegra; z = devm_thermal_zone_of_sensor_register(&pdev->dev, soc->ttgs[i]->id, zone, @@ -608,7 +1239,9 @@ static int tegra_soctherm_probe(struct platform_device *pdev) tegra->thermctl_tzs[soc->ttgs[i]->id] = z; /* Configure hw trip points */ - tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z); + err = tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z); + if (err) + goto disable_clocks; } soctherm_debug_init(pdev); @@ -661,7 +1294,12 @@ static int __maybe_unused soctherm_resume(struct device *dev) struct thermal_zone_device *tz; tz = tegra->thermctl_tzs[soc->ttgs[i]->id]; - tegra_soctherm_set_hwtrips(dev, soc->ttgs[i], tz); + err = tegra_soctherm_set_hwtrips(dev, soc->ttgs[i], tz); + if (err) { + dev_err(&pdev->dev, + "Resume failed: set hwtrips failed\n"); + return err; + } } return 0; diff --git a/drivers/thermal/tegra/soctherm.h b/drivers/thermal/tegra/soctherm.h index 28e18ec..e96ca73 100644 --- a/drivers/thermal/tegra/soctherm.h +++ b/drivers/thermal/tegra/soctherm.h @@ -15,6 +15,11 @@ #ifndef __DRIVERS_THERMAL_TEGRA_SOCTHERM_H #define __DRIVERS_THERMAL_TEGRA_SOCTHERM_H +#define THERMCTL_LEVEL0_GROUP_CPU 0x0 +#define THERMCTL_LEVEL0_GROUP_GPU 0x4 +#define THERMCTL_LEVEL0_GROUP_MEM 0x8 +#define THERMCTL_LEVEL0_GROUP_TSENSE 0xc + #define SENSOR_CONFIG2 8 #define SENSOR_CONFIG2_THERMA_MASK (0xffff << 16) #define SENSOR_CONFIG2_THERMA_SHIFT 16 @@ -65,6 +70,9 @@ struct tegra_tsensor_group { u32 thermtrip_enable_mask; u32 thermtrip_any_en_mask; u32 thermtrip_threshold_mask; + u16 thermctl_lvl0_offset; + u32 thermctl_lvl0_up_thresh_mask; + u32 thermctl_lvl0_dn_thresh_mask; }; struct tegra_tsensor_configuration { @@ -103,6 +111,8 @@ struct tegra_soctherm_soc { const unsigned int num_ttgs; const struct tegra_soctherm_fuse *tfuse; const int thresh_grain; + const unsigned int bptt; + const bool use_ccroc; }; int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse, diff --git a/drivers/thermal/tegra/tegra124-soctherm.c b/drivers/thermal/tegra/tegra124-soctherm.c index beb9d36..3676863 100644 --- a/drivers/thermal/tegra/tegra124-soctherm.c +++ b/drivers/thermal/tegra/tegra124-soctherm.c @@ -28,7 +28,11 @@ #define TEGRA124_THERMTRIP_CPU_THRESH_MASK (0xff << 8) #define TEGRA124_THERMTRIP_TSENSE_THRESH_MASK 0xff +#define TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK (0xff << 17) +#define TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK (0xff << 9) + #define TEGRA124_THRESH_GRAIN 1000 +#define TEGRA124_BPTT 8 static const struct tegra_tsensor_configuration tegra124_tsensor_config = { .tall = 16300, @@ -51,6 +55,9 @@ static const struct tegra_tsensor_group tegra124_tsensor_group_cpu = { .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA124_THERMTRIP_CPU_EN_MASK, .thermtrip_threshold_mask = TEGRA124_THERMTRIP_CPU_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_CPU, + .thermctl_lvl0_up_thresh_mask = TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra124_tsensor_group_gpu = { @@ -66,6 +73,9 @@ static const struct tegra_tsensor_group tegra124_tsensor_group_gpu = { .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA124_THERMTRIP_GPU_EN_MASK, .thermtrip_threshold_mask = TEGRA124_THERMTRIP_GPUMEM_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_GPU, + .thermctl_lvl0_up_thresh_mask = TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra124_tsensor_group_pll = { @@ -79,6 +89,9 @@ static const struct tegra_tsensor_group tegra124_tsensor_group_pll = { .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA124_THERMTRIP_TSENSE_EN_MASK, .thermtrip_threshold_mask = TEGRA124_THERMTRIP_TSENSE_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_TSENSE, + .thermctl_lvl0_up_thresh_mask = TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra124_tsensor_group_mem = { @@ -94,6 +107,9 @@ static const struct tegra_tsensor_group tegra124_tsensor_group_mem = { .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA124_THERMTRIP_MEM_EN_MASK, .thermtrip_threshold_mask = TEGRA124_THERMTRIP_GPUMEM_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_MEM, + .thermctl_lvl0_up_thresh_mask = TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group *tegra124_tsensor_groups[] = { @@ -193,4 +209,6 @@ const struct tegra_soctherm_soc tegra124_soctherm = { .num_ttgs = ARRAY_SIZE(tegra124_tsensor_groups), .tfuse = &tegra124_soctherm_fuse, .thresh_grain = TEGRA124_THRESH_GRAIN, + .bptt = TEGRA124_BPTT, + .use_ccroc = false, }; diff --git a/drivers/thermal/tegra/tegra132-soctherm.c b/drivers/thermal/tegra/tegra132-soctherm.c index e2aa84e..ae5d61a 100644 --- a/drivers/thermal/tegra/tegra132-soctherm.c +++ b/drivers/thermal/tegra/tegra132-soctherm.c @@ -193,4 +193,5 @@ const struct tegra_soctherm_soc tegra132_soctherm = { .num_ttgs = ARRAY_SIZE(tegra132_tsensor_groups), .tfuse = &tegra132_soctherm_fuse, .thresh_grain = TEGRA132_THRESH_GRAIN, + .use_ccroc = true, }; diff --git a/drivers/thermal/tegra/tegra210-soctherm.c b/drivers/thermal/tegra/tegra210-soctherm.c index 19cc0ab..ad53169 100644 --- a/drivers/thermal/tegra/tegra210-soctherm.c +++ b/drivers/thermal/tegra/tegra210-soctherm.c @@ -29,7 +29,11 @@ #define TEGRA210_THERMTRIP_CPU_THRESH_MASK (0x1ff << 9) #define TEGRA210_THERMTRIP_TSENSE_THRESH_MASK 0x1ff +#define TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK (0x1ff << 18) +#define TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK (0x1ff << 9) + #define TEGRA210_THRESH_GRAIN 500 +#define TEGRA210_BPTT 9 static const struct tegra_tsensor_configuration tegra210_tsensor_config = { .tall = 16300, @@ -52,6 +56,9 @@ static const struct tegra_tsensor_group tegra210_tsensor_group_cpu = { .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA210_THERMTRIP_CPU_EN_MASK, .thermtrip_threshold_mask = TEGRA210_THERMTRIP_CPU_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_CPU, + .thermctl_lvl0_up_thresh_mask = TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra210_tsensor_group_gpu = { @@ -67,6 +74,9 @@ static const struct tegra_tsensor_group tegra210_tsensor_group_gpu = { .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA210_THERMTRIP_GPU_EN_MASK, .thermtrip_threshold_mask = TEGRA210_THERMTRIP_GPUMEM_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_GPU, + .thermctl_lvl0_up_thresh_mask = TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra210_tsensor_group_pll = { @@ -80,6 +90,9 @@ static const struct tegra_tsensor_group tegra210_tsensor_group_pll = { .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA210_THERMTRIP_TSENSE_EN_MASK, .thermtrip_threshold_mask = TEGRA210_THERMTRIP_TSENSE_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_TSENSE, + .thermctl_lvl0_up_thresh_mask = TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra210_tsensor_group_mem = { @@ -95,6 +108,9 @@ static const struct tegra_tsensor_group tegra210_tsensor_group_mem = { .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA210_THERMTRIP_MEM_EN_MASK, .thermtrip_threshold_mask = TEGRA210_THERMTRIP_GPUMEM_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_MEM, + .thermctl_lvl0_up_thresh_mask = TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group *tegra210_tsensor_groups[] = { @@ -194,4 +210,6 @@ const struct tegra_soctherm_soc tegra210_soctherm = { .num_ttgs = ARRAY_SIZE(tegra210_tsensor_groups), .tfuse = &tegra210_soctherm_fuse, .thresh_grain = TEGRA210_THRESH_GRAIN, + .bptt = TEGRA210_BPTT, + .use_ccroc = false, }; -- cgit v0.10.2 From 6c7c324570847a459c21e7298bc5c92a40577103 Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:18 +0800 Subject: thermal: tegra: add hw-throttle for Tegra132 Tegra132 use CCROC throttle registers to configure pulse skiper, set these registers to enable throttle function for Tegra132. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index dc75f92..7d2db23 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -136,6 +136,21 @@ #define CAR_SUPER_CCLKG_DIVIDER 0x36c #define CDIVG_USE_THERM_CONTROLS_MASK BIT(30) +/* ccroc register offsets needed for enabling HW throttling for Tegra132 */ +#define CCROC_SUPER_CCLKG_DIVIDER 0x024 + +#define CCROC_GLOBAL_CFG 0x148 + +#define CCROC_THROT_PSKIP_RAMP_CPU 0x150 +#define CCROC_THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK BIT(31) +#define CCROC_THROT_PSKIP_RAMP_DURATION_MASK (0xffff << 8) +#define CCROC_THROT_PSKIP_RAMP_STEP_MASK 0xff + +#define CCROC_THROT_PSKIP_CTRL_CPU 0x154 +#define CCROC_THROT_PSKIP_CTRL_ENB_MASK BIT(31) +#define CCROC_THROT_PSKIP_CTRL_DIVIDEND_MASK (0xff << 8) +#define CCROC_THROT_PSKIP_CTRL_DIVISOR_MASK 0xff + /* get val from register(r) mask bits(m) */ #define REG_GET_MASK(r, m) (((r) & (m)) >> (ffs(m) - 1)) /* set val(v) to mask bits(m) of register(r) */ @@ -158,6 +173,13 @@ #define THROT_DELAY_CTRL(throt) (THROT_DELAY_LITE + \ (THROT_OFFSET * throt)) +/* get CCROC_THROT_PSKIP_xxx offset per HIGH/MED/LOW vect*/ +#define CCROC_THROT_OFFSET 0x0c +#define CCROC_THROT_PSKIP_CTRL_CPU_REG(vect) (CCROC_THROT_PSKIP_CTRL_CPU + \ + (CCROC_THROT_OFFSET * vect)) +#define CCROC_THROT_PSKIP_RAMP_CPU_REG(vect) (CCROC_THROT_PSKIP_RAMP_CPU + \ + (CCROC_THROT_OFFSET * vect)) + /* get THERMCTL_LEVELx offset per CPU/GPU/MEM/TSENSE rg and LEVEL0~3 lv */ #define THERMCTL_LVL_REGS_SIZE 0x20 #define THERMCTL_LVL_REG(rg, lv) ((rg) + ((lv) * THERMCTL_LVL_REGS_SIZE)) @@ -195,6 +217,7 @@ struct soctherm_throt_cfg { const char *name; unsigned int id; u8 priority; + u8 cpu_throt_level; u32 cpu_throt_depth; struct thermal_cooling_device *cdev; bool init; @@ -206,6 +229,7 @@ struct tegra_soctherm { struct clk *clock_soctherm; void __iomem *regs; void __iomem *clk_regs; + void __iomem *ccroc_regs; u32 *calib; struct thermal_zone_device **thermctl_tzs; @@ -241,6 +265,31 @@ static inline u32 clk_readl(struct tegra_soctherm *ts, u32 reg) return readl(ts->clk_regs + reg); } +/** + * ccroc_writel() - writes a value to a CCROC register + * @ts: pointer to a struct tegra_soctherm + * @v: the value to write + * @reg: the register offset + * + * Writes @v to @reg. No return value. + */ +static inline void ccroc_writel(struct tegra_soctherm *ts, u32 value, u32 reg) +{ + writel(value, (ts->ccroc_regs + reg)); +} + +/** + * ccroc_readl() - reads specified register from CCROC IP block + * @ts: pointer to a struct tegra_soctherm + * @reg: register address to be read + * + * Return: the value of the register + */ +static inline u32 ccroc_readl(struct tegra_soctherm *ts, u32 reg) +{ + return readl(ts->ccroc_regs + reg); +} + static void enable_tsensor(struct tegra_soctherm *tegra, unsigned int i) { const struct tegra_tsensor *sensor = &tegra->soc->tsensors[i]; @@ -552,9 +601,6 @@ static int tegra_soctherm_set_hwtrips(struct device *dev, sg->name, temperature); set_throttle: - if (ts->soc->use_ccroc) - return 0; - ret = get_hot_temp(tz, &trip, &temperature); if (ret) { dev_warn(dev, "throttrip: %s: missing hot temperature\n", @@ -676,9 +722,6 @@ static int regs_show(struct seq_file *s, void *data) state = REG_GET_MASK(r, SENSOR_TEMP2_MEM_TEMP_MASK); seq_printf(s, " MEM(%d)\n", translate_temp(state)); - if (ts->soc->use_ccroc) - return 0; - for (i = 0; i < ts->soc->num_ttgs; i++) { seq_printf(s, "%s:\n", ttgs[i]->name); for (level = 0; level < 4; level++) { @@ -779,12 +822,17 @@ static int regs_show(struct seq_file *s, void *data) seq_printf(s, "enabled(%d)\n", state); r = readl(ts->regs + CPU_PSKIP_STATUS); - state = REG_GET_MASK(r, XPU_PSKIP_STATUS_M_MASK); - seq_printf(s, "CPU PSKIP STATUS: M(%d) ", state); - state = REG_GET_MASK(r, XPU_PSKIP_STATUS_N_MASK); - seq_printf(s, "N(%d) ", state); - state = REG_GET_MASK(r, XPU_PSKIP_STATUS_ENABLED_MASK); - seq_printf(s, "enabled(%d)\n", state); + if (ts->soc->use_ccroc) { + state = REG_GET_MASK(r, XPU_PSKIP_STATUS_ENABLED_MASK); + seq_printf(s, "CPU PSKIP STATUS: enabled(%d)\n", state); + } else { + state = REG_GET_MASK(r, XPU_PSKIP_STATUS_M_MASK); + seq_printf(s, "CPU PSKIP STATUS: M(%d) ", state); + state = REG_GET_MASK(r, XPU_PSKIP_STATUS_N_MASK); + seq_printf(s, "N(%d) ", state); + state = REG_GET_MASK(r, XPU_PSKIP_STATUS_ENABLED_MASK); + seq_printf(s, "enabled(%d)\n", state); + } return 0; } @@ -939,15 +987,29 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev) } stc->priority = val; - r = of_property_read_u32(np_stcc, "nvidia,cpu-throt-percent", - &val); - if (r) { - dev_info(dev, - "throttle-cfg: %s: missing cpu-throt-percent\n", - name); - continue; + if (ts->soc->use_ccroc) { + r = of_property_read_u32(np_stcc, + "nvidia,cpu-throt-level", + &val); + if (r) { + dev_info(dev, + "throttle-cfg: %s: missing cpu-throt-level\n", + name); + continue; + } + stc->cpu_throt_level = val; + } else { + r = of_property_read_u32(np_stcc, + "nvidia,cpu-throt-percent", + &val); + if (r) { + dev_info(dev, + "throttle-cfg: %s: missing cpu-throt-percent\n", + name); + continue; + } + stc->cpu_throt_depth = val; } - stc->cpu_throt_depth = val; tcd = thermal_of_cooling_device_register(np_stcc, (char *)name, ts, @@ -968,6 +1030,96 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev) } /** + * throttlectl_cpu_level_cfg() - programs CCROC NV_THERM level config + * @level: describing the level LOW/MED/HIGH of throttling + * + * It's necessary to set up the CPU-local CCROC NV_THERM instance with + * the M/N values desired for each level. This function does this. + * + * This function pre-programs the CCROC NV_THERM levels in terms of + * pre-configured "Low", "Medium" or "Heavy" throttle levels which are + * mapped to THROT_LEVEL_LOW, THROT_LEVEL_MED and THROT_LEVEL_HVY. + */ +static void throttlectl_cpu_level_cfg(struct tegra_soctherm *ts, int level) +{ + u8 depth, dividend; + u32 r; + + switch (level) { + case TEGRA_SOCTHERM_THROT_LEVEL_LOW: + depth = 50; + break; + case TEGRA_SOCTHERM_THROT_LEVEL_MED: + depth = 75; + break; + case TEGRA_SOCTHERM_THROT_LEVEL_HIGH: + depth = 80; + break; + case TEGRA_SOCTHERM_THROT_LEVEL_NONE: + return; + default: + return; + } + + dividend = THROT_DEPTH_DIVIDEND(depth); + + /* setup PSKIP in ccroc nv_therm registers */ + r = ccroc_readl(ts, CCROC_THROT_PSKIP_RAMP_CPU_REG(level)); + r = REG_SET_MASK(r, CCROC_THROT_PSKIP_RAMP_DURATION_MASK, 0xff); + r = REG_SET_MASK(r, CCROC_THROT_PSKIP_RAMP_STEP_MASK, 0xf); + ccroc_writel(ts, r, CCROC_THROT_PSKIP_RAMP_CPU_REG(level)); + + r = ccroc_readl(ts, CCROC_THROT_PSKIP_CTRL_CPU_REG(level)); + r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_ENB_MASK, 1); + r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_DIVIDEND_MASK, dividend); + r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_DIVISOR_MASK, 0xff); + ccroc_writel(ts, r, CCROC_THROT_PSKIP_CTRL_CPU_REG(level)); +} + +/** + * throttlectl_cpu_level_select() - program CPU pulse skipper config + * @throt: the LIGHT/HEAVY of throttle event id + * + * Pulse skippers are used to throttle clock frequencies. This + * function programs the pulse skippers based on @throt and platform + * data. This function is used on SoCs which have CPU-local pulse + * skipper control, such as T13x. It programs soctherm's interface to + * Denver:CCROC NV_THERM in terms of Low, Medium and HIGH throttling + * vectors. PSKIP_BYPASS mode is set as required per HW spec. + */ +static void throttlectl_cpu_level_select(struct tegra_soctherm *ts, + enum soctherm_throttle_id throt) +{ + u32 r, throt_vect; + + /* Denver:CCROC NV_THERM interface N:3 Mapping */ + switch (ts->throt_cfgs[throt].cpu_throt_level) { + case TEGRA_SOCTHERM_THROT_LEVEL_LOW: + throt_vect = THROT_VECT_LOW; + break; + case TEGRA_SOCTHERM_THROT_LEVEL_MED: + throt_vect = THROT_VECT_MED; + break; + case TEGRA_SOCTHERM_THROT_LEVEL_HIGH: + throt_vect = THROT_VECT_HIGH; + break; + default: + throt_vect = THROT_VECT_NONE; + break; + } + + r = readl(ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); + r = REG_SET_MASK(r, THROT_PSKIP_CTRL_ENABLE_MASK, 1); + r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT_CPU_MASK, throt_vect); + r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT2_CPU_MASK, throt_vect); + writel(r, ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); + + /* bypass sequencer in soc_therm as it is programmed in ccroc */ + r = REG_SET_MASK(0, THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK, 1); + writel(r, ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); +} + +/** * throttlectl_cpu_mn() - program CPU pulse skipper configuration * @throt: the LIGHT/HEAVY of throttle event id * @@ -1017,7 +1169,10 @@ static void soctherm_throttle_program(struct tegra_soctherm *ts, return; /* Setup PSKIP parameters */ - throttlectl_cpu_mn(ts, throt); + if (ts->soc->use_ccroc) + throttlectl_cpu_level_select(ts, throt); + else + throttlectl_cpu_mn(ts, throt); r = REG_SET_MASK(0, THROT_PRIORITY_LITE_PRIO_MASK, stc.priority); writel(r, ts->regs + THROT_PRIORITY_CTRL(throt)); @@ -1040,16 +1195,31 @@ static void tegra_soctherm_throttle(struct device *dev) u32 v; int i; + /* configure LOW, MED and HIGH levels for CCROC NV_THERM */ + if (ts->soc->use_ccroc) { + throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_LOW); + throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_MED); + throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_HIGH); + } + /* Thermal HW throttle programming */ for (i = 0; i < THROTTLE_SIZE; i++) soctherm_throttle_program(ts, i); v = REG_SET_MASK(0, THROT_GLOBAL_ENB_MASK, 1); - writel(v, ts->regs + THROT_GLOBAL_CFG); + if (ts->soc->use_ccroc) { + ccroc_writel(ts, v, CCROC_GLOBAL_CFG); - v = clk_readl(ts, CAR_SUPER_CCLKG_DIVIDER); - v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1); - clk_writel(ts, v, CAR_SUPER_CCLKG_DIVIDER); + v = ccroc_readl(ts, CCROC_SUPER_CCLKG_DIVIDER); + v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1); + ccroc_writel(ts, v, CCROC_SUPER_CCLKG_DIVIDER); + } else { + writel(v, ts->regs + THROT_GLOBAL_CFG); + + v = clk_readl(ts, CAR_SUPER_CCLKG_DIVIDER); + v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1); + clk_writel(ts, v, CAR_SUPER_CCLKG_DIVIDER); + } /* initialize stats collection */ v = STATS_CTL_CLR_DN | STATS_CTL_EN_DN | @@ -1084,9 +1254,6 @@ static void soctherm_init(struct platform_device *pdev) writel(pdiv, tegra->regs + SENSOR_PDIV); writel(hotspot, tegra->regs + SENSOR_HOTSPOT_OFF); - if (tegra->soc->use_ccroc) - return; - /* Configure hw throttle */ tegra_soctherm_throttle(&pdev->dev); } @@ -1157,6 +1324,14 @@ static int tegra_soctherm_probe(struct platform_device *pdev) dev_err(&pdev->dev, "can't get car clk registers"); return PTR_ERR(tegra->clk_regs); } + } else { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "ccroc-reg"); + tegra->ccroc_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tegra->ccroc_regs)) { + dev_err(&pdev->dev, "can't get ccroc registers"); + return PTR_ERR(tegra->ccroc_regs); + } } tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm"); @@ -1207,8 +1382,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev) if (err) return err; - if (!tegra->soc->use_ccroc) - soctherm_init_hw_throt_cdev(pdev); + soctherm_init_hw_throt_cdev(pdev); soctherm_init(pdev); diff --git a/drivers/thermal/tegra/tegra132-soctherm.c b/drivers/thermal/tegra/tegra132-soctherm.c index ae5d61a..97fa305 100644 --- a/drivers/thermal/tegra/tegra132-soctherm.c +++ b/drivers/thermal/tegra/tegra132-soctherm.c @@ -28,7 +28,11 @@ #define TEGRA132_THERMTRIP_CPU_THRESH_MASK (0xff << 8) #define TEGRA132_THERMTRIP_TSENSE_THRESH_MASK 0xff +#define TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK (0xff << 17) +#define TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK (0xff << 9) + #define TEGRA132_THRESH_GRAIN 1000 +#define TEGRA132_BPTT 8 static const struct tegra_tsensor_configuration tegra132_tsensor_config = { .tall = 16300, @@ -51,6 +55,9 @@ static const struct tegra_tsensor_group tegra132_tsensor_group_cpu = { .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA132_THERMTRIP_CPU_EN_MASK, .thermtrip_threshold_mask = TEGRA132_THERMTRIP_CPU_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_CPU, + .thermctl_lvl0_up_thresh_mask = TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra132_tsensor_group_gpu = { @@ -66,6 +73,9 @@ static const struct tegra_tsensor_group tegra132_tsensor_group_gpu = { .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA132_THERMTRIP_GPU_EN_MASK, .thermtrip_threshold_mask = TEGRA132_THERMTRIP_GPUMEM_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_GPU, + .thermctl_lvl0_up_thresh_mask = TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra132_tsensor_group_pll = { @@ -79,6 +89,9 @@ static const struct tegra_tsensor_group tegra132_tsensor_group_pll = { .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA132_THERMTRIP_TSENSE_EN_MASK, .thermtrip_threshold_mask = TEGRA132_THERMTRIP_TSENSE_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_TSENSE, + .thermctl_lvl0_up_thresh_mask = TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group tegra132_tsensor_group_mem = { @@ -94,6 +107,9 @@ static const struct tegra_tsensor_group tegra132_tsensor_group_mem = { .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK, .thermtrip_enable_mask = TEGRA132_THERMTRIP_MEM_EN_MASK, .thermtrip_threshold_mask = TEGRA132_THERMTRIP_GPUMEM_THRESH_MASK, + .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_MEM, + .thermctl_lvl0_up_thresh_mask = TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK, + .thermctl_lvl0_dn_thresh_mask = TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK, }; static const struct tegra_tsensor_group *tegra132_tsensor_groups[] = { @@ -193,5 +209,6 @@ const struct tegra_soctherm_soc tegra132_soctherm = { .num_ttgs = ARRAY_SIZE(tegra132_tsensor_groups), .tfuse = &tegra132_soctherm_fuse, .thresh_grain = TEGRA132_THRESH_GRAIN, + .bptt = TEGRA132_BPTT, .use_ccroc = true, }; diff --git a/include/dt-bindings/thermal/tegra124-soctherm.h b/include/dt-bindings/thermal/tegra124-soctherm.h index 729ab9f..2a99f1d 100644 --- a/include/dt-bindings/thermal/tegra124-soctherm.h +++ b/include/dt-bindings/thermal/tegra124-soctherm.h @@ -11,4 +11,9 @@ #define TEGRA124_SOCTHERM_SENSOR_PLLX 3 #define TEGRA124_SOCTHERM_SENSOR_NUM 4 +#define TEGRA_SOCTHERM_THROT_LEVEL_LOW 0 +#define TEGRA_SOCTHERM_THROT_LEVEL_MED 1 +#define TEGRA_SOCTHERM_THROT_LEVEL_HIGH 2 +#define TEGRA_SOCTHERM_THROT_LEVEL_NONE -1 + #endif -- cgit v0.10.2 From 40823f8e267f913d15003975429cffb008f98c75 Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:19 +0800 Subject: arm: tegra: set critical trips for Tegra124 Set general "critical" trip temperatures for cpu, gpu, mem and pllx thermal zones for all Tegra124 platform, these trips can trigger shut down or reset. Tegra124 Jetson TK1 was already set "critical" trips before, so it can overwrite the general values. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/arch/arm/boot/dts/tegra124-jetson-tk1.dts b/arch/arm/boot/dts/tegra124-jetson-tk1.dts index 6403e0d..3cdc232 100644 --- a/arch/arm/boot/dts/tegra124-jetson-tk1.dts +++ b/arch/arm/boot/dts/tegra124-jetson-tk1.dts @@ -2045,44 +2045,32 @@ thermal-zones { cpu { trips { - trip { + cpu-shutdown-trip { temperature = <101000>; hysteresis = <0>; type = "critical"; }; }; - - cooling-maps { - /* There are currently no cooling maps because there are no cooling devices */ - }; }; mem { trips { - trip { + mem-shutdown-trip { temperature = <101000>; hysteresis = <0>; type = "critical"; }; }; - - cooling-maps { - /* There are currently no cooling maps because there are no cooling devices */ - }; }; gpu { trips { - trip { + gpu-shutdown-trip { temperature = <101000>; hysteresis = <0>; type = "critical"; }; }; - - cooling-maps { - /* There are currently no cooling maps because there are no cooling devices */ - }; }; }; }; diff --git a/arch/arm/boot/dts/tegra124.dtsi b/arch/arm/boot/dts/tegra124.dtsi index ea340f9..61c36a3 100644 --- a/arch/arm/boot/dts/tegra124.dtsi +++ b/arch/arm/boot/dts/tegra124.dtsi @@ -1154,6 +1154,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_CPU>; + + trips { + cpu-shutdown-trip { + temperature = <103000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; mem { @@ -1162,6 +1177,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_MEM>; + + trips { + mem-shutdown-trip { + temperature = <103000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; gpu { @@ -1170,6 +1200,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_GPU>; + + trips { + gpu-shutdown-trip { + temperature = <101000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; pllx { @@ -1178,6 +1223,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_PLLX>; + + trips { + pllx-shutdown-trip { + temperature = <103000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; }; -- cgit v0.10.2 From e12b048c3060975203225b6ebd30a4fe85082bab Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:20 +0800 Subject: arm: tegra: set hot trips for Tegra124 Enable throttle function for SOC_THERM. Set "hot" trips for cpu and gpu thermal zones, which can trigger the SOC_THERM hardware throttle. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/arch/arm/boot/dts/tegra124.dtsi b/arch/arm/boot/dts/tegra124.dtsi index 61c36a3..187a36c 100644 --- a/arch/arm/boot/dts/tegra124.dtsi +++ b/arch/arm/boot/dts/tegra124.dtsi @@ -851,7 +851,9 @@ soctherm: thermal-sensor@700e2000 { compatible = "nvidia,tegra124-soctherm"; - reg = <0x0 0x700e2000 0x0 0x1000>; + reg = <0x0 0x700e2000 0x0 0x600 /* SOC_THERM reg_base */ + 0x0 0x60006000 0x0 0x400>; /* CAR reg_base */ + reg-names = "soctherm-reg", "car-reg"; interrupts = ; clocks = <&tegra_car TEGRA124_CLK_TSENSOR>, <&tegra_car TEGRA124_CLK_SOC_THERM>; @@ -859,6 +861,15 @@ resets = <&tegra_car 78>; reset-names = "soctherm"; #thermal-sensor-cells = <1>; + + throttle-cfgs { + throttle_heavy: heavy { + nvidia,priority = <100>; + nvidia,cpu-throt-percent = <85>; + + #cooling-cells = <2>; + }; + }; }; dfll: clock@70110000 { @@ -1161,13 +1172,18 @@ hysteresis = <0>; type = "critical"; }; + cpu_throttle_trip: throttle-trip { + temperature = <100000>; + hysteresis = <1000>; + type = "hot"; + }; }; cooling-maps { - /* - * There are currently no cooling maps, - * because there are no cooling devices. - */ + map0 { + trip = <&cpu_throttle_trip>; + cooling-device = <&throttle_heavy 1 1>; + }; }; }; @@ -1207,13 +1223,18 @@ hysteresis = <0>; type = "critical"; }; + gpu_throttle_trip: throttle-trip { + temperature = <99000>; + hysteresis = <1000>; + type = "hot"; + }; }; cooling-maps { - /* - * There are currently no cooling maps, - * because there are no cooling devices. - */ + map0 { + trip = <&gpu_throttle_trip>; + cooling-device = <&throttle_heavy 1 1>; + }; }; }; -- cgit v0.10.2 From 0fa2bfcd1a0ee2f0465b769f37163b7e7d9c2e50 Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:21 +0800 Subject: arm64: tegra: use tegra132-soctherm for Tegra132 The Tegra132 has the specific settings for soctherm, so change to use campatible "nvidia,tegra132-soctherm" for it. And adds cpu, gpu, mem and pllx thermal zones. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/arch/arm64/boot/dts/nvidia/tegra132.dtsi b/arch/arm64/boot/dts/nvidia/tegra132.dtsi index 2013f89..e41d8e8 100644 --- a/arch/arm64/boot/dts/nvidia/tegra132.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra132.dtsi @@ -4,6 +4,7 @@ #include #include #include +#include / { compatible = "nvidia,tegra132", "nvidia,tegra124"; @@ -727,8 +728,8 @@ }; soctherm: thermal-sensor@700e2000 { - compatible = "nvidia,tegra124-soctherm"; - reg = <0x0 0x700e2000 0x0 0x1000>; + compatible = "nvidia,tegra132-soctherm"; + reg = <0x0 0x700e2000 0x0 0x600>; interrupts = ; clocks = <&tegra_car TEGRA124_CLK_TSENSOR>, <&tegra_car TEGRA124_CLK_SOC_THERM>; @@ -738,6 +739,37 @@ #thermal-sensor-cells = <1>; }; + thermal-zones { + cpu { + polling-delay-passive = <1000>; + polling-delay = <0>; + + thermal-sensors = + <&soctherm TEGRA124_SOCTHERM_SENSOR_CPU>; + }; + mem { + polling-delay-passive = <0>; + polling-delay = <0>; + + thermal-sensors = + <&soctherm TEGRA124_SOCTHERM_SENSOR_MEM>; + }; + gpu { + polling-delay-passive = <1000>; + polling-delay = <0>; + + thermal-sensors = + <&soctherm TEGRA124_SOCTHERM_SENSOR_GPU>; + }; + pllx { + polling-delay-passive = <0>; + polling-delay = <0>; + + thermal-sensors = + <&soctherm TEGRA124_SOCTHERM_SENSOR_PLLX>; + }; + }; + ahub@70300000 { compatible = "nvidia,tegra124-ahub"; reg = <0x0 0x70300000 0x0 0x200>, -- cgit v0.10.2 From a6ebde2540e20fd95150aa1ccdd82fcceb49425b Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:22 +0800 Subject: arm64: tegra: set critical trips for Tegra132 Set general "critical" trip temperatures for cpu, gpu, mem and pllx thermal zones on Tegra132, these trips can trigger shut down or reset. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/arch/arm64/boot/dts/nvidia/tegra132.dtsi b/arch/arm64/boot/dts/nvidia/tegra132.dtsi index e41d8e8..a3889e9 100644 --- a/arch/arm64/boot/dts/nvidia/tegra132.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra132.dtsi @@ -746,6 +746,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_CPU>; + + trips { + cpu_shutdown_trip { + temperature = <105000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; mem { polling-delay-passive = <0>; @@ -753,6 +768,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_MEM>; + + trips { + mem_shutdown_trip { + temperature = <101000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; gpu { polling-delay-passive = <1000>; @@ -760,6 +790,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_GPU>; + + trips { + gpu_shutdown_trip { + temperature = <101000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; pllx { polling-delay-passive = <0>; @@ -767,6 +812,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_PLLX>; + + trips { + pllx_shutdown_trip { + temperature = <105000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; }; -- cgit v0.10.2 From f4357938d058af475ed252a691b53d3b371b65f9 Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:23 +0800 Subject: arm64: tegra: set hot trips for Tegra132 Enable throttle function for SOC_THERM. Set "hot" trips for cpu and gpu thermal zones, which can trigger the SOC_THERM hardware throttle. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/arch/arm64/boot/dts/nvidia/tegra132.dtsi b/arch/arm64/boot/dts/nvidia/tegra132.dtsi index a3889e9..3f3a46a 100644 --- a/arch/arm64/boot/dts/nvidia/tegra132.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra132.dtsi @@ -729,7 +729,9 @@ soctherm: thermal-sensor@700e2000 { compatible = "nvidia,tegra132-soctherm"; - reg = <0x0 0x700e2000 0x0 0x600>; + reg = <0x0 0x700e2000 0x0 0x600 /* 0: SOC_THERM reg_base */ + 0x0 0x70040000 0x0 0x200>; /* 2: CCROC reg_base */ + reg-names = "soctherm-reg", "ccroc-reg"; interrupts = ; clocks = <&tegra_car TEGRA124_CLK_TSENSOR>, <&tegra_car TEGRA124_CLK_SOC_THERM>; @@ -737,6 +739,15 @@ resets = <&tegra_car 78>; reset-names = "soctherm"; #thermal-sensor-cells = <1>; + + throttle-cfgs { + throttle_heavy: heavy { + nvidia,priority = <100>; + nvidia,cpu-throt-level = ; + + #cooling-cells = <2>; + }; + }; }; thermal-zones { @@ -753,13 +764,19 @@ hysteresis = <1000>; type = "critical"; }; + + cpu_throttle_trip: throttle-trip { + temperature = <102000>; + hysteresis = <1000>; + type = "hot"; + }; }; cooling-maps { - /* - * There are currently no cooling maps, - * because there are no cooling devices. - */ + map0 { + trip = <&cpu_throttle_trip>; + cooling-device = <&throttle_heavy 1 1>; + }; }; }; mem { @@ -797,13 +814,19 @@ hysteresis = <1000>; type = "critical"; }; + + gpu_throttle_trip: throttle-trip { + temperature = <99000>; + hysteresis = <1000>; + type = "hot"; + }; }; cooling-maps { - /* - * There are currently no cooling maps, - * because there are no cooling devices. - */ + map0 { + trip = <&gpu_throttle_trip>; + cooling-device = <&throttle_heavy 1 1>; + }; }; }; pllx { -- cgit v0.10.2 From e2bed1ebbf258f530cb6e045d9bbb1a1aeca2c5e Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:24 +0800 Subject: arm64: tegra: add soctherm node for Tegra210 Adds soctherm node for Tegra210, and add cpu, gpu, mem, pllx as thermal-zones. Set critical trip temperatures for them. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/arch/arm64/boot/dts/nvidia/tegra210.dtsi b/arch/arm64/boot/dts/nvidia/tegra210.dtsi index c4cfdcf..ec59e9e 100644 --- a/arch/arm64/boot/dts/nvidia/tegra210.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra210.dtsi @@ -3,6 +3,7 @@ #include #include #include +#include / { compatible = "nvidia,tegra210"; @@ -1094,4 +1095,47 @@ (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>; interrupt-parent = <&gic>; }; + + soctherm: thermal-sensor@700e2000 { + compatible = "nvidia,tegra210-soctherm"; + reg = <0x0 0x700e2000 0x0 0x1000>; + interrupts = ; + clocks = <&tegra_car TEGRA210_CLK_TSENSOR>, + <&tegra_car TEGRA210_CLK_SOC_THERM>; + clock-names = "tsensor", "soctherm"; + resets = <&tegra_car 78>; + reset-names = "soctherm"; + #thermal-sensor-cells = <1>; + }; + + thermal-zones { + cpu { + polling-delay-passive = <1000>; + polling-delay = <0>; + + thermal-sensors = + <&soctherm TEGRA124_SOCTHERM_SENSOR_CPU>; + }; + mem { + polling-delay-passive = <0>; + polling-delay = <0>; + + thermal-sensors = + <&soctherm TEGRA124_SOCTHERM_SENSOR_MEM>; + }; + gpu { + polling-delay-passive = <1000>; + polling-delay = <0>; + + thermal-sensors = + <&soctherm TEGRA124_SOCTHERM_SENSOR_GPU>; + }; + pllx { + polling-delay-passive = <0>; + polling-delay = <0>; + + thermal-sensors = + <&soctherm TEGRA124_SOCTHERM_SENSOR_PLLX>; + }; + }; }; -- cgit v0.10.2 From 5e03f663ca6f3cddb6512df07233c71645fb04a7 Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:25 +0800 Subject: arm64: tegra: set critical trips for Tegra210 Set general "critical" trip temperatures for cpu, gpu, mem and pllx thermal zones on Tegra210, these trips can trigger shut down or reset. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/arch/arm64/boot/dts/nvidia/tegra210.dtsi b/arch/arm64/boot/dts/nvidia/tegra210.dtsi index ec59e9e..604c999 100644 --- a/arch/arm64/boot/dts/nvidia/tegra210.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra210.dtsi @@ -1115,6 +1115,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_CPU>; + + trips { + cpu-shutdown-trip { + temperature = <102500>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; mem { polling-delay-passive = <0>; @@ -1122,6 +1137,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_MEM>; + + trips { + mem-shutdown-trip { + temperature = <103000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; gpu { polling-delay-passive = <1000>; @@ -1129,6 +1159,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_GPU>; + + trips { + gpu-shutdown-trip { + temperature = <103000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; pllx { polling-delay-passive = <0>; @@ -1136,6 +1181,21 @@ thermal-sensors = <&soctherm TEGRA124_SOCTHERM_SENSOR_PLLX>; + + trips { + pllx-shutdown-trip { + temperature = <103000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + /* + * There are currently no cooling maps, + * because there are no cooling devices. + */ + }; }; }; }; -- cgit v0.10.2 From cbd0f00017790caae0107d48ba76b4b768c9448b Mon Sep 17 00:00:00 2001 From: Wei Ni Date: Wed, 11 May 2016 18:20:26 +0800 Subject: arm64: tegra: set hot trips for Tegra210 Enable throttle function for SOC_THERM. Set "hot" trips for cpu and gpu thermal zones, which can trigger the SOC_THERM hardware throttle. Signed-off-by: Wei Ni Signed-off-by: Zhang Rui diff --git a/arch/arm64/boot/dts/nvidia/tegra210.dtsi b/arch/arm64/boot/dts/nvidia/tegra210.dtsi index 604c999..6a6410c 100644 --- a/arch/arm64/boot/dts/nvidia/tegra210.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra210.dtsi @@ -1098,7 +1098,9 @@ soctherm: thermal-sensor@700e2000 { compatible = "nvidia,tegra210-soctherm"; - reg = <0x0 0x700e2000 0x0 0x1000>; + reg = <0x0 0x700e2000 0x0 0x600 /* SOC_THERM reg_base */ + 0x0 0x60006000 0x0 0x400>; /* CAR reg_base */ + reg-names = "soctherm-reg", "car-reg"; interrupts = ; clocks = <&tegra_car TEGRA210_CLK_TSENSOR>, <&tegra_car TEGRA210_CLK_SOC_THERM>; @@ -1106,6 +1108,15 @@ resets = <&tegra_car 78>; reset-names = "soctherm"; #thermal-sensor-cells = <1>; + + throttle-cfgs { + throttle_heavy: heavy { + nvidia,priority = <100>; + nvidia,cpu-throt-percent = <85>; + + #cooling-cells = <2>; + }; + }; }; thermal-zones { @@ -1122,13 +1133,19 @@ hysteresis = <0>; type = "critical"; }; + + cpu_throttle_trip: throttle-trip { + temperature = <98500>; + hysteresis = <1000>; + type = "hot"; + }; }; cooling-maps { - /* - * There are currently no cooling maps, - * because there are no cooling devices. - */ + map0 { + trip = <&cpu_throttle_trip>; + cooling-device = <&throttle_heavy 1 1>; + }; }; }; mem { @@ -1166,13 +1183,19 @@ hysteresis = <0>; type = "critical"; }; + + gpu_throttle_trip: throttle-trip { + temperature = <100000>; + hysteresis = <1000>; + type = "hot"; + }; }; cooling-maps { - /* - * There are currently no cooling maps, - * because there are no cooling devices. - */ + map0 { + trip = <&gpu_throttle_trip>; + cooling-device = <&throttle_heavy 1 1>; + }; }; }; pllx { -- cgit v0.10.2 From 0e70f466fb910ae54c4c71243b99385129e93feb Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Fri, 26 Aug 2016 16:21:16 -0700 Subject: thermal: Enhance thermal_zone_device_update for events Added one additional parameter to thermal_zone_device_update() to provide caller with an optional capability to specify reason. Currently this event is used by user space governor to trigger different processing based on event code. Also it saves an additional call to read temperature when the event is received. The following events are cuurently defined: - Unspecified event - New temperature sample - Trip point violated - Trip point changed - thermal device up and down - thermal device power capability changed Signed-off-by: Srinivas Pandruvada Signed-off-by: Zhang Rui diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index f4ebe39..35e8fbc 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -520,7 +520,8 @@ static void acpi_thermal_check(void *data) if (!tz->tz_enabled) return; - thermal_zone_device_update(tz->thermal_zone); + thermal_zone_device_update(tz->thermal_zone, + THERMAL_EVENT_UNSPECIFIED); } /* sys I/F for generic thermal sysfs support */ diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index 460fa67..2acdb0d 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -405,7 +405,7 @@ static inline void acerhdf_enable_kernelmode(void) kernelmode = 1; thz_dev->polling_delay = interval*1000; - thermal_zone_device_update(thz_dev); + thermal_zone_device_update(thz_dev, THERMAL_EVENT_UNSPECIFIED); pr_notice("kernel mode fan control ON\n"); } diff --git a/drivers/regulator/max8973-regulator.c b/drivers/regulator/max8973-regulator.c index 3958f50..e0c747a 100644 --- a/drivers/regulator/max8973-regulator.c +++ b/drivers/regulator/max8973-regulator.c @@ -495,7 +495,8 @@ static irqreturn_t max8973_thermal_irq(int irq, void *data) { struct max8973_chip *mchip = data; - thermal_zone_device_update(mchip->tz_device); + thermal_zone_device_update(mchip->tz_device, + THERMAL_EVENT_UNSPECIFIED); return IRQ_HANDLED; } diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c index 652acd8..e776cea 100644 --- a/drivers/thermal/db8500_thermal.c +++ b/drivers/thermal/db8500_thermal.c @@ -306,7 +306,7 @@ static void db8500_thermal_work(struct work_struct *work) if (cur_mode == THERMAL_DEVICE_DISABLED) return; - thermal_zone_device_update(pzone->therm_dev); + thermal_zone_device_update(pzone->therm_dev, THERMAL_EVENT_UNSPECIFIED); dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); } diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c index 97fad8f..f642966 100644 --- a/drivers/thermal/hisi_thermal.c +++ b/drivers/thermal/hisi_thermal.c @@ -237,7 +237,8 @@ static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev) if (!data->sensors[i].tzd) continue; - thermal_zone_device_update(data->sensors[i].tzd); + thermal_zone_device_update(data->sensors[i].tzd, + THERMAL_EVENT_UNSPECIFIED); } return IRQ_HANDLED; diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index e473548..06912f0 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -246,7 +246,7 @@ static int imx_set_mode(struct thermal_zone_device *tz, } data->mode = mode; - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); return 0; } @@ -457,7 +457,7 @@ static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n", data->alarm_temp / 1000); - thermal_zone_device_update(data->tz); + thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED); return IRQ_HANDLED; } diff --git a/drivers/thermal/int340x_thermal/int340x_thermal_zone.h b/drivers/thermal/int340x_thermal/int340x_thermal_zone.h index aaadf72..65116b1 100644 --- a/drivers/thermal/int340x_thermal/int340x_thermal_zone.h +++ b/drivers/thermal/int340x_thermal/int340x_thermal_zone.h @@ -62,7 +62,7 @@ static inline void *int340x_thermal_zone_get_priv_data( static inline void int340x_thermal_zone_device_update( struct int34x_thermal_zone *tzone) { - thermal_zone_device_update(tzone->zone); + thermal_zone_device_update(tzone->zone, THERMAL_EVENT_UNSPECIFIED); } #endif diff --git a/drivers/thermal/intel_bxt_pmic_thermal.c b/drivers/thermal/intel_bxt_pmic_thermal.c index 4ae3e0c..0f19a39 100644 --- a/drivers/thermal/intel_bxt_pmic_thermal.c +++ b/drivers/thermal/intel_bxt_pmic_thermal.c @@ -204,7 +204,8 @@ static irqreturn_t pmic_thermal_irq_handler(int irq, void *data) trip = td->maps[i].trip_config[j].trip_num; tzd = thermal_zone_get_zone_by_name(td->maps[i].handle); if (!IS_ERR(tzd)) - thermal_zone_device_update(tzd); + thermal_zone_device_update(tzd, + THERMAL_EVENT_UNSPECIFIED); /* Clear the appropriate irq */ regmap_write(regmap, reg, reg_val & mask); diff --git a/drivers/thermal/intel_soc_dts_iosf.c b/drivers/thermal/intel_soc_dts_iosf.c index f72e1db..e0813df 100644 --- a/drivers/thermal/intel_soc_dts_iosf.c +++ b/drivers/thermal/intel_soc_dts_iosf.c @@ -391,7 +391,8 @@ void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors) for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { pr_debug("TZD update for zone %d\n", i); - thermal_zone_device_update(sensors->soc_dts[i].tzone); + thermal_zone_device_update(sensors->soc_dts[i].tzone, + THERMAL_EVENT_UNSPECIFIED); } } else spin_unlock_irqrestore(&sensors->intr_notify_lock, flags); diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c index 8d61626..83905ff 100644 --- a/drivers/thermal/max77620_thermal.c +++ b/drivers/thermal/max77620_thermal.c @@ -82,7 +82,8 @@ static irqreturn_t max77620_thermal_irq(int irq, void *data) else if (irq == mtherm->irq_tjalarm2) dev_crit(mtherm->dev, "Junction Temp Alarm2(140C) occurred\n"); - thermal_zone_device_update(mtherm->tz_device); + thermal_zone_device_update(mtherm->tz_device, + THERMAL_EVENT_UNSPECIFIED); return IRQ_HANDLED; } diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index ab6e526..d04ec3b 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -286,7 +286,7 @@ static int of_thermal_set_mode(struct thermal_zone_device *tz, mutex_unlock(&tz->lock); data->mode = mode; - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); return 0; } diff --git a/drivers/thermal/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom-spmi-temp-alarm.c index f8a3c60..819c6d5 100644 --- a/drivers/thermal/qcom-spmi-temp-alarm.c +++ b/drivers/thermal/qcom-spmi-temp-alarm.c @@ -150,7 +150,7 @@ static irqreturn_t qpnp_tm_isr(int irq, void *data) { struct qpnp_tm_chip *chip = data; - thermal_zone_device_update(chip->tz_dev); + thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED); return IRQ_HANDLED; } diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index b5c6442..6c73d3e 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -358,7 +358,8 @@ static void rcar_thermal_work(struct work_struct *work) return; if (nctemp != cctemp) - thermal_zone_device_update(priv->zone); + thermal_zone_device_update(priv->zone, + THERMAL_EVENT_UNSPECIFIED); } static u32 rcar_thermal_had_changed(struct rcar_thermal_priv *priv, u32 status) diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index 1f165c9..e227a9f 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -873,7 +873,8 @@ static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev) thermal->chip->irq_ack(thermal->regs); for (i = 0; i < thermal->chip->chn_num; i++) - thermal_zone_device_update(thermal->sensors[i].tzd); + thermal_zone_device_update(thermal->sensors[i].tzd, + THERMAL_EVENT_UNSPECIFIED); return IRQ_HANDLED; } diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index f3ce94e..ad1186d 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -225,7 +225,7 @@ static void exynos_report_trigger(struct exynos_tmu_data *p) return; } - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); mutex_lock(&tz->lock); /* Find the level for which trip happened */ diff --git a/drivers/thermal/st/st_thermal_memmap.c b/drivers/thermal/st/st_thermal_memmap.c index fc0c9e1..91d4231 100644 --- a/drivers/thermal/st/st_thermal_memmap.c +++ b/drivers/thermal/st/st_thermal_memmap.c @@ -42,7 +42,8 @@ static irqreturn_t st_mmap_thermal_trip_handler(int irq, void *sdata) { struct st_thermal_sensor *sensor = sdata; - thermal_zone_device_update(sensor->thermal_dev); + thermal_zone_device_update(sensor->thermal_dev, + THERMAL_EVENT_UNSPECIFIED); return IRQ_HANDLED; } diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index f2d55e4..226b0b4ac 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -607,7 +607,8 @@ static void thermal_zone_device_reset(struct thermal_zone_device *tz) pos->initialized = false; } -void thermal_zone_device_update(struct thermal_zone_device *tz) +void thermal_zone_device_update(struct thermal_zone_device *tz, + enum thermal_notify_event event) { int count; @@ -621,6 +622,8 @@ void thermal_zone_device_update(struct thermal_zone_device *tz) thermal_zone_set_trips(tz); + tz->notify_event = event; + for (count = 0; count < tz->trips; count++) handle_thermal_trip(tz, count); } @@ -631,7 +634,7 @@ static void thermal_zone_device_check(struct work_struct *work) struct thermal_zone_device *tz = container_of(work, struct thermal_zone_device, poll_queue.work); - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); } /* sys I/F for thermal zone */ @@ -755,7 +758,7 @@ trip_point_temp_store(struct device *dev, struct device_attribute *attr, if (ret) return ret; - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); return count; } @@ -877,7 +880,7 @@ passive_store(struct device *dev, struct device_attribute *attr, tz->forced_passive = state; - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); return count; } @@ -968,7 +971,7 @@ emul_temp_store(struct device *dev, struct device_attribute *attr, } if (!ret) - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); return ret ? ret : count; } @@ -1564,7 +1567,8 @@ __thermal_cooling_device_register(struct device_node *np, mutex_lock(&thermal_list_lock); list_for_each_entry(pos, &thermal_tz_list, node) if (atomic_cmpxchg(&pos->need_update, 1, 0)) - thermal_zone_device_update(pos); + thermal_zone_device_update(pos, + THERMAL_EVENT_UNSPECIFIED); mutex_unlock(&thermal_list_lock); return cdev; @@ -2007,7 +2011,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, thermal_zone_device_reset(tz); /* Update the new thermal zone and mark it as already updated. */ if (atomic_cmpxchg(&tz->need_update, 1, 0)) - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); return tz; @@ -2294,7 +2298,8 @@ static int thermal_pm_notify(struct notifier_block *nb, atomic_set(&in_suspend, 0); list_for_each_entry(tz, &thermal_tz_list, node) { thermal_zone_device_reset(tz); - thermal_zone_device_update(tz); + thermal_zone_device_update(tz, + THERMAL_EVENT_UNSPECIFIED); } break; default: diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 4a6757c..0586bd0 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -52,7 +52,7 @@ static void ti_thermal_work(struct work_struct *work) struct ti_thermal_data *data = container_of(work, struct ti_thermal_data, thermal_wq); - thermal_zone_device_update(data->ti_thermal); + thermal_zone_device_update(data->ti_thermal, THERMAL_EVENT_UNSPECIFIED); dev_dbg(&data->ti_thermal->device, "updated thermal zone %s\n", data->ti_thermal->type); @@ -205,7 +205,7 @@ static int ti_thermal_set_mode(struct thermal_zone_device *thermal, data->mode = mode; ti_bandgap_write_update_interval(bgp, data->sensor_id, data->ti_thermal->polling_delay); - thermal_zone_device_update(data->ti_thermal); + thermal_zone_device_update(data->ti_thermal, THERMAL_EVENT_UNSPECIFIED); dev_dbg(&thermal->device, "thermal polling set for duration=%d msec\n", data->ti_thermal->polling_delay); diff --git a/drivers/thermal/x86_pkg_temp_thermal.c b/drivers/thermal/x86_pkg_temp_thermal.c index 97f0a2b..95f4c1b 100644 --- a/drivers/thermal/x86_pkg_temp_thermal.c +++ b/drivers/thermal/x86_pkg_temp_thermal.c @@ -348,7 +348,8 @@ static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work) } if (notify) { pr_debug("thermal_zone_device_update\n"); - thermal_zone_device_update(phdev->tzone); + thermal_zone_device_update(phdev->tzone, + THERMAL_EVENT_UNSPECIFIED); } } diff --git a/include/linux/thermal.h b/include/linux/thermal.h index b3c16f0..511182a 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -92,6 +92,17 @@ enum thermal_trend { THERMAL_TREND_DROP_FULL, /* apply lowest cooling action */ }; +/* Thermal notification reason */ +enum thermal_notify_event { + THERMAL_EVENT_UNSPECIFIED, /* Unspecified event */ + THERMAL_EVENT_TEMP_SAMPLE, /* New Temperature sample */ + THERMAL_TRIP_VIOLATED, /* TRIP Point violation */ + THERMAL_TRIP_CHANGED, /* TRIP Point temperature changed */ + THERMAL_DEVICE_DOWN, /* Thermal device is down */ + THERMAL_DEVICE_UP, /* Thermal device is up after a down event */ + THERMAL_DEVICE_POWER_CAPABILITY_CHANGED, /* power capability changed */ +}; + struct thermal_zone_device_ops { int (*bind) (struct thermal_zone_device *, struct thermal_cooling_device *); @@ -187,6 +198,7 @@ struct thermal_attr { * @lock: lock to protect thermal_instances list * @node: node in thermal_tz_list (in thermal_core.c) * @poll_queue: delayed work for polling + * @notify_event: Last notification event */ struct thermal_zone_device { int id; @@ -217,6 +229,7 @@ struct thermal_zone_device { struct mutex lock; struct list_head node; struct delayed_work poll_queue; + enum thermal_notify_event notify_event; }; /** @@ -436,7 +449,8 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int, unsigned int); int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int, struct thermal_cooling_device *); -void thermal_zone_device_update(struct thermal_zone_device *); +void thermal_zone_device_update(struct thermal_zone_device *, + enum thermal_notify_event); void thermal_zone_set_trips(struct thermal_zone_device *); struct thermal_cooling_device *thermal_cooling_device_register(char *, void *, @@ -487,7 +501,8 @@ static inline int thermal_zone_unbind_cooling_device( struct thermal_zone_device *tz, int trip, struct thermal_cooling_device *cdev) { return -ENODEV; } -static inline void thermal_zone_device_update(struct thermal_zone_device *tz) +static inline void thermal_zone_device_update(struct thermal_zone_device *tz, + enum thermal_notify_event event) { } static inline void thermal_zone_set_trips(struct thermal_zone_device *tz) { } -- cgit v0.10.2 From 998d924b712896c540c76c679fc7d53d6a880d5d Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Fri, 26 Aug 2016 16:21:17 -0700 Subject: thermal: user_space gov: Add additional information in uevent Add additional properties: NAME= Thermal zone type TEMP= Temperature sample value TRIP= Violated trip index EVENT= The notification event (new temperature sample, trip violation trip changed) This is the additional information to what kobject_uevent already provides. So it will not impact existing user spaces. Signed-off-by: Srinivas Pandruvada Signed-off-by: Zhang Rui diff --git a/drivers/thermal/user_space.c b/drivers/thermal/user_space.c index 10adcdd..c908150 100644 --- a/drivers/thermal/user_space.c +++ b/drivers/thermal/user_space.c @@ -23,19 +23,30 @@ */ #include - +#include #include "thermal_core.h" /** * notify_user_space - Notifies user space about thermal events * @tz - thermal_zone_device + * @trip - Trip point index * * This function notifies the user space through UEvents. */ static int notify_user_space(struct thermal_zone_device *tz, int trip) { + char *thermal_prop[5]; + int i; + mutex_lock(&tz->lock); - kobject_uevent(&tz->device.kobj, KOBJ_CHANGE); + thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", tz->type); + thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", tz->temperature); + thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d", trip); + thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", tz->notify_event); + thermal_prop[4] = NULL; + kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, thermal_prop); + for (i = 0; i < 4; ++i) + kfree(thermal_prop[i]); mutex_unlock(&tz->lock); return 0; } -- cgit v0.10.2 From 9176ae8668a005b5441604bd1b47eeec07c27d94 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Fri, 26 Aug 2016 16:21:18 -0700 Subject: thermal: int340x: New Interface to read trip and notify Separated the code for reading trip points from int340x_thermal_zone_add to a standalone function int340x_thermal_read_trips. This standlone interface to read is exported so that int340x drivers can re-read trips on ACPI notification for trip point change. Also the appropriate notification events are sent by int340x driver based on the acpi event using int340x_thermal_zone_device_update(). Signed-off-by: Srinivas Pandruvada Signed-off-by: Zhang Rui diff --git a/drivers/thermal/int340x_thermal/int3402_thermal.c b/drivers/thermal/int340x_thermal/int3402_thermal.c index 69df3d9..8e90b31 100644 --- a/drivers/thermal/int340x_thermal/int3402_thermal.c +++ b/drivers/thermal/int340x_thermal/int3402_thermal.c @@ -35,7 +35,8 @@ static void int3402_notify(acpi_handle handle, u32 event, void *data) case INT3402_PERF_CHANGED_EVENT: break; case INT3402_THERMAL_EVENT: - int340x_thermal_zone_device_update(priv->int340x_zone); + int340x_thermal_zone_device_update(priv->int340x_zone, + THERMAL_TRIP_VIOLATED); break; default: break; diff --git a/drivers/thermal/int340x_thermal/int3403_thermal.c b/drivers/thermal/int340x_thermal/int3403_thermal.c index 50a7a08..7643489 100644 --- a/drivers/thermal/int340x_thermal/int3403_thermal.c +++ b/drivers/thermal/int340x_thermal/int3403_thermal.c @@ -72,7 +72,8 @@ static void int3403_notify(acpi_handle handle, case INT3403_PERF_CHANGED_EVENT: break; case INT3403_THERMAL_EVENT: - int340x_thermal_zone_device_update(obj->int340x_zone); + int340x_thermal_zone_device_update(obj->int340x_zone, + THERMAL_TRIP_VIOLATED); break; default: dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); diff --git a/drivers/thermal/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/int340x_thermal/int340x_thermal_zone.c index b9b2666..145a5c53 100644 --- a/drivers/thermal/int340x_thermal/int340x_thermal_zone.c +++ b/drivers/thermal/int340x_thermal/int340x_thermal_zone.c @@ -177,6 +177,42 @@ static int int340x_thermal_get_trip_config(acpi_handle handle, char *name, return 0; } +int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone) +{ + int trip_cnt = int34x_zone->aux_trip_nr; + int i; + + int34x_zone->crt_trip_id = -1; + if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_CRT", + &int34x_zone->crt_temp)) + int34x_zone->crt_trip_id = trip_cnt++; + + int34x_zone->hot_trip_id = -1; + if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_HOT", + &int34x_zone->hot_temp)) + int34x_zone->hot_trip_id = trip_cnt++; + + int34x_zone->psv_trip_id = -1; + if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_PSV", + &int34x_zone->psv_temp)) + int34x_zone->psv_trip_id = trip_cnt++; + + for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { + char name[5] = { '_', 'A', 'C', '0' + i, '\0' }; + + if (int340x_thermal_get_trip_config(int34x_zone->adev->handle, + name, + &int34x_zone->act_trips[i].temp)) + break; + + int34x_zone->act_trips[i].id = trip_cnt++; + int34x_zone->act_trips[i].valid = true; + } + + return trip_cnt; +} +EXPORT_SYMBOL_GPL(int340x_thermal_read_trips); + static struct thermal_zone_params int340x_thermal_params = { .governor_name = "user_space", .no_hwmon = true, @@ -188,7 +224,7 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev, struct int34x_thermal_zone *int34x_thermal_zone; acpi_status status; unsigned long long trip_cnt; - int trip_mask = 0, i; + int trip_mask = 0; int ret; int34x_thermal_zone = kzalloc(sizeof(*int34x_thermal_zone), @@ -214,28 +250,8 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev, int34x_thermal_zone->aux_trip_nr = trip_cnt; } - int34x_thermal_zone->crt_trip_id = -1; - if (!int340x_thermal_get_trip_config(adev->handle, "_CRT", - &int34x_thermal_zone->crt_temp)) - int34x_thermal_zone->crt_trip_id = trip_cnt++; - int34x_thermal_zone->hot_trip_id = -1; - if (!int340x_thermal_get_trip_config(adev->handle, "_HOT", - &int34x_thermal_zone->hot_temp)) - int34x_thermal_zone->hot_trip_id = trip_cnt++; - int34x_thermal_zone->psv_trip_id = -1; - if (!int340x_thermal_get_trip_config(adev->handle, "_PSV", - &int34x_thermal_zone->psv_temp)) - int34x_thermal_zone->psv_trip_id = trip_cnt++; - for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { - char name[5] = { '_', 'A', 'C', '0' + i, '\0' }; + trip_cnt = int340x_thermal_read_trips(int34x_thermal_zone); - if (int340x_thermal_get_trip_config(adev->handle, name, - &int34x_thermal_zone->act_trips[i].temp)) - break; - - int34x_thermal_zone->act_trips[i].id = trip_cnt++; - int34x_thermal_zone->act_trips[i].valid = true; - } int34x_thermal_zone->lpat_table = acpi_lpat_get_conversion_table( adev->handle); diff --git a/drivers/thermal/int340x_thermal/int340x_thermal_zone.h b/drivers/thermal/int340x_thermal/int340x_thermal_zone.h index 65116b1..5f3ba47 100644 --- a/drivers/thermal/int340x_thermal/int340x_thermal_zone.h +++ b/drivers/thermal/int340x_thermal/int340x_thermal_zone.h @@ -46,6 +46,7 @@ struct int34x_thermal_zone { struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *, struct thermal_zone_device_ops *override_ops); void int340x_thermal_zone_remove(struct int34x_thermal_zone *); +int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone); static inline void int340x_thermal_zone_set_priv_data( struct int34x_thermal_zone *tzone, void *priv_data) @@ -60,9 +61,10 @@ static inline void *int340x_thermal_zone_get_priv_data( } static inline void int340x_thermal_zone_device_update( - struct int34x_thermal_zone *tzone) + struct int34x_thermal_zone *tzone, + enum thermal_notify_event event) { - thermal_zone_device_update(tzone->zone, THERMAL_EVENT_UNSPECIFIED); + thermal_zone_device_update(tzone->zone, event); } #endif diff --git a/drivers/thermal/int340x_thermal/processor_thermal_device.c b/drivers/thermal/int340x_thermal/processor_thermal_device.c index 42c1ac0..ff3b36f 100644 --- a/drivers/thermal/int340x_thermal/processor_thermal_device.c +++ b/drivers/thermal/int340x_thermal/processor_thermal_device.c @@ -258,7 +258,8 @@ static void proc_thermal_notify(acpi_handle handle, u32 event, void *data) switch (event) { case PROC_POWER_CAPABILITY_CHANGED: proc_thermal_read_ppcc(proc_priv); - int340x_thermal_zone_device_update(proc_priv->int340x_zone); + int340x_thermal_zone_device_update(proc_priv->int340x_zone, + THERMAL_DEVICE_POWER_CAPABILITY_CHANGED); break; default: dev_err(proc_priv->dev, "Unsupported event [0x%x]\n", event); -- cgit v0.10.2 From 43720df96023b762843e7ddcc319fc99989ba3c2 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Fri, 26 Aug 2016 16:21:19 -0700 Subject: thermal: int3403: Process trip change notification When ACPI sends notification for trip point change re-read trips and notify thermal core, so that this can be passed to user space thermal controller. Signed-off-by: Srinivas Pandruvada Signed-off-by: Zhang Rui diff --git a/drivers/thermal/int340x_thermal/int3403_thermal.c b/drivers/thermal/int340x_thermal/int3403_thermal.c index 7643489..c4890c9 100644 --- a/drivers/thermal/int340x_thermal/int3403_thermal.c +++ b/drivers/thermal/int340x_thermal/int3403_thermal.c @@ -25,6 +25,7 @@ #define INT3403_TYPE_CHARGER 0x0B #define INT3403_TYPE_BATTERY 0x0C #define INT3403_PERF_CHANGED_EVENT 0x80 +#define INT3403_PERF_TRIP_POINT_CHANGED 0x81 #define INT3403_THERMAL_EVENT 0x90 /* Preserved structure for future expandbility */ @@ -75,6 +76,11 @@ static void int3403_notify(acpi_handle handle, int340x_thermal_zone_device_update(obj->int340x_zone, THERMAL_TRIP_VIOLATED); break; + case INT3403_PERF_TRIP_POINT_CHANGED: + int340x_thermal_read_trips(obj->int340x_zone); + int340x_thermal_zone_device_update(obj->int340x_zone, + THERMAL_TRIP_CHANGED); + break; default: dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); break; -- cgit v0.10.2