[PATCH 1/3] iommu: Add Visconti5 IOMMU driver

From: Nobuhiro Iwamatsu
Date: Tue May 24 2022 - 21:32:56 EST


Add IOMMU (ATU) driver can bse used for Visconti5's multimedia IPs, such as
DCNN (Deep Convolutional Neural Network), VIIF(Video Input), VOIF(Video
output), and others.

Signed-off-by: Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@xxxxxxxxxxxxx>
---
drivers/iommu/Kconfig | 7 +
drivers/iommu/Makefile | 1 +
drivers/iommu/visconti-atu.c | 426 +++++++++++++++++++++++++++++++++++
3 files changed, 434 insertions(+)
create mode 100644 drivers/iommu/visconti-atu.c

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index c79a0df090c0..8a4351020b7f 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -486,4 +486,11 @@ config SPRD_IOMMU

Say Y here if you want to use the multimedia devices listed above.

+config VISCONTI_ATU
+ tristate "Toshiba Visconti5 IOMMU Support"
+ depends on ARCH_VISCONTI || COMPILE_TEST
+ select IOMMU_API
+ help
+ Support for the IOMMU API for Toshiba Visconti5 ARM SoCs.
+
endif # IOMMU_SUPPORT
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 44475a9b3eea..077189f908ea 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -30,3 +30,4 @@ obj-$(CONFIG_VIRTIO_IOMMU) += virtio-iommu.o
obj-$(CONFIG_IOMMU_SVA) += iommu-sva-lib.o io-pgfault.o
obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o
obj-$(CONFIG_APPLE_DART) += apple-dart.o
+obj-$(CONFIG_VISCONTI_ATU) += visconti-atu.o
diff --git a/drivers/iommu/visconti-atu.c b/drivers/iommu/visconti-atu.c
new file mode 100644
index 000000000000..269c912ad4c9
--- /dev/null
+++ b/drivers/iommu/visconti-atu.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Toshiba Visconti5 IOMMU (ATU) driver
+ *
+ * (C) Copyright 2022 Toshiba Electronic Devices & Storage Corporation
+ * (C) Copyright 2022 Toshiba CORPORATION
+ *
+ * Author: Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@xxxxxxxxxxxxx>
+ */
+
+#include <linux/dma-iommu.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+/* Regsiter address */
+#define ATU_AT_EN 0x0000
+#define ATU_AT_ENTRY_EN 0x0020
+
+#define ATU_AT_BLADDR 0x0030
+#define ATU_AT_ELADDR 0x0038
+#define ATU_AT_BGADDR0 0x0040
+#define ATU_AT_BGADDR1 0x0044
+#define ATU_AT_CONF 0x0048
+#define ATU_AT_REG(n, reg) (0x20 * n + reg)
+
+#define ATU_INT_START 0x0440
+#define ATU_INT_MASKED_STAT 0x0444
+#define ATU_INT_MASK 0x0448
+#define ATU_RP_CONF 0x0450
+#define ATU_ERR_ADDR 0x0454
+#define ATU_ERR_CLR 0x045C
+#define ATU_STAT 0x0460
+
+#define ATU_BGADDR_MASK GENMASK(31, 0)
+
+#define ATU_IOMMU_PGSIZE_BITMAP 0x7ffff000 /* SZ_1G - SZ_4K */
+#define ATU_MAX_IOMMU_ENTRY 32
+
+struct visconti_atu_device {
+ struct device *dev;
+ void __iomem *base;
+ struct iommu_device iommu;
+ struct iommu_group *group;
+
+ unsigned int num_entry;
+ unsigned int num_map_entry;
+ unsigned int enable_entry;
+ unsigned long iova[ATU_MAX_IOMMU_ENTRY];
+ phys_addr_t paddr[ATU_MAX_IOMMU_ENTRY];
+ size_t size[ATU_MAX_IOMMU_ENTRY];
+
+ spinlock_t lock;
+};
+
+struct visconti_atu_domain {
+ struct visconti_atu_device *atu;
+ struct iommu_domain io_domain;
+ struct mutex mutex;
+};
+
+static const struct iommu_ops visconti_atu_ops;
+
+static struct visconti_atu_domain *to_atu_domain(struct iommu_domain *domain)
+{
+ return container_of(domain, struct visconti_atu_domain, io_domain);
+}
+
+static inline void visconti_atu_write(struct visconti_atu_device *atu, u32 reg,
+ u32 val)
+{
+ writel_relaxed(val, atu->base + reg);
+}
+
+static inline u32 visconti_atu_read(struct visconti_atu_device *atu, u32 reg)
+{
+ return readl_relaxed(atu->base + reg);
+}
+
+static void visconti_atu_enable_entry(struct visconti_atu_device *atu,
+ int num)
+{
+ dev_dbg(atu->dev, "enable ATU: %d\n", atu->enable_entry);
+
+ visconti_atu_write(atu, ATU_AT_EN, 0);
+ if (atu->enable_entry & BIT(num)) {
+ visconti_atu_write(atu,
+ ATU_AT_REG(num, ATU_AT_BLADDR),
+ atu->iova[num]);
+ visconti_atu_write(atu,
+ ATU_AT_REG(num, ATU_AT_ELADDR),
+ atu->iova[num] + atu->size[num] - 1);
+ visconti_atu_write(atu,
+ ATU_AT_REG(num, ATU_AT_BGADDR0),
+ atu->iova[num] & ATU_BGADDR_MASK);
+ visconti_atu_write(atu,
+ ATU_AT_REG(num, ATU_AT_BGADDR1),
+ (atu->iova[num] >> 32) & ATU_BGADDR_MASK);
+ }
+ visconti_atu_write(atu, ATU_AT_ENTRY_EN, atu->enable_entry);
+ visconti_atu_write(atu, ATU_AT_EN, 1);
+}
+
+static void visconti_atu_disable_entry(struct visconti_atu_device *atu)
+{
+ dev_dbg(atu->dev, "disable ATU: %d\n", atu->enable_entry);
+
+ visconti_atu_write(atu, ATU_AT_EN, 0);
+ visconti_atu_write(atu, ATU_AT_ENTRY_EN, atu->enable_entry);
+ visconti_atu_write(atu, ATU_AT_EN, 1);
+}
+
+static int visconti_atu_attach_device(struct iommu_domain *io_domain,
+ struct device *dev)
+{
+ struct visconti_atu_domain *domain = to_atu_domain(io_domain);
+ struct visconti_atu_device *atu = dev_iommu_priv_get(dev);
+ int ret = 0;
+
+ if (!atu) {
+ dev_err(dev, "Cannot attach to ATU\n");
+ return -ENXIO;
+ }
+
+ mutex_lock(&domain->mutex);
+
+ if (!domain->atu) {
+ domain->atu = atu;
+ } else if (domain->atu != atu) {
+ dev_err(dev, "Can't attach ATU %s to domain on ATU %s\n",
+ dev_name(atu->dev), dev_name(domain->atu->dev));
+ ret = -EINVAL;
+ } else {
+ dev_warn(dev, "Reusing ATU context\n");
+ }
+
+ mutex_unlock(&domain->mutex);
+
+ return ret;
+}
+
+static void visconti_atu_detach_device(struct iommu_domain *io_domain,
+ struct device *dev)
+{
+ struct visconti_atu_domain *domain = to_atu_domain(io_domain);
+ struct visconti_atu_device *atu = dev_iommu_priv_get(dev);
+
+ if (domain->atu != atu)
+ return;
+
+ domain->atu = NULL;
+}
+
+static int visconti_atu_map(struct iommu_domain *io_domain,
+ unsigned long iova,
+ phys_addr_t paddr,
+ size_t size, int prot, gfp_t gfp)
+{
+ struct visconti_atu_domain *domain = to_atu_domain(io_domain);
+ struct visconti_atu_device *atu = domain->atu;
+ unsigned long flags;
+ unsigned int i;
+
+ if (!domain)
+ return -ENODEV;
+
+ spin_lock_irqsave(&atu->lock, flags);
+ for (i = 0; i < atu->num_map_entry; i++) {
+ if (!(atu->enable_entry & BIT(i))) {
+ atu->enable_entry |= BIT(i);
+ atu->iova[i] = iova;
+ atu->paddr[i] = paddr;
+ atu->size[i] = size;
+
+ visconti_atu_enable_entry(atu, i);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&atu->lock, flags);
+
+ if (i == atu->num_map_entry) {
+ dev_err(atu->dev, "map: not enough entry.\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static size_t visconti_atu_unmap(struct iommu_domain *io_domain,
+ unsigned long iova,
+ size_t size,
+ struct iommu_iotlb_gather *iotlb_gather)
+{
+ struct visconti_atu_domain *domain = to_atu_domain(io_domain);
+ struct visconti_atu_device *atu = domain->atu;
+ size_t tmp_size = size;
+ unsigned long flags;
+ unsigned int i;
+
+ spin_lock_irqsave(&atu->lock, flags);
+
+ while (tmp_size != 0) {
+ for (i = 0; i < atu->num_map_entry; i++) {
+ if (atu->iova[i] != iova)
+ continue;
+
+ atu->enable_entry &= ~BIT(i);
+ iova += atu->size[i];
+ tmp_size -= atu->size[i];
+
+ visconti_atu_disable_entry(atu);
+
+ break;
+ }
+ if (i == atu->num_map_entry) {
+ dev_err(atu->dev, "unmap: not found entry.\n");
+ size = 0;
+ goto out;
+ }
+ }
+
+ if (!atu->num_map_entry)
+ visconti_atu_write(atu, ATU_AT_EN, 0);
+out:
+ spin_unlock_irqrestore(&atu->lock, flags);
+ return size;
+}
+
+static phys_addr_t visconti_atu_iova_to_phys(struct iommu_domain *io_domain,
+ dma_addr_t iova)
+{
+ struct visconti_atu_domain *domain = to_atu_domain(io_domain);
+ struct visconti_atu_device *atu = domain->atu;
+ phys_addr_t paddr = 0;
+ unsigned int i;
+
+ for (i = 0; i < atu->num_map_entry; i++) {
+ if (!(atu->enable_entry & BIT(i)))
+ continue;
+ if (atu->iova[i] <= iova && iova < (atu->iova[i] + atu->size[i])) {
+ paddr = atu->paddr[i];
+ paddr += iova & (atu->size[i] - 1);
+ break;
+ }
+ }
+
+ dev_dbg(atu->dev, "iova_to_phys: %llx -> %llx\n", iova, paddr);
+
+ return paddr;
+}
+
+static int visconti_atu_of_xlate(struct device *dev, struct of_phandle_args *args)
+{
+ if (!dev_iommu_priv_get(dev)) {
+ struct platform_device *pdev;
+
+ pdev = of_find_device_by_node(args->np);
+ dev_iommu_priv_set(dev, platform_get_drvdata(pdev));
+ platform_device_put(pdev);
+ }
+
+ return 0;
+}
+
+static struct iommu_domain *visconti_atu_domain_alloc(unsigned int type)
+{
+ struct visconti_atu_domain *domain;
+
+ if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA)
+ return NULL;
+
+ domain = kzalloc(sizeof(*domain), GFP_KERNEL);
+ if (!domain)
+ return NULL;
+
+ mutex_init(&domain->mutex);
+
+ domain->io_domain.geometry.aperture_start = 0;
+ domain->io_domain.geometry.aperture_end = DMA_BIT_MASK(32);
+ domain->io_domain.geometry.force_aperture = true;
+
+ return &domain->io_domain;
+}
+
+static void visconti_atu_domain_free(struct iommu_domain *io_domain)
+{
+ struct visconti_atu_domain *domain = to_atu_domain(io_domain);
+
+ kfree(domain);
+}
+
+static struct iommu_device *visconti_atu_probe_device(struct device *dev)
+{
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ struct visconti_atu_device *atu;
+
+ if (!fwspec || fwspec->ops != &visconti_atu_ops)
+ return ERR_PTR(-ENODEV);
+
+ atu = dev_iommu_priv_get(dev);
+ return &atu->iommu;
+}
+
+static void visconti_atu_release_device(struct device *dev)
+{
+ struct visconti_atu_device *atu = dev_iommu_priv_get(dev);
+
+ if (!atu)
+ return;
+
+ iommu_fwspec_free(dev);
+}
+
+static const struct iommu_ops visconti_atu_ops = {
+ .domain_alloc = visconti_atu_domain_alloc,
+ .probe_device = visconti_atu_probe_device,
+ .release_device = visconti_atu_release_device,
+ .device_group = generic_device_group,
+ .of_xlate = visconti_atu_of_xlate,
+ .pgsize_bitmap = ATU_IOMMU_PGSIZE_BITMAP,
+ .default_domain_ops = &(const struct iommu_domain_ops) {
+ .attach_dev = visconti_atu_attach_device,
+ .detach_dev = visconti_atu_detach_device,
+ .map = visconti_atu_map,
+ .unmap = visconti_atu_unmap,
+ .iova_to_phys = visconti_atu_iova_to_phys,
+ .free = visconti_atu_domain_free,
+ }
+};
+
+static int visconti_atu_probe(struct platform_device *pdev)
+{
+ struct visconti_atu_device *atu;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ u32 reserved_entry;
+ int ret;
+
+ atu = devm_kzalloc(&pdev->dev, sizeof(*atu), GFP_KERNEL);
+ if (!atu)
+ return -ENOMEM;
+
+ ret = of_property_read_u32(dev->of_node, "toshiba,max-entry",
+ &atu->num_entry);
+ if (ret < 0) {
+ dev_err(dev, "cannot get max-entry data\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(dev->of_node, "toshiba,reserved-entry",
+ &reserved_entry);
+ if (ret < 0)
+ reserved_entry = 0;
+
+ if (atu->num_entry < reserved_entry)
+ return -EINVAL;
+
+ atu->num_map_entry = atu->num_entry - reserved_entry;
+ atu->enable_entry = 0;
+ atu->dev = dev;
+
+ atu->group = iommu_group_alloc();
+ if (IS_ERR(atu->group)) {
+ ret = PTR_ERR(atu->group);
+ goto out;
+ }
+
+ spin_lock_init(&atu->lock);
+
+ atu->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(atu->base)) {
+ ret = PTR_ERR(atu->base);
+ goto out;
+ }
+
+ ret = iommu_device_sysfs_add(&atu->iommu, dev, NULL, dev_name(dev));
+ if (ret)
+ goto out;
+
+ ret = iommu_device_register(&atu->iommu, &visconti_atu_ops, dev);
+ if (ret)
+ goto remove_sysfs;
+
+ if (!iommu_present(&platform_bus_type))
+ bus_set_iommu(&platform_bus_type, &visconti_atu_ops);
+ platform_set_drvdata(pdev, atu);
+
+ return 0;
+
+remove_sysfs:
+ iommu_device_sysfs_remove(&atu->iommu);
+out:
+ return ret;
+}
+
+static int visconti_atu_remove(struct platform_device *pdev)
+{
+ struct visconti_atu_device *atu = platform_get_drvdata(pdev);
+
+ iommu_device_sysfs_remove(&atu->iommu);
+ iommu_device_unregister(&atu->iommu);
+
+ return 0;
+}
+
+static const struct of_device_id visconti_atu_of_match[] = {
+ { .compatible = "toshiba,visconti-atu", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, visconti_atu_of_match);
+
+static struct platform_driver visconti_atu_driver = {
+ .driver = {
+ .name = "visconti-atu",
+ .of_match_table = visconti_atu_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = visconti_atu_probe,
+ .remove = visconti_atu_remove,
+};
+
+builtin_platform_driver(visconti_atu_driver);
--
2.36.0