Message ID | 1403815790-8548-5-git-send-email-thierry.reding@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi Thierry, On 06/27/2014 04:49 AM, Thierry Reding wrote: [snip] > + > +#define MC_INTSTATUS 0x000 > +#define MC_INT_DECERR_MTS (1 << 16) > +#define MC_INT_SECERR_SEC (1 << 13) > +#define MC_INT_DECERR_VPR (1 << 12) > +#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11) > +#define MC_INT_INVALID_SMMU_PAGE (1 << 10) > +#define MC_INT_ARBITRATION_EMEM (1 << 9) > +#define MC_INT_SECURITY_VIOLATION (1 << 8) > +#define MC_INT_DECERR_EMEM (1 << 6) > +#define MC_INTMASK 0x004 > +#define MC_ERR_STATUS 0x08 > +#define MC_ERR_ADR 0x0c > + [snip] > + > +#define SMMU_PDE_ATTR (SMMU_PDE_READABLE | SMMU_PDE_WRITABLE | \ > + SMMU_PDE_NONSECURE) > +#define SMMU_PTE_ATTR (SMMU_PTE_READABLE | SMMU_PTE_WRITABLE | \ > + SMMU_PTE_NONSECURE) > + > +#define SMMU_PDE_VACANT(n) (((n) << 10) | SMMU_PDE_ATTR) > +#define SMMU_PTE_VACANT(n) (((n) << 12) | SMMU_PTE_ATTR) There is an ISR to catch the invalid SMMU translation. Do you want to modify the identity mapping with read/write attribute of the unused SMMU pages? This can make sure we capture the invalid SMMU translation. And helps for driver to capture issues when using SMMU. -joseph > +static irqreturn_t tegra124_mc_irq(int irq, void *data) > +{ > + struct tegra_mc *mc = data; > + u32 value, status, mask; > + > + /* mask all interrupts to avoid flooding */ > + mask = mc_readl(mc, MC_INTMASK); > + mc_writel(mc, 0, MC_INTMASK); > + > + status = mc_readl(mc, MC_INTSTATUS); > + mc_writel(mc, status, MC_INTSTATUS); > + > + dev_dbg(mc->dev, "INTSTATUS: %08x\n", status); > + > + if (status & MC_INT_DECERR_MTS) > + dev_dbg(mc->dev, " DECERR_MTS\n"); > + > + if (status & MC_INT_SECERR_SEC) > + dev_dbg(mc->dev, " SECERR_SEC\n"); > + > + if (status & MC_INT_DECERR_VPR) > + dev_dbg(mc->dev, " DECERR_VPR\n"); > + > + if (status & MC_INT_INVALID_APB_ASID_UPDATE) > + dev_dbg(mc->dev, " INVALID_APB_ASID_UPDATE\n"); > + > + if (status & MC_INT_INVALID_SMMU_PAGE) > + dev_dbg(mc->dev, " INVALID_SMMU_PAGE\n"); > + > + if (status & MC_INT_ARBITRATION_EMEM) > + dev_dbg(mc->dev, " ARBITRATION_EMEM\n"); > + > + if (status & MC_INT_SECURITY_VIOLATION) > + dev_dbg(mc->dev, " SECURITY_VIOLATION\n"); > + > + if (status & MC_INT_DECERR_EMEM) > + dev_dbg(mc->dev, " DECERR_EMEM\n"); > + > + value = mc_readl(mc, MC_ERR_STATUS); > + > + dev_dbg(mc->dev, "ERR_STATUS: %08x\n", value); > + dev_dbg(mc->dev, " type: %x\n", (value >> 28) & 0x7); > + dev_dbg(mc->dev, " protection: %x\n", (value >> 25) & 0x7); > + dev_dbg(mc->dev, " adr_hi: %x\n", (value >> 20) & 0x3); > + dev_dbg(mc->dev, " swap: %x\n", (value >> 18) & 0x1); > + dev_dbg(mc->dev, " security: %x\n", (value >> 17) & 0x1); > + dev_dbg(mc->dev, " r/w: %x\n", (value >> 16) & 0x1); > + dev_dbg(mc->dev, " adr1: %x\n", (value >> 12) & 0x7); > + dev_dbg(mc->dev, " client: %x\n", value & 0x7f); > + > + value = mc_readl(mc, MC_ERR_ADR); > + dev_dbg(mc->dev, "ERR_ADR: %08x\n", value); > + > + mc_writel(mc, mask, MC_INTMASK); > + > + return IRQ_HANDLED; > +} > +
On Fri, Jun 27, 2014 at 03:41:20PM +0800, Joseph Lo wrote: > Hi Thierry, > > On 06/27/2014 04:49 AM, Thierry Reding wrote: > [snip] > >+ > >+#define MC_INTSTATUS 0x000 > >+#define MC_INT_DECERR_MTS (1 << 16) > >+#define MC_INT_SECERR_SEC (1 << 13) > >+#define MC_INT_DECERR_VPR (1 << 12) > >+#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11) > >+#define MC_INT_INVALID_SMMU_PAGE (1 << 10) > >+#define MC_INT_ARBITRATION_EMEM (1 << 9) > >+#define MC_INT_SECURITY_VIOLATION (1 << 8) > >+#define MC_INT_DECERR_EMEM (1 << 6) > >+#define MC_INTMASK 0x004 > >+#define MC_ERR_STATUS 0x08 > >+#define MC_ERR_ADR 0x0c > >+ > [snip] > >+ > >+#define SMMU_PDE_ATTR (SMMU_PDE_READABLE | SMMU_PDE_WRITABLE | \ > >+ SMMU_PDE_NONSECURE) > >+#define SMMU_PTE_ATTR (SMMU_PTE_READABLE | SMMU_PTE_WRITABLE | \ > >+ SMMU_PTE_NONSECURE) > >+ > >+#define SMMU_PDE_VACANT(n) (((n) << 10) | SMMU_PDE_ATTR) > >+#define SMMU_PTE_VACANT(n) (((n) << 12) | SMMU_PTE_ATTR) > > There is an ISR to catch the invalid SMMU translation. Do you want to modify > the identity mapping with read/write attribute of the unused SMMU pages? I'm not sure I understand what you mean by "identity mapping". None of the public documentation seems to describe the exact layout of PDEs or PTEs, so it's somewhat hard to tell what to set them to when pages are unmapped. > This can make sure we capture the invalid SMMU translation. And helps for > driver to capture issues when using SMMU. That certainly sounds like a useful thing to have. Like I said this is an RFC and I'm not even sure if it's acceptable in the current form, so I wanted to get feedback early on to avoid wasting effort on something that turn out to be a wild-goose chase. Thierry
Thierry Reding <thierry.reding@gmail.com> writes: > * PGP Signed by an unknown key > > On Fri, Jun 27, 2014 at 03:41:20PM +0800, Joseph Lo wrote: >> Hi Thierry, >> >> On 06/27/2014 04:49 AM, Thierry Reding wrote: >> [snip] >> >+ >> >+#define MC_INTSTATUS 0x000 >> >+#define MC_INT_DECERR_MTS (1 << 16) >> >+#define MC_INT_SECERR_SEC (1 << 13) >> >+#define MC_INT_DECERR_VPR (1 << 12) >> >+#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11) >> >+#define MC_INT_INVALID_SMMU_PAGE (1 << 10) >> >+#define MC_INT_ARBITRATION_EMEM (1 << 9) >> >+#define MC_INT_SECURITY_VIOLATION (1 << 8) >> >+#define MC_INT_DECERR_EMEM (1 << 6) >> >+#define MC_INTMASK 0x004 >> >+#define MC_ERR_STATUS 0x08 >> >+#define MC_ERR_ADR 0x0c >> >+ >> [snip] >> >+ >> >+#define SMMU_PDE_ATTR (SMMU_PDE_READABLE | SMMU_PDE_WRITABLE | \ >> >+ SMMU_PDE_NONSECURE) >> >+#define SMMU_PTE_ATTR (SMMU_PTE_READABLE | SMMU_PTE_WRITABLE | \ >> >+ SMMU_PTE_NONSECURE) >> >+ >> >+#define SMMU_PDE_VACANT(n) (((n) << 10) | SMMU_PDE_ATTR) >> >+#define SMMU_PTE_VACANT(n) (((n) << 12) | SMMU_PTE_ATTR) They should be set 0. The above VACANT macros are legacy support for some special case that a device wanted linear SMMU mapping where iova == phy. No need any more. >> There is an ISR to catch the invalid SMMU translation. Do you want to modify >> the identity mapping with read/write attribute of the unused SMMU pages? > > I'm not sure I understand what you mean by "identity mapping". None of > the public documentation seems to describe the exact layout of PDEs or > PTEs, so it's somewhat hard to tell what to set them to when pages are > unmapped. > >> This can make sure we capture the invalid SMMU translation. And helps for >> driver to capture issues when using SMMU. > > That certainly sounds like a useful thing to have. Like I said this is > an RFC and I'm not even sure if it's acceptable in the current form, so > I wanted to get feedback early on to avoid wasting effort on something > that turn out to be a wild-goose chase. > > Thierry > > * Unknown Key > * 0x7F3EB3A1
Thierry Reding <thierry.reding@gmail.com> writes: > From: Thierry Reding <treding@nvidia.com> > > The memory controller on NVIDIA Tegra124 exposes various knobs that can > be used to tune the behaviour of the clients attached to it. > > Currently this driver sets up the latency allowance registers to the HW > defaults. Eventually an API should be exported by this driver (via a > custom API or a generic subsystem) to allow clients to register latency > requirements. > > This driver also registers an IOMMU (SMMU) that's implemented by the > memory controller. > > Signed-off-by: Thierry Reding <treding@nvidia.com> > --- > drivers/memory/Kconfig | 9 + > drivers/memory/Makefile | 1 + > drivers/memory/tegra124-mc.c | 1945 ++++++++++++++++++++++++++++++ > include/dt-bindings/memory/tegra124-mc.h | 30 + > 4 files changed, 1985 insertions(+) > create mode 100644 drivers/memory/tegra124-mc.c > create mode 100644 include/dt-bindings/memory/tegra124-mc.h I prefer reusing the existing SMMU and having MC and SMMU separated since most of SMMU code are not different from functionality POV, and new MC features are quite independent of SMMU. If it's really convenient to combine MC and SMMU into one driver, we could move "drivers/iomm/tegra-smmu.c" here first, and add MC features on the top of it.
On Thursday 26 June 2014 22:49:44 Thierry Reding wrote: > +static const struct tegra_mc_client tegra124_mc_clients[] = { > + { > + .id = 0x01, > + .name = "display0a", > + .swgroup = TEGRA_SWGROUP_DC, > + .smmu = { > + .reg = 0x228, > + .bit = 1, > + }, > + .latency = { > + .reg = 0x2e8, > + .shift = 0, > + .mask = 0xff, > + .def = 0xc2, > + }, > + }, { This is a rather long table that I assume would need to get duplicated and modified for each specific SoC. Have you considered to put the information into DT instead, as auxiliary data in the iommu specifier as provided by the device? Arnd
On Fri, Jun 27, 2014 at 12:46:38PM +0300, Hiroshi DOyu wrote: > > Thierry Reding <thierry.reding@gmail.com> writes: > > > From: Thierry Reding <treding@nvidia.com> > > > > The memory controller on NVIDIA Tegra124 exposes various knobs that can > > be used to tune the behaviour of the clients attached to it. > > > > Currently this driver sets up the latency allowance registers to the HW > > defaults. Eventually an API should be exported by this driver (via a > > custom API or a generic subsystem) to allow clients to register latency > > requirements. > > > > This driver also registers an IOMMU (SMMU) that's implemented by the > > memory controller. > > > > Signed-off-by: Thierry Reding <treding@nvidia.com> > > --- > > drivers/memory/Kconfig | 9 + > > drivers/memory/Makefile | 1 + > > drivers/memory/tegra124-mc.c | 1945 ++++++++++++++++++++++++++++++ > > include/dt-bindings/memory/tegra124-mc.h | 30 + > > 4 files changed, 1985 insertions(+) > > create mode 100644 drivers/memory/tegra124-mc.c > > create mode 100644 include/dt-bindings/memory/tegra124-mc.h > > I prefer reusing the existing SMMU and having MC and SMMU separated > since most of SMMU code are not different from functionality POV, and > new MC features are quite independent of SMMU. > > If it's really convenient to combine MC and SMMU into one driver, we > could move "drivers/iomm/tegra-smmu.c" here first, and add MC features > on the top of it. I'm not sure if we can do that, since the tegra-smmu driver is technically used by Tegra30 and Tegra114. We've never really made use of it, but there are device trees in mainline releases that contain the separate SMMU node. Perhaps one of the DT folks can comment on whether it would be possible to break compatibility with existing DTs in this case, given that the SMMU on Tegra30 and Tegra114 have never been used. Either way, I do see advantages in incremental patches, but at the same time the old driver and architecture was never enabled (therefore not tested either) upstream and as shown by the Tegra DRM example can't cope with more complex cases. So I'm not completely convinced that an incremental approach would be the best here. Thierry
On Fri, Jun 27, 2014 at 01:07:04PM +0200, Arnd Bergmann wrote: > On Thursday 26 June 2014 22:49:44 Thierry Reding wrote: > > +static const struct tegra_mc_client tegra124_mc_clients[] = { > > + { > > + .id = 0x01, > > + .name = "display0a", > > + .swgroup = TEGRA_SWGROUP_DC, > > + .smmu = { > > + .reg = 0x228, > > + .bit = 1, > > + }, > > + .latency = { > > + .reg = 0x2e8, > > + .shift = 0, > > + .mask = 0xff, > > + .def = 0xc2, > > + }, > > + }, { > > This is a rather long table that I assume would need to get duplicated > and modified for each specific SoC. Have you considered to put the information > into DT instead, as auxiliary data in the iommu specifier as provided by > the device? Most of this data really is register information and I don't think that belongs in DT. Also since this is fixed for a given SoC and in no way configurable (well, with the exception of the .def field above) I don't see any point in parsing this from device tree. Also only the .smmu substruct is immediately relevant to the IOMMU part of the driver. The .swgroup field could possibly also be moved into that substructure since it is only relevant to the IOMMU. So essentially what this table does is map SWGROUPs (which are provided in the IOMMU specifier) to the clients and registers that the IOMMU programming needs. As an analogy it corresponds roughly to the pins and pingroups tables of pinctrl drivers. Those don't belong in device tree either. Thierry
In the future, the EMC driver will also want to write and read quite many registers in the MC block.. MC_EMEM_*, the latency allowance registers and a couple others. Downstream just uses __raw_writel with values from the EMC tables. A fun thing here is that during the point that the values are written, the code cannot do some things like reading registers (I believe) without hanging, so calling into the MC driver to write the changes might not be very nice either. Related to that, reading from MC_EMEM_ADR_CFG is used as a barrier in the sequence. On 26/06/14 23:49, Thierry Reding wrote: > From: Thierry Reding <treding@nvidia.com> > > The memory controller on NVIDIA Tegra124 exposes various knobs that can > be used to tune the behaviour of the clients attached to it. > > Currently this driver sets up the latency allowance registers to the HW > defaults. Eventually an API should be exported by this driver (via a > custom API or a generic subsystem) to allow clients to register latency > requirements. I cannot see where the downstream latency allowance code is reloading the latency allowance registers after a EMC clock rate change. Strange. > > This driver also registers an IOMMU (SMMU) that's implemented by the > memory controller. > > Signed-off-by: Thierry Reding <treding@nvidia.com> > --- > drivers/memory/Kconfig | 9 + > drivers/memory/Makefile | 1 + > drivers/memory/tegra124-mc.c | 1945 ++++++++++++++++++++++++++++++ > include/dt-bindings/memory/tegra124-mc.h | 30 + > 4 files changed, 1985 insertions(+) > create mode 100644 drivers/memory/tegra124-mc.c > create mode 100644 include/dt-bindings/memory/tegra124-mc.h > > diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig > index c59e9c96e86d..d0f0e6781570 100644 > --- a/drivers/memory/Kconfig > +++ b/drivers/memory/Kconfig > @@ -61,6 +61,15 @@ config TEGRA30_MC > analysis, especially for IOMMU/SMMU(System Memory Management > Unit) module. > > +config TEGRA124_MC > + bool "Tegra124 Memory Controller driver" > + depends on ARCH_TEGRA > + select IOMMU_API > + help > + This driver is for the Memory Controller module available on > + Tegra124 SoCs. It provides an IOMMU that can be used for I/O > + virtual address translation. > + > config FSL_IFC > bool > depends on FSL_SOC > diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile > index 71160a2b7313..03143927abab 100644 > --- a/drivers/memory/Makefile > +++ b/drivers/memory/Makefile > @@ -11,3 +11,4 @@ obj-$(CONFIG_FSL_IFC) += fsl_ifc.o > obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o > obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o > obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o > +obj-$(CONFIG_TEGRA124_MC) += tegra124-mc.o > diff --git a/drivers/memory/tegra124-mc.c b/drivers/memory/tegra124-mc.c > new file mode 100644 > index 000000000000..741755b6785d > --- /dev/null > +++ b/drivers/memory/tegra124-mc.c > @@ -0,0 +1,1945 @@ > +/* > + * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/iommu.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > + > +#include <dt-bindings/memory/tegra124-mc.h> > + > +#include <asm/cacheflush.h> > +#ifndef CONFIG_ARM64 > +#include <asm/dma-iommu.h> > +#endif > + > +#define MC_INTSTATUS 0x000 > +#define MC_INT_DECERR_MTS (1 << 16) > +#define MC_INT_SECERR_SEC (1 << 13) > +#define MC_INT_DECERR_VPR (1 << 12) > +#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11) > +#define MC_INT_INVALID_SMMU_PAGE (1 << 10) > +#define MC_INT_ARBITRATION_EMEM (1 << 9) > +#define MC_INT_SECURITY_VIOLATION (1 << 8) > +#define MC_INT_DECERR_EMEM (1 << 6) > +#define MC_INTMASK 0x004 > +#define MC_ERR_STATUS 0x08 > +#define MC_ERR_ADR 0x0c > + > +struct latency_allowance { > + unsigned int reg; > + unsigned int shift; > + unsigned int mask; > + unsigned int def; > +}; > + > +struct smmu_enable { > + unsigned int reg; > + unsigned int bit; > +}; > + > +struct tegra_mc_client { > + unsigned int id; > + const char *name; > + unsigned int swgroup; > + > + struct smmu_enable smmu; > + struct latency_allowance latency; > +}; > + > +static const struct tegra_mc_client tegra124_mc_clients[] = { > + { > + .id = 0x01, > + .name = "display0a", > + .swgroup = TEGRA_SWGROUP_DC, > + .smmu = { > + .reg = 0x228, > + .bit = 1, > + }, > + .latency = { > + .reg = 0x2e8, > + .shift = 0, > + .mask = 0xff, > + .def = 0xc2, > + }, > + }, { > + .id = 0x02, > + .name = "display0ab", > + .swgroup = TEGRA_SWGROUP_DCB, > + .smmu = { > + .reg = 0x228, > + .bit = 2, > + }, > + .latency = { > + .reg = 0x2f4, > + .shift = 0, > + .mask = 0xff, > + .def = 0xc6, > + }, > + }, { > + .id = 0x03, > + .name = "display0b", > + .swgroup = TEGRA_SWGROUP_DC, > + .smmu = { > + .reg = 0x228, > + .bit = 3, > + }, > + .latency = { > + .reg = 0x2e8, > + .shift = 16, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, { > + .id = 0x04, > + .name = "display0bb", > + .swgroup = TEGRA_SWGROUP_DCB, > + .smmu = { > + .reg = 0x228, > + .bit = 4, > + }, > + .latency = { > + .reg = 0x2f4, > + .shift = 16, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, { > + .id = 0x05, > + .name = "display0c", > + .swgroup = TEGRA_SWGROUP_DC, > + .smmu = { > + .reg = 0x228, > + .bit = 5, > + }, > + .latency = { > + .reg = 0x2ec, > + .shift = 0, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, { > + .id = 0x06, > + .name = "display0cb", > + .swgroup = TEGRA_SWGROUP_DCB, > + .smmu = { > + .reg = 0x228, > + .bit = 6, > + }, > + .latency = { > + .reg = 0x2f8, > + .shift = 0, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, { > + .id = 0x0e, > + .name = "afir", > + .swgroup = TEGRA_SWGROUP_AFI, > + .smmu = { > + .reg = 0x228, > + .bit = 14, > + }, > + .latency = { > + .reg = 0x2e0, > + .shift = 0, > + .mask = 0xff, > + .def = 0x13, > + }, > + }, { > + .id = 0x0f, > + .name = "avpcarm7r", > + .swgroup = TEGRA_SWGROUP_AVPC, > + .smmu = { > + .reg = 0x228, > + .bit = 15, > + }, > + .latency = { > + .reg = 0x2e4, > + .shift = 0, > + .mask = 0xff, > + .def = 0x04, > + }, > + }, { > + .id = 0x10, > + .name = "displayhc", > + .swgroup = TEGRA_SWGROUP_DC, > + .smmu = { > + .reg = 0x228, > + .bit = 16, > + }, > + .latency = { > + .reg = 0x2f0, > + .shift = 0, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, { > + .id = 0x11, > + .name = "displayhcb", > + .swgroup = TEGRA_SWGROUP_DCB, > + .smmu = { > + .reg = 0x228, > + .bit = 17, > + }, > + .latency = { > + .reg = 0x2fc, > + .shift = 0, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, { > + .id = 0x15, > + .name = "hdar", > + .swgroup = TEGRA_SWGROUP_HDA, > + .smmu = { > + .reg = 0x228, > + .bit = 21, > + }, > + .latency = { > + .reg = 0x318, > + .shift = 0, > + .mask = 0xff, > + .def = 0x24, > + }, > + }, { > + .id = 0x16, > + .name = "host1xdmar", > + .swgroup = TEGRA_SWGROUP_HC, > + .smmu = { > + .reg = 0x228, > + .bit = 22, > + }, > + .latency = { > + .reg = 0x310, > + .shift = 0, > + .mask = 0xff, > + .def = 0x1e, > + }, > + }, { > + .id = 0x17, > + .name = "host1xr", > + .swgroup = TEGRA_SWGROUP_HC, > + .smmu = { > + .reg = 0x228, > + .bit = 23, > + }, > + .latency = { > + .reg = 0x310, > + .shift = 16, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, { > + .id = 0x1c, > + .name = "msencsrd", > + .swgroup = TEGRA_SWGROUP_MSENC, > + .smmu = { > + .reg = 0x228, > + .bit = 28, > + }, > + .latency = { > + .reg = 0x328, > + .shift = 0, > + .mask = 0xff, > + .def = 0x23, > + }, > + }, { > + .id = 0x1d, > + .name = "ppcsahbdmarhdar", > + .swgroup = TEGRA_SWGROUP_PPCS, > + .smmu = { > + .reg = 0x228, > + .bit = 29, > + }, > + .latency = { > + .reg = 0x344, > + .shift = 0, > + .mask = 0xff, > + .def = 0x49, > + }, > + }, { > + .id = 0x1e, > + .name = "ppcsahbslvr", > + .swgroup = TEGRA_SWGROUP_PPCS, > + .smmu = { > + .reg = 0x228, > + .bit = 30, > + }, > + .latency = { > + .reg = 0x344, > + .shift = 16, > + .mask = 0xff, > + .def = 0x1a, > + }, > + }, { > + .id = 0x1f, > + .name = "satar", > + .swgroup = TEGRA_SWGROUP_SATA, > + .smmu = { > + .reg = 0x228, > + .bit = 31, > + }, > + .latency = { > + .reg = 0x350, > + .shift = 0, > + .mask = 0xff, > + .def = 0x65, > + }, > + }, { > + .id = 0x22, > + .name = "vdebsevr", > + .swgroup = TEGRA_SWGROUP_VDE, > + .smmu = { > + .reg = 0x22c, > + .bit = 2, > + }, > + .latency = { > + .reg = 0x354, > + .shift = 0, > + .mask = 0xff, > + .def = 0x4f, > + }, > + }, { > + .id = 0x23, > + .name = "vdember", > + .swgroup = TEGRA_SWGROUP_VDE, > + .smmu = { > + .reg = 0x22c, > + .bit = 3, > + }, > + .latency = { > + .reg = 0x354, > + .shift = 16, > + .mask = 0xff, > + .def = 0x3d, > + }, > + }, { > + .id = 0x24, > + .name = "vdemcer", > + .swgroup = TEGRA_SWGROUP_VDE, > + .smmu = { > + .reg = 0x22c, > + .bit = 4, > + }, > + .latency = { > + .reg = 0x358, > + .shift = 0, > + .mask = 0xff, > + .def = 0x66, > + }, > + }, { > + .id = 0x25, > + .name = "vdetper", > + .swgroup = TEGRA_SWGROUP_VDE, > + .smmu = { > + .reg = 0x22c, > + .bit = 5, > + }, > + .latency = { > + .reg = 0x358, > + .shift = 16, > + .mask = 0xff, > + .def = 0xa5, > + }, > + }, { > + .id = 0x26, > + .name = "mpcorelpr", > + .swgroup = TEGRA_SWGROUP_MPCORELP, > + .latency = { > + .reg = 0x324, > + .shift = 0, > + .mask = 0xff, > + .def = 0x04, > + }, > + }, { > + .id = 0x27, > + .name = "mpcorer", > + .swgroup = TEGRA_SWGROUP_MPCORE, > + .smmu = { > + .reg = 0x22c, > + .bit = 2, > + }, > + .latency = { > + .reg = 0x320, > + .shift = 0, > + .mask = 0xff, > + .def = 0x04, > + }, > + }, { > + .id = 0x2b, > + .name = "msencswr", > + .swgroup = TEGRA_SWGROUP_MSENC, > + .smmu = { > + .reg = 0x22c, > + .bit = 11, > + }, > + .latency = { > + .reg = 0x328, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x31, > + .name = "afiw", > + .swgroup = TEGRA_SWGROUP_AFI, > + .smmu = { > + .reg = 0x22c, > + .bit = 17, > + }, > + .latency = { > + .reg = 0x2e0, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x32, > + .name = "avpcarm7w", > + .swgroup = TEGRA_SWGROUP_AVPC, > + .smmu = { > + .reg = 0x22c, > + .bit = 18, > + }, > + .latency = { > + .reg = 0x2e4, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x35, > + .name = "hdaw", > + .swgroup = TEGRA_SWGROUP_HDA, > + .smmu = { > + .reg = 0x22c, > + .bit = 21, > + }, > + .latency = { > + .reg = 0x318, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x36, > + .name = "host1xw", > + .swgroup = TEGRA_SWGROUP_HC, > + .smmu = { > + .reg = 0x22c, > + .bit = 22, > + }, > + .latency = { > + .reg = 0x314, > + .shift = 0, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x38, > + .name = "mpcorelpw", > + .swgroup = TEGRA_SWGROUP_MPCORELP, > + .latency = { > + .reg = 0x324, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x39, > + .name = "mpcorew", > + .swgroup = TEGRA_SWGROUP_MPCORE, > + .latency = { > + .reg = 0x320, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x3b, > + .name = "ppcsahbdmaw", > + .swgroup = TEGRA_SWGROUP_PPCS, > + .smmu = { > + .reg = 0x22c, > + .bit = 27, > + }, > + .latency = { > + .reg = 0x348, > + .shift = 0, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x3c, > + .name = "ppcsahbslvw", > + .swgroup = TEGRA_SWGROUP_PPCS, > + .smmu = { > + .reg = 0x22c, > + .bit = 28, > + }, > + .latency = { > + .reg = 0x348, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x3d, > + .name = "sataw", > + .swgroup = TEGRA_SWGROUP_SATA, > + .smmu = { > + .reg = 0x22c, > + .bit = 29, > + }, > + .latency = { > + .reg = 0x350, > + .shift = 16, > + .mask = 0xff, > + .def = 0x65, > + }, > + }, { > + .id = 0x3e, > + .name = "vdebsevw", > + .swgroup = TEGRA_SWGROUP_VDE, > + .smmu = { > + .reg = 0x22c, > + .bit = 30, > + }, > + .latency = { > + .reg = 0x35c, > + .shift = 0, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x3f, > + .name = "vdedbgw", > + .swgroup = TEGRA_SWGROUP_VDE, > + .smmu = { > + .reg = 0x22c, > + .bit = 31, > + }, > + .latency = { > + .reg = 0x35c, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x40, > + .name = "vdembew", > + .swgroup = TEGRA_SWGROUP_VDE, > + .smmu = { > + .reg = 0x230, > + .bit = 0, > + }, > + .latency = { > + .reg = 0x360, > + .shift = 0, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x41, > + .name = "vdetpmw", > + .swgroup = TEGRA_SWGROUP_VDE, > + .smmu = { > + .reg = 0x230, > + .bit = 1, > + }, > + .latency = { > + .reg = 0x360, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x44, > + .name = "ispra", > + .swgroup = TEGRA_SWGROUP_ISP2, > + .smmu = { > + .reg = 0x230, > + .bit = 4, > + }, > + .latency = { > + .reg = 0x370, > + .shift = 0, > + .mask = 0xff, > + .def = 0x18, > + }, > + }, { > + .id = 0x46, > + .name = "ispwa", > + .swgroup = TEGRA_SWGROUP_ISP2, > + .smmu = { > + .reg = 0x230, > + .bit = 6, > + }, > + .latency = { > + .reg = 0x374, > + .shift = 0, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x47, > + .name = "ispwb", > + .swgroup = TEGRA_SWGROUP_ISP2, > + .smmu = { > + .reg = 0x230, > + .bit = 7, > + }, > + .latency = { > + .reg = 0x374, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x4a, > + .name = "xusb_hostr", > + .swgroup = TEGRA_SWGROUP_XUSB_HOST, > + .smmu = { > + .reg = 0x230, > + .bit = 10, > + }, > + .latency = { > + .reg = 0x37c, > + .shift = 0, > + .mask = 0xff, > + .def = 0x39, > + }, > + }, { > + .id = 0x4b, > + .name = "xusb_hostw", > + .swgroup = TEGRA_SWGROUP_XUSB_HOST, > + .smmu = { > + .reg = 0x230, > + .bit = 11, > + }, > + .latency = { > + .reg = 0x37c, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x4c, > + .name = "xusb_devr", > + .swgroup = TEGRA_SWGROUP_XUSB_DEV, > + .smmu = { > + .reg = 0x230, > + .bit = 12, > + }, > + .latency = { > + .reg = 0x380, > + .shift = 0, > + .mask = 0xff, > + .def = 0x39, > + }, > + }, { > + .id = 0x4d, > + .name = "xusb_devw", > + .swgroup = TEGRA_SWGROUP_XUSB_DEV, > + .smmu = { > + .reg = 0x230, > + .bit = 13, > + }, > + .latency = { > + .reg = 0x380, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x4e, > + .name = "isprab", > + .swgroup = TEGRA_SWGROUP_ISP2B, > + .smmu = { > + .reg = 0x230, > + .bit = 14, > + }, > + .latency = { > + .reg = 0x384, > + .shift = 0, > + .mask = 0xff, > + .def = 0x18, > + }, > + }, { > + .id = 0x50, > + .name = "ispwab", > + .swgroup = TEGRA_SWGROUP_ISP2B, > + .smmu = { > + .reg = 0x230, > + .bit = 16, > + }, > + .latency = { > + .reg = 0x388, > + .shift = 0, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x51, > + .name = "ispwbb", > + .swgroup = TEGRA_SWGROUP_ISP2B, > + .smmu = { > + .reg = 0x230, > + .bit = 17, > + }, > + .latency = { > + .reg = 0x388, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x54, > + .name = "tsecsrd", > + .swgroup = TEGRA_SWGROUP_TSEC, > + .smmu = { > + .reg = 0x230, > + .bit = 20, > + }, > + .latency = { > + .reg = 0x390, > + .shift = 0, > + .mask = 0xff, > + .def = 0x9b, > + }, > + }, { > + .id = 0x55, > + .name = "tsecswr", > + .swgroup = TEGRA_SWGROUP_TSEC, > + .smmu = { > + .reg = 0x230, > + .bit = 21, > + }, > + .latency = { > + .reg = 0x390, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x56, > + .name = "a9avpscr", > + .swgroup = TEGRA_SWGROUP_A9AVP, > + .smmu = { > + .reg = 0x230, > + .bit = 22, > + }, > + .latency = { > + .reg = 0x3a4, > + .shift = 0, > + .mask = 0xff, > + .def = 0x04, > + }, > + }, { > + .id = 0x57, > + .name = "a9avpscw", > + .swgroup = TEGRA_SWGROUP_A9AVP, > + .smmu = { > + .reg = 0x230, > + .bit = 23, > + }, > + .latency = { > + .reg = 0x3a4, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x58, > + .name = "gpusrd", > + .swgroup = TEGRA_SWGROUP_GPU, > + .smmu = { > + /* read-only */ > + .reg = 0x230, > + .bit = 24, > + }, > + .latency = { > + .reg = 0x3c8, > + .shift = 0, > + .mask = 0xff, > + .def = 0x1a, > + }, > + }, { > + .id = 0x59, > + .name = "gpuswr", > + .swgroup = TEGRA_SWGROUP_GPU, > + .smmu = { > + /* read-only */ > + .reg = 0x230, > + .bit = 25, > + }, > + .latency = { > + .reg = 0x3c8, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x5a, > + .name = "displayt", > + .swgroup = TEGRA_SWGROUP_DC, > + .smmu = { > + .reg = 0x230, > + .bit = 26, > + }, > + .latency = { > + .reg = 0x2f0, > + .shift = 16, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, { > + .id = 0x60, > + .name = "sdmmcra", > + .swgroup = TEGRA_SWGROUP_SDMMC1A, > + .smmu = { > + .reg = 0x234, > + .bit = 0, > + }, > + .latency = { > + .reg = 0x3b8, > + .shift = 0, > + .mask = 0xff, > + .def = 0x49, > + }, > + }, { > + .id = 0x61, > + .name = "sdmmcraa", > + .swgroup = TEGRA_SWGROUP_SDMMC2A, > + .smmu = { > + .reg = 0x234, > + .bit = 1, > + }, > + .latency = { > + .reg = 0x3bc, > + .shift = 0, > + .mask = 0xff, > + .def = 0x49, > + }, > + }, { > + .id = 0x62, > + .name = "sdmmcr", > + .swgroup = TEGRA_SWGROUP_SDMMC3A, > + .smmu = { > + .reg = 0x234, > + .bit = 2, > + }, > + .latency = { > + .reg = 0x3c0, > + .shift = 0, > + .mask = 0xff, > + .def = 0x49, > + }, > + }, { > + .id = 0x63, > + .swgroup = TEGRA_SWGROUP_SDMMC4A, > + .name = "sdmmcrab", > + .smmu = { > + .reg = 0x234, > + .bit = 3, > + }, > + .latency = { > + .reg = 0x3c4, > + .shift = 0, > + .mask = 0xff, > + .def = 0x49, > + }, > + }, { > + .id = 0x64, > + .name = "sdmmcwa", > + .swgroup = TEGRA_SWGROUP_SDMMC1A, > + .smmu = { > + .reg = 0x234, > + .bit = 4, > + }, > + .latency = { > + .reg = 0x3b8, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x65, > + .name = "sdmmcwaa", > + .swgroup = TEGRA_SWGROUP_SDMMC2A, > + .smmu = { > + .reg = 0x234, > + .bit = 5, > + }, > + .latency = { > + .reg = 0x3bc, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x66, > + .name = "sdmmcw", > + .swgroup = TEGRA_SWGROUP_SDMMC3A, > + .smmu = { > + .reg = 0x234, > + .bit = 6, > + }, > + .latency = { > + .reg = 0x3c0, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x67, > + .name = "sdmmcwab", > + .swgroup = TEGRA_SWGROUP_SDMMC4A, > + .smmu = { > + .reg = 0x234, > + .bit = 7, > + }, > + .latency = { > + .reg = 0x3c4, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x6c, > + .name = "vicsrd", > + .swgroup = TEGRA_SWGROUP_VIC, > + .smmu = { > + .reg = 0x234, > + .bit = 12, > + }, > + .latency = { > + .reg = 0x394, > + .shift = 0, > + .mask = 0xff, > + .def = 0x1a, > + }, > + }, { > + .id = 0x6d, > + .name = "vicswr", > + .swgroup = TEGRA_SWGROUP_VIC, > + .smmu = { > + .reg = 0x234, > + .bit = 13, > + }, > + .latency = { > + .reg = 0x394, > + .shift = 16, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x72, > + .name = "viw", > + .swgroup = TEGRA_SWGROUP_VI, > + .smmu = { > + .reg = 0x234, > + .bit = 18, > + }, > + .latency = { > + .reg = 0x398, > + .shift = 0, > + .mask = 0xff, > + .def = 0x80, > + }, > + }, { > + .id = 0x73, > + .name = "displayd", > + .swgroup = TEGRA_SWGROUP_DC, > + .smmu = { > + .reg = 0x234, > + .bit = 19, > + }, > + .latency = { > + .reg = 0x3c8, > + .shift = 0, > + .mask = 0xff, > + .def = 0x50, > + }, > + }, > +}; > + > +struct tegra_smmu_swgroup { > + unsigned int swgroup; > + unsigned int reg; > +}; > + > +static const struct tegra_smmu_swgroup tegra124_swgroups[] = { > + { .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, > + { .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, > + { .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 }, > + { .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, > + { .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 }, > + { .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 }, > + { .swgroup = TEGRA_SWGROUP_MSENC, .reg = 0x264 }, > + { .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 }, > + { .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x274 }, > + { .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c }, > + { .swgroup = TEGRA_SWGROUP_ISP2, .reg = 0x258 }, > + { .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 }, > + { .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c }, > + { .swgroup = TEGRA_SWGROUP_ISP2B, .reg = 0xaa4 }, > + { .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 }, > + { .swgroup = TEGRA_SWGROUP_A9AVP, .reg = 0x290 }, > + { .swgroup = TEGRA_SWGROUP_GPU, .reg = 0xaa8 }, > + { .swgroup = TEGRA_SWGROUP_SDMMC1A, .reg = 0xa94 }, > + { .swgroup = TEGRA_SWGROUP_SDMMC2A, .reg = 0xa98 }, > + { .swgroup = TEGRA_SWGROUP_SDMMC3A, .reg = 0xa9c }, > + { .swgroup = TEGRA_SWGROUP_SDMMC4A, .reg = 0xaa0 }, > + { .swgroup = TEGRA_SWGROUP_VIC, .reg = 0x284 }, > + { .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 }, > +}; > + > +struct tegra_smmu_group_init { > + unsigned int asid; > + const char *name; > + > + const struct of_device_id *matches; > +}; > + > +struct tegra_smmu_soc { > + const struct tegra_smmu_group_init *groups; > + unsigned int num_groups; > + > + const struct tegra_mc_client *clients; > + unsigned int num_clients; > + > + const struct tegra_smmu_swgroup *swgroups; > + unsigned int num_swgroups; > + > + unsigned int num_asids; > + unsigned int atom_size; > + > + const struct tegra_smmu_ops *ops; > +}; > + > +struct tegra_smmu_ops { > + void (*flush_dcache)(struct page *page, unsigned long offset, > + size_t size); > +}; > + > +struct tegra_smmu_master { > + struct list_head list; > + struct device *dev; > +}; > + > +struct tegra_smmu_group { > + const char *name; > + const struct of_device_id *matches; > + unsigned int asid; > + > +#ifndef CONFIG_ARM64 > + struct dma_iommu_mapping *mapping; > +#endif > + struct list_head masters; > +}; > + > +static const struct of_device_id tegra124_periph_matches[] = { > + { .compatible = "nvidia,tegra124-sdhci", }, > + { } > +}; > + > +static const struct tegra_smmu_group_init tegra124_smmu_groups[] = { > + { 0, "peripherals", tegra124_periph_matches }, > +}; > + > +static void tegra_smmu_group_release(void *data) > +{ > + kfree(data); > +} > + > +struct tegra_smmu { > + void __iomem *regs; > + struct iommu iommu; > + struct device *dev; > + > + const struct tegra_smmu_soc *soc; > + > + struct iommu_group **groups; > + unsigned int num_groups; > + > + unsigned long *asids; > + struct mutex lock; > +}; > + > +struct tegra_smmu_address_space { > + struct iommu_domain *domain; > + struct tegra_smmu *smmu; > + struct page *pd; > + unsigned id; > + u32 attr; > +}; > + > +static inline void smmu_writel(struct tegra_smmu *smmu, u32 value, > + unsigned long offset) > +{ > + writel(value, smmu->regs + offset); > +} > + > +static inline u32 smmu_readl(struct tegra_smmu *smmu, unsigned long offset) > +{ > + return readl(smmu->regs + offset); > +} > + > +#define SMMU_CONFIG 0x010 > +#define SMMU_CONFIG_ENABLE (1 << 0) > + > +#define SMMU_PTB_ASID 0x01c > +#define SMMU_PTB_ASID_VALUE(x) ((x) & 0x7f) > + > +#define SMMU_PTB_DATA 0x020 > +#define SMMU_PTB_DATA_VALUE(page, attr) (page_to_phys(page) >> 12 | (attr)) > + > +#define SMMU_MK_PDE(page, attr) (page_to_phys(page) >> SMMU_PTE_SHIFT | (attr)) > + > +#define SMMU_TLB_FLUSH 0x030 > +#define SMMU_TLB_FLUSH_VA_MATCH_ALL (0 << 0) > +#define SMMU_TLB_FLUSH_VA_MATCH_SECTION (2 << 0) > +#define SMMU_TLB_FLUSH_VA_MATCH_GROUP (3 << 0) > +#define SMMU_TLB_FLUSH_ASID(x) (((x) & 0x7f) << 24) > +#define SMMU_TLB_FLUSH_VA_SECTION(addr) ((((addr) & 0xffc00000) >> 12) | \ > + SMMU_TLB_FLUSH_VA_MATCH_SECTION) > +#define SMMU_TLB_FLUSH_VA_GROUP(addr) ((((addr) & 0xffffc000) >> 12) | \ > + SMMU_TLB_FLUSH_VA_MATCH_GROUP) > +#define SMMU_TLB_FLUSH_ASID_MATCH (1 << 31) > + > +#define SMMU_PTC_FLUSH 0x034 > +#define SMMU_PTC_FLUSH_TYPE_ALL (0 << 0) > +#define SMMU_PTC_FLUSH_TYPE_ADR (1 << 0) > + > +#define SMMU_PTC_FLUSH_HI 0x9b8 > +#define SMMU_PTC_FLUSH_HI_MASK 0x3 > + > +/* per-SWGROUP SMMU_*_ASID register */ > +#define SMMU_ASID_ENABLE (1 << 31) > +#define SMMU_ASID_MASK 0x7f > +#define SMMU_ASID_VALUE(x) ((x) & SMMU_ASID_MASK) > + > +/* page table definitions */ > +#define SMMU_NUM_PDE 1024 > +#define SMMU_NUM_PTE 1024 > + > +#define SMMU_SIZE_PD (SMMU_NUM_PDE * 4) > +#define SMMU_SIZE_PT (SMMU_NUM_PTE * 4) > + > +#define SMMU_PDE_SHIFT 22 > +#define SMMU_PTE_SHIFT 12 > + > +#define SMMU_PFN_MASK 0x000fffff > + > +#define SMMU_PD_READABLE (1 << 31) > +#define SMMU_PD_WRITABLE (1 << 30) > +#define SMMU_PD_NONSECURE (1 << 29) > + > +#define SMMU_PDE_READABLE (1 << 31) > +#define SMMU_PDE_WRITABLE (1 << 30) > +#define SMMU_PDE_NONSECURE (1 << 29) > +#define SMMU_PDE_NEXT (1 << 28) > + > +#define SMMU_PTE_READABLE (1 << 31) > +#define SMMU_PTE_WRITABLE (1 << 30) > +#define SMMU_PTE_NONSECURE (1 << 29) > + > +#define SMMU_PDE_ATTR (SMMU_PDE_READABLE | SMMU_PDE_WRITABLE | \ > + SMMU_PDE_NONSECURE) > +#define SMMU_PTE_ATTR (SMMU_PTE_READABLE | SMMU_PTE_WRITABLE | \ > + SMMU_PTE_NONSECURE) > + > +#define SMMU_PDE_VACANT(n) (((n) << 10) | SMMU_PDE_ATTR) > +#define SMMU_PTE_VACANT(n) (((n) << 12) | SMMU_PTE_ATTR) > + > +#ifdef CONFIG_ARCH_TEGRA_124_SOC > +static void tegra124_flush_dcache(struct page *page, unsigned long offset, > + size_t size) > +{ > + phys_addr_t phys = page_to_phys(page) + offset; > + void *virt = page_address(page) + offset; > + > + __cpuc_flush_dcache_area(virt, size); > + outer_flush_range(phys, phys + size); > +} > + > +static const struct tegra_smmu_ops tegra124_smmu_ops = { > + .flush_dcache = tegra124_flush_dcache, > +}; > +#endif > + > +static void tegra132_flush_dcache(struct page *page, unsigned long offset, > + size_t size) > +{ > + /* TODO: implement */ > +} > + > +static const struct tegra_smmu_ops tegra132_smmu_ops = { > + .flush_dcache = tegra132_flush_dcache, > +}; > + > +static inline void smmu_flush_ptc(struct tegra_smmu *smmu, struct page *page, > + unsigned long offset) > +{ > + phys_addr_t phys = page ? page_to_phys(page) : 0; > + u32 value; > + > + if (page) { > + offset &= ~(smmu->soc->atom_size - 1); > + > +#ifdef CONFIG_PHYS_ADDR_T_64BIT > + value = (phys >> 32) & SMMU_PTC_FLUSH_HI_MASK; > +#else > + value = 0; > +#endif > + smmu_writel(smmu, value, SMMU_PTC_FLUSH_HI); > + > + value = (phys + offset) | SMMU_PTC_FLUSH_TYPE_ADR; > + } else { > + value = SMMU_PTC_FLUSH_TYPE_ALL; > + } > + > + smmu_writel(smmu, value, SMMU_PTC_FLUSH); > +} > + > +static inline void smmu_flush_tlb(struct tegra_smmu *smmu) > +{ > + smmu_writel(smmu, SMMU_TLB_FLUSH_VA_MATCH_ALL, SMMU_TLB_FLUSH); > +} > + > +static inline void smmu_flush_tlb_asid(struct tegra_smmu *smmu, > + unsigned long asid) > +{ > + u32 value; > + > + value = SMMU_TLB_FLUSH_ASID_MATCH | SMMU_TLB_FLUSH_ASID(asid) | > + SMMU_TLB_FLUSH_VA_MATCH_ALL; > + smmu_writel(smmu, value, SMMU_TLB_FLUSH); > +} > + > +static inline void smmu_flush_tlb_section(struct tegra_smmu *smmu, > + unsigned long asid, > + unsigned long iova) > +{ > + u32 value; > + > + value = SMMU_TLB_FLUSH_ASID_MATCH | SMMU_TLB_FLUSH_ASID(asid) | > + SMMU_TLB_FLUSH_VA_SECTION(iova); > + smmu_writel(smmu, value, SMMU_TLB_FLUSH); > +} > + > +static inline void smmu_flush_tlb_group(struct tegra_smmu *smmu, > + unsigned long asid, > + unsigned long iova) > +{ > + u32 value; > + > + value = SMMU_TLB_FLUSH_ASID_MATCH | SMMU_TLB_FLUSH_ASID(asid) | > + SMMU_TLB_FLUSH_VA_GROUP(iova); > + smmu_writel(smmu, value, SMMU_TLB_FLUSH); > +} > + > +static inline void smmu_flush(struct tegra_smmu *smmu) > +{ > + smmu_readl(smmu, SMMU_CONFIG); > +} > + > +static inline struct tegra_smmu *to_tegra_smmu(struct iommu *iommu) > +{ > + return container_of(iommu, struct tegra_smmu, iommu); > +} > + > +static struct tegra_smmu *smmu_handle = NULL; > + > +static int tegra_smmu_alloc_asid(struct tegra_smmu *smmu, unsigned int *idp) > +{ > + unsigned long id; > + > + mutex_lock(&smmu->lock); > + > + id = find_first_zero_bit(smmu->asids, smmu->soc->num_asids); > + if (id >= smmu->soc->num_asids) { > + mutex_unlock(&smmu->lock); > + return -ENOSPC; > + } > + > + set_bit(id, smmu->asids); > + *idp = id; > + > + mutex_unlock(&smmu->lock); > + return 0; > +} > + > +static void tegra_smmu_free_asid(struct tegra_smmu *smmu, unsigned int id) > +{ > + mutex_lock(&smmu->lock); > + clear_bit(id, smmu->asids); > + mutex_unlock(&smmu->lock); > +} > + > +struct tegra_smmu_address_space *foo = NULL; > + > +static int tegra_smmu_domain_init(struct iommu_domain *domain) > +{ > + struct tegra_smmu *smmu = smmu_handle; > + struct tegra_smmu_address_space *as; > + uint32_t *pd, value; > + unsigned int i; > + int err = 0; > + > + as = kzalloc(sizeof(*as), GFP_KERNEL); > + if (!as) { > + err = -ENOMEM; > + goto out; > + } > + > + as->attr = SMMU_PD_READABLE | SMMU_PD_WRITABLE | SMMU_PD_NONSECURE; > + as->smmu = smmu_handle; > + as->domain = domain; > + > + err = tegra_smmu_alloc_asid(smmu, &as->id); > + if (err < 0) { > + kfree(as); > + goto out; > + } > + > + as->pd = alloc_page(GFP_KERNEL | __GFP_DMA); > + if (!as->pd) { > + err = -ENOMEM; > + goto out; > + } > + > + pd = page_address(as->pd); > + SetPageReserved(as->pd); > + > + for (i = 0; i < SMMU_NUM_PDE; i++) > + pd[i] = SMMU_PDE_VACANT(i); > + > + smmu->soc->ops->flush_dcache(as->pd, 0, SMMU_SIZE_PD); > + smmu_flush_ptc(smmu, as->pd, 0); > + smmu_flush_tlb_asid(smmu, as->id); > + > + smmu_writel(smmu, as->id & 0x7f, SMMU_PTB_ASID); > + value = SMMU_PTB_DATA_VALUE(as->pd, as->attr); > + smmu_writel(smmu, value, SMMU_PTB_DATA); > + smmu_flush(smmu); > + > + domain->priv = as; > + > + return 0; > + > +out: > + return err; > +} > + > +static void tegra_smmu_domain_destroy(struct iommu_domain *domain) > +{ > + struct tegra_smmu_address_space *as = domain->priv; > + > + /* TODO: free page directory and page tables */ > + > + tegra_smmu_free_asid(as->smmu, as->id); > + kfree(as); > +} > + > +static const struct tegra_smmu_swgroup * > +tegra_smmu_find_swgroup(struct tegra_smmu *smmu, unsigned int swgroup) > +{ > + const struct tegra_smmu_swgroup *group = NULL; > + unsigned int i; > + > + for (i = 0; i < smmu->soc->num_swgroups; i++) { > + if (smmu->soc->swgroups[i].swgroup == swgroup) { > + group = &smmu->soc->swgroups[i]; > + break; > + } > + } > + > + return group; > +} > + > +static int tegra_smmu_enable(struct tegra_smmu *smmu, unsigned int swgroup, > + unsigned int asid) > +{ > + const struct tegra_smmu_swgroup *group; > + unsigned int i; > + u32 value; > + > + for (i = 0; i < smmu->soc->num_clients; i++) { > + const struct tegra_mc_client *client = &smmu->soc->clients[i]; > + > + if (client->swgroup != swgroup) > + continue; > + > + value = smmu_readl(smmu, client->smmu.reg); > + value |= BIT(client->smmu.bit); > + smmu_writel(smmu, value, client->smmu.reg); > + } > + > + group = tegra_smmu_find_swgroup(smmu, swgroup); > + if (group) { > + value = smmu_readl(smmu, group->reg); > + value &= ~SMMU_ASID_MASK; > + value |= SMMU_ASID_VALUE(asid); > + value |= SMMU_ASID_ENABLE; > + smmu_writel(smmu, value, group->reg); > + } > + > + return 0; > +} > + > +static int tegra_smmu_disable(struct tegra_smmu *smmu, unsigned int swgroup, > + unsigned int asid) > +{ > + const struct tegra_smmu_swgroup *group; > + unsigned int i; > + u32 value; > + > + group = tegra_smmu_find_swgroup(smmu, swgroup); > + if (group) { > + value = smmu_readl(smmu, group->reg); > + value &= ~SMMU_ASID_MASK; > + value |= SMMU_ASID_VALUE(asid); > + value &= ~SMMU_ASID_ENABLE; > + smmu_writel(smmu, value, group->reg); > + } > + > + for (i = 0; i < smmu->soc->num_clients; i++) { > + const struct tegra_mc_client *client = &smmu->soc->clients[i]; > + > + if (client->swgroup != swgroup) > + continue; > + > + value = smmu_readl(smmu, client->smmu.reg); > + value &= ~BIT(client->smmu.bit); > + smmu_writel(smmu, value, client->smmu.reg); > + } > + > + return 0; > +} > + > +static int tegra_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) > +{ > + struct tegra_smmu_address_space *as = domain->priv; > + struct tegra_smmu *smmu = as->smmu; > + struct of_phandle_iter entry; > + int err; > + > + of_property_for_each_phandle_with_args(entry, dev->of_node, "iommus", > + "#iommu-cells", 0) { > + unsigned int swgroup = entry.out_args.args[0]; > + > + if (entry.out_args.np != smmu->dev->of_node) > + continue; > + > + err = tegra_smmu_enable(smmu, swgroup, as->id); > + if (err < 0) > + pr_err("failed to enable SWGROUP#%u\n", swgroup); > + } > + > + return 0; > +} > + > +static void tegra_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) > +{ > + struct tegra_smmu_address_space *as = domain->priv; > + struct tegra_smmu *smmu = as->smmu; > + struct of_phandle_iter entry; > + int err; > + > + of_property_for_each_phandle_with_args(entry, dev->of_node, "iommus", > + "#iommu-cells", 0) { > + unsigned int swgroup; > + > + if (entry.out_args.np != smmu->dev->of_node) > + continue; > + > + swgroup = entry.out_args.args[0]; > + > + err = tegra_smmu_disable(smmu, swgroup, as->id); > + if (err < 0) { > + pr_err("failed to enable SWGROUP#%u\n", swgroup); > + } > + } > +} > + > +static u32 *as_get_pte(struct tegra_smmu_address_space *as, dma_addr_t iova, > + struct page **pagep) > +{ > + struct tegra_smmu *smmu = smmu_handle; > + u32 *pd = page_address(as->pd), *pt; > + u32 pde = (iova >> SMMU_PDE_SHIFT) & 0x3ff; > + u32 pte = (iova >> SMMU_PTE_SHIFT) & 0x3ff; > + struct page *page; > + unsigned int i; > + > + if (pd[pde] != SMMU_PDE_VACANT(pde)) { > + page = pfn_to_page(pd[pde] & SMMU_PFN_MASK); > + pt = page_address(page); > + } else { > + page = alloc_page(GFP_KERNEL | __GFP_DMA); > + if (!page) > + return NULL; > + > + pt = page_address(page); > + SetPageReserved(page); > + > + for (i = 0; i < SMMU_NUM_PTE; i++) > + pt[i] = SMMU_PTE_VACANT(i); > + > + smmu->soc->ops->flush_dcache(page, 0, SMMU_SIZE_PT); > + > + pd[pde] = SMMU_MK_PDE(page, SMMU_PDE_ATTR | SMMU_PDE_NEXT); > + > + smmu->soc->ops->flush_dcache(as->pd, pde << 2, 4); > + smmu_flush_ptc(smmu, as->pd, pde << 2); > + smmu_flush_tlb_section(smmu, as->id, iova); > + smmu_flush(smmu); > + } > + > + *pagep = page; > + > + return &pt[pte]; > +} > + > +static int tegra_smmu_map(struct iommu_domain *domain, unsigned long iova, > + phys_addr_t paddr, size_t size, int prot) > +{ > + struct tegra_smmu_address_space *as = domain->priv; > + struct tegra_smmu *smmu = smmu_handle; > + unsigned long offset; > + struct page *page; > + u32 *pte; > + > + pte = as_get_pte(as, iova, &page); > + if (!pte) > + return -ENOMEM; > + > + offset = offset_in_page(pte); > + > + *pte = __phys_to_pfn(paddr) | SMMU_PTE_ATTR; > + > + smmu->soc->ops->flush_dcache(page, offset, 4); > + smmu_flush_ptc(smmu, page, offset); > + smmu_flush_tlb_group(smmu, as->id, iova); > + smmu_flush(smmu); > + > + return 0; > +} > + > +static size_t tegra_smmu_unmap(struct iommu_domain *domain, unsigned long iova, > + size_t size) > +{ > + struct tegra_smmu_address_space *as = domain->priv; > + struct tegra_smmu *smmu = smmu_handle; > + unsigned long offset; > + struct page *page; > + u32 *pte; > + > + pte = as_get_pte(as, iova, &page); > + if (!pte) > + return 0; > + > + offset = offset_in_page(pte); > + *pte = 0; > + > + smmu->soc->ops->flush_dcache(page, offset, 4); > + smmu_flush_ptc(smmu, page, offset); > + smmu_flush_tlb_group(smmu, as->id, iova); > + smmu_flush(smmu); > + > + return size; > +} > + > +static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain, > + dma_addr_t iova) > +{ > + struct tegra_smmu_address_space *as = domain->priv; > + struct page *page; > + unsigned long pfn; > + u32 *pte; > + > + pte = as_get_pte(as, iova, &page); > + pfn = *pte & SMMU_PFN_MASK; > + > + return PFN_PHYS(pfn); > +} > + > +static int tegra_smmu_attach(struct iommu *iommu, struct device *dev) > +{ > + struct tegra_smmu *smmu = to_tegra_smmu(iommu); > + struct tegra_smmu_group *group; > + unsigned int i; > + > + for (i = 0; i < smmu->soc->num_groups; i++) { > + group = iommu_group_get_iommudata(smmu->groups[i]); > + > + if (of_match_node(group->matches, dev->of_node)) { > + pr_debug("adding device %s to group %s\n", > + dev_name(dev), group->name); > + iommu_group_add_device(smmu->groups[i], dev); > + break; > + } > + } > + > + if (i == smmu->soc->num_groups) > + return 0; > + > +#ifndef CONFIG_ARM64 > + return arm_iommu_attach_device(dev, group->mapping); > +#else > + return 0; > +#endif > +} > + > +static int tegra_smmu_detach(struct iommu *iommu, struct device *dev) > +{ > + return 0; > +} > + > +static const struct iommu_ops tegra_smmu_ops = { > + .domain_init = tegra_smmu_domain_init, > + .domain_destroy = tegra_smmu_domain_destroy, > + .attach_dev = tegra_smmu_attach_dev, > + .detach_dev = tegra_smmu_detach_dev, > + .map = tegra_smmu_map, > + .unmap = tegra_smmu_unmap, > + .iova_to_phys = tegra_smmu_iova_to_phys, > + .attach = tegra_smmu_attach, > + .detach = tegra_smmu_detach, > + > + .pgsize_bitmap = SZ_4K, > +}; > + > +static struct tegra_smmu *tegra_smmu_probe(struct device *dev, > + const struct tegra_smmu_soc *soc, > + void __iomem *regs) > +{ > + struct tegra_smmu *smmu; > + unsigned int i; > + size_t size; > + u32 value; > + int err; > + > + smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL); > + if (!smmu) > + return ERR_PTR(-ENOMEM); > + > + size = BITS_TO_LONGS(soc->num_asids) * sizeof(long); > + > + smmu->asids = devm_kzalloc(dev, size, GFP_KERNEL); > + if (!smmu->asids) > + return ERR_PTR(-ENOMEM); > + > + INIT_LIST_HEAD(&smmu->iommu.list); > + mutex_init(&smmu->lock); > + > + smmu->iommu.ops = &tegra_smmu_ops; > + smmu->iommu.dev = dev; > + > + smmu->regs = regs; > + smmu->soc = soc; > + smmu->dev = dev; > + > + smmu_handle = smmu; > + bus_set_iommu(&platform_bus_type, &tegra_smmu_ops); > + > + smmu->num_groups = soc->num_groups; > + > + smmu->groups = devm_kcalloc(dev, smmu->num_groups, sizeof(*smmu->groups), > + GFP_KERNEL); > + if (!smmu->groups) > + return ERR_PTR(-ENOMEM); > + > + for (i = 0; i < smmu->num_groups; i++) { > + struct tegra_smmu_group *group; > + > + smmu->groups[i] = iommu_group_alloc(); > + if (IS_ERR(smmu->groups[i])) > + return ERR_CAST(smmu->groups[i]); > + > + err = iommu_group_set_name(smmu->groups[i], soc->groups[i].name); > + if (err < 0) { > + } > + > + group = kzalloc(sizeof(*group), GFP_KERNEL); > + if (!group) > + return ERR_PTR(-ENOMEM); > + > + group->matches = soc->groups[i].matches; > + group->asid = soc->groups[i].asid; > + group->name = soc->groups[i].name; > + > + iommu_group_set_iommudata(smmu->groups[i], group, > + tegra_smmu_group_release); > + > +#ifndef CONFIG_ARM64 > + group->mapping = arm_iommu_create_mapping(&platform_bus_type, > + 0, SZ_2G); > + if (IS_ERR(group->mapping)) { > + dev_err(dev, "failed to create mapping for group %s: %ld\n", > + group->name, PTR_ERR(group->mapping)); > + return ERR_CAST(group->mapping); > + } > +#endif > + } > + > + value = (1 << 29) | (8 << 24) | 0x3f; > + smmu_writel(smmu, value, 0x18); > + > + value = (1 << 29) | (1 << 28) | 0x20; > + smmu_writel(smmu, value, 0x014); > + > + smmu_flush_ptc(smmu, NULL, 0); > + smmu_flush_tlb(smmu); > + smmu_writel(smmu, SMMU_CONFIG_ENABLE, SMMU_CONFIG); > + smmu_flush(smmu); > + > + err = iommu_add(&smmu->iommu); > + if (err < 0) > + return ERR_PTR(err); > + > + return smmu; > +} > + > +static int tegra_smmu_remove(struct tegra_smmu *smmu) > +{ > + iommu_remove(&smmu->iommu); > + > + return 0; > +} > + > +#ifdef CONFIG_ARCH_TEGRA_124_SOC > +static const struct tegra_smmu_soc tegra124_smmu_soc = { > + .groups = tegra124_smmu_groups, > + .num_groups = ARRAY_SIZE(tegra124_smmu_groups), > + .clients = tegra124_mc_clients, > + .num_clients = ARRAY_SIZE(tegra124_mc_clients), > + .swgroups = tegra124_swgroups, > + .num_swgroups = ARRAY_SIZE(tegra124_swgroups), > + .num_asids = 128, > + .atom_size = 32, > + .ops = &tegra124_smmu_ops, > +}; > +#endif > + > +static const struct tegra_smmu_soc tegra132_smmu_soc = { > + .groups = tegra124_smmu_groups, > + .num_groups = ARRAY_SIZE(tegra124_smmu_groups), > + .clients = tegra124_mc_clients, > + .num_clients = ARRAY_SIZE(tegra124_mc_clients), > + .swgroups = tegra124_swgroups, > + .num_swgroups = ARRAY_SIZE(tegra124_swgroups), > + .num_asids = 128, > + .atom_size = 32, > + .ops = &tegra132_smmu_ops, > +}; > + > +struct tegra_mc { > + struct device *dev; > + struct tegra_smmu *smmu; > + void __iomem *regs; > + int irq; > + > + const struct tegra_mc_soc *soc; > +}; > + > +static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset) > +{ > + return readl(mc->regs + offset); > +} > + > +static inline void mc_writel(struct tegra_mc *mc, u32 value, unsigned long offset) > +{ > + writel(value, mc->regs + offset); > +} > + > +struct tegra_mc_soc { > + const struct tegra_mc_client *clients; > + unsigned int num_clients; > + > + const struct tegra_smmu_soc *smmu; > +}; > + > +#ifdef CONFIG_ARCH_TEGRA_124_SOC > +static const struct tegra_mc_soc tegra124_mc_soc = { > + .clients = tegra124_mc_clients, > + .num_clients = ARRAY_SIZE(tegra124_mc_clients), > + .smmu = &tegra124_smmu_soc, > +}; > +#endif > + > +static const struct tegra_mc_soc tegra132_mc_soc = { > + .clients = tegra124_mc_clients, > + .num_clients = ARRAY_SIZE(tegra124_mc_clients), > + .smmu = &tegra132_smmu_soc, > +}; > + > +static const struct of_device_id tegra_mc_of_match[] = { > +#ifdef CONFIG_ARCH_TEGRA_124_SOC > + { .compatible = "nvidia,tegra124-mc", .data = &tegra124_mc_soc }, > +#endif > + { .compatible = "nvidia,tegra132-mc", .data = &tegra132_mc_soc }, > + { } > +}; > + > +static irqreturn_t tegra124_mc_irq(int irq, void *data) > +{ > + struct tegra_mc *mc = data; > + u32 value, status, mask; > + > + /* mask all interrupts to avoid flooding */ > + mask = mc_readl(mc, MC_INTMASK); > + mc_writel(mc, 0, MC_INTMASK); > + > + status = mc_readl(mc, MC_INTSTATUS); > + mc_writel(mc, status, MC_INTSTATUS); > + > + dev_dbg(mc->dev, "INTSTATUS: %08x\n", status); > + > + if (status & MC_INT_DECERR_MTS) > + dev_dbg(mc->dev, " DECERR_MTS\n"); > + > + if (status & MC_INT_SECERR_SEC) > + dev_dbg(mc->dev, " SECERR_SEC\n"); > + > + if (status & MC_INT_DECERR_VPR) > + dev_dbg(mc->dev, " DECERR_VPR\n"); > + > + if (status & MC_INT_INVALID_APB_ASID_UPDATE) > + dev_dbg(mc->dev, " INVALID_APB_ASID_UPDATE\n"); > + > + if (status & MC_INT_INVALID_SMMU_PAGE) > + dev_dbg(mc->dev, " INVALID_SMMU_PAGE\n"); > + > + if (status & MC_INT_ARBITRATION_EMEM) > + dev_dbg(mc->dev, " ARBITRATION_EMEM\n"); > + > + if (status & MC_INT_SECURITY_VIOLATION) > + dev_dbg(mc->dev, " SECURITY_VIOLATION\n"); > + > + if (status & MC_INT_DECERR_EMEM) > + dev_dbg(mc->dev, " DECERR_EMEM\n"); > + > + value = mc_readl(mc, MC_ERR_STATUS); > + > + dev_dbg(mc->dev, "ERR_STATUS: %08x\n", value); > + dev_dbg(mc->dev, " type: %x\n", (value >> 28) & 0x7); > + dev_dbg(mc->dev, " protection: %x\n", (value >> 25) & 0x7); > + dev_dbg(mc->dev, " adr_hi: %x\n", (value >> 20) & 0x3); > + dev_dbg(mc->dev, " swap: %x\n", (value >> 18) & 0x1); > + dev_dbg(mc->dev, " security: %x\n", (value >> 17) & 0x1); > + dev_dbg(mc->dev, " r/w: %x\n", (value >> 16) & 0x1); > + dev_dbg(mc->dev, " adr1: %x\n", (value >> 12) & 0x7); > + dev_dbg(mc->dev, " client: %x\n", value & 0x7f); > + > + value = mc_readl(mc, MC_ERR_ADR); > + dev_dbg(mc->dev, "ERR_ADR: %08x\n", value); > + > + mc_writel(mc, mask, MC_INTMASK); > + > + return IRQ_HANDLED; > +} > + > +static int tegra_mc_probe(struct platform_device *pdev) > +{ > + const struct of_device_id *match; > + struct resource *res; > + struct tegra_mc *mc; > + unsigned int i; > + u32 value; > + int err; > + > + match = of_match_node(tegra_mc_of_match, pdev->dev.of_node); > + if (!match) > + return -ENODEV; > + > + mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); > + if (!mc) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, mc); > + mc->soc = match->data; > + mc->dev = &pdev->dev; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + mc->regs = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(mc->regs)) > + return PTR_ERR(mc->regs); > + > + for (i = 0; i < mc->soc->num_clients; i++) { > + const struct latency_allowance *la = &mc->soc->clients[i].latency; > + u32 value; > + > + value = readl(mc->regs + la->reg); > + value &= ~(la->mask << la->shift); > + value |= (la->def & la->mask) << la->shift; > + writel(value, mc->regs + la->reg); > + } > + > + mc->smmu = tegra_smmu_probe(&pdev->dev, mc->soc->smmu, mc->regs); > + if (IS_ERR(mc->smmu)) { > + dev_err(&pdev->dev, "failed to probe SMMU: %ld\n", > + PTR_ERR(mc->smmu)); > + return PTR_ERR(mc->smmu); > + } > + > + mc->irq = platform_get_irq(pdev, 0); > + if (mc->irq < 0) { > + dev_err(&pdev->dev, "interrupt not specified\n"); > + return mc->irq; > + } > + > + err = devm_request_irq(&pdev->dev, mc->irq, tegra124_mc_irq, > + IRQF_SHARED, dev_name(&pdev->dev), mc); > + if (err < 0) { > + dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", mc->irq, > + err); > + return err; > + } > + > + value = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | > + MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | > + MC_INT_ARBITRATION_EMEM | MC_INT_SECURITY_VIOLATION | > + MC_INT_DECERR_EMEM; > + mc_writel(mc, value, MC_INTMASK); > + > + return 0; > +} > + > +static int tegra_mc_remove(struct platform_device *pdev) > +{ > + struct tegra_mc *mc = platform_get_drvdata(pdev); > + int err; > + > + err = tegra_smmu_remove(mc->smmu); > + if (err < 0) > + dev_err(&pdev->dev, "failed to remove SMMU: %d\n", err); > + > + return 0; > +} > + > +static struct platform_driver tegra_mc_driver = { > + .driver = { > + .name = "tegra124-mc", > + .of_match_table = tegra_mc_of_match, > + }, > + .probe = tegra_mc_probe, > + .remove = tegra_mc_remove, > +}; > +module_platform_driver(tegra_mc_driver); > + > +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); > +MODULE_DESCRIPTION("NVIDIA Tegra124 Memory Controller driver"); > +MODULE_LICENSE("GPL v2"); > diff --git a/include/dt-bindings/memory/tegra124-mc.h b/include/dt-bindings/memory/tegra124-mc.h > new file mode 100644 > index 000000000000..6b1617ce022f > --- /dev/null > +++ b/include/dt-bindings/memory/tegra124-mc.h > @@ -0,0 +1,30 @@ > +#ifndef DT_BINDINGS_MEMORY_TEGRA124_MC_H > +#define DT_BINDINGS_MEMORY_TEGRA124_MC_H > + > +#define TEGRA_SWGROUP_DC 0 > +#define TEGRA_SWGROUP_DCB 1 > +#define TEGRA_SWGROUP_AFI 2 > +#define TEGRA_SWGROUP_AVPC 3 > +#define TEGRA_SWGROUP_HDA 4 > +#define TEGRA_SWGROUP_HC 5 > +#define TEGRA_SWGROUP_MSENC 6 > +#define TEGRA_SWGROUP_PPCS 7 > +#define TEGRA_SWGROUP_SATA 8 > +#define TEGRA_SWGROUP_VDE 9 > +#define TEGRA_SWGROUP_MPCORELP 10 > +#define TEGRA_SWGROUP_MPCORE 11 > +#define TEGRA_SWGROUP_ISP2 12 > +#define TEGRA_SWGROUP_XUSB_HOST 13 > +#define TEGRA_SWGROUP_XUSB_DEV 14 > +#define TEGRA_SWGROUP_ISP2B 15 > +#define TEGRA_SWGROUP_TSEC 16 > +#define TEGRA_SWGROUP_A9AVP 17 > +#define TEGRA_SWGROUP_GPU 18 > +#define TEGRA_SWGROUP_SDMMC1A 19 > +#define TEGRA_SWGROUP_SDMMC2A 20 > +#define TEGRA_SWGROUP_SDMMC3A 21 > +#define TEGRA_SWGROUP_SDMMC4A 22 > +#define TEGRA_SWGROUP_VIC 23 > +#define TEGRA_SWGROUP_VI 24 > + > +#endif > -- > 2.0.0 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-tegra" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html >
On 06/27/2014 05:08 AM, Thierry Reding wrote: > On Fri, Jun 27, 2014 at 12:46:38PM +0300, Hiroshi DOyu wrote: >> >> Thierry Reding <thierry.reding@gmail.com> writes: >> >>> From: Thierry Reding <treding@nvidia.com> >>> >>> The memory controller on NVIDIA Tegra124 exposes various knobs that can >>> be used to tune the behaviour of the clients attached to it. >>> >>> Currently this driver sets up the latency allowance registers to the HW >>> defaults. Eventually an API should be exported by this driver (via a >>> custom API or a generic subsystem) to allow clients to register latency >>> requirements. >>> >>> This driver also registers an IOMMU (SMMU) that's implemented by the >>> memory controller. >>> >>> Signed-off-by: Thierry Reding <treding@nvidia.com> >>> --- >>> drivers/memory/Kconfig | 9 + >>> drivers/memory/Makefile | 1 + >>> drivers/memory/tegra124-mc.c | 1945 ++++++++++++++++++++++++++++++ >>> include/dt-bindings/memory/tegra124-mc.h | 30 + >>> 4 files changed, 1985 insertions(+) >>> create mode 100644 drivers/memory/tegra124-mc.c >>> create mode 100644 include/dt-bindings/memory/tegra124-mc.h >> >> I prefer reusing the existing SMMU and having MC and SMMU separated >> since most of SMMU code are not different from functionality POV, and >> new MC features are quite independent of SMMU. >> >> If it's really convenient to combine MC and SMMU into one driver, we >> could move "drivers/iomm/tegra-smmu.c" here first, and add MC features >> on the top of it. > > I'm not sure if we can do that, since the tegra-smmu driver is > technically used by Tegra30 and Tegra114. We've never really made use of > it, but there are device trees in mainline releases that contain the > separate SMMU node. The existing DT nodes do nothing more than instantiate the driver. However, IIUC nothing actually uses the driver for any purpose, so if we simply deleted those nodes or changed them incompatibly, there'd be no functional difference. Perhaps this is stretching DT ABIness very slightly, but I think it makes no practical difference.
On 06/27/2014 05:15 AM, Thierry Reding wrote: > On Fri, Jun 27, 2014 at 01:07:04PM +0200, Arnd Bergmann wrote: >> On Thursday 26 June 2014 22:49:44 Thierry Reding wrote: >>> +static const struct tegra_mc_client tegra124_mc_clients[] = { >>> + { >>> + .id = 0x01, >>> + .name = "display0a", >>> + .swgroup = TEGRA_SWGROUP_DC, >>> + .smmu = { >>> + .reg = 0x228, >>> + .bit = 1, >>> + }, >>> + .latency = { >>> + .reg = 0x2e8, >>> + .shift = 0, >>> + .mask = 0xff, >>> + .def = 0xc2, >>> + }, >>> + }, { >> >> This is a rather long table that I assume would need to get duplicated >> and modified for each specific SoC. Have you considered to put the information >> into DT instead, as auxiliary data in the iommu specifier as provided by >> the device? > > Most of this data really is register information and I don't think that > belongs in DT. I agree. I think it's quite inappropriate to put information into DT that could simply be put into a table in the driver. If the information is put into DT, you have to define a fixed binding for it, munge the table and data representation to fit DT's much less flexible (than C structs/arrays) syntax, write a whole bunch of code to parse it back out (at probably not do a good job with error-checking), all only to end up with exactly the same C structs in the driver at the end of the process. Oh, and if multiple SoCs use the same data values, you have to duplicate those tables into at least the DTBs if not in the .dts files, whereas with C you can just point at the same struct. SoCs come out much less frequently than new boards (perhaps ignoring the fact that we support a small subset of boards in mainline, so the frequency isn't too dissimilar there). It makes good sense to put board-to-board differences in DT, but I see little point in putting static SoC information into DT.
On 06/26/2014 02:49 PM, Thierry Reding wrote: > From: Thierry Reding <treding@nvidia.com> > > The memory controller on NVIDIA Tegra124 exposes various knobs that can > be used to tune the behaviour of the clients attached to it. > > Currently this driver sets up the latency allowance registers to the HW > defaults. Eventually an API should be exported by this driver (via a > custom API or a generic subsystem) to allow clients to register latency > requirements. > > This driver also registers an IOMMU (SMMU) that's implemented by the > memory controller. > diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig > +config TEGRA124_MC > + bool "Tegra124 Memory Controller driver" > + depends on ARCH_TEGRA Does it make sense to default to y for system-level drivers like this? > diff --git a/drivers/memory/tegra124-mc.c b/drivers/memory/tegra124-mc.c As a general comment, I wonder why the Tegra124 code/data here is ifdef'd based on CONFIG_ARCH_TEGRA_124_SOC but the Tegra132 code isn't ifdef'd at all. I'd assert that the Tegra124 code is small enough it's hardly worth worrying about ifdefs. > +static inline void smmu_flush_ptc(struct tegra_smmu *smmu, struct page *page, > + unsigned long offset) > +{ > + phys_addr_t phys = page ? page_to_phys(page) : 0; > + u32 value; > + > + if (page) { > + offset &= ~(smmu->soc->atom_size - 1); > + > +#ifdef CONFIG_PHYS_ADDR_T_64BIT > + value = (phys >> 32) & SMMU_PTC_FLUSH_HI_MASK; > +#else > + value = 0; > +#endif Shouldn't Tegra124 have CONFIG_PHYS_ADDR_T_64BIT defined, such that there's no need for this ifdef? Certainly Tegra124 {has,can have} RAM above 4GB physical, for some memory map layouts (i.e. non swiss cheese). (I assume most of this code matches the existing Tegra30 SMMU driver, so I didn't look at all of it that closely). > +static int tegra_smmu_attach(struct iommu *iommu, struct device *dev) ... > +#ifndef CONFIG_ARM64 > + return arm_iommu_attach_device(dev, group->mapping); > +#else > + return 0; > +#endif Hmm. Why must an SMMU driver for the exact same HW operate differently depending on the CPU that's attached to the SoC? Surely the requirements for how IOMMU drives should work should be the same for all architectures? > +static int tegra_mc_probe(struct platform_device *pdev) > + err = devm_request_irq(&pdev->dev, mc->irq, tegra124_mc_irq, > + IRQF_SHARED, dev_name(&pdev->dev), mc); I don't see any code in tegra_mc_remove() that guarantees that the IRQ won't fire between tegra_mc_remove() returning, and the devm cleanup code running to unhook that IRQ handler. > diff --git a/include/dt-bindings/memory/tegra124-mc.h b/include/dt-bindings/memory/tegra124-mc.h This file is part of the DT binding, so should be added in the patch that adds the binding.
Thierry Reding <thierry.reding@gmail.com> writes: > diff --git a/include/dt-bindings/memory/tegra124-mc.h b/include/dt-bindings/memory/tegra124-mc.h > new file mode 100644 > index 000000000000..6b1617ce022f > --- /dev/null > +++ b/include/dt-bindings/memory/tegra124-mc.h > @@ -0,0 +1,30 @@ > +#ifndef DT_BINDINGS_MEMORY_TEGRA124_MC_H > +#define DT_BINDINGS_MEMORY_TEGRA124_MC_H > + > +#define TEGRA_SWGROUP_DC 0 > +#define TEGRA_SWGROUP_DCB 1 > +#define TEGRA_SWGROUP_AFI 2 > +#define TEGRA_SWGROUP_AVPC 3 > +#define TEGRA_SWGROUP_HDA 4 > +#define TEGRA_SWGROUP_HC 5 > +#define TEGRA_SWGROUP_MSENC 6 > +#define TEGRA_SWGROUP_PPCS 7 > +#define TEGRA_SWGROUP_SATA 8 > +#define TEGRA_SWGROUP_VDE 9 > +#define TEGRA_SWGROUP_MPCORELP 10 > +#define TEGRA_SWGROUP_MPCORE 11 > +#define TEGRA_SWGROUP_ISP2 12 > +#define TEGRA_SWGROUP_XUSB_HOST 13 > +#define TEGRA_SWGROUP_XUSB_DEV 14 > +#define TEGRA_SWGROUP_ISP2B 15 > +#define TEGRA_SWGROUP_TSEC 16 > +#define TEGRA_SWGROUP_A9AVP 17 > +#define TEGRA_SWGROUP_GPU 18 > +#define TEGRA_SWGROUP_SDMMC1A 19 > +#define TEGRA_SWGROUP_SDMMC2A 20 > +#define TEGRA_SWGROUP_SDMMC3A 21 > +#define TEGRA_SWGROUP_SDMMC4A 22 > +#define TEGRA_SWGROUP_VIC 23 > +#define TEGRA_SWGROUP_VI 24 > + > +#endif In the SMMUv8 patch series, I have assigned unique IDs for all those HWAs among Tegra SoC generations so that DT can provide which HWAs are attached to that SoC. The SMMUv8 driver would be unified among Tegra SoCs, then.
diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index c59e9c96e86d..d0f0e6781570 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -61,6 +61,15 @@ config TEGRA30_MC analysis, especially for IOMMU/SMMU(System Memory Management Unit) module. +config TEGRA124_MC + bool "Tegra124 Memory Controller driver" + depends on ARCH_TEGRA + select IOMMU_API + help + This driver is for the Memory Controller module available on + Tegra124 SoCs. It provides an IOMMU that can be used for I/O + virtual address translation. + config FSL_IFC bool depends on FSL_SOC diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 71160a2b7313..03143927abab 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_FSL_IFC) += fsl_ifc.o obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o +obj-$(CONFIG_TEGRA124_MC) += tegra124-mc.o diff --git a/drivers/memory/tegra124-mc.c b/drivers/memory/tegra124-mc.c new file mode 100644 index 000000000000..741755b6785d --- /dev/null +++ b/drivers/memory/tegra124-mc.c @@ -0,0 +1,1945 @@ +/* + * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iommu.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <dt-bindings/memory/tegra124-mc.h> + +#include <asm/cacheflush.h> +#ifndef CONFIG_ARM64 +#include <asm/dma-iommu.h> +#endif + +#define MC_INTSTATUS 0x000 +#define MC_INT_DECERR_MTS (1 << 16) +#define MC_INT_SECERR_SEC (1 << 13) +#define MC_INT_DECERR_VPR (1 << 12) +#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11) +#define MC_INT_INVALID_SMMU_PAGE (1 << 10) +#define MC_INT_ARBITRATION_EMEM (1 << 9) +#define MC_INT_SECURITY_VIOLATION (1 << 8) +#define MC_INT_DECERR_EMEM (1 << 6) +#define MC_INTMASK 0x004 +#define MC_ERR_STATUS 0x08 +#define MC_ERR_ADR 0x0c + +struct latency_allowance { + unsigned int reg; + unsigned int shift; + unsigned int mask; + unsigned int def; +}; + +struct smmu_enable { + unsigned int reg; + unsigned int bit; +}; + +struct tegra_mc_client { + unsigned int id; + const char *name; + unsigned int swgroup; + + struct smmu_enable smmu; + struct latency_allowance latency; +}; + +static const struct tegra_mc_client tegra124_mc_clients[] = { + { + .id = 0x01, + .name = "display0a", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 1, + }, + .latency = { + .reg = 0x2e8, + .shift = 0, + .mask = 0xff, + .def = 0xc2, + }, + }, { + .id = 0x02, + .name = "display0ab", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 2, + }, + .latency = { + .reg = 0x2f4, + .shift = 0, + .mask = 0xff, + .def = 0xc6, + }, + }, { + .id = 0x03, + .name = "display0b", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 3, + }, + .latency = { + .reg = 0x2e8, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x04, + .name = "display0bb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 4, + }, + .latency = { + .reg = 0x2f4, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x05, + .name = "display0c", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 5, + }, + .latency = { + .reg = 0x2ec, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x06, + .name = "display0cb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 6, + }, + .latency = { + .reg = 0x2f8, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x0e, + .name = "afir", + .swgroup = TEGRA_SWGROUP_AFI, + .smmu = { + .reg = 0x228, + .bit = 14, + }, + .latency = { + .reg = 0x2e0, + .shift = 0, + .mask = 0xff, + .def = 0x13, + }, + }, { + .id = 0x0f, + .name = "avpcarm7r", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x228, + .bit = 15, + }, + .latency = { + .reg = 0x2e4, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x10, + .name = "displayhc", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 16, + }, + .latency = { + .reg = 0x2f0, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x11, + .name = "displayhcb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 17, + }, + .latency = { + .reg = 0x2fc, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x15, + .name = "hdar", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x228, + .bit = 21, + }, + .latency = { + .reg = 0x318, + .shift = 0, + .mask = 0xff, + .def = 0x24, + }, + }, { + .id = 0x16, + .name = "host1xdmar", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 22, + }, + .latency = { + .reg = 0x310, + .shift = 0, + .mask = 0xff, + .def = 0x1e, + }, + }, { + .id = 0x17, + .name = "host1xr", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 23, + }, + .latency = { + .reg = 0x310, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x1c, + .name = "msencsrd", + .swgroup = TEGRA_SWGROUP_MSENC, + .smmu = { + .reg = 0x228, + .bit = 28, + }, + .latency = { + .reg = 0x328, + .shift = 0, + .mask = 0xff, + .def = 0x23, + }, + }, { + .id = 0x1d, + .name = "ppcsahbdmarhdar", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 29, + }, + .latency = { + .reg = 0x344, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x1e, + .name = "ppcsahbslvr", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 30, + }, + .latency = { + .reg = 0x344, + .shift = 16, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x1f, + .name = "satar", + .swgroup = TEGRA_SWGROUP_SATA, + .smmu = { + .reg = 0x228, + .bit = 31, + }, + .latency = { + .reg = 0x350, + .shift = 0, + .mask = 0xff, + .def = 0x65, + }, + }, { + .id = 0x22, + .name = "vdebsevr", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 2, + }, + .latency = { + .reg = 0x354, + .shift = 0, + .mask = 0xff, + .def = 0x4f, + }, + }, { + .id = 0x23, + .name = "vdember", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 3, + }, + .latency = { + .reg = 0x354, + .shift = 16, + .mask = 0xff, + .def = 0x3d, + }, + }, { + .id = 0x24, + .name = "vdemcer", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 4, + }, + .latency = { + .reg = 0x358, + .shift = 0, + .mask = 0xff, + .def = 0x66, + }, + }, { + .id = 0x25, + .name = "vdetper", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 5, + }, + .latency = { + .reg = 0x358, + .shift = 16, + .mask = 0xff, + .def = 0xa5, + }, + }, { + .id = 0x26, + .name = "mpcorelpr", + .swgroup = TEGRA_SWGROUP_MPCORELP, + .latency = { + .reg = 0x324, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x27, + .name = "mpcorer", + .swgroup = TEGRA_SWGROUP_MPCORE, + .smmu = { + .reg = 0x22c, + .bit = 2, + }, + .latency = { + .reg = 0x320, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x2b, + .name = "msencswr", + .swgroup = TEGRA_SWGROUP_MSENC, + .smmu = { + .reg = 0x22c, + .bit = 11, + }, + .latency = { + .reg = 0x328, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x31, + .name = "afiw", + .swgroup = TEGRA_SWGROUP_AFI, + .smmu = { + .reg = 0x22c, + .bit = 17, + }, + .latency = { + .reg = 0x2e0, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x32, + .name = "avpcarm7w", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x22c, + .bit = 18, + }, + .latency = { + .reg = 0x2e4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x35, + .name = "hdaw", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x22c, + .bit = 21, + }, + .latency = { + .reg = 0x318, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x36, + .name = "host1xw", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x22c, + .bit = 22, + }, + .latency = { + .reg = 0x314, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x38, + .name = "mpcorelpw", + .swgroup = TEGRA_SWGROUP_MPCORELP, + .latency = { + .reg = 0x324, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x39, + .name = "mpcorew", + .swgroup = TEGRA_SWGROUP_MPCORE, + .latency = { + .reg = 0x320, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3b, + .name = "ppcsahbdmaw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 27, + }, + .latency = { + .reg = 0x348, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3c, + .name = "ppcsahbslvw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 28, + }, + .latency = { + .reg = 0x348, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3d, + .name = "sataw", + .swgroup = TEGRA_SWGROUP_SATA, + .smmu = { + .reg = 0x22c, + .bit = 29, + }, + .latency = { + .reg = 0x350, + .shift = 16, + .mask = 0xff, + .def = 0x65, + }, + }, { + .id = 0x3e, + .name = "vdebsevw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 30, + }, + .latency = { + .reg = 0x35c, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3f, + .name = "vdedbgw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 31, + }, + .latency = { + .reg = 0x35c, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x40, + .name = "vdembew", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x230, + .bit = 0, + }, + .latency = { + .reg = 0x360, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x41, + .name = "vdetpmw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x230, + .bit = 1, + }, + .latency = { + .reg = 0x360, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x44, + .name = "ispra", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 4, + }, + .latency = { + .reg = 0x370, + .shift = 0, + .mask = 0xff, + .def = 0x18, + }, + }, { + .id = 0x46, + .name = "ispwa", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 6, + }, + .latency = { + .reg = 0x374, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x47, + .name = "ispwb", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 7, + }, + .latency = { + .reg = 0x374, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4a, + .name = "xusb_hostr", + .swgroup = TEGRA_SWGROUP_XUSB_HOST, + .smmu = { + .reg = 0x230, + .bit = 10, + }, + .latency = { + .reg = 0x37c, + .shift = 0, + .mask = 0xff, + .def = 0x39, + }, + }, { + .id = 0x4b, + .name = "xusb_hostw", + .swgroup = TEGRA_SWGROUP_XUSB_HOST, + .smmu = { + .reg = 0x230, + .bit = 11, + }, + .latency = { + .reg = 0x37c, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4c, + .name = "xusb_devr", + .swgroup = TEGRA_SWGROUP_XUSB_DEV, + .smmu = { + .reg = 0x230, + .bit = 12, + }, + .latency = { + .reg = 0x380, + .shift = 0, + .mask = 0xff, + .def = 0x39, + }, + }, { + .id = 0x4d, + .name = "xusb_devw", + .swgroup = TEGRA_SWGROUP_XUSB_DEV, + .smmu = { + .reg = 0x230, + .bit = 13, + }, + .latency = { + .reg = 0x380, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4e, + .name = "isprab", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 14, + }, + .latency = { + .reg = 0x384, + .shift = 0, + .mask = 0xff, + .def = 0x18, + }, + }, { + .id = 0x50, + .name = "ispwab", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 16, + }, + .latency = { + .reg = 0x388, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x51, + .name = "ispwbb", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 17, + }, + .latency = { + .reg = 0x388, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x54, + .name = "tsecsrd", + .swgroup = TEGRA_SWGROUP_TSEC, + .smmu = { + .reg = 0x230, + .bit = 20, + }, + .latency = { + .reg = 0x390, + .shift = 0, + .mask = 0xff, + .def = 0x9b, + }, + }, { + .id = 0x55, + .name = "tsecswr", + .swgroup = TEGRA_SWGROUP_TSEC, + .smmu = { + .reg = 0x230, + .bit = 21, + }, + .latency = { + .reg = 0x390, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x56, + .name = "a9avpscr", + .swgroup = TEGRA_SWGROUP_A9AVP, + .smmu = { + .reg = 0x230, + .bit = 22, + }, + .latency = { + .reg = 0x3a4, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x57, + .name = "a9avpscw", + .swgroup = TEGRA_SWGROUP_A9AVP, + .smmu = { + .reg = 0x230, + .bit = 23, + }, + .latency = { + .reg = 0x3a4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x58, + .name = "gpusrd", + .swgroup = TEGRA_SWGROUP_GPU, + .smmu = { + /* read-only */ + .reg = 0x230, + .bit = 24, + }, + .latency = { + .reg = 0x3c8, + .shift = 0, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x59, + .name = "gpuswr", + .swgroup = TEGRA_SWGROUP_GPU, + .smmu = { + /* read-only */ + .reg = 0x230, + .bit = 25, + }, + .latency = { + .reg = 0x3c8, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x5a, + .name = "displayt", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x230, + .bit = 26, + }, + .latency = { + .reg = 0x2f0, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x60, + .name = "sdmmcra", + .swgroup = TEGRA_SWGROUP_SDMMC1A, + .smmu = { + .reg = 0x234, + .bit = 0, + }, + .latency = { + .reg = 0x3b8, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x61, + .name = "sdmmcraa", + .swgroup = TEGRA_SWGROUP_SDMMC2A, + .smmu = { + .reg = 0x234, + .bit = 1, + }, + .latency = { + .reg = 0x3bc, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x62, + .name = "sdmmcr", + .swgroup = TEGRA_SWGROUP_SDMMC3A, + .smmu = { + .reg = 0x234, + .bit = 2, + }, + .latency = { + .reg = 0x3c0, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x63, + .swgroup = TEGRA_SWGROUP_SDMMC4A, + .name = "sdmmcrab", + .smmu = { + .reg = 0x234, + .bit = 3, + }, + .latency = { + .reg = 0x3c4, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x64, + .name = "sdmmcwa", + .swgroup = TEGRA_SWGROUP_SDMMC1A, + .smmu = { + .reg = 0x234, + .bit = 4, + }, + .latency = { + .reg = 0x3b8, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x65, + .name = "sdmmcwaa", + .swgroup = TEGRA_SWGROUP_SDMMC2A, + .smmu = { + .reg = 0x234, + .bit = 5, + }, + .latency = { + .reg = 0x3bc, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x66, + .name = "sdmmcw", + .swgroup = TEGRA_SWGROUP_SDMMC3A, + .smmu = { + .reg = 0x234, + .bit = 6, + }, + .latency = { + .reg = 0x3c0, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x67, + .name = "sdmmcwab", + .swgroup = TEGRA_SWGROUP_SDMMC4A, + .smmu = { + .reg = 0x234, + .bit = 7, + }, + .latency = { + .reg = 0x3c4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x6c, + .name = "vicsrd", + .swgroup = TEGRA_SWGROUP_VIC, + .smmu = { + .reg = 0x234, + .bit = 12, + }, + .latency = { + .reg = 0x394, + .shift = 0, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x6d, + .name = "vicswr", + .swgroup = TEGRA_SWGROUP_VIC, + .smmu = { + .reg = 0x234, + .bit = 13, + }, + .latency = { + .reg = 0x394, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x72, + .name = "viw", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x234, + .bit = 18, + }, + .latency = { + .reg = 0x398, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x73, + .name = "displayd", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x234, + .bit = 19, + }, + .latency = { + .reg = 0x3c8, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, +}; + +struct tegra_smmu_swgroup { + unsigned int swgroup; + unsigned int reg; +}; + +static const struct tegra_smmu_swgroup tegra124_swgroups[] = { + { .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, + { .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 }, + { .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, + { .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 }, + { .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 }, + { .swgroup = TEGRA_SWGROUP_MSENC, .reg = 0x264 }, + { .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 }, + { .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x274 }, + { .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c }, + { .swgroup = TEGRA_SWGROUP_ISP2, .reg = 0x258 }, + { .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 }, + { .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c }, + { .swgroup = TEGRA_SWGROUP_ISP2B, .reg = 0xaa4 }, + { .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 }, + { .swgroup = TEGRA_SWGROUP_A9AVP, .reg = 0x290 }, + { .swgroup = TEGRA_SWGROUP_GPU, .reg = 0xaa8 }, + { .swgroup = TEGRA_SWGROUP_SDMMC1A, .reg = 0xa94 }, + { .swgroup = TEGRA_SWGROUP_SDMMC2A, .reg = 0xa98 }, + { .swgroup = TEGRA_SWGROUP_SDMMC3A, .reg = 0xa9c }, + { .swgroup = TEGRA_SWGROUP_SDMMC4A, .reg = 0xaa0 }, + { .swgroup = TEGRA_SWGROUP_VIC, .reg = 0x284 }, + { .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 }, +}; + +struct tegra_smmu_group_init { + unsigned int asid; + const char *name; + + const struct of_device_id *matches; +}; + +struct tegra_smmu_soc { + const struct tegra_smmu_group_init *groups; + unsigned int num_groups; + + const struct tegra_mc_client *clients; + unsigned int num_clients; + + const struct tegra_smmu_swgroup *swgroups; + unsigned int num_swgroups; + + unsigned int num_asids; + unsigned int atom_size; + + const struct tegra_smmu_ops *ops; +}; + +struct tegra_smmu_ops { + void (*flush_dcache)(struct page *page, unsigned long offset, + size_t size); +}; + +struct tegra_smmu_master { + struct list_head list; + struct device *dev; +}; + +struct tegra_smmu_group { + const char *name; + const struct of_device_id *matches; + unsigned int asid; + +#ifndef CONFIG_ARM64 + struct dma_iommu_mapping *mapping; +#endif + struct list_head masters; +}; + +static const struct of_device_id tegra124_periph_matches[] = { + { .compatible = "nvidia,tegra124-sdhci", }, + { } +}; + +static const struct tegra_smmu_group_init tegra124_smmu_groups[] = { + { 0, "peripherals", tegra124_periph_matches }, +}; + +static void tegra_smmu_group_release(void *data) +{ + kfree(data); +} + +struct tegra_smmu { + void __iomem *regs; + struct iommu iommu; + struct device *dev; + + const struct tegra_smmu_soc *soc; + + struct iommu_group **groups; + unsigned int num_groups; + + unsigned long *asids; + struct mutex lock; +}; + +struct tegra_smmu_address_space { + struct iommu_domain *domain; + struct tegra_smmu *smmu; + struct page *pd; + unsigned id; + u32 attr; +}; + +static inline void smmu_writel(struct tegra_smmu *smmu, u32 value, + unsigned long offset) +{ + writel(value, smmu->regs + offset); +} + +static inline u32 smmu_readl(struct tegra_smmu *smmu, unsigned long offset) +{ + return readl(smmu->regs + offset); +} + +#define SMMU_CONFIG 0x010 +#define SMMU_CONFIG_ENABLE (1 << 0) + +#define SMMU_PTB_ASID 0x01c +#define SMMU_PTB_ASID_VALUE(x) ((x) & 0x7f) + +#define SMMU_PTB_DATA 0x020 +#define SMMU_PTB_DATA_VALUE(page, attr) (page_to_phys(page) >> 12 | (attr)) + +#define SMMU_MK_PDE(page, attr) (page_to_phys(page) >> SMMU_PTE_SHIFT | (attr)) + +#define SMMU_TLB_FLUSH 0x030 +#define SMMU_TLB_FLUSH_VA_MATCH_ALL (0 << 0) +#define SMMU_TLB_FLUSH_VA_MATCH_SECTION (2 << 0) +#define SMMU_TLB_FLUSH_VA_MATCH_GROUP (3 << 0) +#define SMMU_TLB_FLUSH_ASID(x) (((x) & 0x7f) << 24) +#define SMMU_TLB_FLUSH_VA_SECTION(addr) ((((addr) & 0xffc00000) >> 12) | \ + SMMU_TLB_FLUSH_VA_MATCH_SECTION) +#define SMMU_TLB_FLUSH_VA_GROUP(addr) ((((addr) & 0xffffc000) >> 12) | \ + SMMU_TLB_FLUSH_VA_MATCH_GROUP) +#define SMMU_TLB_FLUSH_ASID_MATCH (1 << 31) + +#define SMMU_PTC_FLUSH 0x034 +#define SMMU_PTC_FLUSH_TYPE_ALL (0 << 0) +#define SMMU_PTC_FLUSH_TYPE_ADR (1 << 0) + +#define SMMU_PTC_FLUSH_HI 0x9b8 +#define SMMU_PTC_FLUSH_HI_MASK 0x3 + +/* per-SWGROUP SMMU_*_ASID register */ +#define SMMU_ASID_ENABLE (1 << 31) +#define SMMU_ASID_MASK 0x7f +#define SMMU_ASID_VALUE(x) ((x) & SMMU_ASID_MASK) + +/* page table definitions */ +#define SMMU_NUM_PDE 1024 +#define SMMU_NUM_PTE 1024 + +#define SMMU_SIZE_PD (SMMU_NUM_PDE * 4) +#define SMMU_SIZE_PT (SMMU_NUM_PTE * 4) + +#define SMMU_PDE_SHIFT 22 +#define SMMU_PTE_SHIFT 12 + +#define SMMU_PFN_MASK 0x000fffff + +#define SMMU_PD_READABLE (1 << 31) +#define SMMU_PD_WRITABLE (1 << 30) +#define SMMU_PD_NONSECURE (1 << 29) + +#define SMMU_PDE_READABLE (1 << 31) +#define SMMU_PDE_WRITABLE (1 << 30) +#define SMMU_PDE_NONSECURE (1 << 29) +#define SMMU_PDE_NEXT (1 << 28) + +#define SMMU_PTE_READABLE (1 << 31) +#define SMMU_PTE_WRITABLE (1 << 30) +#define SMMU_PTE_NONSECURE (1 << 29) + +#define SMMU_PDE_ATTR (SMMU_PDE_READABLE | SMMU_PDE_WRITABLE | \ + SMMU_PDE_NONSECURE) +#define SMMU_PTE_ATTR (SMMU_PTE_READABLE | SMMU_PTE_WRITABLE | \ + SMMU_PTE_NONSECURE) + +#define SMMU_PDE_VACANT(n) (((n) << 10) | SMMU_PDE_ATTR) +#define SMMU_PTE_VACANT(n) (((n) << 12) | SMMU_PTE_ATTR) + +#ifdef CONFIG_ARCH_TEGRA_124_SOC +static void tegra124_flush_dcache(struct page *page, unsigned long offset, + size_t size) +{ + phys_addr_t phys = page_to_phys(page) + offset; + void *virt = page_address(page) + offset; + + __cpuc_flush_dcache_area(virt, size); + outer_flush_range(phys, phys + size); +} + +static const struct tegra_smmu_ops tegra124_smmu_ops = { + .flush_dcache = tegra124_flush_dcache, +}; +#endif + +static void tegra132_flush_dcache(struct page *page, unsigned long offset, + size_t size) +{ + /* TODO: implement */ +} + +static const struct tegra_smmu_ops tegra132_smmu_ops = { + .flush_dcache = tegra132_flush_dcache, +}; + +static inline void smmu_flush_ptc(struct tegra_smmu *smmu, struct page *page, + unsigned long offset) +{ + phys_addr_t phys = page ? page_to_phys(page) : 0; + u32 value; + + if (page) { + offset &= ~(smmu->soc->atom_size - 1); + +#ifdef CONFIG_PHYS_ADDR_T_64BIT + value = (phys >> 32) & SMMU_PTC_FLUSH_HI_MASK; +#else + value = 0; +#endif + smmu_writel(smmu, value, SMMU_PTC_FLUSH_HI); + + value = (phys + offset) | SMMU_PTC_FLUSH_TYPE_ADR; + } else { + value = SMMU_PTC_FLUSH_TYPE_ALL; + } + + smmu_writel(smmu, value, SMMU_PTC_FLUSH); +} + +static inline void smmu_flush_tlb(struct tegra_smmu *smmu) +{ + smmu_writel(smmu, SMMU_TLB_FLUSH_VA_MATCH_ALL, SMMU_TLB_FLUSH); +} + +static inline void smmu_flush_tlb_asid(struct tegra_smmu *smmu, + unsigned long asid) +{ + u32 value; + + value = SMMU_TLB_FLUSH_ASID_MATCH | SMMU_TLB_FLUSH_ASID(asid) | + SMMU_TLB_FLUSH_VA_MATCH_ALL; + smmu_writel(smmu, value, SMMU_TLB_FLUSH); +} + +static inline void smmu_flush_tlb_section(struct tegra_smmu *smmu, + unsigned long asid, + unsigned long iova) +{ + u32 value; + + value = SMMU_TLB_FLUSH_ASID_MATCH | SMMU_TLB_FLUSH_ASID(asid) | + SMMU_TLB_FLUSH_VA_SECTION(iova); + smmu_writel(smmu, value, SMMU_TLB_FLUSH); +} + +static inline void smmu_flush_tlb_group(struct tegra_smmu *smmu, + unsigned long asid, + unsigned long iova) +{ + u32 value; + + value = SMMU_TLB_FLUSH_ASID_MATCH | SMMU_TLB_FLUSH_ASID(asid) | + SMMU_TLB_FLUSH_VA_GROUP(iova); + smmu_writel(smmu, value, SMMU_TLB_FLUSH); +} + +static inline void smmu_flush(struct tegra_smmu *smmu) +{ + smmu_readl(smmu, SMMU_CONFIG); +} + +static inline struct tegra_smmu *to_tegra_smmu(struct iommu *iommu) +{ + return container_of(iommu, struct tegra_smmu, iommu); +} + +static struct tegra_smmu *smmu_handle = NULL; + +static int tegra_smmu_alloc_asid(struct tegra_smmu *smmu, unsigned int *idp) +{ + unsigned long id; + + mutex_lock(&smmu->lock); + + id = find_first_zero_bit(smmu->asids, smmu->soc->num_asids); + if (id >= smmu->soc->num_asids) { + mutex_unlock(&smmu->lock); + return -ENOSPC; + } + + set_bit(id, smmu->asids); + *idp = id; + + mutex_unlock(&smmu->lock); + return 0; +} + +static void tegra_smmu_free_asid(struct tegra_smmu *smmu, unsigned int id) +{ + mutex_lock(&smmu->lock); + clear_bit(id, smmu->asids); + mutex_unlock(&smmu->lock); +} + +struct tegra_smmu_address_space *foo = NULL; + +static int tegra_smmu_domain_init(struct iommu_domain *domain) +{ + struct tegra_smmu *smmu = smmu_handle; + struct tegra_smmu_address_space *as; + uint32_t *pd, value; + unsigned int i; + int err = 0; + + as = kzalloc(sizeof(*as), GFP_KERNEL); + if (!as) { + err = -ENOMEM; + goto out; + } + + as->attr = SMMU_PD_READABLE | SMMU_PD_WRITABLE | SMMU_PD_NONSECURE; + as->smmu = smmu_handle; + as->domain = domain; + + err = tegra_smmu_alloc_asid(smmu, &as->id); + if (err < 0) { + kfree(as); + goto out; + } + + as->pd = alloc_page(GFP_KERNEL | __GFP_DMA); + if (!as->pd) { + err = -ENOMEM; + goto out; + } + + pd = page_address(as->pd); + SetPageReserved(as->pd); + + for (i = 0; i < SMMU_NUM_PDE; i++) + pd[i] = SMMU_PDE_VACANT(i); + + smmu->soc->ops->flush_dcache(as->pd, 0, SMMU_SIZE_PD); + smmu_flush_ptc(smmu, as->pd, 0); + smmu_flush_tlb_asid(smmu, as->id); + + smmu_writel(smmu, as->id & 0x7f, SMMU_PTB_ASID); + value = SMMU_PTB_DATA_VALUE(as->pd, as->attr); + smmu_writel(smmu, value, SMMU_PTB_DATA); + smmu_flush(smmu); + + domain->priv = as; + + return 0; + +out: + return err; +} + +static void tegra_smmu_domain_destroy(struct iommu_domain *domain) +{ + struct tegra_smmu_address_space *as = domain->priv; + + /* TODO: free page directory and page tables */ + + tegra_smmu_free_asid(as->smmu, as->id); + kfree(as); +} + +static const struct tegra_smmu_swgroup * +tegra_smmu_find_swgroup(struct tegra_smmu *smmu, unsigned int swgroup) +{ + const struct tegra_smmu_swgroup *group = NULL; + unsigned int i; + + for (i = 0; i < smmu->soc->num_swgroups; i++) { + if (smmu->soc->swgroups[i].swgroup == swgroup) { + group = &smmu->soc->swgroups[i]; + break; + } + } + + return group; +} + +static int tegra_smmu_enable(struct tegra_smmu *smmu, unsigned int swgroup, + unsigned int asid) +{ + const struct tegra_smmu_swgroup *group; + unsigned int i; + u32 value; + + for (i = 0; i < smmu->soc->num_clients; i++) { + const struct tegra_mc_client *client = &smmu->soc->clients[i]; + + if (client->swgroup != swgroup) + continue; + + value = smmu_readl(smmu, client->smmu.reg); + value |= BIT(client->smmu.bit); + smmu_writel(smmu, value, client->smmu.reg); + } + + group = tegra_smmu_find_swgroup(smmu, swgroup); + if (group) { + value = smmu_readl(smmu, group->reg); + value &= ~SMMU_ASID_MASK; + value |= SMMU_ASID_VALUE(asid); + value |= SMMU_ASID_ENABLE; + smmu_writel(smmu, value, group->reg); + } + + return 0; +} + +static int tegra_smmu_disable(struct tegra_smmu *smmu, unsigned int swgroup, + unsigned int asid) +{ + const struct tegra_smmu_swgroup *group; + unsigned int i; + u32 value; + + group = tegra_smmu_find_swgroup(smmu, swgroup); + if (group) { + value = smmu_readl(smmu, group->reg); + value &= ~SMMU_ASID_MASK; + value |= SMMU_ASID_VALUE(asid); + value &= ~SMMU_ASID_ENABLE; + smmu_writel(smmu, value, group->reg); + } + + for (i = 0; i < smmu->soc->num_clients; i++) { + const struct tegra_mc_client *client = &smmu->soc->clients[i]; + + if (client->swgroup != swgroup) + continue; + + value = smmu_readl(smmu, client->smmu.reg); + value &= ~BIT(client->smmu.bit); + smmu_writel(smmu, value, client->smmu.reg); + } + + return 0; +} + +static int tegra_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) +{ + struct tegra_smmu_address_space *as = domain->priv; + struct tegra_smmu *smmu = as->smmu; + struct of_phandle_iter entry; + int err; + + of_property_for_each_phandle_with_args(entry, dev->of_node, "iommus", + "#iommu-cells", 0) { + unsigned int swgroup = entry.out_args.args[0]; + + if (entry.out_args.np != smmu->dev->of_node) + continue; + + err = tegra_smmu_enable(smmu, swgroup, as->id); + if (err < 0) + pr_err("failed to enable SWGROUP#%u\n", swgroup); + } + + return 0; +} + +static void tegra_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) +{ + struct tegra_smmu_address_space *as = domain->priv; + struct tegra_smmu *smmu = as->smmu; + struct of_phandle_iter entry; + int err; + + of_property_for_each_phandle_with_args(entry, dev->of_node, "iommus", + "#iommu-cells", 0) { + unsigned int swgroup; + + if (entry.out_args.np != smmu->dev->of_node) + continue; + + swgroup = entry.out_args.args[0]; + + err = tegra_smmu_disable(smmu, swgroup, as->id); + if (err < 0) { + pr_err("failed to enable SWGROUP#%u\n", swgroup); + } + } +} + +static u32 *as_get_pte(struct tegra_smmu_address_space *as, dma_addr_t iova, + struct page **pagep) +{ + struct tegra_smmu *smmu = smmu_handle; + u32 *pd = page_address(as->pd), *pt; + u32 pde = (iova >> SMMU_PDE_SHIFT) & 0x3ff; + u32 pte = (iova >> SMMU_PTE_SHIFT) & 0x3ff; + struct page *page; + unsigned int i; + + if (pd[pde] != SMMU_PDE_VACANT(pde)) { + page = pfn_to_page(pd[pde] & SMMU_PFN_MASK); + pt = page_address(page); + } else { + page = alloc_page(GFP_KERNEL | __GFP_DMA); + if (!page) + return NULL; + + pt = page_address(page); + SetPageReserved(page); + + for (i = 0; i < SMMU_NUM_PTE; i++) + pt[i] = SMMU_PTE_VACANT(i); + + smmu->soc->ops->flush_dcache(page, 0, SMMU_SIZE_PT); + + pd[pde] = SMMU_MK_PDE(page, SMMU_PDE_ATTR | SMMU_PDE_NEXT); + + smmu->soc->ops->flush_dcache(as->pd, pde << 2, 4); + smmu_flush_ptc(smmu, as->pd, pde << 2); + smmu_flush_tlb_section(smmu, as->id, iova); + smmu_flush(smmu); + } + + *pagep = page; + + return &pt[pte]; +} + +static int tegra_smmu_map(struct iommu_domain *domain, unsigned long iova, + phys_addr_t paddr, size_t size, int prot) +{ + struct tegra_smmu_address_space *as = domain->priv; + struct tegra_smmu *smmu = smmu_handle; + unsigned long offset; + struct page *page; + u32 *pte; + + pte = as_get_pte(as, iova, &page); + if (!pte) + return -ENOMEM; + + offset = offset_in_page(pte); + + *pte = __phys_to_pfn(paddr) | SMMU_PTE_ATTR; + + smmu->soc->ops->flush_dcache(page, offset, 4); + smmu_flush_ptc(smmu, page, offset); + smmu_flush_tlb_group(smmu, as->id, iova); + smmu_flush(smmu); + + return 0; +} + +static size_t tegra_smmu_unmap(struct iommu_domain *domain, unsigned long iova, + size_t size) +{ + struct tegra_smmu_address_space *as = domain->priv; + struct tegra_smmu *smmu = smmu_handle; + unsigned long offset; + struct page *page; + u32 *pte; + + pte = as_get_pte(as, iova, &page); + if (!pte) + return 0; + + offset = offset_in_page(pte); + *pte = 0; + + smmu->soc->ops->flush_dcache(page, offset, 4); + smmu_flush_ptc(smmu, page, offset); + smmu_flush_tlb_group(smmu, as->id, iova); + smmu_flush(smmu); + + return size; +} + +static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain, + dma_addr_t iova) +{ + struct tegra_smmu_address_space *as = domain->priv; + struct page *page; + unsigned long pfn; + u32 *pte; + + pte = as_get_pte(as, iova, &page); + pfn = *pte & SMMU_PFN_MASK; + + return PFN_PHYS(pfn); +} + +static int tegra_smmu_attach(struct iommu *iommu, struct device *dev) +{ + struct tegra_smmu *smmu = to_tegra_smmu(iommu); + struct tegra_smmu_group *group; + unsigned int i; + + for (i = 0; i < smmu->soc->num_groups; i++) { + group = iommu_group_get_iommudata(smmu->groups[i]); + + if (of_match_node(group->matches, dev->of_node)) { + pr_debug("adding device %s to group %s\n", + dev_name(dev), group->name); + iommu_group_add_device(smmu->groups[i], dev); + break; + } + } + + if (i == smmu->soc->num_groups) + return 0; + +#ifndef CONFIG_ARM64 + return arm_iommu_attach_device(dev, group->mapping); +#else + return 0; +#endif +} + +static int tegra_smmu_detach(struct iommu *iommu, struct device *dev) +{ + return 0; +} + +static const struct iommu_ops tegra_smmu_ops = { + .domain_init = tegra_smmu_domain_init, + .domain_destroy = tegra_smmu_domain_destroy, + .attach_dev = tegra_smmu_attach_dev, + .detach_dev = tegra_smmu_detach_dev, + .map = tegra_smmu_map, + .unmap = tegra_smmu_unmap, + .iova_to_phys = tegra_smmu_iova_to_phys, + .attach = tegra_smmu_attach, + .detach = tegra_smmu_detach, + + .pgsize_bitmap = SZ_4K, +}; + +static struct tegra_smmu *tegra_smmu_probe(struct device *dev, + const struct tegra_smmu_soc *soc, + void __iomem *regs) +{ + struct tegra_smmu *smmu; + unsigned int i; + size_t size; + u32 value; + int err; + + smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL); + if (!smmu) + return ERR_PTR(-ENOMEM); + + size = BITS_TO_LONGS(soc->num_asids) * sizeof(long); + + smmu->asids = devm_kzalloc(dev, size, GFP_KERNEL); + if (!smmu->asids) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&smmu->iommu.list); + mutex_init(&smmu->lock); + + smmu->iommu.ops = &tegra_smmu_ops; + smmu->iommu.dev = dev; + + smmu->regs = regs; + smmu->soc = soc; + smmu->dev = dev; + + smmu_handle = smmu; + bus_set_iommu(&platform_bus_type, &tegra_smmu_ops); + + smmu->num_groups = soc->num_groups; + + smmu->groups = devm_kcalloc(dev, smmu->num_groups, sizeof(*smmu->groups), + GFP_KERNEL); + if (!smmu->groups) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < smmu->num_groups; i++) { + struct tegra_smmu_group *group; + + smmu->groups[i] = iommu_group_alloc(); + if (IS_ERR(smmu->groups[i])) + return ERR_CAST(smmu->groups[i]); + + err = iommu_group_set_name(smmu->groups[i], soc->groups[i].name); + if (err < 0) { + } + + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) + return ERR_PTR(-ENOMEM); + + group->matches = soc->groups[i].matches; + group->asid = soc->groups[i].asid; + group->name = soc->groups[i].name; + + iommu_group_set_iommudata(smmu->groups[i], group, + tegra_smmu_group_release); + +#ifndef CONFIG_ARM64 + group->mapping = arm_iommu_create_mapping(&platform_bus_type, + 0, SZ_2G); + if (IS_ERR(group->mapping)) { + dev_err(dev, "failed to create mapping for group %s: %ld\n", + group->name, PTR_ERR(group->mapping)); + return ERR_CAST(group->mapping); + } +#endif + } + + value = (1 << 29) | (8 << 24) | 0x3f; + smmu_writel(smmu, value, 0x18); + + value = (1 << 29) | (1 << 28) | 0x20; + smmu_writel(smmu, value, 0x014); + + smmu_flush_ptc(smmu, NULL, 0); + smmu_flush_tlb(smmu); + smmu_writel(smmu, SMMU_CONFIG_ENABLE, SMMU_CONFIG); + smmu_flush(smmu); + + err = iommu_add(&smmu->iommu); + if (err < 0) + return ERR_PTR(err); + + return smmu; +} + +static int tegra_smmu_remove(struct tegra_smmu *smmu) +{ + iommu_remove(&smmu->iommu); + + return 0; +} + +#ifdef CONFIG_ARCH_TEGRA_124_SOC +static const struct tegra_smmu_soc tegra124_smmu_soc = { + .groups = tegra124_smmu_groups, + .num_groups = ARRAY_SIZE(tegra124_smmu_groups), + .clients = tegra124_mc_clients, + .num_clients = ARRAY_SIZE(tegra124_mc_clients), + .swgroups = tegra124_swgroups, + .num_swgroups = ARRAY_SIZE(tegra124_swgroups), + .num_asids = 128, + .atom_size = 32, + .ops = &tegra124_smmu_ops, +}; +#endif + +static const struct tegra_smmu_soc tegra132_smmu_soc = { + .groups = tegra124_smmu_groups, + .num_groups = ARRAY_SIZE(tegra124_smmu_groups), + .clients = tegra124_mc_clients, + .num_clients = ARRAY_SIZE(tegra124_mc_clients), + .swgroups = tegra124_swgroups, + .num_swgroups = ARRAY_SIZE(tegra124_swgroups), + .num_asids = 128, + .atom_size = 32, + .ops = &tegra132_smmu_ops, +}; + +struct tegra_mc { + struct device *dev; + struct tegra_smmu *smmu; + void __iomem *regs; + int irq; + + const struct tegra_mc_soc *soc; +}; + +static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset) +{ + return readl(mc->regs + offset); +} + +static inline void mc_writel(struct tegra_mc *mc, u32 value, unsigned long offset) +{ + writel(value, mc->regs + offset); +} + +struct tegra_mc_soc { + const struct tegra_mc_client *clients; + unsigned int num_clients; + + const struct tegra_smmu_soc *smmu; +}; + +#ifdef CONFIG_ARCH_TEGRA_124_SOC +static const struct tegra_mc_soc tegra124_mc_soc = { + .clients = tegra124_mc_clients, + .num_clients = ARRAY_SIZE(tegra124_mc_clients), + .smmu = &tegra124_smmu_soc, +}; +#endif + +static const struct tegra_mc_soc tegra132_mc_soc = { + .clients = tegra124_mc_clients, + .num_clients = ARRAY_SIZE(tegra124_mc_clients), + .smmu = &tegra132_smmu_soc, +}; + +static const struct of_device_id tegra_mc_of_match[] = { +#ifdef CONFIG_ARCH_TEGRA_124_SOC + { .compatible = "nvidia,tegra124-mc", .data = &tegra124_mc_soc }, +#endif + { .compatible = "nvidia,tegra132-mc", .data = &tegra132_mc_soc }, + { } +}; + +static irqreturn_t tegra124_mc_irq(int irq, void *data) +{ + struct tegra_mc *mc = data; + u32 value, status, mask; + + /* mask all interrupts to avoid flooding */ + mask = mc_readl(mc, MC_INTMASK); + mc_writel(mc, 0, MC_INTMASK); + + status = mc_readl(mc, MC_INTSTATUS); + mc_writel(mc, status, MC_INTSTATUS); + + dev_dbg(mc->dev, "INTSTATUS: %08x\n", status); + + if (status & MC_INT_DECERR_MTS) + dev_dbg(mc->dev, " DECERR_MTS\n"); + + if (status & MC_INT_SECERR_SEC) + dev_dbg(mc->dev, " SECERR_SEC\n"); + + if (status & MC_INT_DECERR_VPR) + dev_dbg(mc->dev, " DECERR_VPR\n"); + + if (status & MC_INT_INVALID_APB_ASID_UPDATE) + dev_dbg(mc->dev, " INVALID_APB_ASID_UPDATE\n"); + + if (status & MC_INT_INVALID_SMMU_PAGE) + dev_dbg(mc->dev, " INVALID_SMMU_PAGE\n"); + + if (status & MC_INT_ARBITRATION_EMEM) + dev_dbg(mc->dev, " ARBITRATION_EMEM\n"); + + if (status & MC_INT_SECURITY_VIOLATION) + dev_dbg(mc->dev, " SECURITY_VIOLATION\n"); + + if (status & MC_INT_DECERR_EMEM) + dev_dbg(mc->dev, " DECERR_EMEM\n"); + + value = mc_readl(mc, MC_ERR_STATUS); + + dev_dbg(mc->dev, "ERR_STATUS: %08x\n", value); + dev_dbg(mc->dev, " type: %x\n", (value >> 28) & 0x7); + dev_dbg(mc->dev, " protection: %x\n", (value >> 25) & 0x7); + dev_dbg(mc->dev, " adr_hi: %x\n", (value >> 20) & 0x3); + dev_dbg(mc->dev, " swap: %x\n", (value >> 18) & 0x1); + dev_dbg(mc->dev, " security: %x\n", (value >> 17) & 0x1); + dev_dbg(mc->dev, " r/w: %x\n", (value >> 16) & 0x1); + dev_dbg(mc->dev, " adr1: %x\n", (value >> 12) & 0x7); + dev_dbg(mc->dev, " client: %x\n", value & 0x7f); + + value = mc_readl(mc, MC_ERR_ADR); + dev_dbg(mc->dev, "ERR_ADR: %08x\n", value); + + mc_writel(mc, mask, MC_INTMASK); + + return IRQ_HANDLED; +} + +static int tegra_mc_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct resource *res; + struct tegra_mc *mc; + unsigned int i; + u32 value; + int err; + + match = of_match_node(tegra_mc_of_match, pdev->dev.of_node); + if (!match) + return -ENODEV; + + mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + platform_set_drvdata(pdev, mc); + mc->soc = match->data; + mc->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mc->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mc->regs)) + return PTR_ERR(mc->regs); + + for (i = 0; i < mc->soc->num_clients; i++) { + const struct latency_allowance *la = &mc->soc->clients[i].latency; + u32 value; + + value = readl(mc->regs + la->reg); + value &= ~(la->mask << la->shift); + value |= (la->def & la->mask) << la->shift; + writel(value, mc->regs + la->reg); + } + + mc->smmu = tegra_smmu_probe(&pdev->dev, mc->soc->smmu, mc->regs); + if (IS_ERR(mc->smmu)) { + dev_err(&pdev->dev, "failed to probe SMMU: %ld\n", + PTR_ERR(mc->smmu)); + return PTR_ERR(mc->smmu); + } + + mc->irq = platform_get_irq(pdev, 0); + if (mc->irq < 0) { + dev_err(&pdev->dev, "interrupt not specified\n"); + return mc->irq; + } + + err = devm_request_irq(&pdev->dev, mc->irq, tegra124_mc_irq, + IRQF_SHARED, dev_name(&pdev->dev), mc); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", mc->irq, + err); + return err; + } + + value = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | + MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | + MC_INT_ARBITRATION_EMEM | MC_INT_SECURITY_VIOLATION | + MC_INT_DECERR_EMEM; + mc_writel(mc, value, MC_INTMASK); + + return 0; +} + +static int tegra_mc_remove(struct platform_device *pdev) +{ + struct tegra_mc *mc = platform_get_drvdata(pdev); + int err; + + err = tegra_smmu_remove(mc->smmu); + if (err < 0) + dev_err(&pdev->dev, "failed to remove SMMU: %d\n", err); + + return 0; +} + +static struct platform_driver tegra_mc_driver = { + .driver = { + .name = "tegra124-mc", + .of_match_table = tegra_mc_of_match, + }, + .probe = tegra_mc_probe, + .remove = tegra_mc_remove, +}; +module_platform_driver(tegra_mc_driver); + +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); +MODULE_DESCRIPTION("NVIDIA Tegra124 Memory Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/dt-bindings/memory/tegra124-mc.h b/include/dt-bindings/memory/tegra124-mc.h new file mode 100644 index 000000000000..6b1617ce022f --- /dev/null +++ b/include/dt-bindings/memory/tegra124-mc.h @@ -0,0 +1,30 @@ +#ifndef DT_BINDINGS_MEMORY_TEGRA124_MC_H +#define DT_BINDINGS_MEMORY_TEGRA124_MC_H + +#define TEGRA_SWGROUP_DC 0 +#define TEGRA_SWGROUP_DCB 1 +#define TEGRA_SWGROUP_AFI 2 +#define TEGRA_SWGROUP_AVPC 3 +#define TEGRA_SWGROUP_HDA 4 +#define TEGRA_SWGROUP_HC 5 +#define TEGRA_SWGROUP_MSENC 6 +#define TEGRA_SWGROUP_PPCS 7 +#define TEGRA_SWGROUP_SATA 8 +#define TEGRA_SWGROUP_VDE 9 +#define TEGRA_SWGROUP_MPCORELP 10 +#define TEGRA_SWGROUP_MPCORE 11 +#define TEGRA_SWGROUP_ISP2 12 +#define TEGRA_SWGROUP_XUSB_HOST 13 +#define TEGRA_SWGROUP_XUSB_DEV 14 +#define TEGRA_SWGROUP_ISP2B 15 +#define TEGRA_SWGROUP_TSEC 16 +#define TEGRA_SWGROUP_A9AVP 17 +#define TEGRA_SWGROUP_GPU 18 +#define TEGRA_SWGROUP_SDMMC1A 19 +#define TEGRA_SWGROUP_SDMMC2A 20 +#define TEGRA_SWGROUP_SDMMC3A 21 +#define TEGRA_SWGROUP_SDMMC4A 22 +#define TEGRA_SWGROUP_VIC 23 +#define TEGRA_SWGROUP_VI 24 + +#endif