/* * Copyright (C) 2015 Masahiro Yamada * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define pr_fmt(fmt) "uniphier: " fmt #include #include #include #include #include #include #include #include #include #include #include /* * The secondary CPUs check this register from the boot ROM for the jump * destination. After that, it can be reused as a scratch register. */ #define UNIPHIER_SBC_ROM_BOOT_RSV2 0x1208 static void __iomem *uniphier_smp_rom_boot_rsv2; static unsigned int uniphier_smp_max_cpus; extern char uniphier_smp_trampoline; extern char uniphier_smp_trampoline_jump; extern char uniphier_smp_trampoline_poll_addr; extern char uniphier_smp_trampoline_end; /* * Copy trampoline code to the tail of the 1st section of the page table used * in the boot ROM. This area is directly accessible by the secondary CPUs * for all the UniPhier SoCs. */ static const phys_addr_t uniphier_smp_trampoline_dest_end = SECTION_SIZE; static phys_addr_t uniphier_smp_trampoline_dest; static int __init uniphier_smp_copy_trampoline(phys_addr_t poll_addr) { size_t trmp_size; static void __iomem *trmp_base; if (!uniphier_cache_l2_is_enabled()) { pr_warn("outer cache is needed for SMP, but not enabled\n"); return -ENODEV; } uniphier_cache_l2_set_locked_ways(1); outer_flush_all(); trmp_size = &uniphier_smp_trampoline_end - &uniphier_smp_trampoline; uniphier_smp_trampoline_dest = uniphier_smp_trampoline_dest_end - trmp_size; uniphier_cache_l2_touch_range(uniphier_smp_trampoline_dest, uniphier_smp_trampoline_dest_end); trmp_base = ioremap_cache(uniphier_smp_trampoline_dest, trmp_size); if (!trmp_base) { pr_err("failed to map trampoline destination area\n"); return -ENOMEM; } memcpy(trmp_base, &uniphier_smp_trampoline, trmp_size); writel(virt_to_phys(secondary_startup), trmp_base + (&uniphier_smp_trampoline_jump - &uniphier_smp_trampoline)); writel(poll_addr, trmp_base + (&uniphier_smp_trampoline_poll_addr - &uniphier_smp_trampoline)); flush_cache_all(); /* flush out trampoline code to outer cache */ iounmap(trmp_base); return 0; } static int __init uniphier_smp_prepare_trampoline(unsigned int max_cpus) { struct device_node *np; struct resource res; phys_addr_t rom_rsv2_phys; int ret; np = of_find_compatible_node(NULL, NULL, "socionext,uniphier-system-bus-controller"); ret = of_address_to_resource(np, 1, &res); if (ret) { pr_err("failed to get resource of system-bus-controller\n"); return ret; } rom_rsv2_phys = res.start + UNIPHIER_SBC_ROM_BOOT_RSV2; ret = uniphier_smp_copy_trampoline(rom_rsv2_phys); if (ret) return ret; uniphier_smp_rom_boot_rsv2 = ioremap(rom_rsv2_phys, sizeof(SZ_4)); if (!uniphier_smp_rom_boot_rsv2) { pr_err("failed to map ROM_BOOT_RSV2 register\n"); return -ENOMEM; } writel(uniphier_smp_trampoline_dest, uniphier_smp_rom_boot_rsv2); asm("sev"); /* Bring up all secondary CPUs to the trampoline code */ uniphier_smp_max_cpus = max_cpus; /* save for later use */ return 0; } static void __init uniphier_smp_unprepare_trampoline(void) { iounmap(uniphier_smp_rom_boot_rsv2); if (uniphier_smp_trampoline_dest) outer_inv_range(uniphier_smp_trampoline_dest, uniphier_smp_trampoline_dest_end); uniphier_cache_l2_set_locked_ways(0); } static int __init uniphier_smp_enable_scu(void) { unsigned long scu_base_phys = 0; void __iomem *scu_base; if (scu_a9_has_base()) scu_base_phys = scu_a9_get_base(); if (!scu_base_phys) { pr_err("failed to get scu base\n"); return -ENODEV; } scu_base = ioremap(scu_base_phys, SZ_128); if (!scu_base) { pr_err("failed to map scu base\n"); return -ENOMEM; } scu_enable(scu_base); iounmap(scu_base); return 0; } static void __init uniphier_smp_prepare_cpus(unsigned int max_cpus) { static cpumask_t only_cpu_0 = { CPU_BITS_CPU0 }; int ret; ret = uniphier_smp_prepare_trampoline(max_cpus); if (ret) goto err; ret = uniphier_smp_enable_scu(); if (ret) goto err; return; err: pr_warn("disabling SMP\n"); init_cpu_present(&only_cpu_0); uniphier_smp_unprepare_trampoline(); } static int __init uniphier_smp_boot_secondary(unsigned int cpu, struct task_struct *idle) { if (WARN_ON_ONCE(!uniphier_smp_rom_boot_rsv2)) return -EFAULT; writel(cpu, uniphier_smp_rom_boot_rsv2); readl(uniphier_smp_rom_boot_rsv2); /* relax */ asm("sev"); /* wake up secondary CPUs sleeping in the trampoline */ if (cpu == uniphier_smp_max_cpus - 1) { /* clean up resources if this is the last CPU */ uniphier_smp_unprepare_trampoline(); } return 0; } static struct smp_operations uniphier_smp_ops __initdata = { .smp_prepare_cpus = uniphier_smp_prepare_cpus, .smp_boot_secondary = uniphier_smp_boot_secondary, }; CPU_METHOD_OF_DECLARE(uniphier_smp, "socionext,uniphier-smp", &uniphier_smp_ops);