| /* |
| * 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); |
| } |