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