diff mbox

thermal: Add msm tsens thermal sensor driver

Message ID 1422331786-19620-1-git-send-email-nrajan@codeaurora.org (mailing list archive)
State Changes Requested
Delegated to: Eduardo Valentin
Headers show

Commit Message

Narendran Rajan Jan. 27, 2015, 4:09 a.m. UTC
TSENS supports reading temperature from multiple thermal
sensors present in QCOM SOCs.
TSENS HW is enabled only when the main sensor is requested.
The TSENS block is disabled if the main senors is disabled
irrespective of any other sensors that are being enabled.
TSENS driver supports configurable threshold for temperature
monitoring in which case it can generate an interrupt when specific
thresholds are reached

Based on code by Siddartha Mohanadoss and Stephen Boyd.

Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
Cc: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
---
 drivers/thermal/Kconfig         |  14 +
 drivers/thermal/Makefile        |   1 +
 drivers/thermal/msm8960_tsens.c | 841 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 856 insertions(+)
 create mode 100644 drivers/thermal/msm8960_tsens.c

Comments

Lina Iyer Jan. 27, 2015, 4:03 p.m. UTC | #1
On Mon, Jan 26 2015 at 21:10 -0700, Narendran Rajan wrote:
>TSENS supports reading temperature from multiple thermal
>sensors present in QCOM SOCs.
>TSENS HW is enabled only when the main sensor is requested.
>The TSENS block is disabled if the main senors is disabled
>irrespective of any other sensors that are being enabled.
>TSENS driver supports configurable threshold for temperature
>monitoring in which case it can generate an interrupt when specific
>thresholds are reached
>
>Based on code by Siddartha Mohanadoss and Stephen Boyd.
>
>Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
>Cc: Stephen Boyd <sboyd@codeaurora.org>
>Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
>---
> drivers/thermal/Kconfig         |  14 +
> drivers/thermal/Makefile        |   1 +
> drivers/thermal/msm8960_tsens.c | 841 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 856 insertions(+)
> create mode 100644 drivers/thermal/msm8960_tsens.c
>
>diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>index 20915ca..a4cb2c0 100644
>--- a/drivers/thermal/Kconfig
>+++ b/drivers/thermal/Kconfig
>@@ -261,6 +261,20 @@ config INTEL_SOC_DTS_THERMAL
> 	  notification methods.The other trip is a critical trip point, which
> 	  was set by the driver based on the TJ MAX temperature.
>
>+config THERMAL_TSENS8960
>+	tristate "Qualcomm 8960 Tsens Temperature driver"
>+	depends on THERMAL
>+	depends on ARCH_QCOM
>+	help
>+	  QCOM tsens thermal driver provides support for Temperature sensor
>+	  (TSENS) found on QCOM SoCs. It supports four configurable trip points
>+	  and controls multiple sensors on the SOC. The four trip points are
>+	  common across all sensors present in the SoC. The number of sensors
>+	  present vary from chip to chip and are set through device tree entry.
>+	  The driver presents as a standard thermal zone device with configurable
>+	  trip points and cooling device mapping through standard thermal zone
>+	  device tree
>+
> menu "ACPI INT340X thermal drivers"
> source drivers/thermal/int340x_thermal/Kconfig
> endmenu
>diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>index fa0dc48..23c7a34 100644
>--- a/drivers/thermal/Makefile
>+++ b/drivers/thermal/Makefile
>@@ -39,3 +39,4 @@ obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-thermal/
> obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
> obj-$(CONFIG_ST_THERMAL)	+= st/
> obj-$(CONFIG_TEGRA_SOCTHERM)	+= tegra_soctherm.o
>+obj-$(CONFIG_THERMAL_TSENS8960)	+= msm8960_tsens.o
>diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c
>new file mode 100644
>index 0000000..307bdc8
>--- /dev/null
>+++ b/drivers/thermal/msm8960_tsens.c
>@@ -0,0 +1,841 @@
>+/* 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/kernel.h>
>+#include <linux/module.h>
>+#include <linux/platform_device.h>
>+#include <linux/thermal.h>
>+#include <linux/interrupt.h>
>+#include <linux/delay.h>
>+#include <linux/slab.h>
>+#include <linux/err.h>
>+#include <linux/pm.h>
>+#include <linux/bitops.h>
>+#include <linux/regmap.h>
>+#include <linux/of.h>
>+#include <linux/of_address.h>
>+#include <linux/of_platform.h>
>+#include <linux/io.h>
>+#include <linux/mfd/syscon.h>
>+
>+/* Trips: from very hot to very cold */
>+enum tsens_trip_type {
>+	TSENS_TRIP_STAGE3,
>+	TSENS_TRIP_STAGE2,
>+	TSENS_TRIP_STAGE1,
>+	TSENS_TRIP_STAGE0,
>+	TSENS_TRIP_NUM,
>+};
>+
>+#define TSENS_CAL_MDEGC				30000
>+
>+#define TSENS_MAX_SENSORS			11
>+
>+#define TSENS_8960_CONFIG_ADDR			0x3640
>+#define TSENS_8960_CONFIG			0x9b
>+#define TSENS_8960_CONFIG_MASK			0xf
>+
>+#define TSENS_CNTL_ADDR				0x3620
>+#define TSENS_CNTL_RESUME_MASK			0xfffffff9
>+#define TSENS_EN				BIT(0)
>+#define TSENS_SW_RST				BIT(1)
>+#define SENSOR0_EN				BIT(3)
>+#define TSENS_MIN_STATUS_MASK			BIT(0)
>+#define TSENS_LOWER_STATUS_CLR			BIT(1)
>+#define TSENS_UPPER_STATUS_CLR			BIT(2)
>+#define TSENS_MAX_STATUS_MASK			BIT(3)
>+#define TSENS_MEASURE_PERIOD			1
>+#define TSENS_8960_SLP_CLK_ENA			BIT(26)
>+#define TSENS_8660_SLP_CLK_ENA			BIT(24)
>+#define TSENS_8064_STATUS_CNTL			0x3660
>+
>+#define TSENS_THRESHOLD_ADDR			0x3624
>+#define TSENS_THRESHOLD_MAX_CODE		0xff
>+#define TSENS_THRESHOLD_MIN_CODE		0
>+#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT		24
>+#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT		16
>+#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT	8
>+#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT	0
>+
>+/* 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_S0_STATUS_ADDR			0x3628
>+
>+#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_SENSOR_SHIFT			16
>+#define TSENS_REDUND_SHIFT			24
>+#define TSENS_SENSOR0_SHIFT			3
>+
>+#define TSENS_8660_QFPROM_ADDR			0x00bc
>+#define TSENS_8660_CONFIG			1
>+#define TSENS_8660_CONFIG_SHIFT			28
>+#define TSENS_8660_CONFIG_MASK			(3 << TSENS_8660_CONFIG_SHIFT)
>+
>+struct tsens_device;
>+
>+struct tsens_sensor {
>+	struct thermal_zone_device	*tz_dev;
>+	enum thermal_device_mode	mode;
>+	unsigned int			sensor_num;
>+	int				offset;
>+	u32				slope;
>+	struct tsens_device		*tmdev;
>+	u32                             status;
>+};
>+
>+struct tsens_device {
>+	bool			prev_reading_avail;
>+	unsigned int		num_sensors;
>+	int			pm_tsens_thr_data;
>+	int			pm_tsens_cntl;
>+	unsigned int            calib_offset;
>+	unsigned int            backup_calib_offset;
>+	struct work_struct	tsens_work;
>+	struct regmap		*map;
>+	struct regmap_field	*status_field;
>+	struct tsens_sensor	sensor[0];
>+};
>+
>+static struct device *tsens_dev;
>+
>+/* Temperature on y axis and ADC-code on x-axis */
>+static int
>+tsens_tz_code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
>+{
>+	return adc_code * s->slope + s->offset;
>+}
>+
>+static int tsens_tz_get_temp(void *_sensor,
>+			     long *temp)
>+{
>+	const struct tsens_sensor *tm_sensor = _sensor;
>+	struct tsens_device *tmdev = tm_sensor->tmdev;
>+	u32 code, trdy;
>+
>+	if (tm_sensor->mode != THERMAL_DEVICE_ENABLED)
>+		return -EINVAL;
>+
>+	if (!tmdev->prev_reading_avail) {
>+		while (!regmap_read(tmdev->map, TSENS_INT_STATUS_ADDR, &trdy) &&
>+		       !(trdy & TSENS_TRDY_MASK))
>+			usleep_range(1000, 1100);
>+		tmdev->prev_reading_avail = true;
>+	}
>+
>+	regmap_read(tmdev->map, tm_sensor->status, &code);
>+	*temp = tsens_tz_code_to_mdegC(code, tm_sensor);
>+
>+	dev_dbg(tsens_dev, "Temperature for sensor %d is: %ld",
>+		tm_sensor->sensor_num, *temp);
>+
>+	return 0;
>+}
>+
>+/*
>+ * If the main sensor is disabled all the sensors are disable and
>+ * the clock is disabled.
>+ * If the main sensor is disabled and a sub-sensor is enabled
>+ * return with an error.
>+ */
>+static int tsens_tz_set_mode(struct tsens_sensor *tm_sensor,
>+			      enum thermal_device_mode mode)
>+{
>+	struct tsens_device *tmdev = tm_sensor->tmdev;
>+	unsigned int i, n = tmdev->num_sensors;
>+	u32 reg, mask;
>+
>+	if (mode == tm_sensor->mode)
>+		return 0;
>+
>+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg);
>+
>+	mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
>+	if (mode == THERMAL_DEVICE_ENABLED) {
>+		if (!(reg & SENSOR0_EN) && mask != SENSOR0_EN) {
>+			pr_err("Main sensor not enabled\n");
>+			return -EINVAL;
>+		}
>+
>+		regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg | TSENS_SW_RST);
>+		if (tmdev->num_sensors > 1)
>+			reg |= mask | TSENS_8960_SLP_CLK_ENA | TSENS_EN;
>+		else
>+			reg |= mask | TSENS_8660_SLP_CLK_ENA | TSENS_EN;
>+		tmdev->prev_reading_avail = false;
>+	} else {
>+		reg &= ~mask;
>+		if (!(reg & SENSOR0_EN)) {
>+			dev_warn(tsens_dev, "Main sensor not enabled. Disabling subsensors\n");
>+
>+			reg &= ~GENMASK(n + TSENS_SENSOR0_SHIFT - 1,
>+					TSENS_SENSOR0_SHIFT);
>+			reg &= ~TSENS_EN;
>+
>+			if (tmdev->num_sensors > 1)
>+				reg &= ~TSENS_8960_SLP_CLK_ENA;
>+			else
>+				reg &= ~TSENS_8660_SLP_CLK_ENA;
>+
>+			/* Disable all sub-sensors */
>+			for (i = 1; i < n; i++)
>+				tmdev->sensor[i].mode = mode;
>+		}
>+	}
>+
>+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg);
>+	tm_sensor->mode = mode;
>+
>+	return 0;
>+}
>+
>+#ifdef THERMAL_TSENS8960_HWTRIPS
>+static int tsens_get_trip_temp(struct tsens_sensor *tm_sensor,
>+				   int trip, int *temp)
>+{
>+	struct tsens_device *tmdev = tm_sensor->tmdev;
>+	u32 reg;
>+
>+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &reg);
>+	switch (trip) {
>+	case TSENS_TRIP_STAGE3:
>+		reg >>= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE2:
>+		reg >>= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE1:
>+		reg >>= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE0:
>+		reg >>= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+	reg &= TSENS_THRESHOLD_MAX_CODE;
>+
>+	temp = tsens_tz_code_to_mdegC(reg, tm_sensor);
>+
>+	dev_dbg(tsens_dev, "Sensor %d, trip %d temp is :%d",
>+		tm_sensor->sensor_num, trip, *temp);
>+
>+	return 0;
>+}
>+
>+static void tsens_print_trip_temp(struct tsens_sensor *tm_sensor)
>+{
>+	int temp;
>+
>+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE0, &temp);
>+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, &temp);
>+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, &temp);
>+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE3, &temp);
>+}
>+
>+static int tsens_tz_mdegC_to_code(int mdegC, const struct tsens_sensor *s)
>+{
>+	int code;
>+
>+	code = DIV_ROUND_CLOSEST(mdegC - s->offset, s->slope);
>+	return clamp(code, TSENS_THRESHOLD_MIN_CODE, TSENS_THRESHOLD_MAX_CODE);
>+}
>+
>+static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh)
>+{
>+	u32 hi_code;
>+
>+	switch (trip) {
>+	case TSENS_TRIP_STAGE0:
>+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
>+			hi_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE1:
>+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
>+			hi_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE2:
>+		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
>+			hi_code = thresh >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE3:
>+	default:
>+		hi_code = TSENS_THRESHOLD_MAX_CODE;
>+		break;
>+	}
>+
>+	return hi_code & TSENS_THRESHOLD_MAX_CODE;
>+}
>+
>+static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh)
>+{
>+	u32 lo_code;
>+
>+	switch (trip) {
>+	case TSENS_TRIP_STAGE3:
>+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
>+			lo_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE2:
>+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
>+			lo_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE1:
>+		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
>+			lo_code = thresh >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE0:
>+	default:
>+		lo_code = TSENS_THRESHOLD_MIN_CODE;
>+		break;
>+	}
>+
>+	return lo_code & TSENS_THRESHOLD_MAX_CODE;
>+}
>+
>+static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
>+	int trip, unsigned long temp)
>+{
>+	struct tsens_device *tmdev = tm_sensor->tmdev;
>+	struct regmap_field *status = tmdev->status_field;
>+	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
>+	u32 code, hi_code, lo_code, code_err_chk;
>+
>+	code_err_chk = code = tsens_tz_mdegC_to_code(temp, tm_sensor);
>+
>+	regmap_field_read(status, &reg_cntl);
>+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
>+
>+	switch (trip) {
>+	case TSENS_TRIP_STAGE3:
>+		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
>+		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE2:
>+		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE1:
>+		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE0:
>+		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
>+		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+
>+	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
>+	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
>+
>+	if (code_err_chk < lo_code || code_err_chk > hi_code)
>+		return -EINVAL;
>+
>+	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR, mask, code);
>+
>+	return 0;
>+}
>+
>+static int tsens_set_trips(void *_sensor, long low, long high)
>+{
>+	struct tsens_sensor *tm_sensor = _sensor;
>+
>+	tsens_print_trip_temp(tm_sensor);
>+
>+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
>+		return -EINVAL;
>+
>+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
>+		return -EINVAL;
>+
>+	return 0;
>+}
>+#endif
>+
>+static void tsens_scheduler_fn(struct work_struct *work)
>+{
>+	struct tsens_device *tmdev;
>+	struct regmap_field *status;
>+	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
>+	unsigned long sensor;
>+	bool upper_th_x, lower_th_x;
>+
>+	tmdev = container_of(work, struct tsens_device, tsens_work);
>+	status = tmdev->status_field;
>+
>+	regmap_field_update_bits(status,
>+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR,
>+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR);
>+
>+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
>+	threshold_low = threshold >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
>+	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+	threshold &= TSENS_THRESHOLD_MAX_CODE;
>+
>+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
>+	sensor = bits;
>+	sensor >>= TSENS_SENSOR0_SHIFT;
>+	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
>+		regmap_read(tmdev->map, tmdev->sensor[i].status, &code);
>+		upper_th_x = code >= threshold;
>+		lower_th_x = code <= threshold_low;
>+
>+		if (upper_th_x)
>+			mask |= TSENS_UPPER_STATUS_CLR;
>+
>+		if (lower_th_x)
>+			mask |= TSENS_LOWER_STATUS_CLR;
>+
>+#ifdef THERMAL_TSENS8960_HWTRIPS
>+		if (upper_th_x || lower_th_x) {
>+			dev_info(tsens_dev,
>+				"Threshold reached for sensor(%d)\n", i);
>+			thermal_zone_device_update(tmdev->sensor[i].tz_dev);
>+		}
>+#endif
>+	}
>+
>+	regmap_field_update_bits(status,
>+			TSENS_UPPER_STATUS_CLR | TSENS_LOWER_STATUS_CLR, mask);

Can TSENS IRQ fire before this write?
>+}
>+
>+static irqreturn_t tsens_isr(int irq, void *data)
>+{
>+	schedule_work(data);

You probably want to reduce the latency of interrupt notifications here.
If the kernel wq gets loaded up, your IRQ handling would suffer as well.

>+	return IRQ_HANDLED;
>+}
>+
>+#ifdef CONFIG_PM
>+static int tsens_suspend(struct device *dev)
>+{
>+	int i;
>+	struct tsens_device *tmdev = dev_get_drvdata(dev);
>+	struct regmap *map = tmdev->map;
>+
>+	regmap_read(map, TSENS_THRESHOLD_ADDR, &tmdev->pm_tsens_thr_data);
>+	regmap_read(map, TSENS_CNTL_ADDR, &tmdev->pm_tsens_cntl);
>+	regmap_update_bits(map, TSENS_CNTL_ADDR,
>+			   TSENS_8960_SLP_CLK_ENA | TSENS_EN, 0);
>+
>+	tmdev->prev_reading_avail = 0;
>+	for (i = 0; i < tmdev->num_sensors; i++)
>+		tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED;
>+
>+	return 0;
>+}
>+
>+static int tsens_resume(struct device *dev)
>+{
>+	int i;
>+	unsigned long reg_cntl;
>+	struct tsens_device *tmdev = dev_get_drvdata(dev);
>+	struct regmap *map = tmdev->map;
>+
>+	regmap_update_bits(map, TSENS_CNTL_ADDR, TSENS_SW_RST, TSENS_SW_RST);
>+	regmap_field_update_bits(tmdev->status_field,
>+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
>+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
>+
>+	if (tmdev->num_sensors > 1)
>+		regmap_update_bits(map, TSENS_8960_CONFIG_ADDR,
>+				TSENS_8960_CONFIG_MASK,
>+				TSENS_8960_CONFIG);
>+
>+	regmap_write(map, TSENS_THRESHOLD_ADDR, tmdev->pm_tsens_thr_data);
>+	regmap_write(map, TSENS_CNTL_ADDR, tmdev->pm_tsens_cntl);
>+
>+	reg_cntl = tmdev->pm_tsens_cntl;
>+	reg_cntl >>= TSENS_SENSOR0_SHIFT;
>+	for_each_set_bit(i, &reg_cntl, tmdev->num_sensors)
>+		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
>+
>+	return 0;
>+}
>+
>+static const struct dev_pm_ops tsens_pm_ops = {
>+	.suspend	= tsens_suspend,
>+	.resume		= tsens_resume,
>+};
>+#endif
>+
>+static void tsens_disable_mode(const struct tsens_device *tmdev)
>+{
>+	u32 reg_cntl;
>+	u32 mask;
>+
>+	mask = GENMASK(tmdev->num_sensors - 1, 0);
>+	mask <<= TSENS_SENSOR0_SHIFT;
>+	mask |= TSENS_EN;
>+
>+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
>+	reg_cntl &= ~mask;
>+	if (tmdev->num_sensors > 1)
>+		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
>+	else
>+		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
>+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
>+}
>+
>+static void tsens_hw_init(struct tsens_device *tmdev)
>+{
>+	u32 reg_cntl, reg_thr;
>+
>+	reg_cntl = TSENS_SW_RST;
>+	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR, TSENS_SW_RST, reg_cntl);
>+
>+	if (tmdev->num_sensors > 1) {
>+		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
>+			(TSENS_MEASURE_PERIOD << 18);
>+		reg_cntl &= ~TSENS_SW_RST;
>+		regmap_update_bits(tmdev->map, TSENS_8960_CONFIG_ADDR,
>+				   TSENS_8960_CONFIG_MASK, TSENS_8960_CONFIG);
>+	} else {
>+		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
>+			(TSENS_MEASURE_PERIOD << 16);
>+		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
>+		reg_cntl |= TSENS_8660_CONFIG << TSENS_8660_CONFIG_SHIFT;
>+	}
>+
>+	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << TSENS_SENSOR0_SHIFT;
>+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
>+
>+	regmap_field_update_bits(tmdev->status_field,
>+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
>+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
>+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
>+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
>+
>+	reg_cntl |= TSENS_EN;
>+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
>+
>+	reg_thr = (TSENS_LOWER_LIMIT_TH << TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
>+		(TSENS_UPPER_LIMIT_TH << TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
>+		(TSENS_MIN_LIMIT_TH << TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
>+		(TSENS_MAX_LIMIT_TH << TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
>+	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr);
>+}
>+
>+static int
>+tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap *map)
>+{
>+	u32 temp_data, data;
>+	struct tsens_sensor *s = &tmdev->sensor[0];
>+
>+	if (regmap_read(map, tmdev->calib_offset, &temp_data))
>+		return -EINVAL;
>+
>+	data = (temp_data >> 24) & 0xff;
>+
>+	if (!data) {
>+		dev_err(tsens_dev, "QFPROM TSENS calibration data not present\n");
>+		return -EINVAL;
>+	}
>+
>+	s->offset = TSENS_CAL_MDEGC - s->slope * data;
>+
>+	return 0;
>+}
>+
>+static int
>+tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap *map)
>+{
>+	int i;
>+	u32 temp_data[TSENS_MAX_SENSORS];
>+	u8 *byte_data;
>+	u32 fuse, redun, num_read;
>+	struct tsens_sensor *s = tmdev->sensor;
>+
>+	fuse = tmdev->calib_offset;
>+	redun = tmdev->backup_calib_offset;
>+
>+	/**
>+	* syscon regmap is 32-bit data, but calibration data is 8-bit.
>+	* read 4 bytes from regmap in a loop and then extract bytes seprately
>+	*/
>+
>+	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
>+
>+	for (i = 0; i < num_read; i++) {
>+		if (regmap_read(map, (redun + i*4), &temp_data[i]))
>+			return -EINVAL;
>+
>+		if (!temp_data[i]) {
>+			dev_dbg(tsens_dev, "Main calib data not valid\n");
>+			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
>+				return -EINVAL;
>+		}
>+	}
>+
>+	byte_data = (u8 *)temp_data;
>+
>+	for (i = 0; i < tmdev->num_sensors; i++, s++) {
>+		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
>+		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
>+	}
>+
>+	return 0;
>+}
>+
>+static const struct thermal_zone_of_device_ops tsens_thermal_of_ops = {
>+	.get_temp = tsens_tz_get_temp,
>+};
>+
>+
>+static int tsens_register(struct tsens_device *tmdev, int i)
>+{
>+	char name[18];
>+	u32 addr = TSENS_S0_STATUS_ADDR;
>+	struct tsens_sensor *s = &tmdev->sensor[i];
>+
>+	/*
>+	* 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.
>+	*/
>+	if (i >= 5)
>+		addr += 40;
>+
>+	addr += i * 4;
>+
>+	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
>+	s->mode = THERMAL_DEVICE_ENABLED;
>+	s->sensor_num = i;
>+	s->status = addr;
>+	s->tmdev = tmdev;
>+	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
>+						&tsens_thermal_of_ops);
>+
>+	if (IS_ERR(s->tz_dev))
>+		return -ENODEV;
>+
>+	return 0;
>+}
>+
>+static int tsens_probe(struct platform_device *pdev)
>+{
>+	struct device_node *np = pdev->dev.of_node;
>+	struct device_node *base_node;
>+	struct platform_device *base_pdev;
>+	int ret, i, irq, num;
>+	struct tsens_sensor *s;
>+	struct tsens_device *tmdev;
>+	struct regmap *map, *imem_regmap;
>+	struct reg_field *field;
>+	static struct reg_field status_0 = {
>+		.reg = TSENS_8064_STATUS_CNTL,
>+		.lsb = 0,
>+		.msb = 3,
>+	};
>+	static struct reg_field status_8 = {
>+		.reg = TSENS_CNTL_ADDR,
>+		.lsb = 8,
>+		.msb = 11,
>+	};
>+
>+	tsens_dev = &pdev->dev;
>+
>+	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
>+	if (num <= 0) {
>+		dev_err(tsens_dev, "invalid tsens slopes\n");
>+		return -EINVAL;
>+	}
>+
>+	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
>+			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
>+	if (tmdev == NULL)
>+		return -ENOMEM;
>+
>+	tmdev->num_sensors = num;
>+	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
>+		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
>+					   &s->slope);
>+
>+	irq = platform_get_irq(pdev, 0);
>+	if (irq < 0) {
>+		dev_err(tsens_dev,  "no irq resource?\n");
>+		return  -EINVAL;
>+	}
>+
>+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
>+					   &tmdev->calib_offset);
>+	if (ret != 0) {
>+		dev_err(tsens_dev,  "No calibration offset set\n");
>+		return  -EINVAL;
>+	}
>+
>+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
>+					   &tmdev->backup_calib_offset);
>+	if (ret) {
>+		dev_info(tsens_dev, "Missing backup calibration offset\n");
>+		tmdev->backup_calib_offset = tmdev->calib_offset;
>+	}
>+
>+	imem_regmap = syscon_regmap_lookup_by_phandle(np, "qcom,imem");
>+	if (IS_ERR(imem_regmap)) {
>+		dev_err(tsens_dev, "syscon regmap look up error\n");
>+		return PTR_ERR(imem_regmap);
>+	}
>+
>+	if (num == 1)
>+		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
>+	else
>+		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
>+
>+	if (ret < 0) {
>+		dev_err(tsens_dev, "tsense calibration failed\n");
>+		return ret;
>+	}
>+
>+	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
>+	if (base_node == NULL) {
>+		dev_err(tsens_dev, "no base node present\n");
>+		return  -EINVAL;
>+	}
>+
>+	base_pdev = of_find_device_by_node(base_node);
>+	if (base_pdev == NULL) {
>+		dev_err(tsens_dev, "no base pdev node\n");
>+		return  -ENODEV;
>+	}
>+
>+	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
>+	if (map == NULL) {
>+		dev_err(tsens_dev, "base regmap get failed\n");
>+		return  -ENODEV;
>+	}
>+
>+	/* Status bits move when the sensor bits next to them overlap */
>+	if (num > 5)
>+		field = &status_0;
>+	else
>+		field = &status_8;
>+
>+	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map, *field);
>+	if (IS_ERR(tmdev->status_field)) {
>+		dev_err(tsens_dev, "regmap alloc failed\n");
>+		return PTR_ERR(tmdev->status_field);
>+	}
>+
>+	tsens_hw_init(tmdev);
>+
>+	/*
>+	 * Register sensor 0 separately. This sensor is always
>+	 * expected to be present and if this fails, thermal
>+	 * sensor probe would fail
>+	 * Other sensors are optional and if registration fails
>+	 * disable the sensor and continue
>+	*/
>+	ret = tsens_register(tmdev, 0);
>+	if (ret < 0) {
>+		dev_err(tsens_dev, "Registering failed for primary sensor");
>+		ret = -ENODEV;
>+		goto fail;
>+	} else {
>+		tsens_tz_set_mode(&tmdev->sensor[0], THERMAL_DEVICE_ENABLED);
>+	}
>+
>+	for (i = 1;  i < tmdev->num_sensors; i++) {
>+		ret = tsens_register(tmdev, i);
>+
>+		if (ret < 0) {
>+			dev_err(tsens_dev,
>+				"Registering failed. Sensor(%i), disabled", i);
>+			tsens_tz_set_mode(&tmdev->sensor[i],
>+				THERMAL_DEVICE_DISABLED);
>+		} else {
>+			tsens_tz_set_mode(&tmdev->sensor[i],
>+				THERMAL_DEVICE_ENABLED);
>+		}
>+	}
>+
>+	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
>+
>+	ret = devm_request_irq(tsens_dev, irq, tsens_isr, IRQF_TRIGGER_RISING,
>+			       "tsens", &tmdev->tsens_work);
>+	if (ret < 0)
>+		goto err_irq;
>+
>+	platform_set_drvdata(pdev, tmdev);
>+
>+	dev_info(tsens_dev, "Tsens driver initialized\n");
>+
>+	return 0;
>+err_irq:
>+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
>+		thermal_zone_of_sensor_unregister(&pdev->dev, s->tz_dev);
>+fail:
>+	tsens_disable_mode(tmdev);
>+	return ret;
>+}
>+
>+static int tsens_remove(struct platform_device *pdev)
>+{
>+	int i;
>+	struct tsens_sensor *s;
>+	struct tsens_device *tmdev = platform_get_drvdata(pdev);
>+
>+	tsens_disable_mode(tmdev);
>+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
>+		thermal_zone_device_unregister(s->tz_dev);
>+
>+	return 0;
>+}
>+
>+static struct of_device_id tsens_match_table[] = {
>+	{.compatible = "qcom,ipq806x-tsens"},
>+	{},
>+};
>+
>+MODULE_DEVICE_TABLE(of, tsens_match_table);
>+
>+static struct platform_driver tsens_driver = {
>+	.probe = tsens_probe,
>+	.remove = tsens_remove,
>+	.driver = {
>+		.of_match_table = tsens_match_table,
>+		.name = "tsens8960-thermal",

Curious, why specifically 8960?

>+		.owner = THIS_MODULE,
>+#ifdef CONFIG_PM
>+		.pm	= &tsens_pm_ops,
>+#endif
>+	},
>+};
>+module_platform_driver(tsens_driver);
>+
>+MODULE_LICENSE("GPL v2");
>+MODULE_DESCRIPTION("Temperature Sensor driver");
>+MODULE_ALIAS("platform:tsens8960-tm");
>-- 
>Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project
>
>--
>To unsubscribe from this list: send the line "unsubscribe linux-pm" in
>the body of a message to majordomo@vger.kernel.org
>More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Narendran Rajan Jan. 28, 2015, 12:55 a.m. UTC | #2
> -----Original Message-----
> From: linux-arm-msm-owner@vger.kernel.org [mailto:linux-arm-msm-
> owner@vger.kernel.org] On Behalf Of Lina Iyer
> Sent: Tuesday, January 27, 2015 8:03 AM
> To: Narendran Rajan
> Cc: Zhang Rui; Eduardo Valentin; Linux ARM MSM; Linux PM; Siddartha
> Mohanadoss; Stephen Boyd
> Subject: Re: [PATCH] thermal: Add msm tsens thermal sensor driver
> 
> On Mon, Jan 26 2015 at 21:10 -0700, Narendran Rajan wrote:
> >TSENS supports reading temperature from multiple thermal sensors
> >present in QCOM SOCs.
> >TSENS HW is enabled only when the main sensor is requested.
> >The TSENS block is disabled if the main senors is disabled irrespective
> >of any other sensors that are being enabled.
> >TSENS driver supports configurable threshold for temperature monitoring
> >in which case it can generate an interrupt when specific thresholds are
> >reached
> >
> >Based on code by Siddartha Mohanadoss and Stephen Boyd.
> >
> >Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> >Cc: Stephen Boyd <sboyd@codeaurora.org>
> >Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
> >---
> > drivers/thermal/Kconfig         |  14 +
> > drivers/thermal/Makefile        |   1 +
> > drivers/thermal/msm8960_tsens.c | 841
> >++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 856 insertions(+)
> > create mode 100644 drivers/thermal/msm8960_tsens.c
> >
> >diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index
> >20915ca..a4cb2c0 100644
> >--- a/drivers/thermal/Kconfig
> >+++ b/drivers/thermal/Kconfig
> >@@ -261,6 +261,20 @@ config INTEL_SOC_DTS_THERMAL
> > 	  notification methods.The other trip is a critical trip point,
which
> > 	  was set by the driver based on the TJ MAX temperature.
> >
> >+config THERMAL_TSENS8960
> >+	tristate "Qualcomm 8960 Tsens Temperature driver"
> >+	depends on THERMAL
> >+	depends on ARCH_QCOM
> >+	help
> >+	  QCOM tsens thermal driver provides support for Temperature
> sensor
> >+	  (TSENS) found on QCOM SoCs. It supports four configurable trip
> points
> >+	  and controls multiple sensors on the SOC. The four trip points are
> >+	  common across all sensors present in the SoC. The number of
> sensors
> >+	  present vary from chip to chip and are set through device tree
> entry.
> >+	  The driver presents as a standard thermal zone device with
> configurable
> >+	  trip points and cooling device mapping through standard thermal
> zone
> >+	  device tree
> >+
> > menu "ACPI INT340X thermal drivers"
> > source drivers/thermal/int340x_thermal/Kconfig
> > endmenu
> >diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index
> >fa0dc48..23c7a34 100644
> >--- a/drivers/thermal/Makefile
> >+++ b/drivers/thermal/Makefile
> >@@ -39,3 +39,4 @@ obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-
> thermal/
> > obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
> > obj-$(CONFIG_ST_THERMAL)	+= st/
> > obj-$(CONFIG_TEGRA_SOCTHERM)	+= tegra_soctherm.o
> >+obj-$(CONFIG_THERMAL_TSENS8960)	+= msm8960_tsens.o
> >diff --git a/drivers/thermal/msm8960_tsens.c
> >b/drivers/thermal/msm8960_tsens.c new file mode 100644 index
> >0000000..307bdc8
> >--- /dev/null
> >+++ b/drivers/thermal/msm8960_tsens.c
> >@@ -0,0 +1,841 @@
> >+/* 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/kernel.h>
> >+#include <linux/module.h>
> >+#include <linux/platform_device.h>
> >+#include <linux/thermal.h>
> >+#include <linux/interrupt.h>
> >+#include <linux/delay.h>
> >+#include <linux/slab.h>
> >+#include <linux/err.h>
> >+#include <linux/pm.h>
> >+#include <linux/bitops.h>
> >+#include <linux/regmap.h>
> >+#include <linux/of.h>
> >+#include <linux/of_address.h>
> >+#include <linux/of_platform.h>
> >+#include <linux/io.h>
> >+#include <linux/mfd/syscon.h>
> >+
> >+/* Trips: from very hot to very cold */ enum tsens_trip_type {
> >+	TSENS_TRIP_STAGE3,
> >+	TSENS_TRIP_STAGE2,
> >+	TSENS_TRIP_STAGE1,
> >+	TSENS_TRIP_STAGE0,
> >+	TSENS_TRIP_NUM,
> >+};
> >+
> >+#define TSENS_CAL_MDEGC				30000
> >+
> >+#define TSENS_MAX_SENSORS			11
> >+
> >+#define TSENS_8960_CONFIG_ADDR			0x3640
> >+#define TSENS_8960_CONFIG			0x9b
> >+#define TSENS_8960_CONFIG_MASK			0xf
> >+
> >+#define TSENS_CNTL_ADDR				0x3620
> >+#define TSENS_CNTL_RESUME_MASK			0xfffffff9
> >+#define TSENS_EN				BIT(0)
> >+#define TSENS_SW_RST				BIT(1)
> >+#define SENSOR0_EN				BIT(3)
> >+#define TSENS_MIN_STATUS_MASK			BIT(0)
> >+#define TSENS_LOWER_STATUS_CLR			BIT(1)
> >+#define TSENS_UPPER_STATUS_CLR			BIT(2)
> >+#define TSENS_MAX_STATUS_MASK			BIT(3)
> >+#define TSENS_MEASURE_PERIOD			1
> >+#define TSENS_8960_SLP_CLK_ENA			BIT(26)
> >+#define TSENS_8660_SLP_CLK_ENA			BIT(24)
> >+#define TSENS_8064_STATUS_CNTL			0x3660
> >+
> >+#define TSENS_THRESHOLD_ADDR			0x3624
> >+#define TSENS_THRESHOLD_MAX_CODE		0xff
> >+#define TSENS_THRESHOLD_MIN_CODE		0
> >+#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT		24
> >+#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT		16
> >+#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT	8
> >+#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT	0
> >+
> >+/* 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_S0_STATUS_ADDR			0x3628
> >+
> >+#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_SENSOR_SHIFT			16
> >+#define TSENS_REDUND_SHIFT			24
> >+#define TSENS_SENSOR0_SHIFT			3
> >+
> >+#define TSENS_8660_QFPROM_ADDR			0x00bc
> >+#define TSENS_8660_CONFIG			1
> >+#define TSENS_8660_CONFIG_SHIFT			28
> >+#define TSENS_8660_CONFIG_MASK			(3 <<
> TSENS_8660_CONFIG_SHIFT)
> >+
> >+struct tsens_device;
> >+
> >+struct tsens_sensor {
> >+	struct thermal_zone_device	*tz_dev;
> >+	enum thermal_device_mode	mode;
> >+	unsigned int			sensor_num;
> >+	int				offset;
> >+	u32				slope;
> >+	struct tsens_device		*tmdev;
> >+	u32                             status;
> >+};
> >+
> >+struct tsens_device {
> >+	bool			prev_reading_avail;
> >+	unsigned int		num_sensors;
> >+	int			pm_tsens_thr_data;
> >+	int			pm_tsens_cntl;
> >+	unsigned int            calib_offset;
> >+	unsigned int            backup_calib_offset;
> >+	struct work_struct	tsens_work;
> >+	struct regmap		*map;
> >+	struct regmap_field	*status_field;
> >+	struct tsens_sensor	sensor[0];
> >+};
> >+
> >+static struct device *tsens_dev;
> >+
> >+/* Temperature on y axis and ADC-code on x-axis */ static int
> >+tsens_tz_code_to_mdegC(u32 adc_code, const struct tsens_sensor *s) {
> >+	return adc_code * s->slope + s->offset; }
> >+
> >+static int tsens_tz_get_temp(void *_sensor,
> >+			     long *temp)
> >+{
> >+	const struct tsens_sensor *tm_sensor = _sensor;
> >+	struct tsens_device *tmdev = tm_sensor->tmdev;
> >+	u32 code, trdy;
> >+
> >+	if (tm_sensor->mode != THERMAL_DEVICE_ENABLED)
> >+		return -EINVAL;
> >+
> >+	if (!tmdev->prev_reading_avail) {
> >+		while (!regmap_read(tmdev->map,
> TSENS_INT_STATUS_ADDR, &trdy) &&
> >+		       !(trdy & TSENS_TRDY_MASK))
> >+			usleep_range(1000, 1100);
> >+		tmdev->prev_reading_avail = true;
> >+	}
> >+
> >+	regmap_read(tmdev->map, tm_sensor->status, &code);
> >+	*temp = tsens_tz_code_to_mdegC(code, tm_sensor);
> >+
> >+	dev_dbg(tsens_dev, "Temperature for sensor %d is: %ld",
> >+		tm_sensor->sensor_num, *temp);
> >+
> >+	return 0;
> >+}
> >+
> >+/*
> >+ * If the main sensor is disabled all the sensors are disable and
> >+ * the clock is disabled.
> >+ * If the main sensor is disabled and a sub-sensor is enabled
> >+ * return with an error.
> >+ */
> >+static int tsens_tz_set_mode(struct tsens_sensor *tm_sensor,
> >+			      enum thermal_device_mode mode) {
> >+	struct tsens_device *tmdev = tm_sensor->tmdev;
> >+	unsigned int i, n = tmdev->num_sensors;
> >+	u32 reg, mask;
> >+
> >+	if (mode == tm_sensor->mode)
> >+		return 0;
> >+
> >+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg);
> >+
> >+	mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
> >+	if (mode == THERMAL_DEVICE_ENABLED) {
> >+		if (!(reg & SENSOR0_EN) && mask != SENSOR0_EN) {
> >+			pr_err("Main sensor not enabled\n");
> >+			return -EINVAL;
> >+		}
> >+
> >+		regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg |
> TSENS_SW_RST);
> >+		if (tmdev->num_sensors > 1)
> >+			reg |= mask | TSENS_8960_SLP_CLK_ENA |
> TSENS_EN;
> >+		else
> >+			reg |= mask | TSENS_8660_SLP_CLK_ENA |
> TSENS_EN;
> >+		tmdev->prev_reading_avail = false;
> >+	} else {
> >+		reg &= ~mask;
> >+		if (!(reg & SENSOR0_EN)) {
> >+			dev_warn(tsens_dev, "Main sensor not enabled.
> Disabling
> >+subsensors\n");
> >+
> >+			reg &= ~GENMASK(n + TSENS_SENSOR0_SHIFT - 1,
> >+					TSENS_SENSOR0_SHIFT);
> >+			reg &= ~TSENS_EN;
> >+
> >+			if (tmdev->num_sensors > 1)
> >+				reg &= ~TSENS_8960_SLP_CLK_ENA;
> >+			else
> >+				reg &= ~TSENS_8660_SLP_CLK_ENA;
> >+
> >+			/* Disable all sub-sensors */
> >+			for (i = 1; i < n; i++)
> >+				tmdev->sensor[i].mode = mode;
> >+		}
> >+	}
> >+
> >+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg);
> >+	tm_sensor->mode = mode;
> >+
> >+	return 0;
> >+}
> >+
> >+#ifdef THERMAL_TSENS8960_HWTRIPS
> >+static int tsens_get_trip_temp(struct tsens_sensor *tm_sensor,
> >+				   int trip, int *temp)
> >+{
> >+	struct tsens_device *tmdev = tm_sensor->tmdev;
> >+	u32 reg;
> >+
> >+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &reg);
> >+	switch (trip) {
> >+	case TSENS_TRIP_STAGE3:
> >+		reg >>= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE2:
> >+		reg >>= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE1:
> >+		reg >>= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE0:
> >+		reg >>= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+	reg &= TSENS_THRESHOLD_MAX_CODE;
> >+
> >+	temp = tsens_tz_code_to_mdegC(reg, tm_sensor);
> >+
> >+	dev_dbg(tsens_dev, "Sensor %d, trip %d temp is :%d",
> >+		tm_sensor->sensor_num, trip, *temp);
> >+
> >+	return 0;
> >+}
> >+
> >+static void tsens_print_trip_temp(struct tsens_sensor *tm_sensor) {
> >+	int temp;
> >+
> >+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE0, &temp);
> >+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, &temp);
> >+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, &temp);
> >+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE3, &temp); }
> >+
> >+static int tsens_tz_mdegC_to_code(int mdegC, const struct tsens_sensor
> >+*s) {
> >+	int code;
> >+
> >+	code = DIV_ROUND_CLOSEST(mdegC - s->offset, s->slope);
> >+	return clamp(code, TSENS_THRESHOLD_MIN_CODE,
> >+TSENS_THRESHOLD_MAX_CODE); }
> >+
> >+static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh) {
> >+	u32 hi_code;
> >+
> >+	switch (trip) {
> >+	case TSENS_TRIP_STAGE0:
> >+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> >+			hi_code = thresh >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE1:
> >+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> >+			hi_code = thresh >>
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE2:
> >+		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
> >+			hi_code = thresh >>
> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE3:
> >+	default:
> >+		hi_code = TSENS_THRESHOLD_MAX_CODE;
> >+		break;
> >+	}
> >+
> >+	return hi_code & TSENS_THRESHOLD_MAX_CODE; }
> >+
> >+static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh) {
> >+	u32 lo_code;
> >+
> >+	switch (trip) {
> >+	case TSENS_TRIP_STAGE3:
> >+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> >+			lo_code = thresh >>
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE2:
> >+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> >+			lo_code = thresh >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE1:
> >+		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
> >+			lo_code = thresh >>
> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE0:
> >+	default:
> >+		lo_code = TSENS_THRESHOLD_MIN_CODE;
> >+		break;
> >+	}
> >+
> >+	return lo_code & TSENS_THRESHOLD_MAX_CODE; }
> >+
> >+static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
> >+	int trip, unsigned long temp)
> >+{
> >+	struct tsens_device *tmdev = tm_sensor->tmdev;
> >+	struct regmap_field *status = tmdev->status_field;
> >+	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
> >+	u32 code, hi_code, lo_code, code_err_chk;
> >+
> >+	code_err_chk = code = tsens_tz_mdegC_to_code(temp,
> tm_sensor);
> >+
> >+	regmap_field_read(status, &reg_cntl);
> >+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
> >+
> >+	switch (trip) {
> >+	case TSENS_TRIP_STAGE3:
> >+		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> >+		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE2:
> >+		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE1:
> >+		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE0:
> >+		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> >+		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+
> >+	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
> >+	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
> >+
> >+	if (code_err_chk < lo_code || code_err_chk > hi_code)
> >+		return -EINVAL;
> >+
> >+	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR,
> mask, code);
> >+
> >+	return 0;
> >+}
> >+
> >+static int tsens_set_trips(void *_sensor, long low, long high) {
> >+	struct tsens_sensor *tm_sensor = _sensor;
> >+
> >+	tsens_print_trip_temp(tm_sensor);
> >+
> >+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
> >+		return -EINVAL;
> >+
> >+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
> >+		return -EINVAL;
> >+
> >+	return 0;
> >+}
> >+#endif
> >+
> >+static void tsens_scheduler_fn(struct work_struct *work) {
> >+	struct tsens_device *tmdev;
> >+	struct regmap_field *status;
> >+	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
> >+	unsigned long sensor;
> >+	bool upper_th_x, lower_th_x;
> >+
> >+	tmdev = container_of(work, struct tsens_device, tsens_work);
> >+	status = tmdev->status_field;
> >+
> >+	regmap_field_update_bits(status,
> >+			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR,
> >+			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR);
> >+
> >+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
> >+	threshold_low = threshold >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
> >+	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+	threshold &= TSENS_THRESHOLD_MAX_CODE;
> >+
> >+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
> >+	sensor = bits;
> >+	sensor >>= TSENS_SENSOR0_SHIFT;
> >+	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
> >+		regmap_read(tmdev->map, tmdev->sensor[i].status,
> &code);
> >+		upper_th_x = code >= threshold;
> >+		lower_th_x = code <= threshold_low;
> >+
> >+		if (upper_th_x)
> >+			mask |= TSENS_UPPER_STATUS_CLR;
> >+
> >+		if (lower_th_x)
> >+			mask |= TSENS_LOWER_STATUS_CLR;
> >+
> >+#ifdef THERMAL_TSENS8960_HWTRIPS
> >+		if (upper_th_x || lower_th_x) {
> >+			dev_info(tsens_dev,
> >+				"Threshold reached for sensor(%d)\n", i);
> >+			thermal_zone_device_update(tmdev-
> >sensor[i].tz_dev);
> >+		}
> >+#endif
> >+	}
> >+
> >+	regmap_field_update_bits(status,
> >+			TSENS_UPPER_STATUS_CLR |
> TSENS_LOWER_STATUS_CLR, mask);
> 
> Can TSENS IRQ fire before this write?

