memory: tegra-mc: Add interconnect framework
authorDmitry Osipenko <digetx@gmail.com>
Wed, 4 Nov 2020 16:49:08 +0000 (19:49 +0300)
committerKrzysztof Kozlowski <krzk@kernel.org>
Thu, 26 Nov 2020 17:50:35 +0000 (18:50 +0100)
Add common SoC-agnostic ICC framework which turns Tegra Memory Controller
into a memory interconnection provider. This allows us to use interconnect
API for tuning of memory configurations.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Tested-by: Peter Geis <pgwipeout@gmail.com>
Tested-by: Nicolas Chauvet <kwizart@gmail.com>
Link: https://lore.kernel.org/r/20201104164923.21238-33-digetx@gmail.com
Signed-off-by: Krzysztof Kozlowski <krzk@kernel.org>
drivers/memory/tegra/Kconfig
drivers/memory/tegra/mc.c
drivers/memory/tegra/mc.h
include/soc/tegra/mc.h

index 9f0a96b..b38e525 100644 (file)
@@ -3,6 +3,7 @@ config TEGRA_MC
        bool "NVIDIA Tegra Memory Controller support"
        default y
        depends on ARCH_TEGRA
+       select INTERCONNECT
        help
          This driver supports the Memory Controller (MC) hardware found on
          NVIDIA Tegra SoCs.
index 998f914..a7e6a8e 100644 (file)
@@ -639,6 +639,101 @@ static __maybe_unused irqreturn_t tegra20_mc_irq(int irq, void *data)
        return IRQ_HANDLED;
 }
 
