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

s3c-pl330.c

/* linux/arch/arm/plat-samsung/s3c-pl330.c
 *
 * Copyright (C) 2010 Samsung Electronics Co. Ltd.
 *    Jaswinder Singh <jassi.brar@samsung.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/err.h>

#include <asm/hardware/pl330.h>

#include <plat/s3c-pl330-pdata.h>

/**
 * struct s3c_pl330_dmac - Logical representation of a PL330 DMAC.
 * @busy_chan: Number of channels currently busy.
 * @peri: List of IDs of peripherals this DMAC can work with.
 * @node: To attach to the global list of DMACs.
 * @pi: PL330 configuration info for the DMAC.
 * @kmcache: Pool to quickly allocate xfers for all channels in the dmac.
 * @clk: Pointer of DMAC operation clock.
 */
00034 struct s3c_pl330_dmac {
      unsigned          busy_chan;
      enum dma_ch       *peri;
      struct list_head  node;
      struct pl330_info *pi;
      struct kmem_cache *kmcache;
      struct clk        *clk;
};

/**
 * struct s3c_pl330_xfer - A request submitted by S3C DMA clients.
 * @token: Xfer ID provided by the client.
 * @node: To attach to the list of xfers on a channel.
 * @px: Xfer for PL330 core.
 * @chan: Owner channel of this xfer.
 */
00050 struct s3c_pl330_xfer {
      void              *token;
      struct list_head  node;
      struct pl330_xfer px;
      struct s3c_pl330_chan   *chan;
};

/**
 * struct s3c_pl330_chan - Logical channel to communicate with
 *    a Physical peripheral.
 * @pl330_chan_id: Token of a hardware channel thread of PL330 DMAC.
 *    NULL if the channel is available to be acquired.
 * @id: ID of the peripheral that this channel can communicate with.
 * @options: Options specified by the client.
 * @sdaddr: Address provided via s3c2410_dma_devconfig.
 * @node: To attach to the global list of channels.
 * @lrq: Pointer to the last submitted pl330_req to PL330 core.
 * @xfer_list: To manage list of xfers enqueued.
 * @req: Two requests to communicate with the PL330 engine.
 * @callback_fn: Callback function to the client.
 * @rqcfg: Channel configuration for the xfers.
 * @xfer_head: Pointer to the xfer to be next excecuted.
 * @dmac: Pointer to the DMAC that manages this channel, NULL if the
 *    channel is available to be acquired.
 * @client: Client of this channel. NULL if the
 *    channel is available to be acquired.
 */
00077 struct s3c_pl330_chan {
      void                    *pl330_chan_id;
      enum dma_ch             id;
      unsigned int                  options;
      unsigned long                 sdaddr;
      struct list_head        node;
      struct pl330_req        *lrq;
      struct list_head        xfer_list;
      struct pl330_req        req[2];
      s3c2410_dma_cbfn_t            callback_fn;
      struct pl330_reqcfg           rqcfg;
      struct s3c_pl330_xfer         *xfer_head;
      struct s3c_pl330_dmac         *dmac;
      struct s3c2410_dma_client     *client;
};

/* All DMACs in the platform */
static LIST_HEAD(dmac_list);

/* All channels to peripherals in the platform */
static LIST_HEAD(chan_list);

/*
 * Since we add resources(DMACs and Channels) to the global pool,
 * we need to guard access to the resources using a global lock
 */
static DEFINE_SPINLOCK(res_lock);

/* Returns the channel with ID 'id' in the chan_list */
static struct s3c_pl330_chan *id_to_chan(const enum dma_ch id)
{
      struct s3c_pl330_chan *ch;

      list_for_each_entry(ch, &chan_list, node)
            if (ch->id == id)
                  return ch;

      return NULL;
}

/* Allocate a new channel with ID 'id' and add to chan_list */
static void chan_add(const enum dma_ch id)
{
      struct s3c_pl330_chan *ch = id_to_chan(id);

      /* Return if the channel already exists */
      if (ch)
            return;

      ch = kmalloc(sizeof(*ch), GFP_KERNEL);
      /* Return silently to work with other channels */
      if (!ch)
            return;

      ch->id = id;
      ch->dmac = NULL;

      list_add_tail(&ch->node, &chan_list);
}

/* If the channel is not yet acquired by any client */
static bool chan_free(struct s3c_pl330_chan *ch)
{
      if (!ch)
            return false;

      /* Channel points to some DMAC only when it's acquired */
      return ch->dmac ? false : true;
}

/*
 * Returns 0 is peripheral i/f is invalid or not present on the dmac.
 * Index + 1, otherwise.
 */
