e4a125b6a4
This is the final step in making the `zephyr,memory-attr` property actually useful. The problem with the current implementation is that `zephyr,memory-attr` is an enum type, this is making very difficult to use that to actually describe the memory capabilities. The solution proposed in this PR is to use the `zephyr,memory-attr` property as an OR-ed bitmask of memory attributes. With the change proposed in this PR it is possible in the DeviceTree to mark the memory regions with a bitmask of attributes by using the `zephyr,memory-attr` property. This property and the related memory region can then be retrieved at run-time by leveraging a provided helper library or the usual DT helpers. The set of general attributes that can be specified in the property are defined and explained in `include/zephyr/dt-bindings/memory-attr/memory-attr.h` (the list can be extended when needed). For example, to mark a memory region in the DeviceTree as volatile, non-cacheable, out-of-order: mem: memory@10000000 { compatible = "mmio-sram"; reg = <0x10000000 0x1000>; zephyr,memory-attr = <( DT_MEM_VOLATILE | DT_MEM_NON_CACHEABLE | DT_MEM_OOO )>; }; The `zephyr,memory-attr` property can also be used to set architecture-specific custom attributes that can be interpreted at run time. This is leveraged, among other things, to create MPU regions out of DeviceTree defined memory regions on ARM, for example: mem: memory@10000000 { compatible = "mmio-sram"; reg = <0x10000000 0x1000>; zephyr,memory-region = "NOCACHE_REGION"; zephyr,memory-attr = <( DT_ARM_MPU(ATTR_MPU_RAM_NOCACHE) )>; }; See `include/zephyr/dt-bindings/memory-attr/memory-attr-mpu.h` to see how an architecture can define its own special memory attributes (in this case ARM MPU). The property can also be used to set custom software-specific attributes. For example we can think of marking a memory region as available to be used for memory allocation (not yet implemented): mem: memory@10000000 { compatible = "mmio-sram"; reg = <0x10000000 0x1000>; zephyr,memory-attr = <( DT_MEM_NON_CACHEABLE | DT_MEM_SW_ALLOCATABLE )>; }; Or maybe we can leverage the property to specify some alignment requirements for the region: mem: memory@10000000 { compatible = "mmio-sram"; reg = <0x10000000 0x1000>; zephyr,memory-attr = <( DT_MEM_CACHEABLE | DT_MEM_SW_ALIGN(32) )>; }; The conventional and recommended way to deal and manage with memory regions marked with attributes is by using the provided `mem-attr` helper library by enabling `CONFIG_MEM_ATTR` (or by using the usual DT helpers). When this option is enabled the list of memory regions and their attributes are compiled in a user-accessible array and a set of functions is made available that can be used to query, probe and act on regions and attributes, see `include/zephyr/mem_mgmt/mem_attr.h` Note that the `zephyr,memory-attr` property is only a descriptive property of the capabilities of the associated memory region, but it does not result in any actual setting for the memory to be set. The user, code or subsystem willing to use this information to do some work (for example creating an MPU region out of the property) must use either the provided `mem-attr` library or the usual DeviceTree helpers to perform the required work / setting. Signed-off-by: Carlo Caione <ccaione@baylibre.com>
922 lines
23 KiB
C
922 lines
23 KiB
C
/*
|
|
* Copyright (c) 2023 Jeroen van Dooren, Nobleo Technology
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @brief Common part of BDMA drivers for stm32.
|
|
*/
|
|
|
|
#include "dma_stm32_bdma.h"
|
|
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/dma/dma_stm32.h>
|
|
#include <zephyr/dt-bindings/memory-attr/memory-attr-arm.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
LOG_MODULE_REGISTER(dma_stm32_bdma, CONFIG_DMA_LOG_LEVEL);
|
|
|
|
#define DT_DRV_COMPAT st_stm32_bdma
|
|
|
|
#define BDMA_STM32_0_CHANNEL_COUNT 8
|
|
|
|
static const uint32_t table_m_size[] = {
|
|
LL_BDMA_MDATAALIGN_BYTE,
|
|
LL_BDMA_MDATAALIGN_HALFWORD,
|
|
LL_BDMA_MDATAALIGN_WORD,
|
|
};
|
|
|
|
static const uint32_t table_p_size[] = {
|
|
LL_BDMA_PDATAALIGN_BYTE,
|
|
LL_BDMA_PDATAALIGN_HALFWORD,
|
|
LL_BDMA_PDATAALIGN_WORD,
|
|
};
|
|
|
|
uint32_t bdma_stm32_id_to_channel(uint32_t id)
|
|
{
|
|
static const uint32_t channel_nr[] = {
|
|
LL_BDMA_CHANNEL_0,
|
|
LL_BDMA_CHANNEL_1,
|
|
LL_BDMA_CHANNEL_2,
|
|
LL_BDMA_CHANNEL_3,
|
|
LL_BDMA_CHANNEL_4,
|
|
LL_BDMA_CHANNEL_5,
|
|
LL_BDMA_CHANNEL_6,
|
|
LL_BDMA_CHANNEL_7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(channel_nr));
|
|
|
|
return channel_nr[id];
|
|
}
|
|
|
|
#if !defined(CONFIG_DMAMUX_STM32)
|
|
uint32_t bdma_stm32_slot_to_channel(uint32_t slot)
|
|
{
|
|
static const uint32_t channel_nr[] = {
|
|
LL_BDMA_CHANNEL_0,
|
|
LL_BDMA_CHANNEL_1,
|
|
LL_BDMA_CHANNEL_2,
|
|
LL_BDMA_CHANNEL_3,
|
|
LL_BDMA_CHANNEL_4,
|
|
LL_BDMA_CHANNEL_5,
|
|
LL_BDMA_CHANNEL_6,
|
|
LL_BDMA_CHANNEL_7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(slot < ARRAY_SIZE(channel_nr));
|
|
|
|
return channel_nr[slot];
|
|
}
|
|
#endif
|
|
|
|
void bdma_stm32_clear_ht(BDMA_TypeDef *DMAx, uint32_t id)
|
|
{
|
|
static const bdma_stm32_clear_flag_func func[] = {
|
|
LL_BDMA_ClearFlag_HT0,
|
|
LL_BDMA_ClearFlag_HT1,
|
|
LL_BDMA_ClearFlag_HT2,
|
|
LL_BDMA_ClearFlag_HT3,
|
|
LL_BDMA_ClearFlag_HT4,
|
|
LL_BDMA_ClearFlag_HT5,
|
|
LL_BDMA_ClearFlag_HT6,
|
|
LL_BDMA_ClearFlag_HT7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(func));
|
|
|
|
func[id](DMAx);
|
|
}
|
|
|
|
void bdma_stm32_clear_tc(BDMA_TypeDef *DMAx, uint32_t id)
|
|
{
|
|
static const bdma_stm32_clear_flag_func func[] = {
|
|
LL_BDMA_ClearFlag_TC0,
|
|
LL_BDMA_ClearFlag_TC1,
|
|
LL_BDMA_ClearFlag_TC2,
|
|
LL_BDMA_ClearFlag_TC3,
|
|
LL_BDMA_ClearFlag_TC4,
|
|
LL_BDMA_ClearFlag_TC5,
|
|
LL_BDMA_ClearFlag_TC6,
|
|
LL_BDMA_ClearFlag_TC7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(func));
|
|
|
|
func[id](DMAx);
|
|
}
|
|
|
|
bool bdma_stm32_is_ht_active(BDMA_TypeDef *DMAx, uint32_t id)
|
|
{
|
|
static const bdma_stm32_check_flag_func func[] = {
|
|
LL_BDMA_IsActiveFlag_HT0,
|
|
LL_BDMA_IsActiveFlag_HT1,
|
|
LL_BDMA_IsActiveFlag_HT2,
|
|
LL_BDMA_IsActiveFlag_HT3,
|
|
LL_BDMA_IsActiveFlag_HT4,
|
|
LL_BDMA_IsActiveFlag_HT5,
|
|
LL_BDMA_IsActiveFlag_HT6,
|
|
LL_BDMA_IsActiveFlag_HT7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(func));
|
|
|
|
return func[id](DMAx);
|
|
}
|
|
|
|
bool bdma_stm32_is_tc_active(BDMA_TypeDef *DMAx, uint32_t id)
|
|
{
|
|
static const bdma_stm32_check_flag_func func[] = {
|
|
LL_BDMA_IsActiveFlag_TC0,
|
|
LL_BDMA_IsActiveFlag_TC1,
|
|
LL_BDMA_IsActiveFlag_TC2,
|
|
LL_BDMA_IsActiveFlag_TC3,
|
|
LL_BDMA_IsActiveFlag_TC4,
|
|
LL_BDMA_IsActiveFlag_TC5,
|
|
LL_BDMA_IsActiveFlag_TC6,
|
|
LL_BDMA_IsActiveFlag_TC7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(func));
|
|
|
|
return func[id](DMAx);
|
|
}
|
|
|
|
void bdma_stm32_clear_te(BDMA_TypeDef *DMAx, uint32_t id)
|
|
{
|
|
static const bdma_stm32_clear_flag_func func[] = {
|
|
LL_BDMA_ClearFlag_TE0,
|
|
LL_BDMA_ClearFlag_TE1,
|
|
LL_BDMA_ClearFlag_TE2,
|
|
LL_BDMA_ClearFlag_TE3,
|
|
LL_BDMA_ClearFlag_TE4,
|
|
LL_BDMA_ClearFlag_TE5,
|
|
LL_BDMA_ClearFlag_TE6,
|
|
LL_BDMA_ClearFlag_TE7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(func));
|
|
|
|
func[id](DMAx);
|
|
}
|
|
|
|
void bdma_stm32_clear_gi(BDMA_TypeDef *DMAx, uint32_t id)
|
|
{
|
|
static const bdma_stm32_clear_flag_func func[] = {
|
|
LL_BDMA_ClearFlag_GI0,
|
|
LL_BDMA_ClearFlag_GI1,
|
|
LL_BDMA_ClearFlag_GI2,
|
|
LL_BDMA_ClearFlag_GI3,
|
|
LL_BDMA_ClearFlag_GI4,
|
|
LL_BDMA_ClearFlag_GI5,
|
|
LL_BDMA_ClearFlag_GI6,
|
|
LL_BDMA_ClearFlag_GI7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(func));
|
|
|
|
func[id](DMAx);
|
|
}
|
|
|
|
bool bdma_stm32_is_te_active(BDMA_TypeDef *DMAx, uint32_t id)
|
|
{
|
|
static const bdma_stm32_check_flag_func func[] = {
|
|
LL_BDMA_IsActiveFlag_TE0,
|
|
LL_BDMA_IsActiveFlag_TE1,
|
|
LL_BDMA_IsActiveFlag_TE2,
|
|
LL_BDMA_IsActiveFlag_TE3,
|
|
LL_BDMA_IsActiveFlag_TE4,
|
|
LL_BDMA_IsActiveFlag_TE5,
|
|
LL_BDMA_IsActiveFlag_TE6,
|
|
LL_BDMA_IsActiveFlag_TE7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(func));
|
|
|
|
return func[id](DMAx);
|
|
}
|
|
|
|
bool bdma_stm32_is_gi_active(BDMA_TypeDef *DMAx, uint32_t id)
|
|
{
|
|
static const bdma_stm32_check_flag_func func[] = {
|
|
LL_BDMA_IsActiveFlag_GI0,
|
|
LL_BDMA_IsActiveFlag_GI1,
|
|
LL_BDMA_IsActiveFlag_GI2,
|
|
LL_BDMA_IsActiveFlag_GI3,
|
|
LL_BDMA_IsActiveFlag_GI4,
|
|
LL_BDMA_IsActiveFlag_GI5,
|
|
LL_BDMA_IsActiveFlag_GI6,
|
|
LL_BDMA_IsActiveFlag_GI7,
|
|
};
|
|
|
|
__ASSERT_NO_MSG(id < ARRAY_SIZE(func));
|
|
|
|
return func[id](DMAx);
|
|
}
|
|
|
|
void stm32_bdma_dump_channel_irq(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
LOG_INF("te: %d, ht: %d, tc: %d, gi: %d",
|
|
bdma_stm32_is_te_active(dma, id),
|
|
bdma_stm32_is_ht_active(dma, id),
|
|
bdma_stm32_is_tc_active(dma, id),
|
|
bdma_stm32_is_gi_active(dma, id));
|
|
}
|
|
|
|
inline bool stm32_bdma_is_tc_irq_active(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
return LL_BDMA_IsEnabledIT_TC(dma, bdma_stm32_id_to_channel(id)) &&
|
|
bdma_stm32_is_tc_active(dma, id);
|
|
}
|
|
|
|
inline bool stm32_bdma_is_ht_irq_active(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
return LL_BDMA_IsEnabledIT_HT(dma, bdma_stm32_id_to_channel(id)) &&
|
|
bdma_stm32_is_ht_active(dma, id);
|
|
}
|
|
|
|
static inline bool stm32_bdma_is_te_irq_active(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
return LL_BDMA_IsEnabledIT_TE(dma, bdma_stm32_id_to_channel(id)) &&
|
|
bdma_stm32_is_te_active(dma, id);
|
|
}
|
|
|
|
bool stm32_bdma_is_irq_active(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
return stm32_bdma_is_tc_irq_active(dma, id) ||
|
|
stm32_bdma_is_ht_irq_active(dma, id) ||
|
|
stm32_bdma_is_te_irq_active(dma, id);
|
|
}
|
|
|
|
void stm32_bdma_clear_channel_irq(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
bdma_stm32_clear_gi(dma, id);
|
|
bdma_stm32_clear_tc(dma, id);
|
|
bdma_stm32_clear_ht(dma, id);
|
|
bdma_stm32_clear_te(dma, id);
|
|
}
|
|
|
|
bool stm32_bdma_is_enabled_channel(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
if (LL_BDMA_IsEnabledChannel(dma, bdma_stm32_id_to_channel(id)) == 1) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int stm32_bdma_disable_channel(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
LL_BDMA_DisableChannel(dma, bdma_stm32_id_to_channel(id));
|
|
|
|
if (!LL_BDMA_IsEnabledChannel(dma, bdma_stm32_id_to_channel(id))) {
|
|
return 0;
|
|
}
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
void stm32_bdma_enable_channel(BDMA_TypeDef *dma, uint32_t id)
|
|
{
|
|
LL_BDMA_EnableChannel(dma, bdma_stm32_id_to_channel(id));
|
|
}
|
|
|
|
static void bdma_stm32_dump_channel_irq(const struct device *dev, uint32_t id)
|
|
{
|
|
const struct bdma_stm32_config *config = dev->config;
|
|
BDMA_TypeDef *dma = (BDMA_TypeDef *)(config->base);
|
|
|
|
stm32_bdma_dump_channel_irq(dma, id);
|
|
}
|
|
|
|
static void bdma_stm32_clear_channel_irq(const struct device *dev, uint32_t id)
|
|
{
|
|
const struct bdma_stm32_config *config = dev->config;
|
|
BDMA_TypeDef *dma = (BDMA_TypeDef *)(config->base);
|
|
|
|
bdma_stm32_clear_tc(dma, id);
|
|
bdma_stm32_clear_ht(dma, id);
|
|
stm32_bdma_clear_channel_irq(dma, id);
|
|
}
|
|
|
|
static void bdma_stm32_irq_handler(const struct device *dev, uint32_t id)
|
|
{
|
|
const struct bdma_stm32_config *config = dev->config;
|
|
BDMA_TypeDef *dma = (BDMA_TypeDef *)(config->base);
|
|
struct bdma_stm32_channel *channel;
|
|
uint32_t callback_arg;
|
|
|
|
__ASSERT_NO_MSG(id < config->max_channels);
|
|
|
|
channel = &config->channels[id];
|
|
|
|
/* The busy channel is pertinent if not overridden by the HAL */
|
|
if ((channel->hal_override != true) && (channel->busy == false)) {
|
|
/*
|
|
* When DMA channel is not overridden by HAL,
|
|
* ignore irq if the channel is not busy anymore
|
|
*/
|
|
bdma_stm32_clear_channel_irq(dev, id);
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_DMAMUX_STM32
|
|
callback_arg = channel->mux_channel;
|
|
#else
|
|
callback_arg = id;
|
|
#endif /* CONFIG_DMAMUX_STM32 */
|
|
|
|
if (!IS_ENABLED(CONFIG_DMAMUX_STM32)) {
|
|
channel->busy = false;
|
|
}
|
|
|
|
/* the dma channel id is in range from 0..<dma-requests> */
|
|
if (stm32_bdma_is_ht_irq_active(dma, id)) {
|
|
/* Let HAL DMA handle flags on its own */
|
|
if (!channel->hal_override) {
|
|
bdma_stm32_clear_ht(dma, id);
|
|
}
|
|
channel->bdma_callback(dev, channel->user_data, callback_arg, 0);
|
|
} else if (stm32_bdma_is_tc_irq_active(dma, id)) {
|
|
#ifdef CONFIG_DMAMUX_STM32
|
|
channel->busy = false;
|
|
#endif
|
|
/* Let HAL DMA handle flags on its own */
|
|
if (!channel->hal_override) {
|
|
bdma_stm32_clear_tc(dma, id);
|
|
}
|
|
channel->bdma_callback(dev, channel->user_data, callback_arg, 0);
|
|
} else {
|
|
LOG_ERR("Transfer Error.");
|
|
bdma_stm32_dump_channel_irq(dev, id);
|
|
bdma_stm32_clear_channel_irq(dev, id);
|
|
channel->bdma_callback(dev, channel->user_data,
|
|
callback_arg, -EIO);
|
|
}
|
|
}
|
|
|
|
static int bdma_stm32_get_priority(uint8_t priority, uint32_t *ll_priority)
|
|
{
|
|
switch (priority) {
|
|
case 0x0:
|
|
*ll_priority = LL_BDMA_PRIORITY_LOW;
|
|
break;
|
|
case 0x1:
|
|
*ll_priority = LL_BDMA_PRIORITY_MEDIUM;
|
|
break;
|
|
case 0x2:
|
|
*ll_priority = LL_BDMA_PRIORITY_HIGH;
|
|
break;
|
|
case 0x3:
|
|
*ll_priority = LL_BDMA_PRIORITY_VERYHIGH;
|
|
break;
|
|
default:
|
|
LOG_ERR("Priority error. %d", priority);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bdma_stm32_get_direction(enum dma_channel_direction direction,
|
|
uint32_t *ll_direction)
|
|
{
|
|
switch (direction) {
|
|
case MEMORY_TO_MEMORY:
|
|
*ll_direction = LL_BDMA_DIRECTION_MEMORY_TO_MEMORY;
|
|
break;
|
|
case MEMORY_TO_PERIPHERAL:
|
|
*ll_direction = LL_BDMA_DIRECTION_MEMORY_TO_PERIPH;
|
|
break;
|
|
case PERIPHERAL_TO_MEMORY:
|
|
*ll_direction = LL_BDMA_DIRECTION_PERIPH_TO_MEMORY;
|
|
break;
|
|
default:
|
|
LOG_ERR("Direction error. %d", direction);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bdma_stm32_get_memory_increment(enum dma_addr_adj increment,
|
|
uint32_t *ll_increment)
|
|
{
|
|
switch (increment) {
|
|
case DMA_ADDR_ADJ_INCREMENT:
|
|
*ll_increment = LL_BDMA_MEMORY_INCREMENT;
|
|
break;
|
|
case DMA_ADDR_ADJ_NO_CHANGE:
|
|
*ll_increment = LL_BDMA_MEMORY_NOINCREMENT;
|
|
break;
|
|
case DMA_ADDR_ADJ_DECREMENT:
|
|
return -ENOTSUP;
|
|
default:
|
|
LOG_ERR("Memory increment error. %d", increment);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bdma_stm32_get_periph_increment(enum dma_addr_adj increment,
|
|
uint32_t *ll_increment)
|
|
{
|
|
switch (increment) {
|
|
case DMA_ADDR_ADJ_INCREMENT:
|
|
*ll_increment = LL_BDMA_PERIPH_INCREMENT;
|
|
break;
|
|
case DMA_ADDR_ADJ_NO_CHANGE:
|
|
*ll_increment = LL_BDMA_PERIPH_NOINCREMENT;
|
|
break;
|
|
case DMA_ADDR_ADJ_DECREMENT:
|
|
return -ENOTSUP;
|
|
default:
|
|
LOG_ERR("Periph increment error. %d", increment);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bdma_stm32_disable_channel(BDMA_TypeDef *bdma, uint32_t id)
|
|
{
|
|
int count = 0;
|
|
|
|
for (;;) {
|
|
if (stm32_bdma_disable_channel(bdma, id) == 0) {
|
|
return 0;
|
|
}
|
|
/* After trying for 5 seconds, give up */
|
|
if (count++ > (5 * 1000)) {
|
|
return -EBUSY;
|
|
}
|
|
k_sleep(K_MSEC(1));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool bdma_stm32_is_valid_memory_address(const uint32_t address, const uint32_t size)
|
|
{
|
|
/* The BDMA can only access memory addresses in SRAM4 */
|
|
|
|
const uint32_t sram4_start = DT_REG_ADDR(DT_NODELABEL(sram4));
|
|
const uint32_t sram4_end = sram4_start + DT_REG_SIZE(DT_NODELABEL(sram4));
|
|
|
|
if (address < sram4_start) {
|
|
return false;
|
|
}
|
|
|
|
if (address + size > sram4_end) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BDMA_STM32_EXPORT_API int bdma_stm32_configure(const struct device *dev,
|
|
uint32_t id,
|
|
struct dma_config *config)
|
|
{
|
|
const struct bdma_stm32_config *dev_config = dev->config;
|
|
struct bdma_stm32_channel *channel =
|
|
&dev_config->channels[id];
|
|
BDMA_TypeDef *bdma = (BDMA_TypeDef *)dev_config->base;
|
|
LL_BDMA_InitTypeDef BDMA_InitStruct;
|
|
int index;
|
|
int ret;
|
|
|
|
LL_BDMA_StructInit(&BDMA_InitStruct);
|
|
|
|
if (id >= dev_config->max_channels) {
|
|
LOG_ERR("cannot configure the bdma channel %d.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (channel->busy) {
|
|
LOG_ERR("bdma channel %d is busy.", id);
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (bdma_stm32_disable_channel(bdma, id) != 0) {
|
|
LOG_ERR("could not disable bdma channel %d.", id);
|
|
return -EBUSY;
|
|
}
|
|
|
|
bdma_stm32_clear_channel_irq(dev, id);
|
|
|
|
if (config->head_block->block_size > BDMA_STM32_MAX_DATA_ITEMS) {
|
|
LOG_ERR("Data size too big: %d\n",
|
|
config->head_block->block_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((config->channel_direction == MEMORY_TO_MEMORY) &&
|
|
(!dev_config->support_m2m)) {
|
|
LOG_ERR("Memcopy not supported for device %s",
|
|
dev->name);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* support only the same data width for source and dest */
|
|
if (config->dest_data_size != config->source_data_size) {
|
|
LOG_ERR("source and dest data size differ.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (config->source_data_size != 4U &&
|
|
config->source_data_size != 2U &&
|
|
config->source_data_size != 1U) {
|
|
LOG_ERR("source and dest unit size error, %d",
|
|
config->source_data_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* STM32's circular mode will auto reset both source address
|
|
* counter and destination address counter.
|
|
*/
|
|
if (config->head_block->source_reload_en !=
|
|
config->head_block->dest_reload_en) {
|
|
LOG_ERR("source_reload_en and dest_reload_en must "
|
|
"be the same.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
channel->busy = true;
|
|
channel->bdma_callback = config->dma_callback;
|
|
channel->direction = config->channel_direction;
|
|
channel->user_data = config->user_data;
|
|
channel->src_size = config->source_data_size;
|
|
channel->dst_size = config->dest_data_size;
|
|
|
|
/* check dest or source memory address, warn if 0 */
|
|
if (config->head_block->source_address == 0) {
|
|
LOG_WRN("source_buffer address is null.");
|
|
}
|
|
|
|
if (config->head_block->dest_address == 0) {
|
|
LOG_WRN("dest_buffer address is null.");
|
|
}
|
|
|
|
/* ensure all memory addresses are in SRAM4 */
|
|
if (channel->direction == MEMORY_TO_PERIPHERAL || channel->direction == MEMORY_TO_MEMORY) {
|
|
if (!bdma_stm32_is_valid_memory_address(config->head_block->source_address,
|
|
config->head_block->block_size)) {
|
|
LOG_ERR("invalid source address");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (channel->direction == PERIPHERAL_TO_MEMORY || channel->direction == MEMORY_TO_MEMORY) {
|
|
if (!bdma_stm32_is_valid_memory_address(config->head_block->dest_address,
|
|
config->head_block->block_size)) {
|
|
LOG_ERR("invalid destination address");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (channel->direction == MEMORY_TO_PERIPHERAL) {
|
|
BDMA_InitStruct.MemoryOrM2MDstAddress =
|
|
config->head_block->source_address;
|
|
BDMA_InitStruct.PeriphOrM2MSrcAddress =
|
|
config->head_block->dest_address;
|
|
} else {
|
|
BDMA_InitStruct.PeriphOrM2MSrcAddress =
|
|
config->head_block->source_address;
|
|
BDMA_InitStruct.MemoryOrM2MDstAddress =
|
|
config->head_block->dest_address;
|
|
}
|
|
|
|
uint16_t memory_addr_adj = 0, periph_addr_adj = 0;
|
|
|
|
ret = bdma_stm32_get_priority(config->channel_priority,
|
|
&BDMA_InitStruct.Priority);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = bdma_stm32_get_direction(config->channel_direction,
|
|
&BDMA_InitStruct.Direction);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
switch (config->channel_direction) {
|
|
case MEMORY_TO_MEMORY:
|
|
case PERIPHERAL_TO_MEMORY:
|
|
memory_addr_adj = config->head_block->dest_addr_adj;
|
|
periph_addr_adj = config->head_block->source_addr_adj;
|
|
break;
|
|
case MEMORY_TO_PERIPHERAL:
|
|
memory_addr_adj = config->head_block->source_addr_adj;
|
|
periph_addr_adj = config->head_block->dest_addr_adj;
|
|
break;
|
|
/* Direction has been asserted in bdma_stm32_get_direction. */
|
|
default:
|
|
LOG_ERR("Channel direction error (%d).",
|
|
config->channel_direction);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = bdma_stm32_get_memory_increment(memory_addr_adj,
|
|
&BDMA_InitStruct.MemoryOrM2MDstIncMode);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
ret = bdma_stm32_get_periph_increment(periph_addr_adj,
|
|
&BDMA_InitStruct.PeriphOrM2MSrcIncMode);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (config->head_block->source_reload_en) {
|
|
BDMA_InitStruct.Mode = LL_BDMA_MODE_CIRCULAR;
|
|
} else {
|
|
BDMA_InitStruct.Mode = LL_BDMA_MODE_NORMAL;
|
|
}
|
|
|
|
channel->source_periph = (channel->direction == PERIPHERAL_TO_MEMORY);
|
|
|
|
/* set the data width, when source_data_size equals dest_data_size */
|
|
index = find_lsb_set(config->source_data_size) - 1;
|
|
BDMA_InitStruct.PeriphOrM2MSrcDataSize = table_p_size[index];
|
|
index = find_lsb_set(config->dest_data_size) - 1;
|
|
BDMA_InitStruct.MemoryOrM2MDstDataSize = table_m_size[index];
|
|
|
|
if (channel->source_periph) {
|
|
BDMA_InitStruct.NbData = config->head_block->block_size /
|
|
config->source_data_size;
|
|
} else {
|
|
BDMA_InitStruct.NbData = config->head_block->block_size /
|
|
config->dest_data_size;
|
|
}
|
|
|
|
#if defined(CONFIG_DMAMUX_STM32)
|
|
/*
|
|
* with bdma mux,
|
|
* the request ID is stored in the dma_slot
|
|
*/
|
|
BDMA_InitStruct.PeriphRequest = config->dma_slot;
|
|
#endif
|
|
LL_BDMA_Init(bdma, bdma_stm32_id_to_channel(id), &BDMA_InitStruct);
|
|
|
|
LL_BDMA_EnableIT_TC(bdma, bdma_stm32_id_to_channel(id));
|
|
|
|
/* Enable Half-Transfer irq if circular mode is enabled */
|
|
if (config->head_block->source_reload_en) {
|
|
LL_BDMA_EnableIT_HT(bdma, bdma_stm32_id_to_channel(id));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
BDMA_STM32_EXPORT_API int bdma_stm32_reload(const struct device *dev, uint32_t id,
|
|
uint32_t src, uint32_t dst,
|
|
size_t size)
|
|
{
|
|
const struct bdma_stm32_config *config = dev->config;
|
|
BDMA_TypeDef *bdma = (BDMA_TypeDef *)(config->base);
|
|
struct bdma_stm32_channel *channel;
|
|
|
|
if (id >= config->max_channels) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
channel = &config->channels[id];
|
|
|
|
if (bdma_stm32_disable_channel(bdma, id) != 0) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
switch (channel->direction) {
|
|
case MEMORY_TO_PERIPHERAL:
|
|
LL_BDMA_SetMemoryAddress(bdma, bdma_stm32_id_to_channel(id), src);
|
|
LL_BDMA_SetPeriphAddress(bdma, bdma_stm32_id_to_channel(id), dst);
|
|
break;
|
|
case MEMORY_TO_MEMORY:
|
|
case PERIPHERAL_TO_MEMORY:
|
|
LL_BDMA_SetPeriphAddress(bdma, bdma_stm32_id_to_channel(id), src);
|
|
LL_BDMA_SetMemoryAddress(bdma, bdma_stm32_id_to_channel(id), dst);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (channel->source_periph) {
|
|
LL_BDMA_SetDataLength(bdma, bdma_stm32_id_to_channel(id),
|
|
size / channel->src_size);
|
|
} else {
|
|
LL_BDMA_SetDataLength(bdma, bdma_stm32_id_to_channel(id),
|
|
size / channel->dst_size);
|
|
}
|
|
|
|
/* When reloading the dma, the channel is busy again before enabling */
|
|
channel->busy = true;
|
|
|
|
stm32_bdma_enable_channel(bdma, id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
BDMA_STM32_EXPORT_API int bdma_stm32_start(const struct device *dev, uint32_t id)
|
|
{
|
|
const struct bdma_stm32_config *config = dev->config;
|
|
BDMA_TypeDef *bdma = (BDMA_TypeDef *)(config->base);
|
|
struct bdma_stm32_channel *channel;
|
|
|
|
/* Only M2P or M2M mode can be started manually. */
|
|
if (id >= config->max_channels) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Repeated start : return now if channel is already started */
|
|
if (stm32_bdma_is_enabled_channel(bdma, id)) {
|
|
return 0;
|
|
}
|
|
|
|
/* When starting the dma, the channel is busy before enabling */
|
|
channel = &config->channels[id];
|
|
channel->busy = true;
|
|
|
|
bdma_stm32_clear_channel_irq(dev, id);
|
|
stm32_bdma_enable_channel(bdma, id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
BDMA_STM32_EXPORT_API int bdma_stm32_stop(const struct device *dev, uint32_t id)
|
|
{
|
|
const struct bdma_stm32_config *config = dev->config;
|
|
struct bdma_stm32_channel *channel = &config->channels[id];
|
|
BDMA_TypeDef *bdma = (BDMA_TypeDef *)(config->base);
|
|
|
|
if (id >= config->max_channels) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Repeated stop : return now if channel is already stopped */
|
|
if (!stm32_bdma_is_enabled_channel(bdma, id)) {
|
|
return 0;
|
|
}
|
|
|
|
/* in bdma_stm32_configure, enabling is done regardless of defines */
|
|
LL_BDMA_DisableIT_TC(bdma, bdma_stm32_id_to_channel(id));
|
|
LL_BDMA_DisableIT_HT(bdma, bdma_stm32_id_to_channel(id));
|
|
|
|
bdma_stm32_disable_channel(bdma, id);
|
|
bdma_stm32_clear_channel_irq(dev, id);
|
|
|
|
/* Finally, flag channel as free */
|
|
channel->busy = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bdma_stm32_init(const struct device *dev)
|
|
{
|
|
const struct bdma_stm32_config *config = dev->config;
|
|
const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
|
|
|
|
if (!device_is_ready(clk)) {
|
|
LOG_ERR("clock control device not ready");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (clock_control_on(clk,
|
|
(clock_control_subsys_t) &config->pclken) != 0) {
|
|
LOG_ERR("clock op failed\n");
|
|
return -EIO;
|
|
}
|
|
|
|
config->config_irq(dev);
|
|
|
|
for (uint32_t i = 0; i < config->max_channels; i++) {
|
|
config->channels[i].busy = false;
|
|
#ifdef CONFIG_DMAMUX_STM32
|
|
/* each further channel->mux_channel is fixed here */
|
|
config->channels[i].mux_channel = i + config->offset;
|
|
#endif /* CONFIG_DMAMUX_STM32 */
|
|
}
|
|
|
|
((struct bdma_stm32_data *)dev->data)->dma_ctx.magic = 0;
|
|
((struct bdma_stm32_data *)dev->data)->dma_ctx.dma_channels = 0;
|
|
((struct bdma_stm32_data *)dev->data)->dma_ctx.atomic = 0;
|
|
|
|
/* The BDMA can only access SRAM4 and assumes it's nocachable
|
|
* This check verifies that the non-cachable flag is set in the DTS.
|
|
* For example:
|
|
* &sram4 {
|
|
* zephyr,memory-attr = "RAM_NOCACHE";
|
|
* };
|
|
*/
|
|
#if DT_NODE_HAS_PROP(DT_NODELABEL(sram4), zephyr_memory_attr)
|
|
if ((DT_PROP(DT_NODELABEL(sram4), zephyr_memory_attr) & DT_MEM_ARM_MPU_RAM_NOCACHE) == 0) {
|
|
LOG_ERR("SRAM4 is not set as non-cachable.");
|
|
return -EIO;
|
|
}
|
|
#else
|
|
#error "BDMA driver expects SRAM4 to be set as RAM_NOCACHE in DTS"
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
BDMA_STM32_EXPORT_API int bdma_stm32_get_status(const struct device *dev,
|
|
uint32_t id, struct dma_status *stat)
|
|
{
|
|
const struct bdma_stm32_config *config = dev->config;
|
|
BDMA_TypeDef *bdma = (BDMA_TypeDef *)(config->base);
|
|
struct bdma_stm32_channel *channel;
|
|
|
|
if (id >= config->max_channels) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
channel = &config->channels[id];
|
|
stat->pending_length = LL_BDMA_GetDataLength(bdma, bdma_stm32_id_to_channel(id));
|
|
stat->dir = channel->direction;
|
|
stat->busy = channel->busy;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dma_driver_api dma_funcs = {
|
|
.reload = bdma_stm32_reload,
|
|
.config = bdma_stm32_configure,
|
|
.start = bdma_stm32_start,
|
|
.stop = bdma_stm32_stop,
|
|
.get_status = bdma_stm32_get_status,
|
|
};
|
|
|
|
#ifdef CONFIG_DMAMUX_STM32
|
|
#define BDMA_STM32_OFFSET_INIT(index) \
|
|
.offset = DT_INST_PROP(index, dma_offset),
|
|
#else
|
|
#define BDMA_STM32_OFFSET_INIT(index)
|
|
#endif /* CONFIG_DMAMUX_STM32 */
|
|
|
|
#define BDMA_STM32_INIT_DEV(index) \
|
|
static struct bdma_stm32_channel \
|
|
bdma_stm32_channels_##index[BDMA_STM32_##index##_CHANNEL_COUNT];\
|
|
\
|
|
const struct bdma_stm32_config bdma_stm32_config_##index = { \
|
|
.pclken = { .bus = DT_INST_CLOCKS_CELL(index, bus), \
|
|
.enr = DT_INST_CLOCKS_CELL(index, bits) }, \
|
|
.config_irq = bdma_stm32_config_irq_##index, \
|
|
.base = DT_INST_REG_ADDR(index), \
|
|
.support_m2m = DT_INST_PROP(index, st_mem2mem), \
|
|
.max_channels = BDMA_STM32_##index##_CHANNEL_COUNT, \
|
|
.channels = bdma_stm32_channels_##index, \
|
|
BDMA_STM32_OFFSET_INIT(index) \
|
|
}; \
|
|
\
|
|
static struct bdma_stm32_data bdma_stm32_data_##index = { \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(index, \
|
|
&bdma_stm32_init, \
|
|
NULL, \
|
|
&bdma_stm32_data_##index, &bdma_stm32_config_##index, \
|
|
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
|
&dma_funcs)
|
|
|
|
#define BDMA_STM32_DEFINE_IRQ_HANDLER(bdma, chan) \
|
|
static void bdma_stm32_irq_##bdma##_##chan(const struct device *dev) \
|
|
{ \
|
|
bdma_stm32_irq_handler(dev, chan); \
|
|
}
|
|
|
|
|
|
#define BDMA_STM32_IRQ_CONNECT(bdma, chan) \
|
|
do { \
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(bdma, chan, irq), \
|
|
DT_INST_IRQ_BY_IDX(bdma, chan, priority), \
|
|
bdma_stm32_irq_##bdma##_##chan, \
|
|
DEVICE_DT_INST_GET(bdma), 0); \
|
|
irq_enable(DT_INST_IRQ_BY_IDX(bdma, chan, irq)); \
|
|
} while (false)
|
|
|
|
|
|
#if DT_NODE_HAS_STATUS(DT_DRV_INST(0), okay)
|
|
|
|
#define BDMA_STM32_DEFINE_IRQ_HANDLER_GEN(i, _) \
|
|
BDMA_STM32_DEFINE_IRQ_HANDLER(0, i)
|
|
LISTIFY(DT_NUM_IRQS(DT_DRV_INST(0)), BDMA_STM32_DEFINE_IRQ_HANDLER_GEN, (;));
|
|
|
|
static void bdma_stm32_config_irq_0(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
#define BDMA_STM32_IRQ_CONNECT_GEN(i, _) \
|
|
BDMA_STM32_IRQ_CONNECT(0, i);
|
|
LISTIFY(DT_NUM_IRQS(DT_DRV_INST(0)), BDMA_STM32_IRQ_CONNECT_GEN, (;));
|
|
}
|
|
|
|
BDMA_STM32_INIT_DEV(0);
|
|
|
|
#endif /* DT_NODE_HAS_STATUS(DT_DRV_INST(0), okay) */
|