Skip to content

Commit

Permalink
dmaengine: Add support for BCM2708
Browse files Browse the repository at this point in the history
Add support for DMA controller of BCM2708 as used in the Raspberry Pi.
Currently it only supports cyclic DMA.

Signed-off-by: Florian Meier <florian.meier@koalo.de>

dmaengine: expand functionality by supporting scatter/gather transfers sdhci-bcm2708 and dma.c: fix for LITE channels

DMA: fix cyclic LITE length overflow bug

dmaengine: bcm2708: Remove chancnt affectations

Mirror bcm2835-dma.c commit 9eba553:
chancnt is already filled by dma_async_device_register, which uses the channel
list to know how much channels there is.

Since it's already filled, we can safely remove it from the drivers' probe
function.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

dmaengine: bcm2708: overwrite dreq only if it is not set

dreq is set when the DMA channel is fetched from Device Tree.
slave_id is set using dmaengine_slave_config().
Only overwrite dreq with slave_id if it is not set.

dreq/slave_id in the cyclic DMA case is not touched, because I don't
have hardware to test with.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

dmaengine: bcm2708: do device registration in the board file

Don't register the device in the driver. Do it in the board file.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

dmaengine: bcm2708: don't restrict DT support to ARCH_BCM2835

Both ARCH_BCM2835 and ARCH_BCM270x are built with OF now.
Add Device Tree support to the non ARCH_BCM2835 case.
Use the same driver name regardless of architecture.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

BCM270x_DT: add bcm2835-dma entry

Add Device Tree entry for bcm2835-dma.
The entry doesn't contain any resources since they are handled
by the arch/arm/mach-bcm270x/dma.c driver.
In non-DT mode, don't add the device in the board file.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

bcm2708-dmaengine: Add debug options

BCM270x: Add memory and irq resources to dmaengine device and DT

Prepare for merging of the legacy DMA API arch driver dma.c
with bcm2708-dmaengine by adding memory and irq resources both
to platform file device and Device Tree node.
Don't use BCM_DMAMAN_DRIVER_NAME so we don't have to include mach/dma.h

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

dmaengine: bcm2708: Merge with arch dma.c driver and disable dma.c

Merge the legacy DMA API driver with bcm2708-dmaengine.
This is done so we can use bcm2708_fb on ARCH_BCM2835 (mailbox
driver is also needed).

Changes to the dma.c code:
- Use BIT() macro.
- Cutdown some comments to one line.
- Add mutex to vc_dmaman and use this, since the dev lock is locked
  during probing of the engine part.
- Add global g_dmaman variable since drvdata is used by the engine part.
- Restructure for readability:
  vc_dmaman_chan_alloc()
  vc_dmaman_chan_free()
  bcm_dma_chan_free()
- Restructure bcm_dma_chan_alloc() to simplify error handling.
- Use device irq resources instead of hardcoded bcm_dma_irqs table.
- Remove dev_dmaman_register() and code it directly.
- Remove dev_dmaman_deregister() and code it directly.
- Simplify bcm_dmaman_probe() using devm_* functions.
- Get dmachans from DT if available.
- Keep 'dma.dmachans' module argument name for backwards compatibility.

Make it available on ARCH_BCM2835 as well.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

dmaengine: bcm2708: set residue_granularity field

bcm2708-dmaengine supports residue reporting at burst level
but didn't report this via the residue_granularity field.

Without this field set properly we get playback issues with I2S cards.

dmaengine: bcm2708-dmaengine: Fix memory leak when stopping a running transfer

bcm2708-dmaengine: Use more DMA channels (but not 12)

1) Only the bcm2708_fb drivers uses the legacy DMA API, and
it requires a BULK-capable channel, so all other types
(FAST, NORMAL and LITE) can be made available to the regular
DMA API.

2) DMA channels 11-14 share an interrupt. The driver can't
handle this, so don't use channels 12-14 (12 was used, probably
because it appears to have an interrupt, but in reality that
interrupt is for activity on ANY channel). This may explain
a lockup encountered when running out of DMA channels.

The combined effect of this patch is to leave 7 DMA channels
available + channel 0 for bcm2708_fb via the legacy API.

