blob: 554b295eba37cf8a37dc6d34c89fe5addd85a449 [file] [log] [blame]
/*
* arch/arm/mach-meson/platsmp.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/smp.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <asm/smp_scu.h>
#include <asm/smp_plat.h>
#include <asm/smp_scu.h>
#include <asm/cacheflush.h>
#include <asm/mach-types.h>
#include <linux/percpu.h>
#include "platsmp.h"
#include <asm/cache.h>
#include <asm/cacheflush.h>
#include <asm/cp15.h>
#include <linux/amlogic/meson-secure.h>
static DEFINE_SPINLOCK(boot_lock);
static DEFINE_SPINLOCK(clockfw_lock);
static void __iomem *periph_membase;
static void __iomem *sram_membase;
static void __iomem *reg_map[3];
static unsigned int io_cbus_base;
static unsigned int io_aobus_base;
void meson_set_cpu_ctrl_addr(uint32_t cpu, const uint32_t addr)
{
spin_lock(&clockfw_lock);
if (meson_secure_enabled())
meson_auxcoreboot_addr(cpu, addr);
else
writel(addr, (void *)(CPU1_CONTROL_ADDR_REG + ((cpu-1) << 2)));
spin_unlock(&clockfw_lock);
}
void meson_set_cpu_ctrl_reg(int cpu, int is_on)
{
uint32_t value = 0;
spin_lock(&clockfw_lock);
if (meson_secure_enabled()) {
value = meson_read_corectrl();
value = (value & ~(1U << cpu)) | (is_on << cpu);
value |= 1;
meson_modify_corectrl(value);
} else {
aml_set_reg32_bits(CPU_CONTROL_REG, is_on, cpu, 1);
aml_set_reg32_bits(CPU_CONTROL_REG, 1, 0, 1);
}
spin_unlock(&clockfw_lock);
}
int meson_get_cpu_ctrl_addr(int cpu)
{
if (meson_secure_enabled())
return 0;
else
return readl((void *)(CPU1_CONTROL_ADDR_REG + ((cpu-1) << 2)));
}
void meson_set_cpu_power_ctrl(uint32_t cpu, int is_power_on)
{
WARN_ON(cpu == 0);
if (is_power_on) {
/* SCU Power on CPU & CPU PWR_A9_CNTL0 CTRL_MODE bit.
* CTRL_MODE bit may write forward to SCU when cpu reset.
* So, we need clean it here to avoid the forward write happen.
*/
aml_set_reg32_bits(CPU_POWER_CTRL_REG,
0x0, (cpu << 3), 2);
aml_set_reg32_bits(P_AO_RTI_PWR_A9_CNTL0, 0x0, 2*cpu + 16, 2);
udelay(5);
#ifndef CONFIG_MESON_CPU_EMULATOR
/* Reset enable*/
aml_set_reg32_bits(P_HHI_SYS_CPU_CLK_CNTL, 1, (cpu + 24), 1);
/* Power on*/
aml_set_reg32_bits(P_AO_RTI_PWR_A9_MEM_PD0,
0, (32 - cpu * 4), 4);
aml_set_reg32_bits(P_AO_RTI_PWR_A9_CNTL1,
0x0, ((cpu + 1) << 1), 2);
udelay(10);
while (!(readl((void *)(P_AO_RTI_PWR_A9_CNTL1)) &
(1<<(cpu+16)))) {
pr_err("wait power...0x%08x 0x%08x\n",
readl((void *)(P_AO_RTI_PWR_A9_CNTL0)),
readl((void *)(P_AO_RTI_PWR_A9_CNTL1)));
udelay(10);
};
/* Isolation disable */
aml_set_reg32_bits(P_AO_RTI_PWR_A9_CNTL0, 0x0, cpu, 1);
/* Reset disable */
aml_set_reg32_bits(P_HHI_SYS_CPU_CLK_CNTL, 0, (cpu + 24), 1);
aml_set_reg32_bits(CPU_POWER_CTRL_REG,
0x0, (cpu << 3), 2);
#endif
} else{
aml_set_reg32_bits(CPU_POWER_CTRL_REG,
0x3, (cpu << 3), 2);
aml_set_reg32_bits(P_AO_RTI_PWR_A9_CNTL0, 0x3, 2*cpu + 16, 2);
#ifndef CONFIG_MESON_CPU_EMULATOR
/* Isolation enable */
aml_set_reg32_bits(P_AO_RTI_PWR_A9_CNTL0, 0x1, cpu, 1);
udelay(10);
/* Power off */
aml_set_reg32_bits(P_AO_RTI_PWR_A9_CNTL1,
0x3, ((cpu + 1) << 1), 2);
aml_set_reg32_bits(P_AO_RTI_PWR_A9_MEM_PD0,
0xf, (32 - cpu * 4), 4);
#endif
}
dsb();
dmb();
pr_debug("----CPU %d\n", cpu);
pr_debug("----CPU_POWER_CTRL_REG(%08x) = %08x\n",
CPU_POWER_CTRL_REG,
readl((void *)(CPU_POWER_CTRL_REG)));
pr_debug("----P_AO_RTI_PWR_A9_CNTL0(%08x) = %08x\n",
P_AO_RTI_PWR_A9_CNTL0, readl((void *)(P_AO_RTI_PWR_A9_CNTL0)));
pr_debug("----P_AO_RTI_PWR_A9_CNTL1(%08x) = %08x\n",
P_AO_RTI_PWR_A9_CNTL1, readl((void *)(P_AO_RTI_PWR_A9_CNTL1)));
}
static void write_pen_release(int val)
{
pen_release = val;
/*memory barrier*/
smp_wmb();
__cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release));
outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1));
}
static void meson_secondary_set(unsigned int cpu)
{
meson_set_cpu_ctrl_addr(cpu,
(const uint32_t)virt_to_phys(meson_secondary_startup));
meson_set_cpu_ctrl_reg(cpu, 1);
/*memory barrier*/
smp_wmb();
/*memory barrier*/
mb();
}
static void meson_secondary_init(unsigned int cpu)
{
/*
* let the primary processor know we're out of the
* pen, then head off into the C entry point
*/
write_pen_release(-1);
/*memory barrier*/
smp_wmb();
}
static int meson_boot_secondary(unsigned int cpu, struct task_struct *idle)
{
unsigned long timeout;
/*
* Set synchronisation state between this boot processor
* and the secondary one
*/
spin_lock(&boot_lock);
/*
* The secondary processor is waiting to be released from
* the holding pen - release it, then wait for it to flag
* that it has been released by resetting pen_release.
*/
write_pen_release(cpu_logical_map(cpu));
if (!meson_secure_enabled()) {
meson_set_cpu_ctrl_addr(cpu,
(const uint32_t)virt_to_phys(meson_secondary_startup));
meson_set_cpu_power_ctrl(cpu, 1);
timeout = jiffies + (10 * HZ);
while (meson_get_cpu_ctrl_addr(cpu))
;
if (!time_before(jiffies, timeout))
return -EPERM;
}
meson_secondary_set(cpu);
dsb_sev();
/* smp_send_reschedule(cpu); */
timeout = jiffies + (10 * HZ);
while (time_before(jiffies, timeout)) {
/*memory barrier*/
smp_rmb();
if (pen_release == -1)
break;
udelay(10);
}
/*
* now the secondary core is starting up let it run its
* calibrations, then wait for it to finish
*/
spin_unlock(&boot_lock);
return pen_release != -1 ? -ETIME : 0;
}
static void __init meson_smp_prepare_cpus(unsigned int max_cpus)
{
int i = 0;
struct device_node *node;
struct resource res;
struct device_node *child;
node = of_find_compatible_node(NULL, NULL, "amlogic,meson8b-cpuconfig");
if (!node) {
pr_err("Missing A5 CPU config node in the device tree\n");
return;
}
periph_membase = of_iomap(node, 0);
if (!periph_membase)
pr_err("Couldn't map A5 CPU config registers\n");
sram_membase = of_iomap(node, 1);
if (!periph_membase)
pr_err("Couldn't map A5 sram registers\n");
node = of_find_compatible_node(NULL, NULL, "amlogic, iomap");
if (!node) {
pr_err("Missing A5 iomap node in the device tree\n");
return;
}
for_each_child_of_node(node, child) {
if (of_address_to_resource(child, 0, &res)) {
pr_err("Missing iomap child node reg\n");
return;
}
reg_map[i] = ioremap(res.start, resource_size(&res));
i++;
}
io_cbus_base = (unsigned int)reg_map[0];
io_aobus_base = (unsigned int)reg_map[2];
/*
* Initialise the SCU and wake up the secondary core using
* wakeup_secondary().
*/
scu_enable((void __iomem *) periph_membase);
}
#ifdef CONFIG_HOTPLUG_CPU
int meson_cpu_kill(unsigned int cpu)
{
unsigned int value;
unsigned int offset = (cpu<<3);
unsigned int cnt = 0;
do {
udelay(10);
value = readl((void *)(CPU_POWER_CTRL_REG));
cnt++;
WARN_ON(cnt > 5000);
} while ((value&(3<<offset)) != (3<<offset));
udelay(10);
meson_set_cpu_power_ctrl(cpu, 0);
return 1;
}
void meson_cpu_die(unsigned int cpu)
{
meson_set_cpu_ctrl_reg(cpu, 0);
meson_cleanup();
v7_exit_coherency_flush(louis);
aml_set_reg32_bits(CPU_POWER_CTRL_REG, 0x3, (cpu << 3), 2);
asm volatile(
"dsb\n"
"wfi\n"
);
WARN_ON(1);
}
int meson_cpu_disable(unsigned int cpu)
{
/*
* we don't allow CPU 0 to be shutdown (it is still too special
* e.g. clock tick interrupts)
*/
return cpu == 0 ? -EPERM : 0;
}
#endif
const struct smp_operations meson_smp_ops __initconst = {
.smp_prepare_cpus = meson_smp_prepare_cpus,
.smp_secondary_init = meson_secondary_init,
.smp_boot_secondary = meson_boot_secondary,
#ifdef CONFIG_HOTPLUG_CPU
.cpu_die = meson_cpu_die,
.cpu_kill = meson_cpu_kill,
.cpu_disable = meson_cpu_disable,
#endif
};
CPU_METHOD_OF_DECLARE(meson8b_smp, "amlogic,meson8b-smp", &meson_smp_ops);