2013-08-29 08:21:58 +09:00
/*
* SMP support for SoCs with APMU
*
2014-10-24 17:33:08 +09:00
* Copyright ( C ) 2014 Renesas Electronics Corporation
2013-08-29 08:21:58 +09:00
* Copyright ( C ) 2013 Magnus Damm
*
* 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 .
*/
2014-05-29 16:24:27 +09:00
# include <linux/cpu_pm.h>
2013-08-29 08:21:58 +09:00
# include <linux/delay.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/of_address.h>
# include <linux/smp.h>
2014-05-29 16:24:27 +09:00
# include <linux/suspend.h>
2014-06-06 16:20:18 +09:00
# include <linux/threads.h>
2013-08-29 08:21:58 +09:00
# include <asm/cacheflush.h>
# include <asm/cp15.h>
2014-05-29 16:24:27 +09:00
# include <asm/proc-fns.h>
2013-08-29 08:21:58 +09:00
# include <asm/smp_plat.h>
2014-05-29 16:24:27 +09:00
# include <asm/suspend.h>
2014-06-17 16:47:37 +09:00
# include "common.h"
2014-10-24 17:33:08 +09:00
# include "platsmp-apmu.h"
2016-06-28 16:10:36 +02:00
# include "rcar-gen2.h"
2013-08-29 08:21:58 +09:00
static struct {
void __iomem * iomem ;
int bit ;
2014-06-06 16:20:18 +09:00
} apmu_cpus [ NR_CPUS ] ;
2013-08-29 08:21:58 +09:00
2016-12-05 11:39:37 +01:00
# define WUPCR_OFFS 0x10 /* Wake Up Control Register */
# define PSTR_OFFS 0x40 /* Power Status Register */
# define CPUNCR_OFFS(n) (0x100 + (0x10 * (n)))
/* CPUn Power Status Control Register */
2016-12-05 11:39:38 +01:00
# define DBGRCR_OFFS 0x180 /* Debug Resource Reset Control Reg. */
2016-12-05 11:39:37 +01:00
/* Power Status Register */
# define CPUNST(r, n) (((r) >> (n * 4)) & 3) /* CPUn Status Bit */
# define CPUST_RUN 0 /* Run Mode */
# define CPUST_STANDBY 3 /* CoreStandby Mode */
2013-08-29 08:21:58 +09:00
2016-12-05 11:39:38 +01:00
/* Debug Resource Reset Control Register */
# define DBGCPUREN BIT(24) /* CPU Other Reset Request Enable */
# define DBGCPUNREN(n) BIT((n) + 20) /* CPUn Reset Request Enable */
# define DBGCPUPREN BIT(19) /* CPU Peripheral Reset Req. Enable */
2014-06-06 16:20:18 +09:00
static int __maybe_unused apmu_power_on ( void __iomem * p , int bit )
2013-08-29 08:21:58 +09:00
{
/* request power on */
writel_relaxed ( BIT ( bit ) , p + WUPCR_OFFS ) ;
/* wait for APMU to finish */
while ( readl_relaxed ( p + WUPCR_OFFS ) ! = 0 )
;
return 0 ;
}
2015-07-10 22:48:16 +02:00
static int __maybe_unused apmu_power_off ( void __iomem * p , int bit )
2013-08-29 08:21:58 +09:00
{
/* request Core Standby for next WFI */
writel_relaxed ( 3 , p + CPUNCR_OFFS ( bit ) ) ;
return 0 ;
}
2014-06-06 16:20:18 +09:00
static int __maybe_unused apmu_power_off_poll ( void __iomem * p , int bit )
2013-08-29 08:21:58 +09:00
{
int k ;
for ( k = 0 ; k < 1000 ; k + + ) {
2016-12-05 11:39:37 +01:00
if ( CPUNST ( readl_relaxed ( p + PSTR_OFFS ) , bit ) = = CPUST_STANDBY )
2013-08-29 08:21:58 +09:00
return 1 ;
mdelay ( 1 ) ;
}
return 0 ;
}
2015-07-10 22:48:16 +02:00
static int __maybe_unused apmu_wrap ( int cpu , int ( * fn ) ( void __iomem * p , int cpu ) )
2013-08-29 08:21:58 +09:00
{
void __iomem * p = apmu_cpus [ cpu ] . iomem ;
return p ? fn ( p , apmu_cpus [ cpu ] . bit ) : - EINVAL ;
}
2016-06-28 16:10:35 +02:00
# ifdef CONFIG_SMP
2013-08-29 08:21:58 +09:00
static void apmu_init_cpu ( struct resource * res , int cpu , int bit )
{
2016-12-05 11:39:38 +01:00
u32 x ;
2014-06-06 16:20:18 +09:00
if ( ( cpu > = ARRAY_SIZE ( apmu_cpus ) ) | | apmu_cpus [ cpu ] . iomem )
2013-08-29 08:21:58 +09:00
return ;
apmu_cpus [ cpu ] . iomem = ioremap_nocache ( res - > start , resource_size ( res ) ) ;
apmu_cpus [ cpu ] . bit = bit ;
2014-03-04 19:11:15 +01:00
pr_debug ( " apmu ioremap %d %d %pr \n " , cpu , bit , res ) ;
2016-12-05 11:39:38 +01:00
/* Setup for debug mode */
x = readl ( apmu_cpus [ cpu ] . iomem + DBGRCR_OFFS ) ;
x | = DBGCPUREN | DBGCPUNREN ( bit ) | DBGCPUPREN ;
writel ( x , apmu_cpus [ cpu ] . iomem + DBGRCR_OFFS ) ;
2013-08-29 08:21:58 +09:00
}
2014-10-24 17:33:08 +09:00
static void apmu_parse_cfg ( void ( * fn ) ( struct resource * res , int cpu , int bit ) ,
struct rcar_apmu_config * apmu_config , int num )
2013-08-29 08:21:58 +09:00
{
2015-09-21 15:33:45 +02:00
int id ;
2013-08-29 08:21:58 +09:00
int k ;
int bit , index ;
2013-09-15 00:29:07 +09:00
bool is_allowed ;
2013-08-29 08:21:58 +09:00
2014-10-24 17:33:08 +09:00
for ( k = 0 ; k < num ; k + + ) {
2013-09-15 00:29:07 +09:00
/* only enable the cluster that includes the boot CPU */
is_allowed = false ;
for ( bit = 0 ; bit < ARRAY_SIZE ( apmu_config [ k ] . cpus ) ; bit + + ) {
id = apmu_config [ k ] . cpus [ bit ] ;
if ( id > = 0 ) {
if ( id = = cpu_logical_map ( 0 ) )
is_allowed = true ;
}
}
if ( ! is_allowed )
continue ;
2013-08-29 08:21:58 +09:00
for ( bit = 0 ; bit < ARRAY_SIZE ( apmu_config [ k ] . cpus ) ; bit + + ) {
id = apmu_config [ k ] . cpus [ bit ] ;
if ( id > = 0 ) {
index = get_logical_index ( id ) ;
if ( index > = 0 )
fn ( & apmu_config [ k ] . iomem , index , bit ) ;
}
}
}
}
2016-06-28 16:10:36 +02:00
static const struct of_device_id apmu_ids [ ] = {
{ . compatible = " renesas,apmu " } ,
{ /*sentinel*/ }
} ;
static void apmu_parse_dt ( void ( * fn ) ( struct resource * res , int cpu , int bit ) )
{
struct device_node * np_apmu , * np_cpu ;
struct resource res ;
int bit , index ;
u32 id ;
for_each_matching_node ( np_apmu , apmu_ids ) {
/* only enable the cluster that includes the boot CPU */
bool is_allowed = false ;
for ( bit = 0 ; bit < CONFIG_NR_CPUS ; bit + + ) {
np_cpu = of_parse_phandle ( np_apmu , " cpus " , bit ) ;
if ( np_cpu ) {
if ( ! of_property_read_u32 ( np_cpu , " reg " , & id ) ) {
if ( id = = cpu_logical_map ( 0 ) ) {
is_allowed = true ;
of_node_put ( np_cpu ) ;
break ;
}
}
of_node_put ( np_cpu ) ;
}
}
if ( ! is_allowed )
continue ;
for ( bit = 0 ; bit < CONFIG_NR_CPUS ; bit + + ) {
np_cpu = of_parse_phandle ( np_apmu , " cpus " , bit ) ;
if ( np_cpu ) {
if ( ! of_property_read_u32 ( np_cpu , " reg " , & id ) ) {
index = get_logical_index ( id ) ;
if ( ( index > = 0 ) & &
! of_address_to_resource ( np_apmu ,
0 , & res ) )
fn ( & res , index , bit ) ;
}
of_node_put ( np_cpu ) ;
}
}
}
}
static void __init shmobile_smp_apmu_setup_boot ( void )
2013-08-29 08:21:58 +09:00
{
/* install boot code shared by all CPUs */
2017-01-15 03:59:29 +01:00
shmobile_boot_fn = __pa_symbol ( shmobile_smp_boot ) ;
2016-06-28 16:10:36 +02:00
}
2013-08-29 08:21:58 +09:00
2016-06-28 16:10:36 +02:00
void __init shmobile_smp_apmu_prepare_cpus ( unsigned int max_cpus ,
struct rcar_apmu_config * apmu_config ,
int num )
{
shmobile_smp_apmu_setup_boot ( ) ;
2014-10-24 17:33:08 +09:00
apmu_parse_cfg ( apmu_init_cpu , apmu_config , num ) ;
2013-08-29 08:21:58 +09:00
}
int shmobile_smp_apmu_boot_secondary ( unsigned int cpu , struct task_struct * idle )
{
/* For this particular CPU register boot vector */
2017-01-15 03:59:29 +01:00
shmobile_smp_hook ( cpu , __pa_symbol ( secondary_startup ) , 0 ) ;
2013-08-29 08:21:58 +09:00
return apmu_wrap ( cpu , apmu_power_on ) ;
}
2016-06-28 16:10:36 +02:00
static void __init shmobile_smp_apmu_prepare_cpus_dt ( unsigned int max_cpus )
{
shmobile_smp_apmu_setup_boot ( ) ;
apmu_parse_dt ( apmu_init_cpu ) ;
rcar_gen2_pm_init ( ) ;
}
static struct smp_operations apmu_smp_ops __initdata = {
. smp_prepare_cpus = shmobile_smp_apmu_prepare_cpus_dt ,
2016-12-05 11:39:39 +01:00
. smp_boot_secondary = shmobile_smp_apmu_boot_secondary ,
2016-06-28 16:10:36 +02:00
# ifdef CONFIG_HOTPLUG_CPU
. cpu_can_disable = shmobile_smp_cpu_can_disable ,
. cpu_die = shmobile_smp_apmu_cpu_die ,
. cpu_kill = shmobile_smp_apmu_cpu_kill ,
2014-06-06 16:20:18 +09:00
# endif
2016-06-28 16:10:36 +02:00
} ;
CPU_METHOD_OF_DECLARE ( shmobile_smp_apmu , " renesas,apmu " , & apmu_smp_ops ) ;
# endif /* CONFIG_SMP */
2013-08-29 08:21:58 +09:00
2014-05-29 16:24:27 +09:00
# if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_SUSPEND)
2013-08-29 08:21:58 +09:00
/* nicked from arch/arm/mach-exynos/hotplug.c */
static inline void cpu_enter_lowpower_a15 ( void )
{
unsigned int v ;
asm volatile (
" mrc p15, 0, %0, c1, c0, 0 \n "
" bic %0, %0, %1 \n "
" mcr p15, 0, %0, c1, c0, 0 \n "
: " =&r " ( v )
: " Ir " ( CR_C )
: " cc " ) ;
flush_cache_louis ( ) ;
asm volatile (
/*
* Turn off coherency
*/
" mrc p15, 0, %0, c1, c0, 1 \n "
" bic %0, %0, %1 \n "
" mcr p15, 0, %0, c1, c0, 1 \n "
: " =&r " ( v )
: " Ir " ( 0x40 )
: " cc " ) ;
isb ( ) ;
dsb ( ) ;
}
2015-09-30 17:50:18 +02:00
static void shmobile_smp_apmu_cpu_shutdown ( unsigned int cpu )
2013-08-29 08:21:58 +09:00
{
/* Select next sleep mode using the APMU */
apmu_wrap ( cpu , apmu_power_off ) ;
/* Do ARM specific CPU shutdown */
cpu_enter_lowpower_a15 ( ) ;
2014-05-29 16:24:27 +09:00
}
static inline void cpu_leave_lowpower ( void )
{
unsigned int v ;
asm volatile ( " mrc p15, 0, %0, c1, c0, 0 \n "
" orr %0, %0, %1 \n "
" mcr p15, 0, %0, c1, c0, 0 \n "
" mrc p15, 0, %0, c1, c0, 1 \n "
" orr %0, %0, %2 \n "
" mcr p15, 0, %0, c1, c0, 1 \n "
: " =&r " ( v )
: " Ir " ( CR_C ) , " Ir " ( 0x40 )
: " cc " ) ;
}
# endif
# if defined(CONFIG_HOTPLUG_CPU)
void shmobile_smp_apmu_cpu_die ( unsigned int cpu )
{
/* For this particular CPU deregister boot vector */
shmobile_smp_hook ( cpu , 0 , 0 ) ;
/* Shutdown CPU core */
shmobile_smp_apmu_cpu_shutdown ( cpu ) ;
2013-08-29 08:21:58 +09:00
/* jump to shared mach-shmobile sleep / reset code */
shmobile_smp_sleep ( ) ;
}
int shmobile_smp_apmu_cpu_kill ( unsigned int cpu )
{
return apmu_wrap ( cpu , apmu_power_off_poll ) ;
}
# endif
2014-05-29 16:24:27 +09:00
# if defined(CONFIG_SUSPEND)
static int shmobile_smp_apmu_do_suspend ( unsigned long cpu )
{
2017-01-15 03:59:29 +01:00
shmobile_smp_hook ( cpu , __pa_symbol ( cpu_resume ) , 0 ) ;
2014-05-29 16:24:27 +09:00
shmobile_smp_apmu_cpu_shutdown ( cpu ) ;
cpu_do_idle ( ) ; /* WFI selects Core Standby */
return 1 ;
}
static int shmobile_smp_apmu_enter_suspend ( suspend_state_t state )
{
cpu_suspend ( smp_processor_id ( ) , shmobile_smp_apmu_do_suspend ) ;
cpu_leave_lowpower ( ) ;
return 0 ;
}
2014-06-06 16:20:46 +09:00
void __init shmobile_smp_apmu_suspend_init ( void )
2014-05-29 16:24:27 +09:00
{
shmobile_suspend_ops . enter = shmobile_smp_apmu_enter_suspend ;
}
# endif