a907047732
The most notable updates this time are for Qualcomm Snapdragon platforms. The Inline-Crypto-Engine gets a new DT binding and driver. A number of drivers now support additional Snapdragon variants, in particular the rsc, scm, geni, bwm, glink and socinfo, while the llcc (edac) and rpm drivers get notable functionality updates. Updates on other platforms include: - Various updates to the Mediatek mutex and mmsys drivers, including support for the Helio X10 SoC - Support for unidirectional mailbox channels in Arm SCMI firmware - Support for per cpu asynchronous notification in OP-TEE firmware - Minor updates for memory controller drivers. - Minor updates for Renesas, TI, Amlogic, Apple, Broadcom, Tegra, Allwinner, Versatile Express, Canaan, Microchip, Mediatek and i.MX SoC drivers, mainly updating the use of MODULE_LICENSE() macros and obsolete DT driver interfaces. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEiK/NIGsWEZVxh/FrYKtH/8kJUicFAmRGmncACgkQYKtH/8kJ Uif6ghAAw1TiPTJzJLLCNx+txOVFB62WDglv3T1CufjfcWp0Eh0RJSCcsCOPV+/7 UHi4+X4nPAcudeOFMFtslCR8ExLRWY4j7t2ZYo/k+VI3jdB8Qkbr6NAQgAuRdLYX WZ1cV6o76B3bhO2HqSVNVZ8/3Z7OAYw4j9VDD/4AbW+l3GyentlQTjabpJNREvSS 5HzT3ZI33o7M8mM4uYmmEXVrg8sCupbRyL9S7jTiFXRLcfqujclhfezJ4UrJJv7b wxGf+e2YNMqKH6PiKYufzN1TYI2D0YQeB1m56Y9FsAKxgAyHh2xWpsHeyVnaw0jc KaKjRN/H3JDlW/VCMAjQOIShCZdAs02xHnEXxY6pKLMM6i8/FkzzNIxNQwXrx5KH zYESXVd6suOI0eCZT8zkKKLHRT5EJRaliUv5Z+Qp2BBe3vJVZD0JqSlZ7lOznplF lviwL6ydAMr2cfTgfMxbRiYQVDzncFkfnR3t55SC6rYjGt6QWjeS0dDbGHf4WVC4 FDbnST4JaBmi+frh55VooX7EpzIv9wa0/taayaChd9qvXnh22uqaqho1sPYKZ6BI OXduHQ3qojJhKKKK1VJKzN5Ef3OHLQLNrvcc1DsKILrrES4w4LX1C9dmyh2CLXLo q5cX6L1iB1Hx5tujalDYBsHBBmbiT/1tNM2S7pAGigiGy4KEc28= =r6jm -----END PGP SIGNATURE----- Merge tag 'soc-drivers-6.4' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc Pull ARM SoC driver updates from Arnd Bergmann: "The most notable updates this time are for Qualcomm Snapdragon platforms. The Inline-Crypto-Engine gets a new DT binding and driver, and a number of drivers now support additional Snapdragon variants, in particular the rsc, scm, geni, bwm, glink and socinfo, while the llcc (edac) and rpm drivers get notable functionality updates. Updates on other platforms include: - Various updates to the Mediatek mutex and mmsys drivers, including support for the Helio X10 SoC - Support for unidirectional mailbox channels in Arm SCMI firmware - Support for per cpu asynchronous notification in OP-TEE firmware - Minor updates for memory controller drivers. - Minor updates for Renesas, TI, Amlogic, Apple, Broadcom, Tegra, Allwinner, Versatile Express, Canaan, Microchip, Mediatek and i.MX SoC drivers, mainly updating the use of MODULE_LICENSE() macros and obsolete DT driver interfaces" * tag 'soc-drivers-6.4' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc: (165 commits) soc: ti: smartreflex: Simplify getting the opam_sr pointer bus: vexpress-config: Add explicit of_platform.h include soc: mediatek: Kconfig: Add MTK_CMDQ dependency to MTK_MMSYS memory: mtk-smi: mt8365: Add SMI Support dt-bindings: memory-controllers: mediatek,smi-larb: add mt8365 dt-bindings: memory-controllers: mediatek,smi-common: add mt8365 memory: tegra: read values from correct device dt-bindings: crypto: Add Qualcomm Inline Crypto Engine soc: qcom: Make the Qualcomm UFS/SDCC ICE a dedicated driver dt-bindings: firmware: document Qualcomm QCM2290 SCM soc: qcom: rpmh-rsc: Support RSC v3 minor versions soc: qcom: smd-rpm: Use GFP_ATOMIC in write path soc/tegra: fuse: Remove nvmem root only access soc/tegra: cbb: tegra194: Use of_address_count() helper soc/tegra: cbb: Remove MODULE_LICENSE in non-modules ARM: tegra: Remove MODULE_LICENSE in non-modules soc/tegra: flowctrl: Use devm_platform_get_and_ioremap_resource() soc: tegra: cbb: Drop empty platform remove function firmware: arm_scmi: Add support for unidirectional mailbox channels dt-bindings: firmware: arm,scmi: Support mailboxes unidirectional channels ...
986 lines
23 KiB
C
986 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/export.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sort.h>
|
|
|
|
#include <soc/tegra/fuse.h>
|
|
|
|
#include "mc.h"
|
|
|
|
static const struct of_device_id tegra_mc_of_match[] = {
|
|
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
|
|
{ .compatible = "nvidia,tegra20-mc-gart", .data = &tegra20_mc_soc },
|
|
#endif
|
|
#ifdef CONFIG_ARCH_TEGRA_3x_SOC
|
|
{ .compatible = "nvidia,tegra30-mc", .data = &tegra30_mc_soc },
|
|
#endif
|
|
#ifdef CONFIG_ARCH_TEGRA_114_SOC
|
|
{ .compatible = "nvidia,tegra114-mc", .data = &tegra114_mc_soc },
|
|
#endif
|
|
#ifdef CONFIG_ARCH_TEGRA_124_SOC
|
|
{ .compatible = "nvidia,tegra124-mc", .data = &tegra124_mc_soc },
|
|
#endif
|
|
#ifdef CONFIG_ARCH_TEGRA_132_SOC
|
|
{ .compatible = "nvidia,tegra132-mc", .data = &tegra132_mc_soc },
|
|
#endif
|
|
#ifdef CONFIG_ARCH_TEGRA_210_SOC
|
|
{ .compatible = "nvidia,tegra210-mc", .data = &tegra210_mc_soc },
|
|
#endif
|
|
#ifdef CONFIG_ARCH_TEGRA_186_SOC
|
|
{ .compatible = "nvidia,tegra186-mc", .data = &tegra186_mc_soc },
|
|
#endif
|
|
#ifdef CONFIG_ARCH_TEGRA_194_SOC
|
|
{ .compatible = "nvidia,tegra194-mc", .data = &tegra194_mc_soc },
|
|
#endif
|
|
#ifdef CONFIG_ARCH_TEGRA_234_SOC
|
|
{ .compatible = "nvidia,tegra234-mc", .data = &tegra234_mc_soc },
|
|
#endif
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tegra_mc_of_match);
|
|
|
|
static void tegra_mc_devm_action_put_device(void *data)
|
|
{
|
|
struct tegra_mc *mc = data;
|
|
|
|
put_device(mc->dev);
|
|
}
|
|
|
|
/**
|
|
* devm_tegra_memory_controller_get() - get Tegra Memory Controller handle
|
|
* @dev: device pointer for the consumer device
|
|
*
|
|
* This function will search for the Memory Controller node in a device-tree
|
|
* and retrieve the Memory Controller handle.
|
|
*
|
|
* Return: ERR_PTR() on error or a valid pointer to a struct tegra_mc.
|
|
*/
|
|
struct tegra_mc *devm_tegra_memory_controller_get(struct device *dev)
|
|
{
|
|
struct platform_device *pdev;
|
|
struct device_node *np;
|
|
struct tegra_mc *mc;
|
|
int err;
|
|
|
|
np = of_parse_phandle(dev->of_node, "nvidia,memory-controller", 0);
|
|
if (!np)
|
|
return ERR_PTR(-ENOENT);
|
|
|
|
pdev = of_find_device_by_node(np);
|
|
of_node_put(np);
|
|
if (!pdev)
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
mc = platform_get_drvdata(pdev);
|
|
if (!mc) {
|
|
put_device(&pdev->dev);
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
}
|
|
|
|
err = devm_add_action_or_reset(dev, tegra_mc_devm_action_put_device, mc);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
return mc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_tegra_memory_controller_get);
|
|
|
|
int tegra_mc_probe_device(struct tegra_mc *mc, struct device *dev)
|
|
{
|
|
if (mc->soc->ops && mc->soc->ops->probe_device)
|
|
return mc->soc->ops->probe_device(mc, dev);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tegra_mc_probe_device);
|
|
|
|
int tegra_mc_get_carveout_info(struct tegra_mc *mc, unsigned int id,
|
|
phys_addr_t *base, u64 *size)
|
|
{
|
|
u32 offset;
|
|
|
|
if (id < 1 || id >= mc->soc->num_carveouts)
|
|
return -EINVAL;
|
|
|
|
if (id < 6)
|
|
offset = 0xc0c + 0x50 * (id - 1);
|
|
else
|
|
offset = 0x2004 + 0x50 * (id - 6);
|
|
|
|
*base = mc_ch_readl(mc, MC_BROADCAST_CHANNEL, offset + 0x0);
|
|
#ifdef CONFIG_PHYS_ADDR_T_64BIT
|
|
*base |= (phys_addr_t)mc_ch_readl(mc, MC_BROADCAST_CHANNEL, offset + 0x4) << 32;
|
|
#endif
|
|
|
|
if (size)
|
|
*size = mc_ch_readl(mc, MC_BROADCAST_CHANNEL, offset + 0x8) << 17;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tegra_mc_get_carveout_info);
|
|
|
|
static int tegra_mc_block_dma_common(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 tegra_mc_dma_idling_common(struct tegra_mc *mc,
|
|
const struct tegra_mc_reset *rst)
|
|
{
|
|
return (mc_readl(mc, rst->status) & BIT(rst->bit)) != 0;
|
|
}
|
|
|
|
static int tegra_mc_unblock_dma_common(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 int tegra_mc_reset_status_common(struct tegra_mc *mc,
|
|
const struct tegra_mc_reset *rst)
|
|
{
|
|
return (mc_readl(mc, rst->control) & BIT(rst->bit)) != 0;
|
|
}
|
|
|
|
const struct tegra_mc_reset_ops tegra_mc_reset_ops_common = {
|
|
.block_dma = tegra_mc_block_dma_common,
|
|
.dma_idling = tegra_mc_dma_idling_common,
|
|
.unblock_dma = tegra_mc_unblock_dma_common,
|
|
.reset_status = tegra_mc_reset_status_common,
|
|
};
|
|
|
|
static inline struct tegra_mc *reset_to_mc(struct reset_controller_dev *rcdev)
|
|
{
|
|
return container_of(rcdev, struct tegra_mc, reset);
|
|
}
|
|
|
|
static const struct tegra_mc_reset *tegra_mc_reset_find(struct tegra_mc *mc,
|
|
unsigned long id)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < mc->soc->num_resets; i++)
|
|
if (mc->soc->resets[i].id == id)
|
|
return &mc->soc->resets[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int tegra_mc_hotreset_assert(struct reset_controller_dev *rcdev,
|
|
unsigned long id)
|
|
{
|
|
struct tegra_mc *mc = reset_to_mc(rcdev);
|
|
const struct tegra_mc_reset_ops *rst_ops;
|
|
const struct tegra_mc_reset *rst;
|
|
int retries = 500;
|
|
int err;
|
|
|
|
rst = tegra_mc_reset_find(mc, id);
|
|
if (!rst)
|
|
return -ENODEV;
|
|
|
|
rst_ops = mc->soc->reset_ops;
|
|
if (!rst_ops)
|
|
return -ENODEV;
|
|
|
|
/* DMA flushing will fail if reset is already asserted */
|
|
if (rst_ops->reset_status) {
|
|
/* check whether reset is asserted */
|
|
if (rst_ops->reset_status(mc, rst))
|
|
return 0;
|
|
}
|
|
|
|
if (rst_ops->block_dma) {
|
|
/* block clients DMA requests */
|
|
err = rst_ops->block_dma(mc, rst);
|
|
if (err) {
|
|
dev_err(mc->dev, "failed to block %s DMA: %d\n",
|
|
rst->name, err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (rst_ops->dma_idling) {
|
|
/* wait for completion of the outstanding DMA requests */
|
|
while (!rst_ops->dma_idling(mc, rst)) {
|
|
if (!retries--) {
|
|
dev_err(mc->dev, "failed to flush %s DMA\n",
|
|
rst->name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
usleep_range(10, 100);
|
|
}
|
|
}
|
|
|
|
if (rst_ops->hotreset_assert) {
|
|
/* clear clients DMA requests sitting before arbitration */
|
|
err = rst_ops->hotreset_assert(mc, rst);
|
|
if (err) {
|
|
dev_err(mc->dev, "failed to hot reset %s: %d\n",
|
|
rst->name, err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_mc_hotreset_deassert(struct reset_controller_dev *rcdev,
|
|
unsigned long id)
|
|
{
|
|
struct tegra_mc *mc = reset_to_mc(rcdev);
|
|
const struct tegra_mc_reset_ops *rst_ops;
|
|
const struct tegra_mc_reset *rst;
|
|
int err;
|
|
|
|
rst = tegra_mc_reset_find(mc, id);
|
|
if (!rst)
|
|
return -ENODEV;
|
|
|
|
rst_ops = mc->soc->reset_ops;
|
|
if (!rst_ops)
|
|
return -ENODEV;
|
|
|
|
if (rst_ops->hotreset_deassert) {
|
|
/* take out client from hot reset */
|
|
err = rst_ops->hotreset_deassert(mc, rst);
|
|
if (err) {
|
|
dev_err(mc->dev, "failed to deassert hot reset %s: %d\n",
|
|
rst->name, err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (rst_ops->unblock_dma) {
|
|
/* allow new DMA requests to proceed to arbitration */
|
|
err = rst_ops->unblock_dma(mc, rst);
|
|
if (err) {
|
|
dev_err(mc->dev, "failed to unblock %s DMA : %d\n",
|
|
rst->name, err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_mc_hotreset_status(struct reset_controller_dev *rcdev,
|
|
unsigned long id)
|
|
{
|
|
struct tegra_mc *mc = reset_to_mc(rcdev);
|
|
const struct tegra_mc_reset_ops *rst_ops;
|
|
const struct tegra_mc_reset *rst;
|
|
|
|
rst = tegra_mc_reset_find(mc, id);
|
|
if (!rst)
|
|
return -ENODEV;
|
|
|
|
rst_ops = mc->soc->reset_ops;
|
|
if (!rst_ops)
|
|
return -ENODEV;
|
|
|
|
return rst_ops->reset_status(mc, rst);
|
|
}
|
|
|
|
static const struct reset_control_ops tegra_mc_reset_ops = {
|
|
.assert = tegra_mc_hotreset_assert,
|
|
.deassert = tegra_mc_hotreset_deassert,
|
|
.status = tegra_mc_hotreset_status,
|
|
};
|
|
|
|
static int tegra_mc_reset_setup(struct tegra_mc *mc)
|
|
{
|
|
int err;
|
|
|
|
mc->reset.ops = &tegra_mc_reset_ops;
|
|
mc->reset.owner = THIS_MODULE;
|
|
mc->reset.of_node = mc->dev->of_node;
|
|
mc->reset.of_reset_n_cells = 1;
|
|
mc->reset.nr_resets = mc->soc->num_resets;
|
|
|
|
err = reset_controller_register(&mc->reset);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate)
|
|
{
|
|
unsigned int i;
|
|
struct tegra_mc_timing *timing = NULL;
|
|
|
|
for (i = 0; i < mc->num_timings; i++) {
|
|
if (mc->timings[i].rate == rate) {
|
|
timing = &mc->timings[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!timing) {
|
|
dev_err(mc->dev, "no memory timing registered for rate %lu\n",
|
|
rate);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < mc->soc->num_emem_regs; ++i)
|
|
mc_writel(mc, timing->emem_data[i], mc->soc->emem_regs[i]);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tegra_mc_write_emem_configuration);
|
|
|
|
unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc)
|
|
{
|
|
u8 dram_count;
|
|
|
|
dram_count = mc_readl(mc, MC_EMEM_ADR_CFG);
|
|
dram_count &= MC_EMEM_ADR_CFG_EMEM_NUMDEV;
|
|
dram_count++;
|
|
|
|
return dram_count;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tegra_mc_get_emem_device_count);
|
|
|
|
#if defined(CONFIG_ARCH_TEGRA_3x_SOC) || \
|
|
defined(CONFIG_ARCH_TEGRA_114_SOC) || \
|
|
defined(CONFIG_ARCH_TEGRA_124_SOC) || \
|
|
defined(CONFIG_ARCH_TEGRA_132_SOC) || \
|
|
defined(CONFIG_ARCH_TEGRA_210_SOC)
|
|
static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc)
|
|
{
|
|
unsigned long long tick;
|
|
unsigned int i;
|
|
u32 value;
|
|
|
|
/* compute the number of MC clock cycles per tick */
|
|
tick = (unsigned long long)mc->tick * clk_get_rate(mc->clk);
|
|
do_div(tick, NSEC_PER_SEC);
|
|
|
|
value = mc_readl(mc, MC_EMEM_ARB_CFG);
|
|
value &= ~MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE_MASK;
|
|
value |= MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE(tick);
|
|
mc_writel(mc, value, MC_EMEM_ARB_CFG);
|
|
|
|
/* write latency allowance defaults */
|
|
for (i = 0; i < mc->soc->num_clients; i++) {
|
|
const struct tegra_mc_client *client = &mc->soc->clients[i];
|
|
u32 value;
|
|
|
|
value = mc_readl(mc, client->regs.la.reg);
|
|
value &= ~(client->regs.la.mask << client->regs.la.shift);
|
|
value |= (client->regs.la.def & client->regs.la.mask) << client->regs.la.shift;
|
|
mc_writel(mc, value, client->regs.la.reg);
|
|
}
|
|
|
|
/* latch new values */
|
|
mc_writel(mc, MC_TIMING_UPDATE, MC_TIMING_CONTROL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int load_one_timing(struct tegra_mc *mc,
|
|
struct tegra_mc_timing *timing,
|
|
struct device_node *node)
|
|
{
|
|
int err;
|
|
u32 tmp;
|
|
|
|
err = of_property_read_u32(node, "clock-frequency", &tmp);
|
|
if (err) {
|
|
dev_err(mc->dev,
|
|
"timing %pOFn: failed to read rate\n", node);
|
|
return err;
|
|
}
|
|
|
|
timing->rate = tmp;
|
|
timing->emem_data = devm_kcalloc(mc->dev, mc->soc->num_emem_regs,
|
|
sizeof(u32), GFP_KERNEL);
|
|
if (!timing->emem_data)
|
|
return -ENOMEM;
|
|
|
|
err = of_property_read_u32_array(node, "nvidia,emem-configuration",
|
|
timing->emem_data,
|
|
mc->soc->num_emem_regs);
|
|
if (err) {
|
|
dev_err(mc->dev,
|
|
"timing %pOFn: failed to read EMEM configuration\n",
|
|
node);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int load_timings(struct tegra_mc *mc, struct device_node *node)
|
|
{
|
|
struct device_node *child;
|
|
struct tegra_mc_timing *timing;
|
|
int child_count = of_get_child_count(node);
|
|
int i = 0, err;
|
|
|
|
mc->timings = devm_kcalloc(mc->dev, child_count, sizeof(*timing),
|
|
GFP_KERNEL);
|
|
if (!mc->timings)
|
|
return -ENOMEM;
|
|
|
|
mc->num_timings = child_count;
|
|
|
|
for_each_child_of_node(node, child) {
|
|
timing = &mc->timings[i++];
|
|
|
|
err = load_one_timing(mc, timing, child);
|
|
if (err) {
|
|
of_node_put(child);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_mc_setup_timings(struct tegra_mc *mc)
|
|
{
|
|
struct device_node *node;
|
|
u32 ram_code, node_ram_code;
|
|
int err;
|
|
|
|
ram_code = tegra_read_ram_code();
|
|
|
|
mc->num_timings = 0;
|
|
|
|
for_each_child_of_node(mc->dev->of_node, node) {
|
|
err = of_property_read_u32(node, "nvidia,ram-code",
|
|
&node_ram_code);
|
|
if (err || (node_ram_code != ram_code))
|
|
continue;
|
|
|
|
err = load_timings(mc, node);
|
|
of_node_put(node);
|
|
if (err)
|
|
return err;
|
|
break;
|
|
}
|
|
|
|
if (mc->num_timings == 0)
|
|
dev_warn(mc->dev,
|
|
"no memory timings for RAM code %u registered\n",
|
|
ram_code);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tegra30_mc_probe(struct tegra_mc *mc)
|
|
{
|
|
int err;
|
|
|
|
mc->clk = devm_clk_get_optional(mc->dev, "mc");
|
|
if (IS_ERR(mc->clk)) {
|
|
dev_err(mc->dev, "failed to get MC clock: %ld\n", PTR_ERR(mc->clk));
|
|
return PTR_ERR(mc->clk);
|
|
}
|
|
|
|
/* ensure that debug features are disabled */
|
|
mc_writel(mc, 0x00000000, MC_TIMING_CONTROL_DBG);
|
|
|
|
err = tegra_mc_setup_latency_allowance(mc);
|
|
if (err < 0) {
|
|
dev_err(mc->dev, "failed to setup latency allowance: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
err = tegra_mc_setup_timings(mc);
|
|
if (err < 0) {
|
|
dev_err(mc->dev, "failed to setup timings: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct tegra_mc_ops tegra30_mc_ops = {
|
|
.probe = tegra30_mc_probe,
|
|
.handle_irq = tegra30_mc_handle_irq,
|
|
};
|
|
#endif
|
|
|
|
static int mc_global_intstatus_to_channel(const struct tegra_mc *mc, u32 status,
|
|
unsigned int *mc_channel)
|
|
{
|
|
if ((status & mc->soc->ch_intmask) == 0)
|
|
return -EINVAL;
|
|
|
|
*mc_channel = __ffs((status & mc->soc->ch_intmask) >>
|
|
mc->soc->global_intstatus_channel_shift);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 mc_channel_to_global_intstatus(const struct tegra_mc *mc,
|
|
unsigned int channel)
|
|
{
|
|
return BIT(channel) << mc->soc->global_intstatus_channel_shift;
|
|
}
|
|
|
|
irqreturn_t tegra30_mc_handle_irq(int irq, void *data)
|
|
{
|
|
struct tegra_mc *mc = data;
|
|
unsigned int bit, channel;
|
|
unsigned long status;
|
|
|
|
if (mc->soc->num_channels) {
|
|
u32 global_status;
|
|
int err;
|
|
|
|
global_status = mc_ch_readl(mc, MC_BROADCAST_CHANNEL, MC_GLOBAL_INTSTATUS);
|
|
err = mc_global_intstatus_to_channel(mc, global_status, &channel);
|
|
if (err < 0) {
|
|
dev_err_ratelimited(mc->dev, "unknown interrupt channel 0x%08x\n",
|
|
global_status);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
/* mask all interrupts to avoid flooding */
|
|
status = mc_ch_readl(mc, channel, MC_INTSTATUS) & mc->soc->intmask;
|
|
} else {
|
|
status = mc_readl(mc, MC_INTSTATUS) & mc->soc->intmask;
|
|
}
|
|
|
|
if (!status)
|
|
return IRQ_NONE;
|
|
|
|
for_each_set_bit(bit, &status, 32) {
|
|
const char *error = tegra_mc_status_names[bit] ?: "unknown";
|
|
const char *client = "unknown", *desc;
|
|
const char *direction, *secure;
|
|
u32 status_reg, addr_reg;
|
|
u32 intmask = BIT(bit);
|
|
phys_addr_t addr = 0;
|
|
#ifdef CONFIG_PHYS_ADDR_T_64BIT
|
|
u32 addr_hi_reg = 0;
|
|
#endif
|
|
unsigned int i;
|
|
char perm[7];
|
|
u8 id, type;
|
|
u32 value;
|
|
|
|
switch (intmask) {
|
|
case MC_INT_DECERR_VPR:
|
|
status_reg = MC_ERR_VPR_STATUS;
|
|
addr_reg = MC_ERR_VPR_ADR;
|
|
break;
|
|
|
|
case MC_INT_SECERR_SEC:
|
|
status_reg = MC_ERR_SEC_STATUS;
|
|
addr_reg = MC_ERR_SEC_ADR;
|
|
break;
|
|
|
|
case MC_INT_DECERR_MTS:
|
|
status_reg = MC_ERR_MTS_STATUS;
|
|
addr_reg = MC_ERR_MTS_ADR;
|
|
break;
|
|
|
|
case MC_INT_DECERR_GENERALIZED_CARVEOUT:
|
|
status_reg = MC_ERR_GENERALIZED_CARVEOUT_STATUS;
|
|
addr_reg = MC_ERR_GENERALIZED_CARVEOUT_ADR;
|
|
break;
|
|
|
|
case MC_INT_DECERR_ROUTE_SANITY:
|
|
status_reg = MC_ERR_ROUTE_SANITY_STATUS;
|
|
addr_reg = MC_ERR_ROUTE_SANITY_ADR;
|
|
break;
|
|
|
|
default:
|
|
status_reg = MC_ERR_STATUS;
|
|
addr_reg = MC_ERR_ADR;
|
|
|
|
#ifdef CONFIG_PHYS_ADDR_T_64BIT
|
|
if (mc->soc->has_addr_hi_reg)
|
|
addr_hi_reg = MC_ERR_ADR_HI;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
if (mc->soc->num_channels)
|
|
value = mc_ch_readl(mc, channel, status_reg);
|
|
else
|
|
value = mc_readl(mc, status_reg);
|
|
|
|
#ifdef CONFIG_PHYS_ADDR_T_64BIT
|
|
if (mc->soc->num_address_bits > 32) {
|
|
if (addr_hi_reg) {
|
|
if (mc->soc->num_channels)
|
|
addr = mc_ch_readl(mc, channel, addr_hi_reg);
|
|
else
|
|
addr = mc_readl(mc, addr_hi_reg);
|
|
} else {
|
|
addr = ((value >> MC_ERR_STATUS_ADR_HI_SHIFT) &
|
|
MC_ERR_STATUS_ADR_HI_MASK);
|
|
}
|
|
addr <<= 32;
|
|
}
|
|
#endif
|
|
|
|
if (value & MC_ERR_STATUS_RW)
|
|
direction = "write";
|
|
else
|
|
direction = "read";
|
|
|
|
if (value & MC_ERR_STATUS_SECURITY)
|
|
secure = "secure ";
|
|
else
|
|
secure = "";
|
|
|
|
id = value & mc->soc->client_id_mask;
|
|
|
|
for (i = 0; i < mc->soc->num_clients; i++) {
|
|
if (mc->soc->clients[i].id == id) {
|
|
client = mc->soc->clients[i].name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
type = (value & MC_ERR_STATUS_TYPE_MASK) >>
|
|
MC_ERR_STATUS_TYPE_SHIFT;
|
|
desc = tegra_mc_error_names[type];
|
|
|
|
switch (value & MC_ERR_STATUS_TYPE_MASK) {
|
|
case MC_ERR_STATUS_TYPE_INVALID_SMMU_PAGE:
|
|
perm[0] = ' ';
|
|
perm[1] = '[';
|
|
|
|
if (value & MC_ERR_STATUS_READABLE)
|
|
perm[2] = 'R';
|
|
else
|
|
perm[2] = '-';
|
|
|
|
if (value & MC_ERR_STATUS_WRITABLE)
|
|
perm[3] = 'W';
|
|
else
|
|
perm[3] = '-';
|
|
|
|
if (value & MC_ERR_STATUS_NONSECURE)
|
|
perm[4] = '-';
|
|
else
|
|
perm[4] = 'S';
|
|
|
|
perm[5] = ']';
|
|
perm[6] = '\0';
|
|
break;
|
|
|
|
default:
|
|
perm[0] = '\0';
|
|
break;
|
|
}
|
|
|
|
if (mc->soc->num_channels)
|
|
value = mc_ch_readl(mc, channel, addr_reg);
|
|
else
|
|
value = mc_readl(mc, addr_reg);
|
|
addr |= value;
|
|
|
|
dev_err_ratelimited(mc->dev, "%s: %s%s @%pa: %s (%s%s)\n",
|
|
client, secure, direction, &addr, error,
|
|
desc, perm);
|
|
}
|
|
|
|
/* clear interrupts */
|
|
if (mc->soc->num_channels) {
|
|
mc_ch_writel(mc, channel, status, MC_INTSTATUS);
|
|
mc_ch_writel(mc, MC_BROADCAST_CHANNEL,
|
|
mc_channel_to_global_intstatus(mc, channel),
|
|
MC_GLOBAL_INTSTATUS);
|
|
} else {
|
|
mc_writel(mc, status, MC_INTSTATUS);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
const char *const tegra_mc_status_names[32] = {
|
|
[ 1] = "External interrupt",
|
|
[ 6] = "EMEM address decode error",
|
|
[ 7] = "GART page fault",
|
|
[ 8] = "Security violation",
|
|
[ 9] = "EMEM arbitration error",
|
|
[10] = "Page fault",
|
|
[11] = "Invalid APB ASID update",
|
|
[12] = "VPR violation",
|
|
[13] = "Secure carveout violation",
|
|
[16] = "MTS carveout violation",
|
|
[17] = "Generalized carveout violation",
|
|
[20] = "Route Sanity error",
|
|
};
|
|
|
|
const char *const tegra_mc_error_names[8] = {
|
|
[2] = "EMEM decode error",
|
|
[3] = "TrustZone violation",
|
|
[4] = "Carveout violation",
|
|
[6] = "SMMU translation error",
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
|
|
icc_provider_init(&mc->provider);
|
|
|
|
/* create Memory Controller node */
|
|
node = icc_node_create(TEGRA_ICC_MC);
|
|
if (IS_ERR(node))
|
|
return PTR_ERR(node);
|
|
|
|
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;
|
|
}
|
|
|
|
err = icc_provider_register(&mc->provider);
|
|
if (err)
|
|
goto remove_nodes;
|
|
|
|
return 0;
|
|
|
|
remove_nodes:
|
|
icc_nodes_remove(&mc->provider);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int tegra_mc_probe(struct platform_device *pdev)
|
|
{
|
|
struct tegra_mc *mc;
|
|
u64 mask;
|
|
int err;
|
|
|
|
mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL);
|
|
if (!mc)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, mc);
|
|
spin_lock_init(&mc->lock);
|
|
mc->soc = of_device_get_match_data(&pdev->dev);
|
|
mc->dev = &pdev->dev;
|
|
|
|
mask = DMA_BIT_MASK(mc->soc->num_address_bits);
|
|
|
|
err = dma_coerce_mask_and_coherent(&pdev->dev, mask);
|
|
if (err < 0) {
|
|
dev_err(&pdev->dev, "failed to set DMA mask: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/* length of MC tick in nanoseconds */
|
|
mc->tick = 30;
|
|
|
|
mc->regs = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(mc->regs))
|
|
return PTR_ERR(mc->regs);
|
|
|
|
mc->debugfs.root = debugfs_create_dir("mc", NULL);
|
|
|
|
if (mc->soc->ops && mc->soc->ops->probe) {
|
|
err = mc->soc->ops->probe(mc);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
if (mc->soc->ops && mc->soc->ops->handle_irq) {
|
|
mc->irq = platform_get_irq(pdev, 0);
|
|
if (mc->irq < 0)
|
|
return mc->irq;
|
|
|
|
WARN(!mc->soc->client_id_mask, "missing client ID mask for this SoC\n");
|
|
|
|
if (mc->soc->num_channels)
|
|
mc_ch_writel(mc, MC_BROADCAST_CHANNEL, mc->soc->intmask,
|
|
MC_INTMASK);
|
|
else
|
|
mc_writel(mc, mc->soc->intmask, MC_INTMASK);
|
|
|
|
err = devm_request_irq(&pdev->dev, mc->irq, mc->soc->ops->handle_irq, 0,
|
|
dev_name(&pdev->dev), mc);
|
|
if (err < 0) {
|
|
dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", mc->irq,
|
|
err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (mc->soc->reset_ops) {
|
|
err = tegra_mc_reset_setup(mc);
|
|
if (err < 0)
|
|
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)) {
|
|
dev_err(&pdev->dev, "failed to probe SMMU: %ld\n",
|
|
PTR_ERR(mc->smmu));
|
|
mc->smmu = NULL;
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_TEGRA_IOMMU_GART) && !mc->soc->smmu) {
|
|
mc->gart = tegra_gart_probe(&pdev->dev, mc);
|
|
if (IS_ERR(mc->gart)) {
|
|
dev_err(&pdev->dev, "failed to probe GART: %ld\n",
|
|
PTR_ERR(mc->gart));
|
|
mc->gart = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused tegra_mc_suspend(struct device *dev)
|
|
{
|
|
struct tegra_mc *mc = dev_get_drvdata(dev);
|
|
|
|
if (mc->soc->ops && mc->soc->ops->suspend)
|
|
return mc->soc->ops->suspend(mc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused tegra_mc_resume(struct device *dev)
|
|
{
|
|
struct tegra_mc *mc = dev_get_drvdata(dev);
|
|
|
|
if (mc->soc->ops && mc->soc->ops->resume)
|
|
return mc->soc->ops->resume(mc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_mc_sync_state(struct device *dev)
|
|
{
|
|
struct tegra_mc *mc = dev_get_drvdata(dev);
|
|
|
|
/* check whether ICC provider is registered */
|
|
if (mc->provider.dev == dev)
|
|
icc_sync_state(dev);
|
|
}
|
|
|
|
static const struct dev_pm_ops tegra_mc_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(tegra_mc_suspend, tegra_mc_resume)
|
|
};
|
|
|
|
static struct platform_driver tegra_mc_driver = {
|
|
.driver = {
|
|
.name = "tegra-mc",
|
|
.of_match_table = tegra_mc_of_match,
|
|
.pm = &tegra_mc_pm_ops,
|
|
.suppress_bind_attrs = true,
|
|
.sync_state = tegra_mc_sync_state,
|
|
},
|
|
.prevent_deferred_probe = true,
|
|
.probe = tegra_mc_probe,
|
|
};
|
|
|
|
static int tegra_mc_init(void)
|
|
{
|
|
return platform_driver_register(&tegra_mc_driver);
|
|
}
|
|
arch_initcall(tegra_mc_init);
|
|
|
|
MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
|
|
MODULE_DESCRIPTION("NVIDIA Tegra Memory Controller driver");
|