Thread (27 messages) 27 messages, 4 authors, 2019-09-10

Re: [PATCH v2 15/15] drivers: thermal: tsens: Add interrupt support

From: Stephen Boyd <hidden>
Date: 2019-08-28 21:42:32
Also in: linux-arm-msm, lkml

Quoting Amit Kucheria (2019-08-27 05:14:11)
quoted hunk ↗ jump to hunk
diff --git a/drivers/thermal/qcom/tsens-common.c b/drivers/thermal/qcom/tsens-common.c
index 06b44cfd5eab9..c549f8e1488ba 100644
--- a/drivers/thermal/qcom/tsens-common.c
+++ b/drivers/thermal/qcom/tsens-common.c
@@ -114,6 +146,314 @@ static int tsens_hw_to_mC(struct tsens_sensor *s, int field)
        return sign_extend32(temp, priv->tempres) * 100;
 }
 
+/**
+ * tsens_mC_to_hw - Return correct value to be written to threshold
+ * registers, whether in ADC code or deciCelsius depending on IP version
Document arguments and return value? Maybe summary can be 'convert
tsens temperature to hardware register value'?
+ */
+static int tsens_mC_to_hw(struct tsens_sensor *s, int temp)
+{
+       struct tsens_priv *priv = s->priv;
+
+       if (priv->feat->adc) {
+               /* milliC to C to adc code */
+               return degc_to_code(temp / 1000, s);
+       }
+
+       /* milliC to deciC */
+       return temp / 100;
+}
+
+static inline unsigned int tsens_ver(struct tsens_priv *priv)
Can this return the enum instead of unsigned int?
+{
+       return priv->feat->ver_major;
+}
+
+/**
+ * tsens_set_interrupt_v1 - Disable an interrupt (enable = false)
+ *                          Re-enable an interrupt (enable = true)
+ */
+static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
+                                  enum tsens_irq_type irq_type, bool enable)
+{
+       u32 index;
+
+       if (enable) {
+               switch (irq_type) {
+               case UPPER:
+                       index = UP_INT_CLEAR_0 + hw_id;
+                       break;
+               case LOWER:
+                       index = LOW_INT_CLEAR_0 + hw_id;
+                       break;
+               }
+               regmap_field_write(priv->rf[index], 0);
+       } else {
+               switch (irq_type) {
+               case UPPER:
+                       index = UP_INT_CLEAR_0 + hw_id;
+                       break;
+               case LOWER:
+                       index = LOW_INT_CLEAR_0 + hw_id;
+                       break;
+               }
+               regmap_field_write(priv->rf[index], 1);
+       }
De-dup the switch statement and have

	regmap_field_write(priv->rf[index], enable ? 1 : 0);
+}
+
+/**
+ * tsens_set_interrupt_v2 - Disable an interrupt (enable = false)
+ *                          Re-enable an interrupt (enable = true)
+ */
+static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
+                                  enum tsens_irq_type irq_type, bool enable)
+{
+       u32 index_mask, index_clear;
+
+       if (enable) {
+               switch (irq_type) {
+               case UPPER:
+                       index_mask = UP_INT_MASK_0 + hw_id;
+                       break;
+               case LOWER:
+                       index_mask = LOW_INT_MASK_0 + hw_id;
+                       break;
+               }
+               regmap_field_write(priv->rf[index_mask], 0);
+       } else {
+               /* To disable the interrupt flag for a sensor:
Nitpick: Wrong comment style.
+                *  1. Mask further interrupts for this sensor
+                *  2. Write 1 followed by 0 to clear the interrupt
+                */
+               switch (irq_type) {
+               case UPPER:
+                       index_mask  = UP_INT_MASK_0 + hw_id;
+                       index_clear = UP_INT_CLEAR_0 + hw_id;
+                       break;
+               case LOWER:
+                       index_mask  = LOW_INT_MASK_0 + hw_id;
+                       index_clear = LOW_INT_CLEAR_0 + hw_id;
+                       break;
+               }
Please extract index_mask and index_clear assignments to one switch
statement and then change the sequence to an if/else

	if (enable) {
               regmap_field_write(priv->rf[index_mask], 1);
               regmap_field_write(priv->rf[index_clear], 1);
               regmap_field_write(priv->rf[index_clear], 0);
       } else {
               regmap_field_write(priv->rf[index_mask], 0);
       }
+}
+
+/**
+ * tsens_set_interrupt - Disable an interrupt (enable = false)
+ *                       Re-enable an interrupt (enable = true)
+ */
+static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
+                               enum tsens_irq_type irq_type, bool enable)
+{
+       dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
+               irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
+               enable ? "en" : "dis");
+       if (tsens_ver(priv) > VER_1_X)
+               tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
+       else
+               tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
+}
+
+/**
+ * tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
+ *
Document arguments?
+ * Return: 0 if threshold was not violated, 1 if it was violated and negative
+ * errno in case of errors
+ */
+static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
+                                   struct tsens_irq_data *d)
+{
+       int ret;
+
+       ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
+       if (ret)
+               return ret;
+       ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
+       if (ret)
+               return ret;
+       if (d->up_viol || d->low_viol)
+               return 1;
+
+       return 0;
+}
+
+static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
+                               struct tsens_sensor *s, struct tsens_irq_data *d)
+{
+       int ret;
+
+       ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
+       if (ret)
+               return ret;
+       ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
+       if (ret)
+               return ret;
+       if (tsens_ver(priv) > VER_1_X) {
+               ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
+               if (ret)
+                       return ret;
+               ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
+               if (ret)
+                       return ret;
+       } else {
+               /* No mask register on older TSENS */
+               d->up_irq_mask = 0;
+               d->low_irq_mask = 0;
+       }
+
+       d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
+       d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
+
+       dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u) | clr(%u|%u) | mask(%u|%u)\n",
+               hw_id, __func__, (d->up_viol || d->low_viol) ? "(V)" : "",
+               d->low_viol, d->up_viol, d->low_irq_clear, d->up_irq_clear,
+               d->low_irq_mask, d->up_irq_mask);
+       dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d)\n", hw_id, __func__,
+               (d->up_viol || d->low_viol) ? "(violation)" : "",
+               d->low_thresh, d->up_thresh);
+
+       return 0;
+}
+
+static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
+{
+       if (ver > VER_1_X)
+               return mask & (1 << hw_id);
+
+       /* v1, v0.1 don't have a irq mask register */
+       return 0;
+}
+
+irqreturn_t tsens_irq_thread(int irq, void *data)
+{
+       struct tsens_priv *priv = data;
+       struct tsens_irq_data d;
+       bool enable = true, disable = false;
+       unsigned long flags;
+       int temp, ret, i;
+
+       /*
+        * Check if any sensor raised an IRQ - for each sensor connected to the
+        * TSENS block if it set the threshold violation bit.
+        */
+       for (i = 0; i < priv->num_sensors; i++) {
+               bool trigger = 0;
How about trigger = false? It's a bool.
+               struct tsens_sensor *s = &priv->sensor[i];
+               u32 hw_id = s->hw_id;
+
+               if (IS_ERR(priv->sensor[i].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__);
I hope there isn't an interrupt storm where we're trying to print out
messages from the irq handler.
+                       continue;
+               }
+
+               spin_lock_irqsave(&priv->ul_lock, flags);
+
+               tsens_read_irq_state(priv, hw_id, s, &d);
+
+               if (d.up_viol &&
+                   !masked_irq(hw_id, d.up_irq_mask, tsens_ver(priv))) {
+                       tsens_set_interrupt(priv, hw_id, UPPER, disable);
+                       if (d.up_thresh > temp) {
+                               dev_dbg(priv->dev, "[%u] %s: re-arm upper\n",
+                                       priv->sensor[i].hw_id, __func__);
+                               /* unmask the interrupt for this sensor */
+                               tsens_set_interrupt(priv, hw_id, UPPER, enable);
+                       } else {
+                               trigger = 1;
+                               /* Keep irq masked */
+                       }
+               } else if (d.low_viol &&
+                          !masked_irq(hw_id, d.low_irq_mask, tsens_ver(priv))) {
+                       tsens_set_interrupt(priv, hw_id, LOWER, disable);
+                       if (d.low_thresh < temp) {
+                               dev_dbg(priv->dev, "[%u] %s: re-arm low\n",
+                                       priv->sensor[i].hw_id, __func__);
+                               /* unmask the interrupt for this sensor */
+                               tsens_set_interrupt(priv, hw_id, LOWER, enable);
+                       } else {
+                               trigger = 1;
+                               /* Keep irq masked */
+                       }
+               }
+
+               spin_unlock_irqrestore(&priv->ul_lock, flags);
+
+               if (trigger) {
+                       dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
+                               hw_id, __func__, temp);
+                       thermal_zone_device_update(priv->sensor[i].tzd,
+                                                  THERMAL_EVENT_UNSPECIFIED);
+               } else {
+                       dev_dbg(priv->dev, "[%u] %s: no violation:  %d\n",
+                               hw_id, __func__, temp);
+               }
+       }
+
+       return IRQ_HANDLED;
Should we return IRQ_NONE in the case that the above for loop didn't
find anything in those if/else-ifs?
+}
+
+int tsens_set_trips(void *_sensor, int low, int high)
+{
+       struct tsens_sensor *s = _sensor;
+       struct tsens_priv *priv = s->priv;
+       struct device *dev = priv->dev;
+       struct tsens_irq_data d;
+       unsigned long flags;
+       int high_val, low_val, cl_high, cl_low;
+       bool enable = true;
+       u32 hw_id = s->hw_id;
+
+       dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
+               hw_id, __func__, low, high);
+
+       cl_high = clamp_val(high, -40000, 120000);
+       cl_low  = clamp_val(low, -40000, 120000);
+
+       high_val = tsens_mC_to_hw(s, cl_high);
+       low_val  = tsens_mC_to_hw(s, cl_low);
+
+       spin_lock_irqsave(&priv->ul_lock, flags);
+
+       tsens_read_irq_state(priv, hw_id, s, &d);
+
+       /* Write the new thresholds and clear the status */
+       regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
+       regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
+       tsens_set_interrupt(priv, hw_id, LOWER, enable);
+       tsens_set_interrupt(priv, hw_id, UPPER, enable);
Just pass true? Why is there an enable local variable?
+
+       spin_unlock_irqrestore(&priv->ul_lock, flags);
+
+       dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
+               s->hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
+
+       return 0;
+}
+
[...]
quoted hunk ↗ jump to hunk
@@ -319,28 +659,31 @@ int __init init_common(struct tsens_priv *priv)
                ret = PTR_ERR(priv->rf[SENSOR_EN]);
                goto err_put_device;
        }
