root/drivers/cache/hisi_soc_hha.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Driver for HiSilicon Hydra Home Agent (HHA).
 *
 * Copyright (c) 2025 HiSilicon Technologies Co., Ltd.
 * Author: Yicong Yang <yangyicong@hisilicon.com>
 *         Yushan Wang <wangyushan12@huawei.com>
 *
 * 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 <linux/bitfield.h>
#include <linux/cache_coherency.h>
#include <linux/dev_printk.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/memregion.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>

#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 <yangyicong@hisilicon.com>");
MODULE_AUTHOR("Yushan Wang <wangyushan12@huawei.com>");
MODULE_LICENSE("GPL");