+/*
+ * Memory Controller (MC) has few Memory Clients that are issuing memory
+ * bandwidth allocation requests to the MC interconnect provider. The MC
+ * provider aggregates the requests and then sends the aggregated request
+ * up to the External Memory Controller (EMC) interconnect provider which
+ * re-configures hardware interface to External Memory (EMEM) in accordance
+ * to the required bandwidth. Each MC interconnect node represents an
+ * individual Memory Client.
+ *
+ * Memory interconnect topology:
+ *
+ *               +----+
+ * +--------+    |    |
+ * | TEXSRD +--->+    |
+ * +--------+    |    |
+ *               |    |    +-----+    +------+
+ *    ...        | MC +--->+ EMC +--->+ EMEM |
+ *               |    |    +-----+    +------+
+ * +--------+    |    |
+ * | DISP.. +--->+    |
+ * +--------+    |    |
+ *               +----+
+ */
+static int tegra_mc_interconnect_setup(struct tegra_mc *mc)
+{
+       struct icc_node *node;
+       unsigned int i;
+       int err;
+
+       /* older device-trees don't have interconnect properties */
+       if (!device_property_present(mc->dev, "#interconnect-cells") ||
+           !mc->soc->icc_ops)
+               return 0;
+
+       mc->provider.dev = mc->dev;
+       mc->provider.data = &mc->provider;
+       mc->provider.set = mc->soc->icc_ops->set;
+       mc->provider.aggregate = mc->soc->icc_ops->aggregate;
+       mc->provider.xlate_extended = mc->soc->icc_ops->xlate_extended;
+
+       err = icc_provider_add(&mc->provider);
+       if (err)
+               return err;
+
+       /* create Memory Controller node */
+       node = icc_node_create(TEGRA_ICC_MC);
+       if (IS_ERR(node)) {
+               err = PTR_ERR(node);
+               goto del_provider;
+       }
+
+       node->name = "Memory Controller";
+       icc_node_add(node, &mc->provider);
+
+       /* link Memory Controller to External Memory Controller */
+       err = icc_link_create(node, TEGRA_ICC_EMC);
+       if (err)
+               goto remove_nodes;
+
+       for (i = 0; i < mc->soc->num_clients; i++) {
+               /* create MC client node */
+               node = icc_node_create(mc->soc->clients[i].id);
+               if (IS_ERR(node)) {
+                       err = PTR_ERR(node);
+                       goto remove_nodes;
+               }
+
+               node->name = mc->soc->clients[i].name;
+               icc_node_add(node, &mc->provider);
+
+               /* link Memory Client to Memory Controller */
+               err = icc_link_create(node, TEGRA_ICC_MC);
+               if (err)
+                       goto remove_nodes;
+       }
+
+       /*
+        * MC driver is registered too early, so early that generic driver
+        * syncing doesn't work for the MC. But it doesn't really matter
+        * since syncing works for the EMC drivers, hence we can sync the
+        * MC driver by ourselves and then EMC will complete syncing of
+        * the whole ICC state.
+        */
+       icc_sync_state(mc->dev);
+
+       return 0;
+
+remove_nodes:
+       icc_nodes_remove(&mc->provider);
+del_provider:
+       icc_provider_del(&mc->provider);
+
+       return err;
+}
+
 static int tegra_mc_probe(struct platform_device *pdev)
 {
        struct resource *res;
@@ -727,6 +822,11 @@ static int tegra_mc_probe(struct platform_device *pdev)
                dev_err(&pdev->dev, "failed to register reset controller: %d\n",
                        err);
 
+       err = tegra_mc_interconnect_setup(mc);
+       if (err < 0)
+               dev_err(&pdev->dev, "failed to initialize interconnect: %d\n",
+                       err);
+
        if (IS_ENABLED(CONFIG_TEGRA_IOMMU_SMMU) && mc->soc->smmu) {
                mc->smmu = tegra_smmu_probe(&pdev->dev, mc->soc->smmu, mc);
                if (IS_ERR(mc->smmu)) {
index afa3ba4..33e40d6 100644 (file)
 
 #define MC_TIMING_UPDATE                               BIT(0)
 
+static inline u32 tegra_mc_scale_percents(u64 val, unsigned int percents)
+{
+       val = val * percents;
+       do_div(val, 100);
+
+       return min_t(u64, val, U32_MAX);
+}
+
+static inline struct tegra_mc *
+icc_provider_to_tegra_mc(struct icc_provider *provider)
+{
+       return container_of(provider, struct tegra_mc, provider);
+}
+
 static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset)
 {
        return readl_relaxed(mc->regs + offset);
@@ -115,4 +129,12 @@ extern const struct tegra_mc_soc tegra132_mc_soc;
 extern const struct tegra_mc_soc tegra210_mc_soc;
 #endif
 
+/*
+ * These IDs are for internal use of Tegra ICC drivers. The ID numbers are
+ * chosen such that they don't conflict with the device-tree ICC node IDs.
+ */
+#define TEGRA_ICC_MC           1000
+#define TEGRA_ICC_EMC          1001
+#define TEGRA_ICC_EMEM         1002
+
 #endif /* MEMORY_TEGRA_MC_H */
index d9395af..4387621 100644 (file)
@@ -6,7 +6,9 @@
 #ifndef __SOC_TEGRA_MC_H__
 #define __SOC_TEGRA_MC_H__
 
+#include <linux/bits.h>
 #include <linux/err.h>
+#include <linux/interconnect-provider.h>
 #include <linux/reset-controller.h>
 #include <linux/types.h>
 
@@ -141,6 +143,17 @@ struct tegra_mc_reset_ops {
                            const struct tegra_mc_reset *rst);
 };
 
+#define TEGRA_MC_ICC_TAG_DEFAULT                               0
+#define TEGRA_MC_ICC_TAG_ISO                                   BIT(0)
+
+struct tegra_mc_icc_ops {
+       int (*set)(struct icc_node *src, struct icc_node *dst);
+       int (*aggregate)(struct icc_node *node, u32 tag, u32 avg_bw,
+                        u32 peak_bw, u32 *agg_avg, u32 *agg_peak);
+       struct icc_node_data *(*xlate_extended)(struct of_phandle_args *spec,
+                                               void *data);
+};
+
 struct tegra_mc_soc {
        const struct tegra_mc_client *clients;
        unsigned int num_clients;
@@ -160,6 +173,8 @@ struct tegra_mc_soc {
        const struct tegra_mc_reset_ops *reset_ops;
        const struct tegra_mc_reset *resets;
        unsigned int num_resets;
+
+       const struct tegra_mc_icc_ops *icc_ops;
 };
 
 struct tegra_mc {
@@ -178,6 +193,8 @@ struct tegra_mc {
 
        struct reset_controller_dev reset;
 
+       struct icc_provider provider;
+
        spinlock_t lock;
 };