Logo Search packages:      
Sourcecode: linux version File versions

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/sdma.c
 * @brief This file contains functions for Smart DMA  API
 *
 * SDMA (Smart DMA) is used for transferring data between MCU and peripherals
 *
 * @ingroup SDMA
 */

#include <linux/init.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/semaphore.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>

#include <asm/uaccess.h>
#include <asm/irq.h>
#include <mach/dma.h>
#include <mach/hardware.h>

#include "iapi.h"

#define M3_BASE_ADDRESS CSD0_BASE_ADDR
#define CHAD(ch) sdma_data[0].cd->ccb_ptr[ch].channelDescriptor

/*!
 * SDMA status mutex
 */
00047 static struct semaphore sdma_status_mutex;

/*!
 * SDMA channel sleep queues
 */
//static struct semaphore sdma_sleep_mutex[MAX_DMA_CHANNELS];
00053 static wait_queue_head_t sdma_sleep_queue[MAX_DMA_CHANNELS];

/*!
 * SDMA channel synchronization
 */
00058 static struct semaphore sdma_synch_mutex[MAX_DMA_CHANNELS];

/*!
 * SDMA buffers pool initialization function
 */
extern void init_sdma_pool(void);

/*!
 * Flags are save and restored during interrupt handler
 */
00068 unsigned long flags;

struct clk *mxc_sdma_ahb_clk, *mxc_sdma_ipg_clk;

/*!
 * Structure containing sdma channels information.
 */
00075 typedef struct {
      /*! Channel number */
00077       int channel;
      /*! Channel usage name */
00079       int in_use;
      /*! Name of device using the channel */
00081       char devicename[MAX_DEVNAME_LENGTH];
      /*! Transfer type. Needed for setting SDMA script */
00083       sdma_transferT transfer_type;
      /*! Peripheral type. Needed for setting SDMA script */
00085       sdma_periphT peripheral_type;
      /*! Watermark level of device's fifo */
00087       __u32 watermark_level;
      /*! Peripheral event id */
00089       int event_id;
      /*! Peripheral event id2 (for channels that use 2 events) */
00091       int event_id2;
      /*! Running status (boolean)  */
00093       int running;
      /*! buffer descriptors number */
00095       int bd_number;
      /*!   callback function       */
00097       dma_callback_t callback;
      /*!   callback argument       */
00099       void *arg;
      /*! SDMA data access word size */
00101       unsigned long word_size:8;
      /*! channel descriptor pointer */
00103       channelDescriptor *cd;
} sdma_struct;

/*!
 * Used to save the status of channels.
 */
00109 static sdma_struct sdma_data[MAX_DMA_CHANNELS];

/*!
 * Stores the start address of the SDMA scripts
 */
00114 static sdma_script_start_addrs sdma_script_addrs;

extern void mxc_sdma_get_script_info(sdma_script_start_addrs * sdma_script_add);

/*!
 * Init sleep mutex of the channel
 *
 * @param  channel  channel number
 */
00123 static void sdma_init_sleep(int channel)
{
      init_waitqueue_head(&sdma_sleep_queue[channel]);
}

/*!
 * Puts channel to sleep
 *
 * @param  channel  channel number
 */
00133 static void sdma_sleep_channel(int channel)
{
      while ((iapi_SDMAIntr & (1 << channel)) == 0) {
            wait_event_interruptible(sdma_sleep_queue[channel],
                               ((iapi_SDMAIntr & (1 << channel)) !=
                                0));
      }
}

/*!
 * Wake up channel from sleep
 *
 * @param  channel  channel number
 */
00147 static void sdma_wakeup_channel(int channel)
{
      wake_up_interruptible(&sdma_sleep_queue[channel]);
}

/*!
 * Sdma interrupt handler routine.
 * Calls channels callback function
 *
 * @param   irq    the interrupt number
 * @param   dev_id driver private data
 * @return the function returns \b IRQ_RETVAL(1) -  interrupt was handled
 */
00160 static irqreturn_t sdma_int_handler(int irq, void *dev_id)
{
      IRQ_Handler();
      return IRQ_RETVAL(1);
}

/*!
 * I.API channel callback function
 *
 * @param   cd            channel descriptor structure
 * @param   channel_data  SDMA struct of the current channel
 */
00172 static void iapi_interrupt_callback(channelDescriptor * cd,
                            sdma_struct * channel_data)
{
      int channel;
      dma_callback_t callback;
      void *arg;

      channel = channel_data->channel;

      channel_data->running = 0;

      arg = channel_data->arg;

      if (arg == 0) {
            arg = (void *)&channel;
      }

      callback = channel_data->callback;

      if (callback != 0) {
            callback(arg);
      }
}

