diff options
author | dissent1 <be.dissent+github@gmail.com> | 2016-11-05 16:15:33 +0300 |
---|---|---|
committer | John Crispin <john@phrozen.org> | 2016-12-01 15:47:43 +0100 |
commit | fef6a96d9e9e001a28b6a673c56ed79029457b5d (patch) | |
tree | 808f89926a7a7a2b3b29564e2070592ef983d948 /target/linux/ipq806x/patches-4.4/015-1-thermal-qcom-tsens-Add-a-skeletal-TSENS-drivers.patch | |
parent | 2b71f958b1d2022bd0c4dcaa458f5ba1b955f934 (diff) | |
download | upstream-fef6a96d9e9e001a28b6a673c56ed79029457b5d.tar.gz upstream-fef6a96d9e9e001a28b6a673c56ed79029457b5d.tar.bz2 upstream-fef6a96d9e9e001a28b6a673c56ed79029457b5d.zip |
ipq806x: add thermal sensor driver
Allows to check cpu temperature.
Huge thanks to @hnyman for valuable assistance!
Signed-off-by: Pavel Kubelun <be.dissent@gmail.com>
Diffstat (limited to 'target/linux/ipq806x/patches-4.4/015-1-thermal-qcom-tsens-Add-a-skeletal-TSENS-drivers.patch')
-rw-r--r-- | target/linux/ipq806x/patches-4.4/015-1-thermal-qcom-tsens-Add-a-skeletal-TSENS-drivers.patch | 536 |
1 files changed, 536 insertions, 0 deletions
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 <rnayak@codeaurora.org> +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 <rnayak@codeaurora.org> +Reviewed-by: Lina Iyer <lina.iyer@linaro.org> +Signed-off-by: Eduardo Valentin <edubezval@gmail.com> +Signed-off-by: Zhang Rui <rui.zhang@intel.com> +--- + .../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 <linux/err.h> ++#include <linux/io.h> ++#include <linux/nvmem-consumer.h> ++#include <linux/of_address.h> ++#include <linux/platform_device.h> ++#include <linux/regmap.h> ++#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 <linux/err.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/pm.h> ++#include <linux/slab.h> ++#include <linux/thermal.h> ++#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__ */ |