linux/drivers/memory/tegra/tegra20.c
Dmitry Osipenko d5ef16ba5f memory: tegra20: Support interconnect framework
Now Internal and External Memory Controllers are memory interconnection
providers. This allows us to use interconnect API for tuning of memory
configuration. EMC driver now supports OPPs and DVFS.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Link: https://lore.kernel.org/r/20201104164923.21238-36-digetx@gmail.com
Signed-off-by: Krzysztof Kozlowski <krzk@kernel.org>
2020-11-26 18:50:36 +01:00

371 lines
7.6 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <dt-bindings/memory/tegra20-mc.h>
#include "mc.h"
static const struct tegra_mc_client tegra20_mc_clients[] = {
{
.id = 0x00,
.name = "display0a",
}, {
.id = 0x01,
.name = "display0ab",
}, {
.id = 0x02,
.name = "display0b",
}, {
.id = 0x03,
.name = "display0bb",
}, {
.id = 0x04,
.name = "display0c",
}, {
.id = 0x05,
.name = "display0cb",
}, {
.id = 0x06,
.name = "display1b",
}, {
.id = 0x07,
.name = "display1bb",
}, {
.id = 0x08,
.name = "eppup",
}, {
.id = 0x09,
.name = "g2pr",
}, {
.id = 0x0a,
.name = "g2sr",
}, {
.id = 0x0b,
.name = "mpeunifbr",
}, {
.id = 0x0c,
.name = "viruv",
}, {
.id = 0x0d,
.name = "avpcarm7r",
}, {
.id = 0x0e,
.name = "displayhc",
}, {
.id = 0x0f,
.name = "displayhcb",
}, {
.id = 0x10,
.name = "fdcdrd",
}, {
.id = 0x11,
.name = "g2dr",
}, {
.id = 0x12,
.name = "host1xdmar",
}, {
.id = 0x13,
.name = "host1xr",
}, {
.id = 0x14,
.name = "idxsrd",
}, {
.id = 0x15,
.name = "mpcorer",
}, {
.id = 0x16,
.name = "mpe_ipred",
}, {
.id = 0x17,
.name = "mpeamemrd",
}, {
.id = 0x18,
.name = "mpecsrd",
}, {
.id = 0x19,
.name = "ppcsahbdmar",
}, {
.id = 0x1a,
.name = "ppcsahbslvr",
}, {
.id = 0x1b,
.name = "texsrd",
}, {
.id = 0x1c,
.name = "vdebsevr",
}, {
.id = 0x1d,
.name = "vdember",
}, {
.id = 0x1e,
.name = "vdemcer",
}, {
.id = 0x1f,
.name = "vdetper",
}, {
.id = 0x20,
.name = "eppu",
}, {
.id = 0x21,
.name = "eppv",
}, {
.id = 0x22,
.name = "eppy",
}, {
.id = 0x23,
.name = "mpeunifbw",
}, {
.id = 0x24,
.name = "viwsb",
}, {
.id = 0x25,
.name = "viwu",
}, {
.id = 0x26,
.name = "viwv",
}, {
.id = 0x27,
.name = "viwy",
}, {
.id = 0x28,
.name = "g2dw",
}, {
.id = 0x29,
.name = "avpcarm7w",
}, {
.id = 0x2a,
.name = "fdcdwr",
}, {
.id = 0x2b,
.name = "host1xw",
}, {
.id = 0x2c,
.name = "ispw",
}, {
.id = 0x2d,
.name = "mpcorew",
}, {
.id = 0x2e,
.name = "mpecswr",
}, {
.id = 0x2f,
.name = "ppcsahbdmaw",
}, {
.id = 0x30,
.name = "ppcsahbslvw",
}, {
.id = 0x31,
.name = "vdebsevw",
}, {
.id = 0x32,
.name = "vdembew",
}, {
.id = 0x33,
.name = "vdetpmw",
},
};
#define TEGRA20_MC_RESET(_name, _control, _status, _reset, _bit) \
{ \
.name = #_name, \
.id = TEGRA20_MC_RESET_##_name, \
.control = _control, \
.status = _status, \
.reset = _reset, \
.bit = _bit, \
}
static const struct tegra_mc_reset tegra20_mc_resets[] = {
TEGRA20_MC_RESET(AVPC, 0x100, 0x140, 0x104, 0),
TEGRA20_MC_RESET(DC, 0x100, 0x144, 0x104, 1),
TEGRA20_MC_RESET(DCB, 0x100, 0x148, 0x104, 2),
TEGRA20_MC_RESET(EPP, 0x100, 0x14c, 0x104, 3),
TEGRA20_MC_RESET(2D, 0x100, 0x150, 0x104, 4),
TEGRA20_MC_RESET(HC, 0x100, 0x154, 0x104, 5),
TEGRA20_MC_RESET(ISP, 0x100, 0x158, 0x104, 6),
TEGRA20_MC_RESET(MPCORE, 0x100, 0x15c, 0x104, 7),
TEGRA20_MC_RESET(MPEA, 0x100, 0x160, 0x104, 8),
TEGRA20_MC_RESET(MPEB, 0x100, 0x164, 0x104, 9),
TEGRA20_MC_RESET(MPEC, 0x100, 0x168, 0x104, 10),
TEGRA20_MC_RESET(3D, 0x100, 0x16c, 0x104, 11),
TEGRA20_MC_RESET(PPCS, 0x100, 0x170, 0x104, 12),
TEGRA20_MC_RESET(VDE, 0x100, 0x174, 0x104, 13),
TEGRA20_MC_RESET(VI, 0x100, 0x178, 0x104, 14),
};
static int tegra20_mc_hotreset_assert(struct tegra_mc *mc,
const struct tegra_mc_reset *rst)
{
unsigned long flags;
u32 value;
spin_lock_irqsave(&mc->lock, flags);
value = mc_readl(mc, rst->reset);
mc_writel(mc, value & ~BIT(rst->bit), rst->reset);
spin_unlock_irqrestore(&mc->lock, flags);
return 0;
}
static int tegra20_mc_hotreset_deassert(struct tegra_mc *mc,
const struct tegra_mc_reset *rst)
{
unsigned long flags;
u32 value;
spin_lock_irqsave(&mc->lock, flags);
value = mc_readl(mc, rst->reset);
mc_writel(mc, value | BIT(rst->bit), rst->reset);
spin_unlock_irqrestore(&mc->lock, flags);
return 0;
}
static int tegra20_mc_block_dma(struct tegra_mc *mc,
const struct tegra_mc_reset *rst)
{
unsigned long flags;
u32 value;
spin_lock_irqsave(&mc->lock, flags);
value = mc_readl(mc, rst->control) & ~BIT(rst->bit);
mc_writel(mc, value, rst->control);
spin_unlock_irqrestore(&mc->lock, flags);
return 0;
}
static bool tegra20_mc_dma_idling(struct tegra_mc *mc,
const struct tegra_mc_reset *rst)
{
return mc_readl(mc, rst->status) == 0;
}
static int tegra20_mc_reset_status(struct tegra_mc *mc,
const struct tegra_mc_reset *rst)
{
return (mc_readl(mc, rst->reset) & BIT(rst->bit)) == 0;
}
static int tegra20_mc_unblock_dma(struct tegra_mc *mc,
const struct tegra_mc_reset *rst)
{
unsigned long flags;
u32 value;
spin_lock_irqsave(&mc->lock, flags);
value = mc_readl(mc, rst->control) | BIT(rst->bit);
mc_writel(mc, value, rst->control);
spin_unlock_irqrestore(&mc->lock, flags);
return 0;
}
static const struct tegra_mc_reset_ops tegra20_mc_reset_ops = {
.hotreset_assert = tegra20_mc_hotreset_assert,
.hotreset_deassert = tegra20_mc_hotreset_deassert,
.block_dma = tegra20_mc_block_dma,
.dma_idling = tegra20_mc_dma_idling,
.unblock_dma = tegra20_mc_unblock_dma,
.reset_status = tegra20_mc_reset_status,
};
static int tegra20_mc_icc_set(struct icc_node *src, struct icc_node *dst)
{
/*
* It should be possible to tune arbitration knobs here, but the
* default values are known to work well on all devices. Hence
* nothing to do here so far.
*/
return 0;
}
static int tegra20_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, 300);
*agg_avg += avg_bw;
*agg_peak = max(*agg_peak, peak_bw);
return 0;
}
static struct icc_node_data *
tegra20_mc_of_icc_xlate_extended(struct of_phandle_args *spec, void *data)
{
struct tegra_mc *mc = icc_provider_to_tegra_mc(data);
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(sizeof(*ndata), GFP_KERNEL);
if (!ndata)
return ERR_PTR(-ENOMEM);
ndata->node = node;
/* these clients are isochronous by default */
if (strstarts(node->name, "display") ||
strstarts(node->name, "vi"))
ndata->tag = TEGRA_MC_ICC_TAG_ISO;
else
ndata->tag = TEGRA_MC_ICC_TAG_DEFAULT;
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 tegra20_mc_icc_ops = {
.xlate_extended = tegra20_mc_of_icc_xlate_extended,
.aggregate = tegra20_mc_icc_aggreate,
.set = tegra20_mc_icc_set,
};
const struct tegra_mc_soc tegra20_mc_soc = {
.clients = tegra20_mc_clients,
.num_clients = ARRAY_SIZE(tegra20_mc_clients),
.num_address_bits = 32,
.client_id_mask = 0x3f,
.intmask = MC_INT_SECURITY_VIOLATION | MC_INT_INVALID_GART_PAGE |
MC_INT_DECERR_EMEM,
.reset_ops = &tegra20_mc_reset_ops,
.resets = tegra20_mc_resets,
.num_resets = ARRAY_SIZE(tegra20_mc_resets),
.icc_ops = &tegra20_mc_icc_ops,
};