Yes it can as per document, let me run further experiments and update.

> >+}
> >+
> >+static irqreturn_t tsens_isr(int irq, void *data) {
> >+	schedule_work(data);
> 
> You probably want to reduce the latency of interrupt notifications here.
> If the kernel wq gets loaded up, your IRQ handling would suffer as well.

Good point.  Thx. Let me run further experiments on interrupt firing to
confirm the behavior on interrupt frequency and load.
PS: By default thermal framework uses a polling mode and default thresholds
set are very high.

> 
> >+	return IRQ_HANDLED;
> >+}
> >+
> >+#ifdef CONFIG_PM
> >+static int tsens_suspend(struct device *dev) {
> >+	int i;
> >+	struct tsens_device *tmdev = dev_get_drvdata(dev);
> >+	struct regmap *map = tmdev->map;
> >+
> >+	regmap_read(map, TSENS_THRESHOLD_ADDR, &tmdev-
> >pm_tsens_thr_data);
> >+	regmap_read(map, TSENS_CNTL_ADDR, &tmdev->pm_tsens_cntl);
> >+	regmap_update_bits(map, TSENS_CNTL_ADDR,
> >+			   TSENS_8960_SLP_CLK_ENA | TSENS_EN, 0);
> >+
> >+	tmdev->prev_reading_avail = 0;
> >+	for (i = 0; i < tmdev->num_sensors; i++)
> >+		tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED;
> >+
> >+	return 0;
> >+}
> >+
> >+static int tsens_resume(struct device *dev) {
> >+	int i;
> >+	unsigned long reg_cntl;
> >+	struct tsens_device *tmdev = dev_get_drvdata(dev);
> >+	struct regmap *map = tmdev->map;
> >+
> >+	regmap_update_bits(map, TSENS_CNTL_ADDR, TSENS_SW_RST,
> TSENS_SW_RST);
> >+	regmap_field_update_bits(tmdev->status_field,
> >+			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK,
> >+			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK);
> >+
> >+	if (tmdev->num_sensors > 1)
> >+		regmap_update_bits(map, TSENS_8960_CONFIG_ADDR,
> >+				TSENS_8960_CONFIG_MASK,
> >+				TSENS_8960_CONFIG);
> >+
> >+	regmap_write(map, TSENS_THRESHOLD_ADDR, tmdev-
> >pm_tsens_thr_data);
> >+	regmap_write(map, TSENS_CNTL_ADDR, tmdev->pm_tsens_cntl);
> >+
> >+	reg_cntl = tmdev->pm_tsens_cntl;
> >+	reg_cntl >>= TSENS_SENSOR0_SHIFT;
> >+	for_each_set_bit(i, &reg_cntl, tmdev->num_sensors)
> >+		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
> >+
> >+	return 0;
> >+}
> >+
> >+static const struct dev_pm_ops tsens_pm_ops = {
> >+	.suspend	= tsens_suspend,
> >+	.resume		= tsens_resume,
> >+};
> >+#endif
> >+
> >+static void tsens_disable_mode(const struct tsens_device *tmdev) {
> >+	u32 reg_cntl;
> >+	u32 mask;
> >+
> >+	mask = GENMASK(tmdev->num_sensors - 1, 0);
> >+	mask <<= TSENS_SENSOR0_SHIFT;
> >+	mask |= TSENS_EN;
> >+
> >+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
> >+	reg_cntl &= ~mask;
> >+	if (tmdev->num_sensors > 1)
> >+		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
> >+	else
> >+		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
> >+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl); }
> >+
> >+static void tsens_hw_init(struct tsens_device *tmdev) {
> >+	u32 reg_cntl, reg_thr;
> >+
> >+	reg_cntl = TSENS_SW_RST;
> >+	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR,
> TSENS_SW_RST,
> >+reg_cntl);
> >+
> >+	if (tmdev->num_sensors > 1) {
> >+		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
> >+			(TSENS_MEASURE_PERIOD << 18);
> >+		reg_cntl &= ~TSENS_SW_RST;
> >+		regmap_update_bits(tmdev->map,
> TSENS_8960_CONFIG_ADDR,
> >+				   TSENS_8960_CONFIG_MASK,
> TSENS_8960_CONFIG);
> >+	} else {
> >+		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
> >+			(TSENS_MEASURE_PERIOD << 16);
> >+		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
> >+		reg_cntl |= TSENS_8660_CONFIG <<
> TSENS_8660_CONFIG_SHIFT;
> >+	}
> >+
> >+	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) <<
> TSENS_SENSOR0_SHIFT;
> >+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> >+
> >+	regmap_field_update_bits(tmdev->status_field,
> >+			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR |
> >+			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK,
> >+			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR |
> >+			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK);
> >+
> >+	reg_cntl |= TSENS_EN;
> >+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> >+
> >+	reg_thr = (TSENS_LOWER_LIMIT_TH <<
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
> >+		(TSENS_UPPER_LIMIT_TH <<
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
> >+		(TSENS_MIN_LIMIT_TH <<
> TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
> >+		(TSENS_MAX_LIMIT_TH <<
> TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
> >+	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr); }
> >+
> >+static int
> >+tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap
> >+*map) {
> >+	u32 temp_data, data;
> >+	struct tsens_sensor *s = &tmdev->sensor[0];
> >+
> >+	if (regmap_read(map, tmdev->calib_offset, &temp_data))
> >+		return -EINVAL;
> >+
> >+	data = (temp_data >> 24) & 0xff;
> >+
> >+	if (!data) {
> >+		dev_err(tsens_dev, "QFPROM TSENS calibration data not
> present\n");
> >+		return -EINVAL;
> >+	}
> >+
> >+	s->offset = TSENS_CAL_MDEGC - s->slope * data;
> >+
> >+	return 0;
> >+}
> >+
> >+static int
> >+tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap
> >+*map) {
> >+	int i;
> >+	u32 temp_data[TSENS_MAX_SENSORS];
> >+	u8 *byte_data;
> >+	u32 fuse, redun, num_read;
> >+	struct tsens_sensor *s = tmdev->sensor;
> >+
> >+	fuse = tmdev->calib_offset;
> >+	redun = tmdev->backup_calib_offset;
> >+
> >+	/**
> >+	* syscon regmap is 32-bit data, but calibration data is 8-bit.
> >+	* read 4 bytes from regmap in a loop and then extract bytes
> seprately
> >+	*/
> >+
> >+	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
> >+
> >+	for (i = 0; i < num_read; i++) {
> >+		if (regmap_read(map, (redun + i*4), &temp_data[i]))
> >+			return -EINVAL;
> >+
> >+		if (!temp_data[i]) {
> >+			dev_dbg(tsens_dev, "Main calib data not valid\n");
> >+			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
> >+				return -EINVAL;
> >+		}
> >+	}
> >+
> >+	byte_data = (u8 *)temp_data;
> >+
> >+	for (i = 0; i < tmdev->num_sensors; i++, s++) {
> >+		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
> >+		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
> >+	}
> >+
> >+	return 0;
> >+}
> >+
> >+static const struct thermal_zone_of_device_ops tsens_thermal_of_ops =
> {
> >+	.get_temp = tsens_tz_get_temp,
> >+};
> >+
> >+
> >+static int tsens_register(struct tsens_device *tmdev, int i) {
> >+	char name[18];
> >+	u32 addr = TSENS_S0_STATUS_ADDR;
> >+	struct tsens_sensor *s = &tmdev->sensor[i];
> >+
> >+	/*
> >+	* 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.
> >+	*/
> >+	if (i >= 5)
> >+		addr += 40;
> >+
> >+	addr += i * 4;
> >+
> >+	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
> >+	s->mode = THERMAL_DEVICE_ENABLED;
> >+	s->sensor_num = i;
> >+	s->status = addr;
> >+	s->tmdev = tmdev;
> >+	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
> >+						&tsens_thermal_of_ops);
> >+
> >+	if (IS_ERR(s->tz_dev))
> >+		return -ENODEV;
> >+
> >+	return 0;
> >+}
> >+
> >+static int tsens_probe(struct platform_device *pdev) {
> >+	struct device_node *np = pdev->dev.of_node;
> >+	struct device_node *base_node;
> >+	struct platform_device *base_pdev;
> >+	int ret, i, irq, num;
> >+	struct tsens_sensor *s;
> >+	struct tsens_device *tmdev;
> >+	struct regmap *map, *imem_regmap;
> >+	struct reg_field *field;
> >+	static struct reg_field status_0 = {
> >+		.reg = TSENS_8064_STATUS_CNTL,
> >+		.lsb = 0,
> >+		.msb = 3,
> >+	};
> >+	static struct reg_field status_8 = {
> >+		.reg = TSENS_CNTL_ADDR,
> >+		.lsb = 8,
> >+		.msb = 11,
> >+	};
> >+
> >+	tsens_dev = &pdev->dev;
> >+
> >+	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
> >+	if (num <= 0) {
> >+		dev_err(tsens_dev, "invalid tsens slopes\n");
> >+		return -EINVAL;
> >+	}
> >+
> >+	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
> >+			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
> >+	if (tmdev == NULL)
> >+		return -ENOMEM;
> >+
> >+	tmdev->num_sensors = num;
> >+	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
> >+		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
> >+					   &s->slope);
> >+
> >+	irq = platform_get_irq(pdev, 0);
> >+	if (irq < 0) {
> >+		dev_err(tsens_dev,  "no irq resource?\n");
> >+		return  -EINVAL;
> >+	}
> >+
> >+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
> >+					   &tmdev->calib_offset);
> >+	if (ret != 0) {
> >+		dev_err(tsens_dev,  "No calibration offset set\n");
> >+		return  -EINVAL;
> >+	}
> >+
> >+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
> >+					   &tmdev->backup_calib_offset);
> >+	if (ret) {
> >+		dev_info(tsens_dev, "Missing backup calibration offset\n");
> >+		tmdev->backup_calib_offset = tmdev->calib_offset;
> >+	}
> >+
> >+	imem_regmap = syscon_regmap_lookup_by_phandle(np,
> "qcom,imem");
> >+	if (IS_ERR(imem_regmap)) {
> >+		dev_err(tsens_dev, "syscon regmap look up error\n");
> >+		return PTR_ERR(imem_regmap);
> >+	}
> >+
> >+	if (num == 1)
> >+		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
> >+	else
> >+		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
> >+
> >+	if (ret < 0) {
> >+		dev_err(tsens_dev, "tsense calibration failed\n");
> >+		return ret;
> >+	}
> >+
> >+	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
> >+	if (base_node == NULL) {
> >+		dev_err(tsens_dev, "no base node present\n");
> >+		return  -EINVAL;
> >+	}
> >+
> >+	base_pdev = of_find_device_by_node(base_node);
> >+	if (base_pdev == NULL) {
> >+		dev_err(tsens_dev, "no base pdev node\n");
> >+		return  -ENODEV;
> >+	}
> >+
> >+	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
> >+	if (map == NULL) {
> >+		dev_err(tsens_dev, "base regmap get failed\n");
> >+		return  -ENODEV;
> >+	}
> >+
> >+	/* Status bits move when the sensor bits next to them overlap */
> >+	if (num > 5)
> >+		field = &status_0;
> >+	else
> >+		field = &status_8;
> >+
> >+	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map,
> *field);
> >+	if (IS_ERR(tmdev->status_field)) {
> >+		dev_err(tsens_dev, "regmap alloc failed\n");
> >+		return PTR_ERR(tmdev->status_field);
> >+	}
> >+
> >+	tsens_hw_init(tmdev);
> >+
> >+	/*
> >+	 * Register sensor 0 separately. This sensor is always
> >+	 * expected to be present and if this fails, thermal
> >+	 * sensor probe would fail
> >+	 * Other sensors are optional and if registration fails
> >+	 * disable the sensor and continue
> >+	*/
> >+	ret = tsens_register(tmdev, 0);
> >+	if (ret < 0) {
> >+		dev_err(tsens_dev, "Registering failed for primary sensor");
> >+		ret = -ENODEV;
> >+		goto fail;
> >+	} else {
> >+		tsens_tz_set_mode(&tmdev->sensor[0],
> THERMAL_DEVICE_ENABLED);
> >+	}
> >+
> >+	for (i = 1;  i < tmdev->num_sensors; i++) {
> >+		ret = tsens_register(tmdev, i);
> >+
> >+		if (ret < 0) {
> >+			dev_err(tsens_dev,
> >+				"Registering failed. Sensor(%i), disabled",
i);
> >+			tsens_tz_set_mode(&tmdev->sensor[i],
> >+				THERMAL_DEVICE_DISABLED);
> >+		} else {
> >+			tsens_tz_set_mode(&tmdev->sensor[i],
> >+				THERMAL_DEVICE_ENABLED);
> >+		}
> >+	}
> >+
> >+	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
> >+
> >+	ret = devm_request_irq(tsens_dev, irq, tsens_isr,
> IRQF_TRIGGER_RISING,
> >+			       "tsens", &tmdev->tsens_work);
> >+	if (ret < 0)
> >+		goto err_irq;
> >+
> >+	platform_set_drvdata(pdev, tmdev);
> >+
> >+	dev_info(tsens_dev, "Tsens driver initialized\n");
> >+
> >+	return 0;
> >+err_irq:
> >+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> >+		thermal_zone_of_sensor_unregister(&pdev->dev, s-
> >tz_dev);
> >+fail:
> >+	tsens_disable_mode(tmdev);
> >+	return ret;
> >+}
> >+
> >+static int tsens_remove(struct platform_device *pdev) {
> >+	int i;
> >+	struct tsens_sensor *s;
> >+	struct tsens_device *tmdev = platform_get_drvdata(pdev);
> >+
> >+	tsens_disable_mode(tmdev);
> >+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> >+		thermal_zone_device_unregister(s->tz_dev);
> >+
> >+	return 0;
> >+}
> >+
> >+static struct of_device_id tsens_match_table[] = {
> >+	{.compatible = "qcom,ipq806x-tsens"},
> >+	{},
> >+};
> >+
> >+MODULE_DEVICE_TABLE(of, tsens_match_table);
> >+
> >+static struct platform_driver tsens_driver = {
> >+	.probe = tsens_probe,
> >+	.remove = tsens_remove,
> >+	.driver = {
> >+		.of_match_table = tsens_match_table,
> >+		.name = "tsens8960-thermal",
> 
> Curious, why specifically 8960?

I guess your point is why not tsens-thermal ? 
There are different versions of tsens controller used within QCOM SoCs. 
May need a different driver for newer versions of this controller. 
Picked 8960 as I guess this was the earliest chip where this controller was
used.

> 
> >+		.owner = THIS_MODULE,
> >+#ifdef CONFIG_PM
> >+		.pm	= &tsens_pm_ops,
> >+#endif
> >+	},
> >+};
> >+module_platform_driver(tsens_driver);
> >+
> >+MODULE_LICENSE("GPL v2");
> >+MODULE_DESCRIPTION("Temperature Sensor driver");
> >+MODULE_ALIAS("platform:tsens8960-tm");
> >--
> >Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc.
> >is a member of the Code Aurora Forum, a Linux Foundation Collaborative
> >Project
> >
> >--
> >To unsubscribe from this list: send the line "unsubscribe linux-pm" in
> >the body of a message to majordomo@vger.kernel.org More majordomo
> info
> >at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
in
> the body of a message to majordomo@vger.kernel.org More majordomo
> info at  http://vger.kernel.org/majordomo-info.html

--Naren

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Lina Iyer Jan. 28, 2015, 1:18 a.m. UTC | #3
On Tue, Jan 27 2015 at 17:55 -0700, Narendran Rajan wrote:
>
>> -----Original Message-----
>> From: linux-arm-msm-owner@vger.kernel.org [mailto:linux-arm-msm-
>> owner@vger.kernel.org] On Behalf Of Lina Iyer
>> Sent: Tuesday, January 27, 2015 8:03 AM
>> To: Narendran Rajan
>> Cc: Zhang Rui; Eduardo Valentin; Linux ARM MSM; Linux PM; Siddartha
>> Mohanadoss; Stephen Boyd
>> Subject: Re: [PATCH] thermal: Add msm tsens thermal sensor driver
>>
>> On Mon, Jan 26 2015 at 21:10 -0700, Narendran Rajan wrote:
>> >TSENS supports reading temperature from multiple thermal sensors
>> >present in QCOM SOCs.
>> >TSENS HW is enabled only when the main sensor is requested.
>> >The TSENS block is disabled if the main senors is disabled irrespective
>> >of any other sensors that are being enabled.
>> >TSENS driver supports configurable threshold for temperature monitoring
>> >in which case it can generate an interrupt when specific thresholds are
>> >reached
>> >
>> >Based on code by Siddartha Mohanadoss and Stephen Boyd.
>> >
>> >Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
>> >Cc: Stephen Boyd <sboyd@codeaurora.org>
>> >Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
>> >---

[...]
>> >+	regmap_field_update_bits(status,
>> >+			TSENS_UPPER_STATUS_CLR |
>> TSENS_LOWER_STATUS_CLR, mask);
>>
>> Can TSENS IRQ fire before this write?
>
>Yes it can as per document, let me run further experiments and update.
>
If the IRQ fires before this work-fn() is complete, schedule_work() would not
reschedule. 

>> >+}
>> >+
>> >+static irqreturn_t tsens_isr(int irq, void *data) {
>> >+	schedule_work(data);
>>
>> You probably want to reduce the latency of interrupt notifications here.
>> If the kernel wq gets loaded up, your IRQ handling would suffer as well.
>
>Good point.  Thx. Let me run further experiments on interrupt firing to
>confirm the behavior on interrupt frequency and load.
>PS: By default thermal framework uses a polling mode and default thresholds
>set are very high.
>
Yes, sure. But thermal ramps are better handled immediately. IRQs are
the most preferred on production devices.

>>
>> >+	return IRQ_HANDLED;
>> >+}
>> >+

[...]
> >+static struct of_device_id tsens_match_table[] = {
>> >+	{.compatible = "qcom,ipq806x-tsens"},
>> >+	{},
>> >+};
>> >+
>> >+MODULE_DEVICE_TABLE(of, tsens_match_table);
>> >+
>> >+static struct platform_driver tsens_driver = {
>> >+	.probe = tsens_probe,
>> >+	.remove = tsens_remove,
>> >+	.driver = {
>> >+		.of_match_table = tsens_match_table,
>> >+		.name = "tsens8960-thermal",
>>
>> Curious, why specifically 8960?
>
>I guess your point is why not tsens-thermal ?
>There are different versions of tsens controller used within QCOM SoCs.
>May need a different driver for newer versions of this controller.
>Picked 8960 as I guess this was the earliest chip where this controller was
>used.

It would be nice if you keep this generic, as it will be applicable to a
slew of QCOM SoC's. I havent seen it change much in the past few SoCs.
You could also use the compatible flag for any small revisional changes.

>
>>
>> >+		.owner = THIS_MODULE,
>> >+#ifdef CONFIG_PM
>> >+		.pm	= &tsens_pm_ops,
>> >+#endif
>> >+	},
>> >+};
>> >+module_platform_driver(tsens_driver);
>> >+
>> >+MODULE_LICENSE("GPL v2");
>> >+MODULE_DESCRIPTION("Temperature Sensor driver");
>> >+MODULE_ALIAS("platform:tsens8960-tm");
>> >--
>> >Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc.
>> >is a member of the Code Aurora Forum, a Linux Foundation Collaborative
>> >Project
>> >
>> >--
>> >To unsubscribe from this list: send the line "unsubscribe linux-pm" in
>> >the body of a message to majordomo@vger.kernel.org More majordomo
>> info
>> >at  http://vger.kernel.org/majordomo-info.html
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>in
>> the body of a message to majordomo@vger.kernel.org More majordomo
>> info at  http://vger.kernel.org/majordomo-info.html
>
>--Naren
>
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Lina Iyer Jan. 28, 2015, 5:01 p.m. UTC | #4
On Mon, Jan 26 2015 at 21:10 -0700, Narendran Rajan wrote:
>TSENS supports reading temperature from multiple thermal
>sensors present in QCOM SOCs.
>TSENS HW is enabled only when the main sensor is requested.
>The TSENS block is disabled if the main senors is disabled
>irrespective of any other sensors that are being enabled.
>TSENS driver supports configurable threshold for temperature
>monitoring in which case it can generate an interrupt when specific
>thresholds are reached
>
>Based on code by Siddartha Mohanadoss and Stephen Boyd.
>
>Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
>Cc: Stephen Boyd <sboyd@codeaurora.org>
>Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
>---
> drivers/thermal/Kconfig         |  14 +
> drivers/thermal/Makefile        |   1 +
> drivers/thermal/msm8960_tsens.c | 841 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 856 insertions(+)
> create mode 100644 drivers/thermal/msm8960_tsens.c
>
>diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>index 20915ca..a4cb2c0 100644
>--- a/drivers/thermal/Kconfig
>+++ b/drivers/thermal/Kconfig
>@@ -261,6 +261,20 @@ config INTEL_SOC_DTS_THERMAL
> 	  notification methods.The other trip is a critical trip point, which
> 	  was set by the driver based on the TJ MAX temperature.
>
>+config THERMAL_TSENS8960
>+	tristate "Qualcomm 8960 Tsens Temperature driver"
>+	depends on THERMAL
>+	depends on ARCH_QCOM
>+	help
>+	  QCOM tsens thermal driver provides support for Temperature sensor
>+	  (TSENS) found on QCOM SoCs. It supports four configurable trip points
>+	  and controls multiple sensors on the SOC. The four trip points are
>+	  common across all sensors present in the SoC. The number of sensors
>+	  present vary from chip to chip and are set through device tree entry.
>+	  The driver presents as a standard thermal zone device with configurable
>+	  trip points and cooling device mapping through standard thermal zone
>+	  device tree
>+
> menu "ACPI INT340X thermal drivers"
> source drivers/thermal/int340x_thermal/Kconfig
> endmenu
>diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>index fa0dc48..23c7a34 100644
>--- a/drivers/thermal/Makefile
>+++ b/drivers/thermal/Makefile
>@@ -39,3 +39,4 @@ obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-thermal/
> obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
> obj-$(CONFIG_ST_THERMAL)	+= st/
> obj-$(CONFIG_TEGRA_SOCTHERM)	+= tegra_soctherm.o
>+obj-$(CONFIG_THERMAL_TSENS8960)	+= msm8960_tsens.o
>diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c
>new file mode 100644
>index 0000000..307bdc8
>--- /dev/null
>+++ b/drivers/thermal/msm8960_tsens.c
>@@ -0,0 +1,841 @@
>+/* 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/kernel.h>
>+#include <linux/module.h>
>+#include <linux/platform_device.h>
>+#include <linux/thermal.h>
>+#include <linux/interrupt.h>
>+#include <linux/delay.h>
>+#include <linux/slab.h>
>+#include <linux/err.h>
>+#include <linux/pm.h>
>+#include <linux/bitops.h>
>+#include <linux/regmap.h>
>+#include <linux/of.h>
>+#include <linux/of_address.h>
>+#include <linux/of_platform.h>
>+#include <linux/io.h>
>+#include <linux/mfd/syscon.h>
>+
>+/* Trips: from very hot to very cold */
>+enum tsens_trip_type {
>+	TSENS_TRIP_STAGE3,
>+	TSENS_TRIP_STAGE2,
>+	TSENS_TRIP_STAGE1,
>+	TSENS_TRIP_STAGE0,
>+	TSENS_TRIP_NUM,
>+};

Care to explain why is this different from the trip_types defined in
linux/thermal.h ?

