root/sys/powerpc/mpc85xx/platform_mpc85xx.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2008-2012 Semihalf.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "opt_platform.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/pcpu.h>
#include <sys/proc.h>
#include <sys/smp.h>

#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/hid.h>
#include <machine/_inttypes.h>
#include <machine/machdep.h>
#include <machine/md_var.h>
#include <machine/platform.h>
#include <machine/platformvar.h>
#include <machine/smp.h>
#include <machine/spr.h>
#include <machine/vmparam.h>

#include <dev/fdt/fdt_common.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/ofw/openfirm.h>

#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>

#include <powerpc/mpc85xx/mpc85xx.h>

#include "platform_if.h"

#ifdef SMP
extern void *ap_pcpu;
extern vm_paddr_t kernload;             /* Kernel physical load address */
extern uint8_t __boot_page[];           /* Boot page body */
extern vm_paddr_t bp_kernload;          /* Boot page copy of kernload */
extern vm_offset_t bp_virtaddr;         /* Virtual address of boot page */
extern vm_offset_t __startkernel;

struct cpu_release {
        uint32_t entry_h;
        uint32_t entry_l;
        uint32_t r3_h;
        uint32_t r3_l;
        uint32_t reserved;
        uint32_t pir;
};
#endif

extern uint32_t *bootinfo;
vm_paddr_t ccsrbar_pa;
vm_offset_t ccsrbar_va;
vm_size_t ccsrbar_size;

static int cpu, maxcpu;

static device_t rcpm_dev;
static void dummy_freeze(device_t, bool);

static void (*freeze_timebase)(device_t, bool) = dummy_freeze;

static int mpc85xx_probe(platform_t);
static void mpc85xx_mem_regions(platform_t, struct mem_region *phys,
    int *physsz, struct mem_region *avail, int *availsz);
static u_long mpc85xx_timebase_freq(platform_t, struct cpuref *cpuref);
static int mpc85xx_smp_first_cpu(platform_t, struct cpuref *cpuref);
static int mpc85xx_smp_next_cpu(platform_t, struct cpuref *cpuref);
static int mpc85xx_smp_get_bsp(platform_t, struct cpuref *cpuref);
static int mpc85xx_smp_start_cpu(platform_t, struct pcpu *cpu);
static void mpc85xx_smp_timebase_sync(platform_t, u_long tb, int ap);

static void mpc85xx_reset(platform_t);

static platform_method_t mpc85xx_methods[] = {
        PLATFORMMETHOD(platform_probe,          mpc85xx_probe),
        PLATFORMMETHOD(platform_attach,         mpc85xx_attach),
        PLATFORMMETHOD(platform_mem_regions,    mpc85xx_mem_regions),
        PLATFORMMETHOD(platform_timebase_freq,  mpc85xx_timebase_freq),

        PLATFORMMETHOD(platform_smp_first_cpu,  mpc85xx_smp_first_cpu),
        PLATFORMMETHOD(platform_smp_next_cpu,   mpc85xx_smp_next_cpu),
        PLATFORMMETHOD(platform_smp_get_bsp,    mpc85xx_smp_get_bsp),
        PLATFORMMETHOD(platform_smp_start_cpu,  mpc85xx_smp_start_cpu),
        PLATFORMMETHOD(platform_smp_timebase_sync, mpc85xx_smp_timebase_sync),

        PLATFORMMETHOD(platform_reset,          mpc85xx_reset),

        PLATFORMMETHOD_END
};

DEFINE_CLASS_0(mpc85xx, mpc85xx_platform, mpc85xx_methods, 0);

PLATFORM_DEF(mpc85xx_platform);

static int
mpc85xx_probe(platform_t plat)
{
        u_int pvr = (mfpvr() >> 16) & 0xFFFF;

        switch (pvr) {
                case FSL_E500v1:
                case FSL_E500v2:
                case FSL_E500mc:
                case FSL_E5500:
                case FSL_E6500:
                        return (BUS_PROBE_DEFAULT);
        }
        return (ENXIO);
}

