Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8b158709 authored by Shrey Vijay's avatar Shrey Vijay Committed by Gerrit - the friendly Code Review server
Browse files

qcom-sps-dma: Add snapshot of QCOM SPS DMA driver



This change adds a snapshot of QCOM SPS DMA driver
from the msm-4.14 'commit 78bed541 ("Merge "msm:
camera: hyp: To fix Stack overflow"")'.

Change-Id: I9270fcaf52766ce20585333c13eed9f31b7daa8e
Signed-off-by: default avatarShrey Vijay <shreyv@codeaurora.org>
Signed-off-by: default avatarChetan C R <cchinnad@codeaurora.org>
parent 97c44ea1
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -456,6 +456,16 @@ config PXA_DMA
	  16 to 32 channels for peripheral to memory or memory to memory
	  transfers.

config QCOM_SPS_DMA
	tristate "Qualcomm technologies inc DMA driver for sps-BAM"
	depends on ARCH_QCOM
	select DMA_ENGINE
	help
	  Enable support for Qualcomm technologies inc, BAM DMA engine.
	  This DMA-engine-driver is a wrapper of the sps-BAM library. DMA
	  engine callbacks are implemented using the sps-BAM functionality
	  to access HW.

config SIRF_DMA
	tristate "CSR SiRFprimaII/SiRFmarco DMA support"
	depends on ARCH_SIRF
+1 −0
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ obj-$(CONFIG_TIMB_DMA) += timb_dma.o
obj-$(CONFIG_XGENE_DMA) += xgene-dma.o
obj-$(CONFIG_ZX_DMA) += zx_dma.o
obj-$(CONFIG_ST_FDMA) += st_fdma.o
obj-$(CONFIG_QCOM_SPS_DMA) += qcom-sps-dma.o

obj-y += mediatek/
obj-y += qcom/
+711 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2014-2015,2017-2018, 2020, The Linux Foundation. All rights reserved.
 */

/*
 * Qualcomm technologies inc, DMA API for BAM (Bus Access Manager).
 * This DMA driver uses sps-BAM API to access the HW, thus it is effectively a
 * DMA engine wrapper of the sps-BAM API.
 *
 * Client channel configuration example:
 * struct dma_slave_config config {
 *    .direction = DMA_MEM_TO_DEV;
 * };
 *
 * chan = dma_request_slave_channel(client_dev, "rx");
 * dmaengine_slave_config(chan, &config);
 */

#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_dma.h>
#include <linux/list.h>
#include <linux/msm-sps.h>
#include "dmaengine.h"

#define QBAM_OF_SLAVE_N_ARGS	(4)
#define QBAM_OF_MANAGE_LOCAL	"qcom,managed-locally"
#define QBAM_OF_SUM_THRESHOLD	"qcom,summing-threshold"
#define QBAM_MAX_DESCRIPTORS	(0x100)
#define QBAM_MAX_CHANNELS	(32)

/*
 * qbam_async_tx_descriptor - dma descriptor plus a list of xfer_bufs
 *
 * @sgl scatterlist of transfer buffers
 * @sg_len size of that list
 * @flags dma xfer flags
 */
struct qbam_async_tx_descriptor {
	struct dma_async_tx_descriptor	dma_desc;
	struct scatterlist		*sgl;
	unsigned int			sg_len;
	unsigned long			flags;
};

#define DMA_TO_QBAM_ASYNC_DESC(dma_async_desc) \
	container_of(dma_async_desc, struct qbam_async_tx_descriptor, dma_desc)

struct qbam_channel;
/*
 * qbam_device - top level device of current driver
 * @handle bam sps handle.
 * @regs bam register space virtual base address.
 * @mem_resource bam register space resource.
 * @deregister_required if bam is registered by this driver it need to be
 *   unregistered by this driver.
 * @manage is bame managed locally or remotely,
 * @summing_threshold event threshold.
 * @irq bam interrupt line.
 * @channels has the same channels as qbam_dev->dma_dev.channels but
 *   supports fast access by pipe index.
 */