static unsigned iface_of_dmac(struct s3c_pl330_dmac *dmac, enum dma_ch ch_id)
{
      enum dma_ch *id = dmac->peri;
      int i;

      /* Discount invalid markers */
      if (ch_id == DMACH_MAX)
            return 0;

      for (i = 0; i < PL330_MAX_PERI; i++)
            if (id[i] == ch_id)
                  return i + 1;

      return 0;
}

/* If all channel threads of the DMAC are busy */
static inline bool dmac_busy(struct s3c_pl330_dmac *dmac)
{
      struct pl330_info *pi = dmac->pi;

      return (dmac->busy_chan < pi->pcfg.num_chan) ? false : true;
}

/*
 * Returns the number of free channels that
 * can be handled by this dmac only.
 */
static unsigned ch_onlyby_dmac(struct s3c_pl330_dmac *dmac)
{
      enum dma_ch *id = dmac->peri;
      struct s3c_pl330_dmac *d;
      struct s3c_pl330_chan *ch;
      unsigned found, count = 0;
      enum dma_ch p;
      int i;

      for (i = 0; i < PL330_MAX_PERI; i++) {
            p = id[i];
            ch = id_to_chan(p);

            if (p == DMACH_MAX || !chan_free(ch))
                  continue;

            found = 0;
            list_for_each_entry(d, &dmac_list, node) {
                  if (d != dmac && iface_of_dmac(d, ch->id)) {
                        found = 1;
                        break;
                  }
            }
            if (!found)
                  count++;
      }

      return count;
}

/*
 * Measure of suitability of 'dmac' handling 'ch'
 *
 * 0 indicates 'dmac' can not handle 'ch' either
 * because it is not supported by the hardware or
 * because all dmac channels are currently busy.
 *
 * >0 vlaue indicates 'dmac' has the capability.
 * The bigger the value the more suitable the dmac.
 */
#define MAX_SUIT  UINT_MAX
#define MIN_SUIT  0

static unsigned suitablility(struct s3c_pl330_dmac *dmac,
            struct s3c_pl330_chan *ch)
{
      struct pl330_info *pi = dmac->pi;
      enum dma_ch *id = dmac->peri;
      struct s3c_pl330_dmac *d;
      unsigned s;
      int i;

      s = MIN_SUIT;
      /* If all the DMAC channel threads are busy */
      if (dmac_busy(dmac))
            return s;

      for (i = 0; i < PL330_MAX_PERI; i++)
            if (id[i] == ch->id)
                  break;

      /* If the 'dmac' can't talk to 'ch' */
      if (i == PL330_MAX_PERI)
            return s;

      s = MAX_SUIT;
      list_for_each_entry(d, &dmac_list, node) {
            /*
             * If some other dmac can talk to this
             * peri and has some channel free.
             */
            if (d != dmac && iface_of_dmac(d, ch->id) && !dmac_busy(d)) {
                  s = 0;
                  break;
            }
      }
      if (s)
            return s;

      s = 100;

      /* Good if free chans are more, bad otherwise */
      s += (pi->pcfg.num_chan - dmac->busy_chan) - ch_onlyby_dmac(dmac);

      return s;
}

/* More than one DMAC may have capability to transfer data with the
 * peripheral. This function assigns most suitable DMAC to manage the
 * channel and hence communicate with the peripheral.
 */
static struct s3c_pl330_dmac *map_chan_to_dmac(struct s3c_pl330_chan *ch)
{
      struct s3c_pl330_dmac *d, *dmac = NULL;
      unsigned sn, sl = MIN_SUIT;

      list_for_each_entry(d, &dmac_list, node) {
            sn = suitablility(d, ch);

            if (sn == MAX_SUIT)
                  return d;

            if (sn > sl)
                  dmac = d;
      }

      return dmac;
}

/* Acquire the channel for peripheral 'id' */
static struct s3c_pl330_chan *chan_acquire(const enum dma_ch id)
{
      struct s3c_pl330_chan *ch = id_to_chan(id);
      struct s3c_pl330_dmac *dmac;

      /* If the channel doesn't exist or is already acquired */
      if (!ch || !chan_free(ch)) {
            ch = NULL;
            goto acq_exit;
      }

      dmac = map_chan_to_dmac(ch);
      /* If couldn't map */
      if (!dmac) {
            ch = NULL;
            goto acq_exit;
      }

      dmac->busy_chan++;
      ch->dmac = dmac;

acq_exit:
      return ch;
}

