1/* 2 * Copyright (C) 2015 Masahiro Yamada <yamada.masahiro@socionext.com> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 */ 14 15#define pr_fmt(fmt) "uniphier: " fmt 16 17#include <linux/init.h> 18#include <linux/io.h> 19#include <linux/ioport.h> 20#include <linux/of.h> 21#include <linux/of_address.h> 22#include <linux/sizes.h> 23#include <asm/cacheflush.h> 24#include <asm/hardware/cache-uniphier.h> 25#include <asm/pgtable.h> 26#include <asm/smp.h> 27#include <asm/smp_scu.h> 28 29/* 30 * The secondary CPUs check this register from the boot ROM for the jump 31 * destination. After that, it can be reused as a scratch register. 32 */ 33#define UNIPHIER_SBC_ROM_BOOT_RSV2 0x1208 34 35static void __iomem *uniphier_smp_rom_boot_rsv2; 36static unsigned int uniphier_smp_max_cpus; 37 38extern char uniphier_smp_trampoline; 39extern char uniphier_smp_trampoline_jump; 40extern char uniphier_smp_trampoline_poll_addr; 41extern char uniphier_smp_trampoline_end; 42 43/* 44 * Copy trampoline code to the tail of the 1st section of the page table used 45 * in the boot ROM. This area is directly accessible by the secondary CPUs 46 * for all the UniPhier SoCs. 47 */ 48static const phys_addr_t uniphier_smp_trampoline_dest_end = SECTION_SIZE; 49static phys_addr_t uniphier_smp_trampoline_dest; 50 51static int __init uniphier_smp_copy_trampoline(phys_addr_t poll_addr) 52{ 53 size_t trmp_size; 54 static void __iomem *trmp_base; 55 56 if (!uniphier_cache_l2_is_enabled()) { 57 pr_warn("outer cache is needed for SMP, but not enabled\n"); 58 return -ENODEV; 59 } 60 61 uniphier_cache_l2_set_locked_ways(1); 62 63 outer_flush_all(); 64 65 trmp_size = &uniphier_smp_trampoline_end - &uniphier_smp_trampoline; 66 uniphier_smp_trampoline_dest = uniphier_smp_trampoline_dest_end - 67 trmp_size; 68 69 uniphier_cache_l2_touch_range(uniphier_smp_trampoline_dest, 70 uniphier_smp_trampoline_dest_end); 71 72 trmp_base = ioremap_cache(uniphier_smp_trampoline_dest, trmp_size); 73 if (!trmp_base) { 74 pr_err("failed to map trampoline destination area\n"); 75 return -ENOMEM; 76 } 77 78 memcpy(trmp_base, &uniphier_smp_trampoline, trmp_size); 79 80 writel(virt_to_phys(secondary_startup), 81 trmp_base + (&uniphier_smp_trampoline_jump - 82 &uniphier_smp_trampoline)); 83 84 writel(poll_addr, trmp_base + (&uniphier_smp_trampoline_poll_addr - 85 &uniphier_smp_trampoline)); 86 87 flush_cache_all(); /* flush out trampoline code to outer cache */ 88 89 iounmap(trmp_base); 90 91 return 0; 92} 93 94static int __init uniphier_smp_prepare_trampoline(unsigned int max_cpus) 95{ 96 struct device_node *np; 97 struct resource res; 98 phys_addr_t rom_rsv2_phys; 99 int ret; 100 101 np = of_find_compatible_node(NULL, NULL, 102 "socionext,uniphier-system-bus-controller"); 103 ret = of_address_to_resource(np, 1, &res); 104 if (ret) { 105 pr_err("failed to get resource of system-bus-controller\n"); 106 return ret; 107 } 108 109 rom_rsv2_phys = res.start + UNIPHIER_SBC_ROM_BOOT_RSV2; 110 111 ret = uniphier_smp_copy_trampoline(rom_rsv2_phys); 112 if (ret) 113 return ret; 114 115 uniphier_smp_rom_boot_rsv2 = ioremap(rom_rsv2_phys, sizeof(SZ_4)); 116 if (!uniphier_smp_rom_boot_rsv2) { 117 pr_err("failed to map ROM_BOOT_RSV2 register\n"); 118 return -ENOMEM; 119 } 120 121 writel(uniphier_smp_trampoline_dest, uniphier_smp_rom_boot_rsv2); 122 asm("sev"); /* Bring up all secondary CPUs to the trampoline code */ 123 124 uniphier_smp_max_cpus = max_cpus; /* save for later use */ 125 126 return 0; 127} 128 129static void __init uniphier_smp_unprepare_trampoline(void) 130{ 131 iounmap(uniphier_smp_rom_boot_rsv2); 132 133 if (uniphier_smp_trampoline_dest) 134 outer_inv_range(uniphier_smp_trampoline_dest, 135 uniphier_smp_trampoline_dest_end); 136 137 uniphier_cache_l2_set_locked_ways(0); 138} 139 140static int __init uniphier_smp_enable_scu(void) 141{ 142 unsigned long scu_base_phys = 0; 143 void __iomem *scu_base; 144 145 if (scu_a9_has_base()) 146 scu_base_phys = scu_a9_get_base(); 147 148 if (!scu_base_phys) { 149 pr_err("failed to get scu base\n"); 150 return -ENODEV; 151 } 152 153 scu_base = ioremap(scu_base_phys, SZ_128); 154 if (!scu_base) { 155 pr_err("failed to map scu base\n"); 156 return -ENOMEM; 157 } 158 159 scu_enable(scu_base); 160 iounmap(scu_base); 161 162 return 0; 163} 164 165static void __init uniphier_smp_prepare_cpus(unsigned int max_cpus) 166{ 167 static cpumask_t only_cpu_0 = { CPU_BITS_CPU0 }; 168 int ret; 169 170 ret = uniphier_smp_prepare_trampoline(max_cpus); 171 if (ret) 172 goto err; 173 174 ret = uniphier_smp_enable_scu(); 175 if (ret) 176 goto err; 177 178 return; 179err: 180 pr_warn("disabling SMP\n"); 181 init_cpu_present(&only_cpu_0); 182 uniphier_smp_unprepare_trampoline(); 183} 184 185static int __init uniphier_smp_boot_secondary(unsigned int cpu, 186 struct task_struct *idle) 187{ 188 if (WARN_ON_ONCE(!uniphier_smp_rom_boot_rsv2)) 189 return -EFAULT; 190 191 writel(cpu, uniphier_smp_rom_boot_rsv2); 192 readl(uniphier_smp_rom_boot_rsv2); /* relax */ 193 194 asm("sev"); /* wake up secondary CPUs sleeping in the trampoline */ 195 196 if (cpu == uniphier_smp_max_cpus - 1) { 197 /* clean up resources if this is the last CPU */ 198 uniphier_smp_unprepare_trampoline(); 199 } 200 201 return 0; 202} 203 204static struct smp_operations uniphier_smp_ops __initdata = { 205 .smp_prepare_cpus = uniphier_smp_prepare_cpus, 206 .smp_boot_secondary = uniphier_smp_boot_secondary, 207}; 208CPU_METHOD_OF_DECLARE(uniphier_smp, "socionext,uniphier-smp", 209 &uniphier_smp_ops); 210