>+
>+#define TSENS_CAL_MDEGC				30000
>+
>+#define TSENS_MAX_SENSORS			11
>+
>+#define TSENS_8960_CONFIG_ADDR			0x3640
>+#define TSENS_8960_CONFIG			0x9b
>+#define TSENS_8960_CONFIG_MASK			0xf
>+
>+#define TSENS_CNTL_ADDR				0x3620
>+#define TSENS_CNTL_RESUME_MASK			0xfffffff9
>+#define TSENS_EN				BIT(0)
>+#define TSENS_SW_RST				BIT(1)
>+#define SENSOR0_EN				BIT(3)
>+#define TSENS_MIN_STATUS_MASK			BIT(0)
>+#define TSENS_LOWER_STATUS_CLR			BIT(1)
>+#define TSENS_UPPER_STATUS_CLR			BIT(2)
>+#define TSENS_MAX_STATUS_MASK			BIT(3)
>+#define TSENS_MEASURE_PERIOD			1
>+#define TSENS_8960_SLP_CLK_ENA			BIT(26)
>+#define TSENS_8660_SLP_CLK_ENA			BIT(24)
>+#define TSENS_8064_STATUS_CNTL			0x3660
>+
>+#define TSENS_THRESHOLD_ADDR			0x3624
>+#define TSENS_THRESHOLD_MAX_CODE		0xff
>+#define TSENS_THRESHOLD_MIN_CODE		0
>+#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT		24
>+#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT		16
>+#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT	8
>+#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT	0
>+
>+/* 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_S0_STATUS_ADDR			0x3628
>+
>+#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_SENSOR_SHIFT			16
>+#define TSENS_REDUND_SHIFT			24
>+#define TSENS_SENSOR0_SHIFT			3
>+
>+#define TSENS_8660_QFPROM_ADDR			0x00bc
>+#define TSENS_8660_CONFIG			1
>+#define TSENS_8660_CONFIG_SHIFT			28
>+#define TSENS_8660_CONFIG_MASK			(3 << TSENS_8660_CONFIG_SHIFT)

Could you align these?
>+
>+struct tsens_device;
>+
>+struct tsens_sensor {
>+	struct thermal_zone_device	*tz_dev;
>+	enum thermal_device_mode	mode;
>+	unsigned int			sensor_num;
>+	int				offset;
>+	u32				slope;
>+	struct tsens_device		*tmdev;
>+	u32                             status;
>+};
>+
>+struct tsens_device {
>+	bool			prev_reading_avail;
>+	unsigned int		num_sensors;
>+	int			pm_tsens_thr_data;
>+	int			pm_tsens_cntl;
>+	unsigned int            calib_offset;
>+	unsigned int            backup_calib_offset;
>+	struct work_struct	tsens_work;
>+	struct regmap		*map;
>+	struct regmap_field	*status_field;
>+	struct tsens_sensor	sensor[0];
>+};
>+
>+static struct device *tsens_dev;
>+
>+/* Temperature on y axis and ADC-code on x-axis */
>+static int
>+tsens_tz_code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
>+{
>+	return adc_code * s->slope + s->offset;
>+}
>+
>+static int tsens_tz_get_temp(void *_sensor,
>+			     long *temp)
>+{
>+	const struct tsens_sensor *tm_sensor = _sensor;
>+	struct tsens_device *tmdev = tm_sensor->tmdev;
>+	u32 code, trdy;
>+
>+	if (tm_sensor->mode != THERMAL_DEVICE_ENABLED)
>+		return -EINVAL;
>+
>+	if (!tmdev->prev_reading_avail) {
>+		while (!regmap_read(tmdev->map, TSENS_INT_STATUS_ADDR, &trdy) &&
>+		       !(trdy & TSENS_TRDY_MASK))
>+			usleep_range(1000, 1100);
>+		tmdev->prev_reading_avail = true;
>+	}
>+
>+	regmap_read(tmdev->map, tm_sensor->status, &code);
>+	*temp = tsens_tz_code_to_mdegC(code, tm_sensor);
>+
>+	dev_dbg(tsens_dev, "Temperature for sensor %d is: %ld",
>+		tm_sensor->sensor_num, *temp);
>+
>+	return 0;
>+}
>+
>+/*
>+ * If the main sensor is disabled all the sensors are disable and

/s/disable/disabled

>+ * the clock is disabled.
>+ * If the main sensor is disabled and a sub-sensor is enabled
>+ * return with an error.
>+ */
>+static int tsens_tz_set_mode(struct tsens_sensor *tm_sensor,
>+			      enum thermal_device_mode mode)
>+{
>+	struct tsens_device *tmdev = tm_sensor->tmdev;
>+	unsigned int i, n = tmdev->num_sensors;
>+	u32 reg, mask;
>+
>+	if (mode == tm_sensor->mode)
>+		return 0;
>+
>+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg);
>+
>+	mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
>+	if (mode == THERMAL_DEVICE_ENABLED) {
>+		if (!(reg & SENSOR0_EN) && mask != SENSOR0_EN) {
>+			pr_err("Main sensor not enabled\n");
>+			return -EINVAL;
>+		}
>+
>+		regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg | TSENS_SW_RST);
>+		if (tmdev->num_sensors > 1)
>+			reg |= mask | TSENS_8960_SLP_CLK_ENA | TSENS_EN;
>+		else
>+			reg |= mask | TSENS_8660_SLP_CLK_ENA | TSENS_EN;

Instead of doing this check everytime to decide between 8660 and 8960,
at init you could set the right mask value in the tsens_device.

>+		tmdev->prev_reading_avail = false;
>+	} else {
>+		reg &= ~mask;
>+		if (!(reg & SENSOR0_EN)) {
>+			dev_warn(tsens_dev, "Main sensor not enabled. Disabling subsensors\n");
>+
>+			reg &= ~GENMASK(n + TSENS_SENSOR0_SHIFT - 1,
>+					TSENS_SENSOR0_SHIFT);
>+			reg &= ~TSENS_EN;
>+
>+			if (tmdev->num_sensors > 1)
>+				reg &= ~TSENS_8960_SLP_CLK_ENA;
>+			else
>+				reg &= ~TSENS_8660_SLP_CLK_ENA;
>+
>+			/* Disable all sub-sensors */
>+			for (i = 1; i < n; i++)
>+				tmdev->sensor[i].mode = mode;
>+		}
>+	}
>+
>+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg);
>+	tm_sensor->mode = mode;
>+
>+	return 0;
>+}
>+
>+#ifdef THERMAL_TSENS8960_HWTRIPS
>+static int tsens_get_trip_temp(struct tsens_sensor *tm_sensor,
>+				   int trip, int *temp)
>+{
>+	struct tsens_device *tmdev = tm_sensor->tmdev;
>+	u32 reg;
>+
>+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &reg);
>+	switch (trip) {
>+	case TSENS_TRIP_STAGE3:
>+		reg >>= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE2:
>+		reg >>= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE1:
>+		reg >>= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE0:
>+		reg >>= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+	reg &= TSENS_THRESHOLD_MAX_CODE;
>+
>+	temp = tsens_tz_code_to_mdegC(reg, tm_sensor);
>+
>+	dev_dbg(tsens_dev, "Sensor %d, trip %d temp is :%d",
>+		tm_sensor->sensor_num, trip, *temp);
>+
>+	return 0;
>+}
>+
>+static void tsens_print_trip_temp(struct tsens_sensor *tm_sensor)
>+{
>+	int temp;
>+
>+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE0, &temp);
>+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, &temp);
>+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, &temp);
>+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE3, &temp);
>+}
>+
>+static int tsens_tz_mdegC_to_code(int mdegC, const struct tsens_sensor *s)
>+{
>+	int code;
>+
>+	code = DIV_ROUND_CLOSEST(mdegC - s->offset, s->slope);
>+	return clamp(code, TSENS_THRESHOLD_MIN_CODE, TSENS_THRESHOLD_MAX_CODE);
>+}
>+
>+static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh)
>+{
>+	u32 hi_code;
>+
>+	switch (trip) {
>+	case TSENS_TRIP_STAGE0:
>+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
>+			hi_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE1:
>+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
>+			hi_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE2:
>+		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
>+			hi_code = thresh >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE3:
>+	default:
>+		hi_code = TSENS_THRESHOLD_MAX_CODE;
>+		break;
>+	}
>+
>+	return hi_code & TSENS_THRESHOLD_MAX_CODE;
>+}
>+
>+static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh)
>+{
>+	u32 lo_code;
>+
>+	switch (trip) {
>+	case TSENS_TRIP_STAGE3:
>+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
>+			lo_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE2:
>+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
>+			lo_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE1:
>+		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
>+			lo_code = thresh >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
>+			break;
>+		}
>+		/* else fall through */
>+	case TSENS_TRIP_STAGE0:
>+	default:
>+		lo_code = TSENS_THRESHOLD_MIN_CODE;
>+		break;
>+	}
>+
>+	return lo_code & TSENS_THRESHOLD_MAX_CODE;
>+}
>+
>+static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
>+	int trip, unsigned long temp)
>+{
>+	struct tsens_device *tmdev = tm_sensor->tmdev;
>+	struct regmap_field *status = tmdev->status_field;
>+	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
>+	u32 code, hi_code, lo_code, code_err_chk;
>+
>+	code_err_chk = code = tsens_tz_mdegC_to_code(temp, tm_sensor);
>+
>+	regmap_field_read(status, &reg_cntl);
>+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
>+
>+	switch (trip) {
>+	case TSENS_TRIP_STAGE3:
>+		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
>+		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE2:
>+		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE1:
>+		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+		break;
>+	case TSENS_TRIP_STAGE0:
>+		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
>+		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+
>+	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
>+	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
>+
>+	if (code_err_chk < lo_code || code_err_chk > hi_code)
>+		return -EINVAL;
>+
>+	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR, mask, code);
>+
>+	return 0;
>+}
>+
>+static int tsens_set_trips(void *_sensor, long low, long high)
>+{
>+	struct tsens_sensor *tm_sensor = _sensor;
>+
>+	tsens_print_trip_temp(tm_sensor);
>+
>+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
>+		return -EINVAL;
>+
>+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
>+		return -EINVAL;
>+
>+	return 0;
>+}
>+#endif
>+
>+static void tsens_scheduler_fn(struct work_struct *work)
>+{
>+	struct tsens_device *tmdev;
>+	struct regmap_field *status;
>+	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
>+	unsigned long sensor;
>+	bool upper_th_x, lower_th_x;
>+
>+	tmdev = container_of(work, struct tsens_device, tsens_work);
>+	status = tmdev->status_field;
>+
>+	regmap_field_update_bits(status,
>+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR,
>+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR);
>+
>+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
>+	threshold_low = threshold >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
>+	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
>+	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
>+	threshold &= TSENS_THRESHOLD_MAX_CODE;
>+
>+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
>+	sensor = bits;
>+	sensor >>= TSENS_SENSOR0_SHIFT;
>+	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
>+		regmap_read(tmdev->map, tmdev->sensor[i].status, &code);
>+		upper_th_x = code >= threshold;
>+		lower_th_x = code <= threshold_low;
>+
>+		if (upper_th_x)
>+			mask |= TSENS_UPPER_STATUS_CLR;
>+
>+		if (lower_th_x)
>+			mask |= TSENS_LOWER_STATUS_CLR;
>+
>+#ifdef THERMAL_TSENS8960_HWTRIPS
>+		if (upper_th_x || lower_th_x) {
>+			dev_info(tsens_dev,
>+				"Threshold reached for sensor(%d)\n", i);
>+			thermal_zone_device_update(tmdev->sensor[i].tz_dev);
>+		}
>+#endif
>+	}
>+
>+	regmap_field_update_bits(status,
>+			TSENS_UPPER_STATUS_CLR | TSENS_LOWER_STATUS_CLR, mask);
>+}
>+
>+static irqreturn_t tsens_isr(int irq, void *data)
>+{
>+	schedule_work(data);
>+	return IRQ_HANDLED;
>+}
>+
>+#ifdef CONFIG_PM
>+static int tsens_suspend(struct device *dev)
>+{
>+	int i;
>+	struct tsens_device *tmdev = dev_get_drvdata(dev);
>+	struct regmap *map = tmdev->map;
>+
>+	regmap_read(map, TSENS_THRESHOLD_ADDR, &tmdev->pm_tsens_thr_data);
>+	regmap_read(map, TSENS_CNTL_ADDR, &tmdev->pm_tsens_cntl);
>+	regmap_update_bits(map, TSENS_CNTL_ADDR,
>+			   TSENS_8960_SLP_CLK_ENA | TSENS_EN, 0);

No check for num_sensors here?
>+
>+	tmdev->prev_reading_avail = 0;
>+	for (i = 0; i < tmdev->num_sensors; i++)
>+		tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED;
>+
>+	return 0;
>+}
>+
>+static int tsens_resume(struct device *dev)
>+{
>+	int i;
>+	unsigned long reg_cntl;
>+	struct tsens_device *tmdev = dev_get_drvdata(dev);
>+	struct regmap *map = tmdev->map;
>+
>+	regmap_update_bits(map, TSENS_CNTL_ADDR, TSENS_SW_RST, TSENS_SW_RST);
>+	regmap_field_update_bits(tmdev->status_field,
>+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
>+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
>+
>+	if (tmdev->num_sensors > 1)
>+		regmap_update_bits(map, TSENS_8960_CONFIG_ADDR,
>+				TSENS_8960_CONFIG_MASK,
>+				TSENS_8960_CONFIG);

What about 8660?

>+
>+	regmap_write(map, TSENS_THRESHOLD_ADDR, tmdev->pm_tsens_thr_data);
>+	regmap_write(map, TSENS_CNTL_ADDR, tmdev->pm_tsens_cntl);
>+
>+	reg_cntl = tmdev->pm_tsens_cntl;
>+	reg_cntl >>= TSENS_SENSOR0_SHIFT;
>+	for_each_set_bit(i, &reg_cntl, tmdev->num_sensors)
>+		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
>+
>+	return 0;
>+}
>+
>+static const struct dev_pm_ops tsens_pm_ops = {
>+	.suspend	= tsens_suspend,
>+	.resume		= tsens_resume,
>+};
>+#endif
>+
>+static void tsens_disable_mode(const struct tsens_device *tmdev)
>+{
>+	u32 reg_cntl;
>+	u32 mask;
>+
>+	mask = GENMASK(tmdev->num_sensors - 1, 0);
>+	mask <<= TSENS_SENSOR0_SHIFT;
>+	mask |= TSENS_EN;
>+
>+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
>+	reg_cntl &= ~mask;
>+	if (tmdev->num_sensors > 1)
>+		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
>+	else
>+		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
>+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
>+}
>+
>+static void tsens_hw_init(struct tsens_device *tmdev)
>+{
>+	u32 reg_cntl, reg_thr;
>+
>+	reg_cntl = TSENS_SW_RST;
>+	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR, TSENS_SW_RST, reg_cntl);
>+
>+	if (tmdev->num_sensors > 1) {
>+		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
>+			(TSENS_MEASURE_PERIOD << 18);
>+		reg_cntl &= ~TSENS_SW_RST;
>+		regmap_update_bits(tmdev->map, TSENS_8960_CONFIG_ADDR,
>+				   TSENS_8960_CONFIG_MASK, TSENS_8960_CONFIG);
>+	} else {
>+		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
>+			(TSENS_MEASURE_PERIOD << 16);
>+		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
>+		reg_cntl |= TSENS_8660_CONFIG << TSENS_8660_CONFIG_SHIFT;
>+	}
>+
>+	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << TSENS_SENSOR0_SHIFT;
>+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
>+
>+	regmap_field_update_bits(tmdev->status_field,
>+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
>+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
>+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
>+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
>+
>+	reg_cntl |= TSENS_EN;
>+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
>+
>+	reg_thr = (TSENS_LOWER_LIMIT_TH << TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
>+		(TSENS_UPPER_LIMIT_TH << TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
>+		(TSENS_MIN_LIMIT_TH << TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
>+		(TSENS_MAX_LIMIT_TH << TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
>+	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr);
>+}
>+
>+static int
>+tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap *map)
>+{
>+	u32 temp_data, data;
>+	struct tsens_sensor *s = &tmdev->sensor[0];
>+
>+	if (regmap_read(map, tmdev->calib_offset, &temp_data))
>+		return -EINVAL;
>+
>+	data = (temp_data >> 24) & 0xff;
>+
>+	if (!data) {
>+		dev_err(tsens_dev, "QFPROM TSENS calibration data not present\n");
>+		return -EINVAL;
>+	}
>+
>+	s->offset = TSENS_CAL_MDEGC - s->slope * data;
>+
>+	return 0;
>+}
>+
>+static int
>+tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap *map)
>+{
>+	int i;
>+	u32 temp_data[TSENS_MAX_SENSORS];
>+	u8 *byte_data;
>+	u32 fuse, redun, num_read;
>+	struct tsens_sensor *s = tmdev->sensor;
>+
>+	fuse = tmdev->calib_offset;
>+	redun = tmdev->backup_calib_offset;
>+
>+	/**
>+	* syscon regmap is 32-bit data, but calibration data is 8-bit.
>+	* read 4 bytes from regmap in a loop and then extract bytes seprately
>+	*/
>+
>+	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
>+
>+	for (i = 0; i < num_read; i++) {
>+		if (regmap_read(map, (redun + i*4), &temp_data[i]))
>+			return -EINVAL;
>+
>+		if (!temp_data[i]) {
>+			dev_dbg(tsens_dev, "Main calib data not valid\n");
>+			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
>+				return -EINVAL;
>+		}
>+	}
>+
>+	byte_data = (u8 *)temp_data;
>+
>+	for (i = 0; i < tmdev->num_sensors; i++, s++) {
>+		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
>+		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
>+	}
>+
>+	return 0;
>+}
>+
>+static const struct thermal_zone_of_device_ops tsens_thermal_of_ops = {
>+	.get_temp = tsens_tz_get_temp,
>+};
>+
>+
>+static int tsens_register(struct tsens_device *tmdev, int i)
>+{
>+	char name[18];
>+	u32 addr = TSENS_S0_STATUS_ADDR;
>+	struct tsens_sensor *s = &tmdev->sensor[i];
>+
>+	/*
>+	* 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.
>+	*/
>+	if (i >= 5)
>+		addr += 40;
>+
>+	addr += i * 4;
>+
>+	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
>+	s->mode = THERMAL_DEVICE_ENABLED;
>+	s->sensor_num = i;
>+	s->status = addr;
>+	s->tmdev = tmdev;
>+	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
>+						&tsens_thermal_of_ops);
>+
>+	if (IS_ERR(s->tz_dev))
>+		return -ENODEV;
>+
>+	return 0;
>+}
>+
>+static int tsens_probe(struct platform_device *pdev)
>+{
>+	struct device_node *np = pdev->dev.of_node;
>+	struct device_node *base_node;
>+	struct platform_device *base_pdev;
>+	int ret, i, irq, num;
>+	struct tsens_sensor *s;
>+	struct tsens_device *tmdev;
>+	struct regmap *map, *imem_regmap;
>+	struct reg_field *field;
>+	static struct reg_field status_0 = {
>+		.reg = TSENS_8064_STATUS_CNTL,
>+		.lsb = 0,
>+		.msb = 3,
>+	};
>+	static struct reg_field status_8 = {
>+		.reg = TSENS_CNTL_ADDR,
>+		.lsb = 8,
>+		.msb = 11,
>+	};
>+
>+	tsens_dev = &pdev->dev;
>+
>+	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
>+	if (num <= 0) {
>+		dev_err(tsens_dev, "invalid tsens slopes\n");
>+		return -EINVAL;
>+	}
>+
>+	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
>+			     num * sizeof(struct tsens_sensor), GFP_KERNEL);

Any particular reason, the sensors are appended to the tmdev, instead of
allocating separately?

>+	if (tmdev == NULL)
>+		return -ENOMEM;
>+
>+	tmdev->num_sensors = num;
>+	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
>+		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
>+					   &s->slope);
>+
>+	irq = platform_get_irq(pdev, 0);
>+	if (irq < 0) {
>+		dev_err(tsens_dev,  "no irq resource?\n");
>+		return  -EINVAL;
>+	}
>+
>+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
>+					   &tmdev->calib_offset);
>+	if (ret != 0) {
>+		dev_err(tsens_dev,  "No calibration offset set\n");
>+		return  -EINVAL;
>+	}
>+
>+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
>+					   &tmdev->backup_calib_offset);
>+	if (ret) {
>+		dev_info(tsens_dev, "Missing backup calibration offset\n");
>+		tmdev->backup_calib_offset = tmdev->calib_offset;
>+	}
>+
>+	imem_regmap = syscon_regmap_lookup_by_phandle(np, "qcom,imem");
>+	if (IS_ERR(imem_regmap)) {
>+		dev_err(tsens_dev, "syscon regmap look up error\n");
>+		return PTR_ERR(imem_regmap);
>+	}
>+
>+	if (num == 1)
>+		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
>+	else
>+		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
>+
>+	if (ret < 0) {
>+		dev_err(tsens_dev, "tsense calibration failed\n");
>+		return ret;
>+	}
>+
>+	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
>+	if (base_node == NULL) {
>+		dev_err(tsens_dev, "no base node present\n");
>+		return  -EINVAL;
>+	}
>+
>+	base_pdev = of_find_device_by_node(base_node);
>+	if (base_pdev == NULL) {
>+		dev_err(tsens_dev, "no base pdev node\n");
>+		return  -ENODEV;
>+	}
>+
>+	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
>+	if (map == NULL) {
>+		dev_err(tsens_dev, "base regmap get failed\n");
>+		return  -ENODEV;
>+	}
>+
>+	/* Status bits move when the sensor bits next to them overlap */
>+	if (num > 5)
>+		field = &status_0;
>+	else
>+		field = &status_8;
>+
>+	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map, *field);
>+	if (IS_ERR(tmdev->status_field)) {
>+		dev_err(tsens_dev, "regmap alloc failed\n");
>+		return PTR_ERR(tmdev->status_field);
>+	}
>+
>+	tsens_hw_init(tmdev);
>+
>+	/*
>+	 * Register sensor 0 separately. This sensor is always
>+	 * expected to be present and if this fails, thermal
>+	 * sensor probe would fail
>+	 * Other sensors are optional and if registration fails
>+	 * disable the sensor and continue
>+	*/
>+	ret = tsens_register(tmdev, 0);
>+	if (ret < 0) {
>+		dev_err(tsens_dev, "Registering failed for primary sensor");
>+		ret = -ENODEV;
>+		goto fail;
>+	} else {
>+		tsens_tz_set_mode(&tmdev->sensor[0], THERMAL_DEVICE_ENABLED);
>+	}
>+
>+	for (i = 1;  i < tmdev->num_sensors; i++) {
>+		ret = tsens_register(tmdev, i);
>+
>+		if (ret < 0) {
>+			dev_err(tsens_dev,
>+				"Registering failed. Sensor(%i), disabled", i);
>+			tsens_tz_set_mode(&tmdev->sensor[i],
>+				THERMAL_DEVICE_DISABLED);
>+		} else {
>+			tsens_tz_set_mode(&tmdev->sensor[i],
>+				THERMAL_DEVICE_ENABLED);
>+		}
>+	}
>+
>+	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
>+
>+	ret = devm_request_irq(tsens_dev, irq, tsens_isr, IRQF_TRIGGER_RISING,
>+			       "tsens", &tmdev->tsens_work);
>+	if (ret < 0)
>+		goto err_irq;
>+
>+	platform_set_drvdata(pdev, tmdev);
>+
>+	dev_info(tsens_dev, "Tsens driver initialized\n");
>+
>+	return 0;
>+err_irq:
>+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
>+		thermal_zone_of_sensor_unregister(&pdev->dev, s->tz_dev);
>+fail:
>+	tsens_disable_mode(tmdev);
>+	return ret;
>+}
>+
>+static int tsens_remove(struct platform_device *pdev)
>+{
>+	int i;
>+	struct tsens_sensor *s;
>+	struct tsens_device *tmdev = platform_get_drvdata(pdev);
>+
>+	tsens_disable_mode(tmdev);
>+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
>+		thermal_zone_device_unregister(s->tz_dev);
>+
>+	return 0;
>+}
>+
>+static struct of_device_id tsens_match_table[] = {
>+	{.compatible = "qcom,ipq806x-tsens"},
>+	{},
>+};
>+
>+MODULE_DEVICE_TABLE(of, tsens_match_table);
>+
>+static struct platform_driver tsens_driver = {
>+	.probe = tsens_probe,
>+	.remove = tsens_remove,
>+	.driver = {
>+		.of_match_table = tsens_match_table,
>+		.name = "tsens8960-thermal",
>+		.owner = THIS_MODULE,
>+#ifdef CONFIG_PM
>+		.pm	= &tsens_pm_ops,
>+#endif
>+	},
>+};
>+module_platform_driver(tsens_driver);
>+
>+MODULE_LICENSE("GPL v2");
>+MODULE_DESCRIPTION("Temperature Sensor driver");
>+MODULE_ALIAS("platform:tsens8960-tm");
>-- 
>Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project
>
>--
>To unsubscribe from this list: send the line "unsubscribe linux-pm" in
>the body of a message to majordomo@vger.kernel.org
>More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Boyd Jan. 29, 2015, 1:46 a.m. UTC | #5
On 01/26, Narendran Rajan wrote:
> TSENS supports reading temperature from multiple thermal
> sensors present in QCOM SOCs.
> TSENS HW is enabled only when the main sensor is requested.
> The TSENS block is disabled if the main senors is disabled
> irrespective of any other sensors that are being enabled.
> TSENS driver supports configurable threshold for temperature
> monitoring in which case it can generate an interrupt when specific
> thresholds are reached
> 
> Based on code by Siddartha Mohanadoss and Stephen Boyd.

So as far as I can tell you removed my loop TODOs, added debug
prints and the device singleton, put the calibration data offset
into DT (another TODO), used regmap on the qfprom, made this into
a standalone device instead of a child of GCC, and moved to OF
thermal zones (forcing that THERMAL_TSENS8960_HWTRIPS ifdef I
suppose?) Anything else? 

> 
> Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> Cc: Stephen Boyd <sboyd@codeaurora.org>
> Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>

Something is wrong with your git. There should only be one space
between your name and the email id.

> ---
> diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c
> new file mode 100644
> index 0000000..307bdc8
> --- /dev/null
> +++ b/drivers/thermal/msm8960_tsens.c
> +
> +static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh)
> +{
> +	u32 hi_code;
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE0:
> +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE1:
> +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE2:
> +		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE3:
> +	default:
> +		hi_code = TSENS_THRESHOLD_MAX_CODE;
> +		break;
> +	}
> +
> +	return hi_code & TSENS_THRESHOLD_MAX_CODE;
> +}
> +
> +static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh)
> +{
> +	u32 lo_code;
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE3:
> +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE2:
> +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE1:
> +		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE0:
> +	default:
> +		lo_code = TSENS_THRESHOLD_MIN_CODE;
> +		break;
> +	}
> +
> +	return lo_code & TSENS_THRESHOLD_MAX_CODE;
> +}

I wonder if these can be loops, or something on top of
find_next_bit()? That's what my TODO was about, hoping to reduce
these functions even further.

> +
> +static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
> +	int trip, unsigned long temp)
> +{
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	struct regmap_field *status = tmdev->status_field;
> +	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
> +	u32 code, hi_code, lo_code, code_err_chk;
> +
> +	code_err_chk = code = tsens_tz_mdegC_to_code(temp, tm_sensor);
> +
> +	regmap_field_read(status, &reg_cntl);
> +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE3:
> +		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE2:
> +		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE1:
> +		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE0:
> +		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
> +	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
> +
> +	if (code_err_chk < lo_code || code_err_chk > hi_code)
> +		return -EINVAL;
> +
> +	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR, mask, code);
> +
> +	return 0;
> +}
> +
> +static int tsens_set_trips(void *_sensor, long low, long high)

Is this function even used?

> +{
> +	struct tsens_sensor *tm_sensor = _sensor;
> +
> +	tsens_print_trip_temp(tm_sensor);
> +
> +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
> +		return -EINVAL;
> +
> +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +#endif
> +
> +static void tsens_scheduler_fn(struct work_struct *work)
> +{
> +	struct tsens_device *tmdev;
> +	struct regmap_field *status;
> +	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
> +	unsigned long sensor;
> +	bool upper_th_x, lower_th_x;
> +
> +	tmdev = container_of(work, struct tsens_device, tsens_work);
> +	status = tmdev->status_field;
> +
> +	regmap_field_update_bits(status,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR);
> +
> +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
> +	threshold_low = threshold >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
> +	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +	threshold &= TSENS_THRESHOLD_MAX_CODE;
> +
> +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
> +	sensor = bits;
> +	sensor >>= TSENS_SENSOR0_SHIFT;
> +	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
> +		regmap_read(tmdev->map, tmdev->sensor[i].status, &code);
> +		upper_th_x = code >= threshold;
> +		lower_th_x = code <= threshold_low;
> +
> +		if (upper_th_x)
> +			mask |= TSENS_UPPER_STATUS_CLR;
> +
> +		if (lower_th_x)
> +			mask |= TSENS_LOWER_STATUS_CLR;
> +
> +#ifdef THERMAL_TSENS8960_HWTRIPS
> +		if (upper_th_x || lower_th_x) {
> +			dev_info(tsens_dev,
> +				"Threshold reached for sensor(%d)\n", i);

This looks like debug noise. Please remove.

> +			thermal_zone_device_update(tmdev->sensor[i].tz_dev);
> +		}
> +#endif
> +	}
> +
> +	regmap_field_update_bits(status,
> +			TSENS_UPPER_STATUS_CLR | TSENS_LOWER_STATUS_CLR, mask);
> +}
> +
> +static irqreturn_t tsens_isr(int irq, void *data)
> +{
> +	schedule_work(data);

I was going to move this to threaded irqs, please do that.

> +	return IRQ_HANDLED;
> +}
> +
> +static void tsens_disable_mode(const struct tsens_device *tmdev)
> +{
> +	u32 reg_cntl;
> +	u32 mask;
> +
> +	mask = GENMASK(tmdev->num_sensors - 1, 0);
> +	mask <<= TSENS_SENSOR0_SHIFT;
> +	mask |= TSENS_EN;
> +
> +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
> +	reg_cntl &= ~mask;
> +	if (tmdev->num_sensors > 1)
> +		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
> +	else
> +		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +}
> +
> +static void tsens_hw_init(struct tsens_device *tmdev)
> +{
> +	u32 reg_cntl, reg_thr;
> +
> +	reg_cntl = TSENS_SW_RST;
> +	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR, TSENS_SW_RST, reg_cntl);
> +
> +	if (tmdev->num_sensors > 1) {
> +		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
> +			(TSENS_MEASURE_PERIOD << 18);

Unnecessary ()?

> +		reg_cntl &= ~TSENS_SW_RST;
> +		regmap_update_bits(tmdev->map, TSENS_8960_CONFIG_ADDR,
> +				   TSENS_8960_CONFIG_MASK, TSENS_8960_CONFIG);
> +	} else {
> +		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
> +			(TSENS_MEASURE_PERIOD << 16);

Unnecessary ()?

> +		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
> +		reg_cntl |= TSENS_8660_CONFIG << TSENS_8660_CONFIG_SHIFT;
> +	}
> +
> +	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << TSENS_SENSOR0_SHIFT;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +
> +	regmap_field_update_bits(tmdev->status_field,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
> +
> +	reg_cntl |= TSENS_EN;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +
> +	reg_thr = (TSENS_LOWER_LIMIT_TH << TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
> +		(TSENS_UPPER_LIMIT_TH << TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
> +		(TSENS_MIN_LIMIT_TH << TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
> +		(TSENS_MAX_LIMIT_TH << TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
> +	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr);
> +}
> +
> +static int
> +tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap *map)
> +{
> +	u32 temp_data, data;
> +	struct tsens_sensor *s = &tmdev->sensor[0];
> +
> +	if (regmap_read(map, tmdev->calib_offset, &temp_data))
> +		return -EINVAL;
> +
> +	data = (temp_data >> 24) & 0xff;
> +
> +	if (!data) {
> +		dev_err(tsens_dev, "QFPROM TSENS calibration data not present\n");

Ah we do use it for an error here. It isn't hard to put a struct
device inside tmdev...

> +		return -EINVAL;
> +	}
> +
> +	s->offset = TSENS_CAL_MDEGC - s->slope * data;
> +
> +	return 0;
> +}
> +
> +static int
> +tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap *map)
> +{
> +	int i;
> +	u32 temp_data[TSENS_MAX_SENSORS];
> +	u8 *byte_data;
> +	u32 fuse, redun, num_read;
> +	struct tsens_sensor *s = tmdev->sensor;
> +
> +	fuse = tmdev->calib_offset;
> +	redun = tmdev->backup_calib_offset;
> +
> +	/**
> +	* syscon regmap is 32-bit data, but calibration data is 8-bit.
> +	* read 4 bytes from regmap in a loop and then extract bytes seprately
> +	*/

Weird comment style. Please do it like

 /*
  * This because this isn't kernel-doc.
  */

> +
> +	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
> +
> +	for (i = 0; i < num_read; i++) {
> +		if (regmap_read(map, (redun + i*4), &temp_data[i]))
> +			return -EINVAL;
> +
> +		if (!temp_data[i]) {
> +			dev_dbg(tsens_dev, "Main calib data not valid\n");
> +			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
> +				return -EINVAL;
> +		}
> +	}
> +
> +	byte_data = (u8 *)temp_data;
> +
> +	for (i = 0; i < tmdev->num_sensors; i++, s++) {
> +		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
> +		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
> +	}
> +
> +	return 0;
> +}

This is a lot of hoops to jump through just to use a syscon
regmap (does this even work in big-endian mode?). Given your
point about consumers wanting to read with different strides
makes me think that using a regmap is not a good idea. It would
be better if we had an API that could be used to read an
arbitrary number of bytes from an eeprom. 

> +
> +static const struct thermal_zone_of_device_ops tsens_thermal_of_ops = {
> +	.get_temp = tsens_tz_get_temp,
> +};
> +
> +

Nitpick: Two newlines instead of one.

> +static int tsens_register(struct tsens_device *tmdev, int i)
> +{
> +	char name[18];
> +	u32 addr = TSENS_S0_STATUS_ADDR;
> +	struct tsens_sensor *s = &tmdev->sensor[i];
> +
> +	/*
> +	* 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.
> +	*/
> +	if (i >= 5)
> +		addr += 40;
> +
> +	addr += i * 4;
> +
> +	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
> +	s->mode = THERMAL_DEVICE_ENABLED;
> +	s->sensor_num = i;
> +	s->status = addr;
> +	s->tmdev = tmdev;
> +	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
> +						&tsens_thermal_of_ops);
> +
> +	if (IS_ERR(s->tz_dev))
> +		return -ENODEV;

So if it's an error pointer we leave it there? And then if an
interrupt arrives and this sensor has a new temperature to report
we'll update the thermal framework with an invalid pointer? When
I wrote this code I didn't allow probe to continue if any of
these zones failed to register. Why change that because we're
using the sensor API?

> +
> +	return 0;
> +}
> +
> +static int tsens_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct device_node *base_node;
> +	struct platform_device *base_pdev;
> +	int ret, i, irq, num;
> +	struct tsens_sensor *s;
> +	struct tsens_device *tmdev;
> +	struct regmap *map, *imem_regmap;
> +	struct reg_field *field;
> +	static struct reg_field status_0 = {
> +		.reg = TSENS_8064_STATUS_CNTL,
> +		.lsb = 0,
> +		.msb = 3,
> +	};
> +	static struct reg_field status_8 = {
> +		.reg = TSENS_CNTL_ADDR,
> +		.lsb = 8,
> +		.msb = 11,
> +	};
> +
> +	tsens_dev = &pdev->dev;
> +
> +	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
> +	if (num <= 0) {
> +		dev_err(tsens_dev, "invalid tsens slopes\n");
> +		return -EINVAL;
> +	}
> +
> +	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
> +			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
> +	if (tmdev == NULL)
> +		return -ENOMEM;
> +
> +	tmdev->num_sensors = num;
> +	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
> +		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
> +					   &s->slope);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(tsens_dev,  "no irq resource?\n");
> +		return  -EINVAL;
> +	}
> +
> +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
> +					   &tmdev->calib_offset);
> +	if (ret != 0) {
> +		dev_err(tsens_dev,  "No calibration offset set\n");
> +		return  -EINVAL;
> +	}
> +
> +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
> +					   &tmdev->backup_calib_offset);
> +	if (ret) {
> +		dev_info(tsens_dev, "Missing backup calibration offset\n");

Why make any noise at all? A backup is here.

> +		tmdev->backup_calib_offset = tmdev->calib_offset;
> +	}
> +
> +	imem_regmap = syscon_regmap_lookup_by_phandle(np, "qcom,imem");
> +	if (IS_ERR(imem_regmap)) {
> +		dev_err(tsens_dev, "syscon regmap look up error\n");
> +		return PTR_ERR(imem_regmap);
> +	}
> +
> +	if (num == 1)
> +		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
> +	else
> +		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
> +
> +	if (ret < 0) {
> +		dev_err(tsens_dev, "tsense calibration failed\n");
> +		return ret;
> +	}
> +
> +	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
> +	if (base_node == NULL) {

Kernel style is typically

 if (!base_node)

> +		dev_err(tsens_dev, "no base node present\n");
> +		return  -EINVAL;
> +	}
> +
> +	base_pdev = of_find_device_by_node(base_node);
> +	if (base_pdev == NULL) {
> +		dev_err(tsens_dev, "no base pdev node\n");
> +		return  -ENODEV;
> +	}
> +
> +	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
> +	if (map == NULL) {
> +		dev_err(tsens_dev, "base regmap get failed\n");
> +		return  -ENODEV;
> +	}
> +
> +	/* Status bits move when the sensor bits next to them overlap */
> +	if (num > 5)
> +		field = &status_0;
> +	else
> +		field = &status_8;
> +
> +	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map, *field);
> +	if (IS_ERR(tmdev->status_field)) {
> +		dev_err(tsens_dev, "regmap alloc failed\n");
> +		return PTR_ERR(tmdev->status_field);
> +	}
> +
> +	tsens_hw_init(tmdev);
> +
> +	/*
> +	 * Register sensor 0 separately. This sensor is always
> +	 * expected to be present and if this fails, thermal
> +	 * sensor probe would fail
> +	 * Other sensors are optional and if registration fails
> +	 * disable the sensor and continue
> +	*/
> +	ret = tsens_register(tmdev, 0);
> +	if (ret < 0) {
> +		dev_err(tsens_dev, "Registering failed for primary sensor");
> +		ret = -ENODEV;
> +		goto fail;
> +	} else {
> +		tsens_tz_set_mode(&tmdev->sensor[0], THERMAL_DEVICE_ENABLED);
> +	}
> +
> +	for (i = 1;  i < tmdev->num_sensors; i++) {
> +		ret = tsens_register(tmdev, i);
> +
> +		if (ret < 0) {
> +			dev_err(tsens_dev,
> +				"Registering failed. Sensor(%i), disabled", i);

Missing newline...

> +			tsens_tz_set_mode(&tmdev->sensor[i],
> +				THERMAL_DEVICE_DISABLED);

Does this do anything? The mode is already
THERMAL_DEVICE_DISABLED so I imagine it just bails out
immediately?

> +		} else {
> +			tsens_tz_set_mode(&tmdev->sensor[i],
> +				THERMAL_DEVICE_ENABLED);
> +		}
> +	}
> +
> +	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
> +
> +	ret = devm_request_irq(tsens_dev, irq, tsens_isr, IRQF_TRIGGER_RISING,
> +			       "tsens", &tmdev->tsens_work);
> +	if (ret < 0)
> +		goto err_irq;
> +
> +	platform_set_drvdata(pdev, tmdev);
> +
> +	dev_info(tsens_dev, "Tsens driver initialized\n");