/* Delete xfer from the queue */
static inline void del_from_queue(struct s3c_pl330_xfer *xfer)
{
      struct s3c_pl330_xfer *t;
      struct s3c_pl330_chan *ch;
      int found;

      if (!xfer)
            return;

      ch = xfer->chan;

      /* Make sure xfer is in the queue */
      found = 0;
      list_for_each_entry(t, &ch->xfer_list, node)
            if (t == xfer) {
                  found = 1;
                  break;
            }

      if (!found)
            return;

      /* If xfer is last entry in the queue */
      if (xfer->node.next == &ch->xfer_list)
            t = list_entry(ch->xfer_list.next,
                        struct s3c_pl330_xfer, node);
      else
            t = list_entry(xfer->node.next,
                        struct s3c_pl330_xfer, node);

      /* If there was only one node left */
      if (t == xfer)
            ch->xfer_head = NULL;
      else if (ch->xfer_head == xfer)
            ch->xfer_head = t;

      list_del(&xfer->node);
}

/* Provides pointer to the next xfer in the queue.
 * If CIRCULAR option is set, the list is left intact,
 * otherwise the xfer is removed from the list.
 * Forced delete 'pluck' can be set to override the CIRCULAR option.
 */
static struct s3c_pl330_xfer *get_from_queue(struct s3c_pl330_chan *ch,
            int pluck)
{
      struct s3c_pl330_xfer *xfer = ch->xfer_head;

      if (!xfer)
            return NULL;

      /* If xfer is last entry in the queue */
      if (xfer->node.next == &ch->xfer_list)
            ch->xfer_head = list_entry(ch->xfer_list.next,
                              struct s3c_pl330_xfer, node);
      else
            ch->xfer_head = list_entry(xfer->node.next,
                              struct s3c_pl330_xfer, node);

      if (pluck || !(ch->options & S3C2410_DMAF_CIRCULAR))
            del_from_queue(xfer);

      return xfer;
}

static inline void add_to_queue(struct s3c_pl330_chan *ch,
            struct s3c_pl330_xfer *xfer, int front)
{
      struct pl330_xfer *xt;

      /* If queue empty */
      if (ch->xfer_head == NULL)
            ch->xfer_head = xfer;

      xt = &ch->xfer_head->px;
      /* If the head already submitted (CIRCULAR head) */
      if (ch->options & S3C2410_DMAF_CIRCULAR &&
            (xt == ch->req[0].x || xt == ch->req[1].x))
            ch->xfer_head = xfer;

      /* If this is a resubmission, it should go at the head */
      if (front) {
            ch->xfer_head = xfer;
            list_add(&xfer->node, &ch->xfer_list);
      } else {
            list_add_tail(&xfer->node, &ch->xfer_list);
      }
}

static inline void _finish_off(struct s3c_pl330_xfer *xfer,
            enum s3c2410_dma_buffresult res, int ffree)
{
      struct s3c_pl330_chan *ch;

      if (!xfer)
            return;

      ch = xfer->chan;

      /* Do callback */
      if (ch->callback_fn)
            ch->callback_fn(NULL, xfer->token, xfer->px.bytes, res);

      /* Force Free or if buffer is not needed anymore */
      if (ffree || !(ch->options & S3C2410_DMAF_CIRCULAR))
            kmem_cache_free(ch->dmac->kmcache, xfer);
}

static inline int s3c_pl330_submit(struct s3c_pl330_chan *ch,
            struct pl330_req *r)
{
      struct s3c_pl330_xfer *xfer;
      int ret = 0;

      /* If already submitted */
      if (r->x)
            return 0;

      xfer = get_from_queue(ch, 0);
      if (xfer) {
            r->x = &xfer->px;

            /* Use max bandwidth for M<->M xfers */
            if (r->rqtype == MEMTOMEM) {
                  struct pl330_info *pi = xfer->chan->dmac->pi;
                  int burst = 1 << ch->rqcfg.brst_size;
                  u32 bytes = r->x->bytes;
                  int bl;

                  bl = pi->pcfg.data_bus_width / 8;
                  bl *= pi->pcfg.data_buf_dep;
                  bl /= burst;

                  /* src/dst_burst_len can't be more than 16 */
                  if (bl > 16)
                        bl = 16;

                  while (bl > 1) {
                        if (!(bytes % (bl * burst)))
                              break;
                        bl--;
                  }

                  ch->rqcfg.brst_len = bl;
            } else {
                  ch->rqcfg.brst_len = 1;
            }

            ret = pl330_submit_req(ch->pl330_chan_id, r);

            /* If submission was successful */
            if (!ret) {
                  ch->lrq = r; /* latest submitted req */
                  return 0;
            }

            r->x = NULL;

            /* If both of the PL330 ping-pong buffers filled */
            if (ret == -EAGAIN) {
                  dev_err(ch->dmac->pi->dev, "%s:%d!\n",
                        __func__, __LINE__);
                  /* Queue back again */
                  add_to_queue(ch, xfer, 1);
                  ret = 0;
            } else {
                  dev_err(ch->dmac->pi->dev, "%s:%d!\n",
                        __func__, __LINE__);
                  _finish_off(xfer, S3C2410_RES_ERR, 0);
            }
      }

