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) "yamon-dt: " fmt
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/bug.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/libfdt.h>
1362306a36Sopenharmony_ci#include <linux/printk.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <asm/fw/fw.h>
1662306a36Sopenharmony_ci#include <asm/yamon-dt.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define MAX_MEM_ARRAY_ENTRIES	2
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci__init int yamon_dt_append_cmdline(void *fdt)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	int err, chosen_off;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	/* find or add chosen node */
2562306a36Sopenharmony_ci	chosen_off = fdt_path_offset(fdt, "/chosen");
2662306a36Sopenharmony_ci	if (chosen_off == -FDT_ERR_NOTFOUND)
2762306a36Sopenharmony_ci		chosen_off = fdt_add_subnode(fdt, 0, "chosen");
2862306a36Sopenharmony_ci	if (chosen_off < 0) {
2962306a36Sopenharmony_ci		pr_err("Unable to find or add DT chosen node: %d\n",
3062306a36Sopenharmony_ci		       chosen_off);
3162306a36Sopenharmony_ci		return chosen_off;
3262306a36Sopenharmony_ci	}
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	err = fdt_setprop_string(fdt, chosen_off, "bootargs", fw_getcmdline());
3562306a36Sopenharmony_ci	if (err) {
3662306a36Sopenharmony_ci		pr_err("Unable to set bootargs property: %d\n", err);
3762306a36Sopenharmony_ci		return err;
3862306a36Sopenharmony_ci	}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	return 0;
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic unsigned int __init gen_fdt_mem_array(
4462306a36Sopenharmony_ci					const struct yamon_mem_region *regions,
4562306a36Sopenharmony_ci					__be32 *mem_array,
4662306a36Sopenharmony_ci					unsigned int max_entries,
4762306a36Sopenharmony_ci					unsigned long memsize)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	const struct yamon_mem_region *mr;
5062306a36Sopenharmony_ci	unsigned long size;
5162306a36Sopenharmony_ci	unsigned int entries = 0;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	for (mr = regions; mr->size && memsize; ++mr) {
5462306a36Sopenharmony_ci		if (entries >= max_entries) {
5562306a36Sopenharmony_ci			pr_warn("Number of regions exceeds max %u\n",
5662306a36Sopenharmony_ci				max_entries);
5762306a36Sopenharmony_ci			break;
5862306a36Sopenharmony_ci		}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci		/* How much of the remaining RAM fits in the next region? */
6162306a36Sopenharmony_ci		size = min_t(unsigned long, memsize, mr->size);
6262306a36Sopenharmony_ci		memsize -= size;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci		/* Emit a memory region */
6562306a36Sopenharmony_ci		*(mem_array++) = cpu_to_be32(mr->start);
6662306a36Sopenharmony_ci		*(mem_array++) = cpu_to_be32(size);
6762306a36Sopenharmony_ci		++entries;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci		/* Discard the next mr->discard bytes */
7062306a36Sopenharmony_ci		memsize -= min_t(unsigned long, memsize, mr->discard);
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci	return entries;
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci__init int yamon_dt_append_memory(void *fdt,
7662306a36Sopenharmony_ci				  const struct yamon_mem_region *regions)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	unsigned long phys_memsize = 0, memsize;
7962306a36Sopenharmony_ci	__be32 mem_array[2 * MAX_MEM_ARRAY_ENTRIES];
8062306a36Sopenharmony_ci	unsigned int mem_entries;
8162306a36Sopenharmony_ci	int i, err, mem_off;
8262306a36Sopenharmony_ci	char *var, param_name[10], *var_names[] = {
8362306a36Sopenharmony_ci		"ememsize", "memsize",
8462306a36Sopenharmony_ci	};
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	/* find memory size from the bootloader environment */
8762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(var_names); i++) {
8862306a36Sopenharmony_ci		var = fw_getenv(var_names[i]);
8962306a36Sopenharmony_ci		if (!var)
9062306a36Sopenharmony_ci			continue;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci		err = kstrtoul(var, 0, &phys_memsize);
9362306a36Sopenharmony_ci		if (!err)
9462306a36Sopenharmony_ci			break;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		pr_warn("Failed to read the '%s' env variable '%s'\n",
9762306a36Sopenharmony_ci			var_names[i], var);
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	if (!phys_memsize) {
10162306a36Sopenharmony_ci		pr_warn("The bootloader didn't provide memsize: defaulting to 32MB\n");
10262306a36Sopenharmony_ci		phys_memsize = 32 << 20;
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	/* default to using all available RAM */
10662306a36Sopenharmony_ci	memsize = phys_memsize;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	/* allow the user to override the usable memory */
10962306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(var_names); i++) {
11062306a36Sopenharmony_ci		snprintf(param_name, sizeof(param_name), "%s=", var_names[i]);
11162306a36Sopenharmony_ci		var = strstr(arcs_cmdline, param_name);
11262306a36Sopenharmony_ci		if (!var)
11362306a36Sopenharmony_ci			continue;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci		memsize = memparse(var + strlen(param_name), NULL);
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/* if the user says there's more RAM than we thought, believe them */
11962306a36Sopenharmony_ci	phys_memsize = max_t(unsigned long, phys_memsize, memsize);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	/* find or add a memory node */
12262306a36Sopenharmony_ci	mem_off = fdt_path_offset(fdt, "/memory");
12362306a36Sopenharmony_ci	if (mem_off == -FDT_ERR_NOTFOUND)
12462306a36Sopenharmony_ci		mem_off = fdt_add_subnode(fdt, 0, "memory");
12562306a36Sopenharmony_ci	if (mem_off < 0) {
12662306a36Sopenharmony_ci		pr_err("Unable to find or add memory DT node: %d\n", mem_off);
12762306a36Sopenharmony_ci		return mem_off;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	err = fdt_setprop_string(fdt, mem_off, "device_type", "memory");
13162306a36Sopenharmony_ci	if (err) {
13262306a36Sopenharmony_ci		pr_err("Unable to set memory node device_type: %d\n", err);
13362306a36Sopenharmony_ci		return err;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	mem_entries = gen_fdt_mem_array(regions, mem_array,
13762306a36Sopenharmony_ci					MAX_MEM_ARRAY_ENTRIES, phys_memsize);
13862306a36Sopenharmony_ci	err = fdt_setprop(fdt, mem_off, "reg",
13962306a36Sopenharmony_ci			  mem_array, mem_entries * 2 * sizeof(mem_array[0]));
14062306a36Sopenharmony_ci	if (err) {
14162306a36Sopenharmony_ci		pr_err("Unable to set memory regs property: %d\n", err);
14262306a36Sopenharmony_ci		return err;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	mem_entries = gen_fdt_mem_array(regions, mem_array,
14662306a36Sopenharmony_ci					MAX_MEM_ARRAY_ENTRIES, memsize);
14762306a36Sopenharmony_ci	err = fdt_setprop(fdt, mem_off, "linux,usable-memory",
14862306a36Sopenharmony_ci			  mem_array, mem_entries * 2 * sizeof(mem_array[0]));
14962306a36Sopenharmony_ci	if (err) {
15062306a36Sopenharmony_ci		pr_err("Unable to set linux,usable-memory property: %d\n", err);
15162306a36Sopenharmony_ci		return err;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	return 0;
15562306a36Sopenharmony_ci}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci__init int yamon_dt_serial_config(void *fdt)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	const char *yamontty, *mode_var;
16062306a36Sopenharmony_ci	char mode_var_name[9], path[20], parity;
16162306a36Sopenharmony_ci	unsigned int uart, baud, stop_bits;
16262306a36Sopenharmony_ci	bool hw_flow;
16362306a36Sopenharmony_ci	int chosen_off, err;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	yamontty = fw_getenv("yamontty");
16662306a36Sopenharmony_ci	if (!yamontty || !strcmp(yamontty, "tty0")) {
16762306a36Sopenharmony_ci		uart = 0;
16862306a36Sopenharmony_ci	} else if (!strcmp(yamontty, "tty1")) {
16962306a36Sopenharmony_ci		uart = 1;
17062306a36Sopenharmony_ci	} else {
17162306a36Sopenharmony_ci		pr_warn("yamontty environment variable '%s' invalid\n",
17262306a36Sopenharmony_ci			yamontty);
17362306a36Sopenharmony_ci		uart = 0;
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	baud = stop_bits = 0;
17762306a36Sopenharmony_ci	parity = 0;
17862306a36Sopenharmony_ci	hw_flow = false;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	snprintf(mode_var_name, sizeof(mode_var_name), "modetty%u", uart);
18162306a36Sopenharmony_ci	mode_var = fw_getenv(mode_var_name);
18262306a36Sopenharmony_ci	if (mode_var) {
18362306a36Sopenharmony_ci		while (mode_var[0] >= '0' && mode_var[0] <= '9') {
18462306a36Sopenharmony_ci			baud *= 10;
18562306a36Sopenharmony_ci			baud += mode_var[0] - '0';
18662306a36Sopenharmony_ci			mode_var++;
18762306a36Sopenharmony_ci		}
18862306a36Sopenharmony_ci		if (mode_var[0] == ',')
18962306a36Sopenharmony_ci			mode_var++;
19062306a36Sopenharmony_ci		if (mode_var[0])
19162306a36Sopenharmony_ci			parity = mode_var[0];
19262306a36Sopenharmony_ci		if (mode_var[0] == ',')
19362306a36Sopenharmony_ci			mode_var++;
19462306a36Sopenharmony_ci		if (mode_var[0])
19562306a36Sopenharmony_ci			stop_bits = mode_var[0] - '0';
19662306a36Sopenharmony_ci		if (mode_var[0] == ',')
19762306a36Sopenharmony_ci			mode_var++;
19862306a36Sopenharmony_ci		if (!strcmp(mode_var, "hw"))
19962306a36Sopenharmony_ci			hw_flow = true;
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	if (!baud)
20362306a36Sopenharmony_ci		baud = 38400;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	if (parity != 'e' && parity != 'n' && parity != 'o')
20662306a36Sopenharmony_ci		parity = 'n';
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (stop_bits != 7 && stop_bits != 8)
20962306a36Sopenharmony_ci		stop_bits = 8;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	WARN_ON(snprintf(path, sizeof(path), "serial%u:%u%c%u%s",
21262306a36Sopenharmony_ci			 uart, baud, parity, stop_bits,
21362306a36Sopenharmony_ci			 hw_flow ? "r" : "") >= sizeof(path));
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	/* find or add chosen node */
21662306a36Sopenharmony_ci	chosen_off = fdt_path_offset(fdt, "/chosen");
21762306a36Sopenharmony_ci	if (chosen_off == -FDT_ERR_NOTFOUND)
21862306a36Sopenharmony_ci		chosen_off = fdt_add_subnode(fdt, 0, "chosen");
21962306a36Sopenharmony_ci	if (chosen_off < 0) {
22062306a36Sopenharmony_ci		pr_err("Unable to find or add DT chosen node: %d\n",
22162306a36Sopenharmony_ci		       chosen_off);
22262306a36Sopenharmony_ci		return chosen_off;
22362306a36Sopenharmony_ci	}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	err = fdt_setprop_string(fdt, chosen_off, "stdout-path", path);
22662306a36Sopenharmony_ci	if (err) {
22762306a36Sopenharmony_ci		pr_err("Unable to set stdout-path property: %d\n", err);
22862306a36Sopenharmony_ci		return err;
22962306a36Sopenharmony_ci	}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	return 0;
23262306a36Sopenharmony_ci}
233