See: #1110
     #1108

dmaengine: bcm2708: Make legacy API available for bcm2835-dma

bcm2708_fb uses the legacy DMA API, so in order to start using
bcm2835-dma, bcm2835-dma has to support the legacy API. Make this
possible by exporting bcm_dmaman_probe() and bcm_dmaman_remove().

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

dmaengine: bcm2708: Change DT compatible string

Both bcm2835-dma and bcm2708-dmaengine have the same compatible string.
So change compatible to "brcm,bcm2708-dma".

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

dmaengine: bcm2708: Remove driver but keep legacy API

Dropping non-DT support means we don't need this driver,
but we still need the legacy DMA API.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

bcm2708-dmaengine - Fix arm64 portability/build issues

dma-bcm2708: Fix module compilation of CONFIG_DMA_BCM2708

bcm2708-dmaengine.c defines functions like bcm_dma_start which are
defined as well in dma-bcm2708.h as inline versions when
CONFIG_DMA_BCM2708 is not defined. This works fine when
CONFIG_DMA_BCM2708 is built in, but when it is selected as module build
fails with redefinition errors because in the build system when
CONFIG_DMA_BCM2708 is selected as module, the macro becomes
CONFIG_DMA_BCM2708_MODULE.

This patch makes the header use CONFIG_DMA_BCM2708_MODULE too when
available.

Fixes #2056

Signed-off-by: Andrei Gherzan <andrei@gherzan.com>

bcm2708-dmaengine: Use platform_get_irq

The platform driver framework no longer creates IRQ resources for
platform devices because they are expected to use platform_get_irq.
This causes the bcm2808_fb acceleration to fail.

Fix the problem by calling platform_get_irq as intended.

See: #5131

Signed-off-by: Phil Elwell <phil@raspberrypi.com>
  • Loading branch information
koalo authored and popcornmix committed Sep 10, 2024
1 parent 5b67177 commit 66726d3
Show file tree
Hide file tree
Showing 4 changed files with 430 additions and 1 deletion.
6 changes: 5 additions & 1 deletion drivers/dma/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ config BCM_SBA_RAID

config DMA_BCM2835
tristate "BCM2835 DMA engine support"
depends on ARCH_BCM2835 || ARCH_BCM2708 || ARCH_BCM2709
depends on ARCH_BCM2835
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS

Expand Down Expand Up @@ -651,6 +651,10 @@ config UNIPHIER_XDMAC
UniPhier platform. This DMA controller can transfer data from
memory to memory, memory to peripheral and peripheral to memory.

config DMA_BCM2708
tristate "BCM2708 DMA legacy API support"
depends on DMA_BCM2835

config XGENE_DMA
tristate "APM X-Gene DMA support"
depends on ARCH_XGENE || COMPILE_TEST
Expand Down
1 change: 1 addition & 0 deletions drivers/dma/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ obj-$(CONFIG_AT_HDMAC) += at_hdmac.o
obj-$(CONFIG_AT_XDMAC) += at_xdmac.o
obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o
obj-$(CONFIG_BCM_SBA_RAID) += bcm-sba-raid.o
obj-$(CONFIG_DMA_BCM2708) += bcm2708-dmaengine.o
obj-$(CONFIG_DMA_BCM2835) += bcm2835-dma.o
obj-$(CONFIG_DMA_JZ4780) += dma-jz4780.o
obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o
Expand Down
281 changes: 281 additions & 0 deletions drivers/dma/bcm2708-dmaengine.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
/*
* BCM2708 legacy DMA API
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/platform_data/dma-bcm2708.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/spinlock.h>

#include "virt-dma.h"

#define CACHE_LINE_MASK 31
#define DEFAULT_DMACHAN_BITMAP 0x10 /* channel 4 only */

/* valid only for channels 0 - 14, 15 has its own base address */
#define BCM2708_DMA_CHAN(n) ((n) << 8) /* base address */
#define BCM2708_DMA_CHANIO(dma_base, n) \
((void __iomem *)((char *)(dma_base) + BCM2708_DMA_CHAN(n)))

