]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/thermal/rcar_gen3_thermal.c
net: dsa: change scope of bridging code
[karo-tx-linux.git] / drivers / thermal / rcar_gen3_thermal.c
index d33c845244b1d819b774ed482f78665013ee5203..37fcefd06d9f81b4eb1238634103b5e1ab06a5c3 100644 (file)
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/module.h>
-#include <linux/mutex.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
 #include <linux/thermal.h>
 
+#include "thermal_core.h"
+
 /* Register offsets */
 #define REG_GEN3_IRQSTR                0x04
 #define REG_GEN3_IRQMSK                0x08
 #define REG_GEN3_THCODE2       0x54
 #define REG_GEN3_THCODE3       0x58
 
+/* IRQ{STR,MSK,EN} bits */
+#define IRQ_TEMP1              BIT(0)
+#define IRQ_TEMP2              BIT(1)
+#define IRQ_TEMP3              BIT(2)
+#define IRQ_TEMPD1             BIT(3)
+#define IRQ_TEMPD2             BIT(4)
+#define IRQ_TEMPD3             BIT(5)
+
 /* CTSR bits */
 #define CTSR_PONM      BIT(8)
 #define CTSR_AOUT      BIT(7)
@@ -72,11 +82,15 @@ struct rcar_gen3_thermal_tsc {
        void __iomem *base;
        struct thermal_zone_device *zone;
        struct equation_coefs coef;
-       struct mutex lock;
+       int low;
+       int high;
 };
 
 struct rcar_gen3_thermal_priv {
        struct rcar_gen3_thermal_tsc *tscs[TSC_MAX_NUM];
+       unsigned int num_tscs;
+       spinlock_t lock; /* Protect interrupts on and off */
+       const struct rcar_gen3_thermal_data *data;
 };
 
 struct rcar_gen3_thermal_data {
@@ -114,6 +128,7 @@ static inline void rcar_gen3_thermal_write(struct rcar_gen3_thermal_tsc *tsc,
 
 #define FIXPT_SHIFT 7
 #define FIXPT_INT(_x) ((_x) << FIXPT_SHIFT)
+#define INT_FIXPT(_x) ((_x) >> FIXPT_SHIFT)
 #define FIXPT_DIV(_a, _b) DIV_ROUND_CLOSEST(((_a) << FIXPT_SHIFT), (_b))
 #define FIXPT_TO_MCELSIUS(_x) ((_x) * 1000 >> FIXPT_SHIFT)
 
@@ -163,16 +178,12 @@ static int rcar_gen3_thermal_get_temp(void *devdata, int *temp)
        u32 reg;
 
        /* Read register and convert to mili Celsius */
-       mutex_lock(&tsc->lock);
-
        reg = rcar_gen3_thermal_read(tsc, REG_GEN3_TEMP) & CTEMP_MASK;
 
        val1 = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b1, tsc->coef.a1);
        val2 = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b2, tsc->coef.a2);
        mcelsius = FIXPT_TO_MCELSIUS((val1 + val2) / 2);
 
-       mutex_unlock(&tsc->lock);
-
        /* Make sure we are inside specifications */
        if ((mcelsius < MCELSIUS(-40)) || (mcelsius > MCELSIUS(125)))
                return -EIO;
@@ -183,10 +194,90 @@ static int rcar_gen3_thermal_get_temp(void *devdata, int *temp)
        return 0;
 }
 
+static int rcar_gen3_thermal_mcelsius_to_temp(struct rcar_gen3_thermal_tsc *tsc,
+                                             int mcelsius)
+{
+       int celsius, val1, val2;
+
+       celsius = DIV_ROUND_CLOSEST(mcelsius, 1000);
+       val1 = celsius * tsc->coef.a1 + tsc->coef.b1;
+       val2 = celsius * tsc->coef.a2 + tsc->coef.b2;
+
+       return INT_FIXPT((val1 + val2) / 2);
+}
+
+static int rcar_gen3_thermal_set_trips(void *devdata, int low, int high)
+{
+       struct rcar_gen3_thermal_tsc *tsc = devdata;
+
+       low = clamp_val(low, -40000, 125000);
+       high = clamp_val(high, -40000, 125000);
+
+       rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1,
+                               rcar_gen3_thermal_mcelsius_to_temp(tsc, low));
+
+       rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2,
+                               rcar_gen3_thermal_mcelsius_to_temp(tsc, high));
+
+       tsc->low = low;
+       tsc->high = high;
+
+       return 0;
+}
+
 static struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = {
        .get_temp       = rcar_gen3_thermal_get_temp,
+       .set_trips      = rcar_gen3_thermal_set_trips,
 };
 
+static void rcar_thermal_irq_set(struct rcar_gen3_thermal_priv *priv, bool on)
+{
+       unsigned int i;
+       u32 val = on ? IRQ_TEMPD1 | IRQ_TEMP2 : 0;
+
+       for (i = 0; i < priv->num_tscs; i++)
+               rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQMSK, val);
+}
+
+static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data)
+{
+       struct rcar_gen3_thermal_priv *priv = data;
+       u32 status;
+       int i, ret = IRQ_HANDLED;
+
+       spin_lock(&priv->lock);
+       for (i = 0; i < priv->num_tscs; i++) {
+               status = rcar_gen3_thermal_read(priv->tscs[i], REG_GEN3_IRQSTR);
+               rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQSTR, 0);
+               if (status)
+                       ret = IRQ_WAKE_THREAD;
+       }
+
+       if (ret == IRQ_WAKE_THREAD)
+               rcar_thermal_irq_set(priv, false);
+
+       spin_unlock(&priv->lock);
+
+       return ret;
+}
+
+static irqreturn_t rcar_gen3_thermal_irq_thread(int irq, void *data)
+{
+       struct rcar_gen3_thermal_priv *priv = data;
+       unsigned long flags;
+       int i;
+
+       for (i = 0; i < priv->num_tscs; i++)
+               thermal_zone_device_update(priv->tscs[i]->zone,
+                                          THERMAL_EVENT_UNSPECIFIED);
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rcar_thermal_irq_set(priv, true);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
 static void r8a7795_thermal_init(struct rcar_gen3_thermal_tsc *tsc)
 {
        rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR,  CTSR_THBGR);
@@ -195,7 +286,11 @@ static void r8a7795_thermal_init(struct rcar_gen3_thermal_tsc *tsc)
        usleep_range(1000, 2000);
 
        rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_PONM);