-       /* now alloc regmap_fields in tm_map */
-       for (i = 0, j = LAST_TEMP_0; i < priv->feat->max_sensors; i++, j++) {
-               priv->rf[j] = devm_regmap_field_alloc(dev, priv->tm_map,
-                                                     priv->fields[j]);
-               if (IS_ERR(priv->rf[j])) {
-                       ret = PTR_ERR(priv->rf[j]);
-                       goto err_put_device;
-               }
+       priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
+                                                  priv->fields[INT_EN]);
+       if (IS_ERR(priv->rf[INT_EN])) {
+               ret = PTR_ERR(priv->rf[INT_EN]);
+               goto err_put_device;
        }
 
-       /* Save away resolution of signed temperature value for this IP */
-       priv->tempres = priv->fields[LAST_TEMP_0].msb - priv->fields[LAST_TEMP_0].lsb;
-
-       for (i = 0, j = VALID_0; i < priv->feat->max_sensors; i++, j++) {
-               priv->rf[j] = devm_regmap_field_alloc(dev, priv->tm_map,
-                                                     priv->fields[j]);
-               if (IS_ERR(priv->rf[j])) {
-                       ret = PTR_ERR(priv->rf[j]);
-                       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++) {
+                       int idx = j + i;
+
+                       priv->rf[idx] = devm_regmap_field_alloc(dev, priv->tm_map,
+                                                               priv->fields[idx]);
+                       if (IS_ERR(priv->rf[idx])) {
+                               ret = PTR_ERR(priv->rf[idx]);
+                               goto err_put_device;
+                       }
                }
        }