struct vc_dmaman {
void __iomem *dma_base;
u32 chan_available; /* bitmap of available channels */
u32 has_feature[BCM_DMA_FEATURE_COUNT]; /* bitmap of feature presence */
struct mutex lock;
};

static struct device *dmaman_dev; /* we assume there's only one! */
static struct vc_dmaman *g_dmaman; /* DMA manager */

/* DMA Auxiliary Functions */

/* A DMA buffer on an arbitrary boundary may separate a cache line into a
section inside the DMA buffer and another section outside it.
Even if we flush DMA buffers from the cache there is always the chance that
during a DMA someone will access the part of a cache line that is outside
the DMA buffer - which will then bring in unwelcome data.
Without being able to dictate our own buffer pools we must insist that
DMA buffers consist of a whole number of cache lines.
*/
extern int bcm_sg_suitable_for_dma(struct scatterlist *sg_ptr, int sg_len)
{
int i;

for (i = 0; i < sg_len; i++) {
if (sg_ptr[i].offset & CACHE_LINE_MASK ||
sg_ptr[i].length & CACHE_LINE_MASK)
return 0;
}

return 1;
}
EXPORT_SYMBOL_GPL(bcm_sg_suitable_for_dma);

extern void bcm_dma_start(void __iomem *dma_chan_base,
dma_addr_t control_block)
{
dsb(sy); /* ARM data synchronization (push) operation */

writel(control_block, dma_chan_base + BCM2708_DMA_ADDR);
writel(BCM2708_DMA_ACTIVE, dma_chan_base + BCM2708_DMA_CS);
}
EXPORT_SYMBOL_GPL(bcm_dma_start);

extern void bcm_dma_wait_idle(void __iomem *dma_chan_base)
{
dsb(sy);

/* ugly busy wait only option for now */
while (readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_ACTIVE)
cpu_relax();
}
EXPORT_SYMBOL_GPL(bcm_dma_wait_idle);

extern bool bcm_dma_is_busy(void __iomem *dma_chan_base)
{
dsb(sy);

return readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_ACTIVE;
}
EXPORT_SYMBOL_GPL(bcm_dma_is_busy);

/* Complete an ongoing DMA (assuming its results are to be ignored)
Does nothing if there is no DMA in progress.
This routine waits for the current AXI transfer to complete before
terminating the current DMA. If the current transfer is hung on a DREQ used
by an uncooperative peripheral the AXI transfer may never complete. In this
case the routine times out and return a non-zero error code.
Use of this routine doesn't guarantee that the ongoing or aborted DMA
does not produce an interrupt.
*/
extern int bcm_dma_abort(void __iomem *dma_chan_base)
{
unsigned long int cs;
int rc = 0;

cs = readl(dma_chan_base + BCM2708_DMA_CS);

if (BCM2708_DMA_ACTIVE & cs) {
long int timeout = 10000;

/* write 0 to the active bit - pause the DMA */
writel(0, dma_chan_base + BCM2708_DMA_CS);

/* wait for any current AXI transfer to complete */
while (0 != (cs & BCM2708_DMA_ISPAUSED) && --timeout >= 0)
cs = readl(dma_chan_base + BCM2708_DMA_CS);

if (0 != (cs & BCM2708_DMA_ISPAUSED)) {
/* we'll un-pause when we set of our next DMA */
rc = -ETIMEDOUT;

} else if (BCM2708_DMA_ACTIVE & cs) {
/* terminate the control block chain */
writel(0, dma_chan_base + BCM2708_DMA_NEXTCB);

/* abort the whole DMA */
writel(BCM2708_DMA_ABORT | BCM2708_DMA_ACTIVE,
dma_chan_base + BCM2708_DMA_CS);
}
}

return rc;
}
EXPORT_SYMBOL_GPL(bcm_dma_abort);

/* DMA Manager Device Methods */