struct qbam_device {
	struct dma_device		dma_dev;
	void __iomem			*regs;
	struct resource			*mem_resource;
	ulong				handle;
	bool				deregister_required;
	u32				summing_threshold;
	u32				manage;
	int				irq;
	struct qbam_channel		*channels[QBAM_MAX_CHANNELS];
};

/* qbam_pipe: aggregate of bam pipe related entries of qbam_channel */
struct qbam_pipe {
	u32				index;
	struct sps_pipe			*handle;
	struct sps_connect		cfg;
	u32				num_descriptors;
	u32				sps_connect_flags;
	u32				sps_register_event_flags;
};

/*
 * qbam_channel - dma channel plus bam pipe info and current pending transfers
 *
 * @direction is a producer or consumer (MEM => DEV or DEV => MEM)
 * @pending_desc next set of transfer to process
 * @error last error that took place on the current pending_desc
 */
struct qbam_channel {
	struct qbam_pipe		bam_pipe;

	struct dma_chan			chan;
	enum dma_transfer_direction	direction;
	struct qbam_async_tx_descriptor	pending_desc;

	struct qbam_device		*qbam_dev;
	struct mutex			lock;
	int				error;
};
#define DMA_TO_QBAM_CHAN(dma_chan) \
			container_of(dma_chan, struct qbam_channel, chan)
#define qbam_err(qbam_dev, fmt ...) dev_err(qbam_dev->dma_dev.dev, fmt)

/*  qbam_disconnect_chan - disconnect a channel */
static int qbam_disconnect_chan(struct qbam_channel *qbam_chan)
{
	struct qbam_device  *qbam_dev    = qbam_chan->qbam_dev;
	struct sps_pipe     *pipe_handle = qbam_chan->bam_pipe.handle;
	struct sps_connect   pipe_config_no_irq = {.options = SPS_O_POLL};
	int ret;

	/*
	 * SW workaround:
	 * When disconnecting BAM pipe a spurious interrupt sometimes appears.
	 * To avoid that, we change the pipe setting from interrupt (default)
	 * to polling (SPS_O_POLL) before diconnecting the pipe.
	 */
	ret = sps_set_config(pipe_handle, &pipe_config_no_irq);
	if (ret)
		qbam_err(qbam_dev,
			"error:%d sps_set_config(pipe:%d) before disconnect\n",
			ret, qbam_chan->bam_pipe.index);

	ret = sps_disconnect(pipe_handle);
	if (ret)
		qbam_err(qbam_dev, "error:%d sps_disconnect(pipe:%d)\n",
			 ret, qbam_chan->bam_pipe.index);

	return ret;
}

/*  qbam_free_chan - disconnect channel and free its resources */
static void qbam_free_chan(struct dma_chan *chan)
{
	struct qbam_channel *qbam_chan = DMA_TO_QBAM_CHAN(chan);
	struct qbam_device  *qbam_dev  = qbam_chan->qbam_dev;

	mutex_lock(&qbam_chan->lock);
	if (qbam_disconnect_chan(qbam_chan))
		qbam_err(qbam_dev,
			"error free_chan() failed to disconnect(pipe:%d)\n",
			qbam_chan->bam_pipe.index);
	qbam_chan->pending_desc.sgl = NULL;
	qbam_chan->pending_desc.sg_len = 0;
	mutex_unlock(&qbam_chan->lock);
}

static struct dma_chan *qbam_dma_xlate(struct of_phandle_args *dma_spec,
							struct of_dma *of)
{
	struct qbam_device  *qbam_dev  = of->of_dma_data;
	struct qbam_channel *qbam_chan;
	u32 channel_index;
	u32 num_descriptors;

	if (dma_spec->args_count != QBAM_OF_SLAVE_N_ARGS) {
		qbam_err(qbam_dev,
			"invalid number of dma arguments, expect:%d got:%d\n",
			QBAM_OF_SLAVE_N_ARGS, dma_spec->args_count);
		return NULL;
	}

	channel_index = dma_spec->args[0];

	if (channel_index >= QBAM_MAX_CHANNELS) {
		qbam_err(qbam_dev,
			"error: channel_index:%d out of bounds",
			channel_index);
		return NULL;
	}
	qbam_chan = qbam_dev->channels[channel_index];
	 /* return qbam_chan if exists, or create one */
	if (qbam_chan) {
		qbam_chan->chan.client_count = 1;
		return &qbam_chan->chan;
	}