+       /* Save away resolution of signed temperature value for this IP */
+       priv->tempres = priv->fields[LAST_TEMP_0].msb - priv->fields[LAST_TEMP_0].lsb;
Leave this where it was, i.e. before the for loop? Or is that a bug and
it doesn't actually work unless it's after the for loop? In which case,
this should go to the previous patch.
quoted hunk ↗ jump to hunk
 
+       spin_lock_init(&priv->ul_lock);
+       tsens_enable_irq(priv);
        tsens_debug_init(op);
 
        return 0;
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
index 772aa76b50e12..a4335717aeede 100644
--- a/drivers/thermal/qcom/tsens.c
+++ b/drivers/thermal/qcom/tsens.c
@@ -96,7 +99,33 @@ static int tsens_register(struct tsens_priv *priv)
                if (priv->ops->enable)
                        priv->ops->enable(priv, i);
        }
+
+       pdev = of_find_device_by_node(priv->dev->of_node);
+       if (!pdev) {
+               dev_err(&pdev->dev, "%s: device node not found in DT\n", __func__);
Do we really need this? Maybe just bail out in silence because this
should never happen?
+               return -ENODEV;
+       }
+
+       irq = platform_get_irq_byname(pdev, "uplow");
+       if (irq < 0) {
+               dev_err(&pdev->dev, "%s: missing irq in dt: uplow\n", __func__);
You can drop the error print. I upstreamed a change to print the error
generically in the core.
+               return irq;
Did we need to put_device() here?
+       }
+
+       ret = devm_request_threaded_irq(&pdev->dev, irq,
+                                       NULL, tsens_irq_thread,
+                                       IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+                                       dev_name(&pdev->dev), priv);
+       if (ret) {
+               dev_err(&pdev->dev, "%s: failed to get irq\n", __func__);
+               goto err_put_device;
+       }
+       enable_irq_wake(irq);
        return 0;
+
+err_put_device:
+       put_device(&pdev->dev);
+       return ret;
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help