Please remove.

> +
> +	return 0;
> +err_irq:
> +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)

I should have made this simpler like:

  for (s = tmdev->sensor; s < tmdev->sensor + tmdev->num_sensors; s++)

> +		thermal_zone_of_sensor_unregister(&pdev->dev, s->tz_dev);
> +fail:
> +	tsens_disable_mode(tmdev);
> +	return ret;
> +}
> +
> +static int tsens_remove(struct platform_device *pdev)
> +{
> +	int i;

Because then we can save a whole variable here.

> +	struct tsens_sensor *s;
> +	struct tsens_device *tmdev = platform_get_drvdata(pdev);
> +
> +	tsens_disable_mode(tmdev);
> +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> +		thermal_zone_device_unregister(s->tz_dev);
> +
> +	return 0;
> +}
> +
> +static struct of_device_id tsens_match_table[] = {
> +	{.compatible = "qcom,ipq806x-tsens"},
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(of, tsens_match_table);

I would prefer we leave it as a child of GCC like I had done in
my original patch. The one ugly part was passing the DT node to
the virtual tsens device so that it could figure out the number
of sensors, etc. We should be able to fix that by creating the
device, assigning the of_node, and then registering the device
with the platform bus.

> +
> +static struct platform_driver tsens_driver = {
> +	.probe = tsens_probe,
> +	.remove = tsens_remove,
> +	.driver = {
> +		.of_match_table = tsens_match_table,
> +		.name = "tsens8960-thermal",

A better name may be tsens-tm or qcom-tsens. The same version of
the hardware exists in so many different SoCs there really isn't
anything SoC specific about the hardware. The SoC difference is
almost entirely in the qfprom layout, except for when the hw
engineers decide to put a different number of sensors on the
chip. The same comment applies to the file name. Probably
qcom-tsens.c or something.

> +		.owner = THIS_MODULE,
> +#ifdef CONFIG_PM
> +		.pm	= &tsens_pm_ops,
> +#endif
> +	},
> +};
> +module_platform_driver(tsens_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Temperature Sensor driver");
> +MODULE_ALIAS("platform:tsens8960-tm");
Eduardo Valentin Jan. 29, 2015, 10:39 p.m. UTC | #6
On Mon, Jan 26, 2015 at 08:09:46PM -0800, Narendran Rajan wrote:
> TSENS supports reading temperature from multiple thermal
> sensors present in QCOM SOCs.
> TSENS HW is enabled only when the main sensor is requested.
> The TSENS block is disabled if the main senors is disabled
> irrespective of any other sensors that are being enabled.
> TSENS driver supports configurable threshold for temperature
> monitoring in which case it can generate an interrupt when specific
> thresholds are reached
> 
> Based on code by Siddartha Mohanadoss and Stephen Boyd.
> 
> Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> Cc: Stephen Boyd <sboyd@codeaurora.org>
> Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>

I know this might not be completely related, but since you are sending
QCOM code, can you please check my comments on your PMIC thermal code?
https://patchwork.kernel.org/patch/5013021/

Ivan seams to have disappeared.

> ---
>  drivers/thermal/Kconfig         |  14 +
>  drivers/thermal/Makefile        |   1 +
>  drivers/thermal/msm8960_tsens.c | 841 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 856 insertions(+)
>  create mode 100644 drivers/thermal/msm8960_tsens.c
> 
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 20915ca..a4cb2c0 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -261,6 +261,20 @@ config INTEL_SOC_DTS_THERMAL
>  	  notification methods.The other trip is a critical trip point, which
>  	  was set by the driver based on the TJ MAX temperature.
>  
> +config THERMAL_TSENS8960
> +	tristate "Qualcomm 8960 Tsens Temperature driver"
> +	depends on THERMAL
> +	depends on ARCH_QCOM
> +	help
> +	  QCOM tsens thermal driver provides support for Temperature sensor
> +	  (TSENS) found on QCOM SoCs. It supports four configurable trip points
> +	  and controls multiple sensors on the SOC. The four trip points are
> +	  common across all sensors present in the SoC. The number of sensors
> +	  present vary from chip to chip and are set through device tree entry.
> +	  The driver presents as a standard thermal zone device with configurable
> +	  trip points and cooling device mapping through standard thermal zone
> +	  device tree
> +
>  menu "ACPI INT340X thermal drivers"
>  source drivers/thermal/int340x_thermal/Kconfig
>  endmenu
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fa0dc48..23c7a34 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -39,3 +39,4 @@ obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-thermal/
>  obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
>  obj-$(CONFIG_ST_THERMAL)	+= st/
>  obj-$(CONFIG_TEGRA_SOCTHERM)	+= tegra_soctherm.o
> +obj-$(CONFIG_THERMAL_TSENS8960)	+= msm8960_tsens.o
> diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c
> new file mode 100644
> index 0000000..307bdc8
> --- /dev/null
> +++ b/drivers/thermal/msm8960_tsens.c
> @@ -0,0 +1,841 @@
> +/* 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/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/thermal.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +#include <linux/pm.h>
> +#include <linux/bitops.h>
> +#include <linux/regmap.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/io.h>
> +#include <linux/mfd/syscon.h>
> +
> +/* Trips: from very hot to very cold */
> +enum tsens_trip_type {
> +	TSENS_TRIP_STAGE3,
> +	TSENS_TRIP_STAGE2,
> +	TSENS_TRIP_STAGE1,
> +	TSENS_TRIP_STAGE0,
> +	TSENS_TRIP_NUM,
> +};
> +
> +#define TSENS_CAL_MDEGC				30000
> +
> +#define TSENS_MAX_SENSORS			11
> +
> +#define TSENS_8960_CONFIG_ADDR			0x3640
> +#define TSENS_8960_CONFIG			0x9b
> +#define TSENS_8960_CONFIG_MASK			0xf
> +
> +#define TSENS_CNTL_ADDR				0x3620
> +#define TSENS_CNTL_RESUME_MASK			0xfffffff9
> +#define TSENS_EN				BIT(0)
> +#define TSENS_SW_RST				BIT(1)
> +#define SENSOR0_EN				BIT(3)
> +#define TSENS_MIN_STATUS_MASK			BIT(0)
> +#define TSENS_LOWER_STATUS_CLR			BIT(1)
> +#define TSENS_UPPER_STATUS_CLR			BIT(2)
> +#define TSENS_MAX_STATUS_MASK			BIT(3)
> +#define TSENS_MEASURE_PERIOD			1
> +#define TSENS_8960_SLP_CLK_ENA			BIT(26)
> +#define TSENS_8660_SLP_CLK_ENA			BIT(24)
> +#define TSENS_8064_STATUS_CNTL			0x3660
> +
> +#define TSENS_THRESHOLD_ADDR			0x3624
> +#define TSENS_THRESHOLD_MAX_CODE		0xff
> +#define TSENS_THRESHOLD_MIN_CODE		0
> +#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT		24
> +#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT		16
> +#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT	8
> +#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT	0
> +
> +/* 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_S0_STATUS_ADDR			0x3628
> +
> +#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_SENSOR_SHIFT			16
> +#define TSENS_REDUND_SHIFT			24
> +#define TSENS_SENSOR0_SHIFT			3
> +
> +#define TSENS_8660_QFPROM_ADDR			0x00bc
> +#define TSENS_8660_CONFIG			1
> +#define TSENS_8660_CONFIG_SHIFT			28
> +#define TSENS_8660_CONFIG_MASK			(3 << TSENS_8660_CONFIG_SHIFT)
> +
> +struct tsens_device;
> +
> +struct tsens_sensor {
> +	struct thermal_zone_device	*tz_dev;
> +	enum thermal_device_mode	mode;
> +	unsigned int			sensor_num;
> +	int				offset;
> +	u32				slope;
> +	struct tsens_device		*tmdev;
> +	u32                             status;
> +};
> +
> +struct tsens_device {
> +	bool			prev_reading_avail;
> +	unsigned int		num_sensors;
> +	int			pm_tsens_thr_data;
> +	int			pm_tsens_cntl;
> +	unsigned int            calib_offset;
> +	unsigned int            backup_calib_offset;
> +	struct work_struct	tsens_work;
> +	struct regmap		*map;
> +	struct regmap_field	*status_field;
> +	struct tsens_sensor	sensor[0];
> +};
> +
> +static struct device *tsens_dev;
> +
> +/* Temperature on y axis and ADC-code on x-axis */
> +static int
> +tsens_tz_code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
> +{
> +	return adc_code * s->slope + s->offset;
> +}
> +
> +static int tsens_tz_get_temp(void *_sensor,
> +			     long *temp)
> +{
> +	const struct tsens_sensor *tm_sensor = _sensor;
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	u32 code, trdy;
> +
> +	if (tm_sensor->mode != THERMAL_DEVICE_ENABLED)
> +		return -EINVAL;
> +
> +	if (!tmdev->prev_reading_avail) {
> +		while (!regmap_read(tmdev->map, TSENS_INT_STATUS_ADDR, &trdy) &&
> +		       !(trdy & TSENS_TRDY_MASK))
> +			usleep_range(1000, 1100);
> +		tmdev->prev_reading_avail = true;
> +	}
> +
> +	regmap_read(tmdev->map, tm_sensor->status, &code);
> +	*temp = tsens_tz_code_to_mdegC(code, tm_sensor);
> +
> +	dev_dbg(tsens_dev, "Temperature for sensor %d is: %ld",
> +		tm_sensor->sensor_num, *temp);
> +
> +	return 0;
> +}
> +
> +/*
> + * If the main sensor is disabled all the sensors are disable and
> + * the clock is disabled.
> + * If the main sensor is disabled and a sub-sensor is enabled
> + * return with an error.
> + */
> +static int tsens_tz_set_mode(struct tsens_sensor *tm_sensor,
> +			      enum thermal_device_mode mode)
> +{
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	unsigned int i, n = tmdev->num_sensors;
> +	u32 reg, mask;
> +
> +	if (mode == tm_sensor->mode)
> +		return 0;
> +
> +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg);
> +
> +	mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
> +	if (mode == THERMAL_DEVICE_ENABLED) {
> +		if (!(reg & SENSOR0_EN) && mask != SENSOR0_EN) {
> +			pr_err("Main sensor not enabled\n");
> +			return -EINVAL;
> +		}
> +
> +		regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg | TSENS_SW_RST);
> +		if (tmdev->num_sensors > 1)
> +			reg |= mask | TSENS_8960_SLP_CLK_ENA | TSENS_EN;
> +		else
> +			reg |= mask | TSENS_8660_SLP_CLK_ENA | TSENS_EN;
> +		tmdev->prev_reading_avail = false;
> +	} else {
> +		reg &= ~mask;
> +		if (!(reg & SENSOR0_EN)) {
> +			dev_warn(tsens_dev, "Main sensor not enabled. Disabling subsensors\n");
> +
> +			reg &= ~GENMASK(n + TSENS_SENSOR0_SHIFT - 1,
> +					TSENS_SENSOR0_SHIFT);
> +			reg &= ~TSENS_EN;
> +
> +			if (tmdev->num_sensors > 1)
> +				reg &= ~TSENS_8960_SLP_CLK_ENA;
> +			else
> +				reg &= ~TSENS_8660_SLP_CLK_ENA;
> +
> +			/* Disable all sub-sensors */
> +			for (i = 1; i < n; i++)
> +				tmdev->sensor[i].mode = mode;
> +		}
> +	}
> +
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg);
> +	tm_sensor->mode = mode;
> +
> +	return 0;
> +}
> +
> +#ifdef THERMAL_TSENS8960_HWTRIPS
> +static int tsens_get_trip_temp(struct tsens_sensor *tm_sensor,
> +				   int trip, int *temp)
> +{
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	u32 reg;
> +
> +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &reg);
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE3:
> +		reg >>= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE2:
> +		reg >>= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE1:
> +		reg >>= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE0:
> +		reg >>= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	reg &= TSENS_THRESHOLD_MAX_CODE;
> +
> +	temp = tsens_tz_code_to_mdegC(reg, tm_sensor);
> +
> +	dev_dbg(tsens_dev, "Sensor %d, trip %d temp is :%d",
> +		tm_sensor->sensor_num, trip, *temp);
> +
> +	return 0;
> +}
> +
> +static void tsens_print_trip_temp(struct tsens_sensor *tm_sensor)
> +{
> +	int temp;
> +
> +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE0, &temp);
> +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, &temp);
> +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, &temp);
> +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE3, &temp);
> +}
> +
> +static int tsens_tz_mdegC_to_code(int mdegC, const struct tsens_sensor *s)
> +{
> +	int code;
> +
> +	code = DIV_ROUND_CLOSEST(mdegC - s->offset, s->slope);
> +	return clamp(code, TSENS_THRESHOLD_MIN_CODE, TSENS_THRESHOLD_MAX_CODE);
> +}
> +
> +static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh)
> +{
> +	u32 hi_code;
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE0:
> +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE1:
> +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE2:
> +		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE3:
> +	default:
> +		hi_code = TSENS_THRESHOLD_MAX_CODE;
> +		break;
> +	}
> +
> +	return hi_code & TSENS_THRESHOLD_MAX_CODE;
> +}
> +
> +static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh)
> +{
> +	u32 lo_code;
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE3:
> +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE2:
> +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE1:
> +		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE0:
> +	default:
> +		lo_code = TSENS_THRESHOLD_MIN_CODE;
> +		break;
> +	}
> +
> +	return lo_code & TSENS_THRESHOLD_MAX_CODE;
> +}
> +
> +static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
> +	int trip, unsigned long temp)
> +{
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	struct regmap_field *status = tmdev->status_field;
> +	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
> +	u32 code, hi_code, lo_code, code_err_chk;
> +
> +	code_err_chk = code = tsens_tz_mdegC_to_code(temp, tm_sensor);
> +
> +	regmap_field_read(status, &reg_cntl);
> +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE3:
> +		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE2:
> +		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE1:
> +		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE0:
> +		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
> +	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
> +
> +	if (code_err_chk < lo_code || code_err_chk > hi_code)
> +		return -EINVAL;
> +
> +	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR, mask, code);
> +
> +	return 0;
> +}
> +
> +static int tsens_set_trips(void *_sensor, long low, long high)
> +{
> +	struct tsens_sensor *tm_sensor = _sensor;
> +
> +	tsens_print_trip_temp(tm_sensor);
> +
> +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
> +		return -EINVAL;
> +
> +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +#endif
> +
> +static void tsens_scheduler_fn(struct work_struct *work)
> +{
> +	struct tsens_device *tmdev;
> +	struct regmap_field *status;
> +	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
> +	unsigned long sensor;
> +	bool upper_th_x, lower_th_x;
> +
> +	tmdev = container_of(work, struct tsens_device, tsens_work);
> +	status = tmdev->status_field;
> +
> +	regmap_field_update_bits(status,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR);
> +
> +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
> +	threshold_low = threshold >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
> +	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +	threshold &= TSENS_THRESHOLD_MAX_CODE;
> +
> +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
> +	sensor = bits;
> +	sensor >>= TSENS_SENSOR0_SHIFT;
> +	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
> +		regmap_read(tmdev->map, tmdev->sensor[i].status, &code);
> +		upper_th_x = code >= threshold;
> +		lower_th_x = code <= threshold_low;
> +
> +		if (upper_th_x)
> +			mask |= TSENS_UPPER_STATUS_CLR;
> +
> +		if (lower_th_x)
> +			mask |= TSENS_LOWER_STATUS_CLR;
> +
> +#ifdef THERMAL_TSENS8960_HWTRIPS
> +		if (upper_th_x || lower_th_x) {
> +			dev_info(tsens_dev,
> +				"Threshold reached for sensor(%d)\n", i);
> +			thermal_zone_device_update(tmdev->sensor[i].tz_dev);
> +		}
> +#endif
> +	}
> +
> +	regmap_field_update_bits(status,
> +			TSENS_UPPER_STATUS_CLR | TSENS_LOWER_STATUS_CLR, mask);
> +}
> +
> +static irqreturn_t tsens_isr(int irq, void *data)
> +{
> +	schedule_work(data);
> +	return IRQ_HANDLED;
> +}
> +
> +#ifdef CONFIG_PM
> +static int tsens_suspend(struct device *dev)
> +{
> +	int i;
> +	struct tsens_device *tmdev = dev_get_drvdata(dev);
> +	struct regmap *map = tmdev->map;
> +
> +	regmap_read(map, TSENS_THRESHOLD_ADDR, &tmdev->pm_tsens_thr_data);
> +	regmap_read(map, TSENS_CNTL_ADDR, &tmdev->pm_tsens_cntl);
> +	regmap_update_bits(map, TSENS_CNTL_ADDR,
> +			   TSENS_8960_SLP_CLK_ENA | TSENS_EN, 0);
> +
> +	tmdev->prev_reading_avail = 0;
> +	for (i = 0; i < tmdev->num_sensors; i++)
> +		tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED;
> +
> +	return 0;
> +}
> +
> +static int tsens_resume(struct device *dev)
> +{
> +	int i;
> +	unsigned long reg_cntl;
> +	struct tsens_device *tmdev = dev_get_drvdata(dev);
> +	struct regmap *map = tmdev->map;
> +
> +	regmap_update_bits(map, TSENS_CNTL_ADDR, TSENS_SW_RST, TSENS_SW_RST);
> +	regmap_field_update_bits(tmdev->status_field,
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
> +
> +	if (tmdev->num_sensors > 1)
> +		regmap_update_bits(map, TSENS_8960_CONFIG_ADDR,
> +				TSENS_8960_CONFIG_MASK,
> +				TSENS_8960_CONFIG);
> +
> +	regmap_write(map, TSENS_THRESHOLD_ADDR, tmdev->pm_tsens_thr_data);
> +	regmap_write(map, TSENS_CNTL_ADDR, tmdev->pm_tsens_cntl);
> +
> +	reg_cntl = tmdev->pm_tsens_cntl;
> +	reg_cntl >>= TSENS_SENSOR0_SHIFT;
> +	for_each_set_bit(i, &reg_cntl, tmdev->num_sensors)
> +		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops tsens_pm_ops = {
> +	.suspend	= tsens_suspend,
> +	.resume		= tsens_resume,
> +};
> +#endif
> +
> +static void tsens_disable_mode(const struct tsens_device *tmdev)
> +{
> +	u32 reg_cntl;
> +	u32 mask;
> +
> +	mask = GENMASK(tmdev->num_sensors - 1, 0);
> +	mask <<= TSENS_SENSOR0_SHIFT;
> +	mask |= TSENS_EN;
> +
> +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
> +	reg_cntl &= ~mask;
> +	if (tmdev->num_sensors > 1)
> +		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
> +	else
> +		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +}
> +
> +static void tsens_hw_init(struct tsens_device *tmdev)
> +{
> +	u32 reg_cntl, reg_thr;
> +
> +	reg_cntl = TSENS_SW_RST;
> +	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR, TSENS_SW_RST, reg_cntl);
> +
> +	if (tmdev->num_sensors > 1) {
> +		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
> +			(TSENS_MEASURE_PERIOD << 18);
> +		reg_cntl &= ~TSENS_SW_RST;
> +		regmap_update_bits(tmdev->map, TSENS_8960_CONFIG_ADDR,
> +				   TSENS_8960_CONFIG_MASK, TSENS_8960_CONFIG);
> +	} else {
> +		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
> +			(TSENS_MEASURE_PERIOD << 16);
> +		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
> +		reg_cntl |= TSENS_8660_CONFIG << TSENS_8660_CONFIG_SHIFT;
> +	}
> +
> +	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << TSENS_SENSOR0_SHIFT;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +
> +	regmap_field_update_bits(tmdev->status_field,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
> +
> +	reg_cntl |= TSENS_EN;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +
> +	reg_thr = (TSENS_LOWER_LIMIT_TH << TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
> +		(TSENS_UPPER_LIMIT_TH << TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
> +		(TSENS_MIN_LIMIT_TH << TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
> +		(TSENS_MAX_LIMIT_TH << TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
> +	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr);
> +}
> +
> +static int
> +tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap *map)
> +{
> +	u32 temp_data, data;
> +	struct tsens_sensor *s = &tmdev->sensor[0];
> +
> +	if (regmap_read(map, tmdev->calib_offset, &temp_data))
> +		return -EINVAL;
> +
> +	data = (temp_data >> 24) & 0xff;
> +
> +	if (!data) {
> +		dev_err(tsens_dev, "QFPROM TSENS calibration data not present\n");
> +		return -EINVAL;
> +	}
> +
> +	s->offset = TSENS_CAL_MDEGC - s->slope * data;
> +
> +	return 0;
> +}
> +
> +static int
> +tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap *map)
> +{
> +	int i;
> +	u32 temp_data[TSENS_MAX_SENSORS];
> +	u8 *byte_data;
> +	u32 fuse, redun, num_read;
> +	struct tsens_sensor *s = tmdev->sensor;
> +
> +	fuse = tmdev->calib_offset;
> +	redun = tmdev->backup_calib_offset;
> +
> +	/**
> +	* syscon regmap is 32-bit data, but calibration data is 8-bit.
> +	* read 4 bytes from regmap in a loop and then extract bytes seprately
> +	*/
> +
> +	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
> +
> +	for (i = 0; i < num_read; i++) {
> +		if (regmap_read(map, (redun + i*4), &temp_data[i]))
> +			return -EINVAL;
> +
> +		if (!temp_data[i]) {
> +			dev_dbg(tsens_dev, "Main calib data not valid\n");
> +			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
> +				return -EINVAL;
> +		}
> +	}
> +
> +	byte_data = (u8 *)temp_data;
> +
> +	for (i = 0; i < tmdev->num_sensors; i++, s++) {
> +		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
> +		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct thermal_zone_of_device_ops tsens_thermal_of_ops = {
> +	.get_temp = tsens_tz_get_temp,
> +};
> +
> +
> +static int tsens_register(struct tsens_device *tmdev, int i)
> +{
> +	char name[18];
> +	u32 addr = TSENS_S0_STATUS_ADDR;
> +	struct tsens_sensor *s = &tmdev->sensor[i];
> +
> +	/*
> +	* 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.
> +	*/
> +	if (i >= 5)
> +		addr += 40;
> +
> +	addr += i * 4;
> +
> +	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
> +	s->mode = THERMAL_DEVICE_ENABLED;
> +	s->sensor_num = i;
> +	s->status = addr;
> +	s->tmdev = tmdev;
> +	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
> +						&tsens_thermal_of_ops);
> +
> +	if (IS_ERR(s->tz_dev))
> +		return -ENODEV;
> +
> +	return 0;
> +}
> +
> +static int tsens_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct device_node *base_node;
> +	struct platform_device *base_pdev;
> +	int ret, i, irq, num;
> +	struct tsens_sensor *s;
> +	struct tsens_device *tmdev;
> +	struct regmap *map, *imem_regmap;
> +	struct reg_field *field;
> +	static struct reg_field status_0 = {
> +		.reg = TSENS_8064_STATUS_CNTL,
> +		.lsb = 0,
> +		.msb = 3,
> +	};
> +	static struct reg_field status_8 = {
> +		.reg = TSENS_CNTL_ADDR,
> +		.lsb = 8,
> +		.msb = 11,
> +	};
> +
> +	tsens_dev = &pdev->dev;
> +
> +	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
> +	if (num <= 0) {
> +		dev_err(tsens_dev, "invalid tsens slopes\n");
> +		return -EINVAL;
> +	}
> +
> +	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
> +			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
> +	if (tmdev == NULL)
> +		return -ENOMEM;
> +
> +	tmdev->num_sensors = num;
> +	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
> +		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
> +					   &s->slope);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(tsens_dev,  "no irq resource?\n");
> +		return  -EINVAL;
> +	}
> +
> +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
> +					   &tmdev->calib_offset);
> +	if (ret != 0) {
> +		dev_err(tsens_dev,  "No calibration offset set\n");
> +		return  -EINVAL;
> +	}
> +
> +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
> +					   &tmdev->backup_calib_offset);
> +	if (ret) {
> +		dev_info(tsens_dev, "Missing backup calibration offset\n");
> +		tmdev->backup_calib_offset = tmdev->calib_offset;
> +	}
> +
> +	imem_regmap = syscon_regmap_lookup_by_phandle(np, "qcom,imem");
> +	if (IS_ERR(imem_regmap)) {
> +		dev_err(tsens_dev, "syscon regmap look up error\n");
> +		return PTR_ERR(imem_regmap);
> +	}
> +
> +	if (num == 1)
> +		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
> +	else
> +		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
> +
> +	if (ret < 0) {
> +		dev_err(tsens_dev, "tsense calibration failed\n");
> +		return ret;
> +	}
> +
> +	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
> +	if (base_node == NULL) {
> +		dev_err(tsens_dev, "no base node present\n");
> +		return  -EINVAL;
> +	}
> +
> +	base_pdev = of_find_device_by_node(base_node);
> +	if (base_pdev == NULL) {
> +		dev_err(tsens_dev, "no base pdev node\n");
> +		return  -ENODEV;
> +	}
> +
> +	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
> +	if (map == NULL) {
> +		dev_err(tsens_dev, "base regmap get failed\n");
> +		return  -ENODEV;
> +	}
> +
> +	/* Status bits move when the sensor bits next to them overlap */
> +	if (num > 5)
> +		field = &status_0;
> +	else
> +		field = &status_8;
> +
> +	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map, *field);
> +	if (IS_ERR(tmdev->status_field)) {
> +		dev_err(tsens_dev, "regmap alloc failed\n");
> +		return PTR_ERR(tmdev->status_field);
> +	}
> +
> +	tsens_hw_init(tmdev);
> +
> +	/*
> +	 * Register sensor 0 separately. This sensor is always
> +	 * expected to be present and if this fails, thermal
> +	 * sensor probe would fail
> +	 * Other sensors are optional and if registration fails
> +	 * disable the sensor and continue
> +	*/
> +	ret = tsens_register(tmdev, 0);
> +	if (ret < 0) {
> +		dev_err(tsens_dev, "Registering failed for primary sensor");
> +		ret = -ENODEV;
> +		goto fail;
> +	} else {
> +		tsens_tz_set_mode(&tmdev->sensor[0], THERMAL_DEVICE_ENABLED);
> +	}
> +
> +	for (i = 1;  i < tmdev->num_sensors; i++) {
> +		ret = tsens_register(tmdev, i);
> +
> +		if (ret < 0) {
> +			dev_err(tsens_dev,
> +				"Registering failed. Sensor(%i), disabled", i);
> +			tsens_tz_set_mode(&tmdev->sensor[i],
> +				THERMAL_DEVICE_DISABLED);
> +		} else {
> +			tsens_tz_set_mode(&tmdev->sensor[i],
> +				THERMAL_DEVICE_ENABLED);
> +		}
> +	}
> +
> +	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
> +
> +	ret = devm_request_irq(tsens_dev, irq, tsens_isr, IRQF_TRIGGER_RISING,
> +			       "tsens", &tmdev->tsens_work);
> +	if (ret < 0)
> +		goto err_irq;
> +
> +	platform_set_drvdata(pdev, tmdev);
> +
> +	dev_info(tsens_dev, "Tsens driver initialized\n");
> +
> +	return 0;
> +err_irq:
> +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> +		thermal_zone_of_sensor_unregister(&pdev->dev, s->tz_dev);
> +fail:
> +	tsens_disable_mode(tmdev);
> +	return ret;
> +}
> +
> +static int tsens_remove(struct platform_device *pdev)
> +{
> +	int i;
> +	struct tsens_sensor *s;
> +	struct tsens_device *tmdev = platform_get_drvdata(pdev);
> +
> +	tsens_disable_mode(tmdev);
> +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> +		thermal_zone_device_unregister(s->tz_dev);
> +
> +	return 0;
> +}
> +
> +static struct of_device_id tsens_match_table[] = {
> +	{.compatible = "qcom,ipq806x-tsens"},
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(of, tsens_match_table);
> +
> +static struct platform_driver tsens_driver = {
> +	.probe = tsens_probe,
> +	.remove = tsens_remove,
> +	.driver = {
> +		.of_match_table = tsens_match_table,
> +		.name = "tsens8960-thermal",
> +		.owner = THIS_MODULE,
> +#ifdef CONFIG_PM
> +		.pm	= &tsens_pm_ops,
> +#endif
> +	},
> +};
> +module_platform_driver(tsens_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Temperature Sensor driver");
> +MODULE_ALIAS("platform:tsens8960-tm");
> -- 
> Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eduardo Valentin Jan. 29, 2015, 10:49 p.m. UTC | #7
On Mon, Jan 26, 2015 at 08:09:46PM -0800, Narendran Rajan wrote:
> TSENS supports reading temperature from multiple thermal
> sensors present in QCOM SOCs.
> TSENS HW is enabled only when the main sensor is requested.
> The TSENS block is disabled if the main senors is disabled
> irrespective of any other sensors that are being enabled.
> TSENS driver supports configurable threshold for temperature
> monitoring in which case it can generate an interrupt when specific
> thresholds are reached
> 
> Based on code by Siddartha Mohanadoss and Stephen Boyd.
> 
> Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> Cc: Stephen Boyd <sboyd@codeaurora.org>
> Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
> ---
>  drivers/thermal/Kconfig         |  14 +
>  drivers/thermal/Makefile        |   1 +
>  drivers/thermal/msm8960_tsens.c | 841 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 856 insertions(+)
>  create mode 100644 drivers/thermal/msm8960_tsens.c
> 
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 20915ca..a4cb2c0 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -261,6 +261,20 @@ config INTEL_SOC_DTS_THERMAL
>  	  notification methods.The other trip is a critical trip point, which
>  	  was set by the driver based on the TJ MAX temperature.
>  
> +config THERMAL_TSENS8960
> +	tristate "Qualcomm 8960 Tsens Temperature driver"
> +	depends on THERMAL
> +	depends on ARCH_QCOM
> +	help
> +	  QCOM tsens thermal driver provides support for Temperature sensor
> +	  (TSENS) found on QCOM SoCs. It supports four configurable trip points
> +	  and controls multiple sensors on the SOC. The four trip points are
> +	  common across all sensors present in the SoC. The number of sensors
> +	  present vary from chip to chip and are set through device tree entry.
> +	  The driver presents as a standard thermal zone device with configurable
> +	  trip points and cooling device mapping through standard thermal zone
> +	  device tree
> +
>  menu "ACPI INT340X thermal drivers"
>  source drivers/thermal/int340x_thermal/Kconfig
>  endmenu
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fa0dc48..23c7a34 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -39,3 +39,4 @@ obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-thermal/
>  obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
>  obj-$(CONFIG_ST_THERMAL)	+= st/
>  obj-$(CONFIG_TEGRA_SOCTHERM)	+= tegra_soctherm.o
> +obj-$(CONFIG_THERMAL_TSENS8960)	+= msm8960_tsens.o
> diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c
> new file mode 100644
> index 0000000..307bdc8
> --- /dev/null
> +++ b/drivers/thermal/msm8960_tsens.c
> @@ -0,0 +1,841 @@
> +/* 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/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/thermal.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +#include <linux/pm.h>
> +#include <linux/bitops.h>
> +#include <linux/regmap.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/io.h>
> +#include <linux/mfd/syscon.h>


Please review your includes, remove those that are not needed.

> +
> +/* Trips: from very hot to very cold */
> +enum tsens_trip_type {
> +	TSENS_TRIP_STAGE3,
> +	TSENS_TRIP_STAGE2,
> +	TSENS_TRIP_STAGE1,
> +	TSENS_TRIP_STAGE0,
> +	TSENS_TRIP_NUM,
> +};

Why is the above needed?

> +
> +#define TSENS_CAL_MDEGC				30000
> +
> +#define TSENS_MAX_SENSORS			11
> +
> +#define TSENS_8960_CONFIG_ADDR			0x3640
> +#define TSENS_8960_CONFIG			0x9b
> +#define TSENS_8960_CONFIG_MASK			0xf
> +
> +#define TSENS_CNTL_ADDR				0x3620
> +#define TSENS_CNTL_RESUME_MASK			0xfffffff9
> +#define TSENS_EN				BIT(0)
> +#define TSENS_SW_RST				BIT(1)
> +#define SENSOR0_EN				BIT(3)
> +#define TSENS_MIN_STATUS_MASK			BIT(0)
> +#define TSENS_LOWER_STATUS_CLR			BIT(1)
> +#define TSENS_UPPER_STATUS_CLR			BIT(2)
> +#define TSENS_MAX_STATUS_MASK			BIT(3)
> +#define TSENS_MEASURE_PERIOD			1
> +#define TSENS_8960_SLP_CLK_ENA			BIT(26)
> +#define TSENS_8660_SLP_CLK_ENA			BIT(24)
> +#define TSENS_8064_STATUS_CNTL			0x3660
> +
> +#define TSENS_THRESHOLD_ADDR			0x3624
> +#define TSENS_THRESHOLD_MAX_CODE		0xff
> +#define TSENS_THRESHOLD_MIN_CODE		0
> +#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT		24
> +#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT		16
> +#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT	8
> +#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT	0
> +
> +/* 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_S0_STATUS_ADDR			0x3628
> +
> +#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_SENSOR_SHIFT			16
> +#define TSENS_REDUND_SHIFT			24
> +#define TSENS_SENSOR0_SHIFT			3
> +
> +#define TSENS_8660_QFPROM_ADDR			0x00bc
> +#define TSENS_8660_CONFIG			1
> +#define TSENS_8660_CONFIG_SHIFT			28
> +#define TSENS_8660_CONFIG_MASK			(3 << TSENS_8660_CONFIG_SHIFT)
> +
> +struct tsens_device;
> +
> +struct tsens_sensor {
> +	struct thermal_zone_device	*tz_dev;
> +	enum thermal_device_mode	mode;
> +	unsigned int			sensor_num;
> +	int				offset;
> +	u32				slope;
> +	struct tsens_device		*tmdev;
> +	u32                             status;
> +};
> +
> +struct tsens_device {
> +	bool			prev_reading_avail;
> +	unsigned int		num_sensors;
> +	int			pm_tsens_thr_data;
> +	int			pm_tsens_cntl;
> +	unsigned int            calib_offset;
> +	unsigned int            backup_calib_offset;
> +	struct work_struct	tsens_work;
> +	struct regmap		*map;
> +	struct regmap_field	*status_field;
> +	struct tsens_sensor	sensor[0];
> +};
> +
> +static struct device *tsens_dev;
> +
> +/* Temperature on y axis and ADC-code on x-axis */
> +static int
> +tsens_tz_code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
> +{
> +	return adc_code * s->slope + s->offset;
> +}
> +
> +static int tsens_tz_get_temp(void *_sensor,
> +			     long *temp)
> +{
> +	const struct tsens_sensor *tm_sensor = _sensor;
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	u32 code, trdy;
> +
> +	if (tm_sensor->mode != THERMAL_DEVICE_ENABLED)
> +		return -EINVAL;
> +
> +	if (!tmdev->prev_reading_avail) {
> +		while (!regmap_read(tmdev->map, TSENS_INT_STATUS_ADDR, &trdy) &&
> +		       !(trdy & TSENS_TRDY_MASK))
> +			usleep_range(1000, 1100);
> +		tmdev->prev_reading_avail = true;
> +	}
> +
> +	regmap_read(tmdev->map, tm_sensor->status, &code);
> +	*temp = tsens_tz_code_to_mdegC(code, tm_sensor);
> +
> +	dev_dbg(tsens_dev, "Temperature for sensor %d is: %ld",
> +		tm_sensor->sensor_num, *temp);
> +
> +	return 0;
> +}
> +
> +/*
> + * If the main sensor is disabled all the sensors are disable and
> + * the clock is disabled.
> + * If the main sensor is disabled and a sub-sensor is enabled
> + * return with an error.
> + */
> +static int tsens_tz_set_mode(struct tsens_sensor *tm_sensor,
> +			      enum thermal_device_mode mode)
> +{
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	unsigned int i, n = tmdev->num_sensors;
> +	u32 reg, mask;
> +
> +	if (mode == tm_sensor->mode)
> +		return 0;
> +
> +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg);
> +
> +	mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
> +	if (mode == THERMAL_DEVICE_ENABLED) {
> +		if (!(reg & SENSOR0_EN) && mask != SENSOR0_EN) {
> +			pr_err("Main sensor not enabled\n");
> +			return -EINVAL;
> +		}
> +
> +		regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg | TSENS_SW_RST);
> +		if (tmdev->num_sensors > 1)
> +			reg |= mask | TSENS_8960_SLP_CLK_ENA | TSENS_EN;
> +		else
> +			reg |= mask | TSENS_8660_SLP_CLK_ENA | TSENS_EN;
> +		tmdev->prev_reading_avail = false;
> +	} else {
> +		reg &= ~mask;
> +		if (!(reg & SENSOR0_EN)) {
> +			dev_warn(tsens_dev, "Main sensor not enabled. Disabling subsensors\n");
> +
> +			reg &= ~GENMASK(n + TSENS_SENSOR0_SHIFT - 1,
> +					TSENS_SENSOR0_SHIFT);
> +			reg &= ~TSENS_EN;
> +
> +			if (tmdev->num_sensors > 1)
> +				reg &= ~TSENS_8960_SLP_CLK_ENA;
> +			else
> +				reg &= ~TSENS_8660_SLP_CLK_ENA;
> +
> +			/* Disable all sub-sensors */
> +			for (i = 1; i < n; i++)
> +				tmdev->sensor[i].mode = mode;
> +		}
> +	}
> +
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg);
> +	tm_sensor->mode = mode;
> +
> +	return 0;
> +}
> +
> +#ifdef THERMAL_TSENS8960_HWTRIPS
> +static int tsens_get_trip_temp(struct tsens_sensor *tm_sensor,
> +				   int trip, int *temp)
> +{
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	u32 reg;
> +
> +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &reg);
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE3:
> +		reg >>= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE2:
> +		reg >>= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE1:
> +		reg >>= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE0:
> +		reg >>= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	reg &= TSENS_THRESHOLD_MAX_CODE;
> +
> +	temp = tsens_tz_code_to_mdegC(reg, tm_sensor);
> +
> +	dev_dbg(tsens_dev, "Sensor %d, trip %d temp is :%d",
> +		tm_sensor->sensor_num, trip, *temp);
> +
> +	return 0;
> +}
> +
> +static void tsens_print_trip_temp(struct tsens_sensor *tm_sensor)
> +{
> +	int temp;
> +
> +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE0, &temp);
> +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, &temp);
> +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, &temp);
> +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE3, &temp);
> +}
> +
> +static int tsens_tz_mdegC_to_code(int mdegC, const struct tsens_sensor *s)
> +{
> +	int code;
> +
> +	code = DIV_ROUND_CLOSEST(mdegC - s->offset, s->slope);
> +	return clamp(code, TSENS_THRESHOLD_MIN_CODE, TSENS_THRESHOLD_MAX_CODE);
> +}
> +
> +static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh)
> +{
> +	u32 hi_code;
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE0:
> +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE1:
> +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE2:
> +		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
> +			hi_code = thresh >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE3:
> +	default:
> +		hi_code = TSENS_THRESHOLD_MAX_CODE;
> +		break;
> +	}
> +
> +	return hi_code & TSENS_THRESHOLD_MAX_CODE;
> +}
> +
> +static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh)
> +{
> +	u32 lo_code;
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE3:
> +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE2:
> +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE1:
> +		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
> +			lo_code = thresh >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +			break;
> +		}
> +		/* else fall through */
> +	case TSENS_TRIP_STAGE0:
> +	default:
> +		lo_code = TSENS_THRESHOLD_MIN_CODE;
> +		break;
> +	}
> +
> +	return lo_code & TSENS_THRESHOLD_MAX_CODE;
> +}
> +
> +static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
> +	int trip, unsigned long temp)
> +{
> +	struct tsens_device *tmdev = tm_sensor->tmdev;
> +	struct regmap_field *status = tmdev->status_field;
> +	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
> +	u32 code, hi_code, lo_code, code_err_chk;
> +
> +	code_err_chk = code = tsens_tz_mdegC_to_code(temp, tm_sensor);
> +
> +	regmap_field_read(status, &reg_cntl);
> +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
> +
> +	switch (trip) {
> +	case TSENS_TRIP_STAGE3:
> +		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE2:
> +		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE1:
> +		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +		break;
> +	case TSENS_TRIP_STAGE0:
> +		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
> +	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
> +
> +	if (code_err_chk < lo_code || code_err_chk > hi_code)
> +		return -EINVAL;
> +
> +	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR, mask, code);
> +
> +	return 0;
> +}
> +
> +static int tsens_set_trips(void *_sensor, long low, long high)
> +{
> +	struct tsens_sensor *tm_sensor = _sensor;
> +
> +	tsens_print_trip_temp(tm_sensor);
> +
> +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
> +		return -EINVAL;
> +
> +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +#endif

This whole chunk of code seams to be unused. Should we remove it?

Looks like this driver brings again the HW trip discussion again. Do you
have any specific needs to extend of thermal?



> +
> +static void tsens_scheduler_fn(struct work_struct *work)
> +{
> +	struct tsens_device *tmdev;
> +	struct regmap_field *status;
> +	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
> +	unsigned long sensor;
> +	bool upper_th_x, lower_th_x;
> +
> +	tmdev = container_of(work, struct tsens_device, tsens_work);
> +	status = tmdev->status_field;
> +
> +	regmap_field_update_bits(status,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR);
> +
> +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
> +	threshold_low = threshold >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> +	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
> +	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> +	threshold &= TSENS_THRESHOLD_MAX_CODE;
> +
> +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
> +	sensor = bits;
> +	sensor >>= TSENS_SENSOR0_SHIFT;
> +	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
> +		regmap_read(tmdev->map, tmdev->sensor[i].status, &code);
> +		upper_th_x = code >= threshold;
> +		lower_th_x = code <= threshold_low;
> +
> +		if (upper_th_x)
> +			mask |= TSENS_UPPER_STATUS_CLR;
> +
> +		if (lower_th_x)
> +			mask |= TSENS_LOWER_STATUS_CLR;
> +
> +#ifdef THERMAL_TSENS8960_HWTRIPS
> +		if (upper_th_x || lower_th_x) {
> +			dev_info(tsens_dev,
> +				"Threshold reached for sensor(%d)\n", i);
> +			thermal_zone_device_update(tmdev->sensor[i].tz_dev);
> +		}
> +#endif
> +	}
> +
> +	regmap_field_update_bits(status,
> +			TSENS_UPPER_STATUS_CLR | TSENS_LOWER_STATUS_CLR, mask);
> +}
> +
> +static irqreturn_t tsens_isr(int irq, void *data)
> +{
> +	schedule_work(data);
> +	return IRQ_HANDLED;
> +}
> +
> +#ifdef CONFIG_PM
> +static int tsens_suspend(struct device *dev)
> +{
> +	int i;
> +	struct tsens_device *tmdev = dev_get_drvdata(dev);
> +	struct regmap *map = tmdev->map;
> +
> +	regmap_read(map, TSENS_THRESHOLD_ADDR, &tmdev->pm_tsens_thr_data);
> +	regmap_read(map, TSENS_CNTL_ADDR, &tmdev->pm_tsens_cntl);
> +	regmap_update_bits(map, TSENS_CNTL_ADDR,
> +			   TSENS_8960_SLP_CLK_ENA | TSENS_EN, 0);
> +
> +	tmdev->prev_reading_avail = 0;
> +	for (i = 0; i < tmdev->num_sensors; i++)
> +		tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED;
> +
> +	return 0;
> +}
> +
> +static int tsens_resume(struct device *dev)
> +{
> +	int i;
> +	unsigned long reg_cntl;
> +	struct tsens_device *tmdev = dev_get_drvdata(dev);
> +	struct regmap *map = tmdev->map;
> +
> +	regmap_update_bits(map, TSENS_CNTL_ADDR, TSENS_SW_RST, TSENS_SW_RST);
> +	regmap_field_update_bits(tmdev->status_field,
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
> +
> +	if (tmdev->num_sensors > 1)
> +		regmap_update_bits(map, TSENS_8960_CONFIG_ADDR,
> +				TSENS_8960_CONFIG_MASK,
> +				TSENS_8960_CONFIG);
> +
> +	regmap_write(map, TSENS_THRESHOLD_ADDR, tmdev->pm_tsens_thr_data);
> +	regmap_write(map, TSENS_CNTL_ADDR, tmdev->pm_tsens_cntl);
> +
> +	reg_cntl = tmdev->pm_tsens_cntl;
> +	reg_cntl >>= TSENS_SENSOR0_SHIFT;
> +	for_each_set_bit(i, &reg_cntl, tmdev->num_sensors)
> +		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops tsens_pm_ops = {
> +	.suspend	= tsens_suspend,
> +	.resume		= tsens_resume,
> +};
> +#endif
> +
> +static void tsens_disable_mode(const struct tsens_device *tmdev)
> +{
> +	u32 reg_cntl;
> +	u32 mask;
> +
> +	mask = GENMASK(tmdev->num_sensors - 1, 0);
> +	mask <<= TSENS_SENSOR0_SHIFT;
> +	mask |= TSENS_EN;
> +
> +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
> +	reg_cntl &= ~mask;
> +	if (tmdev->num_sensors > 1)
> +		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
> +	else
> +		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +}
> +
> +static void tsens_hw_init(struct tsens_device *tmdev)
> +{
> +	u32 reg_cntl, reg_thr;
> +
> +	reg_cntl = TSENS_SW_RST;
> +	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR, TSENS_SW_RST, reg_cntl);
> +
> +	if (tmdev->num_sensors > 1) {
> +		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
> +			(TSENS_MEASURE_PERIOD << 18);
> +		reg_cntl &= ~TSENS_SW_RST;
> +		regmap_update_bits(tmdev->map, TSENS_8960_CONFIG_ADDR,
> +				   TSENS_8960_CONFIG_MASK, TSENS_8960_CONFIG);
> +	} else {
> +		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
> +			(TSENS_MEASURE_PERIOD << 16);
> +		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
> +		reg_cntl |= TSENS_8660_CONFIG << TSENS_8660_CONFIG_SHIFT;
> +	}
> +
> +	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << TSENS_SENSOR0_SHIFT;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +
> +	regmap_field_update_bits(tmdev->status_field,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
> +			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
> +			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
> +
> +	reg_cntl |= TSENS_EN;
> +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> +
> +	reg_thr = (TSENS_LOWER_LIMIT_TH << TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
> +		(TSENS_UPPER_LIMIT_TH << TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
> +		(TSENS_MIN_LIMIT_TH << TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
> +		(TSENS_MAX_LIMIT_TH << TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
> +	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr);
> +}
> +
> +static int
> +tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap *map)
> +{
> +	u32 temp_data, data;
> +	struct tsens_sensor *s = &tmdev->sensor[0];
> +
> +	if (regmap_read(map, tmdev->calib_offset, &temp_data))
> +		return -EINVAL;
> +
> +	data = (temp_data >> 24) & 0xff;
> +
> +	if (!data) {
> +		dev_err(tsens_dev, "QFPROM TSENS calibration data not present\n");
> +		return -EINVAL;
> +	}
> +
> +	s->offset = TSENS_CAL_MDEGC - s->slope * data;
> +
> +	return 0;
> +}
> +
> +static int
> +tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap *map)
> +{
> +	int i;
> +	u32 temp_data[TSENS_MAX_SENSORS];
> +	u8 *byte_data;
> +	u32 fuse, redun, num_read;
> +	struct tsens_sensor *s = tmdev->sensor;
> +
> +	fuse = tmdev->calib_offset;
> +	redun = tmdev->backup_calib_offset;
> +
> +	/**
> +	* syscon regmap is 32-bit data, but calibration data is 8-bit.
> +	* read 4 bytes from regmap in a loop and then extract bytes seprately
> +	*/
> +
> +	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
> +
> +	for (i = 0; i < num_read; i++) {
> +		if (regmap_read(map, (redun + i*4), &temp_data[i]))
> +			return -EINVAL;
> +
> +		if (!temp_data[i]) {
> +			dev_dbg(tsens_dev, "Main calib data not valid\n");
> +			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
> +				return -EINVAL;
> +		}
> +	}
> +
> +	byte_data = (u8 *)temp_data;
> +
> +	for (i = 0; i < tmdev->num_sensors; i++, s++) {
> +		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
> +		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct thermal_zone_of_device_ops tsens_thermal_of_ops = {
> +	.get_temp = tsens_tz_get_temp,
> +};
> +
> +
> +static int tsens_register(struct tsens_device *tmdev, int i)
> +{
> +	char name[18];
> +	u32 addr = TSENS_S0_STATUS_ADDR;
> +	struct tsens_sensor *s = &tmdev->sensor[i];
> +
> +	/*
> +	* 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.
> +	*/
> +	if (i >= 5)
> +		addr += 40;
> +
> +	addr += i * 4;
> +
> +	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
> +	s->mode = THERMAL_DEVICE_ENABLED;
> +	s->sensor_num = i;
> +	s->status = addr;
> +	s->tmdev = tmdev;
> +	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
> +						&tsens_thermal_of_ops);
> +
> +	if (IS_ERR(s->tz_dev))
> +		return -ENODEV;
> +
> +	return 0;
> +}
> +
> +static int tsens_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct device_node *base_node;
> +	struct platform_device *base_pdev;
> +	int ret, i, irq, num;
> +	struct tsens_sensor *s;
> +	struct tsens_device *tmdev;
> +	struct regmap *map, *imem_regmap;
> +	struct reg_field *field;
> +	static struct reg_field status_0 = {
> +		.reg = TSENS_8064_STATUS_CNTL,
> +		.lsb = 0,
> +		.msb = 3,
> +	};
> +	static struct reg_field status_8 = {
> +		.reg = TSENS_CNTL_ADDR,
> +		.lsb = 8,
> +		.msb = 11,
> +	};
> +
> +	tsens_dev = &pdev->dev;
> +
> +	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
> +	if (num <= 0) {
> +		dev_err(tsens_dev, "invalid tsens slopes\n");
> +		return -EINVAL;
> +	}
> +
> +	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
> +			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
> +	if (tmdev == NULL)
> +		return -ENOMEM;
> +
> +	tmdev->num_sensors = num;
> +	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
> +		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
> +					   &s->slope);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(tsens_dev,  "no irq resource?\n");
> +		return  -EINVAL;
> +	}
> +
> +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
> +					   &tmdev->calib_offset);
> +	if (ret != 0) {
> +		dev_err(tsens_dev,  "No calibration offset set\n");
> +		return  -EINVAL;
> +	}
> +
> +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
> +					   &tmdev->backup_calib_offset);
> +	if (ret) {
> +		dev_info(tsens_dev, "Missing backup calibration offset\n");
> +		tmdev->backup_calib_offset = tmdev->calib_offset;
> +	}
> +
> +	imem_regmap = syscon_regmap_lookup_by_phandle(np, "qcom,imem");
> +	if (IS_ERR(imem_regmap)) {
> +		dev_err(tsens_dev, "syscon regmap look up error\n");
> +		return PTR_ERR(imem_regmap);
> +	}
> +
> +	if (num == 1)
> +		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
> +	else
> +		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
> +
> +	if (ret < 0) {
> +		dev_err(tsens_dev, "tsense calibration failed\n");
> +		return ret;
> +	}
> +
> +	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
> +	if (base_node == NULL) {
> +		dev_err(tsens_dev, "no base node present\n");
> +		return  -EINVAL;
> +	}
> +
> +	base_pdev = of_find_device_by_node(base_node);
> +	if (base_pdev == NULL) {
> +		dev_err(tsens_dev, "no base pdev node\n");
> +		return  -ENODEV;
> +	}
> +
> +	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
> +	if (map == NULL) {
> +		dev_err(tsens_dev, "base regmap get failed\n");
> +		return  -ENODEV;
> +	}
> +
> +	/* Status bits move when the sensor bits next to them overlap */
> +	if (num > 5)
> +		field = &status_0;
> +	else
> +		field = &status_8;
> +
> +	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map, *field);
> +	if (IS_ERR(tmdev->status_field)) {
> +		dev_err(tsens_dev, "regmap alloc failed\n");
> +		return PTR_ERR(tmdev->status_field);
> +	}
> +
> +	tsens_hw_init(tmdev);
> +
> +	/*
> +	 * Register sensor 0 separately. This sensor is always
> +	 * expected to be present and if this fails, thermal
> +	 * sensor probe would fail
> +	 * Other sensors are optional and if registration fails
> +	 * disable the sensor and continue
> +	*/
> +	ret = tsens_register(tmdev, 0);
> +	if (ret < 0) {
> +		dev_err(tsens_dev, "Registering failed for primary sensor");
> +		ret = -ENODEV;
> +		goto fail;
> +	} else {
> +		tsens_tz_set_mode(&tmdev->sensor[0], THERMAL_DEVICE_ENABLED);
> +	}
> +
> +	for (i = 1;  i < tmdev->num_sensors; i++) {
> +		ret = tsens_register(tmdev, i);
> +
> +		if (ret < 0) {
> +			dev_err(tsens_dev,
> +				"Registering failed. Sensor(%i), disabled", i);
> +			tsens_tz_set_mode(&tmdev->sensor[i],
> +				THERMAL_DEVICE_DISABLED);
> +		} else {
> +			tsens_tz_set_mode(&tmdev->sensor[i],
> +				THERMAL_DEVICE_ENABLED);
> +		}
> +	}
> +
> +	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
> +
> +	ret = devm_request_irq(tsens_dev, irq, tsens_isr, IRQF_TRIGGER_RISING,
> +			       "tsens", &tmdev->tsens_work);
> +	if (ret < 0)
> +		goto err_irq;
> +
> +	platform_set_drvdata(pdev, tmdev);
> +
> +	dev_info(tsens_dev, "Tsens driver initialized\n");
> +
> +	return 0;
> +err_irq:
> +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> +		thermal_zone_of_sensor_unregister(&pdev->dev, s->tz_dev);
> +fail:
> +	tsens_disable_mode(tmdev);
> +	return ret;
> +}
> +
> +static int tsens_remove(struct platform_device *pdev)
> +{
> +	int i;
> +	struct tsens_sensor *s;
> +	struct tsens_device *tmdev = platform_get_drvdata(pdev);
> +
> +	tsens_disable_mode(tmdev);
> +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> +		thermal_zone_device_unregister(s->tz_dev);
> +
> +	return 0;
> +}
> +
> +static struct of_device_id tsens_match_table[] = {
> +	{.compatible = "qcom,ipq806x-tsens"},
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(of, tsens_match_table);
> +
> +static struct platform_driver tsens_driver = {
> +	.probe = tsens_probe,
> +	.remove = tsens_remove,
> +	.driver = {
> +		.of_match_table = tsens_match_table,
> +		.name = "tsens8960-thermal",
> +		.owner = THIS_MODULE,
> +#ifdef CONFIG_PM
> +		.pm	= &tsens_pm_ops,
> +#endif
> +	},
> +};
> +module_platform_driver(tsens_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Temperature Sensor driver");
> +MODULE_ALIAS("platform:tsens8960-tm");
> -- 
> Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Narendran Rajan Jan. 30, 2015, 1:06 a.m. UTC | #8
Thanks for  the detail look.

> -----Original Message-----
> From: Lina Iyer [mailto:lina.iyer@linaro.org]
> Sent: Wednesday, January 28, 2015 9:01 AM
> To: Narendran Rajan
> Cc: Zhang Rui; Eduardo Valentin; Linux ARM MSM; Linux PM; Siddartha
> Mohanadoss; Stephen Boyd
> Subject: Re: [PATCH] thermal: Add msm tsens thermal sensor driver
> 
> On Mon, Jan 26 2015 at 21:10 -0700, Narendran Rajan wrote:
> >TSENS supports reading temperature from multiple thermal sensors
> >present in QCOM SOCs.
> >TSENS HW is enabled only when the main sensor is requested.
> >The TSENS block is disabled if the main senors is disabled irrespective
> >of any other sensors that are being enabled.
> >TSENS driver supports configurable threshold for temperature monitoring
> >in which case it can generate an interrupt when specific thresholds are
> >reached
> >
> >Based on code by Siddartha Mohanadoss and Stephen Boyd.
> >
> >Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> >Cc: Stephen Boyd <sboyd@codeaurora.org>
> >Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
> >---
> > drivers/thermal/Kconfig         |  14 +
> > drivers/thermal/Makefile        |   1 +
> > drivers/thermal/msm8960_tsens.c | 841
> >++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 856 insertions(+)
> > create mode 100644 drivers/thermal/msm8960_tsens.c
> >
> >diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index
> >20915ca..a4cb2c0 100644
> >--- a/drivers/thermal/Kconfig
> >+++ b/drivers/thermal/Kconfig
> >@@ -261,6 +261,20 @@ config INTEL_SOC_DTS_THERMAL
> > 	  notification methods.The other trip is a critical trip point,
which
> > 	  was set by the driver based on the TJ MAX temperature.
> >
> >+config THERMAL_TSENS8960
> >+	tristate "Qualcomm 8960 Tsens Temperature driver"
> >+	depends on THERMAL
> >+	depends on ARCH_QCOM
> >+	help
> >+	  QCOM tsens thermal driver provides support for Temperature
> sensor
> >+	  (TSENS) found on QCOM SoCs. It supports four configurable trip
> points
> >+	  and controls multiple sensors on the SOC. The four trip points are
> >+	  common across all sensors present in the SoC. The number of
> sensors
> >+	  present vary from chip to chip and are set through device tree
> entry.
> >+	  The driver presents as a standard thermal zone device with
> configurable
> >+	  trip points and cooling device mapping through standard thermal
> zone
> >+	  device tree
> >+
> > menu "ACPI INT340X thermal drivers"
> > source drivers/thermal/int340x_thermal/Kconfig
> > endmenu
> >diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index
> >fa0dc48..23c7a34 100644
> >--- a/drivers/thermal/Makefile
> >+++ b/drivers/thermal/Makefile
> >@@ -39,3 +39,4 @@ obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-
> thermal/
> > obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
> > obj-$(CONFIG_ST_THERMAL)	+= st/
> > obj-$(CONFIG_TEGRA_SOCTHERM)	+= tegra_soctherm.o
> >+obj-$(CONFIG_THERMAL_TSENS8960)	+= msm8960_tsens.o
> >diff --git a/drivers/thermal/msm8960_tsens.c
> >b/drivers/thermal/msm8960_tsens.c new file mode 100644 index
> >0000000..307bdc8
> >--- /dev/null
> >+++ b/drivers/thermal/msm8960_tsens.c
> >@@ -0,0 +1,841 @@
> >+/* 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/kernel.h>
> >+#include <linux/module.h>
> >+#include <linux/platform_device.h>
> >+#include <linux/thermal.h>
> >+#include <linux/interrupt.h>
> >+#include <linux/delay.h>
> >+#include <linux/slab.h>
> >+#include <linux/err.h>
> >+#include <linux/pm.h>
> >+#include <linux/bitops.h>
> >+#include <linux/regmap.h>
> >+#include <linux/of.h>
> >+#include <linux/of_address.h>
> >+#include <linux/of_platform.h>
> >+#include <linux/io.h>
> >+#include <linux/mfd/syscon.h>
> >+
> >+/* Trips: from very hot to very cold */ enum tsens_trip_type {
> >+	TSENS_TRIP_STAGE3,
> >+	TSENS_TRIP_STAGE2,
> >+	TSENS_TRIP_STAGE1,
> >+	TSENS_TRIP_STAGE0,
> >+	TSENS_TRIP_NUM,
> >+};
> 
> Care to explain why is this different from the trip_types defined in
> linux/thermal.h ?
> 

The internal trip points have a different meaning (configurable trips, which
could moved around 
based on userspace thermal daemon algorithm) than the one defined in
thermal.h.

Given all your feedbacks, for now I am going to remove HW TRIP dependencies
and will have this removed as well

> >+
> >+#define TSENS_CAL_MDEGC				30000
> >+
> >+#define TSENS_MAX_SENSORS			11
> >+
> >+#define TSENS_8960_CONFIG_ADDR			0x3640
> >+#define TSENS_8960_CONFIG			0x9b
> >+#define TSENS_8960_CONFIG_MASK			0xf
> >+
> >+#define TSENS_CNTL_ADDR				0x3620
> >+#define TSENS_CNTL_RESUME_MASK			0xfffffff9
> >+#define TSENS_EN				BIT(0)
> >+#define TSENS_SW_RST				BIT(1)
> >+#define SENSOR0_EN				BIT(3)
> >+#define TSENS_MIN_STATUS_MASK			BIT(0)
> >+#define TSENS_LOWER_STATUS_CLR			BIT(1)
> >+#define TSENS_UPPER_STATUS_CLR			BIT(2)
> >+#define TSENS_MAX_STATUS_MASK			BIT(3)
> >+#define TSENS_MEASURE_PERIOD			1
> >+#define TSENS_8960_SLP_CLK_ENA			BIT(26)
> >+#define TSENS_8660_SLP_CLK_ENA			BIT(24)
> >+#define TSENS_8064_STATUS_CNTL			0x3660
> >+
> >+#define TSENS_THRESHOLD_ADDR			0x3624
> >+#define TSENS_THRESHOLD_MAX_CODE		0xff
> >+#define TSENS_THRESHOLD_MIN_CODE		0
> >+#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT		24
> >+#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT		16
> >+#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT	8
> >+#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT	0
> >+
> >+/* 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_S0_STATUS_ADDR			0x3628
> >+
> >+#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_SENSOR_SHIFT			16
> >+#define TSENS_REDUND_SHIFT			24
> >+#define TSENS_SENSOR0_SHIFT			3
> >+
> >+#define TSENS_8660_QFPROM_ADDR			0x00bc
> >+#define TSENS_8660_CONFIG			1
> >+#define TSENS_8660_CONFIG_SHIFT			28
> >+#define TSENS_8660_CONFIG_MASK			(3 <<
> TSENS_8660_CONFIG_SHIFT)
> 
> Could you align these?

Didn't notice. Thx, will do.

> >+
> >+struct tsens_device;
> >+
> >+struct tsens_sensor {
> >+	struct thermal_zone_device	*tz_dev;
> >+	enum thermal_device_mode	mode;
> >+	unsigned int			sensor_num;
> >+	int				offset;
> >+	u32				slope;
> >+	struct tsens_device		*tmdev;
> >+	u32                             status;
> >+};
> >+
> >+struct tsens_device {
> >+	bool			prev_reading_avail;
> >+	unsigned int		num_sensors;
> >+	int			pm_tsens_thr_data;
> >+	int			pm_tsens_cntl;
> >+	unsigned int            calib_offset;
> >+	unsigned int            backup_calib_offset;
> >+	struct work_struct	tsens_work;
> >+	struct regmap		*map;
> >+	struct regmap_field	*status_field;
> >+	struct tsens_sensor	sensor[0];
> >+};
> >+
> >+static struct device *tsens_dev;
> >+
> >+/* Temperature on y axis and ADC-code on x-axis */ static int
> >+tsens_tz_code_to_mdegC(u32 adc_code, const struct tsens_sensor *s) {
> >+	return adc_code * s->slope + s->offset; }
> >+
> >+static int tsens_tz_get_temp(void *_sensor,
> >+			     long *temp)
> >+{
> >+	const struct tsens_sensor *tm_sensor = _sensor;
> >+	struct tsens_device *tmdev = tm_sensor->tmdev;
> >+	u32 code, trdy;
> >+
> >+	if (tm_sensor->mode != THERMAL_DEVICE_ENABLED)
> >+		return -EINVAL;
> >+
> >+	if (!tmdev->prev_reading_avail) {
> >+		while (!regmap_read(tmdev->map,
> TSENS_INT_STATUS_ADDR, &trdy) &&
> >+		       !(trdy & TSENS_TRDY_MASK))
> >+			usleep_range(1000, 1100);
> >+		tmdev->prev_reading_avail = true;
> >+	}
> >+
> >+	regmap_read(tmdev->map, tm_sensor->status, &code);
> >+	*temp = tsens_tz_code_to_mdegC(code, tm_sensor);
> >+
> >+	dev_dbg(tsens_dev, "Temperature for sensor %d is: %ld",
> >+		tm_sensor->sensor_num, *temp);
> >+
> >+	return 0;
> >+}
> >+
> >+/*
> >+ * If the main sensor is disabled all the sensors are disable and
> 
> /s/disable/disabled
> 

Thanks. Will fix

> >+ * the clock is disabled.
> >+ * If the main sensor is disabled and a sub-sensor is enabled
> >+ * return with an error.
> >+ */
> >+static int tsens_tz_set_mode(struct tsens_sensor *tm_sensor,
> >+			      enum thermal_device_mode mode) {
> >+	struct tsens_device *tmdev = tm_sensor->tmdev;
> >+	unsigned int i, n = tmdev->num_sensors;
> >+	u32 reg, mask;
> >+
> >+	if (mode == tm_sensor->mode)
> >+		return 0;
> >+
> >+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg);
> >+
> >+	mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
> >+	if (mode == THERMAL_DEVICE_ENABLED) {
> >+		if (!(reg & SENSOR0_EN) && mask != SENSOR0_EN) {
> >+			pr_err("Main sensor not enabled\n");
> >+			return -EINVAL;
> >+		}
> >+
> >+		regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg |
> TSENS_SW_RST);
> >+		if (tmdev->num_sensors > 1)
> >+			reg |= mask | TSENS_8960_SLP_CLK_ENA |
> TSENS_EN;
> >+		else
> >+			reg |= mask | TSENS_8660_SLP_CLK_ENA |
> TSENS_EN;
> 
> Instead of doing this check everytime to decide between 8660 and 8960, at
> init you could set the right mask value in the tsens_device.
> 

Agree, its cleaner and scalable that way. Will do.

> >+		tmdev->prev_reading_avail = false;
> >+	} else {
> >+		reg &= ~mask;
> >+		if (!(reg & SENSOR0_EN)) {
> >+			dev_warn(tsens_dev, "Main sensor not enabled.
> Disabling
> >+subsensors\n");
> >+
> >+			reg &= ~GENMASK(n + TSENS_SENSOR0_SHIFT - 1,
> >+					TSENS_SENSOR0_SHIFT);
> >+			reg &= ~TSENS_EN;
> >+
> >+			if (tmdev->num_sensors > 1)
> >+				reg &= ~TSENS_8960_SLP_CLK_ENA;
> >+			else
> >+				reg &= ~TSENS_8660_SLP_CLK_ENA;
> >+
> >+			/* Disable all sub-sensors */
> >+			for (i = 1; i < n; i++)
> >+				tmdev->sensor[i].mode = mode;
> >+		}
> >+	}
> >+
> >+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg);
> >+	tm_sensor->mode = mode;
> >+
> >+	return 0;
> >+}
> >+
> >+#ifdef THERMAL_TSENS8960_HWTRIPS
> >+static int tsens_get_trip_temp(struct tsens_sensor *tm_sensor,
> >+				   int trip, int *temp)
> >+{
> >+	struct tsens_device *tmdev = tm_sensor->tmdev;
> >+	u32 reg;
> >+
> >+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &reg);
> >+	switch (trip) {
> >+	case TSENS_TRIP_STAGE3:
> >+		reg >>= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE2:
> >+		reg >>= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE1:
> >+		reg >>= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE0:
> >+		reg >>= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+	reg &= TSENS_THRESHOLD_MAX_CODE;
> >+
> >+	temp = tsens_tz_code_to_mdegC(reg, tm_sensor);
> >+
> >+	dev_dbg(tsens_dev, "Sensor %d, trip %d temp is :%d",
> >+		tm_sensor->sensor_num, trip, *temp);
> >+
> >+	return 0;
> >+}
> >+
> >+static void tsens_print_trip_temp(struct tsens_sensor *tm_sensor) {
> >+	int temp;
> >+
> >+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE0, &temp);
> >+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, &temp);
> >+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, &temp);
> >+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE3, &temp); }
> >+
> >+static int tsens_tz_mdegC_to_code(int mdegC, const struct tsens_sensor
> >+*s) {
> >+	int code;
> >+
> >+	code = DIV_ROUND_CLOSEST(mdegC - s->offset, s->slope);
> >+	return clamp(code, TSENS_THRESHOLD_MIN_CODE,
> >+TSENS_THRESHOLD_MAX_CODE); }
> >+
> >+static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh) {
> >+	u32 hi_code;
> >+
> >+	switch (trip) {
> >+	case TSENS_TRIP_STAGE0:
> >+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> >+			hi_code = thresh >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE1:
> >+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> >+			hi_code = thresh >>
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE2:
> >+		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
> >+			hi_code = thresh >>
> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE3:
> >+	default:
> >+		hi_code = TSENS_THRESHOLD_MAX_CODE;
> >+		break;
> >+	}
> >+
> >+	return hi_code & TSENS_THRESHOLD_MAX_CODE; }
> >+
> >+static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh) {
> >+	u32 lo_code;
> >+
> >+	switch (trip) {
> >+	case TSENS_TRIP_STAGE3:
> >+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> >+			lo_code = thresh >>
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE2:
> >+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> >+			lo_code = thresh >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE1:
> >+		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
> >+			lo_code = thresh >>
> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> >+			break;
> >+		}
> >+		/* else fall through */
> >+	case TSENS_TRIP_STAGE0:
> >+	default:
> >+		lo_code = TSENS_THRESHOLD_MIN_CODE;
> >+		break;
> >+	}
> >+
> >+	return lo_code & TSENS_THRESHOLD_MAX_CODE; }
> >+
> >+static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
> >+	int trip, unsigned long temp)
> >+{
> >+	struct tsens_device *tmdev = tm_sensor->tmdev;
> >+	struct regmap_field *status = tmdev->status_field;
> >+	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
> >+	u32 code, hi_code, lo_code, code_err_chk;
> >+
> >+	code_err_chk = code = tsens_tz_mdegC_to_code(temp,
> tm_sensor);
> >+
> >+	regmap_field_read(status, &reg_cntl);
> >+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
> >+
> >+	switch (trip) {
> >+	case TSENS_TRIP_STAGE3:
> >+		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> >+		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE2:
> >+		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE1:
> >+		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+		break;
> >+	case TSENS_TRIP_STAGE0:
> >+		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> >+		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+
> >+	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
> >+	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
> >+
> >+	if (code_err_chk < lo_code || code_err_chk > hi_code)
> >+		return -EINVAL;
> >+
> >+	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR,
> mask, code);
> >+
> >+	return 0;
> >+}
> >+
> >+static int tsens_set_trips(void *_sensor, long low, long high) {
> >+	struct tsens_sensor *tm_sensor = _sensor;
> >+
> >+	tsens_print_trip_temp(tm_sensor);
> >+
> >+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
> >+		return -EINVAL;
> >+
> >+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
> >+		return -EINVAL;
> >+
> >+	return 0;
> >+}
> >+#endif
> >+
> >+static void tsens_scheduler_fn(struct work_struct *work) {
> >+	struct tsens_device *tmdev;
> >+	struct regmap_field *status;
> >+	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
> >+	unsigned long sensor;
> >+	bool upper_th_x, lower_th_x;
> >+
> >+	tmdev = container_of(work, struct tsens_device, tsens_work);
> >+	status = tmdev->status_field;
> >+
> >+	regmap_field_update_bits(status,
> >+			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR,
> >+			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR);
> >+
> >+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
> >+	threshold_low = threshold >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> >+	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
> >+	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> >+	threshold &= TSENS_THRESHOLD_MAX_CODE;
> >+
> >+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
> >+	sensor = bits;
> >+	sensor >>= TSENS_SENSOR0_SHIFT;
> >+	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
> >+		regmap_read(tmdev->map, tmdev->sensor[i].status,
> &code);
> >+		upper_th_x = code >= threshold;
> >+		lower_th_x = code <= threshold_low;
> >+
> >+		if (upper_th_x)
> >+			mask |= TSENS_UPPER_STATUS_CLR;
> >+
> >+		if (lower_th_x)
> >+			mask |= TSENS_LOWER_STATUS_CLR;
> >+
> >+#ifdef THERMAL_TSENS8960_HWTRIPS
> >+		if (upper_th_x || lower_th_x) {
> >+			dev_info(tsens_dev,
> >+				"Threshold reached for sensor(%d)\n", i);
> >+			thermal_zone_device_update(tmdev-
> >sensor[i].tz_dev);
> >+		}
> >+#endif
> >+	}
> >+
> >+	regmap_field_update_bits(status,
> >+			TSENS_UPPER_STATUS_CLR |
> TSENS_LOWER_STATUS_CLR, mask); }
> >+
> >+static irqreturn_t tsens_isr(int irq, void *data) {
> >+	schedule_work(data);
> >+	return IRQ_HANDLED;
> >+}
> >+
> >+#ifdef CONFIG_PM
> >+static int tsens_suspend(struct device *dev) {
> >+	int i;
> >+	struct tsens_device *tmdev = dev_get_drvdata(dev);
> >+	struct regmap *map = tmdev->map;
> >+
> >+	regmap_read(map, TSENS_THRESHOLD_ADDR, &tmdev-
> >pm_tsens_thr_data);
> >+	regmap_read(map, TSENS_CNTL_ADDR, &tmdev->pm_tsens_cntl);
> >+	regmap_update_bits(map, TSENS_CNTL_ADDR,
> >+			   TSENS_8960_SLP_CLK_ENA | TSENS_EN, 0);
> 
> No check for num_sensors here?

Good catch. Thx. Will fix

> >+
> >+	tmdev->prev_reading_avail = 0;
> >+	for (i = 0; i < tmdev->num_sensors; i++)
> >+		tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED;
> >+
> >+	return 0;
> >+}
> >+
> >+static int tsens_resume(struct device *dev) {
> >+	int i;
> >+	unsigned long reg_cntl;
> >+	struct tsens_device *tmdev = dev_get_drvdata(dev);
> >+	struct regmap *map = tmdev->map;
> >+
> >+	regmap_update_bits(map, TSENS_CNTL_ADDR, TSENS_SW_RST,
> TSENS_SW_RST);
> >+	regmap_field_update_bits(tmdev->status_field,
> >+			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK,
> >+			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK);
> >+
> >+	if (tmdev->num_sensors > 1)
> >+		regmap_update_bits(map, TSENS_8960_CONFIG_ADDR,
> >+				TSENS_8960_CONFIG_MASK,
> >+				TSENS_8960_CONFIG);
> 
> What about 8660?
> 

Thx, will fix.

> >+
> >+	regmap_write(map, TSENS_THRESHOLD_ADDR, tmdev-
> >pm_tsens_thr_data);
> >+	regmap_write(map, TSENS_CNTL_ADDR, tmdev->pm_tsens_cntl);
> >+
> >+	reg_cntl = tmdev->pm_tsens_cntl;
> >+	reg_cntl >>= TSENS_SENSOR0_SHIFT;
> >+	for_each_set_bit(i, &reg_cntl, tmdev->num_sensors)
> >+		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
> >+
> >+	return 0;
> >+}
> >+
> >+static const struct dev_pm_ops tsens_pm_ops = {
> >+	.suspend	= tsens_suspend,
> >+	.resume		= tsens_resume,
> >+};
> >+#endif
> >+
> >+static void tsens_disable_mode(const struct tsens_device *tmdev) {
> >+	u32 reg_cntl;
> >+	u32 mask;
> >+
> >+	mask = GENMASK(tmdev->num_sensors - 1, 0);
> >+	mask <<= TSENS_SENSOR0_SHIFT;
> >+	mask |= TSENS_EN;
> >+
> >+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
> >+	reg_cntl &= ~mask;
> >+	if (tmdev->num_sensors > 1)
> >+		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
> >+	else
> >+		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
> >+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl); }
> >+
> >+static void tsens_hw_init(struct tsens_device *tmdev) {
> >+	u32 reg_cntl, reg_thr;
> >+
> >+	reg_cntl = TSENS_SW_RST;
> >+	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR,
> TSENS_SW_RST,
> >+reg_cntl);
> >+
> >+	if (tmdev->num_sensors > 1) {
> >+		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
> >+			(TSENS_MEASURE_PERIOD << 18);
> >+		reg_cntl &= ~TSENS_SW_RST;
> >+		regmap_update_bits(tmdev->map,
> TSENS_8960_CONFIG_ADDR,
> >+				   TSENS_8960_CONFIG_MASK,
> TSENS_8960_CONFIG);
> >+	} else {
> >+		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
> >+			(TSENS_MEASURE_PERIOD << 16);
> >+		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
> >+		reg_cntl |= TSENS_8660_CONFIG <<
> TSENS_8660_CONFIG_SHIFT;
> >+	}
> >+
> >+	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) <<
> TSENS_SENSOR0_SHIFT;
> >+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> >+
> >+	regmap_field_update_bits(tmdev->status_field,
> >+			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR |
> >+			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK,
> >+			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR |
> >+			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK);
> >+
> >+	reg_cntl |= TSENS_EN;
> >+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> >+
> >+	reg_thr = (TSENS_LOWER_LIMIT_TH <<
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
> >+		(TSENS_UPPER_LIMIT_TH <<
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
> >+		(TSENS_MIN_LIMIT_TH <<
> TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
> >+		(TSENS_MAX_LIMIT_TH <<
> TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
> >+	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr); }
> >+
> >+static int
> >+tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap
> >+*map) {
> >+	u32 temp_data, data;
> >+	struct tsens_sensor *s = &tmdev->sensor[0];
> >+
> >+	if (regmap_read(map, tmdev->calib_offset, &temp_data))
> >+		return -EINVAL;
> >+
> >+	data = (temp_data >> 24) & 0xff;
> >+
> >+	if (!data) {
> >+		dev_err(tsens_dev, "QFPROM TSENS calibration data not
> present\n");
> >+		return -EINVAL;
> >+	}
> >+
> >+	s->offset = TSENS_CAL_MDEGC - s->slope * data;
> >+
> >+	return 0;
> >+}
> >+
> >+static int
> >+tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap
> >+*map) {
> >+	int i;
> >+	u32 temp_data[TSENS_MAX_SENSORS];
> >+	u8 *byte_data;
> >+	u32 fuse, redun, num_read;
> >+	struct tsens_sensor *s = tmdev->sensor;
> >+
> >+	fuse = tmdev->calib_offset;
> >+	redun = tmdev->backup_calib_offset;
> >+
> >+	/**
> >+	* syscon regmap is 32-bit data, but calibration data is 8-bit.
> >+	* read 4 bytes from regmap in a loop and then extract bytes
> seprately
> >+	*/
> >+
> >+	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
> >+
> >+	for (i = 0; i < num_read; i++) {
> >+		if (regmap_read(map, (redun + i*4), &temp_data[i]))
> >+			return -EINVAL;
> >+
> >+		if (!temp_data[i]) {
> >+			dev_dbg(tsens_dev, "Main calib data not valid\n");
> >+			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
> >+				return -EINVAL;
> >+		}
> >+	}
> >+
> >+	byte_data = (u8 *)temp_data;
> >+
> >+	for (i = 0; i < tmdev->num_sensors; i++, s++) {
> >+		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
> >+		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
> >+	}
> >+
> >+	return 0;
> >+}
> >+
> >+static const struct thermal_zone_of_device_ops tsens_thermal_of_ops =
> {
> >+	.get_temp = tsens_tz_get_temp,
> >+};
> >+
> >+
> >+static int tsens_register(struct tsens_device *tmdev, int i) {
> >+	char name[18];
> >+	u32 addr = TSENS_S0_STATUS_ADDR;
> >+	struct tsens_sensor *s = &tmdev->sensor[i];
> >+
> >+	/*
> >+	* 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.
> >+	*/
> >+	if (i >= 5)
> >+		addr += 40;
> >+
> >+	addr += i * 4;
> >+
> >+	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
> >+	s->mode = THERMAL_DEVICE_ENABLED;
> >+	s->sensor_num = i;
> >+	s->status = addr;
> >+	s->tmdev = tmdev;
> >+	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
> >+						&tsens_thermal_of_ops);
> >+
> >+	if (IS_ERR(s->tz_dev))
> >+		return -ENODEV;
> >+
> >+	return 0;
> >+}
> >+
> >+static int tsens_probe(struct platform_device *pdev) {
> >+	struct device_node *np = pdev->dev.of_node;
> >+	struct device_node *base_node;
> >+	struct platform_device *base_pdev;
> >+	int ret, i, irq, num;
> >+	struct tsens_sensor *s;
> >+	struct tsens_device *tmdev;
> >+	struct regmap *map, *imem_regmap;
> >+	struct reg_field *field;
> >+	static struct reg_field status_0 = {
> >+		.reg = TSENS_8064_STATUS_CNTL,
> >+		.lsb = 0,
> >+		.msb = 3,
> >+	};
> >+	static struct reg_field status_8 = {
> >+		.reg = TSENS_CNTL_ADDR,
> >+		.lsb = 8,
> >+		.msb = 11,
> >+	};
> >+
> >+	tsens_dev = &pdev->dev;
> >+
> >+	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
> >+	if (num <= 0) {
> >+		dev_err(tsens_dev, "invalid tsens slopes\n");
> >+		return -EINVAL;
> >+	}
> >+
> >+	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
> >+			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
> 
> Any particular reason, the sensors are appended to the tmdev, instead of
> allocating separately?
>

Avoid two separate allocs, so less fragmentation and do not see a downside
as such.
(original idea is from Stephen and carried forwarded as it looked elegant)
 
> >+	if (tmdev == NULL)
> >+		return -ENOMEM;
> >+
> >+	tmdev->num_sensors = num;
> >+	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
> >+		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
> >+					   &s->slope);
> >+
> >+	irq = platform_get_irq(pdev, 0);
> >+	if (irq < 0) {
> >+		dev_err(tsens_dev,  "no irq resource?\n");
> >+		return  -EINVAL;
> >+	}
> >+
> >+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
> >+					   &tmdev->calib_offset);
> >+	if (ret != 0) {
> >+		dev_err(tsens_dev,  "No calibration offset set\n");
> >+		return  -EINVAL;
> >+	}
> >+
> >+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
> >+					   &tmdev->backup_calib_offset);
> >+	if (ret) {
> >+		dev_info(tsens_dev, "Missing backup calibration offset\n");
> >+		tmdev->backup_calib_offset = tmdev->calib_offset;
> >+	}
> >+
> >+	imem_regmap = syscon_regmap_lookup_by_phandle(np,
> "qcom,imem");
> >+	if (IS_ERR(imem_regmap)) {
> >+		dev_err(tsens_dev, "syscon regmap look up error\n");
> >+		return PTR_ERR(imem_regmap);
> >+	}
> >+
> >+	if (num == 1)
> >+		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
> >+	else
> >+		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
> >+
> >+	if (ret < 0) {
> >+		dev_err(tsens_dev, "tsense calibration failed\n");
> >+		return ret;
> >+	}
> >+
> >+	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
> >+	if (base_node == NULL) {
> >+		dev_err(tsens_dev, "no base node present\n");
> >+		return  -EINVAL;
> >+	}
> >+
> >+	base_pdev = of_find_device_by_node(base_node);
> >+	if (base_pdev == NULL) {
> >+		dev_err(tsens_dev, "no base pdev node\n");
> >+		return  -ENODEV;
> >+	}
> >+
> >+	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
> >+	if (map == NULL) {
> >+		dev_err(tsens_dev, "base regmap get failed\n");
> >+		return  -ENODEV;
> >+	}
> >+
> >+	/* Status bits move when the sensor bits next to them overlap */
> >+	if (num > 5)
> >+		field = &status_0;
> >+	else
> >+		field = &status_8;
> >+
> >+	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map,
> *field);
> >+	if (IS_ERR(tmdev->status_field)) {
> >+		dev_err(tsens_dev, "regmap alloc failed\n");
> >+		return PTR_ERR(tmdev->status_field);
> >+	}
> >+
> >+	tsens_hw_init(tmdev);
> >+
> >+	/*
> >+	 * Register sensor 0 separately. This sensor is always
> >+	 * expected to be present and if this fails, thermal
> >+	 * sensor probe would fail
> >+	 * Other sensors are optional and if registration fails
> >+	 * disable the sensor and continue
> >+	*/
> >+	ret = tsens_register(tmdev, 0);
> >+	if (ret < 0) {
> >+		dev_err(tsens_dev, "Registering failed for primary sensor");
> >+		ret = -ENODEV;
> >+		goto fail;
> >+	} else {
> >+		tsens_tz_set_mode(&tmdev->sensor[0],
> THERMAL_DEVICE_ENABLED);
> >+	}
> >+
> >+	for (i = 1;  i < tmdev->num_sensors; i++) {
> >+		ret = tsens_register(tmdev, i);
> >+
> >+		if (ret < 0) {
> >+			dev_err(tsens_dev,
> >+				"Registering failed. Sensor(%i), disabled",
i);
> >+			tsens_tz_set_mode(&tmdev->sensor[i],
> >+				THERMAL_DEVICE_DISABLED);
> >+		} else {
> >+			tsens_tz_set_mode(&tmdev->sensor[i],
> >+				THERMAL_DEVICE_ENABLED);
> >+		}
> >+	}
> >+
> >+	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
> >+
> >+	ret = devm_request_irq(tsens_dev, irq, tsens_isr,
> IRQF_TRIGGER_RISING,
> >+			       "tsens", &tmdev->tsens_work);
> >+	if (ret < 0)
> >+		goto err_irq;
> >+
> >+	platform_set_drvdata(pdev, tmdev);
> >+
> >+	dev_info(tsens_dev, "Tsens driver initialized\n");
> >+
> >+	return 0;
> >+err_irq:
> >+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> >+		thermal_zone_of_sensor_unregister(&pdev->dev, s-
> >tz_dev);
> >+fail:
> >+	tsens_disable_mode(tmdev);
> >+	return ret;
> >+}
> >+
> >+static int tsens_remove(struct platform_device *pdev) {
> >+	int i;
> >+	struct tsens_sensor *s;
> >+	struct tsens_device *tmdev = platform_get_drvdata(pdev);
> >+
> >+	tsens_disable_mode(tmdev);
> >+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> >+		thermal_zone_device_unregister(s->tz_dev);
> >+
> >+	return 0;
> >+}
> >+
> >+static struct of_device_id tsens_match_table[] = {
> >+	{.compatible = "qcom,ipq806x-tsens"},
> >+	{},
> >+};
> >+
> >+MODULE_DEVICE_TABLE(of, tsens_match_table);
> >+
> >+static struct platform_driver tsens_driver = {
> >+	.probe = tsens_probe,
> >+	.remove = tsens_remove,
> >+	.driver = {
> >+		.of_match_table = tsens_match_table,
> >+		.name = "tsens8960-thermal",
> >+		.owner = THIS_MODULE,
> >+#ifdef CONFIG_PM
> >+		.pm	= &tsens_pm_ops,
> >+#endif
> >+	},
> >+};
> >+module_platform_driver(tsens_driver);
> >+
> >+MODULE_LICENSE("GPL v2");
> >+MODULE_DESCRIPTION("Temperature Sensor driver");
> >+MODULE_ALIAS("platform:tsens8960-tm");
> >--
> >Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc.
> >is a member of the Code Aurora Forum, a Linux Foundation Collaborative
> >Project
> >
> >--
> >To unsubscribe from this list: send the line "unsubscribe linux-pm" in
> >the body of a message to majordomo@vger.kernel.org More majordomo
> info
> >at  http://vger.kernel.org/majordomo-info.html

