The Tegra memory controller implements a flush feature to flush pending
accesses and prevent further accesses from occurring. This feature is
used when powering down IP blocks to ensure the IP block is in a good
state. The flushes are organised by software groups and IP blocks are
assigned in hardware to the different software groups. Add helper
functions for requesting a handle to an MC flush for a given
software group and enabling/disabling the MC flush itself.

This is based upon a change by Vince Hsu <[email protected]>.

Signed-off-by: Jon Hunter <[email protected]>
---
 drivers/memory/tegra/mc.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/memory/tegra/mc.h |   2 +
 include/soc/tegra/mc.h    |  34 ++++++++++++++
 3 files changed, 146 insertions(+)

diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c
index c71ede67e6c8..fb8da3d4caf4 100644
--- a/drivers/memory/tegra/mc.c
+++ b/drivers/memory/tegra/mc.c
@@ -7,6 +7,7 @@
  */
 
 #include <linux/clk.h>
+#include <linux/delay.h>
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -71,6 +72,107 @@ static const struct of_device_id tegra_mc_of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, tegra_mc_of_match);
 
+const struct tegra_mc_flush *tegra_mc_flush_get(struct tegra_mc *mc,
+                                               unsigned int swgroup)
+{
+       const struct tegra_mc_flush *flush = NULL;
+       int i;
+
+       mutex_lock(&mc->lock);
+
+       for (i = 0; i < mc->soc->num_flushes; i++) {
+               if (mc->soc->flushes[i].swgroup == swgroup) {
+                       if (mc->flush_reserved[i] == false) {
+                               mc->flush_reserved[i] = true;
+                               flush = &mc->soc->flushes[i];
+                       }
+                       break;
+               }
+       }
+
+       mutex_unlock(&mc->lock);
+
+       return flush;
+}
+EXPORT_SYMBOL(tegra_mc_flush_get);
+
+static bool tegra_mc_flush_done(struct tegra_mc *mc,
+                               const struct tegra_mc_flush *flush)
+{
+       unsigned long timeout = jiffies + msecs_to_jiffies(100);
+       int i;
+       u32 val;
+
+       while (time_before(jiffies, timeout)) {
+               val = mc_readl(mc, flush->status);
+
+               /*
+                * If the flush bit is still set it
+                * is not done and so wait then retry.
+                */
+               if (val & BIT(flush->bit))
+                       goto retry;
+
+               /*
+                * Depending on the tegra SoC, it may be necessary to read
+                * the status register multiple times to ensure the value
+                * read is correct. Some tegra devices have a HW issue where
+                * reading the status register shortly after writing the
+                * control register (on the order of 5 cycles) may return
+                * an incorrect value.
+                */
+               for (i = 0; i < mc->soc->metastable_flush_reads; i++) {
+                       if (mc_readl(mc, flush->status) != val)
+                               goto retry;
+               }
+
+               /*
+                * The flush is complete and so return.
+                */
+               return 0;
+retry:
+               udelay(10);
+       }
+
+       return -ETIMEDOUT;
+}
+
+int tegra_mc_flush(struct tegra_mc *mc, const struct tegra_mc_flush *flush,
+                  bool enable)
+{
+       int ret = 0;
+       u32 val;
+
+       if (!mc || !flush)
+               return -EINVAL;
+
+       mutex_lock(&mc->lock);
+
+       val = mc_readl(mc, flush->ctrl);
+
+       if (enable)
+               val |= BIT(flush->bit);
+       else
+               val &= ~BIT(flush->bit);
+
+       mc_writel(mc, val, flush->ctrl);
+       mc_readl(mc, flush->ctrl);
+
+       /*
+        * If activating the flush, poll the
+        * status register until the flush is done.
+        */
+       if (enable)
+               ret = tegra_mc_flush_done(mc, flush);
+
+       mutex_unlock(&mc->lock);
+
+       dev_dbg(mc->dev, "%s bit %d\n", __func__, flush->bit);
+
+       return ret;
+}
+EXPORT_SYMBOL(tegra_mc_flush);
+
 static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc)
 {
        unsigned long long tick;
@@ -359,6 +461,12 @@ static int tegra_mc_probe(struct platform_device *pdev)
        mc->soc = match->data;
        mc->dev = &pdev->dev;
 
+       mc->flush_reserved = devm_kcalloc(&pdev->dev, mc->soc->num_flushes,
+                                         sizeof(mc->flush_reserved),
+                                         GFP_KERNEL);
+       if (!mc->flush_reserved)
+               return -ENOMEM;
+
        /* length of MC tick in nanoseconds */
        mc->tick = 30;
 
@@ -410,6 +518,8 @@ static int tegra_mc_probe(struct platform_device *pdev)
                return err;
        }
 
+       mutex_init(&mc->lock);
+
        value = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR |
                MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE |
                MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM;
diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h
index b7361b0a6696..0f59d49b735b 100644
--- a/drivers/memory/tegra/mc.h
+++ b/drivers/memory/tegra/mc.h
@@ -14,6 +14,8 @@
 
 #include <soc/tegra/mc.h>
 
+#define MC_FLUSH_METASTABLE_READS      5
+
 static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset)
 {
        return readl(mc->regs + offset);
diff --git a/include/soc/tegra/mc.h b/include/soc/tegra/mc.h
index 1ab2813273cd..b634c6df79eb 100644
--- a/include/soc/tegra/mc.h
+++ b/include/soc/tegra/mc.h
@@ -45,6 +45,13 @@ struct tegra_mc_client {
        struct tegra_mc_la la;
 };
 
+struct tegra_mc_flush {
+       unsigned int swgroup;
+       unsigned int ctrl;
+       unsigned int status;
+       unsigned int bit;
+};
+
 struct tegra_smmu_swgroup {
        const char *name;
        unsigned int swgroup;
@@ -96,6 +103,10 @@ struct tegra_mc_soc {
        const struct tegra_mc_client *clients;
        unsigned int num_clients;
 
+       const struct tegra_mc_flush *flushes;
+       unsigned int num_flushes;
+       unsigned int metastable_flush_reads;
+
        const unsigned long *emem_regs;
        unsigned int num_emem_regs;
 
@@ -117,9 +128,32 @@ struct tegra_mc {
 
        struct tegra_mc_timing *timings;
        unsigned int num_timings;
+
+       bool *flush_reserved;
+
+       struct mutex lock;
 };
 
 void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long 
rate);
 unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc);
 
+#ifdef CONFIG_TEGRA_MC
+const struct tegra_mc_flush *tegra_mc_flush_get(struct tegra_mc *mc,
+                                               unsigned int swgroup);
+int tegra_mc_flush(struct tegra_mc *mc, const struct tegra_mc_flush *s,
+                  bool enable);
+#else
+const struct tegra_mc_flush *tegra_mc_flush_get(struct tegra_mc *mc,
+                                               unsigned int swgroup)
+{
+       return NULL;
+}
+
+int tegra_mc_flush(struct tegra_mc *mc, const struct tegra_mc_flush *s,
+                  bool enable)
+{
+       return -ENOTSUPP;
+}
+#endif
+
 #endif /* __SOC_TEGRA_MC_H__ */
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to