aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSrinivas Kandagatla <srinivas.kandagatla@linaro.org>2015-01-30 17:42:05 +0000
committerSrinivas Kandagatla <srinivas.kandagatla@linaro.org>2015-01-30 17:42:05 +0000
commite37b32df53fb60634cc189f14b4bc21be517ce30 (patch)
tree4331fec8b5a115c70f79d9534c960c406e0fb5d0
parent35d233256a2c4359ec474ae64d4332e436899100 (diff)
parent279fe9083762118a8dd87e41b8ec5149996b5a57 (diff)
Merge branch 'tracking-qcomlt-tsens' into integration-linux-qcomltqcomlt-v3.19-rc6-30012015
* tracking-qcomlt-tsens: thermal: qcom: Add 8960 thermal sensor support WIP: Add wrappers for qfprom access via syscon WIP: mfd: syscon: Add register stride to DT bindings. Conflicts: drivers/soc/qcom/Kconfig drivers/soc/qcom/Makefile
-rw-r--r--Documentation/devicetree/bindings/mfd/syscon.txt3
-rw-r--r--Documentation/devicetree/bindings/soc/qcom/qfprom.txt29
-rw-r--r--drivers/mfd/syscon.c9
-rw-r--r--drivers/soc/qcom/Kconfig7
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/qfprom.c134
-rw-r--r--drivers/thermal/Kconfig11
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/msm8960_tsens.c740
-rw-r--r--include/linux/soc/qcom/qfprom.h38
10 files changed, 973 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/mfd/syscon.txt b/Documentation/devicetree/bindings/mfd/syscon.txt
index fe8150bb3248..7f06ec1ff591 100644
--- a/Documentation/devicetree/bindings/mfd/syscon.txt
+++ b/Documentation/devicetree/bindings/mfd/syscon.txt
@@ -13,6 +13,9 @@ Required properties:
- compatible: Should contain "syscon".
- reg: the register region can be accessed from syscon
+Optional properties:
+- stride : register address stride in bytes.
+
Examples:
gpr: iomuxc-gpr@020e0000 {
compatible = "fsl,imx6q-iomuxc-gpr", "syscon";
diff --git a/Documentation/devicetree/bindings/soc/qcom/qfprom.txt b/Documentation/devicetree/bindings/soc/qcom/qfprom.txt
new file mode 100644
index 000000000000..3ed73090ad09
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qfprom.txt
@@ -0,0 +1,29 @@
+QCOM QFPROM
+
+QFPROM is basically some efuses where things like calibration data, speed bins,
+etc are stored. This data is accessed by various drivers like the cpufreq,
+thermal, etc.
+
+Required properties:
+- compatible: must contain "qcom,qfprom" followed by "syscon"
+- reg: Address range for QFPROM
+- stride : register address stride.
+ 1 for byte.
+ 2 for 2 bytes
+ 3 for 3 bytes
+ 4 for a word.
+
+
+Example:
+ qfprom: qfprom@00700000 {
+ compatible = "qcom,qfprom", "syscon";
+ reg = <0x00700000 0x1000>;
+ stride = <1>;
+ };
+
+ tsens@34000 {
+ compatible = "qcom,tsens-apq8064";
+ reg = <0x34000 0x1000>;
+ qcom,qfprom = <&qfprom 0x18 0x10>, <&qfprom 0x28 0x10>;
+ qcom,qfprom-names = "calib", "backup_calib";
+ };
diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c
index 176bf0fa2685..98769d554f1c 100644
--- a/drivers/mfd/syscon.c
+++ b/drivers/mfd/syscon.c
@@ -48,6 +48,7 @@ static struct syscon *of_syscon_register(struct device_node *np)
struct regmap *regmap;
void __iomem *base;
int ret;
+ u32 stride;
struct regmap_config syscon_config = syscon_regmap_config;
if (!of_device_is_compatible(np, "syscon"))
@@ -69,6 +70,14 @@ static struct syscon *of_syscon_register(struct device_node *np)
else if (of_property_read_bool(np, "little-endian"))
syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE;
+ if (!of_property_read_u32(np, "stride", &stride)) {
+ if (stride > 4)
+ stride = 4;
+
+ syscon_config.reg_stride = stride;
+ syscon_config.val_bits = 8 * stride;
+ }
+
regmap = regmap_init_mmio(NULL, base, &syscon_config);
if (IS_ERR(regmap)) {
pr_err("regmap init failed\n");
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 012fb37b3ba9..389ec3ea7190 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -19,3 +19,10 @@ config QCOM_PM
QCOM Platform specific power driver to manage cores and L2 low power
modes. It interface with various system drivers to put the cores in
low power modes.
+
+config QCOM_QFPROM
+ tristate "QCOM QFPROM Interface"
+ depends on ARCH_QCOM && OF
+ help
+ Say y here to enable QFPROM support. The QFPROM provides access
+ functions for QFPROM data to rest of the drivers via syscon wrappers.
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 4d4ff4a09b7d..54249f557778 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
obj-$(CONFIG_QCOM_PM) += spm.o
CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o scm-pas.o
+obj-$(CONFIG_QCOM_QFPROM) += qfprom.o
diff --git a/drivers/soc/qcom/qfprom.c b/drivers/soc/qcom/qfprom.c
new file mode 100644
index 000000000000..d00ed25235d7
--- /dev/null
+++ b/drivers/soc/qcom/qfprom.c
@@ -0,0 +1,134 @@
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/soc/qcom/qfprom.h>
+#include <linux/slab.h>
+
+#define QFPROM_MAX_ARGS 2
+
+static char *__qfprom_get_data(struct device *dev,
+ bool devm, int idx, int *len)
+{
+ struct device_node *syscon_np, *np = dev->of_node;
+ struct regmap *rm;
+ struct of_phandle_args args;
+ int rc, stride = 4;
+ u32 offset, size;
+ char *data;
+
+ if (!np)
+ return ERR_PTR(-EINVAL);
+
+ rc = of_parse_phandle_with_fixed_args(np, "qcom,qfprom",
+ QFPROM_MAX_ARGS, idx, &args);
+ if (rc)
+ return ERR_PTR(rc);
+
+ syscon_np = args.np;
+
+ of_property_read_u32(syscon_np, "stride", &stride);
+
+ if (stride >= 4)
+ stride = 4;
+
+ if (args.args_count < QFPROM_MAX_ARGS) {
+ dev_err(dev, "Insufficient qfprom arguments %d\n",
+ args.args_count);
+ return ERR_PTR(-EINVAL);
+ }
+
+ rm = syscon_node_to_regmap(syscon_np);
+ if (IS_ERR(rm))
+ return ERR_CAST(rm);
+
+ offset = args.args[0];
+ size = args.args[1];
+
+ of_node_put(syscon_np);
+
+ if (devm)
+ data = devm_kzalloc(dev, size, GFP_KERNEL | GFP_ATOMIC);
+ else
+ data = kzalloc(size, GFP_KERNEL | GFP_ATOMIC);
+
+ if (!data)
+ return ERR_PTR(-ENOMEM);
+
+ rc = regmap_bulk_read(rm, offset, data, size/stride);
+ if (rc < 0) {
+ if (devm)
+ devm_kfree(dev, data);
+ else
+ kfree(data);
+
+ return ERR_PTR(rc);
+ }
+
+ *len = size;
+
+ return data;
+}
+
+static char *__qfprom_get_data_byname(struct device *dev,
+ bool devm, const char *name, int *len)
+{
+ int index = 0;
+
+ if (name)
+ index = of_property_match_string(dev->of_node,
+ "qcom,qfprom-names", name);
+
+ return __qfprom_get_data(dev, devm, index, len);
+}
+
+char *devm_qfprom_get_data_byname(struct device *dev,
+ const char *name, int *len)
+{
+ return __qfprom_get_data_byname(dev, true, name, len);
+}
+EXPORT_SYMBOL_GPL(devm_qfprom_get_data_byname);
+
+char *devm_qfprom_get_data(struct device *dev,
+ int index, int *len)
+{
+ return __qfprom_get_data(dev, true, index, len);
+}
+EXPORT_SYMBOL_GPL(devm_qfprom_get_data);
+
+/**
+ * qfprom_get_data_byname(): Reads qfprom data by name
+ *
+ * @dev: device which is requesting qfprom data
+ * @index: name of qfprom resources specified "qcom,qfprom-names" DT property.
+ * @len: length of data read from qfprom.
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a data buffer. The buffer should be freed by the user once its
+ * finished working with it kfree.
+ **/
+char *qfprom_get_data_byname(struct device *dev,
+ const char *name, int *len)
+{
+ return __qfprom_get_data_byname(dev, false, name, len);
+}
+EXPORT_SYMBOL_GPL(qfprom_get_data_byname);
+
+/**
+ * qfprom_get_data(): Reads qfprom data from the index
+ *
+ * @dev: device which is requesting qfprom data
+ * @index: index into qfprom resources specified "qcom,qfprom" DT property.
+ * @len: length of data read from qfprom.
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a data buffer. The buffer should be freed by the user once its
+ * finished working with it kfree.
+ **/
+char *qfprom_get_data(struct device *dev,
+ int index, int *len)
+{
+ return __qfprom_get_data(dev, false, index, len);
+}
+EXPORT_SYMBOL_GPL(qfprom_get_data);
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index af40db0df58e..4b4346da8d44 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -281,6 +281,17 @@ config INT340X_THERMAL
information to allow the user to select his laptop to run without
turning on the fans.
+config THERMAL_TSENS8960
+ tristate "Qualcomm 8960 Tsens Temperature Alarm"
+ depends on THERMAL
+ select QCOM_QFPROM
+ help
+ This enables the thermal sysfs driver for the Tsens device. It shows
+ up in Sysfs as a thermal zone with mutiple 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.
+
config ACPI_THERMAL_REL
tristate
depends on ACPI
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fa0dc486790f..2e695c0da885 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -35,6 +35,7 @@ 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
obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o
+obj-$(CONFIG_THERMAL_TSENS8960) += msm8960_tsens.o
obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
obj-$(CONFIG_ST_THERMAL) += st/
diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c
new file mode 100644
index 000000000000..f13f10770cde
--- /dev/null
+++ b/drivers/thermal/msm8960_tsens.c
@@ -0,0 +1,740 @@
+/*
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/thermal.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/pm.h>
+#include <linux/soc/qcom/qfprom.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+
+#define TSENS_CNTL_ADDR 0x3620
+#define TSENS_EN BIT(0)
+#define TSENS_SW_RST BIT(1)
+#define TSENS_ADC_CLK_SEL BIT(2)
+#define SENSOR0_EN BIT(3)
+#define TSENS_SENSOR0_SHIFT 3
+#define TSENS_MASK1 1
+#define SENSORS_EN_MASK(n) GENMASK((n + 2), 3)
+
+#define TSENS_TH_ADDR 0x3624
+#define TSENS_TH_MAX_LIMIT_SHIFT 24
+#define TSENS_TH_MIN_LIMIT_SHIFT 16
+#define TSENS_TH_UPPER_LIMIT_SHIFT 8
+#define TSENS_TH_LOWER_LIMIT_SHIFT 0
+#define TSENS_TH_MAX_LIMIT_MASK GENMASK(31, 24)
+#define TSENS_TH_MIN_LIMIT_MASK GENMASK(23, 16)
+#define TSENS_TH_UPPER_LIMIT_MASK GENMASK(15, 8)
+#define TSENS_TH_LOWER_LIMIT_MASK GENMASK(7, 0)
+
+#define TSENS_S0_STATUS_ADDR 0x3628
+#define TSENS_STATUS_ADDR_OFFSET 2
+
+#define TSENS_INT_STATUS_ADDR 0x363c
+#define TSENS_LOWER_INT_MASK BIT(1)
+#define TSENS_UPPER_INT_MASK BIT(2)
+#define TSENS_MAX_INT_MASK BIT(3)
+#define TSENS_TRDY_MASK BIT(7)
+
+#define TSENS_TH_MAX_CODE 0xff
+#define TSENS_TH_MIN_CODE 0
+#define TSENS_TRDY_RDY_MIN_TIME 1000
+#define TSENS_TRDY_RDY_MAX_TIME 1100
+
+#define TSENS_MEASURE_PERIOD 1
+/* Initial temperature threshold values */
+#define TSENS_LOWER_LIMIT_TH 0x50
+#define TSENS_UPPER_LIMIT_TH 0xdf
+#define TSENS_MIN_LIMIT_TH 0x0
+#define TSENS_MAX_LIMIT_TH 0xff
+
+#define TSENS_MIN_STATUS_MASK(offset) BIT((offset))
+#define TSENS_LOWER_STATUS_CLR(offset) BIT((offset + 1))
+#define TSENS_UPPER_STATUS_CLR(offset) BIT((offset + 2))
+#define TSENS_MAX_STATUS_MASK(offset) BIT((offset + 3))
+
+/* QFPROM addresses */
+#define TSENS_8960_QFPROM_ADDR0 0x0
+#define TSENS_8960_QFPROM_SPARE_OFFSET 0x10
+/* 8960 Specifics */
+#define TSENS_8960_CONFIG 0x9b
+#define TSENS_8960_CONFIG_MASK GENMASK(7, 0)
+#define TSENS_8960_CONFIG_ADDR 0x3640
+#define TSENS_8064_STATUS_CNTL 0x3660
+#define TSENS_8064_SEQ_SENSORS 5
+#define TSENS_8064_S4_S5_OFFSET 40
+
+#define TSENS_CAL_MILLI_DEGC 30000
+#define TSENS_MAX_SENSORS 11
+
+/* Trips: from very hot to very cold */
+enum tsens_trip_type {
+ TSENS_TRIP_STAGE3 = 0,
+ TSENS_TRIP_STAGE2,
+ TSENS_TRIP_STAGE1,
+ TSENS_TRIP_STAGE0,
+ TSENS_TRIP_NUM,
+};
+
+struct tsens_tm_device;
+
+struct tsens_tm_device_sensor {
+ struct thermal_zone_device *tzone;
+ enum thermal_device_mode mode;
+ struct tsens_tm_device *tmdev;
+ unsigned int sensor_num;
+ int offset;
+ uint32_t slope;
+ bool user_zone;
+};
+
+struct tsens_variant_data {
+ int slope[TSENS_MAX_SENSORS];
+ u32 nsensors;
+ /* enable to tsens slp clock */
+ u32 slp_clk_ena;
+ u32 sctrl_reg;
+ u32 sctrl_offset;
+ u32 config_reg;
+ u32 config_mask;
+ u32 config;
+ int (*calib_sensors)(struct tsens_tm_device *tmdev, struct device *dev);
+};
+
+struct tsens_tm_device {
+ struct regmap *base;
+ struct tsens_variant_data *data;
+ struct work_struct tsens_work;
+ int nsensors;
+ int irq;
+ /* tsens ready bit for reading valid temperature */
+ bool trdy;
+ struct tsens_tm_device_sensor sensor[0];
+};
+
+/* Temperature on y axis and ADC-code on x-axis */
+static inline int tsens_code_to_degc(struct tsens_tm_device_sensor *s,
+ int adc_code)
+{
+ return adc_code * s->slope + s->offset;
+}
+
+static inline int tsens_degc_to_code(struct tsens_tm_device_sensor *s, int degc)
+{
+ return (degc - s->offset + s->slope / 2) / s->slope;
+}
+
+static int __tsens_get_temp(void *data, long *temp)
+{
+ struct tsens_tm_device_sensor *tm_sensor = data;
+ unsigned int code, offset = 0;
+ struct tsens_tm_device *tmdev = tm_sensor->tmdev;
+ int sensor_num = tm_sensor->sensor_num;
+ struct regmap *base = tmdev->base;
+
+ if (!tmdev->trdy) {
+ u32 val;
+
+ regmap_read(base, TSENS_INT_STATUS_ADDR, &val);
+
+ while (!(val & TSENS_TRDY_MASK)) {
+ usleep_range(TSENS_TRDY_RDY_MIN_TIME,
+ TSENS_TRDY_RDY_MAX_TIME);
+ regmap_read(base, TSENS_INT_STATUS_ADDR, &val);
+ }
+ tmdev->trdy = true;
+ }
+//FIXME
+ if (sensor_num >= TSENS_8064_SEQ_SENSORS)
+ offset = TSENS_8064_S4_S5_OFFSET;
+
+ regmap_read(base, TSENS_S0_STATUS_ADDR + offset +
+ (sensor_num << TSENS_STATUS_ADDR_OFFSET), &code);
+ *temp = tsens_code_to_degc(tm_sensor, code);
+
+ return 0;
+}
+
+static int tsens_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+
+ if (!tm_sensor || tm_sensor->mode != THERMAL_DEVICE_ENABLED)
+ return -EINVAL;
+
+ return __tsens_get_temp(tm_sensor, temp);
+
+}
+
+static int tsens_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+
+ if (tm_sensor)
+ *mode = tm_sensor->mode;
+
+ return 0;
+}
+
+/**
+ * Function to enable the mode.
+ * If the main sensor is disabled all the sensors are disable and
+ * the clock is disabled.
+ * If the main sensor is not enabled and sub sensor is enabled
+ * returns with an error stating the main sensor is not enabled.
+ **/
+
+static int tsens_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+ struct tsens_tm_device *tmdev = tm_sensor->tmdev;
+ struct tsens_variant_data *data = tmdev->data;
+ struct regmap *base = tmdev->base;
+ unsigned int reg, mask, i;
+
+ if (!tm_sensor)
+ return -EINVAL;
+
+ if (mode != tm_sensor->mode) {
+ regmap_read(base, TSENS_CNTL_ADDR, &reg);
+ mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
+ if (mode == THERMAL_DEVICE_ENABLED) {
+ if ((mask != SENSOR0_EN) && !(reg & SENSOR0_EN)) {
+ pr_info("Main sensor not enabled\n");
+ return -EINVAL;
+ }
+ regmap_write(base, TSENS_CNTL_ADDR, reg | TSENS_SW_RST);
+ reg |= mask | data->slp_clk_ena | TSENS_EN;
+ tmdev->trdy = false;
+ } else {
+ reg &= ~mask;
+ if (!(reg & SENSOR0_EN)) {
+ reg &= ~(SENSORS_EN_MASK(tmdev->nsensors) |
+ data->slp_clk_ena |
+ TSENS_EN);
+
+ for (i = 1; i < tmdev->nsensors; i++)
+ tmdev->sensor[i].mode = mode;
+
+ }
+ }
+ regmap_write(base, TSENS_CNTL_ADDR, reg);
+ }
+ tm_sensor->mode = mode;
+
+ return 0;
+}
+
+static int tsens_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+
+ if (!tm_sensor || trip < 0 || !type)
+ return -EINVAL;
+
+ switch (trip) {
+ case TSENS_TRIP_STAGE3:
+ *type = THERMAL_TRIP_CRITICAL;
+ break;
+ case TSENS_TRIP_STAGE2:
+ *type = THERMAL_TRIP_HOT;
+ break;
+ case TSENS_TRIP_STAGE1:
+ *type = THERMAL_TRIP_PASSIVE;
+ break;
+ case TSENS_TRIP_STAGE0:
+ *type = THERMAL_TRIP_ACTIVE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tsens_get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, unsigned long *temp)
+{
+ struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+ struct tsens_tm_device *tmdev = tm_sensor->tmdev;
+ unsigned int reg;
+
+ if (!tm_sensor || trip < 0 || !temp)
+ return -EINVAL;
+
+ regmap_read(tmdev->base, TSENS_TH_ADDR, &reg);
+ switch (trip) {
+ case TSENS_TRIP_STAGE3:
+ reg = (reg & TSENS_TH_MAX_LIMIT_MASK)
+ >> TSENS_TH_MAX_LIMIT_SHIFT;
+ break;
+ case TSENS_TRIP_STAGE2:
+ reg = (reg & TSENS_TH_UPPER_LIMIT_MASK)
+ >> TSENS_TH_UPPER_LIMIT_SHIFT;
+ break;
+ case TSENS_TRIP_STAGE1:
+ reg = (reg & TSENS_TH_LOWER_LIMIT_MASK)
+ >> TSENS_TH_LOWER_LIMIT_SHIFT;
+ break;
+ case TSENS_TRIP_STAGE0:
+ reg = (reg & TSENS_TH_MIN_LIMIT_MASK)
+ >> TSENS_TH_MIN_LIMIT_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *temp = tsens_code_to_degc(tm_sensor, reg);
+
+ return 0;
+}
+
+static int tsens_set_trip_temp(struct thermal_zone_device *thermal,
+ int trip, unsigned long temp)
+{
+ struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+ unsigned int reg_th, reg_cntl;
+ int code, hi_code, lo_code, code_err_chk;
+ struct tsens_tm_device *tmdev = tm_sensor->tmdev;
+ struct regmap *base = tmdev->base;
+ struct tsens_variant_data *data = tmdev->data;
+ u32 offset = data->sctrl_offset;
+
+ code_err_chk = code = tsens_degc_to_code(tm_sensor, temp);
+ if (!tm_sensor || trip < 0)
+ return -EINVAL;
+
+ lo_code = TSENS_TH_MIN_CODE;
+ hi_code = TSENS_TH_MAX_CODE;
+
+ regmap_read(base, data->sctrl_reg, &reg_cntl);
+
+ regmap_read(base, TSENS_TH_ADDR, &reg_th);
+ switch (trip) {
+ case TSENS_TRIP_STAGE3:
+ code <<= TSENS_TH_MAX_LIMIT_SHIFT;
+ reg_th &= ~TSENS_TH_MAX_LIMIT_MASK;
+
+ if (!(reg_cntl & TSENS_UPPER_STATUS_CLR(offset)))
+ lo_code = (reg_th & TSENS_TH_UPPER_LIMIT_MASK)
+ >> TSENS_TH_UPPER_LIMIT_SHIFT;
+ else if (!(reg_cntl & TSENS_LOWER_STATUS_CLR(offset)))
+ lo_code = (reg_th & TSENS_TH_LOWER_LIMIT_MASK)
+ >> TSENS_TH_LOWER_LIMIT_SHIFT;
+ else if (!(reg_cntl & TSENS_MIN_STATUS_MASK(offset)))
+ lo_code = (reg_th & TSENS_TH_MIN_LIMIT_MASK)
+ >> TSENS_TH_MIN_LIMIT_SHIFT;
+ break;
+ case TSENS_TRIP_STAGE2:
+ code <<= TSENS_TH_UPPER_LIMIT_SHIFT;
+ reg_th &= ~TSENS_TH_UPPER_LIMIT_MASK;
+
+ if (!(reg_cntl & TSENS_MAX_STATUS_MASK(offset)))
+ hi_code = (reg_th & TSENS_TH_MAX_LIMIT_MASK)
+ >> TSENS_TH_MAX_LIMIT_SHIFT;
+ if (!(reg_cntl & TSENS_LOWER_STATUS_CLR(offset)))
+ lo_code = (reg_th & TSENS_TH_LOWER_LIMIT_MASK)
+ >> TSENS_TH_LOWER_LIMIT_SHIFT;
+ else if (!(reg_cntl & TSENS_MIN_STATUS_MASK(offset)))
+ lo_code = (reg_th & TSENS_TH_MIN_LIMIT_MASK)
+ >> TSENS_TH_MIN_LIMIT_SHIFT;
+ break;
+ case TSENS_TRIP_STAGE1:
+ code <<= TSENS_TH_LOWER_LIMIT_SHIFT;
+ reg_th &= ~TSENS_TH_LOWER_LIMIT_MASK;
+
+ if (!(reg_cntl & TSENS_MIN_STATUS_MASK(offset)))
+ lo_code = (reg_th & TSENS_TH_MIN_LIMIT_MASK)
+ >> TSENS_TH_MIN_LIMIT_SHIFT;
+ if (!(reg_cntl & TSENS_UPPER_STATUS_CLR(offset)))
+ hi_code = (reg_th & TSENS_TH_UPPER_LIMIT_MASK)
+ >> TSENS_TH_UPPER_LIMIT_SHIFT;
+ else if (!(reg_cntl & TSENS_MAX_STATUS_MASK(offset)))
+ hi_code = (reg_th & TSENS_TH_MAX_LIMIT_MASK)
+ >> TSENS_TH_MAX_LIMIT_SHIFT;
+ break;
+ case TSENS_TRIP_STAGE0:
+ code <<= TSENS_TH_MIN_LIMIT_SHIFT;
+ reg_th &= ~TSENS_TH_MIN_LIMIT_MASK;
+
+ if (!(reg_cntl & TSENS_LOWER_STATUS_CLR(offset)))
+ hi_code = (reg_th & TSENS_TH_LOWER_LIMIT_MASK)
+ >> TSENS_TH_LOWER_LIMIT_SHIFT;
+ else if (!(reg_cntl & TSENS_UPPER_STATUS_CLR(offset)))
+ hi_code = (reg_th & TSENS_TH_UPPER_LIMIT_MASK)
+ >> TSENS_TH_UPPER_LIMIT_SHIFT;
+ else if (!(reg_cntl & TSENS_MAX_STATUS_MASK(offset)))
+ hi_code = (reg_th & TSENS_TH_MAX_LIMIT_MASK)
+ >> TSENS_TH_MAX_LIMIT_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (code_err_chk < lo_code || code_err_chk > hi_code)
+ return -EINVAL;
+
+ regmap_write(base, TSENS_TH_ADDR, reg_th | code);
+
+ return 0;
+}
+
+static int tsens_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ return tsens_get_trip_temp(thermal, TSENS_TRIP_STAGE3, temp);
+}
+
+static struct thermal_zone_device_ops tsens_thermal_zone_ops = {
+ .get_temp = tsens_get_temp,
+ .get_mode = tsens_get_mode,
+ .set_mode = tsens_set_mode,
+ .get_trip_type = tsens_get_trip_type,
+ .get_trip_temp = tsens_get_trip_temp,
+ .set_trip_temp = tsens_set_trip_temp,
+ .get_crit_temp = tsens_get_crit_temp,
+};
+
+static const struct thermal_zone_of_device_ops tsens_of_thermal_ops = {
+ .get_temp = __tsens_get_temp,
+};
+
+static void tsens_scheduler_fn(struct work_struct *work)
+{
+ struct tsens_tm_device *tmdev = container_of(work,
+ struct tsens_tm_device, tsens_work);
+ unsigned int threshold, threshold_low, i, code, reg, sensor, mask;
+ unsigned int sensor_addr;
+ bool upper_th_x, lower_th_x;
+ struct regmap *base = tmdev->base;
+ struct tsens_variant_data *data = tmdev->data;
+ u32 offset = data->sctrl_offset;
+
+ regmap_read(base, data->sctrl_reg, &reg);
+ regmap_write(base, data->sctrl_reg, reg |
+ TSENS_LOWER_STATUS_CLR(offset) |
+ TSENS_UPPER_STATUS_CLR(offset));
+
+ mask = ~(TSENS_LOWER_STATUS_CLR(offset) | TSENS_UPPER_STATUS_CLR(offset));
+ regmap_read(tmdev->base, TSENS_TH_ADDR, &threshold);
+ threshold_low = (threshold & TSENS_TH_LOWER_LIMIT_MASK)
+ >> TSENS_TH_LOWER_LIMIT_SHIFT;
+ threshold = (threshold & TSENS_TH_UPPER_LIMIT_MASK)
+ >> TSENS_TH_UPPER_LIMIT_SHIFT;
+
+ regmap_read(base, TSENS_CNTL_ADDR, &sensor);
+ sensor &= SENSORS_EN_MASK(tmdev->nsensors);
+ sensor >>= TSENS_SENSOR0_SHIFT;
+ sensor_addr = TSENS_S0_STATUS_ADDR;
+ for (i = 0; i < tmdev->nsensors; i++) {
+ if (i == TSENS_8064_SEQ_SENSORS)
+ sensor_addr += TSENS_8064_S4_S5_OFFSET;
+ if (sensor & TSENS_MASK1) {
+ regmap_read(base, sensor_addr, &code);
+ upper_th_x = code >= threshold;
+ lower_th_x = code <= threshold_low;
+ if (upper_th_x)
+ mask |= TSENS_UPPER_STATUS_CLR(offset);
+ if (lower_th_x)
+ mask |= TSENS_LOWER_STATUS_CLR(offset);
+ if (upper_th_x || lower_th_x)
+ thermal_zone_device_update(tmdev->sensor[i].tzone);
+ }
+ sensor >>= 1;
+ sensor_addr += 4;
+ }
+ regmap_read(base, data->sctrl_reg, &reg);
+ regmap_write(base, data->sctrl_reg, reg & mask);
+}
+
+static irqreturn_t tsens_isr(int irq, void *dev)
+{
+ struct tsens_tm_device *tmdev = dev;
+
+ schedule_work(&tmdev->tsens_work);
+
+ return IRQ_HANDLED;
+}
+
+static void tsens_disable_mode(struct tsens_tm_device *tmdev)
+{
+ unsigned int reg_cntl = 0;
+ struct regmap *base = tmdev->base;
+ struct tsens_variant_data *data = tmdev->data;
+
+ regmap_read(base, TSENS_CNTL_ADDR, &reg_cntl);
+ regmap_write(base, TSENS_CNTL_ADDR, reg_cntl &
+ ~(SENSORS_EN_MASK(tmdev->nsensors) | data->slp_clk_ena
+ | TSENS_EN));
+}
+
+static void tsens_hw_init(struct tsens_tm_device *tmdev)
+{
+ unsigned int reg_cntl = 0, reg_cfg = 0, reg_thr = 0;
+ unsigned int reg_status_cntl = 0;
+ struct regmap *base = tmdev->base;
+ struct tsens_variant_data *data = tmdev->data;
+ u32 offset = data->sctrl_offset;
+
+ regmap_read(base, TSENS_CNTL_ADDR, &reg_cntl);
+ regmap_write(base, TSENS_CNTL_ADDR, reg_cntl | TSENS_SW_RST);
+
+ reg_cntl |= data->slp_clk_ena |
+ (TSENS_MEASURE_PERIOD << 18) |
+ SENSORS_EN_MASK(tmdev->nsensors);
+ regmap_write(base, TSENS_CNTL_ADDR, reg_cntl);
+
+ regmap_read(base, data->sctrl_reg, &reg_status_cntl);
+ reg_status_cntl |= TSENS_LOWER_STATUS_CLR(offset) |
+ TSENS_UPPER_STATUS_CLR(offset) |
+ TSENS_MIN_STATUS_MASK(offset) |
+ TSENS_MAX_STATUS_MASK(offset);
+
+ regmap_write(base, data->sctrl_reg, reg_status_cntl);
+
+
+ reg_cntl |= TSENS_EN;
+ regmap_write(base, TSENS_CNTL_ADDR, reg_cntl);
+
+ regmap_read(base, data->config_reg, &reg_cfg);
+ reg_cfg = (reg_cfg & ~data->config_mask) | data->config;
+ regmap_write(base, data->config_reg, reg_cfg);
+
+ reg_thr |= (TSENS_LOWER_LIMIT_TH << TSENS_TH_LOWER_LIMIT_SHIFT) |
+ (TSENS_UPPER_LIMIT_TH << TSENS_TH_UPPER_LIMIT_SHIFT) |
+ (TSENS_MIN_LIMIT_TH << TSENS_TH_MIN_LIMIT_SHIFT) |
+ (TSENS_MAX_LIMIT_TH << TSENS_TH_MAX_LIMIT_SHIFT);
+ regmap_write(base, TSENS_TH_ADDR, reg_thr);
+}
+
+static int tsens_8960_calib_sensors(struct tsens_tm_device *tmdev, struct device *dev)
+{
+
+ uint8_t calib_data, calib_data_backup;
+ int sz, i;// = round_up(tmdev->qfprom_size, sizeof(int));
+ u8 *cdata, *cdata_backup;
+
+ cdata = devm_qfprom_get_data(dev, 0, &sz);
+ cdata_backup = devm_qfprom_get_data(dev, 1, &sz);
+
+ if (!cdata || !cdata_backup)
+ return -EINVAL;
+
+ for (i = 0; i < tmdev->nsensors; i++) {
+ calib_data = cdata[i];
+ calib_data_backup = cdata_backup[i];
+ if (calib_data_backup)
+ calib_data = calib_data_backup;
+
+ if (!calib_data) {
+ pr_err("QFPROM TSENS calibration data not present\n");
+ return -ENODEV;
+ }
+ tmdev->sensor[i].offset = TSENS_CAL_MILLI_DEGC - (calib_data * tmdev->sensor[i].slope);
+ tmdev->trdy = false;
+ }
+
+ return 0;
+}
+
+static int tsens_calib_sensors(struct tsens_tm_device *tmdev, struct device *dev)
+{
+ if (tmdev->data->calib_sensors)
+ return tmdev->data->calib_sensors(tmdev, dev);
+
+ return -ENODEV;
+}
+
+struct tsens_variant_data msm8960_data =
+{
+ /**
+ * Slope for a thermocouple in a given part is always same
+ * for desired range of temperature measurements
+ **/
+ .slope = {910, 910, 910, 910, 910},
+ .nsensors = 5,
+ .slp_clk_ena = BIT(26),
+ .sctrl_reg = TSENS_CNTL_ADDR,
+ .sctrl_offset = 8,
+ .config_reg = TSENS_8960_CONFIG_ADDR,
+ .config_mask = TSENS_8960_CONFIG_MASK,
+ .config = TSENS_8960_CONFIG,
+ .calib_sensors = tsens_8960_calib_sensors,
+};
+
+struct tsens_variant_data apq8064_data = {
+ /**
+ * Slope for a thermocouple in a given part is always same
+ * for desired range of temperature measurements
+ **/
+ .slope = {1176, 1176, 1154, 1176, 1111, 1132,
+ 1132, 1199, 1132, 1199, 1132},
+ .nsensors = 11,
+ .slp_clk_ena = BIT(26),
+ .sctrl_reg = TSENS_8064_STATUS_CNTL,
+ .sctrl_offset = 0,
+ .config_reg = TSENS_8960_CONFIG_ADDR,
+ .config_mask = TSENS_8960_CONFIG_MASK,
+ .config = TSENS_8960_CONFIG,
+ .calib_sensors = tsens_8960_calib_sensors,
+};
+
+static struct of_device_id qcom_tsens_of_match[] = {
+ { .compatible = "qcom,msm8960-tsens", .data = &msm8960_data },
+ { .compatible = "qcom,apq8064-tsens", .data = &apq8064_data },
+};
+MODULE_DEVICE_TABLE(of, qcom_tsens_of_match);
+
+static int tsens_tm_probe(struct platform_device *pdev)
+{
+ int rc, i;
+ struct device *dev = &pdev->dev;
+ struct tsens_tm_device *tmdev;
+ struct tsens_variant_data *data;
+ struct device_node *np = pdev->dev.of_node;
+
+ if (!np) {
+ dev_err(dev, "Non DT not supported\n");
+ return -EINVAL;
+ }
+
+ data = (struct tsens_variant_data *)of_match_node(qcom_tsens_of_match, np)->data;
+ tmdev = devm_kzalloc(dev, sizeof(struct tsens_tm_device) +
+ data->nsensors *
+ sizeof(struct tsens_tm_device_sensor),
+ GFP_ATOMIC);
+ if (tmdev == NULL) {
+ pr_err("%s: kzalloc() failed.\n", __func__);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < data->nsensors; i++)
+ tmdev->sensor[i].slope = data->slope[i];
+
+ tmdev->data = data;
+ tmdev->nsensors = data->nsensors;
+ tmdev->base = dev_get_regmap(dev->parent, NULL);
+
+ if (!tmdev->base) {
+ dev_err(&pdev->dev, "Parent regmap unavailable.\n");
+ return -ENXIO;
+ }
+
+ rc = tsens_calib_sensors(tmdev, dev);
+ if (rc < 0)
+ return rc;
+
+ tsens_hw_init(tmdev);
+
+ for (i = 0; i < tmdev->nsensors; i++) {
+ char name[18];
+ snprintf(name, sizeof(name), "tsens_sensor%d", i);
+ tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
+ tmdev->sensor[i].sensor_num = i;
+ tmdev->sensor[i].tmdev = tmdev;
+
+ tmdev->sensor[i].tzone = thermal_zone_of_sensor_register(dev, i,
+ &tmdev->sensor[i], &tsens_of_thermal_ops);
+
+ if (IS_ERR(tmdev->sensor[i].tzone)) {
+ tmdev->sensor[i].tzone = thermal_zone_device_register(
+ name,
+ TSENS_TRIP_NUM,
+ GENMASK(TSENS_TRIP_NUM - 1 , 0),
+ &tmdev->sensor[i],
+ &tsens_thermal_zone_ops, NULL, 0, 0);
+ if (IS_ERR(tmdev->sensor[i].tzone)) {
+ dev_err(dev, "thermal_zone_device_register() failed.\n");
+ rc = -ENODEV;
+ goto fail;
+ }
+ tmdev->sensor[i].user_zone = true;
+ }
+ }
+
+ tmdev->irq = platform_get_irq_byname(pdev, "tsens-ul");
+ if (tmdev->irq > 0) {
+ rc = devm_request_irq(dev, tmdev->irq, tsens_isr,
+ IRQF_TRIGGER_RISING,
+ "tsens_interrupt", tmdev);
+ if (rc < 0) {
+ pr_err("%s: request_irq FAIL: %d\n", __func__, rc);
+ for (i = 0; i < tmdev->nsensors; i++)
+ if (tmdev->sensor[i].user_zone)
+ thermal_zone_device_unregister(
+ tmdev->sensor[i].tzone);
+ else
+ thermal_zone_of_sensor_unregister(dev,
+ tmdev->sensor[i].tzone);
+ goto fail;
+ }
+ INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
+ }
+ platform_set_drvdata(pdev, tmdev);
+
+ dev_info(dev, "Probed sucessfully\n");
+
+ return 0;
+fail:
+ tsens_disable_mode(tmdev);
+
+ return rc;
+}
+
+static int tsens_tm_remove(struct platform_device *pdev)
+{
+ int i;
+ struct tsens_tm_device *tmdev = platform_get_drvdata(pdev);
+ struct tsens_tm_device_sensor *s;
+
+ tsens_disable_mode(tmdev);
+
+ for (i = 0; i < tmdev->nsensors; i++) {
+ s = &tmdev->sensor[i];
+ if (s->user_zone)
+ thermal_zone_device_unregister(s->tzone);
+ else
+ thermal_zone_of_sensor_unregister(&pdev->dev,
+ s->tzone);
+ }
+
+ return 0;
+}
+
+static struct platform_driver tsens_tm_driver = {
+ .probe = tsens_tm_probe,
+ .remove = tsens_tm_remove,
+ .driver = {
+ .name = "tsens8960-tm",
+ .owner = THIS_MODULE,
+ .of_match_table = qcom_tsens_of_match,
+ },
+};
+
+module_platform_driver(tsens_tm_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MSM8960 Temperature Sensor driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:tsens8960-tm");
diff --git a/include/linux/soc/qcom/qfprom.h b/include/linux/soc/qcom/qfprom.h
new file mode 100644
index 000000000000..b56b64220e60
--- /dev/null
+++ b/include/linux/soc/qcom/qfprom.h
@@ -0,0 +1,38 @@
+#ifndef __QCOM_QFPROM_H__
+#define __QCOM_QFPROM_H__
+
+#ifdef CONFIG_QCOM_QFPROM
+
+char *qfprom_get_data_byname(struct device *dev, const char *name, int *len);
+char *devm_qfprom_get_data_byname(struct device *dev,
+ const char *name, int *len);
+char *qfprom_get_data(struct device *dev, int index, int *len);
+char *devm_qfprom_get_data(struct device *dev, int index, int *len);
+
+#else
+
+static inline char *qfprom_get_data_byname(struct device *dev,
+ const char *name)
+{
+ return NULL:
+}
+
+static inline char *qfprom_get_data(struct device *dev, int index)
+{
+ return NULL:
+}
+
+static inline char *devm_qfprom_get_data_byname(struct device *dev,
+ const char *name);
+{
+ return NULL:
+}
+
+static inline char *devm_qfprom_get_data(struct device *dev, int index)
+{
+ return NULL:
+}
+
+#endif /* CONFIG_QCOM_QFPROM */
+
+#endif /* __QCOM_QFPROM_H__ */