soc: intel_adsp: New IDC driver
The original interface for the intra-DSP communication hardware on these devices was buried inside a Zephyr IPM implementation. Unfortunately IPM is a two-endpoint point-to-point communication layer, it can't represent the idea of devices with more than 2 cores. And our usage (to push a no-argument/no-response scheduler IPI) was sort of an abuse of that metaphor anyway. Add a new IDC interface at the SOC layer, borrowing the C struct convention already used for the DSP shim registers. Augment with extensive documentation, extracted via a ton of experimentation on cAVS 2.5 hardware. Note that this leaves the previous driver in place for the cavs_v15 and intel_s1000 devices. In principle they should use it too (the hardware registers are identical), but this hasn't been validated yet. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
parent
7fac06746a
commit
0228c05681
3 changed files with 191 additions and 42 deletions
84
soc/xtensa/intel_adsp/common/include/cavs-idc.h
Normal file
84
soc/xtensa/intel_adsp/common/include/cavs-idc.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#ifndef ZEPHYR_SOC_INTEL_ADSP_CAVS_IDC_H_
|
||||
#define ZEPHYR_SOC_INTEL_ADSP_CAVS_IDC_H_
|
||||
|
||||
/*
|
||||
* (I)ntra (D)SP (C)ommunication is the facility for sending
|
||||
* interrupts directly between DSP cores. The interface
|
||||
* is... somewhat needlessly complicated.
|
||||
*
|
||||
* Each core has a set of registers its is supposed to use, but all
|
||||
* registers seem to behave symmetrically regardless of which CPU does
|
||||
* the access.
|
||||
*
|
||||
* Each core has a "ITC" register associated with each other core in
|
||||
* the system (including itself). When the high bit becomes 1 in an
|
||||
* ITC register, an IDC interrupt is latched for the target core.
|
||||
* Data in other bits is stored but otherwise ignored, it's merely
|
||||
* data to be transmitted along with the interrupt.
|
||||
*
|
||||
* On the target core, there is a "TFC" register for each core that
|
||||
* reflects the same value written to ITC. In fact experimentally
|
||||
* these seem to be the same register at different addresses. When
|
||||
* the high bit of TFC is written with a 1, the value becomes ZERO,
|
||||
* indicating an acknowledgment of the interrupt. This action can
|
||||
* also latch an interrupt to send back to the originator if unmasked
|
||||
* (see below).
|
||||
*
|
||||
* (There is also an IETC/TEFC register pair that stores 30 bits of
|
||||
* data but otherwise has no hardware behavior. This is probably best
|
||||
* ignored for new protocols, as experimentally it seems to provide no
|
||||
* performance benefit vs. storing a message in RAM. The cAVS 1.5/1.8
|
||||
* ROM boot protocol uses it to store an entry point address, though.)
|
||||
*
|
||||
* So you can send a synchronous message from core "src" (where src is
|
||||
* the PRID of the CPU, equal to arch_curr_cpu()->id in Zephyr) to
|
||||
* core "dst" with:
|
||||
*
|
||||
* IDC[src].core[dst].itc = BIT(31) | message;
|
||||
* while (IDC[src].core[dst].itc & BIT(31)) {}
|
||||
*
|
||||
* And the other side (on cpu "dst", generally in the IDC interruupt
|
||||
* handler) will read and acknowledge those same values via:
|
||||
*
|
||||
* uint32_t my_msg = IDC[dst].core[src].tfc & 0x7fffffff;
|
||||
* IDC[dst].core[src].tfc = BIT(31); // clear high bit to signal completion
|
||||
*
|
||||
* And for clarity, at all times and for all cores and all pairs of src/dst:
|
||||
*
|
||||
* IDC[src].core[dst].itc == IDC[dst].core[src].tfc
|
||||
*
|
||||
* Finally note the two control registers at the end of each core's
|
||||
* register block, which store a bitmask of cores that are allowed to
|
||||
* send that core an interrupt via either ITC (set high "BUSY" bit) or
|
||||
* TFC (clear high "DONE" bit). This masking is in ADDITION to the
|
||||
* level 2 bit for IDC in the per-core INTCTRL DSP register AND the
|
||||
* Xtensa architectural INTENABLE SR. You must enable IDC interrupts
|
||||
* form core "src" to core "dst" with:
|
||||
*
|
||||
* IDC[dst].busy_int |= BIT(src) // Or disable with "&= ~BIT(src)" of course
|
||||
*/
|
||||
struct cavs_idc {
|
||||
struct {
|
||||
uint32_t tfc; /* (T)arget (F)rom (C)ore */
|
||||
uint32_t tefc; /* ^^ + (E)xtension */
|
||||
uint32_t itc; /* (I)nitiator (T)o (C)ore */
|
||||
uint32_t ietc; /* ^^ + (E)xtension */
|
||||
} core[4];
|
||||
uint32_t unused0[4];
|
||||
uint8_t busy_int; /* bitmask of cores that can IDC via ITC */
|
||||
uint8_t done_int; /* bitmask of cores that can IDC via TFC */
|
||||
uint8_t unused1;
|
||||
uint8_t unused2;
|
||||
uint32_t unused3[11];
|
||||
};
|
||||
|
||||
#define IDC ((volatile struct cavs_idc *)DT_REG_ADDR(DT_NODELABEL(idc)))
|
||||
|
||||
extern void soc_idc_init(void);
|
||||
|
||||
#endif /* ZEPHYR_SOC_INTEL_ADSP_CAVS_IDC_H_ */
|
Loading…
Add table
Add a link
Reference in a new issue