162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2016 Imagination Technologies
462306a36Sopenharmony_ci * Author: Paul Burton <paul.burton@mips.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define pr_fmt(fmt) "sead3: " fmt
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/errno.h>
1062306a36Sopenharmony_ci#include <linux/libfdt.h>
1162306a36Sopenharmony_ci#include <linux/printk.h>
1262306a36Sopenharmony_ci#include <linux/sizes.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <asm/fw/fw.h>
1562306a36Sopenharmony_ci#include <asm/io.h>
1662306a36Sopenharmony_ci#include <asm/machine.h>
1762306a36Sopenharmony_ci#include <asm/yamon-dt.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define SEAD_CONFIG			CKSEG1ADDR(0x1b100110)
2062306a36Sopenharmony_ci#define SEAD_CONFIG_GIC_PRESENT		BIT(1)
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define MIPS_REVISION			CKSEG1ADDR(0x1fc00010)
2362306a36Sopenharmony_ci#define MIPS_REVISION_MACHINE		(0xf << 4)
2462306a36Sopenharmony_ci#define MIPS_REVISION_MACHINE_SEAD3	(0x4 << 4)
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/*
2762306a36Sopenharmony_ci * Maximum 384MB RAM at physical address 0, preceding any I/O.
2862306a36Sopenharmony_ci */
2962306a36Sopenharmony_cistatic struct yamon_mem_region mem_regions[] __initdata = {
3062306a36Sopenharmony_ci	/* start	size */
3162306a36Sopenharmony_ci	{ 0,		SZ_256M + SZ_128M },
3262306a36Sopenharmony_ci	{}
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic __init bool sead3_detect(void)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	uint32_t rev;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	rev = __raw_readl((void *)MIPS_REVISION);
4062306a36Sopenharmony_ci	return (rev & MIPS_REVISION_MACHINE) == MIPS_REVISION_MACHINE_SEAD3;
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic __init int append_memory(void *fdt)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	return yamon_dt_append_memory(fdt, mem_regions);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic __init int remove_gic(void *fdt)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	const unsigned int cpu_ehci_int = 2;
5162306a36Sopenharmony_ci	const unsigned int cpu_uart_int = 4;
5262306a36Sopenharmony_ci	const unsigned int cpu_eth_int = 6;
5362306a36Sopenharmony_ci	int gic_off, cpu_off, uart_off, eth_off, ehci_off, err;
5462306a36Sopenharmony_ci	uint32_t cfg, cpu_phandle;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	/* leave the GIC node intact if a GIC is present */
5762306a36Sopenharmony_ci	cfg = __raw_readl((uint32_t *)SEAD_CONFIG);
5862306a36Sopenharmony_ci	if (cfg & SEAD_CONFIG_GIC_PRESENT)
5962306a36Sopenharmony_ci		return 0;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	gic_off = fdt_node_offset_by_compatible(fdt, -1, "mti,gic");
6262306a36Sopenharmony_ci	if (gic_off < 0) {
6362306a36Sopenharmony_ci		pr_err("unable to find DT GIC node: %d\n", gic_off);
6462306a36Sopenharmony_ci		return gic_off;
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	err = fdt_nop_node(fdt, gic_off);
6862306a36Sopenharmony_ci	if (err) {
6962306a36Sopenharmony_ci		pr_err("unable to nop GIC node\n");
7062306a36Sopenharmony_ci		return err;
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	cpu_off = fdt_node_offset_by_compatible(fdt, -1,
7462306a36Sopenharmony_ci			"mti,cpu-interrupt-controller");
7562306a36Sopenharmony_ci	if (cpu_off < 0) {
7662306a36Sopenharmony_ci		pr_err("unable to find CPU intc node: %d\n", cpu_off);
7762306a36Sopenharmony_ci		return cpu_off;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	cpu_phandle = fdt_get_phandle(fdt, cpu_off);
8162306a36Sopenharmony_ci	if (!cpu_phandle) {
8262306a36Sopenharmony_ci		pr_err("unable to get CPU intc phandle\n");
8362306a36Sopenharmony_ci		return -EINVAL;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	uart_off = fdt_node_offset_by_compatible(fdt, -1, "ns16550a");
8762306a36Sopenharmony_ci	while (uart_off >= 0) {
8862306a36Sopenharmony_ci		err = fdt_setprop_u32(fdt, uart_off, "interrupt-parent",
8962306a36Sopenharmony_ci				      cpu_phandle);
9062306a36Sopenharmony_ci		if (err) {
9162306a36Sopenharmony_ci			pr_warn("unable to set UART interrupt-parent: %d\n",
9262306a36Sopenharmony_ci				err);
9362306a36Sopenharmony_ci			return err;
9462306a36Sopenharmony_ci		}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		err = fdt_setprop_u32(fdt, uart_off, "interrupts",
9762306a36Sopenharmony_ci				      cpu_uart_int);
9862306a36Sopenharmony_ci		if (err) {
9962306a36Sopenharmony_ci			pr_err("unable to set UART interrupts property: %d\n",
10062306a36Sopenharmony_ci			       err);
10162306a36Sopenharmony_ci			return err;
10262306a36Sopenharmony_ci		}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci		uart_off = fdt_node_offset_by_compatible(fdt, uart_off,
10562306a36Sopenharmony_ci							 "ns16550a");
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci	if (uart_off != -FDT_ERR_NOTFOUND) {
10862306a36Sopenharmony_ci		pr_err("error searching for UART DT node: %d\n", uart_off);
10962306a36Sopenharmony_ci		return uart_off;
11062306a36Sopenharmony_ci	}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	eth_off = fdt_node_offset_by_compatible(fdt, -1, "smsc,lan9115");
11362306a36Sopenharmony_ci	if (eth_off < 0) {
11462306a36Sopenharmony_ci		pr_err("unable to find ethernet DT node: %d\n", eth_off);
11562306a36Sopenharmony_ci		return eth_off;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	err = fdt_setprop_u32(fdt, eth_off, "interrupt-parent", cpu_phandle);
11962306a36Sopenharmony_ci	if (err) {
12062306a36Sopenharmony_ci		pr_err("unable to set ethernet interrupt-parent: %d\n", err);
12162306a36Sopenharmony_ci		return err;
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	err = fdt_setprop_u32(fdt, eth_off, "interrupts", cpu_eth_int);
12562306a36Sopenharmony_ci	if (err) {
12662306a36Sopenharmony_ci		pr_err("unable to set ethernet interrupts property: %d\n", err);
12762306a36Sopenharmony_ci		return err;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	ehci_off = fdt_node_offset_by_compatible(fdt, -1, "generic-ehci");
13162306a36Sopenharmony_ci	if (ehci_off < 0) {
13262306a36Sopenharmony_ci		pr_err("unable to find EHCI DT node: %d\n", ehci_off);
13362306a36Sopenharmony_ci		return ehci_off;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	err = fdt_setprop_u32(fdt, ehci_off, "interrupt-parent", cpu_phandle);
13762306a36Sopenharmony_ci	if (err) {
13862306a36Sopenharmony_ci		pr_err("unable to set EHCI interrupt-parent: %d\n", err);
13962306a36Sopenharmony_ci		return err;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	err = fdt_setprop_u32(fdt, ehci_off, "interrupts", cpu_ehci_int);
14362306a36Sopenharmony_ci	if (err) {
14462306a36Sopenharmony_ci		pr_err("unable to set EHCI interrupts property: %d\n", err);
14562306a36Sopenharmony_ci		return err;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return 0;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic const struct mips_fdt_fixup sead3_fdt_fixups[] __initconst = {
15262306a36Sopenharmony_ci	{ yamon_dt_append_cmdline, "append command line" },
15362306a36Sopenharmony_ci	{ append_memory, "append memory" },
15462306a36Sopenharmony_ci	{ remove_gic, "remove GIC when not present" },
15562306a36Sopenharmony_ci	{ yamon_dt_serial_config, "append serial configuration" },
15662306a36Sopenharmony_ci	{ },
15762306a36Sopenharmony_ci};
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic __init const void *sead3_fixup_fdt(const void *fdt,
16062306a36Sopenharmony_ci					  const void *match_data)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	static unsigned char fdt_buf[16 << 10] __initdata;
16362306a36Sopenharmony_ci	int err;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	if (fdt_check_header(fdt))
16662306a36Sopenharmony_ci		panic("Corrupt DT");
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	/* if this isn't SEAD3, something went wrong */
16962306a36Sopenharmony_ci	BUG_ON(fdt_node_check_compatible(fdt, 0, "mti,sead-3"));
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	fw_init_cmdline();
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	err = apply_mips_fdt_fixups(fdt_buf, sizeof(fdt_buf),
17462306a36Sopenharmony_ci				    fdt, sead3_fdt_fixups);
17562306a36Sopenharmony_ci	if (err)
17662306a36Sopenharmony_ci		panic("Unable to fixup FDT: %d", err);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	return fdt_buf;
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic __init unsigned int sead3_measure_hpt_freq(void)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	void __iomem *status_reg = (void __iomem *)0xbf000410;
18462306a36Sopenharmony_ci	unsigned int freq, orig, tick = 0;
18562306a36Sopenharmony_ci	unsigned long flags;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	local_irq_save(flags);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	orig = readl(status_reg) & 0x2;		      /* get original sample */
19062306a36Sopenharmony_ci	/* wait for transition */
19162306a36Sopenharmony_ci	while ((readl(status_reg) & 0x2) == orig)
19262306a36Sopenharmony_ci		;
19362306a36Sopenharmony_ci	orig = orig ^ 0x2;			      /* flip the bit */
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	write_c0_count(0);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	/* wait 1 second (the sampling clock transitions every 10ms) */
19862306a36Sopenharmony_ci	while (tick < 100) {
19962306a36Sopenharmony_ci		/* wait for transition */
20062306a36Sopenharmony_ci		while ((readl(status_reg) & 0x2) == orig)
20162306a36Sopenharmony_ci			;
20262306a36Sopenharmony_ci		orig = orig ^ 0x2;			      /* flip the bit */
20362306a36Sopenharmony_ci		tick++;
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	freq = read_c0_count();
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	local_irq_restore(flags);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	return freq;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ciextern char __dtb_sead3_begin[];
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ciMIPS_MACHINE(sead3) = {
21662306a36Sopenharmony_ci	.fdt = __dtb_sead3_begin,
21762306a36Sopenharmony_ci	.detect = sead3_detect,
21862306a36Sopenharmony_ci	.fixup_fdt = sead3_fixup_fdt,
21962306a36Sopenharmony_ci	.measure_hpt_freq = sead3_measure_hpt_freq,
22062306a36Sopenharmony_ci};
221