memory: tegra-mc: Add interconnect framework

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>
This commit is contained in:
Dmitry Osipenko 2020-11-04 19:49:08 +03:00 committed by Krzysztof Kozlowski
parent d5ecac0afa
commit 06f079816d
4 changed files with 140 additions and 0 deletions

View 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.

View 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)) {

View File

@ -78,6 +78,20 @@
#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 */

View 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;
};