aboutsummaryrefslogtreecommitdiff
path: root/board/CZ.NIC/turris_1x/turris_1x.c
diff options
context:
space:
mode:
Diffstat (limited to 'board/CZ.NIC/turris_1x/turris_1x.c')
-rw-r--r--board/CZ.NIC/turris_1x/turris_1x.c571
1 files changed, 571 insertions, 0 deletions
diff --git a/board/CZ.NIC/turris_1x/turris_1x.c b/board/CZ.NIC/turris_1x/turris_1x.c
new file mode 100644
index 00000000000..7a0b68caaef
--- /dev/null
+++ b/board/CZ.NIC/turris_1x/turris_1x.c
@@ -0,0 +1,571 @@
+// SPDX-License-Identifier: GPL-2.0+
+// (C) 2022 Pali Rohár <pali@kernel.org>
+
+#include <init.h>
+#include <env.h>
+#include <fdt_support.h>
+#include <clock_legacy.h>
+#include <image.h>
+#include <asm/fsl_law.h>
+#include <asm/global_data.h>
+#include <asm/mmu.h>
+#include <dm/device.h>
+#include <dm/ofnode.h>
+#include <linux/build_bug.h>
+#include <display_options.h>
+
+#include "../turris_atsha_otp.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/*
+ * Reset time cycle register provided by Turris CPLD firmware.
+ * Turris CPLD firmware is open source and available at:
+ * https://gitlab.nic.cz/turris/hw/turris_cpld/-/blob/master/CZ_NIC_Router_CPLD.v
+ */
+#define TURRIS_CPLD_RESET_TIME_CYCLE_REG ((void *)CFG_SYS_CPLD_BASE + 0x1f)
+#define TURRIS_CPLD_RESET_TIME_CYCLE_300MS BIT(0)
+#define TURRIS_CPLD_RESET_TIME_CYCLE_1S BIT(1)
+#define TURRIS_CPLD_RESET_TIME_CYCLE_2S BIT(2)
+#define TURRIS_CPLD_RESET_TIME_CYCLE_3S BIT(3)
+#define TURRIS_CPLD_RESET_TIME_CYCLE_4S BIT(4)
+#define TURRIS_CPLD_RESET_TIME_CYCLE_5S BIT(5)
+#define TURRIS_CPLD_RESET_TIME_CYCLE_6S BIT(6)
+
+#define TURRIS_CPLD_LED_BRIGHTNESS_REG_FIRST ((void *)CFG_SYS_CPLD_BASE + 0x13)
+#define TURRIS_CPLD_LED_BRIGHTNESS_REG_LAST ((void *)CFG_SYS_CPLD_BASE + 0x1e)
+#define TURRIS_CPLD_LED_SW_OVERRIDE_REG ((void *)CFG_SYS_CPLD_BASE + 0x22)
+
+int dram_init_banksize(void)
+{
+ phys_size_t size = gd->ram_size;
+
+ static_assert(CONFIG_NR_DRAM_BANKS >= 3);
+
+ gd->bd->bi_dram[0].start = gd->ram_base;
+ gd->bd->bi_dram[0].size = get_effective_memsize();
+ size -= gd->bd->bi_dram[0].size;
+
+ /* Note: This address space is not mapped via TLB entries in U-Boot */
+
+#ifndef CONFIG_SDCARD
+ if (size > 0) {
+ /*
+ * Setup additional overlapping 1 GB DDR LAW at the end of
+ * 32-bit physical address space. It overlaps with all other
+ * peripherals on P2020 mapped to physical address space.
+ * But this is not issue because documentation says:
+ * P2020 QorIQ Integrated Processor Reference Manual,
+ * section 2.3.1 Precedence of local access windows:
+ * If two local access windows overlap, the lower
+ * numbered window takes precedence.
+ */
+ if (set_ddr_laws(0xc0000000, SZ_1G, LAW_TRGT_IF_DDR_1) < 0) {
+ printf("Error: Cannot setup DDR LAW for more than 2 GB\n");
+ return 0;
+ }
+ }
+
+ if (size > 0) {
+ /* Free space between PCIe bus 3 MEM and NOR */
+ gd->bd->bi_dram[1].start = 0xc0200000;
+ gd->bd->bi_dram[1].size = min(size, 0xef000000 - gd->bd->bi_dram[1].start);
+ size -= gd->bd->bi_dram[1].size;
+ }
+
+ if (size > 0) {
+ /* Free space between NOR and NAND */
+ gd->bd->bi_dram[2].start = 0xf0000000;
+ gd->bd->bi_dram[2].size = min(size, 0xff800000 - gd->bd->bi_dram[2].start);
+ size -= gd->bd->bi_dram[2].size;
+ }
+#else
+ puts("\n\n!!! TODO: fix sdcard >2GB RAM\n\n\n");
+#endif
+ return 0;
+}
+
+static inline int fdt_setprop_inplace_u32_partial(void *blob, int node,
+ const char *name,
+ u32 idx, u32 val)
+{
+ val = cpu_to_fdt32(val);
+
+ return fdt_setprop_inplace_namelen_partial(blob, node, name,
+ strlen(name),
+ idx * sizeof(u32),
+ &val, sizeof(u32));
+}
+
+/* Setup correct size of PCIe controller MEM in DT "ranges" property recursively */
+static void fdt_fixup_pcie_mem_size(void *blob, int node, phys_size_t pcie1_mem,
+ phys_size_t pcie2_mem, phys_size_t pcie3_mem)
+{
+ int pci_cells, cpu_cells, size_cells;
+ const u32 *ranges;
+ int pnode;
+ int i, len;
+ u32 pci_flags;
+ u64 cpu_addr;
+ u64 size;
+ u64 new_size;
+ int pcie_id;
+ int idx;
+ int subnode;
+ int ret;
+
+ if (!fdtdec_get_is_enabled(blob, node))
+ return;
+
+ ranges = fdt_getprop(blob, node, "ranges", &len);
+ if (!ranges || !len || len % sizeof(u32))
+ return;
+
+ /*
+ * The "ranges" property is an array of
+ * { <PCI address> <CPU address> <size in PCI address space> }
+ * where number of PCI address cells and size cells is stored in the
+ * "#address-cells" and "#size-cells" properties of the same node
+ * containing the "ranges" property and number of CPU address cells
+ * is stored in the parent's "#address-cells" property.
+ *
+ * All 3 elements can span a different number of cells. Fetch them.
+ */
+ pnode = fdt_parent_offset(blob, node);
+ pci_cells = fdt_address_cells(blob, node);
+ cpu_cells = fdt_address_cells(blob, pnode);
+ size_cells = fdt_size_cells(blob, node);
+
+ /* PCI addresses always use 3 cells */
+ if (pci_cells != 3)
+ return;
+
+ /* CPU addresses and sizes on P2020 may be 32-bit (1 cell) or 64-bit (2 cells) */
+ if (cpu_cells != 1 && cpu_cells != 2)
+ return;
+ if (size_cells != 1 && size_cells != 2)
+ return;
+
+ for (i = 0; i < len / sizeof(u32); i += pci_cells + cpu_cells + size_cells) {
+ /* PCI address consists of 3 cells: flags, addr.hi, addr.lo */
+ pci_flags = fdt32_to_cpu(ranges[i]);
+
+ cpu_addr = fdt32_to_cpu(ranges[i + pci_cells]);
+ if (cpu_cells == 2) {
+ cpu_addr <<= 32;
+ cpu_addr |= fdt32_to_cpu(ranges[i + pci_cells + 1]);
+ }
+
+ size = fdt32_to_cpu(ranges[i + pci_cells + cpu_cells]);
+ if (size_cells == 2) {
+ size <<= 32;
+ size |= fdt32_to_cpu(ranges[i + pci_cells + cpu_cells + 1]);
+ }
+
+ /*
+ * Bits [25:24] of PCI flags defines space code
+ * 0b10 is 32-bit MEM and 0b11 is 64-bit MEM.
+ * Check for any type of PCIe MEM mapping.
+ */
+ if (!(pci_flags & 0x02000000))
+ continue;
+
+ if (cpu_addr == CFG_SYS_PCIE1_MEM_PHYS && size > pcie1_mem) {
+ pcie_id = 1;
+ new_size = pcie1_mem;
+ } else if (cpu_addr == CFG_SYS_PCIE2_MEM_PHYS && size > pcie2_mem) {
+ pcie_id = 2;
+ new_size = pcie2_mem;
+ } else if (cpu_addr == CFG_SYS_PCIE3_MEM_PHYS && size > pcie3_mem) {
+ pcie_id = 3;
+ new_size = pcie3_mem;
+ } else {
+ continue;
+ }
+
+ printf("Decreasing PCIe MEM %d size from ", pcie_id);
+ print_size(size, " to ");
+ print_size(new_size, "\n");
+ idx = i + pci_cells + cpu_cells;
+ if (size_cells == 2) {
+ ret = fdt_setprop_inplace_u32_partial(blob, node,
+ "ranges", idx, 0);
+ if (ret)
+ goto err;
+ idx++;
+ }
+ ret = fdt_setprop_inplace_u32_partial(blob, node,
+ "ranges", idx, SZ_2M);
+ if (ret)
+ goto err;
+ }
+
+ /* Recursively fix also all subnodes */
+ fdt_for_each_subnode(subnode, blob, node)
+ fdt_fixup_pcie_mem_size(blob, subnode, pcie1_mem, pcie2_mem, pcie3_mem);
+
+ return;
+
+err:
+ printf("Error: Cannot update \"ranges\" property\n");
+}
+
+static inline phys_size_t get_law_size(phys_addr_t addr, enum law_trgt_if id)
+{
+ struct law_entry e;
+
+ e = find_law_by_addr_id(addr, id);
+ if (e.index < 0)
+ return 0;
+
+ return 2ULL << e.size;
+}
+
+void ft_memory_setup(void *blob, struct bd_info *bd)
+{
+ phys_size_t pcie1_mem, pcie2_mem, pcie3_mem;
+ u64 start[CONFIG_NR_DRAM_BANKS];
+ u64 size[CONFIG_NR_DRAM_BANKS];
+ int count;
+ int node;
+
+ if (!env_get("bootm_low") && !env_get("bootm_size")) {
+ for (count = 0; count < CONFIG_NR_DRAM_BANKS; count++) {
+ start[count] = gd->bd->bi_dram[count].start;
+ size[count] = gd->bd->bi_dram[count].size;
+ if (!size[count])
+ break;
+ }
+ fdt_fixup_memory_banks(blob, start, size, count);
+ } else {
+ fdt_fixup_memory(blob, env_get_bootm_low(), env_get_bootm_size());
+ }
+
+ pcie1_mem = get_law_size(CFG_SYS_PCIE1_MEM_PHYS, LAW_TRGT_IF_PCIE_1);
+ pcie2_mem = get_law_size(CFG_SYS_PCIE2_MEM_PHYS, LAW_TRGT_IF_PCIE_2);
+ pcie3_mem = get_law_size(CFG_SYS_PCIE3_MEM_PHYS, LAW_TRGT_IF_PCIE_3);
+
+ fdt_for_each_node_by_compatible(node, blob, -1, "fsl,mpc8548-pcie")
+ fdt_fixup_pcie_mem_size(blob, node, pcie1_mem, pcie2_mem, pcie3_mem);
+}
+
+static int detect_model_serial(const char **model, char serial[17])
+{
+ u32 version_num;
+ int err;
+
+ err = turris_atsha_otp_get_serial_number(serial);
+ if (err) {
+ *model = "Turris 1.x";
+ strcpy(serial, "unknown");
+ return -1;
+ }
+
+ version_num = simple_strtoull(serial, NULL, 16) >> 32;
+
+ /*
+ * Turris 1.0 boards (RTRS01) have version_num 0x5.
+ * Turris 1.1 boards (RTRS02) have version_num 0x6, 0x7, 0x8 and 0x9.
+ */
+ if (be32_to_cpu(version_num) >= 0x6) {
+ *model = "Turris 1.1 (RTRS02)";
+ return 1;
+ }
+
+ *model = "Turris 1.0 (RTRS01)";
+ return 0;
+}
+
+void p1_p2_rdb_pc_fix_fdt_model(void *blob)
+{
+ const char *model;
+ char serial[17];
+ int len;
+ int off;
+ int rev;
+ char c;
+
+ rev = detect_model_serial(&model, serial);
+ if (rev < 0)
+ return;
+
+ /* Turris 1.0 boards (RTRS01) do not have third PCIe controller */
+ if (rev == 0) {
+ off = fdt_path_offset(blob, "pci2");
+ if (off >= 0)
+ fdt_del_node(blob, off);
+ }
+
+ /* Fix model string only in case it is generic "Turris 1.x" */
+ model = fdt_getprop(blob, 0, "model", &len);
+ if (len < sizeof("Turris 1.x") - 1)
+ return;
+ if (memcmp(model, "Turris 1.x", sizeof("Turris 1.x") - 1) != 0)
+ return;
+
+ c = '0' + rev;
+ fdt_setprop_inplace_namelen_partial(blob, 0, "model", sizeof("model") - 1,
+ sizeof("Turris 1.") - 1, &c, 1);
+}
+
+int misc_init_r(void)
+{
+ turris_atsha_otp_init_mac_addresses(0);
+ turris_atsha_otp_init_serial_number();
+ return 0;
+}
+
+/* This comes from ../../freescale/p1_p2_rdb_pc/p1_p2_rdb_pc.c */
+extern int checkboard_p1_p2(void);
+
+int checkboard(void)
+{
+ const char *model;
+ char serial[17];
+ void *reg;
+
+ /* Disable software control of all Turris LEDs */
+ out_8(TURRIS_CPLD_LED_SW_OVERRIDE_REG, 0x00);
+
+ /* Reset colors of all Turris LEDs to their default values */
+ for (reg = TURRIS_CPLD_LED_BRIGHTNESS_REG_FIRST;
+ reg <= TURRIS_CPLD_LED_BRIGHTNESS_REG_LAST;
+ reg++)
+ out_8(reg, 0xff);
+
+ detect_model_serial(&model, serial);
+ printf("Revision: %s\n", model);
+ printf("Serial Number: %s\n", serial);
+
+ return checkboard_p1_p2();
+}
+
+static void handle_reset_button(void)
+{
+ const char * const vars[1] = { "bootcmd_rescue", };
+ u8 reset_time_raw, reset_time;
+
+ /*
+ * Ensure that bootcmd_rescue has always stock value, so that running
+ * run bootcmd_rescue
+ * always works correctly.
+ */
+ env_set_default_vars(1, (char * const *)vars, 0);
+
+ reset_time_raw = in_8(TURRIS_CPLD_RESET_TIME_CYCLE_REG);
+ if (reset_time_raw & TURRIS_CPLD_RESET_TIME_CYCLE_6S)
+ reset_time = 6;
+ else if (reset_time_raw & TURRIS_CPLD_RESET_TIME_CYCLE_5S)
+ reset_time = 5;
+ else if (reset_time_raw & TURRIS_CPLD_RESET_TIME_CYCLE_4S)
+ reset_time = 4;
+ else if (reset_time_raw & TURRIS_CPLD_RESET_TIME_CYCLE_3S)
+ reset_time = 3;
+ else if (reset_time_raw & TURRIS_CPLD_RESET_TIME_CYCLE_2S)
+ reset_time = 2;
+ else if (reset_time_raw & TURRIS_CPLD_RESET_TIME_CYCLE_1S)
+ reset_time = 1;
+ else
+ reset_time = 0;
+
+ env_set_ulong("turris_reset", reset_time);
+
+ /* Check if red reset button was hold for at least six seconds. */
+ if (reset_time >= 6) {
+ const char * const vars[3] = {
+ "bootcmd",
+ "bootdelay",
+ "distro_bootcmd",
+ };
+
+ /*
+ * Set the above envs to their default values, in case the user
+ * managed to break them.
+ */
+ env_set_default_vars(3, (char * const *)vars, 0);
+
+ /* Ensure bootcmd_rescue is used by distroboot */
+ env_set("boot_targets", "rescue");
+
+ printf("RESET button was hold for >= 6s, overwriting boot_targets for system rescue!\n");
+ } else {
+ /*
+ * In case the user somehow managed to save environment with
+ * boot_targets=rescue, reset boot_targets to default value.
+ * This could happen in subsequent commands if bootcmd_rescue
+ * failed.
+ */
+ if (!strcmp(env_get("boot_targets"), "rescue")) {
+ const char * const vars[1] = {
+ "boot_targets",
+ };
+
+ env_set_default_vars(1, (char * const *)vars, 0);
+ }
+
+ if (reset_time > 0)
+ printf("RESET button was hold for %us.\n", reset_time);
+ }
+}
+
+static int recalculate_pcie_mem_law(phys_addr_t addr,
+ pci_size_t pcie_size,
+ enum law_trgt_if id,
+ phys_addr_t *free_start,
+ phys_size_t *free_size)
+{
+ phys_size_t cur_size, new_size;
+ struct law_entry e;
+
+ e = find_law_by_addr_id(addr, id);
+ if (e.index < 0) {
+ *free_start = *free_size = 0;
+ return 0;
+ }
+
+ cur_size = 2ULL << e.size;
+ new_size = roundup_pow_of_two(pcie_size);
+
+ if (new_size >= cur_size) {
+ *free_start = *free_size = 0;
+ return 0;
+ }
+
+ set_law(e.index, addr, law_size_bits(new_size), id);
+
+ *free_start = addr + new_size;
+ *free_size = cur_size - new_size;
+ return 1;
+}
+
+static void recalculate_used_pcie_mem(void)
+{
+ phys_addr_t free_start1, free_start2;
+ phys_size_t free_size1, free_size2;
+ pci_size_t pcie1_used_mem_size;
+ pci_size_t pcie2_used_mem_size;
+ struct law_entry e;
+ phys_size_t size;
+ ofnode node;
+ int i;
+
+ size = gd->ram_size;
+
+ for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++)
+ size -= gd->bd->bi_dram[i].size;
+
+ if (size == 0)
+ return;
+
+ e = find_law_by_addr_id(CFG_SYS_PCIE3_MEM_PHYS, LAW_TRGT_IF_PCIE_3);
+ if (e.index < 0 && gd->bd->bi_dram[1].size > 0) {
+ /*
+ * If there is no LAW for PCIe 3 MEM then 3rd PCIe controller
+ * is inactive, which is the case for Turris 1.0 boards. So
+ * use its reserved 2 MB physical space for DDR RAM.
+ */
+ unsigned int bank_size = SZ_2M;
+
+ if (bank_size > size)
+ bank_size = size;
+ printf("Reserving unused ");
+ print_size(bank_size, "");
+ printf(" of PCIe 3 MEM for DDR RAM\n");
+ gd->bd->bi_dram[1].start -= bank_size;
+ gd->bd->bi_dram[1].size += bank_size;
+ size -= bank_size;
+ if (size == 0)
+ return;
+ }
+
+#ifdef CONFIG_PCI_PNP
+ /*
+ * Detect how much space of PCIe MEM is needed for both PCIe 1 and
+ * PCIe 2 controllers with all connected cards on whole hierarchy.
+ * This works only when U-Boot has enabled PCI PNP code which scans
+ * all PCI devices and calculate required memory for every PCI BAR of
+ * every PCI device.
+ */
+ ofnode_for_each_compatible_node(node, "fsl,mpc8548-pcie") {
+ struct udevice *dev;
+
+ if (device_find_global_by_ofnode(node, &dev))
+ continue;
+
+ struct pci_controller *hose = dev_get_uclass_priv(pci_get_controller(dev));
+
+ if (!hose)
+ continue;
+ if (!hose->pci_mem)
+ continue;
+ if (!hose->pci_mem->size)
+ continue;
+
+ pci_size_t used_mem_size = hose->pci_mem->bus_lower - hose->pci_mem->bus_start;
+
+ if (hose->pci_mem->phys_start == CFG_SYS_PCIE1_MEM_PHYS)
+ pcie1_used_mem_size = used_mem_size;
+ else if (hose->pci_mem->phys_start == CFG_SYS_PCIE2_MEM_PHYS)
+ pcie2_used_mem_size = used_mem_size;
+ }
+
+ if (pcie1_used_mem_size == 0 && pcie2_used_mem_size == 0)
+ return;
+
+ e = find_law_by_addr_id(0xc0000000, LAW_TRGT_IF_DDR_1);
+ if (e.index < 0) {
+ printf("Error: Cannot setup DDR LAW for more than 3 GB of RAM\n");
+ return;
+ }
+
+ /*
+ * Increase additional overlapping 1 GB DDR LAW from 1GB to 2GB by
+ * moving its left side from 0xc0000000 to 0x80000000. After this
+ * change it would overlap with PCIe MEM 1 and 2 LAWs.
+ */
+ set_law(e.index, 0x80000000, LAW_SIZE_2G, LAW_TRGT_IF_DDR_1);
+
+ i = 3;
+ static_assert(CONFIG_NR_DRAM_BANKS >= 3 + 2);
+
+ if (recalculate_pcie_mem_law(CFG_SYS_PCIE2_MEM_PHYS,
+ pcie2_used_mem_size, LAW_TRGT_IF_PCIE_2,
+ &free_start2, &free_size2)) {
+ printf("Reserving unused ");
+ print_size(free_size2, "");
+ printf(" of PCIe 2 MEM for DDR RAM\n");
+ gd->bd->bi_dram[i].start = free_start2;
+ gd->bd->bi_dram[i].size = min(size, free_size2);
+ size -= gd->bd->bi_dram[i].start;
+ i++;
+ if (size == 0)
+ return;
+ }
+
+ if (recalculate_pcie_mem_law(CFG_SYS_PCIE1_MEM_PHYS,
+ pcie1_used_mem_size, LAW_TRGT_IF_PCIE_1,
+ &free_start1, &free_size1)) {
+ printf("Reserving unused ");
+ print_size(free_size1, "");
+ printf(" of PCIe 1 MEM for DDR RAM\n");
+ gd->bd->bi_dram[i].start = free_start1;
+ gd->bd->bi_dram[i].size = min(size, free_size1);
+ size -= gd->bd->bi_dram[i].size;
+ i++;
+ if (size == 0)
+ return;
+ }
+#endif
+}
+
+int last_stage_init(void)
+{
+ handle_reset_button();
+ recalculate_used_pcie_mem();
+ return 0;
+}
+
+int get_serial_clock(void)
+{
+ return get_bus_freq(0);
+}