mirror of
https://github.com/rd-stuffs/msm-4.14.git
synced 2025-02-20 11:45:48 +08:00
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: Siddartha Mohanadoss <smohanad@codeaurora.org>
This commit is contained in:
parent
6122ce251e
commit
4304409ff4
46
Documentation/devicetree/bindings/thermal/tsens.txt
Normal file
46
Documentation/devicetree/bindings/thermal/tsens.txt
Normal file
@ -0,0 +1,46 @@
|
||||
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";
|
||||
};
|
@ -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"
|
||||
|
@ -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
drivers/thermal/msm-tsens.c
Normal file
261
drivers/thermal/msm-tsens.c
Normal file
@ -0,0 +1,261 @@
|
||||
/* 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
drivers/thermal/tsens-dbg.c
Normal file
221
drivers/thermal/tsens-dbg.c
Normal file
@ -0,0 +1,221 @@
|
||||
/* 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);
|
137
drivers/thermal/tsens.h
Normal file
137
drivers/thermal/tsens.h
Normal file
@ -0,0 +1,137 @@
|
||||
/* 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.
|
||||
*
|
||||
*/
|
||||
#ifndef __QCOM_TSENS_H__
|
||||
#define __QCOM_TSENS_H__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define DEBUG_SIZE 10
|
||||
#define TSENS_MAX_SENSORS 16
|
||||
#define TSENS_CONTROLLER_ID(n) (n)
|
||||
#define TSENS_CTRL_ADDR(n) (n)
|
||||
#define TSENS_TM_SN_STATUS(n) ((n) + 0xa0)
|
||||
|
||||
enum tsens_dbg_type {
|
||||
TSENS_DBG_POLL,
|
||||
TSENS_DBG_LOG_TEMP_READS,
|
||||
TSENS_DBG_LOG_INTERRUPT_TIMESTAMP,
|
||||
TSENS_DBG_LOG_BUS_ID_DATA,
|
||||
TSENS_DBG_LOG_MAX
|
||||
};
|
||||
|
||||
#define tsens_sec_to_msec_value 1000
|
||||
|
||||
struct tsens_device;
|
||||
|
||||
#if defined(CONFIG_THERMAL_TSENS)
|
||||
int tsens2xxx_dbg(struct tsens_device *data, u32 id, u32 dbg_type, int *temp);
|
||||
#else
|
||||
static inline int tsens2xxx_dbg(struct tsens_device *data, u32 id,
|
||||
u32 dbg_type, int *temp)
|
||||
{ return -ENXIO; }
|
||||
#endif
|
||||
|
||||
struct tsens_dbg {
|
||||
u32 idx;
|
||||
unsigned long long time_stmp[DEBUG_SIZE];
|
||||
unsigned long temp[DEBUG_SIZE];
|
||||
};
|
||||
|
||||
struct tsens_dbg_context {
|
||||
struct tsens_device *tmdev;
|
||||
struct tsens_dbg sensor_dbg_info[TSENS_MAX_SENSORS];
|
||||
int tsens_critical_wd_cnt;
|
||||
u32 irq_idx;
|
||||
unsigned long long irq_time_stmp[DEBUG_SIZE];
|
||||
struct delayed_work tsens_critical_poll_test;
|
||||
};
|
||||
|
||||
struct tsens_context {
|
||||
enum thermal_device_mode high_th_state;
|
||||
enum thermal_device_mode low_th_state;
|
||||
enum thermal_device_mode crit_th_state;
|
||||
int high_temp;
|
||||
int low_temp;
|
||||
int crit_temp;
|
||||
};
|
||||
|
||||
struct tsens_sensor {
|
||||
struct tsens_device *tmdev;
|
||||
struct thermal_zone_device *tzd;
|
||||
u32 hw_id;
|
||||
u32 id;
|
||||
const char *sensor_name;
|
||||
struct tsens_context thr_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tsens_ops - operations as supported by the tsens device
|
||||
* @init: Function to initialize the tsens device
|
||||
* @get_temp: Function which returns the temp in millidegC
|
||||
*/
|
||||
struct tsens_ops {
|
||||
int (*hw_init)(struct tsens_device *);
|
||||
int (*get_temp)(struct tsens_sensor *, int *);
|
||||
int (*set_trips)(struct tsens_sensor *, int, int);
|
||||
int (*interrupts_reg)(struct tsens_device *);
|
||||
int (*dbg)(struct tsens_device *, u32, u32, int *);
|
||||
int (*sensor_en)(struct tsens_device *, u32);
|
||||
};
|
||||
|
||||
struct tsens_irqs {
|
||||
const char *name;
|
||||
irqreturn_t (*handler)(int, void *);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tsens_data - tsens instance specific data
|
||||
* @num_sensors: Max number of sensors supported by platform
|
||||
* @ops: operations the tsens instance supports
|
||||
* @hw_ids: Subset of sensors ids supported by platform, if not the first n
|
||||
*/
|
||||
struct tsens_data {
|
||||
const u32 num_sensors;
|
||||
const struct tsens_ops *ops;
|
||||
unsigned int *hw_ids;
|
||||
u32 temp_factor;
|
||||
bool cycle_monitor;
|
||||
u32 cycle_compltn_monitor_mask;
|
||||
bool wd_bark;
|
||||
u32 wd_bark_mask;
|
||||
};
|
||||
|
||||
struct tsens_device {
|
||||
struct device *dev;
|
||||
struct platform_device *pdev;
|
||||
struct list_head list;
|
||||
struct regmap *map;
|
||||
struct regmap_field *status_field;
|
||||
void __iomem *tsens_srot_addr;
|
||||
void __iomem *tsens_tm_addr;
|
||||
const struct tsens_ops *ops;
|
||||
struct tsens_dbg_context tsens_dbg;
|
||||
spinlock_t tsens_crit_lock;
|
||||
spinlock_t tsens_upp_low_lock;
|
||||
const struct tsens_data *ctrl_data;
|
||||
struct tsens_sensor sensor[0];
|
||||
};
|
||||
|
||||
extern const struct tsens_data data_tsens2xxx, data_tsens23xx, data_tsens24xx;
|
||||
|
||||
#endif /* __QCOM_TSENS_H__ */
|
642
drivers/thermal/tsens2xxx.c
Normal file
642
drivers/thermal/tsens2xxx.c
Normal file
@ -0,0 +1,642 @@
|
||||
/* Copyright (c) 2012-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/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include "tsens.h"
|
||||
#include "thermal_core.h"
|
||||
|
||||
#define TSENS_DRIVER_NAME "msm-tsens"
|
||||
|
||||
#define TSENS_TM_INT_EN(n) ((n) + 0x4)
|
||||
#define TSENS_TM_CRITICAL_INT_STATUS(n) ((n) + 0x14)
|
||||
#define TSENS_TM_CRITICAL_INT_CLEAR(n) ((n) + 0x18)
|
||||
#define TSENS_TM_CRITICAL_INT_MASK(n) ((n) + 0x1c)
|
||||
#define TSENS_TM_CRITICAL_WD_BARK BIT(31)
|
||||
#define TSENS_TM_CRITICAL_CYCLE_MONITOR BIT(30)
|
||||
#define TSENS_TM_CRITICAL_INT_EN BIT(2)
|
||||
#define TSENS_TM_UPPER_INT_EN BIT(1)
|
||||
#define TSENS_TM_LOWER_INT_EN BIT(0)
|
||||
#define TSENS_TM_SN_UPPER_LOWER_THRESHOLD(n) ((n) + 0x20)
|
||||
#define TSENS_TM_SN_ADDR_OFFSET 0x4
|
||||
#define TSENS_TM_UPPER_THRESHOLD_SET(n) ((n) << 12)
|
||||
#define TSENS_TM_UPPER_THRESHOLD_VALUE_SHIFT(n) ((n) >> 12)
|
||||
#define TSENS_TM_LOWER_THRESHOLD_VALUE(n) ((n) & 0xfff)
|
||||
#define TSENS_TM_UPPER_THRESHOLD_VALUE(n) (((n) & 0xfff000) >> 12)
|
||||
#define TSENS_TM_UPPER_THRESHOLD_MASK 0xfff000
|
||||
#define TSENS_TM_LOWER_THRESHOLD_MASK 0xfff
|
||||
#define TSENS_TM_UPPER_THRESHOLD_SHIFT 12
|
||||
#define TSENS_TM_SN_CRITICAL_THRESHOLD(n) ((n) + 0x60)
|
||||
#define TSENS_STATUS_ADDR_OFFSET 2
|
||||
#define TSENS_TM_UPPER_INT_MASK(n) (((n) & 0xffff0000) >> 16)
|
||||
#define TSENS_TM_LOWER_INT_MASK(n) ((n) & 0xffff)
|
||||
#define TSENS_TM_UPPER_LOWER_INT_STATUS(n) ((n) + 0x8)
|
||||
#define TSENS_TM_UPPER_LOWER_INT_CLEAR(n) ((n) + 0xc)
|
||||
#define TSENS_TM_UPPER_LOWER_INT_MASK(n) ((n) + 0x10)
|
||||
#define TSENS_TM_UPPER_INT_SET(n) (1 << (n + 16))
|
||||
#define TSENS_TM_SN_CRITICAL_THRESHOLD_MASK 0xfff
|
||||
#define TSENS_TM_SN_STATUS_VALID_BIT BIT(21)
|
||||
#define TSENS_TM_SN_STATUS_CRITICAL_STATUS BIT(19)
|
||||
#define TSENS_TM_SN_STATUS_UPPER_STATUS BIT(18)
|
||||
#define TSENS_TM_SN_STATUS_LOWER_STATUS BIT(17)
|
||||
#define TSENS_TM_SN_LAST_TEMP_MASK 0xfff
|
||||
#define TSENS_TM_CODE_BIT_MASK 0xfff
|
||||
#define TSENS_TM_CODE_SIGN_BIT 0x800
|
||||
#define TSENS_TM_SCALE_DECI_MILLIDEG 100
|
||||
#define TSENS_DEBUG_WDOG_TRIGGER_COUNT 5
|
||||
#define TSENS_TM_WATCHDOG_LOG(n) ((n) + 0x13c)
|
||||
|
||||
#define TSENS_EN BIT(0)
|
||||
#define TSENS_CTRL_SENSOR_EN_MASK(n) ((n >> 3) & 0xffff)
|
||||
|
||||
static void msm_tsens_convert_temp(int last_temp, int *temp)
|
||||
{
|
||||
int code_mask = ~TSENS_TM_CODE_BIT_MASK;
|
||||
|
||||
if (last_temp & TSENS_TM_CODE_SIGN_BIT) {
|
||||
/* Sign extension for negative value */
|
||||
last_temp |= code_mask;
|
||||
}
|
||||
|
||||
*temp = last_temp * TSENS_TM_SCALE_DECI_MILLIDEG;
|
||||
}
|
||||
|
||||
static int tsens2xxx_get_temp(struct tsens_sensor *sensor, int *temp)
|
||||
{
|
||||
struct tsens_device *tmdev = NULL;
|
||||
unsigned int code;
|
||||
void __iomem *sensor_addr;
|
||||
int last_temp = 0, last_temp2 = 0, last_temp3 = 0;
|
||||
|
||||
if (!sensor)
|
||||
return -EINVAL;
|
||||
|
||||
tmdev = sensor->tmdev;
|
||||
sensor_addr = TSENS_TM_SN_STATUS(tmdev->tsens_tm_addr);
|
||||
|
||||
code = readl_relaxed_no_log(sensor_addr +
|
||||
(sensor->hw_id << TSENS_STATUS_ADDR_OFFSET));
|
||||
last_temp = code & TSENS_TM_SN_LAST_TEMP_MASK;
|
||||
|
||||
if (code & TSENS_TM_SN_STATUS_VALID_BIT) {
|
||||
msm_tsens_convert_temp(last_temp, temp);
|
||||
goto dbg;
|
||||
}
|
||||
|
||||
code = readl_relaxed_no_log(sensor_addr +
|
||||
(sensor->hw_id << TSENS_STATUS_ADDR_OFFSET));
|
||||
last_temp2 = code & TSENS_TM_SN_LAST_TEMP_MASK;
|
||||
if (code & TSENS_TM_SN_STATUS_VALID_BIT) {
|
||||
last_temp = last_temp2;
|
||||
msm_tsens_convert_temp(last_temp, temp);
|
||||
goto dbg;
|
||||
}
|
||||
|
||||
code = readl_relaxed_no_log(sensor_addr +
|
||||
(sensor->hw_id <<
|
||||
TSENS_STATUS_ADDR_OFFSET));
|
||||
last_temp3 = code & TSENS_TM_SN_LAST_TEMP_MASK;
|
||||
if (code & TSENS_TM_SN_STATUS_VALID_BIT) {
|
||||
last_temp = last_temp3;
|
||||
msm_tsens_convert_temp(last_temp, temp);
|
||||
goto dbg;
|
||||
}
|
||||
|
||||
if (last_temp == last_temp2)
|
||||
last_temp = last_temp2;
|
||||
else if (last_temp2 == last_temp3)
|
||||
last_temp = last_temp3;
|
||||
|
||||
msm_tsens_convert_temp(last_temp, temp);
|
||||
|
||||
dbg:
|
||||
if (tmdev->ops->dbg)
|
||||
tmdev->ops->dbg(tmdev, (u32) sensor->hw_id,
|
||||
TSENS_DBG_LOG_TEMP_READS, temp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsens_tm_activate_trip_type(struct tsens_sensor *tm_sensor,
|
||||
int trip, enum thermal_device_mode mode)
|
||||
{
|
||||
struct tsens_device *tmdev = NULL;
|
||||
unsigned int reg_cntl, mask;
|
||||
int rc = 0;
|
||||
|
||||
/* clear the interrupt and unmask */
|
||||
if (!tm_sensor || trip < 0)
|
||||
return -EINVAL;
|
||||
|
||||
tmdev = tm_sensor->tmdev;
|
||||
if (!tmdev)
|
||||
return -EINVAL;
|
||||
|
||||
|
||||
mask = (tm_sensor->hw_id);
|
||||
switch (trip) {
|
||||
case THERMAL_TRIP_CRITICAL:
|
||||
tmdev->sensor[tm_sensor->hw_id].thr_state.crit_th_state = mode;
|
||||
reg_cntl = readl_relaxed(TSENS_TM_CRITICAL_INT_MASK
|
||||
(tmdev->tsens_tm_addr));
|
||||
if (mode == THERMAL_DEVICE_DISABLED)
|
||||
writel_relaxed(reg_cntl | (1 << mask),
|
||||
(TSENS_TM_CRITICAL_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
else
|
||||
writel_relaxed(reg_cntl & ~(1 << mask),
|
||||
(TSENS_TM_CRITICAL_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
break;
|
||||
case THERMAL_TRIP_CONFIGURABLE_HI:
|
||||
tmdev->sensor[tm_sensor->hw_id].thr_state.high_th_state = mode;
|
||||
reg_cntl = readl_relaxed(TSENS_TM_UPPER_LOWER_INT_MASK
|
||||
(tmdev->tsens_tm_addr));
|
||||
if (mode == THERMAL_DEVICE_DISABLED)
|
||||
writel_relaxed(reg_cntl |
|
||||
(TSENS_TM_UPPER_INT_SET(mask)),
|
||||
(TSENS_TM_UPPER_LOWER_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
else
|
||||
writel_relaxed(reg_cntl &
|
||||
~(TSENS_TM_UPPER_INT_SET(mask)),
|
||||
(TSENS_TM_UPPER_LOWER_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
break;
|
||||
case THERMAL_TRIP_CONFIGURABLE_LOW:
|
||||
tmdev->sensor[tm_sensor->hw_id].thr_state.low_th_state = mode;
|
||||
reg_cntl = readl_relaxed(TSENS_TM_UPPER_LOWER_INT_MASK
|
||||
(tmdev->tsens_tm_addr));
|
||||
if (mode == THERMAL_DEVICE_DISABLED)
|
||||
writel_relaxed(reg_cntl | (1 << mask),
|
||||
(TSENS_TM_UPPER_LOWER_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
else
|
||||
writel_relaxed(reg_cntl & ~(1 << mask),
|
||||
(TSENS_TM_UPPER_LOWER_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
break;
|
||||
default:
|
||||
rc = -EINVAL;
|
||||
}
|
||||
|
||||
/* Activate and enable the respective trip threshold setting */
|
||||
mb();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int tsens2xxx_set_trip_temp(struct tsens_sensor *tm_sensor,
|
||||
int low_temp, int high_temp)
|
||||
{
|
||||
unsigned int reg_cntl;
|
||||
unsigned long flags;
|
||||
struct tsens_device *tmdev = NULL;
|
||||
int rc = 0;
|
||||
|
||||
if (!tm_sensor)
|
||||
return -EINVAL;
|
||||
|
||||
tmdev = tm_sensor->tmdev;
|
||||
if (!tmdev)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&tmdev->tsens_upp_low_lock, flags);
|
||||
|
||||
if (high_temp != INT_MAX) {
|
||||
tmdev->sensor[tm_sensor->hw_id].thr_state.high_temp = high_temp;
|
||||
reg_cntl = readl_relaxed((TSENS_TM_SN_UPPER_LOWER_THRESHOLD
|
||||
(tmdev->tsens_tm_addr)) +
|
||||
(tm_sensor->hw_id *
|
||||
TSENS_TM_SN_ADDR_OFFSET));
|
||||
high_temp /= TSENS_TM_SCALE_DECI_MILLIDEG;
|
||||
high_temp = TSENS_TM_UPPER_THRESHOLD_SET(high_temp);
|
||||
high_temp &= TSENS_TM_UPPER_THRESHOLD_MASK;
|
||||
reg_cntl &= ~TSENS_TM_UPPER_THRESHOLD_MASK;
|
||||
writel_relaxed(reg_cntl | high_temp,
|
||||
(TSENS_TM_SN_UPPER_LOWER_THRESHOLD
|
||||
(tmdev->tsens_tm_addr) +
|
||||
(tm_sensor->hw_id * TSENS_TM_SN_ADDR_OFFSET)));
|
||||
}
|
||||
|
||||
if (low_temp != INT_MIN) {
|
||||
tmdev->sensor[tm_sensor->hw_id].thr_state.low_temp = low_temp;
|
||||
reg_cntl = readl_relaxed((TSENS_TM_SN_UPPER_LOWER_THRESHOLD
|
||||
(tmdev->tsens_tm_addr)) +
|
||||
(tm_sensor->hw_id *
|
||||
TSENS_TM_SN_ADDR_OFFSET));
|
||||
low_temp /= TSENS_TM_SCALE_DECI_MILLIDEG;
|
||||
low_temp &= TSENS_TM_LOWER_THRESHOLD_MASK;
|
||||
reg_cntl &= ~TSENS_TM_LOWER_THRESHOLD_MASK;
|
||||
writel_relaxed(reg_cntl | low_temp,
|
||||
(TSENS_TM_SN_UPPER_LOWER_THRESHOLD
|
||||
(tmdev->tsens_tm_addr) +
|
||||
(tm_sensor->hw_id * TSENS_TM_SN_ADDR_OFFSET)));
|
||||
}
|
||||
|
||||
/* Set trip temperature thresholds */
|
||||
mb();
|
||||
|
||||
if (high_temp != INT_MAX) {
|
||||
rc = tsens_tm_activate_trip_type(tm_sensor,
|
||||
THERMAL_TRIP_CONFIGURABLE_HI,
|
||||
THERMAL_DEVICE_ENABLED);
|
||||
if (rc) {
|
||||
pr_err("trip high enable error :%d\n", rc);
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
rc = tsens_tm_activate_trip_type(tm_sensor,
|
||||
THERMAL_TRIP_CONFIGURABLE_HI,
|
||||
THERMAL_DEVICE_DISABLED);
|
||||
if (rc) {
|
||||
pr_err("trip high disable error :%d\n", rc);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (low_temp != INT_MIN) {
|
||||
rc = tsens_tm_activate_trip_type(tm_sensor,
|
||||
THERMAL_TRIP_CONFIGURABLE_LOW,
|
||||
THERMAL_DEVICE_ENABLED);
|
||||
if (rc) {
|
||||
pr_err("trip low enable activation error :%d\n", rc);
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
rc = tsens_tm_activate_trip_type(tm_sensor,
|
||||
THERMAL_TRIP_CONFIGURABLE_LOW,
|
||||
THERMAL_DEVICE_DISABLED);
|
||||
if (rc) {
|
||||
pr_err("trip low disable error :%d\n", rc);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
spin_unlock_irqrestore(&tmdev->tsens_upp_low_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static irqreturn_t tsens_tm_critical_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct tsens_device *tm = data;
|
||||
unsigned int i, status, wd_log, wd_mask;
|
||||
unsigned long flags;
|
||||
void __iomem *sensor_status_addr, *sensor_int_mask_addr;
|
||||
void __iomem *sensor_critical_addr;
|
||||
void __iomem *wd_critical_addr, *wd_log_addr;
|
||||
|
||||
sensor_status_addr = TSENS_TM_SN_STATUS(tm->tsens_tm_addr);
|
||||
sensor_int_mask_addr =
|
||||
TSENS_TM_CRITICAL_INT_MASK(tm->tsens_tm_addr);
|
||||
sensor_critical_addr =
|
||||
TSENS_TM_SN_CRITICAL_THRESHOLD(tm->tsens_tm_addr);
|
||||
wd_critical_addr =
|
||||
TSENS_TM_CRITICAL_INT_STATUS(tm->tsens_tm_addr);
|
||||
wd_log_addr = TSENS_TM_WATCHDOG_LOG(tm->tsens_tm_addr);
|
||||
|
||||
if (tm->ctrl_data->wd_bark) {
|
||||
wd_mask = readl_relaxed(wd_critical_addr);
|
||||
if (wd_mask & TSENS_TM_CRITICAL_WD_BARK) {
|
||||
/*
|
||||
* Clear watchdog interrupt and
|
||||
* increment global wd count
|
||||
*/
|
||||
writel_relaxed(wd_mask | TSENS_TM_CRITICAL_WD_BARK,
|
||||
(TSENS_TM_CRITICAL_INT_CLEAR
|
||||
(tm->tsens_tm_addr)));
|
||||
writel_relaxed(wd_mask & ~(TSENS_TM_CRITICAL_WD_BARK),
|
||||
(TSENS_TM_CRITICAL_INT_CLEAR
|
||||
(tm->tsens_tm_addr)));
|
||||
wd_log = readl_relaxed(wd_log_addr);
|
||||
if (wd_log >= TSENS_DEBUG_WDOG_TRIGGER_COUNT) {
|
||||
pr_err("Watchdog count:%d\n", wd_log);
|
||||
if (tm->ops->dbg)
|
||||
tm->ops->dbg(tm, 0,
|
||||
TSENS_DBG_LOG_BUS_ID_DATA, NULL);
|
||||
BUG();
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < TSENS_MAX_SENSORS; i++) {
|
||||
int int_mask, int_mask_val;
|
||||
u32 addr_offset;
|
||||
|
||||
if (IS_ERR(tm->sensor[i].tzd))
|
||||
continue;
|
||||
|
||||
spin_lock_irqsave(&tm->tsens_crit_lock, flags);
|
||||
addr_offset = tm->sensor[i].hw_id *
|
||||
TSENS_TM_SN_ADDR_OFFSET;
|
||||
status = readl_relaxed(sensor_status_addr + addr_offset);
|
||||
int_mask = readl_relaxed(sensor_int_mask_addr);
|
||||
|
||||
if ((status & TSENS_TM_SN_STATUS_CRITICAL_STATUS) &&
|
||||
!(int_mask & (1 << tm->sensor[i].hw_id))) {
|
||||
int_mask = readl_relaxed(sensor_int_mask_addr);
|
||||
int_mask_val = (1 << tm->sensor[i].hw_id);
|
||||
/* Mask the corresponding interrupt for the sensors */
|
||||
writel_relaxed(int_mask | int_mask_val,
|
||||
TSENS_TM_CRITICAL_INT_MASK(
|
||||
tm->tsens_tm_addr));
|
||||
/* Clear the corresponding sensors interrupt */
|
||||
writel_relaxed(int_mask_val,
|
||||
TSENS_TM_CRITICAL_INT_CLEAR
|
||||
(tm->tsens_tm_addr));
|
||||
writel_relaxed(0,
|
||||
TSENS_TM_CRITICAL_INT_CLEAR(
|
||||
tm->tsens_tm_addr));
|
||||
tm->sensor[i].thr_state.crit_th_state =
|
||||
THERMAL_DEVICE_DISABLED;
|
||||
}
|
||||
spin_unlock_irqrestore(&tm->tsens_crit_lock, flags);
|
||||
}
|
||||
|
||||
/* Mask critical interrupt */
|
||||
mb();
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t tsens_tm_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct tsens_device *tm = data;
|
||||
unsigned int i, status, threshold, temp;
|
||||
unsigned long flags;
|
||||
void __iomem *sensor_status_addr;
|
||||
void __iomem *sensor_int_mask_addr;
|
||||
void __iomem *sensor_upper_lower_addr;
|
||||
u32 addr_offset = 0;
|
||||
|
||||
sensor_status_addr = TSENS_TM_SN_STATUS(tm->tsens_tm_addr);
|
||||
sensor_int_mask_addr =
|
||||
TSENS_TM_UPPER_LOWER_INT_MASK(tm->tsens_tm_addr);
|
||||
sensor_upper_lower_addr =
|
||||
TSENS_TM_SN_UPPER_LOWER_THRESHOLD(tm->tsens_tm_addr);
|
||||
|
||||
for (i = 0; i < TSENS_MAX_SENSORS; i++) {
|
||||
bool upper_thr = false, lower_thr = false;
|
||||
int int_mask, int_mask_val = 0, rc;
|
||||
|
||||
if (IS_ERR(tm->sensor[i].tzd))
|
||||
continue;
|
||||
|
||||
rc = tsens2xxx_get_temp(&tm->sensor[i], &temp);
|
||||
if (rc) {
|
||||
pr_debug("Error:%d reading temp sensor:%d\n", rc, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&tm->tsens_upp_low_lock, flags);
|
||||
addr_offset = tm->sensor[i].hw_id *
|
||||
TSENS_TM_SN_ADDR_OFFSET;
|
||||
status = readl_relaxed(sensor_status_addr + addr_offset);
|
||||
threshold = readl_relaxed(sensor_upper_lower_addr +
|
||||
addr_offset);
|
||||
int_mask = readl_relaxed(sensor_int_mask_addr);
|
||||
|
||||
if ((status & TSENS_TM_SN_STATUS_UPPER_STATUS) &&
|
||||
!(int_mask &
|
||||
(1 << (tm->sensor[i].hw_id + 16)))) {
|
||||
int_mask = readl_relaxed(sensor_int_mask_addr);
|
||||
int_mask_val = TSENS_TM_UPPER_INT_SET(
|
||||
tm->sensor[i].hw_id);
|
||||
/* Mask the corresponding interrupt for the sensors */
|
||||
writel_relaxed(int_mask | int_mask_val,
|
||||
TSENS_TM_UPPER_LOWER_INT_MASK(
|
||||
tm->tsens_tm_addr));
|
||||
/* Clear the corresponding sensors interrupt */
|
||||
writel_relaxed(int_mask_val,
|
||||
TSENS_TM_UPPER_LOWER_INT_CLEAR(
|
||||
tm->tsens_tm_addr));
|
||||
writel_relaxed(0,
|
||||
TSENS_TM_UPPER_LOWER_INT_CLEAR(
|
||||
tm->tsens_tm_addr));
|
||||
if (TSENS_TM_UPPER_THRESHOLD_VALUE(threshold) >
|
||||
(temp/TSENS_TM_SCALE_DECI_MILLIDEG)) {
|
||||
pr_debug("Re-arm high threshold\n");
|
||||
rc = tsens_tm_activate_trip_type(
|
||||
&tm->sensor[i],
|
||||
THERMAL_TRIP_CONFIGURABLE_HI,
|
||||
THERMAL_DEVICE_ENABLED);
|
||||
if (rc)
|
||||
pr_err("high rearm failed:%d\n", rc);
|
||||
} else {
|
||||
upper_thr = true;
|
||||
tm->sensor[i].thr_state.high_th_state =
|
||||
THERMAL_DEVICE_DISABLED;
|
||||
}
|
||||
}
|
||||
|
||||
if ((status & TSENS_TM_SN_STATUS_LOWER_STATUS) &&
|
||||
!(int_mask &
|
||||
(1 << tm->sensor[i].hw_id))) {
|
||||
int_mask = readl_relaxed(sensor_int_mask_addr);
|
||||
int_mask_val = (1 << tm->sensor[i].hw_id);
|
||||
/* Mask the corresponding interrupt for the sensors */
|
||||
writel_relaxed(int_mask | int_mask_val,
|
||||
TSENS_TM_UPPER_LOWER_INT_MASK(
|
||||
tm->tsens_tm_addr));
|
||||
/* Clear the corresponding sensors interrupt */
|
||||
writel_relaxed(int_mask_val,
|
||||
TSENS_TM_UPPER_LOWER_INT_CLEAR(
|
||||
tm->tsens_tm_addr));
|
||||
writel_relaxed(0,
|
||||
TSENS_TM_UPPER_LOWER_INT_CLEAR(
|
||||
tm->tsens_tm_addr));
|
||||
if (TSENS_TM_LOWER_THRESHOLD_VALUE(threshold)
|
||||
< (temp/TSENS_TM_SCALE_DECI_MILLIDEG)) {
|
||||
pr_debug("Re-arm low threshold\n");
|
||||
rc = tsens_tm_activate_trip_type(
|
||||
&tm->sensor[i],
|
||||
THERMAL_TRIP_CONFIGURABLE_LOW,
|
||||
THERMAL_DEVICE_ENABLED);
|
||||
if (rc)
|
||||
pr_err("low rearm failed:%d\n", rc);
|
||||
} else {
|
||||
lower_thr = true;
|
||||
tm->sensor[i].thr_state.low_th_state =
|
||||
THERMAL_DEVICE_DISABLED;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&tm->tsens_upp_low_lock, flags);
|
||||
|
||||
if (upper_thr || lower_thr) {
|
||||
/* Use id for multiple controllers */
|
||||
pr_debug("sensor:%d trigger temp (%d degC)\n",
|
||||
tm->sensor[i].hw_id, temp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable monitoring sensor trip threshold for triggered sensor */
|
||||
mb();
|
||||
|
||||
if (tm->ops->dbg)
|
||||
tm->ops->dbg(tm, 0, TSENS_DBG_LOG_INTERRUPT_TIMESTAMP, NULL);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int tsens2xxx_hw_sensor_en(struct tsens_device *tmdev,
|
||||
u32 sensor_id)
|
||||
{
|
||||
void __iomem *srot_addr;
|
||||
unsigned int srot_val, sensor_en;
|
||||
|
||||
srot_addr = TSENS_CTRL_ADDR(tmdev->tsens_srot_addr + 0x4);
|
||||
srot_val = readl_relaxed(srot_addr);
|
||||
srot_val = TSENS_CTRL_SENSOR_EN_MASK(srot_val);
|
||||
|
||||
sensor_en = ((1 << sensor_id) & srot_val);
|
||||
|
||||
return sensor_en;
|
||||
}
|
||||
|
||||
static int tsens2xxx_hw_init(struct tsens_device *tmdev)
|
||||
{
|
||||
void __iomem *srot_addr;
|
||||
void __iomem *sensor_int_mask_addr;
|
||||
unsigned int srot_val, crit_mask, crit_val;
|
||||
|
||||
srot_addr = TSENS_CTRL_ADDR(tmdev->tsens_srot_addr + 0x4);
|
||||
srot_val = readl_relaxed(srot_addr);
|
||||
if (!(srot_val & TSENS_EN)) {
|
||||
pr_err("TSENS device is not enabled\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (tmdev->ctrl_data->cycle_monitor) {
|
||||
sensor_int_mask_addr =
|
||||
TSENS_TM_CRITICAL_INT_MASK(tmdev->tsens_tm_addr);
|
||||
crit_mask = readl_relaxed(sensor_int_mask_addr);
|
||||
crit_val = TSENS_TM_CRITICAL_CYCLE_MONITOR;
|
||||
if (tmdev->ctrl_data->cycle_compltn_monitor_mask)
|
||||
writel_relaxed((crit_mask | crit_val),
|
||||
(TSENS_TM_CRITICAL_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
else
|
||||
writel_relaxed((crit_mask & ~crit_val),
|
||||
(TSENS_TM_CRITICAL_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
/*Update critical cycle monitoring*/
|
||||
mb();
|
||||
}
|
||||
|
||||
if (tmdev->ctrl_data->wd_bark) {
|
||||
sensor_int_mask_addr =
|
||||
TSENS_TM_CRITICAL_INT_MASK(tmdev->tsens_tm_addr);
|
||||
crit_mask = readl_relaxed(sensor_int_mask_addr);
|
||||
crit_val = TSENS_TM_CRITICAL_WD_BARK;
|
||||
if (tmdev->ctrl_data->wd_bark_mask)
|
||||
writel_relaxed((crit_mask | crit_val),
|
||||
(TSENS_TM_CRITICAL_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
else
|
||||
writel_relaxed((crit_mask & ~crit_val),
|
||||
(TSENS_TM_CRITICAL_INT_MASK
|
||||
(tmdev->tsens_tm_addr)));
|
||||
/*Update watchdog monitoring*/
|
||||
mb();
|
||||
}
|
||||
|
||||
writel_relaxed(TSENS_TM_CRITICAL_INT_EN |
|
||||
TSENS_TM_UPPER_INT_EN | TSENS_TM_LOWER_INT_EN,
|
||||
TSENS_TM_INT_EN(tmdev->tsens_tm_addr));
|
||||
|
||||
spin_lock_init(&tmdev->tsens_crit_lock);
|
||||
spin_lock_init(&tmdev->tsens_upp_low_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tsens_irqs tsens2xxx_irqs[] = {
|
||||
{ "tsens-upper-lower", tsens_tm_irq_thread},
|
||||
{ "tsens-critical", tsens_tm_critical_irq_thread},
|
||||
};
|
||||
|
||||
static int tsens2xxx_register_interrupts(struct tsens_device *tmdev)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
int i, rc;
|
||||
|
||||
if (!tmdev)
|
||||
return -EINVAL;
|
||||
|
||||
pdev = tmdev->pdev;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tsens2xxx_irqs); i++) {
|
||||
int irq;
|
||||
|
||||
irq = platform_get_irq_byname(pdev, tsens2xxx_irqs[i].name);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "failed to get irq %s\n",
|
||||
tsens2xxx_irqs[i].name);
|
||||
return irq;
|
||||
}
|
||||
|
||||
rc = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
||||
tsens2xxx_irqs[i].handler,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
tsens2xxx_irqs[i].name, tmdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "failed to get irq %s\n",
|
||||
tsens2xxx_irqs[i].name);
|
||||
return rc;
|
||||
}
|
||||
enable_irq_wake(irq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tsens_ops ops_tsens2xxx = {
|
||||
.hw_init = tsens2xxx_hw_init,
|
||||
.get_temp = tsens2xxx_get_temp,
|
||||
.set_trips = tsens2xxx_set_trip_temp,
|
||||
.interrupts_reg = tsens2xxx_register_interrupts,
|
||||
.dbg = tsens2xxx_dbg,
|
||||
.sensor_en = tsens2xxx_hw_sensor_en,
|
||||
};
|
||||
|
||||
const struct tsens_data data_tsens2xxx = {
|
||||
.cycle_monitor = false,
|
||||
.cycle_compltn_monitor_mask = 1,
|
||||
.wd_bark = false,
|
||||
.wd_bark_mask = 1,
|
||||
.ops = &ops_tsens2xxx,
|
||||
};
|
||||
|
||||
const struct tsens_data data_tsens23xx = {
|
||||
.cycle_monitor = true,
|
||||
.cycle_compltn_monitor_mask = 1,
|
||||
.wd_bark = true,
|
||||
.wd_bark_mask = 1,
|
||||
.ops = &ops_tsens2xxx,
|
||||
};
|
||||
|
||||
const struct tsens_data data_tsens24xx = {
|
||||
.cycle_monitor = true,
|
||||
.cycle_compltn_monitor_mask = 1,
|
||||
.wd_bark = true,
|
||||
/* Enable Watchdog monitoring by unmasking */
|
||||
.wd_bark_mask = 0,
|
||||
.ops = &ops_tsens2xxx,
|
||||
};
|
@ -83,6 +83,9 @@ enum thermal_trip_type {
|
||||
THERMAL_TRIP_PASSIVE,
|
||||
THERMAL_TRIP_HOT,
|
||||
THERMAL_TRIP_CRITICAL,
|
||||
THERMAL_TRIP_CONFIGURABLE_HI,
|
||||
THERMAL_TRIP_CONFIGURABLE_LOW,
|
||||
THERMAL_TRIP_CRITICAL_LOW,
|
||||
};
|
||||
|
||||
enum thermal_trend {
|
||||
|
Loading…
x
Reference in New Issue
Block a user