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

Nobuhiro Iwamatsu nobuhiro1.iwamatsu at toshiba.co.jp
Tue May 24 18:31:45 PDT 2022


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 at toshiba.co.jp>
---
 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 at toshiba.co.jp>
+ */
+
+#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





More information about the linux-arm-kernel mailing list