blob: 7a0b68caaef6eba220ccc5ab8952a1fc5cf0da5a [file] [log] [blame]
// 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);
}