| /* |
| * (Hisilicon's HiP04 SoC) flattened device tree enabled machine |
| * |
| * Copyright (c) 2013-2014 Hisilicon Ltd. |
| * Copyright (c) 2013-2014 Linaro Ltd. |
| * |
| * Author: Haojian Zhuang <haojian.zhuang@linaro.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/ahci_platform.h> |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <linux/irqchip/arm-gic.h> |
| #include <linux/memblock.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.h> |
| #include <linux/slab.h> |
| |
| #include <asm/cp15.h> |
| #include <asm/cputype.h> |
| #include <asm/mcpm.h> |
| |
| #include <asm/mach/arch.h> |
| #include <asm/mach/map.h> |
| |
| #define BOOTWRAPPER_PHYS 0x10c00000 |
| #define BOOTWRAPPER_MAGIC 0xa5a5a5a5 |
| #define BOOTWRAPPER_SIZE 0x00010000 |
| |
| /* bits definition in SC_CPU_RESET_REQ[x]/SC_CPU_RESET_DREQ[x] |
| * 1 -- unreset; 0 -- reset |
| */ |
| #define CORE_RESET_BIT(x) (1 << x) |
| #define NEON_RESET_BIT(x) (1 << (x + 4)) |
| #define CORE_DEBUG_RESET_BIT(x) (1 << (x + 9)) |
| #define CLUSTER_L2_RESET_BIT (1 << 8) |
| #define CLUSTER_DEBUG_RESET_BIT (1 << 13) |
| |
| /* |
| * bits definition in SC_CPU_RESET_STATUS[x] |
| * 1 -- reset status; 0 -- unreset status |
| */ |
| #define CORE_RESET_STATUS(x) (1 << x) |
| #define NEON_RESET_STATUS(x) (1 << (x + 4)) |
| #define CORE_DEBUG_RESET_STATUS(x) (1 << (x + 9)) |
| #define CLUSTER_L2_RESET_STATUS (1 << 8) |
| #define CLUSTER_DEBUG_RESET_STATUS (1 << 13) |
| #define CORE_WFI_STATUS(x) (1 << (x + 16)) |
| #define CORE_WFE_STATUS(x) (1 << (x + 20)) |
| #define CORE_DEBUG_ACK(x) (1 << (x + 24)) |
| |
| #define SC_CPU_RESET_REQ(x) (0x520 + (x << 3)) /* reset */ |
| #define SC_CPU_RESET_DREQ(x) (0x524 + (x << 3)) /* unreset */ |
| #define SC_CPU_RESET_STATUS(x) (0x1520 + (x << 3)) |
| |
| #define FAB_SF_MODE 0x0c |
| #define FAB_SF_INVLD 0x10 |
| |
| /* bits definition in FB_SF_INVLD */ |
| #define FB_SF_INVLD_START (1 << 8) |
| |
| #define HIP04_MAX_CLUSTERS 4 |
| #define HIP04_MAX_CPUS_PER_CLUSTER 4 |
| |
| static void __iomem *relocation = NULL, *sysctrl = NULL, *fabric = NULL; |
| static int hip04_cpu_table[HIP04_MAX_CLUSTERS][HIP04_MAX_CPUS_PER_CLUSTER]; |
| static DEFINE_SPINLOCK(boot_lock); |
| |
| static bool hip04_cluster_down(unsigned int cluster) |
| { |
| int i; |
| |
| for (i = 0; i < HIP04_MAX_CPUS_PER_CLUSTER; i++) |
| if (hip04_cpu_table[cluster][i]) |
| return false; |
| return true; |
| } |
| |
| static void hip04_set_snoop_filter(unsigned int cluster, unsigned int on) |
| { |
| unsigned long data; |
| |
| if (!fabric) |
| return; |
| data = readl_relaxed(fabric + FAB_SF_MODE); |
| if (on) |
| data |= 1 << cluster; |
| else |
| data &= ~(1 << cluster); |
| writel_relaxed(data, fabric + FAB_SF_MODE); |
| while (1) { |
| if (data == readl_relaxed(fabric + FAB_SF_MODE)) |
| break; |
| } |
| } |
| |
| static int hip04_mcpm_power_up(unsigned int cpu, unsigned int cluster) |
| { |
| unsigned long data, mask; |
| |
| if (!relocation || !sysctrl) |
| return -ENODEV; |
| if (cluster >= HIP04_MAX_CLUSTERS || cpu >= HIP04_MAX_CPUS_PER_CLUSTER) |
| return -EINVAL; |
| |
| spin_lock(&boot_lock); |
| writel_relaxed(BOOTWRAPPER_PHYS, relocation); |
| writel_relaxed(BOOTWRAPPER_MAGIC, relocation + 4); |
| writel_relaxed(virt_to_phys(mcpm_entry_point), relocation + 8); |
| writel_relaxed(0, relocation + 12); |
| |
| if (hip04_cluster_down(cluster)) { |
| data = CLUSTER_L2_RESET_BIT | CLUSTER_DEBUG_RESET_BIT; |
| writel_relaxed(data, sysctrl + SC_CPU_RESET_DREQ(cluster)); |
| do { |
| mask = CLUSTER_L2_RESET_STATUS | \ |
| CLUSTER_DEBUG_RESET_STATUS; |
| data = readl_relaxed(sysctrl + \ |
| SC_CPU_RESET_STATUS(cluster)); |
| } while (data & mask); |
| hip04_set_snoop_filter(cluster, 1); |
| } |
| |
| hip04_cpu_table[cluster][cpu]++; |
| |
| data = CORE_RESET_BIT(cpu) | NEON_RESET_BIT(cpu) | \ |
| CORE_DEBUG_RESET_BIT(cpu); |
| writel_relaxed(data, sysctrl + SC_CPU_RESET_DREQ(cluster)); |
| spin_unlock(&boot_lock); |
| |
| return 0; |
| } |
| |
| static void hip04_mcpm_power_down(void) |
| { |
| unsigned int mpidr, cpu, cluster; |
| unsigned int v; |
| |
| spin_lock(&boot_lock); |
| spin_unlock(&boot_lock); |
| |
| mpidr = read_cpuid_mpidr(); |
| cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); |
| cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); |
| |
| local_irq_disable(); |
| gic_cpu_if_down(); |
| |
| __mcpm_cpu_down(cpu, cluster); |
| |
| asm volatile( |
| " mrc p15, 0, %0, c1, c0, 0\n" |
| " bic %0, %0, %1\n" |
| " mcr p15, 0, %0, c1, c0, 0\n" |
| : "=&r" (v) |
| : "Ir" (CR_C) |
| : "cc"); |
| |
| flush_cache_louis(); |
| |
| asm volatile( |
| /* |
| * Turn off coherency |
| */ |
| " mrc p15, 0, %0, c1, c0, 1\n" |
| " bic %0, %0, %1\n" |
| " mcr p15, 0, %0, c1, c0, 1\n" |
| : "=&r" (v) |
| : "Ir" (0x40) |
| : "cc"); |
| |
| isb(); |
| dsb(); |
| } |
| |
| static int hip04_mcpm_power_down_finish(unsigned int cpu, unsigned int cluster) |
| { |
| int ret = -EBUSY; |
| |
| spin_lock(&boot_lock); |
| BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP); |
| __mcpm_cpu_going_down(cpu, cluster); |
| |
| hip04_cpu_table[cluster][cpu]--; |
| if (hip04_cpu_table[cluster][cpu]) { |
| pr_err("Cluster %d CPU%d is still running\n", cluster, cpu); |
| goto out; |
| } |
| ret = 0; |
| out: |
| spin_unlock(&boot_lock); |
| return ret; |
| } |
| |
| static void hip04_mcpm_powered_up(void) |
| { |
| if (!relocation) |
| return; |
| spin_lock(&boot_lock); |
| writel_relaxed(0, relocation); |
| writel_relaxed(0, relocation + 4); |
| writel_relaxed(0, relocation + 8); |
| writel_relaxed(0, relocation + 12); |
| spin_unlock(&boot_lock); |
| } |
| |
| static const struct mcpm_platform_ops hip04_mcpm_ops = { |
| .power_up = hip04_mcpm_power_up, |
| .power_down = hip04_mcpm_power_down, |
| .power_down_finish = hip04_mcpm_power_down_finish, |
| .powered_up = hip04_mcpm_powered_up, |
| }; |
| |
| static bool __init hip04_cpu_table_init(void) |
| { |
| unsigned int mpidr, cpu, cluster; |
| |
| mpidr = read_cpuid_mpidr(); |
| cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); |
| cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); |
| |
| if (cluster >= HIP04_MAX_CLUSTERS || |
| cpu >= HIP04_MAX_CPUS_PER_CLUSTER) { |
| pr_err("%s: boot CPU is out of bound!\n", __func__); |
| return false; |
| } |
| hip04_set_snoop_filter(cluster, 1); |
| hip04_cpu_table[cluster][cpu] = 1; |
| return true; |
| } |
| |
| static int __init hip04_mcpm_init(void) |
| { |
| struct device_node *np; |
| int ret = -ENODEV; |
| |
| np = of_find_compatible_node(NULL, NULL, "hisilicon,hip04-mcpm"); |
| if (!np) { |
| pr_err("failed to find hisilicon,hip04-mcpm node\n"); |
| goto err; |
| } |
| relocation = of_iomap(np, 0); |
| if (!relocation) { |
| pr_err("failed to get relocation space\n"); |
| ret = -ENOMEM; |
| goto err; |
| } |
| sysctrl = of_iomap(np, 1); |
| if (!sysctrl) { |
| pr_err("failed to get sysctrl base\n"); |
| ret = -ENOMEM; |
| goto err_sysctrl; |
| } |
| fabric = of_iomap(np, 2); |
| if (!fabric) { |
| pr_err("failed to get fabric base\n"); |
| ret = -ENOMEM; |
| goto err_fabric; |
| } |
| if (!hip04_cpu_table_init()) |
| return -EINVAL; |
| ret = mcpm_platform_register(&hip04_mcpm_ops); |
| if (!ret) { |
| mcpm_sync_init(NULL); |
| pr_info("HiP04 MCPM initialized\n"); |
| } |
| return ret; |
| err_fabric: |
| iounmap(sysctrl); |
| err_sysctrl: |
| iounmap(relocation); |
| err: |
| return ret; |
| } |
| early_initcall(hip04_mcpm_init); |
| |
| bool __init hip04_smp_init_ops(void) |
| { |
| mcpm_smp_set_ops(); |
| return true; |
| } |
| |
| #define HIP04_SATA_BASE (0xea000000) |
| |
| static int sata_vsemiphy_init(struct device *dev, void __iomem *addr) |
| { |
| return 0; |
| } |
| |
| static struct ahci_platform_data hip04_sata_pdata = { |
| .init = sata_vsemiphy_init, |
| }; |
| |
| static struct of_dev_auxdata hip04_auxdata_lookup[] __initdata = { |
| OF_DEV_AUXDATA("hisilicon,hisi-ahci", HIP04_SATA_BASE, |
| NULL, &hip04_sata_pdata), |
| {}, |
| }; |
| |
| static void __init hip04_init_machine(void) |
| { |
| of_platform_populate(NULL, of_default_bus_match_table, |
| hip04_auxdata_lookup, NULL); |
| } |
| |
| static const char *hip04_compat[] __initconst = { |
| "hisilicon,hip04-d01", |
| NULL, |
| }; |
| |
| static void __init hip04_reserve(void) |
| { |
| memblock_reserve(BOOTWRAPPER_PHYS, BOOTWRAPPER_SIZE); |
| } |
| |
| DT_MACHINE_START(HIP01, "Hisilicon HiP04 (Flattened Device Tree)") |
| .dt_compat = hip04_compat, |
| .smp_init = smp_init_ops(hip04_smp_init_ops), |
| .init_machine = hip04_init_machine, |
| .reserve = hip04_reserve, |
| MACHINE_END |