Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4304409f authored by Siddartha Mohanadoss's avatar Siddartha Mohanadoss
Browse files

thermal: tsens: Add TSENS driver snapshot



Temperature sensor (TSENS) driver is used by thermal
client to set temperature thresholds and send notification
once set thresholds are crossed. Clients can read from
supported TSENS sensors from the TSENS controller.
There can be multiple instances of the TSENS controllers
to support multiple sensors across the system.

This snapshot is taken as of msm-4.9
'commit <d5d55ba> ("Merge "dwc3-msm: Use upstream
IOMMU driver based dma_ops() with USB dev"")'.

Change-Id: I0217c55d8462812bfa545fdadf545897da66bebb
Signed-off-by: default avatarSiddartha Mohanadoss <smohanad@codeaurora.org>
parent 6122ce25
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
Qualcomm Technologies, Inc. TSENS driver

Temperature sensor (TSENS) driver supports reading temperature from sensors
across the MSM. The driver defaults to support a 12 bit ADC.

The driver uses the Thermal sysfs framework to provide thermal
clients the ability to read from supported on-die temperature sensors,
set temperature thresholds for cool/warm thresholds and receive notification
on temperature threshold events.

TSENS node

Required properties:
- compatible : should be "qcom,msm8996-tsens" for 8996 TSENS driver.
	       should be "qcom,msm8953-tsens" for 8953 TSENS driver.
	       should be "qcom,msm8998-tsens" for 8998 TSENS driver.
	       should be "qcom,msmhamster-tsens" for hamster TSENS driver.
	       should be "qcom,sdm660-tsens" for 660 TSENS driver.
	       should be "qcom,sdm630-tsens" for 630 TSENS driver.
	       should be "qcom,sdm845-tsens" for SDM845 TSENS driver.
	       should be "qcom,tsens24xx" for 2.4 TSENS controller.
	       The compatible property is used to identify the respective controller to use
	       for the corresponding SoC.
- reg : offset and length of the TSENS registers with associated property in reg-names
	as "tsens_srot_physical" for TSENS SROT physical address region. TSENS TM
	physical address region as "tsens_tm_physical".
- reg-names : resource names used for the physical address of the TSENS
	      registers. Should be "tsens_srot_physical" for physical address of the TSENS
	      SROT region and "tsens_tm_physical" for physical address of the TM region.
- interrupts : TSENS interrupt to notify Upper/Lower and Critical temperature threshold.
- interrupt-names: Should be "tsens-upper-lower" for temperature threshold.
		   Add "tsens-critical" for Critical temperature threshold notification
		   in addition to "tsens-upper-lower" for 8996 TSENS since
		   8996 supports Upper/Lower and Critical temperature threshold.

Example:

tsens@fc4a8000 {
	compatible = "qcom,msm-tsens";
	reg = <0xfc4a8000 0x10>,
		<0xfc4b8000 0x1ff>;
	reg-names = "tsens_srot_physical",
		    "tsens_tm_physical";
	interrupts = <0 184 0>;
	interrupt-names = "tsens-upper-lower";
};
+10 −0
Original line number Diff line number Diff line
@@ -460,6 +460,16 @@ config GENERIC_ADC_THERMAL
	  to this driver. This driver reports the temperature by reading ADC
	  channel and converts it to temperature based on lookup table.

config THERMAL_TSENS
	tristate "Qualcomm Technologies Inc. TSENS Temperature driver"
	depends on THERMAL
	help
	  This enables the thermal sysfs driver for the TSENS device. It shows
	  up in Sysfs as a thermal zone with multiple trip points. Also able
	  to set threshold temperature for both warm and cool and update
	  thermal userspace client when a threshold is reached. Warm/Cool
	  temperature thresholds can be set independently for each sensor.

menu "Qualcomm thermal drivers"
depends on (ARCH_QCOM && OF) || COMPILE_TEST
source "drivers/thermal/qcom/Kconfig"
+1 −0
Original line number Diff line number Diff line
@@ -61,3 +61,4 @@ obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
obj-$(CONFIG_GENERIC_ADC_THERMAL)	+= thermal-generic-adc.o
obj-$(CONFIG_ZX2967_THERMAL)	+= zx2967_thermal.o
obj-$(CONFIG_UNIPHIER_THERMAL)	+= uniphier_thermal.o
obj-$(CONFIG_THERMAL_TSENS)	+= msm-tsens.o tsens2xxx.o tsens-dbg.o
+261 −0
Original line number Diff line number Diff line
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include "tsens.h"

