blob: 1a208dbf682f95ee8359c1cbf3d1e70aae314ff6 [file] [log] [blame]
/*
* linux/arch/arm/mach-tegra/platsmp.c
*
* Copyright (C) 2002 ARM Ltd.
* All Rights Reserved
*
* Copyright (C) 2009 Palm
* 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 version 2 as
* published by the Free Software Foundation.
*/
#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 <asm/cacheflush.h>
#include <asm/hardware/gic.h>
#include <asm/mach-types.h>
#include <asm/smp_scu.h>
#include <mach/clk.h>
#include <mach/iomap.h>
#include <mach/powergate.h>
#include "fuse.h"
#include "flowctrl.h"
#include "reset.h"
extern void tegra_secondary_startup(void);
static void __iomem *scu_base = IO_ADDRESS(TEGRA_ARM_PERIF_BASE);
#define EVP_CPU_RESET_VECTOR \
(IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE) + 0x100)
#define CLK_RST_CONTROLLER_CLK_CPU_CMPLX \
(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x4c)
#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET \
(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x340)
#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR \
(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x344)
#define CLK_RST_CONTROLLER_CLK_CPU_CMPLX_CLR \
(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x34c)
#define CPU_CLOCK(cpu) (0x1<<(8+cpu))
#define CPU_RESET(cpu) (0x1111ul<<(cpu))
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);
}
static int tegra20_power_up_cpu(unsigned int cpu)
{
u32 reg;
/* Enable the CPU clock. */
reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
writel(reg & ~CPU_CLOCK(cpu), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
barrier();
reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
/* Clear flow controller CSR. */
flowctrl_write_cpu_csr(cpu, 0);
return 0;
}
static int tegra30_power_up_cpu(unsigned int cpu)
{
u32 reg;
int ret, pwrgateid;
unsigned long timeout;
pwrgateid = tegra_cpu_powergate_id(cpu);
if (pwrgateid < 0)
return pwrgateid;
/* If this is the first boot, toggle powergates directly. */
if (!tegra_powergate_is_powered(pwrgateid)) {
ret = tegra_powergate_power_on(pwrgateid);
if (ret)
return ret;
/* Wait for the power to come up. */
timeout = jiffies + 10*HZ;
while (tegra_powergate_is_powered(pwrgateid)) {
if (time_after(jiffies, timeout))
return -ETIMEDOUT;
udelay(10);
}
}
/* CPU partition is powered. Enable the CPU clock. */
writel(CPU_CLOCK(cpu), CLK_RST_CONTROLLER_CLK_CPU_CMPLX_CLR);
reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX_CLR);
udelay(10);
/* Remove I/O clamps. */
ret = tegra_powergate_remove_clamping(pwrgateid);
udelay(10);
/* Clear flow controller CSR. */
flowctrl_write_cpu_csr(cpu, 0);
return 0;
}
int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle)
{
int status;
/*
* Force the CPU into reset. The CPU must remain in reset when the
* flow controller state is cleared (which will cause the flow
* controller to stop driving reset if the CPU has been power-gated
* via the flow controller). This will have no effect on first boot
* of the CPU since it should already be in reset.
*/
writel(CPU_RESET(cpu), CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET);
dmb();
/*
* Unhalt the CPU. If the flow controller was used to power-gate the
* CPU this will cause the flow controller to stop driving reset.
* The CPU will remain in reset because the clock and reset block
* is now driving reset.
*/
flowctrl_write_cpu_halt(cpu, 0);
switch (tegra_chip_id) {
case TEGRA20:
status = tegra20_power_up_cpu(cpu);
break;
case TEGRA30:
status = tegra30_power_up_cpu(cpu);
break;
default:
status = -EINVAL;
break;
}
if (status)
goto done;
/* Take the CPU out of reset. */
writel(CPU_RESET(cpu), CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
wmb();
done:
return status;
}
/*
* 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)
{
unsigned int i, ncores = scu_get_core_count(scu_base);
if (ncores > nr_cpu_ids) {
pr_warn("SMP: %u cores greater than maximum (%u), clipping\n",
ncores, nr_cpu_ids);
ncores = nr_cpu_ids;
}
for (i = 0; i < ncores; i++)
set_cpu_possible(i, true);
set_smp_cross_call(gic_raise_softirq);
}
void __init platform_smp_prepare_cpus(unsigned int max_cpus)
{
tegra_cpu_reset_handler_init();
scu_enable(scu_base);
}