/*!
 * Returns pc of SDMA script according to peripheral and transfer type
 *
 * @param   peripheral_type   peripheral type
 * @param   transfer_type     transfer type
 *
 * @return  PC of SDMA script
*/
00204 static unsigned short sdma_get_pc(sdma_periphT peripheral_type,
                          sdma_transferT transfer_type)
{
      int res = 0;

      if (peripheral_type == MEMORY) {
            switch (transfer_type) {
            case emi_2_int:
                  res = sdma_script_addrs.mxc_sdma_ap_2_ap_addr;
                  break;
            case emi_2_emi:
                  res = sdma_script_addrs.mxc_sdma_ap_2_ap_addr;
                  break;
            case int_2_emi:
                  res = sdma_script_addrs.mxc_sdma_ap_2_ap_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == DSP) {
            switch (transfer_type) {
            case emi_2_dsp:
                  res = sdma_script_addrs.mxc_sdma_ap_2_bp_addr;
                  break;
            case dsp_2_emi:
                  res = sdma_script_addrs.mxc_sdma_bp_2_ap_addr;
                  break;
            case dsp_2_emi_loop:
                  res =
                      sdma_script_addrs.
                      mxc_sdma_loopback_on_dsp_side_addr;
                  break;
            case emi_2_dsp_loop:
                  res =
                      sdma_script_addrs.mxc_sdma_mcu_interrupt_only_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == FIRI) {
            switch (transfer_type) {
            case per_2_int:
                  res = sdma_script_addrs.mxc_sdma_firi_2_per_addr;
                  break;
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_firi_2_mcu_addr;
                  break;
            case int_2_per:
                  res = sdma_script_addrs.mxc_sdma_per_2_firi_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_firi_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == UART) {
            switch (transfer_type) {
            case per_2_int:
                  res = sdma_script_addrs.mxc_sdma_uart_2_per_addr;
                  break;
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_uart_2_mcu_addr;
                  break;
            case int_2_per:
                  res = sdma_script_addrs.mxc_sdma_per_2_app_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_app_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == UART_SP) {
            switch (transfer_type) {
            case per_2_int:
                  res = sdma_script_addrs.mxc_sdma_uartsh_2_per_addr;
                  break;
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_uartsh_2_mcu_addr;
                  break;
            case int_2_per:
                  res = sdma_script_addrs.mxc_sdma_per_2_shp_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_shp_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == ATA) {
            switch (transfer_type) {
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_ata_2_mcu_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_ata_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == CSPI || peripheral_type == EXT ||
               peripheral_type == SSI) {
            switch (transfer_type) {
            case per_2_int:
                  res = sdma_script_addrs.mxc_sdma_app_2_per_addr;
                  break;
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_app_2_mcu_addr;
                  break;
            case int_2_per:
                  res = sdma_script_addrs.mxc_sdma_per_2_app_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_app_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == SSI_SP || peripheral_type == MMC ||
               peripheral_type == SDHC || peripheral_type == CSPI_SP ||
               peripheral_type == ESAI || peripheral_type == MSHC_SP) {
            switch (transfer_type) {
            case per_2_int:
                  res = sdma_script_addrs.mxc_sdma_shp_2_per_addr;
                  break;
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_shp_2_mcu_addr;
                  break;
            case int_2_per:
                  res = sdma_script_addrs.mxc_sdma_per_2_shp_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_shp_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == ASRC) {
            switch (transfer_type) {
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_shp_2_mcu_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_shp_addr;
                  break;
            case per_2_per:
                  res = sdma_script_addrs.mxc_sdma_per_2_per_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == MSHC) {
            switch (transfer_type) {
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_mshc_2_mcu_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_mshc_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == CCM) {
            switch (transfer_type) {
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_dptc_dvfs_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == FIFO_MEMORY) {
            res = sdma_script_addrs.mxc_sdma_ap_2_ap_fixed_addr;
      } else if (peripheral_type == SPDIF) {
            switch (transfer_type) {
            case per_2_emi:
                  res = sdma_script_addrs.mxc_sdma_spdif_2_mcu_addr;
                  break;
            case emi_2_per:
                  res = sdma_script_addrs.mxc_sdma_mcu_2_spdif_addr;
                  break;
            default:
                  res = -EINVAL;
            }
      } else if (peripheral_type == IPU_MEMORY) {
            if (transfer_type == emi_2_per) {
                  res = sdma_script_addrs.mxc_sdma_ext_mem_2_ipu_addr;
            } else {
                  res = -EINVAL;
            }
      }

      if (res < 0) {
            printk(KERN_ERR "SDMA script not found\n");
      }

      return res;

}

/*!
 * Downloads channel context according to channel parameters
 *
 * @param   channel           channel number
 * @param   p                 channel parameters
 */
00410 static int sdma_load_context(int channel, dma_channel_params * p)
{
      script_data context;
      int res;
      int event1_greater_than_32;
      int event2_greater_than_32;

      res = 0;

      memset(&context, 0, sizeof(script_data));
      context.load_address = sdma_get_pc(p->peripheral_type,
                                 p->transfer_type);

      if (context.load_address > 0) {
            if ((p->peripheral_type != MEMORY)
                && (p->peripheral_type != DSP)) {
                  /* Handle multiple event channels differently */
                  if (p->event_id2) {
                        if (p->event_id2 < 32) {
                              context.event_mask2 =
                                  0x1 << p->event_id2;
                              event2_greater_than_32 = 0;
                        } else {
                              context.event_mask2 =
                                  0x1 << (p->event_id2 - 32);
                              event2_greater_than_32 = 1 << 31;
                        }
                        if (p->event_id < 32) {
                              context.event_mask1 =
                                  0x1 << p->event_id;
                              event1_greater_than_32 = 0;
                        } else {
                              context.event_mask1 =
                                  0x1 << (p->event_id - 32);
                              event1_greater_than_32 = 1 << 30;
                        }
                  } else {
                        event1_greater_than_32 = 0;
                        event2_greater_than_32 = 0;
                        if (p->event_id < 32) {
                              context.event_mask1 =
                                  0x1 << p->event_id;
                              context.event_mask2 = 0;
                        } else {
                              context.event_mask1 = 0;
                              context.event_mask2 =
                                  0x1 << (p->event_id - 32);
                        }
                  }
                  /* Watermark Level */
                  context.wml =
                      event2_greater_than_32 | event1_greater_than_32 |
                      p->watermark_level;

                  /* Address */
                  context.shp_addr = (unsigned long)(p->per_address);
                  iapi_IoCtl(sdma_data[channel].cd,
                           IAPI_CHANGE_PERIPHADDR, p->per_address);
            } else {
                  context.wml = M3_BASE_ADDRESS;
            }

            sdma_data[channel].transfer_type = p->transfer_type;
            sdma_data[channel].peripheral_type = p->peripheral_type;
            sdma_data[channel].watermark_level = p->watermark_level;
            iapi_AssignScript(sdma_data[channel].cd, &context);
      } else {
            res = context.load_address;
      }

      return res;
}

/*!
 * Setup channel according to parameters. Must be called once after mxc_request_dma()
 *
 * @param   channel           channel number
 * @param   p                 channel parameters pointer
 * @return  0 on success, error code on fail
 */
00490 int mxc_dma_setup_channel(int channel, dma_channel_params * p)
{
      int err = 0;
      int i;

      mxc_dma_stop(channel);

      for (i = 0; i < sdma_data[channel].bd_number; i++) {
            iapi_IoCtl(sdma_data[channel].cd,
                     (i << BD_NUM_OFFSET) |
                     IAPI_CHANGE_SET_STATUS, (unsigned long)0);
      }

      sdma_data[channel].bd_number = (p->bd_number <= 0) ? 1 : p->bd_number;

      sdma_data[channel].word_size = p->word_size;

      sdma_data[channel].event_id = p->event_id;
      sdma_data[channel].event_id2 = p->event_id2;

      sdma_data[channel].callback = p->callback;

      sdma_data[channel].arg = p->arg;

      err = iapi_IoCtl(sdma_data[channel].cd,
                   IAPI_CHANGE_BDNUM, sdma_data[channel].bd_number);

      if (err < 0) {
            printk(KERN_ERR "Failed allocating buffer \
descriptors (0x%x)\n", err);
            err = -ENOMEM;
            goto setup_channel_fail;
      }

      if (channel != 0) {
            switch (p->transfer_type) {
            case dsp_2_per:
                  break;
            case emi_2_per:
            case int_2_per:
            case per_2_int:
            case per_2_emi:
                  /*
                   * Peripheral <------> Memory
                   * evtOvr = 0 dspOvr = 1
                   */
                  iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP,
                           (OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) |
                           (OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) |
                           (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP));
                  if (p->event_id) {
                        err = iapi_SetChannelEventMapping(p->event_id,
                                                  0x1 <<
                                                  channel);
                  }
                  if (!err && p->event_id2) {
                        err = iapi_SetChannelEventMapping(p->event_id2,
                                                  0x1 <<
                                                  channel);
                  }
                  break;
            case emi_2_dsp:
            case int_2_dsp:
            case dsp_2_int:
            case dsp_2_emi:
            case dsp_2_dsp:
                  /*
                   * DSP <-----------> Memory
                   * evtOvr = 1 dspOvr = 0
                   */
                  iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP,
                           (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) |
                           (OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) |
                           (OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP));
                  break;
            case emi_2_int:
            case emi_2_emi:
            case int_2_int:
            case int_2_emi:
            case emi_2_dsp_loop:
            case dsp_2_emi_loop:
                  /* evtOvr = 1 dspOvr = 1 */
                  iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP,
                           (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) |
                           (OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) |
                           (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP));
                  break;
            case per_2_dsp:
                  /* evtOvr = 0 dspOvr = 0 */
                  iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP,
                           (OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) |
                           (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) |
                           (OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP));
                  err = iapi_SetChannelEventMapping(p->event_id,
                                            0x1 << channel);
                  break;
            default:
                  break;
                  printk(KERN_ERR "Wrong SDMA transfer type\n");
                  err = -EINVAL;
            }
            if (err == 0) {
                  err = sdma_load_context(channel, p);
                  iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_PRIORITY,
                           MXC_SDMA_DEFAULT_PRIORITY);
            }
      }
      setup_channel_fail:
      return err;
}

/*!
 * Setup the channel priority. This can be used to change the default priority
 * for the channel.
 *
 * @param   channel           channel number
 * @param   priority          priority to be set for the channel
 *
 * @return  0 on success, error code on failure
 */
00610 int mxc_dma_set_channel_priority(unsigned int channel, unsigned int priority)
{
      if (priority < MXC_SDMA_MIN_PRIORITY
          || priority > MXC_SDMA_MAX_PRIORITY) {
            return -EINVAL;
      }
      return iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_PRIORITY,
                    priority);
}

/*!
 * Allocates dma channel.
 * If channel's value is 0, then the function allocates a free channel
 * dynamically and sets its value to channel.
 * Else allocates requested channel if it is free.
 * If the channel is busy or no free channels (in dynamic allocation) -EBUSY returned.
 *
 * @param   channel           pointer to channel number
 * @param   devicename        device name
 * @return  0 on success, error code on fail
 */
00631 int mxc_request_dma(int *channel, const char *devicename)
{
      int i, res;

      res = 0;

      down(&sdma_status_mutex);

      /* Dynamic allocation */
      if (*channel == 0) {
            for (i = MAX_DMA_CHANNELS - 1; i > 0; i--) {
#ifdef CONFIG_SDMA_IRAM
                  /*TODO:It will be removed after DPTC used UDMA interface */
                  if (i >= MXC_DMA_CHANNEL_IRAM)
                        continue;
#endif                        /*CONFIG_SDMA_IRAM */
                  if (!sdma_data[i].in_use) {
                        *channel = i;
                        break;
                  }
            }
      }

      if (*channel > 0 && *channel < MAX_DMA_CHANNELS &&
          sdma_data[*channel].in_use == 0) {
            res = iapi_Open(sdma_data[0].cd, *channel);

            if (res < 0) {
                  printk(KERN_ERR "Failed iapi_Open channel %d, 0x%x\n",
                         *channel, res);
            } else {
                  sdma_data[*channel].in_use = 1;
                  strcpy(sdma_data[*channel].devicename, devicename);
                  sdma_data[*channel].cd = CHAD(*channel);

                  iapi_IoCtl(sdma_data[*channel].cd, IAPI_CHANGE_SYNCH,
                           CALLBACK_ISR);
                  iapi_IoCtl(sdma_data[*channel].cd,
                           IAPI_CHANGE_CALLBACKFUNC,
                           (unsigned long)iapi_interrupt_callback);
                  iapi_IoCtl(sdma_data[*channel].cd,
                           IAPI_CHANGE_USER_ARG,
                           (unsigned long)&(sdma_data[*channel]));
            }
      } else {
            res = -EBUSY;
      }

      up(&sdma_status_mutex);

      return res;
}

/*!
 * Configures request parameters. Can be called multiple times after
 * mxc_request_dma() and mxc_dma_setup_channel().
 *
 *
 * @param   channel           channel number
 * @param   p                 request parameters pointer
 * @param   bd_index          index of buffer descriptor to set
 * @return  0 on success, error code on fail
 */
00694 int mxc_dma_set_config(int channel, dma_request_t * p, int bd_index)
{
      unsigned char param;

      if (!sdma_data[channel].in_use) {
            return -EINVAL;
      }

      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) |
               IAPI_CHANGE_SET_TRANSFER_CD, sdma_data[channel].word_size);

      param = BD_DONE | BD_INTR | BD_EXTD;

      if (sdma_data[channel].bd_number > 1 && p->bd_cont == 1) {
            param |= BD_CONT;
      }

      if (bd_index == sdma_data[channel].bd_number - 1) {
            param |= BD_WRAP;
      }

      switch (sdma_data[channel].transfer_type) {
      case emi_2_per:
      case dsp_2_per:
      case int_2_per:
      case emi_2_dsp:
      case int_2_dsp:
      case emi_2_dsp_loop:
            iapi_IoCtl(sdma_data[channel].cd,
                     (bd_index << BD_NUM_OFFSET) |
                     IAPI_CHANGE_SET_BUFFERADDR,
                     (unsigned long)p->sourceAddr);
            break;
      case per_2_int:
      case per_2_emi:
      case per_2_dsp:
      case dsp_2_int:
      case dsp_2_emi:
      case dsp_2_dsp:
      case dsp_2_emi_loop:
            iapi_IoCtl(sdma_data[channel].cd,
                     (bd_index << BD_NUM_OFFSET) |
                     IAPI_CHANGE_SET_BUFFERADDR,
                     (unsigned long)p->destAddr);
            break;
      case emi_2_int:
      case emi_2_emi:
      case int_2_int:
      case int_2_emi:
            iapi_IoCtl(sdma_data[channel].cd,
                     (bd_index << BD_NUM_OFFSET) |
                     IAPI_CHANGE_SET_BUFFERADDR,
                     (unsigned long)p->sourceAddr);
            iapi_IoCtl(sdma_data[channel].cd,
                     (bd_index << BD_NUM_OFFSET) |
                     IAPI_CHANGE_SET_EXTDBUFFERADDR,
                     (unsigned long)p->destAddr);
            break;
      default:
            break;
      }

      /* Change the endianness for DSP to MCU Data transfers */
      if (sdma_data[channel].transfer_type == dsp_2_emi ||
          sdma_data[channel].transfer_type == emi_2_dsp) {
            iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_SET_ENDIANNESS,
                     SET_BIT_ALL);
      }

      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) |
               IAPI_CHANGE_SET_COUNT, p->count);

      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) | IAPI_CHANGE_SET_STATUS, param);

      return 0;
}

/*!
 * Configures the BD_INTR bit on a buffer descriptor parameters.
 *
 *
 * @param   channel           channel number
 * @param   bd_index          index of buffer descriptor to set
 * @param   bd_intr           flag to set or clear the BD_INTR bit
 * @return  0 on success, error code on fail
 */
00783 void mxc_dma_set_bd_intr(int channel, int bd_index, int bd_intr)
{
      unsigned long param;

      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) |
               IAPI_CHANGE_GET_STATUS, (unsigned long)&param);

      if (bd_intr) {
            param |= BD_INTR;
      } else {
            param &= ~BD_INTR;
      }
      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) | IAPI_CHANGE_SET_STATUS, param);

}

/*!
 * Gets the BD_INTR bit on a buffer descriptor.
 *
 *
 * @param   channel           channel number
 * @param   bd_index          index of buffer descriptor to set
 *
 * @return returns the BD_INTR bit status
 */
00810 int mxc_dma_get_bd_intr(int channel, int bd_index)
{
      unsigned long bd_status = 0;

      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) |
               IAPI_CHANGE_GET_STATUS, (unsigned long)&bd_status);

      return (bd_status & BD_INTR);
}

/*!
 * Stop the current transfer
 *
 * @param   channel           channel number
 * @param   buffer_number     number of buffers (beginning with 0),
 *                            whose done bits should be reset to 0
 */