	num_descriptors = dma_spec->args[1];
	if (!num_descriptors || (num_descriptors > QBAM_MAX_DESCRIPTORS)) {
		qbam_err(qbam_dev,
			"invalid number of descriptors, range[1..%d] got:%d\n",
			QBAM_MAX_DESCRIPTORS, num_descriptors);
		return NULL;
	}

	/* allocate a channel */
	qbam_chan = kzalloc(sizeof(*qbam_chan), GFP_KERNEL);
	if (!qbam_chan)
		return NULL;

	/* allocate BAM resources for that channel */
	qbam_chan->bam_pipe.handle = sps_alloc_endpoint();
	if (!qbam_chan->bam_pipe.handle) {
		qbam_err(qbam_dev, "error: sps_alloc_endpoint() return NULL\n");
		kfree(qbam_chan);
		return NULL;
	}

	/* init dma_chan */
	qbam_chan->chan.device = &qbam_dev->dma_dev;
	dma_cookie_init(&qbam_chan->chan);
	qbam_chan->chan.client_count                 = 1;
	/* init qbam_chan */
	qbam_chan->bam_pipe.index                    = channel_index;
	qbam_chan->bam_pipe.num_descriptors          = num_descriptors;
	qbam_chan->bam_pipe.sps_connect_flags        = dma_spec->args[2];
	qbam_chan->bam_pipe.sps_register_event_flags = dma_spec->args[3];
	qbam_chan->qbam_dev                          = qbam_dev;
	mutex_init(&qbam_chan->lock);

	/* add to dma_device list of channels */
	list_add(&qbam_chan->chan.device_node, &qbam_dev->dma_dev.channels);
	qbam_dev->channels[channel_index] = qbam_chan;

	return &qbam_chan->chan;
}

static enum dma_status qbam_tx_status(struct dma_chan *chan,
			dma_cookie_t cookie, struct dma_tx_state *state)
{
	struct qbam_channel *qbam_chan = DMA_TO_QBAM_CHAN(chan);
	struct qbam_async_tx_descriptor	*qbam_desc = &qbam_chan->pending_desc;
	enum dma_status ret;

	mutex_lock(&qbam_chan->lock);

	if (qbam_chan->error) {
		mutex_unlock(&qbam_chan->lock);
		return DMA_ERROR;
	}

	ret = dma_cookie_status(chan, cookie, state);
	if (ret == DMA_IN_PROGRESS) {
		struct scatterlist *sg;
		int i;
		u32 transfer_size = 0;

		for_each_sg(qbam_desc->sgl, sg, qbam_desc->sg_len, i)
			transfer_size += sg_dma_len(sg);

		dma_set_residue(state, transfer_size);
	}
	mutex_unlock(&qbam_chan->lock);

	return ret;
}

/*
 * qbam_init_bam_handle - find or create bam handle.
 *
 * BAM device needs to be registered for each BLSP once and only once. if it
 * was registered, then we find the handle to the registered bam and return
 * it, otherwise we register it here.
 * The module which registered BAM is responsible for deregistering it.
 */
static int qbam_init_bam_handle(struct qbam_device *qbam_dev)
{
	int ret = 0;
	struct sps_bam_props bam_props = {0};

	/*
	 * Check if BAM is already registred with SPS on the current
	 * BLSP. If it isn't then go ahead and register it.
	 */
	ret = sps_phy2h(qbam_dev->mem_resource->start, &qbam_dev->handle);
	if (qbam_dev->handle)
		return 0;

	qbam_dev->regs = devm_ioremap_resource(qbam_dev->dma_dev.dev,
					       qbam_dev->mem_resource);
	if (IS_ERR(qbam_dev->regs)) {
		qbam_err(qbam_dev, "error:%ld ioremap(phy:0x%lx len:0x%lx)\n",
			 PTR_ERR(qbam_dev->regs),
			 (ulong) qbam_dev->mem_resource->start,
			 (ulong) resource_size(qbam_dev->mem_resource));
		return PTR_ERR(qbam_dev->regs);
	}

	bam_props.phys_addr		= qbam_dev->mem_resource->start;
	bam_props.virt_addr		= qbam_dev->regs;
	bam_props.summing_threshold	= qbam_dev->summing_threshold;
	bam_props.manage		= qbam_dev->manage;
	bam_props.irq			= qbam_dev->irq;

	ret = sps_register_bam_device(&bam_props, &qbam_dev->handle);
	if (ret)
		qbam_err(qbam_dev, "error:%d sps_register_bam_device\n"
			 "(phy:0x%lx virt:0x%lx irq:%d)\n",
			 ret, (ulong) bam_props.phys_addr,
			 (ulong) bam_props.virt_addr, qbam_dev->irq);
	else
		qbam_dev->deregister_required = true;

	return ret;
}