LIST_HEAD(tsens_device_list);

static int tsens_get_temp(void *data, int *temp)
{
	struct tsens_sensor *s = data;
	struct tsens_device *tmdev = s->tmdev;

	return tmdev->ops->get_temp(s, temp);
}

static int tsens_set_trip_temp(void *data, int low_temp, int high_temp)
{
	struct tsens_sensor *s = data;
	struct tsens_device *tmdev = s->tmdev;

	if (tmdev->ops->set_trips)
		return tmdev->ops->set_trips(s, low_temp, high_temp);

	return 0;
}

static int tsens_init(struct tsens_device *tmdev)
{
	return tmdev->ops->hw_init(tmdev);
}

static int tsens_register_interrupts(struct tsens_device *tmdev)
{
	if (tmdev->ops->interrupts_reg)
		return tmdev->ops->interrupts_reg(tmdev);

	return 0;
}

static const struct of_device_id tsens_table[] = {
	{	.compatible = "qcom,msm8996-tsens",
		.data = &data_tsens2xxx,
	},
	{	.compatible = "qcom,msm8953-tsens",
		.data = &data_tsens2xxx,
	},
	{	.compatible = "qcom,msm8998-tsens",
		.data = &data_tsens2xxx,
	},
	{	.compatible = "qcom,msmhamster-tsens",
		.data = &data_tsens2xxx,
	},
	{	.compatible = "qcom,sdm660-tsens",
		.data = &data_tsens23xx,
	},
	{	.compatible = "qcom,sdm630-tsens",
		.data = &data_tsens23xx,
	},
	{	.compatible = "qcom,sdm845-tsens",
		.data = &data_tsens24xx,
	},
	{	.compatible = "qcom,tsens24xx",
		.data = &data_tsens24xx,
	},
	{}
};
MODULE_DEVICE_TABLE(of, tsens_table);

static struct thermal_zone_of_device_ops tsens_tm_thermal_zone_ops = {
	.get_temp = tsens_get_temp,
	.set_trips = tsens_set_trip_temp,
};

static int get_device_tree_data(struct platform_device *pdev,
				struct tsens_device *tmdev)
{
	struct device_node *of_node = pdev->dev.of_node;
	const struct of_device_id *id;
	const struct tsens_data *data;
	struct resource *res_tsens_mem;

	if (!of_match_node(tsens_table, of_node)) {
		pr_err("Need to read SoC specific fuse map\n");
		return -ENODEV;
	}

	id = of_match_node(tsens_table, of_node);
	if (id == NULL) {
		pr_err("can not find tsens_table of_node\n");
		return -ENODEV;
	}

	data = id->data;
	tmdev->ops = data->ops;
	tmdev->ctrl_data = data;
	tmdev->pdev = pdev;

	if (!tmdev->ops || !tmdev->ops->hw_init || !tmdev->ops->get_temp) {
		pr_err("Invalid ops\n");
		return -EINVAL;
	}

	/* TSENS register region */
	res_tsens_mem = platform_get_resource_byname(pdev,
				IORESOURCE_MEM, "tsens_srot_physical");
	if (!res_tsens_mem) {
		pr_err("Could not get tsens physical address resource\n");
		return -EINVAL;
	}

	tmdev->tsens_srot_addr = devm_ioremap_resource(&pdev->dev,
							res_tsens_mem);
	if (IS_ERR(tmdev->tsens_srot_addr)) {
		dev_err(&pdev->dev, "Failed to IO map TSENS registers.\n");
		return PTR_ERR(tmdev->tsens_srot_addr);
	}

	/* TSENS TM register region */
	res_tsens_mem = platform_get_resource_byname(pdev,
				IORESOURCE_MEM, "tsens_tm_physical");
	if (!res_tsens_mem) {
		pr_err("Could not get tsens physical address resource\n");
		return -EINVAL;
	}

	tmdev->tsens_tm_addr = devm_ioremap_resource(&pdev->dev,
								res_tsens_mem);
	if (IS_ERR(tmdev->tsens_tm_addr)) {
		dev_err(&pdev->dev, "Failed to IO map TSENS TM registers.\n");
		return PTR_ERR(tmdev->tsens_tm_addr);
	}

