diff options
Diffstat (limited to 'drivers/dma/qcom-sps-dma.c')
-rw-r--r-- | drivers/dma/qcom-sps-dma.c | 724 |
1 files changed, 724 insertions, 0 deletions
diff --git a/drivers/dma/qcom-sps-dma.c b/drivers/dma/qcom-sps-dma.c new file mode 100644 index 000000000000..959e284bdb71 --- /dev/null +++ b/drivers/dma/qcom-sps-dma.c @@ -0,0 +1,724 @@ +/* + * Copyright (c) 2014-2015, 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) { + qbam_err(qbam_dev, "error kmalloc(size:%llu) failed\n", + (u64) sizeof(*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 registerd for each BLSP once and only once. if it was + * registred, then we find the handle to the registerd bam and return it, + * otherwise we register it here. + * The module which registerd 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: qbam_error_callback(pipe:%d\n)", + 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 qbam_issue_pending() no pending descriptor pipe:%d\n", + 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) { + qbam_err(qbam_dev, "error kmalloc(size:%llu) failed", + (u64) sizeof(*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 */ + ret = dma_async_device_register(&qbam_dev->dma_dev); + if (ret) { + qbam_err(qbam_dev, "error:%d dma_async_device_register()\n", + ret); + goto err_unregister_bam; + } + + 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); +err_unregister_bam: + 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"); |