e7dc951eec
This wires up CPU hotplug for SH-X3 SMP CPUs. Presently only secondary cores can be hotplugged given that the boot CPU has to contend with the broadcast timer. When real local timers are implemented this restriction can be lifted. Signed-off-by: Paul Mundt <lethal@linux-sh.org>
167 lines
4.0 KiB
C
167 lines
4.0 KiB
C
/*
|
|
* SH-X3 SMP
|
|
*
|
|
* Copyright (C) 2007 - 2010 Paul Mundt
|
|
* Copyright (C) 2007 Magnus Damm
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*/
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/cpu.h>
|
|
#include <asm/sections.h>
|
|
|
|
#define STBCR_REG(phys_id) (0xfe400004 | (phys_id << 12))
|
|
#define RESET_REG(phys_id) (0xfe400008 | (phys_id << 12))
|
|
|
|
#define STBCR_MSTP 0x00000001
|
|
#define STBCR_RESET 0x00000002
|
|
#define STBCR_SLEEP 0x00000004
|
|
#define STBCR_LTSLP 0x80000000
|
|
|
|
static irqreturn_t ipi_interrupt_handler(int irq, void *arg)
|
|
{
|
|
unsigned int message = (unsigned int)(long)arg;
|
|
unsigned int cpu = hard_smp_processor_id();
|
|
unsigned int offs = 4 * cpu;
|
|
unsigned int x;
|
|
|
|
x = __raw_readl(0xfe410070 + offs); /* C0INITICI..CnINTICI */
|
|
x &= (1 << (message << 2));
|
|
__raw_writel(x, 0xfe410080 + offs); /* C0INTICICLR..CnINTICICLR */
|
|
|
|
smp_message_recv(message);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void shx3_smp_setup(void)
|
|
{
|
|
unsigned int cpu = 0;
|
|
int i, num;
|
|
|
|
init_cpu_possible(cpumask_of(cpu));
|
|
|
|
/* Enable light sleep for the boot CPU */
|
|
__raw_writel(__raw_readl(STBCR_REG(cpu)) | STBCR_LTSLP, STBCR_REG(cpu));
|
|
|
|
__cpu_number_map[0] = 0;
|
|
__cpu_logical_map[0] = 0;
|
|
|
|
/*
|
|
* Do this stupidly for now.. we don't have an easy way to probe
|
|
* for the total number of cores.
|
|
*/
|
|
for (i = 1, num = 0; i < NR_CPUS; i++) {
|
|
set_cpu_possible(i, true);
|
|
__cpu_number_map[i] = ++num;
|
|
__cpu_logical_map[num] = i;
|
|
}
|
|
|
|
printk(KERN_INFO "Detected %i available secondary CPU(s)\n", num);
|
|
}
|
|
|
|
static void shx3_prepare_cpus(unsigned int max_cpus)
|
|
{
|
|
int i;
|
|
|
|
local_timer_setup(0);
|
|
|
|
BUILD_BUG_ON(SMP_MSG_NR >= 8);
|
|
|
|
for (i = 0; i < SMP_MSG_NR; i++)
|
|
request_irq(104 + i, ipi_interrupt_handler,
|
|
IRQF_DISABLED | IRQF_PERCPU, "IPI", (void *)(long)i);
|
|
|
|
for (i = 0; i < max_cpus; i++)
|
|
set_cpu_present(i, true);
|
|
}
|
|
|
|
static void shx3_start_cpu(unsigned int cpu, unsigned long entry_point)
|
|
{
|
|
if (__in_29bit_mode())
|
|
__raw_writel(entry_point, RESET_REG(cpu));
|
|
else
|
|
__raw_writel(virt_to_phys(entry_point), RESET_REG(cpu));
|
|
|
|
if (!(__raw_readl(STBCR_REG(cpu)) & STBCR_MSTP))
|
|
__raw_writel(STBCR_MSTP, STBCR_REG(cpu));
|
|
|
|
while (!(__raw_readl(STBCR_REG(cpu)) & STBCR_MSTP))
|
|
cpu_relax();
|
|
|
|
/* Start up secondary processor by sending a reset */
|
|
__raw_writel(STBCR_RESET | STBCR_LTSLP, STBCR_REG(cpu));
|
|
}
|
|
|
|
static unsigned int shx3_smp_processor_id(void)
|
|
{
|
|
return __raw_readl(0xff000048); /* CPIDR */
|
|
}
|
|
|
|
static void shx3_send_ipi(unsigned int cpu, unsigned int message)
|
|
{
|
|
unsigned long addr = 0xfe410070 + (cpu * 4);
|
|
|
|
BUG_ON(cpu >= 4);
|
|
|
|
__raw_writel(1 << (message << 2), addr); /* C0INTICI..CnINTICI */
|
|
}
|
|
|
|
static void shx3_update_boot_vector(unsigned int cpu)
|
|
{
|
|
__raw_writel(STBCR_MSTP, STBCR_REG(cpu));
|
|
while (!(__raw_readl(STBCR_REG(cpu)) & STBCR_MSTP))
|
|
cpu_relax();
|
|
__raw_writel(STBCR_RESET, STBCR_REG(cpu));
|
|
}
|
|
|
|
static int __cpuinit
|
|
shx3_cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu)
|
|
{
|
|
unsigned int cpu = (unsigned int)hcpu;
|
|
|
|
switch (action) {
|
|
case CPU_UP_PREPARE:
|
|
shx3_update_boot_vector(cpu);
|
|
break;
|
|
case CPU_ONLINE:
|
|
pr_info("CPU %u is now online\n", cpu);
|
|
break;
|
|
case CPU_DEAD:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block __cpuinitdata shx3_cpu_notifier = {
|
|
.notifier_call = shx3_cpu_callback,
|
|
};
|
|
|
|
static int __cpuinit register_shx3_cpu_notifier(void)
|
|
{
|
|
register_hotcpu_notifier(&shx3_cpu_notifier);
|
|
return 0;
|
|
}
|
|
late_initcall(register_shx3_cpu_notifier);
|
|
|
|
struct plat_smp_ops shx3_smp_ops = {
|
|
.smp_setup = shx3_smp_setup,
|
|
.prepare_cpus = shx3_prepare_cpus,
|
|
.start_cpu = shx3_start_cpu,
|
|
.smp_processor_id = shx3_smp_processor_id,
|
|
.send_ipi = shx3_send_ipi,
|
|
.cpu_die = native_cpu_die,
|
|
.cpu_disable = native_cpu_disable,
|
|
.play_dead = native_play_dead,
|
|
};
|