root/arch/mips/generic/init.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2016 Imagination Technologies
 * Author: Paul Burton <paul.burton@mips.com>
 */

#include <linux/clk.h>
#include <linux/clocksource.h>
#include <linux/init.h>
#include <linux/irqchip.h>
#include <linux/of_clk.h>
#include <linux/of_fdt.h>

#include <asm/bootinfo.h>
#include <asm/fw/fw.h>
#include <asm/irq_cpu.h>
#include <asm/machine.h>
#include <asm/mips-cps.h>
#include <asm/prom.h>
#include <asm/smp-ops.h>
#include <asm/time.h>

static __initconst const void *fdt;
static __initconst const struct mips_machine *mach;
static __initconst const void *mach_match_data;

void __init prom_init(void)
{
        plat_get_fdt();
        BUG_ON(!fdt);
}

void __init *plat_get_fdt(void)
{
        const struct mips_machine *check_mach;
        const struct of_device_id *match;

        if (fdt)
                /* Already set up */
                return (void *)fdt;

        fdt = (void *)get_fdt();
        if (fdt && !fdt_check_header(fdt)) {
                /*
                 * We have been provided with the appropriate device tree for
                 * the board. Make use of it & search for any machine struct
                 * based upon the root compatible string.
                 */
                for_each_mips_machine(check_mach) {
                        match = mips_machine_is_compatible(check_mach, fdt);
                        if (match) {
                                mach = check_mach;
                                mach_match_data = match->data;
                                break;
                        }
                }
        } else if (IS_ENABLED(CONFIG_LEGACY_BOARDS)) {
                /*
                 * We weren't booted using the UHI boot protocol, but do
                 * support some number of boards with legacy boot protocols.
                 * Attempt to find the right one.
                 */
                for_each_mips_machine(check_mach) {
                        if (!check_mach->detect)
                                continue;

                        if (!check_mach->detect())
                                continue;

                        mach = check_mach;
                }

                /*
                 * If we don't recognise the machine then we can't continue, so
                 * die here.
                 */
                BUG_ON(!mach);

                /* Retrieve the machine's FDT */
                fdt = mach->fdt;
        }
        return (void *)fdt;
}

#ifdef CONFIG_RELOCATABLE

void __init plat_fdt_relocated(void *new_location)
{
        /*
         * reset fdt as the cached value would point to the location
         * before relocations happened and update the location argument
         * if it was passed using UHI
         */
        fdt = NULL;

        if (fw_arg0 == -2)
                fw_arg1 = (unsigned long)new_location;
}

#endif /* CONFIG_RELOCATABLE */

void __init plat_mem_setup(void)
{
        if (mach && mach->fixup_fdt)
                fdt = mach->fixup_fdt(fdt, mach_match_data);

        fw_init_cmdline();
        __dt_setup_arch((void *)fdt);
}

void __init device_tree_init(void)
{
        unflatten_and_copy_device_tree();
        mips_cpc_probe();

        if (!register_cps_smp_ops())
                return;
        if (!register_vsmp_smp_ops())
                return;

        register_up_smp_ops();
}

int __init apply_mips_fdt_fixups(void *fdt_out, size_t fdt_out_size,
                                 const void *fdt_in,
                                 const struct mips_fdt_fixup *fixups)
{
        int err;

        err = fdt_open_into(fdt_in, fdt_out, fdt_out_size);
        if (err) {
                pr_err("Failed to open FDT\n");
                return err;
        }

        for (; fixups->apply; fixups++) {
                err = fixups->apply(fdt_out);
                if (err) {
                        pr_err("Failed to apply FDT fixup \"%s\"\n",
                               fixups->description);
                        return err;
                }
        }

        err = fdt_pack(fdt_out);
        if (err)
                pr_err("Failed to pack FDT\n");
        return err;
}

void __init plat_time_init(void)
{
        struct device_node *np;
        struct clk *clk;

        of_clk_init(NULL);

        if (!cpu_has_counter) {
                mips_hpt_frequency = 0;
        } else if (mach && mach->measure_hpt_freq) {
                mips_hpt_frequency = mach->measure_hpt_freq();
        } else {
                np = of_get_cpu_node(0, NULL);
                if (!np) {
                        pr_err("Failed to get CPU node\n");
                        return;
                }

                clk = of_clk_get(np, 0);
                if (IS_ERR(clk)) {
                        pr_err("Failed to get CPU clock: %ld\n", PTR_ERR(clk));
                        return;
                }

                mips_hpt_frequency = clk_get_rate(clk);
                clk_put(clk);

                switch (boot_cpu_type()) {
                case CPU_20KC:
                case CPU_25KF:
                        /* The counter runs at the CPU clock rate */
                        break;
                default:
                        /* The counter runs at half the CPU clock rate */
                        mips_hpt_frequency /= 2;
                        break;
                }
        }

        timer_probe();
}

void __init arch_init_irq(void)
{
        struct device_node *intc_node;

        intc_node = of_find_compatible_node(NULL, NULL,
                                            "mti,cpu-interrupt-controller");
        if (!cpu_has_veic && !intc_node)
                mips_cpu_irq_init();
        of_node_put(intc_node);

        irqchip_init();
}