int
mpc85xx_attach(platform_t plat)
{
        phandle_t cpus, child, ccsr;
        const char *soc_name_guesses[] = {"/soc", "soc", NULL};
        const char **name;
        pcell_t ranges[6], acells, pacells, scells;
        uint64_t ccsrbar, ccsrsize;
        int i;

        if ((cpus = OF_finddevice("/cpus")) != -1) {
                for (maxcpu = 0, child = OF_child(cpus); child != 0;
                    child = OF_peer(child), maxcpu++)
                        ;
        } else
                maxcpu = 1;

        /*
         * Locate CCSR region. Irritatingly, there is no way to find it
         * unless you already know where it is. Try to infer its location
         * from the device tree.
         */

        ccsr = -1;
        for (name = soc_name_guesses; *name != NULL && ccsr == -1; name++)
                ccsr = OF_finddevice(*name);
        if (ccsr == -1) {
                char type[64];

                /* That didn't work. Search for devices of type "soc" */
                child = OF_child(OF_peer(0));
                for (OF_child(child); child != 0; child = OF_peer(child)) {
                        if (OF_getprop(child, "device_type", type, sizeof(type))
                            <= 0)
                                continue;

                        if (strcmp(type, "soc") == 0) {
                                ccsr = child;
                                break;
                        }
                }
        }

        if (ccsr == -1)
                panic("Could not locate CCSR window!");

        OF_getprop(ccsr, "#size-cells", &scells, sizeof(scells));
        OF_getprop(ccsr, "#address-cells", &acells, sizeof(acells));
        OF_searchprop(OF_parent(ccsr), "#address-cells", &pacells,
            sizeof(pacells));
        OF_getprop(ccsr, "ranges", ranges, sizeof(ranges));
        ccsrbar = ccsrsize = 0;
        for (i = acells; i < acells + pacells; i++) {
                ccsrbar <<= 32;
                ccsrbar |= ranges[i];
        }
        for (i = acells + pacells; i < acells + pacells + scells; i++) {
                ccsrsize <<= 32;
                ccsrsize |= ranges[i];
        }
        ccsrbar_va = pmap_early_io_map(ccsrbar, ccsrsize);
        ccsrbar_pa = ccsrbar;
        ccsrbar_size = ccsrsize;

        mpc85xx_enable_l3_cache();

        return (0);
}

void
mpc85xx_mem_regions(platform_t plat, struct mem_region *phys, int *physsz,
    struct mem_region *avail, int *availsz)
{

        ofw_mem_regions(phys, physsz, avail, availsz);
}

static u_long
mpc85xx_timebase_freq(platform_t plat, struct cpuref *cpuref)
{
        u_long ticks;
        phandle_t cpus, child;
        pcell_t freq;

        if (bootinfo != NULL) {
                if (bootinfo[0] == 1) {
                        /* Backward compatibility. See 8-STABLE. */
                        ticks = bootinfo[3] >> 3;
                } else {
                        /* Compatibility with Juniper's loader. */
                        ticks = bootinfo[5] >> 3;
                }
        } else
                ticks = 0;

        if ((cpus = OF_finddevice("/cpus")) == -1)
                goto out;

        if ((child = OF_child(cpus)) == 0)
                goto out;

        switch (OF_getproplen(child, "timebase-frequency")) {
        case 4:
        {
                uint32_t tbase;
                OF_getprop(child, "timebase-frequency", &tbase, sizeof(tbase));
                ticks = tbase;
                return (ticks);
        }
        case 8:
        {
                uint64_t tbase;
                OF_getprop(child, "timebase-frequency", &tbase, sizeof(tbase));
                ticks = tbase;
                return (ticks);
        }
        default:
                break;
        }

        freq = 0;
        if (OF_getprop(child, "bus-frequency", (void *)&freq,
            sizeof(freq)) <= 0)
                goto out;

        if (freq == 0)
                goto out;

        /*
         * Time Base and Decrementer are updated every 8 CCB bus clocks.
         * HID0[SEL_TBCLK] = 0
         */
        if (mpc85xx_is_qoriq())
                ticks = freq / 32;
        else
                ticks = freq / 8;

out:
        if (ticks <= 0)
                panic("Unable to determine timebase frequency!");

        return (ticks);
}

static int
mpc85xx_smp_first_cpu(platform_t plat, struct cpuref *cpuref)
{

        cpu = 0;
        cpuref->cr_cpuid = cpu;
        cpuref->cr_hwref = cpuref->cr_cpuid;
        if (bootverbose)
                printf("powerpc_smp_first_cpu: cpuid %d\n", cpuref->cr_cpuid);
        cpu++;

        return (0);
}

static int
mpc85xx_smp_next_cpu(platform_t plat, struct cpuref *cpuref)
{
        phandle_t node;
        pcell_t reg;
        int i;

        if (cpu >= maxcpu)
                return (ENOENT);

        cpuref->cr_cpuid = cpu++;

        node = OF_finddevice("/cpus");
        for (i = 0, node = OF_child(node); i < cpuref->cr_cpuid;
            i++, node = OF_peer(node))
                ;
        if (OF_getencprop(node, "reg", &reg, sizeof(reg)) > 0)
                cpuref->cr_hwref = reg;
        else
                cpuref->cr_hwref = cpuref->cr_cpuid;
        if (bootverbose)
                printf("powerpc_smp_next_cpu: cpuid %d, hwref %d\n",
                    cpuref->cr_cpuid, (int)cpuref->cr_hwref);

        return (0);
}