-- Naren

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Narendran Rajan Jan. 30, 2015, 1:36 a.m. UTC | #9
Thank you Stephen for the detail comments.

> -----Original Message-----
> From: linux-arm-msm-owner@vger.kernel.org [mailto:linux-arm-msm-
> owner@vger.kernel.org] On Behalf Of Stephen Boyd
> Sent: Wednesday, January 28, 2015 5:46 PM
> To: Narendran Rajan
> Cc: Zhang Rui; Eduardo Valentin; Linux ARM MSM; Linux PM; Siddartha
> Mohanadoss
> Subject: Re: [PATCH] thermal: Add msm tsens thermal sensor driver
> 
> On 01/26, Narendran Rajan wrote:
> > TSENS supports reading temperature from multiple thermal sensors
> > present in QCOM SOCs.
> > TSENS HW is enabled only when the main sensor is requested.
> > The TSENS block is disabled if the main senors is disabled
> > irrespective of any other sensors that are being enabled.
> > TSENS driver supports configurable threshold for temperature
> > monitoring in which case it can generate an interrupt when specific
> > thresholds are reached
> >
> > Based on code by Siddartha Mohanadoss and Stephen Boyd.
> 
> So as far as I can tell you removed my loop TODOs, added debug prints and
> the device singleton, put the calibration data offset into DT (another
TODO),
> used regmap on the qfprom, made this into a standalone device instead of a
> child of GCC, and moved to OF thermal zones (forcing that
> THERMAL_TSENS8960_HWTRIPS ifdef I
> suppose?) Anything else?
>