      return ret;
}

static void s3c_pl330_rq(struct s3c_pl330_chan *ch,
      struct pl330_req *r, enum pl330_op_err err)
{
      unsigned long flags;
      struct s3c_pl330_xfer *xfer;
      struct pl330_xfer *xl = r->x;
      enum s3c2410_dma_buffresult res;

      spin_lock_irqsave(&res_lock, flags);

      r->x = NULL;

      s3c_pl330_submit(ch, r);

      spin_unlock_irqrestore(&res_lock, flags);

      /* Map result to S3C DMA API */
      if (err == PL330_ERR_NONE)
            res = S3C2410_RES_OK;
      else if (err == PL330_ERR_ABORT)
            res = S3C2410_RES_ABORT;
      else
            res = S3C2410_RES_ERR;

      /* If last request had some xfer */
      if (xl) {
            xfer = container_of(xl, struct s3c_pl330_xfer, px);
            _finish_off(xfer, res, 0);
      } else {
            dev_info(ch->dmac->pi->dev, "%s:%d No Xfer?!\n",
                  __func__, __LINE__);
      }
}

static void s3c_pl330_rq0(void *token, enum pl330_op_err err)
{
      struct pl330_req *r = token;
      struct s3c_pl330_chan *ch = container_of(r,
                              struct s3c_pl330_chan, req[0]);
      s3c_pl330_rq(ch, r, err);
}

static void s3c_pl330_rq1(void *token, enum pl330_op_err err)
{
      struct pl330_req *r = token;
      struct s3c_pl330_chan *ch = container_of(r,
                              struct s3c_pl330_chan, req[1]);
      s3c_pl330_rq(ch, r, err);
}

/* Release an acquired channel */
static void chan_release(struct s3c_pl330_chan *ch)
{
      struct s3c_pl330_dmac *dmac;

      if (chan_free(ch))
            return;

      dmac = ch->dmac;
      ch->dmac = NULL;
      dmac->busy_chan--;
}

int s3c2410_dma_ctrl(enum dma_ch id, enum s3c2410_chan_op op)
{
      struct s3c_pl330_xfer *xfer;
      enum pl330_chan_op pl330op;
      struct s3c_pl330_chan *ch;
      unsigned long flags;
      int idx, ret;

      spin_lock_irqsave(&res_lock, flags);

      ch = id_to_chan(id);

      if (!ch || chan_free(ch)) {
            ret = -EINVAL;
            goto ctrl_exit;
      }

      switch (op) {
      case S3C2410_DMAOP_START:
            /* Make sure both reqs are enqueued */
            idx = (ch->lrq == &ch->req[0]) ? 1 : 0;
            s3c_pl330_submit(ch, &ch->req[idx]);
            s3c_pl330_submit(ch, &ch->req[1 - idx]);
            pl330op = PL330_OP_START;
            break;

      case S3C2410_DMAOP_STOP:
            pl330op = PL330_OP_ABORT;
            break;

      case S3C2410_DMAOP_FLUSH:
            pl330op = PL330_OP_FLUSH;
            break;

      case S3C2410_DMAOP_PAUSE:
      case S3C2410_DMAOP_RESUME:
      case S3C2410_DMAOP_TIMEOUT:
      case S3C2410_DMAOP_STARTED:
            spin_unlock_irqrestore(&res_lock, flags);
            return 0;

      default:
            spin_unlock_irqrestore(&res_lock, flags);
            return -EINVAL;
      }

      ret = pl330_chan_ctrl(ch->pl330_chan_id, pl330op);

      if (pl330op == PL330_OP_START) {
            spin_unlock_irqrestore(&res_lock, flags);
            return ret;
      }

      idx = (ch->lrq == &ch->req[0]) ? 1 : 0;

      /* Abort the current xfer */
      if (ch->req[idx].x) {
            xfer = container_of(ch->req[idx].x,
                        struct s3c_pl330_xfer, px);

            /* Drop xfer during FLUSH */
            if (pl330op == PL330_OP_FLUSH)
                  del_from_queue(xfer);

            ch->req[idx].x = NULL;

            spin_unlock_irqrestore(&res_lock, flags);
            _finish_off(xfer, S3C2410_RES_ABORT,
                        pl330op == PL330_OP_FLUSH ? 1 : 0);
            spin_lock_irqsave(&res_lock, flags);
      }

      /* Flush the whole queue */
      if (pl330op == PL330_OP_FLUSH) {

            if (ch->req[1 - idx].x) {
                  xfer = container_of(ch->req[1 - idx].x,
                              struct s3c_pl330_xfer, px);

                  del_from_queue(xfer);

                  ch->req[1 - idx].x = NULL;

                  spin_unlock_irqrestore(&res_lock, flags);
                  _finish_off(xfer, S3C2410_RES_ABORT, 1);
                  spin_lock_irqsave(&res_lock, flags);
            }

            /* Finish off the remaining in the queue */
            xfer = ch->xfer_head;
            while (xfer) {

                  del_from_queue(xfer);

                  spin_unlock_irqrestore(&res_lock, flags);
                  _finish_off(xfer, S3C2410_RES_ABORT, 1);
                  spin_lock_irqsave(&res_lock, flags);

                  xfer = ch->xfer_head;
            }
      }

ctrl_exit:
      spin_unlock_irqrestore(&res_lock, flags);

      return ret;
}
EXPORT_SYMBOL(s3c2410_dma_ctrl);

