summaryrefslogtreecommitdiff
path: root/drivers/cache/hisi_soc_hha.c
blob: 25ff0f5ae79b301a256f3c7e537e217985c596cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// 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/mod_devicetable.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");