static int qbam_alloc_chan(struct dma_chan *chan)
{
	return 0;
}

static void qbam_eot_callback(struct sps_event_notify *notify)
{
	struct qbam_async_tx_descriptor *qbam_desc = notify->data.transfer.user;
	struct dma_async_tx_descriptor  *dma_desc  = &qbam_desc->dma_desc;
	dma_async_tx_callback callback	= dma_desc->callback;
	void *param			= dma_desc->callback_param;

	if (callback)
		callback(param);
}

static void qbam_error_callback(struct sps_event_notify *notify)
{
	struct qbam_channel *qbam_chan	= notify->user;

	qbam_err(qbam_chan->qbam_dev, "error: %s(pipe:%d\n)",
		 __func__, qbam_chan->bam_pipe.index);
}

static int qbam_connect_chan(struct qbam_channel *qbam_chan)
{
	int ret = 0;
	struct qbam_device       *qbam_dev = qbam_chan->qbam_dev;
	struct sps_register_event bam_eot_event = {
		.mode		= SPS_TRIGGER_CALLBACK,
		.options	= qbam_chan->bam_pipe.sps_register_event_flags,
		.callback	= qbam_eot_callback,
		};
	struct sps_register_event bam_error_event = {
		.mode		= SPS_TRIGGER_CALLBACK,
		.options	= SPS_O_ERROR,
		.callback	= qbam_error_callback,
		.user		= qbam_chan,
		};

	ret = sps_connect(qbam_chan->bam_pipe.handle, &qbam_chan->bam_pipe.cfg);
	if (ret) {
		qbam_err(qbam_dev, "error:%d sps_connect(pipe:%d)\n", ret,
			 qbam_chan->bam_pipe.index);
		return ret;
	}

	ret = sps_register_event(qbam_chan->bam_pipe.handle, &bam_eot_event);
	if (ret) {
		qbam_err(qbam_dev, "error:%d sps_register_event(eot@pipe:%d)\n",
			 ret, qbam_chan->bam_pipe.index);
		goto need_disconnect;
	}

	ret = sps_register_event(qbam_chan->bam_pipe.handle, &bam_error_event);
	if (ret) {
		qbam_err(qbam_dev, "error:%d sps_register_event(err@pipe:%d)\n",
			 ret, qbam_chan->bam_pipe.index);
		goto need_disconnect;
	}

	return 0;

need_disconnect:
	ret = sps_disconnect(qbam_chan->bam_pipe.handle);
	if (ret)
		qbam_err(qbam_dev, "error:%d sps_disconnect(pipe:%d)\n", ret,
			 qbam_chan->bam_pipe.index);
	return ret;
}

/*
 * qbam_slave_cfg - configure and connect a BAM pipe
 *
 * @cfg only cares about cfg->direction
 */
static int qbam_slave_cfg(struct dma_chan *chan,
						struct dma_slave_config *cfg)
{
	int ret = 0;
	struct qbam_channel *qbam_chan = DMA_TO_QBAM_CHAN(chan);
	struct qbam_device *qbam_dev = qbam_chan->qbam_dev;
	struct sps_connect *pipe_cfg = &qbam_chan->bam_pipe.cfg;

	if (!qbam_dev->handle) {
		ret = qbam_init_bam_handle(qbam_dev);
		if (ret)
			return ret;
	}