static int
mpc85xx_smp_get_bsp(platform_t plat, struct cpuref *cpuref)
{

        cpuref->cr_cpuid = mfspr(SPR_PIR);
        cpuref->cr_hwref = cpuref->cr_cpuid;

        return (0);
}

#ifdef SMP
static int
mpc85xx_smp_start_cpu_epapr(platform_t plat, struct pcpu *pc)
{
        vm_paddr_t rel_pa, bptr;
        volatile struct cpu_release *rel;
        vm_offset_t rel_va, rel_page;
        phandle_t node;
        int i;

        /* If we're calling this, the node already exists. */
        node = OF_finddevice("/cpus");
        for (i = 0, node = OF_child(node); i < pc->pc_cpuid;
            i++, node = OF_peer(node))
                ;
        if (OF_getencprop(node, "cpu-release-addr", (pcell_t *)&rel_pa,
            sizeof(rel_pa)) == -1) {
                return (ENOENT);
        }

        rel_page = kva_alloc(PAGE_SIZE);
        if (rel_page == 0)
                return (ENOMEM);

        critical_enter();
        rel_va = rel_page + (rel_pa & PAGE_MASK);
        pmap_kenter(rel_page, rel_pa & ~PAGE_MASK);
        rel = (struct cpu_release *)rel_va;
        bptr = pmap_kextract((uintptr_t)__boot_page);

        cpu_flush_dcache(__DEVOLATILE(struct cpu_release *,rel), sizeof(*rel));
        rel->pir = pc->pc_cpuid; __asm __volatile("sync" ::: "memory");
        rel->entry_h = (bptr >> 32); __asm __volatile("sync" ::: "memory");
        cpu_flush_dcache(__DEVOLATILE(struct cpu_release *,rel), sizeof(*rel));
        rel->entry_l = bptr & 0xffffffff; __asm __volatile("sync" ::: "memory");
        cpu_flush_dcache(__DEVOLATILE(struct cpu_release *,rel), sizeof(*rel));
        if (bootverbose)
                printf("Waking up CPU %d via CPU release page %p\n",
                    pc->pc_cpuid, rel);
        critical_exit();
        pmap_kremove(rel_page);
        kva_free(rel_page, PAGE_SIZE);

        return (0);
}
#endif

