505 lines
16 KiB
C
505 lines
16 KiB
C
/******************************************************************************
|
|
* The MIT License
|
|
*
|
|
* Copyright (c) 2012 LeafLabs, LLC.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy,
|
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
* of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
* @file libmaple/stm32f2/dma.c
|
|
* @author Marti Bolivar <mbolivar@leaflabs.com>
|
|
* @brief STM32F2 DMA support.
|
|
*/
|
|
|
|
#include <libmaple/dma.h>
|
|
#include <libmaple/bitband.h>
|
|
#include <libmaple/util.h>
|
|
|
|
/* Hack to ensure inlining in dma_irq_handler() */
|
|
#define DMA_GET_HANDLER(dev, tube) (dev->handlers[tube].handler)
|
|
#include "dma_private.h"
|
|
|
|
/*
|
|
* Devices
|
|
*/
|
|
|
|
static dma_dev dma1 = {
|
|
.regs = DMA1_BASE,
|
|
.clk_id = RCC_DMA1,
|
|
.handlers = {{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM0 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM1 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM2 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM3 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM4 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM5 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM6 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM7 }},
|
|
};
|
|
dma_dev *DMA1 = &dma1;
|
|
|
|
static dma_dev dma2 = {
|
|
.regs = DMA2_BASE,
|
|
.clk_id = RCC_DMA2,
|
|
.handlers = {{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM0 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM1 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM2 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM3 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM4 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM5 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM6 },
|
|
{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM7 }},
|
|
};
|
|
dma_dev *DMA2 = &dma2;
|
|
|
|
/*
|
|
* Helpers for dealing with dma_request_src's bit encoding (see the
|
|
* comments in the dma_request_src definition).
|
|
*/
|
|
|
|
/* rcc_clk_id of dma_dev which supports src. */
|
|
static __always_inline rcc_clk_id src_clk_id(dma_request_src src) {
|
|
return (rcc_clk_id)(((uint32)src >> 3) & 0x3F);
|
|
}
|
|
|
|
/* Bit vector of streams supporting src (e.g., bit 0 set => DMA_S0 support). */
|
|
static __always_inline uint32 src_stream_mask(dma_request_src src) {
|
|
return ((uint32)src >> 10) & 0xFF;
|
|
}
|
|
|
|
/* Channel corresponding to src. */
|
|
static __always_inline dma_channel src_channel(dma_request_src src) {
|
|
return (dma_channel)(src & 0x7);
|
|
}
|
|
|
|
/*
|
|
* Routines
|
|
*/
|
|
|
|
/* For convenience */
|
|
#define ASSERT_NOT_ENABLED(dev, tube) ASSERT(!dma_is_enabled(dev, tube))
|
|
|
|
/* Helpers for dma_tube_cfg() */
|
|
static int preconfig_check(dma_dev *dev, dma_tube tube, dma_tube_config *cfg);
|
|
static int postconfig_check(dma_tube_reg_map *dummy, dma_tube_config *cfg);
|
|
static int config_fifo(dma_tube_reg_map *dummy, dma_tube_config *cfg);
|
|
static int config_src_dst(dma_tube_reg_map *dummy, dma_tube_config *cfg);
|
|
static void copy_regs(dma_tube_reg_map *src, dma_tube_reg_map *dst);
|
|
|
|
int dma_tube_cfg(dma_dev *dev, dma_tube tube, dma_tube_config *cfg) {
|
|
dma_tube_reg_map dummy_regs;
|
|
dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
|
|
int ret;
|
|
|
|
/* Initial error checking. */
|
|
ret = preconfig_check(dev, tube, cfg);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Disable `tube' as per RM0033. */
|
|
dma_disable(dev, tube);
|
|
dma_clear_isr_bits(dev, tube);
|
|
|
|
/* Don't write to tregs until we've decided `cfg' is really OK,
|
|
* so as not to make a half-formed mess if we have to error out. */
|
|
copy_regs(tregs, &dummy_regs);
|
|
|
|
/* Try to reconfigure `tube', bailing on error. */
|
|
ret = config_fifo(&dummy_regs, cfg);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
ret = config_src_dst(&dummy_regs, cfg);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
dummy_regs.SNDTR = cfg->tube_nr_xfers;
|
|
ret = postconfig_check(&dummy_regs, cfg);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Ok, we're good. Commit to the new configuration. */
|
|
copy_regs(&dummy_regs, tregs);
|
|
return ret;
|
|
}
|
|
|
|
void dma_set_priority(dma_dev *dev, dma_stream stream, dma_priority priority) {
|
|
dma_tube_reg_map *tregs = dma_tube_regs(dev, stream);
|
|
uint32 scr;
|
|
ASSERT_NOT_ENABLED(dev, stream);
|
|
scr = tregs->SCR;
|
|
scr &= ~DMA_SCR_PL;
|
|
scr |= (priority << 16);
|
|
tregs->SCR = scr;
|
|
}
|
|
|
|
void dma_set_num_transfers(dma_dev *dev, dma_tube tube, uint16 num_transfers) {
|
|
dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
|
|
ASSERT_NOT_ENABLED(dev, tube);
|
|
tregs->SNDTR = num_transfers;
|
|
}
|
|
|
|
/**
|
|
* @brief Set memory 0 or memory 1 address.
|
|
*
|
|
* This is a general function for setting one of the two memory
|
|
* addresses available on the double-buffered STM32F2 DMA controllers.
|
|
*
|
|
* @param dev DMA device
|
|
* @param tube Tube on dev.
|
|
* @param n If 0, set memory 0 address. If 1, set memory 1 address.
|
|
* @param address Address to set
|
|
*/
|
|
void dma_set_mem_n_addr(dma_dev *dev, dma_tube tube, int n,
|
|
__io void *address) {
|
|
dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
|
|
uint32 addr = (uint32)address;
|
|
|
|
ASSERT_NOT_ENABLED(dev, tube);
|
|
if (n) {
|
|
tregs->SM1AR = addr;
|
|
} else {
|
|
tregs->SM0AR = addr;
|
|
}
|
|
}
|
|
|
|
void dma_set_per_addr(dma_dev *dev, dma_tube tube, __io void *address) {
|
|
dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
|
|
ASSERT_NOT_ENABLED(dev, tube);
|
|
tregs->SPAR = (uint32)address;
|
|
}
|
|
|
|
/**
|
|
* @brief Enable a stream's FIFO.
|
|
*
|
|
* You may only call this function when the stream is disabled.
|
|
*
|
|
* @param dev DMA device
|
|
* @param tube Stream whose FIFO to enable.
|
|
*/
|
|
void dma_enable_fifo(dma_dev *dev, dma_tube tube) {
|
|
ASSERT_NOT_ENABLED(dev, tube);
|
|
bb_peri_set_bit(&(dma_tube_regs(dev, tube)->SFCR), DMA_SFCR_DMDIS_BIT, 1);
|
|
}
|
|
|
|
/**
|
|
* @brief Disable a stream's FIFO.
|
|
*
|
|
* You may only call this function when the stream is disabled.
|
|
*
|
|
* @param dev DMA device
|
|
* @param tube Stream whose FIFO to disable.
|
|
*/
|
|
void dma_disable_fifo(dma_dev *dev, dma_tube tube) {
|
|
ASSERT_NOT_ENABLED(dev, tube);
|
|
bb_peri_set_bit(&(dma_tube_regs(dev, tube)->SFCR), DMA_SFCR_DMDIS_BIT, 0);
|
|
}
|
|
|
|
void dma_attach_interrupt(dma_dev *dev, dma_tube tube,
|
|
void (*handler)(void)) {
|
|
dev->handlers[tube].handler = handler;
|
|
nvic_irq_enable(dev->handlers[tube].irq_line);
|
|
}
|
|
|
|
void dma_detach_interrupt(dma_dev *dev, dma_tube tube) {
|
|
nvic_irq_disable(dev->handlers[tube].irq_line);
|
|
dev->handlers[tube].handler = NULL;
|
|
}
|
|
|
|
void dma_enable(dma_dev *dev, dma_tube tube) {
|
|
dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
|
|
bb_peri_set_bit(&tregs->SCR, DMA_SCR_EN_BIT, 1);
|
|
}
|
|
|
|
void dma_disable(dma_dev *dev, dma_tube tube) {
|
|
dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
|
|
bb_peri_set_bit(&tregs->SCR, DMA_SCR_EN_BIT, 0);
|
|
/* The stream might not get disabled immediately, so wait. */
|
|
while (tregs->SCR & DMA_SCR_EN)
|
|
;
|
|
}
|
|
|
|
dma_irq_cause dma_get_irq_cause(dma_dev *dev, dma_tube tube) {
|
|
/* TODO: does it still make sense to have this function? We should
|
|
* probably just be returning the ISR bits, with some defines to
|
|
* pull the flags out. The lack of masked status bits is an
|
|
* annoyance that would require documentation to solve, though. */
|
|
uint8 status_bits = dma_get_isr_bits(dev, tube);
|
|
dma_clear_isr_bits(dev, tube);
|
|
ASSERT(status_bits); /* Or something's very wrong */
|
|
/* Don't change the order of these if statements. */
|
|
if (status_bits & 0x0) {
|
|
return DMA_TRANSFER_FIFO_ERROR;
|
|
} else if (status_bits & 0x4) {
|
|
return DMA_TRANSFER_DME_ERROR;
|
|
} else if (status_bits & 0x8) {
|
|
return DMA_TRANSFER_ERROR;
|
|
} else if (status_bits & 0x20) {
|
|
return DMA_TRANSFER_COMPLETE;
|
|
} else if (status_bits & 0x10) {
|
|
return DMA_TRANSFER_HALF_COMPLETE;
|
|
}
|
|
|
|
/* Something's wrong; one of those bits should have been set. Fail
|
|
* an assert, and mimic the error behavior in case of a high debug
|
|
* level. */
|
|
ASSERT(0);
|
|
dma_disable(dev, tube);
|
|
return DMA_TRANSFER_ERROR;
|
|
}
|
|
|
|
/*
|
|
* IRQ handlers
|
|
*/
|
|
|
|
void __irq_dma1_stream0(void) {
|
|
dma_irq_handler(DMA1, DMA_S0);
|
|
}
|
|
|
|
void __irq_dma1_stream1(void) {
|
|
dma_irq_handler(DMA1, DMA_S1);
|
|
}
|
|
|
|
void __irq_dma1_stream2(void) {
|
|
dma_irq_handler(DMA1, DMA_S2);
|
|
}
|
|
|
|
void __irq_dma1_stream3(void) {
|
|
dma_irq_handler(DMA1, DMA_S3);
|
|
}
|
|
|
|
void __irq_dma1_stream4(void) {
|
|
dma_irq_handler(DMA1, DMA_S4);
|
|
}
|
|
|
|
void __irq_dma1_stream5(void) {
|
|
dma_irq_handler(DMA1, DMA_S5);
|
|
}
|
|
|
|
void __irq_dma1_stream6(void) {
|
|
dma_irq_handler(DMA1, DMA_S6);
|
|
}
|
|
|
|
void __irq_dma1_stream7(void) {
|
|
dma_irq_handler(DMA1, DMA_S7);
|
|
}
|
|
|
|
void __irq_dma2_stream0(void) {
|
|
dma_irq_handler(DMA2, DMA_S0);
|
|
}
|
|
|
|
void __irq_dma2_stream1(void) {
|
|
dma_irq_handler(DMA2, DMA_S1);
|
|
}
|
|
|
|
void __irq_dma2_stream2(void) {
|
|
dma_irq_handler(DMA2, DMA_S2);
|
|
}
|
|
|
|
void __irq_dma2_stream3(void) {
|
|
dma_irq_handler(DMA2, DMA_S3);
|
|
}
|
|
|
|
void __irq_dma2_stream4(void) {
|
|
dma_irq_handler(DMA2, DMA_S4);
|
|
}
|
|
|
|
void __irq_dma2_stream5(void) {
|
|
dma_irq_handler(DMA2, DMA_S5);
|
|
}
|
|
|
|
void __irq_dma2_stream6(void) {
|
|
dma_irq_handler(DMA2, DMA_S6);
|
|
}
|
|
|
|
void __irq_dma2_stream7(void) {
|
|
dma_irq_handler(DMA2, DMA_S7);
|
|
}
|
|
|
|
/*
|
|
* Auxiliary routines for dma_tube_cfg()
|
|
*/
|
|
|
|
/* Is addr acceptable for use as DMA src/dst? */
|
|
static int cfg_mem_ok(__io void *addr) {
|
|
enum dma_atype atype = _dma_addr_type(addr);
|
|
return atype == DMA_ATYPE_MEM || atype == DMA_ATYPE_PER;
|
|
}
|
|
|
|
/* Is src -> dst a reasonable combination of [MEM,PER] -> [MEM,PER]? */
|
|
static int cfg_dir_ok(dma_dev *dev, __io void *src, __io void *dst) {
|
|
switch (_dma_addr_type(dst)) {
|
|
case DMA_ATYPE_MEM:
|
|
/* Only DMA2 can do memory-to-memory */
|
|
return ((_dma_addr_type(src) == DMA_ATYPE_PER) ||
|
|
(dev->clk_id == RCC_DMA2));
|
|
case DMA_ATYPE_PER:
|
|
/* Peripheral-to-peripheral is illegal */
|
|
return _dma_addr_type(src) == DMA_ATYPE_PER;
|
|
default: /* Can't happen */
|
|
ASSERT(0);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Initial sanity check for dma_tube_cfg() */
|
|
static int preconfig_check(dma_dev *dev, dma_tube tube,
|
|
dma_tube_config *cfg) {
|
|
if (!(src_stream_mask(cfg->tube_req_src) & (1U << tube))) {
|
|
/* ->tube_req_src not supported by stream */
|
|
return -DMA_TUBE_CFG_EREQ;
|
|
}
|
|
if (cfg->tube_nr_xfers > 65535) {
|
|
/* That's too many. */
|
|
return -DMA_TUBE_CFG_ENDATA;
|
|
}
|
|
if (src_clk_id(cfg->tube_req_src) != dev->clk_id) {
|
|
/* ->tube_req_src not supported by dev */
|
|
return -DMA_TUBE_CFG_EDEV;
|
|
}
|
|
if (!cfg_mem_ok(cfg->tube_src)) {
|
|
return -DMA_TUBE_CFG_ESRC;
|
|
}
|
|
if (!cfg_mem_ok(cfg->tube_dst)) {
|
|
return -DMA_TUBE_CFG_EDST;
|
|
}
|
|
if (!cfg_dir_ok(dev, cfg->tube_src, cfg->tube_dst)) {
|
|
return -DMA_TUBE_CFG_EDIR;
|
|
}
|
|
return DMA_TUBE_CFG_SUCCESS;
|
|
}
|
|
|
|
static int config_fifo(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
|
|
/* TODO: FIFO configuration based on cfg->target_data */
|
|
uint32 sfcr = dummy->SFCR;
|
|
sfcr &= ~DMA_SFCR_FEIE;
|
|
sfcr |= (cfg->tube_flags & DMA_CFG_FIFO_ERR_IE) ? DMA_SFCR_FEIE : 0;
|
|
dummy->SFCR = sfcr;
|
|
return DMA_TUBE_CFG_SUCCESS;
|
|
}
|
|
|
|
/* Helper for configuring (DMA_SxCR) */
|
|
#define BITS_WE_CARE_ABOUT \
|
|
(DMA_SCR_CHSEL | DMA_SCR_MBURST | DMA_SCR_PBURST | DMA_SCR_PINCOS | \
|
|
DMA_SCR_MINC | DMA_SCR_PINC | DMA_SCR_CIRC | DMA_SCR_DIR | \
|
|
DMA_SCR_PFCTRL | DMA_SCR_TCIE | DMA_SCR_HTIE | DMA_SCR_TEIE | \
|
|
DMA_SCR_DMEIE)
|
|
static inline void config_scr(dma_tube_reg_map *dummy, dma_tube_config *cfg,
|
|
unsigned src_shift, uint32 src_inc,
|
|
unsigned dst_shift, uint32 dst_inc,
|
|
uint32 dir) {
|
|
/* These would go here if we supported them: MBURST, PBURST,
|
|
* PINCOS, PFCTRL. We explicitly choose low priority, and double
|
|
* buffering belongs elsewhere, I think. [mbolivar] */
|
|
uint32 flags = cfg->tube_flags & BITS_WE_CARE_ABOUT;
|
|
uint32 scr = dummy->SCR;
|
|
scr &= ~(BITS_WE_CARE_ABOUT | DMA_SCR_PL);
|
|
scr |= (/* CHSEL */
|
|
(src_channel(cfg->tube_req_src) << 25) |
|
|
/* MSIZE/PSIZE */
|
|
(cfg->tube_src_size << src_shift) |
|
|
(cfg->tube_dst_size << dst_shift) |
|
|
/* MINC/PINC */
|
|
((cfg->tube_flags & DMA_CFG_SRC_INC) ? src_inc : 0) |
|
|
((cfg->tube_flags & DMA_CFG_DST_INC) ? dst_inc : 0) |
|
|
/* DIR */
|
|
dir |
|
|
/* Other flags carried by cfg->tube_flags */
|
|
flags);
|
|
dummy->SCR = scr;
|
|
}
|
|
#undef BITS_WE_CARE_ABOUT
|
|
|
|
/* Helper for when cfg->tube_dst is memory */
|
|
static int config_to_mem(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
|
|
uint32 dir = (_dma_addr_type(cfg->tube_src) == DMA_ATYPE_MEM ?
|
|
DMA_SCR_DIR_MEM_TO_MEM : DMA_SCR_DIR_PER_TO_MEM);
|
|
|
|
if ((dir == DMA_SCR_DIR_MEM_TO_MEM) && (cfg->tube_flags & DMA_CFG_CIRC)) {
|
|
return -DMA_TUBE_CFG_ECFG; /* Can't do DMA_CFG_CIRC and mem->mem. */
|
|
}
|
|
|
|
config_scr(dummy, cfg, 11, DMA_SCR_PINC, 13, DMA_SCR_MINC, dir);
|
|
dummy->SPAR = (uint32)cfg->tube_src;
|
|
dummy->SM0AR = (uint32)cfg->tube_dst;
|
|
return DMA_TUBE_CFG_SUCCESS;
|
|
}
|
|
|
|
/* Helper for when cfg->tube_src is peripheral */
|
|
static int config_to_per(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
|
|
config_scr(dummy, cfg, 13, DMA_SCR_MINC, 11, DMA_SCR_PINC,
|
|
DMA_SCR_DIR_MEM_TO_PER);
|
|
dummy->SM0AR = (uint32)cfg->tube_src;
|
|
dummy->SPAR = (uint32)cfg->tube_dst;
|
|
return DMA_TUBE_CFG_SUCCESS;
|
|
}
|
|
|
|
/* Configures SCR, SPAR, SM0AR, and checks that the result is OK. */
|
|
static int config_src_dst(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
|
|
switch (_dma_addr_type(cfg->tube_dst)) {
|
|
case DMA_ATYPE_MEM:
|
|
return config_to_mem(dummy, cfg);
|
|
case DMA_ATYPE_PER:
|
|
return config_to_per(dummy, cfg);
|
|
case DMA_ATYPE_OTHER:
|
|
default: /* shut up, GCC */
|
|
/* Can't happen */
|
|
ASSERT(0);
|
|
return -DMA_TUBE_CFG_ECFG;
|
|
}
|
|
}
|
|
|
|
/* Final checks we can only perform when fully configured */
|
|
static int postconfig_check(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
|
|
/* TODO add dma_get_[mem,per]_size() and use them here */
|
|
/* msize and psize are in bytes here: */
|
|
uint32 scr = dummy->SCR;
|
|
uint32 msize = 1U << ((scr >> 13) & 0x3);
|
|
uint32 psize = 1U << ((scr >> 11) & 0x3);
|
|
|
|
/* Ensure NDT will work with PSIZE/MSIZE.
|
|
*
|
|
* RM0033 specifies that PSIZE, MSIZE, and NDT must be such that
|
|
* the last transfer completes; i.e. that if PSIZE < MSIZE, then
|
|
* NDT is a multiple of MSIZE/PSIZE. See e.g. Table 27. */
|
|
if ((psize < msize) && (cfg->tube_nr_xfers % (msize / psize))) {
|
|
return -DMA_TUBE_CFG_ENDATA;
|
|
}
|
|
|
|
/* Direct mode is only possible if MSIZE == PSIZE. */
|
|
if ((msize != psize) && !(dummy->SFCR & DMA_SFCR_DMDIS)) {
|
|
return -DMA_TUBE_CFG_ESIZE;
|
|
}
|
|
|
|
return DMA_TUBE_CFG_SUCCESS;
|
|
}
|
|
|
|
/* Convenience for dealing with dummy registers */
|
|
static void copy_regs(dma_tube_reg_map *src, dma_tube_reg_map *dst) {
|
|
dst->SCR = src->SCR;
|
|
dst->SNDTR = src->SNDTR;
|
|
dst->SPAR = src->SPAR;
|
|
dst->SM0AR = src->SM0AR;
|
|
dst->SFCR = src->SFCR;
|
|
}
|