blob: 568917b86417c833047c556f078e2b22fa277ae9 [file] [log] [blame]
/*
* arch/arm/mach-comcerto/platsmp.c
*
* Copyright (C) 2011 Mindspeed Technologies, Inc.
*
* 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.
*
* 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/init.h>
#include <linux/errno.h>
#include <linux/smp.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <asm/cacheflush.h>
#include <mach/hardware.h>
#include <asm/hardware/gic.h>
#include <asm/mach-types.h>
#include <asm/smp_scu.h>
#include <asm/unified.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h> // for threads
#include <linux/sched.h> // for task_struct
#include <linux/time.h> // for using jiffies
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/pid.h>
extern void comcerto_secondary_startup(void);
static void __iomem *scu_base_addr(void)
{
return (void *)COMCERTO_SCU_VADDR;
}
/*
* Initialise the CPU possible map early - this describes the CPUs
* which may be present or become present in the system.
*/
void __init smp_init_cpus(void)
{
void __iomem *scu_base = scu_base_addr();
unsigned int i, ncores;
ncores = scu_base ? scu_get_core_count(scu_base) : 1;
if (ncores > NR_CPUS) {
printk(KERN_WARNING
"Comcerto: no. of cores (%d) greater than configured "
"maximum of %d - clipping\n",
ncores, NR_CPUS);
ncores = NR_CPUS;
}
for (i = 0; i < ncores; i++)
set_cpu_possible(i, true);
set_smp_cross_call(gic_raise_softirq);
}
#define JUMP_TO_KERNEL_START_1 0xe3a00020 /* mov r0, #32 */
#define JUMP_TO_KERNEL_START_2 0xe590f000 /* ldr pc, [r0] */
/* Creating Task for Cpu-1 Hotplug */
struct task_struct *thread1=NULL;
DECLARE_WAIT_QUEUE_HEAD(cpu1_hotplug);
/*
* Hotplug signal from CPU Hotplug framework
* to invoke the Hotplug task
*/
u32 cpu1_hotplug_done;
/* Cpu-1 Hotplug Task */
void hotplug_cpu1_die(void)
{
unsigned int reset;
struct cpumask in_mask;
int cpu = 0;
int pid = 0;
cpumask_set_cpu(cpu, &in_mask);
sched_setaffinity(pid, &in_mask);
wait_for_cpu1_hotplug_done:
/* Waiting for hotplug event invoked by CPU hotplug framework */
pr_info("cpu1 waiting for hotplug event.\n");
wait_event_interruptible(cpu1_hotplug, cpu1_hotplug_done>0);
pr_info("cpu1 hotplug done!\n");
if (cpu1_hotplug_done > 0) {
#ifdef CONFIG_NEON
__raw_writel((__raw_readl(A9DP_CPU_CLK_CNTRL) & ~NEON1_CLK_ENABLE), A9DP_CPU_CLK_CNTRL);
__raw_writel((__raw_readl(A9DP_CPU_RESET) | NEON1_RST), A9DP_CPU_RESET);
#endif
__raw_writel((__raw_readl(A9DP_CPU_CLK_CNTRL) & ~CPU1_CLK_ENABLE), A9DP_CPU_CLK_CNTRL);
__raw_writel((__raw_readl(A9DP_PWR_CNTRL) | CLAMP_CORE1), A9DP_PWR_CNTRL);
__raw_writel((__raw_readl(A9DP_PWR_CNTRL) | CORE_PWRDWN1), A9DP_PWR_CNTRL);
__raw_writel((__raw_readl(A9DP_CPU_RESET) | CPU1_RST), A9DP_CPU_RESET);
__raw_writel((__raw_readl(A9DP_PWR_CNTRL) & ~CORE_PWRDWN1), A9DP_PWR_CNTRL);
cpu1_hotplug_done = 0;
}
goto wait_for_cpu1_hotplug_done;
}
void __init platform_smp_prepare_cpus(unsigned int max_cpus)
{
int i;
char our_thread[25]="cpu1_hotplug_thread";
/*
* Initialise the present map, which describes the set of CPUs
* actually populated at the present time.
*/
for (i = 0; i < max_cpus; i++)
set_cpu_present(i, true);
scu_enable(scu_base_addr());
/* Create cpu1_hotplug_thread */
printk(" Creating cpu1_hotplug_thread....\n");
cpu1_hotplug_done = 0;
thread1 = kthread_create(hotplug_cpu1_die,NULL,our_thread);
if((thread1))
{
printk(" cpu1_hotplug_thread Created\n");
wake_up_process(thread1);
}
return 0;
}
/*
* control for which core is the next to come out of the secondary
* boot "holding pen"
*/
volatile int __cpuinitdata pen_release = -1;
/*
* Write pen_release in a way that is guaranteed to be visible to all
* observers, irrespective of whether they're taking part in coherency
* or not. This is necessary for the hotplug code to work reliably.
*/
static void __cpuinit write_pen_release(int val)
{
pen_release = val;
smp_wmb();
__cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release));
outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1));
}
static DEFINE_SPINLOCK(boot_lock);
void __cpuinit platform_secondary_init(unsigned int cpu)
{
/*
* if any interrupts are already enabled for the primary
* core (e.g. timer irq), then they will not have been enabled
* for us: do so
*/
gic_secondary_init(0);
/*
* let the primary processor know we're out of the
* pen, then head off into the C entry point
*/
write_pen_release(-1);
/*
* Synchronise with the boot thread.
*/
spin_lock(&boot_lock);
spin_unlock(&boot_lock);
}
int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle)
{
unsigned long timeout;
unsigned int *loop = (unsigned int *)phys_to_virt(0x08);
/*
* Install the comcerto_secondary_startup pointer at 0x20
* Physical Address
*/
__raw_writel(BSYM(virt_to_phys(comcerto_secondary_startup)), phys_to_virt(0x20));
__raw_writel((unsigned int)JUMP_TO_KERNEL_START_1 , phys_to_virt(0x00));
__raw_writel((unsigned int)JUMP_TO_KERNEL_START_2 , phys_to_virt(0x04));
smp_wmb();
__cpuc_flush_dcache_area((void *)phys_to_virt(0x00), 0x24);
outer_clean_range(__pa(phys_to_virt(0x00)), __pa(phys_to_virt(0x24)));
/* Get CPU 1 out of reset */
__raw_writel((__raw_readl(A9DP_CPU_RESET) & ~CPU1_RST), A9DP_CPU_RESET);
__raw_writel((__raw_readl(A9DP_PWR_CNTRL) & ~CLAMP_CORE1), A9DP_PWR_CNTRL);
__raw_writel((__raw_readl(A9DP_CPU_CLK_CNTRL) | CPU1_CLK_ENABLE), A9DP_CPU_CLK_CNTRL);
#ifdef CONFIG_NEON
/* Get NEON 1 out of reset */
__raw_writel((__raw_readl(A9DP_CPU_RESET) & ~NEON1_RST), A9DP_CPU_RESET);
__raw_writel((__raw_readl(A9DP_CPU_CLK_CNTRL) | NEON1_CLK_ENABLE), A9DP_CPU_CLK_CNTRL);
#endif
/*
* Set synchronisation state between this boot processor
* and the secondary one
*/
spin_lock(&boot_lock);
/*
* This is really belt and braces; we hold unintended secondary
* CPUs in the holding pen until we're ready for them. However,
* since we haven't sent them a soft interrupt, they shouldn't
* be there.
*/
write_pen_release(cpu);
/*
* Send the secondary CPU a soft interrupt, thereby causing
* the boot monitor to read the system wide flags register,
* and branch to the address found there.
*/
timeout = jiffies + (1 * HZ);
while (time_before(jiffies, timeout)) {
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 ? -ENOSYS : 0;
}