Let me please know if you prefer a different attribution, would be glad to
add.
 
> >
> > Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> > Cc: Stephen Boyd <sboyd@codeaurora.org>
> > Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
> 
> Something is wrong with your git. There should only be one space between
> your name and the email id.
> 

Good eyes :-)
Thx, I did notice just after a I sent the mail. Fixed it internally, will be
corrected in next patch.

> > ---
> > diff --git a/drivers/thermal/msm8960_tsens.c
> > b/drivers/thermal/msm8960_tsens.c new file mode 100644 index
> > 0000000..307bdc8
> > --- /dev/null
> > +++ b/drivers/thermal/msm8960_tsens.c
> > +
> > +static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh) {
> > +	u32 hi_code;
> > +
> > +	switch (trip) {
> > +	case TSENS_TRIP_STAGE0:
> > +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> > +			hi_code = thresh >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE1:
> > +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> > +			hi_code = thresh >>
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE2:
> > +		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
> > +			hi_code = thresh >>
> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE3:
> > +	default:
> > +		hi_code = TSENS_THRESHOLD_MAX_CODE;
> > +		break;
> > +	}
> > +
> > +	return hi_code & TSENS_THRESHOLD_MAX_CODE; }
> > +
> > +static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh) {
> > +	u32 lo_code;
> > +
> > +	switch (trip) {
> > +	case TSENS_TRIP_STAGE3:
> > +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> > +			lo_code = thresh >>
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE2:
> > +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> > +			lo_code = thresh >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE1:
> > +		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
> > +			lo_code = thresh >>
> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE0:
> > +	default:
> > +		lo_code = TSENS_THRESHOLD_MIN_CODE;
> > +		break;
> > +	}
> > +
> > +	return lo_code & TSENS_THRESHOLD_MAX_CODE; }
> 
> I wonder if these can be loops, or something on top of find_next_bit()?
> That's what my TODO was about, hoping to reduce these functions even
> further.
> 
This looked pretty readable enough. Let me see find_next_bit helps. Thx

> > +
> > +static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
> > +	int trip, unsigned long temp)
> > +{
> > +	struct tsens_device *tmdev = tm_sensor->tmdev;
> > +	struct regmap_field *status = tmdev->status_field;
> > +	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
> > +	u32 code, hi_code, lo_code, code_err_chk;
> > +
> > +	code_err_chk = code = tsens_tz_mdegC_to_code(temp,
> tm_sensor);
> > +
> > +	regmap_field_read(status, &reg_cntl);
> > +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
> > +
> > +	switch (trip) {
> > +	case TSENS_TRIP_STAGE3:
> > +		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> > +		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE2:
> > +		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE1:
> > +		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE0:
> > +		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> > +		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
> > +	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
> > +
> > +	if (code_err_chk < lo_code || code_err_chk > hi_code)
> > +		return -EINVAL;
> > +
> > +	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR,
> mask, code);
> > +
> > +	return 0;
> > +}
> > +
> > +static int tsens_set_trips(void *_sensor, long low, long high)
> 
> Is this function even used?
> 

Yes, only when HW trip path is present.  Let me remove this as you suggested
in previous comment 
And have only polling mode.

> > +{
> > +	struct tsens_sensor *tm_sensor = _sensor;
> > +
> > +	tsens_print_trip_temp(tm_sensor);
> > +
> > +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
> > +		return -EINVAL;
> > +
> > +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
> > +		return -EINVAL;
> > +
> > +	return 0;
> > +}
> > +#endif
> > +
> > +static void tsens_scheduler_fn(struct work_struct *work) {
> > +	struct tsens_device *tmdev;
> > +	struct regmap_field *status;
> > +	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
> > +	unsigned long sensor;
> > +	bool upper_th_x, lower_th_x;
> > +
> > +	tmdev = container_of(work, struct tsens_device, tsens_work);
> > +	status = tmdev->status_field;
> > +
> > +	regmap_field_update_bits(status,
> > +			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR,
> > +			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR);
> > +
> > +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
> > +	threshold_low = threshold >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
> > +	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +	threshold &= TSENS_THRESHOLD_MAX_CODE;
> > +
> > +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
> > +	sensor = bits;
> > +	sensor >>= TSENS_SENSOR0_SHIFT;
> > +	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
> > +		regmap_read(tmdev->map, tmdev->sensor[i].status,
> &code);
> > +		upper_th_x = code >= threshold;
> > +		lower_th_x = code <= threshold_low;
> > +
> > +		if (upper_th_x)
> > +			mask |= TSENS_UPPER_STATUS_CLR;
> > +
> > +		if (lower_th_x)
> > +			mask |= TSENS_LOWER_STATUS_CLR;
> > +
> > +#ifdef THERMAL_TSENS8960_HWTRIPS
> > +		if (upper_th_x || lower_th_x) {
> > +			dev_info(tsens_dev,
> > +				"Threshold reached for sensor(%d)\n", i);
> 
> This looks like debug noise. Please remove.

Will do (in fact plan to remove interrupt mode completely)
> 
> > +			thermal_zone_device_update(tmdev-
> >sensor[i].tz_dev);
> > +		}
> > +#endif
> > +	}
> > +
> > +	regmap_field_update_bits(status,
> > +			TSENS_UPPER_STATUS_CLR |
> TSENS_LOWER_STATUS_CLR, mask); }
> > +
> > +static irqreturn_t tsens_isr(int irq, void *data) {
> > +	schedule_work(data);
> 
> I was going to move this to threaded irqs, please do that.
> 
I guess in a polling mode, this will not be needed. Will remove this
completely.

> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static void tsens_disable_mode(const struct tsens_device *tmdev) {
> > +	u32 reg_cntl;
> > +	u32 mask;
> > +
> > +	mask = GENMASK(tmdev->num_sensors - 1, 0);
> > +	mask <<= TSENS_SENSOR0_SHIFT;
> > +	mask |= TSENS_EN;
> > +
> > +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
> > +	reg_cntl &= ~mask;
> > +	if (tmdev->num_sensors > 1)
> > +		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
> > +	else
> > +		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
> > +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl); }
> > +
> > +static void tsens_hw_init(struct tsens_device *tmdev) {
> > +	u32 reg_cntl, reg_thr;
> > +
> > +	reg_cntl = TSENS_SW_RST;
> > +	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR,
> TSENS_SW_RST,
> > +reg_cntl);
> > +
> > +	if (tmdev->num_sensors > 1) {
> > +		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
> > +			(TSENS_MEASURE_PERIOD << 18);
> 
> Unnecessary ()?

Ok, Srini also wanted not to hardcode '18' (and '16' below). Will redefine
TSENS_MEASURE_PERIOD_8960 as BIT(18)

> 
> > +		reg_cntl &= ~TSENS_SW_RST;
> > +		regmap_update_bits(tmdev->map,
> TSENS_8960_CONFIG_ADDR,
> > +				   TSENS_8960_CONFIG_MASK,
> TSENS_8960_CONFIG);
> > +	} else {
> > +		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
> > +			(TSENS_MEASURE_PERIOD << 16);
> 
> Unnecessary ()?


Ok, Srini also wanted not to hardcode '18' (and '16' below). Will redefine
TSENS_MEASURE_PERIOD_8960 as BIT(16)

> 
> > +		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
> > +		reg_cntl |= TSENS_8660_CONFIG <<
> TSENS_8660_CONFIG_SHIFT;
> > +	}
> > +
> > +	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) <<
> TSENS_SENSOR0_SHIFT;
> > +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> > +
> > +	regmap_field_update_bits(tmdev->status_field,
> > +			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR |
> > +			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK,
> > +			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR |
> > +			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK);
> > +
> > +	reg_cntl |= TSENS_EN;
> > +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> > +
> > +	reg_thr = (TSENS_LOWER_LIMIT_TH <<
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
> > +		(TSENS_UPPER_LIMIT_TH <<
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
> > +		(TSENS_MIN_LIMIT_TH <<
> TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
> > +		(TSENS_MAX_LIMIT_TH <<
> TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
> > +	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr); }
> > +
> > +static int
> > +tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap
> > +*map) {
> > +	u32 temp_data, data;
> > +	struct tsens_sensor *s = &tmdev->sensor[0];
> > +
> > +	if (regmap_read(map, tmdev->calib_offset, &temp_data))
> > +		return -EINVAL;
> > +
> > +	data = (temp_data >> 24) & 0xff;
> > +
> > +	if (!data) {
> > +		dev_err(tsens_dev, "QFPROM TSENS calibration data not
> present\n");
> 
> Ah we do use it for an error here. It isn't hard to put a struct device
inside
> tmdev...

Agree (did look around some drivers and they seem to have used). Anyways, I
agree
It's a bad practice. Will correct

> 
> > +		return -EINVAL;
> > +	}
> > +
> > +	s->offset = TSENS_CAL_MDEGC - s->slope * data;
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap
> > +*map) {
> > +	int i;
> > +	u32 temp_data[TSENS_MAX_SENSORS];
> > +	u8 *byte_data;
> > +	u32 fuse, redun, num_read;
> > +	struct tsens_sensor *s = tmdev->sensor;
> > +
> > +	fuse = tmdev->calib_offset;
> > +	redun = tmdev->backup_calib_offset;
> > +
> > +	/**
> > +	* syscon regmap is 32-bit data, but calibration data is 8-bit.
> > +	* read 4 bytes from regmap in a loop and then extract bytes
> seprately
> > +	*/
> 
> Weird comment style. Please do it like
> 

Thx. Will correct.

>  /*
>   * This because this isn't kernel-doc.
>   */
> 
> > +
> > +	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
> > +
> > +	for (i = 0; i < num_read; i++) {
> > +		if (regmap_read(map, (redun + i*4), &temp_data[i]))
> > +			return -EINVAL;
> > +
> > +		if (!temp_data[i]) {
> > +			dev_dbg(tsens_dev, "Main calib data not valid\n");
> > +			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
> > +				return -EINVAL;
> > +		}
> > +	}
> > +
> > +	byte_data = (u8 *)temp_data;
> > +
> > +	for (i = 0; i < tmdev->num_sensors; i++, s++) {
> > +		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
> > +		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
> > +	}
> > +
> > +	return 0;
> > +}
> 
> This is a lot of hoops to jump through just to use a syscon regmap (does
this
> even work in big-endian mode?). Given your point about consumers wanting
> to read with different strides makes me think that using a regmap is not a
> good idea. It would be better if we had an API that could be used to read
an
> arbitrary number of bytes from an eeprom.
>

I guess Srini's solution is elegant enough. It allows to set stride values
and then helper 
Functions to read the number of bytes needed.
 
> > +
> > +static const struct thermal_zone_of_device_ops tsens_thermal_of_ops =
> {
> > +	.get_temp = tsens_tz_get_temp,
> > +};
> > +
> > +
> 
> Nitpick: Two newlines instead of one.
> 
> > +static int tsens_register(struct tsens_device *tmdev, int i) {
> > +	char name[18];
> > +	u32 addr = TSENS_S0_STATUS_ADDR;
> > +	struct tsens_sensor *s = &tmdev->sensor[i];
> > +
> > +	/*
> > +	* 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.
> > +	*/
> > +	if (i >= 5)
> > +		addr += 40;
> > +
> > +	addr += i * 4;
> > +
> > +	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
> > +	s->mode = THERMAL_DEVICE_ENABLED;
> > +	s->sensor_num = i;
> > +	s->status = addr;
> > +	s->tmdev = tmdev;
> > +	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
> > +						&tsens_thermal_of_ops);
> > +
> > +	if (IS_ERR(s->tz_dev))
> > +		return -ENODEV;
> 
> So if it's an error pointer we leave it there? And then if an interrupt
arrives
> and this sensor has a new temperature to report we'll update the thermal
> framework with an invalid pointer? When I wrote this code I didn't allow
> probe to continue if any of these zones failed to register. Why change
that
> because we're using the sensor API?
> 

When the registration fails, the sensor is not enabled (its explicitly
disabled in the code). 
The interrupts by design is common across all sensors. Primary sensor
registration fail means
Probe fail and the code deregisters from thermal framework.

The registration of other sensors can fail as there may not a dt entry for
that sensor. 
This is valid in mind as the system integrator may prefer (based on thermal
profiling) to enable
only a few sensors out of total N and monitor only them.

> > +
> > +	return 0;
> > +}
> > +
> > +static int tsens_probe(struct platform_device *pdev) {
> > +	struct device_node *np = pdev->dev.of_node;
> > +	struct device_node *base_node;
> > +	struct platform_device *base_pdev;
> > +	int ret, i, irq, num;
> > +	struct tsens_sensor *s;
> > +	struct tsens_device *tmdev;
> > +	struct regmap *map, *imem_regmap;
> > +	struct reg_field *field;
> > +	static struct reg_field status_0 = {
> > +		.reg = TSENS_8064_STATUS_CNTL,
> > +		.lsb = 0,
> > +		.msb = 3,
> > +	};
> > +	static struct reg_field status_8 = {
> > +		.reg = TSENS_CNTL_ADDR,
> > +		.lsb = 8,
> > +		.msb = 11,
> > +	};
> > +
> > +	tsens_dev = &pdev->dev;
> > +
> > +	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
> > +	if (num <= 0) {
> > +		dev_err(tsens_dev, "invalid tsens slopes\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
> > +			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
> > +	if (tmdev == NULL)
> > +		return -ENOMEM;
> > +
> > +	tmdev->num_sensors = num;
> > +	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
> > +		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
> > +					   &s->slope);
> > +
> > +	irq = platform_get_irq(pdev, 0);
> > +	if (irq < 0) {
> > +		dev_err(tsens_dev,  "no irq resource?\n");
> > +		return  -EINVAL;
> > +	}
> > +
> > +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
> > +					   &tmdev->calib_offset);
> > +	if (ret != 0) {
> > +		dev_err(tsens_dev,  "No calibration offset set\n");
> > +		return  -EINVAL;
> > +	}
> > +
> > +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
> > +					   &tmdev->backup_calib_offset);
> > +	if (ret) {
> > +		dev_info(tsens_dev, "Missing backup calibration offset\n");
> 
> Why make any noise at all? A backup is here.

Ok, will move to dbg.
> 
> > +		tmdev->backup_calib_offset = tmdev->calib_offset;
> > +	}
> > +
> > +	imem_regmap = syscon_regmap_lookup_by_phandle(np,
> "qcom,imem");
> > +	if (IS_ERR(imem_regmap)) {
> > +		dev_err(tsens_dev, "syscon regmap look up error\n");
> > +		return PTR_ERR(imem_regmap);
> > +	}
> > +
> > +	if (num == 1)
> > +		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
> > +	else
> > +		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
> > +
> > +	if (ret < 0) {
> > +		dev_err(tsens_dev, "tsense calibration failed\n");
> > +		return ret;
> > +	}
> > +
> > +	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
> > +	if (base_node == NULL) {
> 
> Kernel style is typically
> 

Thx, will correct

>  if (!base_node)
> 
> > +		dev_err(tsens_dev, "no base node present\n");
> > +		return  -EINVAL;
> > +	}
> > +
> > +	base_pdev = of_find_device_by_node(base_node);
> > +	if (base_pdev == NULL) {
> > +		dev_err(tsens_dev, "no base pdev node\n");
> > +		return  -ENODEV;
> > +	}
> > +
> > +	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
> > +	if (map == NULL) {
> > +		dev_err(tsens_dev, "base regmap get failed\n");
> > +		return  -ENODEV;
> > +	}
> > +
> > +	/* Status bits move when the sensor bits next to them overlap */
> > +	if (num > 5)
> > +		field = &status_0;
> > +	else
> > +		field = &status_8;
> > +
> > +	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map,
> *field);
> > +	if (IS_ERR(tmdev->status_field)) {
> > +		dev_err(tsens_dev, "regmap alloc failed\n");
> > +		return PTR_ERR(tmdev->status_field);
> > +	}
> > +
> > +	tsens_hw_init(tmdev);
> > +
> > +	/*
> > +	 * Register sensor 0 separately. This sensor is always
> > +	 * expected to be present and if this fails, thermal
> > +	 * sensor probe would fail
> > +	 * Other sensors are optional and if registration fails
> > +	 * disable the sensor and continue
> > +	*/
> > +	ret = tsens_register(tmdev, 0);
> > +	if (ret < 0) {
> > +		dev_err(tsens_dev, "Registering failed for primary sensor");
> > +		ret = -ENODEV;
> > +		goto fail;
> > +	} else {
> > +		tsens_tz_set_mode(&tmdev->sensor[0],
> THERMAL_DEVICE_ENABLED);
> > +	}
> > +
> > +	for (i = 1;  i < tmdev->num_sensors; i++) {
> > +		ret = tsens_register(tmdev, i);
> > +
> > +		if (ret < 0) {
> > +			dev_err(tsens_dev,
> > +				"Registering failed. Sensor(%i), disabled",
i);
> 
> Missing newline...

Thx, will fix

> 
> > +			tsens_tz_set_mode(&tmdev->sensor[i],
> > +				THERMAL_DEVICE_DISABLED);
> 
> Does this do anything? The mode is already THERMAL_DEVICE_DISABLED so I
> imagine it just bails out immediately?

Didn't want to rely on default.  As noted above, the registration for non
Primary sensors are allowed to fail and the driver just mark those sensors 
As disabled and continue.

> 
> > +		} else {
> > +			tsens_tz_set_mode(&tmdev->sensor[i],
> > +				THERMAL_DEVICE_ENABLED);
> > +		}
> > +	}
> > +
> > +	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
> > +
> > +	ret = devm_request_irq(tsens_dev, irq, tsens_isr,
> IRQF_TRIGGER_RISING,
> > +			       "tsens", &tmdev->tsens_work);
> > +	if (ret < 0)
> > +		goto err_irq;
> > +
> > +	platform_set_drvdata(pdev, tmdev);
> > +
> > +	dev_info(tsens_dev, "Tsens driver initialized\n");
> 
> Please remove.
> 