	if (qbam_chan->bam_pipe.cfg.desc.base)
		goto cfg_done;

	ret = sps_get_config(qbam_chan->bam_pipe.handle,
						&qbam_chan->bam_pipe.cfg);
	if (ret) {
		qbam_err(qbam_dev, "error:%d sps_get_config(0x%p)\n",
			 ret, qbam_chan->bam_pipe.handle);
		return ret;
	}

	qbam_chan->direction = cfg->direction;
	if (cfg->direction == DMA_MEM_TO_DEV) {
		pipe_cfg->source          = SPS_DEV_HANDLE_MEM;
		pipe_cfg->destination     = qbam_dev->handle;
		pipe_cfg->mode            = SPS_MODE_DEST;
		pipe_cfg->src_pipe_index  = 0;
		pipe_cfg->dest_pipe_index = qbam_chan->bam_pipe.index;
	} else {
		pipe_cfg->source          = qbam_dev->handle;
		pipe_cfg->destination     = SPS_DEV_HANDLE_MEM;
		pipe_cfg->mode            = SPS_MODE_SRC;
		pipe_cfg->src_pipe_index  = qbam_chan->bam_pipe.index;
		pipe_cfg->dest_pipe_index = 0;
	}
	pipe_cfg->options   =  qbam_chan->bam_pipe.sps_connect_flags;
	pipe_cfg->desc.size = (qbam_chan->bam_pipe.num_descriptors + 1) *
						 sizeof(struct sps_iovec);
	/* managed dma_alloc_coherent() */
	pipe_cfg->desc.base = dmam_alloc_coherent(qbam_dev->dma_dev.dev,
						  pipe_cfg->desc.size,
						  &pipe_cfg->desc.phys_base,
						  GFP_KERNEL);
	if (!pipe_cfg->desc.base) {
		qbam_err(qbam_dev,
			"error dma_alloc_coherent(desc-sz:%llu * n-descs:%d)\n",
			(u64) sizeof(struct sps_iovec),
			qbam_chan->bam_pipe.num_descriptors);
		return -ENOMEM;
	}
cfg_done:
	ret = qbam_connect_chan(qbam_chan);
	if (ret)
		dmam_free_coherent(qbam_dev->dma_dev.dev, pipe_cfg->desc.size,
				 pipe_cfg->desc.base, pipe_cfg->desc.phys_base);

	return ret;
}

static int qbam_flush_chan(struct dma_chan *chan)
{
	struct qbam_channel *qbam_chan = DMA_TO_QBAM_CHAN(chan);
	int ret = qbam_disconnect_chan(qbam_chan);

	if (ret) {
		qbam_err(qbam_chan->qbam_dev,
			 "error: disconnect flush(pipe:%d\n)",
			 qbam_chan->bam_pipe.index);
		return ret;
	}
	ret = qbam_connect_chan(qbam_chan);
	if (ret)
		qbam_err(qbam_chan->qbam_dev,
			 "error: reconnect flush(pipe:%d\n)",
			 qbam_chan->bam_pipe.index);
	return ret;
}

/* qbam_tx_submit - sets the descriptor as the next one to be executed */
static dma_cookie_t qbam_tx_submit(struct dma_async_tx_descriptor *dma_desc)
{
	struct qbam_channel *qbam_chan = DMA_TO_QBAM_CHAN(dma_desc->chan);
	dma_cookie_t ret;

	mutex_lock(&qbam_chan->lock);

	ret = dma_cookie_assign(dma_desc);

	mutex_unlock(&qbam_chan->lock);

	return ret;
}

/*
 * qbam_prep_slave_sg - creates qbam_xfer_buf from a list of sg
 *
 * @chan: dma channel
 * @sgl: scatter gather list
 * @sg_len: length of sg
 * @direction: DMA transfer direction
 * @flags: DMA flags
 * @context: transfer context (unused)
 * @return the newly created descriptor or negative ERR_PTR() on error
 */
