blob: 95826c6611b12592c39fbeae3820029049c1920d [file] [log] [blame]
/*
* Copyright (C) 2009 Broadcom Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <linux/version.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/smp.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/reboot.h>
#include <linux/io.h>
#include <asm/time.h>
#include <asm/pgtable.h>
#include <asm/processor.h>
#include <asm/system.h>
#include <asm/bootinfo.h>
#include <asm/pmon.h>
#include <asm/cacheflush.h>
#include <asm/tlbflush.h>
#include <asm/mipsregs.h>
#include <asm/brcmstb/brcmstb.h>
/* initial $sp, $gp - used by arch/mips/brcmstb/vector.S */
unsigned long brcmstb_smp_boot_sp;
unsigned long brcmstb_smp_boot_gp;
static void brcmstb_send_ipi_single(int cpu, unsigned int action);
static void brcmstb_ack_ipi(unsigned int irq);
/* Early cpumask setup - runs on TP0 */
static void brcmstb_smp_setup(void)
{
__cpu_number_map[0] = 0;
__cpu_logical_map[0] = 0;
set_cpu_possible(0, 1);
set_cpu_present(0, 1);
if (brcm_smp_enabled) {
__cpu_number_map[1] = 1;
__cpu_logical_map[1] = 1;
set_cpu_possible(1, 1);
set_cpu_present(1, 1);
}
#if defined(CONFIG_BMIPS4380)
/* NBK and weak order flags */
set_c0_brcm_config_0(0x30000);
/*
* MIPS interrupts 0,1 (SW INT 0,1) cross over to the other TP
* MIPS interrupt 2 (HW INT 0) is the TP0 L1 controller output
* MIPS interrupt 3 (HW INT 1) is the TP1 L1 controller output
*/
change_c0_brcm_cmt_intr(0xf8018000, (0x02 << 27) | (0x03 << 15));
#elif defined(CONFIG_BMIPS5000)
/* enable raceless SW interrupts */
set_c0_brcm_config(0x03 << 22);
/* clear any pending SW interrupts */
write_c0_brcm_action(0x2000 | (0 << 9) | (0 << 8));
write_c0_brcm_action(0x2000 | (0 << 9) | (1 << 8));
write_c0_brcm_action(0x2000 | (1 << 9) | (0 << 8));
write_c0_brcm_action(0x2000 | (1 << 9) | (1 << 8));
/* send HW interrupt 0 to TP0, HW interrupt 1 to TP1 */
change_c0_brcm_mode(0x1f << 27, 0x02 << 27);
#endif
}
static irqreturn_t brcmstb_ipi_interrupt(int irq, void *dev_id);
/* IRQ setup - runs on TP0 */
static void brcmstb_prepare_cpus(unsigned int max_cpus)
{
if (request_irq(BRCM_IRQ_IPI0, brcmstb_ipi_interrupt, IRQF_DISABLED,
"smp_ipi_tp0", NULL))
panic("Can't request TP0 IPI interrupt\n");
if (request_irq(BRCM_IRQ_IPI1, brcmstb_ipi_interrupt, IRQF_DISABLED,
"smp_ipi_tp1", NULL))
panic("Can't request TP1 IPI interrupt\n");
}
/* Tell the hardware to boot TP1 - runs on TP0 */
static void brcmstb_boot_secondary(int cpu, struct task_struct *idle)
{
brcmstb_smp_boot_sp = __KSTK_TOS(idle);
brcmstb_smp_boot_gp = (unsigned long)task_thread_info(idle);
mb();
/*
* TP1 initial boot sequence:
* brcm_reset_nmi_vec @ a000_0000 ->
* brcmstb_tp1_entry ->
* brcm_upper_tlb_setup (cached function call) ->
* start_secondary (cached jump)
*
* TP1 warm restart sequence:
* play_dead WAIT loop ->
* brcm_tp1_int_vec @ BRCM_WARM_RESTART_VEC ->
* eret to play_dead ->
* brcmstb_tp1_reentry ->
* start_secondary
*
* Vector relocation code is in arch/mips/brcmstb/prom.c
* Actual boot vectors are in arch/mips/brcmstb/vector.S
*/
printk(KERN_INFO "SMP: Booting CPU%d...\n", cpu);
/* warm restart */
brcmstb_send_ipi_single(1, 0);
#if defined(CONFIG_BMIPS4380)
set_c0_brcm_cmt_ctrl(0x01);
#elif defined(CONFIG_BMIPS5000)
write_c0_brcm_action(0x9);
#endif
}
/* Early setup - runs on TP1 after cache probe */
static void brcmstb_init_secondary(void)
{
#if defined(CONFIG_BMIPS4380)
unsigned long cbr = BMIPS_GET_CBR();
unsigned long old_vec = DEV_RD(cbr + BMIPS_RELO_VECTOR_CONTROL_1);
/* make sure the NMI vector is in kseg0 now that we've booted */
DEV_WR_RB(cbr + BMIPS_RELO_VECTOR_CONTROL_1, old_vec & ~0x20000000);
#elif defined(CONFIG_BMIPS5000)
write_c0_brcm_bootvec(read_c0_brcm_bootvec() & ~0x20000000);
#endif
brcmstb_ack_ipi(0);
write_c0_compare(read_c0_count() + mips_hpt_frequency / HZ);
set_c0_status(IE_SW0 | IE_SW1 | IE_IRQ1 | IE_IRQ5 | ST0_IE);
irq_enable_hazard();
}
/* Late setup - runs on TP1 before entering the idle loop */
static void brcmstb_smp_finish(void)
{
printk(KERN_INFO "SMP: CPU%d is running\n", smp_processor_id());
}
/* Runs on TP0 after all CPUs have been booted */
static void brcmstb_cpus_done(void)
{
}
#if defined(CONFIG_BMIPS4380)
static DEFINE_SPINLOCK(ipi_lock);
static void brcmstb_send_ipi_single(int cpu, unsigned int action)
{
unsigned long flags;
spin_lock_irqsave(&ipi_lock, flags);
set_c0_cause(smp_processor_id() ? C_SW0 : C_SW1);
irq_enable_hazard();
spin_unlock_irqrestore(&ipi_lock, flags);
}
static void brcmstb_ack_ipi(unsigned int irq)
{
unsigned long flags;
spin_lock_irqsave(&ipi_lock, flags);
clear_c0_cause(smp_processor_id() ? C_SW1 : C_SW0);
irq_enable_hazard();
spin_unlock_irqrestore(&ipi_lock, flags);
}
#elif defined(CONFIG_BMIPS5000)
static void brcmstb_send_ipi_single(int cpu, unsigned int action)
{
unsigned int bit = cpu;
write_c0_brcm_action(0x3000 | (bit << 8) | (cpu << 9));
irq_enable_hazard();
}
static void brcmstb_ack_ipi(unsigned int irq)
{
unsigned int bit = smp_processor_id();
write_c0_brcm_action(0x2000 | (bit << 8) | (smp_processor_id() << 9));
irq_enable_hazard();
}
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32)
static void brcmstb_send_ipi_mask(const struct cpumask *mask,
unsigned int action)
{
unsigned int i;
for_each_cpu(i, mask)
brcmstb_send_ipi_single(i, action);
}
#else
static void brcmstb_send_ipi_mask(cpumask_t mask, unsigned int action)
{
unsigned int i;
for_each_cpu_mask(i, mask)
brcmstb_send_ipi_single(i, action);
}
#endif
static irqreturn_t brcmstb_ipi_interrupt(int irq, void *dev_id)
{
brcmstb_ack_ipi(irq);
smp_call_function_interrupt();
return IRQ_HANDLED;
}
#ifdef CONFIG_HOTPLUG_CPU
static int brcmstb_cpu_disable(void)
{
unsigned int cpu = smp_processor_id();
if (cpu == 0)
return -EBUSY;
printk(KERN_INFO "SMP: CPU%d is offline\n", cpu);
cpu_clear(cpu, cpu_online_map);
cpu_clear(cpu, cpu_callin_map);
local_flush_tlb_all();
local_flush_icache_range(0, ~0);
return 0;
}
static void brcmstb_cpu_die(unsigned int cpu)
{
}
void __ref play_dead(void)
{
idle_task_exit();
/* flush data cache */
_dma_cache_wback_inv(0, ~0);
/*
* Wakeup is on SW0 or SW1; disable everything else
* Use BEV !IV (BRCM_WARM_RESTART_VEC) to avoid the regular Linux
* IRQ handlers; this clears ST0_IE and returns immediately.
*/
clear_c0_cause(CAUSEF_IV | C_SW0 | C_SW1);
change_c0_status(IE_IRQ5 | IE_IRQ1 | IE_SW0 | IE_SW1 | ST0_IE | ST0_BEV,
IE_SW0 | IE_SW1 | ST0_IE | ST0_BEV);
irq_disable_hazard();
/*
* wait for SW interrupt from brcmstb_boot_secondary(), then jump
* back to start_secondary()
*/
__asm__ __volatile__(
" wait\n"
" j brcmstb_tp1_reentry\n"
: : : "memory");
}
#endif /* CONFIG_HOTPLUG_CPU */
struct plat_smp_ops brcmstb_smp_ops = {
.smp_setup = brcmstb_smp_setup,
.prepare_cpus = brcmstb_prepare_cpus,
.boot_secondary = brcmstb_boot_secondary,
.smp_finish = brcmstb_smp_finish,
.init_secondary = brcmstb_init_secondary,
.cpus_done = brcmstb_cpus_done,
.send_ipi_single = brcmstb_send_ipi_single,
.send_ipi_mask = brcmstb_send_ipi_mask,
#ifdef CONFIG_HOTPLUG_CPU
.cpu_disable = brcmstb_cpu_disable,
.cpu_die = brcmstb_cpu_die,
#endif
};