Less boot up noise ? Ok.

> > +
> > +	return 0;
> > +err_irq:
> > +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> 
> I should have made this simpler like:
> 
>   for (s = tmdev->sensor; s < tmdev->sensor + tmdev->num_sensors; s++)
> 
> > +		thermal_zone_of_sensor_unregister(&pdev->dev, s-
> >tz_dev);
> > +fail:
> > +	tsens_disable_mode(tmdev);
> > +	return ret;
> > +}
> > +
> > +static int tsens_remove(struct platform_device *pdev) {
> > +	int i;
> 
> Because then we can save a whole variable here.
> 
> > +	struct tsens_sensor *s;
> > +	struct tsens_device *tmdev = platform_get_drvdata(pdev);
> > +
> > +	tsens_disable_mode(tmdev);
> > +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> > +		thermal_zone_device_unregister(s->tz_dev);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct of_device_id tsens_match_table[] = {
> > +	{.compatible = "qcom,ipq806x-tsens"},
> > +	{},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, tsens_match_table);
> 
> I would prefer we leave it as a child of GCC like I had done in my
original
> patch. The one ugly part was passing the DT node to the virtual tsens
device
> so that it could figure out the number of sensors, etc. We should be able
to
> fix that by creating the device, assigning the of_node, and then
registering
> the device with the platform bus.
> 

Ok - personally the existing one looked cleaner, but I agree with both you
and Srini.
Will change to GCC child node.

> > +
> > +static struct platform_driver tsens_driver = {
> > +	.probe = tsens_probe,
> > +	.remove = tsens_remove,
> > +	.driver = {
> > +		.of_match_table = tsens_match_table,
> > +		.name = "tsens8960-thermal",
> 
> A better name may be tsens-tm or qcom-tsens. The same version of the
> hardware exists in so many different SoCs there really isn't anything SoC
> specific about the hardware. The SoC difference is almost entirely in the
> qfprom layout, except for when the hw engineers decide to put a different
> number of sensors on the chip. The same comment applies to the file name.
> Probably qcom-tsens.c or something.
>

Looks like this is the consensus, so will change to qcom-tsens.

> > +		.owner = THIS_MODULE,
> > +#ifdef CONFIG_PM
> > +		.pm	= &tsens_pm_ops,
> > +#endif
> > +	},
> > +};
> > +module_platform_driver(tsens_driver);
> > +
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_DESCRIPTION("Temperature Sensor driver");
> > +MODULE_ALIAS("platform:tsens8960-tm");
> 
> --
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a
> Linux Foundation Collaborative Project
> --
> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
in
> the body of a message to majordomo@vger.kernel.org More majordomo
> info at  http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Narendran Rajan Jan. 30, 2015, 1:42 a.m. UTC | #10
> -----Original Message-----
> From: Eduardo Valentin [mailto:edubezval@gmail.com]
> Sent: Thursday, January 29, 2015 2:49 PM
> To: Narendran Rajan
> Cc: Zhang Rui; Linux ARM MSM; Linux PM; Siddartha Mohanadoss; Stephen
> Boyd
> Subject: Re: [PATCH] thermal: Add msm tsens thermal sensor driver
> 
> On Mon, Jan 26, 2015 at 08:09:46PM -0800, Narendran Rajan wrote:
> > TSENS supports reading temperature from multiple thermal sensors
> > present in QCOM SOCs.
> > TSENS HW is enabled only when the main sensor is requested.
> > The TSENS block is disabled if the main senors is disabled
> > irrespective of any other sensors that are being enabled.
> > TSENS driver supports configurable threshold for temperature
> > monitoring in which case it can generate an interrupt when specific
> > thresholds are reached
> >
> > Based on code by Siddartha Mohanadoss and Stephen Boyd.
> >
> > Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> > Cc: Stephen Boyd <sboyd@codeaurora.org>
> > Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
> > ---
> >  drivers/thermal/Kconfig         |  14 +
> >  drivers/thermal/Makefile        |   1 +
> >  drivers/thermal/msm8960_tsens.c | 841
> > ++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 856 insertions(+)
> >  create mode 100644 drivers/thermal/msm8960_tsens.c
> >
> > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index
> > 20915ca..a4cb2c0 100644
> > --- a/drivers/thermal/Kconfig
> > +++ b/drivers/thermal/Kconfig
> > @@ -261,6 +261,20 @@ config INTEL_SOC_DTS_THERMAL
> >  	  notification methods.The other trip is a critical trip point,
which
> >  	  was set by the driver based on the TJ MAX temperature.
> >
> > +config THERMAL_TSENS8960
> > +	tristate "Qualcomm 8960 Tsens Temperature driver"
> > +	depends on THERMAL
> > +	depends on ARCH_QCOM
> > +	help
> > +	  QCOM tsens thermal driver provides support for Temperature
> sensor
> > +	  (TSENS) found on QCOM SoCs. It supports four configurable trip
> points
> > +	  and controls multiple sensors on the SOC. The four trip points are
> > +	  common across all sensors present in the SoC. The number of
> sensors
> > +	  present vary from chip to chip and are set through device tree
> entry.
> > +	  The driver presents as a standard thermal zone device with
> configurable
> > +	  trip points and cooling device mapping through standard thermal
> zone
> > +	  device tree
> > +
> >  menu "ACPI INT340X thermal drivers"
> >  source drivers/thermal/int340x_thermal/Kconfig
> >  endmenu
> > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index
> > fa0dc48..23c7a34 100644
> > --- a/drivers/thermal/Makefile
> > +++ b/drivers/thermal/Makefile
> > @@ -39,3 +39,4 @@ obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-
> thermal/
> >  obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
> >  obj-$(CONFIG_ST_THERMAL)	+= st/
> >  obj-$(CONFIG_TEGRA_SOCTHERM)	+= tegra_soctherm.o
> > +obj-$(CONFIG_THERMAL_TSENS8960)	+= msm8960_tsens.o
> > diff --git a/drivers/thermal/msm8960_tsens.c
> > b/drivers/thermal/msm8960_tsens.c new file mode 100644 index
> > 0000000..307bdc8
> > --- /dev/null
> > +++ b/drivers/thermal/msm8960_tsens.c
> > @@ -0,0 +1,841 @@
> > +/* 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/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/thermal.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/delay.h>
> > +#include <linux/slab.h>
> > +#include <linux/err.h>
> > +#include <linux/pm.h>
> > +#include <linux/bitops.h>
> > +#include <linux/regmap.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_platform.h>
> > +#include <linux/io.h>
> > +#include <linux/mfd/syscon.h>
> 
> 
> Please review your includes, remove those that are not needed.
>

Will do.
 
> > +
> > +/* Trips: from very hot to very cold */ enum tsens_trip_type {
> > +	TSENS_TRIP_STAGE3,
> > +	TSENS_TRIP_STAGE2,
> > +	TSENS_TRIP_STAGE1,
> > +	TSENS_TRIP_STAGE0,
> > +	TSENS_TRIP_NUM,
> > +};
> 
> Why is the above needed?
> 
> > +
> > +#define TSENS_CAL_MDEGC				30000
> > +
> > +#define TSENS_MAX_SENSORS			11
> > +
> > +#define TSENS_8960_CONFIG_ADDR			0x3640
> > +#define TSENS_8960_CONFIG			0x9b
> > +#define TSENS_8960_CONFIG_MASK			0xf
> > +
> > +#define TSENS_CNTL_ADDR				0x3620
> > +#define TSENS_CNTL_RESUME_MASK			0xfffffff9
> > +#define TSENS_EN				BIT(0)
> > +#define TSENS_SW_RST				BIT(1)
> > +#define SENSOR0_EN				BIT(3)
> > +#define TSENS_MIN_STATUS_MASK			BIT(0)
> > +#define TSENS_LOWER_STATUS_CLR			BIT(1)
> > +#define TSENS_UPPER_STATUS_CLR			BIT(2)
> > +#define TSENS_MAX_STATUS_MASK			BIT(3)
> > +#define TSENS_MEASURE_PERIOD			1
> > +#define TSENS_8960_SLP_CLK_ENA			BIT(26)
> > +#define TSENS_8660_SLP_CLK_ENA			BIT(24)
> > +#define TSENS_8064_STATUS_CNTL			0x3660
> > +
> > +#define TSENS_THRESHOLD_ADDR			0x3624
> > +#define TSENS_THRESHOLD_MAX_CODE		0xff
> > +#define TSENS_THRESHOLD_MIN_CODE		0
> > +#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT		24
> > +#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT		16
> > +#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT	8
> > +#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT	0
> > +
> > +/* 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_S0_STATUS_ADDR			0x3628
> > +
> > +#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_SENSOR_SHIFT			16
> > +#define TSENS_REDUND_SHIFT			24
> > +#define TSENS_SENSOR0_SHIFT			3
> > +
> > +#define TSENS_8660_QFPROM_ADDR			0x00bc
> > +#define TSENS_8660_CONFIG			1
> > +#define TSENS_8660_CONFIG_SHIFT			28
> > +#define TSENS_8660_CONFIG_MASK			(3 <<
> TSENS_8660_CONFIG_SHIFT)
> > +
> > +struct tsens_device;
> > +
> > +struct tsens_sensor {
> > +	struct thermal_zone_device	*tz_dev;
> > +	enum thermal_device_mode	mode;
> > +	unsigned int			sensor_num;
> > +	int				offset;
> > +	u32				slope;
> > +	struct tsens_device		*tmdev;
> > +	u32                             status;
> > +};
> > +
> > +struct tsens_device {
> > +	bool			prev_reading_avail;
> > +	unsigned int		num_sensors;
> > +	int			pm_tsens_thr_data;
> > +	int			pm_tsens_cntl;
> > +	unsigned int            calib_offset;
> > +	unsigned int            backup_calib_offset;
> > +	struct work_struct	tsens_work;
> > +	struct regmap		*map;
> > +	struct regmap_field	*status_field;
> > +	struct tsens_sensor	sensor[0];
> > +};
> > +
> > +static struct device *tsens_dev;
> > +
> > +/* Temperature on y axis and ADC-code on x-axis */ static int
> > +tsens_tz_code_to_mdegC(u32 adc_code, const struct tsens_sensor *s) {
> > +	return adc_code * s->slope + s->offset; }
> > +
> > +static int tsens_tz_get_temp(void *_sensor,
> > +			     long *temp)
> > +{
> > +	const struct tsens_sensor *tm_sensor = _sensor;
> > +	struct tsens_device *tmdev = tm_sensor->tmdev;
> > +	u32 code, trdy;
> > +
> > +	if (tm_sensor->mode != THERMAL_DEVICE_ENABLED)
> > +		return -EINVAL;
> > +
> > +	if (!tmdev->prev_reading_avail) {
> > +		while (!regmap_read(tmdev->map,
> TSENS_INT_STATUS_ADDR, &trdy) &&
> > +		       !(trdy & TSENS_TRDY_MASK))
> > +			usleep_range(1000, 1100);
> > +		tmdev->prev_reading_avail = true;
> > +	}
> > +
> > +	regmap_read(tmdev->map, tm_sensor->status, &code);
> > +	*temp = tsens_tz_code_to_mdegC(code, tm_sensor);
> > +
> > +	dev_dbg(tsens_dev, "Temperature for sensor %d is: %ld",
> > +		tm_sensor->sensor_num, *temp);
> > +
> > +	return 0;
> > +}
> > +
> > +/*
> > + * If the main sensor is disabled all the sensors are disable and
> > + * the clock is disabled.
> > + * If the main sensor is disabled and a sub-sensor is enabled
> > + * return with an error.
> > + */
> > +static int tsens_tz_set_mode(struct tsens_sensor *tm_sensor,
> > +			      enum thermal_device_mode mode) {
> > +	struct tsens_device *tmdev = tm_sensor->tmdev;
> > +	unsigned int i, n = tmdev->num_sensors;
> > +	u32 reg, mask;
> > +
> > +	if (mode == tm_sensor->mode)
> > +		return 0;
> > +
> > +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg);
> > +
> > +	mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
> > +	if (mode == THERMAL_DEVICE_ENABLED) {
> > +		if (!(reg & SENSOR0_EN) && mask != SENSOR0_EN) {
> > +			pr_err("Main sensor not enabled\n");
> > +			return -EINVAL;
> > +		}
> > +
> > +		regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg |
> TSENS_SW_RST);
> > +		if (tmdev->num_sensors > 1)
> > +			reg |= mask | TSENS_8960_SLP_CLK_ENA |
> TSENS_EN;
> > +		else
> > +			reg |= mask | TSENS_8660_SLP_CLK_ENA |
> TSENS_EN;
> > +		tmdev->prev_reading_avail = false;
> > +	} else {
> > +		reg &= ~mask;
> > +		if (!(reg & SENSOR0_EN)) {
> > +			dev_warn(tsens_dev, "Main sensor not enabled.
> Disabling
> > +subsensors\n");
> > +
> > +			reg &= ~GENMASK(n + TSENS_SENSOR0_SHIFT - 1,
> > +					TSENS_SENSOR0_SHIFT);
> > +			reg &= ~TSENS_EN;
> > +
> > +			if (tmdev->num_sensors > 1)
> > +				reg &= ~TSENS_8960_SLP_CLK_ENA;
> > +			else
> > +				reg &= ~TSENS_8660_SLP_CLK_ENA;
> > +
> > +			/* Disable all sub-sensors */
> > +			for (i = 1; i < n; i++)
> > +				tmdev->sensor[i].mode = mode;
> > +		}
> > +	}
> > +
> > +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg);
> > +	tm_sensor->mode = mode;
> > +
> > +	return 0;
> > +}
> > +
> > +#ifdef THERMAL_TSENS8960_HWTRIPS
> > +static int tsens_get_trip_temp(struct tsens_sensor *tm_sensor,
> > +				   int trip, int *temp)
> > +{
> > +	struct tsens_device *tmdev = tm_sensor->tmdev;
> > +	u32 reg;
> > +
> > +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &reg);
> > +	switch (trip) {
> > +	case TSENS_TRIP_STAGE3:
> > +		reg >>= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE2:
> > +		reg >>= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE1:
> > +		reg >>= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE0:
> > +		reg >>= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +	reg &= TSENS_THRESHOLD_MAX_CODE;
> > +
> > +	temp = tsens_tz_code_to_mdegC(reg, tm_sensor);
> > +
> > +	dev_dbg(tsens_dev, "Sensor %d, trip %d temp is :%d",
> > +		tm_sensor->sensor_num, trip, *temp);
> > +
> > +	return 0;
> > +}
> > +
> > +static void tsens_print_trip_temp(struct tsens_sensor *tm_sensor) {
> > +	int temp;
> > +
> > +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE0, &temp);
> > +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, &temp);
> > +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, &temp);
> > +	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE3, &temp); }
> > +
> > +static int tsens_tz_mdegC_to_code(int mdegC, const struct
> > +tsens_sensor *s) {
> > +	int code;
> > +
> > +	code = DIV_ROUND_CLOSEST(mdegC - s->offset, s->slope);
> > +	return clamp(code, TSENS_THRESHOLD_MIN_CODE,
> > +TSENS_THRESHOLD_MAX_CODE); }
> > +
> > +static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh) {
> > +	u32 hi_code;
> > +
> > +	switch (trip) {
> > +	case TSENS_TRIP_STAGE0:
> > +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> > +			hi_code = thresh >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE1:
> > +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> > +			hi_code = thresh >>
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE2:
> > +		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
> > +			hi_code = thresh >>
> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE3:
> > +	default:
> > +		hi_code = TSENS_THRESHOLD_MAX_CODE;
> > +		break;
> > +	}
> > +
> > +	return hi_code & TSENS_THRESHOLD_MAX_CODE; }
> > +
> > +static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh) {
> > +	u32 lo_code;
> > +
> > +	switch (trip) {
> > +	case TSENS_TRIP_STAGE3:
> > +		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
> > +			lo_code = thresh >>
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE2:
> > +		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
> > +			lo_code = thresh >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE1:
> > +		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
> > +			lo_code = thresh >>
> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> > +			break;
> > +		}
> > +		/* else fall through */
> > +	case TSENS_TRIP_STAGE0:
> > +	default:
> > +		lo_code = TSENS_THRESHOLD_MIN_CODE;
> > +		break;
> > +	}
> > +
> > +	return lo_code & TSENS_THRESHOLD_MAX_CODE; }
> > +
> > +static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
> > +	int trip, unsigned long temp)
> > +{
> > +	struct tsens_device *tmdev = tm_sensor->tmdev;
> > +	struct regmap_field *status = tmdev->status_field;
> > +	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
> > +	u32 code, hi_code, lo_code, code_err_chk;
> > +
> > +	code_err_chk = code = tsens_tz_mdegC_to_code(temp,
> tm_sensor);
> > +
> > +	regmap_field_read(status, &reg_cntl);
> > +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
> > +
> > +	switch (trip) {
> > +	case TSENS_TRIP_STAGE3:
> > +		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> > +		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE2:
> > +		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE1:
> > +		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +		break;
> > +	case TSENS_TRIP_STAGE0:
> > +		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> > +		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
> > +	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
> > +
> > +	if (code_err_chk < lo_code || code_err_chk > hi_code)
> > +		return -EINVAL;
> > +
> > +	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR,
> mask, code);
> > +
> > +	return 0;
> > +}
> > +
> > +static int tsens_set_trips(void *_sensor, long low, long high) {
> > +	struct tsens_sensor *tm_sensor = _sensor;
> > +
> > +	tsens_print_trip_temp(tm_sensor);
> > +
> > +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
> > +		return -EINVAL;
> > +
> > +	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
> > +		return -EINVAL;
> > +
> > +	return 0;
> > +}
> > +#endif
> 
> This whole chunk of code seams to be unused. Should we remove it?
> 
> Looks like this driver brings again the HW trip discussion again. Do you
have
> any specific needs to extend of thermal?
> 
> 

Is not used when compiled with upstream kernel.  
Wanted to keep a common code base with a downstream kernel which has this
support. 
But in my tests, yet to observe any noticeable advantage for this driver
with HW trips.
May be I need to run more tests, collect data points. 

Let me revise my patch with this code removed completely.

