// SPDX-License-Identifier: GPL-2.0 /* * Driver for HiSilicon Hydra Home Agent (HHA). * * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. * Author: Yicong Yang * Yushan Wang * * A system typically contains multiple HHAs. Each is responsible for a subset * of the physical addresses in the system, but interleave can make the mapping * from a particular cache line to a responsible HHA complex. As such no * filtering is done in the driver, with the hardware being responsible for * responding with success for even if it was not responsible for any addresses * in the range on which the operation was requested. */ #include #include #include #include #include #include #include #include #include #include #include #include #define HISI_HHA_CTRL 0x5004 #define HISI_HHA_CTRL_EN BIT(0) #define HISI_HHA_CTRL_RANGE BIT(1) #define HISI_HHA_CTRL_TYPE GENMASK(3, 2) #define HISI_HHA_START_L 0x5008 #define HISI_HHA_START_H 0x500c #define HISI_HHA_LEN_L 0x5010 #define HISI_HHA_LEN_H 0x5014 /* The maintain operation performs in a 128 Byte granularity */ #define HISI_HHA_MAINT_ALIGN 128 #define HISI_HHA_POLL_GAP_US 10 #define HISI_HHA_POLL_TIMEOUT_US 50000 struct hisi_soc_hha { /* Must be first element */ struct cache_coherency_ops_inst cci; /* Locks HHA instance to forbid overlapping access. */ struct mutex lock; void __iomem *base; }; static bool hisi_hha_cache_maintain_wait_finished(struct hisi_soc_hha *soc_hha) { u32 val; return !readl_poll_timeout_atomic(soc_hha->base + HISI_HHA_CTRL, val, !(val & HISI_HHA_CTRL_EN), HISI_HHA_POLL_GAP_US, HISI_HHA_POLL_TIMEOUT_US); } static int hisi_soc_hha_wbinv(struct cache_coherency_ops_inst *cci, struct cc_inval_params *invp) { struct hisi_soc_hha *soc_hha = container_of(cci, struct hisi_soc_hha, cci); phys_addr_t top, addr = invp->addr; size_t size = invp->size; u32 reg; if (!size) return -EINVAL; addr = ALIGN_DOWN(addr, HISI_HHA_MAINT_ALIGN); top = ALIGN(addr + size, HISI_HHA_MAINT_ALIGN); size = top - addr; guard(mutex)(&soc_hha->lock); if (!hisi_hha_cache_maintain_wait_finished(soc_hha)) return -EBUSY; /* * Hardware will search for addresses ranging [addr, addr + size - 1], * last byte included, and perform maintenance in 128 byte granules * on those cachelines which contain the addresses. If a given instance * is either not responsible for a cacheline or that cacheline is not * currently present then the search will fail, no operation will be * necessary and the device will report success. */ size -= 1; writel(lower_32_bits(addr), soc_hha->base + HISI_HHA_START_L); writel(upper_32_bits(addr), soc_hha->base + HISI_HHA_START_H); writel(lower_32_bits(size), soc_hha->base + HISI_HHA_LEN_L); writel(upper_32_bits(size), soc_hha->base + HISI_HHA_LEN_H); reg = FIELD_PREP(HISI_HHA_CTRL_TYPE, 1); /* Clean Invalid */ reg |= HISI_HHA_CTRL_RANGE | HISI_HHA_CTRL_EN; writel(reg, soc_hha->base + HISI_HHA_CTRL); return 0; } static int hisi_soc_hha_done(struct cache_coherency_ops_inst *cci) { struct hisi_soc_hha *soc_hha = container_of(cci, struct hisi_soc_hha, cci); guard(mutex)(&soc_hha->lock); if (!hisi_hha_cache_maintain_wait_finished(soc_hha)) return -ETIMEDOUT; return 0; } static const struct cache_coherency_ops hha_ops = { .wbinv = hisi_soc_hha_wbinv, .done = hisi_soc_hha_done, }; static int hisi_soc_hha_probe(struct platform_device *pdev) { struct hisi_soc_hha *soc_hha; struct resource *mem; int ret; soc_hha = cache_coherency_ops_instance_alloc(&hha_ops, struct hisi_soc_hha, cci); if (!soc_hha) return -ENOMEM; platform_set_drvdata(pdev, soc_hha); mutex_init(&soc_hha->lock); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { ret = -ENOMEM; goto err_free_cci; } soc_hha->base = ioremap(mem->start, resource_size(mem)); if (!soc_hha->base) { ret = dev_err_probe(&pdev->dev, -ENOMEM, "failed to remap io memory"); goto err_free_cci; } ret = cache_coherency_ops_instance_register(&soc_hha->cci); if (ret) goto err_iounmap; return 0; err_iounmap: iounmap(soc_hha->base); err_free_cci: cache_coherency_ops_instance_put(&soc_hha->cci); return ret; } static void hisi_soc_hha_remove(struct platform_device *pdev) { struct hisi_soc_hha *soc_hha = platform_get_drvdata(pdev); cache_coherency_ops_instance_unregister(&soc_hha->cci); iounmap(soc_hha->base); cache_coherency_ops_instance_put(&soc_hha->cci); } static const struct acpi_device_id hisi_soc_hha_ids[] = { { "HISI0511", }, { } }; MODULE_DEVICE_TABLE(acpi, hisi_soc_hha_ids); static struct platform_driver hisi_soc_hha_driver = { .driver = { .name = "hisi_soc_hha", .acpi_match_table = hisi_soc_hha_ids, }, .probe = hisi_soc_hha_probe, .remove = hisi_soc_hha_remove, }; module_platform_driver(hisi_soc_hha_driver); MODULE_IMPORT_NS("CACHE_COHERENCY"); MODULE_DESCRIPTION("HiSilicon Hydra Home Agent driver supporting cache maintenance"); MODULE_AUTHOR("Yicong Yang "); MODULE_AUTHOR("Yushan Wang "); MODULE_LICENSE("GPL");