|
@@ -424,3 +424,193 @@ int s3c2410_dma_devconfig(enum dma_ch channel,
|
|
|
u32 config = 0;
|
|
|
|
|
|
pr_debug("%s: channel %d, source %d, dev %08lx, chan %p\n",
|
|
|
+ __func__, channel, source, devaddr, chan);
|
|
|
+
|
|
|
+ WARN_ON(!chan);
|
|
|
+ if (!chan)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ peripheral = (chan->peripheral & 0xf);
|
|
|
+ chan->source = source;
|
|
|
+ chan->dev_addr = devaddr;
|
|
|
+
|
|
|
+ pr_debug("%s: peripheral %d\n", __func__, peripheral);
|
|
|
+
|
|
|
+ switch (source) {
|
|
|
+ case DMA_FROM_DEVICE:
|
|
|
+ config = 2 << PL080_CONFIG_FLOW_CONTROL_SHIFT;
|
|
|
+ config |= peripheral << PL080_CONFIG_SRC_SEL_SHIFT;
|
|
|
+ break;
|
|
|
+ case DMA_TO_DEVICE:
|
|
|
+ config = 1 << PL080_CONFIG_FLOW_CONTROL_SHIFT;
|
|
|
+ config |= peripheral << PL080_CONFIG_DST_SEL_SHIFT;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ printk(KERN_ERR "%s: bad source\n", __func__);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* allow TC and ERR interrupts */
|
|
|
+ config |= PL080_CONFIG_TC_IRQ_MASK;
|
|
|
+ config |= PL080_CONFIG_ERR_IRQ_MASK;
|
|
|
+
|
|
|
+ pr_debug("%s: config %08x\n", __func__, config);
|
|
|
+
|
|
|
+ writel(config, chan->regs + PL080S_CH_CONFIG);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(s3c2410_dma_devconfig);
|
|
|
+
|
|
|
+
|
|
|
+int s3c2410_dma_getposition(enum dma_ch channel,
|
|
|
+ dma_addr_t *src, dma_addr_t *dst)
|
|
|
+{
|
|
|
+ struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
|
|
|
+
|
|
|
+ WARN_ON(!chan);
|
|
|
+ if (!chan)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (src != NULL)
|
|
|
+ *src = readl(chan->regs + PL080_CH_SRC_ADDR);
|
|
|
+
|
|
|
+ if (dst != NULL)
|
|
|
+ *dst = readl(chan->regs + PL080_CH_DST_ADDR);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(s3c2410_dma_getposition);
|
|
|
+
|
|
|
+/* s3c2410_request_dma
|
|
|
+ *
|
|
|
+ * get control of an dma channel
|
|
|
+*/
|
|
|
+
|
|
|
+int s3c2410_dma_request(enum dma_ch channel,
|
|
|
+ struct s3c2410_dma_client *client,
|
|
|
+ void *dev)
|
|
|
+{
|
|
|
+ struct s3c2410_dma_chan *chan;
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n",
|
|
|
+ channel, client->name, dev);
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+
|
|
|
+ chan = s3c64xx_dma_map_channel(channel);
|
|
|
+ if (chan == NULL) {
|
|
|
+ local_irq_restore(flags);
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+
|
|
|
+ dbg_showchan(chan);
|
|
|
+
|
|
|
+ chan->client = client;
|
|
|
+ chan->in_use = 1;
|
|
|
+ chan->peripheral = channel;
|
|
|
+
|
|
|
+ local_irq_restore(flags);
|
|
|
+
|
|
|
+ /* need to setup */
|
|
|
+
|
|
|
+ pr_debug("%s: channel initialised, %p\n", __func__, chan);
|
|
|
+
|
|
|
+ return chan->number | DMACH_LOW_LEVEL;
|
|
|
+}
|
|
|
+
|
|
|
+EXPORT_SYMBOL(s3c2410_dma_request);
|
|
|
+
|
|
|
+/* s3c2410_dma_free
|
|
|
+ *
|
|
|
+ * release the given channel back to the system, will stop and flush
|
|
|
+ * any outstanding transfers, and ensure the channel is ready for the
|
|
|
+ * next claimant.
|
|
|
+ *
|
|
|
+ * Note, although a warning is currently printed if the freeing client
|
|
|
+ * info is not the same as the registrant's client info, the free is still
|
|
|
+ * allowed to go through.
|
|
|
+*/
|
|
|
+
|
|
|
+int s3c2410_dma_free(enum dma_ch channel, struct s3c2410_dma_client *client)
|
|
|
+{
|
|
|
+ struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ if (chan == NULL)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+
|
|
|
+ if (chan->client != client) {
|
|
|
+ printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)\n",
|
|
|
+ channel, chan->client, client);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* sort out stopping and freeing the channel */
|
|
|
+
|
|
|
+
|
|
|
+ chan->client = NULL;
|
|
|
+ chan->in_use = 0;
|
|
|
+
|
|
|
+ if (!(channel & DMACH_LOW_LEVEL))
|
|
|
+ s3c_dma_chan_map[channel] = NULL;
|
|
|
+
|
|
|
+ local_irq_restore(flags);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+EXPORT_SYMBOL(s3c2410_dma_free);
|
|
|
+
|
|
|
+static irqreturn_t s3c64xx_dma_irq(int irq, void *pw)
|
|
|
+{
|
|
|
+ struct s3c64xx_dmac *dmac = pw;
|
|
|
+ struct s3c2410_dma_chan *chan;
|
|
|
+ enum s3c2410_dma_buffresult res;
|
|
|
+ u32 tcstat, errstat;
|
|
|
+ u32 bit;
|
|
|
+ int offs;
|
|
|
+
|
|
|
+ tcstat = readl(dmac->regs + PL080_TC_STATUS);
|
|
|
+ errstat = readl(dmac->regs + PL080_ERR_STATUS);
|
|
|
+
|
|
|
+ for (offs = 0, bit = 1; offs < 8; offs++, bit <<= 1) {
|
|
|
+ struct s3c64xx_dma_buff *buff;
|
|
|
+
|
|
|
+ if (!(errstat & bit) && !(tcstat & bit))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ chan = dmac->channels + offs;
|
|
|
+ res = S3C2410_RES_ERR;
|
|
|
+
|
|
|
+ if (tcstat & bit) {
|
|
|
+ writel(bit, dmac->regs + PL080_TC_CLEAR);
|
|
|
+ res = S3C2410_RES_OK;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (errstat & bit)
|
|
|
+ writel(bit, dmac->regs + PL080_ERR_CLEAR);
|
|
|
+
|
|
|
+ /* 'next' points to the buffer that is next to the
|
|
|
+ * currently active buffer.
|
|
|
+ * For CIRCULAR queues, 'next' will be same as 'curr'
|
|
|
+ * when 'end' is the active buffer.
|
|
|
+ */
|
|
|
+ buff = chan->curr;
|
|
|
+ while (buff && buff != chan->next
|
|
|
+ && buff->next != chan->next)
|
|
|
+ buff = buff->next;
|
|
|
+
|
|
|
+ if (!buff)
|
|
|
+ BUG();
|
|
|
+
|
|
|
+ if (buff == chan->next)
|
|
|
+ buff = chan->end;
|
|
|
+
|
|
|
+ s3c64xx_dma_bufffdone(chan, buff, res);
|
|
|
+
|
|
|
+ /* Free the node and update curr, if non-circular queue */
|
|
|
+ if (!(chan->flags & S3C2410_DMAF_CIRCULAR)) {
|
|
|
+ chan->curr = buff->next;
|