static struct dma_async_tx_descriptor *qbam_prep_slave_sg(struct dma_chan *chan,
	struct scatterlist *sgl, unsigned int sg_len,
	enum dma_transfer_direction direction, unsigned long flags,
	void *context)
{
	struct qbam_channel *qbam_chan = DMA_TO_QBAM_CHAN(chan);
	struct qbam_device *qbam_dev = qbam_chan->qbam_dev;
	struct qbam_async_tx_descriptor *qbam_desc = &qbam_chan->pending_desc;

	if (qbam_chan->direction != direction) {
		qbam_err(qbam_dev,
			"invalid dma transfer direction expected:%d given:%d\n",
			qbam_chan->direction, direction);
		return ERR_PTR(-EINVAL);
	}

	qbam_desc->dma_desc.chan	= &qbam_chan->chan;
	qbam_desc->dma_desc.tx_submit	= qbam_tx_submit;
	qbam_desc->sgl			= sgl;
	qbam_desc->sg_len		= sg_len;
	qbam_desc->flags		= flags;
	return &qbam_desc->dma_desc;
}

/*
 * qbam_issue_pending - queue pending descriptor to BAM
 *
 * Iterate over the transfers of the pending descriptor and push them to bam
 */
static void qbam_issue_pending(struct dma_chan *chan)
{
	int i;
	int ret = 0;
	struct qbam_channel *qbam_chan = DMA_TO_QBAM_CHAN(chan);
	struct qbam_device  *qbam_dev  = qbam_chan->qbam_dev;
	struct qbam_async_tx_descriptor *qbam_desc = &qbam_chan->pending_desc;
	struct scatterlist		*sg;

	mutex_lock(&qbam_chan->lock);
	if (!qbam_chan->pending_desc.sgl) {
		qbam_err(qbam_dev,
		   "error %s() no pending descriptor pipe:%d\n",
		   __func__, qbam_chan->bam_pipe.index);
		mutex_unlock(&qbam_chan->lock);
		return;
	}

	for_each_sg(qbam_desc->sgl, sg, qbam_desc->sg_len, i) {

		/* Add BAM flags only on the last buffer */
		bool is_last_buf = (i == ((qbam_desc->sg_len) - 1));

		ret = sps_transfer_one(qbam_chan->bam_pipe.handle,
					sg_dma_address(sg), sg_dma_len(sg),
					qbam_desc,
					(is_last_buf ? qbam_desc->flags : 0));
		if (ret < 0) {
			qbam_chan->error = ret;

			qbam_err(qbam_dev, "erorr:%d sps_transfer_one\n"
				"(addr:0x%lx len:%d flags:0x%lx pipe:%d)\n",
				ret, (ulong) sg_dma_address(sg), sg_dma_len(sg),
				qbam_desc->flags, qbam_chan->bam_pipe.index);
			break;
		}
	}

	dma_cookie_complete(&qbam_desc->dma_desc);
	qbam_chan->error = 0;
	qbam_desc->sgl = NULL;
	qbam_desc->sg_len = 0;
	mutex_unlock(&qbam_chan->lock);
};

static int qbam_deregister_bam_dev(struct qbam_device *qbam_dev)
{
	int ret;

	if (!qbam_dev->handle)
		return 0;

	ret = sps_deregister_bam_device(qbam_dev->handle);
	if (ret)
		qbam_err(qbam_dev,
			"error:%d sps_deregister_bam_device(hndl:0x%lx) failed",
			ret, qbam_dev->handle);
	return ret;
}

static void qbam_pipes_free(struct qbam_device *qbam_dev)
{
	struct qbam_channel *qbam_chan_cur, *qbam_chan_next;

	list_for_each_entry_safe(qbam_chan_cur, qbam_chan_next,
			&qbam_dev->dma_dev.channels, chan.device_node) {
		mutex_lock(&qbam_chan_cur->lock);
		qbam_free_chan(&qbam_chan_cur->chan);
		sps_free_endpoint(qbam_chan_cur->bam_pipe.handle);
		list_del(&qbam_chan_cur->chan.device_node);
		mutex_unlock(&qbam_chan_cur->lock);
		kfree(qbam_chan_cur);
	}
}

