2014-07-08 02:54:13 +04:00
/*
2011-02-14 10:33:10 +03:00
* Copyright ( c ) 2010 - 2011 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com
2010-07-26 16:08:52 +04:00
*
* Cloned from linux / arch / arm / mach - vexpress / platsmp . c
*
* Copyright ( C ) 2002 ARM Ltd .
* 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>
2014-05-13 02:13:44 +04:00
# include <linux/of_address.h>
2010-07-26 16:08:52 +04:00
# include <asm/cacheflush.h>
2012-01-20 15:01:12 +04:00
# include <asm/smp_plat.h>
2010-07-26 16:08:52 +04:00
# include <asm/smp_scu.h>
2012-12-11 08:58:43 +04:00
# include <asm/firmware.h>
2010-07-26 16:08:52 +04:00
2014-07-18 22:43:22 +04:00
# include <mach/map.h>
2011-09-08 16:15:22 +04:00
# include "common.h"
2013-12-18 23:06:56 +04:00
# include "regs-pmu.h"
2011-09-08 16:15:22 +04:00
2011-02-14 10:33:10 +03:00
extern void exynos4_secondary_startup ( void ) ;
2010-07-26 16:08:52 +04:00
2014-07-18 23:45:02 +04:00
/**
* exynos_core_power_down : power down the specified cpu
* @ cpu : the cpu to power down
*
* Power down the specified cpu . The sequence must be finished by a
* call to cpu_do_idle ( )
*
*/
void exynos_cpu_power_down ( int cpu )
{
2014-07-26 19:54:21 +04:00
pmu_raw_writel ( 0 , EXYNOS_ARM_CORE_CONFIGURATION ( cpu ) ) ;
2014-07-18 23:45:02 +04:00
}
/**
* exynos_cpu_power_up : power up the specified cpu
* @ cpu : the cpu to power up
*
* Power up the specified cpu
*/
void exynos_cpu_power_up ( int cpu )
{
2014-07-26 19:54:21 +04:00
pmu_raw_writel ( S5P_CORE_LOCAL_PWR_EN ,
EXYNOS_ARM_CORE_CONFIGURATION ( cpu ) ) ;
2014-07-18 23:45:02 +04:00
}
/**
* exynos_cpu_power_state : returns the power state of the cpu
* @ cpu : the cpu to retrieve the power state from
*
*/
int exynos_cpu_power_state ( int cpu )
{
2014-07-26 19:54:21 +04:00
return ( pmu_raw_readl ( EXYNOS_ARM_CORE_STATUS ( cpu ) ) &
2014-07-18 23:45:02 +04:00
S5P_CORE_LOCAL_PWR_EN ) ;
}
/**
* exynos_cluster_power_down : power down the specified cluster
* @ cluster : the cluster to power down
*/
void exynos_cluster_power_down ( int cluster )
{
2014-07-26 19:54:21 +04:00
pmu_raw_writel ( 0 , EXYNOS_COMMON_CONFIGURATION ( cluster ) ) ;
2014-07-18 23:45:02 +04:00
}
/**
* exynos_cluster_power_up : power up the specified cluster
* @ cluster : the cluster to power up
*/
void exynos_cluster_power_up ( int cluster )
{
2014-07-26 19:54:21 +04:00
pmu_raw_writel ( S5P_CORE_LOCAL_PWR_EN ,
EXYNOS_COMMON_CONFIGURATION ( cluster ) ) ;
2014-07-18 23:45:02 +04:00
}
/**
* exynos_cluster_power_state : returns the power state of the cluster
* @ cluster : the cluster to retrieve the power state from
*
*/
int exynos_cluster_power_state ( int cluster )
{
2014-07-26 19:54:21 +04:00
return ( pmu_raw_readl ( EXYNOS_COMMON_STATUS ( cluster ) ) &
S5P_CORE_LOCAL_PWR_EN ) ;
2014-07-18 23:45:02 +04:00
}
2012-11-24 06:13:48 +04:00
static inline void __iomem * cpu_boot_reg_base ( void )
{
if ( soc_is_exynos4210 ( ) & & samsung_rev ( ) = = EXYNOS4210_REV_1_1 )
2014-07-18 22:43:22 +04:00
return pmu_base_addr + S5P_INFORM5 ;
2014-05-13 02:13:44 +04:00
return sysram_base_addr ;
2012-11-24 06:13:48 +04:00
}
static inline void __iomem * cpu_boot_reg ( int cpu )
{
void __iomem * boot_reg ;
boot_reg = cpu_boot_reg_base ( ) ;
2014-05-13 02:13:44 +04:00
if ( ! boot_reg )
return ERR_PTR ( - ENODEV ) ;
2012-11-24 06:13:48 +04:00
if ( soc_is_exynos4412 ( ) )
boot_reg + = 4 * cpu ;
2014-05-25 23:16:11 +04:00
else if ( soc_is_exynos5420 ( ) | | soc_is_exynos5800 ( ) )
2013-06-18 19:29:35 +04:00
boot_reg + = 4 ;
2012-11-24 06:13:48 +04:00
return boot_reg ;
}
2011-07-16 08:39:09 +04:00
ARM: Fix subtle race in CPU pen_release hotplug code
There is a subtle race in the CPU hotplug code, where a CPU which has
been offlined can online itself before being requested, which results
in things going astray on the next online/offline cycle.
What happens in the normal online/offline/online cycle is:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads -1
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
However, as the write of -1 of pen_release is not fully flushed back to
memory, and the checking of pen_release is done with caches disabled,
this allows CPU3 the opportunity to read the old value of pen_release:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads 3
starts boot
pen_release = -1
requests boot of CPU3
pen_release = 3
flush cache line
Fix this by grouping the write of pen_release along with its cache line
flushing code to ensure that any update to pen_release is always pushed
out to physical memory.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2010-12-18 13:53:12 +03:00
/*
* 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 write_pen_release ( int val )
{
pen_release = val ;
smp_wmb ( ) ;
2013-12-05 23:26:16 +04:00
sync_cache_w ( & pen_release ) ;
ARM: Fix subtle race in CPU pen_release hotplug code
There is a subtle race in the CPU hotplug code, where a CPU which has
been offlined can online itself before being requested, which results
in things going astray on the next online/offline cycle.
What happens in the normal online/offline/online cycle is:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads -1
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
However, as the write of -1 of pen_release is not fully flushed back to
memory, and the checking of pen_release is done with caches disabled,
this allows CPU3 the opportunity to read the old value of pen_release:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads 3
starts boot
pen_release = -1
requests boot of CPU3
pen_release = 3
flush cache line
Fix this by grouping the write of pen_release along with its cache line
flushing code to ensure that any update to pen_release is always pushed
out to physical memory.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2010-12-18 13:53:12 +03:00
}
2010-07-26 16:08:52 +04:00
static void __iomem * scu_base_addr ( void )
{
return ( void __iomem * ) ( S5P_VA_SCU ) ;
}
static DEFINE_SPINLOCK ( boot_lock ) ;
2013-06-17 23:43:14 +04:00
static void exynos_secondary_init ( unsigned int cpu )
2010-07-26 16:08:52 +04:00
{
/*
* let the primary processor know we ' re out of the
* pen , then head off into the C entry point
*/
ARM: Fix subtle race in CPU pen_release hotplug code
There is a subtle race in the CPU hotplug code, where a CPU which has
been offlined can online itself before being requested, which results
in things going astray on the next online/offline cycle.
What happens in the normal online/offline/online cycle is:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads -1
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
However, as the write of -1 of pen_release is not fully flushed back to
memory, and the checking of pen_release is done with caches disabled,
this allows CPU3 the opportunity to read the old value of pen_release:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads 3
starts boot
pen_release = -1
requests boot of CPU3
pen_release = 3
flush cache line
Fix this by grouping the write of pen_release along with its cache line
flushing code to ensure that any update to pen_release is always pushed
out to physical memory.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2010-12-18 13:53:12 +03:00
write_pen_release ( - 1 ) ;
2010-07-26 16:08:52 +04:00
/*
* Synchronise with the boot thread .
*/
spin_lock ( & boot_lock ) ;
spin_unlock ( & boot_lock ) ;
}
2013-06-17 23:43:14 +04:00
static int exynos_boot_secondary ( unsigned int cpu , struct task_struct * idle )
2010-07-26 16:08:52 +04:00
{
unsigned long timeout ;
2014-07-15 21:59:18 +04:00
u32 mpidr = cpu_logical_map ( cpu ) ;
u32 core_id = MPIDR_AFFINITY_LEVEL ( mpidr , 0 ) ;
2014-05-13 02:13:44 +04:00
int ret = - ENOSYS ;
2010-07-26 16:08:52 +04:00
/*
* Set synchronisation state between this boot processor
* and the secondary one
*/
spin_lock ( & boot_lock ) ;
/*
* The secondary processor is waiting to be released from
* the holding pen - release it , then wait for it to flag
* that it has been released by resetting pen_release .
*
2014-07-15 21:59:18 +04:00
* Note that " pen_release " is the hardware CPU core ID , whereas
2010-07-26 16:08:52 +04:00
* " cpu " is Linux ' s internal ID .
*/
2014-07-15 21:59:18 +04:00
write_pen_release ( core_id ) ;
2010-07-26 16:08:52 +04:00
2014-07-15 21:59:18 +04:00
if ( ! exynos_cpu_power_state ( core_id ) ) {
exynos_cpu_power_up ( core_id ) ;
2011-07-16 08:39:09 +04:00
timeout = 10 ;
/* wait max 10 ms until cpu1 is on */
2014-07-15 21:59:18 +04:00
while ( exynos_cpu_power_state ( core_id )
! = S5P_CORE_LOCAL_PWR_EN ) {
2011-07-16 08:39:09 +04:00
if ( timeout - - = = 0 )
break ;
mdelay ( 1 ) ;
}
if ( timeout = = 0 ) {
printk ( KERN_ERR " cpu1 power enable failed " ) ;
spin_unlock ( & boot_lock ) ;
return - ETIMEDOUT ;
}
}
2010-07-26 16:08:52 +04:00
/*
* 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 ) ) {
2012-12-11 08:58:43 +04:00
unsigned long boot_addr ;
2010-07-26 16:08:52 +04:00
smp_rmb ( ) ;
2011-07-16 08:39:09 +04:00
2012-12-11 08:58:43 +04:00
boot_addr = virt_to_phys ( exynos4_secondary_startup ) ;
/*
* Try to set boot address using firmware first
* and fall back to boot register if it fails .
*/
2014-07-15 21:59:18 +04:00
ret = call_firmware_op ( set_cpu_boot_addr , core_id , boot_addr ) ;
2014-05-13 02:13:44 +04:00
if ( ret & & ret ! = - ENOSYS )
goto fail ;
if ( ret = = - ENOSYS ) {
2014-07-15 21:59:18 +04:00
void __iomem * boot_reg = cpu_boot_reg ( core_id ) ;
2014-05-13 02:13:44 +04:00
if ( IS_ERR ( boot_reg ) ) {
ret = PTR_ERR ( boot_reg ) ;
goto fail ;
}
2014-09-13 21:31:19 +04:00
__raw_writel ( boot_addr , boot_reg ) ;
2014-05-13 02:13:44 +04:00
}
2012-12-11 08:58:43 +04:00
2014-07-15 21:59:18 +04:00
call_firmware_op ( cpu_boot , core_id ) ;
2012-12-11 08:58:43 +04:00
2012-11-27 01:05:48 +04:00
arch_send_wakeup_ipi_mask ( cpumask_of ( cpu ) ) ;
2011-07-16 08:39:09 +04:00
2010-07-26 16:08:52 +04:00
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
*/
2014-05-13 02:13:44 +04:00
fail :
2010-07-26 16:08:52 +04:00
spin_unlock ( & boot_lock ) ;
2014-05-13 02:13:44 +04:00
return pen_release ! = - 1 ? ret : 0 ;
2010-07-26 16:08:52 +04:00
}
/*
* Initialise the CPU possible map early - this describes the CPUs
* which may be present or become present in the system .
*/
2011-09-08 16:15:22 +04:00
static void __init exynos_smp_init_cpus ( void )
2010-07-26 16:08:52 +04:00
{
void __iomem * scu_base = scu_base_addr ( ) ;
unsigned int i , ncores ;
2014-06-24 22:43:15 +04:00
if ( read_cpuid_part ( ) = = ARM_CPU_PART_CORTEX_A9 )
2012-01-25 10:35:57 +04:00
ncores = scu_base ? scu_get_core_count ( scu_base ) : 1 ;
2013-06-18 19:29:34 +04:00
else
/*
* CPU Nodes are passed thru DT and set_cpu_possible
* is set by " arm_dt_init_cpu_maps " .
*/
return ;
2010-07-26 16:08:52 +04:00
/* sanity check */
2011-10-21 01:04:18 +04:00
if ( ncores > nr_cpu_ids ) {
pr_warn ( " SMP: %u cores greater than maximum (%u), clipping \n " ,
ncores , nr_cpu_ids ) ;
ncores = nr_cpu_ids ;
2010-07-26 16:08:52 +04:00
}
for ( i = 0 ; i < ncores ; i + + )
set_cpu_possible ( i , true ) ;
}
2011-09-08 16:15:22 +04:00
static void __init exynos_smp_prepare_cpus ( unsigned int max_cpus )
2010-07-26 16:08:52 +04:00
{
2012-11-24 06:13:48 +04:00
int i ;
2014-06-03 08:47:46 +04:00
exynos_sysram_init ( ) ;
2014-06-24 22:43:15 +04:00
if ( read_cpuid_part ( ) = = ARM_CPU_PART_CORTEX_A9 )
2012-01-25 10:35:57 +04:00
scu_enable ( scu_base_addr ( ) ) ;
2010-12-03 14:09:48 +03:00
2010-07-26 16:08:52 +04:00
/*
2010-12-03 14:09:48 +03:00
* Write the address of secondary startup into the
* system - wide flags register . The boot monitor waits
* until it receives a soft interrupt , and then the
* secondary CPU branches to this address .
2012-12-11 08:58:43 +04:00
*
* Try using firmware operation first and fall back to
* boot register if it fails .
2010-07-26 16:08:52 +04:00
*/
2012-12-11 08:58:43 +04:00
for ( i = 1 ; i < max_cpus ; + + i ) {
unsigned long boot_addr ;
2014-07-15 21:59:18 +04:00
u32 mpidr ;
u32 core_id ;
2014-05-13 02:13:44 +04:00
int ret ;
2012-12-11 08:58:43 +04:00
2014-07-15 21:59:18 +04:00
mpidr = cpu_logical_map ( i ) ;
core_id = MPIDR_AFFINITY_LEVEL ( mpidr , 0 ) ;
2012-12-11 08:58:43 +04:00
boot_addr = virt_to_phys ( exynos4_secondary_startup ) ;
2014-07-15 21:59:18 +04:00
ret = call_firmware_op ( set_cpu_boot_addr , core_id , boot_addr ) ;
2014-05-13 02:13:44 +04:00
if ( ret & & ret ! = - ENOSYS )
break ;
if ( ret = = - ENOSYS ) {
2014-07-15 21:59:18 +04:00
void __iomem * boot_reg = cpu_boot_reg ( core_id ) ;
2014-05-13 02:13:44 +04:00
if ( IS_ERR ( boot_reg ) )
break ;
2014-09-13 21:31:19 +04:00
__raw_writel ( boot_addr , boot_reg ) ;
2014-05-13 02:13:44 +04:00
}
2012-12-11 08:58:43 +04:00
}
2010-07-26 16:08:52 +04:00
}
2011-09-08 16:15:22 +04:00
struct smp_operations exynos_smp_ops __initdata = {
. smp_init_cpus = exynos_smp_init_cpus ,
. smp_prepare_cpus = exynos_smp_prepare_cpus ,
. smp_secondary_init = exynos_secondary_init ,
. smp_boot_secondary = exynos_boot_secondary ,
# ifdef CONFIG_HOTPLUG_CPU
. cpu_die = exynos_cpu_die ,
# endif
} ;