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 %pr\n", cpu, bit, res); 79 } 80 81 static struct { 82 struct resource iomem; 83 int cpus[4]; 84 } apmu_config[] = { 85 { 86 .iomem = DEFINE_RES_MEM(0xe6152000, 0x88), 87 .cpus = { 0, 1, 2, 3 }, 88 }, 89 { 90 .iomem = DEFINE_RES_MEM(0xe6151000, 0x88), 91 .cpus = { 0x100, 0x101, 0x102, 0x103 }, 92 } 93 }; 94 95 static void apmu_parse_cfg(void (*fn)(struct resource *res, int cpu, int bit)) 96 { 97 u32 id; 98 int k; 99 int bit, index; 100 bool is_allowed; 101 102 for (k = 0; k < ARRAY_SIZE(apmu_config); k++) { 103 /* only enable the cluster that includes the boot CPU */ 104 is_allowed = false; 105 for (bit = 0; bit < ARRAY_SIZE(apmu_config[k].cpus); bit++) { 106 id = apmu_config[k].cpus[bit]; 107 if (id >= 0) { 108 if (id == cpu_logical_map(0)) 109 is_allowed = true; 110 } 111 } 112 if (!is_allowed) 113 continue; 114 115 for (bit = 0; bit < ARRAY_SIZE(apmu_config[k].cpus); bit++) { 116 id = apmu_config[k].cpus[bit]; 117 if (id >= 0) { 118 index = get_logical_index(id); 119 if (index >= 0) 120 fn(&apmu_config[k].iomem, index, bit); 121 } 122 } 123 } 124 } 125 126 void __init shmobile_smp_apmu_prepare_cpus(unsigned int max_cpus) 127 { 128 /* install boot code shared by all CPUs */ 129 shmobile_boot_fn = virt_to_phys(shmobile_smp_boot); 130 shmobile_boot_arg = MPIDR_HWID_BITMASK; 131 132 /* perform per-cpu setup */ 133 apmu_parse_cfg(apmu_init_cpu); 134 } 135 136 int shmobile_smp_apmu_boot_secondary(unsigned int cpu, struct task_struct *idle) 137 { 138 /* For this particular CPU register boot vector */ 139 shmobile_smp_hook(cpu, virt_to_phys(shmobile_invalidate_start), 0); 140 141 return apmu_wrap(cpu, apmu_power_on); 142 } 143 144 #ifdef CONFIG_HOTPLUG_CPU 145 /* nicked from arch/arm/mach-exynos/hotplug.c */ 146 static inline void cpu_enter_lowpower_a15(void) 147 { 148 unsigned int v; 149 150 asm volatile( 151 " mrc p15, 0, %0, c1, c0, 0\n" 152 " bic %0, %0, %1\n" 153 " mcr p15, 0, %0, c1, c0, 0\n" 154 : "=&r" (v) 155 : "Ir" (CR_C) 156 : "cc"); 157 158 flush_cache_louis(); 159 160 asm volatile( 161 /* 162 * Turn off coherency 163 */ 164 " mrc p15, 0, %0, c1, c0, 1\n" 165 " bic %0, %0, %1\n" 166 " mcr p15, 0, %0, c1, c0, 1\n" 167 : "=&r" (v) 168 : "Ir" (0x40) 169 : "cc"); 170 171 isb(); 172 dsb(); 173 } 174 175 void shmobile_smp_apmu_cpu_die(unsigned int cpu) 176 { 177 /* For this particular CPU deregister boot vector */ 178 shmobile_smp_hook(cpu, 0, 0); 179 180 /* Select next sleep mode using the APMU */ 181 apmu_wrap(cpu, apmu_power_off); 182 183 /* Do ARM specific CPU shutdown */ 184 cpu_enter_lowpower_a15(); 185 186 /* jump to shared mach-shmobile sleep / reset code */ 187 shmobile_smp_sleep(); 188 } 189 190 int shmobile_smp_apmu_cpu_kill(unsigned int cpu) 191 { 192 return apmu_wrap(cpu, apmu_power_off_poll); 193 } 194 #endif 195