|
|
@@ -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); |