本文主要讲述mxc处理器中,sdma的处理过程及用户如何去使用sdma进行数据的传输。
一 sdma 的通道数及优先级
#define MAX_DMA_CHANNELS 32 /* 32 通道 */
#define MAX_BD_NUMBER 16 /* 缓冲描述符数目 */
#define MXC_SDMA_DEFAULT_PRIORITY 1 /* 默认的优先级 */
#define MXC_SDMA_MIN_PRIORITY 1 /* 最低优先级 */
#define MXC_SDMA_MAX_PRIORITY 7 /* 最高优先级 */
二 些重要的数据结构
传输类型;
typedef enum {
emi_2_per = 0, /*!< EMI memory to peripheral */
emi_2_int, /*!< EMI memory to internal RAM */
emi_2_emi, /*!< EMI memory to EMI memory */
emi_2_dsp, /*!< EMI memory to DSP memory */
per_2_int, /*!< Peripheral to internal RAM */
per_2_emi, /*!< Peripheral to internal EMI memory */
per_2_dsp, /*!< Peripheral to DSP memory */
per_2_per, /*!< Peripheral to Peripheral */
int_2_per, /*!< Internal RAM to peripheral */
int_2_int, /*!< Internal RAM to Internal RAM */
int_2_emi, /*!< Internal RAM to EMI memory */
int_2_dsp, /*!< Internal RAM to DSP memory */
dsp_2_per, /*!< DSP memory to peripheral */
dsp_2_int, /*!< DSP memory to internal RAM */
dsp_2_emi, /*!< DSP memory to EMI memory */
dsp_2_dsp, /*!< DSP memory to DSP memory */
emi_2_dsp_loop, /*!< EMI memory to DSP memory loopback */
dsp_2_emi_loop, /*!< DSP memory to EMI memory loopback */
dvfs_pll, /*!< DVFS script with PLL change */
dvfs_pdr /*!< DVFS script without PLL change */
} sdma_transferT;
外设类型;
typedef enum {
SSI, /*!< MCU domain SSI */
SSI_SP, /*!< Shared SSI */
MMC, /*!< MMC */
SDHC, /*!< SDHC */
UART, /*!< MCU domain UART */
UART_SP, /*!< Shared UART */
FIRI, /*!< FIRI */
CSPI, /*!< MCU domain CSPI */
CSPI_SP, /*!< Shared CSPI */
SIM, /*!< SIM */
ATA, /*!< ATA */
CCM, /*!< CCM */
EXT, /*!< External peripheral */
MSHC, /*!< Memory Stick Host Controller */
MSHC_SP, /*!< Shared Memory Stick Host Controller */
DSP, /*!< DSP */
MEMORY, /*!< Memory */
FIFO_MEMORY, /*!< FIFO type Memory */
SPDIF, /*!< SPDIF */
IPU_MEMORY, /*!< IPU Memory */
ASRC, /*!< ASRC */
ESAI, /*!< ESAI */
} sdma_periphT;
dma 通道参数类型;
typedef struct {
__u32 watermark_level; /*!< Lower/upper threshold that
* triggers SDMA event
*/
__u32 per_address; /*!< Peripheral source/destination
* physical address
*/
sdma_periphT peripheral_type; /*!< Peripheral type */
sdma_transferT transfer_type; /*!< Transfer type */
int event_id; /*!< Event number,
* needed by all channels
* that started by peripherals dma
* request (per_2_*,*_2_per)
* Not used for memory and DSP
* transfers.
*/
int event_id2; /*!< Second event number,
* used in ATA scripts only.
*/
int bd_number; /*!< Buffer descriptors number.
* If not set, single buffer
* descriptor will be used.
*/
dma_callback_t callback; /*! callback function */
void *arg; /*! callback argument */
unsigned long word_size:8; /*!< SDMA data access word size */
} dma_channel_params;
mxc sma 通道类型;
/*! Structure to store the initialized dma_channel parameters */
typedef struct mxc_sdma_channel_params {
/*! Channel params */
dma_channel_params chnl_params;
/*! Channel type (static channel number or dynamic channel) */
unsigned int channel_num;
/*! Channel priority [0x1(lowest) – 0x7(highest)] */
unsigned int chnl_priority;
} mxc_sdma_channel_params_t;
dma请求类型;
typedef struct {
/*! physical source memory address */
__u8 *sourceAddr;
/*! physical destination memory address */
__u8 *destAddr;
/*! amount of data to transfer,
* updated during mxc_dma_get_config
*/
__u16 count;
/*!< DONE bit of the buffer descriptor,
* updated during mxc_dma_get_config
* 0 – means the BD is done and closed by SDMA
* 1 – means the BD is still being processed by SDMA
*/
int bd_done;
/*!< CONT bit of the buffer descriptor,
* set it if full multi-buffer descriptor mechanism
* required.
*/
int bd_cont;
/*!< ERROR bit of the buffer descriptor,
* updated during mxc_dma_get_config.
* If it is set – there was an error during BD processing.
*/
int bd_error;
} dma_request_t;
dma 通道私有数据类型;
/*! Private SDMA data structure */
typedef struct mxc_dma_channel_private {
/*! ID of the buffer that was processed */
unsigned int buf_tail;
/*! Tasklet for the channel */
struct tasklet_struct chnl_tasklet;
/*! Flag indicates if interrupt is required after every BD transfer */
int intr_after_every_bd;
} mxc_dma_channel_private_t;
/*! This defines the prototype of callback funtion registered by the drivers */
typedef void (*mxc_dma_callback_t) (void *arg, int error_status,
unsigned int count);
/*! This defines the type of DMA transfer requested */
typedef enum mxc_dma_mode {
MXC_DMA_MODE_READ,
MXC_DMA_MODE_WRITE,
} mxc_dma_mode_t;
/*! This defines the DMA channel parameters */
typedef struct mxc_dma_channel {
unsigned int active:1; /*!< When there has a active tranfer, it is set to 1 */
unsigned int lock; /*!< Defines the channel is allocated or not */
int curr_buf; /*!< Current buffer */
mxc_dma_mode_t mode; /*!< Read or Write */
unsigned int channel; /*!< Channel info */
unsigned int dynamic:1; /*!< Channel not statically allocated when 1 */
char *dev_name; /*!< Device name */
void *private; /*!< Private structure for platform */
mxc_dma_callback_t cb_fn; /*!< The callback function */
void *cb_args; /*!< The argument of callback function */
} mxc_dma_channel_t;
/*! This structure contains the information about a dma transfer */
typedef struct mxc_dma_requestbuf {
dma_addr_t src_addr; /*!< source address */
dma_addr_t dst_addr; /*!< destination address */
int num_of_bytes; /*!< the length of this transfer : bytes */
} mxc_dma_requestbuf_t;
sdma_data[MAX_DMA_CHANNELS] 初始化过程
int __init sdma_init(void)
{
int res = 0;
configs_data confreg_data;
/* Initialize to the default values */
confreg_data = iapi_ConfigDefaults;
confreg_data.dspdma = MXC_SDMA_DSPDMA;
/* Set ACR bit */
mxc_sdma_ahb_clk = clk_get(NULL, "sdma_ahb_clk");
mxc_sdma_ipg_clk = clk_get(NULL, "sdma_ipg_clk");
clk_enable(mxc_sdma_ahb_clk);
clk_enable(mxc_sdma_ipg_clk);
if (clk_get_rate(mxc_sdma_ahb_clk) / clk_get_rate(mxc_sdma_ipg_clk) < 2) {
printk(KERN_INFO "Setting SDMA ACR\n");
confreg_data.acr = 1;
}
init_sdma_data();
init_sdma_pool();
res = request_irq(MXC_INT_SDMA, sdma_int_handler, 0, "mxcsdma", 0);
if (res < 0) {
goto sdma_init_fail;
}
init_mutexes();
init_iapi_struct();
mxc_sdma_get_script_info(&sdma_script_addrs);
res = iapi_Init(sdma_data[0].cd, &confreg_data,
sdma_script_addrs.mxc_sdma_start_addr,
sdma_script_addrs.mxc_sdma_ram_code_size * 2,
sdma_script_addrs.mxc_sdma_ram_code_start_addr);
if (res < 0) {
free_irq(MXC_INT_SDMA, 0);
goto sdma_init_fail;
}
init_priorities();
init_event_table();
#if defined(CONFIG_MXC_SUPER_GEM)
res = init_super_gem();
if (res < 0) {
free_irq(MXC_INT_SDMA, 0);
goto sdma_init_fail;
}
#endif
init_proc_fs();
printk(KERN_INFO "MXC DMA API initialized\n");
clk_disable(mxc_sdma_ahb_clk);
clk_disable(mxc_sdma_ipg_clk);
return res;
}
/*!
* This function is generally called by the driver at open time.
* The DMA driver would do any initialization steps that is required
* to get the channel ready for data transfer.
*
* @param channel_id a pre-defined id. The peripheral driver would specify
* the id associated with its peripheral. This would be
* used by the DMA driver to identify the peripheral
* requesting DMA and do the necessary setup on the
* channel associated with the particular peripheral.
* The DMA driver could use static or dynamic DMA channel
* allocation.
* @param dev_name module name or device name
* @return returns a negative number on error if request for a DMA channel did not
* succeed, returns the channel number to be used on success.
*/
int mxc_dma_request(mxc_dma_device_t channel_id, char *dev_name)
{
mxc_sdma_channel_params_t *chnl;
mxc_dma_channel_private_t *data_priv;
int ret = 0, i = 0, channel_num = 0;
chnl = mxc_sdma_get_channel_params(channel_id);
if (chnl == NULL) {
return -EINVAL;
}
/* Enable the SDMA clock */
clk_enable(mxc_sdma_ahb_clk);
clk_enable(mxc_sdma_ipg_clk);
channel_num = chnl->channel_num;
if (chnl->channel_num == MXC_DMA_DYNAMIC_CHANNEL) {
/* Get the first free channel */
for (i = (MAX_DMA_CHANNELS – 1); i > 0; i–) {
/* See if channel is available */
if ((mxc_sdma_channels[i].dynamic != 1)
|| (mxc_sdma_channels[i].lock != 0)) {
continue;
}
channel_num = i;
/* Check to see if we can get this channel */
ret = mxc_request_dma(&channel_num, dev_name);
if (ret == 0) {
break;
} else {
continue;
}
}
if (ret != 0) {
/* No free channel */
goto err_ret;
}
} else {
if (mxc_sdma_channels[chnl->channel_num].lock == 1) {
ret = -ENODEV;
goto err_ret;
}
ret = mxc_request_dma(&channel_num, dev_name);
if (ret != 0) {
goto err_ret;
}
}
ret = mxc_dma_setup_channel(channel_num, &chnl->chnl_params);
if (ret == 0) {
if (chnl->chnl_priority != MXC_SDMA_DEFAULT_PRIORITY) {
ret =
mxc_dma_set_channel_priority(channel_num,
chnl->chnl_priority);
if (ret != 0) {
pr_info("Failed to set channel prority,\
continue with the existing \
priority\n");
goto err_ret;
}
}
mxc_sdma_channels[channel_num].lock = 1;
if ((chnl->chnl_params.transfer_type == per_2_emi)
|| (chnl->chnl_params.transfer_type == dsp_2_emi)) {
mxc_sdma_channels[channel_num].mode = MXC_DMA_MODE_READ;
} else {
mxc_sdma_channels[channel_num].mode =
MXC_DMA_MODE_WRITE;
}
mxc_sdma_channels[channel_num].channel = channel_id;
data_priv = mxc_sdma_channels[channel_num].private;
tasklet_init(&data_priv->chnl_tasklet,
mxc_sdma_channeltasklet, channel_num);
if ((channel_id == MXC_DMA_ATA_RX)
|| (channel_id == MXC_DMA_ATA_TX)) {
data_priv->intr_after_every_bd = 0;
} else {
data_priv->intr_after_every_bd = 1;
}
}
err_ret:
if (ret != 0) {
clk_disable(mxc_sdma_ahb_clk);
clk_disable(mxc_sdma_ipg_clk);
channel_num = -ENODEV;
}
return channel_num;
}
/*!
* This function would just configure the scatterlist specified by the
* user into dma channel. This is a slight variation of mxc_dma_config(),
* it is provided for the convenience of drivers that have a scatterlist
* passed into them. It is the calling driver's responsibility to have the
* correct physical address filled in the "dma_address" field of the
* scatterlist.
*
* @param channel_num the channel number returned at request time. This
* would be used by the DMA driver to identify the calling
* driver and do the necessary cleanup on the channel
* associated with the particular peripheral
* @param sg a scatterlist of buffers. The caller must guarantee
* the dma_buf is available until the transfer is
* completed.
* @param num_buf number of buffers in the array
* @param num_of_bytes total number of bytes to transfer. If set to 0, this
* would imply to use the length field of the scatterlist
* for each DMA transfer. Else it would calculate the size
* for each DMA transfer.
* @param mode specifies whether this is READ or WRITE operation
* @return This function returns a negative number on error if buffer could not
* be added with DMA for transfer. On Success, it returns 0
*/
int mxc_dma_sg_config(int channel_num, struct scatterlist *sg,
int num_buf, int num_of_bytes, mxc_dma_mode_t mode)
{
int ret = 0, i = 0;
mxc_dma_requestbuf_t *dma_buf;
if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
return -EINVAL;
}
if (mxc_sdma_channels[channel_num].lock != 1) {
return -ENODEV;
}
dma_buf =
(mxc_dma_requestbuf_t *) kmalloc(num_buf *
sizeof(mxc_dma_requestbuf_t),
GFP_KERNEL);
if (dma_buf == NULL) {
return -EFAULT;
}
for (i = 0; i < num_buf; i++) {
if (mode == MXC_DMA_MODE_READ) {
(dma_buf + i)->dst_addr = sg->dma_address;
} else {
(dma_buf + i)->src_addr = sg->dma_address;
}
if ((num_of_bytes > sg->length) || (num_of_bytes == 0)) {
(dma_buf + i)->num_of_bytes = sg->length;
} else {
(dma_buf + i)->num_of_bytes = num_of_bytes;
}
sg++;
num_of_bytes -= (dma_buf + i)->num_of_bytes;
}
ret = mxc_dma_config(channel_num, dma_buf, num_buf, mode);
kfree(dma_buf);
return ret;
}
/*!
* This function would just configure the buffers specified by the user into
* dma channel. The caller must call mxc_dma_enable to start this transfer.
*
* @param channel_num the channel number returned at request time. This
* would be used by the DMA driver to identify the calling
* driver and do the necessary cleanup on the channel
* associated with the particular peripheral
* @param dma_buf an array of physical addresses to the user defined
* buffers. The caller must guarantee the dma_buf is
* available until the transfer is completed.
* @param num_buf number of buffers in the array
* @param mode specifies whether this is READ or WRITE operation
* @return This function returns a negative number on error if buffer could not be
* added with DMA for transfer. On Success, it returns 0
*/
int mxc_dma_config(int channel_num, mxc_dma_requestbuf_t * dma_buf,
int num_buf, mxc_dma_mode_t mode)
{
int ret = 0, i = 0, prev_buf;
mxc_dma_channel_t *chnl_info;
mxc_dma_channel_private_t *data_priv;
mxc_sdma_channel_params_t *chnl;
dma_channel_params chnl_param;
dma_request_t request_t;
if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
return -EINVAL;
}
if (num_buf <= 0) {
return -EINVAL;
}
chnl_info = &mxc_sdma_channels[channel_num];
data_priv = chnl_info->private;
if (chnl_info->lock != 1) {
return -ENODEV;
}
/* Check to see if all buffers are taken */
if (chnl_info->active == 1) {
return -EBUSY;
}
chnl = mxc_sdma_get_channel_params(chnl_info->channel);
chnl_param = chnl->chnl_params;
/* Re-setup the SDMA channel if the transfer direction is changed */
if ((chnl_param.peripheral_type != MEMORY) && (mode != chnl_info->mode)) {
if (chnl_param.peripheral_type == DSP) {
if (mode == MXC_DMA_MODE_READ) {
chnl_param.transfer_type = dsp_2_emi;
} else {
chnl_param.transfer_type = emi_2_dsp;
}
} else if (chnl_param.peripheral_type == FIFO_MEMORY) {
if (mode == MXC_DMA_MODE_READ) {
chnl_param.per_address = MXC_FIFO_MEM_SRC_FIXED;
} else {
chnl_param.per_address =
MXC_FIFO_MEM_DEST_FIXED;
}
} else {
if (mode == MXC_DMA_MODE_READ) {
chnl_param.transfer_type = per_2_emi;
} else {
chnl_param.transfer_type = emi_2_per;
}
}
chnl_param.callback = mxc_dma_chnl_callback;
chnl_param.arg = (void *)channel_num;
ret = mxc_dma_setup_channel(channel_num, &chnl_param);
if (ret != 0) {
return ret;
}
if (chnl->chnl_priority != MXC_SDMA_DEFAULT_PRIORITY) {
ret =
mxc_dma_set_channel_priority(channel_num,
chnl->chnl_priority);
if (ret != 0) {
pr_info("Failed to set channel prority,\
continue with the existing \
priority\n");
}
}
chnl_info->mode = mode;
}
for (i = 0; i < num_buf; i++, dma_buf++) {
/* Check to see if all buffers are taken */
if (chnl_info->active == 1) {
break;
}
request_t.destAddr = (__u8 *) dma_buf->dst_addr;
request_t.sourceAddr = (__u8 *) dma_buf->src_addr;
request_t.count = dma_buf->num_of_bytes;
request_t.bd_cont = 1;
ret = mxc_dma_set_config(channel_num, &request_t,
chnl_info->curr_buf);
if (ret != 0) {
break;
}
if (data_priv->intr_after_every_bd == 0) {
if (i == num_buf – 1) {
mxc_dma_set_bd_intr(channel_num,
chnl_info->curr_buf, 1);
} else {
mxc_dma_set_bd_intr(channel_num,
chnl_info->curr_buf, 0);
}
}
prev_buf = chnl_info->curr_buf;
if ((chnl_info->curr_buf += 1) >= chnl_param.bd_number) {
chnl_info->curr_buf = 0;
}
if (chnl_info->curr_buf == data_priv->buf_tail) {
if ((data_priv->intr_after_every_bd == 0)
&& (i != num_buf – 1)) {
/*
* Set the BD_INTR flag on the last BD that
* was queued
*/
mxc_dma_set_bd_intr(channel_num, prev_buf, 1);
}
chnl_info->active = 1;
}
}
if (i == 0) {
return -EBUSY;
}
return 0;
}
/*!
* Callback function called from the SDMA Interrupt routine
*
* @param arg driver specific argument that was registered
*/
static void mxc_dma_chnl_callback(void *arg)
{
int priv;
mxc_dma_channel_private_t *data_priv;
priv = (int)arg;
data_priv = mxc_sdma_channels[priv].private;
/* Process the buffers in a tasklet */
tasklet_schedule(&data_priv->chnl_tasklet);
}
/*!
* Tasket to handle processing the channel buffers
*
* @param arg channel id
*/
static void mxc_sdma_channeltasklet(unsigned long arg)
{
dma_request_t request_t;
dma_channel_params chnl_param;
mxc_dma_channel_t *chnl_info;
mxc_dma_channel_private_t *data_priv;
int bd_intr = 0, error = MXC_DMA_DONE;
chnl_info = &mxc_sdma_channels[arg];
data_priv = chnl_info->private;
chnl_param =
mxc_sdma_get_channel_params(chnl_info->channel)->chnl_params;
mxc_dma_get_config(arg, &request_t, data_priv->buf_tail);
while (request_t.bd_done == 0) {
bd_intr = mxc_dma_get_bd_intr(arg, data_priv->buf_tail);
if ((data_priv->buf_tail += 1) >= chnl_param.bd_number) {
data_priv->buf_tail = 0;
}
chnl_info->active = 0;
if (request_t.bd_error) {
error = MXC_DMA_TRANSFER_ERROR;
}
if (bd_intr != 0) {
chnl_info->cb_fn(chnl_info->cb_args, error,
request_t.count);
error = MXC_DMA_DONE;
}
if (data_priv->buf_tail == chnl_info->curr_buf) {
break;
}
memset(&request_t, 0, sizeof(dma_request_t));
mxc_dma_get_config(arg, &request_t, data_priv->buf_tail);
}
}