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

Commit 41b4cd9d authored by Siddartha Mohanadoss's avatar Siddartha Mohanadoss
Browse files

thermal: tsens: Add Temperature sensor driver



Temperature sensor (TSENS) driver registers with
the of_thermal framework to provide sysfs interface
to thermal clients to set trips and read temperature
from supported TSENS temperature.

The driver has a general API for supported operations
needed as part of registering with of_thermal such as
reading temperature and setting thresholds. These
functions call into supported controller for the
platform to program the thresholds and enable
the controller for temperature monitoring.
Minor controller updates can be abstracted as a
platform data within the TSENS controller driver.

The driver also supports debug capabilities as a
separate interface such as tracing temperature
reads done by individual clients for sensors and
tracing the time when programmed threshold interrupts
are tripped.

Change-Id: I6678cbe9ee370fba05263199cb1b5ccc3a4f7700
Signed-off-by: default avatarSiddartha Mohanadoss <smohanad@codeaurora.org>
parent 436b4a5f
Loading
Loading
Loading
Loading
+54 −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.
	       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_physical" for TSENS TM physical address region.
- reg-names : resource names used for the physical address of the TSENS
	      registers. Should be "tsens_physical" for physical address of the TSENS.
- 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.
- qcom,sensors : Total number of available Temperature sensors for TSENS.

Optional properties:
- qcom,sensor-id : If the flag is present map the TSENS sensors based on the
		remote sensors that are enabled in HW. Ensure the mapping is not
		more than the number of supported sensors.
- qcom,client-id : If the flag is present use it to identify the SW ID mapping
		used to associate it with the controller and the physical sensor
		mapping within the controller. The physical sensor mapping within
		each controller is done using the qcom,sensor-id property. If the
		property is not present the SW ID mapping with default from 0 to
		total number of supported sensors with each controller instance.

Example:

tsens@fc4a8000 {
	compatible = "qcom,msm-tsens";
	reg = <0xfc4a8000 0x2000>;,
	reg-names = "tsens_physical";
	interrupts = <0 184 0>;
	interrupt-names = "tsens-upper-lower";
	qcom,sensors = <11>;
};
+10 −0
Original line number Diff line number Diff line
@@ -454,6 +454,16 @@ config THERMAL_QPNP_ADC_TM
	  the sensor. Also able to set threshold temperature for both hot and cold
	  and update when a threshold is reached.

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
@@ -57,3 +57,4 @@ obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
obj-$(CONFIG_MTK_THERMAL)	+= mtk_thermal.o
obj-$(CONFIG_GENERIC_ADC_THERMAL)	+= thermal-generic-adc.o
obj-$(CONFIG_THERMAL_QPNP_ADC_TM)	+= qpnp-adc-tm.o
obj-$(CONFIG_THERMAL_TSENS)	+= msm-tsens.o tsens2xxx.o tsens-dbg.o
+288 −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 term_tm 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(struct tsens_sensor *s, int *temp)
{
	struct tsens_device *tmdev = s->tmdev;

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

static int tsens_set_trip_temp(struct tsens_sensor *s, int trip, int temp)
{
	struct tsens_device *tmdev = s->tmdev;

	if (tmdev->ops->set_trip_temp)
		return tmdev->ops->set_trip_temp(s, trip, temp);

	return 0;
}

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

	return 0;
}

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,
	},
	{}
};
MODULE_DEVICE_TABLE(of, tsens_table);

static struct thermal_zone_of_device_ops tsens_tm_thermal_zone_ops = {
	.get_temp = tsens_get_temp,
	.set_trip_temp = 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;
	u32 *hw_id, *client_id;
	u32 rc = 0, i, tsens_num_sensors = 0;
	int tsens_len;
	const struct of_device_id *id;
	const struct tsens_data *data;
	struct resource *res_tsens_mem, *res_mem = NULL;

	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;
	hw_id = devm_kzalloc(&pdev->dev,
		tsens_num_sensors * sizeof(u32), GFP_KERNEL);
	if (!hw_id)
		return -ENOMEM;

	client_id = devm_kzalloc(&pdev->dev,
		tsens_num_sensors * sizeof(u32), GFP_KERNEL);
	if (!client_id)
		return -ENOMEM;

	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_physical");
	if (!res_tsens_mem) {
		pr_err("Could not get tsens physical address resource\n");
		return -EINVAL;
	}

	tsens_len = res_tsens_mem->end - res_tsens_mem->start + 1;

	res_mem = request_mem_region(res_tsens_mem->start,
				tsens_len, res_tsens_mem->name);
	if (!res_mem) {
		pr_err("Request tsens physical memory region failed\n");
		return -EINVAL;
	}

	tmdev->tsens_addr = ioremap(res_mem->start, tsens_len);
	if (!tmdev->tsens_addr) {
		pr_err("Failed to IO map TSENS registers.\n");
		return -EINVAL;
	}

	rc = of_property_read_u32_array(of_node,
		"qcom,sensor-id", hw_id, tsens_num_sensors);
	if (rc) {
		pr_err("Default sensor id mapping\n");
		for (i = 0; i < tsens_num_sensors; i++)
			tmdev->sensor[i].hw_id = i;
	} else {
		pr_err("Use specified sensor id mapping\n");
		for (i = 0; i < tsens_num_sensors; i++)
			tmdev->sensor[i].hw_id = hw_id[i];
	}

	rc = of_property_read_u32_array(of_node,
		"qcom,client-id", client_id, tsens_num_sensors);
	if (rc) {
		for (i = 0; i < tsens_num_sensors; i++)
			tmdev->sensor[i].id = i;
		pr_debug("Default client id mapping\n");
	} else {
		for (i = 0; i < tsens_num_sensors; i++)
			tmdev->sensor[i].id = client_id[i];
		pr_debug("Use specified client id mapping\n");
	}

	return 0;
}

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

	for (i = 0; i < tmdev->num_sensors; i++) {
		tmdev->sensor[i].tmdev = tmdev;
		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_err("Error registering sensor:%d\n", i);
			continue;
		}
	}

	return rc;
}

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 device_node *of_node = pdev->dev.of_node;
	struct tsens_device *tmdev = NULL;
	u32 tsens_num_sensors = 0;
	int rc;

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

	rc = of_property_read_u32(of_node,
			"qcom,sensors", &tsens_num_sensors);
	if (rc || (!tsens_num_sensors)) {
		dev_err(&pdev->dev, "missing sensors\n");
		return -ENODEV;
	}

	tmdev = devm_kzalloc(&pdev->dev,
			sizeof(struct tsens_device) +
			tsens_num_sensors *
			sizeof(struct tsens_sensor),
			GFP_KERNEL);
	if (tmdev == NULL) {
		pr_err("%s: kzalloc() failed.\n", __func__);
		return -ENOMEM;
	}

	tmdev->num_sensors = tsens_num_sensors;

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

	rc = tsens_init(tmdev);
	if (rc)
		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");
+104 −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 term_tm 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 "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) + 0x1130)
#define TSENS_DEBUG_DATA(n)			((n) + 0x1134)

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.tsens_thread_iq_dbg.idx;
	tmdev->tsens_dbg.tsens_thread_iq_dbg.dbg_count[idx%10]++;
	tmdev->tsens_dbg.tsens_thread_iq_dbg.time_stmp[idx%10] =
							sched_clock();
	tmdev->tsens_dbg.tsens_thread_iq_dbg.idx++;

	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},
};

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