Logo Search packages:      
Sourcecode: linux version File versions  Download package

dma_sdma.c

Go to the documentation of this file.
/*
 * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @file plat-mxc/sdma/dma_sdma.c
 * @brief Front-end to the DMA handling.  This handles the allocation/freeing
 * of DMA channels, and provides a unified interface to the machines
 * DMA facilities. This file contains functions for Smart DMA.
 *
 * @ingroup SDMA
 */

#include <linux/init.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <mach/dma.h>
#include <mach/hardware.h>

#ifdef CONFIG_MXC_SDMA_API

static mxc_dma_channel_t mxc_sdma_channels[MAX_DMA_CHANNELS];
static mxc_dma_channel_private_t mxc_sdma_private[MAX_DMA_CHANNELS];

extern struct clk *mxc_sdma_ahb_clk, *mxc_sdma_ipg_clk;

/*!
 * 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);
      }
}

/*!
 * 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 is generally called by the driver at close time. The DMA
 * driver would do any cleanup associated with this channel.
 *
 * @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
 * @return returns a negative number on error or 0 on success
 */
int mxc_dma_free(int channel_num)
{
      mxc_dma_channel_private_t *data_priv;

      if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
            return -EINVAL;
      }

      if (mxc_sdma_channels[channel_num].lock != 1) {
            return -ENODEV;
      }

      mxc_free_dma(channel_num);

      /* Disable the SDMA clock */
      clk_disable(mxc_sdma_ahb_clk);
      clk_disable(mxc_sdma_ipg_clk);

      mxc_sdma_channels[channel_num].lock = 0;
      mxc_sdma_channels[channel_num].active = 0;
      mxc_sdma_channels[channel_num].curr_buf = 0;
      data_priv = mxc_sdma_channels[channel_num].private;
      data_priv->buf_tail = 0;
      tasklet_kill(&data_priv->chnl_tasklet);

      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);
}

/*!
 * 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;
}

/*!
 * 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 is provided if the driver would like to set/change its
 * callback function.
 *
 * @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 callback     a callback function to provide notification on transfer
 *                     completion, user could specify NULL if he does not wish
 *                     to be notified
 * @param arg          an argument that gets passed in to the callback
 *                     function, used by the user to do any driver specific
 *                     operations.
 * @return this function returns a negative number on error if the callback
 *         could not be set for the channel or 0 on success
 */
int mxc_dma_callback_set(int channel_num,
                   mxc_dma_callback_t callback, void *arg)
{
      if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
            return -EINVAL;
      }

      if (mxc_sdma_channels[channel_num].lock != 1) {
            return -ENODEV;
      }

      mxc_sdma_channels[channel_num].cb_fn = callback;
      mxc_sdma_channels[channel_num].cb_args = arg;

      mxc_dma_set_callback(channel_num, mxc_dma_chnl_callback,
                       (void *)channel_num);

      return 0;
}

/*!
 * This stops the DMA channel and any ongoing transfers. Subsequent use of
 * mxc_dma_enable() will restart the channel and restart the 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
 * @return returns a negative number on error or 0 on success
 */
int mxc_dma_disable(int channel_num)
{
      if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
            return -EINVAL;
      }

      if (mxc_sdma_channels[channel_num].lock != 1) {
            return -ENODEV;
      }

      mxc_dma_stop(channel_num);
      return 0;
}

/*!
 * This starts DMA transfer. Or it restarts DMA on a stopped channel
 * previously stopped with mxc_dma_disable().
 *
 * @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
 * @return returns a negative number on error or 0 on success
 */
int mxc_dma_enable(int channel_num)
{
      if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
            return -EINVAL;
      }

      if (mxc_sdma_channels[channel_num].lock != 1) {
            return -ENODEV;
      }

      mxc_dma_start(channel_num);
      return 0;
}

/*!
 * Initializes dma structure with dma_operations
 *
 * @param   dma           dma structure
 * @return  returns 0 on success
 */
