summaryrefslogtreecommitdiffstats
path: root/arch/mips/mti-malta/malta-dtshim.c
blob: c398582c316fcc9a8b01f567052ac4bb2ef18c26 (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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/*
 * Copyright (C) 2015 Imagination Technologies
 * Author: Paul Burton <paul.burton@imgtec.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 */

#include <linux/bug.h>
#include <linux/kernel.h>
#include <linux/libfdt.h>
#include <linux/of_fdt.h>
#include <linux/sizes.h>
#include <asm/addrspace.h>
#include <asm/bootinfo.h>
#include <asm/fw/fw.h>
#include <asm/mips-boards/generic.h>
#include <asm/mips-boards/malta.h>
#include <asm/mips-cm.h>
#include <asm/page.h>

#define ROCIT_REG_BASE			0x1f403000
#define ROCIT_CONFIG_GEN1		(ROCIT_REG_BASE + 0x04)
#define  ROCIT_CONFIG_GEN1_MEMMAP_SHIFT	8
#define  ROCIT_CONFIG_GEN1_MEMMAP_MASK	(0xf << 8)

static unsigned char fdt_buf[16 << 10] __initdata;

/* determined physical memory size, not overridden by command line args	 */
extern unsigned long physical_memsize;

enum mem_map {
	MEM_MAP_V1 = 0,
	MEM_MAP_V2,
};

#define MAX_MEM_ARRAY_ENTRIES 2

static __init int malta_scon(void)
{
	int scon = MIPS_REVISION_SCONID;

	if (scon != MIPS_REVISION_SCON_OTHER)
		return scon;

	switch (MIPS_REVISION_CORID) {
	case MIPS_REVISION_CORID_QED_RM5261:
	case MIPS_REVISION_CORID_CORE_LV:
	case MIPS_REVISION_CORID_CORE_FPGA:
	case MIPS_REVISION_CORID_CORE_FPGAR2:
		return MIPS_REVISION_SCON_GT64120;

	case MIPS_REVISION_CORID_CORE_EMUL_BON:
	case MIPS_REVISION_CORID_BONITO64:
	case MIPS_REVISION_CORID_CORE_20K:
		return MIPS_REVISION_SCON_BONITO;

	case MIPS_REVISION_CORID_CORE_MSC:
	case MIPS_REVISION_CORID_CORE_FPGA2:
	case MIPS_REVISION_CORID_CORE_24K:
		return MIPS_REVISION_SCON_SOCIT;

	case MIPS_REVISION_CORID_CORE_FPGA3:
	case MIPS_REVISION_CORID_CORE_FPGA4:
	case MIPS_REVISION_CORID_CORE_FPGA5:
	case MIPS_REVISION_CORID_CORE_EMUL_MSC:
	default:
		return MIPS_REVISION_SCON_ROCIT;
	}
}

static unsigned __init gen_fdt_mem_array(__be32 *mem_array, unsigned long size,
					 enum mem_map map)
{
	unsigned long size_preio;
	unsigned entries;

	entries = 1;
	mem_array[0] = cpu_to_be32(PHYS_OFFSET);
	if (IS_ENABLED(CONFIG_EVA)) {
		/*
		 * The current Malta EVA configuration is "special" in that it
		 * always makes use of addresses in the upper half of the 32 bit
		 * physical address map, which gives it a contiguous region of
		 * DDR but limits it to 2GB.
		 */
		mem_array[1] = cpu_to_be32(size);
		goto done;
	}

	size_preio = min_t(unsigned long, size, SZ_256M);
	mem_array[1] = cpu_to_be32(size_preio);
	size -= size_preio;
	if (!size)
		goto done;

	if (map == MEM_MAP_V2) {
		/*
		 * We have a flat 32 bit physical memory map with DDR filling
		 * all 4GB of the memory map, apart from the I/O region which
		 * obscures 256MB from 0x10000000-0x1fffffff.
		 *
		 * Therefore we discard the 256MB behind the I/O region.
		 */
		if (size <= SZ_256M)
			goto done;
		size -= SZ_256M;

		/* Make use of the memory following the I/O region */
		entries++;
		mem_array[2] = cpu_to_be32(PHYS_OFFSET + SZ_512M);
		mem_array[3] = cpu_to_be32(size);
	} else {
		/*
		 * We have a 32 bit physical memory map with a 2GB DDR region
		 * aliased in the upper & lower halves of it. The I/O region
		 * obscures 256MB from 0x10000000-0x1fffffff in the low alias
		 * but the DDR it obscures is accessible via the high alias.
		 *
		 * Simply access everything beyond the lowest 256MB of DDR using
		 * the high alias.
		 */
		entries++;
		mem_array[2] = cpu_to_be32(PHYS_OFFSET + SZ_2G + SZ_256M);
		mem_array[3] = cpu_to_be32(size);
	}

done:
	BUG_ON(entries > MAX_MEM_ARRAY_ENTRIES);
	return entries;
}

static void __init append_memory(void *fdt, int root_off)
{
	__be32 mem_array[2 * MAX_MEM_ARRAY_ENTRIES];
	unsigned long memsize;
	unsigned mem_entries;
	int i, err, mem_off;
	enum mem_map mem_map;
	u32 config;
	char *var, param_name[10], *var_names[] = {
		"ememsize", "memsize",
	};

	/* if a memory node already exists, leave it alone */
	mem_off = fdt_path_offset(fdt, "/memory");
	if (mem_off >= 0)
		return;

	/* find memory size from the bootloader environment */
	for (i = 0; i < ARRAY_SIZE(var_names); i++) {
		var = fw_getenv(var_names[i]);
		if (!var)
			continue;

		err = kstrtoul(var, 0, &physical_memsize);
		if (!err)
			break;

		pr_warn("Failed to read the '%s' env variable '%s'\n",
			var_names[i], var);
	}

	if (!physical_memsize) {
		pr_warn("The bootloader didn't provide memsize: defaulting to 32MB\n");
		physical_memsize = 32 << 20;
	}

	if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) {
		/*
		 * SOC-it swaps, or perhaps doesn't swap, when DMA'ing
		 * the last word of physical memory.
		 */
		physical_memsize -= PAGE_SIZE;
	}

	/* default to using all available RAM */
	memsize = physical_memsize;

	/* allow the user to override the usable memory */
	for (i = 0; i < ARRAY_SIZE(var_names); i++) {
		snprintf(param_name, sizeof(param_name), "%s=", var_names[i]);
		var = strstr(arcs_cmdline, param_name);
		if (!var)
			continue;

		memsize = memparse(var + strlen(param_name), NULL);
	}

	/* if the user says there's more RAM than we thought, believe them */
	physical_memsize = max_t(unsigned long, physical_memsize, memsize);

	/* detect the memory map in use */
	if (malta_scon() == MIPS_REVISION_SCON_ROCIT) {
		/* ROCit has a register indicating the memory map in use */
		config = readl((void __iomem *)CKSEG1ADDR(ROCIT_CONFIG_GEN1));
		mem_map = config & ROCIT_CONFIG_GEN1_MEMMAP_MASK;
		mem_map >>= ROCIT_CONFIG_GEN1_MEMMAP_SHIFT;
	} else {
		/* if not using ROCit, presume the v1 memory map */
		mem_map = MEM_MAP_V1;
	}
	if (mem_map > MEM_MAP_V2)
		panic("Unsupported physical memory map v%u detected",
		      (unsigned int)mem_map);

	/* append memory to the DT */
	mem_off = fdt_add_subnode(fdt, root_off, "memory");
	if (mem_off < 0)
		panic("Unable to add memory node to DT: %d", mem_off);

	err = fdt_setprop_string(fdt, mem_off, "device_type", "memory");
	if (err)
		panic("Unable to set memory node device_type: %d", err);

	mem_entries = gen_fdt_mem_array(mem_array, physical_memsize, mem_map);
	err = fdt_setprop(fdt, mem_off, "reg", mem_array,
			  mem_entries * 2 * sizeof(mem_array[0]));
	if (err)
		panic("Unable to set memory regs property: %d", err);

	mem_entries = gen_fdt_mem_array(mem_array, memsize, mem_map);
	err = fdt_setprop(fdt, mem_off, "linux,usable-memory", mem_array,
			  mem_entries * 2 * sizeof(mem_array[0]));
	if (err)
		panic("Unable to set linux,usable-memory property: %d", err);
}

