]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
memory: tegra: Implement EMEM regs and ICC ops for Tegra114
authorSvyatoslav Ryhel <clamor95@gmail.com>
Mon, 27 Apr 2026 07:03:07 +0000 (10:03 +0300)
committerKrzysztof Kozlowski <krzk@kernel.org>
Mon, 4 May 2026 18:47:17 +0000 (20:47 +0200)
Prepare Internal Memory Controller for introduction of External Memory
Controller.

Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Mikko Perttunen <mperttunen@nvidia.com>
Link: https://patch.msgid.link/20260427070312.81679-3-clamor95@gmail.com
Signed-off-by: Krzysztof Kozlowski <krzk@kernel.org>
drivers/memory/tegra/tegra114.c

index 02dd4e26288a1d6ed45c94d5ae805508f800ecbc..2cb153091228a73d8caf708be8481ab68a9bcc61 100644 (file)
@@ -3,6 +3,7 @@
  * Copyright (C) 2014-2026 NVIDIA CORPORATION.  All rights reserved.
  */
 
+#include <linux/device.h>
 #include <linux/of.h>
 #include <linux/mm.h>
 
@@ -1101,6 +1102,195 @@ static const struct tegra_mc_reset tegra114_mc_resets[] = {
        TEGRA114_MC_RESET(VI,       0x200, 0x204, 17),
 };
 
