1 /* 2 * SMP support for SoCs with APMU 3 * 4 * Copyright (C) 2013 Magnus Damm 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 */ 10 #include <linux/delay.h> 11 #include <linux/init.h> 12 #include <linux/io.h> 13 #include <linux/ioport.h> 14 #include <linux/of_address.h> 15 #include <linux/smp.h> 16 #include <asm/cacheflush.h> 17 #include <asm/cp15.h> 18 #include <asm/smp_plat.h> 19 #include <mach/common.h> 20 21 static struct { 22 void __iomem *iomem; 23 int bit; 24 } apmu_cpus[CONFIG_NR_CPUS]; 25 26 #define WUPCR_OFFS 0x10 27 #define PSTR_OFFS 0x40 28 #define CPUNCR_OFFS(n) (0x100 + (0x10 * (n))) 29 30 static int apmu_power_on(void __iomem *p, int bit) 31 { 32 /* request power on */ 33 writel_relaxed(BIT(bit), p + WUPCR_OFFS); 34 35 /* wait for APMU to finish */ 36 while (readl_relaxed(p + WUPCR_OFFS) != 0) 37 ; 38 39 return 0; 40 } 41 42 static int apmu_power_off(void __iomem *p, int bit) 43 { 44 /* request Core Standby for next WFI */ 45 writel_relaxed(3, p + CPUNCR_OFFS(bit)); 46 return 0; 47 } 48 49 static int apmu_power_off_poll(void __iomem *p, int bit) 50 { 51 int k; 52 53 for (k = 0; k < 1000; k++) { 54 if (((readl_relaxed(p + PSTR_OFFS) >> (bit * 4)) & 0x03) == 3) 55 return 1; 56 57 mdelay(1); 58 } 59 60 return 0; 61 } 62 63 static int apmu_wrap(int cpu, int (*fn)(void __iomem *p, int cpu)) 64 { 65 void __iomem *p = apmu_cpus[cpu].iomem; 66 67 return p ? fn(p, apmu_cpus[cpu].bit) : -EINVAL; 68 } 69 70 static void apmu_init_cpu(struct resource *res, int cpu, int bit) 71 { 72 if (apmu_cpus[cpu].iomem) 73 return; 74 75 apmu_cpus[cpu].iomem = ioremap_nocache(res->start, resource_size(res)); 76 apmu_cpus[cpu].bit = bit; 77 78 pr_debug("apmu ioremap %d %d 0x%08x 0x%08x\n", cpu, bit, 79 res->start, resource_size(res)); 80 } 81 82 static struct { 83 struct resource iomem; 84 int cpus[4]; 85 } apmu_config[] = { 86 { 87 .iomem = DEFINE_RES_MEM(0xe6152000, 0x88), 88 .cpus = { 0, 1, 2, 3 }, 89 }, 90 { 91 .iomem = DEFINE_RES_MEM(0xe6151000, 0x88), 92 .cpus = { 0x100, 0x101, 0x102, 0x103 }, 93 } 94 }; 95 96 static void apmu_parse_cfg(void (*fn)(struct resource *res, int cpu, int bit)) 97 { 98 u32 id; 99 int k; 100 int bit, index; 101 bool is_allowed; 102 103 for (k = 0; k < ARRAY_SIZE(apmu_config); k++) { 104 /* only enable the cluster that includes the boot CPU */ 105 is_allowed = false; 106 for (bit = 0; bit < ARRAY_SIZE(apmu_config[k].cpus); bit++) { 107 id = apmu_config[k].cpus[bit]; 108 if (id >= 0) { 109 if (id == cpu_logical_map(0)) 110 is_allowed = true; 111 } 112 } 113 if (!is_allowed) 114 continue; 115 116 for (bit = 0; bit < ARRAY_SIZE(apmu_config[k].cpus); bit++) { 117 id = apmu_config[k].cpus[bit]; 118 if (id >= 0) { 119 index = get_logical_index(id); 120 if (index >= 0) 121 fn(&apmu_config[k].iomem, index, bit); 122 } 123 } 124 } 125 } 126 127 void __init shmobile_smp_apmu_prepare_cpus(unsigned int max_cpus) 128 { 129 /* install boot code shared by all CPUs */ 130 shmobile_boot_fn = virt_to_phys(shmobile_smp_boot); 131 shmobile_boot_arg = MPIDR_HWID_BITMASK; 132 133 /* perform per-cpu setup */ 134 apmu_parse_cfg(apmu_init_cpu); 135 } 136 137 int shmobile_smp_apmu_boot_secondary(unsigned int cpu, struct task_struct *idle) 138 { 139 /* For this particular CPU register boot vector */ 140 shmobile_smp_hook(cpu, virt_to_phys(shmobile_invalidate_start), 0); 141 142 return apmu_wrap(cpu, apmu_power_on); 143 } 144 145 #ifdef CONFIG_HOTPLUG_CPU 146 /* nicked from arch/arm/mach-exynos/hotplug.c */ 147 static inline void cpu_enter_lowpower_a15(void) 148 { 149 unsigned int v; 150 151 asm volatile( 152 " mrc p15, 0, %0, c1, c0, 0\n" 153 " bic %0, %0, %1\n" 154 " mcr p15, 0, %0, c1, c0, 0\n" 155 : "=&r" (v) 156 : "Ir" (CR_C) 157 : "cc"); 158 159 flush_cache_louis(); 160 161 asm volatile( 162 /* 163 * Turn off coherency 164 */ 165 " mrc p15, 0, %0, c1, c0, 1\n" 166 " bic %0, %0, %1\n" 167 " mcr p15, 0, %0, c1, c0, 1\n" 168 : "=&r" (v) 169 : "Ir" (0x40) 170 : "cc"); 171 172 isb(); 173 dsb(); 174 } 175 176 void shmobile_smp_apmu_cpu_die(unsigned int cpu) 177 { 178 /* For this particular CPU deregister boot vector */ 179 shmobile_smp_hook(cpu, 0, 0); 180 181 /* Select next sleep mode using the APMU */ 182 apmu_wrap(cpu, apmu_power_off); 183 184 /* Do ARM specific CPU shutdown */ 185 cpu_enter_lowpower_a15(); 186 187 /* jump to shared mach-shmobile sleep / reset code */ 188 shmobile_smp_sleep(); 189 } 190 191 int shmobile_smp_apmu_cpu_kill(unsigned int cpu) 192 { 193 return apmu_wrap(cpu, apmu_power_off_poll); 194 } 195 #endif 196