	return 0;
}

static int tsens_thermal_zone_register(struct tsens_device *tmdev)
{
	int i = 0, sensor_missing = 0;

	for (i = 0; i < TSENS_MAX_SENSORS; i++) {
		tmdev->sensor[i].tmdev = tmdev;
		tmdev->sensor[i].hw_id = i;
		if (tmdev->ops->sensor_en(tmdev, i)) {
			tmdev->sensor[i].tzd =
				devm_thermal_zone_of_sensor_register(
				&tmdev->pdev->dev, i,
				&tmdev->sensor[i], &tsens_tm_thermal_zone_ops);
			if (IS_ERR(tmdev->sensor[i].tzd)) {
				pr_debug("Error registering sensor:%d\n", i);
				sensor_missing++;
				continue;
			}
		} else {
			pr_debug("Sensor not enabled:%d\n", i);
		}
	}

	if (sensor_missing == TSENS_MAX_SENSORS) {
		pr_err("No TSENS sensors to register?\n");
		return -ENODEV;
	}

	return 0;
}

static int tsens_tm_remove(struct platform_device *pdev)
{
	platform_set_drvdata(pdev, NULL);

	return 0;
}

int tsens_tm_probe(struct platform_device *pdev)
{
	struct tsens_device *tmdev = NULL;
	int rc;

	if (!(pdev->dev.of_node))
		return -ENODEV;

	tmdev = devm_kzalloc(&pdev->dev,
			sizeof(struct tsens_device) +
			TSENS_MAX_SENSORS *
			sizeof(struct tsens_sensor),
			GFP_KERNEL);
	if (tmdev == NULL)
		return -ENOMEM;

	rc = get_device_tree_data(pdev, tmdev);
	if (rc) {
		pr_err("Error reading TSENS DT\n");
		return rc;
	}

	rc = tsens_init(tmdev);
	if (rc) {
		pr_err("Error initializing TSENS controller\n");
		return rc;
	}

	rc = tsens_thermal_zone_register(tmdev);
	if (rc) {
		pr_err("Error registering the thermal zone\n");
		return rc;
	}

	rc = tsens_register_interrupts(tmdev);
	if (rc < 0) {
		pr_err("TSENS interrupt register failed:%d\n", rc);
		return rc;
	}

	list_add_tail(&tmdev->list, &tsens_device_list);
	platform_set_drvdata(pdev, tmdev);

	return rc;
}

static struct platform_driver tsens_tm_driver = {
	.probe = tsens_tm_probe,
	.remove = tsens_tm_remove,
	.driver = {
		.name = "msm-tsens",
		.owner = THIS_MODULE,
		.of_match_table = tsens_table,
	},
};

int __init tsens_tm_init_driver(void)
{
	return platform_driver_register(&tsens_tm_driver);
}
subsys_initcall(tsens_tm_init_driver);

static void __exit tsens_tm_deinit(void)
{
	platform_driver_unregister(&tsens_tm_driver);
}
module_exit(tsens_tm_deinit);

MODULE_ALIAS("platform:" TSENS_DRIVER_NAME);
MODULE_LICENSE("GPL v2");
+221 −0
Original line number Diff line number Diff line
/* Copyright (c) 2017, 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 <asm/arch_timer.h>
#include <linux/sched/clock.h>
#include "tsens.h"

/* debug defines */
#define	TSENS_DBG_BUS_ID_0			0
#define	TSENS_DBG_BUS_ID_1			1
#define	TSENS_DBG_BUS_ID_2			2
#define	TSENS_DBG_BUS_ID_15			15
#define	TSENS_DEBUG_LOOP_COUNT_ID_0		2
#define	TSENS_DEBUG_LOOP_COUNT			5
#define	TSENS_DEBUG_STATUS_REG_START		10
#define	TSENS_DEBUG_OFFSET_RANGE		16
#define	TSENS_DEBUG_OFFSET_WORD1		0x4
#define	TSENS_DEBUG_OFFSET_WORD2		0x8
#define	TSENS_DEBUG_OFFSET_WORD3		0xc
#define	TSENS_DEBUG_OFFSET_ROW			0x10
#define	TSENS_DEBUG_DECIDEGC			-950
#define	TSENS_DEBUG_CYCLE_MS			64
#define	TSENS_DEBUG_POLL_MS			200
#define	TSENS_DEBUG_BUS_ID2_MIN_CYCLE		50
#define	TSENS_DEBUG_BUS_ID2_MAX_CYCLE		51
#define	TSENS_DEBUG_ID_MASK_1_4			0xffffffe1
#define	DEBUG_SIZE				10

