| /* |
| * Copyright (C) 2015 Masahiro Yamada <yamada.masahiro@socionext.com> |
| * |
| * 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 <linux/init.h> |
| #include <linux/io.h> |
| #include <linux/ioport.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/sizes.h> |
| #include <asm/cacheflush.h> |
| #include <asm/hardware/cache-uniphier.h> |
| #include <asm/pgtable.h> |
| #include <asm/smp.h> |
| #include <asm/smp_scu.h> |
| |
| /* |
| * 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); |