aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinaro CI <ci_notify@linaro.org>2022-08-19 16:55:52 +0000
committerLinaro CI <ci_notify@linaro.org>2022-08-19 16:55:52 +0000
commitded147bcdea6fdcc1bc995b5d4f6a4f060d78dfc (patch)
treeb16dc4f91d85d50297278cad7ebada0f57d6247b
parenta60e3192c29e86bf058565b8daad3029481e8719 (diff)
parentfe95b8e58c814faab92c927782cb243aa87a40e3 (diff)
Merge remote-tracking branch 'sa8155p-adp-dts-drivers/tracking-qcomlt-sa8155p-dts-drivers' into integration-linux-qcomlt
-rw-r--r--Documentation/devicetree/bindings/phy/qcom,qmp-phy.yaml24
-rw-r--r--drivers/firmware/qcom_scm.c17
-rw-r--r--drivers/firmware/qcom_scm.h4
-rw-r--r--drivers/thermal/qcom/tsens-v2.c14
-rw-r--r--drivers/thermal/qcom/tsens.c241
-rw-r--r--drivers/thermal/qcom/tsens.h12
-rw-r--r--include/linux/qcom_scm.h2
7 files changed, 301 insertions, 13 deletions
diff --git a/Documentation/devicetree/bindings/phy/qcom,qmp-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,qmp-phy.yaml
index 220788ce215f..60f412a33852 100644
--- a/Documentation/devicetree/bindings/phy/qcom,qmp-phy.yaml
+++ b/Documentation/devicetree/bindings/phy/qcom,qmp-phy.yaml
@@ -67,9 +67,6 @@ properties:
- description: Address and length of PHY's common serdes block.
- description: Address and length of PHY's DP_COM control block.
- "#clock-cells":
- enum: [ 1, 2 ]
-
"#address-cells":
enum: [ 1, 2 ]
@@ -106,6 +103,10 @@ properties:
description:
Phandle to a regulator supply to any specific refclk pll block.
+ power-domains:
+ description: Phandle to the power domain node.
+ maxItems: 1
+
#Required nodes:
patternProperties:
"^phy@[0-9a-f]+$":
@@ -113,11 +114,25 @@ patternProperties:
description:
Each device node of QMP phy is required to have as many child nodes as
the number of lanes the PHY has.
+ properties:
+ reg:
+ minItems: 1
+ maxItems: 6
+ description: |
+ List of offset and length pairs of register sets for PHY blocks.
+ There are two possible cases::
+ - tx, rx, pcs and pcs_misc.
+ - tx, rx, pcs, tx2, rx2 and pcs_misc.
+
+ "#phy-cells":
+ const: 0
+
+ "#clock-cells":
+ enum: [ 0, 1, 2 ]
required:
- compatible
- reg
- - "#clock-cells"
- "#address-cells"
- "#size-cells"
- ranges
@@ -470,7 +485,6 @@ examples:
usb_2_qmpphy: phy-wrapper@88eb000 {
compatible = "qcom,sdm845-qmp-usb3-uni-phy";
reg = <0x088eb000 0x18c>;
- #clock-cells = <1>;
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0x088eb000 0x2000>;
diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c
index cdbfe54c8146..74fa095090cc 100644
--- a/drivers/firmware/qcom_scm.c
+++ b/drivers/firmware/qcom_scm.c
@@ -858,6 +858,23 @@ int qcom_scm_mem_protect_video_var(u32 cp_start, u32 cp_size,
}
EXPORT_SYMBOL(qcom_scm_mem_protect_video_var);
+int qcom_scm_tsens_reinit(int *tsens_ret)
+{
+ unsigned int ret;
+ struct qcom_scm_desc desc = {
+ .svc = QCOM_SCM_SVC_TSENS,
+ .cmd = QCOM_SCM_TSENS_INIT_ID,
+ };
+ struct qcom_scm_res res;
+
+ ret = qcom_scm_call(__scm->dev, &desc, &res);
+ if (tsens_ret)
+ *tsens_ret = res.result[0];
+
+ return ret;
+}
+EXPORT_SYMBOL(qcom_scm_tsens_reinit);
+
static int __qcom_scm_assign_mem(struct device *dev, phys_addr_t mem_region,
size_t mem_sz, phys_addr_t src, size_t src_sz,
phys_addr_t dest, size_t dest_sz)
diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h
index 0d51eef2472f..495fa00230c7 100644
--- a/drivers/firmware/qcom_scm.h
+++ b/drivers/firmware/qcom_scm.h
@@ -94,6 +94,10 @@ extern int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc,
#define QCOM_SCM_PIL_PAS_IS_SUPPORTED 0x07
#define QCOM_SCM_PIL_PAS_MSS_RESET 0x0a
+/* TSENS Services and Function IDs */
+#define QCOM_SCM_SVC_TSENS 0x1E
+#define QCOM_SCM_TSENS_INIT_ID 0x5
+
#define QCOM_SCM_SVC_IO 0x05
#define QCOM_SCM_IO_READ 0x01
#define QCOM_SCM_IO_WRITE 0x02
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
index b293ed32174b..9bb542f16482 100644
--- a/drivers/thermal/qcom/tsens-v2.c
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -88,6 +88,9 @@ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
/* TRDY: 1=ready, 0=in progress */
[TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
+
+ /* FIRST_ROUND_COMPLETE: 1=complete, 0=not complete */
+ [FIRST_ROUND_COMPLETE] = REG_FIELD(TM_TRDY_OFF, 3, 3),
};
static const struct tsens_ops ops_generic_v2 = {
@@ -101,6 +104,17 @@ struct tsens_plat_data data_tsens_v2 = {
.fields = tsens_v2_regfields,
};
+/* For sm8150 tsens, its suggested to monitor the controller health
+ * periodically and in case an issue is detected to reinit tsens
+ * controller via trustzone.
+ */
+struct tsens_plat_data data_tsens_sm8150 = {
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .needs_reinit_wa = true,
+ .fields = tsens_v2_regfields,
+};
+
/* Kept around for backward compatibility with old msm8996.dtsi */
struct tsens_plat_data data_8996 = {
.num_sensors = 13,
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
index e49f58e83513..be2445923d85 100644
--- a/drivers/thermal/qcom/tsens.c
+++ b/drivers/thermal/qcom/tsens.c
@@ -7,6 +7,7 @@
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/io.h>
+#include <linux/qcom_scm.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
@@ -21,6 +22,8 @@
#include "../thermal_hwmon.h"
#include "tsens.h"
+LIST_HEAD(tsens_device_list);
+
/**
* struct tsens_irq_data - IRQ status and temperature violations
* @up_viol: upper threshold violated
@@ -594,19 +597,159 @@ static void tsens_disable_irq(struct tsens_priv *priv)
regmap_field_write(priv->rf[INT_EN], 0);
}
+static int tsens_reenable_hw_after_scm(struct tsens_priv *priv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->ul_lock, flags);
+
+ /* Re-enable watchdog, unmask the bark and
+ * disable cycle completion monitoring.
+ */
+ regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1);
+ regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0);
+ regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
+ regmap_field_write(priv->rf[CC_MON_MASK], 1);
+
+ /* Re-enable interrupts */
+ tsens_enable_irq(priv);
+
+ spin_unlock_irqrestore(&priv->ul_lock, flags);
+
+ return 0;
+}
+
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
{
- struct tsens_priv *priv = s->priv;
+ struct tsens_priv *priv = s->priv, *priv_reinit;
int hw_id = s->hw_id;
u32 temp_idx = LAST_TEMP_0 + hw_id;
u32 valid_idx = VALID_0 + hw_id;
u32 valid;
- int ret;
+ int ret, trdy, first_round, tsens_ret, sw_reg;
+ unsigned long timeout;
+ static atomic_t in_tsens_reinit;
/* VER_0 doesn't have VALID bit */
if (tsens_version(priv) == VER_0)
goto get_temp;
+ /* For some tsens controllers, its suggested to
+ * monitor the controller health periodically
+ * and in case an issue is detected to reinit
+ * tsens controller via trustzone.
+ */
+ if (priv->needs_reinit_wa) {
+ /* First check if TRDY is SET */
+ timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
+ do {
+ ret = regmap_field_read(priv->rf[TRDY], &trdy);
+ if (ret)
+ goto err;
+ if (!trdy)
+ continue;
+ } while (time_before(jiffies, timeout));
+
+ if (!trdy) {
+ ret = regmap_field_read(priv->rf[FIRST_ROUND_COMPLETE], &first_round);
+ if (ret)
+ goto err;
+
+ if (!first_round) {
+ if (atomic_read(&in_tsens_reinit)) {
+ dev_dbg(priv->dev, "tsens re-init is in progress\n");
+ ret = -EAGAIN;
+ goto err;
+ }
+
+ /* Wait for 2 ms for tsens controller to recover */
+ timeout = jiffies + msecs_to_jiffies(RESET_TIMEOUT_MS);
+ do {
+ ret = regmap_field_read(priv->rf[FIRST_ROUND_COMPLETE],
+ &first_round);
+ if (ret)
+ goto err;
+
+ if (first_round) {
+ dev_dbg(priv->dev, "tsens controller recovered\n");
+ goto sensor_read;
+ }
+ } while (time_before(jiffies, timeout));
+
+ /*
+ * tsens controller did not recover,
+ * proceed with SCM call to re-init it
+ */
+ if (atomic_read(&in_tsens_reinit)) {
+ dev_dbg(priv->dev, "tsens re-init is in progress\n");
+ ret = -EAGAIN;
+ goto err;
+ }
+
+ atomic_set(&in_tsens_reinit, 1);
+
+ /*
+ * Invoke scm call only if SW register write is
+ * reflecting in controller. Try it for 2 ms.
+ */
+ timeout = jiffies + msecs_to_jiffies(RESET_TIMEOUT_MS);
+ do {
+ ret = regmap_field_write(priv->rf[INT_EN], BIT(2));
+ if (ret)
+ goto err_unset;
+
+ ret = regmap_field_read(priv->rf[INT_EN], &sw_reg);
+ if (ret)
+ goto err_unset;
+
+ if (!(sw_reg & BIT(2)))
+ continue;
+ } while (time_before(jiffies, timeout));
+
+ if (!(sw_reg & BIT(2))) {
+ ret = -ENOTRECOVERABLE;
+ goto err_unset;
+ }
+
+ ret = qcom_scm_tsens_reinit(&tsens_ret);
+ if (ret || tsens_ret) {
+ dev_err(priv->dev, "tsens reinit scm call failed (%d : %d)\n",
+ ret, tsens_ret);
+ if (tsens_ret)
+ ret = -ENOTRECOVERABLE;
+
+ goto err_unset;
+ }
+
+ /* After the SCM call, we need to re-enable
+ * the interrupts and also set active threshold
+ * for each sensor.
+ */
+ list_for_each_entry(priv_reinit,
+ &tsens_device_list, list) {
+ ret = tsens_reenable_hw_after_scm(priv_reinit);
+ if (ret) {
+ dev_err(priv->dev,
+ "tsens re-enable after scm call failed (%d)\n",
+ ret);
+ ret = -ENOTRECOVERABLE;
+ goto err_unset;
+ }
+ }
+
+ atomic_set(&in_tsens_reinit, 0);
+
+ /* Notify reinit wa worker */
+ list_for_each_entry(priv_reinit,
+ &tsens_device_list, list) {
+ queue_work(priv_reinit->reinit_wa_worker,
+ &priv_reinit->reinit_wa_notify);
+ }
+ }
+ }
+ }
+
+sensor_read:
/* Valid bit is 0 for 6 AHB clock cycles.
* At 19.2MHz, 1 AHB clock is ~60ns.
* We should enter this loop very, very rarely.
@@ -623,6 +766,12 @@ get_temp:
*temp = tsens_hw_to_mC(s, temp_idx);
return 0;
+
+err_unset:
+ atomic_set(&in_tsens_reinit, 0);
+
+err:
+ return ret;
}
int get_temp_common(const struct tsens_sensor *s, int *temp)
@@ -860,6 +1009,14 @@ int __init init_common(struct tsens_priv *priv)
goto err_put_device;
}
+ priv->rf[FIRST_ROUND_COMPLETE] = devm_regmap_field_alloc(dev,
+ priv->tm_map,
+ priv->fields[FIRST_ROUND_COMPLETE]);
+ if (IS_ERR(priv->rf[FIRST_ROUND_COMPLETE])) {
+ ret = PTR_ERR(priv->rf[FIRST_ROUND_COMPLETE]);
+ goto err_put_device;
+ }
+
/* This loop might need changes if enum regfield_ids is reordered */
for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
for (i = 0; i < priv->feat->max_sensors; i++) {
@@ -981,6 +1138,9 @@ static const struct of_device_id tsens_table[] = {
.compatible = "qcom,msm8996-tsens",
.data = &data_8996,
}, {
+ .compatible = "qcom,sm8150-tsens",
+ .data = &data_tsens_sm8150,
+ }, {
.compatible = "qcom,tsens-v1",
.data = &data_tsens_v1,
}, {
@@ -1082,6 +1242,43 @@ static int tsens_register(struct tsens_priv *priv)
return ret;
}
+static void tsens_reinit_worker_notify(struct work_struct *work)
+{
+ int i, ret, temp;
+ struct tsens_irq_data d;
+ struct tsens_priv *priv = container_of(work, struct tsens_priv,
+ reinit_wa_notify);
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ const struct tsens_sensor *s = &priv->sensor[i];
+ u32 hw_id = s->hw_id;
+
+ if (!s->tzd)
+ continue;
+ if (!tsens_threshold_violated(priv, hw_id, &d))
+ continue;
+
+ ret = get_temp_tsens_valid(s, &temp);
+ if (ret) {
+ dev_err(priv->dev, "[%u] %s: error reading sensor\n",
+ hw_id, __func__);
+ continue;
+ }
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+
+ if ((d.up_thresh < temp) || (d.low_thresh > temp)) {
+ dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
+ hw_id, __func__, temp);
+ thermal_zone_device_update(s->tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ } else {
+ dev_dbg(priv->dev, "[%u] %s: no violation: %d\n",
+ hw_id, __func__, temp);
+ }
+ }
+}
+
static int tsens_probe(struct platform_device *pdev)
{
int ret, i;
@@ -1123,6 +1320,20 @@ static int tsens_probe(struct platform_device *pdev)
priv->dev = dev;
priv->num_sensors = num_sensors;
+ priv->needs_reinit_wa = data->needs_reinit_wa;
+
+ if (priv->needs_reinit_wa && !qcom_scm_is_available())
+ return -EPROBE_DEFER;
+
+ if (priv->needs_reinit_wa) {
+ priv->reinit_wa_worker = alloc_workqueue("tsens_reinit_work",
+ WQ_HIGHPRI, 0);
+ if (!priv->reinit_wa_worker)
+ return -ENOMEM;
+
+ INIT_WORK(&priv->reinit_wa_notify, tsens_reinit_worker_notify);
+ }
+
priv->ops = data->ops;
for (i = 0; i < priv->num_sensors; i++) {
if (data->hw_ids)
@@ -1135,13 +1346,15 @@ static int tsens_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, priv);
- if (!priv->ops || !priv->ops->init || !priv->ops->get_temp)
- return -EINVAL;
+ if (!priv->ops || !priv->ops->init || !priv->ops->get_temp) {
+ ret = -EINVAL;
+ goto free_wq;
+ }
ret = priv->ops->init(priv);
if (ret < 0) {
dev_err(dev, "%s: init failed\n", __func__);
- return ret;
+ goto free_wq;
}
if (priv->ops->calibrate) {
@@ -1149,11 +1362,23 @@ static int tsens_probe(struct platform_device *pdev)
if (ret < 0) {
if (ret != -EPROBE_DEFER)
dev_err(dev, "%s: calibration failed\n", __func__);
- return ret;
+
+ goto free_wq;
}
}
- return tsens_register(priv);
+ ret = tsens_register(priv);
+ if (ret < 0) {
+ dev_err(dev, "%s: registration failed\n", __func__);
+ goto free_wq;
+ }
+
+ list_add_tail(&priv->list, &tsens_device_list);
+ return 0;
+
+free_wq:
+ destroy_workqueue(priv->reinit_wa_worker);
+ return ret;
}
static int tsens_remove(struct platform_device *pdev)
@@ -1165,6 +1390,8 @@ static int tsens_remove(struct platform_device *pdev)
if (priv->ops->disable)
priv->ops->disable(priv);
+ destroy_workqueue(priv->reinit_wa_worker);
+
return 0;
}
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
index ba05c8233356..ec7b2d941852 100644
--- a/drivers/thermal/qcom/tsens.h
+++ b/drivers/thermal/qcom/tsens.h
@@ -14,6 +14,7 @@
#define SLOPE_FACTOR 1000
#define SLOPE_DEFAULT 3200
#define TIMEOUT_US 100
+#define RESET_TIMEOUT_MS 2
#define THRESHOLD_MAX_ADC_CODE 0x3ff
#define THRESHOLD_MIN_ADC_CODE 0x0
@@ -165,6 +166,7 @@ enum regfield_ids {
/* ----- TM ------ */
/* TRDY */
TRDY,
+ FIRST_ROUND_COMPLETE,
/* INTERRUPT ENABLE */
INT_EN, /* v2+ has separate enables for crit, upper and lower irq */
/* STATUS */
@@ -513,6 +515,7 @@ struct tsens_features {
* @num_sensors: 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
+ * @needs_reinit_wa: tsens controller might need reinit via trustzone
* @feat: features of the IP
* @fields: bitfield locations
*/
@@ -520,6 +523,7 @@ struct tsens_plat_data {
const u32 num_sensors;
const struct tsens_ops *ops;
unsigned int *hw_ids;
+ bool needs_reinit_wa;
struct tsens_features *feat;
const struct reg_field *fields;
};
@@ -542,6 +546,7 @@ struct tsens_context {
* @srot_map: pointer to SROT register address space
* @tm_offset: deal with old device trees that don't address TM and SROT
* address space separately
+ * @needs_reinit_wa: tsens controller might need reinit via trustzone
* @ul_lock: lock while processing upper/lower threshold interrupts
* @crit_lock: lock while processing critical threshold interrupts
* @rf: array of regmap_fields used to store value of the field
@@ -559,6 +564,11 @@ struct tsens_priv {
struct regmap *tm_map;
struct regmap *srot_map;
u32 tm_offset;
+ bool needs_reinit_wa;
+ struct workqueue_struct *reinit_wa_worker;
+ struct work_struct reinit_wa_notify;
+
+ struct list_head list;
/* lock for upper/lower threshold interrupts */
spinlock_t ul_lock;
@@ -591,6 +601,6 @@ extern struct tsens_plat_data data_8916, data_8939, data_8974, data_9607;
extern struct tsens_plat_data data_tsens_v1, data_8976;
/* TSENS v2 targets */
-extern struct tsens_plat_data data_8996, data_tsens_v2;
+extern struct tsens_plat_data data_8996, data_tsens_sm8150, data_tsens_v2;
#endif /* __QCOM_TSENS_H__ */
diff --git a/include/linux/qcom_scm.h b/include/linux/qcom_scm.h
index f8335644a01a..f8c9eb739df1 100644
--- a/include/linux/qcom_scm.h
+++ b/include/linux/qcom_scm.h
@@ -124,4 +124,6 @@ extern int qcom_scm_lmh_dcvsh(u32 payload_fn, u32 payload_reg, u32 payload_val,
extern int qcom_scm_lmh_profile_change(u32 profile_id);
extern bool qcom_scm_lmh_dcvsh_available(void);
+extern int qcom_scm_tsens_reinit(int *tsens_ret);
+
#endif