00828 int mxc_dma_reset(int channel, int buffer_number)
{
      unsigned char param = 0;
      int i = 0;

      if (!sdma_data[channel].in_use) {
            return -EINVAL;
      }

      /* clear the BD_DONE bits for all the necessary buffers */
      for (i = 0; i < buffer_number; i++) {

            iapi_IoCtl(sdma_data[channel].cd, (i << BD_NUM_OFFSET) |
                     IAPI_CHANGE_GET_STATUS, (unsigned long)&param);

            /* clear the BD_DONE bit of the buffer */
            param = param & (~BD_DONE);

            iapi_IoCtl(sdma_data[channel].cd, (i << BD_NUM_OFFSET) |
                     IAPI_CHANGE_SET_STATUS, param);
      }

      return 0;
}

/*!
 * Returns request parameters.
 *
 * @param   channel           channel number
 * @param   p                 request parameters pointer
 * @param   bd_index          index of buffer descriptor to get
 * @return  0 on success, error code on fail
 */
00861 int mxc_dma_get_config(int channel, dma_request_t * p, int bd_index)
{
      int err = 0;
      unsigned long bd_status;
      unsigned long bd_count;
      __u8 *sourceAddr;
      __u8 *destAddr;

      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) |
               IAPI_CHANGE_GET_STATUS, (unsigned long)&bd_status);
      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) |
               IAPI_CHANGE_GET_COUNT, (unsigned long)&bd_count);
      iapi_IoCtl(sdma_data[channel].cd,
               (bd_index << BD_NUM_OFFSET) |
               IAPI_CHANGE_GET_BUFFERADDR, (unsigned long)&sourceAddr);

      switch (sdma_data[channel].transfer_type) {
      case emi_2_per:
      case dsp_2_per:
      case int_2_per:
      case emi_2_dsp:
      case int_2_dsp:
      case emi_2_dsp_loop:
            p->sourceAddr = sourceAddr;
            break;
      case per_2_int:
      case per_2_emi:
      case per_2_dsp:
      case dsp_2_int:
      case dsp_2_emi:
      case dsp_2_dsp:
      case dsp_2_emi_loop:
            p->destAddr = sourceAddr;
            break;
      case emi_2_int:
      case emi_2_emi:
      case int_2_int:
      case int_2_emi:
            p->sourceAddr = sourceAddr;
            iapi_IoCtl(sdma_data[channel].cd,
                     (bd_index << BD_NUM_OFFSET) |
                     IAPI_CHANGE_GET_EXTDBUFFERADDR,
                     (unsigned long)&destAddr);
            p->destAddr = destAddr;
            break;
      default:
            break;
      }

      p->count = bd_count;
      p->bd_done = bd_status & BD_DONE;
      p->bd_cont = bd_status & BD_CONT;
      p->bd_error = bd_status & BD_RROR;

      return err;
}