int s3c2410_dma_enqueue(enum dma_ch id, void *token,
                  dma_addr_t addr, int size)
{
      struct s3c_pl330_chan *ch;
      struct s3c_pl330_xfer *xfer;
      unsigned long flags;
      int idx, ret = 0;

      spin_lock_irqsave(&res_lock, flags);

      ch = id_to_chan(id);

      /* Error if invalid or free channel */
      if (!ch || chan_free(ch)) {
            ret = -EINVAL;
            goto enq_exit;
      }

      /* Error if size is unaligned */
      if (ch->rqcfg.brst_size && size % (1 << ch->rqcfg.brst_size)) {
            ret = -EINVAL;
            goto enq_exit;
      }

      xfer = kmem_cache_alloc(ch->dmac->kmcache, GFP_ATOMIC);
      if (!xfer) {
            ret = -ENOMEM;
            goto enq_exit;
      }

      xfer->token = token;
      xfer->chan = ch;
      xfer->px.bytes = size;
      xfer->px.next = NULL; /* Single request */

      /* For S3C DMA API, direction is always fixed for all xfers */
      if (ch->req[0].rqtype == MEMTODEV) {
            xfer->px.src_addr = addr;
            xfer->px.dst_addr = ch->sdaddr;
      } else {
            xfer->px.src_addr = ch->sdaddr;
            xfer->px.dst_addr = addr;
      }

      add_to_queue(ch, xfer, 0);

      /* Try submitting on either request */
      idx = (ch->lrq == &ch->req[0]) ? 1 : 0;

      if (!ch->req[idx].x)
            s3c_pl330_submit(ch, &ch->req[idx]);
      else
            s3c_pl330_submit(ch, &ch->req[1 - idx]);

      spin_unlock_irqrestore(&res_lock, flags);

      if (ch->options & S3C2410_DMAF_AUTOSTART)
            s3c2410_dma_ctrl(id, S3C2410_DMAOP_START);

      return 0;

enq_exit:
      spin_unlock_irqrestore(&res_lock, flags);

      return ret;
}
EXPORT_SYMBOL(s3c2410_dma_enqueue);

int s3c2410_dma_request(enum dma_ch id,
                  struct s3c2410_dma_client *client,
                  void *dev)
{
      struct s3c_pl330_dmac *dmac;
      struct s3c_pl330_chan *ch;
      unsigned long flags;
      int ret = 0;

      spin_lock_irqsave(&res_lock, flags);

      ch = chan_acquire(id);
      if (!ch) {
            ret = -EBUSY;
            goto req_exit;
      }

      dmac = ch->dmac;

      ch->pl330_chan_id = pl330_request_channel(dmac->pi);
      if (!ch->pl330_chan_id) {
            chan_release(ch);
            ret = -EBUSY;
            goto req_exit;
      }

      ch->client = client;
      ch->options = 0; /* Clear any option */
      ch->callback_fn = NULL; /* Clear any callback */
      ch->lrq = NULL;

      ch->rqcfg.brst_size = 2; /* Default word size */
      ch->rqcfg.swap = SWAP_NO;
      ch->rqcfg.scctl = SCCTRL0; /* Noncacheable and nonbufferable */
      ch->rqcfg.dcctl = DCCTRL0; /* Noncacheable and nonbufferable */
      ch->rqcfg.privileged = 0;
      ch->rqcfg.insnaccess = 0;

      /* Set invalid direction */
      ch->req[0].rqtype = DEVTODEV;
      ch->req[1].rqtype = ch->req[0].rqtype;

      ch->req[0].cfg = &ch->rqcfg;
      ch->req[1].cfg = ch->req[0].cfg;

      ch->req[0].peri = iface_of_dmac(dmac, id) - 1; /* Original index */
      ch->req[1].peri = ch->req[0].peri;

      ch->req[0].token = &ch->req[0];
      ch->req[0].xfer_cb = s3c_pl330_rq0;
      ch->req[1].token = &ch->req[1];
      ch->req[1].xfer_cb = s3c_pl330_rq1;

      ch->req[0].x = NULL;
      ch->req[1].x = NULL;

      /* Reset xfer list */
      INIT_LIST_HEAD(&ch->xfer_list);
      ch->xfer_head = NULL;

req_exit:
      spin_unlock_irqrestore(&res_lock, flags);

      return ret;
}
EXPORT_SYMBOL(s3c2410_dma_request);

