Merge "drm/msm/sde: update fast clear enable logic"
diff --git a/Documentation/devicetree/bindings/rtc/qpnp-rtc.txt b/Documentation/devicetree/bindings/rtc/qpnp-rtc.txt
new file mode 100644
index 0000000..e0934b2
--- /dev/null
+++ b/Documentation/devicetree/bindings/rtc/qpnp-rtc.txt
@@ -0,0 +1,60 @@
+* msm-qpnp-rtc
+
+msm-qpnp-rtc is a RTC driver that supports 32 bit RTC housed inside PMIC.
+Driver utilizes MSM SPMI interface to communicate with the RTC module.
+RTC device is divided into two sub-peripherals one which controls basic RTC
+and other for controlling alarm.
+
+[PMIC RTC Device Declarations]
+
+-Root Node-
+
+Required properties :
+ - compatible: Must be "qcom,qpnp-rtc"
+ - #address-cells: The number of cells dedicated to represent an address
+ This must be set to '1'.
+ - #size-cells: The number of cells dedicated to represent address
+ space range of a peripheral. This must be set to '1'.
+
+Optional properties:
+ - qcom,qpnp-rtc-write: This property enables/disables rtc write
+ operation. If not mentioned rtc driver keeps
+ rtc writes disabled.
+ 0 = Disable rtc writes.
+ 1 = Enable rtc writes.
+ - qcom,qpnp-rtc-alarm-pwrup: This property enables/disables feature of
+ powering up phone (from power down state)
+ through alarm interrupt.
+ If not mentioned rtc driver will disable
+ feature of powring-up phone through alarm.
+ 0 = Disable powering up of phone through
+ alarm interrupt.
+ 1 = Enable powering up of phone through
+ alarm interrupt.
+
+-Child Nodes-
+
+Required properties :
+ - reg : Specify the spmi offset and size for device.
+ - interrupts: Specifies alarm interrupt, only for rtc_alarm
+ sub-peripheral.
+
+Example:
+ qcom,pm8941_rtc {
+ compatible = "qcom,qpnp-rtc";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ qcom,qpnp-rtc-write = <0>;
+ qcom,qpnp-rtc-alarm-pwrup = <0>;
+
+ qcom,pm8941_rtc_rw@6000 {
+ reg = <0x6000 0x100>;
+ };
+
+ qcom,pm8941_rtc_alarm@6100 {
+ reg = <0x6100 0x100>;
+ interrupts = <0x0 0x61 0x1>;
+ };
+ };
+
+
diff --git a/Documentation/devicetree/bindings/thermal/tsens.txt b/Documentation/devicetree/bindings/thermal/tsens.txt
new file mode 100644
index 0000000..67ffaed
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/tsens.txt
@@ -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";
+};
diff --git a/arch/arm64/boot/dts/qcom/pm855.dtsi b/arch/arm64/boot/dts/qcom/pm855.dtsi
index 15252db..008d84e 100644
--- a/arch/arm64/boot/dts/qcom/pm855.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm855.dtsi
@@ -28,6 +28,22 @@
#thermal-sensor-cells = <0>;
qcom,temperature-threshold-set = <1>;
};
+
+ pm855_rtc: qcom,pm855_rtc {
+ compatible = "qcom,qpnp-rtc";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ qcom,qpnp-rtc-write = <0>;
+ qcom,qpnp-rtc-alarm-pwrup = <0>;
+
+ qcom,pm855_rtc_rw@6000 {
+ reg = <0x6000 0x100>;
+ };
+ qcom,pm855_rtc_alarm@6100 {
+ reg = <0x6100 0x100>;
+ interrupts = <0x0 0x61 0x1 IRQ_TYPE_NONE>;
+ };
+ };
};
qcom,pm855@1 {
diff --git a/arch/arm64/boot/dts/qcom/sdm855.dtsi b/arch/arm64/boot/dts/qcom/sdm855.dtsi
index ff29912..61f43d2 100644
--- a/arch/arm64/boot/dts/qcom/sdm855.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm855.dtsi
@@ -1944,6 +1944,28 @@
<DCC_READ 0x0c202244 1 0>;
};
+ tsens0: tsens@c222000 {
+ compatible = "qcom,tsens24xx";
+ reg = <0xc222000 0x4>,
+ <0xc263000 0x1ff>;
+ reg-names = "tsens_srot_physical",
+ "tsens_tm_physical";
+ interrupts = <0 506 0>, <0 508 0>;
+ interrupt-names = "tsens-upper-lower", "tsens-critical";
+ #thermal-sensor-cells = <1>;
+ };
+
+ tsens1: tsens@c223000 {
+ compatible = "qcom,tsens24xx";
+ reg = <0xc223000 0x4>,
+ <0xc265000 0x1ff>;
+ reg-names = "tsens_srot_physical",
+ "tsens_tm_physical";
+ interrupts = <0 507 0>, <0 509 0>;
+ interrupt-names = "tsens-upper-lower", "tsens-critical";
+ #thermal-sensor-cells = <1>;
+ };
+
thermal_zones: thermal-zones {
};
};
diff --git a/arch/arm64/configs/sdm855-perf_defconfig b/arch/arm64/configs/sdm855-perf_defconfig
index ae342ed..1765e14 100644
--- a/arch/arm64/configs/sdm855-perf_defconfig
+++ b/arch/arm64/configs/sdm855-perf_defconfig
@@ -269,6 +269,7 @@
CONFIG_POWER_RESET_SYSCON=y
CONFIG_THERMAL=y
CONFIG_QCOM_SPMI_TEMP_ALARM=y
+CONFIG_THERMAL_TSENS=y
CONFIG_MFD_SPMI_PMIC=y
CONFIG_REGULATOR=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
@@ -325,6 +326,7 @@
CONFIG_EDAC_KRYO_ARM64_PANIC_ON_CE=y
CONFIG_EDAC_KRYO_ARM64_PANIC_ON_UE=y
CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_QPNP=y
CONFIG_DMADEVICES=y
CONFIG_UIO=y
CONFIG_STAGING=y
@@ -373,7 +375,6 @@
CONFIG_QCOM_COMMAND_DB=y
CONFIG_QTI_RPMH_API=y
CONFIG_QCOM_GLINK=y
-CONFIG_MSM_JTAGV8=y
CONFIG_IIO=y
CONFIG_PWM=y
CONFIG_ARM_GIC_V3_ACL=y
@@ -403,6 +404,17 @@
# CONFIG_DEBUG_PREEMPT is not set
CONFIG_IPC_LOGGING=y
CONFIG_DEBUG_ALIGN_RODATA=y
+CONFIG_CORESIGHT=y
+CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y
+CONFIG_CORESIGHT_SOURCE_ETM4X=y
+CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y
+CONFIG_CORESIGHT_STM=y
+CONFIG_CORESIGHT_CTI=y
+CONFIG_CORESIGHT_TPDA=y
+CONFIG_CORESIGHT_TPDM=y
+CONFIG_CORESIGHT_HWEVENT=y
+CONFIG_CORESIGHT_DUMMY=y
+CONFIG_CORESIGHT_EVENT=y
CONFIG_SECURITY_PERF_EVENTS_RESTRICT=y
CONFIG_SECURITY=y
CONFIG_HARDENED_USERCOPY=y
diff --git a/arch/arm64/configs/sdm855_defconfig b/arch/arm64/configs/sdm855_defconfig
index 9716270..de7f430 100644
--- a/arch/arm64/configs/sdm855_defconfig
+++ b/arch/arm64/configs/sdm855_defconfig
@@ -277,6 +277,7 @@
CONFIG_POWER_RESET_SYSCON=y
CONFIG_THERMAL=y
CONFIG_QCOM_SPMI_TEMP_ALARM=y
+CONFIG_THERMAL_TSENS=y
CONFIG_MFD_SPMI_PMIC=y
CONFIG_REGULATOR=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
@@ -335,6 +336,7 @@
CONFIG_EDAC_QCOM_LLCC_PANIC_ON_CE=y
CONFIG_EDAC_QCOM_LLCC_PANIC_ON_UE=y
CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_QPNP=y
CONFIG_DMADEVICES=y
CONFIG_UIO=y
CONFIG_STAGING=y
@@ -383,10 +385,11 @@
CONFIG_QCOM_SECURE_BUFFER=y
CONFIG_ICNSS=y
CONFIG_ICNSS_DEBUG=y
+CONFIG_QCOM_BUS_SCALING=y
+CONFIG_QCOM_BUS_CONFIG_RPMH=y
CONFIG_QCOM_COMMAND_DB=y
CONFIG_QTI_RPMH_API=y
CONFIG_QCOM_GLINK=y
-CONFIG_MSM_JTAGV8=y
CONFIG_IIO=y
CONFIG_PWM=y
CONFIG_ARM_GIC_V3_ACL=y
@@ -460,6 +463,18 @@
CONFIG_BUG_ON_DATA_CORRUPTION=y
CONFIG_PID_IN_CONTEXTIDR=y
CONFIG_ARM64_STRICT_BREAK_BEFORE_MAKE=y
+CONFIG_CORESIGHT=y
+CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y
+CONFIG_CORESIGHT_SOURCE_ETM4X=y
+CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y
+CONFIG_CORESIGHT_STM=y
+CONFIG_CORESIGHT_CTI=y
+CONFIG_CORESIGHT_TPDA=y
+CONFIG_CORESIGHT_TPDM=y
+CONFIG_CORESIGHT_HWEVENT=y
+CONFIG_CORESIGHT_DUMMY=y
+CONFIG_CORESIGHT_TGU=y
+CONFIG_CORESIGHT_EVENT=y
CONFIG_SECURITY_PERF_EVENTS_RESTRICT=y
CONFIG_SECURITY=y
CONFIG_HARDENED_USERCOPY=y
diff --git a/drivers/clk/qcom/clk-regmap-divider.c b/drivers/clk/qcom/clk-regmap-divider.c
index 5348491..c314d2c 100644
--- a/drivers/clk/qcom/clk-regmap-divider.c
+++ b/drivers/clk/qcom/clk-regmap-divider.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014, 2017, The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
@@ -28,8 +28,10 @@ static long div_round_rate(struct clk_hw *hw, unsigned long rate,
{
struct clk_regmap_div *divider = to_clk_regmap_div(hw);
- return divider_round_rate(hw, rate, prate, NULL, divider->width,
- CLK_DIVIDER_ROUND_CLOSEST);
+ return divider_round_rate(hw, rate, prate, divider->table,
+ divider->width,
+ CLK_DIVIDER_ROUND_CLOSEST |
+ divider->flags);
}
static int div_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -39,8 +41,9 @@ static int div_set_rate(struct clk_hw *hw, unsigned long rate,
struct clk_regmap *clkr = ÷r->clkr;
u32 div;
- div = divider_get_val(rate, parent_rate, NULL, divider->width,
- CLK_DIVIDER_ROUND_CLOSEST);
+ div = divider_get_val(rate, parent_rate, divider->table,
+ divider->width, CLK_DIVIDER_ROUND_CLOSEST |
+ divider->flags);
return regmap_update_bits(clkr->regmap, divider->reg,
(BIT(divider->width) - 1) << divider->shift,
@@ -58,8 +61,8 @@ static unsigned long div_recalc_rate(struct clk_hw *hw,
div >>= divider->shift;
div &= BIT(divider->width) - 1;
- return divider_recalc_rate(hw, parent_rate, div, NULL,
- CLK_DIVIDER_ROUND_CLOSEST);
+ return divider_recalc_rate(hw, parent_rate, div, divider->table,
+ CLK_DIVIDER_ROUND_CLOSEST | divider->flags);
}
const struct clk_ops clk_regmap_div_ops = {
diff --git a/drivers/clk/qcom/clk-regmap-divider.h b/drivers/clk/qcom/clk-regmap-divider.h
index fc4492e..1c5e087 100644
--- a/drivers/clk/qcom/clk-regmap-divider.h
+++ b/drivers/clk/qcom/clk-regmap-divider.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014, 2017, The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
@@ -18,10 +18,12 @@
#include "clk-regmap.h"
struct clk_regmap_div {
- u32 reg;
- u32 shift;
- u32 width;
- struct clk_regmap clkr;
+ u32 reg;
+ u32 shift;
+ u32 width;
+ u32 flags;
+ const struct clk_div_table *table;
+ struct clk_regmap clkr;
};
extern const struct clk_ops clk_regmap_div_ops;
diff --git a/drivers/hwtracing/coresight/coresight-cti.c b/drivers/hwtracing/coresight/coresight-cti.c
index 621e08f..6c036bc 100644
--- a/drivers/hwtracing/coresight/coresight-cti.c
+++ b/drivers/hwtracing/coresight/coresight-cti.c
@@ -1451,8 +1451,8 @@ static int cti_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->cpu = -1;
cpu_node = of_parse_phandle(adev->dev.of_node, "cpu", 0);
if (cpu_node) {
- drvdata->cpu = pdata ? pdata->cpu : -1;
- if (drvdata->cpu == -1) {
+ drvdata->cpu = pdata ? pdata->cpu : -ENODEV;
+ if (drvdata->cpu == -ENODEV) {
dev_err(drvdata->dev, "CTI cpu node invalid\n");
return -EINVAL;
}
diff --git a/drivers/hwtracing/coresight/coresight-etm4x.c b/drivers/hwtracing/coresight/coresight-etm4x.c
index 212c942..ba736d1 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x.c
@@ -984,9 +984,9 @@ static int etm4_probe(struct amba_device *adev, const struct amba_id *id)
spin_lock_init(&drvdata->spinlock);
- drvdata->cpu = pdata ? pdata->cpu : -1;
+ drvdata->cpu = pdata ? pdata->cpu : -ENODEV;
- if (drvdata->cpu == -1) {
+ if (drvdata->cpu == -ENODEV) {
dev_info(dev, "CPU not available\n");
return -ENODEV;
}
diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c
index b71eda1..bab01b25 100644
--- a/drivers/hwtracing/coresight/coresight-tmc.c
+++ b/drivers/hwtracing/coresight/coresight-tmc.c
@@ -71,7 +71,7 @@ void tmc_enable_hw(struct tmc_drvdata *drvdata)
void tmc_disable_hw(struct tmc_drvdata *drvdata)
{
- drvdata->enable = true;
+ drvdata->enable = false;
writel_relaxed(0x0, drvdata->base + TMC_CTL);
}
@@ -461,7 +461,7 @@ static DEVICE_ATTR_RW(block_size);
static int tmc_iommu_init(struct tmc_drvdata *drvdata)
{
struct device_node *node = drvdata->dev->of_node;
- int atomic_ctx = 1, ret = 0;
+ int ret = 0;
if (!of_property_read_bool(node, "iommus"))
return 0;
diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c
index 5129a6f..591f970 100644
--- a/drivers/hwtracing/coresight/coresight.c
+++ b/drivers/hwtracing/coresight/coresight.c
@@ -158,6 +158,7 @@ static void coresight_disable_sink(struct coresight_device *csdev)
if (atomic_dec_return(csdev->refcnt) == 0) {
if (sink_ops(csdev)->disable) {
sink_ops(csdev)->disable(csdev);
+ csdev->enable = false;
csdev->activated = false;
}
}
diff --git a/drivers/hwtracing/coresight/of_coresight.c b/drivers/hwtracing/coresight/of_coresight.c
index 47834df..beb734f 100644
--- a/drivers/hwtracing/coresight/of_coresight.c
+++ b/drivers/hwtracing/coresight/of_coresight.c
@@ -109,9 +109,9 @@ int of_coresight_get_cpu(const struct device_node *node)
dn = of_parse_phandle(node, "cpu", 0);
- /* Affinity defaults to CPU0 */
+ /* Affinity defaults to invalid */
if (!dn)
- return 0;
+ return -ENODEV;
for_each_possible_cpu(cpu) {
np = of_cpu_device_node_get(cpu);
@@ -122,8 +122,8 @@ int of_coresight_get_cpu(const struct device_node *node)
}
of_node_put(dn);
- /* Affinity to CPU0 if no cpu nodes are found */
- return found ? cpu : 0;
+ /* Affinity to invalid if no cpu nodes are found */
+ return found ? cpu : -ENODEV;
}
EXPORT_SYMBOL_GPL(of_coresight_get_cpu);
@@ -207,8 +207,6 @@ of_get_coresight_platform_data(struct device *dev,
}
pdata->cpu = of_coresight_get_cpu(node);
- /* Affinity defaults to invalid */
- pdata->cpu = -1;
return pdata;
}
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_core.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_core.c
index e31f798..267f214 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_core.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_core.c
@@ -3194,6 +3194,26 @@ void sde_rotator_core_destroy(struct sde_rot_mgr *mgr)
devm_kfree(dev, mgr);
}
+void sde_rotator_core_dump(struct sde_rot_mgr *mgr)
+{
+ if (!mgr) {
+ SDEROT_ERR("null parameters\n");
+ return;
+ }
+
+ sde_rotator_resource_ctrl(mgr, true);
+ /* dump first snapshot */
+ if (mgr->ops_hw_dump_status)
+ mgr->ops_hw_dump_status(mgr->hw_data);
+
+ SDEROT_EVTLOG_TOUT_HANDLER("rot", "rot_dbg_bus", "vbif_dbg_bus");
+
+ /* dump second snapshot for comparison */
+ if (mgr->ops_hw_dump_status)
+ mgr->ops_hw_dump_status(mgr->hw_data);
+ sde_rotator_resource_ctrl(mgr, false);
+}
+
static void sde_rotator_suspend_cancel_rot_work(struct sde_rot_mgr *mgr)
{
struct sde_rot_file_private *priv, *priv_next;
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_core.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_core.h
index 3edb2d0..e23ed7a 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_core.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_core.h
@@ -478,6 +478,7 @@ struct sde_rot_mgr {
int (*ops_hw_get_downscale_caps)(struct sde_rot_mgr *mgr, char *caps,
int len);
int (*ops_hw_get_maxlinewidth)(struct sde_rot_mgr *mgr);
+ void (*ops_hw_dump_status)(struct sde_rot_mgr *mgr);
void *hw_data;
};
@@ -570,6 +571,12 @@ int sde_rotator_core_init(struct sde_rot_mgr **pmgr,
void sde_rotator_core_destroy(struct sde_rot_mgr *mgr);
/*
+ * sde_rotator_core_dump - perform register dump
+ * @mgr: Pointer to rotator manager
+ */
+void sde_rotator_core_dump(struct sde_rot_mgr *mgr);
+
+/*
* sde_rotator_session_open - open a new rotator per file session
* @mgr: Pointer to rotator manager
* @pprivate: Pointer to pointer of the newly initialized per file session
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_debug.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_debug.c
index 46f64d2..b9158e1 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_debug.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_debug.c
@@ -638,18 +638,6 @@ static void sde_rot_evtlog_debug_work(struct work_struct *work)
}
/*
- * sde_rot_dump_panic - Issue evtlog dump and generic panic
- */
-void sde_rot_dump_panic(bool do_panic)
-{
- sde_rot_evtlog_dump_all();
- sde_rot_dump_reg_all();
-
- if (do_panic)
- panic("sde_rotator");
-}
-
-/*
* sde_rot_evtlog_tout_handler - log dump timeout handler
* @queue: boolean indicate putting log dump into queue
* @name: function name having timeout
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_debug.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_debug.h
index 2fc8e3f..fa53083 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_debug.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_debug.h
@@ -42,7 +42,6 @@ enum sde_rot_dbg_evtlog_flag {
SDE_ROT_EVTLOG_TOUT_DATA_LIMITER)
void sde_rot_evtlog(const char *name, int line, int flag, ...);
-void sde_rot_dump_panic(bool do_panic);
void sde_rot_evtlog_tout_handler(bool queue, const char *name, ...);
struct sde_rotator_device;
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c
index c032a73..1c0731d 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c
@@ -1423,6 +1423,61 @@ int sde_rotator_inline_get_pixfmt_caps(struct platform_device *pdev,
EXPORT_SYMBOL(sde_rotator_inline_get_pixfmt_caps);
/*
+ * _sde_rotator_inline_cleanup - perform inline related request cleanup
+ * This function assumes rot_dev->mgr lock has been taken when called.
+ * @handle: Pointer to rotator context
+ * @request: Pointer to rotation request
+ * return: 0 if success; -EAGAIN if cleanup should be retried
+ */
+static int _sde_rotator_inline_cleanup(void *handle,
+ struct sde_rotator_request *request)
+{
+ struct sde_rotator_ctx *ctx;
+ struct sde_rotator_device *rot_dev;
+ int ret;
+
+ if (!handle || !request) {
+ SDEROT_ERR("invalid rotator handle/request\n");
+ return -EINVAL;
+ }
+
+ ctx = handle;
+ rot_dev = ctx->rot_dev;
+
+ if (!rot_dev || !rot_dev->mgr) {
+ SDEROT_ERR("invalid rotator device\n");
+ return -EINVAL;
+ }
+
+ if (request->committed) {
+ /* wait until request is finished */
+ sde_rot_mgr_unlock(rot_dev->mgr);
+ mutex_unlock(&rot_dev->lock);
+ ret = wait_event_timeout(ctx->wait_queue,
+ sde_rotator_is_request_retired(request),
+ msecs_to_jiffies(rot_dev->streamoff_timeout));
+ mutex_lock(&rot_dev->lock);
+ sde_rot_mgr_lock(rot_dev->mgr);
+
+ if (!ret) {
+ SDEROT_ERR("timeout w/o retire s:%d\n",
+ ctx->session_id);
+ SDEROT_EVTLOG(ctx->session_id, SDE_ROT_EVTLOG_ERROR);
+ sde_rotator_abort_inline_request(rot_dev->mgr,
+ ctx->private, request->req);
+ return -EAGAIN;
+ } else if (ret == 1) {
+ SDEROT_ERR("timeout w/ retire s:%d\n", ctx->session_id);
+ SDEROT_EVTLOG(ctx->session_id, SDE_ROT_EVTLOG_ERROR);
+ }
+ }
+
+ sde_rotator_req_finish(rot_dev->mgr, ctx->private, request->req);
+ sde_rotator_retire_request(request);
+ return 0;
+}
+
+/*
* sde_rotator_inline_commit - commit given rotator command
* @handle: Pointer to rotator context
* @cmd: Pointer to rotator command
@@ -1449,7 +1504,7 @@ int sde_rotator_inline_commit(void *handle, struct sde_rotator_inline_cmd *cmd,
ctx = handle;
rot_dev = ctx->rot_dev;
- if (!rot_dev) {
+ if (!rot_dev || !rot_dev->mgr) {
SDEROT_ERR("invalid rotator device\n");
return -EINVAL;
}
@@ -1481,6 +1536,7 @@ int sde_rotator_inline_commit(void *handle, struct sde_rotator_inline_cmd *cmd,
(cmd->video_mode << 5) |
(cmd_type << 24));
+ mutex_lock(&rot_dev->lock);
sde_rot_mgr_lock(rot_dev->mgr);
if (cmd_type == SDE_ROTATOR_INLINE_CMD_VALIDATE ||
@@ -1690,30 +1746,12 @@ int sde_rotator_inline_commit(void *handle, struct sde_rotator_inline_cmd *cmd,
}
request = cmd->priv_handle;
- req = request->req;
- if (request->committed) {
- /* wait until request is finished */
- sde_rot_mgr_unlock(rot_dev->mgr);
- ret = wait_event_timeout(ctx->wait_queue,
- sde_rotator_is_request_retired(request),
- msecs_to_jiffies(rot_dev->streamoff_timeout));
- if (!ret) {
- SDEROT_ERR("timeout w/o retire s:%d\n",
- ctx->session_id);
- SDEROT_EVTLOG(ctx->session_id,
- SDE_ROT_EVTLOG_ERROR);
- } else if (ret == 1) {
- SDEROT_ERR("timeout w/ retire s:%d\n",
- ctx->session_id);
- SDEROT_EVTLOG(ctx->session_id,
- SDE_ROT_EVTLOG_ERROR);
- }
- sde_rot_mgr_lock(rot_dev->mgr);
- }
+ /* attempt single retry if first cleanup attempt failed */
+ if (_sde_rotator_inline_cleanup(handle, request) == -EAGAIN)
+ _sde_rotator_inline_cleanup(handle, request);
- sde_rotator_req_finish(rot_dev->mgr, ctx->private, req);
- sde_rotator_retire_request(request);
+ cmd->priv_handle = NULL;
} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_ABORT) {
if (!cmd->priv_handle) {
ret = -EINVAL;
@@ -1722,11 +1760,13 @@ int sde_rotator_inline_commit(void *handle, struct sde_rotator_inline_cmd *cmd,
}
request = cmd->priv_handle;
- sde_rotator_abort_inline_request(rot_dev->mgr,
- ctx->private, request->req);
+ if (!sde_rotator_is_request_retired(request))
+ sde_rotator_abort_inline_request(rot_dev->mgr,
+ ctx->private, request->req);
}
sde_rot_mgr_unlock(rot_dev->mgr);
+ mutex_unlock(&rot_dev->lock);
return 0;
error_handle_request:
@@ -1739,13 +1779,29 @@ int sde_rotator_inline_commit(void *handle, struct sde_rotator_inline_cmd *cmd,
error_invalid_handle:
error_init_request:
sde_rot_mgr_unlock(rot_dev->mgr);
+ mutex_unlock(&rot_dev->lock);
return ret;
}
EXPORT_SYMBOL(sde_rotator_inline_commit);
void sde_rotator_inline_reg_dump(struct platform_device *pdev)
{
- sde_rot_dump_panic(false);
+ struct sde_rotator_device *rot_dev;
+
+ if (!pdev) {
+ SDEROT_ERR("invalid platform device\n");
+ return;
+ }
+
+ rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
+ if (!rot_dev || !rot_dev->mgr) {
+ SDEROT_ERR("invalid rotator device\n");
+ return;
+ }
+
+ sde_rot_mgr_lock(rot_dev->mgr);
+ sde_rotator_core_dump(rot_dev->mgr);
+ sde_rot_mgr_unlock(rot_dev->mgr);
}
EXPORT_SYMBOL(sde_rotator_inline_reg_dump);
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
index 3864cfb..a7eca43 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
@@ -54,7 +54,7 @@
* When in sbuf mode, select a much longer wait, to allow the other driver
* to detect timeouts and abort if necessary.
*/
-#define KOFF_TIMEOUT_SBUF (2000)
+#define KOFF_TIMEOUT_SBUF (10000)
/* default stream buffer headroom in lines */
#define DEFAULT_SBUF_HEADROOM 20
@@ -132,6 +132,9 @@
#define SDE_ROTREG_READ(base, off) \
readl_relaxed(base + (off))
+#define SDE_ROTTOP_IN_OFFLINE_MODE(_rottop_op_mode_) \
+ (((_rottop_op_mode_) & ROTTOP_OP_MODE_ROT_OUT_MASK) == 0)
+
static const u32 sde_hw_rotator_v3_inpixfmts[] = {
SDE_PIX_FMT_XRGB_8888,
SDE_PIX_FMT_ARGB_8888,
@@ -536,6 +539,8 @@ static struct sde_rot_regdump sde_rot_r3_regdump[] = {
SDE_ROT_REGDUMP_READ },
{ "SDEROT_VBIF_NRT", SDE_ROT_VBIF_NRT_OFFSET, 0x590,
SDE_ROT_REGDUMP_VBIF },
+ { "SDEROT_REGDMA_RESET", ROTTOP_SW_RESET_OVERRIDE, 0,
+ SDE_ROT_REGDUMP_WRITE },
};
struct sde_rot_cdp_params {
@@ -693,7 +698,7 @@ static void sde_hw_rotator_halt_vbif_xin_client(void)
/**
* sde_hw_rotator_reset - Reset rotator hardware
* @rot: pointer to hw rotator
- * @ctx: pointer to current rotator context during the hw hang
+ * @ctx: pointer to current rotator context during the hw hang (optional)
*/
static int sde_hw_rotator_reset(struct sde_hw_rotator *rot,
struct sde_hw_rotator_context *ctx)
@@ -707,13 +712,8 @@ static int sde_hw_rotator_reset(struct sde_hw_rotator *rot,
int i, j;
unsigned long flags;
- if (!rot || !ctx) {
- SDEROT_ERR("NULL rotator context\n");
- return -EINVAL;
- }
-
- if (ctx->q_id >= ROT_QUEUE_MAX) {
- SDEROT_ERR("context q_id out of range: %d\n", ctx->q_id);
+ if (!rot) {
+ SDEROT_ERR("NULL rotator\n");
return -EINVAL;
}
@@ -725,6 +725,15 @@ static int sde_hw_rotator_reset(struct sde_hw_rotator *rot,
/* halt vbif xin client to ensure no pending transaction */
sde_hw_rotator_halt_vbif_xin_client();
+ /* if no ctx is specified, skip ctx wake up */
+ if (!ctx)
+ return 0;
+
+ if (ctx->q_id >= ROT_QUEUE_MAX) {
+ SDEROT_ERR("context q_id out of range: %d\n", ctx->q_id);
+ return -EINVAL;
+ }
+
spin_lock_irqsave(&rot->rotisr_lock, flags);
/* update timestamp register with current context */
@@ -780,10 +789,11 @@ static int sde_hw_rotator_reset(struct sde_hw_rotator *rot,
}
/**
- * sde_hw_rotator_dump_status - Dump hw rotator status on error
+ * _sde_hw_rotator_dump_status - Dump hw rotator status on error
* @rot: Pointer to hw rotator
*/
-static void sde_hw_rotator_dump_status(struct sde_hw_rotator *rot, u32 *ubwcerr)
+static void _sde_hw_rotator_dump_status(struct sde_hw_rotator *rot,
+ u32 *ubwcerr)
{
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
u32 reg = 0;
@@ -815,6 +825,11 @@ static void sde_hw_rotator_dump_status(struct sde_hw_rotator *rot, u32 *ubwcerr)
SDE_ROTREG_READ(rot->mdss_base,
REGDMA_CSR_REGDMA_FSM_STATE));
+ SDEROT_ERR("rottop: op_mode = %x, status = %x, clk_status = %x\n",
+ SDE_ROTREG_READ(rot->mdss_base, ROTTOP_OP_MODE),
+ SDE_ROTREG_READ(rot->mdss_base, ROTTOP_STATUS),
+ SDE_ROTREG_READ(rot->mdss_base, ROTTOP_CLK_STATUS));
+
reg = SDE_ROTREG_READ(rot->mdss_base, ROT_SSPP_UBWC_ERROR_STATUS);
if (ubwcerr)
*ubwcerr = reg;
@@ -826,12 +841,35 @@ static void sde_hw_rotator_dump_status(struct sde_hw_rotator *rot, u32 *ubwcerr)
SDE_VBIF_READ(mdata, MMSS_VBIF_XIN_HALT_CTRL1),
SDE_VBIF_READ(mdata, MMSS_VBIF_AXI_HALT_CTRL1));
- SDEROT_ERR(
- "sbuf_status_plane0 = %x, sbuf_status_plane1 = %x\n",
- SDE_ROTREG_READ(rot->mdss_base,
- ROT_WB_SBUF_STATUS_PLANE0),
- SDE_ROTREG_READ(rot->mdss_base,
- ROT_WB_SBUF_STATUS_PLANE1));
+ SDEROT_ERR("sspp unpack wr: plane0 = %x, plane1 = %x, plane2 = %x\n",
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_FETCH_SMP_WR_PLANE0),
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_FETCH_SMP_WR_PLANE1),
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_FETCH_SMP_WR_PLANE2));
+ SDEROT_ERR("sspp unpack rd: plane0 = %x, plane1 = %x, plane2 = %x\n",
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_SMP_UNPACK_RD_PLANE0),
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_SMP_UNPACK_RD_PLANE1),
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_SMP_UNPACK_RD_PLANE2));
+ SDEROT_ERR("sspp: unpack_ln = %x, unpack_blk = %x, fill_lvl = %x\n",
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_UNPACK_LINE_COUNT),
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_UNPACK_BLK_COUNT),
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_SSPP_FILL_LEVELS));
+
+ SDEROT_ERR("wb: sbuf0 = %x, sbuf1 = %x, sys_cache = %x\n",
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_WB_SBUF_STATUS_PLANE0),
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_WB_SBUF_STATUS_PLANE1),
+ SDE_ROTREG_READ(rot->mdss_base,
+ ROT_WB_SYS_CACHE_MODE));
}
/**
@@ -1952,7 +1990,7 @@ static u32 sde_hw_rotator_wait_done_regdma(
else if (status & REGDMA_INVALID_CMD)
SDEROT_ERR("REGDMA invalid command\n");
- sde_hw_rotator_dump_status(rot, &ubwcerr);
+ _sde_hw_rotator_dump_status(rot, &ubwcerr);
if (ubwcerr || abort) {
/*
@@ -1997,12 +2035,12 @@ static u32 sde_hw_rotator_wait_done_regdma(
if (last_isr & REGDMA_INT_ERR_MASK) {
SDEROT_ERR("Rotator error, ts:0x%X/0x%X status:%x\n",
ctx->timestamp, swts, last_isr);
- sde_hw_rotator_dump_status(rot, NULL);
+ _sde_hw_rotator_dump_status(rot, NULL);
status = ROT_ERROR_BIT;
} else if (pending) {
SDEROT_ERR("Rotator timeout, ts:0x%X/0x%X status:%x\n",
ctx->timestamp, swts, last_isr);
- sde_hw_rotator_dump_status(rot, NULL);
+ _sde_hw_rotator_dump_status(rot, NULL);
status = ROT_ERROR_BIT;
} else {
status = 0;
@@ -2140,7 +2178,7 @@ void sde_hw_rotator_pre_pmevent(struct sde_rot_mgr *mgr, bool pmon)
{
struct sde_hw_rotator *rot;
u32 l_ts, h_ts, swts, hwts;
- u32 rotsts, regdmasts;
+ u32 rotsts, regdmasts, rotopmode;
/*
* Check last HW timestamp with SW timestamp before power off event.
@@ -2165,19 +2203,37 @@ void sde_hw_rotator_pre_pmevent(struct sde_rot_mgr *mgr, bool pmon)
regdmasts = SDE_ROTREG_READ(rot->mdss_base,
REGDMA_CSR_REGDMA_BLOCK_STATUS);
rotsts = SDE_ROTREG_READ(rot->mdss_base, ROTTOP_STATUS);
+ rotopmode = SDE_ROTREG_READ(rot->mdss_base, ROTTOP_OP_MODE);
SDEROT_DBG(
- "swts:0x%x, hwts:0x%x, regdma-sts:0x%x, rottop-sts:0x%x\n",
- swts, hwts, regdmasts, rotsts);
- SDEROT_EVTLOG(swts, hwts, regdmasts, rotsts);
+ "swts:0x%x, hwts:0x%x, regdma-sts:0x%x, rottop-sts:0x%x, rottop-opmode:0x%x\n",
+ swts, hwts, regdmasts, rotsts, rotopmode);
+ SDEROT_EVTLOG(swts, hwts, regdmasts, rotsts, rotopmode);
if ((swts != hwts) && ((regdmasts & REGDMA_BUSY) ||
(rotsts & ROT_STATUS_MASK))) {
SDEROT_ERR(
"Mismatch SWTS with HWTS: swts:0x%x, hwts:0x%x, regdma-sts:0x%x, rottop-sts:0x%x\n",
swts, hwts, regdmasts, rotsts);
+ _sde_hw_rotator_dump_status(rot, NULL);
SDEROT_EVTLOG_TOUT_HANDLER("rot", "rot_dbg_bus",
"vbif_dbg_bus", "panic");
+ } else if (!SDE_ROTTOP_IN_OFFLINE_MODE(rotopmode) &&
+ ((regdmasts & REGDMA_BUSY) ||
+ (rotsts & ROT_BUSY_BIT))) {
+ /*
+ * rotator can stuck in inline while mdp is detached
+ */
+ SDEROT_WARN(
+ "Inline Rot busy: regdma-sts:0x%x, rottop-sts:0x%x, rottop-opmode:0x%x\n",
+ regdmasts, rotsts, rotopmode);
+ sde_hw_rotator_reset(rot, NULL);
+ } else if ((regdmasts & REGDMA_BUSY) ||
+ (rotsts & ROT_BUSY_BIT)) {
+ _sde_hw_rotator_dump_status(rot, NULL);
+ SDEROT_EVTLOG_TOUT_HANDLER("rot", "rot_dbg_bus",
+ "vbif_dbg_bus", "panic");
+ sde_hw_rotator_reset(rot, NULL);
}
/* Turn off rotator clock after checking rotator registers */
@@ -2481,7 +2537,7 @@ static int sde_hw_rotator_config(struct sde_rot_hw_resource *hw,
if (status & BIT(0)) {
SDEROT_ERR("rotator busy 0x%x\n",
status);
- sde_hw_rotator_dump_status(rot, NULL);
+ _sde_hw_rotator_dump_status(rot, NULL);
SDEROT_EVTLOG_TOUT_HANDLER("rot",
"vbif_dbg_bus",
"panic");
@@ -3494,6 +3550,21 @@ static int sde_hw_rotator_get_maxlinewidth(struct sde_rot_mgr *mgr)
}
/*
+ * sde_hw_rotator_dump_status - dump status to debug output
+ * @mgr: Pointer to rotator manager
+ * return: none
+ */
+static void sde_hw_rotator_dump_status(struct sde_rot_mgr *mgr)
+{
+ if (!mgr || !mgr->hw_data) {
+ SDEROT_ERR("null parameters\n");
+ return;
+ }
+
+ _sde_hw_rotator_dump_status(mgr->hw_data, NULL);
+}
+
+/*
* sde_hw_rotator_parse_dt - parse r3 specific device tree settings
* @hw_data: Pointer to rotator hw
* @dev: Pointer to platform device
@@ -3622,6 +3693,7 @@ int sde_rotator_r3_init(struct sde_rot_mgr *mgr)
mgr->ops_hw_post_pmevent = sde_hw_rotator_post_pmevent;
mgr->ops_hw_get_downscale_caps = sde_hw_rotator_get_downscale_caps;
mgr->ops_hw_get_maxlinewidth = sde_hw_rotator_get_maxlinewidth;
+ mgr->ops_hw_dump_status = sde_hw_rotator_dump_status;
ret = sde_hw_rotator_parse_dt(mgr->hw_data, mgr->pdev);
if (ret)
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h
index 2afd032..aaaa28c 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h
@@ -50,6 +50,8 @@
#define ROTTOP_START_CTRL_TRIG_SEL_REGDMA 2
#define ROTTOP_START_CTRL_TRIG_SEL_MDP 3
+#define ROTTOP_OP_MODE_ROT_OUT_MASK (0x3 << 4)
+
/* SDE_ROT_SSPP:
* OFFSET=0x0A8900
*/
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
index a6b37d3..86e4af7 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
@@ -177,11 +177,20 @@ static int rmnet_newlink(struct net *src_net, struct net_device *dev,
if (err)
goto err2;
- netdev_dbg(dev, "data format [ingress 0x%08X]\n", ingress_format);
- port->ingress_data_format = ingress_format;
port->rmnet_mode = mode;
hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[mux_id]);
+
+ if (data[IFLA_VLAN_FLAGS]) {
+ struct ifla_vlan_flags *flags;
+
+ flags = nla_data(data[IFLA_VLAN_FLAGS]);
+ ingress_format = flags->flags & flags->mask;
+ }
+
+ netdev_dbg(dev, "data format [ingress 0x%08X]\n", ingress_format);
+ port->ingress_data_format = ingress_format;
+
return 0;
err2:
@@ -311,9 +320,49 @@ static int rmnet_rtnl_validate(struct nlattr *tb[], struct nlattr *data[],
return 0;
}
+static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
+ struct nlattr *data[],
+ struct netlink_ext_ack *extack)
+{
+ struct rmnet_priv *priv = netdev_priv(dev);
+ struct net_device *real_dev;
+ struct rmnet_endpoint *ep;
+ struct rmnet_port *port;
+ u16 mux_id;
+
+ real_dev = __dev_get_by_index(dev_net(dev),
+ nla_get_u32(tb[IFLA_LINK]));
+
+ if (!real_dev || !dev || !rmnet_is_real_dev_registered(real_dev))
+ return -ENODEV;
+
+ port = rmnet_get_port_rtnl(real_dev);
+
+ if (data[IFLA_VLAN_ID]) {
+ mux_id = nla_get_u16(data[IFLA_VLAN_ID]);
+ ep = rmnet_get_endpoint(port, priv->mux_id);
+
+ hlist_del_init_rcu(&ep->hlnode);
+ hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[mux_id]);
+
+ ep->mux_id = mux_id;
+ priv->mux_id = mux_id;
+ }
+
+ if (data[IFLA_VLAN_FLAGS]) {
+ struct ifla_vlan_flags *flags;
+
+ flags = nla_data(data[IFLA_VLAN_FLAGS]);
+ port->ingress_data_format = flags->flags & flags->mask;
+ }
+
+ return 0;
+}
+
static size_t rmnet_get_size(const struct net_device *dev)
{
- return nla_total_size(2); /* IFLA_VLAN_ID */
+ return nla_total_size(2) /* IFLA_VLAN_ID */ +
+ nla_total_size(sizeof(struct ifla_vlan_flags)); /* IFLA_VLAN_FLAGS */
}
struct rtnl_link_ops rmnet_link_ops __read_mostly = {
@@ -325,6 +374,7 @@ struct rtnl_link_ops rmnet_link_ops __read_mostly = {
.newlink = rmnet_newlink,
.dellink = rmnet_dellink,
.get_size = rmnet_get_size,
+ .changelink = rmnet_changelink,
};
/* Needs either rcu_read_lock() or rtnl lock */
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index e0e58f3..623fdec 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1619,6 +1619,15 @@
To compile this driver as a module, choose M here: the
module will be called rtc-pm8xxx.
+config RTC_DRV_QPNP
+ tristate "Qualcomm Technologies, Inc. QPNP PMIC RTC"
+ depends on SPMI
+ help
+ This enables support for the RTC found on Qualcomm Technologies, Inc.
+ QPNP PMIC chips. This driver supports using the PMIC RTC peripheral
+ to wake a mobile device up from suspend or to wake it up from power-
+ off.
+
config RTC_DRV_TEGRA
tristate "NVIDIA Tegra Internal RTC driver"
depends on ARCH_TEGRA || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 0bf1fc0..3cb23b5 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -124,6 +124,7 @@
obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o
obj-$(CONFIG_RTC_DRV_PUV3) += rtc-puv3.o
obj-$(CONFIG_RTC_DRV_PXA) += rtc-pxa.o
+obj-$(CONFIG_RTC_DRV_QPNP) += qpnp-rtc.o
obj-$(CONFIG_RTC_DRV_R7301) += rtc-r7301.o
obj-$(CONFIG_RTC_DRV_R9701) += rtc-r9701.o
obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5t583.o
diff --git a/drivers/rtc/qpnp-rtc.c b/drivers/rtc/qpnp-rtc.c
new file mode 100644
index 0000000..c6e4d9a
--- /dev/null
+++ b/drivers/rtc/qpnp-rtc.c
@@ -0,0 +1,714 @@
+/* Copyright (c) 2012-2015, 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/regmap.h>
+#include <linux/init.h>
+#include <linux/rtc.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/idr.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/alarmtimer.h>
+
+/* RTC/ALARM Register offsets */
+#define REG_OFFSET_ALARM_RW 0x40
+#define REG_OFFSET_ALARM_CTRL1 0x46
+#define REG_OFFSET_ALARM_CTRL2 0x48
+#define REG_OFFSET_RTC_WRITE 0x40
+#define REG_OFFSET_RTC_CTRL 0x46
+#define REG_OFFSET_RTC_READ 0x48
+#define REG_OFFSET_PERP_SUBTYPE 0x05
+
+/* RTC_CTRL register bit fields */
+#define BIT_RTC_ENABLE BIT(7)
+#define BIT_RTC_ALARM_ENABLE BIT(7)
+#define BIT_RTC_ABORT_ENABLE BIT(0)
+#define BIT_RTC_ALARM_CLEAR BIT(0)
+
+/* RTC/ALARM peripheral subtype values */
+#define RTC_PERPH_SUBTYPE 0x1
+#define ALARM_PERPH_SUBTYPE 0x3
+
+#define NUM_8_BIT_RTC_REGS 0x4
+
+#define TO_SECS(arr) (arr[0] | (arr[1] << 8) | (arr[2] << 16) | \
+ (arr[3] << 24))
+
+/* Module parameter to control power-on-alarm */
+bool poweron_alarm;
+EXPORT_SYMBOL(poweron_alarm);
+module_param(poweron_alarm, bool, 0644);
+MODULE_PARM_DESC(poweron_alarm, "Enable/Disable power-on alarm");
+
+/* rtc driver internal structure */
+struct qpnp_rtc {
+ u8 rtc_ctrl_reg;
+ u8 alarm_ctrl_reg1;
+ u16 rtc_base;
+ u16 alarm_base;
+ u32 rtc_write_enable;
+ u32 rtc_alarm_powerup;
+ int rtc_alarm_irq;
+ struct device *rtc_dev;
+ struct rtc_device *rtc;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ spinlock_t alarm_ctrl_lock;
+};
+
+static int qpnp_read_wrapper(struct qpnp_rtc *rtc_dd, u8 *rtc_val,
+ u16 base, int count)
+{
+ int rc;
+
+ rc = regmap_bulk_read(rtc_dd->regmap, base, rtc_val, count);
+ if (rc) {
+ dev_err(rtc_dd->rtc_dev, "SPMI read failed\n");
+ return rc;
+ }
+ return 0;
+}
+
+static int qpnp_write_wrapper(struct qpnp_rtc *rtc_dd, u8 *rtc_val,
+ u16 base, int count)
+{
+ int rc;
+
+ rc = regmap_bulk_write(rtc_dd->regmap, base, rtc_val, count);
+ if (rc) {
+ dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+qpnp_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ unsigned long secs, irq_flags;
+ u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg;
+ u8 rtc_disabled = 0, rtc_ctrl_reg;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rtc_tm_to_time(tm, &secs);
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ dev_dbg(dev, "Seconds value to be written to RTC = %lu\n", secs);
+
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+
+ if (ctrl_reg & BIT_RTC_ALARM_ENABLE) {
+ alarm_enabled = 1;
+ ctrl_reg &= ~BIT_RTC_ALARM_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(dev, "Write to ALARM ctrl reg failed\n");
+ goto rtc_rw_fail;
+ }
+ } else
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ /*
+ * 32 bit seconds value is coverted to four 8 bit values
+ * |<------ 32 bit time value in seconds ------>|
+ * <- 8 bit ->|<- 8 bit ->|<- 8 bit ->|<- 8 bit ->|
+ * ----------------------------------------------
+ * | BYTE[3] | BYTE[2] | BYTE[1] | BYTE[0] |
+ * ----------------------------------------------
+ *
+ * RTC has four 8 bit registers for writing time in seconds:
+ * WDATA[3], WDATA[2], WDATA[1], WDATA[0]
+ *
+ * Write to the RTC registers should be done in following order
+ * Clear WDATA[0] register
+ *
+ * Write BYTE[1], BYTE[2] and BYTE[3] of time to
+ * RTC WDATA[3], WDATA[2], WDATA[1] registers
+ *
+ * Write BYTE[0] of time to RTC WDATA[0] register
+ *
+ * Clearing BYTE[0] and writing in the end will prevent any
+ * unintentional overflow from WDATA[0] to higher bytes during the
+ * write operation
+ */
+
+ /* Disable RTC H/w before writing on RTC register*/
+ rtc_ctrl_reg = rtc_dd->rtc_ctrl_reg;
+ if (rtc_ctrl_reg & BIT_RTC_ENABLE) {
+ rtc_disabled = 1;
+ rtc_ctrl_reg &= ~BIT_RTC_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &rtc_ctrl_reg,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+ if (rc) {
+ dev_err(dev, "Disabling of RTC control reg failed with error:%d\n",
+ rc);
+ goto rtc_rw_fail;
+ }
+ rtc_dd->rtc_ctrl_reg = rtc_ctrl_reg;
+ }
+
+ /* Clear WDATA[0] */
+ reg = 0x0;
+ rc = qpnp_write_wrapper(rtc_dd, ®,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE, 1);
+ if (rc) {
+ dev_err(dev, "Write to RTC reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ /* Write to WDATA[3], WDATA[2] and WDATA[1] */
+ rc = qpnp_write_wrapper(rtc_dd, &value[1],
+ rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE + 1, 3);
+ if (rc) {
+ dev_err(dev, "Write to RTC reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ /* Write to WDATA[0] */
+ rc = qpnp_write_wrapper(rtc_dd, value,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE, 1);
+ if (rc) {
+ dev_err(dev, "Write to RTC reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ /* Enable RTC H/w after writing on RTC register*/
+ if (rtc_disabled) {
+ rtc_ctrl_reg |= BIT_RTC_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &rtc_ctrl_reg,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+ if (rc) {
+ dev_err(dev, "Enabling of RTC control reg failed with error:%d\n",
+ rc);
+ goto rtc_rw_fail;
+ }
+ rtc_dd->rtc_ctrl_reg = rtc_ctrl_reg;
+ }
+
+ if (alarm_enabled) {
+ ctrl_reg |= BIT_RTC_ALARM_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(dev, "Write to ALARM ctrl reg failed\n");
+ goto rtc_rw_fail;
+ }
+ }
+
+ rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+rtc_rw_fail:
+ if (alarm_enabled)
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ return rc;
+}
+
+static int
+qpnp_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = qpnp_read_wrapper(rtc_dd, value,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_READ,
+ NUM_8_BIT_RTC_REGS);
+ if (rc) {
+ dev_err(dev, "Read from RTC reg failed\n");
+ return rc;
+ }
+
+ /*
+ * Read the LSB again and check if there has been a carry over
+ * If there is, redo the read operation
+ */
+ rc = qpnp_read_wrapper(rtc_dd, ®,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_READ, 1);
+ if (rc) {
+ dev_err(dev, "Read from RTC reg failed\n");
+ return rc;
+ }
+
+ if (reg < value[0]) {
+ rc = qpnp_read_wrapper(rtc_dd, value,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_READ,
+ NUM_8_BIT_RTC_REGS);
+ if (rc) {
+ dev_err(dev, "Read from RTC reg failed\n");
+ return rc;
+ }
+ }
+
+ secs = TO_SECS(value);
+
+ rtc_time_to_tm(secs, tm);
+
+ rc = rtc_valid_tm(tm);
+ if (rc) {
+ dev_err(dev, "Invalid time read from RTC\n");
+ return rc;
+ }
+
+ dev_dbg(dev, "secs = %lu, h:m:s == %d:%d:%d, d/m/y = %d/%d/%d\n",
+ secs, tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+ return 0;
+}
+
+static int
+qpnp_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4], ctrl_reg;
+ unsigned long secs, secs_rtc, irq_flags;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+ struct rtc_time rtc_tm;
+
+ rtc_tm_to_time(&alarm->time, &secs);
+
+ /*
+ * Read the current RTC time and verify if the alarm time is in the
+ * past. If yes, return invalid
+ */
+ rc = qpnp_rtc_read_time(dev, &rtc_tm);
+ if (rc) {
+ dev_err(dev, "Unable to read RTC time\n");
+ return -EINVAL;
+ }
+
+ rtc_tm_to_time(&rtc_tm, &secs_rtc);
+ if (secs < secs_rtc) {
+ dev_err(dev, "Trying to set alarm in the past\n");
+ return -EINVAL;
+ }
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ rc = qpnp_write_wrapper(rtc_dd, value,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+ NUM_8_BIT_RTC_REGS);
+ if (rc) {
+ dev_err(dev, "Write to ALARM reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ ctrl_reg = (alarm->enabled) ?
+ (rtc_dd->alarm_ctrl_reg1 | BIT_RTC_ALARM_ENABLE) :
+ (rtc_dd->alarm_ctrl_reg1 & ~BIT_RTC_ALARM_ENABLE);
+
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(dev, "Write to ALARM cntrol reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+ dev_dbg(dev, "Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+rtc_rw_fail:
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ return rc;
+}
+
+static int
+qpnp_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4];
+ unsigned long secs;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = qpnp_read_wrapper(rtc_dd, value,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+ NUM_8_BIT_RTC_REGS);
+ if (rc) {
+ dev_err(dev, "Read from ALARM reg failed\n");
+ return rc;
+ }
+
+ secs = TO_SECS(value);
+ rtc_time_to_tm(secs, &alarm->time);
+
+ rc = rtc_valid_tm(&alarm->time);
+ if (rc) {
+ dev_err(dev, "Invalid time read from RTC\n");
+ return rc;
+ }
+
+ dev_dbg(dev, "Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+
+ return 0;
+}
+
+
+static int
+qpnp_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ int rc;
+ unsigned long irq_flags;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+ u8 ctrl_reg;
+ u8 value[4] = {0};
+
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+ ctrl_reg = enabled ? (ctrl_reg | BIT_RTC_ALARM_ENABLE) :
+ (ctrl_reg & ~BIT_RTC_ALARM_ENABLE);
+
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(dev, "Write to ALARM control reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+ /* Clear Alarm register */
+ if (!enabled) {
+ rc = qpnp_write_wrapper(rtc_dd, value,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+ NUM_8_BIT_RTC_REGS);
+ if (rc)
+ dev_err(dev, "Clear ALARM value reg failed\n");
+ }
+
+rtc_rw_fail:
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ return rc;
+}
+
+static const struct rtc_class_ops qpnp_rtc_ro_ops = {
+ .read_time = qpnp_rtc_read_time,
+ .set_alarm = qpnp_rtc_set_alarm,
+ .read_alarm = qpnp_rtc_read_alarm,
+ .alarm_irq_enable = qpnp_rtc_alarm_irq_enable,
+};
+
+static const struct rtc_class_ops qpnp_rtc_rw_ops = {
+ .read_time = qpnp_rtc_read_time,
+ .set_alarm = qpnp_rtc_set_alarm,
+ .read_alarm = qpnp_rtc_read_alarm,
+ .alarm_irq_enable = qpnp_rtc_alarm_irq_enable,
+ .set_time = qpnp_rtc_set_time,
+};
+
+static irqreturn_t qpnp_alarm_trigger(int irq, void *dev_id)
+{
+ struct qpnp_rtc *rtc_dd = dev_id;
+ u8 ctrl_reg;
+ int rc;
+ unsigned long irq_flags;
+
+ rtc_update_irq(rtc_dd->rtc, 1, RTC_IRQF | RTC_AF);
+
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ /* Clear the alarm enable bit */
+ ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+ ctrl_reg &= ~BIT_RTC_ALARM_ENABLE;
+
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ dev_err(rtc_dd->rtc_dev,
+ "Write to ALARM control reg failed\n");
+ goto rtc_alarm_handled;
+ }
+
+ rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ /* Set ALARM_CLR bit */
+ ctrl_reg = 0x1;
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL2, 1);
+ if (rc)
+ dev_err(rtc_dd->rtc_dev,
+ "Write to ALARM control reg failed\n");
+
+rtc_alarm_handled:
+ return IRQ_HANDLED;
+}
+
+static int qpnp_rtc_probe(struct platform_device *pdev)
+{
+ const struct rtc_class_ops *rtc_ops = &qpnp_rtc_ro_ops;
+ int rc;
+ u8 subtype;
+ struct qpnp_rtc *rtc_dd;
+ unsigned int base;
+ struct device_node *child;
+
+ rtc_dd = devm_kzalloc(&pdev->dev, sizeof(*rtc_dd), GFP_KERNEL);
+ if (rtc_dd == NULL)
+ return -ENOMEM;
+
+ rtc_dd->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!rtc_dd->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ /* Get the rtc write property */
+ rc = of_property_read_u32(pdev->dev.of_node, "qcom,qpnp-rtc-write",
+ &rtc_dd->rtc_write_enable);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pdev->dev,
+ "Error reading rtc_write_enable property %d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,qpnp-rtc-alarm-pwrup",
+ &rtc_dd->rtc_alarm_powerup);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pdev->dev,
+ "Error reading rtc_alarm_powerup property %d\n", rc);
+ return rc;
+ }
+
+ /* Initialise spinlock to protect RTC control register */
+ spin_lock_init(&rtc_dd->alarm_ctrl_lock);
+
+ rtc_dd->rtc_dev = &(pdev->dev);
+ rtc_dd->pdev = pdev;
+
+
+ if (of_get_available_child_count(pdev->dev.of_node) == 0) {
+ pr_err("no child nodes\n");
+ rc = -ENXIO;
+ goto fail_rtc_enable;
+ }
+
+ /* Get RTC/ALARM resources */
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ child->full_name, rc);
+ goto fail_rtc_enable;
+ }
+
+ rc = qpnp_read_wrapper(rtc_dd, &subtype,
+ base + REG_OFFSET_PERP_SUBTYPE, 1);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Peripheral subtype read failed\n");
+ goto fail_rtc_enable;
+ }
+
+ switch (subtype) {
+ case RTC_PERPH_SUBTYPE:
+ rtc_dd->rtc_base = base;
+ break;
+ case ALARM_PERPH_SUBTYPE:
+ rtc_dd->alarm_base = base;
+ rtc_dd->rtc_alarm_irq = of_irq_get(child, 0);
+ if (rtc_dd->rtc_alarm_irq < 0) {
+ dev_err(&pdev->dev, "ALARM IRQ absent\n");
+ rc = -ENXIO;
+ goto fail_rtc_enable;
+ }
+ break;
+ default:
+ dev_err(&pdev->dev, "Invalid peripheral subtype\n");
+ rc = -EINVAL;
+ goto fail_rtc_enable;
+ }
+ }
+
+ rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->rtc_ctrl_reg,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+ if (rc) {
+ dev_err(&pdev->dev, "Read from RTC control reg failed\n");
+ goto fail_rtc_enable;
+ }
+
+ if (!(rtc_dd->rtc_ctrl_reg & BIT_RTC_ENABLE)) {
+ dev_err(&pdev->dev, "RTC h/w disabled, rtc not registered\n");
+ goto fail_rtc_enable;
+ }
+
+ rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(&pdev->dev, "Read from Alarm control reg failed\n");
+ goto fail_rtc_enable;
+ }
+ /* Enable abort enable feature */
+ rtc_dd->alarm_ctrl_reg1 |= BIT_RTC_ABORT_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(&pdev->dev, "SPMI write failed!\n");
+ goto fail_rtc_enable;
+ }
+
+ if (rtc_dd->rtc_write_enable == true)
+ rtc_ops = &qpnp_rtc_rw_ops;
+
+ dev_set_drvdata(&pdev->dev, rtc_dd);
+
+ /* Register the RTC device */
+ rtc_dd->rtc = rtc_device_register("qpnp_rtc", &pdev->dev,
+ rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc_dd->rtc)) {
+ dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",
+ __func__, PTR_ERR(rtc_dd->rtc));
+ rc = PTR_ERR(rtc_dd->rtc);
+ goto fail_rtc_enable;
+ }
+
+ /* Request the alarm IRQ */
+ rc = request_any_context_irq(rtc_dd->rtc_alarm_irq,
+ qpnp_alarm_trigger, IRQF_TRIGGER_RISING,
+ "qpnp_rtc_alarm", rtc_dd);
+ if (rc) {
+ dev_err(&pdev->dev, "Request IRQ failed (%d)\n", rc);
+ goto fail_req_irq;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+ enable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+ dev_dbg(&pdev->dev, "Probe success !!\n");
+
+ return 0;
+
+fail_req_irq:
+ rtc_device_unregister(rtc_dd->rtc);
+fail_rtc_enable:
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return rc;
+}
+
+static int qpnp_rtc_remove(struct platform_device *pdev)
+{
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(&pdev->dev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ free_irq(rtc_dd->rtc_alarm_irq, rtc_dd);
+ rtc_device_unregister(rtc_dd->rtc);
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static void qpnp_rtc_shutdown(struct platform_device *pdev)
+{
+ u8 value[4] = {0};
+ u8 reg;
+ int rc;
+ unsigned long irq_flags;
+ struct qpnp_rtc *rtc_dd;
+ bool rtc_alarm_powerup;
+
+ if (!pdev) {
+ pr_err("qpnp-rtc: spmi device not found\n");
+ return;
+ }
+ rtc_dd = dev_get_drvdata(&pdev->dev);
+ if (!rtc_dd) {
+ pr_err("qpnp-rtc: rtc driver data not found\n");
+ return;
+ }
+ rtc_alarm_powerup = rtc_dd->rtc_alarm_powerup;
+ if (!rtc_alarm_powerup && !poweron_alarm) {
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ dev_dbg(&pdev->dev, "Disabling alarm interrupts\n");
+
+ /* Disable RTC alarms */
+ reg = rtc_dd->alarm_ctrl_reg1;
+ reg &= ~BIT_RTC_ALARM_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, ®,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+ goto fail_alarm_disable;
+ }
+
+ /* Clear Alarm register */
+ rc = qpnp_write_wrapper(rtc_dd, value,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+ NUM_8_BIT_RTC_REGS);
+ if (rc)
+ dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+
+fail_alarm_disable:
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ }
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ {
+ .compatible = "qcom,qpnp-rtc",
+ },
+ {}
+};
+
+static struct platform_driver qpnp_rtc_driver = {
+ .probe = qpnp_rtc_probe,
+ .remove = qpnp_rtc_remove,
+ .shutdown = qpnp_rtc_shutdown,
+ .driver = {
+ .name = "qcom,qpnp-rtc",
+ .owner = THIS_MODULE,
+ .of_match_table = spmi_match_table,
+ },
+};
+
+static int __init qpnp_rtc_init(void)
+{
+ return platform_driver_register(&qpnp_rtc_driver);
+}
+module_init(qpnp_rtc_init);
+
+static void __exit qpnp_rtc_exit(void)
+{
+ platform_driver_unregister(&qpnp_rtc_driver);
+}
+module_exit(qpnp_rtc_exit);
+
+MODULE_DESCRIPTION("SPMI PMIC RTC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 07002df..322eb13 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -460,6 +460,16 @@
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"
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 195cd08..6af08f9 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -61,3 +61,4 @@
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
diff --git a/drivers/thermal/msm-tsens.c b/drivers/thermal/msm-tsens.c
new file mode 100644
index 0000000..abaf8bb6
--- /dev/null
+++ b/drivers/thermal/msm-tsens.c
@@ -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");
diff --git a/drivers/thermal/tsens-dbg.c b/drivers/thermal/tsens-dbg.c
new file mode 100644
index 0000000..4beefe8
--- /dev/null
+++ b/drivers/thermal/tsens-dbg.c
@@ -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);
diff --git a/drivers/thermal/tsens.h b/drivers/thermal/tsens.h
new file mode 100644
index 0000000..ec2d592
--- /dev/null
+++ b/drivers/thermal/tsens.h
@@ -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__ */
diff --git a/drivers/thermal/tsens2xxx.c b/drivers/thermal/tsens2xxx.c
new file mode 100644
index 0000000..660c8ea
--- /dev/null
+++ b/drivers/thermal/tsens2xxx.c
@@ -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,
+};
diff --git a/include/linux/thermal.h b/include/linux/thermal.h
index fd5b959..ec26b1e 100644
--- a/include/linux/thermal.h
+++ b/include/linux/thermal.h
@@ -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 {