* [PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support
2012-06-04 6:37 [PATCH v1 0/5] mmp audio support Zhangfei Gao
@ 2012-06-04 6:37 ` Zhangfei Gao
2012-06-07 9:22 ` Vinod Koul
2012-06-04 6:37 ` [PATCH v1 2/5] ASoC: mmp: add audio dma support Zhangfei Gao
` (3 subsequent siblings)
4 siblings, 1 reply; 20+ messages in thread
From: Zhangfei Gao @ 2012-06-04 6:37 UTC (permalink / raw)
To: linux-arm-kernel
Add support for two-channel dma under dmaengine
support: mmp-adma and pxa910-squ
Signed-off-by: Zhangfei Gao <zhangfei.gao@marvell.com>
Signed-off-by: Leo Yan <leoy@marvell.com>
Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
---
drivers/dma/Kconfig | 10 +
drivers/dma/Makefile | 1 +
drivers/dma/mmp_tdma.c | 649 +++++++++++++++++++++++++++++++++
include/linux/platform_data/mmp_dma.h | 20 +
4 files changed, 680 insertions(+), 0 deletions(-)
create mode 100644 drivers/dma/mmp_tdma.c
create mode 100644 include/linux/platform_data/mmp_dma.h
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index ef378b5..26d5b1b 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -259,6 +259,16 @@ config DMA_SA11X0
SA-1110 SoCs. This DMA engine can only be used with on-chip
devices.
+config MMP_TDMA
+ bool "MMP Two-Channel DMA support"
+ default ARCH_MMP
+ select DMA_ENGINE
+ help
+ Support the MMP Two-Channel DMA engine.
+ This engine used for MMP Audio DMA and pxa910 SQU.
+
+ Say Y here if you enabled MMP ADMA, otherwise say N.
+
config DMA_ENGINE
bool
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 86b795b..37e9258 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -28,3 +28,4 @@ obj-$(CONFIG_PCH_DMA) += pch_dma.o
obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o
obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o
obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o
+obj-$(CONFIG_MMP_TDMA) += mmp_tdma.o
diff --git a/drivers/dma/mmp_tdma.c b/drivers/dma/mmp_tdma.c
new file mode 100644
index 0000000..f92c70e
--- /dev/null
+++ b/drivers/dma/mmp_tdma.c
@@ -0,0 +1,649 @@
+/*
+ * Driver For Marvell Two-channel DMA Engine
+ *
+ * Copyright: Marvell International Ltd.
+ * Leo Yan <leoy@marvell.com>
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/platform_data/mmp_dma.h>
+#include <mach/regs-icu.h>
+#include <mach/sram.h>
+
+#include "dmaengine.h"
+
+/*
+ * Two-Channel DMA registers
+ */
+#define TDBCR 0x00 /* Byte Count */
+#define TDSAR 0x10 /* Src Addr */
+#define TDDAR 0x20 /* Dst Addr */
+#define TDNDPR 0x30 /* Next Desc */
+#define TDCR 0x40 /* Control */
+#define TDCP 0x60 /* Priority*/
+#define TDCDPR 0x70 /* Current Desc */
+#define TDIMR 0x80 /* Int Mask */
+#define TDISR 0xa0 /* Int Status */
+
+/* Two-Channel DMA Control Register */
+#define TDCR_SSZ_8_BITS (0x0 << 22) /* Sample Size */
+#define TDCR_SSZ_12_BITS (0x1 << 22)
+#define TDCR_SSZ_16_BITS (0x2 << 22)
+#define TDCR_SSZ_20_BITS (0x3 << 22)
+#define TDCR_SSZ_24_BITS (0x4 << 22)
+#define TDCR_SSZ_32_BITS (0x5 << 22)
+#define TDCR_SSZ_SHIFT (0x1 << 22)
+#define TDCR_SSZ_MASK (0x7 << 22)
+#define TDCR_SSPMOD (0x1 << 21) /* SSP MOD */
+#define TDCR_ABR (0x1 << 20) /* Channel Abort */
+#define TDCR_CDE (0x1 << 17) /* Close Desc Enable */
+#define TDCR_PACKMOD (0x1 << 16) /* Pack Mode (ADMA Only) */
+#define TDCR_CHANACT (0x1 << 14) /* Channel Active */
+#define TDCR_FETCHND (0x1 << 13) /* Fetch Next Desc */
+#define TDCR_CHANEN (0x1 << 12) /* Channel Enable */
+#define TDCR_INTMODE (0x1 << 10) /* Interrupt Mode */
+#define TDCR_CHAINMOD (0x1 << 9) /* Chain Mode */
+#define TDCR_BURSTSZ_MSK (0x7 << 6) /* Burst Size */
+#define TDCR_BURSTSZ_4B (0x0 << 6)
+#define TDCR_BURSTSZ_8B (0x1 << 6)
+#define TDCR_BURSTSZ_16B (0x3 << 6)
+#define TDCR_BURSTSZ_32B (0x6 << 6)
+#define TDCR_BURSTSZ_64B (0x7 << 6)
+#define TDCR_BURSTSZ_SQU_32B (0x7 << 6)
+#define TDCR_BURSTSZ_128B (0x5 << 6)
+#define TDCR_DSTDIR_MSK (0x3 << 4) /* Dst Direction */
+#define TDCR_DSTDIR_ADDR_HOLD (0x2 << 4) /* Dst Addr Hold */
+#define TDCR_DSTDIR_ADDR_INC (0x0 << 4) /* Dst Addr Increment */
+#define TDCR_SRCDIR_MSK (0x3 << 2) /* Src Direction */
+#define TDCR_SRCDIR_ADDR_HOLD (0x2 << 2) /* Src Addr Hold */
+#define TDCR_SRCDIR_ADDR_INC (0x0 << 2) /* Src Addr Increment */
+#define TDCR_DSTDESCCONT (0x1 << 1)
+#define TDCR_SRCDESTCONT (0x1 << 0)
+
+/* Two-Channel DMA Int Mask Register */
+#define TDIMR_COMP (0x1 << 0)
+
+/* Two-Channel DMA Int Status Register */
+#define TDISR_COMP (0x1 << 0)
+
+/*
+ * Two-Channel DMA Descriptor Struct
+ * NOTE: desc's buf must be aligned to 16 bytes.
+ */
+struct mmp_tdma_desc {
+ u32 byte_cnt;
+ u32 src_addr;
+ u32 dst_addr;
+ u32 nxt_desc;
+};
+
+enum mmp_tdma_type {
+ MMP_AUD_TDMA = 0,
+ PXA910_SQU,
+};
+
+#define TDMA_ALIGNMENT 3
+#define TDMA_MAX_XFER_BYTES SZ_64K
+
+struct mmp_tdma_chan {
+ struct device *dev;
+ struct dma_chan chan;
+ struct dma_async_tx_descriptor desc;
+ struct tasklet_struct tasklet;
+
+ struct mmp_tdma_desc *desc_arr;
+ phys_addr_t desc_arr_phys;
+ int desc_num;
+ enum dma_transfer_direction dir;
+ dma_addr_t dev_addr;
+ u32 burst_sz;
+ enum dma_status status;
+
+ int idx;
+ enum mmp_tdma_type type;
+ int irq;
+ unsigned long reg_base;
+
+ size_t buf_len;
+ size_t period_len;
+ size_t pos;
+};
+
+#define TDMA_CHANNEL_NUM 2
+struct mmp_tdma_device {
+ struct device *dev;
+ void __iomem *base;
+ struct dma_device device;
+ struct mmp_tdma_chan *tdmac[TDMA_CHANNEL_NUM];
+ int irq;
+};
+
+#define to_mmp_tdma_chan(dchan) container_of(dchan, struct mmp_tdma_chan, chan)
+
+static void mmp_tdma_chan_set_desc(struct mmp_tdma_chan *tdmac, dma_addr_t phys)
+{
+ writel(phys, tdmac->reg_base + TDNDPR);
+ writel(readl(tdmac->reg_base + TDCR) | TDCR_FETCHND,
+ tdmac->reg_base + TDCR);
+}
+
+static void mmp_tdma_enable_chan(struct mmp_tdma_chan *tdmac)
+{
+ /* enable irq */
+ writel(TDIMR_COMP, tdmac->reg_base + TDIMR);
+ /* enable dma chan */
+ writel(readl(tdmac->reg_base + TDCR) | TDCR_CHANEN,
+ tdmac->reg_base + TDCR);
+ tdmac->status = DMA_IN_PROGRESS;
+}
+
+static void mmp_tdma_disable_chan(struct mmp_tdma_chan *tdmac)
+{
+ writel(readl(tdmac->reg_base + TDCR) & ~TDCR_CHANEN,
+ tdmac->reg_base + TDCR);
+ tdmac->status = DMA_SUCCESS;
+}
+
+static void mmp_tdma_resume_chan(struct mmp_tdma_chan *tdmac)
+{
+ writel(readl(tdmac->reg_base + TDCR) | TDCR_CHANEN,
+ tdmac->reg_base + TDCR);
+ tdmac->status = DMA_IN_PROGRESS;
+}
+
+static void mmp_tdma_pause_chan(struct mmp_tdma_chan *tdmac)
+{
+ writel(readl(tdmac->reg_base + TDCR) & ~TDCR_CHANEN,
+ tdmac->reg_base + TDCR);
+ tdmac->status = DMA_PAUSED;
+}
+
+static int mmp_tdma_config_chan(struct mmp_tdma_chan *tdmac)
+{
+ struct mmp_tdma_data *tdma_data = tdmac->chan.private;
+ unsigned int tdcr;
+
+ mmp_tdma_disable_chan(tdmac);
+
+ if (tdmac->dir == DMA_MEM_TO_DEV)
+ tdcr = TDCR_DSTDIR_ADDR_HOLD | TDCR_SRCDIR_ADDR_INC;
+ else if (tdmac->dir == DMA_DEV_TO_MEM)
+ tdcr = TDCR_SRCDIR_ADDR_HOLD | TDCR_DSTDIR_ADDR_INC;
+ else if (tdmac->dir == DMA_MEM_TO_MEM)
+ tdcr = TDCR_SRCDIR_ADDR_INC | TDCR_DSTDIR_ADDR_INC;
+ else
+ tdcr = TDCR_SRCDIR_ADDR_HOLD | TDCR_DSTDIR_ADDR_HOLD;
+
+ if (tdmac->type == MMP_AUD_TDMA) {
+ switch (tdmac->burst_sz) {
+ case 4:
+ tdcr |= TDCR_BURSTSZ_4B;
+ break;
+ case 8:
+ tdcr |= TDCR_BURSTSZ_8B;
+ break;
+ case 16:
+ tdcr |= TDCR_BURSTSZ_16B;
+ break;
+ case 32:
+ tdcr |= TDCR_BURSTSZ_32B;
+ break;
+ case 64:
+ tdcr |= TDCR_BURSTSZ_64B;
+ break;
+ case 128:
+ tdcr |= TDCR_BURSTSZ_128B;
+ break;
+ default:
+ dev_err(tdmac->dev, "mmp_tdma: unknown burst size.\n");
+ return -EINVAL;
+ }
+
+ if (tdma_data->pack_mod)
+ tdcr |= TDCR_PACKMOD;
+
+ switch (tdma_data->bus_size) {
+ case 8:
+ tdcr |= TDCR_SSZ_8_BITS;
+ break;
+ case 12:
+ tdcr |= TDCR_SSZ_12_BITS;
+ break;
+ case 16:
+ tdcr |= TDCR_SSZ_16_BITS;
+ break;
+ case 20:
+ tdcr |= TDCR_SSZ_20_BITS;
+ break;
+ case 24:
+ tdcr |= TDCR_SSZ_24_BITS;
+ break;
+ case 32:
+ tdcr |= TDCR_SSZ_32_BITS;
+ break;
+ default:
+ dev_err(tdmac->dev, "mmp_tdma: unknown bus size.\n");
+ return -EINVAL;
+ }
+ } else if (tdmac->type == PXA910_SQU) {
+ tdcr |= TDCR_BURSTSZ_SQU_32B;
+ tdcr |= TDCR_SSPMOD;
+ }
+
+ writel(tdcr, tdmac->reg_base + TDCR);
+ return 0;
+}
+
+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;
+ }
+ 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);
+
+ 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;
+
+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);
+ 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;
+}
+
+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;
+}
+
+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];
+
+ if (i + 1 == num_periods)
+ desc->nxt_desc = tdmac->desc_arr_phys;
+ else
+ desc->nxt_desc = tdmac->desc_arr_phys +
+ sizeof(*desc) * (i + 1);
+
+ 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);
+}
+
+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");
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 */
--
1.7.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support
2012-06-04 6:37 ` [PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support Zhangfei Gao
@ 2012-06-07 9:22 ` Vinod Koul
2012-06-07 10:39 ` [alsa-devel] " zhangfei gao
0 siblings, 1 reply; 20+ messages in thread
From: Vinod Koul @ 2012-06-07 9:22 UTC (permalink / raw)
To: linux-arm-kernel
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@marvell.com>
> Signed-off-by: Leo Yan <leoy@marvell.com>
> Signed-off-by: Qiao Zhou <zhouqiao@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
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support
2012-06-07 9:22 ` Vinod Koul
@ 2012-06-07 10:39 ` zhangfei gao
2012-06-07 11:30 ` Vinod Koul
0 siblings, 1 reply; 20+ messages in thread
From: zhangfei gao @ 2012-06-07 10:39 UTC (permalink / raw)
To: linux-arm-kernel
On Thu, Jun 7, 2012 at 5:22 PM, Vinod Koul <vinod.koul@linux.intel.com> wrote:
> 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@marvell.com>
>> Signed-off-by: Leo Yan <leoy@marvell.com>
>> Signed-off-by: Qiao Zhou <zhouqiao@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.?
This is local function used to check whether this channel has irq then return 1.
Used by chan_handler.
>> + ? ? }
>> + ? ? 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?
The driver is specifically for audio.
So we make it simple, only support cylic method and no pending list at all.
Only support sync mode.
tx_submit will directly write dma address with single descriptor chain.
.issue_pending will directly start the dma.
When dma started, it will keep process data from/to user.
>> +
>> + ? ? 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.
Thanks for remainder.
>> +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?
Is there case directly call dma_put_channel without DMA_TERMINATE_ALL?
>> + ? ? 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
Got it, my mistake,
confused by
BUG_ON(dma_has_cap(DMA_SLAVE, device->cap_mask) &&
!device->device_control);
>> +
>> +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
Could you give more hints?
>> +}
>> +
>> +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?
We use tdmac->status to prevent calling again during transfering
except the resource is freed.
Usually once cyclic dma is build, only data comming and out, without
rebuild dma chain.
Here dma chain is simple, does not support asyn mode.
> 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.
We have limitation for hardware.
SRAM has to be used for both dma buffer and dma descriptor.
While SRAM is very very limited on some platform,
as a result link node may not able to be allocated.
So we simply organize descriptor physically one by one.
>> +
>> +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.
Since the SRAM is limited, I am afraid it can not allocate memory for queue.
And to make it simple, we just arrange descriptor one by one.
>> +
>> +module_platform_driver(mmp_tdma_driver);
>> +
>> +MODULE_DESCRIPTION("MMP Two-Channel DMA Driver");
>> +MODULE_LICENSE("GPL");
> AUTHOR, ALIAS too pls
Thanks, got it.
>
>> 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?
The structure is sharing private config between dma driver and pcm
driver under sound.
The header file will be expanded for other dma driver.
>
>
> --
> ~Vinod
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support
2012-06-07 10:39 ` [alsa-devel] " zhangfei gao
@ 2012-06-07 11:30 ` Vinod Koul
2012-06-07 13:33 ` zhangfei gao
0 siblings, 1 reply; 20+ messages in thread
From: Vinod Koul @ 2012-06-07 11:30 UTC (permalink / raw)
To: linux-arm-kernel
On Thu, 2012-06-07 at 18:39 +0800, zhangfei gao wrote:
> >> +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.?
> This is local function used to check whether this channel has irq then return 1.
> Used by chan_handler.
quite linux unlike.
> >> +
> >> +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?
>
> The driver is specifically for audio.
> So we make it simple, only support cylic method and no pending list at all.
> Only support sync mode.
> tx_submit will directly write dma address with single descriptor chain.
> .issue_pending will directly start the dma.
> When dma started, it will keep process data from/to user.
But then you are putting unnecessary limitation on its use.
Limiation should be based on h/w caps
>
> >> +
> >> + 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.
> Thanks for remainder.
>
>
> >> +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?
>
> Is there case directly call dma_put_channel without DMA_TERMINATE_ALL?
in cyclic no, otherwise yes.
>
> >> + 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
>
> Got it, my mistake,
> confused by
> BUG_ON(dma_has_cap(DMA_SLAVE, device->cap_mask) &&
> !device->device_control);
>
> >> +
> >> +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
> Could you give more hints?
return the descriptors allocated
>
> >> +}
> >> +
> >> +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?
>
> We use tdmac->status to prevent calling again during transfering
> except the resource is freed.
> Usually once cyclic dma is build, only data comming and out, without
> rebuild dma chain.
> Here dma chain is simple, does not support asyn mode.
>
> > 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.
>
> We have limitation for hardware.
> SRAM has to be used for both dma buffer and dma descriptor.
> While SRAM is very very limited on some platform,
> as a result link node may not able to be allocated.
> So we simply organize descriptor physically one by one.
You can embed your h/w descriptor in your list structure, managing them
becomes easy.
>
>
> >> +
> >> +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.
>
> Since the SRAM is limited, I am afraid it can not allocate memory for queue.
> And to make it simple, we just arrange descriptor one by one.
>
>
> >> +
> >> +module_platform_driver(mmp_tdma_driver);
> >> +
> >> +MODULE_DESCRIPTION("MMP Two-Channel DMA Driver");
> >> +MODULE_LICENSE("GPL");
> > AUTHOR, ALIAS too pls
> Thanks, got it.
>
> >
> >> 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?
>
> The structure is sharing private config between dma driver and pcm
> driver under sound.
> The header file will be expanded for other dma driver.
Other?
--
~Vinod
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support
2012-06-07 11:30 ` Vinod Koul
@ 2012-06-07 13:33 ` zhangfei gao
0 siblings, 0 replies; 20+ messages in thread
From: zhangfei gao @ 2012-06-07 13:33 UTC (permalink / raw)
To: linux-arm-kernel
On Thu, Jun 7, 2012 at 7:30 PM, Vinod Koul <vinod.koul@linux.intel.com> wrote:
> On Thu, 2012-06-07 at 18:39 +0800, zhangfei gao wrote:
>> >> +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.?
>> This is local function used to check whether this channel has irq then return 1.
>> Used by chan_handler.
> quite linux unlike.
>
>> >> +
>> >> +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?
>>
>> The driver is specifically for audio.
>> So we make it simple, only support cylic method and no pending list at all.
>> Only support sync mode.
>> tx_submit will directly write dma address with single descriptor chain.
>> .issue_pending will directly start the dma.
>> When dma started, it will keep process data from/to user.
> But then you are putting unnecessary limitation on its use.
> Limiation should be based on h/w caps
>>
>> >> +
>> >> + ? ? 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.
>> Thanks for remainder.
>>
>>
>> >> +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?
>>
>> Is there case directly call dma_put_channel without DMA_TERMINATE_ALL?
> in cyclic no, otherwise yes.
>>
>> >> + ? ? 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
>>
>> Got it, my mistake,
>> confused by
>> BUG_ON(dma_has_cap(DMA_SLAVE, device->cap_mask) &&
>> ? ? ? ? ? ? ? ? !device->device_control);
>>
>> >> +
>> >> +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
>> Could you give more hints?
> return the descriptors allocated
>>
>> >> +}
>> >> +
>> >> +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?
>>
>> We use ?tdmac->status to prevent calling again during transfering
>> except the resource is freed.
>> Usually once cyclic dma is build, only data comming and out, without
>> rebuild dma chain.
>> Here dma chain is simple, does not support asyn mode.
>>
>> > 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.
>>
>> We have limitation for hardware.
>> SRAM has to be used for both dma buffer and dma descriptor.
>> While SRAM is very very limited on some platform,
>> as a result link node may not able to be allocated.
>> So we simply organize descriptor physically one by one.
> You can embed your h/w descriptor in your list structure, managing them
> becomes easy.
Thanks Vinod.
Our plan is keep this audio dma driver as simple as possible
Only support cyclic & sync mode,
And dma descriptor chain only build once, have no chance to re-organize.
Then cyclic dma keep running and consuming/generating data.
SRAM have to be used, which is limited, but SRAM can work at low power mode.
So maybe it is easier to organize statically.
We still have peripheral dma working on ddr, with different register
and free usage.
It support async mode and have to use pending list, etc.
>>
>>
>> >> +
>> >> +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.
>>
>> Since the SRAM is limited, I am afraid it can not allocate memory for queue.
>> And to make it simple, we just arrange descriptor one by one.
>>
>>
>> >> +
>> >> +module_platform_driver(mmp_tdma_driver);
>> >> +
>> >> +MODULE_DESCRIPTION("MMP Two-Channel DMA Driver");
>> >> +MODULE_LICENSE("GPL");
>> > AUTHOR, ALIAS too pls
>> Thanks, got it.
>>
>> >
>> >> 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?
>>
>> The structure is sharing private config between dma driver and pcm
>> driver under sound.
>> The header file will be expanded for other dma driver.
> Other?
>
> --
> ~Vinod
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v1 2/5] ASoC: mmp: add audio dma support
2012-06-04 6:37 [PATCH v1 0/5] mmp audio support Zhangfei Gao
2012-06-04 6:37 ` [PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support Zhangfei Gao
@ 2012-06-04 6:37 ` Zhangfei Gao
2012-06-07 9:24 ` Vinod Koul
2012-06-04 6:37 ` [PATCH v1 3/5] ASOC: mmp: add sspa support Zhangfei Gao
` (2 subsequent siblings)
4 siblings, 1 reply; 20+ messages in thread
From: Zhangfei Gao @ 2012-06-04 6:37 UTC (permalink / raw)
To: linux-arm-kernel
mmp-pcm handle audio dma based on soc-dmaengine
Support mmp and pxa910
Signed-off-by: Zhangfei Gao <zhangfei.gao@marvell.com>
Signed-off-by: Leo Yan <leoy@marvell.com>
Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
---
include/linux/platform_data/mmp_audio.h | 22 ++
sound/soc/pxa/Kconfig | 9 +
sound/soc/pxa/Makefile | 2 +
sound/soc/pxa/mmp-pcm.c | 317 +++++++++++++++++++++++++++++++
4 files changed, 350 insertions(+), 0 deletions(-)
create mode 100644 include/linux/platform_data/mmp_audio.h
create mode 100644 sound/soc/pxa/mmp-pcm.c
diff --git a/include/linux/platform_data/mmp_audio.h b/include/linux/platform_data/mmp_audio.h
new file mode 100644
index 0000000..0f25d16
--- /dev/null
+++ b/include/linux/platform_data/mmp_audio.h
@@ -0,0 +1,22 @@
+/*
+ * MMP Platform AUDIO 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_AUDIO_H
+#define MMP_AUDIO_H
+
+struct mmp_audio_platdata {
+ u32 period_max_capture;
+ u32 buffer_max_capture;
+ u32 period_max_playback;
+ u32 buffer_max_playback;
+};
+
+#endif /* MMP_AUDIO_H */
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index a0f7d3c..5d76e29 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -8,6 +8,15 @@ config SND_PXA2XX_SOC
the PXA2xx AC97, I2S or SSP interface. You will also need
to select the audio interfaces to support below.
+config SND_MMP_SOC
+ bool "Soc Audio for Marvell MMP chips"
+ depends on ARCH_MMP
+ select SND_SOC_DMAENGINE_PCM
+ select SND_ARM
+ help
+ Say Y if you want to add support for codecs attached to
+ the MMP SSPA interface.
+
config SND_PXA2XX_AC97
tristate
select SND_AC97_CODEC
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
index af35762..f913e9b 100644
--- a/sound/soc/pxa/Makefile
+++ b/sound/soc/pxa/Makefile
@@ -3,11 +3,13 @@ snd-soc-pxa2xx-objs := pxa2xx-pcm.o
snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o
snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o
snd-soc-pxa-ssp-objs := pxa-ssp.o
+snd-soc-mmp-objs := mmp-pcm.o
obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o
obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o
obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o
obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o
+obj-$(CONFIG_SND_MMP_SOC) += snd-soc-mmp.o
# PXA Machine Support
snd-soc-corgi-objs := corgi.o
diff --git a/sound/soc/pxa/mmp-pcm.c b/sound/soc/pxa/mmp-pcm.c
new file mode 100644
index 0000000..b7b09f9
--- /dev/null
+++ b/sound/soc/pxa/mmp-pcm.c
@@ -0,0 +1,317 @@
+/*
+ * linux/sound/soc/pxa/mmp-pcm.c
+ *
+ * Copyright (C) 2011 Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/platform_data/mmp_dma.h>
+#include <linux/platform_data/mmp_audio.h>
+#include <sound/pxa2xx-lib.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/sram.h>
+#include <sound/dmaengine_pcm.h>
+
+struct mmp_dma_data {
+ int ssp_id;
+ struct resource *dma_res;
+ struct mmp_tdma_data tdma_data;
+};
+
+#define MMP_PCM_INFO (SNDRV_PCM_INFO_MMAP | \
+ SNDRV_PCM_INFO_MMAP_VALID | \
+ SNDRV_PCM_INFO_INTERLEAVED | \
+ SNDRV_PCM_INFO_PAUSE | \
+ SNDRV_PCM_INFO_RESUME)
+
+#define MMP_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_pcm_hardware mmp_pcm_hardware[] = {
+ {
+ .info = MMP_PCM_INFO,
+ .formats = MMP_PCM_FORMATS,
+ .period_bytes_min = 1024,
+ .period_bytes_max = 2048,
+ .periods_min = 2,
+ .periods_max = 32,
+ .buffer_bytes_max = 4096,
+ .fifo_size = 32,
+ },
+ {
+ .info = MMP_PCM_INFO,
+ .formats = MMP_PCM_FORMATS,
+ .period_bytes_min = 1024,
+ .period_bytes_max = 2048,
+ .periods_min = 2,
+ .periods_max = 32,
+ .buffer_bytes_max = 4096,
+ .fifo_size = 32,
+ },
+};
+
+static int mmp_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct mmp_dma_data *dma_data = snd_dmaengine_pcm_get_data(substream);
+ struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct pxa2xx_pcm_dma_params *dma_params;
+ int ret;
+ struct dma_slave_config slave_config;
+
+ dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ if (!dma_params)
+ return 0;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ dma_data->tdma_data.bus_size = 8;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dma_data->tdma_data.bus_size = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ dma_data->tdma_data.bus_size = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ dma_data->tdma_data.bus_size = 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ dma_data->tdma_data.pack_mod = true;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ slave_config.direction = DMA_TO_DEVICE;
+ slave_config.dst_addr = dma_params->dev_addr;
+ slave_config.dst_maxburst = 4;
+ } else {
+ slave_config.direction = DMA_FROM_DEVICE;
+ slave_config.src_addr = dma_params->dev_addr;
+ slave_config.src_maxburst = 4;
+ }
+
+ ret = dmaengine_slave_config(chan, &slave_config);
+ if (ret)
+ return ret;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+ struct mmp_dma_data *dma_data = param;
+ bool found = false;
+ char *devname;
+
+ devname = kasprintf(GFP_KERNEL, "%s.%d", dma_data->dma_res->name,
+ dma_data->ssp_id);
+ if ((strcmp(dev_name(chan->device->dev), devname) == 0) &&
+ (chan->chan_id == dma_data->dma_res->start)) {
+ chan->private = &dma_data->tdma_data;
+ found = true;
+ }
+
+ kfree(devname);
+ return found;
+}
+
+static int mmp_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct platform_device *pdev = to_platform_device(rtd->platform->dev);
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct mmp_dma_data *dma_data;
+ struct resource *r;
+ int ret;
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, substream->stream);
+ if (!r)
+ return -EBUSY;
+
+ snd_soc_set_runtime_hwparams(substream,
+ &mmp_pcm_hardware[substream->stream]);
+ dma_data = devm_kzalloc(&pdev->dev,
+ sizeof(struct mmp_dma_data), GFP_KERNEL);
+ if (dma_data == NULL)
+ return -ENOMEM;
+
+ dma_data->dma_res = r;
+ dma_data->ssp_id = cpu_dai->id;
+
+ ret = snd_dmaengine_pcm_open(substream, filter, dma_data);
+ if (ret) {
+ devm_kfree(&pdev->dev, dma_data);
+ return ret;
+ }
+
+ snd_dmaengine_pcm_set_data(substream, dma_data);
+ return 0;
+}
+
+static int mmp_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct mmp_dma_data *dma_data = snd_dmaengine_pcm_get_data(substream);
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct platform_device *pdev = to_platform_device(rtd->platform->dev);
+
+ snd_dmaengine_pcm_close(substream);
+ devm_kfree(&pdev->dev, dma_data);
+ return 0;
+}
+
+static int mmp_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long off = vma->vm_pgoff;
+
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ return remap_pfn_range(vma, vma->vm_start,
+ __phys_to_pfn(runtime->dma_addr) + off,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+struct snd_pcm_ops mmp_pcm_ops = {
+ .open = mmp_pcm_open,
+ .close = mmp_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = mmp_pcm_hw_params,
+ .trigger = snd_dmaengine_pcm_trigger,
+ .pointer = snd_dmaengine_pcm_pointer,
+ .mmap = mmp_pcm_mmap,
+};
+
+static void mmp_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+ struct gen_pool *gpool;
+
+ gpool = sram_get_gpool("asram");
+ if (!gpool)
+ return;
+
+ for (stream = 0; stream < 2; stream++) {
+ size_t size = mmp_pcm_hardware[stream].buffer_bytes_max;
+
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+ gen_pool_free(gpool, (unsigned long)buf->area, size);
+ buf->area = NULL;
+ }
+
+ return;
+}
+
+static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream,
+ int stream)
+{
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = mmp_pcm_hardware[stream].buffer_bytes_max;
+ struct gen_pool *gpool;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = substream->pcm->card->dev;
+ buf->private_data = NULL;
+
+ gpool = sram_get_gpool("asram");
+ if (!gpool)
+ return -ENOMEM;
+
+ buf->area = (unsigned char *)gen_pool_alloc(gpool, size);
+ if (!buf->area)
+ return -ENOMEM;
+ buf->addr = gen_pool_virt_to_phys(gpool, (unsigned long)buf->area);
+ buf->bytes = size;
+ return 0;
+}
+
+int mmp_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm *pcm = rtd->pcm;
+ int ret = 0, stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+
+ ret = mmp_pcm_preallocate_dma_buffer(substream, stream);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ mmp_pcm_free_dma_buffers(pcm);
+ return ret;
+}
+
+struct snd_soc_platform_driver mmp_soc_platform = {
+ .ops = &mmp_pcm_ops,
+ .pcm_new = mmp_pcm_new,
+ .pcm_free = mmp_pcm_free_dma_buffers,
+};
+
+static __devinit int mmp_pcm_probe(struct platform_device *pdev)
+{
+ struct mmp_audio_platdata *pdata = pdev->dev.platform_data;
+
+ if (pdata) {
+ mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].buffer_bytes_max =
+ pdata->buffer_max_playback;
+ mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].period_bytes_max =
+ pdata->period_max_playback;
+ mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].buffer_bytes_max =
+ pdata->buffer_max_capture;
+ mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].period_bytes_max =
+ pdata->period_max_capture;
+ }
+ return snd_soc_register_platform(&pdev->dev, &mmp_soc_platform);
+}
+
+static int __devexit mmp_pcm_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver mmp_pcm_driver = {
+ .driver = {
+ .name = "mmp-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = mmp_pcm_probe,
+ .remove = __devexit_p(mmp_pcm_remove),
+};
+
+module_platform_driver(mmp_pcm_driver);
+
+MODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
+MODULE_DESCRIPTION("MMP Soc Audio DMA module");
+MODULE_LICENSE("GPL");
--
1.7.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v1 2/5] ASoC: mmp: add audio dma support
2012-06-04 6:37 ` [PATCH v1 2/5] ASoC: mmp: add audio dma support Zhangfei Gao
@ 2012-06-07 9:24 ` Vinod Koul
2012-06-07 9:59 ` [alsa-devel] " zhangfei gao
0 siblings, 1 reply; 20+ messages in thread
From: Vinod Koul @ 2012-06-07 9:24 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, 2012-06-04 at 14:37 +0800, Zhangfei Gao wrote:
> mmp-pcm handle audio dma based on soc-dmaengine
> Support mmp and pxa910
See soc-dmaengine.c. You sneed to use the library to use dmaengine.
>
> Signed-off-by: Zhangfei Gao <zhangfei.gao@marvell.com>
> Signed-off-by: Leo Yan <leoy@marvell.com>
> Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
> ---
> include/linux/platform_data/mmp_audio.h | 22 ++
> sound/soc/pxa/Kconfig | 9 +
> sound/soc/pxa/Makefile | 2 +
> sound/soc/pxa/mmp-pcm.c | 317 +++++++++++++++++++++++++++++++
> 4 files changed, 350 insertions(+), 0 deletions(-)
> create mode 100644 include/linux/platform_data/mmp_audio.h
> create mode 100644 sound/soc/pxa/mmp-pcm.c
>
> diff --git a/include/linux/platform_data/mmp_audio.h b/include/linux/platform_data/mmp_audio.h
> new file mode 100644
> index 0000000..0f25d16
> --- /dev/null
> +++ b/include/linux/platform_data/mmp_audio.h
> @@ -0,0 +1,22 @@
> +/*
> + * MMP Platform AUDIO 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_AUDIO_H
> +#define MMP_AUDIO_H
> +
> +struct mmp_audio_platdata {
> + u32 period_max_capture;
> + u32 buffer_max_capture;
> + u32 period_max_playback;
> + u32 buffer_max_playback;
> +};
> +
> +#endif /* MMP_AUDIO_H */
> diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
> index a0f7d3c..5d76e29 100644
> --- a/sound/soc/pxa/Kconfig
> +++ b/sound/soc/pxa/Kconfig
> @@ -8,6 +8,15 @@ config SND_PXA2XX_SOC
> the PXA2xx AC97, I2S or SSP interface. You will also need
> to select the audio interfaces to support below.
>
> +config SND_MMP_SOC
> + bool "Soc Audio for Marvell MMP chips"
> + depends on ARCH_MMP
> + select SND_SOC_DMAENGINE_PCM
> + select SND_ARM
> + help
> + Say Y if you want to add support for codecs attached to
> + the MMP SSPA interface.
> +
> config SND_PXA2XX_AC97
> tristate
> select SND_AC97_CODEC
> diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
> index af35762..f913e9b 100644
> --- a/sound/soc/pxa/Makefile
> +++ b/sound/soc/pxa/Makefile
> @@ -3,11 +3,13 @@ snd-soc-pxa2xx-objs := pxa2xx-pcm.o
> snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o
> snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o
> snd-soc-pxa-ssp-objs := pxa-ssp.o
> +snd-soc-mmp-objs := mmp-pcm.o
>
> obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o
> obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o
> obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o
> obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o
> +obj-$(CONFIG_SND_MMP_SOC) += snd-soc-mmp.o
>
> # PXA Machine Support
> snd-soc-corgi-objs := corgi.o
> diff --git a/sound/soc/pxa/mmp-pcm.c b/sound/soc/pxa/mmp-pcm.c
> new file mode 100644
> index 0000000..b7b09f9
> --- /dev/null
> +++ b/sound/soc/pxa/mmp-pcm.c
> @@ -0,0 +1,317 @@
> +/*
> + * linux/sound/soc/pxa/mmp-pcm.c
> + *
> + * Copyright (C) 2011 Marvell International Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + */
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/dmaengine.h>
> +#include <linux/platform_data/mmp_dma.h>
> +#include <linux/platform_data/mmp_audio.h>
> +#include <sound/pxa2xx-lib.h>
> +#include <sound/core.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +#include <sound/soc.h>
> +#include <mach/sram.h>
> +#include <sound/dmaengine_pcm.h>
> +
> +struct mmp_dma_data {
> + int ssp_id;
> + struct resource *dma_res;
> + struct mmp_tdma_data tdma_data;
> +};
> +
> +#define MMP_PCM_INFO (SNDRV_PCM_INFO_MMAP | \
> + SNDRV_PCM_INFO_MMAP_VALID | \
> + SNDRV_PCM_INFO_INTERLEAVED | \
> + SNDRV_PCM_INFO_PAUSE | \
> + SNDRV_PCM_INFO_RESUME)
> +
> +#define MMP_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
> + SNDRV_PCM_FMTBIT_S24_LE | \
> + SNDRV_PCM_FMTBIT_S32_LE)
> +
> +static struct snd_pcm_hardware mmp_pcm_hardware[] = {
> + {
> + .info = MMP_PCM_INFO,
> + .formats = MMP_PCM_FORMATS,
> + .period_bytes_min = 1024,
> + .period_bytes_max = 2048,
> + .periods_min = 2,
> + .periods_max = 32,
> + .buffer_bytes_max = 4096,
> + .fifo_size = 32,
> + },
> + {
> + .info = MMP_PCM_INFO,
> + .formats = MMP_PCM_FORMATS,
> + .period_bytes_min = 1024,
> + .period_bytes_max = 2048,
> + .periods_min = 2,
> + .periods_max = 32,
> + .buffer_bytes_max = 4096,
> + .fifo_size = 32,
> + },
> +};
> +
> +static int mmp_pcm_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params)
> +{
> + struct mmp_dma_data *dma_data = snd_dmaengine_pcm_get_data(substream);
> + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct pxa2xx_pcm_dma_params *dma_params;
> + int ret;
> + struct dma_slave_config slave_config;
> +
> + dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
> + if (!dma_params)
> + return 0;
> +
> + switch (params_format(params)) {
> + case SNDRV_PCM_FORMAT_S8:
> + dma_data->tdma_data.bus_size = 8;
> + break;
> + case SNDRV_PCM_FORMAT_S16_LE:
> + dma_data->tdma_data.bus_size = 16;
> + break;
> + case SNDRV_PCM_FORMAT_S24_LE:
> + dma_data->tdma_data.bus_size = 24;
> + break;
> + case SNDRV_PCM_FORMAT_S32_LE:
> + dma_data->tdma_data.bus_size = 32;
> + break;
> + default:
> + return -EINVAL;
> + }
> + dma_data->tdma_data.pack_mod = true;
> +
> + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> + slave_config.direction = DMA_TO_DEVICE;
> + slave_config.dst_addr = dma_params->dev_addr;
> + slave_config.dst_maxburst = 4;
> + } else {
> + slave_config.direction = DMA_FROM_DEVICE;
> + slave_config.src_addr = dma_params->dev_addr;
> + slave_config.src_maxburst = 4;
> + }
> +
> + ret = dmaengine_slave_config(chan, &slave_config);
> + if (ret)
> + return ret;
> +
> + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
> +
> + return 0;
> +}
> +
> +static bool filter(struct dma_chan *chan, void *param)
> +{
> + struct mmp_dma_data *dma_data = param;
> + bool found = false;
> + char *devname;
> +
> + devname = kasprintf(GFP_KERNEL, "%s.%d", dma_data->dma_res->name,
> + dma_data->ssp_id);
> + if ((strcmp(dev_name(chan->device->dev), devname) == 0) &&
> + (chan->chan_id == dma_data->dma_res->start)) {
> + chan->private = &dma_data->tdma_data;
> + found = true;
> + }
> +
> + kfree(devname);
> + return found;
> +}
> +
> +static int mmp_pcm_open(struct snd_pcm_substream *substream)
> +{
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct platform_device *pdev = to_platform_device(rtd->platform->dev);
> + struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
> + struct mmp_dma_data *dma_data;
> + struct resource *r;
> + int ret;
> +
> + r = platform_get_resource(pdev, IORESOURCE_DMA, substream->stream);
> + if (!r)
> + return -EBUSY;
> +
> + snd_soc_set_runtime_hwparams(substream,
> + &mmp_pcm_hardware[substream->stream]);
> + dma_data = devm_kzalloc(&pdev->dev,
> + sizeof(struct mmp_dma_data), GFP_KERNEL);
> + if (dma_data == NULL)
> + return -ENOMEM;
> +
> + dma_data->dma_res = r;
> + dma_data->ssp_id = cpu_dai->id;
> +
> + ret = snd_dmaengine_pcm_open(substream, filter, dma_data);
> + if (ret) {
> + devm_kfree(&pdev->dev, dma_data);
> + return ret;
> + }
> +
> + snd_dmaengine_pcm_set_data(substream, dma_data);
> + return 0;
> +}
> +
> +static int mmp_pcm_close(struct snd_pcm_substream *substream)
> +{
> + struct mmp_dma_data *dma_data = snd_dmaengine_pcm_get_data(substream);
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct platform_device *pdev = to_platform_device(rtd->platform->dev);
> +
> + snd_dmaengine_pcm_close(substream);
> + devm_kfree(&pdev->dev, dma_data);
> + return 0;
> +}
> +
> +static int mmp_pcm_mmap(struct snd_pcm_substream *substream,
> + struct vm_area_struct *vma)
> +{
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + unsigned long off = vma->vm_pgoff;
> +
> + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> + return remap_pfn_range(vma, vma->vm_start,
> + __phys_to_pfn(runtime->dma_addr) + off,
> + vma->vm_end - vma->vm_start, vma->vm_page_prot);
> +}
> +
> +struct snd_pcm_ops mmp_pcm_ops = {
> + .open = mmp_pcm_open,
> + .close = mmp_pcm_close,
> + .ioctl = snd_pcm_lib_ioctl,
> + .hw_params = mmp_pcm_hw_params,
> + .trigger = snd_dmaengine_pcm_trigger,
> + .pointer = snd_dmaengine_pcm_pointer,
> + .mmap = mmp_pcm_mmap,
> +};
> +
> +static void mmp_pcm_free_dma_buffers(struct snd_pcm *pcm)
> +{
> + struct snd_pcm_substream *substream;
> + struct snd_dma_buffer *buf;
> + int stream;
> + struct gen_pool *gpool;
> +
> + gpool = sram_get_gpool("asram");
> + if (!gpool)
> + return;
> +
> + for (stream = 0; stream < 2; stream++) {
> + size_t size = mmp_pcm_hardware[stream].buffer_bytes_max;
> +
> + substream = pcm->streams[stream].substream;
> + if (!substream)
> + continue;
> +
> + buf = &substream->dma_buffer;
> + if (!buf->area)
> + continue;
> + gen_pool_free(gpool, (unsigned long)buf->area, size);
> + buf->area = NULL;
> + }
> +
> + return;
> +}
> +
> +static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream,
> + int stream)
> +{
> + struct snd_dma_buffer *buf = &substream->dma_buffer;
> + size_t size = mmp_pcm_hardware[stream].buffer_bytes_max;
> + struct gen_pool *gpool;
> +
> + buf->dev.type = SNDRV_DMA_TYPE_DEV;
> + buf->dev.dev = substream->pcm->card->dev;
> + buf->private_data = NULL;
> +
> + gpool = sram_get_gpool("asram");
> + if (!gpool)
> + return -ENOMEM;
> +
> + buf->area = (unsigned char *)gen_pool_alloc(gpool, size);
> + if (!buf->area)
> + return -ENOMEM;
> + buf->addr = gen_pool_virt_to_phys(gpool, (unsigned long)buf->area);
> + buf->bytes = size;
> + return 0;
> +}
> +
> +int mmp_pcm_new(struct snd_soc_pcm_runtime *rtd)
> +{
> + struct snd_pcm_substream *substream;
> + struct snd_pcm *pcm = rtd->pcm;
> + int ret = 0, stream;
> +
> + for (stream = 0; stream < 2; stream++) {
> + substream = pcm->streams[stream].substream;
> +
> + ret = mmp_pcm_preallocate_dma_buffer(substream, stream);
> + if (ret)
> + goto err;
> + }
> +
> + return 0;
> +
> +err:
> + mmp_pcm_free_dma_buffers(pcm);
> + return ret;
> +}
> +
> +struct snd_soc_platform_driver mmp_soc_platform = {
> + .ops = &mmp_pcm_ops,
> + .pcm_new = mmp_pcm_new,
> + .pcm_free = mmp_pcm_free_dma_buffers,
> +};
> +
> +static __devinit int mmp_pcm_probe(struct platform_device *pdev)
> +{
> + struct mmp_audio_platdata *pdata = pdev->dev.platform_data;
> +
> + if (pdata) {
> + mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].buffer_bytes_max =
> + pdata->buffer_max_playback;
> + mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].period_bytes_max =
> + pdata->period_max_playback;
> + mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].buffer_bytes_max =
> + pdata->buffer_max_capture;
> + mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].period_bytes_max =
> + pdata->period_max_capture;
> + }
> + return snd_soc_register_platform(&pdev->dev, &mmp_soc_platform);
> +}
> +
> +static int __devexit mmp_pcm_remove(struct platform_device *pdev)
> +{
> + snd_soc_unregister_platform(&pdev->dev);
> + return 0;
> +}
> +
> +static struct platform_driver mmp_pcm_driver = {
> + .driver = {
> + .name = "mmp-pcm-audio",
> + .owner = THIS_MODULE,
> + },
> +
> + .probe = mmp_pcm_probe,
> + .remove = __devexit_p(mmp_pcm_remove),
> +};
> +
> +module_platform_driver(mmp_pcm_driver);
> +
> +MODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
> +MODULE_DESCRIPTION("MMP Soc Audio DMA module");
> +MODULE_LICENSE("GPL");
--
~Vinod
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 2/5] ASoC: mmp: add audio dma support
2012-06-07 9:24 ` Vinod Koul
@ 2012-06-07 9:59 ` zhangfei gao
2012-06-07 10:05 ` Vinod Koul
0 siblings, 1 reply; 20+ messages in thread
From: zhangfei gao @ 2012-06-07 9:59 UTC (permalink / raw)
To: linux-arm-kernel
On Thu, Jun 7, 2012 at 5:24 PM, Vinod Koul <vinod.koul@linux.intel.com> wrote:
> On Mon, 2012-06-04 at 14:37 +0800, Zhangfei Gao wrote:
>> mmp-pcm handle audio dma based on soc-dmaengine
>> Support mmp and pxa910
> See soc-dmaengine.c. You sneed to use the library to use dmaengine.
>
Thanks Vinod for kind review.
Some confusion, only find sound/soc/soc-dmaengine-pcm.c.
So the driver base on the library, snd_dmaengine_pcm_open/close etc.
Replacing dma_request_channel etc.
Is it right direction?
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 2/5] ASoC: mmp: add audio dma support
2012-06-07 9:59 ` [alsa-devel] " zhangfei gao
@ 2012-06-07 10:05 ` Vinod Koul
2012-06-07 10:44 ` zhangfei gao
0 siblings, 1 reply; 20+ messages in thread
From: Vinod Koul @ 2012-06-07 10:05 UTC (permalink / raw)
To: linux-arm-kernel
On Thu, 2012-06-07 at 17:59 +0800, zhangfei gao wrote:
> On Thu, Jun 7, 2012 at 5:24 PM, Vinod Koul <vinod.koul@linux.intel.com> wrote:
> > On Mon, 2012-06-04 at 14:37 +0800, Zhangfei Gao wrote:
> >> mmp-pcm handle audio dma based on soc-dmaengine
> >> Support mmp and pxa910
> > See soc-dmaengine.c. You sneed to use the library to use dmaengine.
> >
>
> Thanks Vinod for kind review.
>
> Some confusion, only find sound/soc/soc-dmaengine-pcm.c.
> So the driver base on the library, snd_dmaengine_pcm_open/close etc.
> Replacing dma_request_channel etc.
>
> Is it right direction?
Yes your driver should use it. See examples in other soc drivers
--
~Vinod
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 2/5] ASoC: mmp: add audio dma support
2012-06-07 10:05 ` Vinod Koul
@ 2012-06-07 10:44 ` zhangfei gao
2012-06-07 11:20 ` Vinod Koul
0 siblings, 1 reply; 20+ messages in thread
From: zhangfei gao @ 2012-06-07 10:44 UTC (permalink / raw)
To: linux-arm-kernel
On Thu, Jun 7, 2012 at 6:05 PM, Vinod Koul <vinod.koul@linux.intel.com> wrote:
> On Thu, 2012-06-07 at 17:59 +0800, zhangfei gao wrote:
>> On Thu, Jun 7, 2012 at 5:24 PM, Vinod Koul <vinod.koul@linux.intel.com> wrote:
>> > On Mon, 2012-06-04 at 14:37 +0800, Zhangfei Gao wrote:
>> >> mmp-pcm handle audio dma based on soc-dmaengine
>> >> Support mmp and pxa910
>> > See soc-dmaengine.c. You sneed to use the library to use dmaengine.
>> >
>>
>> Thanks Vinod for kind review.
>>
>> Some confusion, only find sound/soc/soc-dmaengine-pcm.c.
>> So the driver base on the library, snd_dmaengine_pcm_open/close etc.
>> Replacing dma_request_channel etc.
>>
>> Is it right direction?
> Yes your driver should use it. See examples in other soc drivers
This driver already been ported to soc-dmaengine-pcm.c, refer
mxs-pcm.c and imx-pcm-dma-mx2.c.
Using snd_dmaengine_pcm_open, snd_dmaengine_pcm_close, etc.
Only exception is dmaengine_slave_config, since we have some specific config.
>
>
> --
> ~Vinod
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 2/5] ASoC: mmp: add audio dma support
2012-06-07 10:44 ` zhangfei gao
@ 2012-06-07 11:20 ` Vinod Koul
2012-06-07 13:17 ` zhangfei gao
0 siblings, 1 reply; 20+ messages in thread
From: Vinod Koul @ 2012-06-07 11:20 UTC (permalink / raw)
To: linux-arm-kernel
On Thu, 2012-06-07 at 18:44 +0800, zhangfei gao wrote:
> Only exception is dmaengine_slave_config, since we have some specific config.
And why?
> +static bool filter(struct dma_chan *chan, void *param)
> +{
> + struct mmp_dma_data *dma_data = param;
> + bool found = false;
> + char *devname;
> +
> + devname = kasprintf(GFP_KERNEL, "%s.%d", dma_data->dma_res->name,
> + dma_data->ssp_id);
> + if ((strcmp(dev_name(chan->device->dev), devname) == 0) &&
> + (chan->chan_id == dma_data->dma_res->start)) {
> + chan->private = &dma_data->tdma_data;
chan->private is deprecated and should not be used
> + found = true;
> + }
> +
> + kfree(devname);
> + return found;
> +}
--
~Vinod
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 2/5] ASoC: mmp: add audio dma support
2012-06-07 11:20 ` Vinod Koul
@ 2012-06-07 13:17 ` zhangfei gao
0 siblings, 0 replies; 20+ messages in thread
From: zhangfei gao @ 2012-06-07 13:17 UTC (permalink / raw)
To: linux-arm-kernel
On Thu, Jun 7, 2012 at 7:20 PM, Vinod Koul <vinod.koul@linux.intel.com> wrote:
> On Thu, 2012-06-07 at 18:44 +0800, zhangfei gao wrote:
>> Only exception is dmaengine_slave_config, since we have some specific config.
> And why?
>
>> +static bool filter(struct dma_chan *chan, void *param)
>> +{
>> + ? ? struct mmp_dma_data *dma_data = param;
>> + ? ? bool found = false;
>> + ? ? char *devname;
>> +
>> + ? ? devname = kasprintf(GFP_KERNEL, "%s.%d", dma_data->dma_res->name,
>> + ? ? ? ? ? ? dma_data->ssp_id);
>> + ? ? if ((strcmp(dev_name(chan->device->dev), devname) == 0) &&
>> + ? ? ? ? ? ? (chan->chan_id == dma_data->dma_res->start)) {
>> + ? ? ? ? ? ? chan->private = &dma_data->tdma_data;
> chan->private is deprecated and should not be used
Thanks for the info, will update accordingly.
The concern is DMA_SLAVE_BUSWIDTH_4_BYTES is used for S20, S24, S32.
And our controller handles differently, though they not seen much.
Besides, dmaengine_slave_config still have to be used for src/dst_addr
and src/dst_maxburst.
>> + ? ? ? ? ? ? found = true;
>> + ? ? }
>> +
>> + ? ? kfree(devname);
>> + ? ? return found;
>> +}
>
>
> --
> ~Vinod
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v1 3/5] ASOC: mmp: add sspa support
2012-06-04 6:37 [PATCH v1 0/5] mmp audio support Zhangfei Gao
2012-06-04 6:37 ` [PATCH v1 1/5] dmaengine: mmp_tdma: add mmp tdma support Zhangfei Gao
2012-06-04 6:37 ` [PATCH v1 2/5] ASoC: mmp: add audio dma support Zhangfei Gao
@ 2012-06-04 6:37 ` Zhangfei Gao
2012-06-07 22:10 ` Mark Brown
2012-06-04 6:37 ` [PATCH v1 4/5] ASoC: add mmp brownstone support Zhangfei Gao
2012-06-04 6:37 ` [PATCH v1 5/5] ASoC: add ttc-dkb machine support Zhangfei Gao
4 siblings, 1 reply; 20+ messages in thread
From: Zhangfei Gao @ 2012-06-04 6:37 UTC (permalink / raw)
To: linux-arm-kernel
The SSPA is a configurable multi-channel audio serial (TDM) interface.
It's configurable at runtime to support up to 128 channels and the
number of bits per sample: 8, 12, 16, 20, 24 and 32 bits. It also
support stereo format: I2S, left-justified or right-justified.
Signed-off-by: Zhangfei Gao <zhangfei.gao@marvell.com>
Signed-off-by: Leo Yan <leoy@marvell.com>
---
sound/soc/pxa/Kconfig | 3 +
sound/soc/pxa/Makefile | 2 +
sound/soc/pxa/mmp-sspa.c | 496 ++++++++++++++++++++++++++++++++++++++++++++++
sound/soc/pxa/mmp-sspa.h | 92 +++++++++
4 files changed, 593 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/pxa/mmp-sspa.c
create mode 100644 sound/soc/pxa/mmp-sspa.h
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index 5d76e29..6c3d00b 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -35,6 +35,9 @@ config SND_PXA_SOC_SSP
tristate
select PXA_SSP
+config SND_MMP_SOC_SSPA
+ tristate
+
config SND_PXA2XX_SOC_CORGI
tristate "SoC Audio support for Sharp Zaurus SL-C7x0"
depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
index f913e9b..07b8417 100644
--- a/sound/soc/pxa/Makefile
+++ b/sound/soc/pxa/Makefile
@@ -4,12 +4,14 @@ snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o
snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o
snd-soc-pxa-ssp-objs := pxa-ssp.o
snd-soc-mmp-objs := mmp-pcm.o
+snd-soc-mmp-sspa-objs := mmp-sspa.o
obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o
obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o
obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o
obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o
obj-$(CONFIG_SND_MMP_SOC) += snd-soc-mmp.o
+obj-$(CONFIG_SND_MMP_SOC_SSPA) += snd-soc-mmp-sspa.o
# PXA Machine Support
snd-soc-corgi-objs := corgi.o
diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c
new file mode 100644
index 0000000..d6f5f82
--- /dev/null
+++ b/sound/soc/pxa/mmp-sspa.c
@@ -0,0 +1,496 @@
+/*
+ * linux/sound/soc/pxa/mmp-sspa.c
+ * Base on pxa2xx-ssp.c
+ *
+ * Copyright (C) 2011 Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/pxa2xx_ssp.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+#include "mmp-sspa.h"
+
+/*
+ * SSPA audio private data
+ */
+struct sspa_priv {
+ struct ssp_device *sspa;
+ struct pxa2xx_pcm_dma_params *dma_params;
+ int dai_fmt;
+ int running_cnt;
+};
+
+static struct clk *audio_clk;
+static struct clk *sysclk;
+
+static void mmp_sspa_write_reg(struct ssp_device *sspa, u32 reg, u32 val)
+{
+ __raw_writel(val, sspa->mmio_base + reg);
+}
+
+static u32 mmp_sspa_read_reg(struct ssp_device *sspa, u32 reg)
+{
+ return __raw_readl(sspa->mmio_base + reg);
+}
+
+static void mmp_sspa_tx_enable(struct ssp_device *sspa)
+{
+ unsigned int sspa_sp;
+
+ sspa_sp = mmp_sspa_read_reg(sspa, SSPA_TXSP);
+ sspa_sp |= SSPA_SP_S_EN;
+ sspa_sp |= SSPA_SP_WEN;
+ mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp);
+}
+
+static void mmp_sspa_tx_disable(struct ssp_device *sspa)
+{
+ unsigned int sspa_sp;
+
+ sspa_sp = mmp_sspa_read_reg(sspa, SSPA_TXSP);
+ sspa_sp &= ~SSPA_SP_S_EN;
+ sspa_sp |= SSPA_SP_WEN;
+ mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp);
+}
+
+static void mmp_sspa_rx_enable(struct ssp_device *sspa)
+{
+ unsigned int sspa_sp;
+
+ sspa_sp = mmp_sspa_read_reg(sspa, SSPA_RXSP);
+ sspa_sp |= SSPA_SP_S_EN;
+ sspa_sp |= SSPA_SP_WEN;
+ mmp_sspa_write_reg(sspa, SSPA_RXSP, sspa_sp);
+}
+
+static void mmp_sspa_rx_disable(struct ssp_device *sspa)
+{
+ unsigned int sspa_sp;
+
+ sspa_sp = mmp_sspa_read_reg(sspa, SSPA_RXSP);
+ sspa_sp &= ~SSPA_SP_S_EN;
+ sspa_sp |= SSPA_SP_WEN;
+ mmp_sspa_write_reg(sspa, SSPA_RXSP, sspa_sp);
+}
+
+static int mmp_sspa_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(dai);
+ struct ssp_device *sspa = sspa_priv->sspa;
+
+ clk_enable(sysclk);
+ clk_enable(sspa->clk);
+
+ return 0;
+}
+
+static void mmp_sspa_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(dai);
+ struct ssp_device *sspa = sspa_priv->sspa;
+
+ clk_disable(sspa->clk);
+ clk_disable(sysclk);
+
+ return;
+}
+
+/*
+ * Set the SSP ports SYSCLK.
+ */
+static int mmp_sspa_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ int ret = 0;
+
+ switch (clk_id) {
+ case MMP_SSPA_CLK_AUDIO:
+ ret = clk_set_rate(audio_clk, freq);
+ if (ret)
+ return ret;
+ break;
+ case MMP_SSPA_CLK_PLL:
+ case MMP_SSPA_CLK_VCXO:
+ /* not support yet */
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mmp_sspa_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id,
+ int source, unsigned int freq_in,
+ unsigned int freq_out)
+{
+ struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *sspa = sspa_priv->sspa;
+ int ret = 0;
+
+ switch (pll_id) {
+ case MMP_SYSCLK:
+ ret = clk_set_rate(sysclk, freq_out);
+ if (ret)
+ return ret;
+ break;
+ case MMP_SSPA_CLK:
+ ret = clk_set_rate(sspa->clk, freq_out);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/*
+ * Set up the sspa dai format. The sspa port must be inactive
+ * before calling this function as the physical
+ * interface format is changed.
+ */
+static int mmp_sspa_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *sspa = sspa_priv->sspa;
+ u32 sspa_sp, sspa_ctrl;
+
+ /* check if we need to change anything at all */
+ if (sspa_priv->dai_fmt == fmt)
+ return 0;
+
+ /* we can only change the settings if the port is not in use */
+ if ((mmp_sspa_read_reg(sspa, SSPA_TXSP) & SSPA_SP_S_EN) ||
+ (mmp_sspa_read_reg(sspa, SSPA_RXSP) & SSPA_SP_S_EN)) {
+ dev_err(&sspa->pdev->dev,
+ "can't change hardware dai format: stream is in use\n");
+ return -EINVAL;
+ }
+
+ /* reset port settings */
+ sspa_sp = SSPA_SP_WEN | SSPA_SP_S_RST | SSPA_SP_FFLUSH;
+ sspa_ctrl = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ sspa_sp |= SSPA_SP_MSL;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ sspa_sp |= SSPA_SP_FSP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ sspa_sp |= SSPA_TXSP_FPER(63);
+ sspa_sp |= SSPA_SP_FWID(31);
+ sspa_ctrl |= SSPA_CTL_XDATDLY(1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp);
+ mmp_sspa_write_reg(sspa, SSPA_RXSP, sspa_sp);
+
+ sspa_sp &= ~(SSPA_SP_S_RST | SSPA_SP_FFLUSH);
+ mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp);
+ mmp_sspa_write_reg(sspa, SSPA_RXSP, sspa_sp);
+
+ /*
+ * FIXME: hw issue, for the tx serial port,
+ * can not config the master/slave mode;
+ * so must clean this bit.
+ * The master/slave mode has been set in the
+ * rx port.
+ */
+ sspa_sp &= ~SSPA_SP_MSL;
+ mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp);
+
+ mmp_sspa_write_reg(sspa, SSPA_TXCTL, sspa_ctrl);
+ mmp_sspa_write_reg(sspa, SSPA_RXCTL, sspa_ctrl);
+
+ /* Since we are configuring the timings for the format by hand
+ * we have to defer some things until hw_params() where we
+ * know parameters like the sample size.
+ */
+ sspa_priv->dai_fmt = fmt;
+ return 0;
+}
+
+/*
+ * Set the SSPA audio DMA parameters and sample size.
+ * Can be called multiple times by oss emulation.
+ */
+static int mmp_sspa_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(dai);
+ struct ssp_device *sspa = sspa_priv->sspa;
+ struct pxa2xx_pcm_dma_params *dma_params;
+ u32 sspa_ctrl;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sspa_ctrl = mmp_sspa_read_reg(sspa, SSPA_TXCTL);
+ else
+ sspa_ctrl = mmp_sspa_read_reg(sspa, SSPA_RXCTL);
+
+ sspa_ctrl &= ~SSPA_CTL_XFRLEN1_MASK;
+ sspa_ctrl |= SSPA_CTL_XFRLEN1(params_channels(params) - 1);
+ sspa_ctrl &= ~SSPA_CTL_XWDLEN1_MASK;
+ sspa_ctrl |= SSPA_CTL_XWDLEN1(SSPA_CTL_32_BITS);
+ sspa_ctrl &= ~SSPA_CTL_XSSZ1_MASK;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_8_BITS);
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_16_BITS);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_20_BITS);
+ break;
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_24_BITS);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_32_BITS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ mmp_sspa_write_reg(sspa, SSPA_TXCTL, sspa_ctrl);
+ mmp_sspa_write_reg(sspa, SSPA_TXFIFO_LL, 0x1);
+ } else {
+ mmp_sspa_write_reg(sspa, SSPA_RXCTL, sspa_ctrl);
+ mmp_sspa_write_reg(sspa, SSPA_RXFIFO_UL, 0x0);
+ }
+
+ dma_params = &sspa_priv->dma_params[substream->stream];
+ dma_params->dev_addr = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ (sspa->phys_base + SSPA_TXD) :
+ (sspa->phys_base + SSPA_RXD);
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_params);
+ return 0;
+}
+
+static int mmp_sspa_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(dai);
+ struct ssp_device *sspa = sspa_priv->sspa;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /*
+ * whatever playback or capture, must enable rx.
+ * this is a hw issue, so need check if rx has been
+ * enabled or not; if has been enabled by another
+ * stream, do not enable again.
+ */
+ if (!sspa_priv->running_cnt)
+ mmp_sspa_rx_enable(sspa);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mmp_sspa_tx_enable(sspa);
+
+ sspa_priv->running_cnt++;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ sspa_priv->running_cnt--;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mmp_sspa_tx_disable(sspa);
+
+ /* have no capture stream, disable rx port */
+ if (!sspa_priv->running_cnt)
+ mmp_sspa_rx_disable(sspa);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int mmp_sspa_probe(struct snd_soc_dai *dai)
+{
+ struct sspa_priv *priv = dev_get_drvdata(dai->dev);
+
+ snd_soc_dai_set_drvdata(dai, priv);
+ return 0;
+
+}
+
+#define MMP_SSPA_RATES SNDRV_PCM_RATE_8000_192000
+#define MMP_SSPA_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops mmp_sspa_dai_ops = {
+ .startup = mmp_sspa_startup,
+ .shutdown = mmp_sspa_shutdown,
+ .trigger = mmp_sspa_trigger,
+ .hw_params = mmp_sspa_hw_params,
+ .set_sysclk = mmp_sspa_set_dai_sysclk,
+ .set_pll = mmp_sspa_set_dai_pll,
+ .set_fmt = mmp_sspa_set_dai_fmt,
+};
+
+struct snd_soc_dai_driver mmp_sspa_dai = {
+ .probe = mmp_sspa_probe,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 128,
+ .rates = MMP_SSPA_RATES,
+ .formats = MMP_SSPA_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MMP_SSPA_RATES,
+ .formats = MMP_SSPA_FORMATS,
+ },
+ .ops = &mmp_sspa_dai_ops,
+};
+
+static __devinit int asoc_mmp_sspa_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct sspa_priv *priv;
+ struct resource *res;
+
+ priv = devm_kzalloc(&pdev->dev,
+ sizeof(struct sspa_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->sspa = devm_kzalloc(&pdev->dev,
+ sizeof(struct ssp_device), GFP_KERNEL);
+ if (priv->sspa == NULL)
+ return -ENOMEM;
+
+ priv->dma_params = devm_kzalloc(&pdev->dev,
+ 2 * sizeof(struct pxa2xx_pcm_dma_params), GFP_KERNEL);
+ if (priv->dma_params == NULL)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL)
+ return -ENOMEM;
+
+ priv->sspa->mmio_base = devm_request_and_ioremap(&pdev->dev, res);
+ if (priv->sspa->mmio_base == NULL)
+ return -ENODEV;
+
+ priv->sspa->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(priv->sspa->clk)) {
+ dev_err(&pdev->dev, "unable to get clock!\n");
+ ret = PTR_ERR(priv->sspa->clk);
+ return ret;
+ }
+
+ priv->dai_fmt = (unsigned int) -1;
+ platform_set_drvdata(pdev, priv);
+ return snd_soc_register_dai(&pdev->dev, &mmp_sspa_dai);
+}
+
+static int __devexit asoc_mmp_sspa_remove(struct platform_device *pdev)
+{
+ struct sspa_priv *priv = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ clk_put(priv->sspa->clk);
+ return 0;
+}
+
+static struct platform_driver asoc_mmp_sspa_driver = {
+ .driver = {
+ .name = "mmp-sspa-dai",
+ .owner = THIS_MODULE,
+ },
+ .probe = asoc_mmp_sspa_probe,
+ .remove = __devexit_p(asoc_mmp_sspa_remove),
+};
+
+
+static int __init mmp_sspa_modinit(void)
+{
+ audio_clk = clk_get(NULL, "mmp-audio");
+ if (IS_ERR(audio_clk))
+ return PTR_ERR(audio_clk);
+
+ sysclk = clk_get(NULL, "mmp-sysclk");
+ if (IS_ERR(sysclk))
+ return PTR_ERR(sysclk);
+ clk_enable(audio_clk);
+ return platform_driver_register(&asoc_mmp_sspa_driver);
+}
+module_init(mmp_sspa_modinit);
+
+static void __exit mmp_sspa_exit(void)
+{
+ clk_disable(audio_clk);
+ clk_put(sysclk);
+ clk_put(audio_clk);
+ platform_driver_unregister(&asoc_mmp_sspa_driver);
+}
+module_exit(mmp_sspa_exit);
+
+MODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
+MODULE_DESCRIPTION("MMP SSPA SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/mmp-sspa.h b/sound/soc/pxa/mmp-sspa.h
new file mode 100644
index 0000000..ea365cb
--- /dev/null
+++ b/sound/soc/pxa/mmp-sspa.h
@@ -0,0 +1,92 @@
+/*
+ * linux/sound/soc/pxa/mmp-sspa.h
+ *
+ * Copyright (C) 2011 Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#ifndef _MMP_SSPA_H
+#define _MMP_SSPA_H
+
+/*
+ * SSPA Registers
+ */
+#define SSPA_RXD (0x00)
+#define SSPA_RXID (0x04)
+#define SSPA_RXCTL (0x08)
+#define SSPA_RXSP (0x0c)
+#define SSPA_RXFIFO_UL (0x10)
+#define SSPA_RXINT_MASK (0x14)
+#define SSPA_RXC (0x18)
+#define SSPA_RXFIFO_NOFS (0x1c)
+#define SSPA_RXFIFO_SIZE (0x20)
+
+#define SSPA_TXD (0x80)
+#define SSPA_TXID (0x84)
+#define SSPA_TXCTL (0x88)
+#define SSPA_TXSP (0x8c)
+#define SSPA_TXFIFO_LL (0x90)
+#define SSPA_TXINT_MASK (0x94)
+#define SSPA_TXC (0x98)
+#define SSPA_TXFIFO_NOFS (0x9c)
+#define SSPA_TXFIFO_SIZE (0xa0)
+
+/* SSPA Control Register */
+#define SSPA_CTL_XPH (1 << 31) /* Read Phase */
+#define SSPA_CTL_XFIG (1 << 15) /* Transmit Zeros when FIFO Empty */
+#define SSPA_CTL_JST (1 << 3) /* Audio Sample Justification */
+#define SSPA_CTL_XFRLEN2_MASK (7 << 24)
+#define SSPA_CTL_XFRLEN2(x) ((x) << 24) /* Transmit Frame Length in Phase 2 */
+#define SSPA_CTL_XWDLEN2_MASK (7 << 21)
+#define SSPA_CTL_XWDLEN2(x) ((x) << 21) /* Transmit Word Length in Phase 2 */
+#define SSPA_CTL_XDATDLY(x) ((x) << 19) /* Tansmit Data Delay */
+#define SSPA_CTL_XSSZ2_MASK (7 << 16)
+#define SSPA_CTL_XSSZ2(x) ((x) << 16) /* Transmit Sample Audio Size */
+#define SSPA_CTL_XFRLEN1_MASK (7 << 8)
+#define SSPA_CTL_XFRLEN1(x) ((x) << 8) /* Transmit Frame Length in Phase 1 */
+#define SSPA_CTL_XWDLEN1_MASK (7 << 5)
+#define SSPA_CTL_XWDLEN1(x) ((x) << 5) /* Transmit Word Length in Phase 1 */
+#define SSPA_CTL_XSSZ1_MASK (7 << 0)
+#define SSPA_CTL_XSSZ1(x) ((x) << 0) /* XSSZ1 */
+
+#define SSPA_CTL_8_BITS (0x0) /* Sample Size */
+#define SSPA_CTL_12_BITS (0x1)
+#define SSPA_CTL_16_BITS (0x2)
+#define SSPA_CTL_20_BITS (0x3)
+#define SSPA_CTL_24_BITS (0x4)
+#define SSPA_CTL_32_BITS (0x5)
+
+/* SSPA Serial Port Register */
+#define SSPA_SP_WEN (1 << 31) /* Write Configuration Enable */
+#define SSPA_SP_MSL (1 << 18) /* Master Slave Configuration */
+#define SSPA_SP_CLKP (1 << 17) /* CLKP Polarity Clock Edge Select */
+#define SSPA_SP_FSP (1 << 16) /* FSP Polarity Clock Edge Select */
+#define SSPA_SP_FFLUSH (1 << 2) /* FIFO Flush */
+#define SSPA_SP_S_RST (1 << 1) /* Active High Reset Signal */
+#define SSPA_SP_S_EN (1 << 0) /* Serial Clock Domain Enable */
+#define SSPA_SP_FWID(x) ((x) << 20) /* Frame-Sync Width */
+#define SSPA_TXSP_FPER(x) ((x) << 4) /* Frame-Sync Active */
+
+/* sspa clock sources */
+#define MMP_SSPA_CLK_PLL 0
+#define MMP_SSPA_CLK_VCXO 1
+#define MMP_SSPA_CLK_AUDIO 3
+
+/* sspa pll id */
+#define MMP_SYSCLK 0
+#define MMP_SSPA_CLK 1
+
+#endif /* _MMP_SSPA_H */
--
1.7.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v1 3/5] ASOC: mmp: add sspa support
2012-06-04 6:37 ` [PATCH v1 3/5] ASOC: mmp: add sspa support Zhangfei Gao
@ 2012-06-07 22:10 ` Mark Brown
2012-06-08 13:28 ` [alsa-devel] " zhangfei gao
0 siblings, 1 reply; 20+ messages in thread
From: Mark Brown @ 2012-06-07 22:10 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, Jun 04, 2012 at 02:37:29PM +0800, Zhangfei Gao wrote:
> The SSPA is a configurable multi-channel audio serial (TDM) interface.
> It's configurable at runtime to support up to 128 channels and the
> number of bits per sample: 8, 12, 16, 20, 24 and 32 bits. It also
> support stereo format: I2S, left-justified or right-justified.
This looks mostly good (though you want to check your indentation a bit)
but...
> +static int __init mmp_sspa_modinit(void)
> +{
> + audio_clk = clk_get(NULL, "mmp-audio");
> + if (IS_ERR(audio_clk))
> + return PTR_ERR(audio_clk);
> +
> + sysclk = clk_get(NULL, "mmp-sysclk");
> + if (IS_ERR(sysclk))
> + return PTR_ERR(sysclk);
> + clk_enable(audio_clk);
You should be doing this stuff from the driver, not from the module init
(and ideally you'd be managing the audio clock dynamically). Even if
it's the same clock every time in current silicon it's still a good idea
to do this from a quality of implementation point of view.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120608/7c7f2870/attachment-0001.sig>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 3/5] ASOC: mmp: add sspa support
2012-06-07 22:10 ` Mark Brown
@ 2012-06-08 13:28 ` zhangfei gao
0 siblings, 0 replies; 20+ messages in thread
From: zhangfei gao @ 2012-06-08 13:28 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, Jun 8, 2012 at 6:10 AM, Mark Brown
<broonie@opensource.wolfsonmicro.com> wrote:
> On Mon, Jun 04, 2012 at 02:37:29PM +0800, Zhangfei Gao wrote:
>> The SSPA is a configurable multi-channel audio serial (TDM) interface.
>> It's configurable at runtime to support up to 128 channels and the
>> number of bits per sample: 8, 12, 16, 20, 24 and 32 bits. It also
>> support stereo format: I2S, left-justified or right-justified.
>
> This looks mostly good (though you want to check your indentation a bit)
> but...
>
>> +static int __init mmp_sspa_modinit(void)
>> +{
>> + ? ? audio_clk = clk_get(NULL, "mmp-audio");
>> + ? ? if (IS_ERR(audio_clk))
>> + ? ? ? ? ? ? return PTR_ERR(audio_clk);
>> +
>> + ? ? sysclk = clk_get(NULL, "mmp-sysclk");
>> + ? ? if (IS_ERR(sysclk))
>> + ? ? ? ? ? ? return PTR_ERR(sysclk);
>> + ? ? clk_enable(audio_clk);
>
> You should be doing this stuff from the driver, not from the module init
> (and ideally you'd be managing the audio clock dynamically). ?Even if
> it's the same clock every time in current silicon it's still a good idea
> to do this from a quality of implementation point of view.
Thanks, will remove the module init, and move clk staff to probe.
However, dynamically control audio_clk make audio subsystem works abnormally.
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v1 4/5] ASoC: add mmp brownstone support
2012-06-04 6:37 [PATCH v1 0/5] mmp audio support Zhangfei Gao
` (2 preceding siblings ...)
2012-06-04 6:37 ` [PATCH v1 3/5] ASOC: mmp: add sspa support Zhangfei Gao
@ 2012-06-04 6:37 ` Zhangfei Gao
2012-06-07 22:13 ` Mark Brown
2012-06-04 6:37 ` [PATCH v1 5/5] ASoC: add ttc-dkb machine support Zhangfei Gao
4 siblings, 1 reply; 20+ messages in thread
From: Zhangfei Gao @ 2012-06-04 6:37 UTC (permalink / raw)
To: linux-arm-kernel
Adds Alsa audio platform driver for mmp brownstone machine
Signed-off-by: Zhangfei Gao <zhangfei.gao@marvell.com>
Signed-off-by: Leo Yan <leoy@marvell.com>
---
sound/soc/pxa/Kconfig | 10 +++
sound/soc/pxa/Makefile | 2 +
sound/soc/pxa/brownstone.c | 178 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 190 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/pxa/brownstone.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index 6c3d00b..d389fd5 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -206,3 +206,13 @@ config SND_PXA2XX_SOC_IMOTE2
help
Say Y if you want to add support for SoC audio on the
IMote 2.
+
+config SND_MMP_SOC_BROWNSTONE
+ tristate "SoC Audio support for Marvell Brownstone"
+ depends on SND_MMP_SOC && MACH_BROWNSTONE
+ select SND_MMP_SOC_SSPA
+ select MFD_WM8994
+ select SND_SOC_WM8994
+ help
+ Say Y if you want to add support for SoC audio on the
+ Marvell Brownstone reference platform.
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
index 07b8417..c12aa2a 100644
--- a/sound/soc/pxa/Makefile
+++ b/sound/soc/pxa/Makefile
@@ -32,6 +32,7 @@ snd-soc-mioa701-objs := mioa701_wm9713.o
snd-soc-z2-objs := z2.o
snd-soc-imote2-objs := imote2.o
snd-soc-raumfeld-objs := raumfeld.o
+snd-soc-brownstone-objs := brownstone.o
obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o
obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o
@@ -51,3 +52,4 @@ obj-$(CONFIG_SND_SOC_TAVOREVB3) += snd-soc-tavorevb3.o
obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o
obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o
obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o
+obj-$(CONFIG_SND_MMP_SOC_BROWNSTONE) += snd-soc-brownstone.o
diff --git a/sound/soc/pxa/brownstone.c b/sound/soc/pxa/brownstone.c
new file mode 100644
index 0000000..7aa7064
--- /dev/null
+++ b/sound/soc/pxa/brownstone.c
@@ -0,0 +1,178 @@
+/*
+ * linux/sound/soc/pxa/brownstone.c
+ *
+ * Copyright (C) 2011 Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/wm8994.h"
+#include "mmp-sspa.h"
+
+static const struct snd_kcontrol_new brownstone_dapm_control[] = {
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+static const struct snd_soc_dapm_widget brownstone_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route brownstone_audio_map[] = {
+ {"Ext Spk", NULL, "SPKOUTLP"},
+ {"Ext Spk", NULL, "SPKOUTLN"},
+ {"Ext Spk", NULL, "SPKOUTRP"},
+ {"Ext Spk", NULL, "SPKOUTRN"},
+
+ {"Headset Stereophone", NULL, "HPOUT1L"},
+ {"Headset Stereophone", NULL, "HPOUT1R"},
+
+ {"IN1RN", NULL, "Headset Mic"},
+
+ {"DMIC1DAT", NULL, "MICBIAS1"},
+ {"MICBIAS1", NULL, "Main Mic"},
+};
+
+static int brownstone_wm8994_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ ret = snd_soc_add_card_controls(dapm->card, brownstone_dapm_control,
+ ARRAY_SIZE(brownstone_dapm_control));
+ if (ret < 0)
+ return ret;
+
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ snd_soc_dapm_enable_pin(dapm, "Headset Stereophone");
+ snd_soc_dapm_enable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_enable_pin(dapm, "Main Mic");
+
+ /* set endpoints to not connected */
+ snd_soc_dapm_nc_pin(dapm, "HPOUT2P");
+ snd_soc_dapm_nc_pin(dapm, "HPOUT2N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT1N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT1P");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT2N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT2P");
+ snd_soc_dapm_nc_pin(dapm, "IN1LN");
+ snd_soc_dapm_nc_pin(dapm, "IN1LP");
+ snd_soc_dapm_nc_pin(dapm, "IN1RP");
+ snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN");
+ snd_soc_dapm_nc_pin(dapm, "IN2RN");
+ snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP");
+ snd_soc_dapm_nc_pin(dapm, "IN2LN");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static int brownstone_wm8994_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int freq_out, sspa_mclk, sysclk;
+ int sspa_div;
+
+ if (params_rate(params) > 11025) {
+ freq_out = params_rate(params) * 512;
+ sysclk = params_rate(params) * 256;
+ sspa_mclk = params_rate(params) * 64;
+ } else {
+ freq_out = params_rate(params) * 1024;
+ sysclk = params_rate(params) * 512;
+ sspa_mclk = params_rate(params) * 64;
+ }
+ sspa_div = freq_out;
+ do_div(sspa_div, sspa_mclk);
+
+ snd_soc_dai_set_sysclk(cpu_dai, MMP_SSPA_CLK_AUDIO, freq_out, 0);
+ snd_soc_dai_set_pll(cpu_dai, MMP_SYSCLK, 0, freq_out, sysclk);
+ snd_soc_dai_set_pll(cpu_dai, MMP_SSPA_CLK, 0, freq_out, sspa_mclk);
+
+ /* set wm8994 sysclk */
+ snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1, sysclk, 0);
+
+ return 0;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops brownstone_ops = {
+ .hw_params = brownstone_wm8994_hw_params,
+};
+
+static struct snd_soc_dai_link brownstone_wm8994_dai[] = {
+{
+ .name = "WM8994",
+ .stream_name = "WM8994 HiFi",
+ .cpu_dai_name = "mmp-sspa-dai.0",
+ .codec_dai_name = "wm8994-aif1",
+ .platform_name = "mmp-pcm-audio",
+ .codec_name = "wm8994-codec",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &brownstone_ops,
+ .init = brownstone_wm8994_init,
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card brownstone = {
+ .name = "brownstone",
+ .dai_link = brownstone_wm8994_dai,
+ .num_links = ARRAY_SIZE(brownstone_wm8994_dai),
+
+ .dapm_widgets = brownstone_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(brownstone_dapm_widgets),
+ .dapm_routes = brownstone_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(brownstone_audio_map),
+};
+
+static int __devinit brownstone_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ brownstone.dev = &pdev->dev;
+ ret = snd_soc_register_card(&brownstone);
+ if (ret)
+ dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+ ret);
+ return ret;
+}
+
+static int __devexit brownstone_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&brownstone);
+ return 0;
+}
+
+static struct platform_driver mmp_driver = {
+ .driver = {
+ .name = "brownstone-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = brownstone_probe,
+ .remove = __devexit_p(brownstone_remove),
+};
+
+module_platform_driver(mmp_driver);
+
+MODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
+MODULE_DESCRIPTION("ALSA SoC Brownstone");
+MODULE_LICENSE("GPL");
--
1.7.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v1 4/5] ASoC: add mmp brownstone support
2012-06-04 6:37 ` [PATCH v1 4/5] ASoC: add mmp brownstone support Zhangfei Gao
@ 2012-06-07 22:13 ` Mark Brown
2012-06-08 13:24 ` [alsa-devel] " zhangfei gao
0 siblings, 1 reply; 20+ messages in thread
From: Mark Brown @ 2012-06-07 22:13 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, Jun 04, 2012 at 02:37:30PM +0800, Zhangfei Gao wrote:
One small thing here...
> + ret = snd_soc_add_card_controls(dapm->card, brownstone_dapm_control,
> + ARRAY_SIZE(brownstone_dapm_control));
> + if (ret < 0)
> + return ret;
You can just set these up with the controls member of the card struct.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120608/bdcc3c68/attachment.sig>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [alsa-devel] [PATCH v1 4/5] ASoC: add mmp brownstone support
2012-06-07 22:13 ` Mark Brown
@ 2012-06-08 13:24 ` zhangfei gao
0 siblings, 0 replies; 20+ messages in thread
From: zhangfei gao @ 2012-06-08 13:24 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, Jun 8, 2012 at 6:13 AM, Mark Brown
<broonie@opensource.wolfsonmicro.com> wrote:
> On Mon, Jun 04, 2012 at 02:37:30PM +0800, Zhangfei Gao wrote:
>
> One small thing here...
>
>> + ? ? ret = snd_soc_add_card_controls(dapm->card, brownstone_dapm_control,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?ARRAY_SIZE(brownstone_dapm_control));
>> + ? ? if (ret < 0)
>> + ? ? ? ? ? ? return ret;
>
> You can just set these up with the controls member of the card struct.
Great, it will make it easier, thanks.
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v1 5/5] ASoC: add ttc-dkb machine support
2012-06-04 6:37 [PATCH v1 0/5] mmp audio support Zhangfei Gao
` (3 preceding siblings ...)
2012-06-04 6:37 ` [PATCH v1 4/5] ASoC: add mmp brownstone support Zhangfei Gao
@ 2012-06-04 6:37 ` Zhangfei Gao
4 siblings, 0 replies; 20+ messages in thread
From: Zhangfei Gao @ 2012-06-04 6:37 UTC (permalink / raw)
To: linux-arm-kernel
From: Qiao Zhou <zhouqiao@marvell.com>
add ttc-dkb machine support for pxa910. It uses 88pm8607 as codec
dai, mmp-pcm as platform and pxa-ssp as cpu dai.
Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
---
sound/soc/pxa/Kconfig | 20 ++++++
sound/soc/pxa/Makefile | 2 +
sound/soc/pxa/ttc-dkb.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 195 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/pxa/ttc-dkb.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index d389fd5..4d2e46f 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -150,6 +150,26 @@ config SND_SOC_TAVOREVB3
Say Y if you want to add support for SoC audio on the
Marvell Saarb reference platform.
+config SND_PXA910_SOC
+ tristate "SoC Audio for Marvell PXA910 chip"
+ depends on ARCH_MMP && SND
+ select SND_PCM
+ help
+ Say Y if you want to add support for SoC audio on the
+ Marvell PXA910 reference platform.
+
+config SND_SOC_TTC_DKB
+ bool "SoC Audio support for TTC DKB"
+ depends on SND_PXA910_SOC && MACH_TTC_DKB
+ select PXA_SSP
+ select SND_PXA_SOC_SSP
+ select SND_MMP_SOC
+ select MFD_88PM860X
+ select SND_SOC_88PM860X
+ help
+ Say Y if you want to add support for SoC audio on TTC DKB
+
+
config SND_SOC_ZYLONITE
tristate "SoC Audio support for Marvell Zylonite"
depends on SND_PXA2XX_SOC && MACH_ZYLONITE
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
index c12aa2a..d8a265d 100644
--- a/sound/soc/pxa/Makefile
+++ b/sound/soc/pxa/Makefile
@@ -33,6 +33,7 @@ snd-soc-z2-objs := z2.o
snd-soc-imote2-objs := imote2.o
snd-soc-raumfeld-objs := raumfeld.o
snd-soc-brownstone-objs := brownstone.o
+snd-soc-ttc-dkb-objs := ttc-dkb.o
obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o
obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o
@@ -53,3 +54,4 @@ obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o
obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o
obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o
obj-$(CONFIG_SND_MMP_SOC_BROWNSTONE) += snd-soc-brownstone.o
+obj-$(CONFIG_SND_SOC_TTC_DKB) += snd-soc-ttc-dkb.o
diff --git a/sound/soc/pxa/ttc-dkb.c b/sound/soc/pxa/ttc-dkb.c
new file mode 100644
index 0000000..ac6bf33
--- /dev/null
+++ b/sound/soc/pxa/ttc-dkb.c
@@ -0,0 +1,173 @@
+/*
+ * linux/sound/soc/pxa/ttc_dkb.c
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <asm/mach-types.h>
+#include <sound/pcm_params.h>
+#include "../codecs/88pm860x-codec.h"
+
+static struct snd_soc_jack hs_jack, mic_jack;
+
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE, },
+};
+
+static struct snd_soc_jack_pin mic_jack_pins[] = {
+ { .pin = "Headset Mic 2", .mask = SND_JACK_MICROPHONE, },
+};
+
+/* ttc machine dapm widgets */
+static const struct snd_soc_dapm_widget ttc_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+ SND_SOC_DAPM_LINE("Lineout Out 1", NULL),
+ SND_SOC_DAPM_LINE("Lineout Out 2", NULL),
+ SND_SOC_DAPM_SPK("Ext Speaker", NULL),
+ SND_SOC_DAPM_MIC("Ext Mic 1", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic 2", NULL),
+ SND_SOC_DAPM_MIC("Ext Mic 3", NULL),
+};
+
+/* ttc machine audio map */
+static const struct snd_soc_dapm_route ttc_audio_map[] = {
+ {"Headset Stereophone", NULL, "HS1"},
+ {"Headset Stereophone", NULL, "HS2"},
+
+ {"Ext Speaker", NULL, "LSP"},
+ {"Ext Speaker", NULL, "LSN"},
+
+ {"Lineout Out 1", NULL, "LINEOUT1"},
+ {"Lineout Out 2", NULL, "LINEOUT2"},
+
+ {"MIC1P", NULL, "Mic1 Bias"},
+ {"MIC1N", NULL, "Mic1 Bias"},
+ {"Mic1 Bias", NULL, "Ext Mic 1"},
+
+ {"MIC2P", NULL, "Mic1 Bias"},
+ {"MIC2N", NULL, "Mic1 Bias"},
+ {"Mic1 Bias", NULL, "Headset Mic 2"},
+
+ {"MIC3P", NULL, "Mic3 Bias"},
+ {"MIC3N", NULL, "Mic3 Bias"},
+ {"Mic3 Bias", NULL, "Ext Mic 3"},
+};
+
+static int ttc_pm860x_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ /* connected pins */
+ snd_soc_dapm_enable_pin(dapm, "Ext Speaker");
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic 1");
+ snd_soc_dapm_enable_pin(dapm, "Ext Mic 3");
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic2");
+ snd_soc_dapm_disable_pin(dapm, "Headset Stereophone");
+
+ /* Headset jack detection */
+ snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE
+ | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2,
+ &hs_jack);
+ snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ snd_soc_jack_new(codec, "Microphone Jack", SND_JACK_MICROPHONE,
+ &mic_jack);
+ snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins),
+ mic_jack_pins);
+
+ /* headphone, microphone detection & headset short detection */
+ pm860x_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADPHONE,
+ SND_JACK_BTN_0, SND_JACK_BTN_1, SND_JACK_BTN_2);
+ pm860x_mic_jack_detect(codec, &hs_jack, SND_JACK_MICROPHONE);
+
+ return 0;
+}
+
+/* ttc/td-dkb digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link ttc_pm860x_hifi_dai[] = {
+{
+ .name = "88pm860x i2s",
+ .stream_name = "audio playback",
+ .codec_name = "88pm860x-codec",
+ .platform_name = "mmp-pcm-audio",
+ .cpu_dai_name = "pxa-ssp-dai.1",
+ .codec_dai_name = "88pm860x-i2s",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .init = ttc_pm860x_init,
+},
+};
+
+/* ttc/td audio machine driver */
+static struct snd_soc_card ttc_dkb_card = {
+ .name = "ttc-dkb-hifi",
+ .dai_link = ttc_pm860x_hifi_dai,
+ .num_links = ARRAY_SIZE(ttc_pm860x_hifi_dai),
+
+ .dapm_widgets = ttc_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ttc_dapm_widgets),
+ .dapm_routes = ttc_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(ttc_audio_map),
+};
+
+static int __devinit ttc_dkb_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &ttc_dkb_card;
+ int ret;
+
+ card->dev = &pdev->dev;
+
+ ret = snd_soc_register_card(card);
+ if (ret)
+ dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+ ret);
+
+ return ret;
+}
+
+static int __devexit ttc_dkb_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_card(card);
+
+ return 0;
+}
+
+static struct platform_driver ttc_dkb_driver = {
+ .driver = {
+ .name = "ttc-dkb-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = ttc_dkb_probe,
+ .remove = __devexit_p(ttc_dkb_remove),
+};
+
+module_platform_driver(ttc_dkb_driver);
+
+/* Module information */
+MODULE_AUTHOR("Qiao Zhou, <zhouqiao@marvell.com>");
+MODULE_DESCRIPTION("ALSA SoC TTC DKB");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ttc-dkb-audio");
--
1.7.1
^ permalink raw reply related [flat|nested] 20+ messages in thread