426 changes: 426 additions & 0 deletions 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@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);