static int __init mxc_dma_init(void)
{
      int i;
      for (i = 0; i < MAX_DMA_CHANNELS; i++) {
            mxc_sdma_channels[i].active = 0;
            mxc_sdma_channels[i].lock = 0;
            mxc_sdma_channels[i].curr_buf = 0;
            mxc_sdma_channels[i].dynamic = 1;
            mxc_sdma_private[i].buf_tail = 0;
            mxc_sdma_channels[i].private = &mxc_sdma_private[i];
      }
      /*
       * Make statically allocated channels unavailable for dynamic channel
       * requests
       */
      mxc_get_static_channels(mxc_sdma_channels);

      return 0;
}

arch_initcall(mxc_dma_init);

#else
00558 int mxc_request_dma(int *channel, const char *devicename)
{
      return -ENODEV;
}

00563 int mxc_dma_setup_channel(int channel, dma_channel_params * p)
{
      return -ENODEV;
}

00568 int mxc_dma_set_channel_priority(unsigned int channel, unsigned int priority)
{
      return -ENODEV;
}

00573 int mxc_dma_set_config(int channel, dma_request_t * p, int bd_index)
{
      return -ENODEV;
}

00578 int mxc_dma_get_config(int channel, dma_request_t * p, int bd_index)
{
      return -ENODEV;
}

00583 int mxc_dma_start(int channel)
{
      return -ENODEV;
}

00588 int mxc_dma_stop(int channel)
{
      return -ENODEV;
}

00593 void mxc_free_dma(int channel)
{
}

00597 void mxc_dma_set_callback(int channel, dma_callback_t callback, void *arg)
{
}

00601 void *sdma_malloc(size_t size)
{
      return 0;
}

00606 void sdma_free(void *buf)
{
}

00610 void *sdma_phys_to_virt(unsigned long buf)
{
      return 0;
}

00615 unsigned long sdma_virt_to_phys(void *buf)
{
      return 0;
}

00620 int mxc_dma_request(mxc_dma_device_t channel_id, char *dev_name)
{
      return -ENODEV;
}

00625 int mxc_dma_free(int channel_num)
{
      return -ENODEV;
}

00630 int mxc_dma_config(int channel_num, mxc_dma_requestbuf_t * dma_buf,
               int num_buf, mxc_dma_mode_t mode)
{
      return -ENODEV;
}

00636 int mxc_dma_sg_config(int channel_num, struct scatterlist *sg,
                  int num_buf, int num_of_bytes, mxc_dma_mode_t mode)
{
      return -ENODEV;
}

00642 int mxc_dma_callback_set(int channel_num, mxc_dma_callback_t callback,
                   void *arg)
{
      return -ENODEV;
}

00648 int mxc_dma_disable(int channel_num)
{
      return -ENODEV;
}

00653 int mxc_dma_enable(int channel_num)
{
      return -ENODEV;
}

EXPORT_SYMBOL(mxc_request_dma);
EXPORT_SYMBOL(mxc_dma_setup_channel);
EXPORT_SYMBOL(mxc_dma_set_channel_priority);
EXPORT_SYMBOL(mxc_dma_set_config);
EXPORT_SYMBOL(mxc_dma_get_config);
EXPORT_SYMBOL(mxc_dma_start);
EXPORT_SYMBOL(mxc_dma_stop);
EXPORT_SYMBOL(mxc_free_dma);
EXPORT_SYMBOL(mxc_dma_set_callback);
EXPORT_SYMBOL(sdma_malloc);
EXPORT_SYMBOL(sdma_free);
EXPORT_SYMBOL(sdma_phys_to_virt);
EXPORT_SYMBOL(sdma_virt_to_phys);

#endif

EXPORT_SYMBOL(mxc_dma_request);
EXPORT_SYMBOL(mxc_dma_free);
EXPORT_SYMBOL(mxc_dma_config);
EXPORT_SYMBOL(mxc_dma_sg_config);
EXPORT_SYMBOL(mxc_dma_callback_set);
EXPORT_SYMBOL(mxc_dma_disable);
EXPORT_SYMBOL(mxc_dma_enable);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MXC Linux SDMA API");
MODULE_LICENSE("GPL");

Generated by  Doxygen 1.6.0   Back to index