> 
> > +
> > +static void tsens_scheduler_fn(struct work_struct *work) {
> > +	struct tsens_device *tmdev;
> > +	struct regmap_field *status;
> > +	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
> > +	unsigned long sensor;
> > +	bool upper_th_x, lower_th_x;
> > +
> > +	tmdev = container_of(work, struct tsens_device, tsens_work);
> > +	status = tmdev->status_field;
> > +
> > +	regmap_field_update_bits(status,
> > +			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR,
> > +			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR);
> > +
> > +	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
> > +	threshold_low = threshold >>
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
> > +	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
> > +	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
> > +	threshold &= TSENS_THRESHOLD_MAX_CODE;
> > +
> > +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
> > +	sensor = bits;
> > +	sensor >>= TSENS_SENSOR0_SHIFT;
> > +	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
> > +		regmap_read(tmdev->map, tmdev->sensor[i].status,
> &code);
> > +		upper_th_x = code >= threshold;
> > +		lower_th_x = code <= threshold_low;
> > +
> > +		if (upper_th_x)
> > +			mask |= TSENS_UPPER_STATUS_CLR;
> > +
> > +		if (lower_th_x)
> > +			mask |= TSENS_LOWER_STATUS_CLR;
> > +
> > +#ifdef THERMAL_TSENS8960_HWTRIPS
> > +		if (upper_th_x || lower_th_x) {
> > +			dev_info(tsens_dev,
> > +				"Threshold reached for sensor(%d)\n", i);
> > +			thermal_zone_device_update(tmdev-
> >sensor[i].tz_dev);
> > +		}
> > +#endif
> > +	}
> > +
> > +	regmap_field_update_bits(status,
> > +			TSENS_UPPER_STATUS_CLR |
> TSENS_LOWER_STATUS_CLR, mask); }
> > +
> > +static irqreturn_t tsens_isr(int irq, void *data) {
> > +	schedule_work(data);
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int tsens_suspend(struct device *dev) {
> > +	int i;
> > +	struct tsens_device *tmdev = dev_get_drvdata(dev);
> > +	struct regmap *map = tmdev->map;
> > +
> > +	regmap_read(map, TSENS_THRESHOLD_ADDR, &tmdev-
> >pm_tsens_thr_data);
> > +	regmap_read(map, TSENS_CNTL_ADDR, &tmdev->pm_tsens_cntl);
> > +	regmap_update_bits(map, TSENS_CNTL_ADDR,
> > +			   TSENS_8960_SLP_CLK_ENA | TSENS_EN, 0);
> > +
> > +	tmdev->prev_reading_avail = 0;
> > +	for (i = 0; i < tmdev->num_sensors; i++)
> > +		tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED;
> > +
> > +	return 0;
> > +}
> > +
> > +static int tsens_resume(struct device *dev) {
> > +	int i;
> > +	unsigned long reg_cntl;
> > +	struct tsens_device *tmdev = dev_get_drvdata(dev);
> > +	struct regmap *map = tmdev->map;
> > +
> > +	regmap_update_bits(map, TSENS_CNTL_ADDR, TSENS_SW_RST,
> TSENS_SW_RST);
> > +	regmap_field_update_bits(tmdev->status_field,
> > +			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK,
> > +			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK);
> > +
> > +	if (tmdev->num_sensors > 1)
> > +		regmap_update_bits(map, TSENS_8960_CONFIG_ADDR,
> > +				TSENS_8960_CONFIG_MASK,
> > +				TSENS_8960_CONFIG);
> > +
> > +	regmap_write(map, TSENS_THRESHOLD_ADDR, tmdev-
> >pm_tsens_thr_data);
> > +	regmap_write(map, TSENS_CNTL_ADDR, tmdev->pm_tsens_cntl);
> > +
> > +	reg_cntl = tmdev->pm_tsens_cntl;
> > +	reg_cntl >>= TSENS_SENSOR0_SHIFT;
> > +	for_each_set_bit(i, &reg_cntl, tmdev->num_sensors)
> > +		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct dev_pm_ops tsens_pm_ops = {
> > +	.suspend	= tsens_suspend,
> > +	.resume		= tsens_resume,
> > +};
> > +#endif
> > +
> > +static void tsens_disable_mode(const struct tsens_device *tmdev) {
> > +	u32 reg_cntl;
> > +	u32 mask;
> > +
> > +	mask = GENMASK(tmdev->num_sensors - 1, 0);
> > +	mask <<= TSENS_SENSOR0_SHIFT;
> > +	mask |= TSENS_EN;
> > +
> > +	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
> > +	reg_cntl &= ~mask;
> > +	if (tmdev->num_sensors > 1)
> > +		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
> > +	else
> > +		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
> > +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl); }
> > +
> > +static void tsens_hw_init(struct tsens_device *tmdev) {
> > +	u32 reg_cntl, reg_thr;
> > +
> > +	reg_cntl = TSENS_SW_RST;
> > +	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR,
> TSENS_SW_RST,
> > +reg_cntl);
> > +
> > +	if (tmdev->num_sensors > 1) {
> > +		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
> > +			(TSENS_MEASURE_PERIOD << 18);
> > +		reg_cntl &= ~TSENS_SW_RST;
> > +		regmap_update_bits(tmdev->map,
> TSENS_8960_CONFIG_ADDR,
> > +				   TSENS_8960_CONFIG_MASK,
> TSENS_8960_CONFIG);
> > +	} else {
> > +		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
> > +			(TSENS_MEASURE_PERIOD << 16);
> > +		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
> > +		reg_cntl |= TSENS_8660_CONFIG <<
> TSENS_8660_CONFIG_SHIFT;
> > +	}
> > +
> > +	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) <<
> TSENS_SENSOR0_SHIFT;
> > +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> > +
> > +	regmap_field_update_bits(tmdev->status_field,
> > +			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR |
> > +			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK,
> > +			TSENS_LOWER_STATUS_CLR |
> TSENS_UPPER_STATUS_CLR |
> > +			TSENS_MIN_STATUS_MASK |
> TSENS_MAX_STATUS_MASK);
> > +
> > +	reg_cntl |= TSENS_EN;
> > +	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
> > +
> > +	reg_thr = (TSENS_LOWER_LIMIT_TH <<
> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
> > +		(TSENS_UPPER_LIMIT_TH <<
> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
> > +		(TSENS_MIN_LIMIT_TH <<
> TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
> > +		(TSENS_MAX_LIMIT_TH <<
> TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
> > +	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr); }
> > +
> > +static int
> > +tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap
> > +*map) {
> > +	u32 temp_data, data;
> > +	struct tsens_sensor *s = &tmdev->sensor[0];
> > +
> > +	if (regmap_read(map, tmdev->calib_offset, &temp_data))
> > +		return -EINVAL;
> > +
> > +	data = (temp_data >> 24) & 0xff;
> > +
> > +	if (!data) {
> > +		dev_err(tsens_dev, "QFPROM TSENS calibration data not
> present\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	s->offset = TSENS_CAL_MDEGC - s->slope * data;
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap
> > +*map) {
> > +	int i;
> > +	u32 temp_data[TSENS_MAX_SENSORS];
> > +	u8 *byte_data;
> > +	u32 fuse, redun, num_read;
> > +	struct tsens_sensor *s = tmdev->sensor;
> > +
> > +	fuse = tmdev->calib_offset;
> > +	redun = tmdev->backup_calib_offset;
> > +
> > +	/**
> > +	* syscon regmap is 32-bit data, but calibration data is 8-bit.
> > +	* read 4 bytes from regmap in a loop and then extract bytes
> seprately
> > +	*/
> > +
> > +	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
> > +
> > +	for (i = 0; i < num_read; i++) {
> > +		if (regmap_read(map, (redun + i*4), &temp_data[i]))
> > +			return -EINVAL;
> > +
> > +		if (!temp_data[i]) {
> > +			dev_dbg(tsens_dev, "Main calib data not valid\n");
> > +			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
> > +				return -EINVAL;
> > +		}
> > +	}
> > +
> > +	byte_data = (u8 *)temp_data;
> > +
> > +	for (i = 0; i < tmdev->num_sensors; i++, s++) {
> > +		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
> > +		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct thermal_zone_of_device_ops tsens_thermal_of_ops =
> {
> > +	.get_temp = tsens_tz_get_temp,
> > +};
> > +
> > +
> > +static int tsens_register(struct tsens_device *tmdev, int i) {
> > +	char name[18];
> > +	u32 addr = TSENS_S0_STATUS_ADDR;
> > +	struct tsens_sensor *s = &tmdev->sensor[i];
> > +
> > +	/*
> > +	* 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.
> > +	*/
> > +	if (i >= 5)
> > +		addr += 40;
> > +
> > +	addr += i * 4;
> > +
> > +	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
> > +	s->mode = THERMAL_DEVICE_ENABLED;
> > +	s->sensor_num = i;
> > +	s->status = addr;
> > +	s->tmdev = tmdev;
> > +	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
> > +						&tsens_thermal_of_ops);
> > +
> > +	if (IS_ERR(s->tz_dev))
> > +		return -ENODEV;
> > +
> > +	return 0;
> > +}
> > +
> > +static int tsens_probe(struct platform_device *pdev) {
> > +	struct device_node *np = pdev->dev.of_node;
> > +	struct device_node *base_node;
> > +	struct platform_device *base_pdev;
> > +	int ret, i, irq, num;
> > +	struct tsens_sensor *s;
> > +	struct tsens_device *tmdev;
> > +	struct regmap *map, *imem_regmap;
> > +	struct reg_field *field;
> > +	static struct reg_field status_0 = {
> > +		.reg = TSENS_8064_STATUS_CNTL,
> > +		.lsb = 0,
> > +		.msb = 3,
> > +	};
> > +	static struct reg_field status_8 = {
> > +		.reg = TSENS_CNTL_ADDR,
> > +		.lsb = 8,
> > +		.msb = 11,
> > +	};
> > +
> > +	tsens_dev = &pdev->dev;
> > +
> > +	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
> > +	if (num <= 0) {
> > +		dev_err(tsens_dev, "invalid tsens slopes\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
> > +			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
> > +	if (tmdev == NULL)
> > +		return -ENOMEM;
> > +
> > +	tmdev->num_sensors = num;
> > +	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
> > +		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
> > +					   &s->slope);
> > +
> > +	irq = platform_get_irq(pdev, 0);
> > +	if (irq < 0) {
> > +		dev_err(tsens_dev,  "no irq resource?\n");
> > +		return  -EINVAL;
> > +	}
> > +
> > +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
> > +					   &tmdev->calib_offset);
> > +	if (ret != 0) {
> > +		dev_err(tsens_dev,  "No calibration offset set\n");
> > +		return  -EINVAL;
> > +	}
> > +
> > +	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
> > +					   &tmdev->backup_calib_offset);
> > +	if (ret) {
> > +		dev_info(tsens_dev, "Missing backup calibration offset\n");
> > +		tmdev->backup_calib_offset = tmdev->calib_offset;
> > +	}
> > +
> > +	imem_regmap = syscon_regmap_lookup_by_phandle(np,
> "qcom,imem");
> > +	if (IS_ERR(imem_regmap)) {
> > +		dev_err(tsens_dev, "syscon regmap look up error\n");
> > +		return PTR_ERR(imem_regmap);
> > +	}
> > +
> > +	if (num == 1)
> > +		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
> > +	else
> > +		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
> > +
> > +	if (ret < 0) {
> > +		dev_err(tsens_dev, "tsense calibration failed\n");
> > +		return ret;
> > +	}
> > +
> > +	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
> > +	if (base_node == NULL) {
> > +		dev_err(tsens_dev, "no base node present\n");
> > +		return  -EINVAL;
> > +	}
> > +
> > +	base_pdev = of_find_device_by_node(base_node);
> > +	if (base_pdev == NULL) {
> > +		dev_err(tsens_dev, "no base pdev node\n");
> > +		return  -ENODEV;
> > +	}
> > +
> > +	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
> > +	if (map == NULL) {
> > +		dev_err(tsens_dev, "base regmap get failed\n");
> > +		return  -ENODEV;
> > +	}
> > +
> > +	/* Status bits move when the sensor bits next to them overlap */
> > +	if (num > 5)
> > +		field = &status_0;
> > +	else
> > +		field = &status_8;
> > +
> > +	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map,
> *field);
> > +	if (IS_ERR(tmdev->status_field)) {
> > +		dev_err(tsens_dev, "regmap alloc failed\n");
> > +		return PTR_ERR(tmdev->status_field);
> > +	}
> > +
> > +	tsens_hw_init(tmdev);
> > +
> > +	/*
> > +	 * Register sensor 0 separately. This sensor is always
> > +	 * expected to be present and if this fails, thermal
> > +	 * sensor probe would fail
> > +	 * Other sensors are optional and if registration fails
> > +	 * disable the sensor and continue
> > +	*/
> > +	ret = tsens_register(tmdev, 0);
> > +	if (ret < 0) {
> > +		dev_err(tsens_dev, "Registering failed for primary sensor");
> > +		ret = -ENODEV;
> > +		goto fail;
> > +	} else {
> > +		tsens_tz_set_mode(&tmdev->sensor[0],
> THERMAL_DEVICE_ENABLED);
> > +	}
> > +
> > +	for (i = 1;  i < tmdev->num_sensors; i++) {
> > +		ret = tsens_register(tmdev, i);
> > +
> > +		if (ret < 0) {
> > +			dev_err(tsens_dev,
> > +				"Registering failed. Sensor(%i), disabled",
i);
> > +			tsens_tz_set_mode(&tmdev->sensor[i],
> > +				THERMAL_DEVICE_DISABLED);
> > +		} else {
> > +			tsens_tz_set_mode(&tmdev->sensor[i],
> > +				THERMAL_DEVICE_ENABLED);
> > +		}
> > +	}
> > +
> > +	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
> > +
> > +	ret = devm_request_irq(tsens_dev, irq, tsens_isr,
> IRQF_TRIGGER_RISING,
> > +			       "tsens", &tmdev->tsens_work);
> > +	if (ret < 0)
> > +		goto err_irq;
> > +
> > +	platform_set_drvdata(pdev, tmdev);
> > +
> > +	dev_info(tsens_dev, "Tsens driver initialized\n");
> > +
> > +	return 0;
> > +err_irq:
> > +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> > +		thermal_zone_of_sensor_unregister(&pdev->dev, s-
> >tz_dev);
> > +fail:
> > +	tsens_disable_mode(tmdev);
> > +	return ret;
> > +}
> > +
> > +static int tsens_remove(struct platform_device *pdev) {
> > +	int i;
> > +	struct tsens_sensor *s;
> > +	struct tsens_device *tmdev = platform_get_drvdata(pdev);
> > +
> > +	tsens_disable_mode(tmdev);
> > +	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
> > +		thermal_zone_device_unregister(s->tz_dev);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct of_device_id tsens_match_table[] = {
> > +	{.compatible = "qcom,ipq806x-tsens"},
> > +	{},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, tsens_match_table);
> > +
> > +static struct platform_driver tsens_driver = {
> > +	.probe = tsens_probe,
> > +	.remove = tsens_remove,
> > +	.driver = {
> > +		.of_match_table = tsens_match_table,
> > +		.name = "tsens8960-thermal",
> > +		.owner = THIS_MODULE,
> > +#ifdef CONFIG_PM
> > +		.pm	= &tsens_pm_ops,
> > +#endif
> > +	},
> > +};
> > +module_platform_driver(tsens_driver);
> > +
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_DESCRIPTION("Temperature Sensor driver");
> > +MODULE_ALIAS("platform:tsens8960-tm");
> > --
> > Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc.
> > is a member of the Code Aurora Forum, a Linux Foundation Collaborative
> > Project
> >

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ivan T. Ivanov Jan. 30, 2015, 8:39 a.m. UTC | #11
On Thu, 2015-01-29 at 18:39 -0400, Eduardo Valentin wrote:
> On Mon, Jan 26, 2015 at 08:09:46PM -0800, Narendran Rajan wrote:
> > TSENS supports reading temperature from multiple thermal
> > sensors present in QCOM SOCs.
> > TSENS HW is enabled only when the main sensor is requested.
> > The TSENS block is disabled if the main senors is disabled
> > irrespective of any other sensors that are being enabled.
> > TSENS driver supports configurable threshold for temperature
> > monitoring in which case it can generate an interrupt when specific
> > thresholds are reached
> > 
> > Based on code by Siddartha Mohanadoss and Stephen Boyd.
> > 
> > Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> > Cc: Stephen Boyd <sboyd@codeaurora.org>
> > Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
> 
> I know this might not be completely related, but since you are sending
> QCOM code, can you please check my comments on your PMIC thermal code?
> https://patchwork.kernel.org/patch/5013021/
> 
> Ivan seams to have disappeared.



Mm no, sorry, just distracted by other tasks. PMIC thermal driver is in the
road map. I will try to accelerate rework.

Regards,
Ivan
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eduardo Valentin Jan. 31, 2015, 6:17 p.m. UTC | #12
On Fri, Jan 30, 2015 at 10:39:08AM +0200, Ivan T. Ivanov wrote:
> 
> On Thu, 2015-01-29 at 18:39 -0400, Eduardo Valentin wrote:
> > On Mon, Jan 26, 2015 at 08:09:46PM -0800, Narendran Rajan wrote:
> > > TSENS supports reading temperature from multiple thermal
> > > sensors present in QCOM SOCs.
> > > TSENS HW is enabled only when the main sensor is requested.
> > > The TSENS block is disabled if the main senors is disabled
> > > irrespective of any other sensors that are being enabled.
> > > TSENS driver supports configurable threshold for temperature
> > > monitoring in which case it can generate an interrupt when specific
> > > thresholds are reached
> > > 
> > > Based on code by Siddartha Mohanadoss and Stephen Boyd.
> > > 
> > > Cc: Siddartha Mohanadoss <smohanad@codeaurora.org>
> > > Cc: Stephen Boyd <sboyd@codeaurora.org>
> > > Signed-off-by: Narendran Rajan  <nrajan@codeaurora.org>
> > 
> > I know this might not be completely related, but since you are sending
> > QCOM code, can you please check my comments on your PMIC thermal code?
> > https://patchwork.kernel.org/patch/5013021/
> > 
> > Ivan seams to have disappeared.
> 
> 
> 
> Mm no, sorry, just distracted by other tasks. PMIC thermal driver is in the
> road map. I will try to accelerate rework.

Great news Ivan!

Let me know if you have doubts or need any support / extension on the OF
thermal API in order to get the PMIC thermal properly implemented. 

> 
> Regards,
> Ivan

Eduardo Valentin
Eduardo Valentin Jan. 31, 2015, 6:23 p.m. UTC | #13
On Thu, Jan 29, 2015 at 05:42:53PM -0800, Narendran Rajan wrote:

<cut>

> > 
> > 
> 
> Is not used when compiled with upstream kernel.  
> Wanted to keep a common code base with a downstream kernel which has this
> support. 

Please, keep in mind that vanilla kernels at kernel.org never maintains
compatibility to code outside the tree. You either remove the unused
code, or port the full feature, if that makes sense.

> But in my tests, yet to observe any noticeable advantage for this driver
> with HW trips.

I see. That seams to be the final common conclusion of developers that
write code for chips with such feature, once they port to the linux
kernel thermal framework. We may need a good use case to get that
ported.

> May be I need to run more tests, collect data points. 
> 

OK. Let me know your results or if you have any insights regarding HW
trip points.

> Let me revise my patch with this code removed completely.
> 


sounds good.

BR,

Eduardo Valentin
diff mbox

Patch

diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 20915ca..a4cb2c0 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -261,6 +261,20 @@  config INTEL_SOC_DTS_THERMAL
 	  notification methods.The other trip is a critical trip point, which
 	  was set by the driver based on the TJ MAX temperature.
 
+config THERMAL_TSENS8960
+	tristate "Qualcomm 8960 Tsens Temperature driver"
+	depends on THERMAL
+	depends on ARCH_QCOM
+	help
+	  QCOM tsens thermal driver provides support for Temperature sensor
+	  (TSENS) found on QCOM SoCs. It supports four configurable trip points
+	  and controls multiple sensors on the SOC. The four trip points are
+	  common across all sensors present in the SoC. The number of sensors
+	  present vary from chip to chip and are set through device tree entry.
+	  The driver presents as a standard thermal zone device with configurable
+	  trip points and cooling device mapping through standard thermal zone
+	  device tree
+
 menu "ACPI INT340X thermal drivers"
 source drivers/thermal/int340x_thermal/Kconfig
 endmenu
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fa0dc48..23c7a34 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -39,3 +39,4 @@  obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-thermal/
 obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
 obj-$(CONFIG_ST_THERMAL)	+= st/
 obj-$(CONFIG_TEGRA_SOCTHERM)	+= tegra_soctherm.o
+obj-$(CONFIG_THERMAL_TSENS8960)	+= msm8960_tsens.o
diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c
new file mode 100644
index 0000000..307bdc8
--- /dev/null
+++ b/drivers/thermal/msm8960_tsens.c
@@ -0,0 +1,841 @@ 
+/* 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+
+/* Trips: from very hot to very cold */
+enum tsens_trip_type {
+	TSENS_TRIP_STAGE3,
+	TSENS_TRIP_STAGE2,
+	TSENS_TRIP_STAGE1,
+	TSENS_TRIP_STAGE0,
+	TSENS_TRIP_NUM,
+};
+
+#define TSENS_CAL_MDEGC				30000
+
+#define TSENS_MAX_SENSORS			11
+
+#define TSENS_8960_CONFIG_ADDR			0x3640
+#define TSENS_8960_CONFIG			0x9b
+#define TSENS_8960_CONFIG_MASK			0xf
+
+#define TSENS_CNTL_ADDR				0x3620
+#define TSENS_CNTL_RESUME_MASK			0xfffffff9
+#define TSENS_EN				BIT(0)
+#define TSENS_SW_RST				BIT(1)
+#define SENSOR0_EN				BIT(3)
+#define TSENS_MIN_STATUS_MASK			BIT(0)
+#define TSENS_LOWER_STATUS_CLR			BIT(1)
+#define TSENS_UPPER_STATUS_CLR			BIT(2)
+#define TSENS_MAX_STATUS_MASK			BIT(3)
+#define TSENS_MEASURE_PERIOD			1
+#define TSENS_8960_SLP_CLK_ENA			BIT(26)
+#define TSENS_8660_SLP_CLK_ENA			BIT(24)
+#define TSENS_8064_STATUS_CNTL			0x3660
+
+#define TSENS_THRESHOLD_ADDR			0x3624
+#define TSENS_THRESHOLD_MAX_CODE		0xff
+#define TSENS_THRESHOLD_MIN_CODE		0
+#define TSENS_THRESHOLD_MAX_LIMIT_SHIFT		24
+#define TSENS_THRESHOLD_MIN_LIMIT_SHIFT		16
+#define TSENS_THRESHOLD_UPPER_LIMIT_SHIFT	8
+#define TSENS_THRESHOLD_LOWER_LIMIT_SHIFT	0
+
+/* 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_S0_STATUS_ADDR			0x3628
+
+#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_SENSOR_SHIFT			16
+#define TSENS_REDUND_SHIFT			24
+#define TSENS_SENSOR0_SHIFT			3
+
+#define TSENS_8660_QFPROM_ADDR			0x00bc
+#define TSENS_8660_CONFIG			1
+#define TSENS_8660_CONFIG_SHIFT			28
+#define TSENS_8660_CONFIG_MASK			(3 << TSENS_8660_CONFIG_SHIFT)
+
+struct tsens_device;
+
+struct tsens_sensor {
+	struct thermal_zone_device	*tz_dev;
+	enum thermal_device_mode	mode;
+	unsigned int			sensor_num;
+	int				offset;
+	u32				slope;
+	struct tsens_device		*tmdev;
+	u32                             status;
+};
+
+struct tsens_device {
+	bool			prev_reading_avail;
+	unsigned int		num_sensors;
+	int			pm_tsens_thr_data;
+	int			pm_tsens_cntl;
+	unsigned int            calib_offset;
+	unsigned int            backup_calib_offset;
+	struct work_struct	tsens_work;
+	struct regmap		*map;
+	struct regmap_field	*status_field;
+	struct tsens_sensor	sensor[0];
+};
+
+static struct device *tsens_dev;
+
+/* Temperature on y axis and ADC-code on x-axis */
+static int
+tsens_tz_code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
+{
+	return adc_code * s->slope + s->offset;
+}
+
+static int tsens_tz_get_temp(void *_sensor,
+			     long *temp)
+{
+	const struct tsens_sensor *tm_sensor = _sensor;
+	struct tsens_device *tmdev = tm_sensor->tmdev;
+	u32 code, trdy;
+
+	if (tm_sensor->mode != THERMAL_DEVICE_ENABLED)
+		return -EINVAL;
+
+	if (!tmdev->prev_reading_avail) {
+		while (!regmap_read(tmdev->map, TSENS_INT_STATUS_ADDR, &trdy) &&
+		       !(trdy & TSENS_TRDY_MASK))
+			usleep_range(1000, 1100);
+		tmdev->prev_reading_avail = true;
+	}
+
+	regmap_read(tmdev->map, tm_sensor->status, &code);
+	*temp = tsens_tz_code_to_mdegC(code, tm_sensor);
+
+	dev_dbg(tsens_dev, "Temperature for sensor %d is: %ld",
+		tm_sensor->sensor_num, *temp);
+
+	return 0;
+}
+
+/*
+ * If the main sensor is disabled all the sensors are disable and
+ * the clock is disabled.
+ * If the main sensor is disabled and a sub-sensor is enabled
+ * return with an error.
+ */
+static int tsens_tz_set_mode(struct tsens_sensor *tm_sensor,
+			      enum thermal_device_mode mode)
+{
+	struct tsens_device *tmdev = tm_sensor->tmdev;
+	unsigned int i, n = tmdev->num_sensors;
+	u32 reg, mask;
+
+	if (mode == tm_sensor->mode)
+		return 0;
+
+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg);
+
+	mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT);
+	if (mode == THERMAL_DEVICE_ENABLED) {
+		if (!(reg & SENSOR0_EN) && mask != SENSOR0_EN) {
+			pr_err("Main sensor not enabled\n");
+			return -EINVAL;
+		}
+
+		regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg | TSENS_SW_RST);
+		if (tmdev->num_sensors > 1)
+			reg |= mask | TSENS_8960_SLP_CLK_ENA | TSENS_EN;
+		else
+			reg |= mask | TSENS_8660_SLP_CLK_ENA | TSENS_EN;
+		tmdev->prev_reading_avail = false;
+	} else {
+		reg &= ~mask;
+		if (!(reg & SENSOR0_EN)) {
+			dev_warn(tsens_dev, "Main sensor not enabled. Disabling subsensors\n");
+
+			reg &= ~GENMASK(n + TSENS_SENSOR0_SHIFT - 1,
+					TSENS_SENSOR0_SHIFT);
+			reg &= ~TSENS_EN;
+
+			if (tmdev->num_sensors > 1)
+				reg &= ~TSENS_8960_SLP_CLK_ENA;
+			else
+				reg &= ~TSENS_8660_SLP_CLK_ENA;
+
+			/* Disable all sub-sensors */
+			for (i = 1; i < n; i++)
+				tmdev->sensor[i].mode = mode;
+		}
+	}
+
+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg);
+	tm_sensor->mode = mode;
+
+	return 0;
+}
+
+#ifdef THERMAL_TSENS8960_HWTRIPS
+static int tsens_get_trip_temp(struct tsens_sensor *tm_sensor,
+				   int trip, int *temp)
+{
+	struct tsens_device *tmdev = tm_sensor->tmdev;
+	u32 reg;
+
+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &reg);
+	switch (trip) {
+	case TSENS_TRIP_STAGE3:
+		reg >>= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
+		break;
+	case TSENS_TRIP_STAGE2:
+		reg >>= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
+		break;
+	case TSENS_TRIP_STAGE1:
+		reg >>= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
+		break;
+	case TSENS_TRIP_STAGE0:
+		reg >>= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	reg &= TSENS_THRESHOLD_MAX_CODE;
+
+	temp = tsens_tz_code_to_mdegC(reg, tm_sensor);
+
+	dev_dbg(tsens_dev, "Sensor %d, trip %d temp is :%d",
+		tm_sensor->sensor_num, trip, *temp);
+
+	return 0;
+}
+
+static void tsens_print_trip_temp(struct tsens_sensor *tm_sensor)
+{
+	int temp;
+
+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE0, &temp);
+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, &temp);
+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, &temp);
+	tsens_get_trip_temp(tm_sensor, TSENS_TRIP_STAGE3, &temp);
+}
+
+static int tsens_tz_mdegC_to_code(int mdegC, const struct tsens_sensor *s)
+{
+	int code;
+
+	code = DIV_ROUND_CLOSEST(mdegC - s->offset, s->slope);
+	return clamp(code, TSENS_THRESHOLD_MIN_CODE, TSENS_THRESHOLD_MAX_CODE);
+}
+
+static u32 tsens_hi_code(int trip, u32 reg_cntl, u32 thresh)
+{
+	u32 hi_code;
+
+	switch (trip) {
+	case TSENS_TRIP_STAGE0:
+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
+			hi_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
+			break;
+		}
+		/* else fall through */
+	case TSENS_TRIP_STAGE1:
+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
+			hi_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
+			break;
+		}
+		/* else fall through */
+	case TSENS_TRIP_STAGE2:
+		if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) {
+			hi_code = thresh >> TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
+			break;
+		}
+		/* else fall through */
+	case TSENS_TRIP_STAGE3:
+	default:
+		hi_code = TSENS_THRESHOLD_MAX_CODE;
+		break;
+	}
+
+	return hi_code & TSENS_THRESHOLD_MAX_CODE;
+}
+
+static u32 tsens_lo_code(int trip, u32 reg_cntl, u32 thresh)
+{
+	u32 lo_code;
+
+	switch (trip) {
+	case TSENS_TRIP_STAGE3:
+		if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) {
+			lo_code = thresh >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
+			break;
+		}
+		/* else fall through */
+	case TSENS_TRIP_STAGE2:
+		if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) {
+			lo_code = thresh >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
+			break;
+		}
+		/* else fall through */
+	case TSENS_TRIP_STAGE1:
+		if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) {
+			lo_code = thresh >> TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
+			break;
+		}
+		/* else fall through */
+	case TSENS_TRIP_STAGE0:
+	default:
+		lo_code = TSENS_THRESHOLD_MIN_CODE;
+		break;
+	}
+
+	return lo_code & TSENS_THRESHOLD_MAX_CODE;
+}
+
+static int tsens_tz_set_trip_temp(struct tsens_sensor *tm_sensor,
+	int trip, unsigned long temp)
+{
+	struct tsens_device *tmdev = tm_sensor->tmdev;
+	struct regmap_field *status = tmdev->status_field;
+	u32 thresh, reg_cntl, mask = TSENS_THRESHOLD_MAX_CODE;
+	u32 code, hi_code, lo_code, code_err_chk;
+
+	code_err_chk = code = tsens_tz_mdegC_to_code(temp, tm_sensor);
+
+	regmap_field_read(status, &reg_cntl);
+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &thresh);
+
+	switch (trip) {
+	case TSENS_TRIP_STAGE3:
+		code <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
+		mask <<= TSENS_THRESHOLD_MAX_LIMIT_SHIFT;
+		break;
+	case TSENS_TRIP_STAGE2:
+		code <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
+		mask <<= TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
+		break;
+	case TSENS_TRIP_STAGE1:
+		code <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
+		mask <<= TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
+		break;
+	case TSENS_TRIP_STAGE0:
+		code <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
+		mask <<= TSENS_THRESHOLD_MIN_LIMIT_SHIFT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	hi_code = tsens_hi_code(trip, reg_cntl, thresh);
+	lo_code = tsens_lo_code(trip, reg_cntl, thresh);
+
+	if (code_err_chk < lo_code || code_err_chk > hi_code)
+		return -EINVAL;
+
+	regmap_update_bits(tmdev->map, TSENS_THRESHOLD_ADDR, mask, code);
+
+	return 0;
+}
+
+static int tsens_set_trips(void *_sensor, long low, long high)
+{
+	struct tsens_sensor *tm_sensor = _sensor;
+
+	tsens_print_trip_temp(tm_sensor);
+
+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE2, high))
+		return -EINVAL;
+
+	if (tsens_tz_set_trip_temp(tm_sensor, TSENS_TRIP_STAGE1, low))
+		return -EINVAL;
+
+	return 0;
+}
+#endif
+
+static void tsens_scheduler_fn(struct work_struct *work)
+{
+	struct tsens_device *tmdev;
+	struct regmap_field *status;
+	unsigned int threshold, threshold_low, i, code, bits, mask = 0;
+	unsigned long sensor;
+	bool upper_th_x, lower_th_x;
+
+	tmdev = container_of(work, struct tsens_device, tsens_work);
+	status = tmdev->status_field;
+
+	regmap_field_update_bits(status,
+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR,
+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR);
+
+	regmap_read(tmdev->map, TSENS_THRESHOLD_ADDR, &threshold);
+	threshold_low = threshold >> TSENS_THRESHOLD_LOWER_LIMIT_SHIFT;
+	threshold_low &= TSENS_THRESHOLD_MAX_CODE;
+	threshold = threshold >> TSENS_THRESHOLD_UPPER_LIMIT_SHIFT;
+	threshold &= TSENS_THRESHOLD_MAX_CODE;
+
+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &bits);
+	sensor = bits;
+	sensor >>= TSENS_SENSOR0_SHIFT;
+	for_each_set_bit(i, &sensor, tmdev->num_sensors) {
+		regmap_read(tmdev->map, tmdev->sensor[i].status, &code);
+		upper_th_x = code >= threshold;
+		lower_th_x = code <= threshold_low;
+
+		if (upper_th_x)
+			mask |= TSENS_UPPER_STATUS_CLR;
+
+		if (lower_th_x)
+			mask |= TSENS_LOWER_STATUS_CLR;
+
+#ifdef THERMAL_TSENS8960_HWTRIPS
+		if (upper_th_x || lower_th_x) {
+			dev_info(tsens_dev,
+				"Threshold reached for sensor(%d)\n", i);
+			thermal_zone_device_update(tmdev->sensor[i].tz_dev);
+		}
+#endif
+	}
+
+	regmap_field_update_bits(status,
+			TSENS_UPPER_STATUS_CLR | TSENS_LOWER_STATUS_CLR, mask);
+}
+
+static irqreturn_t tsens_isr(int irq, void *data)
+{
+	schedule_work(data);
+	return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int tsens_suspend(struct device *dev)
+{
+	int i;
+	struct tsens_device *tmdev = dev_get_drvdata(dev);
+	struct regmap *map = tmdev->map;
+
+	regmap_read(map, TSENS_THRESHOLD_ADDR, &tmdev->pm_tsens_thr_data);
+	regmap_read(map, TSENS_CNTL_ADDR, &tmdev->pm_tsens_cntl);
+	regmap_update_bits(map, TSENS_CNTL_ADDR,
+			   TSENS_8960_SLP_CLK_ENA | TSENS_EN, 0);
+
+	tmdev->prev_reading_avail = 0;
+	for (i = 0; i < tmdev->num_sensors; i++)
+		tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED;
+
+	return 0;
+}
+
+static int tsens_resume(struct device *dev)
+{
+	int i;
+	unsigned long reg_cntl;
+	struct tsens_device *tmdev = dev_get_drvdata(dev);
+	struct regmap *map = tmdev->map;
+
+	regmap_update_bits(map, TSENS_CNTL_ADDR, TSENS_SW_RST, TSENS_SW_RST);
+	regmap_field_update_bits(tmdev->status_field,
+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
+
+	if (tmdev->num_sensors > 1)
+		regmap_update_bits(map, TSENS_8960_CONFIG_ADDR,
+				TSENS_8960_CONFIG_MASK,
+				TSENS_8960_CONFIG);
+
+	regmap_write(map, TSENS_THRESHOLD_ADDR, tmdev->pm_tsens_thr_data);
+	regmap_write(map, TSENS_CNTL_ADDR, tmdev->pm_tsens_cntl);
+
+	reg_cntl = tmdev->pm_tsens_cntl;
+	reg_cntl >>= TSENS_SENSOR0_SHIFT;
+	for_each_set_bit(i, &reg_cntl, tmdev->num_sensors)
+		tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
+
+	return 0;
+}
+
+static const struct dev_pm_ops tsens_pm_ops = {
+	.suspend	= tsens_suspend,
+	.resume		= tsens_resume,
+};
+#endif
+
+static void tsens_disable_mode(const struct tsens_device *tmdev)
+{
+	u32 reg_cntl;
+	u32 mask;
+
+	mask = GENMASK(tmdev->num_sensors - 1, 0);
+	mask <<= TSENS_SENSOR0_SHIFT;
+	mask |= TSENS_EN;
+
+	regmap_read(tmdev->map, TSENS_CNTL_ADDR, &reg_cntl);
+	reg_cntl &= ~mask;
+	if (tmdev->num_sensors > 1)
+		reg_cntl &= ~TSENS_8960_SLP_CLK_ENA;
+	else
+		reg_cntl &= ~TSENS_8660_SLP_CLK_ENA;
+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
+}
+
+static void tsens_hw_init(struct tsens_device *tmdev)
+{
+	u32 reg_cntl, reg_thr;
+
+	reg_cntl = TSENS_SW_RST;
+	regmap_update_bits(tmdev->map, TSENS_CNTL_ADDR, TSENS_SW_RST, reg_cntl);
+
+	if (tmdev->num_sensors > 1) {
+		reg_cntl |= TSENS_8960_SLP_CLK_ENA |
+			(TSENS_MEASURE_PERIOD << 18);
+		reg_cntl &= ~TSENS_SW_RST;
+		regmap_update_bits(tmdev->map, TSENS_8960_CONFIG_ADDR,
+				   TSENS_8960_CONFIG_MASK, TSENS_8960_CONFIG);
+	} else {
+		reg_cntl |= TSENS_8660_SLP_CLK_ENA |
+			(TSENS_MEASURE_PERIOD << 16);
+		reg_cntl &= ~TSENS_8660_CONFIG_MASK;
+		reg_cntl |= TSENS_8660_CONFIG << TSENS_8660_CONFIG_SHIFT;
+	}
+
+	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << TSENS_SENSOR0_SHIFT;
+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
+
+	regmap_field_update_bits(tmdev->status_field,
+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK,
+			TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR |
+			TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK);
+
+	reg_cntl |= TSENS_EN;
+	regmap_write(tmdev->map, TSENS_CNTL_ADDR, reg_cntl);
+
+	reg_thr = (TSENS_LOWER_LIMIT_TH << TSENS_THRESHOLD_LOWER_LIMIT_SHIFT) |
+		(TSENS_UPPER_LIMIT_TH << TSENS_THRESHOLD_UPPER_LIMIT_SHIFT) |
+		(TSENS_MIN_LIMIT_TH << TSENS_THRESHOLD_MIN_LIMIT_SHIFT) |
+		(TSENS_MAX_LIMIT_TH << TSENS_THRESHOLD_MAX_LIMIT_SHIFT);
+	regmap_write(tmdev->map, TSENS_THRESHOLD_ADDR, reg_thr);
+}
+
+static int
+tsens_calib_sensors8660(struct tsens_device *tmdev, struct regmap *map)
+{
+	u32 temp_data, data;
+	struct tsens_sensor *s = &tmdev->sensor[0];
+
+	if (regmap_read(map, tmdev->calib_offset, &temp_data))
+		return -EINVAL;
+
+	data = (temp_data >> 24) & 0xff;
+
+	if (!data) {
+		dev_err(tsens_dev, "QFPROM TSENS calibration data not present\n");
+		return -EINVAL;
+	}
+
+	s->offset = TSENS_CAL_MDEGC - s->slope * data;
+
+	return 0;
+}
+
+static int
+tsens_calib_sensors8960(struct tsens_device *tmdev, struct regmap *map)
+{
+	int i;
+	u32 temp_data[TSENS_MAX_SENSORS];
+	u8 *byte_data;
+	u32 fuse, redun, num_read;
+	struct tsens_sensor *s = tmdev->sensor;
+
+	fuse = tmdev->calib_offset;
+	redun = tmdev->backup_calib_offset;
+
+	/**
+	* syscon regmap is 32-bit data, but calibration data is 8-bit.
+	* read 4 bytes from regmap in a loop and then extract bytes seprately
+	*/
+
+	num_read = DIV_ROUND_UP(tmdev->num_sensors, 4);
+
+	for (i = 0; i < num_read; i++) {
+		if (regmap_read(map, (redun + i*4), &temp_data[i]))
+			return -EINVAL;
+
+		if (!temp_data[i]) {
+			dev_dbg(tsens_dev, "Main calib data not valid\n");
+			if (regmap_read(map, (fuse + i*4), &temp_data[i]))
+				return -EINVAL;
+		}
+	}
+
+	byte_data = (u8 *)temp_data;
+
+	for (i = 0; i < tmdev->num_sensors; i++, s++) {
+		s->offset = TSENS_CAL_MDEGC - s->slope * byte_data[i];
+		dev_dbg(tsens_dev, "calib data %d is %c", i, byte_data[i]);
+	}
+
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops tsens_thermal_of_ops = {
+	.get_temp = tsens_tz_get_temp,
+};
+
+
+static int tsens_register(struct tsens_device *tmdev, int i)
+{
+	char name[18];
+	u32 addr = TSENS_S0_STATUS_ADDR;
+	struct tsens_sensor *s = &tmdev->sensor[i];
+
+	/*
+	* 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.
+	*/
+	if (i >= 5)
+		addr += 40;
+
+	addr += i * 4;
+
+	snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
+	s->mode = THERMAL_DEVICE_ENABLED;
+	s->sensor_num = i;
+	s->status = addr;
+	s->tmdev = tmdev;
+	s->tz_dev = thermal_zone_of_sensor_register(tsens_dev, i, s,
+						&tsens_thermal_of_ops);
+
+	if (IS_ERR(s->tz_dev))
+		return -ENODEV;
+
+	return 0;
+}
+
+static int tsens_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *base_node;
+	struct platform_device *base_pdev;
+	int ret, i, irq, num;
+	struct tsens_sensor *s;
+	struct tsens_device *tmdev;
+	struct regmap *map, *imem_regmap;
+	struct reg_field *field;
+	static struct reg_field status_0 = {
+		.reg = TSENS_8064_STATUS_CNTL,
+		.lsb = 0,
+		.msb = 3,
+	};
+	static struct reg_field status_8 = {
+		.reg = TSENS_CNTL_ADDR,
+		.lsb = 8,
+		.msb = 11,
+	};
+
+	tsens_dev = &pdev->dev;
+
+	num = of_property_count_u32_elems(np, "qcom,tsens-slopes");
+	if (num <= 0) {
+		dev_err(tsens_dev, "invalid tsens slopes\n");
+		return -EINVAL;
+	}
+
+	tmdev = devm_kzalloc(tsens_dev, sizeof(*tmdev) +
+			     num * sizeof(struct tsens_sensor), GFP_KERNEL);
+	if (tmdev == NULL)
+		return -ENOMEM;
+
+	tmdev->num_sensors = num;
+	for (i = 0, s = tmdev->sensor; i < num; i++, s++)
+		of_property_read_u32_index(np, "qcom,tsens-slopes", i,
+					   &s->slope);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(tsens_dev,  "no irq resource?\n");
+		return  -EINVAL;
+	}
+
+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 0,
+					   &tmdev->calib_offset);
+	if (ret != 0) {
+		dev_err(tsens_dev,  "No calibration offset set\n");
+		return  -EINVAL;
+	}
+
+	ret = of_property_read_u32_index(np, "qcom,calib-offsets", 1,
+					   &tmdev->backup_calib_offset);
+	if (ret) {
+		dev_info(tsens_dev, "Missing backup calibration offset\n");
+		tmdev->backup_calib_offset = tmdev->calib_offset;
+	}
+
+	imem_regmap = syscon_regmap_lookup_by_phandle(np, "qcom,imem");
+	if (IS_ERR(imem_regmap)) {
+		dev_err(tsens_dev, "syscon regmap look up error\n");
+		return PTR_ERR(imem_regmap);
+	}
+
+	if (num == 1)
+		ret = tsens_calib_sensors8660(tmdev, imem_regmap);
+	else
+		ret = tsens_calib_sensors8960(tmdev, imem_regmap);
+
+	if (ret < 0) {
+		dev_err(tsens_dev, "tsense calibration failed\n");
+		return ret;
+	}
+
+	base_node = of_parse_phandle(np, "qcom,tsens-base", 0);
+	if (base_node == NULL) {
+		dev_err(tsens_dev, "no base node present\n");
+		return  -EINVAL;
+	}
+
+	base_pdev = of_find_device_by_node(base_node);
+	if (base_pdev == NULL) {
+		dev_err(tsens_dev, "no base pdev node\n");
+		return  -ENODEV;
+	}
+
+	tmdev->map = map = dev_get_regmap(&base_pdev->dev, NULL);
+	if (map == NULL) {
+		dev_err(tsens_dev, "base regmap get failed\n");
+		return  -ENODEV;
+	}
+
+	/* Status bits move when the sensor bits next to them overlap */
+	if (num > 5)
+		field = &status_0;
+	else
+		field = &status_8;
+
+	tmdev->status_field = devm_regmap_field_alloc(tsens_dev, map, *field);
+	if (IS_ERR(tmdev->status_field)) {
+		dev_err(tsens_dev, "regmap alloc failed\n");
+		return PTR_ERR(tmdev->status_field);
+	}
+
+	tsens_hw_init(tmdev);
+
+	/*
+	 * Register sensor 0 separately. This sensor is always
+	 * expected to be present and if this fails, thermal
+	 * sensor probe would fail
+	 * Other sensors are optional and if registration fails
+	 * disable the sensor and continue
+	*/
+	ret = tsens_register(tmdev, 0);
+	if (ret < 0) {
+		dev_err(tsens_dev, "Registering failed for primary sensor");
+		ret = -ENODEV;
+		goto fail;
+	} else {
+		tsens_tz_set_mode(&tmdev->sensor[0], THERMAL_DEVICE_ENABLED);
+	}
+
+	for (i = 1;  i < tmdev->num_sensors; i++) {
+		ret = tsens_register(tmdev, i);
+
+		if (ret < 0) {
+			dev_err(tsens_dev,
+				"Registering failed. Sensor(%i), disabled", i);
+			tsens_tz_set_mode(&tmdev->sensor[i],
+				THERMAL_DEVICE_DISABLED);
+		} else {
+			tsens_tz_set_mode(&tmdev->sensor[i],
+				THERMAL_DEVICE_ENABLED);
+		}
+	}
+
+	INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn);
+
+	ret = devm_request_irq(tsens_dev, irq, tsens_isr, IRQF_TRIGGER_RISING,
+			       "tsens", &tmdev->tsens_work);
+	if (ret < 0)
+		goto err_irq;
+
+	platform_set_drvdata(pdev, tmdev);
+
+	dev_info(tsens_dev, "Tsens driver initialized\n");
+
+	return 0;
+err_irq:
+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
+		thermal_zone_of_sensor_unregister(&pdev->dev, s->tz_dev);
+fail:
+	tsens_disable_mode(tmdev);
+	return ret;
+}
+
+static int tsens_remove(struct platform_device *pdev)
+{
+	int i;
+	struct tsens_sensor *s;
+	struct tsens_device *tmdev = platform_get_drvdata(pdev);
+
+	tsens_disable_mode(tmdev);
+	for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++)
+		thermal_zone_device_unregister(s->tz_dev);
+
+	return 0;
+}
+
+static struct of_device_id tsens_match_table[] = {
+	{.compatible = "qcom,ipq806x-tsens"},
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, tsens_match_table);
+
+static struct platform_driver tsens_driver = {
+	.probe = tsens_probe,
+	.remove = tsens_remove,
+	.driver = {
+		.of_match_table = tsens_match_table,
+		.name = "tsens8960-thermal",
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm	= &tsens_pm_ops,
+#endif
+	},
+};
+module_platform_driver(tsens_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Temperature Sensor driver");
+MODULE_ALIAS("platform:tsens8960-tm");