qcom-sps-dma: Add snapshot of QCOM SPS DMA driver
This change adds a snapshot of QCOM SPS DMA driver from the msm-4.19 'commit 8b158709ddb7 ("qcom-sps-dma: Add snapshot of QCOM SPS DMA driver"")'. Change-Id: Ie340044913dfe8638b0ba336e67e51386432826a Signed-off-by: Vipin Deep Kaur <vkaur@codeaurora.org>
This commit is contained in:
parent
2920b62683
commit
f4a9c2e742
@ -477,6 +477,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
|
||||
|
@ -75,6 +75,7 @@ obj-$(CONFIG_UNIPHIER_MDMAC) += uniphier-mdmac.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
drivers/dma/qcom-sps-dma.c
Normal file
711
drivers/dma/qcom-sps-dma.c
Normal file
@ -0,0 +1,711 @@
|
||||
// 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");
|
Loading…
Reference in New Issue
Block a user