int s3c2410_dma_free(enum dma_ch id, struct s3c2410_dma_client *client)
{
      struct s3c_pl330_chan *ch;
      struct s3c_pl330_xfer *xfer;
      unsigned long flags;
      int ret = 0;
      unsigned idx;

      spin_lock_irqsave(&res_lock, flags);

      ch = id_to_chan(id);

      if (!ch || chan_free(ch))
            goto free_exit;

      /* Refuse if someone else wanted to free the channel */
      if (ch->client != client) {
            ret = -EBUSY;
            goto free_exit;
      }

      /* Stop any active xfer, Flushe the queue and do callbacks */
      pl330_chan_ctrl(ch->pl330_chan_id, PL330_OP_FLUSH);

      /* Abort the submitted requests */
      idx = (ch->lrq == &ch->req[0]) ? 1 : 0;

      if (ch->req[idx].x) {
            xfer = container_of(ch->req[idx].x,
                        struct s3c_pl330_xfer, px);

            ch->req[idx].x = NULL;
            del_from_queue(xfer);

            spin_unlock_irqrestore(&res_lock, flags);
            _finish_off(xfer, S3C2410_RES_ABORT, 1);
            spin_lock_irqsave(&res_lock, flags);
      }

      if (ch->req[1 - idx].x) {
            xfer = container_of(ch->req[1 - idx].x,
                        struct s3c_pl330_xfer, px);

            ch->req[1 - idx].x = NULL;
            del_from_queue(xfer);

            spin_unlock_irqrestore(&res_lock, flags);
            _finish_off(xfer, S3C2410_RES_ABORT, 1);
            spin_lock_irqsave(&res_lock, flags);
      }

      /* Pluck and Abort the queued requests in order */
      do {
            xfer = get_from_queue(ch, 1);

            spin_unlock_irqrestore(&res_lock, flags);
            _finish_off(xfer, S3C2410_RES_ABORT, 1);
            spin_lock_irqsave(&res_lock, flags);
      } while (xfer);

      ch->client = NULL;

      pl330_release_channel(ch->pl330_chan_id);

      ch->pl330_chan_id = NULL;

      chan_release(ch);

free_exit:
      spin_unlock_irqrestore(&res_lock, flags);

      return ret;
}
EXPORT_SYMBOL(s3c2410_dma_free);

int s3c2410_dma_config(enum dma_ch id, int xferunit)
{
      struct s3c_pl330_chan *ch;
      struct pl330_info *pi;
      unsigned long flags;
      int i, dbwidth, ret = 0;

      spin_lock_irqsave(&res_lock, flags);

      ch = id_to_chan(id);

      if (!ch || chan_free(ch)) {
            ret = -EINVAL;
            goto cfg_exit;
      }

      pi = ch->dmac->pi;
      dbwidth = pi->pcfg.data_bus_width / 8;

      /* Max size of xfer can be pcfg.data_bus_width */
      if (xferunit > dbwidth) {
            ret = -EINVAL;
            goto cfg_exit;
      }

      i = 0;
      while (xferunit != (1 << i))
            i++;

      /* If valid value */
      if (xferunit == (1 << i))
            ch->rqcfg.brst_size = i;
      else
            ret = -EINVAL;

cfg_exit:
      spin_unlock_irqrestore(&res_lock, flags);

      return ret;
}
EXPORT_SYMBOL(s3c2410_dma_config);

/* Options that are supported by this driver */
#define S3C_PL330_FLAGS (S3C2410_DMAF_CIRCULAR | S3C2410_DMAF_AUTOSTART)

