linux/arch/arm/mach-exynos/platsmp.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2// Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
   3//              http://www.samsung.com
   4//
   5// Cloned from linux/arch/arm/mach-vexpress/platsmp.c
   6//
   7//  Copyright (C) 2002 ARM Ltd.
   8//  All Rights Reserved
   9
  10#include <linux/init.h>
  11#include <linux/errno.h>
  12#include <linux/delay.h>
  13#include <linux/jiffies.h>
  14#include <linux/smp.h>
  15#include <linux/io.h>
  16#include <linux/of_address.h>
  17#include <linux/soc/samsung/exynos-regs-pmu.h>
  18
  19#include <asm/cacheflush.h>
  20#include <asm/cp15.h>
  21#include <asm/smp_plat.h>
  22#include <asm/smp_scu.h>
  23#include <asm/firmware.h>
  24
  25#include "common.h"
  26
  27extern void exynos4_secondary_startup(void);
  28
  29/* XXX exynos_pen_release is cargo culted code - DO NOT COPY XXX */
  30volatile int exynos_pen_release = -1;
  31
  32#ifdef CONFIG_HOTPLUG_CPU
  33static inline void cpu_leave_lowpower(u32 core_id)
  34{
  35        unsigned int v;
  36
  37        asm volatile(
  38        "mrc    p15, 0, %0, c1, c0, 0\n"
  39        "       orr     %0, %0, %1\n"
  40        "       mcr     p15, 0, %0, c1, c0, 0\n"
  41        "       mrc     p15, 0, %0, c1, c0, 1\n"
  42        "       orr     %0, %0, %2\n"
  43        "       mcr     p15, 0, %0, c1, c0, 1\n"
  44          : "=&r" (v)
  45          : "Ir" (CR_C), "Ir" (0x40)
  46          : "cc");
  47}
  48
  49static inline void platform_do_lowpower(unsigned int cpu, int *spurious)
  50{
  51        u32 mpidr = cpu_logical_map(cpu);
  52        u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
  53
  54        for (;;) {
  55
  56                /* Turn the CPU off on next WFI instruction. */
  57                exynos_cpu_power_down(core_id);
  58
  59                wfi();
  60
  61                if (exynos_pen_release == core_id) {
  62                        /*
  63                         * OK, proper wakeup, we're done
  64                         */
  65                        break;
  66                }
  67
  68                /*
  69                 * Getting here, means that we have come out of WFI without
  70                 * having been woken up - this shouldn't happen
  71                 *
  72                 * Just note it happening - when we're woken, we can report
  73                 * its occurrence.
  74                 */
  75                (*spurious)++;
  76        }
  77}
  78#endif /* CONFIG_HOTPLUG_CPU */
  79
  80/**
  81 * exynos_cpu_power_down() - power down the specified cpu
  82 * @cpu: the cpu to power down
  83 *
  84 * Power down the specified cpu. The sequence must be finished by a
  85 * call to cpu_do_idle()
  86 */
  87void exynos_cpu_power_down(int cpu)
  88{
  89        u32 core_conf;
  90
  91        if (cpu == 0 && (soc_is_exynos5420() || soc_is_exynos5800())) {
  92                /*
  93                 * Bypass power down for CPU0 during suspend. Check for
  94                 * the SYS_PWR_REG value to decide if we are suspending
  95                 * the system.
  96                 */
  97                int val = pmu_raw_readl(EXYNOS5_ARM_CORE0_SYS_PWR_REG);
  98
  99                if (!(val & S5P_CORE_LOCAL_PWR_EN))
 100                        return;
 101        }
 102
 103        core_conf = pmu_raw_readl(EXYNOS_ARM_CORE_CONFIGURATION(cpu));
 104        core_conf &= ~S5P_CORE_LOCAL_PWR_EN;
 105        pmu_raw_writel(core_conf, EXYNOS_ARM_CORE_CONFIGURATION(cpu));
 106}
 107
 108/**
 109 * exynos_cpu_power_up() - power up the specified cpu
 110 * @cpu: the cpu to power up
 111 *
 112 * Power up the specified cpu
 113 */
 114void exynos_cpu_power_up(int cpu)
 115{
 116        u32 core_conf = S5P_CORE_LOCAL_PWR_EN;
 117
 118        if (soc_is_exynos3250())
 119                core_conf |= S5P_CORE_AUTOWAKEUP_EN;
 120
 121        pmu_raw_writel(core_conf,
 122                        EXYNOS_ARM_CORE_CONFIGURATION(cpu));
 123}
 124
 125/**
 126 * exynos_cpu_power_state() - returns the power state of the cpu
 127 * @cpu: the cpu to retrieve the power state from
 128 */
 129int exynos_cpu_power_state(int cpu)
 130{
 131        return (pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(cpu)) &
 132                        S5P_CORE_LOCAL_PWR_EN);
 133}
 134
 135/**
 136 * exynos_cluster_power_down() - power down the specified cluster
 137 * @cluster: the cluster to power down
 138 */
 139void exynos_cluster_power_down(int cluster)
 140{
 141        pmu_raw_writel(0, EXYNOS_COMMON_CONFIGURATION(cluster));
 142}
 143
 144/**
 145 * exynos_cluster_power_up() - power up the specified cluster
 146 * @cluster: the cluster to power up
 147 */
 148void exynos_cluster_power_up(int cluster)
 149{
 150        pmu_raw_writel(S5P_CORE_LOCAL_PWR_EN,
 151                        EXYNOS_COMMON_CONFIGURATION(cluster));
 152}
 153
 154/**
 155 * exynos_cluster_power_state() - returns the power state of the cluster
 156 * @cluster: the cluster to retrieve the power state from
 157 *
 158 */
 159int exynos_cluster_power_state(int cluster)
 160{
 161        return (pmu_raw_readl(EXYNOS_COMMON_STATUS(cluster)) &
 162                S5P_CORE_LOCAL_PWR_EN);
 163}
 164
 165/**
 166 * exynos_scu_enable() - enables SCU for Cortex-A9 based system
 167 */
 168void exynos_scu_enable(void)
 169{
 170        struct device_node *np;
 171        static void __iomem *scu_base;
 172
 173        if (!scu_base) {
 174                np = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-scu");
 175                if (np) {
 176                        scu_base = of_iomap(np, 0);
 177                        of_node_put(np);
 178                } else {
 179                        scu_base = ioremap(scu_a9_get_base(), SZ_4K);
 180                }
 181        }
 182        scu_enable(scu_base);
 183}
 184
 185static void __iomem *cpu_boot_reg_base(void)
 186{
 187        if (soc_is_exynos4210() && exynos_rev() == EXYNOS4210_REV_1_1)
 188                return pmu_base_addr + S5P_INFORM5;
 189        return sysram_base_addr;
 190}
 191
 192static inline void __iomem *cpu_boot_reg(int cpu)
 193{
 194        void __iomem *boot_reg;
 195
 196        boot_reg = cpu_boot_reg_base();
 197        if (!boot_reg)
 198                return IOMEM_ERR_PTR(-ENODEV);
 199        if (soc_is_exynos4412())
 200                boot_reg += 4*cpu;
 201        else if (soc_is_exynos5420() || soc_is_exynos5800())
 202                boot_reg += 4;
 203        return boot_reg;
 204}
 205
 206/*
 207 * Set wake up by local power mode and execute software reset for given core.
 208 *
 209 * Currently this is needed only when booting secondary CPU on Exynos3250.
 210 */
 211void exynos_core_restart(u32 core_id)
 212{
 213        unsigned int timeout = 16;
 214        u32 val;
 215
 216        if (!soc_is_exynos3250())
 217                return;
 218
 219        while (timeout && !pmu_raw_readl(S5P_PMU_SPARE2)) {
 220                timeout--;
 221                udelay(10);
 222        }
 223        if (timeout == 0) {
 224                pr_err("cpu core %u restart failed\n", core_id);
 225                return;
 226        }
 227        udelay(10);
 228
 229        val = pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(core_id));
 230        val |= S5P_CORE_WAKEUP_FROM_LOCAL_CFG;
 231        pmu_raw_writel(val, EXYNOS_ARM_CORE_STATUS(core_id));
 232
 233        pmu_raw_writel(EXYNOS_CORE_PO_RESET(core_id), EXYNOS_SWRESET);
 234}
 235
 236/*
 237 * XXX CARGO CULTED CODE - DO NOT COPY XXX
 238 *
 239 * Write exynos_pen_release in a way that is guaranteed to be visible to
 240 * all observers, irrespective of whether they're taking part in coherency
 241 * or not.  This is necessary for the hotplug code to work reliably.
 242 */
 243static void exynos_write_pen_release(int val)
 244{
 245        exynos_pen_release = val;
 246        smp_wmb();
 247        sync_cache_w(&exynos_pen_release);
 248}
 249
 250static DEFINE_SPINLOCK(boot_lock);
 251
 252static void exynos_secondary_init(unsigned int cpu)
 253{
 254        /*
 255         * let the primary processor know we're out of the
 256         * pen, then head off into the C entry point
 257         */
 258        exynos_write_pen_release(-1);
 259
 260        /*
 261         * Synchronise with the boot thread.
 262         */
 263        spin_lock(&boot_lock);
 264        spin_unlock(&boot_lock);
 265}
 266
 267int exynos_set_boot_addr(u32 core_id, unsigned long boot_addr)
 268{
 269        int ret;
 270
 271        /*
 272         * Try to set boot address using firmware first
 273         * and fall back to boot register if it fails.
 274         */
 275        ret = call_firmware_op(set_cpu_boot_addr, core_id, boot_addr);
 276        if (ret && ret != -ENOSYS)
 277                goto fail;
 278        if (ret == -ENOSYS) {
 279                void __iomem *boot_reg = cpu_boot_reg(core_id);
 280
 281                if (IS_ERR(boot_reg)) {
 282                        ret = PTR_ERR(boot_reg);
 283                        goto fail;
 284                }
 285                writel_relaxed(boot_addr, boot_reg);
 286                ret = 0;
 287        }
 288fail:
 289        return ret;
 290}
 291
 292int exynos_get_boot_addr(u32 core_id, unsigned long *boot_addr)
 293{
 294        int ret;
 295
 296        /*
 297         * Try to get boot address using firmware first
 298         * and fall back to boot register if it fails.
 299         */
 300        ret = call_firmware_op(get_cpu_boot_addr, core_id, boot_addr);
 301        if (ret && ret != -ENOSYS)
 302                goto fail;
 303        if (ret == -ENOSYS) {
 304                void __iomem *boot_reg = cpu_boot_reg(core_id);
 305
 306                if (IS_ERR(boot_reg)) {
 307                        ret = PTR_ERR(boot_reg);
 308                        goto fail;
 309                }
 310                *boot_addr = readl_relaxed(boot_reg);
 311                ret = 0;
 312        }
 313fail:
 314        return ret;
 315}
 316
 317static int exynos_boot_secondary(unsigned int cpu, struct task_struct *idle)
 318{
 319        unsigned long timeout;
 320        u32 mpidr = cpu_logical_map(cpu);
 321        u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
 322        int ret = -ENOSYS;
 323
 324        /*
 325         * Set synchronisation state between this boot processor
 326         * and the secondary one
 327         */
 328        spin_lock(&boot_lock);
 329
 330        /*
 331         * The secondary processor is waiting to be released from
 332         * the holding pen - release it, then wait for it to flag
 333         * that it has been released by resetting exynos_pen_release.
 334         *
 335         * Note that "exynos_pen_release" is the hardware CPU core ID, whereas
 336         * "cpu" is Linux's internal ID.
 337         */
 338        exynos_write_pen_release(core_id);
 339
 340        if (!exynos_cpu_power_state(core_id)) {
 341                exynos_cpu_power_up(core_id);
 342                timeout = 10;
 343
 344                /* wait max 10 ms until cpu1 is on */
 345                while (exynos_cpu_power_state(core_id)
 346                       != S5P_CORE_LOCAL_PWR_EN) {
 347                        if (timeout == 0)
 348                                break;
 349                        timeout--;
 350                        mdelay(1);
 351                }
 352
 353                if (timeout == 0) {
 354                        printk(KERN_ERR "cpu1 power enable failed");
 355                        spin_unlock(&boot_lock);
 356                        return -ETIMEDOUT;
 357                }
 358        }
 359
 360        exynos_core_restart(core_id);
 361
 362        /*
 363         * Send the secondary CPU a soft interrupt, thereby causing
 364         * the boot monitor to read the system wide flags register,
 365         * and branch to the address found there.
 366         */
 367
 368        timeout = jiffies + (1 * HZ);
 369        while (time_before(jiffies, timeout)) {
 370                unsigned long boot_addr;
 371
 372                smp_rmb();
 373
 374                boot_addr = __pa_symbol(exynos4_secondary_startup);
 375
 376                ret = exynos_set_boot_addr(core_id, boot_addr);
 377                if (ret)
 378                        goto fail;
 379
 380                call_firmware_op(cpu_boot, core_id);
 381
 382                if (soc_is_exynos3250())
 383                        dsb_sev();
 384                else
 385                        arch_send_wakeup_ipi_mask(cpumask_of(cpu));
 386
 387                if (exynos_pen_release == -1)
 388                        break;
 389
 390                udelay(10);
 391        }
 392
 393        if (exynos_pen_release != -1)
 394                ret = -ETIMEDOUT;
 395
 396        /*
 397         * now the secondary core is starting up let it run its
 398         * calibrations, then wait for it to finish
 399         */
 400fail:
 401        spin_unlock(&boot_lock);
 402
 403        return exynos_pen_release != -1 ? ret : 0;
 404}
 405
 406static void __init exynos_smp_prepare_cpus(unsigned int max_cpus)
 407{
 408        exynos_sysram_init();
 409
 410        exynos_set_delayed_reset_assertion(true);
 411
 412        if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9)
 413                exynos_scu_enable();
 414}
 415
 416#ifdef CONFIG_HOTPLUG_CPU
 417/*
 418 * platform-specific code to shutdown a CPU
 419 *
 420 * Called with IRQs disabled
 421 */
 422static void exynos_cpu_die(unsigned int cpu)
 423{
 424        int spurious = 0;
 425        u32 mpidr = cpu_logical_map(cpu);
 426        u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
 427
 428        v7_exit_coherency_flush(louis);
 429
 430        platform_do_lowpower(cpu, &spurious);
 431
 432        /*
 433         * bring this CPU back into the world of cache
 434         * coherency, and then restore interrupts
 435         */
 436        cpu_leave_lowpower(core_id);
 437
 438        if (spurious)
 439                pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious);
 440}
 441#endif /* CONFIG_HOTPLUG_CPU */
 442
 443const struct smp_operations exynos_smp_ops __initconst = {
 444        .smp_prepare_cpus       = exynos_smp_prepare_cpus,
 445        .smp_secondary_init     = exynos_secondary_init,
 446        .smp_boot_secondary     = exynos_boot_secondary,
 447#ifdef CONFIG_HOTPLUG_CPU
 448        .cpu_die                = exynos_cpu_die,
 449#endif
 450};
 451