static void __init remove_gic(void *fdt)
{
	int err, gic_off, i8259_off, cpu_off;
	void __iomem *biu_base;
	uint32_t cpu_phandle, sc_cfg;

	/* if we have a CM which reports a GIC is present, leave the DT alone */
	err = mips_cm_probe();
	if (!err && (read_gcr_gic_status() & CM_GCR_GIC_STATUS_GICEX_MSK))
		return;

	if (malta_scon() == MIPS_REVISION_SCON_ROCIT) {
		/*
		 * On systems using the RocIT system controller a GIC may be
		 * present without a CM. Detect whether that is the case.
		 */
		biu_base = ioremap_nocache(MSC01_BIU_REG_BASE,
				MSC01_BIU_ADDRSPACE_SZ);
		sc_cfg = __raw_readl(biu_base + MSC01_SC_CFG_OFS);
		if (sc_cfg & MSC01_SC_CFG_GICPRES_MSK) {
			/* enable the GIC at the system controller level */
			sc_cfg |= BIT(MSC01_SC_CFG_GICENA_SHF);
			__raw_writel(sc_cfg, biu_base + MSC01_SC_CFG_OFS);
			return;
		}
	}

	gic_off = fdt_node_offset_by_compatible(fdt, -1, "mti,gic");
	if (gic_off < 0) {
		pr_warn("malta-dtshim: unable to find DT GIC node: %d\n",
			gic_off);
		return;
	}

	err = fdt_nop_node(fdt, gic_off);
	if (err)
		pr_warn("malta-dtshim: unable to nop GIC node\n");

	i8259_off = fdt_node_offset_by_compatible(fdt, -1, "intel,i8259");
	if (i8259_off < 0) {
		pr_warn("malta-dtshim: unable to find DT i8259 node: %d\n",
			i8259_off);
		return;
	}

	cpu_off = fdt_node_offset_by_compatible(fdt, -1,
			"mti,cpu-interrupt-controller");
	if (cpu_off < 0) {
		pr_warn("malta-dtshim: unable to find CPU intc node: %d\n",
			cpu_off);
		return;
	}

	cpu_phandle = fdt_get_phandle(fdt, cpu_off);
	if (!cpu_phandle) {
		pr_warn("malta-dtshim: unable to get CPU intc phandle\n");
		return;
	}

	err = fdt_setprop_u32(fdt, i8259_off, "interrupt-parent", cpu_phandle);
	if (err) {
		pr_warn("malta-dtshim: unable to set i8259 interrupt-parent: %d\n",
			err);
		return;
	}

	err = fdt_setprop_u32(fdt, i8259_off, "interrupts", 2);
	if (err) {
		pr_warn("malta-dtshim: unable to set i8259 interrupts: %d\n",
			err);
		return;
	}
}

void __init *malta_dt_shim(void *fdt)
{
	int root_off, len, err;
	const char *compat;

	if (fdt_check_header(fdt))
		panic("Corrupt DT");

	err = fdt_open_into(fdt, fdt_buf, sizeof(fdt_buf));
	if (err)
		panic("Unable to open FDT: %d", err);

	root_off = fdt_path_offset(fdt_buf, "/");
	if (root_off < 0)
		panic("No / node in DT");

	compat = fdt_getprop(fdt_buf, root_off, "compatible", &len);
	if (!compat)
		panic("No root compatible property in DT: %d", len);

	/* if this isn't Malta, leave the DT alone */
	if (strncmp(compat, "mti,malta", len))
		return fdt;

	append_memory(fdt_buf, root_off);
	remove_gic(fdt_buf);

	err = fdt_pack(fdt_buf);
	if (err)
		panic("Unable to pack FDT: %d\n", err);

	return fdt_buf;
}