435 lines
14 KiB
C
435 lines
14 KiB
C
/*
|
|
* Copyright (c) 2020 STMicroelectronics
|
|
* Copyright (c) 2023 Jeroen van Dooren, Nobleo Technology
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @brief Common part of DMAMUX drivers for stm32.
|
|
* @note api functions named dmamux_stm32_
|
|
* are calling the dma_stm32 corresponding function
|
|
* implemented in dma_stm32.c
|
|
*/
|
|
|
|
#include <soc.h>
|
|
#include <stm32_ll_dmamux.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/drivers/dma.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
|
|
|
|
#include "dma_stm32.h"
|
|
#ifdef CONFIG_DMA_STM32_BDMA
|
|
#include "dma_stm32_bdma.h"
|
|
#endif
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(dmamux_stm32, CONFIG_DMA_LOG_LEVEL);
|
|
|
|
#define DT_DRV_COMPAT st_stm32_dmamux
|
|
|
|
/* this is the configuration of one dmamux channel */
|
|
struct dmamux_stm32_channel {
|
|
/* pointer to the associated dma instance */
|
|
const struct device *dev_dma;
|
|
/* ref of the associated dma stream for this instance */
|
|
uint8_t dma_id;
|
|
};
|
|
|
|
/* the table of all the dmamux channel */
|
|
struct dmamux_stm32_data {
|
|
void *callback_arg;
|
|
void (*dmamux_callback)(void *arg, uint32_t id,
|
|
int error_code);
|
|
};
|
|
|
|
/* this is the configuration of the dmamux IP */
|
|
struct dmamux_stm32_config {
|
|
#if DT_INST_NODE_HAS_PROP(0, clocks)
|
|
struct stm32_pclken pclken;
|
|
#endif
|
|
uint32_t base;
|
|
uint8_t channel_nb; /* total nb of channels */
|
|
uint8_t gen_nb; /* total nb of Request generator */
|
|
uint8_t req_nb; /* total nb of Peripheral Request inputs */
|
|
const struct dmamux_stm32_channel *mux_channels;
|
|
};
|
|
|
|
/*
|
|
* LISTIFY is used to generate arrays with function pointers to check
|
|
* and clear interrupt flags using LL functions
|
|
*/
|
|
#define DMAMUX_CHANNEL(i, _) LL_DMAMUX_CHANNEL_ ## i
|
|
#define IS_ACTIVE_FLAG_SOX(i, _) LL_DMAMUX_IsActiveFlag_SO ## i
|
|
#define CLEAR_FLAG_SOX(i, _) LL_DMAMUX_ClearFlag_SO ## i
|
|
#define IS_ACTIVE_FLAG_RGOX(i, _) LL_DMAMUX_IsActiveFlag_RGO ## i
|
|
#define CLEAR_FLAG_RGOX(i, _) LL_DMAMUX_ClearFlag_RGO ## i
|
|
|
|
uint32_t table_ll_channel[] = {
|
|
LISTIFY(DT_INST_PROP(0, dma_channels), DMAMUX_CHANNEL, (,))
|
|
};
|
|
|
|
uint32_t (*func_ll_is_active_so[])(DMAMUX_Channel_TypeDef *DMAMUXx) = {
|
|
LISTIFY(DT_INST_PROP(0, dma_channels), IS_ACTIVE_FLAG_SOX, (,))
|
|
};
|
|
|
|
void (*func_ll_clear_so[])(DMAMUX_Channel_TypeDef *DMAMUXx) = {
|
|
LISTIFY(DT_INST_PROP(0, dma_channels), CLEAR_FLAG_SOX, (,))
|
|
};
|
|
|
|
uint32_t (*func_ll_is_active_rgo[])(DMAMUX_Channel_TypeDef *DMAMUXx) = {
|
|
LISTIFY(DT_INST_PROP(0, dma_generators), IS_ACTIVE_FLAG_RGOX, (,))
|
|
};
|
|
|
|
void (*func_ll_clear_rgo[])(DMAMUX_Channel_TypeDef *DMAMUXx) = {
|
|
LISTIFY(DT_INST_PROP(0, dma_generators), CLEAR_FLAG_RGOX, (,))
|
|
};
|
|
|
|
typedef int (*dma_configure_fn)(const struct device *dev, uint32_t id, struct dma_config *config);
|
|
typedef int (*dma_start_fn)(const struct device *dev, uint32_t id);
|
|
typedef int (*dma_stop_fn)(const struct device *dev, uint32_t id);
|
|
typedef int (*dma_reload_fn)(const struct device *dev, uint32_t id,
|
|
uint32_t src, uint32_t dst, size_t size);
|
|
typedef int (*dma_status_fn)(const struct device *dev, uint32_t id,
|
|
struct dma_status *stat);
|
|
|
|
struct dmamux_stm32_dma_fops {
|
|
dma_configure_fn configure;
|
|
dma_start_fn start;
|
|
dma_stop_fn stop;
|
|
dma_reload_fn reload;
|
|
dma_status_fn get_status;
|
|
};
|
|
|
|
#if (defined(CONFIG_DMA_STM32_V1) || defined(CONFIG_DMA_STM32_V2)) && \
|
|
DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux1), okay)
|
|
static const struct dmamux_stm32_dma_fops dmamux1 = {
|
|
dma_stm32_configure,
|
|
dma_stm32_start,
|
|
dma_stm32_stop,
|
|
dma_stm32_reload,
|
|
dma_stm32_get_status,
|
|
};
|
|
#endif
|
|
|
|
#if defined(CONFIG_DMA_STM32_BDMA) && DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux2), okay)
|
|
static const struct dmamux_stm32_dma_fops dmamux2 = {
|
|
bdma_stm32_configure,
|
|
bdma_stm32_start,
|
|
bdma_stm32_stop,
|
|
bdma_stm32_reload,
|
|
bdma_stm32_get_status
|
|
};
|
|
#endif /* CONFIG_DMA_STM32_BDMA */
|
|
|
|
const struct dmamux_stm32_dma_fops *get_dma_fops(const struct dmamux_stm32_config *dev_config)
|
|
{
|
|
#if DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux1), okay)
|
|
if (dev_config->base == DT_REG_ADDR(DT_NODELABEL(dmamux1))) {
|
|
return &dmamux1;
|
|
}
|
|
#endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux1), okay) */
|
|
|
|
#if DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux2), okay)
|
|
if (dev_config->base == DT_REG_ADDR(DT_NODELABEL(dmamux2))) {
|
|
return &dmamux2;
|
|
}
|
|
#endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux2), okay) */
|
|
|
|
__ASSERT(false, "Unknown dma base address %x", dev_config->base);
|
|
return (void *)0;
|
|
}
|
|
|
|
int dmamux_stm32_configure(const struct device *dev, uint32_t id,
|
|
struct dma_config *config)
|
|
{
|
|
/* device is the dmamux, id is the dmamux channel from 0 */
|
|
const struct dmamux_stm32_config *dev_config = dev->config;
|
|
const struct dmamux_stm32_dma_fops *dma_device = get_dma_fops(dev_config);
|
|
|
|
/*
|
|
* request line ID for this mux channel is stored
|
|
* in the dma_slot parameter
|
|
*/
|
|
int request_id = config->dma_slot;
|
|
|
|
if (request_id > dev_config->req_nb + dev_config->gen_nb) {
|
|
LOG_ERR("request ID %d is not valid.", request_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check if this channel is valid */
|
|
if (id >= dev_config->channel_nb) {
|
|
LOG_ERR("channel ID %d is too big.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Also configures the corresponding dma channel
|
|
* instance is given by the dev_dma
|
|
* stream is given by the index i
|
|
* config is directly this dma_config
|
|
*/
|
|
|
|
/*
|
|
* This dmamux channel 'id' is now used for this peripheral request
|
|
* It gives this mux request ID to the dma through the config.dma_slot
|
|
*/
|
|
if (dma_device->configure(dev_config->mux_channels[id].dev_dma,
|
|
dev_config->mux_channels[id].dma_id, config) != 0) {
|
|
LOG_ERR("cannot configure the dmamux.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* set the Request Line ID to this dmamux channel i */
|
|
DMAMUX_Channel_TypeDef *dmamux =
|
|
(DMAMUX_Channel_TypeDef *)dev_config->base;
|
|
|
|
|
|
LL_DMAMUX_SetRequestID(dmamux, id, request_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dmamux_stm32_start(const struct device *dev, uint32_t id)
|
|
{
|
|
const struct dmamux_stm32_config *dev_config = dev->config;
|
|
const struct dmamux_stm32_dma_fops *dma_device = get_dma_fops(dev_config);
|
|
|
|
/* check if this channel is valid */
|
|
if (id >= dev_config->channel_nb) {
|
|
LOG_ERR("channel ID %d is too big.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dma_device->start(dev_config->mux_channels[id].dev_dma,
|
|
dev_config->mux_channels[id].dma_id) != 0) {
|
|
LOG_ERR("cannot start the dmamux channel %d.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dmamux_stm32_stop(const struct device *dev, uint32_t id)
|
|
{
|
|
const struct dmamux_stm32_config *dev_config = dev->config;
|
|
const struct dmamux_stm32_dma_fops *dma_device = get_dma_fops(dev_config);
|
|
|
|
/* check if this channel is valid */
|
|
if (id >= dev_config->channel_nb) {
|
|
LOG_ERR("channel ID %d is too big.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dma_device->stop(dev_config->mux_channels[id].dev_dma,
|
|
dev_config->mux_channels[id].dma_id) != 0) {
|
|
LOG_ERR("cannot stop the dmamux channel %d.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dmamux_stm32_reload(const struct device *dev, uint32_t id,
|
|
uint32_t src, uint32_t dst, size_t size)
|
|
{
|
|
const struct dmamux_stm32_config *dev_config = dev->config;
|
|
const struct dmamux_stm32_dma_fops *dma_device = get_dma_fops(dev_config);
|
|
|
|
/* check if this channel is valid */
|
|
if (id >= dev_config->channel_nb) {
|
|
LOG_ERR("channel ID %d is too big.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dma_device->reload(dev_config->mux_channels[id].dev_dma,
|
|
dev_config->mux_channels[id].dma_id,
|
|
src, dst, size) != 0) {
|
|
LOG_ERR("cannot reload the dmamux channel %d.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dmamux_stm32_get_status(const struct device *dev, uint32_t id,
|
|
struct dma_status *stat)
|
|
{
|
|
const struct dmamux_stm32_config *dev_config = dev->config;
|
|
const struct dmamux_stm32_dma_fops *dma_device = get_dma_fops(dev_config);
|
|
|
|
/* check if this channel is valid */
|
|
if (id >= dev_config->channel_nb) {
|
|
LOG_ERR("channel ID %d is too big.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dma_device->get_status(dev_config->mux_channels[id].dev_dma,
|
|
dev_config->mux_channels[id].dma_id, stat) != 0) {
|
|
LOG_ERR("cannot get the status of dmamux channel %d.", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dmamux_stm32_init(const struct device *dev)
|
|
{
|
|
const struct dmamux_stm32_config *config = dev->config;
|
|
#if DT_INST_NODE_HAS_PROP(0, clocks)
|
|
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;
|
|
}
|
|
#endif /* DT_INST_NODE_HAS_PROP(0, clocks) */
|
|
|
|
#if DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux1), okay)
|
|
/* DMA 1 and DMA2 for DMAMUX1, BDMA for DMAMUX2 */
|
|
if (config->base == DT_REG_ADDR(DT_NODELABEL(dmamux1))) {
|
|
/* DMAs assigned to DMAMUX channels at build time might not be ready. */
|
|
#if DT_NODE_HAS_STATUS(DT_NODELABEL(dma1), okay)
|
|
if (device_is_ready(DEVICE_DT_GET(DT_NODELABEL(dma1))) == false) {
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
#if DT_NODE_HAS_STATUS(DT_NODELABEL(dma2), okay)
|
|
if (device_is_ready(DEVICE_DT_GET(DT_NODELABEL(dma2))) == false) {
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
}
|
|
#endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux1), okay) */
|
|
|
|
#if DT_NODE_HAS_STATUS(DT_NODELABEL(dmamux2), okay) && DT_NODE_HAS_STATUS(DT_NODELABEL(bdma1), okay)
|
|
if (config->base == DT_REG_ADDR(DT_NODELABEL(dmamux2))) {
|
|
if (device_is_ready(DEVICE_DT_GET(DT_NODELABEL(bdma1))) == false) {
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static const struct dma_driver_api dma_funcs = {
|
|
.reload = dmamux_stm32_reload,
|
|
.config = dmamux_stm32_configure,
|
|
.start = dmamux_stm32_start,
|
|
.stop = dmamux_stm32_stop,
|
|
.get_status = dmamux_stm32_get_status,
|
|
};
|
|
|
|
/*
|
|
* Each dmamux channel is hardwired to one dma controllers dma channel.
|
|
* DMAMUX_CHANNEL_INIT_X macros resolve this mapping at build time for each
|
|
* dmamux channel using the dma dt properties dma_offset and dma_requests,
|
|
* such that it can be stored in dmamux_stm32_channels_X configuration.
|
|
* The Macros to get the corresponding dma device binding and dma channel
|
|
* for a given dmamux channel, are currently valid for series having
|
|
* up to 2 dmamuxes and up to 3 dmas.
|
|
*/
|
|
|
|
#define DMA_1_BEGIN_DMAMUX_CHANNEL DT_PROP_OR(DT_NODELABEL(dma1), dma_offset, 0)
|
|
#define DMA_1_END_DMAMUX_CHANNEL (DMA_1_BEGIN_DMAMUX_CHANNEL + \
|
|
DT_PROP_OR(DT_NODELABEL(dma1), dma_requests, 0))
|
|
#define DEV_DMA1 COND_CODE_1(DT_NODE_HAS_STATUS(DT_NODELABEL(dma1), okay), \
|
|
DEVICE_DT_GET(DT_NODELABEL(dma1)), NULL)
|
|
|
|
#define DMA_2_BEGIN_DMAMUX_CHANNEL DT_PROP_OR(DT_NODELABEL(dma2), dma_offset, 0)
|
|
#define DMA_2_END_DMAMUX_CHANNEL (DMA_2_BEGIN_DMAMUX_CHANNEL + \
|
|
DT_PROP_OR(DT_NODELABEL(dma2), dma_requests, 0))
|
|
#define DEV_DMA2 COND_CODE_1(DT_NODE_HAS_STATUS(DT_NODELABEL(dma2), okay), \
|
|
DEVICE_DT_GET(DT_NODELABEL(dma2)), NULL)
|
|
|
|
#define BDMA_1_BEGIN_DMAMUX_CHANNEL DT_PROP_OR(DT_NODELABEL(bdma1), dma_offset, 0)
|
|
#define BDMA_1_END_DMAMUX_CHANNEL (BDMA_1_BEGIN_DMAMUX_CHANNEL + \
|
|
DT_PROP_OR(DT_NODELABEL(bdma1), dma_requests, 0))
|
|
#define DEV_BDMA COND_CODE_1(DT_NODE_HAS_STATUS(DT_NODELABEL(bdma1), okay), \
|
|
DEVICE_DT_GET(DT_NODELABEL(bdma1)), NULL)
|
|
|
|
#define DEV_DMA_BINDING(mux_channel) \
|
|
((mux_channel < DMA_1_END_DMAMUX_CHANNEL) ? DEV_DMA1 : DEV_DMA2)
|
|
|
|
#define DEV_BDMA_BINDING(mux_channel) \
|
|
(DEV_BDMA)
|
|
|
|
|
|
#define DMA_CHANNEL(mux_channel) \
|
|
((mux_channel < DMA_1_END_DMAMUX_CHANNEL) ? \
|
|
(mux_channel + 1) : (mux_channel - DMA_2_BEGIN_DMAMUX_CHANNEL + 1))
|
|
|
|
#define BDMA_CHANNEL(mux_channel) \
|
|
((mux_channel < BDMA_1_END_DMAMUX_CHANNEL) ? \
|
|
(mux_channel) : 0 /* not supported */)
|
|
|
|
/*
|
|
* H7 series implements DMAMUX1 and DMAMUX2
|
|
* DMAMUX1 is used by DMA1 and DMA2
|
|
* DMAMUX2 is used by BDMA
|
|
*
|
|
* Note: Instance Number (or index) has no guarantee to which dmamux it refers
|
|
*/
|
|
|
|
#define INIT_DMAMUX1_CHANNEL(x, ...) \
|
|
{ .dev_dma = DEV_DMA_BINDING(x), .dma_id = DMA_CHANNEL(x), }
|
|
#define INIT_DMAMUX2_CHANNEL(x, ...) \
|
|
{ .dev_dma = DEV_BDMA_BINDING(x), .dma_id = BDMA_CHANNEL(x), }
|
|
|
|
#if DT_SAME_NODE(DT_DRV_INST(0), DT_NODELABEL(dmamux1))
|
|
#define INIT_INST0_CHANNEL(x, ...) INIT_DMAMUX1_CHANNEL(x, ...)
|
|
#define INIT_INST1_CHANNEL(x, ...) INIT_DMAMUX2_CHANNEL(x, ...)
|
|
#else
|
|
#define INIT_INST0_CHANNEL(x, ...) INIT_DMAMUX2_CHANNEL(x, ...)
|
|
#define INIT_INST1_CHANNEL(x, ...) INIT_DMAMUX1_CHANNEL(x, ...)
|
|
#endif
|
|
|
|
#define DMAMUX_CHANNELS_INIT(index, count) \
|
|
LISTIFY(count, INIT_INST##index##_CHANNEL, (,))
|
|
|
|
#define DMAMUX_CLOCK_INIT(index) \
|
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(index, clocks), \
|
|
(.pclken = { .bus = DT_INST_CLOCKS_CELL(index, bus), \
|
|
.enr = DT_INST_CLOCKS_CELL(index, bits)},), \
|
|
())
|
|
|
|
#define DMAMUX_INIT(index) \
|
|
static const struct dmamux_stm32_channel \
|
|
dmamux_stm32_channels_##index[DT_INST_PROP(index, dma_channels)] = { \
|
|
DMAMUX_CHANNELS_INIT(index, DT_INST_PROP(index, dma_channels))\
|
|
}; \
|
|
\
|
|
const struct dmamux_stm32_config dmamux_stm32_config_##index = { \
|
|
DMAMUX_CLOCK_INIT(index) \
|
|
.base = DT_INST_REG_ADDR(index), \
|
|
.channel_nb = DT_INST_PROP(index, dma_channels), \
|
|
.gen_nb = DT_INST_PROP(index, dma_generators), \
|
|
.req_nb = DT_INST_PROP(index, dma_requests), \
|
|
.mux_channels = dmamux_stm32_channels_##index, \
|
|
}; \
|
|
\
|
|
static struct dmamux_stm32_data dmamux_stm32_data_##index; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(index, \
|
|
&dmamux_stm32_init, \
|
|
NULL, \
|
|
&dmamux_stm32_data_##index, &dmamux_stm32_config_##index,\
|
|
PRE_KERNEL_1, CONFIG_DMAMUX_STM32_INIT_PRIORITY, \
|
|
&dma_funcs);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(DMAMUX_INIT)
|
|
|
|
/*
|
|
* Make sure that this driver is initialized after the DMA driver (higher priority)
|
|
*/
|
|
BUILD_ASSERT(CONFIG_DMAMUX_STM32_INIT_PRIORITY >= CONFIG_DMA_INIT_PRIORITY,
|
|
"CONFIG_DMAMUX_STM32_INIT_PRIORITY must be higher than CONFIG_DMA_INIT_PRIORITY");
|