/*!
 * This function is used by MXC IPC's write_ex2. It passes the pointer to the
 * data control structure to iapi_write_ipcv2()
 *
 * @param channel  SDMA channel number
 * @param ctrl_ptr Data Control structure pointer
 */
00927 int mxc_sdma_write_ipcv2(int channel, void *ctrl_ptr)
{
      return iapi_Write_ipcv2(sdma_data[channel].cd, ctrl_ptr);
}

/*!
 * This function is used by MXC IPC's read_ex2. It passes the pointer to the
 * data control structure to iapi_read_ipcv2()
 *
 * @param channel   SDMA channel number
 * @param ctrl_ptr  Data Control structure pointer
 */
00939 int mxc_sdma_read_ipcv2(int channel, void *ctrl_ptr)
{
      return iapi_Read_ipcv2(sdma_data[channel].cd, ctrl_ptr);
}

/*!
 * Starts dma channel.
 *
 * @param   channel           channel number
 */
00949 int mxc_dma_start(int channel)
{
      if (sdma_data[channel].running == 0) {
            sdma_data[channel].running = 1;
            iapi_StartChannel(channel);
      }

      return 0;
}

/*!
 * Stops dma channel.
 *
 * @param   channel           channel number
 */
00964 int mxc_dma_stop(int channel)
{
      iapi_StopChannel(channel);
      sdma_data[channel].running = 0;

      return 0;
}

