thermal: tsens: Add 8974 TSENS driver

MSM8974 TSENS block supports 11 temperature
sensors that are used for thermal mitigation.

The TSENS driver is part of the thermal sysfs
framework that allows clients to set thresholds
and receive notification on temperature thresholds
being crossed through thermal sysfs.

The driver provides ability to thermal clients
to set upper/lower thresholds for each sensor
and receive individual notification for each
sensor once the threshold is crossed.

All sensors are enabled by default during
driver initialization.

The driver is initialized early during kernel
initialization to provide capability to the
thermal kernel driver to read temperature
from the TSENS sensors. To support this feature
a kernel API is provide to read the TSENS
temperature.

Change-Id: If17daae81cfb522a9cea786b5db0c920adb5ed2d
Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/thermal/tsens.txt b/Documentation/devicetree/bindings/thermal/tsens.txt
new file mode 100644
index 0000000..c683f58
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/tsens.txt
@@ -0,0 +1,42 @@
+Qualcomm's TSENS driver
+
+The TSENS driver supports reading temperature from sensors across
+the MSM. The driver defaults to support a 10 bit ADC.
+
+The driver uses the Thermal sysfs framework to provide thermal
+clients the ability to enable/disable the sensors, read trip zones,
+read cool/warm temperature thresholds, set temperature thresholds
+for cool/warm notification and receive notification on temperature
+threshold events.
+
+TSENS node
+
+Required properties:
+- compatible : should be "qcom,msm-tsens" for MSM8974 TSENS driver.
+- reg : offset and length of the TSENS registers.
+- reg : offset and length of the QFPROM registers used for storing
+	the calibration data for the individual sensors.
+- reg-names : resource names used for the physical address of the TSENS
+	      registers and the QFPROM efuse calibration address.
+	      Should be "tsens_physical" for physical address of the TSENS
+	      and "tsens_eeprom_physical" for physical address where calibration
+	      data is stored.
+- interrupts : TSENS interrupt for cool/warm temperature threshold.
+- qcom,sensors : Total number of available Temperature sensors for TSENS.
+- qcom,slope : One point calibration characterized slope data for each
+	       sensor used to compute the offset. Slope is represented
+	       as ADC code/DegC and the value is multipled by a factor
+	       of 1000.
+
+Example:
+
+tsens@fc4a8000 {
+	compatible = "qcom,msm-tsens";
+	reg = <0xfc4a8000 0x2000>,
+	      <0xfc4b80d0 0x5>;
+	reg-names = "tsens_physical", "tsens_eeprom_physical";
+	interrupts = <0 184 0>;
+	qcom,sensors = <11>;
+	qcom,slope = <1134 1122 1142 1123 1176 1176 1176 1186 1176
+			1176>;
+};
diff --git a/arch/arm/boot/dts/msm8974.dtsi b/arch/arm/boot/dts/msm8974.dtsi
index 54f6863..1531a95 100644
--- a/arch/arm/boot/dts/msm8974.dtsi
+++ b/arch/arm/boot/dts/msm8974.dtsi
@@ -477,6 +477,17 @@
 		qcom,firmware-min-paddr = <0xF500000>;
 		qcom,firmware-max-paddr = <0xFA00000>;
 	};
+
+	tsens@fc4a8000 {
+		compatible = "qcom,msm-tsens";
+		reg = <0xfc4a8000 0x2000>,
+		      <0xfc4b80d0 0x5>;
+		reg-names = "tsens_physical", "tsens_eeprom_physical";
+		interrupts = <0 184 0>;
+		qcom,sensors = <11>;
+		qcom,slope = <1134 1122 1142 1123 1176 1176 1176 1186 1176
+				1176 1176>;
+	};
 };
 
 /include/ "msm-pm8x41-rpm-regulator.dtsi"
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index d99a02a..b2d2f74 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -52,6 +52,16 @@
 	  Also able to set threshold temperature for both hot and cold and update
 	  when a threshold is reached.
 
+config THERMAL_TSENS8974
+	tristate "Qualcomm 8974 TSENS Temperature driver"
+	depends on THERMAL
+	help
+	  This enables the thermal sysfs driver for the TSENS device. It shows
+	  up in Sysfs as a thermal zone with multiple trip points. Also able
+	  to set threshold temperature for both warm and cool and update
+	  thermal userspace client when a threshold is reached. Warm/Cool
+	  temperature thresholds can be set independently for each sensor.
+
 config THERMAL_PM8XXX
 	tristate "Qualcomm PMIC PM8xxx Temperature Alarm"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 275a692..f7e7cc6 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -8,4 +8,5 @@
 obj-$(CONFIG_THERMAL_TSENS8960) += msm8960_tsens.o
 obj-$(CONFIG_THERMAL_PM8XXX)	+= pm8xxx-tm.o
 obj-$(CONFIG_THERMAL_MONITOR)	+= msm_thermal.o
-obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
\ No newline at end of file
+obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
+obj-$(CONFIG_THERMAL_TSENS8974)	+= msm8974-tsens.o
diff --git a/drivers/thermal/msm8974-tsens.c b/drivers/thermal/msm8974-tsens.c
new file mode 100644
index 0000000..6628b79
--- /dev/null
+++ b/drivers/thermal/msm8974-tsens.c
@@ -0,0 +1,870 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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/thermal.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/msm_tsens.h>
+#include <linux/err.h>
+#include <linux/of.h>
+
+#include <mach/msm_iomap.h>
+
+#define TSENS_DRIVER_NAME		"msm-tsens"
+/* TSENS register info */
+#define TSENS_UPPER_LOWER_INTERRUPT_CTRL(n)		((n) + 0x1000)
+#define TSENS_INTERRUPT_EN		BIT(0)
+
+#define TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR(n)	((n) + 0x1004)
+#define TSENS_UPPER_STATUS_CLR		BIT(21)
+#define TSENS_LOWER_STATUS_CLR		BIT(20)
+#define TSENS_UPPER_THRESHOLD_MASK	0xffc00
+#define TSENS_LOWER_THRESHOLD_MASK	0x3ff
+#define TSENS_UPPER_THRESHOLD_SHIFT	10
+
+#define TSENS_S0_STATUS_ADDR(n)		((n) + 0x1030)
+#define TSENS_SN_ADDR_OFFSET		0x4
+#define TSENS_SN_STATUS_TEMP_MASK	0x3ff
+#define TSENS_SN_STATUS_LOWER_STATUS	BIT(11)
+#define TSENS_SN_STATUS_UPPER_STATUS	BIT(12)
+#define TSENS_STATUS_ADDR_OFFSET			2
+
+#define TSENS_TRDY_ADDR(n)		((n) + 0x105c)
+#define TSENS_TRDY_MASK			BIT(0)
+
+#define TSENS_CTRL_ADDR(n)		(n)
+#define TSENS_SW_RST			BIT(1)
+#define TSENS_SN_MIN_MAX_STATUS_CTRL(n)	((n) + 4)
+#define TSENS_GLOBAL_CONFIG(n)		((n) + 0x34)
+#define TSENS_S0_MAIN_CONFIG(n)		((n) + 0x38)
+#define TSENS_SN_REMOTE_CONFIG(n)	((n) + 0x3c)
+
+/* TSENS calibration Mask data */
+#define TSENS_BASE1_MASK		0xff
+#define TSENS0_POINT1_MASK		0x3f00
+#define TSENS1_POINT1_MASK		0xfc000
+#define TSENS2_POINT1_MASK		0x3f00000
+#define TSENS3_POINT1_MASK		0xfc000000
+#define TSENS4_POINT1_MASK		0x3f
+#define TSENS5_POINT1_MASK		0xfc00
+#define TSENS6_POINT1_MASK		0x3f000
+#define TSENS7_POINT1_MASK		0xfc0000
+#define TSENS8_POINT1_MASK		0x3f000000
+#define TSENS9_POINT1_MASK		0x3f
+#define TSENS10_POINT1_MASK		0xfc00
+#define TSENS_CAL_SEL_0_1		0xc0000000
+#define TSENS_CAL_SEL_2			BIT(30)
+#define TSENS_CAL_SEL_SHIFT		30
+#define TSENS_CAL_SEL_SHIFT_2		28
+#define TSENS_ONE_POINT_CALIB		0x3
+#define TSENS_TWO_POINT_CALIB		0x2
+
+#define TSENS_BASE2_MASK		0xff000
+#define TSENS0_POINT2_MASK		0x3f00000
+#define TSENS1_POINT2_MASK		0xfc000000
+#define TSENS2_POINT2_MASK		0x3f
+#define TSENS3_POINT2_MASK		0xfc00
+#define TSENS4_POINT2_MASK		0x3f000
+#define TSENS5_POINT2_MASK		0xfc0000
+#define TSENS6_POINT2_MASK		0x3f000000
+#define TSENS7_POINT2_MASK		0x3f
+#define TSENS8_POINT2_MASK		0xfc00
+#define TSENS9_POINT2_MASK		0x3f000
+#define TSENS10_POINT2_MASK		0xfc0000
+
+#define TSENS_BIT_APPEND		0x3
+#define TSENS_CAL_DEGC_POINT1		30
+#define TSENS_CAL_DEGC_POINT2		120
+#define TSENS_SLOPE_FACTOR		1000
+
+/* TSENS register data */
+#define TSENS_TRDY_RDY_MIN_TIME		2000
+#define TSENS_TRDY_RDY_MAX_TIME		2100
+#define TSENS_THRESHOLD_MAX_CODE	0x3ff
+#define TSENS_THRESHOLD_MIN_CODE	0x0
+
+#define TSENS_CTRL_INIT_DATA1		0x3fffff9
+#define TSENS_GLOBAL_INIT_DATA		0x20013
+#define TSENS_S0_MAIN_CFG_INIT_DATA	0x1ba
+#define TSENS_SN_MIN_MAX_STATUS_CTRL_DATA	0x3ffc00
+#define TSENS_SN_REMOTE_CFG_DATA	0xdba
+
+/* Trips: warm and cool */
+enum tsens_trip_type {
+	TSENS_TRIP_WARM = 0,
+	TSENS_TRIP_COOL,
+	TSENS_TRIP_NUM,
+};
+
+struct tsens_tm_device_sensor {
+	struct thermal_zone_device	*tz_dev;
+	enum thermal_device_mode	mode;
+	unsigned int			sensor_num;
+	struct work_struct		work;
+	int				offset;
+	int				calib_data_point1;
+	int				calib_data_point2;
+	uint32_t			slope_mul_tsens_factor;
+};
+
+struct tsens_tm_device {
+	struct platform_device		*pdev;
+	bool				prev_reading_avail;
+	int				tsens_factor;
+	uint32_t			tsens_num_sensor;
+	int				tsens_irq;
+	void				*tsens_addr;
+	void				*tsens_calib_addr;
+	int				tsens_len;
+	int				calib_len;
+	struct resource			*res_tsens_mem;
+	struct resource			*res_calib_mem;
+	struct tsens_tm_device_sensor	sensor[0];
+};
+
+struct tsens_tm_device *tmdev;
+
+static int tsens_tz_code_to_degc(int adc_code, int sensor_num)
+{
+	int degcbeforefactor, degc;
+	degcbeforefactor = (adc_code *
+			tmdev->sensor[sensor_num].slope_mul_tsens_factor
+			+ tmdev->sensor[sensor_num].offset);
+
+	if (degcbeforefactor == 0)
+		degc = degcbeforefactor;
+	else if (degcbeforefactor > 0)
+		degc = (degcbeforefactor + tmdev->tsens_factor/2)
+				/ tmdev->tsens_factor;
+	else
+		degc = (degcbeforefactor - tmdev->tsens_factor/2)
+				/ tmdev->tsens_factor;
+	return degc;
+}
+
+static int tsens_tz_degc_to_code(int degc, int sensor_num)
+{
+	int code = (degc * tmdev->tsens_factor -
+		tmdev->sensor[sensor_num].offset
+		+ tmdev->sensor[sensor_num].slope_mul_tsens_factor/2)
+		/ tmdev->sensor[sensor_num].slope_mul_tsens_factor;
+
+	if (code > TSENS_THRESHOLD_MAX_CODE)
+		code = TSENS_THRESHOLD_MAX_CODE;
+	else if (code < TSENS_THRESHOLD_MIN_CODE)
+		code = TSENS_THRESHOLD_MIN_CODE;
+	return code;
+}
+
+static void msm_tsens_get_temp(int sensor_num, unsigned long *temp)
+{
+	unsigned int code, sensor_addr;
+
+	if (!tmdev->prev_reading_avail) {
+		while (!(readl_relaxed(TSENS_TRDY_ADDR(tmdev->tsens_addr))
+					& TSENS_TRDY_MASK))
+			usleep_range(TSENS_TRDY_RDY_MIN_TIME,
+				TSENS_TRDY_RDY_MAX_TIME);
+		tmdev->prev_reading_avail = true;
+	}
+
+	sensor_addr =
+		(unsigned int)TSENS_S0_STATUS_ADDR(tmdev->tsens_addr);
+	code = readl_relaxed(sensor_addr +
+			(sensor_num << TSENS_STATUS_ADDR_OFFSET));
+	*temp = tsens_tz_code_to_degc((code & TSENS_SN_STATUS_TEMP_MASK),
+								sensor_num);
+}
+
+static int tsens_tz_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 || !temp)
+		return -EINVAL;
+
+	msm_tsens_get_temp(tm_sensor->sensor_num, temp);
+
+	return 0;
+}
+
+int tsens_get_temp(struct tsens_device *device, unsigned long *temp)
+{
+	if (!tmdev)
+		return -ENODEV;
+
+	msm_tsens_get_temp(device->sensor_num, temp);
+
+	return 0;
+}
+EXPORT_SYMBOL(msm_tsens_get_temp);
+
+static int tsens_tz_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)
+		return -EINVAL;
+
+	*mode = tm_sensor->mode;
+
+	return 0;
+}
+
+static int tsens_tz_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_WARM:
+		*type = THERMAL_TRIP_CONFIGURABLE_HI;
+		break;
+	case TSENS_TRIP_COOL:
+		*type = THERMAL_TRIP_CONFIGURABLE_LOW;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int tsens_tz_activate_trip_type(struct thermal_zone_device *thermal,
+			int trip, enum thermal_trip_activation_mode mode)
+{
+	struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+	unsigned int reg_cntl, code, hi_code, lo_code, mask;
+
+	if (!tm_sensor || trip < 0)
+		return -EINVAL;
+
+	lo_code = TSENS_THRESHOLD_MIN_CODE;
+	hi_code = TSENS_THRESHOLD_MAX_CODE;
+
+	reg_cntl = readl_relaxed((TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
+				(tmdev->tsens_addr) +
+				(tm_sensor->sensor_num * 4)));
+	switch (trip) {
+	case TSENS_TRIP_WARM:
+		code = (reg_cntl & TSENS_UPPER_THRESHOLD_MASK)
+					>> TSENS_UPPER_THRESHOLD_SHIFT;
+		mask = TSENS_UPPER_STATUS_CLR;
+
+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR))
+			lo_code = (reg_cntl & TSENS_LOWER_THRESHOLD_MASK);
+		break;
+	case TSENS_TRIP_COOL:
+		code = (reg_cntl & TSENS_LOWER_THRESHOLD_MASK);
+		mask = TSENS_LOWER_STATUS_CLR;
+
+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR))
+			hi_code = (reg_cntl & TSENS_UPPER_THRESHOLD_MASK)
+					>> TSENS_UPPER_THRESHOLD_SHIFT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (mode == THERMAL_TRIP_ACTIVATION_DISABLED)
+		writel_relaxed(reg_cntl | mask,
+			(TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
+					(tmdev->tsens_addr) +
+					(tm_sensor->sensor_num * 4)));
+	else {
+		if (code < lo_code || code > hi_code) {
+			pr_err("%s with invalid code %x\n", __func__, code);
+			return -EINVAL;
+		}
+		writel_relaxed(reg_cntl & ~mask,
+		(TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR(tmdev->tsens_addr) +
+		(tm_sensor->sensor_num * 4)));
+	}
+	mb();
+	return 0;
+}
+
+static int tsens_tz_get_trip_temp(struct thermal_zone_device *thermal,
+				   int trip, unsigned long *temp)
+{
+	struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+	unsigned int reg;
+
+	if (!tm_sensor || trip < 0 || !temp)
+		return -EINVAL;
+
+	reg = readl_relaxed(TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
+						(tmdev->tsens_addr) +
+			(tm_sensor->sensor_num * TSENS_SN_ADDR_OFFSET));
+	switch (trip) {
+	case TSENS_TRIP_WARM:
+		reg = (reg & TSENS_UPPER_THRESHOLD_MASK) >>
+				TSENS_UPPER_THRESHOLD_SHIFT;
+		break;
+	case TSENS_TRIP_COOL:
+		reg = (reg & TSENS_LOWER_THRESHOLD_MASK);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	*temp = tsens_tz_code_to_degc(reg, tm_sensor->sensor_num);
+
+	return 0;
+}
+
+static int tsens_tz_notify(struct thermal_zone_device *thermal,
+				int count, enum thermal_trip_type type)
+{
+	/* TSENS driver does not shutdown the device.
+	   All Thermal notification are sent to the
+	   thermal daemon to take appropriate action */
+	pr_debug("%s debug\n", __func__);
+	return 1;
+}
+
+static int tsens_tz_set_trip_temp(struct thermal_zone_device *thermal,
+				   int trip, long temp)
+{
+	struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
+	unsigned int reg_cntl;
+	int code, hi_code, lo_code, code_err_chk;
+
+	code_err_chk = code = tsens_tz_degc_to_code(temp,
+					tm_sensor->sensor_num);
+	if (!tm_sensor || trip < 0)
+		return -EINVAL;
+
+	lo_code = TSENS_THRESHOLD_MIN_CODE;
+	hi_code = TSENS_THRESHOLD_MAX_CODE;
+
+	reg_cntl = readl_relaxed(TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
+				(tmdev->tsens_addr) +
+				(tm_sensor->sensor_num * TSENS_SN_ADDR_OFFSET));
+	switch (trip) {
+	case TSENS_TRIP_WARM:
+		code <<= TSENS_UPPER_THRESHOLD_SHIFT;
+		reg_cntl &= ~TSENS_UPPER_THRESHOLD_MASK;
+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR))
+			lo_code = (reg_cntl & TSENS_LOWER_THRESHOLD_MASK);
+		break;
+	case TSENS_TRIP_COOL:
+		reg_cntl &= ~TSENS_LOWER_THRESHOLD_MASK;
+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR))
+			hi_code = (reg_cntl & TSENS_UPPER_THRESHOLD_MASK)
+					>> TSENS_UPPER_THRESHOLD_SHIFT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (code_err_chk < lo_code || code_err_chk > hi_code)
+		return -EINVAL;
+
+	writel_relaxed(reg_cntl | code, (TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
+					(tmdev->tsens_addr) +
+					(tm_sensor->sensor_num *
+					TSENS_SN_ADDR_OFFSET)));
+	mb();
+	return 0;
+}
+
+static struct thermal_zone_device_ops tsens_thermal_zone_ops = {
+	.get_temp = tsens_tz_get_temp,
+	.get_mode = tsens_tz_get_mode,
+	.get_trip_type = tsens_tz_get_trip_type,
+	.activate_trip_type = tsens_tz_activate_trip_type,
+	.get_trip_temp = tsens_tz_get_trip_temp,
+	.set_trip_temp = tsens_tz_set_trip_temp,
+	.notify = tsens_tz_notify,
+};
+
+static void notify_uspace_tsens_fn(struct work_struct *work)
+{
+	struct tsens_tm_device_sensor *tm = container_of(work,
+		struct tsens_tm_device_sensor, work);
+
+	sysfs_notify(&tm->tz_dev->device.kobj,
+					NULL, "type");
+}
+
+static irqreturn_t tsens_isr(int irq, void *data)
+{
+	struct tsens_tm_device *tm = data;
+	unsigned int i, status, threshold;
+	unsigned int sensor_status_addr, sensor_status_ctrl_addr;
+
+	sensor_status_addr =
+		(unsigned int)TSENS_S0_STATUS_ADDR(tmdev->tsens_addr);
+	sensor_status_ctrl_addr =
+		(unsigned int)TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
+		(tmdev->tsens_addr);
+	for (i = 0; i < tmdev->tsens_num_sensor; i++) {
+		bool upper_thr = false, lower_thr = false;
+		status = readl_relaxed(sensor_status_addr);
+		threshold = readl_relaxed(sensor_status_ctrl_addr);
+		if (status & TSENS_SN_STATUS_UPPER_STATUS) {
+			writel_relaxed(threshold | TSENS_UPPER_STATUS_CLR,
+				sensor_status_ctrl_addr);
+			upper_thr = true;
+		}
+		if (status & TSENS_SN_STATUS_LOWER_STATUS) {
+			writel_relaxed(threshold | TSENS_LOWER_STATUS_CLR,
+				sensor_status_ctrl_addr);
+			lower_thr = true;
+		}
+		if (upper_thr || lower_thr) {
+			/* Notify user space */
+			schedule_work(&tm->sensor[i].work);
+			pr_debug("sensor:%d trigger temp (%d degC)\n", i,
+				tsens_tz_code_to_degc((status &
+				TSENS_SN_STATUS_TEMP_MASK), i));
+		}
+		sensor_status_addr += TSENS_SN_ADDR_OFFSET;
+		sensor_status_ctrl_addr += TSENS_SN_ADDR_OFFSET;
+	}
+	mb();
+	return IRQ_HANDLED;
+}
+
+static void tsens_hw_init(void)
+{
+	unsigned int reg_cntl = 0;
+	unsigned int i;
+
+	reg_cntl = readl_relaxed(TSENS_CTRL_ADDR(tmdev->tsens_addr));
+	writel_relaxed(reg_cntl | TSENS_SW_RST,
+			TSENS_CTRL_ADDR(tmdev->tsens_addr));
+	writel_relaxed(TSENS_CTRL_INIT_DATA1,
+			TSENS_CTRL_ADDR(tmdev->tsens_addr));
+	writel_relaxed(TSENS_GLOBAL_INIT_DATA,
+			TSENS_GLOBAL_CONFIG(tmdev->tsens_addr));
+	writel_relaxed(TSENS_S0_MAIN_CFG_INIT_DATA,
+			TSENS_S0_MAIN_CONFIG(tmdev->tsens_addr));
+	for (i = 0; i < tmdev->tsens_num_sensor; i++) {
+		writel_relaxed(TSENS_SN_MIN_MAX_STATUS_CTRL_DATA,
+			TSENS_SN_MIN_MAX_STATUS_CTRL(tmdev->tsens_addr)
+				+ (i * TSENS_SN_ADDR_OFFSET));
+		writel_relaxed(TSENS_SN_REMOTE_CFG_DATA,
+			TSENS_SN_REMOTE_CONFIG(tmdev->tsens_addr)
+				+ (i * TSENS_SN_ADDR_OFFSET));
+	}
+	writel_relaxed(TSENS_INTERRUPT_EN,
+		TSENS_UPPER_LOWER_INTERRUPT_CTRL(tmdev->tsens_addr));
+}
+
+static int tsens_calib_sensors(void)
+{
+	int i, tsens_base1_data, tsens0_point1, tsens1_point1;
+	int tsens2_point1, tsens3_point1, tsens4_point1, tsens5_point1;
+	int tsens6_point1, tsens7_point1, tsens8_point1, tsens9_point1;
+	int tsens10_point1, tsens0_point2, tsens1_point2, tsens2_point2;
+	int tsens3_point2, tsens4_point2, tsens5_point2, tsens6_point2;
+	int tsens7_point2, tsens8_point2, tsens9_point2, tsens10_point2;
+	int tsens_base2_data, tsens_calibration_mode, temp;
+	uint32_t calib_data[5];
+
+	for (i = 0; i < 5; i++)
+		calib_data[i] = readl_relaxed(tmdev->tsens_calib_addr
+					+ (i * TSENS_SN_ADDR_OFFSET));
+
+	tsens_calibration_mode = (calib_data[1] & TSENS_CAL_SEL_0_1
+			>> TSENS_CAL_SEL_SHIFT);
+	temp = (calib_data[3] & TSENS_CAL_SEL_2
+			>> TSENS_CAL_SEL_SHIFT_2);
+	tsens_calibration_mode |= temp;
+	/* Remove this after bringup */
+	tsens_calibration_mode = TSENS_ONE_POINT_CALIB;
+
+	if (!tsens_calibration_mode) {
+		pr_err("TSENS not calibrated\n");
+		return -ENODEV;
+	} else if (tsens_calibration_mode == TSENS_ONE_POINT_CALIB ||
+				TSENS_TWO_POINT_CALIB) {
+		tsens_base1_data = calib_data[0] & TSENS_BASE1_MASK;
+		tsens0_point1 = calib_data[0] & TSENS0_POINT1_MASK;
+		tsens1_point1 = calib_data[0] & TSENS1_POINT1_MASK;
+		tsens2_point1 = calib_data[0] & TSENS2_POINT1_MASK;
+		tsens3_point1 = calib_data[0] & TSENS3_POINT1_MASK;
+		tsens4_point1 = calib_data[1] & TSENS4_POINT1_MASK;
+		tsens5_point1 = calib_data[1] & TSENS5_POINT1_MASK;
+		tsens6_point1 = calib_data[1] & TSENS6_POINT1_MASK;
+		tsens7_point1 = calib_data[1] & TSENS7_POINT1_MASK;
+		tsens8_point1 = calib_data[1] & TSENS8_POINT1_MASK;
+		tsens9_point1 = calib_data[2] & TSENS9_POINT1_MASK;
+		tsens10_point1 = calib_data[2] & TSENS10_POINT1_MASK;
+	} else if (tsens_calibration_mode == TSENS_TWO_POINT_CALIB) {
+		tsens_base2_data = calib_data[2] & TSENS_BASE2_MASK;
+		tsens0_point2 = calib_data[2] & TSENS0_POINT2_MASK;
+		tsens1_point2 = calib_data[2] & TSENS1_POINT2_MASK;
+		tsens2_point2 = calib_data[3] & TSENS2_POINT2_MASK;
+		tsens3_point2 = calib_data[3] & TSENS3_POINT2_MASK;
+		tsens4_point2 = calib_data[3] & TSENS4_POINT2_MASK;
+		tsens5_point2 = calib_data[3] & TSENS5_POINT2_MASK;
+		tsens6_point2 = calib_data[3] & TSENS6_POINT2_MASK;
+		tsens7_point2 = calib_data[4] & TSENS7_POINT2_MASK;
+		tsens8_point2 = calib_data[4] & TSENS8_POINT2_MASK;
+		tsens9_point2 = calib_data[4] & TSENS9_POINT2_MASK;
+		tsens10_point2 = calib_data[4] & TSENS10_POINT2_MASK;
+	} else
+		pr_debug("Calibration mode is unknown: %d\n",
+						tsens_calibration_mode);
+
+	tmdev->sensor[0].calib_data_point1 =
+		(((tsens_base1_data + tsens0_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[0].calib_data_point2 =
+		(((tsens_base2_data + tsens0_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[1].calib_data_point1 =
+		(((tsens_base1_data + tsens1_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[1].calib_data_point2 =
+		(((tsens_base2_data + tsens1_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[2].calib_data_point1 =
+		(((tsens_base1_data + tsens2_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[2].calib_data_point2 =
+		(((tsens_base2_data + tsens2_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[3].calib_data_point1 =
+		(((tsens_base1_data + tsens3_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[3].calib_data_point2 =
+		(((tsens_base2_data + tsens3_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[4].calib_data_point1 =
+		(((tsens_base1_data + tsens4_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[4].calib_data_point2 =
+		(((tsens_base2_data + tsens4_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[5].calib_data_point1 =
+		(((tsens_base1_data + tsens5_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[5].calib_data_point2 =
+		(((tsens_base2_data + tsens5_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[6].calib_data_point1 =
+		(((tsens_base1_data + tsens6_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[6].calib_data_point2 =
+		(((tsens_base2_data + tsens6_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[7].calib_data_point1 =
+		(((tsens_base1_data + tsens7_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[7].calib_data_point2 =
+		(((tsens_base2_data + tsens7_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[8].calib_data_point1 =
+		(((tsens_base1_data + tsens8_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[8].calib_data_point2 =
+		(((tsens_base2_data + tsens8_point2) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[9].calib_data_point1 =
+		(((tsens_base1_data + tsens9_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[9].calib_data_point2 =
+		(((tsens_base2_data + tsens9_point2) < 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[10].calib_data_point1 =
+		(((tsens_base1_data + tsens10_point1) << 2) | TSENS_BIT_APPEND);
+	tmdev->sensor[10].calib_data_point2 =
+		(((tsens_base2_data + tsens10_point2) << 2) | TSENS_BIT_APPEND);
+
+	for (i = 0; i < tmdev->tsens_num_sensor; i++) {
+		int32_t num = 0, den = 0;
+		num = TSENS_CAL_DEGC_POINT2 - TSENS_CAL_DEGC_POINT2;
+		den = tmdev->sensor[i].calib_data_point2 -
+					tmdev->sensor[i].calib_data_point1;
+		num *= tmdev->tsens_factor;
+		if (tsens_calibration_mode == TSENS_TWO_POINT_CALIB)
+			tmdev->sensor[i].slope_mul_tsens_factor = num/den;
+		tmdev->sensor[i].offset = (TSENS_CAL_DEGC_POINT1 *
+			tmdev->tsens_factor)
+			- (tmdev->sensor[i].calib_data_point1 *
+			tmdev->sensor[i].slope_mul_tsens_factor);
+		INIT_WORK(&tmdev->sensor[i].work, notify_uspace_tsens_fn);
+		tmdev->prev_reading_avail = false;
+	}
+
+	return 0;
+}
+
+static int get_device_tree_data(struct platform_device *pdev)
+{
+	const struct device_node *of_node = pdev->dev.of_node;
+	struct resource *res_mem = NULL;
+	u32 *tsens_slope_data;
+	u32 rc = 0, i, tsens_num_sensors;
+
+	rc = of_property_read_u32(of_node,
+			"qcom,sensors", &tsens_num_sensors);
+	if (rc) {
+		dev_err(&pdev->dev, "missing sensor number\n");
+		return -ENODEV;
+	}
+
+	tsens_slope_data = devm_kzalloc(&pdev->dev,
+				tsens_num_sensors, GFP_KERNEL);
+	if (!tsens_slope_data) {
+		dev_err(&pdev->dev, "can not allocate slope data\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node,
+		"qcom,slope", tsens_slope_data, tsens_num_sensors/sizeof(u32));
+	if (rc) {
+		dev_err(&pdev->dev, "invalid or missing property: tsens-slope\n");
+		return rc;
+	};
+
+	tmdev = devm_kzalloc(&pdev->dev,
+			sizeof(struct tsens_tm_device) +
+			tsens_num_sensors *
+			sizeof(struct tsens_tm_device_sensor),
+			GFP_KERNEL);
+	if (tmdev == NULL) {
+		pr_err("%s: kzalloc() failed.\n", __func__);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < tsens_num_sensors; i++)
+		tmdev->sensor[i].slope_mul_tsens_factor = tsens_slope_data[i];
+	tmdev->tsens_factor = TSENS_SLOPE_FACTOR;
+	tmdev->tsens_num_sensor = tsens_num_sensors;
+
+	tmdev->tsens_irq = platform_get_irq(pdev, 0);
+	if (tmdev->tsens_irq < 0) {
+		pr_err("Invalid get irq\n");
+		return tmdev->tsens_irq;
+	}
+
+	tmdev->res_tsens_mem = platform_get_resource_byname(pdev,
+					IORESOURCE_MEM, "tsens_physical");
+	if (!tmdev->res_tsens_mem) {
+		pr_err("Could not get tsens physical address resource\n");
+		rc = -EINVAL;
+		goto fail_free_irq;
+	}
+
+	tmdev->tsens_len = tmdev->res_tsens_mem->end -
+					tmdev->res_tsens_mem->start + 1;
+
+	res_mem = request_mem_region(tmdev->res_tsens_mem->start,
+				tmdev->tsens_len, tmdev->res_tsens_mem->name);
+	if (!res_mem) {
+		pr_err("Request tsens physical memory region failed\n");
+		rc = -EINVAL;
+		goto fail_free_irq;
+	}
+
+	tmdev->tsens_addr = ioremap(res_mem->start, tmdev->tsens_len);
+	if (!tmdev->tsens_addr) {
+		pr_err("Failed to IO map TSENS registers.\n");
+		rc = -EINVAL;
+		goto fail_unmap_tsens_region;
+	}
+
+	tmdev->res_calib_mem = platform_get_resource_byname(pdev,
+				IORESOURCE_MEM, "tsens_eeprom_physical");
+	if (!tmdev->res_calib_mem) {
+		pr_err("Could not get qfprom physical address resource\n");
+		rc = -EINVAL;
+		goto fail_unmap_tsens;
+	}
+
+	tmdev->calib_len = tmdev->res_calib_mem->end -
+					tmdev->res_calib_mem->start + 1;
+
+	res_mem = request_mem_region(tmdev->res_calib_mem->start,
+				tmdev->calib_len, tmdev->res_calib_mem->name);
+	if (!res_mem) {
+		pr_err("Request calibration memory region failed\n");
+		rc = -EINVAL;
+		goto fail_unmap_tsens;
+	}
+
+	tmdev->tsens_calib_addr = ioremap(res_mem->start,
+						tmdev->calib_len);
+	if (!tmdev->tsens_calib_addr) {
+		pr_err("Failed to IO map EEPROM registers.\n");
+		rc = -EINVAL;
+		goto fail_unmap_calib_region;
+	}
+
+	return 0;
+
+fail_unmap_calib_region:
+	if (tmdev->res_calib_mem)
+		release_mem_region(tmdev->res_calib_mem->start,
+					tmdev->calib_len);
+fail_unmap_tsens:
+	if (tmdev->tsens_addr)
+		iounmap(tmdev->tsens_addr);
+fail_unmap_tsens_region:
+	if (tmdev->res_tsens_mem)
+		release_mem_region(tmdev->res_tsens_mem->start,
+					tmdev->tsens_len);
+fail_free_irq:
+	free_irq(tmdev->tsens_irq, tmdev);
+
+	return rc;
+}
+
+static int __devinit tsens_tm_probe(struct platform_device *pdev)
+{
+	int rc;
+
+	if (tmdev) {
+		pr_err("TSENS device already in use\n");
+		return -EBUSY;
+	}
+
+	if (pdev->dev.of_node)
+		rc = get_device_tree_data(pdev);
+	else
+		return -ENODEV;
+
+	tmdev->pdev = pdev;
+	rc = tsens_calib_sensors();
+	if (rc < 0)
+		goto fail;
+
+	tsens_hw_init();
+	tmdev->prev_reading_avail = true;
+
+	platform_set_drvdata(pdev, tmdev);
+
+	return 0;
+fail:
+	if (tmdev->tsens_calib_addr)
+		iounmap(tmdev->tsens_calib_addr);
+	if (tmdev->res_calib_mem)
+		release_mem_region(tmdev->res_calib_mem->start,
+					tmdev->calib_len);
+	if (tmdev->tsens_addr)
+		iounmap(tmdev->tsens_addr);
+	if (tmdev->res_tsens_mem)
+		release_mem_region(tmdev->res_tsens_mem->start,
+			tmdev->tsens_len);
+	free_irq(tmdev->tsens_irq, tmdev);
+	kfree(tmdev);
+
+	return rc;
+}
+
+static int __devinit _tsens_register_thermal(void)
+{
+	struct platform_device *pdev = tmdev->pdev;
+	int rc, i;
+
+	if (!tmdev) {
+		pr_err("%s: TSENS early init not done\n", __func__);
+		return -ENODEV;
+	}
+
+	for (i = 0; i < tmdev->tsens_num_sensor; i++) {
+		char name[18];
+		snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
+		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
+		tmdev->sensor[i].sensor_num = i;
+		tmdev->sensor[i].tz_dev = thermal_zone_device_register(name,
+				TSENS_TRIP_NUM, &tmdev->sensor[i],
+				&tsens_thermal_zone_ops, 0, 0, 0, 0);
+		if (IS_ERR(tmdev->sensor[i].tz_dev)) {
+			pr_err("%s: thermal_zone_device_register() failed.\n",
+			__func__);
+			rc = -ENODEV;
+			goto fail;
+		}
+	}
+	rc = request_irq(tmdev->tsens_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->tsens_num_sensor; i++)
+			thermal_zone_device_unregister(tmdev->sensor[i].tz_dev);
+		goto fail;
+	}
+	platform_set_drvdata(pdev, tmdev);
+
+	return 0;
+fail:
+	if (tmdev->tsens_calib_addr)
+		iounmap(tmdev->tsens_calib_addr);
+	if (tmdev->res_calib_mem)
+		release_mem_region(tmdev->res_calib_mem->start,
+				tmdev->calib_len);
+	if (tmdev->tsens_addr)
+		iounmap(tmdev->tsens_addr);
+	if (tmdev->res_tsens_mem)
+		release_mem_region(tmdev->res_tsens_mem->start,
+				tmdev->tsens_len);
+	kfree(tmdev);
+
+	return rc;
+}
+
+static int __devexit tsens_tm_remove(struct platform_device *pdev)
+{
+	struct tsens_tm_device *tmdev = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; i < tmdev->tsens_num_sensor; i++)
+		thermal_zone_device_unregister(tmdev->sensor[i].tz_dev);
+	if (tmdev->tsens_calib_addr)
+		iounmap(tmdev->tsens_calib_addr);
+	if (tmdev->res_calib_mem)
+		release_mem_region(tmdev->res_calib_mem->start,
+				tmdev->calib_len);
+	if (tmdev->tsens_addr)
+		iounmap(tmdev->tsens_addr);
+	if (tmdev->res_tsens_mem)
+		release_mem_region(tmdev->res_tsens_mem->start,
+				tmdev->tsens_len);
+	free_irq(tmdev->tsens_irq, tmdev);
+	platform_set_drvdata(pdev, NULL);
+	kfree(tmdev);
+
+	return 0;
+}
+
+static struct of_device_id tsens_match[] = {
+	{	.compatible = "qcom,msm-tsens",
+	},
+	{}
+};
+
+static struct platform_driver tsens_tm_driver = {
+	.probe = tsens_tm_probe,
+	.remove = tsens_tm_remove,
+	.driver = {
+		.name = "msm-tsens",
+		.owner = THIS_MODULE,
+		.of_match_table = tsens_match,
+	},
+};
+
+static int __init tsens_tm_init_driver(void)
+{
+	return platform_driver_register(&tsens_tm_driver);
+}
+arch_initcall(tsens_tm_init_driver);
+
+static int __init tsens_thermal_register(void)
+{
+	return _tsens_register_thermal();
+}
+module_init(tsens_thermal_register);
+
+static void __exit _tsens_tm_remove(void)
+{
+	platform_driver_unregister(&tsens_tm_driver);
+}
+module_exit(_tsens_tm_remove);
+
+MODULE_ALIAS("platform:" TSENS_DRIVER_NAME);
+MODULE_LICENSE("GPL v2");