本文主要讲述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);
    }
}