1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2019, Rick Chen <rick@andestech.com>
4 *
5 * U-Boot syscon driver for Andes' PLICSW
6 * The PLICSW block is an Andes-specific design for software interrupts,
7 * contains memory-mapped priority, enable, claim and pending registers
8 * similar to RISC-V PLIC.
9 */
10
11#include <dm.h>
12#include <asm/global_data.h>
13#include <dm/device-internal.h>
14#include <dm/lists.h>
15#include <dm/uclass-internal.h>
16#include <regmap.h>
17#include <syscon.h>
18#include <asm/io.h>
19#include <asm/syscon.h>
20#include <cpu.h>
21#include <linux/err.h>
22
23/* pending register */
24#define PENDING_REG(base, hart)	((ulong)(base) + 0x1000 + 4 * (((hart) + 1) / 32))
25/* enable register */
26#define ENABLE_REG(base, hart)	((ulong)(base) + 0x2000 + (hart) * 0x80 + 4 * (((hart) + 1) / 32))
27/* claim register */
28#define CLAIM_REG(base, hart)	((ulong)(base) + 0x200004 + (hart) * 0x1000)
29/* priority register */
30#define PRIORITY_REG(base)	((ulong)(base) + PLICSW_PRIORITY_BASE)
31
32/* Bit 0 of PLIC-SW pending array is hardwired to zero, so we start from bit 1 */
33#define PLICSW_PRIORITY_BASE        0x4
34
35DECLARE_GLOBAL_DATA_PTR;
36
37static int enable_ipi(int hart)
38{
39	u32 enable_bit = (hart + 1) % 32;
40
41	writel(BIT(enable_bit), (void __iomem *)ENABLE_REG(gd->arch.plicsw, hart));
42
43	return 0;
44}
45
46static void init_priority_ipi(int hart_num)
47{
48	u32 *priority = (void *)PRIORITY_REG(gd->arch.plicsw);
49
50	for (int i = 0; i < hart_num; i++)
51		writel(1, &priority[i]);
52
53	return;
54}
55
56int riscv_init_ipi(void)
57{
58	int ret;
59	int hart_num = 0;
60	long *base = syscon_get_first_range(RISCV_SYSCON_PLICSW);
61	ofnode node;
62	struct udevice *dev;
63	u32 reg;
64
65	if (IS_ERR(base))
66		return PTR_ERR(base);
67	gd->arch.plicsw = base;
68
69	ret = uclass_find_first_device(UCLASS_CPU, &dev);
70	if (ret)
71		return ret;
72	if (!dev)
73		return -ENODEV;
74
75	ofnode_for_each_subnode(node, dev_ofnode(dev->parent)) {
76		const char *device_type;
77
78		device_type = ofnode_read_string(node, "device_type");
79		if (!device_type)
80			continue;
81
82		if (strcmp(device_type, "cpu"))
83			continue;
84
85		/* skip if hart is marked as not available */
86		if (!ofnode_is_enabled(node))
87			continue;
88
89		/* read hart ID of CPU */
90		ret = ofnode_read_u32(node, "reg", &reg);
91		if (ret == 0)
92			enable_ipi(reg);
93		hart_num++;
94	}
95
96	init_priority_ipi(hart_num);
97	return 0;
98}
99
100int riscv_send_ipi(int hart)
101{
102	u32 interrupt_id = hart + 1;
103	u32 pending_bit  = interrupt_id % 32;
104
105	writel(BIT(pending_bit), (void __iomem *)PENDING_REG(gd->arch.plicsw, hart));
106
107	return 0;
108}
109
110int riscv_clear_ipi(int hart)
111{
112	u32 source_id;
113
114	source_id = readl((void __iomem *)CLAIM_REG(gd->arch.plicsw, hart));
115	writel(source_id, (void __iomem *)CLAIM_REG(gd->arch.plicsw, hart));
116
117	return 0;
118}
119
120int riscv_get_ipi(int hart, int *pending)
121{
122	u32 interrupt_id = hart + 1;
123	u32 pending_bit  = interrupt_id % 32;
124
125	*pending = readl((void __iomem *)PENDING_REG(gd->arch.plicsw, hart));
126	*pending = !!(*pending & BIT(pending_bit));
127
128	return 0;
129}
130
131static const struct udevice_id andes_plicsw_ids[] = {
132	{ .compatible = "andestech,plicsw", .data = RISCV_SYSCON_PLICSW },
133	{ }
134};
135
136U_BOOT_DRIVER(andes_plicsw) = {
137	.name		= "andes_plicsw",
138	.id		= UCLASS_SYSCON,
139	.of_match	= andes_plicsw_ids,
140	.flags		= DM_FLAG_PRE_RELOC,
141};
142