static int qbam_probe(struct platform_device *pdev)
{
	struct qbam_device *qbam_dev;
	int ret;
	bool managed_locally;
	struct device_node *of_node = pdev->dev.of_node;

	qbam_dev = devm_kzalloc(&pdev->dev, sizeof(*qbam_dev), GFP_KERNEL);
	if (!qbam_dev)
		return -ENOMEM;

	qbam_dev->dma_dev.dev = &pdev->dev;
	platform_set_drvdata(pdev, qbam_dev);

	qbam_dev->mem_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!qbam_dev->mem_resource) {
		qbam_err(qbam_dev, "missing 'reg' DT entry");
		return -ENODEV;
	}

	qbam_dev->irq = platform_get_irq(pdev, 0);
	if (qbam_dev->irq < 0) {
		qbam_err(qbam_dev, "missing DT IRQ resource entry");
		return -EINVAL;
	}

	ret = of_property_read_u32(of_node, QBAM_OF_SUM_THRESHOLD,
				   &qbam_dev->summing_threshold);
	if (ret) {
		qbam_err(qbam_dev, "missing '%s' DT entry",
			 QBAM_OF_SUM_THRESHOLD);
		return ret;
	}

	/* read from DT and set sps_bam_props.manage */
	managed_locally = of_property_read_bool(of_node, QBAM_OF_MANAGE_LOCAL);
	qbam_dev->manage = managed_locally ? SPS_BAM_MGR_LOCAL :
					     SPS_BAM_MGR_DEVICE_REMOTE;

	/* Init channels */
	INIT_LIST_HEAD(&qbam_dev->dma_dev.channels);

	/* Set capabilities */
	dma_cap_zero(qbam_dev->dma_dev.cap_mask);
	dma_cap_set(DMA_SLAVE,		qbam_dev->dma_dev.cap_mask);
	dma_cap_set(DMA_PRIVATE,	qbam_dev->dma_dev.cap_mask);

	/* Initialize dmaengine callback apis */
	qbam_dev->dma_dev.device_alloc_chan_resources	= qbam_alloc_chan;
	qbam_dev->dma_dev.device_free_chan_resources	= qbam_free_chan;
	qbam_dev->dma_dev.device_prep_slave_sg		= qbam_prep_slave_sg;
	qbam_dev->dma_dev.device_terminate_all		= qbam_flush_chan;
	qbam_dev->dma_dev.device_config			= qbam_slave_cfg;
	qbam_dev->dma_dev.device_issue_pending		= qbam_issue_pending;
	qbam_dev->dma_dev.device_tx_status		= qbam_tx_status;

	/* Regiser to DMA framework */
	dma_async_device_register(&qbam_dev->dma_dev);

	/*
	 * Do not return error in order to not break the existing
	 * way of requesting channels.
	 */
	ret = of_dma_controller_register(of_node, qbam_dma_xlate, qbam_dev);
	if (ret) {
		qbam_err(qbam_dev, "error:%d of_dma_controller_register()\n",
			 ret);
		goto err_unregister_dma;
	}
	return 0;

err_unregister_dma:
	dma_async_device_unregister(&qbam_dev->dma_dev);
	if (qbam_dev->deregister_required)
		return qbam_deregister_bam_dev(qbam_dev);

	return ret;
}

static int qbam_remove(struct platform_device *pdev)
{
	struct qbam_device *qbam_dev = platform_get_drvdata(pdev);

	dma_async_device_unregister(&qbam_dev->dma_dev);

	/* free BAM pipes resources */
	qbam_pipes_free(qbam_dev);

	if (qbam_dev->deregister_required)
		return qbam_deregister_bam_dev(qbam_dev);

	return 0;
}

static const struct of_device_id qbam_of_match[] = {
	{ .compatible = "qcom,sps-dma" },
	{}
};
MODULE_DEVICE_TABLE(of, qbam_of_match);

static struct platform_driver qbam_driver = {
	.probe = qbam_probe,
	.remove = qbam_remove,
	.driver = {
		.name = "qcom-sps-dma",
		.of_match_table = qbam_of_match,
	},
};

module_platform_driver(qbam_driver);

MODULE_DESCRIPTION("DMA-API driver to qcom BAM");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:qcom-sps-dma");