* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
+#include <linux/delay.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
spinlock_t lock;
struct tasklet_struct task;
struct list_head pending;
+ struct omap_system_dma_plat_info *plat;
};
struct omap_chan {
struct virt_dma_chan vc;
struct list_head node;
+ struct omap_system_dma_plat_info *plat;
struct dma_slave_config cfg;
unsigned dma_sig;
uint8_t sync_mode; /* OMAP_DMA_SYNC_xxx */
uint8_t sync_type; /* OMAP_DMA_xxx_SYNC* */
uint8_t periph_port; /* Peripheral port */
+ uint16_t cicr; /* CICR value */
unsigned sglen;
struct omap_sg sg[0];
kfree(container_of(vd, struct omap_desc, vd));
}
+static void omap_dma_start(struct omap_chan *c, struct omap_desc *d)
+{
+ struct omap_dmadev *od = to_omap_dma_dev(c->vc.chan.device);
+ uint32_t val;
+
+ if (__dma_omap15xx(od->plat->dma_attr))
+ c->plat->dma_write(0, CPC, c->dma_ch);
+ else
+ c->plat->dma_write(0, CDAC, c->dma_ch);
+
+ if (!__dma_omap15xx(od->plat->dma_attr) && c->cyclic) {
+ val = c->plat->dma_read(CLNK_CTRL, c->dma_ch);
+
+ if (dma_omap1())
+ val &= ~(1 << 14);
+
+ val |= c->dma_ch | 1 << 15;
+
+ c->plat->dma_write(val, CLNK_CTRL, c->dma_ch);
+ } else if (od->plat->errata & DMA_ERRATA_PARALLEL_CHANNELS)
+ c->plat->dma_write(c->dma_ch, CLNK_CTRL, c->dma_ch);
+
+ /* Clear CSR */
+ if (dma_omap1())
+ c->plat->dma_read(CSR, c->dma_ch);
+ else
+ c->plat->dma_write(~0, CSR, c->dma_ch);
+
+ /* Enable interrupts */
+ c->plat->dma_write(d->cicr, CICR, c->dma_ch);
+
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ if (od->plat->errata & DMA_ERRATA_IFRAME_BUFFERING)
+ val |= OMAP_DMA_CCR_BUFFERING_DISABLE;
+ val |= OMAP_DMA_CCR_EN;
+ mb();
+ c->plat->dma_write(val, CCR, c->dma_ch);
+}
+
+static void omap_dma_stop(struct omap_chan *c)
+{
+ struct omap_dmadev *od = to_omap_dma_dev(c->vc.chan.device);
+ uint32_t val;
+
+ /* disable irq */
+ c->plat->dma_write(0, CICR, c->dma_ch);
+
+ /* Clear CSR */
+ if (dma_omap1())
+ c->plat->dma_read(CSR, c->dma_ch);
+ else
+ c->plat->dma_write(~0, CSR, c->dma_ch);
+
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ if (od->plat->errata & DMA_ERRATA_i541 &&
+ val & OMAP_DMA_CCR_SEL_SRC_DST_SYNC) {
+ uint32_t sysconfig;
+ unsigned i;
+
+ sysconfig = c->plat->dma_read(OCP_SYSCONFIG, c->dma_ch);
+ val = sysconfig & ~DMA_SYSCONFIG_MIDLEMODE_MASK;
+ val |= DMA_SYSCONFIG_MIDLEMODE(DMA_IDLEMODE_NO_IDLE);
+ c->plat->dma_write(val, OCP_SYSCONFIG, c->dma_ch);
+
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ val &= ~OMAP_DMA_CCR_EN;
+ c->plat->dma_write(val, CCR, c->dma_ch);
+
+ /* Wait for sDMA FIFO to drain */
+ for (i = 0; ; i++) {
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ if (!(val & (OMAP_DMA_CCR_RD_ACTIVE | OMAP_DMA_CCR_WR_ACTIVE)))
+ break;
+
+ if (i > 100)
+ break;
+
+ udelay(5);
+ }
+
+ if (val & (OMAP_DMA_CCR_RD_ACTIVE | OMAP_DMA_CCR_WR_ACTIVE))
+ dev_err(c->vc.chan.device->dev,
+ "DMA drain did not complete on lch %d\n",
+ c->dma_ch);
+
+ c->plat->dma_write(sysconfig, OCP_SYSCONFIG, c->dma_ch);
+ } else {
+ val &= ~OMAP_DMA_CCR_EN;
+ c->plat->dma_write(val, CCR, c->dma_ch);
+ }
+
+ mb();
+
+ if (!__dma_omap15xx(od->plat->dma_attr) && c->cyclic) {
+ val = c->plat->dma_read(CLNK_CTRL, c->dma_ch);
+
+ if (dma_omap1())
+ val |= 1 << 14; /* set the STOP_LNK bit */
+ else
+ val &= ~(1 << 15); /* Clear the ENABLE_LNK bit */
+
+ c->plat->dma_write(val, CLNK_CTRL, c->dma_ch);
+ }
+}
+
static void omap_dma_start_sg(struct omap_chan *c, struct omap_desc *d,
unsigned idx)
{
struct omap_sg *sg = d->sg + idx;
- if (d->dir == DMA_DEV_TO_MEM)
- omap_set_dma_dest_params(c->dma_ch, OMAP_DMA_PORT_EMIFF,
- OMAP_DMA_AMODE_POST_INC, sg->addr, 0, 0);
- else
- omap_set_dma_src_params(c->dma_ch, OMAP_DMA_PORT_EMIFF,
- OMAP_DMA_AMODE_POST_INC, sg->addr, 0, 0);
+ if (d->dir == DMA_DEV_TO_MEM) {
+ c->plat->dma_write(sg->addr, CDSA, c->dma_ch);
+ c->plat->dma_write(0, CDEI, c->dma_ch);
+ c->plat->dma_write(0, CDFI, c->dma_ch);
+ } else {
+ c->plat->dma_write(sg->addr, CSSA, c->dma_ch);
+ c->plat->dma_write(0, CSEI, c->dma_ch);
+ c->plat->dma_write(0, CSFI, c->dma_ch);
+ }
- omap_set_dma_transfer_params(c->dma_ch, d->es, sg->en, sg->fn,
- d->sync_mode, c->dma_sig, d->sync_type);
+ c->plat->dma_write(sg->en, CEN, c->dma_ch);
+ c->plat->dma_write(sg->fn, CFN, c->dma_ch);
- omap_start_dma(c->dma_ch);
+ omap_dma_start(c, d);
}
static void omap_dma_start_desc(struct omap_chan *c)
{
struct virt_dma_desc *vd = vchan_next_desc(&c->vc);
struct omap_desc *d;
+ uint32_t val;
if (!vd) {
c->desc = NULL;
c->desc = d = to_omap_dma_desc(&vd->tx);
c->sgidx = 0;
- if (d->dir == DMA_DEV_TO_MEM)
- omap_set_dma_src_params(c->dma_ch, d->periph_port,
- OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, d->fi);
- else
- omap_set_dma_dest_params(c->dma_ch, d->periph_port,
- OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, d->fi);
+ if (d->dir == DMA_DEV_TO_MEM) {
+ if (dma_omap1()) {
+ val = c->plat->dma_read(CSDP, c->dma_ch);
+ val &= ~(0x1f << 9 | 0x1f << 2);
+ val |= OMAP_DMA_PORT_EMIFF << 9;
+ val |= d->periph_port << 2;
+ c->plat->dma_write(val, CSDP, c->dma_ch);
+ }
+
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ val &= ~(0x03 << 14 | 0x03 << 12);
+ val |= OMAP_DMA_AMODE_POST_INC << 14;
+ val |= OMAP_DMA_AMODE_CONSTANT << 12;
+ c->plat->dma_write(val, CCR, c->dma_ch);
+
+ c->plat->dma_write(d->dev_addr, CSSA, c->dma_ch);
+ c->plat->dma_write(0, CSEI, c->dma_ch);
+ c->plat->dma_write(d->fi, CSFI, c->dma_ch);
+ } else {
+ if (dma_omap1()) {
+ val = c->plat->dma_read(CSDP, c->dma_ch);
+ val &= ~(0x1f << 9 | 0x1f << 2);
+ val |= d->periph_port << 9;
+ val |= OMAP_DMA_PORT_EMIFF << 2;
+ c->plat->dma_write(val, CSDP, c->dma_ch);
+ }
+
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ val &= ~(0x03 << 12 | 0x03 << 14);
+ val |= OMAP_DMA_AMODE_CONSTANT << 14;
+ val |= OMAP_DMA_AMODE_POST_INC << 12;
+ c->plat->dma_write(val, CCR, c->dma_ch);
+
+ c->plat->dma_write(d->dev_addr, CDSA, c->dma_ch);
+ c->plat->dma_write(0, CDEI, c->dma_ch);
+ c->plat->dma_write(d->fi, CDFI, c->dma_ch);
+ }
+
+ val = c->plat->dma_read(CSDP, c->dma_ch);
+ val &= ~0x03;
+ val |= d->es;
+ c->plat->dma_write(val, CSDP, c->dma_ch);
+
+ if (dma_omap1()) {
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ val &= ~(1 << 5);
+ if (d->sync_mode == OMAP_DMA_SYNC_FRAME)
+ val |= 1 << 5;
+ c->plat->dma_write(val, CCR, c->dma_ch);
+
+ val = c->plat->dma_read(CCR2, c->dma_ch);
+ val &= ~(1 << 2);
+ if (d->sync_mode == OMAP_DMA_SYNC_BLOCK)
+ val |= 1 << 2;
+ c->plat->dma_write(val, CCR2, c->dma_ch);
+ } else if (c->dma_sig) {
+ val = c->plat->dma_read(CCR, c->dma_ch);
+
+ /* DMA_SYNCHRO_CONTROL_UPPER depends on the channel number */
+ val &= ~(1 << 24 | 1 << 23 | 3 << 19 | 1 << 18 | 1 << 5 | 0x1f);
+ val |= (c->dma_sig & ~0x1f) << 14;
+ val |= c->dma_sig & 0x1f;
+
+ if (d->sync_mode & OMAP_DMA_SYNC_FRAME)
+ val |= 1 << 5;
+
+ if (d->sync_mode & OMAP_DMA_SYNC_BLOCK)
+ val |= 1 << 18;
+
+ switch (d->sync_type) {
+ case OMAP_DMA_DST_SYNC_PREFETCH:/* dest synch */
+ val |= 1 << 23; /* Prefetch */
+ break;
+ case 0:
+ break;
+ default:
+ val |= 1 << 24; /* source synch */
+ break;
+ }
+ c->plat->dma_write(val, CCR, c->dma_ch);
+ }
omap_dma_start_sg(c, d, 0);
}
return size;
}
+static dma_addr_t omap_dma_get_src_pos(struct omap_chan *c)
+{
+ struct omap_dmadev *od = to_omap_dma_dev(c->vc.chan.device);
+ dma_addr_t addr;
+
+ if (__dma_omap15xx(od->plat->dma_attr))
+ addr = c->plat->dma_read(CPC, c->dma_ch);
+ else
+ addr = c->plat->dma_read(CSAC, c->dma_ch);
+
+ if (od->plat->errata & DMA_ERRATA_3_3 && addr == 0)
+ addr = c->plat->dma_read(CSAC, c->dma_ch);
+
+ if (!__dma_omap15xx(od->plat->dma_attr)) {
+ /*
+ * CDAC == 0 indicates that the DMA transfer on the channel has
+ * not been started (no data has been transferred so far).
+ * Return the programmed source start address in this case.
+ */
+ if (c->plat->dma_read(CDAC, c->dma_ch))
+ addr = c->plat->dma_read(CSAC, c->dma_ch);
+ else
+ addr = c->plat->dma_read(CSSA, c->dma_ch);
+ }
+
+ if (dma_omap1())
+ addr |= c->plat->dma_read(CSSA, c->dma_ch) & 0xffff0000;
+
+ return addr;
+}
+
+static dma_addr_t omap_dma_get_dst_pos(struct omap_chan *c)
+{
+ struct omap_dmadev *od = to_omap_dma_dev(c->vc.chan.device);
+ dma_addr_t addr;
+
+ if (__dma_omap15xx(od->plat->dma_attr))
+ addr = c->plat->dma_read(CPC, c->dma_ch);
+ else
+ addr = c->plat->dma_read(CDAC, c->dma_ch);
+
+ /*
+ * omap 3.2/3.3 erratum: sometimes 0 is returned if CSAC/CDAC is
+ * read before the DMA controller finished disabling the channel.
+ */
+ if (!__dma_omap15xx(od->plat->dma_attr) && addr == 0) {
+ addr = c->plat->dma_read(CDAC, c->dma_ch);
+ /*
+ * CDAC == 0 indicates that the DMA transfer on the channel has
+ * not been started (no data has been transferred so far).
+ * Return the programmed destination start address in this case.
+ */
+ if (addr == 0)
+ addr = c->plat->dma_read(CDSA, c->dma_ch);
+ }
+
+ if (dma_omap1())
+ addr |= c->plat->dma_read(CDSA, c->dma_ch) & 0xffff0000;
+
+ return addr;
+}
+
static enum dma_status omap_dma_tx_status(struct dma_chan *chan,
dma_cookie_t cookie, struct dma_tx_state *txstate)
{
dma_addr_t pos;
if (d->dir == DMA_MEM_TO_DEV)
- pos = omap_get_dma_src_pos(c->dma_ch);
+ pos = omap_dma_get_src_pos(c);
else if (d->dir == DMA_DEV_TO_MEM)
- pos = omap_get_dma_dst_pos(c->dma_ch);
+ pos = omap_dma_get_dst_pos(c);
else
pos = 0;
d->sync_mode = OMAP_DMA_SYNC_FRAME;
d->sync_type = sync_type;
d->periph_port = OMAP_DMA_PORT_TIPB;
+ d->cicr = OMAP_DMA_DROP_IRQ | OMAP_DMA_BLOCK_IRQ;
+
+ if (dma_omap1())
+ d->cicr |= OMAP1_DMA_TOUT_IRQ;
+ else
+ d->cicr |= OMAP2_DMA_MISALIGNED_ERR_IRQ | OMAP2_DMA_TRANS_ERR_IRQ;
/*
* Build our scatterlist entries: each contains the address,
size_t period_len, enum dma_transfer_direction dir, unsigned long flags,
void *context)
{
+ struct omap_dmadev *od = to_omap_dma_dev(chan->device);
struct omap_chan *c = to_omap_dma_chan(chan);
enum dma_slave_buswidth dev_width;
struct omap_desc *d;
d->sg[0].en = period_len / es_bytes[es];
d->sg[0].fn = buf_len / period_len;
d->sglen = 1;
+ d->cicr = OMAP_DMA_DROP_IRQ;
+ if (flags & DMA_PREP_INTERRUPT)
+ d->cicr |= OMAP_DMA_FRAME_IRQ;
+
+ if (dma_omap1())
+ d->cicr |= OMAP1_DMA_TOUT_IRQ;
+ else
+ d->cicr |= OMAP2_DMA_MISALIGNED_ERR_IRQ | OMAP2_DMA_TRANS_ERR_IRQ;
if (!c->cyclic) {
c->cyclic = true;
- omap_dma_link_lch(c->dma_ch, c->dma_ch);
- if (flags & DMA_PREP_INTERRUPT)
- omap_enable_dma_irq(c->dma_ch, OMAP_DMA_FRAME_IRQ);
+ if (__dma_omap15xx(od->plat->dma_attr)) {
+ uint32_t val;
- omap_disable_dma_irq(c->dma_ch, OMAP_DMA_BLOCK_IRQ);
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ val |= 3 << 8;
+ c->plat->dma_write(val, CCR, c->dma_ch);
+ }
}
if (dma_omap2plus()) {
- omap_set_dma_src_burst_mode(c->dma_ch, OMAP_DMA_DATA_BURST_16);
- omap_set_dma_dest_burst_mode(c->dma_ch, OMAP_DMA_DATA_BURST_16);
+ uint32_t val;
+
+ val = c->plat->dma_read(CSDP, c->dma_ch);
+ val |= 0x03 << 7; /* src burst mode 16 */
+ val |= 0x03 << 14; /* dst burst mode 16 */
+ c->plat->dma_write(val, CSDP, c->dma_ch);
}
return vchan_tx_prep(&c->vc, &d->vd, flags);
/*
* Stop DMA activity: we assume the callback will not be called
- * after omap_stop_dma() returns (even if it does, it will see
+ * after omap_dma_stop() returns (even if it does, it will see
* c->desc is NULL and exit.)
*/
if (c->desc) {
c->desc = NULL;
/* Avoid stopping the dma twice */
if (!c->paused)
- omap_stop_dma(c->dma_ch);
+ omap_dma_stop(c);
}
if (c->cyclic) {
c->cyclic = false;
c->paused = false;
- omap_dma_unlink_lch(c->dma_ch, c->dma_ch);
+
+ if (__dma_omap15xx(od->plat->dma_attr)) {
+ uint32_t val;
+
+ val = c->plat->dma_read(CCR, c->dma_ch);
+ val &= ~(3 << 8);
+ c->plat->dma_write(val, CCR, c->dma_ch);
+ }
}
vchan_get_all_descriptors(&c->vc, &head);
return -EINVAL;
if (!c->paused) {
- omap_stop_dma(c->dma_ch);
+ omap_dma_stop(c);
c->paused = true;
}
return -EINVAL;
if (c->paused) {
- omap_start_dma(c->dma_ch);
+ omap_dma_start(c, c->desc);
c->paused = false;
}
if (!c)
return -ENOMEM;
+ c->plat = od->plat;
c->dma_sig = dma_sig;
c->vc.desc_free = omap_dma_desc_free;
vchan_init(&c->vc, &od->ddev);
tasklet_kill(&c->vc.task);
kfree(c);
}
- kfree(od);
}
static int omap_dma_probe(struct platform_device *pdev)
struct omap_dmadev *od;
int rc, i;
- od = kzalloc(sizeof(*od), GFP_KERNEL);
+ od = devm_kzalloc(&pdev->dev, sizeof(*od), GFP_KERNEL);
if (!od)
return -ENOMEM;
+ od->plat = omap_get_plat_info();
+ if (!od->plat)
+ return -EPROBE_DEFER;
+
dma_cap_set(DMA_SLAVE, od->ddev.cap_mask);
dma_cap_set(DMA_CYCLIC, od->ddev.cap_mask);
od->ddev.device_alloc_chan_resources = omap_dma_alloc_chan_resources;