Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | /* Copyright (c) 2021 Intel Corporation * SPDX-License-Identifier: Apache-2.0 */ #include <zephyr/kernel.h> #include <cavs-idc.h> #include <adsp_memory.h> #include <adsp_shim.h> #include <zephyr/irq.h> #include <zephyr/pm/pm.h> #include <zephyr/cache.h> #include <ipi.h> /* IDC power up message to the ROM firmware. This isn't documented * anywhere, it's basically just a magic number (except the high bit, * which signals the hardware) */ #define IDC_MSG_POWER_UP \ (BIT(31) | /* Latch interrupt in ITC write */ \ (0x1 << 24) | /* "ROM control version" = 1 */ \ (0x2 << 0)) /* "Core wake version" = 2 */ #define IDC_CORE_MASK(num_cpus) (BIT(num_cpus) - 1) __imr void soc_mp_startup(uint32_t cpu) { /* We got here via an IDC interrupt. Clear the TFC high bit * (by writing a one!) to acknowledge and clear the latched * hardware interrupt (so we don't have to service it as a * spurious IPI when we enter user code). Remember: this * could have come from any core, clear all of them. */ unsigned int num_cpus = arch_num_cpus(); for (int i = 0; i < num_cpus; i++) { IDC[cpu].core[i].tfc = BIT(31); } /* Interrupt must be enabled while running on current core */ irq_enable(DT_IRQN(INTEL_ADSP_IDC_DTNODE)); } void soc_start_core(int cpu_num) { uint32_t curr_cpu = arch_proc_id(); /* On cAVS v2.5, MP startup works differently. The core has * no ROM, and starts running immediately upon receipt of an * IDC interrupt at the start of LPSRAM at 0xbe800000. Note * that means we don't need to bother constructing a "message" * below, it will be ignored. But it's left in place for * simplicity and compatibility. * * All we need to do is place a single jump at that address to * our existing MP entry point. Unfortunately Xtensa makes * this difficult, as the region is beyond the range of a * relative jump instruction, so we need an immediate, which * can only be backwards-referenced. So we hand-assemble a * tiny trampoline here ("jump over the immediate address, * load it, jump to it"). * * Long term we want to have this in linkable LP-SRAM memory * such that the standard system bootstrap out of IMR can * place it there. But this is fine for now. */ void **lpsram = sys_cache_uncached_ptr_get( (__sparse_force void __sparse_cache *)LP_SRAM_BASE); uint8_t tramp[] = { 0x06, 0x01, 0x00, /* J <PC+8> (jump to L32R) */ 0, /* (padding to align entry_addr) */ 0, 0, 0, 0, /* (entry_addr goes here) */ 0x01, 0xff, 0xff, /* L32R a0, <entry_addr> */ 0xa0, 0x00, 0x00, /* JX a0 */ }; memcpy(lpsram, tramp, ARRAY_SIZE(tramp)); #if CONFIG_PM extern void dsp_restore_vector(void); /* We need to find out what type of booting is taking place here. Secondary cores * can be disabled and enabled multiple times during runtime. During kernel * initialization, the next pm state is set to ACTIVE. This way we can determine * whether the core is being turned on again or for the first time. */ if (pm_state_next_get(cpu_num)->state == PM_STATE_ACTIVE) { lpsram[1] = z_soc_mp_asm_entry; } else { lpsram[1] = dsp_restore_vector; } #else lpsram[1] = z_soc_mp_asm_entry; #endif /* Disable automatic power and clock gating for that CPU, so * it won't just go back to sleep. Note that after startup, * the cores are NOT power gated even if they're configured to * be, so by default a core will launch successfully but then * turn itself off when it gets to the WAITI instruction in * the idle thread. */ CAVS_SHIM.clkctl |= CAVS_CLKCTL_TCPLCG(cpu_num); CAVS_SHIM.pwrctl |= CAVS_PWRCTL_TCPDSPPG(cpu_num); /* We set the interrupt controller up already, but the ROM on * some platforms will mess it up. */ CAVS_INTCTRL[cpu_num].l2.clear = CAVS_L2_IDC; unsigned int num_cpus = arch_num_cpus(); for (int c = 0; c < num_cpus; c++) { IDC[c].busy_int |= IDC_CORE_MASK(num_cpus); } /* Send power-up message to the other core. Start address * gets passed via the IETC scratch register (only 30 bits * available, so it's sent shifted). The write to ITC * triggers the interrupt, so that comes last. */ uint32_t ietc = ((long)lpsram[1]) >> 2; IDC[curr_cpu].core[cpu_num].ietc = ietc; IDC[curr_cpu].core[cpu_num].itc = IDC_MSG_POWER_UP; } static void send_ipi(uint32_t cpu_bitmap) { uint32_t curr = arch_proc_id(); unsigned int num_cpus = arch_num_cpus(); for (int c = 0; c < num_cpus; c++) { if ((c != curr) && soc_cpus_active[c] && ((cpu_bitmap & BIT(c)) != 0)) { IDC[curr].core[c].itc = BIT(31); } } } void arch_sched_broadcast_ipi(void) { send_ipi(IPI_ALL_CPUS_MASK); } void arch_sched_directed_ipi(uint32_t cpu_bitmap) { send_ipi(cpu_bitmap); } void idc_isr(const void *param) { ARG_UNUSED(param); #ifdef CONFIG_SMP /* Right now this interrupt is only used for IPIs */ z_sched_ipi(); #endif /* ACK the interrupt to all the possible sources. This is a * level-sensitive interrupt triggered by a logical OR of each * of the ITC/TFC high bits, INCLUDING the one "from this * CPU". */ unsigned int num_cpus = arch_num_cpus(); for (int i = 0; i < num_cpus; i++) { IDC[arch_proc_id()].core[i].tfc = BIT(31); } } __imr void soc_mp_init(void) { IRQ_CONNECT(DT_IRQN(INTEL_ADSP_IDC_DTNODE), 0, idc_isr, NULL, 0); /* Every CPU should be able to receive an IDC interrupt from * every other CPU, but not to be back-interrupted when the * target core clears the busy bit. */ unsigned int num_cpus = arch_num_cpus(); for (int core = 0; core < num_cpus; core++) { IDC[core].busy_int |= IDC_CORE_MASK(num_cpus); IDC[core].done_int &= ~IDC_CORE_MASK(num_cpus); /* Also unmask the IDC interrupt for every core in the * L2 mask register. */ CAVS_INTCTRL[core].l2.clear = CAVS_L2_IDC; } /* Clear out any existing pending interrupts that might be present */ for (int i = 0; i < num_cpus; i++) { for (int j = 0; j < num_cpus; j++) { IDC[i].core[j].tfc = BIT(31); } } soc_cpus_active[0] = true; } int soc_adsp_halt_cpu(int id) { unsigned int irq_mask; if (id == 0 || id == arch_curr_cpu()->id) { return -EINVAL; } irq_mask = CAVS_L2_IDC; #ifdef CONFIG_INTEL_ADSP_TIMER /* * Mask timer interrupt for this CPU so it won't wake up * by itself once WFI (wait for interrupt) instruction * runs. */ irq_mask |= CAVS_L2_DWCT0; #endif CAVS_INTCTRL[id].l2.set = irq_mask; /* Stop sending IPIs to this core */ soc_cpus_active[id] = false; /* Turn off the "prevent power/clock gating" bits, enabling * low power idle */ CAVS_SHIM.pwrctl &= ~CAVS_PWRCTL_TCPDSPPG(id); CAVS_SHIM.clkctl &= ~CAVS_CLKCTL_TCPLCG(id); /* If possible, wait for the other CPU to reach an idle state * before returning. On older hardware this doesn't work * because power is controlled by the host, so synchronization * needs to be part of the application layer. */ while ((CAVS_SHIM.pwrsts & CAVS_PWRSTS_PDSPPGS(id))) { } return 0; } |