You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
722 lines
20 KiB
722 lines
20 KiB
7 years ago
|
/*
|
||
|
* Copyright (c) 2014-2015,2017-2018, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* 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",
|
||
|
.owner = THIS_MODULE,
|
||
|
.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");
|