static int
mpc85xx_smp_start_cpu(platform_t plat, struct pcpu *pc)
{
#ifdef SMP
        vm_paddr_t bptr;
        uint32_t reg;
        int timeout;
        uintptr_t brr;
        int cpuid;
        int epapr_boot = 0;
        uint32_t tgt;

        if (mpc85xx_is_qoriq()) {
                reg = ccsr_read4(OCP85XX_COREDISR);
                cpuid = pc->pc_cpuid;

                if ((reg & (1 << cpuid)) != 0) {
                    printf("%s: CPU %d is disabled!\n", __func__, pc->pc_cpuid);
                    return (-1);
                }

                brr = OCP85XX_BRR;
        } else {
                brr = OCP85XX_EEBPCR;
                cpuid = pc->pc_cpuid + 24;
        }
        bp_kernload = kernload;
        bp_virtaddr = (vm_offset_t)&__boot_page;
        /*
         * bp_kernload and bp_virtaddr are in the boot page.  Sync the cache
         * because ePAPR booting has the other core(s) already running.
         */
        cpu_flush_dcache(&bp_kernload, sizeof(bp_kernload));
        cpu_flush_dcache(&bp_virtaddr, sizeof(bp_virtaddr));

        ap_pcpu = pc;
        __asm __volatile("msync; isync");

        /* First try the ePAPR way. */
        if (mpc85xx_smp_start_cpu_epapr(plat, pc) == 0) {
                epapr_boot = 1;
                goto spin_wait;
        }

        reg = ccsr_read4(brr);
        if ((reg & (1 << cpuid)) != 0) {
                printf("SMP: CPU %d already out of hold-off state!\n",
                    pc->pc_cpuid);
                return (ENXIO);
        }

        /* Flush caches to have our changes hit DRAM. */
        cpu_flush_dcache(__boot_page, 4096);

        bptr = pmap_kextract((uintptr_t)__boot_page);
        KASSERT((bptr & 0xfff) == 0,
            ("%s: boot page is not aligned (%#jx)", __func__, (uintmax_t)bptr));
        if (mpc85xx_is_qoriq()) {
                /*
                 * Read DDR controller configuration to select proper BPTR target ID.
                 *
                 * On P5020 bit 29 of DDR1_CS0_CONFIG enables DDR controllers
                 * interleaving. If this bit is set, we have to use
                 * OCP85XX_TGTIF_RAM_INTL as BPTR target ID. On other QorIQ DPAA SoCs,
                 * this bit is reserved and always 0.
                 */

                reg = ccsr_read4(OCP85XX_DDR1_CS0_CONFIG);
                if (reg & (1 << 29))
                        tgt = OCP85XX_TGTIF_RAM_INTL;
                else
                        tgt = OCP85XX_TGTIF_RAM1;

                /*
                 * Set BSTR to the physical address of the boot page
                 */
                ccsr_write4(OCP85XX_BSTRH, bptr >> 32);
                ccsr_write4(OCP85XX_BSTRL, bptr);
                ccsr_write4(OCP85XX_BSTAR, OCP85XX_ENA_MASK |
                    (tgt << OCP85XX_TRGT_SHIFT_QORIQ) | (ffsl(PAGE_SIZE) - 2));

                /* Read back OCP85XX_BSTAR to synchronize write */
                ccsr_read4(OCP85XX_BSTAR);

                /*
                 * Enable and configure time base on new CPU.
                 */

                /* Set TB clock source to platform clock / 32 */
                reg = ccsr_read4(CCSR_CTBCKSELR);
                ccsr_write4(CCSR_CTBCKSELR, reg & ~(1 << pc->pc_cpuid));

                /* Enable TB */
                reg = ccsr_read4(CCSR_CTBENR);
                ccsr_write4(CCSR_CTBENR, reg | (1 << pc->pc_cpuid));
        } else {
                /*
                 * Set BPTR to the physical address of the boot page
                 */
                bptr = (bptr >> 12) | 0x80000000u;
                ccsr_write4(OCP85XX_BPTR, bptr);
                __asm __volatile("isync; msync");
        }

        /*
         * Release AP from hold-off state
         */
        reg = ccsr_read4(brr);
        ccsr_write4(brr, reg | (1 << cpuid));
        __asm __volatile("isync; msync");

spin_wait:
        timeout = 500;
        while (!pc->pc_awake && timeout--)
                DELAY(1000);    /* wait 1ms */

        /*
         * Disable boot page translation so that the 4K page at the default
         * address (= 0xfffff000) isn't permanently remapped and thus not
         * usable otherwise.
         */
        if (!epapr_boot) {
                if (mpc85xx_is_qoriq())
                        ccsr_write4(OCP85XX_BSTAR, 0);
                else
                        ccsr_write4(OCP85XX_BPTR, 0);
                __asm __volatile("isync; msync");
        }

        if (!pc->pc_awake)
                panic("SMP: CPU %d didn't wake up.\n", pc->pc_cpuid);
        return ((pc->pc_awake) ? 0 : EBUSY);
#else
        /* No SMP support */
        return (ENXIO);
#endif
}

static void
mpc85xx_reset(platform_t plat)
{

        /*
         * Try the dedicated reset register first.
         * If the SoC doesn't have one, we'll fall
         * back to using the debug control register.
         */
        ccsr_write4(OCP85XX_RSTCR, 2);

        mtmsr(mfmsr() & ~PSL_DE);

        /* Enable debug interrupts and issue reset. */
        mtspr(SPR_DBCR0, DBCR0_IDM | DBCR0_RST_SYSTEM);
        __asm __volatile("isync");

        /* Enable Debug Interrupts in MSR. */
        mtmsr(mfmsr() | PSL_DE);

        printf("Reset failed...\n");
        while (1)
                ;
}

static void
mpc85xx_smp_timebase_sync(platform_t plat, u_long tb, int ap)
{
        static volatile bool tb_ready;
        static volatile int cpu_done;

        if (ap) {
                /* APs.  Hold off until we get a stable timebase. */
                while (!tb_ready)
                        atomic_thread_fence_seq_cst();
                mttb(tb);
                atomic_add_int(&cpu_done, 1);
                while (cpu_done < mp_ncpus)
                        atomic_thread_fence_seq_cst();
        } else {
                /* BSP */
                freeze_timebase(rcpm_dev, true);
                tb_ready = true;
                mttb(tb);
                atomic_add_int(&cpu_done, 1);
                while (cpu_done < mp_ncpus)
                        atomic_thread_fence_seq_cst();
                freeze_timebase(rcpm_dev, false);
        }
}