/*!
 * Frees dma channel.
 *
 * @param   channel           channel number
 */
00977 void mxc_free_dma(int channel)
{
      int i;

      mxc_dma_stop(channel);

      if (sdma_data[channel].event_id != 0) {
            iapi_SetChannelEventMapping(sdma_data[channel].event_id, 0x0);
      }
      if (sdma_data[channel].event_id2 != 0) {
            iapi_SetChannelEventMapping(sdma_data[channel].event_id2, 0x0);
      }

      sdma_data[channel].event_id = 0;

      iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_PRIORITY, 0x0);
      iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP,
               (OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) |
               (OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) |
               (OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP));

      for (i = 0; i < sdma_data[channel].bd_number; i++) {
            iapi_IoCtl(sdma_data[channel].cd,
                     (i << BD_NUM_OFFSET) |
                     IAPI_CHANGE_SET_STATUS, (unsigned long)0);
      }

      iapi_Close(sdma_data[channel].cd);

      strcpy(sdma_data[channel].devicename, "not used");

      sdma_data[channel].in_use = 0;
}

/*!
 * Initializes channel's priorities
 *
 */
01015 static void __init init_priorities(void)
{
      iapi_IoCtl(sdma_data[0].cd, IAPI_CHANGE_PRIORITY, 0x7);
}

