123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Unisoc IOMMU driver
- *
- * Copyright (C) 2020 Unisoc, Inc.
- * Author: Chunyan Zhang <[email protected]>
- */
- #include <linux/clk.h>
- #include <linux/device.h>
- #include <linux/dma-mapping.h>
- #include <linux/errno.h>
- #include <linux/iommu.h>
- #include <linux/mfd/syscon.h>
- #include <linux/module.h>
- #include <linux/of_platform.h>
- #include <linux/regmap.h>
- #include <linux/slab.h>
- #define SPRD_IOMMU_PAGE_SHIFT 12
- #define SPRD_IOMMU_PAGE_SIZE SZ_4K
- #define SPRD_EX_CFG 0x0
- #define SPRD_IOMMU_VAOR_BYPASS BIT(4)
- #define SPRD_IOMMU_GATE_EN BIT(1)
- #define SPRD_IOMMU_EN BIT(0)
- #define SPRD_EX_UPDATE 0x4
- #define SPRD_EX_FIRST_VPN 0x8
- #define SPRD_EX_VPN_RANGE 0xc
- #define SPRD_EX_FIRST_PPN 0x10
- #define SPRD_EX_DEFAULT_PPN 0x14
- #define SPRD_IOMMU_VERSION 0x0
- #define SPRD_VERSION_MASK GENMASK(15, 8)
- #define SPRD_VERSION_SHIFT 0x8
- #define SPRD_VAU_CFG 0x4
- #define SPRD_VAU_UPDATE 0x8
- #define SPRD_VAU_AUTH_CFG 0xc
- #define SPRD_VAU_FIRST_PPN 0x10
- #define SPRD_VAU_DEFAULT_PPN_RD 0x14
- #define SPRD_VAU_DEFAULT_PPN_WR 0x18
- #define SPRD_VAU_FIRST_VPN 0x1c
- #define SPRD_VAU_VPN_RANGE 0x20
- enum sprd_iommu_version {
- SPRD_IOMMU_EX,
- SPRD_IOMMU_VAU,
- };
- /*
- * struct sprd_iommu_device - high-level sprd IOMMU device representation,
- * including hardware information and configuration, also driver data, etc
- *
- * @ver: sprd IOMMU IP version
- * @prot_page_va: protect page base virtual address
- * @prot_page_pa: protect page base physical address, data would be
- * written to here while translation fault
- * @base: mapped base address for accessing registers
- * @dev: pointer to basic device structure
- * @iommu: IOMMU core representation
- * @group: IOMMU group
- * @eb: gate clock which controls IOMMU access
- */
- struct sprd_iommu_device {
- enum sprd_iommu_version ver;
- u32 *prot_page_va;
- dma_addr_t prot_page_pa;
- void __iomem *base;
- struct device *dev;
- struct iommu_device iommu;
- struct iommu_group *group;
- struct clk *eb;
- };
- struct sprd_iommu_domain {
- spinlock_t pgtlock; /* lock for page table */
- struct iommu_domain domain;
- u32 *pgt_va; /* page table virtual address base */
- dma_addr_t pgt_pa; /* page table physical address base */
- struct sprd_iommu_device *sdev;
- };
- static const struct iommu_ops sprd_iommu_ops;
- static struct sprd_iommu_domain *to_sprd_domain(struct iommu_domain *dom)
- {
- return container_of(dom, struct sprd_iommu_domain, domain);
- }
- static inline void
- sprd_iommu_write(struct sprd_iommu_device *sdev, unsigned int reg, u32 val)
- {
- writel_relaxed(val, sdev->base + reg);
- }
- static inline u32
- sprd_iommu_read(struct sprd_iommu_device *sdev, unsigned int reg)
- {
- return readl_relaxed(sdev->base + reg);
- }
- static inline void
- sprd_iommu_update_bits(struct sprd_iommu_device *sdev, unsigned int reg,
- u32 mask, u32 shift, u32 val)
- {
- u32 t = sprd_iommu_read(sdev, reg);
- t = (t & (~(mask << shift))) | ((val & mask) << shift);
- sprd_iommu_write(sdev, reg, t);
- }
- static inline int
- sprd_iommu_get_version(struct sprd_iommu_device *sdev)
- {
- int ver = (sprd_iommu_read(sdev, SPRD_IOMMU_VERSION) &
- SPRD_VERSION_MASK) >> SPRD_VERSION_SHIFT;
- switch (ver) {
- case SPRD_IOMMU_EX:
- case SPRD_IOMMU_VAU:
- return ver;
- default:
- return -EINVAL;
- }
- }
- static size_t
- sprd_iommu_pgt_size(struct iommu_domain *domain)
- {
- return ((domain->geometry.aperture_end -
- domain->geometry.aperture_start + 1) >>
- SPRD_IOMMU_PAGE_SHIFT) * sizeof(u32);
- }
- static struct iommu_domain *sprd_iommu_domain_alloc(unsigned int domain_type)
- {
- struct sprd_iommu_domain *dom;
- if (domain_type != IOMMU_DOMAIN_DMA && domain_type != IOMMU_DOMAIN_UNMANAGED)
- return NULL;
- dom = kzalloc(sizeof(*dom), GFP_KERNEL);
- if (!dom)
- return NULL;
- spin_lock_init(&dom->pgtlock);
- dom->domain.geometry.aperture_start = 0;
- dom->domain.geometry.aperture_end = SZ_256M - 1;
- dom->domain.geometry.force_aperture = true;
- return &dom->domain;
- }
- static void sprd_iommu_domain_free(struct iommu_domain *domain)
- {
- struct sprd_iommu_domain *dom = to_sprd_domain(domain);
- kfree(dom);
- }
- static void sprd_iommu_first_vpn(struct sprd_iommu_domain *dom)
- {
- struct sprd_iommu_device *sdev = dom->sdev;
- u32 val;
- unsigned int reg;
- if (sdev->ver == SPRD_IOMMU_EX)
- reg = SPRD_EX_FIRST_VPN;
- else
- reg = SPRD_VAU_FIRST_VPN;
- val = dom->domain.geometry.aperture_start >> SPRD_IOMMU_PAGE_SHIFT;
- sprd_iommu_write(sdev, reg, val);
- }
- static void sprd_iommu_vpn_range(struct sprd_iommu_domain *dom)
- {
- struct sprd_iommu_device *sdev = dom->sdev;
- u32 val;
- unsigned int reg;
- if (sdev->ver == SPRD_IOMMU_EX)
- reg = SPRD_EX_VPN_RANGE;
- else
- reg = SPRD_VAU_VPN_RANGE;
- val = (dom->domain.geometry.aperture_end -
- dom->domain.geometry.aperture_start) >> SPRD_IOMMU_PAGE_SHIFT;
- sprd_iommu_write(sdev, reg, val);
- }
- static void sprd_iommu_first_ppn(struct sprd_iommu_domain *dom)
- {
- u32 val = dom->pgt_pa >> SPRD_IOMMU_PAGE_SHIFT;
- struct sprd_iommu_device *sdev = dom->sdev;
- unsigned int reg;
- if (sdev->ver == SPRD_IOMMU_EX)
- reg = SPRD_EX_FIRST_PPN;
- else
- reg = SPRD_VAU_FIRST_PPN;
- sprd_iommu_write(sdev, reg, val);
- }
- static void sprd_iommu_default_ppn(struct sprd_iommu_device *sdev)
- {
- u32 val = sdev->prot_page_pa >> SPRD_IOMMU_PAGE_SHIFT;
- if (sdev->ver == SPRD_IOMMU_EX) {
- sprd_iommu_write(sdev, SPRD_EX_DEFAULT_PPN, val);
- } else if (sdev->ver == SPRD_IOMMU_VAU) {
- sprd_iommu_write(sdev, SPRD_VAU_DEFAULT_PPN_RD, val);
- sprd_iommu_write(sdev, SPRD_VAU_DEFAULT_PPN_WR, val);
- }
- }
- static void sprd_iommu_hw_en(struct sprd_iommu_device *sdev, bool en)
- {
- unsigned int reg_cfg;
- u32 mask, val;
- if (sdev->ver == SPRD_IOMMU_EX)
- reg_cfg = SPRD_EX_CFG;
- else
- reg_cfg = SPRD_VAU_CFG;
- mask = SPRD_IOMMU_EN | SPRD_IOMMU_GATE_EN;
- val = en ? mask : 0;
- sprd_iommu_update_bits(sdev, reg_cfg, mask, 0, val);
- }
- static int sprd_iommu_attach_device(struct iommu_domain *domain,
- struct device *dev)
- {
- struct sprd_iommu_device *sdev = dev_iommu_priv_get(dev);
- struct sprd_iommu_domain *dom = to_sprd_domain(domain);
- size_t pgt_size = sprd_iommu_pgt_size(domain);
- if (dom->sdev) {
- pr_err("There's already a device attached to this domain.\n");
- return -EINVAL;
- }
- dom->pgt_va = dma_alloc_coherent(sdev->dev, pgt_size, &dom->pgt_pa, GFP_KERNEL);
- if (!dom->pgt_va)
- return -ENOMEM;
- dom->sdev = sdev;
- sprd_iommu_first_ppn(dom);
- sprd_iommu_first_vpn(dom);
- sprd_iommu_vpn_range(dom);
- sprd_iommu_default_ppn(sdev);
- sprd_iommu_hw_en(sdev, true);
- return 0;
- }
- static void sprd_iommu_detach_device(struct iommu_domain *domain,
- struct device *dev)
- {
- struct sprd_iommu_domain *dom = to_sprd_domain(domain);
- struct sprd_iommu_device *sdev = dom->sdev;
- size_t pgt_size = sprd_iommu_pgt_size(domain);
- if (!sdev)
- return;
- dma_free_coherent(sdev->dev, pgt_size, dom->pgt_va, dom->pgt_pa);
- sprd_iommu_hw_en(sdev, false);
- dom->sdev = NULL;
- }
- static int sprd_iommu_map(struct iommu_domain *domain, unsigned long iova,
- phys_addr_t paddr, size_t size, int prot, gfp_t gfp)
- {
- struct sprd_iommu_domain *dom = to_sprd_domain(domain);
- unsigned int page_num = size >> SPRD_IOMMU_PAGE_SHIFT;
- unsigned long flags;
- unsigned int i;
- u32 *pgt_base_iova;
- u32 pabase = (u32)paddr;
- unsigned long start = domain->geometry.aperture_start;
- unsigned long end = domain->geometry.aperture_end;
- if (!dom->sdev) {
- pr_err("No sprd_iommu_device attached to the domain\n");
- return -EINVAL;
- }
- if (iova < start || (iova + size) > (end + 1)) {
- dev_err(dom->sdev->dev, "(iova(0x%lx) + size(%zx)) are not in the range!\n",
- iova, size);
- return -EINVAL;
- }
- pgt_base_iova = dom->pgt_va + ((iova - start) >> SPRD_IOMMU_PAGE_SHIFT);
- spin_lock_irqsave(&dom->pgtlock, flags);
- for (i = 0; i < page_num; i++) {
- pgt_base_iova[i] = pabase >> SPRD_IOMMU_PAGE_SHIFT;
- pabase += SPRD_IOMMU_PAGE_SIZE;
- }
- spin_unlock_irqrestore(&dom->pgtlock, flags);
- return 0;
- }
- static size_t sprd_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
- size_t size, struct iommu_iotlb_gather *iotlb_gather)
- {
- struct sprd_iommu_domain *dom = to_sprd_domain(domain);
- unsigned long flags;
- u32 *pgt_base_iova;
- unsigned int page_num = size >> SPRD_IOMMU_PAGE_SHIFT;
- unsigned long start = domain->geometry.aperture_start;
- unsigned long end = domain->geometry.aperture_end;
- if (iova < start || (iova + size) > (end + 1))
- return -EINVAL;
- pgt_base_iova = dom->pgt_va + ((iova - start) >> SPRD_IOMMU_PAGE_SHIFT);
- spin_lock_irqsave(&dom->pgtlock, flags);
- memset(pgt_base_iova, 0, page_num * sizeof(u32));
- spin_unlock_irqrestore(&dom->pgtlock, flags);
- return 0;
- }
- static void sprd_iommu_sync_map(struct iommu_domain *domain,
- unsigned long iova, size_t size)
- {
- struct sprd_iommu_domain *dom = to_sprd_domain(domain);
- unsigned int reg;
- if (dom->sdev->ver == SPRD_IOMMU_EX)
- reg = SPRD_EX_UPDATE;
- else
- reg = SPRD_VAU_UPDATE;
- /* clear IOMMU TLB buffer after page table updated */
- sprd_iommu_write(dom->sdev, reg, 0xffffffff);
- }
- static void sprd_iommu_sync(struct iommu_domain *domain,
- struct iommu_iotlb_gather *iotlb_gather)
- {
- sprd_iommu_sync_map(domain, 0, 0);
- }
- static phys_addr_t sprd_iommu_iova_to_phys(struct iommu_domain *domain,
- dma_addr_t iova)
- {
- struct sprd_iommu_domain *dom = to_sprd_domain(domain);
- unsigned long flags;
- phys_addr_t pa;
- unsigned long start = domain->geometry.aperture_start;
- unsigned long end = domain->geometry.aperture_end;
- if (WARN_ON(iova < start || iova > end))
- return 0;
- spin_lock_irqsave(&dom->pgtlock, flags);
- pa = *(dom->pgt_va + ((iova - start) >> SPRD_IOMMU_PAGE_SHIFT));
- pa = (pa << SPRD_IOMMU_PAGE_SHIFT) + ((iova - start) & (SPRD_IOMMU_PAGE_SIZE - 1));
- spin_unlock_irqrestore(&dom->pgtlock, flags);
- return pa;
- }
- static struct iommu_device *sprd_iommu_probe_device(struct device *dev)
- {
- struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
- struct sprd_iommu_device *sdev;
- if (!fwspec || fwspec->ops != &sprd_iommu_ops)
- return ERR_PTR(-ENODEV);
- sdev = dev_iommu_priv_get(dev);
- return &sdev->iommu;
- }
- static struct iommu_group *sprd_iommu_device_group(struct device *dev)
- {
- struct sprd_iommu_device *sdev = dev_iommu_priv_get(dev);
- return iommu_group_ref_get(sdev->group);
- }
- static int sprd_iommu_of_xlate(struct device *dev, struct of_phandle_args *args)
- {
- struct platform_device *pdev;
- if (!dev_iommu_priv_get(dev)) {
- pdev = of_find_device_by_node(args->np);
- dev_iommu_priv_set(dev, platform_get_drvdata(pdev));
- platform_device_put(pdev);
- }
- return 0;
- }
- static const struct iommu_ops sprd_iommu_ops = {
- .domain_alloc = sprd_iommu_domain_alloc,
- .probe_device = sprd_iommu_probe_device,
- .device_group = sprd_iommu_device_group,
- .of_xlate = sprd_iommu_of_xlate,
- .pgsize_bitmap = ~0UL << SPRD_IOMMU_PAGE_SHIFT,
- .owner = THIS_MODULE,
- .default_domain_ops = &(const struct iommu_domain_ops) {
- .attach_dev = sprd_iommu_attach_device,
- .detach_dev = sprd_iommu_detach_device,
- .map = sprd_iommu_map,
- .unmap = sprd_iommu_unmap,
- .iotlb_sync_map = sprd_iommu_sync_map,
- .iotlb_sync = sprd_iommu_sync,
- .iova_to_phys = sprd_iommu_iova_to_phys,
- .free = sprd_iommu_domain_free,
- }
- };
- static const struct of_device_id sprd_iommu_of_match[] = {
- { .compatible = "sprd,iommu-v1" },
- { },
- };
- MODULE_DEVICE_TABLE(of, sprd_iommu_of_match);
- /*
- * Clock is not required, access to some of IOMMUs is controlled by gate
- * clk, enabled clocks for that kind of IOMMUs before accessing.
- * Return 0 for success or no clocks found.
- */
- static int sprd_iommu_clk_enable(struct sprd_iommu_device *sdev)
- {
- struct clk *eb;
- eb = devm_clk_get_optional(sdev->dev, NULL);
- if (!eb)
- return 0;
- if (IS_ERR(eb))
- return PTR_ERR(eb);
- sdev->eb = eb;
- return clk_prepare_enable(eb);
- }
- static void sprd_iommu_clk_disable(struct sprd_iommu_device *sdev)
- {
- if (sdev->eb)
- clk_disable_unprepare(sdev->eb);
- }
- static int sprd_iommu_probe(struct platform_device *pdev)
- {
- struct sprd_iommu_device *sdev;
- struct device *dev = &pdev->dev;
- void __iomem *base;
- int ret;
- sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL);
- if (!sdev)
- return -ENOMEM;
- base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(base)) {
- dev_err(dev, "Failed to get ioremap resource.\n");
- return PTR_ERR(base);
- }
- sdev->base = base;
- sdev->prot_page_va = dma_alloc_coherent(dev, SPRD_IOMMU_PAGE_SIZE,
- &sdev->prot_page_pa, GFP_KERNEL);
- if (!sdev->prot_page_va)
- return -ENOMEM;
- platform_set_drvdata(pdev, sdev);
- sdev->dev = dev;
- /* All the client devices are in the same iommu-group */
- sdev->group = iommu_group_alloc();
- if (IS_ERR(sdev->group)) {
- ret = PTR_ERR(sdev->group);
- goto free_page;
- }
- ret = iommu_device_sysfs_add(&sdev->iommu, dev, NULL, dev_name(dev));
- if (ret)
- goto put_group;
- ret = iommu_device_register(&sdev->iommu, &sprd_iommu_ops, dev);
- if (ret)
- goto remove_sysfs;
- ret = sprd_iommu_clk_enable(sdev);
- if (ret)
- goto unregister_iommu;
- ret = sprd_iommu_get_version(sdev);
- if (ret < 0) {
- dev_err(dev, "IOMMU version(%d) is invalid.\n", ret);
- goto disable_clk;
- }
- sdev->ver = ret;
- return 0;
- disable_clk:
- sprd_iommu_clk_disable(sdev);
- unregister_iommu:
- iommu_device_unregister(&sdev->iommu);
- remove_sysfs:
- iommu_device_sysfs_remove(&sdev->iommu);
- put_group:
- iommu_group_put(sdev->group);
- free_page:
- dma_free_coherent(sdev->dev, SPRD_IOMMU_PAGE_SIZE, sdev->prot_page_va, sdev->prot_page_pa);
- return ret;
- }
- static int sprd_iommu_remove(struct platform_device *pdev)
- {
- struct sprd_iommu_device *sdev = platform_get_drvdata(pdev);
- dma_free_coherent(sdev->dev, SPRD_IOMMU_PAGE_SIZE, sdev->prot_page_va, sdev->prot_page_pa);
- iommu_group_put(sdev->group);
- sdev->group = NULL;
- platform_set_drvdata(pdev, NULL);
- iommu_device_sysfs_remove(&sdev->iommu);
- iommu_device_unregister(&sdev->iommu);
- return 0;
- }
- static struct platform_driver sprd_iommu_driver = {
- .driver = {
- .name = "sprd-iommu",
- .of_match_table = sprd_iommu_of_match,
- .suppress_bind_attrs = true,
- },
- .probe = sprd_iommu_probe,
- .remove = sprd_iommu_remove,
- };
- module_platform_driver(sprd_iommu_driver);
- MODULE_DESCRIPTION("IOMMU driver for Unisoc SoCs");
- MODULE_ALIAS("platform:sprd-iommu");
- MODULE_LICENSE("GPL");
|