/* Fallback freeze.  In case no real handler is found in the device tree. */
static void
dummy_freeze(device_t dev, bool freeze)
{
        /* Nothing to do here, move along. */
}

/* QorIQ Run control/power management timebase management. */

#define RCPM_CTBENR     0x00000084
struct mpc85xx_rcpm_softc {
        struct resource *sc_mem;
};

static void
mpc85xx_rcpm_freeze_timebase(device_t dev, bool freeze)
{
        struct mpc85xx_rcpm_softc *sc;

        sc = device_get_softc(dev);

        if (freeze)
                bus_write_4(sc->sc_mem, RCPM_CTBENR, 0);
        else
                bus_write_4(sc->sc_mem, RCPM_CTBENR, (1 << maxcpu) - 1);
}

static int
mpc85xx_rcpm_probe(device_t dev)
{
        if (!ofw_bus_is_compatible(dev, "fsl,qoriq-rcpm-1.0"))
                return (ENXIO);

        device_set_desc(dev, "QorIQ Run control and power management");
        return (BUS_PROBE_GENERIC);
}

static int
mpc85xx_rcpm_attach(device_t dev)
{
        struct mpc85xx_rcpm_softc *sc;
        int rid;

        sc = device_get_softc(dev);
        freeze_timebase = mpc85xx_rcpm_freeze_timebase;
        rcpm_dev = dev;

        rid = 0;
        sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
            RF_ACTIVE | RF_SHAREABLE);

        return (0);
}

static device_method_t mpc85xx_rcpm_methods[] = {
        DEVMETHOD(device_probe,         mpc85xx_rcpm_probe),
        DEVMETHOD(device_attach,        mpc85xx_rcpm_attach),
        DEVMETHOD_END
};

static driver_t mpc85xx_rcpm_driver = {
        "rcpm",
        mpc85xx_rcpm_methods,
        sizeof(struct mpc85xx_rcpm_softc)
};

EARLY_DRIVER_MODULE(mpc85xx_rcpm, simplebus, mpc85xx_rcpm_driver, 0, 0,
    BUS_PASS_BUS);

/* "Global utilities" power management/Timebase management. */

#define GUTS_DEVDISR    0x00000070
#define   DEVDISR_TB0   0x00004000
#define   DEVDISR_TB1   0x00001000

struct mpc85xx_guts_softc {
        struct resource *sc_mem;
};

static void
mpc85xx_guts_freeze_timebase(device_t dev, bool freeze)
{
        struct mpc85xx_guts_softc *sc;
        uint32_t devdisr;

        sc = device_get_softc(dev);

        devdisr = bus_read_4(sc->sc_mem, GUTS_DEVDISR);
        if (freeze)
                bus_write_4(sc->sc_mem, GUTS_DEVDISR,
                    devdisr | (DEVDISR_TB0 | DEVDISR_TB1));
        else
                bus_write_4(sc->sc_mem, GUTS_DEVDISR,
                    devdisr & ~(DEVDISR_TB0 | DEVDISR_TB1));
}

static int
mpc85xx_guts_probe(device_t dev)
{
        if (!ofw_bus_is_compatible(dev, "fsl,mpc8572-guts") &&
            !ofw_bus_is_compatible(dev, "fsl,p1020-guts") &&
            !ofw_bus_is_compatible(dev, "fsl,p1021-guts") &&
            !ofw_bus_is_compatible(dev, "fsl,p1022-guts") &&
            !ofw_bus_is_compatible(dev, "fsl,p1023-guts") &&
            !ofw_bus_is_compatible(dev, "fsl,p2020-guts"))
                return (ENXIO);

        device_set_desc(dev, "MPC85xx Global Utilities");
        return (BUS_PROBE_GENERIC);
}

static int
mpc85xx_guts_attach(device_t dev)
{
        struct mpc85xx_rcpm_softc *sc;
        int rid;

        sc = device_get_softc(dev);
        freeze_timebase = mpc85xx_guts_freeze_timebase;
        rcpm_dev = dev;

        rid = 0;
        sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
            RF_ACTIVE | RF_SHAREABLE);

        return (0);
}

static device_method_t mpc85xx_guts_methods[] = {
        DEVMETHOD(device_probe,         mpc85xx_guts_probe),
        DEVMETHOD(device_attach,        mpc85xx_guts_attach),
        DEVMETHOD_END
};

static driver_t mpc85xx_guts_driver = {
        "guts",
        mpc85xx_guts_methods,
        sizeof(struct mpc85xx_guts_softc)
};

EARLY_DRIVER_MODULE(mpc85xx_guts, simplebus, mpc85xx_guts_driver, 0, 0,
    BUS_PASS_BUS);