static void vc_dmaman_init(struct vc_dmaman *dmaman, void __iomem *dma_base,
u32 chans_available)
{
dmaman->dma_base = dma_base;
dmaman->chan_available = chans_available;
dmaman->has_feature[BCM_DMA_FEATURE_FAST_ORD] = 0x0c; /* 2 & 3 */
dmaman->has_feature[BCM_DMA_FEATURE_BULK_ORD] = 0x01; /* 0 */
dmaman->has_feature[BCM_DMA_FEATURE_NORMAL_ORD] = 0xfe; /* 1 to 7 */
dmaman->has_feature[BCM_DMA_FEATURE_LITE_ORD] = 0x7f00; /* 8 to 14 */
}

static int vc_dmaman_chan_alloc(struct vc_dmaman *dmaman,
unsigned required_feature_set)
{
u32 chans;
int chan = 0;
int feature;

chans = dmaman->chan_available;
for (feature = 0; feature < BCM_DMA_FEATURE_COUNT; feature++)
/* select the subset of available channels with the desired
features */
if (required_feature_set & (1 << feature))
chans &= dmaman->has_feature[feature];

if (!chans)
return -ENOENT;

/* return the ordinal of the first channel in the bitmap */
while (chans != 0 && (chans & 1) == 0) {
chans >>= 1;
chan++;
}
/* claim the channel */
dmaman->chan_available &= ~(1 << chan);

return chan;
}

static int vc_dmaman_chan_free(struct vc_dmaman *dmaman, int chan)
{
if (chan < 0)
return -EINVAL;

if ((1 << chan) & dmaman->chan_available)
return -EIDRM;

dmaman->chan_available |= (1 << chan);

return 0;
}

/* DMA Manager Monitor */

extern int bcm_dma_chan_alloc(unsigned required_feature_set,
void __iomem **out_dma_base, int *out_dma_irq)
{
struct vc_dmaman *dmaman = g_dmaman;
struct platform_device *pdev = to_platform_device(dmaman_dev);
int chan;
int irq;

if (!dmaman_dev)
return -ENODEV;

mutex_lock(&dmaman->lock);
chan = vc_dmaman_chan_alloc(dmaman, required_feature_set);
if (chan < 0)
goto out;

irq = platform_get_irq(pdev, (unsigned int)chan);
if (irq < 0) {
dev_err(dmaman_dev, "failed to get irq for DMA channel %d\n",
chan);
vc_dmaman_chan_free(dmaman, chan);
chan = -ENOENT;
goto out;
}

*out_dma_base = BCM2708_DMA_CHANIO(dmaman->dma_base, chan);
*out_dma_irq = irq;
dev_dbg(dmaman_dev,
"Legacy API allocated channel=%d, base=%p, irq=%i\n",
chan, *out_dma_base, *out_dma_irq);

out:
mutex_unlock(&dmaman->lock);

return chan;
}
EXPORT_SYMBOL_GPL(bcm_dma_chan_alloc);

extern int bcm_dma_chan_free(int channel)
{
struct vc_dmaman *dmaman = g_dmaman;
int rc;

if (!dmaman_dev)
return -ENODEV;

mutex_lock(&dmaman->lock);
rc = vc_dmaman_chan_free(dmaman, channel);
mutex_unlock(&dmaman->lock);

return rc;
}
EXPORT_SYMBOL_GPL(bcm_dma_chan_free);

int bcm_dmaman_probe(struct platform_device *pdev, void __iomem *base,
u32 chans_available)
{
struct device *dev = &pdev->dev;
struct vc_dmaman *dmaman;

dmaman = devm_kzalloc(dev, sizeof(*dmaman), GFP_KERNEL);
if (!dmaman)
return -ENOMEM;

mutex_init(&dmaman->lock);
vc_dmaman_init(dmaman, base, chans_available);
g_dmaman = dmaman;
dmaman_dev = dev;

dev_info(dev, "DMA legacy API manager, dmachans=0x%x\n",
chans_available);

return 0;
}
EXPORT_SYMBOL(bcm_dmaman_probe);

int bcm_dmaman_remove(struct platform_device *pdev)
{
dmaman_dev = NULL;

return 0;
}
EXPORT_SYMBOL(bcm_dmaman_remove);

MODULE_LICENSE("GPL");
Loading

0 comments on commit 66726d3

Please sign in to comment.