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_core_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 *
  87 */
  88void exynos_cpu_power_down(int cpu)
  89{
  90        u32 core_conf;
  91
  92        if (cpu == 0 && (soc_is_exynos5420() || soc_is_exynos5800())) {
  93                /*
  94                 * Bypass power down for CPU0 during suspend. Check for
  95                 * the SYS_PWR_REG value to decide if we are suspending
  96                 * the system.
  97                 */
  98                int val = pmu_raw_readl(EXYNOS5_ARM_CORE0_SYS_PWR_REG);
  99
 100                if (!(val & S5P_CORE_LOCAL_PWR_EN))
 101                        return;
 102        }
 103
 104        core_conf = pmu_raw_readl(EXYNOS_ARM_CORE_CONFIGURATION(cpu));
 105        core_conf &= ~S5P_CORE_LOCAL_PWR_EN;
 106        pmu_raw_writel(core_conf, EXYNOS_ARM_CORE_CONFIGURATION(cpu));
 107}
 108
 109/**
 110 * exynos_cpu_power_up : power up the specified cpu
 111 * @cpu : the cpu to power up
 112 *
 113 * Power up the specified cpu
 114 */
 115void exynos_cpu_power_up(int cpu)
 116{
 117        u32 core_conf = S5P_CORE_LOCAL_PWR_EN;
 118
 119        if (soc_is_exynos3250())
 120                core_conf |= S5P_CORE_AUTOWAKEUP_EN;
 121
 122        pmu_raw_writel(core_conf,
 123                        EXYNOS_ARM_CORE_CONFIGURATION(cpu));
 124}
 125
 126/**
 127 * exynos_cpu_power_state : returns the power state of the cpu
 128 * @cpu : the cpu to retrieve the power state from
 129 *
 130 */
 131int exynos_cpu_power_state(int cpu)
 132{
 133        return (pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(cpu)) &
 134                        S5P_CORE_LOCAL_PWR_EN);
 135}
 136
 137/**
 138 * exynos_cluster_power_down : power down the specified cluster
 139 * @cluster : the cluster to power down
 140 */
 141void exynos_cluster_power_down(int cluster)
 142{
 143        pmu_raw_writel(0, EXYNOS_COMMON_CONFIGURATION(cluster));
 144}
 145
 146/**
 147 * exynos_cluster_power_up : power up the specified cluster
 148 * @cluster : the cluster to power up
 149 */
 150void exynos_cluster_power_up(int cluster)
 151{
 152        pmu_raw_writel(S5P_CORE_LOCAL_PWR_EN,
 153                        EXYNOS_COMMON_CONFIGURATION(cluster));
 154}
 155
 156/**
 157 * exynos_cluster_power_state : returns the power state of the cluster
 158 * @cluster : the cluster to retrieve the power state from
 159 *
 160 */
 161int exynos_cluster_power_state(int cluster)
 162{
 163        return (pmu_raw_readl(EXYNOS_COMMON_STATUS(cluster)) &
 164                S5P_CORE_LOCAL_PWR_EN);
 165}
 166
 167/**
 168 * exynos_scu_enable : enables SCU for Cortex-A9 based system
 169 */
 170void exynos_scu_enable(void)
 171{
 172        struct device_node *np;
 173        static void __iomem *scu_base;
 174
 175        if (!scu_base) {
 176                np = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-scu");
 177                if (np) {
 178                        scu_base = of_iomap(np, 0);
 179                        of_node_put(np);
 180                } else {
 181                        scu_base = ioremap(scu_a9_get_base(), SZ_4K);
 182                }
 183        }
 184        scu_enable(scu_base);
 185}
 186
 187static void __iomem *cpu_boot_reg_base(void)
 188{
 189        if (soc_is_exynos4210() && exynos_rev() == EXYNOS4210_REV_1_1)
 190                return pmu_base_addr + S5P_INFORM5;
 191        return sysram_base_addr;
 192}
 193
 194static inline void __iomem *cpu_boot_reg(int cpu)
 195{
 196        void __iomem *boot_reg;
 197
 198        boot_reg = cpu_boot_reg_base();
 199        if (!boot_reg)
 200                return IOMEM_ERR_PTR(-ENODEV);
 201        if (soc_is_exynos4412())
 202                boot_reg += 4*cpu;
 203        else if (soc_is_exynos5420() || soc_is_exynos5800())
 204                boot_reg += 4;
 205        return boot_reg;
 206}
 207
 208/*
 209 * Set wake up by local power mode and execute software reset for given core.
 210 *
 211 * Currently this is needed only when booting secondary CPU on Exynos3250.
 212 */
 213void exynos_core_restart(u32 core_id)
 214{
 215        unsigned int timeout = 16;
 216        u32 val;
 217
 218        if (!soc_is_exynos3250())
 219                return;
 220
 221        while (timeout && !pmu_raw_readl(S5P_PMU_SPARE2)) {
 222                timeout--;
 223                udelay(10);
 224        }
 225        if (timeout == 0) {
 226                pr_err("cpu core %u restart failed\n", core_id);
 227                return;
 228        }
 229        udelay(10);
 230
 231        val = pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(core_id));
 232        val |= S5P_CORE_WAKEUP_FROM_LOCAL_CFG;
 233        pmu_raw_writel(val, EXYNOS_ARM_CORE_STATUS(core_id));
 234
 235        pmu_raw_writel(EXYNOS_CORE_PO_RESET(core_id), EXYNOS_SWRESET);
 236}
 237
 238/*
 239 * XXX CARGO CULTED CODE - DO NOT COPY XXX
 240 *
 241 * Write exynos_pen_release in a way that is guaranteed to be visible to
 242 * all observers, irrespective of whether they're taking part in coherency
 243 * or not.  This is necessary for the hotplug code to work reliably.
 244 */
 245static void exynos_write_pen_release(int val)
 246{
 247        exynos_pen_release = val;
 248        smp_wmb();
 249        sync_cache_w(&exynos_pen_release);
 250}
 251
 252static DEFINE_SPINLOCK(boot_lock);
 253
 254static void exynos_secondary_init(unsigned int cpu)
 255{
 256        /*
 257         * let the primary processor know we're out of the
 258         * pen, then head off into the C entry point
 259         */
 260        exynos_write_pen_release(-1);
 261
 262        /*
 263         * Synchronise with the boot thread.
 264         */
 265        spin_lock(&boot_lock);
 266        spin_unlock(&boot_lock);
 267}
 268
 269int exynos_set_boot_addr(u32 core_id, unsigned long boot_addr)
 270{
 271        int ret;
 272
 273        /*
 274         * Try to set boot address using firmware first
 275         * and fall back to boot register if it fails.
 276         */
 277        ret = call_firmware_op(set_cpu_boot_addr, core_id, boot_addr);
 278        if (ret && ret != -ENOSYS)
 279                goto fail;
 280        if (ret == -ENOSYS) {
 281                void __iomem *boot_reg = cpu_boot_reg(core_id);
 282
 283                if (IS_ERR(boot_reg)) {
 284                        ret = PTR_ERR(boot_reg);
 285                        goto fail;
 286                }
 287                writel_relaxed(boot_addr, boot_reg);
 288                ret = 0;
 289        }
 290fail:
 291        return ret;
 292}
 293
 294int exynos_get_boot_addr(u32 core_id, unsigned long *boot_addr)
 295{
 296        int ret;
 297
 298        /*
 299         * Try to get boot address using firmware first
 300         * and fall back to boot register if it fails.
 301         */
 302        ret = call_firmware_op(get_cpu_boot_addr, core_id, boot_addr);
 303        if (ret && ret != -ENOSYS)
 304                goto fail;
 305        if (ret == -ENOSYS) {
 306                void __iomem *boot_reg = cpu_boot_reg(core_id);
 307
 308                if (IS_ERR(boot_reg)) {
 309                        ret = PTR_ERR(boot_reg);
 310                        goto fail;
 311                }
 312                *boot_addr = readl_relaxed(boot_reg);
 313                ret = 0;
 314        }
 315fail:
 316        return ret;
 317}
 318
 319static int exynos_boot_secondary(unsigned int cpu, struct task_struct *idle)
 320{
 321        unsigned long timeout;
 322        u32 mpidr = cpu_logical_map(cpu);
 323        u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
 324        int ret = -ENOSYS;
 325
 326        /*
 327         * Set synchronisation state between this boot processor
 328         * and the secondary one
 329         */
 330        spin_lock(&boot_lock);
 331
 332        /*
 333         * The secondary processor is waiting to be released from
 334         * the holding pen - release it, then wait for it to flag
 335         * that it has been released by resetting exynos_pen_release.
 336         *
 337         * Note that "exynos_pen_release" is the hardware CPU core ID, whereas
 338         * "cpu" is Linux's internal ID.
 339         */
 340        exynos_write_pen_release(core_id);
 341
 342        if (!exynos_cpu_power_state(core_id)) {
 343                exynos_cpu_power_up(core_id);
 344                timeout = 10;
 345
 346                /* wait max 10 ms until cpu1 is on */
 347                while (exynos_cpu_power_state(core_id)
 348                       != S5P_CORE_LOCAL_PWR_EN) {
 349                        if (timeout == 0)
 350                                break;
 351                        timeout--;
 352                        mdelay(1);
 353                }
 354
 355                if (timeout == 0) {
 356                        printk(KERN_ERR "cpu1 power enable failed");
 357                        spin_unlock(&boot_lock);
 358                        return -ETIMEDOUT;
 359                }
 360        }
 361
 362        exynos_core_restart(core_id);
 363
 364        /*
 365         * Send the secondary CPU a soft interrupt, thereby causing
 366         * the boot monitor to read the system wide flags register,
 367         * and branch to the address found there.
 368         */
 369
 370        timeout = jiffies + (1 * HZ);
 371        while (time_before(jiffies, timeout)) {
 372                unsigned long boot_addr;
 373
 374                smp_rmb();
 375
 376                boot_addr = __pa_symbol(exynos4_secondary_startup);
 377
 378                ret = exynos_set_boot_addr(core_id, boot_addr);
 379                if (ret)
 380                        goto fail;
 381
 382                call_firmware_op(cpu_boot, core_id);
 383
 384                if (soc_is_exynos3250())
 385                        dsb_sev();
 386                else
 387                        arch_send_wakeup_ipi_mask(cpumask_of(cpu));
 388
 389                if (exynos_pen_release == -1)
 390                        break;
 391
 392                udelay(10);
 393        }
 394
 395        if (exynos_pen_release != -1)
 396                ret = -ETIMEDOUT;
 397
 398        /*
 399         * now the secondary core is starting up let it run its
 400         * calibrations, then wait for it to finish
 401         */
 402fail:
 403        spin_unlock(&boot_lock);
 404
 405        return exynos_pen_release != -1 ? ret : 0;
 406}
 407
 408static void __init exynos_smp_prepare_cpus(unsigned int max_cpus)
 409{
 410        exynos_sysram_init();
 411
 412        exynos_set_delayed_reset_assertion(true);
 413
 414        if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9)
 415                exynos_scu_enable();
 416}
 417
 418#ifdef CONFIG_HOTPLUG_CPU
 419/*
 420 * platform-specific code to shutdown a CPU
 421 *
 422 * Called with IRQs disabled
 423 */
 424static void exynos_cpu_die(unsigned int cpu)
 425{
 426        int spurious = 0;
 427        u32 mpidr = cpu_logical_map(cpu);
 428        u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
 429
 430        v7_exit_coherency_flush(louis);
 431
 432        platform_do_lowpower(cpu, &spurious);
 433
 434        /*
 435         * bring this CPU back into the world of cache
 436         * coherency, and then restore interrupts
 437         */
 438        cpu_leave_lowpower(core_id);
 439
 440        if (spurious)
 441                pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious);
 442}
 443#endif /* CONFIG_HOTPLUG_CPU */
 444
 445const struct smp_operations exynos_smp_ops __initconst = {
 446        .smp_prepare_cpus       = exynos_smp_prepare_cpus,
 447        .smp_secondary_init     = exynos_secondary_init,
 448        .smp_boot_secondary     = exynos_boot_secondary,
 449#ifdef CONFIG_HOTPLUG_CPU
 450        .cpu_die                = exynos_cpu_die,
 451#endif
 452};
 453