/*!
 * Initializes events table
 */
01023 static void __init init_event_table(void)
{
      int channel;

      for (channel = 0; channel < MAX_DMA_CHANNELS; channel++) {
            iapi_SetChannelEventMapping(channel, 0);
      }
}

/*!
 * Sets callback function. Used with standard dma api
 * for supporting interrupts
 *
 * @param   channel           channel number
 * @param   callback          callback function pointer
 * @param   arg               argument for callback function
 */
01040 void mxc_dma_set_callback(int channel, dma_callback_t callback, void *arg)
{
      sdma_data[channel].callback = callback;
      sdma_data[channel].arg = arg;
}

/*!
 * Synchronization function used by I.API
 *
 * @param channel        channel number
 */
01051 static int getChannel(int channel)
{
      if (irqs_disabled() || in_atomic()) {
            if (down_trylock(&sdma_synch_mutex[channel])) {
                  return -EBUSY;
            }
      } else {
            if (down_interruptible(&sdma_synch_mutex[channel])) {
                  return -EBUSY;
            }
      }

      return 0;
}

/*!
 * Synchronization function used by I.API
 *
 * @param channel        channel number
 */
01071 static int releaseChannel(int channel)
{
      up(&sdma_synch_mutex[channel]);
      return 0;
}

/*!
 * Unmask interrupt function. Used by I.API
 *
 */
01081 static void unmask_sdma_interrupt(void)
{
      /* Commented out tp take care of the PREEMPT_RT option
       * local_irq_restore(flags);
       */
}

/*!
 * Mask interrupt function. Used by I.API
 *
 */
01092 static void mask_sdma_interrupt(void)
{
      /* Commented to take of the PREEMPT_RT option
       * local_irq_save(flags);
       */
}

/*!
 * Initializes I.API
 */
01102 static void __init init_iapi_struct(void)
{
      channelDescriptor *cd;

      printk(KERN_INFO "Using SDMA I.API\n");

      iapi_Malloc = &sdma_malloc;
#ifdef CONFIG_SDMA_IRAM
      iapi_iram_Malloc = &sdma_iram_malloc;
#endif                        /*CONFIG_SDMA_IRAM */

      iapi_Free = &sdma_free;
      iapi_Virt2Phys = (void *(*)(void *))&sdma_virt_to_phys;
      iapi_Phys2Virt = (void *(*)(void *))&sdma_phys_to_virt;
      iapi_memset = &memset;
      iapi_memcpy = &memcpy;

      iapi_GotoSleep = &sdma_sleep_channel;
      iapi_WakeUp = &sdma_wakeup_channel;
      iapi_InitSleep = &sdma_init_sleep;
      iapi_ReleaseChannel = &releaseChannel;
      iapi_GetChannel = &getChannel;

      iapi_EnableInterrupts = &unmask_sdma_interrupt;
      iapi_DisableInterrupts = &mask_sdma_interrupt;

      cd = kmalloc(sizeof(channelDescriptor), GFP_KERNEL);

      memset(cd, 0, sizeof(channelDescriptor));

      sdma_data[0].cd = cd;
}

/*!
 * Initializes channel synchronization mutexes
 */
01138 static void __init init_mutexes(void)
{
      int i;

      for (i = 0; i < MAX_DMA_CHANNELS; i++) {
            init_MUTEX(&sdma_synch_mutex[i]);
      }

      init_MUTEX(&sdma_status_mutex);
}

/*!
 * Channels status read proc file system function
 *
 * @param    buf  pointer to the buffer the data shuld be written to.
 * @param    start      pointer to the pointer where the new data is
 *                      written to.
 *                procedure should update the start pointer to point to
 *                where in the buffer the data was written.
 * @param    offset     offset from start of the file
 * @param    count      number of bytes to read.
 * @param    eof  pointer to eof flag. sould be set to 1 when
 *                      reaching eof.
 * @param    data driver specific data pointer.
 *
 * @return   number byte read from the log buffer.
 */