#define TSENS_DEBUG_CONTROL(n)			((n) + 0x130)
#define TSENS_DEBUG_DATA(n)			((n) + 0x134)

struct tsens_dbg_func {
	int (*dbg_func)(struct tsens_device *, u32, u32, int *);
};

static int tsens_dbg_log_temp_reads(struct tsens_device *data, u32 id,
					u32 dbg_type, int *temp)
{
	struct tsens_sensor *sensor;
	struct tsens_device *tmdev = NULL;
	u32 idx = 0;

	if (!data)
		return -EINVAL;

	pr_debug("%d %d\n", id, dbg_type);
	tmdev = data;
	sensor = &tmdev->sensor[id];
	idx = tmdev->tsens_dbg.sensor_dbg_info[sensor->hw_id].idx;
	tmdev->tsens_dbg.sensor_dbg_info[sensor->hw_id].temp[idx%10] = *temp;
	tmdev->tsens_dbg.sensor_dbg_info[sensor->hw_id].time_stmp[idx%10] =
					sched_clock();
	idx++;
	tmdev->tsens_dbg.sensor_dbg_info[sensor->hw_id].idx = idx;

	return 0;
}

static int tsens_dbg_log_interrupt_timestamp(struct tsens_device *data,
						u32 id, u32 dbg_type, int *val)
{
	struct tsens_device *tmdev = NULL;
	u32 idx = 0;

	if (!data)
		return -EINVAL;

	pr_debug("%d %d\n", id, dbg_type);
	tmdev = data;
	/* debug */
	idx = tmdev->tsens_dbg.irq_idx;
	tmdev->tsens_dbg.irq_time_stmp[idx%10] =
							sched_clock();
	tmdev->tsens_dbg.irq_idx++;

	return 0;
}

