Files
kernel-zhihe-a210/drivers/iommu/riscv/iommu-platform.c
2025-12-25 15:40:45 +08:00

206 lines
5.5 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* RISC-V IOMMU as a platform device
*
* Copyright © 2023 FORTH-ICS/CARV
*
* Author: Nick Kossifidis <mick@ics.forth.gr>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of_platform.h>
#include <linux/bitfield.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
#include <linux/clk.h>
#include "iommu-bits.h"
#include "iommu.h"
#define A210_IOMMU_CAP 0x28d0008210
static int riscv_iommu_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct zh_iommu_device *iommus = NULL;
struct riscv_iommu_device *iommu = NULL;
struct resource *res = NULL;
struct device_node *np = dev->of_node;
u32 fctl = 0;
int irq = 0;
int ret = 0;
int count;
iommus = devm_kzalloc(dev, sizeof(*iommus), GFP_KERNEL);
if (!iommus)
return -ENOMEM;
iommus->dev = dev;
mutex_init(&iommus->lock);
INIT_LIST_HEAD(&iommus->iommus);
INIT_LIST_HEAD(&iommus->groups);
dev_set_drvdata(dev, iommus);
dev_set_name(dev, "%s", "zhihe,iommu");
iommus->iommu_ptw_aclk = devm_clk_get(dev, "iommu_ptw_aclk");
if (IS_ERR(iommus->iommu_ptw_aclk)) {
dev_err(dev, "failed to get iommu_ptw_aclk\n");
return -EINVAL;
}
ret = clk_prepare_enable(iommus->iommu_ptw_aclk);
if (ret < 0) {
dev_err(dev, "could not prepare or enable iommu_ptw_aclk\n");
clk_disable_unprepare(iommus->iommu_ptw_aclk);
return -EINVAL;
}
count = of_count_phandle_with_args(np, "power-domains", "#power-domain-cells");
if (count > 0) {
iommus->pd_devs = devm_kcalloc(dev, count, sizeof(*iommus->pd_devs), GFP_KERNEL);
if (!iommus->pd_devs)
return -ENOMEM;
iommus->num_pds = count;
for (int i = 0; i < count; i++) {
iommus->pd_devs[i] = dev_pm_domain_attach_by_id(dev, i);
if (IS_ERR(iommus->pd_devs[i]))
return PTR_ERR(iommus->pd_devs[i]);
device_link_add(dev, iommus->pd_devs[i], DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
}
}
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
for (int i = 0; i < pdev->num_resources; i++) {
const char *name;
iommu = devm_kzalloc(dev, sizeof(*iommu), GFP_KERNEL);
if (!iommu)
return -ENOMEM;
iommu->dev = dev;
INIT_LIST_HEAD(&iommu->list);
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
if (!res) {
dev_err(dev, "could not find resource for register region\n");
return -EINVAL;
}
iommu->reg = devm_platform_get_and_ioremap_resource(pdev, i, &res);
if (IS_ERR(iommu->reg)) {
ret = dev_err_probe(dev, PTR_ERR(iommu->reg),
"could not map register region\n");
goto fail;
}
of_property_read_string_index(np, "reg-names", i, &name);
iommu->name = kstrdup(name, GFP_KERNEL);
iommu->reg_phys = res->start;
ret = -ENODEV;
/* Sanity check: Did we get the whole register space ? */
if ((res->end - res->start + 1) < RISCV_IOMMU_REG_SIZE) {
dev_err(dev, "device region smaller than register file (0x%llx)\n",
res->end - res->start);
goto fail;
}
iommu->cap = riscv_iommu_readq(iommu, RISCV_IOMMU_REG_CAP);
if (iommu->cap != A210_IOMMU_CAP) {
dev_err_probe(dev, -ENODEV,
"IOMMU:%s Capacity Reg=0x%llx Error.Check clock power reset!\n", iommu->name, iommu->cap);
goto fail;
}
/* For now we only support WSIs until we have AIA support */
ret = FIELD_GET(RISCV_IOMMU_CAP_IGS, iommu->cap);
if (ret == RISCV_IOMMU_CAP_IGS_MSI) {
dev_err(dev, "IOMMU only supports MSIs\n");
goto fail;
}
/* Parse IRQ assignment */
irq = platform_get_irq(pdev, i);
if (irq > 0)
iommu->irq = irq;
else {
dev_err(dev, "no IRQ provided for iommu\n");
goto fail;
}
/* Make sure fctl.WSI is set */
fctl = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_FCTL);
fctl |= RISCV_IOMMU_FCTL_WSI;
riscv_iommu_writel(iommu, RISCV_IOMMU_REG_FCTL, fctl);
/* Parse Queue lengts */
ret = of_property_read_u32(pdev->dev.of_node, "cmdq_len", &iommu->cmdq_len);
if (!ret)
dev_info(dev, "command queue length set to %i\n", iommu->cmdq_len);
ret = of_property_read_u32(pdev->dev.of_node, "fltq_len", &iommu->fltq_len);
if (!ret)
dev_info(dev, "fault/event queue length set to %i\n", iommu->fltq_len);
ret = riscv_iommu_init(iommu);
if (ret) {
dev_err(dev, "riscv_iommu_init failed %d", ret);
goto fail;
}
list_add_tail(&iommu->list, &iommus->iommus);
}
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
riscv_iommu_register(iommus);
list_for_each_entry(iommu, &iommus->iommus, list) {
//clear tlb with a tmp domain
struct riscv_iommu_domain iommu_domain;
iommu_domain.iommus = iommus;
iommu_domain.pscid = 0;
iommu_domain.domain.ops = iommus->iommu.ops->default_domain_ops;
iommu_flush_iotlb_all(&iommu_domain.domain);
}
fail:
/* Note: devres_release_all() will release iommu and iommu->reg */
return ret;
};
static void riscv_iommu_platform_remove(struct platform_device *pdev)
{
riscv_iommu_remove(dev_get_drvdata(&pdev->dev));
}
static void riscv_iommu_platform_shutdown(struct platform_device *pdev)
{
return;
}
static const struct of_device_id riscv_iommu_of_match[] = {
{.compatible = "zhihe,iommu",},
{},
};
MODULE_DEVICE_TABLE(of, riscv_iommu_of_match);
static struct platform_driver riscv_iommu_platform_driver = {
.driver = {
.name = "zhihe,iommu",
.of_match_table = riscv_iommu_of_match,
.suppress_bind_attrs = true,
},
.probe = riscv_iommu_platform_probe,
.remove_new = riscv_iommu_platform_remove,
.shutdown = riscv_iommu_platform_shutdown,
};
module_driver(riscv_iommu_platform_driver, platform_driver_register,
platform_driver_unregister);