01165 static int proc_read_channels(char *buf, char **start, off_t offset, int count,
                        int *eof, void *data)
{
      char *log;
      char *log_ptr;
      char tmp[48];
      int i;

      log = kmalloc(4096, GFP_KERNEL);
      memset(log, 0, 4096);
      log_ptr = log;

      for (i = 0; i < MAX_DMA_CHANNELS; i++) {
            if (sdma_data[i].in_use == 0) {
                  continue;
            }

            memset(tmp, 0, 48);
            sprintf(tmp, "Channel %d: %s\n", i, sdma_data[i].devicename);

            strcpy(log_ptr, tmp);
            log_ptr += strlen(tmp);
      }

      if (offset > strlen(log)) {
            *eof = 1;
            count = 0;
      } else {
            if (offset + count > strlen(log)) {
                  count = strlen(log) - offset;
                  *eof = 1;
            } else {
                  *eof = 0;
            }

            memcpy(buf, log, count);
            *start = buf;
            kfree(log);
      }

      return count;
}

/*!
 * SDMA proc file system read function
 */
01211 static int __init init_proc_fs(void)
{
      struct proc_dir_entry *sdma_proc_dir;
      int res;

      res = 0;

      sdma_proc_dir = proc_mkdir("sdma", NULL);
      create_proc_read_entry("channels", 0, sdma_proc_dir,
                         proc_read_channels, NULL);

      if (res < 0) {
            printk(KERN_WARNING "Failed create SDMA proc entry\n");
      }

      return res;
}

/*!
 * Initializes SDMA private data
 */
01232 static void __init init_sdma_data(void)
{
      int i;

      memset(sdma_data, 0, sizeof(sdma_struct) * MAX_DMA_CHANNELS);
      sdma_data[0].in_use = 1;
      strcpy(sdma_data[0].devicename, "MCU");

      for (i = 0; i < MAX_DMA_CHANNELS; i++) {
            sdma_data[i].channel = i;
      }
}

#if defined(CONFIG_MXC_SUPER_GEM)
/*!
 * Initialize the Super GEM SDMA channel
 *
 * @return returns -1 on error, 0 on success.
 */
static int __init init_super_gem(void)
{
      channelDescriptor *cd;
      script_data context;
      int res = 0;

      res = iapi_Open(sdma_data[0].cd, MXC_DMA_CHANNEL_GEM);
      if (res < 0) {
            return -1;
      }
      sdma_data[MXC_DMA_CHANNEL_GEM].in_use = 1;
      cd = CHAD(MXC_DMA_CHANNEL_GEM);
      memset(&context, 0, sizeof(script_data));
      context.load_address = sdma_script_addrs.mxc_sdma_utra_addr;
      context.wml = M3_BASE_ADDRESS;
      res = iapi_AssignScript(cd, &context);
      if (res < 0) {
            iapi_Close(cd);
            sdma_data[MXC_DMA_CHANNEL_GEM].in_use = 0;
            return -1;
      }
      res =
          iapi_IoCtl(cd, IAPI_CHANGE_OWNERSHIP,
                   (OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) |
                   (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) |
                   (OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP));
      if (res < 0) {
            iapi_Close(cd);
            sdma_data[MXC_DMA_CHANNEL_GEM].in_use = 0;
            return -1;
      }
      /* Set EP=1, which is required to start SuperGem script the first time */
      /* This can be done only on the AP side */
      SDMA_H_EVTPEND |= 1 << MXC_DMA_CHANNEL_GEM;

      res =
          iapi_SetChannelEventMapping(DMA_REQ_GEM, 1 << MXC_DMA_CHANNEL_GEM);
      if (res < 0) {
            iapi_Close(cd);
            sdma_data[MXC_DMA_CHANNEL_GEM].in_use = 0;
            return -1;
      }

      return 0;
}
#endif

/*!
 * Initializes dma
 */
01301 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;

      sdma_init_fail:
      printk(KERN_ERR "Error 0x%x in sdma_init\n", res);
      clk_disable(mxc_sdma_ahb_clk);
      clk_disable(mxc_sdma_ipg_clk);
      return res;

}

arch_initcall(sdma_init);

EXPORT_SYMBOL(mxc_request_dma);
EXPORT_SYMBOL(mxc_free_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_set_bd_intr);
EXPORT_SYMBOL(mxc_dma_get_bd_intr);
EXPORT_SYMBOL(mxc_dma_reset);
EXPORT_SYMBOL(mxc_sdma_write_ipcv2);
EXPORT_SYMBOL(mxc_sdma_read_ipcv2);
EXPORT_SYMBOL(mxc_dma_start);
EXPORT_SYMBOL(mxc_dma_stop);
EXPORT_SYMBOL(sdma_malloc);
EXPORT_SYMBOL(sdma_free);
EXPORT_SYMBOL(mxc_dma_set_callback);
EXPORT_SYMBOL(sdma_virt_to_phys);
EXPORT_SYMBOL(sdma_phys_to_virt);

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

Generated by  Doxygen 1.6.0   Back to index