[PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support
Vinod Koul
vinod.koul at linux.intel.com
Thu Jun 7 05:22:41 EDT 2012
On Mon, 2012-06-04 at 14:37 +0800, Zhangfei Gao wrote:
> Add support for two-channel dma under dmaengine
> support: mmp-adma and pxa910-squ
>
> Signed-off-by: Zhangfei Gao <zhangfei.gao at marvell.com>
> Signed-off-by: Leo Yan <leoy at marvell.com>
> Signed-off-by: Qiao Zhou <zhouqiao at marvell.com>
> +
> +static int mmp_tdma_clear_chan_irq(struct mmp_tdma_chan *tdmac)
> +{
> + u32 reg = readl(tdmac->reg_base + TDISR);
> +
> + if (reg & TDISR_COMP) {
> + /* clear irq */
> + reg &= ~TDISR_COMP;
> + writel(reg, tdmac->reg_base + TDISR);
> +
> + return 1;
hrmm.?
> + }
> + return 0;
> +}
> +
> +static irqreturn_t mmp_tdma_chan_handler(int irq, void *dev_id)
> +{
> + struct mmp_tdma_chan *tdmac = dev_id;
> +
> + if (mmp_tdma_clear_chan_irq(tdmac)) {
> + tdmac->pos = (tdmac->pos + tdmac->period_len) % tdmac->buf_len;
> + tasklet_schedule(&tdmac->tasklet);
> + return IRQ_HANDLED;
> + } else
> + return IRQ_NONE;
> +}
> +
> +static irqreturn_t mmp_tdma_int_handler(int irq, void *dev_id)
> +{
> + struct mmp_tdma_device *tdev = dev_id;
> + int i, ret;
> + int irq_num = 0;
> +
> + for (i = 0; i < TDMA_CHANNEL_NUM; i++) {
> + struct mmp_tdma_chan *tdmac = tdev->tdmac[i];
> +
> + ret = mmp_tdma_chan_handler(irq, tdmac);
> + if (ret == IRQ_HANDLED)
> + irq_num++;
> + }
> +
> + if (irq_num)
> + return IRQ_HANDLED;
> + else
> + return IRQ_NONE;
> +}
> +
> +static void dma_do_tasklet(unsigned long data)
> +{
> + struct mmp_tdma_chan *tdmac = (struct mmp_tdma_chan *)data;
> +
> + if (tdmac->desc.callback)
> + tdmac->desc.callback(tdmac->desc.callback_param);
> +
> +}
> +
> +static void mmp_tdma_free_descriptor(struct mmp_tdma_chan *tdmac)
> +{
> + struct gen_pool *gpool;
> + int size = tdmac->desc_num * sizeof(struct mmp_tdma_desc);
> +
> + gpool = sram_get_gpool("asram");
> + if (tdmac->desc_arr)
> + gen_pool_free(gpool, (unsigned long)tdmac->desc_arr,
> + size);
> + tdmac->desc_arr = NULL;
> +
> + return;
> +}
> +
> +static dma_cookie_t mmp_tdma_tx_submit(struct dma_async_tx_descriptor *tx)
> +{
> + struct mmp_tdma_chan *tdmac = to_mmp_tdma_chan(tx->chan);
> +
> + mmp_tdma_chan_set_desc(tdmac, tdmac->desc_arr_phys);
This seems odd. In submit you are supposed to move this to your pending
list. Btw where is the .issue_pending handler?
> +
> + return 0;
> +}
> +
> +static int mmp_tdma_alloc_chan_resources(struct dma_chan *chan)
> +{
> + struct mmp_tdma_chan *tdmac = to_mmp_tdma_chan(chan);
> + int ret;
> +
> + dev_dbg(tdmac->dev, "%s: enter\n", __func__);
> +
> + dma_async_tx_descriptor_init(&tdmac->desc, chan);
> + tdmac->desc.tx_submit = mmp_tdma_tx_submit;
> +
> + if (tdmac->irq) {
> + ret = devm_request_irq(tdmac->dev, tdmac->irq,
> + mmp_tdma_chan_handler, IRQF_DISABLED, "tdma", tdmac);
> + if (ret)
> + goto err_request_irq;
> + }
> +
> + return 0;
NO. This is supposed to return the number of descriptors allocated.
> +
> +err_request_irq:
> + mmp_tdma_free_descriptor(tdmac);
> + return ret;
> +}
> +
> +static void mmp_tdma_free_chan_resources(struct dma_chan *chan)
> +{
> + struct mmp_tdma_chan *tdmac = to_mmp_tdma_chan(chan);
> +
> + dev_dbg(tdmac->dev, "%s: enter\n", __func__);
> +
> + if (tdmac->irq)
> + devm_free_irq(tdmac->dev, tdmac->irq, tdmac);
> + mmp_tdma_disable_chan(tdmac);
channel should be already disabled, why do you need this here?
> + mmp_tdma_free_descriptor(tdmac);
> + return;
> +}
> +
> +
> +static struct dma_async_tx_descriptor *mmp_tdma_prep_slave_sg(
> + struct dma_chan *chan, struct scatterlist *sgl,
> + unsigned int sg_len, enum dma_transfer_direction direction,
> + unsigned long flags, void *context)
> +{
> + /*
> + * This operation is not supported on the TDMA controller
> + *
> + * However, we need to provide the function pointer to allow the
> + * device_control() method to work.
> + */
> + return NULL;
> +}
Pls remove if not supported
> +
> +static int mmp_tdma_alloc_descriptor(struct mmp_tdma_chan *tdmac)
> +{
> + struct gen_pool *gpool;
> + int size = tdmac->desc_num * sizeof(struct mmp_tdma_desc);
> +
> + dev_dbg(tdmac->dev, "%s: enter\n", __func__);
> +
> + gpool = sram_get_gpool("asram");
> + if (!gpool)
> + return -ENOMEM;
> +
> + tdmac->desc_arr = (void *)gen_pool_alloc(gpool, size);
> + if (!tdmac->desc_arr)
> + return -ENOMEM;
> +
> + tdmac->desc_arr_phys = gen_pool_virt_to_phys(gpool,
> + (unsigned long)tdmac->desc_arr);
> +
> + return 0;
this needs fix
> +}
> +
> +static struct dma_async_tx_descriptor *mmp_tdma_prep_dma_cyclic(
> + struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len,
> + size_t period_len, enum dma_transfer_direction direction,
> + void *context)
> +{
> + struct mmp_tdma_chan *tdmac = to_mmp_tdma_chan(chan);
> + int num_periods = buf_len / period_len;
> + int i = 0, buf = 0;
> + int ret;
> +
> + if (tdmac->status != DMA_SUCCESS)
> + return NULL;
> +
> + if (period_len > TDMA_MAX_XFER_BYTES) {
> + dev_err(tdmac->dev,
> + "maximum period size exceeded: %d > %d\n",
> + period_len, TDMA_MAX_XFER_BYTES);
> + goto err_out;
> + }
> +
> + tdmac->status = DMA_IN_PROGRESS;
???
> + tdmac->desc_num = num_periods;
> + ret = mmp_tdma_alloc_descriptor(tdmac);
> + if (ret < 0)
> + goto err_out;
> +
> + while (buf < buf_len) {
> + struct mmp_tdma_desc *desc = &tdmac->desc_arr[i];
what if i call prepare twice on the same channel?
Better way would to manage a descriptor list, see other drivers for
examples.
> +
> + if (i + 1 == num_periods)
> + desc->nxt_desc = tdmac->desc_arr_phys;
> + else
> + desc->nxt_desc = tdmac->desc_arr_phys +
> + sizeof(*desc) * (i + 1);
pls use kernel link list, it is provided to you so that you dont have to
do above.
> +
> + if (direction == DMA_MEM_TO_DEV) {
> + desc->src_addr = dma_addr;
> + desc->dst_addr = tdmac->dev_addr;
> + } else {
> + desc->src_addr = tdmac->dev_addr;
> + desc->dst_addr = dma_addr;
> + }
> + desc->byte_cnt = period_len;
> + dma_addr += period_len;
> + buf += period_len;
> + i++;
> + }
> +
> + tdmac->buf_len = buf_len;
> + tdmac->period_len = period_len;
> + tdmac->pos = 0;
> +
> + return &tdmac->desc;
> +
> +err_out:
> + tdmac->status = DMA_ERROR;
> + return NULL;
> +}
> +
> +static int mmp_tdma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
> + unsigned long arg)
> +{
> + struct mmp_tdma_chan *tdmac = to_mmp_tdma_chan(chan);
> + struct dma_slave_config *dmaengine_cfg = (void *)arg;
> + int ret = 0;
> +
> + switch (cmd) {
> + case DMA_TERMINATE_ALL:
> + mmp_tdma_disable_chan(tdmac);
> + break;
> + case DMA_PAUSE:
> + mmp_tdma_pause_chan(tdmac);
> + break;
> + case DMA_RESUME:
> + mmp_tdma_resume_chan(tdmac);
> + break;
> + case DMA_SLAVE_CONFIG:
> + if (dmaengine_cfg->direction == DMA_DEV_TO_MEM) {
> + tdmac->dev_addr = dmaengine_cfg->src_addr;
> + tdmac->burst_sz = dmaengine_cfg->src_maxburst;
> + } else {
> + tdmac->dev_addr = dmaengine_cfg->dst_addr;
> + tdmac->burst_sz = dmaengine_cfg->dst_maxburst;
> + }
> + tdmac->dir = dmaengine_cfg->direction;
> + return mmp_tdma_config_chan(tdmac);
> + default:
> + ret = -ENOSYS;
> + }
> +
> + return ret;
> +}
> +
> +static enum dma_status mmp_tdma_tx_status(struct dma_chan *chan,
> + dma_cookie_t cookie, struct dma_tx_state *txstate)
> +{
> + struct mmp_tdma_chan *tdmac = to_mmp_tdma_chan(chan);
> +
> + dma_set_residue(txstate, tdmac->buf_len - tdmac->pos);
> +
> + return tdmac->status;
> +}
> +
> +static void mmp_tdma_issue_pending(struct dma_chan *chan)
> +{
> + struct mmp_tdma_chan *tdmac = to_mmp_tdma_chan(chan);
> +
> + mmp_tdma_enable_chan(tdmac);
can you queue the descriptors here? That was purpose to have separate
issue pending and submit.
> +}
> +
> +static int __devexit mmp_tdma_remove(struct platform_device *pdev)
> +{
> + struct mmp_tdma_device *tdev = platform_get_drvdata(pdev);
> +
> + dma_async_device_unregister(&tdev->device);
> + return 0;
> +}
> +
> +static int __devinit mmp_tdma_chan_init(struct mmp_tdma_device *tdev,
> + int idx, int irq, int type)
> +{
> + struct mmp_tdma_chan *tdmac;
> +
> + if (idx >= TDMA_CHANNEL_NUM) {
> + dev_err(tdev->dev, "too many channels for device!\n");
> + return -EINVAL;
> + }
> +
> + /* alloc channel */
> + tdmac = devm_kzalloc(tdev->dev, sizeof(*tdmac), GFP_KERNEL);
> + if (!tdmac) {
> + dev_err(tdev->dev, "no free memory for DMA channels!\n");
> + return -ENOMEM;
> + }
> + if (irq)
> + tdmac->irq = irq + idx;
> + tdmac->dev = tdev->dev;
> + tdmac->chan.device = &tdev->device;
> + tdmac->idx = idx;
> + tdmac->type = type;
> + tdmac->reg_base = (unsigned long)tdev->base + idx * 4;
> + tdmac->status = DMA_SUCCESS;
> + tdev->tdmac[tdmac->idx] = tdmac;
> + tasklet_init(&tdmac->tasklet, dma_do_tasklet, (unsigned long)tdmac);
> +
> + /* add the channel to tdma_chan list */
> + list_add_tail(&tdmac->chan.device_node,
> + &tdev->device.channels);
> +
> + return 0;
> +}
> +
> +static int __devinit mmp_tdma_probe(struct platform_device *pdev)
> +{
> + const struct platform_device_id *id = platform_get_device_id(pdev);
> + enum mmp_tdma_type type = id->driver_data;
> + struct mmp_tdma_device *tdev;
> + struct resource *iores;
> + int i, ret;
> + int irq = 0;
> + int chan_num = TDMA_CHANNEL_NUM;
> +
> + /* always have couple channels */
> + tdev = devm_kzalloc(&pdev->dev, sizeof(*tdev), GFP_KERNEL);
> + if (!tdev)
> + return -ENOMEM;
> +
> + tdev->dev = &pdev->dev;
> + iores = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
> + if (!iores)
> + return -EINVAL;
> +
> + if (resource_size(iores) != chan_num)
> + tdev->irq = iores->start;
> + else
> + irq = iores->start;
> +
> + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!iores)
> + return -EINVAL;
> +
> + tdev->base = devm_request_and_ioremap(&pdev->dev, iores);
> + if (!tdev->base)
> + return -EADDRNOTAVAIL;
> +
> + if (tdev->irq) {
> + ret = devm_request_irq(&pdev->dev, tdev->irq,
> + mmp_tdma_int_handler, IRQF_DISABLED, "tdma", tdev);
> + if (ret)
> + return ret;
> + }
> +
> + dma_cap_set(DMA_SLAVE, tdev->device.cap_mask);
> + dma_cap_set(DMA_CYCLIC, tdev->device.cap_mask);
> +
> + INIT_LIST_HEAD(&tdev->device.channels);
> +
> + /* initialize channel parameters */
> + for (i = 0; i < chan_num; i++) {
> + ret = mmp_tdma_chan_init(tdev, i, irq, type);
> + if (ret)
> + return ret;
> + }
> +
> + tdev->device.dev = &pdev->dev;
> + tdev->device.device_alloc_chan_resources =
> + mmp_tdma_alloc_chan_resources;
> + tdev->device.device_free_chan_resources =
> + mmp_tdma_free_chan_resources;
> + tdev->device.device_prep_slave_sg = mmp_tdma_prep_slave_sg;
> + tdev->device.device_prep_dma_cyclic = mmp_tdma_prep_dma_cyclic;
> + tdev->device.device_tx_status = mmp_tdma_tx_status;
> + tdev->device.device_issue_pending = mmp_tdma_issue_pending;
> + tdev->device.device_control = mmp_tdma_control;
> + tdev->device.copy_align = TDMA_ALIGNMENT;
> +
> + dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
> + platform_set_drvdata(pdev, tdev);
> +
> + ret = dma_async_device_register(&tdev->device);
> + if (ret) {
> + dev_err(tdev->device.dev, "unable to register\n");
> + return ret;
> + }
> +
> + dev_info(tdev->device.dev, "initialized\n");
> + return 0;
> +}
> +
> +static const struct platform_device_id mmp_tdma_id_table[] = {
> + { "mmp-adma", MMP_AUD_TDMA },
> + { "pxa910-squ", PXA910_SQU },
> + { },
> +};
> +
> +static struct platform_driver mmp_tdma_driver = {
> + .driver = {
> + .name = "mmp-tdma",
> + .owner = THIS_MODULE,
> + },
> + .id_table = mmp_tdma_id_table,
> + .probe = mmp_tdma_probe,
> + .remove = __devexit_p(mmp_tdma_remove),
> +};
> +
> +module_platform_driver(mmp_tdma_driver);
> +
> +MODULE_DESCRIPTION("MMP Two-Channel DMA Driver");
> +MODULE_LICENSE("GPL");
AUTHOR, ALIAS too pls
> diff --git a/include/linux/platform_data/mmp_dma.h b/include/linux/platform_data/mmp_dma.h
> new file mode 100644
> index 0000000..4e21cf9
> --- /dev/null
> +++ b/include/linux/platform_data/mmp_dma.h
> @@ -0,0 +1,20 @@
> +/*
> + * MMP Platform DMA Management
> + *
> + * Copyright (c) 2011 Marvell Semiconductors Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef MMP_DMA_H
> +#define MMP_DMA_H
> +
> +struct mmp_tdma_data {
> + u32 bus_size;
> + u32 pack_mod;
> +};
> +
> +#endif /* MMP_DMA_H */
why do you need separate header for this?
--
~Vinod
More information about the linux-arm-kernel
mailing list