Re: [PATCH v4 3/4] thermal: qcom: tsens: Add driver support for re-initialization quirk

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Sep 01, 2022 at 04:24:13PM +0530, Bhupesh Sharma wrote:
> Since for some Qualcomm tsens controllers, its suggested to
> monitor the controller health periodically and in case an
> issue is detected, to re-initialize the tsens controller
> via trustzone, add the support for the same in the
> qcom tsens driver.
> 
> Note that once the tsens controller is reset using scm call,
> all SROT and TM region registers will enter the reset mode.
> 
> While all the SROT registers will be re-programmed and
> re-enabled in trustzone prior to the scm call exit, the TM
> region registers will not re-initialized in trustzone and thus
> need to be handled by the tsens driver.
> 
> Cc: Bjorn Andersson <andersson@xxxxxxxxxx>
> Cc: Amit Kucheria <amitk@xxxxxxxxxx>
> Cc: Thara Gopinath <thara.gopinath@xxxxxxxxx>
> Cc: linux-pm@xxxxxxxxxxxxxxx
> Cc: linux-arm-msm@xxxxxxxxxxxxxxx
> Signed-off-by: Bhupesh Sharma <bhupesh.sharma@xxxxxxxxxx>
> ---
>  drivers/thermal/qcom/tsens-v2.c |   3 +
>  drivers/thermal/qcom/tsens.c    | 190 ++++++++++++++++++++++++++++++++
>  drivers/thermal/qcom/tsens.h    |  12 ++
>  3 files changed, 205 insertions(+)
> 
> diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
> index b293ed32174b..f521e4479cc5 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 = {
> diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
> index b1b10005fb28..ecf544683e73 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>
> @@ -594,6 +595,107 @@ static void tsens_disable_irq(struct tsens_priv *priv)
>  	regmap_field_write(priv->rf[INT_EN], 0);
>  }
>  
> +static void tsens_reenable_hw_after_scm(struct tsens_priv *priv)
> +{
> +	/*
> +	 * 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);
> +}
> +
> +static int tsens_health_check_and_reinit(struct tsens_priv *priv,
> +					 int hw_id)

This function is much easier to read now, thanks.

> +{
> +	int ret, trdy, first_round, sw_reg;
> +	unsigned long timeout;
> +
> +	/* Check early if the mutex is in locked state */
> +	WARN_ON(!mutex_is_locked(&priv->reinit_mutex));
> +
> +	/* First check if TRDY is SET */
> +	ret = regmap_field_read(priv->rf[TRDY], &trdy);
> +	if (ret)
> +		goto err;
> +
> +	if (trdy)
> +		return 0; /* success */
> +
> +	ret = regmap_field_read(priv->rf[FIRST_ROUND_COMPLETE], &first_round);
> +	if (ret)
> +		goto err;
> +
> +	if (first_round)
> +		return 0; /* success */
> +
> +	/* Wait for 2 ms for tsens controller to recover */
> +	ret = regmap_field_read_poll_timeout(priv->rf[FIRST_ROUND_COMPLETE],
> +					     first_round, first_round, 100,
> +					     RESET_TIMEOUT_MS * USEC_PER_MSEC);
> +	if (ret == 0) {
> +		dev_dbg(priv->dev, "tsens controller recovered\n");
> +		return 0; /* success */
> +	}
> +
> +	if (ret)
> +		goto err;
> +
> +	spin_lock(&priv->reinit_lock);

This spinlock is only used to ensure mutual exclusion of the
reinitialization attempt, but this is only attempted under the
reinit_mutex.

As such you should be able to eliminate the spinlock.

> +
> +	/*
> +	 * Invoke SCM call only if SW register write is reflecting in controller.
> +	 * Try it for 2 ms. In case that fails mark the tsens controller as
> +	 * unrecoverable.
> +	 */
> +	timeout = jiffies + msecs_to_jiffies(RESET_TIMEOUT_MS);
> +	do {
> +		ret = regmap_field_write(priv->rf[INT_EN], CRITICAL_INT_EN);
> +		if (ret)
> +			goto err_unlock;
> +
> +		ret = regmap_field_read(priv->rf[INT_EN], &sw_reg);
> +		if (ret)
> +			goto err_unlock;
> +	} while ((sw_reg & CRITICAL_INT_EN) && (time_before(jiffies, timeout)));
> +
> +	if (!(sw_reg & CRITICAL_INT_EN)) {
> +		ret = -ENOTRECOVERABLE;
> +		goto err_unlock;
> +	}
> +
> +	/* tsens controller did not recover, proceed with SCM call to re-init it. */
> +	ret = qcom_scm_tsens_reinit();
> +	if (ret) {
> +		dev_err(priv->dev, "tsens reinit scm call failed (%d)\n", ret);
> +		goto err_unlock;
> +	}
> +
> +	/*
> +	 * After the SCM call, we need to re-enable the interrupts and also set
> +	 * active threshold for each sensor.
> +	 */
> +	tsens_reenable_hw_after_scm(priv);
> +
> +	/* Notify reinit wa worker */
> +	queue_work(system_highpri_wq, &priv->reinit_wa_notify);
> +
> +	spin_unlock(&priv->reinit_lock);
> +
> +	return 0; /* success */
> +
> +err_unlock:
> +	spin_unlock(&priv->reinit_lock);
> +
> +err:
> +	return ret;
> +}
> +
>  int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
>  {
>  	struct tsens_priv *priv = s->priv;
> @@ -607,6 +709,20 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
>  	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) {
> +		mutex_lock(&priv->reinit_mutex);
> +		ret = tsens_health_check_and_reinit(priv, hw_id);
> +		mutex_unlock(&priv->reinit_mutex);
> +
> +		if (ret)
> +			return ret;
> +	}
> +
>  	/* 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.
> @@ -739,6 +855,40 @@ static const struct regmap_config tsens_srot_config = {
>  	.reg_stride	= 4,
>  };
>  
> +static void __tsens_reinit_worker(struct tsens_priv *priv)
> +{
> +	int ret, temp;
> +	unsigned int i;
> +	struct tsens_irq_data d;
> +
> +	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] error reading sensor during reinit\n", hw_id);
> +			continue;
> +		}
> +
> +		tsens_read_irq_state(priv, hw_id, s, &d);
> +
> +		if ((d.up_thresh < temp) || (d.low_thresh > temp)) {
> +			dev_dbg(priv->dev, "[%u] TZ update trigger during reinit (%d mC)\n",
> +				hw_id, temp);
> +			thermal_zone_device_update(s->tzd, THERMAL_EVENT_UNSPECIFIED);
> +		} else {
> +			dev_dbg(priv->dev, "[%u] no violation during reinit (%d)\n",
> +				hw_id, temp);
> +		}
> +	}
> +}
> +
>  int __init init_common(struct tsens_priv *priv)
>  {
>  	void __iomem *tm_base, *srot_base;
> @@ -860,6 +1010,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++) {
> @@ -1082,6 +1240,14 @@ static int tsens_register(struct tsens_priv *priv)
>  	return ret;
>  }
>  
> +static void tsens_reinit_worker_notify(struct work_struct *work)
> +{
> +	struct tsens_priv *priv = container_of(work, struct tsens_priv,
> +					       reinit_wa_notify);
> +
> +	__tsens_reinit_worker(priv);

Seems reasonable to inline that.

> +}
> +
>  static int tsens_probe(struct platform_device *pdev)
>  {
>  	int ret, i;
> @@ -1123,6 +1289,11 @@ 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;
> +
>  	priv->ops = data->ops;
>  	for (i = 0;  i < priv->num_sensors; i++) {
>  		if (data->hw_ids)
> @@ -1138,6 +1309,25 @@ static int tsens_probe(struct platform_device *pdev)
>  	if (!priv->ops || !priv->ops->init || !priv->ops->get_temp)
>  		return -EINVAL;
>  
> +	/*
> +	 * Reinitialization workaround is currently supported only for
> +	 * tsens controller versions v2.
> +	 *
> +	 * If incorrect platform data is passed to this effect, ignore
> +	 * the requested setting and move forward.
> +	 */
> +	if (priv->needs_reinit_wa && (tsens_version(priv) < VER_2_X)) {
> +		dev_warn(dev,
> +			 "%s: Reinit quirk available only for tsens v2\n", __func__);
> +		priv->needs_reinit_wa = false;
> +	}
> +
> +	mutex_init(&priv->reinit_mutex);
> +	spin_lock_init(&priv->reinit_lock);
> +
> +	if (priv->needs_reinit_wa)
> +		INIT_WORK(&priv->reinit_wa_notify, tsens_reinit_worker_notify);
> +
>  	ret = priv->ops->init(priv);
>  	if (ret < 0) {
>  		dev_err(dev, "%s: init failed\n", __func__);
> diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
> index 92787017c6ab..900d2a74d25e 100644
> --- a/drivers/thermal/qcom/tsens.h
> +++ b/drivers/thermal/qcom/tsens.h
> @@ -14,9 +14,12 @@
>  #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
>  
> +#define CRITICAL_INT_EN		(BIT(2))
> +
>  #include <linux/interrupt.h>
>  #include <linux/thermal.h>
>  #include <linux/regmap.h>
> @@ -165,6 +168,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 */
> @@ -564,6 +568,14 @@ struct tsens_priv {
>  	u32				tm_offset;
>  	bool				needs_reinit_wa;
>  
> +	struct work_struct		reinit_wa_notify;
> +
> +	/* protects reinit related serialization */

/* serializes health check and reinit */ ?

> +	struct mutex			reinit_mutex;
> +
> +	/* lock for reinit workaround */
> +	spinlock_t			reinit_lock;
> +

Regards,
Bjorn

>  	/* lock for upper/lower threshold interrupts */
>  	spinlock_t			ul_lock;
>  
> -- 
> 2.37.1
> 



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux