From: dissent1 Date: Sat, 5 Nov 2016 13:15:33 +0000 (+0300) Subject: ipq806x: add thermal sensor driver X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=fef6a96d9e9e001a28b6a673c56ed79029457b5d;p=openwrt%2Fstaging%2Frobimarko.git ipq806x: add thermal sensor driver Allows to check cpu temperature. Huge thanks to @hnyman for valuable assistance! Signed-off-by: Pavel Kubelun --- diff --git a/target/linux/ipq806x/config-4.4 b/target/linux/ipq806x/config-4.4 index 56a2254489..cdb20ebb05 100644 --- a/target/linux/ipq806x/config-4.4 +++ b/target/linux/ipq806x/config-4.4 @@ -352,6 +352,7 @@ CONFIG_QCOM_SCM=y CONFIG_QCOM_SCM_32=y # CONFIG_QCOM_SMD is not set CONFIG_QCOM_SMEM=y +CONFIG_QCOM_TSENS=y CONFIG_QCOM_WDT=y CONFIG_RAS=y CONFIG_RATIONAL=y diff --git a/target/linux/ipq806x/files/arch/arm/boot/dts/qcom-ipq8065.dtsi b/target/linux/ipq806x/files/arch/arm/boot/dts/qcom-ipq8065.dtsi index e0ba99a41c..614610c016 100644 --- a/target/linux/ipq806x/files/arch/arm/boot/dts/qcom-ipq8065.dtsi +++ b/target/linux/ipq806x/files/arch/arm/boot/dts/qcom-ipq8065.dtsi @@ -79,6 +79,92 @@ }; }; + thermal-zones { + cpu-thermal0 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&gcc 5>; + coefficients = <1132 0>; + + trips { + cpu_alert0: trip0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit0: trip1 { + temperature = <110000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal1 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&gcc 6>; + coefficients = <1132 0>; + + trips { + cpu_alert1: trip0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit1: trip1 { + temperature = <110000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal2 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&gcc 7>; + coefficients = <1199 0>; + + trips { + cpu_alert2: trip0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit2: trip1 { + temperature = <110000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal3 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&gcc 8>; + coefficients = <1132 0>; + + trips { + cpu_alert3: trip0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit3: trip1 { + temperature = <110000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + }; + cpu-pmu { compatible = "qcom,krait-pmu"; interrupts = <1 10 0x304>; @@ -205,8 +291,14 @@ reg = <0x00700000 0x1000>; #address-cells = <1>; #size-cells = <1>; - stride = <1>; - ranges = <0x0 0x00700000 0x1000>; + ranges; + + tsens_calib: calib { + reg = <0x400 0x10>; + }; + tsens_backup: backup_calib { + reg = <0x410 0x10>; + }; }; rpm@108000 { @@ -687,9 +779,12 @@ gcc: clock-controller@900000 { compatible = "qcom,gcc-ipq8064"; reg = <0x00900000 0x4000>; + nvmem-cells = <&tsens_calib>, <&tsens_backup>; + nvmem-cell-names = "calib", "calib_backup"; #clock-cells = <1>; #reset-cells = <1>; #power-domain-cells = <1>; + #thermal-sensor-cells = <1>; }; lcc: clock-controller@28000000 { @@ -704,16 +799,6 @@ reg = <0x1a400000 0x100>; }; - tsens: tsens-ipq806x { - compatible = "qcom,ipq806x-tsens"; - reg = <0x900000 0x3678>, <0x700000 0x420>; - reg-names = "tsens_physical", "tsens_eeprom_physical"; - interrupts = <0 178 0>; - qcom,sensors = <11>; - qcom,tsens_factor = <1000>; - qcom,slope = <1176 1176 1154 1176 1111 1132 1132 1199 1132 1199 1132>; - }; - qcom,msm-thermal { compatible = "qcom,msm-thermal"; qcom,sensor-id = <0>; diff --git a/target/linux/ipq806x/patches-4.4/015-1-thermal-qcom-tsens-Add-a-skeletal-TSENS-drivers.patch b/target/linux/ipq806x/patches-4.4/015-1-thermal-qcom-tsens-Add-a-skeletal-TSENS-drivers.patch new file mode 100644 index 0000000000..ef7e2f4152 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/015-1-thermal-qcom-tsens-Add-a-skeletal-TSENS-drivers.patch @@ -0,0 +1,536 @@ +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 +--- + .../devicetree/bindings/thermal/qcom-tsens.txt | 21 +++ + drivers/thermal/Kconfig | 5 + + drivers/thermal/Makefile | 1 + + drivers/thermal/qcom/Kconfig | 11 ++ + drivers/thermal/qcom/Makefile | 2 + + drivers/thermal/qcom/tsens-common.c | 141 +++++++++++++++ + drivers/thermal/qcom/tsens.c | 195 +++++++++++++++++++++ + drivers/thermal/qcom/tsens.h | 90 ++++++++++ + 8 files changed, 466 insertions(+) + create mode 100644 Documentation/devicetree/bindings/thermal/qcom-tsens.txt + create mode 100644 drivers/thermal/qcom/Kconfig + create mode 100644 drivers/thermal/qcom/Makefile + create mode 100644 drivers/thermal/qcom/tsens-common.c + create mode 100644 drivers/thermal/qcom/tsens.c + create mode 100644 drivers/thermal/qcom/tsens.h + +--- /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>; ++ }; +--- a/drivers/thermal/Kconfig ++++ b/drivers/thermal/Kconfig +@@ -391,4 +391,9 @@ config QCOM_SPMI_TEMP_ALARM + real time die temperature if an ADC is present or an estimate of the + temperature based upon the over temperature stage value. + ++menu "Qualcomm thermal drivers" ++depends on (ARCH_QCOM && OF) || COMPILE_TEST ++source "drivers/thermal/qcom/Kconfig" ++endmenu ++ + endif +--- a/drivers/thermal/Makefile ++++ b/drivers/thermal/Makefile +@@ -48,3 +48,4 @@ obj-$(CONFIG_INTEL_PCH_THERMAL) += intel + obj-$(CONFIG_ST_THERMAL) += st/ + obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o + obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o ++obj-$(CONFIG_QCOM_TSENS) += qcom/ +--- /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. +--- /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 +--- /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; ++} +--- /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"); +--- /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__ */ diff --git a/target/linux/ipq806x/patches-4.4/015-2-thermal-qcom-tsens-8916-Add-support-for-8916-family-of-SoCs.patch b/target/linux/ipq806x/patches-4.4/015-2-thermal-qcom-tsens-8916-Add-support-for-8916-family-of-SoCs.patch new file mode 100644 index 0000000000..d07619645c --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/015-2-thermal-qcom-tsens-8916-Add-support-for-8916-family-of-SoCs.patch @@ -0,0 +1,165 @@ +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 +--- + drivers/thermal/qcom/Makefile | 2 +- + drivers/thermal/qcom/tsens-8916.c | 113 ++++++++++++++++++++++++++++++++++++++ + drivers/thermal/qcom/tsens.c | 1 + + drivers/thermal/qcom/tsens.h | 2 + + 4 files changed, 117 insertions(+), 1 deletion(-) + create mode 100644 drivers/thermal/qcom/tsens-8916.c + +--- 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 +--- /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 }, ++}; +--- a/drivers/thermal/qcom/tsens.c ++++ b/drivers/thermal/qcom/tsens.c +@@ -65,6 +65,7 @@ static SIMPLE_DEV_PM_OPS(tsens_pm_ops, t + static const struct of_device_id tsens_table[] = { + { + .compatible = "qcom,msm8916-tsens", ++ .data = &data_8916, + }, { + .compatible = "qcom,msm8974-tsens", + }, +--- a/drivers/thermal/qcom/tsens.h ++++ b/drivers/thermal/qcom/tsens.h +@@ -87,4 +87,6 @@ void compute_intercept_slope(struct tsen + 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__ */ diff --git a/target/linux/ipq806x/patches-4.4/015-3-thermal-qcom-tsens-8974-Add-support-for-8974-family-of-SoCs.patch b/target/linux/ipq806x/patches-4.4/015-3-thermal-qcom-tsens-8974-Add-support-for-8974-family-of-SoCs.patch new file mode 100644 index 0000000000..671f461057 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/015-3-thermal-qcom-tsens-8974-Add-support-for-8974-family-of-SoCs.patch @@ -0,0 +1,293 @@ +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 +--- + drivers/thermal/qcom/Makefile | 2 +- + drivers/thermal/qcom/tsens-8974.c | 244 ++++++++++++++++++++++++++++++++++++++ + drivers/thermal/qcom/tsens.c | 1 + + drivers/thermal/qcom/tsens.h | 2 +- + 4 files changed, 247 insertions(+), 2 deletions(-) + create mode 100644 drivers/thermal/qcom/tsens-8974.c + +--- 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 +--- /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, ++}; +--- a/drivers/thermal/qcom/tsens.c ++++ b/drivers/thermal/qcom/tsens.c +@@ -68,6 +68,7 @@ static const struct of_device_id tsens_t + .data = &data_8916, + }, { + .compatible = "qcom,msm8974-tsens", ++ .data = &data_8974, + }, + {} + }; +--- a/drivers/thermal/qcom/tsens.h ++++ b/drivers/thermal/qcom/tsens.h +@@ -87,6 +87,6 @@ void compute_intercept_slope(struct tsen + 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__ */ diff --git a/target/linux/ipq806x/patches-4.4/015-4-thermal-qcom-tsens-8960-Add-support-for-8960-family-of-SoCs.patch b/target/linux/ipq806x/patches-4.4/015-4-thermal-qcom-tsens-8960-Add-support-for-8960-family-of-SoCs.patch new file mode 100644 index 0000000000..05490cd97d --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/015-4-thermal-qcom-tsens-8960-Add-support-for-8960-family-of-SoCs.patch @@ -0,0 +1,364 @@ +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 +--- + drivers/thermal/qcom/Makefile | 2 +- + drivers/thermal/qcom/tsens-8960.c | 292 ++++++++++++++++++++++++++++++++++++++ + drivers/thermal/qcom/tsens.c | 8 +- + drivers/thermal/qcom/tsens.h | 2 +- + 4 files changed, 298 insertions(+), 6 deletions(-) + create mode 100644 drivers/thermal/qcom/tsens-8960.c + +--- 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 +--- /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, ++}; +--- a/drivers/thermal/qcom/tsens.c ++++ b/drivers/thermal/qcom/tsens.c +@@ -122,10 +122,10 @@ static int tsens_probe(struct platform_d + 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"); +--- a/drivers/thermal/qcom/tsens.h ++++ b/drivers/thermal/qcom/tsens.h +@@ -87,6 +87,6 @@ void compute_intercept_slope(struct tsen + 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__ */ diff --git a/target/linux/ipq806x/patches-4.4/015-8-qcom-tsens-8916-mark-PM-functions-__maybe_unused.patch b/target/linux/ipq806x/patches-4.4/015-8-qcom-tsens-8916-mark-PM-functions-__maybe_unused.patch new file mode 100644 index 0000000000..39d2173fe9 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/015-8-qcom-tsens-8916-mark-PM-functions-__maybe_unused.patch @@ -0,0 +1,46 @@ +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 +--- + drivers/thermal/qcom/tsens.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/thermal/qcom/tsens.c ++++ b/drivers/thermal/qcom/tsens.c +@@ -40,7 +40,7 @@ static int tsens_get_trend(void *data, l + 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 * + 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); + diff --git a/target/linux/ipq806x/patches-4.4/016-2-thermal-of-thermal-Add-devm-version-of.patch b/target/linux/ipq806x/patches-4.4/016-2-thermal-of-thermal-Add-devm-version-of.patch new file mode 100644 index 0000000000..1ca7326832 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/016-2-thermal-of-thermal-Add-devm-version-of.patch @@ -0,0 +1,143 @@ +From e498b4984db82b4ba3ceea7dba813222a31e9c2e Mon Sep 17 00:00:00 2001 +From: Laxman Dewangan +Date: Wed, 9 Mar 2016 18:40:06 +0530 +Subject: thermal: of-thermal: Add devm version of + thermal_zone_of_sensor_register + +Add resource managed version of thermal_zone_of_sensor_register() and +thermal_zone_of_sensor_unregister(). + +This helps in reducing the code size in error path, remove of +driver remove callbacks and making proper sequence for deallocations. + +Signed-off-by: Laxman Dewangan +Signed-off-by: Eduardo Valentin +--- + drivers/thermal/of-thermal.c | 81 ++++++++++++++++++++++++++++++++++++++++++++ + include/linux/thermal.h | 18 ++++++++++ + 2 files changed, 99 insertions(+) + +--- a/drivers/thermal/of-thermal.c ++++ b/drivers/thermal/of-thermal.c +@@ -559,6 +559,87 @@ void thermal_zone_of_sensor_unregister(s + } + EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister); + ++static void devm_thermal_zone_of_sensor_release(struct device *dev, void *res) ++{ ++ thermal_zone_of_sensor_unregister(dev, ++ *(struct thermal_zone_device **)res); ++} ++ ++static int devm_thermal_zone_of_sensor_match(struct device *dev, void *res, ++ void *data) ++{ ++ struct thermal_zone_device **r = res; ++ ++ if (WARN_ON(!r || !*r)) ++ return 0; ++ ++ return *r == data; ++} ++ ++/** ++ * devm_thermal_zone_of_sensor_register - Resource managed version of ++ * thermal_zone_of_sensor_register() ++ * @dev: a valid struct device pointer of a sensor device. Must contain ++ * a valid .of_node, for the sensor node. ++ * @sensor_id: a sensor identifier, in case the sensor IP has more ++ * than one sensors ++ * @data: a private pointer (owned by the caller) that will be passed ++ * back, when a temperature reading is needed. ++ * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp. ++ * ++ * Refer thermal_zone_of_sensor_register() for more details. ++ * ++ * 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 ++ * released when device is unbounded. ++ */ ++struct thermal_zone_device *devm_thermal_zone_of_sensor_register( ++ struct device *dev, int sensor_id, ++ void *data, const struct thermal_zone_of_device_ops *ops) ++{ ++ struct thermal_zone_device **ptr, *tzd; ++ ++ ptr = devres_alloc(devm_thermal_zone_of_sensor_release, sizeof(*ptr), ++ GFP_KERNEL); ++ if (!ptr) ++ return ERR_PTR(-ENOMEM); ++ ++ tzd = thermal_zone_of_sensor_register(dev, sensor_id, data, ops); ++ if (IS_ERR(tzd)) { ++ devres_free(ptr); ++ return tzd; ++ } ++ ++ *ptr = tzd; ++ devres_add(dev, ptr); ++ ++ return tzd; ++} ++EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_register); ++ ++/** ++ * devm_thermal_zone_of_sensor_unregister - Resource managed version of ++ * thermal_zone_of_sensor_unregister(). ++ * @dev: Device for which which resource was allocated. ++ * @tzd: a pointer to struct thermal_zone_device where the sensor is registered. ++ * ++ * This function removes the sensor callbacks and private data from the ++ * thermal zone device registered with devm_thermal_zone_of_sensor_register() ++ * API. It will also silent the zone by remove the .get_temp() and .get_trend() ++ * thermal zone device callbacks. ++ * Normally this function will not need to be called and the resource ++ * management code will ensure that the resource is freed. ++ */ ++void devm_thermal_zone_of_sensor_unregister(struct device *dev, ++ struct thermal_zone_device *tzd) ++{ ++ WARN_ON(devres_release(dev, devm_thermal_zone_of_sensor_release, ++ devm_thermal_zone_of_sensor_match, tzd)); ++} ++EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_unregister); ++ + /*** functions parsing device tree nodes ***/ + + /** +--- a/include/linux/thermal.h ++++ b/include/linux/thermal.h +@@ -364,6 +364,11 @@ thermal_zone_of_sensor_register(struct d + const struct thermal_zone_of_device_ops *ops); + void thermal_zone_of_sensor_unregister(struct device *dev, + struct thermal_zone_device *tz); ++struct thermal_zone_device *devm_thermal_zone_of_sensor_register( ++ struct device *dev, int id, void *data, ++ const struct thermal_zone_of_device_ops *ops); ++void devm_thermal_zone_of_sensor_unregister(struct device *dev, ++ struct thermal_zone_device *tz); + #else + static inline struct thermal_zone_device * + thermal_zone_of_sensor_register(struct device *dev, int id, void *data, +@@ -378,6 +383,19 @@ void thermal_zone_of_sensor_unregister(s + { + } + ++static inline struct thermal_zone_device *devm_thermal_zone_of_sensor_register( ++ struct device *dev, int id, void *data, ++ const struct thermal_zone_of_device_ops *ops) ++{ ++ return ERR_PTR(-ENODEV); ++} ++ ++static inline ++void devm_thermal_zone_of_sensor_unregister(struct device *dev, ++ struct thermal_zone_device *tz) ++{ ++} ++ + #endif + + #if IS_ENABLED(CONFIG_THERMAL) diff --git a/target/linux/ipq806x/patches-4.4/017-09-thermal-core-export-apis-to-get-slope-and-offset.patch b/target/linux/ipq806x/patches-4.4/017-09-thermal-core-export-apis-to-get-slope-and-offset.patch new file mode 100644 index 0000000000..3fbe5f521a --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/017-09-thermal-core-export-apis-to-get-slope-and-offset.patch @@ -0,0 +1,101 @@ +From 4a7069a32c99a81950de035535b0a064dcceaeba Mon Sep 17 00:00:00 2001 +From: Rajendra Nayak +Date: Thu, 5 May 2016 14:21:42 +0530 +Subject: [PATCH] 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 +--- + Documentation/thermal/sysfs-api.txt | 12 ++++++++++++ + drivers/thermal/thermal_core.c | 30 ++++++++++++++++++++++++++++++ + include/linux/thermal.h | 8 ++++++++ + 3 files changed, 50 insertions(+) + +--- a/Documentation/thermal/sysfs-api.txt ++++ b/Documentation/thermal/sysfs-api.txt +@@ -72,6 +72,18 @@ temperature) and throttle appropriate de + It deletes the corresponding entry form /sys/class/thermal folder and + unbind all the thermal cooling devices it uses. + ++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 *) +--- a/drivers/thermal/thermal_core.c ++++ b/drivers/thermal/thermal_core.c +@@ -2061,6 +2061,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, }, +--- a/include/linux/thermal.h ++++ b/include/linux/thermal.h +@@ -432,6 +432,8 @@ thermal_of_cooling_device_register(struc + 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 *, +@@ -489,6 +491,12 @@ static inline struct thermal_zone_device + 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 * diff --git a/target/linux/ipq806x/patches-4.4/019-1-nvmem-core-return-error-for-non-word-aligned-access.patch b/target/linux/ipq806x/patches-4.4/019-1-nvmem-core-return-error-for-non-word-aligned-access.patch new file mode 100644 index 0000000000..13415f51d0 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/019-1-nvmem-core-return-error-for-non-word-aligned-access.patch @@ -0,0 +1,42 @@ +From 313a72ff983cc2e00ac4dcb791d40ebf2f9d5718 Mon Sep 17 00:00:00 2001 +From: Srinivas Kandagatla +Date: Tue, 17 Nov 2015 09:12:41 +0000 +Subject: nvmem: core: return error for non word aligned access + +nvmem providers have restrictions on register strides, so return error +when users attempt to read/write buffers with sizes which are less +than word size. + +Without this patch the userspace would continue to try as it does not +get any error from the nvmem core, resulting in a hang or endless loop +in userspace. + +Reported-by: Ariel D'Alessandro +Signed-off-by: Srinivas Kandagatla +Signed-off-by: Greg Kroah-Hartman +--- + drivers/nvmem/core.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/drivers/nvmem/core.c ++++ b/drivers/nvmem/core.c +@@ -70,6 +70,9 @@ static ssize_t bin_attr_nvmem_read(struc + if (pos >= nvmem->size) + return 0; + ++ if (count < nvmem->word_size) ++ return -EINVAL; ++ + if (pos + count > nvmem->size) + count = nvmem->size - pos; + +@@ -95,6 +98,9 @@ static ssize_t bin_attr_nvmem_write(stru + if (pos >= nvmem->size) + return 0; + ++ if (count < nvmem->word_size) ++ return -EINVAL; ++ + if (pos + count > nvmem->size) + count = nvmem->size - pos; + diff --git a/target/linux/ipq806x/patches-4.4/019-2-nvmem-core-fix-error-path-in-nvmem_add_cells.patch b/target/linux/ipq806x/patches-4.4/019-2-nvmem-core-fix-error-path-in-nvmem_add_cells.patch new file mode 100644 index 0000000000..1f9473bafd --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/019-2-nvmem-core-fix-error-path-in-nvmem_add_cells.patch @@ -0,0 +1,34 @@ +From dfdf141429f0895b63c882facc42c86f225033cb Mon Sep 17 00:00:00 2001 +From: Rasmus Villemoes +Date: Mon, 8 Feb 2016 22:04:29 +0100 +Subject: nvmem: core: fix error path in nvmem_add_cells() + +The current code fails to nvmem_cell_drop(cells[0]) - even worse, if +the loop above fails already at i==0, we'll enter an essentially +infinite loop doing nvmem_cell_drop on cells[-1], cells[-2], ... which +is unlikely to end well. + +Also, we're not freeing the temporary backing array cells on the error +path. + +Signed-off-by: Rasmus Villemoes +Signed-off-by: Greg Kroah-Hartman +--- + drivers/nvmem/core.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +--- a/drivers/nvmem/core.c ++++ b/drivers/nvmem/core.c +@@ -294,9 +294,11 @@ static int nvmem_add_cells(struct nvmem_ + + return 0; + err: +- while (--i) ++ while (i--) + nvmem_cell_drop(cells[i]); + ++ kfree(cells); ++ + return rval; + } + diff --git a/target/linux/ipq806x/patches-4.4/019-3-nvmem-Add-flag-to-export-NVMEM-to-root-only.patch b/target/linux/ipq806x/patches-4.4/019-3-nvmem-Add-flag-to-export-NVMEM-to-root-only.patch new file mode 100644 index 0000000000..77136eab72 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/019-3-nvmem-Add-flag-to-export-NVMEM-to-root-only.patch @@ -0,0 +1,101 @@ +From 811b0d6538b9f26f3eb0f90fe4e6118f2480ec6f Mon Sep 17 00:00:00 2001 +From: Andrew Lunn +Date: Fri, 26 Feb 2016 20:59:18 +0100 +Subject: nvmem: Add flag to export NVMEM to root only + +Legacy AT24, AT25 EEPROMs are exported in sys so that only root can +read the contents. The EEPROMs may contain sensitive information. Add +a flag so the provide can indicate that NVMEM should also restrict +access to root only. + +Signed-off-by: Andrew Lunn +Acked-by: Srinivas Kandagatla +Signed-off-by: Greg Kroah-Hartman +--- + drivers/nvmem/core.c | 57 ++++++++++++++++++++++++++++++++++++++++-- + include/linux/nvmem-provider.h | 1 + + 2 files changed, 56 insertions(+), 2 deletions(-) + +--- a/drivers/nvmem/core.c ++++ b/drivers/nvmem/core.c +@@ -161,6 +161,53 @@ static const struct attribute_group *nvm + NULL, + }; + ++/* default read/write permissions, root only */ ++static struct bin_attribute bin_attr_rw_root_nvmem = { ++ .attr = { ++ .name = "nvmem", ++ .mode = S_IWUSR | S_IRUSR, ++ }, ++ .read = bin_attr_nvmem_read, ++ .write = bin_attr_nvmem_write, ++}; ++ ++static struct bin_attribute *nvmem_bin_rw_root_attributes[] = { ++ &bin_attr_rw_root_nvmem, ++ NULL, ++}; ++ ++static const struct attribute_group nvmem_bin_rw_root_group = { ++ .bin_attrs = nvmem_bin_rw_root_attributes, ++}; ++ ++static const struct attribute_group *nvmem_rw_root_dev_groups[] = { ++ &nvmem_bin_rw_root_group, ++ NULL, ++}; ++ ++/* read only permission, root only */ ++static struct bin_attribute bin_attr_ro_root_nvmem = { ++ .attr = { ++ .name = "nvmem", ++ .mode = S_IRUSR, ++ }, ++ .read = bin_attr_nvmem_read, ++}; ++ ++static struct bin_attribute *nvmem_bin_ro_root_attributes[] = { ++ &bin_attr_ro_root_nvmem, ++ NULL, ++}; ++ ++static const struct attribute_group nvmem_bin_ro_root_group = { ++ .bin_attrs = nvmem_bin_ro_root_attributes, ++}; ++ ++static const struct attribute_group *nvmem_ro_root_dev_groups[] = { ++ &nvmem_bin_ro_root_group, ++ NULL, ++}; ++ + static void nvmem_release(struct device *dev) + { + struct nvmem_device *nvmem = to_nvmem_device(dev); +@@ -355,8 +402,14 @@ struct nvmem_device *nvmem_register(cons + nvmem->read_only = of_property_read_bool(np, "read-only") | + config->read_only; + +- nvmem->dev.groups = nvmem->read_only ? nvmem_ro_dev_groups : +- nvmem_rw_dev_groups; ++ if (config->root_only) ++ nvmem->dev.groups = nvmem->read_only ? ++ nvmem_ro_root_dev_groups : ++ nvmem_rw_root_dev_groups; ++ else ++ nvmem->dev.groups = nvmem->read_only ? ++ nvmem_ro_dev_groups : ++ nvmem_rw_dev_groups; + + device_initialize(&nvmem->dev); + +--- a/include/linux/nvmem-provider.h ++++ b/include/linux/nvmem-provider.h +@@ -23,6 +23,7 @@ struct nvmem_config { + const struct nvmem_cell_info *cells; + int ncells; + bool read_only; ++ bool root_only; + }; + + #if IS_ENABLED(CONFIG_NVMEM) diff --git a/target/linux/ipq806x/patches-4.4/019-4-nvmem-Add-backwards-compatibility-support-for-older-EEPROM-drivers.patch b/target/linux/ipq806x/patches-4.4/019-4-nvmem-Add-backwards-compatibility-support-for-older-EEPROM-drivers.patch new file mode 100644 index 0000000000..6344d0ed03 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/019-4-nvmem-Add-backwards-compatibility-support-for-older-EEPROM-drivers.patch @@ -0,0 +1,181 @@ +From b6c217ab9be6895384cf0b284ace84ad79e5c53b Mon Sep 17 00:00:00 2001 +From: Andrew Lunn +Date: Fri, 26 Feb 2016 20:59:19 +0100 +Subject: nvmem: Add backwards compatibility support for older EEPROM drivers. + +Older drivers made an 'eeprom' file available in the /sys device +directory. Have the NVMEM core provide this to retain backwards +compatibility. + +Signed-off-by: Andrew Lunn +Acked-by: Srinivas Kandagatla +Signed-off-by: Greg Kroah-Hartman +--- + drivers/nvmem/core.c | 84 ++++++++++++++++++++++++++++++++++++++---- + include/linux/nvmem-provider.h | 4 +- + 2 files changed, 79 insertions(+), 9 deletions(-) + +--- a/drivers/nvmem/core.c ++++ b/drivers/nvmem/core.c +@@ -38,8 +38,13 @@ struct nvmem_device { + int users; + size_t size; + bool read_only; ++ int flags; ++ struct bin_attribute eeprom; ++ struct device *base_dev; + }; + ++#define FLAG_COMPAT BIT(0) ++ + struct nvmem_cell { + const char *name; + int offset; +@@ -56,16 +61,26 @@ static DEFINE_IDA(nvmem_ida); + static LIST_HEAD(nvmem_cells); + static DEFINE_MUTEX(nvmem_cells_mutex); + ++#ifdef CONFIG_DEBUG_LOCK_ALLOC ++static struct lock_class_key eeprom_lock_key; ++#endif ++ + #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) + + static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t pos, size_t count) + { +- struct device *dev = container_of(kobj, struct device, kobj); +- struct nvmem_device *nvmem = to_nvmem_device(dev); ++ struct device *dev; ++ struct nvmem_device *nvmem; + int rc; + ++ if (attr->private) ++ dev = attr->private; ++ else ++ dev = container_of(kobj, struct device, kobj); ++ nvmem = to_nvmem_device(dev); ++ + /* Stop the user from reading */ + if (pos >= nvmem->size) + return 0; +@@ -90,10 +105,16 @@ static ssize_t bin_attr_nvmem_write(stru + struct bin_attribute *attr, + char *buf, loff_t pos, size_t count) + { +- struct device *dev = container_of(kobj, struct device, kobj); +- struct nvmem_device *nvmem = to_nvmem_device(dev); ++ struct device *dev; ++ struct nvmem_device *nvmem; + int rc; + ++ if (attr->private) ++ dev = attr->private; ++ else ++ dev = container_of(kobj, struct device, kobj); ++ nvmem = to_nvmem_device(dev); ++ + /* Stop the user from writing */ + if (pos >= nvmem->size) + return 0; +@@ -349,6 +370,43 @@ err: + return rval; + } + ++/* ++ * nvmem_setup_compat() - Create an additional binary entry in ++ * drivers sys directory, to be backwards compatible with the older ++ * drivers/misc/eeprom drivers. ++ */ ++static int nvmem_setup_compat(struct nvmem_device *nvmem, ++ const struct nvmem_config *config) ++{ ++ int rval; ++ ++ if (!config->base_dev) ++ return -EINVAL; ++ ++ if (nvmem->read_only) ++ nvmem->eeprom = bin_attr_ro_root_nvmem; ++ else ++ nvmem->eeprom = bin_attr_rw_root_nvmem; ++ nvmem->eeprom.attr.name = "eeprom"; ++ nvmem->eeprom.size = nvmem->size; ++#ifdef CONFIG_DEBUG_LOCK_ALLOC ++ nvmem->eeprom.attr.key = &eeprom_lock_key; ++#endif ++ nvmem->eeprom.private = &nvmem->dev; ++ nvmem->base_dev = config->base_dev; ++ ++ rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom); ++ if (rval) { ++ dev_err(&nvmem->dev, ++ "Failed to create eeprom binary file %d\n", rval); ++ return rval; ++ } ++ ++ nvmem->flags |= FLAG_COMPAT; ++ ++ return 0; ++} ++ + /** + * nvmem_register() - Register a nvmem device for given nvmem_config. + * Also creates an binary entry in /sys/bus/nvmem/devices/dev-name/nvmem +@@ -416,16 +474,23 @@ struct nvmem_device *nvmem_register(cons + dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); + + rval = device_add(&nvmem->dev); +- if (rval) { +- ida_simple_remove(&nvmem_ida, nvmem->id); +- kfree(nvmem); +- return ERR_PTR(rval); ++ if (rval) ++ goto out; ++ ++ if (config->compat) { ++ rval = nvmem_setup_compat(nvmem, config); ++ if (rval) ++ goto out; + } + + if (config->cells) + nvmem_add_cells(nvmem, config); + + return nvmem; ++out: ++ ida_simple_remove(&nvmem_ida, nvmem->id); ++ kfree(nvmem); ++ return ERR_PTR(rval); + } + EXPORT_SYMBOL_GPL(nvmem_register); + +@@ -445,6 +510,9 @@ int nvmem_unregister(struct nvmem_device + } + mutex_unlock(&nvmem_mutex); + ++ if (nvmem->flags & FLAG_COMPAT) ++ device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); ++ + nvmem_device_remove_all_cells(nvmem); + device_del(&nvmem->dev); + +--- a/include/linux/nvmem-provider.h ++++ b/include/linux/nvmem-provider.h +@@ -24,6 +24,9 @@ struct nvmem_config { + int ncells; + bool read_only; + bool root_only; ++ /* To be only used by old driver/misc/eeprom drivers */ ++ bool compat; ++ struct device *base_dev; + }; + + #if IS_ENABLED(CONFIG_NVMEM) +@@ -44,5 +47,4 @@ static inline int nvmem_unregister(struc + } + + #endif /* CONFIG_NVMEM */ +- + #endif /* ifndef _LINUX_NVMEM_PROVIDER_H */ diff --git a/target/linux/ipq806x/patches-4.4/309-clk-gcc-add-tsens-child-node.patch b/target/linux/ipq806x/patches-4.4/309-clk-gcc-add-tsens-child-node.patch new file mode 100644 index 0000000000..0eae3e7bfd --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/309-clk-gcc-add-tsens-child-node.patch @@ -0,0 +1,38 @@ +From 856371ca1561ca9b3280cc323ff296c7c5e1fa93 Mon Sep 17 00:00:00 2001 +From: Pavel Kubelun +Date: Tue, 22 Nov 2016 17:37:56 +0300 +Subject: [PATCH] ipq806x: clk: gcc: add tsens child node + +Thermal sensors in ipq806x are inside a Global clock controller. +Add a child node into it to be used by the TSENS driver. + +Signed-off-by: Pavel Kubelun + +--- + drivers/clk/qcom/gcc-ipq806x.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -3109,6 +3109,7 @@ MODULE_DEVICE_TABLE(of, gcc_ipq806x_matc + static int gcc_ipq806x_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; ++ struct platform_device *tsens; + struct regmap *regmap; + int ret; + +@@ -3138,6 +3139,13 @@ static int gcc_ipq806x_probe(struct plat + regmap_write(regmap, 0x3cf8, 8); + regmap_write(regmap, 0x3d18, 8); + ++ tsens = platform_device_register_data(&pdev->dev, "qcom-tsens", -1, ++ NULL, 0); ++ if (IS_ERR(tsens)) ++ return PTR_ERR(tsens); ++ ++ platform_set_drvdata(pdev, tsens); ++ + return 0; + } + diff --git a/target/linux/ipq806x/patches-4.4/310-add-necessary-thermal-data.patch b/target/linux/ipq806x/patches-4.4/310-add-necessary-thermal-data.patch new file mode 100644 index 0000000000..b2564a5407 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/310-add-necessary-thermal-data.patch @@ -0,0 +1,150 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -31,6 +31,9 @@ + clock-latency = <100000>; + cpu-supply = <&smb208_s2a>; + voltage-tolerance = <5>; ++ cooling-min-state = <0>; ++ cooling-max-state = <10>; ++ #cooling-cells = <2>; + cpu-idle-states = <&CPU_SPC>; + }; + +@@ -46,6 +49,9 @@ + clock-names = "cpu", "l2"; + clock-latency = <100000>; + cpu-supply = <&smb208_s2b>; ++ cooling-min-state = <0>; ++ cooling-max-state = <10>; ++ #cooling-cells = <2>; + cpu-idle-states = <&CPU_SPC>; + }; + +@@ -70,6 +76,92 @@ + }; + }; + ++ thermal-zones { ++ cpu-thermal0 { ++ polling-delay-passive = <250>; ++ polling-delay = <1000>; ++ ++ thermal-sensors = <&gcc 5>; ++ coefficients = <1132 0>; ++ ++ trips { ++ cpu_alert0: trip0 { ++ temperature = <75000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ cpu_crit0: trip1 { ++ temperature = <110000>; ++ hysteresis = <2000>; ++ type = "critical"; ++ }; ++ }; ++ }; ++ ++ cpu-thermal1 { ++ polling-delay-passive = <250>; ++ polling-delay = <1000>; ++ ++ thermal-sensors = <&gcc 6>; ++ coefficients = <1132 0>; ++ ++ trips { ++ cpu_alert1: trip0 { ++ temperature = <75000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ cpu_crit1: trip1 { ++ temperature = <110000>; ++ hysteresis = <2000>; ++ type = "critical"; ++ }; ++ }; ++ }; ++ ++ cpu-thermal2 { ++ polling-delay-passive = <250>; ++ polling-delay = <1000>; ++ ++ thermal-sensors = <&gcc 7>; ++ coefficients = <1199 0>; ++ ++ trips { ++ cpu_alert2: trip0 { ++ temperature = <75000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ cpu_crit2: trip1 { ++ temperature = <110000>; ++ hysteresis = <2000>; ++ type = "critical"; ++ }; ++ }; ++ }; ++ ++ cpu-thermal3 { ++ polling-delay-passive = <250>; ++ polling-delay = <1000>; ++ ++ thermal-sensors = <&gcc 8>; ++ coefficients = <1132 0>; ++ ++ trips { ++ cpu_alert3: trip0 { ++ temperature = <75000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ cpu_crit3: trip1 { ++ temperature = <110000>; ++ hysteresis = <2000>; ++ type = "critical"; ++ }; ++ }; ++ }; ++ }; ++ + cpu-pmu { + compatible = "qcom,krait-pmu"; + interrupts = <1 10 0x304>; +@@ -172,6 +264,21 @@ + reg-names = "lpass-lpaif"; + }; + ++ qfprom: qfprom@700000 { ++ compatible = "qcom,qfprom", "syscon"; ++ reg = <0x00700000 0x1000>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ ++ tsens_calib: calib { ++ reg = <0x400 0x10>; ++ }; ++ tsens_backup: backup_calib { ++ reg = <0x410 0x10>; ++ }; ++ }; ++ + rpm@108000 { + compatible = "qcom,rpm-ipq8064"; + reg = <0x108000 0x1000>; +@@ -499,8 +606,12 @@ + gcc: clock-controller@900000 { + compatible = "qcom,gcc-ipq8064"; + reg = <0x00900000 0x4000>; ++ nvmem-cells = <&tsens_calib>, <&tsens_backup>; ++ nvmem-cell-names = "calib", "calib_backup"; + #clock-cells = <1>; + #reset-cells = <1>; ++ #power-domain-cells = <1>; ++ #thermal-sensor-cells = <1>; + }; + + tcsr: syscon@1a400000 { diff --git a/target/linux/ipq806x/patches-4.4/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-4.4/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch index aa12121dff..f6f357253f 100644 --- a/target/linux/ipq806x/patches-4.4/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch +++ b/target/linux/ipq806x/patches-4.4/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch @@ -121,7 +121,7 @@ Signed-off-by: Mathieu Olivari }; --- a/arch/arm/boot/dts/qcom-ipq8064.dtsi +++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi -@@ -793,6 +793,92 @@ +@@ -904,6 +904,92 @@ status = "disabled"; };