int s3c2410_dma_setflags(enum dma_ch id, unsigned int options)
{
      struct s3c_pl330_chan *ch;
      unsigned long flags;
      int ret = 0;

      spin_lock_irqsave(&res_lock, flags);

      ch = id_to_chan(id);

      if (!ch || chan_free(ch) || options & ~(S3C_PL330_FLAGS))
            ret = -EINVAL;
      else
            ch->options = options;

      spin_unlock_irqrestore(&res_lock, flags);

      return 0;
}
EXPORT_SYMBOL(s3c2410_dma_setflags);

int s3c2410_dma_set_buffdone_fn(enum dma_ch id, s3c2410_dma_cbfn_t rtn)
{
      struct s3c_pl330_chan *ch;
      unsigned long flags;
      int ret = 0;

      spin_lock_irqsave(&res_lock, flags);

      ch = id_to_chan(id);

      if (!ch || chan_free(ch))
            ret = -EINVAL;
      else
            ch->callback_fn = rtn;

      spin_unlock_irqrestore(&res_lock, flags);

      return ret;
}
EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);

int s3c2410_dma_devconfig(enum dma_ch id, enum s3c2410_dmasrc source,
                    unsigned long address)
{
      struct s3c_pl330_chan *ch;
      unsigned long flags;
      int ret = 0;

      spin_lock_irqsave(&res_lock, flags);

      ch = id_to_chan(id);

      if (!ch || chan_free(ch)) {
            ret = -EINVAL;
            goto devcfg_exit;
      }

      switch (source) {
      case S3C2410_DMASRC_HW: /* P->M */
            ch->req[0].rqtype = DEVTOMEM;
            ch->req[1].rqtype = DEVTOMEM;
            ch->rqcfg.src_inc = 0;
            ch->rqcfg.dst_inc = 1;
            break;
      case S3C2410_DMASRC_MEM: /* M->P */
            ch->req[0].rqtype = MEMTODEV;
            ch->req[1].rqtype = MEMTODEV;
            ch->rqcfg.src_inc = 1;
            ch->rqcfg.dst_inc = 0;
            break;
      default:
            ret = -EINVAL;
            goto devcfg_exit;
      }

      ch->sdaddr = address;

devcfg_exit:
      spin_unlock_irqrestore(&res_lock, flags);

      return ret;
}
EXPORT_SYMBOL(s3c2410_dma_devconfig);

int s3c2410_dma_getposition(enum dma_ch id, dma_addr_t *src, dma_addr_t *dst)
{
      struct s3c_pl330_chan *ch = id_to_chan(id);
      struct pl330_chanstatus status;
      int ret;

      if (!ch || chan_free(ch))
            return -EINVAL;

      ret = pl330_chan_status(ch->pl330_chan_id, &status);
      if (ret < 0)
            return ret;

      *src = status.src_addr;
      *dst = status.dst_addr;

      return 0;
}
EXPORT_SYMBOL(s3c2410_dma_getposition);

static irqreturn_t pl330_irq_handler(int irq, void *data)
{
      if (pl330_update(data))
            return IRQ_HANDLED;
      else
            return IRQ_NONE;
}

