blob: 7287deebd1fc23304e272d875964055f47f892dd [file] [edit]
/*
* Copyright (c) 2025 Henrik Lindblom <henrik.lindblom@vaisala.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/spinlock.h>
#include <zephyr/drivers/cache.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/math_extras.h>
#include <stm32_ll_dcache.h>
#include <stm32_ll_icache.h>
LOG_MODULE_REGISTER(cache_stm32, CONFIG_CACHE_LOG_LEVEL);
#ifdef CONFIG_STM32_HAL2
/* On STM32 HAL2 this macro adds the dev (for example ICACHE) instance as the first argument,
* with or without a comma depending on number of arguments
*/
#define STM32_ARG(dev, ...) COND_CODE_1(IS_EMPTY(__VA_ARGS__), (dev), (dev, __VA_ARGS__))
#else /* CONFIG_STM32_HAL2 */
#define STM32_ARG(dev, ...) __VA_ARGS__
#endif /* CONFIG_STM32_HAL2 */
#ifdef CONFIG_DCACHE
static struct k_spinlock lock;
static int cache_data_manage_range(void *addr, size_t size, uint32_t command)
{
/*
* This is a simple approach to invalidate the range. The address might be in either DCACHE1
* or DCACHE2 (if present). The cache invalidation algorithm checks the TAG memory for the
* specified address range so there's little harm in just checking both caches.
*/
uint32_t start = (uint32_t)addr;
uint32_t end;
if (u32_add_overflow(start, size, &end)) {
return -EOVERFLOW;
}
LL_DCACHE_SetStartAddress(DCACHE1, start);
LL_DCACHE_SetEndAddress(DCACHE1, end);
LL_DCACHE_SetCommand(DCACHE1, command);
LL_DCACHE_StartCommand(DCACHE1);
#if defined(DCACHE2)
LL_DCACHE_SetStartAddress(DCACHE2, start);
LL_DCACHE_SetEndAddress(DCACHE2, end);
LL_DCACHE_SetCommand(DCACHE2, command);
LL_DCACHE_StartCommand(DCACHE2);
#endif
while (LL_DCACHE_IsActiveFlag_BUSYCMD(DCACHE1)) {
}
/* Clear CMDEND to avoid an extra interrupt if somebody enables them. */
LL_DCACHE_ClearFlag_CMDEND(DCACHE1);
#if defined(DCACHE2)
while (LL_DCACHE_IsActiveFlag_BUSYCMD(DCACHE2)) {
}
/* Clear CMDEND to avoid an extra interrupt if somebody enables them. */
LL_DCACHE_ClearFlag_CMDEND(DCACHE2);
#endif
return 0;
}
int cache_data_flush_range(void *addr, size_t size)
{
int ret = 0;
K_SPINLOCK(&lock) {
ret = cache_data_manage_range(addr, size, LL_DCACHE_COMMAND_CLEAN_BY_ADDR);
}
return ret;
}
int cache_data_invd_range(void *addr, size_t size)
{
int ret = 0;
K_SPINLOCK(&lock) {
ret = cache_data_manage_range(addr, size, LL_DCACHE_COMMAND_INVALIDATE_BY_ADDR);
}
return ret;
}
int cache_data_flush_and_invd_range(void *addr, size_t size)
{
int ret = 0;
K_SPINLOCK(&lock) {
ret = cache_data_manage_range(addr, size,
LL_DCACHE_COMMAND_CLEAN_INVALIDATE_BY_ADDR);
}
return ret;
}
int cache_data_flush_all(void)
{
return cache_data_flush_range(0, UINT32_MAX);
}
int cache_data_invd_all(void)
{
K_SPINLOCK(&lock) {
LL_DCACHE_Invalidate(DCACHE1);
#if defined(DCACHE2)
LL_DCACHE_Invalidate(DCACHE2);
#endif
while (LL_DCACHE_IsActiveFlag_BUSY(DCACHE1)) {
}
/* Clear BSYEND to avoid an extra interrupt if somebody enables them. */
LL_DCACHE_ClearFlag_BSYEND(DCACHE1);
#if defined(DCACHE2)
while (LL_DCACHE_IsActiveFlag_BUSY(DCACHE2)) {
}
/* Clear BSYEND to avoid an extra interrupt if somebody enables them. */
LL_DCACHE_ClearFlag_BSYEND(DCACHE2);
#endif
}
return 0;
}
int cache_data_flush_and_invd_all(void)
{
return cache_data_flush_and_invd_range(0, UINT32_MAX);
}
void cache_data_enable(void)
{
K_SPINLOCK(&lock) {
LL_DCACHE_Enable(DCACHE1);
#if defined(DCACHE2)
LL_DCACHE_Enable(DCACHE2);
#endif
}
}
void cache_data_disable(void)
{
K_SPINLOCK(&lock) {
/* Flush entire D$ before disabling */
(void)cache_data_manage_range(0, UINT32_MAX, LL_DCACHE_COMMAND_CLEAN_BY_ADDR);
LL_DCACHE_Disable(DCACHE1);
while (LL_DCACHE_IsEnabled(DCACHE1)) {
}
#if defined(DCACHE2)
LL_DCACHE_Disable(DCACHE2);
while (LL_DCACHE_IsEnabled(DCACHE2)) {
}
#endif
}
}
#endif /* CONFIG_DCACHE */
static inline void wait_for_icache(void)
{
while (LL_ICACHE_IsActiveFlag_BUSY(STM32_ARG(ICACHE))) {
}
/* Clear BSYEND to avoid an extra interrupt if somebody enables them. */
LL_ICACHE_ClearFlag_BSYEND(STM32_ARG(ICACHE));
}
void cache_instr_enable(void)
{
if (IS_ENABLED(CONFIG_CACHE_STM32_ICACHE_DIRECT_MAPPING)) {
LL_ICACHE_SetMode(STM32_ARG(ICACHE, LL_ICACHE_1WAY));
}
/*
* Need to wait until any pending cache invalidation operations finish. This is recommended
* in the reference manual to ensure execution timing determinism.
*/
wait_for_icache();
LL_ICACHE_Enable(STM32_ARG(ICACHE));
}
void cache_instr_disable(void)
{
LL_ICACHE_Disable(STM32_ARG(ICACHE));
while (LL_ICACHE_IsEnabled(STM32_ARG(ICACHE))) {
/**
* Wait until the ICACHE is disabled (CR.EN=0), at which point
* all requests bypass the cache and are forwarded directly
* from the ICACHE slave port to the ICACHE master port(s).
*
* The cache invalidation will start once disabled, but we allow
* it to proceed in the background since it doesn't need to be
* complete for requests to bypass the ICACHE.
*/
}
}
int cache_instr_flush_all(void)
{
return -ENOTSUP;
}
int cache_instr_invd_all(void)
{
LL_ICACHE_Invalidate(STM32_ARG(ICACHE));
return 0;
}
int cache_instr_flush_and_invd_all(void)
{
return -ENOTSUP;
}
int cache_instr_flush_range(void *addr, size_t size)
{
ARG_UNUSED(addr);
ARG_UNUSED(size);
return -ENOTSUP;
}
int cache_instr_invd_range(void *addr, size_t size)
{
ARG_UNUSED(addr);
ARG_UNUSED(size);
return -ENOTSUP;
}
int cache_instr_flush_and_invd_range(void *addr, size_t size)
{
ARG_UNUSED(addr);
ARG_UNUSED(size);
return -ENOTSUP;
}