+
        rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F);
+       rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
+       rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2);
+
        rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR,
                                CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN);
 
@@ -219,9 +314,14 @@ static void r8a7796_thermal_init(struct rcar_gen3_thermal_tsc *tsc)
        usleep_range(1000, 2000);
 
        rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F);
+       rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
+       rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2);
+
        reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR);
        reg_val |= THCTR_THSST;
        rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val);
+
+       usleep_range(1000, 2000);
 }
 
 static const struct rcar_gen3_thermal_data r8a7795_data = {
@@ -255,9 +355,8 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
        struct device *dev = &pdev->dev;
        struct resource *res;
        struct thermal_zone_device *zone;
-       int ret, i;
-       const struct rcar_gen3_thermal_data *match_data =
-               of_device_get_match_data(dev);
+       int ret, irq, i;
+       char *irqname;
 
        /* default values if FUSEs are missing */
        /* TODO: Read values from hardware on supported platforms */
@@ -272,24 +371,50 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
        if (!priv)
                return -ENOMEM;
 
+       priv->data = of_device_get_match_data(dev);
+
+       spin_lock_init(&priv->lock);
+
        platform_set_drvdata(pdev, priv);
 
+       /*
+        * Request 2 (of the 3 possible) IRQs, the driver only needs to
+        * to trigger on the low and high trip points of the current
+        * temp window at this point.
+        */
+       for (i = 0; i < 2; i++) {
+               irq = platform_get_irq(pdev, i);
+               if (irq < 0)
+                       return irq;
+
+               irqname = devm_kasprintf(dev, GFP_KERNEL, "%s:ch%d",
+                                        dev_name(dev), i);
+               if (!irqname)
+                       return -ENOMEM;
+
+               ret = devm_request_threaded_irq(dev, irq, rcar_gen3_thermal_irq,
+                                               rcar_gen3_thermal_irq_thread,
+                                               IRQF_SHARED, irqname, priv);
+               if (ret)
+                       return ret;
+       }
+
        pm_runtime_enable(dev);
        pm_runtime_get_sync(dev);
 
        for (i = 0; i < TSC_MAX_NUM; i++) {
                struct rcar_gen3_thermal_tsc *tsc;
 
+               res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+               if (!res)
+                       break;
+
                tsc = devm_kzalloc(dev, sizeof(*tsc), GFP_KERNEL);
                if (!tsc) {
                        ret = -ENOMEM;
                        goto error_unregister;
                }
 
-               res = platform_get_resource(pdev, IORESOURCE_MEM, i);
-               if (!res)
-                       break;
-
                tsc->base = devm_ioremap_resource(dev, res);
                if (IS_ERR(tsc->base)) {
                        ret = PTR_ERR(tsc->base);
@@ -297,9 +422,8 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
                }
 
                priv->tscs[i] = tsc;
-               mutex_init(&tsc->lock);
 
-               match_data->thermal_init(tsc);
+               priv->data->thermal_init(tsc);
                rcar_gen3_thermal_calc_coefs(&tsc->coef, ptat, thcode[i]);
 
                zone = devm_thermal_zone_of_sensor_register(dev, i, tsc,
@@ -310,8 +434,23 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
                        goto error_unregister;
                }
                tsc->zone = zone;
+
+               ret = of_thermal_get_ntrips(tsc->zone);
+               if (ret < 0)
+                       goto error_unregister;
+
+               dev_info(dev, "TSC%d: Loaded %d trip points\n", i, ret);
        }
 
+       priv->num_tscs = i;
+
+       if (!priv->num_tscs) {
+               ret = -ENODEV;
+               goto error_unregister;
+       }
+
+       rcar_thermal_irq_set(priv, true);
+
        return 0;
 
 error_unregister:
@@ -320,9 +459,39 @@ error_unregister:
        return ret;
 }
 
+static int __maybe_unused rcar_gen3_thermal_suspend(struct device *dev)
+{
+       struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
+
+       rcar_thermal_irq_set(priv, false);
+
+       return 0;
+}
+
+static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
+{
+       struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
+       unsigned int i;
+
+       for (i = 0; i < priv->num_tscs; i++) {
+               struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
+
+               priv->data->thermal_init(tsc);
+               rcar_gen3_thermal_set_trips(tsc, tsc->low, tsc->high);
+       }
+
+       rcar_thermal_irq_set(priv, true);
+
+       return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, rcar_gen3_thermal_suspend,
+                        rcar_gen3_thermal_resume);
+
 static struct platform_driver rcar_gen3_thermal_driver = {
        .driver = {
                .name   = "rcar_gen3_thermal",
+               .pm = &rcar_gen3_thermal_pm_ops,
                .of_match_table = rcar_gen3_thermal_dt_ids,
        },
        .probe          = rcar_gen3_thermal_probe,