static int pl330_probe(struct platform_device *pdev)
{
      struct s3c_pl330_dmac *s3c_pl330_dmac;
      struct s3c_pl330_platdata *pl330pd;
      struct pl330_info *pl330_info;
      struct resource *res;
      int i, ret, irq;

      pl330pd = pdev->dev.platform_data;

      /* Can't do without the list of _32_ peripherals */
      if (!pl330pd || !pl330pd->peri) {
            dev_err(&pdev->dev, "platform data missing!\n");
            return -ENODEV;
      }

      pl330_info = kzalloc(sizeof(*pl330_info), GFP_KERNEL);
      if (!pl330_info)
            return -ENOMEM;

      pl330_info->pl330_data = NULL;
      pl330_info->dev = &pdev->dev;

      res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
      if (!res) {
            ret = -ENODEV;
            goto probe_err1;
      }

      request_mem_region(res->start, resource_size(res), pdev->name);

      pl330_info->base = ioremap(res->start, resource_size(res));
      if (!pl330_info->base) {
            ret = -ENXIO;
            goto probe_err2;
      }

      irq = platform_get_irq(pdev, 0);
      if (irq < 0) {
            ret = irq;
            goto probe_err3;
      }

      ret = request_irq(irq, pl330_irq_handler, 0,
                  dev_name(&pdev->dev), pl330_info);
      if (ret)
            goto probe_err4;

      /* Allocate a new DMAC */
      s3c_pl330_dmac = kmalloc(sizeof(*s3c_pl330_dmac), GFP_KERNEL);
      if (!s3c_pl330_dmac) {
            ret = -ENOMEM;
            goto probe_err5;
      }

      /* Get operation clock and enable it */
      s3c_pl330_dmac->clk = clk_get(&pdev->dev, "pdma");
      if (IS_ERR(s3c_pl330_dmac->clk)) {
            dev_err(&pdev->dev, "Cannot get operation clock.\n");
            ret = -EINVAL;
            goto probe_err6;
      }
      clk_enable(s3c_pl330_dmac->clk);

      ret = pl330_add(pl330_info);
      if (ret)
            goto probe_err7;

      /* Hook the info */
      s3c_pl330_dmac->pi = pl330_info;

      /* No busy channels */
      s3c_pl330_dmac->busy_chan = 0;

      s3c_pl330_dmac->kmcache = kmem_cache_create(dev_name(&pdev->dev),
                        sizeof(struct s3c_pl330_xfer), 0, 0, NULL);

      if (!s3c_pl330_dmac->kmcache) {
            ret = -ENOMEM;
            goto probe_err8;
      }

      /* Get the list of peripherals */
      s3c_pl330_dmac->peri = pl330pd->peri;

      /* Attach to the list of DMACs */
      list_add_tail(&s3c_pl330_dmac->node, &dmac_list);

      /* Create a channel for each peripheral in the DMAC
       * that is, if it doesn't already exist
       */
      for (i = 0; i < PL330_MAX_PERI; i++)
            if (s3c_pl330_dmac->peri[i] != DMACH_MAX)
                  chan_add(s3c_pl330_dmac->peri[i]);

      printk(KERN_INFO
            "Loaded driver for PL330 DMAC-%d %s\n",   pdev->id, pdev->name);
      printk(KERN_INFO
            "\tDBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u\n",
            pl330_info->pcfg.data_buf_dep,
            pl330_info->pcfg.data_bus_width / 8, pl330_info->pcfg.num_chan,
            pl330_info->pcfg.num_peri, pl330_info->pcfg.num_events);

      return 0;

probe_err8:
      pl330_del(pl330_info);
probe_err7:
      clk_disable(s3c_pl330_dmac->clk);
      clk_put(s3c_pl330_dmac->clk);
probe_err6:
      kfree(s3c_pl330_dmac);
probe_err5:
      free_irq(irq, pl330_info);
probe_err4:
probe_err3:
      iounmap(pl330_info->base);
probe_err2:
      release_mem_region(res->start, resource_size(res));
probe_err1:
      kfree(pl330_info);

      return ret;
}

static int pl330_remove(struct platform_device *pdev)
{
      struct s3c_pl330_dmac *dmac, *d;
      struct s3c_pl330_chan *ch;
      unsigned long flags;
      int del, found;

      if (!pdev->dev.platform_data)
            return -EINVAL;

      spin_lock_irqsave(&res_lock, flags);

      found = 0;
      list_for_each_entry(d, &dmac_list, node)
            if (d->pi->dev == &pdev->dev) {
                  found = 1;
                  break;
            }

      if (!found) {
            spin_unlock_irqrestore(&res_lock, flags);
            return 0;
      }

      dmac = d;

      /* Remove all Channels that are managed only by this DMAC */
      list_for_each_entry(ch, &chan_list, node) {

            /* Only channels that are handled by this DMAC */
            if (iface_of_dmac(dmac, ch->id))
                  del = 1;
            else
                  continue;

            /* Don't remove if some other DMAC has it too */
            list_for_each_entry(d, &dmac_list, node)
                  if (d != dmac && iface_of_dmac(d, ch->id)) {
                        del = 0;
                        break;
                  }

            if (del) {
                  spin_unlock_irqrestore(&res_lock, flags);
                  s3c2410_dma_free(ch->id, ch->client);
                  spin_lock_irqsave(&res_lock, flags);
                  list_del(&ch->node);
                  kfree(ch);
            }
      }

      /* Disable operation clock */
      clk_disable(dmac->clk);
      clk_put(dmac->clk);

      /* Remove the DMAC */
      list_del(&dmac->node);
      kfree(dmac);

      spin_unlock_irqrestore(&res_lock, flags);

      return 0;
}

static struct platform_driver pl330_driver = {
      .driver           = {
            .owner      = THIS_MODULE,
            .name = "s3c-pl330",
      },
      .probe            = pl330_probe,
      .remove           = pl330_remove,
};

static int __init pl330_init(void)
{
      return platform_driver_register(&pl330_driver);
}
module_init(pl330_init);

static void __exit pl330_exit(void)
{
      platform_driver_unregister(&pl330_driver);
      return;
}
module_exit(pl330_exit);

MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>");
MODULE_DESCRIPTION("Driver for PL330 DMA Controller");
MODULE_LICENSE("GPL");

Generated by  Doxygen 1.6.0   Back to index