static int tsens_dbg_log_bus_id_data(struct tsens_device *data,
					u32 id, u32 dbg_type, int *val)
{
	struct tsens_device *tmdev = NULL;
	u32 loop = 0, i = 0;
	uint32_t r1, r2, r3, r4, offset = 0;
	unsigned int debug_dump;
	unsigned int debug_id = 0, cntrl_id = 0;
	void __iomem *srot_addr;
	void __iomem *controller_id_addr;
	void __iomem *debug_id_addr;
	void __iomem *debug_data_addr;

	if (!data)
		return -EINVAL;

	pr_debug("%d %d\n", id, dbg_type);
	tmdev = data;
	controller_id_addr = TSENS_CONTROLLER_ID(tmdev->tsens_tm_addr);
	debug_id_addr = TSENS_DEBUG_CONTROL(tmdev->tsens_tm_addr);
	debug_data_addr = TSENS_DEBUG_DATA(tmdev->tsens_tm_addr);
	srot_addr = TSENS_CTRL_ADDR(tmdev->tsens_srot_addr);

	cntrl_id = readl_relaxed(controller_id_addr);
	pr_err("Controller_id: 0x%x\n", cntrl_id);

	loop = 0;
	i = 0;
	debug_id = readl_relaxed(debug_id_addr);
	writel_relaxed((debug_id | (i << 1) | 1),
			TSENS_DEBUG_CONTROL(tmdev->tsens_tm_addr));
	while (loop < TSENS_DEBUG_LOOP_COUNT_ID_0) {
		debug_dump = readl_relaxed(debug_data_addr);
		r1 = readl_relaxed(debug_data_addr);
		r2 = readl_relaxed(debug_data_addr);
		r3 = readl_relaxed(debug_data_addr);
		r4 = readl_relaxed(debug_data_addr);
		pr_err("cntrl:%d, bus-id:%d value:0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n",
			cntrl_id, i, debug_dump, r1, r2, r3, r4);
		loop++;
	}

	for (i = TSENS_DBG_BUS_ID_1; i <= TSENS_DBG_BUS_ID_15; i++) {
		loop = 0;
		debug_id = readl_relaxed(debug_id_addr);
		debug_id = debug_id & TSENS_DEBUG_ID_MASK_1_4;
		writel_relaxed((debug_id | (i << 1) | 1),
				TSENS_DEBUG_CONTROL(tmdev->tsens_tm_addr));
		while (loop < TSENS_DEBUG_LOOP_COUNT) {
			debug_dump = readl_relaxed(debug_data_addr);
			pr_err("cntrl:%d, bus-id:%d with value: 0x%x\n",
				cntrl_id, i, debug_dump);
			if (i == TSENS_DBG_BUS_ID_2)
				usleep_range(
					TSENS_DEBUG_BUS_ID2_MIN_CYCLE,
					TSENS_DEBUG_BUS_ID2_MAX_CYCLE);
			loop++;
		}
	}

	pr_err("Start of TSENS TM dump\n");
	for (i = 0; i < TSENS_DEBUG_OFFSET_RANGE; i++) {
		r1 = readl_relaxed(controller_id_addr + offset);
		r2 = readl_relaxed(controller_id_addr + (offset +
					TSENS_DEBUG_OFFSET_WORD1));
		r3 = readl_relaxed(controller_id_addr +	(offset +
					TSENS_DEBUG_OFFSET_WORD2));
		r4 = readl_relaxed(controller_id_addr + (offset +
					TSENS_DEBUG_OFFSET_WORD3));

		pr_err("ctrl:%d:0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
			cntrl_id, offset, r1, r2, r3, r4);
		offset += TSENS_DEBUG_OFFSET_ROW;
	}

	offset = 0;
	pr_err("Start of TSENS SROT dump\n");
	for (i = 0; i < TSENS_DEBUG_OFFSET_RANGE; i++) {
		r1 = readl_relaxed(srot_addr + offset);
		r2 = readl_relaxed(srot_addr + (offset +
					TSENS_DEBUG_OFFSET_WORD1));
		r3 = readl_relaxed(srot_addr + (offset +
					TSENS_DEBUG_OFFSET_WORD2));
		r4 = readl_relaxed(srot_addr + (offset +
					TSENS_DEBUG_OFFSET_WORD3));

		pr_err("ctrl:%d:0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
			cntrl_id, offset, r1, r2, r3, r4);
		offset += TSENS_DEBUG_OFFSET_ROW;
	}

	loop = 0;
	while (loop < TSENS_DEBUG_LOOP_COUNT) {
		offset = TSENS_DEBUG_OFFSET_ROW *
				TSENS_DEBUG_STATUS_REG_START;
		pr_err("Start of TSENS TM dump %d\n", loop);
		/* Limited dump of the registers for the temperature */
		for (i = 0; i < TSENS_DEBUG_LOOP_COUNT; i++) {
			r1 = readl_relaxed(controller_id_addr + offset);
			r2 = readl_relaxed(controller_id_addr +
				(offset + TSENS_DEBUG_OFFSET_WORD1));
			r3 = readl_relaxed(controller_id_addr +
				(offset + TSENS_DEBUG_OFFSET_WORD2));
			r4 = readl_relaxed(controller_id_addr +
				(offset + TSENS_DEBUG_OFFSET_WORD3));

		pr_err("ctrl:%d:0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
			cntrl_id, offset, r1, r2, r3, r4);
			offset += TSENS_DEBUG_OFFSET_ROW;
		}
		loop++;
	}

	return 0;
}

static struct tsens_dbg_func dbg_arr[] = {
	[TSENS_DBG_LOG_TEMP_READS] = {tsens_dbg_log_temp_reads},
	[TSENS_DBG_LOG_INTERRUPT_TIMESTAMP] = {
			tsens_dbg_log_interrupt_timestamp},
	[TSENS_DBG_LOG_BUS_ID_DATA] = {tsens_dbg_log_bus_id_data},
};

int tsens2xxx_dbg(struct tsens_device *data, u32 id, u32 dbg_type, int *val)
{
	if (dbg_type >= TSENS_DBG_LOG_MAX)
		return -EINVAL;

	dbg_arr[dbg_type].dbg_func(data, id, dbg_type, val);

	return 0;
}
EXPORT_SYMBOL(tsens2xxx_dbg);
Loading