| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (C) 2016 Imagination Technologies | |
| * Author: Paul Burton <paul.burton@mips.com> | |
| */ | |
| #define pr_fmt(fmt) "yamon-dt: " fmt | |
| #include <linux/bug.h> | |
| #include <linux/errno.h> | |
| #include <linux/kernel.h> | |
| #include <linux/libfdt.h> | |
| #include <linux/printk.h> | |
| #include <asm/fw/fw.h> | |
| #include <asm/yamon-dt.h> | |
| #define MAX_MEM_ARRAY_ENTRIES 2 | |
| __init int yamon_dt_append_cmdline(void *fdt) | |
| { | |
| int err, chosen_off; | |
| /* find or add chosen node */ | |
| chosen_off = fdt_path_offset(fdt, "/chosen"); | |
| if (chosen_off == -FDT_ERR_NOTFOUND) | |
| chosen_off = fdt_add_subnode(fdt, 0, "chosen"); | |
| if (chosen_off < 0) { | |
| pr_err("Unable to find or add DT chosen node: %d\n", | |
| chosen_off); | |
| return chosen_off; | |
| } | |
| err = fdt_setprop_string(fdt, chosen_off, "bootargs", fw_getcmdline()); | |
| if (err) { | |
| pr_err("Unable to set bootargs property: %d\n", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static unsigned int __init gen_fdt_mem_array( | |
| const struct yamon_mem_region *regions, | |
| __be32 *mem_array, | |
| unsigned int max_entries, | |
| unsigned long memsize) | |
| { | |
| const struct yamon_mem_region *mr; | |
| unsigned long size; | |
| unsigned int entries = 0; | |
| for (mr = regions; mr->size && memsize; ++mr) { | |
| if (entries >= max_entries) { | |
| pr_warn("Number of regions exceeds max %u\n", | |
| max_entries); | |
| break; | |
| } | |
| /* How much of the remaining RAM fits in the next region? */ | |
| size = min_t(unsigned long, memsize, mr->size); | |
| memsize -= size; | |
| /* Emit a memory region */ | |
| *(mem_array++) = cpu_to_be32(mr->start); | |
| *(mem_array++) = cpu_to_be32(size); | |
| ++entries; | |
| /* Discard the next mr->discard bytes */ | |
| memsize -= min_t(unsigned long, memsize, mr->discard); | |
| } | |
| return entries; | |
| } | |
| __init int yamon_dt_append_memory(void *fdt, | |
| const struct yamon_mem_region *regions) | |
| { | |
| unsigned long phys_memsize = 0, memsize; | |
| __be32 mem_array[2 * MAX_MEM_ARRAY_ENTRIES]; | |
| unsigned int mem_entries; | |
| int i, err, mem_off; | |
| char *var, param_name[10], *var_names[] = { | |
| "ememsize", "memsize", | |
| }; | |
| /* 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, &phys_memsize); | |
| if (!err) | |
| break; | |
| pr_warn("Failed to read the '%s' env variable '%s'\n", | |
| var_names[i], var); | |
| } | |
| if (!phys_memsize) { | |
| pr_warn("The bootloader didn't provide memsize: defaulting to 32MB\n"); | |
| phys_memsize = 32 << 20; | |
| } | |
| /* default to using all available RAM */ | |
| memsize = phys_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 */ | |
| phys_memsize = max_t(unsigned long, phys_memsize, memsize); | |
| /* find or add a memory node */ | |
| mem_off = fdt_path_offset(fdt, "/memory"); | |
| if (mem_off == -FDT_ERR_NOTFOUND) | |
| mem_off = fdt_add_subnode(fdt, 0, "memory"); | |
| if (mem_off < 0) { | |
| pr_err("Unable to find or add memory DT node: %d\n", mem_off); | |
| return mem_off; | |
| } | |
| err = fdt_setprop_string(fdt, mem_off, "device_type", "memory"); | |
| if (err) { | |
| pr_err("Unable to set memory node device_type: %d\n", err); | |
| return err; | |
| } | |
| mem_entries = gen_fdt_mem_array(regions, mem_array, | |
| MAX_MEM_ARRAY_ENTRIES, phys_memsize); | |
| err = fdt_setprop(fdt, mem_off, "reg", | |
| mem_array, mem_entries * 2 * sizeof(mem_array[0])); | |
| if (err) { | |
| pr_err("Unable to set memory regs property: %d\n", err); | |
| return err; | |
| } | |
| mem_entries = gen_fdt_mem_array(regions, mem_array, | |
| MAX_MEM_ARRAY_ENTRIES, memsize); | |
| err = fdt_setprop(fdt, mem_off, "linux,usable-memory", | |
| mem_array, mem_entries * 2 * sizeof(mem_array[0])); | |
| if (err) { | |
| pr_err("Unable to set linux,usable-memory property: %d\n", err); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| __init int yamon_dt_serial_config(void *fdt) | |
| { | |
| const char *yamontty, *mode_var; | |
| char mode_var_name[9], path[20], parity; | |
| unsigned int uart, baud, stop_bits; | |
| bool hw_flow; | |
| int chosen_off, err; | |
| yamontty = fw_getenv("yamontty"); | |
| if (!yamontty || !strcmp(yamontty, "tty0")) { | |
| uart = 0; | |
| } else if (!strcmp(yamontty, "tty1")) { | |
| uart = 1; | |
| } else { | |
| pr_warn("yamontty environment variable '%s' invalid\n", | |
| yamontty); | |
| uart = 0; | |
| } | |
| baud = stop_bits = 0; | |
| parity = 0; | |
| hw_flow = false; | |
| snprintf(mode_var_name, sizeof(mode_var_name), "modetty%u", uart); | |
| mode_var = fw_getenv(mode_var_name); | |
| if (mode_var) { | |
| while (mode_var[0] >= '0' && mode_var[0] <= '9') { | |
| baud *= 10; | |
| baud += mode_var[0] - '0'; | |
| mode_var++; | |
| } | |
| if (mode_var[0] == ',') | |
| mode_var++; | |
| if (mode_var[0]) | |
| parity = mode_var[0]; | |
| if (mode_var[0] == ',') | |
| mode_var++; | |
| if (mode_var[0]) | |
| stop_bits = mode_var[0] - '0'; | |
| if (mode_var[0] == ',') | |
| mode_var++; | |
| if (!strcmp(mode_var, "hw")) | |
| hw_flow = true; | |
| } | |
| if (!baud) | |
| baud = 38400; | |
| if (parity != 'e' && parity != 'n' && parity != 'o') | |
| parity = 'n'; | |
| if (stop_bits != 7 && stop_bits != 8) | |
| stop_bits = 8; | |
| WARN_ON(snprintf(path, sizeof(path), "serial%u:%u%c%u%s", | |
| uart, baud, parity, stop_bits, | |
| hw_flow ? "r" : "") >= sizeof(path)); | |
| /* find or add chosen node */ | |
| chosen_off = fdt_path_offset(fdt, "/chosen"); | |
| if (chosen_off == -FDT_ERR_NOTFOUND) | |
| chosen_off = fdt_add_subnode(fdt, 0, "chosen"); | |
| if (chosen_off < 0) { | |
| pr_err("Unable to find or add DT chosen node: %d\n", | |
| chosen_off); | |
| return chosen_off; | |
| } | |
| err = fdt_setprop_string(fdt, chosen_off, "stdout-path", path); | |
| if (err) { | |
| pr_err("Unable to set stdout-path property: %d\n", err); | |
| return err; | |
| } | |
| return 0; | |
| } |