/*
* DMA driver for STMicroelectronics STi FDMA controller
*
* Copyright (C) 2014 STMicroelectronics
*
* Author: Ludovic Barre <Ludovic.barre@st.com>
* Peter Griffin <peter.griffin@linaro.org>
*
* 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/of_device.h>
#include <linux/of_dma.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/remoteproc.h>
#include "st_fdma.h"
static inline struct st_fdma_chan *to_st_fdma_chan(struct dma_chan *c)
{
return container_of(c, struct st_fdma_chan, vchan.chan);
}
static struct st_fdma_desc *to_st_fdma_desc(struct virt_dma_desc *vd)
{
return container_of(vd, struct st_fdma_desc, vdesc);
}
static int st_fdma_dreq_get(struct st_fdma_chan *fchan)
{
struct st_fdma_dev *fdev = fchan->fdev;
u32 req_line_cfg = fchan->cfg.req_line;
u32 dreq_line;
int try = 0;
/*
* dreq_mask is shared for n channels of fdma, so all accesses must be
* atomic. if the dreq_mask is changed between ffz and set_bit,
* we retry
*/
do {
if (fdev->dreq_mask == ~0L) {
dev_err(fdev->dev, "No req lines available\n");
return -EINVAL;
}
if (try || req_line_cfg >= ST_FDMA_NR_DREQS) {
dev_err(fdev->dev, "Invalid or used req line\n");
return -EINVAL;
} else {
dreq_line = req_line_cfg;
}
try++;
} while (test_and_set_bit(dreq_line, &fdev->dreq_mask));
dev_dbg(fdev->dev, "get dreq_line:%d mask:%#lx\n",
dreq_line, fdev->dreq_mask);
return dreq_line;
}
static void st_fdma_dreq_put(struct st_fdma_chan *fchan)
{
struct st_fdma_dev *fdev = fchan->fdev;
dev_dbg(fdev->dev, "put dreq_line:%#x\n", fchan->dreq_line);
clear_bit(fchan->dreq_line, &fdev->dreq_mask);
}
static void st_fdma_xfer_desc(struct st_fdma_chan *fchan)
{
struct virt_dma_desc *vdesc;
unsigned long nbytes, ch_cmd, cmd;
vdesc = vchan_next_desc(&fchan->vchan);
if (!vdesc)
return;
fchan->fdesc = to_st_fdma_desc(vdesc);
nbytes = fchan->fdesc->node[0].desc->nbytes;
cmd = FDMA_CMD_START(fchan->vchan.chan.chan_id);
ch_cmd = fchan->fdesc->node[0].pdesc | FDMA_CH_CMD_STA_START;
/* start the channel for the descriptor */
fnode_write(fchan, nbytes, FDMA_CNTN_OFST);
fchan_write(fchan, ch_cmd, FDMA_CH_CMD_OFST);
writel(cmd,
fchan->fdev->slim_rproc->peri + FDMA_CMD_SET_OFST);
dev_dbg(fchan->fdev->dev, "start chan:%d\n", fchan->vchan.chan.chan_id);
}
static void st_fdma_ch_sta_update(struct st_fdma_chan *fchan,
unsigned long int_sta)
{
unsigned long ch_sta, ch_err;
int ch_id = fchan->vchan.chan.chan_id;
struct st_fdma_dev *fdev = fchan->fdev;
ch_sta = fchan_read(fchan, FDMA_CH_CMD_OFST);
ch_err = ch_sta & FDMA_CH_CMD_ERR_MASK;
ch_sta &= FDMA_CH_CMD_STA_MASK;
if (int_sta & FDMA_INT_STA_ERR) {
dev_warn(fdev->dev, "chan:%d, error:%ld\n", ch_id, ch_err);
fchan->status = DMA_ERROR;
return;
}
switch (ch_sta) {
case FDMA_CH_CMD_STA_PAUSED:
fchan->status = DMA_PAUSED;
break;
case FDMA_CH_CMD_STA_RUNNING:
fchan->status = DMA_IN_PROGRESS;
break;
}
}
static irqreturn_t st_fdma_irq_handler(int irq, void *dev_id)
{
struct st_fdma_dev *fdev = dev_id;
irqreturn_t ret = IRQ_NONE;
struct st_fdma_chan *fchan = &fdev->chans[0];
unsigned long int_sta, clr;
int_sta = fdma_read(fdev, FDMA_INT_STA_OFST);
clr = int_sta;
for (; int_sta != 0 ; int_sta >>= 2, fchan++) {
if (!(int_sta & (FDMA_INT_STA_CH | FDMA_INT_STA_ERR)))
continue;
spin_lock(&fchan->vchan.lock);
st_fdma_ch_sta_update(fchan, int_sta);
if (fchan->fdesc) {
if (!fchan->fdesc->iscyclic) {
list_del(&fchan->fdesc->vdesc.node);
vchan_cookie_complete(&fchan->fdesc->vdesc);
fchan->fdesc = NULL;
fchan->status = DMA_COMPLETE;
} else {
vchan_cyclic_callback(&fchan->fdesc->vdesc);
}
/* Start the next descriptor (if available) */
if (!fchan->fdesc)
st_fdma_xfer_desc(fchan);
}