+static void tegra114_mc_tune_client_latency(struct tegra_mc *mc,
+                                           const struct tegra_mc_client *client,
+                                           unsigned int bandwidth_mbytes_sec)
+{
+       u32 arb_tolerance_compensation_nsec, arb_tolerance_compensation_div;
+       unsigned int fifo_size = client->fifo_size;
+       u32 arb_nsec, la_ticks, value;
+
+       /* see 20.3.1.1 Client Configuration in Tegra4 TRM v01p */
+       if (bandwidth_mbytes_sec)
+               arb_nsec = fifo_size * NSEC_PER_USEC / bandwidth_mbytes_sec;
+       else
+               arb_nsec = U32_MAX;
+
+       /*
+        * Latency allowness should be set with consideration for the module's
+        * latency tolerance and internal buffering capabilities.
+        *
+        * Display memory clients use isochronous transfers and have very low
+        * tolerance to a belated transfers. Hence we need to compensate the
+        * memory arbitration imperfection for them in order to prevent FIFO
+        * underflow condition when memory bus is busy.
+        *
+        * VI clients also need a stronger compensation.
+        */
+       switch (client->swgroup) {
+       case TEGRA_SWGROUP_MPCORE:
+       case TEGRA_SWGROUP_PTC:
+               /*
+                * We always want lower latency for these clients, hence
+                * don't touch them.
+                */
+               return;
+
+       case TEGRA_SWGROUP_DC:
+       case TEGRA_SWGROUP_DCB:
+               arb_tolerance_compensation_nsec = 1050;
+               arb_tolerance_compensation_div = 2;
+               break;
+
+       case TEGRA_SWGROUP_VI:
+               arb_tolerance_compensation_nsec = 1050;
+               arb_tolerance_compensation_div = 1;
+               break;
+
+       default:
+               arb_tolerance_compensation_nsec = 150;
+               arb_tolerance_compensation_div = 1;
+               break;
+       }
+
+       if (arb_nsec > arb_tolerance_compensation_nsec)
+               arb_nsec -= arb_tolerance_compensation_nsec;
+       else
+               arb_nsec = 0;
+
+       arb_nsec /= arb_tolerance_compensation_div;
+
+       /*
+        * Latency allowance is a number of ticks a request from a particular
+        * client may wait in the EMEM arbiter before it becomes a high-priority
+        * request.
+        */
+       la_ticks = arb_nsec / mc->tick;
+       la_ticks = min(la_ticks, client->regs.la.mask);
+
+       value = mc_readl(mc, client->regs.la.reg);
+       value &= ~(client->regs.la.mask << client->regs.la.shift);
+       value |= la_ticks << client->regs.la.shift;
+       mc_writel(mc, value, client->regs.la.reg);
+}
+
+static int tegra114_mc_icc_set(struct icc_node *src, struct icc_node *dst)
+{
+       struct tegra_mc *mc = icc_provider_to_tegra_mc(src->provider);
+       const struct tegra_mc_client *client = &mc->soc->clients[src->id];
+       u64 peak_bandwidth = icc_units_to_bps(src->peak_bw);
+
+       /*
+        * Skip pre-initialization that is done by icc_node_add(), which sets
+        * bandwidth to maximum for all clients before drivers are loaded.
+        *
+        * This doesn't make sense for us because we don't have drivers for all
+        * clients and it's okay to keep configuration left from bootloader
+        * during boot, at least for today.
+        */
+       if (src == dst)
+               return 0;
+
+       /* convert bytes/sec to megabytes/sec */
+       do_div(peak_bandwidth, 1000000);
+
+       tegra114_mc_tune_client_latency(mc, client, peak_bandwidth);
+
+       return 0;
+}
+
+static int tegra114_mc_icc_aggreate(struct icc_node *node, u32 tag, u32 avg_bw,
+                                   u32 peak_bw, u32 *agg_avg, u32 *agg_peak)
+{
+       /*
+        * ISO clients need to reserve extra bandwidth up-front because
+        * there could be high bandwidth pressure during initial filling
+        * of the client's FIFO buffers.  Secondly, we need to take into
+        * account impurities of the memory subsystem.
+        */
+       if (tag & TEGRA_MC_ICC_TAG_ISO)
+               peak_bw = tegra_mc_scale_percents(peak_bw, 400);
+
+       *agg_avg += avg_bw;
+       *agg_peak = max(*agg_peak, peak_bw);
+
+       return 0;
+}
+
+static struct icc_node_data *
+tegra114_mc_of_icc_xlate_extended(const struct of_phandle_args *spec, void *data)
+{
+       struct tegra_mc *mc = icc_provider_to_tegra_mc(data);
+       const struct tegra_mc_client *client;
+       unsigned int i, idx = spec->args[0];
+       struct icc_node_data *ndata;
+       struct icc_node *node;
+
+       list_for_each_entry(node, &mc->provider.nodes, node_list) {
+               if (node->id != idx)
+                       continue;
+
+               ndata = kzalloc_obj(*ndata);
+               if (!ndata)
+                       return ERR_PTR(-ENOMEM);
+
+               client = &mc->soc->clients[idx];
+               ndata->node = node;
+
+               switch (client->swgroup) {
+               case TEGRA_SWGROUP_DC:
+               case TEGRA_SWGROUP_DCB:
+               case TEGRA_SWGROUP_PTC:
+               case TEGRA_SWGROUP_VI:
+                       /* these clients are isochronous by default */
+                       ndata->tag = TEGRA_MC_ICC_TAG_ISO;
+                       break;
+
+               default:
+                       ndata->tag = TEGRA_MC_ICC_TAG_DEFAULT;
+                       break;
+               }
+
+               return ndata;
+       }
+
+       for (i = 0; i < mc->soc->num_clients; i++) {
+               if (mc->soc->clients[i].id == idx)
+                       return ERR_PTR(-EPROBE_DEFER);
+       }
+
+       dev_err(mc->dev, "invalid ICC client ID %u\n", idx);
+
+       return ERR_PTR(-EINVAL);
+}
+
+static const struct tegra_mc_icc_ops tegra114_mc_icc_ops = {
+       .xlate_extended = tegra114_mc_of_icc_xlate_extended,
+       .aggregate = tegra114_mc_icc_aggreate,
+       .set = tegra114_mc_icc_set,
+};
+
+static const unsigned long tegra114_mc_emem_regs[] = {
+       MC_EMEM_ARB_CFG,
+       MC_EMEM_ARB_OUTSTANDING_REQ,
+       MC_EMEM_ARB_TIMING_RCD,
+       MC_EMEM_ARB_TIMING_RP,
+       MC_EMEM_ARB_TIMING_RC,
+       MC_EMEM_ARB_TIMING_RAS,
+       MC_EMEM_ARB_TIMING_FAW,
+       MC_EMEM_ARB_TIMING_RRD,
+       MC_EMEM_ARB_TIMING_RAP2PRE,
+       MC_EMEM_ARB_TIMING_WAP2PRE,
+       MC_EMEM_ARB_TIMING_R2R,
+       MC_EMEM_ARB_TIMING_W2W,
+       MC_EMEM_ARB_TIMING_R2W,
+       MC_EMEM_ARB_TIMING_W2R,
+       MC_EMEM_ARB_DA_TURNS,
+       MC_EMEM_ARB_DA_COVERS,
+       MC_EMEM_ARB_MISC0,
+       MC_EMEM_ARB_RING1_THROTTLE,
+};
+
 static const struct tegra_mc_intmask tegra114_mc_intmasks[] = {
        {
                .reg = MC_INTMASK,
@@ -1116,11 +1306,14 @@ const struct tegra_mc_soc tegra114_mc_soc = {
        .atom_size = 32,
        .client_id_mask = 0x7f,
        .smmu = &tegra114_smmu_soc,
+       .emem_regs = tegra114_mc_emem_regs,
+       .num_emem_regs = ARRAY_SIZE(tegra114_mc_emem_regs),
        .intmasks = tegra114_mc_intmasks,
        .num_intmasks = ARRAY_SIZE(tegra114_mc_intmasks),
        .reset_ops = &tegra_mc_reset_ops_common,
        .resets = tegra114_mc_resets,
        .num_resets = ARRAY_SIZE(tegra114_mc_resets),
+       .icc_ops = &tegra114_mc_icc_ops,
        .ops = &tegra30_mc_ops,
        .regs = &tegra20_mc_regs,
        .handle_irq = tegra30_mc_irq_handlers,