aboutsummaryrefslogtreecommitdiffstats
path: root/roms/u-boot/arch/arm/mach-mvebu/armada3700/cpu.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/u-boot/arch/arm/mach-mvebu/armada3700/cpu.c')
-rw-r--r--roms/u-boot/arch/arm/mach-mvebu/armada3700/cpu.c386
1 files changed, 386 insertions, 0 deletions
diff --git a/roms/u-boot/arch/arm/mach-mvebu/armada3700/cpu.c b/roms/u-boot/arch/arm/mach-mvebu/armada3700/cpu.c
new file mode 100644
index 000000000..9aec0ce9a
--- /dev/null
+++ b/roms/u-boot/arch/arm/mach-mvebu/armada3700/cpu.c
@@ -0,0 +1,386 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2016 Stefan Roese <sr@denx.de>
+ * Copyright (C) 2020 Marek Behun <marek.behun@nic.cz>
+ */
+
+#include <common.h>
+#include <cpu_func.h>
+#include <dm.h>
+#include <fdtdec.h>
+#include <fdt_support.h>
+#include <init.h>
+#include <asm/global_data.h>
+#include <linux/bitops.h>
+#include <linux/libfdt.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/soc.h>
+#include <asm/armv8/mmu.h>
+#include <sort.h>
+
+/* Armada 3700 */
+#define MVEBU_GPIO_NB_REG_BASE (MVEBU_REGISTER(0x13800))
+
+#define MVEBU_TEST_PIN_LATCH_N (MVEBU_GPIO_NB_REG_BASE + 0x8)
+#define MVEBU_XTAL_MODE_MASK BIT(9)
+#define MVEBU_XTAL_MODE_OFFS 9
+#define MVEBU_XTAL_CLOCK_25MHZ 0x0
+#define MVEBU_XTAL_CLOCK_40MHZ 0x1
+
+#define MVEBU_NB_WARM_RST_REG (MVEBU_GPIO_NB_REG_BASE + 0x40)
+#define MVEBU_NB_WARM_RST_MAGIC_NUM 0x1d1e
+
+/* Armada 3700 CPU Address Decoder registers */
+#define MVEBU_CPU_DEC_WIN_REG_BASE (size_t)(MVEBU_REGISTER(0xcf00))
+#define MVEBU_CPU_DEC_WIN_CTRL(w) \
+ (MVEBU_CPU_DEC_WIN_REG_BASE + ((w) << 4))
+#define MVEBU_CPU_DEC_WIN_CTRL_EN BIT(0)
+#define MVEBU_CPU_DEC_WIN_CTRL_TGT_MASK 0xf
+#define MVEBU_CPU_DEC_WIN_CTRL_TGT_OFFS 4
+#define MVEBU_CPU_DEC_WIN_CTRL_TGT_DRAM 0
+#define MVEBU_CPU_DEC_WIN_CTRL_TGT_PCIE 2
+#define MVEBU_CPU_DEC_WIN_SIZE(w) (MVEBU_CPU_DEC_WIN_CTRL(w) + 0x4)
+#define MVEBU_CPU_DEC_WIN_BASE(w) (MVEBU_CPU_DEC_WIN_CTRL(w) + 0x8)
+#define MVEBU_CPU_DEC_WIN_REMAP(w) (MVEBU_CPU_DEC_WIN_CTRL(w) + 0xc)
+#define MVEBU_CPU_DEC_WIN_GRANULARITY 16
+#define MVEBU_CPU_DEC_WINS 5
+
+#define MAX_MEM_MAP_REGIONS (MVEBU_CPU_DEC_WINS + 2)
+
+#define A3700_PTE_BLOCK_NORMAL \
+ (PTE_BLOCK_MEMTYPE(MT_NORMAL) | PTE_BLOCK_INNER_SHARE)
+#define A3700_PTE_BLOCK_DEVICE \
+ (PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) | PTE_BLOCK_NON_SHARE)
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static struct mm_region mvebu_mem_map[MAX_MEM_MAP_REGIONS] = {
+ {
+ /*
+ * SRAM, MMIO regions
+ * Don't remove this, a3700_build_mem_map needs it.
+ */
+ .phys = SOC_REGS_PHY_BASE,
+ .virt = SOC_REGS_PHY_BASE,
+ .size = 0x02000000UL, /* 32MiB internal registers */
+ .attrs = A3700_PTE_BLOCK_DEVICE
+ },
+};
+
+struct mm_region *mem_map = mvebu_mem_map;
+
+static int get_cpu_dec_win(int win, u32 *tgt, u32 *base, u32 *size)
+{
+ u32 reg;
+
+ reg = readl(MVEBU_CPU_DEC_WIN_CTRL(win));
+ if (!(reg & MVEBU_CPU_DEC_WIN_CTRL_EN))
+ return -1;
+
+ if (tgt) {
+ reg >>= MVEBU_CPU_DEC_WIN_CTRL_TGT_OFFS;
+ reg &= MVEBU_CPU_DEC_WIN_CTRL_TGT_MASK;
+ *tgt = reg;
+ }
+
+ if (base) {
+ reg = readl(MVEBU_CPU_DEC_WIN_BASE(win));
+ *base = reg << MVEBU_CPU_DEC_WIN_GRANULARITY;
+ }
+
+ if (size) {
+ /*
+ * Window size is encoded as the number of 1s from LSB to MSB,
+ * followed by 0s. The number of 1s specifies the size in 64 KiB
+ * granularity.
+ */
+ reg = readl(MVEBU_CPU_DEC_WIN_SIZE(win));
+ *size = ((reg + 1) << MVEBU_CPU_DEC_WIN_GRANULARITY);
+ }
+
+ return 0;
+}
+
+/*
+ * Builds mem_map according to CPU Address Decoder settings, which were set by
+ * the TIMH image on the Cortex-M3 secure processor, or by ARM Trusted Firmware
+ */
+static void build_mem_map(void)
+{
+ int win, region;
+
+ region = 1;
+ for (win = 0; win < MVEBU_CPU_DEC_WINS; ++win) {
+ u32 base, tgt, size;
+ u64 attrs;
+
+ /* skip disabled windows */
+ if (get_cpu_dec_win(win, &tgt, &base, &size))
+ continue;
+
+ if (tgt == MVEBU_CPU_DEC_WIN_CTRL_TGT_DRAM)
+ attrs = A3700_PTE_BLOCK_NORMAL;
+ else if (tgt == MVEBU_CPU_DEC_WIN_CTRL_TGT_PCIE)
+ attrs = A3700_PTE_BLOCK_DEVICE;
+ else
+ /* skip windows with other targets */
+ continue;
+
+ mvebu_mem_map[region].phys = base;
+ mvebu_mem_map[region].virt = base;
+ mvebu_mem_map[region].size = size;
+ mvebu_mem_map[region].attrs = attrs;
+ ++region;
+ }
+
+ /* add list terminator */
+ mvebu_mem_map[region].size = 0;
+ mvebu_mem_map[region].attrs = 0;
+}
+
+void enable_caches(void)
+{
+ build_mem_map();
+
+ icache_enable();
+ dcache_enable();
+}
+
+int a3700_dram_init(void)
+{
+ int win;
+
+ gd->ram_size = 0;
+ for (win = 0; win < MVEBU_CPU_DEC_WINS; ++win) {
+ u32 base, tgt, size;
+
+ /* skip disabled windows */
+ if (get_cpu_dec_win(win, &tgt, &base, &size))
+ continue;
+
+ /* skip non-DRAM windows */
+ if (tgt != MVEBU_CPU_DEC_WIN_CTRL_TGT_DRAM)
+ continue;
+
+ /*
+ * It is possible that one image was built for boards with
+ * different RAM sizes, for example 512 MiB and 1 GiB.
+ * We therefore try to determine the actual RAM size in the
+ * window with get_ram_size.
+ */
+ gd->ram_size += get_ram_size((void *)(size_t)base, size);
+ }
+
+ return 0;
+}
+
+struct a3700_dram_window {
+ size_t base, size;
+};
+
+static int dram_win_cmp(const void *a, const void *b)
+{
+ size_t ab, bb;
+
+ ab = ((const struct a3700_dram_window *)a)->base;
+ bb = ((const struct a3700_dram_window *)b)->base;
+
+ if (ab < bb)
+ return -1;
+ else if (ab > bb)
+ return 1;
+ else
+ return 0;
+}
+
+int a3700_dram_init_banksize(void)
+{
+ struct a3700_dram_window dram_wins[MVEBU_CPU_DEC_WINS];
+ int bank, win, ndram_wins;
+ u32 last_end;
+ size_t size;
+
+ ndram_wins = 0;
+ for (win = 0; win < MVEBU_CPU_DEC_WINS; ++win) {
+ u32 base, tgt, size;
+
+ /* skip disabled windows */
+ if (get_cpu_dec_win(win, &tgt, &base, &size))
+ continue;
+
+ /* skip non-DRAM windows */
+ if (tgt != MVEBU_CPU_DEC_WIN_CTRL_TGT_DRAM)
+ continue;
+
+ dram_wins[win].base = base;
+ dram_wins[win].size = size;
+ ++ndram_wins;
+ }
+
+ qsort(dram_wins, ndram_wins, sizeof(dram_wins[0]), dram_win_cmp);
+
+ bank = 0;
+ last_end = -1;
+
+ for (win = 0; win < ndram_wins; ++win) {
+ /* again determining actual RAM size as in a3700_dram_init */
+ size = get_ram_size((void *)dram_wins[win].base,
+ dram_wins[win].size);
+
+ /*
+ * Check if previous window ends as the current starts. If yes,
+ * merge these windows into one "bank". This is possible by this
+ * simple check thanks to mem_map regions being qsorted in
+ * build_mem_map.
+ */
+ if (last_end == dram_wins[win].base) {
+ gd->bd->bi_dram[bank - 1].size += size;
+ last_end += size;
+ } else {
+ if (bank == CONFIG_NR_DRAM_BANKS) {
+ printf("Need more CONFIG_NR_DRAM_BANKS\n");
+ return -ENOBUFS;
+ }
+
+ gd->bd->bi_dram[bank].start = dram_wins[win].base;
+ gd->bd->bi_dram[bank].size = size;
+ last_end = dram_wins[win].base + size;
+ ++bank;
+ }
+ }
+
+ /*
+ * If there is more place for DRAM BANKS definitions than needed, fill
+ * the rest with zeros.
+ */
+ for (; bank < CONFIG_NR_DRAM_BANKS; ++bank) {
+ gd->bd->bi_dram[bank].start = 0;
+ gd->bd->bi_dram[bank].size = 0;
+ }
+
+ return 0;
+}
+
+static u32 find_pcie_window_base(void)
+{
+ int win;
+
+ for (win = 0; win < MVEBU_CPU_DEC_WINS; ++win) {
+ u32 base, tgt;
+
+ /* skip disabled windows */
+ if (get_cpu_dec_win(win, &tgt, &base, NULL))
+ continue;
+
+ if (tgt == MVEBU_CPU_DEC_WIN_CTRL_TGT_PCIE)
+ return base;
+ }
+
+ return -1;
+}
+
+static 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));
+}
+
+int a3700_fdt_fix_pcie_regions(void *blob)
+{
+ int acells, pacells, scells;
+ u32 base, fix_offset;
+ const u32 *ranges;
+ int node, pnode;
+ int ret, i, len;
+
+ base = find_pcie_window_base();
+ if (base == -1)
+ return -ENOENT;
+
+ node = fdt_node_offset_by_compatible(blob, -1, "marvell,armada-3700-pcie");
+ if (node < 0)
+ return node;
+
+ ranges = fdt_getprop(blob, node, "ranges", &len);
+ if (!ranges || len % sizeof(u32))
+ return -ENOENT;
+
+ /*
+ * The "ranges" property is an array of
+ * { <child address> <parent address> <size in child address space> }
+ *
+ * All 3 elements can span a diffent number of cells. Fetch their sizes.
+ */
+ pnode = fdt_parent_offset(blob, node);
+ acells = fdt_address_cells(blob, node);
+ pacells = fdt_address_cells(blob, pnode);
+ scells = fdt_size_cells(blob, node);
+
+ /* Child PCI addresses always use 3 cells */
+ if (acells != 3)
+ return -ENOENT;
+
+ /* Calculate fixup offset from first child address (in last cell) */
+ fix_offset = base - fdt32_to_cpu(ranges[2]);
+
+ /*
+ * Fix address (last cell) of each child address and each parent
+ * address
+ */
+ for (i = 0; i < len / sizeof(u32); i += acells + pacells + scells) {
+ int idx;
+
+ /* fix child address */
+ idx = i + acells - 1;
+ ret = fdt_setprop_inplace_u32_partial(blob, node, "ranges", idx,
+ fdt32_to_cpu(ranges[idx]) +
+ fix_offset);
+ if (ret)
+ return ret;
+
+ /* fix parent address */
+ idx = i + acells + pacells - 1;
+ ret = fdt_setprop_inplace_u32_partial(blob, node, "ranges", idx,
+ fdt32_to_cpu(ranges[idx]) +
+ fix_offset);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+void reset_cpu(void)
+{
+ /*
+ * Write magic number of 0x1d1e to North Bridge Warm Reset register
+ * to trigger warm reset
+ */
+ writel(MVEBU_NB_WARM_RST_MAGIC_NUM, MVEBU_NB_WARM_RST_REG);
+}
+
+/*
+ * get_ref_clk
+ *
+ * return: reference clock in MHz (25 or 40)
+ */
+u32 get_ref_clk(void)
+{
+ u32 regval;
+
+ regval = (readl(MVEBU_TEST_PIN_LATCH_N) & MVEBU_XTAL_MODE_MASK) >>
+ MVEBU_XTAL_MODE_OFFS;
+
+ if (regval == MVEBU_XTAL_CLOCK_25MHZ)
+ return 25;
+ else
+ return 40;
+}