blob: 258f9c58e235de2694a12462137e01e85f89141e [file] [log] [blame]
/* Copyright (c) 2022 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_INTEL_ADSP_HDA_H
#define ZEPHYR_INCLUDE_INTEL_ADSP_HDA_H
#include <zephyr/arch/xtensa/cache.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <adsp_shim.h>
#include <adsp_memory.h>
/**
* @brief HDA stream functionality for Intel ADSP
*
* Provides low level calls to support Intel ADSP HDA streams with
* minimal abstraction that allows testing the hardware
* and its demands separately from the intended DMA API
* usage. The only requirement is that you define the base
* addresses, the stream count, and the size of the ip blocks.
*/
/* The read/write positions are masked to 24 bits */
#define HDA_RWP_MASK 0x00FFFFFF
/* Buffers must be 128 byte aligned, this mask enforces that */
#define HDA_ALIGN_MASK 0xFFFFFF80
/* Calculate base address of the stream registers */
#define HDA_ADDR(base, regblock_size, stream) ((base) + (stream)*(regblock_size))
/* Gateway Control and Status Register */
#define DGCS(base, regblock_size, stream) \
((volatile uint32_t *)HDA_ADDR(base, regblock_size, stream))
#define DGCS_SCS BIT(31) /* Sample container size */
#define DGCS_GEN BIT(26) /* Gateway Enable */
#define DGCS_L1ETP BIT(25) /* L1 Enter Prevent */
#define DGCS_L1EXP BIT(25) /* L1 Exit Prevent */
#define DGCS_FWCB BIT(23) /* Firmware Control Buffer */
#define DGCS_GBUSY BIT(15) /* Gateway Busy */
#define DGCS_TE BIT(14) /* Transfer Error */
#define DGCS_BSC BIT(11) /* Buffer Segment Completion */
#define DGCS_BOR BIT(10) /* Buffer Overrun */
#define DGCS_BUR BIT(10) /* Buffer Underrun */
#define DGCS_BF BIT(9) /* Buffer Full */
#define DGCS_BNE BIT(8) /* Buffer Not Empty */
#define DGCS_FIFORDY BIT(5) /* Enable FIFO */
#define DGCS_BSCIE BIT(3) /* Buffer Segment Completion Interrupt Enable */
/* Gateway Buffer Base Address */
#define DGBBA(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x04))
/* Gateway Buffer Size */
#define DGBS(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x08))
/* Gateway Buffer Position Increment */
#define DGBFPI(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x0c))
/* Gateway Buffer Read Position */
#define DGBRP(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x10))
/* Gateway Buffer Write Position */
#define DGBWP(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x14))
/* Gateway Buffer Segment Position */
#define DGBSP(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x18))
/* Gateway Minimum Buffer Size */
#define DGMBS(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x1c))
/* Gateway Linear Link Position Increment */
#define DGLLPI(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x24))
/* Gateway Linear Position In Buffer Increment */
#define DGLPIBI(base, regblock_size, stream) \
((volatile uint32_t *)(HDA_ADDR(base, regblock_size, stream) + 0x28))
/**
* @brief Dump all the useful registers of an HDA stream to printk
*
* This can be invaluable when finding out why HDA isn't doing (or maybe is)
* doing what you want it to do. Macro so you get the file and line
* of the call site included.
*
* @param name String that contains a name of the hda stream (or anything really)
* @param base Base address of the IP register block
* @param regblock_size Register block size
* @param sid Stream ID
*/
#define intel_adsp_hda_dbg(name, base, regblock_size, sid) \
printk("%s:%u %s(%u:0x%p), dgcs: 0x%x, dgbba 0x%x, " \
"dgbs %u, dgbrp %u, dgbwp %u, dgbsp %u, " \
"dgmbs %u, dgbllpi 0x%x, dglpibi 0x%x\n", \
__FILE__, __LINE__, name, \
sid, DGCS(base, regblock_size, sid), \
*DGCS(base, regblock_size, sid), \
*DGBBA(base, regblock_size, sid), \
*DGBS(base, regblock_size, sid), \
*DGBRP(base, regblock_size, sid), \
*DGBWP(base, regblock_size, sid), \
*DGBSP(base, regblock_size, sid), \
*DGMBS(base, regblock_size, sid), \
*DGLLPI(base, regblock_size, sid), \
*DGLPIBI(base, regblock_size, sid))
/**
* @brief Initialize an HDA stream for use with the firmware
*
* @param hda Stream set to work with
* @param sid Stream ID
*/
static inline void intel_adsp_hda_init(uint32_t base, uint32_t regblock_size, uint32_t sid)
{
*DGCS(base, regblock_size, sid) |= DGCS_FWCB;
}
/**
* @brief Set the buffer, size, and element size for an HDA stream
*
* Sanity checks that the buffer address and size are valid and that the
* stream isn't enabled or busy.
*
* Prior to enabling an HDA stream to/from the host this is the minimum configuration
* that is required. It must be set *after* the host has configured its own buffers.
*
*
* @param hda Stream set to work with
* @param regblock_size Register block size
* @param sid Stream ID
* @param buf Buffer address to use for the shared FIFO. Must be in L2 and 128 byte aligned.
* @param buf_size Buffer size in bytes Must be 128 byte aligned
*
* @retval -EBUSY if the HDA stream is already enabled
* @retval -EINVAL if the buf is not in L2, buf isn't aligned on 128 byte boundaries
* @retval 0 on Success
*/
static inline int intel_adsp_hda_set_buffer(uint32_t base,
uint32_t regblock_size,
uint32_t sid,
uint8_t *buf,
uint32_t buf_size)
{
/* While we don't actually care if the pointer is in the cached
* region or not, we do need a consistent address space to check
* against for our assertion. This is cheap.
*/
uint32_t addr = (uint32_t)arch_xtensa_cached_ptr(buf);
uint32_t aligned_addr = addr & HDA_ALIGN_MASK;
uint32_t aligned_size = buf_size & HDA_ALIGN_MASK;
__ASSERT(aligned_addr == addr, "Buffer must be 128 byte aligned");
__ASSERT(aligned_addr >= L2_SRAM_BASE
&& aligned_addr < L2_SRAM_BASE + L2_SRAM_SIZE,
"Buffer must be in L2 address space");
__ASSERT(aligned_size == buf_size,
"Buffer must be 128 byte aligned in size");
__ASSERT(aligned_addr + aligned_size < L2_SRAM_BASE + L2_SRAM_SIZE,
"Buffer must end in L2 address space");
if (*DGCS(base, regblock_size, sid) & DGCS_GEN) {
return -EBUSY;
}
if (*DGCS(base, regblock_size, sid) & DGCS_GBUSY) {
return -EBUSY;
}
*DGBBA(base, regblock_size, sid) = aligned_addr;
*DGBS(base, regblock_size, sid) = aligned_size;
return 0;
}
/**
* @brief Get the buffer size
*
* @param hda Stream set to work with
* @param regblock_size Register block size
* @param sid Stream ID
*
* @retval buf_size Buffer size in bytes
*/
static inline uint32_t intel_adsp_hda_get_buffer_size(uint32_t base,
uint32_t regblock_size,
uint32_t sid)
{
return *DGBS(base, regblock_size, sid);
}
/**
* @brief Enable the stream
*
* @param hda HDA stream set
* @param regblock_size Register block size
* @param sid Stream ID
*/
static inline void intel_adsp_hda_enable(uint32_t base, uint32_t regblock_size, uint32_t sid)
{
*DGCS(base, regblock_size, sid) |= DGCS_GEN | DGCS_FIFORDY;
}
/**
* @brief Disable stream
*
* @param hda HDA stream set
* @param regblock_size Register block size
* @param sid Stream ID
*/
static inline void intel_adsp_hda_disable(uint32_t base, uint32_t regblock_size, uint32_t sid)
{
*DGCS(base, regblock_size, sid) &= ~(DGCS_GEN | DGCS_FIFORDY);
}
/**
* @brief Determine the number of unused bytes in the buffer
*
* This is useful primarily for a host in (dsp -> host) stream.
*
* @param base Base address of the IP register block
* @param regblock_size Register block size
* @param sid Stream ID within the register block
*
* @retval n Number of unused bytes
*/
static inline uint32_t intel_adsp_hda_unused(uint32_t base, uint32_t regblock_size, uint32_t sid)
{
uint32_t dgcs = *DGCS(base, regblock_size, sid);
uint32_t dgbs = *DGBS(base, regblock_size, sid);
/* Check if buffer is empty */
if ((dgcs & DGCS_BNE) == 0) {
return dgbs;
}
/* Check if the buffer is full */
if (dgcs & DGCS_BF) {
return 0;
}
int32_t rp = *DGBRP(base, regblock_size, sid);
int32_t wp = *DGBWP(base, regblock_size, sid);
int32_t size = rp - wp;
if (size <= 0) {
size += dgbs;
}
return size;
}
/**
* @brief Commit a number of bytes that have been transferred to/from host
*
* Writes the length to BFPI. For host transfers LLPI and LPIB are
* also written to with the given length.
*
* This then updates the read or write position depending on the direction.
*
* LPIBI writes here can be seen on the host side of the transfer in the
* matching LPIB register.
*
* LLPI seems to, from behavior, inform the hardware to actually read/write
* from the buffer. Without updating BFPI AND LLPI, the transfer doesn't
* happen in testing for host transfers.
*
* @param base Base address of the IP register block
* @param regblock_size Register block size
* @param sid Stream ID within the register block
* @param len Len to increment postion by
*/
static inline void intel_adsp_hda_host_commit(uint32_t base,
uint32_t regblock_size,
uint32_t sid,
uint32_t len)
{
*DGBFPI(base, regblock_size, sid) = len;
*DGLLPI(base, regblock_size, sid) = len;
*DGLPIBI(base, regblock_size, sid) = len;
}
/**
* @brief Commit a number of bytes that have been transferred to/from link
*
* Writes the length to BFPI.
*
* @seealso intel_adsp_hda_host_commit
*
* @param base Base address of the IP register block
* @param regblock_size Register block size
* @param sid Stream ID within the register block
* @param len Len to increment postion by
*/
static inline void intel_adsp_hda_link_commit(uint32_t base,
uint32_t regblock_size,
uint32_t sid,
uint32_t len)
{
*DGBFPI(base, regblock_size, sid) = len;
}
/**
* @brief Read the buffer full bit of the given stream.
*
* @param base Base address of the IP register block
* @param regblock_size Register block size
* @param sid Stream ID within the register block
*
* @retval true If the buffer full flag is set
*/
static inline bool intel_adsp_hda_buf_full(uint32_t base, uint32_t regblock_size, uint32_t sid)
{
return *DGCS(base, regblock_size, sid) & DGCS_BF;
}
/**
* @brief Check if the write and read position are equal
*
* For HDA this does not mean that the buffer is full or empty
* there are bit flags for those cases.
*
* Useful for waiting on the hardware to catch up to
* reads or writes (e.g. after a intel_adsp_hda_commit)
*
* @param base Base address of the IP register block
* @param regblock_size Register block size
* @param sid Stream D
*
* @retval true If the read and write positions are equal
*/
static inline bool intel_adsp_hda_wp_rp_eq(uint32_t base, uint32_t regblock_size, uint32_t sid)
{
return *DGBWP(base, regblock_size, sid) == *DGBRP(base, regblock_size, sid);
}
#endif /* ZEPHYR_INCLUDE_INTEL_ADSP_HDA_H */