blob: 3cf798978749ba641ee245600adf8126aebce8e4 [file] [log] [blame]
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/cache.h>
#include <hal/nrf_cache.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(cache_nrfx, CONFIG_CACHE_LOG_LEVEL);
#if !defined(NRF_ICACHE) && defined(NRF_CACHE)
#define NRF_ICACHE NRF_CACHE
#endif
#define CACHE_BUSY_RETRY_INTERVAL_US 10
enum k_nrf_cache_op {
/*
* Sequentially loop through all dirty lines and write those data units to
* memory.
*
* This is FLUSH in Zephyr nomenclature.
*/
K_NRF_CACHE_CLEAN,
/*
* Mark all lines as invalid, ignoring any dirty data.
*
* This is INVALIDATE in Zephyr nomenclature.
*/
K_NRF_CACHE_INVD,
/*
* Clean followed by invalidate
*
* This is FLUSH_AND_INVALIDATE in Zephyr nomenclature.
*/
K_NRF_CACHE_FLUSH,
};
static inline bool is_cache_busy(NRF_CACHE_Type *cache)
{
#if NRF_CACHE_HAS_STATUS
return nrf_cache_busy_check(cache);
#else
return false;
#endif
}
static inline void wait_for_cache(NRF_CACHE_Type *cache)
{
while (is_cache_busy(cache)) {
}
}
static inline int _cache_all(NRF_CACHE_Type *cache, enum k_nrf_cache_op op)
{
/*
* We really do not want to invalidate the whole cache.
*/
if (op == K_NRF_CACHE_INVD) {
return -ENOTSUP;
}
wait_for_cache(cache);
switch (op) {
#if NRF_CACHE_HAS_TASK_CLEAN
case K_NRF_CACHE_CLEAN:
nrf_cache_task_trigger(cache, NRF_CACHE_TASK_CLEANCACHE);
break;
#endif
case K_NRF_CACHE_INVD:
nrf_cache_task_trigger(cache, NRF_CACHE_TASK_INVALIDATECACHE);
break;
#if NRF_CACHE_HAS_TASK_FLUSH
case K_NRF_CACHE_FLUSH:
nrf_cache_task_trigger(cache, NRF_CACHE_TASK_FLUSHCACHE);
break;
#endif
default:
break;
}
wait_for_cache(cache);
return 0;
}
static inline void _cache_line(NRF_CACHE_Type *cache, enum k_nrf_cache_op op, uintptr_t line_addr)
{
do {
wait_for_cache(cache);
nrf_cache_lineaddr_set(cache, line_addr);
switch (op) {
#if NRF_CACHE_HAS_TASK_CLEAN
case K_NRF_CACHE_CLEAN:
nrf_cache_task_trigger(cache, NRF_CACHE_TASK_CLEANLINE);
break;
#endif
case K_NRF_CACHE_INVD:
nrf_cache_task_trigger(cache, NRF_CACHE_TASK_INVALIDATELINE);
break;
#if NRF_CACHE_HAS_TASK_FLUSH
case K_NRF_CACHE_FLUSH:
nrf_cache_task_trigger(cache, NRF_CACHE_TASK_FLUSHLINE);
break;
#endif
default:
break;
}
} while (nrf_cache_lineaddr_get(cache) != line_addr);
}
static inline int _cache_range(NRF_CACHE_Type *cache, enum k_nrf_cache_op op, void *addr,
size_t size)
{
uintptr_t line_addr = (uintptr_t)addr;
uintptr_t end_addr;
/* Some SOCs has a bug that requires to set 28th bit in the address on
* Trustzone secure builds.
*/
if (IS_ENABLED(CONFIG_CACHE_NRF_PATCH_LINEADDR) &&
!IS_ENABLED(CONFIG_TRUSTED_EXECUTION_NONSECURE)) {
line_addr |= BIT(28);
}
end_addr = line_addr + size;
/*
* Align address to line size
*/
line_addr &= ~(CONFIG_DCACHE_LINE_SIZE - 1);
do {
_cache_line(cache, op, line_addr);
line_addr += CONFIG_DCACHE_LINE_SIZE;
} while (line_addr < end_addr);
wait_for_cache(cache);
return 0;
}
static inline int _cache_checks(NRF_CACHE_Type *cache, enum k_nrf_cache_op op, void *addr,
size_t size, bool is_range)
{
/* Check if the cache is enabled */
if (!(cache->ENABLE & CACHE_ENABLE_ENABLE_Enabled)) {
return -EAGAIN;
}
if (!is_range) {
return _cache_all(cache, op);
}
/* Check for invalid address or size */
if ((!addr) || (!size)) {
return -EINVAL;
}
return _cache_range(cache, op, addr, size);
}
#if defined(NRF_DCACHE) && NRF_CACHE_HAS_TASKS
void cache_data_enable(void)
{
nrf_cache_enable(NRF_DCACHE);
}
int cache_data_flush_all(void)
{
#if NRF_CACHE_HAS_TASK_CLEAN
return _cache_checks(NRF_DCACHE, K_NRF_CACHE_CLEAN, NULL, 0, false);
#else
return -ENOTSUP;
#endif
}
void cache_data_disable(void)
{
if (nrf_cache_enable_check(NRF_DCACHE)) {
(void)cache_data_flush_all();
}
nrf_cache_disable(NRF_DCACHE);
}
int cache_data_invd_all(void)
{
return _cache_checks(NRF_DCACHE, K_NRF_CACHE_INVD, NULL, 0, false);
}
int cache_data_flush_and_invd_all(void)
{
#if NRF_CACHE_HAS_TASK_FLUSH
return _cache_checks(NRF_DCACHE, K_NRF_CACHE_FLUSH, NULL, 0, false);
#else
return -ENOTSUP;
#endif
}
int cache_data_flush_range(void *addr, size_t size)
{
#if NRF_CACHE_HAS_TASK_CLEAN
return _cache_checks(NRF_DCACHE, K_NRF_CACHE_CLEAN, addr, size, true);
#else
return -ENOTSUP;
#endif
}
int cache_data_invd_range(void *addr, size_t size)
{
return _cache_checks(NRF_DCACHE, K_NRF_CACHE_INVD, addr, size, true);
}
int cache_data_flush_and_invd_range(void *addr, size_t size)
{
#if NRF_CACHE_HAS_TASK_FLUSH
return _cache_checks(NRF_DCACHE, K_NRF_CACHE_FLUSH, addr, size, true);
#else
return -ENOTSUP;
#endif
}
#else
void cache_data_enable(void)
{
/* Nothing */
}
void cache_data_disable(void)
{
/* Nothing */
}
int cache_data_flush_all(void)
{
return -ENOTSUP;
}
int cache_data_invd_all(void)
{
return -ENOTSUP;
}
int cache_data_flush_and_invd_all(void)
{
return -ENOTSUP;
}
int cache_data_flush_range(void *addr, size_t size)
{
return -ENOTSUP;
}
int cache_data_invd_range(void *addr, size_t size)
{
return -ENOTSUP;
}
int cache_data_flush_and_invd_range(void *addr, size_t size)
{
return -ENOTSUP;
}
#endif /* NRF_DCACHE */
#if defined(NRF_ICACHE) && NRF_CACHE_HAS_TASKS
void cache_instr_enable(void)
{
nrf_cache_enable(NRF_ICACHE);
}
void cache_instr_disable(void)
{
nrf_cache_disable(NRF_ICACHE);
}
int cache_instr_flush_all(void)
{
#if NRF_CACHE_HAS_TASK_CLEAN
return _cache_checks(NRF_ICACHE, K_NRF_CACHE_CLEAN, NULL, 0, false);
#else
return -ENOTSUP;
#endif
}
int cache_instr_invd_all(void)
{
return _cache_checks(NRF_ICACHE, K_NRF_CACHE_INVD, NULL, 0, false);
}
int cache_instr_flush_and_invd_all(void)
{
#if NRF_CACHE_HAS_TASK_FLUSH
return _cache_checks(NRF_ICACHE, K_NRF_CACHE_FLUSH, NULL, 0, false);
#else
return -ENOTSUP;
#endif
}
int cache_instr_flush_range(void *addr, size_t size)
{
#if NRF_CACHE_HAS_TASK_CLEAN
return _cache_checks(NRF_ICACHE, K_NRF_CACHE_CLEAN, addr, size, true);
#else
return -ENOTSUP;
#endif
}
int cache_instr_invd_range(void *addr, size_t size)
{
return _cache_checks(NRF_ICACHE, K_NRF_CACHE_INVD, addr, size, true);
}
int cache_instr_flush_and_invd_range(void *addr, size_t size)
{
#if NRF_CACHE_HAS_TASK_FLUSH
return _cache_checks(NRF_ICACHE, K_NRF_CACHE_FLUSH, addr, size, true);
#else
return -ENOTSUP;
#endif
}
#else
void cache_instr_enable(void)
{
/* Nothing */
}
void cache_instr_disable(void)
{
/* Nothing */
}
int cache_instr_flush_all(void)
{
return -ENOTSUP;
}
int cache_instr_invd_all(void)
{
return -ENOTSUP;
}
int cache_instr_flush_and_invd_all(void)
{
return -ENOTSUP;
}
int cache_instr_flush_range(void *addr, size_t size)
{
return -ENOTSUP;
}
int cache_instr_invd_range(void *addr, size_t size)
{
return -ENOTSUP;
}
int cache_instr_flush_and_invd_range(void *addr, size_t size)
{
return -ENOTSUP;
}
#endif /* NRF_ICACHE */